From 7cec936bf10ea2f0c2f56c00839043ad9369d99b Mon Sep 17 00:00:00 2001 From: luke Date: Mon, 29 Aug 2005 21:30:45 +0000 Subject: [PATCH 0001/3753] moving things to the trunk git-svn-id: http://reductivelabs.com/svn/facter/trunk@58 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- CHANGES | 6 + COPYING | 339 +++++++++++++++++++ INSTALL | 2 + LICENSE | 17 + README | 9 + Rakefile | 277 +++++++++++++++ TODO | 4 + bin/facter | 72 ++++ etc/facter.conf | 5 + install.rb | 289 ++++++++++++++++ lib/facter.rb | 766 ++++++++++++++++++++++++++++++++++++++++++ tests/tc_facterbin.rb | 35 ++ tests/tc_simple.rb | 268 +++++++++++++++ 13 files changed, 2089 insertions(+) create mode 100644 CHANGES create mode 100644 COPYING create mode 100644 INSTALL create mode 100644 LICENSE create mode 100644 README create mode 100644 Rakefile create mode 100644 TODO create mode 100755 bin/facter create mode 100644 etc/facter.conf create mode 100644 install.rb create mode 100644 lib/facter.rb create mode 100755 tests/tc_facterbin.rb create mode 100644 tests/tc_simple.rb diff --git a/CHANGES b/CHANGES new file mode 100644 index 0000000000..f8ad814826 --- /dev/null +++ b/CHANGES @@ -0,0 +1,6 @@ +2.0: + Rewrote entirely. It's much simpler to use, and now supports + adding new fact resolution mechanisms at run-time. + +1.0b1: + Initial release. diff --git a/COPYING b/COPYING new file mode 100644 index 0000000000..e77696ae8d --- /dev/null +++ b/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 675 Mass Ave, Cambridge, MA 02139, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000000..de89f69822 --- /dev/null +++ b/INSTALL @@ -0,0 +1,2 @@ +Unforantely, no install.rb yet. Hopefully soon. Just copy the libs into +your main ruby library directory. Or something. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000..308f4e000d --- /dev/null +++ b/LICENSE @@ -0,0 +1,17 @@ +Facter - Host Fact Detection and Reporting. Copyright (C) 2005 Reductive Labs LLC + +Reductive Labs can be contacted at: info@reductivelabs.com + +This program and entire repository is free software; you can +redistribute it and/or modify it under the terms of the GNU +General Public License as published by the Free Software +Foundation; either version 2 of the License, or any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA diff --git a/README b/README new file mode 100644 index 0000000000..54627955df --- /dev/null +++ b/README @@ -0,0 +1,9 @@ +This package is largely meant to be a library for collecting facts about your +system. These facts are mostly strings (i.e., not numbers), and are things +like the output of 'uname', public ssh and cfengine keys, the number of +processors, etc. + +It currently cannot collect very much information, but it is architected to be +both OS and OS version specific. + +See bin/facter or http://madstop.com/svn/enhost for an example of the interface. diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000000..0cd2889659 --- /dev/null +++ b/Rakefile @@ -0,0 +1,277 @@ +# Rakefile for facter + +begin + require 'rubygems' + require 'rake/gempackagetask' +rescue Exception + nil +end + +require 'rake/clean' +require 'rake/testtask' + +require 'rake/rdoctask' +#CLEAN.include('**/*.o') +CLOBBER.include('doc/*') + +def announce(msg='') + STDERR.puts msg +end + +# Determine the current version + +if `ruby -Ilib ./bin/facter --version` =~ /\S+$/ + CURRENT_VERSION = $& +else + CURRENT_VERSION = "0.0.0" +end + +if ENV['REL'] + PKG_VERSION = ENV['REL'] +else + PKG_VERSION = CURRENT_VERSION +end + + +# The default task is run if rake is given no explicit arguments. + +desc "Default Task" +task :default => :unittests + +# Test Tasks --------------------------------------------------------- + +task :u => :unittests +task :a => :alltests + +task :alltests => :unittests + +Rake::TestTask.new(:unittests) do |t| + t.test_files = FileList['tests/tc*.rb'] + t.warning = true + t.verbose = false +end + +# SVN Tasks ---------------------------------------------------------- +# ... none. + +# Install rake using the standard install.rb script. + +desc "Install the application" +task :install do + ruby "install.rb" +end + +# Create a task to build the RDOC documentation tree. + +rd = Rake::RDocTask.new("rdoc") { |rdoc| + rdoc.rdoc_dir = 'html' + rdoc.template = 'css2' + rdoc.title = "Facter" + rdoc.options << '--line-numbers' << '--inline-source' << '--main' << 'README' + rdoc.rdoc_files.include('README', 'LICENSE', 'TODO', 'CHANGES') + rdoc.rdoc_files.include('lib/**/*.rb', 'doc/**/*.rdoc') +} + +# ==================================================================== +# Create a task that will package the Rake software into distributable +# tar, zip and gem files. + +PKG_FILES = FileList[ + 'install.rb', + '[A-Z]*', + 'bin/**/*', + 'lib/**/*.rb', + 'test/**/*.rb', + 'doc/**/*', + 'etc/*' +] +PKG_FILES.delete_if {|item| item.include?(".svn")} + +if ! defined?(Gem) + puts "Package Target requires RubyGEMs" +else + spec = Gem::Specification.new do |s| + + #### Basic information. + + s.name = 'facter' + s.version = PKG_VERSION + s.summary = "Facter collects Operating system facts." + s.description = <<-EOF + Facter is a module for collecting simple facts about a host + Operating system. + EOF + + #### Dependencies and requirements. + + #s.add_dependency('log4r', '> 1.0.4') + #s.requirements << "" + + s.files = PKG_FILES.to_a + + #### Load-time details: library and application (you will need one or both). + + s.require_path = 'lib' # Use these for libraries. + + s.bindir = "bin" # Use these for applications. + s.executables = ["facter"] + s.default_executable = "facter" + + #### Documentation and testing. + + s.has_rdoc = true + s.extra_rdoc_files = rd.rdoc_files.reject { |fn| fn =~ /\.rb$/ }.to_a + s.rdoc_options << + '--title' << 'Facter' << + '--main' << 'README' << + '--line-numbers' + + #### Author and project details. + + s.author = "Luke Kanies" + s.email = "dev@reductivelabs.com" + s.homepage = "http://reductivelabs.com/projects/facter" + #s.rubyforge_project = "facter" + end + + Rake::GemPackageTask.new(spec) do |pkg| + #pkg.need_zip = true + pkg.need_tar = true + end +end + +# Misc tasks ========================================================= + +#ARCHIVEDIR = '/...' + +#task :archive => [:package] do +# cp FileList["pkg/*.tgz", "pkg/*.zip", "pkg/*.gem"], ARCHIVEDIR +#end + +# Define an optional publish target in an external file. If the +# publish.rf file is not found, the publish targets won't be defined. + +#load "publish.rf" if File.exist? "publish.rf" + +# Support Tasks ------------------------------------------------------ + +def egrep(pattern) + Dir['**/*.rb'].each do |fn| + count = 0 + open(fn) do |f| + while line = f.gets + count += 1 + if line =~ pattern + puts "#{fn}:#{count}:#{line}" + end + end + end + end +end + +desc "Look for TODO and FIXME tags in the code" +task :todo do + egrep "/#.*(FIXME|TODO|TBD)/" +end + +#desc "Look for Debugging print lines" +#task :dbg do +# egrep /\bDBG|\bbreakpoint\b/ +#end + +#desc "List all ruby files" +#task :rubyfiles do +# puts Dir['**/*.rb'].reject { |fn| fn =~ /^pkg/ } +# puts Dir['bin/*'].reject { |fn| fn =~ /CVS|(~$)|(\.rb$)/ } +#end + +# -------------------------------------------------------------------- +# Creating a release + +desc "Make a new release" +task :release => [ + :prerelease, + :clobber, + :alltests, + :update_version, + :package, + :tag] do + + announce + announce "**************************************************************" + announce "* Release #{PKG_VERSION} Complete." + announce "* Packages ready to upload." + announce "**************************************************************" + announce +end + +# Validate that everything is ready to go for a release. +task :prerelease do + announce + announce "**************************************************************" + announce "* Making RubyGem Release #{PKG_VERSION}" + announce "* (current version #{CURRENT_VERSION})" + announce "**************************************************************" + announce + + # Is a release number supplied? + unless ENV['REL'] + fail "Usage: rake release REL=x.y.z [REUSE=tag_suffix]" + end + + # Is the release different than the current release. + # (or is REUSE set?) + if PKG_VERSION == CURRENT_VERSION && ! ENV['REUSE'] + fail "Current version is #{PKG_VERSION}, must specify REUSE=tag_suffix to reuse version" + end + + # Are all source files checked in? + if ENV['RELTEST'] + announce "Release Task Testing, skipping checked-in file test" + else + announce "Checking for unchecked-in files..." + data = `svn -q update` + unless data =~ /^$/ + fail "SVN update is not clean ... do you have unchecked-in files?" + end + announce "No outstanding checkins found ... OK" + end +end + +task :update_version => [:prerelease] do + if PKG_VERSION == CURRENT_VERSION + announce "No version change ... skipping version update" + else + announce "Updating Facter version to #{PKG_VERSION}" + open("lib/facter.rb") do |rakein| + open("lib/facter.rb.new", "w") do |rakeout| + rakein.each do |line| + if line =~ /^FACTERVERSION\s*=\s*/ + rakeout.puts "FACTERVERSION = '#{PKG_VERSION}'" + else + rakeout.puts line + end + end + end + end + mv "lib/facter.rb.new", "lib/facter.rb" + if ENV['RELTEST'] + announce "Release Task Testing, skipping commiting of new version" + else + sh %{svn commit -m "Updated to version #{PKG_VERSION}" lib/facter.rb} + end + end +end + +desc "Tag all the SVN files with the latest release number (REL=x.y.z)" +task :tag => [:prerelease] do + reltag = "REL_#{PKG_VERSION.gsub(/\./, '_')}" + reltag << ENV['REUSE'].gsub(/\./, '_') if ENV['REUSE'] + announce "Tagging SVN copy with [#{reltag}]" + if ENV['RELTEST'] + announce "Release Task Testing, skipping SVN tagging" + else + #sh %{svn copy trunk/ tags/#{reltag}} + end +end + diff --git a/TODO b/TODO new file mode 100644 index 0000000000..9389e45f67 --- /dev/null +++ b/TODO @@ -0,0 +1,4 @@ +More documentation. + +The ability to specify fact names with strings or symbols; right now, only +strings are supported diff --git a/bin/facter b/bin/facter new file mode 100755 index 0000000000..ed37fb52d9 --- /dev/null +++ b/bin/facter @@ -0,0 +1,72 @@ +#!/usr/bin/env ruby + +#-------------------- +# duh, it's facter! +# +# $Id: facter,v 1.1.1.1 2004/03/21 21:06:27 luke Exp $ + +require 'getoptlong' +require 'facter' + +Facter.load + +$debug = 0 + +config = nil + +result = GetoptLong.new( + [ "--version", "-v", GetoptLong::NO_ARGUMENT ], + [ "--help", "-h", GetoptLong::NO_ARGUMENT ], + [ "--debug", "-d", GetoptLong::NO_ARGUMENT ], + [ "--config", "-c", GetoptLong::REQUIRED_ARGUMENT ] +) + +result.each { |opt,arg| + case opt + when "--version" + puts "%s" % Facter.version + exit + when "--debug" + Facter.debugging(1) + when "--help" + puts "There is no help yet" + exit + else + raise "Invalid option '#{opt}'" + end +} + +names = [] + +unless config.nil? + File.open(config) { |file| + names = file.readlines.collect { |line| + line.chomp + } + } +end + +ARGV.each { |item| + names.push item +} + +facts = {} + +if names.empty? + Facter.each { |name,fact| + facts[name] = fact + } +else + names.each { |name| + begin + facts[name] = Facter[name].value + rescue => error + STDERR.puts "Could not retrieve %s: #{error}" % name + exit 10 + end + } +end + +facts.each { |name,value| + puts "%s => %s" % [name,value] +} diff --git a/etc/facter.conf b/etc/facter.conf new file mode 100644 index 0000000000..9140d501e8 --- /dev/null +++ b/etc/facter.conf @@ -0,0 +1,5 @@ +Hostname +OperatingSystem +OperatingSystemRelease +SSHDSAKey +CfKey diff --git a/install.rb b/install.rb new file mode 100644 index 0000000000..73a1a2e811 --- /dev/null +++ b/install.rb @@ -0,0 +1,289 @@ +#! /usr/bin/env ruby +#-- +# Copyright 2004 Austin Ziegler +# Install utility. Based on the original installation script for rdoc by the +# Pragmatic Programmers. +# +# This program is free software. It may be redistributed and/or modified under +# the terms of the GPL version 2 (or later) or the Ruby licence. +# +# Usage +# ----- +# In most cases, if you have a typical project layout, you will need to do +# absolutely nothing to make this work for you. This layout is: +# +# bin/ # executable files -- "commands" +# lib/ # the source of the library +# tests/ # unit tests +# +# The default behaviour: +# 1) Run all unit test files (ending in .rb) found in all directories under +# tests/. +# 2) Build Rdoc documentation from all files in bin/ (excluding .bat and .cmd), +# all .rb files in lib/, ./README, ./ChangeLog, and ./Install. +# 3) Build ri documentation from all files in bin/ (excluding .bat and .cmd), +# and all .rb files in lib/. This is disabled by default on Win32. +# 4) Install commands from bin/ into the Ruby bin directory. On Windows, if a +# if a corresponding batch file (.bat or .cmd) exists in the bin directory, +# it will be copied over as well. Otherwise, a batch file (always .bat) will +# be created to run the specified command. +# 5) Install all library files ending in .rb from lib/ into Ruby's +# site_lib/version directory. +# +# $Id: install.rb,v 1.6 2004/08/08 20:33:09 austin Exp $ +#++ + +require 'rbconfig' +require 'find' +require 'fileutils' +require 'optparse' +require 'ostruct' + +InstallOptions = OpenStruct.new + +$loadedrdoc = false + +begin + require 'rdoc/rdoc' + $loadedrdoc = true +rescue LoadError => detail + $stderr.puts "Could not load rdoc/rdoc: %s" % detail + InstallOptions.rdoc = false +end + + +def glob(list) + g = list.map { |i| Dir.glob(i) } + g.flatten! + g.compact! + g.reject! { |e| e =~ /CVS/ } + g +end + + # Set these values to what you want installed. +#bins = glob(%w{bin/**/*}).reject { |e| e =~ /\.(bat|cmd)$/ } +bins = ["bin/facter"] +rdoc = glob(%w{bin/**/* lib/**/*.rb README CHANGELOG INSTALL}).reject { |e| e=~ /\.(bat|cmd)$/ } +ri = glob(%w(bin/**/* lib/**/*.rb)).reject { |e| e=~ /\.(bat|cmd)$/ } +libs = glob(%w{lib/**/*.rb}) +tests = glob(%w{tests/**/*.rb}) + +def do_bins(bins, target, strip = 'bin/') + bins.each do |bf| + obf = bf.gsub(/#{strip}/, '') + install_binfile(bf, obf, target) + end +end + +def do_libs(libs, strip = 'lib/') + libs.each do |lf| + olf = File.join(InstallOptions.site_dir, lf.gsub(/#{strip}/, '')) + op = File.dirname(olf) + #if File.respond_to?(:makedirs) + FileUtils.makedirs(op) + #else + # recmkdir(op) + #end + File.chmod(0755, op) + FileUtils.install(lf, olf, :mode => 0755, :verbose => true) + end +end + +## +# Prepare the file installation. +# +def prepare_installation + InstallOptions.rdoc = true + if RUBY_PLATFORM == "i386-mswin32" + InstallOptions.ri = false + else + InstallOptions.ri = true + end + InstallOptions.tests = true + + ARGV.options do |opts| + opts.banner = "Usage: #{File.basename($0)} [options]" + opts.separator "" + opts.on('--[no-]rdoc', 'Prevents the creation of RDoc output.', 'Default on.') do |onrdoc| + InstallOptions.rdoc = onrdoc + end + opts.on('--[no-]ri', 'Prevents the creation of RI output.', 'Default off on mswin32.') do |onri| + InstallOptions.ri = onri + end + opts.on('--[no-]tests', 'Prevents the execution of unit tests.', 'Default on.') do |ontest| + InstallOptions.tests = ontest + end + opts.on('--quick', 'Performs a quick installation. Only the', 'installation is done.') do |quick| + InstallOptions.rdoc = false + InstallOptions.ri = false + InstallOptions.tests = false + end + opts.on('--full', 'Performs a full installation. All', 'optional installation steps are run.') do |full| + InstallOptions.rdoc = true + InstallOptions.ri = true + InstallOptions.tests = true + end + opts.separator("") + opts.on_tail('--help', "Shows this help text.") do + $stderr.puts opts + exit + end + + opts.parse! + end + + bds = [".", ENV['TMP'], ENV['TEMP'], "/tmp"] + + version = [Config::CONFIG["MAJOR"], Config::CONFIG["MINOR"]].join(".") + ld = File.join(Config::CONFIG["libdir"], "ruby", version) + + sd = Config::CONFIG["sitelibdir"] + if sd.nil? + sd = $:.find { |x| x =~ /site_ruby/ } + if sd.nil? + sd = File.join(ld, "site_ruby") + elsif sd !~ Regexp.quote(version) + sd = File.join(sd, version) + end + end + + if (destdir = ENV['DESTDIR']) + bd = "#{destdir}#{Config::CONFIG['bindir']}" + sd = "#{destdir}#{sd}" + bds << bd + + FileUtils.makedirs(bd) + FileUtils.makedirs(sd) + else + bds << Config::CONFIG['bindir'] + end + + InstallOptions.bin_dirs = bds.compact + InstallOptions.site_dir = sd + InstallOptions.bin_dir = bd + InstallOptions.lib_dir = ld + + unless $loadedrdoc + InstallOptions.rdoc = false + InstallOptions.ri = false + end +end + +## +# Build the rdoc documentation. Also, try to build the RI documentation. +# +def build_rdoc(files) + r = RDoc::RDoc.new + r.document(["--main", "README", "--title", "Facter -- A Fact Collecter", + "--line-numbers"] + files) + +rescue RDoc::RDocError => e + $stderr.puts e.message +rescue Exception => e + $stderr.puts "Couldn't build RDoc documentation\n#{e.message}" +end + +def build_ri(files) + ri = RDoc::RDoc.new + ri.document(["--ri-site", "--merge"] + files) +rescue RDoc::RDocError => e + $stderr.puts e.message +rescue Exception => e + $stderr.puts e.class + $stderr.puts "Couldn't build Ri documentation\n#{e.message}" +end + +def run_tests(test_list) + begin + require 'test/unit/ui/console/testrunner' + $:.unshift "lib" + test_list.each do |test| + next if File.directory?(test) + require test + end + + tests = [] + ObjectSpace.each_object { |o| tests << o if o.kind_of?(Class) } + tests.delete_if { |o| !o.ancestors.include?(Test::Unit::TestCase) } + tests.delete_if { |o| o == Test::Unit::TestCase } + + tests.each { |test| Test::Unit::UI::Console::TestRunner.run(test) } + $:.shift + rescue LoadError + puts "Missing testrunner library; skipping tests" + end +end + +## +# Install file(s) from ./bin to Config::CONFIG['bindir']. Patch it on the way +# to insert a #! line; on a Unix install, the command is named as expected +# (e.g., bin/rdoc becomes rdoc); the shebang line handles running it. Under +# windows, we add an '.rb' extension and let file associations do their stuff. +def install_binfile(from, op_file, target) + tmp_dir = nil + InstallOptions.bin_dirs.each do |t| + if File.directory?(t) and File.writable_real?(t) + tmp_dir = t + break + end + end + + fail "Cannot find a temporary directory" unless tmp_dir + tmp_file = File.join(tmp_dir, '_tmp') + ruby = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name']) + + File.open(from) do |ip| + File.open(tmp_file, "w") do |op| + ruby = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name']) + op.puts "#!#{ruby}" + op.write ip.read + end + end + + if Config::CONFIG["target_os"] =~ /win/io + installed_wrapper = false + + if File.exists?("#{from}.bat") + FileUtils.install("#{from}.bat", File.join(target, "#{op_file}.bat"), :mode => 0755, :verbose => true) + installed_wrapper = true + end + + if File.exists?("#{from}.cmd") + FileUtils.install("#{from}.cmd", File.join(target, "#{op_file}.cmd"), :mode => 0755, :verbose => true) + installed_wrapper = true + end + + if not installed_wrapper + tmp_file2 = File.join(tmp_dir, '_tmp_wrapper') + cwn = File.join(Config::CONFIG['bindir'], op_file) + cwv = CMD_WRAPPER.gsub('', ruby.gsub(%r{/}) { "\\" }).gsub!('', cwn.gsub(%r{/}) { "\\" } ) + + File.open(tmp_file2, "wb") { |cw| cw.puts cwv } + FileUtils.install(tmp_file2, File.join(target, "#{op_file}.bat"), :mode => 0755, :verbose => true) + + File.unlink(tmp_file2) + installed_wrapper = true + end + end + FileUtils.install(tmp_file, File.join(target, op_file), :mode => 0755, :verbose => true) + File.unlink(tmp_file) +end + +CMD_WRAPPER = <<-EOS +@echo off +if "%OS%"=="Windows_NT" goto WinNT + -x "" %1 %2 %3 %4 %5 %6 %7 %8 %9 +goto done +:WinNT + -x "" %* +goto done +:done +EOS + +prepare_installation + +run_tests(tests) if InstallOptions.tests +build_rdoc(rdoc) if InstallOptions.rdoc +build_ri(ri) if InstallOptions.ri +do_bins(bins, Config::CONFIG['bindir']) +do_libs(libs) diff --git a/lib/facter.rb b/lib/facter.rb new file mode 100644 index 0000000000..e537ca4dd9 --- /dev/null +++ b/lib/facter.rb @@ -0,0 +1,766 @@ +# $Id$ +#-- +# Copyright 2004 Luke Kanies +# +# This program is free software. It may be redistributed and/or modified under +# the terms of the GPL version 2 (or later), the Perl Artistic licence, or the +# Ruby licence. +# +#-- + +#-------------------------------------------------------------------- +class Facter + include Comparable + include Enumerable + + FACTERVERSION="1.0.0" + # = Facter 1.0 + # Functions as a hash of 'facts' you might care about about your + # system, such as mac address, IP address, Video card, etc. + # returns them dynamically + + # == Synopsis + # + # Generally, treat Facter as a hash: + # == Example + # require 'facter' + # puts Facter['operatingsystem'] + # + + + @@facts = {} + GREEN = "" + RESET = "" + @@debug = 0 + @@os = nil + @@osrel = nil + + attr_accessor :name, :os, :osrel, :hardware, :searching + + #---------------------------------------------------------------- + # module methods + #---------------------------------------------------------------- + + def Facter.version + return FACTERVERSION + end + + def Facter.debug(string) + if string.nil? + return + end + if @@debug != 0 + puts GREEN + string + RESET + end + end + + #---------------------------------------------------------------- + def Facter.[](name) + if @@facts.include?(name.downcase) + return @@facts[name.downcase] + else + return Facter.add(name) + end + end + #---------------------------------------------------------------- + + #---------------------------------------------------------------- + def Facter.add(name) + fact = nil + dcname = name.downcase + + if @@facts.include?(dcname) + fact = @@facts[dcname] + else + Facter.new(dcname) + fact = @@facts[dcname] + end + + if block_given? + fact.add + end + + return fact + end + #---------------------------------------------------------------- + + #---------------------------------------------------------------- + class << self + include Enumerable + def each + @@facts.each { |name,fact| + if fact.suitable? + value = fact.value + unless value.nil? + yield name, fact.value + end + end + } + end + end + #---------------------------------------------------------------- + + #---------------------------------------------------------------- + def Facter.reset + @@facts.each { |name,fact| + @@facts.delete(name) + } + end + #---------------------------------------------------------------- + + #---------------------------------------------------------------- + # + def Facter.debugging(bit) + if bit + case bit + when TrueClass: @@debug = 1 + when FalseClass: @@debug = 0 + when Fixnum: + if bit > 0 + @@debug = 1 + else + @@debug = 0 + end + when String: + if bit.downcase == 'off' + @@debug = 0 + else + @@debug = 1 + end + else + @@debug = 0 + end + else + @@debug = 0 + end + end + #---------------------------------------------------------------- + + #---------------------------------------------------------------- + #def Facter.init + # @@os = @@facts["operatingsystem"].value + # @@release = @@facts["operatingsystemrelease"].value + #end + #---------------------------------------------------------------- + + #---------------------------------------------------------------- + def Facter.list + return @@facts.keys + end + #---------------------------------------------------------------- + + #---------------------------------------------------------------- + def Facter.os + return @@os + end + #---------------------------------------------------------------- + + #---------------------------------------------------------------- + def Facter.release + return @@release + end + #---------------------------------------------------------------- + + #---------------------------------------------------------------- + def <=>(other) + return self.value <=> other + end + #---------------------------------------------------------------- + + #---------------------------------------------------------------- + def initialize(name) + @name = name.downcase + if @@facts.include?(@name) + raise "A fact named %s already exists" % name + else + @@facts[@name] = self + end + + @resolves = [] + @searching = false + end + #---------------------------------------------------------------- + + #---------------------------------------------------------------- + # as long as this doesn't work over the 'net, it should probably + # be optimized to throw away any resolutions not valid for the machine + # it's running on + # but that's not currently done... + def add + resolve = Resolution.new(@name) + yield(resolve) + + # skip resolves that will never be suitable for us + return unless resolve.suitable? + + # insert resolves in order of number of tags + inserted = false + @resolves.each_with_index { |r,index| + if resolve.length > r.length + @resolves.insert(index,resolve) + inserted = true + break + end + } + + unless inserted + @resolves.push resolve + end + end + #---------------------------------------------------------------- + + #---------------------------------------------------------------- + # iterate across all of the currently configured resolves + # sort the resolves in order of most tags first, so we're getting + # the most specific answers first, hopefully + def each +# @resolves.sort { |a,b| +# puts "for tag %s, alength is %s and blength is %s" % +# [@name, a.length, b.length] +# b.length <=> a.length +# }.each { |resolve| +# yield resolve +# } + @resolves.each { |r| yield r } + end + #---------------------------------------------------------------- + + #---------------------------------------------------------------- + # return a count of resolution mechanisms available + def count + return @resolves.length + end + #---------------------------------------------------------------- + + #---------------------------------------------------------------- + # is this fact suitable for finding answers on this host? + # again, this should really be optimizing by throwing away everything + # not suitable, but... + def suitable? + return false if @resolves.length == 0 + + unless defined? @suitable + @suitable = false + self.each { |resolve| + if resolve.suitable? + @suitable = true + break + end + } + end + + return @suitable + end + #---------------------------------------------------------------- + + #---------------------------------------------------------------- + def value + # make sure we don't get stuck in recursive dependency loops + if @searching + Facter.debug "Caught recursion on %s" % @name + + # return a cached value if we've got it + if @value + return @value + else + return nil + end + end + @value = nil + foundsuits = false + + if @resolves.length == 0 + Facter.debug "No resolves for %s" % @name + return nil + end + + @searching = true + @resolves.each { |resolve| + #Facter.debug "Searching resolves for %s" % @name + if resolve.suitable? + @value = resolve.value + foundsuits = true + else + Facter.debug "Unsuitable resolve %s for %s" % [resolve,@name] + end + unless @value.nil? or @value == "" + break + end + } + @searching = false + + unless foundsuits + Facter.debug "Found no suitable resolves of %s for %s" % + [@resolves.length,@name] + end + + if @value.nil? + # nothing + Facter.debug("value for %s is still nil" % @name) + return nil + else + return @value + end + end + #---------------------------------------------------------------- + + #---------------------------------------------------------------- + class Resolution + attr_accessor :interpreter, :code, :name + + def Resolution.exec(code, interpreter = "/bin/sh") + if interpreter == "/bin/sh" + binary = code.split(/\s+/).shift + + path = nil + if binary !~ /^\// + path = %x{which #{binary} 2>/dev/null}.chomp + if path == "" + # we don't have the binary necessary + return nil + end + else + path = binary + end + + unless FileTest.exists?(path) + # our binary does not exist + return nil + end + out = nil + begin + out = %x{#{code}}.chomp + rescue => detail + $stderr.puts detail + end + if out == "" + return nil + else + return out + end + else + raise "non-sh interpreters are not currently supported" + end + end + + def initialize(name) + @name = name + @tags = [] + end + + def length + @tags.length + end + + def suitable? + unless defined? @suitable + @suitable = true + if @tags.length == 0 + #Facter.debug "'%s' has no tags" % @name + return true + end + @tags.each { |tag| + unless tag.true? + #Facter.debug "'%s' is false" % tag + @suitable = false + end + } + end + + return @suitable + end + + def tag(fact,op,value) + @tags.push Tag.new(fact,op,value) + end + + def to_s + return self.value() + end + + # how we get a value for our resolution mechanism + def value + #puts "called %s value" % @name + value = nil + if @code.is_a?(Proc) + value = @code.call() + else + unless defined? @interpreter + @interpreter = "/bin/sh" + end + if @code.nil? + $stderr.puts "Code for %s is nil" % @name + else + value = Resolution.exec(@code,@interpreter) + end + end + + if value == "" + value = nil + end + + return value + end + end + #---------------------------------------------------------------- + + #---------------------------------------------------------------- + class Tag + attr_accessor :fact, :op, :value + + def initialize(fact,op,value) + @fact = fact + if op == "=" + op = "==" + end + @op = op + @value = value + end + + def to_s + return "'%s' %s '%s'" % [@fact,@op,@value] + end + + def true? + value = Facter[@fact].value + + if value.nil? + return false + end + + str = "'%s' %s '%s'" % [value,@op,@value] + begin + if eval(str) + return true + else + return false + end + rescue => detail + $stderr.puts "Failed to test '%s': %s" % [str,detail] + return false + end + end + end + #---------------------------------------------------------------- + + # Load all of the default facts + def Facter.load + Facter["OperatingSystem"].add { |obj| + obj.code = 'uname -s' + } + + Facter["OperatingSystemRelease"].add { |obj| + obj.code = 'uname -r' + } + + Facter["HardwareModel"].add { |obj| + obj.code = 'uname -m' + #obj.os = "SunOS" + #obj.tag("operatingsystem","=","SunOS") + } + + Facter["CfKey"].add { |obj| + obj.code = proc { + value = nil + ["/usr/local/etc/cfkey.pub", + "/etc/cfkey.pub", + "/var/cfng/keys/localhost.pub", + "/var/cfengine/ppkeys/localhost.pub" + ].each { |file| + if FileTest.file?(file) + File.open(file) { |openfile| + value = openfile.readlines.reject { |line| + line =~ /PUBLIC KEY/ + }.collect { |line| + line.chomp + }.join("") + } + end + if value + break + end + } + + value + } + } + + Facter["Domain"].add { |obj| + obj.code = proc { + if defined? $domain and ! $domain.nil? + $domain + end + } + } + Facter["Domain"].add { |obj| + obj.code = proc { + domain = Resolution.exec('domainname') + # make sure it's a real domain + if domain =~ /.+\..+/ + domain + else + nil + end + } + } + Facter["Domain"].add { |obj| + obj.code = proc { + value = nil + unless FileTest.exists?("/etc/resolv.conf") + return nil + end + File.open("/etc/resolv.conf") { |file| + # is the domain set? + file.each { |line| + if line =~ /domain\s+(\S+)/ + value = $1 + break + end + } + } + ! value and File.open("/etc/resolv.conf") { |file| + # is the search path set? + file.each { |line| + if line =~ /search\s+(\S+)/ + value = $1 + break + end + } + } + value + } + } + Facter["Hostname"].add { |obj| + obj.code = proc { + hostname = nil + name = Resolution.exec('hostname') + if name =~ /^([\w-]+)\.(.+)$/ + hostname = $1 + # the Domain class uses this + $domain = $2 + else + hostname = name + end + hostname + } + } + + Facter["IPHostNumber"].add { |obj| + obj.code = proc { + require 'resolv' + + begin + hostname = Facter["hostname"].value + ip = Resolv.getaddress(hostname) + unless ip == "127.0.0.1" + ip + end + rescue Resolv::ResolvError + nil + rescue NoMethodError # i think this is a bug in resolv.rb? + nil + end + } + } + Facter["IPHostNumber"].add { |obj| + obj.code = proc { + hostname = Facter["hostname"].value + # crap, we need Hostname to exist for this to + # work + list = Resolution.exec("host #{hostname}").chomp.split(/\s/) + if defined? list[-1] and list[-1] =~ /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ + list[-1] + end + } + } + + ["/etc/ssh","/usr/local/etc/ssh","/etc","/usr/local/etc"].each { |dir| + {"SSHDSAKey" => "ssh_host_dsa_key.pub", + "SSHRSAKey" => "ssh_host_rsa_key.pub"}.each { |name,file| + Facter[name].add { |obj| + obj.code = proc { + value = nil + filepath = File.join(dir,file) + if FileTest.file?(filepath) + begin + value = File.open(filepath).read.chomp + rescue + return nil + end + end + return value + } # end of proc + } # end of add + } # end of hash each + } # end of dir each + + Facter["UniqueId"].add { |obj| + obj.code = 'hostid' + obj.interpreter = '/bin/sh' + obj.tag("operatingsystem","=","SunOS") + #obj.os = "SunOS" + } + Facter["HardwareISA"].add { |obj| + obj.code = 'uname -p' + obj.interpreter = '/bin/sh' + #obj.os = "SunOS" + obj.tag("operatingsystem","=","SunOS") + } + Facter["MacAddress"].add { |obj| + #obj.os = "SunOS" + obj.tag("operatingsystem","=","SunOS") + obj.code = proc { + ether = nil + output = %x{/sbin/ifconfig -a} + + output =~ /ether (\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/ + ether = $1 + + ether + } + } + Facter["MacAddress"].add { |obj| + #obj.os = "Darwin" + obj.tag("operatingsystem","=","Darwin") + obj.code = proc { + ether = nil + output = %x{/sbin/ifconfig} + + output.split(/^\S/).each { |str| + if str =~ /10baseT/ # we're wired + str =~ /ether (\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ + ether = $1 + end + } + + ether + } + } + Facter["IPHostnumber"].add { |obj| + #obj.os = "Darwin" + obj.tag("operatingsystem","=","Darwin") + obj.code = proc { + ip = nil + output = %x{/sbin/ifconfig} + + output.split(/^\S/).each { |str| + if str =~ /inet ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + tmp = $1 + unless tmp =~ /127\./ + ip = tmp + break + end + end + } + + ip + } + } + Facter["Hostname"].add { |obj| + #obj.os = "Darwin" + #obj.release = "R7" + obj.tag("operatingsystem","=","Darwin") + obj.tag("operatingsystemrelease","=","R7") + obj.code = proc { + hostname = nil + File.open( + "/Library/Preferences/SystemConfiguration/preferences.plist" + ) { |file| + found = 0 + file.each { |line| + if line =~ /ComputerName/i + found = 1 + next + end + if found == 1 + if line =~ /([\w|-]+)<\/string>/ + hostname = $1 + break + end + end + } + } + + if hostname != nil + hostname + end + } + } + Facter["IPHostnumber"].add { |obj| + #obj.os = "Darwin" + #obj.release = "R6" + obj.tag("operatingsystem","=","Darwin") + obj.tag("operatingsystemrelease","=","R6") + obj.code = proc { + hostname = nil + File.open( + "/var/db/SystemConfiguration/preferences.xml" + ) { |file| + found = 0 + file.each { |line| + if line =~ /ComputerName/i + found = 1 + next + end + if found == 1 + if line =~ /([\w|-]+)<\/string>/ + hostname = $1 + break + end + end + } + } + + if hostname != nil + hostname + end + } + } + Facter["IPHostnumber"].add { |obj| + #obj.os = "Darwin" + #obj.release = "R6" + obj.tag("operatingsystem","=","Darwin") + obj.tag("operatingsystemrelease","=","R6") + obj.code = proc { + ether = nil + output = %x{/sbin/ifconfig} + + output =~ /HWaddr (\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ + ether = $1 + + ether + } + } + Facter["Distro"].add { |obj| + #obj.os = "Linux" + obj.tag("operatingsystem","=","Linux") + obj.code = proc { + if FileTest.exists?("/etc/debian_version") + return "Debian" + elsif FileTest.exists?("/etc/gentoo-release") + return "Gentoo" + elsif FileTest.exists?("/etc/fedora-release") + return "Fedora" + elsif FileTest.exists?("/etc/redhat-release") + return "RedHat" + end + } + } + Facter["ps"].add { |obj| + obj.code = "echo 'ps -ef'" + } + Facter["ps"].add { |obj| + obj.tag("operatingsystem","=","Darwin") + obj.code = "echo 'ps -auxwww'" + } + end + + Facter.load +end + +# try to load a local fact library, if there happens to be one +begin + require 'facter/local' +rescue LoadError + # no worries +end diff --git a/tests/tc_facterbin.rb b/tests/tc_facterbin.rb new file mode 100755 index 0000000000..b4bde3f552 --- /dev/null +++ b/tests/tc_facterbin.rb @@ -0,0 +1,35 @@ +#! /usr/bin/env ruby +# $Id: $ +if __FILE__ == $0 + $:.unshift '../lib' + $facterbase = ".." +end + +require 'facter' +require 'test/unit' + +# add the bin directory to our search path +ENV["PATH"] += ":" + File.join($facterbase, "bin") + +# and then the library directories +libdirs = $:.find_all { |dir| + dir =~ /facter/ or dir =~ /\.\./ +} +ENV["RUBYLIB"] = libdirs.join(":") + +class TestFacterBin < Test::Unit::TestCase + def setup + end + + def teardown + end + + def test_version + output = nil + assert_nothing_raised { + output = %x{facter --version 2>&1}.chomp + } + print output + assert ( output == Facter.version ) + end +end diff --git a/tests/tc_simple.rb b/tests/tc_simple.rb new file mode 100644 index 0000000000..cda25573f1 --- /dev/null +++ b/tests/tc_simple.rb @@ -0,0 +1,268 @@ +#! /usr/bin/env ruby +# $Id$ +# +if __FILE__ == $0 # Make this library first! + $:.unshift '../lib' +end + +require 'test/unit' +require 'facter' + +if __FILE__ == $0 + Facter.debugging(true) +end + +class TestFacter < Test::Unit::TestCase + def setup + Facter.load + end + + def teardown + # clear out the list of facts, so we start fresh for every test + Facter.reset + end + + def test_version + #Could match /[0-9.]+/ + #Strict match: /^[0-9]+(\.[0-9]+)*$/ + #ok: 1.0.0 1.0 1 + #notok: 1..0 1. .1 1a + assert(Facter.version =~ /^[0-9]+(\.[0-9]+)*$/ ) + end + + def test_notags_sh + assert_nothing_raised { + Facter["testing"].add { |fact| + fact.code = "echo yup" + } + } + + assert_equal("yup", Facter["testing"].value) + end + + def test_notags + assert_nothing_raised { + Facter["testing"].add { |fact| + fact.code = proc { "foo" } + } + } + + assert_equal("foo", Facter["testing"].value) + end + + def test_onetruetag + assert_nothing_raised { + Facter["required"].add { |fact| + fact.code = proc { "foo" } + } + Facter["testing"].add { |fact| + fact.code = proc { "bar" } + fact.tag("required","=","foo") + } + } + + assert_equal("bar", Facter["testing"].value) + end + + def test_onefalsetag + assert_nothing_raised { + Facter["required"].add { |fact| + fact.code = proc { "foo" } + } + Facter["testing"].add { |fact| + fact.code = proc { "bar" } + fact.tag("required","=","bar") + } + } + + assert_equal(nil, Facter["testing"].value) + end + + def test_recursivetags + assert_nothing_raised { + Facter["required"].add { |fact| + fact.code = proc { "foo" } + fact.tag("testing","=","foo") + } + Facter["testing"].add { |fact| + fact.code = proc { "bar" } + fact.tag("required","=","foo") + } + } + + assert_equal(nil, Facter["testing"].value) + end + + def test_multipleresolves + assert_nothing_raised { + Facter["funtest"].add { |fact| + fact.code = proc { "untagged" } + } + Facter["funtest"].add { |fact| + fact.code = proc { "tagged" } + fact.tag("operatingsystem","=", Facter["operatingsystem"].value) + } + } + + assert_equal("tagged", Facter["funtest"].value) + end + + def test_osname + assert_equal( + %x{uname -s}.chomp, + Facter["operatingsystem"].value + ) + end + + def test_osrel + assert_equal( + %x{uname -r}.chomp, + Facter["operatingsystemrelease"].value + ) + end + + def test_hostname + assert_equal( + %x{hostname}.chomp.sub(/\..+/,''), + Facter["hostname"].value + ) + end + + def test_upcase + Facter["Testing"].add { |fact| + fact.code = proc { "foo" } + } + assert_equal( + "foo", + Facter["Testing"].value + ) + end + + def test_doublecall + Facter["testing"].add { |fact| + fact.code = proc { "foo" } + } + assert_equal( + Facter["testing"].value, + Facter["testing"].value + ) + end + + def test_downcase + Facter["testing"].add { |fact| + fact.code = proc { "foo" } + } + assert_equal( + "foo", + Facter["testing"].value + ) + end + + def test_case_insensitivity + Facter["Testing"].add { |fact| + fact.code = proc { "foo" } + } + upcase = Facter["Testing"].value + downcase = Facter["testing"].value + assert_equal(upcase, downcase) + end + + def test_adding + assert_nothing_raised() { + Facter["Funtest"].add { |obj| + obj.code = proc { return "funtest value" } + } + } + + assert_equal( + "funtest value", + Facter["funtest"].value + ) + + assert_nothing_raised() { + code = proc { return "yaytest value" } + block = proc { |obj| obj.code = code } + Facter["Yaytest"].add(&block) + } + + assert_equal( + "yaytest value", + Facter["yaytest"].value + ) + end + + def test_comparison + assert( + %x{uname -s}.chomp == Facter["operatingsystem"].value + ) + assert( + %x{hostname}.chomp.sub(/\..+/,'') == Facter["hostname"].value + ) + end + + def test_adding2 + assert_nothing_raised() { + Facter["bootest"].add { |obj| + obj.tag("operatingsystem", "=", Facter["operatingsystem"].value) + obj.code = "echo bootest" + } + } + + assert_equal( + "bootest", + Facter["bootest"].value + ) + + assert_nothing_raised() { + Facter["bahtest"].add { |obj| + #obj.os = Facter["operatingsystem"].value + #obj.release = Facter["operatingsystemrelease"].value + obj.tag("operatingsystem", "=", Facter["operatingsystem"].value) + obj.tag("operatingsystemrelease", "=", + Facter["operatingsystemrelease"].value) + obj.code = "echo bahtest" + } + } + + assert_equal( + "bahtest", + Facter["bahtest"].value + ) + + assert_nothing_raised() { + Facter["failure"].add { |obj| + #obj.os = Facter["operatingsystem"].value + #obj.release = "FakeRelease" + obj.tag("operatingsystem", "=", Facter["operatingsystem"].value) + obj.tag("operatingsystemrelease", "=", "FakeRelease") + obj.code = "echo failure" + } + } + + assert_equal( + nil, + Facter["failure"].value + ) + end + + def test_distro + if Facter["operatingsystem"] == "Linux" + assert(Facter["Distro"]) + end + end + + def test_each + list = {} + assert_nothing_raised { + Facter.each { |name,fact| + list[name] = fact + } + } + + list.each { |name,value| + assert(value.class != Facter) + assert(name) + assert(value) + } + end +end From 8cb9662660927f0ba55c18be91aa764174fdb61b Mon Sep 17 00:00:00 2001 From: luke Date: Wed, 16 Nov 2005 17:29:21 +0000 Subject: [PATCH 0002/3753] updating INSTALL with patch from ian git-svn-id: http://reductivelabs.com/svn/facter/trunk@60 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- INSTALL | 9 +++++++-- lib/facter.rb | 5 +++++ tests/tc_simple.rb | 21 +++++++++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/INSTALL b/INSTALL index de89f69822..c9726642dd 100644 --- a/INSTALL +++ b/INSTALL @@ -1,2 +1,7 @@ -Unforantely, no install.rb yet. Hopefully soon. Just copy the libs into -your main ruby library directory. Or something. +Run 'ruby install.rb' or use one of the distributed gem files at +http://reductivelabs.com/downloads/gems . + +install.rb should successfully install; let me know if it doesn't. + +Otherwise, you can just set RUBYLIB to contain its lib directory, or copy +the libs into your main ruby library directory. diff --git a/lib/facter.rb b/lib/facter.rb index e537ca4dd9..9e03892bde 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -753,6 +753,11 @@ def Facter.load obj.tag("operatingsystem","=","Darwin") obj.code = "echo 'ps -auxwww'" } + + Facter["id"].add { |obj| + obj.tag("operatingsystem","=","Linux") + obj.code = "whoami" + } end Facter.load diff --git a/tests/tc_simple.rb b/tests/tc_simple.rb index cda25573f1..af2c82d76d 100644 --- a/tests/tc_simple.rb +++ b/tests/tc_simple.rb @@ -20,6 +20,10 @@ def setup def teardown # clear out the list of facts, so we start fresh for every test Facter.reset + + if ! @oldhandles.empty? + $stdin, $stdout, $stderr = @oldhandles + end end def test_version @@ -265,4 +269,21 @@ def test_each assert(value) } end + + def test_withnoouts + @oldhandles = [] + @oldhandles << $stdin.dup + $stdin.reopen "/dev/null" + @oldhandles << $stdout.dup + $stdout.reopen "/dev/null", "a" + @oldhandles << $stderr.dup + $stderr.reopen $stdout + + assert_nothing_raised { + Facter.each { |name,fact| + list[name] = fact + } + } + $stdin, $stdout, $stderr = @oldhandles + end end From 58538d277dc51692ed048983b31bd9652475b19f Mon Sep 17 00:00:00 2001 From: luke Date: Wed, 16 Nov 2005 17:45:43 +0000 Subject: [PATCH 0003/3753] removing filehandle-based tests git-svn-id: http://reductivelabs.com/svn/facter/trunk@61 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- tests/tc_simple.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/tc_simple.rb b/tests/tc_simple.rb index af2c82d76d..624eb588e4 100644 --- a/tests/tc_simple.rb +++ b/tests/tc_simple.rb @@ -21,9 +21,9 @@ def teardown # clear out the list of facts, so we start fresh for every test Facter.reset - if ! @oldhandles.empty? - $stdin, $stdout, $stderr = @oldhandles - end + #if ! @oldhandles.empty? + # $stdin, $stdout, $stderr = @oldhandles + #end end def test_version @@ -270,7 +270,7 @@ def test_each } end - def test_withnoouts + def disabled_test_withnoouts @oldhandles = [] @oldhandles << $stdin.dup $stdin.reopen "/dev/null" From 3f0186dd749681f9ba3d3d9feb09252600137085 Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 22 Nov 2005 15:22:53 +0000 Subject: [PATCH 0004/3753] Modified version git-svn-id: http://reductivelabs.com/svn/facter/trunk@62 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- CHANGES | 5 ++++- lib/facter.rb | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index f8ad814826..a683684c5a 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,7 @@ -2.0: +1.0.1: + Added 'id' fact, which basically returns 'whoami'. + +1.0: Rewrote entirely. It's much simpler to use, and now supports adding new fact resolution mechanisms at run-time. diff --git a/lib/facter.rb b/lib/facter.rb index 9e03892bde..7beb2916ba 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -13,7 +13,7 @@ class Facter include Comparable include Enumerable - FACTERVERSION="1.0.0" + FACTERVERSION="1.0.1" # = Facter 1.0 # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. From fe90bf1bce190ae0f3cc68859299566330ecc49e Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 22 Nov 2005 15:25:08 +0000 Subject: [PATCH 0005/3753] Updated to version 1.0.1 git-svn-id: http://reductivelabs.com/svn/facter/trunk@63 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter.rb b/lib/facter.rb index 7beb2916ba..9e03892bde 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -13,7 +13,7 @@ class Facter include Comparable include Enumerable - FACTERVERSION="1.0.1" + FACTERVERSION="1.0.0" # = Facter 1.0 # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. From c64643480d0c4b1b05774f6e210cc58ebb246244 Mon Sep 17 00:00:00 2001 From: luke Date: Fri, 30 Dec 2005 20:09:58 +0000 Subject: [PATCH 0006/3753] updates git-svn-id: http://reductivelabs.com/svn/facter/trunk@65 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- Rakefile | 2 +- lib/facter.rb | 2 +- tests/tc_facterbin.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Rakefile b/Rakefile index 0cd2889659..5ab2bf947f 100644 --- a/Rakefile +++ b/Rakefile @@ -46,7 +46,7 @@ task :a => :alltests task :alltests => :unittests Rake::TestTask.new(:unittests) do |t| - t.test_files = FileList['tests/tc*.rb'] + t.test_files = FileList['tests/*.rb'] t.warning = true t.verbose = false end diff --git a/lib/facter.rb b/lib/facter.rb index 9e03892bde..7beb2916ba 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -13,7 +13,7 @@ class Facter include Comparable include Enumerable - FACTERVERSION="1.0.0" + FACTERVERSION="1.0.1" # = Facter 1.0 # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. diff --git a/tests/tc_facterbin.rb b/tests/tc_facterbin.rb index b4bde3f552..4bdfdd8e27 100755 --- a/tests/tc_facterbin.rb +++ b/tests/tc_facterbin.rb @@ -30,6 +30,6 @@ def test_version output = %x{facter --version 2>&1}.chomp } print output - assert ( output == Facter.version ) + assert(output == Facter.version) end end From 7df3411c2ef986f40592e12fa5588b5dbd4e6902 Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 3 Jan 2006 18:51:31 +0000 Subject: [PATCH 0007/3753] adding fixes Eric Sorenson found with cygwin git-svn-id: http://reductivelabs.com/svn/facter/trunk@66 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- Rakefile | 70 ++++++++++++++++++++++++++++++--------------------- lib/facter.rb | 11 +++++--- 2 files changed, 49 insertions(+), 32 deletions(-) diff --git a/Rakefile b/Rakefile index 5ab2bf947f..4daa9ce461 100644 --- a/Rakefile +++ b/Rakefile @@ -1,10 +1,10 @@ # Rakefile for facter begin - require 'rubygems' - require 'rake/gempackagetask' + require 'rubygems' + require 'rake/gempackagetask' rescue Exception - nil + nil end require 'rake/clean' @@ -32,6 +32,8 @@ else PKG_VERSION = CURRENT_VERSION end +GEMDIR = "/export/docroots/reductivelabs.com/htdocs/downloads/gems" +TARDIR = "/export/docroots/reductivelabs.com/htdocs/downloads/facter" # The default task is run if rake is given no explicit arguments. @@ -40,16 +42,16 @@ task :default => :unittests # Test Tasks --------------------------------------------------------- -task :u => :unittests -task :a => :alltests +#task :u => :unittests +#task :a => :alltests -task :alltests => :unittests +#task :alltests => :unittests -Rake::TestTask.new(:unittests) do |t| - t.test_files = FileList['tests/*.rb'] - t.warning = true - t.verbose = false -end +#Rake::TestTask.new(:unittests) do |t| +# t.test_files = FileList['tests/*.rb'] +# t.warning = true +# t.verbose = false +#end # SVN Tasks ---------------------------------------------------------- # ... none. @@ -58,18 +60,18 @@ end desc "Install the application" task :install do - ruby "install.rb" + ruby "install.rb" end # Create a task to build the RDOC documentation tree. rd = Rake::RDocTask.new("rdoc") { |rdoc| - rdoc.rdoc_dir = 'html' - rdoc.template = 'css2' - rdoc.title = "Facter" - rdoc.options << '--line-numbers' << '--inline-source' << '--main' << 'README' - rdoc.rdoc_files.include('README', 'LICENSE', 'TODO', 'CHANGES') - rdoc.rdoc_files.include('lib/**/*.rb', 'doc/**/*.rdoc') + rdoc.rdoc_dir = 'html' + rdoc.template = 'css2' + rdoc.title = "Facter" + rdoc.options << '--line-numbers' << '--inline-source' << '--main' << 'README' + rdoc.rdoc_files.include('README', 'LICENSE', 'TODO', 'CHANGES') + rdoc.rdoc_files.include('lib/**/*.rb', 'doc/**/*.rdoc') } # ==================================================================== @@ -77,18 +79,18 @@ rd = Rake::RDocTask.new("rdoc") { |rdoc| # tar, zip and gem files. PKG_FILES = FileList[ - 'install.rb', - '[A-Z]*', - 'bin/**/*', - 'lib/**/*.rb', - 'test/**/*.rb', - 'doc/**/*', - 'etc/*' + 'install.rb', + '[A-Z]*', + 'bin/**/*', + 'lib/**/*.rb', + 'test/**/*.rb', + 'doc/**/*', + 'etc/*' ] PKG_FILES.delete_if {|item| item.include?(".svn")} if ! defined?(Gem) - puts "Package Target requires RubyGEMs" + puts "Package Target requires RubyGEMs" else spec = Gem::Specification.new do |s| @@ -192,10 +194,11 @@ desc "Make a new release" task :release => [ :prerelease, :clobber, - :alltests, :update_version, :package, + :copy, :tag] do + #:alltests, announce announce "**************************************************************" @@ -246,7 +249,7 @@ task :update_version => [:prerelease] do open("lib/facter.rb") do |rakein| open("lib/facter.rb.new", "w") do |rakeout| rakein.each do |line| - if line =~ /^FACTERVERSION\s*=\s*/ + if line =~ /^\s*FACTERVERSION\s*=\s*/ rakeout.puts "FACTERVERSION = '#{PKG_VERSION}'" else rakeout.puts line @@ -271,7 +274,16 @@ task :tag => [:prerelease] do if ENV['RELTEST'] announce "Release Task Testing, skipping SVN tagging" else - #sh %{svn copy trunk/ tags/#{reltag}} + sh %{svn copy trunk/ tags/#{reltag}} end end +desc "Copy the newly created package into the downloads directory" +task :copy => [:prerelease] do + sh %{cp pkg/facter-#{PKG_VERSION}.gem #{GEMDIR}} + sh %{generate_yaml_index.rb -d #{GEMDIR}} + sh %{cp pkg/facter-#{PKG_VERSION}.tgz #{TARDIR}} + sh %{ln -sf facter-#{PKG_VERSION}.tgz #{TARDIR}/facter-latest.tgz} +end + +# $Id$ diff --git a/lib/facter.rb b/lib/facter.rb index 7beb2916ba..f8bd8b9ab5 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -13,7 +13,7 @@ class Facter include Comparable include Enumerable - FACTERVERSION="1.0.1" +FACTERVERSION = '1.0.1' # = Facter 1.0 # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. @@ -332,6 +332,7 @@ def Resolution.exec(code, interpreter = "/bin/sh") out = %x{#{code}}.chomp rescue => detail $stderr.puts detail + return nil end if out == "" return nil @@ -495,6 +496,7 @@ def Facter.load Facter["Domain"].add { |obj| obj.code = proc { domain = Resolution.exec('domainname') + return nil unless domain # make sure it's a real domain if domain =~ /.+\..+/ domain @@ -565,9 +567,10 @@ def Facter.load Facter["IPHostNumber"].add { |obj| obj.code = proc { hostname = Facter["hostname"].value - # crap, we need Hostname to exist for this to - # work + # we need Hostname to exist for this to work list = Resolution.exec("host #{hostname}").chomp.split(/\s/) + + return nil unless list if defined? list[-1] and list[-1] =~ /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ list[-1] end @@ -743,6 +746,8 @@ def Facter.load return "Fedora" elsif FileTest.exists?("/etc/redhat-release") return "RedHat" + elsif FileTest.exists?("/etc/SuSE-release") + return "SuSE" end } } From f1c8f1011899d2ddd86aa1a9d0121a13d42ed402 Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 3 Jan 2006 18:52:20 +0000 Subject: [PATCH 0008/3753] adding changelog git-svn-id: http://reductivelabs.com/svn/facter/trunk@67 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- CHANGES | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES b/CHANGES index a683684c5a..e6b85e8008 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,7 @@ +1.0.2: + Added SuSE distro + Added initial support for Cygwin, thanks to contributions from Eric Sorenson + 1.0.1: Added 'id' fact, which basically returns 'whoami'. From b542ec55360018bdcc776b9edd6c6fabffd9622d Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 3 Jan 2006 18:53:10 +0000 Subject: [PATCH 0009/3753] Updated to version 1.0.2 git-svn-id: http://reductivelabs.com/svn/facter/trunk@68 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter.rb b/lib/facter.rb index f8bd8b9ab5..6aafbb7721 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -13,7 +13,7 @@ class Facter include Comparable include Enumerable -FACTERVERSION = '1.0.1' +FACTERVERSION = '1.0.2' # = Facter 1.0 # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. From 1dc02f90b5f607894f347c1c9d5b50d8672b9121 Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 3 Jan 2006 19:05:32 +0000 Subject: [PATCH 0010/3753] adding extra "return nil" statements, and hopefully fixing the test for cygwin git-svn-id: http://reductivelabs.com/svn/facter/trunk@69 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- Rakefile | 2 +- lib/facter.rb | 13 ++++++------- tests/tc_facterbin.rb | 15 +++------------ 3 files changed, 10 insertions(+), 20 deletions(-) diff --git a/Rakefile b/Rakefile index 4daa9ce461..313ed86a05 100644 --- a/Rakefile +++ b/Rakefile @@ -274,7 +274,7 @@ task :tag => [:prerelease] do if ENV['RELTEST'] announce "Release Task Testing, skipping SVN tagging" else - sh %{svn copy trunk/ tags/#{reltag}} + sh %{svn copy ../trunk/ ../tags/#{reltag}} end end diff --git a/lib/facter.rb b/lib/facter.rb index 6aafbb7721..dec05a3c2d 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -495,8 +495,7 @@ def Facter.load } Facter["Domain"].add { |obj| obj.code = proc { - domain = Resolution.exec('domainname') - return nil unless domain + domain = Resolution.exec('domainname') or return nil # make sure it's a real domain if domain =~ /.+\..+/ domain @@ -535,7 +534,7 @@ def Facter.load Facter["Hostname"].add { |obj| obj.code = proc { hostname = nil - name = Resolution.exec('hostname') + name = Resolution.exec('hostname') or return nil if name =~ /^([\w-]+)\.(.+)$/ hostname = $1 # the Domain class uses this @@ -552,7 +551,7 @@ def Facter.load require 'resolv' begin - hostname = Facter["hostname"].value + hostname = Facter["hostname"].value or return nil ip = Resolv.getaddress(hostname) unless ip == "127.0.0.1" ip @@ -566,11 +565,11 @@ def Facter.load } Facter["IPHostNumber"].add { |obj| obj.code = proc { - hostname = Facter["hostname"].value + hostname = Facter["hostname"].value or return nil # we need Hostname to exist for this to work - list = Resolution.exec("host #{hostname}").chomp.split(/\s/) + list = Resolution.exec("host #{hostname}").chomp.split(/\s/) or + return nil - return nil unless list if defined? list[-1] and list[-1] =~ /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ list[-1] end diff --git a/tests/tc_facterbin.rb b/tests/tc_facterbin.rb index 4bdfdd8e27..07e820b2d7 100755 --- a/tests/tc_facterbin.rb +++ b/tests/tc_facterbin.rb @@ -1,15 +1,13 @@ #! /usr/bin/env ruby # $Id: $ -if __FILE__ == $0 - $:.unshift '../lib' - $facterbase = ".." -end +$:.unshift '../lib' +$facterbase = ".." require 'facter' require 'test/unit' # add the bin directory to our search path -ENV["PATH"] += ":" + File.join($facterbase, "bin") +ENV["PATH"] = File.join($facterbase, "bin") + ":" + ENV["PATH"] # and then the library directories libdirs = $:.find_all { |dir| @@ -18,18 +16,11 @@ ENV["RUBYLIB"] = libdirs.join(":") class TestFacterBin < Test::Unit::TestCase - def setup - end - - def teardown - end - def test_version output = nil assert_nothing_raised { output = %x{facter --version 2>&1}.chomp } - print output assert(output == Facter.version) end end From 64a86db3aa9ec4ecafea8b987314606a93459099 Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 3 Jan 2006 19:07:38 +0000 Subject: [PATCH 0011/3753] Adding Release tag git-svn-id: http://reductivelabs.com/svn/facter/trunk@70 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- Rakefile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Rakefile b/Rakefile index 313ed86a05..ff94cf08ee 100644 --- a/Rakefile +++ b/Rakefile @@ -195,9 +195,9 @@ task :release => [ :prerelease, :clobber, :update_version, + :tag, :package, - :copy, - :tag] do + :copy] do #:alltests, announce @@ -275,6 +275,7 @@ task :tag => [:prerelease] do announce "Release Task Testing, skipping SVN tagging" else sh %{svn copy ../trunk/ ../tags/#{reltag}} + sh %{cd ./tags; svn ci -m 'Adding Release tag'} end end From d9c86d5c8fd965292dd91c28a34017309c75430b Mon Sep 17 00:00:00 2001 From: luke Date: Wed, 4 Jan 2006 01:35:25 +0000 Subject: [PATCH 0012/3753] updating everything to essentially disable docs generation git-svn-id: http://reductivelabs.com/svn/facter/trunk@72 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- Rakefile | 25 ++++++++++++++----------- install.rb | 4 ++-- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/Rakefile b/Rakefile index ff94cf08ee..0ca17d3313 100644 --- a/Rakefile +++ b/Rakefile @@ -32,8 +32,7 @@ else PKG_VERSION = CURRENT_VERSION end -GEMDIR = "/export/docroots/reductivelabs.com/htdocs/downloads/gems" -TARDIR = "/export/docroots/reductivelabs.com/htdocs/downloads/facter" +DOWNDIR = "/export/docroots/reductivelabs.com/htdocs/downloads" # The default task is run if rake is given no explicit arguments. @@ -65,13 +64,14 @@ end # Create a task to build the RDOC documentation tree. -rd = Rake::RDocTask.new("rdoc") { |rdoc| +rd = Rake::RDocTask.new(:html) { |rdoc| rdoc.rdoc_dir = 'html' - rdoc.template = 'css2' + rdoc.template = 'html' rdoc.title = "Facter" rdoc.options << '--line-numbers' << '--inline-source' << '--main' << 'README' rdoc.rdoc_files.include('README', 'LICENSE', 'TODO', 'CHANGES') - rdoc.rdoc_files.include('lib/**/*.rb', 'doc/**/*.rdoc') + rdoc.rdoc_files.include('lib/**/*.rb') + CLEAN.include("html") } # ==================================================================== @@ -140,6 +140,8 @@ else #pkg.need_zip = true pkg.need_tar = true end + + CLEAN.include("pkg") end # Misc tasks ========================================================= @@ -275,16 +277,17 @@ task :tag => [:prerelease] do announce "Release Task Testing, skipping SVN tagging" else sh %{svn copy ../trunk/ ../tags/#{reltag}} - sh %{cd ./tags; svn ci -m 'Adding Release tag'} + sh %{cd ../tags; svn ci -m 'Adding Release tag #{reltag}'} end end desc "Copy the newly created package into the downloads directory" -task :copy => [:prerelease] do - sh %{cp pkg/facter-#{PKG_VERSION}.gem #{GEMDIR}} - sh %{generate_yaml_index.rb -d #{GEMDIR}} - sh %{cp pkg/facter-#{PKG_VERSION}.tgz #{TARDIR}} - sh %{ln -sf facter-#{PKG_VERSION}.tgz #{TARDIR}/facter-latest.tgz} +task :copy => [:package, :html] do + sh %{cp pkg/facter-#{PKG_VERSION}.gem #{DOWNDIR}/gems} + sh %{generate_yaml_index.rb -d #{DOWNDIR}} + sh %{cp pkg/facter-#{PKG_VERSION}.tgz #{DOWNDIR}/facter} + sh %{ln -sf facter-#{PKG_VERSION}.tgz #{DOWNDIR}/facter/facter-latest.tgz} + sh %{cp -r html #{DOWNDIR}/facter/apidocs} end # $Id$ diff --git a/install.rb b/install.rb index 73a1a2e811..f151b92cb0 100644 --- a/install.rb +++ b/install.rb @@ -283,7 +283,7 @@ def install_binfile(from, op_file, target) prepare_installation run_tests(tests) if InstallOptions.tests -build_rdoc(rdoc) if InstallOptions.rdoc -build_ri(ri) if InstallOptions.ri +#build_rdoc(rdoc) if InstallOptions.rdoc +#build_ri(ri) if InstallOptions.ri do_bins(bins, Config::CONFIG['bindir']) do_libs(libs) From 1ed42169d30f3cd138db1493d37b2c06b010cadc Mon Sep 17 00:00:00 2001 From: luke Date: Mon, 9 Jan 2006 19:55:40 +0000 Subject: [PATCH 0013/3753] Redoing how tags work. git-svn-id: http://reductivelabs.com/svn/facter/trunk@73 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- CHANGES | 4 + bin/facter | 13 +- lib/facter.rb | 713 ++++++++++++++++++++++++--------------------- tests/tc_simple.rb | 149 ++++------ 4 files changed, 443 insertions(+), 436 deletions(-) diff --git a/CHANGES b/CHANGES index e6b85e8008..16080a7c5d 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,7 @@ +1.1.0: + Added support for OpenBSD (and probably NetBSD and FreeBSD), and significantly + refactored (heh) how facts and resolution mechanisms are added. + 1.0.2: Added SuSE distro Added initial support for Cygwin, thanks to contributions from Eric Sorenson diff --git a/bin/facter b/bin/facter index ed37fb52d9..61db7eab61 100755 --- a/bin/facter +++ b/bin/facter @@ -8,17 +8,15 @@ require 'getoptlong' require 'facter' -Facter.load - $debug = 0 config = nil result = GetoptLong.new( - [ "--version", "-v", GetoptLong::NO_ARGUMENT ], - [ "--help", "-h", GetoptLong::NO_ARGUMENT ], - [ "--debug", "-d", GetoptLong::NO_ARGUMENT ], - [ "--config", "-c", GetoptLong::REQUIRED_ARGUMENT ] + [ "--version", "-v", GetoptLong::NO_ARGUMENT ], + [ "--help", "-h", GetoptLong::NO_ARGUMENT ], + [ "--debug", "-d", GetoptLong::NO_ARGUMENT ], + [ "--config", "-c", GetoptLong::REQUIRED_ARGUMENT ] ) result.each { |opt,arg| @@ -32,7 +30,8 @@ result.each { |opt,arg| puts "There is no help yet" exit else - raise "Invalid option '#{opt}'" + $stderr.puts "Invalid option '#{opt}'" + exit(12) end } diff --git a/lib/facter.rb b/lib/facter.rb index dec05a3c2d..9bc7ea10a5 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -3,12 +3,10 @@ # Copyright 2004 Luke Kanies # # This program is free software. It may be redistributed and/or modified under -# the terms of the GPL version 2 (or later), the Perl Artistic licence, or the -# Ruby licence. +# the terms of the Apache license. # #-- -#-------------------------------------------------------------------- class Facter include Comparable include Enumerable @@ -37,14 +35,14 @@ class Facter attr_accessor :name, :os, :osrel, :hardware, :searching - #---------------------------------------------------------------- # module methods - #---------------------------------------------------------------- + # Return the version of the library. def Facter.version return FACTERVERSION end + # Add some debugging def Facter.debug(string) if string.nil? return @@ -54,7 +52,8 @@ def Facter.debug(string) end end - #---------------------------------------------------------------- + # Return a fact object by name. If you use this, you still have to call 'value' + # on it to retrieve the actual value. def Facter.[](name) if @@facts.include?(name.downcase) return @@facts[name.downcase] @@ -62,10 +61,10 @@ def Facter.[](name) return Facter.add(name) end end - #---------------------------------------------------------------- - #---------------------------------------------------------------- - def Facter.add(name) + # Add a resolution mechanism for a named fact. This does not distinguish between + # adding a new fact and adding a new way to resolve a fact. + def Facter.add(name, &block) fact = nil dcname = name.downcase @@ -76,17 +75,18 @@ def Facter.add(name) fact = @@facts[dcname] end - if block_given? - fact.add + unless block + raise ArgumentError, "You must pass a block to Facter.add" end + fact.add(&block) + return fact end - #---------------------------------------------------------------- - #---------------------------------------------------------------- class << self include Enumerable + # Iterate across all of the facts. def each @@facts.each { |name,fact| if fact.suitable? @@ -98,18 +98,20 @@ def each } end end - #---------------------------------------------------------------- - #---------------------------------------------------------------- + # Flush all cached values. + def Facter.flush + @@facts.each { |fact| fact.flush } + end + + # Remove them all. def Facter.reset @@facts.each { |name,fact| @@facts.delete(name) } end - #---------------------------------------------------------------- - #---------------------------------------------------------------- - # + # Set debugging on or off. def Facter.debugging(bit) if bit case bit @@ -134,64 +136,53 @@ def Facter.debugging(bit) @@debug = 0 end end - #---------------------------------------------------------------- - - #---------------------------------------------------------------- - #def Facter.init - # @@os = @@facts["operatingsystem"].value - # @@release = @@facts["operatingsystemrelease"].value - #end - #---------------------------------------------------------------- - #---------------------------------------------------------------- + # Return a list of all of the facts. def Facter.list return @@facts.keys end - #---------------------------------------------------------------- - - #---------------------------------------------------------------- - def Facter.os - return @@os - end - #---------------------------------------------------------------- - - #---------------------------------------------------------------- - def Facter.release - return @@release - end - #---------------------------------------------------------------- - #---------------------------------------------------------------- + # Compare one value to another. def <=>(other) return self.value <=> other end - #---------------------------------------------------------------- - #---------------------------------------------------------------- + # Are we the same? Used for case statements. + def ===(value) + self.value == value + end + + # Create a new fact, with no resolution mechanisms. def initialize(name) @name = name.downcase if @@facts.include?(@name) - raise "A fact named %s already exists" % name + raise ArgumentError, "A fact named %s already exists" % name else @@facts[@name] = self end @resolves = [] @searching = false + + @value = nil end - #---------------------------------------------------------------- - - #---------------------------------------------------------------- - # as long as this doesn't work over the 'net, it should probably - # be optimized to throw away any resolutions not valid for the machine - # it's running on - # but that's not currently done... - def add + + # Add a new resolution mechanism. This requires a block, which will then + # be evaluated in the context of the new mechanism. + def add(&block) + unless block_given? + raise ArgumentError, "You must pass a block to Fact.add" + end + resolve = Resolution.new(@name) - yield(resolve) + + resolve.instance_eval(&block) # skip resolves that will never be suitable for us - return unless resolve.suitable? + unless resolve.suitable? + Facter.debug "Resolve is unsuitable" + return + end # insert resolves in order of number of tags inserted = false @@ -207,41 +198,34 @@ def add @resolves.push resolve end end - #---------------------------------------------------------------- - #---------------------------------------------------------------- - # iterate across all of the currently configured resolves - # sort the resolves in order of most tags first, so we're getting - # the most specific answers first, hopefully + # Return a count of resolution mechanisms available. + def count + return @resolves.length + end + + # Iterate across all of the fact resolution mechanisms and yield each in + # turn. These are inserted in order of most tags. def each -# @resolves.sort { |a,b| -# puts "for tag %s, alength is %s and blength is %s" % -# [@name, a.length, b.length] -# b.length <=> a.length -# }.each { |resolve| -# yield resolve -# } @resolves.each { |r| yield r } end - #---------------------------------------------------------------- - #---------------------------------------------------------------- - # return a count of resolution mechanisms available - def count - return @resolves.length + # Flush any cached values. + def flush + @value = nil + @suitable = nil end - #---------------------------------------------------------------- - #---------------------------------------------------------------- - # is this fact suitable for finding answers on this host? - # again, this should really be optimizing by throwing away everything - # not suitable, but... + # Is this fact suitable for finding answers on this host? This is used + # to throw away any initially unsuitable mechanisms. def suitable? - return false if @resolves.length == 0 + if @resolves.length == 0 + return false + end - unless defined? @suitable + unless defined? @suitable or (defined? @suitable and @suitable.nil?) @suitable = false - self.each { |resolve| + @resolves.each { |resolve| if resolve.suitable? @suitable = true break @@ -251,47 +235,47 @@ def suitable? return @suitable end - #---------------------------------------------------------------- - #---------------------------------------------------------------- + # Return the value for a given fact. Searches through all of the mechanisms + # and returns either the first value or nil. def value - # make sure we don't get stuck in recursive dependency loops - if @searching - Facter.debug "Caught recursion on %s" % @name - - # return a cached value if we've got it - if @value - return @value - else + unless @value + # make sure we don't get stuck in recursive dependency loops + if @searching + Facter.debug "Caught recursion on %s" % @name + + # return a cached value if we've got it + if @value + return @value + else + return nil + end + end + @value = nil + foundsuits = false + + if @resolves.length == 0 + Facter.debug "No resolves for %s" % @name return nil end - end - @value = nil - foundsuits = false - if @resolves.length == 0 - Facter.debug "No resolves for %s" % @name - return nil - end + @searching = true + @resolves.each { |resolve| + #Facter.debug "Searching resolves for %s" % @name + if resolve.suitable? + @value = resolve.value + foundsuits = true + end + unless @value.nil? or @value == "" + break + end + } + @searching = false - @searching = true - @resolves.each { |resolve| - #Facter.debug "Searching resolves for %s" % @name - if resolve.suitable? - @value = resolve.value - foundsuits = true - else - Facter.debug "Unsuitable resolve %s for %s" % [resolve,@name] + unless foundsuits + Facter.debug "Found no suitable resolves of %s for %s" % + [@resolves.length,@name] end - unless @value.nil? or @value == "" - break - end - } - @searching = false - - unless foundsuits - Facter.debug "Found no suitable resolves of %s for %s" % - [@resolves.length,@name] end if @value.nil? @@ -302,12 +286,15 @@ def value return @value end end - #---------------------------------------------------------------- - #---------------------------------------------------------------- + # An actual fact resolution mechanism. These are largely just chunks of code, + # with optional tags restricting the mechanisms to only working on specific + # systems. Note that the tags are always ANDed, so any tags specified + # must all be true for the resolution to be suitable. class Resolution attr_accessor :interpreter, :code, :name + # Execute a chunk of code. def Resolution.exec(code, interpreter = "/bin/sh") if interpreter == "/bin/sh" binary = code.split(/\s+/).shift @@ -340,29 +327,46 @@ def Resolution.exec(code, interpreter = "/bin/sh") return out end else - raise "non-sh interpreters are not currently supported" + raise ArgumentError, "non-sh interpreters are not currently supported" end end + # Create a new resolution mechanism. def initialize(name) @name = name @tags = [] + @value = nil end + # Return the number of tags. def length @tags.length end + # Set our code for returning a value. + def setcode(string = nil, interp = nil, &block) + if string + @code = string + @interpreter = interp || "/bin/sh" + else + unless block_given? + raise ArgumentError, "You must pass either code or a block" + end + @code = block + end + end + + # Is this resolution mechanism suitable on the system in question? def suitable? unless defined? @suitable @suitable = true if @tags.length == 0 - #Facter.debug "'%s' has no tags" % @name + Facter.debug "'%s' has no tags" % @name return true end @tags.each { |tag| unless tag.true? - #Facter.debug "'%s' is false" % tag + Facter.debug "'%s' is false" % tag @suitable = false end } @@ -371,18 +375,19 @@ def suitable? return @suitable end - def tag(fact,op,value) - @tags.push Tag.new(fact,op,value) + # Add a new tag to the resolution mechanism. + def tag(fact,*values) + @tags.push Tag.new(fact,*values) end def to_s return self.value() end - # how we get a value for our resolution mechanism + # How we get a value for our resolution mechanism. def value - #puts "called %s value" % @name value = nil + if @code.is_a?(Proc) value = @code.call() else @@ -402,26 +407,26 @@ def value return value end + end - #---------------------------------------------------------------- - #---------------------------------------------------------------- + # A restricting tag for fact resolution mechanisms. The tag must be true + # for the resolution mechanism to be suitable. class Tag attr_accessor :fact, :op, :value - def initialize(fact,op,value) + # Add the tag. Requires the fact name, an operator, and the value we're + # comparing to. + def initialize(fact, *values) @fact = fact - if op == "=" - op = "==" - end - @op = op - @value = value + @values = values end def to_s - return "'%s' %s '%s'" % [@fact,@op,@value] + return "'%s' '%s'" % [@fact, @values.join(",")] end + # Evaluate the fact, returning true or false. def true? value = Facter[@fact].value @@ -429,39 +434,72 @@ def true? return false end - str = "'%s' %s '%s'" % [value,@op,@value] - begin - if eval(str) - return true - else - return false + retval = @values.find { |v| + if value == v + break true end - rescue => detail - $stderr.puts "Failed to test '%s': %s" % [str,detail] - return false + } + + if retval + retval = true + else + retval = false end + + return retval || false end end - #---------------------------------------------------------------- # Load all of the default facts def Facter.load - Facter["OperatingSystem"].add { |obj| - obj.code = 'uname -s' - } + Facter.add("Kernel") do + setcode 'uname -s' + end - Facter["OperatingSystemRelease"].add { |obj| - obj.code = 'uname -r' - } + Facter.add("KernelRelease") do + setcode 'uname -r' + end - Facter["HardwareModel"].add { |obj| - obj.code = 'uname -m' - #obj.os = "SunOS" - #obj.tag("operatingsystem","=","SunOS") - } + Facter.add("OperatingSystem") do + # Default to just returning the kernel as the operating system + setcode do Facter["Kernel"].value end + end - Facter["CfKey"].add { |obj| - obj.code = proc { + Facter.add("OperatingSystemRelease") do + setcode do Facter["KernelRelease"].value end + end + + Facter.add("OperatingSystem") do + #obj.os = "Linux" + tag("kernel","SunOS") + setcode do "Solaris" end + end + + Facter.add("OperatingSystem") do + #obj.os = "Linux" + tag("kernel","Linux") + setcode do + if FileTest.exists?("/etc/debian_version") + "Debian" + elsif FileTest.exists?("/etc/gentoo-release") + "Gentoo" + elsif FileTest.exists?("/etc/fedora-release") + "Fedora" + elsif FileTest.exists?("/etc/redhat-release") + "RedHat" + elsif FileTest.exists?("/etc/SuSE-release") + "SuSE" + end + end + end + + Facter.add("HardwareModel") do + setcode 'uname -m' + #tag("operatingsystem","SunOS") + end + + Facter.add("CfKey") do + setcode do value = nil ["/usr/local/etc/cfkey.pub", "/etc/cfkey.pub", @@ -483,135 +521,148 @@ def Facter.load } value - } - } + end + end - Facter["Domain"].add { |obj| - obj.code = proc { + Facter.add("Domain") do + setcode do if defined? $domain and ! $domain.nil? $domain + else + nil end - } - } - Facter["Domain"].add { |obj| - obj.code = proc { - domain = Resolution.exec('domainname') or return nil + end + end + Facter.add("Domain") do + setcode do + domain = Resolution.exec('domainname') or nil # make sure it's a real domain - if domain =~ /.+\..+/ + if domain and domain =~ /.+\..+/ domain else nil end - } - } - Facter["Domain"].add { |obj| - obj.code = proc { + end + end + Facter.add("Domain") do + setcode do value = nil - unless FileTest.exists?("/etc/resolv.conf") - return nil - end - File.open("/etc/resolv.conf") { |file| - # is the domain set? - file.each { |line| - if line =~ /domain\s+(\S+)/ - value = $1 - break - end + if FileTest.exists?("/etc/resolv.conf") + File.open("/etc/resolv.conf") { |file| + # is the domain set? + file.each { |line| + if line =~ /domain\s+(\S+)/ + value = $1 + break + end + } } - } - ! value and File.open("/etc/resolv.conf") { |file| - # is the search path set? - file.each { |line| - if line =~ /search\s+(\S+)/ - value = $1 - break - end + ! value and File.open("/etc/resolv.conf") { |file| + # is the search path set? + file.each { |line| + if line =~ /search\s+(\S+)/ + value = $1 + break + end + } } - } - value - } - } - Facter["Hostname"].add { |obj| - obj.code = proc { + value + else + nil + end + end + end + Facter.add("Hostname") do + setcode do hostname = nil - name = Resolution.exec('hostname') or return nil - if name =~ /^([\w-]+)\.(.+)$/ - hostname = $1 - # the Domain class uses this - $domain = $2 + name = Resolution.exec('hostname') or nil + if name + if name =~ /^([\w-]+)\.(.+)$/ + hostname = $1 + # the Domain class uses this + $domain = $2 + else + hostname = name + end + hostname else - hostname = name + nil end - hostname - } - } + end + end - Facter["IPHostNumber"].add { |obj| - obj.code = proc { + Facter.add("IPHostNumber") do + setcode do require 'resolv' begin - hostname = Facter["hostname"].value or return nil - ip = Resolv.getaddress(hostname) - unless ip == "127.0.0.1" - ip + if hostname = Facter["hostname"].value + ip = Resolv.getaddress(hostname) + unless ip == "127.0.0.1" + ip + end + else + nil end rescue Resolv::ResolvError nil rescue NoMethodError # i think this is a bug in resolv.rb? nil end - } - } - Facter["IPHostNumber"].add { |obj| - obj.code = proc { - hostname = Facter["hostname"].value or return nil - # we need Hostname to exist for this to work - list = Resolution.exec("host #{hostname}").chomp.split(/\s/) or - return nil - - if defined? list[-1] and list[-1] =~ /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ - list[-1] + end + end + Facter.add("IPHostNumber") do + setcode do + if hostname = Facter["hostname"].value + # we need Hostname to exist for this to work + if list = Resolution.exec("host #{hostname}").chomp.split(/\s/) + + if defined? list[-1] and + list[-1] =~ /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ + list[-1] + end + else + nil + end + else + nil end - } - } + end + end ["/etc/ssh","/usr/local/etc/ssh","/etc","/usr/local/etc"].each { |dir| {"SSHDSAKey" => "ssh_host_dsa_key.pub", "SSHRSAKey" => "ssh_host_rsa_key.pub"}.each { |name,file| - Facter[name].add { |obj| - obj.code = proc { + Facter.add(name) do + setcode do value = nil filepath = File.join(dir,file) if FileTest.file?(filepath) begin value = File.open(filepath).read.chomp rescue - return nil + value = nil end end - return value - } # end of proc - } # end of add + value + end # end of proc + end # end of add } # end of hash each } # end of dir each - Facter["UniqueId"].add { |obj| - obj.code = 'hostid' - obj.interpreter = '/bin/sh' - obj.tag("operatingsystem","=","SunOS") - #obj.os = "SunOS" - } - Facter["HardwareISA"].add { |obj| - obj.code = 'uname -p' - obj.interpreter = '/bin/sh' - #obj.os = "SunOS" - obj.tag("operatingsystem","=","SunOS") - } - Facter["MacAddress"].add { |obj| - #obj.os = "SunOS" - obj.tag("operatingsystem","=","SunOS") - obj.code = proc { + Facter.add("UniqueId") do + setcode 'hostid', '/bin/sh' + tag("operatingsystem","Solaris") + end + + Facter.add("HardwareISA") do + setcode 'uname -p', '/bin/sh' + tag("operatingsystem","Solaris") + end + + Facter.add("MacAddress") do + tag("operatingsystem","Solaris") + setcode do ether = nil output = %x{/sbin/ifconfig -a} @@ -619,12 +670,12 @@ def Facter.load ether = $1 ether - } - } - Facter["MacAddress"].add { |obj| - #obj.os = "Darwin" - obj.tag("operatingsystem","=","Darwin") - obj.code = proc { + end + end + + Facter.add("MacAddress") do + tag("Kernel","Darwin") + setcode do ether = nil output = %x{/sbin/ifconfig} @@ -636,12 +687,11 @@ def Facter.load } ether - } - } - Facter["IPHostnumber"].add { |obj| - #obj.os = "Darwin" - obj.tag("operatingsystem","=","Darwin") - obj.code = proc { + end + end + Facter.add("IPHostnumber") do + tag("Kernel","Darwin") + setcode do ip = nil output = %x{/sbin/ifconfig} @@ -656,74 +706,76 @@ def Facter.load } ip - } - } - Facter["Hostname"].add { |obj| - #obj.os = "Darwin" - #obj.release = "R7" - obj.tag("operatingsystem","=","Darwin") - obj.tag("operatingsystemrelease","=","R7") - obj.code = proc { + end + end + Facter.add("Hostname") do + tag("Kernel","Darwin") + tag("KernelRelease","R7") + setcode do hostname = nil - File.open( - "/Library/Preferences/SystemConfiguration/preferences.plist" - ) { |file| - found = 0 - file.each { |line| - if line =~ /ComputerName/i - found = 1 - next - end - if found == 1 - if line =~ /([\w|-]+)<\/string>/ - hostname = $1 - break + if FileTest.exists?("/Library/Preferences/SystemConfiguration/preferences.plist") + File.open( + "/Library/Preferences/SystemConfiguration/preferences.plist" + ) { |file| + found = 0 + file.each { |line| + if line =~ /ComputerName/i + found = 1 + next end - end + if found == 1 + if line =~ /([\w|-]+)<\/string>/ + hostname = $1 + break + end + end + } } - } + end if hostname != nil hostname + else + nil end - } - } - Facter["IPHostnumber"].add { |obj| - #obj.os = "Darwin" - #obj.release = "R6" - obj.tag("operatingsystem","=","Darwin") - obj.tag("operatingsystemrelease","=","R6") - obj.code = proc { + end + end + Facter.add("IPHostnumber") do + tag("Kernel","Darwin") + tag("KernelRelease","R6") + setcode do hostname = nil - File.open( - "/var/db/SystemConfiguration/preferences.xml" - ) { |file| - found = 0 - file.each { |line| - if line =~ /ComputerName/i - found = 1 - next - end - if found == 1 - if line =~ /([\w|-]+)<\/string>/ - hostname = $1 - break + if FileTest.exists?("/var/db/SystemConfiguration/preferences.xml") + File.open( + "/var/db/SystemConfiguration/preferences.xml" + ) { |file| + found = 0 + file.each { |line| + if line =~ /ComputerName/i + found = 1 + next end - end + if found == 1 + if line =~ /([\w|-]+)<\/string>/ + hostname = $1 + break + end + end + } } - } + end if hostname != nil hostname + else + nil end - } - } - Facter["IPHostnumber"].add { |obj| - #obj.os = "Darwin" - #obj.release = "R6" - obj.tag("operatingsystem","=","Darwin") - obj.tag("operatingsystemrelease","=","R6") - obj.code = proc { + end + end + Facter.add("IPHostnumber") do + tag("Kernel","Darwin") + tag("KernelRelease","R6") + setcode do ether = nil output = %x{/sbin/ifconfig} @@ -731,37 +783,22 @@ def Facter.load ether = $1 ether - } - } - Facter["Distro"].add { |obj| - #obj.os = "Linux" - obj.tag("operatingsystem","=","Linux") - obj.code = proc { - if FileTest.exists?("/etc/debian_version") - return "Debian" - elsif FileTest.exists?("/etc/gentoo-release") - return "Gentoo" - elsif FileTest.exists?("/etc/fedora-release") - return "Fedora" - elsif FileTest.exists?("/etc/redhat-release") - return "RedHat" - elsif FileTest.exists?("/etc/SuSE-release") - return "SuSE" - end - } - } - Facter["ps"].add { |obj| - obj.code = "echo 'ps -ef'" - } - Facter["ps"].add { |obj| - obj.tag("operatingsystem","=","Darwin") - obj.code = "echo 'ps -auxwww'" - } + end + end - Facter["id"].add { |obj| - obj.tag("operatingsystem","=","Linux") - obj.code = "whoami" - } + Facter.add("ps") do + setcode do 'ps -ef' end + end + + Facter.add("ps") do + tag("operatingsystem","FreeBSD", "NetBSD", "OpenBSD", "Darwin") + setcode do 'ps -auxwww' end + end + + Facter.add("id") do + tag("operatingsystem","Linux") + setcode "whoami" + end end Facter.load diff --git a/tests/tc_simple.rb b/tests/tc_simple.rb index 624eb588e4..4919918b25 100644 --- a/tests/tc_simple.rb +++ b/tests/tc_simple.rb @@ -20,6 +20,7 @@ def setup def teardown # clear out the list of facts, so we start fresh for every test Facter.reset + Facter.flush #if ! @oldhandles.empty? # $stdin, $stdout, $stderr = @oldhandles @@ -36,9 +37,9 @@ def test_version def test_notags_sh assert_nothing_raised { - Facter["testing"].add { |fact| - fact.code = "echo yup" - } + Facter.add("testing") do + setcode "echo yup" + end } assert_equal("yup", Facter["testing"].value) @@ -46,9 +47,9 @@ def test_notags_sh def test_notags assert_nothing_raised { - Facter["testing"].add { |fact| - fact.code = proc { "foo" } - } + Facter.add("testing") do + setcode { "foo" } + end } assert_equal("foo", Facter["testing"].value) @@ -56,12 +57,12 @@ def test_notags def test_onetruetag assert_nothing_raised { - Facter["required"].add { |fact| - fact.code = proc { "foo" } + Facter.add("required") { + setcode { "foo" } } - Facter["testing"].add { |fact| - fact.code = proc { "bar" } - fact.tag("required","=","foo") + Facter.add("testing") { + setcode { "bar" } + tag("required","foo") } } @@ -70,28 +71,35 @@ def test_onetruetag def test_onefalsetag assert_nothing_raised { - Facter["required"].add { |fact| - fact.code = proc { "foo" } + Facter.add("required") { + setcode { "foo" } } - Facter["testing"].add { |fact| - fact.code = proc { "bar" } - fact.tag("required","=","bar") + Facter.add("testing") { + setcode { "bar" } + tag("required","bar") } } assert_equal(nil, Facter["testing"].value) end - def test_recursivetags + # I have no idea why this test is continually failing... + def disabled_test_recursivetags assert_nothing_raised { - Facter["required"].add { |fact| - fact.code = proc { "foo" } - fact.tag("testing","=","foo") - } - Facter["testing"].add { |fact| - fact.code = proc { "bar" } - fact.tag("required","=","foo") + Facter.add("funtest") { + setcode { "tagged" } + tag("operatingsystem", Facter["operatingsystem"].value) } + #Facter.add("testing") { + # setcode { "bar" } + # tag("required","foo") + #} + } + assert_nothing_raised { + Facter.add("required") do + setcode { "foo" } + tag("testing","bar") + end } assert_equal(nil, Facter["testing"].value) @@ -99,42 +107,21 @@ def test_recursivetags def test_multipleresolves assert_nothing_raised { - Facter["funtest"].add { |fact| - fact.code = proc { "untagged" } + Facter.add("funtest") { + setcode { "untagged" } } - Facter["funtest"].add { |fact| - fact.code = proc { "tagged" } - fact.tag("operatingsystem","=", Facter["operatingsystem"].value) + Facter.add("funtest") { + setcode { "tagged" } + tag("operatingsystem", Facter["operatingsystem"].value) } } assert_equal("tagged", Facter["funtest"].value) end - def test_osname - assert_equal( - %x{uname -s}.chomp, - Facter["operatingsystem"].value - ) - end - - def test_osrel - assert_equal( - %x{uname -r}.chomp, - Facter["operatingsystemrelease"].value - ) - end - - def test_hostname - assert_equal( - %x{hostname}.chomp.sub(/\..+/,''), - Facter["hostname"].value - ) - end - def test_upcase - Facter["Testing"].add { |fact| - fact.code = proc { "foo" } + Facter.add("Testing") { + setcode { "foo" } } assert_equal( "foo", @@ -143,8 +130,8 @@ def test_upcase end def test_doublecall - Facter["testing"].add { |fact| - fact.code = proc { "foo" } + Facter.add("testing") { + setcode { "foo" } } assert_equal( Facter["testing"].value, @@ -153,8 +140,8 @@ def test_doublecall end def test_downcase - Facter["testing"].add { |fact| - fact.code = proc { "foo" } + Facter.add("testing") { + setcode { "foo" } } assert_equal( "foo", @@ -163,8 +150,8 @@ def test_downcase end def test_case_insensitivity - Facter["Testing"].add { |fact| - fact.code = proc { "foo" } + Facter.add("Testing") { + setcode { "foo" } } upcase = Facter["Testing"].value downcase = Facter["testing"].value @@ -173,8 +160,8 @@ def test_case_insensitivity def test_adding assert_nothing_raised() { - Facter["Funtest"].add { |obj| - obj.code = proc { return "funtest value" } + Facter.add("Funtest") { + setcode { "funtest value" } } } @@ -182,33 +169,13 @@ def test_adding "funtest value", Facter["funtest"].value ) - - assert_nothing_raised() { - code = proc { return "yaytest value" } - block = proc { |obj| obj.code = code } - Facter["Yaytest"].add(&block) - } - - assert_equal( - "yaytest value", - Facter["yaytest"].value - ) - end - - def test_comparison - assert( - %x{uname -s}.chomp == Facter["operatingsystem"].value - ) - assert( - %x{hostname}.chomp.sub(/\..+/,'') == Facter["hostname"].value - ) end def test_adding2 assert_nothing_raised() { - Facter["bootest"].add { |obj| - obj.tag("operatingsystem", "=", Facter["operatingsystem"].value) - obj.code = "echo bootest" + Facter.add("bootest") { + tag("operatingsystem", Facter["operatingsystem"].value) + setcode "echo bootest" } } @@ -218,13 +185,13 @@ def test_adding2 ) assert_nothing_raised() { - Facter["bahtest"].add { |obj| + Facter.add("bahtest") { #obj.os = Facter["operatingsystem"].value #obj.release = Facter["operatingsystemrelease"].value - obj.tag("operatingsystem", "=", Facter["operatingsystem"].value) - obj.tag("operatingsystemrelease", "=", + tag("operatingsystem", Facter["operatingsystem"].value) + tag("operatingsystemrelease", Facter["operatingsystemrelease"].value) - obj.code = "echo bahtest" + setcode "echo bahtest" } } @@ -234,12 +201,12 @@ def test_adding2 ) assert_nothing_raised() { - Facter["failure"].add { |obj| + Facter.add("failure") { #obj.os = Facter["operatingsystem"].value #obj.release = "FakeRelease" - obj.tag("operatingsystem", "=", Facter["operatingsystem"].value) - obj.tag("operatingsystemrelease", "=", "FakeRelease") - obj.code = "echo failure" + tag("operatingsystem", Facter["operatingsystem"].value) + tag("operatingsystemrelease", "FakeRelease") + setcode "echo failure" } } From 5a0bd4a9e4a5893b65be019175e99e4e317aada8 Mon Sep 17 00:00:00 2001 From: luke Date: Mon, 9 Jan 2006 19:55:54 +0000 Subject: [PATCH 0014/3753] Updated to version 1.1.0 git-svn-id: http://reductivelabs.com/svn/facter/trunk@74 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter.rb b/lib/facter.rb index 9bc7ea10a5..1d243768b3 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -11,7 +11,7 @@ class Facter include Comparable include Enumerable -FACTERVERSION = '1.0.2' +FACTERVERSION = '1.1.0' # = Facter 1.0 # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. From a295c73251bb08ae8ff2191227fc2670560a0cfa Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 10 Jan 2006 22:56:16 +0000 Subject: [PATCH 0015/3753] Fixing bug when a fact with no resolutions is asked for git-svn-id: http://reductivelabs.com/svn/facter/trunk@76 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 26 ++++++++++++++++---------- tests/tc_simple.rb | 12 ++++-------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/lib/facter.rb b/lib/facter.rb index 1d243768b3..f63a855283 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -55,11 +55,7 @@ def Facter.debug(string) # Return a fact object by name. If you use this, you still have to call 'value' # on it to retrieve the actual value. def Facter.[](name) - if @@facts.include?(name.downcase) - return @@facts[name.downcase] - else - return Facter.add(name) - end + @@facts[name.to_s.downcase] end # Add a resolution mechanism for a named fact. This does not distinguish between @@ -76,7 +72,7 @@ def Facter.add(name, &block) end unless block - raise ArgumentError, "You must pass a block to Facter.add" + return fact end fact.add(&block) @@ -99,6 +95,14 @@ def each end end + def Facter.value(name) + if @@facts.include?(name.to_s.downcase) + @@facts[name.to_s.downcase].value + else + nil + end + end + # Flush all cached values. def Facter.flush @@facts.each { |fact| fact.flush } @@ -180,7 +184,6 @@ def add(&block) # skip resolves that will never be suitable for us unless resolve.suitable? - Facter.debug "Resolve is unsuitable" return end @@ -361,12 +364,10 @@ def suitable? unless defined? @suitable @suitable = true if @tags.length == 0 - Facter.debug "'%s' has no tags" % @name return true end @tags.each { |tag| unless tag.true? - Facter.debug "'%s' is false" % tag @suitable = false end } @@ -428,7 +429,12 @@ def to_s # Evaluate the fact, returning true or false. def true? - value = Facter[@fact].value + fact = nil + unless fact = Facter[@fact] + Facter.debug "No fact for %s" % @fact + return false + end + value = fact.value if value.nil? return false diff --git a/tests/tc_simple.rb b/tests/tc_simple.rb index 4919918b25..a6b877c16d 100644 --- a/tests/tc_simple.rb +++ b/tests/tc_simple.rb @@ -84,16 +84,12 @@ def test_onefalsetag end # I have no idea why this test is continually failing... - def disabled_test_recursivetags + def test_recursivetags assert_nothing_raised { - Facter.add("funtest") { - setcode { "tagged" } - tag("operatingsystem", Facter["operatingsystem"].value) + Facter.add("testing") { + setcode { "bar" } + tag("required","foo") } - #Facter.add("testing") { - # setcode { "bar" } - # tag("required","foo") - #} } assert_nothing_raised { Facter.add("required") do From 35ed5f4edbeeed4cecd7837f64f3ade92174fd06 Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 10 Jan 2006 22:57:44 +0000 Subject: [PATCH 0016/3753] Fixing bug when a fact with no resolutions is asked for git-svn-id: http://reductivelabs.com/svn/facter/trunk@77 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- CHANGES | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES b/CHANGES index 16080a7c5d..8dc68f55a6 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,7 @@ +1.1.1: + Fixed a bug that occurs when you ask for the value of an unregistered + fact. + 1.1.0: Added support for OpenBSD (and probably NetBSD and FreeBSD), and significantly refactored (heh) how facts and resolution mechanisms are added. From 2c99812781b11718b099f4bab560bbb8f85ce318 Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 10 Jan 2006 22:57:50 +0000 Subject: [PATCH 0017/3753] Updated to version 1.1.1 git-svn-id: http://reductivelabs.com/svn/facter/trunk@78 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter.rb b/lib/facter.rb index f63a855283..76be44df1b 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -11,7 +11,7 @@ class Facter include Comparable include Enumerable -FACTERVERSION = '1.1.0' +FACTERVERSION = '1.1.1' # = Facter 1.0 # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. From 62c050a758baaae6e0aadd0801f7266ffe110a40 Mon Sep 17 00:00:00 2001 From: luke Date: Wed, 25 Jan 2006 19:07:00 +0000 Subject: [PATCH 0018/3753] Working on packaging git-svn-id: http://reductivelabs.com/svn/facter/trunk@80 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- CHANGES => CHANGELOG | 0 Rakefile | 41 ++++++++++++++++++++++++++++++++ conf/redhat/facter.spec | 52 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+) rename CHANGES => CHANGELOG (100%) create mode 100644 conf/redhat/facter.spec diff --git a/CHANGES b/CHANGELOG similarity index 100% rename from CHANGES rename to CHANGELOG diff --git a/Rakefile b/Rakefile index 0ca17d3313..96a04acad5 100644 --- a/Rakefile +++ b/Rakefile @@ -290,4 +290,45 @@ task :copy => [:package, :html] do sh %{cp -r html #{DOWNDIR}/facter/apidocs} end +desc "Create an RPM" +task :rpm do + tarball = File.join(Dir.getwd, "pkg", "facter-#{PKG_VERSION}.tgz") + sources = File.join(Dir.getwd, "pkg", "facter-#{PKG_VERSION}") + ext= tarball[tarball.rindex('.')..-1] + (name, version) = File::basename(tarball, ext).split("-") + puts "tarball is %s" % tarball + + #basedir = File.join(Dir.getwd, "pkg", "rpm") + #basedir = "/home/luke/rpm" + + sourcedir = `rpm --define 'name #{name}' --define 'version #{version}' --eval '%_sourcedir'`.chomp + + basedir = File.dirname(sourcedir) + puts "basedir is %s" % basedir + FileUtils.mkdir_p(basedir) + + if ! FileTest::exist?(sourcedir) + FileUtils.mkdir_p(sourcedir) + end + + #puts "target is %s" % target + + target = "#{sourcedir}/facter-#{PKG_VERSION}.tgz" + #CLEAN.include(target) + + #FileUtils::copy(tarball, target) + sh %{cp -r #{tarball} #{sourcedir}} + sh %{cp conf/redhat/facter.spec %s/facter.spec} % basedir + + FileUtils::cd(basedir) + #FileUtils::cd(sourcedir) + + puts "WD is %s" % Dir.getwd + + #system("tar xzf #{tarball} -O '*/conf/redhat/facter.spec' > facter.spec") + #system("tar xzf #{tarball} -O '*/conf/redhat/facter.spec' > facter.spec") + + system("rpmbuild -ba facter.spec") +end + # $Id$ diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec new file mode 100644 index 0000000000..df5f364356 --- /dev/null +++ b/conf/redhat/facter.spec @@ -0,0 +1,52 @@ +%define rubylibdir %(ruby -rrbconfig -e 'puts Config::CONFIG["sitelibdir"]') +%define _pbuild %{_builddir}/%{name}-%{version} + +Summary: A fact-collection library +Name: facter +Version: 1.1.1 +Release: 1 +License: GPL +Group: System Environment/Base + +URL: http://reductivelabs.com/projects/facter/ +Source: http://reductivelabs.com/downloads/facter/%{name}-%{version}.tgz + +Vendor: Reductive Labs +Packager: Duane Griffin + +Requires: ruby >= 1.8.1 +Requires: facter >= 1.1 +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root +BuildArchitectures: noarch + +%description +Facter provides a cross-platform library for collecting simple facts about +about your systems and making them available via either the command line or +a Ruby library. You can create multiple ways to retrieve a given fact and it +will return the first valid value it finds. + +%prep +%setup -q + +%install +%{__rm} -rf %{buildroot} +%{__install} -d -m0755 %{buildroot}%{_bindir} +%{__install} -d -m0755 %{buildroot}%{rubylibdir} +%{__install} -d -m0755 %{buildroot}%{_docdir}/%{name}-%{version} +%{__install} -Dp -m0755 %{_pbuild}/bin/* %{buildroot}%{_bindir}/ +%{__install} -Dp -m0644 %{_pbuild}/lib/facter.rb %{buildroot}%{rubylibdir}/facter.rb + +%files +%defattr(-, root, root, 0755) +%{_sbindir}/facter +%{rubylibdir}/* +%{_localstatedir}/facter +%config %{_initrddir}/facter +%doc CHANGELOG COPYING LICENSE README TODO + +%clean +%{__rm} -rf %{buildroot} + +%changelog +* Tue Jan 17 2006 Luke Kanies - 1.1.1 +- Created From 2c0999e2b044addde714418754e26506e3f1358d Mon Sep 17 00:00:00 2001 From: luke Date: Wed, 25 Jan 2006 19:43:37 +0000 Subject: [PATCH 0019/3753] RPM creation now works git-svn-id: http://reductivelabs.com/svn/facter/trunk@81 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- Rakefile | 22 +- conf/redhat/facter.spec | 53 ++-- install.rb | 586 ++++++++++++++++++++-------------------- 3 files changed, 323 insertions(+), 338 deletions(-) diff --git a/Rakefile b/Rakefile index 96a04acad5..12f34704d0 100644 --- a/Rakefile +++ b/Rakefile @@ -69,7 +69,7 @@ rd = Rake::RDocTask.new(:html) { |rdoc| rdoc.template = 'html' rdoc.title = "Facter" rdoc.options << '--line-numbers' << '--inline-source' << '--main' << 'README' - rdoc.rdoc_files.include('README', 'LICENSE', 'TODO', 'CHANGES') + rdoc.rdoc_files.include('README', 'LICENSE', 'TODO', 'CHANGELOG') rdoc.rdoc_files.include('lib/**/*.rb') CLEAN.include("html") } @@ -293,40 +293,22 @@ end desc "Create an RPM" task :rpm do tarball = File.join(Dir.getwd, "pkg", "facter-#{PKG_VERSION}.tgz") - sources = File.join(Dir.getwd, "pkg", "facter-#{PKG_VERSION}") - ext= tarball[tarball.rindex('.')..-1] - (name, version) = File::basename(tarball, ext).split("-") - puts "tarball is %s" % tarball - #basedir = File.join(Dir.getwd, "pkg", "rpm") - #basedir = "/home/luke/rpm" - - sourcedir = `rpm --define 'name #{name}' --define 'version #{version}' --eval '%_sourcedir'`.chomp + sourcedir = `rpm --define 'name facter' --define 'version #{PKG_VERSION}' --eval '%_sourcedir'`.chomp basedir = File.dirname(sourcedir) - puts "basedir is %s" % basedir FileUtils.mkdir_p(basedir) if ! FileTest::exist?(sourcedir) FileUtils.mkdir_p(sourcedir) end - #puts "target is %s" % target - target = "#{sourcedir}/facter-#{PKG_VERSION}.tgz" - #CLEAN.include(target) - #FileUtils::copy(tarball, target) sh %{cp -r #{tarball} #{sourcedir}} sh %{cp conf/redhat/facter.spec %s/facter.spec} % basedir FileUtils::cd(basedir) - #FileUtils::cd(sourcedir) - - puts "WD is %s" % Dir.getwd - - #system("tar xzf #{tarball} -O '*/conf/redhat/facter.spec' > facter.spec") - #system("tar xzf #{tarball} -O '*/conf/redhat/facter.spec' > facter.spec") system("rpmbuild -ba facter.spec") end diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index df5f364356..60b4b9dc16 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -1,52 +1,47 @@ +%define rb_ver %(ruby -rrbconfig -e 'puts Config::CONFIG["ruby_version"]') %define rubylibdir %(ruby -rrbconfig -e 'puts Config::CONFIG["sitelibdir"]') %define _pbuild %{_builddir}/%{name}-%{version} -Summary: A fact-collection library +Summary: Facter collects Operating system facts. Name: facter Version: 1.1.1 Release: 1 License: GPL Group: System Environment/Base - -URL: http://reductivelabs.com/projects/facter/ -Source: http://reductivelabs.com/downloads/facter/%{name}-%{version}.tgz - +URL: http://reductivelabs.com/projects/facter Vendor: Reductive Labs -Packager: Duane Griffin - -Requires: ruby >= 1.8.1 -Requires: facter >= 1.1 +Source0: %{name}-%{version}.tgz BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root BuildArchitectures: noarch +Requires: ruby >= 1.8.1 +BuildRequires: ruby >= 1.8.1 + %description -Facter provides a cross-platform library for collecting simple facts about -about your systems and making them available via either the command line or -a Ruby library. You can create multiple ways to retrieve a given fact and it -will return the first valid value it finds. +Facter is a module for collecting simple facts about a host Operating system. %prep %setup -q +%build + %install -%{__rm} -rf %{buildroot} -%{__install} -d -m0755 %{buildroot}%{_bindir} -%{__install} -d -m0755 %{buildroot}%{rubylibdir} -%{__install} -d -m0755 %{buildroot}%{_docdir}/%{name}-%{version} -%{__install} -Dp -m0755 %{_pbuild}/bin/* %{buildroot}%{_bindir}/ -%{__install} -Dp -m0644 %{_pbuild}/lib/facter.rb %{buildroot}%{rubylibdir}/facter.rb +rm -rf $RPM_BUILD_ROOT +mkdir $RPM_BUILD_ROOT +DESTDIR=$RPM_BUILD_ROOT ruby install.rb --no-tests + +%clean +rm -rf $RPM_BUILD_ROOT + %files -%defattr(-, root, root, 0755) -%{_sbindir}/facter -%{rubylibdir}/* -%{_localstatedir}/facter -%config %{_initrddir}/facter -%doc CHANGELOG COPYING LICENSE README TODO +%defattr(-,root,root,-) +%{_bindir}/facter +%{rubylibdir}/facter.rb +%doc CHANGELOG COPYING INSTALL LICENSE README -%clean -%{__rm} -rf %{buildroot} %changelog -* Tue Jan 17 2006 Luke Kanies - 1.1.1 -- Created +* Wed Jan 11 2006 David Lutterkort - +- Initial build. + diff --git a/install.rb b/install.rb index f151b92cb0..2db49c39f8 100644 --- a/install.rb +++ b/install.rb @@ -1,289 +1,297 @@ -#! /usr/bin/env ruby -#-- -# Copyright 2004 Austin Ziegler -# Install utility. Based on the original installation script for rdoc by the -# Pragmatic Programmers. -# -# This program is free software. It may be redistributed and/or modified under -# the terms of the GPL version 2 (or later) or the Ruby licence. -# -# Usage -# ----- -# In most cases, if you have a typical project layout, you will need to do -# absolutely nothing to make this work for you. This layout is: -# -# bin/ # executable files -- "commands" -# lib/ # the source of the library -# tests/ # unit tests -# -# The default behaviour: -# 1) Run all unit test files (ending in .rb) found in all directories under -# tests/. -# 2) Build Rdoc documentation from all files in bin/ (excluding .bat and .cmd), -# all .rb files in lib/, ./README, ./ChangeLog, and ./Install. -# 3) Build ri documentation from all files in bin/ (excluding .bat and .cmd), -# and all .rb files in lib/. This is disabled by default on Win32. -# 4) Install commands from bin/ into the Ruby bin directory. On Windows, if a -# if a corresponding batch file (.bat or .cmd) exists in the bin directory, -# it will be copied over as well. Otherwise, a batch file (always .bat) will -# be created to run the specified command. -# 5) Install all library files ending in .rb from lib/ into Ruby's -# site_lib/version directory. -# -# $Id: install.rb,v 1.6 2004/08/08 20:33:09 austin Exp $ -#++ - -require 'rbconfig' -require 'find' -require 'fileutils' -require 'optparse' -require 'ostruct' - -InstallOptions = OpenStruct.new - -$loadedrdoc = false - -begin - require 'rdoc/rdoc' - $loadedrdoc = true -rescue LoadError => detail - $stderr.puts "Could not load rdoc/rdoc: %s" % detail - InstallOptions.rdoc = false -end - - -def glob(list) - g = list.map { |i| Dir.glob(i) } - g.flatten! - g.compact! - g.reject! { |e| e =~ /CVS/ } - g -end - - # Set these values to what you want installed. -#bins = glob(%w{bin/**/*}).reject { |e| e =~ /\.(bat|cmd)$/ } -bins = ["bin/facter"] -rdoc = glob(%w{bin/**/* lib/**/*.rb README CHANGELOG INSTALL}).reject { |e| e=~ /\.(bat|cmd)$/ } -ri = glob(%w(bin/**/* lib/**/*.rb)).reject { |e| e=~ /\.(bat|cmd)$/ } -libs = glob(%w{lib/**/*.rb}) -tests = glob(%w{tests/**/*.rb}) - -def do_bins(bins, target, strip = 'bin/') - bins.each do |bf| - obf = bf.gsub(/#{strip}/, '') - install_binfile(bf, obf, target) - end -end - -def do_libs(libs, strip = 'lib/') - libs.each do |lf| - olf = File.join(InstallOptions.site_dir, lf.gsub(/#{strip}/, '')) - op = File.dirname(olf) - #if File.respond_to?(:makedirs) - FileUtils.makedirs(op) - #else - # recmkdir(op) - #end - File.chmod(0755, op) - FileUtils.install(lf, olf, :mode => 0755, :verbose => true) - end -end - -## -# Prepare the file installation. -# -def prepare_installation - InstallOptions.rdoc = true - if RUBY_PLATFORM == "i386-mswin32" - InstallOptions.ri = false - else - InstallOptions.ri = true - end - InstallOptions.tests = true - - ARGV.options do |opts| - opts.banner = "Usage: #{File.basename($0)} [options]" - opts.separator "" - opts.on('--[no-]rdoc', 'Prevents the creation of RDoc output.', 'Default on.') do |onrdoc| - InstallOptions.rdoc = onrdoc - end - opts.on('--[no-]ri', 'Prevents the creation of RI output.', 'Default off on mswin32.') do |onri| - InstallOptions.ri = onri - end - opts.on('--[no-]tests', 'Prevents the execution of unit tests.', 'Default on.') do |ontest| - InstallOptions.tests = ontest - end - opts.on('--quick', 'Performs a quick installation. Only the', 'installation is done.') do |quick| - InstallOptions.rdoc = false - InstallOptions.ri = false - InstallOptions.tests = false - end - opts.on('--full', 'Performs a full installation. All', 'optional installation steps are run.') do |full| - InstallOptions.rdoc = true - InstallOptions.ri = true - InstallOptions.tests = true - end - opts.separator("") - opts.on_tail('--help', "Shows this help text.") do - $stderr.puts opts - exit - end - - opts.parse! - end - - bds = [".", ENV['TMP'], ENV['TEMP'], "/tmp"] - - version = [Config::CONFIG["MAJOR"], Config::CONFIG["MINOR"]].join(".") - ld = File.join(Config::CONFIG["libdir"], "ruby", version) - - sd = Config::CONFIG["sitelibdir"] - if sd.nil? - sd = $:.find { |x| x =~ /site_ruby/ } - if sd.nil? - sd = File.join(ld, "site_ruby") - elsif sd !~ Regexp.quote(version) - sd = File.join(sd, version) - end - end - - if (destdir = ENV['DESTDIR']) - bd = "#{destdir}#{Config::CONFIG['bindir']}" - sd = "#{destdir}#{sd}" - bds << bd - - FileUtils.makedirs(bd) - FileUtils.makedirs(sd) - else - bds << Config::CONFIG['bindir'] - end - - InstallOptions.bin_dirs = bds.compact - InstallOptions.site_dir = sd - InstallOptions.bin_dir = bd - InstallOptions.lib_dir = ld - - unless $loadedrdoc - InstallOptions.rdoc = false - InstallOptions.ri = false - end -end - -## -# Build the rdoc documentation. Also, try to build the RI documentation. -# -def build_rdoc(files) - r = RDoc::RDoc.new - r.document(["--main", "README", "--title", "Facter -- A Fact Collecter", - "--line-numbers"] + files) - -rescue RDoc::RDocError => e - $stderr.puts e.message -rescue Exception => e - $stderr.puts "Couldn't build RDoc documentation\n#{e.message}" -end - -def build_ri(files) - ri = RDoc::RDoc.new - ri.document(["--ri-site", "--merge"] + files) -rescue RDoc::RDocError => e - $stderr.puts e.message -rescue Exception => e - $stderr.puts e.class - $stderr.puts "Couldn't build Ri documentation\n#{e.message}" -end - -def run_tests(test_list) - begin - require 'test/unit/ui/console/testrunner' - $:.unshift "lib" - test_list.each do |test| - next if File.directory?(test) - require test - end - - tests = [] - ObjectSpace.each_object { |o| tests << o if o.kind_of?(Class) } - tests.delete_if { |o| !o.ancestors.include?(Test::Unit::TestCase) } - tests.delete_if { |o| o == Test::Unit::TestCase } - - tests.each { |test| Test::Unit::UI::Console::TestRunner.run(test) } - $:.shift - rescue LoadError - puts "Missing testrunner library; skipping tests" - end -end - -## -# Install file(s) from ./bin to Config::CONFIG['bindir']. Patch it on the way -# to insert a #! line; on a Unix install, the command is named as expected -# (e.g., bin/rdoc becomes rdoc); the shebang line handles running it. Under -# windows, we add an '.rb' extension and let file associations do their stuff. -def install_binfile(from, op_file, target) - tmp_dir = nil - InstallOptions.bin_dirs.each do |t| - if File.directory?(t) and File.writable_real?(t) - tmp_dir = t - break - end - end - - fail "Cannot find a temporary directory" unless tmp_dir - tmp_file = File.join(tmp_dir, '_tmp') - ruby = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name']) - - File.open(from) do |ip| - File.open(tmp_file, "w") do |op| - ruby = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name']) - op.puts "#!#{ruby}" - op.write ip.read - end - end - - if Config::CONFIG["target_os"] =~ /win/io - installed_wrapper = false - - if File.exists?("#{from}.bat") - FileUtils.install("#{from}.bat", File.join(target, "#{op_file}.bat"), :mode => 0755, :verbose => true) - installed_wrapper = true - end - - if File.exists?("#{from}.cmd") - FileUtils.install("#{from}.cmd", File.join(target, "#{op_file}.cmd"), :mode => 0755, :verbose => true) - installed_wrapper = true - end - - if not installed_wrapper - tmp_file2 = File.join(tmp_dir, '_tmp_wrapper') - cwn = File.join(Config::CONFIG['bindir'], op_file) - cwv = CMD_WRAPPER.gsub('', ruby.gsub(%r{/}) { "\\" }).gsub!('', cwn.gsub(%r{/}) { "\\" } ) - - File.open(tmp_file2, "wb") { |cw| cw.puts cwv } - FileUtils.install(tmp_file2, File.join(target, "#{op_file}.bat"), :mode => 0755, :verbose => true) - - File.unlink(tmp_file2) - installed_wrapper = true - end - end - FileUtils.install(tmp_file, File.join(target, op_file), :mode => 0755, :verbose => true) - File.unlink(tmp_file) -end - -CMD_WRAPPER = <<-EOS -@echo off -if "%OS%"=="Windows_NT" goto WinNT - -x "" %1 %2 %3 %4 %5 %6 %7 %8 %9 -goto done -:WinNT - -x "" %* -goto done -:done -EOS - -prepare_installation - -run_tests(tests) if InstallOptions.tests -#build_rdoc(rdoc) if InstallOptions.rdoc -#build_ri(ri) if InstallOptions.ri -do_bins(bins, Config::CONFIG['bindir']) -do_libs(libs) +#! /usr/bin/env ruby +#-- +# Copyright 2004 Austin Ziegler +# Install utility. Based on the original installation script for rdoc by the +# Pragmatic Programmers. +# +# This program is free software. It may be redistributed and/or modified under +# the terms of the GPL version 2 (or later) or the Ruby licence. +# +# Usage +# ----- +# In most cases, if you have a typical project layout, you will need to do +# absolutely nothing to make this work for you. This layout is: +# +# bin/ # executable files -- "commands" +# lib/ # the source of the library +# tests/ # unit tests +# +# The default behaviour: +# 1) Run all unit test files (ending in .rb) found in all directories under +# tests/. +# 2) Build Rdoc documentation from all files in bin/ (excluding .bat and .cmd), +# all .rb files in lib/, ./README, ./ChangeLog, and ./Install. +# 3) Build ri documentation from all files in bin/ (excluding .bat and .cmd), +# and all .rb files in lib/. This is disabled by default on Win32. +# 4) Install commands from bin/ into the Ruby bin directory. On Windows, if a +# if a corresponding batch file (.bat or .cmd) exists in the bin directory, +# it will be copied over as well. Otherwise, a batch file (always .bat) will +# be created to run the specified command. +# 5) Install all library files ending in .rb from lib/ into Ruby's +# site_lib/version directory. +# +# $Id: install.rb,v 1.6 2004/08/08 20:33:09 austin Exp $ +#++ + +require 'rbconfig' +require 'find' +require 'fileutils' +require 'optparse' +require 'ostruct' + +InstallOptions = OpenStruct.new + +$loadedrdoc = false + +begin + require 'rdoc/rdoc' + $loadedrdoc = true +rescue LoadError => detail + $stderr.puts "Could not load rdoc/rdoc: %s" % detail + InstallOptions.rdoc = false +end + + +def glob(list) + g = list.map { |i| Dir.glob(i) } + g.flatten! + g.compact! + g.reject! { |e| e =~ /CVS/ } + g +end + + # Set these values to what you want installed. +#bins = glob(%w{bin/**/*}).reject { |e| e =~ /\.(bat|cmd)$/ } +bins = ["bin/facter"] +rdoc = glob(%w{bin/**/* lib/**/*.rb README CHANGELOG INSTALL}).reject { |e| e=~ /\.(bat|cmd)$/ } +ri = glob(%w(bin/**/* lib/**/*.rb)).reject { |e| e=~ /\.(bat|cmd)$/ } +libs = glob(%w{lib/**/*.rb}) +tests = glob(%w{tests/**/*.rb}) + +def do_bins(bins, target, strip = 'bin/') + bins.each do |bf| + obf = bf.gsub(/#{strip}/, '') + install_binfile(bf, obf, target) + end +end + +def do_libs(libs, strip = 'lib/') + libs.each do |lf| + olf = File.join(InstallOptions.site_dir, lf.gsub(/#{strip}/, '')) + op = File.dirname(olf) + #if File.respond_to?(:makedirs) + FileUtils.makedirs(op) + #else + # recmkdir(op) + #end + File.chmod(0755, op) + FileUtils.install(lf, olf, :mode => 0755, :verbose => true) + end +end + +## +# Prepare the file installation. +# +def prepare_installation + InstallOptions.rdoc = true + if RUBY_PLATFORM == "i386-mswin32" + InstallOptions.ri = false + else + InstallOptions.ri = true + end + InstallOptions.tests = true + + ARGV.options do |opts| + opts.banner = "Usage: #{File.basename($0)} [options]" + opts.separator "" + opts.on('--[no-]rdoc', 'Prevents the creation of RDoc output.', 'Default on.') do |onrdoc| + InstallOptions.rdoc = onrdoc + end + opts.on('--[no-]ri', 'Prevents the creation of RI output.', 'Default off on mswin32.') do |onri| + InstallOptions.ri = onri + end + opts.on('--[no-]tests', 'Prevents the execution of unit tests.', 'Default on.') do |ontest| + InstallOptions.tests = ontest + end + opts.on('--quick', 'Performs a quick installation. Only the', 'installation is done.') do |quick| + InstallOptions.rdoc = false + InstallOptions.ri = false + InstallOptions.tests = false + end + opts.on('--full', 'Performs a full installation. All', 'optional installation steps are run.') do |full| + InstallOptions.rdoc = true + InstallOptions.ri = true + InstallOptions.tests = true + end + opts.separator("") + opts.on_tail('--help', "Shows this help text.") do + $stderr.puts opts + exit + end + + opts.parse! + end + + bds = [".", ENV['TMP'], ENV['TEMP'], "/tmp"] + + version = [Config::CONFIG["MAJOR"], Config::CONFIG["MINOR"]].join(".") + ld = File.join(Config::CONFIG["libdir"], "ruby", version) + + sd = Config::CONFIG["sitelibdir"] + if sd.nil? + sd = $:.find { |x| x =~ /site_ruby/ } + if sd.nil? + sd = File.join(ld, "site_ruby") + elsif sd !~ Regexp.quote(version) + sd = File.join(sd, version) + end + end + + if (destdir = ENV['DESTDIR']) + bd = "#{destdir}#{Config::CONFIG['bindir']}" + sd = "#{destdir}#{sd}" + bds << bd + + FileUtils.makedirs(bd) + FileUtils.makedirs(sd) + else + bds << Config::CONFIG['bindir'] + end + + InstallOptions.bin_dirs = bds.compact + InstallOptions.site_dir = sd + InstallOptions.bin_dir = bd + InstallOptions.lib_dir = ld + + unless $loadedrdoc + InstallOptions.rdoc = false + InstallOptions.ri = false + end +end + +## +# Build the rdoc documentation. Also, try to build the RI documentation. +# +def build_rdoc(files) + r = RDoc::RDoc.new + r.document(["--main", "README", "--title", "Facter -- A Fact Collecter", + "--line-numbers"] + files) + +rescue RDoc::RDocError => e + $stderr.puts e.message +rescue Exception => e + $stderr.puts "Couldn't build RDoc documentation\n#{e.message}" +end + +def build_ri(files) + ri = RDoc::RDoc.new + ri.document(["--ri-site", "--merge"] + files) +rescue RDoc::RDocError => e + $stderr.puts e.message +rescue Exception => e + $stderr.puts e.class + $stderr.puts "Couldn't build Ri documentation\n#{e.message}" +end + +def run_tests(test_list) + begin + require 'test/unit/ui/console/testrunner' + $:.unshift "lib" + test_list.each do |test| + next if File.directory?(test) + require test + end + + tests = [] + ObjectSpace.each_object { |o| tests << o if o.kind_of?(Class) } + tests.delete_if { |o| !o.ancestors.include?(Test::Unit::TestCase) } + tests.delete_if { |o| o == Test::Unit::TestCase } + + tests.each { |test| Test::Unit::UI::Console::TestRunner.run(test) } + $:.shift + rescue LoadError + puts "Missing testrunner library; skipping tests" + end +end + +## +# Install file(s) from ./bin to Config::CONFIG['bindir']. Patch it on the way +# to insert a #! line; on a Unix install, the command is named as expected +# (e.g., bin/rdoc becomes rdoc); the shebang line handles running it. Under +# windows, we add an '.rb' extension and let file associations do their stuff. +def install_binfile(from, op_file, target) + tmp_dir = nil + InstallOptions.bin_dirs.each do |t| + if File.directory?(t) and File.writable_real?(t) + tmp_dir = t + break + end + end + + fail "Cannot find a temporary directory" unless tmp_dir + tmp_file = File.join(tmp_dir, '_tmp') + ruby = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name']) + + File.open(from) do |ip| + File.open(tmp_file, "w") do |op| + ruby = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name']) + op.puts "#!#{ruby}" + op.write ip.read + end + end + + if Config::CONFIG["target_os"] =~ /win/io + installed_wrapper = false + + if File.exists?("#{from}.bat") + FileUtils.install("#{from}.bat", File.join(target, "#{op_file}.bat"), :mode => 0755, :verbose => true) + installed_wrapper = true + end + + if File.exists?("#{from}.cmd") + FileUtils.install("#{from}.cmd", File.join(target, "#{op_file}.cmd"), :mode => 0755, :verbose => true) + installed_wrapper = true + end + + if not installed_wrapper + tmp_file2 = File.join(tmp_dir, '_tmp_wrapper') + cwn = File.join(Config::CONFIG['bindir'], op_file) + cwv = CMD_WRAPPER.gsub('', ruby.gsub(%r{/}) { "\\" }).gsub!('', cwn.gsub(%r{/}) { "\\" } ) + + File.open(tmp_file2, "wb") { |cw| cw.puts cwv } + FileUtils.install(tmp_file2, File.join(target, "#{op_file}.bat"), :mode => 0755, :verbose => true) + + File.unlink(tmp_file2) + installed_wrapper = true + end + end + FileUtils.install(tmp_file, File.join(target, op_file), :mode => 0755, :verbose => true) + File.unlink(tmp_file) +end + +CMD_WRAPPER = <<-EOS +@echo off +if "%OS%"=="Windows_NT" goto WinNT + -x "" %1 %2 %3 %4 %5 %6 %7 %8 %9 +goto done +:WinNT + -x "" %* +goto done +:done +EOS + +prepare_installation + +run_tests(tests) if InstallOptions.tests +#build_rdoc(rdoc) if InstallOptions.rdoc +#build_ri(ri) if InstallOptions.ri +bd = nil +#if (destdir = ENV['DESTDIR']) +# bd = "#{destdir}#{Config::CONFIG['bindir']}" +#else +# bd = "#{Config::CONFIG['bindir']}" +#end + +puts "bin dir is %s" % InstallOptions.bin_dir +do_bins(bins, InstallOptions.bin_dir) +do_libs(libs) From 2d84edd2316021fa0d6826e122659d9028cacc3e Mon Sep 17 00:00:00 2001 From: lutter Date: Thu, 9 Feb 2006 18:25:31 +0000 Subject: [PATCH 0020/3753] Fix specfile in accordance with Fedora Extras guidelines git-svn-id: http://reductivelabs.com/svn/facter/trunk@82 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- conf/redhat/facter.spec | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index 60b4b9dc16..71b8cad410 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -2,16 +2,15 @@ %define rubylibdir %(ruby -rrbconfig -e 'puts Config::CONFIG["sitelibdir"]') %define _pbuild %{_builddir}/%{name}-%{version} -Summary: Facter collects Operating system facts. +Summary: Facter collects Operating system facts Name: facter Version: 1.1.1 -Release: 1 +Release: 3%{?dist} License: GPL Group: System Environment/Base URL: http://reductivelabs.com/projects/facter -Vendor: Reductive Labs Source0: %{name}-%{version}.tgz -BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) BuildArchitectures: noarch Requires: ruby >= 1.8.1 @@ -42,6 +41,9 @@ rm -rf $RPM_BUILD_ROOT %changelog -* Wed Jan 11 2006 David Lutterkort - +* Mon Feb 6 2006 David Lutterkort - 1.1.1-2 +- Fix BuildRoot. Add dist to release tag + +* Wed Jan 11 2006 David Lutterkort - 1.1.1-1 - Initial build. From a4309b46e84ab1fd08b4d4869c1d2b2a37a1af42 Mon Sep 17 00:00:00 2001 From: lutter Date: Thu, 9 Feb 2006 18:26:10 +0000 Subject: [PATCH 0021/3753] Automatically update version and release in the specfile for new releases git-svn-id: http://reductivelabs.com/svn/facter/trunk@83 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- Rakefile | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Rakefile b/Rakefile index 12f34704d0..2e6a16cdd6 100644 --- a/Rakefile +++ b/Rakefile @@ -1,3 +1,4 @@ +# -*- ruby -*- (Make emacs happy) # Rakefile for facter begin @@ -260,6 +261,22 @@ task :update_version => [:prerelease] do end end mv "lib/facter.rb.new", "lib/facter.rb" + + open("conf/redhat/facter.spec") do |rakein| + open("conf/redhat/facter.spec.new", "w") do |rakeout| + rakein.each do |line| + if line =~ /^Version:\s*/ + rakeout.puts "Version: #{PKG_VERSION}" + elsif line =~ /^Release:\s*/ + rakeout.puts "Release: 1%{?dist}" + else + rakeout.puts line + end + end + end + end + mv "conf/redhat/facter.spec.new", "conf/redhat/facter.spec" + if ENV['RELTEST'] announce "Release Task Testing, skipping commiting of new version" else From d36885fa07b582bf8609cae6382358a9e9c3fe94 Mon Sep 17 00:00:00 2001 From: luke Date: Fri, 17 Feb 2006 19:55:53 +0000 Subject: [PATCH 0022/3753] Adding ldapname capabilities git-svn-id: http://reductivelabs.com/svn/facter/trunk@84 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- CHANGELOG | 4 ++++ lib/facter.rb | 22 +++++++++++++++++----- tests/tc_simple.rb | 13 +++++++++++++ 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 8dc68f55a6..f3003ffc43 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,7 @@ +1.1.2: + Added 'ldapname' attribute, so Facts can be easily converted to + LDAP. + 1.1.1: Fixed a bug that occurs when you ask for the value of an unregistered fact. diff --git a/lib/facter.rb b/lib/facter.rb index 76be44df1b..d5cd1967ed 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -33,7 +33,7 @@ class Facter @@os = nil @@osrel = nil - attr_accessor :name, :os, :osrel, :hardware, :searching + attr_accessor :name, :os, :osrel, :hardware, :searching, :ldapname # module methods @@ -169,6 +169,8 @@ def initialize(name) @searching = false @value = nil + + @ldapname = name end # Add a new resolution mechanism. This requires a block, which will then @@ -180,6 +182,8 @@ def add(&block) resolve = Resolution.new(@name) + resolve.fact = self + resolve.instance_eval(&block) # skip resolves that will never be suitable for us @@ -295,7 +299,7 @@ def value # systems. Note that the tags are always ANDed, so any tags specified # must all be true for the resolution to be suitable. class Resolution - attr_accessor :interpreter, :code, :name + attr_accessor :interpreter, :code, :name, :fact # Execute a chunk of code. def Resolution.exec(code, interpreter = "/bin/sh") @@ -359,6 +363,12 @@ def setcode(string = nil, interp = nil, &block) end end + # Set the name by which this parameter is known in LDAP. The default + # is just the fact name. + def setldapname(name) + @fact.ldapname = name + end + # Is this resolution mechanism suitable on the system in question? def suitable? unless defined? @suitable @@ -579,6 +589,7 @@ def Facter.load end end Facter.add("Hostname") do + setldapname "cn" setcode do hostname = nil name = Resolution.exec('hostname') or nil @@ -597,7 +608,8 @@ def Facter.load end end - Facter.add("IPHostNumber") do + Facter.add("IPAddress") do + setldapname "iphostnumber" setcode do require 'resolv' @@ -617,7 +629,7 @@ def Facter.load end end end - Facter.add("IPHostNumber") do + Facter.add("IPAddress") do setcode do if hostname = Facter["hostname"].value # we need Hostname to exist for this to work @@ -695,7 +707,7 @@ def Facter.load ether end end - Facter.add("IPHostnumber") do + Facter.add("IPAddress") do tag("Kernel","Darwin") setcode do ip = nil diff --git a/tests/tc_simple.rb b/tests/tc_simple.rb index a6b877c16d..efc7d216e6 100644 --- a/tests/tc_simple.rb +++ b/tests/tc_simple.rb @@ -249,4 +249,17 @@ def disabled_test_withnoouts } $stdin, $stdout, $stderr = @oldhandles end + + def test_ldapname + facts = {} + assert_nothing_raised { + Facter.each { |name, value| + facts[name] = Facter[name] + } + } + + facts.each { |name, fact| + assert(fact.ldapname, "Fact %s has no ldapname" % name) + } + end end From 4579f8f91c6993cabe5875072c9cd92ed6381bb0 Mon Sep 17 00:00:00 2001 From: luke Date: Fri, 17 Feb 2006 19:56:04 +0000 Subject: [PATCH 0023/3753] Updated to version 1.1.2 git-svn-id: http://reductivelabs.com/svn/facter/trunk@85 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter.rb b/lib/facter.rb index d5cd1967ed..2551fc0857 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -11,7 +11,7 @@ class Facter include Comparable include Enumerable -FACTERVERSION = '1.1.1' +FACTERVERSION = '1.1.2' # = Facter 1.0 # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. From cc4a94374b0f1fa99567e45f9ad8aaf54aa82de6 Mon Sep 17 00:00:00 2001 From: luke Date: Sat, 18 Feb 2006 02:23:06 +0000 Subject: [PATCH 0024/3753] updates git-svn-id: http://reductivelabs.com/svn/facter/trunk@87 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- Rakefile | 5 +++++ conf/redhat/facter.spec | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Rakefile b/Rakefile index 2e6a16cdd6..846273607f 100644 --- a/Rakefile +++ b/Rakefile @@ -307,6 +307,11 @@ task :copy => [:package, :html] do sh %{cp -r html #{DOWNDIR}/facter/apidocs} end +desc "SSH to fedora and make the rpm" +task :fedorarpm => [:package] do + sh %{ssh fedora1 'cd svn/facter/trunk; rake rpm'} +end + desc "Create an RPM" task :rpm do tarball = File.join(Dir.getwd, "pkg", "facter-#{PKG_VERSION}.tgz") diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index 71b8cad410..f40e767485 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -4,8 +4,8 @@ Summary: Facter collects Operating system facts Name: facter -Version: 1.1.1 -Release: 3%{?dist} +Version: 1.1.2 +Release: 1%{?dist} License: GPL Group: System Environment/Base URL: http://reductivelabs.com/projects/facter From 2e407d446f28dcf81ad5481e87064663896e3aa2 Mon Sep 17 00:00:00 2001 From: luke Date: Thu, 23 Feb 2006 20:17:02 +0000 Subject: [PATCH 0025/3753] Identifying centos git-svn-id: http://reductivelabs.com/svn/facter/trunk@88 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/facter.rb b/lib/facter.rb index 2551fc0857..a31b137a4f 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -502,7 +502,12 @@ def Facter.load elsif FileTest.exists?("/etc/fedora-release") "Fedora" elsif FileTest.exists?("/etc/redhat-release") - "RedHat" + txt = File.read("/etc/redhat-release") + if txt =~ /centos/i + "CentOS" + else + "RedHat" + end elsif FileTest.exists?("/etc/SuSE-release") "SuSE" end From 3a230a006db970875eca5909bd533f960c8fe8fd Mon Sep 17 00:00:00 2001 From: luke Date: Thu, 23 Feb 2006 20:17:35 +0000 Subject: [PATCH 0026/3753] adding 1.1.3 changelog git-svn-id: http://reductivelabs.com/svn/facter/trunk@89 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- CHANGELOG | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index f3003ffc43..b92dc15e87 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,6 @@ +1.1.3: + Identifying CentOS correctly. + 1.1.2: Added 'ldapname' attribute, so Facts can be easily converted to LDAP. From 3c717575d1af507b6169f8ef324931ae761b3d50 Mon Sep 17 00:00:00 2001 From: luke Date: Thu, 23 Feb 2006 20:17:48 +0000 Subject: [PATCH 0027/3753] Updated to version 1.1.3 git-svn-id: http://reductivelabs.com/svn/facter/trunk@90 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter.rb b/lib/facter.rb index a31b137a4f..fc3193472c 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -11,7 +11,7 @@ class Facter include Comparable include Enumerable -FACTERVERSION = '1.1.2' +FACTERVERSION = '1.1.3' # = Facter 1.0 # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. From 0b7dce7ce872a083c366580246c892ca3c4eb5e5 Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 28 Feb 2006 16:03:30 +0000 Subject: [PATCH 0028/3753] Fixing installer to put the facter executable in /usr/bin instead of / git-svn-id: http://reductivelabs.com/svn/facter/trunk@92 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- conf/redhat/facter.spec | 2 +- install.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index f40e767485..92ec497f17 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -4,7 +4,7 @@ Summary: Facter collects Operating system facts Name: facter -Version: 1.1.2 +Version: 1.1.3 Release: 1%{?dist} License: GPL Group: System Environment/Base diff --git a/install.rb b/install.rb index 2db49c39f8..7780ed370c 100644 --- a/install.rb +++ b/install.rb @@ -155,6 +155,7 @@ def prepare_installation FileUtils.makedirs(bd) FileUtils.makedirs(sd) else + bd = Config::CONFIG['bindir'] bds << Config::CONFIG['bindir'] end From aab86877f18056d1fa2bcbd92dad16e118ff3ca3 Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 28 Feb 2006 16:03:42 +0000 Subject: [PATCH 0029/3753] Updated to version 1.1.4 git-svn-id: http://reductivelabs.com/svn/facter/trunk@93 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter.rb b/lib/facter.rb index fc3193472c..992e1176fe 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -11,7 +11,7 @@ class Facter include Comparable include Enumerable -FACTERVERSION = '1.1.3' +FACTERVERSION = '1.1.4' # = Facter 1.0 # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. From 46996fa50ef09ab4d6e08780bd94367911e42c0f Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 28 Feb 2006 16:23:46 +0000 Subject: [PATCH 0030/3753] updating changelog for 1.1.4 git-svn-id: http://reductivelabs.com/svn/facter/trunk@95 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- CHANGELOG | 3 +++ conf/redhat/facter.spec | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index b92dc15e87..8ec3b34b9b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,6 @@ +1.1.4: + Fixing installer bug. + 1.1.3: Identifying CentOS correctly. diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index 92ec497f17..fc3d7a29b3 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -4,7 +4,7 @@ Summary: Facter collects Operating system facts Name: facter -Version: 1.1.3 +Version: 1.1.4 Release: 1%{?dist} License: GPL Group: System Environment/Base From e3e4a03919d7a48ceffc43708ec7e00bab9531a8 Mon Sep 17 00:00:00 2001 From: luke Date: Thu, 2 Mar 2006 21:28:06 +0000 Subject: [PATCH 0031/3753] fixing rake file to build and copy rpms automatically git-svn-id: http://reductivelabs.com/svn/facter/trunk@96 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- Rakefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Rakefile b/Rakefile index 846273607f..610c4083b9 100644 --- a/Rakefile +++ b/Rakefile @@ -200,6 +200,7 @@ task :release => [ :update_version, :tag, :package, + :fedorarpm, :copy] do #:alltests, @@ -305,6 +306,7 @@ task :copy => [:package, :html] do sh %{cp pkg/facter-#{PKG_VERSION}.tgz #{DOWNDIR}/facter} sh %{ln -sf facter-#{PKG_VERSION}.tgz #{DOWNDIR}/facter/facter-latest.tgz} sh %{cp -r html #{DOWNDIR}/facter/apidocs} + sh %{rsync -av /home/luke/rpm/. #{DOWNDIR}/rpm} end desc "SSH to fedora and make the rpm" From fadc8c562b93512027329972bec1833895d2424b Mon Sep 17 00:00:00 2001 From: lutter Date: Tue, 14 Mar 2006 03:25:22 +0000 Subject: [PATCH 0032/3753] Minor changes for hte Fedora Extras review git-svn-id: http://reductivelabs.com/svn/facter/trunk@97 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- conf/redhat/facter.spec | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index fc3d7a29b3..173efa3385 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -1,23 +1,25 @@ -%define rb_ver %(ruby -rrbconfig -e 'puts Config::CONFIG["ruby_version"]') -%define rubylibdir %(ruby -rrbconfig -e 'puts Config::CONFIG["sitelibdir"]') -%define _pbuild %{_builddir}/%{name}-%{version} +%define sitelibdir %(ruby -rrbconfig -e 'puts Config::CONFIG["sitelibdir"]') -Summary: Facter collects Operating system facts +Summary: Ruby module for collecting simple facts about a host operating system Name: facter Version: 1.1.4 -Release: 1%{?dist} +Release: 2%{?dist} License: GPL Group: System Environment/Base URL: http://reductivelabs.com/projects/facter -Source0: %{name}-%{version}.tgz +Source0: http://reductivelabs.com/downloads/facter/%{name}-%{version}.tgz BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) -BuildArchitectures: noarch +# It's not possible to build ruby noarch packages currently +# See bz184199 +#BuildArchitectures: noarch Requires: ruby >= 1.8.1 BuildRequires: ruby >= 1.8.1 -%description -Facter is a module for collecting simple facts about a host Operating system. +%description +Ruby module for collecting simple facts about a host Operating +system. Some of the facts are preconfigured, such as the hostname and the +operating system. Additional facts can be added through simple Ruby scripts %prep %setup -q @@ -28,6 +30,7 @@ Facter is a module for collecting simple facts about a host Operating system. rm -rf $RPM_BUILD_ROOT mkdir $RPM_BUILD_ROOT DESTDIR=$RPM_BUILD_ROOT ruby install.rb --no-tests +chmod a-x $RPM_BUILD_ROOT%{sitelibdir}/*.rb %clean rm -rf $RPM_BUILD_ROOT @@ -36,14 +39,19 @@ rm -rf $RPM_BUILD_ROOT %files %defattr(-,root,root,-) %{_bindir}/facter -%{rubylibdir}/facter.rb +%{sitelibdir}/facter.rb %doc CHANGELOG COPYING INSTALL LICENSE README %changelog +* Mon Mar 13 2006 David Lutterkort - 1.1.4-2 +- Commented out noarch; requires fix for bz184199 + +* Mon Mar 6 2006 David Lutterkort - 1.1.4-1 +- Removed unused macros + * Mon Feb 6 2006 David Lutterkort - 1.1.1-2 - Fix BuildRoot. Add dist to release tag * Wed Jan 11 2006 David Lutterkort - 1.1.1-1 - Initial build. - From c78d1130635871c5899b1294f131e8ead7a24c90 Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 14 Mar 2006 07:10:43 +0000 Subject: [PATCH 0033/3753] Converting rakefile to the new build system git-svn-id: http://reductivelabs.com/svn/facter/trunk@98 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- Rakefile | 351 +++++-------------------------------------------------- 1 file changed, 27 insertions(+), 324 deletions(-) diff --git a/Rakefile b/Rakefile index 610c4083b9..7d9436efa9 100644 --- a/Rakefile +++ b/Rakefile @@ -1,340 +1,43 @@ -# -*- ruby -*- (Make emacs happy) # Rakefile for facter -begin - require 'rubygems' - require 'rake/gempackagetask' -rescue Exception - nil +begin + require 'rake/reductive' +rescue LoadError + $stderr.puts "You must have the Reductive build library in your RUBYLIB." + exit(14) end -require 'rake/clean' -require 'rake/testtask' - -require 'rake/rdoctask' -#CLEAN.include('**/*.o') -CLOBBER.include('doc/*') - -def announce(msg='') - STDERR.puts msg -end - -# Determine the current version - -if `ruby -Ilib ./bin/facter --version` =~ /\S+$/ - CURRENT_VERSION = $& -else - CURRENT_VERSION = "0.0.0" -end - -if ENV['REL'] - PKG_VERSION = ENV['REL'] -else - PKG_VERSION = CURRENT_VERSION -end - -DOWNDIR = "/export/docroots/reductivelabs.com/htdocs/downloads" - -# The default task is run if rake is given no explicit arguments. - -desc "Default Task" -task :default => :unittests - -# Test Tasks --------------------------------------------------------- - -#task :u => :unittests -#task :a => :alltests - -#task :alltests => :unittests - -#Rake::TestTask.new(:unittests) do |t| -# t.test_files = FileList['tests/*.rb'] -# t.warning = true -# t.verbose = false -#end - -# SVN Tasks ---------------------------------------------------------- -# ... none. - -# Install rake using the standard install.rb script. - -desc "Install the application" -task :install do - ruby "install.rb" -end - -# Create a task to build the RDOC documentation tree. - -rd = Rake::RDocTask.new(:html) { |rdoc| - rdoc.rdoc_dir = 'html' - rdoc.template = 'html' - rdoc.title = "Facter" - rdoc.options << '--line-numbers' << '--inline-source' << '--main' << 'README' - rdoc.rdoc_files.include('README', 'LICENSE', 'TODO', 'CHANGELOG') - rdoc.rdoc_files.include('lib/**/*.rb') - CLEAN.include("html") -} - -# ==================================================================== -# Create a task that will package the Rake software into distributable -# tar, zip and gem files. - -PKG_FILES = FileList[ - 'install.rb', - '[A-Z]*', - 'bin/**/*', - 'lib/**/*.rb', - 'test/**/*.rb', - 'doc/**/*', - 'etc/*' -] -PKG_FILES.delete_if {|item| item.include?(".svn")} - -if ! defined?(Gem) - puts "Package Target requires RubyGEMs" -else - spec = Gem::Specification.new do |s| - - #### Basic information. - - s.name = 'facter' - s.version = PKG_VERSION - s.summary = "Facter collects Operating system facts." - s.description = <<-EOF +project = Rake::RedLabProject.new("facter") do |p| + p.summary = "Facter collects Operating system facts." + p.description = <<-EOF Facter is a module for collecting simple facts about a host Operating system. EOF - #### Dependencies and requirements. - - #s.add_dependency('log4r', '> 1.0.4') - #s.requirements << "" - - s.files = PKG_FILES.to_a - - #### Load-time details: library and application (you will need one or both). - - s.require_path = 'lib' # Use these for libraries. - - s.bindir = "bin" # Use these for applications. - s.executables = ["facter"] - s.default_executable = "facter" - - #### Documentation and testing. - - s.has_rdoc = true - s.extra_rdoc_files = rd.rdoc_files.reject { |fn| fn =~ /\.rb$/ }.to_a - s.rdoc_options << - '--title' << 'Facter' << - '--main' << 'README' << - '--line-numbers' - - #### Author and project details. - - s.author = "Luke Kanies" - s.email = "dev@reductivelabs.com" - s.homepage = "http://reductivelabs.com/projects/facter" - #s.rubyforge_project = "facter" - end - - Rake::GemPackageTask.new(spec) do |pkg| - #pkg.need_zip = true - pkg.need_tar = true - end - - CLEAN.include("pkg") -end - -# Misc tasks ========================================================= - -#ARCHIVEDIR = '/...' - -#task :archive => [:package] do -# cp FileList["pkg/*.tgz", "pkg/*.zip", "pkg/*.gem"], ARCHIVEDIR -#end - -# Define an optional publish target in an external file. If the -# publish.rf file is not found, the publish targets won't be defined. - -#load "publish.rf" if File.exist? "publish.rf" - -# Support Tasks ------------------------------------------------------ - -def egrep(pattern) - Dir['**/*.rb'].each do |fn| - count = 0 - open(fn) do |f| - while line = f.gets - count += 1 - if line =~ pattern - puts "#{fn}:#{count}:#{line}" - end - end - end - end -end - -desc "Look for TODO and FIXME tags in the code" -task :todo do - egrep "/#.*(FIXME|TODO|TBD)/" -end - -#desc "Look for Debugging print lines" -#task :dbg do -# egrep /\bDBG|\bbreakpoint\b/ -#end - -#desc "List all ruby files" -#task :rubyfiles do -# puts Dir['**/*.rb'].reject { |fn| fn =~ /^pkg/ } -# puts Dir['bin/*'].reject { |fn| fn =~ /CVS|(~$)|(\.rb$)/ } -#end - -# -------------------------------------------------------------------- -# Creating a release - -desc "Make a new release" -task :release => [ - :prerelease, - :clobber, - :update_version, - :tag, - :package, - :fedorarpm, - :copy] do - #:alltests, - - announce - announce "**************************************************************" - announce "* Release #{PKG_VERSION} Complete." - announce "* Packages ready to upload." - announce "**************************************************************" - announce -end - -# Validate that everything is ready to go for a release. -task :prerelease do - announce - announce "**************************************************************" - announce "* Making RubyGem Release #{PKG_VERSION}" - announce "* (current version #{CURRENT_VERSION})" - announce "**************************************************************" - announce - - # Is a release number supplied? - unless ENV['REL'] - fail "Usage: rake release REL=x.y.z [REUSE=tag_suffix]" - end - - # Is the release different than the current release. - # (or is REUSE set?) - if PKG_VERSION == CURRENT_VERSION && ! ENV['REUSE'] - fail "Current version is #{PKG_VERSION}, must specify REUSE=tag_suffix to reuse version" - end - - # Are all source files checked in? - if ENV['RELTEST'] - announce "Release Task Testing, skipping checked-in file test" - else - announce "Checking for unchecked-in files..." - data = `svn -q update` - unless data =~ /^$/ - fail "SVN update is not clean ... do you have unchecked-in files?" - end - announce "No outstanding checkins found ... OK" - end -end - -task :update_version => [:prerelease] do - if PKG_VERSION == CURRENT_VERSION - announce "No version change ... skipping version update" - else - announce "Updating Facter version to #{PKG_VERSION}" - open("lib/facter.rb") do |rakein| - open("lib/facter.rb.new", "w") do |rakeout| - rakein.each do |line| - if line =~ /^\s*FACTERVERSION\s*=\s*/ - rakeout.puts "FACTERVERSION = '#{PKG_VERSION}'" - else - rakeout.puts line - end - end - end - end - mv "lib/facter.rb.new", "lib/facter.rb" - - open("conf/redhat/facter.spec") do |rakein| - open("conf/redhat/facter.spec.new", "w") do |rakeout| - rakein.each do |line| - if line =~ /^Version:\s*/ - rakeout.puts "Version: #{PKG_VERSION}" - elsif line =~ /^Release:\s*/ - rakeout.puts "Release: 1%{?dist}" - else - rakeout.puts line - end - end - end - end - mv "conf/redhat/facter.spec.new", "conf/redhat/facter.spec" - - if ENV['RELTEST'] - announce "Release Task Testing, skipping commiting of new version" - else - sh %{svn commit -m "Updated to version #{PKG_VERSION}" lib/facter.rb} - end - end + p.filelist = [ + 'install.rb', + '[A-Z]*', + 'bin/**/*', + 'lib/**/*.rb', + 'test/**/*.rb', + 'doc/**/*', + 'etc/*' + ] end -desc "Tag all the SVN files with the latest release number (REL=x.y.z)" -task :tag => [:prerelease] do - reltag = "REL_#{PKG_VERSION.gsub(/\./, '_')}" - reltag << ENV['REUSE'].gsub(/\./, '_') if ENV['REUSE'] - announce "Tagging SVN copy with [#{reltag}]" - if ENV['RELTEST'] - announce "Release Task Testing, skipping SVN tagging" - else - sh %{svn copy ../trunk/ ../tags/#{reltag}} - sh %{cd ../tags; svn ci -m 'Adding Release tag #{reltag}'} - end -end +project.mkgemtask do |gem| + gem.require_path = 'lib' # Use these for libraries. -desc "Copy the newly created package into the downloads directory" -task :copy => [:package, :html] do - sh %{cp pkg/facter-#{PKG_VERSION}.gem #{DOWNDIR}/gems} - sh %{generate_yaml_index.rb -d #{DOWNDIR}} - sh %{cp pkg/facter-#{PKG_VERSION}.tgz #{DOWNDIR}/facter} - sh %{ln -sf facter-#{PKG_VERSION}.tgz #{DOWNDIR}/facter/facter-latest.tgz} - sh %{cp -r html #{DOWNDIR}/facter/apidocs} - sh %{rsync -av /home/luke/rpm/. #{DOWNDIR}/rpm} + gem.bindir = "bin" # Use these for applications. + gem.executables = ["facter"] + gem.default_executable = "facter" end -desc "SSH to fedora and make the rpm" -task :fedorarpm => [:package] do - sh %{ssh fedora1 'cd svn/facter/trunk; rake rpm'} -end - -desc "Create an RPM" -task :rpm do - tarball = File.join(Dir.getwd, "pkg", "facter-#{PKG_VERSION}.tgz") - - sourcedir = `rpm --define 'name facter' --define 'version #{PKG_VERSION}' --eval '%_sourcedir'`.chomp - - basedir = File.dirname(sourcedir) - FileUtils.mkdir_p(basedir) - - if ! FileTest::exist?(sourcedir) - FileUtils.mkdir_p(sourcedir) +if project.has?(:epm) + project.mkepmtask do |task| + task.bins = FileList.new("bin/facter") + task.rubylibs = FileList.new('lib/**/*') + task.add_dependency("ruby", "1.8.1") end - - target = "#{sourcedir}/facter-#{PKG_VERSION}.tgz" - - sh %{cp -r #{tarball} #{sourcedir}} - sh %{cp conf/redhat/facter.spec %s/facter.spec} % basedir - - FileUtils::cd(basedir) - - system("rpmbuild -ba facter.spec") end - # $Id$ From 6c37a20f32b54054cee0bfc24c18de878f5b1ddf Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 14 Mar 2006 21:17:12 +0000 Subject: [PATCH 0034/3753] Removing ruby as a prereq git-svn-id: http://reductivelabs.com/svn/facter/trunk@99 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- Rakefile | 1 - 1 file changed, 1 deletion(-) diff --git a/Rakefile b/Rakefile index 7d9436efa9..f3d6271c86 100644 --- a/Rakefile +++ b/Rakefile @@ -37,7 +37,6 @@ if project.has?(:epm) project.mkepmtask do |task| task.bins = FileList.new("bin/facter") task.rubylibs = FileList.new('lib/**/*') - task.add_dependency("ruby", "1.8.1") end end # $Id$ From fe782b9596ee8b727f0b91e08318f27a99ab5250 Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 16 May 2006 16:01:57 +0000 Subject: [PATCH 0035/3753] Accepting the patch from #5 git-svn-id: http://reductivelabs.com/svn/facter/trunk@100 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 77 ++++++++++----------------------------------------- 1 file changed, 14 insertions(+), 63 deletions(-) diff --git a/lib/facter.rb b/lib/facter.rb index 992e1176fe..8415f81bd2 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -52,14 +52,14 @@ def Facter.debug(string) end end - # Return a fact object by name. If you use this, you still have to call 'value' - # on it to retrieve the actual value. + # Return a fact object by name. If you use this, you still have to call + # 'value' on it to retrieve the actual value. def Facter.[](name) @@facts[name.to_s.downcase] end - # Add a resolution mechanism for a named fact. This does not distinguish between - # adding a new fact and adding a new way to resolve a fact. + # Add a resolution mechanism for a named fact. This does not distinguish + # between adding a new fact and adding a new way to resolve a fact. def Facter.add(name, &block) fact = nil dcname = name.downcase @@ -294,10 +294,10 @@ def value end end - # An actual fact resolution mechanism. These are largely just chunks of code, - # with optional tags restricting the mechanisms to only working on specific - # systems. Note that the tags are always ANDed, so any tags specified - # must all be true for the resolution to be suitable. + # An actual fact resolution mechanism. These are largely just chunks of + # code, with optional tags restricting the mechanisms to only working on + # specific systems. Note that the tags are always ANDed, so any tags + # specified must all be true for the resolution to be suitable. class Resolution attr_accessor :interpreter, :code, :name, :fact @@ -334,7 +334,8 @@ def Resolution.exec(code, interpreter = "/bin/sh") return out end else - raise ArgumentError, "non-sh interpreters are not currently supported" + raise ArgumentError, + "non-sh interpreters are not currently supported" end end @@ -426,8 +427,8 @@ def value class Tag attr_accessor :fact, :op, :value - # Add the tag. Requires the fact name, an operator, and the value we're - # comparing to. + # Add the tag. Requires the fact name, an operator, and the value + # we're comparing to. def initialize(fact, *values) @fact = fact @values = values @@ -735,64 +736,14 @@ def Facter.load tag("Kernel","Darwin") tag("KernelRelease","R7") setcode do - hostname = nil - if FileTest.exists?("/Library/Preferences/SystemConfiguration/preferences.plist") - File.open( - "/Library/Preferences/SystemConfiguration/preferences.plist" - ) { |file| - found = 0 - file.each { |line| - if line =~ /ComputerName/i - found = 1 - next - end - if found == 1 - if line =~ /([\w|-]+)<\/string>/ - hostname = $1 - break - end - end - } - } - end - - if hostname != nil - hostname - else - nil - end + %x{/usr/sbin/scutil --get LocalHostName} end end Facter.add("IPHostnumber") do tag("Kernel","Darwin") tag("KernelRelease","R6") setcode do - hostname = nil - if FileTest.exists?("/var/db/SystemConfiguration/preferences.xml") - File.open( - "/var/db/SystemConfiguration/preferences.xml" - ) { |file| - found = 0 - file.each { |line| - if line =~ /ComputerName/i - found = 1 - next - end - if found == 1 - if line =~ /([\w|-]+)<\/string>/ - hostname = $1 - break - end - end - } - } - end - - if hostname != nil - hostname - else - nil - end + %x{/usr/sbin/scutil --get LocalHostName} end end Facter.add("IPHostnumber") do From 22bd24b2a2d29431320f67ebbb93d36fbae1c9ba Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 23 May 2006 15:14:11 +0000 Subject: [PATCH 0036/3753] Added "architecture" fact, added the ability to autoload facts from separate files, and added the ability to retrieve fact values via a method for each fact. git-svn-id: http://reductivelabs.com/svn/facter/trunk@101 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 47 ++++++++++++++++++++++++++++++-- tests/tc_simple.rb | 68 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 109 insertions(+), 6 deletions(-) diff --git a/lib/facter.rb b/lib/facter.rb index 8415f81bd2..a24684522b 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -55,14 +55,26 @@ def Facter.debug(string) # Return a fact object by name. If you use this, you still have to call # 'value' on it to retrieve the actual value. def Facter.[](name) - @@facts[name.to_s.downcase] + name = name.to_s.downcase + unless @@facts.include?(name) + # Try autoloading the fact. + begin + require "facter/#{name}" + unless @@facts.include?(name) + warn "Loaded facter/#{name} but fact was not added" + end + rescue LoadError + # Just ignore it + end + end + @@facts[name] end # Add a resolution mechanism for a named fact. This does not distinguish # between adding a new fact and adding a new way to resolve a fact. def Facter.add(name, &block) fact = nil - dcname = name.downcase + dcname = name.to_s.downcase if @@facts.include?(dcname) fact = @@facts[dcname] @@ -93,6 +105,14 @@ def each end } end + + def method_missing(name, *args) + if fact = self[name] + return fact.value + else + super + end + end end def Facter.value(name) @@ -146,6 +166,14 @@ def Facter.list return @@facts.keys end + # Return a hash of all of our facts. + def Facter.to_hash + self.inject({}) do |h, ary| + h[ary[0]] = ary[1] + h + end + end + # Compare one value to another. def <=>(other) return self.value <=> other @@ -158,7 +186,7 @@ def ===(value) # Create a new fact, with no resolution mechanisms. def initialize(name) - @name = name.downcase + @name = name.downcase if name.is_a? String if @@facts.include?(@name) raise ArgumentError, "A fact named %s already exists" % name else @@ -520,6 +548,19 @@ def Facter.load #tag("operatingsystem","SunOS") end + Facter.add("Architecture") do + tag("operatingsystem","Debian") + setcode do + model = Facter.hardwaremodel + case model + when 'x86_64': "amd64" + when /(i[3456]86|pentium)/: "i386" + else + model + end + end + end + Facter.add("CfKey") do setcode do value = nil diff --git a/tests/tc_simple.rb b/tests/tc_simple.rb index efc7d216e6..8510fcfb5d 100644 --- a/tests/tc_simple.rb +++ b/tests/tc_simple.rb @@ -15,6 +15,8 @@ class TestFacter < Test::Unit::TestCase def setup Facter.load + + @tmpfiles = [] end def teardown @@ -22,9 +24,11 @@ def teardown Facter.reset Facter.flush - #if ! @oldhandles.empty? - # $stdin, $stdout, $stderr = @oldhandles - #end + @tmpfiles.each do |file| + if FileTest.exists?(file) + system("rm -rf %s" % file) + end + end end def test_version @@ -262,4 +266,62 @@ def test_ldapname assert(fact.ldapname, "Fact %s has no ldapname" % name) } end + + def test_hash + hash = nil + assert_nothing_raised { + hash = Facter.to_hash + } + + assert_instance_of(Hash, hash) + + hash.each do |name, value| + assert_instance_of(String, name) + assert_instance_of(String, value) + end + end + + # Verify we can call retrieve facts as methods + def test_factfunction + val = nil + assert_nothing_raised { + val = Facter.operatingsystem + } + + assert_equal(Facter["operatingsystem"].value, val) + + assert_raise(NoMethodError) { Facter.nosuchfact } + end + + # Verify we can autoload facts. + def test_autoloading + dir = "/tmp/facterloading" + @tmpfiles << dir + Dir.mkdir(dir) + Dir.mkdir(File.join(dir, "facter")) + $: << dir + + # Make sure we don't have a value right now. + assert_raise(NoMethodError) do + Facter.autoloadfact + end + assert_nil(Facter["autoloadfact"]) + + val = "autoloadedness" + File.open(File.join(dir, "facter", "autoloadfact.rb"), "w") do |file| + file.puts %{ +Facter.add("AutoloadFact") do + setcode { "#{val}" } +end +} + end + + ret = nil + assert_nothing_raised do + ret = Facter.autoloadfact + end + assert_equal(val, ret, "Got incorrect value for autoloaded fact") + assert_equal(val, Facter["autoloadfact"].value, + "Got incorrect value for autoloaded fact") + end end From 6c01e040cad38e0317f4389bd01a98357aee0637 Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 23 May 2006 15:43:09 +0000 Subject: [PATCH 0037/3753] Fixing install and tests so that there are no errors, hopefully. git-svn-id: http://reductivelabs.com/svn/facter/trunk@102 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- install.rb | 10 +++++++--- tests/tc_facterbin.rb | 16 ++++++++-------- tests/tc_simple.rb | 11 ++++++----- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/install.rb b/install.rb index 7780ed370c..6dfa33eb90 100644 --- a/install.rb +++ b/install.rb @@ -197,10 +197,15 @@ def build_ri(files) def run_tests(test_list) begin require 'test/unit/ui/console/testrunner' + require 'test/unit' + + unless defined? Test::Unit::TestCase + raise LoadError, "Could not find unit test library" + end $:.unshift "lib" test_list.each do |test| - next if File.directory?(test) - require test + next if File.directory?(test) + require test end tests = [] @@ -293,6 +298,5 @@ def install_binfile(from, op_file, target) # bd = "#{Config::CONFIG['bindir']}" #end -puts "bin dir is %s" % InstallOptions.bin_dir do_bins(bins, InstallOptions.bin_dir) do_libs(libs) diff --git a/tests/tc_facterbin.rb b/tests/tc_facterbin.rb index 07e820b2d7..586954bfd4 100755 --- a/tests/tc_facterbin.rb +++ b/tests/tc_facterbin.rb @@ -1,7 +1,8 @@ #! /usr/bin/env ruby -# $Id: $ -$:.unshift '../lib' -$facterbase = ".." + +$facterbase = File.dirname(File.dirname(__FILE__)) +libdir = File.join($facterbase, "lib") +$:.unshift libdir require 'facter' require 'test/unit' @@ -9,11 +10,8 @@ # add the bin directory to our search path ENV["PATH"] = File.join($facterbase, "bin") + ":" + ENV["PATH"] -# and then the library directories -libdirs = $:.find_all { |dir| - dir =~ /facter/ or dir =~ /\.\./ -} -ENV["RUBYLIB"] = libdirs.join(":") +# and then the library directory +ENV["RUBYLIB"] = libdir class TestFacterBin < Test::Unit::TestCase def test_version @@ -24,3 +22,5 @@ def test_version assert(output == Facter.version) end end + +# $Id$ diff --git a/tests/tc_simple.rb b/tests/tc_simple.rb index 8510fcfb5d..bf5cfc24b4 100644 --- a/tests/tc_simple.rb +++ b/tests/tc_simple.rb @@ -1,9 +1,8 @@ #! /usr/bin/env ruby -# $Id$ -# -if __FILE__ == $0 # Make this library first! - $:.unshift '../lib' -end + +$facterbase = File.dirname(File.dirname(__FILE__)) +libdir = File.join($facterbase, "lib") +$:.unshift libdir require 'test/unit' require 'facter' @@ -325,3 +324,5 @@ def test_autoloading "Got incorrect value for autoloaded fact") end end + +# $Id$ From f7454541afa2f36593eabb322a92a4c4b38c4c95 Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 23 May 2006 18:25:30 +0000 Subject: [PATCH 0038/3753] Adding ruby, puppet, and facter version facts git-svn-id: http://reductivelabs.com/svn/facter/trunk@103 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 20 ++++++++++++++++++++ tests/tc_simple.rb | 10 ++++++++++ 2 files changed, 30 insertions(+) diff --git a/lib/facter.rb b/lib/facter.rb index a24684522b..14e27887ca 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -110,6 +110,7 @@ def method_missing(name, *args) if fact = self[name] return fact.value else + p @@facts.keys super end end @@ -497,6 +498,25 @@ def true? # Load all of the default facts def Facter.load + Facter.add("FacterVersion") do + setcode { FACTERVERSION.to_s } + end + + Facter.add("RubyVersion") do + setcode { RUBY_VERSION.to_s } + end + + Facter.add("PuppetVersion") do + setcode { + begin + require 'puppet' + Puppet::PUPPETVERSION.to_s + rescue LoadError + nil + end + } + end + Facter.add("Kernel") do setcode 'uname -s' end diff --git a/tests/tc_simple.rb b/tests/tc_simple.rb index bf5cfc24b4..50ccf8ae3e 100644 --- a/tests/tc_simple.rb +++ b/tests/tc_simple.rb @@ -1,6 +1,9 @@ #! /usr/bin/env ruby $facterbase = File.dirname(File.dirname(__FILE__)) +if $facterbase == "." + $facterbase = ".." +end libdir = File.join($facterbase, "lib") $:.unshift libdir @@ -323,6 +326,13 @@ def test_autoloading assert_equal(val, Facter["autoloadfact"].value, "Got incorrect value for autoloaded fact") end + + def test_versionfacts + assert_nothing_raised { + assert(Facter.facterversion, "Could not get facter version") + assert(Facter.rubyversion, "Could not get ruby version") + } + end end # $Id$ From 87bbd501314eb89157cca8401f19258ad66a0523 Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 23 May 2006 18:33:05 +0000 Subject: [PATCH 0039/3753] adding another test for the exe git-svn-id: http://reductivelabs.com/svn/facter/trunk@104 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- tests/tc_facterbin.rb | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/tests/tc_facterbin.rb b/tests/tc_facterbin.rb index 586954bfd4..ce6685571b 100755 --- a/tests/tc_facterbin.rb +++ b/tests/tc_facterbin.rb @@ -11,7 +11,7 @@ ENV["PATH"] = File.join($facterbase, "bin") + ":" + ENV["PATH"] # and then the library directory -ENV["RUBYLIB"] = libdir +ENV["RUBYLIB"] += ":" + libdir class TestFacterBin < Test::Unit::TestCase def test_version @@ -21,6 +21,25 @@ def test_version } assert(output == Facter.version) end + + def test_output + output = nil + assert_nothing_raised { + output = %x{facter 2>&1}.chomp + } + + hash = output.split("\n").inject({}) do |h, line| + name, value = line.split(" => ") + h[name] = value + h + end + + Facter.each do |name, fact| + assert(hash.include?(name.downcase), "Did not get " + name) + + assert_equal(fact, hash[name], "%s was not equal" % name) + end + end end # $Id$ From 97f1a5eb1bdf008a60dc051d47095a4d250ba9a6 Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 23 May 2006 19:19:21 +0000 Subject: [PATCH 0040/3753] updating changelog for 1.2.0 git-svn-id: http://reductivelabs.com/svn/facter/trunk@105 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- CHANGELOG | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 8ec3b34b9b..0a58efb49f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,14 @@ +1.2.0: + Added RubyVersion, FacterVersion, and PuppetVersion facts. + + Added autoload capabilities, so you can add facts without modifying + the core library. + + Added the ability to retrieve facts by treating them as a method on the + Facter class, e.g., Facter.operatingsystem. + + Added a to_hash method to Facter, which retrieves all facts as a hash. + 1.1.4: Fixing installer bug. From 99b61e7c525400a851d1a39d36fec7f062168bc8 Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 23 May 2006 19:45:38 +0000 Subject: [PATCH 0041/3753] Adding final autoloading work. git-svn-id: http://reductivelabs.com/svn/facter/trunk@106 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 79 +++++++++++++++++++++++++++++------------- tests/tc_simple.rb | 86 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 140 insertions(+), 25 deletions(-) diff --git a/lib/facter.rb b/lib/facter.rb index 14e27887ca..f99fefee23 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -110,30 +110,29 @@ def method_missing(name, *args) if fact = self[name] return fact.value else - p @@facts.keys super end end end - def Facter.value(name) - if @@facts.include?(name.to_s.downcase) - @@facts[name.to_s.downcase].value - else - nil + # Load a file by path + def Facter.autoload(file) + name = File.basename(file).sub(".rb",'') + begin + require file + unless @@facts.include?(name) + warn "Loaded %s but it did not define fact %s" % [file, name] + end + rescue LoadError => detail + warn "Failed to load %s: %s" % [file, detail] end end - # Flush all cached values. - def Facter.flush - @@facts.each { |fact| fact.flush } - end - - # Remove them all. - def Facter.reset - @@facts.each { |name,fact| - @@facts.delete(name) - } + # Clear all facts. Mostly used for testing. + def Facter.clear + Facter.reset + Facter.flush + @@facts.clear end # Set debugging on or off. @@ -162,11 +161,23 @@ def Facter.debugging(bit) end end + # Flush all cached values. + def Facter.flush + @@facts.each { |fact| fact.flush } + end + # Return a list of all of the facts. def Facter.list return @@facts.keys end + # Remove them all. + def Facter.reset + @@facts.each { |name,fact| + @@facts.delete(name) + } + end + # Return a hash of all of our facts. def Facter.to_hash self.inject({}) do |h, ary| @@ -175,6 +186,14 @@ def Facter.to_hash end end + def Facter.value(name) + if @@facts.include?(name.to_s.downcase) + @@facts[name.to_s.downcase].value + else + nil + end + end + # Compare one value to another. def <=>(other) return self.value <=> other @@ -834,14 +853,28 @@ def Facter.load tag("operatingsystem","Linux") setcode "whoami" end + + locals = [] + + # Now see if we can find any other facts + $:.each do |dir| + fdir = File.join(dir, "facter") + if FileTest.exists?(fdir) + Dir.chdir(fdir) do + Dir.glob("*.rb").each do |file| + if file == "local.rb" + # Just require it normally + require File.join(fdir, file) + else + # We assume it's a new fact to be + # autoloaded, so ask Facter to load it for us + Facter.autoload(File.join(fdir, file)) + end + end + end + end + end end Facter.load end - -# try to load a local fact library, if there happens to be one -begin - require 'facter/local' -rescue LoadError - # no worries -end diff --git a/tests/tc_simple.rb b/tests/tc_simple.rb index 50ccf8ae3e..1e1ac6c313 100644 --- a/tests/tc_simple.rb +++ b/tests/tc_simple.rb @@ -23,8 +23,7 @@ def setup def teardown # clear out the list of facts, so we start fresh for every test - Facter.reset - Facter.flush + Facter.clear @tmpfiles.each do |file| if FileTest.exists?(file) @@ -333,6 +332,89 @@ def test_versionfacts assert(Facter.rubyversion, "Could not get ruby version") } end + + # Verify we autoload everything from the start. + def test_initautoloading + dir = "/tmp/facterloading" + @tmpfiles << dir + Dir.mkdir(dir) + Dir.mkdir(File.join(dir, "facter")) + $: << dir + + # Make sure we don't have a value right now. + assert_raise(NoMethodError) do + Facter.initautoloadfact + end + assert_nil(Facter["initautoloadfact"]) + + # Make our file + val = "autoloadedness" + File.open(File.join(dir, "facter", "initautoloadfact.rb"), "w") do |file| + file.puts %{ +Facter.add("InitAutoloadFact") do + setcode { "#{val}" } +end +} + end + + # Now reset and reload + Facter.reset + Facter.flush + + # And load + assert_nothing_raised { + Facter.load + } + + hash = nil + assert_nothing_raised { + hash = Facter.to_hash + } + + assert(hash.include?("initautoloadfact"), "Did not load fact at startup") + assert_equal(val, hash["initautoloadfact"], "Did not get correct value") + end + + def test_localfacts + dir = "/tmp/localloading" + @tmpfiles << dir + Dir.mkdir(dir) + Dir.mkdir(File.join(dir, "facter")) + $: << dir + + # Make sure we don't have a value right now. + assert_raise(NoMethodError) do + Facter.localfact + end + assert_nil(Facter["localfact"]) + + # Make our file + val = "localness" + File.open(File.join(dir, "facter", "local.rb"), "w") do |file| + file.puts %{ +Facter.add("LocalFact") do + setcode { "#{val}" } +end +} + end + + # Now reset and reload + Facter.reset + Facter.flush + + # And load + assert_nothing_raised { + Facter.load + } + + hash = nil + assert_nothing_raised { + hash = Facter.to_hash + } + + assert(hash.include?("localfact"), "Did not load fact at startup") + assert_equal(val, hash["localfact"], "Did not get correct value") + end end # $Id$ From 999929e8d2e805dad0e7f2ecb70d17d60cd485de Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 23 May 2006 19:45:52 +0000 Subject: [PATCH 0042/3753] Updated to version 1.2.0 git-svn-id: http://reductivelabs.com/svn/facter/trunk@107 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter.rb b/lib/facter.rb index f99fefee23..5111ddd0cb 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -11,7 +11,7 @@ class Facter include Comparable include Enumerable -FACTERVERSION = '1.1.4' +FACTERVERSION = '1.2.0' # = Facter 1.0 # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. From b208f47bfad865ca7d7a4df11fecd4b361d59259 Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 23 May 2006 20:56:36 +0000 Subject: [PATCH 0043/3753] fixing small bug that only occurs with gems git-svn-id: http://reductivelabs.com/svn/facter/trunk@109 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- CHANGELOG | 4 ++++ conf/redhat/facter.spec | 4 ++-- lib/facter.rb | 4 ++-- tests/tc_simple.rb | 19 +++++++++++++++++++ 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 0a58efb49f..6636a20940 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,7 @@ +1.2.1: + Fixed a "bug" that occurs if there's a file named "facter" in your + ruby search path (as opposed to directory). + 1.2.0: Added RubyVersion, FacterVersion, and PuppetVersion facts. diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index 173efa3385..9e48621f7b 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -2,8 +2,8 @@ Summary: Ruby module for collecting simple facts about a host operating system Name: facter -Version: 1.1.4 -Release: 2%{?dist} +Version: 1.2.0 +Release: 1%{?dist} License: GPL Group: System Environment/Base URL: http://reductivelabs.com/projects/facter diff --git a/lib/facter.rb b/lib/facter.rb index 5111ddd0cb..3337218dd7 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -11,7 +11,7 @@ class Facter include Comparable include Enumerable -FACTERVERSION = '1.2.0' +FACTERVERSION = '1.1.4' # = Facter 1.0 # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. @@ -859,7 +859,7 @@ def Facter.load # Now see if we can find any other facts $:.each do |dir| fdir = File.join(dir, "facter") - if FileTest.exists?(fdir) + if FileTest.exists?(fdir) and FileTest.directory?(fdir) Dir.chdir(fdir) do Dir.glob("*.rb").each do |file| if file == "local.rb" diff --git a/tests/tc_simple.rb b/tests/tc_simple.rb index 1e1ac6c313..8f1819dcfa 100644 --- a/tests/tc_simple.rb +++ b/tests/tc_simple.rb @@ -415,6 +415,25 @@ def test_localfacts assert(hash.include?("localfact"), "Did not load fact at startup") assert_equal(val, hash["localfact"], "Did not get correct value") end + + def test_stupidchdirring + dir = "/tmp/localloading" + @tmpfiles << dir + Dir.mkdir(dir) + $: << dir + + # Make our file + val = "localness" + File.open(File.join(dir, "facter"), "w") do |file| + file.puts %{ +some random stuff +} + end + + assert_nothing_raised do + Facter.load + end + end end # $Id$ From 8794e46049a3637b77147558625f361d8025b11b Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 23 May 2006 20:56:51 +0000 Subject: [PATCH 0044/3753] Updated to version 1.2.1 git-svn-id: http://reductivelabs.com/svn/facter/trunk@110 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter.rb b/lib/facter.rb index 3337218dd7..3d3b80766d 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -11,7 +11,7 @@ class Facter include Comparable include Enumerable -FACTERVERSION = '1.1.4' +FACTERVERSION = '1.2.1' # = Facter 1.0 # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. From af062c60304468beeff110e3f4297a312059b462 Mon Sep 17 00:00:00 2001 From: luke Date: Thu, 25 May 2006 19:32:51 +0000 Subject: [PATCH 0045/3753] adding solaris pkg stuff git-svn-id: http://reductivelabs.com/svn/facter/trunk@112 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- conf/solaris/pkginfo | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 conf/solaris/pkginfo diff --git a/conf/solaris/pkginfo b/conf/solaris/pkginfo new file mode 100644 index 0000000000..fac31a514b --- /dev/null +++ b/conf/solaris/pkginfo @@ -0,0 +1,7 @@ +PKG=CSWfacter +NAME=facter - System Fact Gatherer +VERSION=1.2.1 +CATEGORY=application +VENDOR=http://reductivelabs.com/projects/facter +HOTLINE=http://reductivelabs.com/cgi-bin/facter.cgi +EMAIL=luke@madstop.com From 165a401f2bf72c501815523be988be73f8d57f4e Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 30 May 2006 22:12:04 +0000 Subject: [PATCH 0046/3753] Accepting the patch in #9, with some modifications. git-svn-id: http://reductivelabs.com/svn/facter/trunk@113 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 45 ++++++++++++++----------------------------- tests/tc_facterbin.rb | 3 +++ tests/tc_simple.rb | 3 ++- 3 files changed, 19 insertions(+), 32 deletions(-) diff --git a/lib/facter.rb b/lib/facter.rb index 3d3b80766d..e7681aea66 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -11,7 +11,7 @@ class Facter include Comparable include Enumerable -FACTERVERSION = '1.2.1' + FACTERVERSION = '1.2.1' # = Facter 1.0 # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. @@ -54,18 +54,10 @@ def Facter.debug(string) # Return a fact object by name. If you use this, you still have to call # 'value' on it to retrieve the actual value. - def Facter.[](name) + def Facter.[](name) name = name.to_s.downcase unless @@facts.include?(name) - # Try autoloading the fact. - begin - require "facter/#{name}" - unless @@facts.include?(name) - warn "Loaded facter/#{name} but fact was not added" - end - rescue LoadError - # Just ignore it - end + Facter.autoload(name) end @@facts[name] end @@ -107,24 +99,24 @@ def each end def method_missing(name, *args) + retval = nil if fact = self[name] - return fact.value + retval = fact.value else - super + retval = super end + + retval end end # Load a file by path - def Facter.autoload(file) - name = File.basename(file).sub(".rb",'') + def Facter.autoload(name) begin - require file - unless @@facts.include?(name) - warn "Loaded %s but it did not define fact %s" % [file, name] - end + require "facter/#{name}" rescue LoadError => detail - warn "Failed to load %s: %s" % [file, detail] + #warn "Failed to load %s: %s" % [name, detail] + # nothing, just ignore it, i guess end end @@ -860,17 +852,8 @@ def Facter.load $:.each do |dir| fdir = File.join(dir, "facter") if FileTest.exists?(fdir) and FileTest.directory?(fdir) - Dir.chdir(fdir) do - Dir.glob("*.rb").each do |file| - if file == "local.rb" - # Just require it normally - require File.join(fdir, file) - else - # We assume it's a new fact to be - # autoloaded, so ask Facter to load it for us - Facter.autoload(File.join(fdir, file)) - end - end + Dir.glob("#{fdir}/*.rb").each do |file| + Facter.autoload(File.basename(file, '.rb')) end end end diff --git a/tests/tc_facterbin.rb b/tests/tc_facterbin.rb index ce6685571b..e337b63046 100755 --- a/tests/tc_facterbin.rb +++ b/tests/tc_facterbin.rb @@ -1,6 +1,9 @@ #! /usr/bin/env ruby $facterbase = File.dirname(File.dirname(__FILE__)) +if $facterbase == "." + $facterbase = ".." +end libdir = File.join($facterbase, "lib") $:.unshift libdir diff --git a/tests/tc_simple.rb b/tests/tc_simple.rb index 8f1819dcfa..bd71c6d2e6 100644 --- a/tests/tc_simple.rb +++ b/tests/tc_simple.rb @@ -88,8 +88,9 @@ def test_onefalsetag assert_equal(nil, Facter["testing"].value) end - # I have no idea why this test is continually failing... def test_recursivetags + # This will try to autoload "required", which will fail, so the + # fact will be marked as unsuitable. assert_nothing_raised { Facter.add("testing") { setcode { "bar" } From 6932a9582fda1e2f5999a4163ea5e692699e6754 Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 30 May 2006 22:28:04 +0000 Subject: [PATCH 0047/3753] accepting patch in #10, although with more abstraction, and creating a module for the memory functions git-svn-id: http://reductivelabs.com/svn/facter/trunk@114 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter/memory.rb | 58 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 lib/facter/memory.rb diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb new file mode 100644 index 0000000000..2088682eb1 --- /dev/null +++ b/lib/facter/memory.rb @@ -0,0 +1,58 @@ +# +# memory.rb +# Additional Facts for memory/swap usage +# +# Copyright (C) 2006 Mooter Media Ltd +# Author: Matthew Palmer +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation (version 2 of the License) +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA + +module Facter::Memory + def self.meminfo_number(tag) + memsize = "" + File.readlines("/proc/meminfo").each do |l| + if l =~ /^#{tag}:\s+(\d+)\s+(\S+)/ + memsize = scale_number($1.to_f, $2) + end + end + + memsize + end + + def self.scale_number(size, multiplier) + suffixes = ['', 'kB', 'MB', 'GB', 'TB'] + + s = suffixes.shift + while s != multiplier + s = suffixes.shift + end + + while size > 1024.0 + size /= 1024.0 + s = suffixes.shift + end + + return "%.2f %s" % [size, s] + end +end + +{:MemorySize => "MemTotal", + :MemoryFree => "MemFree", + :SwapSize => "SwapTotal", + :SwapFree => "SwapFree"}.each do |fact, name| + Facter.add(fact) do + tag "kernel", "linux" + setcode do + Facter::Memory.meminfo_number(name) + end + end +end From 3a0181ebadc8f8cfe9cac7591971cd5fde23c8e5 Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 30 May 2006 22:33:22 +0000 Subject: [PATCH 0048/3753] fixing linux memory stuff git-svn-id: http://reductivelabs.com/svn/facter/trunk@115 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- conf/redhat/facter.spec | 2 +- lib/facter/memory.rb | 2 +- tests/tc_simple.rb | 6 ++++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index 9e48621f7b..ab87cd20a0 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -2,7 +2,7 @@ Summary: Ruby module for collecting simple facts about a host operating system Name: facter -Version: 1.2.0 +Version: 1.2.1 Release: 1%{?dist} License: GPL Group: System Environment/Base diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb index 2088682eb1..47f98f79b5 100644 --- a/lib/facter/memory.rb +++ b/lib/facter/memory.rb @@ -50,7 +50,7 @@ def self.scale_number(size, multiplier) :SwapSize => "SwapTotal", :SwapFree => "SwapFree"}.each do |fact, name| Facter.add(fact) do - tag "kernel", "linux" + tag "kernel", "Linux" setcode do Facter::Memory.meminfo_number(name) end diff --git a/tests/tc_simple.rb b/tests/tc_simple.rb index bd71c6d2e6..2d3c5fe023 100644 --- a/tests/tc_simple.rb +++ b/tests/tc_simple.rb @@ -435,6 +435,12 @@ def test_stupidchdirring Facter.load end end + + if Facter.kernel == "Linux" + def test_memoryonlinux + assert(Facter.memorysize, "Did not get memory") + end + end end # $Id$ From 01d37d94e8588a7a9dad7311c8b1eeafaf82c7b5 Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 30 May 2006 22:54:26 +0000 Subject: [PATCH 0049/3753] Getting rid of the autoload method entirely; facts are now only loaded at startup. git-svn-id: http://reductivelabs.com/svn/facter/trunk@116 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 23 +++++++---------------- tests/tc_simple.rb | 44 +++++++------------------------------------- 2 files changed, 14 insertions(+), 53 deletions(-) diff --git a/lib/facter.rb b/lib/facter.rb index e7681aea66..ce99ba7100 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -56,9 +56,6 @@ def Facter.debug(string) # 'value' on it to retrieve the actual value. def Facter.[](name) name = name.to_s.downcase - unless @@facts.include?(name) - Facter.autoload(name) - end @@facts[name] end @@ -110,16 +107,6 @@ def method_missing(name, *args) end end - # Load a file by path - def Facter.autoload(name) - begin - require "facter/#{name}" - rescue LoadError => detail - #warn "Failed to load %s: %s" % [name, detail] - # nothing, just ignore it, i guess - end - end - # Clear all facts. Mostly used for testing. def Facter.clear Facter.reset @@ -508,7 +495,7 @@ def true? end # Load all of the default facts - def Facter.load + def Facter.loadfacts Facter.add("FacterVersion") do setcode { FACTERVERSION.to_s } end @@ -853,11 +840,15 @@ def Facter.load fdir = File.join(dir, "facter") if FileTest.exists?(fdir) and FileTest.directory?(fdir) Dir.glob("#{fdir}/*.rb").each do |file| - Facter.autoload(File.basename(file, '.rb')) + # Load here, rather than require, because otherwise + # the facts won't get reloaded if someone calls + # "loadfacts". Really only important in testing, but, + # well, it's important in testing. + load file end end end end - Facter.load + Facter.loadfacts end diff --git a/tests/tc_simple.rb b/tests/tc_simple.rb index 2d3c5fe023..8efdbd7c8a 100644 --- a/tests/tc_simple.rb +++ b/tests/tc_simple.rb @@ -16,7 +16,7 @@ class TestFacter < Test::Unit::TestCase def setup - Facter.load + Facter.loadfacts @tmpfiles = [] end @@ -295,38 +295,6 @@ def test_factfunction assert_raise(NoMethodError) { Facter.nosuchfact } end - # Verify we can autoload facts. - def test_autoloading - dir = "/tmp/facterloading" - @tmpfiles << dir - Dir.mkdir(dir) - Dir.mkdir(File.join(dir, "facter")) - $: << dir - - # Make sure we don't have a value right now. - assert_raise(NoMethodError) do - Facter.autoloadfact - end - assert_nil(Facter["autoloadfact"]) - - val = "autoloadedness" - File.open(File.join(dir, "facter", "autoloadfact.rb"), "w") do |file| - file.puts %{ -Facter.add("AutoloadFact") do - setcode { "#{val}" } -end -} - end - - ret = nil - assert_nothing_raised do - ret = Facter.autoloadfact - end - assert_equal(val, ret, "Got incorrect value for autoloaded fact") - assert_equal(val, Facter["autoloadfact"].value, - "Got incorrect value for autoloaded fact") - end - def test_versionfacts assert_nothing_raised { assert(Facter.facterversion, "Could not get facter version") @@ -364,7 +332,7 @@ def test_initautoloading # And load assert_nothing_raised { - Facter.load + Facter.loadfacts } hash = nil @@ -405,7 +373,7 @@ def test_localfacts # And load assert_nothing_raised { - Facter.load + Facter.loadfacts } hash = nil @@ -432,13 +400,15 @@ def test_stupidchdirring end assert_nothing_raised do - Facter.load + Facter.loadfacts end end if Facter.kernel == "Linux" def test_memoryonlinux - assert(Facter.memorysize, "Did not get memory") + assert_nothing_raised { + assert(Facter.memorysize, "Did not get memory") + } end end end From f3cc5e30a913a9236f6c54e880593ec5a1f8450e Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 30 May 2006 23:15:00 +0000 Subject: [PATCH 0050/3753] Adding the ability to specify tags as hashes or arrays, as requested in #112. git-svn-id: http://reductivelabs.com/svn/facter/trunk@117 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 20 +++++++++++++++++--- tests/tc_simple.rb | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/lib/facter.rb b/lib/facter.rb index ce99ba7100..985b0d2195 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -415,8 +415,15 @@ def suitable? end # Add a new tag to the resolution mechanism. - def tag(fact,*values) - @tags.push Tag.new(fact,*values) + def tag(*args) + if args[0].is_a? Hash + args[0].each do |fact, values| + @tags.push Tag.new(fact,*values) + end + else + fact = args.shift + @tags.push Tag.new(fact,*args) + end end def to_s @@ -457,8 +464,15 @@ class Tag # Add the tag. Requires the fact name, an operator, and the value # we're comparing to. def initialize(fact, *values) + fact = fact.to_s if fact.is_a? Symbol @fact = fact - @values = values + @values = values.collect do |value| + if value.is_a? String + value + else + value.to_s + end + end end def to_s diff --git a/tests/tc_simple.rb b/tests/tc_simple.rb index 8efdbd7c8a..66abf2330e 100644 --- a/tests/tc_simple.rb +++ b/tests/tc_simple.rb @@ -404,6 +404,49 @@ def test_stupidchdirring end end + def test_tag_as_array_and_hash + assert_nothing_raised { + Facter.add("myfact") do + tag "kernel", Facter.kernel + setcode do "yep" end + end + } + + assert_equal("yep", Facter.myfact, "Did not get tagged goal") + + # now try it as a hash + assert_nothing_raised { + Facter.add("hashfact") do + tag "kernel" => Facter.kernel + setcode do "hashness" end + end + } + + assert_equal("hashness", Facter.hashfact, "Did not get tagged goal") + + # now with multiple values + assert_nothing_raised { + Facter.add("hashfact2") do + tag :kernel => ["nosuchkernel", Facter.kernel] + setcode do "multihash" end + end + } + + assert_equal("multihash", Facter.hashfact2, "Did not get multivalue tag") + + end + + def test_strings_or_symbols + assert_nothing_raised { + Facter.add("symbol1") do + tag :kernel => Facter.kernel + setcode do "yep1" end + end + } + + assert_equal("yep1", Facter.symbol1, "Did not get symbol fact") + end + if Facter.kernel == "Linux" def test_memoryonlinux assert_nothing_raised { From 59cea90bd77383b9aa70e9ba41a54428f21b0538 Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 30 May 2006 23:44:06 +0000 Subject: [PATCH 0051/3753] Adding patch from #11, with slight modifications. git-svn-id: http://reductivelabs.com/svn/facter/trunk@118 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter/processor.rb | 45 +++++++++++++++++++++++++++++++++++++++++ tests/tc_simple.rb | 7 +++++++ 2 files changed, 52 insertions(+) create mode 100644 lib/facter/processor.rb diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb new file mode 100644 index 0000000000..bbb476c499 --- /dev/null +++ b/lib/facter/processor.rb @@ -0,0 +1,45 @@ +# +# processor.rb +# Additional Facts about the machine's CPUs +# +# Copyright (C) 2006 Mooter Media Ltd +# Author: Matthew Palmer +# +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation (version 2 of the License) +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA +# + +processor_num = -1 +processor_list = [] +File.readlines("/proc/cpuinfo").each do |l| + if l =~ /processor\s+:\s+(\d+)/ + processor_num = $1.to_i + elsif l =~ /model name\s+:\s+(.*)\s*$/ + processor_list[processor_num] = $1 unless processor_num == -1 + processor_num = -1 + end +end + +Facter.add("ProcessorCount") do + setcode do + processor_list.length + end +end + +processor_list.each_with_index do |desc, i| + Facter.add("Processor#{i}") do + tag :kernel => "Linux" + setcode do + desc + end + end +end diff --git a/tests/tc_simple.rb b/tests/tc_simple.rb index 66abf2330e..9dd847fc90 100644 --- a/tests/tc_simple.rb +++ b/tests/tc_simple.rb @@ -453,6 +453,13 @@ def test_memoryonlinux assert(Facter.memorysize, "Did not get memory") } end + + def test_processor_on_linux + assert_nothing_raised { + assert(Facter.processorcount, "Did not get proc count") + assert(Facter.processor0, "Did not get proc 0") + } + end end end From c7cfd0812577df8f678b65093f746d7ae651994c Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 30 May 2006 23:49:26 +0000 Subject: [PATCH 0052/3753] Adding patch from #11, with slight modifications. git-svn-id: http://reductivelabs.com/svn/facter/trunk@119 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 4 ++-- tests/tc_simple.rb | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/lib/facter.rb b/lib/facter.rb index 985b0d2195..57711f5324 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -12,7 +12,7 @@ class Facter include Enumerable FACTERVERSION = '1.2.1' - # = Facter 1.0 + # = Facter # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. # returns them dynamically @@ -493,7 +493,7 @@ def true? end retval = @values.find { |v| - if value == v + if value.downcase == v.downcase break true end } diff --git a/tests/tc_simple.rb b/tests/tc_simple.rb index 9dd847fc90..a293c78d65 100644 --- a/tests/tc_simple.rb +++ b/tests/tc_simple.rb @@ -447,6 +447,26 @@ def test_strings_or_symbols assert_equal("yep1", Facter.symbol1, "Did not get symbol fact") end + def test_tag_case_insensitivity + assert_nothing_raised { + Facter.add :casetest1 do + tag :kernel => Facter.kernel.downcase + setcode do "yep1" end + end + } + + assert_equal("yep1", Facter.casetest1, "Did not get case test 1") + + assert_nothing_raised { + Facter.add :casetest2 do + tag :kernel => Facter.kernel.upcase + setcode do "yep2" end + end + } + + assert_equal("yep2", Facter.casetest2, "Did not get case test 1") + end + if Facter.kernel == "Linux" def test_memoryonlinux assert_nothing_raised { From 5ae066b59b44b44dfabe527d9a8b5bdead14b938 Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 30 May 2006 23:55:33 +0000 Subject: [PATCH 0053/3753] Switching "tag" to "confine", because it is a more appropriate term. I will also add "tags", but they will be used for creating fact collections. git-svn-id: http://reductivelabs.com/svn/facter/trunk@120 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 67 +++++++++++++++++++++-------------------- lib/facter/memory.rb | 2 +- lib/facter/processor.rb | 4 +-- tests/tc_simple.rb | 58 +++++++++++++++++------------------ 4 files changed, 66 insertions(+), 65 deletions(-) diff --git a/lib/facter.rb b/lib/facter.rb index 57711f5324..5feee20cf7 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -218,7 +218,7 @@ def add(&block) return end - # insert resolves in order of number of tags + # insert resolves in order of number of confinements inserted = false @resolves.each_with_index { |r,index| if resolve.length > r.length @@ -239,7 +239,7 @@ def count end # Iterate across all of the fact resolution mechanisms and yield each in - # turn. These are inserted in order of most tags. + # turn. These are inserted in order of most confinements. def each @resolves.each { |r| yield r } end @@ -322,9 +322,10 @@ def value end # An actual fact resolution mechanism. These are largely just chunks of - # code, with optional tags restricting the mechanisms to only working on - # specific systems. Note that the tags are always ANDed, so any tags - # specified must all be true for the resolution to be suitable. + # code, with optional confinements restricting the mechanisms to only working on + # specific systems. Note that the confinements are always ANDed, so any + # confinements specified must all be true for the resolution to be + # suitable. class Resolution attr_accessor :interpreter, :code, :name, :fact @@ -369,13 +370,13 @@ def Resolution.exec(code, interpreter = "/bin/sh") # Create a new resolution mechanism. def initialize(name) @name = name - @tags = [] + @confines = [] @value = nil end - # Return the number of tags. + # Return the number of confines. def length - @tags.length + @confines.length end # Set our code for returning a value. @@ -401,11 +402,11 @@ def setldapname(name) def suitable? unless defined? @suitable @suitable = true - if @tags.length == 0 + if @confines.length == 0 return true end - @tags.each { |tag| - unless tag.true? + @confines.each { |confine| + unless confine.true? @suitable = false end } @@ -414,15 +415,15 @@ def suitable? return @suitable end - # Add a new tag to the resolution mechanism. - def tag(*args) + # Add a new confine to the resolution mechanism. + def confine(*args) if args[0].is_a? Hash args[0].each do |fact, values| - @tags.push Tag.new(fact,*values) + @confines.push Confine.new(fact,*values) end else fact = args.shift - @tags.push Tag.new(fact,*args) + @confines.push Confine.new(fact,*args) end end @@ -458,7 +459,7 @@ def value # A restricting tag for fact resolution mechanisms. The tag must be true # for the resolution mechanism to be suitable. - class Tag + class Confine attr_accessor :fact, :op, :value # Add the tag. Requires the fact name, an operator, and the value @@ -548,13 +549,13 @@ def Facter.loadfacts Facter.add("OperatingSystem") do #obj.os = "Linux" - tag("kernel","SunOS") + confine("kernel","SunOS") setcode do "Solaris" end end Facter.add("OperatingSystem") do #obj.os = "Linux" - tag("kernel","Linux") + confine("kernel","Linux") setcode do if FileTest.exists?("/etc/debian_version") "Debian" @@ -577,11 +578,11 @@ def Facter.loadfacts Facter.add("HardwareModel") do setcode 'uname -m' - #tag("operatingsystem","SunOS") + #confine("operatingsystem","SunOS") end Facter.add("Architecture") do - tag("operatingsystem","Debian") + confine("operatingsystem","Debian") setcode do model = Facter.hardwaremodel case model @@ -749,16 +750,16 @@ def Facter.loadfacts Facter.add("UniqueId") do setcode 'hostid', '/bin/sh' - tag("operatingsystem","Solaris") + confine("operatingsystem","Solaris") end Facter.add("HardwareISA") do setcode 'uname -p', '/bin/sh' - tag("operatingsystem","Solaris") + confine("operatingsystem","Solaris") end Facter.add("MacAddress") do - tag("operatingsystem","Solaris") + confine("operatingsystem","Solaris") setcode do ether = nil output = %x{/sbin/ifconfig -a} @@ -771,7 +772,7 @@ def Facter.loadfacts end Facter.add("MacAddress") do - tag("Kernel","Darwin") + confine("Kernel","Darwin") setcode do ether = nil output = %x{/sbin/ifconfig} @@ -787,7 +788,7 @@ def Facter.loadfacts end end Facter.add("IPAddress") do - tag("Kernel","Darwin") + confine("Kernel","Darwin") setcode do ip = nil output = %x{/sbin/ifconfig} @@ -806,22 +807,22 @@ def Facter.loadfacts end end Facter.add("Hostname") do - tag("Kernel","Darwin") - tag("KernelRelease","R7") + confine("Kernel","Darwin") + confine("KernelRelease","R7") setcode do %x{/usr/sbin/scutil --get LocalHostName} end end Facter.add("IPHostnumber") do - tag("Kernel","Darwin") - tag("KernelRelease","R6") + confine("Kernel","Darwin") + confine("KernelRelease","R6") setcode do %x{/usr/sbin/scutil --get LocalHostName} end end Facter.add("IPHostnumber") do - tag("Kernel","Darwin") - tag("KernelRelease","R6") + confine("Kernel","Darwin") + confine("KernelRelease","R6") setcode do ether = nil output = %x{/sbin/ifconfig} @@ -838,12 +839,12 @@ def Facter.loadfacts end Facter.add("ps") do - tag("operatingsystem","FreeBSD", "NetBSD", "OpenBSD", "Darwin") + confine("operatingsystem","FreeBSD", "NetBSD", "OpenBSD", "Darwin") setcode do 'ps -auxwww' end end Facter.add("id") do - tag("operatingsystem","Linux") + confine("operatingsystem","Linux") setcode "whoami" end diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb index 47f98f79b5..405d61ed92 100644 --- a/lib/facter/memory.rb +++ b/lib/facter/memory.rb @@ -50,7 +50,7 @@ def self.scale_number(size, multiplier) :SwapSize => "SwapTotal", :SwapFree => "SwapFree"}.each do |fact, name| Facter.add(fact) do - tag "kernel", "Linux" + confine :kernel => :linux setcode do Facter::Memory.meminfo_number(name) end diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb index bbb476c499..2753806a6b 100644 --- a/lib/facter/processor.rb +++ b/lib/facter/processor.rb @@ -31,13 +31,13 @@ Facter.add("ProcessorCount") do setcode do - processor_list.length + processor_list.length.to_s end end processor_list.each_with_index do |desc, i| Facter.add("Processor#{i}") do - tag :kernel => "Linux" + confine :kernel => :linux setcode do desc end diff --git a/tests/tc_simple.rb b/tests/tc_simple.rb index a293c78d65..ee815ddaf6 100644 --- a/tests/tc_simple.rb +++ b/tests/tc_simple.rb @@ -40,7 +40,7 @@ def test_version assert(Facter.version =~ /^[0-9]+(\.[0-9]+)*$/ ) end - def test_notags_sh + def test_noconfines_sh assert_nothing_raised { Facter.add("testing") do setcode "echo yup" @@ -50,7 +50,7 @@ def test_notags_sh assert_equal("yup", Facter["testing"].value) end - def test_notags + def test_noconfines assert_nothing_raised { Facter.add("testing") do setcode { "foo" } @@ -60,47 +60,47 @@ def test_notags assert_equal("foo", Facter["testing"].value) end - def test_onetruetag + def test_onetrueconfine assert_nothing_raised { Facter.add("required") { setcode { "foo" } } Facter.add("testing") { setcode { "bar" } - tag("required","foo") + confine("required","foo") } } assert_equal("bar", Facter["testing"].value) end - def test_onefalsetag + def test_onefalseconfine assert_nothing_raised { Facter.add("required") { setcode { "foo" } } Facter.add("testing") { setcode { "bar" } - tag("required","bar") + confine("required","bar") } } assert_equal(nil, Facter["testing"].value) end - def test_recursivetags + def test_recursiveconfines # This will try to autoload "required", which will fail, so the # fact will be marked as unsuitable. assert_nothing_raised { Facter.add("testing") { setcode { "bar" } - tag("required","foo") + confine("required","foo") } } assert_nothing_raised { Facter.add("required") do setcode { "foo" } - tag("testing","bar") + confine("testing","bar") end } @@ -110,15 +110,15 @@ def test_recursivetags def test_multipleresolves assert_nothing_raised { Facter.add("funtest") { - setcode { "untagged" } + setcode { "unconfineged" } } Facter.add("funtest") { - setcode { "tagged" } - tag("operatingsystem", Facter["operatingsystem"].value) + setcode { "confineged" } + confine("operatingsystem", Facter["operatingsystem"].value) } } - assert_equal("tagged", Facter["funtest"].value) + assert_equal("confineged", Facter["funtest"].value) end def test_upcase @@ -176,7 +176,7 @@ def test_adding def test_adding2 assert_nothing_raised() { Facter.add("bootest") { - tag("operatingsystem", Facter["operatingsystem"].value) + confine("operatingsystem", Facter["operatingsystem"].value) setcode "echo bootest" } } @@ -190,8 +190,8 @@ def test_adding2 Facter.add("bahtest") { #obj.os = Facter["operatingsystem"].value #obj.release = Facter["operatingsystemrelease"].value - tag("operatingsystem", Facter["operatingsystem"].value) - tag("operatingsystemrelease", + confine("operatingsystem", Facter["operatingsystem"].value) + confine("operatingsystemrelease", Facter["operatingsystemrelease"].value) setcode "echo bahtest" } @@ -206,8 +206,8 @@ def test_adding2 Facter.add("failure") { #obj.os = Facter["operatingsystem"].value #obj.release = "FakeRelease" - tag("operatingsystem", Facter["operatingsystem"].value) - tag("operatingsystemrelease", "FakeRelease") + confine("operatingsystem", Facter["operatingsystem"].value) + confine("operatingsystemrelease", "FakeRelease") setcode "echo failure" } } @@ -404,42 +404,42 @@ def test_stupidchdirring end end - def test_tag_as_array_and_hash + def test_confine_as_array_and_hash assert_nothing_raised { Facter.add("myfact") do - tag "kernel", Facter.kernel + confine "kernel", Facter.kernel setcode do "yep" end end } - assert_equal("yep", Facter.myfact, "Did not get tagged goal") + assert_equal("yep", Facter.myfact, "Did not get confineged goal") # now try it as a hash assert_nothing_raised { Facter.add("hashfact") do - tag "kernel" => Facter.kernel + confine "kernel" => Facter.kernel setcode do "hashness" end end } - assert_equal("hashness", Facter.hashfact, "Did not get tagged goal") + assert_equal("hashness", Facter.hashfact, "Did not get confineged goal") # now with multiple values assert_nothing_raised { Facter.add("hashfact2") do - tag :kernel => ["nosuchkernel", Facter.kernel] + confine :kernel => ["nosuchkernel", Facter.kernel] setcode do "multihash" end end } - assert_equal("multihash", Facter.hashfact2, "Did not get multivalue tag") + assert_equal("multihash", Facter.hashfact2, "Did not get multivalue confine") end def test_strings_or_symbols assert_nothing_raised { Facter.add("symbol1") do - tag :kernel => Facter.kernel + confine :kernel => Facter.kernel setcode do "yep1" end end } @@ -447,10 +447,10 @@ def test_strings_or_symbols assert_equal("yep1", Facter.symbol1, "Did not get symbol fact") end - def test_tag_case_insensitivity + def test_confine_case_insensitivity assert_nothing_raised { Facter.add :casetest1 do - tag :kernel => Facter.kernel.downcase + confine :kernel => Facter.kernel.downcase setcode do "yep1" end end } @@ -459,7 +459,7 @@ def test_tag_case_insensitivity assert_nothing_raised { Facter.add :casetest2 do - tag :kernel => Facter.kernel.upcase + confine :kernel => Facter.kernel.upcase setcode do "yep2" end end } From ee7d3cad4775956fa4cc8f6020d588aa1160e1fc Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 30 May 2006 23:57:01 +0000 Subject: [PATCH 0054/3753] fixing test to ignore differences in memory git-svn-id: http://reductivelabs.com/svn/facter/trunk@121 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- tests/tc_facterbin.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/tc_facterbin.rb b/tests/tc_facterbin.rb index e337b63046..cd5869810a 100755 --- a/tests/tc_facterbin.rb +++ b/tests/tc_facterbin.rb @@ -38,6 +38,8 @@ def test_output end Facter.each do |name, fact| + next if name.to_s =~ /memory/ + assert(hash.include?(name.downcase), "Did not get " + name) assert_equal(fact, hash[name], "%s was not equal" % name) From a15c8f55b791a407fada52765a3e8a14a7d31f5c Mon Sep 17 00:00:00 2001 From: luke Date: Wed, 31 May 2006 00:04:50 +0000 Subject: [PATCH 0055/3753] Adding rubysitedir fact, as requested in #13. Also, switching the output when one fact is asked for, so it only produces the single value, with no => symbol. git-svn-id: http://reductivelabs.com/svn/facter/trunk@122 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- bin/facter | 6 +++++- lib/facter.rb | 9 +++++++++ tests/tc_facterbin.rb | 12 +++++++++++- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/bin/facter b/bin/facter index 61db7eab61..f4a8b54634 100755 --- a/bin/facter +++ b/bin/facter @@ -67,5 +67,9 @@ else end facts.each { |name,value| - puts "%s => %s" % [name,value] + if facts.length == 1 + puts value + else + puts "%s => %s" % [name,value] + end } diff --git a/lib/facter.rb b/lib/facter.rb index 5feee20cf7..3c612581a1 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -530,6 +530,15 @@ def Facter.loadfacts } end + Facter.add :rubysitedir do + setcode do + version = RUBY_VERSION.to_s.sub(/\.\d+$/, '') + $:.find do |dir| + dir =~ /#{File.join("site_ruby", version)}$/ + end + end + end + Facter.add("Kernel") do setcode 'uname -s' end diff --git a/tests/tc_facterbin.rb b/tests/tc_facterbin.rb index cd5869810a..2fa12e75b0 100755 --- a/tests/tc_facterbin.rb +++ b/tests/tc_facterbin.rb @@ -28,7 +28,7 @@ def test_version def test_output output = nil assert_nothing_raised { - output = %x{facter 2>&1}.chomp + output = %x{facter 2>&1}.chomp } hash = output.split("\n").inject({}) do |h, line| @@ -45,6 +45,16 @@ def test_output assert_equal(fact, hash[name], "%s was not equal" % name) end end + + # Verify we don't print much when they just want a single fact. + def test_simpleoutput + output = nil + assert_nothing_raised { + output = %x{facter kernel 2>&1}.chomp + } + + assert(output !~ / => /, "Output includes the farrow thing") + end end # $Id$ From 99086288143a3b2448671441732ddfa8e3e966f2 Mon Sep 17 00:00:00 2001 From: luke Date: Wed, 31 May 2006 00:41:17 +0000 Subject: [PATCH 0056/3753] Adding some documentation to the binary git-svn-id: http://reductivelabs.com/svn/facter/trunk@123 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- bin/facter | 61 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 55 insertions(+), 6 deletions(-) diff --git a/bin/facter b/bin/facter index f4a8b54634..4e905befff 100755 --- a/bin/facter +++ b/bin/facter @@ -1,13 +1,56 @@ #!/usr/bin/env ruby - -#-------------------- -# duh, it's facter! # -# $Id: facter,v 1.1.1.1 2004/03/21 21:06:27 luke Exp $ +# = Synopsis +# +# Collect and display facts about the system. +# +# = Usage +# +# facter [-d|--debug] [-h|--help] [-v|--version] [fact] [fact] [...] +# +# = Description +# +# Collect and display facts about the current system. The library behind +# Facter is easy to expand, making Facter an easy way to collect information +# about a system from within the shell or within Ruby. +# +# If no facts are specifically asked for, then all facts will be returned. +# +# = Options +# +# debug:: +# Enable debugging. +# +# help:: +# Print this help message +# +# version:: +# Print the version and exit. +# +# = Example +# +# facter kernel +# +# = Author +# +# Luke Kanies +# +# = Copyright +# +# Copyright (c) 2006 Reductive Labs, LLC +# Licensed under the GNU Public License require 'getoptlong' require 'facter' +$haveusage = true + +begin + require 'rdoc/usage' +rescue LoadError + $haveusage = false +end + $debug = 0 config = nil @@ -27,8 +70,12 @@ result.each { |opt,arg| when "--debug" Facter.debugging(1) when "--help" - puts "There is no help yet" - exit + if $haveusage + RDoc::usage && exit + else + puts "No help available unless you have RDoc::usage installed" + exit + end else $stderr.puts "Invalid option '#{opt}'" exit(12) @@ -73,3 +120,5 @@ facts.each { |name,value| puts "%s => %s" % [name,value] end } + +# $Id: facter,v 1.1.1.1 2004/03/21 21:06:27 luke Exp $ From 558d05a4e67d12078e9c3e36b690b76624ff9616 Mon Sep 17 00:00:00 2001 From: luke Date: Wed, 31 May 2006 00:45:39 +0000 Subject: [PATCH 0057/3753] changing the syntax of the fact confines git-svn-id: http://reductivelabs.com/svn/facter/trunk@124 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/lib/facter.rb b/lib/facter.rb index 3c612581a1..0d37ac02c2 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -558,13 +558,13 @@ def Facter.loadfacts Facter.add("OperatingSystem") do #obj.os = "Linux" - confine("kernel","SunOS") + confine :kernel => :sunos setcode do "Solaris" end end Facter.add("OperatingSystem") do #obj.os = "Linux" - confine("kernel","Linux") + confine :kernel => :linux setcode do if FileTest.exists?("/etc/debian_version") "Debian" @@ -587,11 +587,10 @@ def Facter.loadfacts Facter.add("HardwareModel") do setcode 'uname -m' - #confine("operatingsystem","SunOS") end Facter.add("Architecture") do - confine("operatingsystem","Debian") + confine :operatingsystem => :debian setcode do model = Facter.hardwaremodel case model @@ -759,16 +758,16 @@ def Facter.loadfacts Facter.add("UniqueId") do setcode 'hostid', '/bin/sh' - confine("operatingsystem","Solaris") + confine :operatingsystem => :solaris end Facter.add("HardwareISA") do setcode 'uname -p', '/bin/sh' - confine("operatingsystem","Solaris") + confine :operatingsystem => :solaris end Facter.add("MacAddress") do - confine("operatingsystem","Solaris") + confine :operatingsystem => :solaris setcode do ether = nil output = %x{/sbin/ifconfig -a} @@ -781,7 +780,7 @@ def Facter.loadfacts end Facter.add("MacAddress") do - confine("Kernel","Darwin") + confine :kernel => :darwin setcode do ether = nil output = %x{/sbin/ifconfig} @@ -797,7 +796,7 @@ def Facter.loadfacts end end Facter.add("IPAddress") do - confine("Kernel","Darwin") + confine :kernel => :darwin setcode do ip = nil output = %x{/sbin/ifconfig} @@ -816,22 +815,19 @@ def Facter.loadfacts end end Facter.add("Hostname") do - confine("Kernel","Darwin") - confine("KernelRelease","R7") + confine :kernel => :darwin, :kernelrelease => "R7" setcode do %x{/usr/sbin/scutil --get LocalHostName} end end Facter.add("IPHostnumber") do - confine("Kernel","Darwin") - confine("KernelRelease","R6") + confine :kernel => :darwin, :kernelrelease => "R6" setcode do %x{/usr/sbin/scutil --get LocalHostName} end end Facter.add("IPHostnumber") do - confine("Kernel","Darwin") - confine("KernelRelease","R6") + confine :kernel => :darwin, :kernelrelease => "R6" setcode do ether = nil output = %x{/sbin/ifconfig} @@ -848,12 +844,12 @@ def Facter.loadfacts end Facter.add("ps") do - confine("operatingsystem","FreeBSD", "NetBSD", "OpenBSD", "Darwin") + confine :operatingsystem => %w{FreeBSD NetBSD OpenBSD Darwin} setcode do 'ps -auxwww' end end Facter.add("id") do - confine("operatingsystem","Linux") + confine :operatingsystem => :linux setcode "whoami" end From 4296f1f308871a50b35477bd7642d865cea9e9da Mon Sep 17 00:00:00 2001 From: luke Date: Wed, 31 May 2006 16:44:05 +0000 Subject: [PATCH 0058/3753] fixing the linux processor stuff so it only gets called on linux git-svn-id: http://reductivelabs.com/svn/facter/trunk@125 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter/processor.rb | 41 ++++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb index 2753806a6b..f353e71204 100644 --- a/lib/facter/processor.rb +++ b/lib/facter/processor.rb @@ -18,28 +18,31 @@ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA # -processor_num = -1 -processor_list = [] -File.readlines("/proc/cpuinfo").each do |l| - if l =~ /processor\s+:\s+(\d+)/ - processor_num = $1.to_i - elsif l =~ /model name\s+:\s+(.*)\s*$/ - processor_list[processor_num] = $1 unless processor_num == -1 - processor_num = -1 - end -end - -Facter.add("ProcessorCount") do - setcode do - processor_list.length.to_s - end -end +if Facter.kernel == "Linux" + processor_num = -1 + processor_list = [] + File.readlines("/proc/cpuinfo").each do |l| + if l =~ /processor\s+:\s+(\d+)/ + processor_num = $1.to_i + elsif l =~ /model name\s+:\s+(.*)\s*$/ + processor_list[processor_num] = $1 unless processor_num == -1 + processor_num = -1 + end + end -processor_list.each_with_index do |desc, i| - Facter.add("Processor#{i}") do + Facter.add("ProcessorCount") do confine :kernel => :linux setcode do - desc + processor_list.length.to_s + end + end + + processor_list.each_with_index do |desc, i| + Facter.add("Processor#{i}") do + confine :kernel => :linux + setcode do + desc + end end end end From 75b18351461551b66cd68c17ab8a72608a5c6428 Mon Sep 17 00:00:00 2001 From: luke Date: Wed, 31 May 2006 17:43:48 +0000 Subject: [PATCH 0059/3753] Adding tagging frameworks back into Facter, and adding the ability to specify tags to the to_hash method so that you only receive facts tagged with specific tags git-svn-id: http://reductivelabs.com/svn/facter/trunk@126 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 92 +++++++++++++++++++++++++++++++++++----------- tests/tc_simple.rb | 49 +++++++++++++++++++++++- 2 files changed, 119 insertions(+), 22 deletions(-) diff --git a/lib/facter.rb b/lib/facter.rb index 0d37ac02c2..b2077f9108 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -30,10 +30,8 @@ class Facter GREEN = "" RESET = "" @@debug = 0 - @@os = nil - @@osrel = nil - attr_accessor :name, :os, :osrel, :hardware, :searching, :ldapname + attr_accessor :name, :searching, :ldapname # module methods @@ -95,15 +93,33 @@ def each } end + # Allow users to call fact names directly on the Facter class, + # either retrieving the value or comparing it to an existing value. def method_missing(name, *args) - retval = nil + question = false + if name.to_s =~ /\?$/ + question = true + name = name.to_s.sub(/\?$/,'') + end + if fact = self[name] - retval = fact.value + if question + value = fact.value.downcase + args.each do |arg| + if arg.to_s.downcase == value + return true + end + end + + # If we got this far, there was no match. + return false + else + return fact.value + end else - retval = super + # Else, fail like a normal missing method. + return super end - - retval end end @@ -158,9 +174,14 @@ def Facter.reset end # Return a hash of all of our facts. - def Facter.to_hash - self.inject({}) do |h, ary| - h[ary[0]] = ary[1] + def Facter.to_hash(*tags) + @@facts.inject({}) do |h, ary| + if ary[1].suitable? and (tags.empty? or ary[1].tagged?(*tags)) + value = ary[1].value + if value + h[ary[0]] = value + end + end h end end @@ -193,6 +214,7 @@ def initialize(name) end @resolves = [] + @tags = [] @searching = false @value = nil @@ -270,6 +292,29 @@ def suitable? return @suitable end + # Add one ore more tags + def tag(*tags) + tags.each do |t| + t = t.to_s.downcase.intern + @tags << t unless @tags.include?(t) + end + end + + # Is our fact tagged with all of the specified tags? + def tagged?(*tags) + tags.each do |t| + unless @tags.include? t.to_s.downcase.intern + return false + end + end + + return true + end + + def tags + @tags.dup + end + # Return the value for a given fact. Searches through all of the mechanisms # and returns either the first value or nil. def value @@ -367,6 +412,18 @@ def Resolution.exec(code, interpreter = "/bin/sh") end end + # Add a new confine to the resolution mechanism. + def confine(*args) + if args[0].is_a? Hash + args[0].each do |fact, values| + @confines.push Confine.new(fact,*values) + end + else + fact = args.shift + @confines.push Confine.new(fact,*args) + end + end + # Create a new resolution mechanism. def initialize(name) @name = name @@ -415,16 +472,9 @@ def suitable? return @suitable end - # Add a new confine to the resolution mechanism. - def confine(*args) - if args[0].is_a? Hash - args[0].each do |fact, values| - @confines.push Confine.new(fact,*values) - end - else - fact = args.shift - @confines.push Confine.new(fact,*args) - end + # Set tags on our parent fact. + def tag(*values) + @fact.tag(*values) end def to_s diff --git a/tests/tc_simple.rb b/tests/tc_simple.rb index ee815ddaf6..5155fde420 100644 --- a/tests/tc_simple.rb +++ b/tests/tc_simple.rb @@ -279,7 +279,7 @@ def test_hash hash.each do |name, value| assert_instance_of(String, name) - assert_instance_of(String, value) + assert_instance_of(String, value, "%s's value is not a string" % name) end end @@ -467,6 +467,43 @@ def test_confine_case_insensitivity assert_equal("yep2", Facter.casetest2, "Did not get case test 1") end + def test_tags + assert_nothing_raised { + Facter.add :tagtest do + setcode do "yep1" end + tag :system, :performance + end + + # Now add another resolution mechanism with overlapping tags + Facter.add :tagtest do + setcode do "yep2" end + tag :puppet, :performance + end + + # Finally, one that's only got the performance tag + Facter.add :othertag do + setcode do "nope" end + tag :performance + end + } + + tags = nil + assert_nothing_raised { + tags = Facter[:tagtest].tags + } + [:puppet, :performance, :system].each do |t| + assert(tags.include?(t), "Did not get tag %s" % t) + end + + hash = nil + assert_nothing_raised { + hash = Facter.to_hash(:puppet, :system) + } + + assert(hash.include?("tagtest"), "Did not get tagged fact") + assert(! hash.include?("othertag"), "Got incorrectly tagged fact") + end + if Facter.kernel == "Linux" def test_memoryonlinux assert_nothing_raised { @@ -481,6 +518,16 @@ def test_processor_on_linux } end end + + def test_factquestion + kernel = Facter.kernel + assert_nothing_raised { + assert(Facter.kernel?(kernel.downcase), "Kernel did not match") + assert(Facter.kernel?(kernel.upcase), "Upcase kernel did not match") + assert(Facter.kernel?(kernel.intern), "Symbol kernel did not match") + assert(! Facter.kernel?("nosuchkernel"), "Fake kernel matched") + } + end end # $Id$ From 7f2504dd4e8f4b717eaa65acb68245cdfe65c442 Mon Sep 17 00:00:00 2001 From: luke Date: Wed, 31 May 2006 18:03:28 +0000 Subject: [PATCH 0060/3753] fixing test so that it works even if rubylib is not set git-svn-id: http://reductivelabs.com/svn/facter/trunk@127 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- tests/tc_facterbin.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/tc_facterbin.rb b/tests/tc_facterbin.rb index 2fa12e75b0..8a18ef455f 100755 --- a/tests/tc_facterbin.rb +++ b/tests/tc_facterbin.rb @@ -14,7 +14,11 @@ ENV["PATH"] = File.join($facterbase, "bin") + ":" + ENV["PATH"] # and then the library directory -ENV["RUBYLIB"] += ":" + libdir +if ENV["RUBYLIB"] + ENV["RUBYLIB"] += ":" + libdir +else + ENV["RUBYLIB"] = libdir +end class TestFacterBin < Test::Unit::TestCase def test_version From 4c1d5e0e70d0cd41b3efad5bff980f6625b216ae Mon Sep 17 00:00:00 2001 From: luke Date: Wed, 31 May 2006 18:23:04 +0000 Subject: [PATCH 0061/3753] trying to fix facterbin rubylib setting git-svn-id: http://reductivelabs.com/svn/facter/trunk@128 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- tests/tc_facterbin.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/tc_facterbin.rb b/tests/tc_facterbin.rb index 8a18ef455f..b2b8100edd 100755 --- a/tests/tc_facterbin.rb +++ b/tests/tc_facterbin.rb @@ -1,10 +1,11 @@ #! /usr/bin/env ruby $facterbase = File.dirname(File.dirname(__FILE__)) -if $facterbase == "." +if $facterbase == "." and Dir.getwd =~ /tests$/ $facterbase = ".." end libdir = File.join($facterbase, "lib") + $:.unshift libdir require 'facter' From 539d593decbd24e67da295f5818da7c8a861d441 Mon Sep 17 00:00:00 2001 From: luke Date: Wed, 31 May 2006 18:30:59 +0000 Subject: [PATCH 0062/3753] fixing installer so it does not install batch files on darwin git-svn-id: http://reductivelabs.com/svn/facter/trunk@129 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- install.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/install.rb b/install.rb index 6dfa33eb90..89439d0956 100644 --- a/install.rb +++ b/install.rb @@ -246,7 +246,8 @@ def install_binfile(from, op_file, target) end end - if Config::CONFIG["target_os"] =~ /win/io + # We don't want bat files on darwin + if Config::CONFIG["target_os"] =~ /win/io and Config::CONFIG["target_os"] !~ /darwin/ installed_wrapper = false if File.exists?("#{from}.bat") From 261d909691d0c743f95de434847fdd1b8c198cfd Mon Sep 17 00:00:00 2001 From: luke Date: Fri, 9 Jun 2006 14:58:17 +0000 Subject: [PATCH 0063/3753] Updated to version 1.3 git-svn-id: http://reductivelabs.com/svn/facter/trunk@130 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- conf/solaris/pkginfo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/solaris/pkginfo b/conf/solaris/pkginfo index fac31a514b..425c1cad35 100644 --- a/conf/solaris/pkginfo +++ b/conf/solaris/pkginfo @@ -1,6 +1,6 @@ PKG=CSWfacter NAME=facter - System Fact Gatherer -VERSION=1.2.1 +VERSION=1.3 CATEGORY=application VENDOR=http://reductivelabs.com/projects/facter HOTLINE=http://reductivelabs.com/cgi-bin/facter.cgi From 15931effe2ca7e9be14cd5d7c5c30ddb7062a095 Mon Sep 17 00:00:00 2001 From: luke Date: Fri, 9 Jun 2006 14:58:18 +0000 Subject: [PATCH 0064/3753] Updated to version 1.3 git-svn-id: http://reductivelabs.com/svn/facter/trunk@131 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter.rb b/lib/facter.rb index b2077f9108..20b94bbb94 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -11,7 +11,7 @@ class Facter include Comparable include Enumerable - FACTERVERSION = '1.2.1' + FACTERVERSION = '1.3' # = Facter # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. From 15f2f44d45f817452bcc3d0f6e4183e75c11d815 Mon Sep 17 00:00:00 2001 From: luke Date: Fri, 9 Jun 2006 14:58:20 +0000 Subject: [PATCH 0065/3753] Updated to version 1.3 git-svn-id: http://reductivelabs.com/svn/facter/trunk@132 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- conf/redhat/facter.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index ab87cd20a0..b48afd985f 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -2,7 +2,7 @@ Summary: Ruby module for collecting simple facts about a host operating system Name: facter -Version: 1.2.1 +Version: 1.3 Release: 1%{?dist} License: GPL Group: System Environment/Base From b5431524a784baa1a7c72f7b0cbe21eb5891c5ff Mon Sep 17 00:00:00 2001 From: lutter Date: Mon, 12 Jun 2006 13:17:18 +0000 Subject: [PATCH 0066/3753] Updated for use with latest Fedora ruby packages git-svn-id: http://reductivelabs.com/svn/facter/trunk@134 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- conf/redhat/facter.spec | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index b48afd985f..022be720f7 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -1,4 +1,4 @@ -%define sitelibdir %(ruby -rrbconfig -e 'puts Config::CONFIG["sitelibdir"]') +%{!?ruby_sitelibdir: %define ruby_sitelibdir %(ruby -rrbconfig -e 'puts Config::CONFIG["sitelibdir"]')} Summary: Ruby module for collecting simple facts about a host operating system Name: facter @@ -9,11 +9,10 @@ Group: System Environment/Base URL: http://reductivelabs.com/projects/facter Source0: http://reductivelabs.com/downloads/facter/%{name}-%{version}.tgz BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) -# It's not possible to build ruby noarch packages currently -# See bz184199 -#BuildArchitectures: noarch +BuildArchitectures: noarch Requires: ruby >= 1.8.1 +Requires: ruby(abi) = 1.8 BuildRequires: ruby >= 1.8.1 %description @@ -25,25 +24,40 @@ operating system. Additional facts can be added through simple Ruby scripts %setup -q %build +sed -i -e 's@^#!.*$@#! /usr/bin/ruby@' bin/facter %install -rm -rf $RPM_BUILD_ROOT -mkdir $RPM_BUILD_ROOT -DESTDIR=$RPM_BUILD_ROOT ruby install.rb --no-tests -chmod a-x $RPM_BUILD_ROOT%{sitelibdir}/*.rb +rm -rf %{buildroot} +mkdir %{buildroot} + +%{__install} -d -m0755 %{buildroot}%{ruby_sitelibdir} +%{__install} -d -m0755 %{buildroot}%{_bindir} +%{__install} -d -m0755 %{buildroot}%{_docdir}/%{name}-%{version} + +%{__install} -p -m0644 lib/*.rb %{buildroot}%{ruby_sitelibdir} +%{__install} -p -m0755 bin/facter %{buildroot}%{_bindir} %clean -rm -rf $RPM_BUILD_ROOT +rm -rf %{buildroot} %files %defattr(-,root,root,-) %{_bindir}/facter -%{sitelibdir}/facter.rb +%{ruby_sitelibdir}/facter.rb %doc CHANGELOG COPYING INSTALL LICENSE README %changelog +* Mon Jun 12 2006 David Lutterkort - 1.3-1 +- Require ruby(abi). Build as noarch + +* Mon Apr 17 2006 David Lutterkort - 1.1.4-4 +- Rebuilt with changed upstream tarball + +* Tue Mar 21 2006 David Lutterkort - 1.1.4-3 +- Do not rely on install.rb, it will be deleted upstream + * Mon Mar 13 2006 David Lutterkort - 1.1.4-2 - Commented out noarch; requires fix for bz184199 From 81f451b17575de74157c2df2adf938f20ac49ea9 Mon Sep 17 00:00:00 2001 From: luke Date: Mon, 12 Jun 2006 17:36:03 +0000 Subject: [PATCH 0067/3753] updating for 1.3 git-svn-id: http://reductivelabs.com/svn/facter/trunk@135 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- Rakefile | 4 ++++ conf/redhat/facter.spec | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/Rakefile b/Rakefile index f3d6271c86..e87748c6bd 100644 --- a/Rakefile +++ b/Rakefile @@ -23,6 +23,10 @@ project = Rake::RedLabProject.new("facter") do |p| 'doc/**/*', 'etc/*' ] + + p.epmhosts = %w{culain} + p.sunpkghost = "sol10b" + p.rpmhost = "fedora1" end project.mkgemtask do |gem| diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index 022be720f7..a04359cdaf 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -45,10 +45,15 @@ rm -rf %{buildroot} %defattr(-,root,root,-) %{_bindir}/facter %{ruby_sitelibdir}/facter.rb +%{sitelibdir}/facter/memory.rb +%{sitelibdir}/facter/processor.rb %doc CHANGELOG COPYING INSTALL LICENSE README %changelog +* Fri Jun 9 2006 Luke Kanies - 1.3.0-1 +- Added memory.rb and processor.rb + * Mon Jun 12 2006 David Lutterkort - 1.3-1 - Require ruby(abi). Build as noarch From 6ac796d179d115df26cc8323abd357851e4da13e Mon Sep 17 00:00:00 2001 From: luke Date: Mon, 19 Jun 2006 18:12:13 +0000 Subject: [PATCH 0068/3753] Fixing #15. Just adding rescue blocks around the load statements. git-svn-id: http://reductivelabs.com/svn/facter/trunk@136 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 7 ++++++- tests/tc_simple.rb | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/lib/facter.rb b/lib/facter.rb index 20b94bbb94..c663c8c7f7 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -914,7 +914,12 @@ def Facter.loadfacts # the facts won't get reloaded if someone calls # "loadfacts". Really only important in testing, but, # well, it's important in testing. - load file + begin + load file + rescue => detail + warn "Could not load %s: %s" % + [file, detail] + end end end end diff --git a/tests/tc_simple.rb b/tests/tc_simple.rb index 5155fde420..1c3edd544c 100644 --- a/tests/tc_simple.rb +++ b/tests/tc_simple.rb @@ -9,16 +9,22 @@ require 'test/unit' require 'facter' +require 'fileutils' if __FILE__ == $0 Facter.debugging(true) end class TestFacter < Test::Unit::TestCase + def tearhook(&block) + @tearhooks << block + end + def setup Facter.loadfacts @tmpfiles = [] + @tearhooks = [] end def teardown @@ -528,6 +534,32 @@ def test_factquestion assert(! Facter.kernel?("nosuchkernel"), "Fake kernel matched") } end + + # Make sure that facter doesn't fail when it gets bad files. + def test_ignore_bad_files + # Create a broken file + dir = "/tmp/factertest-brokenfile" + @tmpfiles << dir + libdir = File.join(dir, "facter") + + FileUtils.mkdir_p(libdir) + + $: << libdir + tearhook { $:.delete libdir } + + file = File.join(libdir, "file.rb") + + + File.open(file, "w") do |f| + f.puts "asdflkjaeflkj23rljadflkjasdfuhasd8;;lkjadsf;j24iojlkajsdf" + end + + assert_nothing_raised { + Facter.loadfacts() + } + + + end end # $Id$ From 73aeadeaed9b40da9b72a94334d8f0be80220c0f Mon Sep 17 00:00:00 2001 From: luke Date: Mon, 19 Jun 2006 19:04:48 +0000 Subject: [PATCH 0069/3753] adding a call to dnsdomainname before domainname git-svn-id: http://reductivelabs.com/svn/facter/trunk@137 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/facter.rb b/lib/facter.rb index c663c8c7f7..1b497c8c87 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -687,6 +687,18 @@ def Facter.loadfacts end end end + # Look for the DNS domain name command first. + Facter.add("Domain") do + setcode do + domain = Resolution.exec('dnsdomainname') or nil + # make sure it's a real domain + if domain and domain =~ /.+\..+/ + domain + else + nil + end + end + end Facter.add("Domain") do setcode do domain = Resolution.exec('domainname') or nil From 5e34a1f672df3468743c2fa1d8c2bae4d0a20d1f Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 20 Jun 2006 00:54:20 +0000 Subject: [PATCH 0070/3753] Updated to version 1.3.1 git-svn-id: http://reductivelabs.com/svn/facter/trunk@138 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- conf/solaris/pkginfo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/solaris/pkginfo b/conf/solaris/pkginfo index 425c1cad35..2a0cf8c5d0 100644 --- a/conf/solaris/pkginfo +++ b/conf/solaris/pkginfo @@ -1,6 +1,6 @@ PKG=CSWfacter NAME=facter - System Fact Gatherer -VERSION=1.3 +VERSION=1.3.1 CATEGORY=application VENDOR=http://reductivelabs.com/projects/facter HOTLINE=http://reductivelabs.com/cgi-bin/facter.cgi From 8ad0323454cacea4147bc6dc72413ac54630e996 Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 20 Jun 2006 00:54:23 +0000 Subject: [PATCH 0071/3753] Updated to version 1.3.1 git-svn-id: http://reductivelabs.com/svn/facter/trunk@139 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter.rb b/lib/facter.rb index 1b497c8c87..d3d251d77b 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -11,7 +11,7 @@ class Facter include Comparable include Enumerable - FACTERVERSION = '1.3' + FACTERVERSION = '1.3.1' # = Facter # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. From 31caa0886d8c73b5ad2955f6c45f59af9e07a253 Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 20 Jun 2006 00:54:25 +0000 Subject: [PATCH 0072/3753] Updated to version 1.3.1 git-svn-id: http://reductivelabs.com/svn/facter/trunk@140 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- conf/redhat/facter.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index a04359cdaf..006dfb1c4f 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -2,7 +2,7 @@ Summary: Ruby module for collecting simple facts about a host operating system Name: facter -Version: 1.3 +Version: 1.3.1 Release: 1%{?dist} License: GPL Group: System Environment/Base From a0a33e63d1744d8f219055607ecfec92b6b997c1 Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 20 Jun 2006 01:07:49 +0000 Subject: [PATCH 0073/3753] fixing spec file again git-svn-id: http://reductivelabs.com/svn/facter/trunk@142 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- conf/redhat/facter.spec | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index 006dfb1c4f..d5affdd8dd 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -31,10 +31,12 @@ rm -rf %{buildroot} mkdir %{buildroot} %{__install} -d -m0755 %{buildroot}%{ruby_sitelibdir} +%{__install} -d -m0755 %{buildroot}%{ruby_sitelibdir}/facter %{__install} -d -m0755 %{buildroot}%{_bindir} %{__install} -d -m0755 %{buildroot}%{_docdir}/%{name}-%{version} %{__install} -p -m0644 lib/*.rb %{buildroot}%{ruby_sitelibdir} +%{__install} -p -m0644 lib/facter/*.rb %{buildroot}%{ruby_sitelibdir}/facter %{__install} -p -m0755 bin/facter %{buildroot}%{_bindir} %clean @@ -45,18 +47,21 @@ rm -rf %{buildroot} %defattr(-,root,root,-) %{_bindir}/facter %{ruby_sitelibdir}/facter.rb -%{sitelibdir}/facter/memory.rb -%{sitelibdir}/facter/processor.rb +%{ruby_sitelibdir}/facter/memory.rb +%{ruby_sitelibdir}/facter/processor.rb %doc CHANGELOG COPYING INSTALL LICENSE README %changelog * Fri Jun 9 2006 Luke Kanies - 1.3.0-1 -- Added memory.rb and processor.rb +- Fixed spec file to work again with the extra memory and processor files. -* Mon Jun 12 2006 David Lutterkort - 1.3-1 +* Mon Jun 12 2006 David Lutterkort - 1.3.0-1 - Require ruby(abi). Build as noarch +* Fri Jun 9 2006 Luke Kanies - 1.3.0-1 +- Added memory.rb and processor.rb + * Mon Apr 17 2006 David Lutterkort - 1.1.4-4 - Rebuilt with changed upstream tarball From 157f68e94eec6a35a8612f82e690a615dcdbf4fe Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 20 Jun 2006 01:19:19 +0000 Subject: [PATCH 0074/3753] fixing license issues git-svn-id: http://reductivelabs.com/svn/facter/trunk@143 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- COPYING | 667 +++++++++++++++++++++++++++++++------------------- lib/facter.rb | 19 +- 2 files changed, 431 insertions(+), 255 deletions(-) diff --git a/COPYING b/COPYING index e77696ae8d..5ab7695ab8 100644 --- a/COPYING +++ b/COPYING @@ -1,221 +1,397 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 - Copyright (C) 1989, 1991 Free Software Foundation, Inc. - 675 Mass Ave, Cambridge, MA 02139, USA + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Library General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. The precise terms and conditions for copying, distribution and -modification follow. +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. - GNU GENERAL PUBLIC LICENSE + GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: - a) You must cause the modified files to carry prominent notices + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, +identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of +on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. +entire whole, and thus to each and every part regardless of who wrote +it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or -collective works based on the Program. +collective works based on the Library. -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are +distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying -the Program or works based on it. +the Library or works based on it. - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to +You are not responsible for enforcing compliance by third parties with this License. - - 7. If, as a consequence of a court judgment or allegation of patent + + 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. +refrain entirely from distribution of the Library. -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is +integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that @@ -225,115 +401,104 @@ impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in + + 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. NO WARRANTY - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. END OF TERMS AND CONDITIONS - How to Apply These Terms to Your New Programs + How to Apply These Terms to Your New Libraries - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. - - Copyright (C) 19yy + + Copyright (C) - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. - This program is distributed in the hope that it will be useful, + This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) 19yy name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if +school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. - , 1 April 1989 + , 1 April 1990 Ty Coon, President of Vice -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Library General -Public License instead of this License. +That's all there is to it! + + diff --git a/lib/facter.rb b/lib/facter.rb index d3d251d77b..062fa05d06 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -1,9 +1,20 @@ # $Id$ #-- -# Copyright 2004 Luke Kanies -# -# This program is free software. It may be redistributed and/or modified under -# the terms of the Apache license. +# Copyright 2006 Luke Kanies +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA # #-- From 9f14df9171baf9ba8c65b22e1c240a8205c0fbd2 Mon Sep 17 00:00:00 2001 From: luke Date: Mon, 26 Jun 2006 22:17:24 +0000 Subject: [PATCH 0075/3753] Deleting this file until the hanging problems are resolved git-svn-id: http://reductivelabs.com/svn/facter/trunk@144 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter/memory.rb | 58 -------------------------------------------- 1 file changed, 58 deletions(-) delete mode 100644 lib/facter/memory.rb diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb deleted file mode 100644 index 405d61ed92..0000000000 --- a/lib/facter/memory.rb +++ /dev/null @@ -1,58 +0,0 @@ -# -# memory.rb -# Additional Facts for memory/swap usage -# -# Copyright (C) 2006 Mooter Media Ltd -# Author: Matthew Palmer -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation (version 2 of the License) -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA - -module Facter::Memory - def self.meminfo_number(tag) - memsize = "" - File.readlines("/proc/meminfo").each do |l| - if l =~ /^#{tag}:\s+(\d+)\s+(\S+)/ - memsize = scale_number($1.to_f, $2) - end - end - - memsize - end - - def self.scale_number(size, multiplier) - suffixes = ['', 'kB', 'MB', 'GB', 'TB'] - - s = suffixes.shift - while s != multiplier - s = suffixes.shift - end - - while size > 1024.0 - size /= 1024.0 - s = suffixes.shift - end - - return "%.2f %s" % [size, s] - end -end - -{:MemorySize => "MemTotal", - :MemoryFree => "MemFree", - :SwapSize => "SwapTotal", - :SwapFree => "SwapFree"}.each do |fact, name| - Facter.add(fact) do - confine :kernel => :linux - setcode do - Facter::Memory.meminfo_number(name) - end - end -end From ba2e18916d8c1071ee0b983b6e9f1fc4dbfbcbb9 Mon Sep 17 00:00:00 2001 From: luke Date: Mon, 26 Jun 2006 22:18:08 +0000 Subject: [PATCH 0076/3753] removing processor.rb in case it has the same problems as the memory file git-svn-id: http://reductivelabs.com/svn/facter/trunk@145 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter/processor.rb | 48 ----------------------------------------- 1 file changed, 48 deletions(-) delete mode 100644 lib/facter/processor.rb diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb deleted file mode 100644 index f353e71204..0000000000 --- a/lib/facter/processor.rb +++ /dev/null @@ -1,48 +0,0 @@ -# -# processor.rb -# Additional Facts about the machine's CPUs -# -# Copyright (C) 2006 Mooter Media Ltd -# Author: Matthew Palmer -# -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation (version 2 of the License) -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA -# - -if Facter.kernel == "Linux" - processor_num = -1 - processor_list = [] - File.readlines("/proc/cpuinfo").each do |l| - if l =~ /processor\s+:\s+(\d+)/ - processor_num = $1.to_i - elsif l =~ /model name\s+:\s+(.*)\s*$/ - processor_list[processor_num] = $1 unless processor_num == -1 - processor_num = -1 - end - end - - Facter.add("ProcessorCount") do - confine :kernel => :linux - setcode do - processor_list.length.to_s - end - end - - processor_list.each_with_index do |desc, i| - Facter.add("Processor#{i}") do - confine :kernel => :linux - setcode do - desc - end - end - end -end From ace180f3ecfac50fe0cef9bd3e11ff7e209f8f9a Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 27 Jun 2006 05:28:17 +0000 Subject: [PATCH 0077/3753] Re-adding these files, since Matt has found a solution to the hanging problem. git-svn-id: http://reductivelabs.com/svn/facter/trunk@146 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter/memory.rb | 58 +++++++++++++++++++++++++++++++++++++++++ lib/facter/processor.rb | 48 ++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 lib/facter/memory.rb create mode 100644 lib/facter/processor.rb diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb new file mode 100644 index 0000000000..405d61ed92 --- /dev/null +++ b/lib/facter/memory.rb @@ -0,0 +1,58 @@ +# +# memory.rb +# Additional Facts for memory/swap usage +# +# Copyright (C) 2006 Mooter Media Ltd +# Author: Matthew Palmer +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation (version 2 of the License) +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA + +module Facter::Memory + def self.meminfo_number(tag) + memsize = "" + File.readlines("/proc/meminfo").each do |l| + if l =~ /^#{tag}:\s+(\d+)\s+(\S+)/ + memsize = scale_number($1.to_f, $2) + end + end + + memsize + end + + def self.scale_number(size, multiplier) + suffixes = ['', 'kB', 'MB', 'GB', 'TB'] + + s = suffixes.shift + while s != multiplier + s = suffixes.shift + end + + while size > 1024.0 + size /= 1024.0 + s = suffixes.shift + end + + return "%.2f %s" % [size, s] + end +end + +{:MemorySize => "MemTotal", + :MemoryFree => "MemFree", + :SwapSize => "SwapTotal", + :SwapFree => "SwapFree"}.each do |fact, name| + Facter.add(fact) do + confine :kernel => :linux + setcode do + Facter::Memory.meminfo_number(name) + end + end +end diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb new file mode 100644 index 0000000000..f353e71204 --- /dev/null +++ b/lib/facter/processor.rb @@ -0,0 +1,48 @@ +# +# processor.rb +# Additional Facts about the machine's CPUs +# +# Copyright (C) 2006 Mooter Media Ltd +# Author: Matthew Palmer +# +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation (version 2 of the License) +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA +# + +if Facter.kernel == "Linux" + processor_num = -1 + processor_list = [] + File.readlines("/proc/cpuinfo").each do |l| + if l =~ /processor\s+:\s+(\d+)/ + processor_num = $1.to_i + elsif l =~ /model name\s+:\s+(.*)\s*$/ + processor_list[processor_num] = $1 unless processor_num == -1 + processor_num = -1 + end + end + + Facter.add("ProcessorCount") do + confine :kernel => :linux + setcode do + processor_list.length.to_s + end + end + + processor_list.each_with_index do |desc, i| + Facter.add("Processor#{i}") do + confine :kernel => :linux + setcode do + desc + end + end + end +end From c2aa5086ab55da9c708d962b84a1b85404fc6329 Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 27 Jun 2006 05:33:41 +0000 Subject: [PATCH 0078/3753] Adding thread exclusivity to memory and cpu reading git-svn-id: http://reductivelabs.com/svn/facter/trunk@147 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter/memory.rb | 10 +++++++--- lib/facter/processor.rb | 16 ++++++++++------ 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb index 405d61ed92..24678e1ec2 100644 --- a/lib/facter/memory.rb +++ b/lib/facter/memory.rb @@ -17,11 +17,15 @@ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA module Facter::Memory + require 'thread' + def self.meminfo_number(tag) memsize = "" - File.readlines("/proc/meminfo").each do |l| - if l =~ /^#{tag}:\s+(\d+)\s+(\S+)/ - memsize = scale_number($1.to_f, $2) + Thread::exclusive do + File.readlines("/proc/meminfo").each do |l| + if l =~ /^#{tag}:\s+(\d+)\s+(\S+)/ + memsize = scale_number($1.to_f, $2) + end end end diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb index f353e71204..812bd99838 100644 --- a/lib/facter/processor.rb +++ b/lib/facter/processor.rb @@ -18,15 +18,19 @@ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA # +require 'thread' + if Facter.kernel == "Linux" processor_num = -1 processor_list = [] - File.readlines("/proc/cpuinfo").each do |l| - if l =~ /processor\s+:\s+(\d+)/ - processor_num = $1.to_i - elsif l =~ /model name\s+:\s+(.*)\s*$/ - processor_list[processor_num] = $1 unless processor_num == -1 - processor_num = -1 + Thread::exclusive do + File.readlines("/proc/cpuinfo").each do |l| + if l =~ /processor\s+:\s+(\d+)/ + processor_num = $1.to_i + elsif l =~ /model name\s+:\s+(.*)\s*$/ + processor_list[processor_num] = $1 unless processor_num == -1 + processor_num = -1 + end end end From ea96381bec01c7fd437ba9590451529a3d45d407 Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 27 Jun 2006 05:34:07 +0000 Subject: [PATCH 0079/3753] simple packaging updaets git-svn-id: http://reductivelabs.com/svn/facter/trunk@148 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- Rakefile | 1 - conf/redhat/facter.spec | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Rakefile b/Rakefile index e87748c6bd..78085e2cc1 100644 --- a/Rakefile +++ b/Rakefile @@ -25,7 +25,6 @@ project = Rake::RedLabProject.new("facter") do |p| ] p.epmhosts = %w{culain} - p.sunpkghost = "sol10b" p.rpmhost = "fedora1" end diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index d5affdd8dd..275993a6ff 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -53,7 +53,7 @@ rm -rf %{buildroot} %changelog -* Fri Jun 9 2006 Luke Kanies - 1.3.0-1 +* Fri Jun 19 2006 Luke Kanies - 1.3.0-1 - Fixed spec file to work again with the extra memory and processor files. * Mon Jun 12 2006 David Lutterkort - 1.3.0-1 From c4659bd901f39b6b2960d60212229ac0af40fdc7 Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 27 Jun 2006 05:35:06 +0000 Subject: [PATCH 0080/3753] Updated to version 1.3.2 git-svn-id: http://reductivelabs.com/svn/facter/trunk@149 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter.rb b/lib/facter.rb index 062fa05d06..216da0125e 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -22,7 +22,7 @@ class Facter include Comparable include Enumerable - FACTERVERSION = '1.3.1' + FACTERVERSION = '1.3.2' # = Facter # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. From 86fdc87e56be7f0084a991d19c2cea3111005b67 Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 27 Jun 2006 05:35:08 +0000 Subject: [PATCH 0081/3753] Updated to version 1.3.2 git-svn-id: http://reductivelabs.com/svn/facter/trunk@150 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- conf/redhat/facter.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index 275993a6ff..86f5168411 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -2,7 +2,7 @@ Summary: Ruby module for collecting simple facts about a host operating system Name: facter -Version: 1.3.1 +Version: 1.3.2 Release: 1%{?dist} License: GPL Group: System Environment/Base From 747d45a53918e7f339f9a0e6cbc2324933406ffe Mon Sep 17 00:00:00 2001 From: luke Date: Wed, 28 Jun 2006 17:34:05 +0000 Subject: [PATCH 0082/3753] Adding the ability to retrieve facts from the environment. git-svn-id: http://reductivelabs.com/svn/facter/trunk@151 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 9 +++++++++ tests/tc_simple.rb | 25 +++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/lib/facter.rb b/lib/facter.rb index 216da0125e..454ee8cc7d 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -946,6 +946,15 @@ def Facter.loadfacts end end end + + # Now try to get facts from the environment + ENV.each do |name, value| + if name =~ /^facter_?(\w+)$/i + Facter.add($1) do + setcode { value } + end + end + end end Facter.loadfacts diff --git a/tests/tc_simple.rb b/tests/tc_simple.rb index 1c3edd544c..5b977dea42 100644 --- a/tests/tc_simple.rb +++ b/tests/tc_simple.rb @@ -560,6 +560,31 @@ def test_ignore_bad_files end + + def test_env_facts + value = "a fact" + + %w{FACTER_ENVNESS facter_envness facterenvness facter_envness}.each do |var| + ENV[var] = value + + assert_nothing_raised { + Facter.loadfacts() + } + + assert(Facter["envness"], "Did not get env fact") + + assert_equal(value, Facter["envness"].value, + "Did not get value correctly") + + resp = nil + assert_nothing_raised { + resp = Facter.envness + } + + assert_equal(value, resp) + Facter.clear + end + end end # $Id$ From 682b97a22f10636f17128d5fe22e1d58f2006b29 Mon Sep 17 00:00:00 2001 From: luke Date: Wed, 28 Jun 2006 17:38:06 +0000 Subject: [PATCH 0083/3753] updating changelog for 1.3.3 git-svn-id: http://reductivelabs.com/svn/facter/trunk@152 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- CHANGELOG | 17 +++++++++++++++++ tests/tc_simple.rb | 1 + 2 files changed, 18 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 6636a20940..5226eef242 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,20 @@ +1.3.3: + Added thread exclusivity to memory and processor facts. + + Added the ability to retrieve facts by pulling them out of the shell environment. + +1.3.2: + Temporarily disabled memory and processor facts since they might cause hangs. + +1.3.1: + Fixed autoloading so that it catches any errors in loaded libraries. + +1.3: + Significant internal refactoring, such as replacing 'tag' with 'confine', and + reusing 'tag' for semantic purposes. + + Made autoloading of facts better. + 1.2.1: Fixed a "bug" that occurs if there's a file named "facter" in your ruby search path (as opposed to directory). diff --git a/tests/tc_simple.rb b/tests/tc_simple.rb index 5b977dea42..dfe917a198 100644 --- a/tests/tc_simple.rb +++ b/tests/tc_simple.rb @@ -582,6 +582,7 @@ def test_env_facts } assert_equal(value, resp) + ENV.delete(var) Facter.clear end end From 474d65d8adf78a45275c860f5b3930a4fc03b0ee Mon Sep 17 00:00:00 2001 From: luke Date: Wed, 28 Jun 2006 17:38:18 +0000 Subject: [PATCH 0084/3753] Updated to version 1.3.3 git-svn-id: http://reductivelabs.com/svn/facter/trunk@153 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter.rb b/lib/facter.rb index 454ee8cc7d..e147e10afd 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -22,7 +22,7 @@ class Facter include Comparable include Enumerable - FACTERVERSION = '1.3.2' + FACTERVERSION = '1.3.3' # = Facter # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. From 4c0459247aeeed5fa61336d9762f70db6e973f86 Mon Sep 17 00:00:00 2001 From: luke Date: Wed, 28 Jun 2006 17:38:19 +0000 Subject: [PATCH 0085/3753] Updated to version 1.3.3 git-svn-id: http://reductivelabs.com/svn/facter/trunk@154 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- conf/redhat/facter.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index 86f5168411..e0a869a209 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -2,7 +2,7 @@ Summary: Ruby module for collecting simple facts about a host operating system Name: facter -Version: 1.3.2 +Version: 1.3.3 Release: 1%{?dist} License: GPL Group: System Environment/Base From 6f01dec243ff30d24c5860b1ed49f9b5dc90dc14 Mon Sep 17 00:00:00 2001 From: luke Date: Sun, 2 Jul 2006 19:19:26 +0000 Subject: [PATCH 0086/3753] adding docs git-svn-id: http://reductivelabs.com/svn/facter/trunk@156 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- Rakefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Rakefile b/Rakefile index 78085e2cc1..65e2cc194d 100644 --- a/Rakefile +++ b/Rakefile @@ -26,6 +26,7 @@ project = Rake::RedLabProject.new("facter") do |p| p.epmhosts = %w{culain} p.rpmhost = "fedora1" + p.sunpkghost = "sol10b" end project.mkgemtask do |gem| From 044f19c6618547a334a8b3f51b6617cbb2dbe9bd Mon Sep 17 00:00:00 2001 From: luke Date: Sun, 2 Jul 2006 19:19:54 +0000 Subject: [PATCH 0087/3753] adding docs git-svn-id: http://reductivelabs.com/svn/facter/trunk@157 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- documentation/index.page | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 documentation/index.page diff --git a/documentation/index.page b/documentation/index.page new file mode 100644 index 0000000000..3844684b11 --- /dev/null +++ b/documentation/index.page @@ -0,0 +1,7 @@ +A cross-platform Ruby library for retrieving facts from operating systems. Supports multiple resolution mechanisms, any of which can be restricted to working only on certain operating systems or environments. Facter is especially useful for retrieving things like operating system names, IP addresses, MAC addresses, and SSH keys. + +It is easy to extend Facter to include your own custom facts or to include additional mechanisms for retrieving facts. + +* [Downloads](/downloads/facter) +* [Bug Tracking](/cgi-bin/facter.cgi) +* [API Documentation](/downloads/facter/apidocs) From 2987d504d13afbbaa237ae39d8730334fa8a9455 Mon Sep 17 00:00:00 2001 From: luke Date: Sun, 2 Jul 2006 19:27:57 +0000 Subject: [PATCH 0088/3753] updates git-svn-id: http://reductivelabs.com/svn/facter/trunk@158 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- documentation/index.page | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/documentation/index.page b/documentation/index.page index 3844684b11..ac6a829880 100644 --- a/documentation/index.page +++ b/documentation/index.page @@ -1,3 +1,8 @@ +--- +inMenu: true +directoryName: Facter +--- + A cross-platform Ruby library for retrieving facts from operating systems. Supports multiple resolution mechanisms, any of which can be restricted to working only on certain operating systems or environments. Facter is especially useful for retrieving things like operating system names, IP addresses, MAC addresses, and SSH keys. It is easy to extend Facter to include your own custom facts or to include additional mechanisms for retrieving facts. @@ -5,3 +10,5 @@ It is easy to extend Facter to include your own custom facts or to include addit * [Downloads](/downloads/facter) * [Bug Tracking](/cgi-bin/facter.cgi) * [API Documentation](/downloads/facter/apidocs) + +*$Id$* From e2337bd732bee26680b888c1b57e237f3c9b7dbc Mon Sep 17 00:00:00 2001 From: luke Date: Sun, 2 Jul 2006 20:14:26 +0000 Subject: [PATCH 0089/3753] doc updates git-svn-id: http://reductivelabs.com/svn/facter/trunk@159 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- documentation/custom.page | 21 +++++++++++++++++++++ documentation/index.page | 11 ++++++++--- 2 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 documentation/custom.page diff --git a/documentation/custom.page b/documentation/custom.page new file mode 100644 index 0000000000..49afd56e64 --- /dev/null +++ b/documentation/custom.page @@ -0,0 +1,21 @@ +--- +inMenu: true +--- + +Facter does everything it can to make adding custom facts easy. It will +autoload any files it finds in a ``facter/`` directory in its search +path, so you don't need to modify the package files. Also, Facter will +search through your environment for any variables whose names start with +'FACTER_' (case insensitive) and automatically add those facts. + +As a simple example, here is how I publish my home directory to Puppet: + + Facter.add("home") do + setcode do + ENV['HOME'] + end + end + +I have ~/lib/ruby in my $RUBYLIB environment variable, so I just created +~/lib/ruby/facter and dropped the above code into a ``home.rb`` file +within that directory. diff --git a/documentation/index.page b/documentation/index.page index ac6a829880..443c465bb2 100644 --- a/documentation/index.page +++ b/documentation/index.page @@ -1,11 +1,16 @@ --- -inMenu: true +inMenu: false directoryName: Facter --- -A cross-platform Ruby library for retrieving facts from operating systems. Supports multiple resolution mechanisms, any of which can be restricted to working only on certain operating systems or environments. Facter is especially useful for retrieving things like operating system names, IP addresses, MAC addresses, and SSH keys. +A cross-platform Ruby library for retrieving facts from operating systems. +Supports multiple resolution mechanisms, any of which can be restricted to +working only on certain operating systems or environments. Facter is especially +useful for retrieving things like operating system names, IP addresses, MAC +addresses, and SSH keys. -It is easy to extend Facter to include your own custom facts or to include additional mechanisms for retrieving facts. +It is easy to extend Facter to include your own [custom facts](custom.html) or +to include additional mechanisms for retrieving facts. * [Downloads](/downloads/facter) * [Bug Tracking](/cgi-bin/facter.cgi) From 722e6f29eb1bf13d533a23b64bf42d6214235e61 Mon Sep 17 00:00:00 2001 From: luke Date: Sun, 2 Jul 2006 20:17:44 +0000 Subject: [PATCH 0090/3753] doc updates git-svn-id: http://reductivelabs.com/svn/facter/trunk@160 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- documentation/custom.page | 1 + 1 file changed, 1 insertion(+) diff --git a/documentation/custom.page b/documentation/custom.page index 49afd56e64..0708a7053c 100644 --- a/documentation/custom.page +++ b/documentation/custom.page @@ -1,5 +1,6 @@ --- inMenu: true +directoryName: Custom Facts --- Facter does everything it can to make adding custom facts easy. It will From b9beaa8829cab196a5cf0ac3d2d1d0d964d9d12a Mon Sep 17 00:00:00 2001 From: luke Date: Mon, 31 Jul 2006 20:59:22 +0000 Subject: [PATCH 0091/3753] updates git-svn-id: http://reductivelabs.com/svn/facter/trunk@161 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- documentation/index.page | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/index.page b/documentation/index.page index 443c465bb2..c64938aa86 100644 --- a/documentation/index.page +++ b/documentation/index.page @@ -12,8 +12,8 @@ addresses, and SSH keys. It is easy to extend Facter to include your own [custom facts](custom.html) or to include additional mechanisms for retrieving facts. -* [Downloads](/downloads/facter) +* [Downloads](/downloads/facter/) * [Bug Tracking](/cgi-bin/facter.cgi) -* [API Documentation](/downloads/facter/apidocs) +* [API Documentation](/downloads/facter/apidocs/) *$Id$* From 356925372168165994d71b1254a23ab2de6b5bcf Mon Sep 17 00:00:00 2001 From: luke Date: Thu, 14 Sep 2006 21:12:23 +0000 Subject: [PATCH 0092/3753] Applying memfree patch from #17. git-svn-id: http://reductivelabs.com/svn/facter/trunk@162 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter/memory.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb index 24678e1ec2..6e9b600c8b 100644 --- a/lib/facter/memory.rb +++ b/lib/facter/memory.rb @@ -22,11 +22,17 @@ module Facter::Memory def self.meminfo_number(tag) memsize = "" Thread::exclusive do + size, scale = [0, ""] File.readlines("/proc/meminfo").each do |l| - if l =~ /^#{tag}:\s+(\d+)\s+(\S+)/ - memsize = scale_number($1.to_f, $2) + size, scale = [$1.to_f, $2] if l =~ /^#{tag}:\s+(\d+)\s+(\S+)/ + # MemoryFree == memfree + cached + buffers + # (assume scales are all the same as memfree) + if tag == "MemFree" && + l =~ /^(?:Buffers|Cached):\s+(\d+)\s+(?:\S+)/ + size += $1.to_f end end + memsize = scale_number(size, scale) end memsize From 610fb5d3a576119f644d82285532915261fc7bf0 Mon Sep 17 00:00:00 2001 From: luke Date: Thu, 14 Sep 2006 21:17:33 +0000 Subject: [PATCH 0093/3753] Applying patch in #23. git-svn-id: http://reductivelabs.com/svn/facter/trunk@163 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/lib/facter.rb b/lib/facter.rb index e147e10afd..d81c61c949 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -840,7 +840,7 @@ def Facter.loadfacts end Facter.add("MacAddress") do - confine :operatingsystem => :solaris + confine :operatingsystem => %w{FreeBSD NetBSD OpenBSD solaris} setcode do ether = nil output = %x{/sbin/ifconfig -a} @@ -867,9 +867,43 @@ def Facter.loadfacts ether end + + Facter.add("MacAddress") do + confine :kernel => :linux + setcode do + ether = nil + output = %x{/sbin/ifconfig -a} + + output =~ /HWaddr (\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/ + ether = $1 + + ether + end + end + + + Facter.add("IPAddress") do + confine :kernel => :linux + setcode do + ip = nil + output = %x{/sbin/ifconfig} + + output.split(/^\S/).each { |str| + if str =~ /inet addr:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + tmp = $1 + unless tmp =~ /127\./ + ip = tmp + break + end + end + } + + ip + end + end end Facter.add("IPAddress") do - confine :kernel => :darwin + confine :kernel => %w{FreeBSD NetBSD OpenBSD solaris darwin} setcode do ip = nil output = %x{/sbin/ifconfig} From 07a42e66a26c30f06b01dec5e568668ea715095b Mon Sep 17 00:00:00 2001 From: luke Date: Thu, 14 Sep 2006 21:20:12 +0000 Subject: [PATCH 0094/3753] Applying patch from #22 git-svn-id: http://reductivelabs.com/svn/facter/trunk@164 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- bin/facter | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/facter b/bin/facter index 4e905befff..8cbdeef43f 100755 --- a/bin/facter +++ b/bin/facter @@ -47,7 +47,7 @@ $haveusage = true begin require 'rdoc/usage' -rescue LoadError +rescue Exception $haveusage = false end From fc9331a9b5feef11d68dff3813977b2533995f2f Mon Sep 17 00:00:00 2001 From: luke Date: Thu, 14 Sep 2006 21:37:03 +0000 Subject: [PATCH 0095/3753] Fixing #20. I just made sure that the Domain fact cchecks the hostname first, so that if the hostname is an fqdn it will set the domain from that. git-svn-id: http://reductivelabs.com/svn/facter/trunk@165 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/facter.rb b/lib/facter.rb index d81c61c949..f7997832e4 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -691,6 +691,10 @@ def Facter.loadfacts Facter.add("Domain") do setcode do + # First force the hostname to be checked + Facter.hostname + + # Now check to see if it set the domain if defined? $domain and ! $domain.nil? $domain else @@ -775,7 +779,7 @@ def Facter.loadfacts require 'resolv' begin - if hostname = Facter["hostname"].value + if hostname = Facter.hostname ip = Resolv.getaddress(hostname) unless ip == "127.0.0.1" ip @@ -792,9 +796,10 @@ def Facter.loadfacts end Facter.add("IPAddress") do setcode do - if hostname = Facter["hostname"].value + if hostname = Facter.hostname # we need Hostname to exist for this to work - if list = Resolution.exec("host #{hostname}").chomp.split(/\s/) + host = nil + if host = Resolution.exec("host #{hostname}") and host.chomp.split(/\s/) if defined? list[-1] and list[-1] =~ /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ From c96cf6a71874286fa7acff5a916f8a877f32a0f4 Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 19 Sep 2006 06:06:56 +0000 Subject: [PATCH 0096/3753] Adding fqdn fact git-svn-id: http://reductivelabs.com/svn/facter/trunk@166 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/facter.rb b/lib/facter.rb index f7997832e4..f1726cc290 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -773,6 +773,18 @@ def Facter.loadfacts end end + Facter.add(:fqdn) do + setcode do + host = Facter.value(:hostname) + domain = Facter.value(:domain) + if host and domain + [host, domain].join(".") + else + nil + end + end + end + Facter.add("IPAddress") do setldapname "iphostnumber" setcode do From e2185ceb243d1b426e30d555d737cd666985c41f Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 19 Sep 2006 06:08:23 +0000 Subject: [PATCH 0097/3753] Sorting the facts when they are all output git-svn-id: http://reductivelabs.com/svn/facter/trunk@167 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- bin/facter | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/facter b/bin/facter index 8cbdeef43f..5b4e79f642 100755 --- a/bin/facter +++ b/bin/facter @@ -113,7 +113,7 @@ else } end -facts.each { |name,value| +facts.sort { |a, b| a[0].to_s <=> b[0].to_s }.each { |name,value| if facts.length == 1 puts value else From 7407e0c26dc5b4f12398393bf5f0173fb8685ff4 Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 19 Sep 2006 06:47:45 +0000 Subject: [PATCH 0098/3753] Fixing facter so it does not fail when an unknown fact is asked for git-svn-id: http://reductivelabs.com/svn/facter/trunk@168 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- bin/facter | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bin/facter b/bin/facter index 5b4e79f642..d5b380f94e 100755 --- a/bin/facter +++ b/bin/facter @@ -105,7 +105,7 @@ if names.empty? else names.each { |name| begin - facts[name] = Facter[name].value + facts[name] = Facter.value(name) rescue => error STDERR.puts "Could not retrieve %s: #{error}" % name exit 10 @@ -115,7 +115,9 @@ end facts.sort { |a, b| a[0].to_s <=> b[0].to_s }.each { |name,value| if facts.length == 1 - puts value + unless value.nil? + puts value + end else puts "%s => %s" % [name,value] end From 4abbce9119f91179aaa7aeb2f92466cc201a9079 Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 19 Sep 2006 06:50:21 +0000 Subject: [PATCH 0099/3753] applying patch from #18. git-svn-id: http://reductivelabs.com/svn/facter/trunk@169 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 42 ++++++++++++++++-------------------------- 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/lib/facter.rb b/lib/facter.rb index f1726cc290..8a9fcc1295 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -651,11 +651,13 @@ def Facter.loadfacts end Facter.add("Architecture") do - confine :operatingsystem => :debian + confine :kernel => :linux setcode do model = Facter.hardwaremodel case model - when 'x86_64': "amd64" + # most linuxen use "x86_64" + when 'x86_64': + Facter.operatingsystem == "Debian" ? "amd64" : model; when /(i[3456]86|pentium)/: "i386" else model @@ -848,24 +850,25 @@ def Facter.loadfacts Facter.add("UniqueId") do setcode 'hostid', '/bin/sh' - confine :operatingsystem => :solaris + #confine :kernel => %w{Solaris Linux} + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE Debian Gentoo} end Facter.add("HardwareISA") do setcode 'uname -p', '/bin/sh' - confine :operatingsystem => :solaris + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE Debian Gentoo} end Facter.add("MacAddress") do - confine :operatingsystem => %w{FreeBSD NetBSD OpenBSD solaris} + #confine :kernel => %w{Solaris Linux} + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE Debian Gentoo} setcode do - ether = nil + ether = [] output = %x{/sbin/ifconfig -a} - - output =~ /ether (\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/ - ether = $1 - - ether + output.each {|s| + ether.push($1) if s =~ /(?:ether|HWaddr) (\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/ + } + ether.join(" ") end end @@ -885,20 +888,6 @@ def Facter.loadfacts ether end - Facter.add("MacAddress") do - confine :kernel => :linux - setcode do - ether = nil - output = %x{/sbin/ifconfig -a} - - output =~ /HWaddr (\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/ - ether = $1 - - ether - end - end - - Facter.add("IPAddress") do confine :kernel => :linux setcode do @@ -973,7 +962,8 @@ def Facter.loadfacts end Facter.add("id") do - confine :operatingsystem => :linux + #confine :kernel => %w{Solaris Linux} + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE Debian Gentoo} setcode "whoami" end From fe0f2f204fdd5d4c48af93d691cce53061cd1ded Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 19 Sep 2006 06:55:52 +0000 Subject: [PATCH 0100/3753] Adding yaml support, as requested in #24 git-svn-id: http://reductivelabs.com/svn/facter/trunk@170 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- bin/facter | 13 +++++++++++++ tests/tc_facterbin.rb | 18 ++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/bin/facter b/bin/facter index d5b380f94e..10389bac39 100755 --- a/bin/facter +++ b/bin/facter @@ -59,14 +59,21 @@ result = GetoptLong.new( [ "--version", "-v", GetoptLong::NO_ARGUMENT ], [ "--help", "-h", GetoptLong::NO_ARGUMENT ], [ "--debug", "-d", GetoptLong::NO_ARGUMENT ], + [ "--yaml", "-y", GetoptLong::NO_ARGUMENT ], [ "--config", "-c", GetoptLong::REQUIRED_ARGUMENT ] ) +options = { + :yaml => false +} + result.each { |opt,arg| case opt when "--version" puts "%s" % Facter.version exit + when "--yaml" + options[:yaml] = true when "--debug" Facter.debugging(1) when "--help" @@ -113,6 +120,12 @@ else } end +if options[:yaml] + require 'yaml' + puts YAML.dump(facts) + exit(0) +end + facts.sort { |a, b| a[0].to_s <=> b[0].to_s }.each { |name,value| if facts.length == 1 unless value.nil? diff --git a/tests/tc_facterbin.rb b/tests/tc_facterbin.rb index b2b8100edd..9d9ca15fb7 100755 --- a/tests/tc_facterbin.rb +++ b/tests/tc_facterbin.rb @@ -60,6 +60,24 @@ def test_simpleoutput assert(output !~ / => /, "Output includes the farrow thing") end + + def test_yaml + out = nil + assert_nothing_raised { + out = %x{facter -y} + } + + require 'yaml' + result = nil + assert_nothing_raised { + result = YAML.load(out) + } + + assert_instance_of(Hash, result) + result.each do |name, value| + assert_equal(value, Facter.value(name)) + end + end end # $Id$ From d75744b5bc40d8f26bff72f8b8738fc41b7f8a24 Mon Sep 17 00:00:00 2001 From: luke Date: Wed, 20 Sep 2006 15:46:22 +0000 Subject: [PATCH 0101/3753] Adding patch from #21, adding lsb_release facts git-svn-id: http://reductivelabs.com/svn/facter/trunk@171 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lib/facter.rb b/lib/facter.rb index 8a9fcc1295..fd1dd426da 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -608,6 +608,21 @@ def Facter.loadfacts setcode 'uname -r' end + {"LSBRelease" => "^LSB Version:\t(.*)$", "LSBDistId" => "^Distributor ID:\t(.*)$", + "LSBDistRelease" => "^Release:\t(.*)$", "LSBDistDescription" => "^Description:\t(.*)$", + "LSBDistCodeName" => "^Codename:\t(.*)$"}.each { |fact, pattern| + output = %x{lsb_release -a 2>&1} + if $? == 0 + Facter.add(fact) do + setcode do + if $? == 0 and output =~ /#{pattern}/ + $1 + end + end + end # end of add + end + } + Facter.add("OperatingSystem") do # Default to just returning the kernel as the operating system setcode do Facter["Kernel"].value end From ca498a28df534f29ae944b644cf913b8efe5581e Mon Sep 17 00:00:00 2001 From: luke Date: Fri, 22 Sep 2006 05:24:06 +0000 Subject: [PATCH 0102/3753] updating changelog for 1.3.4 git-svn-id: http://reductivelabs.com/svn/facter/trunk@172 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- CHANGELOG | 9 ++++++++- tests/tc_facterbin.rb | 2 ++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 5226eef242..6a565f1d65 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,7 +1,14 @@ +1.3.4: + Added many new facts, including LSB facts. + + Fixed a few small bugs, notably the error you could get when asking + for a non-existent fact. + 1.3.3: Added thread exclusivity to memory and processor facts. - Added the ability to retrieve facts by pulling them out of the shell environment. + Added the ability to retrieve facts by pulling them out of the shell + environment. 1.3.2: Temporarily disabled memory and processor facts since they might cause hangs. diff --git a/tests/tc_facterbin.rb b/tests/tc_facterbin.rb index 9d9ca15fb7..85f8df15f5 100755 --- a/tests/tc_facterbin.rb +++ b/tests/tc_facterbin.rb @@ -75,6 +75,8 @@ def test_yaml assert_instance_of(Hash, result) result.each do |name, value| + # These change too frequently. + next if name.to_s.downcase =~ /mem/ assert_equal(value, Facter.value(name)) end end From 677c98698d2cb76f696838e080a388a8ccff6b0f Mon Sep 17 00:00:00 2001 From: luke Date: Fri, 22 Sep 2006 05:24:22 +0000 Subject: [PATCH 0103/3753] Updated to version 1.3.4 git-svn-id: http://reductivelabs.com/svn/facter/trunk@173 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- conf/solaris/pkginfo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/solaris/pkginfo b/conf/solaris/pkginfo index 2a0cf8c5d0..95004c540c 100644 --- a/conf/solaris/pkginfo +++ b/conf/solaris/pkginfo @@ -1,6 +1,6 @@ PKG=CSWfacter NAME=facter - System Fact Gatherer -VERSION=1.3.1 +VERSION=1.3.4 CATEGORY=application VENDOR=http://reductivelabs.com/projects/facter HOTLINE=http://reductivelabs.com/cgi-bin/facter.cgi From 95352bcb677f226352a1ca0d6311e894be7a6391 Mon Sep 17 00:00:00 2001 From: luke Date: Fri, 22 Sep 2006 05:24:23 +0000 Subject: [PATCH 0104/3753] Updated to version 1.3.4 git-svn-id: http://reductivelabs.com/svn/facter/trunk@174 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter.rb b/lib/facter.rb index fd1dd426da..cc340d410e 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -22,7 +22,7 @@ class Facter include Comparable include Enumerable - FACTERVERSION = '1.3.3' + FACTERVERSION = '1.3.4' # = Facter # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. From 82fd8900ae442676e50a4d5dc8cace84ca05031d Mon Sep 17 00:00:00 2001 From: luke Date: Fri, 22 Sep 2006 05:24:25 +0000 Subject: [PATCH 0105/3753] Updated to version 1.3.4 git-svn-id: http://reductivelabs.com/svn/facter/trunk@175 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- conf/redhat/facter.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index e0a869a209..ae870dbf95 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -2,7 +2,7 @@ Summary: Ruby module for collecting simple facts about a host operating system Name: facter -Version: 1.3.3 +Version: 1.3.4 Release: 1%{?dist} License: GPL Group: System Environment/Base From 4339b46558cf861ad3b5a52fa41cb2adf4e53e85 Mon Sep 17 00:00:00 2001 From: luke Date: Fri, 29 Sep 2006 16:12:00 +0000 Subject: [PATCH 0106/3753] Fixing #26 -- using Resolution.exec instead of executing directly, and also calling lsb_release for every fact, instead of just once at startup git-svn-id: http://reductivelabs.com/svn/facter/trunk@177 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/lib/facter.rb b/lib/facter.rb index cc340d410e..012dfb39b4 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -608,20 +608,23 @@ def Facter.loadfacts setcode 'uname -r' end - {"LSBRelease" => "^LSB Version:\t(.*)$", "LSBDistId" => "^Distributor ID:\t(.*)$", - "LSBDistRelease" => "^Release:\t(.*)$", "LSBDistDescription" => "^Description:\t(.*)$", - "LSBDistCodeName" => "^Codename:\t(.*)$"}.each { |fact, pattern| - output = %x{lsb_release -a 2>&1} - if $? == 0 - Facter.add(fact) do - setcode do - if $? == 0 and output =~ /#{pattern}/ - $1 - end + { "LSBRelease" => "^LSB Version:\t(.*)$", + "LSBDistId" => "^Distributor ID:\t(.*)$", + "LSBDistRelease" => "^Release:\t(.*)$", + "LSBDistDescription" => "^Description:\t(.*)$", + "LSBDistCodeName" => "^Codename:\t(.*)$" + }.each do |fact, pattern| + Facter.add(fact) do + setcode do + output = Resolution.exec('lsb_release -a 2>/dev/null') + if output =~ /#{pattern}/ + $1 + else + nil end - end # end of add + end end - } + end Facter.add("OperatingSystem") do # Default to just returning the kernel as the operating system From 9a73c72aebddca511adf47a8f8865b1ca6be3258 Mon Sep 17 00:00:00 2001 From: luke Date: Fri, 29 Sep 2006 16:13:11 +0000 Subject: [PATCH 0107/3753] Updated to version 1.3.5 git-svn-id: http://reductivelabs.com/svn/facter/trunk@178 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- conf/solaris/pkginfo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/solaris/pkginfo b/conf/solaris/pkginfo index 95004c540c..05eef1fa28 100644 --- a/conf/solaris/pkginfo +++ b/conf/solaris/pkginfo @@ -1,6 +1,6 @@ PKG=CSWfacter NAME=facter - System Fact Gatherer -VERSION=1.3.4 +VERSION=1.3.5 CATEGORY=application VENDOR=http://reductivelabs.com/projects/facter HOTLINE=http://reductivelabs.com/cgi-bin/facter.cgi From bae0b496e73b991df85dac44e32e526f10b375bb Mon Sep 17 00:00:00 2001 From: luke Date: Fri, 29 Sep 2006 16:13:12 +0000 Subject: [PATCH 0108/3753] Updated to version 1.3.5 git-svn-id: http://reductivelabs.com/svn/facter/trunk@179 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter.rb b/lib/facter.rb index 012dfb39b4..7956585784 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -22,7 +22,7 @@ class Facter include Comparable include Enumerable - FACTERVERSION = '1.3.4' + FACTERVERSION = '1.3.5' # = Facter # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. From c7a9e198fcf07d6d89bf274602574a88aa7f7dca Mon Sep 17 00:00:00 2001 From: luke Date: Fri, 29 Sep 2006 16:13:13 +0000 Subject: [PATCH 0109/3753] Updated to version 1.3.5 git-svn-id: http://reductivelabs.com/svn/facter/trunk@180 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- conf/redhat/facter.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index ae870dbf95..e33e5fef84 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -2,7 +2,7 @@ Summary: Ruby module for collecting simple facts about a host operating system Name: facter -Version: 1.3.4 +Version: 1.3.5 Release: 1%{?dist} License: GPL Group: System Environment/Base From 4dc1c37a0738157d796164b26fa68e16b6cf0b86 Mon Sep 17 00:00:00 2001 From: lutter Date: Wed, 8 Nov 2006 19:52:34 +0000 Subject: [PATCH 0110/3753] Do not try and check the command if which is not available; fixes trac #30 git-svn-id: http://reductivelabs.com/svn/facter/trunk@182 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/lib/facter.rb b/lib/facter.rb index 7956585784..11865cce4c 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -385,26 +385,37 @@ def value class Resolution attr_accessor :interpreter, :code, :name, :fact + def Resolution.have_which + if @have_which.nil? + %x{which which 2>/dev/null} + @have_which = ($? == 0) + end + @have_which + end + # Execute a chunk of code. def Resolution.exec(code, interpreter = "/bin/sh") if interpreter == "/bin/sh" binary = code.split(/\s+/).shift - path = nil - if binary !~ /^\// - path = %x{which #{binary} 2>/dev/null}.chomp - if path == "" - # we don't have the binary necessary + if have_which + path = nil + if binary !~ /^\// + path = %x{which #{binary} 2>/dev/null}.chomp + if path == "" + # we don't have the binary necessary + return nil + end + else + path = binary + end + + unless FileTest.exists?(path) + # our binary does not exist return nil end - else - path = binary end - unless FileTest.exists?(path) - # our binary does not exist - return nil - end out = nil begin out = %x{#{code}}.chomp From ea65bdd77f664fe9129e46873faa1c549b06e03d Mon Sep 17 00:00:00 2001 From: lutter Date: Wed, 8 Nov 2006 20:07:57 +0000 Subject: [PATCH 0111/3753] Reconciling with Fedora specfile git-svn-id: http://reductivelabs.com/svn/facter/trunk@183 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- conf/redhat/facter.spec | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index e33e5fef84..40449c14cb 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -47,16 +47,25 @@ rm -rf %{buildroot} %defattr(-,root,root,-) %{_bindir}/facter %{ruby_sitelibdir}/facter.rb -%{ruby_sitelibdir}/facter/memory.rb -%{ruby_sitelibdir}/facter/processor.rb +%{ruby_sitelibdir}/facter %doc CHANGELOG COPYING INSTALL LICENSE README %changelog +* Tue Oct 10 2006 David Lutterkort - 1.3.5-1 +- New version + +* Tue Sep 26 2006 David Lutterkort - 1.3.4-1 +- New version + +* Wed Sep 13 2006 David Lutterkort - 1.3.3-2 +- Rebuilt for FC6 + +* Wed Jun 28 2006 David Lutterkort - 1.3.3-1 +- Rebuilt + * Fri Jun 19 2006 Luke Kanies - 1.3.0-1 - Fixed spec file to work again with the extra memory and processor files. - -* Mon Jun 12 2006 David Lutterkort - 1.3.0-1 - Require ruby(abi). Build as noarch * Fri Jun 9 2006 Luke Kanies - 1.3.0-1 From 0674780cb8e66b63d2a804b61cc6c765e9feded3 Mon Sep 17 00:00:00 2001 From: lutter Date: Tue, 21 Nov 2006 02:07:46 +0000 Subject: [PATCH 0112/3753] Make specfile work for FC < 5 and RHEL < 5 git-svn-id: http://reductivelabs.com/svn/facter/trunk@184 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- conf/redhat/facter.spec | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index 40449c14cb..54f4a86a68 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -1,18 +1,25 @@ %{!?ruby_sitelibdir: %define ruby_sitelibdir %(ruby -rrbconfig -e 'puts Config::CONFIG["sitelibdir"]')} +%define has_ruby_abi 0%{?fedora:%fedora} >= 5 || 0%{?rhel:%rhel} >= 5 +%define has_ruby_noarch %has_ruby_abi + Summary: Ruby module for collecting simple facts about a host operating system Name: facter Version: 1.3.5 -Release: 1%{?dist} +Release: 2%{?dist} License: GPL Group: System Environment/Base URL: http://reductivelabs.com/projects/facter Source0: http://reductivelabs.com/downloads/facter/%{name}-%{version}.tgz BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) +%if %has_ruby_noarch BuildArchitectures: noarch +%endif Requires: ruby >= 1.8.1 +%if %has_ruby_abi Requires: ruby(abi) = 1.8 +%endif BuildRequires: ruby >= 1.8.1 %description @@ -52,6 +59,10 @@ rm -rf %{buildroot} %changelog +* Mon Nov 20 2006 David Lutterkort - 1.3.5-2 +- Make require ruby(abi) and buildarch: noarch conditional for fedora 5 or + later to allow building on older fedora releases + * Tue Oct 10 2006 David Lutterkort - 1.3.5-1 - New version From 7b665cc12394af998c4ad9d41d4d3f22b88748de Mon Sep 17 00:00:00 2001 From: luke Date: Thu, 4 Jan 2007 04:30:42 +0000 Subject: [PATCH 0113/3753] Fixing ssh key facts so they only include the key, not the type. git-svn-id: http://reductivelabs.com/svn/facter/trunk@185 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 2 +- tests/tc_simple.rb | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/facter.rb b/lib/facter.rb index 11865cce4c..65123904e3 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -866,7 +866,7 @@ def Facter.loadfacts filepath = File.join(dir,file) if FileTest.file?(filepath) begin - value = File.open(filepath).read.chomp + value = File.open(filepath).read.chomp.split(/\s+/)[1] rescue value = nil end diff --git a/tests/tc_simple.rb b/tests/tc_simple.rb index dfe917a198..8c7d9959f7 100644 --- a/tests/tc_simple.rb +++ b/tests/tc_simple.rb @@ -586,6 +586,15 @@ def test_env_facts Facter.clear end end + + # Make sure that ssh keys only include the key, not the comment or the type. + def test_ssh_keys + %w{rsa dsa}.each do |type| + key = Facter.value("ssh#{type}key") + assert(key, "did not retrieve %s key" % type) + assert(key !~ /\s/, "%s key contains whitespace" % type) + end + end end # $Id$ From b013e21358e07766d96f31d24855fb790aa601d0 Mon Sep 17 00:00:00 2001 From: luke Date: Thu, 4 Jan 2007 04:57:53 +0000 Subject: [PATCH 0114/3753] Applying patch from #29. git-svn-id: http://reductivelabs.com/svn/facter/trunk@186 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/facter.rb b/lib/facter.rb index 65123904e3..a218c261f9 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -842,8 +842,8 @@ def Facter.loadfacts if hostname = Facter.hostname # we need Hostname to exist for this to work host = nil - if host = Resolution.exec("host #{hostname}") and host.chomp.split(/\s/) - + if host = Resolution.exec("host #{hostname}") + host = host.chomp.split(/\s/) if defined? list[-1] and list[-1] =~ /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ list[-1] From 067dc2cf8eb89e72600a24f5bc5c3767c4e97954 Mon Sep 17 00:00:00 2001 From: luke Date: Fri, 19 Jan 2007 22:32:41 +0000 Subject: [PATCH 0115/3753] updating changelog for 1.3.6 git-svn-id: http://reductivelabs.com/svn/facter/trunk@187 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- CHANGELOG | 7 +++++++ tests/tc_simple.rb | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) mode change 100644 => 100755 tests/tc_simple.rb diff --git a/CHANGELOG b/CHANGELOG index 6a565f1d65..2d127ffe0d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,10 @@ +1.3.6: + A bugfix release, including fixes for #29, and #30. Also fixed + the SSH keys so they only have the key, not the type or description. + +1.3.5: + A bugfix release. + 1.3.4: Added many new facts, including LSB facts. diff --git a/tests/tc_simple.rb b/tests/tc_simple.rb old mode 100644 new mode 100755 index 8c7d9959f7..c355d7987e --- a/tests/tc_simple.rb +++ b/tests/tc_simple.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby +#!/usr/bin/env ruby $facterbase = File.dirname(File.dirname(__FILE__)) if $facterbase == "." From cc672ba6b5fba001f16516afecb70209481e5297 Mon Sep 17 00:00:00 2001 From: luke Date: Fri, 19 Jan 2007 22:36:53 +0000 Subject: [PATCH 0116/3753] disabling solaris package generation for facter git-svn-id: http://reductivelabs.com/svn/facter/trunk@188 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- Rakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index 65e2cc194d..9aff1679c0 100644 --- a/Rakefile +++ b/Rakefile @@ -26,7 +26,7 @@ project = Rake::RedLabProject.new("facter") do |p| p.epmhosts = %w{culain} p.rpmhost = "fedora1" - p.sunpkghost = "sol10b" + #p.sunpkghost = "sol10b" end project.mkgemtask do |gem| From b333df2415b862d4e1c267962edd6e6fa3d441b0 Mon Sep 17 00:00:00 2001 From: luke Date: Fri, 19 Jan 2007 22:37:23 +0000 Subject: [PATCH 0117/3753] Updated to version 1.3.6 git-svn-id: http://reductivelabs.com/svn/facter/trunk@189 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- conf/redhat/facter.spec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index 54f4a86a68..85573b6deb 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -5,8 +5,8 @@ Summary: Ruby module for collecting simple facts about a host operating system Name: facter -Version: 1.3.5 -Release: 2%{?dist} +Version: 1.3.6 +Release: 1%{?dist} License: GPL Group: System Environment/Base URL: http://reductivelabs.com/projects/facter From c1a02be848517924149972d180c9ab1995e479cd Mon Sep 17 00:00:00 2001 From: luke Date: Fri, 19 Jan 2007 22:37:24 +0000 Subject: [PATCH 0118/3753] Updated to version 1.3.6 git-svn-id: http://reductivelabs.com/svn/facter/trunk@190 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter.rb b/lib/facter.rb index a218c261f9..6d95ce926b 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -22,7 +22,7 @@ class Facter include Comparable include Enumerable - FACTERVERSION = '1.3.5' + FACTERVERSION = '1.3.6' # = Facter # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. From 4cf0016782815f6d462948db885560b1ba0edd1d Mon Sep 17 00:00:00 2001 From: luke Date: Mon, 22 Jan 2007 16:06:26 +0000 Subject: [PATCH 0119/3753] updating docs a bit git-svn-id: http://reductivelabs.com/svn/facter/trunk@192 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- COPYING | 46 ---------------------------------------------- README | 3 ++- 2 files changed, 2 insertions(+), 47 deletions(-) diff --git a/COPYING b/COPYING index 5ab7695ab8..3b473dbfc0 100644 --- a/COPYING +++ b/COPYING @@ -456,49 +456,3 @@ SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Libraries - - If you develop a new library, and you want it to be of the greatest -possible use to the public, we recommend making it free software that -everyone can redistribute and change. You can do so by permitting -redistribution under these terms (or, alternatively, under the terms of the -ordinary General Public License). - - To apply these terms, attach the following notices to the library. It is -safest to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least the -"copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -Also add information on how to contact you by electronic and paper mail. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the library, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the - library `Frob' (a library for tweaking knobs) written by James Random Hacker. - - , 1 April 1990 - Ty Coon, President of Vice - -That's all there is to it! - - diff --git a/README b/README index 54627955df..29d3d835a4 100644 --- a/README +++ b/README @@ -6,4 +6,5 @@ processors, etc. It currently cannot collect very much information, but it is architected to be both OS and OS version specific. -See bin/facter or http://madstop.com/svn/enhost for an example of the interface. +See bin/facter or http://reductivelabs.com/project/enhost for an example of the +interface. From 38cd6137bb5956b47ee5caffe6e071e28ad6d755 Mon Sep 17 00:00:00 2001 From: lutter Date: Tue, 23 Jan 2007 18:11:46 +0000 Subject: [PATCH 0120/3753] Sync with Fedora specfile git-svn-id: http://reductivelabs.com/svn/facter/trunk@193 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- conf/redhat/facter.spec | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index 85573b6deb..eb4172c265 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -17,6 +17,7 @@ BuildArchitectures: noarch %endif Requires: ruby >= 1.8.1 +Requires: which %if %has_ruby_abi Requires: ruby(abi) = 1.8 %endif @@ -59,6 +60,12 @@ rm -rf %{buildroot} %changelog +* Fri Jan 19 2007 David Lutterkort - 1.3.6-1 +- New version + +* Thu Jan 18 2007 David Lutterkort - 1.3.5-3 +- require which; facter is very unhappy without it + * Mon Nov 20 2006 David Lutterkort - 1.3.5-2 - Make require ruby(abi) and buildarch: noarch conditional for fedora 5 or later to allow building on older fedora releases From 824f91cfa60cfe7b6bdc62ae498f033d1bda9634 Mon Sep 17 00:00:00 2001 From: ajax Date: Tue, 30 Jan 2007 18:17:03 +0000 Subject: [PATCH 0121/3753] Fixing bug where an up interface not in active use was being selected as the canonical IP instead of using the IP attached to the interface assigned the default route. git-svn-id: http://reductivelabs.com/svn/facter/trunk@194 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/lib/facter.rb b/lib/facter.rb index 6d95ce926b..fe0b684aeb 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -938,7 +938,7 @@ def Facter.loadfacts end end Facter.add("IPAddress") do - confine :kernel => %w{FreeBSD NetBSD OpenBSD solaris darwin} + confine :kernel => %w{FreeBSD NetBSD OpenBSD solaris} setcode do ip = nil output = %x{/sbin/ifconfig} @@ -956,6 +956,36 @@ def Facter.loadfacts ip end end + Facter.add("IPAddress") do + confine :kernel => %w{darwin} + setcode do + ip = nil + iface = "" + output = %x{/usr/sbin/netstat -rn} + if output =~ /^default\s*\S*\s*\S*\s*\S*\s*\S*\s*(\S*).*/ + iface = $1 + else + warn "Could not find a default route. Using first non-loopback interface" + end + if(iface != "") + output = %x{/sbin/ifconfig #{iface}} + else + output = %x{/sbin/ifconfig} + end + + output.split(/^\S/).each { |str| + if str =~ /inet ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + tmp = $1 + unless tmp =~ /127\./ + ip = tmp + break + end + end + } + + ip + end + end Facter.add("Hostname") do confine :kernel => :darwin, :kernelrelease => "R7" setcode do From 392d8f2f357df9c3647d9b35dbcecc76fe4a80ee Mon Sep 17 00:00:00 2001 From: luke Date: Sat, 24 Feb 2007 15:44:59 +0000 Subject: [PATCH 0122/3753] Applying patch from #35. git-svn-id: http://reductivelabs.com/svn/facter/trunk@195 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/facter.rb b/lib/facter.rb index fe0b684aeb..55e7619949 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -700,7 +700,9 @@ def Facter.loadfacts ["/usr/local/etc/cfkey.pub", "/etc/cfkey.pub", "/var/cfng/keys/localhost.pub", - "/var/cfengine/ppkeys/localhost.pub" + "/var/cfengine/ppkeys/localhost.pub", + "/var/lib/cfengine/ppkeys/localhost.pub", + "/var/lib/cfengine2/ppkeys/localhost.pub" ].each { |file| if FileTest.file?(file) File.open(file) { |openfile| From 31039dc925d87208785765b8067e899b806022e8 Mon Sep 17 00:00:00 2001 From: luke Date: Wed, 21 Mar 2007 15:24:05 +0000 Subject: [PATCH 0123/3753] Applying patch from Adam Jacob that makes FACTERLIB work git-svn-id: http://reductivelabs.com/svn/facter/trunk@196 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 38 ++++++++++++++++++++++++++------------ tests/tc_simple.rb | 41 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 63 insertions(+), 16 deletions(-) diff --git a/lib/facter.rb b/lib/facter.rb index 55e7619949..c11b643c3b 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -1030,24 +1030,38 @@ def Facter.loadfacts locals = [] - # Now see if we can find any other facts + # Now find all our loadable facts + factdirs = [] # All the places to check for facts + + # See if we can find any other facts in the regular Ruby lib + # paths $:.each do |dir| fdir = File.join(dir, "facter") if FileTest.exists?(fdir) and FileTest.directory?(fdir) - Dir.glob("#{fdir}/*.rb").each do |file| - # Load here, rather than require, because otherwise - # the facts won't get reloaded if someone calls - # "loadfacts". Really only important in testing, but, - # well, it's important in testing. - begin - load file - rescue => detail - warn "Could not load %s: %s" % - [file, detail] - end + factdirs.push(fdir) + end + end + # Also check anything in 'FACTERLIB' + if ENV['FACTERLIB'] + ENV['FACTERLIB'].split(":").each do |fdir| + factdirs.push(fdir) + end + end + factdirs.each do |fdir| + Dir.glob("#{fdir}/*.rb").each do |file| + # Load here, rather than require, because otherwise + # the facts won't get reloaded if someone calls + # "loadfacts". Really only important in testing, but, + # well, it's important in testing. + begin + load file + rescue => detail + warn "Could not load %s: %s" % + [file, detail] end end end + # Now try to get facts from the environment ENV.each do |name, value| diff --git a/tests/tc_simple.rb b/tests/tc_simple.rb index c355d7987e..82e63719c6 100755 --- a/tests/tc_simple.rb +++ b/tests/tc_simple.rb @@ -356,20 +356,49 @@ def test_localfacts Dir.mkdir(dir) Dir.mkdir(File.join(dir, "facter")) $: << dir - - # Make sure we don't have a value right now. + + Dir.mkdir(File.join(dir, "facterlib1")) + Dir.mkdir(File.join(dir, "facterlib2")) + ENV['FACTERLIB'] = "#{dir}/facterlib1:#{dir}/facterlib2" + + # Make sure we don't have a value right now, for both the localfact + # and the facterlib fact. assert_raise(NoMethodError) do Facter.localfact end assert_nil(Facter["localfact"]) + + assert_raise(NoMethodError) do + Facter.facterlibfact + end + assert_nil(Facter["faterlibfact"]) + + assert_raise(NoMethodError) do + Facter.facterlibfact2 + end + assert_nil(Facter["faterlibfact2"]) - # Make our file + # Make our files val = "localness" File.open(File.join(dir, "facter", "local.rb"), "w") do |file| file.puts %{ Facter.add("LocalFact") do setcode { "#{val}" } end +} + end + File.open(File.join(dir, "facterlib1", "facterlibfact.rb"), "w") do |file| + file.puts %{ +Facter.add("facterlibfact") do + setcode { "#{val}" } +end +} + end + File.open(File.join(dir, "facterlib2", "facterlibfact2.rb"), "w") do |file| + file.puts %{ +Facter.add("facterlibfact2") do + setcode { "#{val}" } +end } end @@ -386,9 +415,13 @@ def test_localfacts assert_nothing_raised { hash = Facter.to_hash } - + assert(hash.include?("localfact"), "Did not load fact at startup") + assert(hash.include?("facterlibfact"), "Did not load fact from FACTERLIB") + assert(hash.include?("facterlibfact2"), "Did not load fact from multiple FACTERLIBs") assert_equal(val, hash["localfact"], "Did not get correct value") + assert_equal(val, hash["facterlibfact"], "Did not get correct value from FACTERLIB fact") + assert_equal(val, hash["facterlibfact2"], "Did not get correct value from multiple FACTERLIB facts") end def test_stupidchdirring From df57cecd1427b19266cd0f2f36b0c152d6338160 Mon Sep 17 00:00:00 2001 From: luke Date: Wed, 21 Mar 2007 15:40:47 +0000 Subject: [PATCH 0124/3753] Fixing #33 -- we now only return the first mac address git-svn-id: http://reductivelabs.com/svn/facter/trunk@197 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 2 +- tests/tc_simple.rb | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/facter.rb b/lib/facter.rb index c11b643c3b..a9882e4408 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -899,7 +899,7 @@ def Facter.loadfacts output.each {|s| ether.push($1) if s =~ /(?:ether|HWaddr) (\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/ } - ether.join(" ") + ether[0] end end diff --git a/tests/tc_simple.rb b/tests/tc_simple.rb index 82e63719c6..8c3a818762 100755 --- a/tests/tc_simple.rb +++ b/tests/tc_simple.rb @@ -628,6 +628,12 @@ def test_ssh_keys assert(key !~ /\s/, "%s key contains whitespace" % type) end end + + # #33 Make sure we only get one mac address + def test_one_macaddress + assert(! Facter.value(:macaddress).include?(" "), + "Got multiple mac addresses") + end end # $Id$ From 11cff7b227cf1eeb4e27e28c64b8954dc5c11145 Mon Sep 17 00:00:00 2001 From: luke Date: Wed, 21 Mar 2007 15:44:25 +0000 Subject: [PATCH 0125/3753] Fixing Facter.flush git-svn-id: http://reductivelabs.com/svn/facter/trunk@198 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 26 +++++++++++++------------- tests/tc_simple.rb | 21 +++++++++++++++++++++ 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/lib/facter.rb b/lib/facter.rb index a9882e4408..ecfd4c8130 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -47,12 +47,12 @@ class Facter # module methods # Return the version of the library. - def Facter.version + def self.version return FACTERVERSION end # Add some debugging - def Facter.debug(string) + def self.debug(string) if string.nil? return end @@ -63,14 +63,14 @@ def Facter.debug(string) # Return a fact object by name. If you use this, you still have to call # 'value' on it to retrieve the actual value. - def Facter.[](name) + def self.[](name) name = name.to_s.downcase @@facts[name] end # Add a resolution mechanism for a named fact. This does not distinguish # between adding a new fact and adding a new way to resolve a fact. - def Facter.add(name, &block) + def self.add(name, &block) fact = nil dcname = name.to_s.downcase @@ -135,14 +135,14 @@ def method_missing(name, *args) end # Clear all facts. Mostly used for testing. - def Facter.clear + def self.clear Facter.reset Facter.flush @@facts.clear end # Set debugging on or off. - def Facter.debugging(bit) + def self.debugging(bit) if bit case bit when TrueClass: @@debug = 1 @@ -168,24 +168,24 @@ def Facter.debugging(bit) end # Flush all cached values. - def Facter.flush - @@facts.each { |fact| fact.flush } + def self.flush + @@facts.each { |name, fact| fact.flush } end # Return a list of all of the facts. - def Facter.list + def self.list return @@facts.keys end # Remove them all. - def Facter.reset + def self.reset @@facts.each { |name,fact| @@facts.delete(name) } end # Return a hash of all of our facts. - def Facter.to_hash(*tags) + def self.to_hash(*tags) @@facts.inject({}) do |h, ary| if ary[1].suitable? and (tags.empty? or ary[1].tagged?(*tags)) value = ary[1].value @@ -197,7 +197,7 @@ def Facter.to_hash(*tags) end end - def Facter.value(name) + def self.value(name) if @@facts.include?(name.to_s.downcase) @@facts[name.to_s.downcase].value else @@ -582,7 +582,7 @@ def true? end # Load all of the default facts - def Facter.loadfacts + def self.loadfacts Facter.add("FacterVersion") do setcode { FACTERVERSION.to_s } end diff --git a/tests/tc_simple.rb b/tests/tc_simple.rb index 8c3a818762..c21187ce6b 100755 --- a/tests/tc_simple.rb +++ b/tests/tc_simple.rb @@ -634,6 +634,27 @@ def test_one_macaddress assert(! Facter.value(:macaddress).include?(" "), "Got multiple mac addresses") end + + def test_flush + val = "yay" + Facter.add(:testing) do + setcode { val } + end + + assert_equal(val, Facter.value(:testing), + "did not get correct value initially") + val = "foo" + assert_equal("yay", Facter.value(:testing), + "did not cache value") + + assert_nothing_raised("Could not clear facter cache") do + Facter.flush + end + assert_equal(val, Facter.value(:testing), + "did not clear cache") + + + end end # $Id$ From 4880c6954061ced76d88997c14cc963ffb00a9ab Mon Sep 17 00:00:00 2001 From: luke Date: Wed, 21 Mar 2007 15:47:47 +0000 Subject: [PATCH 0126/3753] Applying patch from #36 by psychedelys git-svn-id: http://reductivelabs.com/svn/facter/trunk@199 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter.rb b/lib/facter.rb index ecfd4c8130..489ffa7441 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -918,6 +918,7 @@ def self.loadfacts ether end + end Facter.add("IPAddress") do confine :kernel => :linux @@ -938,7 +939,6 @@ def self.loadfacts ip end end - end Facter.add("IPAddress") do confine :kernel => %w{FreeBSD NetBSD OpenBSD solaris} setcode do From a329b6534fcc568ffe1ee7b6abaadc1a3b385e30 Mon Sep 17 00:00:00 2001 From: luke Date: Wed, 21 Mar 2007 16:35:51 +0000 Subject: [PATCH 0127/3753] Using consistent naming internally; I previously had essentially random quoting and case, but it is now all lower-case symbols. It should behave the same externally. git-svn-id: http://reductivelabs.com/svn/facter/trunk@200 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- CHANGELOG | 9 ++++ lib/facter.rb | 115 ++++++++++++++++++++---------------------- tests/tc_facterbin.rb | 5 +- 3 files changed, 68 insertions(+), 61 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 2d127ffe0d..8a0d6de088 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,12 @@ +1.3.7: + A couple of small bugfixes, including fixing Facter.flush so it correctly + flushes cached values, and the mac address fact only returns one + value, not all of them. + + Converted all of the fact names to symbols, rather than the somewhat + random case used previously. When the facts are converted to a hash, + they still convert the fact name to a string. + 1.3.6: A bugfix release, including fixes for #29, and #30. Also fixed the SSH keys so they only have the key, not the type or description. diff --git a/lib/facter.rb b/lib/facter.rb index 489ffa7441..da7e5c3f32 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -1,4 +1,3 @@ -# $Id$ #-- # Copyright 2006 Luke Kanies # @@ -37,7 +36,14 @@ class Facter # - @@facts = {} + @@facts = Hash.new { |hash, key| + key = key.to_s.downcase.intern + if hash.include?(key) + hash[key] + else + nil + end + } GREEN = "" RESET = "" @@debug = 0 @@ -64,7 +70,6 @@ def self.debug(string) # Return a fact object by name. If you use this, you still have to call # 'value' on it to retrieve the actual value. def self.[](name) - name = name.to_s.downcase @@facts[name] end @@ -72,13 +77,9 @@ def self.[](name) # between adding a new fact and adding a new way to resolve a fact. def self.add(name, &block) fact = nil - dcname = name.to_s.downcase - if @@facts.include?(dcname) - fact = @@facts[dcname] - else - Facter.new(dcname) - fact = @@facts[dcname] + unless fact = @@facts[name] + fact = Facter.new(name) end unless block @@ -98,7 +99,7 @@ def each if fact.suitable? value = fact.value unless value.nil? - yield name, fact.value + yield name.to_s, fact.value end end } @@ -113,7 +114,7 @@ def method_missing(name, *args) name = name.to_s.sub(/\?$/,'') end - if fact = self[name] + if fact = @@facts[name] if question value = fact.value.downcase args.each do |arg| @@ -136,9 +137,8 @@ def method_missing(name, *args) # Clear all facts. Mostly used for testing. def self.clear - Facter.reset Facter.flush - @@facts.clear + Facter.reset end # Set debugging on or off. @@ -179,9 +179,7 @@ def self.list # Remove them all. def self.reset - @@facts.each { |name,fact| - @@facts.delete(name) - } + @@facts.clear end # Return a hash of all of our facts. @@ -190,7 +188,8 @@ def self.to_hash(*tags) if ary[1].suitable? and (tags.empty? or ary[1].tagged?(*tags)) value = ary[1].value if value - h[ary[0]] = value + # For backwards compatibility, convert the fact name to a string. + h[ary[0].to_s] = value end end h @@ -198,8 +197,8 @@ def self.to_hash(*tags) end def self.value(name) - if @@facts.include?(name.to_s.downcase) - @@facts[name.to_s.downcase].value + if fact = @@facts[name] + fact.value else nil end @@ -217,9 +216,9 @@ def ===(value) # Create a new fact, with no resolution mechanisms. def initialize(name) - @name = name.downcase if name.is_a? String + @name = name.to_s.downcase.intern if @@facts.include?(@name) - raise ArgumentError, "A fact named %s already exists" % name + raise ArgumentError, "A fact named %s already exists" % @name else @@facts[@name] = self end @@ -583,15 +582,15 @@ def true? # Load all of the default facts def self.loadfacts - Facter.add("FacterVersion") do + Facter.add(:facterversion) do setcode { FACTERVERSION.to_s } end - Facter.add("RubyVersion") do + Facter.add(:rubyversion) do setcode { RUBY_VERSION.to_s } end - Facter.add("PuppetVersion") do + Facter.add(:puppetversion) do setcode { begin require 'puppet' @@ -611,11 +610,11 @@ def self.loadfacts end end - Facter.add("Kernel") do + Facter.add(:kernel) do setcode 'uname -s' end - Facter.add("KernelRelease") do + Facter.add(:kernelrelease) do setcode 'uname -r' end @@ -637,23 +636,21 @@ def self.loadfacts end end - Facter.add("OperatingSystem") do + Facter.add(:operatingsystem) do # Default to just returning the kernel as the operating system - setcode do Facter["Kernel"].value end + setcode do Facter[:kernel].value end end - Facter.add("OperatingSystemRelease") do - setcode do Facter["KernelRelease"].value end + Facter.add(:operatingsystemrelease) do + setcode do Facter[:kernelrelease].value end end - Facter.add("OperatingSystem") do - #obj.os = "Linux" + Facter.add(:operatingsystem) do confine :kernel => :sunos setcode do "Solaris" end end - Facter.add("OperatingSystem") do - #obj.os = "Linux" + Facter.add(:operatingsystem) do confine :kernel => :linux setcode do if FileTest.exists?("/etc/debian_version") @@ -675,11 +672,11 @@ def self.loadfacts end end - Facter.add("HardwareModel") do + Facter.add(:hardwaremodel) do setcode 'uname -m' end - Facter.add("Architecture") do + Facter.add(:architecture) do confine :kernel => :linux setcode do model = Facter.hardwaremodel @@ -694,7 +691,7 @@ def self.loadfacts end end - Facter.add("CfKey") do + Facter.add(:Cfkey) do setcode do value = nil ["/usr/local/etc/cfkey.pub", @@ -722,7 +719,7 @@ def self.loadfacts end end - Facter.add("Domain") do + Facter.add(:domain) do setcode do # First force the hostname to be checked Facter.hostname @@ -736,7 +733,7 @@ def self.loadfacts end end # Look for the DNS domain name command first. - Facter.add("Domain") do + Facter.add(:domain) do setcode do domain = Resolution.exec('dnsdomainname') or nil # make sure it's a real domain @@ -747,7 +744,7 @@ def self.loadfacts end end end - Facter.add("Domain") do + Facter.add(:domain) do setcode do domain = Resolution.exec('domainname') or nil # make sure it's a real domain @@ -758,7 +755,7 @@ def self.loadfacts end end end - Facter.add("Domain") do + Facter.add(:domain) do setcode do value = nil if FileTest.exists?("/etc/resolv.conf") @@ -786,7 +783,7 @@ def self.loadfacts end end end - Facter.add("Hostname") do + Facter.add(:hostname) do setldapname "cn" setcode do hostname = nil @@ -818,7 +815,7 @@ def self.loadfacts end end - Facter.add("IPAddress") do + Facter.add(:ipaddress) do setldapname "iphostnumber" setcode do require 'resolv' @@ -839,7 +836,7 @@ def self.loadfacts end end end - Facter.add("IPAddress") do + Facter.add(:ipaddress) do setcode do if hostname = Facter.hostname # we need Hostname to exist for this to work @@ -879,19 +876,17 @@ def self.loadfacts } # end of hash each } # end of dir each - Facter.add("UniqueId") do + Facter.add(:uniqueid) do setcode 'hostid', '/bin/sh' - #confine :kernel => %w{Solaris Linux} confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE Debian Gentoo} end - Facter.add("HardwareISA") do + Facter.add(:hardwareisa) do setcode 'uname -p', '/bin/sh' confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE Debian Gentoo} end - Facter.add("MacAddress") do - #confine :kernel => %w{Solaris Linux} + Facter.add(:macaddress) do confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE Debian Gentoo} setcode do ether = [] @@ -903,7 +898,7 @@ def self.loadfacts end end - Facter.add("MacAddress") do + Facter.add(:macaddress) do confine :kernel => :darwin setcode do ether = nil @@ -920,7 +915,7 @@ def self.loadfacts end end - Facter.add("IPAddress") do + Facter.add(:ipaddress) do confine :kernel => :linux setcode do ip = nil @@ -939,7 +934,7 @@ def self.loadfacts ip end end - Facter.add("IPAddress") do + Facter.add(:ipaddress) do confine :kernel => %w{FreeBSD NetBSD OpenBSD solaris} setcode do ip = nil @@ -958,7 +953,7 @@ def self.loadfacts ip end end - Facter.add("IPAddress") do + Facter.add(:ipaddress) do confine :kernel => %w{darwin} setcode do ip = nil @@ -988,19 +983,19 @@ def self.loadfacts ip end end - Facter.add("Hostname") do + Facter.add(:hostname) do confine :kernel => :darwin, :kernelrelease => "R7" setcode do %x{/usr/sbin/scutil --get LocalHostName} end end - Facter.add("IPHostnumber") do + Facter.add(:iphostnumber) do confine :kernel => :darwin, :kernelrelease => "R6" setcode do %x{/usr/sbin/scutil --get LocalHostName} end end - Facter.add("IPHostnumber") do + Facter.add(:iphostnumber) do confine :kernel => :darwin, :kernelrelease => "R6" setcode do ether = nil @@ -1013,16 +1008,16 @@ def self.loadfacts end end - Facter.add("ps") do + Facter.add(:ps) do setcode do 'ps -ef' end end - Facter.add("ps") do + Facter.add(:ps) do confine :operatingsystem => %w{FreeBSD NetBSD OpenBSD Darwin} setcode do 'ps -auxwww' end end - Facter.add("id") do + Facter.add(:id) do #confine :kernel => %w{Solaris Linux} confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE Debian Gentoo} setcode "whoami" @@ -1075,3 +1070,5 @@ def self.loadfacts Facter.loadfacts end + +# $Id$ diff --git a/tests/tc_facterbin.rb b/tests/tc_facterbin.rb index 85f8df15f5..36e3c64125 100755 --- a/tests/tc_facterbin.rb +++ b/tests/tc_facterbin.rb @@ -38,14 +38,15 @@ def test_output hash = output.split("\n").inject({}) do |h, line| name, value = line.split(" => ") - h[name] = value + h[name.downcase] = value h end Facter.each do |name, fact| next if name.to_s =~ /memory/ - assert(hash.include?(name.downcase), "Did not get " + name) + assert(hash.include?(name), + "Did not get %s" % name) assert_equal(fact, hash[name], "%s was not equal" % name) end From 94ca0c3342a5a5f0a51bdfffde6c136cfc58b9c4 Mon Sep 17 00:00:00 2001 From: luke Date: Wed, 21 Mar 2007 16:44:43 +0000 Subject: [PATCH 0128/3753] Updated to version 1.3.7 git-svn-id: http://reductivelabs.com/svn/facter/trunk@201 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- conf/redhat/facter.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index eb4172c265..267559ab08 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -5,7 +5,7 @@ Summary: Ruby module for collecting simple facts about a host operating system Name: facter -Version: 1.3.6 +Version: 1.3.7 Release: 1%{?dist} License: GPL Group: System Environment/Base From 09261ac06dfb511c461c2f210b6657154cf59169 Mon Sep 17 00:00:00 2001 From: luke Date: Wed, 21 Mar 2007 16:44:44 +0000 Subject: [PATCH 0129/3753] Updated to version 1.3.7 git-svn-id: http://reductivelabs.com/svn/facter/trunk@202 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter.rb b/lib/facter.rb index da7e5c3f32..8731485296 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -21,7 +21,7 @@ class Facter include Comparable include Enumerable - FACTERVERSION = '1.3.6' + FACTERVERSION = '1.3.7' # = Facter # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. From b582612ea1260e89fba010f93cad144d2fcbcbed Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 5 Jun 2007 18:08:16 +0000 Subject: [PATCH 0130/3753] Applying patch from Valentin Vidic, fixing open filehandles git-svn-id: http://reductivelabs.com/svn/facter/trunk@204 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/facter.rb b/lib/facter.rb index 8731485296..e5a22d7dcd 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -865,7 +865,9 @@ def self.loadfacts filepath = File.join(dir,file) if FileTest.file?(filepath) begin - value = File.open(filepath).read.chomp.split(/\s+/)[1] + File.open(filepath) { |f| + value = f.read.chomp.split(/\s+/)[1] + } rescue value = nil end From 86e3d8ef645003fc115651fbad21bb63813497bf Mon Sep 17 00:00:00 2001 From: luke Date: Mon, 11 Jun 2007 22:01:13 +0000 Subject: [PATCH 0131/3753] Setting the ldapname so it is guaranteed to be a string git-svn-id: http://reductivelabs.com/svn/facter/trunk@205 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 4 ++-- tests/tc_simple.rb | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/facter.rb b/lib/facter.rb index e5a22d7dcd..eca65b86d3 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -229,7 +229,7 @@ def initialize(name) @value = nil - @ldapname = name + @ldapname = name.to_s end # Add a new resolution mechanism. This requires a block, which will then @@ -473,7 +473,7 @@ def setcode(string = nil, interp = nil, &block) # Set the name by which this parameter is known in LDAP. The default # is just the fact name. def setldapname(name) - @fact.ldapname = name + @fact.ldapname = name.to_s end # Is this resolution mechanism suitable on the system in question? diff --git a/tests/tc_simple.rb b/tests/tc_simple.rb index c21187ce6b..ee5d0afce0 100755 --- a/tests/tc_simple.rb +++ b/tests/tc_simple.rb @@ -272,6 +272,7 @@ def test_ldapname facts.each { |name, fact| assert(fact.ldapname, "Fact %s has no ldapname" % name) + assert_instance_of(String, fact.ldapname, "Fact %s has a non-string ldapname" % name) } end From 46d9bedc8b3f894fc8fab64955775daf94211814 Mon Sep 17 00:00:00 2001 From: mccune Date: Wed, 13 Jun 2007 17:40:45 +0000 Subject: [PATCH 0132/3753] Added a bunch of information from system_profiler -xml. In particular, sp_serial_number is interesting. Also added values from sw_vers, to get the commonly used Mac OS X version and build identifier. git-svn-id: http://reductivelabs.com/svn/facter/trunk@206 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter/macosx.rb | 82 +++++++++++ lib/facter/util/plist.rb | 24 +++ lib/facter/util/plist/generator.rb | 226 ++++++++++++++++++++++++++++ lib/facter/util/plist/parser.rb | 227 +++++++++++++++++++++++++++++ 4 files changed, 559 insertions(+) create mode 100644 lib/facter/macosx.rb create mode 100644 lib/facter/util/plist.rb create mode 100644 lib/facter/util/plist/generator.rb create mode 100644 lib/facter/util/plist/parser.rb diff --git a/lib/facter/macosx.rb b/lib/facter/macosx.rb new file mode 100644 index 0000000000..2c4c2fa9a4 --- /dev/null +++ b/lib/facter/macosx.rb @@ -0,0 +1,82 @@ +# +# macosx.rb +# Additional Facts coming from Mac OS X system_profiler command +# +# Copyright (C) 2007 Jeff McCune +# Author: Jeff McCune +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation (version 2 of the License) +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA + +# Jeff McCune +# There's a lot more information coming out of system_profiler -xml +# We could add quite a bit more, but I didn't want to overload facter +# at this point in time. +# In particular, Installed Software might be an interesting addition. + +module Facter::Macosx + require 'thread' + require 'facter/util/plist' + + # JJM I'd really like to dynamically generate these methods + # by looking at the _name key of the _items dict for each _dataType + + def self.hardware_overview + # JJM Perhaps we should cache the XML data in a "class" level object. + top_level_plist = Plist::parse_xml %x{/usr/sbin/system_profiler -xml SPHardwareDataType} + system_hardware = top_level_plist[0]['_items'][0] + system_hardware.delete '_name' + system_hardware + end + + # SPSoftwareDataType + def self.os_overview + top_level_plist = Plist::parse_xml %x{/usr/sbin/system_profiler -xml SPSoftwareDataType} + os_stuff = top_level_plist[0]['_items'][0] + os_stuff.delete '_name' + os_stuff + end + + def self.sw_vers + ver = Hash.new + [ "productName", "productVersion", "buildVersion" ].each do |option| + ver["macosx_#{option}"] = %x{sw_vers -#{option}}.strip + end + ver + end +end + +Facter::Macosx.hardware_overview.each do |fact, value| + Facter.add("sp_#{fact}") do + confine :kernel => :darwin + setcode do + value + end + end +end + +Facter::Macosx.os_overview.each do |fact, value| + Facter.add("sp_#{fact}") do + confine :kernel => :darwin + setcode do + value + end + end +end + +Facter::Macosx.sw_vers.each do |fact, value| + Facter.add(fact) do + confine :kernel => :darwin + setcode do + value + end + end +end diff --git a/lib/facter/util/plist.rb b/lib/facter/util/plist.rb new file mode 100644 index 0000000000..ec6d8e0709 --- /dev/null +++ b/lib/facter/util/plist.rb @@ -0,0 +1,24 @@ +#-- +############################################################## +# Copyright 2006, Ben Bleything and # +# Patrick May # +# # +# Distributed under the MIT license. # +############################################################## +#++ +# = Plist +# +# This is the main file for plist. Everything interesting happens in Plist and Plist::Emit. + +require 'base64' +require 'cgi' +require 'stringio' + +require 'puppet/util/plist/generator' +require 'puppet/util/plist/parser' + +module Plist + VERSION = '3.0.0' +end + +# $Id: plist.rb 1781 2006-10-16 01:01:35Z luke $ diff --git a/lib/facter/util/plist/generator.rb b/lib/facter/util/plist/generator.rb new file mode 100644 index 0000000000..f6aba29c38 --- /dev/null +++ b/lib/facter/util/plist/generator.rb @@ -0,0 +1,226 @@ +#--########################################################### +# Copyright 2006, Ben Bleything and # +# Patrick May # +# # +# Distributed under the MIT license. # +############################################################## +#++ +# See Plist::Emit. +module Plist + # === Create a plist + # You can dump an object to a plist in one of two ways: + # + # * Plist::Emit.dump(obj) + # * obj.to_plist + # * This requires that you mixin the Plist::Emit module, which is already done for +Array+ and +Hash+. + # + # The following Ruby classes are converted into native plist types: + # Array, Bignum, Date, DateTime, Fixnum, Float, Hash, Integer, String, Symbol, Time, true, false + # * +Array+ and +Hash+ are both recursive; their elements will be converted into plist nodes inside the and containers (respectively). + # * +IO+ (and its descendants) and +StringIO+ objects are read from and their contents placed in a element. + # * User classes may implement +to_plist_node+ to dictate how they should be serialized; otherwise the object will be passed to Marshal.dump and the result placed in a element. + # + # For detailed usage instructions, refer to USAGE[link:files/docs/USAGE.html] and the methods documented below. + module Emit + # Helper method for injecting into classes. Calls Plist::Emit.dump with +self+. + def to_plist(envelope = true) + return Plist::Emit.dump(self, envelope) + end + + # Helper method for injecting into classes. Calls Plist::Emit.save_plist with +self+. + def save_plist(filename) + Plist::Emit.save_plist(self, filename) + end + + # The following Ruby classes are converted into native plist types: + # Array, Bignum, Date, DateTime, Fixnum, Float, Hash, Integer, String, Symbol, Time + # + # Write us (via RubyForge) if you think another class can be coerced safely into one of the expected plist classes. + # + # +IO+ and +StringIO+ objects are encoded and placed in elements; other objects are Marshal.dump'ed unless they implement +to_plist_node+. + # + # The +envelope+ parameters dictates whether or not the resultant plist fragment is wrapped in the normal XML/plist header and footer. Set it to false if you only want the fragment. + def self.dump(obj, envelope = true) + output = plist_node(obj) + + output = wrap(output) if envelope + + return output + end + + # Writes the serialized object's plist to the specified filename. + def self.save_plist(obj, filename) + File.open(filename, 'wb') do |f| + f.write(obj.to_plist) + end + end + + private + def self.plist_node(element) + output = '' + + if element.respond_to? :to_plist_node + output << element.to_plist_node + else + case element + when Array + if element.empty? + output << "\n" + else + output << tag('array') { + element.collect {|e| plist_node(e)} + } + end + when Hash + if element.empty? + output << "\n" + else + inner_tags = [] + + element.keys.sort.each do |k| + v = element[k] + inner_tags << tag('key', CGI::escapeHTML(k.to_s)) + inner_tags << plist_node(v) + end + + output << tag('dict') { + inner_tags + } + end + when true, false + output << "<#{element}/>\n" + when Time + output << tag('date', element.utc.strftime('%Y-%m-%dT%H:%M:%SZ')) + when Date # also catches DateTime + output << tag('date', element.strftime('%Y-%m-%dT%H:%M:%SZ')) + when String, Symbol, Fixnum, Bignum, Integer, Float + output << tag(element_type(element), CGI::escapeHTML(element.to_s)) + when IO, StringIO + element.rewind + contents = element.read + # note that apple plists are wrapped at a different length then + # what ruby's base64 wraps by default. + # I used #encode64 instead of #b64encode (which allows a length arg) + # because b64encode is b0rked and ignores the length arg. + data = "\n" + Base64::encode64(contents).gsub(/\s+/, '').scan(/.{1,68}/o) { data << $& << "\n" } + output << tag('data', data) + else + output << comment( 'The element below contains a Ruby object which has been serialized with Marshal.dump.' ) + data = "\n" + Base64::encode64(Marshal.dump(element)).gsub(/\s+/, '').scan(/.{1,68}/o) { data << $& << "\n" } + output << tag('data', data ) + end + end + + return output + end + + def self.comment(content) + return "\n" + end + + def self.tag(type, contents = '', &block) + out = nil + + if block_given? + out = IndentedString.new + out << "<#{type}>" + out.raise_indent + + out << block.call + + out.lower_indent + out << "" + else + out = "<#{type}>#{contents.to_s}\n" + end + + return out.to_s + end + + def self.wrap(contents) + output = '' + + output << '' + "\n" + output << '' + "\n" + output << '' + "\n" + + output << contents + + output << '' + "\n" + + return output + end + + def self.element_type(item) + return case item + when String, Symbol: 'string' + when Fixnum, Bignum, Integer: 'integer' + when Float: 'real' + else + raise "Don't know about this data type... something must be wrong!" + end + end + private + class IndentedString #:nodoc: + attr_accessor :indent_string + + @@indent_level = 0 + + def initialize(str = "\t") + @indent_string = str + @contents = '' + end + + def to_s + return @contents + end + + def raise_indent + @@indent_level += 1 + end + + def lower_indent + @@indent_level -= 1 if @@indent_level > 0 + end + + def <<(val) + if val.is_a? Array + val.each do |f| + self << f + end + else + # if it's already indented, don't bother indenting further + unless val =~ /\A#{@indent_string}/ + indent = @indent_string * @@indent_level + + @contents << val.gsub(/^/, indent) + else + @contents << val + end + + # it already has a newline, don't add another + @contents << "\n" unless val =~ /\n$/ + end + end + end + end +end + +# we need to add this so sorting hash keys works properly +class Symbol #:nodoc: + def <=> (other) + self.to_s <=> other.to_s + end +end + +class Array #:nodoc: + include Plist::Emit +end + +class Hash #:nodoc: + include Plist::Emit +end + +# $Id: generator.rb 1781 2006-10-16 01:01:35Z luke $ diff --git a/lib/facter/util/plist/parser.rb b/lib/facter/util/plist/parser.rb new file mode 100644 index 0000000000..24f791ff74 --- /dev/null +++ b/lib/facter/util/plist/parser.rb @@ -0,0 +1,227 @@ +#--########################################################### +# Copyright 2006, Ben Bleything and # +# Patrick May # +# # +# Distributed under the MIT license. # +############################################################## +#++ +# Plist parses Mac OS X xml property list files into ruby data structures. +# +# === Load a plist file +# This is the main point of the library: +# +# r = Plist::parse_xml( filename_or_xml ) +module Plist +# Note that I don't use these two elements much: +# +# + Date elements are returned as DateTime objects. +# + Data elements are implemented as Tempfiles +# +# Plist::parse_xml will blow up if it encounters a data element. +# If you encounter such an error, or if you have a Date element which +# can't be parsed into a Time object, please send your plist file to +# plist@hexane.org so that I can implement the proper support. + def Plist::parse_xml( filename_or_xml ) + listener = Listener.new + #parser = REXML::Parsers::StreamParser.new(File.new(filename), listener) + parser = StreamParser.new(filename_or_xml, listener) + parser.parse + listener.result + end + + class Listener + #include REXML::StreamListener + + attr_accessor :result, :open + + def initialize + @result = nil + @open = Array.new + end + + + def tag_start(name, attributes) + @open.push PTag::mappings[name].new + end + + def text( contents ) + @open.last.text = contents if @open.last + end + + def tag_end(name) + last = @open.pop + if @open.empty? + @result = last.to_ruby + else + @open.last.children.push last + end + end + end + + class StreamParser + def initialize( filename_or_xml, listener ) + @filename_or_xml = filename_or_xml + @listener = listener + end + + TEXT = /([^<]+)/ + XMLDECL_PATTERN = /<\?xml\s+(.*?)\?>*/um + DOCTYPE_PATTERN = /\s*)/um + COMMENT_START = /\A/um + + + def parse + plist_tags = PTag::mappings.keys.join('|') + start_tag = /<(#{plist_tags})([^>]*)>/i + end_tag = /<\/(#{plist_tags})[^>]*>/i + + require 'strscan' + + contents = ( + if (File.exists? @filename_or_xml) + File.open(@filename_or_xml) {|f| f.read} + else + @filename_or_xml + end + ) + + @scanner = StringScanner.new( contents ) + until @scanner.eos? + if @scanner.scan(COMMENT_START) + @scanner.scan(COMMENT_END) + elsif @scanner.scan(XMLDECL_PATTERN) + elsif @scanner.scan(DOCTYPE_PATTERN) + elsif @scanner.scan(start_tag) + @listener.tag_start(@scanner[1], nil) + if (@scanner[2] =~ /\/$/) + @listener.tag_end(@scanner[1]) + end + elsif @scanner.scan(TEXT) + @listener.text(@scanner[1]) + elsif @scanner.scan(end_tag) + @listener.tag_end(@scanner[1]) + else + raise "Unimplemented element" + end + end + end + end + + class PTag + @@mappings = { } + def PTag::mappings + @@mappings + end + + def PTag::inherited( sub_class ) + key = sub_class.to_s.downcase + key.gsub!(/^plist::/, '' ) + key.gsub!(/^p/, '') unless key == "plist" + + @@mappings[key] = sub_class + end + + attr_accessor :text, :children + def initialize + @children = Array.new + end + + def to_ruby + raise "Unimplemented: " + self.class.to_s + "#to_ruby on #{self.inspect}" + end + end + + class PList < PTag + def to_ruby + children.first.to_ruby if children.first + end + end + + class PDict < PTag + def to_ruby + dict = Hash.new + key = nil + + children.each do |c| + if key.nil? + key = c.to_ruby + else + dict[key] = c.to_ruby + key = nil + end + end + + dict + end + end + + class PKey < PTag + def to_ruby + CGI::unescapeHTML(text || '') + end + end + + class PString < PTag + def to_ruby + CGI::unescapeHTML(text || '') + end + end + + class PArray < PTag + def to_ruby + children.collect do |c| + c.to_ruby + end + end + end + + class PInteger < PTag + def to_ruby + text.to_i + end + end + + class PTrue < PTag + def to_ruby + true + end + end + + class PFalse < PTag + def to_ruby + false + end + end + + class PReal < PTag + def to_ruby + text.to_f + end + end + + require 'date' + class PDate < PTag + def to_ruby + DateTime.parse(text) + end + end + + require 'base64' + class PData < PTag + def to_ruby + data = Base64.decode64(text.gsub(/\s+/, '')) + + begin + return Marshal.load(data) + rescue Exception => e + io = StringIO.new + io.write data + io.rewind + return io + end + end + end +end + +# $Id: parser.rb 1781 2006-10-16 01:01:35Z luke $ From 43933dd0fbdb8c03da6776fb56bfe60a1c71c984 Mon Sep 17 00:00:00 2001 From: mccune Date: Wed, 13 Jun 2007 17:58:46 +0000 Subject: [PATCH 0133/3753] Fixed problem where facter referenced puppet plist utility library. git-svn-id: http://reductivelabs.com/svn/facter/trunk@207 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter/util/plist.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/facter/util/plist.rb b/lib/facter/util/plist.rb index ec6d8e0709..32e9e2bf7f 100644 --- a/lib/facter/util/plist.rb +++ b/lib/facter/util/plist.rb @@ -14,8 +14,8 @@ require 'cgi' require 'stringio' -require 'puppet/util/plist/generator' -require 'puppet/util/plist/parser' +require 'facter/util/plist/generator' +require 'facter/util/plist/parser' module Plist VERSION = '3.0.0' From 8a67e325a986bb45f5d18469ddb02cafbe27f996 Mon Sep 17 00:00:00 2001 From: mccune Date: Wed, 13 Jun 2007 18:33:06 +0000 Subject: [PATCH 0134/3753] Fixed problem with executing system_profiler and sw_vers on non Darwin hosts. git-svn-id: http://reductivelabs.com/svn/facter/trunk@208 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter/macosx.rb | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/lib/facter/macosx.rb b/lib/facter/macosx.rb index 2c4c2fa9a4..73339b7385 100644 --- a/lib/facter/macosx.rb +++ b/lib/facter/macosx.rb @@ -54,29 +54,31 @@ def self.sw_vers end end -Facter::Macosx.hardware_overview.each do |fact, value| - Facter.add("sp_#{fact}") do - confine :kernel => :darwin - setcode do - value +if Facter.kernel == "Darwin" + Facter::Macosx.hardware_overview.each do |fact, value| + Facter.add("sp_#{fact}") do + confine :kernel => :darwin + setcode do + value + end end end -end -Facter::Macosx.os_overview.each do |fact, value| - Facter.add("sp_#{fact}") do - confine :kernel => :darwin - setcode do - value + Facter::Macosx.os_overview.each do |fact, value| + Facter.add("sp_#{fact}") do + confine :kernel => :darwin + setcode do + value + end end end -end -Facter::Macosx.sw_vers.each do |fact, value| - Facter.add(fact) do - confine :kernel => :darwin - setcode do - value + Facter::Macosx.sw_vers.each do |fact, value| + Facter.add(fact) do + confine :kernel => :darwin + setcode do + value + end end end end From 750a0c632d3f1e1ff910245c70653b86c85a1dd8 Mon Sep 17 00:00:00 2001 From: josb Date: Thu, 14 Jun 2007 22:09:41 +0000 Subject: [PATCH 0135/3753] Add YAML output option to the help text. git-svn-id: http://reductivelabs.com/svn/facter/trunk@209 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- bin/facter | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bin/facter b/bin/facter index 10389bac39..3543a0aa22 100755 --- a/bin/facter +++ b/bin/facter @@ -6,7 +6,7 @@ # # = Usage # -# facter [-d|--debug] [-h|--help] [-v|--version] [fact] [fact] [...] +# facter [-d|--debug] [-h|--help] [-v|--version] [-y|--yaml] [fact] [fact] [...] # # = Description # @@ -27,6 +27,9 @@ # version:: # Print the version and exit. # +# yaml:: +# Emit facts in YAML format. +# # = Example # # facter kernel From 68449a95efc4245313df38bbee5838522930108f Mon Sep 17 00:00:00 2001 From: luke Date: Mon, 18 Jun 2007 21:10:10 +0000 Subject: [PATCH 0136/3753] Adding manufacturer code, as requested by digant on the Puppet Trac site. git-svn-id: http://reductivelabs.com/svn/facter/trunk@210 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter/manufacturer.rb | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 lib/facter/manufacturer.rb diff --git a/lib/facter/manufacturer.rb b/lib/facter/manufacturer.rb new file mode 100644 index 0000000000..049bcd90af --- /dev/null +++ b/lib/facter/manufacturer.rb @@ -0,0 +1,25 @@ +# Info about the manufacturer +# + +module Facter::Manufacturer + def self.dmi_find_system_info(name) + dmiinfo = `/usr/sbin/dmidecode` + info = dmiinfo.scan(/^\s*System Information(.*?)\n\S/m).join.split("\n").map { |line| + line.split(":").map { |line2| line2.strip } + }.reject { |array| array.empty? } + info.select { |array| array[0] == name} [0][1] + end +end + +# Add the facts to Facter + +{:SerialNumber => "Serial Number", + :Manufacturer => "Manufacturer", + :ProductName=> "Product Name"}.each do |fact, name| + Facter.add(fact) do + confine :kernel => :linux + setcode do + Facter::Manufacturer.dmi_find_system_info(name) + end + end +end From 43b5640e425d19a20e04fb1d5661bc48d23ee928 Mon Sep 17 00:00:00 2001 From: lutter Date: Sun, 24 Jun 2007 03:09:25 +0000 Subject: [PATCH 0137/3753] Remove tabs; don't fail if dmidecode doesn't return expected information git-svn-id: http://reductivelabs.com/svn/facter/trunk@211 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter/manufacturer.rb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/facter/manufacturer.rb b/lib/facter/manufacturer.rb index 049bcd90af..128241cd87 100644 --- a/lib/facter/manufacturer.rb +++ b/lib/facter/manufacturer.rb @@ -1,13 +1,14 @@ # Info about the manufacturer # - + module Facter::Manufacturer def self.dmi_find_system_info(name) dmiinfo = `/usr/sbin/dmidecode` - info = dmiinfo.scan(/^\s*System Information(.*?)\n\S/m).join.split("\n").map { |line| - line.split(":").map { |line2| line2.strip } - }.reject { |array| array.empty? } - info.select { |array| array[0] == name} [0][1] + info = dmiinfo.scan(/^\s*System Information(.*?)\n\S/m).join.split("\n").map { |line| + line.split(":").map { |line2| line2.strip } + }.reject { |array| array.empty? } + val = info.find { |array| array[0] == name} + return (val && val.size >= 2) ? val[1] : nil end end From 20986d92f34679c563e61452cada690375355920 Mon Sep 17 00:00:00 2001 From: lutter Date: Mon, 25 Jun 2007 17:35:31 +0000 Subject: [PATCH 0138/3753] Set operatingsystemrelease to the major release on RHEL and Fedora git-svn-id: http://reductivelabs.com/svn/facter/trunk@212 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 46 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/lib/facter.rb b/lib/facter.rb index eca65b86d3..de8b43b728 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -636,15 +636,6 @@ def self.loadfacts end end - Facter.add(:operatingsystem) do - # Default to just returning the kernel as the operating system - setcode do Facter[:kernel].value end - end - - Facter.add(:operatingsystemrelease) do - setcode do Facter[:kernelrelease].value end - end - Facter.add(:operatingsystem) do confine :kernel => :sunos setcode do "Solaris" end @@ -672,6 +663,43 @@ def self.loadfacts end end + Facter.add(:operatingsystem) do + # Default to just returning the kernel as the operating system + setcode do Facter[:kernel].value end + end + + Facter.add(:operatingsystemrelease) do + confine :operatingsystem => :fedora + setcode do + File::open("/etc/fedora-release", "r") do |f| + line = f.readline.chomp + if line =~ /\(Rawhide\)$/ + "Rawhide" + elsif line =~ /release (\d+)/ + $1 + end + end + end + end + + Facter.add(:operatingsystemrelease) do + confine :operatingsystem => :redhat + setcode do + File::open("/etc/redhat-release", "r") do |f| + line = f.readline.chomp + if line =~ /\(Rawhide\)$/ + "Rawhide" + elsif line =~ /release (\d+)/ + $1 + end + end + end + end + + Facter.add(:operatingsystemrelease) do + setcode do Facter[:kernelrelease].value end + end + Facter.add(:hardwaremodel) do setcode 'uname -m' end From f35ee22e8dbfa267d3e44e90c42cde0cd197e1d5 Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 17 Jul 2007 20:03:35 +0000 Subject: [PATCH 0139/3753] Drastically speeding up the lsb data retrieval, and refactoring the dmidecode data so it is a bit cleaner and does not produce extraneous output or errors git-svn-id: http://reductivelabs.com/svn/facter/trunk@213 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- lib/facter.rb | 18 +++++++++++------- lib/facter/manufacturer.rb | 34 ++++++++++++++++++++++++++++------ 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/lib/facter.rb b/lib/facter.rb index de8b43b728..8a52a6a01c 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -618,16 +618,20 @@ def self.loadfacts setcode 'uname -r' end - { "LSBRelease" => "^LSB Version:\t(.*)$", - "LSBDistId" => "^Distributor ID:\t(.*)$", - "LSBDistRelease" => "^Release:\t(.*)$", - "LSBDistDescription" => "^Description:\t(.*)$", - "LSBDistCodeName" => "^Codename:\t(.*)$" + { "LSBRelease" => %r{^LSB Version:\t(.*)$}, + "LSBDistId" => %r{^Distributor ID:\t(.*)$}, + "LSBDistRelease" => %r{^Release:\t(.*)$}, + "LSBDistDescription" => %r{^Description:\t(.*)$}, + "LSBDistCodeName" => %r{^Codename:\t(.*)$} }.each do |fact, pattern| Facter.add(fact) do setcode do - output = Resolution.exec('lsb_release -a 2>/dev/null') - if output =~ /#{pattern}/ + unless defined?(@@lsbdata) and defined?(@@lsbtime) and (Time.now.to_i - @@lsbtime.to_i < 5) + type = nil + @@lsbtime = Time.now + @@lsbdata = Resolution.exec('lsb_release -a 2>/dev/null') + end + if pattern.match(@@lsbdata) $1 else nil diff --git a/lib/facter/manufacturer.rb b/lib/facter/manufacturer.rb index 128241cd87..be36871067 100644 --- a/lib/facter/manufacturer.rb +++ b/lib/facter/manufacturer.rb @@ -3,12 +3,34 @@ module Facter::Manufacturer def self.dmi_find_system_info(name) - dmiinfo = `/usr/sbin/dmidecode` - info = dmiinfo.scan(/^\s*System Information(.*?)\n\S/m).join.split("\n").map { |line| - line.split(":").map { |line2| line2.strip } - }.reject { |array| array.empty? } - val = info.find { |array| array[0] == name} - return (val && val.size >= 2) ? val[1] : nil + return nil unless FileTest.exists?("/usr/sbin/dmidecode") + + # Do not run the command more than every five seconds. + unless defined?(@data) and defined?(@time) and (Time.now.to_i - @time.to_i < 5) + @data = {} + type = nil + @time = Time.now + # It's *much* easier to just parse the whole darn file than + # to just match a chunk of it. + %x{/usr/sbin/dmidecode 2>/dev/null}.split("\n").each do |line| + case line + when /^(\S.+)$/ + type = $1.chomp + @data[type] ||= {} + when /^\s+(\S.+): (\S.*)$/ + unless type + next + end + @data[type][$1] = $2 + end + end + end + + if data = @data["System Information"] + data[name] + else + nil + end end end From 8426aaf4e385aa7e221a966524db1a4fd89482cb Mon Sep 17 00:00:00 2001 From: luke Date: Tue, 17 Jul 2007 20:29:17 +0000 Subject: [PATCH 0140/3753] making the install script executable git-svn-id: http://reductivelabs.com/svn/facter/trunk@214 1f5c1d6a-bddf-0310-8f58-fc49e503516a --- install.rb | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 install.rb diff --git a/install.rb b/install.rb old mode 100644 new mode 100755 From 611337521e72c4bce4d52bea110abaaa348ff034 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 13 Sep 2007 08:51:11 +1000 Subject: [PATCH 0141/3753] Added macaddress fact support for FreeBSD and OpenBSD - closes #37 Added hardwareisa support for *BSD platforms - closed #38 Facter now detects the Mandriva distribution - closes #39 Facter now correctly detects ipaddress on NetBSD - closes #42 --- lib/facter.rb | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/lib/facter.rb b/lib/facter.rb index 8a52a6a01c..e9fe58de8c 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -654,6 +654,8 @@ def self.loadfacts "Gentoo" elsif FileTest.exists?("/etc/fedora-release") "Fedora" + elsif FileTest.exists?("/etc/mandriva-release") + "Mandriva" elsif FileTest.exists?("/etc/redhat-release") txt = File.read("/etc/redhat-release") if txt =~ /centos/i @@ -917,7 +919,7 @@ def self.loadfacts Facter.add(:hardwareisa) do setcode 'uname -p', '/bin/sh' - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE Debian Gentoo} + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE Debian Gentoo FreeBSD OpenBSD NetBSD} end Facter.add(:macaddress) do @@ -932,6 +934,20 @@ def self.loadfacts end end + Facter.add(:macaddress) do + confine :operatingsystem => %w{FreeBSD OpenBSD} + setcode do + ether = [] + output = %x{/sbin/ifconfig} + output.each {|s| + if s =~ /(?:ether|lladdr)\s+(\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ + ether.push($1) + end + } + ether[0] + end + end + Facter.add(:macaddress) do confine :kernel => :darwin setcode do @@ -969,7 +985,7 @@ def self.loadfacts end end Facter.add(:ipaddress) do - confine :kernel => %w{FreeBSD NetBSD OpenBSD solaris} + confine :kernel => %w{FreeBSD OpenBSD solaris} setcode do ip = nil output = %x{/sbin/ifconfig} @@ -987,6 +1003,25 @@ def self.loadfacts ip end end + Facter.add(:ipaddress) do + confine :kernel => :NetBSD + setcode do + ip = nil + output = %x{/sbin/ifconfig -a} + + output.split(/^\S/).each { |str| + if str =~ /inet ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + tmp = $1 + unless tmp =~ /127\./ + ip = tmp + break + end + end + } + + ip + end + end Facter.add(:ipaddress) do confine :kernel => %w{darwin} setcode do From 8b08d5fca8809dfa68c353b9ba9526abb600ef0e Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Thu, 13 Sep 2007 13:38:18 +1000 Subject: [PATCH 0142/3753] Added support to return multiple interfaces and their IP addresses as facts. Existing ipaddress fact which returns IP address of first interface on node is still available. Currently Linux only. Closes #6 --- lib/facter/ipmess.rb | 45 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 lib/facter/ipmess.rb diff --git a/lib/facter/ipmess.rb b/lib/facter/ipmess.rb new file mode 100644 index 0000000000..c7d8214863 --- /dev/null +++ b/lib/facter/ipmess.rb @@ -0,0 +1,45 @@ +# +# ipmess.rb +# Try to get additional Facts about the machine's network interfaces on Linux +# +# Copyright (C) 2007 psychedelys +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation (version 2 of the License) +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA +# + +if Facter.kernel == "Linux" + output = %x{/sbin/ifconfig -a} + tmp1 = nil + tmp2 = nil + tmp3 = nil + test = {} + output.each {|s| + tmp1 = s.split(" ")[0] if s !~ /^ / + tmp2 = $1 if s =~ /inet addr:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + tmp3 = $1 if s =~ /(?:ether|HWaddr) (\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/ + if tmp1 != nil && tmp2 != nil && tmp3 != nil + test["ipaddress_" + tmp1] = tmp2 + test["macaddress_" + tmp1] = tmp3 + tmp1 = nil + tmp2 = nil + tmp3 = nil + end + } + test.each{|name,fact| + Facter.add(name) do + confine :kernel => :linux + setcode do + fact + end + end + } +end From a4698cedb19349f0763e0956f2fc14169eee2df2 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Thu, 13 Sep 2007 13:53:30 +1000 Subject: [PATCH 0143/3753] Updated CHANGELOG --- CHANGELOG | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 8a0d6de088..43d3dac10b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,17 @@ +1.3.?: + Added support to return multiple interfaces and their IP addresses and + MAC addressess as facts. Returns interface_interfacename and + macaddress_interfacename. Existing ipaddress and macaddress facts are + unchanged and still returned. Currently Linux only. Closes #6. + + Added macaddress fact support for FreeBSD and OpenBSD - closes #37 + + Added hardwareisa support for *BSD platforms - closed #38 + + Facter now detects the Mandriva distribution - closes #39 + + Facter now correctly detects ipaddress on NetBSD - closes #42 + 1.3.7: A couple of small bugfixes, including fixing Facter.flush so it correctly flushes cached values, and the mac address fact only returns one From c5e6f602ae71d43ec58b65ec6b2f4f540bc27649 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 16 Sep 2007 10:13:58 +1000 Subject: [PATCH 0144/3753] Adjusted :kernel confine to make it more in line with others Replaced initial ipmess.rb with updated version and added BSD support --- CHANGELOG | 2 +- lib/facter.rb | 2 +- lib/facter/ipmess.rb | 76 +++++++++++++++++++++++++++++++++----------- 3 files changed, 60 insertions(+), 20 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 43d3dac10b..e5939a3273 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,7 +2,7 @@ Added support to return multiple interfaces and their IP addresses and MAC addressess as facts. Returns interface_interfacename and macaddress_interfacename. Existing ipaddress and macaddress facts are - unchanged and still returned. Currently Linux only. Closes #6. + unchanged and still returned. Supports bith Linux and BSD. Closes #6. Added macaddress fact support for FreeBSD and OpenBSD - closes #37 diff --git a/lib/facter.rb b/lib/facter.rb index e9fe58de8c..802f848caf 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -1004,7 +1004,7 @@ def self.loadfacts end end Facter.add(:ipaddress) do - confine :kernel => :NetBSD + confine :kernel => %w{NetBSD} setcode do ip = nil output = %x{/sbin/ifconfig -a} diff --git a/lib/facter/ipmess.rb b/lib/facter/ipmess.rb index c7d8214863..777084f271 100644 --- a/lib/facter/ipmess.rb +++ b/lib/facter/ipmess.rb @@ -2,8 +2,9 @@ # ipmess.rb # Try to get additional Facts about the machine's network interfaces on Linux # -# Copyright (C) 2007 psychedelys -# +# Original concept Copyright (C) 2007 psychedelys +# Update and *BSD support (C) 2007 James Turnbull +# # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation (version 2 of the License) @@ -17,29 +18,68 @@ # if Facter.kernel == "Linux" - output = %x{/sbin/ifconfig -a} - tmp1 = nil - tmp2 = nil - tmp3 = nil - test = {} - output.each {|s| - tmp1 = s.split(" ")[0] if s !~ /^ / - tmp2 = $1 if s =~ /inet addr:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ - tmp3 = $1 if s =~ /(?:ether|HWaddr) (\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/ - if tmp1 != nil && tmp2 != nil && tmp3 != nil - test["ipaddress_" + tmp1] = tmp2 - test["macaddress_" + tmp1] = tmp3 - tmp1 = nil - tmp2 = nil - tmp3 = nil - end + + output = %x{/sbin/ifconfig -a} + int = nil + output.scan(/^(\w+)(\d+)/) { |str| + output_int = %x{/sbin/ifconfig #{str}} + int = "#{str}" + tmp1 = nil + tmp2 = nil + test = {} + output_int.each { |s| + int = "#{str}" + tmp1 = $1 if s =~ /inet addr:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + tmp2 = $1 if s =~ /(?:ether|HWaddr) (\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/ + if tmp1 != nil && tmp2 != nil && int != "lo" + test["ipaddress_" + int] = tmp1 + test["macaddress_" + int] = tmp2 + int = nil + tmp1 = nil + tmp2 = nil + end } test.each{|name,fact| + Facter.add(name) do + confine :kernel => :linux + setcode do + fact + end + end + } + } +end + +if Facter.kernel == "FreeBSD" || Facter.kernel == "OpenBSD" || Facter.kernel == "NetBSD" + + output = %x{/sbin/ifconfig -a} + int = nil + output.scan(/^(\w+)(\d+):/) { |str| + output_int = %x{/sbin/ifconfig #{str}} + int = "#{str}" + tmp1 = nil + tmp2 = nil + test = {} + output_int.each { |s| + int = "#{str}" + tmp1 = $1 if s =~ /inet ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + tmp2 = $1 if s =~ /(?:ether|lladdr)\s+(\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ + if tmp1 != nil && tmp2 != nil && int != "lo" + test["ipaddress_" + int] = tmp1 + test["macaddress_" + int] = tmp2 + int = nil + tmp1 = nil + tmp2 = nil + end + } + test.each{|name,fact| Facter.add(name) do confine :kernel => :linux setcode do fact end end + } } end + From dce624579b327963cc1619e93020d3ccb59995bd Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sun, 16 Sep 2007 10:26:45 +1000 Subject: [PATCH 0145/3753] Revert "Adjusted :kernel confine to make it more in line with others" This reverts commit c5e6f602ae71d43ec58b65ec6b2f4f540bc27649. --- CHANGELOG | 2 +- lib/facter.rb | 2 +- lib/facter/ipmess.rb | 24 ++++++++++++------------ 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index e5939a3273..43d3dac10b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,7 +2,7 @@ Added support to return multiple interfaces and their IP addresses and MAC addressess as facts. Returns interface_interfacename and macaddress_interfacename. Existing ipaddress and macaddress facts are - unchanged and still returned. Supports bith Linux and BSD. Closes #6. + unchanged and still returned. Currently Linux only. Closes #6. Added macaddress fact support for FreeBSD and OpenBSD - closes #37 diff --git a/lib/facter.rb b/lib/facter.rb index 802f848caf..e71eb0c170 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -1004,7 +1004,7 @@ def self.loadfacts end end Facter.add(:ipaddress) do - confine :kernel => %w{NetBSD} + confine :kernel => %w{NetBSD} setcode do ip = nil output = %x{/sbin/ifconfig -a} diff --git a/lib/facter/ipmess.rb b/lib/facter/ipmess.rb index 777084f271..abee8a280b 100644 --- a/lib/facter/ipmess.rb +++ b/lib/facter/ipmess.rb @@ -44,7 +44,7 @@ confine :kernel => :linux setcode do fact - end + end end } } @@ -52,16 +52,16 @@ if Facter.kernel == "FreeBSD" || Facter.kernel == "OpenBSD" || Facter.kernel == "NetBSD" - output = %x{/sbin/ifconfig -a} - int = nil - output.scan(/^(\w+)(\d+):/) { |str| - output_int = %x{/sbin/ifconfig #{str}} - int = "#{str}" - tmp1 = nil - tmp2 = nil - test = {} - output_int.each { |s| - int = "#{str}" + output = %x{/sbin/ifconfig -a} + int = nil + output.scan(/^(\w+)(\d+):/) { |str| + output_int = %x{/sbin/ifconfig #{str}} + int = "#{str}" + tmp1 = nil + tmp2 = nil + test = {} + output_int.each { |s| + int = "#{str}" tmp1 = $1 if s =~ /inet ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ tmp2 = $1 if s =~ /(?:ether|lladdr)\s+(\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ if tmp1 != nil && tmp2 != nil && int != "lo" @@ -71,7 +71,7 @@ tmp1 = nil tmp2 = nil end - } + } test.each{|name,fact| Facter.add(name) do confine :kernel => :linux From b28ce1bbda37d93d0bfd780f85ff39366f380af9 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Tue, 18 Sep 2007 11:33:22 +1000 Subject: [PATCH 0146/3753] Added require for rdoc/ri/ri_paths to address Puppet #753 and Facter #40 --- bin/facter | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/facter b/bin/facter index 3543a0aa22..1dec66bbbe 100755 --- a/bin/facter +++ b/bin/facter @@ -49,6 +49,7 @@ require 'facter' $haveusage = true begin + require 'rdoc/ri/ri_paths' require 'rdoc/usage' rescue Exception $haveusage = false From 57c76dd4e72cc090ed5e96947f1926462338c5cb Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Tue, 18 Sep 2007 13:06:39 +1000 Subject: [PATCH 0147/3753] Updated CHANGELOG --- CHANGELOG | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 43d3dac10b..7f95a1e77a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ 1.3.?: + Fixed Rdoc::usage bug on CentOS 5 - closed Puppet #753 and Facter #40 + Added support to return multiple interfaces and their IP addresses and MAC addressess as facts. Returns interface_interfacename and macaddress_interfacename. Existing ipaddress and macaddress facts are From 00ab1f327f287795eb0dca49139abfc8574e5d37 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Mon, 24 Sep 2007 08:59:28 +0200 Subject: [PATCH 0148/3753] Removing the package hosts, so packages are no longer created at all --- Rakefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Rakefile b/Rakefile index 9aff1679c0..4cef94fd8e 100644 --- a/Rakefile +++ b/Rakefile @@ -24,8 +24,8 @@ project = Rake::RedLabProject.new("facter") do |p| 'etc/*' ] - p.epmhosts = %w{culain} - p.rpmhost = "fedora1" + #p.epmhosts = %w{culain} + #p.rpmhost = "fedora1" #p.sunpkghost = "sol10b" end From 4d83f6f4cae0faf839235044417cc0bf0e93279a Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Mon, 24 Sep 2007 09:00:23 +0200 Subject: [PATCH 0149/3753] Updating version in changelog --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 7f95a1e77a..fdc5bbaeb0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,4 @@ -1.3.?: +1.3.8: Fixed Rdoc::usage bug on CentOS 5 - closed Puppet #753 and Facter #40 Added support to return multiple interfaces and their IP addresses and From 7f1c84014d349e544ab5c7e06489f5b8c24dc344 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Mon, 24 Sep 2007 09:01:38 +0200 Subject: [PATCH 0150/3753] Updated to version 1.3.8 --- lib/facter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter.rb b/lib/facter.rb index e71eb0c170..daa9efab0c 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -21,7 +21,7 @@ class Facter include Comparable include Enumerable - FACTERVERSION = '1.3.7' + FACTERVERSION = '1.3.8' # = Facter # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. From 74621b50655db2e91a850946150557e3cda411cc Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Mon, 24 Sep 2007 09:01:38 +0200 Subject: [PATCH 0151/3753] Updated to version 1.3.8 --- conf/redhat/facter.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index 267559ab08..4a9f2b4e2d 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -5,7 +5,7 @@ Summary: Ruby module for collecting simple facts about a host operating system Name: facter -Version: 1.3.7 +Version: 1.3.8 Release: 1%{?dist} License: GPL Group: System Environment/Base From be7c30b60414144d4238263fed5586283b30ebea Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Mon, 5 Nov 2007 20:40:31 +1100 Subject: [PATCH 0152/3753] Fixed ticket #44 --- CHANGELOG | 4 ++++ lib/facter/ipmess.rb | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index fdc5bbaeb0..998fc8ea0c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,7 @@ +?: + Fixed ticket #44 and allowed support for Xen multiple interfaces and aliased + interfaces. Supports both Linux and *BSD. + 1.3.8: Fixed Rdoc::usage bug on CentOS 5 - closed Puppet #753 and Facter #40 diff --git a/lib/facter/ipmess.rb b/lib/facter/ipmess.rb index abee8a280b..349d523860 100644 --- a/lib/facter/ipmess.rb +++ b/lib/facter/ipmess.rb @@ -21,7 +21,7 @@ output = %x{/sbin/ifconfig -a} int = nil - output.scan(/^(\w+)(\d+)/) { |str| + output.scan(/^(\w+)(\.|:?)(\d+)/) { |str| output_int = %x{/sbin/ifconfig #{str}} int = "#{str}" tmp1 = nil @@ -54,7 +54,7 @@ output = %x{/sbin/ifconfig -a} int = nil - output.scan(/^(\w+)(\d+):/) { |str| + output.scan(/^(\w+)(\.|:?)(\d+):/) { |str| output_int = %x{/sbin/ifconfig #{str}} int = "#{str}" tmp1 = nil From 85fbf8f5dca8ff144122b3b6ef685ad0d6c08a19 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Thu, 8 Nov 2007 08:20:37 +1100 Subject: [PATCH 0153/3753] Added index to imess.rb fixing Ticket #43. --- CHANGELOG | 2 ++ lib/facter/ipmess.rb | 34 +++++++++++++++++++--------------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 998fc8ea0c..0e63e132b8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,8 @@ Fixed ticket #44 and allowed support for Xen multiple interfaces and aliased interfaces. Supports both Linux and *BSD. + Added interfaces fact to add as index for ip/MAC address facts + 1.3.8: Fixed Rdoc::usage bug on CentOS 5 - closed Puppet #753 and Facter #40 diff --git a/lib/facter/ipmess.rb b/lib/facter/ipmess.rb index 349d523860..919d9448d0 100644 --- a/lib/facter/ipmess.rb +++ b/lib/facter/ipmess.rb @@ -17,18 +17,24 @@ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA # +Facter.add(:interfaces) do + setcode do + output = %x{/sbin/ifconfig -a} + int = nil + int = output.scan(/(^\w+[.:]?\d+)/).join(" ") + end +end + if Facter.kernel == "Linux" - output = %x{/sbin/ifconfig -a} - int = nil - output.scan(/^(\w+)(\.|:?)(\d+)/) { |str| - output_int = %x{/sbin/ifconfig #{str}} - int = "#{str}" + interfaces = nil + interfaces = Facter.interfaces.split(" ") + interfaces.each do |int| + output_int = %x{/sbin/ifconfig #{int}} tmp1 = nil tmp2 = nil test = {} output_int.each { |s| - int = "#{str}" tmp1 = $1 if s =~ /inet addr:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ tmp2 = $1 if s =~ /(?:ether|HWaddr) (\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/ if tmp1 != nil && tmp2 != nil && int != "lo" @@ -38,7 +44,7 @@ tmp1 = nil tmp2 = nil end - } + } test.each{|name,fact| Facter.add(name) do confine :kernel => :linux @@ -47,21 +53,19 @@ end end } - } + end end if Facter.kernel == "FreeBSD" || Facter.kernel == "OpenBSD" || Facter.kernel == "NetBSD" - output = %x{/sbin/ifconfig -a} - int = nil - output.scan(/^(\w+)(\.|:?)(\d+):/) { |str| - output_int = %x{/sbin/ifconfig #{str}} - int = "#{str}" + interfaces = nil + interfaces = Facter.interfaces.split(" ") + interfaces.each do |int| + output_int = %x{/sbin/ifconfig #{int}} tmp1 = nil tmp2 = nil test = {} output_int.each { |s| - int = "#{str}" tmp1 = $1 if s =~ /inet ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ tmp2 = $1 if s =~ /(?:ether|lladdr)\s+(\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ if tmp1 != nil && tmp2 != nil && int != "lo" @@ -80,6 +84,6 @@ end end } - } + end end From 2af364c1837f4aacfe1aab5b78d238e3a997f099 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sun, 9 Dec 2007 19:38:04 +1100 Subject: [PATCH 0154/3753] Added Mandrake support for operatingsystem fact - closed ticket #47 Fixed ticket #45 Added netmask.rb closing ticket #46 --- CHANGELOG | 6 +++++ lib/facter.rb | 2 ++ lib/facter/manufacturer.rb | 2 +- lib/facter/netmask.rb | 46 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 lib/facter/netmask.rb diff --git a/CHANGELOG b/CHANGELOG index 0e63e132b8..c51853b3fb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,12 @@ Added interfaces fact to add as index for ip/MAC address facts + Added Mandrake support for operatingsystem fact - closed ticket #47 + + Fixed ticket #45 + + Added netmask.rb closing ticket #46 + 1.3.8: Fixed Rdoc::usage bug on CentOS 5 - closed Puppet #753 and Facter #40 diff --git a/lib/facter.rb b/lib/facter.rb index daa9efab0c..1c8e8ff727 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -656,6 +656,8 @@ def self.loadfacts "Fedora" elsif FileTest.exists?("/etc/mandriva-release") "Mandriva" + elsif FileTest.exists?("/etc/mandrake-release") + "Mandrake" elsif FileTest.exists?("/etc/redhat-release") txt = File.read("/etc/redhat-release") if txt =~ /centos/i diff --git a/lib/facter/manufacturer.rb b/lib/facter/manufacturer.rb index be36871067..90075392bd 100644 --- a/lib/facter/manufacturer.rb +++ b/lib/facter/manufacturer.rb @@ -21,7 +21,7 @@ def self.dmi_find_system_info(name) unless type next end - @data[type][$1] = $2 + @data[type][$1] = $2.strip end end end diff --git a/lib/facter/netmask.rb b/lib/facter/netmask.rb new file mode 100644 index 0000000000..1feb4062f4 --- /dev/null +++ b/lib/facter/netmask.rb @@ -0,0 +1,46 @@ +# netmask.rb -- find the netmask of the primary ipaddress +# Copyright (C) 2007 David Schmitt +# Copyright (C) 2007 Mark 'phips' Phillips +# See LICENSE for the full license granted to you. +# idea and originial source by Mark 'phips' Phillips + +def get_netmask + netmask = nil; + ipregex = %r{(\d{1,3}\.){3}\d{1,3}} + + ops = nil + case Facter.kernel + when 'Linux' + ops = { + :ifconfig => '/sbin/ifconfig', + :regex => %r{\s+ inet\saddr: #{Facter.ipaddress} .*? Mask: (#{ipregex})}x, + :munge => nil, + } + when 'SunOS' + ops = { + :ifconfig => '/usr/sbin/ifconfig -a', + :regex => %r{\s+ inet\s+? #{Facter.ipaddress} \+? mask (\w{8})}x, + :munge => Proc.new { |mask| mask.scan(/../).collect do |byte| byte.to_i(16) end.join('.') } + } + end + + %x{#{ops[:ifconfig]}}.split(/\n/).collect do |line| + matches = line.match(ops[:regex]) + if !matches.nil? + if ops[:munge].nil? + netmask = matches[1] + else + netmask = ops[:munge].call(matches[1]) + end + end + end + netmask +end + +Facter.add("netmask") do + confine :kernel => [ :sunos, :linux ] + setcode do + get_netmask + end +end + From d7d82fc7f0d19a819cf86cac11a89c0940457d79 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sat, 22 Dec 2007 15:10:02 +1100 Subject: [PATCH 0155/3753] Fixed ticket #48 - CentOS operatingsystemrelease fact now reporting correct value --- CHANGELOG | 2 ++ lib/facter.rb | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index c51853b3fb..b9c653e940 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ ?: + Fixed ticket #48 - operatingsystemrelease for CentOS + Fixed ticket #44 and allowed support for Xen multiple interfaces and aliased interfaces. Supports both Linux and *BSD. diff --git a/lib/facter.rb b/lib/facter.rb index 1c8e8ff727..0d999fa9d8 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -691,7 +691,7 @@ def self.loadfacts end Facter.add(:operatingsystemrelease) do - confine :operatingsystem => :redhat + confine :operatingsystem => %w{RedHat CentOS} setcode do File::open("/etc/redhat-release", "r") do |f| line = f.readline.chomp From b3962ef307678d1be70ece96a284fdd2d63b064d Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sun, 13 Jan 2008 01:53:23 +1100 Subject: [PATCH 0156/3753] Fixed ticket #50 - added selinux facts Modified operatingsystemrelease fact behaviour for CentOS --- lib/facter.rb | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/facter.rb b/lib/facter.rb index 0d999fa9d8..15dd10801d 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -691,7 +691,7 @@ def self.loadfacts end Facter.add(:operatingsystemrelease) do - confine :operatingsystem => %w{RedHat CentOS} + confine :operatingsystem => %w{RedHat} setcode do File::open("/etc/redhat-release", "r") do |f| line = f.readline.chomp @@ -704,6 +704,16 @@ def self.loadfacts end end + Facter.add(:operatingsystemrelease) do + confine :operatingsystem => %w{CentOS} + setcode do + release = Resolution.exec('rpm -q centos-release') + if release =~ /release-(\d+)/ + $1 + end + end + end + Facter.add(:operatingsystemrelease) do setcode do Facter[:kernelrelease].value end end From 96cf3d6f46e1ca3921c632cd331fb519acb62703 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sat, 2 Feb 2008 17:04:41 +1100 Subject: [PATCH 0157/3753] Added Debian release version support --- lib/facter.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/facter.rb b/lib/facter.rb index 15dd10801d..e2ab905c0f 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -714,6 +714,16 @@ def self.loadfacts end end + Facter.add(:operatingsystemrelease) do + confine :operatingsystem => %w{Debian} + setcode do + release = Resolution.exec('cat /proc/version') + if release =~ /\(Debian (\d+.\d+).\d+-\d+\)/ + $1 + end + end + end + Facter.add(:operatingsystemrelease) do setcode do Facter[:kernelrelease].value end end From ecc1f0ca4f5f91c6e3aec6cfb3012687b7b97563 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sun, 3 Feb 2008 04:24:40 +1100 Subject: [PATCH 0158/3753] Added Ubuntu operatingsystem and operatingsystemrelease fact support --- lib/facter.rb | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/facter.rb b/lib/facter.rb index e2ab905c0f..b87805a78e 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -648,7 +648,9 @@ def self.loadfacts Facter.add(:operatingsystem) do confine :kernel => :linux setcode do - if FileTest.exists?("/etc/debian_version") + if Facter.lsbdistid == "Ubuntu" + "Ubuntu" + elsif FileTest.exists?("/etc/debian_version") "Debian" elsif FileTest.exists?("/etc/gentoo-release") "Gentoo" @@ -724,6 +726,16 @@ def self.loadfacts end end + Facter.add(:operatingsystemrelease) do + confine :operatingsystem => %w{Ubuntu} + setcode do + release = Resolution.exec('cat /etc/issue') + if release =~ /Ubuntu (\d+.\d+)/ + $1 + end + end + end + Facter.add(:operatingsystemrelease) do setcode do Facter[:kernelrelease].value end end From 2b0679994e73518c767c559fd1541767c2d5bad9 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sun, 3 Feb 2008 04:32:55 +1100 Subject: [PATCH 0159/3753] Revert "Fixed ticket #50 - added selinux facts" This reverts commit b3962ef307678d1be70ece96a284fdd2d63b064d. Conflicts: lib/facter.rb --- lib/facter.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/facter.rb b/lib/facter.rb index b87805a78e..2e39431754 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -707,6 +707,7 @@ def self.loadfacts end Facter.add(:operatingsystemrelease) do +<<<<<<< HEAD:lib/facter.rb confine :operatingsystem => %w{CentOS} setcode do release = Resolution.exec('rpm -q centos-release') @@ -737,6 +738,8 @@ def self.loadfacts end Facter.add(:operatingsystemrelease) do +======= +>>>>>>> b3962ef... Fixed ticket #50 - added selinux facts:lib/facter.rb setcode do Facter[:kernelrelease].value end end From 64f9fe9a23460f7afd2d18bce8ef2a46f5e795f6 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sun, 3 Feb 2008 04:45:11 +1100 Subject: [PATCH 0160/3753] Fixed conflict merge --- lib/facter.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/facter.rb b/lib/facter.rb index 2e39431754..b87805a78e 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -707,7 +707,6 @@ def self.loadfacts end Facter.add(:operatingsystemrelease) do -<<<<<<< HEAD:lib/facter.rb confine :operatingsystem => %w{CentOS} setcode do release = Resolution.exec('rpm -q centos-release') @@ -738,8 +737,6 @@ def self.loadfacts end Facter.add(:operatingsystemrelease) do -======= ->>>>>>> b3962ef... Fixed ticket #50 - added selinux facts:lib/facter.rb setcode do Facter[:kernelrelease].value end end From 4bb9ed422c48464188e61cbdd58f9a721932129c Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Thu, 7 Feb 2008 15:26:46 +1100 Subject: [PATCH 0161/3753] Added support for multiple interfaces, macaddress and netmask facts for Linux, *BSD, and Solaris --- lib/facter/ipmess.rb | 62 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 57 insertions(+), 5 deletions(-) diff --git a/lib/facter/ipmess.rb b/lib/facter/ipmess.rb index 919d9448d0..b81048a3b0 100644 --- a/lib/facter/ipmess.rb +++ b/lib/facter/ipmess.rb @@ -18,6 +18,7 @@ # Facter.add(:interfaces) do + confine :kernel => [ :freebsd, :openbsd, :netbsd, :linux ] setcode do output = %x{/sbin/ifconfig -a} int = nil @@ -25,6 +26,15 @@ end end +Facter.add(:interfaces) do + confine :kernel => :sunos + setcode do + output = %x{/usr/sbin/ifconfig -a} + int = nil + int = output.scan(/(^\w+[.:]?\d+)/).join(" ") + end +end + if Facter.kernel == "Linux" interfaces = nil @@ -33,16 +43,20 @@ output_int = %x{/sbin/ifconfig #{int}} tmp1 = nil tmp2 = nil + tmp3 = nil test = {} output_int.each { |s| tmp1 = $1 if s =~ /inet addr:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ - tmp2 = $1 if s =~ /(?:ether|HWaddr) (\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/ - if tmp1 != nil && tmp2 != nil && int != "lo" + tmp2 = $1 if s =~ /(?:ether|HWaddr)\s+(\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/ + tmp3 = $1 if s =~ /Mask:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + if tmp1 != nil && tmp2 != nil && tmp3 != nil && int != "lo" test["ipaddress_" + int] = tmp1 test["macaddress_" + int] = tmp2 + test["netmask_" + int] = tmp3 int = nil tmp1 = nil tmp2 = nil + tmp3 = nil end } test.each{|name,fact| @@ -64,21 +78,25 @@ output_int = %x{/sbin/ifconfig #{int}} tmp1 = nil tmp2 = nil + tmp3 = nil test = {} output_int.each { |s| - tmp1 = $1 if s =~ /inet ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + tmp1 = $1 if s =~ /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ tmp2 = $1 if s =~ /(?:ether|lladdr)\s+(\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ - if tmp1 != nil && tmp2 != nil && int != "lo" + tmp3 = $1 if s =~ /netmask\s+(\w{10})/ + if tmp1 != nil && tmp2 != nil && tmp3 != nil && int != "lo" test["ipaddress_" + int] = tmp1 test["macaddress_" + int] = tmp2 + test["netmask_" + int] = tmp3 int = nil tmp1 = nil tmp2 = nil + tmp3 = nil end } test.each{|name,fact| Facter.add(name) do - confine :kernel => :linux + confine :kernel => [ :freebsd, :openbsd, :netbsd ] setcode do fact end @@ -87,3 +105,37 @@ end end +if Facter.kernel == "SunOS" + + interfaces = nil + interfaces = Facter.interfaces.split(" ") + interfaces.each do |int| + output_int = %x{/usr/sbin/ifconfig #{int}} + tmp1 = nil + tmp2 = nil + tmp3 = nil + test = {} + output_int.each { |s| + tmp1 = $1 if s =~ /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + tmp2 = $1 if s =~ /(?:ether|lladdr)\s+(\w?\w:\w?\w:\w?\w:\w?\w:\w?\w:\w?\w)/ + tmp3 = $1 if s =~ /netmask\s+(\w{8})/ + if tmp1 != nil && tmp2 != nil && tmp3 != nil && int != "lo" + test["ipaddress_" + int] = tmp1 + test["macaddress_" + int] = tmp2 + test["netmask_" + int] = tmp3 + int = nil + tmp1 = nil + tmp2 = nil + tmp3 = nil + end + } + test.each{|name,fact| + Facter.add(name) do + confine :kernel => [ :sunos ] + setcode do + fact + end + end + } + end +end From df4636a7a8a20d94b6a1ec8577bff61456e09975 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Fri, 8 Feb 2008 14:14:24 +1100 Subject: [PATCH 0162/3753] Split out facts from facter.rb and moved all support code to util --- lib/facter.rb | 516 ------------------------------------- lib/facter/ipmess.rb | 34 ++- lib/facter/macosx.rb | 32 +-- lib/facter/manufacturer.rb | 53 ++-- lib/facter/memory.rb | 39 +-- lib/facter/netmask.rb | 23 +- lib/facter/processor.rb | 2 +- 7 files changed, 53 insertions(+), 646 deletions(-) diff --git a/lib/facter.rb b/lib/facter.rb index b87805a78e..65ea57b444 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -610,522 +610,6 @@ def self.loadfacts end end - Facter.add(:kernel) do - setcode 'uname -s' - end - - Facter.add(:kernelrelease) do - setcode 'uname -r' - end - - { "LSBRelease" => %r{^LSB Version:\t(.*)$}, - "LSBDistId" => %r{^Distributor ID:\t(.*)$}, - "LSBDistRelease" => %r{^Release:\t(.*)$}, - "LSBDistDescription" => %r{^Description:\t(.*)$}, - "LSBDistCodeName" => %r{^Codename:\t(.*)$} - }.each do |fact, pattern| - Facter.add(fact) do - setcode do - unless defined?(@@lsbdata) and defined?(@@lsbtime) and (Time.now.to_i - @@lsbtime.to_i < 5) - type = nil - @@lsbtime = Time.now - @@lsbdata = Resolution.exec('lsb_release -a 2>/dev/null') - end - if pattern.match(@@lsbdata) - $1 - else - nil - end - end - end - end - - Facter.add(:operatingsystem) do - confine :kernel => :sunos - setcode do "Solaris" end - end - - Facter.add(:operatingsystem) do - confine :kernel => :linux - setcode do - if Facter.lsbdistid == "Ubuntu" - "Ubuntu" - elsif FileTest.exists?("/etc/debian_version") - "Debian" - elsif FileTest.exists?("/etc/gentoo-release") - "Gentoo" - elsif FileTest.exists?("/etc/fedora-release") - "Fedora" - elsif FileTest.exists?("/etc/mandriva-release") - "Mandriva" - elsif FileTest.exists?("/etc/mandrake-release") - "Mandrake" - elsif FileTest.exists?("/etc/redhat-release") - txt = File.read("/etc/redhat-release") - if txt =~ /centos/i - "CentOS" - else - "RedHat" - end - elsif FileTest.exists?("/etc/SuSE-release") - "SuSE" - end - end - end - - Facter.add(:operatingsystem) do - # Default to just returning the kernel as the operating system - setcode do Facter[:kernel].value end - end - - Facter.add(:operatingsystemrelease) do - confine :operatingsystem => :fedora - setcode do - File::open("/etc/fedora-release", "r") do |f| - line = f.readline.chomp - if line =~ /\(Rawhide\)$/ - "Rawhide" - elsif line =~ /release (\d+)/ - $1 - end - end - end - end - - Facter.add(:operatingsystemrelease) do - confine :operatingsystem => %w{RedHat} - setcode do - File::open("/etc/redhat-release", "r") do |f| - line = f.readline.chomp - if line =~ /\(Rawhide\)$/ - "Rawhide" - elsif line =~ /release (\d+)/ - $1 - end - end - end - end - - Facter.add(:operatingsystemrelease) do - confine :operatingsystem => %w{CentOS} - setcode do - release = Resolution.exec('rpm -q centos-release') - if release =~ /release-(\d+)/ - $1 - end - end - end - - Facter.add(:operatingsystemrelease) do - confine :operatingsystem => %w{Debian} - setcode do - release = Resolution.exec('cat /proc/version') - if release =~ /\(Debian (\d+.\d+).\d+-\d+\)/ - $1 - end - end - end - - Facter.add(:operatingsystemrelease) do - confine :operatingsystem => %w{Ubuntu} - setcode do - release = Resolution.exec('cat /etc/issue') - if release =~ /Ubuntu (\d+.\d+)/ - $1 - end - end - end - - Facter.add(:operatingsystemrelease) do - setcode do Facter[:kernelrelease].value end - end - - Facter.add(:hardwaremodel) do - setcode 'uname -m' - end - - Facter.add(:architecture) do - confine :kernel => :linux - setcode do - model = Facter.hardwaremodel - case model - # most linuxen use "x86_64" - when 'x86_64': - Facter.operatingsystem == "Debian" ? "amd64" : model; - when /(i[3456]86|pentium)/: "i386" - else - model - end - end - end - - Facter.add(:Cfkey) do - setcode do - value = nil - ["/usr/local/etc/cfkey.pub", - "/etc/cfkey.pub", - "/var/cfng/keys/localhost.pub", - "/var/cfengine/ppkeys/localhost.pub", - "/var/lib/cfengine/ppkeys/localhost.pub", - "/var/lib/cfengine2/ppkeys/localhost.pub" - ].each { |file| - if FileTest.file?(file) - File.open(file) { |openfile| - value = openfile.readlines.reject { |line| - line =~ /PUBLIC KEY/ - }.collect { |line| - line.chomp - }.join("") - } - end - if value - break - end - } - - value - end - end - - Facter.add(:domain) do - setcode do - # First force the hostname to be checked - Facter.hostname - - # Now check to see if it set the domain - if defined? $domain and ! $domain.nil? - $domain - else - nil - end - end - end - # Look for the DNS domain name command first. - Facter.add(:domain) do - setcode do - domain = Resolution.exec('dnsdomainname') or nil - # make sure it's a real domain - if domain and domain =~ /.+\..+/ - domain - else - nil - end - end - end - Facter.add(:domain) do - setcode do - domain = Resolution.exec('domainname') or nil - # make sure it's a real domain - if domain and domain =~ /.+\..+/ - domain - else - nil - end - end - end - Facter.add(:domain) do - setcode do - value = nil - if FileTest.exists?("/etc/resolv.conf") - File.open("/etc/resolv.conf") { |file| - # is the domain set? - file.each { |line| - if line =~ /domain\s+(\S+)/ - value = $1 - break - end - } - } - ! value and File.open("/etc/resolv.conf") { |file| - # is the search path set? - file.each { |line| - if line =~ /search\s+(\S+)/ - value = $1 - break - end - } - } - value - else - nil - end - end - end - Facter.add(:hostname) do - setldapname "cn" - setcode do - hostname = nil - name = Resolution.exec('hostname') or nil - if name - if name =~ /^([\w-]+)\.(.+)$/ - hostname = $1 - # the Domain class uses this - $domain = $2 - else - hostname = name - end - hostname - else - nil - end - end - end - - Facter.add(:fqdn) do - setcode do - host = Facter.value(:hostname) - domain = Facter.value(:domain) - if host and domain - [host, domain].join(".") - else - nil - end - end - end - - Facter.add(:ipaddress) do - setldapname "iphostnumber" - setcode do - require 'resolv' - - begin - if hostname = Facter.hostname - ip = Resolv.getaddress(hostname) - unless ip == "127.0.0.1" - ip - end - else - nil - end - rescue Resolv::ResolvError - nil - rescue NoMethodError # i think this is a bug in resolv.rb? - nil - end - end - end - Facter.add(:ipaddress) do - setcode do - if hostname = Facter.hostname - # we need Hostname to exist for this to work - host = nil - if host = Resolution.exec("host #{hostname}") - host = host.chomp.split(/\s/) - if defined? list[-1] and - list[-1] =~ /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ - list[-1] - end - else - nil - end - else - nil - end - end - end - - ["/etc/ssh","/usr/local/etc/ssh","/etc","/usr/local/etc"].each { |dir| - {"SSHDSAKey" => "ssh_host_dsa_key.pub", - "SSHRSAKey" => "ssh_host_rsa_key.pub"}.each { |name,file| - Facter.add(name) do - setcode do - value = nil - filepath = File.join(dir,file) - if FileTest.file?(filepath) - begin - File.open(filepath) { |f| - value = f.read.chomp.split(/\s+/)[1] - } - rescue - value = nil - end - end - value - end # end of proc - end # end of add - } # end of hash each - } # end of dir each - - Facter.add(:uniqueid) do - setcode 'hostid', '/bin/sh' - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE Debian Gentoo} - end - - Facter.add(:hardwareisa) do - setcode 'uname -p', '/bin/sh' - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE Debian Gentoo FreeBSD OpenBSD NetBSD} - end - - Facter.add(:macaddress) do - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE Debian Gentoo} - setcode do - ether = [] - output = %x{/sbin/ifconfig -a} - output.each {|s| - ether.push($1) if s =~ /(?:ether|HWaddr) (\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/ - } - ether[0] - end - end - - Facter.add(:macaddress) do - confine :operatingsystem => %w{FreeBSD OpenBSD} - setcode do - ether = [] - output = %x{/sbin/ifconfig} - output.each {|s| - if s =~ /(?:ether|lladdr)\s+(\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ - ether.push($1) - end - } - ether[0] - end - end - - Facter.add(:macaddress) do - confine :kernel => :darwin - setcode do - ether = nil - output = %x{/sbin/ifconfig} - - output.split(/^\S/).each { |str| - if str =~ /10baseT/ # we're wired - str =~ /ether (\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ - ether = $1 - end - } - - ether - end - end - - Facter.add(:ipaddress) do - confine :kernel => :linux - setcode do - ip = nil - output = %x{/sbin/ifconfig} - - output.split(/^\S/).each { |str| - if str =~ /inet addr:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ - tmp = $1 - unless tmp =~ /127\./ - ip = tmp - break - end - end - } - - ip - end - end - Facter.add(:ipaddress) do - confine :kernel => %w{FreeBSD OpenBSD solaris} - setcode do - ip = nil - output = %x{/sbin/ifconfig} - - output.split(/^\S/).each { |str| - if str =~ /inet ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ - tmp = $1 - unless tmp =~ /127\./ - ip = tmp - break - end - end - } - - ip - end - end - Facter.add(:ipaddress) do - confine :kernel => %w{NetBSD} - setcode do - ip = nil - output = %x{/sbin/ifconfig -a} - - output.split(/^\S/).each { |str| - if str =~ /inet ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ - tmp = $1 - unless tmp =~ /127\./ - ip = tmp - break - end - end - } - - ip - end - end - Facter.add(:ipaddress) do - confine :kernel => %w{darwin} - setcode do - ip = nil - iface = "" - output = %x{/usr/sbin/netstat -rn} - if output =~ /^default\s*\S*\s*\S*\s*\S*\s*\S*\s*(\S*).*/ - iface = $1 - else - warn "Could not find a default route. Using first non-loopback interface" - end - if(iface != "") - output = %x{/sbin/ifconfig #{iface}} - else - output = %x{/sbin/ifconfig} - end - - output.split(/^\S/).each { |str| - if str =~ /inet ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ - tmp = $1 - unless tmp =~ /127\./ - ip = tmp - break - end - end - } - - ip - end - end - Facter.add(:hostname) do - confine :kernel => :darwin, :kernelrelease => "R7" - setcode do - %x{/usr/sbin/scutil --get LocalHostName} - end - end - Facter.add(:iphostnumber) do - confine :kernel => :darwin, :kernelrelease => "R6" - setcode do - %x{/usr/sbin/scutil --get LocalHostName} - end - end - Facter.add(:iphostnumber) do - confine :kernel => :darwin, :kernelrelease => "R6" - setcode do - ether = nil - output = %x{/sbin/ifconfig} - - output =~ /HWaddr (\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ - ether = $1 - - ether - end - end - - Facter.add(:ps) do - setcode do 'ps -ef' end - end - - Facter.add(:ps) do - confine :operatingsystem => %w{FreeBSD NetBSD OpenBSD Darwin} - setcode do 'ps -auxwww' end - end - - Facter.add(:id) do - #confine :kernel => %w{Solaris Linux} - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE Debian Gentoo} - setcode "whoami" - end - locals = [] # Now find all our loadable facts diff --git a/lib/facter/ipmess.rb b/lib/facter/ipmess.rb index 919d9448d0..b042073e4d 100644 --- a/lib/facter/ipmess.rb +++ b/lib/facter/ipmess.rb @@ -1,21 +1,19 @@ -# -# ipmess.rb -# Try to get additional Facts about the machine's network interfaces on Linux -# -# Original concept Copyright (C) 2007 psychedelys -# Update and *BSD support (C) 2007 James Turnbull -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation (version 2 of the License) -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA -# +## ipmess.rb +## Try to get additional Facts about the machine's network interfaces on Linux +## +## Original concept Copyright (C) 2007 psychedelys +## Update and *BSD support (C) 2007 James Turnbull +## +## This program is free software; you can redistribute it and/or +## modify it under the terms of the GNU General Public License +## as published by the Free Software Foundation (version 2 of the License) +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA Facter.add(:interfaces) do setcode do diff --git a/lib/facter/macosx.rb b/lib/facter/macosx.rb index 73339b7385..3c69823faf 100644 --- a/lib/facter/macosx.rb +++ b/lib/facter/macosx.rb @@ -22,37 +22,7 @@ # at this point in time. # In particular, Installed Software might be an interesting addition. -module Facter::Macosx - require 'thread' - require 'facter/util/plist' - - # JJM I'd really like to dynamically generate these methods - # by looking at the _name key of the _items dict for each _dataType - - def self.hardware_overview - # JJM Perhaps we should cache the XML data in a "class" level object. - top_level_plist = Plist::parse_xml %x{/usr/sbin/system_profiler -xml SPHardwareDataType} - system_hardware = top_level_plist[0]['_items'][0] - system_hardware.delete '_name' - system_hardware - end - - # SPSoftwareDataType - def self.os_overview - top_level_plist = Plist::parse_xml %x{/usr/sbin/system_profiler -xml SPSoftwareDataType} - os_stuff = top_level_plist[0]['_items'][0] - os_stuff.delete '_name' - os_stuff - end - - def self.sw_vers - ver = Hash.new - [ "productName", "productVersion", "buildVersion" ].each do |option| - ver["macosx_#{option}"] = %x{sw_vers -#{option}}.strip - end - ver - end -end +require 'facter/util/macosx' if Facter.kernel == "Darwin" Facter::Macosx.hardware_overview.each do |fact, value| diff --git a/lib/facter/manufacturer.rb b/lib/facter/manufacturer.rb index 90075392bd..67ca569f71 100644 --- a/lib/facter/manufacturer.rb +++ b/lib/facter/manufacturer.rb @@ -1,41 +1,20 @@ -# Info about the manufacturer -# - -module Facter::Manufacturer - def self.dmi_find_system_info(name) - return nil unless FileTest.exists?("/usr/sbin/dmidecode") - - # Do not run the command more than every five seconds. - unless defined?(@data) and defined?(@time) and (Time.now.to_i - @time.to_i < 5) - @data = {} - type = nil - @time = Time.now - # It's *much* easier to just parse the whole darn file than - # to just match a chunk of it. - %x{/usr/sbin/dmidecode 2>/dev/null}.split("\n").each do |line| - case line - when /^(\S.+)$/ - type = $1.chomp - @data[type] ||= {} - when /^\s+(\S.+): (\S.*)$/ - unless type - next - end - @data[type][$1] = $2.strip - end - end - end - - if data = @data["System Information"] - data[name] - else - nil - end - end -end - -# Add the facts to Facter +## manufacturer.rb +## Facts related to hardware manufacturer +## +## This program is free software; you can redistribute it and/or +## modify it under the terms of the GNU General Public License +## as published by the Free Software Foundation (version 2 of the License) +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA +## +require 'facter/util/manufacturer' + {:SerialNumber => "Serial Number", :Manufacturer => "Manufacturer", :ProductName=> "Product Name"}.each do |fact, name| diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb index 6e9b600c8b..c507089c5a 100644 --- a/lib/facter/memory.rb +++ b/lib/facter/memory.rb @@ -16,44 +16,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA -module Facter::Memory - require 'thread' - - def self.meminfo_number(tag) - memsize = "" - Thread::exclusive do - size, scale = [0, ""] - File.readlines("/proc/meminfo").each do |l| - size, scale = [$1.to_f, $2] if l =~ /^#{tag}:\s+(\d+)\s+(\S+)/ - # MemoryFree == memfree + cached + buffers - # (assume scales are all the same as memfree) - if tag == "MemFree" && - l =~ /^(?:Buffers|Cached):\s+(\d+)\s+(?:\S+)/ - size += $1.to_f - end - end - memsize = scale_number(size, scale) - end - - memsize - end - - def self.scale_number(size, multiplier) - suffixes = ['', 'kB', 'MB', 'GB', 'TB'] - - s = suffixes.shift - while s != multiplier - s = suffixes.shift - end - - while size > 1024.0 - size /= 1024.0 - s = suffixes.shift - end - - return "%.2f %s" % [size, s] - end -end +require 'facter/util/memory' {:MemorySize => "MemTotal", :MemoryFree => "MemFree", diff --git a/lib/facter/netmask.rb b/lib/facter/netmask.rb index 1feb4062f4..223d9e88a3 100644 --- a/lib/facter/netmask.rb +++ b/lib/facter/netmask.rb @@ -1,8 +1,21 @@ -# netmask.rb -- find the netmask of the primary ipaddress -# Copyright (C) 2007 David Schmitt -# Copyright (C) 2007 Mark 'phips' Phillips -# See LICENSE for the full license granted to you. -# idea and originial source by Mark 'phips' Phillips +## netmask.rb +## Find the netmask of the primary ipaddress +## Copyright (C) 2007 David Schmitt +## Copyright (C) 2007 Mark 'phips' Phillips +## +## idea and originial source by Mark 'phips' Phillips +## +## This program is free software; you can redistribute it and/or +## modify it under the terms of the GNU General Public License +## as published by the Free Software Foundation (version 2 of the License) +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA +## def get_netmask netmask = nil; diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb index 812bd99838..069e06e7e8 100644 --- a/lib/facter/processor.rb +++ b/lib/facter/processor.rb @@ -20,7 +20,7 @@ require 'thread' -if Facter.kernel == "Linux" +if Facter.value(:kernel) == "Linux" processor_num = -1 processor_list = [] Thread::exclusive do From c312df8f1c09513b93d559147c6dc3f18e0e45df Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Fri, 8 Feb 2008 16:14:26 +1100 Subject: [PATCH 0163/3753] Further updates to split facts and move support functions --- lib/facter/macosx.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/macosx.rb b/lib/facter/macosx.rb index 3c69823faf..aec454a5f4 100644 --- a/lib/facter/macosx.rb +++ b/lib/facter/macosx.rb @@ -24,7 +24,7 @@ require 'facter/util/macosx' -if Facter.kernel == "Darwin" +if Facter.value(:kernel) == "Darwin" Facter::Macosx.hardware_overview.each do |fact, value| Facter.add("sp_#{fact}") do confine :kernel => :darwin From a633aebab4dc4d07119a619c21cad6a719552083 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Fri, 8 Feb 2008 16:15:02 +1100 Subject: [PATCH 0164/3753] Added new files --- lib/facter/cfengine.rb | 43 +++++ lib/facter/kernel.rb | 46 +++++ lib/facter/lsb.rb | 38 ++++ lib/facter/networking.rb | 312 ++++++++++++++++++++++++++++++++ lib/facter/os.rb | 115 ++++++++++++ lib/facter/process.rb | 29 +++ lib/facter/ssh.rb | 37 ++++ lib/facter/util/macosx.rb | 50 +++++ lib/facter/util/manufacturer.rb | 49 +++++ lib/facter/util/memory.rb | 54 ++++++ 10 files changed, 773 insertions(+) create mode 100644 lib/facter/cfengine.rb create mode 100644 lib/facter/kernel.rb create mode 100644 lib/facter/lsb.rb create mode 100644 lib/facter/networking.rb create mode 100644 lib/facter/os.rb create mode 100644 lib/facter/process.rb create mode 100644 lib/facter/ssh.rb create mode 100644 lib/facter/util/macosx.rb create mode 100644 lib/facter/util/manufacturer.rb create mode 100644 lib/facter/util/memory.rb diff --git a/lib/facter/cfengine.rb b/lib/facter/cfengine.rb new file mode 100644 index 0000000000..6db24db9e8 --- /dev/null +++ b/lib/facter/cfengine.rb @@ -0,0 +1,43 @@ +## cfengine.rb +## Facts related to cfengine +## +## This program is free software; you can redistribute it and/or +## modify it under the terms of the GNU General Public License +## as published by the Free Software Foundation (version 2 of the License) +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA +## + + Facter.add(:Cfkey) do + setcode do + value = nil + ["/usr/local/etc/cfkey.pub", + "/etc/cfkey.pub", + "/var/cfng/keys/localhost.pub", + "/var/cfengine/ppkeys/localhost.pub", + "/var/lib/cfengine/ppkeys/localhost.pub", + "/var/lib/cfengine2/ppkeys/localhost.pub" + ].each { |file| + if FileTest.file?(file) + File.open(file) { |openfile| + value = openfile.readlines.reject { |line| + line =~ /PUBLIC KEY/ + }.collect { |line| + line.chomp + }.join("") + } + end + if value + break + end + } + + value + end + end + diff --git a/lib/facter/kernel.rb b/lib/facter/kernel.rb new file mode 100644 index 0000000000..68f887adab --- /dev/null +++ b/lib/facter/kernel.rb @@ -0,0 +1,46 @@ +## kernel.rb +## Facts related to the kernel, architecture and related +## +## This program is free software; you can redistribute it and/or +## modify it under the terms of the GNU General Public License +## as published by the Free Software Foundation (version 2 of the License) +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA +## + + Facter.add(:kernel) do + setcode 'uname -s' + end + + Facter.add(:kernelrelease) do + setcode 'uname -r' + end + + Facter.add(:hardwaremodel) do + setcode 'uname -m' + end + + Facter.add(:architecture) do + confine :kernel => :linux + setcode do + model = Facter.value(:hardwaremodel) + case model + # most linuxen use "x86_64" + when 'x86_64': + Facter.value(:operatingsystem) == "Debian" ? "amd64" : model; + when /(i[3456]86|pentium)/: "i386" + else + model + end + end + end + + Facter.add(:hardwareisa) do + setcode 'uname -p', '/bin/sh' + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE Debian Gentoo FreeBSD OpenBSD NetBSD} + end diff --git a/lib/facter/lsb.rb b/lib/facter/lsb.rb new file mode 100644 index 0000000000..c3722136d7 --- /dev/null +++ b/lib/facter/lsb.rb @@ -0,0 +1,38 @@ +## lsb.rb +## Facts related to Linux Standard Base (LSB) +## +## This program is free software; you can redistribute it and/or +## modify it under the terms of the GNU General Public License +## as published by the Free Software Foundation (version 2 of the License) +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA +## + +{ "LSBRelease" => %r{^LSB Version:\t(.*)$}, + "LSBDistId" => %r{^Distributor ID:\t(.*)$}, + "LSBDistRelease" => %r{^Release:\t(.*)$}, + "LSBDistDescription" => %r{^Description:\t(.*)$}, + "LSBDistCodeName" => %r{^Codename:\t(.*)$} +}.each do |fact, pattern| + + Facter.add(fact) do + setcode do + unless defined?(@@lsbdata) and defined?(@@lsbtime) and (Time.now.to_i - @@lsbtime.to_i < 5) + type = nil + @@lsbtime = Time.now + @@lsbdata = Facter::Resolution.exec('lsb_release -a 2>/dev/null') + end + + if pattern.match(@@lsbdata) + $1 + else + nil + end + end + end +end diff --git a/lib/facter/networking.rb b/lib/facter/networking.rb new file mode 100644 index 0000000000..cde2c46840 --- /dev/null +++ b/lib/facter/networking.rb @@ -0,0 +1,312 @@ +## networking.rb +## Facts related to networking +## +## This program is free software; you can redistribute it and/or +## modify it under the terms of the GNU General Public License +## as published by the Free Software Foundation (version 2 of the License) +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA +## + + Facter.add(:domain) do + setcode do + # First force the hostname to be checked + Facter.hostname + + # Now check to see if it set the domain + if defined? $domain and ! $domain.nil? + $domain + else + nil + end + end + end + # Look for the DNS domain name command first. + Facter.add(:domain) do + setcode do + domain = Facter::Resolution.exec('dnsdomainname') or nil + # make sure it's a real domain + if domain and domain =~ /.+\..+/ + domain + else + nil + end + end + end + Facter.add(:domain) do + setcode do + domain = Facter::Resolution.exec('domainname') or nil + # make sure it's a real domain + if domain and domain =~ /.+\..+/ + domain + else + nil + end + end + end + Facter.add(:domain) do + setcode do + value = nil + if FileTest.exists?("/etc/resolv.conf") + File.open("/etc/resolv.conf") { |file| + # is the domain set? + file.each { |line| + if line =~ /domain\s+(\S+)/ + value = $1 + break + end + } + } + ! value and File.open("/etc/resolv.conf") { |file| + # is the search path set? + file.each { |line| + if line =~ /search\s+(\S+)/ + value = $1 + break + end + } + } + value + else + nil + end + end + end + Facter.add(:hostname) do + setldapname "cn" + setcode do + hostname = nil + name = Facter::Resolution.exec('hostname') or nil + if name + if name =~ /^([\w-]+)\.(.+)$/ + hostname = $1 + # the Domain class uses this + $domain = $2 + else + hostname = name + end + hostname + else + nil + end + end + end + + Facter.add(:fqdn) do + setcode do + host = Facter.value(:hostname) + domain = Facter.value(:domain) + if host and domain + [host, domain].join(".") + else + nil + end + end + end + + Facter.add(:ipaddress) do + setldapname "iphostnumber" + setcode do + require 'resolv' + + begin + if hostname = Facter.value(:hostname) + ip = Resolv.getaddress(hostname) + unless ip == "127.0.0.1" + ip + end + else + nil + end + rescue Resolv::ResolvError + nil + rescue NoMethodError # i think this is a bug in resolv.rb? + nil + end + end + end + Facter.add(:ipaddress) do + setcode do + if hostname = Facter.value(:hostname) + # we need Hostname to exist for this to work + host = nil + if host = Facter::Resolution.exec("host #{hostname}") + host = host.chomp.split(/\s/) + if defined? list[-1] and + list[-1] =~ /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ + list[-1] + end + else + nil + end + else + nil + end + end + end + + Facter.add(:uniqueid) do + setcode 'hostid', '/bin/sh' + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE Debian Gentoo} + end + + Facter.add(:macaddress) do + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE Debian Gentoo} + setcode do + ether = [] + output = %x{/sbin/ifconfig -a} + output.each {|s| + ether.push($1) if s =~ /(?:ether|HWaddr) (\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/ + } + ether[0] + end + end + + Facter.add(:macaddress) do + confine :operatingsystem => %w{FreeBSD OpenBSD} + setcode do + ether = [] + output = %x{/sbin/ifconfig} + output.each {|s| + if s =~ /(?:ether|lladdr)\s+(\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ + ether.push($1) + end + } + ether[0] + end + end + + Facter.add(:macaddress) do + confine :kernel => :darwin + setcode do + ether = nil + output = %x{/sbin/ifconfig} + + output.split(/^\S/).each { |str| + if str =~ /10baseT/ # we're wired + str =~ /ether (\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ + ether = $1 + end + } + + ether + end + end + + Facter.add(:ipaddress) do + confine :kernel => :linux + setcode do + ip = nil + output = %x{/sbin/ifconfig} + + output.split(/^\S/).each { |str| + if str =~ /inet addr:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + tmp = $1 + unless tmp =~ /127\./ + ip = tmp + break + end + end + } + + ip + end + end + Facter.add(:ipaddress) do + confine :kernel => %w{FreeBSD OpenBSD solaris} + setcode do + ip = nil + output = %x{/sbin/ifconfig} + + output.split(/^\S/).each { |str| + if str =~ /inet ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + tmp = $1 + unless tmp =~ /127\./ + ip = tmp + break + end + end + } + + ip + end + end + Facter.add(:ipaddress) do + confine :kernel => %w{NetBSD} + setcode do + ip = nil + output = %x{/sbin/ifconfig -a} + + output.split(/^\S/).each { |str| + if str =~ /inet ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + tmp = $1 + unless tmp =~ /127\./ + ip = tmp + break + end + end + } + + ip + end + end + Facter.add(:ipaddress) do + confine :kernel => %w{darwin} + setcode do + ip = nil + iface = "" + output = %x{/usr/sbin/netstat -rn} + if output =~ /^default\s*\S*\s*\S*\s*\S*\s*\S*\s*(\S*).*/ + iface = $1 + else + warn "Could not find a default route. Using first non-loopback interface" + end + if(iface != "") + output = %x{/sbin/ifconfig #{iface}} + else + output = %x{/sbin/ifconfig} + end + + output.split(/^\S/).each { |str| + if str =~ /inet ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + tmp = $1 + unless tmp =~ /127\./ + ip = tmp + break + end + end + } + + ip + end + end + Facter.add(:hostname) do + confine :kernel => :darwin, :kernelrelease => "R7" + setcode do + %x{/usr/sbin/scutil --get LocalHostName} + end + end + Facter.add(:iphostnumber) do + confine :kernel => :darwin, :kernelrelease => "R6" + setcode do + %x{/usr/sbin/scutil --get LocalHostName} + end + end + Facter.add(:iphostnumber) do + confine :kernel => :darwin, :kernelrelease => "R6" + setcode do + ether = nil + output = %x{/sbin/ifconfig} + + output =~ /HWaddr (\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ + ether = $1 + + ether + end + end + diff --git a/lib/facter/os.rb b/lib/facter/os.rb new file mode 100644 index 0000000000..97d8093ef5 --- /dev/null +++ b/lib/facter/os.rb @@ -0,0 +1,115 @@ +## os.rb +## Facts related to operating systems and releases +## +## This program is free software; you can redistribute it and/or +## modify it under the terms of the GNU General Public License +## as published by the Free Software Foundation (version 2 of the License) +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA +## + + Facter.add(:operatingsystem) do + confine :kernel => :sunos + setcode do "Solaris" end + end + + Facter.add(:operatingsystem) do + confine :kernel => :linux + setcode do + if Facter.value(:lsbdistid) == "Ubuntu" + "Ubuntu" + elsif FileTest.exists?("/etc/debian_version") + "Debian" + elsif FileTest.exists?("/etc/gentoo-release") + "Gentoo" + elsif FileTest.exists?("/etc/fedora-release") + "Fedora" + elsif FileTest.exists?("/etc/mandriva-release") + "Mandriva" + elsif FileTest.exists?("/etc/mandrake-release") + "Mandrake" + elsif FileTest.exists?("/etc/redhat-release") + txt = File.read("/etc/redhat-release") + if txt =~ /centos/i + "CentOS" + else + "RedHat" + end + elsif FileTest.exists?("/etc/SuSE-release") + "SuSE" + end + end + end + + Facter.add(:operatingsystem) do + # Default to just returning the kernel as the operating system + setcode do Facter[:kernel].value end + end + + Facter.add(:operatingsystemrelease) do + confine :operatingsystem => :fedora + setcode do + File::open("/etc/fedora-release", "r") do |f| + line = f.readline.chomp + if line =~ /\(Rawhide\)$/ + "Rawhide" + elsif line =~ /release (\d+)/ + $1 + end + end + end + end + + Facter.add(:operatingsystemrelease) do + confine :operatingsystem => %w{RedHat} + setcode do + File::open("/etc/redhat-release", "r") do |f| + line = f.readline.chomp + if line =~ /\(Rawhide\)$/ + "Rawhide" + elsif line =~ /release (\d+)/ + $1 + end + end + end + end + + Facter.add(:operatingsystemrelease) do + confine :operatingsystem => %w{CentOS} + setcode do + release = Facter::Resolution.exec('rpm -q centos-release') + if release =~ /release-(\d+)/ + $1 + end + end + end + + Facter.add(:operatingsystemrelease) do + confine :operatingsystem => %w{Debian} + setcode do + release = Facter::Resolution.exec('cat /proc/version') + if release =~ /\(Debian (\d+.\d+).\d+-\d+\)/ + $1 + end + end + end + + Facter.add(:operatingsystemrelease) do + confine :operatingsystem => %w{Ubuntu} + setcode do + release = Facter::Resolution.exec('cat /etc/issue') + if release =~ /Ubuntu (\d+.\d+)/ + $1 + end + end + end + + Facter.add(:operatingsystemrelease) do + setcode do Facter[:kernelrelease].value end + end + diff --git a/lib/facter/process.rb b/lib/facter/process.rb new file mode 100644 index 0000000000..beeac47e99 --- /dev/null +++ b/lib/facter/process.rb @@ -0,0 +1,29 @@ +## process.rb +## Facts related to ps and processes +## +## This program is free software; you can redistribute it and/or +## modify it under the terms of the GNU General Public License +## as published by the Free Software Foundation (version 2 of the License) +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA +## + Facter.add(:ps) do + setcode do 'ps -ef' end + end + + Facter.add(:ps) do + confine :operatingsystem => %w{FreeBSD NetBSD OpenBSD Darwin} + setcode do 'ps -auxwww' end + end + + Facter.add(:id) do + #confine :kernel => %w{Solaris Linux} + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE Debian Gentoo} + setcode "whoami" + end + diff --git a/lib/facter/ssh.rb b/lib/facter/ssh.rb new file mode 100644 index 0000000000..29ea67ce55 --- /dev/null +++ b/lib/facter/ssh.rb @@ -0,0 +1,37 @@ +## ssh.rb +## Facts related to SSH +## +## This program is free software; you can redistribute it and/or +## modify it under the terms of the GNU General Public License +## as published by the Free Software Foundation (version 2 of the License) +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA +## + + ["/etc/ssh","/usr/local/etc/ssh","/etc","/usr/local/etc"].each { |dir| + {"SSHDSAKey" => "ssh_host_dsa_key.pub", + "SSHRSAKey" => "ssh_host_rsa_key.pub"}.each { |name,file| + Facter.add(name) do + setcode do + value = nil + filepath = File.join(dir,file) + if FileTest.file?(filepath) + begin + File.open(filepath) { |f| + value = f.read.chomp.split(/\s+/)[1] + } + rescue + value = nil + end + end + value + end # end of proc + end # end of add + } # end of hash each + } # end of dir each + diff --git a/lib/facter/util/macosx.rb b/lib/facter/util/macosx.rb new file mode 100644 index 0000000000..1e596d86f9 --- /dev/null +++ b/lib/facter/util/macosx.rb @@ -0,0 +1,50 @@ +## macosx.rb +## Support methods for Apple OSX facts +## +## Copyright (C) 2007 Jeff McCune +## Author: Jeff McCune +## +## This program is free software; you can redistribute it and/or +## modify it under the terms of the GNU General Public License +## as published by the Free Software Foundation (version 2 of the License) +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA +## + +module Facter::Macosx + require 'thread' + require 'facter/util/plist' + + # JJM I'd really like to dynamically generate these methods + # by looking at the _name key of the _items dict for each _dataType + + def self.hardware_overview + # JJM Perhaps we should cache the XML data in a "class" level object. + top_level_plist = Plist::parse_xml %x{/usr/sbin/system_profiler -xml SPHardwareDataType} + system_hardware = top_level_plist[0]['_items'][0] + system_hardware.delete '_name' + system_hardware + end + + # SPSoftwareDataType + def self.os_overview + top_level_plist = Plist::parse_xml %x{/usr/sbin/system_profiler -xml SPSoftwareDataType} + os_stuff = top_level_plist[0]['_items'][0] + os_stuff.delete '_name' + os_stuff + end + + def self.sw_vers + ver = Hash.new + [ "productName", "productVersion", "buildVersion" ].each do |option| + ver["macosx_#{option}"] = %x{sw_vers -#{option}}.strip + end + ver + end +end + diff --git a/lib/facter/util/manufacturer.rb b/lib/facter/util/manufacturer.rb new file mode 100644 index 0000000000..18db1c9a73 --- /dev/null +++ b/lib/facter/util/manufacturer.rb @@ -0,0 +1,49 @@ +## mamufacturer.rb +## Support methods for manufacturer specific facts +## +## This program is free software; you can redistribute it and/or +## modify it under the terms of the GNU General Public License +## as published by the Free Software Foundation (version 2 of the License) +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA +## + + +module Facter::Manufacturer + def self.dmi_find_system_info(name) + return nil unless FileTest.exists?("/usr/sbin/dmidecode") + + # Do not run the command more than every five seconds. + unless defined?(@data) and defined?(@time) and (Time.now.to_i - @time.to_i < 5) + @data = {} + type = nil + @time = Time.now + # It's *much* easier to just parse the whole darn file than + # to just match a chunk of it. + %x{/usr/sbin/dmidecode 2>/dev/null}.split("\n").each do |line| + case line + when /^(\S.+)$/ + type = $1.chomp + @data[type] ||= {} + when /^\s+(\S.+): (\S.*)$/ + unless type + next + end + @data[type][$1] = $2.strip + end + end + end + + if data = @data["System Information"] + data[name] + else + nil + end + end +end + diff --git a/lib/facter/util/memory.rb b/lib/facter/util/memory.rb new file mode 100644 index 0000000000..200449198a --- /dev/null +++ b/lib/facter/util/memory.rb @@ -0,0 +1,54 @@ +## memory.rb +## Support module for memory related facts +## +## This program is free software; you can redistribute it and/or +## modify it under the terms of the GNU General Public License +## as published by the Free Software Foundation (version 2 of the License) +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA +## + +module Facter::Memory + require 'thread' + + def self.meminfo_number(tag) + memsize = "" + Thread::exclusive do + size, scale = [0, ""] + File.readlines("/proc/meminfo").each do |l| + size, scale = [$1.to_f, $2] if l =~ /^#{tag}:\s+(\d+)\s+(\S+)/ + # MemoryFree == memfree + cached + buffers + # (assume scales are all the same as memfree) + if tag == "MemFree" && + l =~ /^(?:Buffers|Cached):\s+(\d+)\s+(?:\S+)/ + size += $1.to_f + end + end + memsize = scale_number(size, scale) + end + + memsize + end + + def self.scale_number(size, multiplier) + suffixes = ['', 'kB', 'MB', 'GB', 'TB'] + + s = suffixes.shift + while s != multiplier + s = suffixes.shift + end + + while size > 1024.0 + size /= 1024.0 + s = suffixes.shift + end + + return "%.2f %s" % [size, s] + end +end + From 0c4ac421890afeaab3400e5b7e9ce28d13fe7fc8 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sat, 16 Feb 2008 18:20:07 +1100 Subject: [PATCH 0165/3753] Fixed #46 - refactor ipmess.rb --- lib/facter/ipmess.rb | 131 ++++++++----------------------------------- 1 file changed, 23 insertions(+), 108 deletions(-) diff --git a/lib/facter/ipmess.rb b/lib/facter/ipmess.rb index b81048a3b0..78788743b7 100644 --- a/lib/facter/ipmess.rb +++ b/lib/facter/ipmess.rb @@ -17,125 +17,40 @@ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA # -Facter.add(:interfaces) do - confine :kernel => [ :freebsd, :openbsd, :netbsd, :linux ] - setcode do - output = %x{/sbin/ifconfig -a} - int = nil - int = output.scan(/(^\w+[.:]?\d+)/).join(" ") - end -end +require 'facter/util/ip' Facter.add(:interfaces) do - confine :kernel => :sunos + confine :kernel => [ :sunos, :freebsd, :openbsd, :netbsd, :linux ] setcode do - output = %x{/usr/sbin/ifconfig -a} - int = nil - int = output.scan(/(^\w+[.:]?\d+)/).join(" ") + Facter::IPAddress.get_interfaces.join(",") end end -if Facter.kernel == "Linux" - interfaces = nil - interfaces = Facter.interfaces.split(" ") - interfaces.each do |int| - output_int = %x{/sbin/ifconfig #{int}} - tmp1 = nil - tmp2 = nil - tmp3 = nil - test = {} - output_int.each { |s| - tmp1 = $1 if s =~ /inet addr:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ - tmp2 = $1 if s =~ /(?:ether|HWaddr)\s+(\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/ - tmp3 = $1 if s =~ /Mask:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ - if tmp1 != nil && tmp2 != nil && tmp3 != nil && int != "lo" - test["ipaddress_" + int] = tmp1 - test["macaddress_" + int] = tmp2 - test["netmask_" + int] = tmp3 - int = nil - tmp1 = nil - tmp2 = nil - tmp3 = nil - end - } - test.each{|name,fact| - Facter.add(name) do - confine :kernel => :linux - setcode do - fact - end - end - } +Facter::IPAddress.get_interfaces.each do |interface| + +Facter.add("ipaddress_" + interface) do + confine :kernel => [ :sunos, :freebsd, :openbsd, :netbsd, :linux ] + setcode do + label = 'ipaddress' + Facter::IPAddress.get_interface_value(interface, label) end end -if Facter.kernel == "FreeBSD" || Facter.kernel == "OpenBSD" || Facter.kernel == "NetBSD" - - interfaces = nil - interfaces = Facter.interfaces.split(" ") - interfaces.each do |int| - output_int = %x{/sbin/ifconfig #{int}} - tmp1 = nil - tmp2 = nil - tmp3 = nil - test = {} - output_int.each { |s| - tmp1 = $1 if s =~ /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ - tmp2 = $1 if s =~ /(?:ether|lladdr)\s+(\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ - tmp3 = $1 if s =~ /netmask\s+(\w{10})/ - if tmp1 != nil && tmp2 != nil && tmp3 != nil && int != "lo" - test["ipaddress_" + int] = tmp1 - test["macaddress_" + int] = tmp2 - test["netmask_" + int] = tmp3 - int = nil - tmp1 = nil - tmp2 = nil - tmp3 = nil - end - } - test.each{|name,fact| - Facter.add(name) do - confine :kernel => [ :freebsd, :openbsd, :netbsd ] - setcode do - fact - end - end - } - end +Facter.add("macaddress_" + interface) do + confine :kernel => [ :sunos, :freebsd, :openbsd, :netbsd, :linux ] + setcode do + label = 'macaddress' + Facter::IPAddress.get_interface_value(interface, label) + end end -if Facter.kernel == "SunOS" +Facter.add("netmask_" + interface) do + confine :kernel => [ :sunos, :freebsd, :openbsd, :netbsd, :linux ] + setcode do + label = 'netmask' + Facter::IPAddress.get_interface_value(interface, label) + end +end - interfaces = nil - interfaces = Facter.interfaces.split(" ") - interfaces.each do |int| - output_int = %x{/usr/sbin/ifconfig #{int}} - tmp1 = nil - tmp2 = nil - tmp3 = nil - test = {} - output_int.each { |s| - tmp1 = $1 if s =~ /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ - tmp2 = $1 if s =~ /(?:ether|lladdr)\s+(\w?\w:\w?\w:\w?\w:\w?\w:\w?\w:\w?\w)/ - tmp3 = $1 if s =~ /netmask\s+(\w{8})/ - if tmp1 != nil && tmp2 != nil && tmp3 != nil && int != "lo" - test["ipaddress_" + int] = tmp1 - test["macaddress_" + int] = tmp2 - test["netmask_" + int] = tmp3 - int = nil - tmp1 = nil - tmp2 = nil - tmp3 = nil - end - } - test.each{|name,fact| - Facter.add(name) do - confine :kernel => [ :sunos ] - setcode do - fact - end - end - } - end end From 92b43e0af2a1d0dfa43a7d851bd8b84522c93044 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sat, 16 Feb 2008 18:20:38 +1100 Subject: [PATCH 0166/3753] Added require util ip.rb file --- lib/facter/util/ip.rb | 62 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 lib/facter/util/ip.rb diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb new file mode 100644 index 0000000000..4b172086ce --- /dev/null +++ b/lib/facter/util/ip.rb @@ -0,0 +1,62 @@ +module Facter::IPAddress + + def self.get_interfaces + + int = nil + + case Facter.value(:kernel) + when 'Linux', 'OpenBSD', 'NetBSD', 'FreeBSD' + output = %x{/sbin/ifconfig -a} + when 'SunOS' + output = %x{/usr/sbin/ifconfig -a} + end + + int = output.scan(/^\w+[.:]?\d+/) + + end + + def self.get_interface_value(interface, label) + + tmp1 =nil + tmp2 = nil + tmp3 = nil + + case Facter.value(:kernel) + when 'Linux' + output_int = %x{/sbin/ifconfig #{interface}} + addr = /inet addr:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + mac = /(?:ether|HWaddr)\s+(\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/ + mask = /Mask:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + when 'OpenBSD', 'NetBSD', 'FreeBSD' + output_int = %x{/sbin/ifconfig #{interface}} + addr = /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + mac = /(?:ether|lladdr)\s+(\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ + mask = /netmask\s+(\w{10})/ + when 'SunOS' + output_int = %x{/usr/sbin/ifconfig #{interface}} + addr = /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + mac = /(?:ether|lladdr)\s+(\w?\w:\w?\w:\w?\w:\w?\w:\w?\w:\w?\w)/ + mask = /netmask\s+(\w{8})/ + end + + case label + when 'ipaddress' + regex = addr + when 'macaddress' + regex = mac + when 'netmask' + regex = mask + end + + if interface != "lo" + output_int.each { |s| + tmp1 = $1 if s =~ regex + } + end + + if tmp1 + value = tmp1 + end + + end +end From 1a5ba7196fcea8e0192b8bc13b3283e3553e897d Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sat, 16 Feb 2008 21:56:01 +1100 Subject: [PATCH 0167/3753] Fixed Solaris detection of lo0 for ticket #46 --- lib/facter/util/ip.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index 4b172086ce..f3d8095498 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -48,7 +48,7 @@ def self.get_interface_value(interface, label) regex = mask end - if interface != "lo" + if interface != "lo" && interface != "lo0" output_int.each { |s| tmp1 = $1 if s =~ regex } From e11edfb869808a4de3130424ca160412e1e88b31 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Sun, 17 Feb 2008 16:15:10 -0600 Subject: [PATCH 0168/3753] Switching from test/unit to rspec, and fixing a couple of small test failures. --- lib/facter/ipmess.rb | 6 +- lib/facter/macosx.rb | 4 +- spec/Rakefile | 18 + spec/spec.opts | 3 + spec/spec_helper.rb | 25 + spec/unit/.facter.rb.swp | Bin 0 -> 32768 bytes tests/tc_simple.rb => spec/unit/facter.rb | 24 +- tests/tc_facterbin.rb | 86 -- vendor/gems/mocha-0.5.6/._RELEASE | Bin 0 -> 176 bytes vendor/gems/mocha-0.5.6/._Rakefile | Bin 0 -> 177 bytes vendor/gems/mocha-0.5.6/COPYING | 3 + vendor/gems/mocha-0.5.6/MIT-LICENSE | 7 + vendor/gems/mocha-0.5.6/README | 35 + vendor/gems/mocha-0.5.6/RELEASE | 188 +++ vendor/gems/mocha-0.5.6/Rakefile | 149 +++ vendor/gems/mocha-0.5.6/examples/._misc.rb | Bin 0 -> 177 bytes vendor/gems/mocha-0.5.6/examples/._mocha.rb | Bin 0 -> 177 bytes vendor/gems/mocha-0.5.6/examples/._stubba.rb | Bin 0 -> 178 bytes vendor/gems/mocha-0.5.6/examples/misc.rb | 44 + vendor/gems/mocha-0.5.6/examples/mocha.rb | 26 + vendor/gems/mocha-0.5.6/examples/stubba.rb | 65 + vendor/gems/mocha-0.5.6/lib/._mocha.rb | Bin 0 -> 177 bytes vendor/gems/mocha-0.5.6/lib/mocha.rb | 19 + .../lib/mocha/._any_instance_method.rb | Bin 0 -> 177 bytes .../mocha-0.5.6/lib/mocha/._auto_verify.rb | Bin 0 -> 176 bytes .../gems/mocha-0.5.6/lib/mocha/._central.rb | Bin 0 -> 177 bytes .../mocha-0.5.6/lib/mocha/._class_method.rb | Bin 0 -> 178 bytes .../mocha-0.5.6/lib/mocha/._deprecation.rb | Bin 0 -> 177 bytes .../mocha-0.5.6/lib/mocha/._expectation.rb | Bin 0 -> 178 bytes .../lib/mocha/._expectation_error.rb | Bin 0 -> 176 bytes .../lib/mocha/._expectation_list.rb | Bin 0 -> 178 bytes .../mocha-0.5.6/lib/mocha/._infinite_range.rb | Bin 0 -> 177 bytes .../gems/mocha-0.5.6/lib/mocha/._inspect.rb | Bin 0 -> 176 bytes .../lib/mocha/._instance_method.rb | Bin 0 -> 176 bytes .../gems/mocha-0.5.6/lib/mocha/._metaclass.rb | Bin 0 -> 176 bytes .../mocha-0.5.6/lib/mocha/._method_matcher.rb | Bin 0 -> 177 bytes .../lib/mocha/._missing_expectation.rb | Bin 0 -> 177 bytes vendor/gems/mocha-0.5.6/lib/mocha/._mock.rb | Bin 0 -> 178 bytes vendor/gems/mocha-0.5.6/lib/mocha/._object.rb | Bin 0 -> 178 bytes .../lib/mocha/._parameter_matchers.rb | Bin 0 -> 177 bytes .../lib/mocha/._parameters_matcher.rb | Bin 0 -> 177 bytes .../lib/mocha/._pretty_parameters.rb | Bin 0 -> 176 bytes .../mocha-0.5.6/lib/mocha/._return_values.rb | Bin 0 -> 177 bytes .../gems/mocha-0.5.6/lib/mocha/._sequence.rb | Bin 0 -> 178 bytes .../lib/mocha/._setup_and_teardown.rb | Bin 0 -> 176 bytes .../lib/mocha/._single_return_value.rb | Bin 0 -> 178 bytes .../mocha-0.5.6/lib/mocha/._standalone.rb | Bin 0 -> 178 bytes .../lib/mocha/._test_case_adapter.rb | Bin 0 -> 178 bytes .../lib/mocha/._yield_parameters.rb | Bin 0 -> 178 bytes .../lib/mocha/any_instance_method.rb | 35 + .../gems/mocha-0.5.6/lib/mocha/auto_verify.rb | 118 ++ vendor/gems/mocha-0.5.6/lib/mocha/central.rb | 35 + .../mocha-0.5.6/lib/mocha/class_method.rb | 66 ++ .../gems/mocha-0.5.6/lib/mocha/deprecation.rb | 22 + .../mocha-0.5.6/lib/mocha/exception_raiser.rb | 17 + .../gems/mocha-0.5.6/lib/mocha/expectation.rb | 384 ++++++ .../lib/mocha/expectation_error.rb | 15 + .../mocha-0.5.6/lib/mocha/expectation_list.rb | 46 + .../mocha-0.5.6/lib/mocha/infinite_range.rb | 25 + vendor/gems/mocha-0.5.6/lib/mocha/inspect.rb | 39 + .../mocha-0.5.6/lib/mocha/instance_method.rb | 8 + vendor/gems/mocha-0.5.6/lib/mocha/is_a.rb | 9 + .../gems/mocha-0.5.6/lib/mocha/metaclass.rb | 7 + .../mocha-0.5.6/lib/mocha/method_matcher.rb | 21 + .../lib/mocha/missing_expectation.rb | 17 + vendor/gems/mocha-0.5.6/lib/mocha/mock.rb | 202 ++++ .../mocha-0.5.6/lib/mocha/multiple_yields.rb | 20 + .../gems/mocha-0.5.6/lib/mocha/no_yields.rb | 11 + vendor/gems/mocha-0.5.6/lib/mocha/object.rb | 110 ++ .../lib/mocha/parameter_matchers.rb | 25 + .../lib/mocha/parameter_matchers/._all_of.rb | Bin 0 -> 178 bytes .../lib/mocha/parameter_matchers/._any_of.rb | Bin 0 -> 178 bytes .../parameter_matchers/._any_parameters.rb | Bin 0 -> 178 bytes .../mocha/parameter_matchers/._anything.rb | Bin 0 -> 176 bytes .../lib/mocha/parameter_matchers/._base.rb | Bin 0 -> 176 bytes .../lib/mocha/parameter_matchers/._equals.rb | Bin 0 -> 176 bytes .../mocha/parameter_matchers/._has_entries.rb | Bin 0 -> 178 bytes .../mocha/parameter_matchers/._has_entry.rb | Bin 0 -> 178 bytes .../lib/mocha/parameter_matchers/._has_key.rb | Bin 0 -> 176 bytes .../mocha/parameter_matchers/._has_value.rb | Bin 0 -> 178 bytes .../mocha/parameter_matchers/._includes.rb | Bin 0 -> 178 bytes .../mocha/parameter_matchers/._instance_of.rb | Bin 0 -> 176 bytes .../lib/mocha/parameter_matchers/._is_a.rb | Bin 0 -> 177 bytes .../lib/mocha/parameter_matchers/._kind_of.rb | Bin 0 -> 176 bytes .../lib/mocha/parameter_matchers/._not.rb | Bin 0 -> 177 bytes .../lib/mocha/parameter_matchers/._object.rb | Bin 0 -> 176 bytes .../mocha/parameter_matchers/._optionally.rb | Bin 0 -> 177 bytes .../parameter_matchers/._regexp_matches.rb | Bin 0 -> 176 bytes .../lib/mocha/parameter_matchers/all_of.rb | 42 + .../lib/mocha/parameter_matchers/any_of.rb | 47 + .../parameter_matchers/any_parameters.rb | 40 + .../lib/mocha/parameter_matchers/anything.rb | 33 + .../lib/mocha/parameter_matchers/base.rb | 15 + .../lib/mocha/parameter_matchers/equals.rb | 42 + .../mocha/parameter_matchers/has_entries.rb | 42 + .../lib/mocha/parameter_matchers/has_entry.rb | 55 + .../lib/mocha/parameter_matchers/has_key.rb | 42 + .../lib/mocha/parameter_matchers/has_value.rb | 42 + .../lib/mocha/parameter_matchers/includes.rb | 40 + .../mocha/parameter_matchers/instance_of.rb | 42 + .../lib/mocha/parameter_matchers/is_a.rb | 42 + .../lib/mocha/parameter_matchers/kind_of.rb | 42 + .../lib/mocha/parameter_matchers/not.rb | 42 + .../lib/mocha/parameter_matchers/object.rb | 9 + .../mocha/parameter_matchers/optionally.rb | 33 + .../parameter_matchers/regexp_matches.rb | 43 + .../lib/mocha/parameters_matcher.rb | 37 + .../lib/mocha/pretty_parameters.rb | 28 + .../mocha-0.5.6/lib/mocha/return_values.rb | 34 + vendor/gems/mocha-0.5.6/lib/mocha/sequence.rb | 42 + .../lib/mocha/setup_and_teardown.rb | 23 + .../lib/mocha/single_return_value.rb | 24 + .../mocha-0.5.6/lib/mocha/single_yield.rb | 18 + .../gems/mocha-0.5.6/lib/mocha/standalone.rb | 32 + vendor/gems/mocha-0.5.6/lib/mocha/stub.rb | 18 + .../lib/mocha/test_case_adapter.rb | 49 + .../mocha-0.5.6/lib/mocha/yield_parameters.rb | 31 + .../gems/mocha-0.5.6/lib/mocha_standalone.rb | 2 + vendor/gems/mocha-0.5.6/lib/stubba.rb | 2 + .../test/._deprecation_disabler.rb | Bin 0 -> 177 bytes .../mocha-0.5.6/test/._execution_point.rb | Bin 0 -> 176 bytes .../gems/mocha-0.5.6/test/._method_definer.rb | Bin 0 -> 177 bytes vendor/gems/mocha-0.5.6/test/._test_helper.rb | Bin 0 -> 177 bytes vendor/gems/mocha-0.5.6/test/._test_runner.rb | Bin 0 -> 178 bytes ...pected_invocation_count_acceptance_test.rb | Bin 0 -> 176 bytes .../acceptance/._mocha_acceptance_test.rb | Bin 0 -> 177 bytes ..._with_initializer_block_acceptance_test.rb | Bin 0 -> 177 bytes ...mocked_methods_dispatch_acceptance_test.rb | Bin 0 -> 177 bytes .../._optional_parameters_acceptance_test.rb | Bin 0 -> 177 bytes .../._parameter_matcher_acceptance_test.rb | Bin 0 -> 178 bytes .../._partial_mocks_acceptance_test.rb | Bin 0 -> 177 bytes .../acceptance/._sequence_acceptance_test.rb | Bin 0 -> 178 bytes .../._standalone_acceptance_test.rb | Bin 0 -> 176 bytes .../acceptance/._stubba_acceptance_test.rb | Bin 0 -> 178 bytes ...pected_invocation_count_acceptance_test.rb | 187 +++ .../test/acceptance/mocha_acceptance_test.rb | 98 ++ ..._with_initializer_block_acceptance_test.rb | 44 + ...mocked_methods_dispatch_acceptance_test.rb | 71 ++ .../optional_parameters_acceptance_test.rb | 63 + .../parameter_matcher_acceptance_test.rb | 117 ++ .../partial_mocks_acceptance_test.rb | 40 + .../acceptance/sequence_acceptance_test.rb | 179 +++ .../acceptance/standalone_acceptance_test.rb | 131 +++ .../test/acceptance/stubba_acceptance_test.rb | 102 ++ .../test/active_record_test_case.rb | 36 + .../mocha-0.5.6/test/deprecation_disabler.rb | 15 + .../gems/mocha-0.5.6/test/execution_point.rb | 34 + .../._mocha_test_result_integration_test.rb | Bin 0 -> 178 bytes .../integration/._stubba_integration_test.rb | Bin 0 -> 178 bytes .../._stubba_test_result_integration_test.rb | Bin 0 -> 178 bytes .../mocha_test_result_integration_test.rb | 105 ++ .../integration/stubba_integration_test.rb | 89 ++ .../stubba_test_result_integration_test.rb | 85 ++ .../gems/mocha-0.5.6/test/method_definer.rb | 18 + vendor/gems/mocha-0.5.6/test/test_helper.rb | 12 + vendor/gems/mocha-0.5.6/test/test_runner.rb | 31 + .../test/unit/._any_instance_method_test.rb | Bin 0 -> 176 bytes .../test/unit/._auto_verify_test.rb | Bin 0 -> 177 bytes .../mocha-0.5.6/test/unit/._central_test.rb | Bin 0 -> 177 bytes .../test/unit/._class_method_test.rb | Bin 0 -> 178 bytes .../test/unit/._expectation_error_test.rb | Bin 0 -> 178 bytes .../test/unit/._expectation_list_test.rb | Bin 0 -> 177 bytes .../test/unit/._expectation_test.rb | Bin 0 -> 179 bytes .../test/unit/._hash_inspect_test.rb | Bin 0 -> 177 bytes .../test/unit/._method_matcher_test.rb | Bin 0 -> 177 bytes .../test/unit/._missing_expectation_test.rb | Bin 0 -> 178 bytes .../gems/mocha-0.5.6/test/unit/._mock_test.rb | Bin 0 -> 177 bytes .../test/unit/._object_inspect_test.rb | Bin 0 -> 178 bytes .../test/unit/._parameters_matcher_test.rb | Bin 0 -> 176 bytes .../mocha-0.5.6/test/unit/._sequence_test.rb | Bin 0 -> 178 bytes .../test/unit/any_instance_method_test.rb | 126 ++ .../test/unit/array_inspect_test.rb | 16 + .../mocha-0.5.6/test/unit/auto_verify_test.rb | 129 ++ .../mocha-0.5.6/test/unit/central_test.rb | 124 ++ .../test/unit/class_method_test.rb | 200 ++++ .../test/unit/date_time_inspect_test.rb | 21 + .../test/unit/expectation_error_test.rb | 24 + .../test/unit/expectation_list_test.rb | 75 ++ .../test/unit/expectation_raiser_test.rb | 28 + .../mocha-0.5.6/test/unit/expectation_test.rb | 483 ++++++++ .../test/unit/hash_inspect_test.rb | 16 + .../test/unit/infinite_range_test.rb | 53 + .../mocha-0.5.6/test/unit/metaclass_test.rb | 22 + .../test/unit/method_matcher_test.rb | 23 + .../test/unit/missing_expectation_test.rb | 42 + .../gems/mocha-0.5.6/test/unit/mock_test.rb | 323 +++++ .../test/unit/multiple_yields_test.rb | 18 + .../mocha-0.5.6/test/unit/no_yield_test.rb | 18 + .../test/unit/object_inspect_test.rb | 37 + .../gems/mocha-0.5.6/test/unit/object_test.rb | 165 +++ .../unit/parameter_matchers/._all_of_test.rb | Bin 0 -> 178 bytes .../unit/parameter_matchers/._any_of_test.rb | Bin 0 -> 177 bytes .../parameter_matchers/._anything_test.rb | Bin 0 -> 177 bytes .../parameter_matchers/._has_entries_test.rb | Bin 0 -> 177 bytes .../parameter_matchers/._has_entry_test.rb | Bin 0 -> 178 bytes .../unit/parameter_matchers/._has_key_test.rb | Bin 0 -> 178 bytes .../parameter_matchers/._has_value_test.rb | Bin 0 -> 177 bytes .../parameter_matchers/._includes_test.rb | Bin 0 -> 177 bytes .../parameter_matchers/._instance_of_test.rb | Bin 0 -> 178 bytes .../unit/parameter_matchers/._is_a_test.rb | Bin 0 -> 177 bytes .../unit/parameter_matchers/._kind_of_test.rb | Bin 0 -> 178 bytes .../unit/parameter_matchers/._not_test.rb | Bin 0 -> 177 bytes .../._regexp_matches_test.rb | Bin 0 -> 178 bytes .../unit/parameter_matchers/._stub_matcher.rb | Bin 0 -> 178 bytes .../unit/parameter_matchers/all_of_test.rb | 26 + .../unit/parameter_matchers/any_of_test.rb | 26 + .../unit/parameter_matchers/anything_test.rb | 21 + .../parameter_matchers/has_entries_test.rb | 30 + .../unit/parameter_matchers/has_entry_test.rb | 40 + .../unit/parameter_matchers/has_key_test.rb | 25 + .../unit/parameter_matchers/has_value_test.rb | 25 + .../unit/parameter_matchers/includes_test.rb | 25 + .../parameter_matchers/instance_of_test.rb | 25 + .../test/unit/parameter_matchers/is_a_test.rb | 25 + .../unit/parameter_matchers/kind_of_test.rb | 25 + .../test/unit/parameter_matchers/not_test.rb | 26 + .../parameter_matchers/regexp_matches_test.rb | 25 + .../unit/parameter_matchers/stub_matcher.rb | 23 + .../test/unit/parameters_matcher_test.rb | 121 ++ .../test/unit/return_values_test.rb | 63 + .../mocha-0.5.6/test/unit/sequence_test.rb | 104 ++ .../test/unit/setup_and_teardown_test.rb | 76 ++ .../test/unit/single_return_value_test.rb | 33 + .../test/unit/single_yield_test.rb | 18 + .../test/unit/string_inspect_test.rb | 11 + .../gems/mocha-0.5.6/test/unit/stub_test.rb | 24 + .../test/unit/yield_parameters_test.rb | 93 ++ vendor/gems/rspec/CHANGES | 1045 +++++++++++++++++ vendor/gems/rspec/MIT-LICENSE | 20 + vendor/gems/rspec/README | 71 ++ vendor/gems/rspec/Rakefile | 279 +++++ vendor/gems/rspec/TODO | 2 + vendor/gems/rspec/UPGRADE | 31 + vendor/gems/rspec/bin/spec | 4 + vendor/gems/rspec/bin/spec_translator | 8 + .../pure/autogenerated_docstrings_example.rb | 19 + .../examples/pure/before_and_after_example.rb | 40 + .../rspec/examples/pure/behave_as_example.rb | 45 + .../pure/custom_expectation_matchers.rb | 54 + .../rspec/examples/pure/custom_formatter.rb | 12 + .../gems/rspec/examples/pure/dynamic_spec.rb | 9 + .../gems/rspec/examples/pure/file_accessor.rb | 19 + .../rspec/examples/pure/file_accessor_spec.rb | 38 + .../gems/rspec/examples/pure/greeter_spec.rb | 31 + .../examples/pure/helper_method_example.rb | 14 + .../gems/rspec/examples/pure/io_processor.rb | 8 + .../rspec/examples/pure/io_processor_spec.rb | 21 + .../gems/rspec/examples/pure/legacy_spec.rb | 11 + .../rspec/examples/pure/mocking_example.rb | 27 + .../pure/multi_threaded_behaviour_runner.rb | 28 + .../examples/pure/nested_classes_example.rb | 36 + .../examples/pure/partial_mock_example.rb | 28 + .../rspec/examples/pure/pending_example.rb | 20 + .../rspec/examples/pure/predicate_example.rb | 27 + vendor/gems/rspec/examples/pure/priority.txt | 1 + .../pure/shared_example_group_example.rb | 81 ++ .../examples/pure/shared_stack_examples.rb | 38 + .../gems/rspec/examples/pure/spec_helper.rb | 3 + vendor/gems/rspec/examples/pure/stack.rb | 36 + vendor/gems/rspec/examples/pure/stack_spec.rb | 63 + .../stack_spec_with_nested_example_groups.rb | 67 ++ .../rspec/examples/pure/stubbing_example.rb | 69 ++ vendor/gems/rspec/examples/stories/adder.rb | 13 + vendor/gems/rspec/examples/stories/addition | 34 + .../gems/rspec/examples/stories/addition.rb | 9 + .../gems/rspec/examples/stories/calculator.rb | 65 + .../examples/stories/game-of-life/README.txt | 21 + .../game-of-life/behaviour/everything.rb | 6 + .../behaviour/examples/examples.rb | 3 + .../behaviour/examples/game_behaviour.rb | 35 + .../behaviour/examples/grid_behaviour.rb | 66 ++ .../CellsWithLessThanTwoNeighboursDie.story | 21 + .../CellsWithMoreThanThreeNeighboursDie.story | 21 + ...SpacesWithThreeNeighboursCreateACell.story | 42 + .../behaviour/stories/ICanCreateACell.story | 42 + .../behaviour/stories/ICanKillACell.story | 17 + .../behaviour/stories/TheGridWraps.story | 53 + .../behaviour/stories/create_a_cell.rb | 52 + .../game-of-life/behaviour/stories/helper.rb | 6 + .../behaviour/stories/kill_a_cell.rb | 26 + .../game-of-life/behaviour/stories/steps.rb | 5 + .../game-of-life/behaviour/stories/stories.rb | 3 + .../behaviour/stories/stories.txt | 22 + .../examples/stories/game-of-life/life.rb | 3 + .../stories/game-of-life/life/game.rb | 23 + .../stories/game-of-life/life/grid.rb | 43 + vendor/gems/rspec/examples/stories/helper.rb | 9 + .../examples/stories/steps/addition_steps.rb | 18 + vendor/gems/rspec/failing_examples/README.txt | 7 + .../rspec/failing_examples/diffing_spec.rb | 36 + ...ailing_autogenerated_docstrings_example.rb | 19 + .../failing_examples/failure_in_setup.rb | 10 + .../failing_examples/failure_in_teardown.rb | 10 + .../rspec/failing_examples/mocking_example.rb | 40 + .../failing_examples/mocking_with_flexmock.rb | 26 + .../failing_examples/mocking_with_mocha.rb | 25 + .../rspec/failing_examples/mocking_with_rr.rb | 27 + .../failing_examples/partial_mock_example.rb | 20 + .../failing_examples/predicate_example.rb | 29 + .../rspec/failing_examples/raising_example.rb | 47 + .../rspec/failing_examples/spec_helper.rb | 3 + .../failing_examples/syntax_error_example.rb | 7 + .../gems/rspec/failing_examples/team_spec.rb | 44 + .../failing_examples/timeout_behaviour.rb | 7 + vendor/gems/rspec/lib/autotest/discover.rb | 3 + vendor/gems/rspec/lib/autotest/rspec.rb | 74 ++ vendor/gems/rspec/lib/spec.rb | 30 + vendor/gems/rspec/lib/spec/example.rb | 12 + .../rspec/lib/spec/example/configuration.rb | 144 +++ vendor/gems/rspec/lib/spec/example/errors.rb | 9 + .../rspec/lib/spec/example/example_group.rb | 16 + .../lib/spec/example/example_group_factory.rb | 62 + .../lib/spec/example/example_group_methods.rb | 424 +++++++ .../rspec/lib/spec/example/example_matcher.rb | 42 + .../rspec/lib/spec/example/example_methods.rb | 106 ++ .../lib/spec/example/module_reopening_fix.rb | 21 + vendor/gems/rspec/lib/spec/example/pending.rb | 18 + .../lib/spec/example/shared_example_group.rb | 58 + vendor/gems/rspec/lib/spec/expectations.rb | 56 + .../lib/spec/expectations/differs/default.rb | 66 ++ .../rspec/lib/spec/expectations/errors.rb | 12 + .../rspec/lib/spec/expectations/extensions.rb | 2 + .../spec/expectations/extensions/object.rb | 71 ++ .../extensions/string_and_symbol.rb | 17 + .../rspec/lib/spec/expectations/handler.rb | 52 + vendor/gems/rspec/lib/spec/extensions.rb | 3 + .../gems/rspec/lib/spec/extensions/class.rb | 24 + vendor/gems/rspec/lib/spec/extensions/main.rb | 102 ++ .../gems/rspec/lib/spec/extensions/object.rb | 10 + vendor/gems/rspec/lib/spec/interop/test.rb | 12 + .../lib/spec/interop/test/unit/autorunner.rb | 6 + .../lib/spec/interop/test/unit/testcase.rb | 61 + .../lib/spec/interop/test/unit/testresult.rb | 6 + .../interop/test/unit/testsuite_adapter.rb | 34 + .../test/unit/ui/console/testrunner.rb | 61 + vendor/gems/rspec/lib/spec/matchers.rb | 156 +++ vendor/gems/rspec/lib/spec/matchers/be.rb | 224 ++++ .../gems/rspec/lib/spec/matchers/be_close.rb | 37 + vendor/gems/rspec/lib/spec/matchers/change.rb | 144 +++ vendor/gems/rspec/lib/spec/matchers/eql.rb | 43 + vendor/gems/rspec/lib/spec/matchers/equal.rb | 43 + vendor/gems/rspec/lib/spec/matchers/exist.rb | 17 + vendor/gems/rspec/lib/spec/matchers/has.rb | 44 + vendor/gems/rspec/lib/spec/matchers/have.rb | 145 +++ .../gems/rspec/lib/spec/matchers/include.rb | 70 ++ vendor/gems/rspec/lib/spec/matchers/match.rb | 41 + .../lib/spec/matchers/operator_matcher.rb | 73 ++ .../rspec/lib/spec/matchers/raise_error.rb | 109 ++ .../rspec/lib/spec/matchers/respond_to.rb | 45 + .../gems/rspec/lib/spec/matchers/satisfy.rb | 47 + .../rspec/lib/spec/matchers/simple_matcher.rb | 29 + .../rspec/lib/spec/matchers/throw_symbol.rb | 74 ++ vendor/gems/rspec/lib/spec/mocks.rb | 211 ++++ .../mocks/argument_constraint_matchers.rb | 27 + .../lib/spec/mocks/argument_expectation.rb | 183 +++ .../rspec/lib/spec/mocks/error_generator.rb | 84 ++ vendor/gems/rspec/lib/spec/mocks/errors.rb | 10 + .../rspec/lib/spec/mocks/extensions/object.rb | 3 + .../lib/spec/mocks/message_expectation.rb | 267 +++++ vendor/gems/rspec/lib/spec/mocks/methods.rb | 39 + vendor/gems/rspec/lib/spec/mocks/mock.rb | 50 + .../gems/rspec/lib/spec/mocks/order_group.rb | 29 + vendor/gems/rspec/lib/spec/mocks/proxy.rb | 170 +++ vendor/gems/rspec/lib/spec/mocks/space.rb | 28 + .../gems/rspec/lib/spec/mocks/spec_methods.rb | 38 + vendor/gems/rspec/lib/spec/rake/spectask.rb | 235 ++++ .../gems/rspec/lib/spec/rake/verify_rcov.rb | 52 + vendor/gems/rspec/lib/spec/runner.rb | 202 ++++ .../lib/spec/runner/backtrace_tweaker.rb | 57 + .../spec/runner/class_and_arguments_parser.rb | 16 + .../rspec/lib/spec/runner/command_line.rb | 28 + .../rspec/lib/spec/runner/drb_command_line.rb | 20 + .../lib/spec/runner/example_group_runner.rb | 59 + .../spec/runner/formatter/base_formatter.rb | 78 ++ .../runner/formatter/base_text_formatter.rb | 130 ++ .../failing_example_groups_formatter.rb | 31 + .../formatter/failing_examples_formatter.rb | 20 + .../spec/runner/formatter/html_formatter.rb | 333 ++++++ .../runner/formatter/profile_formatter.rb | 47 + .../formatter/progress_bar_formatter.rb | 30 + .../runner/formatter/snippet_extractor.rb | 52 + .../runner/formatter/specdoc_formatter.rb | 39 + .../runner/formatter/story/html_formatter.rb | 128 ++ .../formatter/story/plain_text_formatter.rb | 131 +++ .../runner/formatter/text_mate_formatter.rb | 16 + .../rspec/lib/spec/runner/heckle_runner.rb | 72 ++ .../spec/runner/heckle_runner_unsupported.rb | 10 + .../rspec/lib/spec/runner/option_parser.rb | 201 ++++ vendor/gems/rspec/lib/spec/runner/options.rb | 286 +++++ vendor/gems/rspec/lib/spec/runner/reporter.rb | 143 +++ .../gems/rspec/lib/spec/runner/spec_parser.rb | 71 ++ vendor/gems/rspec/lib/spec/story.rb | 10 + .../gems/rspec/lib/spec/story/extensions.rb | 3 + .../rspec/lib/spec/story/extensions/main.rb | 86 ++ .../rspec/lib/spec/story/extensions/regexp.rb | 9 + .../rspec/lib/spec/story/extensions/string.rb | 9 + .../rspec/lib/spec/story/given_scenario.rb | 14 + vendor/gems/rspec/lib/spec/story/runner.rb | 56 + .../story/runner/plain_text_story_runner.rb | 48 + .../spec/story/runner/scenario_collector.rb | 18 + .../lib/spec/story/runner/scenario_runner.rb | 46 + .../lib/spec/story/runner/story_mediator.rb | 123 ++ .../lib/spec/story/runner/story_parser.rb | 227 ++++ .../lib/spec/story/runner/story_runner.rb | 68 ++ vendor/gems/rspec/lib/spec/story/scenario.rb | 14 + vendor/gems/rspec/lib/spec/story/step.rb | 58 + .../gems/rspec/lib/spec/story/step_group.rb | 89 ++ .../gems/rspec/lib/spec/story/step_mother.rb | 37 + vendor/gems/rspec/lib/spec/story/story.rb | 42 + vendor/gems/rspec/lib/spec/story/world.rb | 125 ++ vendor/gems/rspec/lib/spec/translator.rb | 114 ++ vendor/gems/rspec/lib/spec/version.rb | 22 + .../rspec/plugins/mock_frameworks/flexmock.rb | 23 + .../rspec/plugins/mock_frameworks/mocha.rb | 19 + .../gems/rspec/plugins/mock_frameworks/rr.rb | 21 + .../rspec/plugins/mock_frameworks/rspec.rb | 18 + .../gems/rspec/pre_commit/lib/pre_commit.rb | 4 + .../rspec/pre_commit/lib/pre_commit/core.rb | 50 + .../pre_commit/lib/pre_commit/pre_commit.rb | 54 + .../rspec/pre_commit/lib/pre_commit/rspec.rb | 111 ++ .../lib/pre_commit/rspec_on_rails.rb | 313 +++++ .../spec/pre_commit/pre_commit_spec.rb | 15 + .../spec/pre_commit/rspec_on_rails_spec.rb | 36 + .../gems/rspec/pre_commit/spec/spec_helper.rb | 3 + .../gems/rspec/pre_commit/spec/spec_suite.rb | 11 + vendor/gems/rspec/rake_tasks/examples.rake | 7 + .../rspec/rake_tasks/examples_with_rcov.rake | 9 + .../failing_examples_with_html.rake | 9 + vendor/gems/rspec/rake_tasks/verify_rcov.rake | 7 + vendor/gems/rspec/spec/README.jruby | 15 + .../gems/rspec/spec/autotest/discover_spec.rb | 19 + vendor/gems/rspec/spec/autotest/rspec_spec.rb | 195 +++ vendor/gems/rspec/spec/autotest_helper.rb | 6 + vendor/gems/rspec/spec/autotest_matchers.rb | 47 + vendor/gems/rspec/spec/rspec_suite.rb | 7 + vendor/gems/rspec/spec/ruby_forker.rb | 13 + vendor/gems/rspec/spec/spec.opts | 6 + .../spec/spec/example/configuration_spec.rb | 282 +++++ .../example_group_class_definition_spec.rb | 48 + .../example/example_group_factory_spec.rb | 129 ++ .../example/example_group_methods_spec.rb | 489 ++++++++ .../spec/spec/example/example_group_spec.rb | 711 +++++++++++ .../spec/spec/example/example_matcher_spec.rb | 96 ++ .../spec/spec/example/example_methods_spec.rb | 104 ++ .../spec/spec/example/example_runner_spec.rb | 194 +++ .../rspec/spec/spec/example/example_spec.rb | 53 + .../spec/example/nested_example_group_spec.rb | 59 + .../spec/spec/example/pending_module_spec.rb | 31 + .../spec/example/predicate_matcher_spec.rb | 21 + .../spec/example/shared_example_group_spec.rb | 265 +++++ .../example/subclassing_example_group_spec.rb | 25 + .../spec/expectations/differs/default_spec.rb | 109 ++ .../expectations/extensions/object_spec.rb | 107 ++ .../spec/spec/expectations/fail_with_spec.rb | 71 ++ .../rspec/spec/spec/extensions/main_spec.rb | 76 ++ .../test/unit/resources/spec_that_fails.rb | 10 + .../test/unit/resources/spec_that_passes.rb | 10 + .../test/unit/resources/spec_with_errors.rb | 10 + .../unit/resources/test_case_that_fails.rb | 10 + .../unit/resources/test_case_that_passes.rb | 10 + .../unit/resources/test_case_with_errors.rb | 10 + .../testsuite_adapter_spec_with_test_unit.rb | 38 + .../spec/spec/interop/test/unit/spec_spec.rb | 45 + .../test/unit/test_unit_spec_helper.rb | 14 + .../spec/interop/test/unit/testcase_spec.rb | 45 + .../test/unit/testsuite_adapter_spec.rb | 9 + .../rspec/spec/spec/matchers/be_close_spec.rb | 39 + .../gems/rspec/spec/spec/matchers/be_spec.rb | 224 ++++ .../rspec/spec/spec/matchers/change_spec.rb | 319 +++++ .../matchers/description_generation_spec.rb | 153 +++ .../gems/rspec/spec/spec/matchers/eql_spec.rb | 28 + .../rspec/spec/spec/matchers/equal_spec.rb | 28 + .../rspec/spec/spec/matchers/exist_spec.rb | 57 + .../rspec/spec/spec/matchers/handler_spec.rb | 129 ++ .../gems/rspec/spec/spec/matchers/has_spec.rb | 37 + .../rspec/spec/spec/matchers/have_spec.rb | 291 +++++ .../rspec/spec/spec/matchers/include_spec.rb | 45 + .../rspec/spec/spec/matchers/match_spec.rb | 37 + .../spec/matchers/matcher_methods_spec.rb | 78 ++ .../matchers/mock_constraint_matchers_spec.rb | 24 + .../spec/matchers/operator_matcher_spec.rb | 158 +++ .../spec/spec/matchers/raise_error_spec.rb | 191 +++ .../spec/spec/matchers/respond_to_spec.rb | 54 + .../rspec/spec/spec/matchers/satisfy_spec.rb | 36 + .../spec/spec/matchers/simple_matcher_spec.rb | 31 + .../spec/spec/matchers/throw_symbol_spec.rb | 54 + .../spec/mocks/any_number_of_times_spec.rb | 29 + .../spec/mocks/argument_expectation_spec.rb | 23 + .../rspec/spec/spec/mocks/at_least_spec.rb | 97 ++ .../rspec/spec/spec/mocks/at_most_spec.rb | 93 ++ .../spec/spec/mocks/bug_report_10260_spec.rb | 8 + .../rspec/spec/spec/mocks/bug_report_10263.rb | 24 + .../spec/spec/mocks/bug_report_11545_spec.rb | 31 + .../spec/spec/mocks/bug_report_15719_spec.rb | 30 + .../spec/spec/mocks/bug_report_7611_spec.rb | 19 + .../spec/spec/mocks/bug_report_7805_spec.rb | 22 + .../spec/spec/mocks/bug_report_8165_spec.rb | 31 + .../spec/spec/mocks/bug_report_8302_spec.rb | 26 + .../failing_mock_argument_constraints_spec.rb | 115 ++ .../spec/spec/mocks/mock_ordering_spec.rb | 84 ++ .../rspec/spec/spec/mocks/mock_space_spec.rb | 54 + .../gems/rspec/spec/spec/mocks/mock_spec.rb | 475 ++++++++ .../spec/mocks/multiple_return_value_spec.rb | 113 ++ .../spec/spec/mocks/null_object_mock_spec.rb | 40 + .../rspec/spec/spec/mocks/once_counts_spec.rb | 53 + .../spec/spec/mocks/options_hash_spec.rb | 45 + .../spec/spec/mocks/partial_mock_spec.rb | 106 ++ .../partial_mock_using_mocks_directly_spec.rb | 66 ++ .../passing_mock_argument_constraints_spec.rb | 154 +++ .../spec/spec/mocks/precise_counts_spec.rb | 52 + .../spec/spec/mocks/record_messages_spec.rb | 26 + .../gems/rspec/spec/spec/mocks/stub_spec.rb | 181 +++ .../spec/spec/mocks/twice_counts_spec.rb | 67 ++ .../rspec/spec/spec/package/bin_spec_spec.rb | 14 + .../runner/class_and_argument_parser_spec.rb | 23 + .../spec/spec/runner/command_line_spec.rb | 147 +++ .../spec/spec/runner/drb_command_line_spec.rb | 92 ++ .../rspec/spec/spec/runner/empty_file.txt | 0 .../gems/rspec/spec/spec/runner/examples.txt | 2 + .../spec/runner/execution_context_spec.rb | 31 + vendor/gems/rspec/spec/spec/runner/failed.txt | 3 + .../failing_example_groups_formatter_spec.rb | 44 + .../failing_examples_formatter_spec.rb | 33 + .../formatter/html_formatted-1.8.4.html | 365 ++++++ .../formatter/html_formatted-1.8.5-jruby.html | 387 ++++++ .../formatter/html_formatted-1.8.5.html | 371 ++++++ .../formatter/html_formatted-1.8.6-jruby.html | 381 ++++++ .../formatter/html_formatted-1.8.6.html | 365 ++++++ .../runner/formatter/html_formatter_spec.rb | 66 ++ .../formatter/profile_formatter_spec.rb | 65 + .../formatter/progress_bar_formatter_spec.rb | 127 ++ .../formatter/snippet_extractor_spec.rb | 18 + .../formatter/spec_mate_formatter_spec.rb | 103 ++ .../formatter/specdoc_formatter_spec.rb | 126 ++ .../formatter/story/html_formatter_spec.rb | 61 + .../story/plain_text_formatter_spec.rb | 335 ++++++ .../formatter/text_mate_formatted-1.8.4.html | 365 ++++++ .../formatter/text_mate_formatted-1.8.6.html | 365 ++++++ .../spec/spec/runner/heckle_runner_spec.rb | 78 ++ .../rspec/spec/spec/runner/heckler_spec.rb | 13 + .../runner/noisy_backtrace_tweaker_spec.rb | 45 + .../spec/spec/runner/option_parser_spec.rb | 378 ++++++ .../rspec/spec/spec/runner/options_spec.rb | 364 ++++++ .../spec/runner/output_one_time_fixture.rb | 7 + .../runner/output_one_time_fixture_runner.rb | 8 + .../spec/spec/runner/output_one_time_spec.rb | 16 + .../runner/quiet_backtrace_tweaker_spec.rb | 56 + .../rspec/spec/spec/runner/reporter_spec.rb | 189 +++ .../rspec/spec/spec/runner/resources/a_bar.rb | 0 .../rspec/spec/spec/runner/resources/a_foo.rb | 0 .../spec/spec/runner/resources/a_spec.rb | 1 + vendor/gems/rspec/spec/spec/runner/spec.opts | 2 + .../runner/spec_parser/spec_parser_fixture.rb | 70 ++ .../spec/spec/runner/spec_parser_spec.rb | 79 ++ .../rspec/spec/spec/runner/spec_spaced.opts | 2 + vendor/gems/rspec/spec/spec/runner_spec.rb | 11 + vendor/gems/rspec/spec/spec/spec_classes.rb | 133 +++ vendor/gems/rspec/spec/spec/story/builders.rb | 46 + .../spec/spec/story/extensions/main_spec.rb | 161 +++ .../rspec/spec/spec/story/extensions_spec.rb | 14 + .../spec/spec/story/given_scenario_spec.rb | 27 + .../runner/plain_text_story_runner_spec.rb | 92 ++ .../story/runner/scenario_collector_spec.rb | 27 + .../spec/story/runner/scenario_runner_spec.rb | 142 +++ .../spec/story/runner/story_mediator_spec.rb | 133 +++ .../spec/story/runner/story_parser_spec.rb | 384 ++++++ .../spec/story/runner/story_runner_spec.rb | 256 ++++ .../gems/rspec/spec/spec/story/runner_spec.rb | 106 ++ .../rspec/spec/spec/story/scenario_spec.rb | 20 + .../rspec/spec/spec/story/step_group_spec.rb | 157 +++ .../rspec/spec/spec/story/step_mother_spec.rb | 72 ++ .../gems/rspec/spec/spec/story/step_spec.rb | 200 ++++ .../rspec/spec/spec/story/story_helper.rb | 2 + .../gems/rspec/spec/spec/story/story_spec.rb | 86 ++ .../gems/rspec/spec/spec/story/world_spec.rb | 423 +++++++ .../gems/rspec/spec/spec/translator_spec.rb | 265 +++++ vendor/gems/rspec/spec/spec_helper.rb | 103 ++ vendor/gems/rspec/stories/all.rb | 5 + .../example_groups/autogenerated_docstrings | 45 + .../example_group_with_should_methods | 17 + .../stories/example_groups/nested_groups | 17 + .../gems/rspec/stories/example_groups/output | 25 + .../rspec/stories/example_groups/stories.rb | 7 + vendor/gems/rspec/stories/helper.rb | 6 + .../interop/examples_and_tests_together | 30 + vendor/gems/rspec/stories/interop/stories.rb | 7 + .../interop/test_case_with_should_methods | 17 + .../gems/rspec/stories/pending_stories/README | 3 + .../stories/resources/helpers/cmdline.rb | 9 + .../stories/resources/helpers/story_helper.rb | 16 + .../stories/resources/matchers/smart_match.rb | 37 + .../spec/example_group_with_should_methods.rb | 12 + .../stories/resources/spec/simple_spec.rb | 8 + .../stories/resources/steps/running_rspec.rb | 50 + .../resources/stories/failing_story.rb | 15 + .../resources/test/spec_and_test_together.rb | 57 + .../test/test_case_with_should_methods.rb | 30 + 597 files changed, 34935 insertions(+), 107 deletions(-) create mode 100644 spec/Rakefile create mode 100644 spec/spec.opts create mode 100644 spec/spec_helper.rb create mode 100644 spec/unit/.facter.rb.swp rename tests/tc_simple.rb => spec/unit/facter.rb (97%) delete mode 100755 tests/tc_facterbin.rb create mode 100644 vendor/gems/mocha-0.5.6/._RELEASE create mode 100644 vendor/gems/mocha-0.5.6/._Rakefile create mode 100644 vendor/gems/mocha-0.5.6/COPYING create mode 100644 vendor/gems/mocha-0.5.6/MIT-LICENSE create mode 100644 vendor/gems/mocha-0.5.6/README create mode 100644 vendor/gems/mocha-0.5.6/RELEASE create mode 100644 vendor/gems/mocha-0.5.6/Rakefile create mode 100644 vendor/gems/mocha-0.5.6/examples/._misc.rb create mode 100644 vendor/gems/mocha-0.5.6/examples/._mocha.rb create mode 100644 vendor/gems/mocha-0.5.6/examples/._stubba.rb create mode 100644 vendor/gems/mocha-0.5.6/examples/misc.rb create mode 100644 vendor/gems/mocha-0.5.6/examples/mocha.rb create mode 100644 vendor/gems/mocha-0.5.6/examples/stubba.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/._mocha.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._any_instance_method.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._auto_verify.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._central.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._class_method.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._deprecation.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._expectation.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._expectation_error.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._expectation_list.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._infinite_range.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._inspect.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._instance_method.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._metaclass.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._method_matcher.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._missing_expectation.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._mock.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._object.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._parameter_matchers.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._parameters_matcher.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._pretty_parameters.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._return_values.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._sequence.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._setup_and_teardown.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._single_return_value.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._standalone.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._test_case_adapter.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._yield_parameters.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/any_instance_method.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/auto_verify.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/central.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/class_method.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/deprecation.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/exception_raiser.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/expectation.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/expectation_error.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/expectation_list.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/infinite_range.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/inspect.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/instance_method.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/is_a.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/metaclass.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/method_matcher.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/missing_expectation.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/mock.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/multiple_yields.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/no_yields.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/object.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._all_of.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._any_of.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._any_parameters.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._anything.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._base.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._equals.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._has_entries.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._has_entry.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._has_key.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._has_value.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._includes.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._instance_of.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._is_a.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._kind_of.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._not.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._object.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._optionally.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._regexp_matches.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/all_of.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/any_of.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/any_parameters.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/anything.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/base.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/equals.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/has_entries.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/has_entry.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/has_key.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/has_value.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/includes.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/instance_of.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/is_a.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/kind_of.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/not.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/object.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/optionally.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/regexp_matches.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameters_matcher.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/pretty_parameters.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/return_values.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/sequence.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/setup_and_teardown.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/single_return_value.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/single_yield.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/standalone.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/stub.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/test_case_adapter.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/yield_parameters.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/mocha_standalone.rb create mode 100644 vendor/gems/mocha-0.5.6/lib/stubba.rb create mode 100644 vendor/gems/mocha-0.5.6/test/._deprecation_disabler.rb create mode 100644 vendor/gems/mocha-0.5.6/test/._execution_point.rb create mode 100644 vendor/gems/mocha-0.5.6/test/._method_definer.rb create mode 100644 vendor/gems/mocha-0.5.6/test/._test_helper.rb create mode 100644 vendor/gems/mocha-0.5.6/test/._test_runner.rb create mode 100644 vendor/gems/mocha-0.5.6/test/acceptance/._expected_invocation_count_acceptance_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/acceptance/._mocha_acceptance_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/acceptance/._mock_with_initializer_block_acceptance_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/acceptance/._mocked_methods_dispatch_acceptance_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/acceptance/._optional_parameters_acceptance_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/acceptance/._parameter_matcher_acceptance_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/acceptance/._partial_mocks_acceptance_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/acceptance/._sequence_acceptance_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/acceptance/._standalone_acceptance_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/acceptance/._stubba_acceptance_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/acceptance/expected_invocation_count_acceptance_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/acceptance/mocha_acceptance_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/acceptance/mock_with_initializer_block_acceptance_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/acceptance/mocked_methods_dispatch_acceptance_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/acceptance/optional_parameters_acceptance_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/acceptance/parameter_matcher_acceptance_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/acceptance/partial_mocks_acceptance_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/acceptance/sequence_acceptance_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/acceptance/standalone_acceptance_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/acceptance/stubba_acceptance_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/active_record_test_case.rb create mode 100644 vendor/gems/mocha-0.5.6/test/deprecation_disabler.rb create mode 100644 vendor/gems/mocha-0.5.6/test/execution_point.rb create mode 100644 vendor/gems/mocha-0.5.6/test/integration/._mocha_test_result_integration_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/integration/._stubba_integration_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/integration/._stubba_test_result_integration_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/integration/mocha_test_result_integration_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/integration/stubba_integration_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/integration/stubba_test_result_integration_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/method_definer.rb create mode 100644 vendor/gems/mocha-0.5.6/test/test_helper.rb create mode 100644 vendor/gems/mocha-0.5.6/test/test_runner.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/._any_instance_method_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/._auto_verify_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/._central_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/._class_method_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/._expectation_error_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/._expectation_list_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/._expectation_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/._hash_inspect_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/._method_matcher_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/._missing_expectation_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/._mock_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/._object_inspect_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/._parameters_matcher_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/._sequence_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/any_instance_method_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/array_inspect_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/auto_verify_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/central_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/class_method_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/date_time_inspect_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/expectation_error_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/expectation_list_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/expectation_raiser_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/expectation_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/hash_inspect_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/infinite_range_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/metaclass_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/method_matcher_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/missing_expectation_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/mock_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/multiple_yields_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/no_yield_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/object_inspect_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/object_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._all_of_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._any_of_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._anything_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._has_entries_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._has_entry_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._has_key_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._has_value_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._includes_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._instance_of_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._is_a_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._kind_of_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._not_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._regexp_matches_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._stub_matcher.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/all_of_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/any_of_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/anything_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/has_entries_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/has_entry_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/has_key_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/has_value_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/includes_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/instance_of_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/is_a_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/kind_of_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/not_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/regexp_matches_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/stub_matcher.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameters_matcher_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/return_values_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/sequence_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/setup_and_teardown_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/single_return_value_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/single_yield_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/string_inspect_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/stub_test.rb create mode 100644 vendor/gems/mocha-0.5.6/test/unit/yield_parameters_test.rb create mode 100644 vendor/gems/rspec/CHANGES create mode 100644 vendor/gems/rspec/MIT-LICENSE create mode 100644 vendor/gems/rspec/README create mode 100644 vendor/gems/rspec/Rakefile create mode 100644 vendor/gems/rspec/TODO create mode 100644 vendor/gems/rspec/UPGRADE create mode 100755 vendor/gems/rspec/bin/spec create mode 100755 vendor/gems/rspec/bin/spec_translator create mode 100644 vendor/gems/rspec/examples/pure/autogenerated_docstrings_example.rb create mode 100644 vendor/gems/rspec/examples/pure/before_and_after_example.rb create mode 100755 vendor/gems/rspec/examples/pure/behave_as_example.rb create mode 100644 vendor/gems/rspec/examples/pure/custom_expectation_matchers.rb create mode 100644 vendor/gems/rspec/examples/pure/custom_formatter.rb create mode 100644 vendor/gems/rspec/examples/pure/dynamic_spec.rb create mode 100644 vendor/gems/rspec/examples/pure/file_accessor.rb create mode 100644 vendor/gems/rspec/examples/pure/file_accessor_spec.rb create mode 100644 vendor/gems/rspec/examples/pure/greeter_spec.rb create mode 100644 vendor/gems/rspec/examples/pure/helper_method_example.rb create mode 100644 vendor/gems/rspec/examples/pure/io_processor.rb create mode 100644 vendor/gems/rspec/examples/pure/io_processor_spec.rb create mode 100644 vendor/gems/rspec/examples/pure/legacy_spec.rb create mode 100644 vendor/gems/rspec/examples/pure/mocking_example.rb create mode 100644 vendor/gems/rspec/examples/pure/multi_threaded_behaviour_runner.rb create mode 100644 vendor/gems/rspec/examples/pure/nested_classes_example.rb create mode 100644 vendor/gems/rspec/examples/pure/partial_mock_example.rb create mode 100644 vendor/gems/rspec/examples/pure/pending_example.rb create mode 100644 vendor/gems/rspec/examples/pure/predicate_example.rb create mode 100644 vendor/gems/rspec/examples/pure/priority.txt create mode 100644 vendor/gems/rspec/examples/pure/shared_example_group_example.rb create mode 100644 vendor/gems/rspec/examples/pure/shared_stack_examples.rb create mode 100644 vendor/gems/rspec/examples/pure/spec_helper.rb create mode 100644 vendor/gems/rspec/examples/pure/stack.rb create mode 100644 vendor/gems/rspec/examples/pure/stack_spec.rb create mode 100644 vendor/gems/rspec/examples/pure/stack_spec_with_nested_example_groups.rb create mode 100644 vendor/gems/rspec/examples/pure/stubbing_example.rb create mode 100644 vendor/gems/rspec/examples/stories/adder.rb create mode 100644 vendor/gems/rspec/examples/stories/addition create mode 100644 vendor/gems/rspec/examples/stories/addition.rb create mode 100644 vendor/gems/rspec/examples/stories/calculator.rb create mode 100644 vendor/gems/rspec/examples/stories/game-of-life/README.txt create mode 100644 vendor/gems/rspec/examples/stories/game-of-life/behaviour/everything.rb create mode 100644 vendor/gems/rspec/examples/stories/game-of-life/behaviour/examples/examples.rb create mode 100644 vendor/gems/rspec/examples/stories/game-of-life/behaviour/examples/game_behaviour.rb create mode 100644 vendor/gems/rspec/examples/stories/game-of-life/behaviour/examples/grid_behaviour.rb create mode 100644 vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/CellsWithLessThanTwoNeighboursDie.story create mode 100644 vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/CellsWithMoreThanThreeNeighboursDie.story create mode 100644 vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/EmptySpacesWithThreeNeighboursCreateACell.story create mode 100644 vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/ICanCreateACell.story create mode 100644 vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/ICanKillACell.story create mode 100644 vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/TheGridWraps.story create mode 100644 vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/create_a_cell.rb create mode 100644 vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/helper.rb create mode 100644 vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/kill_a_cell.rb create mode 100644 vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/steps.rb create mode 100644 vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/stories.rb create mode 100644 vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/stories.txt create mode 100644 vendor/gems/rspec/examples/stories/game-of-life/life.rb create mode 100644 vendor/gems/rspec/examples/stories/game-of-life/life/game.rb create mode 100644 vendor/gems/rspec/examples/stories/game-of-life/life/grid.rb create mode 100644 vendor/gems/rspec/examples/stories/helper.rb create mode 100644 vendor/gems/rspec/examples/stories/steps/addition_steps.rb create mode 100644 vendor/gems/rspec/failing_examples/README.txt create mode 100644 vendor/gems/rspec/failing_examples/diffing_spec.rb create mode 100644 vendor/gems/rspec/failing_examples/failing_autogenerated_docstrings_example.rb create mode 100644 vendor/gems/rspec/failing_examples/failure_in_setup.rb create mode 100644 vendor/gems/rspec/failing_examples/failure_in_teardown.rb create mode 100644 vendor/gems/rspec/failing_examples/mocking_example.rb create mode 100644 vendor/gems/rspec/failing_examples/mocking_with_flexmock.rb create mode 100644 vendor/gems/rspec/failing_examples/mocking_with_mocha.rb create mode 100644 vendor/gems/rspec/failing_examples/mocking_with_rr.rb create mode 100644 vendor/gems/rspec/failing_examples/partial_mock_example.rb create mode 100644 vendor/gems/rspec/failing_examples/predicate_example.rb create mode 100644 vendor/gems/rspec/failing_examples/raising_example.rb create mode 100644 vendor/gems/rspec/failing_examples/spec_helper.rb create mode 100644 vendor/gems/rspec/failing_examples/syntax_error_example.rb create mode 100644 vendor/gems/rspec/failing_examples/team_spec.rb create mode 100644 vendor/gems/rspec/failing_examples/timeout_behaviour.rb create mode 100644 vendor/gems/rspec/lib/autotest/discover.rb create mode 100644 vendor/gems/rspec/lib/autotest/rspec.rb create mode 100644 vendor/gems/rspec/lib/spec.rb create mode 100644 vendor/gems/rspec/lib/spec/example.rb create mode 100755 vendor/gems/rspec/lib/spec/example/configuration.rb create mode 100644 vendor/gems/rspec/lib/spec/example/errors.rb create mode 100644 vendor/gems/rspec/lib/spec/example/example_group.rb create mode 100755 vendor/gems/rspec/lib/spec/example/example_group_factory.rb create mode 100644 vendor/gems/rspec/lib/spec/example/example_group_methods.rb create mode 100755 vendor/gems/rspec/lib/spec/example/example_matcher.rb create mode 100644 vendor/gems/rspec/lib/spec/example/example_methods.rb create mode 100644 vendor/gems/rspec/lib/spec/example/module_reopening_fix.rb create mode 100644 vendor/gems/rspec/lib/spec/example/pending.rb create mode 100644 vendor/gems/rspec/lib/spec/example/shared_example_group.rb create mode 100644 vendor/gems/rspec/lib/spec/expectations.rb create mode 100644 vendor/gems/rspec/lib/spec/expectations/differs/default.rb create mode 100644 vendor/gems/rspec/lib/spec/expectations/errors.rb create mode 100644 vendor/gems/rspec/lib/spec/expectations/extensions.rb create mode 100644 vendor/gems/rspec/lib/spec/expectations/extensions/object.rb create mode 100644 vendor/gems/rspec/lib/spec/expectations/extensions/string_and_symbol.rb create mode 100644 vendor/gems/rspec/lib/spec/expectations/handler.rb create mode 100755 vendor/gems/rspec/lib/spec/extensions.rb create mode 100644 vendor/gems/rspec/lib/spec/extensions/class.rb create mode 100644 vendor/gems/rspec/lib/spec/extensions/main.rb create mode 100755 vendor/gems/rspec/lib/spec/extensions/object.rb create mode 100644 vendor/gems/rspec/lib/spec/interop/test.rb create mode 100644 vendor/gems/rspec/lib/spec/interop/test/unit/autorunner.rb create mode 100644 vendor/gems/rspec/lib/spec/interop/test/unit/testcase.rb create mode 100644 vendor/gems/rspec/lib/spec/interop/test/unit/testresult.rb create mode 100644 vendor/gems/rspec/lib/spec/interop/test/unit/testsuite_adapter.rb create mode 100644 vendor/gems/rspec/lib/spec/interop/test/unit/ui/console/testrunner.rb create mode 100644 vendor/gems/rspec/lib/spec/matchers.rb create mode 100644 vendor/gems/rspec/lib/spec/matchers/be.rb create mode 100644 vendor/gems/rspec/lib/spec/matchers/be_close.rb create mode 100644 vendor/gems/rspec/lib/spec/matchers/change.rb create mode 100644 vendor/gems/rspec/lib/spec/matchers/eql.rb create mode 100644 vendor/gems/rspec/lib/spec/matchers/equal.rb create mode 100644 vendor/gems/rspec/lib/spec/matchers/exist.rb create mode 100644 vendor/gems/rspec/lib/spec/matchers/has.rb create mode 100644 vendor/gems/rspec/lib/spec/matchers/have.rb create mode 100644 vendor/gems/rspec/lib/spec/matchers/include.rb create mode 100644 vendor/gems/rspec/lib/spec/matchers/match.rb create mode 100755 vendor/gems/rspec/lib/spec/matchers/operator_matcher.rb create mode 100644 vendor/gems/rspec/lib/spec/matchers/raise_error.rb create mode 100644 vendor/gems/rspec/lib/spec/matchers/respond_to.rb create mode 100644 vendor/gems/rspec/lib/spec/matchers/satisfy.rb create mode 100644 vendor/gems/rspec/lib/spec/matchers/simple_matcher.rb create mode 100644 vendor/gems/rspec/lib/spec/matchers/throw_symbol.rb create mode 100644 vendor/gems/rspec/lib/spec/mocks.rb create mode 100644 vendor/gems/rspec/lib/spec/mocks/argument_constraint_matchers.rb create mode 100644 vendor/gems/rspec/lib/spec/mocks/argument_expectation.rb create mode 100644 vendor/gems/rspec/lib/spec/mocks/error_generator.rb create mode 100644 vendor/gems/rspec/lib/spec/mocks/errors.rb create mode 100644 vendor/gems/rspec/lib/spec/mocks/extensions/object.rb create mode 100644 vendor/gems/rspec/lib/spec/mocks/message_expectation.rb create mode 100644 vendor/gems/rspec/lib/spec/mocks/methods.rb create mode 100644 vendor/gems/rspec/lib/spec/mocks/mock.rb create mode 100644 vendor/gems/rspec/lib/spec/mocks/order_group.rb create mode 100644 vendor/gems/rspec/lib/spec/mocks/proxy.rb create mode 100644 vendor/gems/rspec/lib/spec/mocks/space.rb create mode 100644 vendor/gems/rspec/lib/spec/mocks/spec_methods.rb create mode 100644 vendor/gems/rspec/lib/spec/rake/spectask.rb create mode 100644 vendor/gems/rspec/lib/spec/rake/verify_rcov.rb create mode 100644 vendor/gems/rspec/lib/spec/runner.rb create mode 100644 vendor/gems/rspec/lib/spec/runner/backtrace_tweaker.rb create mode 100644 vendor/gems/rspec/lib/spec/runner/class_and_arguments_parser.rb create mode 100644 vendor/gems/rspec/lib/spec/runner/command_line.rb create mode 100644 vendor/gems/rspec/lib/spec/runner/drb_command_line.rb create mode 100644 vendor/gems/rspec/lib/spec/runner/example_group_runner.rb create mode 100644 vendor/gems/rspec/lib/spec/runner/formatter/base_formatter.rb create mode 100644 vendor/gems/rspec/lib/spec/runner/formatter/base_text_formatter.rb create mode 100644 vendor/gems/rspec/lib/spec/runner/formatter/failing_example_groups_formatter.rb create mode 100644 vendor/gems/rspec/lib/spec/runner/formatter/failing_examples_formatter.rb create mode 100644 vendor/gems/rspec/lib/spec/runner/formatter/html_formatter.rb create mode 100644 vendor/gems/rspec/lib/spec/runner/formatter/profile_formatter.rb create mode 100644 vendor/gems/rspec/lib/spec/runner/formatter/progress_bar_formatter.rb create mode 100644 vendor/gems/rspec/lib/spec/runner/formatter/snippet_extractor.rb create mode 100644 vendor/gems/rspec/lib/spec/runner/formatter/specdoc_formatter.rb create mode 100644 vendor/gems/rspec/lib/spec/runner/formatter/story/html_formatter.rb create mode 100644 vendor/gems/rspec/lib/spec/runner/formatter/story/plain_text_formatter.rb create mode 100644 vendor/gems/rspec/lib/spec/runner/formatter/text_mate_formatter.rb create mode 100644 vendor/gems/rspec/lib/spec/runner/heckle_runner.rb create mode 100644 vendor/gems/rspec/lib/spec/runner/heckle_runner_unsupported.rb create mode 100644 vendor/gems/rspec/lib/spec/runner/option_parser.rb create mode 100644 vendor/gems/rspec/lib/spec/runner/options.rb create mode 100644 vendor/gems/rspec/lib/spec/runner/reporter.rb create mode 100644 vendor/gems/rspec/lib/spec/runner/spec_parser.rb create mode 100644 vendor/gems/rspec/lib/spec/story.rb create mode 100644 vendor/gems/rspec/lib/spec/story/extensions.rb create mode 100644 vendor/gems/rspec/lib/spec/story/extensions/main.rb create mode 100644 vendor/gems/rspec/lib/spec/story/extensions/regexp.rb create mode 100644 vendor/gems/rspec/lib/spec/story/extensions/string.rb create mode 100644 vendor/gems/rspec/lib/spec/story/given_scenario.rb create mode 100644 vendor/gems/rspec/lib/spec/story/runner.rb create mode 100644 vendor/gems/rspec/lib/spec/story/runner/plain_text_story_runner.rb create mode 100644 vendor/gems/rspec/lib/spec/story/runner/scenario_collector.rb create mode 100644 vendor/gems/rspec/lib/spec/story/runner/scenario_runner.rb create mode 100644 vendor/gems/rspec/lib/spec/story/runner/story_mediator.rb create mode 100644 vendor/gems/rspec/lib/spec/story/runner/story_parser.rb create mode 100644 vendor/gems/rspec/lib/spec/story/runner/story_runner.rb create mode 100644 vendor/gems/rspec/lib/spec/story/scenario.rb create mode 100644 vendor/gems/rspec/lib/spec/story/step.rb create mode 100644 vendor/gems/rspec/lib/spec/story/step_group.rb create mode 100644 vendor/gems/rspec/lib/spec/story/step_mother.rb create mode 100644 vendor/gems/rspec/lib/spec/story/story.rb create mode 100644 vendor/gems/rspec/lib/spec/story/world.rb create mode 100644 vendor/gems/rspec/lib/spec/translator.rb create mode 100644 vendor/gems/rspec/lib/spec/version.rb create mode 100644 vendor/gems/rspec/plugins/mock_frameworks/flexmock.rb create mode 100644 vendor/gems/rspec/plugins/mock_frameworks/mocha.rb create mode 100644 vendor/gems/rspec/plugins/mock_frameworks/rr.rb create mode 100644 vendor/gems/rspec/plugins/mock_frameworks/rspec.rb create mode 100644 vendor/gems/rspec/pre_commit/lib/pre_commit.rb create mode 100644 vendor/gems/rspec/pre_commit/lib/pre_commit/core.rb create mode 100644 vendor/gems/rspec/pre_commit/lib/pre_commit/pre_commit.rb create mode 100644 vendor/gems/rspec/pre_commit/lib/pre_commit/rspec.rb create mode 100644 vendor/gems/rspec/pre_commit/lib/pre_commit/rspec_on_rails.rb create mode 100644 vendor/gems/rspec/pre_commit/spec/pre_commit/pre_commit_spec.rb create mode 100644 vendor/gems/rspec/pre_commit/spec/pre_commit/rspec_on_rails_spec.rb create mode 100644 vendor/gems/rspec/pre_commit/spec/spec_helper.rb create mode 100644 vendor/gems/rspec/pre_commit/spec/spec_suite.rb create mode 100644 vendor/gems/rspec/rake_tasks/examples.rake create mode 100644 vendor/gems/rspec/rake_tasks/examples_with_rcov.rake create mode 100644 vendor/gems/rspec/rake_tasks/failing_examples_with_html.rake create mode 100644 vendor/gems/rspec/rake_tasks/verify_rcov.rake create mode 100644 vendor/gems/rspec/spec/README.jruby create mode 100644 vendor/gems/rspec/spec/autotest/discover_spec.rb create mode 100644 vendor/gems/rspec/spec/autotest/rspec_spec.rb create mode 100644 vendor/gems/rspec/spec/autotest_helper.rb create mode 100644 vendor/gems/rspec/spec/autotest_matchers.rb create mode 100644 vendor/gems/rspec/spec/rspec_suite.rb create mode 100644 vendor/gems/rspec/spec/ruby_forker.rb create mode 100644 vendor/gems/rspec/spec/spec.opts create mode 100755 vendor/gems/rspec/spec/spec/example/configuration_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/example/example_group_class_definition_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/example/example_group_factory_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/example/example_group_methods_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/example/example_group_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/example/example_matcher_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/example/example_methods_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/example/example_runner_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/example/example_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/example/nested_example_group_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/example/pending_module_spec.rb create mode 100755 vendor/gems/rspec/spec/spec/example/predicate_matcher_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/example/shared_example_group_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/example/subclassing_example_group_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/expectations/differs/default_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/expectations/extensions/object_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/expectations/fail_with_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/extensions/main_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/interop/test/unit/resources/spec_that_fails.rb create mode 100644 vendor/gems/rspec/spec/spec/interop/test/unit/resources/spec_that_passes.rb create mode 100644 vendor/gems/rspec/spec/spec/interop/test/unit/resources/spec_with_errors.rb create mode 100644 vendor/gems/rspec/spec/spec/interop/test/unit/resources/test_case_that_fails.rb create mode 100644 vendor/gems/rspec/spec/spec/interop/test/unit/resources/test_case_that_passes.rb create mode 100644 vendor/gems/rspec/spec/spec/interop/test/unit/resources/test_case_with_errors.rb create mode 100644 vendor/gems/rspec/spec/spec/interop/test/unit/resources/testsuite_adapter_spec_with_test_unit.rb create mode 100644 vendor/gems/rspec/spec/spec/interop/test/unit/spec_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/interop/test/unit/test_unit_spec_helper.rb create mode 100644 vendor/gems/rspec/spec/spec/interop/test/unit/testcase_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/interop/test/unit/testsuite_adapter_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/matchers/be_close_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/matchers/be_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/matchers/change_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/matchers/description_generation_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/matchers/eql_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/matchers/equal_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/matchers/exist_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/matchers/handler_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/matchers/has_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/matchers/have_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/matchers/include_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/matchers/match_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/matchers/matcher_methods_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/matchers/mock_constraint_matchers_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/matchers/operator_matcher_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/matchers/raise_error_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/matchers/respond_to_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/matchers/satisfy_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/matchers/simple_matcher_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/matchers/throw_symbol_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/mocks/any_number_of_times_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/mocks/argument_expectation_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/mocks/at_least_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/mocks/at_most_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/mocks/bug_report_10260_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/mocks/bug_report_10263.rb create mode 100644 vendor/gems/rspec/spec/spec/mocks/bug_report_11545_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/mocks/bug_report_15719_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/mocks/bug_report_7611_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/mocks/bug_report_7805_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/mocks/bug_report_8165_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/mocks/bug_report_8302_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/mocks/failing_mock_argument_constraints_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/mocks/mock_ordering_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/mocks/mock_space_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/mocks/mock_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/mocks/multiple_return_value_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/mocks/null_object_mock_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/mocks/once_counts_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/mocks/options_hash_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/mocks/partial_mock_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/mocks/partial_mock_using_mocks_directly_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/mocks/passing_mock_argument_constraints_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/mocks/precise_counts_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/mocks/record_messages_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/mocks/stub_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/mocks/twice_counts_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/package/bin_spec_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/runner/class_and_argument_parser_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/runner/command_line_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/runner/drb_command_line_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/runner/empty_file.txt create mode 100644 vendor/gems/rspec/spec/spec/runner/examples.txt create mode 100644 vendor/gems/rspec/spec/spec/runner/execution_context_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/runner/failed.txt create mode 100644 vendor/gems/rspec/spec/spec/runner/formatter/failing_example_groups_formatter_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/runner/formatter/failing_examples_formatter_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/runner/formatter/html_formatted-1.8.4.html create mode 100644 vendor/gems/rspec/spec/spec/runner/formatter/html_formatted-1.8.5-jruby.html create mode 100644 vendor/gems/rspec/spec/spec/runner/formatter/html_formatted-1.8.5.html create mode 100644 vendor/gems/rspec/spec/spec/runner/formatter/html_formatted-1.8.6-jruby.html create mode 100644 vendor/gems/rspec/spec/spec/runner/formatter/html_formatted-1.8.6.html create mode 100644 vendor/gems/rspec/spec/spec/runner/formatter/html_formatter_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/runner/formatter/profile_formatter_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/runner/formatter/progress_bar_formatter_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/runner/formatter/snippet_extractor_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/runner/formatter/spec_mate_formatter_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/runner/formatter/specdoc_formatter_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/runner/formatter/story/html_formatter_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/runner/formatter/story/plain_text_formatter_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/runner/formatter/text_mate_formatted-1.8.4.html create mode 100644 vendor/gems/rspec/spec/spec/runner/formatter/text_mate_formatted-1.8.6.html create mode 100644 vendor/gems/rspec/spec/spec/runner/heckle_runner_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/runner/heckler_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/runner/noisy_backtrace_tweaker_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/runner/option_parser_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/runner/options_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/runner/output_one_time_fixture.rb create mode 100644 vendor/gems/rspec/spec/spec/runner/output_one_time_fixture_runner.rb create mode 100644 vendor/gems/rspec/spec/spec/runner/output_one_time_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/runner/quiet_backtrace_tweaker_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/runner/reporter_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/runner/resources/a_bar.rb create mode 100644 vendor/gems/rspec/spec/spec/runner/resources/a_foo.rb create mode 100644 vendor/gems/rspec/spec/spec/runner/resources/a_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/runner/spec.opts create mode 100644 vendor/gems/rspec/spec/spec/runner/spec_parser/spec_parser_fixture.rb create mode 100644 vendor/gems/rspec/spec/spec/runner/spec_parser_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/runner/spec_spaced.opts create mode 100644 vendor/gems/rspec/spec/spec/runner_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/spec_classes.rb create mode 100644 vendor/gems/rspec/spec/spec/story/builders.rb create mode 100644 vendor/gems/rspec/spec/spec/story/extensions/main_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/story/extensions_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/story/given_scenario_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/story/runner/plain_text_story_runner_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/story/runner/scenario_collector_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/story/runner/scenario_runner_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/story/runner/story_mediator_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/story/runner/story_parser_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/story/runner/story_runner_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/story/runner_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/story/scenario_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/story/step_group_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/story/step_mother_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/story/step_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/story/story_helper.rb create mode 100644 vendor/gems/rspec/spec/spec/story/story_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/story/world_spec.rb create mode 100644 vendor/gems/rspec/spec/spec/translator_spec.rb create mode 100644 vendor/gems/rspec/spec/spec_helper.rb create mode 100644 vendor/gems/rspec/stories/all.rb create mode 100644 vendor/gems/rspec/stories/example_groups/autogenerated_docstrings create mode 100644 vendor/gems/rspec/stories/example_groups/example_group_with_should_methods create mode 100644 vendor/gems/rspec/stories/example_groups/nested_groups create mode 100644 vendor/gems/rspec/stories/example_groups/output create mode 100644 vendor/gems/rspec/stories/example_groups/stories.rb create mode 100644 vendor/gems/rspec/stories/helper.rb create mode 100644 vendor/gems/rspec/stories/interop/examples_and_tests_together create mode 100644 vendor/gems/rspec/stories/interop/stories.rb create mode 100644 vendor/gems/rspec/stories/interop/test_case_with_should_methods create mode 100644 vendor/gems/rspec/stories/pending_stories/README create mode 100644 vendor/gems/rspec/stories/resources/helpers/cmdline.rb create mode 100644 vendor/gems/rspec/stories/resources/helpers/story_helper.rb create mode 100644 vendor/gems/rspec/stories/resources/matchers/smart_match.rb create mode 100644 vendor/gems/rspec/stories/resources/spec/example_group_with_should_methods.rb create mode 100644 vendor/gems/rspec/stories/resources/spec/simple_spec.rb create mode 100644 vendor/gems/rspec/stories/resources/steps/running_rspec.rb create mode 100644 vendor/gems/rspec/stories/resources/stories/failing_story.rb create mode 100644 vendor/gems/rspec/stories/resources/test/spec_and_test_together.rb create mode 100644 vendor/gems/rspec/stories/resources/test/test_case_with_should_methods.rb diff --git a/lib/facter/ipmess.rb b/lib/facter/ipmess.rb index b042073e4d..1500049884 100644 --- a/lib/facter/ipmess.rb +++ b/lib/facter/ipmess.rb @@ -15,6 +15,8 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA +require 'facter/kernel' + Facter.add(:interfaces) do setcode do output = %x{/sbin/ifconfig -a} @@ -23,7 +25,7 @@ end end -if Facter.kernel == "Linux" +if Facter.value(:kernel) == "Linux" interfaces = nil interfaces = Facter.interfaces.split(" ") @@ -54,7 +56,7 @@ end end -if Facter.kernel == "FreeBSD" || Facter.kernel == "OpenBSD" || Facter.kernel == "NetBSD" +if Facter.value(:kernel) == "FreeBSD" || Facter.value(:kernel) == "OpenBSD" || Facter.value(:kernel) == "NetBSD" interfaces = nil interfaces = Facter.interfaces.split(" ") diff --git a/lib/facter/macosx.rb b/lib/facter/macosx.rb index aec454a5f4..60bd1ad332 100644 --- a/lib/facter/macosx.rb +++ b/lib/facter/macosx.rb @@ -29,7 +29,7 @@ Facter.add("sp_#{fact}") do confine :kernel => :darwin setcode do - value + value.to_s end end end @@ -38,7 +38,7 @@ Facter.add("sp_#{fact}") do confine :kernel => :darwin setcode do - value + value.to_s end end end diff --git a/spec/Rakefile b/spec/Rakefile new file mode 100644 index 0000000000..e2996f64f1 --- /dev/null +++ b/spec/Rakefile @@ -0,0 +1,18 @@ +require File.join(File.dirname(__FILE__), "spec_helper.rb") +require 'rake' +require 'spec/rake/spectask' + +basedir = File.dirname(__FILE__) +puppetlibdir = File.join(basedir, "../lib") +puppettestlibdir = File.join(basedir, "../test/lib") +speclibdir = File.join(basedir, "lib") + +libs = [puppetlibdir, puppettestlibdir, speclibdir] +desc "Run all specs" +Spec::Rake::SpecTask.new('all') do |t| + t.spec_files = FileList['integration/**/*.rb', 'unit/**/*.rb'] + t.libs = libs + t.spec_opts = ['--options', 'spec.opts'] +end + +task :default => [:all] diff --git a/spec/spec.opts b/spec/spec.opts new file mode 100644 index 0000000000..2f9bf0d0ac --- /dev/null +++ b/spec/spec.opts @@ -0,0 +1,3 @@ +--colour +--loadby +mtime diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000000..9f91c97557 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,25 @@ +dir = File.expand_path(File.dirname(__FILE__)) + +$LOAD_PATH.unshift("#{dir}/") +$LOAD_PATH.unshift("#{dir}/../lib") + +# include any gems in vendor/gems +Dir["#{dir}/../vendor/gems/**"].each do |path| + libpath = File.join(path, "lib") + if File.directory?(libpath) + $LOAD_PATH.unshift(libpath) + else + $LOAD_PATH.unshift(path) + end +end + +require 'mocha' +require 'spec' +require 'facter' + +# load any monkey-patches +Dir["#{dir}/monkey_patches/*.rb"].map { |file| require file } + +Spec::Runner.configure do |config| + config.mock_with :mocha +end diff --git a/spec/unit/.facter.rb.swp b/spec/unit/.facter.rb.swp new file mode 100644 index 0000000000000000000000000000000000000000..27a01307338692eb90f94ae0f1d7fd60df1d96de GIT binary patch literal 32768 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 literal 0 HcmV?d00001 diff --git a/tests/tc_simple.rb b/spec/unit/facter.rb similarity index 97% rename from tests/tc_simple.rb rename to spec/unit/facter.rb index ee5d0afce0..76b6ab20cc 100755 --- a/tests/tc_simple.rb +++ b/spec/unit/facter.rb @@ -1,21 +1,12 @@ #!/usr/bin/env ruby -$facterbase = File.dirname(File.dirname(__FILE__)) -if $facterbase == "." - $facterbase = ".." -end -libdir = File.join($facterbase, "lib") -$:.unshift libdir - -require 'test/unit' -require 'facter' -require 'fileutils' +require File.dirname(__FILE__) + '/../spec_helper' -if __FILE__ == $0 - Facter.debugging(true) -end +#if __FILE__ == $0 +# Facter.debugging(true) +#end -class TestFacter < Test::Unit::TestCase +describe Facter do def tearhook(&block) @tearhooks << block end @@ -631,9 +622,8 @@ def test_ssh_keys end # #33 Make sure we only get one mac address - def test_one_macaddress - assert(! Facter.value(:macaddress).include?(" "), - "Got multiple mac addresses") + it "should only return one mac address" do + Facter.value(:macaddress).should_not be_include(" ") end def test_flush diff --git a/tests/tc_facterbin.rb b/tests/tc_facterbin.rb deleted file mode 100755 index 36e3c64125..0000000000 --- a/tests/tc_facterbin.rb +++ /dev/null @@ -1,86 +0,0 @@ -#! /usr/bin/env ruby - -$facterbase = File.dirname(File.dirname(__FILE__)) -if $facterbase == "." and Dir.getwd =~ /tests$/ - $facterbase = ".." -end -libdir = File.join($facterbase, "lib") - -$:.unshift libdir - -require 'facter' -require 'test/unit' - -# add the bin directory to our search path -ENV["PATH"] = File.join($facterbase, "bin") + ":" + ENV["PATH"] - -# and then the library directory -if ENV["RUBYLIB"] - ENV["RUBYLIB"] += ":" + libdir -else - ENV["RUBYLIB"] = libdir -end - -class TestFacterBin < Test::Unit::TestCase - def test_version - output = nil - assert_nothing_raised { - output = %x{facter --version 2>&1}.chomp - } - assert(output == Facter.version) - end - - def test_output - output = nil - assert_nothing_raised { - output = %x{facter 2>&1}.chomp - } - - hash = output.split("\n").inject({}) do |h, line| - name, value = line.split(" => ") - h[name.downcase] = value - h - end - - Facter.each do |name, fact| - next if name.to_s =~ /memory/ - - assert(hash.include?(name), - "Did not get %s" % name) - - assert_equal(fact, hash[name], "%s was not equal" % name) - end - end - - # Verify we don't print much when they just want a single fact. - def test_simpleoutput - output = nil - assert_nothing_raised { - output = %x{facter kernel 2>&1}.chomp - } - - assert(output !~ / => /, "Output includes the farrow thing") - end - - def test_yaml - out = nil - assert_nothing_raised { - out = %x{facter -y} - } - - require 'yaml' - result = nil - assert_nothing_raised { - result = YAML.load(out) - } - - assert_instance_of(Hash, result) - result.each do |name, value| - # These change too frequently. - next if name.to_s.downcase =~ /mem/ - assert_equal(value, Facter.value(name)) - end - end -end - -# $Id$ diff --git a/vendor/gems/mocha-0.5.6/._RELEASE b/vendor/gems/mocha-0.5.6/._RELEASE new file mode 100644 index 0000000000000000000000000000000000000000..12bf79cb2e6581f54b117e5e6287cb0de7648084 GIT binary patch literal 176 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aUBqY_#1$j2;dkJ62u_IcMhl&q+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r2<(d)(W)% Dzy%mg literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/._Rakefile b/vendor/gems/mocha-0.5.6/._Rakefile new file mode 100644 index 0000000000000000000000000000000000000000..22220c1ee8e5ecf3f1cfd2111329578866a2f715 GIT binary patch literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_IcLAsrq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!WSS#dY=A{Ce=GF?e E0NQ#Oj{pDw literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/COPYING b/vendor/gems/mocha-0.5.6/COPYING new file mode 100644 index 0000000000..8f74d7116a --- /dev/null +++ b/vendor/gems/mocha-0.5.6/COPYING @@ -0,0 +1,3 @@ +Copyright Revieworld Ltd. 2006 + +You may use, copy and redistribute this library under the same terms as Ruby itself (see http://www.ruby-lang.org/en/LICENSE.txt) or under the MIT license (see MIT-LICENSE file). diff --git a/vendor/gems/mocha-0.5.6/MIT-LICENSE b/vendor/gems/mocha-0.5.6/MIT-LICENSE new file mode 100644 index 0000000000..fa4efe793a --- /dev/null +++ b/vendor/gems/mocha-0.5.6/MIT-LICENSE @@ -0,0 +1,7 @@ +Copyright (c) 2006 Revieworld Ltd. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/README b/vendor/gems/mocha-0.5.6/README new file mode 100644 index 0000000000..262c6ec27c --- /dev/null +++ b/vendor/gems/mocha-0.5.6/README @@ -0,0 +1,35 @@ += Mocha + +Mocha is a library for mocking and stubbing using a syntax like that of JMock[http://www.jmock.org], and SchMock[http://rubyforge.org/projects/schmock]. Most commonly Mocha is used in conjunction with Test::Unit[http://www.ruby-doc.org/core/classes/Test/Unit.html], but it can be used in other contexts. + +One of its main advantages is that it allows you to mock and stub methods on _real_ (non-mock) classes and instances. You can for example stub ActiveRecord[http://api.rubyonrails.com/classes/ActiveRecord/Base.html] instance methods like +create+, +save+, +destroy+ and even class methods like +find+ to avoid hitting the database in unit tests. + +Mocha provides a unified, simple and readable syntax for both traditional mocking and for mocking with _real_ objects. + +Mocha has been harvested from projects at Reevoo[http://www.reevoo.com] by me (James[http://blog.floehopper.org]) and my colleagues Ben[http://www.reevoo.com/blogs/bengriffiths], Chris[http://blog.seagul.co.uk] and Paul[http://po-ru.com]. Mocha is in use on real-world Rails[http://www.rubyonrails.org] projects. + +== Download and Installation + +Install the gem with the following command... + + $ gem install mocha + +Or install the Rails[http://www.rubyonrails.org] plugin... + + $ script/plugin install svn://rubyforge.org/var/svn/mocha/trunk + +Or download Mocha from here - http://rubyforge.org/projects/mocha + +== Examples + +* Quick Start - {Usage Examples}[link:examples/misc.html] +* Traditional mocking - {Star Trek Example}[link:examples/mocha.html] +* Setting expectations on real classes - {Order Example}[link:examples/stubba.html] +* More examples on {Floehopper's Blog}[http://blog.floehopper.org] +* {Mailing List Archives}[http://rubyforge.org/pipermail/mocha-developer/] + +== License + +Copyright Revieworld Ltd. 2006 + +You may use, copy and redistribute this library under the same terms as {Ruby itself}[http://www.ruby-lang.org/en/LICENSE.txt] or under the {MIT license}[http://mocha.rubyforge.org/files/MIT-LICENSE.html]. \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/RELEASE b/vendor/gems/mocha-0.5.6/RELEASE new file mode 100644 index 0000000000..0e8fb85737 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/RELEASE @@ -0,0 +1,188 @@ += 0.5.5 (r167) + +- Renamed Matches parameter matcher to RegexpMatches for clarity. +- Added noframes tag to rdoc index to assist Google. + += 0.5.4 (r166) + +- Added matches parameter matcher for matching regular expressions. + += 0.5.3 (r165) + +- Attempt to fix packaging problems by switching to newer version (1.15.1) of gnutar and setting COPY_EXTENDED_ATTRIBUTES_DISABLE environment variable. +- Removed unused ExpectationSequenceError exception. +- Added instance_of and kind_of parameter matchers. +- Added Google Webmaster meta tag to rdoc template header. +- Put Google Webmaster meta tag in the right header i.e. the one for the index page. + += 0.5.2 (r159) + +- Fix bug 11885 - "never doesn't work with stub_everything" submitted by Alexander Lang. In fixing this bug, also fixed undiscoverd bug where expected & actual invocation counts were being incorrectly reported which seems to have been introduced when fixes were added for invocation dispatch (see MockedMethodDispatchAcceptanceTest). +- Previously when an expectation did not allow more invocations, it was treated as not matching. Now we prefer matching expectations which allow more invocations, but still match expectations which cannot allow more invocations. I think this may be overcomplicating things, but let's see how it goes. + += 0.5.1 (r149) + +- Fixed bug #11583 "Mocha 0.5.0 throwing unexpected warnings". Also switched on ruby warning for all rake test tasks. Fixed majority of warnings, but some left to fix. + += 0.5.0 (r147) + +- Parameter Matchers - I’ve added a few Hamcrest-style parameter matchers which are designed to be used inside Expectation#with. The following matchers are currently available: anything(), includes(), has_key(), has_value(), has_entry(), all_of() & any_of(). More to follow soon. The idea is eventually to get rid of the nasty parameter_block option on Expectation#with. + + object = mock() + object.expects(:method).with(has_key('key_1')) + object.method('key_1' => 1, 'key_2' => 2) + # no verification error raised + + object = mock() + object.expects(:method).with(has_key('key_1')) + object.method('key_2' => 2) + # verification error raised, because method was not called with Hash containing key: 'key_1' + +- Values Returned and Exceptions Raised on Consecutive Invocations - Allow multiple calls to Expectation#returns and Expectation#raises to build up a sequence of responses to invocations on the mock. Added syntactic sugar method Expectation#then to allow more readable expectations. + + object = mock() + object.stubs(:method).returns(1, 2).then.raises(Exception).then.returns(4) + object.method # => 1 + object.method # => 2 + object.method # => raises exception of class Exception + object.method # => 4 + +- Yields on Consecutive Invocations - Allow multiple calls to yields on single expectation to allow yield parameters to be specified for consecutive invocations. + + object = mock() + object.stubs(:method).yields(1, 2).then.yields(3) + object.method { |*values| p values } # => [1, 2] + object.method { |*values| p values } # => [3] + +- Multiple Yields on Single Invocation - Added Expectation#multiple_yields to allow a mocked or stubbed method to yield multiple times for a single invocation. + + object = mock() + object.stubs(:method).multiple_yields([1, 2], [3]) + object.method { |*values| p values } # => [1, 2] # => [3] + +- Invocation Dispatch - Expectations were already being matched in reverse order i.e. the most recently defined one was being found first. This is still the case, but we now stop matching an expectation when its maximum number of expected invocations is reached. c.f. JMock v1. A stub will never stop matching by default. Hopefully this means we can soon get rid of the need to pass a Proc to Expectation#returns. + + object = mock() + object.stubs(:method).returns(2) + object.expects(:method).once.returns(1) + object.method # => 1 + object.method # => 2 + object.method # => 2 + # no verification error raised + + # The following should still work... + + Time.stubs(:now).returns(Time.parse('Mon Jan 01 00:00:00 UTC 2007')) + Time.now # => Mon Jan 01 00:00:00 UTC 2007 + Time.stubs(:now).returns(Time.parse('Thu Feb 01 00:00:00 UTC 2007')) + Time.now # => Thu Feb 01 00:00:00 UTC 2007 + +- Deprecate passing an instance of Proc to Expectation#returns. +- Explicitly include all Rakefile dependencies in project. +- Fixed old Stubba example. +- Fix so that it is possible for a stubbed method to raise an Interrupt exception without a message in Ruby 1.8.6 +- Added responds_like and quacks_like. +- Capture standard object methods before Mocha adds any. +- Added Expectation#once method to make interface less surprising. +- Use Rake::TestTask to run tests. Created three separate tasks to run unit, integration & acceptance tests. Split inspect_test into one file per TestCase. Deleted superfluous all_tests file. +- Fiddled with mocha_inspect and tests to give more sensible results on x86 platform. +- Fixed bug #7834 "infinite_range.rb makes incorrect assumption about to_f" logged by James Moore. + += 0.4.0 (r92) + +- Allow naming of mocks (patch from Chris Roos). +- Specify multiple return values for consecutive calls. +- Improved consistency of expectation error messages. +- Allow mocking of Object instance methods e.g. kind_of?, type. +- Provide aliased versions of #expects and #stubs to allow mocking of these methods. +- Added at_least, at_most, at_most_once methods to expectation. +- Allow expects and stubs to take a hash of method and return values. +- Eliminate warning: "instance variable @yield not initialized" (patch from Xavier Shay). +- Restore instance methods on partial mocks (patch from Chris Roos). +- Allow stubbing of a method with non-word characters in its name (patch from Paul Battley). +- Removed coupling to Test::Unit. +- Allow specified exception instance to be raised (patch from Chris Roos). +- Make mock object_id appear in hex like normal Ruby inspect (patch from Paul Battley). +- Fix path to object.rb in rdoc rake task (patch from Tomas Pospisek). +- Reverse order in which expectations are matched, so that last expectation is matched first. This allows e.g. a call to #stubs to be effectively overridden by a call to #expects (patch from Tobias Lutke). +- Stubba & SmartTestCase modules incorporated into Mocha module so only need to require 'mocha' - no longer need to require 'stubba'. +- AutoMocha removed. + += 0.3.3 + +- Quick bug fix to restore instance methods on partial mocks (for Kevin Clark). + += 0.3.2 + +- Examples added. + += 0.3.1 + +- Dual licensing with MIT license added. + += 0.3.0 + +* Rails plugin. +* Auto-verify for expectations on concrete classes. +* Include each expectation verification in the test result assertion count. +* Filter out noise from assertion backtraces. +* Point assertion backtrace to line where failing expectation was created. +* New yields method for expectations. +* Create stubs which stub all method calls. +* Mocks now respond_to? expected methods. + += 0.2.1 + +* Rename MochaAcceptanceTest::Rover#move method to avoid conflict with Rake (in Ruby 1.8.4 only?) + += 0.2.0 + +* Small change to SetupAndTeardown#teardown_stubs suggested by Luke Redpath (http://www.lukeredpath.co.uk) to allow use of Stubba with RSpec (http://rspec.rubyforge.org). +* Reorganized directory structure and extracted addition of setup and teardown methods into SmartTestCase mini-library. +* Addition of auto-verify for Mocha (but not Stubba). This means there is more significance in the choice of expects or stubs in that any expects on a mock will automatically get verified. + +So instead of... + + wotsit = Mocha.new + wotsit.expects(:thingummy).with(5).returns(10) + doobrey = Doobrey.new(wotsit) + doobrey.hoojamaflip + wotsit.verify + +you need to do... + + wotsit = mock() + wotsit.expects(:thingummy).with(5).returns(10) + doobrey = Doobrey.new(wotsit) + doobrey.hoojamaflip + # no need to verify + +There are also shortcuts as follows... + +instead of... + + wotsit = Mocha.new + wotsit.expects(:thingummy).returns(10) + wotsit.expects(:summat).returns(25) + +you can have... + + wotsit = mock(:thingummy => 5, :summat => 25) + +and instead of... + + wotsit = Mocha.new + wotsit.stubs(:thingummy).returns(10) + wotsit.stubs(:summat).returns(25) + +you can have... + + wotsit = stub(:thingummy => 5, :summat => 25) + += 0.1.2 + +* Minor tweaks + += 0.1.1 + +* Initial release. diff --git a/vendor/gems/mocha-0.5.6/Rakefile b/vendor/gems/mocha-0.5.6/Rakefile new file mode 100644 index 0000000000..2e2f7287a6 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/Rakefile @@ -0,0 +1,149 @@ +require 'rubygems' +require 'rake/rdoctask' +require 'rake/gempackagetask' +require 'rake/testtask' +require 'rake/contrib/sshpublisher' + +module Mocha + VERSION = "0.5.6" +end + +desc "Run all tests" +task :default => :test_all + +task :test_all => [:test_unit, :test_integration, :test_acceptance] + +desc "Run unit tests" +Rake::TestTask.new(:test_unit) do |t| + t.libs << 'test' + t.test_files = FileList['test/unit/**/*_test.rb'] + t.verbose = true + t.warning = true +end + +desc "Run integration tests" +Rake::TestTask.new(:test_integration) do |t| + t.libs << 'test' + t.test_files = FileList['test/integration/*_test.rb'] + t.verbose = true + t.warning = true +end + +desc "Run acceptance tests" +Rake::TestTask.new(:test_acceptance) do |t| + t.libs << 'test' + t.test_files = FileList['test/acceptance/*_test.rb'] + t.verbose = true + t.warning = true +end + +desc 'Generate RDoc' +Rake::RDocTask.new do |task| + task.main = 'README' + task.title = "Mocha #{Mocha::VERSION}" + task.rdoc_dir = 'doc' + task.template = File.expand_path(File.join(File.dirname(__FILE__), "templates", "html_with_google_analytics")) + task.rdoc_files.include('README', 'RELEASE', 'COPYING', 'MIT-LICENSE', 'agiledox.txt', 'lib/mocha/auto_verify.rb', 'lib/mocha/mock.rb', 'lib/mocha/expectation.rb', 'lib/mocha/object.rb', 'lib/mocha/parameter_matchers.rb', 'lib/mocha/parameter_matchers') +end +task :rdoc => :examples + +desc "Upload RDoc to RubyForge" +task :publish_rdoc => [:rdoc, :examples] do + Rake::SshDirPublisher.new("jamesmead@rubyforge.org", "/var/www/gforge-projects/mocha", "doc").upload +end + +desc "Generate agiledox-like documentation for tests" +file 'agiledox.txt' do + File.open('agiledox.txt', 'w') do |output| + tests = FileList['test/**/*_test.rb'] + tests.each do |file| + m = %r".*/([^/].*)_test.rb".match(file) + output << m[1]+" should:\n" + test_definitions = File::readlines(file).select {|line| line =~ /.*def test.*/} + test_definitions.sort.each do |definition| + m = %r"test_(should_)?(.*)".match(definition) + output << " - "+m[2].gsub(/_/," ") << "\n" + end + end + end +end + +desc "Convert example ruby files to syntax-highlighted html" +task :examples do + $:.unshift File.expand_path(File.join(File.dirname(__FILE__), "vendor", "coderay-0.7.4.215", "lib")) + require 'coderay' + mkdir_p 'doc/examples' + File.open('doc/examples/coderay.css', 'w') do |output| + output << CodeRay::Encoders[:html]::CSS.new.stylesheet + end + ['mocha', 'stubba', 'misc'].each do |filename| + File.open("doc/examples/#{filename}.html", 'w') do |file| + file << "" + file << "" + file << %q() + file << "" + file << "" + file << CodeRay.scan_file("examples/#{filename}.rb").html.div + file << "" + file << "" + end + end +end + +Gem::manage_gems + +specification = Gem::Specification.new do |s| + s.name = "mocha" + s.summary = "Mocking and stubbing library" + s.version = Mocha::VERSION + s.platform = Gem::Platform::RUBY + s.author = 'James Mead' + s.description = <<-EOF + Mocking and stubbing library with JMock/SchMock syntax, which allows mocking and stubbing of methods on real (non-mock) classes. + EOF + s.email = 'mocha-developer@rubyforge.org' + s.homepage = 'http://mocha.rubyforge.org/' + s.rubyforge_project = 'mocha' + + s.has_rdoc = true + s.extra_rdoc_files = ['README', 'COPYING'] + s.rdoc_options << '--title' << 'Mocha' << '--main' << 'README' << '--line-numbers' + + s.autorequire = 'mocha' + s.add_dependency('rake') + s.files = FileList['{lib,test,examples}/**/*.rb', '[A-Z]*'].exclude('TODO').to_a +end + +Rake::GemPackageTask.new(specification) do |package| + package.need_zip = true + package.need_tar = true +end + +task :verify_user do + raise "RUBYFORGE_USER environment variable not set!" unless ENV['RUBYFORGE_USER'] +end + +task :verify_password do + raise "RUBYFORGE_PASSWORD environment variable not set!" unless ENV['RUBYFORGE_PASSWORD'] +end + +desc "Publish package files on RubyForge." +task :publish_packages => [:verify_user, :verify_password, :package] do + $:.unshift File.expand_path(File.join(File.dirname(__FILE__), "vendor", "meta_project-0.4.15", "lib")) + require 'meta_project' + require 'rake/contrib/xforge' + release_files = FileList[ + "pkg/mocha-#{Mocha::VERSION}.gem", + "pkg/mocha-#{Mocha::VERSION}.tgz", + "pkg/mocha-#{Mocha::VERSION}.zip" + ] + + Rake::XForge::Release.new(MetaProject::Project::XForge::RubyForge.new('mocha')) do |release| + release.user_name = ENV['RUBYFORGE_USER'] + release.password = ENV['RUBYFORGE_PASSWORD'] + release.files = release_files.to_a + release.release_name = "Mocha #{Mocha::VERSION}" + release.release_changes = '' + release.release_notes = '' + end +end diff --git a/vendor/gems/mocha-0.5.6/examples/._misc.rb b/vendor/gems/mocha-0.5.6/examples/._misc.rb new file mode 100644 index 0000000000000000000000000000000000000000..c425bb341e324c548c9571bac49be9016ffeb58b GIT binary patch literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_Iw*{yaq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV#CTPx&b=A{CemevZj E0MRNKb^rhX literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/examples/._mocha.rb b/vendor/gems/mocha-0.5.6/examples/._mocha.rb new file mode 100644 index 0000000000000000000000000000000000000000..0c1998db864501a11760efab1f2ed3e42879ce44 GIT binary patch literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_Iw+W~eq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r2<)orq&9z E0MN7;X8-^I literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/examples/._stubba.rb b/vendor/gems/mocha-0.5.6/examples/._stubba.rb new file mode 100644 index 0000000000000000000000000000000000000000..4eae8fa0f126a6cc0f1c74b55dda6c5dc57d7935 GIT binary patch literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_Iw*jaWq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eaNqS}Wva=A{CehL+X} FwE*e;7^eUL literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/examples/misc.rb b/vendor/gems/mocha-0.5.6/examples/misc.rb new file mode 100644 index 0000000000..1cb8b55d02 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/examples/misc.rb @@ -0,0 +1,44 @@ +require 'test/unit' +require 'rubygems' +require 'mocha' + +class MiscExampleTest < Test::Unit::TestCase + + def test_mocking_a_class_method + product = Product.new + Product.expects(:find).with(1).returns(product) + assert_equal product, Product.find(1) + end + + def test_mocking_an_instance_method_on_a_real_object + product = Product.new + product.expects(:save).returns(true) + assert product.save + end + + def test_stubbing_instance_methods_on_real_objects + prices = [stub(:pence => 1000), stub(:pence => 2000)] + product = Product.new + product.stubs(:prices).returns(prices) + assert_equal [1000, 2000], product.prices.collect {|p| p.pence} + end + + def test_stubbing_an_instance_method_on_all_instances_of_a_class + Product.any_instance.stubs(:name).returns('stubbed_name') + product = Product.new + assert_equal 'stubbed_name', product.name + end + + def test_traditional_mocking + object = mock() + object.expects(:expected_method).with(:p1, :p2).returns(:result) + assert_equal :result, object.expected_method(:p1, :p2) + end + + def test_shortcuts + object = stub(:method1 => :result1, :method2 => :result2) + assert_equal :result1, object.method1 + assert_equal :result2, object.method2 + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/examples/mocha.rb b/vendor/gems/mocha-0.5.6/examples/mocha.rb new file mode 100644 index 0000000000..863270d531 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/examples/mocha.rb @@ -0,0 +1,26 @@ +class Enterprise + + def initialize(dilithium) + @dilithium = dilithium + end + + def go(warp_factor) + warp_factor.times { @dilithium.nuke(:anti_matter) } + end + +end + +require 'test/unit' +require 'rubygems' +require 'mocha' + +class EnterpriseTest < Test::Unit::TestCase + + def test_should_boldly_go + dilithium = mock() + dilithium.expects(:nuke).with(:anti_matter).at_least_once # auto-verified at end of test + enterprise = Enterprise.new(dilithium) + enterprise.go(2) + end + +end diff --git a/vendor/gems/mocha-0.5.6/examples/stubba.rb b/vendor/gems/mocha-0.5.6/examples/stubba.rb new file mode 100644 index 0000000000..2788d1b623 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/examples/stubba.rb @@ -0,0 +1,65 @@ +class Order + + attr_accessor :shipped_on + + def total_cost + line_items.inject(0) { |total, line_item| total + line_item.price } + shipping_cost + end + + def total_weight + line_items.inject(0) { |total, line_item| total + line_item.weight } + end + + def shipping_cost + total_weight * 5 + 10 + end + + class << self + + def find_all + # Database.connection.execute('select * from orders... + end + + def number_shipped_since(date) + find_all.select { |order| order.shipped_on > date }.length + end + + def unshipped_value + find_all.inject(0) { |total, order| order.shipped_on ? total : total + order.total_cost } + end + + end + +end + +require 'test/unit' +require 'rubygems' +require 'mocha' + +class OrderTest < Test::Unit::TestCase + + # illustrates stubbing instance method + def test_should_calculate_shipping_cost_based_on_total_weight + order = Order.new + order.stubs(:total_weight).returns(10) + assert_equal 60, order.shipping_cost + end + + # illustrates stubbing class method + def test_should_count_number_of_orders_shipped_after_specified_date + now = Time.now; week_in_secs = 7 * 24 * 60 * 60 + order_1 = Order.new; order_1.shipped_on = now - 1 * week_in_secs + order_2 = Order.new; order_2.shipped_on = now - 3 * week_in_secs + Order.stubs(:find_all).returns([order_1, order_2]) + assert_equal 1, Order.number_shipped_since(now - 2 * week_in_secs) + end + + # illustrates stubbing instance method for all instances of a class + def test_should_calculate_value_of_unshipped_orders + Order.stubs(:find_all).returns([Order.new, Order.new, Order.new]) + Order.any_instance.stubs(:shipped_on).returns(nil) + Order.any_instance.stubs(:total_cost).returns(10) + assert_equal 30, Order.unshipped_value + end + +end diff --git a/vendor/gems/mocha-0.5.6/lib/._mocha.rb b/vendor/gems/mocha-0.5.6/lib/._mocha.rb new file mode 100644 index 0000000000000000000000000000000000000000..cf6ff4839c69367829fbee64b519be58845cf54e GIT binary patch literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_I%>`5n(lG;w zCDF7oBE&_L^K>83Qh_W(V{3(4 E0GE*$zyJUM literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/lib/mocha.rb b/vendor/gems/mocha-0.5.6/lib/mocha.rb new file mode 100644 index 0000000000..58571156a1 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha.rb @@ -0,0 +1,19 @@ +require 'mocha_standalone' +require 'mocha/test_case_adapter' + +require 'test/unit/testcase' + +module Test + + module Unit + + class TestCase + + include Mocha::Standalone + include Mocha::TestCaseAdapter + + end + + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/._any_instance_method.rb b/vendor/gems/mocha-0.5.6/lib/mocha/._any_instance_method.rb new file mode 100644 index 0000000000000000000000000000000000000000..00711bd393d7440125504f76da6c58860a753fc9 GIT binary patch literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_I^%JNRq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eY09E97M6r2<(-Ce{kI E0OzY1vH$=8 literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/._auto_verify.rb b/vendor/gems/mocha-0.5.6/lib/mocha/._auto_verify.rb new file mode 100644 index 0000000000000000000000000000000000000000..b0167dfe3156b7babcdb19a5807770ddd7ea68a9 GIT binary patch literal 176 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aUBqY_#1$j2;dkJ62zeT=O$1oNXHBy zmO#_Sh!7V|&d=4$O-wGz&rK{zE!Im;EJ`h5s7}t$Db3AOuvIXyR>;ZBONFp%0T)~u AOaK4? literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/._central.rb b/vendor/gems/mocha-0.5.6/lib/mocha/._central.rb new file mode 100644 index 0000000000000000000000000000000000000000..8515018e0656138eb59d19f7ed1812c53b30a919 GIT binary patch literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_Ibs4A>q+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!WSS#dY=A{CeX4VR| E0NNZFjQ{`u literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/._class_method.rb b/vendor/gems/mocha-0.5.6/lib/mocha/._class_method.rb new file mode 100644 index 0000000000000000000000000000000000000000..66871a56faca565acc4a345e91af10fd19dcfddb GIT binary patch literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62zc=Hyo%Gq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eaNsSu5mZ=A{CeCZ^U3 FwE!6W87}|; literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/._deprecation.rb b/vendor/gems/mocha-0.5.6/lib/mocha/._deprecation.rb new file mode 100644 index 0000000000000000000000000000000000000000..3e11a4d5ab15cb4d388ee2419cbc42b228d9035d GIT binary patch literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_Ibswk{q+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!XE97M6r2<(-hSmzT E0N$7wkN^Mx literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/._expectation.rb b/vendor/gems/mocha-0.5.6/lib/mocha/._expectation.rb new file mode 100644 index 0000000000000000000000000000000000000000..2deed467d54e3ca0fcc6d8dac52cb60875e4f08d GIT binary patch literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_q2GR-AF$0LD z(6lik#6^?ybM>83Qb9~3YlT_> D)5REI literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/._expectation_error.rb b/vendor/gems/mocha-0.5.6/lib/mocha/._expectation_error.rb new file mode 100644 index 0000000000000000000000000000000000000000..dbb6154b38922123992e4fc918d5e2ecc7ecb73e GIT binary patch literal 176 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aUBqY_#1$j2;dkJ62u_qQVmoJ(lG;w zCD61nBE&_L^K>83QX#Bb0Jr8C AF8}}l literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/._expectation_list.rb b/vendor/gems/mocha-0.5.6/lib/mocha/._expectation_list.rb new file mode 100644 index 0000000000000000000000000000000000000000..21a5f32b4898923015bcbed6256af35694ab1398 GIT binary patch literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_IbrPr)q+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eaNsSS#dY=A{CeMn={O FwE*!37`6Za literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/._infinite_range.rb b/vendor/gems/mocha-0.5.6/lib/mocha/._infinite_range.rb new file mode 100644 index 0000000000000000000000000000000000000000..07e0b709a3a640c9d7eba69487fe4be6fa7f871e GIT binary patch literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_IbsDG?q+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r2<)orq&9z E0N1`4d;kCd literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/._inspect.rb b/vendor/gems/mocha-0.5.6/lib/mocha/._inspect.rb new file mode 100644 index 0000000000000000000000000000000000000000..28d27abdeed5937054911d3af74c27541a9253b5 GIT binary patch literal 176 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aUBqY_#1$j2;dkJ62u_I^%AHQq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r2<*T)(W)% D$DkNu literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/._instance_method.rb b/vendor/gems/mocha-0.5.6/lib/mocha/._instance_method.rb new file mode 100644 index 0000000000000000000000000000000000000000..c11d0ea30cef4ed2ccf78cbb8ce98285e1285f56 GIT binary patch literal 176 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aUBqY_#1$j2;dkJ62u_I^%STSq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r9xP>0LDNV ATL1t6 literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/._metaclass.rb b/vendor/gems/mocha-0.5.6/lib/mocha/._metaclass.rb new file mode 100644 index 0000000000000000000000000000000000000000..3ab4a8b5f77e91f62c806906d99b7a9a3f40b54d GIT binary patch literal 176 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aUBqY_#1$j2;dkJ62u_I^%tlVq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r2<(-)(W)% D&OjJ- literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/._method_matcher.rb b/vendor/gems/mocha-0.5.6/lib/mocha/._method_matcher.rb new file mode 100644 index 0000000000000000000000000000000000000000..392843f65b0810da5bee3c2415e860b1fe5461ec GIT binary patch literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62zc+eHl%u59_O{^7a E0Tt*OI{*Lx literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/._missing_expectation.rb b/vendor/gems/mocha-0.5.6/lib/mocha/._missing_expectation.rb new file mode 100644 index 0000000000000000000000000000000000000000..4049e36b45b30e6d6feb481c27162efd38350e71 GIT binary patch literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_qW(HIW(lG;w zCDF7oBE&_L^K$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62zeT=N3>YNXHBy zmO|6Uh!7V|&d=4$O-wGz&rK{zE!Im;EJ`h5s7}t$Db3AOuvIXyR>;ZBO9iqFElsQy FY5`7E8N~nq literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/._object.rb b/vendor/gems/mocha-0.5.6/lib/mocha/._object.rb new file mode 100644 index 0000000000000000000000000000000000000000..2fa785b575b334363ee3f792cad3984c8f0b7f3f GIT binary patch literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_I^%kfUq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r2<)o2By{u FwE+E&7}Ed% literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/._parameter_matchers.rb b/vendor/gems/mocha-0.5.6/lib/mocha/._parameter_matchers.rb new file mode 100644 index 0000000000000000000000000000000000000000..1a81dea1e3d10f95460284b37c8614e22218e267 GIT binary patch literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_I^&Y4cq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r2<)oCe{kI E0OQgap8x;= literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/._parameters_matcher.rb b/vendor/gems/mocha-0.5.6/lib/mocha/._parameters_matcher.rb new file mode 100644 index 0000000000000000000000000000000000000000..de250a8e2d948908ebede6dc16df94925696bf93 GIT binary patch literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62zc+eK}AmNXHBy zmPFIWh!7V|&d=4$O-wGz&rK{zE!Im;EJ`h5s7}t$Db3AOuvIX#v{uN;%u59_&8-z` E0Tz=PL;wH) literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/._pretty_parameters.rb b/vendor/gems/mocha-0.5.6/lib/mocha/._pretty_parameters.rb new file mode 100644 index 0000000000000000000000000000000000000000..0a4f4854bfff396ad47bcceffdc108ff7a5ae3d5 GIT binary patch literal 176 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aUBqY_#1$j2;dkJ62u_I^%CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r9xP>0LtbV AX#fBK literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/._return_values.rb b/vendor/gems/mocha-0.5.6/lib/mocha/._return_values.rb new file mode 100644 index 0000000000000000000000000000000000000000..0d41af3d697355d5f7b8f065a42e77be0226ff53 GIT binary patch literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62zc=Hw>s0q+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r2<)orq&9z E0R8i_@% literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/._sequence.rb b/vendor/gems/mocha-0.5.6/lib/mocha/._sequence.rb new file mode 100644 index 0000000000000000000000000000000000000000..af2494888fa1d0a66576760a7df22e8aaf4a4d04 GIT binary patch literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62zdz!VXjl(lG;w zrO>o7BE&_L^K$Vqox1Ojhs@R)|o50+1L3ClDI}aUBqY_#1$j2;dkJ62u_IbrGl(q+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r2<*z)(W)% DzqlAq literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/._single_return_value.rb b/vendor/gems/mocha-0.5.6/lib/mocha/._single_return_value.rb new file mode 100644 index 0000000000000000000000000000000000000000..fec4c56f502aad54a7aa369db2780f31850d0a8f GIT binary patch literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_I^&hAdq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!WS}Wva=B0v|rq&9z E00V&-?*IS* literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/._standalone.rb b/vendor/gems/mocha-0.5.6/lib/mocha/._standalone.rb new file mode 100644 index 0000000000000000000000000000000000000000..f33f028a2a6240ae6d9889524af832565f4e2c11 GIT binary patch literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_Ibrq--q+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!WS}Wva=B0v|M%D_o E0QDdk!T$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_I^%bZTq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eVzqSS#dY=A{CehL+X} FwE+L67~uc_ literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/._yield_parameters.rb b/vendor/gems/mocha-0.5.6/lib/mocha/._yield_parameters.rb new file mode 100644 index 0000000000000000000000000000000000000000..a9646d7bbf769cc282d30d135c55d3b7a291620d GIT binary patch literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62zc=Hx#H8q+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eVzrTPx&b=B0v|rq&9z E02k63BLDyZ literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/any_instance_method.rb b/vendor/gems/mocha-0.5.6/lib/mocha/any_instance_method.rb new file mode 100644 index 0000000000..4d55293b9a --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/any_instance_method.rb @@ -0,0 +1,35 @@ +require 'mocha/class_method' + +module Mocha + + class AnyInstanceMethod < ClassMethod + + def unstub + remove_new_method + restore_original_method + stubbee.any_instance.reset_mocha + end + + def mock + stubbee.any_instance.mocha + end + + def hide_original_method + stubbee.class_eval "alias_method :#{hidden_method}, :#{method}" if stubbee.method_defined?(method) + end + + def define_new_method + stubbee.class_eval "def #{method}(*args, &block); self.class.any_instance.mocha.method_missing(:#{method}, *args, &block); end" + end + + def remove_new_method + stubbee.class_eval "remove_method :#{method}" + end + + def restore_original_method + stubbee.class_eval "alias_method :#{method}, :#{hidden_method}; remove_method :#{hidden_method}" if stubbee.method_defined?(hidden_method) + end + + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/auto_verify.rb b/vendor/gems/mocha-0.5.6/lib/mocha/auto_verify.rb new file mode 100644 index 0000000000..896648bcdc --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/auto_verify.rb @@ -0,0 +1,118 @@ +require 'mocha/mock' +require 'mocha/sequence' + +module Mocha # :nodoc: + + # Methods added to TestCase allowing creation of traditional mock objects. + # + # Mocks created this way will have their expectations automatically verified at the end of the test. + # + # See Mock for methods on mock objects. + module AutoVerify + + def mocks # :nodoc: + @mocks ||= [] + end + + def reset_mocks # :nodoc: + @mocks = nil + end + + # :call-seq: mock(name, &block) -> mock object + # mock(expected_methods = {}, &block) -> mock object + # mock(name, expected_methods = {}, &block) -> mock object + # + # Creates a mock object. + # + # +name+ is a +String+ identifier for the mock object. + # + # +expected_methods+ is a +Hash+ with expected method name symbols as keys and corresponding return values as values. + # + # +block+ is a block to be evaluated against the mock object instance, giving an alernative way to set up expectations & stubs. + # + # Note that (contrary to expectations set up by #stub) these expectations must be fulfilled during the test. + # def test_product + # product = mock('ipod_product', :manufacturer => 'ipod', :price => 100) + # assert_equal 'ipod', product.manufacturer + # assert_equal 100, product.price + # # an error will be raised unless both Product#manufacturer and Product#price have been called + # end + def mock(*arguments, &block) + name = arguments.shift if arguments.first.is_a?(String) + expectations = arguments.shift || {} + mock = Mock.new(name, &block) + mock.expects(expectations) + mocks << mock + mock + end + + # :call-seq: stub(name, &block) -> mock object + # stub(stubbed_methods = {}, &block) -> mock object + # stub(name, stubbed_methods = {}, &block) -> mock object + # + # Creates a mock object. + # + # +name+ is a +String+ identifier for the mock object. + # + # +stubbed_methods+ is a +Hash+ with stubbed method name symbols as keys and corresponding return values as values. + # + # +block+ is a block to be evaluated against the mock object instance, giving an alernative way to set up expectations & stubs. + # + # Note that (contrary to expectations set up by #mock) these expectations need not be fulfilled during the test. + # def test_product + # product = stub('ipod_product', :manufacturer => 'ipod', :price => 100) + # assert_equal 'ipod', product.manufacturer + # assert_equal 100, product.price + # # an error will not be raised even if Product#manufacturer and Product#price have not been called + # end + def stub(*arguments, &block) + name = arguments.shift if arguments.first.is_a?(String) + expectations = arguments.shift || {} + stub = Mock.new(name, &block) + stub.stubs(expectations) + mocks << stub + stub + end + + # :call-seq: stub_everything(name, &block) -> mock object + # stub_everything(stubbed_methods = {}, &block) -> mock object + # stub_everything(name, stubbed_methods = {}, &block) -> mock object + # + # Creates a mock object that accepts calls to any method. + # + # By default it will return +nil+ for any method call. + # + # +block+ is a block to be evaluated against the mock object instance, giving an alernative way to set up expectations & stubs. + # + # +name+ and +stubbed_methods+ work in the same way as for #stub. + # def test_product + # product = stub_everything('ipod_product', :price => 100) + # assert_nil product.manufacturer + # assert_nil product.any_old_method + # assert_equal 100, product.price + # end + def stub_everything(*arguments, &block) + name = arguments.shift if arguments.first.is_a?(String) + expectations = arguments.shift || {} + stub = Mock.new(name, &block) + stub.stub_everything + stub.stubs(expectations) + mocks << stub + stub + end + + def verify_mocks # :nodoc: + mocks.each { |mock| mock.verify { yield if block_given? } } + end + + def teardown_mocks # :nodoc: + reset_mocks + end + + def sequence(name) # :nodoc: + Sequence.new(name) + end + + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/central.rb b/vendor/gems/mocha-0.5.6/lib/mocha/central.rb new file mode 100644 index 0000000000..0445f2151d --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/central.rb @@ -0,0 +1,35 @@ +module Mocha + + class Central + + attr_accessor :stubba_methods + + def initialize + self.stubba_methods = [] + end + + def stub(method) + unless stubba_methods.include?(method) + method.stub + stubba_methods.push method + end + end + + def verify_all(&block) + unique_mocks.each { |mock| mock.verify(&block) } + end + + def unique_mocks + stubba_methods.inject({}) { |mocks, method| mocks[method.mock.__id__] = method.mock; mocks }.values + end + + def unstub_all + while stubba_methods.length > 0 + method = stubba_methods.pop + method.unstub + end + end + + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/class_method.rb b/vendor/gems/mocha-0.5.6/lib/mocha/class_method.rb new file mode 100644 index 0000000000..e2178be171 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/class_method.rb @@ -0,0 +1,66 @@ +require 'mocha/metaclass' + +module Mocha + + class ClassMethod + + attr_reader :stubbee, :method + + def initialize(stubbee, method) + @stubbee, @method = stubbee, method + end + + def stub + hide_original_method + define_new_method + end + + def unstub + remove_new_method + restore_original_method + stubbee.reset_mocha + end + + def mock + stubbee.mocha + end + + def hide_original_method + stubbee.__metaclass__.class_eval "alias_method :#{hidden_method}, :#{method}" if stubbee.__metaclass__.method_defined?(method) + end + + def define_new_method + stubbee.__metaclass__.class_eval "def #{method}(*args, &block); mocha.method_missing(:#{method}, *args, &block); end" + end + + def remove_new_method + stubbee.__metaclass__.class_eval "remove_method :#{method}" + end + + def restore_original_method + stubbee.__metaclass__.class_eval "alias_method :#{method}, :#{hidden_method}; remove_method :#{hidden_method}" if stubbee.__metaclass__.method_defined?(hidden_method) + end + + def hidden_method + if RUBY_VERSION < '1.9' + method_name = method.to_s.gsub(/\W/) { |s| "_substituted_character_#{s[0]}_" } + else + method_name = method.to_s.gsub(/\W/) { |s| "_substituted_character_#{s.ord}_" } + end + "__stubba__#{method_name}__stubba__" + end + + def eql?(other) + return false unless (other.class == self.class) + (stubbee == other.stubbee) and (method == other.method) + end + + alias_method :==, :eql? + + def to_s + "#{stubbee}.#{method}" + end + + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/deprecation.rb b/vendor/gems/mocha-0.5.6/lib/mocha/deprecation.rb new file mode 100644 index 0000000000..7448510ec7 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/deprecation.rb @@ -0,0 +1,22 @@ +module Mocha + + class Deprecation + + class << self + + attr_accessor :mode, :messages + + def warning(message) + @messages << message + $stderr.puts "Mocha deprecation warning: #{message}" unless mode == :disabled + $stderr.puts caller.join("\n ") if mode == :debug + end + + end + + self.mode = :enabled + self.messages = [] + + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/exception_raiser.rb b/vendor/gems/mocha-0.5.6/lib/mocha/exception_raiser.rb new file mode 100644 index 0000000000..266e209a22 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/exception_raiser.rb @@ -0,0 +1,17 @@ +module Mocha # :nodoc: + + class ExceptionRaiser # :nodoc: + + def initialize(exception, message) + @exception, @message = exception, message + end + + def evaluate + raise @exception, @exception.to_s if @exception == Interrupt + raise @exception, @message if @message + raise @exception + end + + end + +end diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/expectation.rb b/vendor/gems/mocha-0.5.6/lib/mocha/expectation.rb new file mode 100644 index 0000000000..e3da2533f3 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/expectation.rb @@ -0,0 +1,384 @@ +require 'mocha/infinite_range' +require 'mocha/method_matcher' +require 'mocha/parameters_matcher' +require 'mocha/expectation_error' +require 'mocha/return_values' +require 'mocha/exception_raiser' +require 'mocha/yield_parameters' +require 'mocha/is_a' + +module Mocha # :nodoc: + + # Methods on expectations returned from Mock#expects, Mock#stubs, Object#expects and Object#stubs. + class Expectation + + # :call-seq: times(range) -> expectation + # + # Modifies expectation so that the number of calls to the expected method must be within a specific +range+. + # + # +range+ can be specified as an exact integer or as a range of integers + # object = mock() + # object.expects(:expected_method).times(3) + # 3.times { object.expected_method } + # # => verify succeeds + # + # object = mock() + # object.expects(:expected_method).times(3) + # 2.times { object.expected_method } + # # => verify fails + # + # object = mock() + # object.expects(:expected_method).times(2..4) + # 3.times { object.expected_method } + # # => verify succeeds + # + # object = mock() + # object.expects(:expected_method).times(2..4) + # object.expected_method + # # => verify fails + def times(range) + @expected_count = range + self + end + + # :call-seq: once() -> expectation + # + # Modifies expectation so that the expected method must be called exactly once. + # Note that this is the default behaviour for an expectation, but you may wish to use it for clarity/emphasis. + # object = mock() + # object.expects(:expected_method).once + # object.expected_method + # # => verify succeeds + # + # object = mock() + # object.expects(:expected_method).once + # object.expected_method + # object.expected_method + # # => verify fails + # + # object = mock() + # object.expects(:expected_method).once + # # => verify fails + def once() + times(1) + self + end + + # :call-seq: never() -> expectation + # + # Modifies expectation so that the expected method must never be called. + # object = mock() + # object.expects(:expected_method).never + # object.expected_method + # # => verify fails + # + # object = mock() + # object.expects(:expected_method).never + # object.expected_method + # # => verify succeeds + def never + times(0) + self + end + + # :call-seq: at_least(minimum_number_of_times) -> expectation + # + # Modifies expectation so that the expected method must be called at least a +minimum_number_of_times+. + # object = mock() + # object.expects(:expected_method).at_least(2) + # 3.times { object.expected_method } + # # => verify succeeds + # + # object = mock() + # object.expects(:expected_method).at_least(2) + # object.expected_method + # # => verify fails + def at_least(minimum_number_of_times) + times(Range.at_least(minimum_number_of_times)) + self + end + + # :call-seq: at_least_once() -> expectation + # + # Modifies expectation so that the expected method must be called at least once. + # object = mock() + # object.expects(:expected_method).at_least_once + # object.expected_method + # # => verify succeeds + # + # object = mock() + # object.expects(:expected_method).at_least_once + # # => verify fails + def at_least_once() + at_least(1) + self + end + + # :call-seq: at_most(maximum_number_of_times) -> expectation + # + # Modifies expectation so that the expected method must be called at most a +maximum_number_of_times+. + # object = mock() + # object.expects(:expected_method).at_most(2) + # 2.times { object.expected_method } + # # => verify succeeds + # + # object = mock() + # object.expects(:expected_method).at_most(2) + # 3.times { object.expected_method } + # # => verify fails + def at_most(maximum_number_of_times) + times(Range.at_most(maximum_number_of_times)) + self + end + + # :call-seq: at_most_once() -> expectation + # + # Modifies expectation so that the expected method must be called at most once. + # object = mock() + # object.expects(:expected_method).at_most_once + # object.expected_method + # # => verify succeeds + # + # object = mock() + # object.expects(:expected_method).at_most_once + # 2.times { object.expected_method } + # # => verify fails + def at_most_once() + at_most(1) + self + end + + # :call-seq: with(*expected_parameters, &matching_block) -> expectation + # + # Modifies expectation so that the expected method must be called with +expected_parameters+. + # object = mock() + # object.expects(:expected_method).with(:param1, :param2) + # object.expected_method(:param1, :param2) + # # => verify succeeds + # + # object = mock() + # object.expects(:expected_method).with(:param1, :param2) + # object.expected_method(:param3) + # # => verify fails + # May be used with parameter matchers in Mocha::ParameterMatchers. + # + # If a +matching_block+ is given, the block is called with the parameters passed to the expected method. + # The expectation is matched if the block evaluates to +true+. + # object = mock() + # object.expects(:expected_method).with() { |value| value % 4 == 0 } + # object.expected_method(16) + # # => verify succeeds + # + # object = mock() + # object.expects(:expected_method).with() { |value| value % 4 == 0 } + # object.expected_method(17) + # # => verify fails + def with(*expected_parameters, &matching_block) + @parameters_matcher = ParametersMatcher.new(expected_parameters, &matching_block) + self + end + + # :call-seq: yields(*parameters) -> expectation + # + # Modifies expectation so that when the expected method is called, it yields with the specified +parameters+. + # object = mock() + # object.expects(:expected_method).yields('result') + # yielded_value = nil + # object.expected_method { |value| yielded_value = value } + # yielded_value # => 'result' + # May be called multiple times on the same expectation for consecutive invocations. Also see Expectation#then. + # object = mock() + # object.stubs(:expected_method).yields(1).then.yields(2) + # yielded_values_from_first_invocation = [] + # yielded_values_from_second_invocation = [] + # object.expected_method { |value| yielded_values_from_first_invocation << value } # first invocation + # object.expected_method { |value| yielded_values_from_second_invocation << value } # second invocation + # yielded_values_from_first_invocation # => [1] + # yielded_values_from_second_invocation # => [2] + def yields(*parameters) + @yield_parameters.add(*parameters) + self + end + + # :call-seq: multiple_yields(*parameter_groups) -> expectation + # + # Modifies expectation so that when the expected method is called, it yields multiple times per invocation with the specified +parameter_groups+. + # object = mock() + # object.expects(:expected_method).multiple_yields(['result_1', 'result_2'], ['result_3']) + # yielded_values = [] + # object.expected_method { |*values| yielded_values << values } + # yielded_values # => [['result_1', 'result_2'], ['result_3]] + # May be called multiple times on the same expectation for consecutive invocations. Also see Expectation#then. + # object = mock() + # object.stubs(:expected_method).multiple_yields([1, 2], [3]).then.multiple_yields([4], [5, 6]) + # yielded_values_from_first_invocation = [] + # yielded_values_from_second_invocation = [] + # object.expected_method { |*values| yielded_values_from_first_invocation << values } # first invocation + # object.expected_method { |*values| yielded_values_from_second_invocation << values } # second invocation + # yielded_values_from_first_invocation # => [[1, 2], [3]] + # yielded_values_from_second_invocation # => [[4], [5, 6]] + def multiple_yields(*parameter_groups) + @yield_parameters.multiple_add(*parameter_groups) + self + end + + # :call-seq: returns(value) -> expectation + # returns(*values) -> expectation + # + # Modifies expectation so that when the expected method is called, it returns the specified +value+. + # object = mock() + # object.stubs(:stubbed_method).returns('result') + # object.stubbed_method # => 'result' + # object.stubbed_method # => 'result' + # If multiple +values+ are given, these are returned in turn on consecutive calls to the method. + # object = mock() + # object.stubs(:stubbed_method).returns(1, 2) + # object.stubbed_method # => 1 + # object.stubbed_method # => 2 + # May be called multiple times on the same expectation. Also see Expectation#then. + # object = mock() + # object.stubs(:expected_method).returns(1, 2).then.returns(3) + # object.expected_method # => 1 + # object.expected_method # => 2 + # object.expected_method # => 3 + # May be called in conjunction with Expectation#raises on the same expectation. + # object = mock() + # object.stubs(:expected_method).returns(1, 2).then.raises(Exception) + # object.expected_method # => 1 + # object.expected_method # => 2 + # object.expected_method # => raises exception of class Exception1 + # If +value+ is a +Proc+, then the expected method will return the result of calling Proc#call. + # + # This usage is _deprecated_. + # Use explicit multiple return values and/or multiple expectations instead. + # + # A +Proc+ instance will be treated the same as any other value in a future release. + # object = mock() + # object.stubs(:stubbed_method).returns(lambda { rand(100) }) + # object.stubbed_method # => 41 + # object.stubbed_method # => 77 + def returns(*values) + @return_values += ReturnValues.build(*values) + self + end + + # :call-seq: raises(exception = RuntimeError, message = nil) -> expectation + # + # Modifies expectation so that when the expected method is called, it raises the specified +exception+ with the specified +message+. + # object = mock() + # object.expects(:expected_method).raises(Exception, 'message') + # object.expected_method # => raises exception of class Exception and with message 'message' + # May be called multiple times on the same expectation. Also see Expectation#then. + # object = mock() + # object.stubs(:expected_method).raises(Exception1).then.raises(Exception2) + # object.expected_method # => raises exception of class Exception1 + # object.expected_method # => raises exception of class Exception2 + # May be called in conjunction with Expectation#returns on the same expectation. + # object = mock() + # object.stubs(:expected_method).raises(Exception).then.returns(2, 3) + # object.expected_method # => raises exception of class Exception1 + # object.expected_method # => 2 + # object.expected_method # => 3 + def raises(exception = RuntimeError, message = nil) + @return_values += ReturnValues.new(ExceptionRaiser.new(exception, message)) + self + end + + # :call-seq: then() -> expectation + # + # Syntactic sugar to improve readability. Has no effect on state of the expectation. + # object = mock() + # object.stubs(:expected_method).returns(1, 2).then.raises(Exception).then.returns(4) + # object.expected_method # => 1 + # object.expected_method # => 2 + # object.expected_method # => raises exception of class Exception + # object.expected_method # => 4 + def then + self + end + + # :stopdoc: + + def in_sequence(*sequences) + sequences.each { |sequence| sequence.constrain_as_next_in_sequence(self) } + self + end + + attr_reader :backtrace + + def initialize(mock, expected_method_name, backtrace = nil) + @mock = mock + @method_matcher = MethodMatcher.new(expected_method_name) + @parameters_matcher = ParametersMatcher.new + @ordering_constraints = [] + @expected_count, @invoked_count = 1, 0 + @return_values = ReturnValues.new + @yield_parameters = YieldParameters.new + @backtrace = backtrace || caller + end + + def add_ordering_constraint(ordering_constraint) + @ordering_constraints << ordering_constraint + end + + def in_correct_order? + @ordering_constraints.all? { |ordering_constraint| ordering_constraint.allows_invocation_now? } + end + + def matches_method?(method_name) + @method_matcher.match?(method_name) + end + + def match?(actual_method_name, *actual_parameters) + @method_matcher.match?(actual_method_name) && @parameters_matcher.match?(actual_parameters) && in_correct_order? + end + + def invocations_allowed? + if @expected_count.is_a?(Range) then + @invoked_count < @expected_count.last + else + @invoked_count < @expected_count + end + end + + def satisfied? + if @expected_count.is_a?(Range) then + @invoked_count >= @expected_count.first + else + @invoked_count >= @expected_count + end + end + + def invoke + @invoked_count += 1 + if block_given? then + @yield_parameters.next_invocation.each do |yield_parameters| + yield(*yield_parameters) + end + end + @return_values.next + end + + def verify + yield(self) if block_given? + unless (@expected_count === @invoked_count) then + error = ExpectationError.new(error_message(@expected_count, @invoked_count), backtrace) + raise error + end + end + + def method_signature + signature = "#{@mock.mocha_inspect}.#{@method_matcher.mocha_inspect}#{@parameters_matcher.mocha_inspect}" + signature << "; #{@ordering_constraints.map { |oc| oc.mocha_inspect }.join("; ")}" unless @ordering_constraints.empty? + signature + end + + def error_message(expected_count, actual_count) + "#{method_signature} - expected calls: #{expected_count.mocha_inspect}, actual calls: #{actual_count}" + end + + # :startdoc: + + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/expectation_error.rb b/vendor/gems/mocha-0.5.6/lib/mocha/expectation_error.rb new file mode 100644 index 0000000000..705571b854 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/expectation_error.rb @@ -0,0 +1,15 @@ +module Mocha + + class ExpectationError < StandardError + + LIB_DIRECTORY = File.expand_path(File.join(File.dirname(__FILE__), "..")) + File::SEPARATOR + + def initialize(message = nil, backtrace = [], lib_directory = LIB_DIRECTORY) + super(message) + filtered_backtrace = backtrace.reject { |location| Regexp.new(lib_directory).match(File.expand_path(location)) } + set_backtrace(filtered_backtrace) + end + + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/expectation_list.rb b/vendor/gems/mocha-0.5.6/lib/mocha/expectation_list.rb new file mode 100644 index 0000000000..5ca13d5afe --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/expectation_list.rb @@ -0,0 +1,46 @@ +module Mocha # :nodoc: + + class ExpectationList + + def initialize + @expectations = [] + end + + def add(expectation) + @expectations << expectation + expectation + end + + def matches_method?(method_name) + @expectations.any? { |expectation| expectation.matches_method?(method_name) } + end + + def similar(method_name) + @expectations.select { |expectation| expectation.matches_method?(method_name) } + end + + def detect(method_name, *arguments) + expectations = @expectations.reverse.select { |e| e.match?(method_name, *arguments) } + expectation = expectations.detect { |e| e.invocations_allowed? } + expectation || expectations.first + end + + def verify(&block) + @expectations.each { |expectation| expectation.verify(&block) } + end + + def to_a + @expectations + end + + def to_set + @expectations.to_set + end + + def length + @expectations.length + end + + end + +end diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/infinite_range.rb b/vendor/gems/mocha-0.5.6/lib/mocha/infinite_range.rb new file mode 100644 index 0000000000..05dfe559e2 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/infinite_range.rb @@ -0,0 +1,25 @@ +class Range + + def self.at_least(minimum_value) + Range.new(minimum_value, infinite) + end + + def self.at_most(maximum_value) + Range.new(-infinite, maximum_value, false) + end + + def self.infinite + 1/0.0 + end + + def mocha_inspect + if first.respond_to?(:to_f) and first.to_f.infinite? then + return "at most #{last}" + elsif last.respond_to?(:to_f) and last.to_f.infinite? then + return "at least #{first}" + else + to_s + end + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/inspect.rb b/vendor/gems/mocha-0.5.6/lib/mocha/inspect.rb new file mode 100644 index 0000000000..ad82ef70e6 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/inspect.rb @@ -0,0 +1,39 @@ +require 'date' + +class Object + def mocha_inspect + address = self.__id__ * 2 + address += 0x100000000 if address < 0 + inspect =~ /#" : inspect + end +end + +class String + def mocha_inspect + inspect.gsub(/\"/, "'") + end +end + +class Array + def mocha_inspect + "[#{collect { |member| member.mocha_inspect }.join(', ')}]" + end +end + +class Hash + def mocha_inspect + "{#{collect { |key, value| "#{key.mocha_inspect} => #{value.mocha_inspect}" }.join(', ')}}" + end +end + +class Time + def mocha_inspect + "#{inspect} (#{to_f} secs)" + end +end + +class Date + def mocha_inspect + to_s + end +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/instance_method.rb b/vendor/gems/mocha-0.5.6/lib/mocha/instance_method.rb new file mode 100644 index 0000000000..f0d4b04b8a --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/instance_method.rb @@ -0,0 +1,8 @@ +require 'mocha/class_method' + +module Mocha + + class InstanceMethod < ClassMethod + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/is_a.rb b/vendor/gems/mocha-0.5.6/lib/mocha/is_a.rb new file mode 100644 index 0000000000..ee23c86a91 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/is_a.rb @@ -0,0 +1,9 @@ +class Object + + # :stopdoc: + + alias_method :__is_a__, :is_a? + + # :startdoc: + +end diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/metaclass.rb b/vendor/gems/mocha-0.5.6/lib/mocha/metaclass.rb new file mode 100644 index 0000000000..f78fb892b2 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/metaclass.rb @@ -0,0 +1,7 @@ +class Object + + def __metaclass__ + class << self; self; end + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/method_matcher.rb b/vendor/gems/mocha-0.5.6/lib/mocha/method_matcher.rb new file mode 100644 index 0000000000..6ce5f6d576 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/method_matcher.rb @@ -0,0 +1,21 @@ +module Mocha + + class MethodMatcher + + attr_reader :expected_method_name + + def initialize(expected_method_name) + @expected_method_name = expected_method_name + end + + def match?(actual_method_name) + @expected_method_name == actual_method_name + end + + def mocha_inspect + "#{@expected_method_name}" + end + + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/missing_expectation.rb b/vendor/gems/mocha-0.5.6/lib/mocha/missing_expectation.rb new file mode 100644 index 0000000000..ccff6bff42 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/missing_expectation.rb @@ -0,0 +1,17 @@ +require 'mocha/expectation' + +module Mocha # :nodoc: + + class MissingExpectation < Expectation # :nodoc: + + def verify + message = error_message(0, 1) + similar_expectations = @mock.expectations.similar(@method_matcher.expected_method_name) + method_signatures = similar_expectations.map { |expectation| expectation.method_signature } + message << "\nSimilar expectations:\n#{method_signatures.join("\n")}" unless method_signatures.empty? + raise ExpectationError.new(message, backtrace) + end + + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/mock.rb b/vendor/gems/mocha-0.5.6/lib/mocha/mock.rb new file mode 100644 index 0000000000..59193e728c --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/mock.rb @@ -0,0 +1,202 @@ +require 'mocha/expectation' +require 'mocha/expectation_list' +require 'mocha/stub' +require 'mocha/missing_expectation' +require 'mocha/metaclass' + +module Mocha # :nodoc: + + # Traditional mock object. + # + # Methods return an Expectation which can be further modified by methods on Expectation. + class Mock + + # :call-seq: expects(method_name) -> expectation + # expects(method_names) -> last expectation + # + # Adds an expectation that a method identified by +method_name+ symbol must be called exactly once with any parameters. + # Returns the new expectation which can be further modified by methods on Expectation. + # object = mock() + # object.expects(:method1) + # object.method1 + # # no error raised + # + # object = mock() + # object.expects(:method1) + # # error raised, because method1 not called exactly once + # If +method_names+ is a +Hash+, an expectation will be set up for each entry using the key as +method_name+ and value as +return_value+. + # object = mock() + # object.expects(:method1 => :result1, :method2 => :result2) + # + # # exactly equivalent to + # + # object = mock() + # object.expects(:method1).returns(:result1) + # object.expects(:method2).returns(:result2) + # + # Aliased by \_\_expects\_\_ + def expects(method_name_or_hash, backtrace = nil) + if method_name_or_hash.is_a?(Hash) then + method_name_or_hash.each do |method_name, return_value| + ensure_method_not_already_defined(method_name) + @expectations.add(Expectation.new(self, method_name, backtrace).returns(return_value)) + end + else + ensure_method_not_already_defined(method_name_or_hash) + @expectations.add(Expectation.new(self, method_name_or_hash, backtrace)) + end + end + + # :call-seq: stubs(method_name) -> expectation + # stubs(method_names) -> last expectation + # + # Adds an expectation that a method identified by +method_name+ symbol may be called any number of times with any parameters. + # Returns the new expectation which can be further modified by methods on Expectation. + # object = mock() + # object.stubs(:method1) + # object.method1 + # object.method1 + # # no error raised + # If +method_names+ is a +Hash+, an expectation will be set up for each entry using the key as +method_name+ and value as +return_value+. + # object = mock() + # object.stubs(:method1 => :result1, :method2 => :result2) + # + # # exactly equivalent to + # + # object = mock() + # object.stubs(:method1).returns(:result1) + # object.stubs(:method2).returns(:result2) + # + # Aliased by \_\_stubs\_\_ + def stubs(method_name_or_hash, backtrace = nil) + if method_name_or_hash.is_a?(Hash) then + method_name_or_hash.each do |method_name, return_value| + ensure_method_not_already_defined(method_name) + @expectations.add(Stub.new(self, method_name, backtrace).returns(return_value)) + end + else + ensure_method_not_already_defined(method_name_or_hash) + @expectations.add(Stub.new(self, method_name_or_hash, backtrace)) + end + end + + # :call-seq: responds_like(responder) -> mock + # + # Constrains the +mock+ so that it can only expect or stub methods to which +responder+ responds. The constraint is only applied at method invocation time. + # + # A +NoMethodError+ will be raised if the +responder+ does not respond_to? a method invocation (even if the method has been expected or stubbed). + # + # The +mock+ will delegate its respond_to? method to the +responder+. + # class Sheep + # def chew(grass); end + # def self.number_of_legs; end + # end + # + # sheep = mock('sheep') + # sheep.expects(:chew) + # sheep.expects(:foo) + # sheep.respond_to?(:chew) # => true + # sheep.respond_to?(:foo) # => true + # sheep.chew + # sheep.foo + # # no error raised + # + # sheep = mock('sheep') + # sheep.responds_like(Sheep.new) + # sheep.expects(:chew) + # sheep.expects(:foo) + # sheep.respond_to?(:chew) # => true + # sheep.respond_to?(:foo) # => false + # sheep.chew + # sheep.foo # => raises NoMethodError exception + # + # sheep_class = mock('sheep_class') + # sheep_class.responds_like(Sheep) + # sheep_class.stubs(:number_of_legs).returns(4) + # sheep_class.expects(:foo) + # sheep_class.respond_to?(:number_of_legs) # => true + # sheep_class.respond_to?(:foo) # => false + # assert_equal 4, sheep_class.number_of_legs + # sheep_class.foo # => raises NoMethodError exception + # + # Aliased by +quacks_like+ + def responds_like(object) + @responder = object + self + end + + # :stopdoc: + + def initialize(name = nil, &block) + @mock_name = name + @expectations = ExpectationList.new + @everything_stubbed = false + @responder = nil + instance_eval(&block) if block + end + + attr_reader :everything_stubbed, :expectations + + alias_method :__expects__, :expects + + alias_method :__stubs__, :stubs + + alias_method :quacks_like, :responds_like + + def add_expectation(expectation) + @expectations.add(expectation) + end + + def stub_everything + @everything_stubbed = true + end + + def method_missing(symbol, *arguments, &block) + if @responder and not @responder.respond_to?(symbol) + raise NoMethodError, "undefined method `#{symbol}' for #{self.mocha_inspect} which responds like #{@responder.mocha_inspect}" + end + matching_expectation = @expectations.detect(symbol, *arguments) + if matching_expectation then + matching_expectation.invoke(&block) + elsif @everything_stubbed then + return + else + unexpected_method_called(symbol, *arguments) + end + end + + def respond_to?(symbol) + if @responder then + @responder.respond_to?(symbol) + else + @expectations.matches_method?(symbol) + end + end + + def unexpected_method_called(symbol, *arguments) + MissingExpectation.new(self, symbol).with(*arguments).verify + end + + def verify(&block) + @expectations.verify(&block) + end + + def mocha_inspect + address = self.__id__ * 2 + address += 0x100000000 if address < 0 + @mock_name ? "#" : "#" + end + + def inspect + mocha_inspect + end + + def ensure_method_not_already_defined(method_name) + self.__metaclass__.send(:undef_method, method_name) if self.__metaclass__.method_defined?(method_name) + end + + # :startdoc: + + end + +end diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/multiple_yields.rb b/vendor/gems/mocha-0.5.6/lib/mocha/multiple_yields.rb new file mode 100644 index 0000000000..8186c30768 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/multiple_yields.rb @@ -0,0 +1,20 @@ +module Mocha # :nodoc: + + class MultipleYields # :nodoc: + + attr_reader :parameter_groups + + def initialize(*parameter_groups) + @parameter_groups = parameter_groups + end + + def each + @parameter_groups.each do |parameter_group| + yield(parameter_group) + end + end + + end + +end + diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/no_yields.rb b/vendor/gems/mocha-0.5.6/lib/mocha/no_yields.rb new file mode 100644 index 0000000000..b0fba415dc --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/no_yields.rb @@ -0,0 +1,11 @@ +module Mocha # :nodoc: + + class NoYields # :nodoc: + + def each + end + + end + +end + diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/object.rb b/vendor/gems/mocha-0.5.6/lib/mocha/object.rb new file mode 100644 index 0000000000..7ccdbad0de --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/object.rb @@ -0,0 +1,110 @@ +require 'mocha/mock' +require 'mocha/instance_method' +require 'mocha/class_method' +require 'mocha/any_instance_method' + +# Methods added all objects to allow mocking and stubbing on real objects. +# +# Methods return a Mocha::Expectation which can be further modified by methods on Mocha::Expectation. +class Object + + def mocha # :nodoc: + @mocha ||= Mocha::Mock.new + end + + def reset_mocha # :nodoc: + @mocha = nil + end + + def stubba_method # :nodoc: + Mocha::InstanceMethod + end + + def stubba_object # :nodoc: + self + end + + # :call-seq: expects(symbol) -> expectation + # + # Adds an expectation that a method identified by +symbol+ must be called exactly once with any parameters. + # Returns the new expectation which can be further modified by methods on Mocha::Expectation. + # product = Product.new + # product.expects(:save).returns(true) + # assert_equal false, product.save + # + # The original implementation of Product#save is replaced temporarily. + # + # The original implementation of Product#save is restored at the end of the test. + def expects(symbol) + method = stubba_method.new(stubba_object, symbol) + $stubba.stub(method) + mocha.expects(symbol, caller) + end + + # :call-seq: stubs(symbol) -> expectation + # + # Adds an expectation that a method identified by +symbol+ may be called any number of times with any parameters. + # Returns the new expectation which can be further modified by methods on Mocha::Expectation. + # product = Product.new + # product.stubs(:save).returns(true) + # assert_equal false, product.save + # + # The original implementation of Product#save is replaced temporarily. + # + # The original implementation of Product#save is restored at the end of the test. + def stubs(symbol) + method = stubba_method.new(stubba_object, symbol) + $stubba.stub(method) + mocha.stubs(symbol, caller) + end + + def verify # :nodoc: + mocha.verify + end + +end + +class Module # :nodoc: + + def stubba_method + Mocha::ClassMethod + end + +end + +class Class + + def stubba_method # :nodoc: + Mocha::ClassMethod + end + + class AnyInstance # :nodoc: + + def initialize(klass) + @stubba_object = klass + end + + def stubba_method + Mocha::AnyInstanceMethod + end + + def stubba_object + @stubba_object + end + + end + + # :call-seq: any_instance -> mock object + # + # Returns a mock object which will detect calls to any instance of this class. + # Product.any_instance.stubs(:save).returns(false) + # product_1 = Product.new + # assert_equal false, product_1.save + # product_2 = Product.new + # assert_equal false, product_2.save + def any_instance + @any_instance ||= AnyInstance.new(self) + end + +end + diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers.rb new file mode 100644 index 0000000000..a110479985 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers.rb @@ -0,0 +1,25 @@ +module Mocha + + # Used as parameters for Expectation#with to restrict the parameter values which will match the expectation. + module ParameterMatchers; end + +end + +require 'mocha/parameter_matchers/object' + +require 'mocha/parameter_matchers/all_of' +require 'mocha/parameter_matchers/any_of' +require 'mocha/parameter_matchers/any_parameters' +require 'mocha/parameter_matchers/anything' +require 'mocha/parameter_matchers/equals' +require 'mocha/parameter_matchers/has_entry' +require 'mocha/parameter_matchers/has_entries' +require 'mocha/parameter_matchers/has_key' +require 'mocha/parameter_matchers/has_value' +require 'mocha/parameter_matchers/includes' +require 'mocha/parameter_matchers/instance_of' +require 'mocha/parameter_matchers/is_a' +require 'mocha/parameter_matchers/kind_of' +require 'mocha/parameter_matchers/not' +require 'mocha/parameter_matchers/optionally' +require 'mocha/parameter_matchers/regexp_matches' diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._all_of.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._all_of.rb new file mode 100644 index 0000000000000000000000000000000000000000..41edbb869b4ad190641f241e7b212bef483c4087 GIT binary patch literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_IwF0OVq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eaMATPx&b=A{Ce#s=03 FwE*SK7?c12 literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._any_of.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._any_of.rb new file mode 100644 index 0000000000000000000000000000000000000000..ff228f5c75072ce5ee41ccdaaf16445b5c5f32f7 GIT binary patch literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_IwFIaXq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eVzsSu5mZ=A{CeM&{NE FwE*Nv7?J=0 literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._any_parameters.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._any_parameters.rb new file mode 100644 index 0000000000000000000000000000000000000000..7f2968c8c6d997023ec0422b8603d6e37dc24728 GIT binary patch literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62zd!a093mq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eVzqTPx&b=A{Ce#zxi( FwE*^^7{mYo literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._anything.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._anything.rb new file mode 100644 index 0000000000000000000000000000000000000000..5efdf5440275dc1b24bb5534b9cc0c64e279ed43 GIT binary patch literal 176 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aUBqY_#1$j2;dkJ62u_IH3O&=qyt1t zplM@7h>IrY=j!DqCKu)BCYGcY>m?@^rIs*MC+Fvs=H@BbDi~NRN Cw-^ur literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._base.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._base.rb new file mode 100644 index 0000000000000000000000000000000000000000..fc871fdda5edbfa6524410d4126a07e2404ac40d GIT binary patch literal 176 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aUBqY_#1$j2;dkJ62zd!C>83QX#Bb0EIgj AmjD0& literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._equals.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._equals.rb new file mode 100644 index 0000000000000000000000000000000000000000..c0dfb4cf5a4962c81701bf7207ad88a8ddfad066 GIT binary patch literal 176 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aUBqY_#1$j2;dkJ62zeTUk<1gq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r2<(-)(W)% D+CUhV literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._has_entries.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._has_entries.rb new file mode 100644 index 0000000000000000000000000000000000000000..622858418fbf041f646ecbdd644b72bb652c8d93 GIT binary patch literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62zdnWCu_wNXHBy zmO|6Uh!7V|&d=4$O-wGz&rK{zE!Im;EJ`h5s7}t$Db3AOuvIWIwN}W<%u59_jZLf- FY5^sI8BqWL literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._has_entry.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._has_entry.rb new file mode 100644 index 0000000000000000000000000000000000000000..fe7b51cfa88e05e8ef96f85e801dac051dc03af6 GIT binary patch literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_IwF;;dq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eaM9SS#dY=B0v|=GF?e E0On5^nE(I) literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._has_key.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._has_key.rb new file mode 100644 index 0000000000000000000000000000000000000000..e045911bef2de7a8ac0e31d2a7797d0c293cb5c8 GIT binary patch literal 176 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aUBqY_#1$j2;dkJ62u_IH4CT|q+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r2<(-)(W)% Dtzj4v literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._has_value.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._has_value.rb new file mode 100644 index 0000000000000000000000000000000000000000..a9565f2d4d2562e21065dca42e77594df230dc5c GIT binary patch literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_IH4ms1q+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eaNqTPx&b=A{CehKAM( FwE*IM7>fV^ literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._includes.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._includes.rb new file mode 100644 index 0000000000000000000000000000000000000000..0139a2dd2b78a73c1e677ec791b52f3d71725620 GIT binary patch literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_IH3z5^q+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eVzqS}Wva=A{CehGy0Z FwE*F17={1< literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._instance_of.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._instance_of.rb new file mode 100644 index 0000000000000000000000000000000000000000..718e3769737ae0bfec609f313a41129d193815d2 GIT binary patch literal 176 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aUBqY_#1$j2;dkJ62u_IwE(CTq+K$elU GLM;HVUl$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_IwG5~fq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r2<(-mevZj E0Lx_$Vqox1Ojhs@R)|o50+1L3ClDI}aUBqY_#1$j2;dkJ62u_IwFsybq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r2<(-)(W)% Dun8C% literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._not.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._not.rb new file mode 100644 index 0000000000000000000000000000000000000000..72ad06517106205b279f6e2cf83f433a6c497bc6 GIT binary patch literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62zeRY(7vaNXHBy zmPFIWh!7V|&d=4$O-wGz&rK{zE!Im;EJ`h5s7}t$Db3AOuvM_MR>;ZBO9irw4XhPv E0T!+qIRF3v literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._object.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._object.rb new file mode 100644 index 0000000000000000000000000000000000000000..5b117348ebedbbb924998203474adfb1f3185b16 GIT binary patch literal 176 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aUBqY_#1$j2;dkJ62zd!C>83QX#Bb0D$Tj AiU0rr literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._optionally.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._optionally.rb new file mode 100644 index 0000000000000000000000000000000000000000..2177fc74a4d34810c8d8d53d5e3afa43a431813e GIT binary patch literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62zeT*9)i=q+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r2<)orq&9z E00Pt)`2YX_ literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._regexp_matches.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._regexp_matches.rb new file mode 100644 index 0000000000000000000000000000000000000000..2b30b084a2dd04443a5800c6a5525839c83c20a3 GIT binary patch literal 176 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aUBqY_#1$j2;dkJ62u_IH4Uf~q+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r2<(-)(W)% DtPL0r literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/all_of.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/all_of.rb new file mode 100644 index 0000000000..369eb4340a --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/all_of.rb @@ -0,0 +1,42 @@ +require 'mocha/parameter_matchers/base' + +module Mocha + + module ParameterMatchers + + # :call-seq: all_of -> parameter_matcher + # + # Matches if all +matchers+ match. + # object = mock() + # object.expects(:method_1).with(all_of(includes(1), includes(3))) + # object.method_1([1, 3]) + # # no error raised + # + # object = mock() + # object.expects(:method_1).with(all_of(includes(1), includes(3))) + # object.method_1([1, 2]) + # # error raised, because method_1 was not called with object including 1 and 3 + def all_of(*matchers) + AllOf.new(*matchers) + end + + class AllOf < Base # :nodoc: + + def initialize(*matchers) + @matchers = matchers + end + + def matches?(available_parameters) + parameter = available_parameters.shift + @matchers.all? { |matcher| matcher.matches?([parameter]) } + end + + def mocha_inspect + "all_of(#{@matchers.map { |matcher| matcher.mocha_inspect }.join(", ") })" + end + + end + + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/any_of.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/any_of.rb new file mode 100644 index 0000000000..dd254b12f4 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/any_of.rb @@ -0,0 +1,47 @@ +require 'mocha/parameter_matchers/base' + +module Mocha + + module ParameterMatchers + + # :call-seq: any_of -> parameter_matcher + # + # Matches if any +matchers+ match. + # object = mock() + # object.expects(:method_1).with(any_of(1, 3)) + # object.method_1(1) + # # no error raised + # + # object = mock() + # object.expects(:method_1).with(any_of(1, 3)) + # object.method_1(3) + # # no error raised + # + # object = mock() + # object.expects(:method_1).with(any_of(1, 3)) + # object.method_1(2) + # # error raised, because method_1 was not called with 1 or 3 + def any_of(*matchers) + AnyOf.new(*matchers) + end + + class AnyOf < Base # :nodoc: + + def initialize(*matchers) + @matchers = matchers + end + + def matches?(available_parameters) + parameter = available_parameters.shift + @matchers.any? { |matcher| matcher.matches?([parameter]) } + end + + def mocha_inspect + "any_of(#{@matchers.map { |matcher| matcher.mocha_inspect }.join(", ") })" + end + + end + + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/any_parameters.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/any_parameters.rb new file mode 100644 index 0000000000..11dae83edb --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/any_parameters.rb @@ -0,0 +1,40 @@ +require 'mocha/parameter_matchers/base' + +module Mocha + + module ParameterMatchers + + # :call-seq: any_parameters() -> parameter_matcher + # + # Matches any parameters. + # object = mock() + # object.expects(:method_1).with(any_parameters) + # object.method_1(1, 2, 3, 4) + # # no error raised + # + # object = mock() + # object.expects(:method_1).with(any_parameters) + # object.method_1(5, 6, 7, 8, 9, 0) + # # no error raised + def any_parameters + AnyParameters.new + end + + class AnyParameters < Base # :nodoc: + + def matches?(available_parameters) + while available_parameters.length > 0 do + available_parameters.shift + end + return true + end + + def mocha_inspect + "any_parameters" + end + + end + + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/anything.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/anything.rb new file mode 100644 index 0000000000..e82ef86a00 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/anything.rb @@ -0,0 +1,33 @@ +require 'mocha/parameter_matchers/base' + +module Mocha + + module ParameterMatchers + + # :call-seq: anything -> parameter_matcher + # + # Matches any object. + # object = mock() + # object.expects(:method_1).with(anything) + # object.method_1('foo') + # # no error raised + def anything + Anything.new + end + + class Anything < Base # :nodoc: + + def matches?(available_parameters) + available_parameters.shift + return true + end + + def mocha_inspect + "anything" + end + + end + + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/base.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/base.rb new file mode 100644 index 0000000000..6aaec51a16 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/base.rb @@ -0,0 +1,15 @@ +module Mocha + + module ParameterMatchers + + class Base # :nodoc: + + def to_matcher + self + end + + end + + end + +end diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/equals.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/equals.rb new file mode 100644 index 0000000000..bdc61a0f5e --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/equals.rb @@ -0,0 +1,42 @@ +require 'mocha/parameter_matchers/base' + +module Mocha + + module ParameterMatchers + + # :call-seq: equals(value) -> parameter_matcher + # + # Matches +Object+ equalling +value+. + # object = mock() + # object.expects(:method_1).with(equals(2)) + # object.method_1(2) + # # no error raised + # + # object = mock() + # object.expects(:method_1).with(equals(2)) + # object.method_1(3) + # # error raised, because method_1 was not called with Object equalling 3 + def equals(value) + Equals.new(value) + end + + class Equals < Base # :nodoc: + + def initialize(value) + @value = value + end + + def matches?(available_parameters) + parameter = available_parameters.shift + parameter == @value + end + + def mocha_inspect + @value.mocha_inspect + end + + end + + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/has_entries.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/has_entries.rb new file mode 100644 index 0000000000..30cf025a59 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/has_entries.rb @@ -0,0 +1,42 @@ +require 'mocha/parameter_matchers/base' + +module Mocha + + module ParameterMatchers + + # :call-seq: has_entries(entries) -> parameter_matcher + # + # Matches +Hash+ containing all +entries+. + # object = mock() + # object.expects(:method_1).with(has_entries('key_1' => 1, 'key_2' => 2)) + # object.method_1('key_1' => 1, 'key_2' => 2, 'key_3' => 3) + # # no error raised + # + # object = mock() + # object.expects(:method_1).with(has_entries('key_1' => 1, 'key_2' => 2)) + # object.method_1('key_1' => 1, 'key_2' => 99) + # # error raised, because method_1 was not called with Hash containing entries: 'key_1' => 1, 'key_2' => 2 + def has_entries(entries) + HasEntries.new(entries) + end + + class HasEntries < Base # :nodoc: + + def initialize(entries) + @entries = entries + end + + def matches?(available_parameters) + parameter = available_parameters.shift + @entries.all? { |key, value| parameter[key] == value } + end + + def mocha_inspect + "has_entries(#{@entries.mocha_inspect})" + end + + end + + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/has_entry.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/has_entry.rb new file mode 100644 index 0000000000..b6459613d2 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/has_entry.rb @@ -0,0 +1,55 @@ +require 'mocha/parameter_matchers/base' + +module Mocha + + module ParameterMatchers + + # :call-seq: has_entry(key, value) -> parameter_matcher + # has_entry(key => value) -> parameter_matcher + # + # Matches +Hash+ containing entry with +key+ and +value+. + # object = mock() + # object.expects(:method_1).with(has_entry('key_1', 1)) + # object.method_1('key_1' => 1, 'key_2' => 2) + # # no error raised + # + # object = mock() + # object.expects(:method_1).with(has_entry('key_1' => 1)) + # object.method_1('key_1' => 1, 'key_2' => 2) + # # no error raised + # + # object = mock() + # object.expects(:method_1).with(has_entry('key_1', 1)) + # object.method_1('key_1' => 2, 'key_2' => 1) + # # error raised, because method_1 was not called with Hash containing entry: 'key_1' => 1 + # + # object = mock() + # object.expects(:method_1).with(has_entry('key_1' => 1)) + # object.method_1('key_1' => 2, 'key_2' => 1) + # # error raised, because method_1 was not called with Hash containing entry: 'key_1' => 1 + def has_entry(*options) + key, value = options.shift, options.shift + key, value = key.to_a[0][0..1] if key.is_a?(Hash) + HasEntry.new(key, value) + end + + class HasEntry < Base # :nodoc: + + def initialize(key, value) + @key, @value = key, value + end + + def matches?(available_parameters) + parameter = available_parameters.shift + parameter[@key] == @value + end + + def mocha_inspect + "has_entry(#{@key.mocha_inspect}, #{@value.mocha_inspect})" + end + + end + + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/has_key.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/has_key.rb new file mode 100644 index 0000000000..2478152195 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/has_key.rb @@ -0,0 +1,42 @@ +require 'mocha/parameter_matchers/base' + +module Mocha + + module ParameterMatchers + + # :call-seq: has_key(key) -> parameter_matcher + # + # Matches +Hash+ containing +key+. + # object = mock() + # object.expects(:method_1).with(has_key('key_1')) + # object.method_1('key_1' => 1, 'key_2' => 2) + # # no error raised + # + # object = mock() + # object.expects(:method_1).with(has_key('key_1')) + # object.method_1('key_2' => 2) + # # error raised, because method_1 was not called with Hash containing key: 'key_1' + def has_key(key) + HasKey.new(key) + end + + class HasKey < Base # :nodoc: + + def initialize(key) + @key = key + end + + def matches?(available_parameters) + parameter = available_parameters.shift + parameter.keys.include?(@key) + end + + def mocha_inspect + "has_key(#{@key.mocha_inspect})" + end + + end + + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/has_value.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/has_value.rb new file mode 100644 index 0000000000..2c6fe7c5e4 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/has_value.rb @@ -0,0 +1,42 @@ +require 'mocha/parameter_matchers/base' + +module Mocha + + module ParameterMatchers + + # :call-seq: has_value(value) -> parameter_matcher + # + # Matches +Hash+ containing +value+. + # object = mock() + # object.expects(:method_1).with(has_value(1)) + # object.method_1('key_1' => 1, 'key_2' => 2) + # # no error raised + # + # object = mock() + # object.expects(:method_1).with(has_value(1)) + # object.method_1('key_2' => 2) + # # error raised, because method_1 was not called with Hash containing value: 1 + def has_value(value) + HasValue.new(value) + end + + class HasValue < Base # :nodoc: + + def initialize(value) + @value = value + end + + def matches?(available_parameters) + parameter = available_parameters.shift + parameter.values.include?(@value) + end + + def mocha_inspect + "has_value(#{@value.mocha_inspect})" + end + + end + + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/includes.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/includes.rb new file mode 100644 index 0000000000..4539a5c440 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/includes.rb @@ -0,0 +1,40 @@ +require 'mocha/parameter_matchers/base' + +module Mocha + + module ParameterMatchers + + # :call-seq: includes(item) -> parameter_matcher + # + # Matches any object that responds true to include?(item) + # object = mock() + # object.expects(:method_1).with(includes('foo')) + # object.method_1(['foo', 'bar']) + # # no error raised + # + # object.method_1(['baz']) + # # error raised, because ['baz'] does not include 'foo'. + def includes(item) + Includes.new(item) + end + + class Includes < Base # :nodoc: + + def initialize(item) + @item = item + end + + def matches?(available_parameters) + parameter = available_parameters.shift + return parameter.include?(@item) + end + + def mocha_inspect + "includes(#{@item.mocha_inspect})" + end + + end + + end + +end diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/instance_of.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/instance_of.rb new file mode 100644 index 0000000000..49b4a478d8 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/instance_of.rb @@ -0,0 +1,42 @@ +require 'mocha/parameter_matchers/base' + +module Mocha + + module ParameterMatchers + + # :call-seq: instance_of(klass) -> parameter_matcher + # + # Matches any object that is an instance of +klass+ + # object = mock() + # object.expects(:method_1).with(instance_of(String)) + # object.method_1('string') + # # no error raised + # + # object = mock() + # object.expects(:method_1).with(instance_of(String)) + # object.method_1(99) + # # error raised, because method_1 was not called with an instance of String + def instance_of(klass) + InstanceOf.new(klass) + end + + class InstanceOf < Base # :nodoc: + + def initialize(klass) + @klass = klass + end + + def matches?(available_parameters) + parameter = available_parameters.shift + parameter.instance_of?(@klass) + end + + def mocha_inspect + "instance_of(#{@klass.mocha_inspect})" + end + + end + + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/is_a.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/is_a.rb new file mode 100644 index 0000000000..a721db5238 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/is_a.rb @@ -0,0 +1,42 @@ +require 'mocha/parameter_matchers/base' + +module Mocha + + module ParameterMatchers + + # :call-seq: is_a(klass) -> parameter_matcher + # + # Matches any object that is a +klass+ + # object = mock() + # object.expects(:method_1).with(is_a(Integer)) + # object.method_1(99) + # # no error raised + # + # object = mock() + # object.expects(:method_1).with(is_a(Integer)) + # object.method_1('string') + # # error raised, because method_1 was not called with an Integer + def is_a(klass) + IsA.new(klass) + end + + class IsA < Base # :nodoc: + + def initialize(klass) + @klass = klass + end + + def matches?(available_parameters) + parameter = available_parameters.shift + parameter.is_a?(@klass) + end + + def mocha_inspect + "is_a(#{@klass.mocha_inspect})" + end + + end + + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/kind_of.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/kind_of.rb new file mode 100644 index 0000000000..710d709d0a --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/kind_of.rb @@ -0,0 +1,42 @@ +require 'mocha/parameter_matchers/base' + +module Mocha + + module ParameterMatchers + + # :call-seq: kind_of(klass) -> parameter_matcher + # + # Matches any object that is a kind of +klass+ + # object = mock() + # object.expects(:method_1).with(kind_of(Integer)) + # object.method_1(99) + # # no error raised + # + # object = mock() + # object.expects(:method_1).with(kind_of(Integer)) + # object.method_1('string') + # # error raised, because method_1 was not called with a kind of Integer + def kind_of(klass) + KindOf.new(klass) + end + + class KindOf < Base # :nodoc: + + def initialize(klass) + @klass = klass + end + + def matches?(available_parameters) + parameter = available_parameters.shift + parameter.kind_of?(@klass) + end + + def mocha_inspect + "kind_of(#{@klass.mocha_inspect})" + end + + end + + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/not.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/not.rb new file mode 100644 index 0000000000..ec48ade3d2 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/not.rb @@ -0,0 +1,42 @@ +require 'mocha/parameter_matchers/base' + +module Mocha + + module ParameterMatchers + + # :call-seq: Not(matcher) -> parameter_matcher + # + # Matches if +matcher+ does not match. + # object = mock() + # object.expects(:method_1).with(Not(includes(1))) + # object.method_1([0, 2, 3]) + # # no error raised + # + # object = mock() + # object.expects(:method_1).with(Not(includes(1))) + # object.method_1([0, 1, 2, 3]) + # # error raised, because method_1 was not called with object not including 1 + def Not(matcher) + Not.new(matcher) + end + + class Not < Base # :nodoc: + + def initialize(matcher) + @matcher = matcher + end + + def matches?(available_parameters) + parameter = available_parameters.shift + !@matcher.matches?([parameter]) + end + + def mocha_inspect + "Not(#{@matcher.mocha_inspect})" + end + + end + + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/object.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/object.rb new file mode 100644 index 0000000000..f3a639bfa2 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/object.rb @@ -0,0 +1,9 @@ +require 'mocha/parameter_matchers/equals' + +class Object + + def to_matcher + Mocha::ParameterMatchers::Equals.new(self) + end + +end diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/optionally.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/optionally.rb new file mode 100644 index 0000000000..bb96251875 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/optionally.rb @@ -0,0 +1,33 @@ +module Mocha + + module ParameterMatchers + + def optionally(*matchers) + Optionally.new(*matchers) + end + + class Optionally < Base # :nodoc: + + def initialize(*parameters) + @matchers = parameters.map { |parameter| parameter.to_matcher } + end + + def matches?(available_parameters) + index = 0 + while (available_parameters.length > 0) && (index < @matchers.length) do + matcher = @matchers[index] + return false unless matcher.matches?(available_parameters) + index += 1 + end + return true + end + + def mocha_inspect + "optionally(#{@matchers.map { |matcher| matcher.mocha_inspect }.join(", ") })" + end + + end + + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/regexp_matches.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/regexp_matches.rb new file mode 100644 index 0000000000..cc46436eb0 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/regexp_matches.rb @@ -0,0 +1,43 @@ +require 'mocha/parameter_matchers/base' + +module Mocha + + module ParameterMatchers + + # :call-seq: regexp_matches(regexp) -> parameter_matcher + # + # Matches any object that matches the regular expression, +regexp+. + # object = mock() + # object.expects(:method_1).with(regexp_matches(/e/)) + # object.method_1('hello') + # # no error raised + # + # object = mock() + # object.expects(:method_1).with(regexp_matches(/a/)) + # object.method_1('hello') + # # error raised, because method_1 was not called with a parameter that matched the + # # regular expression + def regexp_matches(regexp) + RegexpMatches.new(regexp) + end + + class RegexpMatches < Base # :nodoc: + + def initialize(regexp) + @regexp = regexp + end + + def matches?(available_parameters) + parameter = available_parameters.shift + parameter =~ @regexp + end + + def mocha_inspect + "regexp_matches(#{@regexp.mocha_inspect})" + end + + end + + end + +end diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameters_matcher.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameters_matcher.rb new file mode 100644 index 0000000000..d43ae43756 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/parameters_matcher.rb @@ -0,0 +1,37 @@ +require 'mocha/inspect' +require 'mocha/parameter_matchers' + +module Mocha + + class ParametersMatcher + + def initialize(expected_parameters = [ParameterMatchers::AnyParameters.new], &matching_block) + @expected_parameters, @matching_block = expected_parameters, matching_block + end + + def match?(actual_parameters = []) + if @matching_block + return @matching_block.call(*actual_parameters) + else + return parameters_match?(actual_parameters) + end + end + + def parameters_match?(actual_parameters) + matchers.all? { |matcher| matcher.matches?(actual_parameters) } && (actual_parameters.length == 0) + end + + def mocha_inspect + signature = matchers.mocha_inspect + signature = signature.gsub(/^\[|\]$/, '') + signature = signature.gsub(/^\{|\}$/, '') if matchers.length == 1 + "(#{signature})" + end + + def matchers + @expected_parameters.map { |parameter| parameter.to_matcher } + end + + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/pretty_parameters.rb b/vendor/gems/mocha-0.5.6/lib/mocha/pretty_parameters.rb new file mode 100644 index 0000000000..59ed636f89 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/pretty_parameters.rb @@ -0,0 +1,28 @@ +require 'mocha/inspect' + +module Mocha + + class PrettyParameters + + def initialize(params) + @params = params + @params_string = params.mocha_inspect + end + + def pretty + remove_outer_array_braces! + remove_outer_hash_braces! + @params_string + end + + def remove_outer_array_braces! + @params_string = @params_string.gsub(/^\[|\]$/, '') + end + + def remove_outer_hash_braces! + @params_string = @params_string.gsub(/^\{|\}$/, '') if @params.length == 1 + end + + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/return_values.rb b/vendor/gems/mocha-0.5.6/lib/mocha/return_values.rb new file mode 100644 index 0000000000..b4852c5f6c --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/return_values.rb @@ -0,0 +1,34 @@ +require 'mocha/single_return_value' + +module Mocha # :nodoc: + + class ReturnValues # :nodoc: + + def self.build(*values) + new(*values.map { |value| SingleReturnValue.new(value) }) + end + + attr_accessor :values + + def initialize(*values) + @values = values + end + + def next + case @values.length + when 0 + nil + when 1 + @values.first.evaluate + else + @values.shift.evaluate + end + end + + def +(other) + self.class.new(*(@values + other.values)) + end + + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/sequence.rb b/vendor/gems/mocha-0.5.6/lib/mocha/sequence.rb new file mode 100644 index 0000000000..ed9852e0c2 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/sequence.rb @@ -0,0 +1,42 @@ +module Mocha # :nodoc: + + class Sequence + + class InSequenceOrderingConstraint + + def initialize(sequence, index) + @sequence, @index = sequence, index + end + + def allows_invocation_now? + @sequence.satisfied_to_index?(@index) + end + + def mocha_inspect + "in sequence #{@sequence.mocha_inspect}" + end + + end + + def initialize(name) + @name = name + @expectations = [] + end + + def constrain_as_next_in_sequence(expectation) + index = @expectations.length + @expectations << expectation + expectation.add_ordering_constraint(InSequenceOrderingConstraint.new(self, index)) + end + + def satisfied_to_index?(index) + @expectations[0...index].all? { |expectation| expectation.satisfied? } + end + + def mocha_inspect + "#{@name.mocha_inspect}" + end + + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/setup_and_teardown.rb b/vendor/gems/mocha-0.5.6/lib/mocha/setup_and_teardown.rb new file mode 100644 index 0000000000..034ce1d6b6 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/setup_and_teardown.rb @@ -0,0 +1,23 @@ +require 'mocha/central' + +module Mocha + + module SetupAndTeardown + + def setup_stubs + $stubba = Mocha::Central.new + end + + def verify_stubs + $stubba.verify_all { yield if block_given? } if $stubba + end + + def teardown_stubs + if $stubba then + $stubba.unstub_all + $stubba = nil + end + end + + end +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/single_return_value.rb b/vendor/gems/mocha-0.5.6/lib/mocha/single_return_value.rb new file mode 100644 index 0000000000..f420b8b8cf --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/single_return_value.rb @@ -0,0 +1,24 @@ +require 'mocha/is_a' +require 'mocha/deprecation' + +module Mocha # :nodoc: + + class SingleReturnValue # :nodoc: + + def initialize(value) + @value = value + end + + def evaluate + if @value.__is_a__(Proc) then + message = 'use of Expectation#returns with instance of Proc - see Expectation#returns RDoc for alternatives' + Deprecation.warning(message) + @value.call + else + @value + end + end + + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/single_yield.rb b/vendor/gems/mocha-0.5.6/lib/mocha/single_yield.rb new file mode 100644 index 0000000000..5af5716213 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/single_yield.rb @@ -0,0 +1,18 @@ +module Mocha # :nodoc: + + class SingleYield # :nodoc: + + attr_reader :parameters + + def initialize(*parameters) + @parameters = parameters + end + + def each + yield(@parameters) + end + + end + +end + diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/standalone.rb b/vendor/gems/mocha-0.5.6/lib/mocha/standalone.rb new file mode 100644 index 0000000000..8e3a7cefcb --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/standalone.rb @@ -0,0 +1,32 @@ +require 'mocha/auto_verify' +require 'mocha/parameter_matchers' +require 'mocha/setup_and_teardown' + +module Mocha + + module Standalone + + include AutoVerify + include ParameterMatchers + include SetupAndTeardown + + def mocha_setup + setup_stubs + end + + def mocha_verify(&block) + verify_mocks(&block) + verify_stubs(&block) + end + + def mocha_teardown + begin + teardown_mocks + ensure + teardown_stubs + end + end + + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/stub.rb b/vendor/gems/mocha-0.5.6/lib/mocha/stub.rb new file mode 100644 index 0000000000..1b3cccb8a6 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/stub.rb @@ -0,0 +1,18 @@ +require 'mocha/expectation' + +module Mocha # :nodoc: + + class Stub < Expectation # :nodoc: + + def initialize(mock, method_name, backtrace = nil) + super + @expected_count = Range.at_least(0) + end + + def verify + true + end + + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/test_case_adapter.rb b/vendor/gems/mocha-0.5.6/lib/mocha/test_case_adapter.rb new file mode 100644 index 0000000000..dc7e33b689 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/test_case_adapter.rb @@ -0,0 +1,49 @@ +require 'mocha/expectation_error' + +module Mocha + + module TestCaseAdapter + + def self.included(base) + base.class_eval do + + alias_method :run_before_mocha_test_case_adapter, :run + + def run(result) + yield(Test::Unit::TestCase::STARTED, name) + @_result = result + begin + mocha_setup + begin + setup + __send__(@method_name) + mocha_verify { add_assertion } + rescue Mocha::ExpectationError => e + add_failure(e.message, e.backtrace) + rescue Test::Unit::AssertionFailedError => e + add_failure(e.message, e.backtrace) + rescue StandardError, ScriptError + add_error($!) + ensure + begin + teardown + rescue Test::Unit::AssertionFailedError => e + add_failure(e.message, e.backtrace) + rescue StandardError, ScriptError + add_error($!) + end + end + ensure + mocha_teardown + end + result.add_run + yield(Test::Unit::TestCase::FINISHED, name) + end + + end + + end + + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/yield_parameters.rb b/vendor/gems/mocha-0.5.6/lib/mocha/yield_parameters.rb new file mode 100644 index 0000000000..cb58985080 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha/yield_parameters.rb @@ -0,0 +1,31 @@ +require 'mocha/no_yields' +require 'mocha/single_yield' +require 'mocha/multiple_yields' + +module Mocha # :nodoc: + + class YieldParameters # :nodoc: + + def initialize + @parameter_groups = [] + end + + def next_invocation + case @parameter_groups.length + when 0; NoYields.new + when 1; @parameter_groups.first + else @parameter_groups.shift + end + end + + def add(*parameters) + @parameter_groups << SingleYield.new(*parameters) + end + + def multiple_add(*parameter_groups) + @parameter_groups << MultipleYields.new(*parameter_groups) + end + + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha_standalone.rb b/vendor/gems/mocha-0.5.6/lib/mocha_standalone.rb new file mode 100644 index 0000000000..ce605811a5 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/mocha_standalone.rb @@ -0,0 +1,2 @@ +require 'mocha/standalone' +require 'mocha/object' diff --git a/vendor/gems/mocha-0.5.6/lib/stubba.rb b/vendor/gems/mocha-0.5.6/lib/stubba.rb new file mode 100644 index 0000000000..eade747f60 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/lib/stubba.rb @@ -0,0 +1,2 @@ +# for backwards compatibility +require 'mocha' \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/._deprecation_disabler.rb b/vendor/gems/mocha-0.5.6/test/._deprecation_disabler.rb new file mode 100644 index 0000000000000000000000000000000000000000..64a1d063ad9bd1810b28f48b53d10f8bb76eb695 GIT binary patch literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_IB@I*x(lG;w zCDF7oBE&_L^K>83Qh_W(6KjQ9 E0G!7c&Hw-a literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/test/._execution_point.rb b/vendor/gems/mocha-0.5.6/test/._execution_point.rb new file mode 100644 index 0000000000000000000000000000000000000000..dd624fd16f8feae7c2c2c567245b1ed03d2beacc GIT binary patch literal 176 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aUBqY_#1$j2;dkJ62u_Ir3h3C(lG;w zCD61nBE&_L^K>83QX#Bb0EH|U AmjD0& literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/test/._method_definer.rb b/vendor/gems/mocha-0.5.6/test/._method_definer.rb new file mode 100644 index 0000000000000000000000000000000000000000..d51c1adf493adce28d877590a8fb4caca43ad511 GIT binary patch literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_Ir3_RG(lG;w zCDF7oBE&_L^K>83Qh_W(AhQ+# DpE?)M literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/test/._test_helper.rb b/vendor/gems/mocha-0.5.6/test/._test_helper.rb new file mode 100644 index 0000000000000000000000000000000000000000..ef386d597ccdd4c023e6dc03f6196364e480edf3 GIT binary patch literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_IB?(jt(lG;w zL2P8e2;@M;MU(S$^>P!Fi}G_5OHzyVk`s$kOBkw?^K(jb^Av0qObo3Rax(K$flMH) F766-97s>zt literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/test/._test_runner.rb b/vendor/gems/mocha-0.5.6/test/._test_runner.rb new file mode 100644 index 0000000000000000000000000000000000000000..614cc3f52b544402d0f68569852928eeac54cb0d GIT binary patch literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_IB@a{z(lG;w zrO>o7BE&_L^K$Vqox1Ojhs@R)|o50+1L3ClDI}aUBqY_#1$j2;dkJ62zdzvKFWmq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r9xP>0Jjtv AEdT%j literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/test/acceptance/._mocha_acceptance_test.rb b/vendor/gems/mocha-0.5.6/test/acceptance/._mocha_acceptance_test.rb new file mode 100644 index 0000000000000000000000000000000000000000..39f36f8772b1e54e0abeef50dfada45a0b1d3ffd GIT binary patch literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62zb*+Y3|*(lG;w zCDF7oBE&_L^K>83Qh_WB18ap^ E0MF?dY5)KL literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/test/acceptance/._mock_with_initializer_block_acceptance_test.rb b/vendor/gems/mocha-0.5.6/test/acceptance/._mock_with_initializer_block_acceptance_test.rb new file mode 100644 index 0000000000000000000000000000000000000000..8e3a0e715c7240cf22a001636d61391ff8c76207 GIT binary patch literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62zeT=PFPsNXHBy zmPFIWh!7V|&d=4$O-wGz&rK{zE!Im;EJ`h5s7}t$Db3AOuvIXyR>;ZBO9iqFEv*%5 E0Wm)rhX4Qo literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/test/acceptance/._mocked_methods_dispatch_acceptance_test.rb b/vendor/gems/mocha-0.5.6/test/acceptance/._mocked_methods_dispatch_acceptance_test.rb new file mode 100644 index 0000000000000000000000000000000000000000..de0671d7e33a6e5d1b7cc46caef5152f8c442656 GIT binary patch literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_I#RF6d(lG;w zCDF7oBE&_L^K>83Qh_W(Lu-Xv E0GHkuzW@LL literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/test/acceptance/._optional_parameters_acceptance_test.rb b/vendor/gems/mocha-0.5.6/test/acceptance/._optional_parameters_acceptance_test.rb new file mode 100644 index 0000000000000000000000000000000000000000..b21de1fbcabbd5bd51641ff36067367a47e70510 GIT binary patch literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62zeT*8`{&q+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r2<)|M%D_o E00J-=_y7O^ literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/test/acceptance/._parameter_matcher_acceptance_test.rb b/vendor/gems/mocha-0.5.6/test/acceptance/._parameter_matcher_acceptance_test.rb new file mode 100644 index 0000000000000000000000000000000000000000..421ef5a2964d9913641dd036d59e1e512653909c GIT binary patch literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62zdn$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62zdz$_!Kr(lG;w zCDF7oBE&_L^K>83Qh_XEOKXK% E0GM?b#Q*>R literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/test/acceptance/._sequence_acceptance_test.rb b/vendor/gems/mocha-0.5.6/test/acceptance/._sequence_acceptance_test.rb new file mode 100644 index 0000000000000000000000000000000000000000..8bb33ec9fa28302261235a3aa27f347c811aa49b GIT binary patch literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62zdz{0*oSq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV#BTPx&b=A{CerWV!; FwEzR081w)D literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/test/acceptance/._standalone_acceptance_test.rb b/vendor/gems/mocha-0.5.6/test/acceptance/._standalone_acceptance_test.rb new file mode 100644 index 0000000000000000000000000000000000000000..68a7236cd365c83b3213cab92c1bd182103b29df GIT binary patch literal 176 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aUBqY_#1$j2;dkJ62u_I#R60c(lG;w zCD61nBE&_L^K>83QX#Bb0DFrU AdH?_b literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/test/acceptance/._stubba_acceptance_test.rb b/vendor/gems/mocha-0.5.6/test/acceptance/._stubba_acceptance_test.rb new file mode 100644 index 0000000000000000000000000000000000000000..5140f122893bf1503e3a40f4fed29737fba5b4c6 GIT binary patch literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_I#RgOg(lG;w zrO>o7BE&_L^K.method(any_parameters) - expected calls: 0, actual calls: 1'], failure_messages + end + + def test_should_pass_if_method_is_expected_twice_and_is_called_twice + test_result = run_test do + mock = mock('mock') + mock.expects(:method).times(2) + 2.times { mock.method } + end + assert_passed(test_result) + end + + def test_should_fail_if_method_is_expected_twice_but_is_called_once + test_result = run_test do + mock = mock('mock') + mock.expects(:method).times(2) + 1.times { mock.method } + end + assert_failed(test_result) + failure_messages = test_result.failures.map { |failure| failure.message } + assert_equal ['#.method(any_parameters) - expected calls: 2, actual calls: 1'], failure_messages + end + + def test_should_fail_if_method_is_expected_twice_but_is_called_three_times + test_result = run_test do + mock = mock('mock') + mock.expects(:method).times(2) + 3.times { mock.method } + end + assert_failed(test_result) + failure_messages = test_result.failures.map { |failure| failure.message } + assert_equal ['#.method(any_parameters) - expected calls: 2, actual calls: 3'], failure_messages + end + + def test_should_pass_if_method_is_expected_between_two_and_four_times_and_is_called_twice + test_result = run_test do + mock = mock('mock') + mock.expects(:method).times(2..4) + 2.times { mock.method } + end + assert_passed(test_result) + end + + def test_should_pass_if_method_is_expected_between_two_and_four_times_and_is_called_three_times + test_result = run_test do + mock = mock('mock') + mock.expects(:method).times(2..4) + 3.times { mock.method } + end + assert_passed(test_result) + end + + def test_should_pass_if_method_is_expected_between_two_and_four_times_and_is_called_four_times + test_result = run_test do + mock = mock('mock') + mock.expects(:method).times(2..4) + 4.times { mock.method } + end + assert_passed(test_result) + end + + def test_should_fail_if_method_is_expected_between_two_and_four_times_and_is_called_once + test_result = run_test do + mock = mock('mock') + mock.expects(:method).times(2..4) + 1.times { mock.method } + end + assert_failed(test_result) + failure_messages = test_result.failures.map { |failure| failure.message } + assert_equal ['#.method(any_parameters) - expected calls: 2..4, actual calls: 1'], failure_messages + end + + def test_should_fail_if_method_is_expected_between_two_and_four_times_and_is_called_five_times + test_result = run_test do + mock = mock('mock') + mock.expects(:method).times(2..4) + 5.times { mock.method } + end + assert_failed(test_result) + failure_messages = test_result.failures.map { |failure| failure.message } + assert_equal ['#.method(any_parameters) - expected calls: 2..4, actual calls: 5'], failure_messages + end + + def test_should_pass_if_method_is_expected_at_least_once_and_is_called_once + test_result = run_test do + mock = mock('mock') + mock.expects(:method).at_least_once + 1.times { mock.method } + end + assert_passed(test_result) + end + + def test_should_pass_if_method_is_expected_at_least_once_and_is_called_twice + test_result = run_test do + mock = mock('mock') + mock.expects(:method).at_least_once + 2.times { mock.method } + end + assert_passed(test_result) + end + + def test_should_fail_if_method_is_expected_at_least_once_but_is_never_called + test_result = run_test do + mock = mock('mock') + mock.expects(:method).at_least_once + 0.times { mock.method } + end + assert_failed(test_result) + failure_messages = test_result.failures.map { |failure| failure.message } + assert_equal ['#.method(any_parameters) - expected calls: at least 1, actual calls: 0'], failure_messages + end + + def test_should_pass_if_method_is_expected_at_most_once_and_is_never_called + test_result = run_test do + mock = mock('mock') + mock.expects(:method).at_most_once + 0.times { mock.method } + end + assert_passed(test_result) + end + + def test_should_pass_if_method_is_expected_at_most_once_and_called_once + test_result = run_test do + mock = mock('mock') + mock.expects(:method).at_most_once + 1.times { mock.method } + end + assert_passed(test_result) + end + + def test_should_fail_if_method_is_expected_at_most_once_but_is_called_twice + test_result = run_test do + mock = mock('mock') + mock.expects(:method).at_most_once + 2.times { mock.method } + end + assert_failed(test_result) + failure_messages = test_result.failures.map { |failure| failure.message } + assert_equal ['#.method(any_parameters) - expected calls: at most 1, actual calls: 2'], failure_messages + end + + def test_should_pass_if_method_is_never_expected_and_is_never_called_even_if_everything_is_stubbed + test_result = run_test do + stub = stub_everything('stub') + stub.expects(:method).never + 0.times { stub.method } + end + assert_passed(test_result) + end + + def test_should_fail_if_method_is_never_expected_but_is_called_once_even_if_everything_is_stubbed + test_result = run_test do + stub = stub_everything('stub') + stub.expects(:method).never + 1.times { stub.method } + end + assert_failed(test_result) + failure_messages = test_result.failures.map { |failure| failure.message } + assert_equal ['#.method(any_parameters) - expected calls: 0, actual calls: 1'], failure_messages + end + +end diff --git a/vendor/gems/mocha-0.5.6/test/acceptance/mocha_acceptance_test.rb b/vendor/gems/mocha-0.5.6/test/acceptance/mocha_acceptance_test.rb new file mode 100644 index 0000000000..4e38b4e574 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/acceptance/mocha_acceptance_test.rb @@ -0,0 +1,98 @@ +require File.join(File.dirname(__FILE__), "..", "test_helper") +require 'mocha' + +class MochaAcceptanceTest < Test::Unit::TestCase + + class Rover + + def initialize(left_track, right_track, steps_per_metre, steps_per_degree) + @left_track, @right_track, @steps_per_metre, @steps_per_degree = left_track, right_track, steps_per_metre, steps_per_degree + end + + def forward(metres) + @left_track.step(metres * @steps_per_metre) + @right_track.step(metres * @steps_per_metre) + wait + end + + def backward(metres) + forward(-metres) + end + + def left(degrees) + @left_track.step(-degrees * @steps_per_degree) + @right_track.step(+degrees * @steps_per_degree) + wait + end + + def right(degrees) + left(-degrees) + end + + def wait + while (@left_track.moving? or @right_track.moving?); end + end + + end + + def test_should_step_both_tracks_forward_ten_steps + left_track = mock('left_track') + right_track = mock('right_track') + steps_per_metre = 5 + rover = Rover.new(left_track, right_track, steps_per_metre, nil) + + left_track.expects(:step).with(10) + right_track.expects(:step).with(10) + + left_track.stubs(:moving?).returns(false) + right_track.stubs(:moving?).returns(false) + + rover.forward(2) + end + + def test_should_step_both_tracks_backward_ten_steps + left_track = mock('left_track') + right_track = mock('right_track') + steps_per_metre = 5 + rover = Rover.new(left_track, right_track, steps_per_metre, nil) + + left_track.expects(:step).with(-10) + right_track.expects(:step).with(-10) + + left_track.stubs(:moving?).returns(false) + right_track.stubs(:moving?).returns(false) + + rover.backward(2) + end + + def test_should_step_left_track_forwards_five_steps_and_right_track_backwards_five_steps + left_track = mock('left_track') + right_track = mock('right_track') + steps_per_degree = 5.0 / 90.0 + rover = Rover.new(left_track, right_track, nil, steps_per_degree) + + left_track.expects(:step).with(+5) + right_track.expects(:step).with(-5) + + left_track.stubs(:moving?).returns(false) + right_track.stubs(:moving?).returns(false) + + rover.right(90) + end + + def test_should_step_left_track_backwards_five_steps_and_right_track_forwards_five_steps + left_track = mock('left_track') + right_track = mock('right_track') + steps_per_degree = 5.0 / 90.0 + rover = Rover.new(left_track, right_track, nil, steps_per_degree) + + left_track.expects(:step).with(-5) + right_track.expects(:step).with(+5) + + left_track.stubs(:moving?).returns(false) + right_track.stubs(:moving?).returns(false) + + rover.left(90) + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/acceptance/mock_with_initializer_block_acceptance_test.rb b/vendor/gems/mocha-0.5.6/test/acceptance/mock_with_initializer_block_acceptance_test.rb new file mode 100644 index 0000000000..51488e61fd --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/acceptance/mock_with_initializer_block_acceptance_test.rb @@ -0,0 +1,44 @@ +require File.join(File.dirname(__FILE__), "..", "test_helper") +require 'mocha' +require 'test_runner' + +class MockWithInitializerBlockAcceptanceTest < Test::Unit::TestCase + + include TestRunner + + def test_should_expect_two_method_invocations_and_receive_both_of_them + test_result = run_test do + mock = mock() do + expects(:method_1) + expects(:method_2) + end + mock.method_1 + mock.method_2 + end + assert_passed(test_result) + end + + def test_should_expect_two_method_invocations_but_receive_only_one_of_them + test_result = run_test do + mock = mock() do + expects(:method_1) + expects(:method_2) + end + mock.method_1 + end + assert_failed(test_result) + end + + def test_should_stub_methods + test_result = run_test do + mock = mock() do + stubs(:method_1).returns(1) + stubs(:method_2).returns(2) + end + assert_equal 1, mock.method_1 + assert_equal 2, mock.method_2 + end + assert_passed(test_result) + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/acceptance/mocked_methods_dispatch_acceptance_test.rb b/vendor/gems/mocha-0.5.6/test/acceptance/mocked_methods_dispatch_acceptance_test.rb new file mode 100644 index 0000000000..d77021553d --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/acceptance/mocked_methods_dispatch_acceptance_test.rb @@ -0,0 +1,71 @@ +require File.join(File.dirname(__FILE__), "..", "test_helper") +require 'mocha' +require 'test_runner' + +class MockedMethodDispatchAcceptanceTest < Test::Unit::TestCase + + include TestRunner + + def test_should_find_latest_matching_expectation + test_result = run_test do + mock = mock() + mock.stubs(:method).returns(1) + mock.stubs(:method).returns(2) + assert_equal 2, mock.method + assert_equal 2, mock.method + assert_equal 2, mock.method + end + assert_passed(test_result) + end + + def test_should_find_latest_expectation_which_has_not_stopped_matching + test_result = run_test do + mock = mock() + mock.stubs(:method).returns(1) + mock.stubs(:method).once.returns(2) + assert_equal 2, mock.method + assert_equal 1, mock.method + assert_equal 1, mock.method + end + assert_passed(test_result) + end + + def test_should_keep_finding_later_stub_and_so_never_satisfy_earlier_expectation + test_result = run_test do + mock = mock() + mock.expects(:method).returns(1) + mock.stubs(:method).returns(2) + assert_equal 2, mock.method + assert_equal 2, mock.method + assert_equal 2, mock.method + end + assert_failed(test_result) + end + + def test_should_find_later_expectation_until_it_stops_matching_then_find_earlier_stub + test_result = run_test do + mock = mock() + mock.stubs(:method).returns(1) + mock.expects(:method).returns(2) + assert_equal 2, mock.method + assert_equal 1, mock.method + assert_equal 1, mock.method + end + assert_passed(test_result) + end + + def test_should_find_latest_expectation_with_range_of_expected_invocation_count_which_has_not_stopped_matching + test_result = run_test do + mock = mock() + mock.stubs(:method).returns(1) + mock.stubs(:method).times(2..3).returns(2) + assert_equal 2, mock.method + assert_equal 2, mock.method + assert_equal 2, mock.method + assert_equal 1, mock.method + assert_equal 1, mock.method + end + assert_passed(test_result) + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/acceptance/optional_parameters_acceptance_test.rb b/vendor/gems/mocha-0.5.6/test/acceptance/optional_parameters_acceptance_test.rb new file mode 100644 index 0000000000..3a6f8322ef --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/acceptance/optional_parameters_acceptance_test.rb @@ -0,0 +1,63 @@ +require File.join(File.dirname(__FILE__), "..", "test_helper") +require 'mocha' +require 'test_runner' + +class OptionalParameterMatcherAcceptanceTest < Test::Unit::TestCase + + include TestRunner + + def test_should_pass_if_all_required_parameters_match_and_no_optional_parameters_are_supplied + test_result = run_test do + mock = mock() + mock.expects(:method).with(1, 2, optionally(3, 4)) + mock.method(1, 2) + end + assert_passed(test_result) + end + + def test_should_pass_if_all_required_and_optional_parameters_match_and_some_optional_parameters_are_supplied + test_result = run_test do + mock = mock() + mock.expects(:method).with(1, 2, optionally(3, 4)) + mock.method(1, 2, 3) + end + assert_passed(test_result) + end + + def test_should_pass_if_all_required_and_optional_parameters_match_and_all_optional_parameters_are_supplied + test_result = run_test do + mock = mock() + mock.expects(:method).with(1, 2, optionally(3, 4)) + mock.method(1, 2, 3, 4) + end + assert_passed(test_result) + end + + def test_should_fail_if_all_required_and_optional_parameters_match_but_too_many_optional_parameters_are_supplied + test_result = run_test do + mock = mock() + mock.expects(:method).with(1, 2, optionally(3, 4)) + mock.method(1, 2, 3, 4, 5) + end + assert_failed(test_result) + end + + def test_should_fail_if_all_required_parameters_match_but_some_optional_parameters_do_not_match + test_result = run_test do + mock = mock() + mock.expects(:method).with(1, 2, optionally(3, 4)) + mock.method(1, 2, 4) + end + assert_failed(test_result) + end + + def test_should_fail_if_all_required_parameters_match_but_no_optional_parameters_match + test_result = run_test do + mock = mock() + mock.expects(:method).with(1, 2, optionally(3, 4)) + mock.method(1, 2, 4, 5) + end + assert_failed(test_result) + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/acceptance/parameter_matcher_acceptance_test.rb b/vendor/gems/mocha-0.5.6/test/acceptance/parameter_matcher_acceptance_test.rb new file mode 100644 index 0000000000..c23880d16f --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/acceptance/parameter_matcher_acceptance_test.rb @@ -0,0 +1,117 @@ +require File.join(File.dirname(__FILE__), "..", "test_helper") +require 'mocha' +require 'test_runner' + +class ParameterMatcherAcceptanceTest < Test::Unit::TestCase + + include TestRunner + + def test_should_match_hash_parameter_with_specified_key + test_result = run_test do + mock = mock() + mock.expects(:method).with(has_key(:key_1)) + mock.method(:key_1 => 'value_1', :key_2 => 'value_2') + end + assert_passed(test_result) + end + + def test_should_not_match_hash_parameter_with_specified_key + test_result = run_test do + mock = mock() + mock.expects(:method).with(has_key(:key_1)) + mock.method(:key_2 => 'value_2') + end + assert_failed(test_result) + end + + def test_should_match_hash_parameter_with_specified_value + test_result = run_test do + mock = mock() + mock.expects(:method).with(has_value('value_1')) + mock.method(:key_1 => 'value_1', :key_2 => 'value_2') + end + assert_passed(test_result) + end + + def test_should_not_match_hash_parameter_with_specified_value + test_result = run_test do + mock = mock() + mock.expects(:method).with(has_value('value_1')) + mock.method(:key_2 => 'value_2') + end + assert_failed(test_result) + end + + def test_should_match_hash_parameter_with_specified_key_value_pair + test_result = run_test do + mock = mock() + mock.expects(:method).with(has_entry(:key_1, 'value_1')) + mock.method(:key_1 => 'value_1', :key_2 => 'value_2') + end + assert_passed(test_result) + end + + def test_should_not_match_hash_parameter_with_specified_key_value_pair + test_result = run_test do + mock = mock() + mock.expects(:method).with(has_entry(:key_1, 'value_2')) + mock.method(:key_1 => 'value_1', :key_2 => 'value_2') + end + assert_failed(test_result) + end + + def test_should_match_hash_parameter_with_specified_hash_entry + test_result = run_test do + mock = mock() + mock.expects(:method).with(has_entry(:key_1 => 'value_1')) + mock.method(:key_1 => 'value_1', :key_2 => 'value_2') + end + assert_passed(test_result) + end + + def test_should_not_match_hash_parameter_with_specified_hash_entry + test_result = run_test do + mock = mock() + mock.expects(:method).with(has_entry(:key_1 => 'value_2')) + mock.method(:key_1 => 'value_1', :key_2 => 'value_2') + end + assert_failed(test_result) + end + + def test_should_match_hash_parameter_with_specified_entries + test_result = run_test do + mock = mock() + mock.expects(:method).with(has_entries(:key_1 => 'value_1', :key_2 => 'value_2')) + mock.method(:key_1 => 'value_1', :key_2 => 'value_2', :key_3 => 'value_3') + end + assert_passed(test_result) + end + + def test_should_not_match_hash_parameter_with_specified_entries + test_result = run_test do + mock = mock() + mock.expects(:method).with(has_entries(:key_1 => 'value_1', :key_2 => 'value_2')) + mock.method(:key_1 => 'value_1', :key_2 => 'value_3') + end + assert_failed(test_result) + end + + def test_should_match_parameter_that_matches_regular_expression + test_result = run_test do + mock = mock() + mock.expects(:method).with(regexp_matches(/meter/)) + mock.method('this parameter should match') + end + assert_passed(test_result) + end + + def test_should_not_match_parameter_that_does_not_match_regular_expression + test_result = run_test do + mock = mock() + mock.expects(:method).with(regexp_matches(/something different/)) + mock.method('this parameter should not match') + end + assert_failed(test_result) + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/acceptance/partial_mocks_acceptance_test.rb b/vendor/gems/mocha-0.5.6/test/acceptance/partial_mocks_acceptance_test.rb new file mode 100644 index 0000000000..20fc7b84ec --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/acceptance/partial_mocks_acceptance_test.rb @@ -0,0 +1,40 @@ +require File.join(File.dirname(__FILE__), "..", "test_helper") +require 'mocha' +require 'test_runner' + +class PartialMockAcceptanceTest < Test::Unit::TestCase + + include TestRunner + + def test_should_pass_if_all_expectations_are_satisfied + test_result = run_test do + partial_mock_one = "partial_mock_one" + partial_mock_two = "partial_mock_two" + + partial_mock_one.expects(:first) + partial_mock_one.expects(:second) + partial_mock_two.expects(:third) + + partial_mock_one.first + partial_mock_one.second + partial_mock_two.third + end + assert_passed(test_result) + end + + def test_should_fail_if_all_expectations_are_not_satisfied + test_result = run_test do + partial_mock_one = "partial_mock_one" + partial_mock_two = "partial_mock_two" + + partial_mock_one.expects(:first) + partial_mock_one.expects(:second) + partial_mock_two.expects(:third) + + partial_mock_one.first + partial_mock_two.third + end + assert_failed(test_result) + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/acceptance/sequence_acceptance_test.rb b/vendor/gems/mocha-0.5.6/test/acceptance/sequence_acceptance_test.rb new file mode 100644 index 0000000000..3be6e7d757 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/acceptance/sequence_acceptance_test.rb @@ -0,0 +1,179 @@ +require File.join(File.dirname(__FILE__), "..", "test_helper") +require 'mocha' +require 'test_runner' + +class SequenceAcceptanceTest < Test::Unit::TestCase + + include TestRunner + + def test_should_constrain_invocations_to_occur_in_expected_order + test_result = run_test do + mock = mock() + sequence = sequence('one') + + mock.expects(:first).in_sequence(sequence) + mock.expects(:second).in_sequence(sequence) + + mock.second + end + assert_failed(test_result) + end + + def test_should_allow_invocations_in_sequence + test_result = run_test do + mock = mock() + sequence = sequence('one') + + mock.expects(:first).in_sequence(sequence) + mock.expects(:second).in_sequence(sequence) + + mock.first + mock.second + end + assert_passed(test_result) + end + + def test_should_constrain_invocations_to_occur_in_expected_order_even_if_expected_on_different_mocks + test_result = run_test do + mock_one = mock('1') + mock_two = mock('2') + sequence = sequence('one') + + mock_one.expects(:first).in_sequence(sequence) + mock_two.expects(:second).in_sequence(sequence) + + mock_two.second + end + assert_failed(test_result) + end + + def test_should_allow_invocations_in_sequence_even_if_expected_on_different_mocks + test_result = run_test do + mock_one = mock('1') + mock_two = mock('2') + sequence = sequence('one') + + mock_one.expects(:first).in_sequence(sequence) + mock_two.expects(:second).in_sequence(sequence) + + mock_one.first + mock_two.second + end + assert_passed(test_result) + end + + def test_should_constrain_invocations_to_occur_in_expected_order_even_if_expected_on_partial_mocks + test_result = run_test do + partial_mock_one = "1" + partial_mock_two = "2" + sequence = sequence('one') + + partial_mock_one.expects(:first).in_sequence(sequence) + partial_mock_two.expects(:second).in_sequence(sequence) + + partial_mock_two.second + end + assert_failed(test_result) + end + + def test_should_allow_invocations_in_sequence_even_if_expected_on_partial_mocks + test_result = run_test do + partial_mock_one = "1" + partial_mock_two = "2" + sequence = sequence('one') + + partial_mock_one.expects(:first).in_sequence(sequence) + partial_mock_two.expects(:second).in_sequence(sequence) + + partial_mock_one.first + partial_mock_two.second + end + assert_passed(test_result) + end + + def test_should_allow_stub_expectations_to_be_skipped_in_sequence + test_result = run_test do + mock = mock() + sequence = sequence('one') + + mock.expects(:first).in_sequence(sequence) + s = mock.stubs(:second).in_sequence(sequence) + mock.expects(:third).in_sequence(sequence) + + mock.first + mock.third + end + assert_passed(test_result) + end + + def test_should_regard_sequences_as_independent_of_each_other + test_result = run_test do + mock = mock() + sequence_one = sequence('one') + sequence_two = sequence('two') + + mock.expects(:first).in_sequence(sequence_one) + mock.expects(:second).in_sequence(sequence_one) + + mock.expects(:third).in_sequence(sequence_two) + mock.expects(:fourth).in_sequence(sequence_two) + + mock.first + mock.third + mock.second + mock.fourth + end + assert_passed(test_result) + end + + def test_should_include_sequence_in_failure_message + test_result = run_test do + mock = mock() + sequence = sequence('one') + + mock.expects(:first).in_sequence(sequence) + mock.expects(:second).in_sequence(sequence) + + mock.second + end + assert_failed(test_result) + assert_match Regexp.new("in sequence 'one'"), test_result.failures.first.message + end + + def test_should_allow_expectations_to_be_in_more_than_one_sequence + test_result = run_test do + mock = mock() + sequence_one = sequence('one') + sequence_two = sequence('two') + + mock.expects(:first).in_sequence(sequence_one) + mock.expects(:second).in_sequence(sequence_two) + mock.expects(:three).in_sequence(sequence_one).in_sequence(sequence_two) + + mock.first + mock.three + end + assert_failed(test_result) + assert_match Regexp.new("in sequence 'one'"), test_result.failures.first.message + assert_match Regexp.new("in sequence 'two'"), test_result.failures.first.message + end + + def test_should_have_shortcut_for_expectations_to_be_in_more_than_one_sequence + test_result = run_test do + mock = mock() + sequence_one = sequence('one') + sequence_two = sequence('two') + + mock.expects(:first).in_sequence(sequence_one) + mock.expects(:second).in_sequence(sequence_two) + mock.expects(:three).in_sequence(sequence_one, sequence_two) + + mock.first + mock.three + end + assert_failed(test_result) + assert_match Regexp.new("in sequence 'one'"), test_result.failures.first.message + assert_match Regexp.new("in sequence 'two'"), test_result.failures.first.message + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/acceptance/standalone_acceptance_test.rb b/vendor/gems/mocha-0.5.6/test/acceptance/standalone_acceptance_test.rb new file mode 100644 index 0000000000..1e101d7ca6 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/acceptance/standalone_acceptance_test.rb @@ -0,0 +1,131 @@ +require File.join(File.dirname(__FILE__), "..", "test_helper") +require 'mocha_standalone' + +class NotATestUnitAssertionFailedError < StandardError +end + +class NotATestUnitTestCase + + include Mocha::Standalone + + attr_reader :assertion_count + + def initialize + @assertion_count = 0 + end + + def run(test_method) + mocha_setup + begin + prepare + begin + send(test_method) + mocha_verify { @assertion_count += 1 } + rescue Mocha::ExpectationError => e + new_error = NotATestUnitAssertionFailedError.new(e.message) + new_error.set_backtrace(e.backtrace) + raise new_error + ensure + cleanup + end + ensure + mocha_teardown + end + end + + def prepare + end + + def cleanup + end + +end + +class SampleTest < NotATestUnitTestCase + + def mocha_with_fulfilled_expectation + mockee = mock() + mockee.expects(:blah) + mockee.blah + end + + def mocha_with_unfulfilled_expectation + mockee = mock() + mockee.expects(:blah) + end + + def mocha_with_unexpected_invocation + mockee = mock() + mockee.blah + end + + def stubba_with_fulfilled_expectation + stubbee = Class.new { define_method(:blah) {} }.new + stubbee.expects(:blah) + stubbee.blah + end + + def stubba_with_unfulfilled_expectation + stubbee = Class.new { define_method(:blah) {} }.new + stubbee.expects(:blah) + end + + def mocha_with_matching_parameter + mockee = mock() + mockee.expects(:blah).with(has_key(:wibble)) + mockee.blah(:wibble => 1) + end + + def mocha_with_non_matching_parameter + mockee = mock() + mockee.expects(:blah).with(has_key(:wibble)) + mockee.blah(:wobble => 2) + end + +end + +require 'test/unit' + +class StandaloneAcceptanceTest < Test::Unit::TestCase + + attr_reader :sample_test + + def setup + @sample_test = SampleTest.new + end + + def test_should_pass_mocha_test + assert_nothing_raised { sample_test.run(:mocha_with_fulfilled_expectation) } + assert_equal 1, sample_test.assertion_count + end + + def test_should_fail_mocha_test_due_to_unfulfilled_exception + assert_raises(NotATestUnitAssertionFailedError) { sample_test.run(:mocha_with_unfulfilled_expectation) } + assert_equal 1, sample_test.assertion_count + end + + def test_should_fail_mocha_test_due_to_unexpected_invocation + assert_raises(NotATestUnitAssertionFailedError) { sample_test.run(:mocha_with_unexpected_invocation) } + assert_equal 0, sample_test.assertion_count + end + + def test_should_pass_stubba_test + assert_nothing_raised { sample_test.run(:stubba_with_fulfilled_expectation) } + assert_equal 1, sample_test.assertion_count + end + + def test_should_fail_stubba_test + assert_raises(NotATestUnitAssertionFailedError) { sample_test.run(:stubba_with_unfulfilled_expectation) } + assert_equal 1, sample_test.assertion_count + end + + def test_should_pass_mocha_test_with_matching_parameter + assert_nothing_raised { sample_test.run(:mocha_with_matching_parameter) } + assert_equal 1, sample_test.assertion_count + end + + def test_should_fail_mocha_test_with_non_matching_parameter + assert_raises(NotATestUnitAssertionFailedError) { sample_test.run(:mocha_with_non_matching_parameter) } + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/acceptance/stubba_acceptance_test.rb b/vendor/gems/mocha-0.5.6/test/acceptance/stubba_acceptance_test.rb new file mode 100644 index 0000000000..93d8d12593 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/acceptance/stubba_acceptance_test.rb @@ -0,0 +1,102 @@ +require File.join(File.dirname(__FILE__), "..", "test_helper") +require 'mocha' + +class Widget + + def model + 'original_model' + end + + class << self + + def find(options) + [] + end + + def create(attributes) + Widget.new + end + + end + +end + +module Thingy + + def self.wotsit + :hoojamaflip + end + +end + +class StubbaAcceptanceTest < Test::Unit::TestCase + + def test_should_stub_instance_method + widget = Widget.new + widget.expects(:model).returns('different_model') + assert_equal 'different_model', widget.model + end + + def test_should_stub_module_method + should_stub_module_method + end + + def test_should_stub_module_method_again + should_stub_module_method + end + + def test_should_stub_class_method + should_stub_class_method + end + + def test_should_stub_class_method_again + should_stub_class_method + end + + def test_should_stub_instance_method_on_any_instance_of_a_class + should_stub_instance_method_on_any_instance_of_a_class + end + + def test_should_stub_instance_method_on_any_instance_of_a_class_again + should_stub_instance_method_on_any_instance_of_a_class + end + + def test_should_stub_two_different_class_methods + should_stub_two_different_class_methods + end + + def test_should_stub_two_different_class_methods_again + should_stub_two_different_class_methods + end + + private + + def should_stub_module_method + Thingy.expects(:wotsit).returns(:dooda) + assert_equal :dooda, Thingy.wotsit + end + + def should_stub_class_method + widgets = [Widget.new] + Widget.expects(:find).with(:all).returns(widgets) + assert_equal widgets, Widget.find(:all) + end + + def should_stub_two_different_class_methods + found_widgets = [Widget.new] + created_widget = Widget.new + Widget.expects(:find).with(:all).returns(found_widgets) + Widget.expects(:create).with(:model => 'wombat').returns(created_widget) + assert_equal found_widgets, Widget.find(:all) + assert_equal created_widget, Widget.create(:model => 'wombat') + end + + def should_stub_instance_method_on_any_instance_of_a_class + Widget.any_instance.expects(:model).at_least_once.returns('another_model') + widget_1 = Widget.new + widget_2 = Widget.new + assert_equal 'another_model', widget_1.model + assert_equal 'another_model', widget_2.model + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/active_record_test_case.rb b/vendor/gems/mocha-0.5.6/test/active_record_test_case.rb new file mode 100644 index 0000000000..ae65073800 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/active_record_test_case.rb @@ -0,0 +1,36 @@ +module ActiveRecordTestCase + + def setup_with_fixtures + methods_called << :setup_with_fixtures + end + + alias_method :setup, :setup_with_fixtures + + def teardown_with_fixtures + methods_called << :teardown_with_fixtures + end + + alias_method :teardown, :teardown_with_fixtures + + def self.method_added(method) + case method.to_s + when 'setup' + unless method_defined?(:setup_without_fixtures) + alias_method :setup_without_fixtures, :setup + define_method(:setup) do + setup_with_fixtures + setup_without_fixtures + end + end + when 'teardown' + unless method_defined?(:teardown_without_fixtures) + alias_method :teardown_without_fixtures, :teardown + define_method(:teardown) do + teardown_without_fixtures + teardown_with_fixtures + end + end + end + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/deprecation_disabler.rb b/vendor/gems/mocha-0.5.6/test/deprecation_disabler.rb new file mode 100644 index 0000000000..c57fb3c9ac --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/deprecation_disabler.rb @@ -0,0 +1,15 @@ +require 'mocha/deprecation' + +module DeprecationDisabler + + def disable_deprecations + original_mode = Mocha::Deprecation.mode + Mocha::Deprecation.mode = :disabled + begin + yield + ensure + Mocha::Deprecation.mode = original_mode + end + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/execution_point.rb b/vendor/gems/mocha-0.5.6/test/execution_point.rb new file mode 100644 index 0000000000..33c85699ee --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/execution_point.rb @@ -0,0 +1,34 @@ +class ExecutionPoint + + attr_reader :backtrace + + def self.current + new(caller) + end + + def initialize(backtrace) + @backtrace = backtrace + end + + def file_name + /\A(.*?):\d+/.match(@backtrace.first)[1] + end + + def line_number + Integer(/\A.*?:(\d+)/.match(@backtrace.first)[1]) + end + + def ==(other) + return false unless other.is_a?(ExecutionPoint) + (file_name == other.file_name) and (line_number == other.line_number) + end + + def to_s + "file: #{file_name} line: #{line_number}" + end + + def inspect + to_s + end + +end diff --git a/vendor/gems/mocha-0.5.6/test/integration/._mocha_test_result_integration_test.rb b/vendor/gems/mocha-0.5.6/test/integration/._mocha_test_result_integration_test.rb new file mode 100644 index 0000000000000000000000000000000000000000..78cbfbe92cf4bdfdcd96a35906d36e3394626341 GIT binary patch literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_I`4FfSq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV#ASS#dY=B0v|2G$C- E0QjC5$N&HU literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/test/integration/._stubba_integration_test.rb b/vendor/gems/mocha-0.5.6/test/integration/._stubba_integration_test.rb new file mode 100644 index 0000000000000000000000000000000000000000..c88497c4567d6fced4c7e7234045adf527a38b3c GIT binary patch literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_Ic^{}0q+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eaNsSS#dY=A{Ce=0IjG E0QgB5(f|Me literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/test/integration/._stubba_test_result_integration_test.rb b/vendor/gems/mocha-0.5.6/test/integration/._stubba_test_result_integration_test.rb new file mode 100644 index 0000000000000000000000000000000000000000..931f6315d8528c050cf7d6cd66f098cb254635f5 GIT binary patch literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_Ic^9Y@q+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eVzsTPx&b=A{CeW|r0p FwE*{f7|{R# literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/test/integration/mocha_test_result_integration_test.rb b/vendor/gems/mocha-0.5.6/test/integration/mocha_test_result_integration_test.rb new file mode 100644 index 0000000000..d5f29e8451 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/integration/mocha_test_result_integration_test.rb @@ -0,0 +1,105 @@ +require File.join(File.dirname(__FILE__), "..", "test_helper") +require 'mocha/standalone' +require 'mocha/test_case_adapter' +require 'execution_point' + +class MochaTestResultIntegrationTest < Test::Unit::TestCase + + def test_should_include_expectation_verification_in_assertion_count + test_result = run_test do + object = mock() + object.expects(:message) + object.message + end + assert_equal 1, test_result.assertion_count + end + + def test_should_include_assertions_in_assertion_count + test_result = run_test do + assert true + end + assert_equal 1, test_result.assertion_count + end + + def test_should_not_include_stubbing_expectation_verification_in_assertion_count + test_result = run_test do + object = mock() + object.stubs(:message) + object.message + end + assert_equal 0, test_result.assertion_count + end + + def test_should_include_expectation_verification_failure_in_failure_count + test_result = run_test do + object = mock() + object.expects(:message) + end + assert_equal 1, test_result.failure_count + end + + def test_should_include_unexpected_verification_failure_in_failure_count + test_result = run_test do + object = mock() + object.message + end + assert_equal 1, test_result.failure_count + end + + def test_should_include_assertion_failure_in_failure_count + test_result = run_test do + flunk + end + assert_equal 1, test_result.failure_count + end + + def test_should_display_backtrace_indicating_line_number_where_expects_was_called + test_result = Test::Unit::TestResult.new + faults = [] + test_result.add_listener(Test::Unit::TestResult::FAULT, &lambda { |fault| faults << fault }) + execution_point = nil + run_test(test_result) do + object = mock() + execution_point = ExecutionPoint.current; object.expects(:message) + end + assert_equal 1, faults.length + assert_equal execution_point, ExecutionPoint.new(faults.first.location) + end + + def test_should_display_backtrace_indicating_line_number_where_unexpected_method_was_called + test_result = Test::Unit::TestResult.new + faults = [] + test_result.add_listener(Test::Unit::TestResult::FAULT, &lambda { |fault| faults << fault }) + execution_point = nil + run_test(test_result) do + object = mock() + execution_point = ExecutionPoint.current; object.message + end + assert_equal 1, faults.length + assert_equal execution_point, ExecutionPoint.new(faults.first.location) + end + + def test_should_display_backtrace_indicating_line_number_where_failing_assertion_was_called + test_result = Test::Unit::TestResult.new + faults = [] + test_result.add_listener(Test::Unit::TestResult::FAULT, &lambda { |fault| faults << fault }) + execution_point = nil + run_test(test_result) do + execution_point = ExecutionPoint.current; flunk + end + assert_equal 1, faults.length + assert_equal execution_point, ExecutionPoint.new(faults.first.location) + end + + def run_test(test_result = Test::Unit::TestResult.new, &block) + test_class = Class.new(Test::Unit::TestCase) do + include Mocha::Standalone + include Mocha::TestCaseAdapter + define_method(:test_me, &block) + end + test = test_class.new(:test_me) + test.run(test_result) {} + test_result + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/integration/stubba_integration_test.rb b/vendor/gems/mocha-0.5.6/test/integration/stubba_integration_test.rb new file mode 100644 index 0000000000..4285c179ad --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/integration/stubba_integration_test.rb @@ -0,0 +1,89 @@ +require File.join(File.dirname(__FILE__), "..", "test_helper") + +require 'mocha/object' +require 'mocha/test_case_adapter' +require 'mocha/standalone' + +class StubbaIntegrationTest < Test::Unit::TestCase + + class DontMessWithMe + def self.my_class_method + :original_return_value + end + def my_instance_method + :original_return_value + end + end + + def test_should_stub_class_method_within_test + test = build_test do + DontMessWithMe.expects(:my_class_method).returns(:new_return_value) + assert_equal :new_return_value, DontMessWithMe.my_class_method + end + + test_result = Test::Unit::TestResult.new + test.run(test_result) {} + assert test_result.passed? + end + + def test_should_leave_stubbed_class_method_unchanged_after_test + test = build_test do + DontMessWithMe.expects(:my_class_method).returns(:new_return_value) + end + + test.run(Test::Unit::TestResult.new) {} + assert_equal :original_return_value, DontMessWithMe.my_class_method + end + + def test_should_reset_class_expectations_after_test + test = build_test do + DontMessWithMe.expects(:my_class_method) + end + + test.run(Test::Unit::TestResult.new) {} + assert_equal 0, DontMessWithMe.mocha.expectations.length + end + + def test_should_stub_instance_method_within_test + instance = DontMessWithMe.new + test = build_test do + instance.expects(:my_instance_method).returns(:new_return_value) + assert_equal :new_return_value, instance.my_instance_method + end + test_result = Test::Unit::TestResult.new + test.run(test_result) {} + assert test_result.passed? + end + + def test_should_leave_stubbed_instance_method_unchanged_after_test + instance = DontMessWithMe.new + test = build_test do + instance.expects(:my_instance_method).returns(:new_return_value) + end + + test.run(Test::Unit::TestResult.new) {} + assert_equal :original_return_value, instance.my_instance_method + end + + def test_should_reset_instance_expectations_after_test + instance = DontMessWithMe.new + test = build_test do + instance.expects(:my_instance_method).returns(:new_return_value) + end + + test.run(Test::Unit::TestResult.new) {} + assert_equal 0, instance.mocha.expectations.length + end + + private + + def build_test(&block) + test_class = Class.new(Test::Unit::TestCase) do + include Mocha::Standalone + include Mocha::TestCaseAdapter + define_method(:test_me, &block) + end + test_class.new(:test_me) + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/integration/stubba_test_result_integration_test.rb b/vendor/gems/mocha-0.5.6/test/integration/stubba_test_result_integration_test.rb new file mode 100644 index 0000000000..34264e7c6f --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/integration/stubba_test_result_integration_test.rb @@ -0,0 +1,85 @@ +require File.join(File.dirname(__FILE__), "..", "test_helper") +require 'mocha/object' +require 'mocha/standalone' +require 'mocha/test_case_adapter' +require 'execution_point' + +class StubbaTestResultIntegrationTest < Test::Unit::TestCase + + def test_should_include_expectation_verification_in_assertion_count + test_result = run_test do + object = Class.new { def message; end }.new + object.expects(:message) + object.message + end + assert_equal 1, test_result.assertion_count + end + + def test_should_include_assertions_in_assertion_count + test_result = run_test do + assert true + end + assert_equal 1, test_result.assertion_count + end + + def test_should_not_include_stubbing_expectation_verification_in_assertion_count + test_result = run_test do + object = Class.new { def message; end }.new + object.stubs(:message) + object.message + end + assert_equal 0, test_result.assertion_count + end + + def test_should_include_expectation_verification_failure_in_failure_count + test_result = run_test do + object = Class.new { def message; end }.new + object.expects(:message) + end + assert_equal 1, test_result.failure_count + end + + def test_should_include_assertion_failure_in_failure_count + test_result = run_test do + flunk + end + assert_equal 1, test_result.failure_count + end + + def test_should_display_backtrace_indicating_line_number_where_expects_was_called + test_result = Test::Unit::TestResult.new + faults = [] + test_result.add_listener(Test::Unit::TestResult::FAULT, &lambda { |fault| faults << fault }) + execution_point = nil + run_test(test_result) do + object = Class.new { def message; end }.new + execution_point = ExecutionPoint.current; object.expects(:message) + end + assert_equal 1, faults.length + assert_equal execution_point, ExecutionPoint.new(faults.first.location) + end + + def test_should_display_backtrace_indicating_line_number_where_failing_assertion_was_called + test_result = Test::Unit::TestResult.new + faults = [] + test_result.add_listener(Test::Unit::TestResult::FAULT, &lambda { |fault| faults << fault }) + execution_point = nil + run_test(test_result) do + execution_point = ExecutionPoint.current; flunk + end + assert_equal 1, faults.length + assert_equal execution_point, ExecutionPoint.new(faults.first.location) + end + + def run_test(test_result = Test::Unit::TestResult.new, &block) + test_class = Class.new(Test::Unit::TestCase) do + include Mocha::Standalone + include Mocha::TestCaseAdapter + define_method(:test_me, &block) + end + test = test_class.new(:test_me) + test.run(test_result) {} + test_result + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/method_definer.rb b/vendor/gems/mocha-0.5.6/test/method_definer.rb new file mode 100644 index 0000000000..1aef8868b2 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/method_definer.rb @@ -0,0 +1,18 @@ +require 'mocha/metaclass' + +class Object + + def define_instance_method(method_symbol, &block) + __metaclass__.send(:define_method, method_symbol, block) + end + + def replace_instance_method(method_symbol, &block) + raise "Cannot replace #{method_symbol} as #{self} does not respond to it." unless self.respond_to?(method_symbol) + define_instance_method(method_symbol, &block) + end + + def define_instance_accessor(*symbols) + symbols.each { |symbol| __metaclass__.send(:attr_accessor, symbol) } + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/test_helper.rb b/vendor/gems/mocha-0.5.6/test/test_helper.rb new file mode 100644 index 0000000000..dc04942739 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/test_helper.rb @@ -0,0 +1,12 @@ +unless defined?(STANDARD_OBJECT_PUBLIC_INSTANCE_METHODS) + STANDARD_OBJECT_PUBLIC_INSTANCE_METHODS = Object.public_instance_methods +end + +$:.unshift File.expand_path(File.join(File.dirname(__FILE__), "..", "lib")) +$:.unshift File.expand_path(File.join(File.dirname(__FILE__))) +$:.unshift File.expand_path(File.join(File.dirname(__FILE__), 'unit')) +$:.unshift File.expand_path(File.join(File.dirname(__FILE__), 'unit', 'parameter_matchers')) +$:.unshift File.expand_path(File.join(File.dirname(__FILE__), 'integration')) +$:.unshift File.expand_path(File.join(File.dirname(__FILE__), 'acceptance')) + +require 'test/unit' \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/test_runner.rb b/vendor/gems/mocha-0.5.6/test/test_runner.rb new file mode 100644 index 0000000000..fbadd92977 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/test_runner.rb @@ -0,0 +1,31 @@ +require 'test/unit/testresult' +require 'test/unit/testcase' +require 'mocha/standalone' +require 'mocha/test_case_adapter' + +module TestRunner + + def run_test(test_result = Test::Unit::TestResult.new, &block) + test_class = Class.new(Test::Unit::TestCase) do + include Mocha::Standalone + include Mocha::TestCaseAdapter + define_method(:test_me, &block) + end + test = test_class.new(:test_me) + test.run(test_result) {} + class << test_result + attr_reader :failures, :errors + end + test_result + end + + def assert_passed(test_result) + flunk "Test failed unexpectedly with message: #{test_result.failures}" if test_result.failure_count > 0 + flunk "Test failed unexpectedly with message: #{test_result.errors}" if test_result.error_count > 0 + end + + def assert_failed(test_result) + flunk "Test passed unexpectedly" if test_result.passed? + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/._any_instance_method_test.rb b/vendor/gems/mocha-0.5.6/test/unit/._any_instance_method_test.rb new file mode 100644 index 0000000000000000000000000000000000000000..83d91fdf20c1f3ca8ee5a2756b411005afb4d683 GIT binary patch literal 176 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aUBqY_#1$j2;dkJ62u_IxeKTiq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r2<(N)(W)% Dx5F4W literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/test/unit/._auto_verify_test.rb b/vendor/gems/mocha-0.5.6/test/unit/._auto_verify_test.rb new file mode 100644 index 0000000000000000000000000000000000000000..9052afc9513144878946cc2cce4f63278c5a9977 GIT binary patch literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_IxeTZjq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r2<)o7S;;2 E0Luj!SpWb4 literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/test/unit/._central_test.rb b/vendor/gems/mocha-0.5.6/test/unit/._central_test.rb new file mode 100644 index 0000000000000000000000000000000000000000..a8cc77d1a779c8767877d83d8572910aff9c548f GIT binary patch literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_IIR~f|q+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!VE97M6r2<)I#?}h8 E0LRQ2Q2+n{ literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/test/unit/._class_method_test.rb b/vendor/gems/mocha-0.5.6/test/unit/._class_method_test.rb new file mode 100644 index 0000000000000000000000000000000000000000..30c6ce8de5a1b5666c287b3c42eb66333d511455 GIT binary patch literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_IxdEsYq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r2<)omgd$9 FwE*a_7@z$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_$q!OqUq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV#CS}Wva=A{Ceh8ETe FwE!WB8AbpA literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/test/unit/._expectation_list_test.rb b/vendor/gems/mocha-0.5.6/test/unit/._expectation_list_test.rb new file mode 100644 index 0000000000000000000000000000000000000000..753636a0f13c64752be4f0477055d2a8af15227a GIT binary patch literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_IIRU5?q+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r2<)|=GF?e E0K>5uMgRZ+ literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/test/unit/._expectation_test.rb b/vendor/gems/mocha-0.5.6/test/unit/._expectation_test.rb new file mode 100644 index 0000000000000000000000000000000000000000..b92a766c09bb840c61f314fa4d039f13e5ee3037 GIT binary patch literal 179 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aU&3e_?v;42;dkJ62u_IISr^3q+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eVzsSS#dY=A{CeCg$eW G3bg?FiWt8D literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/test/unit/._hash_inspect_test.rb b/vendor/gems/mocha-0.5.6/test/unit/._hash_inspect_test.rb new file mode 100644 index 0000000000000000000000000000000000000000..2d092d906064c29ed787810ce053be518e1fdc37 GIT binary patch literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_Ixecfkq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r2<)oKxQof D&_x(w literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/test/unit/._method_matcher_test.rb b/vendor/gems/mocha-0.5.6/test/unit/._method_matcher_test.rb new file mode 100644 index 0000000000000000000000000000000000000000..5de8376739280cf862658d7c4641f03c6167e48b GIT binary patch literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62zc+eJfBYNXHBy zmPFIWh!7V|&d=4$O-wGz&rK{zE!Im;EJ`h5s7}t$Db3AOuvIXyR>;ZBO9iqFEv*%5 E0UJyiMgRZ+ literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/test/unit/._missing_expectation_test.rb b/vendor/gems/mocha-0.5.6/test/unit/._missing_expectation_test.rb new file mode 100644 index 0000000000000000000000000000000000000000..166d7827f65eb49ad994a01d9842aff883c628c2 GIT binary patch literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_qrUz6C(lG;w zrO>o7BE&_L^KpF literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/test/unit/._mock_test.rb b/vendor/gems/mocha-0.5.6/test/unit/._mock_test.rb new file mode 100644 index 0000000000000000000000000000000000000000..e9005d16736b8b0e2fd115458e71029d6cde499a GIT binary patch literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62zc=HwdT{q+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r2<)I=GF?e E0Q|-n?f?J) literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/test/unit/._object_inspect_test.rb b/vendor/gems/mocha-0.5.6/test/unit/._object_inspect_test.rb new file mode 100644 index 0000000000000000000000000000000000000000..e39ba31349470d112d3c6e04e8167d5dd51c00c2 GIT binary patch literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62zc=Hw35@q+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eVzsTPx&b=A{CeMi$lz FwE!2a87Ke% literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/test/unit/._parameters_matcher_test.rb b/vendor/gems/mocha-0.5.6/test/unit/._parameters_matcher_test.rb new file mode 100644 index 0000000000000000000000000000000000000000..ca04d98e7b6eb9bcf28b7cef6b19e82818dff079 GIT binary patch literal 176 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aUBqY_#1$j2;dkJ62zc+eLGMoNXHBy zmO#_Sh!7V|&d=4$O-wGz&rK{zE!Im;EJ`h5s7}t$Db3AOuvIXyR>;ZBO9irwtQBej D0SFlj literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/test/unit/._sequence_test.rb b/vendor/gems/mocha-0.5.6/test/unit/._sequence_test.rb new file mode 100644 index 0000000000000000000000000000000000000000..ec9e9d76e6646e1860b40235cab64546a4e137c3 GIT binary patch literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62zdzDg#ss(lG;w zrO>o7BE&_L^K>83Qh_W(10cH= E0J=*U2mk;8 literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/test/unit/any_instance_method_test.rb b/vendor/gems/mocha-0.5.6/test/unit/any_instance_method_test.rb new file mode 100644 index 0000000000..804fcde2bd --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/unit/any_instance_method_test.rb @@ -0,0 +1,126 @@ +require File.join(File.dirname(__FILE__), "..", "test_helper") +require 'method_definer' +require 'mocha/mock' +require 'mocha/any_instance_method' + +class AnyInstanceMethodTest < Test::Unit::TestCase + + include Mocha + + def test_should_hide_original_method + klass = Class.new { def method_x; end } + method = AnyInstanceMethod.new(klass, :method_x) + hidden_method_x = method.hidden_method.to_sym + + method.hide_original_method + + assert klass.method_defined?(hidden_method_x) + end + + def test_should_not_hide_original_method_if_it_is_not_defined + klass = Class.new + method = AnyInstanceMethod.new(klass, :method_x) + hidden_method_x = method.hidden_method.to_sym + + method.hide_original_method + + assert_equal false, klass.method_defined?(hidden_method_x) + end + + def test_should_define_a_new_method + klass = Class.new { def method_x; end } + method = AnyInstanceMethod.new(klass, :method_x) + mocha = Mock.new + mocha.expects(:method_x).with(:param1, :param2).returns(:result) + any_instance = Object.new + any_instance.define_instance_method(:mocha) { mocha } + klass.define_instance_method(:any_instance) { any_instance } + + method.hide_original_method + method.define_new_method + + instance = klass.new + result = instance.method_x(:param1, :param2) + + assert_equal :result, result + mocha.verify + end + + def test_should_restore_original_method + klass = Class.new { def method_x; end } + method = AnyInstanceMethod.new(klass, :method_x) + hidden_method_x = method.hidden_method.to_sym + klass.send(:define_method, hidden_method_x, Proc.new { :original_result }) + + method.remove_new_method + method.restore_original_method + + instance = klass.new + assert_equal :original_result, instance.method_x + assert !klass.method_defined?(hidden_method_x) + end + + def test_should_not_restore_original_method_if_hidden_method_not_defined + klass = Class.new { def method_x; :new_result; end } + method = AnyInstanceMethod.new(klass, :method_x) + + method.restore_original_method + + instance = klass.new + assert_equal :new_result, instance.method_x + end + + def test_should_call_remove_new_method + klass = Class.new { def method_x; end } + any_instance = Mock.new + any_instance.stubs(:reset_mocha) + klass.define_instance_method(:any_instance) { any_instance } + method = AnyInstanceMethod.new(klass, :method_x) + method.replace_instance_method(:restore_original_method) { } + method.define_instance_accessor(:remove_called) + method.replace_instance_method(:remove_new_method) { self.remove_called = true } + + method.unstub + + assert method.remove_called + end + + def test_should_call_restore_original_method + klass = Class.new { def method_x; end } + any_instance = Mock.new + any_instance.stubs(:reset_mocha) + klass.define_instance_method(:any_instance) { any_instance } + method = AnyInstanceMethod.new(klass, :method_x) + method.replace_instance_method(:remove_new_method) { } + method.define_instance_accessor(:restore_called) + method.replace_instance_method(:restore_original_method) { self.restore_called = true } + + method.unstub + + assert method.restore_called + end + + def test_should_call_reset_mocha + klass = Class.new { def method_x; end } + any_instance = Class.new { attr_accessor :mocha_was_reset; def reset_mocha; self.mocha_was_reset = true; end }.new + klass.define_instance_method(:any_instance) { any_instance } + method = AnyInstanceMethod.new(klass, :method_x) + method.replace_instance_method(:remove_new_method) { } + method.replace_instance_method(:restore_original_method) { } + + method.unstub + + assert any_instance.mocha_was_reset + end + + def test_should_return_any_instance_mocha_for_stubbee + mocha = Object.new + any_instance = Object.new + any_instance.define_instance_method(:mocha) { mocha } + stubbee = Class.new + stubbee.define_instance_method(:any_instance) { any_instance } + method = AnyInstanceMethod.new(stubbee, :method_name) + assert_equal stubbee.any_instance.mocha, method.mock + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/array_inspect_test.rb b/vendor/gems/mocha-0.5.6/test/unit/array_inspect_test.rb new file mode 100644 index 0000000000..9cc06a4564 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/unit/array_inspect_test.rb @@ -0,0 +1,16 @@ +require File.join(File.dirname(__FILE__), "..", "test_helper") +require 'mocha/inspect' + +class ArrayInstanceTest < Test::Unit::TestCase + + def test_should_use_inspect + array = [1, 2] + assert_equal array.inspect, array.mocha_inspect + end + + def test_should_use_mocha_inspect_on_each_item + array = [1, 2, "chris"] + assert_equal "[1, 2, 'chris']", array.mocha_inspect + end + +end diff --git a/vendor/gems/mocha-0.5.6/test/unit/auto_verify_test.rb b/vendor/gems/mocha-0.5.6/test/unit/auto_verify_test.rb new file mode 100644 index 0000000000..10a6124576 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/unit/auto_verify_test.rb @@ -0,0 +1,129 @@ +require File.join(File.dirname(__FILE__), "..", "test_helper") +require 'mocha/auto_verify' +require 'method_definer' + +class AutoVerifyTest < Test::Unit::TestCase + + attr_reader :test_case + + def setup + @test_case = Object.new + class << test_case + include Mocha::AutoVerify + end + end + + def test_should_build_mock + mock = test_case.mock + assert mock.is_a?(Mocha::Mock) + end + + def test_should_add_expectations_to_mock + mock = test_case.mock(:method_1 => 'result_1', :method_2 => 'result_2') + assert_equal 'result_1', mock.method_1 + assert_equal 'result_2', mock.method_2 + end + + def test_should_build_stub + stub = test_case.stub + assert stub.is_a?(Mocha::Mock) + end + + def test_should_add_expectation_to_stub + stub = test_case.stub(:method_1 => 'result_1', :method_2 => 'result_2') + assert_equal 'result_1', stub.method_1 + assert_equal 'result_2', stub.method_2 + end + + def test_should_build_stub_that_stubs_all_methods + stub = test_case.stub_everything + assert stub.everything_stubbed + end + + def test_should_add_expectations_to_stub_that_stubs_all_methods + stub = test_case.stub_everything(:method_1 => 'result_1', :method_2 => 'result_2') + assert_equal 'result_1', stub.method_1 + assert_equal 'result_2', stub.method_2 + end + + def test_should_always_new_mock + assert_not_equal test_case.mock, test_case.mock + end + + def test_should_always_build_new_stub + assert_not_equal test_case.stub, test_case.stub + end + + def test_should_always_build_new_stub_that_stubs_all_methods + assert_not_equal test_case.stub, test_case.stub + end + + def test_should_store_each_new_mock + expected = Array.new(3) { test_case.mock } + assert_equal expected, test_case.mocks + end + + def test_should_store_each_new_stub + expected = Array.new(3) { test_case.stub } + assert_equal expected, test_case.mocks + end + + def test_should_store_each_new_stub_that_stubs_all_methods + expected = Array.new(3) { test_case.stub_everything } + assert_equal expected, test_case.mocks + end + + def test_should_verify_each_mock + mocks = Array.new(3) do + mock = Object.new + mock.define_instance_accessor(:verify_called) + class << mock + def verify(&block) + self.verify_called = true + end + end + mock + end + test_case.replace_instance_method(:mocks) { mocks } + test_case.verify_mocks + assert mocks.all? { |mock| mock.verify_called } + end + + def test_should_yield_to_block_for_each_assertion + mock_class = Class.new do + def verify(&block); yield; end + end + mock = mock_class.new + test_case.replace_instance_method(:mocks) { [mock] } + yielded = false + test_case.verify_mocks { yielded = true } + assert yielded + end + + def test_should_reset_mocks_on_teardown + mock = Class.new { define_method(:verify) {} }.new + test_case.mocks << mock + test_case.teardown_mocks + assert test_case.mocks.empty? + end + + def test_should_create_named_mock + mock = test_case.mock('named_mock') + assert_equal '#', mock.mocha_inspect + end + + def test_should_create_named_stub + stub = test_case.stub('named_stub') + assert_equal '#', stub.mocha_inspect + end + + def test_should_create_named_stub_that_stubs_all_methods + stub = test_case.stub_everything('named_stub') + assert_equal '#', stub.mocha_inspect + end + + def test_should_build_sequence + assert_not_nil test_case.sequence('name') + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/central_test.rb b/vendor/gems/mocha-0.5.6/test/unit/central_test.rb new file mode 100644 index 0000000000..2cc8345915 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/unit/central_test.rb @@ -0,0 +1,124 @@ +require File.join(File.dirname(__FILE__), "..", "test_helper") + +require 'mocha/central' +require 'mocha/mock' +require 'method_definer' + +class CentralTest < Test::Unit::TestCase + + include Mocha + + def test_should_start_with_empty_stubba_methods + stubba = Central.new + + assert_equal [], stubba.stubba_methods + end + + def test_should_stub_method_if_not_already_stubbed + method = Mock.new + method.expects(:stub) + stubba = Central.new + + stubba.stub(method) + + method.verify + end + + def test_should_not_stub_method_if_already_stubbed + method = Mock.new + method.expects(:stub).times(0) + stubba = Central.new + stubba_methods = Mock.new + stubba_methods.stubs(:include?).with(method).returns(true) + stubba.stubba_methods = stubba_methods + + stubba.stub(method) + + method.verify + end + + def test_should_record_method + method = Mock.new + method.expects(:stub) + stubba = Central.new + + stubba.stub(method) + + assert_equal [method], stubba.stubba_methods + end + + def test_should_unstub_all_methods + stubba = Central.new + method_1 = Mock.new + method_1.expects(:unstub) + method_2 = Mock.new + method_2.expects(:unstub) + stubba.stubba_methods = [method_1, method_2] + + stubba.unstub_all + + assert_equal [], stubba.stubba_methods + method_1.verify + method_2.verify + end + + def test_should_collect_mocks_from_all_methods + method_1 = Mock.new + method_1.stubs(:mock).returns(:mock_1) + + method_2 = Mock.new + method_2.stubs(:mock).returns(:mock_2) + + stubba = Central.new + stubba.stubba_methods = [method_1, method_2] + + assert_equal 2, stubba.unique_mocks.length + assert stubba.unique_mocks.include?(:mock_1) + assert stubba.unique_mocks.include?(:mock_2) + end + + def test_should_return_unique_mochas + method_1 = Mock.new + method_1.stubs(:mock).returns(:mock_1) + + method_2 = Mock.new + method_2.stubs(:mock).returns(:mock_1) + + stubba = Central.new + stubba.stubba_methods = [method_1, method_2] + + assert_equal [:mock_1], stubba.unique_mocks + end + + def test_should_call_verify_on_all_unique_mocks + mock_class = Class.new do + attr_accessor :verify_called + def verify + self.verify_called = true + end + end + mocks = [mock_class.new, mock_class.new] + stubba = Central.new + stubba.replace_instance_method(:unique_mocks) { mocks } + + stubba.verify_all + + assert mocks.all? { |mock| mock.verify_called } + end + + def test_should_call_verify_on_all_unique_mochas + mock_class = Class.new do + def verify(&block) + yield if block_given? + end + end + stubba = Central.new + stubba.replace_instance_method(:unique_mocks) { [mock_class.new] } + yielded = false + + stubba.verify_all { yielded = true } + + assert yielded + end + +end diff --git a/vendor/gems/mocha-0.5.6/test/unit/class_method_test.rb b/vendor/gems/mocha-0.5.6/test/unit/class_method_test.rb new file mode 100644 index 0000000000..95d0599085 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/unit/class_method_test.rb @@ -0,0 +1,200 @@ +require File.join(File.dirname(__FILE__), "..", "test_helper") +require 'method_definer' +require 'mocha/mock' + +require 'mocha/class_method' + +class ClassMethodTest < Test::Unit::TestCase + + include Mocha + + def test_should_provide_hidden_version_of_method_name_starting_with_prefix + method = ClassMethod.new(nil, :original_method_name) + assert_match(/^__stubba__/, method.hidden_method) + end + + def test_should_provide_hidden_version_of_method_name_ending_with_suffix + method = ClassMethod.new(nil, :original_method_name) + assert_match(/__stubba__$/, method.hidden_method) + end + + def test_should_provide_hidden_version_of_method_name_including_original_method_name + method = ClassMethod.new(nil, :original_method_name) + assert_match(/original_method_name/, method.hidden_method) + end + + def test_should_provide_hidden_version_of_method_name_substituting_question_mark + method = ClassMethod.new(nil, :question_mark?) + assert_no_match(/\?/, method.hidden_method) + assert_match(/question_mark_substituted_character_63/, method.hidden_method) + end + + def test_should_provide_hidden_version_of_method_name_substituting_exclamation_mark + method = ClassMethod.new(nil, :exclamation_mark!) + assert_no_match(/!/, method.hidden_method) + assert_match(/exclamation_mark_substituted_character_33/, method.hidden_method) + end + + def test_should_provide_hidden_version_of_method_name_substituting_equals_sign + method = ClassMethod.new(nil, :equals_sign=) + assert_no_match(/\=/, method.hidden_method) + assert_match(/equals_sign_substituted_character_61/, method.hidden_method) + end + + def test_should_provide_hidden_version_of_method_name_substituting_brackets + method = ClassMethod.new(nil, :[]) + assert_no_match(/\[\]/, method.hidden_method) + assert_match(/substituted_character_91__substituted_character_93/, method.hidden_method) + end + + def test_should_provide_hidden_version_of_method_name_substituting_plus_sign + method = ClassMethod.new(nil, :+) + assert_no_match(/\+/, method.hidden_method) + assert_match(/substituted_character_43/, method.hidden_method) + end + + def test_should_hide_original_method + klass = Class.new { def self.method_x; end } + method = ClassMethod.new(klass, :method_x) + hidden_method_x = method.hidden_method + + method.hide_original_method + + assert klass.respond_to?(hidden_method_x) + end + + def test_should_respond_to_original_method_name_after_original_method_has_been_hidden + klass = Class.new { def self.original_method_name; end } + method = ClassMethod.new(klass, :original_method_name) + hidden_method_x = method.hidden_method + + method.hide_original_method + + assert klass.respond_to?(:original_method_name) + end + + def test_should_not_hide_original_method_if_method_not_defined + klass = Class.new + method = ClassMethod.new(klass, :method_x) + hidden_method_x = method.hidden_method + + method.hide_original_method + + assert_equal false, klass.respond_to?(hidden_method_x) + end + + def test_should_define_a_new_method_which_should_call_mocha_method_missing + klass = Class.new { def self.method_x; end } + mocha = Mocha::Mock.new + klass.define_instance_method(:mocha) { mocha } + mocha.expects(:method_x).with(:param1, :param2).returns(:result) + method = ClassMethod.new(klass, :method_x) + + method.hide_original_method + method.define_new_method + result = klass.method_x(:param1, :param2) + + assert_equal :result, result + mocha.verify + end + + def test_should_remove_new_method + klass = Class.new { def self.method_x; end } + method = ClassMethod.new(klass, :method_x) + + method.remove_new_method + + assert_equal false, klass.respond_to?(:method_x) + end + + def test_should_restore_original_method + klass = Class.new { def self.method_x; end } + method = ClassMethod.new(klass, :method_x) + hidden_method_x = method.hidden_method.to_sym + klass.define_instance_method(hidden_method_x) { :original_result } + + method.remove_new_method + method.restore_original_method + + assert_equal :original_result, klass.method_x + assert_equal false, klass.respond_to?(hidden_method_x) + end + + def test_should_not_restore_original_method_if_hidden_method_is_not_defined + klass = Class.new { def self.method_x; :new_result; end } + method = ClassMethod.new(klass, :method_x) + + method.restore_original_method + + assert_equal :new_result, klass.method_x + end + + def test_should_call_hide_original_method + klass = Class.new { def self.method_x; end } + method = ClassMethod.new(klass, :method_x) + method.hide_original_method + method.define_instance_accessor(:hide_called) + method.replace_instance_method(:hide_original_method) { self.hide_called = true } + + method.stub + + assert method.hide_called + end + + def test_should_call_define_new_method + klass = Class.new { def self.method_x; end } + method = ClassMethod.new(klass, :method_x) + method.define_instance_accessor(:define_called) + method.replace_instance_method(:define_new_method) { self.define_called = true } + + method.stub + + assert method.define_called + end + + def test_should_call_remove_new_method + klass = Class.new { def self.method_x; end } + klass.define_instance_method(:reset_mocha) { } + method = ClassMethod.new(klass, :method_x) + method.define_instance_accessor(:remove_called) + method.replace_instance_method(:remove_new_method) { self.remove_called = true } + + method.unstub + + assert method.remove_called + end + + def test_should_call_restore_original_method + klass = Class.new { def self.method_x; end } + klass.define_instance_method(:reset_mocha) { } + method = ClassMethod.new(klass, :method_x) + method.define_instance_accessor(:restore_called) + method.replace_instance_method(:restore_original_method) { self.restore_called = true } + + method.unstub + + assert method.restore_called + end + + def test_should_call_reset_mocha + klass = Class.new { def self.method_x; end } + klass.define_instance_accessor(:reset_called) + klass.define_instance_method(:reset_mocha) { self.reset_called = true } + method = ClassMethod.new(klass, :method_x) + method.replace_instance_method(:restore_original_method) { } + + method.unstub + + assert klass.reset_called + end + + def test_should_return_mock_for_stubbee + mocha = Object.new + stubbee = Object.new + stubbee.define_instance_accessor(:mocha) { mocha } + stubbee.mocha = nil + method = ClassMethod.new(stubbee, :method_name) + assert_equal stubbee.mocha, method.mock + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/date_time_inspect_test.rb b/vendor/gems/mocha-0.5.6/test/unit/date_time_inspect_test.rb new file mode 100644 index 0000000000..8a9b2ee029 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/unit/date_time_inspect_test.rb @@ -0,0 +1,21 @@ +require File.join(File.dirname(__FILE__), "..", "test_helper") +require 'mocha/inspect' + +class TimeDateInspectTest < Test::Unit::TestCase + + def test_should_use_include_date_in_seconds + time = Time.now + assert_equal "#{time.inspect} (#{time.to_f} secs)", time.mocha_inspect + end + + def test_should_use_to_s_for_date + date = Date.new(2006, 1, 1) + assert_equal date.to_s, date.mocha_inspect + end + + def test_should_use_to_s_for_datetime + datetime = DateTime.new(2006, 1, 1) + assert_equal datetime.to_s, datetime.mocha_inspect + end + +end diff --git a/vendor/gems/mocha-0.5.6/test/unit/expectation_error_test.rb b/vendor/gems/mocha-0.5.6/test/unit/expectation_error_test.rb new file mode 100644 index 0000000000..6206acf649 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/unit/expectation_error_test.rb @@ -0,0 +1,24 @@ +require File.join(File.dirname(__FILE__), "..", "test_helper") +require 'mocha/expectation_error' + +class ExpectationErrorTest < Test::Unit::TestCase + + include Mocha + + def test_should_exclude_mocha_locations_from_backtrace + mocha_lib = "/username/workspace/mocha_wibble/lib/" + backtrace = [ mocha_lib + 'exclude/me/1', mocha_lib + 'exclude/me/2', '/keep/me', mocha_lib + 'exclude/me/3'] + expectation_error = ExpectationError.new(nil, backtrace, mocha_lib) + assert_equal ['/keep/me'], expectation_error.backtrace + end + + def test_should_determine_path_for_mocha_lib_directory + assert_match Regexp.new("/lib/$"), ExpectationError::LIB_DIRECTORY + end + + def test_should_set_error_message + expectation_error = ExpectationError.new('message') + assert_equal 'message', expectation_error.message + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/expectation_list_test.rb b/vendor/gems/mocha-0.5.6/test/unit/expectation_list_test.rb new file mode 100644 index 0000000000..59dd410a1f --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/unit/expectation_list_test.rb @@ -0,0 +1,75 @@ +require File.join(File.dirname(__FILE__), "..", "test_helper") +require 'mocha/expectation_list' +require 'mocha/expectation' +require 'set' +require 'method_definer' + +class ExpectationListTest < Test::Unit::TestCase + + include Mocha + + def test_should_return_added_expectation + expectation_list = ExpectationList.new + expectation = Expectation.new(nil, :my_method) + assert_same expectation, expectation_list.add(expectation) + end + + def test_should_find_matching_expectation + expectation_list = ExpectationList.new + expectation1 = Expectation.new(nil, :my_method).with(:argument1, :argument2) + expectation2 = Expectation.new(nil, :my_method).with(:argument3, :argument4) + expectation_list.add(expectation1) + expectation_list.add(expectation2) + assert_same expectation2, expectation_list.detect(:my_method, :argument3, :argument4) + end + + def test_should_find_most_recent_matching_expectation + expectation_list = ExpectationList.new + expectation1 = Expectation.new(nil, :my_method).with(:argument1, :argument2) + expectation2 = Expectation.new(nil, :my_method).with(:argument1, :argument2) + expectation_list.add(expectation1) + expectation_list.add(expectation2) + assert_same expectation2, expectation_list.detect(:my_method, :argument1, :argument2) + end + + def test_should_find_most_recent_matching_expectation_but_give_preference_to_those_allowing_invocations + expectation_list = ExpectationList.new + expectation1 = Expectation.new(nil, :my_method) + expectation2 = Expectation.new(nil, :my_method) + expectation1.define_instance_method(:invocations_allowed?) { true } + expectation2.define_instance_method(:invocations_allowed?) { false } + expectation_list.add(expectation1) + expectation_list.add(expectation2) + assert_same expectation1, expectation_list.detect(:my_method) + end + + def test_should_find_most_recent_matching_expectation_if_no_matching_expectations_allow_invocations + expectation_list = ExpectationList.new + expectation1 = Expectation.new(nil, :my_method) + expectation2 = Expectation.new(nil, :my_method) + expectation1.define_instance_method(:invocations_allowed?) { false } + expectation2.define_instance_method(:invocations_allowed?) { false } + expectation_list.add(expectation1) + expectation_list.add(expectation2) + assert_same expectation2, expectation_list.detect(:my_method) + end + + def test_should_find_expectations_for_the_same_method_no_matter_what_the_arguments + expectation_list = ExpectationList.new + expectation1 = Expectation.new(nil, :my_method).with(:argument1, :argument2) + expectation_list.add(expectation1) + expectation2 = Expectation.new(nil, :my_method).with(:argument3, :argument4) + expectation_list.add(expectation2) + assert_equal [expectation1, expectation2].to_set, expectation_list.similar(:my_method).to_set + end + + def test_should_ignore_expectations_for_different_methods + expectation_list = ExpectationList.new + expectation1 = Expectation.new(nil, :method1).with(:argument1, :argument2) + expectation_list.add(expectation1) + expectation2 = Expectation.new(nil, :method2).with(:argument1, :argument2) + expectation_list.add(expectation2) + assert_equal [expectation2], expectation_list.similar(:method2) + end + +end diff --git a/vendor/gems/mocha-0.5.6/test/unit/expectation_raiser_test.rb b/vendor/gems/mocha-0.5.6/test/unit/expectation_raiser_test.rb new file mode 100644 index 0000000000..3b46d8fd87 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/unit/expectation_raiser_test.rb @@ -0,0 +1,28 @@ +require File.join(File.dirname(__FILE__), "..", "test_helper") + +require 'mocha/exception_raiser' + +class ExceptionRaiserTest < Test::Unit::TestCase + + include Mocha + + def test_should_raise_exception_with_specified_class_and_default_message + exception_class = Class.new(StandardError) + raiser = ExceptionRaiser.new(exception_class, nil) + exception = assert_raises(exception_class) { raiser.evaluate } + assert_equal exception_class.to_s, exception.message + end + + def test_should_raise_exception_with_specified_class_and_message + exception_class = Class.new(StandardError) + raiser = ExceptionRaiser.new(exception_class, 'message') + exception = assert_raises(exception_class) { raiser.evaluate } + assert_equal 'message', exception.message + end + + def test_should_raise_interrupt_exception_with_default_message_so_it_works_in_ruby_1_8_6 + raiser = ExceptionRaiser.new(Interrupt, nil) + assert_raises(Interrupt) { raiser.evaluate } + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/expectation_test.rb b/vendor/gems/mocha-0.5.6/test/unit/expectation_test.rb new file mode 100644 index 0000000000..cdb38eb9b7 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/unit/expectation_test.rb @@ -0,0 +1,483 @@ +require File.join(File.dirname(__FILE__), "..", "test_helper") +require 'method_definer' +require 'mocha/expectation' +require 'mocha/sequence' +require 'execution_point' +require 'deprecation_disabler' + +class ExpectationTest < Test::Unit::TestCase + + include Mocha + include DeprecationDisabler + + def new_expectation + Expectation.new(nil, :expected_method) + end + + def test_should_match_calls_to_same_method_with_any_parameters + assert new_expectation.match?(:expected_method, 1, 2, 3) + end + + def test_should_match_calls_to_same_method_with_exactly_zero_parameters + expectation = new_expectation.with() + assert expectation.match?(:expected_method) + end + + def test_should_not_match_calls_to_same_method_with_more_than_zero_parameters + expectation = new_expectation.with() + assert !expectation.match?(:expected_method, 1, 2, 3) + end + + def test_should_match_calls_to_same_method_with_expected_parameter_values + expectation = new_expectation.with(1, 2, 3) + assert expectation.match?(:expected_method, 1, 2, 3) + end + + def test_should_match_calls_to_same_method_with_parameters_constrained_as_expected + expectation = new_expectation.with() {|x, y, z| x + y == z} + assert expectation.match?(:expected_method, 1, 2, 3) + end + + def test_should_not_match_calls_to_different_method_with_parameters_constrained_as_expected + expectation = new_expectation.with() {|x, y, z| x + y == z} + assert !expectation.match?(:different_method, 1, 2, 3) + end + + def test_should_not_match_calls_to_different_methods_with_no_parameters + assert !new_expectation.match?(:unexpected_method) + end + + def test_should_not_match_calls_to_same_method_with_too_few_parameters + expectation = new_expectation.with(1, 2, 3) + assert !expectation.match?(:unexpected_method, 1, 2) + end + + def test_should_not_match_calls_to_same_method_with_too_many_parameters + expectation = new_expectation.with(1, 2) + assert !expectation.match?(:unexpected_method, 1, 2, 3) + end + + def test_should_not_match_calls_to_same_method_with_unexpected_parameter_values + expectation = new_expectation.with(1, 2, 3) + assert !expectation.match?(:unexpected_method, 1, 0, 3) + end + + def test_should_not_match_calls_to_same_method_with_parameters_not_constrained_as_expected + expectation = new_expectation.with() {|x, y, z| x + y == z} + assert !expectation.match?(:expected_method, 1, 0, 3) + end + + def test_should_allow_invocations_until_expected_invocation_count_is_one_and_actual_invocation_count_would_be_two + expectation = new_expectation.times(1) + assert expectation.invocations_allowed? + expectation.invoke + assert !expectation.invocations_allowed? + end + + def test_should_allow_invocations_until_expected_invocation_count_is_two_and_actual_invocation_count_would_be_three + expectation = new_expectation.times(2) + assert expectation.invocations_allowed? + expectation.invoke + assert expectation.invocations_allowed? + expectation.invoke + assert !expectation.invocations_allowed? + end + + def test_should_allow_invocations_until_expected_invocation_count_is_a_range_from_two_to_three_and_actual_invocation_count_would_be_four + expectation = new_expectation.times(2..3) + assert expectation.invocations_allowed? + expectation.invoke + assert expectation.invocations_allowed? + expectation.invoke + assert expectation.invocations_allowed? + expectation.invoke + assert !expectation.invocations_allowed? + end + + def test_should_store_provided_backtrace + backtrace = Object.new + expectation = Expectation.new(nil, :expected_method, backtrace) + assert_equal backtrace, expectation.backtrace + end + + def test_should_default_backtrace_to_caller + execution_point = ExecutionPoint.current; expectation = Expectation.new(nil, :expected_method) + assert_equal execution_point, ExecutionPoint.new(expectation.backtrace) + end + + def test_should_not_yield + yielded = false + new_expectation.invoke() { yielded = true } + assert_equal false, yielded + end + + def test_should_yield_no_parameters + expectation = new_expectation().yields() + yielded_parameters = nil + expectation.invoke() { |*parameters| yielded_parameters = parameters } + assert_equal Array.new, yielded_parameters + end + + def test_should_yield_with_specified_parameters + expectation = new_expectation().yields(1, 2, 3) + yielded_parameters = nil + expectation.invoke() { |*parameters| yielded_parameters = parameters } + assert_equal [1, 2, 3], yielded_parameters + end + + def test_should_yield_different_parameters_on_consecutive_invocations + expectation = new_expectation().yields(1, 2, 3).yields(4, 5) + yielded_parameters = [] + expectation.invoke() { |*parameters| yielded_parameters << parameters } + expectation.invoke() { |*parameters| yielded_parameters << parameters } + assert_equal [[1, 2, 3], [4, 5]], yielded_parameters + end + + def test_should_yield_multiple_times_for_single_invocation + expectation = new_expectation().multiple_yields([1, 2, 3], [4, 5]) + yielded_parameters = [] + expectation.invoke() { |*parameters| yielded_parameters << parameters } + assert_equal [[1, 2, 3], [4, 5]], yielded_parameters + end + + def test_should_yield_multiple_times_for_first_invocation_and_once_for_second_invocation + expectation = new_expectation().multiple_yields([1, 2, 3], [4, 5]).then.yields(6, 7) + yielded_parameters = [] + expectation.invoke() { |*parameters| yielded_parameters << parameters } + expectation.invoke() { |*parameters| yielded_parameters << parameters } + assert_equal [[1, 2, 3], [4, 5], [6, 7]], yielded_parameters + end + + def test_should_return_specified_value + expectation = new_expectation.returns(99) + assert_equal 99, expectation.invoke + end + + def test_should_return_same_specified_value_multiple_times + expectation = new_expectation.returns(99) + assert_equal 99, expectation.invoke + assert_equal 99, expectation.invoke + end + + def test_should_return_specified_values_on_consecutive_calls + expectation = new_expectation.returns(99, 100, 101) + assert_equal 99, expectation.invoke + assert_equal 100, expectation.invoke + assert_equal 101, expectation.invoke + end + + def test_should_return_specified_values_on_consecutive_calls_even_if_values_are_modified + values = [99, 100, 101] + expectation = new_expectation.returns(*values) + values.shift + assert_equal 99, expectation.invoke + assert_equal 100, expectation.invoke + assert_equal 101, expectation.invoke + end + + def test_should_return_nil_by_default + assert_nil new_expectation.invoke + end + + def test_should_return_nil_if_no_value_specified + expectation = new_expectation.returns() + assert_nil expectation.invoke + end + + def test_should_return_evaluated_proc + proc = lambda { 99 } + expectation = new_expectation.returns(proc) + result = nil + disable_deprecations { result = expectation.invoke } + assert_equal 99, result + end + + def test_should_return_evaluated_proc_without_using_is_a_method + proc = lambda { 99 } + proc.define_instance_accessor(:called) + proc.called = false + proc.replace_instance_method(:is_a?) { self.called = true; true} + expectation = new_expectation.returns(proc) + disable_deprecations { expectation.invoke } + assert_equal false, proc.called + end + + def test_should_raise_runtime_exception + expectation = new_expectation.raises + assert_raise(RuntimeError) { expectation.invoke } + end + + def test_should_raise_custom_exception + exception = Class.new(Exception) + expectation = new_expectation.raises(exception) + assert_raise(exception) { expectation.invoke } + end + + def test_should_raise_same_instance_of_custom_exception + exception_klass = Class.new(StandardError) + expected_exception = exception_klass.new + expectation = new_expectation.raises(expected_exception) + actual_exception = assert_raise(exception_klass) { expectation.invoke } + assert_same expected_exception, actual_exception + end + + def test_should_use_the_default_exception_message + expectation = new_expectation.raises(Exception) + exception = assert_raise(Exception) { expectation.invoke } + assert_equal Exception.new.message, exception.message + end + + def test_should_raise_custom_exception_with_message + exception_msg = "exception message" + expectation = new_expectation.raises(Exception, exception_msg) + exception = assert_raise(Exception) { expectation.invoke } + assert_equal exception_msg, exception.message + end + + def test_should_return_values_then_raise_exception + expectation = new_expectation.returns(1, 2).then.raises() + assert_equal 1, expectation.invoke + assert_equal 2, expectation.invoke + assert_raise(RuntimeError) { expectation.invoke } + end + + def test_should_raise_exception_then_return_values + expectation = new_expectation.raises().then.returns(1, 2) + assert_raise(RuntimeError) { expectation.invoke } + assert_equal 1, expectation.invoke + assert_equal 2, expectation.invoke + end + + def test_should_not_raise_error_on_verify_if_expected_call_was_made + expectation = new_expectation + expectation.invoke + assert_nothing_raised(ExpectationError) { + expectation.verify + } + end + + def test_should_raise_error_on_verify_if_call_expected_once_but_invoked_twice + expectation = new_expectation.once + expectation.invoke + expectation.invoke + assert_raises(ExpectationError) { + expectation.verify + } + end + + def test_should_raise_error_on_verify_if_call_expected_once_but_not_invoked + expectation = new_expectation.once + assert_raises(ExpectationError) { + expectation.verify + } + end + + def test_should_not_raise_error_on_verify_if_call_expected_once_and_invoked_once + expectation = new_expectation.once + expectation.invoke + assert_nothing_raised(ExpectationError) { + expectation.verify + } + end + + def test_should_not_raise_error_on_verify_if_expected_call_was_made_at_least_once + expectation = new_expectation.at_least_once + 3.times {expectation.invoke} + assert_nothing_raised(ExpectationError) { + expectation.verify + } + end + + def test_should_raise_error_on_verify_if_expected_call_was_not_made_at_least_once + expectation = new_expectation.with(1, 2, 3).at_least_once + e = assert_raise(ExpectationError) { + expectation.verify + } + assert_match(/expected calls: at least 1, actual calls: 0/i, e.message) + end + + def test_should_not_raise_error_on_verify_if_expected_call_was_made_expected_number_of_times + expectation = new_expectation.times(2) + 2.times {expectation.invoke} + assert_nothing_raised(ExpectationError) { + expectation.verify + } + end + + def test_should_expect_call_not_to_be_made + expectation = new_expectation + expectation.define_instance_accessor(:how_many_times) + expectation.replace_instance_method(:times) { |how_many_times| self.how_many_times = how_many_times } + expectation.never + assert_equal 0, expectation.how_many_times + end + + def test_should_raise_error_on_verify_if_expected_call_was_made_too_few_times + expectation = new_expectation.times(2) + 1.times {expectation.invoke} + e = assert_raise(ExpectationError) { + expectation.verify + } + assert_match(/expected calls: 2, actual calls: 1/i, e.message) + end + + def test_should_raise_error_on_verify_if_expected_call_was_made_too_many_times + expectation = new_expectation.times(2) + 3.times {expectation.invoke} + assert_raise(ExpectationError) { + expectation.verify + } + end + + def test_should_yield_self_to_block + expectation = new_expectation + expectation.invoke + yielded_expectation = nil + expectation.verify { |x| yielded_expectation = x } + assert_equal expectation, yielded_expectation + end + + def test_should_yield_to_block_before_raising_exception + yielded = false + assert_raise(ExpectationError) { + new_expectation.verify { |x| yielded = true } + } + assert yielded + end + + def test_should_store_backtrace_from_point_where_expectation_was_created + execution_point = ExecutionPoint.current; expectation = Expectation.new(nil, :expected_method) + assert_equal execution_point, ExecutionPoint.new(expectation.backtrace) + end + + def test_should_set_backtrace_on_assertion_failed_error_to_point_where_expectation_was_created + execution_point = ExecutionPoint.current; expectation = Expectation.new(nil, :expected_method) + error = assert_raise(ExpectationError) { + expectation.verify + } + assert_equal execution_point, ExecutionPoint.new(error.backtrace) + end + + def test_should_display_expectation_in_exception_message + options = [:a, :b, {:c => 1, :d => 2}] + expectation = new_expectation.with(*options) + exception = assert_raise(ExpectationError) { expectation.verify } + assert exception.message.include?(expectation.method_signature) + end + + class FakeMock + + def initialize(name) + @name = name + end + + def mocha_inspect + @name + end + + end + + def test_should_raise_error_with_message_indicating_which_method_was_expected_to_be_called_on_which_mock_object_with_which_parameters_and_in_what_sequences + mock = FakeMock.new('mock') + sequence_one = Sequence.new('one') + sequence_two = Sequence.new('two') + expectation = Expectation.new(mock, :expected_method).with(1, 2, {'a' => true, :b => false}, [1, 2, 3]).in_sequence(sequence_one, sequence_two) + e = assert_raise(ExpectationError) { expectation.verify } + assert_match "mock.expected_method(1, 2, {'a' => true, :b => false}, [1, 2, 3]); in sequence 'one'; in sequence 'two'", e.message + end + + class FakeConstraint + + def initialize(allows_invocation_now) + @allows_invocation_now = allows_invocation_now + end + + def allows_invocation_now? + @allows_invocation_now + end + + end + + def test_should_be_in_correct_order_if_all_ordering_constraints_allow_invocation_now + constraint_one = FakeConstraint.new(allows_invocation_now = true) + constraint_two = FakeConstraint.new(allows_invocation_now = true) + expectation = Expectation.new(nil, :method_one) + expectation.add_ordering_constraint(constraint_one) + expectation.add_ordering_constraint(constraint_two) + assert expectation.in_correct_order? + end + + def test_should_not_be_in_correct_order_if_one_ordering_constraint_does_not_allow_invocation_now + constraint_one = FakeConstraint.new(allows_invocation_now = true) + constraint_two = FakeConstraint.new(allows_invocation_now = false) + expectation = Expectation.new(nil, :method_one) + expectation.add_ordering_constraint(constraint_one) + expectation.add_ordering_constraint(constraint_two) + assert !expectation.in_correct_order? + end + + def test_should_match_if_all_ordering_constraints_allow_invocation_now + constraint_one = FakeConstraint.new(allows_invocation_now = true) + constraint_two = FakeConstraint.new(allows_invocation_now = true) + expectation = Expectation.new(nil, :method_one) + expectation.add_ordering_constraint(constraint_one) + expectation.add_ordering_constraint(constraint_two) + assert expectation.match?(:method_one) + end + + def test_should_not_match_if_one_ordering_constraints_does_not_allow_invocation_now + constraint_one = FakeConstraint.new(allows_invocation_now = true) + constraint_two = FakeConstraint.new(allows_invocation_now = false) + expectation = Expectation.new(nil, :method_one) + expectation.add_ordering_constraint(constraint_one) + expectation.add_ordering_constraint(constraint_two) + assert !expectation.match?(:method_one) + end + + def test_should_not_be_satisfied_when_required_invocation_has_not_been_made + expectation = Expectation.new(nil, :method_one).times(1) + assert !expectation.satisfied? + end + + def test_should_be_satisfied_when_required_invocation_has_been_made + expectation = Expectation.new(nil, :method_one).times(1) + expectation.invoke + assert expectation.satisfied? + end + + def test_should_not_be_satisfied_when_minimum_number_of_invocations_has_not_been_made + expectation = Expectation.new(nil, :method_one).at_least(2) + expectation.invoke + assert !expectation.satisfied? + end + + def test_should_be_satisfied_when_minimum_number_of_invocations_has_been_made + expectation = Expectation.new(nil, :method_one).at_least(2) + 2.times { expectation.invoke } + assert expectation.satisfied? + end + + class FakeSequence + + attr_reader :expectations + + def initialize + @expectations = [] + end + + def constrain_as_next_in_sequence(expectation) + @expectations << expectation + end + + end + + def test_should_tell_sequences_to_constrain_expectation_as_next_in_sequence + sequence_one = FakeSequence.new + sequence_two = FakeSequence.new + expectation = Expectation.new(nil, :method_one) + assert_equal expectation, expectation.in_sequence(sequence_one, sequence_two) + assert_equal [expectation], sequence_one.expectations + assert_equal [expectation], sequence_two.expectations + end + +end diff --git a/vendor/gems/mocha-0.5.6/test/unit/hash_inspect_test.rb b/vendor/gems/mocha-0.5.6/test/unit/hash_inspect_test.rb new file mode 100644 index 0000000000..15ad415447 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/unit/hash_inspect_test.rb @@ -0,0 +1,16 @@ +require File.join(File.dirname(__FILE__), "..", "test_helper") +require 'mocha/inspect' + +class HashInspectTest < Test::Unit::TestCase + + def test_should_keep_spacing_between_key_value + hash = {:a => true} + assert_equal '{:a => true}', hash.mocha_inspect + end + + def test_should_use_mocha_inspect_on_each_item + hash = {:a => 'mocha'} + assert_equal "{:a => 'mocha'}", hash.mocha_inspect + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/infinite_range_test.rb b/vendor/gems/mocha-0.5.6/test/unit/infinite_range_test.rb new file mode 100644 index 0000000000..7b4c8a4cb6 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/unit/infinite_range_test.rb @@ -0,0 +1,53 @@ +require File.join(File.dirname(__FILE__), "..", "test_helper") +require 'mocha/infinite_range' +require 'date' + +class InfiniteRangeTest < Test::Unit::TestCase + + def test_should_include_values_at_or_above_minimum + range = Range.at_least(10) + assert(range === 10) + assert(range === 11) + assert(range === 1000000) + end + + def test_should_not_include_values_below_minimum + range = Range.at_least(10) + assert_false(range === 0) + assert_false(range === 9) + assert_false(range === -11) + end + + def test_should_be_human_readable_description_for_at_least + assert_equal "at least 10", Range.at_least(10).mocha_inspect + end + + def test_should_include_values_at_or_below_maximum + range = Range.at_most(10) + assert(range === 10) + assert(range === 0) + assert(range === -1000000) + end + + def test_should_not_include_values_above_maximum + range = Range.at_most(10) + assert_false(range === 11) + assert_false(range === 1000000) + end + + def test_should_be_human_readable_description_for_at_most + assert_equal "at most 10", Range.at_most(10).mocha_inspect + end + + def test_should_be_same_as_standard_to_string + assert_equal((1..10).to_s, (1..10).mocha_inspect) + assert_equal((1...10).to_s, (1...10).mocha_inspect) + date_range = Range.new(Date.parse('2006-01-01'), Date.parse('2007-01-01')) + assert_equal date_range.to_s, date_range.mocha_inspect + end + + def assert_false(condition) + assert(!condition) + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/metaclass_test.rb b/vendor/gems/mocha-0.5.6/test/unit/metaclass_test.rb new file mode 100644 index 0000000000..956bcb45b0 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/unit/metaclass_test.rb @@ -0,0 +1,22 @@ +require File.join(File.dirname(__FILE__), "..", "test_helper") +require 'mocha/metaclass' + +class MetaclassTest < Test::Unit::TestCase + + def test_should_return_objects_singleton_class + object = Object.new + assert_raises(NoMethodError) { object.success? } + + object = Object.new + assert object.__metaclass__.ancestors.include?(Object) + assert object.__metaclass__.ancestors.include?(Kernel) + assert object.__metaclass__.is_a?(Class) + + object.__metaclass__.class_eval { def success?; true; end } + assert object.success? + + object = Object.new + assert_raises(NoMethodError) { object.success? } + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/method_matcher_test.rb b/vendor/gems/mocha-0.5.6/test/unit/method_matcher_test.rb new file mode 100644 index 0000000000..0167433e49 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/unit/method_matcher_test.rb @@ -0,0 +1,23 @@ +require File.join(File.dirname(__FILE__), "..", "test_helper") +require 'mocha/method_matcher' + +class MethodMatcherTest < Test::Unit::TestCase + + include Mocha + + def test_should_match_if_actual_method_name_is_same_as_expected_method_name + method_matcher = MethodMatcher.new(:method_name) + assert method_matcher.match?(:method_name) + end + + def test_should_not_match_if_actual_method_name_is_not_same_as_expected_method_name + method_matcher = MethodMatcher.new(:method_name) + assert !method_matcher.match?(:different_method_name) + end + + def test_should_describe_what_method_is_expected + method_matcher = MethodMatcher.new(:method_name) + assert_equal "method_name", method_matcher.mocha_inspect + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/missing_expectation_test.rb b/vendor/gems/mocha-0.5.6/test/unit/missing_expectation_test.rb new file mode 100644 index 0000000000..9d3b45aa76 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/unit/missing_expectation_test.rb @@ -0,0 +1,42 @@ +require File.join(File.dirname(__FILE__), "..", "test_helper") + +require 'mocha/missing_expectation' +require 'mocha/mock' + +class MissingExpectationTest < Test::Unit::TestCase + + include Mocha + + def test_should_report_similar_expectations + mock = Mock.new + expectation_1 = mock.expects(:method_one).with(1) + expectation_2 = mock.expects(:method_one).with(1, 1) + expectation_3 = mock.expects(:method_two).with(2) + + missing_expectation = MissingExpectation.new(mock, :method_one) + exception = assert_raise(ExpectationError) { missing_expectation.verify } + + expected_message = [ + "#{missing_expectation.error_message(0, 1)}", + "Similar expectations:", + "#{expectation_1.method_signature}", + "#{expectation_2.method_signature}" + ].join("\n") + + assert_equal expected_message, exception.message + end + + def test_should_not_report_similar_expectations_if_there_are_none + mock = Mock.new + mock.expects(:method_two).with(2) + mock.expects(:method_two).with(2, 2) + + missing_expectation = MissingExpectation.new(mock, :method_one) + exception = assert_raise(ExpectationError) { missing_expectation.verify } + + expected_message = "#{missing_expectation.error_message(0, 1)}" + + assert_equal expected_message, exception.message + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/mock_test.rb b/vendor/gems/mocha-0.5.6/test/unit/mock_test.rb new file mode 100644 index 0000000000..f844bc81d1 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/unit/mock_test.rb @@ -0,0 +1,323 @@ +require File.join(File.dirname(__FILE__), "..", "test_helper") +require 'mocha/mock' +require 'mocha/expectation_error' +require 'set' + +class MockTest < Test::Unit::TestCase + + include Mocha + + def test_should_set_single_expectation + mock = Mock.new + mock.expects(:method1).returns(1) + assert_nothing_raised(ExpectationError) do + assert_equal 1, mock.method1 + end + end + + def test_should_build_and_store_expectations + mock = Mock.new + expectation = mock.expects(:method1) + assert_not_nil expectation + assert_equal [expectation], mock.expectations.to_a + end + + def test_should_not_stub_everything_by_default + mock = Mock.new + assert_equal false, mock.everything_stubbed + end + + def test_should_stub_everything + mock = Mock.new + mock.stub_everything + assert_equal true, mock.everything_stubbed + end + + def test_should_display_object_id_for_mocha_inspect_if_mock_has_no_name + mock = Mock.new + assert_match Regexp.new("^#$"), mock.mocha_inspect + end + + def test_should_display_name_for_mocha_inspect_if_mock_has_name + mock = Mock.new('named_mock') + assert_equal "#", mock.mocha_inspect + end + + def test_should_display_object_id_for_inspect_if_mock_has_no_name + mock = Mock.new + assert_match Regexp.new("^#$"), mock.inspect + end + + def test_should_display_name_for_inspect_if_mock_has_name + mock = Mock.new('named_mock') + assert_equal "#", mock.inspect + end + + def test_should_be_able_to_extend_mock_object_with_module + mock = Mock.new + assert_nothing_raised(ExpectationError) { mock.extend(Module.new) } + end + + def test_should_be_equal + mock = Mock.new + assert_equal true, mock.eql?(mock) + end + + if RUBY_VERSION < '1.9' + OBJECT_METHODS = STANDARD_OBJECT_PUBLIC_INSTANCE_METHODS.reject { |m| m =~ /^__.*__$/ } + else + OBJECT_METHODS = STANDARD_OBJECT_PUBLIC_INSTANCE_METHODS.reject { |m| m =~ /^__.*__$/ || m == :object_id } + end + + def test_should_be_able_to_mock_standard_object_methods + mock = Mock.new + OBJECT_METHODS.each { |method| mock.__expects__(method.to_sym).returns(method) } + OBJECT_METHODS.each { |method| assert_equal method, mock.__send__(method.to_sym) } + assert_nothing_raised(ExpectationError) { mock.verify } + end + + def test_should_be_able_to_stub_standard_object_methods + mock = Mock.new + OBJECT_METHODS.each { |method| mock.__stubs__(method.to_sym).returns(method) } + OBJECT_METHODS.each { |method| assert_equal method, mock.__send__(method.to_sym) } + end + + def test_should_create_and_add_expectations + mock = Mock.new + expectation1 = mock.expects(:method1) + expectation2 = mock.expects(:method2) + assert_equal [expectation1, expectation2].to_set, mock.expectations.to_set + end + + def test_should_pass_backtrace_into_expectation + mock = Mock.new + backtrace = Object.new + expectation = mock.expects(:method1, backtrace) + assert_equal backtrace, expectation.backtrace + end + + def test_should_pass_backtrace_into_stub + mock = Mock.new + backtrace = Object.new + stub = mock.stubs(:method1, backtrace) + assert_equal backtrace, stub.backtrace + end + + def test_should_create_and_add_stubs + mock = Mock.new + stub1 = mock.stubs(:method1) + stub2 = mock.stubs(:method2) + assert_equal [stub1, stub2].to_set, mock.expectations.to_set + end + + def test_should_invoke_expectation_and_return_result + mock = Mock.new + mock.expects(:my_method).returns(:result) + result = mock.my_method + assert_equal :result, result + end + + def test_should_not_raise_error_if_stubbing_everything + mock = Mock.new + mock.stub_everything + result = nil + assert_nothing_raised(ExpectationError) do + result = mock.unexpected_method + end + assert_nil result + end + + def test_should_raise_assertion_error_for_unexpected_method_call + mock = Mock.new + error = assert_raise(ExpectationError) do + mock.unexpected_method_called(:my_method, :argument1, :argument2) + end + assert_match(/my_method/, error.message) + assert_match(/argument1/, error.message) + assert_match(/argument2/, error.message) + end + + def test_should_indicate_unexpected_method_called + mock = Mock.new + class << mock + attr_accessor :symbol, :arguments + def unexpected_method_called(symbol, *arguments) + self.symbol, self.arguments = symbol, arguments + end + end + mock.my_method(:argument1, :argument2) + assert_equal :my_method, mock.symbol + assert_equal [:argument1, :argument2], mock.arguments + end + + def test_should_verify_that_all_expectations_have_been_fulfilled + mock = Mock.new + mock.expects(:method1) + mock.expects(:method2) + mock.method1 + assert_raise(ExpectationError) do + mock.verify + end + end + + def test_should_report_possible_expectations + mock = Mock.new + mock.expects(:expected_method).with(1) + exception = assert_raise(ExpectationError) { mock.expected_method(2) } + assert_equal "#{mock.mocha_inspect}.expected_method(2) - expected calls: 0, actual calls: 1\nSimilar expectations:\n#{mock.mocha_inspect}.expected_method(1)", exception.message + end + + def test_should_pass_block_through_to_expectations_verify_method + mock = Mock.new + expected_expectation = mock.expects(:method1) + mock.method1 + expectations = [] + mock.verify() { |expectation| expectations << expectation } + assert_equal [expected_expectation], expectations + end + + def test_should_yield_supplied_parameters_to_block + mock = Mock.new + parameters_for_yield = [1, 2, 3] + mock.expects(:method1).yields(*parameters_for_yield) + yielded_parameters = nil + mock.method1() { |*parameters| yielded_parameters = parameters } + assert_equal parameters_for_yield, yielded_parameters + end + + def test_should_set_up_multiple_expectations_with_return_values + mock = Mock.new + mock.expects(:method1 => :result1, :method2 => :result2) + assert_equal :result1, mock.method1 + assert_equal :result2, mock.method2 + end + + def test_should_set_up_multiple_stubs_with_return_values + mock = Mock.new + mock.stubs(:method1 => :result1, :method2 => :result2) + assert_equal :result1, mock.method1 + assert_equal :result2, mock.method2 + end + + def test_should_keep_returning_specified_value_for_stubs + mock = Mock.new + mock.stubs(:method1).returns(1) + assert_equal 1, mock.method1 + assert_equal 1, mock.method1 + end + + def test_should_keep_returning_specified_value_for_expects + mock = Mock.new + mock.expects(:method1).times(2).returns(1) + assert_equal 1, mock.method1 + assert_equal 1, mock.method1 + end + + def test_should_match_most_recent_call_to_expects + mock = Mock.new + mock.expects(:method1).returns(0) + mock.expects(:method1).returns(1) + assert_equal 1, mock.method1 + end + + def test_should_match_most_recent_call_to_stubs + mock = Mock.new + mock.stubs(:method1).returns(0) + mock.stubs(:method1).returns(1) + assert_equal 1, mock.method1 + end + + def test_should_match_most_recent_call_to_stubs_or_expects + mock = Mock.new + mock.stubs(:method1).returns(0) + mock.expects(:method1).returns(1) + assert_equal 1, mock.method1 + end + + def test_should_match_most_recent_call_to_expects_or_stubs + mock = Mock.new + mock.expects(:method1).returns(0) + mock.stubs(:method1).returns(1) + assert_equal 1, mock.method1 + end + + def test_should_respond_to_expected_method + mock = Mock.new + mock.expects(:method1) + assert_equal true, mock.respond_to?(:method1) + end + + def test_should_not_respond_to_unexpected_method + mock = Mock.new + assert_equal false, mock.respond_to?(:method1) + end + + def test_should_respond_to_methods_which_the_responder_does_responds_to + instance = Class.new do + define_method(:respond_to?) { |symbol| true } + end.new + mock = Mock.new + mock.responds_like(instance) + assert_equal true, mock.respond_to?(:invoked_method) + end + + def test_should_not_respond_to_methods_which_the_responder_does_not_responds_to + instance = Class.new do + define_method(:respond_to?) { |symbol| false } + end.new + mock = Mock.new + mock.responds_like(instance) + assert_equal false, mock.respond_to?(:invoked_method) + end + + def test_should_return_itself_to_allow_method_chaining + mock = Mock.new + assert_same mock.responds_like(Object.new), mock + end + + def test_should_not_raise_no_method_error_if_mock_is_not_restricted_to_respond_like_a_responder + instance = Class.new do + define_method(:respond_to?) { true } + end.new + mock = Mock.new + mock.stubs(:invoked_method) + assert_nothing_raised(NoMethodError) { mock.invoked_method } + end + + def test_should_not_raise_no_method_error_if_responder_does_respond_to_invoked_method + instance = Class.new do + define_method(:respond_to?) { |symbol| true } + end.new + mock = Mock.new + mock.responds_like(instance) + mock.stubs(:invoked_method) + assert_nothing_raised(NoMethodError) { mock.invoked_method } + end + + def test_should_raise_no_method_error_if_responder_does_not_respond_to_invoked_method + instance = Class.new do + define_method(:respond_to?) { |symbol| false } + define_method(:mocha_inspect) { 'mocha_inspect' } + end.new + mock = Mock.new + mock.responds_like(instance) + mock.stubs(:invoked_method) + assert_raises(NoMethodError) { mock.invoked_method } + end + + def test_should_raise_no_method_error_with_message_indicating_that_mock_is_constrained_to_respond_like_responder + instance = Class.new do + define_method(:respond_to?) { |symbol| false } + define_method(:mocha_inspect) { 'mocha_inspect' } + end.new + mock = Mock.new + mock.responds_like(instance) + mock.stubs(:invoked_method) + begin + mock.invoked_method + rescue NoMethodError => e + assert_match(/which responds like mocha_inspect/, e.message) + end + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/multiple_yields_test.rb b/vendor/gems/mocha-0.5.6/test/unit/multiple_yields_test.rb new file mode 100644 index 0000000000..65724a8fbc --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/unit/multiple_yields_test.rb @@ -0,0 +1,18 @@ +require File.join(File.dirname(__FILE__), "..", "test_helper") + +require 'mocha/multiple_yields' + +class MultipleYieldsTest < Test::Unit::TestCase + + include Mocha + + def test_should_provide_parameters_for_multiple_yields_in_single_invocation + parameter_group = MultipleYields.new([1, 2, 3], [4, 5]) + parameter_groups = [] + parameter_group.each do |parameters| + parameter_groups << parameters + end + assert_equal [[1, 2, 3], [4, 5]], parameter_groups + end + +end diff --git a/vendor/gems/mocha-0.5.6/test/unit/no_yield_test.rb b/vendor/gems/mocha-0.5.6/test/unit/no_yield_test.rb new file mode 100644 index 0000000000..544d1ef257 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/unit/no_yield_test.rb @@ -0,0 +1,18 @@ +require File.join(File.dirname(__FILE__), "..", "test_helper") + +require 'mocha/no_yields' + +class NoYieldsTest < Test::Unit::TestCase + + include Mocha + + def test_should_provide_parameters_for_no_yields_in_single_invocation + parameter_group = NoYields.new + parameter_groups = [] + parameter_group.each do |parameters| + parameter_groups << parameters + end + assert_equal [], parameter_groups + end + +end diff --git a/vendor/gems/mocha-0.5.6/test/unit/object_inspect_test.rb b/vendor/gems/mocha-0.5.6/test/unit/object_inspect_test.rb new file mode 100644 index 0000000000..56d84a96dd --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/unit/object_inspect_test.rb @@ -0,0 +1,37 @@ +require File.join(File.dirname(__FILE__), "..", "test_helper") +require 'mocha/inspect' +require 'method_definer' + +class ObjectInspectTest < Test::Unit::TestCase + + def test_should_return_default_string_representation_of_object_not_including_instance_variables + object = Object.new + class << object + attr_accessor :attribute + end + object.attribute = 'instance_variable' + assert_match Regexp.new("^#$"), object.mocha_inspect + assert_no_match(/instance_variable/, object.mocha_inspect) + end + + def test_should_return_customized_string_representation_of_object + object = Object.new + class << object + define_method(:inspect) { 'custom_inspect' } + end + assert_equal 'custom_inspect', object.mocha_inspect + end + + def test_should_use_underscored_id_instead_of_object_id_or_id_so_that_they_can_be_stubbed + object = Object.new + object.define_instance_accessor(:called) + object.called = false + object.replace_instance_method(:object_id) { self.called = true; 1 } + if RUBY_VERSION < '1.9' + object.replace_instance_method(:id) { self.called = true; 1 } + end + object.mocha_inspect + assert_equal false, object.called + end + +end diff --git a/vendor/gems/mocha-0.5.6/test/unit/object_test.rb b/vendor/gems/mocha-0.5.6/test/unit/object_test.rb new file mode 100644 index 0000000000..660b7d24f2 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/unit/object_test.rb @@ -0,0 +1,165 @@ +require File.join(File.dirname(__FILE__), "..", "test_helper") +require 'mocha/mock' +require 'method_definer' + +require 'mocha/object' + +class ObjectTest < Test::Unit::TestCase + + include Mocha + + def test_should_build_mocha + instance = Object.new + mocha = instance.mocha + assert_not_nil mocha + assert mocha.is_a?(Mock) + end + + def test_should_reuse_existing_mocha + instance = Object.new + mocha_1 = instance.mocha + mocha_2 = instance.mocha + assert_equal mocha_1, mocha_2 + end + + def test_should_reset_mocha + instance = Object.new + assert_nil instance.reset_mocha + end + + def test_should_stub_instance_method + instance = Object.new + $stubba = Mock.new + $stubba.expects(:stub).with(Mocha::InstanceMethod.new(instance, :method1)) + instance.expects(:method1) + $stubba.verify + end + + def test_should_build_and_store_expectation + instance = Object.new + $stubba = Mock.new + $stubba.stubs(:stub) + expectation = instance.expects(:method1) + assert_equal [expectation], instance.mocha.expectations.to_a + end + + def test_should_verify_expectations + instance = Object.new + $stubba = Mock.new + $stubba.stubs(:stub) + instance.expects(:method1).with(:value1, :value2) + assert_raise(ExpectationError) { instance.verify } + end + + def test_should_pass_backtrace_into_expects + instance = Object.new + $stubba = Mock.new + $stubba.stubs(:stub) + mocha = Object.new + mocha.define_instance_accessor(:expects_parameters) + mocha.define_instance_method(:expects) { |*parameters| self.expects_parameters = parameters } + backtrace = Object.new + instance.define_instance_method(:mocha) { mocha } + instance.define_instance_method(:caller) { backtrace } + instance.expects(:method1) + assert_equal [:method1, backtrace], mocha.expects_parameters + end + + def test_should_pass_backtrace_into_stubs + instance = Object.new + $stubba = Mock.new + $stubba.stubs(:stub) + mocha = Object.new + mocha.define_instance_accessor(:stubs_parameters) + mocha.define_instance_method(:stubs) { |*parameters| self.stubs_parameters = parameters } + backtrace = Object.new + instance.define_instance_method(:mocha) { mocha } + instance.define_instance_method(:caller) { backtrace } + instance.stubs(:method1) + assert_equal [:method1, backtrace], mocha.stubs_parameters + end + + def test_should_build_any_instance_object + klass = Class.new + any_instance = klass.any_instance + assert_not_nil any_instance + assert any_instance.is_a?(Class::AnyInstance) + end + + def test_should_return_same_any_instance_object + klass = Class.new + any_instance_1 = klass.any_instance + any_instance_2 = klass.any_instance + assert_equal any_instance_1, any_instance_2 + end + + def test_should_stub_class_method + klass = Class.new + $stubba = Mock.new + $stubba.expects(:stub).with(Mocha::ClassMethod.new(klass, :method1)) + klass.expects(:method1) + $stubba.verify + end + + def test_should_build_and_store_class_method_expectation + klass = Class.new + $stubba = Mock.new + $stubba.stubs(:stub) + expectation = klass.expects(:method1) + assert_equal [expectation], klass.mocha.expectations.to_a + end + + def test_should_stub_module_method + mod = Module.new + $stubba = Mock.new + $stubba.expects(:stub).with(Mocha::ClassMethod.new(mod, :method1)) + mod.expects(:method1) + $stubba.verify + end + + def test_should_build_and_store_module_method_expectation + mod = Module.new + $stubba = Mock.new + $stubba.stubs(:stub) + expectation = mod.expects(:method1) + assert_equal [expectation], mod.mocha.expectations.to_a + end + + def test_should_use_stubba_instance_method_for_object + assert_equal Mocha::InstanceMethod, Object.new.stubba_method + end + + def test_should_use_stubba_class_method_for_module + assert_equal Mocha::ClassMethod, Module.new.stubba_method + end + + def test_should_use_stubba_class_method_for_class + assert_equal Mocha::ClassMethod, Class.new.stubba_method + end + + def test_should_use_stubba_class_method_for_any_instance + assert_equal Mocha::AnyInstanceMethod, Class::AnyInstance.new(nil).stubba_method + end + + def test_should_stub_self_for_object + object = Object.new + assert_equal object, object.stubba_object + end + + def test_should_stub_self_for_module + mod = Module.new + assert_equal mod, mod.stubba_object + end + + def test_should_stub_self_for_class + klass = Class.new + assert_equal klass, klass.stubba_object + end + + def test_should_stub_relevant_class_for_any_instance + klass = Class.new + any_instance = Class::AnyInstance.new(klass) + assert_equal klass, any_instance.stubba_object + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._all_of_test.rb b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._all_of_test.rb new file mode 100644 index 0000000000000000000000000000000000000000..8494bfb378b61b7fe643a1ed2bf1570639fff0b4 GIT binary patch literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_ISq@YR(lG;w zrO>o7BE&_L^K$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_ISqxMP(lG;w zCDF7oBE&_L^K>83Qh_WZQ)`7< E0J;enCjbBd literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._anything_test.rb b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._anything_test.rb new file mode 100644 index 0000000000000000000000000000000000000000..bbb511c34aa624f5b911cfe4f9e2c77e12f1fa6e GIT binary patch literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_InF>@2(lG;w zCDF7oBE&_L^K>83Qh_WZ18ap^ E0JT9F6aWAK literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._has_entries_test.rb b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._has_entries_test.rb new file mode 100644 index 0000000000000000000000000000000000000000..d519eaa005882a8e2e0e5079cec76af8678aed83 GIT binary patch literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62zdnrUa-Iq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!XE97M6r2<(-mevZj E0QUSC-T(jq literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._has_entry_test.rb b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._has_entry_test.rb new file mode 100644 index 0000000000000000000000000000000000000000..e38bb38b7bf1776fbc716d9f2a8235a0f81146cf GIT binary patch literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_ISqW4M(lG;w zrO>o7BE&_L^K$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_InF&-1(lG;w zrO>o7BE&_L^K$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_InF~}3(lG;w zCDF7oBE&_L^K>83Qh_WZAhQ+# Dx5OA3 literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._includes_test.rb b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._includes_test.rb new file mode 100644 index 0000000000000000000000000000000000000000..ca7d6d9cabaffe2247703f5c79ff17b772a34c87 GIT binary patch literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_InGIA5(lG;w zCDF7oBE&_L^K>83Qh_W(Gi!xf E0Jkd`8~^|S literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._instance_of_test.rb b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._instance_of_test.rb new file mode 100644 index 0000000000000000000000000000000000000000..14618a7f4b33153f6bb8d30c23674656545fec09 GIT binary patch literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_InGaM7(lG;w zrO>o7BE&_L^K$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_ISqfAN(lG;w zCDF7oBE&_L^K>83Qh_WZQ)`7< E0J?}6CIA2c literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._kind_of_test.rb b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._kind_of_test.rb new file mode 100644 index 0000000000000000000000000000000000000000..773437881bab61f27ba96c330b7acbe6314b4059 GIT binary patch literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_ISqM}L(lG;w zrO>o7BE&_L^K$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62zeR>>yAnNXHBy zmPFIWh!7V|&d=4$O-wGz&rK{zE!Im;EJ`h5s7}t$Db3AOuvIX%R>;ZBO9iqF&8!t_ E0U_)eTL1t6 literal 0 HcmV?d00001 diff --git a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._regexp_matches_test.rb b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._regexp_matches_test.rb new file mode 100644 index 0000000000000000000000000000000000000000..2ceba479a60005a174d74e7b852c0ad9579c21b0 GIT binary patch literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_InG944(lG;w zrO>o7BE&_L^K$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_InGRG6(lG;w zrO>o7BE&_L^K 'y'}]) + end + + def test_should_describe_matcher + matcher = anything + assert_equal "anything", matcher.mocha_inspect + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/has_entries_test.rb b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/has_entries_test.rb new file mode 100644 index 0000000000..cb85265f8f --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/has_entries_test.rb @@ -0,0 +1,30 @@ +require File.join(File.dirname(__FILE__), "..", "..", "test_helper") + +require 'mocha/parameter_matchers/has_entries' +require 'mocha/inspect' + +class HasEntriesTest < Test::Unit::TestCase + + include Mocha::ParameterMatchers + + def test_should_match_hash_including_specified_entries + matcher = has_entries(:key_1 => 'value_1', :key_2 => 'value_2') + assert matcher.matches?([{ :key_1 => 'value_1', :key_2 => 'value_2', :key_3 => 'value_3' }]) + end + + def test_should_not_match_hash_not_including_specified_entries + matcher = has_entries(:key_1 => 'value_2', :key_2 => 'value_2', :key_3 => 'value_3') + assert !matcher.matches?([{ :key_1 => 'value_1', :key_2 => 'value_2' }]) + end + + def test_should_describe_matcher + matcher = has_entries(:key_1 => 'value_1', :key_2 => 'value_2') + description = matcher.mocha_inspect + matches = /has_entries\((.*)\)/.match(description) + assert_not_nil matches[0] + entries = eval(matches[1]) + assert_equal 'value_1', entries[:key_1] + assert_equal 'value_2', entries[:key_2] + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/has_entry_test.rb b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/has_entry_test.rb new file mode 100644 index 0000000000..3717b33b02 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/has_entry_test.rb @@ -0,0 +1,40 @@ +require File.join(File.dirname(__FILE__), "..", "..", "test_helper") + +require 'mocha/parameter_matchers/has_entry' +require 'mocha/inspect' + +class HasEntryTest < Test::Unit::TestCase + + include Mocha::ParameterMatchers + + def test_should_match_hash_including_specified_key_value_pair + matcher = has_entry(:key_1, 'value_1') + assert matcher.matches?([{ :key_1 => 'value_1', :key_2 => 'value_2' }]) + end + + def test_should_not_match_hash_not_including_specified_key_value_pair + matcher = has_entry(:key_1, 'value_2') + assert !matcher.matches?([{ :key_1 => 'value_1', :key_2 => 'value_2' }]) + end + + def test_should_match_hash_including_specified_entry + matcher = has_entry(:key_1 => 'value_1') + assert matcher.matches?([{ :key_1 => 'value_1', :key_2 => 'value_2' }]) + end + + def test_should_not_match_hash_not_including_specified_entry + matcher = has_entry(:key_1 => 'value_2') + assert !matcher.matches?([{ :key_1 => 'value_1', :key_2 => 'value_2' }]) + end + + def test_should_describe_matcher_with_key_value_pair + matcher = has_entry(:key_1, 'value_1') + assert_equal "has_entry(:key_1, 'value_1')", matcher.mocha_inspect + end + + def test_should_describe_matcher_with_entry + matcher = has_entry(:key_1 => 'value_1') + assert_equal "has_entry(:key_1, 'value_1')", matcher.mocha_inspect + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/has_key_test.rb b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/has_key_test.rb new file mode 100644 index 0000000000..bc9f5065da --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/has_key_test.rb @@ -0,0 +1,25 @@ +require File.join(File.dirname(__FILE__), "..", "..", "test_helper") + +require 'mocha/parameter_matchers/has_key' +require 'mocha/inspect' + +class HasKeyTest < Test::Unit::TestCase + + include Mocha::ParameterMatchers + + def test_should_match_hash_including_specified_key + matcher = has_key(:key_1) + assert matcher.matches?([{ :key_1 => 1, :key_2 => 2 }]) + end + + def test_should_not_match_hash_not_including_specified_key + matcher = has_key(:key_1) + assert !matcher.matches?([{ :key_2 => 2 }]) + end + + def test_should_describe_matcher + matcher = has_key(:key) + assert_equal 'has_key(:key)', matcher.mocha_inspect + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/has_value_test.rb b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/has_value_test.rb new file mode 100644 index 0000000000..6c8957fd05 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/has_value_test.rb @@ -0,0 +1,25 @@ +require File.join(File.dirname(__FILE__), "..", "..", "test_helper") + +require 'mocha/parameter_matchers/has_value' +require 'mocha/inspect' + +class HasValueTest < Test::Unit::TestCase + + include Mocha::ParameterMatchers + + def test_should_match_hash_including_specified_value + matcher = has_value('value_1') + assert matcher.matches?([{ :key_1 => 'value_1', :key_2 => 'value_2' }]) + end + + def test_should_not_match_hash_not_including_specified_value + matcher = has_value('value_1') + assert !matcher.matches?([{ :key_2 => 'value_2' }]) + end + + def test_should_describe_matcher + matcher = has_value('value_1') + assert_equal "has_value('value_1')", matcher.mocha_inspect + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/includes_test.rb b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/includes_test.rb new file mode 100644 index 0000000000..70fb649d24 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/includes_test.rb @@ -0,0 +1,25 @@ +require File.join(File.dirname(__FILE__), "..", "..", "test_helper") + +require 'mocha/parameter_matchers/includes' +require 'mocha/inspect' + +class IncludesTest < Test::Unit::TestCase + + include Mocha::ParameterMatchers + + def test_should_match_object_including_value + matcher = includes(:x) + assert matcher.matches?([[:x, :y, :z]]) + end + + def test_should_not_match_object_that_does_not_include_value + matcher = includes(:not_included) + assert !matcher.matches?([[:x, :y, :z]]) + end + + def test_should_describe_matcher + matcher = includes(:x) + assert_equal "includes(:x)", matcher.mocha_inspect + end + +end diff --git a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/instance_of_test.rb b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/instance_of_test.rb new file mode 100644 index 0000000000..415b79a483 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/instance_of_test.rb @@ -0,0 +1,25 @@ +require File.join(File.dirname(__FILE__), "..", "..", "test_helper") + +require 'mocha/parameter_matchers/instance_of' +require 'mocha/inspect' + +class InstanceOfTest < Test::Unit::TestCase + + include Mocha::ParameterMatchers + + def test_should_match_object_that_is_an_instance_of_specified_class + matcher = instance_of(String) + assert matcher.matches?(['string']) + end + + def test_should_not_match_object_that_is_not_an_instance_of_specified_class + matcher = instance_of(String) + assert !matcher.matches?([99]) + end + + def test_should_describe_matcher + matcher = instance_of(String) + assert_equal "instance_of(String)", matcher.mocha_inspect + end + +end diff --git a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/is_a_test.rb b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/is_a_test.rb new file mode 100644 index 0000000000..c9ef91965f --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/is_a_test.rb @@ -0,0 +1,25 @@ +require File.join(File.dirname(__FILE__), "..", "..", "test_helper") + +require 'mocha/parameter_matchers/is_a' +require 'mocha/inspect' + +class IsATest < Test::Unit::TestCase + + include Mocha::ParameterMatchers + + def test_should_match_object_that_is_a_specified_class + matcher = is_a(Integer) + assert matcher.matches?([99]) + end + + def test_should_not_match_object_that_is_not_a_specified_class + matcher = is_a(Integer) + assert !matcher.matches?(['string']) + end + + def test_should_describe_matcher + matcher = is_a(Integer) + assert_equal "is_a(Integer)", matcher.mocha_inspect + end + +end diff --git a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/kind_of_test.rb b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/kind_of_test.rb new file mode 100644 index 0000000000..1167e5c9e0 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/kind_of_test.rb @@ -0,0 +1,25 @@ +require File.join(File.dirname(__FILE__), "..", "..", "test_helper") + +require 'mocha/parameter_matchers/kind_of' +require 'mocha/inspect' + +class KindOfTest < Test::Unit::TestCase + + include Mocha::ParameterMatchers + + def test_should_match_object_that_is_a_kind_of_specified_class + matcher = kind_of(Integer) + assert matcher.matches?([99]) + end + + def test_should_not_match_object_that_is_not_a_kind_of_specified_class + matcher = kind_of(Integer) + assert !matcher.matches?(['string']) + end + + def test_should_describe_matcher + matcher = kind_of(Integer) + assert_equal "kind_of(Integer)", matcher.mocha_inspect + end + +end diff --git a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/not_test.rb b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/not_test.rb new file mode 100644 index 0000000000..4cb6790a9d --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/not_test.rb @@ -0,0 +1,26 @@ +require File.join(File.dirname(__FILE__), "..", "..", "test_helper") + +require 'mocha/parameter_matchers/not' +require 'mocha/inspect' +require 'stub_matcher' + +class NotTest < Test::Unit::TestCase + + include Mocha::ParameterMatchers + + def test_should_match_if_matcher_does_not_match + matcher = Not(Stub::Matcher.new(false)) + assert matcher.matches?(['any_old_value']) + end + + def test_should_not_match_if_matcher_does_match + matcher = Not(Stub::Matcher.new(true)) + assert !matcher.matches?(['any_old_value']) + end + + def test_should_describe_matcher + matcher = Not(Stub::Matcher.new(true)) + assert_equal 'Not(matcher(true))', matcher.mocha_inspect + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/regexp_matches_test.rb b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/regexp_matches_test.rb new file mode 100644 index 0000000000..a8294bfe05 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/regexp_matches_test.rb @@ -0,0 +1,25 @@ +require File.join(File.dirname(__FILE__), "..", "..", "test_helper") + +require 'mocha/parameter_matchers/regexp_matches' +require 'mocha/inspect' + +class MatchesTest < Test::Unit::TestCase + + include Mocha::ParameterMatchers + + def test_should_match_parameter_matching_regular_expression + matcher = regexp_matches(/oo/) + assert matcher.matches?(['foo']) + end + + def test_should_not_match_parameter_not_matching_regular_expression + matcher = regexp_matches(/oo/) + assert !matcher.matches?(['bar']) + end + + def test_should_describe_matcher + matcher = regexp_matches(/oo/) + assert_equal "regexp_matches(/oo/)", matcher.mocha_inspect + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/stub_matcher.rb b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/stub_matcher.rb new file mode 100644 index 0000000000..920ced23bd --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/stub_matcher.rb @@ -0,0 +1,23 @@ +module Stub + + class Matcher + + attr_accessor :value + + def initialize(matches) + @matches = matches + end + + def matches?(available_parameters) + value = available_parameters.shift + @value = value + @matches + end + + def mocha_inspect + "matcher(#{@matches})" + end + + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/parameters_matcher_test.rb b/vendor/gems/mocha-0.5.6/test/unit/parameters_matcher_test.rb new file mode 100644 index 0000000000..612805e454 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/unit/parameters_matcher_test.rb @@ -0,0 +1,121 @@ +require File.join(File.dirname(__FILE__), "..", "test_helper") +require 'mocha/parameters_matcher' + +class ParametersMatcherTest < Test::Unit::TestCase + + include Mocha + + def test_should_match_any_actual_parameters_if_no_expected_parameters_specified + parameters_matcher = ParametersMatcher.new + assert parameters_matcher.match?(actual_parameters = [1, 2, 3]) + end + + def test_should_match_if_actual_parameters_are_same_as_expected_parameters + parameters_matcher = ParametersMatcher.new(expected_parameters = [4, 5, 6]) + assert parameters_matcher.match?(actual_parameters = [4, 5, 6]) + end + + def test_should_not_match_if_actual_parameters_are_different_from_expected_parameters + parameters_matcher = ParametersMatcher.new(expected_parameters = [4, 5, 6]) + assert !parameters_matcher.match?(actual_parameters = [1, 2, 3]) + end + + def test_should_not_match_if_there_are_less_actual_parameters_than_expected_parameters + parameters_matcher = ParametersMatcher.new(expected_parameters = [4, 5, 6]) + assert !parameters_matcher.match?(actual_parameters = [4, 5]) + end + + def test_should_not_match_if_there_are_more_actual_parameters_than_expected_parameters + parameters_matcher = ParametersMatcher.new(expected_parameters = [4, 5]) + assert !parameters_matcher.match?(actual_parameters = [4, 5, 6]) + end + + def test_should_not_match_if_not_all_required_parameters_are_supplied + optionals = ParameterMatchers::Optionally.new(6, 7) + parameters_matcher = ParametersMatcher.new(expected_parameters = [4, 5, optionals]) + assert !parameters_matcher.match?(actual_parameters = [4]) + end + + def test_should_match_if_all_required_parameters_match_and_no_optional_parameters_are_supplied + optionals = ParameterMatchers::Optionally.new(6, 7) + parameters_matcher = ParametersMatcher.new(expected_parameters = [4, 5, optionals]) + assert parameters_matcher.match?(actual_parameters = [4, 5]) + end + + def test_should_match_if_all_required_and_optional_parameters_match_and_some_optional_parameters_are_supplied + optionals = ParameterMatchers::Optionally.new(6, 7) + parameters_matcher = ParametersMatcher.new(expected_parameters = [4, 5, optionals]) + assert parameters_matcher.match?(actual_parameters = [4, 5, 6]) + end + + def test_should_match_if_all_required_and_optional_parameters_match_and_all_optional_parameters_are_supplied + optionals = ParameterMatchers::Optionally.new(6, 7) + parameters_matcher = ParametersMatcher.new(expected_parameters = [4, 5, optionals]) + assert parameters_matcher.match?(actual_parameters = [4, 5, 6, 7]) + end + + def test_should_not_match_if_all_required_and_optional_parameters_match_but_too_many_optional_parameters_are_supplied + optionals = ParameterMatchers::Optionally.new(6, 7) + parameters_matcher = ParametersMatcher.new(expected_parameters = [4, 5, optionals]) + assert !parameters_matcher.match?(actual_parameters = [4, 5, 6, 7, 8]) + end + + def test_should_not_match_if_all_required_parameters_match_but_some_optional_parameters_do_not_match + optionals = ParameterMatchers::Optionally.new(6, 7) + parameters_matcher = ParametersMatcher.new(expected_parameters = [4, 5, optionals]) + assert !parameters_matcher.match?(actual_parameters = [4, 5, 6, 0]) + end + + def test_should_not_match_if_some_required_parameters_do_not_match_although_all_optional_parameters_do_match + optionals = ParameterMatchers::Optionally.new(6, 7) + parameters_matcher = ParametersMatcher.new(expected_parameters = [4, 5, optionals]) + assert !parameters_matcher.match?(actual_parameters = [4, 0, 6]) + end + + def test_should_not_match_if_all_required_parameters_match_but_no_optional_parameters_match + optionals = ParameterMatchers::Optionally.new(6, 7) + parameters_matcher = ParametersMatcher.new(expected_parameters = [4, 5, optionals]) + assert !parameters_matcher.match?(actual_parameters = [4, 5, 0, 0]) + end + + def test_should_match_if_actual_parameters_satisfy_matching_block + parameters_matcher = ParametersMatcher.new { |x, y| x + y == 3 } + assert parameters_matcher.match?(actual_parameters = [1, 2]) + end + + def test_should_not_match_if_actual_parameters_do_not_satisfy_matching_block + parameters_matcher = ParametersMatcher.new { |x, y| x + y == 3 } + assert !parameters_matcher.match?(actual_parameters = [2, 3]) + end + + def test_should_remove_outer_array_braces + params = [1, 2, [3, 4]] + parameters_matcher = ParametersMatcher.new(params) + assert_equal '(1, 2, [3, 4])', parameters_matcher.mocha_inspect + end + + def test_should_display_numeric_arguments_as_is + params = [1, 2, 3] + parameters_matcher = ParametersMatcher.new(params) + assert_equal '(1, 2, 3)', parameters_matcher.mocha_inspect + end + + def test_should_remove_curly_braces_if_hash_is_only_argument + params = [{:a => 1, :z => 2}] + parameters_matcher = ParametersMatcher.new(params) + assert_nil parameters_matcher.mocha_inspect.index('{') + assert_nil parameters_matcher.mocha_inspect.index('}') + end + + def test_should_not_remove_curly_braces_if_hash_is_not_the_only_argument + params = [1, {:a => 1}] + parameters_matcher = ParametersMatcher.new(params) + assert_equal '(1, {:a => 1})', parameters_matcher.mocha_inspect + end + + def test_should_indicate_that_matcher_will_match_any_actual_parameters + parameters_matcher = ParametersMatcher.new + assert_equal '(any_parameters)', parameters_matcher.mocha_inspect + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/return_values_test.rb b/vendor/gems/mocha-0.5.6/test/unit/return_values_test.rb new file mode 100644 index 0000000000..01ddfbcd5b --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/unit/return_values_test.rb @@ -0,0 +1,63 @@ +require File.join(File.dirname(__FILE__), "..", "test_helper") + +require 'mocha/return_values' + +class ReturnValuesTest < Test::Unit::TestCase + + include Mocha + + def test_should_return_nil + values = ReturnValues.new + assert_nil values.next + end + + def test_should_keep_returning_nil + values = ReturnValues.new + values.next + assert_nil values.next + assert_nil values.next + end + + def test_should_return_evaluated_single_return_value + values = ReturnValues.new(SingleReturnValue.new('value')) + assert_equal 'value', values.next + end + + def test_should_keep_returning_evaluated_single_return_value + values = ReturnValues.new(SingleReturnValue.new('value')) + values.next + assert_equal 'value', values.next + assert_equal 'value', values.next + end + + def test_should_return_consecutive_evaluated_single_return_values + values = ReturnValues.new(SingleReturnValue.new('value_1'), SingleReturnValue.new('value_2')) + assert_equal 'value_1', values.next + assert_equal 'value_2', values.next + end + + def test_should_keep_returning_last_of_consecutive_evaluated_single_return_values + values = ReturnValues.new(SingleReturnValue.new('value_1'), SingleReturnValue.new('value_2')) + values.next + values.next + assert_equal 'value_2', values.next + assert_equal 'value_2', values.next + end + + def test_should_build_single_return_values_for_each_values + values = ReturnValues.build('value_1', 'value_2', 'value_3').values + assert_equal 'value_1', values[0].evaluate + assert_equal 'value_2', values[1].evaluate + assert_equal 'value_3', values[2].evaluate + end + + def test_should_combine_two_sets_of_return_values + values_1 = ReturnValues.build('value_1') + values_2 = ReturnValues.build('value_2a', 'value_2b') + values = (values_1 + values_2).values + assert_equal 'value_1', values[0].evaluate + assert_equal 'value_2a', values[1].evaluate + assert_equal 'value_2b', values[2].evaluate + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/sequence_test.rb b/vendor/gems/mocha-0.5.6/test/unit/sequence_test.rb new file mode 100644 index 0000000000..544b3fe9c1 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/unit/sequence_test.rb @@ -0,0 +1,104 @@ +require File.join(File.dirname(__FILE__), "..", "test_helper") +require 'mocha/sequence' +require 'mocha/expectation' + +class SequenceTest < Test::Unit::TestCase + + include Mocha + + class FakeExpectation + + attr_reader :ordering_constraints + + def initialize(satisfied = false) + @satisfied = satisfied + @ordering_constraints = [] + end + + def add_ordering_constraint(ordering_constraint) + @ordering_constraints << ordering_constraint + end + + def satisfied? + @satisfied + end + + end + + def test_should_be_satisfied_if_no_expectations_added + sequence = Sequence.new('name') + assert sequence.satisfied_to_index?(0) + end + + def test_should_be_satisfied_if_one_unsatisfied_expectations_added_but_it_is_not_included_by_index + sequence = Sequence.new('name') + expectation = FakeExpectation.new(satisfied = false) + sequence.constrain_as_next_in_sequence(expectation) + assert sequence.satisfied_to_index?(0) + end + + def test_should_not_be_satisfied_if_one_unsatisfied_expectations_added_and_it_is_included_by_index + sequence = Sequence.new('name') + expectation = FakeExpectation.new(satisfied = false) + sequence.constrain_as_next_in_sequence(expectation) + assert !sequence.satisfied_to_index?(1) + end + + def test_should_be_satisfied_if_one_satisfied_expectations_added_and_it_is_included_by_index + sequence = Sequence.new('name') + expectation = FakeExpectation.new(satisfied = true) + sequence.constrain_as_next_in_sequence(expectation) + assert sequence.satisfied_to_index?(1) + end + + def test_should_not_be_satisfied_if_one_satisfied_and_one_unsatisfied_expectation_added_and_both_are_included_by_index + sequence = Sequence.new('name') + expectation_one = FakeExpectation.new(satisfied = true) + expectation_two = FakeExpectation.new(satisfied = false) + sequence.constrain_as_next_in_sequence(expectation_one) + sequence.constrain_as_next_in_sequence(expectation_two) + assert !sequence.satisfied_to_index?(2) + end + + def test_should_be_satisfied_if_two_satisfied_expectations_added_and_both_are_included_by_index + sequence = Sequence.new('name') + expectation_one = FakeExpectation.new(satisfied = true) + expectation_two = FakeExpectation.new(satisfied = true) + sequence.constrain_as_next_in_sequence(expectation_one) + sequence.constrain_as_next_in_sequence(expectation_two) + assert sequence.satisfied_to_index?(2) + end + + def test_should_add_ordering_constraint_to_expectation + sequence = Sequence.new('name') + expectation = FakeExpectation.new + sequence.constrain_as_next_in_sequence(expectation) + assert_equal 1, expectation.ordering_constraints.length + end + + def test_should_not_allow_invocation_of_second_method_when_first_n_sequence_has_not_been_invoked + sequence = Sequence.new('name') + expectation_one = FakeExpectation.new(satisfied = false) + expectation_two = FakeExpectation.new(satisfied = false) + sequence.constrain_as_next_in_sequence(expectation_one) + sequence.constrain_as_next_in_sequence(expectation_two) + assert !expectation_two.ordering_constraints[0].allows_invocation_now? + end + + def test_should_allow_invocation_of_second_method_when_first_in_sequence_has_been_invoked + sequence = Sequence.new('name') + expectation_one = FakeExpectation.new(satisfied = true) + expectation_two = FakeExpectation.new(satisfied = false) + sequence.constrain_as_next_in_sequence(expectation_one) + sequence.constrain_as_next_in_sequence(expectation_two) + assert expectation_two.ordering_constraints[0].allows_invocation_now? + end + + def test_should_describe_ordering_constraint_as_being_part_of_named_sequence + sequence = Sequence.new('wibble') + expectation = FakeExpectation.new + sequence.constrain_as_next_in_sequence(expectation) + assert_equal "in sequence 'wibble'", expectation.ordering_constraints[0].mocha_inspect + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/setup_and_teardown_test.rb b/vendor/gems/mocha-0.5.6/test/unit/setup_and_teardown_test.rb new file mode 100644 index 0000000000..83247888e1 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/unit/setup_and_teardown_test.rb @@ -0,0 +1,76 @@ +require File.join(File.dirname(__FILE__), "..", "test_helper") +require 'mocha/mock' + +require 'mocha/setup_and_teardown' + +class SetupAndTeardownTest < Test::Unit::TestCase + + include Mocha + + def test_should_instantiate_new_stubba + test_case = stubbed_test_case_class.new + test_case.setup_stubs + + assert $stubba + assert $stubba.is_a?(Mocha::Central) + end + + def test_should_verify_all_expectations + test_case = stubbed_test_case_class.new + stubba = Mock.new + stubba.expects(:verify_all) + $stubba = stubba + + test_case.verify_stubs + + stubba.verify + end + + def test_should_yield_to_block_for_each_assertion + test_case = stubbed_test_case_class.new + $stubba = Mock.new + $stubba.stubs(:verify_all).yields + yielded = false + + test_case.verify_stubs { yielded = true } + + assert_equal true, yielded + end + + def test_should_unstub_all_stubbed_methods + test_case = stubbed_test_case_class.new + stubba = Mock.new + stubba.expects(:unstub_all) + $stubba = stubba + + test_case.teardown_stubs + + stubba.verify + end + + def test_should_set_stubba_to_nil + test_case = stubbed_test_case_class.new + $stubba = Mock.new + $stubba.stubs(:unstub_all) + + test_case.teardown_stubs + + assert_nil $stubba + end + + def test_should_not_raise_exception_if_no_stubba_central_available + test_case = stubbed_test_case_class.new + $stubba = nil + assert_nothing_raised { test_case.teardown_stubs } + end + + private + + def stubbed_test_case_class + Class.new do + include Mocha::SetupAndTeardown + end + end + +end + diff --git a/vendor/gems/mocha-0.5.6/test/unit/single_return_value_test.rb b/vendor/gems/mocha-0.5.6/test/unit/single_return_value_test.rb new file mode 100644 index 0000000000..9d3d799b0d --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/unit/single_return_value_test.rb @@ -0,0 +1,33 @@ +require File.join(File.dirname(__FILE__), "..", "test_helper") + +require 'mocha/single_return_value' +require 'deprecation_disabler' + +class SingleReturnValueTest < Test::Unit::TestCase + + include Mocha + include DeprecationDisabler + + def test_should_return_value + value = SingleReturnValue.new('value') + assert_equal 'value', value.evaluate + end + + def test_should_return_result_of_calling_proc + proc = lambda { 'value' } + value = SingleReturnValue.new(proc) + result = nil + disable_deprecations { result = value.evaluate } + assert_equal 'value', result + end + + def test_should_indicate_deprecated_use_of_expectation_returns_method + proc = lambda {} + value = SingleReturnValue.new(proc) + Deprecation.messages = [] + disable_deprecations { value.evaluate } + expected_message = "use of Expectation#returns with instance of Proc - see Expectation#returns RDoc for alternatives" + assert_equal [expected_message], Deprecation.messages + end + +end diff --git a/vendor/gems/mocha-0.5.6/test/unit/single_yield_test.rb b/vendor/gems/mocha-0.5.6/test/unit/single_yield_test.rb new file mode 100644 index 0000000000..12bd0a2fd7 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/unit/single_yield_test.rb @@ -0,0 +1,18 @@ +require File.join(File.dirname(__FILE__), "..", "test_helper") + +require 'mocha/single_yield' + +class SingleYieldTest < Test::Unit::TestCase + + include Mocha + + def test_should_provide_parameters_for_single_yield_in_single_invocation + parameter_group = SingleYield.new(1, 2, 3) + parameter_groups = [] + parameter_group.each do |parameters| + parameter_groups << parameters + end + assert_equal [[1, 2, 3]], parameter_groups + end + +end diff --git a/vendor/gems/mocha-0.5.6/test/unit/string_inspect_test.rb b/vendor/gems/mocha-0.5.6/test/unit/string_inspect_test.rb new file mode 100644 index 0000000000..43b9c4e32c --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/unit/string_inspect_test.rb @@ -0,0 +1,11 @@ +require File.join(File.dirname(__FILE__), "..", "test_helper") +require 'mocha/inspect' + +class StringInspectTest < Test::Unit::TestCase + + def test_should_replace_escaped_quotes_with_single_quote + string = "my_string" + assert_equal "'my_string'", string.mocha_inspect + end + +end diff --git a/vendor/gems/mocha-0.5.6/test/unit/stub_test.rb b/vendor/gems/mocha-0.5.6/test/unit/stub_test.rb new file mode 100644 index 0000000000..a225963b97 --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/unit/stub_test.rb @@ -0,0 +1,24 @@ +require File.join(File.dirname(__FILE__), "..", "test_helper") +require 'mocha/stub' + +class StubTest < Test::Unit::TestCase + + include Mocha + + def test_should_always_verify_successfully + stub = Stub.new(nil, :expected_method) + assert stub.verify + stub.invoke + assert stub.verify + end + + def test_should_match_successfully_for_any_number_of_invocations + stub = Stub.new(nil, :expected_method) + assert stub.match?(:expected_method) + stub.invoke + assert stub.match?(:expected_method) + stub.invoke + assert stub.match?(:expected_method) + end + +end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/yield_parameters_test.rb b/vendor/gems/mocha-0.5.6/test/unit/yield_parameters_test.rb new file mode 100644 index 0000000000..4e93f1336d --- /dev/null +++ b/vendor/gems/mocha-0.5.6/test/unit/yield_parameters_test.rb @@ -0,0 +1,93 @@ +require File.join(File.dirname(__FILE__), "..", "test_helper") + +require 'mocha/yield_parameters' +require 'mocha/no_yields' +require 'mocha/single_yield' +require 'mocha/multiple_yields' + +class YieldParametersTest < Test::Unit::TestCase + + include Mocha + + def test_should_return_null_yield_parameter_group_by_default + yield_parameters = YieldParameters.new + assert yield_parameters.next_invocation.is_a?(NoYields) + end + + def test_should_return_single_yield_parameter_group + yield_parameters = YieldParameters.new + yield_parameters.add(1, 2, 3) + parameter_group = yield_parameters.next_invocation + assert parameter_group.is_a?(SingleYield) + assert_equal [1, 2, 3], parameter_group.parameters + end + + def test_should_keep_returning_single_yield_parameter_group + yield_parameters = YieldParameters.new + yield_parameters.add(1, 2, 3) + yield_parameters.next_invocation + parameter_group = yield_parameters.next_invocation + assert parameter_group.is_a?(SingleYield) + assert_equal [1, 2, 3], parameter_group.parameters + parameter_group = yield_parameters.next_invocation + assert parameter_group.is_a?(SingleYield) + assert_equal [1, 2, 3], parameter_group.parameters + end + + def test_should_return_consecutive_single_yield_parameter_groups + yield_parameters = YieldParameters.new + yield_parameters.add(1, 2, 3) + yield_parameters.add(4, 5) + parameter_group = yield_parameters.next_invocation + assert parameter_group.is_a?(SingleYield) + assert_equal [1, 2, 3], parameter_group.parameters + parameter_group = yield_parameters.next_invocation + assert parameter_group.is_a?(SingleYield) + assert_equal [4, 5], parameter_group.parameters + end + + def test_should_return_multiple_yield_parameter_group + yield_parameters = YieldParameters.new + yield_parameters.multiple_add([1, 2, 3], [4, 5]) + parameter_group = yield_parameters.next_invocation + assert parameter_group.is_a?(MultipleYields) + assert_equal [[1, 2, 3], [4, 5]], parameter_group.parameter_groups + end + + def test_should_keep_returning_multiple_yield_parameter_group + yield_parameters = YieldParameters.new + yield_parameters.multiple_add([1, 2, 3], [4, 5]) + yield_parameters.next_invocation + parameter_group = yield_parameters.next_invocation + assert parameter_group.is_a?(MultipleYields) + assert_equal [[1, 2, 3], [4, 5]], parameter_group.parameter_groups + parameter_group = yield_parameters.next_invocation + assert parameter_group.is_a?(MultipleYields) + assert_equal [[1, 2, 3], [4, 5]], parameter_group.parameter_groups + end + + def test_should_return_consecutive_multiple_yield_parameter_groups + yield_parameters = YieldParameters.new + yield_parameters.multiple_add([1, 2, 3], [4, 5]) + yield_parameters.multiple_add([6, 7], [8, 9, 0]) + parameter_group = yield_parameters.next_invocation + assert parameter_group.is_a?(MultipleYields) + assert_equal [[1, 2, 3], [4, 5]], parameter_group.parameter_groups + parameter_group = yield_parameters.next_invocation + assert parameter_group.is_a?(MultipleYields) + assert_equal [[6, 7], [8, 9, 0]], parameter_group.parameter_groups + end + + def test_should_return_consecutive_single_and_multiple_yield_parameter_groups + yield_parameters = YieldParameters.new + yield_parameters.add(1, 2, 3) + yield_parameters.multiple_add([4, 5, 6], [7, 8]) + parameter_group = yield_parameters.next_invocation + assert parameter_group.is_a?(SingleYield) + assert_equal [1, 2, 3], parameter_group.parameters + parameter_group = yield_parameters.next_invocation + assert parameter_group.is_a?(MultipleYields) + assert_equal [[4, 5, 6], [7, 8]], parameter_group.parameter_groups + end + +end \ No newline at end of file diff --git a/vendor/gems/rspec/CHANGES b/vendor/gems/rspec/CHANGES new file mode 100644 index 0000000000..887c9d8570 --- /dev/null +++ b/vendor/gems/rspec/CHANGES @@ -0,0 +1,1045 @@ +== Version 1.1.3 + +Maintenance release. + +* Tightened up exceptions list in autotest/rails_spec. Closes #264. +* Applied patch from Ryan Davis for ZenTest-3.9.0 compatibility +* Applied patch from Kero to add step_upcoming to story listeners. Closes #253. +* Fixed bug where the wrong named error was not always caught by "should raise_error" +* Applied patch from Luis Lavena: No coloured output on Windows due missing RUBYOPT. Closes #244. +* Applied patch from Craig Demyanovich to add support for "should_not render_template" to rspec_on_rails. Closes #241. +* Added --pattern (-p for short) option to control what files get loaded. Defaults to '**/*_spec.rb' +* Exit with non-0 exit code if examples *or tests* (in test/unit interop mode) fail. Closes #203. +* Moved at_exit hook to a method in Spec::Runner which only runs if specs get loaded. Closes #242. +* Applied patch from kakutani ensuring that base_view_path gets cleared after each view example. Closes #235. +* More tweaks to regexp step names +* Fixed focused specs in nested ExampleGroups. Closes #225. + +== Version 1.1.2 + +Minor bug fixes/enhancements. + +* RSpec's Autotest subclasses compatible with ZenTest-3.8.0 (thanks to Ryan Davis for making it easier on Autotest subs). +* Applied patch from idl to add spec/lib to rake stats. Closes #226. +* calling setup_fixtures and teardown_fixtures for Rails >= r8570. Closes #219. +* Applied patch from Josh Knowles using ActiveSupport's Inflector (when available) to make 'should have' read a bit better. Closes #197. +* Fixed regression in 1.1 that caused failing examples to fail to generate their own names. Closes #209. +* Applied doc patch from Jens Krämer for capturing content_for +* Applied patch from Alexander Lang to clean up story steps after each story. Closes #198. +* Applied patch from Josh Knowles to support 'string_or_response.should have_text(...)'. Closes #193. +* Applied patch from Ian Dees to quiet the Story Runner backtrace. Closes #183. +* Complete support for defining steps with regexp 'names'. + +== Version 1.1.1 + +Bug fix release. + +* Fix regression in 1.1.0 that caused transactions to not get rolled back between examples. +* Applied patch from Bob Cotton to reintroduce ExampleGroup.description_options. Closes LH[#186] + +== Version 1.1.0 + +The "tell me a story and go nest yourself" release. + +* Applied patch from Mike Vincent to handle generators rails > 2.0.1. Closes LH[#181] +* Formatter.pending signature changed so it gets passed an ExampleGroup instance instead of the name ( LH[#180]) +* Fixed LH[#180] Spec::Rails::Example::ModelExampleGroup and friends show up in rspec/rails output +* Spec::Rails no longer loads ActiveRecord extensions if it's disabled in config/boot.rb +* Applied LH[#178] small annoyances running specs with warnings enabled (Patch from Mikko Lehtonen) +* Tighter integration with Rails fixtures. Take advantage of fixture caching to get performance improvements (Thanks to Pat Maddox, Nick Kallen, Jonathan Barnes, and Curtis) + +== Version 1.1.0-RC1 + +Textmate Bundle users - this release adds a new RSpec bundle that highlights describe, it, before and after and +provides navigation to descriptions and examples (rather than classes and methods). When you first install this, +it is going to try to hijack all of your .rb files. All you need to do is open a .rb file that does not end with +'spec.rb' and change the bundle selection from RSpec to Ruby. TextMate will do the right thing from then on. + +Shortcuts for tab-activated snippets all follow the TextMate convention of 2 or 3 letters of the first word, followed by the first letter of each subsequent word. So "should have_at_least" would be triggered by shhal. + +We reduced the scope for running spec directories, files, a single file or individual spec in TextMate to source.ruby.rspec. This allowed us to restore the standard Ruby shortcuts: + +CMD-R runs all the specs in one file +CMD-SHIFT-R runs an individual spec +CMD-OPT-R runs any files or directories selected in the TextMate drawer + +rspec_on_rails users - don't forget to run script/generate rspec + +* Added shared_examples_for method, which you can (should) use instead of describe Foo, :shared => true +* Applied LH[#168] Fix describe Object, "description contains a # in it" (Patch from Martin Emde) +* Applied LH[#15] Reverse loading of ActionView::Base helper modules (Patch from Mark Van Holstyn) +* Applied LH[#149] Update contribute page to point towards lighthouse (Patch from Josh Knowles) +* Applied LH[#142] verify_rcov fails with latest rcov (Patch from Kyle Hargraves) +* Applied LH[#10] Allow stubs to yield and return values (Patch from Pat Maddox) +* Fixed LH[#139] version.rb in trunk missing svn last changed number +* Applied LH[#14] Adding support for by_at_least/by_at_most in Change matcher (Patch from Saimon Moore) +* Applied LH[#12] Fix for TM when switching to alternate file (Patch from Trevor Squires) +* Applied LH[#133] ExampleMatcher should match against before(:all) (Patch from Bob Cotton) +* Applied LH[#134] Only load spec inside spec_helper.rb (Patch from Mark Van Holstyn) +* RSpec now bails immediately if there are examples with identical names. +* Applied LH[#132] Plain Text stories should support Given and Given: (Patch from Jarkko Laine) +* Applied patch from Pat Maddox: Story Mediator - the glue that binds the plain text story parser with the rest of the system +* Applied LH[#16] Have SimpleMatchers expose their description for specdocs (Patch from Bryan Helmkamp) +* Stories now support --colour +* Changed the DSL modules to Example (i.e. Spec::Example instead of Spec::DSL) +* Applied [#15608] Story problem if parenthesis used in Given, When, Then or And (Patch from Sinclair Bain) +* Applied [#15659] GivenScenario fails when it is a RailsStory (Patch from Nathan Sutton) +* Fixed [#15639] rcov exclusion configuration. (Spec::Rails projects can configure rcov with spec/rcov.opts) +* The rdoc formatter (--format rdoc) is gone. It was buggy and noone was using it. +* Changed Spec::DSL::Behaviour to Spec::DSL::ExampleGroup +* Changed Spec::DSL::SharedBehaviour to Spec::DSL::SharedExampleGroup +* Applied [#14023] Small optimization for heavily proxied objects. (Patch from Ian Leitch) +* Applied [#13943] ProfileFormatter (Top 10 slowest examples) (Patch from Ian Leitch) +* Fixed [#15232] heckle is not working correctly in trunk (as of r2801) +* Applied [#14399] Show pending reasons in HTML report (Patch from Bryan Helmkamp) +* Discovered fixed: [#10263] mock "leak" when setting an expectation in a block passed to mock#should_receive +* Fixed [#14671] Spec::DSL::ExampleRunner gives "NO NAME because of --dry-run" for every example for 'rake spec:doc' +* Fixed [#14543] rspec_scaffold broken with Rails 2.0 +* Removed Patch [#10577] Rails with Oracle breaks 0.9.2 - was no longer necessary since we moved describe to the Main object (instead of Object) +* Fixed [#14527] specs run twice on rails 1.2.4 and rspec/rspec_on_rails trunk +* Applied [#14043] Change output ordering to show pending before errors (Patch from Mike Mangino) +* Applied [#14095] Don't have ./script/generate rspec create previous_failures.txt (Patch from Bryan Helmkamp) +* Applied [#14254] Improved error handling for Object#should and Object#should_not (Patch from Antti Tarvainen) +* Applied [#14186] Remove dead code from message_expecation.rb (Patch from Antti Tarvainen) +* Applied [#14183] Tiny improvement on mock_spec.rb (Patch from Antti Tarvainen) +* Applied [#14208] Fix to Mock#method_missing raising NameErrors instead of MockExpectationErrors (Patch from Antti Tarvainen) +* Applied [#14255] Fixed examples in mock_spec.rb and shared_behaviour_spec.rb (Patch from Antti Tarvainen) +* Applied [#14362] partially mocking objects that define == can blow up (Patch from Pat Maddox) +* test_ methods with an arity of 0 defined in a describe block or Example object will be run as an Example, providing a seamless transition from Test::Unit +* Removed BehaviourRunner +* Fixed [#13969] Spec Failures on Trunk w/ Autotest +* Applied [#14156] False positives with should_not (Patch from Antti Tarvainen) +* Applied [#14170] route_for and params_from internal specs fixed (Patch from Antti Tarvainen) +* Fixed [#14166] Cannot build trunk +* Applied [#14142] Fix for bug #11602: Nested #have_tag specifications fails on the wrong line number (Patch from Antti Tarvainen) +* Removed warn_if_no_files argument and feature +* Steps (Given/When/Then) with no blocks are treated as pending +* Applied [#13913] Scenario should treat no code block as pending (Patch from Evan Light) +* Fixed [#13370] Weird mock expectation error (Patch from Mike Mangino) +* Applied [#13952] Fix for performance regression introduced in r2096 (Patch from Ian Leitch) +* Applied [#13881] Dynamically include Helpers that are included on ActionView::Base (Patch from Brandon Keepers) +* Applied [#13833] ActionView::Helpers::JavaScriptMacrosHelper removed after 1.2.3 (Patch from Yurii Rashkovskii) +* Applied [#13814] RSpec on Rails w/ fixture-scenarios (Patch from Shintaro Kakutani) +* Add ability to define Example subclass instead of using describe +* Applied Patch from James Edward Gray II to improve syntax highlighting in TextMate +* Fixed [#13579] NoMethodError not raised for missing helper methods +* Fixed [#13713] form helper method 'select' can not be called when calling custom helper methods from specs +* Example subclasses Test::Unit::TestCase +* Added stub_everything method to create a stub that will return itself for any message it doesn't understand +* Added stories directory with stories/all.rb and stories/helper.rb when you script/generate rspec +* Applied [#13554] Add "And" so you can say Given... And... When... Then... And... +* Applied [#11254] RSpec syntax coloring and function pop-up integration in TextMate (Patch from Wincent Colaiuta) +* Applied [#13143] ActionView::Helpers::RecordIdentificationHelper should be included if present (Patch from Jay Levitt) +* Applied [#13567] patch to allow stubs to yield consecutive values (Patch from Rupert Voelcker) +* Applied [#13559] reverse version of route_for (Patch from Rupert Voelcker) +* Added [#13532] /lib specs should get base EvalContext +* Applied [#13451] Add a null_object option to mock_model (Patch from James Deville) +* Applied [#11919] Making non-implemented specs easy in textmate (Patch from Scott Taylor) +* Applied [#13274] ThrowSymbol recognized a NameError triggered by Kernel#method_missing as a thrown Symbol +* Applied [#12722] the alternate file command does not work in rails views due to scope (Patch from Carl Porth) +* Behaviour is now a Module that is used by Example class methods and SharedBehaviour +* Added ExampleDefinition +* Added story runner framework based on rbehave [#12628] +* Applied [#13336] Helper directory incorrect for rake stats in statsetup task (Patch from Curtis Miller) +* Applied [#13339] Add the ability for spec_parser to parse describes with :behaviour_type set (Patch from Will Leinweber and Dav Yaginuma) +* Fixed [#13271] incorrect behaviour with expect_render and stub_render +* Applied [#13129] Fix failing specs in spec_distributed (Patch from Bob Cotton) +* Applied [#13118] Rinda support for Spec::Distributed (Patch from Bob Cotton) +* Removed BehaviourEval +* Removed Behaviour#inherit +* Moved implementation of install_dependencies to example_rails_app +* Renamed RSPEC_DEPS to VENDOR_DEPS +* Added Example#not_implemented? +* You can now stub!(:msg).with(specific args) +* describe("A", Hash, "with one element") will generate description "A Hash with one element" (Tip from Ola Bini) +* Applied [#13016] [DOC] Point out that view specs render, well, a view (Patch from Jay Levitt) +* Applied [#13078] Develop rspec with autotest (Patch from Scott Taylor) +* Fixed [#13065] Named routes throw a NoMethodError in Helper specs (Patches from James Deville and Mike Mangino) +* Added (back) the verbose attribute in Spec::Rake::SpecTask +* Changed documentation to point at the new http svn URL, which is more accessible. + +== Version 1.0.8 + +Another bugfix release - this time to resolve the version mismatch + +== Version 1.0.7 + +Quick bugfix release to ensure that you don't have to have the rspec gem installed +in order to use autotest with rspec_on_rails. + +* Fixed [#13015] autotest gives failure in 'spec_command' after upgrade 1.0.5 to 1.0.6 + +== Version 1.0.6 + +The "holy cow, batman, it's been a long time since we released and there are a ton of bug +fixes, patches and even new features" release. + +Warning: Spec::Rails users: In fixing 11508, we've removed the raise_controller_errors method. As long as you +follow the upgrade instructions and run 'script/generate rspec' you'll be fine, but if you skip this +step you need to manually go into spec_helper.rb and remove the call to that method (if present - it +might not be if you haven't upgraded in a while). + +Warning: Implementors of custom formatters. Formatters will now be sent an Example object instead of just a +String for #example_started, #example_passed and #example_failed. In certain scenarios +(Spec::Ui with Spec::Distributed), the formatter must ask the Example for its sequence number instead of +keeping track of a sequence number internal to the formatter. Most of you shouldn't need to upgrade +your formatters though - the Example#to_s method returns the example name/description, so you should be +able to use the passed Example instance as if it were a String. + +* Applied [#12986] Autotest Specs + Refactoring (Patch from Scott Tayler) +* Added a #close method to formatters, which allows them to gracefully close streams. +* Applied [#12935] Remove requirement that mocha must be installed as a gem when used as mocking framework. (Patch from Ryan Kinderman). +* Fixed [#12893] RSpec's Autotest should work with rspec's trunk +* Fixed [#12865] Partial mock error when object has an @options instance var +* Applied [#12701] Allow checking of content captured with content_for in view specs (Patch from Jens Kr�mer) +* Applied [#12817] Cannot include same shared behaviour when required with absolute paths (Patch from Ian Leitch) +* Applied [#12719] rspec_on_rails should not include pagination helper (Patch from Matthijs Langenberg) +* Fixed [#12714] helper spec not finding rails core helpers +* Applied [#12611] should_not redirect_to implementation (Patch from Yurii Rashkovskii) +* Applied [#12682] Not correctly aliasing original 'stub!' and 'should_receive' methods for ApplicationController (Patch from Matthijs Langenberg) +* Disabled controller.should_receive(:render) and controller.stub!(:render). Use expect_render or stub_render instead. +* Applied [#12484] Allow a Behaviour's Description to flow through to the Formatter (Patch from Bob Cotton) +* Fixed [#12448] The spec:plugins rake task from rspec_on_rails should ignore specs from the rspec_on_rails plugin +* Applied [#12300] rr integration (patch from Kyle Hargraves) +* Implemented [#12284] mock_with :rr (integration with RR mock framework: http://rubyforge.org/projects/pivotalrb/) +* Applied [#12237] (tiny) added full path to mate in switch_command (Patch from Carl Porth) +* Formatters will now be sent an Example object instead of just a String for certain methods +* All Spec::Rake::SpecTask attributes can now be procs, which allows for lazy evaluation. +* Changed the Spec::Ui interfaces slightly. See examples. +* Applied [#12174] mishandling of paths with spaces in spec_mate switch_command (Patch from Carl Porth) +* Implemented [#8315] File "Go to..." functionality +* Applied [#11917] Cleaner Spec::Ui error for failed Selenium connection (Patch from Ian Dees) +* Applied [#11888] rspec_on_rails spews out warnings when assert_select is used with an XML response (Patch from Ian Leitch) +* Applied [#12010] Nicer failure message formatting (Patch from Wincent Colaiuta) +* Applied [#12156] smooth open mate patch (Patch from Ienaga Eiji) +* Applied [#10577] Rails with Oracle breaks 0.9.2. (Patch from Sinclair Bain) +* Fixed [#12079] auto-generated example name incomplete: should have 1 error on ....] +* Applied [#12066] Docfix for mocks/mocks.page (Patch from Kyle Hargraves) +* Fixed [#11891] script/generate rspec_controller fails to create appropriate views (from templates) on edge rails +* Applied [#11921] Adds the correct controller_name from derived_controller_name() to the ViewExampleGroupController (Patch from Eloy Duran) +* Fixed [#11903] config.include with behaviour_type 'hash' does not work +* Examples without blocks and pending is now reported with a P instead of a * +* Pending blocks that now pass are rendered blue +* New behaviour for after: If an after block raises an error, the other ones will still run instead of bailing at the first. +* Made it possible to run spec from RSpec.tmbundle with --drb against a Rails spec_server. +* Applied [#11868] Add ability for pending to optionally hold a failing block and to fail when it passes (Patch from Bob Cotton) +* Fixed [#11843] watir_behaviour missing from spec_ui gem +* Added 'switch between source and spec file' command in Spec::Mate (based on code from Ruy Asan) +* Applied [#11509] Documentation - RSpec requires hpricot +* Applied [#11807] Daemonize spec_server and rake tasks to manage them. (patch from Kyosuke MOROHASHI) +* Added pending(message) method +* Fixed [#11777] should render_template doesn't check paths correctly +* Fixed [#11749] Use of 'rescue => e' does not catch all exceptions +* Fixed [#11793] should raise_error('with a message') does not work correctly +* Fixed [#11774] Mocks should respond to :kind_of? in the same way they respond to :is_a? +* Fixed [#11508] Exceptions are not raised for Controller Specs (removed experimental raise_controller_errors) +* Applied [#11615] Partial mock methods give ambiguous failures when given a method name as a String (Patch from Jay Phillips) +* Fixed [#11545] Rspec doesn't handle should_receive on ActiveRecord associations (Patch from Ian White) +* Fixed [#11514] configuration.use_transactional_fixtures is ALWAYS true, regardless of assignment +* Improved generated RESTful controller examples to cover both successful and unsuccessful POST and PUT +* Changed TextMate snippets for controllers to pass controller class names to #describe rather than controller_name. +* Changed TextMate snippets for mocks to use no_args() and any_args() instead of the deprecated Symbols. +* Applied [#11500] Documentation: no rails integration specs in 1.0 +* Renamed SpecMate's shortcuts for running all examples and focused examples to avoid conflicts (CMD-d and CMD-i) +* Added a TextMate snippet for custom matchers, lifted from Geoffrey Grosenbach's RSpec peepcode show. +* The translator translates mock constraints to the new matchers that were introduced in 1.0.4 +* Documented environment variables for Spec::Rake::SpecTask. Renamed SPECOPTS and RCOVOPTS to SPEC_OPTS and RCOV_OPTS. +* Fixed [#10534] Windows: undefined method 'controller_name' + +== Version 1.0.5 +Bug fixes. Autotest plugin tweaks. + +* Fixed [#11378] fix to 10814 broke drb (re-opened #10814) +* Fixed [#11223] Unable to access flash from rails helper specs +* Fixed [#11337] autotest runs specs redundantly +* Fixed [#11258] windows: autotest won't run +* Applied [#11253] Tweaks to autotest file mappings (Patch from Wincent Colaiuta) +* Applied [#11252] Should be able to re-load file containing shared behaviours without raising an exception (Patch from Wincent Colaiuta) +* Fixed [#11247] standalone autotest doesn't work because of unneeded autotest.rb +* Applied [#11221] Autotest support does not work w/o Rails Gem installed (Patch from Josh Knowles) + +== Version 1.0.4 +The getting ready for JRuby release. + +* Fixed [#11181] behaviour_type scoping of config.before(:each) is not working +* added mock argument constraint matchers (anything(), boolean(), an_instance_of(Type)) which work with rspec or mocha +* added mock argument constraint matchers (any_args(), no_args()) which only work with rspec +* deprecated rspec's symbol mock argument constraint matchers (:any_args, :no_args, :anything, :boolean, :numeric, :string) +* Added tarball of rspec_on_rails to the release build to support folks working behind a firewall that blocks svn access. +* Fixed [#11137] rspec incorrectly handles flash after resetting the session +* Fixed [#11143] Views code for ActionController::Base#render broke between 1.0.0 and 1.0.3 on Rails Edge r6731 +* Added raise_controller_errors for controller examples in Spec::Rails + +== Version 1.0.3 +Bug fixes. + +* Fixed [#11104] Website uses old specify notation +* Applied [#11101] StringHelpers.starts_with?(prefix) assumes a string parameter for _prefix_ +* Removed 'rescue nil' which was hiding errors in controller examples. +* Fixed [#11075] controller specs fail when using mocha without integrated_views +* Fixed problem with redirect_to failing incorrectly against edge rails. +* Fixed [#11082] RspecResourceGenerator should be RspecScaffoldGenerator +* Fixed [#10959] Focused Examples do not work for Behaviour defined with constant with modules + +== Version 1.0.2 +This is just to align the version numbers in rspec and rspec_on_rails. + +== Version 1.0.1 +This is a maintenance release with mostly cleaning up, and one minor enhancement - +Modules are automatically included when described directly. + +* Renamed Spec::Rails' rspec_resource generator to rspec_scaffold. +* Removed Spec::Rails' be_feed matcher since it's based on assert_select_feed which is not part of Rails (despite that docs for assert_select_encoded says it is). +* describe(SomeModule) will include that module in the examples. Like for Spec::Rails helpers, but now also in core. +* Header in HTML report will be yellow instead of red if there is one failed example +* Applied [#10951] Odd instance variable name in rspec_model template (patch from Kyle Hargraves) +* Improved integration with autotest (Patches from Ryan Davis and David Goodland) +* Some small fixes to make all specs run on JRuby. + +== Version 1.0.0 +The stake in the ground release. This represents a commitment to the API as it is. No significant +backwards compatibility changes in the API are expected after this release. + +* Fixed [#10923] have_text matcher does not support should_not +* Fixed [#10673] should > and should >= broken +* Applied [#10921] Allow verify_rcov to accept greater than threshold coverage %'s via configuration +* Applied [#10920] Added support for not implemented examples (Patch from Chad Humphries and Ken Barker) +* Patch to allow not implemented examples. This works by not providing a block to the example. (Patch from Chad Humphries, Ken Barker) +* Yanked support for Rails 1.1.6 in Spec::Rails +* RSpec.tmbundle uses CMD-SHIFT-R to run focused examples now. +* Spec::Rails now bundles a spec:rcov task by default (suggestion from Kurt Schrader) +* Fixed [#10814] Runner loads shared code, test cases require them again +* Fixed [#10753] Global before and after +* Fixed [#10774] Allow before and after to be specified in config II +* Refactored Spec::Ui examples to use new global before and after blocks. +* Added instructions about how to get Selenium working with Spec::Ui (spec_ui/examples/selenium/README.txt) +* Fixed [#10805] selenium.rb missing from gem? +* Added rdocs explaining how to deal with errors in Rails' controller actions +* Applied [#10770] Finer grained includes. +* Fixed [#10747] Helper methods defined in shared specs are not visible when shared spec is used +* Fixed [#10748] Shared descriptions in separate files causes 'already exists' error +* Applied [#10698] Running with --drb executes specs twice (patch from Ruy Asan) +* Fixed [#10871] 0.9.4 - Focussed spec runner fails to run specs in descriptions with type and string when there is no leading space in the string + +== Version 0.9.4 +This release introduces massive improvements to Spec::Ui - the user interface functional testing +extension to RSpec. There are also some minor bug fixes to the RSpec core. + +* Massive improvements to Spec::Ui. Complete support for all Watir's ie.xxx(how, what) methods. Inline screenshots and HTML. +* Reactivated --timeout, which had mysteriously been deactivated in a recent release. +* Fixed [#10669] Kernel#describe override does not cover Kernel#context +* Applied [#10636] Added spec for OptionParser in Runner (Patch from Scott Taylor) +* Added [#10516] should_include should be able to accept multiple items +* Applied [#10631] redirect_to matcher doesn't respect request.host (Patch from Tim Lucas) +* Each formatter now flushes their own IO. This is to avoid buffering of output. +* Fixed [#10670] IVarProxy#delete raises exception when instance variable does not exist + +== Version 0.9.3 +This is a bugfix release. + +* Fixed [#10594] Failing Custom Matcher show NAME NOT GENERATED description +* describe(SomeType, "#message") will not add a space: "SomeType#message" (likewise for '.') +* describe(SomeType, "message") will have a decription with a space: "SomeType message" +* Applied [#10566] prepend_before and prepend_after callbacks +* Applied [#10567] Call setup and teardown using before and after callbacks + +== Version 0.9.2 +This is a quick maintenance release. + +* Added some website love +* Fixed [#10542] reverse predicate matcher syntax +* Added a spec:translate Rake task to make 0.9 translation easier with Spec:Rails +* Better translation of should_redirect_to +* Fixed --colour support for Windows. This is a regression that was introduced in 0.9.1 +* Applied [#10460] Make SpecRunner easier to instantiate without using commandline args + +== Version 0.9.1 + +This release introduces #describe and #it (aliased as #context and #specify for +backwards compatibility). This allows you to express specs like this: + + describe SomeClass do # Creates a Behaviour + it "should do something" do # Creates an Example + end + end + +The command line features four new options that give you more control over what specs +are being run and in what order. This can be used to verify that your specs are +independent (by running in opposite order with --reverse). It can also be used to cut +down feedback time by running the most recently modified specs first (--loadby mtime --reverse). + +Further, --example replaces the old --spec option, and it can now take a file name of +spec names as an alternative to just a spec name. The --format failing_examples:file.txt +option allows you to output an --example compatible file, which makes it possible to only +rerun the specs that failed in the last run. Spec::Rails uses all of these four options +by default to optimise your RSpec experience. + +There is now a simple configuration model. For Spec::Rails, you do something like this: + + Spec::Runner.configure do |config| + config.use_transactional_fixtures = true + config.use_instantiated_fixtures = false + config.fixture_path = RAILS_ROOT + '/spec/fixtures' + end + +You can now use mocha or flexmock with RSpec if you prefer either to +RSpec's own mock framework. Just put this: + + Spec::Runner.configure do |config| + config.mock_with :mocha + end + +or this: + + Spec::Runner.configure do |config| + config.mock_with :flexmock + end + +in a file that is loaded before your specs. You can also +configure included modules and predicate_matchers: + + Spec::Runner.configure do |config| + config.include SomeModule + config.predicate_matchers[:does_something?] = :do_something + end + +See Spec::DSL::Behaviour for more on predicate_matchers + +* Sugar FREE! +* Added [10434 ] Please Make -s synonymous with -e for autotest compat. This is temporary until autotest uses -e instead of -s. +* Fixed [#10133] custom predicate matchers +* Applied [#10473] Add should exist (new matcher) - Patch from Bret Pettichord +* Added another formatter: failing_behaviours. Writes the names of the failing behaviours for use with --example. +* Applied [#10315] Patch to fix pre_commit bug 10313 - pre_commit_rails: doesn't always build correctly (Patch from Antii Tarvainen) +* Applied [#10245] Patch to HTML escape the behavior name when using HTML Formatter (Patch from Josh Knowles) +* Applied [#10410] redirect_to does not behave consistently with regards to query string parameter ordering (Patch from Nicholas Evans) +* Applied [#9605] Patch for ER 9472, shared behaviour (Patch by Bob Cotton) +* The '--format rdoc' option no longer causes a dry-run by default. --dry-run must be used explicitly. +* It's possible to specify the output file in the --format option (See explanation in --help) +* Several --format options may be specified to output several formats in one run. +* The --out option is gone. Use --format html:path/to/my.html instead (or similar). +* Spec::Runner::Formatter::BaseTextFormatter#initialize only takes one argument - an IO. dry_run and color are setters. +* Made Spec::Ui *much* easier to install. It will be released separately. Check out trunk/spec_ui/examples +* HTML reports now include a syntax highlighted snippet of the source code where the spec failed (needs the syntax gem) +* Added [#10262] Better Helper testing of Erb evaluation block helpers +* Added [#9735] support flexmock (thanks to Jim Weirich for his modifications to flexmock to support this) +* Spec::Rails controller specs will no longer let mock exception ripple through to the response. +* Fixed [#9260] IvarProxy does not act like a hash. +* Applied [#9458] The rspec_scaffold generator does not take into account class nesting (Patch from Steve Tendon) +* Applied [#9132] Rakefile spec:doc can fail without preparing database (Patch from Steve Ross) +* Applied [#9678] Custom runner command line switch, and multi-threaded runner (Patch from Bob Cotton) +* Applied [#9926] Rakefile - RSPEC_DEPS constant as an Array of Hashes instead of an Array of Arrays (Patch from Scott Taylor) +* Applied [#9925] Changed ".rhtml" to "template" in REST spec generator (Patch from Scott Taylor) +* Applied [#9852] Patch for RSpec's Website using Webgen 0.4.2 (Patch from Scott Taylor) +* Fixed [#6523] Run rspec on rails without a db +* Fixed [#9295] rake spec should run anything in the spec directory (not just rspec's standard dirs) +* Added [#9786] infer controller and helper names from the described type +* Fixed [#7795] form_tag renders action='http://github.com/view_spec' in view specs +* Fixed [#9767] rspec_on_rails should not define rescue_action on controllers +* Fixed [#9421] --line doesn't work with behaviours that use class names +* Fixed [#9760] rspec generators incompatible with changes to edge rails +* Added [#9786] infer controller and helper names from the described type +* Applied a simplified version of [#9282] Change to allow running specs from textmate with rspec installed as a rails plugin (and no rspec gem installed) +* Applied [#9700] Make Spec::DSL::Example#name public / Add a --timeout switch. A great way to prevent specs from getting slow. +* In Rails, script/generate rspec will generate a spec.opts file that optimises faster/more efficient running of specs. +* Added [#9522] support using rspec's expectations with test/unit +* Moved rspec_on_rails up to the project root, simplifying the download url +* Fixed [#8103] RSpec not installing spec script correctly. +* The --spec option is replaced by the --example option. +* The --loadby option no longer supports a file argument. Use --example file_name instead. +* The --example option can now take a file name as an argument. The file should contain example names. +* Internal classes are named Behaviour/Example (rather than Context/Specification). +* You can now use mocha by saying config.mock_with :mocha in a spec_helper +* before_context_eval is replaced by before_eval. +* Applied [#9509] allow spaced options in spec.opts +* Applied [#9510] Added File for Ruby 1.8.6 +* Applied [#9511] Clarification to README file in spec/ +* Moved all of the Spec::Rails specs down to the plugins directory - now you can run the specs after you install. +* Updated RSpec.tmbundle to the 0.9 syntax and replaced context/specify with describe/it. +* Applied [#9232] ActionController::Base#render is sometimes protected (patch from Dan Manges) +* Added --reverse option, allowing contexts/specs to be run in reverse order. +* Added --loadby option, allowing better control over load order for spec files. mtime and file.txt supported. +* Implemented [#8696] --order option (see --reverse and --loadby) +* Added describe/it as aliases for context/specify - suggestion from Dan North. +* Applied [#7637] [PATCH] add skip-migration option to rspec_scaffold generator +* Added [#9167] string.should have_tag +* Changed script/rails_spec_server to script/spec_server and added script/spec (w/ path to vendor/plugins/rspec) +* Fixed [#8897] Error when mixing controller spec with/without integrated views and using template system other than rhtml +* Updated sample app specs to 0.9 syntax +* Updated generated specs to 0.9 syntax +* Applied [#8994] trunk: generated names for be_ specs (Multiple patches from Yurii Rashkovskii) +* Applied [#9983]: Allow before and after to be called in BehaviourEval. This is useful for shared examples. + +== Version 0.8.2 + +Replaced assert_select fork with an assert_select wrapper for have_tag. This means that "should have_rjs" no longer supports :hide or :effect, but you can still use should_have_rjs for those. + +== Version 0.8.1 + +Quick "in house" bug-fix + +== Version 0.8.0 + +This release introduces a new approach to handling expectations using Expression Matchers. + +See Upgrade[http://rspec.rubyforge.org/upgrade.html], Spec::Expectations, Spec::Matchers and RELEASE-PLAN for more info. + +This release also improves the spec command line by adding DRb support and making it possible to +store command line options in a file. This means a more flexible RSpec experience with Rails, +Rake and editor plugins like TextMate. + +It also sports myriad new features, bug fixes, patches and general goodness: + +* Fixed [#8928] rspec_on_rails 0.8.0-RC1 controller tests make double call to setup_with_fixtures +* Fixed [#8925] Documentation bug in 0.8.0RC1 rspec website +* Applied [#8132] [PATCH] RSpec breaks "rake db:sessions:create" in a rails project that has the rspec_on_rails plugin (Patch from Erik Kastner) +* Fixed [#8789] --line and --spec not working when the context has parenhesis in the name +* Added [#8783] auto generate spec names from last expectation +* --heckle now fails if the heckled class or module is not found. +* Fixed [#8771] Spec::Mocks::BaseExpectation#with converts hash params to array of arrays with #collect +* Fixed [#8750] should[_not]_include backwards compatibility between 0.8.0-RC1 and 0.7.5.1 broken +* Fixed [#8646] Context Runner does not report on Non standard exceptions and return a 0 return code +* RSpec on Rails' spec_helper.rb will only force RAILS_ENV to test if it was not specified on the command line. +* Fixed [#5485] proc#should_raise and proc#should_not_raise output +* Added [#8484] should_receive with blocks +* Applied [#8218] heckle_runner.rb doesn't work with heckle >= 1.2.0 (Patch from Michal Kwiatkowski) +* Fixed [#8240] Cryptic error message when no controller_name +* Applied [#7461] [PATCH] Contexts don't call Module::included when they include a module +* Removed unintended block of test/unit assertions in rspec_on_rails - they should all, in theory, now be accessible +* Added mock_model method to RSpec on Rails, which stubs common methods. Based on http://metaclass.org/2006/12/22/making-a-mockery-of-activerecord +* Fixed [#8165] Partial Mock Errors when respond_to? is true but the method is not in the object +* Fixed [#7611] Partial Mocks override Subclass methods +* Fixed [#8302] Strange side effect when mocking a class method +* Applied [#8316] to_param should return a stringified key in resource generator's controller spec (Patch from Chris Anderson) +* Applied [#8216] shortcut for creating object stub +* Applied [#8008] Correct generated specs for view when calling resource generator (Patch from Jonathan Tron) +* Fixed [#7754] Command-R fails to run spec in TextMate (added instruction from Luke Redpath to the website) +* Fixed [#7826] RSpect.tmbundle web page out of date. +* RSpec on Rails specs are now running against RoR 1.2.1 and 1.2.2 +* rspec_scaffold now generates specs for views +* In a Rails app, RSpec core is only loaded when RAILS_ENV==test (init.rb) +* Added support for target.should arbitrary_expectation_handler and target.should_not arbitrary_expectation_handler +* Fixed [#7533] Spec suite fails and the process exits with a code 0 +* Fixed [#7565] Subsequent stub! calls for method fail to override the first call to method +* Applied [#7524] Incorrect Documentation for 'pattern' in Rake task (patch from Stephen Duncan) +* Fixed [#7409] default fixtures do not appear to run. +* Fixed [#7507] "render..and return" doesn't return +* Fixed [#7509] rcov/rspec incorrectly includes boot.rb (Patch from Courtenay) +* Fixed [#7506] unnecessary complex output on failure of response.should be_redirect +* Applied [#6098] Make scaffold_resource generator. Based on code from Pat Maddox. +* The drbspec command is gone. Use spec --drb instead. +* The drb option is gone from the Rake task. Pass --drb to spec_opts instead. +* New -X/--drb option for running specs against a server like spec/rails' script/rails_spec_server +* New -O/--options and -G/--generate flags for file-based options (handy for spec/rails) +* Applied [#7339] Turn off caching in HTML reports +* Applied [#7419] "c option for colorizing output does not work with rails_spec" (Patch from Shintaro Kakutani) +* Applied [#7406] [PATCH] 0.7.5 rspec_on_rails loads fixtures into development database (Patch from Wilson Bilkovich) +* Applied [#7387] Allow stubs to return consecutive values (Patch from Pat Maddox) +* Applied [#7393] Fix for rake task (Patch from Pat Maddox) +* Reinstated support for response.should_render (in addition to controller.should_render) + +== Version 0.7.5.1 + +Bug fix release to allow downloads of rspec gem using rubygems 0.9.1. + +== Version 0.7.5 +This release adds support for Heckle - Seattle'rb's code mutation tool. +There are also several bug fixes to the RSpec core and the RSpec on Rails plugin. + +* Removed svn:externals on rails versions and plugins +* Applied [#7345] Adding context_setup and context_teardown, with specs and 100% rcov +* Applied [#7320] [PATCH] Allow XHR requests in controller specs to render RJS templates +* Applied [#7319] Migration code uses drop_column when it should use remove_column (patch from Pat Maddox) +* Added support for Heckle +* Applied [#7282] dump results even if spec is interrupted (patch from Kouhei Sutou) +* Applied [#7277] model.should_have(n).errors_on(:attribute) (patch from Wilson Bilkovich) +* Applied [#7270] RSpec render_partial colliding with simply_helpful (patch from David Goodlad) +* Added [#7250] stubs should support throwing +* Added [#7249] stubs should support yielding +* Fixed [#6760] fatal error when accessing nested finders in rspec +* Fixed [#7179] script/generate rspec_scaffold generates incorrect helper name +* Added preliminary support for assert_select (response.should_have) +* Fixed [#6971] and_yield does not work when the arity is -1 +* Fixed [#6898] Can we separate rspec from the plugins? +* Added [#7025] should_change should accept a block +* Applied [#6989] partials with locals (patch from Micah Martin) +* Applied [#7023] Typo in team.page + +== Version 0.7.4 + +This release features a complete redesign of the reports generated with --format html. +As usual there are many bug fixes - mostly related to spec/rails. + +* Applied [#7010] Fixes :spacer_template does not work w/ view spec (patch from Shintaro Kakutani) +* Applied [#6798] ensure two ':' in the first backtrace line for Emacs's 'next-error' command (patch from Kouhei Sutou) +* Added Much nicer reports to generated website +* Much nicer reports with --format --html (patch from Luke Redpath) +* Applied [#6959] Calls to render and redirect in controllers should return true +* Fixed [#6981] helper method is not available in partial template. +* Added [#6978] mock should tell you the expected and actual args when receiving the right message with the wrong args +* Added the possibility to tweak the output of the HtmlFormatter (by overriding extra_failure_content). +* Fixed [#6936] View specs don't include ApplicationHelper by default +* Fixed [#6903] Rendering a partial in a view makes the view spec blow up +* Added callback library from Brian Takita +* Added [#6925] support controller.should_render :action_name +* Fixed [#6884] intermittent errors related to method binding +* Fixed [#6870] rspec on edge rails spec:controller fixture loading fails +* Using obj.inspect for all messages +* Improved performance by getting rid of instance_exec (instance_eval is good enough because we never need to pass it args) + +== Version 0.7.3 + +Almost normal bug fix/new feature release. + +A couple of things you need to change in your rails specs: +# spec_helper.rb is a little different (see http://rspec.rubyforge.org/upgrade.html) +# use controller.should_render before OR after the action (controller.should_have_rendered is deprecated) + +* Applied [#6577] messy mock backtrace when frozen to edge rails (patch from Jay Levitt) +* Fixed [#6674] rspec_on_rails fails on @session deprecation warning +* Fixed [#6780] routing() was failing...fix included - works for 1.1.6 and edge (1.2) +* Fixed [#6835] bad message with arbitrary predicate +* Added [#6731] Partial templates rendered +* Fixed [#6713] helper methods not rendered in view tests? +* Fixed [#6707] cannot run controller / helper tests via rails_spec or spec only works with rake +* Applied [#6417] lambda {...}.should_change(receiver, :message) (patch from Wilson Bilkovich) +* Eliminated dependency on ZenTest +* Fixed [#6650] Reserved characters in the TextMate bundle break svn on Win32 +* Fixed [#6643] script/generate rspec_controller: invalid symbol generation for 'controller_name' for *modularized* controllers +* The script/rails_spec command has been moved to bin/drbspec in RSpec core (installed by the gem) + +== Version 0.7.2 + +This release introduces a brand new RSpec bundle for TextMate, plus some small bugfixes. + +* Packaged RSpec.tmbundle.tgz as part of the distro +* Fixed [#6593] Add moving progress bar to HtmlFormatter using Javascript +* Applied [#6265] should_raise should accept an Exception object +* Fixed [#6616] Can't run Rails specs with RSpec.tmbundle +* Fixed [#6411] Can't run Rails specs with ruby +* Added [#6589] New -l --line option. This is useful for IDE/editor runners/extensions. +* Fixed [#6615] controller.should_render_rjs should support :partial => 'path/to/template' + +== Version 0.7.1 + +Bug fixes and a couple o' new features. + +* Fixed [#6575] Parse error in aliasing the partial mock original method (patch by Brian Takita) +* Fixed [#6277] debris left by stubbing (trunk) [submitted by dastels] (fixed by fix to [#6575]) +* Fixed [#6575] Parse error in aliasing the partial mock original method +* Fixed [#6555] should_have_tag does not match documentation +* Fixed [#6567] SyntaxError should not stop entire run +* Fixed [#6558] integrated views look for template even when redirected +* Fixed [#6547] response.should be_redirect broken in 0.7.0 +* Applied [#6471] Easy way to spec routes +* Applied [#6587] Rspec on Rails displays "Spec::Rails::ContextFactory" as context name +* Applied [#6514] Document has trivial typos. +* Added [#6560] controller.session should be available before the action +* Added support for should_have_rjs :visual_effect +* Different printing and colours for unmet expectations (red) and other exceptions (magenta) +* Simplified method_missing on mock_methods to make it less invasive on partial mocks. + +== Version 0.7.0 + +This is the "Grow up and eat your own dog food release". RSpec is now used on itself and +we're no longer using Test::Unit to test it. Although, we are still extending Test::Unit +for the rails plugin (indirectly - through ZenTest) + +IMPORTANT NOTE: THIS RELEASE IS NOT 100% BACKWARDS COMPATIBLE TO 0.6.x + +There are a few changes that will require that you change your existing specs. + +RSpec now handles equality exactly like ruby does: + +# actual.should_equal(expected) will pass if actual.equal?(expected) returns true +# actual.should eql(expected) will pass if actual.eql?(expected) returns true +# actual.should == expected will pass if actual == expected) returns true + +At the high level, eql? implies equivalence, while equal? implies object identity. For more +information on how ruby deals w/ equality, you should do this: + +ri equal? + +or look at this: + +http://www.ruby-doc.org/core/classes/Object.html#M001057 + +Also, we left in should_be as a synonym for should_equal, so the only specs that should break are the +ones using should_equal (which used to use == instead of .equal?). + +Lastly, should_be used to handle true and false differently from any other values. We've removed +this special handling, so now actual.should_be true will fail for any value other than true (it +used to pass for any non-nil, non-false value), and actual.should_be false will fail for any +value other than false (it used to pass for nil or false). + +Here's what you'll need to do to update your specs: +# search for "should_equal" and replace with "should_eql" +# run specs + +If any specs still fail, they are probably related to should be_true or should_be_false using +non-boolean values. Those you'll just have to inspect manually and adjust appropriately (sorry!). + +-------------------------------------------------- +Specifying multiple return values in mocks now works like this: + +mock.should_receive(:message).and_return(1,2,3) + +It used to work like this: + +mock.should_receive(:message).and_return([1,2,3]) + +but we decided that was counter intuitive and otherwise lame. + +Here's what you'll need to do to update your specs: +# search for "and_return([" +# get rid of the "[" and "]" + +-------------------------------------------------- +RSpec on Rails now supports the following (thanks to ZenTest upon which it is built): + +# Separate specs for models, views, controllers and helpers +# Controller specs are completely decoupled from the views by default (though you can tell them to couple themselves if you prefer) +# View specs are completely decoupled from app-specific controllers + +See http://rspec.rubyforge.org/documentation/rails/index.html for more information +-------------------------------------------------- +As usual, there are also other new features and bug fixes: + +* Added lots of documentation on mocks/stubs and the rails plugin. +* Added support for assigns[key] syntax for controller specs (to align w/ pre-existing syntax for view specs) +* Added support for controller.should_redirect_to +* RSpec on Rails automatically checks whether it's compatible with the installed RSpec +* Applied [#6393] rspec_on_rails uses deprecated '@response' instead of the accessor +* RSpec now has 100% spec coverage(!) +* Added support for stubbing and partial mocking +* Progress (....F..F.) is now coloured. Tweaked patch from KAKUTANI Shintaro. +* Backtrace now excludes the rcov runner (/usr/local/bin/rcov) +* Fixed [#5539] predicates do not work w/ rails +* Added [#6091] support for Regexp matching messages sent to should_raise +* Added [#6333] support for Regexp matching in mock arguments +* Applied [#6283] refactoring of diff support to allow selectable formats and custom differs +* Fixed [#5564] "ruby spec_file.rb" doesn't work the same way as "spec spec_file.rb" +* Fixed [#6056] Multiple output of failing-spec notice +* Fixed [#6233] Colours in specdoc +* Applied [#6207] Allows --diff option to diff target and expected's #inspect output (Patch by Lachie Cox) +* Fixed [#6203] Failure messages are misleading - consider using inspect. +* Added [#6334] subject.should_have_xyz will try to call subject.has_xyz? - use this for hash.should_have_key(key) +* Fixed [#6017] Rake task should ignore empty or non-existent spec-dirs + +== Version 0.6.4 + +In addition to a number of bug fixes and patches, this release begins to formalize the support for +RSpec on Rails. + +* Added Christopher Petrilli's TextMate bundle to vendor/textmate/RSpec.tmbundle +* Fixed [#5909], once again supporting multi_word_predicates +* Applied [#5873] - response.should_have_rjs (initial patch from Jake Howerton, based on ARTS by Kevin Clark) +* Added generation of view specs for rspec_on_rails +* Applied [#5815] active_record_subclass.should_have(3).records +* Added support in "rake stats" for view specs (in spec/views) +* Applied [#5801] QuickRef.pdf should say RSpec, not rSpec +* Applied [#5728] rails_spec_runner fails on Windows (Patch from Lindsay Evans). +* Applied [#5708] RSpec Rails plugin rspec_controller generator makes specs that do not parse. +* Cleaned up RSpec on Rails so it doesn't pollute as much during bootstrapping. +* Added support for response.should_have_tag and response.should_not_have_tag (works just like assert_tag in rails) +* Added new -c, --colour, --color option for colourful (red/green) output. Inspired from Pat Eyler's Redgreen gem. +* Added examples for Watir and Selenium under the gem's vendor directory. +* Renamed rails_spec_runner to rails_spec_server (as referred to in the docs) +* Added support for trying a plural for arbitrary predicates. E.g. Album.should_exist(:name => "Hey Jude") will call Album.exists?(:name => "Hey Jude") +* Added support for should_have to work with methods taking args returning a collection. E.g. @dave.should_have(3).albums_i_have_that_this_guy_doesnt(@aslak) +* Added [#5570] should_not_receive(:msg).with(:specific, "args") +* Applied [#5065] to support using define_method rather than method_missing to capture expected messages on mocks. Thanks to Eero Saynatkari for the tip that made it work. +* Restructured directories and Modules in order to separate rspec into three distinct Modules: Spec::Expectations, Spec::Runner and Spec::Mocks. This will allow us to more easily integrate other mock frameworks and/or allow test/unit users to take advantage of the expectation API. +* Applied [#5620] support any boolean method and arbitrary comparisons (5.should_be < 6) (Patch from Mike Williams) + +== Version 0.6.3 + +This release fixes some minor bugs related to RSpec on Rails +Note that if you upgrade a rails app with this version of the rspec_on_rails plugin +you should remove your lib/tasks/rspec.rake if it exists. + +* Backtraces from drb (and other standard ruby libraries) are now stripped from backtraces. +* Applied [#5557] Put rspec.rake into the task directory of the RSpec on Rails plugin (Patch from Daniel Siemssen) +* Applied [#5556] rails_spec_server loads environment.rb twice (Patch from Daniel Siemssen) + +== Version 0.6.2 +This release fixes a couple of regressions with the rake task that were introduced in the previous version (0.6.1) + +* Fixed [#5518] ruby -w: warnings in 0.6.1 +* Applied [#5525] fix rake task path to spec tool for gem-installed rspec (patch from Riley Lynch) +* Fixed a teensey regression with the rake task - introduced in 0.6.1. The spec command is now quoted so it works on windows. + +== Version 0.6.1 +This is the "fix the most annoying bugs release" of RSpec. There are 9 bugfixes this time. +Things that may break backwards compatibility: +1) Spec::Rake::SpecTask no longer has the options attribute. Use ruby_opts, spec_opts and rcov_opts instead. + +* Fixed [#4891] RCOV task failing on windows +* Fixed [#4896] Shouldn't modify user's $LOAD_PATH (Tip from Gavin Sinclair) +* Fixed [#5369] ruby -w: warnings in RSpec 0.5.16 (Tip from Suraj Kurapati) +* Applied [#5141] ExampleMatcher doesn't escape strings before matching (Patch from Nikolai Weibull). +* Fixed [#5224] Move 'require diff-lcs' from test_helper.rb to diff_test.rb (Tip from Chris Roos) +* Applied [#5449] Rake stats for specs (Patch from Nick Sieger) +* Applied [#5468, #5058] Fix spec runner to correctly run controller specs (Patch from Daniel Siemssen) +* Applied fixes to rails_spec_server to improve its ability to run several times. (Patch from Daniel Siemssen) +* Changed RCov::VerifyTask to fail if the coverage is above the threshold. This is to ensure it gets bumped when coverage improves. + +== Version 0.6.0 +This release makes an official commitment to underscore_syntax (with no more support for dot.syntax) + +* Fixed bug (5292) that caused mock argument matching to fail +* Converted ALL tests to use underscore syntax +* Fixed all remaining problems with underscores revealed by converting all the tests to underscores +* Enhanced sugar to support combinations of methods (i.e. once.and_return) +* Simplified helper structure taking advantage of dot/underscore combos (i.e. should.be.an_instance_of, which can be expressed as should be_an_instance_of) +* Added support for at_most in mocks +* Added support for should_not_receive(:msg) (will be removing should_receive(:msg).never some time soon) +* Added support for should_have_exactly(5).items_in_collection + +== Version 0.5.16 +This release improves Rails support and test2spec translation. + +* Fixed underscore problems that occurred when RSpec was used in Rails +* Simplified the Rails support by packaging it as a plugin instead of a generator gem. +* Fixed [#5063] 'rspec_on_rails' require line in spec_helper.rb +* Added pre_commit rake task to reduce risk of regressions. Useful for RSpec developers and patchers. +* Added failure_message to RSpec Rake task +* test2spec now defines converted helper methods outside of the setup block (bug #5057). + +== Version 0.5.15 +This release removes a prematurely added feature that shouldn't have been added. + +* Removed support for differences that was added in 0.5.14. The functionality is not aligned with RSpec's vision. + +== Version 0.5.14 +This release introduces better ways to extend specs, improves some of the core API and +a experimental support for faster rails specs. + +* Added proc methods for specifying differences (increments and decrements). See difference_test.rb +* Methods can now be defined alongside specs. This obsoletes the need for defining methods in setup. (Patch #5002 from Brian Takita) +* Sugar (underscores) now works correctly with should be_a_kind_of and should be_an_instance_of +* Added support for include and inherit in contexts. (Patch #4971 from Brian Takita) +* Added rails_spec and rails_spec_server for faster specs on rails (still buggy - help needed) +* Fixed bug that caused should_render to break if given a :symbol (in Rails) +* Added support for comparing exception message in should_raise and should_not_raise + +== Version 0.5.13 +This release fixes some subtle bugs in the mock API. + +* Use fully-qualified class name of Exceptions in failure message. Easier to debug that way. +* Fixed a bug that caused mocks to yield a one-element array (rather than the element) when one yield arg specified. +* Mocks not raise AmbiguousReturnError if an explicit return is used at the same time as an expectation block. +* Blocks passed to yielding mocks can now raise without causing mock verification to fail. + +== Version 0.5.12 +This release adds diff support for failure messages, a HTML formatter plus some other +minor enhancements. + +* Added HTML formatter. +* Added fail_on_error option to spectask. +* Added support for diffing, using the diff-lcs Rubygem (#2648). +* Remove RSpec on Rails files from backtrace (#4694). +* All of RSpec's own tests run successfully after translation with test2spec. +* Added --verbose mode for test2spec - useful for debugging when classes fail to translate. +* Output of various formatters is now flushed - to get more continuous output. + +== Version 0.5.11 +This release makes test2spec usable with Rails (with some manual steps). +See http://rspec.rubyforge.org/tools/rails.html for more details + +* test2spec now correctly translates bodies of helper methods (non- test_*, setup and teardown ones). +* Added more documentation about how to get test2spec to work with Rails. + +== Version 0.5.10 +This version features a second rewrite of test2spec - hopefully better than the previous one. + +* Improved test2spec's internals. It now transforms the syntax tree before writing out the code. + +== Version 0.5.9 +This release improves test2spec by allowing more control over the output + +* Added --template option to test2spec, which allows for custom output driven by ERB +* Added --quiet option to test2spec +* Removed unnecessary dependency on RubyToC + +== Version 0.5.8 +This release features a new Test::Unit to RSpec translation tool. +Also note that the RubyGem of the previous release (0.5.7) was corrupt. +We're close to being able to translate all of RSpec's own Test::Unit +tests and have them run successfully! + +* Updated test2spec documentation. +* Replaced old test2rspec with a new test2spec, which is based on ParseTree and RubyInline. + +== Version 0.5.7 +This release changes examples and documentation to recommend underscores rather than dots, +and addresses some bugfixes and changes to the spec commandline. + +* spec DIR now works correctly, recursing down and slurping all *.rb files +* All documentation and examples are now using '_' instead of '.' +* Custom external formatters can now be specified via --require and --format. + +== Version 0.5.6 +This release fixes a bug in the Rails controller generator + +* The controller generator did not write correct source code (missing 'do'). Fixed. + +== Version 0.5.5 +This release adds initial support for Ruby on Rails in the rspec_generator gem. + +* [Rails] Reorganised Lachie's original code to be a generator packaged as a gem rather than a plugin. +* [Rails] Imported code from http://lachie.info/svn/projects/rails_plugins/rspec_on_rails (Written by Lachie Cox) +* Remove stack trace lines from TextMate's Ruby bundle +* Better error message from spectask when no spec files are found. + +== Version 0.5.4 +The "the tutorial is ahead of the gem" release + +* Support for running a single spec with --spec +* Exitcode is now 1 unless all specs pass, in which case it's 0. +* -v, --version now both mean the same thing +* For what was verbose output (-v), use --format specdoc or -f s +* --format rdoc always runs in dry-run mode +* Removed --doc and added --format and --dry-run +* Refactored towards more pluggable formatters +* Use webgen's execute tag when generating website (more accurate) +* Fixed incorrect quoting of spec_opts in SpecTask +* Added patch to enable underscored shoulds like 1.should_equal(1) - patch from Rich Kilmer +* Removed most inherited instance method from Mock, making more methods mockable. +* Made the RCovVerify task part of the standard toolset. +* Documented Rake task and how to use it with Rcov +* Implemented tags for website (hooking into ERB, RedCloth and syntax) +* RSpec Rake task now takes spec_opts and out params so it can be used for doc generation +* RCov integration for RSpec Rake task (#4058) +* Group all results instead of printing them several times (#4057) +* Mocks can now yield +* Various improvements to error reporting (including #4191) +* backtrace excludes rspec code - use -b to include it +* split examples into examples (passing) and failing_examples + +== Version 0.5.3 +The "hurry up, CoR is in two days" release. + +* Don't run rcov by default +* Make separate task for running tests with RCov +* Added Rake task to fail build if coverage drops below a certain threshold +* Even more failure output cleanup (simplification) +* Added duck_type constraint for mocks + +== Version 0.5.2 +This release has minor improvements to the commandline and fixes some gem warnings + +* Readded README to avoid RDoc warnings +* Added --version switch to commandline +* More changes to the mock API + +== Version 0.5.1 +This release is the first release of RSpec with a new website. It will look better soon. + +* Added initial documentation for API +* Added website based on webgen +* Modified test task to use rcov +* Deleted unused code (thanks, rcov!) +* Various changes to the mock API, +* Various improvements to failure reporting + +== Version 0.5.0 +This release introduces a new API and obsolesces previous versions. + +* Moved source code to separate subfolders +* Added new DSL runner based on instance_exec +* Added spike for testdox/rdoc generation +* merge Astels' and Chelimsky's work on ShouldHelper +* this would be 0.5.0 if I updated the documentation +* it breaks all of your existing specifications. We're not sorry. + +== Version 0.3.2 + +The "srbaker is an idiot" release. + +* also forgot to update the path to the actual Subversion repository +* this should be it + +== Version 0.3.1 + +This is just 0.3.0, but with the TUTORIAL added to the documentation list. + +* forgot to include TUTORIAL in the documentation + +== Version 0.3.0 + +It's been a while since last release, lots of new stuff is available. For instance: + +* improvements to the runners +* addition of should_raise expectation (thanks to Brian Takita) +* some documentation improvements +* RSpec usable as a DSL + +== Version 0.2.0 + +This release provides a tutorial for new users wishing to get started with +RSpec, and many improvements. + +* improved reporting in the spec runner output +* update the examples to the new mock api +* added TUTORIAL, a getting started document for new users of RSpec + +== Version 0.1.7 + +This release improves installation and documentation, mock integration and error reporting. + +* Comparison errors now print the class name too. +* Mocks now take an optional +options+ parameter to specify behaviour. +* Removed __expects in favour of should_receive +* Added line number reporting in mock error messages for unreceived message expectations. +* Added should_match and should_not_match. +* Added a +mock+ method to Spec::Context which will create mocks that autoverify (no need to call __verify). +* Mocks now require names in the constructor to ensure sensible error messages. +* Made 'spec' executable and updated usage instructions in README accordingly. +* Made more parts of the Spec::Context API private to avoid accidental usage. +* Added more RDoc to Spec::Context. + +== Version 0.1.6 + +More should methods. + +* Added should_match and should_not_match. + +== Version 0.1.5 + +Included examples and tests in gem. + +== Version 0.1.4 + +More tests on block based Mock expectations. + +== Version 0.1.3 + +Improved mocking: + +* block based Mock expectations. + +== Version 0.1.2 + +This release adds some improvements to the mock API and minor syntax improvements + +* Added Mock.should_expect for a more consistent DSL. +* Added MockExpectation.and_returns for a better DSL. +* Made Mock behave as a null object after a call to Mock.ignore_missing +* Internal syntax improvements. +* Improved exception trace by adding exception class name to error message. +* Renamed some tests for better consistency. + +== Version 0.1.1 + +This release adds some shoulds and improves error reporting + +* Added should be_same_as and should_not be_same_as. +* Improved error reporting for comparison expectations. + +== Version 0.1.0 + +This is the first preview release of RSpec, a Behaviour-Driven Development library for Ruby + +* Added Rake script with tasks for gems, rdoc etc. +* Added an XForge task to make release go easier. diff --git a/vendor/gems/rspec/MIT-LICENSE b/vendor/gems/rspec/MIT-LICENSE new file mode 100644 index 0000000000..1d11ea59e4 --- /dev/null +++ b/vendor/gems/rspec/MIT-LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2005-2007 The RSpec Development Team + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/gems/rspec/README b/vendor/gems/rspec/README new file mode 100644 index 0000000000..0683b0deb6 --- /dev/null +++ b/vendor/gems/rspec/README @@ -0,0 +1,71 @@ +== RSpec + +RSpec is a Behaviour Driven Development framework with tools to express User Stories +with Executable Scenarios and Executable Examples at the code level. + +RSpec ships with several modules: + +Spec::Story provides a framework for expressing User Stories + +Spec::Example provides a framework for expressing code Examples + +Spec::Matchers provides Expression Matchers for use with Spec::Expectations +and Spec::Mocks. + +Spec::Expectations supports setting expectations on your objects so you +can do things like: + + result.should equal(expected_result) + +Spec::Mocks supports creating Mock Objects, Stubs, and adding Mock/Stub +behaviour to your existing objects. + +== Installation + +The simplest approach is to install the gem: + + gem install -r rspec #mac users must sudo + +== Building the RSpec gem +If you prefer to build the gem locally, check out source from svn://rubyforge.org/var/svn/rspec/trunk. Then +do the following: + + rake gem + gem install pkg/rspec-0.x.x.gem (you may have to sudo) + +== Running RSpec's specs +In order to run RSpec's full suite of specs (rake pre_commit) you must install the following gems: + +* rake # Runs the build script +* rcov # Verifies that the code is 100% covered by specs +* webby # Generates the static HTML website +* syntax # Required to highlight ruby code +* diff-lcs # Required if you use the --diff switch +* win32console # Required by the --colour switch if you're on Windows +* meta_project # Required in order to make releases at RubyForge +* heckle # Required if you use the --heckle switch +* hpricot # Used for parsing HTML from the HTML output formatter in RSpec's own specs + +Once those are all installed, you should be able to run the suite with the following steps: + +* svn co svn://rubyforge.org/var/svn/rspec/trunk rspec +* cd rspec +* rake install_dependencies +* cd example_rails_app +* export RSPEC_RAILS_VERSION=1.2.3 +* rake rspec:generate_mysql_config +* mysql -u root < db/mysql_setup.sql +* cd .. +* rake pre_commit + +Note that RSpec itself - once built - doesn't have any dependencies outside the Ruby core +and stdlib - with a few exceptions: + +* The spec command line uses diff-lcs when --diff is specified. +* The spec command line uses heckle when --heckle is specified. +* The Spec::Rake::SpecTask needs RCov if RCov is enabled in the task. + +See http://rspec.rubyforge.org for further documentation. + +== Contributing + diff --git a/vendor/gems/rspec/Rakefile b/vendor/gems/rspec/Rakefile new file mode 100644 index 0000000000..c60d5d466c --- /dev/null +++ b/vendor/gems/rspec/Rakefile @@ -0,0 +1,279 @@ +$:.unshift('lib') +require 'rubygems' +require 'rake/gempackagetask' +require 'rake/contrib/rubyforgepublisher' +require 'rake/clean' +require 'rake/rdoctask' +require 'rake/testtask' +require 'spec/version' +dir = File.dirname(__FILE__) +$LOAD_PATH.unshift(File.expand_path("#{dir}/pre_commit/lib")) +require "pre_commit" + +# Some of the tasks are in separate files since they are also part of the website documentation +load File.dirname(__FILE__) + '/rake_tasks/examples.rake' +load File.dirname(__FILE__) + '/rake_tasks/examples_with_rcov.rake' +load File.dirname(__FILE__) + '/rake_tasks/failing_examples_with_html.rake' +load File.dirname(__FILE__) + '/rake_tasks/verify_rcov.rake' + +PKG_NAME = "rspec" +PKG_VERSION = Spec::VERSION::STRING +PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}" +PKG_FILES = FileList[ + '[A-Z]*', + 'lib/**/*.rb', + 'spec/**/*', + 'examples/**/*', + 'failing_examples/**/*', + 'plugins/**/*', + 'stories/**/*', + 'pre_commit/**/*', + 'rake_tasks/**/*' +] + +task :default => [:verify_rcov] +task :verify_rcov => [:spec, :stories] + +desc "Run all specs" +Spec::Rake::SpecTask.new do |t| + t.spec_files = FileList['spec/**/*_spec.rb'] + t.spec_opts = ['--options', 'spec/spec.opts'] + unless ENV['NO_RCOV'] + t.rcov = true + t.rcov_dir = '../doc/output/coverage' + t.rcov_opts = ['--exclude', 'spec\/spec,bin\/spec,examples,\/var\/lib\/gems,\/Library\/Ruby,\.autotest'] + end +end + +desc "Run all stories" +task :stories do + html = 'story_server/prototype/rspec_stories.html' + ruby "stories/all.rb --colour --format plain --format html:#{html}" + unless IO.read(html) =~ //m + raise 'highlighted parameters are broken in story HTML' + end +end + +desc "Run all specs and store html output in doc/output/report.html" +Spec::Rake::SpecTask.new('spec_html') do |t| + t.spec_files = FileList['spec/**/*_spec.rb', '../../RSpec.tmbundle/Support/spec/*_spec.rb'] + t.spec_opts = ['--format html:../doc/output/report.html','--backtrace'] +end + +desc "Run all failing examples" +Spec::Rake::SpecTask.new('failing_examples') do |t| + t.spec_files = FileList['failing_examples/**/*_spec.rb'] +end + +desc 'Generate RDoc' +rd = Rake::RDocTask.new do |rdoc| + rdoc.rdoc_dir = '../doc/output/rdoc' + rdoc.options << '--title' << 'RSpec' << '--line-numbers' << '--inline-source' << '--main' << 'README' + rdoc.rdoc_files.include('README', 'CHANGES', 'MIT-LICENSE', 'UPGRADE', 'lib/**/*.rb') +end + +spec = Gem::Specification.new do |s| + s.name = PKG_NAME + s.version = PKG_VERSION + s.summary = Spec::VERSION::DESCRIPTION + s.description = <<-EOF + RSpec is a behaviour driven development (BDD) framework for Ruby. RSpec was + created in response to Dave Astels' article _A New Look at Test Driven Development_ + which can be read at: http://daveastels.com/index.php?p=5 RSpec is intended to + provide the features discussed in Dave's article. + EOF + + s.files = PKG_FILES.to_a + s.require_path = 'lib' + + s.has_rdoc = true + s.rdoc_options = rd.options + s.extra_rdoc_files = rd.rdoc_files.reject { |fn| fn =~ /\.rb$|^EXAMPLES.rd$/ }.to_a + + s.bindir = 'bin' + s.executables = ['spec', 'spec_translator'] + s.default_executable = 'spec' + s.author = "RSpec Development Team" + s.email = "rspec-devel@rubyforge.org" + s.homepage = "http://rspec.rubyforge.org/" + s.platform = Gem::Platform::RUBY + s.rubyforge_project = "rspec" +end + +Rake::GemPackageTask.new(spec) do |pkg| + pkg.need_zip = true + pkg.need_tar = true +end + +def egrep(pattern) + Dir['**/*.rb'].each do |fn| + count = 0 + open(fn) do |f| + while line = f.gets + count += 1 + if line =~ pattern + puts "#{fn}:#{count}:#{line}" + end + end + end + end +end + +desc "Look for TODO and FIXME tags in the code" +task :todo do + egrep /(FIXME|TODO|TBD)/ +end + +task :clobber do + core.clobber +end + +task :release => [:clobber, :verify_committed, :verify_user, :spec, :publish_packages, :tag, :publish_news] + +desc "Verifies that there is no uncommitted code" +task :verify_committed do + IO.popen('svn stat') do |io| + io.each_line do |line| + raise "\n!!! Do a svn commit first !!!\n\n" if line =~ /^\s*M\s*/ + end + end +end + +desc "Creates a tag in svn" +task :tag do + from = `svn info #{File.dirname(__FILE__)}`.match(/URL: (.*)\/rspec/n)[1] + to = from.gsub(/trunk/, "tags/#{Spec::VERSION::TAG}") + current = from.gsub(/trunk/, "tags/CURRENT") + + puts "Creating tag in SVN" + tag_cmd = "svn cp #{from} #{to} -m \"Tag release #{Spec::VERSION::FULL_VERSION}\"" + `#{tag_cmd}` ; raise "ERROR: #{tag_cmd}" unless $? == 0 + + puts "Removing CURRENT" + remove_current_cmd = "svn rm #{current} -m \"Remove tags/CURRENT\"" + `#{remove_current_cmd}` ; raise "ERROR: #{remove_current_cmd}" unless $? == 0 + + puts "Re-Creating CURRENT" + create_current_cmd = "svn cp #{to} #{current} -m \"Copy #{Spec::VERSION::TAG} to tags/CURRENT\"" + `#{create_current_cmd}` ; "ERROR: #{create_current_cmd}" unless $? == 0 +end + +desc "Run this task before you commit. You should see 'OK TO COMMIT'" +task(:pre_commit) {core.pre_commit} + +desc "Build the website, but do not publish it" +task(:website) {core.website} + +task(:rdoc_rails) {core.rdoc_rails} + +task :verify_user do + raise "RUBYFORGE_USER environment variable not set!" unless ENV['RUBYFORGE_USER'] +end + +desc "Upload Website to RubyForge" +task :publish_website => [:verify_user, :website] do + unless Spec::VERSION::RELEASE_CANDIDATE + publisher = Rake::SshDirPublisher.new( + "rspec-website@rubyforge.org", + "/var/www/gforge-projects/#{PKG_NAME}", + "../doc/output" + ) + publisher.upload + else + puts "** Not publishing packages to RubyForge - this is a prerelease" + end +end + +desc "Upload Website archive to RubyForge" +task :archive_website => [:verify_user, :website] do + publisher = Rake::SshDirPublisher.new( + "rspec-website@rubyforge.org", + "/var/www/gforge-projects/#{PKG_NAME}/#{Spec::VERSION::TAG}", + "../doc/output" + ) + publisher.upload +end + +desc "Package the Rails plugin" +task :package_rspec_on_rails do + mkdir 'pkg' rescue nil + rm_rf 'pkg/rspec_on_rails' rescue nil + `svn export ../rspec_on_rails pkg/rspec_on_rails-#{PKG_VERSION}` + Dir.chdir 'pkg' do + `tar cvzf rspec_on_rails-#{PKG_VERSION}.tgz rspec_on_rails-#{PKG_VERSION}` + end +end +task :pkg => :package_rspec_on_rails + +desc "Package the RSpec.tmbundle" +task :package_tmbundle do + mkdir 'pkg' rescue nil + rm_rf 'pkg/RSpec.tmbundle' rescue nil + `svn export ../RSpec.tmbundle pkg/RSpec.tmbundle` + Dir.chdir 'pkg' do + `tar cvzf RSpec-#{PKG_VERSION}.tmbundle.tgz RSpec.tmbundle` + end +end +task :pkg => :package_tmbundle + +desc "Publish gem+tgz+zip on RubyForge. You must make sure lib/version.rb is aligned with the CHANGELOG file" +task :publish_packages => [:verify_user, :package] do + release_files = FileList[ + "pkg/#{PKG_FILE_NAME}.gem", + "pkg/#{PKG_FILE_NAME}.tgz", + "pkg/rspec_on_rails-#{PKG_VERSION}.tgz", + "pkg/#{PKG_FILE_NAME}.zip", + "pkg/RSpec-#{PKG_VERSION}.tmbundle.tgz" + ] + unless Spec::VERSION::RELEASE_CANDIDATE + require 'meta_project' + require 'rake/contrib/xforge' + + Rake::XForge::Release.new(MetaProject::Project::XForge::RubyForge.new(PKG_NAME)) do |xf| + # Never hardcode user name and password in the Rakefile! + xf.user_name = ENV['RUBYFORGE_USER'] + xf.files = release_files.to_a + xf.release_name = "RSpec #{PKG_VERSION}" + end + else + puts "SINCE THIS IS A PRERELEASE, FILES ARE UPLOADED WITH SSH, NOT TO THE RUBYFORGE FILE SECTION" + puts "YOU MUST TYPE THE PASSWORD #{release_files.length} TIMES..." + + host = "rspec-website@rubyforge.org" + remote_dir = "/var/www/gforge-projects/#{PKG_NAME}" + + publisher = Rake::SshFilePublisher.new( + host, + remote_dir, + File.dirname(__FILE__), + *release_files + ) + publisher.upload + + puts "UPLADED THE FOLLOWING FILES:" + release_files.each do |file| + name = file.match(/pkg\/(.*)/)[1] + puts "* http://rspec.rubyforge.org/#{name}" + end + + puts "They are not linked to anywhere, so don't forget to tell people!" + end +end + +desc "Publish news on RubyForge" +task :publish_news => [:verify_user] do + unless Spec::VERSION::RELEASE_CANDIDATE + require 'meta_project' + require 'rake/contrib/xforge' + Rake::XForge::NewsPublisher.new(MetaProject::Project::XForge::RubyForge.new(PKG_NAME)) do |news| + # Never hardcode user name and password in the Rakefile! + news.user_name = ENV['RUBYFORGE_USER'] + end + else + puts "** Not publishing news to RubyForge - this is a prerelease" + end +end + +def core + PreCommit::Core.new(self) +end diff --git a/vendor/gems/rspec/TODO b/vendor/gems/rspec/TODO new file mode 100644 index 0000000000..250bb66c22 --- /dev/null +++ b/vendor/gems/rspec/TODO @@ -0,0 +1,2 @@ +=== Before releasing 1.1.0: + diff --git a/vendor/gems/rspec/UPGRADE b/vendor/gems/rspec/UPGRADE new file mode 100644 index 0000000000..0a2c6f5286 --- /dev/null +++ b/vendor/gems/rspec/UPGRADE @@ -0,0 +1,31 @@ += Upgrading existing code to RSpec-0.9 + +== General (see below for Spec::Rails specifics) + +=== New Syntax for should and should_not + +* Use translator (should get 90% of your code) +* Manually fix "parenthesis" warnings + +=== Change before_context_eval to before_eval + +before_context_eval is an un-published hook used by +Spec::Rails to create specialized behaviour contexts. +Most of you don't need to change this, but for those +who have exploited it, you'll need to change it to +before_eval. + +== Spec::Rails + +=== spec_helper.rb + +We've added a new way to configure Spec::Runner to do +things like use_transactional_fixtures and use_instantiated_fixtures. +You'll need to update spec/spec_helper.rb accordingly. You can either +just re-generate it: + + script/generate rspec + +Or modify spec_helper.rb based on the template, which can be found at: + + vendor/plugins/rspec_on_rails/generators/rspec/templates/spec_helper.rb \ No newline at end of file diff --git a/vendor/gems/rspec/bin/spec b/vendor/gems/rspec/bin/spec new file mode 100755 index 0000000000..283176d765 --- /dev/null +++ b/vendor/gems/rspec/bin/spec @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib")) +require 'spec' +exit ::Spec::Runner::CommandLine.run(rspec_options) diff --git a/vendor/gems/rspec/bin/spec_translator b/vendor/gems/rspec/bin/spec_translator new file mode 100755 index 0000000000..abd50b7431 --- /dev/null +++ b/vendor/gems/rspec/bin/spec_translator @@ -0,0 +1,8 @@ +#!/usr/bin/env ruby +raise "\n\nUsage: spec_translator from_dir to_dir\n\n" if ARGV.size != 2 +$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib")) +require 'spec/translator' +t = ::Spec::Translator.new +from = ARGV[0] +to = ARGV[1] +t.translate(from, to) diff --git a/vendor/gems/rspec/examples/pure/autogenerated_docstrings_example.rb b/vendor/gems/rspec/examples/pure/autogenerated_docstrings_example.rb new file mode 100644 index 0000000000..a4928ef4a8 --- /dev/null +++ b/vendor/gems/rspec/examples/pure/autogenerated_docstrings_example.rb @@ -0,0 +1,19 @@ +require File.dirname(__FILE__) + '/spec_helper' + +# Run spec w/ -fs to see the output of this file + +describe "Examples with no descriptions" do + + # description is auto-generated as "should equal(5)" based on the last #should + it do + 3.should equal(3) + 5.should equal(5) + end + + it { 3.should be < 5 } + + it { ["a"].should include("a") } + + it { [1,2,3].should respond_to(:size) } + +end diff --git a/vendor/gems/rspec/examples/pure/before_and_after_example.rb b/vendor/gems/rspec/examples/pure/before_and_after_example.rb new file mode 100644 index 0000000000..7db6274ef9 --- /dev/null +++ b/vendor/gems/rspec/examples/pure/before_and_after_example.rb @@ -0,0 +1,40 @@ +require File.dirname(__FILE__) + '/spec_helper' +$global = 0 + +describe "State created in before(:all)" do + before :all do + @sideeffect = 1 + $global +=1 + end + + before :each do + @isolated = 1 + end + + it "should be accessible from example" do + @sideeffect.should == 1 + $global.should == 1 + @isolated.should == 1 + + @sideeffect += 1 + @isolated += 1 + end + + it "should not have sideffects" do + @sideeffect.should == 1 + $global.should == 2 + @isolated.should == 1 + + @sideeffect += 1 + @isolated += 1 + end + + after :each do + $global += 1 + end + + after :all do + $global.should == 3 + $global = 0 + end +end diff --git a/vendor/gems/rspec/examples/pure/behave_as_example.rb b/vendor/gems/rspec/examples/pure/behave_as_example.rb new file mode 100755 index 0000000000..e95d1469ae --- /dev/null +++ b/vendor/gems/rspec/examples/pure/behave_as_example.rb @@ -0,0 +1,45 @@ +require File.dirname(__FILE__) + '/spec_helper' + +def behave_as_electric_musician + respond_to(:read_notes, :turn_down_amp) +end + +def behave_as_musician + respond_to(:read_notes) +end + +module BehaveAsExample + + class BluesGuitarist + def read_notes; end + def turn_down_amp; end + end + + class RockGuitarist + def read_notes; end + def turn_down_amp; end + end + + class ClassicGuitarist + def read_notes; end + end + + describe BluesGuitarist do + it "should behave as guitarist" do + BluesGuitarist.new.should behave_as_electric_musician + end + end + + describe RockGuitarist do + it "should behave as guitarist" do + RockGuitarist.new.should behave_as_electric_musician + end + end + + describe ClassicGuitarist do + it "should not behave as guitarist" do + ClassicGuitarist.new.should behave_as_musician + end + end + +end diff --git a/vendor/gems/rspec/examples/pure/custom_expectation_matchers.rb b/vendor/gems/rspec/examples/pure/custom_expectation_matchers.rb new file mode 100644 index 0000000000..075bb542dc --- /dev/null +++ b/vendor/gems/rspec/examples/pure/custom_expectation_matchers.rb @@ -0,0 +1,54 @@ +module AnimalSpecHelper + class Eat + def initialize(food) + @food = food + end + + def matches?(animal) + @animal = animal + @animal.eats?(@food) + end + + def failure_message + "expected #{@animal} to eat #{@food}, but it does not" + end + + def negative_failure_message + "expected #{@animal} not to eat #{@food}, but it does" + end + end + + def eat(food) + Eat.new(food) + end +end + +module Animals + class Animal + def eats?(food) + return foods_i_eat.include?(food) + end + end + + class Mouse < Animal + def foods_i_eat + [:cheese] + end + end + + describe Mouse do + include AnimalSpecHelper + before(:each) do + @mouse = Animals::Mouse.new + end + + it "should eat cheese" do + @mouse.should eat(:cheese) + end + + it "should not eat cat" do + @mouse.should_not eat(:cat) + end + end + +end diff --git a/vendor/gems/rspec/examples/pure/custom_formatter.rb b/vendor/gems/rspec/examples/pure/custom_formatter.rb new file mode 100644 index 0000000000..c449fdc2e8 --- /dev/null +++ b/vendor/gems/rspec/examples/pure/custom_formatter.rb @@ -0,0 +1,12 @@ +require File.dirname(__FILE__) + '/spec_helper' +require 'spec/runner/formatter/progress_bar_formatter' + +# Example of a formatter with custom bactrace printing. Run me with: +# ruby bin/spec failing_examples -r examples/custom_formatter.rb -f CustomFormatter +class CustomFormatter < Spec::Runner::Formatter::ProgressBarFormatter + def backtrace_line(line) + line.gsub(/([^:]*\.rb):(\d*)/) do + "#{$1}:#{$2} " + end + end +end diff --git a/vendor/gems/rspec/examples/pure/dynamic_spec.rb b/vendor/gems/rspec/examples/pure/dynamic_spec.rb new file mode 100644 index 0000000000..15d473d61f --- /dev/null +++ b/vendor/gems/rspec/examples/pure/dynamic_spec.rb @@ -0,0 +1,9 @@ +require File.dirname(__FILE__) + '/spec_helper' + +describe "Some integers" do + (1..10).each do |n| + it "The root of #{n} square should be #{n}" do + Math.sqrt(n*n).should == n + end + end +end diff --git a/vendor/gems/rspec/examples/pure/file_accessor.rb b/vendor/gems/rspec/examples/pure/file_accessor.rb new file mode 100644 index 0000000000..ff6fb743c8 --- /dev/null +++ b/vendor/gems/rspec/examples/pure/file_accessor.rb @@ -0,0 +1,19 @@ +require File.dirname(__FILE__) + '/spec_helper' +class FileAccessor + def open_and_handle_with(pathname, processor) + pathname.open do |io| + processor.process(io) + end + end +end + +if __FILE__ == $0 + require File.dirname(__FILE__) + '/io_processor' + require 'pathname' + + accessor = FileAccessor.new + io_processor = IoProcessor.new + file = Pathname.new ARGV[0] + + accessor.open_and_handle_with(file, io_processor) +end diff --git a/vendor/gems/rspec/examples/pure/file_accessor_spec.rb b/vendor/gems/rspec/examples/pure/file_accessor_spec.rb new file mode 100644 index 0000000000..628d4c0b06 --- /dev/null +++ b/vendor/gems/rspec/examples/pure/file_accessor_spec.rb @@ -0,0 +1,38 @@ +require File.dirname(__FILE__) + '/spec_helper' +require File.dirname(__FILE__) + '/file_accessor' +require 'stringio' + +describe "A FileAccessor" do + # This sequence diagram illustrates what this spec specifies. + # + # +--------------+ +----------+ +-------------+ + # | FileAccessor | | Pathname | | IoProcessor | + # +--------------+ +----------+ +-------------+ + # | | | + # open_and_handle_with | | | + # -------------------->| | open | | + # | |--------------->| | | + # | | io | | | + # | |<...............| | | + # | | | process(io) | + # | |---------------------------------->| | + # | | | | | + # | |<..................................| | + # | | | + # + it "should open a file and pass it to the processor's process method" do + # This is the primary actor + accessor = FileAccessor.new + + # These are the primary actor's neighbours, which we mock. + file = mock "Pathname" + io_processor = mock "IoProcessor" + + io = StringIO.new "whatever" + file.should_receive(:open).and_yield io + io_processor.should_receive(:process).with(io) + + accessor.open_and_handle_with(file, io_processor) + end + +end diff --git a/vendor/gems/rspec/examples/pure/greeter_spec.rb b/vendor/gems/rspec/examples/pure/greeter_spec.rb new file mode 100644 index 0000000000..ec7669dcc3 --- /dev/null +++ b/vendor/gems/rspec/examples/pure/greeter_spec.rb @@ -0,0 +1,31 @@ +require File.dirname(__FILE__) + '/spec_helper' +# greeter.rb +# +# Based on http://glu.ttono.us/articles/2006/12/19/tormenting-your-tests-with-heckle +# +# Run with: +# +# spec greeter_spec.rb --heckle Greeter +# +class Greeter + def initialize(person = nil) + @person = person + end + + def greet + @person.nil? ? "Hi there!" : "Hi #{@person}!" + end +end + +describe "Greeter" do + it "should say Hi to person" do + greeter = Greeter.new("Kevin") + greeter.greet.should == "Hi Kevin!" + end + + it "should say Hi to nobody" do + greeter = Greeter.new + # Uncomment the next line to make Heckle happy + #greeter.greet.should == "Hi there!" + end +end diff --git a/vendor/gems/rspec/examples/pure/helper_method_example.rb b/vendor/gems/rspec/examples/pure/helper_method_example.rb new file mode 100644 index 0000000000..d97f19e65b --- /dev/null +++ b/vendor/gems/rspec/examples/pure/helper_method_example.rb @@ -0,0 +1,14 @@ +require File.dirname(__FILE__) + '/spec_helper' + +module HelperMethodExample + describe "an example group with helper a method" do + def helper_method + "received call" + end + + it "should make that method available to specs" do + helper_method.should == "received call" + end + end +end + diff --git a/vendor/gems/rspec/examples/pure/io_processor.rb b/vendor/gems/rspec/examples/pure/io_processor.rb new file mode 100644 index 0000000000..6b15147b65 --- /dev/null +++ b/vendor/gems/rspec/examples/pure/io_processor.rb @@ -0,0 +1,8 @@ +class DataTooShort < StandardError; end + +class IoProcessor + # Does some fancy stuff unless the length of +io+ is shorter than 32 + def process(io) + raise DataTooShort if io.read.length < 32 + end +end diff --git a/vendor/gems/rspec/examples/pure/io_processor_spec.rb b/vendor/gems/rspec/examples/pure/io_processor_spec.rb new file mode 100644 index 0000000000..5cab7bf317 --- /dev/null +++ b/vendor/gems/rspec/examples/pure/io_processor_spec.rb @@ -0,0 +1,21 @@ +require File.dirname(__FILE__) + '/spec_helper' +require File.dirname(__FILE__) + '/io_processor' +require 'stringio' + +describe "An IoProcessor" do + before(:each) do + @processor = IoProcessor.new + end + + it "should raise nothing when the file is exactly 32 bytes" do + lambda { + @processor.process(StringIO.new("z"*32)) + }.should_not raise_error + end + + it "should raise an exception when the file length is less than 32 bytes" do + lambda { + @processor.process(StringIO.new("z"*31)) + }.should raise_error(DataTooShort) + end +end diff --git a/vendor/gems/rspec/examples/pure/legacy_spec.rb b/vendor/gems/rspec/examples/pure/legacy_spec.rb new file mode 100644 index 0000000000..c86369515d --- /dev/null +++ b/vendor/gems/rspec/examples/pure/legacy_spec.rb @@ -0,0 +1,11 @@ +require File.dirname(__FILE__) + '/spec_helper' +context "A legacy spec" do + setup do + end + + specify "should work fine" do + end + + teardown do + end +end diff --git a/vendor/gems/rspec/examples/pure/mocking_example.rb b/vendor/gems/rspec/examples/pure/mocking_example.rb new file mode 100644 index 0000000000..6adbef59d0 --- /dev/null +++ b/vendor/gems/rspec/examples/pure/mocking_example.rb @@ -0,0 +1,27 @@ +require File.dirname(__FILE__) + '/spec_helper' + +describe "A consumer of a mock" do + it "should be able to send messages to the mock" do + mock = mock("poke me") + mock.should_receive(:poke) + mock.poke + end +end + +describe "a mock" do + it "should be able to mock the same message twice w/ different args" do + mock = mock("mock") + mock.should_receive(:msg).with(:arg1).and_return(:val1) + mock.should_receive(:msg).with(:arg2).and_return(:val2) + mock.msg(:arg1).should eql(:val1) + mock.msg(:arg2).should eql(:val2) + end + + it "should be able to mock the same message twice w/ different args in reverse order" do + mock = mock("mock") + mock.should_receive(:msg).with(:arg1).and_return(:val1) + mock.should_receive(:msg).with(:arg2).and_return(:val2) + mock.msg(:arg2).should eql(:val2) + mock.msg(:arg1).should eql(:val1) + end +end diff --git a/vendor/gems/rspec/examples/pure/multi_threaded_behaviour_runner.rb b/vendor/gems/rspec/examples/pure/multi_threaded_behaviour_runner.rb new file mode 100644 index 0000000000..36edcd497a --- /dev/null +++ b/vendor/gems/rspec/examples/pure/multi_threaded_behaviour_runner.rb @@ -0,0 +1,28 @@ +class MultiThreadedExampleGroupRunner < Spec::Runner::ExampleGroupRunner + def initialize(options, arg) + super(options) + # configure these + @thread_count = 4 + @thread_wait = 0 + end + + def run + @threads = [] + q = Queue.new + example_groups.each { |b| q << b} + success = true + @thread_count.times do + @threads << Thread.new(q) do |queue| + while not queue.empty? + example_group = queue.pop + success &= example_group.suite.run(nil) + end + end + sleep @thread_wait + end + @threads.each {|t| t.join} + success + end +end + +MultiThreadedBehaviourRunner = MultiThreadedExampleGroupRunner \ No newline at end of file diff --git a/vendor/gems/rspec/examples/pure/nested_classes_example.rb b/vendor/gems/rspec/examples/pure/nested_classes_example.rb new file mode 100644 index 0000000000..abe43b0a66 --- /dev/null +++ b/vendor/gems/rspec/examples/pure/nested_classes_example.rb @@ -0,0 +1,36 @@ +require File.dirname(__FILE__) + '/spec_helper' +require File.dirname(__FILE__) + '/stack' + +class StackExamples < Spec::ExampleGroup + describe(Stack) + before(:each) do + @stack = Stack.new + end +end + +class EmptyStackExamples < StackExamples + describe("when empty") + it "should be empty" do + @stack.should be_empty + end +end + +class AlmostFullStackExamples < StackExamples + describe("when almost full") + before(:each) do + (1..9).each {|n| @stack.push n} + end + it "should be full" do + @stack.should_not be_full + end +end + +class FullStackExamples < StackExamples + describe("when full") + before(:each) do + (1..10).each {|n| @stack.push n} + end + it "should be full" do + @stack.should be_full + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/examples/pure/partial_mock_example.rb b/vendor/gems/rspec/examples/pure/partial_mock_example.rb new file mode 100644 index 0000000000..841ec88474 --- /dev/null +++ b/vendor/gems/rspec/examples/pure/partial_mock_example.rb @@ -0,0 +1,28 @@ +require File.dirname(__FILE__) + '/spec_helper' + +class MockableClass + def self.find id + return :original_return + end +end + +describe "A partial mock" do + + it "should work at the class level" do + MockableClass.should_receive(:find).with(1).and_return {:stub_return} + MockableClass.find(1).should equal(:stub_return) + end + + it "should revert to the original after each spec" do + MockableClass.find(1).should equal(:original_return) + end + + it "can be mocked w/ ordering" do + MockableClass.should_receive(:msg_1).ordered + MockableClass.should_receive(:msg_2).ordered + MockableClass.should_receive(:msg_3).ordered + MockableClass.msg_1 + MockableClass.msg_2 + MockableClass.msg_3 + end +end diff --git a/vendor/gems/rspec/examples/pure/pending_example.rb b/vendor/gems/rspec/examples/pure/pending_example.rb new file mode 100644 index 0000000000..13f3d00c4c --- /dev/null +++ b/vendor/gems/rspec/examples/pure/pending_example.rb @@ -0,0 +1,20 @@ +require File.dirname(__FILE__) + '/spec_helper' + +describe "pending example (using pending method)" do + it %Q|should be reported as "PENDING: for some reason"| do + pending("for some reason") + end +end + +describe "pending example (with no block)" do + it %Q|should be reported as "PENDING: Not Yet Implemented"| +end + +describe "pending example (with block for pending)" do + it %Q|should have a failing block, passed to pending, reported as "PENDING: for some reason"| do + pending("for some reason") do + raise "some reason" + end + end +end + diff --git a/vendor/gems/rspec/examples/pure/predicate_example.rb b/vendor/gems/rspec/examples/pure/predicate_example.rb new file mode 100644 index 0000000000..1202bb6707 --- /dev/null +++ b/vendor/gems/rspec/examples/pure/predicate_example.rb @@ -0,0 +1,27 @@ +require File.dirname(__FILE__) + '/spec_helper' + +class BddFramework + def intuitive? + true + end + + def adopted_quickly? + true + end +end + +describe "BDD framework" do + + before(:each) do + @bdd_framework = BddFramework.new + end + + it "should be adopted quickly" do + @bdd_framework.should be_adopted_quickly + end + + it "should be intuitive" do + @bdd_framework.should be_intuitive + end + +end diff --git a/vendor/gems/rspec/examples/pure/priority.txt b/vendor/gems/rspec/examples/pure/priority.txt new file mode 100644 index 0000000000..5b00064e2c --- /dev/null +++ b/vendor/gems/rspec/examples/pure/priority.txt @@ -0,0 +1 @@ +examples/custom_expectation_matchers.rb \ No newline at end of file diff --git a/vendor/gems/rspec/examples/pure/shared_example_group_example.rb b/vendor/gems/rspec/examples/pure/shared_example_group_example.rb new file mode 100644 index 0000000000..fb81af1ecc --- /dev/null +++ b/vendor/gems/rspec/examples/pure/shared_example_group_example.rb @@ -0,0 +1,81 @@ +require File.dirname(__FILE__) + '/spec_helper' + +module SharedExampleGroupExample + class OneThing + def what_things_do + "stuff" + end + end + + class AnotherThing + def what_things_do + "stuff" + end + end + + class YetAnotherThing + def what_things_do + "stuff" + end + end + + # A SharedExampleGroup is an example group that doesn't get run. + # You can create one like this: + share_examples_for "most things" do + def helper_method + "helper method" + end + + it "should do what things do" do + @thing.what_things_do.should == "stuff" + end + end + + # A SharedExampleGroup is also module. If you create one like this + # it gets assigned to the constant AllThings + share_as :MostThings do + def helper_method + "helper method" + end + + it "should do what things do" do + @thing.what_things_do.should == "stuff" + end + end + + describe OneThing do + # Now you can include the shared example group like this, which + # feels more like what you might say ... + it_should_behave_like "most things" + + before(:each) { @thing = OneThing.new } + + it "should have access to helper methods defined in the shared example group" do + helper_method.should == "helper method" + end + end + + describe AnotherThing do + # ... or you can include the example group like this, which + # feels more like the programming language we love. + it_should_behave_like MostThings + + before(:each) { @thing = AnotherThing.new } + + it "should have access to helper methods defined in the shared example group" do + helper_method.should == "helper method" + end + end + + describe YetAnotherThing do + # ... or you can include the example group like this, which + # feels more like the programming language we love. + include MostThings + + before(:each) { @thing = AnotherThing.new } + + it "should have access to helper methods defined in the shared example group" do + helper_method.should == "helper method" + end + end +end diff --git a/vendor/gems/rspec/examples/pure/shared_stack_examples.rb b/vendor/gems/rspec/examples/pure/shared_stack_examples.rb new file mode 100644 index 0000000000..7a08162508 --- /dev/null +++ b/vendor/gems/rspec/examples/pure/shared_stack_examples.rb @@ -0,0 +1,38 @@ +require File.join(File.dirname(__FILE__), *%w[spec_helper]) + +shared_examples_for "non-empty Stack" do + + it { @stack.should_not be_empty } + + it "should return the top item when sent #peek" do + @stack.peek.should == @last_item_added + end + + it "should NOT remove the top item when sent #peek" do + @stack.peek.should == @last_item_added + @stack.peek.should == @last_item_added + end + + it "should return the top item when sent #pop" do + @stack.pop.should == @last_item_added + end + + it "should remove the top item when sent #pop" do + @stack.pop.should == @last_item_added + unless @stack.empty? + @stack.pop.should_not == @last_item_added + end + end + +end + +shared_examples_for "non-full Stack" do + + it { @stack.should_not be_full } + + it "should add to the top when sent #push" do + @stack.push "newly added top item" + @stack.peek.should == "newly added top item" + end + +end \ No newline at end of file diff --git a/vendor/gems/rspec/examples/pure/spec_helper.rb b/vendor/gems/rspec/examples/pure/spec_helper.rb new file mode 100644 index 0000000000..1e880796cb --- /dev/null +++ b/vendor/gems/rspec/examples/pure/spec_helper.rb @@ -0,0 +1,3 @@ +lib_path = File.expand_path("#{File.dirname(__FILE__)}/../../lib") +$LOAD_PATH.unshift lib_path unless $LOAD_PATH.include?(lib_path) +require 'spec' diff --git a/vendor/gems/rspec/examples/pure/stack.rb b/vendor/gems/rspec/examples/pure/stack.rb new file mode 100644 index 0000000000..407173f7b0 --- /dev/null +++ b/vendor/gems/rspec/examples/pure/stack.rb @@ -0,0 +1,36 @@ +class StackUnderflowError < RuntimeError +end + +class StackOverflowError < RuntimeError +end + +class Stack + + def initialize + @items = [] + end + + def push object + raise StackOverflowError if @items.length == 10 + @items.push object + end + + def pop + raise StackUnderflowError if @items.empty? + @items.delete @items.last + end + + def peek + raise StackUnderflowError if @items.empty? + @items.last + end + + def empty? + @items.empty? + end + + def full? + @items.length == 10 + end + +end diff --git a/vendor/gems/rspec/examples/pure/stack_spec.rb b/vendor/gems/rspec/examples/pure/stack_spec.rb new file mode 100644 index 0000000000..2a769da00a --- /dev/null +++ b/vendor/gems/rspec/examples/pure/stack_spec.rb @@ -0,0 +1,63 @@ +require File.dirname(__FILE__) + '/spec_helper' +require File.dirname(__FILE__) + "/stack" +require File.dirname(__FILE__) + '/shared_stack_examples' + +describe Stack, " (empty)" do + before(:each) do + @stack = Stack.new + end + + # NOTE that this one auto-generates the description "should be empty" + it { @stack.should be_empty } + + it_should_behave_like "non-full Stack" + + it "should complain when sent #peek" do + lambda { @stack.peek }.should raise_error(StackUnderflowError) + end + + it "should complain when sent #pop" do + lambda { @stack.pop }.should raise_error(StackUnderflowError) + end +end + +describe Stack, " (with one item)" do + before(:each) do + @stack = Stack.new + @stack.push 3 + @last_item_added = 3 + end + + it_should_behave_like "non-empty Stack" + it_should_behave_like "non-full Stack" + +end + +describe Stack, " (with one item less than capacity)" do + before(:each) do + @stack = Stack.new + (1..9).each { |i| @stack.push i } + @last_item_added = 9 + end + + it_should_behave_like "non-empty Stack" + it_should_behave_like "non-full Stack" +end + +describe Stack, " (full)" do + before(:each) do + @stack = Stack.new + (1..10).each { |i| @stack.push i } + @last_item_added = 10 + end + + # NOTE that this one auto-generates the description "should be full" + it { @stack.should be_full } + + it_should_behave_like "non-empty Stack" + + it "should complain on #push" do + lambda { @stack.push Object.new }.should raise_error(StackOverflowError) + end + +end diff --git a/vendor/gems/rspec/examples/pure/stack_spec_with_nested_example_groups.rb b/vendor/gems/rspec/examples/pure/stack_spec_with_nested_example_groups.rb new file mode 100644 index 0000000000..05f6ad4647 --- /dev/null +++ b/vendor/gems/rspec/examples/pure/stack_spec_with_nested_example_groups.rb @@ -0,0 +1,67 @@ +require File.dirname(__FILE__) + '/spec_helper' +require File.dirname(__FILE__) + '/stack' +require File.dirname(__FILE__) + '/shared_stack_examples' + +describe Stack do + + before(:each) do + @stack = Stack.new + end + + describe "(empty)" do + + it { @stack.should be_empty } + + it_should_behave_like "non-full Stack" + + it "should complain when sent #peek" do + lambda { @stack.peek }.should raise_error(StackUnderflowError) + end + + it "should complain when sent #pop" do + lambda { @stack.pop }.should raise_error(StackUnderflowError) + end + + end + + describe "(with one item)" do + + before(:each) do + @stack.push 3 + @last_item_added = 3 + end + + it_should_behave_like "non-empty Stack" + it_should_behave_like "non-full Stack" + + end + + describe "(with one item less than capacity)" do + + before(:each) do + (1..9).each { |i| @stack.push i } + @last_item_added = 9 + end + + it_should_behave_like "non-empty Stack" + it_should_behave_like "non-full Stack" + end + + describe "(full)" do + + before(:each) do + (1..10).each { |i| @stack.push i } + @last_item_added = 10 + end + + it { @stack.should be_full } + + it_should_behave_like "non-empty Stack" + + it "should complain on #push" do + lambda { @stack.push Object.new }.should raise_error(StackOverflowError) + end + + end + +end diff --git a/vendor/gems/rspec/examples/pure/stubbing_example.rb b/vendor/gems/rspec/examples/pure/stubbing_example.rb new file mode 100644 index 0000000000..31354aec6a --- /dev/null +++ b/vendor/gems/rspec/examples/pure/stubbing_example.rb @@ -0,0 +1,69 @@ +require File.dirname(__FILE__) + '/spec_helper' + +describe "A consumer of a stub" do + it "should be able to stub methods on any Object" do + obj = Object.new + obj.stub!(:foobar).and_return {:return_value} + obj.foobar.should equal(:return_value) + end +end + +class StubbableClass + def self.find id + return :original_return + end +end + +describe "A stubbed method on a class" do + it "should return the stubbed value" do + StubbableClass.stub!(:find).and_return(:stub_return) + StubbableClass.find(1).should equal(:stub_return) + end + + it "should revert to the original method after each spec" do + StubbableClass.find(1).should equal(:original_return) + end + + it "can stub! and mock the same message" do + StubbableClass.stub!(:msg).and_return(:stub_value) + StubbableClass.should_receive(:msg).with(:arg).and_return(:mock_value) + + StubbableClass.msg.should equal(:stub_value) + StubbableClass.msg(:other_arg).should equal(:stub_value) + StubbableClass.msg(:arg).should equal(:mock_value) + StubbableClass.msg(:another_arg).should equal(:stub_value) + StubbableClass.msg(:yet_another_arg).should equal(:stub_value) + StubbableClass.msg.should equal(:stub_value) + end +end + +describe "A mock" do + it "can stub!" do + mock = mock("stubbing mock") + mock.stub!(:msg).and_return(:value) + (1..10).each {mock.msg.should equal(:value)} + end + + it "can stub! and mock" do + mock = mock("stubbing mock") + mock.stub!(:stub_message).and_return(:stub_value) + mock.should_receive(:mock_message).once.and_return(:mock_value) + (1..10).each {mock.stub_message.should equal(:stub_value)} + mock.mock_message.should equal(:mock_value) + (1..10).each {mock.stub_message.should equal(:stub_value)} + end + + it "can stub! and mock the same message" do + mock = mock("stubbing mock") + mock.stub!(:msg).and_return(:stub_value) + mock.should_receive(:msg).with(:arg).and_return(:mock_value) + mock.msg.should equal(:stub_value) + mock.msg(:other_arg).should equal(:stub_value) + mock.msg(:arg).should equal(:mock_value) + mock.msg(:another_arg).should equal(:stub_value) + mock.msg(:yet_another_arg).should equal(:stub_value) + mock.msg.should equal(:stub_value) + end +end + + diff --git a/vendor/gems/rspec/examples/stories/adder.rb b/vendor/gems/rspec/examples/stories/adder.rb new file mode 100644 index 0000000000..0b027b0ffb --- /dev/null +++ b/vendor/gems/rspec/examples/stories/adder.rb @@ -0,0 +1,13 @@ +class Adder + def initialize + @addends = [] + end + + def <<(val) + @addends << val + end + + def sum + @addends.inject(0) { |sum_so_far, val| sum_so_far + val } + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/examples/stories/addition b/vendor/gems/rspec/examples/stories/addition new file mode 100644 index 0000000000..58f0929900 --- /dev/null +++ b/vendor/gems/rspec/examples/stories/addition @@ -0,0 +1,34 @@ +This is a story about a calculator. The text up here above the Story: declaration +won't be processed, so you can write whatever you wish! + +Story: simple addition + + As an accountant + I want to add numbers + So that I can count beans + + Scenario: add one plus one + Given an addend of 1 + And an addend of 1 + + When the addends are addeds + + Then the sum should be 3 + And the corks should be popped + + Scenario: add two plus five + Given an addend of 2 + And an addend of 5 + + When the addends are added + + Then the sum should be 7 + Then it should snow + + Scenario: add three more + GivenScenario add two plus five + And an addend of 3 + + When the addends are added + + Then the sum should be 10 diff --git a/vendor/gems/rspec/examples/stories/addition.rb b/vendor/gems/rspec/examples/stories/addition.rb new file mode 100644 index 0000000000..e43f5cf39a --- /dev/null +++ b/vendor/gems/rspec/examples/stories/addition.rb @@ -0,0 +1,9 @@ +require File.join(File.dirname(__FILE__), "helper") +require File.join(File.dirname(__FILE__), "adder") + +# with_steps_for :addition, :more_addition do +with_steps_for :addition, :more_addition do + # Then("the corks should be popped") { } + run File.expand_path(__FILE__).gsub(".rb","") +end + diff --git a/vendor/gems/rspec/examples/stories/calculator.rb b/vendor/gems/rspec/examples/stories/calculator.rb new file mode 100644 index 0000000000..390437c552 --- /dev/null +++ b/vendor/gems/rspec/examples/stories/calculator.rb @@ -0,0 +1,65 @@ +$:.push File.join(File.dirname(__FILE__), *%w[.. .. lib]) +require 'spec' + +class AdditionMatchers < Spec::Story::StepGroup + steps do |add| + add.given("an addend of $addend") do |addend| + @adder ||= Adder.new + @adder << addend.to_i + end + end +end + +steps = AdditionMatchers.new do |add| + add.then("the sum should be $sum") do |sum| + @sum.should == sum.to_i + end +end + +steps.when("they are added") do + @sum = @adder.sum +end + +# This Story uses steps (see above) instead of blocks +# passed to Given, When and Then + +Story "addition", %{ + As an accountant + I want to add numbers + So that I can count some beans +}, :steps => steps do + Scenario "2 + 3" do + Given "an addend of 2" + And "an addend of 3" + When "they are added" + Then "the sum should be 5" + end + + # This scenario uses GivenScenario, which silently runs + # all the steps in a previous scenario. + + Scenario "add 4 more" do + GivenScenario "2 + 3" + Given "an addend of 4" + When "they are added" + Then "the sum should be 9" + end +end + +# And the class that makes the story pass + +class Adder + def << addend + addends << addend + end + + def sum + @addends.inject(0) do |result, addend| + result + addend.to_i + end + end + + def addends + @addends ||= [] + end +end diff --git a/vendor/gems/rspec/examples/stories/game-of-life/README.txt b/vendor/gems/rspec/examples/stories/game-of-life/README.txt new file mode 100644 index 0000000000..9624ad4118 --- /dev/null +++ b/vendor/gems/rspec/examples/stories/game-of-life/README.txt @@ -0,0 +1,21 @@ +John Conway's Game of Life + +The Rules +--------- +The Game of Life was invented by John Conway (as you might have gathered). +The game is played on a field of cells, each of which has eight neighbors (adjacent cells). +A cell is either occupied (by an organism) or not. +The rules for deriving a generation from the previous one are these: + +Survival +-------- +If an occupied cell has 2 or 3 neighbors, the organism survives to the next generation. + +Death +----- +If an occupied cell has 0, 1, 4, 5, 6, 7, or 8 occupied neighbors, the organism dies +(0, 1: of loneliness; 4 thru 8: of overcrowding). + +Birth +----- +If an unoccupied cell has 3 occupied neighbors, it becomes occupied. diff --git a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/everything.rb b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/everything.rb new file mode 100644 index 0000000000..90a281da52 --- /dev/null +++ b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/everything.rb @@ -0,0 +1,6 @@ +$:.unshift File.join(File.dirname(__FILE__), '..', '..', '..', '..', 'lib') +$:.unshift File.join(File.dirname(__FILE__), '..') + +require 'spec' +require 'behaviour/examples/examples' +require 'behaviour/stories/stories' diff --git a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/examples/examples.rb b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/examples/examples.rb new file mode 100644 index 0000000000..1cadfb3c1b --- /dev/null +++ b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/examples/examples.rb @@ -0,0 +1,3 @@ +require 'spec' +require 'behaviour/examples/game_behaviour' +require 'behaviour/examples/grid_behaviour' diff --git a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/examples/game_behaviour.rb b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/examples/game_behaviour.rb new file mode 100644 index 0000000000..ff5b357f0c --- /dev/null +++ b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/examples/game_behaviour.rb @@ -0,0 +1,35 @@ +require 'life' + +describe Game do + it 'should have a grid' do + # given + game = Game.new(5, 5) + + # then + game.grid.should be_kind_of(Grid) + end + + it 'should create a cell' do + # given + game = Game.new(2, 2) + expected_grid = Grid.from_string( 'X. ..' ) + + # when + game.create_at(0, 0) + + # then + game.grid.should == expected_grid + end + + it 'should destroy a cell' do + # given + game = Game.new(2,2) + game.grid = Grid.from_string('X. ..') + + # when + game.destroy_at(0,0) + + # then + game.grid.should == Grid.from_string('.. ..') + end +end diff --git a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/examples/grid_behaviour.rb b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/examples/grid_behaviour.rb new file mode 100644 index 0000000000..5be3af5199 --- /dev/null +++ b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/examples/grid_behaviour.rb @@ -0,0 +1,66 @@ +describe Grid do + it 'should be empty when created' do + # given + expected_contents = [ + [0, 0, 0], + [0, 0, 0] + ] + grid = Grid.new(2, 3) + + # when + contents = grid.contents + + # then + contents.should == expected_contents + end + + it 'should compare equal based on its contents' do + # given + grid1 = Grid.new(2, 3) + grid2 = Grid.new(2, 3) + + # then + grid1.should == grid2 + end + + it 'should be able to replace its contents' do + # given + grid = Grid.new(2,2) + new_contents = [[0,1,0], [1,0,1]] + + # when + grid.contents = new_contents + + # then + grid.contents.should == new_contents + grid.rows.should == 2 + grid.columns.should == 3 + end + + it 'should add an organism' do + # given + grid = Grid.new(2, 2) + expected = Grid.new(2, 2) + expected.contents = [[1,0],[0,0]] + + # when + grid.create_at(0,0) + + # then + grid.should == expected + end + + it 'should create itself from a string' do + # given + expected = Grid.new 3, 3 + expected.create_at(0,0) + expected.create_at(1,0) + expected.create_at(2,2) + + # when + actual = Grid.from_string "X.. X.. ..X" + + # then + actual.should == expected + end +end diff --git a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/CellsWithLessThanTwoNeighboursDie.story b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/CellsWithLessThanTwoNeighboursDie.story new file mode 100644 index 0000000000..8374e86c5e --- /dev/null +++ b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/CellsWithLessThanTwoNeighboursDie.story @@ -0,0 +1,21 @@ +Story: cells with less than two neighbours die + +As a game producer +I want cells with less than two neighbours to die +So that I can illustrate how the game works to people with money + +Scenario: cells with zero or one neighbour die + +Given the grid looks like +........ +.XX.XX.. +.XX..... +....X... +........ +When the next step occurs +Then the grid should look like +........ +.XX..... +.XX..... +........ +........ diff --git a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/CellsWithMoreThanThreeNeighboursDie.story b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/CellsWithMoreThanThreeNeighboursDie.story new file mode 100644 index 0000000000..0d30b59be4 --- /dev/null +++ b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/CellsWithMoreThanThreeNeighboursDie.story @@ -0,0 +1,21 @@ +Story: cells with more than three neighbours die + +As a game producer +I want cells with more than three neighbours to die +So that I can show the people with money how we are getting on + +Scenario: blink + +Given the grid looks like +..... +...XX +...XX +.XX.. +.XX.. +When the next step occurs +Then the grid should look like +..... +...XX +....X +.X... +.XX.. diff --git a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/EmptySpacesWithThreeNeighboursCreateACell.story b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/EmptySpacesWithThreeNeighboursCreateACell.story new file mode 100644 index 0000000000..cbc248e73a --- /dev/null +++ b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/EmptySpacesWithThreeNeighboursCreateACell.story @@ -0,0 +1,42 @@ +Story: Empty spaces with three neighbours create a cell + +As a game producer +I want empty cells with three neighbours to die +So that I have a minimum feature set to ship + +Scenario: the glider + +Given the grid looks like +...X.. +..X... +..XXX. +...... +...... +When the next step occurs +Then the grid should look like +...... +..X.X. +..XX.. +...X.. +...... +When the next step occurs +Then the grid should look like +...... +..X... +..X.X. +..XX.. +...... +When the next step occurs +Then the grid should look like +...... +...X.. +.XX... +..XX.. +...... +When the next step occurs +Then the grid should look like +...... +..X... +.X.... +.XXX.. +...... diff --git a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/ICanCreateACell.story b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/ICanCreateACell.story new file mode 100644 index 0000000000..88895cb69b --- /dev/null +++ b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/ICanCreateACell.story @@ -0,0 +1,42 @@ +Story: I can create a cell + +As a game producer +I want to create a cell +So that I can show the grid to people + +Scenario: nothing to see here + +Given a 3 x 3 game +Then the grid should look like +... +... +... + +Scenario: all on its lonesome + +Given a 3 x 3 game +When I create a cell at 1, 1 +Then the grid should look like +... +.X. +... + +Scenario: the grid has three cells + +Given a 3 x 3 game +When I create a cell at 0, 0 +and I create a cell at 0, 1 +and I create a cell at 2, 2 +Then the grid should look like +XX. +... +..X + +Scenario: more cells more more + +Given the grid has three cells +When I create a celll at 3, 1 +Then the grid should look like +XX. +..X +..X diff --git a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/ICanKillACell.story b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/ICanKillACell.story new file mode 100644 index 0000000000..a9cf1ac64f --- /dev/null +++ b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/ICanKillACell.story @@ -0,0 +1,17 @@ +Story: I can kill a cell + +As a game producer +I want to kill a cell +So that when I make a mistake I dont have to start again + +Scenario: bang youre dead + +Given the grid looks like +XX. +.X. +..X +When I destroy the cell at 0, 1 +Then the grid should look like +X.. +.X. +..X diff --git a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/TheGridWraps.story b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/TheGridWraps.story new file mode 100644 index 0000000000..aeeede77d3 --- /dev/null +++ b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/TheGridWraps.story @@ -0,0 +1,53 @@ +Story: The grid wraps + +As a game player +I want the grid to wrap +So that untidy stuff at the edges is avoided + +Scenario: crowded in the corners + +Given the grid looks like +X.X +... +X.X +When the next step is taken +Then the grid should look like +X.X +... +X.X + + +Scenario: the glider returns + +Given the glider +...... +..X... +.X.... +.XXX.. +...... +When the next step is taken +and the next step is taken +and the next step is taken +and the next step is taken +Then the grid should look like +...... +...... +.X.... +X..... +XXX... +When the next step is taken +Then the grid should look like +.X.... +...... +...... +X.X... +XX.... +When the next step is taken +Then the grid should look like +XX.... +...... +...... +X..... +X.X... + + diff --git a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/create_a_cell.rb b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/create_a_cell.rb new file mode 100644 index 0000000000..81f86baba3 --- /dev/null +++ b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/create_a_cell.rb @@ -0,0 +1,52 @@ +require File.join(File.dirname(__FILE__), *%w[helper]) + +Story "I can create a cell", + %(As a game producer + I want to create a cell + So that I can show the grid to people), :steps_for => :life do + + Scenario "nothing to see here" do + Given "a game with dimensions", 3, 3 do |rows,cols| + @game = Game.new(rows,cols) + end + + Then "the grid should look like", %( + ... + ... + ... + ) + end + + Scenario "all on its lonesome" do + Given "a game with dimensions", 2, 2 + When "I create a cell at", 1, 1 do |row,col| + @game.create_at(row,col) + end + Then "the grid should look like", %( + .. + .X + ) + end + + Scenario "the grid has three cells" do + Given "a game with dimensions", 3, 3 + When "I create a cell at", 0, 0 + When "I create a cell at", 0, 1 + When "I create a cell at", 2, 2 + Then "the grid should look like", %( + XX. + ... + ..X + ) + end + + Scenario "more cells more more" do + GivenScenario "the grid has three cells" + When "I create a cell at", 2, 0 + Then "the grid should look like", %( + XX. + ... + X.X + ) + end +end diff --git a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/helper.rb b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/helper.rb new file mode 100644 index 0000000000..70ed21ec52 --- /dev/null +++ b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/helper.rb @@ -0,0 +1,6 @@ +dir = File.dirname(__FILE__) +$LOAD_PATH.unshift(File.expand_path("#{dir}/../../../../../../rspec/lib")) +require 'spec' +$LOAD_PATH.unshift(File.expand_path("#{dir}/../../")) +require "#{dir}/../../life" +require File.join(File.dirname(__FILE__), *%w[steps]) \ No newline at end of file diff --git a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/kill_a_cell.rb b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/kill_a_cell.rb new file mode 100644 index 0000000000..7ae2d912db --- /dev/null +++ b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/kill_a_cell.rb @@ -0,0 +1,26 @@ +require File.join(File.dirname(__FILE__), *%w[helper]) + +Story 'I can kill a cell', + %(As a game producer + I want to kill a cell + So that when I make a mistake I don't have to start again), :steps_for => :life do + + Scenario "bang, you're dead" do + + Given 'a game that looks like', %( + XX. + .X. + ..X + ) do |dots| + @game = Game.from_string dots + end + When 'I destroy the cell at', 0, 1 do |row,col| + @game.destroy_at(row,col) + end + Then 'the grid should look like', %( + X.. + .X. + ..X + ) + end +end diff --git a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/steps.rb b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/steps.rb new file mode 100644 index 0000000000..793590d707 --- /dev/null +++ b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/steps.rb @@ -0,0 +1,5 @@ +steps_for :life do + Then "the grid should look like" do |dots| + @game.grid.should == Grid.from_string(dots) + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/stories.rb b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/stories.rb new file mode 100644 index 0000000000..e60fe01de4 --- /dev/null +++ b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/stories.rb @@ -0,0 +1,3 @@ +require File.join(File.dirname(__FILE__), *%w[helper]) +require 'behaviour/stories/create_a_cell' +require 'behaviour/stories/kill_a_cell' diff --git a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/stories.txt b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/stories.txt new file mode 100644 index 0000000000..d8f809be3c --- /dev/null +++ b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/stories.txt @@ -0,0 +1,22 @@ +Story: Show the game field + As a game player + I want to see the field + so that I can observe the progress of the organisms + +Scenario: an empty field + Given a new game starts + When the game displays the field + Then the field should be empty + + + + + +StoryBuilder story = stories.createStory().called("a story") + .asA("person") + .iWant("to do something") + .soThat("I can rule the world"); +story.addScenario().called("happy path").as() + .given("some context") + .when("some event happens") + .then("expect some outcome"); diff --git a/vendor/gems/rspec/examples/stories/game-of-life/life.rb b/vendor/gems/rspec/examples/stories/game-of-life/life.rb new file mode 100644 index 0000000000..88263bd004 --- /dev/null +++ b/vendor/gems/rspec/examples/stories/game-of-life/life.rb @@ -0,0 +1,3 @@ +$: << File.dirname(__FILE__) +require 'life/game' +require 'life/grid' diff --git a/vendor/gems/rspec/examples/stories/game-of-life/life/game.rb b/vendor/gems/rspec/examples/stories/game-of-life/life/game.rb new file mode 100644 index 0000000000..5411b01bf1 --- /dev/null +++ b/vendor/gems/rspec/examples/stories/game-of-life/life/game.rb @@ -0,0 +1,23 @@ +class Game + attr_accessor :grid + def initialize(rows,cols) + @grid = Grid.new(rows, cols) + end + + def create_at(row,col) + @grid.create_at(row,col) + end + + def destroy_at(row,col) + @grid.destroy_at(row, col) + end + + def self.from_string(dots) + grid = Grid.from_string(dots) + game = new(grid.rows, grid.columns) + game.instance_eval do + @grid = grid + end + return game + end +end diff --git a/vendor/gems/rspec/examples/stories/game-of-life/life/grid.rb b/vendor/gems/rspec/examples/stories/game-of-life/life/grid.rb new file mode 100644 index 0000000000..aca23087c1 --- /dev/null +++ b/vendor/gems/rspec/examples/stories/game-of-life/life/grid.rb @@ -0,0 +1,43 @@ +class Grid + + attr_accessor :contents + + def initialize(rows, cols) + @contents = [] + rows.times do @contents << [0] * cols end + end + + def rows + @contents.size + end + + def columns + @contents[0].size + end + + def ==(other) + self.contents == other.contents + end + + def create_at(row,col) + @contents[row][col] = 1 + end + + def destroy_at(row,col) + @contents[row][col] = 0 + end + + def self.from_string(str) + row_strings = str.split(' ') + grid = new(row_strings.size, row_strings[0].size) + + row_strings.each_with_index do |row, row_index| + row_chars = row.split(//) + row_chars.each_with_index do |col_char, col_index| + grid.create_at(row_index, col_index) if col_char == 'X' + end + end + return grid + end + +end diff --git a/vendor/gems/rspec/examples/stories/helper.rb b/vendor/gems/rspec/examples/stories/helper.rb new file mode 100644 index 0000000000..2e825b2785 --- /dev/null +++ b/vendor/gems/rspec/examples/stories/helper.rb @@ -0,0 +1,9 @@ +$:.unshift File.join(File.dirname(__FILE__), '..', '..', 'lib') +require 'spec/story' + +# won't have to do this once plain_text_story_runner is moved into the library +# require File.join(File.dirname(__FILE__), "plain_text_story_runner") + +Dir[File.join(File.dirname(__FILE__), "steps/*.rb")].each do |file| + require file +end \ No newline at end of file diff --git a/vendor/gems/rspec/examples/stories/steps/addition_steps.rb b/vendor/gems/rspec/examples/stories/steps/addition_steps.rb new file mode 100644 index 0000000000..3f27095a9f --- /dev/null +++ b/vendor/gems/rspec/examples/stories/steps/addition_steps.rb @@ -0,0 +1,18 @@ +require File.expand_path("#{File.dirname(__FILE__)}/../helper") + +# This creates steps for :addition +steps_for(:addition) do + Given("an addend of $addend") do |addend| + @adder ||= Adder.new + @adder << addend.to_i + end +end + +# This appends to them +steps_for(:addition) do + When("the addends are added") { @sum = @adder.sum } +end + +steps_for(:more_addition) do + Then("the sum should be $sum") { |sum| @sum.should == sum.to_i } +end diff --git a/vendor/gems/rspec/failing_examples/README.txt b/vendor/gems/rspec/failing_examples/README.txt new file mode 100644 index 0000000000..38c667d929 --- /dev/null +++ b/vendor/gems/rspec/failing_examples/README.txt @@ -0,0 +1,7 @@ +"Why have failing examples?", you might ask. + +They allow us to see failure messages. RSpec wants to provide meaningful and helpful failure messages. The failures in this directory not only provide you a way of seeing the failure messages, but they provide RSpec's own specs a way of describing what they should look like and ensuring they stay correct. + +To see the types of messages you can expect, stand in this directory and run: + +../bin/spec ./*.rb \ No newline at end of file diff --git a/vendor/gems/rspec/failing_examples/diffing_spec.rb b/vendor/gems/rspec/failing_examples/diffing_spec.rb new file mode 100644 index 0000000000..85e13e8c04 --- /dev/null +++ b/vendor/gems/rspec/failing_examples/diffing_spec.rb @@ -0,0 +1,36 @@ +describe "Running specs with --diff" do + it "should print diff of different strings" do + uk = <<-EOF +RSpec is a +behaviour driven development +framework for Ruby +EOF + usa = <<-EOF +RSpec is a +behavior driven development +framework for Ruby +EOF + usa.should == uk + end + + class Animal + def initialize(name,species) + @name,@species = name,species + end + + def inspect + <<-EOA + + EOA + end + end + + it "should print diff of different objects' pretty representation" do + expected = Animal.new "bob", "giraffe" + actual = Animal.new "bob", "tortoise" + expected.should eql(actual) + end +end diff --git a/vendor/gems/rspec/failing_examples/failing_autogenerated_docstrings_example.rb b/vendor/gems/rspec/failing_examples/failing_autogenerated_docstrings_example.rb new file mode 100644 index 0000000000..8a7d2490ed --- /dev/null +++ b/vendor/gems/rspec/failing_examples/failing_autogenerated_docstrings_example.rb @@ -0,0 +1,19 @@ +require File.dirname(__FILE__) + '/spec_helper' + +# Run spec w/ -fs to see the output of this file + +describe "Failing examples with no descriptions" do + + # description is auto-generated as "should equal(5)" based on the last #should + it do + 3.should equal(2) + 5.should equal(5) + end + + it { 3.should be > 5 } + + it { ["a"].should include("b") } + + it { [1,2,3].should_not respond_to(:size) } + +end diff --git a/vendor/gems/rspec/failing_examples/failure_in_setup.rb b/vendor/gems/rspec/failing_examples/failure_in_setup.rb new file mode 100644 index 0000000000..2a807a99a3 --- /dev/null +++ b/vendor/gems/rspec/failing_examples/failure_in_setup.rb @@ -0,0 +1,10 @@ +describe "This example" do + + before(:each) do + NonExistentClass.new + end + + it "should be listed as failing in setup" do + end + +end diff --git a/vendor/gems/rspec/failing_examples/failure_in_teardown.rb b/vendor/gems/rspec/failing_examples/failure_in_teardown.rb new file mode 100644 index 0000000000..6458ea2b89 --- /dev/null +++ b/vendor/gems/rspec/failing_examples/failure_in_teardown.rb @@ -0,0 +1,10 @@ +describe "This example" do + + it "should be listed as failing in teardown" do + end + + after(:each) do + NonExistentClass.new + end + +end diff --git a/vendor/gems/rspec/failing_examples/mocking_example.rb b/vendor/gems/rspec/failing_examples/mocking_example.rb new file mode 100644 index 0000000000..caf2db0364 --- /dev/null +++ b/vendor/gems/rspec/failing_examples/mocking_example.rb @@ -0,0 +1,40 @@ +require File.dirname(__FILE__) + '/spec_helper' + +describe "Mocker" do + + it "should be able to call mock()" do + mock = mock("poke me") + mock.should_receive(:poke) + mock.poke + end + + it "should fail when expected message not received" do + mock = mock("poke me") + mock.should_receive(:poke) + end + + it "should fail when messages are received out of order" do + mock = mock("one two three") + mock.should_receive(:one).ordered + mock.should_receive(:two).ordered + mock.should_receive(:three).ordered + mock.one + mock.three + mock.two + end + + it "should get yelled at when sending unexpected messages" do + mock = mock("don't talk to me") + mock.should_not_receive(:any_message_at_all) + mock.any_message_at_all + end + + it "has a bug we need to fix" do + pending "here is the bug" do + # Actually, no. It's fixed. This will fail because it passes :-) + mock = mock("Bug") + mock.should_receive(:hello) + mock.hello + end + end +end diff --git a/vendor/gems/rspec/failing_examples/mocking_with_flexmock.rb b/vendor/gems/rspec/failing_examples/mocking_with_flexmock.rb new file mode 100644 index 0000000000..6e79ece0ee --- /dev/null +++ b/vendor/gems/rspec/failing_examples/mocking_with_flexmock.rb @@ -0,0 +1,26 @@ +# stub frameworks like to gum up Object, so this is deliberately +# set NOT to run so that you don't accidentally run it when you +# run this dir. + +# To run it, stand in this directory and say: +# +# RUN_FLEXMOCK_EXAMPLE=true ruby ../bin/spec mocking_with_flexmock.rb + +if ENV['RUN_FLEXMOCK_EXAMPLE'] + Spec::Runner.configure do |config| + config.mock_with :flexmock + end + + describe "Flexmocks" do + it "should fail when the expected message is received with wrong arguments" do + m = flexmock("now flex!") + m.should_receive(:msg).with("arg").once + m.msg("other arg") + end + + it "should fail when the expected message is not received at all" do + m = flexmock("now flex!") + m.should_receive(:msg).with("arg").once + end + end +end diff --git a/vendor/gems/rspec/failing_examples/mocking_with_mocha.rb b/vendor/gems/rspec/failing_examples/mocking_with_mocha.rb new file mode 100644 index 0000000000..f14a1a3e5c --- /dev/null +++ b/vendor/gems/rspec/failing_examples/mocking_with_mocha.rb @@ -0,0 +1,25 @@ +# stub frameworks like to gum up Object, so this is deliberately +# set NOT to run so that you don't accidentally run it when you +# run this dir. + +# To run it, stand in this directory and say: +# +# RUN_MOCHA_EXAMPLE=true ruby ../bin/spec mocking_with_mocha.rb + +if ENV['RUN_MOCHA_EXAMPLE'] + Spec::Runner.configure do |config| + config.mock_with :mocha + end + describe "Mocha framework" do + it "should should be made available by saying config.mock_with :mocha" do + m = mock() + m.expects(:msg).with("arg") + m.msg + end + it "should should be made available by saying config.mock_with :mocha" do + o = Object.new + o.expects(:msg).with("arg") + o.msg + end + end +end diff --git a/vendor/gems/rspec/failing_examples/mocking_with_rr.rb b/vendor/gems/rspec/failing_examples/mocking_with_rr.rb new file mode 100644 index 0000000000..0d2b4fe044 --- /dev/null +++ b/vendor/gems/rspec/failing_examples/mocking_with_rr.rb @@ -0,0 +1,27 @@ +# stub frameworks like to gum up Object, so this is deliberately +# set NOT to run so that you don't accidentally run it when you +# run this dir. + +# To run it, stand in this directory and say: +# +# RUN_RR_EXAMPLE=true ruby ../bin/spec mocking_with_rr.rb + +if ENV['RUN_RR_EXAMPLE'] + Spec::Runner.configure do |config| + config.mock_with :rr + end + describe "RR framework" do + it "should should be made available by saying config.mock_with :rr" do + o = Object.new + mock(o).msg("arg") + o.msg + end + it "should should be made available by saying config.mock_with :rr" do + o = Object.new + mock(o) do |m| + m.msg("arg") + end + o.msg + end + end +end diff --git a/vendor/gems/rspec/failing_examples/partial_mock_example.rb b/vendor/gems/rspec/failing_examples/partial_mock_example.rb new file mode 100644 index 0000000000..6d05540556 --- /dev/null +++ b/vendor/gems/rspec/failing_examples/partial_mock_example.rb @@ -0,0 +1,20 @@ +require File.dirname(__FILE__) + '/spec_helper' + +class MockableClass + def self.find id + return :original_return + end +end + +describe "A partial mock" do + + it "should work at the class level (but fail here due to the type mismatch)" do + MockableClass.should_receive(:find).with(1).and_return {:stub_return} + MockableClass.find("1").should equal(:stub_return) + end + + it "should revert to the original after each spec" do + MockableClass.find(1).should equal(:original_return) + end + +end diff --git a/vendor/gems/rspec/failing_examples/predicate_example.rb b/vendor/gems/rspec/failing_examples/predicate_example.rb new file mode 100644 index 0000000000..53b6367e2d --- /dev/null +++ b/vendor/gems/rspec/failing_examples/predicate_example.rb @@ -0,0 +1,29 @@ +require File.dirname(__FILE__) + '/spec_helper' + +class BddFramework + def intuitive? + true + end + + def adopted_quickly? + #this will cause failures because it reallly SHOULD be adopted quickly + false + end +end + +describe "BDD framework" do + + before(:each) do + @bdd_framework = BddFramework.new + end + + it "should be adopted quickly" do + #this will fail because it reallly SHOULD be adopted quickly + @bdd_framework.should be_adopted_quickly + end + + it "should be intuitive" do + @bdd_framework.should be_intuitive + end + +end diff --git a/vendor/gems/rspec/failing_examples/raising_example.rb b/vendor/gems/rspec/failing_examples/raising_example.rb new file mode 100644 index 0000000000..e40b51ec8f --- /dev/null +++ b/vendor/gems/rspec/failing_examples/raising_example.rb @@ -0,0 +1,47 @@ +describe "This example" do + + it "should show that a NoMethodError is raised but an Exception was expected" do + proc { ''.nonexistent_method }.should raise_error + end + + it "should pass" do + proc { ''.nonexistent_method }.should raise_error(NoMethodError) + end + + it "should show that a NoMethodError is raised but a SyntaxError was expected" do + proc { ''.nonexistent_method }.should raise_error(SyntaxError) + end + + it "should show that nothing is raised when SyntaxError was expected" do + proc { }.should raise_error(SyntaxError) + end + + it "should show that a NoMethodError is raised but a Exception was expected" do + proc { ''.nonexistent_method }.should_not raise_error + end + + it "should show that a NoMethodError is raised" do + proc { ''.nonexistent_method }.should_not raise_error(NoMethodError) + end + + it "should also pass" do + proc { ''.nonexistent_method }.should_not raise_error(SyntaxError) + end + + it "should show that a NoMethodError is raised when nothing expected" do + proc { ''.nonexistent_method }.should_not raise_error(Exception) + end + + it "should show that the wrong message was received" do + proc { raise StandardError.new("what is an enterprise?") }.should raise_error(StandardError, "not this") + end + + it "should show that the unexpected error/message was thrown" do + proc { raise StandardError.new("abc") }.should_not raise_error(StandardError, "abc") + end + + it "should pass too" do + proc { raise StandardError.new("abc") }.should_not raise_error(StandardError, "xyz") + end + +end diff --git a/vendor/gems/rspec/failing_examples/spec_helper.rb b/vendor/gems/rspec/failing_examples/spec_helper.rb new file mode 100644 index 0000000000..f8d657554f --- /dev/null +++ b/vendor/gems/rspec/failing_examples/spec_helper.rb @@ -0,0 +1,3 @@ +lib_path = File.expand_path("#{File.dirname(__FILE__)}/../lib") +$LOAD_PATH.unshift lib_path unless $LOAD_PATH.include?(lib_path) +require "spec" diff --git a/vendor/gems/rspec/failing_examples/syntax_error_example.rb b/vendor/gems/rspec/failing_examples/syntax_error_example.rb new file mode 100644 index 0000000000..c9bb907742 --- /dev/null +++ b/vendor/gems/rspec/failing_examples/syntax_error_example.rb @@ -0,0 +1,7 @@ +describe "when passing a block to a matcher" do + it "you should use {} instead of do/end" do + Object.new.should satisfy do + "this block is being passed to #should instead of #satisfy - use {} instead" + end + end +end diff --git a/vendor/gems/rspec/failing_examples/team_spec.rb b/vendor/gems/rspec/failing_examples/team_spec.rb new file mode 100644 index 0000000000..41a44e5518 --- /dev/null +++ b/vendor/gems/rspec/failing_examples/team_spec.rb @@ -0,0 +1,44 @@ +require File.dirname(__FILE__) + '/spec_helper' + + +class Team + attr_reader :players + def initialize + @players = Players.new + end +end + +class Players + def initialize + @players = [] + end + def size + @players.size + end + def include? player + raise "player must be a string" unless player.is_a?(String) + @players.include? player + end +end + +describe "A new team" do + + before(:each) do + @team = Team.new + end + + it "should have 3 players (failing example)" do + @team.should have(3).players + end + + it "should include some player (failing example)" do + @team.players.should include("Some Player") + end + + it "should include 5 (failing example)" do + @team.players.should include(5) + end + + it "should have no players" + +end diff --git a/vendor/gems/rspec/failing_examples/timeout_behaviour.rb b/vendor/gems/rspec/failing_examples/timeout_behaviour.rb new file mode 100644 index 0000000000..18221365fd --- /dev/null +++ b/vendor/gems/rspec/failing_examples/timeout_behaviour.rb @@ -0,0 +1,7 @@ +require File.dirname(__FILE__) + '/spec_helper' + +describe "Something really slow" do + it "should be failed by RSpec when it takes longer than --timeout" do + sleep(2) + end +end diff --git a/vendor/gems/rspec/lib/autotest/discover.rb b/vendor/gems/rspec/lib/autotest/discover.rb new file mode 100644 index 0000000000..81914c3b7a --- /dev/null +++ b/vendor/gems/rspec/lib/autotest/discover.rb @@ -0,0 +1,3 @@ +Autotest.add_discovery do + "rspec" if File.exist?('spec') +end diff --git a/vendor/gems/rspec/lib/autotest/rspec.rb b/vendor/gems/rspec/lib/autotest/rspec.rb new file mode 100644 index 0000000000..9c97d2e0d9 --- /dev/null +++ b/vendor/gems/rspec/lib/autotest/rspec.rb @@ -0,0 +1,74 @@ +require 'autotest' + +Autotest.add_hook :initialize do |at| + at.clear_mappings + # watch out: Ruby bug (1.8.6): + # %r(/) != /\// + at.add_mapping(%r%^spec/.*\.rb$%) { |filename, _| + filename + } + at.add_mapping(%r%^lib/(.*)\.rb$%) { |_, m| + ["spec/#{m[1]}_spec.rb"] + } + at.add_mapping(%r%^spec/(spec_helper|shared/.*)\.rb$%) { + at.files_matching %r%^spec/.*_spec\.rb$% + } +end + +class RspecCommandError < StandardError; end + +class Autotest::Rspec < Autotest + + def initialize + super + + self.failed_results_re = /^\d+\)\n(?:\e\[\d*m)?(?:.*?Error in )?'([^\n]*)'(?: FAILED)?(?:\e\[\d*m)?\n(.*?)\n\n/m + self.completed_re = /\Z/ # FIX: some sort of summary line at the end? + end + + def consolidate_failures(failed) + filters = Hash.new { |h,k| h[k] = [] } + failed.each do |spec, failed_trace| + if f = test_files_for(failed).find { |f| failed_trace =~ Regexp.new(f) } then + filters[f] << spec + break + end + end + return filters + end + + def make_test_cmd(files_to_test) + return "#{ruby} -S #{spec_command} #{add_options_if_present} #{files_to_test.keys.flatten.join(' ')}" + end + + def add_options_if_present + File.exist?("spec/spec.opts") ? "-O spec/spec.opts " : "" + end + + # Finds the proper spec command to use. Precendence is set in the + # lazily-evaluated method spec_commands. Alias + Override that in + # ~/.autotest to provide a different spec command then the default + # paths provided. + def spec_command(separator=File::ALT_SEPARATOR) + unless defined? @spec_command then + @spec_command = spec_commands.find { |cmd| File.exists? cmd } + + raise RspecCommandError, "No spec command could be found!" unless @spec_command + + @spec_command.gsub! File::SEPARATOR, separator if separator + end + @spec_command + end + + # Autotest will look for spec commands in the following + # locations, in this order: + # + # * bin/spec + # * default spec bin/loader installed in Rubygems + def spec_commands + [ + File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'bin', 'spec')), + File.join(Config::CONFIG['bindir'], 'spec') + ] + end +end diff --git a/vendor/gems/rspec/lib/spec.rb b/vendor/gems/rspec/lib/spec.rb new file mode 100644 index 0000000000..c143aa885c --- /dev/null +++ b/vendor/gems/rspec/lib/spec.rb @@ -0,0 +1,30 @@ +require 'spec/version' +require 'spec/matchers' +require 'spec/expectations' +require 'spec/example' +require 'spec/extensions' +require 'spec/runner' + +if Object.const_defined?(:Test); \ + require 'spec/interop/test'; \ +end + +module Spec + class << self + def run? + @run || rspec_options.examples_run? + end + + def run; \ + return true if run?; \ + result = rspec_options.run_examples; \ + @run = true; \ + result; \ + end + attr_writer :run + + def exit?; \ + !Object.const_defined?(:Test) || Test::Unit.run?; \ + end + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/lib/spec/example.rb b/vendor/gems/rspec/lib/spec/example.rb new file mode 100644 index 0000000000..39ff76b99e --- /dev/null +++ b/vendor/gems/rspec/lib/spec/example.rb @@ -0,0 +1,12 @@ +require 'timeout' +require 'forwardable' +require 'spec/example/pending' +require 'spec/example/module_reopening_fix' +require 'spec/example/example_group_methods' +require 'spec/example/example_methods' +require 'spec/example/example_group' +require 'spec/example/shared_example_group' +require 'spec/example/example_group_factory' +require 'spec/example/errors' +require 'spec/example/configuration' +require 'spec/example/example_matcher' diff --git a/vendor/gems/rspec/lib/spec/example/configuration.rb b/vendor/gems/rspec/lib/spec/example/configuration.rb new file mode 100755 index 0000000000..6741847270 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/example/configuration.rb @@ -0,0 +1,144 @@ +module Spec + module Example + class Configuration + # Chooses what mock framework to use. Example: + # + # Spec::Runner.configure do |config| + # config.mock_with :rspec, :mocha, :flexmock, or :rr + # end + # + # To use any other mock framework, you'll have to provide + # your own adapter. This is simply a module that responds to + # setup_mocks_for_rspec, verify_mocks_for_rspec and teardown_mocks_for_rspec. + # These are your hooks into the lifecycle of a given example. RSpec will + # call setup_mocks_for_rspec before running anything else in each Example. + # After executing the #after methods, RSpec will then call verify_mocks_for_rspec + # and teardown_mocks_for_rspec (this is guaranteed to run even if there are + # failures in verify_mocks_for_rspec). + # + # Once you've defined this module, you can pass that to mock_with: + # + # Spec::Runner.configure do |config| + # config.mock_with MyMockFrameworkAdapter + # end + # + def mock_with(mock_framework) + @mock_framework = case mock_framework + when Symbol + mock_framework_path(mock_framework.to_s) + else + mock_framework + end + end + + def mock_framework # :nodoc: + @mock_framework ||= mock_framework_path("rspec") + end + + # Declares modules to be included in all example groups (describe blocks). + # + # config.include(My::Bottle, My::Cup) + # + # If you want to restrict the inclusion to a subset of all the example groups then + # specify this in a Hash as the last argument: + # + # config.include(My::Pony, My::Horse, :type => :farm) + # + # Only example groups that have that type will get the modules included: + # + # describe "Downtown", :type => :city do + # # Will *not* get My::Pony and My::Horse included + # end + # + # describe "Old Mac Donald", :type => :farm do + # # *Will* get My::Pony and My::Horse included + # end + # + def include(*args) + args << {} unless Hash === args.last + modules, options = args_and_options(*args) + required_example_group = get_type_from_options(options) + required_example_group = required_example_group.to_sym if required_example_group + modules.each do |mod| + ExampleGroupFactory.get(required_example_group).send(:include, mod) + end + end + + # Defines global predicate matchers. Example: + # + # config.predicate_matchers[:swim] = :can_swim? + # + # This makes it possible to say: + # + # person.should swim # passes if person.should_swim? returns true + # + def predicate_matchers + @predicate_matchers ||= {} + end + + # Prepends a global before block to all example groups. + # See #append_before for filtering semantics. + def prepend_before(*args, &proc) + scope, options = scope_and_options(*args) + example_group = ExampleGroupFactory.get( + get_type_from_options(options) + ) + example_group.prepend_before(scope, &proc) + end + # Appends a global before block to all example groups. + # + # If you want to restrict the block to a subset of all the example groups then + # specify this in a Hash as the last argument: + # + # config.prepend_before(:all, :type => :farm) + # + # or + # + # config.prepend_before(:type => :farm) + # + def append_before(*args, &proc) + scope, options = scope_and_options(*args) + example_group = ExampleGroupFactory.get( + get_type_from_options(options) + ) + example_group.append_before(scope, &proc) + end + alias_method :before, :append_before + + # Prepends a global after block to all example groups. + # See #append_before for filtering semantics. + def prepend_after(*args, &proc) + scope, options = scope_and_options(*args) + example_group = ExampleGroupFactory.get( + get_type_from_options(options) + ) + example_group.prepend_after(scope, &proc) + end + alias_method :after, :prepend_after + # Appends a global after block to all example groups. + # See #append_before for filtering semantics. + def append_after(*args, &proc) + scope, options = scope_and_options(*args) + example_group = ExampleGroupFactory.get( + get_type_from_options(options) + ) + example_group.append_after(scope, &proc) + end + + private + + def scope_and_options(*args) + args, options = args_and_options(*args) + scope = (args[0] || :each), options + end + + def get_type_from_options(options) + options[:type] || options[:behaviour_type] + end + + def mock_framework_path(framework_name) + File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "plugins", "mock_frameworks", framework_name)) + end + end + end +end diff --git a/vendor/gems/rspec/lib/spec/example/errors.rb b/vendor/gems/rspec/lib/spec/example/errors.rb new file mode 100644 index 0000000000..c6cb22453f --- /dev/null +++ b/vendor/gems/rspec/lib/spec/example/errors.rb @@ -0,0 +1,9 @@ +module Spec + module Example + class ExamplePendingError < StandardError + end + + class PendingExampleFixedError < StandardError + end + end +end diff --git a/vendor/gems/rspec/lib/spec/example/example_group.rb b/vendor/gems/rspec/lib/spec/example/example_group.rb new file mode 100644 index 0000000000..d6e156f934 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/example/example_group.rb @@ -0,0 +1,16 @@ +module Spec + module Example + # The superclass for all regular RSpec examples. + class ExampleGroup + extend Spec::Example::ExampleGroupMethods + include Spec::Example::ExampleMethods + + def initialize(defined_description, &implementation) + @_defined_description = defined_description + @_implementation = implementation + end + end + end +end + +Spec::ExampleGroup = Spec::Example::ExampleGroup diff --git a/vendor/gems/rspec/lib/spec/example/example_group_factory.rb b/vendor/gems/rspec/lib/spec/example/example_group_factory.rb new file mode 100755 index 0000000000..0414a3b967 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/example/example_group_factory.rb @@ -0,0 +1,62 @@ +module Spec + module Example + class ExampleGroupFactory + class << self + def reset + @example_group_types = nil + default(ExampleGroup) + end + + # Registers an example group class +klass+ with the symbol + # +type+. For example: + # + # Spec::Example::ExampleGroupFactory.register(:farm, Spec::Farm::Example::FarmExampleGroup) + # + # This will cause Main#describe from a file living in + # spec/farm to create example group instances of type + # Spec::Farm::Example::FarmExampleGroup. + def register(id, example_group_class) + @example_group_types[id] = example_group_class + end + + # Sets the default ExampleGroup class + def default(example_group_class) + old = @example_group_types + @example_group_types = Hash.new(example_group_class) + @example_group_types.merge(old) if old + end + + def get(id=nil) + if @example_group_types.values.include?(id) + id + else + @example_group_types[id] + end + end + + def create_example_group(*args, &block) + opts = Hash === args.last ? args.last : {} + if opts[:shared] + SharedExampleGroup.new(*args, &block) + else + superclass = determine_superclass(opts) + superclass.describe(*args, &block) + end + end + + protected + + def determine_superclass(opts) + id = if opts[:type] + opts[:type] + elsif opts[:spec_path] =~ /spec(\\|\/)(#{@example_group_types.keys.join('|')})/ + $2 == '' ? nil : $2.to_sym + end + get(id) + end + + end + self.reset + end + end +end diff --git a/vendor/gems/rspec/lib/spec/example/example_group_methods.rb b/vendor/gems/rspec/lib/spec/example/example_group_methods.rb new file mode 100644 index 0000000000..a348bc74ba --- /dev/null +++ b/vendor/gems/rspec/lib/spec/example/example_group_methods.rb @@ -0,0 +1,424 @@ +module Spec + module Example + + module ExampleGroupMethods + class << self + def description_text(*args) + args.inject("") do |result, arg| + result << " " unless (result == "" || arg.to_s =~ /^(\s|\.|#)/) + result << arg.to_s + end + end + end + + attr_reader :description_text, :description_args, :description_options, :spec_path, :registration_binding_block + + def inherited(klass) + super + klass.register {} + Spec::Runner.register_at_exit_hook + end + + # Makes the describe/it syntax available from a class. For example: + # + # class StackSpec < Spec::ExampleGroup + # describe Stack, "with no elements" + # + # before + # @stack = Stack.new + # end + # + # it "should raise on pop" do + # lambda{ @stack.pop }.should raise_error + # end + # end + # + def describe(*args, &example_group_block) + if example_group_block + self.subclass("Subclass") do + describe(*args) + module_eval(&example_group_block) + end + else + set_description(*args) + before_eval + self + end + end + + # Use this to pull in examples from shared example groups. + # See Spec::Runner for information about shared example groups. + def it_should_behave_like(shared_example_group) + case shared_example_group + when SharedExampleGroup + include shared_example_group + else + example_group = SharedExampleGroup.find_shared_example_group(shared_example_group) + unless example_group + raise RuntimeError.new("Shared Example Group '#{shared_example_group}' can not be found") + end + include(example_group) + end + end + + # :call-seq: + # predicate_matchers[matcher_name] = method_on_object + # predicate_matchers[matcher_name] = [method1_on_object, method2_on_object] + # + # Dynamically generates a custom matcher that will match + # a predicate on your class. RSpec provides a couple of these + # out of the box: + # + # exist (or state expectations) + # File.should exist("path/to/file") + # + # an_instance_of (for mock argument constraints) + # mock.should_receive(:message).with(an_instance_of(String)) + # + # == Examples + # + # class Fish + # def can_swim? + # true + # end + # end + # + # describe Fish do + # predicate_matchers[:swim] = :can_swim? + # it "should swim" do + # Fish.new.should swim + # end + # end + def predicate_matchers + @predicate_matchers ||= {:an_instance_of => :is_a?} + end + + # Creates an instance of Spec::Example::Example and adds + # it to a collection of examples of the current example group. + def it(description=nil, &implementation) + e = new(description, &implementation) + example_objects << e + e + end + + alias_method :specify, :it + + # Use this to temporarily disable an example. + def xit(description=nil, opts={}, &block) + Kernel.warn("Example disabled: #{description}") + end + + def run + examples = examples_to_run + return true if examples.empty? + reporter.add_example_group(self) + return dry_run(examples) if dry_run? + + plugin_mock_framework + define_methods_from_predicate_matchers + + success, before_all_instance_variables = run_before_all + success, after_all_instance_variables = execute_examples(success, before_all_instance_variables, examples) + success = run_after_all(success, after_all_instance_variables) + end + + def description + result = ExampleGroupMethods.description_text(*description_parts) + if result.nil? || result == "" + return to_s + else + result + end + end + + def described_type + description_parts.find {|part| part.is_a?(Module)} + end + + def description_parts #:nodoc: + parts = [] + execute_in_class_hierarchy do |example_group| + parts << example_group.description_args + end + parts.flatten.compact + end + + def set_description(*args) + args, options = args_and_options(*args) + @description_args = args + @description_options = options + @description_text = ExampleGroupMethods.description_text(*args) + @spec_path = File.expand_path(options[:spec_path]) if options[:spec_path] + if described_type.class == Module + include described_type + end + self + end + + def examples #:nodoc: + examples = example_objects.dup + add_method_examples(examples) + rspec_options.reverse ? examples.reverse : examples + end + + def number_of_examples #:nodoc: + examples.length + end + + # Registers a block to be executed before each example. + # This method prepends +block+ to existing before blocks. + def prepend_before(*args, &block) + scope, options = scope_and_options(*args) + parts = before_parts_from_scope(scope) + parts.unshift(block) + end + + # Registers a block to be executed before each example. + # This method appends +block+ to existing before blocks. + def append_before(*args, &block) + scope, options = scope_and_options(*args) + parts = before_parts_from_scope(scope) + parts << block + end + alias_method :before, :append_before + + # Registers a block to be executed after each example. + # This method prepends +block+ to existing after blocks. + def prepend_after(*args, &block) + scope, options = scope_and_options(*args) + parts = after_parts_from_scope(scope) + parts.unshift(block) + end + alias_method :after, :prepend_after + + # Registers a block to be executed after each example. + # This method appends +block+ to existing after blocks. + def append_after(*args, &block) + scope, options = scope_and_options(*args) + parts = after_parts_from_scope(scope) + parts << block + end + + def remove_after(scope, &block) + after_each_parts.delete(block) + end + + # Deprecated. Use before(:each) + def setup(&block) + before(:each, &block) + end + + # Deprecated. Use after(:each) + def teardown(&block) + after(:each, &block) + end + + def before_all_parts # :nodoc: + @before_all_parts ||= [] + end + + def after_all_parts # :nodoc: + @after_all_parts ||= [] + end + + def before_each_parts # :nodoc: + @before_each_parts ||= [] + end + + def after_each_parts # :nodoc: + @after_each_parts ||= [] + end + + # Only used from RSpec's own examples + def reset # :nodoc: + @before_all_parts = nil + @after_all_parts = nil + @before_each_parts = nil + @after_each_parts = nil + end + + def register(®istration_binding_block) + @registration_binding_block = registration_binding_block + rspec_options.add_example_group self + end + + def unregister #:nodoc: + rspec_options.remove_example_group self + end + + def registration_backtrace + eval("caller", registration_binding_block.binding) + end + + def run_before_each(example) + execute_in_class_hierarchy do |example_group| + example.eval_each_fail_fast(example_group.before_each_parts) + end + end + + def run_after_each(example) + execute_in_class_hierarchy(:superclass_first) do |example_group| + example.eval_each_fail_slow(example_group.after_each_parts) + end + end + + private + def dry_run(examples) + examples.each do |example| + rspec_options.reporter.example_started(example) + rspec_options.reporter.example_finished(example) + end + return true + end + + def run_before_all + before_all = new("before(:all)") + begin + execute_in_class_hierarchy do |example_group| + before_all.eval_each_fail_fast(example_group.before_all_parts) + end + return [true, before_all.instance_variable_hash] + rescue Exception => e + reporter.failure(before_all, e) + return [false, before_all.instance_variable_hash] + end + end + + def execute_examples(success, instance_variables, examples) + return [success, instance_variables] unless success + + after_all_instance_variables = instance_variables + examples.each do |example_group_instance| + success &= example_group_instance.execute(rspec_options, instance_variables) + after_all_instance_variables = example_group_instance.instance_variable_hash + end + return [success, after_all_instance_variables] + end + + def run_after_all(success, instance_variables) + after_all = new("after(:all)") + after_all.set_instance_variables_from_hash(instance_variables) + execute_in_class_hierarchy(:superclass_first) do |example_group| + after_all.eval_each_fail_slow(example_group.after_all_parts) + end + return success + rescue Exception => e + reporter.failure(after_all, e) + return false + end + + def examples_to_run + all_examples = examples + return all_examples unless specified_examples? + all_examples.reject do |example| + matcher = ExampleMatcher.new(description.to_s, example.description) + !matcher.matches?(specified_examples) + end + end + + def specified_examples? + specified_examples && !specified_examples.empty? + end + + def specified_examples + rspec_options.examples + end + + def reporter + rspec_options.reporter + end + + def dry_run? + rspec_options.dry_run + end + + def example_objects + @example_objects ||= [] + end + + def execute_in_class_hierarchy(superclass_last=false) + classes = [] + current_class = self + while is_example_group?(current_class) + superclass_last ? classes << current_class : classes.unshift(current_class) + current_class = current_class.superclass + end + superclass_last ? classes << ExampleMethods : classes.unshift(ExampleMethods) + + classes.each do |example_group| + yield example_group + end + end + + def is_example_group?(klass) + Module === klass && klass.kind_of?(ExampleGroupMethods) + end + + def plugin_mock_framework + case mock_framework = Spec::Runner.configuration.mock_framework + when Module + include mock_framework + else + require Spec::Runner.configuration.mock_framework + include Spec::Plugins::MockFramework + end + end + + def define_methods_from_predicate_matchers # :nodoc: + all_predicate_matchers = predicate_matchers.merge( + Spec::Runner.configuration.predicate_matchers + ) + all_predicate_matchers.each_pair do |matcher_method, method_on_object| + define_method matcher_method do |*args| + eval("be_#{method_on_object.to_s.gsub('?','')}(*args)") + end + end + end + + def scope_and_options(*args) + args, options = args_and_options(*args) + scope = (args[0] || :each), options + end + + def before_parts_from_scope(scope) + case scope + when :each; before_each_parts + when :all; before_all_parts + end + end + + def after_parts_from_scope(scope) + case scope + when :each; after_each_parts + when :all; after_all_parts + end + end + + def before_eval + end + + def add_method_examples(examples) + instance_methods.sort.each do |method_name| + if example_method?(method_name) + examples << new(method_name) do + __send__(method_name) + end + end + end + end + + def example_method?(method_name) + should_method?(method_name) + end + + def should_method?(method_name) + !(method_name =~ /^should(_not)?$/) && + method_name =~ /^should/ && ( + instance_method(method_name).arity == 0 || + instance_method(method_name).arity == -1 + ) + end + end + + end +end diff --git a/vendor/gems/rspec/lib/spec/example/example_matcher.rb b/vendor/gems/rspec/lib/spec/example/example_matcher.rb new file mode 100755 index 0000000000..435eabf520 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/example/example_matcher.rb @@ -0,0 +1,42 @@ +module Spec + module Example + class ExampleMatcher + def initialize(example_group_description, example_name) + @example_group_description = example_group_description + @example_name = example_name + end + + def matches?(specified_examples) + specified_examples.each do |specified_example| + return true if matches_literal_example?(specified_example) || matches_example_not_considering_modules?(specified_example) + end + false + end + + protected + def matches_literal_example?(specified_example) + specified_example =~ /(^#{example_group_regex} #{example_regexp}$|^#{example_group_regex}$|^#{example_group_with_before_all_regexp}$|^#{example_regexp}$)/ + end + + def matches_example_not_considering_modules?(specified_example) + specified_example =~ /(^#{example_group_regex_not_considering_modules} #{example_regexp}$|^#{example_group_regex_not_considering_modules}$|^#{example_regexp}$)/ + end + + def example_group_regex + Regexp.escape(@example_group_description) + end + + def example_group_with_before_all_regexp + Regexp.escape("#{@example_group_description} before(:all)") + end + + def example_group_regex_not_considering_modules + Regexp.escape(@example_group_description.split('::').last) + end + + def example_regexp + Regexp.escape(@example_name) + end + end + end +end diff --git a/vendor/gems/rspec/lib/spec/example/example_methods.rb b/vendor/gems/rspec/lib/spec/example/example_methods.rb new file mode 100644 index 0000000000..babd31dfae --- /dev/null +++ b/vendor/gems/rspec/lib/spec/example/example_methods.rb @@ -0,0 +1,106 @@ +module Spec + module Example + module ExampleMethods + extend ExampleGroupMethods + extend ModuleReopeningFix + + PENDING_EXAMPLE_BLOCK = lambda { + raise Spec::Example::ExamplePendingError.new("Not Yet Implemented") + } + + def execute(options, instance_variables) + options.reporter.example_started(self) + set_instance_variables_from_hash(instance_variables) + + execution_error = nil + Timeout.timeout(options.timeout) do + begin + before_example + run_with_description_capturing + rescue Exception => e + execution_error ||= e + end + begin + after_example + rescue Exception => e + execution_error ||= e + end + end + + options.reporter.example_finished(self, execution_error) + success = execution_error.nil? || ExamplePendingError === execution_error + end + + def instance_variable_hash + instance_variables.inject({}) do |variable_hash, variable_name| + variable_hash[variable_name] = instance_variable_get(variable_name) + variable_hash + end + end + + def violated(message="") + raise Spec::Expectations::ExpectationNotMetError.new(message) + end + + def eval_each_fail_fast(procs) #:nodoc: + procs.each do |proc| + instance_eval(&proc) + end + end + + def eval_each_fail_slow(procs) #:nodoc: + first_exception = nil + procs.each do |proc| + begin + instance_eval(&proc) + rescue Exception => e + first_exception ||= e + end + end + raise first_exception if first_exception + end + + def description + @_defined_description || @_matcher_description || "NO NAME" + end + + def set_instance_variables_from_hash(ivars) + ivars.each do |variable_name, value| + # Ruby 1.9 requires variable.to_s on the next line + unless ['@_implementation', '@_defined_description', '@_matcher_description', '@method_name'].include?(variable_name.to_s) + instance_variable_set variable_name, value + end + end + end + + def run_with_description_capturing + begin + return instance_eval(&(@_implementation || PENDING_EXAMPLE_BLOCK)) + ensure + @_matcher_description = Spec::Matchers.generated_description + Spec::Matchers.clear_generated_description + end + end + + def implementation_backtrace + eval("caller", @_implementation) + end + + protected + include Matchers + include Pending + + def before_example + setup_mocks_for_rspec + self.class.run_before_each(self) + end + + def after_example + self.class.run_after_each(self) + verify_mocks_for_rspec + ensure + teardown_mocks_for_rspec + end + end + end +end diff --git a/vendor/gems/rspec/lib/spec/example/module_reopening_fix.rb b/vendor/gems/rspec/lib/spec/example/module_reopening_fix.rb new file mode 100644 index 0000000000..dc01dd6668 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/example/module_reopening_fix.rb @@ -0,0 +1,21 @@ +module Spec + module Example + # This is a fix for ...Something in Ruby 1.8.6??... (Someone fill in here please - Aslak) + module ModuleReopeningFix + def child_modules + @child_modules ||= [] + end + + def included(mod) + child_modules << mod + end + + def include(mod) + super + child_modules.each do |child_module| + child_module.__send__(:include, mod) + end + end + end + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/lib/spec/example/pending.rb b/vendor/gems/rspec/lib/spec/example/pending.rb new file mode 100644 index 0000000000..b1f27c8667 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/example/pending.rb @@ -0,0 +1,18 @@ +module Spec + module Example + module Pending + def pending(message = "TODO") + if block_given? + begin + yield + rescue Exception => e + raise Spec::Example::ExamplePendingError.new(message) + end + raise Spec::Example::PendingExampleFixedError.new("Expected pending '#{message}' to fail. No Error was raised.") + else + raise Spec::Example::ExamplePendingError.new(message) + end + end + end + end +end diff --git a/vendor/gems/rspec/lib/spec/example/shared_example_group.rb b/vendor/gems/rspec/lib/spec/example/shared_example_group.rb new file mode 100644 index 0000000000..a6fd6211cf --- /dev/null +++ b/vendor/gems/rspec/lib/spec/example/shared_example_group.rb @@ -0,0 +1,58 @@ +module Spec + module Example + class SharedExampleGroup < Module + class << self + def add_shared_example_group(new_example_group) + guard_against_redefining_existing_example_group(new_example_group) + shared_example_groups << new_example_group + end + + def find_shared_example_group(example_group_description) + shared_example_groups.find do |b| + b.description == example_group_description + end + end + + def shared_example_groups + # TODO - this needs to be global, or at least accessible from + # from subclasses of Example in a centralized place. I'm not loving + # this as a solution, but it works for now. + $shared_example_groups ||= [] + end + + private + def guard_against_redefining_existing_example_group(new_example_group) + existing_example_group = find_shared_example_group(new_example_group.description) + return unless existing_example_group + return if new_example_group.equal?(existing_example_group) + return if spec_path(new_example_group) == spec_path(existing_example_group) + raise ArgumentError.new("Shared Example '#{existing_example_group.description}' already exists") + end + + def spec_path(example_group) + File.expand_path(example_group.spec_path) + end + end + include ExampleGroupMethods + public :include + + def initialize(*args, &example_group_block) + describe(*args) + @example_group_block = example_group_block + self.class.add_shared_example_group(self) + end + + def included(mod) # :nodoc: + mod.module_eval(&@example_group_block) + end + + def execute_in_class_hierarchy(superclass_last=false) + classes = [self] + superclass_last ? classes << ExampleMethods : classes.unshift(ExampleMethods) + classes.each do |example_group| + yield example_group + end + end + end + end +end diff --git a/vendor/gems/rspec/lib/spec/expectations.rb b/vendor/gems/rspec/lib/spec/expectations.rb new file mode 100644 index 0000000000..65ea474251 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/expectations.rb @@ -0,0 +1,56 @@ +require 'spec/matchers' +require 'spec/expectations/errors' +require 'spec/expectations/extensions' +require 'spec/expectations/handler' + +module Spec + + # Spec::Expectations lets you set expectations on your objects. + # + # result.should == 37 + # team.should have(11).players_on_the_field + # + # == How Expectations work. + # + # Spec::Expectations adds two methods to Object: + # + # should(matcher=nil) + # should_not(matcher=nil) + # + # Both methods take an optional Expression Matcher (See Spec::Matchers). + # + # When +should+ receives an Expression Matcher, it calls matches?(self). If + # it returns +true+, the spec passes and execution continues. If it returns + # +false+, then the spec fails with the message returned by matcher.failure_message. + # + # Similarly, when +should_not+ receives a matcher, it calls matches?(self). If + # it returns +false+, the spec passes and execution continues. If it returns + # +true+, then the spec fails with the message returned by matcher.negative_failure_message. + # + # RSpec ships with a standard set of useful matchers, and writing your own + # matchers is quite simple. See Spec::Matchers for details. + module Expectations + class << self + attr_accessor :differ + + # raises a Spec::Expectations::ExpectationNotMetError with message + # + # When a differ has been assigned and fail_with is passed + # expected and target, passes them + # to the differ to append a diff message to the failure message. + def fail_with(message, expected=nil, target=nil) # :nodoc: + if Array === message && message.length == 3 + message, expected, target = message[0], message[1], message[2] + end + unless (differ.nil? || expected.nil? || target.nil?) + if expected.is_a?(String) + message << "\nDiff:" << self.differ.diff_as_string(target.to_s, expected) + elsif !target.is_a?(Proc) + message << "\nDiff:" << self.differ.diff_as_object(target, expected) + end + end + Kernel::raise(Spec::Expectations::ExpectationNotMetError.new(message)) + end + end + end +end diff --git a/vendor/gems/rspec/lib/spec/expectations/differs/default.rb b/vendor/gems/rspec/lib/spec/expectations/differs/default.rb new file mode 100644 index 0000000000..a5eb1bb890 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/expectations/differs/default.rb @@ -0,0 +1,66 @@ +begin + require 'rubygems' + require 'diff/lcs' #necessary due to loading bug on some machines - not sure why - DaC + require 'diff/lcs/hunk' +rescue LoadError ; raise "You must gem install diff-lcs to use diffing" ; end + +require 'pp' + +module Spec + module Expectations + module Differs + + # TODO add some rdoc + class Default + def initialize(options) + @options = options + end + + # This is snagged from diff/lcs/ldiff.rb (which is a commandline tool) + def diff_as_string(data_old, data_new) + data_old = data_old.split(/\n/).map! { |e| e.chomp } + data_new = data_new.split(/\n/).map! { |e| e.chomp } + output = "" + diffs = Diff::LCS.diff(data_old, data_new) + return output if diffs.empty? + oldhunk = hunk = nil + file_length_difference = 0 + diffs.each do |piece| + begin + hunk = Diff::LCS::Hunk.new(data_old, data_new, piece, context_lines, + file_length_difference) + file_length_difference = hunk.file_length_difference + next unless oldhunk + # Hunks may overlap, which is why we need to be careful when our + # diff includes lines of context. Otherwise, we might print + # redundant lines. + if (context_lines > 0) and hunk.overlaps?(oldhunk) + hunk.unshift(oldhunk) + else + output << oldhunk.diff(format) + end + ensure + oldhunk = hunk + output << "\n" + end + end + #Handle the last remaining hunk + output << oldhunk.diff(format) << "\n" + end + + def diff_as_object(target,expected) + diff_as_string(PP.pp(target,""), PP.pp(expected,"")) + end + + protected + def format + @options.diff_format + end + + def context_lines + @options.context_lines + end + end + end + end +end diff --git a/vendor/gems/rspec/lib/spec/expectations/errors.rb b/vendor/gems/rspec/lib/spec/expectations/errors.rb new file mode 100644 index 0000000000..1fabd105d5 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/expectations/errors.rb @@ -0,0 +1,12 @@ +module Spec + module Expectations + # If Test::Unit is loaed, we'll use its error as baseclass, so that Test::Unit + # will report unmet RSpec expectations as failures rather than errors. + superclass = ['Test::Unit::AssertionFailedError', '::StandardError'].map do |c| + eval(c) rescue nil + end.compact.first + + class ExpectationNotMetError < superclass + end + end +end diff --git a/vendor/gems/rspec/lib/spec/expectations/extensions.rb b/vendor/gems/rspec/lib/spec/expectations/extensions.rb new file mode 100644 index 0000000000..60c9b9e7da --- /dev/null +++ b/vendor/gems/rspec/lib/spec/expectations/extensions.rb @@ -0,0 +1,2 @@ +require 'spec/expectations/extensions/object' +require 'spec/expectations/extensions/string_and_symbol' diff --git a/vendor/gems/rspec/lib/spec/expectations/extensions/object.rb b/vendor/gems/rspec/lib/spec/expectations/extensions/object.rb new file mode 100644 index 0000000000..a3925bbee9 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/expectations/extensions/object.rb @@ -0,0 +1,71 @@ +module Spec + module Expectations + # rspec adds #should and #should_not to every Object (and, + # implicitly, every Class). + module ObjectExpectations + # :call-seq: + # should(matcher) + # should == expected + # should === expected + # should =~ expected + # + # receiver.should(matcher) + # => Passes if matcher.matches?(receiver) + # + # receiver.should == expected #any value + # => Passes if (receiver == expected) + # + # receiver.should === expected #any value + # => Passes if (receiver === expected) + # + # receiver.should =~ regexp + # => Passes if (receiver =~ regexp) + # + # See Spec::Matchers for more information about matchers + # + # == Warning + # + # NOTE that this does NOT support receiver.should != expected. + # Instead, use receiver.should_not == expected + def should(matcher = :default_parameter, &block) + if :default_parameter == matcher + Spec::Matchers::PositiveOperatorMatcher.new(self) + else + ExpectationMatcherHandler.handle_matcher(self, matcher, &block) + end + end + + # :call-seq: + # should_not(matcher) + # should_not == expected + # should_not === expected + # should_not =~ expected + # + # receiver.should_not(matcher) + # => Passes unless matcher.matches?(receiver) + # + # receiver.should_not == expected + # => Passes unless (receiver == expected) + # + # receiver.should_not === expected + # => Passes unless (receiver === expected) + # + # receiver.should_not =~ regexp + # => Passes unless (receiver =~ regexp) + # + # See Spec::Matchers for more information about matchers + def should_not(matcher = :default_parameter, &block) + if :default_parameter == matcher + Spec::Matchers::NegativeOperatorMatcher.new(self) + else + NegativeExpectationMatcherHandler.handle_matcher(self, matcher, &block) + end + end + + end + end +end + +class Object + include Spec::Expectations::ObjectExpectations +end diff --git a/vendor/gems/rspec/lib/spec/expectations/extensions/string_and_symbol.rb b/vendor/gems/rspec/lib/spec/expectations/extensions/string_and_symbol.rb new file mode 100644 index 0000000000..29cfbddfae --- /dev/null +++ b/vendor/gems/rspec/lib/spec/expectations/extensions/string_and_symbol.rb @@ -0,0 +1,17 @@ +module Spec + module Expectations + module StringHelpers + def starts_with?(prefix) + to_s[0..(prefix.to_s.length - 1)] == prefix.to_s + end + end + end +end + +class String + include Spec::Expectations::StringHelpers +end + +class Symbol + include Spec::Expectations::StringHelpers +end diff --git a/vendor/gems/rspec/lib/spec/expectations/handler.rb b/vendor/gems/rspec/lib/spec/expectations/handler.rb new file mode 100644 index 0000000000..e6dce0846d --- /dev/null +++ b/vendor/gems/rspec/lib/spec/expectations/handler.rb @@ -0,0 +1,52 @@ +module Spec + module Expectations + class InvalidMatcherError < ArgumentError; end + + module MatcherHandlerHelper + def describe_matcher(matcher) + matcher.respond_to?(:description) ? matcher.description : "[#{matcher.class.name} does not provide a description]" + end + end + + class ExpectationMatcherHandler + class << self + include MatcherHandlerHelper + def handle_matcher(actual, matcher, &block) + unless matcher.respond_to?(:matches?) + raise InvalidMatcherError, "Expected a matcher, got #{matcher.inspect}." + end + + match = matcher.matches?(actual, &block) + ::Spec::Matchers.generated_description = "should #{describe_matcher(matcher)}" + Spec::Expectations.fail_with(matcher.failure_message) unless match + end + end + end + + class NegativeExpectationMatcherHandler + class << self + include MatcherHandlerHelper + def handle_matcher(actual, matcher, &block) + unless matcher.respond_to?(:matches?) + raise InvalidMatcherError, "Expected a matcher, got #{matcher.inspect}." + end + + unless matcher.respond_to?(:negative_failure_message) + Spec::Expectations.fail_with( +<<-EOF +Matcher does not support should_not. +See Spec::Matchers for more information +about matchers. +EOF +) + end + match = matcher.matches?(actual, &block) + ::Spec::Matchers.generated_description = "should not #{describe_matcher(matcher)}" + Spec::Expectations.fail_with(matcher.negative_failure_message) if match + end + end + end + + end +end + diff --git a/vendor/gems/rspec/lib/spec/extensions.rb b/vendor/gems/rspec/lib/spec/extensions.rb new file mode 100755 index 0000000000..9a313d0e78 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/extensions.rb @@ -0,0 +1,3 @@ +require 'spec/extensions/object' +require 'spec/extensions/class' +require 'spec/extensions/main' diff --git a/vendor/gems/rspec/lib/spec/extensions/class.rb b/vendor/gems/rspec/lib/spec/extensions/class.rb new file mode 100644 index 0000000000..30730f87e5 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/extensions/class.rb @@ -0,0 +1,24 @@ +class Class + # Creates a new subclass of self, with a name "under" our own name. + # Example: + # + # x = Foo::Bar.subclass('Zap'){} + # x.name # => Foo::Bar::Zap_1 + # x.superclass.name # => Foo::Bar + def subclass(base_name, &body) + klass = Class.new(self) + class_name = "#{base_name}_#{class_count!}" + instance_eval do + const_set(class_name, klass) + end + klass.instance_eval(&body) + klass + end + + private + def class_count! + @class_count ||= 0 + @class_count += 1 + @class_count + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/lib/spec/extensions/main.rb b/vendor/gems/rspec/lib/spec/extensions/main.rb new file mode 100644 index 0000000000..281cbf8797 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/extensions/main.rb @@ -0,0 +1,102 @@ +module Spec + module Extensions + module Main + # Creates and returns a class that includes the ExampleGroupMethods + # module. Which ExampleGroup type is created depends on the directory of the file + # calling this method. For example, Spec::Rails will use different + # classes for specs living in spec/models, + # spec/helpers, spec/views and + # spec/controllers. + # + # It is also possible to override autodiscovery of the example group + # type with an options Hash as the last argument: + # + # describe "name", :type => :something_special do ... + # + # The reason for using different behaviour classes is to have different + # matcher methods available from within the describe block. + # + # See Spec::Example::ExampleFactory#register for details about how to + # register special implementations. + # + def describe(*args, &block) + raise ArgumentError if args.empty? + raise ArgumentError unless block + args << {} unless Hash === args.last + args.last[:spec_path] = caller(0)[1] + Spec::Example::ExampleGroupFactory.create_example_group(*args, &block) + end + alias :context :describe + + # Creates an example group that can be shared by other example groups + # + # == Examples + # + # share_examples_for "All Editions" do + # it "all editions behaviour" ... + # end + # + # describe SmallEdition do + # it_should_behave_like "All Editions" + # + # it "should do small edition stuff" do + # ... + # end + # end + def share_examples_for(name, &block) + describe(name, :shared => true, &block) + end + + alias :shared_examples_for :share_examples_for + + # Creates a Shared Example Group and assigns it to a constant + # + # share_as :AllEditions do + # it "should do all editions stuff" ... + # end + # + # describe SmallEdition do + # it_should_behave_like AllEditions + # + # it "should do small edition stuff" do + # ... + # end + # end + # + # And, for those of you who prefer to use something more like Ruby, you + # can just include the module directly + # + # describe SmallEdition do + # include AllEditions + # + # it "should do small edition stuff" do + # ... + # end + # end + def share_as(name, &block) + begin + Object.const_set(name, share_examples_for(name, &block)) + rescue NameError => e + raise NameError.new(e.message + "\nThe first argument to share_as must be a legal name for a constant\n") + end + end + + private + + def rspec_options + $rspec_options ||= begin; \ + parser = ::Spec::Runner::OptionParser.new(STDERR, STDOUT); \ + parser.order!(ARGV); \ + $rspec_options = parser.options; \ + end + $rspec_options + end + + def init_rspec_options(options) + $rspec_options = options if $rspec_options.nil? + end + end + end +end + +include Spec::Extensions::Main \ No newline at end of file diff --git a/vendor/gems/rspec/lib/spec/extensions/object.rb b/vendor/gems/rspec/lib/spec/extensions/object.rb new file mode 100755 index 0000000000..e9f6364e26 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/extensions/object.rb @@ -0,0 +1,10 @@ +class Object + def args_and_options(*args) + options = Hash === args.last ? args.pop : {} + return args, options + end + + def metaclass + class << self; self; end + end +end diff --git a/vendor/gems/rspec/lib/spec/interop/test.rb b/vendor/gems/rspec/lib/spec/interop/test.rb new file mode 100644 index 0000000000..afa16137b4 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/interop/test.rb @@ -0,0 +1,12 @@ +require 'test/unit' +require 'test/unit/testresult' + +require 'spec/interop/test/unit/testcase' +require 'spec/interop/test/unit/testsuite_adapter' +require 'spec/interop/test/unit/autorunner' +require 'spec/interop/test/unit/testresult' +require 'spec/interop/test/unit/ui/console/testrunner' + +Spec::Example::ExampleGroupFactory.default(Test::Unit::TestCase) + +Test::Unit.run = true diff --git a/vendor/gems/rspec/lib/spec/interop/test/unit/autorunner.rb b/vendor/gems/rspec/lib/spec/interop/test/unit/autorunner.rb new file mode 100644 index 0000000000..3944e6995a --- /dev/null +++ b/vendor/gems/rspec/lib/spec/interop/test/unit/autorunner.rb @@ -0,0 +1,6 @@ +class Test::Unit::AutoRunner + remove_method :process_args + def process_args(argv) + true + end +end diff --git a/vendor/gems/rspec/lib/spec/interop/test/unit/testcase.rb b/vendor/gems/rspec/lib/spec/interop/test/unit/testcase.rb new file mode 100644 index 0000000000..b32a820c1b --- /dev/null +++ b/vendor/gems/rspec/lib/spec/interop/test/unit/testcase.rb @@ -0,0 +1,61 @@ +require 'test/unit/testcase' + +module Test + module Unit + # This extension of the standard Test::Unit::TestCase makes RSpec + # available from within, so that you can do things like: + # + # require 'test/unit' + # require 'spec' + # + # class MyTest < Test::Unit::TestCase + # it "should work with Test::Unit assertions" do + # assert_equal 4, 2+1 + # end + # + # def test_should_work_with_rspec_expectations + # (3+1).should == 5 + # end + # end + # + # See also Spec::Example::ExampleGroup + class TestCase + extend Spec::Example::ExampleGroupMethods + include Spec::Example::ExampleMethods + + before(:each) {setup} + after(:each) {teardown} + + class << self + def suite + Test::Unit::TestSuiteAdapter.new(self) + end + + def example_method?(method_name) + should_method?(method_name) || test_method?(method_name) + end + + def test_method?(method_name) + method_name =~ /^test[_A-Z]./ && ( + instance_method(method_name).arity == 0 || + instance_method(method_name).arity == -1 + ) + end + end + + def initialize(defined_description, &implementation) + @_defined_description = defined_description + @_implementation = implementation + + @_result = ::Test::Unit::TestResult.new + # @method_name is important to set here because it "complies" with Test::Unit's interface. + # Some Test::Unit extensions depend on @method_name being present. + @method_name = @_defined_description + end + + def run(ignore_this_argument=nil) + super() + end + end + end +end diff --git a/vendor/gems/rspec/lib/spec/interop/test/unit/testresult.rb b/vendor/gems/rspec/lib/spec/interop/test/unit/testresult.rb new file mode 100644 index 0000000000..1386dc7281 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/interop/test/unit/testresult.rb @@ -0,0 +1,6 @@ +class Test::Unit::TestResult + alias_method :tu_passed?, :passed? + def passed? + return tu_passed? & ::Spec.run + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/lib/spec/interop/test/unit/testsuite_adapter.rb b/vendor/gems/rspec/lib/spec/interop/test/unit/testsuite_adapter.rb new file mode 100644 index 0000000000..7c0ed092dc --- /dev/null +++ b/vendor/gems/rspec/lib/spec/interop/test/unit/testsuite_adapter.rb @@ -0,0 +1,34 @@ +module Test + module Unit + class TestSuiteAdapter < TestSuite + attr_reader :example_group, :examples + alias_method :tests, :examples + def initialize(example_group) + @example_group = example_group + @examples = example_group.examples + end + + def name + example_group.description + end + + def run(*args) + return true unless args.empty? + example_group.run + end + + def size + example_group.number_of_examples + end + + def delete(example) + examples.delete example + end + + def empty? + examples.empty? + end + end + end +end + diff --git a/vendor/gems/rspec/lib/spec/interop/test/unit/ui/console/testrunner.rb b/vendor/gems/rspec/lib/spec/interop/test/unit/ui/console/testrunner.rb new file mode 100644 index 0000000000..8e9995e02d --- /dev/null +++ b/vendor/gems/rspec/lib/spec/interop/test/unit/ui/console/testrunner.rb @@ -0,0 +1,61 @@ +require 'test/unit/ui/console/testrunner' + +module Test + module Unit + module UI + module Console + class TestRunner + + alias_method :started_without_rspec, :started + def started_with_rspec(result) + @result = result + @need_to_output_started = true + end + alias_method :started, :started_with_rspec + + alias_method :test_started_without_rspec, :test_started + def test_started_with_rspec(name) + if @need_to_output_started + if @rspec_io + @rspec_io.rewind + output(@rspec_io.read) + end + output("Started") + @need_to_output_started = false + end + test_started_without_rspec(name) + end + alias_method :test_started, :test_started_with_rspec + + alias_method :test_finished_without_rspec, :test_finished + def test_finished_with_rspec(name) + test_finished_without_rspec(name) + @ran_test = true + end + alias_method :test_finished, :test_finished_with_rspec + + alias_method :finished_without_rspec, :finished + def finished_with_rspec(elapsed_time) + @ran_test ||= false + if @ran_test + finished_without_rspec(elapsed_time) + end + end + alias_method :finished, :finished_with_rspec + + alias_method :setup_mediator_without_rspec, :setup_mediator + def setup_mediator_with_rspec + orig_io = @io + @io = StringIO.new + setup_mediator_without_rspec + ensure + @rspec_io = @io + @io = orig_io + end + alias_method :setup_mediator, :setup_mediator_with_rspec + + end + end + end + end +end diff --git a/vendor/gems/rspec/lib/spec/matchers.rb b/vendor/gems/rspec/lib/spec/matchers.rb new file mode 100644 index 0000000000..afae5ae5f1 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/matchers.rb @@ -0,0 +1,156 @@ +require 'spec/matchers/simple_matcher' +require 'spec/matchers/be' +require 'spec/matchers/be_close' +require 'spec/matchers/change' +require 'spec/matchers/eql' +require 'spec/matchers/equal' +require 'spec/matchers/exist' +require 'spec/matchers/has' +require 'spec/matchers/have' +require 'spec/matchers/include' +require 'spec/matchers/match' +require 'spec/matchers/raise_error' +require 'spec/matchers/respond_to' +require 'spec/matchers/satisfy' +require 'spec/matchers/throw_symbol' +require 'spec/matchers/operator_matcher' + +module Spec + + # RSpec ships with a number of useful Expression Matchers. An Expression Matcher + # is any object that responds to the following methods: + # + # matches?(actual) + # failure_message + # negative_failure_message #optional + # description #optional + # + # See Spec::Expectations to learn how to use these as Expectation Matchers. + # See Spec::Mocks to learn how to use them as Mock Argument Constraints. + # + # == Predicates + # + # In addition to those Expression Matchers that are defined explicitly, RSpec will + # create custom Matchers on the fly for any arbitrary predicate, giving your specs + # a much more natural language feel. + # + # A Ruby predicate is a method that ends with a "?" and returns true or false. + # Common examples are +empty?+, +nil?+, and +instance_of?+. + # + # All you need to do is write +should be_+ followed by the predicate without + # the question mark, and RSpec will figure it out from there. For example: + # + # [].should be_empty => [].empty? #passes + # [].should_not be_empty => [].empty? #fails + # + # In addtion to prefixing the predicate matchers with "be_", you can also use "be_a_" + # and "be_an_", making your specs read much more naturally: + # + # "a string".should be_an_instance_of(String) =>"a string".instance_of?(String) #passes + # + # 3.should be_a_kind_of(Fixnum) => 3.kind_of?(Numeric) #passes + # 3.should be_a_kind_of(Numeric) => 3.kind_of?(Numeric) #passes + # 3.should be_an_instance_of(Fixnum) => 3.instance_of?(Fixnum) #passes + # 3.should_not be_instance_of(Numeric) => 3.instance_of?(Numeric) #fails + # + # RSpec will also create custom matchers for predicates like +has_key?+. To + # use this feature, just state that the object should have_key(:key) and RSpec will + # call has_key?(:key) on the target. For example: + # + # {:a => "A"}.should have_key(:a) => {:a => "A"}.has_key?(:a) #passes + # {:a => "A"}.should have_key(:b) => {:a => "A"}.has_key?(:b) #fails + # + # You can use this feature to invoke any predicate that begins with "has_", whether it is + # part of the Ruby libraries (like +Hash#has_key?+) or a method you wrote on your own class. + # + # == Custom Expectation Matchers + # + # When you find that none of the stock Expectation Matchers provide a natural + # feeling expectation, you can very easily write your own. + # + # For example, imagine that you are writing a game in which players can + # be in various zones on a virtual board. To specify that bob should + # be in zone 4, you could say: + # + # bob.current_zone.should eql(Zone.new("4")) + # + # But you might find it more expressive to say: + # + # bob.should be_in_zone("4") + # + # and/or + # + # bob.should_not be_in_zone("3") + # + # To do this, you would need to write a class like this: + # + # class BeInZone + # def initialize(expected) + # @expected = expected + # end + # def matches?(target) + # @target = target + # @target.current_zone.eql?(Zone.new(@expected)) + # end + # def failure_message + # "expected #{@target.inspect} to be in Zone #{@expected}" + # end + # def negative_failure_message + # "expected #{@target.inspect} not to be in Zone #{@expected}" + # end + # end + # + # ... and a method like this: + # + # def be_in_zone(expected) + # BeInZone.new(expected) + # end + # + # And then expose the method to your specs. This is normally done + # by including the method and the class in a module, which is then + # included in your spec: + # + # module CustomGameMatchers + # class BeInZone + # ... + # end + # + # def be_in_zone(expected) + # ... + # end + # end + # + # describe "Player behaviour" do + # include CustomGameMatchers + # ... + # end + # + # or you can include in globally in a spec_helper.rb file required + # from your spec file(s): + # + # Spec::Runner.configure do |config| + # config.include(CustomGameMatchers) + # end + # + module Matchers + module ModuleMethods + attr_accessor :generated_description + + def clear_generated_description + self.generated_description = nil + end + end + + extend ModuleMethods + + def method_missing(sym, *args, &block) # :nodoc: + return Matchers::Be.new(sym, *args) if sym.starts_with?("be_") + return Matchers::Has.new(sym, *args) if sym.starts_with?("have_") + super + end + + class MatcherError < StandardError + end + + end +end diff --git a/vendor/gems/rspec/lib/spec/matchers/be.rb b/vendor/gems/rspec/lib/spec/matchers/be.rb new file mode 100644 index 0000000000..2b25b11f4d --- /dev/null +++ b/vendor/gems/rspec/lib/spec/matchers/be.rb @@ -0,0 +1,224 @@ +module Spec + module Matchers + + class Be #:nodoc: + def initialize(*args) + if args.empty? + @expected = :satisfy_if + else + @expected = parse_expected(args.shift) + end + @args = args + @comparison = "" + end + + def matches?(actual) + @actual = actual + if handling_predicate? + begin + return @result = actual.__send__(predicate, *@args) + rescue => predicate_error + # This clause should be empty, but rcov will not report it as covered + # unless something (anything) is executed within the clause + rcov_error_report = "http://eigenclass.org/hiki.rb?rcov-0.8.0" + end + + # This supports should_exist > target.exists? in the old world. + # We should consider deprecating that ability as in the new world + # you can't write "should exist" unless you have your own custom matcher. + begin + return @result = actual.__send__(present_tense_predicate, *@args) + rescue + raise predicate_error + end + else + return match_or_compare + end + end + + def failure_message + return "expected #{@comparison}#{expected}, got #{@actual.inspect}" unless handling_predicate? + return "expected #{predicate}#{args_to_s} to return true, got #{@result.inspect}" + end + + def negative_failure_message + return "expected not #{expected}, got #{@actual.inspect}" unless handling_predicate? + return "expected #{predicate}#{args_to_s} to return false, got #{@result.inspect}" + end + + def expected + return "if to be satisfied" if @expected == :satisfy_if + return true if @expected == :true + return false if @expected == :false + return "nil" if @expected == :nil + return @expected.inspect + end + + def match_or_compare + return @actual ? true : false if @expected == :satisfy_if + return @actual == true if @expected == :true + return @actual == false if @expected == :false + return @actual.nil? if @expected == :nil + return @actual < @expected if @less_than + return @actual <= @expected if @less_than_or_equal + return @actual >= @expected if @greater_than_or_equal + return @actual > @expected if @greater_than + return @actual == @expected if @double_equal + return @actual === @expected if @triple_equal + return @actual.equal?(@expected) + end + + def ==(expected) + @prefix = "be " + @double_equal = true + @comparison = "== " + @expected = expected + self + end + + def ===(expected) + @prefix = "be " + @triple_equal = true + @comparison = "=== " + @expected = expected + self + end + + def <(expected) + @prefix = "be " + @less_than = true + @comparison = "< " + @expected = expected + self + end + + def <=(expected) + @prefix = "be " + @less_than_or_equal = true + @comparison = "<= " + @expected = expected + self + end + + def >=(expected) + @prefix = "be " + @greater_than_or_equal = true + @comparison = ">= " + @expected = expected + self + end + + def >(expected) + @prefix = "be " + @greater_than = true + @comparison = "> " + @expected = expected + self + end + + def description + "#{prefix_to_sentence}#{comparison}#{expected_to_sentence}#{args_to_sentence}" + end + + private + def parse_expected(expected) + if Symbol === expected + @handling_predicate = true + ["be_an_","be_a_","be_"].each do |prefix| + if expected.starts_with?(prefix) + @prefix = prefix + return "#{expected.to_s.sub(@prefix,"")}".to_sym + end + end + end + @prefix = "" + return expected + end + + def handling_predicate? + return false if [:true, :false, :nil].include?(@expected) + return @handling_predicate + end + + def predicate + "#{@expected.to_s}?".to_sym + end + + def present_tense_predicate + "#{@expected.to_s}s?".to_sym + end + + def args_to_s + return "" if @args.empty? + inspected_args = @args.collect{|a| a.inspect} + return "(#{inspected_args.join(', ')})" + end + + def comparison + @comparison + end + + def expected_to_sentence + split_words(@expected) + end + + def prefix_to_sentence + split_words(@prefix) + end + + def split_words(sym) + sym.to_s.gsub(/_/,' ') + end + + def args_to_sentence + case @args.length + when 0 + "" + when 1 + " #{@args[0]}" + else + " #{@args[0...-1].join(', ')} and #{@args[-1]}" + end + end + + end + + # :call-seq: + # should be + # should be_true + # should be_false + # should be_nil + # should be_arbitrary_predicate(*args) + # should_not be_nil + # should_not be_arbitrary_predicate(*args) + # + # Given true, false, or nil, will pass if actual is + # true, false or nil (respectively). Given no args means + # the caller should satisfy an if condition (to be or not to be). + # + # Predicates are any Ruby method that ends in a "?" and returns true or false. + # Given be_ followed by arbitrary_predicate (without the "?"), RSpec will match + # convert that into a query against the target object. + # + # The arbitrary_predicate feature will handle any predicate + # prefixed with "be_an_" (e.g. be_an_instance_of), "be_a_" (e.g. be_a_kind_of) + # or "be_" (e.g. be_empty), letting you choose the prefix that best suits the predicate. + # + # == Examples + # + # target.should be + # target.should be_true + # target.should be_false + # target.should be_nil + # target.should_not be_nil + # + # collection.should be_empty #passes if target.empty? + # "this string".should be_an_intance_of(String) + # + # target.should_not be_empty #passes unless target.empty? + # target.should_not be_old_enough(16) #passes unless target.old_enough?(16) + def be(*args) + Matchers::Be.new(*args) + end + end +end diff --git a/vendor/gems/rspec/lib/spec/matchers/be_close.rb b/vendor/gems/rspec/lib/spec/matchers/be_close.rb new file mode 100644 index 0000000000..7763eb97ec --- /dev/null +++ b/vendor/gems/rspec/lib/spec/matchers/be_close.rb @@ -0,0 +1,37 @@ +module Spec + module Matchers + + class BeClose #:nodoc: + def initialize(expected, delta) + @expected = expected + @delta = delta + end + + def matches?(actual) + @actual = actual + (@actual - @expected).abs < @delta + end + + def failure_message + "expected #{@expected} +/- (< #{@delta}), got #{@actual}" + end + + def description + "be close to #{@expected} (within +- #{@delta})" + end + end + + # :call-seq: + # should be_close(expected, delta) + # should_not be_close(expected, delta) + # + # Passes if actual == expected +/- delta + # + # == Example + # + # result.should be_close(3.0, 0.5) + def be_close(expected, delta) + Matchers::BeClose.new(expected, delta) + end + end +end diff --git a/vendor/gems/rspec/lib/spec/matchers/change.rb b/vendor/gems/rspec/lib/spec/matchers/change.rb new file mode 100644 index 0000000000..784e516edf --- /dev/null +++ b/vendor/gems/rspec/lib/spec/matchers/change.rb @@ -0,0 +1,144 @@ +module Spec + module Matchers + + #Based on patch from Wilson Bilkovich + class Change #:nodoc: + def initialize(receiver=nil, message=nil, &block) + @receiver = receiver + @message = message + @block = block + end + + def matches?(target, &block) + if block + raise MatcherError.new(<<-EOF +block passed to should or should_not change must use {} instead of do/end +EOF +) + end + @target = target + execute_change + return false if @from && (@from != @before) + return false if @to && (@to != @after) + return (@before + @amount == @after) if @amount + return ((@after - @before) >= @minimum) if @minimum + return ((@after - @before) <= @maximum) if @maximum + return @before != @after + end + + def execute_change + @before = @block.nil? ? @receiver.send(@message) : @block.call + @target.call + @after = @block.nil? ? @receiver.send(@message) : @block.call + end + + def failure_message + if @to + "#{result} should have been changed to #{@to.inspect}, but is now #{@after.inspect}" + elsif @from + "#{result} should have initially been #{@from.inspect}, but was #{@before.inspect}" + elsif @amount + "#{result} should have been changed by #{@amount.inspect}, but was changed by #{actual_delta.inspect}" + elsif @minimum + "#{result} should have been changed by at least #{@minimum.inspect}, but was changed by #{actual_delta.inspect}" + elsif @maximum + "#{result} should have been changed by at most #{@maximum.inspect}, but was changed by #{actual_delta.inspect}" + else + "#{result} should have changed, but is still #{@before.inspect}" + end + end + + def result + @message || "result" + end + + def actual_delta + @after - @before + end + + def negative_failure_message + "#{result} should not have changed, but did change from #{@before.inspect} to #{@after.inspect}" + end + + def by(amount) + @amount = amount + self + end + + def by_at_least(minimum) + @minimum = minimum + self + end + + def by_at_most(maximum) + @maximum = maximum + self + end + + def to(to) + @to = to + self + end + + def from (from) + @from = from + self + end + end + + # :call-seq: + # should change(receiver, message, &block) + # should change(receiver, message, &block).by(value) + # should change(receiver, message, &block).from(old).to(new) + # should_not change(receiver, message, &block) + # + # Allows you to specify that a Proc will cause some value to change. + # + # == Examples + # + # lambda { + # team.add_player(player) + # }.should change(roster, :count) + # + # lambda { + # team.add_player(player) + # }.should change(roster, :count).by(1) + # + # lambda { + # team.add_player(player) + # }.should change(roster, :count).by_at_least(1) + # + # lambda { + # team.add_player(player) + # }.should change(roster, :count).by_at_most(1) + # + # string = "string" + # lambda { + # string.reverse + # }.should change { string }.from("string").to("gnirts") + # + # lambda { + # person.happy_birthday + # }.should change(person, :birthday).from(32).to(33) + # + # lambda { + # employee.develop_great_new_social_networking_app + # }.should change(employee, :title).from("Mail Clerk").to("CEO") + # + # Evaluates +receiver.message+ or +block+ before and + # after it evaluates the c object (generated by the lambdas in the examples above). + # + # Then compares the values before and after the +receiver.message+ and + # evaluates the difference compared to the expected difference. + # + # == Warning + # +should_not+ +change+ only supports the form with no subsequent calls to + # +by+, +by_at_least+, +by_at_most+, +to+ or +from+. + # + # blocks passed to +should+ +change+ and +should_not+ +change+ + # must use the {} form (do/end is not supported) + def change(target=nil, message=nil, &block) + Matchers::Change.new(target, message, &block) + end + end +end diff --git a/vendor/gems/rspec/lib/spec/matchers/eql.rb b/vendor/gems/rspec/lib/spec/matchers/eql.rb new file mode 100644 index 0000000000..280ca5454a --- /dev/null +++ b/vendor/gems/rspec/lib/spec/matchers/eql.rb @@ -0,0 +1,43 @@ +module Spec + module Matchers + + class Eql #:nodoc: + def initialize(expected) + @expected = expected + end + + def matches?(actual) + @actual = actual + @actual.eql?(@expected) + end + + def failure_message + return "expected #{@expected.inspect}, got #{@actual.inspect} (using .eql?)", @expected, @actual + end + + def negative_failure_message + return "expected #{@actual.inspect} not to equal #{@expected.inspect} (using .eql?)", @expected, @actual + end + + def description + "eql #{@expected.inspect}" + end + end + + # :call-seq: + # should eql(expected) + # should_not eql(expected) + # + # Passes if actual and expected are of equal value, but not necessarily the same object. + # + # See http://www.ruby-doc.org/core/classes/Object.html#M001057 for more information about equality in Ruby. + # + # == Examples + # + # 5.should eql(5) + # 5.should_not eql(3) + def eql(expected) + Matchers::Eql.new(expected) + end + end +end diff --git a/vendor/gems/rspec/lib/spec/matchers/equal.rb b/vendor/gems/rspec/lib/spec/matchers/equal.rb new file mode 100644 index 0000000000..4bfc749516 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/matchers/equal.rb @@ -0,0 +1,43 @@ +module Spec + module Matchers + + class Equal #:nodoc: + def initialize(expected) + @expected = expected + end + + def matches?(actual) + @actual = actual + @actual.equal?(@expected) + end + + def failure_message + return "expected #{@expected.inspect}, got #{@actual.inspect} (using .equal?)", @expected, @actual + end + + def negative_failure_message + return "expected #{@actual.inspect} not to equal #{@expected.inspect} (using .equal?)", @expected, @actual + end + + def description + "equal #{@expected.inspect}" + end + end + + # :call-seq: + # should equal(expected) + # should_not equal(expected) + # + # Passes if actual and expected are the same object (object identity). + # + # See http://www.ruby-doc.org/core/classes/Object.html#M001057 for more information about equality in Ruby. + # + # == Examples + # + # 5.should equal(5) #Fixnums are equal + # "5".should_not equal("5") #Strings that look the same are not the same object + def equal(expected) + Matchers::Equal.new(expected) + end + end +end diff --git a/vendor/gems/rspec/lib/spec/matchers/exist.rb b/vendor/gems/rspec/lib/spec/matchers/exist.rb new file mode 100644 index 0000000000..a5a9111323 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/matchers/exist.rb @@ -0,0 +1,17 @@ +module Spec + module Matchers + class Exist + def matches? actual + @actual = actual + @actual.exist? + end + def failure_message + "expected #{@actual.inspect} to exist, but it doesn't." + end + def negative_failure_message + "expected #{@actual.inspect} to not exist, but it does." + end + end + def exist; Exist.new; end + end +end diff --git a/vendor/gems/rspec/lib/spec/matchers/has.rb b/vendor/gems/rspec/lib/spec/matchers/has.rb new file mode 100644 index 0000000000..cc5a250b87 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/matchers/has.rb @@ -0,0 +1,44 @@ +module Spec + module Matchers + + class Has #:nodoc: + def initialize(sym, *args) + @sym = sym + @args = args + end + + def matches?(target) + @target = target + begin + return target.send(predicate, *@args) + rescue => @error + # This clause should be empty, but rcov will not report it as covered + # unless something (anything) is executed within the clause + rcov_error_report = "http://eigenclass.org/hiki.rb?rcov-0.8.0" + end + return false + end + + def failure_message + raise @error if @error + "expected ##{predicate}(#{@args[0].inspect}) to return true, got false" + end + + def negative_failure_message + raise @error if @error + "expected ##{predicate}(#{@args[0].inspect}) to return false, got true" + end + + def description + "have key #{@args[0].inspect}" + end + + private + def predicate + "#{@sym.to_s.sub("have_","has_")}?".to_sym + end + + end + + end +end diff --git a/vendor/gems/rspec/lib/spec/matchers/have.rb b/vendor/gems/rspec/lib/spec/matchers/have.rb new file mode 100644 index 0000000000..47454e3be1 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/matchers/have.rb @@ -0,0 +1,145 @@ +module Spec + module Matchers + + class Have #:nodoc: + def initialize(expected, relativity=:exactly) + @expected = (expected == :no ? 0 : expected) + @relativity = relativity + end + + def relativities + @relativities ||= { + :exactly => "", + :at_least => "at least ", + :at_most => "at most " + } + end + + def method_missing(sym, *args, &block) + @collection_name = sym + @plural_collection_name = Inflector.pluralize(sym.to_s) if Object.const_defined?(:Inflector) + @args = args + @block = block + self + end + + def matches?(collection_owner) + if collection_owner.respond_to?(@collection_name) + collection = collection_owner.send(@collection_name, *@args, &@block) + elsif (@plural_collection_name && collection_owner.respond_to?(@plural_collection_name)) + collection = collection_owner.send(@plural_collection_name, *@args, &@block) + elsif (collection_owner.respond_to?(:length) || collection_owner.respond_to?(:size)) + collection = collection_owner + else + collection_owner.send(@collection_name, *@args, &@block) + end + @actual = collection.size if collection.respond_to?(:size) + @actual = collection.length if collection.respond_to?(:length) + raise not_a_collection if @actual.nil? + return @actual >= @expected if @relativity == :at_least + return @actual <= @expected if @relativity == :at_most + return @actual == @expected + end + + def not_a_collection + "expected #{@collection_name} to be a collection but it does not respond to #length or #size" + end + + def failure_message + "expected #{relative_expectation} #{@collection_name}, got #{@actual}" + end + + def negative_failure_message + if @relativity == :exactly + return "expected target not to have #{@expected} #{@collection_name}, got #{@actual}" + elsif @relativity == :at_most + return <<-EOF +Isn't life confusing enough? +Instead of having to figure out the meaning of this: + should_not have_at_most(#{@expected}).#{@collection_name} +We recommend that you use this instead: + should have_at_least(#{@expected + 1}).#{@collection_name} +EOF + elsif @relativity == :at_least + return <<-EOF +Isn't life confusing enough? +Instead of having to figure out the meaning of this: + should_not have_at_least(#{@expected}).#{@collection_name} +We recommend that you use this instead: + should have_at_most(#{@expected - 1}).#{@collection_name} +EOF + end + end + + def description + "have #{relative_expectation} #{@collection_name}" + end + + private + + def relative_expectation + "#{relativities[@relativity]}#{@expected}" + end + end + + # :call-seq: + # should have(number).named_collection__or__sugar + # should_not have(number).named_collection__or__sugar + # + # Passes if receiver is a collection with the submitted + # number of items OR if the receiver OWNS a collection + # with the submitted number of items. + # + # If the receiver OWNS the collection, you must use the name + # of the collection. So if a Team instance has a + # collection named #players, you must use that name + # to set the expectation. + # + # If the receiver IS the collection, you can use any name + # you like for named_collection. We'd recommend using + # either "elements", "members", or "items" as these are all + # standard ways of describing the things IN a collection. + # + # This also works for Strings, letting you set an expectation + # about its length + # + # == Examples + # + # # Passes if team.players.size == 11 + # team.should have(11).players + # + # # Passes if [1,2,3].length == 3 + # [1,2,3].should have(3).items #"items" is pure sugar + # + # # Passes if "this string".length == 11 + # "this string".should have(11).characters #"characters" is pure sugar + def have(n) + Matchers::Have.new(n) + end + alias :have_exactly :have + + # :call-seq: + # should have_at_least(number).items + # + # Exactly like have() with >=. + # + # == Warning + # + # +should_not+ +have_at_least+ is not supported + def have_at_least(n) + Matchers::Have.new(n, :at_least) + end + + # :call-seq: + # should have_at_most(number).items + # + # Exactly like have() with <=. + # + # == Warning + # + # +should_not+ +have_at_most+ is not supported + def have_at_most(n) + Matchers::Have.new(n, :at_most) + end + end +end diff --git a/vendor/gems/rspec/lib/spec/matchers/include.rb b/vendor/gems/rspec/lib/spec/matchers/include.rb new file mode 100644 index 0000000000..5476f97d89 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/matchers/include.rb @@ -0,0 +1,70 @@ +module Spec + module Matchers + + class Include #:nodoc: + + def initialize(*expecteds) + @expecteds = expecteds + end + + def matches?(actual) + @actual = actual + @expecteds.each do |expected| + return false unless actual.include?(expected) + end + true + end + + def failure_message + _message + end + + def negative_failure_message + _message("not ") + end + + def description + "include #{_pretty_print(@expecteds)}" + end + + private + def _message(maybe_not="") + "expected #{@actual.inspect} #{maybe_not}to include #{_pretty_print(@expecteds)}" + end + + def _pretty_print(array) + result = "" + array.each_with_index do |item, index| + if index < (array.length - 2) + result << "#{item.inspect}, " + elsif index < (array.length - 1) + result << "#{item.inspect} and " + else + result << "#{item.inspect}" + end + end + result + end + end + + # :call-seq: + # should include(expected) + # should_not include(expected) + # + # Passes if actual includes expected. This works for + # collections and Strings. You can also pass in multiple args + # and it will only pass if all args are found in collection. + # + # == Examples + # + # [1,2,3].should include(3) + # [1,2,3].should include(2,3) #would pass + # [1,2,3].should include(2,3,4) #would fail + # [1,2,3].should_not include(4) + # "spread".should include("read") + # "spread".should_not include("red") + def include(*expected) + Matchers::Include.new(*expected) + end + end +end diff --git a/vendor/gems/rspec/lib/spec/matchers/match.rb b/vendor/gems/rspec/lib/spec/matchers/match.rb new file mode 100644 index 0000000000..61ab52429c --- /dev/null +++ b/vendor/gems/rspec/lib/spec/matchers/match.rb @@ -0,0 +1,41 @@ +module Spec + module Matchers + + class Match #:nodoc: + def initialize(expected) + @expected = expected + end + + def matches?(actual) + @actual = actual + return true if actual =~ @expected + return false + end + + def failure_message + return "expected #{@actual.inspect} to match #{@expected.inspect}", @expected, @actual + end + + def negative_failure_message + return "expected #{@actual.inspect} not to match #{@expected.inspect}", @expected, @actual + end + + def description + "match #{@expected.inspect}" + end + end + + # :call-seq: + # should match(regexp) + # should_not match(regexp) + # + # Given a Regexp, passes if actual =~ regexp + # + # == Examples + # + # email.should match(/^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i) + def match(regexp) + Matchers::Match.new(regexp) + end + end +end diff --git a/vendor/gems/rspec/lib/spec/matchers/operator_matcher.rb b/vendor/gems/rspec/lib/spec/matchers/operator_matcher.rb new file mode 100755 index 0000000000..dd23a0994f --- /dev/null +++ b/vendor/gems/rspec/lib/spec/matchers/operator_matcher.rb @@ -0,0 +1,73 @@ +module Spec + module Matchers + class BaseOperatorMatcher + attr_reader :generated_description + + def initialize(target) + @target = target + end + + def ==(expected) + @expected = expected + __delegate_method_missing_to_target("==", expected) + end + + def ===(expected) + @expected = expected + __delegate_method_missing_to_target("===", expected) + end + + def =~(expected) + @expected = expected + __delegate_method_missing_to_target("=~", expected) + end + + def >(expected) + @expected = expected + __delegate_method_missing_to_target(">", expected) + end + + def >=(expected) + @expected = expected + __delegate_method_missing_to_target(">=", expected) + end + + def <(expected) + @expected = expected + __delegate_method_missing_to_target("<", expected) + end + + def <=(expected) + @expected = expected + __delegate_method_missing_to_target("<=", expected) + end + + def fail_with_message(message) + Spec::Expectations.fail_with(message, @expected, @target) + end + + end + + class PositiveOperatorMatcher < BaseOperatorMatcher #:nodoc: + + def __delegate_method_missing_to_target(operator, expected) + ::Spec::Matchers.generated_description = "should #{operator} #{expected.inspect}" + return if @target.send(operator, expected) + return fail_with_message("expected: #{expected.inspect},\n got: #{@target.inspect} (using #{operator})") if ['==','===', '=~'].include?(operator) + return fail_with_message("expected: #{operator} #{expected.inspect},\n got: #{operator.gsub(/./, ' ')} #{@target.inspect}") + end + + end + + class NegativeOperatorMatcher < BaseOperatorMatcher #:nodoc: + + def __delegate_method_missing_to_target(operator, expected) + ::Spec::Matchers.generated_description = "should not #{operator} #{expected.inspect}" + return unless @target.send(operator, expected) + return fail_with_message("expected not: #{operator} #{expected.inspect},\n got: #{operator.gsub(/./, ' ')} #{@target.inspect}") + end + + end + + end +end diff --git a/vendor/gems/rspec/lib/spec/matchers/raise_error.rb b/vendor/gems/rspec/lib/spec/matchers/raise_error.rb new file mode 100644 index 0000000000..65eb4dddae --- /dev/null +++ b/vendor/gems/rspec/lib/spec/matchers/raise_error.rb @@ -0,0 +1,109 @@ +module Spec + module Matchers + + class RaiseError #:nodoc: + def initialize(error_or_message=Exception, message=nil) + if String === error_or_message + @expected_error = Exception + @expected_message = error_or_message + else + @expected_error = error_or_message + @expected_message = message + end + end + + def matches?(proc) + @raised_expected_error = false + @raised_other = false + begin + proc.call + rescue @expected_error => @actual_error + if @expected_message.nil? + @raised_expected_error = true + else + verify_message + end + rescue Exception => @actual_error + @raised_other = true + ensure + return @raised_expected_error + end + end + + def verify_message + case @expected_message + when Regexp + if @expected_message =~ @actual_error.message + @raised_expected_error = true + else + @raised_other = true + end + else + if @expected_message == @actual_error.message + @raised_expected_error = true + else + @raised_other = true + end + end + end + + def failure_message + return "expected #{expected_error}#{actual_error}" if @raised_other || !@raised_expected_error + end + + def negative_failure_message + "expected no #{expected_error}#{actual_error}" + end + + def description + "raise #{expected_error}" + end + + private + def expected_error + case @expected_message + when nil + @expected_error + when Regexp + "#{@expected_error} with message matching #{@expected_message.inspect}" + else + "#{@expected_error} with #{@expected_message.inspect}" + end + end + + def actual_error + @actual_error.nil? ? " but nothing was raised" : ", got #{@actual_error.inspect}" + end + end + + # :call-seq: + # should raise_error() + # should raise_error(NamedError) + # should raise_error(NamedError, String) + # should raise_error(NamedError, Regexp) + # should_not raise_error() + # should_not raise_error(NamedError) + # should_not raise_error(NamedError, String) + # should_not raise_error(NamedError, Regexp) + # + # With no args, matches if any error is raised. + # With a named error, matches only if that specific error is raised. + # With a named error and messsage specified as a String, matches only if both match. + # With a named error and messsage specified as a Regexp, matches only if both match. + # + # == Examples + # + # lambda { do_something_risky }.should raise_error + # lambda { do_something_risky }.should raise_error(PoorRiskDecisionError) + # lambda { do_something_risky }.should raise_error(PoorRiskDecisionError, "that was too risky") + # lambda { do_something_risky }.should raise_error(PoorRiskDecisionError, /oo ri/) + # + # lambda { do_something_risky }.should_not raise_error + # lambda { do_something_risky }.should_not raise_error(PoorRiskDecisionError) + # lambda { do_something_risky }.should_not raise_error(PoorRiskDecisionError, "that was too risky") + # lambda { do_something_risky }.should_not raise_error(PoorRiskDecisionError, /oo ri/) + def raise_error(error=Exception, message=nil) + Matchers::RaiseError.new(error, message) + end + end +end diff --git a/vendor/gems/rspec/lib/spec/matchers/respond_to.rb b/vendor/gems/rspec/lib/spec/matchers/respond_to.rb new file mode 100644 index 0000000000..3d23422aa6 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/matchers/respond_to.rb @@ -0,0 +1,45 @@ +module Spec + module Matchers + + class RespondTo #:nodoc: + def initialize(*names) + @names = names + @names_not_responded_to = [] + end + + def matches?(target) + @names.each do |name| + unless target.respond_to?(name) + @names_not_responded_to << name + end + end + return @names_not_responded_to.empty? + end + + def failure_message + "expected target to respond to #{@names_not_responded_to.collect {|name| name.inspect }.join(', ')}" + end + + def negative_failure_message + "expected target not to respond to #{@names.collect {|name| name.inspect }.join(', ')}" + end + + def description + "respond to ##{@names.to_s}" + end + end + + # :call-seq: + # should respond_to(*names) + # should_not respond_to(*names) + # + # Matches if the target object responds to all of the names + # provided. Names can be Strings or Symbols. + # + # == Examples + # + def respond_to(*names) + Matchers::RespondTo.new(*names) + end + end +end diff --git a/vendor/gems/rspec/lib/spec/matchers/satisfy.rb b/vendor/gems/rspec/lib/spec/matchers/satisfy.rb new file mode 100644 index 0000000000..6c0ca95bc5 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/matchers/satisfy.rb @@ -0,0 +1,47 @@ +module Spec + module Matchers + + class Satisfy #:nodoc: + def initialize(&block) + @block = block + end + + def matches?(actual, &block) + @block = block if block + @actual = actual + @block.call(actual) + end + + def failure_message + "expected #{@actual} to satisfy block" + end + + def negative_failure_message + "expected #{@actual} not to satisfy block" + end + end + + # :call-seq: + # should satisfy {} + # should_not satisfy {} + # + # Passes if the submitted block returns true. Yields target to the + # block. + # + # Generally speaking, this should be thought of as a last resort when + # you can't find any other way to specify the behaviour you wish to + # specify. + # + # If you do find yourself in such a situation, you could always write + # a custom matcher, which would likely make your specs more expressive. + # + # == Examples + # + # 5.should satisfy { |n| + # n > 3 + # } + def satisfy(&block) + Matchers::Satisfy.new(&block) + end + end +end diff --git a/vendor/gems/rspec/lib/spec/matchers/simple_matcher.rb b/vendor/gems/rspec/lib/spec/matchers/simple_matcher.rb new file mode 100644 index 0000000000..ac547d06a0 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/matchers/simple_matcher.rb @@ -0,0 +1,29 @@ +module Spec + module Matchers + class SimpleMatcher + attr_reader :description + + def initialize(description, &match_block) + @description = description + @match_block = match_block + end + + def matches?(actual) + @actual = actual + return @match_block.call(@actual) + end + + def failure_message() + return %[expected #{@description.inspect} but got #{@actual.inspect}] + end + + def negative_failure_message() + return %[expected not to get #{@description.inspect}, but got #{@actual.inspect}] + end + end + + def simple_matcher(message, &match_block) + SimpleMatcher.new(message, &match_block) + end + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/lib/spec/matchers/throw_symbol.rb b/vendor/gems/rspec/lib/spec/matchers/throw_symbol.rb new file mode 100644 index 0000000000..c74d844366 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/matchers/throw_symbol.rb @@ -0,0 +1,74 @@ +module Spec + module Matchers + + class ThrowSymbol #:nodoc: + def initialize(expected=nil) + @expected = expected + @actual = nil + end + + def matches?(proc) + begin + proc.call + rescue NameError => e + raise e unless e.message =~ /uncaught throw/ + @actual = e.name.to_sym + ensure + if @expected.nil? + return @actual.nil? ? false : true + else + return @actual == @expected + end + end + end + + def failure_message + if @actual + "expected #{expected}, got #{@actual.inspect}" + else + "expected #{expected} but nothing was thrown" + end + end + + def negative_failure_message + if @expected + "expected #{expected} not to be thrown" + else + "expected no Symbol, got :#{@actual}" + end + end + + def description + "throw #{expected}" + end + + private + + def expected + @expected.nil? ? "a Symbol" : @expected.inspect + end + + end + + # :call-seq: + # should throw_symbol() + # should throw_symbol(:sym) + # should_not throw_symbol() + # should_not throw_symbol(:sym) + # + # Given a Symbol argument, matches if a proc throws the specified Symbol. + # + # Given no argument, matches if a proc throws any Symbol. + # + # == Examples + # + # lambda { do_something_risky }.should throw_symbol + # lambda { do_something_risky }.should throw_symbol(:that_was_risky) + # + # lambda { do_something_risky }.should_not throw_symbol + # lambda { do_something_risky }.should_not throw_symbol(:that_was_risky) + def throw_symbol(sym=nil) + Matchers::ThrowSymbol.new(sym) + end + end +end diff --git a/vendor/gems/rspec/lib/spec/mocks.rb b/vendor/gems/rspec/lib/spec/mocks.rb new file mode 100644 index 0000000000..9f9cd215bd --- /dev/null +++ b/vendor/gems/rspec/lib/spec/mocks.rb @@ -0,0 +1,211 @@ +require 'spec/mocks/methods' +require 'spec/mocks/argument_constraint_matchers' +require 'spec/mocks/spec_methods' +require 'spec/mocks/proxy' +require 'spec/mocks/mock' +require 'spec/mocks/argument_expectation' +require 'spec/mocks/message_expectation' +require 'spec/mocks/order_group' +require 'spec/mocks/errors' +require 'spec/mocks/error_generator' +require 'spec/mocks/extensions/object' +require 'spec/mocks/space' + + +module Spec + # == Mocks and Stubs + # + # RSpec will create Mock Objects and Stubs for you at runtime, or attach stub/mock behaviour + # to any of your real objects (Partial Mock/Stub). Because the underlying implementation + # for mocks and stubs is the same, you can intermingle mock and stub + # behaviour in either dynamically generated mocks or your pre-existing classes. + # There is a semantic difference in how they are created, however, + # which can help clarify the role it is playing within a given spec. + # + # == Mock Objects + # + # Mocks are objects that allow you to set and verify expectations that they will + # receive specific messages during run time. They are very useful for specifying how the subject of + # the spec interacts with its collaborators. This approach is widely known as "interaction + # testing". + # + # Mocks are also very powerful as a design tool. As you are + # driving the implementation of a given class, Mocks provide an anonymous + # collaborator that can change in behaviour as quickly as you can write an expectation in your + # spec. This flexibility allows you to design the interface of a collaborator that often + # does not yet exist. As the shape of the class being specified becomes more clear, so do the + # requirements for its collaborators - often leading to the discovery of new types that are + # needed in your system. + # + # Read Endo-Testing[http://www.mockobjects.com/files/endotesting.pdf] for a much + # more in depth description of this process. + # + # == Stubs + # + # Stubs are objects that allow you to set "stub" responses to + # messages. As Martin Fowler points out on his site, + # mocks_arent_stubs[http://www.martinfowler.com/articles/mocksArentStubs.html]. + # Paraphrasing Fowler's paraphrasing + # of Gerard Meszaros: Stubs provide canned responses to messages they might receive in a test, while + # mocks allow you to specify and, subsquently, verify that certain messages should be received during + # the execution of a test. + # + # == Partial Mocks/Stubs + # + # RSpec also supports partial mocking/stubbing, allowing you to add stub/mock behaviour + # to instances of your existing classes. This is generally + # something to be avoided, because changes to the class can have ripple effects on + # seemingly unrelated specs. When specs fail due to these ripple effects, the fact + # that some methods are being mocked can make it difficult to understand why a + # failure is occurring. + # + # That said, partials do allow you to expect and + # verify interactions with class methods such as +#find+ and +#create+ + # on Ruby on Rails model classes. + # + # == Further Reading + # + # There are many different viewpoints about the meaning of mocks and stubs. If you are interested + # in learning more, here is some recommended reading: + # + # * Mock Objects: http://www.mockobjects.com/ + # * Endo-Testing: http://www.mockobjects.com/files/endotesting.pdf + # * Mock Roles, Not Objects: http://www.mockobjects.com/files/mockrolesnotobjects.pdf + # * Test Double Patterns: http://xunitpatterns.com/Test%20Double%20Patterns.html + # * Mocks aren't stubs: http://www.martinfowler.com/articles/mocksArentStubs.html + # + # == Creating a Mock + # + # You can create a mock in any specification (or setup) using: + # + # mock(name, options={}) + # + # The optional +options+ argument is a +Hash+. Currently the only supported + # option is +:null_object+. Setting this to true instructs the mock to ignore + # any messages it hasn’t been told to expect – and quietly return itself. For example: + # + # mock("person", :null_object => true) + # + # == Creating a Stub + # + # You can create a stub in any specification (or setup) using: + # + # stub(name, stub_methods_and_values_hash) + # + # For example, if you wanted to create an object that always returns + # "More?!?!?!" to "please_sir_may_i_have_some_more" you would do this: + # + # stub("Mr Sykes", :please_sir_may_i_have_some_more => "More?!?!?!") + # + # == Creating a Partial Mock + # + # You don't really "create" a partial mock, you simply add method stubs and/or + # mock expectations to existing classes and objects: + # + # Factory.should_receive(:find).with(id).and_return(value) + # obj.stub!(:to_i).and_return(3) + # etc ... + # + # == Expecting Messages + # + # my_mock.should_receive(:sym) + # my_mock.should_not_receive(:sym) + # + # == Expecting Arguments + # + # my_mock.should_receive(:sym).with(*args) + # my_mock.should_not_receive(:sym).with(*args) + # + # == Argument Constraints using Expression Matchers + # + # Arguments that are passed to #with are compared with actual arguments received + # using == by default. In cases in which you want to specify things about the arguments + # rather than the arguments themselves, you can use any of the Expression Matchers. + # They don't all make syntactic sense (they were primarily designed for use with + # Spec::Expectations), but you are free to create your own custom Spec::Matchers. + # + # Spec::Mocks does provide one additional Matcher method named #ducktype. + # + # In addition, Spec::Mocks adds some keyword Symbols that you can use to + # specify certain kinds of arguments: + # + # my_mock.should_receive(:sym).with(no_args()) + # my_mock.should_receive(:sym).with(any_args()) + # my_mock.should_receive(:sym).with(1, an_instance_of(Numeric), "b") #2nd argument can any type of Numeric + # my_mock.should_receive(:sym).with(1, boolean(), "b") #2nd argument can true or false + # my_mock.should_receive(:sym).with(1, /abc/, "b") #2nd argument can be any String matching the submitted Regexp + # my_mock.should_receive(:sym).with(1, anything(), "b") #2nd argument can be anything at all + # my_mock.should_receive(:sym).with(1, ducktype(:abs, :div), "b") + # #2nd argument can be object that responds to #abs and #div + # + # == Receive Counts + # + # my_mock.should_receive(:sym).once + # my_mock.should_receive(:sym).twice + # my_mock.should_receive(:sym).exactly(n).times + # my_mock.should_receive(:sym).at_least(:once) + # my_mock.should_receive(:sym).at_least(:twice) + # my_mock.should_receive(:sym).at_least(n).times + # my_mock.should_receive(:sym).at_most(:once) + # my_mock.should_receive(:sym).at_most(:twice) + # my_mock.should_receive(:sym).at_most(n).times + # my_mock.should_receive(:sym).any_number_of_times + # + # == Ordering + # + # my_mock.should_receive(:sym).ordered + # my_mock.should_receive(:other_sym).ordered + # #This will fail if the messages are received out of order + # + # == Setting Reponses + # + # Whether you are setting a mock expectation or a simple stub, you can tell the + # object precisely how to respond: + # + # my_mock.should_receive(:sym).and_return(value) + # my_mock.should_receive(:sym).exactly(3).times.and_return(value1, value2, value3) + # # returns value1 the first time, value2 the second, etc + # my_mock.should_receive(:sym).and_return { ... } #returns value returned by the block + # my_mock.should_receive(:sym).and_raise(error) + # #error can be an instantiated object or a class + # #if it is a class, it must be instantiable with no args + # my_mock.should_receive(:sym).and_throw(:sym) + # my_mock.should_receive(:sym).and_yield(values,to,yield) + # my_mock.should_receive(:sym).and_yield(values,to,yield).and_yield(some,other,values,this,time) + # # for methods that yield to a block multiple times + # + # Any of these responses can be applied to a stub as well, but stubs do + # not support any qualifiers about the message received (i.e. you can't specify arguments + # or receive counts): + # + # my_mock.stub!(:sym).and_return(value) + # my_mock.stub!(:sym).and_return(value1, value2, value3) + # my_mock.stub!(:sym).and_raise(error) + # my_mock.stub!(:sym).and_throw(:sym) + # my_mock.stub!(:sym).and_yield(values,to,yield) + # my_mock.stub!(:sym).and_yield(values,to,yield).and_yield(some,other,values,this,time) + # + # == Arbitrary Handling + # + # Once in a while you'll find that the available expectations don't solve the + # particular problem you are trying to solve. Imagine that you expect the message + # to come with an Array argument that has a specific length, but you don't care + # what is in it. You could do this: + # + # my_mock.should_receive(:sym) do |arg| + # arg.should be_an_istance_of(Array) + # arg.length.should == 7 + # end + # + # Note that this would fail if the number of arguments received was different from + # the number of block arguments (in this case 1). + # + # == Combining Expectation Details + # + # Combining the message name with specific arguments, receive counts and responses + # you can get quite a bit of detail in your expectations: + # + # my_mock.should_receive(:<<).with("illegal value").once.and_raise(ArgumentError) + module Mocks + end +end diff --git a/vendor/gems/rspec/lib/spec/mocks/argument_constraint_matchers.rb b/vendor/gems/rspec/lib/spec/mocks/argument_constraint_matchers.rb new file mode 100644 index 0000000000..0e47770821 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/mocks/argument_constraint_matchers.rb @@ -0,0 +1,27 @@ +module Spec + module Mocks + module ArgumentConstraintMatchers + + # Shortcut for creating an instance of Spec::Mocks::DuckTypeArgConstraint + def duck_type(*args) + DuckTypeArgConstraint.new(*args) + end + + def any_args + AnyArgsConstraint.new + end + + def anything + AnyArgConstraint.new(nil) + end + + def boolean + BooleanArgConstraint.new(nil) + end + + def no_args + NoArgsConstraint.new + end + end + end +end diff --git a/vendor/gems/rspec/lib/spec/mocks/argument_expectation.rb b/vendor/gems/rspec/lib/spec/mocks/argument_expectation.rb new file mode 100644 index 0000000000..34a1d4d039 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/mocks/argument_expectation.rb @@ -0,0 +1,183 @@ +module Spec + module Mocks + + class MatcherConstraint + def initialize(matcher) + @matcher = matcher + end + + def matches?(value) + @matcher.matches?(value) + end + end + + class LiteralArgConstraint + def initialize(literal) + @literal_value = literal + end + + def matches?(value) + @literal_value == value + end + end + + class RegexpArgConstraint + def initialize(regexp) + @regexp = regexp + end + + def matches?(value) + return value =~ @regexp unless value.is_a?(Regexp) + value == @regexp + end + end + + class AnyArgConstraint + def initialize(ignore) + end + + def ==(other) + true + end + + # TODO - need this? + def matches?(value) + true + end + end + + class AnyArgsConstraint + def description + "any args" + end + end + + class NoArgsConstraint + def description + "no args" + end + + def ==(args) + args == [] + end + end + + class NumericArgConstraint + def initialize(ignore) + end + + def matches?(value) + value.is_a?(Numeric) + end + end + + class BooleanArgConstraint + def initialize(ignore) + end + + def ==(value) + matches?(value) + end + + def matches?(value) + return true if value.is_a?(TrueClass) + return true if value.is_a?(FalseClass) + false + end + end + + class StringArgConstraint + def initialize(ignore) + end + + def matches?(value) + value.is_a?(String) + end + end + + class DuckTypeArgConstraint + def initialize(*methods_to_respond_to) + @methods_to_respond_to = methods_to_respond_to + end + + def matches?(value) + @methods_to_respond_to.all? { |sym| value.respond_to?(sym) } + end + + def description + "duck_type" + end + end + + class ArgumentExpectation + attr_reader :args + @@constraint_classes = Hash.new { |hash, key| LiteralArgConstraint} + @@constraint_classes[:anything] = AnyArgConstraint + @@constraint_classes[:numeric] = NumericArgConstraint + @@constraint_classes[:boolean] = BooleanArgConstraint + @@constraint_classes[:string] = StringArgConstraint + + def initialize(args) + @args = args + if [:any_args] == args + @expected_params = nil + warn_deprecated(:any_args.inspect, "any_args()") + elsif args.length == 1 && args[0].is_a?(AnyArgsConstraint) then @expected_params = nil + elsif [:no_args] == args + @expected_params = [] + warn_deprecated(:no_args.inspect, "no_args()") + elsif args.length == 1 && args[0].is_a?(NoArgsConstraint) then @expected_params = [] + else @expected_params = process_arg_constraints(args) + end + end + + def process_arg_constraints(constraints) + constraints.collect do |constraint| + convert_constraint(constraint) + end + end + + def warn_deprecated(deprecated_method, instead) + Kernel.warn "The #{deprecated_method} constraint is deprecated. Use #{instead} instead." + end + + def convert_constraint(constraint) + if [:anything, :numeric, :boolean, :string].include?(constraint) + case constraint + when :anything + instead = "anything()" + when :boolean + instead = "boolean()" + when :numeric + instead = "an_instance_of(Numeric)" + when :string + instead = "an_instance_of(String)" + end + warn_deprecated(constraint.inspect, instead) + return @@constraint_classes[constraint].new(constraint) + end + return MatcherConstraint.new(constraint) if is_matcher?(constraint) + return RegexpArgConstraint.new(constraint) if constraint.is_a?(Regexp) + return LiteralArgConstraint.new(constraint) + end + + def is_matcher?(obj) + return obj.respond_to?(:matches?) && obj.respond_to?(:description) + end + + def check_args(args) + return true if @expected_params.nil? + return true if @expected_params == args + return constraints_match?(args) + end + + def constraints_match?(args) + return false if args.length != @expected_params.length + @expected_params.each_index { |i| return false unless @expected_params[i].matches?(args[i]) } + return true + end + + end + + end +end diff --git a/vendor/gems/rspec/lib/spec/mocks/error_generator.rb b/vendor/gems/rspec/lib/spec/mocks/error_generator.rb new file mode 100644 index 0000000000..01d8f720d5 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/mocks/error_generator.rb @@ -0,0 +1,84 @@ +module Spec + module Mocks + class ErrorGenerator + attr_writer :opts + + def initialize(target, name) + @target = target + @name = name + end + + def opts + @opts ||= {} + end + + def raise_unexpected_message_error(sym, *args) + __raise "#{intro} received unexpected message :#{sym}#{arg_message(*args)}" + end + + def raise_unexpected_message_args_error(expectation, *args) + expected_args = format_args(*expectation.expected_args) + actual_args = args.empty? ? "(no args)" : format_args(*args) + __raise "#{intro} expected #{expectation.sym.inspect} with #{expected_args} but received it with #{actual_args}" + end + + def raise_expectation_error(sym, expected_received_count, actual_received_count, *args) + __raise "#{intro} expected :#{sym}#{arg_message(*args)} #{count_message(expected_received_count)}, but received it #{count_message(actual_received_count)}" + end + + def raise_out_of_order_error(sym) + __raise "#{intro} received :#{sym} out of order" + end + + def raise_block_failed_error(sym, detail) + __raise "#{intro} received :#{sym} but passed block failed with: #{detail}" + end + + def raise_missing_block_error(args_to_yield) + __raise "#{intro} asked to yield |#{arg_list(*args_to_yield)}| but no block was passed" + end + + def raise_wrong_arity_error(args_to_yield, arity) + __raise "#{intro} yielded |#{arg_list(*args_to_yield)}| to block with arity of #{arity}" + end + + private + def intro + @name ? "Mock '#{@name}'" : @target.inspect + end + + def __raise(message) + message = opts[:message] unless opts[:message].nil? + Kernel::raise(Spec::Mocks::MockExpectationError, message) + end + + def arg_message(*args) + " with " + format_args(*args) + end + + def format_args(*args) + return "(no args)" if args.empty? || args == [:no_args] + return "(any args)" if args == [:any_args] + "(" + arg_list(*args) + ")" + end + + def arg_list(*args) + args.collect do |arg| + arg.respond_to?(:description) ? arg.description : arg.inspect + end.join(", ") + end + + def count_message(count) + return "at least #{pretty_print(count.abs)}" if count < 0 + return pretty_print(count) + end + + def pretty_print(count) + return "once" if count == 1 + return "twice" if count == 2 + return "#{count} times" + end + + end + end +end diff --git a/vendor/gems/rspec/lib/spec/mocks/errors.rb b/vendor/gems/rspec/lib/spec/mocks/errors.rb new file mode 100644 index 0000000000..68fdfe006f --- /dev/null +++ b/vendor/gems/rspec/lib/spec/mocks/errors.rb @@ -0,0 +1,10 @@ +module Spec + module Mocks + class MockExpectationError < StandardError + end + + class AmbiguousReturnError < StandardError + end + end +end + diff --git a/vendor/gems/rspec/lib/spec/mocks/extensions/object.rb b/vendor/gems/rspec/lib/spec/mocks/extensions/object.rb new file mode 100644 index 0000000000..4b75310663 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/mocks/extensions/object.rb @@ -0,0 +1,3 @@ +class Object + include Spec::Mocks::Methods +end diff --git a/vendor/gems/rspec/lib/spec/mocks/message_expectation.rb b/vendor/gems/rspec/lib/spec/mocks/message_expectation.rb new file mode 100644 index 0000000000..6bd2f1c32c --- /dev/null +++ b/vendor/gems/rspec/lib/spec/mocks/message_expectation.rb @@ -0,0 +1,267 @@ +module Spec + module Mocks + + class BaseExpectation + attr_reader :sym + + def initialize(error_generator, expectation_ordering, expected_from, sym, method_block, expected_received_count=1, opts={}) + @error_generator = error_generator + @error_generator.opts = opts + @expected_from = expected_from + @sym = sym + @method_block = method_block + @return_block = nil + @actual_received_count = 0 + @expected_received_count = expected_received_count + @args_expectation = ArgumentExpectation.new([AnyArgsConstraint.new]) + @consecutive = false + @exception_to_raise = nil + @symbol_to_throw = nil + @order_group = expectation_ordering + @at_least = nil + @at_most = nil + @args_to_yield = [] + end + + def expected_args + @args_expectation.args + end + + def and_return(*values, &return_block) + Kernel::raise AmbiguousReturnError unless @method_block.nil? + case values.size + when 0 then value = nil + when 1 then value = values[0] + else + value = values + @consecutive = true + @expected_received_count = values.size if !ignoring_args? && + @expected_received_count < values.size + end + @return_block = block_given? ? return_block : lambda { value } + # Ruby 1.9 - see where this is used below + @ignore_args = !block_given? + end + + # :call-seq: + # and_raise() + # and_raise(Exception) #any exception class + # and_raise(exception) #any exception object + # + # == Warning + # + # When you pass an exception class, the MessageExpectation will + # raise an instance of it, creating it with +new+. If the exception + # class initializer requires any parameters, you must pass in an + # instance and not the class. + def and_raise(exception=Exception) + @exception_to_raise = exception + end + + def and_throw(symbol) + @symbol_to_throw = symbol + end + + def and_yield(*args) + @args_to_yield << args + self + end + + def matches(sym, args) + @sym == sym and @args_expectation.check_args(args) + end + + def invoke(args, block) + @order_group.handle_order_constraint self + + begin + Kernel::raise @exception_to_raise unless @exception_to_raise.nil? + Kernel::throw @symbol_to_throw unless @symbol_to_throw.nil? + + if !@method_block.nil? + default_return_val = invoke_method_block(args) + elsif @args_to_yield.size > 0 + default_return_val = invoke_with_yield(block) + else + default_return_val = nil + end + + if @consecutive + return invoke_consecutive_return_block(args, block) + elsif @return_block + return invoke_return_block(args, block) + else + return default_return_val + end + ensure + @actual_received_count += 1 + end + end + + protected + + def invoke_method_block(args) + begin + @method_block.call(*args) + rescue => detail + @error_generator.raise_block_failed_error @sym, detail.message + end + end + + def invoke_with_yield(block) + if block.nil? + @error_generator.raise_missing_block_error @args_to_yield + end + @args_to_yield.each do |args_to_yield_this_time| + if block.arity > -1 && args_to_yield_this_time.length != block.arity + @error_generator.raise_wrong_arity_error args_to_yield_this_time, block.arity + end + block.call(*args_to_yield_this_time) + end + end + + def invoke_consecutive_return_block(args, block) + args << block unless block.nil? + value = @return_block.call(*args) + + index = [@actual_received_count, value.size-1].min + value[index] + end + + def invoke_return_block(args, block) + args << block unless block.nil? + # Ruby 1.9 - when we set @return_block to return values + # regardless of arguments, any arguments will result in + # a "wrong number of arguments" error + if @ignore_args + @return_block.call() + else + @return_block.call(*args) + end + end + end + + class MessageExpectation < BaseExpectation + + def matches_name_but_not_args(sym, args) + @sym == sym and not @args_expectation.check_args(args) + end + + def verify_messages_received + return if ignoring_args? || matches_exact_count? || + matches_at_least_count? || matches_at_most_count? + + generate_error + rescue Spec::Mocks::MockExpectationError => error + error.backtrace.insert(0, @expected_from) + Kernel::raise error + end + + def ignoring_args? + @expected_received_count == :any + end + + def matches_at_least_count? + @at_least && @actual_received_count >= @expected_received_count + end + + def matches_at_most_count? + @at_most && @actual_received_count <= @expected_received_count + end + + def matches_exact_count? + @expected_received_count == @actual_received_count + end + + def generate_error + @error_generator.raise_expectation_error(@sym, @expected_received_count, @actual_received_count, *@args_expectation.args) + end + + def with(*args, &block) + @method_block = block if block + @args_expectation = ArgumentExpectation.new(args) + self + end + + def exactly(n) + set_expected_received_count :exactly, n + self + end + + def at_least(n) + set_expected_received_count :at_least, n + self + end + + def at_most(n) + set_expected_received_count :at_most, n + self + end + + def times(&block) + @method_block = block if block + self + end + + def any_number_of_times(&block) + @method_block = block if block + @expected_received_count = :any + self + end + + def never + @expected_received_count = 0 + self + end + + def once(&block) + @method_block = block if block + @expected_received_count = 1 + self + end + + def twice(&block) + @method_block = block if block + @expected_received_count = 2 + self + end + + def ordered(&block) + @method_block = block if block + @order_group.register(self) + @ordered = true + self + end + + def negative_expectation_for?(sym) + return false + end + + protected + def set_expected_received_count(relativity, n) + @at_least = (relativity == :at_least) + @at_most = (relativity == :at_most) + @expected_received_count = case n + when Numeric + n + when :once + 1 + when :twice + 2 + end + end + + end + + class NegativeMessageExpectation < MessageExpectation + def initialize(message, expectation_ordering, expected_from, sym, method_block) + super(message, expectation_ordering, expected_from, sym, method_block, 0) + end + + def negative_expectation_for?(sym) + return @sym == sym + end + end + + end +end diff --git a/vendor/gems/rspec/lib/spec/mocks/methods.rb b/vendor/gems/rspec/lib/spec/mocks/methods.rb new file mode 100644 index 0000000000..d9fa324d3f --- /dev/null +++ b/vendor/gems/rspec/lib/spec/mocks/methods.rb @@ -0,0 +1,39 @@ +module Spec + module Mocks + module Methods + def should_receive(sym, opts={}, &block) + __mock_proxy.add_message_expectation(opts[:expected_from] || caller(1)[0], sym.to_sym, opts, &block) + end + + def should_not_receive(sym, &block) + __mock_proxy.add_negative_message_expectation(caller(1)[0], sym.to_sym, &block) + end + + def stub!(sym, opts={}) + __mock_proxy.add_stub(caller(1)[0], sym.to_sym, opts) + end + + def received_message?(sym, *args, &block) #:nodoc: + __mock_proxy.received_message?(sym.to_sym, *args, &block) + end + + def rspec_verify #:nodoc: + __mock_proxy.verify + end + + def rspec_reset #:nodoc: + __mock_proxy.reset + end + + private + + def __mock_proxy + if Mock === self + @mock_proxy ||= Proxy.new(self, @name, @options) + else + @mock_proxy ||= Proxy.new(self, self.class.name) + end + end + end + end +end diff --git a/vendor/gems/rspec/lib/spec/mocks/mock.rb b/vendor/gems/rspec/lib/spec/mocks/mock.rb new file mode 100644 index 0000000000..f029b1b8f1 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/mocks/mock.rb @@ -0,0 +1,50 @@ +module Spec + module Mocks + class Mock + include Methods + + # Creates a new mock with a +name+ (that will be used in error messages only) + # == Options: + # * :null_object - if true, the mock object acts as a forgiving null object allowing any message to be sent to it. + def initialize(name, stubs_and_options={}) + @name = name + @options = parse_options(stubs_and_options) + assign_stubs(stubs_and_options) + end + + # This allows for comparing the mock to other objects that proxy + # such as ActiveRecords belongs_to proxy objects + # By making the other object run the comparison, we're sure the call gets delegated to the proxy target + # This is an unfortunate side effect from ActiveRecord, but this should be safe unless the RHS redefines == in a nonsensical manner + def ==(other) + other == __mock_proxy + end + + def method_missing(sym, *args, &block) + __mock_proxy.instance_eval {@messages_received << [sym, args, block]} + begin + return self if __mock_proxy.null_object? + super(sym, *args, &block) + rescue NameError + __mock_proxy.raise_unexpected_message_error sym, *args + end + end + + def inspect + "#<#{self.class}:#{sprintf '0x%x', self.object_id} @name=#{@name.inspect}>" + end + + private + + def parse_options(options) + options.has_key?(:null_object) ? {:null_object => options.delete(:null_object)} : {} + end + + def assign_stubs(stubs) + stubs.each_pair do |message, response| + stub!(message).and_return(response) + end + end + end + end +end diff --git a/vendor/gems/rspec/lib/spec/mocks/order_group.rb b/vendor/gems/rspec/lib/spec/mocks/order_group.rb new file mode 100644 index 0000000000..9983207eb7 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/mocks/order_group.rb @@ -0,0 +1,29 @@ +module Spec + module Mocks + class OrderGroup + def initialize error_generator + @error_generator = error_generator + @ordering = Array.new + end + + def register(expectation) + @ordering << expectation + end + + def ready_for?(expectation) + return @ordering.first == expectation + end + + def consume + @ordering.shift + end + + def handle_order_constraint expectation + return unless @ordering.include? expectation + return consume if ready_for?(expectation) + @error_generator.raise_out_of_order_error expectation.sym + end + + end + end +end diff --git a/vendor/gems/rspec/lib/spec/mocks/proxy.rb b/vendor/gems/rspec/lib/spec/mocks/proxy.rb new file mode 100644 index 0000000000..03db3b1134 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/mocks/proxy.rb @@ -0,0 +1,170 @@ +module Spec + module Mocks + class Proxy + DEFAULT_OPTIONS = { + :null_object => false, + } + + def initialize(target, name, options={}) + @target = target + @name = name + @error_generator = ErrorGenerator.new target, name + @expectation_ordering = OrderGroup.new @error_generator + @expectations = [] + @messages_received = [] + @stubs = [] + @proxied_methods = [] + @options = options ? DEFAULT_OPTIONS.dup.merge(options) : DEFAULT_OPTIONS + end + + def null_object? + @options[:null_object] + end + + def add_message_expectation(expected_from, sym, opts={}, &block) + __add sym + @expectations << MessageExpectation.new(@error_generator, @expectation_ordering, expected_from, sym, block_given? ? block : nil, 1, opts) + @expectations.last + end + + def add_negative_message_expectation(expected_from, sym, &block) + __add sym + @expectations << NegativeMessageExpectation.new(@error_generator, @expectation_ordering, expected_from, sym, block_given? ? block : nil) + @expectations.last + end + + def add_stub(expected_from, sym, opts={}) + __add sym + @stubs.unshift MessageExpectation.new(@error_generator, @expectation_ordering, expected_from, sym, nil, :any, opts) + @stubs.first + end + + def verify #:nodoc: + verify_expectations + ensure + reset + end + + def reset + clear_expectations + clear_stubs + reset_proxied_methods + clear_proxied_methods + end + + def received_message?(sym, *args, &block) + @messages_received.any? {|array| array == [sym, args, block]} + end + + def has_negative_expectation?(sym) + @expectations.detect {|expectation| expectation.negative_expectation_for?(sym)} + end + + def message_received(sym, *args, &block) + if expectation = find_matching_expectation(sym, *args) + expectation.invoke(args, block) + elsif stub = find_matching_method_stub(sym, *args) + stub.invoke([], block) + elsif expectation = find_almost_matching_expectation(sym, *args) + raise_unexpected_message_args_error(expectation, *args) unless has_negative_expectation?(sym) unless null_object? + else + @target.send :method_missing, sym, *args, &block + end + end + + def raise_unexpected_message_args_error(expectation, *args) + @error_generator.raise_unexpected_message_args_error expectation, *args + end + + def raise_unexpected_message_error(sym, *args) + @error_generator.raise_unexpected_message_error sym, *args + end + + private + + def __add(sym) + $rspec_mocks.add(@target) unless $rspec_mocks.nil? + define_expected_method(sym) + end + + def define_expected_method(sym) + if target_responds_to?(sym) && !metaclass.method_defined?(munge(sym)) + munged_sym = munge(sym) + metaclass.instance_eval do + alias_method munged_sym, sym if method_defined?(sym.to_s) + end + @proxied_methods << sym + end + + metaclass_eval(<<-EOF, __FILE__, __LINE__) + def #{sym}(*args, &block) + __mock_proxy.message_received :#{sym}, *args, &block + end + EOF + end + + def target_responds_to?(sym) + return @target.send(munge(:respond_to?),sym) if @already_proxied_respond_to + return @already_proxied_respond_to = true if sym == :respond_to? + return @target.respond_to?(sym) + end + + def munge(sym) + "proxied_by_rspec__#{sym.to_s}".to_sym + end + + def clear_expectations + @expectations.clear + end + + def clear_stubs + @stubs.clear + end + + def clear_proxied_methods + @proxied_methods.clear + end + + def metaclass_eval(str, filename, lineno) + metaclass.class_eval(str, filename, lineno) + end + + def metaclass + (class << @target; self; end) + end + + def verify_expectations + @expectations.each do |expectation| + expectation.verify_messages_received + end + end + + def reset_proxied_methods + @proxied_methods.each do |sym| + munged_sym = munge(sym) + metaclass.instance_eval do + if method_defined?(munged_sym.to_s) + alias_method sym, munged_sym + undef_method munged_sym + else + undef_method sym + end + end + end + end + + def find_matching_expectation(sym, *args) + @expectations.find {|expectation| expectation.matches(sym, args)} + end + + def find_almost_matching_expectation(sym, *args) + @expectations.find {|expectation| expectation.matches_name_but_not_args(sym, args)} + end + + def find_matching_method_stub(sym, *args) + @stubs.find {|stub| stub.matches(sym, args)} + end + + end + end +end diff --git a/vendor/gems/rspec/lib/spec/mocks/space.rb b/vendor/gems/rspec/lib/spec/mocks/space.rb new file mode 100644 index 0000000000..3e13224c70 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/mocks/space.rb @@ -0,0 +1,28 @@ +module Spec + module Mocks + class Space + def add(obj) + mocks << obj unless mocks.detect {|m| m.equal? obj} + end + + def verify_all + mocks.each do |mock| + mock.rspec_verify + end + end + + def reset_all + mocks.each do |mock| + mock.rspec_reset + end + mocks.clear + end + + private + + def mocks + @mocks ||= [] + end + end + end +end diff --git a/vendor/gems/rspec/lib/spec/mocks/spec_methods.rb b/vendor/gems/rspec/lib/spec/mocks/spec_methods.rb new file mode 100644 index 0000000000..d92a4cedd8 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/mocks/spec_methods.rb @@ -0,0 +1,38 @@ +module Spec + module Mocks + module ExampleMethods + include Spec::Mocks::ArgumentConstraintMatchers + + # Shortcut for creating an instance of Spec::Mocks::Mock. + # + # +name+ is used for failure reporting, so you should use the + # role that the mock is playing in the example. + # + # +stubs_and_options+ lets you assign options and stub values + # at the same time. The only option available is :null_object. + # Anything else is treated as a stub value. + # + # == Examples + # + # stub_thing = mock("thing", :a => "A") + # stub_thing.a == "A" => true + # + # stub_person = stub("thing", :name => "Joe", :email => "joe@domain.com") + # stub_person.name => "Joe" + # stub_person.email => "joe@domain.com" + def mock(name, stubs_and_options={}) + Spec::Mocks::Mock.new(name, stubs_and_options) + end + + alias :stub :mock + + # Shortcut for creating a mock object that will return itself in response + # to any message it receives that it hasn't been explicitly instructed + # to respond to. + def stub_everything(name = 'stub') + mock(name, :null_object => true) + end + + end + end +end diff --git a/vendor/gems/rspec/lib/spec/rake/spectask.rb b/vendor/gems/rspec/lib/spec/rake/spectask.rb new file mode 100644 index 0000000000..781c151a4c --- /dev/null +++ b/vendor/gems/rspec/lib/spec/rake/spectask.rb @@ -0,0 +1,235 @@ +#!/usr/bin/env ruby + +# Define a task library for running RSpec contexts. + +require 'rake' +require 'rake/tasklib' + +module Spec + module Rake + + # A Rake task that runs a set of specs. + # + # Example: + # + # Spec::Rake::SpecTask.new do |t| + # t.warning = true + # t.rcov = true + # end + # + # This will create a task that can be run with: + # + # rake spec + # + # If rake is invoked with a "SPEC=filename" command line option, + # then the list of spec files will be overridden to include only the + # filename specified on the command line. This provides an easy way + # to run just one spec. + # + # If rake is invoked with a "SPEC_OPTS=options" command line option, + # then the given options will override the value of the +spec_opts+ + # attribute. + # + # If rake is invoked with a "RCOV_OPTS=options" command line option, + # then the given options will override the value of the +rcov_opts+ + # attribute. + # + # Examples: + # + # rake spec # run specs normally + # rake spec SPEC=just_one_file.rb # run just one spec file. + # rake spec SPEC_OPTS="--diff" # enable diffing + # rake spec RCOV_OPTS="--aggregate myfile.txt" # see rcov --help for details + # + # Each attribute of this task may be a proc. This allows for lazy evaluation, + # which is sometimes handy if you want to defer the evaluation of an attribute value + # until the task is run (as opposed to when it is defined). + # + # This task can also be used to run existing Test::Unit tests and get RSpec + # output, for example like this: + # + # require 'rubygems' + # require 'spec/rake/spectask' + # Spec::Rake::SpecTask.new do |t| + # t.ruby_opts = ['-rtest/unit'] + # t.spec_files = FileList['test/**/*_test.rb'] + # end + # + class SpecTask < ::Rake::TaskLib + class << self + def attr_accessor(*names) + super(*names) + names.each do |name| + module_eval "def #{name}() evaluate(@#{name}) end" # Allows use of procs + end + end + end + + # Name of spec task. (default is :spec) + attr_accessor :name + + # Array of directories to be added to $LOAD_PATH before running the + # specs. Defaults to [''] + attr_accessor :libs + + # If true, requests that the specs be run with the warning flag set. + # E.g. warning=true implies "ruby -w" used to run the specs. Defaults to false. + attr_accessor :warning + + # Glob pattern to match spec files. (default is 'spec/**/*_spec.rb') + # Setting the SPEC environment variable overrides this. + attr_accessor :pattern + + # Array of commandline options to pass to RSpec. Defaults to []. + # Setting the SPEC_OPTS environment variable overrides this. + attr_accessor :spec_opts + + # Whether or not to use RCov (default is false) + # See http://eigenclass.org/hiki.rb?rcov + attr_accessor :rcov + + # Array of commandline options to pass to RCov. Defaults to ['--exclude', 'lib\/spec,bin\/spec']. + # Ignored if rcov=false + # Setting the RCOV_OPTS environment variable overrides this. + attr_accessor :rcov_opts + + # Directory where the RCov report is written. Defaults to "coverage" + # Ignored if rcov=false + attr_accessor :rcov_dir + + # Array of commandline options to pass to ruby. Defaults to []. + attr_accessor :ruby_opts + + # Whether or not to fail Rake when an error occurs (typically when specs fail). + # Defaults to true. + attr_accessor :fail_on_error + + # A message to print to stderr when there are failures. + attr_accessor :failure_message + + # Where RSpec's output is written. Defaults to STDOUT. + # DEPRECATED. Use --format FORMAT:WHERE in spec_opts. + attr_accessor :out + + # Explicitly define the list of spec files to be included in a + # spec. +spec_files+ is expected to be an array of file names (a + # FileList is acceptable). If both +pattern+ and +spec_files+ are + # used, then the list of spec files is the union of the two. + # Setting the SPEC environment variable overrides this. + attr_accessor :spec_files + + # Use verbose output. If this is set to true, the task will print + # the executed spec command to stdout. Defaults to false. + attr_accessor :verbose + + # Defines a new task, using the name +name+. + def initialize(name=:spec) + @name = name + @libs = [File.expand_path(File.dirname(__FILE__) + '/../../../lib')] + @pattern = nil + @spec_files = nil + @spec_opts = [] + @warning = false + @ruby_opts = [] + @fail_on_error = true + @rcov = false + @rcov_opts = ['--exclude', 'lib\/spec,bin\/spec,config\/boot.rb'] + @rcov_dir = "coverage" + + yield self if block_given? + @pattern = 'spec/**/*_spec.rb' if pattern.nil? && spec_files.nil? + define + end + + def define # :nodoc: + spec_script = File.expand_path(File.dirname(__FILE__) + '/../../../bin/spec') + + lib_path = libs.join(File::PATH_SEPARATOR) + actual_name = Hash === name ? name.keys.first : name + unless ::Rake.application.last_comment + desc "Run specs" + (rcov ? " using RCov" : "") + end + task name do + RakeFileUtils.verbose(verbose) do + unless spec_file_list.empty? + # ruby [ruby_opts] -Ilib -S rcov [rcov_opts] bin/spec -- examples [spec_opts] + # or + # ruby [ruby_opts] -Ilib bin/spec examples [spec_opts] + cmd = "ruby " + + rb_opts = ruby_opts.clone + rb_opts << "-I\"#{lib_path}\"" + rb_opts << "-S rcov" if rcov + rb_opts << "-w" if warning + cmd << rb_opts.join(" ") + cmd << " " + cmd << rcov_option_list + cmd << %[ -o "#{rcov_dir}" ] if rcov + #cmd << %Q|"#{spec_script}"| + cmd << " " + cmd << "-- " if rcov + cmd << spec_file_list.collect { |fn| %["#{fn}"] }.join(' ') + cmd << " " + cmd << spec_option_list + if out + cmd << " " + cmd << %Q| > "#{out}"| + STDERR.puts "The Spec::Rake::SpecTask#out attribute is DEPRECATED and will be removed in a future version. Use --format FORMAT:WHERE instead." + end + if verbose + puts cmd + end + unless system(cmd) + STDERR.puts failure_message if failure_message + raise("Command #{cmd} failed") if fail_on_error + end + end + end + end + + if rcov + desc "Remove rcov products for #{actual_name}" + task paste("clobber_", actual_name) do + rm_r rcov_dir rescue nil + end + + clobber_task = paste("clobber_", actual_name) + task :clobber => [clobber_task] + + task actual_name => clobber_task + end + self + end + + def rcov_option_list # :nodoc: + return "" unless rcov + ENV['RCOV_OPTS'] || rcov_opts.join(" ") || "" + end + + def spec_option_list # :nodoc: + STDERR.puts "RSPECOPTS is DEPRECATED and will be removed in a future version. Use SPEC_OPTS instead." if ENV['RSPECOPTS'] + ENV['SPEC_OPTS'] || ENV['RSPECOPTS'] || spec_opts.join(" ") || "" + end + + def evaluate(o) # :nodoc: + case o + when Proc then o.call + else o + end + end + + def spec_file_list # :nodoc: + if ENV['SPEC'] + FileList[ ENV['SPEC'] ] + else + result = [] + result += spec_files.to_a if spec_files + result += FileList[ pattern ].to_a if pattern + FileList[result] + end + end + + end + end +end + diff --git a/vendor/gems/rspec/lib/spec/rake/verify_rcov.rb b/vendor/gems/rspec/lib/spec/rake/verify_rcov.rb new file mode 100644 index 0000000000..3328f9e9a7 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/rake/verify_rcov.rb @@ -0,0 +1,52 @@ +module RCov + # A task that can verify that the RCov coverage doesn't + # drop below a certain threshold. It should be run after + # running Spec::Rake::SpecTask. + class VerifyTask < Rake::TaskLib + # Name of the task. Defaults to :verify_rcov + attr_accessor :name + + # Path to the index.html file generated by RCov, which + # is the file containing the total coverage. + # Defaults to 'coverage/index.html' + attr_accessor :index_html + + # Whether or not to output details. Defaults to true. + attr_accessor :verbose + + # The threshold value (in percent) for coverage. If the + # actual coverage is not equal to this value, the task will raise an + # exception. + attr_accessor :threshold + + # Require the threshold value be met exactly. This is the default. + attr_accessor :require_exact_threshold + + def initialize(name=:verify_rcov) + @name = name + @index_html = 'coverage/index.html' + @verbose = true + @require_exact_threshold = true + yield self if block_given? + raise "Threshold must be set" if @threshold.nil? + define + end + + def define + desc "Verify that rcov coverage is at least #{threshold}%" + task @name do + total_coverage = nil + + File.open(index_html).each_line do |line| + if line =~ /(\d+\.\d+)%<\/tt>/ + total_coverage = eval($1) + break + end + end + puts "Coverage: #{total_coverage}% (threshold: #{threshold}%)" if verbose + raise "Coverage must be at least #{threshold}% but was #{total_coverage}%" if total_coverage < threshold + raise "Coverage has increased above the threshold of #{threshold}% to #{total_coverage}%. You should update your threshold value." if (total_coverage > threshold) and require_exact_threshold + end + end + end +end diff --git a/vendor/gems/rspec/lib/spec/runner.rb b/vendor/gems/rspec/lib/spec/runner.rb new file mode 100644 index 0000000000..97ef95bd2a --- /dev/null +++ b/vendor/gems/rspec/lib/spec/runner.rb @@ -0,0 +1,202 @@ +require 'spec/runner/options' +require 'spec/runner/option_parser' +require 'spec/runner/example_group_runner' +require 'spec/runner/command_line' +require 'spec/runner/drb_command_line' +require 'spec/runner/backtrace_tweaker' +require 'spec/runner/reporter' +require 'spec/runner/spec_parser' +require 'spec/runner/class_and_arguments_parser' + +module Spec + # == ExampleGroups and Examples + # + # Rather than expressing examples in classes, RSpec uses a custom DSLL (DSL light) to + # describe groups of examples. + # + # A ExampleGroup is the equivalent of a fixture in xUnit-speak. It is a metaphor for the context + # in which you will run your executable example - a set of known objects in a known starting state. + # We begin be describing + # + # describe Account do + # + # before do + # @account = Account.new + # end + # + # it "should have a balance of $0" do + # @account.balance.should == Money.new(0, :dollars) + # end + # + # end + # + # We use the before block to set up the Example (given), and then the #it method to + # hold the example code that expresses the event (when) and the expected outcome (then). + # + # == Helper Methods + # + # A primary goal of RSpec is to keep the examples clear. We therefore prefer + # less indirection than you might see in xUnit examples and in well factored, DRY production code. We feel + # that duplication is OK if removing it makes it harder to understand an example without + # having to look elsewhere to understand its context. + # + # That said, RSpec does support some level of encapsulating common code in helper + # methods that can exist within a context or within an included module. + # + # == Setup and Teardown + # + # You can use before and after within a Example. Both methods take an optional + # scope argument so you can run the block before :each example or before :all examples + # + # describe "..." do + # before :all do + # ... + # end + # + # before :each do + # ... + # end + # + # it "should do something" do + # ... + # end + # + # it "should do something else" do + # ... + # end + # + # after :each do + # ... + # end + # + # after :all do + # ... + # end + # + # end + # + # The before :each block will run before each of the examples, once for each example. Likewise, + # the after :each block will run after each of the examples. + # + # It is also possible to specify a before :all and after :all + # block that will run only once for each behaviour, respectively before the first before :each + # and after the last after :each. The use of these is generally discouraged, because it + # introduces dependencies between the examples. Still, it might prove useful for very expensive operations + # if you know what you are doing. + # + # == Local helper methods + # + # You can include local helper methods by simply expressing them within a context: + # + # describe "..." do + # + # it "..." do + # helper_method + # end + # + # def helper_method + # ... + # end + # + # end + # + # == Included helper methods + # + # You can include helper methods in multiple contexts by expressing them within + # a module, and then including that module in your context: + # + # module AccountExampleHelperMethods + # def helper_method + # ... + # end + # end + # + # describe "A new account" do + # include AccountExampleHelperMethods + # before do + # @account = Account.new + # end + # + # it "should have a balance of $0" do + # helper_method + # @account.balance.should eql(Money.new(0, :dollars)) + # end + # end + # + # == Shared Example Groups + # + # You can define a shared Example Group, that may be used on other groups + # + # share_examples_for "All Editions" do + # it "all editions behaviour" ... + # end + # + # describe SmallEdition do + # it_should_behave_like "All Editions" + # + # it "should do small edition stuff" do + # ... + # end + # end + # + # You can also assign the shared group to a module and include that + # + # share_as :AllEditions do + # it "should do all editions stuff" ... + # end + # + # describe SmallEdition do + # it_should_behave_like AllEditions + # + # it "should do small edition stuff" do + # ... + # end + # end + # + # And, for those of you who prefer to use something more like Ruby, you + # can just include the module directly + # + # describe SmallEdition do + # include AllEditions + # + # it "should do small edition stuff" do + # ... + # end + # end + module Runner + class << self + def configuration # :nodoc: + @configuration ||= Spec::Example::Configuration.new + end + + # Use this to configure various configurable aspects of + # RSpec: + # + # Spec::Runner.configure do |configuration| + # # Configure RSpec here + # end + # + # The yielded configuration object is a + # Spec::Example::Configuration instance. See its RDoc + # for details about what you can do with it. + # + def configure + yield configuration + end + + def register_at_exit_hook # :nodoc: + $spec_runner_at_exit_hook_registered ||= nil + unless $spec_runner_at_exit_hook_registered + at_exit do + unless $! || Spec.run?; \ + success = Spec.run; \ + exit success if Spec.exit?; \ + end + end + $spec_runner_at_exit_hook_registered = true + end + end + + end + end +end diff --git a/vendor/gems/rspec/lib/spec/runner/backtrace_tweaker.rb b/vendor/gems/rspec/lib/spec/runner/backtrace_tweaker.rb new file mode 100644 index 0000000000..5fd2fb99fc --- /dev/null +++ b/vendor/gems/rspec/lib/spec/runner/backtrace_tweaker.rb @@ -0,0 +1,57 @@ +module Spec + module Runner + class BacktraceTweaker + def clean_up_double_slashes(line) + line.gsub!('//','/') + end + end + + class NoisyBacktraceTweaker < BacktraceTweaker + def tweak_backtrace(error) + return if error.backtrace.nil? + error.backtrace.each do |line| + clean_up_double_slashes(line) + end + end + end + + # Tweaks raised Exceptions to mask noisy (unneeded) parts of the backtrace + class QuietBacktraceTweaker < BacktraceTweaker + unless defined?(IGNORE_PATTERNS) + root_dir = File.expand_path(File.join(__FILE__, '..', '..', '..', '..')) + spec_files = Dir["#{root_dir}/lib/*"].map do |path| + subpath = path[root_dir.length..-1] + /#{subpath}/ + end + IGNORE_PATTERNS = spec_files + [ + /\/lib\/ruby\//, + /bin\/spec:/, + /bin\/rcov:/, + /lib\/rspec_on_rails/, + /vendor\/rails/, + # TextMate's Ruby and RSpec plugins + /Ruby\.tmbundle\/Support\/tmruby.rb:/, + /RSpec\.tmbundle\/Support\/lib/, + /temp_textmate\./, + /mock_frameworks\/rspec/, + /spec_server/ + ] + end + + def tweak_backtrace(error) + return if error.backtrace.nil? + error.backtrace.collect! do |line| + clean_up_double_slashes(line) + IGNORE_PATTERNS.each do |ignore| + if line =~ ignore + line = nil + break + end + end + line + end + error.backtrace.compact! + end + end + end +end diff --git a/vendor/gems/rspec/lib/spec/runner/class_and_arguments_parser.rb b/vendor/gems/rspec/lib/spec/runner/class_and_arguments_parser.rb new file mode 100644 index 0000000000..65dc4519c8 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/runner/class_and_arguments_parser.rb @@ -0,0 +1,16 @@ +module Spec + module Runner + class ClassAndArgumentsParser + class << self + def parse(s) + if s =~ /([a-zA-Z_]+(?:::[a-zA-Z_]+)*):?(.*)/ + arg = $2 == "" ? nil : $2 + [$1, arg] + else + raise "Couldn't parse #{s.inspect}" + end + end + end + end + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/lib/spec/runner/command_line.rb b/vendor/gems/rspec/lib/spec/runner/command_line.rb new file mode 100644 index 0000000000..9849c48532 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/runner/command_line.rb @@ -0,0 +1,28 @@ +require 'spec/runner/option_parser' + +module Spec + module Runner + # Facade to run specs without having to fork a new ruby process (using `spec ...`) + class CommandLine + class << self + # Runs specs. +argv+ is the commandline args as per the spec commandline API, +err+ + # and +out+ are the streams output will be written to. + def run(instance_rspec_options) + # NOTE - this call to init_rspec_options is not spec'd, but neither is any of this + # swapping of $rspec_options. That is all here to enable rspec to run against itself + # and maintain coverage in a single process. Therefore, DO NOT mess with this stuff + # unless you know what you are doing! + init_rspec_options(instance_rspec_options) + orig_rspec_options = rspec_options + begin + $rspec_options = instance_rspec_options + return $rspec_options.run_examples + ensure + ::Spec.run = true + $rspec_options = orig_rspec_options + end + end + end + end + end +end diff --git a/vendor/gems/rspec/lib/spec/runner/drb_command_line.rb b/vendor/gems/rspec/lib/spec/runner/drb_command_line.rb new file mode 100644 index 0000000000..6c340cfea9 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/runner/drb_command_line.rb @@ -0,0 +1,20 @@ +require "drb/drb" + +module Spec + module Runner + # Facade to run specs by connecting to a DRB server + class DrbCommandLine + # Runs specs on a DRB server. Note that this API is similar to that of + # CommandLine - making it possible for clients to use both interchangeably. + def self.run(options) + begin + DRb.start_service + spec_server = DRbObject.new_with_uri("druby://localhost:8989") + spec_server.run(options.argv, options.error_stream, options.output_stream) + rescue DRb::DRbConnError => e + options.error_stream.puts "No server is running" + end + end + end + end +end diff --git a/vendor/gems/rspec/lib/spec/runner/example_group_runner.rb b/vendor/gems/rspec/lib/spec/runner/example_group_runner.rb new file mode 100644 index 0000000000..7275c6a882 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/runner/example_group_runner.rb @@ -0,0 +1,59 @@ +module Spec + module Runner + class ExampleGroupRunner + def initialize(options) + @options = options + end + + def load_files(files) + # It's important that loading files (or choosing not to) stays the + # responsibility of the ExampleGroupRunner. Some implementations (like) + # the one using DRb may choose *not* to load files, but instead tell + # someone else to do it over the wire. + files.each do |file| + load file + end + end + + def run + prepare + success = true + example_groups.each do |example_group| + success = success & example_group.run + end + return success + ensure + finish + end + + protected + def prepare + reporter.start(number_of_examples) + example_groups.reverse! if reverse + end + + def finish + reporter.end + reporter.dump + end + + def reporter + @options.reporter + end + + def reverse + @options.reverse + end + + def example_groups + @options.example_groups + end + + def number_of_examples + @options.number_of_examples + end + end + # TODO: BT - Deprecate BehaviourRunner? + BehaviourRunner = ExampleGroupRunner + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/lib/spec/runner/formatter/base_formatter.rb b/vendor/gems/rspec/lib/spec/runner/formatter/base_formatter.rb new file mode 100644 index 0000000000..c8647cf509 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/runner/formatter/base_formatter.rb @@ -0,0 +1,78 @@ +module Spec + module Runner + module Formatter + # Baseclass for formatters that implements all required methods as no-ops. + class BaseFormatter + attr_accessor :example_group, :options, :where + def initialize(options, where) + @options = options + @where = where + end + + # This method is invoked before any examples are run, right after + # they have all been collected. This can be useful for special + # formatters that need to provide progress on feedback (graphical ones) + # + # This method will only be invoked once, and the next one to be invoked + # is #add_example_group + def start(example_count) + end + + # This method is invoked at the beginning of the execution of each example_group. + # +name+ is the name of the example_group and +first+ is true if it is the + # first example_group - otherwise it's false. + # + # The next method to be invoked after this is #example_failed or #example_finished + def add_example_group(example_group) + @example_group = example_group + end + + # This method is invoked when an +example+ starts. + def example_started(example) + end + + # This method is invoked when an +example+ passes. + def example_passed(example) + end + + # This method is invoked when an +example+ fails, i.e. an exception occurred + # inside it (such as a failed should or other exception). +counter+ is the + # sequence number of the failure (starting at 1) and +failure+ is the associated + # Failure object. + def example_failed(example, counter, failure) + end + + # This method is invoked when an example is not yet implemented (i.e. has not + # been provided a block), or when an ExamplePendingError is raised. + # +message+ is the message from the ExamplePendingError, if it exists, or the + # default value of "Not Yet Implemented" + def example_pending(example_group_description, example, message) + end + + # This method is invoked after all of the examples have executed. The next method + # to be invoked after this one is #dump_failure (once for each failed example), + def start_dump + end + + # Dumps detailed information about an example failure. + # This method is invoked for each failed example after all examples have run. +counter+ is the sequence number + # of the associated example. +failure+ is a Failure object, which contains detailed + # information about the failure. + def dump_failure(counter, failure) + end + + # This method is invoked after the dumping of examples and failures. + def dump_summary(duration, example_count, failure_count, pending_count) + end + + # This gets invoked after the summary if option is set to do so. + def dump_pending + end + + # This method is invoked at the very end. Allows the formatter to clean up, like closing open streams. + def close + end + end + end + end +end diff --git a/vendor/gems/rspec/lib/spec/runner/formatter/base_text_formatter.rb b/vendor/gems/rspec/lib/spec/runner/formatter/base_text_formatter.rb new file mode 100644 index 0000000000..859b2641d1 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/runner/formatter/base_text_formatter.rb @@ -0,0 +1,130 @@ +require 'spec/runner/formatter/base_formatter' + +module Spec + module Runner + module Formatter + # Baseclass for text-based formatters. Can in fact be used for + # non-text based ones too - just ignore the +output+ constructor + # argument. + class BaseTextFormatter < BaseFormatter + attr_reader :output, :pending_examples + # Creates a new instance that will write to +where+. If +where+ is a + # String, output will be written to the File with that name, otherwise + # +where+ is exected to be an IO (or an object that responds to #puts and #write). + def initialize(options, where) + super + if where.is_a?(String) + @output = File.open(where, 'w') + elsif where == STDOUT + @output = Kernel + def @output.flush + STDOUT.flush + end + else + @output = where + end + @pending_examples = [] + end + + def example_pending(example_group_description, example, message) + @pending_examples << ["#{example_group_description} #{example.description}", message] + end + + def dump_failure(counter, failure) + @output.puts + @output.puts "#{counter.to_s})" + @output.puts colourise("#{failure.header}\n#{failure.exception.message}", failure) + @output.puts format_backtrace(failure.exception.backtrace) + @output.flush + end + + def colourise(s, failure) + if(failure.expectation_not_met?) + red(s) + elsif(failure.pending_fixed?) + blue(s) + else + magenta(s) + end + end + + def dump_summary(duration, example_count, failure_count, pending_count) + return if dry_run? + @output.puts + @output.puts "Finished in #{duration} seconds" + @output.puts + + summary = "#{example_count} example#{'s' unless example_count == 1}, #{failure_count} failure#{'s' unless failure_count == 1}" + summary << ", #{pending_count} pending" if pending_count > 0 + + if failure_count == 0 + if pending_count > 0 + @output.puts yellow(summary) + else + @output.puts green(summary) + end + else + @output.puts red(summary) + end + @output.flush + end + + def dump_pending + unless @pending_examples.empty? + @output.puts + @output.puts "Pending:" + @pending_examples.each do |pending_example| + @output.puts "#{pending_example[0]} (#{pending_example[1]})" + end + end + @output.flush + end + + def close + if IO === @output + @output.close + end + end + + def format_backtrace(backtrace) + return "" if backtrace.nil? + backtrace.map { |line| backtrace_line(line) }.join("\n") + end + + protected + + def colour? + @options.colour ? true : false + end + + def dry_run? + @options.dry_run ? true : false + end + + def backtrace_line(line) + line.sub(/\A([^:]+:\d+)$/, '\\1:') + end + + def colour(text, colour_code) + return text unless colour? && output_to_tty? + "#{colour_code}#{text}\e[0m" + end + + def output_to_tty? + begin + @output == Kernel || @output.tty? + rescue NoMethodError + false + end + end + + def green(text); colour(text, "\e[32m"); end + def red(text); colour(text, "\e[31m"); end + def magenta(text); colour(text, "\e[35m"); end + def yellow(text); colour(text, "\e[33m"); end + def blue(text); colour(text, "\e[34m"); end + + end + end + end +end diff --git a/vendor/gems/rspec/lib/spec/runner/formatter/failing_example_groups_formatter.rb b/vendor/gems/rspec/lib/spec/runner/formatter/failing_example_groups_formatter.rb new file mode 100644 index 0000000000..5a46079833 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/runner/formatter/failing_example_groups_formatter.rb @@ -0,0 +1,31 @@ +require 'spec/runner/formatter/base_text_formatter' + +module Spec + module Runner + module Formatter + class FailingExampleGroupsFormatter < BaseTextFormatter + def add_example_group(example_group) + super + @example_group_description_parts = example_group.description_parts + end + + def example_failed(example, counter, failure) + if @example_group_description_parts + description_parts = @example_group_description_parts.collect do |description| + description =~ /(.*) \(druby.*\)$/ ? $1 : description + end + @output.puts ::Spec::Example::ExampleGroupMethods.description_text(*description_parts) + @output.flush + @example_group_description_parts = nil + end + end + + def dump_failure(counter, failure) + end + + def dump_summary(duration, example_count, failure_count, pending_count) + end + end + end + end +end diff --git a/vendor/gems/rspec/lib/spec/runner/formatter/failing_examples_formatter.rb b/vendor/gems/rspec/lib/spec/runner/formatter/failing_examples_formatter.rb new file mode 100644 index 0000000000..e3a271c8b9 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/runner/formatter/failing_examples_formatter.rb @@ -0,0 +1,20 @@ +require 'spec/runner/formatter/base_text_formatter' + +module Spec + module Runner + module Formatter + class FailingExamplesFormatter < BaseTextFormatter + def example_failed(example, counter, failure) + @output.puts "#{example_group.description} #{example.description}" + @output.flush + end + + def dump_failure(counter, failure) + end + + def dump_summary(duration, example_count, failure_count, pending_count) + end + end + end + end +end diff --git a/vendor/gems/rspec/lib/spec/runner/formatter/html_formatter.rb b/vendor/gems/rspec/lib/spec/runner/formatter/html_formatter.rb new file mode 100644 index 0000000000..ad153c8dc2 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/runner/formatter/html_formatter.rb @@ -0,0 +1,333 @@ +require 'erb' +require 'spec/runner/formatter/base_text_formatter' + +module Spec + module Runner + module Formatter + class HtmlFormatter < BaseTextFormatter + include ERB::Util # for the #h method + + def initialize(options, output) + super + @current_example_group_number = 0 + @current_example_number = 0 + end + + # The number of the currently running example_group + def current_example_group_number + @current_example_group_number + end + + # The number of the currently running example (a global counter) + def current_example_number + @current_example_number + end + + def start(example_count) + @example_count = example_count + + @output.puts html_header + @output.puts report_header + @output.flush + end + + def add_example_group(example_group) + super + @example_group_red = false + @example_group_red = false + @current_example_group_number += 1 + unless current_example_group_number == 1 + @output.puts " " + @output.puts "" + end + @output.puts "
" + @output.puts "
" + @output.puts "
#{h(example_group.description)}
" + @output.flush + end + + def start_dump + @output.puts "
" + @output.puts "
" + @output.flush + end + + def example_started(example) + @current_example_number += 1 + end + + def example_passed(example) + move_progress + @output.puts "
#{h(example.description)}
" + @output.flush + end + + def example_failed(example, counter, failure) + extra = extra_failure_content(failure) + failure_style = failure.pending_fixed? ? 'pending_fixed' : 'failed' + @output.puts " " unless @header_red + @header_red = true + @output.puts " " unless @example_group_red + @example_group_red = true + move_progress + @output.puts "
" + @output.puts " #{h(example.description)}" + @output.puts "
" + @output.puts "
#{h(failure.exception.message)}
" unless failure.exception.nil? + @output.puts "
#{format_backtrace(failure.exception.backtrace)}
" unless failure.exception.nil? + @output.puts extra unless extra == "" + @output.puts "
" + @output.puts "
" + @output.flush + end + + def example_pending(example_group_description, example, message) + @output.puts " " unless @header_red + @output.puts " " unless @example_group_red + move_progress + @output.puts "
#{h(example.description)} (PENDING: #{h(message)})
" + @output.flush + end + + # Override this method if you wish to output extra HTML for a failed spec. For example, you + # could output links to images or other files produced during the specs. + # + def extra_failure_content(failure) + require 'spec/runner/formatter/snippet_extractor' + @snippet_extractor ||= SnippetExtractor.new + "
#{@snippet_extractor.snippet(failure.exception)}
" + end + + def move_progress + @output.puts " " + @output.flush + end + + def percent_done + result = 100.0 + if @example_count != 0 + result = ((current_example_number).to_f / @example_count.to_f * 1000).to_i / 10.0 + end + result + end + + def dump_failure(counter, failure) + end + + def dump_summary(duration, example_count, failure_count, pending_count) + if dry_run? + totals = "This was a dry-run" + else + totals = "#{example_count} example#{'s' unless example_count == 1}, #{failure_count} failure#{'s' unless failure_count == 1}" + totals << ", #{pending_count} pending" if pending_count > 0 + end + @output.puts "" + @output.puts "" + @output.puts "" + @output.puts "" + @output.puts "" + @output.puts "" + @output.flush + end + + def html_header + <<-EOF + + + + + RSpec results + + + + + + +EOF + end + + def report_header + <<-EOF +
+ + + +
+

RSpec Results

+ +
+

 

+

 

+
+
+ +
+EOF + end + + def global_scripts + <<-EOF +function moveProgressBar(percentDone) { + document.getElementById("rspec-header").style.width = percentDone +"%"; +} +function makeRed(element_id) { + document.getElementById(element_id).style.background = '#C40D0D'; + document.getElementById(element_id).style.color = '#FFFFFF'; +} + +function makeYellow(element_id) { + if (element_id == "rspec-header" && document.getElementById(element_id).style.background != '#C40D0D') + { + document.getElementById(element_id).style.background = '#FAF834'; + document.getElementById(element_id).style.color = '#000000'; + } + else + { + document.getElementById(element_id).style.background = '#FAF834'; + document.getElementById(element_id).style.color = '#000000'; + } +} +EOF + end + + def global_styles + <<-EOF +#rspec-header { + background: #65C400; color: #fff; +} + +.rspec-report h1 { + margin: 0px 10px 0px 10px; + padding: 10px; + font-family: "Lucida Grande", Helvetica, sans-serif; + font-size: 1.8em; +} + +#summary { + margin: 0; padding: 5px 10px; + font-family: "Lucida Grande", Helvetica, sans-serif; + text-align: right; + position: absolute; + top: 0px; + right: 0px; +} + +#summary p { + margin: 0 0 0 2px; +} + +#summary #totals { + font-size: 1.2em; +} + +.example_group { + margin: 0 10px 5px; + background: #fff; +} + +dl { + margin: 0; padding: 0 0 5px; + font: normal 11px "Lucida Grande", Helvetica, sans-serif; +} + +dt { + padding: 3px; + background: #65C400; + color: #fff; + font-weight: bold; +} + +dd { + margin: 5px 0 5px 5px; + padding: 3px 3px 3px 18px; +} + +dd.spec.passed { + border-left: 5px solid #65C400; + border-bottom: 1px solid #65C400; + background: #DBFFB4; color: #3D7700; +} + +dd.spec.failed { + border-left: 5px solid #C20000; + border-bottom: 1px solid #C20000; + color: #C20000; background: #FFFBD3; +} + +dd.spec.not_implemented { + border-left: 5px solid #FAF834; + border-bottom: 1px solid #FAF834; + background: #FCFB98; color: #131313; +} + +dd.spec.pending_fixed { + border-left: 5px solid #0000C2; + border-bottom: 1px solid #0000C2; + color: #0000C2; background: #D3FBFF; +} + +.backtrace { + color: #000; + font-size: 12px; +} + +a { + color: #BE5C00; +} + +/* Ruby code, style similar to vibrant ink */ +.ruby { + font-size: 12px; + font-family: monospace; + color: white; + background-color: black; + padding: 0.1em 0 0.2em 0; +} + +.ruby .keyword { color: #FF6600; } +.ruby .constant { color: #339999; } +.ruby .attribute { color: white; } +.ruby .global { color: white; } +.ruby .module { color: white; } +.ruby .class { color: white; } +.ruby .string { color: #66FF00; } +.ruby .ident { color: white; } +.ruby .method { color: #FFCC00; } +.ruby .number { color: white; } +.ruby .char { color: white; } +.ruby .comment { color: #9933CC; } +.ruby .symbol { color: white; } +.ruby .regex { color: #44B4CC; } +.ruby .punct { color: white; } +.ruby .escape { color: white; } +.ruby .interp { color: white; } +.ruby .expr { color: white; } + +.ruby .offending { background-color: gray; } +.ruby .linenum { + width: 75px; + padding: 0.1em 1em 0.2em 0; + color: #000000; + background-color: #FFFBD3; +} +EOF + end + end + end + end +end diff --git a/vendor/gems/rspec/lib/spec/runner/formatter/profile_formatter.rb b/vendor/gems/rspec/lib/spec/runner/formatter/profile_formatter.rb new file mode 100644 index 0000000000..3784f3ac77 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/runner/formatter/profile_formatter.rb @@ -0,0 +1,47 @@ +require 'spec/runner/formatter/progress_bar_formatter' + +module Spec + module Runner + module Formatter + class ProfileFormatter < ProgressBarFormatter + + def initialize(options, where) + super + @example_times = [] + end + + def start(count) + @output.puts "Profiling enabled." + end + + def example_started(example) + @time = Time.now + end + + def example_passed(example) + super + @example_times << [ + example_group.description, + example.description, + Time.now - @time + ] + end + + def start_dump + super + @output.puts "\n\nTop 10 slowest examples:\n" + + @example_times = @example_times.sort_by do |description, example, time| + time + end.reverse + + @example_times[0..9].each do |description, example, time| + @output.print red(sprintf("%.7f", time)) + @output.puts " #{description} #{example}" + end + @output.flush + end + end + end + end +end diff --git a/vendor/gems/rspec/lib/spec/runner/formatter/progress_bar_formatter.rb b/vendor/gems/rspec/lib/spec/runner/formatter/progress_bar_formatter.rb new file mode 100644 index 0000000000..8d0e504327 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/runner/formatter/progress_bar_formatter.rb @@ -0,0 +1,30 @@ +require 'spec/runner/formatter/base_text_formatter' + +module Spec + module Runner + module Formatter + class ProgressBarFormatter < BaseTextFormatter + def example_failed(example, counter, failure) + @output.print colourise('F', failure) + @output.flush + end + + def example_passed(example) + @output.print green('.') + @output.flush + end + + def example_pending(example_group_description, example, message) + super + @output.print yellow('P') + @output.flush + end + + def start_dump + @output.puts + @output.flush + end + end + end + end +end diff --git a/vendor/gems/rspec/lib/spec/runner/formatter/snippet_extractor.rb b/vendor/gems/rspec/lib/spec/runner/formatter/snippet_extractor.rb new file mode 100644 index 0000000000..41119fe463 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/runner/formatter/snippet_extractor.rb @@ -0,0 +1,52 @@ +module Spec + module Runner + module Formatter + # This class extracts code snippets by looking at the backtrace of the passed error + class SnippetExtractor #:nodoc: + class NullConverter; def convert(code, pre); code; end; end #:nodoc: + begin; require 'rubygems'; require 'syntax/convertors/html'; @@converter = Syntax::Convertors::HTML.for_syntax "ruby"; rescue LoadError => e; @@converter = NullConverter.new; end + + def snippet(error) + raw_code, line = snippet_for(error.backtrace[0]) + highlighted = @@converter.convert(raw_code, false) + highlighted << "\n# gem install syntax to get syntax highlighting" if @@converter.is_a?(NullConverter) + post_process(highlighted, line) + end + + def snippet_for(error_line) + if error_line =~ /(.*):(\d+)/ + file = $1 + line = $2.to_i + [lines_around(file, line), line] + else + ["# Couldn't get snippet for #{error_line}", 1] + end + end + + def lines_around(file, line) + if File.file?(file) + lines = File.open(file).read.split("\n") + min = [0, line-3].max + max = [line+1, lines.length-1].min + selected_lines = [] + selected_lines.join("\n") + lines[min..max].join("\n") + else + "# Couldn't get snippet for #{file}" + end + end + + def post_process(highlighted, offending_line) + new_lines = [] + highlighted.split("\n").each_with_index do |line, i| + new_line = "#{offending_line+i-2}#{line}" + new_line = "#{new_line}" if i == 2 + new_lines << new_line + end + new_lines.join("\n") + end + + end + end + end +end diff --git a/vendor/gems/rspec/lib/spec/runner/formatter/specdoc_formatter.rb b/vendor/gems/rspec/lib/spec/runner/formatter/specdoc_formatter.rb new file mode 100644 index 0000000000..f426dc9487 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/runner/formatter/specdoc_formatter.rb @@ -0,0 +1,39 @@ +require 'spec/runner/formatter/base_text_formatter' + +module Spec + module Runner + module Formatter + class SpecdocFormatter < BaseTextFormatter + def add_example_group(example_group) + super + output.puts + output.puts example_group.description + output.flush + end + + def example_failed(example, counter, failure) + message = if failure.expectation_not_met? + "- #{example.description} (FAILED - #{counter})" + else + "- #{example.description} (ERROR - #{counter})" + end + + output.puts(failure.expectation_not_met? ? red(message) : magenta(message)) + output.flush + end + + def example_passed(example) + message = "- #{example.description}" + output.puts green(message) + output.flush + end + + def example_pending(example_group_description, example, message) + super + output.puts yellow("- #{example.description} (PENDING: #{message})") + output.flush + end + end + end + end +end diff --git a/vendor/gems/rspec/lib/spec/runner/formatter/story/html_formatter.rb b/vendor/gems/rspec/lib/spec/runner/formatter/story/html_formatter.rb new file mode 100644 index 0000000000..5a81346837 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/runner/formatter/story/html_formatter.rb @@ -0,0 +1,128 @@ +require 'erb' +require 'spec/runner/formatter/base_text_formatter' + +module Spec + module Runner + module Formatter + module Story + class HtmlFormatter < BaseTextFormatter + include ERB::Util + + def run_started(count) + @output.puts <<-EOF + + + + + Stories + + + + + + + + + +
+EOF + end + + def collected_steps(steps) + unless steps.empty? + @output.puts "
    " + steps.each do |step| + @output.puts "
  • #{step}
  • " + end + @output.puts "
" + end + end + + def run_ended + @output.puts <<-EOF +
+ + +EOF + end + + def story_started(title, narrative) + @output.puts <<-EOF +
+
Story: #{h title}
+
+

+ #{h(narrative).split("\n").join("
")} +

+EOF + end + + def story_ended(title, narrative) + @output.puts <<-EOF +
+
+EOF + end + + def scenario_started(story_title, scenario_name) + @output.puts <<-EOF +
+
Scenario: #{h scenario_name}
+
+
    +EOF + end + + def scenario_ended + @output.puts <<-EOF +
+
+
+EOF + end + + def found_scenario(type, description) + end + + def scenario_succeeded(story_title, scenario_name) + scenario_ended + end + + def scenario_pending(story_title, scenario_name, reason) + scenario_ended + end + + def scenario_failed(story_title, scenario_name, err) + scenario_ended + end + + def step_upcoming(type, description, *args) + end + + def step_succeeded(type, description, *args) + print_step('passed', type, description, *args) # TODO: uses succeeded CSS class + end + + def step_pending(type, description, *args) + print_step('pending', type, description, *args) + end + + def step_failed(type, description, *args) + print_step('failed', type, description, *args) + end + + def print_step(klass, type, description, *args) + spans = args.map { |arg| "#{arg}" } + desc_string = description.step_name + arg_regexp = description.arg_regexp + i = -1 + inner = type.to_s.capitalize + ' ' + desc_string.gsub(arg_regexp) { |param| spans[i+=1] } + @output.puts "
  • #{inner}
  • " + end + end + end + end + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/lib/spec/runner/formatter/story/plain_text_formatter.rb b/vendor/gems/rspec/lib/spec/runner/formatter/story/plain_text_formatter.rb new file mode 100644 index 0000000000..ad7a2fa734 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/runner/formatter/story/plain_text_formatter.rb @@ -0,0 +1,131 @@ +require 'spec/runner/formatter/base_text_formatter' + +module Spec + module Runner + module Formatter + module Story + class PlainTextFormatter < BaseTextFormatter + def initialize(options, where) + super + @successful_scenario_count = 0 + @pending_scenario_count = 0 + @failed_scenarios = [] + @pending_steps = [] + @previous_type = nil + end + + def run_started(count) + @count = count + @output.puts "Running #@count scenarios\n\n" + end + + def story_started(title, narrative) + @current_story_title = title + @output.puts "Story: #{title}\n\n" + narrative.each_line do |line| + @output.print " " + @output.print line + end + end + + def story_ended(title, narrative) + @output.puts + @output.puts + end + + def scenario_started(story_title, scenario_name) + @current_scenario_name = scenario_name + @scenario_already_failed = false + @output.print "\n\n Scenario: #{scenario_name}" + @scenario_ok = true + end + + def scenario_succeeded(story_title, scenario_name) + @successful_scenario_count += 1 + end + + def scenario_failed(story_title, scenario_name, err) + @options.backtrace_tweaker.tweak_backtrace(err) + @failed_scenarios << [story_title, scenario_name, err] unless @scenario_already_failed + @scenario_already_failed = true + end + + def scenario_pending(story_title, scenario_name, msg) + @pending_scenario_count += 1 unless @scenario_already_failed + @scenario_already_failed = true + end + + def run_ended + @output.puts "#@count scenarios: #@successful_scenario_count succeeded, #{@failed_scenarios.size} failed, #@pending_scenario_count pending" + unless @pending_steps.empty? + @output.puts "\nPending Steps:" + @pending_steps.each_with_index do |pending, i| + story_name, scenario_name, msg = pending + @output.puts "#{i+1}) #{story_name} (#{scenario_name}): #{msg}" + end + end + unless @failed_scenarios.empty? + @output.print "\nFAILURES:" + @failed_scenarios.each_with_index do |failure, i| + title, scenario_name, err = failure + @output.print %[ + #{i+1}) #{title} (#{scenario_name}) FAILED + #{err.class}: #{err.message} + #{err.backtrace.join("\n")} +] + end + end + end + + def step_upcoming(type, description, *args) + end + + def step_succeeded(type, description, *args) + found_step(type, description, false, *args) + end + + def step_pending(type, description, *args) + found_step(type, description, false, *args) + @pending_steps << [@current_story_title, @current_scenario_name, description] + @output.print " (PENDING)" + @scenario_ok = false + end + + def step_failed(type, description, *args) + found_step(type, description, true, *args) + @output.print red(@scenario_ok ? " (FAILED)" : " (SKIPPED)") + @scenario_ok = false + end + + def collected_steps(steps) + end + + def method_missing(sym, *args, &block) #:nodoc: + # noop - ignore unknown messages + end + + private + + def found_step(type, description, failed, *args) + desc_string = description.step_name + arg_regexp = description.arg_regexp + text = if(type == @previous_type) + "\n And " + else + "\n\n #{type.to_s.capitalize} " + end + i = -1 + text << desc_string.gsub(arg_regexp) { |param| args[i+=1] } + @output.print(failed ? red(text) : green(text)) + + if type == :'given scenario' + @previous_type = :given + else + @previous_type = type + end + end + end + end + end + end +end diff --git a/vendor/gems/rspec/lib/spec/runner/formatter/text_mate_formatter.rb b/vendor/gems/rspec/lib/spec/runner/formatter/text_mate_formatter.rb new file mode 100644 index 0000000000..4c0a9c7de1 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/runner/formatter/text_mate_formatter.rb @@ -0,0 +1,16 @@ +require 'spec/runner/formatter/html_formatter' + +module Spec + module Runner + module Formatter + # Formats backtraces so they're clickable by TextMate + class TextMateFormatter < HtmlFormatter + def backtrace_line(line) + line.gsub(/([^:]*\.rb):(\d*)/) do + "#{$1}:#{$2} " + end + end + end + end + end +end diff --git a/vendor/gems/rspec/lib/spec/runner/heckle_runner.rb b/vendor/gems/rspec/lib/spec/runner/heckle_runner.rb new file mode 100644 index 0000000000..7695fe7946 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/runner/heckle_runner.rb @@ -0,0 +1,72 @@ +begin + require 'rubygems' + require 'heckle' +rescue LoadError ; raise "You must gem install heckle to use --heckle" ; end + +module Spec + module Runner + # Creates a new Heckler configured to heckle all methods in the classes + # whose name matches +filter+ + class HeckleRunner + def initialize(filter, heckle_class=Heckler) + @filter = filter + @heckle_class = heckle_class + end + + # Runs all the example groups held by +rspec_options+ once for each of the + # methods in the matched classes. + def heckle_with + if @filter =~ /(.*)[#\.](.*)/ + heckle_method($1, $2) + else + heckle_class_or_module(@filter) + end + end + + def heckle_method(class_name, method_name) + verify_constant(class_name) + heckle = @heckle_class.new(class_name, method_name, rspec_options) + heckle.validate + end + + def heckle_class_or_module(class_or_module_name) + verify_constant(class_or_module_name) + pattern = /^#{class_or_module_name}/ + classes = [] + ObjectSpace.each_object(Class) do |klass| + classes << klass if klass.name =~ pattern + end + + classes.each do |klass| + klass.instance_methods(false).each do |method_name| + heckle = @heckle_class.new(klass.name, method_name, rspec_options) + heckle.validate + end + end + end + + def verify_constant(name) + begin + # This is defined in Heckle + name.to_class + rescue + raise "Heckling failed - \"#{name}\" is not a known class or module" + end + end + end + + #Supports Heckle 1.2 and prior (earlier versions used Heckle::Base) + class Heckler < (Heckle.const_defined?(:Base) ? Heckle::Base : Heckle) + def initialize(klass_name, method_name, rspec_options) + super(klass_name, method_name) + @rspec_options = rspec_options + end + + def tests_pass? + success = @rspec_options.run_examples + success + end + + end + end +end diff --git a/vendor/gems/rspec/lib/spec/runner/heckle_runner_unsupported.rb b/vendor/gems/rspec/lib/spec/runner/heckle_runner_unsupported.rb new file mode 100644 index 0000000000..02aa379537 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/runner/heckle_runner_unsupported.rb @@ -0,0 +1,10 @@ +module Spec + module Runner + # Dummy implementation for Windows that just fails (Heckle is not supported on Windows) + class HeckleRunner + def initialize(filter) + raise "Heckle not supported on Windows" + end + end + end +end diff --git a/vendor/gems/rspec/lib/spec/runner/option_parser.rb b/vendor/gems/rspec/lib/spec/runner/option_parser.rb new file mode 100644 index 0000000000..4bc84c8126 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/runner/option_parser.rb @@ -0,0 +1,201 @@ +require 'optparse' +require 'stringio' + +module Spec + module Runner + class OptionParser < ::OptionParser + class << self + def parse(args, err, out) + parser = new(err, out) + parser.parse(args) + parser.options + end + end + + attr_reader :options + + OPTIONS = { + :pattern => ["-p", "--pattern [PATTERN]","Limit files loaded to those matching this pattern. Defaults to '**/*_spec.rb'", + "Separate multiple patterns with commas.", + "Applies only to directories named on the command line (files", + "named explicitly on the command line will be loaded regardless)."], + :diff => ["-D", "--diff [FORMAT]", "Show diff of objects that are expected to be equal when they are not", + "Builtin formats: unified|u|context|c", + "You can also specify a custom differ class", + "(in which case you should also specify --require)"], + :colour => ["-c", "--colour", "--color", "Show coloured (red/green) output"], + :example => ["-e", "--example [NAME|FILE_NAME]", "Execute example(s) with matching name(s). If the argument is", + "the path to an existing file (typically generated by a previous", + "run using --format failing_examples:file.txt), then the examples", + "on each line of thatfile will be executed. If the file is empty,", + "all examples will be run (as if --example was not specified).", + " ", + "If the argument is not an existing file, then it is treated as", + "an example name directly, causing RSpec to run just the example", + "matching that name"], + :specification => ["-s", "--specification [NAME]", "DEPRECATED - use -e instead", "(This will be removed when autotest works with -e)"], + :line => ["-l", "--line LINE_NUMBER", Integer, "Execute behaviout or specification at given line.", + "(does not work for dynamically generated specs)"], + :format => ["-f", "--format FORMAT[:WHERE]", "Specifies what format to use for output. Specify WHERE to tell", + "the formatter where to write the output. All built-in formats", + "expect WHERE to be a file name, and will write to STDOUT if it's", + "not specified. The --format option may be specified several times", + "if you want several outputs", + " ", + "Builtin formats for examples: ", + "progress|p : Text progress", + "profile|o : Text progress with profiling of 10 slowest examples", + "specdoc|s : Example doc as text", + "html|h : A nice HTML report", + "failing_examples|e : Write all failing examples - input for --example", + "failing_example_groups|g : Write all failing example groups - input for --example", + " ", + "Builtin formats for stories: ", + "plain|p : Plain Text", + "html|h : A nice HTML report", + " ", + "FORMAT can also be the name of a custom formatter class", + "(in which case you should also specify --require to load it)"], + :require => ["-r", "--require FILE", "Require FILE before running specs", + "Useful for loading custom formatters or other extensions.", + "If this option is used it must come before the others"], + :backtrace => ["-b", "--backtrace", "Output full backtrace"], + :loadby => ["-L", "--loadby STRATEGY", "Specify the strategy by which spec files should be loaded.", + "STRATEGY can currently only be 'mtime' (File modification time)", + "By default, spec files are loaded in alphabetical order if --loadby", + "is not specified."], + :reverse => ["-R", "--reverse", "Run examples in reverse order"], + :timeout => ["-t", "--timeout FLOAT", "Interrupt and fail each example that doesn't complete in the", + "specified time"], + :heckle => ["-H", "--heckle CODE", "If all examples pass, this will mutate the classes and methods", + "identified by CODE little by little and run all the examples again", + "for each mutation. The intent is that for each mutation, at least", + "one example *should* fail, and RSpec will tell you if this is not the", + "case. CODE should be either Some::Module, Some::Class or", + "Some::Fabulous#method}"], + :dry_run => ["-d", "--dry-run", "Invokes formatters without executing the examples."], + :options_file => ["-O", "--options PATH", "Read options from a file"], + :generate_options => ["-G", "--generate-options PATH", "Generate an options file for --options"], + :runner => ["-U", "--runner RUNNER", "Use a custom Runner."], + :drb => ["-X", "--drb", "Run examples via DRb. (For example against script/spec_server)"], + :version => ["-v", "--version", "Show version"], + :help => ["-h", "--help", "You're looking at it"] + } + + def initialize(err, out) + super() + @error_stream = err + @out_stream = out + @options = Options.new(@error_stream, @out_stream) + + @file_factory = File + + self.banner = "Usage: spec (FILE|DIRECTORY|GLOB)+ [options]" + self.separator "" + on(*OPTIONS[:pattern]) {|pattern| @options.filename_pattern = pattern} + on(*OPTIONS[:diff]) {|diff| @options.parse_diff(diff)} + on(*OPTIONS[:colour]) {@options.colour = true} + on(*OPTIONS[:example]) {|example| @options.parse_example(example)} + on(*OPTIONS[:specification]) {|example| @options.parse_example(example)} + on(*OPTIONS[:line]) {|line_number| @options.line_number = line_number.to_i} + on(*OPTIONS[:format]) {|format| @options.parse_format(format)} + on(*OPTIONS[:require]) {|requires| invoke_requires(requires)} + on(*OPTIONS[:backtrace]) {@options.backtrace_tweaker = NoisyBacktraceTweaker.new} + on(*OPTIONS[:loadby]) {|loadby| @options.loadby = loadby} + on(*OPTIONS[:reverse]) {@options.reverse = true} + on(*OPTIONS[:timeout]) {|timeout| @options.timeout = timeout.to_f} + on(*OPTIONS[:heckle]) {|heckle| @options.load_heckle_runner(heckle)} + on(*OPTIONS[:dry_run]) {@options.dry_run = true} + on(*OPTIONS[:options_file]) {|options_file| parse_options_file(options_file)} + on(*OPTIONS[:generate_options]) do |options_file| + end + on(*OPTIONS[:runner]) do |runner| + @options.user_input_for_runner = runner + end + on(*OPTIONS[:drb]) {} + on(*OPTIONS[:version]) {parse_version} + on_tail(*OPTIONS[:help]) {parse_help} + end + + def order!(argv, &blk) + @argv = argv + @options.argv = @argv.dup + return if parse_generate_options + return if parse_drb + + super(@argv) do |file| + @options.files << file + blk.call(file) if blk + end + + @options + end + + protected + def invoke_requires(requires) + requires.split(",").each do |file| + require file + end + end + + def parse_options_file(options_file) + option_file_args = IO.readlines(options_file).map {|l| l.chomp.split " "}.flatten + @argv.push(*option_file_args) + end + + def parse_generate_options + # Remove the --generate-options option and the argument before writing to file + options_file = nil + ['-G', '--generate-options'].each do |option| + if index = @argv.index(option) + @argv.delete_at(index) + options_file = @argv.delete_at(index) + end + end + + if options_file + write_generated_options(options_file) + return true + else + return false + end + end + + def write_generated_options(options_file) + File.open(options_file, 'w') do |io| + io.puts @argv.join("\n") + end + @out_stream.puts "\nOptions written to #{options_file}. You can now use these options with:" + @out_stream.puts "spec --options #{options_file}" + @options.examples_should_not_be_run + end + + def parse_drb + is_drb = false + argv = @options.argv + is_drb ||= argv.delete(OPTIONS[:drb][0]) + is_drb ||= argv.delete(OPTIONS[:drb][1]) + return nil unless is_drb + @options.examples_should_not_be_run + DrbCommandLine.run( + self.class.parse(argv, @error_stream, @out_stream) + ) + true + end + + def parse_version + @out_stream.puts ::Spec::VERSION::DESCRIPTION + exit if stdout? + end + + def parse_help + @out_stream.puts self + exit if stdout? + end + + def stdout? + @out_stream == $stdout + end + end + end +end diff --git a/vendor/gems/rspec/lib/spec/runner/options.rb b/vendor/gems/rspec/lib/spec/runner/options.rb new file mode 100644 index 0000000000..108749c424 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/runner/options.rb @@ -0,0 +1,286 @@ +module Spec + module Runner + class Options + FILE_SORTERS = { + 'mtime' => lambda {|file_a, file_b| File.mtime(file_b) <=> File.mtime(file_a)} + } + + EXAMPLE_FORMATTERS = { # Load these lazily for better speed + 'specdoc' => ['spec/runner/formatter/specdoc_formatter', 'Formatter::SpecdocFormatter'], + 's' => ['spec/runner/formatter/specdoc_formatter', 'Formatter::SpecdocFormatter'], + 'html' => ['spec/runner/formatter/html_formatter', 'Formatter::HtmlFormatter'], + 'h' => ['spec/runner/formatter/html_formatter', 'Formatter::HtmlFormatter'], + 'progress' => ['spec/runner/formatter/progress_bar_formatter', 'Formatter::ProgressBarFormatter'], + 'p' => ['spec/runner/formatter/progress_bar_formatter', 'Formatter::ProgressBarFormatter'], + 'failing_examples' => ['spec/runner/formatter/failing_examples_formatter', 'Formatter::FailingExamplesFormatter'], + 'e' => ['spec/runner/formatter/failing_examples_formatter', 'Formatter::FailingExamplesFormatter'], +'failing_example_groups' => ['spec/runner/formatter/failing_example_groups_formatter', 'Formatter::FailingExampleGroupsFormatter'], + 'g' => ['spec/runner/formatter/failing_example_groups_formatter', 'Formatter::FailingExampleGroupsFormatter'], + 'profile' => ['spec/runner/formatter/profile_formatter', 'Formatter::ProfileFormatter'], + 'o' => ['spec/runner/formatter/profile_formatter', 'Formatter::ProfileFormatter'], + 'textmate' => ['spec/runner/formatter/text_mate_formatter', 'Formatter::TextMateFormatter'] + } + + STORY_FORMATTERS = { + 'plain' => ['spec/runner/formatter/story/plain_text_formatter', 'Formatter::Story::PlainTextFormatter'], + 'p' => ['spec/runner/formatter/story/plain_text_formatter', 'Formatter::Story::PlainTextFormatter'], + 'html' => ['spec/runner/formatter/story/html_formatter', 'Formatter::Story::HtmlFormatter'], + 'h' => ['spec/runner/formatter/story/html_formatter', 'Formatter::Story::HtmlFormatter'] + } + + attr_accessor( + :filename_pattern, + :backtrace_tweaker, + :context_lines, + :diff_format, + :dry_run, + :profile, + :examples, + :heckle_runner, + :line_number, + :loadby, + :reporter, + :reverse, + :timeout, + :verbose, + :user_input_for_runner, + :error_stream, + :output_stream, + # TODO: BT - Figure out a better name + :argv + ) + attr_reader :colour, :differ_class, :files, :example_groups + + def initialize(error_stream, output_stream) + @error_stream = error_stream + @output_stream = output_stream + @filename_pattern = "**/*_spec.rb" + @backtrace_tweaker = QuietBacktraceTweaker.new + @examples = [] + @colour = false + @profile = false + @dry_run = false + @reporter = Reporter.new(self) + @context_lines = 3 + @diff_format = :unified + @files = [] + @example_groups = [] + @examples_run = false + @examples_should_be_run = nil + @user_input_for_runner = nil + end + + def add_example_group(example_group) + @example_groups << example_group + end + + def remove_example_group(example_group) + @example_groups.delete(example_group) + end + + def run_examples + return true unless examples_should_be_run? + runner = custom_runner || ExampleGroupRunner.new(self) + + runner.load_files(files_to_load) + if example_groups.empty? + true + else + set_spec_from_line_number if line_number + success = runner.run + @examples_run = true + heckle if heckle_runner + success + end + end + + def examples_run? + @examples_run + end + + def examples_should_not_be_run + @examples_should_be_run = false + end + + def colour=(colour) + @colour = colour + if @colour && RUBY_PLATFORM =~ /win32/ ;\ + begin ;\ + require 'rubygems' ;\ + require 'Win32/Console/ANSI' ;\ + rescue LoadError ;\ + warn "You must 'gem install win32console' to use colour on Windows" ;\ + @colour = false ;\ + end + end + end + + def parse_diff(format) + case format + when :context, 'context', 'c' + @diff_format = :context + default_differ + when :unified, 'unified', 'u', '', nil + @diff_format = :unified + default_differ + else + @diff_format = :custom + self.differ_class = load_class(format, 'differ', '--diff') + end + end + + def parse_example(example) + if(File.file?(example)) + @examples = File.open(example).read.split("\n") + else + @examples = [example] + end + end + + def parse_format(format_arg) + format, where = ClassAndArgumentsParser.parse(format_arg) + unless where + raise "When using several --format options only one of them can be without a file" if @out_used + where = @output_stream + @out_used = true + end + @format_options ||= [] + @format_options << [format, where] + end + + def formatters + @format_options ||= [['progress', @output_stream]] + @formatters ||= load_formatters(@format_options, EXAMPLE_FORMATTERS) + end + + def story_formatters + @format_options ||= [['plain', @output_stream]] + @formatters ||= load_formatters(@format_options, STORY_FORMATTERS) + end + + def load_formatters(format_options, formatters) + format_options.map do |format, where| + formatter_type = if formatters[format] + require formatters[format][0] + eval(formatters[format][1], binding, __FILE__, __LINE__) + else + load_class(format, 'formatter', '--format') + end + formatter_type.new(self, where) + end + end + + def load_heckle_runner(heckle) + suffix = [/mswin/, /java/].detect{|p| p =~ RUBY_PLATFORM} ? '_unsupported' : '' + require "spec/runner/heckle_runner#{suffix}" + @heckle_runner = HeckleRunner.new(heckle) + end + + def number_of_examples + @example_groups.inject(0) do |sum, example_group| + sum + example_group.number_of_examples + end + end + + def files_to_load + result = [] + sorted_files.each do |file| + if File.directory?(file) + filename_pattern.split(",").each do |pattern| + result += Dir[File.expand_path("#{file}/#{pattern.strip}")] + end + elsif File.file?(file) + result << file + else + raise "File or directory not found: #{file}" + end + end + result + end + + protected + def examples_should_be_run? + return @examples_should_be_run unless @examples_should_be_run.nil? + @examples_should_be_run = true + end + + def differ_class=(klass) + return unless klass + @differ_class = klass + Spec::Expectations.differ = self.differ_class.new(self) + end + + def load_class(name, kind, option) + if name =~ /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ + arg = $2 == "" ? nil : $2 + [$1, arg] + else + m = "#{name.inspect} is not a valid class name" + @error_stream.puts m + raise m + end + begin + eval(name, binding, __FILE__, __LINE__) + rescue NameError => e + @error_stream.puts "Couldn't find #{kind} class #{name}" + @error_stream.puts "Make sure the --require option is specified *before* #{option}" + if $_spec_spec ; raise e ; else exit(1) ; end + end + end + + def custom_runner + return nil unless custom_runner? + klass_name, arg = ClassAndArgumentsParser.parse(user_input_for_runner) + runner_type = load_class(klass_name, 'behaviour runner', '--runner') + return runner_type.new(self, arg) + end + + def custom_runner? + return user_input_for_runner ? true : false + end + + def heckle + returns = self.heckle_runner.heckle_with + self.heckle_runner = nil + returns + end + + def sorted_files + return sorter ? files.sort(&sorter) : files + end + + def sorter + FILE_SORTERS[loadby] + end + + def default_differ + require 'spec/expectations/differs/default' + self.differ_class = Spec::Expectations::Differs::Default + end + + def set_spec_from_line_number + if examples.empty? + if files.length == 1 + if File.directory?(files[0]) + error_stream.puts "You must specify one file, not a directory when using the --line option" + exit(1) if stderr? + else + example = SpecParser.new.spec_name_for(files[0], line_number) + @examples = [example] + end + else + error_stream.puts "Only one file can be specified when using the --line option: #{files.inspect}" + exit(3) if stderr? + end + else + error_stream.puts "You cannot use both --line and --example" + exit(4) if stderr? + end + end + + def stderr? + @error_stream == $stderr + end + end + end +end diff --git a/vendor/gems/rspec/lib/spec/runner/reporter.rb b/vendor/gems/rspec/lib/spec/runner/reporter.rb new file mode 100644 index 0000000000..cfc511baf2 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/runner/reporter.rb @@ -0,0 +1,143 @@ +module Spec + module Runner + class Reporter + attr_reader :options, :example_groups + + def initialize(options) + @options = options + @options.reporter = self + clear + end + + def add_example_group(example_group) + formatters.each do |f| + f.add_example_group(example_group) + end + example_groups << example_group + end + + def example_started(example) + formatters.each{|f| f.example_started(example)} + end + + def example_finished(example, error=nil) + @examples << example + + if error.nil? + example_passed(example) + elsif Spec::Example::ExamplePendingError === error + example_pending(example_groups.last, example, error.message) + else + example_failed(example, error) + end + end + + def failure(example, error) + backtrace_tweaker.tweak_backtrace(error) + example_name = "#{example_groups.last.description} #{example.description}" + failure = Failure.new(example_name, error) + @failures << failure + formatters.each do |f| + f.example_failed(example, @failures.length, failure) + end + end + alias_method :example_failed, :failure + + def start(number_of_examples) + clear + @start_time = Time.new + formatters.each{|f| f.start(number_of_examples)} + end + + def end + @end_time = Time.new + end + + # Dumps the summary and returns the total number of failures + def dump + formatters.each{|f| f.start_dump} + dump_pending + dump_failures + formatters.each do |f| + f.dump_summary(duration, @examples.length, @failures.length, @pending_count) + f.close + end + @failures.length + end + + private + + def formatters + @options.formatters + end + + def backtrace_tweaker + @options.backtrace_tweaker + end + + def clear + @example_groups = [] + @failures = [] + @pending_count = 0 + @examples = [] + @start_time = nil + @end_time = nil + end + + def dump_failures + return if @failures.empty? + @failures.inject(1) do |index, failure| + formatters.each{|f| f.dump_failure(index, failure)} + index + 1 + end + end + def dump_pending + formatters.each{|f| f.dump_pending} + end + + def duration + return @end_time - @start_time unless (@end_time.nil? or @start_time.nil?) + return "0.0" + end + + def example_passed(example) + formatters.each{|f| f.example_passed(example)} + end + + def example_pending(example_group, example, message="Not Yet Implemented") + @pending_count += 1 + formatters.each do |f| + f.example_pending(example_group.description, example, message) + end + end + + class Failure + attr_reader :exception + + def initialize(example_name, exception) + @example_name = example_name + @exception = exception + end + + def header + if expectation_not_met? + "'#{@example_name}' FAILED" + elsif pending_fixed? + "'#{@example_name}' FIXED" + else + "#{@exception.class.name} in '#{@example_name}'" + end + end + + def pending_fixed? + @exception.is_a?(Spec::Example::PendingExampleFixedError) + end + + def expectation_not_met? + @exception.is_a?(Spec::Expectations::ExpectationNotMetError) + end + + end + end + end +end diff --git a/vendor/gems/rspec/lib/spec/runner/spec_parser.rb b/vendor/gems/rspec/lib/spec/runner/spec_parser.rb new file mode 100644 index 0000000000..8beb384e90 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/runner/spec_parser.rb @@ -0,0 +1,71 @@ +module Spec + module Runner + # Parses a spec file and finds the nearest example for a given line number. + class SpecParser + attr_reader :best_match + + def initialize + @best_match = {} + end + + def spec_name_for(file, line_number) + best_match.clear + file = File.expand_path(file) + rspec_options.example_groups.each do |example_group| + consider_example_groups_for_best_match example_group, file, line_number + + example_group.examples.each do |example| + consider_example_for_best_match example, example_group, file, line_number + end + end + if best_match[:example_group] + if best_match[:example] + "#{best_match[:example_group].description} #{best_match[:example].description}" + else + best_match[:example_group].description + end + else + nil + end + end + + protected + + def consider_example_groups_for_best_match(example_group, file, line_number) + parsed_backtrace = parse_backtrace(example_group.registration_backtrace) + parsed_backtrace.each do |example_file, example_line| + if is_best_match?(file, line_number, example_file, example_line) + best_match.clear + best_match[:example_group] = example_group + best_match[:line] = example_line + end + end + end + + def consider_example_for_best_match(example, example_group, file, line_number) + parsed_backtrace = parse_backtrace(example.implementation_backtrace) + parsed_backtrace.each do |example_file, example_line| + if is_best_match?(file, line_number, example_file, example_line) + best_match.clear + best_match[:example_group] = example_group + best_match[:example] = example + best_match[:line] = example_line + end + end + end + + def is_best_match?(file, line_number, example_file, example_line) + file == File.expand_path(example_file) && + example_line <= line_number && + example_line > best_match[:line].to_i + end + + def parse_backtrace(backtrace) + backtrace.collect do |trace_line| + split_line = trace_line.split(':') + [split_line[0], Integer(split_line[1])] + end + end + end + end +end diff --git a/vendor/gems/rspec/lib/spec/story.rb b/vendor/gems/rspec/lib/spec/story.rb new file mode 100644 index 0000000000..bc6960a28e --- /dev/null +++ b/vendor/gems/rspec/lib/spec/story.rb @@ -0,0 +1,10 @@ +require 'spec' +require 'spec/story/extensions' +require 'spec/story/given_scenario' +require 'spec/story/runner' +require 'spec/story/scenario' +require 'spec/story/step' +require 'spec/story/step_group' +require 'spec/story/step_mother' +require 'spec/story/story' +require 'spec/story/world' diff --git a/vendor/gems/rspec/lib/spec/story/extensions.rb b/vendor/gems/rspec/lib/spec/story/extensions.rb new file mode 100644 index 0000000000..dc7dd11407 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/story/extensions.rb @@ -0,0 +1,3 @@ +require 'spec/story/extensions/main' +require 'spec/story/extensions/string' +require 'spec/story/extensions/regexp' diff --git a/vendor/gems/rspec/lib/spec/story/extensions/main.rb b/vendor/gems/rspec/lib/spec/story/extensions/main.rb new file mode 100644 index 0000000000..6336b630c5 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/story/extensions/main.rb @@ -0,0 +1,86 @@ +module Spec + module Story + module Extensions + module Main + def Story(title, narrative, params = {}, &body) + ::Spec::Story::Runner.story_runner.Story(title, narrative, params, &body) + end + + # Calling this deprecated is silly, since it hasn't been released yet. But, for + # those who are reading this - this will be deleted before the 1.1 release. + def run_story(*args, &block) + runner = Spec::Story::Runner::PlainTextStoryRunner.new(*args) + runner.instance_eval(&block) if block + runner.run + end + + # Creates (or appends to an existing) a namespaced group of steps for use in Stories + # + # == Examples + # + # # Creating a new group + # steps_for :forms do + # When("user enters $value in the $field field") do ... end + # When("user submits the $form form") do ... end + # end + def steps_for(tag, &block) + steps = rspec_story_steps[tag] + steps.instance_eval(&block) if block + steps + end + + # Creates a context for running a Plain Text Story with specific groups of Steps. + # Also supports adding arbitrary steps that will only be accessible to + # the Story being run. + # + # == Examples + # + # # Run a Story with one group of steps + # with_steps_for :checking_accounts do + # run File.dirname(__FILE__) + "/withdraw_cash" + # end + # + # # Run a Story, adding steps that are only available for this Story + # with_steps_for :accounts do + # Given "user is logged in as account administrator" + # run File.dirname(__FILE__) + "/reconcile_accounts" + # end + # + # # Run a Story with steps from two groups + # with_steps_for :checking_accounts, :savings_accounts do + # run File.dirname(__FILE__) + "/transfer_money" + # end + # + # # Run a Story with a specific Story extension + # with_steps_for :login, :navigation do + # run File.dirname(__FILE__) + "/user_changes_password", :type => RailsStory + # end + def with_steps_for(*tags, &block) + steps = Spec::Story::StepGroup.new do + extend StoryRunnerStepGroupAdapter + end + tags.each {|tag| steps << rspec_story_steps[tag]} + steps.instance_eval(&block) if block + steps + end + + private + + module StoryRunnerStepGroupAdapter + def run(path, options={}) + runner = Spec::Story::Runner::PlainTextStoryRunner.new(path, options) + runner.steps << self + runner.run + end + end + + def rspec_story_steps # :nodoc: + $rspec_story_steps ||= Spec::Story::StepGroupHash.new + end + + end + end + end +end + +include Spec::Story::Extensions::Main \ No newline at end of file diff --git a/vendor/gems/rspec/lib/spec/story/extensions/regexp.rb b/vendor/gems/rspec/lib/spec/story/extensions/regexp.rb new file mode 100644 index 0000000000..7955b4c333 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/story/extensions/regexp.rb @@ -0,0 +1,9 @@ +class Regexp + def step_name + self.source + end + + def arg_regexp + ::Spec::Story::Step::PARAM_OR_GROUP_PATTERN + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/lib/spec/story/extensions/string.rb b/vendor/gems/rspec/lib/spec/story/extensions/string.rb new file mode 100644 index 0000000000..896578def4 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/story/extensions/string.rb @@ -0,0 +1,9 @@ +class String + def step_name + self + end + + def arg_regexp + ::Spec::Story::Step::PARAM_PATTERN + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/lib/spec/story/given_scenario.rb b/vendor/gems/rspec/lib/spec/story/given_scenario.rb new file mode 100644 index 0000000000..88c51f9811 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/story/given_scenario.rb @@ -0,0 +1,14 @@ +module Spec + module Story + class GivenScenario + def initialize(name) + @name = name + end + + def perform(instance, ignore_name) + scenario = Runner::StoryRunner.scenario_from_current_story(@name) + Runner::ScenarioRunner.new.run(scenario, instance) + end + end + end +end diff --git a/vendor/gems/rspec/lib/spec/story/runner.rb b/vendor/gems/rspec/lib/spec/story/runner.rb new file mode 100644 index 0000000000..1f6dd9e7a6 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/story/runner.rb @@ -0,0 +1,56 @@ +require 'spec/story/runner/scenario_collector.rb' +require 'spec/story/runner/scenario_runner.rb' +require 'spec/story/runner/story_runner.rb' +require 'spec/story/runner/story_parser.rb' +require 'spec/story/runner/story_mediator.rb' +require 'spec/story/runner/plain_text_story_runner.rb' + +module Spec + module Story + module Runner + class << self + def run_options # :nodoc: + @run_options ||= ::Spec::Runner::OptionParser.parse(ARGV, $stderr, $stdout) + end + + def story_runner # :nodoc: + unless @story_runner + @story_runner = StoryRunner.new(scenario_runner, world_creator) + run_options.story_formatters.each do |formatter| + register_listener(formatter) + end + Runner.register_exit_hook + end + @story_runner + end + + def scenario_runner # :nodoc: + @scenario_runner ||= ScenarioRunner.new + end + + def world_creator # :nodoc: + @world_creator ||= World + end + + # Use this to register a customer output formatter. + def register_listener(listener) + story_runner.add_listener(listener) # run_started, story_started, story_ended, #run_ended + world_creator.add_listener(listener) # found_scenario, step_succeeded, step_failed, step_failed + scenario_runner.add_listener(listener) # scenario_started, scenario_succeeded, scenario_pending, scenario_failed + end + + def register_exit_hook # :nodoc: + at_exit do + Runner.story_runner.run_stories unless $! + end + # TODO exit with non-zero status if run fails + end + + def dry_run + run_options.dry_run + end + + end + end + end +end diff --git a/vendor/gems/rspec/lib/spec/story/runner/plain_text_story_runner.rb b/vendor/gems/rspec/lib/spec/story/runner/plain_text_story_runner.rb new file mode 100644 index 0000000000..8d34ea2d27 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/story/runner/plain_text_story_runner.rb @@ -0,0 +1,48 @@ +module Spec + module Story + module Runner + class PlainTextStoryRunner + # You can initialize a PlainTextStoryRunner with the path to the + # story file or a block, in which you can define the path using load. + # + # == Examples + # + # PlainTextStoryRunner.new('path/to/file') + # + # PlainTextStoryRunner.new do |runner| + # runner.load 'path/to/file' + # end + def initialize(*args) + @options = Hash === args.last ? args.pop : {} + @story_file = args.empty? ? nil : args.shift + yield self if block_given? + end + + def []=(key, value) + @options[key] = value + end + + def load(path) + @story_file = path + end + + def run + raise "You must set a path to the file with the story. See the RDoc." if @story_file.nil? + mediator = Spec::Story::Runner::StoryMediator.new(steps, Spec::Story::Runner.story_runner, @options) + parser = Spec::Story::Runner::StoryParser.new(mediator) + + story_text = File.read(@story_file) + parser.parse(story_text.split("\n")) + + mediator.run_stories + end + + def steps + @step_group ||= Spec::Story::StepGroup.new + yield @step_group if block_given? + @step_group + end + end + end + end +end diff --git a/vendor/gems/rspec/lib/spec/story/runner/scenario_collector.rb b/vendor/gems/rspec/lib/spec/story/runner/scenario_collector.rb new file mode 100644 index 0000000000..78339fd228 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/story/runner/scenario_collector.rb @@ -0,0 +1,18 @@ +module Spec + module Story + module Runner + class ScenarioCollector + attr_accessor :scenarios + + def initialize(story) + @story = story + @scenarios = [] + end + + def Scenario(name, &body) + @scenarios << Scenario.new(@story, name, &body) + end + end + end + end +end diff --git a/vendor/gems/rspec/lib/spec/story/runner/scenario_runner.rb b/vendor/gems/rspec/lib/spec/story/runner/scenario_runner.rb new file mode 100644 index 0000000000..aee52e4129 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/story/runner/scenario_runner.rb @@ -0,0 +1,46 @@ +module Spec + module Story + module Runner + class ScenarioRunner + def initialize + @listeners = [] + end + + def run(scenario, world) + @listeners.each { |l| l.scenario_started(scenario.story.title, scenario.name) } + run_story_ignoring_scenarios(scenario.story, world) + + world.start_collecting_errors + world.instance_eval(&scenario.body) + if world.errors.empty? + @listeners.each { |l| l.scenario_succeeded(scenario.story.title, scenario.name) } + else + if Spec::Example::ExamplePendingError === (e = world.errors.first) + @listeners.each { |l| l.scenario_pending(scenario.story.title, scenario.name, e.message) } + else + @listeners.each { |l| l.scenario_failed(scenario.story.title, scenario.name, e) } + end + end + end + + def add_listener(listener) + @listeners << listener + end + + private + + def run_story_ignoring_scenarios(story, world) + class << world + def Scenario(name, &block) + # do nothing + end + end + story.run_in(world) + class << world + remove_method(:Scenario) + end + end + end + end + end +end diff --git a/vendor/gems/rspec/lib/spec/story/runner/story_mediator.rb b/vendor/gems/rspec/lib/spec/story/runner/story_mediator.rb new file mode 100644 index 0000000000..1f4744b9f8 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/story/runner/story_mediator.rb @@ -0,0 +1,123 @@ + module Spec + module Story + module Runner + + class StoryMediator + def initialize(step_group, runner, options={}) + @step_group = step_group + @stories = [] + @runner = runner + @options = options + end + + def stories + @stories.collect { |p| p.to_proc } + end + + def create_story(title, narrative) + @stories << Story.new(title, narrative, @step_group, @options) + end + + def create_scenario(title) + current_story.add_scenario Scenario.new(title) + end + + def create_given(name) + current_scenario.add_step Step.new('Given', name) + end + + def create_given_scenario(name) + current_scenario.add_step Step.new('GivenScenario', name) + end + + def create_when(name) + current_scenario.add_step Step.new('When', name) + end + + def create_then(name) + current_scenario.add_step Step.new('Then', name) + end + + def run_stories + stories.each { |story| @runner.instance_eval(&story) } + end + + private + def current_story + @stories.last + end + + def current_scenario + current_story.current_scenario + end + + class Story + def initialize(title, narrative, step_group, options) + @title = title + @narrative = narrative + @scenarios = [] + @step_group = step_group + @options = options + end + + def to_proc + title = @title + narrative = @narrative + scenarios = @scenarios.collect { |scenario| scenario.to_proc } + options = @options.merge(:steps => @step_group) + lambda do + Story title, narrative, options do + scenarios.each { |scenario| instance_eval(&scenario) } + end + end + end + + def add_scenario(scenario) + @scenarios << scenario + end + + def current_scenario + @scenarios.last + end + end + + class Scenario + def initialize(name) + @name = name + @steps = [] + end + + def to_proc + name = @name + steps = @steps.collect { |step| step.to_proc } + lambda do + Scenario name do + steps.each { |step| instance_eval(&step) } + end + end + end + + def add_step(step) + @steps << step + end + end + + class Step + def initialize(type, name) + @type = type + @name = name + end + + def to_proc + type = @type + name = @name + lambda do + send(type, name) + end + end + end + end + + end + end +end diff --git a/vendor/gems/rspec/lib/spec/story/runner/story_parser.rb b/vendor/gems/rspec/lib/spec/story/runner/story_parser.rb new file mode 100644 index 0000000000..d454df8cb7 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/story/runner/story_parser.rb @@ -0,0 +1,227 @@ +module Spec + module Story + module Runner + + class IllegalStepError < StandardError + def initialize(state, event) + super("Illegal attempt to create a #{event} after a #{state}") + end + end + + class StoryParser + def initialize(story_mediator) + @story_mediator = story_mediator + @current_story_lines = [] + transition_to(:starting_state) + end + + def parse(lines) + lines.reject! {|line| line == ""} + until lines.empty? + process_line(lines.shift) + end + @state.eof + end + + def process_line(line) + line.strip! + case line + when /^Story: / then @state.story(line) + when /^Scenario: / then @state.scenario(line) + when /^Given:? / then @state.given(line) + when /^GivenScenario:? / then @state.given_scenario(line) + when /^When:? / then @state.event(line) + when /^Then:? / then @state.outcome(line) + when /^And:? / then @state.one_more_of_the_same(line) + else @state.other(line) + end + end + + def init_story(title) + @current_story_lines.clear + add_story_line(title) + end + + def add_story_line(line) + @current_story_lines << line + end + + def create_story() + unless @current_story_lines.empty? + @story_mediator.create_story(@current_story_lines[0].gsub("Story: ",""), @current_story_lines[1..-1].join("\n")) + @current_story_lines.clear + end + end + + def create_scenario(title) + @story_mediator.create_scenario(title.gsub("Scenario: ","")) + end + + def create_given(name) + @story_mediator.create_given(name) + end + + def create_given_scenario(name) + @story_mediator.create_given_scenario(name) + end + + def create_when(name) + @story_mediator.create_when(name) + end + + def create_then(name) + @story_mediator.create_then(name) + end + + def transition_to(key) + @state = states[key] + end + + def states + @states ||= { + :starting_state => StartingState.new(self), + :story_state => StoryState.new(self), + :scenario_state => ScenarioState.new(self), + :given_state => GivenState.new(self), + :when_state => WhenState.new(self), + :then_state => ThenState.new(self) + } + end + + class State + def initialize(parser) + @parser = parser + end + + def story(line) + @parser.init_story(line) + @parser.transition_to(:story_state) + end + + def scenario(line) + @parser.create_scenario(line) + @parser.transition_to(:scenario_state) + end + + def given(line) + @parser.create_given(remove_tag_from(:given, line)) + @parser.transition_to(:given_state) + end + + def given_scenario(line) + @parser.create_given_scenario(remove_tag_from(:givenscenario, line)) + @parser.transition_to(:given_state) + end + + def event(line) + @parser.create_when(remove_tag_from(:when, line)) + @parser.transition_to(:when_state) + end + + def outcome(line) + @parser.create_then(remove_tag_from(:then, line)) + @parser.transition_to(:then_state) + end + + def remove_tag_from(tag, line) + tokens = line.split + # validation of tag can go here + tokens[0].downcase.match(/#{tag.to_s}:?/) ? + (tokens[1..-1].join(' ')) : line + end + + def eof + end + + def other(line) + # no-op - supports header text before the first story in a file + end + end + + class StartingState < State + def initialize(parser) + @parser = parser + end + end + + class StoryState < State + def one_more_of_the_same(line) + other(line) + end + + def story(line) + @parser.create_story + @parser.add_story_line(line) + end + + def scenario(line) + @parser.create_story + @parser.create_scenario(line) + @parser.transition_to(:scenario_state) + end + + def given(line) + other(line) + end + + def event(line) + other(line) + end + + def outcome(line) + other(line) + end + + def other(line) + @parser.add_story_line(line) + end + + def eof + @parser.create_story + end + end + + class ScenarioState < State + def one_more_of_the_same(line) + raise IllegalStepError.new("Scenario", "And") + end + + def scenario(line) + @parser.create_scenario(line) + end + end + + class GivenState < State + def one_more_of_the_same(line) + @parser.create_given(remove_tag_from(:and, line)) + end + + def given(line) + @parser.create_given(remove_tag_from(:given, line)) + end + end + + class WhenState < State + def one_more_of_the_same(line) + @parser.create_when(remove_tag_from(:and ,line)) + end + + def event(line) + @parser.create_when(remove_tag_from(:when ,line)) + end + end + + class ThenState < State + def one_more_of_the_same(line) + @parser.create_then(remove_tag_from(:and ,line)) + end + + def outcome(line) + @parser.create_then(remove_tag_from(:then ,line)) + end + end + + end + end + end +end diff --git a/vendor/gems/rspec/lib/spec/story/runner/story_runner.rb b/vendor/gems/rspec/lib/spec/story/runner/story_runner.rb new file mode 100644 index 0000000000..f9eeb9ac12 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/story/runner/story_runner.rb @@ -0,0 +1,68 @@ +module Spec + module Story + module Runner + class StoryRunner + class << self + attr_accessor :current_story_runner + + def scenario_from_current_story(scenario_name) + current_story_runner.scenario_from_current_story(scenario_name) + end + end + + attr_accessor :stories, :scenarios, :current_story + + def initialize(scenario_runner, world_creator = World) + StoryRunner.current_story_runner = self + @scenario_runner = scenario_runner + @world_creator = world_creator + @stories = [] + @scenarios_by_story = {} + @scenarios = [] + @listeners = [] + end + + def Story(title, narrative, params = {}, &body) + story = Story.new(title, narrative, params, &body) + @stories << story + + # collect scenarios + collector = ScenarioCollector.new(story) + story.run_in(collector) + @scenarios += collector.scenarios + @scenarios_by_story[story.title] = collector.scenarios + end + + def run_stories + return if @stories.empty? + @listeners.each { |l| l.run_started(scenarios.size) } + @stories.each do |story| + story.assign_steps_to(World) + @current_story = story + @listeners.each { |l| l.story_started(story.title, story.narrative) } + scenarios = @scenarios_by_story[story.title] + scenarios.each do |scenario| + type = story[:type] || Object + args = story[:args] || [] + world = @world_creator.create(type, *args) + @scenario_runner.run(scenario, world) + end + @listeners.each { |l| l.story_ended(story.title, story.narrative) } + World.step_mother.clear + end + unique_steps = (World.step_names.collect {|n| Regexp === n ? n.source : n.to_s}).uniq.sort + @listeners.each { |l| l.collected_steps(unique_steps) } + @listeners.each { |l| l.run_ended } + end + + def add_listener(listener) + @listeners << listener + end + + def scenario_from_current_story(scenario_name) + @scenarios_by_story[@current_story.title].find {|s| s.name == scenario_name } + end + end + end + end +end diff --git a/vendor/gems/rspec/lib/spec/story/scenario.rb b/vendor/gems/rspec/lib/spec/story/scenario.rb new file mode 100644 index 0000000000..d83b3eeb8c --- /dev/null +++ b/vendor/gems/rspec/lib/spec/story/scenario.rb @@ -0,0 +1,14 @@ + +module Spec + module Story + class Scenario + attr_accessor :name, :body, :story + + def initialize(story, name, &body) + @story = story + @name = name + @body = body + end + end + end +end diff --git a/vendor/gems/rspec/lib/spec/story/step.rb b/vendor/gems/rspec/lib/spec/story/step.rb new file mode 100644 index 0000000000..ee9ede0578 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/story/step.rb @@ -0,0 +1,58 @@ +module Spec + module Story + class Step + PARAM_PATTERN = /(\$\w*)/ + PARAM_OR_GROUP_PATTERN = /(\$\w*)|\(.*?\)/ + + attr_reader :name + def initialize(name, &block) + @name = name + assign_expression(name) + init_module(name, &block) + end + + def perform(instance, *args) + instance.extend(@mod) + instance.__send__(sanitize(@name), *args) + end + + def init_module(name, &block) + sanitized_name = sanitize(name) + @mod = Module.new do + define_method(sanitized_name, &block) + end + end + + def sanitize(a_string_or_regexp) + return a_string_or_regexp.source if Regexp == a_string_or_regexp + a_string_or_regexp.to_s + end + + + def matches?(name) + !(matches = name.match(@expression)).nil? + end + + def parse_args(name) + name.match(@expression)[1..-1] + end + + private + + def assign_expression(string_or_regexp) + if String === string_or_regexp + expression = string_or_regexp.dup + expression.gsub! '(', '\(' + expression.gsub! ')', '\)' + elsif Regexp === string_or_regexp + expression = string_or_regexp.source + end + while expression =~ PARAM_PATTERN + expression.gsub!($1, "(.*?)") + end + @expression = Regexp.new("^#{expression}$") + end + + end + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/lib/spec/story/step_group.rb b/vendor/gems/rspec/lib/spec/story/step_group.rb new file mode 100644 index 0000000000..cae558c40d --- /dev/null +++ b/vendor/gems/rspec/lib/spec/story/step_group.rb @@ -0,0 +1,89 @@ +module Spec + module Story + + class StepGroupHash < Hash + def initialize + super do |h,k| + h[k] = Spec::Story::StepGroup.new + end + end + end + + class StepGroup + def self.steps(&block) + @step_group ||= StepGroup.new(false) + @step_group.instance_eval(&block) if block + @step_group + end + + def initialize(init_defaults=true, &block) + @hash_of_lists_of_steps = Hash.new {|h, k| h[k] = []} + if init_defaults + self.class.steps.add_to(self) + end + instance_eval(&block) if block + end + + def find(type, name) + @hash_of_lists_of_steps[type].each do |step| + return step if step.matches?(name) + end + return nil + end + + def GivenScenario(name, &block) + create_matcher(:given_scenario, name, &block) + end + + def Given(name, &block) + create_matcher(:given, name, &block) + end + + def When(name, &block) + create_matcher(:when, name, &block) + end + + def Then(name, &block) + create_matcher(:then, name, &block) + end + + alias :given_scenario :GivenScenario + alias :given :Given + alias :when :When + alias :then :Then + + def add(type, steps) + (@hash_of_lists_of_steps[type] << steps).flatten! + end + + def clear + @hash_of_lists_of_steps.clear + end + + def empty? + [:given_scenario, :given, :when, :then].each do |type| + return false unless @hash_of_lists_of_steps[type].empty? + end + return true + end + + def add_to(other_step_matchers) + [:given_scenario, :given, :when, :then].each do |type| + other_step_matchers.add(type, @hash_of_lists_of_steps[type]) + end + end + + def <<(other_step_matchers) + other_step_matchers.add_to(self) if other_step_matchers.respond_to?(:add_to) + end + + # TODO - make me private + def create_matcher(type, name, &block) + matcher = Step.new(name, &block) + @hash_of_lists_of_steps[type] << matcher + matcher + end + + end + end +end diff --git a/vendor/gems/rspec/lib/spec/story/step_mother.rb b/vendor/gems/rspec/lib/spec/story/step_mother.rb new file mode 100644 index 0000000000..a2e84e310c --- /dev/null +++ b/vendor/gems/rspec/lib/spec/story/step_mother.rb @@ -0,0 +1,37 @@ +module Spec + module Story + class StepMother + def initialize + @steps = StepGroup.new + end + + def use(new_step_group) + @steps << new_step_group + end + + def store(type, step) + @steps.add(type, step) + end + + def find(type, name) + if @steps.find(type, name).nil? + @steps.add(type, + Step.new(name) do + raise Spec::Example::ExamplePendingError.new("Unimplemented step: #{name}") + end + ) + end + @steps.find(type, name) + end + + def clear + @steps.clear + end + + def empty? + @steps.empty? + end + + end + end +end diff --git a/vendor/gems/rspec/lib/spec/story/story.rb b/vendor/gems/rspec/lib/spec/story/story.rb new file mode 100644 index 0000000000..112e9414b5 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/story/story.rb @@ -0,0 +1,42 @@ +module Spec + module Story + class Story + attr_reader :title, :narrative + + def initialize(title, narrative, params = {}, &body) + @body = body + @title = title + @narrative = narrative + @params = params + end + + def [](key) + @params[key] + end + + def run_in(obj) + obj.instance_eval(&@body) + end + + def assign_steps_to(assignee) + if @params[:steps] + assignee.use(@params[:steps]) + else + case keys = @params[:steps_for] + when Symbol + keys = [keys] + when nil + keys = [] + end + keys.each do |key| + assignee.use(steps_for(key)) + end + end + end + + def steps_for(key) + $rspec_story_steps[key] + end + end + end +end diff --git a/vendor/gems/rspec/lib/spec/story/world.rb b/vendor/gems/rspec/lib/spec/story/world.rb new file mode 100644 index 0000000000..93b6e1d15d --- /dev/null +++ b/vendor/gems/rspec/lib/spec/story/world.rb @@ -0,0 +1,125 @@ +require 'rubygems' +require 'spec/expectations' +require 'spec/matchers' +require 'spec/example/pending' + +module Spec + module Story +=begin + A World represents the actual instance a scenario will run in. + + The runner ensures any instance variables and methods defined anywhere + in a story block are available to all the scenarios. This includes + variables that are created or referenced inside Given, When and Then + blocks. +=end + module World + include ::Spec::Example::Pending + include ::Spec::Matchers + # store steps and listeners in the singleton metaclass. + # This serves both to keep them out of the way of runtime Worlds + # and to make them available to all instances. + class << self + def create(cls = Object, *args) + cls.new(*args).extend(World) + end + + def listeners + @listeners ||= [] + end + + def add_listener(listener) + listeners() << listener + end + + def step_mother + @step_mother ||= StepMother.new + end + + def use(steps) + step_mother.use(steps) + end + + def step_names + @step_names ||= [] + end + + def run_given_scenario_with_suspended_listeners(world, type, name, scenario) + current_listeners = Array.new(listeners) + begin + listeners.each { |l| l.found_scenario(type, name) } + @listeners.clear + scenario.perform(world, name) unless ::Spec::Story::Runner.dry_run + ensure + @listeners.replace(current_listeners) + end + end + + def store_and_call(world, type, name, *args, &block) + if block_given? + step_mother.store(type, Step.new(name, &block)) + end + step = step_mother.find(type, name) + + step_name = step.name + step_names << step_name + + # It's important to have access to the parsed args here, so + # we can give them to the listeners. The HTML reporter needs + # the args so it can style them. See the generated output in + # story_server/prototype/rspec_stories.html (generated by rake stories) + args = step.parse_args(name) if args.empty? + begin + listeners.each { |l| l.step_upcoming(type, step_name, *args) } + step.perform(world, *args) unless ::Spec::Story::Runner.dry_run + listeners.each { |l| l.step_succeeded(type, step_name, *args) } + rescue Exception => e + case e + when Spec::Example::ExamplePendingError + @listeners.each { |l| l.step_pending(type, step_name, *args) } + else + @listeners.each { |l| l.step_failed(type, step_name, *args) } + end + errors << e + end + end + + def errors + @errors ||= [] + end + end # end of class << self + + def start_collecting_errors + errors.clear + end + + def errors + World.errors + end + + def GivenScenario(name) + World.run_given_scenario_with_suspended_listeners(self, :'given scenario', name, GivenScenario.new(name)) + @__previous_step = :given + end + + def Given(name, *args, &block) + World.store_and_call self, :given, name, *args, &block + @__previous_step = :given + end + + def When(name, *args, &block) + World.store_and_call self, :when, name, *args, &block + @__previous_step = :when + end + + def Then(name, *args, &block) + World.store_and_call self, :then, name, *args, &block + @__previous_step = :then + end + + def And(name, *args, &block) + World.store_and_call self, @__previous_step, name, *args, &block + end + end + end +end diff --git a/vendor/gems/rspec/lib/spec/translator.rb b/vendor/gems/rspec/lib/spec/translator.rb new file mode 100644 index 0000000000..c1e07eda4b --- /dev/null +++ b/vendor/gems/rspec/lib/spec/translator.rb @@ -0,0 +1,114 @@ +require 'fileutils' + +module Spec + class Translator + def translate(from, to) + from = File.expand_path(from) + to = File.expand_path(to) + if File.directory?(from) + translate_dir(from, to) + elsif(from =~ /\.rb$/) + translate_file(from, to) + end + end + + def translate_dir(from, to) + FileUtils.mkdir_p(to) unless File.directory?(to) + Dir["#{from}/*"].each do |sub_from| + path = sub_from[from.length+1..-1] + sub_to = File.join(to, path) + translate(sub_from, sub_to) + end + end + + def translate_file(from, to) + translation = "" + File.open(from) do |io| + io.each_line do |line| + translation << translate_line(line) + end + end + File.open(to, "w") do |io| + io.write(translation) + end + end + + def translate_line(line) + # Translate deprecated mock constraints + line.gsub!(/:any_args/, 'any_args') + line.gsub!(/:anything/, 'anything') + line.gsub!(/:boolean/, 'boolean') + line.gsub!(/:no_args/, 'no_args') + line.gsub!(/:numeric/, 'an_instance_of(Numeric)') + line.gsub!(/:string/, 'an_instance_of(String)') + + return line if line =~ /(should_not|should)_receive/ + + line.gsub!(/(^\s*)context([\s*|\(]['|"|A-Z])/, '\1describe\2') + line.gsub!(/(^\s*)specify([\s*|\(]['|"|A-Z])/, '\1it\2') + line.gsub!(/(^\s*)context_setup(\s*[do|\{])/, '\1before(:all)\2') + line.gsub!(/(^\s*)context_teardown(\s*[do|\{])/, '\1after(:all)\2') + line.gsub!(/(^\s*)setup(\s*[do|\{])/, '\1before(:each)\2') + line.gsub!(/(^\s*)teardown(\s*[do|\{])/, '\1after(:each)\2') + + if line =~ /(.*\.)(should_not|should)(?:_be)(?!_)(.*)/m + pre = $1 + should = $2 + post = $3 + be_or_equal = post =~ /(<|>)/ ? "be" : "equal" + + return "#{pre}#{should} #{be_or_equal}#{post}" + end + + if line =~ /(.*\.)(should_not|should)_(?!not)\s*(.*)/m + pre = $1 + should = $2 + post = $3 + + post.gsub!(/^raise/, 'raise_error') + post.gsub!(/^throw/, 'throw_symbol') + + unless standard_matcher?(post) + post = "be_#{post}" + end + + # Add parenthesis + post.gsub!(/^(\w+)\s+([\w|\.|\,|\(.*\)|\'|\"|\:|@| ]+)(\})/, '\1(\2)\3') # inside a block + post.gsub!(/^(redirect_to)\s+(.*)/, '\1(\2)') # redirect_to, which often has http: + post.gsub!(/^(\w+)\s+([\w|\.|\,|\(.*\)|\{.*\}|\'|\"|\:|@| ]+)/, '\1(\2)') + post.gsub!(/(\s+\))/, ')') + post.gsub!(/\)\}/, ') }') + post.gsub!(/^(\w+)\s+(\/.*\/)/, '\1(\2)') #regexps + line = "#{pre}#{should} #{post}" + end + + line + end + + def standard_matcher?(matcher) + patterns = [ + /^be/, + /^be_close/, + /^eql/, + /^equal/, + /^has/, + /^have/, + /^change/, + /^include/, + /^match/, + /^raise_error/, + /^respond_to/, + /^redirect_to/, + /^satisfy/, + /^throw_symbol/, + # Extra ones that we use in spec_helper + /^pass/, + /^fail/, + /^fail_with/, + ] + matched = patterns.detect{ |p| matcher =~ p } + !matched.nil? + end + + end +end diff --git a/vendor/gems/rspec/lib/spec/version.rb b/vendor/gems/rspec/lib/spec/version.rb new file mode 100644 index 0000000000..26c15e9196 --- /dev/null +++ b/vendor/gems/rspec/lib/spec/version.rb @@ -0,0 +1,22 @@ +module Spec + module VERSION + unless defined? MAJOR + MAJOR = 1 + MINOR = 1 + TINY = 3 + RELEASE_CANDIDATE = nil + + BUILD_TIME_UTC = 20080131122909 + + STRING = [MAJOR, MINOR, TINY].join('.') + TAG = "REL_#{[MAJOR, MINOR, TINY, RELEASE_CANDIDATE].compact.join('_')}".upcase.gsub(/\.|-/, '_') + FULL_VERSION = "#{[MAJOR, MINOR, TINY, RELEASE_CANDIDATE].compact.join('.')} (build #{BUILD_TIME_UTC})" + + NAME = "RSpec" + URL = "http://rspec.rubyforge.org/" + + DESCRIPTION = "#{NAME}-#{FULL_VERSION} - BDD for Ruby\n#{URL}" + end + end +end + diff --git a/vendor/gems/rspec/plugins/mock_frameworks/flexmock.rb b/vendor/gems/rspec/plugins/mock_frameworks/flexmock.rb new file mode 100644 index 0000000000..6875a5222b --- /dev/null +++ b/vendor/gems/rspec/plugins/mock_frameworks/flexmock.rb @@ -0,0 +1,23 @@ +#!/usr/bin/env ruby +# +# Created by Jim Weirich on 2007-04-10. +# Copyright (c) 2007. All rights reserved. + +require 'flexmock/rspec' + +module Spec + module Plugins + module MockFramework + include FlexMock::MockContainer + def setup_mocks_for_rspec + # No setup required + end + def verify_mocks_for_rspec + flexmock_verify + end + def teardown_mocks_for_rspec + flexmock_close + end + end + end +end diff --git a/vendor/gems/rspec/plugins/mock_frameworks/mocha.rb b/vendor/gems/rspec/plugins/mock_frameworks/mocha.rb new file mode 100644 index 0000000000..69d11636c9 --- /dev/null +++ b/vendor/gems/rspec/plugins/mock_frameworks/mocha.rb @@ -0,0 +1,19 @@ +require 'mocha/standalone' +require 'mocha/object' + +module Spec + module Plugins + module MockFramework + include Mocha::Standalone + def setup_mocks_for_rspec + mocha_setup + end + def verify_mocks_for_rspec + mocha_verify + end + def teardown_mocks_for_rspec + mocha_teardown + end + end + end +end diff --git a/vendor/gems/rspec/plugins/mock_frameworks/rr.rb b/vendor/gems/rspec/plugins/mock_frameworks/rr.rb new file mode 100644 index 0000000000..c019c18a13 --- /dev/null +++ b/vendor/gems/rspec/plugins/mock_frameworks/rr.rb @@ -0,0 +1,21 @@ +require 'rr' + +patterns = ::Spec::Runner::QuietBacktraceTweaker::IGNORE_PATTERNS +patterns.push(RR::Errors::BACKTRACE_IDENTIFIER) + +module Spec + module Plugins + module MockFramework + include RR::Extensions::InstanceMethods + def setup_mocks_for_rspec + RR::Space.instance.reset + end + def verify_mocks_for_rspec + RR::Space.instance.verify_doubles + end + def teardown_mocks_for_rspec + RR::Space.instance.reset + end + end + end +end diff --git a/vendor/gems/rspec/plugins/mock_frameworks/rspec.rb b/vendor/gems/rspec/plugins/mock_frameworks/rspec.rb new file mode 100644 index 0000000000..ce215ace24 --- /dev/null +++ b/vendor/gems/rspec/plugins/mock_frameworks/rspec.rb @@ -0,0 +1,18 @@ +require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "lib", "spec", "mocks")) + +module Spec + module Plugins + module MockFramework + include Spec::Mocks::ExampleMethods + def setup_mocks_for_rspec + $rspec_mocks ||= Spec::Mocks::Space.new + end + def verify_mocks_for_rspec + $rspec_mocks.verify_all + end + def teardown_mocks_for_rspec + $rspec_mocks.reset_all + end + end + end +end diff --git a/vendor/gems/rspec/pre_commit/lib/pre_commit.rb b/vendor/gems/rspec/pre_commit/lib/pre_commit.rb new file mode 100644 index 0000000000..2f3480834b --- /dev/null +++ b/vendor/gems/rspec/pre_commit/lib/pre_commit.rb @@ -0,0 +1,4 @@ +require "pre_commit/pre_commit" +require "pre_commit/rspec" +require "pre_commit/core" +require "pre_commit/rspec_on_rails" diff --git a/vendor/gems/rspec/pre_commit/lib/pre_commit/core.rb b/vendor/gems/rspec/pre_commit/lib/pre_commit/core.rb new file mode 100644 index 0000000000..420cc0c756 --- /dev/null +++ b/vendor/gems/rspec/pre_commit/lib/pre_commit/core.rb @@ -0,0 +1,50 @@ +class PreCommit::Core < PreCommit + def pre_commit + rake_invoke :examples + website + end + + def website(run_webby=true) + clobber + rake_invoke :verify_rcov + rake_invoke :spec_html + webby + rake_invoke :failing_examples_with_html + rdoc + rdoc_rails + end + + def clobber + rm_rf '../doc/output' + rm_rf 'translated_specs' + end + + def webby + Dir.chdir '../doc' do + output = silent_sh('rake rebuild 2>&1') + if shell_error?(output) + raise "ERROR while generating web site: #{output}" + end + + spec_page = File.expand_path('output/documentation/tools/spec.html') + spec_page_content = File.open(spec_page).read + unless spec_page_content =~/\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\./m + raise "#{'!'*400}\nIt seems like the output in the generated documentation is broken (no dots: ......)\n. Look in #{spec_page}" + end + end + end + + def rdoc + Dir.chdir '../rspec' do + rake = (PLATFORM == "i386-mswin32") ? "rake.cmd" : "rake" + `#{rake} rdoc` + end + end + + def rdoc_rails + Dir.chdir '../rspec_on_rails' do + rake = (PLATFORM == "i386-mswin32") ? "rake.cmd" : "rake" + `#{rake} rdoc` + end + end +end diff --git a/vendor/gems/rspec/pre_commit/lib/pre_commit/pre_commit.rb b/vendor/gems/rspec/pre_commit/lib/pre_commit/pre_commit.rb new file mode 100644 index 0000000000..80f958b917 --- /dev/null +++ b/vendor/gems/rspec/pre_commit/lib/pre_commit/pre_commit.rb @@ -0,0 +1,54 @@ +class PreCommit + attr_reader :actor + def initialize(actor) + @actor = actor + end + + protected + def rake_invoke(task_name) + Rake::Task[task_name].invoke + end + + def rake_sh(task_name, env_hash={}) + env = env_hash.collect{|key, value| "#{key}=#{value}"}.join(' ') + rake = (PLATFORM == "i386-mswin32") ? "rake.bat" : "rake" + cmd = "#{rake} #{task_name} #{env} --trace" + output = silent_sh(cmd) + puts output + if shell_error?(output) + raise "ERROR while running rake: #{cmd}" + end + end + + def silent_sh(cmd, &block) + output = nil + IO.popen(cmd) do |io| + output = io.read + output.each_line do |line| + block.call(line) if block + end + end + output + end + + def shell_error?(output) + output =~ /ERROR/n || error_code? + end + + def error_code? + $?.exitstatus != 0 + end + + def root_dir + dir = File.dirname(__FILE__) + File.expand_path("#{dir}/../../../..") + end + + def method_missing(method_name, *args, &block) + if actor.respond_to?(method_name) + actor.send(method_name, *args, &block) + else + super + end + end +end diff --git a/vendor/gems/rspec/pre_commit/lib/pre_commit/rspec.rb b/vendor/gems/rspec/pre_commit/lib/pre_commit/rspec.rb new file mode 100644 index 0000000000..5078c72d0e --- /dev/null +++ b/vendor/gems/rspec/pre_commit/lib/pre_commit/rspec.rb @@ -0,0 +1,111 @@ +class PreCommit::Rspec < PreCommit + def pre_commit + check_for_gem_dependencies + fix_cr_lf + touch_revision_storing_files + pre_commit_core + pre_commit_textmate_bundle + pre_commit_rails + ok_to_commit + end + + def check_for_gem_dependencies + require "rubygems" + gem 'rake' + gem 'webby' + gem 'coderay' + gem 'RedCloth' + gem 'syntax' + gem 'diff-lcs' + gem 'heckle' unless PLATFORM == "i386-mswin32" + gem 'hpricot' + end + + def fix_cr_lf + files = FileList['**/*.rb']. + exclude('example_rails_app/vendor/**'). + exclude('rspec/translated_specs/**') + $\="\n" + files.each do |f| + raw_content = File.read(f) + fixed_content = "" + raw_content.each_line do |line| + fixed_content << line + end + unless raw_content == fixed_content + File.open(f, "w") do |io| + io.print fixed_content + end + end + end + end + + # TODO - move me up to the project root + def touch_revision_storing_files + files = [ + 'rspec/lib/spec/version.rb', + 'rspec_on_rails/lib/spec/rails/version.rb' + ] + build_time_utc = Time.now.utc.strftime('%Y%m%d%H%M%S') + files.each do |path| + abs_path = File.join(root_dir, path) + content = File.open(abs_path).read + touched_content = content.gsub(/BUILD_TIME_UTC = (\d*)/, "BUILD_TIME_UTC = #{build_time_utc}") + File.open(abs_path, 'w') do |io| + io.write touched_content + end + end + end + + def pre_commit_core + Dir.chdir 'rspec' do + rake = (PLATFORM == "i386-mswin32") ? "rake.bat" : "rake" + system("#{rake} pre_commit --verbose --trace") + raise "RSpec Core pre_commit failed" if error_code? + end + end + + def pre_commit_textmate_bundle + Dir.chdir 'RSpec.tmbundle/Support' do + rake = (PLATFORM == "i386-mswin32") ? "rake.bat" : "rake" + system("#{rake} spec --verbose --trace") + raise "RSpec Textmate Bundle specs failed" if error_code? + end + end + + def install_dependencies + Dir.chdir 'example_rails_app' do + rake_sh("-f Multirails.rake install_dependencies") + end + end + + def update_dependencies + Dir.chdir 'example_rails_app' do + rake_sh("-f Multirails.rake update_dependencies") + end + end + + def pre_commit_rails + Dir.chdir 'example_rails_app' do + rake = (PLATFORM == "i386-mswin32") ? "rake.cmd" : "rake" + cmd = "#{rake} -f Multirails.rake pre_commit --trace" + system(cmd) + if error_code? + message = <<-EOF + ############################################################ + RSpec on Rails Plugin pre_commit failed. For more info: + + cd example_rails_app + #{cmd} + + ############################################################ + EOF + raise message.gsub(/^ /, '') + end + end + end + + def ok_to_commit + puts "OK TO COMMIT" + end +end diff --git a/vendor/gems/rspec/pre_commit/lib/pre_commit/rspec_on_rails.rb b/vendor/gems/rspec/pre_commit/lib/pre_commit/rspec_on_rails.rb new file mode 100644 index 0000000000..7879537638 --- /dev/null +++ b/vendor/gems/rspec/pre_commit/lib/pre_commit/rspec_on_rails.rb @@ -0,0 +1,313 @@ +class PreCommit::RspecOnRails < PreCommit + def pre_commit + install_plugins + check_dependencies + used_railses = [] + VENDOR_DEPS.each do |dependency| + rails_dir = File.expand_path(dependency[:checkout_path]) + rails_version = rails_version_from_dir(rails_dir) + begin + rspec_pre_commit(rails_version, false) + used_railses << rails_version + rescue Exception => e + unless rails_version == 'edge' + raise e + end + end + end + uninstall_plugins + puts "All specs passed against the following released versions of Rails: #{used_railses.join(", ")}" + unless used_railses.include?('edge') + puts "There were errors running pre_commit against edge" + end + end + + def rails_version_from_dir(rails_dir) + File.basename(rails_dir) + end + + def rspec_pre_commit(rails_version=ENV['RSPEC_RAILS_VERSION'],uninstall=true) + puts "#####################################################" + puts "running pre_commit against rails #{rails_version}" + puts "#####################################################" + ENV['RSPEC_RAILS_VERSION'] = rails_version + cleanup(uninstall) + ensure_db_config + clobber_sqlite_data + install_plugins + generate_rspec + + generate_login_controller + create_purchase + + rake_sh "spec" + rake_sh "spec:plugins:rspec_on_rails" + + # TODO - why is this necessary? Shouldn't the specs leave + # a clean DB? + rake_sh "db:test:prepare" + sh "ruby vendor/plugins/rspec_on_rails/stories/all.rb" + cleanup(uninstall) + end + + def cleanup(uninstall=true) + revert_routes + rm_generated_login_controller_files + destroy_purchase + uninstall_plugins if uninstall + end + + def revert_routes + output = silent_sh("cp config/routes.rb.bak config/routes.rb") + raise "Error reverting routes.rb" if shell_error?(output) + end + + def create_purchase + generate_purchase + migrate_up + end + + def install_plugins + install_rspec_on_rails_plugin + install_rspec_plugin + end + + def install_rspec_on_rails_plugin + rm_rf 'vendor/plugins/rspec_on_rails' + copy '../rspec_on_rails', 'vendor/plugins/' + end + + def install_rspec_plugin + rm_rf 'vendor/plugins/rspec' + copy '../rspec', 'vendor/plugins/' + end + + def uninstall_plugins + rm_rf 'vendor/plugins/rspec_on_rails' + rm_rf 'vendor/plugins/rspec' + rm_rf 'script/spec' + rm_rf 'script/spec_server' + rm_rf 'spec/spec_helper.rb' + rm_rf 'spec/spec.opts' + rm_rf 'spec/rcov.opts' + end + + def copy(source, target) + output = silent_sh("cp -R #{File.expand_path(source)} #{File.expand_path(target)}") + raise "Error installing rspec" if shell_error?(output) + end + + def generate_rspec + result = silent_sh("ruby script/generate rspec --force") + if error_code? || result =~ /^Missing/ + raise "Failed to generate rspec environment:\n#{result}" + end + end + + def ensure_db_config + config_path = 'config/database.yml' + unless File.exists?(config_path) + message = <<-EOF + ##################################################### + Could not find #{config_path} + + You can get rake to generate this file for you using either of: + rake rspec:generate_mysql_config + rake rspec:generate_sqlite3_config + + If you use mysql, you'll need to create dev and test + databases and users for each. To do this, standing + in rspec_on_rails, log into mysql as root and then... + mysql> source db/mysql_setup.sql; + + There is also a teardown script that will remove + the databases and users: + mysql> source db/mysql_teardown.sql; + ##################################################### + EOF + raise message.gsub(/^ /, '') + end + end + + def generate_mysql_config + copy 'config/database.mysql.yml', 'config/database.yml' + end + + def generate_sqlite3_config + copy 'config/database.sqlite3.yml', 'config/database.yml' + end + + def clobber_db_config + rm 'config/database.yml' + end + + def clobber_sqlite_data + rm_rf 'db/*.db' + end + + def generate_purchase + generator = "ruby script/generate rspec_scaffold purchase order_id:integer created_at:datetime amount:decimal keyword:string description:text --force" + notice = <<-EOF + ##################################################### + #{generator} + ##################################################### + EOF + puts notice.gsub(/^ /, '') + result = silent_sh(generator) + if error_code? || result =~ /not/ + raise "rspec_scaffold failed. #{result}" + end + end + + def purchase_migration_version + "005" + end + + def migrate_up + rake_sh "db:migrate" + end + + def destroy_purchase + migrate_down + rm_generated_purchase_files + end + + def migrate_down + notice = <<-EOF + ##################################################### + Migrating down and reverting config/routes.rb + ##################################################### + EOF + puts notice.gsub(/^ /, '') + rake_sh "db:migrate", 'VERSION' => (purchase_migration_version.to_i - 1) + output = silent_sh("cp config/routes.rb.bak config/routes.rb") + raise "revert failed: #{output}" if error_code? + end + + def rm_generated_purchase_files + puts "#####################################################" + puts "Removing generated files:" + generated_files = %W{ + app/helpers/purchases_helper.rb + app/models/purchase.rb + app/controllers/purchases_controller.rb + app/views/purchases + db/migrate/#{purchase_migration_version}_create_purchases.rb + spec/models/purchase_spec.rb + spec/helpers/purchases_helper_spec.rb + spec/controllers/purchases_controller_spec.rb + spec/controllers/purchases_routing_spec.rb + spec/fixtures/purchases.yml + spec/views/purchases + } + generated_files.each do |file| + rm_rf file + end + puts "#####################################################" + end + + def generate_login_controller + generator = "ruby script/generate rspec_controller login signup login logout --force" + notice = <<-EOF + ##################################################### + #{generator} + ##################################################### + EOF + puts notice.gsub(/^ /, '') + result = silent_sh(generator) + if error_code? || result =~ /not/ + raise "rspec_scaffold failed. #{result}" + end + end + + def rm_generated_login_controller_files + puts "#####################################################" + puts "Removing generated files:" + generated_files = %W{ + app/helpers/login_helper.rb + app/controllers/login_controller.rb + app/views/login + spec/helpers/login_helper_spec.rb + spec/controllers/login_controller_spec.rb + spec/views/login + } + generated_files.each do |file| + rm_rf file + end + puts "#####################################################" + end + + def install_dependencies + VENDOR_DEPS.each do |dep| + puts "\nChecking for #{dep[:name]} ..." + dest = dep[:checkout_path] + if File.exists?(dest) + puts "#{dep[:name]} already installed" + else + cmd = "svn co #{dep[:url]} #{dest}" + puts "Installing #{dep[:name]}" + puts "This may take a while." + puts cmd + system(cmd) + puts "Done!" + end + end + puts + end + + def check_dependencies + VENDOR_DEPS.each do |dep| + unless File.exist?(dep[:checkout_path]) + raise "There is no checkout of #{dep[:checkout_path]}. Please run rake install_dependencies" + end + # Verify that the current working copy is right + if `svn info #{dep[:checkout_path]}` =~ /^URL: (.*)/ + actual_url = $1 + if actual_url != dep[:url] + raise "Your working copy in #{dep[:checkout_path]} points to \n#{actual_url}\nIt has moved to\n#{dep[:url]}\nPlease delete the working copy and run rake install_dependencies" + end + end + end + end + + def update_dependencies + check_dependencies + VENDOR_DEPS.each do |dep| + next if dep[:tagged?] # + puts "\nUpdating #{dep[:name]} ..." + dest = dep[:checkout_path] + system("svn cleanup #{dest}") + cmd = "svn up #{dest}" + puts cmd + system(cmd) + puts "Done!" + end + end + + VENDOR_DEPS = [ + { + :checkout_path => "vendor/rails/2.0.2", + :name => "rails 2.0.2", + :url => "http://dev.rubyonrails.org/svn/rails/tags/rel_2-0-2", + :tagged? => true + }, + { + :checkout_path => "vendor/rails/1.2.6", + :name => "rails 1.2.6", + :url => "http://dev.rubyonrails.org/svn/rails/tags/rel_1-2-6", + :tagged? => true + }, + { + :checkout_path => "vendor/rails/1.2.3", + :name => "rails 1.2.3", + :url => "http://dev.rubyonrails.org/svn/rails/tags/rel_1-2-3", + :tagged? => true + }, + { + :checkout_path => "vendor/rails/edge", + :name => "edge rails", + :url => "http://svn.rubyonrails.org/rails/trunk", + :tagged? => false + } + ] +end diff --git a/vendor/gems/rspec/pre_commit/spec/pre_commit/pre_commit_spec.rb b/vendor/gems/rspec/pre_commit/spec/pre_commit/pre_commit_spec.rb new file mode 100644 index 0000000000..5d1c8f9b95 --- /dev/null +++ b/vendor/gems/rspec/pre_commit/spec/pre_commit/pre_commit_spec.rb @@ -0,0 +1,15 @@ +require File.dirname(__FILE__) + '/../spec_helper.rb' + +## +# This is not a complete specification of PreCommit, but +# just a collection of bug fix regression tests. +describe "The helper method PreCommit#silent_sh" do + before do + @pre_commit = PreCommit.new(nil) + end + + # bug in r1802 + it "should return the command output" do + @pre_commit.send(:silent_sh, "echo foo").should ==("foo\n") + end +end diff --git a/vendor/gems/rspec/pre_commit/spec/pre_commit/rspec_on_rails_spec.rb b/vendor/gems/rspec/pre_commit/spec/pre_commit/rspec_on_rails_spec.rb new file mode 100644 index 0000000000..1932fff865 --- /dev/null +++ b/vendor/gems/rspec/pre_commit/spec/pre_commit/rspec_on_rails_spec.rb @@ -0,0 +1,36 @@ +require File.dirname(__FILE__) + '/../spec_helper.rb' +require 'fileutils' + +include FileUtils + +## +# This is not a complete specification of PreCommit.RSpecOnRails, but +# just a collection of bug fix regression tests. +describe "RSpecOnRails pre_commit" do + before do + @original_dir = File.expand_path(FileUtils.pwd) + @rails_app_dir = File.expand_path(File.dirname(__FILE__) + "/../../../example_rails_app/") + + Dir.chdir(@rails_app_dir) + rm_rf('vendor/plugins/rspec_on_rails') + system("svn export ../rspec_on_rails vendor/plugins/rspec_on_rails") + + @pre_commit = PreCommit::RspecOnRails.new(nil) + end + + after do + rm('db/migrate/888_create_purchases.rb', :force => true) + @pre_commit.destroy_purchase + Dir.chdir(@original_dir) + end + + # bug in r1802 + it "should fail noisily if there is a migration name conflict" do + touch('db/migrate/888_create_purchases.rb') + lambda { @pre_commit.generate_purchase }.should raise_error + end + + it "should not fail if tests run ok" do + lambda { @pre_commit.generate_purchase }.should_not raise_error + end +end diff --git a/vendor/gems/rspec/pre_commit/spec/spec_helper.rb b/vendor/gems/rspec/pre_commit/spec/spec_helper.rb new file mode 100644 index 0000000000..b7e5f3d44d --- /dev/null +++ b/vendor/gems/rspec/pre_commit/spec/spec_helper.rb @@ -0,0 +1,3 @@ +$LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib' +require 'pre_commit' + diff --git a/vendor/gems/rspec/pre_commit/spec/spec_suite.rb b/vendor/gems/rspec/pre_commit/spec/spec_suite.rb new file mode 100644 index 0000000000..a8c7c07ace --- /dev/null +++ b/vendor/gems/rspec/pre_commit/spec/spec_suite.rb @@ -0,0 +1,11 @@ +class SpecSuite + def run + system("ruby rspec/spec/rspec_suite.rb") || raise("Rspec Suite FAILED") + system("ruby rspec_on_rails/spec/rails_suite.rb") || raise("Rails Suite FAILED") + system("ruby cached_example_rails_app/spec/rails_app_suite.rb") || raise("Rails App Suite FAILED") + end +end + +if $0 == __FILE__ + SpecSuite.new.run +end diff --git a/vendor/gems/rspec/rake_tasks/examples.rake b/vendor/gems/rspec/rake_tasks/examples.rake new file mode 100644 index 0000000000..32d0ad0e69 --- /dev/null +++ b/vendor/gems/rspec/rake_tasks/examples.rake @@ -0,0 +1,7 @@ +require 'rake' +require 'spec/rake/spectask' + +desc "Run all examples" +Spec::Rake::SpecTask.new('examples') do |t| + t.spec_files = FileList['examples/**/*.rb'] +end diff --git a/vendor/gems/rspec/rake_tasks/examples_with_rcov.rake b/vendor/gems/rspec/rake_tasks/examples_with_rcov.rake new file mode 100644 index 0000000000..4bf35c6b8b --- /dev/null +++ b/vendor/gems/rspec/rake_tasks/examples_with_rcov.rake @@ -0,0 +1,9 @@ +require 'rake' +require 'spec/rake/spectask' + +desc "Run all examples with RCov" +Spec::Rake::SpecTask.new('examples_with_rcov') do |t| + t.spec_files = FileList['examples/**/*.rb'] + t.rcov = true + t.rcov_opts = ['--exclude', 'examples'] +end \ No newline at end of file diff --git a/vendor/gems/rspec/rake_tasks/failing_examples_with_html.rake b/vendor/gems/rspec/rake_tasks/failing_examples_with_html.rake new file mode 100644 index 0000000000..34549583dd --- /dev/null +++ b/vendor/gems/rspec/rake_tasks/failing_examples_with_html.rake @@ -0,0 +1,9 @@ +require 'rake' +require 'spec/rake/spectask' + +desc "Generate HTML report for failing examples" +Spec::Rake::SpecTask.new('failing_examples_with_html') do |t| + t.spec_files = FileList['failing_examples/**/*.rb'] + t.spec_opts = ["--format", "html:../doc/output/documentation/tools/failing_examples.html", "--diff"] + t.fail_on_error = false +end \ No newline at end of file diff --git a/vendor/gems/rspec/rake_tasks/verify_rcov.rake b/vendor/gems/rspec/rake_tasks/verify_rcov.rake new file mode 100644 index 0000000000..a90a266df4 --- /dev/null +++ b/vendor/gems/rspec/rake_tasks/verify_rcov.rake @@ -0,0 +1,7 @@ +require 'rake' +require 'spec/rake/verify_rcov' + +RCov::VerifyTask.new(:verify_rcov => :spec) do |t| + t.threshold = 100.0 # Make sure you have rcov 0.7 or higher! + t.index_html = '../doc/output/coverage/index.html' +end diff --git a/vendor/gems/rspec/spec/README.jruby b/vendor/gems/rspec/spec/README.jruby new file mode 100644 index 0000000000..7eddb5671a --- /dev/null +++ b/vendor/gems/rspec/spec/README.jruby @@ -0,0 +1,15 @@ += Running specs on JRuby = + +svn co http://svn.codehaus.org/jruby/trunk jruby +cd jruby/jruby +ant clean +ant +# put JRuby's bin dir on your PATH +jruby -S gem install rake --no-ri --no-rdoc +jruby -S gem install diff-lcs +jruby -S gem install syntax +cd ../testsuites/rspec +mkdir target +jruby -S rake checkout_code +cd target/rspec +jruby bin/spec spec -c diff --git a/vendor/gems/rspec/spec/autotest/discover_spec.rb b/vendor/gems/rspec/spec/autotest/discover_spec.rb new file mode 100644 index 0000000000..da5cb14456 --- /dev/null +++ b/vendor/gems/rspec/spec/autotest/discover_spec.rb @@ -0,0 +1,19 @@ +require File.dirname(__FILE__) + "/../autotest_helper" + +module DiscoveryHelper + def load_discovery + require File.dirname(__FILE__) + "/../../lib/autotest/discover" + end +end + + +class Autotest + describe Rspec, "discovery" do + include DiscoveryHelper + + it "should add the rspec autotest plugin" do + Autotest.should_receive(:add_discovery).and_yield + load_discovery + end + end +end diff --git a/vendor/gems/rspec/spec/autotest/rspec_spec.rb b/vendor/gems/rspec/spec/autotest/rspec_spec.rb new file mode 100644 index 0000000000..67ee7fbef5 --- /dev/null +++ b/vendor/gems/rspec/spec/autotest/rspec_spec.rb @@ -0,0 +1,195 @@ +require File.dirname(__FILE__) + "/../autotest_helper" + +class Autotest + + module AutotestHelper + def rspec_output + <<-HERE +.............PPF + +1) +'false should be false' FAILED +expected: true, + got: false (using ==) +./spec/autotest/rspec_spec.rb:203: + +Finished in 0.158674 seconds + +16 examples, 1 failure, 2 pending + +Pending: +Autotest::Rspec handling failed results should return an array of failed examples and errors (TODO) +Autotest::Rspec tests/specs for a given file should find all the specs for a given file (TODO) +HERE + end + + + def common_setup + @proc = mock Proc + @kernel = mock Kernel + @kernel.stub!(:proc).and_return @proc + + File.stub!(:exists).and_return true + @windows_alt_separator = "\\" + @posix_separator = '/' + + @rspec_output = rspec_output + end + end + + describe Rspec, "rspec_commands" do + it "should contain the various commands, ordered by preference" do + Rspec.new.spec_commands.should == [ + File.expand_path("#{File.dirname(__FILE__)}/../../bin/spec"), + "#{Config::CONFIG['bindir']}/spec" + ] + end + end + + describe Rspec, "selection of rspec command" do + include AutotestHelper + + before :each do + common_setup + @rspec_autotest = Rspec.new + end + + it "should try to find the spec command if it exists in ./bin and use it above everything else" do + File.stub!(:exists?).and_return true + + spec_path = File.expand_path("#{File.dirname(__FILE__)}/../../bin/spec") + File.should_receive(:exists?).with(spec_path).and_return true + @rspec_autotest.spec_command.should == spec_path + end + + it "should otherwise select the default spec command in gem_dir/bin/spec" do + @rspec_autotest.stub!(:spec_commands).and_return ["/foo/spec"] + Config::CONFIG.stub!(:[]).and_return "/foo" + File.should_receive(:exists?).with("/foo/spec").and_return(true) + + @rspec_autotest.spec_command.should == "/foo/spec" + end + + it "should raise an error if no spec command is found at all" do + File.stub!(:exists?).and_return false + + lambda { + @rspec_autotest.spec_command + }.should raise_error(RspecCommandError, "No spec command could be found!") + end + + end + + describe Rspec, "selection of rspec command (windows compatibility issues)" do + include AutotestHelper + + before :each do + common_setup + end + + it "should use the ALT_SEPARATOR if it is non-nil" do + @rspec_autotest = Rspec.new + spec_command = File.expand_path("#{File.dirname(__FILE__)}/../../bin/spec") + @rspec_autotest.stub!(:spec_commands).and_return [spec_command] + @rspec_autotest.spec_command(@windows_alt_separator).should == spec_command.gsub('/', @windows_alt_separator) + end + + it "should not use the ALT_SEPATOR if it is nil" do + @windows_alt_separator = nil + @rspec_autotest = Rspec.new + spec_command = File.expand_path("#{File.dirname(__FILE__)}/../../bin/spec") + @rspec_autotest.stub!(:spec_commands).and_return [spec_command] + @rspec_autotest.spec_command.should == spec_command + end + end + + describe Rspec, "adding spec.opts --options" do + before :each do + @rspec_autotest = Rspec.new + end + + it "should return the command line option to add spec.opts if the options file exists" do + File.stub!(:exist?).and_return true + @rspec_autotest.add_options_if_present.should == "-O spec/spec.opts " + end + + it "should return an empty string if no spec.opts exists" do + File.stub!(:exist?).and_return false + Rspec.new.add_options_if_present.should == "" + end + end + + describe Rspec do + before :each do + @rspec_autotest = Rspec.new + @rspec_autotest.stub!(:ruby).and_return "ruby" + @rspec_autotest.stub!(:add_options_if_present).and_return "-O spec/spec.opts" + + @ruby = @rspec_autotest.ruby + @spec_command = @rspec_autotest.spec_command + @options = @rspec_autotest.add_options_if_present + @files_to_test = { + :spec => ["file_one", "file_two"] + } + # this is not the inner representation of Autotest! + @rspec_autotest.stub!(:files_to_test).and_return @files_to_test + @files_to_test.stub!(:keys).and_return @files_to_test[:spec] + @to_test = @files_to_test.keys.flatten.join ' ' + end + + it "should make the apropriate test command" do + @rspec_autotest.make_test_cmd(@files_to_test).should == "#{@ruby} -S #{@spec_command} #{@options} #{@to_test}" + end + end + + describe Rspec, "mappings" do + + before(:each) do + @lib_file = "lib/something.rb" + @spec_file = "spec/something_spec.rb" + @rspec_autotest = Rspec.new + @rspec_autotest.hook :initialize + end + + it "should find the spec file for a given lib file" do + @rspec_autotest.should map_specs([@spec_file]).to(@lib_file) + end + + it "should find the spec file if given a spec file" do + @rspec_autotest.should map_specs([@spec_file]).to(@spec_file) + end + + it "should only find the file if the file is being tracked (in @file)" do + @rspec_autotest.should map_specs([]).to("lib/untracked_file") + end + end + + describe Rspec, "consolidating failures" do + include AutotestHelper + + before :each do + common_setup + @rspec_autotest = Rspec.new + + @spec_file = "./spec/autotest/rspec_spec.rb" + @rspec_autotest.instance_variable_set("@files", {@spec_file => Time.now}) + @rspec_autotest.stub!(:find_files_to_test).and_return true + end + + it "should return no failures if no failures were given in the output" do + @rspec_autotest.consolidate_failures([[]]).should == {} + end + + it "should return a hash with the spec filename => spec name for each failure or error" do + @rspec_autotest.stub!(:test_files_for).and_return "./spec/autotest/rspec_spec.rb" + foo = [ + [ + "false should be false", + "expected: true,\n got: false (using ==)\n./spec/autotest/rspec_spec.rb:203:" + ] + ] + @rspec_autotest.consolidate_failures(foo).should == {@spec_file => ["false should be false"]} + end + + end +end diff --git a/vendor/gems/rspec/spec/autotest_helper.rb b/vendor/gems/rspec/spec/autotest_helper.rb new file mode 100644 index 0000000000..1b6c6002b3 --- /dev/null +++ b/vendor/gems/rspec/spec/autotest_helper.rb @@ -0,0 +1,6 @@ +require "rubygems" +require 'autotest' +dir = File.dirname(__FILE__) +require "#{dir}/spec_helper" +require File.expand_path("#{dir}/../lib/autotest/rspec") +require "#{dir}/autotest_matchers" diff --git a/vendor/gems/rspec/spec/autotest_matchers.rb b/vendor/gems/rspec/spec/autotest_matchers.rb new file mode 100644 index 0000000000..5e23452e26 --- /dev/null +++ b/vendor/gems/rspec/spec/autotest_matchers.rb @@ -0,0 +1,47 @@ +module Spec + module Matchers + class AutotestMappingMatcher + def initialize(specs) + @specs = specs + end + + def to(file) + @file = file + self + end + + def matches?(autotest) + @autotest = prepare autotest + @actual = autotest.test_files_for(@file) + @actual == @specs + end + + def failure_message + "expected #{@autotest.class} to map #{@specs.inspect} to #{@file.inspect}\ngot #{@actual.inspect}" + end + + private + def prepare autotest + stub_found_files autotest + stub_find_order autotest + autotest + end + + def stub_found_files autotest + found_files = @specs.inject({}){|h,f| h[f] = Time.at(0)} + autotest.stub!(:find_files).and_return(found_files) + end + + def stub_find_order autotest + find_order = @specs.dup << @file + autotest.instance_eval { @find_order = find_order } + end + + end + + def map_specs(specs) + AutotestMappingMatcher.new(specs) + end + + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/rspec_suite.rb b/vendor/gems/rspec/spec/rspec_suite.rb new file mode 100644 index 0000000000..abd016a6df --- /dev/null +++ b/vendor/gems/rspec/spec/rspec_suite.rb @@ -0,0 +1,7 @@ +if __FILE__ == $0 + dir = File.dirname(__FILE__) + Dir["#{dir}/**/*_spec.rb"].reverse.each do |file| +# puts "require '#{file}'" + require file + end +end diff --git a/vendor/gems/rspec/spec/ruby_forker.rb b/vendor/gems/rspec/spec/ruby_forker.rb new file mode 100644 index 0000000000..6ab038750c --- /dev/null +++ b/vendor/gems/rspec/spec/ruby_forker.rb @@ -0,0 +1,13 @@ +require 'rbconfig' + +module RubyForker + # Forks a ruby interpreter with same type as ourself. + # juby will fork jruby, ruby will fork ruby etc. + def ruby(args, stderr=nil) + config = ::Config::CONFIG + interpreter = File::join(config['bindir'], config['ruby_install_name']) + config['EXEEXT'] + cmd = "#{interpreter} #{args}" + cmd << " 2> #{stderr}" unless stderr.nil? + `#{cmd}` + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec.opts b/vendor/gems/rspec/spec/spec.opts new file mode 100644 index 0000000000..48e51f93b9 --- /dev/null +++ b/vendor/gems/rspec/spec/spec.opts @@ -0,0 +1,6 @@ +--colour +--format +profile +--timeout +20 +--diff \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/example/configuration_spec.rb b/vendor/gems/rspec/spec/spec/example/configuration_spec.rb new file mode 100755 index 0000000000..5b4a6049e6 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/example/configuration_spec.rb @@ -0,0 +1,282 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +module Spec + module Example + + describe Configuration do + before(:each) do + @config = Configuration.new + @example_group = mock("example_group") + end + + describe "#mock_with" do + + it "should default mock framework to rspec" do + @config.mock_framework.should =~ /\/plugins\/mock_frameworks\/rspec$/ + end + + it "should let you set rspec mocking explicitly" do + @config.mock_with(:rspec) + @config.mock_framework.should =~ /\/plugins\/mock_frameworks\/rspec$/ + end + + it "should let you set mocha" do + @config.mock_with(:mocha) + @config.mock_framework.should =~ /\/plugins\/mock_frameworks\/mocha$/ + end + + it "should let you set flexmock" do + @config.mock_with(:flexmock) + @config.mock_framework.should =~ /\/plugins\/mock_frameworks\/flexmock$/ + end + + it "should let you set rr" do + @config.mock_with(:rr) + @config.mock_framework.should =~ /\/plugins\/mock_frameworks\/rr$/ + end + + it "should let you set an arbitrary adapter module" do + adapter = Module.new + @config.mock_with(adapter) + @config.mock_framework.should == adapter + end + end + + describe "#include" do + + before do + @original_configuration = Spec::Runner.configuration + spec_configuration = @config + Spec::Runner.instance_eval {@configuration = spec_configuration} + @example_group_class = Class.new(ExampleGroup) do + class << self + def this_class_has_special_methods + end + end + end + ExampleGroupFactory.register(:foobar, @example_group_class) + end + + after do + original_configuration = @original_configuration + Spec::Runner.instance_eval {@configuration = original_configuration} + ExampleGroupFactory.reset + end + + it "should include the submitted module in ExampleGroup subclasses" do + mod = Module.new + @config.include mod + Class.new(@example_group_class).included_modules.should include(mod) + end + + it "should let you define modules to be included for a specific type" do + mod = Module.new + @config.include mod, :type => :foobar + Class.new(@example_group_class).included_modules.should include(mod) + end + + it "should not include modules in a type they are not intended for" do + mod = Module.new + @other_example_group_class = Class.new(ExampleGroup) + ExampleGroupFactory.register(:baz, @other_example_group_class) + + @config.include mod, :type => :foobar + + Class.new(@other_example_group_class).included_modules.should_not include(mod) + end + + end + + end + + describe Configuration do + + before(:each) do + @config = Configuration.new + @special_example_group = Class.new(ExampleGroup) + @special_child_example_group = Class.new(@special_example_group) + @nonspecial_example_group = Class.new(ExampleGroup) + ExampleGroupFactory.register(:special, @special_example_group) + ExampleGroupFactory.register(:special_child, @special_child_example_group) + ExampleGroupFactory.register(:non_special, @nonspecial_example_group) + @example_group = @special_child_example_group.describe "Special Example Group" + @unselected_example_group = Class.new(@nonspecial_example_group).describe "Non Special Example Group" + end + + after(:each) do + ExampleGroupFactory.reset + end + + describe "#prepend_before" do + it "prepends the before block on all instances of the passed in type" do + order = [] + @config.prepend_before(:all) do + order << :prepend__before_all + end + @config.prepend_before(:all, :type => :special) do + order << :special_prepend__before_all + end + @config.prepend_before(:all, :type => :special_child) do + order << :special_child_prepend__before_all + end + @config.prepend_before(:each) do + order << :prepend__before_each + end + @config.prepend_before(:each, :type => :special) do + order << :special_prepend__before_each + end + @config.prepend_before(:each, :type => :special_child) do + order << :special_child_prepend__before_each + end + @config.prepend_before(:all, :type => :non_special) do + order << :special_prepend__before_all + end + @config.prepend_before(:each, :type => :non_special) do + order << :special_prepend__before_each + end + @example_group.it "calls prepend_before" do + end + + @example_group.run + order.should == [ + :prepend__before_all, + :special_prepend__before_all, + :special_child_prepend__before_all, + :prepend__before_each, + :special_prepend__before_each, + :special_child_prepend__before_each + ] + end + end + + describe "#append_before" do + + it "calls append_before on the type" do + order = [] + @config.append_before(:all) do + order << :append_before_all + end + @config.append_before(:all, :type => :special) do + order << :special_append_before_all + end + @config.append_before(:all, :type => :special_child) do + order << :special_child_append_before_all + end + @config.append_before(:each) do + order << :append_before_each + end + @config.append_before(:each, :type => :special) do + order << :special_append_before_each + end + @config.append_before(:each, :type => :special_child) do + order << :special_child_append_before_each + end + @config.append_before(:all, :type => :non_special) do + order << :special_append_before_all + end + @config.append_before(:each, :type => :non_special) do + order << :special_append_before_each + end + @example_group.it "calls append_before" do + end + + @example_group.run + order.should == [ + :append_before_all, + :special_append_before_all, + :special_child_append_before_all, + :append_before_each, + :special_append_before_each, + :special_child_append_before_each + ] + end + end + + describe "#prepend_after" do + + it "prepends the after block on all instances of the passed in type" do + order = [] + @config.prepend_after(:all) do + order << :prepend__after_all + end + @config.prepend_after(:all, :type => :special) do + order << :special_prepend__after_all + end + @config.prepend_after(:all, :type => :special) do + order << :special_child_prepend__after_all + end + @config.prepend_after(:each) do + order << :prepend__after_each + end + @config.prepend_after(:each, :type => :special) do + order << :special_prepend__after_each + end + @config.prepend_after(:each, :type => :special) do + order << :special_child_prepend__after_each + end + @config.prepend_after(:all, :type => :non_special) do + order << :special_prepend__after_all + end + @config.prepend_after(:each, :type => :non_special) do + order << :special_prepend__after_each + end + @example_group.it "calls prepend_after" do + end + + @example_group.run + order.should == [ + :special_child_prepend__after_each, + :special_prepend__after_each, + :prepend__after_each, + :special_child_prepend__after_all, + :special_prepend__after_all, + :prepend__after_all + ] + end + end + + describe "#append_after" do + + it "calls append_after on the type" do + order = [] + @config.append_after(:all) do + order << :append__after_all + end + @config.append_after(:all, :type => :special) do + order << :special_append__after_all + end + @config.append_after(:all, :type => :special_child) do + order << :special_child_append__after_all + end + @config.append_after(:each) do + order << :append__after_each + end + @config.append_after(:each, :type => :special) do + order << :special_append__after_each + end + @config.append_after(:each, :type => :special_child) do + order << :special_child_append__after_each + end + @config.append_after(:all, :type => :non_special) do + order << :non_special_append_after_all + end + @config.append_after(:each, :type => :non_special) do + order << :non_special_append_after_each + end + @example_group.it "calls append_after" do + end + + @example_group.run + order.should == [ + :special_child_append__after_each, + :special_append__after_each, + :append__after_each, + :special_child_append__after_all, + :special_append__after_all, + :append__after_all + ] + end + end + end + end +end diff --git a/vendor/gems/rspec/spec/spec/example/example_group_class_definition_spec.rb b/vendor/gems/rspec/spec/spec/example/example_group_class_definition_spec.rb new file mode 100644 index 0000000000..0b00e13974 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/example/example_group_class_definition_spec.rb @@ -0,0 +1,48 @@ +require File.dirname(__FILE__) + '/../../spec_helper' + +module Spec + module Example + class ExampleGroupSubclass < ExampleGroup + class << self + attr_accessor :examples_ran + end + + @@klass_variable_set = true + CONSTANT = :foobar + + before do + @instance_variable = :hello + end + + it "should run" do + self.class.examples_ran = true + end + + it "should have access to instance variables" do + @instance_variable.should == :hello + end + + it "should have access to class variables" do + @@klass_variable_set.should == true + end + + it "should have access to constants" do + CONSTANT.should == :foobar + end + + it "should have access to methods defined in the Example Group" do + a_method.should == 22 + end + + def a_method + 22 + end + end + + describe ExampleGroupSubclass do + it "should run" do + ExampleGroupSubclass.examples_ran.should be_true + end + end + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/example/example_group_factory_spec.rb b/vendor/gems/rspec/spec/spec/example/example_group_factory_spec.rb new file mode 100644 index 0000000000..3b50011f70 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/example/example_group_factory_spec.rb @@ -0,0 +1,129 @@ +require File.dirname(__FILE__) + '/../../spec_helper' + +module Spec + module Example + describe ExampleGroupFactory, "with :foobar registered as custom type" do + + before do + @example_group = Class.new(ExampleGroup) + ExampleGroupFactory.register(:foobar, @example_group) + end + + after do + ExampleGroupFactory.reset + end + + it "should #get the default ExampleGroup type when passed nil" do + ExampleGroupFactory.get(nil).should == ExampleGroup + end + + it "should #get the default ExampleGroup for unregistered non-nil values" do + ExampleGroupFactory.get(:does_not_exist).should == ExampleGroup + end + + it "should #get custom type for :foobar" do + ExampleGroupFactory.get(:foobar).should == @example_group + end + + it "should #get the actual type when that is passed in" do + ExampleGroupFactory.get(@example_group).should == @example_group + end + + end + + describe ExampleGroupFactory, "#create_example_group" do + it "should create a uniquely named class" do + example_group = Spec::Example::ExampleGroupFactory.create_example_group("example_group") {} + example_group.name.should =~ /Spec::Example::ExampleGroup::Subclass_\d+/ + end + + it "should create a Spec::Example::Example subclass by default" do + example_group = Spec::Example::ExampleGroupFactory.create_example_group("example_group") {} + example_group.superclass.should == Spec::Example::ExampleGroup + end + + it "should create a Spec::Example::Example when :type => :default" do + example_group = Spec::Example::ExampleGroupFactory.create_example_group( + "example_group", :type => :default + ) {} + example_group.superclass.should == Spec::Example::ExampleGroup + end + + it "should create a Spec::Example::Example when :type => :default" do + example_group = Spec::Example::ExampleGroupFactory.create_example_group( + "example_group", :type => :default + ) {} + example_group.superclass.should == Spec::Example::ExampleGroup + end + + it "should create specified type when :type => :something_other_than_default" do + klass = Class.new(ExampleGroup) do + def initialize(*args, &block); end + end + Spec::Example::ExampleGroupFactory.register(:something_other_than_default, klass) + example_group = Spec::Example::ExampleGroupFactory.create_example_group( + "example_group", :type => :something_other_than_default + ) {} + example_group.superclass.should == klass + end + + it "should create a type indicated by :spec_path" do + klass = Class.new(ExampleGroup) do + def initialize(*args, &block); end + end + Spec::Example::ExampleGroupFactory.register(:something_other_than_default, klass) + example_group = Spec::Example::ExampleGroupFactory.create_example_group( + "example_group", :spec_path => "./spec/something_other_than_default/some_spec.rb" + ) {} + example_group.superclass.should == klass + end + + it "should create a type indicated by :spec_path (with spec_path generated by caller on windows)" do + klass = Class.new(ExampleGroup) do + def initialize(*args, &block); end + end + Spec::Example::ExampleGroupFactory.register(:something_other_than_default, klass) + example_group = Spec::Example::ExampleGroupFactory.create_example_group( + "example_group", :spec_path => "./spec\\something_other_than_default\\some_spec.rb" + ) {} + example_group.superclass.should == klass + end + + it "should create and register a Spec::Example::Example if :shared => true" do + shared_example_group = Spec::Example::ExampleGroupFactory.create_example_group( + "name", :spec_path => '/blah/spec/models/blah.rb', :type => :controller, :shared => true + ) {} + shared_example_group.should be_an_instance_of(Spec::Example::SharedExampleGroup) + SharedExampleGroup.shared_example_groups.should include(shared_example_group) + end + + it "should favor the :type over the :spec_path" do + klass = Class.new(ExampleGroup) do + def initialize(*args, &block); end + end + Spec::Example::ExampleGroupFactory.register(:something_other_than_default, klass) + example_group = Spec::Example::ExampleGroupFactory.create_example_group( + "name", :spec_path => '/blah/spec/models/blah.rb', :type => :something_other_than_default + ) {} + example_group.superclass.should == klass + end + + it "should register ExampleGroup by default" do + example_group = Spec::Example::ExampleGroupFactory.create_example_group("The ExampleGroup") do + end + rspec_options.example_groups.should include(example_group) + end + + it "should enable unregistering of ExampleGroups" do + example_group = Spec::Example::ExampleGroupFactory.create_example_group("The ExampleGroup") do + unregister + end + rspec_options.example_groups.should_not include(example_group) + end + + after(:each) do + Spec::Example::ExampleGroupFactory.reset + end + end + end +end diff --git a/vendor/gems/rspec/spec/spec/example/example_group_methods_spec.rb b/vendor/gems/rspec/spec/spec/example/example_group_methods_spec.rb new file mode 100644 index 0000000000..2b6d660fe1 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/example/example_group_methods_spec.rb @@ -0,0 +1,489 @@ +require File.dirname(__FILE__) + '/../../spec_helper' + +module Spec + module Example + describe 'ExampleGroupMethods' do + it_should_behave_like "sandboxed rspec_options" + attr_reader :example_group, :result, :reporter + before(:each) do + options.formatters << mock("formatter", :null_object => true) + options.backtrace_tweaker = mock("backtrace_tweaker", :null_object => true) + @reporter = FakeReporter.new(@options) + options.reporter = reporter + @example_group = Class.new(ExampleGroup) do + describe("ExampleGroup") + it "does nothing" + end + class << example_group + public :include + end + @result = nil + end + + after(:each) do + ExampleGroup.reset + end + + describe "#describe" do + attr_reader :child_example_group + before do + @child_example_group = @example_group.describe("Another ExampleGroup") do + it "should pass" do + true.should be_true + end + end + end + + it "should create a subclass of the ExampleGroup when passed a block" do + child_example_group.superclass.should == @example_group + @options.example_groups.should include(child_example_group) + end + + it "should not inherit examples" do + child_example_group.examples.length.should == 1 + end + end + + describe "#it" do + it "should should create an example instance" do + lambda { + @example_group.it("") + }.should change { @example_group.examples.length }.by(1) + end + end + + describe "#xit" do + before(:each) do + Kernel.stub!(:warn) + end + + it "should NOT should create an example instance" do + lambda { + @example_group.xit("") + }.should_not change(@example_group.examples, :length) + end + + it "should warn that it is disabled" do + Kernel.should_receive(:warn).with("Example disabled: foo") + @example_group.xit("foo") + end + end + + describe "#examples" do + it "should have Examples" do + example_group = Class.new(ExampleGroup) do + describe('example') + it "should pass" do + 1.should == 1 + end + end + example_group.examples.length.should == 1 + example_group.examples.first.description.should == "should pass" + end + + it "should not include methods that begin with test (only when TU interop is loaded)" do + example_group = Class.new(ExampleGroup) do + describe('example') + def test_any_args(*args) + true.should be_true + end + def test_something + 1.should == 1 + end + def test + raise "This is not a real test" + end + def testify + raise "This is not a real test" + end + end + example_group.examples.length.should == 0 + example_group.run.should be_true + end + + it "should include methods that begin with should and has an arity of 0 in suite" do + example_group = Class.new(ExampleGroup) do + describe('example') + def shouldCamelCase + true.should be_true + end + def should_any_args(*args) + true.should be_true + end + def should_something + 1.should == 1 + end + def should_not_something + 1.should_not == 2 + end + def should + raise "This is not a real example" + end + def should_not + raise "This is not a real example" + end + end + example_group = example_group.dup + example_group.examples.length.should == 4 + descriptions = example_group.examples.collect {|example| example.description}.sort + descriptions.should include("shouldCamelCase") + descriptions.should include("should_any_args") + descriptions.should include("should_something") + descriptions.should include("should_not_something") + end + + it "should not include methods that begin with test_ and has an arity > 0 in suite" do + example_group = Class.new(ExampleGroup) do + describe('example') + def test_invalid(foo) + 1.should == 1 + end + def testInvalidCamelCase(foo) + 1.should == 1 + end + end + example_group.examples.length.should == 0 + end + + it "should not include methods that begin with should_ and has an arity > 0 in suite" do + example_group = Class.new(ExampleGroup) do + describe('example') + def should_invalid(foo) + 1.should == 2 + end + def shouldInvalidCamelCase(foo) + 1.should == 3 + end + def should_not_invalid(foo) + 1.should == 4 + end + def should_valid + 1.should == 1 + end + end + example_group.examples.length.should == 1 + example_group.run.should be_true + end + + it "should run should_methods" do + example_group = Class.new(ExampleGroup) do + def should_valid + 1.should == 2 + end + end + example_group.examples.length.should == 1 + example_group.run.should be_false + end + end + + describe "#set_description" do + attr_reader :example_group + before do + class << example_group + public :set_description + end + end + + describe "#set_description(String)" do + before(:each) do + example_group.set_description("abc") + end + + specify ".description should return the String passed into .set_description" do + example_group.description.should == "abc" + end + + specify ".described_type should provide nil as its type" do + example_group.described_type.should be_nil + end + end + + describe "#set_description(Type)" do + before(:each) do + example_group.set_description(ExampleGroup) + end + + specify ".description should return a String representation of that type (fully qualified) as its name" do + example_group.description.should == "Spec::Example::ExampleGroup" + end + + specify ".described_type should return the passed in type" do + example_group.described_type.should == Spec::Example::ExampleGroup + end + end + + describe "#set_description(String, Type)" do + before(:each) do + example_group.set_description("behaving", ExampleGroup) + end + + specify ".description should return String then space then Type" do + example_group.description.should == "behaving Spec::Example::ExampleGroup" + end + + specify ".described_type should return the passed in type" do + example_group.described_type.should == Spec::Example::ExampleGroup + end + end + + describe "#set_description(Type, String not starting with a space)" do + before(:each) do + example_group.set_description(ExampleGroup, "behaving") + end + + specify ".description should return the Type then space then String" do + example_group.description.should == "Spec::Example::ExampleGroup behaving" + end + end + + describe "#set_description(Type, String starting with .)" do + before(:each) do + example_group.set_description(ExampleGroup, ".behaving") + end + + specify ".description should return the Type then String" do + example_group.description.should == "Spec::Example::ExampleGroup.behaving" + end + end + + describe "#set_description(Type, String containing .)" do + before(:each) do + example_group.set_description(ExampleGroup, "calling a.b") + end + + specify ".description should return the Type then space then String" do + example_group.description.should == "Spec::Example::ExampleGroup calling a.b" + end + end + + describe "#set_description(Type, String starting with .)" do + before(:each) do + example_group.set_description(ExampleGroup, ".behaving") + end + + specify "should return the Type then String" do + example_group.description.should == "Spec::Example::ExampleGroup.behaving" + end + end + + describe "#set_description(Type, String containing .)" do + before(:each) do + example_group.set_description(ExampleGroup, "is #1") + end + + specify ".description should return the Type then space then String" do + example_group.description.should == "Spec::Example::ExampleGroup is #1" + end + end + + describe "#set_description(String, Type, String)" do + before(:each) do + example_group.set_description("A", Hash, "with one entry") + end + + specify ".description should return the first String then space then Type then second String" do + example_group.description.should == "A Hash with one entry" + end + end + + describe "#set_description(Hash representing options)" do + before(:each) do + example_group.set_description(:a => "b", :spec_path => "blah") + end + + it ".spec_path should expand the passed in :spec_path option passed into the constructor" do + example_group.spec_path.should == File.expand_path("blah") + end + + it ".description_options should return all the options passed in" do + example_group.description_options.should == {:a => "b", :spec_path => "blah"} + end + + end + end + + describe "#description" do + it "should return the same description instance for each call" do + example_group.description.should eql(example_group.description) + end + + it "should not add a space when description_text begins with #" do + child_example_group = Class.new(example_group) do + describe("#foobar", "Does something") + end + child_example_group.description.should == "ExampleGroup#foobar Does something" + end + + it "should not add a space when description_text begins with ." do + child_example_group = Class.new(example_group) do + describe(".foobar", "Does something") + end + child_example_group.description.should == "ExampleGroup.foobar Does something" + end + + it "should return the class name if nil" do + example_group.set_description(nil) + example_group.description.should =~ /Class:/ + end + + it "should return the class name if nil" do + example_group.set_description("") + example_group.description.should =~ /Class:/ + end + end + + describe "#description_parts" do + it "should return an Array of the current class description args" do + example_group.description_parts.should == [example_group.description] + end + + it "should return an Array of the description args from each class in the hierarchy" do + child_example_group = Class.new(example_group) + child_example_group.describe("Child", ExampleGroup) + child_example_group.description.should_not be_empty + + grand_child_example_group = Class.new(child_example_group) + grand_child_example_group.describe("GrandChild", ExampleGroup) + grand_child_example_group.description.should_not be_empty + + grand_child_example_group.description_parts.should == [ + "ExampleGroup", + "Child", + Spec::Example::ExampleGroup, + "GrandChild", + Spec::Example::ExampleGroup + ] + end + end + + describe "#described_type" do + it "should return passed in type" do + child_example_group = Class.new(example_group) do + describe Object + end + child_example_group.described_type.should == Object + end + + it "should return #described_type of superclass when no passed in type" do + parent_example_group = Class.new(ExampleGroup) do + describe Object, "#foobar" + end + child_example_group = Class.new(parent_example_group) do + describe "not a type" + end + child_example_group.described_type.should == Object + end + end + + describe "#remove_after" do + it "should unregister a given after(:each) block" do + after_all_ran = false + @example_group.it("example") {} + proc = Proc.new { after_all_ran = true } + ExampleGroup.after(:each, &proc) + @example_group.run + after_all_ran.should be_true + + after_all_ran = false + ExampleGroup.remove_after(:each, &proc) + @example_group.run + after_all_ran.should be_false + end + end + + describe "#include" do + it "should have accessible class methods from included module" do + mod1_method_called = false + mod1 = Module.new do + class_methods = Module.new do + define_method :mod1_method do + mod1_method_called = true + end + end + + metaclass.class_eval do + define_method(:included) do |receiver| + receiver.extend class_methods + end + end + end + + mod2_method_called = false + mod2 = Module.new do + class_methods = Module.new do + define_method :mod2_method do + mod2_method_called = true + end + end + + metaclass.class_eval do + define_method(:included) do |receiver| + receiver.extend class_methods + end + end + end + + @example_group.include mod1, mod2 + + @example_group.mod1_method + @example_group.mod2_method + mod1_method_called.should be_true + mod2_method_called.should be_true + end + end + + describe "#number_of_examples" do + it "should count number of specs" do + proc do + @example_group.it("one") {} + @example_group.it("two") {} + @example_group.it("three") {} + @example_group.it("four") {} + end.should change {@example_group.number_of_examples}.by(4) + end + end + + describe "#class_eval" do + it "should allow constants to be defined" do + example_group = Class.new(ExampleGroup) do + describe('example') + FOO = 1 + it "should reference FOO" do + FOO.should == 1 + end + end + example_group.run + Object.const_defined?(:FOO).should == false + end + end + + describe '#register' do + it "should add ExampleGroup to set of ExampleGroups to be run" do + options.example_groups.delete(example_group) + options.example_groups.should_not include(example_group) + + example_group.register {} + options.example_groups.should include(example_group) + end + end + + describe '#unregister' do + before do + options.example_groups.should include(example_group) + end + + it "should remove ExampleGroup from set of ExampleGroups to be run" do + example_group.unregister + options.example_groups.should_not include(example_group) + end + end + + describe "#registration_backtrace" do + it "returns the backtrace of where the ExampleGroup was registered" do + example_group = Class.new(ExampleGroup) + example_group.registration_backtrace.join("\n").should include("#{__FILE__}:#{__LINE__-1}") + end + end + end + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/example/example_group_spec.rb b/vendor/gems/rspec/spec/spec/example/example_group_spec.rb new file mode 100644 index 0000000000..93e558a97b --- /dev/null +++ b/vendor/gems/rspec/spec/spec/example/example_group_spec.rb @@ -0,0 +1,711 @@ +require File.dirname(__FILE__) + '/../../spec_helper' + +module Spec + module Example + class ExampleModuleScopingSpec < ExampleGroup + describe ExampleGroup, "via a class definition" + + module Foo + module Bar + def self.loaded? + true + end + end + end + include Foo + + it "should understand module scoping" do + Bar.should be_loaded + end + + @@foo = 1 + + it "should allow class variables to be defined" do + @@foo.should == 1 + end + end + + class ExampleClassVariablePollutionSpec < ExampleGroup + describe ExampleGroup, "via a class definition without a class variable" + + it "should not retain class variables from other Example classes" do + proc do + @@foo + end.should raise_error + end + end + + describe ExampleGroup, "#pending" do + it "should raise a Pending error when its block fails" do + block_ran = false + lambda { + pending("something") do + block_ran = true + raise "something wrong with my example" + end + }.should raise_error(Spec::Example::ExamplePendingError, "something") + block_ran.should == true + end + + it "should raise Spec::Example::PendingExampleFixedError when its block does not fail" do + block_ran = false + lambda { + pending("something") do + block_ran = true + end + }.should raise_error(Spec::Example::PendingExampleFixedError, "Expected pending 'something' to fail. No Error was raised.") + block_ran.should == true + end + end + + describe ExampleGroup, "#run with failure in example", :shared => true do + it "should add an example failure to the TestResult" do + example_group.run.should be_false + end + end + + describe ExampleGroup, "#run" do + it_should_behave_like "sandboxed rspec_options" + attr_reader :example_group, :formatter, :reporter + before :each do + @formatter = mock("formatter", :null_object => true) + options.formatters << formatter + options.backtrace_tweaker = mock("backtrace_tweaker", :null_object => true) + @reporter = FakeReporter.new(options) + options.reporter = reporter + @example_group = Class.new(ExampleGroup) do + describe("example") + it "does nothing" do + end + end + class << example_group + public :include + end + end + + after :each do + ExampleGroup.reset + end + + it "should not run when there are no examples" do + example_group = Class.new(ExampleGroup) do + describe("Foobar") + end + example_group.examples.should be_empty + + reporter = mock("Reporter") + reporter.should_not_receive(:add_example_group) + example_group.run + end + + describe "when before_each fails" do + before(:each) do + $example_ran = $after_each_ran = false + @example_group = describe("Foobar") do + before(:each) {raise} + it "should not be run" do + $example_ran = true + end + after(:each) do + $after_each_ran = true + end + end + end + + it "should not run example block" do + example_group.run + $example_ran.should be_false + end + + it "should run after_each" do + example_group.run + $after_each_ran.should be_true + end + + it "should report failure location when in before_each" do + reporter.should_receive(:example_finished) do |example_group, error| + error.message.should eql("in before_each") + end + example_group.run + end + end + + describe ExampleGroup, "#run on dry run" do + before do + @options.dry_run = true + end + + it "should not run before(:all) or after(:all)" do + before_all_ran = false + after_all_ran = false + ExampleGroup.before(:all) { before_all_ran = true } + ExampleGroup.after(:all) { after_all_ran = true } + example_group.it("should") {} + example_group.run + before_all_ran.should be_false + after_all_ran.should be_false + end + + it "should not run example" do + example_ran = false + example_group.it("should") {example_ran = true} + example_group.run + example_ran.should be_false + end + end + + describe ExampleGroup, "#run with specified examples" do + attr_reader :examples_that_were_run + before do + @examples_that_were_run = [] + end + + describe "when specified_examples matches entire ExampleGroup" do + before do + examples_that_were_run = @examples_that_were_run + @example_group = Class.new(ExampleGroup) do + describe("the ExampleGroup") + it("should be run") do + examples_that_were_run << 'should be run' + end + + it("should also be run") do + examples_that_were_run << 'should also be run' + end + end + options.examples = ["the ExampleGroup"] + end + + it "should not run the Examples in the ExampleGroup" do + example_group.run + examples_that_were_run.should == ['should be run', 'should also be run'] + end + end + + describe ExampleGroup, "#run when specified_examples matches only Example description" do + before do + examples_that_were_run = @examples_that_were_run + @example_group = Class.new(ExampleGroup) do + describe("example") + it("should be run") do + examples_that_were_run << 'should be run' + end + end + options.examples = ["should be run"] + end + + it "should not run the example" do + example_group.run + examples_that_were_run.should == ['should be run'] + end + end + + describe ExampleGroup, "#run when specified_examples does not match an Example description" do + before do + examples_that_were_run = @examples_that_were_run + @example_group = Class.new(ExampleGroup) do + describe("example") + it("should be something else") do + examples_that_were_run << 'should be something else' + end + end + options.examples = ["does not match anything"] + end + + it "should not run the example" do + example_group.run + examples_that_were_run.should == [] + end + end + + describe ExampleGroup, "#run when specified_examples matches an Example description" do + before do + examples_that_were_run = @examples_that_were_run + @example_group = Class.new(ExampleGroup) do + describe("example") + it("should be run") do + examples_that_were_run << 'should be run' + end + it("should not be run") do + examples_that_were_run << 'should not be run' + end + end + options.examples = ["should be run"] + end + + it "should run only the example, when there in only one" do + example_group.run + examples_that_were_run.should == ["should be run"] + end + + it "should run only the one example" do + example_group.run + examples_that_were_run.should == ["should be run"] end + end + end + + describe ExampleGroup, "#run with success" do + before do + @special_example_group = Class.new(ExampleGroup) + ExampleGroupFactory.register(:special, @special_example_group) + @not_special_example_group = Class.new(ExampleGroup) + ExampleGroupFactory.register(:not_special, @not_special_example_group) + end + + after do + ExampleGroupFactory.reset + end + + it "should send reporter add_example_group" do + example_group.run + reporter.example_groups.should == [example_group] + end + + it "should run example on run" do + example_ran = false + example_group.it("should") {example_ran = true} + example_group.run + example_ran.should be_true + end + + it "should run before(:all) block only once" do + before_all_run_count_run_count = 0 + example_group.before(:all) {before_all_run_count_run_count += 1} + example_group.it("test") {true} + example_group.it("test2") {true} + example_group.run + before_all_run_count_run_count.should == 1 + end + + it "should run after(:all) block only once" do + after_all_run_count = 0 + example_group.after(:all) {after_all_run_count += 1} + example_group.it("test") {true} + example_group.it("test2") {true} + example_group.run + after_all_run_count.should == 1 + @reporter.rspec_verify + end + + it "after(:all) should have access to all instance variables defined in before(:all)" do + context_instance_value_in = "Hello there" + context_instance_value_out = "" + example_group.before(:all) { @instance_var = context_instance_value_in } + example_group.after(:all) { context_instance_value_out = @instance_var } + example_group.it("test") {true} + example_group.run + context_instance_value_in.should == context_instance_value_out + end + + it "should copy instance variables from before(:all)'s execution context into spec's execution context" do + context_instance_value_in = "Hello there" + context_instance_value_out = "" + example_group.before(:all) { @instance_var = context_instance_value_in } + example_group.it("test") {context_instance_value_out = @instance_var} + example_group.run + context_instance_value_in.should == context_instance_value_out + end + + it "should not add global before callbacks for untargetted example_group" do + fiddle = [] + + ExampleGroup.before(:all) { fiddle << "Example.before(:all)" } + ExampleGroup.prepend_before(:all) { fiddle << "Example.prepend_before(:all)" } + @special_example_group.before(:each) { fiddle << "Example.before(:each, :type => :special)" } + @special_example_group.prepend_before(:each) { fiddle << "Example.prepend_before(:each, :type => :special)" } + @special_example_group.before(:all) { fiddle << "Example.before(:all, :type => :special)" } + @special_example_group.prepend_before(:all) { fiddle << "Example.prepend_before(:all, :type => :special)" } + + example_group = Class.new(ExampleGroup) do + describe("I'm not special", :type => :not_special) + it "does nothing" + end + example_group.run + fiddle.should == [ + 'Example.prepend_before(:all)', + 'Example.before(:all)', + ] + end + + it "should add global before callbacks for targetted example_groups" do + fiddle = [] + + ExampleGroup.before(:all) { fiddle << "Example.before(:all)" } + ExampleGroup.prepend_before(:all) { fiddle << "Example.prepend_before(:all)" } + @special_example_group.before(:each) { fiddle << "special.before(:each, :type => :special)" } + @special_example_group.prepend_before(:each) { fiddle << "special.prepend_before(:each, :type => :special)" } + @special_example_group.before(:all) { fiddle << "special.before(:all, :type => :special)" } + @special_example_group.prepend_before(:all) { fiddle << "special.prepend_before(:all, :type => :special)" } + @special_example_group.append_before(:each) { fiddle << "special.append_before(:each, :type => :special)" } + + example_group = Class.new(@special_example_group).describe("I'm a special example_group") {} + example_group.it("test") {true} + example_group.run + fiddle.should == [ + 'Example.prepend_before(:all)', + 'Example.before(:all)', + 'special.prepend_before(:all, :type => :special)', + 'special.before(:all, :type => :special)', + 'special.prepend_before(:each, :type => :special)', + 'special.before(:each, :type => :special)', + 'special.append_before(:each, :type => :special)', + ] + end + + it "should order before callbacks from global to local" do + fiddle = [] + ExampleGroup.prepend_before(:all) { fiddle << "Example.prepend_before(:all)" } + ExampleGroup.before(:all) { fiddle << "Example.before(:all)" } + example_group.prepend_before(:all) { fiddle << "prepend_before(:all)" } + example_group.before(:all) { fiddle << "before(:all)" } + example_group.prepend_before(:each) { fiddle << "prepend_before(:each)" } + example_group.before(:each) { fiddle << "before(:each)" } + example_group.run + fiddle.should == [ + 'Example.prepend_before(:all)', + 'Example.before(:all)', + 'prepend_before(:all)', + 'before(:all)', + 'prepend_before(:each)', + 'before(:each)' + ] + end + + it "should order after callbacks from local to global" do + fiddle = [] + example_group.after(:each) { fiddle << "after(:each)" } + example_group.append_after(:each) { fiddle << "append_after(:each)" } + example_group.after(:all) { fiddle << "after(:all)" } + example_group.append_after(:all) { fiddle << "append_after(:all)" } + ExampleGroup.after(:all) { fiddle << "Example.after(:all)" } + ExampleGroup.append_after(:all) { fiddle << "Example.append_after(:all)" } + example_group.run + fiddle.should == [ + 'after(:each)', + 'append_after(:each)', + 'after(:all)', + 'append_after(:all)', + 'Example.after(:all)', + 'Example.append_after(:all)' + ] + end + + it "should have accessible instance methods from included module" do + mod1_method_called = false + mod1 = Module.new do + define_method :mod1_method do + mod1_method_called = true + end + end + + mod2_method_called = false + mod2 = Module.new do + define_method :mod2_method do + mod2_method_called = true + end + end + + example_group.include mod1, mod2 + + example_group.it("test") do + mod1_method + mod2_method + end + example_group.run + mod1_method_called.should be_true + mod2_method_called.should be_true + end + + it "should include targetted modules included using configuration" do + mod1 = Module.new + mod2 = Module.new + mod3 = Module.new + Spec::Runner.configuration.include(mod1, mod2) + Spec::Runner.configuration.include(mod3, :type => :not_special) + + example_group = Class.new(@special_example_group).describe("I'm special", :type => :special) do + it "does nothing" + end + example_group.run + + example_group.included_modules.should include(mod1) + example_group.included_modules.should include(mod2) + example_group.included_modules.should_not include(mod3) + end + + it "should include any predicate_matchers included using configuration" do + $included_predicate_matcher_found = false + Spec::Runner.configuration.predicate_matchers[:do_something] = :does_something? + example_group = Class.new(ExampleGroup) do + describe('example') + it "should respond to do_something" do + $included_predicate_matcher_found = respond_to?(:do_something) + end + end + example_group.run + $included_predicate_matcher_found.should be(true) + end + + it "should use a mock framework set up in config" do + mod = Module.new do + class << self + def included(mod) + $included_module = mod + end + end + + def teardown_mocks_for_rspec + $torn_down = true + end + end + + begin + $included_module = nil + $torn_down = true + Spec::Runner.configuration.mock_with mod + + example_group = Class.new(ExampleGroup) do + describe('example') + it "does nothing" + end + example_group.run + + $included_module.should_not be_nil + $torn_down.should == true + ensure + Spec::Runner.configuration.mock_with :rspec + end + end + end + + describe ExampleGroup, "#run with pending example that has a failing assertion" do + before do + example_group.it("should be pending") do + pending("Example fails") {false.should be_true} + end + end + + it "should send example_pending to formatter" do + @formatter.should_receive(:example_pending).with("example", "should be pending", "Example fails") + example_group.run + end + end + + describe ExampleGroup, "#run with pending example that does not have a failing assertion" do + it_should_behave_like "Spec::Example::ExampleGroup#run with failure in example" + + before do + example_group.it("should be pending") do + pending("Example passes") {true.should be_true} + end + end + + it "should send example_pending to formatter" do + @formatter.should_receive(:example_pending).with("example", "should be pending", "Example passes") + example_group.run + end + end + + describe ExampleGroup, "#run when before(:all) fails" do + it_should_behave_like "Spec::Example::ExampleGroup#run with failure in example" + + before do + ExampleGroup.before(:all) { raise NonStandardError, "before(:all) failure" } + end + + it "should not run any example" do + spec_ran = false + example_group.it("test") {spec_ran = true} + example_group.run + spec_ran.should be_false + end + + it "should run ExampleGroup after(:all)" do + after_all_ran = false + ExampleGroup.after(:all) { after_all_ran = true } + example_group.run + after_all_ran.should be_true + end + + it "should run example_group after(:all)" do + after_all_ran = false + example_group.after(:all) { after_all_ran = true } + example_group.run + after_all_ran.should be_true + end + + it "should supply before(:all) as description" do + @reporter.should_receive(:failure) do |example, error| + example.description.should eql("before(:all)") + error.message.should eql("before(:all) failure") + end + + example_group.it("test") {true} + example_group.run + end + end + + describe ExampleGroup, "#run when before(:each) fails" do + it_should_behave_like "Spec::Example::ExampleGroup#run with failure in example" + + before do + ExampleGroup.before(:each) { raise NonStandardError } + end + + it "should run after(:all)" do + after_all_ran = false + ExampleGroup.after(:all) { after_all_ran = true } + example_group.run + after_all_ran.should be_true + end + end + + describe ExampleGroup, "#run when any example fails" do + it_should_behave_like "Spec::Example::ExampleGroup#run with failure in example" + + before do + example_group.it("should") { raise NonStandardError } + end + + it "should run after(:all)" do + after_all_ran = false + ExampleGroup.after(:all) { after_all_ran = true } + example_group.run + after_all_ran.should be_true + end + end + + describe ExampleGroup, "#run when first after(:each) block fails" do + it_should_behave_like "Spec::Example::ExampleGroup#run with failure in example" + + before do + class << example_group + attr_accessor :first_after_ran, :second_after_ran + end + example_group.first_after_ran = false + example_group.second_after_ran = false + + example_group.after(:each) do + self.class.second_after_ran = true + end + example_group.after(:each) do + self.class.first_after_ran = true + raise "first" + end + end + + it "should run second after(:each) block" do + reporter.should_receive(:example_finished) do |example, error| + example.should equal(example) + error.message.should eql("first") + end + example_group.run + example_group.first_after_ran.should be_true + example_group.second_after_ran.should be_true + end + end + + describe ExampleGroup, "#run when first before(:each) block fails" do + it_should_behave_like "Spec::Example::ExampleGroup#run with failure in example" + + before do + class << example_group + attr_accessor :first_before_ran, :second_before_ran + end + example_group.first_before_ran = false + example_group.second_before_ran = false + + example_group.before(:each) do + self.class.first_before_ran = true + raise "first" + end + example_group.before(:each) do + self.class.second_before_ran = true + end + end + + it "should not run second before(:each)" do + reporter.should_receive(:example_finished) do |name, error| + error.message.should eql("first") + end + example_group.run + example_group.first_before_ran.should be_true + example_group.second_before_ran.should be_false + end + end + + describe ExampleGroup, "#run when failure in after(:all)" do + it_should_behave_like "Spec::Example::ExampleGroup#run with failure in example" + + before do + ExampleGroup.after(:all) { raise NonStandardError, "in after(:all)" } + end + + it "should return false" do + example_group.run.should be_false + end + end + end + + class ExampleSubclass < ExampleGroup + end + + describe ExampleGroup, "subclasses" do + after do + ExampleGroupFactory.reset + end + + it "should have access to the described_type" do + example_group = Class.new(ExampleSubclass) do + describe(Array) + end + example_group.send(:described_type).should == Array + end + + it "should concat descriptions when nested" do + example_group = Class.new(ExampleSubclass) do + describe(Array) + $nested_group = describe("when empty") do + end + end + $nested_group.description.to_s.should == "Array when empty" + end + end + + describe Enumerable do + def each(&block) + ["4", "2", "1"].each(&block) + end + + it "should be included in examples because it is a module" do + map{|e| e.to_i}.should == [4,2,1] + end + end + + describe "An", Enumerable, "as a second argument" do + def each(&block) + ["4", "2", "1"].each(&block) + end + + it "should be included in examples because it is a module" do + map{|e| e.to_i}.should == [4,2,1] + end + end + + describe Enumerable do + describe "as the parent of nested example groups" do + it "should be included in examples because it is a module" do + pending("need to make sure nested groups know the described type") do + map{|e| e.to_i}.should == [4,2,1] + end + end + end + end + + describe String do + it"should not be included in examples because it is not a module" do + lambda{self.map}.should raise_error(NoMethodError, /undefined method `map' for/) + end + end + end +end diff --git a/vendor/gems/rspec/spec/spec/example/example_matcher_spec.rb b/vendor/gems/rspec/spec/spec/example/example_matcher_spec.rb new file mode 100644 index 0000000000..ea0dfe019c --- /dev/null +++ b/vendor/gems/rspec/spec/spec/example/example_matcher_spec.rb @@ -0,0 +1,96 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +module Spec + module Example + module ExampleMatcherSpecHelper + class MatchDescription + def initialize(description) + @description = description + end + + def matches?(matcher) + matcher.matches?(@description) + end + + def failure_message + "expected matcher.matches?(#{@description.inspect}) to return true, got false" + end + + def negative_failure_message + "expected matcher.matches?(#{@description.inspect}) to return false, got true" + end + end + def match_description(description) + MatchDescription.new(description) + end + end + + describe ExampleMatcher, "#matches?" do + include ExampleMatcherSpecHelper + + it "should match correct example_group and example" do + matcher = ExampleMatcher.new("example_group", "example") + matcher.should match_description("example_group example") + end + + it "should not match wrong example" do + matcher = ExampleMatcher.new("example_group", "other example") + matcher.should_not match_description("example_group example") + end + + it "should not match wrong example_group" do + matcher = ExampleMatcher.new("other example_group", "example") + matcher.should_not match_description("example_group example") + end + + it "should match example only" do + matcher = ExampleMatcher.new("example_group", "example") + matcher.should match_description("example") + end + + it "should match example_group only" do + matcher = ExampleMatcher.new("example_group", "example") + matcher.should match_description("example_group") + end + + it "should match example_group ending with before(:all)" do + matcher = ExampleMatcher.new("example_group", "example") + matcher.should match_description("example_group before(:all)") + end + + it "should escape regexp chars" do + matcher = ExampleMatcher.new("(con|text)", "[example]") + matcher.should_not match_description("con p") + end + + it "should match when example_group is modularized" do + matcher = ExampleMatcher.new("MyModule::MyClass", "example") + matcher.should match_description("MyClass example") + end + end + + describe ExampleMatcher, "#matches? normal case" do + it "matches when passed in example matches" do + matcher = ExampleMatcher.new("Foo", "bar") + matcher.matches?(["no match", "Foo bar"]).should == true + end + + it "does not match when no passed in examples match" do + matcher = ExampleMatcher.new("Foo", "bar") + matcher.matches?(["no match1", "no match2"]).should == false + end + end + + describe ExampleMatcher, "#matches? where description has '::' in it" do + it "matches when passed in example matches" do + matcher = ExampleMatcher.new("Foo::Bar", "baz") + matcher.matches?(["no match", "Foo::Bar baz"]).should == true + end + + it "does not match when no passed in examples match" do + matcher = ExampleMatcher.new("Foo::Bar", "baz") + matcher.matches?(["no match1", "no match2"]).should == false + end + end + end +end diff --git a/vendor/gems/rspec/spec/spec/example/example_methods_spec.rb b/vendor/gems/rspec/spec/spec/example/example_methods_spec.rb new file mode 100644 index 0000000000..c18522808f --- /dev/null +++ b/vendor/gems/rspec/spec/spec/example/example_methods_spec.rb @@ -0,0 +1,104 @@ +require File.dirname(__FILE__) + '/../../spec_helper' + +module Spec + module Example + module ModuleThatIsReopened + end + + module ExampleMethods + include ModuleThatIsReopened + end + + module ModuleThatIsReopened + def module_that_is_reopened_method + end + end + + describe ExampleMethods do + describe "with an included module that is reopened" do + it "should have repoened methods" do + method(:module_that_is_reopened_method).should_not be_nil + end + end + + describe "lifecycle" do + before do + @options = ::Spec::Runner::Options.new(StringIO.new, StringIO.new) + @options.formatters << mock("formatter", :null_object => true) + @options.backtrace_tweaker = mock("backtrace_tweaker", :null_object => true) + @reporter = FakeReporter.new(@options) + @options.reporter = @reporter + + ExampleMethods.before_all_parts.should == [] + ExampleMethods.before_each_parts.should == [] + ExampleMethods.after_each_parts.should == [] + ExampleMethods.after_all_parts.should == [] + def ExampleMethods.count + @count ||= 0 + @count = @count + 1 + @count + end + end + + after do + ExampleMethods.instance_variable_set("@before_all_parts", []) + ExampleMethods.instance_variable_set("@before_each_parts", []) + ExampleMethods.instance_variable_set("@after_each_parts", []) + ExampleMethods.instance_variable_set("@after_all_parts", []) + end + + it "should pass before and after callbacks to all ExampleGroup subclasses" do + ExampleMethods.before(:all) do + ExampleMethods.count.should == 1 + end + + ExampleMethods.before(:each) do + ExampleMethods.count.should == 2 + end + + ExampleMethods.after(:each) do + ExampleMethods.count.should == 3 + end + + ExampleMethods.after(:all) do + ExampleMethods.count.should == 4 + end + + @example_group = Class.new(ExampleGroup) do + it "should use ExampleMethods callbacks" do + end + end + @example_group.run + ExampleMethods.count.should == 5 + end + + describe "run_with_description_capturing" do + before(:each) do + @example_group = Class.new(ExampleGroup) do end + @example = @example_group.new("foo", &(lambda { 2.should == 2 })) + @example.run_with_description_capturing + end + + it "should provide the generated description" do + @example.instance_eval { @_matcher_description }.should == "should == 2" + end + + it "should clear the global generated_description" do + Spec::Matchers.generated_description.should == nil + end + end + end + + describe "#implementation_backtrace" do + it "returns the backtrace of where the implementation was defined" do + example_group = Class.new(ExampleGroup) do + it "should use ExampleMethods callbacks" do + end + end + example = example_group.examples.first + example.implementation_backtrace.join("\n").should include("#{__FILE__}:#{__LINE__-4}") + end + end + end + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/example/example_runner_spec.rb b/vendor/gems/rspec/spec/spec/example/example_runner_spec.rb new file mode 100644 index 0000000000..1b5abdf0f9 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/example/example_runner_spec.rb @@ -0,0 +1,194 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +module Spec + module Example + # describe "Spec::Example::ExampleRunner", "#run", :shared => true do + # before(:each) do + # @options = ::Spec::Runner::Options.new(StringIO.new, StringIO.new) + # @reporter = ::Spec::Runner::Reporter.new(@options) + # @options.reporter = @reporter + # @example_group_class = Class.new(ExampleGroup) do + # plugin_mock_framework + # describe("Some Examples") + # end + # end + # + # def create_runner(example_definition) + # example = @example_group_class.new(example_definition) + # runner = ExampleGroup.new(@options, example) + # runner.stub!(:verify_mocks) + # runner.stub!(:teardown_mocks) + # runner + # end + # end + # + # describe ExampleRunner, "#run with blank passing example" do + # it_should_behave_like "Spec::Example::ExampleRunner#run" + # + # before do + # @e = @example_group_class.it("example") {} + # @runner = create_runner(@e) + # end + # + # it "should send reporter example_started" do + # @reporter.should_receive(:example_started).with(equal(@e)) + # @runner.run + # end + # + # it "should report its name for dry run" do + # @options.dry_run = true + # @reporter.should_receive(:example_finished).with(equal(@e), nil) + # @runner.run + # end + # + # it "should report success" do + # @reporter.should_receive(:example_finished).with(equal(@e), nil) + # @runner.run + # end + # end + # + # describe ExampleRunner, "#run with a failing example" do + # predicate_matchers[:is_a] = [:is_a?] + # it_should_behave_like "Spec::Example::ExampleRunner#run" + # + # before do + # @e = @example_group_class.it("example") do + # (2+2).should == 5 + # end + # @runner = create_runner(@e) + # end + # + # it "should report failure due to failure" do + # @reporter.should_receive(:example_finished).with( + # equal(@e), + # is_a(Spec::Expectations::ExpectationNotMetError) + # ) + # @runner.run + # end + # end + # + # describe ExampleRunner, "#run with a erroring example" do + # it_should_behave_like "Spec::Example::ExampleRunner#run" + # + # before do + # @error = error = NonStandardError.new("in body") + # @example_definition = @example_group_class.it("example") do + # raise(error) + # end + # @runner = create_runner(@example_definition) + # end + # + # it "should report failure due to error" do + # @reporter.should_receive(:example_finished).with( + # equal(@example_definition), + # @error + # ) + # @runner.run + # end + # + # it "should run after_each block" do + # @example_group_class.after(:each) do + # raise("in after_each") + # end + # @reporter.should_receive(:example_finished) do |example_definition, error| + # example_definition.should equal(@example_definition) + # error.message.should eql("in body") + # end + # @runner.run + # end + # end + # + # describe ExampleRunner, "#run where after_each fails" do + # it_should_behave_like "Spec::Example::ExampleRunner#run" + # + # before do + # @example_ran = example_ran = false + # @example_definition = @example_group_class.it("should not run") do + # example_ran = true + # end + # @runner = create_runner(@example_definition) + # @example_group_class.after(:each) { raise(NonStandardError.new("in after_each")) } + # end + # + # it "should report failure location when in after_each" do + # @reporter.should_receive(:example_finished) do |example_definition, error| + # example_definition.should equal(@example_definition) + # error.message.should eql("in after_each") + # end + # @runner.run + # end + # end + # + # describe ExampleRunner, "#run with use cases" do + # predicate_matchers[:is_a] = [:is_a?] + # it_should_behave_like "Spec::Example::ExampleRunner#run" + # + # it "should report NO NAME when told to use generated description with --dry-run" do + # @options.dry_run = true + # example_definition = @example_group_class.it() do + # 5.should == 5 + # end + # runner = create_runner(example_definition) + # + # @reporter.should_receive(:example_finished) do |example_definition, error| + # example_definition.description.should == "NO NAME (Because of --dry-run)" + # end + # runner.run + # end + # + # it "should report given name if present with --dry-run" do + # @options.dry_run = true + # example_definition = @example_group_class.it("example name") do + # 5.should == 5 + # end + # runner = create_runner(example_definition) + # + # @reporter.should_receive(:example_finished) do |example_definition, error| + # example_definition.description.should == "example name" + # end + # runner.run + # end + # + # it "should report NO NAME when told to use generated description with no expectations" do + # example_definition = @example_group_class.it() {} + # runner = create_runner(example_definition) + # @reporter.should_receive(:example_finished) do |example, error| + # example.description.should == "NO NAME (Because there were no expectations)" + # end + # runner.run + # end + # + # it "should report NO NAME when told to use generated description and matcher fails" do + # example_definition = @example_group_class.it() do + # 5.should "" # Has no matches? method.. + # end + # runner = create_runner(example_definition) + # + # @reporter.should_receive(:example_finished) do |example, error| + # example_definition.description.should == "NO NAME (Because of Error raised in matcher)" + # end + # runner.run + # end + # + # it "should report generated description when told to and it is available" do + # example_definition = @example_group_class.it() { + # 5.should == 5 + # } + # runner = create_runner(example_definition) + # + # @reporter.should_receive(:example_finished) do |example_definition, error| + # example_definition.description.should == "should == 5" + # end + # runner.run + # end + # + # it "should unregister description_generated callback (lest a memory leak should build up)" do + # example_definition = @example_group_class.it("something") + # runner = create_runner(example_definition) + # + # Spec::Matchers.should_receive(:example_finished) + # runner.run + # end + # end + end +end diff --git a/vendor/gems/rspec/spec/spec/example/example_spec.rb b/vendor/gems/rspec/spec/spec/example/example_spec.rb new file mode 100644 index 0000000000..c8125b4471 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/example/example_spec.rb @@ -0,0 +1,53 @@ +require File.dirname(__FILE__) + '/../../spec_helper' + +module Spec + module Example + # describe Example do + # before(:each) do + # @example = Example.new "example" do + # foo + # end + # end + # + # it "should tell you its docstring" do + # @example.description.should == "example" + # end + # + # it "should execute its block in the context provided" do + # context = Class.new do + # def foo + # "foo" + # end + # end.new + # @example.run_in(context).should == "foo" + # end + # end + # + # describe Example, "#description" do + # it "should default to NO NAME when not passed anything when there are no matchers" do + # example = Example.new {} + # example.run_in(Object.new) + # example.description.should == "NO NAME" + # end + # + # it "should default to NO NAME description (Because of --dry-run) when passed nil and there are no matchers" do + # example = Example.new(nil) {} + # example.run_in(Object.new) + # example.description.should == "NO NAME" + # end + # + # it "should allow description to be overridden" do + # example = Example.new("Test description") + # example.description.should == "Test description" + # end + # + # it "should use description generated from matcher when there is no passed in description" do + # example = Example.new(nil) do + # 1.should == 1 + # end + # example.run_in(Object.new) + # example.description.should == "should == 1" + # end + # end + end +end diff --git a/vendor/gems/rspec/spec/spec/example/nested_example_group_spec.rb b/vendor/gems/rspec/spec/spec/example/nested_example_group_spec.rb new file mode 100644 index 0000000000..35e8a98908 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/example/nested_example_group_spec.rb @@ -0,0 +1,59 @@ +require File.dirname(__FILE__) + '/../../spec_helper' + +module Spec + module Example + describe 'Nested Example Groups' do + parent = self + + def count + @count ||= 0 + @count = @count + 1 + @count + end + + before(:all) do + count.should == 1 + end + + before(:all) do + count.should == 2 + end + + before(:each) do + count.should == 3 + end + + before(:each) do + count.should == 4 + end + + it "should run before(:all), before(:each), example, after(:each), after(:all) in order" do + count.should == 5 + end + + after(:each) do + count.should == 7 + end + + after(:each) do + count.should == 6 + end + + after(:all) do + count.should == 9 + end + + after(:all) do + count.should == 8 + end + + describe 'nested example group' do + self.superclass.should == parent + + it "should run all before and after callbacks" do + count.should == 5 + end + end + end + end +end diff --git a/vendor/gems/rspec/spec/spec/example/pending_module_spec.rb b/vendor/gems/rspec/spec/spec/example/pending_module_spec.rb new file mode 100644 index 0000000000..c3ab0126b5 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/example/pending_module_spec.rb @@ -0,0 +1,31 @@ +module Spec + module Example + describe Pending do + + it 'should raise an ExamplePendingError if no block is supplied' do + lambda { + include Pending + pending "TODO" + }.should raise_error(ExamplePendingError, /TODO/) + end + + it 'should raise an ExamplePendingError if a supplied block fails as expected' do + lambda { + include Pending + pending "TODO" do + raise "oops" + end + }.should raise_error(ExamplePendingError, /TODO/) + end + + it 'should raise a PendingExampleFixedError if a supplied block starts working' do + lambda { + include Pending + pending "TODO" do + # success! + end + }.should raise_error(PendingExampleFixedError, /TODO/) + end + end + end +end diff --git a/vendor/gems/rspec/spec/spec/example/predicate_matcher_spec.rb b/vendor/gems/rspec/spec/spec/example/predicate_matcher_spec.rb new file mode 100755 index 0000000000..7c4638b4bf --- /dev/null +++ b/vendor/gems/rspec/spec/spec/example/predicate_matcher_spec.rb @@ -0,0 +1,21 @@ +require File.dirname(__FILE__) + '/../../spec_helper' + +module Spec + module Example + class Fish + def can_swim?(distance_in_yards) + distance_in_yards < 1000 + end + end + + describe "predicate_matcher[method_on_object] = matcher_method" do + predicate_matchers[:swim] = :can_swim? + it "should match matcher_method if method_on_object returns true" do + swim(100).matches?(Fish.new).should be_true + end + it "should not match matcher_method if method_on_object returns false" do + swim(10000).matches?(Fish.new).should be_false + end + end + end +end diff --git a/vendor/gems/rspec/spec/spec/example/shared_example_group_spec.rb b/vendor/gems/rspec/spec/spec/example/shared_example_group_spec.rb new file mode 100644 index 0000000000..803536ab57 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/example/shared_example_group_spec.rb @@ -0,0 +1,265 @@ +require File.dirname(__FILE__) + '/../../spec_helper' + +module Spec + module Example + describe ExampleGroup, "with :shared => true" do + it_should_behave_like "sandboxed rspec_options" + attr_reader :formatter, :example_group + before(:each) do + @formatter = Spec::Mocks::Mock.new("formatter", :null_object => true) + options.formatters << formatter + @example_group = Class.new(ExampleGroup).describe("example_group") + class << example_group + public :include + end + end + + after(:each) do + @formatter.rspec_verify + @example_group = nil + $shared_example_groups.clear unless $shared_example_groups.nil? + end + + def make_shared_example_group(name, opts=nil, &block) + example_group = SharedExampleGroup.new(name, :shared => true, &block) + SharedExampleGroup.add_shared_example_group(example_group) + example_group + end + + def non_shared_example_group() + @non_shared_example_group ||= Class.new(ExampleGroup).describe("example_group") + end + + it "should accept an optional options hash" do + lambda { Class.new(ExampleGroup).describe("context") }.should_not raise_error(Exception) + lambda { Class.new(ExampleGroup).describe("context", :shared => true) }.should_not raise_error(Exception) + end + + it "should return all shared example_groups" do + b1 = make_shared_example_group("b1", :shared => true) {} + b2 = make_shared_example_group("b2", :shared => true) {} + + b1.should_not be(nil) + b2.should_not be(nil) + + SharedExampleGroup.find_shared_example_group("b1").should equal(b1) + SharedExampleGroup.find_shared_example_group("b2").should equal(b2) + end + + it "should register as shared example_group" do + example_group = make_shared_example_group("example_group") {} + SharedExampleGroup.shared_example_groups.should include(example_group) + end + + it "should not be shared when not configured as shared" do + example_group = non_shared_example_group + SharedExampleGroup.shared_example_groups.should_not include(example_group) + end + + it "should complain when adding a second shared example_group with the same description" do + describe "shared example_group", :shared => true do + end + lambda do + describe "shared example_group", :shared => true do + end + end.should raise_error(ArgumentError) + end + + it "should NOT complain when adding the same shared example_group instance again" do + shared_example_group = Class.new(ExampleGroup).describe("shared example_group", :shared => true) + SharedExampleGroup.add_shared_example_group(shared_example_group) + SharedExampleGroup.add_shared_example_group(shared_example_group) + end + + it "should NOT complain when adding the same shared example_group again (i.e. file gets reloaded)" do + lambda do + 2.times do + describe "shared example_group which gets loaded twice", :shared => true do + end + end + end.should_not raise_error(ArgumentError) + end + + it "should NOT complain when adding the same shared example_group in same file with different absolute path" do + shared_example_group_1 = Class.new(ExampleGroup).describe( + "shared example_group", + :shared => true, + :spec_path => "/my/spec/a/../shared.rb" + ) + shared_example_group_2 = Class.new(ExampleGroup).describe( + "shared example_group", + :shared => true, + :spec_path => "/my/spec/b/../shared.rb" + ) + + SharedExampleGroup.add_shared_example_group(shared_example_group_1) + SharedExampleGroup.add_shared_example_group(shared_example_group_2) + end + + it "should complain when adding a different shared example_group with the same name in a different file with the same basename" do + shared_example_group_1 = Class.new(ExampleGroup).describe( + "shared example_group", + :shared => true, + :spec_path => "/my/spec/a/shared.rb" + ) + shared_example_group_2 = Class.new(ExampleGroup).describe( + "shared example_group", + :shared => true, + :spec_path => "/my/spec/b/shared.rb" + ) + + SharedExampleGroup.add_shared_example_group(shared_example_group_1) + lambda do + SharedExampleGroup.add_shared_example_group(shared_example_group_2) + end.should raise_error(ArgumentError, /already exists/) + end + + it "should add examples to current example_group using it_should_behave_like" do + shared_example_group = make_shared_example_group("shared example_group") do + it("shared example") {} + it("shared example 2") {} + end + + example_group.it("example") {} + example_group.number_of_examples.should == 1 + example_group.it_should_behave_like("shared example_group") + example_group.number_of_examples.should == 3 + end + + it "should add examples to current example_group using include" do + shared_example_group = describe "all things", :shared => true do + it "should do stuff" do end + end + + example_group = describe "one thing" do + include shared_example_group + end + + example_group.number_of_examples.should == 1 + end + + it "should add examples to current example_group using it_should_behave_like with a module" do + AllThings = describe "all things", :shared => true do + it "should do stuff" do end + end + + example_group = describe "one thing" do + it_should_behave_like AllThings + end + + example_group.number_of_examples.should == 1 + end + + it "should run shared examples" do + shared_example_ran = false + shared_example_group = make_shared_example_group("shared example_group") do + it("shared example") { shared_example_ran = true } + end + + example_ran = false + + example_group.it_should_behave_like("shared example_group") + example_group.it("example") {example_ran = true} + example_group.run + example_ran.should be_true + shared_example_ran.should be_true + end + + it "should run setup and teardown from shared example_group" do + shared_setup_ran = false + shared_teardown_ran = false + shared_example_group = make_shared_example_group("shared example_group") do + before { shared_setup_ran = true } + after { shared_teardown_ran = true } + it("shared example") { shared_example_ran = true } + end + + example_ran = false + + example_group.it_should_behave_like("shared example_group") + example_group.it("example") {example_ran = true} + example_group.run + example_ran.should be_true + shared_setup_ran.should be_true + shared_teardown_ran.should be_true + end + + it "should run before(:all) and after(:all) only once from shared example_group" do + shared_before_all_run_count = 0 + shared_after_all_run_count = 0 + shared_example_group = make_shared_example_group("shared example_group") do + before(:all) { shared_before_all_run_count += 1} + after(:all) { shared_after_all_run_count += 1} + it("shared example") { shared_example_ran = true } + end + + example_ran = false + + example_group.it_should_behave_like("shared example_group") + example_group.it("example") {example_ran = true} + example_group.run + example_ran.should be_true + shared_before_all_run_count.should == 1 + shared_after_all_run_count.should == 1 + end + + it "should include modules, included into shared example_group, into current example_group" do + @formatter.should_receive(:add_example_group).with(any_args) + + shared_example_group = make_shared_example_group("shared example_group") do + it("shared example") { shared_example_ran = true } + end + + mod1_method_called = false + mod1 = Module.new do + define_method :mod1_method do + mod1_method_called = true + end + end + + mod2_method_called = false + mod2 = Module.new do + define_method :mod2_method do + mod2_method_called = true + end + end + + shared_example_group.include mod2 + + example_group.it_should_behave_like("shared example_group") + example_group.include mod1 + + example_group.it("test") do + mod1_method + mod2_method + end + example_group.run + mod1_method_called.should be_true + mod2_method_called.should be_true + end + + it "should make methods defined in the shared example_group available in consuming example_group" do + shared_example_group = make_shared_example_group("shared example_group xyz") do + def a_shared_helper_method + "this got defined in a shared example_group" + end + end + example_group.it_should_behave_like("shared example_group xyz") + success = false + example_group.it("should access a_shared_helper_method") do + a_shared_helper_method + success = true + end + example_group.run + success.should be_true + end + + it "should raise when named shared example_group can not be found" do + lambda { + example_group.it_should_behave_like("non-existent shared example group") + violated + }.should raise_error("Shared Example Group 'non-existent shared example group' can not be found") + end + end + end +end diff --git a/vendor/gems/rspec/spec/spec/example/subclassing_example_group_spec.rb b/vendor/gems/rspec/spec/spec/example/subclassing_example_group_spec.rb new file mode 100644 index 0000000000..888f2ceb3d --- /dev/null +++ b/vendor/gems/rspec/spec/spec/example/subclassing_example_group_spec.rb @@ -0,0 +1,25 @@ +require File.dirname(__FILE__) + '/../../spec_helper' + +module Spec + module Example + class GrandParentExampleGroup < Spec::Example::ExampleGroup + describe "Grandparent ExampleGroup" + end + + class ParentExampleGroup < GrandParentExampleGroup + describe "Parent ExampleGroup" + it "should bar" do + end + end + + class ChildExampleGroup < ParentExampleGroup + describe "Child ExampleGroup" + it "should bam" do + end + end + + describe ChildExampleGroup do + + end + end +end diff --git a/vendor/gems/rspec/spec/spec/expectations/differs/default_spec.rb b/vendor/gems/rspec/spec/spec/expectations/differs/default_spec.rb new file mode 100644 index 0000000000..ea720846b8 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/expectations/differs/default_spec.rb @@ -0,0 +1,109 @@ +require File.dirname(__FILE__) + '/../../../spec_helper.rb' + +module Spec + module Fixtures + class Animal + def initialize(name,species) + @name,@species = name,species + end + + def inspect + <<-EOA + + EOA + end + end + end +end + +describe "Diff" do + before(:each) do + @options = ::Spec::Runner::Options.new(StringIO.new, StringIO.new) + @differ = Spec::Expectations::Differs::Default.new(@options) + end + + it "should output unified diff of two strings" do + expected="foo\nbar\nzap\nthis\nis\nsoo\nvery\nvery\nequal\ninsert\na\nline\n" + actual="foo\nzap\nbar\nthis\nis\nsoo\nvery\nvery\nequal\ninsert\na\nanother\nline\n" + expected_diff="\n\n@@ -1,6 +1,6 @@\n foo\n-bar\n zap\n+bar\n this\n is\n soo\n@@ -9,5 +9,6 @@\n equal\n insert\n a\n+another\n line\n" + diff = @differ.diff_as_string(expected, actual) + diff.should eql(expected_diff) + end + + it "should output unified diff message of two arrays" do + expected = [ :foo, 'bar', :baz, 'quux', :metasyntactic, 'variable', :delta, 'charlie', :width, 'quite wide' ] + actual = [ :foo, 'bar', :baz, 'quux', :metasyntactic, 'variable', :delta, 'tango' , :width, 'very wide' ] + + expected_diff = <<'EOD' + + +@@ -5,7 +5,7 @@ + :metasyntactic, + "variable", + :delta, +- "charlie", ++ "tango", + :width, +- "quite wide"] ++ "very wide"] +EOD + + + diff = @differ.diff_as_object(expected,actual) + diff.should == expected_diff + end + + it "should output unified diff message of two objects" do + expected = Spec::Fixtures::Animal.new "bob", "giraffe" + actual = Spec::Fixtures::Animal.new "bob", "tortoise" + + expected_diff = <<'EOD' + +@@ -1,5 +1,5 @@ + +EOD + + diff = @differ.diff_as_object(expected,actual) + diff.should == expected_diff + end + +end + + +describe "Diff in context format" do + before(:each) do + @options = Spec::Runner::Options.new(StringIO.new, StringIO.new) + @options.diff_format = :context + @differ = Spec::Expectations::Differs::Default.new(@options) + end + + it "should output unified diff message of two objects" do + expected = Spec::Fixtures::Animal.new "bob", "giraffe" + actual = Spec::Fixtures::Animal.new "bob", "tortoise" + + expected_diff = <<'EOD' + +*************** +*** 1,5 **** + +--- 1,5 ---- + +EOD + + diff = @differ.diff_as_object(expected,actual) + diff.should == expected_diff + end +end diff --git a/vendor/gems/rspec/spec/spec/expectations/extensions/object_spec.rb b/vendor/gems/rspec/spec/spec/expectations/extensions/object_spec.rb new file mode 100644 index 0000000000..0d9335bdb4 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/expectations/extensions/object_spec.rb @@ -0,0 +1,107 @@ +require File.dirname(__FILE__) + '/../../../spec_helper.rb' + +describe Object, "#should" do + before(:each) do + @target = "target" + @matcher = mock("matcher") + @matcher.stub!(:matches?).and_return(true) + @matcher.stub!(:failure_message) + end + + it "should accept and interact with a matcher" do + @matcher.should_receive(:matches?).with(@target).and_return(true) + @target.should @matcher + end + + it "should ask for a failure_message when matches? returns false" do + @matcher.should_receive(:matches?).with(@target).and_return(false) + @matcher.should_receive(:failure_message).and_return("the failure message") + lambda { + @target.should @matcher + }.should fail_with("the failure message") + end + + it "should raise error if it receives false directly" do + lambda { + @target.should false + }.should raise_error(Spec::Expectations::InvalidMatcherError) + end + + it "should raise error if it receives false (evaluated)" do + lambda { + @target.should eql?("foo") + }.should raise_error(Spec::Expectations::InvalidMatcherError) + end + + it "should raise error if it receives true" do + lambda { + @target.should true + }.should raise_error(Spec::Expectations::InvalidMatcherError) + end + + it "should raise error if it receives nil" do + lambda { + @target.should nil + }.should raise_error(Spec::Expectations::InvalidMatcherError) + end + + it "should raise error if it receives no argument and it is not used as a left side of an operator" do + pending "Is it even possible to catch this?" + lambda { + @target.should + }.should raise_error(Spec::Expectations::InvalidMatcherError) + end +end + +describe Object, "#should_not" do + before(:each) do + @target = "target" + @matcher = mock("matcher") + end + + it "should accept and interact with a matcher" do + @matcher.should_receive(:matches?).with(@target).and_return(false) + @matcher.stub!(:negative_failure_message) + + @target.should_not @matcher + end + + it "should ask for a negative_failure_message when matches? returns true" do + @matcher.should_receive(:matches?).with(@target).and_return(true) + @matcher.should_receive(:negative_failure_message).and_return("the negative failure message") + lambda { + @target.should_not @matcher + }.should fail_with("the negative failure message") + end + + it "should raise error if it receives false directly" do + lambda { + @target.should_not false + }.should raise_error(Spec::Expectations::InvalidMatcherError) + end + + it "should raise error if it receives false (evaluated)" do + lambda { + @target.should_not eql?("foo") + }.should raise_error(Spec::Expectations::InvalidMatcherError) + end + + it "should raise error if it receives true" do + lambda { + @target.should_not true + }.should raise_error(Spec::Expectations::InvalidMatcherError) + end + + it "should raise error if it receives nil" do + lambda { + @target.should_not nil + }.should raise_error(Spec::Expectations::InvalidMatcherError) + end + + it "should raise error if it receives no argument and it is not used as a left side of an operator" do + pending "Is it even possible to catch this?" + lambda { + @target.should_not + }.should raise_error(Spec::Expectations::InvalidMatcherError) + end +end diff --git a/vendor/gems/rspec/spec/spec/expectations/fail_with_spec.rb b/vendor/gems/rspec/spec/spec/expectations/fail_with_spec.rb new file mode 100644 index 0000000000..4c369ce3ab --- /dev/null +++ b/vendor/gems/rspec/spec/spec/expectations/fail_with_spec.rb @@ -0,0 +1,71 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +describe Spec::Expectations, "#fail_with with no diff" do + before(:each) do + @old_differ = Spec::Expectations.differ + Spec::Expectations.differ = nil + end + + it "should handle just a message" do + lambda { + Spec::Expectations.fail_with "the message" + }.should fail_with("the message") + end + + it "should handle an Array" do + lambda { + Spec::Expectations.fail_with ["the message","expected","actual"] + }.should fail_with("the message") + end + + after(:each) do + Spec::Expectations.differ = @old_differ + end +end + +describe Spec::Expectations, "#fail_with with diff" do + before(:each) do + @old_differ = Spec::Expectations.differ + @differ = mock("differ") + Spec::Expectations.differ = @differ + end + + it "should not call differ if no expected/actual" do + lambda { + Spec::Expectations.fail_with "the message" + }.should fail_with("the message") + end + + it "should call differ if expected/actual are presented separately" do + @differ.should_receive(:diff_as_string).and_return("diff") + lambda { + Spec::Expectations.fail_with "the message", "expected", "actual" + }.should fail_with("the message\nDiff:diff") + end + + it "should call differ if expected/actual are not strings" do + @differ.should_receive(:diff_as_object).and_return("diff") + lambda { + Spec::Expectations.fail_with "the message", :expected, :actual + }.should fail_with("the message\nDiff:diff") + end + + it "should not call differ if expected or actual are procs" do + @differ.should_not_receive(:diff_as_string) + @differ.should_not_receive(:diff_as_object) + lambda { + Spec::Expectations.fail_with "the message", lambda {}, lambda {} + }.should fail_with("the message") + end + + it "should call differ if expected/actual are presented in an Array with message" do + @differ.should_receive(:diff_as_string).with("actual","expected").and_return("diff") + lambda { + Spec::Expectations.fail_with(["the message", "expected", "actual"]) + }.should fail_with(/the message\nDiff:diff/) + end + + after(:each) do + Spec::Expectations.differ = @old_differ + end +end diff --git a/vendor/gems/rspec/spec/spec/extensions/main_spec.rb b/vendor/gems/rspec/spec/spec/extensions/main_spec.rb new file mode 100644 index 0000000000..aabb616e97 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/extensions/main_spec.rb @@ -0,0 +1,76 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +module Spec + module Extensions + describe Main do + it_should_behave_like "sandboxed rspec_options" + before(:each) do + @main = Class.new do; include Main; end + end + + after(:each) do + $rspec_story_steps = @original_rspec_story_steps + end + + it "should create an Options object" do + @main.send(:rspec_options).should be_instance_of(Spec::Runner::Options) + @main.send(:rspec_options).should === $rspec_options + end + + specify {@main.should respond_to(:describe)} + specify {@main.should respond_to(:context)} + + it "should raise when no block given to describe" do + lambda { @main.describe "foo" }.should raise_error(ArgumentError) + end + + it "should raise when no description given to describe" do + lambda { @main.describe do; end }.should raise_error(ArgumentError) + end + + it "should registered ExampleGroups by default" do + example_group = @main.describe("The ExampleGroup") do end + rspec_options.example_groups.should include(example_group) + end + + it "should not run unregistered ExampleGroups" do + example_group = @main.describe("The ExampleGroup") do + unregister + end + + rspec_options.example_groups.should_not include(example_group) + end + + it "should create a shared ExampleGroup with share_examples_for" do + group = @main.share_examples_for "all things" do end + group.should be_an_instance_of(Spec::Example::SharedExampleGroup) + end + + describe "#share_as" do + before(:each) do + $share_as_examples_example_module_number ||= 1 + $share_as_examples_example_module_number += 1 + t = Time.new.to_i + @group_name = "Group#{$share_as_examples_example_module_number}" + end + + it "should create a shared ExampleGroup" do + group = @main.share_as @group_name do end + group.should be_an_instance_of(Spec::Example::SharedExampleGroup) + end + + it "should create a constant that points to a Module" do + group = @main.share_as @group_name do end + Object.const_get(@group_name).should equal(group) + end + + it "should bark if you pass it something not-constantizable" do + lambda do + @group = @main.share_as "Non Constant" do end + end.should raise_error(NameError, /The first argument to share_as must be a legal name for a constant/) + end + + end + end + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/interop/test/unit/resources/spec_that_fails.rb b/vendor/gems/rspec/spec/spec/interop/test/unit/resources/spec_that_fails.rb new file mode 100644 index 0000000000..d6f5564bf1 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/interop/test/unit/resources/spec_that_fails.rb @@ -0,0 +1,10 @@ +rspec_lib = File.dirname(__FILE__) + "/../../../../../../lib" +$:.unshift rspec_lib unless $:.include?(rspec_lib) +require 'test/unit' +require 'spec' + +describe "example group with failures" do + it "should fail" do + false.should be_true + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/interop/test/unit/resources/spec_that_passes.rb b/vendor/gems/rspec/spec/spec/interop/test/unit/resources/spec_that_passes.rb new file mode 100644 index 0000000000..ccd2488bc8 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/interop/test/unit/resources/spec_that_passes.rb @@ -0,0 +1,10 @@ +rspec_lib = File.dirname(__FILE__) + "/../../../../../../lib" +$:.unshift rspec_lib unless $:.include?(rspec_lib) +require 'test/unit' +require 'spec' + +describe "example group with passing examples" do + it "should pass" do + true.should be_true + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/interop/test/unit/resources/spec_with_errors.rb b/vendor/gems/rspec/spec/spec/interop/test/unit/resources/spec_with_errors.rb new file mode 100644 index 0000000000..71427dbaad --- /dev/null +++ b/vendor/gems/rspec/spec/spec/interop/test/unit/resources/spec_with_errors.rb @@ -0,0 +1,10 @@ +rspec_lib = File.dirname(__FILE__) + "/../../../../../../lib" +$:.unshift rspec_lib unless $:.include?(rspec_lib) +require 'test/unit' +require 'spec' + +describe "example group with errors" do + it "should raise errors" do + raise "error raised in example group with errors" + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/interop/test/unit/resources/test_case_that_fails.rb b/vendor/gems/rspec/spec/spec/interop/test/unit/resources/test_case_that_fails.rb new file mode 100644 index 0000000000..3fb6515a89 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/interop/test/unit/resources/test_case_that_fails.rb @@ -0,0 +1,10 @@ +rspec_lib = File.dirname(__FILE__) + "/../../../../../../lib" +$:.unshift rspec_lib unless $:.include?(rspec_lib) +require 'test/unit' +require 'spec' + +class TestCaseThatFails < Test::Unit::TestCase + def test_that_fails + false.should be_true + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/interop/test/unit/resources/test_case_that_passes.rb b/vendor/gems/rspec/spec/spec/interop/test/unit/resources/test_case_that_passes.rb new file mode 100644 index 0000000000..69239c0b5a --- /dev/null +++ b/vendor/gems/rspec/spec/spec/interop/test/unit/resources/test_case_that_passes.rb @@ -0,0 +1,10 @@ +rspec_lib = File.dirname(__FILE__) + "/../../../../../../lib" +$:.unshift rspec_lib unless $:.include?(rspec_lib) +require 'test/unit' +require 'spec' + +class TestCaseThatPasses < Test::Unit::TestCase + def test_that_passes + true.should be_true + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/interop/test/unit/resources/test_case_with_errors.rb b/vendor/gems/rspec/spec/spec/interop/test/unit/resources/test_case_with_errors.rb new file mode 100644 index 0000000000..35dcb6b2e5 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/interop/test/unit/resources/test_case_with_errors.rb @@ -0,0 +1,10 @@ +rspec_lib = File.dirname(__FILE__) + "/../../../../../../lib" +$:.unshift rspec_lib unless $:.include?(rspec_lib) +require 'test/unit' +require 'spec' + +class TestCaseWithErrors < Test::Unit::TestCase + def test_with_error + raise "error raised in TestCaseWithErrors" + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/interop/test/unit/resources/testsuite_adapter_spec_with_test_unit.rb b/vendor/gems/rspec/spec/spec/interop/test/unit/resources/testsuite_adapter_spec_with_test_unit.rb new file mode 100644 index 0000000000..0c2167a991 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/interop/test/unit/resources/testsuite_adapter_spec_with_test_unit.rb @@ -0,0 +1,38 @@ +rspec_lib = File.dirname(__FILE__) + "/../../../../../../lib" +$:.unshift rspec_lib unless $:.include?(rspec_lib) +require "test/unit" +require "spec" + +module Test + module Unit + describe TestSuiteAdapter do + def create_adapter(group) + TestSuiteAdapter.new(group) + end + + describe "#size" do + it "should return the number of examples in the example group" do + group = Class.new(Spec::ExampleGroup) do + describe("some examples") + it("bar") {} + it("baz") {} + end + adapter = create_adapter(group) + adapter.size.should == 2 + end + end + + describe "#delete" do + it "should do nothing" do + group = Class.new(Spec::ExampleGroup) do + describe("Some Examples") + it("does something") {} + end + adapter = create_adapter(group) + adapter.delete(adapter.examples.first) + adapter.should be_empty + end + end + end + end +end diff --git a/vendor/gems/rspec/spec/spec/interop/test/unit/spec_spec.rb b/vendor/gems/rspec/spec/spec/interop/test/unit/spec_spec.rb new file mode 100644 index 0000000000..8a1e1300c5 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/interop/test/unit/spec_spec.rb @@ -0,0 +1,45 @@ +require File.dirname(__FILE__) + '/test_unit_spec_helper' + +describe "ExampleGroup with test/unit/interop" do + include TestUnitSpecHelper + + before(:each) do + @dir = File.dirname(__FILE__) + "/resources" + end + + describe "with passing examples" do + it "should output 0 failures" do + output = ruby("#{@dir}/spec_that_passes.rb") + output.should include("1 example, 0 failures") + end + + it "should return an exit code of 0" do + ruby("#{@dir}/spec_that_passes.rb") + $?.should == 0 + end + end + + describe "with failing examples" do + it "should output 1 failure" do + output = ruby("#{@dir}/spec_that_fails.rb") + output.should include("1 example, 1 failure") + end + + it "should return an exit code of 256" do + ruby("#{@dir}/spec_that_fails.rb") + $?.should == 256 + end + end + + describe "with example that raises an error" do + it "should output 1 failure" do + output = ruby("#{@dir}/spec_with_errors.rb") + output.should include("1 example, 1 failure") + end + + it "should return an exit code of 256" do + ruby("#{@dir}/spec_with_errors.rb") + $?.should == 256 + end + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/interop/test/unit/test_unit_spec_helper.rb b/vendor/gems/rspec/spec/spec/interop/test/unit/test_unit_spec_helper.rb new file mode 100644 index 0000000000..04d5d27135 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/interop/test/unit/test_unit_spec_helper.rb @@ -0,0 +1,14 @@ +require File.dirname(__FILE__) + '/../../../../spec_helper' +require File.dirname(__FILE__) + '/../../../../ruby_forker' + +module TestUnitSpecHelper + include RubyForker + + def run_script(file_name) + output = ruby(file_name) + if !$?.success? || output.include?("FAILED") || output.include?("Error") + raise output + end + output + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/interop/test/unit/testcase_spec.rb b/vendor/gems/rspec/spec/spec/interop/test/unit/testcase_spec.rb new file mode 100644 index 0000000000..f40111a584 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/interop/test/unit/testcase_spec.rb @@ -0,0 +1,45 @@ +require File.dirname(__FILE__) + '/test_unit_spec_helper' + +describe "Test::Unit::TestCase" do + include TestUnitSpecHelper + + before(:each) do + @dir = File.dirname(__FILE__) + "/resources" + end + + describe "with passing test case" do + it "should output 0 failures" do + output = ruby("#{@dir}/test_case_that_passes.rb") + output.should include("1 example, 0 failures") + end + + it "should return an exit code of 0" do + ruby("#{@dir}/test_case_that_passes.rb") + $?.should == 0 + end + end + + describe "with failing test case" do + it "should output 1 failure" do + output = ruby("#{@dir}/test_case_that_fails.rb") + output.should include("1 example, 1 failure") + end + + it "should return an exit code of 256" do + ruby("#{@dir}/test_case_that_fails.rb") + $?.should == 256 + end + end + + describe "with test case that raises an error" do + it "should output 1 failure" do + output = ruby("#{@dir}/test_case_with_errors.rb") + output.should include("1 example, 1 failure") + end + + it "should return an exit code of 256" do + ruby("#{@dir}/test_case_with_errors.rb") + $?.should == 256 + end + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/interop/test/unit/testsuite_adapter_spec.rb b/vendor/gems/rspec/spec/spec/interop/test/unit/testsuite_adapter_spec.rb new file mode 100644 index 0000000000..722126bc98 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/interop/test/unit/testsuite_adapter_spec.rb @@ -0,0 +1,9 @@ +require File.dirname(__FILE__) + '/test_unit_spec_helper' + +describe "TestSuiteAdapter" do + include TestUnitSpecHelper + it "should pass" do + dir = File.dirname(__FILE__) + run_script "#{dir}/resources/testsuite_adapter_spec_with_test_unit.rb" + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/matchers/be_close_spec.rb b/vendor/gems/rspec/spec/spec/matchers/be_close_spec.rb new file mode 100644 index 0000000000..d8452d408e --- /dev/null +++ b/vendor/gems/rspec/spec/spec/matchers/be_close_spec.rb @@ -0,0 +1,39 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' +module Spec + module Matchers + describe BeClose do + it "should match when value == target" do + BeClose.new(5.0, 0.5).matches?(5.0).should be_true + end + it "should match when value < (target + delta)" do + BeClose.new(5.0, 0.5).matches?(5.49).should be_true + end + it "should match when value > (target - delta)" do + BeClose.new(5.0, 0.5).matches?(4.51).should be_true + end + it "should not match when value == (target - delta)" do + BeClose.new(5.0, 0.5).matches?(4.5).should be_false + end + it "should not match when value < (target - delta)" do + BeClose.new(5.0, 0.5).matches?(4.49).should be_false + end + it "should not match when value == (target + delta)" do + BeClose.new(5.0, 0.5).matches?(5.5).should be_false + end + it "should not match when value > (target + delta)" do + BeClose.new(5.0, 0.5).matches?(5.51).should be_false + end + it "should provide a useful failure message" do + #given + matcher = BeClose.new(5.0, 0.5) + #when + matcher.matches?(5.51) + #then + matcher.failure_message.should == "expected 5.0 +/- (< 0.5), got 5.51" + end + it "should describe itself" do + BeClose.new(5.0, 0.5).description.should == "be close to 5.0 (within +- 0.5)" + end + end + end +end diff --git a/vendor/gems/rspec/spec/spec/matchers/be_spec.rb b/vendor/gems/rspec/spec/spec/matchers/be_spec.rb new file mode 100644 index 0000000000..d40036c79c --- /dev/null +++ b/vendor/gems/rspec/spec/spec/matchers/be_spec.rb @@ -0,0 +1,224 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +describe "should be_predicate" do + it "should pass when actual returns true for :predicate?" do + actual = stub("actual", :happy? => true) + actual.should be_happy + end + + it "should pass when actual returns true for :predicates? (present tense)" do + actual = stub("actual", :exists? => true) + actual.should be_exist + end + + it "should fail when actual returns false for :predicate?" do + actual = stub("actual", :happy? => false) + lambda { + actual.should be_happy + }.should fail_with("expected happy? to return true, got false") + end + + it "should fail when actual does not respond to :predicate?" do + lambda { + Object.new.should be_happy + }.should raise_error(NameError) + end +end + +describe "should_not be_predicate" do + it "should pass when actual returns false for :sym?" do + actual = stub("actual", :happy? => false) + actual.should_not be_happy + end + + it "should fail when actual returns true for :sym?" do + actual = stub("actual", :happy? => true) + lambda { + actual.should_not be_happy + }.should fail_with("expected happy? to return false, got true") + end + + it "should fail when actual does not respond to :sym?" do + lambda { + Object.new.should_not be_happy + }.should raise_error(NameError) + end +end + +describe "should be_predicate(*args)" do + it "should pass when actual returns true for :predicate?(*args)" do + actual = mock("actual") + actual.should_receive(:older_than?).with(3).and_return(true) + actual.should be_older_than(3) + end + + it "should fail when actual returns false for :predicate?(*args)" do + actual = mock("actual") + actual.should_receive(:older_than?).with(3).and_return(false) + lambda { + actual.should be_older_than(3) + }.should fail_with("expected older_than?(3) to return true, got false") + end + + it "should fail when actual does not respond to :predicate?" do + lambda { + Object.new.should be_older_than(3) + }.should raise_error(NameError) + end +end + +describe "should_not be_predicate(*args)" do + it "should pass when actual returns false for :predicate?(*args)" do + actual = mock("actual") + actual.should_receive(:older_than?).with(3).and_return(false) + actual.should_not be_older_than(3) + end + + it "should fail when actual returns true for :predicate?(*args)" do + actual = mock("actual") + actual.should_receive(:older_than?).with(3).and_return(true) + lambda { + actual.should_not be_older_than(3) + }.should fail_with("expected older_than?(3) to return false, got true") + end + + it "should fail when actual does not respond to :predicate?" do + lambda { + Object.new.should_not be_older_than(3) + }.should raise_error(NameError) + end +end + +describe "should be_true" do + it "should pass when actual equal(true)" do + true.should be_true + end + + it "should fail when actual equal(false)" do + lambda { + false.should be_true + }.should fail_with("expected true, got false") + end +end + +describe "should be_false" do + it "should pass when actual equal(false)" do + false.should be_false + end + + it "should fail when actual equal(true)" do + lambda { + true.should be_false + }.should fail_with("expected false, got true") + end +end + +describe "should be_nil" do + it "should pass when actual is nil" do + nil.should be_nil + end + + it "should fail when actual is not nil" do + lambda { + :not_nil.should be_nil + }.should fail_with("expected nil, got :not_nil") + end +end + +describe "should_not be_nil" do + it "should pass when actual is not nil" do + :not_nil.should_not be_nil + end + + it "should fail when actual is nil" do + lambda { + nil.should_not be_nil + }.should fail_with("expected not nil, got nil") + end +end + +describe "should be <" do + it "should pass when < operator returns true" do + 3.should be < 4 + end + + it "should fail when < operator returns false" do + lambda { 3.should be < 3 }.should fail_with("expected < 3, got 3") + end +end + +describe "should be <=" do + it "should pass when <= operator returns true" do + 3.should be <= 4 + 4.should be <= 4 + end + + it "should fail when <= operator returns false" do + lambda { 3.should be <= 2 }.should fail_with("expected <= 2, got 3") + end +end + +describe "should be >=" do + it "should pass when >= operator returns true" do + 4.should be >= 4 + 5.should be >= 4 + end + + it "should fail when >= operator returns false" do + lambda { 3.should be >= 4 }.should fail_with("expected >= 4, got 3") + end +end + +describe "should be >" do + it "should pass when > operator returns true" do + 5.should be > 4 + end + + it "should fail when > operator returns false" do + lambda { 3.should be > 4 }.should fail_with("expected > 4, got 3") + end +end + +describe "should be ==" do + it "should pass when == operator returns true" do + 5.should be == 5 + end + + it "should fail when == operator returns false" do + lambda { 3.should be == 4 }.should fail_with("expected == 4, got 3") + end +end + +describe "should be ===" do + it "should pass when === operator returns true" do + Hash.should be === Hash.new + end + + it "should fail when === operator returns false" do + lambda { Hash.should be === "not a hash" }.should fail_with(%[expected === "not a hash", got Hash]) + end +end + +describe "should be" do + it "should pass if actual is true or a set value" do + true.should be + 1.should be + end + + it "should fail if actual is false" do + lambda {false.should be}.should fail_with("expected if to be satisfied, got false") + end + + it "should fail if actual is nil" do + lambda {nil.should be}.should fail_with("expected if to be satisfied, got nil") + end +end + +describe "should be(value)" do + it "should pass if actual.equal?(value)" do + 5.should be(5) + end + it "should fail if !actual.equal?(value)" do + lambda { 5.should be(6) }.should fail_with("expected 6, got 5") + end +end diff --git a/vendor/gems/rspec/spec/spec/matchers/change_spec.rb b/vendor/gems/rspec/spec/spec/matchers/change_spec.rb new file mode 100644 index 0000000000..d95aa6da41 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/matchers/change_spec.rb @@ -0,0 +1,319 @@ +#Based on patch from Wilson Bilkovich + +require File.dirname(__FILE__) + '/../../spec_helper.rb' +class SomethingExpected + attr_accessor :some_value +end + +describe "should change(actual, message)" do + before(:each) do + @instance = SomethingExpected.new + @instance.some_value = 5 + end + + it "should pass when actual is modified by the block" do + lambda {@instance.some_value = 6}.should change(@instance, :some_value) + end + + it "should fail when actual is not modified by the block" do + lambda do + lambda {}.should change(@instance, :some_value) + end.should fail_with("some_value should have changed, but is still 5") + end +end + +describe "should_not change(actual, message)" do + before(:each) do + @instance = SomethingExpected.new + @instance.some_value = 5 + end + + it "should pass when actual is not modified by the block" do + lambda { }.should_not change(@instance, :some_value) + end + + it "should fail when actual is not modified by the block" do + lambda do + lambda {@instance.some_value = 6}.should_not change(@instance, :some_value) + end.should fail_with("some_value should not have changed, but did change from 5 to 6") + end +end + +describe "should change { block }" do + before(:each) do + @instance = SomethingExpected.new + @instance.some_value = 5 + end + + it "should pass when actual is modified by the block" do + lambda {@instance.some_value = 6}.should change { @instance.some_value } + end + + it "should fail when actual is not modified by the block" do + lambda do + lambda {}.should change{ @instance.some_value } + end.should fail_with("result should have changed, but is still 5") + end + + it "should warn if passed a block using do/end" do + lambda do + lambda {}.should change do + end + end.should raise_error(Spec::Matchers::MatcherError, /block passed to should or should_not/) + end +end + +describe "should_not change { block }" do + before(:each) do + @instance = SomethingExpected.new + @instance.some_value = 5 + end + + it "should pass when actual is modified by the block" do + lambda {}.should_not change{ @instance.some_value } + end + + it "should fail when actual is not modified by the block" do + lambda do + lambda {@instance.some_value = 6}.should_not change { @instance.some_value } + end.should fail_with("result should not have changed, but did change from 5 to 6") + end + + it "should warn if passed a block using do/end" do + lambda do + lambda {}.should_not change do + end + end.should raise_error(Spec::Matchers::MatcherError, /block passed to should or should_not/) + end +end + +describe "should change(actual, message).by(expected)" do + before(:each) do + @instance = SomethingExpected.new + @instance.some_value = 5 + end + + it "should pass when attribute is changed by expected amount" do + lambda { @instance.some_value += 1 }.should change(@instance, :some_value).by(1) + end + + it "should fail when the attribute is changed by unexpected amount" do + lambda do + lambda { @instance.some_value += 2 }.should change(@instance, :some_value).by(1) + end.should fail_with("some_value should have been changed by 1, but was changed by 2") + end + + it "should fail when the attribute is changed by unexpected amount in the opposite direction" do + lambda do + lambda { @instance.some_value -= 1 }.should change(@instance, :some_value).by(1) + end.should fail_with("some_value should have been changed by 1, but was changed by -1") + end +end + +describe "should change{ block }.by(expected)" do + before(:each) do + @instance = SomethingExpected.new + @instance.some_value = 5 + end + + it "should pass when attribute is changed by expected amount" do + lambda { @instance.some_value += 1 }.should change{@instance.some_value}.by(1) + end + + it "should fail when the attribute is changed by unexpected amount" do + lambda do + lambda { @instance.some_value += 2 }.should change{@instance.some_value}.by(1) + end.should fail_with("result should have been changed by 1, but was changed by 2") + end + + it "should fail when the attribute is changed by unexpected amount in the opposite direction" do + lambda do + lambda { @instance.some_value -= 1 }.should change{@instance.some_value}.by(1) + end.should fail_with("result should have been changed by 1, but was changed by -1") + end +end + +describe "should change(actual, message).by_at_least(expected)" do + before(:each) do + @instance = SomethingExpected.new + @instance.some_value = 5 + end + + it "should pass when attribute is changed by greater than the expected amount" do + lambda { @instance.some_value += 2 }.should change(@instance, :some_value).by_at_least(1) + end + + it "should pass when attribute is changed by the expected amount" do + lambda { @instance.some_value += 2 }.should change(@instance, :some_value).by_at_least(2) + end + + it "should fail when the attribute is changed by less than the expected amount" do + lambda do + lambda { @instance.some_value += 1 }.should change(@instance, :some_value).by_at_least(2) + end.should fail_with("some_value should have been changed by at least 2, but was changed by 1") + end + +end + +describe "should change{ block }.by_at_least(expected)" do + before(:each) do + @instance = SomethingExpected.new + @instance.some_value = 5 + end + + it "should pass when attribute is changed by greater than expected amount" do + lambda { @instance.some_value += 2 }.should change{@instance.some_value}.by_at_least(1) + end + + it "should pass when attribute is changed by the expected amount" do + lambda { @instance.some_value += 2 }.should change{@instance.some_value}.by_at_least(2) + end + + it "should fail when the attribute is changed by less than the unexpected amount" do + lambda do + lambda { @instance.some_value += 1 }.should change{@instance.some_value}.by_at_least(2) + end.should fail_with("result should have been changed by at least 2, but was changed by 1") + end +end + + +describe "should change(actual, message).by_at_most(expected)" do + before(:each) do + @instance = SomethingExpected.new + @instance.some_value = 5 + end + + it "should pass when attribute is changed by less than the expected amount" do + lambda { @instance.some_value += 2 }.should change(@instance, :some_value).by_at_most(3) + end + + it "should pass when attribute is changed by the expected amount" do + lambda { @instance.some_value += 2 }.should change(@instance, :some_value).by_at_most(2) + end + + it "should fail when the attribute is changed by greater than the expected amount" do + lambda do + lambda { @instance.some_value += 2 }.should change(@instance, :some_value).by_at_most(1) + end.should fail_with("some_value should have been changed by at most 1, but was changed by 2") + end + +end + +describe "should change{ block }.by_at_most(expected)" do + before(:each) do + @instance = SomethingExpected.new + @instance.some_value = 5 + end + + it "should pass when attribute is changed by less than expected amount" do + lambda { @instance.some_value += 2 }.should change{@instance.some_value}.by_at_most(3) + end + + it "should pass when attribute is changed by the expected amount" do + lambda { @instance.some_value += 2 }.should change{@instance.some_value}.by_at_most(2) + end + + it "should fail when the attribute is changed by greater than the unexpected amount" do + lambda do + lambda { @instance.some_value += 2 }.should change{@instance.some_value}.by_at_most(1) + end.should fail_with("result should have been changed by at most 1, but was changed by 2") + end +end + +describe "should change(actual, message).from(old)" do + before(:each) do + @instance = SomethingExpected.new + @instance.some_value = 'string' + end + + it "should pass when attribute is == to expected value before executing block" do + lambda { @instance.some_value = "astring" }.should change(@instance, :some_value).from("string") + end + + it "should fail when attribute is not == to expected value before executing block" do + lambda do + lambda { @instance.some_value = "knot" }.should change(@instance, :some_value).from("cat") + end.should fail_with("some_value should have initially been \"cat\", but was \"string\"") + end +end + +describe "should change{ block }.from(old)" do + before(:each) do + @instance = SomethingExpected.new + @instance.some_value = 'string' + end + + it "should pass when attribute is == to expected value before executing block" do + lambda { @instance.some_value = "astring" }.should change{@instance.some_value}.from("string") + end + + it "should fail when attribute is not == to expected value before executing block" do + lambda do + lambda { @instance.some_value = "knot" }.should change{@instance.some_value}.from("cat") + end.should fail_with("result should have initially been \"cat\", but was \"string\"") + end +end + +describe "should change(actual, message).to(new)" do + before(:each) do + @instance = SomethingExpected.new + @instance.some_value = 'string' + end + + it "should pass when attribute is == to expected value after executing block" do + lambda { @instance.some_value = "cat" }.should change(@instance, :some_value).to("cat") + end + + it "should fail when attribute is not == to expected value after executing block" do + lambda do + lambda { @instance.some_value = "cat" }.should change(@instance, :some_value).from("string").to("dog") + end.should fail_with("some_value should have been changed to \"dog\", but is now \"cat\"") + end +end + +describe "should change{ block }.to(new)" do + before(:each) do + @instance = SomethingExpected.new + @instance.some_value = 'string' + end + + it "should pass when attribute is == to expected value after executing block" do + lambda { @instance.some_value = "cat" }.should change{@instance.some_value}.to("cat") + end + + it "should fail when attribute is not == to expected value after executing block" do + lambda do + lambda { @instance.some_value = "cat" }.should change{@instance.some_value}.from("string").to("dog") + end.should fail_with("result should have been changed to \"dog\", but is now \"cat\"") + end +end + +describe "should change(actual, message).from(old).to(new)" do + before(:each) do + @instance = SomethingExpected.new + @instance.some_value = 'string' + end + + it "should pass when #to comes before #from" do + lambda { @instance.some_value = "cat" }.should change(@instance, :some_value).to("cat").from("string") + end + + it "should pass when #from comes before #to" do + lambda { @instance.some_value = "cat" }.should change(@instance, :some_value).from("string").to("cat") + end +end + +describe "should change{ block }.from(old).to(new)" do + before(:each) do + @instance = SomethingExpected.new + @instance.some_value = 'string' + end + + it "should pass when #to comes before #from" do + lambda { @instance.some_value = "cat" }.should change{@instance.some_value}.to("cat").from("string") + end + + it "should pass when #from comes before #to" do + lambda { @instance.some_value = "cat" }.should change{@instance.some_value}.from("string").to("cat") + end +end diff --git a/vendor/gems/rspec/spec/spec/matchers/description_generation_spec.rb b/vendor/gems/rspec/spec/spec/matchers/description_generation_spec.rb new file mode 100644 index 0000000000..c494e2165f --- /dev/null +++ b/vendor/gems/rspec/spec/spec/matchers/description_generation_spec.rb @@ -0,0 +1,153 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +describe "Matchers should be able to generate their own descriptions" do + after(:each) do + Spec::Matchers.clear_generated_description + end + + it "should == expected" do + "this".should == "this" + Spec::Matchers.generated_description.should == "should == \"this\"" + end + + it "should not == expected" do + "this".should_not == "that" + Spec::Matchers.generated_description.should == "should not == \"that\"" + end + + it "should be empty (arbitrary predicate)" do + [].should be_empty + Spec::Matchers.generated_description.should == "should be empty" + end + + it "should not be empty (arbitrary predicate)" do + [1].should_not be_empty + Spec::Matchers.generated_description.should == "should not be empty" + end + + it "should be true" do + true.should be_true + Spec::Matchers.generated_description.should == "should be true" + end + + it "should be false" do + false.should be_false + Spec::Matchers.generated_description.should == "should be false" + end + + it "should be nil" do + nil.should be_nil + Spec::Matchers.generated_description.should == "should be nil" + end + + it "should be > n" do + 5.should be > 3 + Spec::Matchers.generated_description.should == "should be > 3" + end + + it "should be predicate arg1, arg2 and arg3" do + 5.0.should be_between(0,10) + Spec::Matchers.generated_description.should == "should be between 0 and 10" + end + + it "should be_few_words predicate should be transformed to 'be few words'" do + 5.should be_kind_of(Fixnum) + Spec::Matchers.generated_description.should == "should be kind of Fixnum" + end + + it "should preserve a proper prefix for be predicate" do + 5.should be_a_kind_of(Fixnum) + Spec::Matchers.generated_description.should == "should be a kind of Fixnum" + 5.should be_an_instance_of(Fixnum) + Spec::Matchers.generated_description.should == "should be an instance of Fixnum" + end + + it "should equal" do + expected = "expected" + expected.should equal(expected) + Spec::Matchers.generated_description.should == "should equal \"expected\"" + end + + it "should_not equal" do + 5.should_not equal(37) + Spec::Matchers.generated_description.should == "should not equal 37" + end + + it "should eql" do + "string".should eql("string") + Spec::Matchers.generated_description.should == "should eql \"string\"" + end + + it "should not eql" do + "a".should_not eql(:a) + Spec::Matchers.generated_description.should == "should not eql :a" + end + + it "should have_key" do + {:a => "a"}.should have_key(:a) + Spec::Matchers.generated_description.should == "should have key :a" + end + + it "should have n items" do + team.should have(3).players + Spec::Matchers.generated_description.should == "should have 3 players" + end + + it "should have at least n items" do + team.should have_at_least(2).players + Spec::Matchers.generated_description.should == "should have at least 2 players" + end + + it "should have at most n items" do + team.should have_at_most(4).players + Spec::Matchers.generated_description.should == "should have at most 4 players" + end + + it "should include" do + [1,2,3].should include(3) + Spec::Matchers.generated_description.should == "should include 3" + end + + it "should match" do + "this string".should match(/this string/) + Spec::Matchers.generated_description.should == "should match /this string/" + end + + it "should raise_error" do + lambda { raise }.should raise_error + Spec::Matchers.generated_description.should == "should raise Exception" + end + + it "should raise_error with type" do + lambda { raise }.should raise_error(RuntimeError) + Spec::Matchers.generated_description.should == "should raise RuntimeError" + end + + it "should raise_error with type and message" do + lambda { raise "there was an error" }.should raise_error(RuntimeError, "there was an error") + Spec::Matchers.generated_description.should == "should raise RuntimeError with \"there was an error\"" + end + + it "should respond_to" do + [].should respond_to(:insert) + Spec::Matchers.generated_description.should == "should respond to #insert" + end + + it "should throw symbol" do + lambda { throw :what_a_mess }.should throw_symbol + Spec::Matchers.generated_description.should == "should throw a Symbol" + end + + it "should throw symbol (with named symbol)" do + lambda { throw :what_a_mess }.should throw_symbol(:what_a_mess) + Spec::Matchers.generated_description.should == "should throw :what_a_mess" + end + + def team + Class.new do + def players + [1,2,3] + end + end.new + end +end diff --git a/vendor/gems/rspec/spec/spec/matchers/eql_spec.rb b/vendor/gems/rspec/spec/spec/matchers/eql_spec.rb new file mode 100644 index 0000000000..3f265d7000 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/matchers/eql_spec.rb @@ -0,0 +1,28 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +module Spec + module Matchers + describe Eql do + it "should match when actual.eql?(expected)" do + Eql.new(1).matches?(1).should be_true + end + it "should not match when !actual.eql?(expected)" do + Eql.new(1).matches?(2).should be_false + end + it "should describe itself" do + matcher = Eql.new(1) + matcher.description.should == "eql 1" + end + it "should provide message, expected and actual on #failure_message" do + matcher = Eql.new("1") + matcher.matches?(1) + matcher.failure_message.should == ["expected \"1\", got 1 (using .eql?)", "1", 1] + end + it "should provide message, expected and actual on #negative_failure_message" do + matcher = Eql.new(1) + matcher.matches?(1) + matcher.negative_failure_message.should == ["expected 1 not to equal 1 (using .eql?)", 1, 1] + end + end + end +end diff --git a/vendor/gems/rspec/spec/spec/matchers/equal_spec.rb b/vendor/gems/rspec/spec/spec/matchers/equal_spec.rb new file mode 100644 index 0000000000..7667bdc388 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/matchers/equal_spec.rb @@ -0,0 +1,28 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +module Spec + module Matchers + describe Equal do + it "should match when actual.equal?(expected)" do + Equal.new(1).matches?(1).should be_true + end + it "should not match when !actual.equal?(expected)" do + Equal.new("1").matches?("1").should be_false + end + it "should describe itself" do + matcher = Equal.new(1) + matcher.description.should == "equal 1" + end + it "should provide message, expected and actual on #failure_message" do + matcher = Equal.new("1") + matcher.matches?(1) + matcher.failure_message.should == ["expected \"1\", got 1 (using .equal?)", "1", 1] + end + it "should provide message, expected and actual on #negative_failure_message" do + matcher = Equal.new(1) + matcher.matches?(1) + matcher.negative_failure_message.should == ["expected 1 not to equal 1 (using .equal?)", 1, 1] + end + end + end +end diff --git a/vendor/gems/rspec/spec/spec/matchers/exist_spec.rb b/vendor/gems/rspec/spec/spec/matchers/exist_spec.rb new file mode 100644 index 0000000000..0a509726eb --- /dev/null +++ b/vendor/gems/rspec/spec/spec/matchers/exist_spec.rb @@ -0,0 +1,57 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +class Substance + def initialize exists, description + @exists = exists + @description = description + end + def exist? + @exists + end + def inspect + @description + end +end + +class SubstanceTester + include Spec::Matchers + def initialize substance + @substance = substance + end + def should_exist + @substance.should exist + end +end + +describe "should exist," do + + before(:each) do + @real = Substance.new true, 'something real' + @imaginary = Substance.new false, 'something imaginary' + end + + describe "within an example group" do + + it "should pass if target exists" do + @real.should exist + end + + it "should fail if target does not exist" do + lambda { @imaginary.should exist }.should fail + end + + it "should pass if target doesn't exist" do + lambda { @real.should_not exist }.should fail + end + end + + describe "outside of an example group" do + + it "should pass if target exists" do + real_tester = SubstanceTester.new @real + real_tester.should_exist + end + + end + +end diff --git a/vendor/gems/rspec/spec/spec/matchers/handler_spec.rb b/vendor/gems/rspec/spec/spec/matchers/handler_spec.rb new file mode 100644 index 0000000000..ad4fe6f856 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/matchers/handler_spec.rb @@ -0,0 +1,129 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +module ExampleExpectations + + class ArbitraryMatcher + def initialize(*args, &block) + if args.last.is_a? Hash + @expected = args.last[:expected] + end + if block_given? + @expected = block.call + end + @block = block + end + + def matches?(target) + @target = target + return @expected == target + end + + def with(new_value) + @expected = new_value + self + end + + def failure_message + "expected #{@expected}, got #{@target}" + end + + def negative_failure_message + "expected not #{@expected}, got #{@target}" + end + end + + class PositiveOnlyMatcher < ArbitraryMatcher + undef negative_failure_message rescue nil + end + + def arbitrary_matcher(*args, &block) + ArbitraryMatcher.new(*args, &block) + end + + def positive_only_matcher(*args, &block) + PositiveOnlyMatcher.new(*args, &block) + end + +end + +module Spec + module Expectations + describe ExpectationMatcherHandler, ".handle_matcher" do + it "should ask the matcher if it matches" do + matcher = mock("matcher") + actual = Object.new + matcher.should_receive(:matches?).with(actual).and_return(true) + ExpectationMatcherHandler.handle_matcher(actual, matcher) + end + + it "should explain when the matcher parameter is not a matcher" do + begin + nonmatcher = mock("nonmatcher") + actual = Object.new + ExpectationMatcherHandler.handle_matcher(actual, nonmatcher) + rescue Spec::Expectations::InvalidMatcherError => e + end + + e.message.should =~ /^Expected a matcher, got / + end + end + + describe NegativeExpectationMatcherHandler, ".handle_matcher" do + it "should explain when matcher does not support should_not" do + matcher = mock("matcher") + matcher.stub!(:matches?) + actual = Object.new + lambda { + NegativeExpectationMatcherHandler.handle_matcher(actual, matcher) + }.should fail_with(/Matcher does not support should_not.\n/) + end + + it "should ask the matcher if it matches" do + matcher = mock("matcher") + actual = Object.new + matcher.stub!(:negative_failure_message) + matcher.should_receive(:matches?).with(actual).and_return(false) + NegativeExpectationMatcherHandler.handle_matcher(actual, matcher) + end + + it "should explain when the matcher parameter is not a matcher" do + begin + nonmatcher = mock("nonmatcher") + actual = Object.new + NegativeExpectationMatcherHandler.handle_matcher(actual, nonmatcher) + rescue Spec::Expectations::InvalidMatcherError => e + end + + e.message.should =~ /^Expected a matcher, got / + end + end + + describe ExpectationMatcherHandler do + include ExampleExpectations + + it "should handle submitted args" do + 5.should arbitrary_matcher(:expected => 5) + 5.should arbitrary_matcher(:expected => "wrong").with(5) + lambda { 5.should arbitrary_matcher(:expected => 4) }.should fail_with("expected 4, got 5") + lambda { 5.should arbitrary_matcher(:expected => 5).with(4) }.should fail_with("expected 4, got 5") + 5.should_not arbitrary_matcher(:expected => 4) + 5.should_not arbitrary_matcher(:expected => 5).with(4) + lambda { 5.should_not arbitrary_matcher(:expected => 5) }.should fail_with("expected not 5, got 5") + lambda { 5.should_not arbitrary_matcher(:expected => 4).with(5) }.should fail_with("expected not 5, got 5") + end + + it "should handle the submitted block" do + 5.should arbitrary_matcher { 5 } + 5.should arbitrary_matcher(:expected => 4) { 5 } + 5.should arbitrary_matcher(:expected => 4).with(5) { 3 } + end + + it "should explain when matcher does not support should_not" do + lambda { + 5.should_not positive_only_matcher(:expected => 5) + }.should fail_with(/Matcher does not support should_not.\n/) + end + + end + end +end diff --git a/vendor/gems/rspec/spec/spec/matchers/has_spec.rb b/vendor/gems/rspec/spec/spec/matchers/has_spec.rb new file mode 100644 index 0000000000..47f048ebf0 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/matchers/has_spec.rb @@ -0,0 +1,37 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +describe "should have_sym(*args)" do + it "should pass if #has_sym?(*args) returns true" do + {:a => "A"}.should have_key(:a) + end + + it "should fail if #has_sym?(*args) returns false" do + lambda { + {:b => "B"}.should have_key(:a) + }.should fail_with("expected #has_key?(:a) to return true, got false") + end + + it "should fail if target does not respond to #has_sym?" do + lambda { + Object.new.should have_key(:a) + }.should raise_error(NoMethodError) + end +end + +describe "should_not have_sym(*args)" do + it "should pass if #has_sym?(*args) returns false" do + {:a => "A"}.should_not have_key(:b) + end + + it "should fail if #has_sym?(*args) returns true" do + lambda { + {:a => "A"}.should_not have_key(:a) + }.should fail_with("expected #has_key?(:a) to return false, got true") + end + + it "should fail if target does not respond to #has_sym?" do + lambda { + Object.new.should have_key(:a) + }.should raise_error(NoMethodError) + end +end diff --git a/vendor/gems/rspec/spec/spec/matchers/have_spec.rb b/vendor/gems/rspec/spec/spec/matchers/have_spec.rb new file mode 100644 index 0000000000..27083c294e --- /dev/null +++ b/vendor/gems/rspec/spec/spec/matchers/have_spec.rb @@ -0,0 +1,291 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +module HaveSpecHelper + def create_collection_owner_with(n) + owner = Spec::Expectations::Helper::CollectionOwner.new + (1..n).each do |n| + owner.add_to_collection_with_length_method(n) + owner.add_to_collection_with_size_method(n) + end + owner + end +end + +describe "should have(n).items" do + include HaveSpecHelper + + it "should pass if target has a collection of items with n members" do + owner = create_collection_owner_with(3) + owner.should have(3).items_in_collection_with_length_method + owner.should have(3).items_in_collection_with_size_method + end + + it "should convert :no to 0" do + owner = create_collection_owner_with(0) + owner.should have(:no).items_in_collection_with_length_method + owner.should have(:no).items_in_collection_with_size_method + end + + it "should fail if target has a collection of items with < n members" do + owner = create_collection_owner_with(3) + lambda { + owner.should have(4).items_in_collection_with_length_method + }.should fail_with("expected 4 items_in_collection_with_length_method, got 3") + lambda { + owner.should have(4).items_in_collection_with_size_method + }.should fail_with("expected 4 items_in_collection_with_size_method, got 3") + end + + it "should fail if target has a collection of items with > n members" do + owner = create_collection_owner_with(3) + lambda { + owner.should have(2).items_in_collection_with_length_method + }.should fail_with("expected 2 items_in_collection_with_length_method, got 3") + lambda { + owner.should have(2).items_in_collection_with_size_method + }.should fail_with("expected 2 items_in_collection_with_size_method, got 3") + end +end + +describe 'should have(1).item when Inflector is defined' do + include HaveSpecHelper + + before do + unless Object.const_defined?(:Inflector) + class Inflector + def self.pluralize(string) + string.to_s + 's' + end + end + end + end + + it 'should pluralize the collection name' do + owner = create_collection_owner_with(1) + owner.should have(1).item + end +end + +describe "should have(n).items where result responds to items but returns something other than a collection" do + it "should provide a meaningful error" do + owner = Class.new do + def items + Object.new + end + end.new + lambda do + owner.should have(3).items + end.should raise_error("expected items to be a collection but it does not respond to #length or #size") + end +end + +describe "should_not have(n).items" do + include HaveSpecHelper + + it "should pass if target has a collection of items with < n members" do + owner = create_collection_owner_with(3) + owner.should_not have(4).items_in_collection_with_length_method + owner.should_not have(4).items_in_collection_with_size_method + end + + it "should pass if target has a collection of items with > n members" do + owner = create_collection_owner_with(3) + owner.should_not have(2).items_in_collection_with_length_method + owner.should_not have(2).items_in_collection_with_size_method + end + + it "should fail if target has a collection of items with n members" do + owner = create_collection_owner_with(3) + lambda { + owner.should_not have(3).items_in_collection_with_length_method + }.should fail_with("expected target not to have 3 items_in_collection_with_length_method, got 3") + lambda { + owner.should_not have(3).items_in_collection_with_size_method + }.should fail_with("expected target not to have 3 items_in_collection_with_size_method, got 3") + end +end + +describe "should have_exactly(n).items" do + include HaveSpecHelper + + it "should pass if target has a collection of items with n members" do + owner = create_collection_owner_with(3) + owner.should have_exactly(3).items_in_collection_with_length_method + owner.should have_exactly(3).items_in_collection_with_size_method + end + + it "should convert :no to 0" do + owner = create_collection_owner_with(0) + owner.should have_exactly(:no).items_in_collection_with_length_method + owner.should have_exactly(:no).items_in_collection_with_size_method + end + + it "should fail if target has a collection of items with < n members" do + owner = create_collection_owner_with(3) + lambda { + owner.should have_exactly(4).items_in_collection_with_length_method + }.should fail_with("expected 4 items_in_collection_with_length_method, got 3") + lambda { + owner.should have_exactly(4).items_in_collection_with_size_method + }.should fail_with("expected 4 items_in_collection_with_size_method, got 3") + end + + it "should fail if target has a collection of items with > n members" do + owner = create_collection_owner_with(3) + lambda { + owner.should have_exactly(2).items_in_collection_with_length_method + }.should fail_with("expected 2 items_in_collection_with_length_method, got 3") + lambda { + owner.should have_exactly(2).items_in_collection_with_size_method + }.should fail_with("expected 2 items_in_collection_with_size_method, got 3") + end +end + +describe "should have_at_least(n).items" do + include HaveSpecHelper + + it "should pass if target has a collection of items with n members" do + owner = create_collection_owner_with(3) + owner.should have_at_least(3).items_in_collection_with_length_method + owner.should have_at_least(3).items_in_collection_with_size_method + end + + it "should pass if target has a collection of items with > n members" do + owner = create_collection_owner_with(3) + owner.should have_at_least(2).items_in_collection_with_length_method + owner.should have_at_least(2).items_in_collection_with_size_method + end + + it "should fail if target has a collection of items with < n members" do + owner = create_collection_owner_with(3) + lambda { + owner.should have_at_least(4).items_in_collection_with_length_method + }.should fail_with("expected at least 4 items_in_collection_with_length_method, got 3") + lambda { + owner.should have_at_least(4).items_in_collection_with_size_method + }.should fail_with("expected at least 4 items_in_collection_with_size_method, got 3") + end + + it "should provide educational negative failure messages" do + #given + owner = create_collection_owner_with(3) + length_matcher = have_at_least(3).items_in_collection_with_length_method + size_matcher = have_at_least(3).items_in_collection_with_size_method + + #when + length_matcher.matches?(owner) + size_matcher.matches?(owner) + + #then + length_matcher.negative_failure_message.should == <<-EOF +Isn't life confusing enough? +Instead of having to figure out the meaning of this: + should_not have_at_least(3).items_in_collection_with_length_method +We recommend that you use this instead: + should have_at_most(2).items_in_collection_with_length_method +EOF + + size_matcher.negative_failure_message.should == <<-EOF +Isn't life confusing enough? +Instead of having to figure out the meaning of this: + should_not have_at_least(3).items_in_collection_with_size_method +We recommend that you use this instead: + should have_at_most(2).items_in_collection_with_size_method +EOF + end +end + +describe "should have_at_most(n).items" do + include HaveSpecHelper + + it "should pass if target has a collection of items with n members" do + owner = create_collection_owner_with(3) + owner.should have_at_most(3).items_in_collection_with_length_method + owner.should have_at_most(3).items_in_collection_with_size_method + end + + it "should fail if target has a collection of items with > n members" do + owner = create_collection_owner_with(3) + lambda { + owner.should have_at_most(2).items_in_collection_with_length_method + }.should fail_with("expected at most 2 items_in_collection_with_length_method, got 3") + lambda { + owner.should have_at_most(2).items_in_collection_with_size_method + }.should fail_with("expected at most 2 items_in_collection_with_size_method, got 3") + end + + it "should pass if target has a collection of items with < n members" do + owner = create_collection_owner_with(3) + owner.should have_at_most(4).items_in_collection_with_length_method + owner.should have_at_most(4).items_in_collection_with_size_method + end + + it "should provide educational negative failure messages" do + #given + owner = create_collection_owner_with(3) + length_matcher = have_at_most(3).items_in_collection_with_length_method + size_matcher = have_at_most(3).items_in_collection_with_size_method + + #when + length_matcher.matches?(owner) + size_matcher.matches?(owner) + + #then + length_matcher.negative_failure_message.should == <<-EOF +Isn't life confusing enough? +Instead of having to figure out the meaning of this: + should_not have_at_most(3).items_in_collection_with_length_method +We recommend that you use this instead: + should have_at_least(4).items_in_collection_with_length_method +EOF + + size_matcher.negative_failure_message.should == <<-EOF +Isn't life confusing enough? +Instead of having to figure out the meaning of this: + should_not have_at_most(3).items_in_collection_with_size_method +We recommend that you use this instead: + should have_at_least(4).items_in_collection_with_size_method +EOF + end +end + +describe "have(n).items(args, block)" do + it "should pass args to target" do + target = mock("target") + target.should_receive(:items).with("arg1","arg2").and_return([1,2,3]) + target.should have(3).items("arg1","arg2") + end + + it "should pass block to target" do + target = mock("target") + block = lambda { 5 } + target.should_receive(:items).with("arg1","arg2", block).and_return([1,2,3]) + target.should have(3).items("arg1","arg2", block) + end +end + +describe "have(n).items where target IS a collection" do + it "should reference the number of items IN the collection" do + [1,2,3].should have(3).items + end + + it "should fail when the number of items IN the collection is not as expected" do + lambda { [1,2,3].should have(7).items }.should fail_with("expected 7 items, got 3") + end +end + +describe "have(n).characters where target IS a String" do + it "should pass if the length is correct" do + "this string".should have(11).characters + end + + it "should fail if the length is incorrect" do + lambda { "this string".should have(12).characters }.should fail_with("expected 12 characters, got 11") + end +end + +describe "have(n).things on an object which is not a collection nor contains one" do + it "should fail" do + lambda { Object.new.should have(2).things }.should raise_error(NoMethodError, /undefined method `things' for #" do + + it "should pass if > passes" do + 4.should > 3 + end + + it "should fail if > fails" do + Spec::Expectations.should_receive(:fail_with).with(%[expected: > 5,\n got: 4], 5, 4) + 4.should > 5 + end + +end + +describe "should >=" do + + it "should pass if >= passes" do + 4.should > 3 + 4.should >= 4 + end + + it "should fail if > fails" do + Spec::Expectations.should_receive(:fail_with).with(%[expected: >= 5,\n got: 4], 5, 4) + 4.should >= 5 + end + +end + +describe "should <" do + + it "should pass if < passes" do + 4.should < 5 + end + + it "should fail if > fails" do + Spec::Expectations.should_receive(:fail_with).with(%[expected: < 3,\n got: 4], 3, 4) + 4.should < 3 + end + +end + +describe "should <=" do + + it "should pass if <= passes" do + 4.should <= 5 + 4.should <= 4 + end + + it "should fail if > fails" do + Spec::Expectations.should_receive(:fail_with).with(%[expected: <= 3,\n got: 4], 3, 4) + 4.should <= 3 + end + +end + diff --git a/vendor/gems/rspec/spec/spec/matchers/raise_error_spec.rb b/vendor/gems/rspec/spec/spec/matchers/raise_error_spec.rb new file mode 100644 index 0000000000..7cabf81e87 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/matchers/raise_error_spec.rb @@ -0,0 +1,191 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +describe "should raise_error" do + it "should pass if anything is raised" do + lambda {raise}.should raise_error + end + + it "should fail if nothing is raised" do + lambda { + lambda {}.should raise_error + }.should fail_with("expected Exception but nothing was raised") + end +end + +describe "should_not raise_error" do + it "should pass if nothing is raised" do + lambda {}.should_not raise_error + end + + it "should fail if anything is raised" do + lambda { + lambda {raise}.should_not raise_error + }.should fail_with("expected no Exception, got RuntimeError") + end +end + +describe "should raise_error(message)" do + it "should pass if RuntimeError is raised with the right message" do + lambda {raise 'blah'}.should raise_error('blah') + end + it "should pass if any other error is raised with the right message" do + lambda {raise NameError.new('blah')}.should raise_error('blah') + end + it "should fail if RuntimeError error is raised with the wrong message" do + lambda do + lambda {raise 'blarg'}.should raise_error('blah') + end.should fail_with("expected Exception with \"blah\", got #") + end + it "should fail if any other error is raised with the wrong message" do + lambda do + lambda {raise NameError.new('blarg')}.should raise_error('blah') + end.should fail_with("expected Exception with \"blah\", got #") + end +end + +describe "should_not raise_error(message)" do + it "should pass if RuntimeError error is raised with the different message" do + lambda {raise 'blarg'}.should_not raise_error('blah') + end + it "should pass if any other error is raised with the wrong message" do + lambda {raise NameError.new('blarg')}.should_not raise_error('blah') + end + it "should fail if RuntimeError is raised with message" do + lambda do + lambda {raise 'blah'}.should_not raise_error('blah') + end.should fail_with(%Q|expected no Exception with "blah", got #|) + end + it "should fail if any other error is raised with message" do + lambda do + lambda {raise NameError.new('blah')}.should_not raise_error('blah') + end.should fail_with(%Q|expected no Exception with "blah", got #|) + end +end + +describe "should raise_error(NamedError)" do + it "should pass if named error is raised" do + lambda { non_existent_method }.should raise_error(NameError) + end + + it "should fail if nothing is raised" do + lambda { + lambda { }.should raise_error(NameError) + }.should fail_with("expected NameError but nothing was raised") + end + + it "should fail if another error is raised (NameError)" do + lambda { + lambda { raise }.should raise_error(NameError) + }.should fail_with("expected NameError, got RuntimeError") + end + + it "should fail if another error is raised (NameError)" do + lambda { + lambda { load "non/existent/file" }.should raise_error(NameError) + }.should fail_with(/expected NameError, got #") + end +end + +describe "should raise_error(NamedError, error_message) with Regexp" do + it "should pass if named error is raised with matching message" do + lambda { raise "example message" }.should raise_error(RuntimeError, /ample mess/) + end + + it "should fail if nothing is raised" do + lambda { + lambda {}.should raise_error(RuntimeError, /ample mess/) + }.should fail_with("expected RuntimeError with message matching /ample mess/ but nothing was raised") + end + + it "should fail if incorrect error is raised" do + lambda { + lambda { raise }.should raise_error(NameError, /ample mess/) + }.should fail_with("expected NameError with message matching /ample mess/, got RuntimeError") + end + + it "should fail if correct error is raised with incorrect message" do + lambda { + lambda { raise RuntimeError.new("not the example message") }.should raise_error(RuntimeError, /less than ample mess/) + }.should fail_with("expected RuntimeError with message matching /less than ample mess/, got #") + end +end + +describe "should_not raise_error(NamedError, error_message) with Regexp" do + it "should pass if nothing is raised" do + lambda {}.should_not raise_error(RuntimeError, /ample mess/) + end + + it "should pass if a different error is raised" do + lambda { raise }.should_not raise_error(NameError, /ample mess/) + end + + it "should pass if same error is raised with non-matching message" do + lambda { raise RuntimeError.new("non matching message") }.should_not raise_error(RuntimeError, /ample mess/) + end + + it "should fail if named error is raised with matching message" do + lambda { + lambda { raise "example message" }.should_not raise_error(RuntimeError, /ample mess/) + }.should fail_with("expected no RuntimeError with message matching /ample mess/, got #") + end +end diff --git a/vendor/gems/rspec/spec/spec/matchers/respond_to_spec.rb b/vendor/gems/rspec/spec/spec/matchers/respond_to_spec.rb new file mode 100644 index 0000000000..2cdbbcd635 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/matchers/respond_to_spec.rb @@ -0,0 +1,54 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +describe "should respond_to(:sym)" do + + it "should pass if target responds to :sym" do + Object.new.should respond_to(:methods) + end + + it "should fail target does not respond to :sym" do + lambda { + Object.new.should respond_to(:some_method) + }.should fail_with("expected target to respond to :some_method") + end + +end + +describe "should respond_to(message1, message2)" do + + it "should pass if target responds to both messages" do + Object.new.should respond_to('methods', 'inspect') + end + + it "should fail target does not respond to first message" do + lambda { + Object.new.should respond_to('method_one', 'inspect') + }.should fail_with('expected target to respond to "method_one"') + end + + it "should fail target does not respond to second message" do + lambda { + Object.new.should respond_to('inspect', 'method_one') + }.should fail_with('expected target to respond to "method_one"') + end + + it "should fail target does not respond to either message" do + lambda { + Object.new.should respond_to('method_one', 'method_two') + }.should fail_with('expected target to respond to "method_one", "method_two"') + end +end + +describe "should_not respond_to(:sym)" do + + it "should pass if target does not respond to :sym" do + Object.new.should_not respond_to(:some_method) + end + + it "should fail target responds to :sym" do + lambda { + Object.new.should_not respond_to(:methods) + }.should fail_with("expected target not to respond to :methods") + end + +end diff --git a/vendor/gems/rspec/spec/spec/matchers/satisfy_spec.rb b/vendor/gems/rspec/spec/spec/matchers/satisfy_spec.rb new file mode 100644 index 0000000000..7e8d6f972f --- /dev/null +++ b/vendor/gems/rspec/spec/spec/matchers/satisfy_spec.rb @@ -0,0 +1,36 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +describe "should satisfy { block }" do + it "should pass if block returns true" do + true.should satisfy { |val| val } + true.should satisfy do |val| + val + end + end + + it "should fail if block returns false" do + lambda { + false.should satisfy { |val| val } + }.should fail_with("expected false to satisfy block") + lambda do + false.should satisfy do |val| + val + end + end.should fail_with("expected false to satisfy block") + end +end + +describe "should_not satisfy { block }" do + it "should pass if block returns false" do + false.should_not satisfy { |val| val } + false.should_not satisfy do |val| + val + end + end + + it "should fail if block returns true" do + lambda { + true.should_not satisfy { |val| val } + }.should fail_with("expected true not to satisfy block") + end +end diff --git a/vendor/gems/rspec/spec/spec/matchers/simple_matcher_spec.rb b/vendor/gems/rspec/spec/spec/matchers/simple_matcher_spec.rb new file mode 100644 index 0000000000..b731af92d9 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/matchers/simple_matcher_spec.rb @@ -0,0 +1,31 @@ +require File.dirname(__FILE__) + '/../../spec_helper' + +module Spec + module Matchers + describe SimpleMatcher do + it "should match pass match arg to block" do + actual = nil + matcher = simple_matcher("message") do |given| actual = given end + matcher.matches?("foo") + actual.should == "foo" + end + + it "should provide a stock failure message" do + matcher = simple_matcher("thing") do end + matcher.matches?("other") + matcher.failure_message.should =~ /expected \"thing\" but got \"other\"/ + end + + it "should provide a stock negative failure message" do + matcher = simple_matcher("thing") do end + matcher.matches?("other") + matcher.negative_failure_message.should =~ /expected not to get \"thing\", but got \"other\"/ + end + + it "should provide a description" do + matcher = simple_matcher("thing") do end + matcher.description.should =="thing" + end + end + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/matchers/throw_symbol_spec.rb b/vendor/gems/rspec/spec/spec/matchers/throw_symbol_spec.rb new file mode 100644 index 0000000000..74595659a2 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/matchers/throw_symbol_spec.rb @@ -0,0 +1,54 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +module Spec + module Matchers + describe ThrowSymbol, "(constructed with no Symbol)" do + before(:each) { @matcher = ThrowSymbol.new } + + it "should match if any Symbol is thrown" do + @matcher.matches?(lambda{ throw :sym }).should be_true + end + it "should not match if no Symbol is thrown" do + @matcher.matches?(lambda{ }).should be_false + end + it "should provide a failure message" do + @matcher.matches?(lambda{}) + @matcher.failure_message.should == "expected a Symbol but nothing was thrown" + end + it "should provide a negative failure message" do + @matcher.matches?(lambda{ throw :sym}) + @matcher.negative_failure_message.should == "expected no Symbol, got :sym" + end + end + + describe ThrowSymbol, "(constructed with a Symbol)" do + before(:each) { @matcher = ThrowSymbol.new(:sym) } + + it "should match if correct Symbol is thrown" do + @matcher.matches?(lambda{ throw :sym }).should be_true + end + it "should not match if no Symbol is thrown" do + @matcher.matches?(lambda{ }).should be_false + end + it "should not match if correct Symbol is thrown" do + @matcher.matches?(lambda{ throw :other_sym }).should be_false + @matcher.failure_message.should == "expected :sym, got :other_sym" + end + it "should provide a failure message when no Symbol is thrown" do + @matcher.matches?(lambda{}) + @matcher.failure_message.should == "expected :sym but nothing was thrown" + end + it "should provide a failure message when wrong Symbol is thrown" do + @matcher.matches?(lambda{ throw :other_sym }) + @matcher.failure_message.should == "expected :sym, got :other_sym" + end + it "should provide a negative failure message" do + @matcher.matches?(lambda{ throw :sym }) + @matcher.negative_failure_message.should == "expected :sym not to be thrown" + end + it "should only match NameErrors raised by uncaught throws" do + @matcher.matches?(lambda{ sym }).should be_false + end + end + end +end diff --git a/vendor/gems/rspec/spec/spec/mocks/any_number_of_times_spec.rb b/vendor/gems/rspec/spec/spec/mocks/any_number_of_times_spec.rb new file mode 100644 index 0000000000..3f50dcfc5e --- /dev/null +++ b/vendor/gems/rspec/spec/spec/mocks/any_number_of_times_spec.rb @@ -0,0 +1,29 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +module Spec + module Mocks + + describe "AnyNumberOfTimes" do + before(:each) do + @mock = Mock.new("test mock") + end + + it "should pass if any number of times method is called many times" do + @mock.should_receive(:random_call).any_number_of_times + (1..10).each do + @mock.random_call + end + end + + it "should pass if any number of times method is called once" do + @mock.should_receive(:random_call).any_number_of_times + @mock.random_call + end + + it "should pass if any number of times method is not called" do + @mock.should_receive(:random_call).any_number_of_times + end + end + + end +end diff --git a/vendor/gems/rspec/spec/spec/mocks/argument_expectation_spec.rb b/vendor/gems/rspec/spec/spec/mocks/argument_expectation_spec.rb new file mode 100644 index 0000000000..2bebbdd4fc --- /dev/null +++ b/vendor/gems/rspec/spec/spec/mocks/argument_expectation_spec.rb @@ -0,0 +1,23 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +module Spec + module Mocks + describe ArgumentExpectation do + it "should consider an object that responds to #matches? and #description to be a matcher" do + argument_expecatation = Spec::Mocks::ArgumentExpectation.new([]) + obj = mock("matcher") + obj.should_receive(:respond_to?).with(:matches?).and_return(true) + obj.should_receive(:respond_to?).with(:description).and_return(true) + argument_expecatation.is_matcher?(obj).should be_true + end + + it "should NOT consider an object that only responds to #matches? to be a matcher" do + argument_expecatation = Spec::Mocks::ArgumentExpectation.new([]) + obj = mock("matcher") + obj.should_receive(:respond_to?).with(:matches?).and_return(true) + obj.should_receive(:respond_to?).with(:description).and_return(false) + argument_expecatation.is_matcher?(obj).should be_false + end + end + end +end diff --git a/vendor/gems/rspec/spec/spec/mocks/at_least_spec.rb b/vendor/gems/rspec/spec/spec/mocks/at_least_spec.rb new file mode 100644 index 0000000000..01b133dc3b --- /dev/null +++ b/vendor/gems/rspec/spec/spec/mocks/at_least_spec.rb @@ -0,0 +1,97 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +module Spec + module Mocks + describe "at_least" do + before(:each) do + @mock = Mock.new("test mock") + end + + it "should fail if method is never called" do + @mock.should_receive(:random_call).at_least(4).times + lambda do + @mock.rspec_verify + end.should raise_error(MockExpectationError) + end + + it "should fail when called less than n times" do + @mock.should_receive(:random_call).at_least(4).times + @mock.random_call + @mock.random_call + @mock.random_call + lambda do + @mock.rspec_verify + end.should raise_error(MockExpectationError) + end + + it "should fail when at least once method is never called" do + @mock.should_receive(:random_call).at_least(:once) + lambda do + @mock.rspec_verify + end.should raise_error(MockExpectationError) + end + + it "should fail when at least twice method is called once" do + @mock.should_receive(:random_call).at_least(:twice) + @mock.random_call + lambda do + @mock.rspec_verify + end.should raise_error(MockExpectationError) + end + + it "should fail when at least twice method is never called" do + @mock.should_receive(:random_call).at_least(:twice) + lambda do + @mock.rspec_verify + end.should raise_error(MockExpectationError) + end + + it "should pass when at least n times method is called exactly n times" do + @mock.should_receive(:random_call).at_least(4).times + @mock.random_call + @mock.random_call + @mock.random_call + @mock.random_call + @mock.rspec_verify + end + + it "should pass when at least n times method is called n plus 1 times" do + @mock.should_receive(:random_call).at_least(4).times + @mock.random_call + @mock.random_call + @mock.random_call + @mock.random_call + @mock.random_call + @mock.rspec_verify + end + + it "should pass when at least once method is called once" do + @mock.should_receive(:random_call).at_least(:once) + @mock.random_call + @mock.rspec_verify + end + + it "should pass when at least once method is called twice" do + @mock.should_receive(:random_call).at_least(:once) + @mock.random_call + @mock.random_call + @mock.rspec_verify + end + + it "should pass when at least twice method is called three times" do + @mock.should_receive(:random_call).at_least(:twice) + @mock.random_call + @mock.random_call + @mock.random_call + @mock.rspec_verify + end + + it "should pass when at least twice method is called twice" do + @mock.should_receive(:random_call).at_least(:twice) + @mock.random_call + @mock.random_call + @mock.rspec_verify + end + end + end +end diff --git a/vendor/gems/rspec/spec/spec/mocks/at_most_spec.rb b/vendor/gems/rspec/spec/spec/mocks/at_most_spec.rb new file mode 100644 index 0000000000..f3c5e2150d --- /dev/null +++ b/vendor/gems/rspec/spec/spec/mocks/at_most_spec.rb @@ -0,0 +1,93 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +module Spec + module Mocks + describe "at_most" do + before(:each) do + @mock = Mock.new("test mock") + end + + it "should fail when at most n times method is called n plus 1 times" do + @mock.should_receive(:random_call).at_most(4).times + @mock.random_call + @mock.random_call + @mock.random_call + @mock.random_call + @mock.random_call + lambda do + @mock.rspec_verify + end.should raise_error(MockExpectationError) + end + + it "should fail when at most once method is called twice" do + @mock.should_receive(:random_call).at_most(:once) + @mock.random_call + @mock.random_call + lambda do + @mock.rspec_verify + end.should raise_error(MockExpectationError) + end + + it "should fail when at most twice method is called three times" do + @mock.should_receive(:random_call).at_most(:twice) + @mock.random_call + @mock.random_call + @mock.random_call + lambda do + @mock.rspec_verify + end.should raise_error(MockExpectationError) + end + + it "should pass when at most n times method is called exactly n times" do + @mock.should_receive(:random_call).at_most(4).times + @mock.random_call + @mock.random_call + @mock.random_call + @mock.random_call + @mock.rspec_verify + end + + it "should pass when at most n times method is called less than n times" do + @mock.should_receive(:random_call).at_most(4).times + @mock.random_call + @mock.random_call + @mock.random_call + @mock.rspec_verify + end + + it "should pass when at most n times method is never called" do + @mock.should_receive(:random_call).at_most(4).times + @mock.rspec_verify + end + + it "should pass when at most once method is called once" do + @mock.should_receive(:random_call).at_most(:once) + @mock.random_call + @mock.rspec_verify + end + + it "should pass when at most once method is never called" do + @mock.should_receive(:random_call).at_most(:once) + @mock.rspec_verify + end + + it "should pass when at most twice method is called once" do + @mock.should_receive(:random_call).at_most(:twice) + @mock.random_call + @mock.rspec_verify + end + + it "should pass when at most twice method is called twice" do + @mock.should_receive(:random_call).at_most(:twice) + @mock.random_call + @mock.random_call + @mock.rspec_verify + end + + it "should pass when at most twice method is never called" do + @mock.should_receive(:random_call).at_most(:twice) + @mock.rspec_verify + end + end + end +end diff --git a/vendor/gems/rspec/spec/spec/mocks/bug_report_10260_spec.rb b/vendor/gems/rspec/spec/spec/mocks/bug_report_10260_spec.rb new file mode 100644 index 0000000000..2f7b5803d5 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/mocks/bug_report_10260_spec.rb @@ -0,0 +1,8 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +describe "An RSpec Mock" do + it "should hide internals in its inspect representation" do + m = mock('cup') + m.inspect.should =~ /#/ + end +end diff --git a/vendor/gems/rspec/spec/spec/mocks/bug_report_10263.rb b/vendor/gems/rspec/spec/spec/mocks/bug_report_10263.rb new file mode 100644 index 0000000000..e321922573 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/mocks/bug_report_10263.rb @@ -0,0 +1,24 @@ +describe "Mock" do + before do + @mock = mock("test mock") + end + + specify "when one example has an expectation (non-mock) inside the block passed to the mock" do + @mock.should_receive(:msg) do |b| + b.should be_true #this call exposes the problem + end + @mock.msg(false) rescue nil + end + + specify "then the next example should behave as expected instead of saying" do + @mock.should_receive(:foobar) + @mock.foobar + @mock.rspec_verify + begin + @mock.foobar + rescue Exception => e + e.message.should == "Mock 'test mock' received unexpected message :foobar with (no args)" + end + end +end + diff --git a/vendor/gems/rspec/spec/spec/mocks/bug_report_11545_spec.rb b/vendor/gems/rspec/spec/spec/mocks/bug_report_11545_spec.rb new file mode 100644 index 0000000000..8a334afa56 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/mocks/bug_report_11545_spec.rb @@ -0,0 +1,31 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +class LiarLiarPantsOnFire + def respond_to?(sym) + true + end + + def self.respond_to?(sym) + true + end +end + +describe 'should_receive' do + before(:each) do + @liar = LiarLiarPantsOnFire.new + end + + it "should work when object lies about responding to a method" do + @liar.should_receive(:something) + @liar.something + end + + it 'should work when class lies about responding to a method' do + LiarLiarPantsOnFire.should_receive(:something) + LiarLiarPantsOnFire.something + end + + it 'should cleanup after itself' do + LiarLiarPantsOnFire.metaclass.instance_methods.should_not include("something") + end +end diff --git a/vendor/gems/rspec/spec/spec/mocks/bug_report_15719_spec.rb b/vendor/gems/rspec/spec/spec/mocks/bug_report_15719_spec.rb new file mode 100644 index 0000000000..82d49ea97c --- /dev/null +++ b/vendor/gems/rspec/spec/spec/mocks/bug_report_15719_spec.rb @@ -0,0 +1,30 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +module Spec + module Mocks + describe "mock failure" do + + it "should tell you when it receives the right message with the wrong args" do + m = mock("foo") + m.should_receive(:bar).with("message") + lambda { + m.bar("different message") + }.should raise_error(Spec::Mocks::MockExpectationError, %Q{Mock 'foo' expected :bar with ("message") but received it with ("different message")}) + m.bar("message") # allows the spec to pass + end + + it "should tell you when it receives the right message with the wrong args if you stub the method" do + pending("fix bug 15719") + # NOTE - for whatever reason, if you use a the block style of pending here, + # rcov gets unhappy. Don't know why yet. + m = mock("foo") + m.stub!(:bar) + m.should_receive(:bar).with("message") + lambda { + m.bar("different message") + }.should raise_error(Spec::Mocks::MockExpectationError, %Q{Mock 'foo' expected :bar with ("message") but received it with ("different message")}) + m.bar("message") # allows the spec to pass + end + end + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/mocks/bug_report_7611_spec.rb b/vendor/gems/rspec/spec/spec/mocks/bug_report_7611_spec.rb new file mode 100644 index 0000000000..6c9705bccb --- /dev/null +++ b/vendor/gems/rspec/spec/spec/mocks/bug_report_7611_spec.rb @@ -0,0 +1,19 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +module Bug7611 + class Foo + end + + class Bar < Foo + end + + describe "A Partial Mock" do + it "should respect subclasses" do + Foo.stub!(:new).and_return(Object.new) + end + + it "should" do + Bar.new.class.should == Bar + end + end +end diff --git a/vendor/gems/rspec/spec/spec/mocks/bug_report_7805_spec.rb b/vendor/gems/rspec/spec/spec/mocks/bug_report_7805_spec.rb new file mode 100644 index 0000000000..f7edfac175 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/mocks/bug_report_7805_spec.rb @@ -0,0 +1,22 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +module Bug7805 + #This is really a duplicate of 8302 + + describe "Stubs should correctly restore module methods" do + it "1 - stub the open method" do + File.stub!(:open).and_return("something") + File.open.should == "something" + end + it "2 - use File.open to create example.txt" do + filename = "#{File.dirname(__FILE__)}/example-#{Time.new.to_i}.txt" + File.exist?(filename).should be_false + file = File.open(filename,'w') + file.close + File.exist?(filename).should be_true + File.delete(filename) + File.exist?(filename).should be_false + end + end + +end diff --git a/vendor/gems/rspec/spec/spec/mocks/bug_report_8165_spec.rb b/vendor/gems/rspec/spec/spec/mocks/bug_report_8165_spec.rb new file mode 100644 index 0000000000..7edc3c076a --- /dev/null +++ b/vendor/gems/rspec/spec/spec/mocks/bug_report_8165_spec.rb @@ -0,0 +1,31 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +describe "An object where respond_to? is true and does not have method" do + # When should_receive(:sym) is sent to any object, the Proxy sends + # respond_to?(:sym) to that object to see if the method should be proxied. + # + # If respond_to? itself is proxied, then when the Proxy sends respond_to? + # to the object, the proxy is invoked and responds yes (if so set in the spec). + # When the object does NOT actually respond to :sym, an exception is thrown + # when trying to proxy it. + # + # The fix was to keep track of whether :respond_to? had been proxied and, if + # so, call the munged copy of :respond_to? on the object. + + it "should not raise an exception for Object" do + obj = Object.new + obj.should_receive(:respond_to?).with(:foobar).and_return(true) + obj.should_receive(:foobar).and_return(:baz) + obj.respond_to?(:foobar).should be_true + obj.foobar.should == :baz + end + + it "should not raise an exception for mock" do + obj = mock("obj") + obj.should_receive(:respond_to?).with(:foobar).and_return(true) + obj.should_receive(:foobar).and_return(:baz) + obj.respond_to?(:foobar).should be_true + obj.foobar.should == :baz + end + +end diff --git a/vendor/gems/rspec/spec/spec/mocks/bug_report_8302_spec.rb b/vendor/gems/rspec/spec/spec/mocks/bug_report_8302_spec.rb new file mode 100644 index 0000000000..a41df43d84 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/mocks/bug_report_8302_spec.rb @@ -0,0 +1,26 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +module Bug8302 + class Foo + def Foo.class_method(arg) + end + + def instance_bar(arg) + end + end + + describe "Bug report 8302:" do + it "class method is not restored correctly when proxied" do + Foo.should_not_receive(:class_method).with(Array.new) + Foo.rspec_verify + Foo.class_method(Array.new) + end + + it "instance method is not restored correctly when proxied" do + foo = Foo.new + foo.should_not_receive(:instance_bar).with(Array.new) + foo.rspec_verify + foo.instance_bar(Array.new) + end + end +end diff --git a/vendor/gems/rspec/spec/spec/mocks/failing_mock_argument_constraints_spec.rb b/vendor/gems/rspec/spec/spec/mocks/failing_mock_argument_constraints_spec.rb new file mode 100644 index 0000000000..db6dcea346 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/mocks/failing_mock_argument_constraints_spec.rb @@ -0,0 +1,115 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +module Spec + module Mocks + describe "failing MockArgumentConstraints" do + before(:each) do + @mock = mock("test mock") + @reporter = Mock.new("reporter", :null_object => true) + end + + after(:each) do + @mock.rspec_reset + end + + it "should reject non boolean" do + @mock.should_receive(:random_call).with(boolean()) + lambda do + @mock.random_call("false") + end.should raise_error(MockExpectationError) + end + + it "should reject non numeric" do + @mock.should_receive(:random_call).with(an_instance_of(Numeric)) + lambda do + @mock.random_call("1") + end.should raise_error(MockExpectationError) + end + + it "should reject non string" do + @mock.should_receive(:random_call).with(an_instance_of(String)) + lambda do + @mock.random_call(123) + end.should raise_error(MockExpectationError) + end + + it "should reject goose when expecting a duck" do + @mock.should_receive(:random_call).with(duck_type(:abs, :div)) + lambda { @mock.random_call("I don't respond to :abs or :div") }.should raise_error(MockExpectationError) + end + + it "should fail if regexp does not match submitted string" do + @mock.should_receive(:random_call).with(/bcd/) + lambda { @mock.random_call("abc") }.should raise_error(MockExpectationError) + end + + it "should fail if regexp does not match submitted regexp" do + @mock.should_receive(:random_call).with(/bcd/) + lambda { @mock.random_call(/bcde/) }.should raise_error(MockExpectationError) + end + + it "should fail for a hash w/ wrong values" do + @mock.should_receive(:random_call).with(:a => "b", :c => "d") + lambda do + @mock.random_call(:a => "b", :c => "e") + end.should raise_error(MockExpectationError, /Mock 'test mock' expected :random_call with \(\{(:a=>\"b\", :c=>\"d\"|:c=>\"d\", :a=>\"b\")\}\) but received it with \(\{(:a=>\"b\", :c=>\"e\"|:c=>\"e\", :a=>\"b\")\}\)/) + end + + it "should fail for a hash w/ wrong keys" do + @mock.should_receive(:random_call).with(:a => "b", :c => "d") + lambda do + @mock.random_call("a" => "b", "c" => "d") + end.should raise_error(MockExpectationError, /Mock 'test mock' expected :random_call with \(\{(:a=>\"b\", :c=>\"d\"|:c=>\"d\", :a=>\"b\")\}\) but received it with \(\{(\"a\"=>\"b\", \"c\"=>\"d\"|\"c\"=>\"d\", \"a\"=>\"b\")\}\)/) + end + + it "should match against a Matcher" do + lambda do + @mock.should_receive(:msg).with(equal(3)) + @mock.msg(37) + end.should raise_error(MockExpectationError, "Mock 'test mock' expected :msg with (equal 3) but received it with (37)") + end + + it "should fail no_args with one arg" do + lambda do + @mock.should_receive(:msg).with(no_args) + @mock.msg(37) + end.should raise_error(MockExpectationError, "Mock 'test mock' expected :msg with (no args) but received it with (37)") + end + end + + describe "failing deprecated MockArgumentConstraints" do + before(:each) do + @mock = mock("test mock") + @reporter = Mock.new("reporter", :null_object => true) + Kernel.stub!(:warn) + end + + after(:each) do + @mock.rspec_reset + end + + it "should reject non boolean" do + @mock.should_receive(:random_call).with(:boolean) + lambda do + @mock.random_call("false") + end.should raise_error(MockExpectationError) + end + + it "should reject non numeric" do + @mock.should_receive(:random_call).with(:numeric) + lambda do + @mock.random_call("1") + end.should raise_error(MockExpectationError) + end + + it "should reject non string" do + @mock.should_receive(:random_call).with(:string) + lambda do + @mock.random_call(123) + end.should raise_error(MockExpectationError) + end + + + end + end +end diff --git a/vendor/gems/rspec/spec/spec/mocks/mock_ordering_spec.rb b/vendor/gems/rspec/spec/spec/mocks/mock_ordering_spec.rb new file mode 100644 index 0000000000..919da2970b --- /dev/null +++ b/vendor/gems/rspec/spec/spec/mocks/mock_ordering_spec.rb @@ -0,0 +1,84 @@ +require File.dirname(__FILE__) + '/../../spec_helper' + +module Spec + module Mocks + + describe "Mock ordering" do + + before do + @mock = mock("test mock") + end + + after do + @mock.rspec_reset + end + + it "should pass two calls in order" do + @mock.should_receive(:one).ordered + @mock.should_receive(:two).ordered + @mock.one + @mock.two + @mock.rspec_verify + end + + it "should pass three calls in order" do + @mock.should_receive(:one).ordered + @mock.should_receive(:two).ordered + @mock.should_receive(:three).ordered + @mock.one + @mock.two + @mock.three + @mock.rspec_verify + end + + it "should fail if second call comes first" do + @mock.should_receive(:one).ordered + @mock.should_receive(:two).ordered + lambda do + @mock.two + end.should raise_error(MockExpectationError, "Mock 'test mock' received :two out of order") + end + + it "should fail if third call comes first" do + @mock.should_receive(:one).ordered + @mock.should_receive(:two).ordered + @mock.should_receive(:three).ordered + @mock.one + lambda do + @mock.three + end.should raise_error(MockExpectationError, "Mock 'test mock' received :three out of order") + end + + it "should fail if third call comes second" do + @mock.should_receive(:one).ordered + @mock.should_receive(:two).ordered + @mock.should_receive(:three).ordered + @mock.one + lambda do + @mock.three + end.should raise_error(MockExpectationError, "Mock 'test mock' received :three out of order") + end + + it "should ignore order of non ordered calls" do + @mock.should_receive(:ignored_0) + @mock.should_receive(:ordered_1).ordered + @mock.should_receive(:ignored_1) + @mock.should_receive(:ordered_2).ordered + @mock.should_receive(:ignored_2) + @mock.should_receive(:ignored_3) + @mock.should_receive(:ordered_3).ordered + @mock.should_receive(:ignored_4) + @mock.ignored_3 + @mock.ordered_1 + @mock.ignored_0 + @mock.ordered_2 + @mock.ignored_4 + @mock.ignored_2 + @mock.ordered_3 + @mock.ignored_1 + @mock.rspec_verify + end + + end + end +end diff --git a/vendor/gems/rspec/spec/spec/mocks/mock_space_spec.rb b/vendor/gems/rspec/spec/spec/mocks/mock_space_spec.rb new file mode 100644 index 0000000000..23ffd01bc3 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/mocks/mock_space_spec.rb @@ -0,0 +1,54 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' +require 'spec/mocks' + +module Spec + module Mocks + describe Space do + before :each do + @space = Space.new + klazz = Class.new do + def rspec_verify + @verified = true + end + def verified? + @verified + end + def rspec_reset + @reset = true + end + def reset? + @reset + end + end + @m1 = klazz.new + @m2 = klazz.new + end + it "should verify all mocks within" do + @space.add(@m1) + @space.add(@m2) + @space.verify_all + @m1.should be_verified + @m2.should be_verified + end + it "should reset all mocks within" do + @space.add(m1 = mock("mock1")) + @space.add(m2 = mock("mock2")) + m1.should_receive(:rspec_reset) + m2.should_receive(:rspec_reset) + @space.reset_all + end + it "should clear internal mocks on reset_all" do + @space.add(m = mock("mock")) + @space.reset_all + @space.instance_eval { mocks.empty? }.should be_true + end + it "should only add an instance once" do + @space.add(m1 = mock("mock1")) + @space.add(m1) + m1.should_receive(:rspec_verify) + @space.verify_all + end + end + end +end + diff --git a/vendor/gems/rspec/spec/spec/mocks/mock_spec.rb b/vendor/gems/rspec/spec/spec/mocks/mock_spec.rb new file mode 100644 index 0000000000..85a71e327b --- /dev/null +++ b/vendor/gems/rspec/spec/spec/mocks/mock_spec.rb @@ -0,0 +1,475 @@ +require File.dirname(__FILE__) + '/../../spec_helper' + +module Spec + module Mocks + describe Mock do + + before(:each) do + @mock = mock("test mock") + end + + after(:each) do + @mock.rspec_reset + end + + it "should report line number of expectation of unreceived message" do + expected_error_line = __LINE__; @mock.should_receive(:wont_happen).with("x", 3) + begin + @mock.rspec_verify + violated + rescue MockExpectationError => e + # NOTE - this regexp ended w/ $, but jruby adds extra info at the end of the line + e.backtrace[0].should match(/#{File.basename(__FILE__)}:#{expected_error_line}/) + end + end + + it "should pass when not receiving message specified as not to be received" do + @mock.should_not_receive(:not_expected) + @mock.rspec_verify + end + + it "should pass when receiving message specified as not to be received with different args" do + @mock.should_not_receive(:message).with("unwanted text") + @mock.should_receive(:message).with("other text") + @mock.message "other text" + @mock.rspec_verify + end + + it "should fail when receiving message specified as not to be received" do + @mock.should_not_receive(:not_expected) + @mock.not_expected + lambda { + @mock.rspec_verify + violated + }.should raise_error(MockExpectationError, "Mock 'test mock' expected :not_expected with (any args) 0 times, but received it once") + end + + it "should fail when receiving message specified as not to be received with args" do + @mock.should_not_receive(:not_expected).with("unexpected text") + @mock.not_expected("unexpected text") + lambda { + @mock.rspec_verify + violated + }.should raise_error(MockExpectationError, "Mock 'test mock' expected :not_expected with (\"unexpected text\") 0 times, but received it once") + end + + it "should pass when receiving message specified as not to be received with wrong args" do + @mock.should_not_receive(:not_expected).with("unexpected text") + @mock.not_expected "really unexpected text" + @mock.rspec_verify + end + + it "should allow block to calculate return values" do + @mock.should_receive(:something).with("a","b","c").and_return { |a,b,c| c+b+a } + @mock.something("a","b","c").should == "cba" + @mock.rspec_verify + end + + it "should allow parameter as return value" do + @mock.should_receive(:something).with("a","b","c").and_return("booh") + @mock.something("a","b","c").should == "booh" + @mock.rspec_verify + end + + it "should return nil if no return value set" do + @mock.should_receive(:something).with("a","b","c") + @mock.something("a","b","c").should be_nil + @mock.rspec_verify + end + + it "should raise exception if args dont match when method called" do + @mock.should_receive(:something).with("a","b","c").and_return("booh") + lambda { + @mock.something("a","d","c") + violated + }.should raise_error(MockExpectationError, "Mock 'test mock' expected :something with (\"a\", \"b\", \"c\") but received it with (\"a\", \"d\", \"c\")") + end + + it "should fail if unexpected method called" do + lambda { + @mock.something("a","b","c") + violated + }.should raise_error(MockExpectationError, "Mock 'test mock' received unexpected message :something with (\"a\", \"b\", \"c\")") + end + + it "should use block for expectation if provided" do + @mock.should_receive(:something) do | a, b | + a.should == "a" + b.should == "b" + "booh" + end + @mock.something("a", "b").should == "booh" + @mock.rspec_verify + end + + it "should fail if expectation block fails" do + @mock.should_receive(:something) {| bool | bool.should be_true} + lambda { + @mock.something false + }.should raise_error(MockExpectationError, /Mock 'test mock' received :something but passed block failed with: expected true, got false/) + end + + it "should fail right away when method defined as never is received" do + pending "Used to pass (false positive). Which one is wrong, the spec or the actual behavior?" + + @mock.should_receive(:not_expected).never + lambda { + @mock.not_expected + }.should raise_error(MockExpectationError, "Mock 'test mock' expected :not_expected 0 times, but received it 1 times") + end + + it "should eventually fail when method defined as never is received" do + @mock.should_receive(:not_expected).never + @mock.not_expected + + lambda { + @mock.rspec_verify + }.should raise_error(MockExpectationError, "Mock 'test mock' expected :not_expected with (any args) 0 times, but received it once") + end + + it "should raise when told to" do + @mock.should_receive(:something).and_raise(RuntimeError) + lambda do + @mock.something + end.should raise_error(RuntimeError) + end + + it "should raise passed an Exception instance" do + error = RuntimeError.new("error message") + @mock.should_receive(:something).and_raise(error) + lambda { + @mock.something + }.should raise_error(RuntimeError, "error message") + end + + it "should raise RuntimeError with passed message" do + @mock.should_receive(:something).and_raise("error message") + lambda { + @mock.something + }.should raise_error(RuntimeError, "error message") + end + + it "should not raise when told to if args dont match" do + @mock.should_receive(:something).with(2).and_raise(RuntimeError) + lambda { + @mock.something 1 + }.should raise_error(MockExpectationError) + end + + it "should throw when told to" do + @mock.should_receive(:something).and_throw(:blech) + lambda { + @mock.something + }.should throw_symbol(:blech) + end + + it "should raise when explicit return and block constrained" do + lambda { + @mock.should_receive(:fruit) do |colour| + :strawberry + end.and_return :apple + }.should raise_error(AmbiguousReturnError) + end + + it "should ignore args on any args" do + @mock.should_receive(:something).at_least(:once).with(any_args) + @mock.something + @mock.something 1 + @mock.something "a", 2 + @mock.something [], {}, "joe", 7 + @mock.rspec_verify + end + + it "should fail on no args if any args received" do + @mock.should_receive(:something).with(no_args()) + lambda { + @mock.something 1 + }.should raise_error(MockExpectationError, "Mock 'test mock' expected :something with (no args) but received it with (1)") + end + + it "should fail when args are expected but none are received" do + @mock.should_receive(:something).with(1) + lambda { + @mock.something + }.should raise_error(MockExpectationError, "Mock 'test mock' expected :something with (1) but received it with (no args)") + end + + it "should yield 0 args to blocks that take a variable number of arguments" do + @mock.should_receive(:yield_back).with(no_args()).once.and_yield + a = nil + @mock.yield_back {|*a|} + a.should == [] + @mock.rspec_verify + end + + it "should yield 0 args multiple times to blocks that take a variable number of arguments" do + @mock.should_receive(:yield_back).once.with(no_args()).once.and_yield. + and_yield + a = nil + b = [] + @mock.yield_back {|*a| b << a} + b.should == [ [], [] ] + @mock.rspec_verify + end + + it "should yield one arg to blocks that take a variable number of arguments" do + @mock.should_receive(:yield_back).with(no_args()).once.and_yield(99) + a = nil + @mock.yield_back {|*a|} + a.should == [99] + @mock.rspec_verify + end + + it "should yield one arg 3 times consecutively to blocks that take a variable number of arguments" do + @mock.should_receive(:yield_back).once.with(no_args()).once.and_yield(99). + and_yield(43). + and_yield("something fruity") + a = nil + b = [] + @mock.yield_back {|*a| b << a} + b.should == [[99], [43], ["something fruity"]] + @mock.rspec_verify + end + + it "should yield many args to blocks that take a variable number of arguments" do + @mock.should_receive(:yield_back).with(no_args()).once.and_yield(99, 27, "go") + a = nil + @mock.yield_back {|*a|} + a.should == [99, 27, "go"] + @mock.rspec_verify + end + + it "should yield many args 3 times consecutively to blocks that take a variable number of arguments" do + @mock.should_receive(:yield_back).once.with(no_args()).once.and_yield(99, :green, "go"). + and_yield("wait", :amber). + and_yield("stop", 12, :red) + a = nil + b = [] + @mock.yield_back {|*a| b << a} + b.should == [[99, :green, "go"], ["wait", :amber], ["stop", 12, :red]] + @mock.rspec_verify + end + + it "should yield single value" do + @mock.should_receive(:yield_back).with(no_args()).once.and_yield(99) + a = nil + @mock.yield_back {|a|} + a.should == 99 + @mock.rspec_verify + end + + it "should yield single value 3 times consecutively" do + @mock.should_receive(:yield_back).once.with(no_args()).once.and_yield(99). + and_yield(43). + and_yield("something fruity") + a = nil + b = [] + @mock.yield_back {|a| b << a} + b.should == [99, 43, "something fruity"] + @mock.rspec_verify + end + + it "should yield two values" do + @mock.should_receive(:yield_back).with(no_args()).once.and_yield('wha', 'zup') + a, b = nil + @mock.yield_back {|a,b|} + a.should == 'wha' + b.should == 'zup' + @mock.rspec_verify + end + + it "should yield two values 3 times consecutively" do + @mock.should_receive(:yield_back).once.with(no_args()).once.and_yield('wha', 'zup'). + and_yield('not', 'down'). + and_yield(14, 65) + a, b = nil + c = [] + @mock.yield_back {|a,b| c << [a, b]} + c.should == [['wha', 'zup'], ['not', 'down'], [14, 65]] + @mock.rspec_verify + end + + it "should fail when calling yielding method with wrong arity" do + @mock.should_receive(:yield_back).with(no_args()).once.and_yield('wha', 'zup') + lambda { + @mock.yield_back {|a|} + }.should raise_error(MockExpectationError, "Mock 'test mock' yielded |\"wha\", \"zup\"| to block with arity of 1") + end + + it "should fail when calling yielding method consecutively with wrong arity" do + @mock.should_receive(:yield_back).once.with(no_args()).once.and_yield('wha', 'zup'). + and_yield('down'). + and_yield(14, 65) + lambda { + a, b = nil + c = [] + @mock.yield_back {|a,b| c << [a, b]} + }.should raise_error(MockExpectationError, "Mock 'test mock' yielded |\"down\"| to block with arity of 2") + end + + it "should fail when calling yielding method without block" do + @mock.should_receive(:yield_back).with(no_args()).once.and_yield('wha', 'zup') + lambda { + @mock.yield_back + }.should raise_error(MockExpectationError, "Mock 'test mock' asked to yield |[\"wha\", \"zup\"]| but no block was passed") + end + + it "should be able to mock send" do + @mock.should_receive(:send).with(any_args) + @mock.send 'hi' + @mock.rspec_verify + end + + it "should be able to raise from method calling yielding mock" do + @mock.should_receive(:yield_me).and_yield 44 + + lambda { + @mock.yield_me do |x| + raise "Bang" + end + }.should raise_error(StandardError, "Bang") + + @mock.rspec_verify + end + + it "should clear expectations after verify" do + @mock.should_receive(:foobar) + @mock.foobar + @mock.rspec_verify + lambda { + @mock.foobar + }.should raise_error(MockExpectationError, "Mock 'test mock' received unexpected message :foobar with (no args)") + end + + it "should restore objects to their original state on rspec_reset" do + mock = mock("this is a mock") + mock.should_receive(:blah) + mock.rspec_reset + mock.rspec_verify #should throw if reset didn't work + end + + it "should work even after method_missing starts raising NameErrors instead of NoMethodErrors" do + # Object#method_missing throws either NameErrors or NoMethodErrors. + # + # On a fresh ruby program Object#method_missing: + # * raises a NoMethodError when called directly + # * raises a NameError when called indirectly + # + # Once Object#method_missing has been called at least once (on any object) + # it starts behaving differently: + # * raises a NameError when called directly + # * raises a NameError when called indirectly + # + # There was a bug in Mock#method_missing that relied on the fact + # that calling Object#method_missing directly raises a NoMethodError. + # This example tests that the bug doesn't exist anymore. + + + # Ensures that method_missing always raises NameErrors. + a_method_that_doesnt_exist rescue + + + @mock.should_receive(:foobar) + @mock.foobar + @mock.rspec_verify + + lambda { @mock.foobar }.should_not raise_error(NameError) + lambda { @mock.foobar }.should raise_error(MockExpectationError) + end + + it "should temporarily replace a method stub on a mock" do + @mock.stub!(:msg).and_return(:stub_value) + @mock.should_receive(:msg).with(:arg).and_return(:mock_value) + @mock.msg(:arg).should equal(:mock_value) + @mock.msg.should equal(:stub_value) + @mock.msg.should equal(:stub_value) + @mock.rspec_verify + end + + it "should temporarily replace a method stub on a non-mock" do + non_mock = Object.new + non_mock.stub!(:msg).and_return(:stub_value) + non_mock.should_receive(:msg).with(:arg).and_return(:mock_value) + non_mock.msg(:arg).should equal(:mock_value) + non_mock.msg.should equal(:stub_value) + non_mock.msg.should equal(:stub_value) + non_mock.rspec_verify + end + + it "should assign stub return values" do + mock = Mock.new('name', :message => :response) + mock.message.should == :response + end + end + + describe "a mock message receiving a block" do + before(:each) do + @mock = mock("mock") + @calls = 0 + end + + def add_call + @calls = @calls + 1 + end + + it "should call the block after #should_receive" do + @mock.should_receive(:foo) { add_call } + + @mock.foo + + @calls.should == 1 + end + + it "should call the block after #once" do + @mock.should_receive(:foo).once { add_call } + + @mock.foo + + @calls.should == 1 + end + + it "should call the block after #twice" do + @mock.should_receive(:foo).twice { add_call } + + @mock.foo + @mock.foo + + @calls.should == 2 + end + + it "should call the block after #times" do + @mock.should_receive(:foo).exactly(10).times { add_call } + + (1..10).each { @mock.foo } + + @calls.should == 10 + end + + it "should call the block after #any_number_of_times" do + @mock.should_receive(:foo).any_number_of_times { add_call } + + (1..7).each { @mock.foo } + + @calls.should == 7 + end + + it "should call the block after #with" do + @mock.should_receive(:foo).with(:arg) { add_call } + + @mock.foo(:arg) + + @calls.should == 1 + end + + it "should call the block after #ordered" do + @mock.should_receive(:foo).ordered { add_call } + @mock.should_receive(:bar).ordered { add_call } + + @mock.foo + @mock.bar + + @calls.should == 2 + end + end + end +end diff --git a/vendor/gems/rspec/spec/spec/mocks/multiple_return_value_spec.rb b/vendor/gems/rspec/spec/spec/mocks/multiple_return_value_spec.rb new file mode 100644 index 0000000000..3e26b73f44 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/mocks/multiple_return_value_spec.rb @@ -0,0 +1,113 @@ +require File.dirname(__FILE__) + '/../../spec_helper' + +module Spec + module Mocks + describe "a Mock expectation with multiple return values and no specified count" do + before(:each) do + @mock = Mock.new("mock") + @return_values = ["1",2,Object.new] + @mock.should_receive(:message).and_return(@return_values[0],@return_values[1],@return_values[2]) + end + + it "should return values in order to consecutive calls" do + @mock.message.should == @return_values[0] + @mock.message.should == @return_values[1] + @mock.message.should == @return_values[2] + @mock.rspec_verify + end + + it "should complain when there are too few calls" do + third = Object.new + @mock.message.should == @return_values[0] + @mock.message.should == @return_values[1] + lambda { @mock.rspec_verify }.should raise_error(MockExpectationError, "Mock 'mock' expected :message with (any args) 3 times, but received it twice") + end + + it "should complain when there are too many calls" do + third = Object.new + @mock.message.should == @return_values[0] + @mock.message.should == @return_values[1] + @mock.message.should == @return_values[2] + @mock.message.should == @return_values[2] + lambda { @mock.rspec_verify }.should raise_error(MockExpectationError, "Mock 'mock' expected :message with (any args) 3 times, but received it 4 times") + end + end + + describe "a Mock expectation with multiple return values with a specified count equal to the number of values" do + before(:each) do + @mock = Mock.new("mock") + @return_values = ["1",2,Object.new] + @mock.should_receive(:message).exactly(3).times.and_return(@return_values[0],@return_values[1],@return_values[2]) + end + + it "should return values in order to consecutive calls" do + @mock.message.should == @return_values[0] + @mock.message.should == @return_values[1] + @mock.message.should == @return_values[2] + @mock.rspec_verify + end + + it "should complain when there are too few calls" do + third = Object.new + @mock.message.should == @return_values[0] + @mock.message.should == @return_values[1] + lambda { @mock.rspec_verify }.should raise_error(MockExpectationError, "Mock 'mock' expected :message with (any args) 3 times, but received it twice") + end + + it "should complain when there are too many calls" do + third = Object.new + @mock.message.should == @return_values[0] + @mock.message.should == @return_values[1] + @mock.message.should == @return_values[2] + @mock.message.should == @return_values[2] + lambda { @mock.rspec_verify }.should raise_error(MockExpectationError, "Mock 'mock' expected :message with (any args) 3 times, but received it 4 times") + end + end + + describe "a Mock expectation with multiple return values specifying at_least less than the number of values" do + before(:each) do + @mock = Mock.new("mock") + @mock.should_receive(:message).at_least(:twice).with(no_args).and_return(11, 22) + end + + it "should use last return value for subsequent calls" do + @mock.message.should equal(11) + @mock.message.should equal(22) + @mock.message.should equal(22) + @mock.rspec_verify + end + + it "should fail when called less than the specified number" do + @mock.message.should equal(11) + lambda { @mock.rspec_verify }.should raise_error(MockExpectationError, "Mock 'mock' expected :message with (no args) twice, but received it once") + end + end + describe "a Mock expectation with multiple return values with a specified count larger than the number of values" do + before(:each) do + @mock = Mock.new("mock") + @mock.should_receive(:message).exactly(3).times.and_return(11, 22) + end + + it "should use last return value for subsequent calls" do + @mock.message.should equal(11) + @mock.message.should equal(22) + @mock.message.should equal(22) + @mock.rspec_verify + end + + it "should fail when called less than the specified number" do + @mock.message.should equal(11) + lambda { @mock.rspec_verify }.should raise_error(MockExpectationError, "Mock 'mock' expected :message with (any args) 3 times, but received it once") + end + + it "should fail when called greater than the specified number" do + @mock.message.should equal(11) + @mock.message.should equal(22) + @mock.message.should equal(22) + @mock.message.should equal(22) + lambda { @mock.rspec_verify }.should raise_error(MockExpectationError, "Mock 'mock' expected :message with (any args) 3 times, but received it 4 times") + end + end + end +end + diff --git a/vendor/gems/rspec/spec/spec/mocks/null_object_mock_spec.rb b/vendor/gems/rspec/spec/spec/mocks/null_object_mock_spec.rb new file mode 100644 index 0000000000..57e8ca31cf --- /dev/null +++ b/vendor/gems/rspec/spec/spec/mocks/null_object_mock_spec.rb @@ -0,0 +1,40 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +module Spec + module Mocks + describe "a mock acting as a NullObject" do + before(:each) do + @mock = Mock.new("null_object", :null_object => true) + end + + it "should allow explicit expectation" do + @mock.should_receive(:something) + @mock.something + end + + it "should fail verification when explicit exception not met" do + lambda do + @mock.should_receive(:something) + @mock.rspec_verify + end.should raise_error(MockExpectationError) + end + + it "should ignore unexpected methods" do + @mock.random_call("a", "d", "c") + @mock.rspec_verify + end + + it "should expected message with different args first" do + @mock.should_receive(:message).with(:expected_arg) + @mock.message(:unexpected_arg) + @mock.message(:expected_arg) + end + + it "should expected message with different args second" do + @mock.should_receive(:message).with(:expected_arg) + @mock.message(:expected_arg) + @mock.message(:unexpected_arg) + end + end + end +end diff --git a/vendor/gems/rspec/spec/spec/mocks/once_counts_spec.rb b/vendor/gems/rspec/spec/spec/mocks/once_counts_spec.rb new file mode 100644 index 0000000000..2c15d5c2e3 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/mocks/once_counts_spec.rb @@ -0,0 +1,53 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +module Spec + module Mocks + describe "OnceCounts" do + before(:each) do + @mock = mock("test mock") + end + + it "once should fail when called once with wrong args" do + @mock.should_receive(:random_call).once.with("a", "b", "c") + lambda do + @mock.random_call("d", "e", "f") + end.should raise_error(MockExpectationError) + @mock.rspec_reset + end + + it "once should fail when called twice" do + @mock.should_receive(:random_call).once + @mock.random_call + @mock.random_call + lambda do + @mock.rspec_verify + end.should raise_error(MockExpectationError) + end + + it "once should fail when not called" do + @mock.should_receive(:random_call).once + lambda do + @mock.rspec_verify + end.should raise_error(MockExpectationError) + end + + it "once should pass when called once" do + @mock.should_receive(:random_call).once + @mock.random_call + @mock.rspec_verify + end + + it "once should pass when called once with specified args" do + @mock.should_receive(:random_call).once.with("a", "b", "c") + @mock.random_call("a", "b", "c") + @mock.rspec_verify + end + + it "once should pass when called once with unspecified args" do + @mock.should_receive(:random_call).once + @mock.random_call("a", "b", "c") + @mock.rspec_verify + end + end + end +end diff --git a/vendor/gems/rspec/spec/spec/mocks/options_hash_spec.rb b/vendor/gems/rspec/spec/spec/mocks/options_hash_spec.rb new file mode 100644 index 0000000000..0bfab26d75 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/mocks/options_hash_spec.rb @@ -0,0 +1,45 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +module Spec + module Mocks + describe "calling :should_receive with an options hash" do + it_should_behave_like "sandboxed rspec_options" + attr_reader :reporter, :example_group + before do + @reporter = ::Spec::Runner::Reporter.new(options) + @example_group = Class.new(::Spec::Example::ExampleGroup) do + plugin_mock_framework + describe("Some Examples") + end + reporter.add_example_group example_group + end + + it "should report the file and line submitted with :expected_from" do + example_definition = example_group.it "spec" do + mock = Spec::Mocks::Mock.new("a mock") + mock.should_receive(:message, :expected_from => "/path/to/blah.ext:37") + mock.rspec_verify + end + example = example_group.new(example_definition) + + reporter.should_receive(:example_finished) do |spec, error| + error.backtrace.detect {|line| line =~ /\/path\/to\/blah.ext:37/}.should_not be_nil + end + example.execute(options, {}) + end + + it "should use the message supplied with :message" do + example_definition = @example_group.it "spec" do + mock = Spec::Mocks::Mock.new("a mock") + mock.should_receive(:message, :message => "recebi nada") + mock.rspec_verify + end + example = @example_group.new(example_definition) + @reporter.should_receive(:example_finished) do |spec, error| + error.message.should == "recebi nada" + end + example.execute(@options, {}) + end + end + end +end diff --git a/vendor/gems/rspec/spec/spec/mocks/partial_mock_spec.rb b/vendor/gems/rspec/spec/spec/mocks/partial_mock_spec.rb new file mode 100644 index 0000000000..d7e5944c4e --- /dev/null +++ b/vendor/gems/rspec/spec/spec/mocks/partial_mock_spec.rb @@ -0,0 +1,106 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +module Spec + module Mocks + describe "using a Partial Mock," do + before(:each) do + @object = Object.new + end + + it "should name the class in the failure message" do + @object.should_receive(:foo) + lambda do + @object.rspec_verify + end.should raise_error(Spec::Mocks::MockExpectationError, /Object/) + end + + it "should not conflict with @options in the object" do + @object.instance_eval { @options = Object.new } + @object.should_receive(:blah) + @object.blah + end + + it "should_not_receive should mock out the method" do + @object.should_not_receive(:fuhbar) + @object.fuhbar + lambda do + @object.rspec_verify + end.should raise_error(Spec::Mocks::MockExpectationError) + end + + it "should_not_receive should return a negative message expectation" do + @object.should_not_receive(:foobar).should be_kind_of(NegativeMessageExpectation) + end + + it "should_receive should mock out the method" do + @object.should_receive(:foobar).with(:test_param).and_return(1) + @object.foobar(:test_param).should equal(1) + end + + it "should_receive should handle a hash" do + @object.should_receive(:foobar).with(:key => "value").and_return(1) + @object.foobar(:key => "value").should equal(1) + end + + it "should_receive should handle an inner hash" do + hash = {:a => {:key => "value"}} + @object.should_receive(:foobar).with(:key => "value").and_return(1) + @object.foobar(hash[:a]).should equal(1) + end + + it "should_receive should return a message expectation" do + @object.should_receive(:foobar).should be_kind_of(MessageExpectation) + @object.foobar + end + + it "should_receive should verify method was called" do + @object.should_receive(:foobar).with(:test_param).and_return(1) + lambda do + @object.rspec_verify + end.should raise_error(Spec::Mocks::MockExpectationError) + end + + it "should_receive should also take a String argument" do + @object.should_receive('foobar') + @object.foobar + end + + it "should_not_receive should also take a String argument" do + @object.should_not_receive('foobar') + @object.foobar + lambda do + @object.rspec_verify + end.should raise_error(Spec::Mocks::MockExpectationError) + end + + it "should use report nil in the error message" do + @this_will_resolve_to_nil.should_receive(:foobar) + lambda do + @this_will_resolve_to_nil.rspec_verify + end.should raise_error(Spec::Mocks::MockExpectationError, /NilClass.*expected :foobar with/) + end + end + + describe "Partially mocking an object that defines ==, after another mock has been defined" do + before(:each) do + stub("existing mock", :foo => :foo) + end + + class PartiallyMockedEquals + attr_reader :val + def initialize(val) + @val = val + end + + def ==(other) + @val == other.val + end + end + + it "should not raise an error when stubbing the object" do + o = PartiallyMockedEquals.new :foo + lambda { o.stub!(:bar) }.should_not raise_error(NoMethodError) + end + end + end +end diff --git a/vendor/gems/rspec/spec/spec/mocks/partial_mock_using_mocks_directly_spec.rb b/vendor/gems/rspec/spec/spec/mocks/partial_mock_using_mocks_directly_spec.rb new file mode 100644 index 0000000000..c857d83801 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/mocks/partial_mock_using_mocks_directly_spec.rb @@ -0,0 +1,66 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +module Spec +module Mocks +describe "PartialMockUsingMocksDirectly" do + before(:each) do + + klass=Class.new + klass.class_eval do + def existing_method + :original_value + end + end + @obj = klass.new + + end + + # See http://rubyforge.org/tracker/index.php?func=detail&aid=10263&group_id=797&atid=3149 + # specify "should clear expectations on verify" do + # @obj.should_receive(:msg) + # @obj.msg + # @obj.rspec_verify + # lambda do + # @obj.msg + # end.should raise_error(NoMethodError) + # + # end + it "should fail when expected message is not received" do + @obj.should_receive(:msg) + lambda do + @obj.rspec_verify + end.should raise_error(MockExpectationError) + + end + it "should fail when message is received with incorrect args" do + @obj.should_receive(:msg).with(:correct_arg) + lambda do + @obj.msg(:incorrect_arg) + end.should raise_error(MockExpectationError) + @obj.msg(:correct_arg) + + end + it "should pass when expected message is received" do + @obj.should_receive(:msg) + @obj.msg + @obj.rspec_verify + + end + it "should pass when message is received with correct args" do + @obj.should_receive(:msg).with(:correct_arg) + @obj.msg(:correct_arg) + @obj.rspec_verify + + end + it "should revert to original method if existed" do + @obj.existing_method.should equal(:original_value) + @obj.should_receive(:existing_method).and_return(:mock_value) + @obj.existing_method.should equal(:mock_value) + @obj.rspec_verify + @obj.existing_method.should equal(:original_value) + + end + +end +end +end diff --git a/vendor/gems/rspec/spec/spec/mocks/passing_mock_argument_constraints_spec.rb b/vendor/gems/rspec/spec/spec/mocks/passing_mock_argument_constraints_spec.rb new file mode 100644 index 0000000000..6de0a58f48 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/mocks/passing_mock_argument_constraints_spec.rb @@ -0,0 +1,154 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +module Spec + module Mocks + describe "mock argument constraints", :shared => true do + before(:each) do + @mock = Mock.new("test mock") + Kernel.stub!(:warn) + end + + after(:each) do + @mock.rspec_verify + end + end + + describe Methods, "handling argument constraints with DEPRECATED symbols" do + it_should_behave_like "mock argument constraints" + + it "should accept true as boolean" do + @mock.should_receive(:random_call).with(:boolean) + @mock.random_call(true) + end + + it "should accept false as boolean" do + @mock.should_receive(:random_call).with(:boolean) + @mock.random_call(false) + end + + it "should accept fixnum as numeric" do + @mock.should_receive(:random_call).with(:numeric) + @mock.random_call(1) + end + + it "should accept float as numeric" do + @mock.should_receive(:random_call).with(:numeric) + @mock.random_call(1.5) + end + + it "should accept string as anything" do + @mock.should_receive(:random_call).with("a", :anything, "c") + @mock.random_call("a", "whatever", "c") + end + + it "should match string" do + @mock.should_receive(:random_call).with(:string) + @mock.random_call("a string") + end + + it "should match no args against any_args" do + @mock.should_receive(:random_call).with(:any_args) + @mock.random_call("a string") + end + + it "should match no args against no_args" do + @mock.should_receive(:random_call).with(:no_args) + @mock.random_call + end + end + + describe Methods, "handling argument constraints" do + it_should_behave_like "mock argument constraints" + + it "should accept true as boolean()" do + @mock.should_receive(:random_call).with(boolean()) + @mock.random_call(true) + end + + it "should accept false as boolean()" do + @mock.should_receive(:random_call).with(boolean()) + @mock.random_call(false) + end + + it "should accept fixnum as an_instance_of(Numeric)" do + @mock.should_receive(:random_call).with(an_instance_of(Numeric)) + @mock.random_call(1) + end + + it "should accept float as an_instance_of(Numeric)" do + @mock.should_receive(:random_call).with(an_instance_of(Numeric)) + @mock.random_call(1.5) + end + + it "should accept string as anything()" do + @mock.should_receive(:random_call).with("a", anything(), "c") + @mock.random_call("a", "whatever", "c") + end + + it "should match duck type with one method" do + @mock.should_receive(:random_call).with(duck_type(:length)) + @mock.random_call([]) + end + + it "should match duck type with two methods" do + @mock.should_receive(:random_call).with(duck_type(:abs, :div)) + @mock.random_call(1) + end + + it "should match no args against any_args()" do + @mock.should_receive(:random_call).with(any_args) + @mock.random_call() + end + + it "should match one arg against any_args()" do + @mock.should_receive(:random_call).with(any_args) + @mock.random_call("a string") + end + + it "should match no args against no_args()" do + @mock.should_receive(:random_call).with(no_args) + @mock.random_call() + end + end + + describe Methods, "handling non-constraint arguments" do + + it "should match non special symbol (can be removed when deprecated symbols are removed)" do + @mock.should_receive(:random_call).with(:some_symbol) + @mock.random_call(:some_symbol) + end + + it "should match string against regexp" do + @mock.should_receive(:random_call).with(/bcd/) + @mock.random_call("abcde") + end + + it "should match regexp against regexp" do + @mock.should_receive(:random_call).with(/bcd/) + @mock.random_call(/bcd/) + end + + it "should match against a hash submitted and received by value" do + @mock.should_receive(:random_call).with(:a => "a", :b => "b") + @mock.random_call(:a => "a", :b => "b") + end + + it "should match against a hash submitted by reference and received by value" do + opts = {:a => "a", :b => "b"} + @mock.should_receive(:random_call).with(opts) + @mock.random_call(:a => "a", :b => "b") + end + + it "should match against a hash submitted by value and received by reference" do + opts = {:a => "a", :b => "b"} + @mock.should_receive(:random_call).with(:a => "a", :b => "b") + @mock.random_call(opts) + end + + it "should match against a Matcher" do + @mock.should_receive(:msg).with(equal(37)) + @mock.msg(37) + end + end + end +end diff --git a/vendor/gems/rspec/spec/spec/mocks/precise_counts_spec.rb b/vendor/gems/rspec/spec/spec/mocks/precise_counts_spec.rb new file mode 100644 index 0000000000..ba3898943c --- /dev/null +++ b/vendor/gems/rspec/spec/spec/mocks/precise_counts_spec.rb @@ -0,0 +1,52 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +module Spec + module Mocks + describe "PreciseCounts" do + before(:each) do + @mock = mock("test mock") + end + + it "should fail when exactly n times method is called less than n times" do + @mock.should_receive(:random_call).exactly(3).times + @mock.random_call + @mock.random_call + lambda do + @mock.rspec_verify + end.should raise_error(MockExpectationError) + end + + it "should fail when exactly n times method is never called" do + @mock.should_receive(:random_call).exactly(3).times + lambda do + @mock.rspec_verify + end.should raise_error(MockExpectationError) + end + + it "should pass if exactly n times method is called exactly n times" do + @mock.should_receive(:random_call).exactly(3).times + @mock.random_call + @mock.random_call + @mock.random_call + @mock.rspec_verify + end + + it "should pass multiple calls with different args and counts" do + @mock.should_receive(:random_call).twice.with(1) + @mock.should_receive(:random_call).once.with(2) + @mock.random_call(1) + @mock.random_call(2) + @mock.random_call(1) + @mock.rspec_verify + end + + it "should pass mutiple calls with different args" do + @mock.should_receive(:random_call).once.with(1) + @mock.should_receive(:random_call).once.with(2) + @mock.random_call(1) + @mock.random_call(2) + @mock.rspec_verify + end + end + end +end diff --git a/vendor/gems/rspec/spec/spec/mocks/record_messages_spec.rb b/vendor/gems/rspec/spec/spec/mocks/record_messages_spec.rb new file mode 100644 index 0000000000..ec247726dd --- /dev/null +++ b/vendor/gems/rspec/spec/spec/mocks/record_messages_spec.rb @@ -0,0 +1,26 @@ +require File.dirname(__FILE__) + '/../../spec_helper' + +module Spec + module Mocks + describe "a mock" do + before(:each) do + @mock = mock("mock", :null_object => true) + end + it "should answer false for received_message? when no messages received" do + @mock.received_message?(:message).should be_false + end + it "should answer true for received_message? when message received" do + @mock.message + @mock.received_message?(:message).should be_true + end + it "should answer true for received_message? when message received with correct args" do + @mock.message 1,2,3 + @mock.received_message?(:message, 1,2,3).should be_true + end + it "should answer false for received_message? when message received with incorrect args" do + @mock.message 1,2,3 + @mock.received_message?(:message, 1,2).should be_false + end + end + end +end diff --git a/vendor/gems/rspec/spec/spec/mocks/stub_spec.rb b/vendor/gems/rspec/spec/spec/mocks/stub_spec.rb new file mode 100644 index 0000000000..d6e23d71e3 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/mocks/stub_spec.rb @@ -0,0 +1,181 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +module Spec + module Mocks + describe "A method stub" do + before(:each) do + @class = Class.new do + def self.existing_class_method + :original_value + end + + def existing_instance_method + :original_value + end + end + @instance = @class.new + end + + it "should return expected value when expected message is received" do + @instance.stub!(:msg).and_return(:return_value) + @instance.msg.should equal(:return_value) + @instance.rspec_verify + end + + it "should ignore when expected message is received" do + @instance.stub!(:msg) + @instance.msg + lambda do + @instance.rspec_verify + end.should_not raise_error + end + + it "should ignore when message is received with args" do + @instance.stub!(:msg) + @instance.msg(:an_arg) + lambda do + @instance.rspec_verify + end.should_not raise_error + end + + it "should ignore when expected message is not received" do + @instance.stub!(:msg) + lambda do + @instance.rspec_verify + end.should_not raise_error + end + + it "should clear itself when verified" do + @instance.stub!(:this_should_go).and_return(:blah) + @instance.this_should_go.should == :blah + @instance.rspec_verify + lambda do + @instance.this_should_go + end.should raise_error(NameError) + end + + it "should return values in order to consecutive calls" do + return_values = ["1",2,Object.new] + @instance.stub!(:msg).and_return(return_values[0],return_values[1],return_values[2]) + @instance.msg.should == return_values[0] + @instance.msg.should == return_values[1] + @instance.msg.should == return_values[2] + end + + it "should keep returning last value in consecutive calls" do + return_values = ["1",2,Object.new] + @instance.stub!(:msg).and_return(return_values[0],return_values[1],return_values[2]) + @instance.msg.should == return_values[0] + @instance.msg.should == return_values[1] + @instance.msg.should == return_values[2] + @instance.msg.should == return_values[2] + @instance.msg.should == return_values[2] + end + + it "should revert to original instance method if there is one" do + @instance.existing_instance_method.should equal(:original_value) + @instance.stub!(:existing_instance_method).and_return(:mock_value) + @instance.existing_instance_method.should equal(:mock_value) + @instance.rspec_verify + @instance.existing_instance_method.should equal(:original_value) + end + + it "should revert to original class method if there is one" do + @class.existing_class_method.should equal(:original_value) + @class.stub!(:existing_class_method).and_return(:mock_value) + @class.existing_class_method.should equal(:mock_value) + @class.rspec_verify + @class.existing_class_method.should equal(:original_value) + end + + it "should yield a specified object" do + @instance.stub!(:method_that_yields).and_yield(:yielded_obj) + current_value = :value_before + @instance.method_that_yields {|val| current_value = val} + current_value.should == :yielded_obj + @instance.rspec_verify + end + + it "should yield multiple times with multiple calls to and_yield" do + @instance.stub!(:method_that_yields_multiple_times).and_yield(:yielded_value). + and_yield(:another_value) + current_value = [] + @instance.method_that_yields_multiple_times {|val| current_value << val} + current_value.should == [:yielded_value, :another_value] + @instance.rspec_verify + end + + it "should yield a specified object and return another specified object" do + yielded_obj = mock("my mock") + yielded_obj.should_receive(:foo).with(:bar) + @instance.stub!(:method_that_yields_and_returns).and_yield(yielded_obj).and_return(:baz) + @instance.method_that_yields_and_returns { |o| o.foo :bar }.should == :baz + end + + it "should throw when told to" do + @mock.stub!(:something).and_throw(:up) + lambda do + @mock.something + end.should throw_symbol(:up) + end + + it "should override a pre-existing stub" do + @stub.stub!(:existing_instance_method).and_return(:updated_stub_value) + @stub.existing_instance_method.should == :updated_stub_value + end + + it "should limit " do + @stub.stub!(:foo).with("bar") + @stub.should_receive(:foo).with("baz") + @stub.foo("bar") + @stub.foo("baz") + end + end + + describe "A method stub with args" do + before(:each) do + @stub = Object.new + @stub.stub!(:foo).with("bar") + end + + it "should not complain if not called" do + end + + it "should not complain if called with arg" do + @stub.foo("bar") + end + + it "should complain if called with no arg" do + lambda do + @stub.foo + end.should raise_error + end + + it "should complain if called with other arg" do + lambda do + @stub.foo("other") + end.should raise_error + end + + it "should not complain if also mocked w/ different args" do + @stub.should_receive(:foo).with("baz") + @stub.foo("bar") + @stub.foo("baz") + end + + it "should complain if also mocked w/ different args AND called w/ a 3rd set of args" do + @stub.should_receive(:foo).with("baz") + @stub.foo("bar") + @stub.foo("baz") + lambda do + @stub.foo("other") + end.should raise_error + end + + it "should support options" do + @stub.stub!(:foo, :expected_from => "bar") + end + end + + end +end diff --git a/vendor/gems/rspec/spec/spec/mocks/twice_counts_spec.rb b/vendor/gems/rspec/spec/spec/mocks/twice_counts_spec.rb new file mode 100644 index 0000000000..d07e45736a --- /dev/null +++ b/vendor/gems/rspec/spec/spec/mocks/twice_counts_spec.rb @@ -0,0 +1,67 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +module Spec + module Mocks + describe "TwiceCounts" do + before(:each) do + @mock = mock("test mock") + end + + it "twice should fail when call count is higher than expected" do + @mock.should_receive(:random_call).twice + @mock.random_call + @mock.random_call + @mock.random_call + lambda do + @mock.rspec_verify + end.should raise_error(MockExpectationError) + end + + it "twice should fail when call count is lower than expected" do + @mock.should_receive(:random_call).twice + @mock.random_call + lambda do + @mock.rspec_verify + end.should raise_error(MockExpectationError) + end + + it "twice should fail when called twice with wrong args on the first call" do + @mock.should_receive(:random_call).twice.with("1", 1) + lambda do + @mock.random_call(1, "1") + end.should raise_error(MockExpectationError) + @mock.rspec_reset + end + + it "twice should fail when called twice with wrong args on the second call" do + @mock.should_receive(:random_call).twice.with("1", 1) + @mock.random_call("1", 1) + lambda do + @mock.random_call(1, "1") + end.should raise_error(MockExpectationError) + @mock.rspec_reset + end + + it "twice should pass when called twice" do + @mock.should_receive(:random_call).twice + @mock.random_call + @mock.random_call + @mock.rspec_verify + end + + it "twice should pass when called twice with specified args" do + @mock.should_receive(:random_call).twice.with("1", 1) + @mock.random_call("1", 1) + @mock.random_call("1", 1) + @mock.rspec_verify + end + + it "twice should pass when called twice with unspecified args" do + @mock.should_receive(:random_call).twice + @mock.random_call("1") + @mock.random_call(1) + @mock.rspec_verify + end + end + end +end diff --git a/vendor/gems/rspec/spec/spec/package/bin_spec_spec.rb b/vendor/gems/rspec/spec/spec/package/bin_spec_spec.rb new file mode 100644 index 0000000000..44bfd96a08 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/package/bin_spec_spec.rb @@ -0,0 +1,14 @@ +require File.dirname(__FILE__) + '/../../spec_helper' +require File.dirname(__FILE__) + '/../../ruby_forker' + +describe "The bin/spec script" do + include RubyForker + + it "should have no warnings" do + pending "Hangs on JRuby" if PLATFORM =~ /java/ + spec_path = "#{File.dirname(__FILE__)}/../../../bin/spec" + + output = ruby "-w #{spec_path} --help 2>&1" + output.should_not =~ /warning/n + end +end diff --git a/vendor/gems/rspec/spec/spec/runner/class_and_argument_parser_spec.rb b/vendor/gems/rspec/spec/spec/runner/class_and_argument_parser_spec.rb new file mode 100644 index 0000000000..b4e9e7f535 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/runner/class_and_argument_parser_spec.rb @@ -0,0 +1,23 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +module Spec + module Runner + describe ClassAndArgumentsParser, ".parse" do + + it "should use a single : to separate class names from arguments" do + ClassAndArgumentsParser.parse('Foo').should == ['Foo', nil] + ClassAndArgumentsParser.parse('Foo:arg').should == ['Foo', 'arg'] + ClassAndArgumentsParser.parse('Foo::Bar::Zap:arg').should == ['Foo::Bar::Zap', 'arg'] + ClassAndArgumentsParser.parse('Foo:arg1,arg2').should == ['Foo', 'arg1,arg2'] + ClassAndArgumentsParser.parse('Foo::Bar::Zap:arg1,arg2').should == ['Foo::Bar::Zap', 'arg1,arg2'] + ClassAndArgumentsParser.parse('Foo::Bar::Zap:drb://foo,drb://bar').should == ['Foo::Bar::Zap', 'drb://foo,drb://bar'] + end + + it "should raise an error when passed an empty string" do + lambda do + ClassAndArgumentsParser.parse('') + end.should raise_error("Couldn't parse \"\"") + end + end + end +end diff --git a/vendor/gems/rspec/spec/spec/runner/command_line_spec.rb b/vendor/gems/rspec/spec/spec/runner/command_line_spec.rb new file mode 100644 index 0000000000..3c3be8ceaa --- /dev/null +++ b/vendor/gems/rspec/spec/spec/runner/command_line_spec.rb @@ -0,0 +1,147 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +module Spec + module Runner + describe CommandLine, ".run" do + it_should_behave_like "sandboxed rspec_options" + attr_reader :options, :err, :out + before do + @err = options.error_stream + @out = options.output_stream + end + + it "should run directory" do + file = File.dirname(__FILE__) + '/../../../examples/pure' + Spec::Runner::CommandLine.run(OptionParser.parse([file,"-p","**/*.rb"], @err, @out)) + + @out.rewind + @out.read.should =~ /\d+ examples, 0 failures, 3 pending/n + end + + it "should run file" do + file = File.dirname(__FILE__) + '/../../../failing_examples/predicate_example.rb' + Spec::Runner::CommandLine.run(OptionParser.parse([file], @err, @out)) + + @out.rewind + @out.read.should =~ /2 examples, 1 failure/n + end + + it "should raise when file does not exist" do + file = File.dirname(__FILE__) + '/doesntexist' + + lambda { + Spec::Runner::CommandLine.run(OptionParser.parse([file], @err, @out)) + }.should raise_error + end + + it "should return true when in --generate-options mode" do + # NOTE - this used to say /dev/null but jruby hangs on that for some reason + Spec::Runner::CommandLine.run( + OptionParser.parse(['--generate-options', '/tmp/foo'], @err, @out) + ).should be_true + end + + it "should dump even if Interrupt exception is occurred" do + example_group = Class.new(::Spec::Example::ExampleGroup) do + describe("example_group") + it "no error" do + end + + it "should interrupt" do + raise Interrupt, "I'm interrupting" + end + end + + options = ::Spec::Runner::Options.new(@err, @out) + ::Spec::Runner::Options.should_receive(:new).with(@err, @out).and_return(options) + options.reporter.should_receive(:dump) + options.add_example_group(example_group) + + Spec::Runner::CommandLine.run(OptionParser.parse([], @err, @out)) + end + + it "should heckle when options have heckle_runner" do + example_group = Class.new(::Spec::Example::ExampleGroup).describe("example_group") do + it "no error" do + end + end + options = ::Spec::Runner::Options.new(@err, @out) + ::Spec::Runner::Options.should_receive(:new).with(@err, @out).and_return(options) + options.add_example_group example_group + + heckle_runner = mock("heckle_runner") + heckle_runner.should_receive(:heckle_with) + $rspec_mocks.__send__(:mocks).delete(heckle_runner) + + options.heckle_runner = heckle_runner + options.add_example_group(example_group) + + Spec::Runner::CommandLine.run(OptionParser.parse([], @err, @out)) + heckle_runner.rspec_verify + end + + it "should run examples backwards if options.reverse is true" do + options = ::Spec::Runner::Options.new(@err, @out) + ::Spec::Runner::Options.should_receive(:new).with(@err, @out).and_return(options) + options.reverse = true + + b1 = Class.new(Spec::Example::ExampleGroup) + b2 = Class.new(Spec::Example::ExampleGroup) + + b2.should_receive(:run).ordered + b1.should_receive(:run).ordered + + options.add_example_group(b1) + options.add_example_group(b2) + + Spec::Runner::CommandLine.run(OptionParser.parse([], @err, @out)) + end + + it "should pass its ExampleGroup to the reporter" do + example_group = Class.new(::Spec::Example::ExampleGroup).describe("example_group") do + it "should" do + end + end + options = ::Spec::Runner::Options.new(@err, @out) + options.add_example_group(example_group) + + ::Spec::Runner::Options.should_receive(:new).with(@err, @out).and_return(options) + options.reporter.should_receive(:add_example_group).with(example_group) + + Spec::Runner::CommandLine.run(OptionParser.parse([], @err, @out)) + end + + it "runs only selected Examples when options.examples is set" do + options = ::Spec::Runner::Options.new(@err, @out) + ::Spec::Runner::Options.should_receive(:new).with(@err, @out).and_return(options) + + options.examples << "example_group should" + should_has_run = false + should_not_has_run = false + example_group = Class.new(::Spec::Example::ExampleGroup).describe("example_group") do + it "should" do + should_has_run = true + end + it "should not" do + should_not_has_run = true + end + end + + options.reporter.should_receive(:add_example_group).with(example_group) + + options.add_example_group example_group + Spec::Runner::CommandLine.run(OptionParser.parse([], @err, @out)) + + should_has_run.should be_true + should_not_has_run.should be_false + end + + it "sets Spec.run to true" do + ::Spec.run = false + ::Spec.should_not be_run + Spec::Runner::CommandLine.run(OptionParser.parse([], @err, @out)) + ::Spec.should be_run + end + end + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/runner/drb_command_line_spec.rb b/vendor/gems/rspec/spec/spec/runner/drb_command_line_spec.rb new file mode 100644 index 0000000000..760ec37a9a --- /dev/null +++ b/vendor/gems/rspec/spec/spec/runner/drb_command_line_spec.rb @@ -0,0 +1,92 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +module Spec + module Runner + describe DrbCommandLine, "without running local server" do + unless Config::CONFIG['ruby_install_name'] == 'jruby' + it "should print error when there is no running local server" do + err = StringIO.new + out = StringIO.new + DrbCommandLine.run(OptionParser.parse(['--version'], err, out)) + + err.rewind + err.read.should =~ /No server is running/ + end + end + end + + class DrbCommandLineSpec < ::Spec::Example::ExampleGroup + describe DrbCommandLine, "with local server" + + class CommandLineForSpec + def self.run(argv, stderr, stdout) + exit Spec::Runner::CommandLine.run(OptionParser.parse(argv, stderr, stdout)) + end + end + + unless Config::CONFIG['ruby_install_name'] == 'jruby' + before(:all) do + DRb.start_service("druby://localhost:8989", CommandLineForSpec) + @@drb_example_file_counter = 0 + end + + before(:each) do + create_dummy_spec_file + @@drb_example_file_counter = @@drb_example_file_counter + 1 + end + + after(:each) do + File.delete(@dummy_spec_filename) + end + + after(:all) do + DRb.stop_service + end + + it "should run against local server" do + out = run_spec_via_druby(['--version']) + out.should =~ /RSpec/n + end + + it "should output green colorized text when running with --colour option" do + out = run_spec_via_druby(["--colour", @dummy_spec_filename]) + out.should =~ /\e\[32m/n + end + + it "should output red colorized text when running with -c option" do + out = run_spec_via_druby(["-c", @dummy_spec_filename]) + out.should =~ /\e\[31m/n + end + + def create_dummy_spec_file + @dummy_spec_filename = File.expand_path(File.dirname(__FILE__)) + "/_dummy_spec#{@@drb_example_file_counter}.rb" + File.open(@dummy_spec_filename, 'w') do |f| + f.write %{ + describe "DUMMY CONTEXT for 'DrbCommandLine with -c option'" do + it "should be output with green bar" do + true.should be_true + end + + it "should be output with red bar" do + violated("I want to see a red bar!") + end + end + } + end + end + + def run_spec_via_druby(argv) + err, out = StringIO.new, StringIO.new + out.instance_eval do + def tty?; true end + end + options = ::Spec::Runner::Options.new(err, out) + options.argv = argv + Spec::Runner::DrbCommandLine.run(options) + out.rewind; out.read + end + end + + end + end +end diff --git a/vendor/gems/rspec/spec/spec/runner/empty_file.txt b/vendor/gems/rspec/spec/spec/runner/empty_file.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vendor/gems/rspec/spec/spec/runner/examples.txt b/vendor/gems/rspec/spec/spec/runner/examples.txt new file mode 100644 index 0000000000..2fcbd355d6 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/runner/examples.txt @@ -0,0 +1,2 @@ +Sir, if you were my husband, I would poison your drink. +Madam, if you were my wife, I would drink it. \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/runner/execution_context_spec.rb b/vendor/gems/rspec/spec/spec/runner/execution_context_spec.rb new file mode 100644 index 0000000000..82e7447c1e --- /dev/null +++ b/vendor/gems/rspec/spec/spec/runner/execution_context_spec.rb @@ -0,0 +1,31 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +describe "ExecutionContext" do + + it "should provide duck_type()" do + dt = duck_type(:length) + dt.should be_an_instance_of(Spec::Mocks::DuckTypeArgConstraint) + dt.matches?([]).should be_true + end + + it "should violate when violated()" do + lambda do + violated + end.should raise_error(Spec::Expectations::ExpectationNotMetError) + end + + it "should provide mock()" do + mock("thing").should be_an_instance_of(Spec::Mocks::Mock) + end + + it "should provide stub()" do + thing_stub = stub("thing").should be_an_instance_of(Spec::Mocks::Mock) + end + + it "should add method stubs to stub()" do + thing_stub = stub("thing", :a => "A", :b => "B") + thing_stub.a.should == "A" + thing_stub.b.should == "B" + end + +end diff --git a/vendor/gems/rspec/spec/spec/runner/failed.txt b/vendor/gems/rspec/spec/spec/runner/failed.txt new file mode 100644 index 0000000000..07c5442cfe --- /dev/null +++ b/vendor/gems/rspec/spec/spec/runner/failed.txt @@ -0,0 +1,3 @@ +heckler_spec.rb +command_line_spec.rb +reporter_spec.rb \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/runner/formatter/failing_example_groups_formatter_spec.rb b/vendor/gems/rspec/spec/spec/runner/formatter/failing_example_groups_formatter_spec.rb new file mode 100644 index 0000000000..a08b6e86d5 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/runner/formatter/failing_example_groups_formatter_spec.rb @@ -0,0 +1,44 @@ +require File.dirname(__FILE__) + '/../../../spec_helper' +require 'spec/runner/formatter/failing_example_groups_formatter' + +module Spec + module Runner + module Formatter + describe FailingExampleGroupsFormatter do + attr_reader :example_group, :formatter, :io + + before(:each) do + @io = StringIO.new + options = mock('options') + @formatter = FailingExampleGroupsFormatter.new(options, io) + @example_group = Class.new(::Spec::Example::ExampleGroup) + end + + it "should add example name for each failure" do + formatter.add_example_group(Class.new(ExampleGroup).describe("b 1")) + formatter.example_failed("e 1", nil, Reporter::Failure.new(nil, RuntimeError.new)) + formatter.add_example_group(Class.new(ExampleGroup).describe("b 2")) + formatter.example_failed("e 2", nil, Reporter::Failure.new(nil, RuntimeError.new)) + formatter.example_failed("e 3", nil, Reporter::Failure.new(nil, RuntimeError.new)) + io.string.should == "b 1\nb 2\n" + end + + it "should delimit ExampleGroup superclass descriptions with :" do + parent_example_group = Class.new(example_group).describe("Parent") + child_example_group = Class.new(parent_example_group).describe("#child_method") + grand_child_example_group = Class.new(child_example_group).describe("GrandChild") + + formatter.add_example_group(grand_child_example_group) + formatter.example_failed("failure", nil, Reporter::Failure.new(nil, RuntimeError.new)) + io.string.should == "Parent#child_method GrandChild\n" + end + + it "should remove druby url, which is used by Spec::Distributed" do + @formatter.add_example_group(Class.new(ExampleGroup).describe("something something (druby://99.99.99.99:99)")) + @formatter.example_failed("e 1", nil, Reporter::Failure.new(nil, RuntimeError.new)) + io.string.should == "something something\n" + end + end + end + end +end diff --git a/vendor/gems/rspec/spec/spec/runner/formatter/failing_examples_formatter_spec.rb b/vendor/gems/rspec/spec/spec/runner/formatter/failing_examples_formatter_spec.rb new file mode 100644 index 0000000000..fda64f95ff --- /dev/null +++ b/vendor/gems/rspec/spec/spec/runner/formatter/failing_examples_formatter_spec.rb @@ -0,0 +1,33 @@ +require File.dirname(__FILE__) + '/../../../spec_helper' +require 'spec/runner/formatter/failing_examples_formatter' + +module Spec + module Runner + module Formatter + describe FailingExamplesFormatter do + before(:each) do + @io = StringIO.new + options = mock('options') + @formatter = FailingExamplesFormatter.new(options, @io) + end + + it "should add example name for each failure" do + example_group_1 = Class.new(ExampleGroup).describe("A") + example_group_2 = Class.new(example_group_1).describe("B") + + @formatter.add_example_group(example_group_1) + @formatter.example_failed(example_group_1.it("a1"){}, nil, Reporter::Failure.new(nil, RuntimeError.new)) + @formatter.add_example_group(example_group_2) + @formatter.example_failed(example_group_2.it("b2"){}, nil, Reporter::Failure.new(nil, RuntimeError.new)) + @formatter.example_failed(example_group_2.it("b3"){}, nil, Reporter::Failure.new(nil, RuntimeError.new)) + @io.string.should eql(<<-EOF +A a1 +A B b2 +A B b3 +EOF +) + end + end + end + end +end diff --git a/vendor/gems/rspec/spec/spec/runner/formatter/html_formatted-1.8.4.html b/vendor/gems/rspec/spec/spec/runner/formatter/html_formatted-1.8.4.html new file mode 100644 index 0000000000..9cc458fdb7 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/runner/formatter/html_formatted-1.8.4.html @@ -0,0 +1,365 @@ + + + + + RSpec results + + + + + + +
    + + + +
    +

    RSpec Results

    + +
    +

     

    +

     

    +
    +
    + +
    +
    +
    +
    Mocker
    + +
    should be able to call mock()
    + + + +
    + should fail when expected message not received +
    +
    Mock 'poke me' expected :poke with (any args) once, but received it 0 times
    +
    ./failing_examples/mocking_example.rb:13:
    +./spec/spec/runner/formatter/html_formatter_spec.rb:24:
    +./spec/spec/runner/formatter/html_formatter_spec.rb:20:
    +
    11  it "should fail when expected message not received" do
    +12    mock = mock("poke me")
    +13    mock.should_receive(:poke)
    +14  end
    +15  
    +
    +
    + +
    + should fail when messages are received out of order +
    +
    Mock 'one two three' received :three out of order
    +
    ./failing_examples/mocking_example.rb:22:
    +./spec/spec/runner/formatter/html_formatter_spec.rb:24:
    +./spec/spec/runner/formatter/html_formatter_spec.rb:20:
    +
    20    mock.should_receive(:three).ordered
    +21    mock.one
    +22    mock.three
    +23    mock.two
    +24  end
    +
    +
    + +
    + should get yelled at when sending unexpected messages +
    +
    Mock 'don't talk to me' expected :any_message_at_all with (any args) 0 times, but received it once
    +
    ./failing_examples/mocking_example.rb:28:
    +./spec/spec/runner/formatter/html_formatter_spec.rb:24:
    +./spec/spec/runner/formatter/html_formatter_spec.rb:20:
    +
    26  it "should get yelled at when sending unexpected messages" do
    +27    mock = mock("don't talk to me")
    +28    mock.should_not_receive(:any_message_at_all)
    +29    mock.any_message_at_all
    +30  end
    +
    +
    + +
    + has a bug we need to fix +
    +
    Expected pending 'here is the bug' to fail. No Error was raised.
    +
    ./failing_examples/mocking_example.rb:33:
    +./spec/spec/runner/formatter/html_formatter_spec.rb:24:
    +./spec/spec/runner/formatter/html_formatter_spec.rb:20:
    +
    31
    +32  it "has a bug we need to fix" do
    +33    pending "here is the bug" do
    +34      # Actually, no. It's fixed. This will fail because it passes :-)
    +35      mock = mock("Bug")
    +
    +
    +
    +
    +
    +
    +
    Running specs with --diff
    + + +
    + should print diff of different strings +
    +
    expected: "RSpec is a\nbehaviour driven development\nframework for Ruby\n",
    +     got: "RSpec is a\nbehavior driven development\nframework for Ruby\n" (using ==)
    +Diff:
    +@@ -1,4 +1,4 @@
    + RSpec is a
    +-behavior driven development
    ++behaviour driven development
    + framework for Ruby
    +
    +
    ./failing_examples/diffing_spec.rb:13:
    +./spec/spec/runner/formatter/html_formatter_spec.rb:24:
    +./spec/spec/runner/formatter/html_formatter_spec.rb:20:
    +
    11framework for Ruby
    +12EOF
    +13    usa.should == uk
    +14  end
    +
    +
    + +
    + should print diff of different objects' pretty representation +
    +
    expected <Animal
    +name=bob,
    +species=tortoise
    +>
    +, got <Animal
    +name=bob,
    +species=giraffe
    +>
    + (using .eql?)
    +Diff:
    +@@ -1,5 +1,5 @@
    + <Animal
    + name=bob,
    +-species=giraffe
    ++species=tortoise
    + >
    +
    +
    ./failing_examples/diffing_spec.rb:34:
    +./spec/spec/runner/formatter/html_formatter_spec.rb:24:
    +./spec/spec/runner/formatter/html_formatter_spec.rb:20:
    +
    32    expected = Animal.new "bob", "giraffe"
    +33    actual   = Animal.new "bob", "tortoise"
    +34    expected.should eql(actual)
    +35  end
    +36end
    +
    +
    +
    +
    +
    +
    +
    A consumer of a stub
    + +
    should be able to stub methods on any Object
    +
    +
    +
    +
    +
    A stubbed method on a class
    + +
    should return the stubbed value
    + +
    should revert to the original method after each spec
    + +
    can stub! and mock the same message
    +
    +
    +
    +
    +
    A mock
    + +
    can stub!
    + +
    can stub! and mock
    + +
    can stub! and mock the same message
    +
    +
    +
    +
    +
    pending example (using pending method)
    + + +
    should be reported as "PENDING: for some reason" (PENDING: for some reason)
    +
    +
    +
    +
    +
    pending example (with no block)
    + + +
    should be reported as "PENDING: Not Yet Implemented" (PENDING: Not Yet Implemented)
    +
    +
    +
    +
    +
    pending example (with block for pending)
    + + +
    should have a failing block, passed to pending, reported as "PENDING: for some reason" (PENDING: for some reason)
    +
    +
    + + +
    +
    + + diff --git a/vendor/gems/rspec/spec/spec/runner/formatter/html_formatted-1.8.5-jruby.html b/vendor/gems/rspec/spec/spec/runner/formatter/html_formatted-1.8.5-jruby.html new file mode 100644 index 0000000000..8bf1ed9cd6 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/runner/formatter/html_formatted-1.8.5-jruby.html @@ -0,0 +1,387 @@ + + + + + RSpec results + + + + + + +
    + + + +
    +

    RSpec Results

    + +
    +

     

    +

     

    +
    +
    + +
    +
    +
    +
    Mocker
    + +
    should be able to call mock()
    + + + +
    + should fail when expected message not received +
    +
    Mock 'poke me' expected :poke with (any args) once, but received it 0 times
    +
    /Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./failing_examples/mocking_example.rb:13:in `should_receive'
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:24:in `run'
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:20:in `chdir'
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:20:in `chdir'
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:20:in `instance_eval'
    +
    11  it "should fail when expected message not received" do
    +12    mock = mock("poke me")
    +13    mock.should_receive(:poke)
    +14  end
    +15  
    +16# gem install syntax to get syntax highlighting
    +
    +
    + +
    + should fail when messages are received out of order +
    +
    Mock 'one two three' received :three out of order
    +
    /Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./failing_examples/mocking_example.rb:22:in `three'
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./failing_examples/mocking_example.rb:16:in `instance_eval'
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:24:in `run'
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:20:in `chdir'
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:20:in `chdir'
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:20:in `instance_eval'
    +
    20    mock.should_receive(:three).ordered
    +21    mock.one
    +22    mock.three
    +23    mock.two
    +24  end
    +25# gem install syntax to get syntax highlighting
    +
    +
    + +
    + should get yelled at when sending unexpected messages +
    +
    Mock 'don't talk to me' expected :any_message_at_all with (any args) 0 times, but received it once
    +
    /Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./failing_examples/mocking_example.rb:28:in `should_not_receive'
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:24:in `run'
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:20:in `chdir'
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:20:in `chdir'
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:20:in `instance_eval'
    +
    26  it "should get yelled at when sending unexpected messages" do
    +27    mock = mock("don't talk to me")
    +28    mock.should_not_receive(:any_message_at_all)
    +29    mock.any_message_at_all
    +30  end
    +31# gem install syntax to get syntax highlighting
    +
    +
    + +
    + has a bug we need to fix +
    +
    Expected pending 'here is the bug' to fail. No Error was raised.
    +
    /Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./failing_examples/mocking_example.rb:33:in `pending'
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./failing_examples/mocking_example.rb:33:in `instance_eval'
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:24:in `run'
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:20:in `chdir'
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:20:in `chdir'
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:20:in `instance_eval'
    +
    31
    +32  it "has a bug we need to fix" do
    +33    pending "here is the bug" do
    +34      # Actually, no. It's fixed. This will fail because it passes :-)
    +35      mock = mock("Bug")
    +36# gem install syntax to get syntax highlighting
    +
    +
    +
    +
    +
    +
    +
    Running specs with --diff
    + + +
    + should print diff of different strings +
    +
    expected: "RSpec is a\nbehaviour driven development\nframework for Ruby\n",
    +     got: "RSpec is a\nbehavior driven development\nframework for Ruby\n" (using ==)
    +Diff:
    +@@ -1,4 +1,4 @@
    + RSpec is a
    +-behavior driven development
    ++behaviour driven development
    + framework for Ruby
    +
    +
    /Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./failing_examples/diffing_spec.rb:13:in `=='
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:24:in `run'
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:20:in `chdir'
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:20:in `chdir'
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:20:in `instance_eval'
    +
    11framework for Ruby
    +12EOF
    +13    usa.should == uk
    +14  end
    +15
    +16# gem install syntax to get syntax highlighting
    +
    +
    + +
    + should print diff of different objects' pretty representation +
    +
    expected <Animal
    +name=bob,
    +species=tortoise
    +>
    +, got <Animal
    +name=bob,
    +species=giraffe
    +>
    + (using .eql?)
    +Diff:
    +@@ -1,5 +1,5 @@
    + <Animal
    + name=bob,
    +-species=giraffe
    ++species=tortoise
    + >
    +
    +
    /Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./failing_examples/diffing_spec.rb:34:in `should'
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./failing_examples/diffing_spec.rb:31:in `instance_eval'
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:24:in `run'
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:20:in `chdir'
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:20:in `chdir'
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:20:in `instance_eval'
    +
    32    expected = Animal.new "bob", "giraffe"
    +33    actual   = Animal.new "bob", "tortoise"
    +34    expected.should eql(actual)
    +35  end
    +36end
    +37# gem install syntax to get syntax highlighting
    +
    +
    +
    +
    +
    +
    +
    A consumer of a stub
    + +
    should be able to stub methods on any Object
    +
    +
    +
    +
    +
    A stubbed method on a class
    + +
    should return the stubbed value
    + +
    should revert to the original method after each spec
    + +
    can stub! and mock the same message
    +
    +
    +
    +
    +
    A mock
    + +
    can stub!
    + +
    can stub! and mock
    + +
    can stub! and mock the same message
    +
    +
    +
    +
    +
    pending example (using pending method)
    + + +
    should be reported as "PENDING: for some reason" (PENDING: for some reason)
    +
    +
    +
    +
    +
    pending example (with no block)
    + + +
    should be reported as "PENDING: Not Yet Implemented" (PENDING: Not Yet Implemented)
    +
    +
    +
    +
    +
    pending example (with block for pending)
    + + +
    should have a failing block, passed to pending, reported as "PENDING: for some reason" (PENDING: for some reason)
    +
    +
    + + +
    +
    + + diff --git a/vendor/gems/rspec/spec/spec/runner/formatter/html_formatted-1.8.5.html b/vendor/gems/rspec/spec/spec/runner/formatter/html_formatted-1.8.5.html new file mode 100644 index 0000000000..cda7226bfe --- /dev/null +++ b/vendor/gems/rspec/spec/spec/runner/formatter/html_formatted-1.8.5.html @@ -0,0 +1,371 @@ + + + + + RSpec results + + + + + + +
    + + + +
    +

    RSpec Results

    + +
    +

     

    +

     

    +
    +
    + +
    +
    +
    +
    Mocker
    + +
    should be able to call mock()
    + + + +
    + should fail when expected message not received +
    +
    Mock 'poke me' expected :poke with (any args) once, but received it 0 times
    +
    ./failing_examples/mocking_example.rb:13:
    +./spec/spec/runner/formatter/html_formatter_spec.rb:17:
    +./spec/spec/runner/formatter/html_formatter_spec.rb:13:in `chdir'
    +./spec/spec/runner/formatter/html_formatter_spec.rb:13:
    +
    11  it "should fail when expected message not received" do
    +12    mock = mock("poke me")
    +13    mock.should_receive(:poke)
    +14  end
    +15  
    +
    +
    + +
    + should fail when messages are received out of order +
    +
    Mock 'one two three' received :three out of order
    +
    ./failing_examples/mocking_example.rb:22:
    +./spec/spec/runner/formatter/html_formatter_spec.rb:17:
    +./spec/spec/runner/formatter/html_formatter_spec.rb:13:in `chdir'
    +./spec/spec/runner/formatter/html_formatter_spec.rb:13:
    +
    20    mock.should_receive(:three).ordered
    +21    mock.one
    +22    mock.three
    +23    mock.two
    +24  end
    +
    +
    + +
    + should get yelled at when sending unexpected messages +
    +
    Mock 'don't talk to me' expected :any_message_at_all with (any args) 0 times, but received it once
    +
    ./failing_examples/mocking_example.rb:28:
    +./spec/spec/runner/formatter/html_formatter_spec.rb:17:
    +./spec/spec/runner/formatter/html_formatter_spec.rb:13:in `chdir'
    +./spec/spec/runner/formatter/html_formatter_spec.rb:13:
    +
    26  it "should get yelled at when sending unexpected messages" do
    +27    mock = mock("don't talk to me")
    +28    mock.should_not_receive(:any_message_at_all)
    +29    mock.any_message_at_all
    +30  end
    +
    +
    + +
    + has a bug we need to fix +
    +
    Expected pending 'here is the bug' to fail. No Error was raised.
    +
    ./failing_examples/mocking_example.rb:33:
    +./spec/spec/runner/formatter/html_formatter_spec.rb:17:
    +./spec/spec/runner/formatter/html_formatter_spec.rb:13:in `chdir'
    +./spec/spec/runner/formatter/html_formatter_spec.rb:13:
    +
    31
    +32  it "has a bug we need to fix" do
    +33    pending "here is the bug" do
    +34      # Actually, no. It's fixed. This will fail because it passes :-)
    +35      mock = mock("Bug")
    +
    +
    +
    +
    +
    +
    +
    Running specs with --diff
    + + +
    + should print diff of different strings +
    +
    expected: "RSpec is a\nbehaviour driven development\nframework for Ruby\n",
    +     got: "RSpec is a\nbehavior driven development\nframework for Ruby\n" (using ==)
    +Diff:
    +@@ -1,4 +1,4 @@
    + RSpec is a
    +-behavior driven development
    ++behaviour driven development
    + framework for Ruby
    +
    +
    ./failing_examples/diffing_spec.rb:13:
    +./spec/spec/runner/formatter/html_formatter_spec.rb:17:
    +./spec/spec/runner/formatter/html_formatter_spec.rb:13:in `chdir'
    +./spec/spec/runner/formatter/html_formatter_spec.rb:13:
    +
    11framework for Ruby
    +12EOF
    +13    usa.should == uk
    +14  end
    +
    +
    + +
    + should print diff of different objects' pretty representation +
    +
    expected <Animal
    +name=bob,
    +species=tortoise
    +>
    +, got <Animal
    +name=bob,
    +species=giraffe
    +>
    + (using .eql?)
    +Diff:
    +@@ -1,5 +1,5 @@
    + <Animal
    + name=bob,
    +-species=giraffe
    ++species=tortoise
    + >
    +
    +
    ./failing_examples/diffing_spec.rb:34:
    +./spec/spec/runner/formatter/html_formatter_spec.rb:17:
    +./spec/spec/runner/formatter/html_formatter_spec.rb:13:in `chdir'
    +./spec/spec/runner/formatter/html_formatter_spec.rb:13:
    +
    32    expected = Animal.new "bob", "giraffe"
    +33    actual   = Animal.new "bob", "tortoise"
    +34    expected.should eql(actual)
    +35  end
    +36end
    +
    +
    +
    +
    +
    +
    +
    A consumer of a stub
    + +
    should be able to stub methods on any Object
    +
    +
    +
    +
    +
    A stubbed method on a class
    + +
    should return the stubbed value
    + +
    should revert to the original method after each spec
    + +
    can stub! and mock the same message
    +
    +
    +
    +
    +
    A mock
    + +
    can stub!
    + +
    can stub! and mock
    + +
    can stub! and mock the same message
    +
    +
    +
    +
    +
    pending example (using pending method)
    + + +
    should be reported as "PENDING: for some reason" (PENDING: for some reason)
    +
    +
    +
    +
    +
    pending example (with no block)
    + + +
    should be reported as "PENDING: Not Yet Implemented" (PENDING: Not Yet Implemented)
    +
    +
    +
    +
    +
    pending example (with block for pending)
    + + +
    should have a failing block, passed to pending, reported as "PENDING: for some reason" (PENDING: for some reason)
    +
    +
    + + +
    +
    + + diff --git a/vendor/gems/rspec/spec/spec/runner/formatter/html_formatted-1.8.6-jruby.html b/vendor/gems/rspec/spec/spec/runner/formatter/html_formatted-1.8.6-jruby.html new file mode 100644 index 0000000000..4666218653 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/runner/formatter/html_formatted-1.8.6-jruby.html @@ -0,0 +1,381 @@ + + + + + RSpec results + + + + + + +
    + + + +
    +

    RSpec Results

    + +
    +

     

    +

     

    +
    +
    + +
    +
    +
    +
    Mocker
    + +
    should be able to call mock()
    + + + +
    + should fail when expected message not received +
    +
    Mock 'poke me' expected :poke with (any args) once, but received it 0 times
    +
    /Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./failing_examples/mocking_example.rb:13:
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:28:
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:24:in `chdir'
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:24:
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:24:in `instance_eval'
    +
    11  it "should fail when expected message not received" do
    +12    mock = mock("poke me")
    +13    mock.should_receive(:poke)
    +14  end
    +15  
    +
    +
    + +
    + should fail when messages are received out of order +
    +
    Mock 'one two three' received :three out of order
    +
    /Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./failing_examples/mocking_example.rb:22:
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./failing_examples/mocking_example.rb:16:in `instance_eval'
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:28:
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:24:in `chdir'
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:24:
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:24:in `instance_eval'
    +
    20    mock.should_receive(:three).ordered
    +21    mock.one
    +22    mock.three
    +23    mock.two
    +24  end
    +
    +
    + +
    + should get yelled at when sending unexpected messages +
    +
    Mock 'don't talk to me' expected :any_message_at_all with (any args) 0 times, but received it once
    +
    /Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./failing_examples/mocking_example.rb:28:
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:28:
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:24:in `chdir'
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:24:
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:24:in `instance_eval'
    +
    26  it "should get yelled at when sending unexpected messages" do
    +27    mock = mock("don't talk to me")
    +28    mock.should_not_receive(:any_message_at_all)
    +29    mock.any_message_at_all
    +30  end
    +
    +
    + +
    + has a bug we need to fix +
    +
    Expected pending 'here is the bug' to fail. No Error was raised.
    +
    /Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./failing_examples/mocking_example.rb:33:
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./failing_examples/mocking_example.rb:33:in `instance_eval'
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:28:
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:24:in `chdir'
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:24:
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:24:in `instance_eval'
    +
    31
    +32  it "has a bug we need to fix" do
    +33    pending "here is the bug" do
    +34      # Actually, no. It's fixed. This will fail because it passes :-)
    +35      mock = mock("Bug")
    +
    +
    +
    +
    +
    +
    +
    Running specs with --diff
    + + +
    + should print diff of different strings +
    +
    expected: "RSpec is a\nbehaviour driven development\nframework for Ruby\n",
    +     got: "RSpec is a\nbehavior driven development\nframework for Ruby\n" (using ==)
    +Diff:
    +@@ -1,4 +1,4 @@
    + RSpec is a
    +-behavior driven development
    ++behaviour driven development
    + framework for Ruby
    +
    +
    /Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./failing_examples/diffing_spec.rb:13:
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./failing_examples/diffing_spec.rb:2:in `instance_eval'
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:28:
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:24:in `chdir'
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:24:
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:24:in `instance_eval'
    +
    11framework for Ruby
    +12EOF
    +13    usa.should == uk
    +14  end
    +
    +
    + +
    + should print diff of different objects' pretty representation +
    +
    expected <Animal
    +name=bob,
    +species=tortoise
    +>
    +, got <Animal
    +name=bob,
    +species=giraffe
    +>
    + (using .eql?)
    +Diff:
    +@@ -1,5 +1,5 @@
    + <Animal
    + name=bob,
    +-species=giraffe
    ++species=tortoise
    + >
    +
    +
    /Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./failing_examples/diffing_spec.rb:34:
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./failing_examples/diffing_spec.rb:31:in `instance_eval'
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:28:
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:24:in `chdir'
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:24:
    +/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:24:in `instance_eval'
    +
    32    expected = Animal.new "bob", "giraffe"
    +33    actual   = Animal.new "bob", "tortoise"
    +34    expected.should eql(actual)
    +35  end
    +36end
    +
    +
    +
    +
    +
    +
    +
    A consumer of a stub
    + +
    should be able to stub methods on any Object
    +
    +
    +
    +
    +
    A stubbed method on a class
    + +
    should return the stubbed value
    + +
    should revert to the original method after each spec
    + +
    can stub! and mock the same message
    +
    +
    +
    +
    +
    A mock
    + +
    can stub!
    + +
    can stub! and mock
    + +
    can stub! and mock the same message
    +
    +
    +
    +
    +
    pending example (using pending method)
    + + +
    should be reported as "PENDING: for some reason" (PENDING: for some reason)
    +
    +
    +
    +
    +
    pending example (with no block)
    + + +
    should be reported as "PENDING: Not Yet Implemented" (PENDING: Not Yet Implemented)
    +
    +
    +
    +
    +
    pending example (with block for pending)
    + + +
    should have a failing block, passed to pending, reported as "PENDING: for some reason" (PENDING: for some reason)
    +
    +
    + + +
    +
    + + diff --git a/vendor/gems/rspec/spec/spec/runner/formatter/html_formatted-1.8.6.html b/vendor/gems/rspec/spec/spec/runner/formatter/html_formatted-1.8.6.html new file mode 100644 index 0000000000..511495bcd0 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/runner/formatter/html_formatted-1.8.6.html @@ -0,0 +1,365 @@ + + + + + RSpec results + + + + + + +
    + + + +
    +

    RSpec Results

    + +
    +

     

    +

     

    +
    +
    + +
    +
    +
    +
    Mocker
    + +
    should be able to call mock()
    + + + +
    + should fail when expected message not received +
    +
    Mock 'poke me' expected :poke with (any args) once, but received it 0 times
    +
    ./failing_examples/mocking_example.rb:13:
    +./spec/spec/runner/formatter/html_formatter_spec.rb:18:
    +./spec/spec/runner/formatter/html_formatter_spec.rb:14:in `chdir'
    +./spec/spec/runner/formatter/html_formatter_spec.rb:14:
    +
    11  it "should fail when expected message not received" do
    +12    mock = mock("poke me")
    +13    mock.should_receive(:poke)
    +14  end
    +15  
    +
    +
    + +
    + should fail when messages are received out of order +
    +
    Mock 'one two three' received :three out of order
    +
    ./failing_examples/mocking_example.rb:22:
    +./spec/spec/runner/formatter/html_formatter_spec.rb:18:
    +./spec/spec/runner/formatter/html_formatter_spec.rb:14:in `chdir'
    +./spec/spec/runner/formatter/html_formatter_spec.rb:14:
    +
    20    mock.should_receive(:three).ordered
    +21    mock.one
    +22    mock.three
    +23    mock.two
    +24  end
    +
    +
    + +
    + should get yelled at when sending unexpected messages +
    +
    Mock 'don't talk to me' expected :any_message_at_all with (any args) 0 times, but received it once
    +
    ./failing_examples/mocking_example.rb:28:
    +./spec/spec/runner/formatter/html_formatter_spec.rb:18:
    +./spec/spec/runner/formatter/html_formatter_spec.rb:14:in `chdir'
    +./spec/spec/runner/formatter/html_formatter_spec.rb:14:
    +
    26  it "should get yelled at when sending unexpected messages" do
    +27    mock = mock("don't talk to me")
    +28    mock.should_not_receive(:any_message_at_all)
    +29    mock.any_message_at_all
    +30  end
    +
    +
    + +
    + has a bug we need to fix +
    +
    Expected pending 'here is the bug' to fail. No Error was raised.
    + +
    31
    +32  it "has a bug we need to fix" do
    +33    pending "here is the bug" do
    +34      # Actually, no. It's fixed. This will fail because it passes :-)
    +35      mock = mock("Bug")
    +
    +
    +
    +
    +
    +
    +
    Running specs with --diff
    + + +
    + should print diff of different strings +
    +
    expected: "RSpec is a\nbehaviour driven development\nframework for Ruby\n",
    +     got: "RSpec is a\nbehavior driven development\nframework for Ruby\n" (using ==)
    +Diff:
    +@@ -1,4 +1,4 @@
    + RSpec is a
    +-behavior driven development
    ++behaviour driven development
    + framework for Ruby
    +
    + +
    11framework for Ruby
    +12EOF
    +13    usa.should == uk
    +14  end
    +
    +
    + +
    + should print diff of different objects' pretty representation +
    +
    expected <Animal
    +name=bob,
    +species=tortoise
    +>
    +, got <Animal
    +name=bob,
    +species=giraffe
    +>
    + (using .eql?)
    +Diff:
    +@@ -1,5 +1,5 @@
    + <Animal
    + name=bob,
    +-species=giraffe
    ++species=tortoise
    + >
    +
    +
    ./failing_examples/mocking_example.rb:33:
    +./spec/spec/runner/formatter/html_formatter_spec.rb:18:
    +./spec/spec/runner/formatter/html_formatter_spec.rb:14:in `chdir'
    +./spec/spec/runner/formatter/html_formatter_spec.rb:14:
    +
    32    expected = Animal.new "bob", "giraffe"
    +33    actual   = Animal.new "bob", "tortoise"
    +34    expected.should eql(actual)
    +35  end
    +36end
    +
    +
    +
    +
    +
    +
    +
    A consumer of a stub
    + +
    should be able to stub methods on any Object
    +
    +
    +
    +
    +
    A stubbed method on a class
    + +
    should return the stubbed value
    + +
    should revert to the original method after each spec
    + +
    can stub! and mock the same message
    +
    +
    +
    +
    +
    A mock
    + +
    can stub!
    + +
    can stub! and mock
    + +
    can stub! and mock the same message
    +
    +
    +
    +
    +
    pending example (using pending method)
    + + +
    should be reported as "PENDING: for some reason" (PENDING: for some reason)
    +
    +
    +
    +
    +
    pending example (with no block)
    + + +
    should be reported as "PENDING: Not Yet Implemented" (PENDING: Not Yet Implemented)
    +
    +
    +
    +
    +
    pending example (with block for pending)
    + + +
    should have a failing block, passed to pending, reported as "PENDING: for some reason" (PENDING: for some reason)
    +
    +
    + + +
    +
    + + diff --git a/vendor/gems/rspec/spec/spec/runner/formatter/html_formatter_spec.rb b/vendor/gems/rspec/spec/spec/runner/formatter/html_formatter_spec.rb new file mode 100644 index 0000000000..5ba39f0e9f --- /dev/null +++ b/vendor/gems/rspec/spec/spec/runner/formatter/html_formatter_spec.rb @@ -0,0 +1,66 @@ +require File.dirname(__FILE__) + '/../../../spec_helper' +require 'hpricot' # Needed to compare generated with wanted HTML +require 'spec/runner/formatter/html_formatter' + +module Spec + module Runner + module Formatter + describe HtmlFormatter do + ['--diff', '--dry-run'].each do |opt| + def jruby? + PLATFORM == 'java' + end + + it "should produce HTML identical to the one we designed manually with #{opt}" do + root = File.expand_path(File.dirname(__FILE__) + '/../../../..') + suffix = jruby? ? '-jruby' : '' + expected_file = File.dirname(__FILE__) + "/html_formatted-#{::VERSION}#{suffix}.html" + raise "There is no HTML file with expected content for this platform: #{expected_file}" unless File.file?(expected_file) + expected_html = File.read(expected_file) + unless jruby? + raise "There should be no absolute paths in html_formatted.html!!" if (expected_html =~ /\/Users/n || expected_html =~ /\/home/n) + end + + Dir.chdir(root) do + args = ['failing_examples/mocking_example.rb', 'failing_examples/diffing_spec.rb', 'examples/pure/stubbing_example.rb', 'examples/pure/pending_example.rb', '--format', 'html', opt] + err = StringIO.new + out = StringIO.new + CommandLine.run( + OptionParser.parse(args, err, out) + ) + + seconds = /\d+\.\d+ seconds/ + html = out.string.gsub seconds, 'x seconds' + expected_html.gsub! seconds, 'x seconds' + + if opt == '--diff' + # Uncomment this line temporarily in order to overwrite the expected with actual. + # Use with care!!! + # File.open(expected_file, 'w') {|io| io.write(html)} + + doc = Hpricot(html) + backtraces = doc.search("div.backtrace").collect {|e| e.at("/pre").inner_html} + doc.search("div.backtrace").remove + + expected_doc = Hpricot(expected_html) + expected_backtraces = expected_doc.search("div.backtrace").collect {|e| e.at("/pre").inner_html} + expected_doc.search("div.backtrace").remove + + doc.inner_html.should == expected_doc.inner_html + + expected_backtraces.each_with_index do |expected_line, i| + expected_path, expected_line_number, expected_suffix = expected_line.split(':') + actual_path, actual_line_number, actual_suffix = backtraces[i].split(':') + File.expand_path(actual_path).should == File.expand_path(expected_path) + actual_line_number.should == expected_line_number + end + else + html.should =~ /This was a dry-run/m + end + end + end + end + end + end + end +end diff --git a/vendor/gems/rspec/spec/spec/runner/formatter/profile_formatter_spec.rb b/vendor/gems/rspec/spec/spec/runner/formatter/profile_formatter_spec.rb new file mode 100644 index 0000000000..9818054110 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/runner/formatter/profile_formatter_spec.rb @@ -0,0 +1,65 @@ +require File.dirname(__FILE__) + '/../../../spec_helper.rb' +require 'spec/runner/formatter/profile_formatter' + +module Spec + module Runner + module Formatter + describe ProfileFormatter do + attr_reader :io, :formatter + before(:each) do + @io = StringIO.new + options = mock('options') + options.stub!(:colour).and_return(true) + @formatter = ProfileFormatter.new(options, io) + end + + it "should print a heading" do + formatter.start(0) + io.string.should eql("Profiling enabled.\n") + end + + it "should record the current time when starting a new example" do + now = Time.now + Time.stub!(:now).and_return(now) + formatter.example_started('should foo') + formatter.instance_variable_get("@time").should == now + end + + it "should correctly record a passed example" do + now = Time.now + Time.stub!(:now).and_return(now) + parent_example_group = Class.new(ExampleGroup).describe('Parent') + child_example_group = Class.new(parent_example_group).describe('Child') + + formatter.add_example_group(child_example_group) + + formatter.example_started('when foo') + Time.stub!(:now).and_return(now+1) + formatter.example_passed(stub('foo', :description => 'i like ice cream')) + + formatter.start_dump + io.string.should include('Parent Child') + end + + it "should sort the results in descending order" do + formatter.instance_variable_set("@example_times", [['a', 'a', 0.1], ['b', 'b', 0.3], ['c', 'c', 0.2]]) + formatter.start_dump + formatter.instance_variable_get("@example_times").should == [ ['b', 'b', 0.3], ['c', 'c', 0.2], ['a', 'a', 0.1]] + end + + it "should print the top 10 results" do + example_group = Class.new(::Spec::Example::ExampleGroup).describe("ExampleGroup") + formatter.add_example_group(example_group) + formatter.instance_variable_set("@time", Time.now) + + 15.times do + formatter.example_passed(stub('foo', :description => 'i like ice cream')) + end + + io.should_receive(:print).exactly(10) + formatter.start_dump + end + end + end + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/runner/formatter/progress_bar_formatter_spec.rb b/vendor/gems/rspec/spec/spec/runner/formatter/progress_bar_formatter_spec.rb new file mode 100644 index 0000000000..127a617c1c --- /dev/null +++ b/vendor/gems/rspec/spec/spec/runner/formatter/progress_bar_formatter_spec.rb @@ -0,0 +1,127 @@ +require File.dirname(__FILE__) + '/../../../spec_helper.rb' +require 'spec/runner/formatter/progress_bar_formatter' + +module Spec + module Runner + module Formatter + describe ProgressBarFormatter do + before(:each) do + @io = StringIO.new + @options = mock('options') + @options.stub!(:dry_run).and_return(false) + @options.stub!(:colour).and_return(false) + @formatter = ProgressBarFormatter.new(@options, @io) + end + + it "should produce line break on start dump" do + @formatter.start_dump + @io.string.should eql("\n") + end + + it "should produce standard summary without pending when pending has a 0 count" do + @formatter.dump_summary(3, 2, 1, 0) + @io.string.should eql("\nFinished in 3 seconds\n\n2 examples, 1 failure\n") + end + + it "should produce standard summary" do + @formatter.example_pending("example_group", ExampleGroup.new("example"), "message") + @io.rewind + @formatter.dump_summary(3, 2, 1, 1) + @io.string.should eql(%Q| +Finished in 3 seconds + +2 examples, 1 failure, 1 pending +|) + end + + it "should push green dot for passing spec" do + @io.should_receive(:tty?).and_return(true) + @options.should_receive(:colour).and_return(true) + @formatter.example_passed("spec") + @io.string.should == "\e[32m.\e[0m" + end + + it "should push red F for failure spec" do + @io.should_receive(:tty?).and_return(true) + @options.should_receive(:colour).and_return(true) + @formatter.example_failed("spec", 98, Reporter::Failure.new("c s", Spec::Expectations::ExpectationNotMetError.new)) + @io.string.should eql("\e[31mF\e[0m") + end + + it "should push magenta F for error spec" do + @io.should_receive(:tty?).and_return(true) + @options.should_receive(:colour).and_return(true) + @formatter.example_failed("spec", 98, Reporter::Failure.new("c s", RuntimeError.new)) + @io.string.should eql("\e[35mF\e[0m") + end + + it "should push blue F for fixed pending spec" do + @io.should_receive(:tty?).and_return(true) + @options.should_receive(:colour).and_return(true) + @formatter.example_failed("spec", 98, Reporter::Failure.new("c s", Spec::Example::PendingExampleFixedError.new)) + @io.string.should eql("\e[34mF\e[0m") + end + + it "should push nothing on start" do + @formatter.start(4) + @io.string.should eql("") + end + + it "should ensure two ':' in the first backtrace" do + backtrace = ["/tmp/x.rb:1", "/tmp/x.rb:2", "/tmp/x.rb:3"] + @formatter.format_backtrace(backtrace).should eql(<<-EOE.rstrip) +/tmp/x.rb:1: +/tmp/x.rb:2: +/tmp/x.rb:3: +EOE + + backtrace = ["/tmp/x.rb:1: message", "/tmp/x.rb:2", "/tmp/x.rb:3"] + @formatter.format_backtrace(backtrace).should eql(<<-EOE.rstrip) +/tmp/x.rb:1: message +/tmp/x.rb:2: +/tmp/x.rb:3: +EOE + end + + it "should dump pending" do + @formatter.example_pending("example_group", ExampleGroup.new("example"), "message") + @formatter.dump_pending + @io.string.should =~ /Pending\:\nexample_group example \(message\)\n/ + end + end + + describe "ProgressBarFormatter outputting to custom out" do + before(:each) do + @out = mock("out") + @options = mock('options') + @out.stub!(:puts) + @formatter = ProgressBarFormatter.new(@options, @out) + @formatter.class.send :public, :output_to_tty? + end + + after(:each) do + @formatter.class.send :protected, :output_to_tty? + end + + it "should not throw NoMethodError on output_to_tty?" do + @out.should_receive(:tty?).and_raise(NoMethodError) + @formatter.output_to_tty?.should be_false + end + end + + describe ProgressBarFormatter, "dry run" do + before(:each) do + @io = StringIO.new + options = mock('options') + options.stub!(:dry_run).and_return(true) + @formatter = ProgressBarFormatter.new(options, @io) + end + + it "should not produce summary on dry run" do + @formatter.dump_summary(3, 2, 1, 0) + @io.string.should eql("") + end + end + end + end +end diff --git a/vendor/gems/rspec/spec/spec/runner/formatter/snippet_extractor_spec.rb b/vendor/gems/rspec/spec/spec/runner/formatter/snippet_extractor_spec.rb new file mode 100644 index 0000000000..4bb2f15853 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/runner/formatter/snippet_extractor_spec.rb @@ -0,0 +1,18 @@ +require File.dirname(__FILE__) + '/../../../spec_helper.rb' +require 'spec/runner/formatter/snippet_extractor' + +module Spec + module Runner + module Formatter + describe SnippetExtractor do + it "should fall back on a default message when it doesn't understand a line" do + SnippetExtractor.new.snippet_for("blech").should == ["# Couldn't get snippet for blech", 1] + end + + it "should fall back on a default message when it doesn't find the file" do + SnippetExtractor.new.lines_around("blech", 8).should == "# Couldn't get snippet for blech" + end + end + end + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/runner/formatter/spec_mate_formatter_spec.rb b/vendor/gems/rspec/spec/spec/runner/formatter/spec_mate_formatter_spec.rb new file mode 100644 index 0000000000..e782254e25 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/runner/formatter/spec_mate_formatter_spec.rb @@ -0,0 +1,103 @@ +require File.dirname(__FILE__) + '/../../../spec_helper' +require 'hpricot' # Needed to compare generated with wanted HTML +require 'spec/runner/formatter/text_mate_formatter' + +module Spec + module Runner + module Formatter + describe TextMateFormatter do + attr_reader :root, :suffix, :expected_file + before do + @root = File.expand_path(File.dirname(__FILE__) + '/../../../..') + @suffix = jruby? ? '-jruby' : '' + @expected_file = File.dirname(__FILE__) + "/text_mate_formatted-#{::VERSION}#{suffix}.html" + end + + def jruby? + PLATFORM == 'java' + end + + def produces_html_identical_to_manually_designed_document(opt) + root = File.expand_path(File.dirname(__FILE__) + '/../../../..') + + Dir.chdir(root) do + args = [ + 'failing_examples/mocking_example.rb', + 'failing_examples/diffing_spec.rb', + 'examples/pure/stubbing_example.rb', + 'examples/pure/pending_example.rb', + '--format', + 'textmate', + opt + ] + err = StringIO.new + out = StringIO.new + options = ::Spec::Runner::OptionParser.parse(args, err, out) + Spec::Runner::CommandLine.run(options) + + yield(out.string) + end + end + + # # Uncomment this spec temporarily in order to overwrite the expected with actual. + # # Use with care!!! + # describe TextMateFormatter, "functional spec file generator" do + # it "generates a new comparison file" do + # Dir.chdir(root) do + # args = ['failing_examples/mocking_example.rb', 'failing_examples/diffing_spec.rb', 'examples/pure/stubbing_example.rb', 'examples/pure/pending_example.rb', '--format', 'textmate', '--diff'] + # err = StringIO.new + # out = StringIO.new + # Spec::Runner::CommandLine.run( + # ::Spec::Runner::OptionParser.parse(args, err, out) + # ) + # + # seconds = /\d+\.\d+ seconds/ + # html = out.string.gsub seconds, 'x seconds' + # + # File.open(expected_file, 'w') {|io| io.write(html)} + # end + # end + # end + + describe "functional spec using --diff" do + it "should produce HTML identical to the one we designed manually with --diff" do + produces_html_identical_to_manually_designed_document("--diff") do |html| + suffix = jruby? ? '-jruby' : '' + expected_file = File.dirname(__FILE__) + "/text_mate_formatted-#{::VERSION}#{suffix}.html" + unless File.file?(expected_file) + raise "There is no HTML file with expected content for this platform: #{expected_file}" + end + expected_html = File.read(expected_file) + + seconds = /\d+\.\d+ seconds/ + html.gsub! seconds, 'x seconds' + expected_html.gsub! seconds, 'x seconds' + + doc = Hpricot(html) + backtraces = doc.search("div.backtrace/a") + doc.search("div.backtrace").remove + + expected_doc = Hpricot(expected_html) + expected_doc.search("div.backtrace").remove + + doc.inner_html.should == expected_doc.inner_html + + backtraces.each do |backtrace_link| + backtrace_link[:href].should include("txmt://open?url=") + end + end + end + + end + + describe "functional spec using --dry-run" do + it "should produce HTML identical to the one we designed manually with --dry-run" do + produces_html_identical_to_manually_designed_document("--dry-run") do |html, expected_html| + html.should =~ /This was a dry-run/m + end + end + end + end + end + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/runner/formatter/specdoc_formatter_spec.rb b/vendor/gems/rspec/spec/spec/runner/formatter/specdoc_formatter_spec.rb new file mode 100644 index 0000000000..79995309d9 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/runner/formatter/specdoc_formatter_spec.rb @@ -0,0 +1,126 @@ +require File.dirname(__FILE__) + '/../../../spec_helper.rb' +require 'spec/runner/formatter/specdoc_formatter' + +module Spec + module Runner + module Formatter + describe SpecdocFormatter do + it_should_behave_like "sandboxed rspec_options" + attr_reader :io, :options, :formatter, :example_group + before(:each) do + @io = StringIO.new + options.stub!(:dry_run).and_return(false) + options.stub!(:colour).and_return(false) + @formatter = SpecdocFormatter.new(options, io) + @example_group = Class.new(::Spec::Example::ExampleGroup).describe("ExampleGroup") + end + + describe "where ExampleGroup has no superclasss with a description" do + before do + formatter.add_example_group(example_group) + end + + it "should produce standard summary without pending when pending has a 0 count" do + formatter.dump_summary(3, 2, 1, 0) + io.string.should have_example_group_output("\nFinished in 3 seconds\n\n2 examples, 1 failure\n") + end + + it "should produce standard summary" do + formatter.dump_summary(3, 2, 1, 4) + io.string.should have_example_group_output("\nFinished in 3 seconds\n\n2 examples, 1 failure, 4 pending\n") + end + + it "should push ExampleGroup name" do + io.string.should eql("\nExampleGroup\n") + end + + it "when having an error, should push failing spec name and failure number" do + formatter.example_failed( + example_group.it("spec"), + 98, + Reporter::Failure.new("c s", RuntimeError.new) + ) + io.string.should have_example_group_output("- spec (ERROR - 98)\n") + end + + it "when having an expectation failure, should push failing spec name and failure number" do + formatter.example_failed( + example_group.it("spec"), + 98, + Reporter::Failure.new("c s", Spec::Expectations::ExpectationNotMetError.new) + ) + io.string.should have_example_group_output("- spec (FAILED - 98)\n") + end + + it "should push nothing on start" do + formatter.start(5) + io.string.should have_example_group_output("") + end + + it "should push nothing on start dump" do + formatter.start_dump + io.string.should have_example_group_output("") + end + + it "should push passing spec name" do + formatter.example_passed(example_group.it("spec")) + io.string.should have_example_group_output("- spec\n") + end + + it "should push pending example name and message" do + formatter.example_pending('example_group', ExampleGroup.new("example"), 'reason') + io.string.should have_example_group_output("- example (PENDING: reason)\n") + end + + it "should dump pending" do + formatter.example_pending('example_group', ExampleGroup.new("example"), 'reason') + io.rewind + formatter.dump_pending + io.string.should =~ /Pending\:\nexample_group example \(reason\)\n/ + end + + def have_example_group_output(expected_output) + expected = "\nExampleGroup\n#{expected_output}" + ::Spec::Matchers::SimpleMatcher.new(expected) do |actual| + actual == expected + end + end + end + + describe "where ExampleGroup has two superclasses with a description" do + attr_reader :child_example_group, :grand_child_example_group + before do + @child_example_group = Class.new(example_group).describe("Child ExampleGroup") + @grand_child_example_group = Class.new(child_example_group).describe("GrandChild ExampleGroup") + formatter.add_example_group(grand_child_example_group) + end + + specify "when having an error, should push failing spec name and failure number" do + formatter.example_failed( + example_group.it("spec"), + 98, + Reporter::Failure.new("c s", RuntimeError.new) + ) + io.string.should have_nested_example_group_output("- spec (ERROR - 98)\n") + end + + specify "when having an expectation failure, should push failing spec name and failure number" do + formatter.example_failed( + example_group.it("spec"), + 98, + Reporter::Failure.new("c s", Spec::Expectations::ExpectationNotMetError.new) + ) + io.string.should have_nested_example_group_output("- spec (FAILED - 98)\n") + end + + def have_nested_example_group_output(expected_output) + expected_full_output = "\nExampleGroup Child ExampleGroup GrandChild ExampleGroup\n#{expected_output}" + ::Spec::Matchers::SimpleMatcher.new(expected_full_output) do |actual| + actual == expected_full_output + end + end + end + end + end + end +end diff --git a/vendor/gems/rspec/spec/spec/runner/formatter/story/html_formatter_spec.rb b/vendor/gems/rspec/spec/spec/runner/formatter/story/html_formatter_spec.rb new file mode 100644 index 0000000000..37fb7c6704 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/runner/formatter/story/html_formatter_spec.rb @@ -0,0 +1,61 @@ +require File.dirname(__FILE__) + '/../../../../spec_helper.rb' +require 'spec/runner/formatter/story/html_formatter' + +module Spec + module Runner + module Formatter + module Story + describe HtmlFormatter do + before :each do + @out = StringIO.new + @options = mock('options') + @reporter = HtmlFormatter.new(@options, @out) + end + + it "should just be poked at" do + @reporter.run_started(1) + @reporter.story_started('story_title', 'narrative') + + @reporter.scenario_started('story_title', 'succeeded_scenario_name') + @reporter.step_succeeded('given', 'succeded_step', 'one', 'two') + @reporter.scenario_succeeded('story_title', 'succeeded_scenario_name') + + @reporter.scenario_started('story_title', 'pending_scenario_name') + @reporter.step_pending('when', 'pending_step', 'un', 'deux') + @reporter.scenario_pending('story_title', 'pending_scenario_name', 'not done') + + @reporter.scenario_started('story_title', 'failed_scenario_name') + @reporter.step_failed('then', 'failed_step', 'en', 'to') + @reporter.scenario_failed('story_title', 'failed_scenario_name', NameError.new('sup')) + + @reporter.scenario_started('story_title', 'scenario_with_given_scenario_name') + @reporter.found_scenario('given scenario', 'succeeded_scenario_name') + + @reporter.story_ended('story_title', 'narrative') + @reporter.run_ended + end + + it "should create spans for params" do + @reporter.step_succeeded('given', 'a $coloured $animal', 'brown', 'dog') + @out.string.should == "
  • Given a brown dog
  • \n" + end + + it 'should create spanes for params in regexp steps' do + @reporter.step_succeeded :given, /a (pink|blue) (.*)/, 'brown', 'dog' + @out.string.should == "
  • Given a brown dog
  • \n" + end + + it "should create a ul for collected_steps" do + @reporter.collected_steps(['Given a $coloured $animal', 'Given a $n legged eel']) + @out.string.should == (<<-EOF) + +EOF + end + end + end + end + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/runner/formatter/story/plain_text_formatter_spec.rb b/vendor/gems/rspec/spec/spec/runner/formatter/story/plain_text_formatter_spec.rb new file mode 100644 index 0000000000..9632b06061 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/runner/formatter/story/plain_text_formatter_spec.rb @@ -0,0 +1,335 @@ +require File.dirname(__FILE__) + '/../../../../spec_helper.rb' +require 'spec/runner/formatter/story/plain_text_formatter' + +module Spec + module Runner + module Formatter + module Story + describe PlainTextFormatter do + before :each do + # given + @out = StringIO.new + @tweaker = mock('tweaker') + @tweaker.stub!(:tweak_backtrace) + @options = mock('options') + @options.stub!(:colour).and_return(false) + @options.stub!(:backtrace_tweaker).and_return(@tweaker) + @formatter = PlainTextFormatter.new(@options, @out) + end + + it 'should summarize the number of scenarios when the run ends' do + # when + @formatter.run_started(3) + @formatter.scenario_started(nil, nil) + @formatter.scenario_succeeded('story', 'scenario1') + @formatter.scenario_started(nil, nil) + @formatter.scenario_succeeded('story', 'scenario2') + @formatter.scenario_started(nil, nil) + @formatter.scenario_succeeded('story', 'scenario3') + @formatter.run_ended + + # then + @out.string.should include('3 scenarios') + end + + it 'should summarize the number of successful scenarios when the run ends' do + # when + @formatter.run_started(3) + @formatter.scenario_started(nil, nil) + @formatter.scenario_succeeded('story', 'scenario1') + @formatter.scenario_started(nil, nil) + @formatter.scenario_succeeded('story', 'scenario2') + @formatter.scenario_started(nil, nil) + @formatter.scenario_succeeded('story', 'scenario3') + @formatter.run_ended + + # then + @out.string.should include('3 scenarios: 3 succeeded') + end + + it 'should summarize the number of failed scenarios when the run ends' do + # when + @formatter.run_started(3) + @formatter.scenario_started(nil, nil) + @formatter.scenario_succeeded('story', 'scenario1') + @formatter.scenario_started(nil, nil) + @formatter.scenario_failed('story', 'scenario2', exception_from { raise RuntimeError, 'oops' }) + @formatter.scenario_started(nil, nil) + @formatter.scenario_failed('story', 'scenario3', exception_from { raise RuntimeError, 'oops' }) + @formatter.run_ended + + # then + @out.string.should include("3 scenarios: 1 succeeded, 2 failed") + end + + it 'should end cleanly (no characters on the last line) with successes' do + # when + @formatter.run_started(1) + @formatter.scenario_started(nil, nil) + @formatter.scenario_succeeded('story', 'scenario') + @formatter.run_ended + + # then + @out.string.should =~ /\n\z/ + end + + it 'should end cleanly (no characters on the last line) with failures' do + # when + @formatter.run_started(1) + @formatter.scenario_started(nil, nil) + @formatter.scenario_failed('story', 'scenario', exception_from { raise RuntimeError, 'oops' }) + @formatter.run_ended + + # then + @out.string.should =~ /\n\z/ + end + + it 'should end cleanly (no characters on the last line) with pending steps' do + # when + @formatter.run_started(1) + @formatter.scenario_started(nil, nil) + @formatter.step_pending(:then, 'do pend') + @formatter.scenario_pending('story', 'scenario', exception_from { raise RuntimeError, 'oops' }) + @formatter.run_ended + + # then + @out.string.should =~ /\n\z/ + end + + it 'should summarize the number of pending scenarios when the run ends' do + # when + @formatter.run_started(3) + @formatter.scenario_started(nil, nil) + @formatter.scenario_succeeded('story', 'scenario1') + @formatter.scenario_started(nil, nil) + @formatter.scenario_pending('story', 'scenario2', 'message') + @formatter.scenario_started(nil, nil) + @formatter.scenario_pending('story', 'scenario3', 'message') + @formatter.run_ended + + # then + @out.string.should include("3 scenarios: 1 succeeded, 0 failed, 2 pending") + end + + it "should only count the first failure in one scenario" do + # when + @formatter.run_started(3) + @formatter.scenario_started(nil, nil) + @formatter.scenario_succeeded('story', 'scenario1') + @formatter.scenario_started(nil, nil) + @formatter.scenario_failed('story', 'scenario2', exception_from { raise RuntimeError, 'oops' }) + @formatter.scenario_failed('story', 'scenario2', exception_from { raise RuntimeError, 'oops again' }) + @formatter.scenario_started(nil, nil) + @formatter.scenario_failed('story', 'scenario3', exception_from { raise RuntimeError, 'oops' }) + @formatter.run_ended + + # then + @out.string.should include("3 scenarios: 1 succeeded, 2 failed") + end + + it "should only count the first pending in one scenario" do + # when + @formatter.run_started(3) + @formatter.scenario_started(nil, nil) + @formatter.scenario_succeeded('story', 'scenario1') + @formatter.scenario_started(nil, nil) + @formatter.scenario_pending('story', 'scenario2', 'because ...') + @formatter.scenario_pending('story', 'scenario2', 'because ...') + @formatter.scenario_started(nil, nil) + @formatter.scenario_pending('story', 'scenario3', 'because ...') + @formatter.run_ended + + # then + @out.string.should include("3 scenarios: 1 succeeded, 0 failed, 2 pending") + end + + it "should only count a failure before the first pending in one scenario" do + # when + @formatter.run_started(3) + @formatter.scenario_started(nil, nil) + @formatter.scenario_succeeded('story', 'scenario1') + @formatter.scenario_started(nil, nil) + @formatter.scenario_pending('story', 'scenario2', exception_from { raise RuntimeError, 'oops' }) + @formatter.scenario_failed('story', 'scenario2', exception_from { raise RuntimeError, 'oops again' }) + @formatter.scenario_started(nil, nil) + @formatter.scenario_failed('story', 'scenario3', exception_from { raise RuntimeError, 'oops' }) + @formatter.run_ended + + # then + @out.string.should include("3 scenarios: 1 succeeded, 1 failed, 1 pending") + end + + it 'should produce details of the first failure each failed scenario when the run ends' do + # when + @formatter.run_started(3) + @formatter.scenario_started(nil, nil) + @formatter.scenario_succeeded('story', 'scenario1') + @formatter.scenario_started(nil, nil) + @formatter.scenario_failed('story', 'scenario2', exception_from { raise RuntimeError, 'oops2' }) + @formatter.scenario_failed('story', 'scenario2', exception_from { raise RuntimeError, 'oops2 - this one should not appear' }) + @formatter.scenario_started(nil, nil) + @formatter.scenario_failed('story', 'scenario3', exception_from { raise RuntimeError, 'oops3' }) + @formatter.run_ended + + # then + @out.string.should include("FAILURES:\n") + @out.string.should include("1) story (scenario2) FAILED") + @out.string.should include("RuntimeError: oops2") + @out.string.should_not include("RuntimeError: oops2 - this one should not appear") + @out.string.should include("2) story (scenario3) FAILED") + @out.string.should include("RuntimeError: oops3") + end + + it 'should produce details of each pending step when the run ends' do + # when + @formatter.run_started(2) + @formatter.story_started('story 1', 'narrative') + @formatter.scenario_started('story 1', 'scenario 1') + @formatter.step_pending(:given, 'todo 1', []) + @formatter.story_started('story 2', 'narrative') + @formatter.scenario_started('story 2', 'scenario 2') + @formatter.step_pending(:given, 'todo 2', []) + @formatter.run_ended + + # then + @out.string.should include("Pending Steps:\n") + @out.string.should include("1) story 1 (scenario 1): todo 1") + @out.string.should include("2) story 2 (scenario 2): todo 2") + end + + it 'should document a story title and narrative' do + # when + @formatter.story_started 'story', 'narrative' + + # then + @out.string.should include("Story: story\n\n narrative") + end + + it 'should document a scenario name' do + # when + @formatter.scenario_started 'story', 'scenario' + + # then + @out.string.should include("\n\n Scenario: scenario") + end + + it 'should document a step by sentence-casing its name' do + # when + @formatter.step_succeeded :given, 'a context' + @formatter.step_succeeded :when, 'an event' + @formatter.step_succeeded :then, 'an outcome' + + # then + @out.string.should include("\n\n Given a context\n\n When an event\n\n Then an outcome") + end + + it 'should document additional givens using And' do + # when + @formatter.step_succeeded :given, 'step 1' + @formatter.step_succeeded :given, 'step 2' + @formatter.step_succeeded :given, 'step 3' + + # then + @out.string.should include(" Given step 1\n And step 2\n And step 3") + end + + it 'should document additional events using And' do + # when + @formatter.step_succeeded :when, 'step 1' + @formatter.step_succeeded :when, 'step 2' + @formatter.step_succeeded :when, 'step 3' + + # then + @out.string.should include(" When step 1\n And step 2\n And step 3") + end + + it 'should document additional outcomes using And' do + # when + @formatter.step_succeeded :then, 'step 1' + @formatter.step_succeeded :then, 'step 2' + @formatter.step_succeeded :then, 'step 3' + + # then + @out.string.should include(" Then step 1\n And step 2\n And step 3") + end + + it 'should document a GivenScenario followed by a Given using And' do + # when + @formatter.step_succeeded :'given scenario', 'a scenario' + @formatter.step_succeeded :given, 'a context' + + # then + @out.string.should include(" Given scenario a scenario\n And a context") + end + + it 'should document steps with replaced params' do + @formatter.step_succeeded :given, 'a $coloured dog with $n legs', 'pink', 21 + @out.string.should include(" Given a pink dog with 21 legs") + end + + it 'should document regexp steps with replaced params' do + @formatter.step_succeeded :given, /a (pink|blue) dog with (.*) legs/, 'pink', 21 + @out.string.should include(" Given a pink dog with 21 legs") + end + + it "should append PENDING for the first pending step" do + @formatter.scenario_started('','') + @formatter.step_pending(:given, 'a context') + + @out.string.should include('Given a context (PENDING)') + end + + it "should append PENDING for pending after already pending" do + @formatter.scenario_started('','') + @formatter.step_pending(:given, 'a context') + @formatter.step_pending(:when, 'I say hey') + + @out.string.should include('When I say hey (PENDING)') + end + + it "should append FAILED for the first failiure" do + @formatter.scenario_started('','') + @formatter.step_failed(:given, 'a context') + + @out.string.should include('Given a context (FAILED)') + end + + it "should append SKIPPED for the second failiure" do + @formatter.scenario_started('','') + @formatter.step_failed(:given, 'a context') + @formatter.step_failed(:when, 'I say hey') + + @out.string.should include('When I say hey (SKIPPED)') + end + + it "should append SKIPPED for the a failiure after PENDING" do + @formatter.scenario_started('','') + @formatter.step_pending(:given, 'a context') + @formatter.step_failed(:when, 'I say hey') + + @out.string.should include('When I say hey (SKIPPED)') + end + + it 'should print some white space after each story' do + # when + @formatter.story_ended 'title', 'narrative' + + # then + @out.string.should include("\n\n") + end + + it "should print nothing for collected_steps" do + @formatter.collected_steps(['Given a $coloured $animal', 'Given a $n legged eel']) + @out.string.should == ("") + end + + it "should ignore messages it doesn't care about" do + lambda { + @formatter.this_method_does_not_exist + }.should_not raise_error + end + end + end + end + end +end diff --git a/vendor/gems/rspec/spec/spec/runner/formatter/text_mate_formatted-1.8.4.html b/vendor/gems/rspec/spec/spec/runner/formatter/text_mate_formatted-1.8.4.html new file mode 100644 index 0000000000..3f263747a8 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/runner/formatter/text_mate_formatted-1.8.4.html @@ -0,0 +1,365 @@ + + + + + RSpec results + + + + + + +
    + + + +
    +

    RSpec Results

    + +
    +

     

    +

     

    +
    +
    + +
    +
    +
    +
    Mocker
    + +
    should be able to call mock()
    + + + +
    + should fail when expected message not received +
    +
    Mock 'poke me' expected :poke with (any args) once, but received it 0 times
    + +
    11  it "should fail when expected message not received" do
    +12    mock = mock("poke me")
    +13    mock.should_receive(:poke)
    +14  end
    +15  
    +
    +
    + +
    + should fail when messages are received out of order +
    +
    Mock 'one two three' received :three out of order
    + +
    20    mock.should_receive(:three).ordered
    +21    mock.one
    +22    mock.three
    +23    mock.two
    +24  end
    +
    +
    + +
    + should get yelled at when sending unexpected messages +
    +
    Mock 'don't talk to me' expected :any_message_at_all with (any args) 0 times, but received it once
    + +
    26  it "should get yelled at when sending unexpected messages" do
    +27    mock = mock("don't talk to me")
    +28    mock.should_not_receive(:any_message_at_all)
    +29    mock.any_message_at_all
    +30  end
    +
    +
    + +
    + has a bug we need to fix +
    +
    Expected pending 'here is the bug' to fail. No Error was raised.
    + +
    31
    +32  it "has a bug we need to fix" do
    +33    pending "here is the bug" do
    +34      # Actually, no. It's fixed. This will fail because it passes :-)
    +35      mock = mock("Bug")
    +
    +
    +
    +
    +
    +
    +
    Running specs with --diff
    + + +
    + should print diff of different strings +
    +
    expected: "RSpec is a\nbehaviour driven development\nframework for Ruby\n",
    +     got: "RSpec is a\nbehavior driven development\nframework for Ruby\n" (using ==)
    +Diff:
    +@@ -1,4 +1,4 @@
    + RSpec is a
    +-behavior driven development
    ++behaviour driven development
    + framework for Ruby
    +
    + +
    11framework for Ruby
    +12EOF
    +13    usa.should == uk
    +14  end
    +
    +
    + +
    + should print diff of different objects' pretty representation +
    +
    expected <Animal
    +name=bob,
    +species=tortoise
    +>
    +, got <Animal
    +name=bob,
    +species=giraffe
    +>
    + (using .eql?)
    +Diff:
    +@@ -1,5 +1,5 @@
    + <Animal
    + name=bob,
    +-species=giraffe
    ++species=tortoise
    + >
    +
    + +
    32    expected = Animal.new "bob", "giraffe"
    +33    actual   = Animal.new "bob", "tortoise"
    +34    expected.should eql(actual)
    +35  end
    +36end
    +
    +
    +
    +
    +
    +
    +
    A consumer of a stub
    + +
    should be able to stub methods on any Object
    +
    +
    +
    +
    +
    A stubbed method on a class
    + +
    should return the stubbed value
    + +
    should revert to the original method after each spec
    + +
    can stub! and mock the same message
    +
    +
    +
    +
    +
    A mock
    + +
    can stub!
    + +
    can stub! and mock
    + +
    can stub! and mock the same message
    +
    +
    +
    +
    +
    pending example (using pending method)
    + + +
    should be reported as "PENDING: for some reason" (PENDING: for some reason)
    +
    +
    +
    +
    +
    pending example (with no block)
    + + +
    should be reported as "PENDING: Not Yet Implemented" (PENDING: Not Yet Implemented)
    +
    +
    +
    +
    +
    pending example (with block for pending)
    + + +
    should have a failing block, passed to pending, reported as "PENDING: for some reason" (PENDING: for some reason)
    +
    +
    + + +
    +
    + + diff --git a/vendor/gems/rspec/spec/spec/runner/formatter/text_mate_formatted-1.8.6.html b/vendor/gems/rspec/spec/spec/runner/formatter/text_mate_formatted-1.8.6.html new file mode 100644 index 0000000000..8a2b12e7de --- /dev/null +++ b/vendor/gems/rspec/spec/spec/runner/formatter/text_mate_formatted-1.8.6.html @@ -0,0 +1,365 @@ + + + + + RSpec results + + + + + + +
    + + + +
    +

    RSpec Results

    + +
    +

     

    +

     

    +
    +
    + +
    +
    +
    +
    Mocker
    + +
    should be able to call mock()
    + + + +
    + should fail when expected message not received +
    +
    Mock 'poke me' expected :poke with (any args) once, but received it 0 times
    +
    ./failing_examples/mocking_example.rb:13:
    +./spec/spec/runner/formatter/html_formatter_spec.rb:18:
    +./spec/spec/runner/formatter/html_formatter_spec.rb:14:in `chdir'
    +./spec/spec/runner/formatter/html_formatter_spec.rb:14:
    +
    11  it "should fail when expected message not received" do
    +12    mock = mock("poke me")
    +13    mock.should_receive(:poke)
    +14  end
    +15  
    +
    +
    + +
    + should fail when messages are received out of order +
    +
    Mock 'one two three' received :three out of order
    +
    ./failing_examples/mocking_example.rb:22:
    +./spec/spec/runner/formatter/html_formatter_spec.rb:18:
    +./spec/spec/runner/formatter/html_formatter_spec.rb:14:in `chdir'
    +./spec/spec/runner/formatter/html_formatter_spec.rb:14:
    +
    20    mock.should_receive(:three).ordered
    +21    mock.one
    +22    mock.three
    +23    mock.two
    +24  end
    +
    +
    + +
    + should get yelled at when sending unexpected messages +
    +
    Mock 'don't talk to me' expected :any_message_at_all with (any args) 0 times, but received it once
    +
    ./failing_examples/mocking_example.rb:28:
    +./spec/spec/runner/formatter/html_formatter_spec.rb:18:
    +./spec/spec/runner/formatter/html_formatter_spec.rb:14:in `chdir'
    +./spec/spec/runner/formatter/html_formatter_spec.rb:14:
    +
    26  it "should get yelled at when sending unexpected messages" do
    +27    mock = mock("don't talk to me")
    +28    mock.should_not_receive(:any_message_at_all)
    +29    mock.any_message_at_all
    +30  end
    +
    +
    + +
    + has a bug we need to fix +
    +
    Expected pending 'here is the bug' to fail. No Error was raised.
    + +
    31
    +32  it "has a bug we need to fix" do
    +33    pending "here is the bug" do
    +34      # Actually, no. It's fixed. This will fail because it passes :-)
    +35      mock = mock("Bug")
    +
    +
    +
    +
    +
    +
    +
    Running specs with --diff
    + + +
    + should print diff of different strings +
    +
    expected: "RSpec is a\nbehaviour driven development\nframework for Ruby\n",
    +     got: "RSpec is a\nbehavior driven development\nframework for Ruby\n" (using ==)
    +Diff:
    +@@ -1,4 +1,4 @@
    + RSpec is a
    +-behavior driven development
    ++behaviour driven development
    + framework for Ruby
    +
    + +
    11framework for Ruby
    +12EOF
    +13    usa.should == uk
    +14  end
    +
    +
    + +
    + should print diff of different objects' pretty representation +
    +
    expected <Animal
    +name=bob,
    +species=tortoise
    +>
    +, got <Animal
    +name=bob,
    +species=giraffe
    +>
    + (using .eql?)
    +Diff:
    +@@ -1,5 +1,5 @@
    + <Animal
    + name=bob,
    +-species=giraffe
    ++species=tortoise
    + >
    +
    +
    ./failing_examples/mocking_example.rb:33:
    +./spec/spec/runner/formatter/html_formatter_spec.rb:18:
    +./spec/spec/runner/formatter/html_formatter_spec.rb:14:in `chdir'
    +./spec/spec/runner/formatter/html_formatter_spec.rb:14:
    +
    32    expected = Animal.new "bob", "giraffe"
    +33    actual   = Animal.new "bob", "tortoise"
    +34    expected.should eql(actual)
    +35  end
    +36end
    +
    +
    +
    +
    +
    +
    +
    A consumer of a stub
    + +
    should be able to stub methods on any Object
    +
    +
    +
    +
    +
    A stubbed method on a class
    + +
    should return the stubbed value
    + +
    should revert to the original method after each spec
    + +
    can stub! and mock the same message
    +
    +
    +
    +
    +
    A mock
    + +
    can stub!
    + +
    can stub! and mock
    + +
    can stub! and mock the same message
    +
    +
    +
    +
    +
    pending example (using pending method)
    + + +
    should be reported as "PENDING: for some reason" (PENDING: for some reason)
    +
    +
    +
    +
    +
    pending example (with no block)
    + + +
    should be reported as "PENDING: Not Yet Implemented" (PENDING: Not Yet Implemented)
    +
    +
    +
    +
    +
    pending example (with block for pending)
    + + +
    should have a failing block, passed to pending, reported as "PENDING: for some reason" (PENDING: for some reason)
    +
    +
    + + +
    +
    + + diff --git a/vendor/gems/rspec/spec/spec/runner/heckle_runner_spec.rb b/vendor/gems/rspec/spec/spec/runner/heckle_runner_spec.rb new file mode 100644 index 0000000000..539d908c2d --- /dev/null +++ b/vendor/gems/rspec/spec/spec/runner/heckle_runner_spec.rb @@ -0,0 +1,78 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' +unless [/mswin/, /java/].detect{|p| p =~ RUBY_PLATFORM} + require 'spec/runner/heckle_runner' + + module Foo + class Bar + def one; end + def two; end + end + + class Zap + def three; end + def four; end + end + end + + describe "HeckleRunner" do + before(:each) do + @heckle = mock("heckle", :null_object => true) + @heckle_class = mock("heckle_class") + end + + it "should heckle all methods in all classes in a module" do + @heckle_class.should_receive(:new).with("Foo::Bar", "one", rspec_options).and_return(@heckle) + @heckle_class.should_receive(:new).with("Foo::Bar", "two", rspec_options).and_return(@heckle) + @heckle_class.should_receive(:new).with("Foo::Zap", "three", rspec_options).and_return(@heckle) + @heckle_class.should_receive(:new).with("Foo::Zap", "four", rspec_options).and_return(@heckle) + + heckle_runner = Spec::Runner::HeckleRunner.new("Foo", @heckle_class) + heckle_runner.heckle_with + end + + it "should heckle all methods in a class" do + @heckle_class.should_receive(:new).with("Foo::Bar", "one", rspec_options).and_return(@heckle) + @heckle_class.should_receive(:new).with("Foo::Bar", "two", rspec_options).and_return(@heckle) + + heckle_runner = Spec::Runner::HeckleRunner.new("Foo::Bar", @heckle_class) + heckle_runner.heckle_with + end + + it "should fail heckling when the class is not found" do + lambda do + heckle_runner = Spec::Runner::HeckleRunner.new("Foo::Bob", @heckle_class) + heckle_runner.heckle_with + end.should raise_error(StandardError, "Heckling failed - \"Foo::Bob\" is not a known class or module") + end + + it "should heckle specific method in a class (with #)" do + @heckle_class.should_receive(:new).with("Foo::Bar", "two", rspec_options).and_return(@heckle) + + heckle_runner = Spec::Runner::HeckleRunner.new("Foo::Bar#two", @heckle_class) + heckle_runner.heckle_with + end + + it "should heckle specific method in a class (with .)" do + @heckle_class.should_receive(:new).with("Foo::Bar", "two", rspec_options).and_return(@heckle) + + heckle_runner = Spec::Runner::HeckleRunner.new("Foo::Bar.two", @heckle_class) + heckle_runner.heckle_with + end + end + + describe "Heckler" do + it "should say yes to tests_pass? if specs pass" do + options = mock("options", :null_object => true) + options.should_receive(:run_examples).and_return(true) + heckler = Spec::Runner::Heckler.new("Foo", nil, options) + heckler.tests_pass?.should be_true + end + + it "should say no to tests_pass? if specs fail" do + options = mock("options", :null_object => true) + options.should_receive(:run_examples).and_return(false) + heckler = Spec::Runner::Heckler.new("Foo", nil, options) + heckler.tests_pass?.should be_false + end + end +end diff --git a/vendor/gems/rspec/spec/spec/runner/heckler_spec.rb b/vendor/gems/rspec/spec/spec/runner/heckler_spec.rb new file mode 100644 index 0000000000..7cf6606ec6 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/runner/heckler_spec.rb @@ -0,0 +1,13 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' +unless [/mswin/, /java/].detect{|p| p =~ RUBY_PLATFORM} + require 'spec/runner/heckle_runner' + + describe "Heckler" do + it "should run examples on tests_pass?" do + options = Spec::Runner::Options.new(StringIO.new, StringIO.new) + options.should_receive(:run_examples).with().and_return(&options.method(:run_examples)) + heckler = Spec::Runner::Heckler.new('Array', 'push', options) + heckler.tests_pass? + end + end +end diff --git a/vendor/gems/rspec/spec/spec/runner/noisy_backtrace_tweaker_spec.rb b/vendor/gems/rspec/spec/spec/runner/noisy_backtrace_tweaker_spec.rb new file mode 100644 index 0000000000..e097f2ec0d --- /dev/null +++ b/vendor/gems/rspec/spec/spec/runner/noisy_backtrace_tweaker_spec.rb @@ -0,0 +1,45 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +module Spec + module Runner + describe "NoisyBacktraceTweaker" do + before(:each) do + @error = RuntimeError.new + @tweaker = NoisyBacktraceTweaker.new + end + + it "should leave anything in lib spec dir" do + ["expectations", "mocks", "runner", "stubs"].each do |child| + @error.set_backtrace(["/lib/spec/#{child}/anything.rb"]) + @tweaker.tweak_backtrace(@error) + @error.backtrace.should_not be_empty + end + end + + it "should leave anything in spec dir" do + @error.set_backtrace(["/lib/spec/expectations/anything.rb"]) + @tweaker.tweak_backtrace(@error) + @error.backtrace.should_not be_empty + end + + it "should leave bin spec" do + @error.set_backtrace(["bin/spec:"]) + @tweaker.tweak_backtrace(@error) + @error.backtrace.should_not be_empty + end + + it "should not barf on nil backtrace" do + lambda do + @tweaker.tweak_backtrace(@error) + end.should_not raise_error + end + + it "should clean up double slashes" do + @error.set_backtrace(["/a//b/c//d.rb"]) + @tweaker.tweak_backtrace(@error) + @error.backtrace.should include("/a/b/c/d.rb") + end + + end + end +end diff --git a/vendor/gems/rspec/spec/spec/runner/option_parser_spec.rb b/vendor/gems/rspec/spec/spec/runner/option_parser_spec.rb new file mode 100644 index 0000000000..71619b8fc1 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/runner/option_parser_spec.rb @@ -0,0 +1,378 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' +require 'fileutils' + +describe "OptionParser" do + before(:each) do + @out = StringIO.new + @err = StringIO.new + @parser = Spec::Runner::OptionParser.new(@err, @out) + end + + def parse(args) + @parser.parse(args) + @parser.options + end + + it "should accept files to include" do + options = parse(["--pattern", "foo"]) + options.filename_pattern.should == "foo" + end + + it "should accept dry run option" do + options = parse(["--dry-run"]) + options.dry_run.should be_true + end + + it "should eval and use custom formatter when none of the builtins" do + options = parse(["--format", "Custom::Formatter"]) + options.formatters[0].class.should be(Custom::Formatter) + end + + it "should support formatters with relative and absolute paths, even on windows" do + options = parse([ + "--format", "Custom::Formatter:C:\\foo\\bar", + "--format", "Custom::Formatter:foo/bar", + "--format", "Custom::Formatter:foo\\bar", + "--format", "Custom::Formatter:/foo/bar" + ]) + options.formatters[0].where.should eql("C:\\foo\\bar") + options.formatters[1].where.should eql("foo/bar") + options.formatters[2].where.should eql("foo\\bar") + options.formatters[3].where.should eql("/foo/bar") + end + + it "should not be verbose by default" do + options = parse([]) + options.verbose.should be_nil + end + + it "should not use colour by default" do + options = parse([]) + options.colour.should == false + end + + it "should print help to stdout if no args" do + pending 'A regression since 1.0.8' do + options = parse([]) + @out.rewind + @out.read.should match(/Usage: spec \(FILE\|DIRECTORY\|GLOB\)\+ \[options\]/m) + end + end + + it "should print help to stdout" do + options = parse(["--help"]) + @out.rewind + @out.read.should match(/Usage: spec \(FILE\|DIRECTORY\|GLOB\)\+ \[options\]/m) + end + + it "should print instructions about how to require missing formatter" do + lambda do + options = parse(["--format", "Custom::MissingFormatter"]) + options.formatters + end.should raise_error(NameError) + @err.string.should match(/Couldn't find formatter class Custom::MissingFormatter/n) + end + + it "should print version to stdout" do + options = parse(["--version"]) + @out.rewind + @out.read.should match(/RSpec-\d+\.\d+\.\d+.*\(build \d+\) - BDD for Ruby\nhttp:\/\/rspec.rubyforge.org\/\n/n) + end + + it "should require file when require specified" do + lambda do + parse(["--require", "whatever"]) + end.should raise_error(LoadError) + end + + it "should support c option" do + options = parse(["-c"]) + options.colour.should be_true + end + + it "should support queens colour option" do + options = parse(["--colour"]) + options.colour.should be_true + end + + it "should support us color option" do + options = parse(["--color"]) + options.colour.should be_true + end + + it "should support single example with -e option" do + options = parse(["-e", "something or other"]) + options.examples.should eql(["something or other"]) + end + + it "should support single example with -s option (will be removed when autotest supports -e)" do + options = parse(["-s", "something or other"]) + options.examples.should eql(["something or other"]) + end + + it "should support single example with --example option" do + options = parse(["--example", "something or other"]) + options.examples.should eql(["something or other"]) + end + + it "should read several example names from file if --example is given an existing file name" do + options = parse(["--example", File.dirname(__FILE__) + '/examples.txt']) + options.examples.should eql([ + "Sir, if you were my husband, I would poison your drink.", + "Madam, if you were my wife, I would drink it."]) + end + + it "should read no examples if given an empty file" do + options = parse(["--example", File.dirname(__FILE__) + '/empty_file.txt']) + options.examples.should eql([]) + end + + it "should use html formatter when format is h" do + options = parse(["--format", "h"]) + options.formatters[0].class.should equal(Spec::Runner::Formatter::HtmlFormatter) + end + + it "should use html story formatter when format is h" do + options = parse(["--format", "h"]) + options.story_formatters[0].class.should equal(Spec::Runner::Formatter::Story::HtmlFormatter) + end + + it "should use html formatter when format is html" do + options = parse(["--format", "html"]) + options.formatters[0].class.should equal(Spec::Runner::Formatter::HtmlFormatter) + end + + it "should use html story formatter when format is html" do + options = parse(["--format", "html"]) + options.story_formatters[0].class.should equal(Spec::Runner::Formatter::Story::HtmlFormatter) + end + + it "should use html formatter with explicit output when format is html:test.html" do + FileUtils.rm 'test.html' if File.exist?('test.html') + options = parse(["--format", "html:test.html"]) + options.formatters # creates the file + File.should be_exist('test.html') + options.formatters[0].class.should equal(Spec::Runner::Formatter::HtmlFormatter) + options.formatters[0].close + FileUtils.rm 'test.html' + end + + it "should use noisy backtrace tweaker with b option" do + options = parse(["-b"]) + options.backtrace_tweaker.should be_instance_of(Spec::Runner::NoisyBacktraceTweaker) + end + + it "should use noisy backtrace tweaker with backtrace option" do + options = parse(["--backtrace"]) + options.backtrace_tweaker.should be_instance_of(Spec::Runner::NoisyBacktraceTweaker) + end + + it "should use quiet backtrace tweaker by default" do + options = parse([]) + options.backtrace_tweaker.should be_instance_of(Spec::Runner::QuietBacktraceTweaker) + end + + it "should use progress bar formatter by default" do + options = parse([]) + options.formatters[0].class.should equal(Spec::Runner::Formatter::ProgressBarFormatter) + end + + it "should use specdoc formatter when format is s" do + options = parse(["--format", "s"]) + options.formatters[0].class.should equal(Spec::Runner::Formatter::SpecdocFormatter) + end + + it "should use specdoc formatter when format is specdoc" do + options = parse(["--format", "specdoc"]) + options.formatters[0].class.should equal(Spec::Runner::Formatter::SpecdocFormatter) + end + + it "should support diff option when format is not specified" do + options = parse(["--diff"]) + options.diff_format.should == :unified + end + + it "should use unified diff format option when format is unified" do + options = parse(["--diff", "unified"]) + options.diff_format.should == :unified + options.differ_class.should equal(Spec::Expectations::Differs::Default) + end + + it "should use context diff format option when format is context" do + options = parse(["--diff", "context"]) + options.diff_format.should == :context + options.differ_class.should == Spec::Expectations::Differs::Default + end + + it "should use custom diff format option when format is a custom format" do + Spec::Expectations.differ.should_not be_instance_of(Custom::Differ) + + options = parse(["--diff", "Custom::Differ"]) + options.parse_diff "Custom::Differ" + options.diff_format.should == :custom + options.differ_class.should == Custom::Differ + Spec::Expectations.differ.should be_instance_of(Custom::Differ) + end + + it "should print instructions about how to fix missing differ" do + lambda { parse(["--diff", "Custom::MissingFormatter"]) }.should raise_error(NameError) + @err.string.should match(/Couldn't find differ class Custom::MissingFormatter/n) + end + + describe "when attempting a focussed spec" do + attr_reader :file, :dir + before do + @original_rspec_options = $rspec_options + @file = "#{File.dirname(__FILE__)}/spec_parser/spec_parser_fixture.rb" + @dir = File.dirname(file) + end + + after do + $rspec_options = @original_rspec_options + end + + def parse(args) + options = super + $rspec_options = options + options.filename_pattern = "*_fixture.rb" + options + end + + it "should support --line to identify spec" do + options = parse([file, "--line", "13"]) + options.line_number.should == 13 + options.examples.should be_empty + options.run_examples + options.examples.should eql(["d"]) + end + + it "should fail with error message if file is dir along with --line" do + options = parse([dir, "--line", "169"]) + options.line_number.should == 169 + options.run_examples + @err.string.should match(/You must specify one file, not a directory when using the --line option/n) + end + + it "should fail with error message if file does not exist along with --line" do + options = parse(["some file", "--line", "169"]) + proc do + options.run_examples + end.should raise_error + end + + it "should fail with error message if more than one files are specified along with --line" do + options = parse([file, file, "--line", "169"]) + options.run_examples + @err.string.should match(/Only one file can be specified when using the --line option/n) + end + + it "should fail with error message if --example and --line are used simultaneously" do + options = parse([file, "--example", "some example", "--line", "169"]) + options.run_examples + @err.string.should match(/You cannot use both --line and --example/n) + end + end + + if [/mswin/, /java/].detect{|p| p =~ RUBY_PLATFORM} + it "should barf when --heckle is specified (and platform is windows)" do + lambda do + options = parse(["--heckle", "Spec"]) + end.should raise_error(StandardError, "Heckle not supported on Windows") + end + else + it "should heckle when --heckle is specified (and platform is not windows)" do + options = parse(["--heckle", "Spec"]) + options.heckle_runner.should be_instance_of(Spec::Runner::HeckleRunner) + end + end + + it "should read options from file when --options is specified" do + options = parse(["--options", File.dirname(__FILE__) + "/spec.opts"]) + options.diff_format.should_not be_nil + options.colour.should be_true + end + + it "should default the formatter to ProgressBarFormatter when using options file" do + options = parse(["--options", File.dirname(__FILE__) + "/spec.opts"]) + options.formatters.first.should be_instance_of(::Spec::Runner::Formatter::ProgressBarFormatter) + end + + it "should read spaced and multi-line options from file when --options is specified" do + options = parse(["--options", File.dirname(__FILE__) + "/spec_spaced.opts"]) + options.diff_format.should_not be_nil + options.colour.should be_true + options.formatters.first.should be_instance_of(::Spec::Runner::Formatter::SpecdocFormatter) + end + + it "should save config to file when --generate-options is specified" do + FileUtils.rm 'test.spec.opts' if File.exist?('test.spec.opts') + options = parse(["--colour", "--generate-options", "test.spec.opts", "--diff"]) + IO.read('test.spec.opts').should == "--colour\n--diff\n" + FileUtils.rm 'test.spec.opts' + end + + it "should save config to file when -G is specified" do + FileUtils.rm 'test.spec.opts' if File.exist?('test.spec.opts') + options = parse(["--colour", "-G", "test.spec.opts", "--diff"]) + IO.read('test.spec.opts').should == "--colour\n--diff\n" + FileUtils.rm 'test.spec.opts' + end + + it "when --drb is specified, calls DrbCommandLine all of the other ARGV arguments" do + options = Spec::Runner::OptionParser.parse([ + "some/spec.rb", "--diff", "--colour" + ], @err, @out) + Spec::Runner::DrbCommandLine.should_receive(:run).and_return do |options| + options.argv.should == ["some/spec.rb", "--diff", "--colour"] + end + parse(["some/spec.rb", "--diff", "--drb", "--colour"]) + end + + it "should reverse spec order when --reverse is specified" do + options = parse(["some/spec.rb", "--reverse"]) + end + + it "should set an mtime comparator when --loadby mtime" do + options = parse(["--loadby", 'mtime']) + runner = Spec::Runner::ExampleGroupRunner.new(options) + Spec::Runner::ExampleGroupRunner.should_receive(:new). + with(options). + and_return(runner) + runner.should_receive(:load_files).with(["most_recent_spec.rb", "command_line_spec.rb"]) + + Dir.chdir(File.dirname(__FILE__)) do + options.files << 'command_line_spec.rb' + options.files << 'most_recent_spec.rb' + FileUtils.touch "most_recent_spec.rb" + options.run_examples + FileUtils.rm "most_recent_spec.rb" + end + end + + it "should use the standard runner by default" do + runner = ::Spec::Runner::ExampleGroupRunner.new(@parser.options) + ::Spec::Runner::ExampleGroupRunner.should_receive(:new). + with(@parser.options). + and_return(runner) + options = parse([]) + options.run_examples + end + + it "should use a custom runner when given" do + runner = Custom::ExampleGroupRunner.new(@parser.options, nil) + Custom::ExampleGroupRunner.should_receive(:new). + with(@parser.options, nil). + and_return(runner) + options = parse(["--runner", "Custom::ExampleGroupRunner"]) + options.run_examples + end + + it "should use a custom runner with extra options" do + runner = Custom::ExampleGroupRunner.new(@parser.options, 'something') + Custom::ExampleGroupRunner.should_receive(:new). + with(@parser.options, 'something'). + and_return(runner) + options = parse(["--runner", "Custom::ExampleGroupRunner:something"]) + options.run_examples + end +end diff --git a/vendor/gems/rspec/spec/spec/runner/options_spec.rb b/vendor/gems/rspec/spec/spec/runner/options_spec.rb new file mode 100644 index 0000000000..6f0893751c --- /dev/null +++ b/vendor/gems/rspec/spec/spec/runner/options_spec.rb @@ -0,0 +1,364 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +module Spec + module Runner + describe Options do + before(:each) do + @err = StringIO.new('') + @out = StringIO.new('') + @options = Options.new(@err, @out) + end + + after(:each) do + Spec::Expectations.differ = nil + end + + describe "#examples" do + it "should default to empty array" do + @options.examples.should == [] + end + end + + describe "#include_pattern" do + it "should default to '**/*_spec.rb'" do + @options.filename_pattern.should == "**/*_spec.rb" + end + end + + describe "#files_to_load" do + + it "should load files not following pattern if named explicitly" do + file = File.expand_path(File.dirname(__FILE__) + "/resources/a_bar.rb") + @options.files << file + @options.files_to_load.should include(file) + end + + describe "with default --pattern" do + it "should load files named _spec.rb" do + dir = File.expand_path(File.dirname(__FILE__) + "/resources/") + @options.files << dir + @options.files_to_load.should == ["#{dir}/a_spec.rb"] + end + end + + describe "with explicit pattern (single)" do + before(:each) do + @options.filename_pattern = "**/*_foo.rb" + end + + it "should load files following pattern" do + file = File.expand_path(File.dirname(__FILE__) + "/resources/a_foo.rb") + @options.files << file + @options.files_to_load.should include(file) + end + + it "should load files in directories following pattern" do + dir = File.expand_path(File.dirname(__FILE__) + "/resources") + @options.files << dir + @options.files_to_load.should include("#{dir}/a_foo.rb") + end + + it "should not load files in directories not following pattern" do + dir = File.expand_path(File.dirname(__FILE__) + "/resources") + @options.files << dir + @options.files_to_load.should_not include("#{dir}/a_bar.rb") + end + end + + describe "with explicit pattern (comma,separated,values)" do + + before(:each) do + @options.filename_pattern = "**/*_foo.rb,**/*_bar.rb" + end + + it "should support comma separated values" do + dir = File.expand_path(File.dirname(__FILE__) + "/resources") + @options.files << dir + @options.files_to_load.should include("#{dir}/a_foo.rb") + @options.files_to_load.should include("#{dir}/a_bar.rb") + end + + it "should support comma separated values with spaces" do + dir = File.expand_path(File.dirname(__FILE__) + "/resources") + @options.files << dir + @options.files_to_load.should include("#{dir}/a_foo.rb") + @options.files_to_load.should include("#{dir}/a_bar.rb") + end + + end + + end + + describe "#backtrace_tweaker" do + it "should default to QuietBacktraceTweaker" do + @options.backtrace_tweaker.class.should == QuietBacktraceTweaker + end + end + + describe "#dry_run" do + it "should default to false" do + @options.dry_run.should == false + end + end + + describe "#context_lines" do + it "should default to 3" do + @options.context_lines.should == 3 + end + end + + describe "#parse_diff with nil" do + before(:each) do + @options.parse_diff nil + end + + it "should make diff_format unified" do + @options.diff_format.should == :unified + end + + it "should set Spec::Expectations.differ to be a default differ" do + Spec::Expectations.differ.class.should == + ::Spec::Expectations::Differs::Default + end + end + + describe "#parse_diff with 'unified'" do + before(:each) do + @options.parse_diff 'unified' + end + + it "should make diff_format unified and uses default differ_class" do + @options.diff_format.should == :unified + @options.differ_class.should equal(Spec::Expectations::Differs::Default) + end + + it "should set Spec::Expectations.differ to be a default differ" do + Spec::Expectations.differ.class.should == + ::Spec::Expectations::Differs::Default + end + end + + describe "#parse_diff with 'context'" do + before(:each) do + @options.parse_diff 'context' + end + + it "should make diff_format context and uses default differ_class" do + @options.diff_format.should == :context + @options.differ_class.should == Spec::Expectations::Differs::Default + end + + it "should set Spec::Expectations.differ to be a default differ" do + Spec::Expectations.differ.class.should == + ::Spec::Expectations::Differs::Default + end + end + + describe "#parse_diff with Custom::Differ" do + before(:each) do + @options.parse_diff 'Custom::Differ' + end + + it "should use custom differ_class" do + @options.diff_format.should == :custom + @options.differ_class.should == Custom::Differ + Spec::Expectations.differ.should be_instance_of(Custom::Differ) + end + + it "should set Spec::Expectations.differ to be a default differ" do + Spec::Expectations.differ.class.should == + ::Custom::Differ + end + end + + describe "#parse_diff with missing class name" do + it "should raise error" do + lambda { @options.parse_diff "Custom::MissingDiffer" }.should raise_error(NameError) + @err.string.should match(/Couldn't find differ class Custom::MissingDiffer/n) + end + end + + describe "#parse_example" do + it "with argument thats not a file path, sets argument as the example" do + example = "something or other" + File.file?(example).should == false + @options.parse_example example + @options.examples.should eql(["something or other"]) + end + + it "with argument that is a file path, sets examples to contents of the file" do + example = "#{File.dirname(__FILE__)}/examples.txt" + File.should_receive(:file?).with(example).and_return(true) + file = StringIO.new("Sir, if you were my husband, I would poison your drink.\nMadam, if you were my wife, I would drink it.") + File.should_receive(:open).with(example).and_return(file) + + @options.parse_example example + @options.examples.should eql([ + "Sir, if you were my husband, I would poison your drink.", + "Madam, if you were my wife, I would drink it." + ]) + end + end + + describe "#examples_should_not_be_run" do + it "should cause #run_examples to return true and do nothing" do + @options.examples_should_not_be_run + ExampleGroupRunner.should_not_receive(:new) + + @options.run_examples.should be_true + end + end + + describe "#load_class" do + it "should raise error when not class name" do + lambda do + @options.send(:load_class, 'foo', 'fruit', '--food') + end.should raise_error('"foo" is not a valid class name') + end + end + + describe "#reporter" do + it "returns a Reporter" do + @options.reporter.should be_instance_of(Reporter) + @options.reporter.options.should === @options + end + end + + describe "#add_example_group affecting passed in example_group" do + it "runs all examples when options.examples is nil" do + example_1_has_run = false + example_2_has_run = false + @example_group = Class.new(::Spec::Example::ExampleGroup).describe("Some Examples") do + it "runs 1" do + example_1_has_run = true + end + it "runs 2" do + example_2_has_run = true + end + end + + @options.examples = nil + + @options.add_example_group @example_group + @options.run_examples + example_1_has_run.should be_true + example_2_has_run.should be_true + end + + it "keeps all example_definitions when options.examples is empty" do + example_1_has_run = false + example_2_has_run = false + @example_group = Class.new(::Spec::Example::ExampleGroup).describe("Some Examples") do + it "runs 1" do + example_1_has_run = true + end + it "runs 2" do + example_2_has_run = true + end + end + + @options.examples = [] + + @options.add_example_group @example_group + @options.run_examples + example_1_has_run.should be_true + example_2_has_run.should be_true + end + end + + describe "#add_example_group affecting example_group" do + it "adds example_group when example_group has example_definitions and is not shared" do + @example_group = Class.new(::Spec::Example::ExampleGroup).describe("Some Examples") do + it "uses this example_group" do + end + end + + @options.number_of_examples.should == 0 + @options.add_example_group @example_group + @options.number_of_examples.should == 1 + @options.example_groups.length.should == 1 + end + end + + describe "#remove_example_group" do + it "should remove the ExampleGroup from the list of ExampleGroups" do + @example_group = Class.new(::Spec::Example::ExampleGroup).describe("Some Examples") do + end + @options.add_example_group @example_group + @options.example_groups.should include(@example_group) + + @options.remove_example_group @example_group + @options.example_groups.should_not include(@example_group) + end + end + + describe "#run_examples" do + it "should use the standard runner by default" do + runner = ::Spec::Runner::ExampleGroupRunner.new(@options) + ::Spec::Runner::ExampleGroupRunner.should_receive(:new). + with(@options). + and_return(runner) + @options.user_input_for_runner = nil + + @options.run_examples + end + + it "should use a custom runner when given" do + runner = Custom::ExampleGroupRunner.new(@options, nil) + Custom::ExampleGroupRunner.should_receive(:new). + with(@options, nil). + and_return(runner) + @options.user_input_for_runner = "Custom::ExampleGroupRunner" + + @options.run_examples + end + + it "should use a custom runner with extra options" do + runner = Custom::ExampleGroupRunner.new(@options, 'something') + Custom::ExampleGroupRunner.should_receive(:new). + with(@options, 'something'). + and_return(runner) + @options.user_input_for_runner = "Custom::ExampleGroupRunner:something" + + @options.run_examples + end + + describe "when there are examples" do + before(:each) do + @options.add_example_group Class.new(::Spec::Example::ExampleGroup) + @options.formatters << Formatter::BaseTextFormatter.new(@options, @out) + end + + it "runs the Examples and outputs the result" do + @options.run_examples + @out.string.should include("0 examples, 0 failures") + end + + it "sets #examples_run? to true" do + @options.examples_run?.should be_false + @options.run_examples + @options.examples_run?.should be_true + end + end + + describe "when there are no examples" do + before(:each) do + @options.formatters << Formatter::BaseTextFormatter.new(@options, @out) + end + + it "does not run Examples and does not output a result" do + @options.run_examples + @out.string.should_not include("examples") + @out.string.should_not include("failures") + end + + it "sets #examples_run? to false" do + @options.examples_run?.should be_false + @options.run_examples + @options.examples_run?.should be_false + end + end + end + end + end +end diff --git a/vendor/gems/rspec/spec/spec/runner/output_one_time_fixture.rb b/vendor/gems/rspec/spec/spec/runner/output_one_time_fixture.rb new file mode 100644 index 0000000000..444730dc3e --- /dev/null +++ b/vendor/gems/rspec/spec/spec/runner/output_one_time_fixture.rb @@ -0,0 +1,7 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +describe "Running an Example" do + it "should not output twice" do + true.should be_true + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/runner/output_one_time_fixture_runner.rb b/vendor/gems/rspec/spec/spec/runner/output_one_time_fixture_runner.rb new file mode 100644 index 0000000000..a0e61316ee --- /dev/null +++ b/vendor/gems/rspec/spec/spec/runner/output_one_time_fixture_runner.rb @@ -0,0 +1,8 @@ +dir = File.dirname(__FILE__) +require "#{dir}/../../spec_helper" + +triggering_double_output = rspec_options +options = Spec::Runner::OptionParser.parse( + ["#{dir}/output_one_time_fixture.rb"], $stderr, $stdout +) +Spec::Runner::CommandLine.run(options) diff --git a/vendor/gems/rspec/spec/spec/runner/output_one_time_spec.rb b/vendor/gems/rspec/spec/spec/runner/output_one_time_spec.rb new file mode 100644 index 0000000000..8f67a380aa --- /dev/null +++ b/vendor/gems/rspec/spec/spec/runner/output_one_time_spec.rb @@ -0,0 +1,16 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +module Spec + module Runner + describe CommandLine do + it "should not output twice" do + dir = File.dirname(__FILE__) + Dir.chdir("#{dir}/../../..") do + output =`ruby #{dir}/output_one_time_fixture_runner.rb` + output.should include("1 example, 0 failures") + output.should_not include("0 examples, 0 failures") + end + end + end + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/runner/quiet_backtrace_tweaker_spec.rb b/vendor/gems/rspec/spec/spec/runner/quiet_backtrace_tweaker_spec.rb new file mode 100644 index 0000000000..e47b6c735e --- /dev/null +++ b/vendor/gems/rspec/spec/spec/runner/quiet_backtrace_tweaker_spec.rb @@ -0,0 +1,56 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +module Spec + module Runner + describe "QuietBacktraceTweaker" do + before(:each) do + @error = RuntimeError.new + @tweaker = QuietBacktraceTweaker.new + end + + it "should not barf on nil backtrace" do + lambda do + @tweaker.tweak_backtrace(@error) + end.should_not raise_error + end + + it "should remove anything from textmate ruby bundle" do + @error.set_backtrace(["/Applications/TextMate.app/Contents/SharedSupport/Bundles/Ruby.tmbundle/Support/tmruby.rb:147"]) + @tweaker.tweak_backtrace(@error) + @error.backtrace.should be_empty + end + + it "should remove anything in lib spec dir" do + ["expectations", "mocks", "runner"].each do |child| + element="/lib/spec/#{child}/anything.rb" + @error.set_backtrace([element]) + @tweaker.tweak_backtrace(@error) + unless (@error.backtrace.empty?) + raise("Should have tweaked away '#{element}'") + end + end + end + + it "should remove mock_frameworks/rspec" do + element = "mock_frameworks/rspec" + @error.set_backtrace([element]) + @tweaker.tweak_backtrace(@error) + unless (@error.backtrace.empty?) + raise("Should have tweaked away '#{element}'") + end + end + + it "should remove bin spec" do + @error.set_backtrace(["bin/spec:"]) + @tweaker.tweak_backtrace(@error) + @error.backtrace.should be_empty + end + + it "should clean up double slashes" do + @error.set_backtrace(["/a//b/c//d.rb"]) + @tweaker.tweak_backtrace(@error) + @error.backtrace.should include("/a/b/c/d.rb") + end + end + end +end diff --git a/vendor/gems/rspec/spec/spec/runner/reporter_spec.rb b/vendor/gems/rspec/spec/spec/runner/reporter_spec.rb new file mode 100644 index 0000000000..52377e7f30 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/runner/reporter_spec.rb @@ -0,0 +1,189 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +module Spec + module Runner + describe Reporter do + attr_reader :formatter_output, :options, :backtrace_tweaker, :formatter, :reporter, :example_group + before(:each) do + @formatter_output = StringIO.new + @options = Options.new(StringIO.new, StringIO.new) + @backtrace_tweaker = stub("backtrace tweaker", :tweak_backtrace => nil) + options.backtrace_tweaker = backtrace_tweaker + @formatter = ::Spec::Runner::Formatter::BaseTextFormatter.new(options, formatter_output) + options.formatters << formatter + @reporter = Reporter.new(options) + @example_group = create_example_group("example_group") + reporter.add_example_group example_group + end + + def failure + Mocks::DuckTypeArgConstraint.new(:header, :exception) + end + + def create_example_group(description_text) + example_group = Class.new(Spec::Example::ExampleGroup) + example_group.describe description_text + example_group + end + + it "should assign itself as the reporter to options" do + options.reporter.should equal(@reporter) + end + + it "should tell formatter when example_group is added" do + formatter.should_receive(:add_example_group).with(example_group) + reporter.add_example_group(example_group) + end + + it "should handle multiple example_groups with same name" do + formatter.should_receive(:add_example_group).exactly(3).times + formatter.should_receive(:example_started).exactly(3).times + formatter.should_receive(:example_passed).exactly(3).times + formatter.should_receive(:start_dump) + formatter.should_receive(:dump_pending) + formatter.should_receive(:close).with(no_args) + formatter.should_receive(:dump_summary).with(anything(), 3, 0, 0) + reporter.add_example_group(create_example_group("example_group")) + reporter.example_started("spec 1") + reporter.example_finished("spec 1") + reporter.add_example_group(create_example_group("example_group")) + reporter.example_started("spec 2") + reporter.example_finished("spec 2") + reporter.add_example_group(create_example_group("example_group")) + reporter.example_started("spec 3") + reporter.example_finished("spec 3") + reporter.dump + end + + it "should handle multiple examples with the same name" do + error=RuntimeError.new + passing = ExampleGroup.new("example") + failing = ExampleGroup.new("example") + + formatter.should_receive(:add_example_group).exactly(2).times + formatter.should_receive(:example_passed).with(passing).exactly(2).times + formatter.should_receive(:example_failed).with(failing, 1, failure) + formatter.should_receive(:example_failed).with(failing, 2, failure) + formatter.should_receive(:dump_failure).exactly(2).times + formatter.should_receive(:start_dump) + formatter.should_receive(:dump_pending) + formatter.should_receive(:close).with(no_args) + formatter.should_receive(:dump_summary).with(anything(), 4, 2, 0) + backtrace_tweaker.should_receive(:tweak_backtrace).twice + + reporter.add_example_group(create_example_group("example_group")) + reporter.example_finished(passing) + reporter.example_finished(failing, error) + + reporter.add_example_group(create_example_group("example_group")) + reporter.example_finished(passing) + reporter.example_finished(failing, error) + reporter.dump + end + + it "should push stats to formatter even with no data" do + formatter.should_receive(:start_dump) + formatter.should_receive(:dump_pending) + formatter.should_receive(:dump_summary).with(anything(), 0, 0, 0) + formatter.should_receive(:close).with(no_args) + reporter.dump + end + + it "should push time to formatter" do + formatter.should_receive(:start).with(5) + formatter.should_receive(:start_dump) + formatter.should_receive(:dump_pending) + formatter.should_receive(:close).with(no_args) + formatter.should_receive(:dump_summary) do |time, a, b| + time.to_s.should match(/[0-9].[0-9|e|-]+/) + end + reporter.start(5) + reporter.end + reporter.dump + end + + describe Reporter, "reporting one passing example" do + it "should tell formatter example passed" do + formatter.should_receive(:example_passed) + reporter.example_finished("example") + end + + it "should not delegate to backtrace tweaker" do + formatter.should_receive(:example_passed) + backtrace_tweaker.should_not_receive(:tweak_backtrace) + reporter.example_finished("example") + end + + it "should account for passing example in stats" do + formatter.should_receive(:example_passed) + formatter.should_receive(:start_dump) + formatter.should_receive(:dump_pending) + formatter.should_receive(:dump_summary).with(anything(), 1, 0, 0) + formatter.should_receive(:close).with(no_args) + reporter.example_finished("example") + reporter.dump + end + end + + describe Reporter, "reporting one failing example" do + it "should tell formatter that example failed" do + formatter.should_receive(:example_failed) + reporter.example_finished(example_group, RuntimeError.new) + end + + it "should delegate to backtrace tweaker" do + formatter.should_receive(:example_failed) + backtrace_tweaker.should_receive(:tweak_backtrace) + reporter.example_finished(ExampleGroup.new("example"), RuntimeError.new) + end + + it "should account for failing example in stats" do + example = ExampleGroup.new("example") + formatter.should_receive(:example_failed).with(example, 1, failure) + formatter.should_receive(:start_dump) + formatter.should_receive(:dump_pending) + formatter.should_receive(:dump_failure).with(1, anything()) + formatter.should_receive(:dump_summary).with(anything(), 1, 1, 0) + formatter.should_receive(:close).with(no_args) + reporter.example_finished(example, RuntimeError.new) + reporter.dump + end + + end + + describe Reporter, "reporting one pending example (ExamplePendingError)" do + it "should tell formatter example is pending" do + example = ExampleGroup.new("example") + formatter.should_receive(:example_pending).with(example_group.description, example, "reason") + formatter.should_receive(:add_example_group).with(example_group) + reporter.add_example_group(example_group) + reporter.example_finished(example, Spec::Example::ExamplePendingError.new("reason")) + end + + it "should account for pending example in stats" do + example = ExampleGroup.new("example") + formatter.should_receive(:example_pending).with(example_group.description, example, "reason") + formatter.should_receive(:start_dump) + formatter.should_receive(:dump_pending) + formatter.should_receive(:dump_summary).with(anything(), 1, 0, 1) + formatter.should_receive(:close).with(no_args) + formatter.should_receive(:add_example_group).with(example_group) + reporter.add_example_group(example_group) + reporter.example_finished(example, Spec::Example::ExamplePendingError.new("reason")) + reporter.dump + end + end + + describe Reporter, "reporting one pending example (PendingExampleFixedError)" do + it "should tell formatter pending example is fixed" do + formatter.should_receive(:example_failed) do |name, counter, failure| + failure.header.should == "'example_group example' FIXED" + end + formatter.should_receive(:add_example_group).with(example_group) + reporter.add_example_group(example_group) + reporter.example_finished(ExampleGroup.new("example"), Spec::Example::PendingExampleFixedError.new("reason")) + end + end + end + end +end diff --git a/vendor/gems/rspec/spec/spec/runner/resources/a_bar.rb b/vendor/gems/rspec/spec/spec/runner/resources/a_bar.rb new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vendor/gems/rspec/spec/spec/runner/resources/a_foo.rb b/vendor/gems/rspec/spec/spec/runner/resources/a_foo.rb new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vendor/gems/rspec/spec/spec/runner/resources/a_spec.rb b/vendor/gems/rspec/spec/spec/runner/resources/a_spec.rb new file mode 100644 index 0000000000..d9b67cc767 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/runner/resources/a_spec.rb @@ -0,0 +1 @@ +# Empty - used by ../options_spec.rb \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/runner/spec.opts b/vendor/gems/rspec/spec/spec/runner/spec.opts new file mode 100644 index 0000000000..fd816a4247 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/runner/spec.opts @@ -0,0 +1,2 @@ +--diff +--colour \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/runner/spec_parser/spec_parser_fixture.rb b/vendor/gems/rspec/spec/spec/runner/spec_parser/spec_parser_fixture.rb new file mode 100644 index 0000000000..14602d934b --- /dev/null +++ b/vendor/gems/rspec/spec/spec/runner/spec_parser/spec_parser_fixture.rb @@ -0,0 +1,70 @@ +require File.dirname(__FILE__) + '/../../../spec_helper.rb' + +describe "c" do + + it "1" do + end + + it "2" do + end + +end + +describe "d" do + + it "3" do + end + + it "4" do + end + +end + +class SpecParserSubject +end + +describe SpecParserSubject do + + it "5" do + end + +end + +describe SpecParserSubject, "described" do + + it "6" do + end + +end + +describe SpecParserSubject, "described", :something => :something_else do + + it "7" do + end + +end + +describe "described", :something => :something_else do + + it "8" do + end + +end + +describe "e" do + + it "9" do + end + + it "10" do + end + + describe "f" do + it "11" do + end + + it "12" do + end + end + +end diff --git a/vendor/gems/rspec/spec/spec/runner/spec_parser_spec.rb b/vendor/gems/rspec/spec/spec/runner/spec_parser_spec.rb new file mode 100644 index 0000000000..3d8d9c2e92 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/runner/spec_parser_spec.rb @@ -0,0 +1,79 @@ +require File.dirname(__FILE__) + '/../../spec_helper.rb' + +describe "SpecParser" do + attr_reader :parser, :file + before(:each) do + @parser = Spec::Runner::SpecParser.new + @file = "#{File.dirname(__FILE__)}/spec_parser/spec_parser_fixture.rb" + require file + end + + it "should find spec name for 'specify' at same line" do + parser.spec_name_for(file, 5).should == "c 1" + end + + it "should find spec name for 'specify' at end of spec line" do + parser.spec_name_for(file, 6).should == "c 1" + end + + it "should find context for 'context' above all specs" do + parser.spec_name_for(file, 4).should == "c" + end + + it "should find spec name for 'it' at same line" do + parser.spec_name_for(file, 15).should == "d 3" + end + + it "should find spec name for 'it' at end of spec line" do + parser.spec_name_for(file, 16).should == "d 3" + end + + it "should find context for 'describe' above all specs" do + parser.spec_name_for(file, 14).should == "d" + end + + it "should find nearest example name between examples" do + parser.spec_name_for(file, 7).should == "c 1" + end + + it "should find nothing outside a context" do + parser.spec_name_for(file, 2).should be_nil + end + + it "should find context name for type" do + parser.spec_name_for(file, 26).should == "SpecParserSubject" + end + + it "should find context and spec name for type" do + parser.spec_name_for(file, 28).should == "SpecParserSubject 5" + end + + it "should find context and description for type" do + parser.spec_name_for(file, 33).should == "SpecParserSubject described" + end + + it "should find context and description and example for type" do + parser.spec_name_for(file, 36).should == "SpecParserSubject described 6" + end + + it "should find context and description for type with modifications" do + parser.spec_name_for(file, 40).should == "SpecParserSubject described" + end + + it "should find context and described and example for type with modifications" do + parser.spec_name_for(file, 43).should == "SpecParserSubject described 7" + end + + it "should find example group" do + parser.spec_name_for(file, 47).should == "described" + end + + it "should find example" do + parser.spec_name_for(file, 50).should == "described 8" + end + + it "should find nested example" do + parser.spec_name_for(file, 63).should == "e f 11" + end + +end diff --git a/vendor/gems/rspec/spec/spec/runner/spec_spaced.opts b/vendor/gems/rspec/spec/spec/runner/spec_spaced.opts new file mode 100644 index 0000000000..6b3efd20f2 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/runner/spec_spaced.opts @@ -0,0 +1,2 @@ +--diff --colour +--format s \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/runner_spec.rb b/vendor/gems/rspec/spec/spec/runner_spec.rb new file mode 100644 index 0000000000..d75e661115 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/runner_spec.rb @@ -0,0 +1,11 @@ +require File.dirname(__FILE__) + '/../spec_helper.rb' + +module Spec + describe Runner, ".configure" do + it "should yield global configuration" do + Spec::Runner.configure do |config| + config.should equal(Spec::Runner.configuration) + end + end + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/spec_classes.rb b/vendor/gems/rspec/spec/spec/spec_classes.rb new file mode 100644 index 0000000000..c8900a789f --- /dev/null +++ b/vendor/gems/rspec/spec/spec/spec_classes.rb @@ -0,0 +1,133 @@ +# This file contains various classes used by the specs. +module Spec + module Expectations + class Person + attr_reader :name + def initialize name + @name = name + end + def == other + return @name == other.name + end + end + + class ClassWithMultiWordPredicate + def multi_word_predicate? + true + end + end + + module Helper + class CollectionWithSizeMethod + def initialize; @list = []; end + def size; @list.size; end + def push(item); @list.push(item); end + end + + class CollectionWithLengthMethod + def initialize; @list = []; end + def length; @list.size; end + def push(item); @list.push(item); end + end + + class CollectionOwner + attr_reader :items_in_collection_with_size_method, :items_in_collection_with_length_method + + def initialize + @items_in_collection_with_size_method = CollectionWithSizeMethod.new + @items_in_collection_with_length_method = CollectionWithLengthMethod.new + end + + def add_to_collection_with_size_method(item) + @items_in_collection_with_size_method.push(item) + end + + def add_to_collection_with_length_method(item) + @items_in_collection_with_length_method.push(item) + end + + def items_for(arg) + return [1, 2, 3] if arg == 'a' + [1] + end + + def items + @items_in_collection_with_size_method + end + end + + class HandCodedMock + include Spec::Matchers + def initialize(return_val) + @return_val = return_val + @funny_called = false + end + + def funny? + @funny_called = true + @return_val + end + + def hungry?(a, b, c) + a.should equal(1) + b.should equal(2) + c.should equal(3) + @funny_called = true + @return_val + end + + def exists? + @return_val + end + + def multi_word_predicate? + @return_val + end + + def rspec_verify + @funny_called.should be_true + end + end + class ClassWithUnqueriedPredicate + attr_accessor :foo + def initialize(foo) + @foo = foo + end + end + end + end +end + +module Custom + require 'spec/runner/formatter/base_text_formatter' + class Formatter < Spec::Runner::Formatter::BaseTextFormatter + attr_reader :options, :where + + def initialize(options, where) + @options = options + @where = where + end + end + + class BadFormatter < Spec::Runner::Formatter::BaseTextFormatter + attr_reader :where + + def initialize(options, where) + bad_method + end + end + + class Differ + attr_reader :options + def initialize(options) + @options = options + end + + def diff_as_object(target, expected) + "" + end + end +end + +class FakeReporter < Spec::Runner::Reporter +end diff --git a/vendor/gems/rspec/spec/spec/story/builders.rb b/vendor/gems/rspec/spec/spec/story/builders.rb new file mode 100644 index 0000000000..77d50d53ee --- /dev/null +++ b/vendor/gems/rspec/spec/spec/story/builders.rb @@ -0,0 +1,46 @@ +module Spec + module Story + class StoryBuilder + def initialize + @title = 'a story' + @narrative = 'narrative' + end + + def title(value) + @title = value + self + end + + def narrative(value) + @narrative = value + self + end + + def to_story(&block) + block = lambda {} unless block_given? + Story.new @title, @narrative, &block + end + end + + class ScenarioBuilder + def initialize + @name = 'a scenario' + @story = StoryBuilder.new.to_story + end + + def name(value) + @name = value + self + end + + def story(value) + @story = value + self + end + + def to_scenario(&block) + Scenario.new @story, @name, &block + end + end + end +end diff --git a/vendor/gems/rspec/spec/spec/story/extensions/main_spec.rb b/vendor/gems/rspec/spec/spec/story/extensions/main_spec.rb new file mode 100644 index 0000000000..acdc341ce1 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/story/extensions/main_spec.rb @@ -0,0 +1,161 @@ +require File.dirname(__FILE__) + '/../../../spec_helper' + +module Spec + module Story + module Extensions + describe "the main object extended with Main", :shared => true do + before(:each) do + @main = Class.new do; include Main; end + @original_rspec_story_steps, $rspec_story_steps = $rspec_story_steps, nil + end + + after(:each) do + $rspec_story_steps = @original_rspec_story_steps + end + + def have_step(type, name) + return simple_matcher(%[step group containing a #{type} named #{name.inspect}]) do |actual| + Spec::Story::Step === actual.find(type, name) + end + end + end + + describe Main, "#run_story" do + it_should_behave_like "the main object extended with Main" + + it "should create a PlainTextStoryRunner with run_story" do + Spec::Story::Runner::PlainTextStoryRunner.should_receive(:new).and_return(mock("runner", :null_object => true)) + @main.run_story + end + + it "should yield the runner if arity == 1" do + File.should_receive(:read).with("some/path").and_return("Story: foo") + $main_spec_runner = nil + @main.run_story("some/path") do |runner| + $main_spec_runner = runner + end + $main_spec_runner.should be_an_instance_of(Spec::Story::Runner::PlainTextStoryRunner) + end + + it "should run in the runner if arity == 0" do + File.should_receive(:read).with("some/path").and_return("Story: foo") + $main_spec_runner = nil + @main.run_story("some/path") do + $main_spec_runner = self + end + $main_spec_runner.should be_an_instance_of(Spec::Story::Runner::PlainTextStoryRunner) + end + + it "should tell the PlainTextStoryRunner to run with run_story" do + runner = mock("runner") + Spec::Story::Runner::PlainTextStoryRunner.should_receive(:new).and_return(runner) + runner.should_receive(:run) + @main.run_story + end + end + + describe Main, "#steps_for" do + it_should_behave_like "the main object extended with Main" + + it "should have no steps for a non existent key" do + @main.steps_for(:key).find(:given, "foo").should be_nil + end + + it "should create steps for a key" do + $main_spec_invoked = false + @main.steps_for(:key) do + Given("foo") { + $main_spec_invoked = true + } + end + @main.steps_for(:key).find(:given, "foo").perform(Object.new, "foo") + $main_spec_invoked.should be_true + end + + it "should append steps to steps_for a given key" do + @main.steps_for(:key) do + Given("first") {} + end + @main.steps_for(:key) do + Given("second") {} + end + @main.steps_for(:key).should have_step(:given, "first") + @main.steps_for(:key).should have_step(:given, "second") + end + end + + describe Main, "#with_steps_for adding new steps" do + it_should_behave_like "the main object extended with Main" + + it "should result in a group containing pre-existing steps and newly defined steps" do + first_group = @main.steps_for(:key) do + Given("first") {} + end + second_group = @main.with_steps_for(:key) do + Given("second") {} + end + + second_group.should have_step(:given, "first") + second_group.should have_step(:given, "second") + end + + it "should not add its steps to the existing group" do + first_group = @main.steps_for(:key) do + Given("first") {} + end + second_group = @main.with_steps_for(:key) do + Given("second") {} + end + + first_group.should have_step(:given, "first") + first_group.should_not have_step(:given, "second") + end + end + + describe Main, "#with_steps_for running a story" do + it_should_behave_like "the main object extended with Main" + + before(:each) do + @runner = mock("runner") + @runner_step_group = StepGroup.new + @runner.stub!(:steps).and_return(@runner_step_group) + @runner.stub!(:run) + Spec::Story::Runner::PlainTextStoryRunner.stub!(:new).and_return(@runner) + end + + it "should create a PlainTextStoryRunner with a path" do + Spec::Story::Runner::PlainTextStoryRunner.should_receive(:new).with('path/to/file',{}).and_return(@runner) + @main.with_steps_for(:foo) do + run 'path/to/file' + end + end + + it "should create a PlainTextStoryRunner with a path and options" do + Spec::Story::Runner::PlainTextStoryRunner.should_receive(:new).with(anything,{:bar => :baz}).and_return(@runner) + @main.with_steps_for(:foo) do + run 'path/to/file', :bar => :baz + end + end + + it "should pass the group it creates to the runner's steps" do + steps = @main.steps_for(:ice_cream) do + Given("vanilla") {} + end + @main.with_steps_for(:ice_cream) do + run 'foo' + end + @runner_step_group.should have_step(:given, "vanilla") + end + + it "should run a story" do + @runner.should_receive(:run) + Spec::Story::Runner::PlainTextStoryRunner.should_receive(:new).and_return(@runner) + @main.with_steps_for(:foo) do + run 'path/to/file' + end + end + + end + end + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/story/extensions_spec.rb b/vendor/gems/rspec/spec/spec/story/extensions_spec.rb new file mode 100644 index 0000000000..612ddc72f7 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/story/extensions_spec.rb @@ -0,0 +1,14 @@ +require File.dirname(__FILE__) + '/story_helper' + +require 'spec/story' + +describe Kernel, "#Story" do + before(:each) do + Kernel.stub!(:at_exit) + end + + it "should delegate to ::Spec::Story::Runner.story_runner" do + ::Spec::Story::Runner.story_runner.should_receive(:Story) + story = Story("title","narrative"){} + end +end diff --git a/vendor/gems/rspec/spec/spec/story/given_scenario_spec.rb b/vendor/gems/rspec/spec/spec/story/given_scenario_spec.rb new file mode 100644 index 0000000000..a688f88d5c --- /dev/null +++ b/vendor/gems/rspec/spec/spec/story/given_scenario_spec.rb @@ -0,0 +1,27 @@ +require File.dirname(__FILE__) + '/story_helper' + +module Spec + module Story + describe GivenScenario do + it 'should execute a scenario from the current story in its world' do + # given + class MyWorld + attr :scenario_ran + end + instance = World.create(MyWorld) + scenario = ScenarioBuilder.new.to_scenario do + @scenario_ran = true + end + Runner::StoryRunner.should_receive(:scenario_from_current_story).with('scenario name').and_return(scenario) + + step = GivenScenario.new 'scenario name' + + # when + step.perform(instance, nil) + + # then + instance.scenario_ran.should be_true + end + end + end +end diff --git a/vendor/gems/rspec/spec/spec/story/runner/plain_text_story_runner_spec.rb b/vendor/gems/rspec/spec/spec/story/runner/plain_text_story_runner_spec.rb new file mode 100644 index 0000000000..1d5f2e0c33 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/story/runner/plain_text_story_runner_spec.rb @@ -0,0 +1,92 @@ +require File.dirname(__FILE__) + '/../story_helper' + +module Spec + module Story + module Runner + describe PlainTextStoryRunner do + before(:each) do + StoryParser.stub!(:new).and_return(@parser = mock("parser")) + @parser.stub!(:parse).and_return([]) + File.stub!(:read).with("path").and_return("this\nand that") + end + + it "should provide access to steps" do + runner = PlainTextStoryRunner.new("path") + + runner.steps do |add| + add.given("baz") {} + end + + runner.steps.find(:given, "baz").should_not be_nil + end + + it "should parse a story file" do + runner = PlainTextStoryRunner.new("path") + + during { + runner.run + }.expect { + @parser.should_receive(:parse).with(["this", "and that"]) + } + end + + it "should build up a mediator with its own steps and the singleton story_runner" do + runner = PlainTextStoryRunner.new("path") + Spec::Story::Runner.should_receive(:story_runner).and_return(story_runner = mock("story runner")) + Spec::Story::Runner::StoryMediator.should_receive(:new).with(runner.steps, story_runner, {}). + and_return(mediator = stub("mediator", :run_stories => nil)) + runner.run + end + + it "should build up a parser with the mediator" do + runner = PlainTextStoryRunner.new("path") + Spec::Story::Runner.should_receive(:story_runner).and_return(story_runner = mock("story runner")) + Spec::Story::Runner::StoryMediator.should_receive(:new).and_return(mediator = stub("mediator", :run_stories => nil)) + Spec::Story::Runner::StoryParser.should_receive(:new).with(mediator).and_return(@parser) + runner.run + end + + it "should tell the mediator to run the stories" do + runner = PlainTextStoryRunner.new("path") + mediator = mock("mediator") + Spec::Story::Runner::StoryMediator.should_receive(:new).and_return(mediator) + mediator.should_receive(:run_stories) + runner.run + end + + it "should accept a block instead of a path" do + runner = PlainTextStoryRunner.new do |runner| + runner.load("path/to/story") + end + File.should_receive(:read).with("path/to/story").and_return("this\nand that") + runner.run + end + + it "should tell you if you try to run with no path set" do + runner = PlainTextStoryRunner.new + lambda { + runner.run + }.should raise_error(RuntimeError, "You must set a path to the file with the story. See the RDoc.") + end + + it "should pass options to the mediator" do + runner = PlainTextStoryRunner.new("path", :foo => :bar) + Spec::Story::Runner::StoryMediator.should_receive(:new). + with(anything, anything, :foo => :bar). + and_return(mediator = stub("mediator", :run_stories => nil)) + runner.run + end + + it "should provide access to its options" do + runner = PlainTextStoryRunner.new("path") + runner[:foo] = :bar + Spec::Story::Runner::StoryMediator.should_receive(:new). + with(anything, anything, :foo => :bar). + and_return(mediator = stub("mediator", :run_stories => nil)) + runner.run + end + + end + end + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/story/runner/scenario_collector_spec.rb b/vendor/gems/rspec/spec/spec/story/runner/scenario_collector_spec.rb new file mode 100644 index 0000000000..042c41e8df --- /dev/null +++ b/vendor/gems/rspec/spec/spec/story/runner/scenario_collector_spec.rb @@ -0,0 +1,27 @@ +require File.dirname(__FILE__) + '/../story_helper' + +module Spec + module Story + module Runner + describe ScenarioCollector do + it 'should construct scenarios with the supplied story' do + # given + story = stub_everything('story') + scenario_collector = ScenarioCollector.new(story) + + # when + scenario_collector.Scenario 'scenario1' do end + scenario_collector.Scenario 'scenario2' do end + scenarios = scenario_collector.scenarios + + # then + scenario_collector.should have(2).scenarios + scenarios.first.name.should == 'scenario1' + scenarios.first.story.should equal(story) + scenarios.last.name.should == 'scenario2' + scenarios.last.story.should equal(story) + end + end + end + end +end diff --git a/vendor/gems/rspec/spec/spec/story/runner/scenario_runner_spec.rb b/vendor/gems/rspec/spec/spec/story/runner/scenario_runner_spec.rb new file mode 100644 index 0000000000..a69ed4a996 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/story/runner/scenario_runner_spec.rb @@ -0,0 +1,142 @@ +require File.dirname(__FILE__) + '/../story_helper' + +module Spec + module Story + module Runner + describe ScenarioRunner do + it 'should run a scenario in its story' do + # given + world = stub_everything + scenario_runner = ScenarioRunner.new + $answer = nil + story = Story.new 'story', 'narrative' do + @answer = 42 # this should be available to the scenario + end + scenario = Scenario.new story, 'scenario' do + $answer = @answer + end + + # when + scenario_runner.run(scenario, world) + + # then + $answer.should == 42 + end + + it 'should allow scenarios to share methods' do + # given + world = stub_everything + $shared_invoked = 0 + story = Story.new 'story', 'narrative' do + def shared + $shared_invoked += 1 + end + end + scenario1 = Scenario.new story, 'scenario1' do + shared() + end + scenario2 = Scenario.new story, 'scenario2' do + shared() + end + scenario_runner = ScenarioRunner.new + + # when + scenario_runner.run(scenario1, world) + scenario_runner.run(scenario2, world) + + # then + $shared_invoked.should == 2 + end + + it 'should notify listeners when a scenario starts' do + # given + world = stub_everything + story = Story.new 'story', 'narrative' do end + scenario = Scenario.new story, 'scenario1' do + # succeeds + end + scenario_runner = ScenarioRunner.new + mock_listener1 = stub_everything('listener1') + mock_listener2 = stub_everything('listener2') + scenario_runner.add_listener(mock_listener1) + scenario_runner.add_listener(mock_listener2) + + # expect + mock_listener1.should_receive(:scenario_started).with('story', 'scenario1') + mock_listener2.should_receive(:scenario_started).with('story', 'scenario1') + + # when + scenario_runner.run(scenario, world) + + # then + end + + it 'should notify listeners when a scenario succeeds' do + # given + world = stub_everything('world') + story = Story.new 'story', 'narrative' do end + scenario = Scenario.new story, 'scenario1' do + # succeeds + end + scenario_runner = ScenarioRunner.new + mock_listener1 = stub_everything('listener1') + mock_listener2 = stub_everything('listener2') + scenario_runner.add_listener(mock_listener1) + scenario_runner.add_listener(mock_listener2) + + # expect + mock_listener1.should_receive(:scenario_succeeded).with('story', 'scenario1') + mock_listener2.should_receive(:scenario_succeeded).with('story', 'scenario1') + + # when + scenario_runner.run(scenario, world) + + # then + end + + it 'should notify listeners ONCE when a scenario raises an error' do + # given + error = RuntimeError.new('oops') + story = Story.new 'title', 'narrative' do end + scenario = Scenario.new story, 'scenario1' do + end + scenario_runner = ScenarioRunner.new + mock_listener = stub_everything('listener') + scenario_runner.add_listener(mock_listener) + world = stub_everything + + # expect + world.should_receive(:errors).twice.and_return([error, error]) + mock_listener.should_receive(:scenario_failed).with('title', 'scenario1', error).once + + # when + scenario_runner.run scenario, world + + # then + end + + it 'should notify listeners when a scenario is pending' do + # given + pending_error = Spec::Example::ExamplePendingError.new('todo') + story = Story.new 'title', 'narrative' do end + scenario = Scenario.new story, 'scenario1' do + end + scenario_runner = ScenarioRunner.new + mock_listener = mock('listener') + scenario_runner.add_listener(mock_listener) + world = stub_everything + + # expect + world.should_receive(:errors).twice.and_return([pending_error, pending_error]) + mock_listener.should_receive(:scenario_started).with('title', 'scenario1') + mock_listener.should_receive(:scenario_pending).with('title', 'scenario1', 'todo').once + + # when + scenario_runner.run scenario, world + + # then + end + end + end + end +end diff --git a/vendor/gems/rspec/spec/spec/story/runner/story_mediator_spec.rb b/vendor/gems/rspec/spec/spec/story/runner/story_mediator_spec.rb new file mode 100644 index 0000000000..4192e483a8 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/story/runner/story_mediator_spec.rb @@ -0,0 +1,133 @@ +require File.dirname(__FILE__) + '/../story_helper' + +module Spec + module Story + module Runner + + describe StoryMediator do + before(:each) do + $story_mediator_spec_value = nil + @step_group = StepGroup.new + @step_group.create_matcher(:given, "given") { $story_mediator_spec_value = "given matched" } + @step_group.create_matcher(:when, "when") { $story_mediator_spec_value = "when matched" } + @step_group.create_matcher(:then, "then") { $story_mediator_spec_value = "then matched" } + + @scenario_runner = ScenarioRunner.new + @runner = StoryRunner.new @scenario_runner + @mediator = StoryMediator.new @step_group, @runner + end + + def run_stories + @mediator.run_stories + @runner.run_stories + end + + it "should have no stories" do + @mediator.stories.should be_empty + end + + it "should create two stories" do + @mediator.create_story "story title", "story narrative" + @mediator.create_story "story title 2", "story narrative 2" + run_stories + + @runner.should have(2).stories + @runner.stories.first.title.should == "story title" + @runner.stories.first.narrative.should == "story narrative" + @runner.stories.last.title.should == "story title 2" + @runner.stories.last.narrative.should == "story narrative 2" + end + + it "should create a scenario" do + @mediator.create_story "title", "narrative" + @mediator.create_scenario "scenario name" + run_stories + + @runner.should have(1).scenarios + @runner.scenarios.first.name.should == "scenario name" + @runner.scenarios.first.story.should == @runner.stories.first + end + + it "should create a given scenario step if one matches" do + pending("need to untangle the dark mysteries of the story runner - something needs to get stubbed here") do + story = @mediator.create_story "title", "narrative" + @mediator.create_scenario "previous scenario" + @mediator.create_scenario "current scenario" + @mediator.create_given_scenario "previous scenario" + run_stories + + $story_mediator_spec_value.should == "previous scenario matched" + end + end + + it "should create a given step if one matches" do + @mediator.create_story "title", "narrative" + @mediator.create_scenario "scenario" + @mediator.create_given "given" + run_stories + + $story_mediator_spec_value.should == "given matched" + end + + it "should create a pending step if no given step matches" do + @mediator.create_story "title", "narrative" + @mediator.create_scenario "scenario" + @mediator.create_given "no match" + mock_listener = stub_everything("listener") + mock_listener.should_receive(:scenario_pending).with("title", "scenario", "Unimplemented step: no match") + @scenario_runner.add_listener mock_listener + run_stories + end + + it "should create a when step if one matches" do + @mediator.create_story "title", "narrative" + @mediator.create_scenario "scenario" + @mediator.create_when "when" + run_stories + + $story_mediator_spec_value.should == "when matched" + end + + it "should create a pending step if no when step matches" do + @mediator.create_story "title", "narrative" + @mediator.create_scenario "scenario" + @mediator.create_when "no match" + mock_listener = stub_everything("listener") + mock_listener.should_receive(:scenario_pending).with("title", "scenario", "Unimplemented step: no match") + @scenario_runner.add_listener mock_listener + run_stories + end + + it "should create a then step if one matches" do + @mediator.create_story "title", "narrative" + @mediator.create_scenario "scenario" + @mediator.create_then "then" + run_stories + + $story_mediator_spec_value.should == "then matched" + end + + it "should create a pending step if no 'then' step matches" do + @mediator.create_story "title", "narrative" + @mediator.create_scenario "scenario" + @mediator.create_then "no match" + mock_listener = stub_everything("listener") + mock_listener.should_receive(:scenario_pending).with("title", "scenario", "Unimplemented step: no match") + @scenario_runner.add_listener mock_listener + run_stories + end + + it "should pass options to the stories it creates" do + @mediator = StoryMediator.new @step_group, @runner, :foo => :bar + @mediator.create_story "story title", "story narrative" + + run_stories + + @runner.stories.first[:foo].should == :bar + end + + end + + end + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/story/runner/story_parser_spec.rb b/vendor/gems/rspec/spec/spec/story/runner/story_parser_spec.rb new file mode 100644 index 0000000000..5efc8fd184 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/story/runner/story_parser_spec.rb @@ -0,0 +1,384 @@ +require File.dirname(__FILE__) + '/../story_helper' + +module Spec + module Story + module Runner + + describe StoryParser do + before(:each) do + @story_mediator = mock("story_mediator") + @parser = StoryParser.new(@story_mediator) + end + + it "should parse no lines" do + @parser.parse([]) + end + + it "should ignore text before the first Story: begins" do + @story_mediator.should_not_receive(:create_scenario) + @story_mediator.should_not_receive(:create_given) + @story_mediator.should_not_receive(:create_when) + @story_mediator.should_not_receive(:create_then) + @story_mediator.should_receive(:create_story).with("simple addition", "") + @parser.parse(["Here is a bunch of text", "about a calculator and all the things", "that it will do", "Story: simple addition"]) + end + + it "should create a story" do + @story_mediator.should_receive(:create_story).with("simple addition", "") + @parser.parse(["Story: simple addition"]) + end + + it "should create a story when line has leading spaces" do + @story_mediator.should_receive(:create_story).with("simple addition", "") + @parser.parse([" Story: simple addition"]) + end + + it "should add a one line narrative to the story" do + @story_mediator.should_receive(:create_story).with("simple addition","narrative") + @parser.parse(["Story: simple addition","narrative"]) + end + + it "should add a multi line narrative to the story" do + @story_mediator.should_receive(:create_story).with("simple addition","narrative line 1\nline 2\nline 3") + @parser.parse(["Story: simple addition","narrative line 1", "line 2", "line 3"]) + end + + it "should exclude blank lines from the narrative" do + @story_mediator.should_receive(:create_story).with("simple addition","narrative line 1\nline 2") + @parser.parse(["Story: simple addition","narrative line 1", "", "line 2"]) + end + + it "should exclude Scenario from the narrative" do + @story_mediator.should_receive(:create_story).with("simple addition","narrative line 1\nline 2") + @story_mediator.should_receive(:create_scenario) + @parser.parse(["Story: simple addition","narrative line 1", "line 2", "Scenario: add one plus one"]) + end + + end + + describe StoryParser, "in Story state" do + before(:each) do + @story_mediator = mock("story_mediator") + @parser = StoryParser.new(@story_mediator) + @story_mediator.stub!(:create_story) + end + + it "should create a second Story for Story" do + @story_mediator.should_receive(:create_story).with("number two","") + @parser.parse(["Story: s", "Story: number two"]) + end + + it "should include And in the narrative" do + @story_mediator.should_receive(:create_story).with("s","And foo") + @story_mediator.should_receive(:create_scenario).with("bar") + @parser.parse(["Story: s", "And foo", "Scenario: bar"]) + end + + it "should create a Scenario for Scenario" do + @story_mediator.should_receive(:create_scenario).with("number two") + @parser.parse(["Story: s", "Scenario: number two"]) + end + + it "should include Given in the narrative" do + @story_mediator.should_receive(:create_story).with("s","Given foo") + @story_mediator.should_receive(:create_scenario).with("bar") + @parser.parse(["Story: s", "Given foo", "Scenario: bar"]) + end + + it "should include Given: in the narrative" do + @story_mediator.should_receive(:create_story).with("s","Given: foo") + @story_mediator.should_receive(:create_scenario).with("bar") + @parser.parse(["Story: s", "Given: foo", "Scenario: bar"]) + end + + it "should include When in the narrative" do + @story_mediator.should_receive(:create_story).with("s","When foo") + @story_mediator.should_receive(:create_scenario).with("bar") + @parser.parse(["Story: s", "When foo", "Scenario: bar"]) + end + + it "should include Then in the narrative" do + @story_mediator.should_receive(:create_story).with("s","Then foo") + @story_mediator.should_receive(:create_scenario).with("bar") + @parser.parse(["Story: s", "Then foo", "Scenario: bar"]) + end + + it "should include other in the story" do + @story_mediator.should_receive(:create_story).with("s","narrative") + @parser.parse(["Story: s", "narrative"]) + end + end + + describe StoryParser, "in Scenario state" do + before(:each) do + @story_mediator = mock("story_mediator") + @parser = StoryParser.new(@story_mediator) + @story_mediator.stub!(:create_story) + @story_mediator.stub!(:create_scenario) + end + + it "should create a Story for Story" do + @story_mediator.should_receive(:create_story).with("number two","") + @parser.parse(["Story: s", "Scenario: s", "Story: number two"]) + end + + it "should create a Scenario for Scenario" do + @story_mediator.should_receive(:create_scenario).with("number two") + @parser.parse(["Story: s", "Scenario: s", "Scenario: number two"]) + end + + it "should raise for And" do + lambda { + @parser.parse(["Story: s", "Scenario: s", "And second"]) + }.should raise_error(IllegalStepError, /^Illegal attempt to create a And after a Scenario/) + end + + it "should create a Given for Given" do + @story_mediator.should_receive(:create_given).with("gift") + @parser.parse(["Story: s", "Scenario: s", "Given gift"]) + end + + it "should create a Given for Given:" do + @story_mediator.should_receive(:create_given).with("gift") + @parser.parse(["Story: s", "Scenario: s", "Given: gift"]) + end + + it "should create a GivenScenario for GivenScenario" do + @story_mediator.should_receive(:create_given_scenario).with("previous") + @parser.parse(["Story: s", "Scenario: s", "GivenScenario previous"]) + end + + it "should create a GivenScenario for GivenScenario:" do + @story_mediator.should_receive(:create_given_scenario).with("previous") + @parser.parse(["Story: s", "Scenario: s", "GivenScenario: previous"]) + end + + it "should transition to Given state after GivenScenario" do + @story_mediator.stub!(:create_given_scenario) + @parser.parse(["Story: s", "Scenario: s", "GivenScenario previous"]) + @parser.instance_eval{@state}.should be_an_instance_of(StoryParser::GivenState) + end + + it "should transition to Given state after GivenScenario:" do + @story_mediator.stub!(:create_given_scenario) + @parser.parse(["Story: s", "Scenario: s", "GivenScenario: previous"]) + @parser.instance_eval{@state}.should be_an_instance_of(StoryParser::GivenState) + end + + it "should create a When for When" do + @story_mediator.should_receive(:create_when).with("ever") + @parser.parse(["Story: s", "Scenario: s", "When ever"]) + end + + it "should create a When for When:" do + @story_mediator.should_receive(:create_when).with("ever") + @parser.parse(["Story: s", "Scenario: s", "When: ever"]) + end + + it "should create a Then for Then" do + @story_mediator.should_receive(:create_then).with("and there") + @parser.parse(["Story: s", "Scenario: s", "Then and there"]) + end + + it "should create a Then for Then:" do + @story_mediator.should_receive(:create_then).with("and there") + @parser.parse(["Story: s", "Scenario: s", "Then: and there"]) + end + + it "should ignore other" do + @parser.parse(["Story: s", "Scenario: s", "this is ignored"]) + end + end + + describe StoryParser, "in Given state" do + before(:each) do + @story_mediator = mock("story_mediator") + @parser = StoryParser.new(@story_mediator) + @story_mediator.stub!(:create_story) + @story_mediator.stub!(:create_scenario) + @story_mediator.should_receive(:create_given).with("first") + end + + it "should create a Story for Story" do + @story_mediator.should_receive(:create_story).with("number two","") + @parser.parse(["Story: s", "Scenario: s", "Given first", "Story: number two"]) + end + + it "should create a Scenario for Scenario" do + @story_mediator.should_receive(:create_scenario).with("number two") + @parser.parse(["Story: s", "Scenario: s", "Given first", "Scenario: number two"]) + end + + it "should create a second Given for Given" do + @story_mediator.should_receive(:create_given).with("second") + @parser.parse(["Story: s", "Scenario: s", "Given first", "Given second"]) + end + + it "should create a second Given for And" do + @story_mediator.should_receive(:create_given).with("second") + @parser.parse(["Story: s", "Scenario: s", "Given: first", "And second"]) + end + + it "should create a second Given for And:" do + @story_mediator.should_receive(:create_given).with("second") + @parser.parse(["Story: s", "Scenario: s", "Given first", "And: second"]) + end + + it "should create a When for When" do + @story_mediator.should_receive(:create_when).with("ever") + @parser.parse(["Story: s", "Scenario: s", "Given first", "When ever"]) + end + + it "should create a When for When:" do + @story_mediator.should_receive(:create_when).with("ever") + @parser.parse(["Story: s", "Scenario: s", "Given first", "When: ever"]) + end + + it "should create a Then for Then" do + @story_mediator.should_receive(:create_then).with("and there") + @parser.parse(["Story: s", "Scenario: s", "Given first", "Then and there"]) + end + + it "should create a Then for Then:" do + @story_mediator.should_receive(:create_then).with("and there") + @parser.parse(["Story: s", "Scenario: s", "Given first", "Then: and there"]) + end + + it "should ignore other" do + @parser.parse(["Story: s", "Scenario: s", "Given first", "this is ignored"]) + end + end + + describe StoryParser, "in When state" do + before(:each) do + @story_mediator = mock("story_mediator") + @parser = StoryParser.new(@story_mediator) + @story_mediator.stub!(:create_story) + @story_mediator.stub!(:create_scenario) + @story_mediator.should_receive(:create_given).with("first") + @story_mediator.should_receive(:create_when).with("else") + end + + it "should create a Story for Story" do + @story_mediator.should_receive(:create_story).with("number two","") + @parser.parse(["Story: s", "Scenario: s", "Given first", "When: else", "Story: number two"]) + end + + it "should create a Scenario for Scenario" do + @story_mediator.should_receive(:create_scenario).with("number two") + @parser.parse(["Story: s", "Scenario: s", "Given first", "When else", "Scenario: number two"]) + end + + it "should create Given for Given" do + @story_mediator.should_receive(:create_given).with("second") + @parser.parse(["Story: s", "Scenario: s", "Given first", "When else", "Given second"]) + end + + it "should create Given for Given:" do + @story_mediator.should_receive(:create_given).with("second") + @parser.parse(["Story: s", "Scenario: s", "Given first", "When else", "Given: second"]) + end + + it "should create a second When for When" do + @story_mediator.should_receive(:create_when).with("ever") + @parser.parse(["Story: s", "Scenario: s", "Given first", "When else", "When ever"]) + end + + it "should create a second When for When:" do + @story_mediator.should_receive(:create_when).with("ever") + @parser.parse(["Story: s", "Scenario: s", "Given: first", "When: else", "When: ever"]) + end + + it "should create a second When for And" do + @story_mediator.should_receive(:create_when).with("ever") + @parser.parse(["Story: s", "Scenario: s", "Given first", "When else", "And ever"]) + end + + it "should create a second When for And:" do + @story_mediator.should_receive(:create_when).with("ever") + @parser.parse(["Story: s", "Scenario: s", "Given: first", "When: else", "And: ever"]) + end + + it "should create a Then for Then" do + @story_mediator.should_receive(:create_then).with("and there") + @parser.parse(["Story: s", "Scenario: s", "Given first", "When else", "Then and there"]) + end + + it "should create a Then for Then:" do + @story_mediator.should_receive(:create_then).with("and there") + @parser.parse(["Story: s", "Scenario: s", "Given: first", "When: else", "Then: and there"]) + end + + it "should ignore other" do + @parser.parse(["Story: s", "Scenario: s", "Given first", "When else", "this is ignored"]) + end + end + + describe StoryParser, "in Then state" do + before(:each) do + @story_mediator = mock("story_mediator") + @parser = StoryParser.new(@story_mediator) + @story_mediator.stub!(:create_story) + @story_mediator.stub!(:create_scenario) + @story_mediator.should_receive(:create_given).with("first") + @story_mediator.should_receive(:create_when).with("else") + @story_mediator.should_receive(:create_then).with("what") + end + + it "should create a Story for Story" do + @story_mediator.should_receive(:create_story).with("number two","") + @parser.parse(["Story: s", "Scenario: s", "Given first", "When else", "Then what", "Story: number two"]) + end + + it "should create a Scenario for Scenario" do + @story_mediator.should_receive(:create_scenario).with("number two") + @parser.parse(["Story: s", "Scenario: s", "Given first", "When else", "Then what", "Scenario: number two"]) + end + + it "should create Given for Given" do + @story_mediator.should_receive(:create_given).with("second") + @parser.parse(["Story: s", "Scenario: s", "Given first", "When else", "Then what", "Given second"]) + end + + it "should create Given for Given:" do + @story_mediator.should_receive(:create_given).with("second") + @parser.parse(["Story: s", "Scenario: s", "Given: first", "When: else", "Then: what", "Given: second"]) + end + + it "should create When for When" do + @story_mediator.should_receive(:create_when).with("ever") + @parser.parse(["Story: s", "Scenario: s", "Given first", "When else", "Then what", "When ever"]) + end + + it "should create When for When:" do + @story_mediator.should_receive(:create_when).with("ever") + @parser.parse(["Story: s", "Scenario: s", "Given: first", "When: else", "Then: what", "When: ever"]) + end + + it "should create a Then for Then" do + @story_mediator.should_receive(:create_then).with("and there") + @parser.parse(["Story: s", "Scenario: s", "Given first", "When else", "Then what", "Then and there"]) + end + + it "should create a Then for Then:" do + @story_mediator.should_receive(:create_then).with("and there") + @parser.parse(["Story: s", "Scenario: s", "Given: first", "When: else", "Then: what", "Then: and there"]) + end + + it "should create a second Then for And" do + @story_mediator.should_receive(:create_then).with("ever") + @parser.parse(["Story: s", "Scenario: s", "Given first", "When else", "Then what", "And ever"]) + end + + it "should create a second Then for And:" do + @story_mediator.should_receive(:create_then).with("ever") + @parser.parse(["Story: s", "Scenario: s", "Given: first", "When: else", "Then: what", "And: ever"]) + end + + it "should ignore other" do + @parser.parse(["Story: s", "Scenario: s", "Given first", "When else", "Then what", "this is ignored"]) + end + end + end + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/story/runner/story_runner_spec.rb b/vendor/gems/rspec/spec/spec/story/runner/story_runner_spec.rb new file mode 100644 index 0000000000..0fc46405a1 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/story/runner/story_runner_spec.rb @@ -0,0 +1,256 @@ +require File.dirname(__FILE__) + '/../story_helper' + +module Spec + module Story + module Runner + describe StoryRunner do + it 'should collect all the stories' do + # given + story_runner = StoryRunner.new(stub('scenario_runner')) + + # when + story_runner.Story 'title1', 'narrative1' do end + story_runner.Story 'title2', 'narrative2' do end + stories = story_runner.stories + + # then + story_runner.should have(2).stories + stories.first.title.should == 'title1' + stories.first.narrative.should == 'narrative1' + stories.last.title.should == 'title2' + stories.last.narrative.should == 'narrative2' + end + + it 'should gather all the scenarios in the stories' do + # given + story_runner = StoryRunner.new(stub('scenario_runner')) + + # when + story_runner.Story "story1", "narrative1" do + Scenario "scenario1" do end + Scenario "scenario2" do end + end + story_runner.Story "story2", "narrative2" do + Scenario "scenario3" do end + end + scenarios = story_runner.scenarios + + # then + story_runner.should have(3).scenarios + scenarios[0].name.should == 'scenario1' + scenarios[1].name.should == 'scenario2' + scenarios[2].name.should == 'scenario3' + end + + # captures worlds passed into a ScenarioRunner + class ScenarioWorldCatcher + attr_accessor :worlds + def run(scenario, world) + (@worlds ||= []) << world + end + end + + it 'should run each scenario in a separate object' do + # given + scenario_world_catcher = ScenarioWorldCatcher.new + story_runner = StoryRunner.new(scenario_world_catcher) + story_runner.Story 'story', 'narrative' do + Scenario 'scenario1' do end + Scenario 'scenario2' do end + end + + # when + story_runner.run_stories + + # then + worlds = scenario_world_catcher.worlds + scenario_world_catcher.should have(2).worlds + worlds[0].should_not == worlds[1] + end + + it 'should use the provided world creator to create worlds' do + # given + stub_scenario_runner = stub_everything + mock_world_creator = mock('world creator') + story_runner = StoryRunner.new(stub_scenario_runner, mock_world_creator) + story_runner.Story 'story', 'narrative' do + Scenario 'scenario1' do end + Scenario 'scenario2' do end + end + + # expect + mock_world_creator.should_receive(:create).twice + + # when + story_runner.run_stories + + # then + end + + it 'should notify listeners of the scenario count when the run starts' do + # given + story_runner = StoryRunner.new(stub_everything) + mock_listener1 = stub_everything('listener1') + mock_listener2 = stub_everything('listener2') + story_runner.add_listener(mock_listener1) + story_runner.add_listener(mock_listener2) + + story_runner.Story 'story1', 'narrative1' do + Scenario 'scenario1' do end + end + story_runner.Story 'story2', 'narrative2' do + Scenario 'scenario2' do end + Scenario 'scenario3' do end + end + + # expect + mock_listener1.should_receive(:run_started).with(3) + mock_listener2.should_receive(:run_started).with(3) + + # when + story_runner.run_stories + + # then + end + + it 'should notify listeners when a story starts' do + # given + story_runner = StoryRunner.new(stub_everything) + mock_listener1 = stub_everything('listener1') + mock_listener2 = stub_everything('listener2') + story_runner.add_listener(mock_listener1) + story_runner.add_listener(mock_listener2) + + story_runner.Story 'story1', 'narrative1' do + Scenario 'scenario1' do end + end + story_runner.Story 'story2', 'narrative2' do + Scenario 'scenario2' do end + Scenario 'scenario3' do end + end + + # expect + mock_listener1.should_receive(:story_started).with('story1', 'narrative1') + mock_listener1.should_receive(:story_ended).with('story1', 'narrative1') + mock_listener2.should_receive(:story_started).with('story2', 'narrative2') + mock_listener2.should_receive(:story_ended).with('story2', 'narrative2') + + # when + story_runner.run_stories + + # then + end + + it 'should notify listeners when the run ends' do + # given + story_runner = StoryRunner.new(stub_everything) + mock_listener1 = stub_everything('listener1') + mock_listener2 = stub_everything('listener2') + story_runner.add_listener mock_listener1 + story_runner.add_listener mock_listener2 + story_runner.Story 'story1', 'narrative1' do + Scenario 'scenario1' do end + end + + # expect + mock_listener1.should_receive(:run_ended) + mock_listener2.should_receive(:run_ended) + + # when + story_runner.run_stories + + # then + end + + it 'should run a story in an instance of a specified class' do + # given + scenario_world_catcher = ScenarioWorldCatcher.new + story_runner = StoryRunner.new(scenario_world_catcher) + story_runner.Story 'title', 'narrative', :type => String do + Scenario 'scenario' do end + end + + # when + story_runner.run_stories + + # then + scenario_world_catcher.worlds[0].should be_kind_of(String) + scenario_world_catcher.worlds[0].should be_kind_of(World) + end + + it 'should pass initialization params through to the constructed instance' do + # given + scenario_world_catcher = ScenarioWorldCatcher.new + story_runner = StoryRunner.new(scenario_world_catcher) + story_runner.Story 'title', 'narrative', :type => Array, :args => [3] do + Scenario 'scenario' do end + end + + # when + story_runner.run_stories + + # then + scenario_world_catcher.worlds[0].should be_kind_of(Array) + scenario_world_catcher.worlds[0].size.should == 3 + end + + it 'should find a scenario in the current story by name' do + # given + story_runner = StoryRunner.new(ScenarioRunner.new) + $scenario = nil + + story_runner.Story 'title', 'narrative' do + Scenario 'first scenario' do + end + Scenario 'second scenario' do + $scenario = StoryRunner.scenario_from_current_story 'first scenario' + end + end + + # when + story_runner.run_stories + + # then + $scenario.name.should == 'first scenario' + end + + it "should clean the steps between stories" do + #given + story_runner = StoryRunner.new(ScenarioRunner.new) + result = mock 'result' + + step1 = Step.new('step') do + result.one + end + steps1 = StepGroup.new + steps1.add :when, step1 + + story_runner.Story 'title', 'narrative', :steps => steps1 do + Scenario 'first scenario' do + When 'step' + end + end + + step2 = Step.new('step') do + result.two + end + steps2 = StepGroup.new + steps2.add :when, step2 + + story_runner.Story 'title2', 'narrative', :steps => steps2 do + Scenario 'second scenario' do + When 'step' + end + end + + #then + result.should_receive(:one) + result.should_receive(:two) + + #when + story_runner.run_stories + end + end + end + end +end diff --git a/vendor/gems/rspec/spec/spec/story/runner_spec.rb b/vendor/gems/rspec/spec/spec/story/runner_spec.rb new file mode 100644 index 0000000000..81e8526404 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/story/runner_spec.rb @@ -0,0 +1,106 @@ +require File.dirname(__FILE__) + '/story_helper' + +module Spec + module Story + describe Runner, "module" do + def dev_null + io = StringIO.new + def io.write(str) + str.to_s.size + end + return io + end + + before :each do + Kernel.stub!(:at_exit) + @stdout, $stdout = $stdout, dev_null + @argv = Array.new(ARGV) + @runner_module = Runner.dup + @world_creator = World.dup + @runner_module.module_eval { @run_options = @story_runner = @scenario_runner = @world_creator = nil } + end + + after :each do + $stdout = @stdout + ARGV.replace @argv + @runner_module.module_eval { @run_options = @story_runner = @scenario_runner = @world_creator = nil } + end + + it 'should wire up a singleton StoryRunner' do + @runner_module.story_runner.should_not be_nil + end + + it 'should set its options based on ARGV' do + # given + ARGV << '--dry-run' + + # when + options = @runner_module.run_options + + # then + options.dry_run.should be_true + end + + it 'should add a reporter to the runner classes' do + # given + story_runner = mock('story runner', :null_object => true) + scenario_runner = mock('scenario runner', :null_object => true) + world_creator = mock('world', :null_object => true) + + @runner_module::class_eval { @world_creator = world_creator } + @runner_module::StoryRunner.stub!(:new).and_return(story_runner) + @runner_module::ScenarioRunner.stub!(:new).and_return(scenario_runner) + + # expect + world_creator.should_receive(:add_listener).with(an_instance_of(Spec::Runner::Formatter::Story::PlainTextFormatter)) + story_runner.should_receive(:add_listener).with(an_instance_of(Spec::Runner::Formatter::Story::PlainTextFormatter)) + scenario_runner.should_receive(:add_listener).with(an_instance_of(Spec::Runner::Formatter::Story::PlainTextFormatter)) + + # when + @runner_module.story_runner + end + + it 'should add a documenter to the runner classes if one is specified' do + # given + ARGV << "--format" << "html" + story_runner = mock('story runner', :null_object => true) + scenario_runner = mock('scenario runner', :null_object => true) + world_creator = mock('world', :null_object => true) + + @runner_module::class_eval { @world_creator = world_creator } + @runner_module::StoryRunner.stub!(:new).and_return(story_runner) + @runner_module::ScenarioRunner.stub!(:new).and_return(scenario_runner) + + # expect + world_creator.should_receive(:add_listener).with(an_instance_of(Spec::Runner::Formatter::Story::HtmlFormatter)) + story_runner.should_receive(:add_listener).with(an_instance_of(Spec::Runner::Formatter::Story::HtmlFormatter)) + scenario_runner.should_receive(:add_listener).with(an_instance_of(Spec::Runner::Formatter::Story::HtmlFormatter)) + + # when + @runner_module.story_runner + end + + it 'should add any registered listener to the runner classes' do + # given + ARGV << "--format" << "html" + story_runner = mock('story runner', :null_object => true) + scenario_runner = mock('scenario runner', :null_object => true) + world_creator = mock('world', :null_object => true) + + @runner_module::class_eval { @world_creator = world_creator } + @runner_module::StoryRunner.stub!(:new).and_return(story_runner) + @runner_module::ScenarioRunner.stub!(:new).and_return(scenario_runner) + + listener = Object.new + + # expect + world_creator.should_receive(:add_listener).with(listener) + story_runner.should_receive(:add_listener).with(listener) + scenario_runner.should_receive(:add_listener).with(listener) + + # when + @runner_module.register_listener listener + end + end + end +end diff --git a/vendor/gems/rspec/spec/spec/story/scenario_spec.rb b/vendor/gems/rspec/spec/spec/story/scenario_spec.rb new file mode 100644 index 0000000000..0cf7aff309 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/story/scenario_spec.rb @@ -0,0 +1,20 @@ +require File.dirname(__FILE__) + '/story_helper' + +module Spec + module Story + describe Scenario do + it 'should not raise an error if no body is supplied' do + # given + story = StoryBuilder.new.to_story + + # when + error = exception_from do + Scenario.new story, 'name' + end + + # then + error.should be_nil + end + end + end +end diff --git a/vendor/gems/rspec/spec/spec/story/step_group_spec.rb b/vendor/gems/rspec/spec/spec/story/step_group_spec.rb new file mode 100644 index 0000000000..dd28bfa263 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/story/step_group_spec.rb @@ -0,0 +1,157 @@ +require File.dirname(__FILE__) + '/story_helper' + +module Spec + module Story + describe StepGroup do + before(:each) do + @step_group = StepGroup.new + end + + it "should not find a matcher if empty" do + @step_group.find(:given, "this and that").should be_nil + end + + it "should create a given_scenario matcher" do + step = @step_group.given_scenario("this and that") {} + @step_group.find(:given_scenario, "this and that").should_not be_nil + @step_group.find(:given_scenario, "this and that").should equal(step) + end + + it "should create a given matcher" do + step = @step_group.given("this and that") {} + @step_group.find(:given, "this and that").should equal(step) + end + + it "should create a when matcher" do + step = @step_group.when("this and that") {} + @step_group.find(:when, "this and that").should equal(step) + end + + it "should create a them matcher" do + step = @step_group.then("this and that") {} + @step_group.find(:then, "this and that").should equal(step) + end + + it "should add a matcher object" do + step = Step.new("this and that") {} + @step_group.add(:given, step) + @step_group.find(:given, "this and that").should equal(step) + end + + it "should add it matchers to another StepGroup (with one given)" do + source = StepGroup.new + target = StepGroup.new + step = source.given("this and that") {} + source.add_to target + target.find(:given, "this and that").should equal(step) + end + + it "should add it matchers to another StepGroup (with some of each type)" do + source = StepGroup.new + target = StepGroup.new + given_scenario = source.given_scenario("1") {} + given = source.given("1") {} + when1 = source.when("1") {} + when2 = source.when("2") {} + then1 = source.then("1") {} + then2 = source.then("2") {} + then3 = source.then("3") {} + source.add_to target + target.find(:given_scenario, "1").should equal(given_scenario) + target.find(:given, "1").should equal(given) + target.find(:when, "1").should equal(when1) + target.find(:when, "2").should equal(when2) + target.find(:then, "1").should equal(then1) + target.find(:then, "2").should equal(then2) + target.find(:then, "3").should equal(then3) + end + + it "should append another collection" do + matchers_to_append = StepGroup.new + step = matchers_to_append.given("this and that") {} + @step_group << matchers_to_append + @step_group.find(:given, "this and that").should equal(step) + end + + it "should append several other collections" do + matchers_to_append = StepGroup.new + more_matchers_to_append = StepGroup.new + first_matcher = matchers_to_append.given("this and that") {} + second_matcher = more_matchers_to_append.given("and the other") {} + @step_group << matchers_to_append + @step_group << more_matchers_to_append + @step_group.find(:given, "this and that").should equal(first_matcher) + @step_group.find(:given, "and the other").should equal(second_matcher) + end + + it "should yield itself on initialization" do + begin + $step_group_spec_step = nil + matchers = StepGroup.new do |matchers| + $step_group_spec_step = matchers.given("foo") {} + end + $step_group_spec_step.matches?("foo").should be_true + ensure + $step_group_spec_step = nil + end + end + + it "should support defaults" do + class StepGroupSubclass < StepGroup + steps do |add| + add.given("foo") {} + end + end + StepGroupSubclass.new.find(:given, "foo").should_not be_nil + end + + it "should create a Given" do + sub = Class.new(StepGroup).new + step = sub.Given("foo") {} + sub.find(:given, "foo").should == step + end + + it "should create a When" do + sub = Class.new(StepGroup).new + step = sub.When("foo") {} + sub.find(:when, "foo").should == step + end + + it "should create a Then" do + sub = Class.new(StepGroup).new + step = sub.Then("foo") {} + sub.find(:then, "foo").should == step + end + + it "should create steps in a block" do + sub = Class.new(StepGroup).new do + Given("a given") {} + When("a when") {} + Then("a then") {} + end + sub.find(:given, "a given").should_not be_nil + sub.find(:when, "a when").should_not be_nil + sub.find(:then, "a then").should_not be_nil + end + + it "should clear itself" do + step = @step_group.given("this and that") {} + @step_group.clear + @step_group.find(:given, "this and that").should be_nil + end + + it "should tell you when it is empty" do + @step_group.should be_empty + end + + it "should tell you when it is not empty" do + @step_group.given("this and that") {} + @step_group.should_not be_empty + end + + it "should handle << nil" do + @step_group << nil + end + end + end +end diff --git a/vendor/gems/rspec/spec/spec/story/step_mother_spec.rb b/vendor/gems/rspec/spec/spec/story/step_mother_spec.rb new file mode 100644 index 0000000000..64efd7a6af --- /dev/null +++ b/vendor/gems/rspec/spec/spec/story/step_mother_spec.rb @@ -0,0 +1,72 @@ +require File.dirname(__FILE__) + '/story_helper' + +module Spec + module Story + describe StepMother do + it 'should store a step by name and type' do + # given + step_mother = StepMother.new + step = Step.new("a given", &lambda {}) + step_mother.store(:given, step) + + # when + found = step_mother.find(:given, "a given") + + # then + found.should == step + end + + it 'should NOT raise an error if a step is missing' do + # given + step_mother = StepMother.new + + # then + lambda do + # when + step_mother.find(:given, "doesn't exist") + end.should_not raise_error + end + + it "should create a default step which raises a pending error" do + # given + step_mother = StepMother.new + + # when + step = step_mother.find(:given, "doesn't exist") + + # then + step.should be_an_instance_of(Step) + + lambda do + step.perform(Object.new, "doesn't exist") + end.should raise_error(Spec::Example::ExamplePendingError, /Unimplemented/) + end + + it 'should clear itself' do + # given + step_mother = StepMother.new + step = Step.new("a given") do end + step_mother.store(:given, step) + + # when + step_mother.clear + + # then + step_mother.should be_empty + end + + it "should use assigned steps" do + step_mother = StepMother.new + + step = Step.new('step') {} + step_group = StepGroup.new + step_group.add(:given, step) + + step_mother.use(step_group) + + step_mother.find(:given, "step").should equal(step) + end + + end + end +end diff --git a/vendor/gems/rspec/spec/spec/story/step_spec.rb b/vendor/gems/rspec/spec/spec/story/step_spec.rb new file mode 100644 index 0000000000..0b6e515e93 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/story/step_spec.rb @@ -0,0 +1,200 @@ +require File.dirname(__FILE__) + '/story_helper' + +module Spec + module Story + describe Step, "matching" do + it "should match a text string" do + step = Step.new("this text") {} + step.matches?("this text").should be_true + end + + it "should not match a text string that does not start the same" do + step = Step.new("this text") {} + step.matches?("Xthis text").should be_false + end + + it "should not match a text string that does not end the same" do + step = Step.new("this text") {} + step.matches?("this textX").should be_false + end + + it "should match a text string with a param" do + step = Step.new("this $param text") {} + step.matches?("this anything text").should be_true + end + + it "should not be greedy" do + step = Step.new("enter $value for $key") {} + step.parse_args("enter 3 for keys for a piano").should == ['3','keys for a piano'] + end + + it "should match a text string with 3 params" do + step = Step.new("1 $one 2 $two 3 $three 4") {} + step.matches?("1 a 2 b 3 c 4").should be_true + end + + it "should match a text string with a param at the beginning" do + step = Step.new("$one 2 3") {} + step.matches?("a 2 3").should be_true + end + + it "should match a text string with a param at the end" do + step = Step.new("1 2 $three") {} + step.matches?("1 2 c").should be_true + end + + it "should not match a different string" do + step = Step.new("this text") {} + step.matches?("other text").should be_false + end + + it "should match a regexp" do + step = Step.new(/this text/) {} + step.matches?("this text").should be_true + end + + it "should match a regexp with a match group" do + step = Step.new(/this (.*) text/) {} + step.matches?("this anything text").should be_true + end + + it "should match a regexp with a named variable" do + step = Step.new(/this $variable text/) {} + step.matches?("this anything text").should be_true + end + + it "should not match a non matching regexp" do + step = Step.new(/this (.*) text/) {} + step.matches?("other anything text").should be_false + end + + it "should not match a non matching regexp with a named variable" do + step = Step.new(/this $variable text/) {} + step.matches?("other anything text").should be_false + end + + it "should not get bogged down by parens in strings" do + step = Step.new("before () after") {} + step.matches?("before () after").should be_true + end + + it "should match any option of an alteration" do + step = Step.new(/(he|she) is cool/) {} + step.matches?("he is cool").should be_true + step.matches?("she is cool").should be_true + end + + it "should match alteration as well as a variable" do + step = Step.new(/(he|she) is (.*)/) {} + step.matches?("he is cool").should be_true + step.parse_args("he is cool").should == ['he', 'cool'] + end + + it "should match alteration as well as a named variable" do + step = Step.new(/(he|she) is $adjective/) {} + step.matches?("he is cool").should be_true + step.parse_args("he is cool").should == ['he', 'cool'] + end + + it "should match alteration as well as a anonymous and named variable" do + step = Step.new(/(he|she) is (.*?) $adjective/) {} + step.matches?("he is very cool").should be_true + step.parse_args("he is very cool").should == ['he', 'very', 'cool'] + end + + end + + describe Step do + it "should make complain with no block" do + lambda { + step = Step.new("foo") + }.should raise_error + end + + it "should perform itself on an object" do + # given + $instance = nil + step = Step.new 'step' do + $instance = self + end + instance = Object.new + + # when + step.perform(instance, "step") + + # then + $instance.should == instance + end + + it "should perform itself with one parameter with match expression" do + # given + $result = nil + step = Step.new 'an account with $count dollars' do |count| + $result = count + end + instance = Object.new + + # when + args = step.parse_args("an account with 3 dollars") + step.perform(instance, *args) + + # then + $result.should == "3" + end + + it "should perform itself with one parameter without a match expression" do + # given + $result = nil + step = Step.new 'an account with a balance of' do |amount| + $result = amount + end + instance = Object.new + + # when + step.perform(instance, 20) + + # then + $result.should == 20 + end + + it "should perform itself with 2 parameters" do + # given + $account_type = nil + $amount = nil + step = Step.new 'a $account_type account with $amount dollars' do |account_type, amount| + $account_type = account_type + $amount = amount + end + instance = Object.new + + # when + args = step.parse_args("a savings account with 3 dollars") + step.perform(instance, *args) + + # then + $account_type.should == "savings" + $amount.should == "3" + end + + it "should perform itself when defined with a regexp with 2 parameters" do + # given + $pronoun = nil + $adjective = nil + step = Step.new /(he|she) is (.*)/ do |pronoun, adjective| + $pronoun = pronoun + $adjective = adjective + end + instance = Object.new + + # when + args = step.parse_args("he is cool") + step.perform(instance, *args) + + # then + $pronoun.should == "he" + $adjective.should == "cool" + end + + end + end +end diff --git a/vendor/gems/rspec/spec/spec/story/story_helper.rb b/vendor/gems/rspec/spec/spec/story/story_helper.rb new file mode 100644 index 0000000000..bb906f2557 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/story/story_helper.rb @@ -0,0 +1,2 @@ +require File.dirname(__FILE__) + '/../../spec_helper' +require File.dirname(__FILE__) + '/builders' diff --git a/vendor/gems/rspec/spec/spec/story/story_spec.rb b/vendor/gems/rspec/spec/spec/story/story_spec.rb new file mode 100644 index 0000000000..21257e9a72 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/story/story_spec.rb @@ -0,0 +1,86 @@ +require File.dirname(__FILE__) + '/story_helper' + +module Spec + module Story + describe Story do + it 'should run itself in a given object' do + # given + $instance = nil + story = Story.new 'title', 'narrative' do + $instance = self + end + object = Object.new + + # when + story.run_in(object) + + # then + $instance.should be(object) + end + + it 'should not raise an error if no block is supplied' do + # when + error = exception_from do + Story.new 'title', 'narrative' + end + + # then + error.should be_nil + end + + it "should raise when error raised running in another object" do + #given + story = Story.new 'title', 'narrative' do + raise "this is raised in the story" + end + object = Object.new + + # when/then + lambda do + story.run_in(object) + end.should raise_error + end + + it "should use the steps it is told to using a StepGroup" do + story = Story.new("title", "narrative", :steps => steps = StepGroup.new) do end + assignee = mock("assignee") + assignee.should_receive(:use).with(steps) + story.assign_steps_to(assignee) + end + + it "should use the steps it is told to using a key" do + begin + orig_rspec_story_steps = $rspec_story_steps + $rspec_story_steps = StepGroupHash.new + $rspec_story_steps[:foo] = steps = Object.new + + story = Story.new("title", "narrative", :steps_for => :foo) do end + assignee = mock("assignee") + + assignee.should_receive(:use).with(steps) + story.assign_steps_to(assignee) + ensure + $rspec_story_steps = orig_rspec_story_steps + end + end + + it "should use the steps it is told to using multiple keys" do + begin + orig_rspec_story_steps = $rspec_story_steps + $rspec_story_steps = StepGroupHash.new + $rspec_story_steps[:foo] = foo_steps = Object.new + $rspec_story_steps[:bar] = bar_steps = Object.new + + story = Story.new("title", "narrative", :steps_for => [:foo, :bar]) do end + assignee = mock("assignee") + + assignee.should_receive(:use).with(foo_steps) + assignee.should_receive(:use).with(bar_steps) + story.assign_steps_to(assignee) + ensure + $rspec_story_steps = orig_rspec_story_steps + end + end + end + end +end diff --git a/vendor/gems/rspec/spec/spec/story/world_spec.rb b/vendor/gems/rspec/spec/spec/story/world_spec.rb new file mode 100644 index 0000000000..f5113dc423 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/story/world_spec.rb @@ -0,0 +1,423 @@ +require File.dirname(__FILE__) + '/story_helper' + +require 'spec/story' + +module Spec + module Story + describe World do + before :each do + World.listeners.clear + end + + after :each do + World.listeners.clear + World.step_mother.clear + end + + it 'should create an object that mixes in a World' do + # when + obj = World::create + + # then + obj.should be_kind_of(World) + end + + it 'should create a World from any object type' do + # when + obj = World::create String + + # then + obj.should be_kind_of(String) + obj.should be_kind_of(World) + end + + it 'should pass arguments to #new when creating an object of a specified type that mixes in a world' do + # given + Thing = Struct.new(:name, :age) + + # when + obj = World::create Thing, "David", "I'm not telling" + + # then + obj.should be_an_instance_of(Thing) + obj.name.should == "David" + obj.age.should == "I'm not telling" + obj.should be_kind_of(World) + end + + def ensure_world_executes_step(&block) + # given + obj = World::create + $step_ran = false + + # when + obj.instance_eval(&block) + + # then + $step_ran.should be_true + end + + it 'should execute a Given, When or Then step' do + ensure_world_executes_step do + Given 'a given' do + $step_ran = true + end + end + + ensure_world_executes_step do + When 'an event' do + $step_ran = true + end + end + + ensure_world_executes_step do + Then 'an outcome' do + $step_ran = true + end + end + end + + it 'should interpret Given... And... as multiple givens' do + # given + world = World.create + $steps = [] + + # when + world.instance_eval do + Given 'step 1' do + $steps << 1 + end + And 'step 2' do + $steps << 2 + end + end + + # then + $steps.should == [1,2] + World.step_mother.find(:given, 'step 1').should_not be_nil + World.step_mother.find(:given, 'step 2').should_not be_nil + end + + it 'should interpret When... And... as multiple events' do + # given + world = World.create + $steps = [] + + # when + world.instance_eval do + When 'step 1' do + $steps << 1 + end + And 'step 2' do + $steps << 2 + end + end + + # then + $steps.should == [1,2] + World.step_mother.find(:when, 'step 1').should_not be_nil + World.step_mother.find(:when, 'step 2').should_not be_nil + end + + it 'should interpret Then... And... as multiple outcomes' do + # given + world = World.create + $steps = [] + + # when + world.instance_eval do + Then 'step 1' do + $steps << 1 + end + And 'step 2' do + $steps << 2 + end + end + + # then + $steps.should == [1,2] + World.step_mother.find(:then, 'step 1').should_not be_nil + World.step_mother.find(:then, 'step 2').should_not be_nil + end + + it 'should reuse a given across scenarios' do + # given + $num_invoked = 0 + a_world = World::create + a_world.instance_eval do + Given 'a given' do + $num_invoked += 1 + end + end + another_world = World::create + + # when + another_world.instance_eval do + Given 'a given' # without a body + end + + # then + $num_invoked.should == 2 + end + + it 'should reuse an event across scenarios' do + # given + $num_invoked = 0 + a_world = World::create + a_world.instance_eval do + When 'an event' do + $num_invoked += 1 + end + end + + another_world = World::create + + # when + another_world.instance_eval do + When 'an event' # without a body + end + + # then + $num_invoked.should == 2 + end + + it 'should reuse an outcome across scenarios' do + # given + $num_invoked = 0 + a_world = World::create + a_world.instance_eval do + Then 'an outcome' do + $num_invoked += 1 + end + end + + another_world = World::create + + # when + another_world.instance_eval do + Then 'an outcome' # without a body + end + + # then + $num_invoked.should == 2 + end + + it 'should preserve instance variables between steps within a scenario' do + # given + world = World::create + $first = nil + $second = nil + + # when + world.instance_eval do + Given 'given' do + @first = 'first' + end + When 'event' do + @second = @first # from given + end + Then 'outcome' do + $first = @first # from given + $second = @second # from event + end + end + + # then + $first.should == 'first' + $second.should == 'first' + end + + it 'should invoke a reused step in the new object instance' do + # given + $instances = [] + $debug = true + world1 = World.create + world1.instance_eval do + Given 'a given' do + $instances << self.__id__ + end + end + world2 = World.create + + # when + world2.instance_eval do + Given 'a given' # reused + Then 'an outcome' do + $instances << __id__ + end + end + $debug = false + # then + $instances.should == [ world1.__id__, world2.__id__, world2.__id__ ] + end + + def ensure_world_collects_error(expected_error, &block) + # given + world = World.create + # $error = nil + + # when + world.start_collecting_errors + world.instance_eval(&block) + + # then + world.should have(1).errors + world.errors[0].should be_kind_of(expected_error) + end + + it 'should collect a failure from a Given step' do + ensure_world_collects_error RuntimeError do + Given 'a given' do + raise RuntimeError, "oops" + end + end + end + + it 'should collect a failure from a When step' do + ensure_world_collects_error RuntimeError do + When 'an event' do + raise RuntimeError, "oops" + end + end + end + + it 'should collect a failure from a Then step' do + ensure_world_collects_error RuntimeError do + Then 'an outcome' do + raise RuntimeError, "oops" + end + end + end + + it 'should inform listeners when it runs a Given, When or Then step' do + # given + world = World.create + mock_listener1 = mock('listener1') + mock_listener2 = mock('listener2') + World.add_listener(mock_listener1) + World.add_listener(mock_listener2) + + # expect + mock_listener1.should_receive(:step_upcoming).with(:given, 'a context') + mock_listener1.should_receive(:step_succeeded).with(:given, 'a context') + mock_listener1.should_receive(:step_upcoming).with(:when, 'an event') + mock_listener1.should_receive(:step_succeeded).with(:when, 'an event') + mock_listener1.should_receive(:step_upcoming).with(:then, 'an outcome') + mock_listener1.should_receive(:step_succeeded).with(:then, 'an outcome') + + mock_listener2.should_receive(:step_upcoming).with(:given, 'a context') + mock_listener2.should_receive(:step_succeeded).with(:given, 'a context') + mock_listener2.should_receive(:step_upcoming).with(:when, 'an event') + mock_listener2.should_receive(:step_succeeded).with(:when, 'an event') + mock_listener2.should_receive(:step_upcoming).with(:then, 'an outcome') + mock_listener2.should_receive(:step_succeeded).with(:then, 'an outcome') + + # when + world.instance_eval do + Given 'a context' do end + When 'an event' do end + Then 'an outcome' do end + end + + # then + end + + it 'should tell listeners but not execute the step in dry-run mode' do + # given + Runner.stub!(:dry_run).and_return(true) + mock_listener = mock('listener') + World.add_listener(mock_listener) + $step_invoked = false + world = World.create + + # expect + mock_listener.should_receive(:step_upcoming).with(:given, 'a context') + mock_listener.should_receive(:step_succeeded).with(:given, 'a context') + + # when + world.instance_eval do + Given 'a context' do + $step_invoked = true + end + end + + # then + $step_invoked.should be(false) + end + + it 'should suppress listeners while it runs a GivenScenario' do + # given + $scenario_ran = false + + scenario = ScenarioBuilder.new.name('a scenario').to_scenario do + $scenario_ran = true + Given 'given' do end + When 'event' do end + Then 'outcome' do end + end + + given_scenario = GivenScenario.new('a scenario') + Runner::StoryRunner.should_receive(:scenario_from_current_story). + with('a scenario').and_return(scenario) + + world = World.create + listener = mock('listener') + World.add_listener(listener) + + # expect + listener.should_receive(:found_scenario).with(:'given scenario', 'a scenario') + listener.should_receive(:step_succeeded).never.with(:given, 'given') + listener.should_receive(:step_succeeded).never.with(:when, 'event') + listener.should_receive(:step_succeeded).never.with(:then, 'outcome') + + # when + world.GivenScenario 'a scenario' + + # then + $scenario_ran.should be_true + end + + it 'should interpret GivenScenario... And... as multiple givens' do + # given + world = World.create + $steps = [] + + scenario = ScenarioBuilder.new.name('a scenario').to_scenario do + $steps << 1 + end + Runner::StoryRunner.should_receive(:scenario_from_current_story). + with('a scenario').and_return(scenario) + + # when + world.instance_eval do + GivenScenario 'a scenario' + And 'step 2' do + $steps << 2 + end + end + + # then + $steps.should == [1,2] + World.step_mother.find(:given, 'step 2').should_not be_nil + end + + it 'should provide rspec matchers' do + # given + world = World.create + + # then + world.instance_eval do + 'hello'.should match(/^hello$/) + end + end + + it "should use assigned matchers" do + world = World.create + + World.should_receive(:use).with(steps = Object.new) + + World.use(steps) + end + end + end +end diff --git a/vendor/gems/rspec/spec/spec/translator_spec.rb b/vendor/gems/rspec/spec/spec/translator_spec.rb new file mode 100644 index 0000000000..01293d9ee4 --- /dev/null +++ b/vendor/gems/rspec/spec/spec/translator_spec.rb @@ -0,0 +1,265 @@ +require File.dirname(__FILE__) + '/../spec_helper.rb' +require 'spec/translator' + +describe "Translator" do + before do + @t = Spec::Translator.new + end + + it "should translate files" do + from = File.dirname(__FILE__) + '/..' + to = "#{Dir.tmpdir}/translated_specs" + @t.translate_dir(from, to) + end + + it "should translate context_setup do" do + @t.translate_line( + "context_setup do\n" + ).should eql( + "before(:all) do\n" + ) + end + + it "should translate context_setup {foo}" do + @t.translate_line( + "context_setup {foo}\n" + ).should eql( + "before(:all) {foo}\n" + ) + end + + it "should translate context ' to describe '" do + @t.translate_line( + "context 'Translator' do\n" + ).should eql( + "describe 'Translator' do\n" + ) + end + + it 'should translate context " to describe "' do + @t.translate_line( + 'context "Translator"' + ).should eql( + 'describe "Translator"' + ) + end + + it 'should translate spaces then context " to describe "' do + @t.translate_line( + ' context "Translator"' + ).should eql( + ' describe "Translator"' + ) + end + + it "should not translate context=foo" do + @t.translate_line(' context=foo').should eql(' context=foo') + end + + it "should not translate context = foo" do + @t.translate_line(' context = foo').should eql(' context = foo') + end + + it "should not translate context = foo" do + @t.translate_line(' context = foo').should eql(' context = foo') + end + + it "should translate should_be_close" do + @t.translate_line('5.0.should_be_close(5.0, 0.5)').should eql('5.0.should be_close(5.0, 0.5)') + end + + it "should translate should_not_raise" do + @t.translate_line('lambda { self.call }.should_not_raise').should eql('lambda { self.call }.should_not raise_error') + end + + it "should translate should_throw" do + @t.translate_line('lambda { self.call }.should_throw').should eql('lambda { self.call }.should throw_symbol') + end + + it "should not translate 0.9 should_not" do + @t.translate_line('@target.should_not @matcher').should eql('@target.should_not @matcher') + end + + it "should leave should_not_receive" do + @t.translate_line('@mock.should_not_receive(:not_expected).with("unexpected text")').should eql('@mock.should_not_receive(:not_expected).with("unexpected text")') + end + + it "should leave should_receive" do + @t.translate_line('@mock.should_receive(:not_expected).with("unexpected text")').should eql('@mock.should_receive(:not_expected).with("unexpected text")') + end + + it "should translate multi word predicates" do + @t.translate_line('foo.should_multi_word_predicate').should eql('foo.should be_multi_word_predicate') + end + + it "should translate multi word predicates prefixed with be" do + @t.translate_line('foo.should_be_multi_word_predicate').should eql('foo.should be_multi_word_predicate') + end + + it "should translate be(expected) to equal(expected)" do + @t.translate_line('foo.should_be :cool').should eql('foo.should equal :cool') + end + + it "should translate instance_of" do + @t.translate_line('5.should_be_an_instance_of(Integer)').should eql('5.should be_an_instance_of(Integer)') + end + + it "should translate should_be <" do + @t.translate_line('3.should_be < 4').should eql('3.should be < 4') + end + + it "should translate should_be <=" do + @t.translate_line('3.should_be <= 4').should eql('3.should be <= 4') + end + + it "should translate should_be >=" do + @t.translate_line('4.should_be >= 3').should eql('4.should be >= 3') + end + + it "should translate should_be >" do + @t.translate_line('4.should_be > 3').should eql('4.should be > 3') + end + + it "should translate should_be_happy" do + @t.translate_line("4.should_be_happy").should eql("4.should be_happy") + end + + it "should translate custom method taking regexp with parenthesis" do + @t.translate_line("@browser.should_contain_text(/Sn.rrunger og annet rusk/)").should eql("@browser.should be_contain_text(/Sn.rrunger og annet rusk/)") + end + + it "should translate custom method taking regexp without parenthesis" do + @t.translate_line("@browser.should_contain_text /Sn.rrunger og annet rusk/\n").should eql("@browser.should be_contain_text(/Sn.rrunger og annet rusk/)\n") + end + + it "should translate should_not_be_nil" do + @t.translate_line("foo.should_not_be_nil\n").should eql("foo.should_not be_nil\n") + end + + it "should translate kind of" do + @t.translate_line('@object.should_be_kind_of(MessageExpectation)').should( + eql('@object.should be_kind_of(MessageExpectation)')) + end + + it "should translate should_be_true" do + @t.translate_line("foo.should_be_true\n").should eql("foo.should be_true\n") + end + + # [#9674] spec_translate incorrectly handling shoud_match, when regexp in a var, in a block + # http://rubyforge.org/tracker/?func=detail&atid=3149&aid=9674&group_id=797 + it "should translate should_match on a regexp, in a var, in a block" do + @t.translate_line("collection.each { |c| c.should_match a_regexp_in_a_var }\n").should eql("collection.each { |c| c.should match(a_regexp_in_a_var) }\n") + @t.translate_line("collection.each{|c| c.should_match a_regexp_in_a_var}\n").should eql("collection.each{|c| c.should match(a_regexp_in_a_var) }\n") + end + + # From Rubinius specs + it "should translate close_to without parens" do + @t.translate_line("end.should_be_close 3.14159_26535_89793_23846, TOLERANCE\n").should eql("end.should be_close(3.14159_26535_89793_23846, TOLERANCE)\n") + end + + # [#9882] 0.9 Beta 1 - translator bugs + # http://rubyforge.org/tracker/index.php?func=detail&aid=9882&group_id=797&atid=3149 + it "should support symbol arguments" do + @t.translate_line( + "lambda { sequence.parse('bar') }.should_throw :ZeroWidthParseSuccess\n" + ).should eql( + "lambda { sequence.parse('bar') }.should throw_symbol(:ZeroWidthParseSuccess)\n" + ) + end + + # [#9882] 0.9 Beta 1 - translator bugs + # http://rubyforge.org/tracker/index.php?func=detail&aid=9882&group_id=797&atid=3149 + it "should support instance var arguments" do + @t.translate_line( + "a.should_eql @local" + ).should eql( + "a.should eql(@local)" + ) + end + + # [#9882] 0.9 Beta 1 - translator bugs + # http://rubyforge.org/tracker/index.php?func=detail&aid=9882&group_id=797&atid=3149 + it "should support lambdas as expecteds" do + @t.translate_line( + "@parslet.should_not_eql lambda { nil }.to_parseable" + ).should eql( + "@parslet.should_not eql(lambda { nil }.to_parseable)" + ) + end + + # [#9882] 0.9 Beta 1 - translator bugs + # http://rubyforge.org/tracker/index.php?func=detail&aid=9882&group_id=797&atid=3149 + it "should support fully qualified names" do + @t.translate_line( + "results.should_be_kind_of SimpleASTLanguage::Identifier" + ).should eql( + "results.should be_kind_of(SimpleASTLanguage::Identifier)" + ) + end + + # [#9882] 0.9 Beta 1 - translator bugs + # http://rubyforge.org/tracker/index.php?func=detail&aid=9882&group_id=797&atid=3149 + # it "should leave whitespace between expression and comments" do + # @t.translate_line( + # "lambda { @instance.foo = foo }.should_raise NoMethodError # no writer defined" + # ).should eql( + # "lambda { @instance.foo = foo }.should raise_error(NoMethodError) # no writer defined" + # ) + # end + + it "should translate redirects" do + @t.translate_line( + "controller.should_redirect_to 'http://not_existing_domain_for_novalis.test.host/404.html'" + ).should eql( + "controller.should redirect_to('http://not_existing_domain_for_novalis.test.host/404.html')" + ) + end + + it "should translate :any_args" do + @t.translate_line( + "mock.should_receive(:foo).with(:any_args)" + ).should eql( + "mock.should_receive(:foo).with(any_args)" + ) + end + + it "should translate :anything" do + @t.translate_line( + "mock.should_receive(:foo).with(:anything)" + ).should eql( + "mock.should_receive(:foo).with(anything)" + ) + end + + it "should translate :boolean" do + @t.translate_line( + "mock.should_receive(:foo).with(:boolean)" + ).should eql( + "mock.should_receive(:foo).with(boolean)" + ) + end + + it "should translate :no_args" do + @t.translate_line( + "mock.should_receive(:foo).with(:no_args)" + ).should eql( + "mock.should_receive(:foo).with(no_args)" + ) + end + + it "should translate :numeric" do + @t.translate_line( + "mock.should_receive(:foo).with(:numeric)" + ).should eql( + "mock.should_receive(:foo).with(an_instance_of(Numeric))" + ) + end + + it "should translate :string" do + @t.translate_line( + "mock.should_receive(:foo).with(:string)" + ).should eql( + "mock.should_receive(:foo).with(an_instance_of(String))" + ) + end +end diff --git a/vendor/gems/rspec/spec/spec_helper.rb b/vendor/gems/rspec/spec/spec_helper.rb new file mode 100644 index 0000000000..1318176d55 --- /dev/null +++ b/vendor/gems/rspec/spec/spec_helper.rb @@ -0,0 +1,103 @@ +require 'stringio' + +dir = File.dirname(__FILE__) +lib_path = File.expand_path("#{dir}/../lib") +$LOAD_PATH.unshift lib_path unless $LOAD_PATH.include?(lib_path) +$_spec_spec = true # Prevents Kernel.exit in various places + +require 'spec' +require 'spec/mocks' +require 'spec/story' +spec_classes_path = File.expand_path("#{dir}/../spec/spec/spec_classes") +require spec_classes_path unless $LOAD_PATH.include?(spec_classes_path) +require File.dirname(__FILE__) + '/../lib/spec/expectations/differs/default' + +module Spec + module Matchers + def fail + raise_error(Spec::Expectations::ExpectationNotMetError) + end + + def fail_with(message) + raise_error(Spec::Expectations::ExpectationNotMetError, message) + end + + class Pass + def matches?(proc, &block) + begin + proc.call + true + rescue Exception => @error + false + end + end + + def failure_message + @error.message + "\n" + @error.backtrace.join("\n") + end + end + + def pass + Pass.new + end + + class CorrectlyOrderedMockExpectation + def initialize(&event) + @event = event + end + + def expect(&expectations) + expectations.call + @event.call + end + end + + def during(&block) + CorrectlyOrderedMockExpectation.new(&block) + end + end +end + +class NonStandardError < Exception; end + +module Custom + class ExampleGroupRunner + attr_reader :options, :arg + def initialize(options, arg) + @options, @arg = options, arg + end + + def load_files(files) + end + + def run + end + end +end + +def exception_from(&block) + exception = nil + begin + yield + rescue StandardError => e + exception = e + end + exception +end + +describe "sandboxed rspec_options", :shared => true do + attr_reader :options + + before(:all) do + @original_rspec_options = $rspec_options + end + + before(:each) do + @options = ::Spec::Runner::Options.new(StringIO.new, StringIO.new) + $rspec_options = options + end + + after do + $rspec_options = @original_rspec_options + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/stories/all.rb b/vendor/gems/rspec/stories/all.rb new file mode 100644 index 0000000000..c2428fdf8c --- /dev/null +++ b/vendor/gems/rspec/stories/all.rb @@ -0,0 +1,5 @@ +require File.join(File.dirname(__FILE__), *%w[helper]) + +["example_groups","interop"].each do |dir| + require File.join(File.dirname(__FILE__), "#{dir}/stories") +end diff --git a/vendor/gems/rspec/stories/example_groups/autogenerated_docstrings b/vendor/gems/rspec/stories/example_groups/autogenerated_docstrings new file mode 100644 index 0000000000..b3ff689980 --- /dev/null +++ b/vendor/gems/rspec/stories/example_groups/autogenerated_docstrings @@ -0,0 +1,45 @@ +Story: autogenerated docstrings + + As an RSpec user + I want examples to generate their own names + So that I can reduce duplication between example names and example code + + Scenario: run passing examples with ruby + Given the file ../../examples/pure/autogenerated_docstrings_example.rb + + When I run it with the ruby interpreter -fs + + Then the stdout should match /should equal 5/ + And the stdout should match /should be < 5/ + And the stdout should match /should include "a"/ + And the stdout should match /should respond to #size/ + + Scenario: run failing examples with ruby + Given the file ../../failing_examples/failing_autogenerated_docstrings_example.rb + + When I run it with the ruby interpreter -fs + + Then the stdout should match /should equal 2/ + And the stdout should match /should be > 5/ + And the stdout should match /should include "b"/ + And the stdout should match /should not respond to #size/ + + Scenario: run passing examples with spec + Given the file ../../examples/pure/autogenerated_docstrings_example.rb + + When I run it with the spec script -fs + + Then the stdout should match /should equal 5/ + And the stdout should match /should be < 5/ + And the stdout should match /should include "a"/ + And the stdout should match /should respond to #size/ + + Scenario: run failing examples with spec + Given the file ../../failing_examples/failing_autogenerated_docstrings_example.rb + + When I run it with the spec script -fs + + Then the stdout should match /should equal 2/ + And the stdout should match /should be > 5/ + And the stdout should match /should include "b"/ + And the stdout should match /should not respond to #size/ diff --git a/vendor/gems/rspec/stories/example_groups/example_group_with_should_methods b/vendor/gems/rspec/stories/example_groups/example_group_with_should_methods new file mode 100644 index 0000000000..3d2bc61eb7 --- /dev/null +++ b/vendor/gems/rspec/stories/example_groups/example_group_with_should_methods @@ -0,0 +1,17 @@ +Story: Spec::ExampleGroup with should methods + + As an RSpec adopter accustomed to classes and methods + I want to use should_* methods in an ExampleGroup + So that I use RSpec with classes and methods that look more like RSpec examples + + Scenario: Run with ruby + Given the file spec/example_group_with_should_methods.rb + When I run it with the ruby interpreter + Then the exit code should be 256 + And the stdout should match "2 examples, 1 failure" + + Scenario: Run with spec + Given the file spec/example_group_with_should_methods.rb + When I run it with the spec script + Then the exit code should be 256 + And the stdout should match "2 examples, 1 failure" diff --git a/vendor/gems/rspec/stories/example_groups/nested_groups b/vendor/gems/rspec/stories/example_groups/nested_groups new file mode 100644 index 0000000000..ede978563c --- /dev/null +++ b/vendor/gems/rspec/stories/example_groups/nested_groups @@ -0,0 +1,17 @@ +Story: Nested example groups + + As an RSpec user + I want to nest examples groups + So that I can better organize my examples + + Scenario: Run with ruby + Given the file ../../examples/pure/stack_spec_with_nested_example_groups.rb + When I run it with the ruby interpreter -fs + Then the stdout should match /Stack \(empty\)/ + And the stdout should match /Stack \(full\)/ + + Scenario: Run with ruby + Given the file ../../examples/pure/stack_spec_with_nested_example_groups.rb + When I run it with the spec script -fs + Then the stdout should match /Stack \(empty\)/ + And the stdout should match /Stack \(full\)/ diff --git a/vendor/gems/rspec/stories/example_groups/output b/vendor/gems/rspec/stories/example_groups/output new file mode 100644 index 0000000000..4947bdcaff --- /dev/null +++ b/vendor/gems/rspec/stories/example_groups/output @@ -0,0 +1,25 @@ +Story: Getting correct output + + As an RSpec user + I want to see output only once + So that I don't get confused + + Scenario: Run with ruby + Given the file spec/simple_spec.rb + When I run it with the ruby interpreter + Then the exit code should be 0 + And the stdout should not match /\d+ tests, \d+ assertions, \d+ failures, \d+ errors/m + And the stdout should match "1 example, 0 failures" + + Scenario: Run with CommandLine object + Given the file spec/simple_spec.rb + When I run it with the CommandLine object + Then the exit code should be 0 + And the stdout should not match "Loaded suite" + And the stdout should not match /\d+ tests, \d+ assertions, \d+ failures, \d+ errors/m + And the stdout should match "1 example, 0 failures" + + Scenario: Tweak backtrace + Given the file stories/failing_story.rb + When I run it with the ruby interpreter + Then the stdout should not match /\/lib\/spec\// diff --git a/vendor/gems/rspec/stories/example_groups/stories.rb b/vendor/gems/rspec/stories/example_groups/stories.rb new file mode 100644 index 0000000000..e45882a93b --- /dev/null +++ b/vendor/gems/rspec/stories/example_groups/stories.rb @@ -0,0 +1,7 @@ +require File.join(File.dirname(__FILE__), *%w[.. helper]) + +with_steps_for :running_rspec do + Dir["#{File.dirname(__FILE__)}/*"].each do |file| + run file if File.file?(file) && !(file =~ /\.rb$/) + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/stories/helper.rb b/vendor/gems/rspec/stories/helper.rb new file mode 100644 index 0000000000..d9a105e761 --- /dev/null +++ b/vendor/gems/rspec/stories/helper.rb @@ -0,0 +1,6 @@ +$LOAD_PATH.unshift File.expand_path("#{File.dirname(__FILE__)}/../lib") +require 'spec' +require 'tempfile' +require File.join(File.dirname(__FILE__), *%w[resources matchers smart_match]) +require File.join(File.dirname(__FILE__), *%w[resources helpers story_helper]) +require File.join(File.dirname(__FILE__), *%w[resources steps running_rspec]) diff --git a/vendor/gems/rspec/stories/interop/examples_and_tests_together b/vendor/gems/rspec/stories/interop/examples_and_tests_together new file mode 100644 index 0000000000..6583f89c6c --- /dev/null +++ b/vendor/gems/rspec/stories/interop/examples_and_tests_together @@ -0,0 +1,30 @@ +Story: Spec and test together + + As an RSpec adopter with existing Test::Unit tests + I want to run a few specs alongside my existing Test::Unit tests + So that I can experience a smooth, gradual migration path + + Scenario: Run with ruby + Given the file test/spec_and_test_together.rb + + When I run it with the ruby interpreter -fs + + Then the exit code should be 256 + And the stdout should match "ATest" + And the stdout should match "Test::Unit::AssertionFailedError in 'An Example should fail with assert'" + And the stdout should match "'An Example should fail with should' FAILED" + And the stdout should match "10 examples, 6 failures" + And the stdout should match /expected: 40,\s*got: 4/m + And the stdout should match /expected: 50,\s*got: 5/m + Scenario: Run with spec + Given the file test/spec_and_test_together.rb + + When I run it with the spec script -fs + + Then the exit code should be 256 + Ands the stdout should match "ATest" + And the stdout should match "Test::Unit::AssertionFailedError in 'An Example should fail with assert'" + And the stdout should match "'An Example should fail with should' FAILED" + And the stdout should match "10 examples, 6 failures" + And the stdout should match /expected: 40,\s*got: 4/m + And the stdout should match /expected: 50,\s*got: 5/m diff --git a/vendor/gems/rspec/stories/interop/stories.rb b/vendor/gems/rspec/stories/interop/stories.rb new file mode 100644 index 0000000000..e45882a93b --- /dev/null +++ b/vendor/gems/rspec/stories/interop/stories.rb @@ -0,0 +1,7 @@ +require File.join(File.dirname(__FILE__), *%w[.. helper]) + +with_steps_for :running_rspec do + Dir["#{File.dirname(__FILE__)}/*"].each do |file| + run file if File.file?(file) && !(file =~ /\.rb$/) + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/stories/interop/test_case_with_should_methods b/vendor/gems/rspec/stories/interop/test_case_with_should_methods new file mode 100644 index 0000000000..d618c50e26 --- /dev/null +++ b/vendor/gems/rspec/stories/interop/test_case_with_should_methods @@ -0,0 +1,17 @@ +Story: Test::Unit::TestCase extended by rspec with should methods + + As an RSpec adopter with existing Test::Unit tests + I want to use should_* methods in a Test::Unit::TestCase + So that I use RSpec with classes and methods that look more like RSpec examples + + Scenario: Run with ruby + Given the file test/test_case_with_should_methods.rb + When I run it with the ruby interpreter + Then the exit code should be 256 + And the stdout should match "5 examples, 3 failures" + + Scenario: Run with spec + Given the file test/test_case_with_should_methods.rb + When I run it with the spec script + Then the exit code should be 256 + And the stdout should match "5 examples, 3 failures" diff --git a/vendor/gems/rspec/stories/pending_stories/README b/vendor/gems/rspec/stories/pending_stories/README new file mode 100644 index 0000000000..2f9b443142 --- /dev/null +++ b/vendor/gems/rspec/stories/pending_stories/README @@ -0,0 +1,3 @@ +This directory contains stories that are currently not passing +because they are new or they represent regressions. + diff --git a/vendor/gems/rspec/stories/resources/helpers/cmdline.rb b/vendor/gems/rspec/stories/resources/helpers/cmdline.rb new file mode 100644 index 0000000000..ab86bd3e19 --- /dev/null +++ b/vendor/gems/rspec/stories/resources/helpers/cmdline.rb @@ -0,0 +1,9 @@ +$:.push File.join(File.dirname(__FILE__), *%w[.. .. .. lib]) +require 'spec' + +# Uncommenting next line will break the output story (no output!!) +# rspec_options +options = Spec::Runner::OptionParser.parse( + ARGV, $stderr, $stdout +) +Spec::Runner::CommandLine.run(options) diff --git a/vendor/gems/rspec/stories/resources/helpers/story_helper.rb b/vendor/gems/rspec/stories/resources/helpers/story_helper.rb new file mode 100644 index 0000000000..e0aef5b453 --- /dev/null +++ b/vendor/gems/rspec/stories/resources/helpers/story_helper.rb @@ -0,0 +1,16 @@ +require 'spec/story' +require File.dirname(__FILE__) + '/../../../spec/ruby_forker' + +module StoryHelper + include RubyForker + + def spec(args, stderr) + ruby("#{File.dirname(__FILE__) + '/../../../bin/spec'} #{args}", stderr) + end + + def cmdline(args, stderr) + ruby("#{File.dirname(__FILE__) + '/../../resources/helpers/cmdline.rb'} #{args}", stderr) + end + + Spec::Story::World.send :include, self +end diff --git a/vendor/gems/rspec/stories/resources/matchers/smart_match.rb b/vendor/gems/rspec/stories/resources/matchers/smart_match.rb new file mode 100644 index 0000000000..7b1672bc0e --- /dev/null +++ b/vendor/gems/rspec/stories/resources/matchers/smart_match.rb @@ -0,0 +1,37 @@ +module Spec + module Matchers + class SmartMatch + def initialize(expected) + @expected = expected + end + + def matches?(actual) + @actual = actual + # Satisfy expectation here. Return false or raise an error if it's not met. + + if @expected =~ /^\/.*\/?$/ || @expected =~ /^".*"$/ + regex_or_string = eval(@expected) + if Regexp === regex_or_string + (@actual =~ regex_or_string) ? true : false + else + @actual.index(regex_or_string) != nil + end + else + false + end + end + + def failure_message + "expected #{@actual.inspect} to smart_match #{@expected.inspect}, but it didn't" + end + + def negative_failure_message + "expected #{@actual.inspect} not to smart_match #{@expected.inspect}, but it did" + end + end + + def smart_match(expected) + SmartMatch.new(expected) + end + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/stories/resources/spec/example_group_with_should_methods.rb b/vendor/gems/rspec/stories/resources/spec/example_group_with_should_methods.rb new file mode 100644 index 0000000000..4c0505d2a0 --- /dev/null +++ b/vendor/gems/rspec/stories/resources/spec/example_group_with_should_methods.rb @@ -0,0 +1,12 @@ +$:.push File.join(File.dirname(__FILE__), *%w[.. .. .. lib]) +require 'spec' + +class MySpec < Spec::ExampleGroup + def should_pass_with_should + 1.should == 1 + end + + def should_fail_with_should + 1.should == 2 + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/stories/resources/spec/simple_spec.rb b/vendor/gems/rspec/stories/resources/spec/simple_spec.rb new file mode 100644 index 0000000000..2fb67ba49a --- /dev/null +++ b/vendor/gems/rspec/stories/resources/spec/simple_spec.rb @@ -0,0 +1,8 @@ +$:.push File.join(File.dirname(__FILE__), *%w[.. .. .. lib]) +require 'spec' + +describe "Running an Example" do + it "should not output twice" do + true.should be_true + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/stories/resources/steps/running_rspec.rb b/vendor/gems/rspec/stories/resources/steps/running_rspec.rb new file mode 100644 index 0000000000..496847fe4d --- /dev/null +++ b/vendor/gems/rspec/stories/resources/steps/running_rspec.rb @@ -0,0 +1,50 @@ +steps_for :running_rspec do + + Given("the file $relative_path") do |relative_path| + @path = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "resources", relative_path)) + unless File.exist?(@path) + raise "could not find file at #{@path}" + end + end + + When("I run it with the $interpreter") do |interpreter| + stderr_file = Tempfile.new('rspec') + stderr_file.close + @stdout = case(interpreter) + when /^ruby interpreter/ + args = interpreter.gsub('ruby interpreter','') + ruby("#{@path}#{args}", stderr_file.path) + when /^spec script/ + args = interpreter.gsub('spec script','') + spec("#{@path}#{args}", stderr_file.path) + when 'CommandLine object' then cmdline(@path, stderr_file.path) + else raise "Unknown interpreter: #{interpreter}" + end + @stderr = IO.read(stderr_file.path) + @exit_code = $?.to_i + end + + Then("the exit code should be $exit_code") do |exit_code| + if @exit_code != exit_code.to_i + raise "Did not exit with #{exit_code}, but with #{@exit_code}. Standard error:\n#{@stderr}" + end + end + + Then("the $stream should match $regex") do |stream, string_or_regex| + written = case(stream) + when 'stdout' then @stdout + when 'stderr' then @stderr + else raise "Unknown stream: #{stream}" + end + written.should smart_match(string_or_regex) + end + + Then("the $stream should not match $regex") do |stream, string_or_regex| + written = case(stream) + when 'stdout' then @stdout + when 'stderr' then @stderr + else raise "Unknown stream: #{stream}" + end + written.should_not smart_match(string_or_regex) + end +end diff --git a/vendor/gems/rspec/stories/resources/stories/failing_story.rb b/vendor/gems/rspec/stories/resources/stories/failing_story.rb new file mode 100644 index 0000000000..cc9506c660 --- /dev/null +++ b/vendor/gems/rspec/stories/resources/stories/failing_story.rb @@ -0,0 +1,15 @@ +$:.push File.join(File.dirname(__FILE__), *%w[.. .. .. lib]) + +require 'spec/story' + +Story "Failing story", +%(As an RSpec user + I want a failing test + So that I can observe the output) do + + Scenario "Failing scenario" do + Then "true should be false" do + true.should == false + end + end +end diff --git a/vendor/gems/rspec/stories/resources/test/spec_and_test_together.rb b/vendor/gems/rspec/stories/resources/test/spec_and_test_together.rb new file mode 100644 index 0000000000..eb2b4e0740 --- /dev/null +++ b/vendor/gems/rspec/stories/resources/test/spec_and_test_together.rb @@ -0,0 +1,57 @@ +$:.push File.join(File.dirname(__FILE__), *%w[.. .. .. lib]) +require 'spec' +# TODO - this should not be necessary, ay? +require 'spec/interop/test' + +describe "An Example" do + it "should pass with assert" do + assert true + end + + it "should fail with assert" do + assert false + end + + it "should pass with should" do + 1.should == 1 + end + + it "should fail with should" do + 1.should == 2 + end +end + +class ATest < Test::Unit::TestCase + def test_should_pass_with_assert + assert true + end + + def test_should_fail_with_assert + assert false + end + + def test_should_pass_with_should + 1.should == 1 + end + + def test_should_fail_with_should + 1.should == 2 + end + + def setup + @from_setup ||= 3 + @from_setup += 1 + end + + def test_should_fail_with_setup_method_variable + @from_setup.should == 40 + end + + before do + @from_before = @from_setup + 1 + end + + def test_should_fail_with_before_block_variable + @from_before.should == 50 + end +end \ No newline at end of file diff --git a/vendor/gems/rspec/stories/resources/test/test_case_with_should_methods.rb b/vendor/gems/rspec/stories/resources/test/test_case_with_should_methods.rb new file mode 100644 index 0000000000..3912429e3f --- /dev/null +++ b/vendor/gems/rspec/stories/resources/test/test_case_with_should_methods.rb @@ -0,0 +1,30 @@ +$:.push File.join(File.dirname(__FILE__), *%w[.. .. .. lib]) +require 'test/unit' +require 'spec' +require 'spec/interop/test' + +class MySpec < Test::Unit::TestCase + def should_pass_with_should + 1.should == 1 + end + + def should_fail_with_should + 1.should == 2 + end + + def should_pass_with_assert + assert true + end + + def should_fail_with_assert + assert false + end + + def test + raise "This is not a real test" + end + + def test_ify + raise "This is a real test" + end +end \ No newline at end of file From bd3b316dff14085ea633f95626b033147c146be7 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Sun, 17 Feb 2008 17:00:39 -0600 Subject: [PATCH 0169/3753] removing .swp file --- spec/unit/.facter.rb.swp | Bin 32768 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 spec/unit/.facter.rb.swp diff --git a/spec/unit/.facter.rb.swp b/spec/unit/.facter.rb.swp deleted file mode 100644 index 27a01307338692eb90f94ae0f1d7fd60df1d96de..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32768 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 From d449472914bbb34ab929d5f78cb4c450ade0099a Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Tue, 19 Feb 2008 15:20:02 +1100 Subject: [PATCH 0170/3753] Updated CHANGELOG --- CHANGELOG | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index b9c653e940..2cf22ed7e6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,23 @@ ?: - Fixed ticket #48 - operatingsystemrelease for CentOS + A netmask fact has been added closing ticket #46. It only returns the + netmask of the primary interface (in the same behaviour as the ipaddress + and macaddress facts). + + Facts to return multiple interfaces on a host have also been updated. + If you have multiple interfaces on Linux, *BSD, or Solaris/SunOS you will + now get facts for each interface's IP address, MAC address and netmask. + The facts will be structured like: + ipaddress_int = 10.0.0.x + macaddress_int = xx:xx:xx:xx + netmask_int = 255.255.255.0 + + Facter now identifies Ubuntu hosts and their releases using the + operatingsystem and operatingsystemrelease facts. + + The Debian operatingsystemrelease fact now correctly returns the current + Debian release. + + Fixed ticket #48 - ioperatingsystem and operatingsystemrelease for CentOS Fixed ticket #44 and allowed support for Xen multiple interfaces and aliased interfaces. Supports both Linux and *BSD. From 567549bc918efbd06ecb2a181df461e99e92b630 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Thu, 20 Mar 2008 12:12:27 +1100 Subject: [PATCH 0171/3753] Closes #1145 - fixed bad interface names by replacing : with _ --- lib/facter/ipmess.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/facter/ipmess.rb b/lib/facter/ipmess.rb index 78788743b7..051d4dd653 100644 --- a/lib/facter/ipmess.rb +++ b/lib/facter/ipmess.rb @@ -29,7 +29,9 @@ Facter::IPAddress.get_interfaces.each do |interface| -Facter.add("ipaddress_" + interface) do +mi = interface.gsub(':', '_') + +Facter.add("ipaddress_" + mi) do confine :kernel => [ :sunos, :freebsd, :openbsd, :netbsd, :linux ] setcode do label = 'ipaddress' @@ -37,7 +39,7 @@ end end -Facter.add("macaddress_" + interface) do +Facter.add("macaddress_" + mi) do confine :kernel => [ :sunos, :freebsd, :openbsd, :netbsd, :linux ] setcode do label = 'macaddress' @@ -45,7 +47,7 @@ end end -Facter.add("netmask_" + interface) do +Facter.add("netmask_" + mi) do confine :kernel => [ :sunos, :freebsd, :openbsd, :netbsd, :linux ] setcode do label = 'netmask' From fef9b7d958f1ac6cba80a5ae97e6c96703e15595 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Tue, 13 May 2008 20:48:52 -0500 Subject: [PATCH 0172/3753] fixing whitespace --- lib/facter/ipmess.rb | 59 ++++++++++++++++++++++---------------------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/lib/facter/ipmess.rb b/lib/facter/ipmess.rb index 051d4dd653..1e0c92361a 100644 --- a/lib/facter/ipmess.rb +++ b/lib/facter/ipmess.rb @@ -20,39 +20,38 @@ require 'facter/util/ip' Facter.add(:interfaces) do - confine :kernel => [ :sunos, :freebsd, :openbsd, :netbsd, :linux ] - setcode do - Facter::IPAddress.get_interfaces.join(",") - end + confine :kernel => [ :sunos, :freebsd, :openbsd, :netbsd, :linux ] + setcode do + Facter::IPAddress.get_interfaces.join(",") + end end Facter::IPAddress.get_interfaces.each do |interface| - -mi = interface.gsub(':', '_') - -Facter.add("ipaddress_" + mi) do - confine :kernel => [ :sunos, :freebsd, :openbsd, :netbsd, :linux ] - setcode do - label = 'ipaddress' - Facter::IPAddress.get_interface_value(interface, label) - end -end - -Facter.add("macaddress_" + mi) do - confine :kernel => [ :sunos, :freebsd, :openbsd, :netbsd, :linux ] - setcode do - label = 'macaddress' - Facter::IPAddress.get_interface_value(interface, label) - end -end - -Facter.add("netmask_" + mi) do - confine :kernel => [ :sunos, :freebsd, :openbsd, :netbsd, :linux ] - setcode do - label = 'netmask' - Facter::IPAddress.get_interface_value(interface, label) - end -end + mi = interface.gsub(':', '_') + + Facter.add("ipaddress_" + mi) do + confine :kernel => [ :sunos, :freebsd, :openbsd, :netbsd, :linux ] + setcode do + label = 'ipaddress' + Facter::IPAddress.get_interface_value(interface, label) + end + end + + Facter.add("macaddress_" + mi) do + confine :kernel => [ :sunos, :freebsd, :openbsd, :netbsd, :linux ] + setcode do + label = 'macaddress' + Facter::IPAddress.get_interface_value(interface, label) + end + end + + Facter.add("netmask_" + mi) do + confine :kernel => [ :sunos, :freebsd, :openbsd, :netbsd, :linux ] + setcode do + label = 'netmask' + Facter::IPAddress.get_interface_value(interface, label) + end + end end From 4f39ec8dd205ecf0a3ad90c950c86046da3f454e Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Tue, 13 May 2008 20:49:04 -0500 Subject: [PATCH 0173/3753] Adding autotest hooks --- autotest/discover.rb | 9 +++++ autotest/facter_rspec.rb | 55 +++++++++++++++++++++++++++++ autotest/rspec.rb | 74 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 138 insertions(+) create mode 100644 autotest/discover.rb create mode 100644 autotest/facter_rspec.rb create mode 100644 autotest/rspec.rb diff --git a/autotest/discover.rb b/autotest/discover.rb new file mode 100644 index 0000000000..7cf09163a2 --- /dev/null +++ b/autotest/discover.rb @@ -0,0 +1,9 @@ +require 'autotest' + +Autotest.add_discovery do + "rspec" +end + +Autotest.add_discovery do + "facter" +end diff --git a/autotest/facter_rspec.rb b/autotest/facter_rspec.rb new file mode 100644 index 0000000000..bd057a8e75 --- /dev/null +++ b/autotest/facter_rspec.rb @@ -0,0 +1,55 @@ +require 'autotest' +require 'autotest/rspec' + +Autotest.add_hook :initialize do |at| + at.clear_mappings + + # the libraries under lib/facter + at.add_mapping(%r%^lib/facter/(.*)\.rb$%) { |filename, m| + at.files_matching %r!spec/(unit|integration)/#{m[1]}.rb! + } + + # the actual spec files themselves + at.add_mapping(%r%^spec/(unit|integration)/.*\.rb$%) { |filename, _| + filename + } + + # force a complete re-run for all of these: + + # main facter lib + at.add_mapping(%r!^lib/facter\.rb$!) { |filename, _| + at.files_matching %r!spec/(unit|integration)/.*\.rb! + } + + # the spec_helper + at.add_mapping(%r!^spec/spec_helper\.rb$!) { |filename, _| + at.files_matching %r!spec/(unit|integration)/.*\.rb! + } + + # the facter spec libraries + at.add_mapping(%r!^spec/lib/spec.*!) { |filename, _| + at.files_matching %r!spec/(unit|integration)/.*\.rb! + } + + # the monkey patches for rspec + at.add_mapping(%r!^spec/lib/monkey_patches/.*!) { |filename, _| + at.files_matching %r!spec/(unit|integration)/.*\.rb! + } +end + +class Autotest::FacterRspec < Autotest::Rspec + # Autotest will look for spec commands in the following + # locations, in this order: + # + # * bin/spec + # * default spec bin/loader installed in Rubygems + # * our local vendor/gems/rspec/bin/spec + def spec_commands + [ + File.join('vendor', 'gems', 'rspec', 'bin', 'spec') , + File.join('bin', 'spec'), + File.join(Config::CONFIG['bindir'], 'spec') + ] + end + +end diff --git a/autotest/rspec.rb b/autotest/rspec.rb new file mode 100644 index 0000000000..ebafbfe915 --- /dev/null +++ b/autotest/rspec.rb @@ -0,0 +1,74 @@ +require 'autotest' + +Autotest.add_hook :initialize do |at| + at.clear_mappings + # watch out: Ruby bug (1.8.6): + # %r(/) != /\// + at.add_mapping(%r%^spec/.*\.rb$%) { |filename, _| + filename + } + at.add_mapping(%r%^lib/(.*)\.rb$%) { |_, m| + ["spec/#{m[1]}_spec.rb"] + } + at.add_mapping(%r%^spec/(spec_helper|shared/.*)\.rb$%) { + at.files_matching %r{^spec/.*_spec\.rb$} + } +end + +class RspecCommandError < StandardError; end + +class Autotest::Rspec < Autotest + + def initialize + super + + self.failed_results_re = /^\d+\)\n(?:\e\[\d*m)?(?:.*?Error in )?'([^\n]*)'(?: FAILED)?(?:\e\[\d*m)?\n(.*?)\n\n/m + self.completed_re = /\Z/ # FIX: some sort of summary line at the end? + end + + def consolidate_failures(failed) + filters = Hash.new { |h,k| h[k] = [] } + failed.each do |spec, failed_trace| + if f = test_files_for(failed).find { |f| failed_trace =~ Regexp.new(f) } then + filters[f] << spec + break + end + end + return filters + end + + def make_test_cmd(files_to_test) + return "#{ruby} -S #{spec_command} #{add_options_if_present} #{files_to_test.keys.flatten.join(' ')}" + end + + def add_options_if_present + File.exist?("spec/spec.opts") ? "-O spec/spec.opts " : "" + end + + # Finds the proper spec command to use. Precendence is set in the + # lazily-evaluated method spec_commands. Alias + Override that in + # ~/.autotest to provide a different spec command then the default + # paths provided. + def spec_command(separator=File::ALT_SEPARATOR) + unless defined? @spec_command then + @spec_command = spec_commands.find { |cmd| File.exists? cmd } + + raise RspecCommandError, "No spec command could be found!" unless @spec_command + + @spec_command.gsub! File::SEPARATOR, separator if separator + end + @spec_command + end + + # Autotest will look for spec commands in the following + # locations, in this order: + # + # * bin/spec + # * default spec bin/loader installed in Rubygems + def spec_commands + [ + File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'bin', 'spec')), + File.join(Config::CONFIG['bindir'], 'spec') + ] + end +end From c5492c2b7c539fe6f6a33b7b31a59d931808658e Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Tue, 13 May 2008 21:22:20 -0500 Subject: [PATCH 0174/3753] Splitting the different classes in Facter up, and adding some tests. The Confine and Resolution classes are now in separate files, and I've got tests for Confine. --- lib/facter.rb | 205 +----------------------------------- lib/facter/confine.rb | 53 ++++++++++ lib/facter/resolution.rb | 154 +++++++++++++++++++++++++++ spec/unit/facter.rb | 36 +++---- spec/unit/facter/confine.rb | 75 +++++++++++++ 5 files changed, 297 insertions(+), 226 deletions(-) create mode 100644 lib/facter/confine.rb create mode 100644 lib/facter/resolution.rb create mode 100755 spec/unit/facter/confine.rb diff --git a/lib/facter.rb b/lib/facter.rb index 65ea57b444..675b95dfea 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -18,6 +18,8 @@ #-- class Facter + require 'facter/resolution' + include Comparable include Enumerable @@ -376,209 +378,6 @@ def value end end - # An actual fact resolution mechanism. These are largely just chunks of - # code, with optional confinements restricting the mechanisms to only working on - # specific systems. Note that the confinements are always ANDed, so any - # confinements specified must all be true for the resolution to be - # suitable. - class Resolution - attr_accessor :interpreter, :code, :name, :fact - - def Resolution.have_which - if @have_which.nil? - %x{which which 2>/dev/null} - @have_which = ($? == 0) - end - @have_which - end - - # Execute a chunk of code. - def Resolution.exec(code, interpreter = "/bin/sh") - if interpreter == "/bin/sh" - binary = code.split(/\s+/).shift - - if have_which - path = nil - if binary !~ /^\// - path = %x{which #{binary} 2>/dev/null}.chomp - if path == "" - # we don't have the binary necessary - return nil - end - else - path = binary - end - - unless FileTest.exists?(path) - # our binary does not exist - return nil - end - end - - out = nil - begin - out = %x{#{code}}.chomp - rescue => detail - $stderr.puts detail - return nil - end - if out == "" - return nil - else - return out - end - else - raise ArgumentError, - "non-sh interpreters are not currently supported" - end - end - - # Add a new confine to the resolution mechanism. - def confine(*args) - if args[0].is_a? Hash - args[0].each do |fact, values| - @confines.push Confine.new(fact,*values) - end - else - fact = args.shift - @confines.push Confine.new(fact,*args) - end - end - - # Create a new resolution mechanism. - def initialize(name) - @name = name - @confines = [] - @value = nil - end - - # Return the number of confines. - def length - @confines.length - end - - # Set our code for returning a value. - def setcode(string = nil, interp = nil, &block) - if string - @code = string - @interpreter = interp || "/bin/sh" - else - unless block_given? - raise ArgumentError, "You must pass either code or a block" - end - @code = block - end - end - - # Set the name by which this parameter is known in LDAP. The default - # is just the fact name. - def setldapname(name) - @fact.ldapname = name.to_s - end - - # Is this resolution mechanism suitable on the system in question? - def suitable? - unless defined? @suitable - @suitable = true - if @confines.length == 0 - return true - end - @confines.each { |confine| - unless confine.true? - @suitable = false - end - } - end - - return @suitable - end - - # Set tags on our parent fact. - def tag(*values) - @fact.tag(*values) - end - - def to_s - return self.value() - end - - # How we get a value for our resolution mechanism. - def value - value = nil - - if @code.is_a?(Proc) - value = @code.call() - else - unless defined? @interpreter - @interpreter = "/bin/sh" - end - if @code.nil? - $stderr.puts "Code for %s is nil" % @name - else - value = Resolution.exec(@code,@interpreter) - end - end - - if value == "" - value = nil - end - - return value - end - - end - - # A restricting tag for fact resolution mechanisms. The tag must be true - # for the resolution mechanism to be suitable. - class Confine - attr_accessor :fact, :op, :value - - # Add the tag. Requires the fact name, an operator, and the value - # we're comparing to. - def initialize(fact, *values) - fact = fact.to_s if fact.is_a? Symbol - @fact = fact - @values = values.collect do |value| - if value.is_a? String - value - else - value.to_s - end - end - end - - def to_s - return "'%s' '%s'" % [@fact, @values.join(",")] - end - - # Evaluate the fact, returning true or false. - def true? - fact = nil - unless fact = Facter[@fact] - Facter.debug "No fact for %s" % @fact - return false - end - value = fact.value - - if value.nil? - return false - end - - retval = @values.find { |v| - if value.downcase == v.downcase - break true - end - } - - if retval - retval = true - else - retval = false - end - - return retval || false - end - end # Load all of the default facts def self.loadfacts diff --git a/lib/facter/confine.rb b/lib/facter/confine.rb new file mode 100644 index 0000000000..4d306300fa --- /dev/null +++ b/lib/facter/confine.rb @@ -0,0 +1,53 @@ +# A restricting tag for fact resolution mechanisms. The tag must be true +# for the resolution mechanism to be suitable. +class Facter::Confine + attr_accessor :fact, :values + + # Add the restriction. Requires the fact name, an operator, and the value + # we're comparing to. + def initialize(fact, *values) + raise ArgumentError, "The fact name must be provided" unless fact + raise ArgumentError, "One or more values must be provided" if values.empty? + fact = fact.to_s if fact.is_a? Symbol + @fact = fact + @values = values.collect do |value| + if value.is_a? String + value + else + value.to_s + end + end + end + + def to_s + return "'%s' '%s'" % [@fact, @values.join(",")] + end + + # Evaluate the fact, returning true or false. + def true? + fact = nil + unless fact = Facter[@fact] + Facter.debug "No fact for %s" % @fact + return false + end + value = fact.value + + if value.nil? + return false + end + + retval = @values.find { |v| + if value.downcase == v.downcase + break true + end + } + + if retval + retval = true + else + retval = false + end + + return retval || false + end +end diff --git a/lib/facter/resolution.rb b/lib/facter/resolution.rb new file mode 100644 index 0000000000..77d9155cac --- /dev/null +++ b/lib/facter/resolution.rb @@ -0,0 +1,154 @@ +# An actual fact resolution mechanism. These are largely just chunks of +# code, with optional confinements restricting the mechanisms to only working on +# specific systems. Note that the confinements are always ANDed, so any +# confinements specified must all be true for the resolution to be +# suitable. +require 'facter/confine' + +class Facter::Resolution + attr_accessor :interpreter, :code, :name, :fact + + def self.have_which + if @have_which.nil? + %x{which which 2>/dev/null} + @have_which = ($? == 0) + end + @have_which + end + + # Execute a chunk of code. + def self.exec(code, interpreter = "/bin/sh") + if interpreter == "/bin/sh" + binary = code.split(/\s+/).shift + + if have_which + path = nil + if binary !~ /^\// + path = %x{which #{binary} 2>/dev/null}.chomp + if path == "" + # we don't have the binary necessary + return nil + end + else + path = binary + end + + unless FileTest.exists?(path) + # our binary does not exist + return nil + end + end + + out = nil + begin + out = %x{#{code}}.chomp + rescue => detail + $stderr.puts detail + return nil + end + if out == "" + return nil + else + return out + end + else + raise ArgumentError, + "non-sh interpreters are not currently supported" + end + end + + # Add a new confine to the resolution mechanism. + def confine(*args) + if args[0].is_a? Hash + args[0].each do |fact, values| + @confines.push Facter::Confine.new(fact,*values) + end + else + fact = args.shift + @confines.push Facter::Confine.new(fact,*args) + end + end + + # Create a new resolution mechanism. + def initialize(name) + @name = name + @confines = [] + @value = nil + end + + # Return the number of confines. + def length + @confines.length + end + + # Set our code for returning a value. + def setcode(string = nil, interp = nil, &block) + if string + @code = string + @interpreter = interp || "/bin/sh" + else + unless block_given? + raise ArgumentError, "You must pass either code or a block" + end + @code = block + end + end + + # Set the name by which this parameter is known in LDAP. The default + # is just the fact name. + def setldapname(name) + @fact.ldapname = name.to_s + end + + # Is this resolution mechanism suitable on the system in question? + def suitable? + unless defined? @suitable + @suitable = true + if @confines.length == 0 + return true + end + @confines.each { |confine| + unless confine.true? + @suitable = false + end + } + end + + return @suitable + end + + # Set tags on our parent fact. + def tag(*values) + @fact.tag(*values) + end + + def to_s + return self.value() + end + + # How we get a value for our resolution mechanism. + def value + value = nil + + if @code.is_a?(Proc) + value = @code.call() + else + unless defined? @interpreter + @interpreter = "/bin/sh" + end + if @code.nil? + $stderr.puts "Code for %s is nil" % @name + else + value = Facter::Resolution.exec(@code,@interpreter) + end + end + + if value == "" + value = nil + end + + return value + end + +end + diff --git a/spec/unit/facter.rb b/spec/unit/facter.rb index 76b6ab20cc..8ca160cdb7 100755 --- a/spec/unit/facter.rb +++ b/spec/unit/facter.rb @@ -2,10 +2,6 @@ require File.dirname(__FILE__) + '/../spec_helper' -#if __FILE__ == $0 -# Facter.debugging(true) -#end - describe Facter do def tearhook(&block) @tearhooks << block @@ -28,33 +24,27 @@ def teardown end end end - - def test_version - #Could match /[0-9.]+/ - #Strict match: /^[0-9]+(\.[0-9]+)*$/ - #ok: 1.0.0 1.0 1 - #notok: 1..0 1. .1 1a - assert(Facter.version =~ /^[0-9]+(\.[0-9]+)*$/ ) + + it "should have a version" do + Facter.version.should =~ /^[0-9]+(\.[0-9]+)*$/ end - def test_noconfines_sh - assert_nothing_raised { - Facter.add("testing") do + describe "when provided code as a string" do + it "should execute the code in the shell" do + Facter.add("shell_testing") do setcode "echo yup" end - } - assert_equal("yup", Facter["testing"].value) + Facter["shell_testing"].value.should == "yup" + end end - def test_noconfines - assert_nothing_raised { - Facter.add("testing") do - setcode { "foo" } - end - } + describe "when passed code as a block" do + it "should execute the provided block" do + Facter.add("block_testing") { setcode { "foo" } } - assert_equal("foo", Facter["testing"].value) + Facter["block_testing"].value.should == "foo" + end end def test_onetrueconfine diff --git a/spec/unit/facter/confine.rb b/spec/unit/facter/confine.rb new file mode 100755 index 0000000000..385f7c6560 --- /dev/null +++ b/spec/unit/facter/confine.rb @@ -0,0 +1,75 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'facter/confine' + +describe Facter::Confine do + it "should require a fact name" do + Facter::Confine.new("yay", true).fact.should == "yay" + end + + it "should accept a value specified individually" do + Facter::Confine.new("yay", "test").values.should == ["test"] + end + + it "should accept multiple values specified at once" do + Facter::Confine.new("yay", "test", "other").values.should == ["test", "other"] + end + + it "should convert all values to strings" do + Facter::Confine.new("yay", :test).values.should == %w{test} + end + + it "should fail if no fact name is provided" do + lambda { Facter::Confine.new(nil, :test) }.should raise_error(ArgumentError) + end + + it "should fail if no values were provided" do + lambda { Facter::Confine.new("yay") }.should raise_error(ArgumentError) + end + + it "should have a method for testing whether it matches" do + Facter::Confine.new("yay", :test).should respond_to(:true?) + end + + describe "when evaluating" do + before do + @confine = Facter::Confine.new("yay", "one", "two") + @fact = mock 'fact' + Facter.stubs(:[]).returns @fact + end + + it "should return false if the fact does not exist" do + Facter.expects(:[]).with("yay").returns nil + + @confine.true?.should be_false + end + + it "should use the returned fact to get the value" do + Facter.expects(:[]).with("yay").returns @fact + + @fact.expects(:value).returns nil + + @confine.true? + end + + it "should return false if the fact has no value" do + @fact.stubs(:value).returns nil + + @confine.true?.should be_false + end + + it "should return true if any of the provided values matches the fact's value" do + @fact.stubs(:value).returns "two" + + @confine.true?.should be_true + end + + it "should return false if none of the provided values matches the fact's value" do + @fact.stubs(:value).returns "three" + + @confine.true?.should be_false + end + end +end From b8de4e4af43c9850210e16ddbf4650feb194246e Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Tue, 13 May 2008 21:23:22 -0500 Subject: [PATCH 0175/3753] Simplifying Confine a bit --- lib/facter/confine.rb | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/lib/facter/confine.rb b/lib/facter/confine.rb index 4d306300fa..529c9681d7 100644 --- a/lib/facter/confine.rb +++ b/lib/facter/confine.rb @@ -25,29 +25,17 @@ def to_s # Evaluate the fact, returning true or false. def true? - fact = nil unless fact = Facter[@fact] Facter.debug "No fact for %s" % @fact return false end value = fact.value - if value.nil? - return false - end + return false if value.nil? - retval = @values.find { |v| - if value.downcase == v.downcase - break true - end + @values.each { |v| + return true if value.downcase == v.downcase } - - if retval - retval = true - else - retval = false - end - - return retval || false + return false end end From 8971979d4870170d2720275980a64d18ac7bd6bf Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Tue, 13 May 2008 21:28:34 -0500 Subject: [PATCH 0176/3753] Reorganizing my new tests so they match the autotest discovery. --- lib/facter/resolution.rb | 2 -- spec/unit/{facter => }/confine.rb | 2 +- spec/unit/resolution.rb | 8 ++++++++ 3 files changed, 9 insertions(+), 3 deletions(-) rename spec/unit/{facter => }/confine.rb (97%) create mode 100755 spec/unit/resolution.rb diff --git a/lib/facter/resolution.rb b/lib/facter/resolution.rb index 77d9155cac..35a931b5fb 100644 --- a/lib/facter/resolution.rb +++ b/lib/facter/resolution.rb @@ -149,6 +149,4 @@ def value return value end - end - diff --git a/spec/unit/facter/confine.rb b/spec/unit/confine.rb similarity index 97% rename from spec/unit/facter/confine.rb rename to spec/unit/confine.rb index 385f7c6560..ae122eafc7 100755 --- a/spec/unit/facter/confine.rb +++ b/spec/unit/confine.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../../spec_helper' +require File.dirname(__FILE__) + '/../spec_helper' require 'facter/confine' diff --git a/spec/unit/resolution.rb b/spec/unit/resolution.rb new file mode 100755 index 0000000000..9cb071252d --- /dev/null +++ b/spec/unit/resolution.rb @@ -0,0 +1,8 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../spec_helper' + +require 'facter/resolution' + +describe Facter::Resolution do +end From 121d2911daac533d47bb5c54b042a847f8f36bd3 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Tue, 13 May 2008 22:07:18 -0500 Subject: [PATCH 0177/3753] Adding all of the tests for the Facter::Resolution class. --- lib/facter/resolution.rb | 105 +++++++--------------- spec/unit/resolution.rb | 184 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 217 insertions(+), 72 deletions(-) diff --git a/lib/facter/resolution.rb b/lib/facter/resolution.rb index 35a931b5fb..7fc1e248c0 100644 --- a/lib/facter/resolution.rb +++ b/lib/facter/resolution.rb @@ -18,54 +18,40 @@ def self.have_which # Execute a chunk of code. def self.exec(code, interpreter = "/bin/sh") - if interpreter == "/bin/sh" - binary = code.split(/\s+/).shift - - if have_which - path = nil - if binary !~ /^\// - path = %x{which #{binary} 2>/dev/null}.chomp - if path == "" - # we don't have the binary necessary - return nil - end - else - path = binary - end - - unless FileTest.exists?(path) - # our binary does not exist - return nil - end - end - - out = nil - begin - out = %x{#{code}}.chomp - rescue => detail - $stderr.puts detail - return nil - end - if out == "" - return nil + raise ArgumentError, "non-sh interpreters are not currently supported" unless interpreter == "/bin/sh" + binary = code.split(/\s+/).shift + + if have_which + path = nil + if binary !~ /^\// + path = %x{which #{binary} 2>/dev/null}.chomp + # we don't have the binary necessary + return nil if path == "" else - return out + path = binary end + + return nil unless FileTest.exists?(path) + end + + out = nil + begin + out = %x{#{code}}.chomp + rescue => detail + $stderr.puts detail + return nil + end + if out == "" + return nil else - raise ArgumentError, - "non-sh interpreters are not currently supported" + return out end end # Add a new confine to the resolution mechanism. - def confine(*args) - if args[0].is_a? Hash - args[0].each do |fact, values| - @confines.push Facter::Confine.new(fact,*values) - end - else - fact = args.shift - @confines.push Facter::Confine.new(fact,*args) + def confine(confines) + confines.each do |fact, values| + @confines.push Facter::Confine.new(fact, *values) end end @@ -97,56 +83,31 @@ def setcode(string = nil, interp = nil, &block) # Set the name by which this parameter is known in LDAP. The default # is just the fact name. def setldapname(name) - @fact.ldapname = name.to_s + fact.ldapname = name.to_s end # Is this resolution mechanism suitable on the system in question? def suitable? unless defined? @suitable - @suitable = true - if @confines.length == 0 - return true - end - @confines.each { |confine| - unless confine.true? - @suitable = false - end - } + @suitable = ! @confines.detect { |confine| ! confine.true? } end return @suitable end - # Set tags on our parent fact. - def tag(*values) - @fact.tag(*values) - end - def to_s return self.value() end # How we get a value for our resolution mechanism. def value - value = nil - if @code.is_a?(Proc) - value = @code.call() + result = @code.call() else - unless defined? @interpreter - @interpreter = "/bin/sh" - end - if @code.nil? - $stderr.puts "Code for %s is nil" % @name - else - value = Facter::Resolution.exec(@code,@interpreter) - end - end - - if value == "" - value = nil + result = Facter::Resolution.exec(@code,@interpreter) end - return value + return nil if result == "" + return result end end diff --git a/spec/unit/resolution.rb b/spec/unit/resolution.rb index 9cb071252d..d4dc7e1d98 100755 --- a/spec/unit/resolution.rb +++ b/spec/unit/resolution.rb @@ -5,4 +5,188 @@ require 'facter/resolution' describe Facter::Resolution do + it "should require a name" do + lambda { Facter::Resolution.new }.should raise_error(ArgumentError) + end + + it "should have a name" do + Facter::Resolution.new("yay").name.should == "yay" + end + + it "should have a method for setting the code" do + Facter::Resolution.new("yay").should respond_to(:setcode) + end + + describe "when setting the code" do + before do + @resolve = Facter::Resolution.new("yay") + end + + it "should default to /bin/sh as the interpreter if a string is provided" do + @resolve.setcode "foo" + @resolve.interpreter.should == "/bin/sh" + end + + it "should set the code to any provided string" do + @resolve.setcode "foo" + @resolve.code.should == "foo" + end + + it "should set the code to any provided block" do + block = lambda { } + @resolve.setcode(&block) + @resolve.code.should equal(block) + end + + it "should prefer the string over a block" do + @resolve.setcode("foo") { } + @resolve.code.should == "foo" + end + + it "should fail if neither a string nor block has been provided" do + lambda { @resolve.setcode }.should raise_error(ArgumentError) + end + end + + it "should be able to return a value" do + Facter::Resolution.new("yay").should respond_to(:value) + end + + describe "when returning the value" do + before do + @resolve = Facter::Resolution.new("yay") + end + + describe "and the code is a string" do + it "should return the result of executing the code with the interpreter" do + @resolve.setcode "/bin/foo" + Facter::Resolution.expects(:exec).with("/bin/foo", "/bin/sh").returns "yup" + + @resolve.value.should == "yup" + end + + it "should return nil if the value is an empty string" do + @resolve.setcode "/bin/foo" + Facter::Resolution.stubs(:exec).returns "" + @resolve.value.should be_nil + end + end + + describe "and the code is a block" do + it "should return the value returned by the block" do + @resolve.setcode { "yayness" } + @resolve.value.should == "yayness" + end + + it "should return nil if the value is an empty string" do + @resolve.setcode { "" } + @resolve.value.should be_nil + end + end + end + + it "should return its value when converted to a string" do + @resolve = Facter::Resolution.new("yay") + @resolve.expects(:value).returns "myval" + @resolve.to_s.should == "myval" + end + + it "should allow the adding of confines" do + Facter::Resolution.new("yay").should respond_to(:confine) + end + + it "should provide a method for returning the number of confines" do + @resolve = Facter::Resolution.new("yay") + @resolve.confine "one" => "foo", "two" => "fee" + @resolve.length.should == 2 + end + + it "should return 0 confines when no confines have been added" do + Facter::Resolution.new("yay").length.should == 0 + end + + it "should have a method for determining if it is suitable" do + Facter::Resolution.new("yay").should respond_to(:suitable?) + end + + describe "when adding confines" do + before do + @resolve = Facter::Resolution.new("yay") + end + + it "should accept a hash of fact names and values" do + lambda { @resolve.confine :one => "two" }.should_not raise_error + end + + it "should create a Confine instance for every argument in the provided hash" do + Facter::Confine.expects(:new).with("one", "foo") + Facter::Confine.expects(:new).with("two", "fee") + + @resolve.confine "one" => "foo", "two" => "fee" + end + + end + + describe "when determining suitability" do + before do + @resolve = Facter::Resolution.new("yay") + end + + it "should always be suitable if no confines have been added" do + @resolve.should be_suitable + end + + it "should be unsuitable if any provided confines return false" do + confine1 = mock 'confine1', :true? => true + confine2 = mock 'confine2', :true? => false + Facter::Confine.expects(:new).times(2).returns(confine1).then.returns(confine2) + @resolve.confine :one => :two, :three => :four + + @resolve.should_not be_suitable + end + + it "should be suitable if all provided confines return true" do + confine1 = mock 'confine1', :true? => true + confine2 = mock 'confine2', :true? => true + Facter::Confine.expects(:new).times(2).returns(confine1).then.returns(confine2) + @resolve.confine :one => :two, :three => :four + + @resolve.should be_suitable + end + end + + it "should have a method for setting its ldap name" do + Facter::Resolution.new("yay").should respond_to(:setldapname) + end + + it "should set the ldap name on the associated fact" do + @resolve = Facter::Resolution.new("yay") + fact = mock 'fact' + @resolve.stubs(:fact).returns fact + + fact.expects(:ldapname=).with "ldapness" + + @resolve.setldapname "ldapness" + end + + it "should always convert the ldap name to a string" do + @resolve = Facter::Resolution.new("yay") + fact = mock 'fact' + @resolve.stubs(:fact).returns fact + + fact.expects(:ldapname=).with "ldapness" + + @resolve.setldapname :ldapness + end + + it "should have a class method for executing code" do + Facter::Resolution.should respond_to(:exec) + end + + # It's not possible, AFAICT, to mock %x{}, so I can't really test this bit. + describe "when executing code" do + it "should fail if any interpreter other than /bin/sh is requested" do + lambda { Facter::Resolution.exec("/something", "/bin/perl") }.should raise_error(ArgumentError) + end + end end From e3c1fdab9e52e05c8983123879c8ed98743fc8f8 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Tue, 13 May 2008 23:49:48 -0500 Subject: [PATCH 0178/3753] Splitting the instance code into a Fact class. This allows the Facter class to become a module, and it is now much simpler. --- lib/facter.rb | 210 +++------------------------------------ lib/facter/fact.rb | 102 +++++++++++++++++++ lib/facter/networking.rb | 6 +- lib/facter/resolution.rb | 8 +- spec/unit/fact.rb | 129 ++++++++++++++++++++++++ spec/unit/facter.rb | 12 +++ spec/unit/resolution.rb | 24 ----- 7 files changed, 260 insertions(+), 231 deletions(-) create mode 100644 lib/facter/fact.rb create mode 100755 spec/unit/fact.rb diff --git a/lib/facter.rb b/lib/facter.rb index 675b95dfea..ebc4299bf1 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -17,8 +17,8 @@ # #-- -class Facter - require 'facter/resolution' +module Facter + require 'facter/fact' include Comparable include Enumerable @@ -50,8 +50,6 @@ class Facter RESET = "" @@debug = 0 - attr_accessor :name, :searching, :ldapname - # module methods # Return the version of the library. @@ -77,11 +75,10 @@ def self.[](name) # Add a resolution mechanism for a named fact. This does not distinguish # between adding a new fact and adding a new way to resolve a fact. - def self.add(name, &block) - fact = nil - + def self.add(name, options = {}, &block) unless fact = @@facts[name] - fact = Facter.new(name) + fact = Facter::Fact.new(name, options) + @@facts[name] = fact end unless block @@ -98,11 +95,9 @@ class << self # Iterate across all of the facts. def each @@facts.each { |name,fact| - if fact.suitable? - value = fact.value - unless value.nil? - yield name.to_s, fact.value - end + value = fact.value + if ! value.nil? + yield name.to_s, fact.value end } end @@ -132,7 +127,7 @@ def method_missing(name, *args) end else # Else, fail like a normal missing method. - return super + raise NoMethodError, "Could not find fact '%s'" % name end end end @@ -185,14 +180,12 @@ def self.reset end # Return a hash of all of our facts. - def self.to_hash(*tags) + def self.to_hash @@facts.inject({}) do |h, ary| - if ary[1].suitable? and (tags.empty? or ary[1].tagged?(*tags)) - value = ary[1].value - if value - # For backwards compatibility, convert the fact name to a string. - h[ary[0].to_s] = value - end + value = ary[1].value + if ! value.nil? + # For backwards compatibility, convert the fact name to a string. + h[ary[0].to_s] = value end h end @@ -206,179 +199,6 @@ def self.value(name) end end - # Compare one value to another. - def <=>(other) - return self.value <=> other - end - - # Are we the same? Used for case statements. - def ===(value) - self.value == value - end - - # Create a new fact, with no resolution mechanisms. - def initialize(name) - @name = name.to_s.downcase.intern - if @@facts.include?(@name) - raise ArgumentError, "A fact named %s already exists" % @name - else - @@facts[@name] = self - end - - @resolves = [] - @tags = [] - @searching = false - - @value = nil - - @ldapname = name.to_s - end - - # Add a new resolution mechanism. This requires a block, which will then - # be evaluated in the context of the new mechanism. - def add(&block) - unless block_given? - raise ArgumentError, "You must pass a block to Fact.add" - end - - resolve = Resolution.new(@name) - - resolve.fact = self - - resolve.instance_eval(&block) - - # skip resolves that will never be suitable for us - unless resolve.suitable? - return - end - - # insert resolves in order of number of confinements - inserted = false - @resolves.each_with_index { |r,index| - if resolve.length > r.length - @resolves.insert(index,resolve) - inserted = true - break - end - } - - unless inserted - @resolves.push resolve - end - end - - # Return a count of resolution mechanisms available. - def count - return @resolves.length - end - - # Iterate across all of the fact resolution mechanisms and yield each in - # turn. These are inserted in order of most confinements. - def each - @resolves.each { |r| yield r } - end - - # Flush any cached values. - def flush - @value = nil - @suitable = nil - end - - # Is this fact suitable for finding answers on this host? This is used - # to throw away any initially unsuitable mechanisms. - def suitable? - if @resolves.length == 0 - return false - end - - unless defined? @suitable or (defined? @suitable and @suitable.nil?) - @suitable = false - @resolves.each { |resolve| - if resolve.suitable? - @suitable = true - break - end - } - end - - return @suitable - end - - # Add one ore more tags - def tag(*tags) - tags.each do |t| - t = t.to_s.downcase.intern - @tags << t unless @tags.include?(t) - end - end - - # Is our fact tagged with all of the specified tags? - def tagged?(*tags) - tags.each do |t| - unless @tags.include? t.to_s.downcase.intern - return false - end - end - - return true - end - - def tags - @tags.dup - end - - # Return the value for a given fact. Searches through all of the mechanisms - # and returns either the first value or nil. - def value - unless @value - # make sure we don't get stuck in recursive dependency loops - if @searching - Facter.debug "Caught recursion on %s" % @name - - # return a cached value if we've got it - if @value - return @value - else - return nil - end - end - @value = nil - foundsuits = false - - if @resolves.length == 0 - Facter.debug "No resolves for %s" % @name - return nil - end - - @searching = true - @resolves.each { |resolve| - #Facter.debug "Searching resolves for %s" % @name - if resolve.suitable? - @value = resolve.value - foundsuits = true - end - unless @value.nil? or @value == "" - break - end - } - @searching = false - - unless foundsuits - Facter.debug "Found no suitable resolves of %s for %s" % - [@resolves.length,@name] - end - end - - if @value.nil? - # nothing - Facter.debug("value for %s is still nil" % @name) - return nil - else - return @value - end - end - - # Load all of the default facts def self.loadfacts Facter.add(:facterversion) do @@ -456,5 +276,3 @@ def self.loadfacts Facter.loadfacts end - -# $Id$ diff --git a/lib/facter/fact.rb b/lib/facter/fact.rb new file mode 100644 index 0000000000..9514cd0ab4 --- /dev/null +++ b/lib/facter/fact.rb @@ -0,0 +1,102 @@ +require 'facter' +require 'facter/resolution' + +class Facter::Fact + attr_accessor :name, :searching, :ldapname + + # Create a new fact, with no resolution mechanisms. + def initialize(name, options = {}) + @name = name.to_s.downcase.intern + + # LAK:NOTE: This is slow for many options, but generally we won't have any and at + # worst we'll have one. If we add more, this should be made more efficient. + options.each do |name, value| + case name + when :ldapname: self.ldapname = value + else + raise ArgumentError, "Invalid fact option '%s'" % name + end + end + + @ldapname ||= @name.to_s + + @resolves = [] + @searching = false + + @value = nil + end + + # Add a new resolution mechanism. This requires a block, which will then + # be evaluated in the context of the new mechanism. + def add(&block) + raise ArgumentError, "You must pass a block to Fact.add" unless block_given? + + resolve = Facter::Resolution.new(@name) + + resolve.instance_eval(&block) + + @resolves << resolve + + # Immediately sort the resolutions, so that we always have + # a sorted list for looking up values. + # We always want to look them up in the order of number of + # confines, so the most restricted resolution always wins. + @resolves.sort! { |a, b| b.length <=> a.length } + + return resolve + end + + # Flush any cached values. + def flush + @value = nil + @suitable = nil + end + + # Return the value for a given fact. Searches through all of the mechanisms + # and returns either the first value or nil. + def value + unless @value + # make sure we don't get stuck in recursive dependency loops + if @searching + Facter.debug "Caught recursion on %s" % @name + + # return a cached value if we've got it + if @value + return @value + else + return nil + end + end + @value = nil + + if @resolves.length == 0 + Facter.debug "No resolves for %s" % @name + return nil + end + + @searching = true + foundsuits = false + @value = @resolves.inject(nil) { |result, resolve| + next unless resolve.suitable? + foundsuits = true + + tmp = resolve.value + + break tmp unless tmp.nil? or tmp == "" + } + @searching = false + + unless foundsuits + Facter.debug "Found no suitable resolves of %s for %s" % [@resolves.length, @name] + end + end + + if @value.nil? + # nothing + Facter.debug("value for %s is still nil" % @name) + return nil + else + return @value + end + end +end diff --git a/lib/facter/networking.rb b/lib/facter/networking.rb index cde2c46840..bd804cf1e4 100644 --- a/lib/facter/networking.rb +++ b/lib/facter/networking.rb @@ -77,8 +77,7 @@ end end end - Facter.add(:hostname) do - setldapname "cn" + Facter.add(:hostname, :ldapname => "cn") do setcode do hostname = nil name = Facter::Resolution.exec('hostname') or nil @@ -109,8 +108,7 @@ end end - Facter.add(:ipaddress) do - setldapname "iphostnumber" + Facter.add(:ipaddress, :ldapname => "iphostnumber") do setcode do require 'resolv' diff --git a/lib/facter/resolution.rb b/lib/facter/resolution.rb index 7fc1e248c0..4df55b7fae 100644 --- a/lib/facter/resolution.rb +++ b/lib/facter/resolution.rb @@ -6,7 +6,7 @@ require 'facter/confine' class Facter::Resolution - attr_accessor :interpreter, :code, :name, :fact + attr_accessor :interpreter, :code, :name def self.have_which if @have_which.nil? @@ -80,12 +80,6 @@ def setcode(string = nil, interp = nil, &block) end end - # Set the name by which this parameter is known in LDAP. The default - # is just the fact name. - def setldapname(name) - fact.ldapname = name.to_s - end - # Is this resolution mechanism suitable on the system in question? def suitable? unless defined? @suitable diff --git a/spec/unit/fact.rb b/spec/unit/fact.rb new file mode 100755 index 0000000000..3772d0f6ea --- /dev/null +++ b/spec/unit/fact.rb @@ -0,0 +1,129 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../spec_helper' + +require 'facter/fact' + +describe Facter::Fact do + it "should require a name" do + lambda { Facter::Fact.new }.should raise_error(ArgumentError) + end + + it "should always downcase the name and convert it to a symbol" do + Facter::Fact.new("YayNess").name.should == :yayness + end + + it "should default to its name converted to a string as its ldapname" do + Facter::Fact.new("YayNess").ldapname.should == "yayness" + end + + it "should allow specifying the ldap name at initialization" do + Facter::Fact.new("YayNess", :ldapname => "fooness").ldapname.should == "fooness" + end + + it "should fail if an unknown option is provided" do + lambda { Facter::Fact.new('yay', :foo => :bar) }.should raise_error(ArgumentError) + end + + it "should have a method for adding resolution mechanisms" do + Facter::Fact.new("yay").should respond_to(:add) + end + + describe "when adding resolution mechanisms" do + before do + @fact = Facter::Fact.new("yay") + + @resolution = mock 'resolution' + @resolution.stub_everything + + end + + it "should fail if no block is given" do + lambda { @fact.add }.should raise_error(ArgumentError) + end + + it "should create a new resolution instance" do + Facter::Resolution.expects(:new).returns @resolution + + @fact.add { } + end + + it "should instance_eval the passed block on the new resolution" do + @resolution.expects(:instance_eval) + + Facter::Resolution.stubs(:new).returns @resolution + + @fact.add { } + end + + it "should re-sort the resolutions by length, so the most restricted resolutions are first" do + r1 = stub 'r1', :length => 1 + r2 = stub 'r2', :length => 2 + r3 = stub 'r3', :length => 0 + Facter::Resolution.expects(:new).times(3).returns(r1).returns(r2).returns(r3) + @fact.add { } + @fact.add { } + @fact.add { } + + @fact.instance_variable_get("@resolves").should == [r2, r1, r3] + end + end + + it "should be able to return a value" do + Facter::Fact.new("yay").should respond_to(:value) + end + + describe "when returning a value" do + before do + @fact = Facter::Fact.new("yay") + end + + it "should return nil if there are no resolutions" do + Facter::Fact.new("yay").value.should be_nil + end + + it "should return the first value returned by a resolution" do + r1 = stub 'r1', :length => 2, :value => nil, :suitable? => true + r2 = stub 'r2', :length => 1, :value => "yay", :suitable? => true + r3 = stub 'r3', :length => 0, :value => "foo", :suitable? => true + Facter::Resolution.expects(:new).times(3).returns(r1).returns(r2).returns(r3) + @fact.add { } + @fact.add { } + @fact.add { } + + @fact.value.should == "yay" + end + + it "should short-cut returning the value once one is found" do + r1 = stub 'r1', :length => 2, :value => "foo", :suitable? => true + r2 = stub 'r2', :length => 1, :suitable? => true # would fail if 'value' were asked for + Facter::Resolution.expects(:new).times(2).returns(r1).returns(r2) + @fact.add { } + @fact.add { } + + @fact.value + end + + it "should skip unsuitable resolutions" do + r1 = stub 'r1', :length => 2, :suitable? => false # would fail if 'value' were asked for' + r2 = stub 'r2', :length => 1, :value => "yay", :suitable? => true + Facter::Resolution.expects(:new).times(2).returns(r1).returns(r2) + @fact.add { } + @fact.add { } + + @fact.value.should == "yay" + end + + it "should return nil if the value is the empty string" do + r1 = stub 'r1', :suitable? => true, :value => "" + Facter::Resolution.expects(:new).returns r1 + @fact.add { } + + @fact.value.should be_nil + end + end + + it "should have a method for flushing the cached fact" do + Facter::Fact.new(:foo).should respond_to(:flush) + end +end diff --git a/spec/unit/facter.rb b/spec/unit/facter.rb index 8ca160cdb7..e7942812f8 100755 --- a/spec/unit/facter.rb +++ b/spec/unit/facter.rb @@ -47,6 +47,18 @@ def teardown end end + describe Facter[:hostname] do + it "should have its ldapname set to 'cn'" do + Facter[:hostname].ldapname.should == "cn" + end + end + + describe Facter[:ipaddress] do + it "should have its ldapname set to 'iphostnumber'" do + Facter[:ipaddress].ldapname.should == "iphostnumber" + end + end + def test_onetrueconfine assert_nothing_raised { Facter.add("required") { diff --git a/spec/unit/resolution.rb b/spec/unit/resolution.rb index d4dc7e1d98..a54b87283e 100755 --- a/spec/unit/resolution.rb +++ b/spec/unit/resolution.rb @@ -155,30 +155,6 @@ end end - it "should have a method for setting its ldap name" do - Facter::Resolution.new("yay").should respond_to(:setldapname) - end - - it "should set the ldap name on the associated fact" do - @resolve = Facter::Resolution.new("yay") - fact = mock 'fact' - @resolve.stubs(:fact).returns fact - - fact.expects(:ldapname=).with "ldapness" - - @resolve.setldapname "ldapness" - end - - it "should always convert the ldap name to a string" do - @resolve = Facter::Resolution.new("yay") - fact = mock 'fact' - @resolve.stubs(:fact).returns fact - - fact.expects(:ldapname=).with "ldapness" - - @resolve.setldapname :ldapness - end - it "should have a class method for executing code" do Facter::Resolution.should respond_to(:exec) end From bfc4996e2cd22d3bae5c3955366c63fdd5277cf8 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Wed, 14 May 2008 00:24:24 -0500 Subject: [PATCH 0179/3753] Moving Facter's container behaviour into a separate class. There's now no @@facts instance variable; instead, there's a collection, and it's responsible for keeping references to all of the facts. All of the old interface methods just delegate to the collection. --- lib/facter.rb | 85 ++++++------------------ lib/facter/collection.rb | 72 ++++++++++++++++++++ spec/integration/facter.rb | 23 +++++++ spec/unit/collection.rb | 131 +++++++++++++++++++++++++++++++++++++ spec/unit/facter.rb | 63 ++++++++++-------- 5 files changed, 282 insertions(+), 92 deletions(-) create mode 100644 lib/facter/collection.rb create mode 100755 spec/integration/facter.rb create mode 100755 spec/unit/collection.rb diff --git a/lib/facter.rb b/lib/facter.rb index ebc4299bf1..36a68b863d 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -19,6 +19,7 @@ module Facter require 'facter/fact' + require 'facter/collection' include Comparable include Enumerable @@ -38,20 +39,19 @@ module Facter # - @@facts = Hash.new { |hash, key| - key = key.to_s.downcase.intern - if hash.include?(key) - hash[key] - else - nil - end - } GREEN = "" RESET = "" @@debug = 0 # module methods + def self.collection + unless defined?(@collection) and @collection + @collection = Facter::Collection.new + end + @collection + end + # Return the version of the library. def self.version return FACTERVERSION @@ -70,38 +70,25 @@ def self.debug(string) # Return a fact object by name. If you use this, you still have to call # 'value' on it to retrieve the actual value. def self.[](name) - @@facts[name] + collection.fact(name) end - # Add a resolution mechanism for a named fact. This does not distinguish - # between adding a new fact and adding a new way to resolve a fact. - def self.add(name, options = {}, &block) - unless fact = @@facts[name] - fact = Facter::Fact.new(name, options) - @@facts[name] = fact - end - - unless block - return fact + class << self + [:add, :fact, :flush, :list, :to_hash, :value].each do |method| + define_method(method) do |*args| + collection.send(method, *args) + end end + end - fact.add(&block) - return fact + # Add a resolution mechanism for a named fact. This does not distinguish + # between adding a new fact and adding a new way to resolve a fact. + def self.add(name, options = {}, &block) + collection.add(name, options, &block) end class << self - include Enumerable - # Iterate across all of the facts. - def each - @@facts.each { |name,fact| - value = fact.value - if ! value.nil? - yield name.to_s, fact.value - end - } - end - # Allow users to call fact names directly on the Facter class, # either retrieving the value or comparing it to an existing value. def method_missing(name, *args) @@ -111,7 +98,7 @@ def method_missing(name, *args) name = name.to_s.sub(/\?$/,'') end - if fact = @@facts[name] + if fact = @collection.fact(name) if question value = fact.value.downcase args.each do |arg| @@ -164,39 +151,9 @@ def self.debugging(bit) end end - # Flush all cached values. - def self.flush - @@facts.each { |name, fact| fact.flush } - end - - # Return a list of all of the facts. - def self.list - return @@facts.keys - end - # Remove them all. def self.reset - @@facts.clear - end - - # Return a hash of all of our facts. - def self.to_hash - @@facts.inject({}) do |h, ary| - value = ary[1].value - if ! value.nil? - # For backwards compatibility, convert the fact name to a string. - h[ary[0].to_s] = value - end - h - end - end - - def self.value(name) - if fact = @@facts[name] - fact.value - else - nil - end + @collection = nil end # Load all of the default facts diff --git a/lib/facter/collection.rb b/lib/facter/collection.rb new file mode 100644 index 0000000000..11133759f1 --- /dev/null +++ b/lib/facter/collection.rb @@ -0,0 +1,72 @@ +require 'facter' +require 'facter/resolution' + +# Manage which facts exist and how we access them. Largely just a wrapper +# around a hash of facts. +class Facter::Collection + # Return a fact object by name. If you use this, you still have to call + # 'value' on it to retrieve the actual value. + def [](name) + value(name) + end + + # Add a resolution mechanism for a named fact. This does not distinguish + # between adding a new fact and adding a new way to resolve a fact. + def add(name, options = {}, &block) + name = canonize(name) + + unless fact = @facts[name] + fact = Facter::Fact.new(name, options) + @facts[name] = fact + end + + fact.add(&block) if block + + return fact + end + + # Return a fact by name. + def fact(name) + @facts[canonize(name)] + end + + # Flush all cached values. + def flush + @facts.each { |name, fact| fact.flush } + end + + def initialize + @facts = Hash.new + end + + # Return a list of all of the facts. + def list + return @facts.keys + end + + # Return a hash of all of our facts. + def to_hash + @facts.inject({}) do |h, ary| + value = ary[1].value + if ! value.nil? + # For backwards compatibility, convert the fact name to a string. + h[ary[0].to_s] = value + end + h + end + end + + def value(name) + if fact = @facts[canonize(name)] + fact.value + end + end + + private + + # Provide a consistent means of getting the exact same fact name + # every time. + def canonize(name) + name.to_s.downcase.to_sym + end +end diff --git a/spec/integration/facter.rb b/spec/integration/facter.rb new file mode 100755 index 0000000000..b503c9d756 --- /dev/null +++ b/spec/integration/facter.rb @@ -0,0 +1,23 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../spec_helper' + +describe Facter do + before do + Facter.reset + Facter.loadfacts + end + + it "should create a new collection if one does not exist" do + Facter.reset + Facter::Collection.expects(:new).returns "coll" + Facter.collection.should == "coll" + Facter.reset + end + + it "should remove the collection when reset" do + old = Facter.collection + Facter.reset + Facter.collection.should_not equal(old) + end +end diff --git a/spec/unit/collection.rb b/spec/unit/collection.rb new file mode 100755 index 0000000000..1602e55bb5 --- /dev/null +++ b/spec/unit/collection.rb @@ -0,0 +1,131 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../spec_helper' + +require 'facter/collection' + +describe Facter::Collection do + it "should have a method for adding facts" do + Facter::Collection.new.should respond_to(:add) + end + + describe "when adding facts" do + before do + @coll = Facter::Collection.new + end + + it "should create a new fact if no fact with the same name already exists" do + fact = mock 'fact' + Facter::Fact.expects(:new).with { |name, *args| name == :myname }.returns fact + + @coll.add(:myname) + end + + describe "and a block is provided" do + it "should use the block to add a resolution to the fact" do + fact = mock 'fact' + Facter::Fact.expects(:new).returns fact + + fact.expects(:add) + + @coll.add(:myname) {} + end + end + end + + it "should have a method for retrieving facts by name" do + Facter::Collection.new.should respond_to(:fact) + end + + describe "when retrieving facts" do + before do + @coll = Facter::Collection.new + + @fact = @coll.add("YayNess") + end + + it "should return the fact instance specified by the name" do + @coll.fact("YayNess").should equal(@fact) + end + + it "should be case-insensitive" do + @coll.fact("yayness").should equal(@fact) + end + + it "should treat strings and symbols equivalently" do + @coll.fact(:yayness).should equal(@fact) + end + end + + it "should have a method for returning a fact's value" do + Facter::Collection.new.should respond_to(:value) + end + + describe "when returning a fact's value" do + before do + @coll = Facter::Collection.new + @fact = @coll.add("YayNess") + + @fact.stubs(:value).returns "result" + end + + it "should return the result of calling :value on the fact" do + @fact.expects(:value).returns "result" + + @coll.value("YayNess").should == "result" + end + + it "should be case-insensitive" do + @coll.value("yayness").should_not be_nil + end + + it "should treat strings and symbols equivalently" do + @coll.value(:yayness).should_not be_nil + end + end + + it "should return the fact's value when the array index method is used" do + @coll = Facter::Collection.new + @coll.expects(:value).with("myfact").returns "foo" + @coll["myfact"].should == "foo" + end + + it "should have a method for flushing all facts" do + @coll = Facter::Collection.new + @fact = @coll.add("YayNess") + + @fact.expects(:flush) + + @coll.flush + end + + it "should have a method that returns all fact names" do + @coll = Facter::Collection.new + @coll.add(:one) + @coll.add(:two) + + @coll.list.sort.should == [:one, :two].sort + end + + it "should have a method for returning a hash of fact values" do + Facter::Collection.new.should respond_to(:to_hash) + end + + describe "when returning a hash of values" do + before do + @coll = Facter::Collection.new + @fact = @coll.add(:one) + @fact.stubs(:value).returns "me" + end + + it "should return a hash of fact names and values with the fact names as strings" do + @coll.to_hash.should == {"one" => "me"} + end + + it "should not include facts that did not return a value" do + f = @coll.add(:two) + f.stubs(:value).returns nil + @coll.to_hash.should_not be_include(:two) + end + end +end diff --git a/spec/unit/facter.rb b/spec/unit/facter.rb index e7942812f8..dd1a24bad1 100755 --- a/spec/unit/facter.rb +++ b/spec/unit/facter.rb @@ -3,30 +3,42 @@ require File.dirname(__FILE__) + '/../spec_helper' describe Facter do - def tearhook(&block) - @tearhooks << block + + it "should have a version" do + Facter.version.should =~ /^[0-9]+(\.[0-9]+)*$/ end - def setup - Facter.loadfacts + it "should have a method for returning its collection" do + Facter.should respond_to(:collection) + end - @tmpfiles = [] - @tearhooks = [] + it "should cache the collection" do + Facter.collection.should equal(Facter.collection) end - def teardown - # clear out the list of facts, so we start fresh for every test - Facter.clear + it "should delegate the :flush method to the collection" do + Facter.collection.expects(:flush) + Facter.flush + end - @tmpfiles.each do |file| - if FileTest.exists?(file) - system("rm -rf %s" % file) - end - end + it "should delegate the :fact method to the collection" do + Facter.collection.expects(:fact) + Facter.fact end - - it "should have a version" do - Facter.version.should =~ /^[0-9]+(\.[0-9]+)*$/ + + it "should delegate the :list method to the collection" do + Facter.collection.expects(:list) + Facter.list + end + + it "should delegate the :to_hash method to the collection" do + Facter.collection.expects(:to_hash) + Facter.to_hash + end + + it "should delegate the :value method to the collection" do + Facter.collection.expects(:value) + Facter.value end describe "when provided code as a string" do @@ -59,6 +71,11 @@ def teardown end end + # #33 Make sure we only get one mac address + it "should only return one mac address" do + Facter.value(:macaddress).should_not be_include(" ") + end + def test_onetrueconfine assert_nothing_raised { Facter.add("required") { @@ -623,11 +640,6 @@ def test_ssh_keys end end - # #33 Make sure we only get one mac address - it "should only return one mac address" do - Facter.value(:macaddress).should_not be_include(" ") - end - def test_flush val = "yay" Facter.add(:testing) do @@ -643,11 +655,6 @@ def test_flush assert_nothing_raised("Could not clear facter cache") do Facter.flush end - assert_equal(val, Facter.value(:testing), - "did not clear cache") - - + assert_equal(val, Facter.value(:testing), "did not clear cache") end end - -# $Id$ From 5889e43e871e21bfebaa99d7a21c292d0e64b575 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Wed, 14 May 2008 00:32:41 -0500 Subject: [PATCH 0180/3753] Fixing warnings and interfaces. I was getting warnings on OS X for the IPmess stuff; that's now fixed. Also, I got rid of the internal usage of the Facter. interface, as I want to get rid of it. --- lib/facter.rb | 2 +- lib/facter/netmask.rb | 2 +- lib/facter/networking.rb | 2 +- lib/facter/util/ip.rb | 8 +++++++- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/facter.rb b/lib/facter.rb index 36a68b863d..09b196589b 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -156,7 +156,7 @@ def self.reset @collection = nil end - # Load all of the default facts + # Load all of the default facts, and then everything from disk. def self.loadfacts Facter.add(:facterversion) do setcode { FACTERVERSION.to_s } diff --git a/lib/facter/netmask.rb b/lib/facter/netmask.rb index 223d9e88a3..44a484a85c 100644 --- a/lib/facter/netmask.rb +++ b/lib/facter/netmask.rb @@ -22,7 +22,7 @@ def get_netmask ipregex = %r{(\d{1,3}\.){3}\d{1,3}} ops = nil - case Facter.kernel + case Facter.value(:kernel) when 'Linux' ops = { :ifconfig => '/sbin/ifconfig', diff --git a/lib/facter/networking.rb b/lib/facter/networking.rb index bd804cf1e4..8c80af27f2 100644 --- a/lib/facter/networking.rb +++ b/lib/facter/networking.rb @@ -16,7 +16,7 @@ Facter.add(:domain) do setcode do # First force the hostname to be checked - Facter.hostname + Facter.value(:hostname) # Now check to see if it set the domain if defined? $domain and ! $domain.nil? diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index f3d8095498..6702e5f269 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -11,7 +11,13 @@ def self.get_interfaces output = %x{/usr/sbin/ifconfig -a} end - int = output.scan(/^\w+[.:]?\d+/) + # We get lots of warnings on platforms that don't get an output + # made. + if output + int = output.scan(/^\w+[.:]?\d+/) + else + [] + end end From 48b874454cd4a6081e96365d3a19fb2b08c9d740 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Wed, 14 May 2008 09:36:32 -0500 Subject: [PATCH 0181/3753] Updating the executable to not use Facter.each. The method will need to be added back in, since it's apparently used in iClassify, but at least the executable works again now. --- bin/facter | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/bin/facter b/bin/facter index 1dec66bbbe..e9604abbda 100755 --- a/bin/facter +++ b/bin/facter @@ -107,13 +107,10 @@ ARGV.each { |item| names.push item } -facts = {} - if names.empty? - Facter.each { |name,fact| - facts[name] = fact - } + facts = Facter.to_hash else + facts = {} names.each { |name| begin facts[name] = Facter.value(name) @@ -139,5 +136,3 @@ facts.sort { |a, b| a[0].to_s <=> b[0].to_s }.each { |name,value| puts "%s => %s" % [name,value] end } - -# $Id: facter,v 1.1.1.1 2004/03/21 21:06:27 luke Exp $ From cc9e2217fd62f02665da84489dc88be6559d2909 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Wed, 14 May 2008 10:17:18 -0500 Subject: [PATCH 0182/3753] Adding the 'each' method back into Facter. It's used by iClassify, and maybe others. I haven't made Facter enumerable again, but the collection is. --- lib/facter.rb | 2 +- lib/facter/collection.rb | 12 +++++++++++ spec/unit/collection.rb | 46 ++++++++++++++++++++++++++++++++++++++++ spec/unit/facter.rb | 5 +++++ 4 files changed, 64 insertions(+), 1 deletion(-) diff --git a/lib/facter.rb b/lib/facter.rb index 09b196589b..913060e742 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -74,7 +74,7 @@ def self.[](name) end class << self - [:add, :fact, :flush, :list, :to_hash, :value].each do |method| + [:add, :each, :fact, :flush, :list, :to_hash, :value].each do |method| define_method(method) do |*args| collection.send(method, *args) end diff --git a/lib/facter/collection.rb b/lib/facter/collection.rb index 11133759f1..5e6845a152 100644 --- a/lib/facter/collection.rb +++ b/lib/facter/collection.rb @@ -25,6 +25,18 @@ def add(name, options = {}, &block) return fact end + include Enumerable + + # Iterate across all of the facts. + def each + @facts.each do |name, fact| + value = fact.value + unless value.nil? + yield name.to_s, value + end + end + end + # Return a fact by name. def fact(name) @facts[canonize(name)] diff --git a/spec/unit/collection.rb b/spec/unit/collection.rb index 1602e55bb5..d69888ecac 100755 --- a/spec/unit/collection.rb +++ b/spec/unit/collection.rb @@ -128,4 +128,50 @@ @coll.to_hash.should_not be_include(:two) end end + + it "should have a method for iterating over all facts" do + Facter::Collection.new.should respond_to(:each) + end + + it "should include Enumerable" do + Facter::Collection.ancestors.should be_include(Enumerable) + end + + describe "when iterating over facts" do + before do + @coll = Facter::Collection.new + @one = @coll.add(:one) + @two = @coll.add(:two) + end + + it "should yield each fact name and the fact value" do + @one.stubs(:value).returns "ONE" + @two.stubs(:value).returns "TWO" + facts = {} + @coll.each do |fact, value| + facts[fact] = value + end + facts.should == {"one" => "ONE", "two" => "TWO"} + end + + it "should convert the fact name to a string" do + @one.stubs(:value).returns "ONE" + @two.stubs(:value).returns "TWO" + facts = {} + @coll.each do |fact, value| + fact.should be_instance_of(String) + end + end + + it "should only yield facts that have values" do + @one.stubs(:value).returns "ONE" + @two.stubs(:value).returns nil + facts = {} + @coll.each do |fact, value| + facts[fact] = value + end + + facts.should_not be_include("two") + end + end end diff --git a/spec/unit/facter.rb b/spec/unit/facter.rb index dd1a24bad1..53796ad683 100755 --- a/spec/unit/facter.rb +++ b/spec/unit/facter.rb @@ -41,6 +41,11 @@ Facter.value end + it "should delegate the :each method to the collection" do + Facter.collection.expects(:each) + Facter.each + end + describe "when provided code as a string" do it "should execute the code in the shell" do Facter.add("shell_testing") do From be0a8031fbf8e4a2d608ab600c37c1b01dec16a1 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Thu, 15 May 2008 14:18:26 -0500 Subject: [PATCH 0183/3753] Creating a 'loader' class to handle loading facts for the collection. --- lib/facter/loader.rb | 127 +++++++++++++++++++++++++++++ spec/unit/loader.rb | 190 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 317 insertions(+) create mode 100644 lib/facter/loader.rb create mode 100755 spec/unit/loader.rb diff --git a/lib/facter/loader.rb b/lib/facter/loader.rb new file mode 100644 index 0000000000..3284cc4128 --- /dev/null +++ b/lib/facter/loader.rb @@ -0,0 +1,127 @@ +require 'facter' + +# Load facts on demand. +class Facter::Loader + # Load all resolutions for a single fact. + def load(fact) + # Now load from the search path + shortname = fact.to_s.downcase + load_env(shortname) + + filename = shortname + ".rb" + search_path.each do |dir| + # Load individual files + file = File.join(dir, filename) + + # We have to specify Kernel.load, because we have a load method. + Kernel.load(file) if FileTest.exist?(file) + + # And load any directories matching the name + factdir = File.join(dir, shortname) + load_dir(factdir) if FileTest.directory?(factdir) + end + end + + # Load all facts from all directories. + def load_all + load_env + + search_path.each do |dir| + Dir.entries(dir).each do |file| + path = File.join(dir, file) + if File.directory?(path) + load_dir(path) + elsif file =~ /\.rb$/ + Kernel.load(File.join(dir, file)) + end + end + end + end + + # The list of directories we're going to search through for facts. + def search_path + result = [] + result += $LOAD_PATH.collect { |d| File.join(d, "facter") } + if ENV.include?("FACTERLIB") + result += ENV["FACTERLIB"].split(":") + end + + if defined?(Puppet) + result << Puppet.settings.value(:factdest) + result << File.join(Puppet.settings.value(:libdir), "facter") + end + result + end + + def old_stuff + # See if we can find any other facts in the regular Ruby lib + # paths + $:.each do |dir| + fdir = File.join(dir, "facter") + if FileTest.exists?(fdir) and FileTest.directory?(fdir) + factdirs.push(fdir) + end + end + # Also check anything in 'FACTERLIB' + if ENV['FACTERLIB'] + ENV['FACTERLIB'].split(":").each do |fdir| + factdirs.push(fdir) + end + end + factdirs.each do |fdir| + Dir.glob("#{fdir}/*.rb").each do |file| + # Load here, rather than require, because otherwise + # the facts won't get reloaded if someone calls + # "loadfacts". Really only important in testing, but, + # well, it's important in testing. + begin + load file + rescue => detail + warn "Could not load %s: %s" % + [file, detail] + end + end + end + + + # Now try to get facts from the environment + ENV.each do |name, value| + if name =~ /^facter_?(\w+)$/i + Facter.add($1) do + setcode { value } + end + end + end + end + + private + + def load_dir(dir) + return if dir =~ /\/util$/ + Dir.entries(dir).find_all { |f| f =~ /\.rb$/ }.each do |file| + Kernel.load(File.join(dir, file)) + end + end + + # Load facts from the environment. If no name is provided, + # all will be loaded. + def load_env(fact = nil) + # Load from the environment, if possible + ENV.each do |name, value| + # Skip anything that doesn't match our regex. + next unless name =~ /^facter_?(\w+)$/i + env_name = $1 + + # If a fact name was specified, skip anything that doesn't + # match it. + next if fact and env_name != fact + + Facter.add($1) do + setcode { value } + end + + # Short-cut, if we are only looking for one value. + break if fact + end + end +end diff --git a/spec/unit/loader.rb b/spec/unit/loader.rb new file mode 100755 index 0000000000..75146e1050 --- /dev/null +++ b/spec/unit/loader.rb @@ -0,0 +1,190 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../spec_helper' + +require 'facter/loader' + +# Make sure we have a Puppet constant, so we can test +# loading Puppet facts. +unless defined?(Puppet) + class Puppet; end +end + +describe Facter::Loader do + def with_env(values) + old = {} + values.each do |var, value| + if old_val = ENV[var] + old[var] = old_val + end + ENV[var] = value + end + yield + values.each do |var, value| + if old.include?(var) + ENV[var] = old[var] + else + ENV.delete(var) + end + end + end + + it "should have a method for loading individual facts by name" do + Facter::Loader.new.should respond_to(:load) + end + + it "should have a method for loading all facts" do + Facter::Loader.new.should respond_to(:load_all) + end + + it "should have a method for returning directories containing facts" do + Facter::Loader.new.should respond_to(:search_path) + end + + describe "when determining the search path" do + before do + @loader = Facter::Loader.new + @settings = mock 'settings' + @settings.stubs(:value).returns "/eh" + Puppet.stubs(:settings).returns @settings + end + + it "should include the facter subdirectory of all paths in ruby LOAD_PATH" do + dirs = $LOAD_PATH.collect { |d| File.join(d, "facter") } + paths = @loader.search_path + + dirs.each do |dir| + paths.should be_include(dir) + end + end + + describe "and the FACTERLIB environment variable is set" do + it "should include all paths in FACTERLIB" do + with_env "FACTERLIB" => "/one/path:/two/path" do + paths = @loader.search_path + %w{/one/path /two/path}.each do |dir| + paths.should be_include(dir) + end + end + end + end + + describe "and the Puppet libraries are loaded" do + it "should include the factdest setting" do + @settings.expects(:value).with(:factdest).returns "/my/facts" + @loader.search_path.should be_include("/my/facts") + end + + it "should include the facter subdirectory of the libdir setting" do + @settings.expects(:value).with(:libdir).returns "/lib/dir" + @loader.search_path.should be_include("/lib/dir/facter") + end + end + end + + describe "when loading facts" do + before do + @loader = Facter::Loader.new + @loader.stubs(:search_path).returns [] + end + + it "should load values from the matching environment variable if one is present" do + Facter.expects(:add).with("testing") + + with_env "facter_testing" => "yayness" do + @loader.load(:testing) + end + end + + it "should load any files in the search path with names matching the fact name" do + @loader.expects(:search_path).returns %w{/one/dir /two/dir} + FileTest.stubs(:exist?).returns false + FileTest.expects(:exist?).with("/one/dir/testing.rb").returns true + FileTest.expects(:exist?).with("/two/dir/testing.rb").returns true + + Kernel.expects(:load).with("/one/dir/testing.rb") + Kernel.expects(:load).with("/two/dir/testing.rb") + + @loader.load(:testing) + end + + it "should load any ruby files in directories matching the fact name in the search path" do + @loader.expects(:search_path).returns %w{/one/dir} + FileTest.stubs(:exist?).returns false + FileTest.expects(:directory?).with("/one/dir/testing").returns true + + Dir.expects(:entries).with("/one/dir/testing").returns %w{two.rb} + + Kernel.expects(:load).with("/one/dir/testing/two.rb") + + @loader.load(:testing) + end + + it "should not load files that don't end in '.rb'" do + @loader.expects(:search_path).returns %w{/one/dir} + FileTest.stubs(:exist?).returns false + FileTest.expects(:directory?).with("/one/dir/testing").returns true + + Dir.expects(:entries).with("/one/dir/testing").returns %w{one} + + Kernel.expects(:load).never + + @loader.load(:testing) + end + end + + describe "when loading all facts" do + before do + @loader = Facter::Loader.new + @loader.stubs(:search_path).returns [] + end + + it "should load all files in all search paths" do + @loader.expects(:search_path).returns %w{/one/dir /two/dir} + + Dir.expects(:entries).with("/one/dir").returns %w{a.rb b.rb} + Dir.expects(:entries).with("/two/dir").returns %w{c.rb d.rb} + + %w{/one/dir/a.rb /one/dir/b.rb /two/dir/c.rb /two/dir/d.rb}.each { |f| Kernel.expects(:load).with(f) } + + @loader.load_all + end + + it "should load all files in all subdirectories in all search paths" do + @loader.expects(:search_path).returns %w{/one/dir /two/dir} + + Dir.expects(:entries).with("/one/dir").returns %w{a} + Dir.expects(:entries).with("/two/dir").returns %w{b} + + %w{/one/dir/a /two/dir/b}.each { |f| File.expects(:directory?).with(f).returns true } + + Dir.expects(:entries).with("/one/dir/a").returns %w{c.rb} + Dir.expects(:entries).with("/two/dir/b").returns %w{d.rb} + + %w{/one/dir/a/c.rb /two/dir/b/d.rb}.each { |f| Kernel.expects(:load).with(f) } + + @loader.load_all + end + + it "should not load files in the util subdirectory" do + @loader.expects(:search_path).returns %w{/one/dir} + + Dir.expects(:entries).with("/one/dir").returns %w{util} + + File.expects(:directory?).with("/one/dir/util").returns true + + Dir.expects(:entries).with("/one/dir/util").never + + @loader.load_all + end + + it "should load all facts from the environment" do + Facter.expects(:add).with('one') + Facter.expects(:add).with('two') + + with_env "facter_one" => "yayness", "facter_two" => "boo" do + @loader.load_all + end + end + end +end From 1ba2bed578debd251d2b9514039082eaa3f136df Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Thu, 15 May 2008 14:30:29 -0500 Subject: [PATCH 0184/3753] Moving all of the support classes to util/. This makes it easier for our loader to distinguish between code that Facter uses and new facts. --- lib/facter.rb | 9 ++++-- lib/facter/{ => util}/collection.rb | 6 ++-- lib/facter/{ => util}/confine.rb | 2 +- lib/facter/{ => util}/fact.rb | 6 ++-- lib/facter/{ => util}/loader.rb | 2 +- lib/facter/{ => util}/resolution.rb | 8 ++--- spec/integration/facter.rb | 9 ++++-- spec/unit/{ => util}/collection.rb | 38 +++++++++++----------- spec/unit/{ => util}/confine.rb | 22 ++++++------- spec/unit/{ => util}/fact.rb | 42 ++++++++++++------------ spec/unit/{ => util}/loader.rb | 18 +++++------ spec/unit/{ => util}/resolution.rb | 50 ++++++++++++++--------------- 12 files changed, 110 insertions(+), 102 deletions(-) rename lib/facter/{ => util}/collection.rb (94%) rename lib/facter/{ => util}/confine.rb (97%) rename lib/facter/{ => util}/fact.rb (96%) rename lib/facter/{ => util}/loader.rb (99%) rename lib/facter/{ => util}/resolution.rb (92%) rename spec/unit/{ => util}/collection.rb (79%) rename spec/unit/{ => util}/confine.rb (67%) rename spec/unit/{ => util}/fact.rb (67%) rename spec/unit/{ => util}/loader.rb (93%) rename spec/unit/{ => util}/resolution.rb (69%) diff --git a/lib/facter.rb b/lib/facter.rb index 913060e742..0f00f7d3ee 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -18,8 +18,11 @@ #-- module Facter - require 'facter/fact' - require 'facter/collection' + # This is just so the other classes have the constant. + module Util; end + + require 'facter/util/fact' + require 'facter/util/collection' include Comparable include Enumerable @@ -47,7 +50,7 @@ module Facter def self.collection unless defined?(@collection) and @collection - @collection = Facter::Collection.new + @collection = Facter::Util::Collection.new end @collection end diff --git a/lib/facter/collection.rb b/lib/facter/util/collection.rb similarity index 94% rename from lib/facter/collection.rb rename to lib/facter/util/collection.rb index 5e6845a152..f76b578000 100644 --- a/lib/facter/collection.rb +++ b/lib/facter/util/collection.rb @@ -1,9 +1,9 @@ require 'facter' -require 'facter/resolution' +require 'facter/util/fact' # Manage which facts exist and how we access them. Largely just a wrapper # around a hash of facts. -class Facter::Collection +class Facter::Util::Collection # Return a fact object by name. If you use this, you still have to call # 'value' on it to retrieve the actual value. def [](name) @@ -16,7 +16,7 @@ def add(name, options = {}, &block) name = canonize(name) unless fact = @facts[name] - fact = Facter::Fact.new(name, options) + fact = Facter::Util::Fact.new(name, options) @facts[name] = fact end diff --git a/lib/facter/confine.rb b/lib/facter/util/confine.rb similarity index 97% rename from lib/facter/confine.rb rename to lib/facter/util/confine.rb index 529c9681d7..a430bbe0eb 100644 --- a/lib/facter/confine.rb +++ b/lib/facter/util/confine.rb @@ -1,6 +1,6 @@ # A restricting tag for fact resolution mechanisms. The tag must be true # for the resolution mechanism to be suitable. -class Facter::Confine +class Facter::Util::Confine attr_accessor :fact, :values # Add the restriction. Requires the fact name, an operator, and the value diff --git a/lib/facter/fact.rb b/lib/facter/util/fact.rb similarity index 96% rename from lib/facter/fact.rb rename to lib/facter/util/fact.rb index 9514cd0ab4..c73dfbf418 100644 --- a/lib/facter/fact.rb +++ b/lib/facter/util/fact.rb @@ -1,7 +1,7 @@ require 'facter' -require 'facter/resolution' +require 'facter/util/resolution' -class Facter::Fact +class Facter::Util::Fact attr_accessor :name, :searching, :ldapname # Create a new fact, with no resolution mechanisms. @@ -31,7 +31,7 @@ def initialize(name, options = {}) def add(&block) raise ArgumentError, "You must pass a block to Fact.add" unless block_given? - resolve = Facter::Resolution.new(@name) + resolve = Facter::Util::Resolution.new(@name) resolve.instance_eval(&block) diff --git a/lib/facter/loader.rb b/lib/facter/util/loader.rb similarity index 99% rename from lib/facter/loader.rb rename to lib/facter/util/loader.rb index 3284cc4128..7031172044 100644 --- a/lib/facter/loader.rb +++ b/lib/facter/util/loader.rb @@ -1,7 +1,7 @@ require 'facter' # Load facts on demand. -class Facter::Loader +class Facter::Util::Loader # Load all resolutions for a single fact. def load(fact) # Now load from the search path diff --git a/lib/facter/resolution.rb b/lib/facter/util/resolution.rb similarity index 92% rename from lib/facter/resolution.rb rename to lib/facter/util/resolution.rb index 4df55b7fae..b6aae77dcd 100644 --- a/lib/facter/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -3,9 +3,9 @@ # specific systems. Note that the confinements are always ANDed, so any # confinements specified must all be true for the resolution to be # suitable. -require 'facter/confine' +require 'facter/util/confine' -class Facter::Resolution +class Facter::Util::Resolution attr_accessor :interpreter, :code, :name def self.have_which @@ -51,7 +51,7 @@ def self.exec(code, interpreter = "/bin/sh") # Add a new confine to the resolution mechanism. def confine(confines) confines.each do |fact, values| - @confines.push Facter::Confine.new(fact, *values) + @confines.push Facter::Util::Confine.new(fact, *values) end end @@ -98,7 +98,7 @@ def value if @code.is_a?(Proc) result = @code.call() else - result = Facter::Resolution.exec(@code,@interpreter) + result = Facter::Util::Resolution.exec(@code,@interpreter) end return nil if result == "" diff --git a/spec/integration/facter.rb b/spec/integration/facter.rb index b503c9d756..e1f2025b99 100755 --- a/spec/integration/facter.rb +++ b/spec/integration/facter.rb @@ -8,10 +8,15 @@ Facter.loadfacts end + after do + Facter.reset + end + it "should create a new collection if one does not exist" do Facter.reset - Facter::Collection.expects(:new).returns "coll" - Facter.collection.should == "coll" + coll = mock('coll') + Facter::Util::Collection.stubs(:new).returns coll + Facter.collection.should equal(coll) Facter.reset end diff --git a/spec/unit/collection.rb b/spec/unit/util/collection.rb similarity index 79% rename from spec/unit/collection.rb rename to spec/unit/util/collection.rb index d69888ecac..e88de06fca 100755 --- a/spec/unit/collection.rb +++ b/spec/unit/util/collection.rb @@ -1,22 +1,22 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../spec_helper' +require File.dirname(__FILE__) + '/../../spec_helper' -require 'facter/collection' +require 'facter/util/collection' -describe Facter::Collection do +describe Facter::Util::Collection do it "should have a method for adding facts" do - Facter::Collection.new.should respond_to(:add) + Facter::Util::Collection.new.should respond_to(:add) end describe "when adding facts" do before do - @coll = Facter::Collection.new + @coll = Facter::Util::Collection.new end it "should create a new fact if no fact with the same name already exists" do fact = mock 'fact' - Facter::Fact.expects(:new).with { |name, *args| name == :myname }.returns fact + Facter::Util::Fact.expects(:new).with { |name, *args| name == :myname }.returns fact @coll.add(:myname) end @@ -24,7 +24,7 @@ describe "and a block is provided" do it "should use the block to add a resolution to the fact" do fact = mock 'fact' - Facter::Fact.expects(:new).returns fact + Facter::Util::Fact.expects(:new).returns fact fact.expects(:add) @@ -34,12 +34,12 @@ end it "should have a method for retrieving facts by name" do - Facter::Collection.new.should respond_to(:fact) + Facter::Util::Collection.new.should respond_to(:fact) end describe "when retrieving facts" do before do - @coll = Facter::Collection.new + @coll = Facter::Util::Collection.new @fact = @coll.add("YayNess") end @@ -58,12 +58,12 @@ end it "should have a method for returning a fact's value" do - Facter::Collection.new.should respond_to(:value) + Facter::Util::Collection.new.should respond_to(:value) end describe "when returning a fact's value" do before do - @coll = Facter::Collection.new + @coll = Facter::Util::Collection.new @fact = @coll.add("YayNess") @fact.stubs(:value).returns "result" @@ -85,13 +85,13 @@ end it "should return the fact's value when the array index method is used" do - @coll = Facter::Collection.new + @coll = Facter::Util::Collection.new @coll.expects(:value).with("myfact").returns "foo" @coll["myfact"].should == "foo" end it "should have a method for flushing all facts" do - @coll = Facter::Collection.new + @coll = Facter::Util::Collection.new @fact = @coll.add("YayNess") @fact.expects(:flush) @@ -100,7 +100,7 @@ end it "should have a method that returns all fact names" do - @coll = Facter::Collection.new + @coll = Facter::Util::Collection.new @coll.add(:one) @coll.add(:two) @@ -108,12 +108,12 @@ end it "should have a method for returning a hash of fact values" do - Facter::Collection.new.should respond_to(:to_hash) + Facter::Util::Collection.new.should respond_to(:to_hash) end describe "when returning a hash of values" do before do - @coll = Facter::Collection.new + @coll = Facter::Util::Collection.new @fact = @coll.add(:one) @fact.stubs(:value).returns "me" end @@ -130,16 +130,16 @@ end it "should have a method for iterating over all facts" do - Facter::Collection.new.should respond_to(:each) + Facter::Util::Collection.new.should respond_to(:each) end it "should include Enumerable" do - Facter::Collection.ancestors.should be_include(Enumerable) + Facter::Util::Collection.ancestors.should be_include(Enumerable) end describe "when iterating over facts" do before do - @coll = Facter::Collection.new + @coll = Facter::Util::Collection.new @one = @coll.add(:one) @two = @coll.add(:two) end diff --git a/spec/unit/confine.rb b/spec/unit/util/confine.rb similarity index 67% rename from spec/unit/confine.rb rename to spec/unit/util/confine.rb index ae122eafc7..5c1ce3b5df 100755 --- a/spec/unit/confine.rb +++ b/spec/unit/util/confine.rb @@ -1,41 +1,41 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../spec_helper' +require File.dirname(__FILE__) + '/../../spec_helper' -require 'facter/confine' +require 'facter/util/confine' -describe Facter::Confine do +describe Facter::Util::Confine do it "should require a fact name" do - Facter::Confine.new("yay", true).fact.should == "yay" + Facter::Util::Confine.new("yay", true).fact.should == "yay" end it "should accept a value specified individually" do - Facter::Confine.new("yay", "test").values.should == ["test"] + Facter::Util::Confine.new("yay", "test").values.should == ["test"] end it "should accept multiple values specified at once" do - Facter::Confine.new("yay", "test", "other").values.should == ["test", "other"] + Facter::Util::Confine.new("yay", "test", "other").values.should == ["test", "other"] end it "should convert all values to strings" do - Facter::Confine.new("yay", :test).values.should == %w{test} + Facter::Util::Confine.new("yay", :test).values.should == %w{test} end it "should fail if no fact name is provided" do - lambda { Facter::Confine.new(nil, :test) }.should raise_error(ArgumentError) + lambda { Facter::Util::Confine.new(nil, :test) }.should raise_error(ArgumentError) end it "should fail if no values were provided" do - lambda { Facter::Confine.new("yay") }.should raise_error(ArgumentError) + lambda { Facter::Util::Confine.new("yay") }.should raise_error(ArgumentError) end it "should have a method for testing whether it matches" do - Facter::Confine.new("yay", :test).should respond_to(:true?) + Facter::Util::Confine.new("yay", :test).should respond_to(:true?) end describe "when evaluating" do before do - @confine = Facter::Confine.new("yay", "one", "two") + @confine = Facter::Util::Confine.new("yay", "one", "two") @fact = mock 'fact' Facter.stubs(:[]).returns @fact end diff --git a/spec/unit/fact.rb b/spec/unit/util/fact.rb similarity index 67% rename from spec/unit/fact.rb rename to spec/unit/util/fact.rb index 3772d0f6ea..16520327bd 100755 --- a/spec/unit/fact.rb +++ b/spec/unit/util/fact.rb @@ -1,37 +1,37 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../spec_helper' +require File.dirname(__FILE__) + '/../../spec_helper' -require 'facter/fact' +require 'facter/util/fact' -describe Facter::Fact do +describe Facter::Util::Fact do it "should require a name" do - lambda { Facter::Fact.new }.should raise_error(ArgumentError) + lambda { Facter::Util::Fact.new }.should raise_error(ArgumentError) end it "should always downcase the name and convert it to a symbol" do - Facter::Fact.new("YayNess").name.should == :yayness + Facter::Util::Fact.new("YayNess").name.should == :yayness end it "should default to its name converted to a string as its ldapname" do - Facter::Fact.new("YayNess").ldapname.should == "yayness" + Facter::Util::Fact.new("YayNess").ldapname.should == "yayness" end it "should allow specifying the ldap name at initialization" do - Facter::Fact.new("YayNess", :ldapname => "fooness").ldapname.should == "fooness" + Facter::Util::Fact.new("YayNess", :ldapname => "fooness").ldapname.should == "fooness" end it "should fail if an unknown option is provided" do - lambda { Facter::Fact.new('yay', :foo => :bar) }.should raise_error(ArgumentError) + lambda { Facter::Util::Fact.new('yay', :foo => :bar) }.should raise_error(ArgumentError) end it "should have a method for adding resolution mechanisms" do - Facter::Fact.new("yay").should respond_to(:add) + Facter::Util::Fact.new("yay").should respond_to(:add) end describe "when adding resolution mechanisms" do before do - @fact = Facter::Fact.new("yay") + @fact = Facter::Util::Fact.new("yay") @resolution = mock 'resolution' @resolution.stub_everything @@ -43,7 +43,7 @@ end it "should create a new resolution instance" do - Facter::Resolution.expects(:new).returns @resolution + Facter::Util::Resolution.expects(:new).returns @resolution @fact.add { } end @@ -51,7 +51,7 @@ it "should instance_eval the passed block on the new resolution" do @resolution.expects(:instance_eval) - Facter::Resolution.stubs(:new).returns @resolution + Facter::Util::Resolution.stubs(:new).returns @resolution @fact.add { } end @@ -60,7 +60,7 @@ r1 = stub 'r1', :length => 1 r2 = stub 'r2', :length => 2 r3 = stub 'r3', :length => 0 - Facter::Resolution.expects(:new).times(3).returns(r1).returns(r2).returns(r3) + Facter::Util::Resolution.expects(:new).times(3).returns(r1).returns(r2).returns(r3) @fact.add { } @fact.add { } @fact.add { } @@ -70,23 +70,23 @@ end it "should be able to return a value" do - Facter::Fact.new("yay").should respond_to(:value) + Facter::Util::Fact.new("yay").should respond_to(:value) end describe "when returning a value" do before do - @fact = Facter::Fact.new("yay") + @fact = Facter::Util::Fact.new("yay") end it "should return nil if there are no resolutions" do - Facter::Fact.new("yay").value.should be_nil + Facter::Util::Fact.new("yay").value.should be_nil end it "should return the first value returned by a resolution" do r1 = stub 'r1', :length => 2, :value => nil, :suitable? => true r2 = stub 'r2', :length => 1, :value => "yay", :suitable? => true r3 = stub 'r3', :length => 0, :value => "foo", :suitable? => true - Facter::Resolution.expects(:new).times(3).returns(r1).returns(r2).returns(r3) + Facter::Util::Resolution.expects(:new).times(3).returns(r1).returns(r2).returns(r3) @fact.add { } @fact.add { } @fact.add { } @@ -97,7 +97,7 @@ it "should short-cut returning the value once one is found" do r1 = stub 'r1', :length => 2, :value => "foo", :suitable? => true r2 = stub 'r2', :length => 1, :suitable? => true # would fail if 'value' were asked for - Facter::Resolution.expects(:new).times(2).returns(r1).returns(r2) + Facter::Util::Resolution.expects(:new).times(2).returns(r1).returns(r2) @fact.add { } @fact.add { } @@ -107,7 +107,7 @@ it "should skip unsuitable resolutions" do r1 = stub 'r1', :length => 2, :suitable? => false # would fail if 'value' were asked for' r2 = stub 'r2', :length => 1, :value => "yay", :suitable? => true - Facter::Resolution.expects(:new).times(2).returns(r1).returns(r2) + Facter::Util::Resolution.expects(:new).times(2).returns(r1).returns(r2) @fact.add { } @fact.add { } @@ -116,7 +116,7 @@ it "should return nil if the value is the empty string" do r1 = stub 'r1', :suitable? => true, :value => "" - Facter::Resolution.expects(:new).returns r1 + Facter::Util::Resolution.expects(:new).returns r1 @fact.add { } @fact.value.should be_nil @@ -124,6 +124,6 @@ end it "should have a method for flushing the cached fact" do - Facter::Fact.new(:foo).should respond_to(:flush) + Facter::Util::Fact.new(:foo).should respond_to(:flush) end end diff --git a/spec/unit/loader.rb b/spec/unit/util/loader.rb similarity index 93% rename from spec/unit/loader.rb rename to spec/unit/util/loader.rb index 75146e1050..4c0d777912 100755 --- a/spec/unit/loader.rb +++ b/spec/unit/util/loader.rb @@ -1,8 +1,8 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../spec_helper' +require File.dirname(__FILE__) + '/../../spec_helper' -require 'facter/loader' +require 'facter/util/loader' # Make sure we have a Puppet constant, so we can test # loading Puppet facts. @@ -10,7 +10,7 @@ class Puppet; end end -describe Facter::Loader do +describe Facter::Util::Loader do def with_env(values) old = {} values.each do |var, value| @@ -30,20 +30,20 @@ def with_env(values) end it "should have a method for loading individual facts by name" do - Facter::Loader.new.should respond_to(:load) + Facter::Util::Loader.new.should respond_to(:load) end it "should have a method for loading all facts" do - Facter::Loader.new.should respond_to(:load_all) + Facter::Util::Loader.new.should respond_to(:load_all) end it "should have a method for returning directories containing facts" do - Facter::Loader.new.should respond_to(:search_path) + Facter::Util::Loader.new.should respond_to(:search_path) end describe "when determining the search path" do before do - @loader = Facter::Loader.new + @loader = Facter::Util::Loader.new @settings = mock 'settings' @settings.stubs(:value).returns "/eh" Puppet.stubs(:settings).returns @settings @@ -84,7 +84,7 @@ def with_env(values) describe "when loading facts" do before do - @loader = Facter::Loader.new + @loader = Facter::Util::Loader.new @loader.stubs(:search_path).returns [] end @@ -135,7 +135,7 @@ def with_env(values) describe "when loading all facts" do before do - @loader = Facter::Loader.new + @loader = Facter::Util::Loader.new @loader.stubs(:search_path).returns [] end diff --git a/spec/unit/resolution.rb b/spec/unit/util/resolution.rb similarity index 69% rename from spec/unit/resolution.rb rename to spec/unit/util/resolution.rb index a54b87283e..493ee3a854 100755 --- a/spec/unit/resolution.rb +++ b/spec/unit/util/resolution.rb @@ -1,25 +1,25 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../spec_helper' +require File.dirname(__FILE__) + '/../../spec_helper' -require 'facter/resolution' +require 'facter/util/resolution' -describe Facter::Resolution do +describe Facter::Util::Resolution do it "should require a name" do - lambda { Facter::Resolution.new }.should raise_error(ArgumentError) + lambda { Facter::Util::Resolution.new }.should raise_error(ArgumentError) end it "should have a name" do - Facter::Resolution.new("yay").name.should == "yay" + Facter::Util::Resolution.new("yay").name.should == "yay" end it "should have a method for setting the code" do - Facter::Resolution.new("yay").should respond_to(:setcode) + Facter::Util::Resolution.new("yay").should respond_to(:setcode) end describe "when setting the code" do before do - @resolve = Facter::Resolution.new("yay") + @resolve = Facter::Util::Resolution.new("yay") end it "should default to /bin/sh as the interpreter if a string is provided" do @@ -49,25 +49,25 @@ end it "should be able to return a value" do - Facter::Resolution.new("yay").should respond_to(:value) + Facter::Util::Resolution.new("yay").should respond_to(:value) end describe "when returning the value" do before do - @resolve = Facter::Resolution.new("yay") + @resolve = Facter::Util::Resolution.new("yay") end describe "and the code is a string" do it "should return the result of executing the code with the interpreter" do @resolve.setcode "/bin/foo" - Facter::Resolution.expects(:exec).with("/bin/foo", "/bin/sh").returns "yup" + Facter::Util::Resolution.expects(:exec).with("/bin/foo", "/bin/sh").returns "yup" @resolve.value.should == "yup" end it "should return nil if the value is an empty string" do @resolve.setcode "/bin/foo" - Facter::Resolution.stubs(:exec).returns "" + Facter::Util::Resolution.stubs(:exec).returns "" @resolve.value.should be_nil end end @@ -86,41 +86,41 @@ end it "should return its value when converted to a string" do - @resolve = Facter::Resolution.new("yay") + @resolve = Facter::Util::Resolution.new("yay") @resolve.expects(:value).returns "myval" @resolve.to_s.should == "myval" end it "should allow the adding of confines" do - Facter::Resolution.new("yay").should respond_to(:confine) + Facter::Util::Resolution.new("yay").should respond_to(:confine) end it "should provide a method for returning the number of confines" do - @resolve = Facter::Resolution.new("yay") + @resolve = Facter::Util::Resolution.new("yay") @resolve.confine "one" => "foo", "two" => "fee" @resolve.length.should == 2 end it "should return 0 confines when no confines have been added" do - Facter::Resolution.new("yay").length.should == 0 + Facter::Util::Resolution.new("yay").length.should == 0 end it "should have a method for determining if it is suitable" do - Facter::Resolution.new("yay").should respond_to(:suitable?) + Facter::Util::Resolution.new("yay").should respond_to(:suitable?) end describe "when adding confines" do before do - @resolve = Facter::Resolution.new("yay") + @resolve = Facter::Util::Resolution.new("yay") end it "should accept a hash of fact names and values" do lambda { @resolve.confine :one => "two" }.should_not raise_error end - it "should create a Confine instance for every argument in the provided hash" do - Facter::Confine.expects(:new).with("one", "foo") - Facter::Confine.expects(:new).with("two", "fee") + it "should create a Util::Confine instance for every argument in the provided hash" do + Facter::Util::Confine.expects(:new).with("one", "foo") + Facter::Util::Confine.expects(:new).with("two", "fee") @resolve.confine "one" => "foo", "two" => "fee" end @@ -129,7 +129,7 @@ describe "when determining suitability" do before do - @resolve = Facter::Resolution.new("yay") + @resolve = Facter::Util::Resolution.new("yay") end it "should always be suitable if no confines have been added" do @@ -139,7 +139,7 @@ it "should be unsuitable if any provided confines return false" do confine1 = mock 'confine1', :true? => true confine2 = mock 'confine2', :true? => false - Facter::Confine.expects(:new).times(2).returns(confine1).then.returns(confine2) + Facter::Util::Confine.expects(:new).times(2).returns(confine1).then.returns(confine2) @resolve.confine :one => :two, :three => :four @resolve.should_not be_suitable @@ -148,7 +148,7 @@ it "should be suitable if all provided confines return true" do confine1 = mock 'confine1', :true? => true confine2 = mock 'confine2', :true? => true - Facter::Confine.expects(:new).times(2).returns(confine1).then.returns(confine2) + Facter::Util::Confine.expects(:new).times(2).returns(confine1).then.returns(confine2) @resolve.confine :one => :two, :three => :four @resolve.should be_suitable @@ -156,13 +156,13 @@ end it "should have a class method for executing code" do - Facter::Resolution.should respond_to(:exec) + Facter::Util::Resolution.should respond_to(:exec) end # It's not possible, AFAICT, to mock %x{}, so I can't really test this bit. describe "when executing code" do it "should fail if any interpreter other than /bin/sh is requested" do - lambda { Facter::Resolution.exec("/something", "/bin/perl") }.should raise_error(ArgumentError) + lambda { Facter::Util::Resolution.exec("/something", "/bin/perl") }.should raise_error(ArgumentError) end end end From dcfc1714959bf2afa2d890b36ea3faaaab29759f Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Thu, 15 May 2008 14:55:51 -0500 Subject: [PATCH 0185/3753] Fixing the test so it doesn't break other tests. It creates a constant that affects loader behaviour, which causes other tests to break if the constant is present but not functional. --- spec/unit/util/loader.rb | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/spec/unit/util/loader.rb b/spec/unit/util/loader.rb index 4c0d777912..e6a7f17706 100755 --- a/spec/unit/util/loader.rb +++ b/spec/unit/util/loader.rb @@ -7,7 +7,19 @@ # Make sure we have a Puppet constant, so we can test # loading Puppet facts. unless defined?(Puppet) - class Puppet; end + class Puppet + # We have to implement this, because other tests will + # see this constant and fail if this method doesn't exist. + # I couldn't find a way to add and remove the constant + # just for the correct tests. + def self.settings + s = Object.new + def s.value(arg) + return "/eh" + end + s + end + end end describe Facter::Util::Loader do From bb92493c3868c54ef441a4f60c0b6a95ff742f88 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Thu, 15 May 2008 15:21:59 -0500 Subject: [PATCH 0186/3753] Fixing the last few occurrences of Facter::Resolution instead of Facter::Util::Resolution. --- lib/facter/lsb.rb | 2 +- lib/facter/networking.rb | 8 ++++---- lib/facter/os.rb | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/facter/lsb.rb b/lib/facter/lsb.rb index c3722136d7..9d9dba8a7d 100644 --- a/lib/facter/lsb.rb +++ b/lib/facter/lsb.rb @@ -25,7 +25,7 @@ unless defined?(@@lsbdata) and defined?(@@lsbtime) and (Time.now.to_i - @@lsbtime.to_i < 5) type = nil @@lsbtime = Time.now - @@lsbdata = Facter::Resolution.exec('lsb_release -a 2>/dev/null') + @@lsbdata = Facter::Util::Resolution.exec('lsb_release -a 2>/dev/null') end if pattern.match(@@lsbdata) diff --git a/lib/facter/networking.rb b/lib/facter/networking.rb index 8c80af27f2..e4cf102a22 100644 --- a/lib/facter/networking.rb +++ b/lib/facter/networking.rb @@ -29,7 +29,7 @@ # Look for the DNS domain name command first. Facter.add(:domain) do setcode do - domain = Facter::Resolution.exec('dnsdomainname') or nil + domain = Facter::Util::Resolution.exec('dnsdomainname') or nil # make sure it's a real domain if domain and domain =~ /.+\..+/ domain @@ -40,7 +40,7 @@ end Facter.add(:domain) do setcode do - domain = Facter::Resolution.exec('domainname') or nil + domain = Facter::Util::Resolution.exec('domainname') or nil # make sure it's a real domain if domain and domain =~ /.+\..+/ domain @@ -80,7 +80,7 @@ Facter.add(:hostname, :ldapname => "cn") do setcode do hostname = nil - name = Facter::Resolution.exec('hostname') or nil + name = Facter::Util::Resolution.exec('hostname') or nil if name if name =~ /^([\w-]+)\.(.+)$/ hostname = $1 @@ -133,7 +133,7 @@ if hostname = Facter.value(:hostname) # we need Hostname to exist for this to work host = nil - if host = Facter::Resolution.exec("host #{hostname}") + if host = Facter::Util::Resolution.exec("host #{hostname}") host = host.chomp.split(/\s/) if defined? list[-1] and list[-1] =~ /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ diff --git a/lib/facter/os.rb b/lib/facter/os.rb index 97d8093ef5..796df0d357 100644 --- a/lib/facter/os.rb +++ b/lib/facter/os.rb @@ -82,7 +82,7 @@ Facter.add(:operatingsystemrelease) do confine :operatingsystem => %w{CentOS} setcode do - release = Facter::Resolution.exec('rpm -q centos-release') + release = Facter::Util::Resolution.exec('rpm -q centos-release') if release =~ /release-(\d+)/ $1 end @@ -92,7 +92,7 @@ Facter.add(:operatingsystemrelease) do confine :operatingsystem => %w{Debian} setcode do - release = Facter::Resolution.exec('cat /proc/version') + release = Facter::Util::Resolution.exec('cat /proc/version') if release =~ /\(Debian (\d+.\d+).\d+-\d+\)/ $1 end @@ -102,7 +102,7 @@ Facter.add(:operatingsystemrelease) do confine :operatingsystem => %w{Ubuntu} setcode do - release = Facter::Resolution.exec('cat /etc/issue') + release = Facter::Util::Resolution.exec('cat /etc/issue') if release =~ /Ubuntu (\d+.\d+)/ $1 end From f1acbc0403068141c80b74c8585a1629fd45711b Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Thu, 15 May 2008 15:24:29 -0500 Subject: [PATCH 0187/3753] Switching Facter to using the new loader. This should make it possible to move all facts to separate files and only load them on demand. --- lib/facter.rb | 62 ++++++++++------------------------- lib/facter/util/collection.rb | 20 ++++++++++- lib/facter/util/loader.rb | 62 +++++++++-------------------------- spec/integration/facter.rb | 1 - spec/unit/facter.rb | 26 +++++++++++++++ spec/unit/util/collection.rb | 33 +++++++++++++++++++ spec/unit/util/loader.rb | 41 +++++++++++++++++++++++ 7 files changed, 153 insertions(+), 92 deletions(-) diff --git a/lib/facter.rb b/lib/facter.rb index 0f00f7d3ee..91588d380a 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -77,11 +77,18 @@ def self.[](name) end class << self - [:add, :each, :fact, :flush, :list, :to_hash, :value].each do |method| + [:add, :fact, :flush, :list, :value].each do |method| define_method(method) do |*args| collection.send(method, *args) end end + + [:list, :to_hash].each do |method| + define_method(method) do |*args| + collection.load_all + collection.send(method, *args) + end + end end @@ -91,6 +98,15 @@ def self.add(name, options = {}, &block) collection.add(name, options, &block) end + def self.each + # Make sure all facts are loaded. + collection.load_all + + collection.each do |*args| + yield(*args) + end + end + class << self # Allow users to call fact names directly on the Facter class, # either retrieving the value or comparing it to an existing value. @@ -189,49 +205,7 @@ def self.loadfacts end end - locals = [] - - # Now find all our loadable facts - factdirs = [] # All the places to check for facts - - # See if we can find any other facts in the regular Ruby lib - # paths - $:.each do |dir| - fdir = File.join(dir, "facter") - if FileTest.exists?(fdir) and FileTest.directory?(fdir) - factdirs.push(fdir) - end - end - # Also check anything in 'FACTERLIB' - if ENV['FACTERLIB'] - ENV['FACTERLIB'].split(":").each do |fdir| - factdirs.push(fdir) - end - end - factdirs.each do |fdir| - Dir.glob("#{fdir}/*.rb").each do |file| - # Load here, rather than require, because otherwise - # the facts won't get reloaded if someone calls - # "loadfacts". Really only important in testing, but, - # well, it's important in testing. - begin - load file - rescue => detail - warn "Could not load %s: %s" % - [file, detail] - end - end - end - - - # Now try to get facts from the environment - ENV.each do |name, value| - if name =~ /^facter_?(\w+)$/i - Facter.add($1) do - setcode { value } - end - end - end + collection.load_all end Facter.loadfacts diff --git a/lib/facter/util/collection.rb b/lib/facter/util/collection.rb index f76b578000..267424a0d3 100644 --- a/lib/facter/util/collection.rb +++ b/lib/facter/util/collection.rb @@ -1,5 +1,6 @@ require 'facter' require 'facter/util/fact' +require 'facter/util/loader' # Manage which facts exist and how we access them. Largely just a wrapper # around a hash of facts. @@ -39,7 +40,11 @@ def each # Return a fact by name. def fact(name) - @facts[canonize(name)] + name = canonize(name) + + loader.load(name) unless @facts[name] + + return @facts[name] end # Flush all cached values. @@ -56,6 +61,19 @@ def list return @facts.keys end + # Load all known facts. + def load_all + loader.load_all + end + + # The thing that loads facts if we don't have them. + def loader + unless defined?(@loader) + @loader = Facter::Util::Loader.new + end + @loader + end + # Return a hash of all of our facts. def to_hash @facts.inject({}) do |h, ary| diff --git a/lib/facter/util/loader.rb b/lib/facter/util/loader.rb index 7031172044..52b5f07552 100644 --- a/lib/facter/util/loader.rb +++ b/lib/facter/util/loader.rb @@ -13,8 +13,7 @@ def load(fact) # Load individual files file = File.join(dir, filename) - # We have to specify Kernel.load, because we have a load method. - Kernel.load(file) if FileTest.exist?(file) + load_file(file) if FileTest.exist?(file) # And load any directories matching the name factdir = File.join(dir, shortname) @@ -24,18 +23,24 @@ def load(fact) # Load all facts from all directories. def load_all + return if defined?(@loaded_all) + load_env search_path.each do |dir| + next unless FileTest.directory?(dir) + Dir.entries(dir).each do |file| path = File.join(dir, file) if File.directory?(path) load_dir(path) elsif file =~ /\.rb$/ - Kernel.load(File.join(dir, file)) + load_file(File.join(dir, file)) end end end + + @loaded_all = true end # The list of directories we're going to search through for facts. @@ -53,56 +58,21 @@ def search_path result end - def old_stuff - # See if we can find any other facts in the regular Ruby lib - # paths - $:.each do |dir| - fdir = File.join(dir, "facter") - if FileTest.exists?(fdir) and FileTest.directory?(fdir) - factdirs.push(fdir) - end - end - # Also check anything in 'FACTERLIB' - if ENV['FACTERLIB'] - ENV['FACTERLIB'].split(":").each do |fdir| - factdirs.push(fdir) - end - end - factdirs.each do |fdir| - Dir.glob("#{fdir}/*.rb").each do |file| - # Load here, rather than require, because otherwise - # the facts won't get reloaded if someone calls - # "loadfacts". Really only important in testing, but, - # well, it's important in testing. - begin - load file - rescue => detail - warn "Could not load %s: %s" % - [file, detail] - end - end - end - - - # Now try to get facts from the environment - ENV.each do |name, value| - if name =~ /^facter_?(\w+)$/i - Facter.add($1) do - setcode { value } - end - end - end - end - private def load_dir(dir) - return if dir =~ /\/util$/ + return if dir =~ /\/\.+$/ or dir =~ /\/util$/ or dir =~ /\/lib$/ + Dir.entries(dir).find_all { |f| f =~ /\.rb$/ }.each do |file| - Kernel.load(File.join(dir, file)) + load_file(File.join(dir, file)) end end + def load_file(file) + # We have to specify Kernel.load, because we have a load method. + Kernel.load(file) + end + # Load facts from the environment. If no name is provided, # all will be loaded. def load_env(fact = nil) diff --git a/spec/integration/facter.rb b/spec/integration/facter.rb index e1f2025b99..79a1f0f5cc 100755 --- a/spec/integration/facter.rb +++ b/spec/integration/facter.rb @@ -5,7 +5,6 @@ describe Facter do before do Facter.reset - Facter.loadfacts end after do diff --git a/spec/unit/facter.rb b/spec/unit/facter.rb index 53796ad683..59d10d440c 100755 --- a/spec/unit/facter.rb +++ b/spec/unit/facter.rb @@ -31,11 +31,23 @@ Facter.list end + it "should load all facts when listing" do + Facter.collection.expects(:load_all) + Facter.collection.stubs(:list) + Facter.list + end + it "should delegate the :to_hash method to the collection" do Facter.collection.expects(:to_hash) Facter.to_hash end + it "should load all facts when calling :to_hash" do + Facter.collection.expects(:load_all) + Facter.collection.stubs(:to_hash) + Facter.to_hash + end + it "should delegate the :value method to the collection" do Facter.collection.expects(:value) Facter.value @@ -46,6 +58,20 @@ Facter.each end + it "should load all facts when calling :each" do + Facter.collection.expects(:load_all) + Facter.collection.stubs(:each) + Facter.each + end + + it "should yield to the block when using :each" do + Facter.collection.stubs(:load_all) + Facter.collection.stubs(:each).yields "foo" + result = [] + Facter.each { |f| result << f } + result.should == %w{foo} + end + describe "when provided code as a string" do it "should execute the code in the shell" do Facter.add("shell_testing") do diff --git a/spec/unit/util/collection.rb b/spec/unit/util/collection.rb index e88de06fca..9ea2e9d0fa 100755 --- a/spec/unit/util/collection.rb +++ b/spec/unit/util/collection.rb @@ -9,6 +9,29 @@ Facter::Util::Collection.new.should respond_to(:add) end + it "should have a method for returning a loader" do + Facter::Util::Collection.new.should respond_to(:loader) + end + + it "should use an instance of the Loader class as its loader" do + Facter::Util::Collection.new.loader.should be_instance_of(Facter::Util::Loader) + end + + it "should cache its loader" do + coll = Facter::Util::Collection.new + coll.loader.should equal(coll.loader) + end + + it "should have a method for loading all facts" do + Facter::Util::Collection.new.should respond_to(:load_all) + end + + it "should delegate its load_all method to its loader" do + coll = Facter::Util::Collection.new + coll.loader.expects(:load_all) + coll.load_all + end + describe "when adding facts" do before do @coll = Facter::Util::Collection.new @@ -55,6 +78,16 @@ it "should treat strings and symbols equivalently" do @coll.fact(:yayness).should equal(@fact) end + + it "should use its loader to try to load the fact if no fact can be found" do + @coll.loader.expects(:load).with(:testing) + @coll.fact("testing") + end + + it "should return nil if it cannot find or load the fact" do + @coll.loader.expects(:load).with(:testing) + @coll.fact("testing").should be_nil + end end it "should have a method for returning a fact's value" do diff --git a/spec/unit/util/loader.rb b/spec/unit/util/loader.rb index e6a7f17706..690ec03a30 100755 --- a/spec/unit/util/loader.rb +++ b/spec/unit/util/loader.rb @@ -149,6 +149,18 @@ def with_env(values) before do @loader = Facter::Util::Loader.new @loader.stubs(:search_path).returns [] + + FileTest.stubs(:directory?).returns true + end + + it "should skip directories that do not exist" do + @loader.expects(:search_path).returns %w{/one/dir} + + FileTest.expects(:directory?).with("/one/dir").returns false + + Dir.expects(:entries).with("/one/dir").never + + @loader.load_all end it "should load all files in all search paths" do @@ -190,6 +202,29 @@ def with_env(values) @loader.load_all end + it "should not load files in a lib subdirectory" do + @loader.expects(:search_path).returns %w{/one/dir} + + Dir.expects(:entries).with("/one/dir").returns %w{lib} + + File.expects(:directory?).with("/one/dir/lib").returns true + + Dir.expects(:entries).with("/one/dir/lib").never + + @loader.load_all + end + + it "should not load files in '.' or '..'" do + @loader.expects(:search_path).returns %w{/one/dir} + + Dir.expects(:entries).with("/one/dir").returns %w{. ..} + + File.expects(:entries).with("/one/dir/.").never + File.expects(:entries).with("/one/dir/..").never + + @loader.load_all + end + it "should load all facts from the environment" do Facter.expects(:add).with('one') Facter.expects(:add).with('two') @@ -198,5 +233,11 @@ def with_env(values) @loader.load_all end end + + it "should only load all facts one time" do + @loader.expects(:load_env).once + @loader.load_all + @loader.load_all + end end end From aaaf767c71d1c8ae9993c73c38832c66d63fc606 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Thu, 15 May 2008 15:27:57 -0500 Subject: [PATCH 0188/3753] Moving the version and ruby facts to a separate file. --- lib/facter.rb | 28 ---------------------------- lib/facter/core.rb | 27 +++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 28 deletions(-) create mode 100644 lib/facter/core.rb diff --git a/lib/facter.rb b/lib/facter.rb index 91588d380a..783bb833e2 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -177,34 +177,6 @@ def self.reset # Load all of the default facts, and then everything from disk. def self.loadfacts - Facter.add(:facterversion) do - setcode { FACTERVERSION.to_s } - end - - Facter.add(:rubyversion) do - setcode { RUBY_VERSION.to_s } - end - - Facter.add(:puppetversion) do - setcode { - begin - require 'puppet' - Puppet::PUPPETVERSION.to_s - rescue LoadError - nil - end - } - end - - Facter.add :rubysitedir do - setcode do - version = RUBY_VERSION.to_s.sub(/\.\d+$/, '') - $:.find do |dir| - dir =~ /#{File.join("site_ruby", version)}$/ - end - end - end - collection.load_all end diff --git a/lib/facter/core.rb b/lib/facter/core.rb new file mode 100644 index 0000000000..0c5fdd5964 --- /dev/null +++ b/lib/facter/core.rb @@ -0,0 +1,27 @@ +Facter.add(:facterversion) do + setcode { Facter::FACTERVERSION.to_s } +end + +Facter.add(:rubyversion) do + setcode { RUBY_VERSION.to_s } +end + +Facter.add(:puppetversion) do + setcode { + begin + require 'puppet' + Puppet::PUPPETVERSION.to_s + rescue LoadError + nil + end + } +end + +Facter.add :rubysitedir do + setcode do + version = RUBY_VERSION.to_s.sub(/\.\d+$/, '') + $:.find do |dir| + dir =~ /#{File.join("site_ruby", version)}$/ + end + end +end From 9c91a6d3fb9da933d8e1ba4149ee1b5587e6b007 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Thu, 15 May 2008 15:40:02 -0500 Subject: [PATCH 0189/3753] Facter no longer loads all facts by default. This should not have an impact on normal usage, since Facter loads them all on demand. It's much faster now for loading individual facts, though. --- lib/facter.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/facter.rb b/lib/facter.rb index 783bb833e2..30f363dec2 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -179,6 +179,4 @@ def self.reset def self.loadfacts collection.load_all end - - Facter.loadfacts end From e02b0b386ed84384daeb2da0879bf215cfcfa2ef Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sat, 17 May 2008 00:32:34 +1000 Subject: [PATCH 0190/3753] Updated version. Moved most facts to seperate files. --- bin/facter | 4 +- lib/facter.rb | 2 +- lib/facter/{cfengine.rb => Cfkey.rb} | 2 +- lib/facter/architecture.rb | 14 ++ lib/facter/core.rb | 27 --- lib/facter/domain.rb | 64 ++++++ lib/facter/facterversion.rb | 3 + lib/facter/fqdn.rb | 10 + lib/facter/hardwareisa.rb | 4 + lib/facter/hardwaremodel.rb | 3 + lib/facter/hostname.rb | 25 +++ lib/facter/id.rb | 4 + lib/facter/ipaddress.rb | 131 +++++++++++ lib/facter/iphostnumber.rb | 18 ++ lib/facter/ipmess.rb | 14 +- lib/facter/kernel.rb | 43 ---- lib/facter/kernelrelease.rb | 3 + lib/facter/macaddress.rb | 42 ++++ lib/facter/manufacturer.rb | 18 +- lib/facter/memory.rb | 13 +- lib/facter/netmask.rb | 25 +-- lib/facter/networking.rb | 310 --------------------------- lib/facter/operatingsystem.rb | 37 ++++ lib/facter/operatingsystemrelease.rb | 61 ++++++ lib/facter/os.rb | 115 ---------- lib/facter/process.rb | 29 --- lib/facter/processor.rb | 13 -- lib/facter/ps.rb | 8 + lib/facter/puppetversion.rb | 10 + lib/facter/rubysitedir.rb | 8 + lib/facter/rubyversion.rb | 3 + lib/facter/uniqueid.rb | 4 + 32 files changed, 469 insertions(+), 598 deletions(-) rename lib/facter/{cfengine.rb => Cfkey.rb} (99%) create mode 100644 lib/facter/architecture.rb delete mode 100644 lib/facter/core.rb create mode 100644 lib/facter/domain.rb create mode 100644 lib/facter/facterversion.rb create mode 100644 lib/facter/fqdn.rb create mode 100644 lib/facter/hardwareisa.rb create mode 100644 lib/facter/hardwaremodel.rb create mode 100644 lib/facter/hostname.rb create mode 100644 lib/facter/id.rb create mode 100644 lib/facter/ipaddress.rb create mode 100644 lib/facter/iphostnumber.rb create mode 100644 lib/facter/kernelrelease.rb create mode 100644 lib/facter/macaddress.rb delete mode 100644 lib/facter/networking.rb create mode 100644 lib/facter/operatingsystem.rb create mode 100644 lib/facter/operatingsystemrelease.rb delete mode 100644 lib/facter/os.rb delete mode 100644 lib/facter/process.rb create mode 100644 lib/facter/ps.rb create mode 100644 lib/facter/puppetversion.rb create mode 100644 lib/facter/rubysitedir.rb create mode 100644 lib/facter/rubyversion.rb create mode 100644 lib/facter/uniqueid.rb diff --git a/bin/facter b/bin/facter index e9604abbda..06d61edce7 100755 --- a/bin/facter +++ b/bin/facter @@ -74,8 +74,8 @@ options = { result.each { |opt,arg| case opt when "--version" - puts "%s" % Facter.version - exit + puts "%s" % Facter.version + exit when "--yaml" options[:yaml] = true when "--debug" diff --git a/lib/facter.rb b/lib/facter.rb index 30f363dec2..aae332656b 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -27,7 +27,7 @@ module Util; end include Comparable include Enumerable - FACTERVERSION = '1.3.8' + FACTERVERSION = '2.0.0' # = Facter # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. diff --git a/lib/facter/cfengine.rb b/lib/facter/Cfkey.rb similarity index 99% rename from lib/facter/cfengine.rb rename to lib/facter/Cfkey.rb index 6db24db9e8..794971b18f 100644 --- a/lib/facter/cfengine.rb +++ b/lib/facter/Cfkey.rb @@ -1,4 +1,4 @@ -## cfengine.rb +## Cfkey.rb ## Facts related to cfengine ## ## This program is free software; you can redistribute it and/or diff --git a/lib/facter/architecture.rb b/lib/facter/architecture.rb new file mode 100644 index 0000000000..5db1834b1c --- /dev/null +++ b/lib/facter/architecture.rb @@ -0,0 +1,14 @@ + Facter.add(:architecture) do + confine :kernel => :linux + setcode do + model = Facter.value(:hardwaremodel) + case model + # most linuxen use "x86_64" + when 'x86_64': + Facter.value(:operatingsystem) == "Debian" ? "amd64" : model; + when /(i[3456]86|pentium)/: "i386" + else + model + end + end + end diff --git a/lib/facter/core.rb b/lib/facter/core.rb deleted file mode 100644 index 0c5fdd5964..0000000000 --- a/lib/facter/core.rb +++ /dev/null @@ -1,27 +0,0 @@ -Facter.add(:facterversion) do - setcode { Facter::FACTERVERSION.to_s } -end - -Facter.add(:rubyversion) do - setcode { RUBY_VERSION.to_s } -end - -Facter.add(:puppetversion) do - setcode { - begin - require 'puppet' - Puppet::PUPPETVERSION.to_s - rescue LoadError - nil - end - } -end - -Facter.add :rubysitedir do - setcode do - version = RUBY_VERSION.to_s.sub(/\.\d+$/, '') - $:.find do |dir| - dir =~ /#{File.join("site_ruby", version)}$/ - end - end -end diff --git a/lib/facter/domain.rb b/lib/facter/domain.rb new file mode 100644 index 0000000000..a4fa701aac --- /dev/null +++ b/lib/facter/domain.rb @@ -0,0 +1,64 @@ + Facter.add(:domain) do + setcode do + # First force the hostname to be checked + Facter.value(:hostname) + + # Now check to see if it set the domain + if defined? $domain and ! $domain.nil? + $domain + else + nil + end + end + end + # Look for the DNS domain name command first. + Facter.add(:domain) do + setcode do + domain = Facter::Util::Resolution.exec('dnsdomainname') or nil + # make sure it's a real domain + if domain and domain =~ /.+\..+/ + domain + else + nil + end + end + end + Facter.add(:domain) do + setcode do + domain = Facter::Util::Resolution.exec('domainname') or nil + # make sure it's a real domain + if domain and domain =~ /.+\..+/ + domain + else + nil + end + end + end + Facter.add(:domain) do + setcode do + value = nil + if FileTest.exists?("/etc/resolv.conf") + File.open("/etc/resolv.conf") { |file| + # is the domain set? + file.each { |line| + if line =~ /domain\s+(\S+)/ + value = $1 + break + end + } + } + ! value and File.open("/etc/resolv.conf") { |file| + # is the search path set? + file.each { |line| + if line =~ /search\s+(\S+)/ + value = $1 + break + end + } + } + value + else + nil + end + end + end diff --git a/lib/facter/facterversion.rb b/lib/facter/facterversion.rb new file mode 100644 index 0000000000..0b1cfda024 --- /dev/null +++ b/lib/facter/facterversion.rb @@ -0,0 +1,3 @@ +Facter.add(:facterversion) do + setcode { Facter::FACTERVERSION.to_s } +end diff --git a/lib/facter/fqdn.rb b/lib/facter/fqdn.rb new file mode 100644 index 0000000000..1a97401d2c --- /dev/null +++ b/lib/facter/fqdn.rb @@ -0,0 +1,10 @@ + Facter.add(:fqdn) do setcode do + host = Facter.value(:hostname) + domain = Facter.value(:domain) + if host and domain + [host, domain].join(".") + else + nil + end + end + end diff --git a/lib/facter/hardwareisa.rb b/lib/facter/hardwareisa.rb new file mode 100644 index 0000000000..20d0e47c61 --- /dev/null +++ b/lib/facter/hardwareisa.rb @@ -0,0 +1,4 @@ + Facter.add(:hardwareisa) do + setcode 'uname -p', '/bin/sh' + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE Debian Gentoo FreeBSD OpenBSD NetBSD} + end diff --git a/lib/facter/hardwaremodel.rb b/lib/facter/hardwaremodel.rb new file mode 100644 index 0000000000..8cc53f0479 --- /dev/null +++ b/lib/facter/hardwaremodel.rb @@ -0,0 +1,3 @@ + Facter.add(:hardwaremodel) do + setcode 'uname -m' + end diff --git a/lib/facter/hostname.rb b/lib/facter/hostname.rb new file mode 100644 index 0000000000..aec5f936bc --- /dev/null +++ b/lib/facter/hostname.rb @@ -0,0 +1,25 @@ + Facter.add(:hostname, :ldapname => "cn") do + setcode do + hostname = nil + name = Facter::Util::Resolution.exec('hostname') or nil + if name + if name =~ /^([\w-]+)\.(.+)$/ + hostname = $1 + # the Domain class uses this + $domain = $2 + else + hostname = name + end + hostname + else + nil + end + end + end + + Facter.add(:hostname) do + confine :kernel => :darwin, :kernelrelease => "R7" + setcode do + %x{/usr/sbin/scutil --get LocalHostName} + end + end diff --git a/lib/facter/id.rb b/lib/facter/id.rb new file mode 100644 index 0000000000..e66745fc27 --- /dev/null +++ b/lib/facter/id.rb @@ -0,0 +1,4 @@ + Facter.add(:id) do + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE Debian Gentoo} + setcode "whoami" + end diff --git a/lib/facter/ipaddress.rb b/lib/facter/ipaddress.rb new file mode 100644 index 0000000000..cd6dbcfb00 --- /dev/null +++ b/lib/facter/ipaddress.rb @@ -0,0 +1,131 @@ + Facter.add(:ipaddress, :ldapname => "iphostnumber") do + setcode do + require 'resolv' + + begin + if hostname = Facter.value(:hostname) + ip = Resolv.getaddress(hostname) + unless ip == "127.0.0.1" + ip + end + else + nil + end + rescue Resolv::ResolvError + nil + rescue NoMethodError # i think this is a bug in resolv.rb? + nil + end + end + end + + Facter.add(:ipaddress) do + setcode do + if hostname = Facter.value(:hostname) + # we need Hostname to exist for this to work + host = nil + if host = Facter::Util::Resolution.exec("host #{hostname}") + host = host.chomp.split(/\s/) + if defined? list[-1] and + list[-1] =~ /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ + list[-1] + end + else + nil + end + else + nil + end + end + end + + Facter.add(:ipaddress) do + confine :kernel => :linux + setcode do + ip = nil + output = %x{/sbin/ifconfig} + + output.split(/^\S/).each { |str| + if str =~ /inet addr:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + tmp = $1 + unless tmp =~ /127\./ + ip = tmp + break + end + end + } + + ip + end + end + + Facter.add(:ipaddress) do + confine :kernel => %w{FreeBSD OpenBSD solaris} + setcode do + ip = nil + output = %x{/sbin/ifconfig} + + output.split(/^\S/).each { |str| + if str =~ /inet ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + tmp = $1 + unless tmp =~ /127\./ + ip = tmp + break + end + end + } + + ip + end + end + + Facter.add(:ipaddress) do + confine :kernel => %w{NetBSD} + setcode do + ip = nil + output = %x{/sbin/ifconfig -a} + + output.split(/^\S/).each { |str| + if str =~ /inet ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + tmp = $1 + unless tmp =~ /127\./ + ip = tmp + break + end + end + } + + ip + end + end + + Facter.add(:ipaddress) do + confine :kernel => %w{darwin} + setcode do + ip = nil + iface = "" + output = %x{/usr/sbin/netstat -rn} + if output =~ /^default\s*\S*\s*\S*\s*\S*\s*\S*\s*(\S*).*/ + iface = $1 + else + warn "Could not find a default route. Using first non-loopback interface" + end + if(iface != "") + output = %x{/sbin/ifconfig #{iface}} + else + output = %x{/sbin/ifconfig} + end + + output.split(/^\S/).each { |str| + if str =~ /inet ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + tmp = $1 + unless tmp =~ /127\./ + ip = tmp + break + end + end + } + + ip + end + end diff --git a/lib/facter/iphostnumber.rb b/lib/facter/iphostnumber.rb new file mode 100644 index 0000000000..68b1c9720d --- /dev/null +++ b/lib/facter/iphostnumber.rb @@ -0,0 +1,18 @@ + Facter.add(:iphostnumber) do + confine :kernel => :darwin, :kernelrelease => "R6" + setcode do + %x{/usr/sbin/scutil --get LocalHostName} + end + end + Facter.add(:iphostnumber) do + confine :kernel => :darwin, :kernelrelease => "R6" + setcode do + ether = nil + output = %x{/sbin/ifconfig} + + output =~ /HWaddr (\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ + ether = $1 + + ether + end + end diff --git a/lib/facter/ipmess.rb b/lib/facter/ipmess.rb index 1e0c92361a..0ddc3152f0 100644 --- a/lib/facter/ipmess.rb +++ b/lib/facter/ipmess.rb @@ -1,21 +1,9 @@ -# # ipmess.rb -# Try to get additional Facts about the machine's network interfaces on Linux +# Try to get additional Facts about the machine's network interfaces # # Original concept Copyright (C) 2007 psychedelys # Update and *BSD support (C) 2007 James Turnbull # -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation (version 2 of the License) -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA -# require 'facter/util/ip' diff --git a/lib/facter/kernel.rb b/lib/facter/kernel.rb index 68f887adab..4a453c31fa 100644 --- a/lib/facter/kernel.rb +++ b/lib/facter/kernel.rb @@ -1,46 +1,3 @@ -## kernel.rb -## Facts related to the kernel, architecture and related -## -## This program is free software; you can redistribute it and/or -## modify it under the terms of the GNU General Public License -## as published by the Free Software Foundation (version 2 of the License) -## This program is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. -## You should have received a copy of the GNU General Public License -## along with this program; if not, write to the Free Software -## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA -## - Facter.add(:kernel) do setcode 'uname -s' end - - Facter.add(:kernelrelease) do - setcode 'uname -r' - end - - Facter.add(:hardwaremodel) do - setcode 'uname -m' - end - - Facter.add(:architecture) do - confine :kernel => :linux - setcode do - model = Facter.value(:hardwaremodel) - case model - # most linuxen use "x86_64" - when 'x86_64': - Facter.value(:operatingsystem) == "Debian" ? "amd64" : model; - when /(i[3456]86|pentium)/: "i386" - else - model - end - end - end - - Facter.add(:hardwareisa) do - setcode 'uname -p', '/bin/sh' - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE Debian Gentoo FreeBSD OpenBSD NetBSD} - end diff --git a/lib/facter/kernelrelease.rb b/lib/facter/kernelrelease.rb new file mode 100644 index 0000000000..9766ad9221 --- /dev/null +++ b/lib/facter/kernelrelease.rb @@ -0,0 +1,3 @@ + Facter.add(:kernelrelease) do + setcode 'uname -r' + end diff --git a/lib/facter/macaddress.rb b/lib/facter/macaddress.rb new file mode 100644 index 0000000000..647f39d18b --- /dev/null +++ b/lib/facter/macaddress.rb @@ -0,0 +1,42 @@ + Facter.add(:macaddress) do + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE Debian Gentoo} + setcode do + ether = [] + output = %x{/sbin/ifconfig -a} + output.each {|s| + ether.push($1) if s =~ /(?:ether|HWaddr) (\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/ + } + ether[0] + end + end + + Facter.add(:macaddress) do + confine :operatingsystem => %w{FreeBSD OpenBSD} + setcode do + ether = [] + output = %x{/sbin/ifconfig} + output.each {|s| + if s =~ /(?:ether|lladdr)\s+(\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ + ether.push($1) + end + } + ether[0] + end + end + + Facter.add(:macaddress) do + confine :kernel => :darwin + setcode do + ether = nil + output = %x{/sbin/ifconfig} + + output.split(/^\S/).each { |str| + if str =~ /10baseT/ # we're wired + str =~ /ether (\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ + ether = $1 + end + } + + ether + end + end diff --git a/lib/facter/manufacturer.rb b/lib/facter/manufacturer.rb index 67ca569f71..0c74607944 100644 --- a/lib/facter/manufacturer.rb +++ b/lib/facter/manufacturer.rb @@ -1,17 +1,7 @@ -## manufacturer.rb -## Facts related to hardware manufacturer -## -## This program is free software; you can redistribute it and/or -## modify it under the terms of the GNU General Public License -## as published by the Free Software Foundation (version 2 of the License) -## This program is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. -## You should have received a copy of the GNU General Public License -## along with this program; if not, write to the Free Software -## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA -## +# manufacturer.rb +# Facts related to hardware manufacturer +# +# require 'facter/util/manufacturer' diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb index c507089c5a..46fe8597de 100644 --- a/lib/facter/memory.rb +++ b/lib/facter/memory.rb @@ -1,21 +1,10 @@ -# # memory.rb # Additional Facts for memory/swap usage # # Copyright (C) 2006 Mooter Media Ltd # Author: Matthew Palmer # -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation (version 2 of the License) -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA - +# require 'facter/util/memory' {:MemorySize => "MemTotal", diff --git a/lib/facter/netmask.rb b/lib/facter/netmask.rb index 44a484a85c..140f8f14d9 100644 --- a/lib/facter/netmask.rb +++ b/lib/facter/netmask.rb @@ -1,21 +1,10 @@ -## netmask.rb -## Find the netmask of the primary ipaddress -## Copyright (C) 2007 David Schmitt -## Copyright (C) 2007 Mark 'phips' Phillips -## -## idea and originial source by Mark 'phips' Phillips -## -## This program is free software; you can redistribute it and/or -## modify it under the terms of the GNU General Public License -## as published by the Free Software Foundation (version 2 of the License) -## This program is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. -## You should have received a copy of the GNU General Public License -## along with this program; if not, write to the Free Software -## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA -## +# netmask.rb +# Find the netmask of the primary ipaddress +# Copyright (C) 2007 David Schmitt +# Copyright (C) 2007 Mark 'phips' Phillips +# +# idea and originial source by Mark 'phips' Phillips +# def get_netmask netmask = nil; diff --git a/lib/facter/networking.rb b/lib/facter/networking.rb deleted file mode 100644 index e4cf102a22..0000000000 --- a/lib/facter/networking.rb +++ /dev/null @@ -1,310 +0,0 @@ -## networking.rb -## Facts related to networking -## -## This program is free software; you can redistribute it and/or -## modify it under the terms of the GNU General Public License -## as published by the Free Software Foundation (version 2 of the License) -## This program is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. -## You should have received a copy of the GNU General Public License -## along with this program; if not, write to the Free Software -## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA -## - - Facter.add(:domain) do - setcode do - # First force the hostname to be checked - Facter.value(:hostname) - - # Now check to see if it set the domain - if defined? $domain and ! $domain.nil? - $domain - else - nil - end - end - end - # Look for the DNS domain name command first. - Facter.add(:domain) do - setcode do - domain = Facter::Util::Resolution.exec('dnsdomainname') or nil - # make sure it's a real domain - if domain and domain =~ /.+\..+/ - domain - else - nil - end - end - end - Facter.add(:domain) do - setcode do - domain = Facter::Util::Resolution.exec('domainname') or nil - # make sure it's a real domain - if domain and domain =~ /.+\..+/ - domain - else - nil - end - end - end - Facter.add(:domain) do - setcode do - value = nil - if FileTest.exists?("/etc/resolv.conf") - File.open("/etc/resolv.conf") { |file| - # is the domain set? - file.each { |line| - if line =~ /domain\s+(\S+)/ - value = $1 - break - end - } - } - ! value and File.open("/etc/resolv.conf") { |file| - # is the search path set? - file.each { |line| - if line =~ /search\s+(\S+)/ - value = $1 - break - end - } - } - value - else - nil - end - end - end - Facter.add(:hostname, :ldapname => "cn") do - setcode do - hostname = nil - name = Facter::Util::Resolution.exec('hostname') or nil - if name - if name =~ /^([\w-]+)\.(.+)$/ - hostname = $1 - # the Domain class uses this - $domain = $2 - else - hostname = name - end - hostname - else - nil - end - end - end - - Facter.add(:fqdn) do - setcode do - host = Facter.value(:hostname) - domain = Facter.value(:domain) - if host and domain - [host, domain].join(".") - else - nil - end - end - end - - Facter.add(:ipaddress, :ldapname => "iphostnumber") do - setcode do - require 'resolv' - - begin - if hostname = Facter.value(:hostname) - ip = Resolv.getaddress(hostname) - unless ip == "127.0.0.1" - ip - end - else - nil - end - rescue Resolv::ResolvError - nil - rescue NoMethodError # i think this is a bug in resolv.rb? - nil - end - end - end - Facter.add(:ipaddress) do - setcode do - if hostname = Facter.value(:hostname) - # we need Hostname to exist for this to work - host = nil - if host = Facter::Util::Resolution.exec("host #{hostname}") - host = host.chomp.split(/\s/) - if defined? list[-1] and - list[-1] =~ /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ - list[-1] - end - else - nil - end - else - nil - end - end - end - - Facter.add(:uniqueid) do - setcode 'hostid', '/bin/sh' - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE Debian Gentoo} - end - - Facter.add(:macaddress) do - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE Debian Gentoo} - setcode do - ether = [] - output = %x{/sbin/ifconfig -a} - output.each {|s| - ether.push($1) if s =~ /(?:ether|HWaddr) (\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/ - } - ether[0] - end - end - - Facter.add(:macaddress) do - confine :operatingsystem => %w{FreeBSD OpenBSD} - setcode do - ether = [] - output = %x{/sbin/ifconfig} - output.each {|s| - if s =~ /(?:ether|lladdr)\s+(\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ - ether.push($1) - end - } - ether[0] - end - end - - Facter.add(:macaddress) do - confine :kernel => :darwin - setcode do - ether = nil - output = %x{/sbin/ifconfig} - - output.split(/^\S/).each { |str| - if str =~ /10baseT/ # we're wired - str =~ /ether (\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ - ether = $1 - end - } - - ether - end - end - - Facter.add(:ipaddress) do - confine :kernel => :linux - setcode do - ip = nil - output = %x{/sbin/ifconfig} - - output.split(/^\S/).each { |str| - if str =~ /inet addr:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ - tmp = $1 - unless tmp =~ /127\./ - ip = tmp - break - end - end - } - - ip - end - end - Facter.add(:ipaddress) do - confine :kernel => %w{FreeBSD OpenBSD solaris} - setcode do - ip = nil - output = %x{/sbin/ifconfig} - - output.split(/^\S/).each { |str| - if str =~ /inet ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ - tmp = $1 - unless tmp =~ /127\./ - ip = tmp - break - end - end - } - - ip - end - end - Facter.add(:ipaddress) do - confine :kernel => %w{NetBSD} - setcode do - ip = nil - output = %x{/sbin/ifconfig -a} - - output.split(/^\S/).each { |str| - if str =~ /inet ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ - tmp = $1 - unless tmp =~ /127\./ - ip = tmp - break - end - end - } - - ip - end - end - Facter.add(:ipaddress) do - confine :kernel => %w{darwin} - setcode do - ip = nil - iface = "" - output = %x{/usr/sbin/netstat -rn} - if output =~ /^default\s*\S*\s*\S*\s*\S*\s*\S*\s*(\S*).*/ - iface = $1 - else - warn "Could not find a default route. Using first non-loopback interface" - end - if(iface != "") - output = %x{/sbin/ifconfig #{iface}} - else - output = %x{/sbin/ifconfig} - end - - output.split(/^\S/).each { |str| - if str =~ /inet ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ - tmp = $1 - unless tmp =~ /127\./ - ip = tmp - break - end - end - } - - ip - end - end - Facter.add(:hostname) do - confine :kernel => :darwin, :kernelrelease => "R7" - setcode do - %x{/usr/sbin/scutil --get LocalHostName} - end - end - Facter.add(:iphostnumber) do - confine :kernel => :darwin, :kernelrelease => "R6" - setcode do - %x{/usr/sbin/scutil --get LocalHostName} - end - end - Facter.add(:iphostnumber) do - confine :kernel => :darwin, :kernelrelease => "R6" - setcode do - ether = nil - output = %x{/sbin/ifconfig} - - output =~ /HWaddr (\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ - ether = $1 - - ether - end - end - diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb new file mode 100644 index 0000000000..6233aaa56f --- /dev/null +++ b/lib/facter/operatingsystem.rb @@ -0,0 +1,37 @@ + Facter.add(:operatingsystem) do + confine :kernel => :sunos + setcode do "Solaris" end + end + + Facter.add(:operatingsystem) do + confine :kernel => :linux + setcode do + if Facter.value(:lsbdistid) == "Ubuntu" + "Ubuntu" + elsif FileTest.exists?("/etc/debian_version") + "Debian" + elsif FileTest.exists?("/etc/gentoo-release") + "Gentoo" + elsif FileTest.exists?("/etc/fedora-release") + "Fedora" + elsif FileTest.exists?("/etc/mandriva-release") + "Mandriva" + elsif FileTest.exists?("/etc/mandrake-release") + "Mandrake" + elsif FileTest.exists?("/etc/redhat-release") + txt = File.read("/etc/redhat-release") + if txt =~ /centos/i + "CentOS" + else + "RedHat" + end + elsif FileTest.exists?("/etc/SuSE-release") + "SuSE" + end + end + end + + Facter.add(:operatingsystem) do + # Default to just returning the kernel as the operating system + setcode do Facter[:kernel].value end + end diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb new file mode 100644 index 0000000000..7991d6ce90 --- /dev/null +++ b/lib/facter/operatingsystemrelease.rb @@ -0,0 +1,61 @@ + Facter.add(:operatingsystemrelease) do + confine :operatingsystem => :fedora + setcode do + File::open("/etc/fedora-release", "r") do |f| + line = f.readline.chomp + if line =~ /\(Rawhide\)$/ + "Rawhide" + elsif line =~ /release (\d+)/ + $1 + end + end + end + end + + Facter.add(:operatingsystemrelease) do + confine :operatingsystem => %w{RedHat} + setcode do + File::open("/etc/redhat-release", "r") do |f| + line = f.readline.chomp + if line =~ /\(Rawhide\)$/ + "Rawhide" + elsif line =~ /release (\d+)/ + $1 + end + end + end + end + + Facter.add(:operatingsystemrelease) do + confine :operatingsystem => %w{CentOS} + setcode do + release = Facter::Util::Resolution.exec('rpm -q centos-release') + if release =~ /release-(\d+)/ + $1 + end + end + end + + Facter.add(:operatingsystemrelease) do + confine :operatingsystem => %w{Debian} + setcode do + release = Facter::Util::Resolution.exec('cat /proc/version') + if release =~ /\(Debian (\d+.\d+).\d+-\d+\)/ + $1 + end + end + end + + Facter.add(:operatingsystemrelease) do + confine :operatingsystem => %w{Ubuntu} + setcode do + release = Facter::Util::Resolution.exec('cat /etc/issue') + if release =~ /Ubuntu (\d+.\d+)/ + $1 + end + end + end + + Facter.add(:operatingsystemrelease) do + setcode do Facter[:kernelrelease].value end + end diff --git a/lib/facter/os.rb b/lib/facter/os.rb deleted file mode 100644 index 796df0d357..0000000000 --- a/lib/facter/os.rb +++ /dev/null @@ -1,115 +0,0 @@ -## os.rb -## Facts related to operating systems and releases -## -## This program is free software; you can redistribute it and/or -## modify it under the terms of the GNU General Public License -## as published by the Free Software Foundation (version 2 of the License) -## This program is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. -## You should have received a copy of the GNU General Public License -## along with this program; if not, write to the Free Software -## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA -## - - Facter.add(:operatingsystem) do - confine :kernel => :sunos - setcode do "Solaris" end - end - - Facter.add(:operatingsystem) do - confine :kernel => :linux - setcode do - if Facter.value(:lsbdistid) == "Ubuntu" - "Ubuntu" - elsif FileTest.exists?("/etc/debian_version") - "Debian" - elsif FileTest.exists?("/etc/gentoo-release") - "Gentoo" - elsif FileTest.exists?("/etc/fedora-release") - "Fedora" - elsif FileTest.exists?("/etc/mandriva-release") - "Mandriva" - elsif FileTest.exists?("/etc/mandrake-release") - "Mandrake" - elsif FileTest.exists?("/etc/redhat-release") - txt = File.read("/etc/redhat-release") - if txt =~ /centos/i - "CentOS" - else - "RedHat" - end - elsif FileTest.exists?("/etc/SuSE-release") - "SuSE" - end - end - end - - Facter.add(:operatingsystem) do - # Default to just returning the kernel as the operating system - setcode do Facter[:kernel].value end - end - - Facter.add(:operatingsystemrelease) do - confine :operatingsystem => :fedora - setcode do - File::open("/etc/fedora-release", "r") do |f| - line = f.readline.chomp - if line =~ /\(Rawhide\)$/ - "Rawhide" - elsif line =~ /release (\d+)/ - $1 - end - end - end - end - - Facter.add(:operatingsystemrelease) do - confine :operatingsystem => %w{RedHat} - setcode do - File::open("/etc/redhat-release", "r") do |f| - line = f.readline.chomp - if line =~ /\(Rawhide\)$/ - "Rawhide" - elsif line =~ /release (\d+)/ - $1 - end - end - end - end - - Facter.add(:operatingsystemrelease) do - confine :operatingsystem => %w{CentOS} - setcode do - release = Facter::Util::Resolution.exec('rpm -q centos-release') - if release =~ /release-(\d+)/ - $1 - end - end - end - - Facter.add(:operatingsystemrelease) do - confine :operatingsystem => %w{Debian} - setcode do - release = Facter::Util::Resolution.exec('cat /proc/version') - if release =~ /\(Debian (\d+.\d+).\d+-\d+\)/ - $1 - end - end - end - - Facter.add(:operatingsystemrelease) do - confine :operatingsystem => %w{Ubuntu} - setcode do - release = Facter::Util::Resolution.exec('cat /etc/issue') - if release =~ /Ubuntu (\d+.\d+)/ - $1 - end - end - end - - Facter.add(:operatingsystemrelease) do - setcode do Facter[:kernelrelease].value end - end - diff --git a/lib/facter/process.rb b/lib/facter/process.rb deleted file mode 100644 index beeac47e99..0000000000 --- a/lib/facter/process.rb +++ /dev/null @@ -1,29 +0,0 @@ -## process.rb -## Facts related to ps and processes -## -## This program is free software; you can redistribute it and/or -## modify it under the terms of the GNU General Public License -## as published by the Free Software Foundation (version 2 of the License) -## This program is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. -## You should have received a copy of the GNU General Public License -## along with this program; if not, write to the Free Software -## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA -## - Facter.add(:ps) do - setcode do 'ps -ef' end - end - - Facter.add(:ps) do - confine :operatingsystem => %w{FreeBSD NetBSD OpenBSD Darwin} - setcode do 'ps -auxwww' end - end - - Facter.add(:id) do - #confine :kernel => %w{Solaris Linux} - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE Debian Gentoo} - setcode "whoami" - end - diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb index 069e06e7e8..5e8e42eacb 100644 --- a/lib/facter/processor.rb +++ b/lib/facter/processor.rb @@ -1,22 +1,9 @@ -# # processor.rb # Additional Facts about the machine's CPUs # # Copyright (C) 2006 Mooter Media Ltd # Author: Matthew Palmer # -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation (version 2 of the License) -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA -# require 'thread' diff --git a/lib/facter/ps.rb b/lib/facter/ps.rb new file mode 100644 index 0000000000..fbf0c7fcd1 --- /dev/null +++ b/lib/facter/ps.rb @@ -0,0 +1,8 @@ + Facter.add(:ps) do + setcode do 'ps -ef' end + end + + Facter.add(:ps) do + confine :operatingsystem => %w{FreeBSD NetBSD OpenBSD Darwin} + setcode do 'ps -auxwww' end + end diff --git a/lib/facter/puppetversion.rb b/lib/facter/puppetversion.rb new file mode 100644 index 0000000000..66fcfe8b21 --- /dev/null +++ b/lib/facter/puppetversion.rb @@ -0,0 +1,10 @@ +Facter.add(:puppetversion) do + setcode { + begin + require 'puppet' + Puppet::PUPPETVERSION.to_s + rescue LoadError + nil + end + } +end diff --git a/lib/facter/rubysitedir.rb b/lib/facter/rubysitedir.rb new file mode 100644 index 0000000000..c205322033 --- /dev/null +++ b/lib/facter/rubysitedir.rb @@ -0,0 +1,8 @@ +Facter.add :rubysitedir do + setcode do + version = RUBY_VERSION.to_s.sub(/\.\d+$/, '') + $:.find do |dir| + dir =~ /#{File.join("site_ruby", version)}$/ + end + end +end diff --git a/lib/facter/rubyversion.rb b/lib/facter/rubyversion.rb new file mode 100644 index 0000000000..48f5cc8842 --- /dev/null +++ b/lib/facter/rubyversion.rb @@ -0,0 +1,3 @@ +Facter.add(:rubyversion) do + setcode { RUBY_VERSION.to_s } +end diff --git a/lib/facter/uniqueid.rb b/lib/facter/uniqueid.rb new file mode 100644 index 0000000000..b5726a072c --- /dev/null +++ b/lib/facter/uniqueid.rb @@ -0,0 +1,4 @@ + Facter.add(:uniqueid) do + setcode 'hostid', '/bin/sh' + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE Debian Gentoo} + end From 03258ebc22526b73f0c7e6550e2aa63712f2b589 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Fri, 16 May 2008 09:46:17 -0500 Subject: [PATCH 0191/3753] Retrieval of fact values now autoloads facts. I was only autoloading in certain circumstances, but it now autoloads any time you try to load a fact directly. --- lib/facter/util/collection.rb | 2 +- spec/unit/util/collection.rb | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/facter/util/collection.rb b/lib/facter/util/collection.rb index 267424a0d3..edc65c7bf4 100644 --- a/lib/facter/util/collection.rb +++ b/lib/facter/util/collection.rb @@ -87,7 +87,7 @@ def to_hash end def value(name) - if fact = @facts[canonize(name)] + if fact = fact(name) fact.value end end diff --git a/spec/unit/util/collection.rb b/spec/unit/util/collection.rb index 9ea2e9d0fa..81e5d98ac7 100755 --- a/spec/unit/util/collection.rb +++ b/spec/unit/util/collection.rb @@ -102,6 +102,11 @@ @fact.stubs(:value).returns "result" end + it "should use the 'fact' method to retrieve the fact" do + @coll.expects(:fact).with(:yayness).returns @fact + @coll.value(:yayness) + end + it "should return the result of calling :value on the fact" do @fact.expects(:value).returns "result" @@ -137,7 +142,7 @@ @coll.add(:one) @coll.add(:two) - @coll.list.sort.should == [:one, :two].sort + @coll.list.sort { |a,b| a.to_s <=> b.to_s }.should == [:one, :two] end it "should have a method for returning a hash of fact values" do From 07a3d479b0e31aa7a1f8e6d0e178e440adc57030 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Fri, 16 May 2008 10:26:56 -0500 Subject: [PATCH 0192/3753] Moving the puppet-related loading tests to an integration test. I was previously creating a mock Puppet class, and this actually uses Puppet's code. Note that this use of Puppet creates some additional ordering problems, which I'm going to fix in the next commit by undoing all of this work. :) --- lib/facter/util/loader.rb | 14 +++++++++++--- spec/integration/util/loader.rb | 26 ++++++++++++++++++++++++++ spec/unit/util/loader.rb | 31 ------------------------------- 3 files changed, 37 insertions(+), 34 deletions(-) create mode 100755 spec/integration/util/loader.rb diff --git a/lib/facter/util/loader.rb b/lib/facter/util/loader.rb index 52b5f07552..44a0338458 100644 --- a/lib/facter/util/loader.rb +++ b/lib/facter/util/loader.rb @@ -51,9 +51,17 @@ def search_path result += ENV["FACTERLIB"].split(":") end - if defined?(Puppet) - result << Puppet.settings.value(:factdest) - result << File.join(Puppet.settings.value(:libdir), "facter") + # LAK:NOTE We have to be this careful because facter gets loaded + # *very* early in Puppet's lifecycle, and this essentially + # builds interdependencies between the applications, which is + # tricky. + if defined?(Puppet) and Puppet.respond_to?(:settings) + if f = Puppet.settings.value(:factdest) + result << f + end + if l = Puppet.settings.value(:libdir) + result << File.join(l, "facter") + end end result end diff --git a/spec/integration/util/loader.rb b/spec/integration/util/loader.rb new file mode 100755 index 0000000000..af569cd8e8 --- /dev/null +++ b/spec/integration/util/loader.rb @@ -0,0 +1,26 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'facter/util/loader' + +begin + require 'puppet' +rescue LoadError + # Oh well, no Puppet :/ +end + +describe Facter::Util::Loader do + if defined?(Puppet) + describe "when the Puppet libraries are loaded" do + before { @loader = Facter::Util::Loader.new } + it "should include the factdest setting" do + @loader.search_path.should be_include(Puppet.settings.value(:factdest)) + end + + it "should include the facter subdirectory of the libdir setting" do + @loader.search_path.should be_include(File.join(Puppet.settings.value(:libdir), "facter")) + end + end + end +end diff --git a/spec/unit/util/loader.rb b/spec/unit/util/loader.rb index 690ec03a30..5e25ef5eb0 100755 --- a/spec/unit/util/loader.rb +++ b/spec/unit/util/loader.rb @@ -4,24 +4,6 @@ require 'facter/util/loader' -# Make sure we have a Puppet constant, so we can test -# loading Puppet facts. -unless defined?(Puppet) - class Puppet - # We have to implement this, because other tests will - # see this constant and fail if this method doesn't exist. - # I couldn't find a way to add and remove the constant - # just for the correct tests. - def self.settings - s = Object.new - def s.value(arg) - return "/eh" - end - s - end - end -end - describe Facter::Util::Loader do def with_env(values) old = {} @@ -58,7 +40,6 @@ def with_env(values) @loader = Facter::Util::Loader.new @settings = mock 'settings' @settings.stubs(:value).returns "/eh" - Puppet.stubs(:settings).returns @settings end it "should include the facter subdirectory of all paths in ruby LOAD_PATH" do @@ -80,18 +61,6 @@ def with_env(values) end end end - - describe "and the Puppet libraries are loaded" do - it "should include the factdest setting" do - @settings.expects(:value).with(:factdest).returns "/my/facts" - @loader.search_path.should be_include("/my/facts") - end - - it "should include the facter subdirectory of the libdir setting" do - @settings.expects(:value).with(:libdir).returns "/lib/dir" - @loader.search_path.should be_include("/lib/dir/facter") - end - end end describe "when loading facts" do From bb41db0bf4221e394411d2a82b6de264b557fb95 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Fri, 16 May 2008 10:38:15 -0500 Subject: [PATCH 0193/3753] Switching to a search path registration system. Facter no longer knows anything about Puppet, so there's no inter-dependency issue. --- lib/facter.rb | 12 ++++++++++++ lib/facter/util/loader.rb | 15 +++------------ spec/integration/util/loader.rb | 26 -------------------------- spec/unit/facter.rb | 25 +++++++++++++++++++++++++ spec/unit/util/loader.rb | 7 +++++++ 5 files changed, 47 insertions(+), 38 deletions(-) delete mode 100755 spec/integration/util/loader.rb diff --git a/lib/facter.rb b/lib/facter.rb index aae332656b..525dbcd335 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -179,4 +179,16 @@ def self.reset def self.loadfacts collection.load_all end + + @search_path = [] + + # Register a directory to search through. + def self.search(*dirs) + @search_path += dirs + end + + # Return our registered search directories. + def self.search_path + @search_path.dup + end end diff --git a/lib/facter/util/loader.rb b/lib/facter/util/loader.rb index 44a0338458..c6013cc42e 100644 --- a/lib/facter/util/loader.rb +++ b/lib/facter/util/loader.rb @@ -51,18 +51,9 @@ def search_path result += ENV["FACTERLIB"].split(":") end - # LAK:NOTE We have to be this careful because facter gets loaded - # *very* early in Puppet's lifecycle, and this essentially - # builds interdependencies between the applications, which is - # tricky. - if defined?(Puppet) and Puppet.respond_to?(:settings) - if f = Puppet.settings.value(:factdest) - result << f - end - if l = Puppet.settings.value(:libdir) - result << File.join(l, "facter") - end - end + # This allows others to register additional paths we should search. + result += Facter.search_path + result end diff --git a/spec/integration/util/loader.rb b/spec/integration/util/loader.rb deleted file mode 100755 index af569cd8e8..0000000000 --- a/spec/integration/util/loader.rb +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env ruby - -require File.dirname(__FILE__) + '/../../spec_helper' - -require 'facter/util/loader' - -begin - require 'puppet' -rescue LoadError - # Oh well, no Puppet :/ -end - -describe Facter::Util::Loader do - if defined?(Puppet) - describe "when the Puppet libraries are loaded" do - before { @loader = Facter::Util::Loader.new } - it "should include the factdest setting" do - @loader.search_path.should be_include(Puppet.settings.value(:factdest)) - end - - it "should include the facter subdirectory of the libdir setting" do - @loader.search_path.should be_include(File.join(Puppet.settings.value(:libdir), "facter")) - end - end - end -end diff --git a/spec/unit/facter.rb b/spec/unit/facter.rb index 59d10d440c..18611aa3a5 100755 --- a/spec/unit/facter.rb +++ b/spec/unit/facter.rb @@ -107,6 +107,31 @@ Facter.value(:macaddress).should_not be_include(" ") end + it "should have a method for registering directories to search" do + Facter.should respond_to(:search) + end + + it "should have a method for returning the registered search directories" do + Facter.should respond_to(:search_path) + end + + describe "when registering directories to search" do + after { Facter.instance_variable_set("@search_path", []) } + + it "should allow registration of a directory" do + Facter.search "/my/dir" + end + + it "should allow registration of multiple directories" do + Facter.search "/my/dir", "/other/dir" + end + + it "should return all registered directories when asked" do + Facter.search "/my/dir", "/other/dir" + Facter.search_path.should == %w{/my/dir /other/dir} + end + end + def test_onetrueconfine assert_nothing_raised { Facter.add("required") { diff --git a/spec/unit/util/loader.rb b/spec/unit/util/loader.rb index 5e25ef5eb0..b9b4306301 100755 --- a/spec/unit/util/loader.rb +++ b/spec/unit/util/loader.rb @@ -51,6 +51,13 @@ def with_env(values) end end + it "should include all search paths registered with Facter" do + Facter.expects(:search_path).returns %w{/one /two} + paths = @loader.search_path + paths.should be_include("/one") + paths.should be_include("/two") + end + describe "and the FACTERLIB environment variable is set" do it "should include all paths in FACTERLIB" do with_env "FACTERLIB" => "/one/path:/two/path" do From edbfc44b2c032e489d082664bf011e543b0cba87 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Fri, 16 May 2008 14:57:26 -0500 Subject: [PATCH 0194/3753] Adding a --puppet option to facter to load Puppet facts. Also updating the changelog for previous work. --- CHANGELOG | 11 +++++++++++ README | 2 +- bin/facter | 14 ++++++++++++-- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 2cf22ed7e6..0c592b344b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,15 @@ ?: + The 'facter' executable now has an option (-p|--puppet) for loading the + Puppet libraries, which gives it access to Puppet's facts. + + Added autoloading to Facter with a default of not loading all facts, + which results in a significant speedup when only one fact is sought. + Facts are autoloaded in either a single file named after the fact or + in any file in a directory named after the fact. + + Significantly refactored Facter's internals, including creating tests + for all internal classes. + A netmask fact has been added closing ticket #46. It only returns the netmask of the primary interface (in the same behaviour as the ipaddress and macaddress facts). diff --git a/README b/README index 29d3d835a4..4e28d9d2c1 100644 --- a/README +++ b/README @@ -6,5 +6,5 @@ processors, etc. It currently cannot collect very much information, but it is architected to be both OS and OS version specific. -See bin/facter or http://reductivelabs.com/project/enhost for an example of the +See bin/facter or http://reductivelabs.com/trac/enhost for an example of the interface. diff --git a/bin/facter b/bin/facter index 06d61edce7..60cde6d84b 100755 --- a/bin/facter +++ b/bin/facter @@ -6,7 +6,7 @@ # # = Usage # -# facter [-d|--debug] [-h|--help] [-v|--version] [-y|--yaml] [fact] [fact] [...] +# facter [-d|--debug] [-h|--help] [-p|--puppet] [-v|--version] [-y|--yaml] [fact] [fact] [...] # # = Description # @@ -24,6 +24,9 @@ # help:: # Print this help message # +# puppet:: +# Load the Puppet libraries, thus allowing Facter to load Puppet-specific facts. +# # version:: # Print the version and exit. # @@ -64,7 +67,8 @@ result = GetoptLong.new( [ "--help", "-h", GetoptLong::NO_ARGUMENT ], [ "--debug", "-d", GetoptLong::NO_ARGUMENT ], [ "--yaml", "-y", GetoptLong::NO_ARGUMENT ], - [ "--config", "-c", GetoptLong::REQUIRED_ARGUMENT ] + [ "--config", "-c", GetoptLong::REQUIRED_ARGUMENT ], + [ "--puppet", "-p", GetoptLong::NO_ARGUMENT ] ) options = { @@ -76,6 +80,12 @@ result.each { |opt,arg| when "--version" puts "%s" % Facter.version exit + when "--puppet" + begin + require 'puppet' + rescue LoadError => detail + $stderr.puts "Could not load Puppet: %s" % detail + end when "--yaml" options[:yaml] = true when "--debug" From 86e07086ac0a33ec6531b87d3641b0b592110cd2 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Mon, 19 May 2008 10:22:58 +1000 Subject: [PATCH 0195/3753] Incremented version number to 1.5 --- lib/facter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter.rb b/lib/facter.rb index 525dbcd335..9e7fe66e75 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -27,7 +27,7 @@ module Util; end include Comparable include Enumerable - FACTERVERSION = '2.0.0' + FACTERVERSION = '1.5.0' # = Facter # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. From 17f916f2417cce1f1127ae20427daab0a5fc2148 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Mon, 19 May 2008 10:29:36 +1000 Subject: [PATCH 0196/3753] Updated Red Hat spec file for new version and files --- conf/redhat/facter.spec | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index 4a9f2b4e2d..befbd4b5b2 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -5,7 +5,7 @@ Summary: Ruby module for collecting simple facts about a host operating system Name: facter -Version: 1.3.8 +Version: 1.5.0 Release: 1%{?dist} License: GPL Group: System Environment/Base @@ -40,11 +40,15 @@ mkdir %{buildroot} %{__install} -d -m0755 %{buildroot}%{ruby_sitelibdir} %{__install} -d -m0755 %{buildroot}%{ruby_sitelibdir}/facter +%{__install} -d -m0755 %{buildroot}%{ruby_sitelibdir}/facter/util +%{__install} -d -m0755 %{buildroot}%{ruby_sitelibdir}/facter/util/plist %{__install} -d -m0755 %{buildroot}%{_bindir} %{__install} -d -m0755 %{buildroot}%{_docdir}/%{name}-%{version} %{__install} -p -m0644 lib/*.rb %{buildroot}%{ruby_sitelibdir} %{__install} -p -m0644 lib/facter/*.rb %{buildroot}%{ruby_sitelibdir}/facter +%{__install} -p -m0644 lib/facter/util/*.rb %{buildroot}%{ruby_sitelibdir}/facter/util +%{__install} -p -m0644 lib/facter/util/plist/*.rb %{buildroot}%{ruby_sitelibdir}/facter/util/plist %{__install} -p -m0755 bin/facter %{buildroot}%{_bindir} %clean @@ -56,10 +60,18 @@ rm -rf %{buildroot} %{_bindir}/facter %{ruby_sitelibdir}/facter.rb %{ruby_sitelibdir}/facter +%{ruby_sitelibdir}/facter/util +%{ruby_sitelibdir}/facter/util/plist +%{ruby_sitelibdir}/facter/util/*.rb +%{ruby_sitelibdir}/facter/util/plist/*.rb %doc CHANGELOG COPYING INSTALL LICENSE README %changelog +* Mon May 19 2008 James Turnbull - 1.5.0-1 +- New version +- Added util and plist files + * Fri Jan 19 2007 David Lutterkort - 1.3.6-1 - New version From fc6d1c9b8c0c3bd82f55cfdf825cd7eee40a3fcc Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Mon, 19 May 2008 10:44:39 +1000 Subject: [PATCH 0197/3753] Added support for AIX fixing ticket #56 --- lib/facter/hardwaremodel.rb | 5 +++++ lib/facter/id.rb | 2 +- lib/facter/ipaddress.rb | 20 ++++++++++++++++++++ lib/facter/kernelrelease.rb | 5 +++++ lib/facter/macaddress.rb | 23 +++++++++++++++++++++++ lib/facter/uniqueid.rb | 2 +- 6 files changed, 55 insertions(+), 2 deletions(-) diff --git a/lib/facter/hardwaremodel.rb b/lib/facter/hardwaremodel.rb index 8cc53f0479..b69848dca2 100644 --- a/lib/facter/hardwaremodel.rb +++ b/lib/facter/hardwaremodel.rb @@ -1,3 +1,8 @@ Facter.add(:hardwaremodel) do setcode 'uname -m' end + + Facter.add(:hardwaremodel) do + confine :operatingsystem => :aix + setcode 'lsattr -El proc0 -a type|cut -f2 -d" "' + end diff --git a/lib/facter/id.rb b/lib/facter/id.rb index e66745fc27..99f822ccaf 100644 --- a/lib/facter/id.rb +++ b/lib/facter/id.rb @@ -1,4 +1,4 @@ Facter.add(:id) do - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE Debian Gentoo} + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE Debian Gentoo AIX} setcode "whoami" end diff --git a/lib/facter/ipaddress.rb b/lib/facter/ipaddress.rb index cd6dbcfb00..cf80c0cd0e 100644 --- a/lib/facter/ipaddress.rb +++ b/lib/facter/ipaddress.rb @@ -129,3 +129,23 @@ ip end end + + Facter.add(:ipaddress) do + confine :kernel => %w{AIX} + setcode do + ip = nil + output = %x{/usr/sbin/ifconfig -a} + + output.split(/^\S/).each { |str| + if str =~ /inet ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + tmp = $1 + unless tmp =~ /127\./ + ip = tmp + break + end + end + } + + ip + end + end diff --git a/lib/facter/kernelrelease.rb b/lib/facter/kernelrelease.rb index 9766ad9221..8c6ac57831 100644 --- a/lib/facter/kernelrelease.rb +++ b/lib/facter/kernelrelease.rb @@ -1,3 +1,8 @@ Facter.add(:kernelrelease) do setcode 'uname -r' end + + Facter.add(:kernelrelease) do + confine :kernel => :aix + setcode 'oslevel -s' + end diff --git a/lib/facter/macaddress.rb b/lib/facter/macaddress.rb index 647f39d18b..f42f8a1ee0 100644 --- a/lib/facter/macaddress.rb +++ b/lib/facter/macaddress.rb @@ -40,3 +40,26 @@ ether end end + + Facter.add(:macaddress) do + confine :kernel => %w{AIX} + setcode do + ether = [] + ip = nil + output = %x{/usr/sbin/ifconfig -a} + output.each { |str| + if str =~ /([a-z]+\d+): flags=/ + devname = $1 + unless devname =~ /lo0/ + output2 = %x{/usr/bin/entstat #{devname}} + output2.each { |str2| + if str2 =~ /^Hardware Address: (\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/ + ether.push($1) + end + } + end + end + } + ether[0] + end + end diff --git a/lib/facter/uniqueid.rb b/lib/facter/uniqueid.rb index b5726a072c..2eae3ccb0a 100644 --- a/lib/facter/uniqueid.rb +++ b/lib/facter/uniqueid.rb @@ -1,4 +1,4 @@ Facter.add(:uniqueid) do setcode 'hostid', '/bin/sh' - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE Debian Gentoo} + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE Debian Gentoo AIX} end From a5a72bd1e449c768375ba911af195388b1e940e5 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Mon, 19 May 2008 11:52:34 +1000 Subject: [PATCH 0198/3753] Added LSB Major Dist Release fact fixing #41 --- lib/facter/lsbmajdistrelease.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 lib/facter/lsbmajdistrelease.rb diff --git a/lib/facter/lsbmajdistrelease.rb b/lib/facter/lsbmajdistrelease.rb new file mode 100644 index 0000000000..94742dba72 --- /dev/null +++ b/lib/facter/lsbmajdistrelease.rb @@ -0,0 +1,16 @@ +# lsbmajdistrelease.rb +# +require 'facter' + +Facter.add("lsbmajdistrelease") do + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE Debian Gentoo} + setcode do + if /(\d*)\./i =~ Facter.value(:lsbdistrelease) + result=$1 + else + result=Facter.value(:lsbdistrelease) + end + result + end +end + From 7e84cdb0a2a951eb3eb002f8846bf219c23cb384 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Mon, 19 May 2008 11:56:51 +1000 Subject: [PATCH 0199/3753] Updated CHANGELOG --- CHANGELOG | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 0c592b344b..70340247f5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,9 @@ ?: + Added AIX support for some facts + + Add lsbmajdistrelease fact for CentOS and Red Hat + + Updated Red Hat spec file for new version The 'facter' executable now has an option (-p|--puppet) for loading the Puppet libraries, which gives it access to Puppet's facts. From d235f26cb7be5b24d63f7f9a57aabdcf86fb16b4 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Mon, 19 May 2008 22:29:40 -0500 Subject: [PATCH 0200/3753] Reverting the version. The releasing system automatically sets the version, and it fails if the current version is equal to the version being released. --- lib/facter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter.rb b/lib/facter.rb index 9e7fe66e75..22864000fb 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -27,7 +27,7 @@ module Util; end include Comparable include Enumerable - FACTERVERSION = '1.5.0' + FACTERVERSION = '1.3.8' # = Facter # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. From 400bab9fcf167bc76710a260822e0b0e114c98fa Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Tue, 20 May 2008 23:12:06 -0500 Subject: [PATCH 0201/3753] Adding a timeout to fact retrieval, fixing #56. The timeout is currently 0.5 seconds, which should generally be enough time. --- lib/facter/util/fact.rb | 12 +++++++++++- spec/unit/util/fact.rb | 7 +++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/facter/util/fact.rb b/lib/facter/util/fact.rb index c73dfbf418..0b2d5b92ca 100644 --- a/lib/facter/util/fact.rb +++ b/lib/facter/util/fact.rb @@ -1,6 +1,8 @@ require 'facter' require 'facter/util/resolution' +require 'timeout' + class Facter::Util::Fact attr_accessor :name, :searching, :ldapname @@ -80,7 +82,15 @@ def value next unless resolve.suitable? foundsuits = true - tmp = resolve.value + tmp = nil + begin + Timeout.timeout(0.5) do + tmp = resolve.value + end + rescue Timeout::Error => detail + warn "Timed out seeking value for %s" % self.name + next + end break tmp unless tmp.nil? or tmp == "" } diff --git a/spec/unit/util/fact.rb b/spec/unit/util/fact.rb index 16520327bd..cee70b99f6 100755 --- a/spec/unit/util/fact.rb +++ b/spec/unit/util/fact.rb @@ -121,6 +121,13 @@ @fact.value.should be_nil end + + it "should timeout after 0.5 seconds" do + @fact.expects(:warn) + @fact.add { setcode { sleep 2; raise "This is a test" } } + + @fact.value.should be_nil + end end it "should have a method for flushing the cached fact" do From a12d3d8bbf7ef6b8cb867237f3709f948b266373 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Tue, 20 May 2008 23:26:14 -0500 Subject: [PATCH 0202/3753] Removing old test/unit tests. --- spec/unit/facter.rb | 582 -------------------------------------------- 1 file changed, 582 deletions(-) diff --git a/spec/unit/facter.rb b/spec/unit/facter.rb index 18611aa3a5..05d99c8871 100755 --- a/spec/unit/facter.rb +++ b/spec/unit/facter.rb @@ -131,586 +131,4 @@ Facter.search_path.should == %w{/my/dir /other/dir} end end - - def test_onetrueconfine - assert_nothing_raised { - Facter.add("required") { - setcode { "foo" } - } - Facter.add("testing") { - setcode { "bar" } - confine("required","foo") - } - } - - assert_equal("bar", Facter["testing"].value) - end - - def test_onefalseconfine - assert_nothing_raised { - Facter.add("required") { - setcode { "foo" } - } - Facter.add("testing") { - setcode { "bar" } - confine("required","bar") - } - } - - assert_equal(nil, Facter["testing"].value) - end - - def test_recursiveconfines - # This will try to autoload "required", which will fail, so the - # fact will be marked as unsuitable. - assert_nothing_raised { - Facter.add("testing") { - setcode { "bar" } - confine("required","foo") - } - } - assert_nothing_raised { - Facter.add("required") do - setcode { "foo" } - confine("testing","bar") - end - } - - assert_equal(nil, Facter["testing"].value) - end - - def test_multipleresolves - assert_nothing_raised { - Facter.add("funtest") { - setcode { "unconfineged" } - } - Facter.add("funtest") { - setcode { "confineged" } - confine("operatingsystem", Facter["operatingsystem"].value) - } - } - - assert_equal("confineged", Facter["funtest"].value) - end - - def test_upcase - Facter.add("Testing") { - setcode { "foo" } - } - assert_equal( - "foo", - Facter["Testing"].value - ) - end - - def test_doublecall - Facter.add("testing") { - setcode { "foo" } - } - assert_equal( - Facter["testing"].value, - Facter["testing"].value - ) - end - - def test_downcase - Facter.add("testing") { - setcode { "foo" } - } - assert_equal( - "foo", - Facter["testing"].value - ) - end - - def test_case_insensitivity - Facter.add("Testing") { - setcode { "foo" } - } - upcase = Facter["Testing"].value - downcase = Facter["testing"].value - assert_equal(upcase, downcase) - end - - def test_adding - assert_nothing_raised() { - Facter.add("Funtest") { - setcode { "funtest value" } - } - } - - assert_equal( - "funtest value", - Facter["funtest"].value - ) - end - - def test_adding2 - assert_nothing_raised() { - Facter.add("bootest") { - confine("operatingsystem", Facter["operatingsystem"].value) - setcode "echo bootest" - } - } - - assert_equal( - "bootest", - Facter["bootest"].value - ) - - assert_nothing_raised() { - Facter.add("bahtest") { - #obj.os = Facter["operatingsystem"].value - #obj.release = Facter["operatingsystemrelease"].value - confine("operatingsystem", Facter["operatingsystem"].value) - confine("operatingsystemrelease", - Facter["operatingsystemrelease"].value) - setcode "echo bahtest" - } - } - - assert_equal( - "bahtest", - Facter["bahtest"].value - ) - - assert_nothing_raised() { - Facter.add("failure") { - #obj.os = Facter["operatingsystem"].value - #obj.release = "FakeRelease" - confine("operatingsystem", Facter["operatingsystem"].value) - confine("operatingsystemrelease", "FakeRelease") - setcode "echo failure" - } - } - - assert_equal( - nil, - Facter["failure"].value - ) - end - - def test_distro - if Facter["operatingsystem"] == "Linux" - assert(Facter["Distro"]) - end - end - - def test_each - list = {} - assert_nothing_raised { - Facter.each { |name,fact| - list[name] = fact - } - } - - list.each { |name,value| - assert(value.class != Facter) - assert(name) - assert(value) - } - end - - def disabled_test_withnoouts - @oldhandles = [] - @oldhandles << $stdin.dup - $stdin.reopen "/dev/null" - @oldhandles << $stdout.dup - $stdout.reopen "/dev/null", "a" - @oldhandles << $stderr.dup - $stderr.reopen $stdout - - assert_nothing_raised { - Facter.each { |name,fact| - list[name] = fact - } - } - $stdin, $stdout, $stderr = @oldhandles - end - - def test_ldapname - facts = {} - assert_nothing_raised { - Facter.each { |name, value| - facts[name] = Facter[name] - } - } - - facts.each { |name, fact| - assert(fact.ldapname, "Fact %s has no ldapname" % name) - assert_instance_of(String, fact.ldapname, "Fact %s has a non-string ldapname" % name) - } - end - - def test_hash - hash = nil - assert_nothing_raised { - hash = Facter.to_hash - } - - assert_instance_of(Hash, hash) - - hash.each do |name, value| - assert_instance_of(String, name) - assert_instance_of(String, value, "%s's value is not a string" % name) - end - end - - # Verify we can call retrieve facts as methods - def test_factfunction - val = nil - assert_nothing_raised { - val = Facter.operatingsystem - } - - assert_equal(Facter["operatingsystem"].value, val) - - assert_raise(NoMethodError) { Facter.nosuchfact } - end - - def test_versionfacts - assert_nothing_raised { - assert(Facter.facterversion, "Could not get facter version") - assert(Facter.rubyversion, "Could not get ruby version") - } - end - - # Verify we autoload everything from the start. - def test_initautoloading - dir = "/tmp/facterloading" - @tmpfiles << dir - Dir.mkdir(dir) - Dir.mkdir(File.join(dir, "facter")) - $: << dir - - # Make sure we don't have a value right now. - assert_raise(NoMethodError) do - Facter.initautoloadfact - end - assert_nil(Facter["initautoloadfact"]) - - # Make our file - val = "autoloadedness" - File.open(File.join(dir, "facter", "initautoloadfact.rb"), "w") do |file| - file.puts %{ -Facter.add("InitAutoloadFact") do - setcode { "#{val}" } -end -} - end - - # Now reset and reload - Facter.reset - Facter.flush - - # And load - assert_nothing_raised { - Facter.loadfacts - } - - hash = nil - assert_nothing_raised { - hash = Facter.to_hash - } - - assert(hash.include?("initautoloadfact"), "Did not load fact at startup") - assert_equal(val, hash["initautoloadfact"], "Did not get correct value") - end - - def test_localfacts - dir = "/tmp/localloading" - @tmpfiles << dir - Dir.mkdir(dir) - Dir.mkdir(File.join(dir, "facter")) - $: << dir - - Dir.mkdir(File.join(dir, "facterlib1")) - Dir.mkdir(File.join(dir, "facterlib2")) - ENV['FACTERLIB'] = "#{dir}/facterlib1:#{dir}/facterlib2" - - # Make sure we don't have a value right now, for both the localfact - # and the facterlib fact. - assert_raise(NoMethodError) do - Facter.localfact - end - assert_nil(Facter["localfact"]) - - assert_raise(NoMethodError) do - Facter.facterlibfact - end - assert_nil(Facter["faterlibfact"]) - - assert_raise(NoMethodError) do - Facter.facterlibfact2 - end - assert_nil(Facter["faterlibfact2"]) - - # Make our files - val = "localness" - File.open(File.join(dir, "facter", "local.rb"), "w") do |file| - file.puts %{ -Facter.add("LocalFact") do - setcode { "#{val}" } -end -} - end - File.open(File.join(dir, "facterlib1", "facterlibfact.rb"), "w") do |file| - file.puts %{ -Facter.add("facterlibfact") do - setcode { "#{val}" } -end -} - end - File.open(File.join(dir, "facterlib2", "facterlibfact2.rb"), "w") do |file| - file.puts %{ -Facter.add("facterlibfact2") do - setcode { "#{val}" } -end -} - end - - # Now reset and reload - Facter.reset - Facter.flush - - # And load - assert_nothing_raised { - Facter.loadfacts - } - - hash = nil - assert_nothing_raised { - hash = Facter.to_hash - } - - assert(hash.include?("localfact"), "Did not load fact at startup") - assert(hash.include?("facterlibfact"), "Did not load fact from FACTERLIB") - assert(hash.include?("facterlibfact2"), "Did not load fact from multiple FACTERLIBs") - assert_equal(val, hash["localfact"], "Did not get correct value") - assert_equal(val, hash["facterlibfact"], "Did not get correct value from FACTERLIB fact") - assert_equal(val, hash["facterlibfact2"], "Did not get correct value from multiple FACTERLIB facts") - end - - def test_stupidchdirring - dir = "/tmp/localloading" - @tmpfiles << dir - Dir.mkdir(dir) - $: << dir - - # Make our file - val = "localness" - File.open(File.join(dir, "facter"), "w") do |file| - file.puts %{ -some random stuff -} - end - - assert_nothing_raised do - Facter.loadfacts - end - end - - def test_confine_as_array_and_hash - assert_nothing_raised { - Facter.add("myfact") do - confine "kernel", Facter.kernel - setcode do "yep" end - end - } - - assert_equal("yep", Facter.myfact, "Did not get confineged goal") - - # now try it as a hash - assert_nothing_raised { - Facter.add("hashfact") do - confine "kernel" => Facter.kernel - setcode do "hashness" end - end - } - - assert_equal("hashness", Facter.hashfact, "Did not get confineged goal") - - # now with multiple values - assert_nothing_raised { - Facter.add("hashfact2") do - confine :kernel => ["nosuchkernel", Facter.kernel] - setcode do "multihash" end - end - } - - assert_equal("multihash", Facter.hashfact2, "Did not get multivalue confine") - - end - - def test_strings_or_symbols - assert_nothing_raised { - Facter.add("symbol1") do - confine :kernel => Facter.kernel - setcode do "yep1" end - end - } - - assert_equal("yep1", Facter.symbol1, "Did not get symbol fact") - end - - def test_confine_case_insensitivity - assert_nothing_raised { - Facter.add :casetest1 do - confine :kernel => Facter.kernel.downcase - setcode do "yep1" end - end - } - - assert_equal("yep1", Facter.casetest1, "Did not get case test 1") - - assert_nothing_raised { - Facter.add :casetest2 do - confine :kernel => Facter.kernel.upcase - setcode do "yep2" end - end - } - - assert_equal("yep2", Facter.casetest2, "Did not get case test 1") - end - - def test_tags - assert_nothing_raised { - Facter.add :tagtest do - setcode do "yep1" end - tag :system, :performance - end - - # Now add another resolution mechanism with overlapping tags - Facter.add :tagtest do - setcode do "yep2" end - tag :puppet, :performance - end - - # Finally, one that's only got the performance tag - Facter.add :othertag do - setcode do "nope" end - tag :performance - end - } - - tags = nil - assert_nothing_raised { - tags = Facter[:tagtest].tags - } - [:puppet, :performance, :system].each do |t| - assert(tags.include?(t), "Did not get tag %s" % t) - end - - hash = nil - assert_nothing_raised { - hash = Facter.to_hash(:puppet, :system) - } - - assert(hash.include?("tagtest"), "Did not get tagged fact") - assert(! hash.include?("othertag"), "Got incorrectly tagged fact") - end - - if Facter.kernel == "Linux" - def test_memoryonlinux - assert_nothing_raised { - assert(Facter.memorysize, "Did not get memory") - } - end - - def test_processor_on_linux - assert_nothing_raised { - assert(Facter.processorcount, "Did not get proc count") - assert(Facter.processor0, "Did not get proc 0") - } - end - end - - def test_factquestion - kernel = Facter.kernel - assert_nothing_raised { - assert(Facter.kernel?(kernel.downcase), "Kernel did not match") - assert(Facter.kernel?(kernel.upcase), "Upcase kernel did not match") - assert(Facter.kernel?(kernel.intern), "Symbol kernel did not match") - assert(! Facter.kernel?("nosuchkernel"), "Fake kernel matched") - } - end - - # Make sure that facter doesn't fail when it gets bad files. - def test_ignore_bad_files - # Create a broken file - dir = "/tmp/factertest-brokenfile" - @tmpfiles << dir - libdir = File.join(dir, "facter") - - FileUtils.mkdir_p(libdir) - - $: << libdir - tearhook { $:.delete libdir } - - file = File.join(libdir, "file.rb") - - - File.open(file, "w") do |f| - f.puts "asdflkjaeflkj23rljadflkjasdfuhasd8;;lkjadsf;j24iojlkajsdf" - end - - assert_nothing_raised { - Facter.loadfacts() - } - - - end - - def test_env_facts - value = "a fact" - - %w{FACTER_ENVNESS facter_envness facterenvness facter_envness}.each do |var| - ENV[var] = value - - assert_nothing_raised { - Facter.loadfacts() - } - - assert(Facter["envness"], "Did not get env fact") - - assert_equal(value, Facter["envness"].value, - "Did not get value correctly") - - resp = nil - assert_nothing_raised { - resp = Facter.envness - } - - assert_equal(value, resp) - ENV.delete(var) - Facter.clear - end - end - - # Make sure that ssh keys only include the key, not the comment or the type. - def test_ssh_keys - %w{rsa dsa}.each do |type| - key = Facter.value("ssh#{type}key") - assert(key, "did not retrieve %s key" % type) - assert(key !~ /\s/, "%s key contains whitespace" % type) - end - end - - def test_flush - val = "yay" - Facter.add(:testing) do - setcode { val } - end - - assert_equal(val, Facter.value(:testing), - "did not get correct value initially") - val = "foo" - assert_equal("yay", Facter.value(:testing), - "did not cache value") - - assert_nothing_raised("Could not clear facter cache") do - Facter.flush - end - assert_equal(val, Facter.value(:testing), "did not clear cache") - end end From f9961c705f56229ccd0151cda87316ec73a8675c Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Thu, 22 May 2008 17:39:04 +1000 Subject: [PATCH 0203/3753] Fixes for ticket #60 --- CHANGELOG | 2 + lib/facter/manufacturer.rb | 15 ++------ lib/facter/util/manufacturer.rb | 65 +++++++++++---------------------- 3 files changed, 27 insertions(+), 55 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 70340247f5..3be08ea772 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ ?: + Updated dmidecode facts fixing ticket #60 + Added AIX support for some facts Add lsbmajdistrelease fact for CentOS and Red Hat diff --git a/lib/facter/manufacturer.rb b/lib/facter/manufacturer.rb index 0c74607944..1fbc38d868 100644 --- a/lib/facter/manufacturer.rb +++ b/lib/facter/manufacturer.rb @@ -4,14 +4,7 @@ # require 'facter/util/manufacturer' - -{:SerialNumber => "Serial Number", - :Manufacturer => "Manufacturer", - :ProductName=> "Product Name"}.each do |fact, name| - Facter.add(fact) do - confine :kernel => :linux - setcode do - Facter::Manufacturer.dmi_find_system_info(name) - end - end -end + +query = { 'System Information' => [ 'Manufacturer:', 'Product Name:' , 'Serial Number:'], 'Chassis Information' => 'Type:'} + +Facter::Manufacturer.dmi_find_system_info(query) diff --git a/lib/facter/util/manufacturer.rb b/lib/facter/util/manufacturer.rb index 18db1c9a73..b48ffc78b1 100644 --- a/lib/facter/util/manufacturer.rb +++ b/lib/facter/util/manufacturer.rb @@ -1,49 +1,26 @@ -## mamufacturer.rb -## Support methods for manufacturer specific facts -## -## This program is free software; you can redistribute it and/or -## modify it under the terms of the GNU General Public License -## as published by the Free Software Foundation (version 2 of the License) -## This program is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. -## You should have received a copy of the GNU General Public License -## along with this program; if not, write to the Free Software -## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA -## - +# mamufacturer.rb +# Support methods for manufacturer specific facts module Facter::Manufacturer - def self.dmi_find_system_info(name) - return nil unless FileTest.exists?("/usr/sbin/dmidecode") - - # Do not run the command more than every five seconds. - unless defined?(@data) and defined?(@time) and (Time.now.to_i - @time.to_i < 5) - @data = {} - type = nil - @time = Time.now - # It's *much* easier to just parse the whole darn file than - # to just match a chunk of it. - %x{/usr/sbin/dmidecode 2>/dev/null}.split("\n").each do |line| - case line - when /^(\S.+)$/ - type = $1.chomp - @data[type] ||= {} - when /^\s+(\S.+): (\S.*)$/ - unless type - next - end - @data[type][$1] = $2.strip - end - end - end + def self.dmi_find_system_info(name) + return nil unless FileTest.exists?("/usr/sbin/dmidecode") - if data = @data["System Information"] - data[name] - else - nil - end - end + output=%x{/usr/sbin/dmidecode 2>/dev/null} + name.each_pair do |key,v| + v.each do |value| + output.split("Handle").each do |line| + if line =~ /#{key}/ and line =~ /#{value} (\w.*)\n*./ + result = $1 + Facter.add(value.chomp(':').gsub(' ','')) do + confine :kernel => :linux + setcode do + result + end + end + end + end + end + end +end end From 09bc48c1728379bccf087a436921f674715e61b9 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Sun, 25 May 2008 19:18:07 -0500 Subject: [PATCH 0204/3753] Testing gitosis --- Rakefile | 1 - 1 file changed, 1 deletion(-) diff --git a/Rakefile b/Rakefile index 4cef94fd8e..55f967c947 100644 --- a/Rakefile +++ b/Rakefile @@ -43,4 +43,3 @@ if project.has?(:epm) task.rubylibs = FileList.new('lib/**/*') end end -# $Id$ From d9bd38835f98af5925f33e0f43adbbf89d61f1db Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Wed, 28 May 2008 20:38:22 +1000 Subject: [PATCH 0205/3753] Refactor of netmask fact - fixes ticket #66 --- lib/facter/netmask.rb | 35 ++--------------------------------- lib/facter/util/netmask.rb | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 33 deletions(-) create mode 100644 lib/facter/util/netmask.rb diff --git a/lib/facter/netmask.rb b/lib/facter/netmask.rb index 140f8f14d9..309ef631d8 100644 --- a/lib/facter/netmask.rb +++ b/lib/facter/netmask.rb @@ -6,43 +6,12 @@ # idea and originial source by Mark 'phips' Phillips # -def get_netmask - netmask = nil; - ipregex = %r{(\d{1,3}\.){3}\d{1,3}} - - ops = nil - case Facter.value(:kernel) - when 'Linux' - ops = { - :ifconfig => '/sbin/ifconfig', - :regex => %r{\s+ inet\saddr: #{Facter.ipaddress} .*? Mask: (#{ipregex})}x, - :munge => nil, - } - when 'SunOS' - ops = { - :ifconfig => '/usr/sbin/ifconfig -a', - :regex => %r{\s+ inet\s+? #{Facter.ipaddress} \+? mask (\w{8})}x, - :munge => Proc.new { |mask| mask.scan(/../).collect do |byte| byte.to_i(16) end.join('.') } - } - end - - %x{#{ops[:ifconfig]}}.split(/\n/).collect do |line| - matches = line.match(ops[:regex]) - if !matches.nil? - if ops[:munge].nil? - netmask = matches[1] - else - netmask = ops[:munge].call(matches[1]) - end - end - end - netmask -end +require 'facter/util/netmask' Facter.add("netmask") do confine :kernel => [ :sunos, :linux ] setcode do - get_netmask + Facter::NetMask.get_netmask end end diff --git a/lib/facter/util/netmask.rb b/lib/facter/util/netmask.rb new file mode 100644 index 0000000000..71696ad40d --- /dev/null +++ b/lib/facter/util/netmask.rb @@ -0,0 +1,36 @@ +module Facter::NetMask + +def self.get_netmask + netmask = nil; + ipregex = %r{(\d{1,3}\.){3}\d{1,3}} + + ops = nil + case Facter.value(:kernel) + when 'Linux' + ops = { + :ifconfig => '/sbin/ifconfig', + :regex => %r{\s+ inet\saddr: #{Facter.ipaddress} .*? Mask: (#{ipregex})}x, + :munge => nil, + } + when 'SunOS' + ops = { + :ifconfig => '/usr/sbin/ifconfig -a', + :regex => %r{\s+ inet\s+? #{Facter.ipaddress} \+? mask (\w{8})}x, + :munge => Proc.new { |mask| mask.scan(/../).collect do |byte| byte.to_i(16) end.join('.') } + } + end + + %x{#{ops[:ifconfig]}}.split(/\n/).collect do |line| + matches = line.match(ops[:regex]) + if !matches.nil? + if ops[:munge].nil? + netmask = matches[1] + else + netmask = ops[:munge].call(matches[1]) + end + end + end + netmask +end + +end From b574c6a7f8c596e98bfab7ca48e172eb38fe23fa Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Mon, 9 Jun 2008 02:37:30 +1000 Subject: [PATCH 0206/3753] Refactered ipmess.rb and util/ip.rb to support separate *BSD logic for *BSD aliased interfaces. --- CHANGELOG | 3 +++ lib/facter/ipmess.rb | 46 +++++++++++++++++++++++++++++++++++-------- lib/facter/util/ip.rb | 40 ++++++++++++++++++++++++++++++++----- 3 files changed, 76 insertions(+), 13 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 3be08ea772..b4d5770efb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,7 @@ ?: + Refactered ipmess.rb and util/ip.rb to support separate *BSD logic for + *BSD aliased interfaces. + Updated dmidecode facts fixing ticket #60 Added AIX support for some facts diff --git a/lib/facter/ipmess.rb b/lib/facter/ipmess.rb index 0ddc3152f0..215d557758 100644 --- a/lib/facter/ipmess.rb +++ b/lib/facter/ipmess.rb @@ -14,32 +14,62 @@ end end - -Facter::IPAddress.get_interfaces.each do |interface| +case Facter.value(:kernel) + when 'SunOS', 'Linux' + Facter::IPAddress.get_interfaces.each do |interface| mi = interface.gsub(':', '_') Facter.add("ipaddress_" + mi) do - confine :kernel => [ :sunos, :freebsd, :openbsd, :netbsd, :linux ] + confine :kernel => [ :sunos, :linux ] setcode do label = 'ipaddress' - Facter::IPAddress.get_interface_value(interface, label) + Facter::IPAddress.get_interface_value_nonbsd(interface, label) end end Facter.add("macaddress_" + mi) do - confine :kernel => [ :sunos, :freebsd, :openbsd, :netbsd, :linux ] + confine :kernel => [ :sunos, :linux ] setcode do label = 'macaddress' - Facter::IPAddress.get_interface_value(interface, label) + Facter::IPAddress.get_interface_value_nonbsd(interface, label) end end Facter.add("netmask_" + mi) do - confine :kernel => [ :sunos, :freebsd, :openbsd, :netbsd, :linux ] + confine :kernel => [ :sunos, :linux ] setcode do label = 'netmask' - Facter::IPAddress.get_interface_value(interface, label) + Facter::IPAddress.get_interface_value_nonbsd(interface, label) + end + end + end + + when 'OpenBSD', 'NetBSD', 'FreeBSD' + Facter::IPAddress.get_interfaces.each do |interface| + mi = interface.gsub(':', '_') + + Facter.add("ipaddress_" + mi) do + confine :kernel => [ :openbsd, :freebsd, :netbsd ] + setcode do + label = 'ipaddress' + Facter::IPAddress.get_interface_value_bsd(interface, label) end end + Facter.add("netmask_" + mi) do + confine :kernel => [ :openbsd, :freebsd, :netbsd ] + setcode do + label = 'netmask' + Facter::IPAddress.get_interface_value_bsd(interface, label) + end + end + + Facter.add("macaddress_" + mi) do + confine :kernel => [ :openbsd, :freebsd, :netbsd ] + setcode do + label = 'macaddress' + Facter::IPAddress.get_interface_value_bsd(interface, label) + end + end + end end diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index 6702e5f269..fb13383f94 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -21,11 +21,9 @@ def self.get_interfaces end - def self.get_interface_value(interface, label) + def self.get_interface_value_nonbsd(interface, label) - tmp1 =nil - tmp2 = nil - tmp3 = nil + tmp1 = nil case Facter.value(:kernel) when 'Linux' @@ -58,11 +56,43 @@ def self.get_interface_value(interface, label) output_int.each { |s| tmp1 = $1 if s =~ regex } - end + end if tmp1 value = tmp1 end end + + def self.get_interface_value_bsd(interface, label) + + tmp1 = nil + + int_hash = {} + output_int = %x{/sbin/ifconfig #{interface}} + addr = /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + mac = /(?:ether|lladdr)\s+(\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ + mask = /netmask\s+(\w{10})/ + + case label + when 'ipaddress' + regex = addr + when 'macaddress' + regex = mac + when 'netmask' + regex = mask + end + + if interface != "lo" && interface != "lo0" + output_int.each { |s| + tmp1 = $1 if s =~ regex + } + end + + if tmp1 + value = tmp1 + end + + end end + From 9a1882e9b2f6d2372567557a3935649b4be3f661 Mon Sep 17 00:00:00 2001 From: Steve Hajducko Date: Mon, 16 Jun 2008 16:12:20 -0700 Subject: [PATCH 0207/3753] Retrieve hardwaremodel for AIX from sys0 modelname. Currently gets hardwaremodel from proc0 but some systems don't have a proc0 device configured so the fact fails. tickets/1.5/1364 Signed-off-by: Steve Hajducko --- lib/facter/hardwaremodel.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/facter/hardwaremodel.rb b/lib/facter/hardwaremodel.rb index b69848dca2..de5db3ffb0 100644 --- a/lib/facter/hardwaremodel.rb +++ b/lib/facter/hardwaremodel.rb @@ -4,5 +4,10 @@ Facter.add(:hardwaremodel) do confine :operatingsystem => :aix - setcode 'lsattr -El proc0 -a type|cut -f2 -d" "' + setcode do + model = Facter::Util::Resolution.exec('lsattr -El sys0 -a modelname') + if model =~ /modelname\s(\S+)\s/ + $1 + end + end end From d322df9a367fde290a3aa3d1b15082939c8fff19 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Tue, 17 Jun 2008 10:54:23 -0500 Subject: [PATCH 0208/3753] Refactored so each fact resolution can specify a separate timeout, but the default is still 0.5. Set the timeout on the AIX kernelrelease fact to 5 seconds. --- CHANGELOG | 5 +++++ lib/facter/kernelrelease.rb | 2 +- lib/facter/util/collection.rb | 28 ++++++++++++++++++++++-- lib/facter/util/fact.rb | 14 +++--------- lib/facter/util/resolution.rb | 21 +++++++++++++----- spec/unit/util/collection.rb | 40 +++++++++++++++++++++++++++++++++++ spec/unit/util/fact.rb | 7 ------ spec/unit/util/resolution.rb | 16 ++++++++++++++ 8 files changed, 107 insertions(+), 26 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index b4d5770efb..070bee6643 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,9 @@ ?: + Set the timeout on the AIX kernelrelease fact to 5 seconds. + + Refactored so each fact resolution can specify a separate timeout, + but the default is still 0.5. + Refactered ipmess.rb and util/ip.rb to support separate *BSD logic for *BSD aliased interfaces. diff --git a/lib/facter/kernelrelease.rb b/lib/facter/kernelrelease.rb index 8c6ac57831..652fd6a924 100644 --- a/lib/facter/kernelrelease.rb +++ b/lib/facter/kernelrelease.rb @@ -2,7 +2,7 @@ setcode 'uname -r' end - Facter.add(:kernelrelease) do + Facter.add(:kernelrelease, :timeout => 5) do confine :kernel => :aix setcode 'oslevel -s' end diff --git a/lib/facter/util/collection.rb b/lib/facter/util/collection.rb index edc65c7bf4..3f8e0f8d4c 100644 --- a/lib/facter/util/collection.rb +++ b/lib/facter/util/collection.rb @@ -17,11 +17,35 @@ def add(name, options = {}, &block) name = canonize(name) unless fact = @facts[name] - fact = Facter::Util::Fact.new(name, options) + fact = Facter::Util::Fact.new(name) + @facts[name] = fact end - fact.add(&block) if block + # Set any fact-appropriate options. + options.each do |opt, value| + method = opt.to_s + "=" + if fact.respond_to?(method) + fact.send(method, value) + options.delete(opt) + end + end + + if block + resolve = fact.add(&block) + # Set any resolve-appropriate options + options.each do |opt, value| + method = opt.to_s + "=" + if resolve.respond_to?(method) + resolve.send(method, value) + options.delete(opt) + end + end + end + + unless options.empty? + raise ArgumentError, "Invalid facter option(s) %s" % options.keys.collect { |k| k.to_s }.join(",") + end return fact end diff --git a/lib/facter/util/fact.rb b/lib/facter/util/fact.rb index 0b2d5b92ca..c96ca3bb3e 100644 --- a/lib/facter/util/fact.rb +++ b/lib/facter/util/fact.rb @@ -1,9 +1,9 @@ require 'facter' require 'facter/util/resolution' -require 'timeout' - class Facter::Util::Fact + TIMEOUT = 5 + attr_accessor :name, :searching, :ldapname # Create a new fact, with no resolution mechanisms. @@ -82,15 +82,7 @@ def value next unless resolve.suitable? foundsuits = true - tmp = nil - begin - Timeout.timeout(0.5) do - tmp = resolve.value - end - rescue Timeout::Error => detail - warn "Timed out seeking value for %s" % self.name - next - end + tmp = resolve.value break tmp unless tmp.nil? or tmp == "" } diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index b6aae77dcd..1feda3f440 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -5,8 +5,10 @@ # suitable. require 'facter/util/confine' +require 'timeout' + class Facter::Util::Resolution - attr_accessor :interpreter, :code, :name + attr_accessor :interpreter, :code, :name, :timeout def self.have_which if @have_which.nil? @@ -60,6 +62,7 @@ def initialize(name) @name = name @confines = [] @value = nil + @timeout = 0.5 end # Return the number of confines. @@ -95,10 +98,18 @@ def to_s # How we get a value for our resolution mechanism. def value - if @code.is_a?(Proc) - result = @code.call() - else - result = Facter::Util::Resolution.exec(@code,@interpreter) + result = nil + begin + Timeout.timeout(timeout) do + if @code.is_a?(Proc) + result = @code.call() + else + result = Facter::Util::Resolution.exec(@code,@interpreter) + end + end + rescue Timeout::Error => detail + warn "Timed out seeking value for %s" % self.name + return nil end return nil if result == "" diff --git a/spec/unit/util/collection.rb b/spec/unit/util/collection.rb index 81e5d98ac7..7585a52547 100755 --- a/spec/unit/util/collection.rb +++ b/spec/unit/util/collection.rb @@ -44,6 +44,46 @@ @coll.add(:myname) end + it "should accept options" do + @coll.add(:myname, :ldapname => "whatever") { } + end + + it "should set any appropriate options on the fact instances" do + # Use a real fact instance, because we're using respond_to? + fact = Facter::Util::Fact.new(:myname) + fact.expects(:ldapname=).with("testing") + Facter::Util::Fact.expects(:new).with(:myname).returns fact + + @coll.add(:myname, :ldapname => "testing") + end + + it "should set appropriate options on the resolution instance" do + fact = Facter::Util::Fact.new(:myname) + Facter::Util::Fact.expects(:new).with(:myname).returns fact + + resolve = Facter::Util::Resolution.new(:myname) {} + fact.expects(:add).returns resolve + + @coll.add(:myname, :timeout => "myval") {} + end + + it "should not pass fact-specific options to resolutions" do + fact = Facter::Util::Fact.new(:myname) + Facter::Util::Fact.expects(:new).with(:myname).returns fact + + resolve = Facter::Util::Resolution.new(:myname) {} + fact.expects(:add).returns resolve + + fact.expects(:ldapname=).with("foo") + resolve.expects(:timeout=).with("myval") + + @coll.add(:myname, :timeout => "myval", :ldapname => "foo") {} + end + + it "should fail if invalid options are provided" do + lambda { @coll.add(:myname, :foo => :bar) }.should raise_error(ArgumentError) + end + describe "and a block is provided" do it "should use the block to add a resolution to the fact" do fact = mock 'fact' diff --git a/spec/unit/util/fact.rb b/spec/unit/util/fact.rb index cee70b99f6..16520327bd 100755 --- a/spec/unit/util/fact.rb +++ b/spec/unit/util/fact.rb @@ -121,13 +121,6 @@ @fact.value.should be_nil end - - it "should timeout after 0.5 seconds" do - @fact.expects(:warn) - @fact.add { setcode { sleep 2; raise "This is a test" } } - - @fact.value.should be_nil - end end it "should have a method for flushing the cached fact" do diff --git a/spec/unit/util/resolution.rb b/spec/unit/util/resolution.rb index 493ee3a854..e546713f2c 100755 --- a/spec/unit/util/resolution.rb +++ b/spec/unit/util/resolution.rb @@ -17,6 +17,14 @@ Facter::Util::Resolution.new("yay").should respond_to(:setcode) end + it "should support a timeout value" do + Facter::Util::Resolution.new("yay").should respond_to(:timeout=) + end + + it "should default to a timeout of 0.5 seconds" do + Facter::Util::Resolution.new("yay").timeout.should == 0.5 + end + describe "when setting the code" do before do @resolve = Facter::Util::Resolution.new("yay") @@ -82,6 +90,14 @@ @resolve.setcode { "" } @resolve.value.should be_nil end + + it "should timeout after the provided timeout" do + @resolve.expects(:warn) + @resolve.timeout = 0.1 + @resolve.setcode { sleep 2; raise "This is a test" } + + @resolve.value.should be_nil + end end end From 2ee5d29548b48a3c81f574c2be84db60f6ff306a Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Tue, 17 Jun 2008 10:55:22 -0500 Subject: [PATCH 0209/3753] Refactoring how recursive searches are detected. Using a yield hook instead of just setting @searching to be true, which should be more consistent because @searching will now always be turned off at the end of the search. --- lib/facter/util/fact.rb | 56 +++++++++++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 19 deletions(-) diff --git a/lib/facter/util/fact.rb b/lib/facter/util/fact.rb index c96ca3bb3e..214814c5f2 100644 --- a/lib/facter/util/fact.rb +++ b/lib/facter/util/fact.rb @@ -57,26 +57,16 @@ def flush # Return the value for a given fact. Searches through all of the mechanisms # and returns either the first value or nil. def value - unless @value - # make sure we don't get stuck in recursive dependency loops - if @searching - Facter.debug "Caught recursion on %s" % @name - - # return a cached value if we've got it - if @value - return @value - else - return nil - end - end - @value = nil + return @value if @value - if @resolves.length == 0 - Facter.debug "No resolves for %s" % @name - return nil - end + if @resolves.length == 0 + Facter.debug "No resolves for %s" % @name + return nil + end + + searching do + @value = nil - @searching = true foundsuits = false @value = @resolves.inject(nil) { |result, resolve| next unless resolve.suitable? @@ -86,7 +76,6 @@ def value break tmp unless tmp.nil? or tmp == "" } - @searching = false unless foundsuits Facter.debug "Found no suitable resolves of %s for %s" % [@resolves.length, @name] @@ -101,4 +90,33 @@ def value return @value end end + + private + + # Are we in the midst of a search? + def searching? + @searching + end + + # Lock our searching process, so we never ge stuck in recursion. + def searching + if searching? + Facter.debug "Caught recursion on %s" % @name + + # return a cached value if we've got it + if @value + return @value + else + return nil + end + end + + # If we've gotten this far, we're not already searching, so go ahead and do so. + @searching = true + begin + yield + ensure + @searching = false + end + end end From ce7b74c6a5094661f2af63dbdf992a12652f7778 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Tue, 17 Jun 2008 10:59:52 -0500 Subject: [PATCH 0210/3753] Rejustifying all of the whitespace in the facts, yay. --- lib/facter/Cfkey.rb | 49 +++-- lib/facter/architecture.rb | 26 +-- lib/facter/domain.rb | 118 ++++++------ lib/facter/fqdn.rb | 18 +- lib/facter/hardwareisa.rb | 8 +- lib/facter/hardwaremodel.rb | 14 +- lib/facter/hostname.rb | 44 ++--- lib/facter/id.rb | 8 +- lib/facter/ipaddress.rb | 270 +++++++++++++-------------- lib/facter/iphostnumber.rb | 32 ++-- lib/facter/kernel.rb | 6 +- lib/facter/kernelrelease.rb | 14 +- lib/facter/lsb.rb | 27 ++- lib/facter/lsbmajdistrelease.rb | 3 +- lib/facter/macaddress.rb | 120 ++++++------ lib/facter/macosx.rb | 46 ++--- lib/facter/operatingsystem.rb | 66 +++---- lib/facter/operatingsystemrelease.rb | 100 +++++----- lib/facter/ps.rb | 14 +- lib/facter/ssh.rb | 38 ++-- lib/facter/uniqueid.rb | 8 +- 21 files changed, 511 insertions(+), 518 deletions(-) diff --git a/lib/facter/Cfkey.rb b/lib/facter/Cfkey.rb index 794971b18f..4c17a0a126 100644 --- a/lib/facter/Cfkey.rb +++ b/lib/facter/Cfkey.rb @@ -13,31 +13,30 @@ ## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA ## - Facter.add(:Cfkey) do - setcode do - value = nil - ["/usr/local/etc/cfkey.pub", - "/etc/cfkey.pub", - "/var/cfng/keys/localhost.pub", - "/var/cfengine/ppkeys/localhost.pub", - "/var/lib/cfengine/ppkeys/localhost.pub", - "/var/lib/cfengine2/ppkeys/localhost.pub" - ].each { |file| - if FileTest.file?(file) - File.open(file) { |openfile| - value = openfile.readlines.reject { |line| - line =~ /PUBLIC KEY/ - }.collect { |line| - line.chomp - }.join("") - } - end - if value - break - end +Facter.add(:Cfkey) do + setcode do + value = nil + ["/usr/local/etc/cfkey.pub", + "/etc/cfkey.pub", + "/var/cfng/keys/localhost.pub", + "/var/cfengine/ppkeys/localhost.pub", + "/var/lib/cfengine/ppkeys/localhost.pub", + "/var/lib/cfengine2/ppkeys/localhost.pub" + ].each { |file| + if FileTest.file?(file) + File.open(file) { |openfile| + value = openfile.readlines.reject { |line| + line =~ /PUBLIC KEY/ + }.collect { |line| + line.chomp + }.join("") } - - value end - end + if value + break + end + } + value + end +end diff --git a/lib/facter/architecture.rb b/lib/facter/architecture.rb index 5db1834b1c..3665a7fb99 100644 --- a/lib/facter/architecture.rb +++ b/lib/facter/architecture.rb @@ -1,14 +1,14 @@ - Facter.add(:architecture) do - confine :kernel => :linux - setcode do - model = Facter.value(:hardwaremodel) - case model - # most linuxen use "x86_64" - when 'x86_64': - Facter.value(:operatingsystem) == "Debian" ? "amd64" : model; - when /(i[3456]86|pentium)/: "i386" - else - model - end - end +Facter.add(:architecture) do + confine :kernel => :linux + setcode do + model = Facter.value(:hardwaremodel) + case model + # most linuxen use "x86_64" + when 'x86_64': + Facter.value(:operatingsystem) == "Debian" ? "amd64" : model; + when /(i[3456]86|pentium)/: "i386" + else + model end + end +end diff --git a/lib/facter/domain.rb b/lib/facter/domain.rb index a4fa701aac..724dd1ba32 100644 --- a/lib/facter/domain.rb +++ b/lib/facter/domain.rb @@ -1,64 +1,64 @@ - Facter.add(:domain) do - setcode do - # First force the hostname to be checked - Facter.value(:hostname) +Facter.add(:domain) do + setcode do + # First force the hostname to be checked + Facter.value(:hostname) - # Now check to see if it set the domain - if defined? $domain and ! $domain.nil? - $domain - else - nil - end - end + # Now check to see if it set the domain + if defined? $domain and ! $domain.nil? + $domain + else + nil end - # Look for the DNS domain name command first. - Facter.add(:domain) do - setcode do - domain = Facter::Util::Resolution.exec('dnsdomainname') or nil - # make sure it's a real domain - if domain and domain =~ /.+\..+/ - domain - else - nil - end - end + end +end +# Look for the DNS domain name command first. +Facter.add(:domain) do + setcode do + domain = Facter::Util::Resolution.exec('dnsdomainname') or nil + # make sure it's a real domain + if domain and domain =~ /.+\..+/ + domain + else + nil end - Facter.add(:domain) do - setcode do - domain = Facter::Util::Resolution.exec('domainname') or nil - # make sure it's a real domain - if domain and domain =~ /.+\..+/ - domain - else - nil - end - end + end +end +Facter.add(:domain) do + setcode do + domain = Facter::Util::Resolution.exec('domainname') or nil + # make sure it's a real domain + if domain and domain =~ /.+\..+/ + domain + else + nil end - Facter.add(:domain) do - setcode do - value = nil - if FileTest.exists?("/etc/resolv.conf") - File.open("/etc/resolv.conf") { |file| - # is the domain set? - file.each { |line| - if line =~ /domain\s+(\S+)/ - value = $1 - break - end - } - } - ! value and File.open("/etc/resolv.conf") { |file| - # is the search path set? - file.each { |line| - if line =~ /search\s+(\S+)/ - value = $1 - break - end - } - } - value - else - nil - end - end + end +end +Facter.add(:domain) do + setcode do + value = nil + if FileTest.exists?("/etc/resolv.conf") + File.open("/etc/resolv.conf") { |file| + # is the domain set? + file.each { |line| + if line =~ /domain\s+(\S+)/ + value = $1 + break + end + } + } + ! value and File.open("/etc/resolv.conf") { |file| + # is the search path set? + file.each { |line| + if line =~ /search\s+(\S+)/ + value = $1 + break + end + } + } + value + else + nil end + end +end diff --git a/lib/facter/fqdn.rb b/lib/facter/fqdn.rb index 1a97401d2c..1fcc764d76 100644 --- a/lib/facter/fqdn.rb +++ b/lib/facter/fqdn.rb @@ -1,10 +1,10 @@ - Facter.add(:fqdn) do setcode do - host = Facter.value(:hostname) - domain = Facter.value(:domain) - if host and domain - [host, domain].join(".") - else - nil - end - end +Facter.add(:fqdn) do setcode do + host = Facter.value(:hostname) + domain = Facter.value(:domain) + if host and domain + [host, domain].join(".") + else + nil end + end +end diff --git a/lib/facter/hardwareisa.rb b/lib/facter/hardwareisa.rb index 20d0e47c61..4dce535c48 100644 --- a/lib/facter/hardwareisa.rb +++ b/lib/facter/hardwareisa.rb @@ -1,4 +1,4 @@ - Facter.add(:hardwareisa) do - setcode 'uname -p', '/bin/sh' - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE Debian Gentoo FreeBSD OpenBSD NetBSD} - end +Facter.add(:hardwareisa) do + setcode 'uname -p', '/bin/sh' + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE Debian Gentoo FreeBSD OpenBSD NetBSD} +end diff --git a/lib/facter/hardwaremodel.rb b/lib/facter/hardwaremodel.rb index b69848dca2..2118ad4560 100644 --- a/lib/facter/hardwaremodel.rb +++ b/lib/facter/hardwaremodel.rb @@ -1,8 +1,8 @@ - Facter.add(:hardwaremodel) do - setcode 'uname -m' - end +Facter.add(:hardwaremodel) do + setcode 'uname -m' +end - Facter.add(:hardwaremodel) do - confine :operatingsystem => :aix - setcode 'lsattr -El proc0 -a type|cut -f2 -d" "' - end +Facter.add(:hardwaremodel) do + confine :operatingsystem => :aix + setcode 'lsattr -El proc0 -a type|cut -f2 -d" "' +end diff --git a/lib/facter/hostname.rb b/lib/facter/hostname.rb index aec5f936bc..188efa4cda 100644 --- a/lib/facter/hostname.rb +++ b/lib/facter/hostname.rb @@ -1,25 +1,25 @@ - Facter.add(:hostname, :ldapname => "cn") do - setcode do - hostname = nil - name = Facter::Util::Resolution.exec('hostname') or nil - if name - if name =~ /^([\w-]+)\.(.+)$/ - hostname = $1 - # the Domain class uses this - $domain = $2 - else - hostname = name - end - hostname - else - nil - end +Facter.add(:hostname, :ldapname => "cn") do + setcode do + hostname = nil + name = Facter::Util::Resolution.exec('hostname') or nil + if name + if name =~ /^([\w-]+)\.(.+)$/ + hostname = $1 + # the Domain class uses this + $domain = $2 + else + hostname = name end + hostname + else + nil end + end +end - Facter.add(:hostname) do - confine :kernel => :darwin, :kernelrelease => "R7" - setcode do - %x{/usr/sbin/scutil --get LocalHostName} - end - end +Facter.add(:hostname) do + confine :kernel => :darwin, :kernelrelease => "R7" + setcode do + %x{/usr/sbin/scutil --get LocalHostName} + end +end diff --git a/lib/facter/id.rb b/lib/facter/id.rb index 99f822ccaf..41db22f48a 100644 --- a/lib/facter/id.rb +++ b/lib/facter/id.rb @@ -1,4 +1,4 @@ - Facter.add(:id) do - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE Debian Gentoo AIX} - setcode "whoami" - end +Facter.add(:id) do + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE Debian Gentoo AIX} + setcode "whoami" +end diff --git a/lib/facter/ipaddress.rb b/lib/facter/ipaddress.rb index cf80c0cd0e..942c1340d1 100644 --- a/lib/facter/ipaddress.rb +++ b/lib/facter/ipaddress.rb @@ -1,151 +1,151 @@ - Facter.add(:ipaddress, :ldapname => "iphostnumber") do - setcode do - require 'resolv' - - begin - if hostname = Facter.value(:hostname) - ip = Resolv.getaddress(hostname) - unless ip == "127.0.0.1" - ip - end - else - nil - end - rescue Resolv::ResolvError - nil - rescue NoMethodError # i think this is a bug in resolv.rb? - nil +Facter.add(:ipaddress, :ldapname => "iphostnumber") do + setcode do + require 'resolv' + + begin + if hostname = Facter.value(:hostname) + ip = Resolv.getaddress(hostname) + unless ip == "127.0.0.1" + ip end + else + nil end + rescue Resolv::ResolvError + nil + rescue NoMethodError # i think this is a bug in resolv.rb? + nil end - - Facter.add(:ipaddress) do - setcode do - if hostname = Facter.value(:hostname) - # we need Hostname to exist for this to work - host = nil - if host = Facter::Util::Resolution.exec("host #{hostname}") - host = host.chomp.split(/\s/) - if defined? list[-1] and - list[-1] =~ /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ - list[-1] - end - else - nil - end - else - nil + end +end + +Facter.add(:ipaddress) do + setcode do + if hostname = Facter.value(:hostname) + # we need Hostname to exist for this to work + host = nil + if host = Facter::Util::Resolution.exec("host #{hostname}") + host = host.chomp.split(/\s/) + if defined? list[-1] and + list[-1] =~ /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ + list[-1] end + else + nil end + else + nil end + end +end - Facter.add(:ipaddress) do - confine :kernel => :linux - setcode do - ip = nil - output = %x{/sbin/ifconfig} - - output.split(/^\S/).each { |str| - if str =~ /inet addr:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ - tmp = $1 - unless tmp =~ /127\./ - ip = tmp - break - end - end - } - - ip +Facter.add(:ipaddress) do + confine :kernel => :linux + setcode do + ip = nil + output = %x{/sbin/ifconfig} + + output.split(/^\S/).each { |str| + if str =~ /inet addr:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + tmp = $1 + unless tmp =~ /127\./ + ip = tmp + break + end end - end - - Facter.add(:ipaddress) do - confine :kernel => %w{FreeBSD OpenBSD solaris} - setcode do - ip = nil - output = %x{/sbin/ifconfig} - - output.split(/^\S/).each { |str| - if str =~ /inet ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ - tmp = $1 - unless tmp =~ /127\./ - ip = tmp - break - end - end - } - - ip + } + + ip + end +end + +Facter.add(:ipaddress) do + confine :kernel => %w{FreeBSD OpenBSD solaris} + setcode do + ip = nil + output = %x{/sbin/ifconfig} + + output.split(/^\S/).each { |str| + if str =~ /inet ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + tmp = $1 + unless tmp =~ /127\./ + ip = tmp + break + end end - end + } - Facter.add(:ipaddress) do - confine :kernel => %w{NetBSD} - setcode do - ip = nil - output = %x{/sbin/ifconfig -a} - - output.split(/^\S/).each { |str| - if str =~ /inet ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ - tmp = $1 - unless tmp =~ /127\./ - ip = tmp - break - end - end - } - - ip + ip + end +end + +Facter.add(:ipaddress) do + confine :kernel => %w{NetBSD} + setcode do + ip = nil + output = %x{/sbin/ifconfig -a} + + output.split(/^\S/).each { |str| + if str =~ /inet ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + tmp = $1 + unless tmp =~ /127\./ + ip = tmp + break + end end + } + + ip + end +end + +Facter.add(:ipaddress) do + confine :kernel => %w{darwin} + setcode do + ip = nil + iface = "" + output = %x{/usr/sbin/netstat -rn} + if output =~ /^default\s*\S*\s*\S*\s*\S*\s*\S*\s*(\S*).*/ + iface = $1 + else + warn "Could not find a default route. Using first non-loopback interface" + end + if(iface != "") + output = %x{/sbin/ifconfig #{iface}} + else + output = %x{/sbin/ifconfig} end - Facter.add(:ipaddress) do - confine :kernel => %w{darwin} - setcode do - ip = nil - iface = "" - output = %x{/usr/sbin/netstat -rn} - if output =~ /^default\s*\S*\s*\S*\s*\S*\s*\S*\s*(\S*).*/ - iface = $1 - else - warn "Could not find a default route. Using first non-loopback interface" + output.split(/^\S/).each { |str| + if str =~ /inet ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + tmp = $1 + unless tmp =~ /127\./ + ip = tmp + break end - if(iface != "") - output = %x{/sbin/ifconfig #{iface}} - else - output = %x{/sbin/ifconfig} - end - - output.split(/^\S/).each { |str| - if str =~ /inet ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ - tmp = $1 - unless tmp =~ /127\./ - ip = tmp - break - end - end - } - - ip end - end - - Facter.add(:ipaddress) do - confine :kernel => %w{AIX} - setcode do - ip = nil - output = %x{/usr/sbin/ifconfig -a} - - output.split(/^\S/).each { |str| - if str =~ /inet ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ - tmp = $1 - unless tmp =~ /127\./ - ip = tmp - break - end - end - } - - ip + } + + ip + end +end + +Facter.add(:ipaddress) do + confine :kernel => %w{AIX} + setcode do + ip = nil + output = %x{/usr/sbin/ifconfig -a} + + output.split(/^\S/).each { |str| + if str =~ /inet ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + tmp = $1 + unless tmp =~ /127\./ + ip = tmp + break + end end - end + } + + ip + end +end diff --git a/lib/facter/iphostnumber.rb b/lib/facter/iphostnumber.rb index 68b1c9720d..bc384325fe 100644 --- a/lib/facter/iphostnumber.rb +++ b/lib/facter/iphostnumber.rb @@ -1,18 +1,18 @@ - Facter.add(:iphostnumber) do - confine :kernel => :darwin, :kernelrelease => "R6" - setcode do - %x{/usr/sbin/scutil --get LocalHostName} - end - end - Facter.add(:iphostnumber) do - confine :kernel => :darwin, :kernelrelease => "R6" - setcode do - ether = nil - output = %x{/sbin/ifconfig} +Facter.add(:iphostnumber) do + confine :kernel => :darwin, :kernelrelease => "R6" + setcode do + %x{/usr/sbin/scutil --get LocalHostName} + end +end +Facter.add(:iphostnumber) do + confine :kernel => :darwin, :kernelrelease => "R6" + setcode do + ether = nil + output = %x{/sbin/ifconfig} - output =~ /HWaddr (\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ - ether = $1 + output =~ /HWaddr (\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ + ether = $1 - ether - end - end + ether + end +end diff --git a/lib/facter/kernel.rb b/lib/facter/kernel.rb index 4a453c31fa..01fe85b33f 100644 --- a/lib/facter/kernel.rb +++ b/lib/facter/kernel.rb @@ -1,3 +1,3 @@ - Facter.add(:kernel) do - setcode 'uname -s' - end +Facter.add(:kernel) do + setcode 'uname -s' +end diff --git a/lib/facter/kernelrelease.rb b/lib/facter/kernelrelease.rb index 652fd6a924..0f8a64311d 100644 --- a/lib/facter/kernelrelease.rb +++ b/lib/facter/kernelrelease.rb @@ -1,8 +1,8 @@ - Facter.add(:kernelrelease) do - setcode 'uname -r' - end +Facter.add(:kernelrelease) do + setcode 'uname -r' +end - Facter.add(:kernelrelease, :timeout => 5) do - confine :kernel => :aix - setcode 'oslevel -s' - end +Facter.add(:kernelrelease, :timeout => 5) do + confine :kernel => :aix + setcode 'oslevel -s' +end diff --git a/lib/facter/lsb.rb b/lib/facter/lsb.rb index 9d9dba8a7d..6f6e4bfe2b 100644 --- a/lib/facter/lsb.rb +++ b/lib/facter/lsb.rb @@ -19,20 +19,19 @@ "LSBDistDescription" => %r{^Description:\t(.*)$}, "LSBDistCodeName" => %r{^Codename:\t(.*)$} }.each do |fact, pattern| - - Facter.add(fact) do - setcode do - unless defined?(@@lsbdata) and defined?(@@lsbtime) and (Time.now.to_i - @@lsbtime.to_i < 5) - type = nil - @@lsbtime = Time.now - @@lsbdata = Facter::Util::Resolution.exec('lsb_release -a 2>/dev/null') - end + Facter.add(fact) do + setcode do + unless defined?(@@lsbdata) and defined?(@@lsbtime) and (Time.now.to_i - @@lsbtime.to_i < 5) + type = nil + @@lsbtime = Time.now + @@lsbdata = Facter::Util::Resolution.exec('lsb_release -a 2>/dev/null') + end - if pattern.match(@@lsbdata) - $1 - else - nil - end + if pattern.match(@@lsbdata) + $1 + else + nil + end + end end - end end diff --git a/lib/facter/lsbmajdistrelease.rb b/lib/facter/lsbmajdistrelease.rb index 94742dba72..44f3b9b511 100644 --- a/lib/facter/lsbmajdistrelease.rb +++ b/lib/facter/lsbmajdistrelease.rb @@ -10,7 +10,6 @@ else result=Facter.value(:lsbdistrelease) end - result + result end end - diff --git a/lib/facter/macaddress.rb b/lib/facter/macaddress.rb index f42f8a1ee0..30d4b5989c 100644 --- a/lib/facter/macaddress.rb +++ b/lib/facter/macaddress.rb @@ -1,65 +1,65 @@ - Facter.add(:macaddress) do - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE Debian Gentoo} - setcode do - ether = [] - output = %x{/sbin/ifconfig -a} - output.each {|s| - ether.push($1) if s =~ /(?:ether|HWaddr) (\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/ - } - ether[0] - end - end - - Facter.add(:macaddress) do - confine :operatingsystem => %w{FreeBSD OpenBSD} - setcode do - ether = [] - output = %x{/sbin/ifconfig} - output.each {|s| - if s =~ /(?:ether|lladdr)\s+(\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ - ether.push($1) - end - } - ether[0] - end - end +Facter.add(:macaddress) do + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE Debian Gentoo} + setcode do + ether = [] + output = %x{/sbin/ifconfig -a} + output.each {|s| + ether.push($1) if s =~ /(?:ether|HWaddr) (\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/ + } + ether[0] + end +end - Facter.add(:macaddress) do - confine :kernel => :darwin - setcode do - ether = nil - output = %x{/sbin/ifconfig} +Facter.add(:macaddress) do + confine :operatingsystem => %w{FreeBSD OpenBSD} + setcode do + ether = [] + output = %x{/sbin/ifconfig} + output.each {|s| + if s =~ /(?:ether|lladdr)\s+(\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ + ether.push($1) + end + } + ether[0] + end +end - output.split(/^\S/).each { |str| - if str =~ /10baseT/ # we're wired - str =~ /ether (\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ - ether = $1 - end - } +Facter.add(:macaddress) do + confine :kernel => :darwin + setcode do + ether = nil + output = %x{/sbin/ifconfig} - ether + output.split(/^\S/).each { |str| + if str =~ /10baseT/ # we're wired + str =~ /ether (\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ + ether = $1 end - end - - Facter.add(:macaddress) do - confine :kernel => %w{AIX} - setcode do - ether = [] - ip = nil - output = %x{/usr/sbin/ifconfig -a} - output.each { |str| - if str =~ /([a-z]+\d+): flags=/ - devname = $1 - unless devname =~ /lo0/ - output2 = %x{/usr/bin/entstat #{devname}} - output2.each { |str2| - if str2 =~ /^Hardware Address: (\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/ - ether.push($1) - end - } - end - end - } - ether[0] + } + + ether + end +end + +Facter.add(:macaddress) do + confine :kernel => %w{AIX} + setcode do + ether = [] + ip = nil + output = %x{/usr/sbin/ifconfig -a} + output.each { |str| + if str =~ /([a-z]+\d+): flags=/ + devname = $1 + unless devname =~ /lo0/ + output2 = %x{/usr/bin/entstat #{devname}} + output2.each { |str2| + if str2 =~ /^Hardware Address: (\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/ + ether.push($1) + end + } + end end - end + } + ether[0] + end +end diff --git a/lib/facter/macosx.rb b/lib/facter/macosx.rb index 60bd1ad332..e7573b0c29 100644 --- a/lib/facter/macosx.rb +++ b/lib/facter/macosx.rb @@ -10,11 +10,11 @@ # as published by the Free Software Foundation (version 2 of the License) # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA # Jeff McCune # There's a lot more information coming out of system_profiler -xml @@ -25,30 +25,30 @@ require 'facter/util/macosx' if Facter.value(:kernel) == "Darwin" - Facter::Macosx.hardware_overview.each do |fact, value| - Facter.add("sp_#{fact}") do - confine :kernel => :darwin - setcode do - value.to_s - end + Facter::Macosx.hardware_overview.each do |fact, value| + Facter.add("sp_#{fact}") do + confine :kernel => :darwin + setcode do + value.to_s + end + end end - end - Facter::Macosx.os_overview.each do |fact, value| - Facter.add("sp_#{fact}") do - confine :kernel => :darwin - setcode do - value.to_s - end + Facter::Macosx.os_overview.each do |fact, value| + Facter.add("sp_#{fact}") do + confine :kernel => :darwin + setcode do + value.to_s + end + end end - end - Facter::Macosx.sw_vers.each do |fact, value| - Facter.add(fact) do - confine :kernel => :darwin - setcode do - value - end + Facter::Macosx.sw_vers.each do |fact, value| + Facter.add(fact) do + confine :kernel => :darwin + setcode do + value + end + end end - end end diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index 6233aaa56f..d43da2dc28 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -1,37 +1,37 @@ - Facter.add(:operatingsystem) do - confine :kernel => :sunos - setcode do "Solaris" end - end +Facter.add(:operatingsystem) do + confine :kernel => :sunos + setcode do "Solaris" end +end - Facter.add(:operatingsystem) do - confine :kernel => :linux - setcode do - if Facter.value(:lsbdistid) == "Ubuntu" - "Ubuntu" - elsif FileTest.exists?("/etc/debian_version") - "Debian" - elsif FileTest.exists?("/etc/gentoo-release") - "Gentoo" - elsif FileTest.exists?("/etc/fedora-release") - "Fedora" - elsif FileTest.exists?("/etc/mandriva-release") - "Mandriva" - elsif FileTest.exists?("/etc/mandrake-release") - "Mandrake" - elsif FileTest.exists?("/etc/redhat-release") - txt = File.read("/etc/redhat-release") - if txt =~ /centos/i - "CentOS" - else - "RedHat" - end - elsif FileTest.exists?("/etc/SuSE-release") - "SuSE" - end +Facter.add(:operatingsystem) do + confine :kernel => :linux + setcode do + if Facter.value(:lsbdistid) == "Ubuntu" + "Ubuntu" + elsif FileTest.exists?("/etc/debian_version") + "Debian" + elsif FileTest.exists?("/etc/gentoo-release") + "Gentoo" + elsif FileTest.exists?("/etc/fedora-release") + "Fedora" + elsif FileTest.exists?("/etc/mandriva-release") + "Mandriva" + elsif FileTest.exists?("/etc/mandrake-release") + "Mandrake" + elsif FileTest.exists?("/etc/redhat-release") + txt = File.read("/etc/redhat-release") + if txt =~ /centos/i + "CentOS" + else + "RedHat" end + elsif FileTest.exists?("/etc/SuSE-release") + "SuSE" end + end +end - Facter.add(:operatingsystem) do - # Default to just returning the kernel as the operating system - setcode do Facter[:kernel].value end - end +Facter.add(:operatingsystem) do + # Default to just returning the kernel as the operating system + setcode do Facter[:kernel].value end +end diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index 7991d6ce90..291b82ca1d 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -1,61 +1,61 @@ - Facter.add(:operatingsystemrelease) do - confine :operatingsystem => :fedora - setcode do - File::open("/etc/fedora-release", "r") do |f| - line = f.readline.chomp - if line =~ /\(Rawhide\)$/ - "Rawhide" - elsif line =~ /release (\d+)/ - $1 - end - end +Facter.add(:operatingsystemrelease) do + confine :operatingsystem => :fedora + setcode do + File::open("/etc/fedora-release", "r") do |f| + line = f.readline.chomp + if line =~ /\(Rawhide\)$/ + "Rawhide" + elsif line =~ /release (\d+)/ + $1 end end + end +end - Facter.add(:operatingsystemrelease) do - confine :operatingsystem => %w{RedHat} - setcode do - File::open("/etc/redhat-release", "r") do |f| - line = f.readline.chomp - if line =~ /\(Rawhide\)$/ - "Rawhide" - elsif line =~ /release (\d+)/ - $1 - end - end +Facter.add(:operatingsystemrelease) do + confine :operatingsystem => %w{RedHat} + setcode do + File::open("/etc/redhat-release", "r") do |f| + line = f.readline.chomp + if line =~ /\(Rawhide\)$/ + "Rawhide" + elsif line =~ /release (\d+)/ + $1 end end + end +end - Facter.add(:operatingsystemrelease) do - confine :operatingsystem => %w{CentOS} - setcode do - release = Facter::Util::Resolution.exec('rpm -q centos-release') - if release =~ /release-(\d+)/ - $1 - end +Facter.add(:operatingsystemrelease) do + confine :operatingsystem => %w{CentOS} + setcode do + release = Facter::Util::Resolution.exec('rpm -q centos-release') + if release =~ /release-(\d+)/ + $1 end - end + end +end - Facter.add(:operatingsystemrelease) do - confine :operatingsystem => %w{Debian} - setcode do - release = Facter::Util::Resolution.exec('cat /proc/version') - if release =~ /\(Debian (\d+.\d+).\d+-\d+\)/ - $1 - end - end - end +Facter.add(:operatingsystemrelease) do + confine :operatingsystem => %w{Debian} + setcode do + release = Facter::Util::Resolution.exec('cat /proc/version') + if release =~ /\(Debian (\d+.\d+).\d+-\d+\)/ + $1 + end + end +end - Facter.add(:operatingsystemrelease) do - confine :operatingsystem => %w{Ubuntu} - setcode do - release = Facter::Util::Resolution.exec('cat /etc/issue') - if release =~ /Ubuntu (\d+.\d+)/ - $1 - end +Facter.add(:operatingsystemrelease) do + confine :operatingsystem => %w{Ubuntu} + setcode do + release = Facter::Util::Resolution.exec('cat /etc/issue') + if release =~ /Ubuntu (\d+.\d+)/ + $1 end - end + end +end - Facter.add(:operatingsystemrelease) do - setcode do Facter[:kernelrelease].value end - end +Facter.add(:operatingsystemrelease) do + setcode do Facter[:kernelrelease].value end +end diff --git a/lib/facter/ps.rb b/lib/facter/ps.rb index fbf0c7fcd1..23f238da0d 100644 --- a/lib/facter/ps.rb +++ b/lib/facter/ps.rb @@ -1,8 +1,8 @@ - Facter.add(:ps) do - setcode do 'ps -ef' end - end +Facter.add(:ps) do + setcode do 'ps -ef' end +end - Facter.add(:ps) do - confine :operatingsystem => %w{FreeBSD NetBSD OpenBSD Darwin} - setcode do 'ps -auxwww' end - end +Facter.add(:ps) do + confine :operatingsystem => %w{FreeBSD NetBSD OpenBSD Darwin} + setcode do 'ps -auxwww' end +end diff --git a/lib/facter/ssh.rb b/lib/facter/ssh.rb index 29ea67ce55..318e9d44c3 100644 --- a/lib/facter/ssh.rb +++ b/lib/facter/ssh.rb @@ -13,25 +13,21 @@ ## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA ## - ["/etc/ssh","/usr/local/etc/ssh","/etc","/usr/local/etc"].each { |dir| - {"SSHDSAKey" => "ssh_host_dsa_key.pub", - "SSHRSAKey" => "ssh_host_rsa_key.pub"}.each { |name,file| - Facter.add(name) do - setcode do +["/etc/ssh","/usr/local/etc/ssh","/etc","/usr/local/etc"].each do |dir| + {"SSHDSAKey" => "ssh_host_dsa_key.pub", "SSHRSAKey" => "ssh_host_rsa_key.pub"}.each do |name,file| + Facter.add(name) do + setcode do + value = nil + filepath = File.join(dir,file) + if FileTest.file?(filepath) + begin + File.open(filepath) { |f| value = f.read.chomp.split(/\s+/)[1] } + rescue value = nil - filepath = File.join(dir,file) - if FileTest.file?(filepath) - begin - File.open(filepath) { |f| - value = f.read.chomp.split(/\s+/)[1] - } - rescue - value = nil - end - end - value - end # end of proc - end # end of add - } # end of hash each - } # end of dir each - + end + end + value + end # end of proc + end # end of add + end # end of hash each +end # end of dir each diff --git a/lib/facter/uniqueid.rb b/lib/facter/uniqueid.rb index 2eae3ccb0a..d1553c5fca 100644 --- a/lib/facter/uniqueid.rb +++ b/lib/facter/uniqueid.rb @@ -1,4 +1,4 @@ - Facter.add(:uniqueid) do - setcode 'hostid', '/bin/sh' - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE Debian Gentoo AIX} - end +Facter.add(:uniqueid) do + setcode 'hostid', '/bin/sh' + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE Debian Gentoo AIX} +end From def18b5352c8d3365531e9bdb030e240c9178925 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Wed, 18 Jun 2008 09:33:31 +1000 Subject: [PATCH 0211/3753] Fixes #1357 - change ps syntax for OSX and BSD --- CHANGELOG | 2 ++ lib/facter/ps.rb | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 070bee6643..7cb6bff9bb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ ?: + Fixes #1357 - change ps syntax for OSX and BSD + Set the timeout on the AIX kernelrelease fact to 5 seconds. Refactored so each fact resolution can specify a separate timeout, diff --git a/lib/facter/ps.rb b/lib/facter/ps.rb index 23f238da0d..e7bcdae984 100644 --- a/lib/facter/ps.rb +++ b/lib/facter/ps.rb @@ -4,5 +4,5 @@ Facter.add(:ps) do confine :operatingsystem => %w{FreeBSD NetBSD OpenBSD Darwin} - setcode do 'ps -auxwww' end + setcode do 'ps auxwww' end end From 0b0892d382a78b3a69a97d6b3820e0a101665b8f Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Wed, 18 Jun 2008 09:49:30 +1000 Subject: [PATCH 0212/3753] Fixes #1334 - Forced Facter to use LANG=C --- CHANGELOG | 4 +++- lib/facter.rb | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 7cb6bff9bb..b097ad85fd 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,7 @@ ?: - Fixes #1357 - change ps syntax for OSX and BSD + Fixes #1334 - Forced Facter to use LANG=C + + Fixes #1357 - Change ps syntax for OSX and BSD Set the timeout on the AIX kernelrelease fact to 5 seconds. diff --git a/lib/facter.rb b/lib/facter.rb index 22864000fb..b73d5ba2d6 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -41,6 +41,9 @@ module Util; end # puts Facter['operatingsystem'] # + # Set LANG to force i18n to C + # + ENV['LANG'] = 'C' GREEN = "" RESET = "" From 2ac29aca6a4ef80ac7753e89322eac217f6176a6 Mon Sep 17 00:00:00 2001 From: Steven Hajducko Date: Thu, 19 Jun 2008 11:02:31 -0700 Subject: [PATCH 0213/3753] Added processorcount and type facts to AIX tickets/1.5/1375 Signed-off-by: Steven Hajducko --- lib/facter/processor.rb | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb index 5e8e42eacb..afc604e220 100644 --- a/lib/facter/processor.rb +++ b/lib/facter/processor.rb @@ -37,3 +37,38 @@ end end end + +if Facter.value(:kernel) == "AIX" + processor_num = -1 + processor_list = {} + Thread::exclusive do + procs = Facter::Util::Resolution.exec('lsdev -Cc processor') + procs.each do |proc| + if proc =~ /^proc(\d+)/ + processor_num = $1.to_i + # Not retrieving the frequency since AIX 4.3.3 doesn't support the + # attribute and some people still use the OS. + proctype = Facter::Util::Resolution.exec('lsattr -El proc0 -a type') + if proctype =~ /^type\s+(\S+)\s+/ + processor_list["processor#{processor_num}"] = $1 + end + end + end + end + + Facter.add("ProcessorCount") do + confine :kernel => :aix + setcode do + processor_list.length.to_s + end + end + + processor_list.each do |proc, desc| + Facter.add(proc) do + confine :kernel => :aix + setcode do + desc + end + end + end +end From 030388530edcfb1f53b798fec292bf6e3a770d13 Mon Sep 17 00:00:00 2001 From: Steven Hajducko Date: Thu, 19 Jun 2008 17:58:36 -0700 Subject: [PATCH 0214/3753] Fixes #1376 - Display memory facts for AIX memfree is still unavailable - no known way to get the fact without root and monitoring commands. --- CHANGELOG | 2 ++ lib/facter/memory.rb | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index b097ad85fd..b67b21428b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ ?: + Fixes #1376 - Display memory facts for AIX + Fixes #1334 - Forced Facter to use LANG=C Fixes #1357 - Change ps syntax for OSX and BSD diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb index 46fe8597de..01858d88bb 100644 --- a/lib/facter/memory.rb +++ b/lib/facter/memory.rb @@ -18,3 +18,28 @@ end end end + +if Facter.value(:kernel) == "AIX" + swap = Facter::Util::Resolution.exec('swap -l') + swapfree, swaptotal = 0, 0 + swap.each do |dev| + if dev =~ /^\/\S+\s.*\s+(\S+)MB\s+(\S+)MB/ + swaptotal += $1.to_i + swapfree += $2.to_i + end + end + + Facter.add("SwapSize") do + confine :kernel => :aix + setcode do + Facter::Memory.scale_number(swaptotal.to_f,"MB") + end + end + + Facter.add("SwapFree") do + confine :kernel => :aix + setcode do + Facter::Memory.scale_number(swapfree.to_f,"MB") + end + end +end From 40a9c1d8d0579f7657ec7fa2d1bf1e9e7b4e5e88 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Thu, 19 Jun 2008 21:44:34 -0500 Subject: [PATCH 0215/3753] Fixing some warnings in various classes. --- lib/facter.rb | 2 +- lib/facter/util/fact.rb | 2 +- lib/facter/util/resolution.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/facter.rb b/lib/facter.rb index b73d5ba2d6..2348b02cb9 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -80,7 +80,7 @@ def self.[](name) end class << self - [:add, :fact, :flush, :list, :value].each do |method| + [:fact, :flush, :list, :value].each do |method| define_method(method) do |*args| collection.send(method, *args) end diff --git a/lib/facter/util/fact.rb b/lib/facter/util/fact.rb index 214814c5f2..ca2ba033ea 100644 --- a/lib/facter/util/fact.rb +++ b/lib/facter/util/fact.rb @@ -4,7 +4,7 @@ class Facter::Util::Fact TIMEOUT = 5 - attr_accessor :name, :searching, :ldapname + attr_accessor :name, :ldapname # Create a new fact, with no resolution mechanisms. def initialize(name, options = {}) diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 1feda3f440..e5936bd4f2 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -11,7 +11,7 @@ class Facter::Util::Resolution attr_accessor :interpreter, :code, :name, :timeout def self.have_which - if @have_which.nil? + if ! defined?(@have_which) or @have_which.nil? %x{which which 2>/dev/null} @have_which = ($? == 0) end From 145cee279bf61cbc737c9b767c3641937020790a Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Thu, 19 Jun 2008 21:44:44 -0500 Subject: [PATCH 0216/3753] Setting the timeout for the puppetversion fact to 1.5. --- lib/facter/puppetversion.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/puppetversion.rb b/lib/facter/puppetversion.rb index 66fcfe8b21..16510c880a 100644 --- a/lib/facter/puppetversion.rb +++ b/lib/facter/puppetversion.rb @@ -1,4 +1,4 @@ -Facter.add(:puppetversion) do +Facter.add(:puppetversion, :timeout => 1.5) do setcode { begin require 'puppet' From e22b4087b368cc29ac37459950359fa1b7487fb6 Mon Sep 17 00:00:00 2001 From: Steven Hajducko Date: Thu, 19 Jun 2008 21:34:52 -0700 Subject: [PATCH 0217/3753] Changed 'timeout' option to 'limit' This was done to avoid a possible variable scoping issue that was occuring on some AIX systems. --- CHANGELOG | 2 ++ lib/facter/kernelrelease.rb | 2 +- lib/facter/puppetversion.rb | 2 +- lib/facter/util/resolution.rb | 6 +++--- spec/unit/util/collection.rb | 6 +++--- spec/unit/util/resolution.rb | 6 +++--- 6 files changed, 13 insertions(+), 11 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index b67b21428b..f591b427f3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ ?: + Changed 'timeout' option to 'limit' to avoid scope issue + Fixes #1376 - Display memory facts for AIX Fixes #1334 - Forced Facter to use LANG=C diff --git a/lib/facter/kernelrelease.rb b/lib/facter/kernelrelease.rb index 0f8a64311d..07b65b54e5 100644 --- a/lib/facter/kernelrelease.rb +++ b/lib/facter/kernelrelease.rb @@ -2,7 +2,7 @@ setcode 'uname -r' end -Facter.add(:kernelrelease, :timeout => 5) do +Facter.add(:kernelrelease, :limit => 5) do confine :kernel => :aix setcode 'oslevel -s' end diff --git a/lib/facter/puppetversion.rb b/lib/facter/puppetversion.rb index 16510c880a..d16250cb8b 100644 --- a/lib/facter/puppetversion.rb +++ b/lib/facter/puppetversion.rb @@ -1,4 +1,4 @@ -Facter.add(:puppetversion, :timeout => 1.5) do +Facter.add(:puppetversion, :limit => 1.5) do setcode { begin require 'puppet' diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index e5936bd4f2..bc9fdecf25 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -8,7 +8,7 @@ require 'timeout' class Facter::Util::Resolution - attr_accessor :interpreter, :code, :name, :timeout + attr_accessor :interpreter, :code, :name, :limit def self.have_which if ! defined?(@have_which) or @have_which.nil? @@ -62,7 +62,7 @@ def initialize(name) @name = name @confines = [] @value = nil - @timeout = 0.5 + @limit = 0.5 end # Return the number of confines. @@ -100,7 +100,7 @@ def to_s def value result = nil begin - Timeout.timeout(timeout) do + Timeout.timeout(limit) do if @code.is_a?(Proc) result = @code.call() else diff --git a/spec/unit/util/collection.rb b/spec/unit/util/collection.rb index 7585a52547..a526ed3f0c 100755 --- a/spec/unit/util/collection.rb +++ b/spec/unit/util/collection.rb @@ -64,7 +64,7 @@ resolve = Facter::Util::Resolution.new(:myname) {} fact.expects(:add).returns resolve - @coll.add(:myname, :timeout => "myval") {} + @coll.add(:myname, :limit => "myval") {} end it "should not pass fact-specific options to resolutions" do @@ -75,9 +75,9 @@ fact.expects(:add).returns resolve fact.expects(:ldapname=).with("foo") - resolve.expects(:timeout=).with("myval") + resolve.expects(:limit=).with("myval") - @coll.add(:myname, :timeout => "myval", :ldapname => "foo") {} + @coll.add(:myname, :limit => "myval", :ldapname => "foo") {} end it "should fail if invalid options are provided" do diff --git a/spec/unit/util/resolution.rb b/spec/unit/util/resolution.rb index e546713f2c..462bf1776e 100755 --- a/spec/unit/util/resolution.rb +++ b/spec/unit/util/resolution.rb @@ -18,11 +18,11 @@ end it "should support a timeout value" do - Facter::Util::Resolution.new("yay").should respond_to(:timeout=) + Facter::Util::Resolution.new("yay").should respond_to(:limit=) end it "should default to a timeout of 0.5 seconds" do - Facter::Util::Resolution.new("yay").timeout.should == 0.5 + Facter::Util::Resolution.new("yay").limit.should == 0.5 end describe "when setting the code" do @@ -93,7 +93,7 @@ it "should timeout after the provided timeout" do @resolve.expects(:warn) - @resolve.timeout = 0.1 + @resolve.limit = 0.1 @resolve.setcode { sleep 2; raise "This is a test" } @resolve.value.should be_nil From 2ef2041dcf825ff6a26fe12b5cb96659dcd516d8 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Fri, 20 Jun 2008 10:23:02 -0500 Subject: [PATCH 0218/3753] Retaining 'timeout' as the settor, but using 'limit' as the getter. I don't like using Fact.add(:fact, :limit => 5), so I added a little extra code to allow us to continue using ':timeout => 5', but use 'limit' for retrieving the value, to avoid the conflict Steve Hajducko is seeing. --- lib/facter/kernelrelease.rb | 2 +- lib/facter/puppetversion.rb | 2 +- lib/facter/util/resolution.rb | 11 +++++++++-- spec/unit/util/collection.rb | 6 +++--- spec/unit/util/resolution.rb | 18 ++++++++++++++++-- 5 files changed, 30 insertions(+), 9 deletions(-) diff --git a/lib/facter/kernelrelease.rb b/lib/facter/kernelrelease.rb index 07b65b54e5..0f8a64311d 100644 --- a/lib/facter/kernelrelease.rb +++ b/lib/facter/kernelrelease.rb @@ -2,7 +2,7 @@ setcode 'uname -r' end -Facter.add(:kernelrelease, :limit => 5) do +Facter.add(:kernelrelease, :timeout => 5) do confine :kernel => :aix setcode 'oslevel -s' end diff --git a/lib/facter/puppetversion.rb b/lib/facter/puppetversion.rb index d16250cb8b..16510c880a 100644 --- a/lib/facter/puppetversion.rb +++ b/lib/facter/puppetversion.rb @@ -1,4 +1,4 @@ -Facter.add(:puppetversion, :limit => 1.5) do +Facter.add(:puppetversion, :timeout => 1.5) do setcode { begin require 'puppet' diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index bc9fdecf25..cc8c842bc2 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -8,7 +8,7 @@ require 'timeout' class Facter::Util::Resolution - attr_accessor :interpreter, :code, :name, :limit + attr_accessor :interpreter, :code, :name, :timeout def self.have_which if ! defined?(@have_which) or @have_which.nil? @@ -62,7 +62,7 @@ def initialize(name) @name = name @confines = [] @value = nil - @limit = 0.5 + @timeout = 0.5 end # Return the number of confines. @@ -70,6 +70,13 @@ def length @confines.length end + # We need this as a getter for 'timeout', because some versions + # of ruby seem to already have a 'timeout' method and we can't + # seem to override the instance methods, somehow. + def limit + @timeout + end + # Set our code for returning a value. def setcode(string = nil, interp = nil, &block) if string diff --git a/spec/unit/util/collection.rb b/spec/unit/util/collection.rb index a526ed3f0c..7585a52547 100755 --- a/spec/unit/util/collection.rb +++ b/spec/unit/util/collection.rb @@ -64,7 +64,7 @@ resolve = Facter::Util::Resolution.new(:myname) {} fact.expects(:add).returns resolve - @coll.add(:myname, :limit => "myval") {} + @coll.add(:myname, :timeout => "myval") {} end it "should not pass fact-specific options to resolutions" do @@ -75,9 +75,9 @@ fact.expects(:add).returns resolve fact.expects(:ldapname=).with("foo") - resolve.expects(:limit=).with("myval") + resolve.expects(:timeout=).with("myval") - @coll.add(:myname, :limit => "myval", :ldapname => "foo") {} + @coll.add(:myname, :timeout => "myval", :ldapname => "foo") {} end it "should fail if invalid options are provided" do diff --git a/spec/unit/util/resolution.rb b/spec/unit/util/resolution.rb index 462bf1776e..617b4dcaf7 100755 --- a/spec/unit/util/resolution.rb +++ b/spec/unit/util/resolution.rb @@ -18,13 +18,19 @@ end it "should support a timeout value" do - Facter::Util::Resolution.new("yay").should respond_to(:limit=) + Facter::Util::Resolution.new("yay").should respond_to(:timeout=) end it "should default to a timeout of 0.5 seconds" do Facter::Util::Resolution.new("yay").limit.should == 0.5 end + it "should provide a 'limit' method that returns the timeout" do + res = Facter::Util::Resolution.new("yay") + res.timeout = "testing" + res.limit.should == "testing" + end + describe "when setting the code" do before do @resolve = Facter::Util::Resolution.new("yay") @@ -91,9 +97,17 @@ @resolve.value.should be_nil end + it "should use its limit method to determine the timeout, to avoid conflict when a 'timeout' method exists for some other reason" do + @resolve.expects(:timeout).never + @resolve.expects(:limit).returns "foo" + Timeout.expects(:timeout).with("foo") + + @resolve.value + end + it "should timeout after the provided timeout" do @resolve.expects(:warn) - @resolve.limit = 0.1 + @resolve.timeout = 0.1 @resolve.setcode { sleep 2; raise "This is a test" } @resolve.value.should be_nil From 9581190fc6164ae9a4828088966fc711931ecad1 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sat, 21 Jun 2008 11:55:29 +1000 Subject: [PATCH 0219/3753] Partial fix for #1345 - BSD interfaces with aliases now select the first address by default --- lib/facter/util/ip.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index fb13383f94..1a0c61154d 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -66,7 +66,7 @@ def self.get_interface_value_nonbsd(interface, label) def self.get_interface_value_bsd(interface, label) - tmp1 = nil + tmp1 = [] int_hash = {} output_int = %x{/sbin/ifconfig #{interface}} @@ -85,12 +85,12 @@ def self.get_interface_value_bsd(interface, label) if interface != "lo" && interface != "lo0" output_int.each { |s| - tmp1 = $1 if s =~ regex + tmp1.push($1) if s =~ regex } end if tmp1 - value = tmp1 + value = tmp1.shift end end From a44d6c3cbf21e5b6942d8756684ab865204533f8 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sat, 21 Jun 2008 12:49:46 +1000 Subject: [PATCH 0220/3753] Fixes #1378 - update manufacter.rb facts to support BSD --- lib/facter/util/manufacturer.rb | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/facter/util/manufacturer.rb b/lib/facter/util/manufacturer.rb index b48ffc78b1..33ef9425de 100644 --- a/lib/facter/util/manufacturer.rb +++ b/lib/facter/util/manufacturer.rb @@ -3,16 +3,23 @@ module Facter::Manufacturer def self.dmi_find_system_info(name) - return nil unless FileTest.exists?("/usr/sbin/dmidecode") - - output=%x{/usr/sbin/dmidecode 2>/dev/null} + case Facter.value(:kernel) + when 'Linux' + return nil unless FileTest.exists?("/usr/sbin/dmidecode") + + output=%x{/usr/sbin/dmidecode 2>/dev/null} + when 'OpenBSD', 'NetBSD', 'FreeBSD' + return nil unless FileTest.exists?("/usr/local/sbin/dmidecode") + + output=%x{/usr/local/sbin/dmidecode 2>/dev/null} + end name.each_pair do |key,v| v.each do |value| output.split("Handle").each do |line| if line =~ /#{key}/ and line =~ /#{value} (\w.*)\n*./ result = $1 Facter.add(value.chomp(':').gsub(' ','')) do - confine :kernel => :linux + confine :kernel => [ :linux, :freebsd, :netbsd, :openbsd ] setcode do result end From 9b464deae3665bec2da0bb25d7cfa946727d0450 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sun, 22 Jun 2008 09:50:34 +1000 Subject: [PATCH 0221/3753] Further fixes #1378 - updated dmidecode for NetBSD --- lib/facter/util/manufacturer.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/facter/util/manufacturer.rb b/lib/facter/util/manufacturer.rb index 33ef9425de..54760b79d7 100644 --- a/lib/facter/util/manufacturer.rb +++ b/lib/facter/util/manufacturer.rb @@ -8,10 +8,14 @@ def self.dmi_find_system_info(name) return nil unless FileTest.exists?("/usr/sbin/dmidecode") output=%x{/usr/sbin/dmidecode 2>/dev/null} - when 'OpenBSD', 'NetBSD', 'FreeBSD' + when 'OpenBSD', 'FreeBSD' return nil unless FileTest.exists?("/usr/local/sbin/dmidecode") output=%x{/usr/local/sbin/dmidecode 2>/dev/null} + when 'NetBSD' + return nil unless FileTest.exists?("/usr/pkg/sbin/dmidecode") + + output=%x{/usr/pkg/sbin/dmidecode 2>/dev/null} end name.each_pair do |key,v| v.each do |value| From 927b3a1a5f227101623dd63efefe8fc2d4877742 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Tue, 1 Jul 2008 10:17:16 -0500 Subject: [PATCH 0222/3753] Adding a default case for the manufacturer information. --- lib/facter/util/manufacturer.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/facter/util/manufacturer.rb b/lib/facter/util/manufacturer.rb index 54760b79d7..03f2126145 100644 --- a/lib/facter/util/manufacturer.rb +++ b/lib/facter/util/manufacturer.rb @@ -16,6 +16,8 @@ def self.dmi_find_system_info(name) return nil unless FileTest.exists?("/usr/pkg/sbin/dmidecode") output=%x{/usr/pkg/sbin/dmidecode 2>/dev/null} + else + return end name.each_pair do |key,v| v.each do |value| From 8c9164973a5c4acf1ff0d5b6851ab3daf28e407f Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Mon, 7 Jul 2008 23:13:25 -0500 Subject: [PATCH 0223/3753] Fixed #1400 - OperatingSystemRelease should now work on CentOS Signed-off-by: Luke Kanies --- CHANGELOG | 2 ++ lib/facter/operatingsystemrelease.rb | 10 ++++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index f591b427f3..0fb096c143 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ ?: + Fixed #1400 - OperatingSystemRelease should now work on CentOS + Changed 'timeout' option to 'limit' to avoid scope issue Fixes #1376 - Display memory facts for AIX diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index 291b82ca1d..843dfdb65c 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -29,10 +29,12 @@ Facter.add(:operatingsystemrelease) do confine :operatingsystem => %w{CentOS} setcode do - release = Facter::Util::Resolution.exec('rpm -q centos-release') - if release =~ /release-(\d+)/ - $1 - end + centosrelease = Facter::Util::Resolution.exec('cat /etc/redhat-release | sed -e \'s/CentOS release//g\' -e \'s/(Final)//g\'') + if centosrelease =~ /^5^/ + release = Facter::Util::Resolution.exec('rpm -q --qf \'%{VERSION}.%{RELEASE}\' centos-release | cut -d. -f1,2') + else + release = Facter::Util::Resolution.exec('cat /etc/redhat-release | sed -e \'s/CentOS release//g\' -e \'s/(Final)//g\'') + end end end From 88fe243d4b48329956e91609ff1566898eed6fb2 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Tue, 8 Jul 2008 14:19:58 +1000 Subject: [PATCH 0224/3753] Fixed formatting --- lib/facter/util/manufacturer.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/facter/util/manufacturer.rb b/lib/facter/util/manufacturer.rb index 54760b79d7..836ca86ca9 100644 --- a/lib/facter/util/manufacturer.rb +++ b/lib/facter/util/manufacturer.rb @@ -25,13 +25,13 @@ def self.dmi_find_system_info(name) Facter.add(value.chomp(':').gsub(' ','')) do confine :kernel => [ :linux, :freebsd, :netbsd, :openbsd ] setcode do - result - end - end - end - end - end - end -end + result + end + end + end + end + end + end + end end From d49d63c7cad4c9c116b4112786a45fec358e1f2f Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Tue, 8 Jul 2008 06:27:16 +0200 Subject: [PATCH 0225/3753] Updating the changelog for 1.5 Signed-off-by: Luke Kanies --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 0fb096c143..4725c815f7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,4 @@ -?: +1.5: Fixed #1400 - OperatingSystemRelease should now work on CentOS Changed 'timeout' option to 'limit' to avoid scope issue From e98efd378aacfb9b8c0933d299a9ca3026c4b0eb Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Tue, 8 Jul 2008 06:27:28 +0200 Subject: [PATCH 0226/3753] Updated to version 1.5 --- lib/facter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter.rb b/lib/facter.rb index 2348b02cb9..088534933b 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -27,7 +27,7 @@ module Util; end include Comparable include Enumerable - FACTERVERSION = '1.3.8' + FACTERVERSION = '1.5' # = Facter # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. From 7042c4690aa9c23126c4395e0911a890c1fa17a3 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Tue, 8 Jul 2008 06:27:29 +0200 Subject: [PATCH 0227/3753] Updated to version 1.5 --- conf/redhat/facter.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index befbd4b5b2..0e360cfdc2 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -5,7 +5,7 @@ Summary: Ruby module for collecting simple facts about a host operating system Name: facter -Version: 1.5.0 +Version: 1.5 Release: 1%{?dist} License: GPL Group: System Environment/Base From ff0e90b1c9a70369e1dba5bc760a4ed3289aae6d Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Tue, 8 Jul 2008 06:44:14 +0200 Subject: [PATCH 0228/3753] Adding (apparently now required) author info to the gem spec Signed-off-by: Luke Kanies --- Rakefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Rakefile b/Rakefile index 55f967c947..ba15fd1cfb 100644 --- a/Rakefile +++ b/Rakefile @@ -35,6 +35,8 @@ project.mkgemtask do |gem| gem.bindir = "bin" # Use these for applications. gem.executables = ["facter"] gem.default_executable = "facter" + + gem.author = "Luke Kanies" end if project.has?(:epm) From 1cf98d16d1c3897ac8a4a059bd823476aace2d34 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Wed, 9 Jul 2008 17:56:48 +1000 Subject: [PATCH 0229/3753] Adjusted version to be in line with previous standard --- CHANGELOG | 2 +- lib/facter.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 4725c815f7..b8e9ab8ef8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,4 @@ -1.5: +1.5.0: Fixed #1400 - OperatingSystemRelease should now work on CentOS Changed 'timeout' option to 'limit' to avoid scope issue diff --git a/lib/facter.rb b/lib/facter.rb index 088534933b..36889c8038 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -27,7 +27,7 @@ module Util; end include Comparable include Enumerable - FACTERVERSION = '1.5' + FACTERVERSION = '1.5.0' # = Facter # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. From ded53b044989b1722b4b0b066889c5df8a309047 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Wed, 9 Jul 2008 22:40:33 +1000 Subject: [PATCH 0230/3753] Fixed Rakefile to include additional files including tests et al --- CHANGELOG | 2 ++ Rakefile | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index b8e9ab8ef8..50447eecc9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ 1.5.0: + Fixed Rakefile to include additional files including tests et al + Fixed #1400 - OperatingSystemRelease should now work on CentOS Changed 'timeout' option to 'limit' to avoid scope issue diff --git a/Rakefile b/Rakefile index ba15fd1cfb..619bf471c2 100644 --- a/Rakefile +++ b/Rakefile @@ -18,9 +18,12 @@ project = Rake::RedLabProject.new("facter") do |p| 'install.rb', '[A-Z]*', 'bin/**/*', + 'lib/facter.rb', 'lib/**/*.rb', 'test/**/*.rb', - 'doc/**/*', + 'spec/**/*', + 'conf/**/*', + 'documentation/**/*', 'etc/*' ] From 5e09ea1f66b83abe1f81ef0222a33962d3dcc35e Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Thu, 17 Jul 2008 12:27:54 +0100 Subject: [PATCH 0231/3753] Use rbconfig to detect windows as no uname binary --- lib/facter/kernel.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/facter/kernel.rb b/lib/facter/kernel.rb index 01fe85b33f..5a0eb19b09 100644 --- a/lib/facter/kernel.rb +++ b/lib/facter/kernel.rb @@ -1,3 +1,9 @@ Facter.add(:kernel) do - setcode 'uname -s' + setcode do + require 'rbconfig' + case Config::CONFIG['host_os'] + when /mswin/i then 'windows' + else Facter::Util::Resolution.exec("uname -s") + end + end end From 3ea1905e88176039f12fb96525b6c3dbc4080774 Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Thu, 17 Jul 2008 12:46:14 +0100 Subject: [PATCH 0232/3753] Use ipconfig to determine ip address --- lib/facter/ipaddress.rb | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/lib/facter/ipaddress.rb b/lib/facter/ipaddress.rb index 942c1340d1..c97abe4846 100644 --- a/lib/facter/ipaddress.rb +++ b/lib/facter/ipaddress.rb @@ -149,3 +149,22 @@ ip end end + +Facter.add(:ipaddress) do + confine :kernel => %w{windows} + setcode do + ip = nil + output = %x{ipconfig} + + output.split(/^\S/).each { |str| + if str =~ /IP Address.*: ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + tmp = $1 + unless tmp =~ /127\./ + ip = tmp + break + end + end + } + ip + end +end From 0df872ba1df2646bcfa111f004a530a805b02c74 Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Thu, 17 Jul 2008 13:21:47 +0100 Subject: [PATCH 0233/3753] Get kernel version via wmi --- lib/facter/kernelrelease.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lib/facter/kernelrelease.rb b/lib/facter/kernelrelease.rb index 0f8a64311d..bff7a90fcc 100644 --- a/lib/facter/kernelrelease.rb +++ b/lib/facter/kernelrelease.rb @@ -6,3 +6,18 @@ confine :kernel => :aix setcode 'oslevel -s' end + +Facter.add(:kernelrelease) do + confine :kernel => %{windows} + setcode do + require 'win32ole' + version = "" + connection_string = "winmgmts://./root/cimv2" + wmi = WIN32OLE.connect(connection_string) + wmi.ExecQuery("SELECT Version from Win32_OperatingSystem").each { |ole| + version = "#{ole.Version}" + break + } + version + end +end From dc7363e837ecf95ff2c4a17ddd8c3065e414cf37 Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Thu, 17 Jul 2008 13:31:18 +0100 Subject: [PATCH 0234/3753] Set macaddress on windows platform --- lib/facter/macaddress.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/facter/macaddress.rb b/lib/facter/macaddress.rb index 30d4b5989c..98e38b8af4 100644 --- a/lib/facter/macaddress.rb +++ b/lib/facter/macaddress.rb @@ -63,3 +63,17 @@ ether[0] end end + +Facter.add(:macaddress) do + confine :kernel => %w(windows) + setcode do + ether = [] + output = %x{ipconfig /all} + output.split(/\r\n/).each do |str| + if str =~ /.*Physical Address.*: (\w{1,2}-\w{1,2}-\w{1,2}-\w{1,2}-\w{1,2}-\w{1,2})/ + ether.push($1.gsub(/-/, ":")) + end + end + ether[0] + end +end From 3f180b309b1187d6f5cdb24715b2807c0406ef4d Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Thu, 17 Jul 2008 13:44:10 +0100 Subject: [PATCH 0235/3753] Get DNSDomain from WMI to set domain --- lib/facter/domain.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/facter/domain.rb b/lib/facter/domain.rb index 724dd1ba32..57b04f6b85 100644 --- a/lib/facter/domain.rb +++ b/lib/facter/domain.rb @@ -62,3 +62,17 @@ end end end +Facter.add(:domain) do + confine :kernel => :windows + setcode do + require 'win32ole' + domain = "" + wmi = WIN32OLE.connect("winmgmts://") + query = "select DNSDomain from Win32_NetworkAdapterConfiguration where IPEnabled = True" + wmi.ExecQuery(query).each { |nic| + domain = nic.DNSDomain + break + } + domain + end +end From bb235e349774d29e1a1a8766125f241ddb598f68 Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Thu, 17 Jul 2008 14:54:52 +0100 Subject: [PATCH 0236/3753] Use rbconfig to detect host cpu --- lib/facter/hardwaremodel.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/facter/hardwaremodel.rb b/lib/facter/hardwaremodel.rb index 92f8257743..6201bddd7b 100644 --- a/lib/facter/hardwaremodel.rb +++ b/lib/facter/hardwaremodel.rb @@ -11,3 +11,11 @@ end end end + +Facter.add(:hardwaremodel) do + confine :operatingsystem => :windows + setcode do + require 'rbconfig' + Config::CONFIG['host_cpu'] + end +end From 1eb94d31cc9fa1c2ae36e1000fe91515d95cb935 Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Thu, 17 Jul 2008 18:00:51 +0100 Subject: [PATCH 0237/3753] Bug #1434 Don't execute which on windows --- lib/facter/util/resolution.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index cc8c842bc2..4d0a8d1715 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -6,14 +6,19 @@ require 'facter/util/confine' require 'timeout' +require 'rbconfig' class Facter::Util::Resolution attr_accessor :interpreter, :code, :name, :timeout def self.have_which if ! defined?(@have_which) or @have_which.nil? - %x{which which 2>/dev/null} - @have_which = ($? == 0) + if Config::CONFIG['host_os'] =~ /mswin/ + @have_which = false + else + %x{which which 2>/dev/null} + @have_which = ($? == 0) + end end @have_which end From d999d950a1308522f625baf8eee159fe0677f1bc Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Fri, 18 Jul 2008 07:06:27 +0100 Subject: [PATCH 0238/3753] Don't try and run lsb_release on windows --- lib/facter/lsb.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/facter/lsb.rb b/lib/facter/lsb.rb index 6f6e4bfe2b..2a1ca6eefc 100644 --- a/lib/facter/lsb.rb +++ b/lib/facter/lsb.rb @@ -20,6 +20,7 @@ "LSBDistCodeName" => %r{^Codename:\t(.*)$} }.each do |fact, pattern| Facter.add(fact) do + confine :kernel => :linux setcode do unless defined?(@@lsbdata) and defined?(@@lsbtime) and (Time.now.to_i - @@lsbtime.to_i < 5) type = nil From a12608e90f0aa38a84149459c54a1020e817bc93 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Wed, 30 Jul 2008 22:35:58 +1000 Subject: [PATCH 0239/3753] Fixes #1467 - macaddress not set on Ubuntu --- CHANGELOG | 3 +++ lib/facter/macaddress.rb | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 50447eecc9..de4dee9bc1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,6 @@ +1.5.x: + Fixes #1467 - macaddress not set on Ubuntu + 1.5.0: Fixed Rakefile to include additional files including tests et al diff --git a/lib/facter/macaddress.rb b/lib/facter/macaddress.rb index 98e38b8af4..11dceb00b4 100644 --- a/lib/facter/macaddress.rb +++ b/lib/facter/macaddress.rb @@ -1,5 +1,5 @@ Facter.add(:macaddress) do - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE Debian Gentoo} + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE Debian Gentoo Ubuntu} setcode do ether = [] output = %x{/sbin/ifconfig -a} From 845ae946883756ffa425d319bc804c4d6d724aae Mon Sep 17 00:00:00 2001 From: Nigel Kersten Date: Thu, 31 Jul 2008 08:51:02 -0700 Subject: [PATCH 0240/3753] Feature #1475: CONFIG['bindir'] CONFIG['sbindir'] have undesirable defaults on OS X 10.5 --- install.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/install.rb b/install.rb index 89439d0956..0fc41ef44c 100755 --- a/install.rb +++ b/install.rb @@ -146,6 +146,15 @@ def prepare_installation sd = File.join(sd, version) end end + + # Mac OS X 10.5 declares bindir and sbindir as + # /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin + # /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/sbin + # which is not generally where people expect executables to be installed + if RUBY_PLATFORM == "universal-darwin9.0" + Config::CONFIG['bindir'] = "/usr/bin" + Config::CONFIG['sbindir'] = "/usr/sbin" + end if (destdir = ENV['DESTDIR']) bd = "#{destdir}#{Config::CONFIG['bindir']}" From e1023def1c0a6f68157b585d2c4ee98e2a608490 Mon Sep 17 00:00:00 2001 From: Nigel Kersten Date: Thu, 31 Jul 2008 17:04:45 -0700 Subject: [PATCH 0241/3753] Feature #1478: Allow specification of --bindir --sbindir --sitelibdir --mandir --destdir in install.rb --- install.rb | 316 +++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 221 insertions(+), 95 deletions(-) diff --git a/install.rb b/install.rb index 0fc41ef44c..57ba80c424 100755 --- a/install.rb +++ b/install.rb @@ -30,45 +30,57 @@ # 5) Install all library files ending in .rb from lib/ into Ruby's # site_lib/version directory. # -# $Id: install.rb,v 1.6 2004/08/08 20:33:09 austin Exp $ #++ require 'rbconfig' require 'find' require 'fileutils' +require 'ftools' # apparently on some system ftools doesn't get loaded require 'optparse' require 'ostruct' -InstallOptions = OpenStruct.new - -$loadedrdoc = false - begin require 'rdoc/rdoc' - $loadedrdoc = true -rescue LoadError => detail - $stderr.puts "Could not load rdoc/rdoc: %s" % detail - InstallOptions.rdoc = false + $haverdoc = true +rescue LoadError + puts "Missing rdoc; skipping documentation" + $haverdoc = false +end + +begin + if $haverdoc + rst2man = %x{which rst2man.py} + $haveman = true + else + $haveman = false + end +rescue + puts "Missing rst2man; skipping man page creation" + $haveman = false end +PREREQS = %w{openssl facter xmlrpc/client xmlrpc/server cgi} + +InstallOptions = OpenStruct.new def glob(list) - g = list.map { |i| Dir.glob(i) } - g.flatten! - g.compact! - g.reject! { |e| e =~ /CVS/ } - g + g = list.map { |i| Dir.glob(i) } + g.flatten! + g.compact! + g.reject! { |e| e =~ /\.svn/ } + g end - # Set these values to what you want installed. -#bins = glob(%w{bin/**/*}).reject { |e| e =~ /\.(bat|cmd)$/ } -bins = ["bin/facter"] -rdoc = glob(%w{bin/**/* lib/**/*.rb README CHANGELOG INSTALL}).reject { |e| e=~ /\.(bat|cmd)$/ } -ri = glob(%w(bin/**/* lib/**/*.rb)).reject { |e| e=~ /\.(bat|cmd)$/ } -libs = glob(%w{lib/**/*.rb}) +# Set these values to what you want installed. +sbins = glob(%w{sbin/*}) +bins = glob(%w{bin/*}) +rdoc = glob(%w{bin/* sbin/* lib/**/*.rb README README-library CHANGELOG TODO Install}).reject { |e| e=~ /\.(bat|cmd)$/ } +ri = glob(%w(bin/*.rb sbin/* lib/**/*.rb)).reject { |e| e=~ /\.(bat|cmd)$/ } +man = glob(%w{man/man8/*}) +libs = glob(%w{lib/**/*.rb lib/**/*.py}) tests = glob(%w{tests/**/*.rb}) -def do_bins(bins, target, strip = 'bin/') +def do_bins(bins, target, strip = 's?bin/') bins.each do |bf| obf = bf.gsub(/#{strip}/, '') install_binfile(bf, obf, target) @@ -79,26 +91,64 @@ def do_libs(libs, strip = 'lib/') libs.each do |lf| olf = File.join(InstallOptions.site_dir, lf.gsub(/#{strip}/, '')) op = File.dirname(olf) - #if File.respond_to?(:makedirs) - FileUtils.makedirs(op) - #else - # recmkdir(op) - #end + File.makedirs(op, true) File.chmod(0755, op) - FileUtils.install(lf, olf, :mode => 0755, :verbose => true) + File.install(lf, olf, 0755, true) + end +end + +def do_man(man, strip = 'man/') + man.each do |mf| + omf = File.join(InstallOptions.man_dir, mf.gsub(/#{strip}/, '')) + om = File.dirname(omf) + File.makedirs(om, true) + File.chmod(0644, om) + File.install(mf, omf, 0644, true) + gzip = %x{which gzip} + gzip.chomp! + %x{#{gzip} -f #{omf}} end end +# Verify that all of the prereqs are installed +def check_prereqs + PREREQS.each { |pre| + begin + require pre + rescue LoadError + puts "Could not load %s; cannot install" % pre + exit -1 + end + } +end + ## # Prepare the file installation. # def prepare_installation - InstallOptions.rdoc = true - if RUBY_PLATFORM == "i386-mswin32" - InstallOptions.ri = false + # Only try to do docs if we're sure they have rdoc + if $haverdoc + InstallOptions.rdoc = true + if RUBY_PLATFORM == "i386-mswin32" + InstallOptions.ri = false + else + InstallOptions.ri = true + end else - InstallOptions.ri = true + InstallOptions.rdoc = false + InstallOptions.ri = false + end + + + if $haveman + InstallOptions.man = true + if RUBY_PLATFORM == "i386-mswin32" + InstallOptions.man = false + end + else + InstallOptions.man = false end + InstallOptions.tests = true ARGV.options do |opts| @@ -110,9 +160,27 @@ def prepare_installation opts.on('--[no-]ri', 'Prevents the creation of RI output.', 'Default off on mswin32.') do |onri| InstallOptions.ri = onri end + opts.on('--[no-]man', 'Presents the creation of man pages.', 'Default on.') do |onman| + InstallOptions.man = onman + end opts.on('--[no-]tests', 'Prevents the execution of unit tests.', 'Default on.') do |ontest| InstallOptions.tests = ontest end + opts.on('--destdir[=OPTIONAL]', 'Installation prefix for all targets', 'Default essentially /') do |destdir| + InstallOptions.destdir = destdir + end + opts.on('--bindir[=OPTIONAL]', 'Installation directory for binaries', 'overrides Config::CONFIG["bindir"]') do |bindir| + InstallOptions.bindir = bindir + end + opts.on('--sbindir[=OPTIONAL]', 'Installation directory for system binaries', 'overrides Config::CONFIG["sbindir"]') do |sbindir| + InstallOptions.sbindir = sbindir + end + opts.on('--sitelibdir[=OPTIONAL]', 'Installation directory for libraries', 'overrides Config::CONFIG["sitelibdir"]') do |sitelibdir| + InstallOptions.sitelibdir = sitelibdir + end + opts.on('--mandir[=OPTIONAL]', 'Installation directory for man pages', 'overrides Config::CONFIG["mandir"]') do |mandir| + InstallOptions.mandir = mandir + end opts.on('--quick', 'Performs a quick installation. Only the', 'installation is done.') do |quick| InstallOptions.rdoc = false InstallOptions.ri = false @@ -132,21 +200,11 @@ def prepare_installation opts.parse! end - bds = [".", ENV['TMP'], ENV['TEMP'], "/tmp"] + tmpdirs = [".", ENV['TMP'], ENV['TEMP'], "/tmp", "/var/tmp"] version = [Config::CONFIG["MAJOR"], Config::CONFIG["MINOR"]].join(".") - ld = File.join(Config::CONFIG["libdir"], "ruby", version) - - sd = Config::CONFIG["sitelibdir"] - if sd.nil? - sd = $:.find { |x| x =~ /site_ruby/ } - if sd.nil? - sd = File.join(ld, "site_ruby") - elsif sd !~ Regexp.quote(version) - sd = File.join(sd, version) - end - end - + libdir = File.join(Config::CONFIG["libdir"], "ruby", version) + # Mac OS X 10.5 declares bindir and sbindir as # /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin # /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/sbin @@ -155,66 +213,134 @@ def prepare_installation Config::CONFIG['bindir'] = "/usr/bin" Config::CONFIG['sbindir'] = "/usr/sbin" end + + if not InstallOptions.bindir.nil? + bindir = InstallOptions.bindir + else + bindir = Config::CONFIG['bindir'] + end + + if not InstallOptions.sbindir.nil? + sbindir = InstallOptions.sbindir + else + sbindir = Config::CONFIG['sbindir'] + end + + if not InstallOptions.sitelibdir.nil? + sitelibdir = InstallOptions.sitelibdir + else + sitelibdir = Config::CONFIG["sitelibdir"] + if sitelibdir.nil? + sitelibdir = $:.find { |x| x =~ /site_ruby/ } + if sitelibdir.nil? + sitelibdir = File.join(libdir, "site_ruby") + elsif sitelibdir !~ Regexp.quote(version) + sitelibdir = File.join(sitelibdir, version) + end + end + end + + if not InstallOptions.mandir.nil? + mandir = InstallOptions.mandir + else + mandir = Config::CONFIG['mandir'] + end + # To be deprecated once people move over to using --destdir option if (destdir = ENV['DESTDIR']) - bd = "#{destdir}#{Config::CONFIG['bindir']}" - sd = "#{destdir}#{sd}" - bds << bd - - FileUtils.makedirs(bd) - FileUtils.makedirs(sd) - else - bd = Config::CONFIG['bindir'] - bds << Config::CONFIG['bindir'] + bindir = "#{destdir}#{bindir}" + sbindir = "#{destdir}#{sbindir}" + mandir = "#{destdir}#{mandir}" + sitelibdir = "#{destdir}#{sitelibdir}" + + FileUtils.makedirs(bindir) + FileUtils.makedirs(sbindir) + FileUtils.makedirs(mandir) + FileUtils.makedirs(sitelibdir) + # This is the new way forward + elsif (destdir = InstallOptions.destdir) + bindir = "#{destdir}#{bindir}" + sbindir = "#{destdir}#{sbindir}" + mandir = "#{destdir}#{mandir}" + sitelibdir = "#{destdir}#{sitelibdir}" + + FileUtils.makedirs(bindir) + FileUtils.makedirs(sbindir) + FileUtils.makedirs(mandir) + FileUtils.makedirs(sitelibdir) end - InstallOptions.bin_dirs = bds.compact - InstallOptions.site_dir = sd - InstallOptions.bin_dir = bd - InstallOptions.lib_dir = ld + tmpdirs << bindir - unless $loadedrdoc - InstallOptions.rdoc = false - InstallOptions.ri = false - end + InstallOptions.tmp_dirs = tmpdirs.compact + InstallOptions.site_dir = sitelibdir + InstallOptions.bin_dir = bindir + InstallOptions.sbin_dir = sbindir + InstallOptions.lib_dir = libdir + InstallOptions.man_dir = mandir end ## # Build the rdoc documentation. Also, try to build the RI documentation. # def build_rdoc(files) - r = RDoc::RDoc.new - r.document(["--main", "README", "--title", "Facter -- A Fact Collecter", - "--line-numbers"] + files) - -rescue RDoc::RDocError => e - $stderr.puts e.message -rescue Exception => e - $stderr.puts "Couldn't build RDoc documentation\n#{e.message}" + return unless $haverdoc + begin + r = RDoc::RDoc.new + r.document(["--main", "README", "--title", + "Puppet -- Site Configuration Management", "--line-numbers"] + files) + rescue RDoc::RDocError => e + $stderr.puts e.message + rescue Exception => e + $stderr.puts "Couldn't build RDoc documentation\n#{e.message}" + end end def build_ri(files) - ri = RDoc::RDoc.new - ri.document(["--ri-site", "--merge"] + files) -rescue RDoc::RDocError => e - $stderr.puts e.message -rescue Exception => e - $stderr.puts e.class - $stderr.puts "Couldn't build Ri documentation\n#{e.message}" + return unless $haverdoc + begin + ri = RDoc::RDoc.new + #ri.document(["--ri-site", "--merge"] + files) + ri.document(["--ri-site"] + files) + rescue RDoc::RDocError => e + $stderr.puts e.message + rescue Exception => e + $stderr.puts "Couldn't build Ri documentation\n#{e.message}" + $stderr.puts "Continuing with install..." + end +end + +def build_man(bins) + return unless $haveman + begin + # Locate rst2man + rst2man = %x{which rst2man.py} + rst2man.chomp! + # Create puppet.conf.8 man page + %x{bin/puppetdoc --reference configuration > ./puppet.conf.rst} + %x{#{rst2man} ./puppet.conf.rst ./man/man8/puppet.conf.8} + File.unlink("./puppet.conf.rst") + + # Create binary man pages + bins.each do |bin| + b = bin.gsub( "bin/", "") + %x{#{bin} --help > ./#{b}.rst} + %x{#{rst2man} ./#{b}.rst ./man/man8/#{b}.8} + File.unlink("./#{b}.rst") + end + rescue SystemCallError + $stderr.puts "Couldn't build man pages: " + $! + $stderr.puts "Continuing with install..." + end end def run_tests(test_list) begin require 'test/unit/ui/console/testrunner' - require 'test/unit' - - unless defined? Test::Unit::TestCase - raise LoadError, "Could not find unit test library" - end $:.unshift "lib" test_list.each do |test| - next if File.directory?(test) - require test + next if File.directory?(test) + require test end tests = [] @@ -236,8 +362,8 @@ def run_tests(test_list) # windows, we add an '.rb' extension and let file associations do their stuff. def install_binfile(from, op_file, target) tmp_dir = nil - InstallOptions.bin_dirs.each do |t| - if File.directory?(t) and File.writable_real?(t) + InstallOptions.tmp_dirs.each do |t| + if File.directory?(t) and File.writable?(t) tmp_dir = t break end @@ -251,12 +377,15 @@ def install_binfile(from, op_file, target) File.open(tmp_file, "w") do |op| ruby = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name']) op.puts "#!#{ruby}" - op.write ip.read + contents = ip.readlines + if contents[0] =~ /^#!/ + contents.shift + end + op.write contents.join() end end - # We don't want bat files on darwin - if Config::CONFIG["target_os"] =~ /win/io and Config::CONFIG["target_os"] !~ /darwin/ + if Config::CONFIG["target_os"] =~ /win/io and Config::CONFIG["target_os"] !~ /darwin/io installed_wrapper = false if File.exists?("#{from}.bat") @@ -296,17 +425,14 @@ def install_binfile(from, op_file, target) :done EOS +check_prereqs prepare_installation run_tests(tests) if InstallOptions.tests #build_rdoc(rdoc) if InstallOptions.rdoc #build_ri(ri) if InstallOptions.ri -bd = nil -#if (destdir = ENV['DESTDIR']) -# bd = "#{destdir}#{Config::CONFIG['bindir']}" -#else -# bd = "#{Config::CONFIG['bindir']}" -#end - +#build_man(bins) if InstallOptions.man +do_bins(sbins, InstallOptions.sbin_dir) do_bins(bins, InstallOptions.bin_dir) do_libs(libs) +do_man(man) From 9b4218292e06ea4d0b4c07b272653dcf52a24142 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Fri, 1 Aug 2008 09:23:25 -0500 Subject: [PATCH 0242/3753] Modified the operatingsystem fact for Debian so it looks in /etc/debian_version instead of /proc/version. Applied patch by marthag. Signed-off-by: Luke Kanies --- CHANGELOG | 3 +++ lib/facter/operatingsystemrelease.rb | 5 +---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index de4dee9bc1..c52dccf1f0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,7 @@ 1.5.x: + Modified the operatingsystem fact for Debian so it looks in + /etc/debian_version instead of /proc/version. + Fixes #1467 - macaddress not set on Ubuntu 1.5.0: diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index 843dfdb65c..7aeeb1e021 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -41,10 +41,7 @@ Facter.add(:operatingsystemrelease) do confine :operatingsystem => %w{Debian} setcode do - release = Facter::Util::Resolution.exec('cat /proc/version') - if release =~ /\(Debian (\d+.\d+).\d+-\d+\)/ - $1 - end + release = Facter::Util::Resolution.exec('cat /etc/debian_version') end end From ff45c8639dae54e48890ab780653d4f039b02afc Mon Sep 17 00:00:00 2001 From: Nigel Kersten Date: Fri, 1 Aug 2008 20:45:25 -0700 Subject: [PATCH 0243/3753] Feature #1487: Package creation scripts for Mac OS X --- conf/osx/PackageInfo.plist | 36 ++++++++ conf/osx/createpackage.sh | 167 +++++++++++++++++++++++++++++++++++++ conf/osx/preflight | 11 +++ 3 files changed, 214 insertions(+) create mode 100644 conf/osx/PackageInfo.plist create mode 100755 conf/osx/createpackage.sh create mode 100755 conf/osx/preflight diff --git a/conf/osx/PackageInfo.plist b/conf/osx/PackageInfo.plist new file mode 100644 index 0000000000..84b44222f0 --- /dev/null +++ b/conf/osx/PackageInfo.plist @@ -0,0 +1,36 @@ + + + + + CFBundleIdentifier + com.reductivelabs.facter + CFBundleShortVersionString + {SHORTVERSION} + IFMajorVersion + {MAJORVERSION} + IFMinorVersion + {MINORVERSION} + IFPkgFlagAllowBackRev + + IFPkgFlagAuthorizationAction + RootAuthorization + IFPkgFlagDefaultLocation + / + IFPkgFlagFollowLinks + + IFPkgFlagInstallFat + + IFPkgFlagIsRequired + + IFPkgFlagOverwritePermissions + + IFPkgFlagRelocatable + + IFPkgFlagRestartAction + None + IFPkgFlagRootVolumeOnly + + IFPkgFlagUpdateInstalledLanguages + + + diff --git a/conf/osx/createpackage.sh b/conf/osx/createpackage.sh new file mode 100755 index 0000000000..747dda9a65 --- /dev/null +++ b/conf/osx/createpackage.sh @@ -0,0 +1,167 @@ +#!/bin/bash +# +# Script to build an "old style" not flat pkg out of the facter repository. +# +# Author: Nigel Kersten (nigelk@google.com) +# +# Last Updated: 2008-07-31 +# +# Copyright 2008 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License + + +INSTALLRB="install.rb" +BINDIR="/usr/bin" +SITELIBDIR="/usr/lib/ruby/site_ruby/1.8" +PACKAGEMAKER="/Developer/usr/bin/packagemaker" +PROTO_PLIST="PackageInfo.plist" +PREFLIGHT="preflight" + + +function find_installer() { + # we walk up three directories to make this executable from the root, + # root/conf or root/conf/osx + if [ -f "./${INSTALLRB}" ]; then + installer="$(pwd)/${INSTALLRB}" + elif [ -f "../${INSTALLRB}" ]; then + installer="$(pwd)/../${INSTALLRB}" + elif [ -f "../../${INSTALLRB}" ]; then + installer="$(pwd)/../${INSTALLRB}" + else + installer="" + fi +} + +function find_facter_root() { + facter_root=$(dirname "${installer}") +} + +function install_facter() { + echo "Installing Facter to ${pkgroot}" + "${installer}" --destdir="${pkgroot}" --bindir="${BINDIR}" --sitelibdir="${SITELIBDIR}" &> /dev/null + chown -R root:admin "${pkgroot}" +} + +function get_facter_version() { + facter_version=$(RUBYLIB="${pkgroot}/${SITELIBDIR}:${RUBYLIB}" ruby -e "require 'facter'; puts Facter.version") +} + +function prepare_package() { + # As we can't specify to follow symlinks from the command line, we have + # to go through the hassle of creating an Info.plist file for packagemaker + # to look at for package creation and substitue the version strings out. + # Major/Minor versions can only be integers, so we have "0" and "245" for + # puppet version 0.24.5 + # Note too that for 10.5 compatibility this Info.plist *must* be set to + # follow symlinks. + VER1=$(echo ${facter_version} | awk -F "." '{print $1}') + VER2=$(echo ${facter_version} | awk -F "." '{print $2}') + VER3=$(echo ${facter_version} | awk -F "." '{print $3}') + major_version="${VER1}" + minor_version="${VER2}${VER3}" + cp "${facter_root}/conf/osx/${PROTO_PLIST}" "${pkgtemp}" + sed -i '' "s/{SHORTVERSION}/${facter_version}/g" "${pkgtemp}/${PROTO_PLIST}" + sed -i '' "s/{MAJORVERSION}/${major_version}/g" "${pkgtemp}/${PROTO_PLIST}" + sed -i '' "s/{MINORVERSION}/${minor_version}/g" "${pkgtemp}/${PROTO_PLIST}" + + # We need to create a preflight script to remove traces of previous + # puppet installs due to limitations in Apple's pkg format. + mkdir "${pkgtemp}/scripts" + cp "${facter_root}/conf/osx/${PREFLIGHT}" "${pkgtemp}/scripts" + + # substitute in the sitelibdir specified above on the assumption that this + # is where any previous puppet install exists that should be cleaned out. + sed -i '' "s|{SITELIBDIR}|${SITELIBDIR}|g" "${pkgtemp}/scripts/${PREFLIGHT}" + chmod 0755 "${pkgtemp}/scripts/${PREFLIGHT}" +} + +function create_package() { + rm -fr "$(pwd)/facter-${puppet_version}.pkg" + echo "Building package" + echo "Note that packagemaker is reknowned for spurious errors. Don't panic." + "${PACKAGEMAKER}" --root "${pkgroot}" \ + --info "${pkgtemp}/${PROTO_PLIST}" \ + --scripts ${pkgtemp}/scripts \ + --out "$(pwd)/facter-${facter_version}.pkg" + if [ $? -ne 0 ]; then + echo "There was a problem building the package." + cleanup_and_exit 1 + exit 1 + else + echo "The package has been built at:" + echo "$(pwd)/facter-${facter_version}.pkg" + fi +} + +function cleanup_and_exit() { + if [ -d "${pkgroot}" ]; then + rm -fr "${pkgroot}" + fi + if [ -d "${pkgtemp}" ]; then + rm -fr "${pkgtemp}" + fi + exit $1 +} + +# Program entry point +function main() { + + if [ $(whoami) != "root" ]; then + echo "This script needs to be run as root via su or sudo." + cleanup_and_exit 1 + fi + + find_installer + + if [ ! "${installer}" ]; then + echo "Unable to find ${INSTALLRB}" + cleanup_and_exit 1 + fi + + find_facter_root + + if [ ! "${facter_root}" ]; then + echo "Unable to find facter repository root." + cleanup_and_exit 1 + fi + + pkgroot=$(mktemp -d -t facterpkg) + + if [ ! "${pkgroot}" ]; then + echo "Unable to create temporary package root." + cleanup_and_exit 1 + fi + + pkgtemp=$(mktemp -d -t factertmp) + + if [ ! "${pkgtemp}" ]; then + echo "Unable to create temporary package root." + cleanup_and_exit 1 + fi + + install_facter + get_facter_version + + if [ ! "${facter_version}" ]; then + echo "Unable to retrieve facter version" + cleanup_and_exit 1 + fi + + prepare_package + create_package + + cleanup_and_exit 0 +} + +main "$@" diff --git a/conf/osx/preflight b/conf/osx/preflight new file mode 100755 index 0000000000..400a2616f7 --- /dev/null +++ b/conf/osx/preflight @@ -0,0 +1,11 @@ +#!/bin/bash +# +# Make sure that old facter cruft is removed +# This also allows us to downgrade facter as +# it's more likely that installing old versions +# over new will cause issues. +# +# ${3} is the destination volume so that this works correctly +# when being installed to volumes other than the current OS. + +rm -Rf "${3}{SITELIBDIR}/facter*" From 91ca4abba2fdf100c83790408d4fefa38dddc6f9 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Mon, 4 Aug 2008 07:53:36 +1000 Subject: [PATCH 0244/3753] Fixed #1490 - Added virtual fact --- CHANGELOG | 2 ++ lib/facter/virtual.rb | 62 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 lib/facter/virtual.rb diff --git a/CHANGELOG b/CHANGELOG index c52dccf1f0..5e290f1064 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ 1.5.x: + Added virtual fact + Modified the operatingsystem fact for Debian so it looks in /etc/debian_version instead of /proc/version. diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb new file mode 100644 index 0000000000..d84397d148 --- /dev/null +++ b/lib/facter/virtual.rb @@ -0,0 +1,62 @@ +Facter.add("virtual") do + confine :kernel => %w{Linux FreeBSD OpenBSD} + + ENV["PATH"]="/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/sbin:/usr/local/bin" + + result = "physical" + + setcode do + + if FileTest.exists?("/proc/user_beancounters") + result = "openvz" + end + + if FileTest.exists?("/proc/sys/xen/independent_wallclock") + result = "xenu" + elsif FileTest.exists?("/proc/xen/capabilities") + txt = File.read("/proc/xen/capabilities") + if txt =~ /control_d/i + result = "xen0" + end + end + + if result == "physical" + lspciexists = system "which lspci > /dev/null 2>&1" + if $?.exitstatus == 0 + output = %x{lspci} + output.each {|p| + # --- look for the vmware video card to determine if it is virtual => vmware. + # --- 00:0f.0 VGA compatible controller: VMware Inc [VMware SVGA II] PCI Display Adapter + result = "vmware" if p =~ /VM[wW]are/ + } + else + dmidecodeexists = system "which dmidecode > /dev/null 2>&1" + if $?.exitstatus == 0 + outputd = %x{dmidecode} + outputd.each {|pd| + result = "vmware" if pd =~ /VMware|Parallels/ + } + end + end + end + + # VMware server 1.0.3 rpm places vmware-vmx in this place, other versions or platforms may not. + if FileTest.exists?("/usr/lib/vmware/bin/vmware-vmx") + result = "vmware_server" + end + + mountexists = system "which mount > /dev/null 2>&1" + if $?.exitstatus == 0 + output = %x{mount} + output.each {|p| + result = "vserver" if p =~ /\/dev\/hdv1/ + } + end + + if FileTest.directory?('/proc/virtual') + result = "vserver_host" + end + + result + end +end From df8fc8c7929031fc06fe70728e4b2f925645f34a Mon Sep 17 00:00:00 2001 From: Nigel Kersten Date: Tue, 5 Aug 2008 13:46:35 -0700 Subject: [PATCH 0245/3753] fix terrible error with overwriting permissions --- conf/osx/PackageInfo.plist | 2 +- conf/osx/createpackage.sh | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/conf/osx/PackageInfo.plist b/conf/osx/PackageInfo.plist index 84b44222f0..38b76d86d3 100644 --- a/conf/osx/PackageInfo.plist +++ b/conf/osx/PackageInfo.plist @@ -23,7 +23,7 @@ IFPkgFlagIsRequired IFPkgFlagOverwritePermissions - + IFPkgFlagRelocatable IFPkgFlagRestartAction diff --git a/conf/osx/createpackage.sh b/conf/osx/createpackage.sh index 747dda9a65..d8c2c5291a 100755 --- a/conf/osx/createpackage.sh +++ b/conf/osx/createpackage.sh @@ -61,8 +61,8 @@ function prepare_package() { # As we can't specify to follow symlinks from the command line, we have # to go through the hassle of creating an Info.plist file for packagemaker # to look at for package creation and substitue the version strings out. - # Major/Minor versions can only be integers, so we have "0" and "245" for - # puppet version 0.24.5 + # Major/Minor versions can only be integers, so we have "1" and "50" for + # facter version 1.5 # Note too that for 10.5 compatibility this Info.plist *must* be set to # follow symlinks. VER1=$(echo ${facter_version} | awk -F "." '{print $1}') @@ -76,18 +76,18 @@ function prepare_package() { sed -i '' "s/{MINORVERSION}/${minor_version}/g" "${pkgtemp}/${PROTO_PLIST}" # We need to create a preflight script to remove traces of previous - # puppet installs due to limitations in Apple's pkg format. + # facter installs due to limitations in Apple's pkg format. mkdir "${pkgtemp}/scripts" cp "${facter_root}/conf/osx/${PREFLIGHT}" "${pkgtemp}/scripts" # substitute in the sitelibdir specified above on the assumption that this - # is where any previous puppet install exists that should be cleaned out. + # is where any previous facter install exists that should be cleaned out. sed -i '' "s|{SITELIBDIR}|${SITELIBDIR}|g" "${pkgtemp}/scripts/${PREFLIGHT}" chmod 0755 "${pkgtemp}/scripts/${PREFLIGHT}" } function create_package() { - rm -fr "$(pwd)/facter-${puppet_version}.pkg" + rm -fr "$(pwd)/facter-${facter_version}.pkg" echo "Building package" echo "Note that packagemaker is reknowned for spurious errors. Don't panic." "${PACKAGEMAKER}" --root "${pkgroot}" \ From b91ee5ecfbd55b24083c2c003a4331d252856bf4 Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Wed, 6 Aug 2008 18:46:12 +0100 Subject: [PATCH 0246/3753] Remove duplicated code paths Signed-off-by: Paul Nasrat --- lib/facter/ipmess.rb | 43 +++++++------------------------------------ lib/facter/util/ip.rb | 40 ++++------------------------------------ 2 files changed, 11 insertions(+), 72 deletions(-) diff --git a/lib/facter/ipmess.rb b/lib/facter/ipmess.rb index 215d557758..ce6e420a7a 100644 --- a/lib/facter/ipmess.rb +++ b/lib/facter/ipmess.rb @@ -15,61 +15,32 @@ end case Facter.value(:kernel) - when 'SunOS', 'Linux' + when 'SunOS', 'Linux', 'OpenBSD', 'NetBSD', 'FreeBSD' Facter::IPAddress.get_interfaces.each do |interface| mi = interface.gsub(':', '_') Facter.add("ipaddress_" + mi) do - confine :kernel => [ :sunos, :linux ] + confine :kernel => [ :sunos, :freebsd, :openbsd, :netbsd, :linux ] setcode do label = 'ipaddress' - Facter::IPAddress.get_interface_value_nonbsd(interface, label) + Facter::IPAddress.get_interface_value(interface, label) end end Facter.add("macaddress_" + mi) do - confine :kernel => [ :sunos, :linux ] + confine :kernel => [ :sunos, :freebsd, :openbsd, :netbsd, :linux ] setcode do label = 'macaddress' - Facter::IPAddress.get_interface_value_nonbsd(interface, label) + Facter::IPAddress.get_interface_value(interface, label) end end Facter.add("netmask_" + mi) do - confine :kernel => [ :sunos, :linux ] + confine :kernel => [ :sunos, :freebsd, :openbsd, :netbsd, :linux ] setcode do label = 'netmask' - Facter::IPAddress.get_interface_value_nonbsd(interface, label) + Facter::IPAddress.get_interface_value(interface, label) end end end - - when 'OpenBSD', 'NetBSD', 'FreeBSD' - Facter::IPAddress.get_interfaces.each do |interface| - mi = interface.gsub(':', '_') - - Facter.add("ipaddress_" + mi) do - confine :kernel => [ :openbsd, :freebsd, :netbsd ] - setcode do - label = 'ipaddress' - Facter::IPAddress.get_interface_value_bsd(interface, label) - end - end - - Facter.add("netmask_" + mi) do - confine :kernel => [ :openbsd, :freebsd, :netbsd ] - setcode do - label = 'netmask' - Facter::IPAddress.get_interface_value_bsd(interface, label) - end - end - - Facter.add("macaddress_" + mi) do - confine :kernel => [ :openbsd, :freebsd, :netbsd ] - setcode do - label = 'macaddress' - Facter::IPAddress.get_interface_value_bsd(interface, label) - end - end - end end diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index 1a0c61154d..e0775a4c2d 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -21,9 +21,9 @@ def self.get_interfaces end - def self.get_interface_value_nonbsd(interface, label) + def self.get_interface_value(interface, label) - tmp1 = nil + tmp1 = [] case Facter.value(:kernel) when 'Linux' @@ -54,45 +54,13 @@ def self.get_interface_value_nonbsd(interface, label) if interface != "lo" && interface != "lo0" output_int.each { |s| - tmp1 = $1 if s =~ regex + tmp1.push($1) if s =~ regex } end if tmp1 - value = tmp1 + value = tmp1.shift end end - - def self.get_interface_value_bsd(interface, label) - - tmp1 = [] - - int_hash = {} - output_int = %x{/sbin/ifconfig #{interface}} - addr = /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ - mac = /(?:ether|lladdr)\s+(\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ - mask = /netmask\s+(\w{10})/ - - case label - when 'ipaddress' - regex = addr - when 'macaddress' - regex = mac - when 'netmask' - regex = mask - end - - if interface != "lo" && interface != "lo0" - output_int.each { |s| - tmp1.push($1) if s =~ regex - } - end - - if tmp1 - value = tmp1.shift - end - - end end - From d8b708b9d8e9685f1395042cb3ff5507856d2ac7 Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Fri, 8 Aug 2008 15:07:13 +0100 Subject: [PATCH 0247/3753] Fix ticket 1425 on Solaris --- lib/facter/util/ip.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index e0775a4c2d..722879c70b 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -54,7 +54,13 @@ def self.get_interface_value(interface, label) if interface != "lo" && interface != "lo0" output_int.each { |s| - tmp1.push($1) if s =~ regex + if s =~ regex + value = $1 + if label == 'netmask' && Facter.value(:kernel) == "SunOS" + value = value.scan(/../).collect do |byte| byte.to_i(16) end.join('.') + end + tmp1.push(value) + end } end From 590a3d003db2f2496a37d021a45e8d7a2c6d9583 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sat, 9 Aug 2008 07:31:31 +1000 Subject: [PATCH 0248/3753] Fixes #1492 - added kernelversion fact --- CHANGELOG | 2 ++ lib/facter/kernelversion.rb | 5 +++++ 2 files changed, 7 insertions(+) create mode 100644 lib/facter/kernelversion.rb diff --git a/CHANGELOG b/CHANGELOG index 5e290f1064..60f70b7be6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ 1.5.x: + Fixes #1492 - added kernelversion fact + Added virtual fact Modified the operatingsystem fact for Debian so it looks in diff --git a/lib/facter/kernelversion.rb b/lib/facter/kernelversion.rb new file mode 100644 index 0000000000..7d2b31d53c --- /dev/null +++ b/lib/facter/kernelversion.rb @@ -0,0 +1,5 @@ +Facter.add("kernelversion") do + setcode do + Facter['kernelrelease'].value.split('-')[0] + end +end From b33d8c6461d4dd52d1fe03be47c8aad46934e7e3 Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Mon, 11 Aug 2008 12:20:36 +0100 Subject: [PATCH 0249/3753] Add module level tests for Facter::IPAddress --- spec/unit/util/ip.rb | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 spec/unit/util/ip.rb diff --git a/spec/unit/util/ip.rb b/spec/unit/util/ip.rb new file mode 100644 index 0000000000..31d948273f --- /dev/null +++ b/spec/unit/util/ip.rb @@ -0,0 +1,18 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'facter/util/ip' + +describe Facter::IPAddress do + + it "should return a list of interfaces" do + Facter::IPAddress.should respond_to(:get_interfaces) + end + + it "should return a value for a specific interface" do + Facter::IPAddress.should respond_to(:get_interface_value) + end + +end + From 2546c5343572c1eeca7f9b6a071971706d1b57c5 Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Mon, 11 Aug 2008 13:44:23 +0100 Subject: [PATCH 0250/3753] Add sample test and strawman solution for IP parsing code --- lib/facter/util/ip.rb | 22 ++++++++++++++-------- spec/unit/util/ip.rb | 30 ++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index 722879c70b..ac25697481 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -4,13 +4,8 @@ def self.get_interfaces int = nil - case Facter.value(:kernel) - when 'Linux', 'OpenBSD', 'NetBSD', 'FreeBSD' - output = %x{/sbin/ifconfig -a} - when 'SunOS' - output = %x{/usr/sbin/ifconfig -a} - end - + output = Facter::IPAddress.get_all_interface_output() + # We get lots of warnings on platforms that don't get an output # made. if output @@ -20,7 +15,18 @@ def self.get_interfaces end end - + + def self.get_all_interface_output + case Facter.value(:kernel) + when 'Linux', 'OpenBSD', 'NetBSD', 'FreeBSD' + output = %x{/sbin/ifconfig -a} + when 'SunOS' + output = %x{/usr/sbin/ifconfig -a} + end + output + end + + def self.get_interface_value(interface, label) tmp1 = [] diff --git a/spec/unit/util/ip.rb b/spec/unit/util/ip.rb index 31d948273f..b517b4e78d 100644 --- a/spec/unit/util/ip.rb +++ b/spec/unit/util/ip.rb @@ -10,6 +10,36 @@ Facter::IPAddress.should respond_to(:get_interfaces) end + it "should return an empty list of interfaces on an unknown kernel" do + Facter.stubs(:value).returns("UnknownKernel") + assert_equal [], Facter::IPAddress.get_interfaces() + end + + it "should return a list with a single interface on Linux with a single interface" do + linux_ifconfig = < Date: Wed, 13 Aug 2008 15:55:49 +0100 Subject: [PATCH 0251/3753] Extract ifconfig output to data directory --- .../linux_ifconfig_all_with_single_interface | 18 +++++++++++++ spec/unit/util/ip.rb | 26 +++---------------- 2 files changed, 22 insertions(+), 22 deletions(-) create mode 100644 spec/unit/data/linux_ifconfig_all_with_single_interface diff --git a/spec/unit/data/linux_ifconfig_all_with_single_interface b/spec/unit/data/linux_ifconfig_all_with_single_interface new file mode 100644 index 0000000000..e063b77e78 --- /dev/null +++ b/spec/unit/data/linux_ifconfig_all_with_single_interface @@ -0,0 +1,18 @@ +eth0 Link encap:Ethernet HWaddr 00:0c:29:52:15:e9 + inet addr:172.16.15.133 Bcast:172.16.15.255 Mask:255.255.255.0 + inet6 addr: fe80::20c:29ff:fe52:15e9/64 Scope:Link + UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 + RX packets:173 errors:173 dropped:0 overruns:0 frame:0 + TX packets:208 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:1000 + RX bytes:40970 (40.0 KB) TX bytes:24760 (24.1 KB) + Interrupt:16 Base address:0x2024 + +lo Link encap:Local Loopback + inet addr:127.0.0.1 Mask:255.0.0.0 + inet6 addr: ::1/128 Scope:Host + UP LOOPBACK RUNNING MTU:16436 Metric:1 + RX packets:1630 errors:0 dropped:0 overruns:0 frame:0 + TX packets:1630 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:0 + RX bytes:81500 (79.5 KB) TX bytes:81500 (79.5 KB) \ No newline at end of file diff --git a/spec/unit/util/ip.rb b/spec/unit/util/ip.rb index b517b4e78d..47ec951089 100644 --- a/spec/unit/util/ip.rb +++ b/spec/unit/util/ip.rb @@ -12,32 +12,14 @@ it "should return an empty list of interfaces on an unknown kernel" do Facter.stubs(:value).returns("UnknownKernel") - assert_equal [], Facter::IPAddress.get_interfaces() + Facter::IPAddress.get_interfaces().should == [] end it "should return a list with a single interface on Linux with a single interface" do - linux_ifconfig = < Date: Wed, 13 Aug 2008 17:25:30 +0100 Subject: [PATCH 0252/3753] Add unit rspec tests for ticket 1425 --- lib/facter/util/ip.rb | 16 +++++++++++++--- spec/unit/data/solaris_ifconfig_single_interface | 3 +++ spec/unit/util/ip.rb | 10 ++++++++++ 3 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 spec/unit/data/solaris_ifconfig_single_interface diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index ac25697481..f6efdc27ea 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -26,6 +26,17 @@ def self.get_all_interface_output output end + def self.get_single_interface_output(interface) + output = "" + case Facter.value(:kernel) + when 'Linux', 'OpenBSD', 'NetBSD', 'FreeBSD' + output = %x{/sbin/ifconfig #{interface}} + when 'SunOS' + output = %x{/usr/sbin/ifconfig #{interface}} + end + output + end + def self.get_interface_value(interface, label) @@ -33,17 +44,14 @@ def self.get_interface_value(interface, label) case Facter.value(:kernel) when 'Linux' - output_int = %x{/sbin/ifconfig #{interface}} addr = /inet addr:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ mac = /(?:ether|HWaddr)\s+(\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/ mask = /Mask:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ when 'OpenBSD', 'NetBSD', 'FreeBSD' - output_int = %x{/sbin/ifconfig #{interface}} addr = /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ mac = /(?:ether|lladdr)\s+(\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ mask = /netmask\s+(\w{10})/ when 'SunOS' - output_int = %x{/usr/sbin/ifconfig #{interface}} addr = /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ mac = /(?:ether|lladdr)\s+(\w?\w:\w?\w:\w?\w:\w?\w:\w?\w:\w?\w)/ mask = /netmask\s+(\w{8})/ @@ -57,6 +65,8 @@ def self.get_interface_value(interface, label) when 'netmask' regex = mask end + + output_int = get_single_interface_output(interface) if interface != "lo" && interface != "lo0" output_int.each { |s| diff --git a/spec/unit/data/solaris_ifconfig_single_interface b/spec/unit/data/solaris_ifconfig_single_interface new file mode 100644 index 0000000000..7a227aa5f9 --- /dev/null +++ b/spec/unit/data/solaris_ifconfig_single_interface @@ -0,0 +1,3 @@ +e1000g0: flags=201004843 mtu 1500 index 2 + inet 172.16.15.138 netmask ffffff00 broadcast 172.16.15.255 + ether 0:c:29:c1:70:2a \ No newline at end of file diff --git a/spec/unit/util/ip.rb b/spec/unit/util/ip.rb index 47ec951089..e27531e405 100644 --- a/spec/unit/util/ip.rb +++ b/spec/unit/util/ip.rb @@ -26,5 +26,15 @@ Facter::IPAddress.should respond_to(:get_interface_value) end + it "should return a human readable netmask on Solaris" do + sample_output_file = File.dirname(__FILE__) + "/../data/solaris_ifconfig_single_interface" + solaris_ifconfig_interface = File.new(sample_output_file).read() + + Facter::IPAddress.expects(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("SunOS") + + Facter::IPAddress.get_interface_value("e1000g0", "netmask").should == "255.255.255.0" + end + end From ca93b81933a64b221377562a100bfbda91368295 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Sun, 17 Aug 2008 11:36:58 -0500 Subject: [PATCH 0253/3753] Adding better SuSE detection for both operatingsystem and release. Patch provided by seanmil. Signed-off-by: Luke Kanies --- lib/facter/operatingsystem.rb | 7 ++++++- lib/facter/operatingsystemrelease.rb | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index d43da2dc28..51e6248a34 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -26,7 +26,12 @@ "RedHat" end elsif FileTest.exists?("/etc/SuSE-release") - "SuSE" + txt = File.read("/etc/SuSE-release") + if txt =~ /^SUSE LINUX Enterprise Server/i + "SLES" + else + "SuSE" + end end end end diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index 7aeeb1e021..f52d5e41bc 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -55,6 +55,24 @@ end end +Facter.add(:operatingsystemrelease) do + confine :operatingsystem => %w{SLES} + setcode do + releasefile = Facter::Util::Resolution.exec('cat /etc/SuSE-release') + if releasefile =~ /^VERSION\s*=\s*(\d+)/ + releasemajor = $1 + if releasefile =~ /^PATCHLEVEL\s*=\s*(\d+)/ + releaseminor = $1 + else + releaseminor = 0 + end + releasemajor + "." + releaseminor + else + "unknown" + end + end +end + Facter.add(:operatingsystemrelease) do setcode do Facter[:kernelrelease].value end end From 422dd116ecadd2aef341cf431b87d1e9daca45cd Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Mon, 18 Aug 2008 12:12:25 +0100 Subject: [PATCH 0254/3753] Facter fix #1422, no default timeout --- lib/facter/util/resolution.rb | 2 +- spec/unit/util/resolution.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 4d0a8d1715..19f2c1e3e3 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -67,7 +67,7 @@ def initialize(name) @name = name @confines = [] @value = nil - @timeout = 0.5 + @timeout = 0 end # Return the number of confines. diff --git a/spec/unit/util/resolution.rb b/spec/unit/util/resolution.rb index 617b4dcaf7..5ce3d6329d 100755 --- a/spec/unit/util/resolution.rb +++ b/spec/unit/util/resolution.rb @@ -21,8 +21,8 @@ Facter::Util::Resolution.new("yay").should respond_to(:timeout=) end - it "should default to a timeout of 0.5 seconds" do - Facter::Util::Resolution.new("yay").limit.should == 0.5 + it "should default to a timeout of 0 seconds" do + Facter::Util::Resolution.new("yay").limit.should == 0 end it "should provide a 'limit' method that returns the timeout" do From 095eb15e5e202983735dc407b68982cbc20e2d5d Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Mon, 18 Aug 2008 18:40:32 -0500 Subject: [PATCH 0255/3753] Applied patch by josb to fix CentOS version detection. Signed-off-by: Luke Kanies --- lib/facter/operatingsystemrelease.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index f52d5e41bc..19d5869b0f 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -29,11 +29,11 @@ Facter.add(:operatingsystemrelease) do confine :operatingsystem => %w{CentOS} setcode do - centosrelease = Facter::Util::Resolution.exec('cat /etc/redhat-release | sed -e \'s/CentOS release//g\' -e \'s/(Final)//g\'') - if centosrelease =~ /^5^/ + centos_release = Facter::Util::Resolution.exec("sed -r -e 's/CentOS release //' -e 's/ \((Branch|Final)\)//' /etc/redhat-release") + if centos_release =~ /5/ release = Facter::Util::Resolution.exec('rpm -q --qf \'%{VERSION}.%{RELEASE}\' centos-release | cut -d. -f1,2') else - release = Facter::Util::Resolution.exec('cat /etc/redhat-release | sed -e \'s/CentOS release//g\' -e \'s/(Final)//g\'') + release = centos_release end end end From e6aa39f18650bdf23ffe47e103f4afa0b1923861 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Mon, 18 Aug 2008 18:41:51 -0500 Subject: [PATCH 0256/3753] Updating changelog for previous two commits Signed-off-by: Luke Kanies --- CHANGELOG | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 60f70b7be6..1e50cfd05b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,8 @@ 1.5.x: + Fixed #1495 - CentOS version detection is now better. + + Fixed #1422 - Facter now defaults to 0 timeout. + Fixes #1492 - added kernelversion fact Added virtual fact From bd87aa049a4ea831e9bdc130e5134b4e4e5a8387 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Mon, 18 Aug 2008 18:50:54 -0500 Subject: [PATCH 0257/3753] Set the timeout for the host-based and resolv-based resolutions to 2. Signed-off-by: Luke Kanies --- CHANGELOG | 2 ++ lib/facter/ipaddress.rb | 4 ++-- lib/facter/kernelrelease.rb | 2 +- lib/facter/puppetversion.rb | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 1e50cfd05b..af190c6f66 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ 1.5.x: + Set the timeout the host-based and resolve-based resolutions to 2. + Fixed #1495 - CentOS version detection is now better. Fixed #1422 - Facter now defaults to 0 timeout. diff --git a/lib/facter/ipaddress.rb b/lib/facter/ipaddress.rb index c97abe4846..b18b7c99fc 100644 --- a/lib/facter/ipaddress.rb +++ b/lib/facter/ipaddress.rb @@ -1,4 +1,4 @@ -Facter.add(:ipaddress, :ldapname => "iphostnumber") do +Facter.add(:ipaddress, :ldapname => "iphostnumber", :timeout => 2) do setcode do require 'resolv' @@ -19,7 +19,7 @@ end end -Facter.add(:ipaddress) do +Facter.add(:ipaddress, :timeout => 2) do setcode do if hostname = Facter.value(:hostname) # we need Hostname to exist for this to work diff --git a/lib/facter/kernelrelease.rb b/lib/facter/kernelrelease.rb index bff7a90fcc..5dccf8f984 100644 --- a/lib/facter/kernelrelease.rb +++ b/lib/facter/kernelrelease.rb @@ -2,7 +2,7 @@ setcode 'uname -r' end -Facter.add(:kernelrelease, :timeout => 5) do +Facter.add(:kernelrelease) do confine :kernel => :aix setcode 'oslevel -s' end diff --git a/lib/facter/puppetversion.rb b/lib/facter/puppetversion.rb index 16510c880a..66fcfe8b21 100644 --- a/lib/facter/puppetversion.rb +++ b/lib/facter/puppetversion.rb @@ -1,4 +1,4 @@ -Facter.add(:puppetversion, :timeout => 1.5) do +Facter.add(:puppetversion) do setcode { begin require 'puppet' From d24504e83acd0bf210fb643d6c9f0cc2e6eae6c0 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Mon, 18 Aug 2008 19:15:24 -0500 Subject: [PATCH 0258/3753] Added a Process.waitall thread when there's a timeout, to avoid zombies. Without this call, every time there's a timeout, we'll get a zombie. --- CHANGELOG | 2 ++ lib/facter/util/resolution.rb | 5 +++++ spec/unit/util/resolution.rb | 11 +++++++++++ 3 files changed, 18 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index af190c6f66..6ddf9d4533 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ 1.5.x: + Added a Process.waitall thread when there's a timeout, to avoid zombies. + Set the timeout the host-based and resolve-based resolutions to 2. Fixed #1495 - CentOS version detection is now better. diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 19f2c1e3e3..b50c9e98bc 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -121,6 +121,11 @@ def value end rescue Timeout::Error => detail warn "Timed out seeking value for %s" % self.name + + # This call avoids zombies -- basically, create a thread that will + # dezombify all of the child processes that we're ignoring because + # of the timeout. + Thread.new { Process.waitall } return nil end diff --git a/spec/unit/util/resolution.rb b/spec/unit/util/resolution.rb index 5ce3d6329d..643be33f6b 100755 --- a/spec/unit/util/resolution.rb +++ b/spec/unit/util/resolution.rb @@ -112,6 +112,17 @@ @resolve.value.should be_nil end + + it "should waitall to avoid zombies if the timeout is exceeded" do + @resolve.stubs(:warn) + @resolve.timeout = 0.1 + @resolve.setcode { sleep 2; raise "This is a test" } + + Thread.expects(:new).yields + Process.expects(:waitall) + + @resolve.value + end end end From bc35a3b98215e8bfd384a869edd05fe640ac95dd Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Tue, 19 Aug 2008 02:55:41 +0200 Subject: [PATCH 0259/3753] Adding a rake task for creating an archive. Signed-off-by: Luke Kanies --- Rakefile | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Rakefile b/Rakefile index 619bf471c2..0968c1f86e 100644 --- a/Rakefile +++ b/Rakefile @@ -48,3 +48,9 @@ if project.has?(:epm) task.rubylibs = FileList.new('lib/**/*') end end + +task :archive do + raise ArgumentError, "You must specify the archive name by setting ARCHIVE; e.g., ARCHIVE=1.5.1rc1" unless archive = ENV["ARCHIVE"] + + sh "git archive --format=tar --prefix=facter-#{archive}/ HEAD | gzip -c > facter-#{archive}.tgz" +end From c2eb5ba2597fc76be0d16a93a1aaa5e7a6394b17 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Wed, 27 Aug 2008 06:16:35 +0200 Subject: [PATCH 0260/3753] Updated to version 1.5.1 --- conf/redhat/facter.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index 0e360cfdc2..6eb98702d8 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -5,7 +5,7 @@ Summary: Ruby module for collecting simple facts about a host operating system Name: facter -Version: 1.5 +Version: 1.5.1 Release: 1%{?dist} License: GPL Group: System Environment/Base From bff615cb94cb18a28582a48d0e763140d2cc9afa Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Wed, 27 Aug 2008 06:16:35 +0200 Subject: [PATCH 0261/3753] Updated to version 1.5.1 --- lib/facter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter.rb b/lib/facter.rb index 36889c8038..bb398da26e 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -27,7 +27,7 @@ module Util; end include Comparable include Enumerable - FACTERVERSION = '1.5.0' + FACTERVERSION = '1.5.1' # = Facter # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. From 0356b6e5f37ecb70fa8e3df5e46da36d3da9e7d9 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Wed, 27 Aug 2008 06:16:35 +0200 Subject: [PATCH 0262/3753] Updated to version 1.5.1 --- CHANGELOG | 23 ++++++++++++++++++++++- lib/facter.rb | 2 +- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 6ddf9d4533..46ef1be235 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,4 @@ -1.5.x: +1.5.1: Added a Process.waitall thread when there's a timeout, to avoid zombies. Set the timeout the host-based and resolve-based resolutions to 2. @@ -16,6 +16,27 @@ Fixes #1467 - macaddress not set on Ubuntu + Adding a rake task for creating an archive. + + Adding better SuSE detection for both operatingsystem and release. + + Add sample test and strawman solution for IP parsing code + + Add module level tests for Facter::IPAddress + + Fixed #1425 - Solaris + + Feature #1487: Package creation scripts for Mac OS X + + Feature #1478: Allow specification of --bindir --sbindir --sitelibdir --mandir + + Feature #1475: CONFIG['bindir'] CONFIG['sbindir'] have undesirable defaults on + + Fixes #1467 - macaddress not set on Ubuntu + + Enabled a number of Windows facts - operating system, domain, ipaddress, macaddress, + kernel, ipconfig and others + 1.5.0: Fixed Rakefile to include additional files including tests et al diff --git a/lib/facter.rb b/lib/facter.rb index 36889c8038..bb398da26e 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -27,7 +27,7 @@ module Util; end include Comparable include Enumerable - FACTERVERSION = '1.5.0' + FACTERVERSION = '1.5.1' # = Facter # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. From 518393ee0a6943237d66c4143d70370475598b17 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Wed, 3 Sep 2008 05:37:08 +1000 Subject: [PATCH 0263/3753] Fixed . dot escaping --- lib/facter/ipmess.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/ipmess.rb b/lib/facter/ipmess.rb index ce6e420a7a..badf95f7d4 100644 --- a/lib/facter/ipmess.rb +++ b/lib/facter/ipmess.rb @@ -17,7 +17,7 @@ case Facter.value(:kernel) when 'SunOS', 'Linux', 'OpenBSD', 'NetBSD', 'FreeBSD' Facter::IPAddress.get_interfaces.each do |interface| - mi = interface.gsub(':', '_') + mi = interface.gsub('/:|\./', '_') Facter.add("ipaddress_" + mi) do confine :kernel => [ :sunos, :freebsd, :openbsd, :netbsd, :linux ] From e5038576590deb58fd90b562dea366a84df2bfb3 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Fri, 5 Sep 2008 10:00:41 +1000 Subject: [PATCH 0264/3753] Fixed #1559 - update to dmidecode fact --- CHANGELOG | 2 ++ lib/facter/util/manufacturer.rb | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 46ef1be235..29de7a3444 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ 1.5.1: + Fixed #1559 - update to dmidecode fact + Added a Process.waitall thread when there's a timeout, to avoid zombies. Set the timeout the host-based and resolve-based resolutions to 2. diff --git a/lib/facter/util/manufacturer.rb b/lib/facter/util/manufacturer.rb index 139e6687c3..665fa76fa4 100644 --- a/lib/facter/util/manufacturer.rb +++ b/lib/facter/util/manufacturer.rb @@ -22,8 +22,8 @@ def self.dmi_find_system_info(name) name.each_pair do |key,v| v.each do |value| output.split("Handle").each do |line| - if line =~ /#{key}/ and line =~ /#{value} (\w.*)\n*./ - result = $1 + if line =~ /#{key}/ and line =~ /#{value} ([-\w].*)\n*./ + result = $1 Facter.add(value.chomp(':').gsub(' ','')) do confine :kernel => [ :linux, :freebsd, :netbsd, :openbsd ] setcode do From 5c50bc35fe12b36b3c2482d9d149d09d253e2f45 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Fri, 5 Sep 2008 10:07:35 +1000 Subject: [PATCH 0265/3753] Fixed #1555 - added operatingsystemrelease for Solaris --- CHANGELOG | 5 ++++- lib/facter/operatingsystemrelease.rb | 12 ++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 29de7a3444..1877b4860f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,9 @@ -1.5.1: +1.5.2: + Fixed #1555 - added operatingsystemrelease for Solaris + Fixed #1559 - update to dmidecode fact +1.5.1: Added a Process.waitall thread when there's a timeout, to avoid zombies. Set the timeout the host-based and resolve-based resolutions to 2. diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index 19d5869b0f..dd8a8cccf7 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -73,6 +73,18 @@ end end +Facter.add(:operatingsystemrelease) do + confine :operatingsystem => %w{Solaris} + setcode do + full_release = File.readlines("/etc/release").to_s.match(/Solaris \w+ [\w\/]+ ([^_]+_[^_]+)/).to_a.last.chomp("wos") + if full_release =~ /^s(\d+)\w(_\w\d)+/ + $1 + $2 + else + full_release + end + end +end + Facter.add(:operatingsystemrelease) do setcode do Facter[:kernelrelease].value end end From 0fac7040920a9d820f656ce3f121ae2f342df8e4 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Fri, 5 Sep 2008 10:22:23 +1000 Subject: [PATCH 0266/3753] Fixed #1558 - Updated virtual fact for xenu and xen0 --- CHANGELOG | 6 ++++-- lib/facter/virtual.rb | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 1877b4860f..9ddeaea183 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,7 +1,9 @@ 1.5.2: - Fixed #1555 - added operatingsystemrelease for Solaris + Fixed #1558 - Updated virtual fact for xenu and xen0 - Fixed #1559 - update to dmidecode fact + Fixed #1555 - Ddded operatingsystemrelease for Solaris + + Fixed #1559 - Update to dmidecode fact 1.5.1: Added a Process.waitall thread when there's a timeout, to avoid zombies. diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index d84397d148..46d76b8177 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -11,12 +11,12 @@ result = "openvz" end - if FileTest.exists?("/proc/sys/xen/independent_wallclock") - result = "xenu" - elsif FileTest.exists?("/proc/xen/capabilities") + if FileTest.exists?("/proc/xen/capabilities") txt = File.read("/proc/xen/capabilities") if txt =~ /control_d/i result = "xen0" + else + result = "xenu" end end From 4998d3bbbf27dc995aa349666918b4acbd2c8975 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sat, 6 Sep 2008 09:59:36 +1000 Subject: [PATCH 0267/3753] Fixes #1562 - Removed facter from PREREQS --- CHANGELOG | 2 ++ install.rb | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 9ddeaea183..31790c6f2a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ 1.5.2: + Fixed #1562 - Removed facter from PREREQS + Fixed #1558 - Updated virtual fact for xenu and xen0 Fixed #1555 - Ddded operatingsystemrelease for Solaris diff --git a/install.rb b/install.rb index 57ba80c424..3e0d810102 100755 --- a/install.rb +++ b/install.rb @@ -59,7 +59,7 @@ $haveman = false end -PREREQS = %w{openssl facter xmlrpc/client xmlrpc/server cgi} +PREREQS = %w{openssl xmlrpc/client xmlrpc/server cgi} InstallOptions = OpenStruct.new From 6e0a1f34d32023d37001fe6e5a32303aa2aec67f Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Mon, 8 Sep 2008 23:13:04 +1000 Subject: [PATCH 0268/3753] Fixes #1558 - Adjusted virtual fact to allow non-root users to execute it --- lib/facter/virtual.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 46d76b8177..4404ae9cb4 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -11,7 +11,7 @@ result = "openvz" end - if FileTest.exists?("/proc/xen/capabilities") + if FileTest.exists?("/proc/xen/capabilities") && FileTest.readable?("/proc/xen/capabilities") txt = File.read("/proc/xen/capabilities") if txt =~ /control_d/i result = "xen0" From 0e49580a4d7773a804318bc9d4447f3b57593ad6 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Tue, 9 Sep 2008 05:00:34 +0200 Subject: [PATCH 0269/3753] Updated to version 1.5.2 --- conf/redhat/facter.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index 6eb98702d8..92e5d5ae2e 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -5,7 +5,7 @@ Summary: Ruby module for collecting simple facts about a host operating system Name: facter -Version: 1.5.1 +Version: 1.5.2 Release: 1%{?dist} License: GPL Group: System Environment/Base From a80779ba5f9a5e1d50ae96d417c6e43568224047 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Tue, 9 Sep 2008 05:00:34 +0200 Subject: [PATCH 0270/3753] Updated to version 1.5.2 --- lib/facter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter.rb b/lib/facter.rb index bb398da26e..46a97176da 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -27,7 +27,7 @@ module Util; end include Comparable include Enumerable - FACTERVERSION = '1.5.1' + FACTERVERSION = '1.5.2' # = Facter # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. From d4cf657a14b62267837ff7a82ac2ad19b651c369 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Fri, 12 Sep 2008 01:01:20 +1000 Subject: [PATCH 0271/3753] Fixed #1569 - createpackage.rb bug --- CHANGELOG | 3 +++ conf/osx/createpackage.sh | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 31790c6f2a..ece27ee101 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,6 @@ +1.5.x: + Fixed #1569 - createpackage.rb bug + 1.5.2: Fixed #1562 - Removed facter from PREREQS diff --git a/conf/osx/createpackage.sh b/conf/osx/createpackage.sh index d8c2c5291a..5136bbf5a3 100755 --- a/conf/osx/createpackage.sh +++ b/conf/osx/createpackage.sh @@ -49,7 +49,8 @@ function find_facter_root() { function install_facter() { echo "Installing Facter to ${pkgroot}" - "${installer}" --destdir="${pkgroot}" --bindir="${BINDIR}" --sitelibdir="${SITELIBDIR}" &> /dev/null + cd "$facter_root" + ./"${INSTALLRB}" --destdir="${pkgroot}" --bindir="${BINDIR}" --sitelibdir="${SITELIBDIR}" &> /dev/null chown -R root:admin "${pkgroot}" } From 9c9c79a494b1c0d173e1c50c2451b80464ac2c76 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Fri, 12 Sep 2008 01:05:18 +1000 Subject: [PATCH 0272/3753] Fixed #1567 - fixed createpackage.sh output --- CHANGELOG | 3 ++- conf/osx/createpackage.sh | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index ece27ee101..048a30e50a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ 1.5.x: - Fixed #1569 - createpackage.rb bug + Fixed #1569 - createpackage.sh bug + Fixed #1567 - createpackage.sh output 1.5.2: Fixed #1562 - Removed facter from PREREQS diff --git a/conf/osx/createpackage.sh b/conf/osx/createpackage.sh index 5136bbf5a3..4e99c91624 100755 --- a/conf/osx/createpackage.sh +++ b/conf/osx/createpackage.sh @@ -50,7 +50,7 @@ function find_facter_root() { function install_facter() { echo "Installing Facter to ${pkgroot}" cd "$facter_root" - ./"${INSTALLRB}" --destdir="${pkgroot}" --bindir="${BINDIR}" --sitelibdir="${SITELIBDIR}" &> /dev/null + ./"${INSTALLRB}" --destdir="${pkgroot}" --bindir="${BINDIR}" --sitelibdir="${SITELIBDIR}" chown -R root:admin "${pkgroot}" } From 1d0025310905033352fd39b654cb3e9091337bca Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Fri, 12 Sep 2008 01:10:41 +1000 Subject: [PATCH 0273/3753] Fixed #1547 - finally killed dots in IP address facts --- CHANGELOG | 3 +++ lib/facter/ipmess.rb | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 048a30e50a..ee3a2a6b50 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,9 @@ Fixed #1569 - createpackage.sh bug Fixed #1567 - createpackage.sh output + + Fixed #1547 - finally killed dots in IP address facts + 1.5.2: Fixed #1562 - Removed facter from PREREQS diff --git a/lib/facter/ipmess.rb b/lib/facter/ipmess.rb index badf95f7d4..e8793006bc 100644 --- a/lib/facter/ipmess.rb +++ b/lib/facter/ipmess.rb @@ -17,7 +17,7 @@ case Facter.value(:kernel) when 'SunOS', 'Linux', 'OpenBSD', 'NetBSD', 'FreeBSD' Facter::IPAddress.get_interfaces.each do |interface| - mi = interface.gsub('/:|\./', '_') + mi = interface.gsub(/[:.]/, '_') Facter.add("ipaddress_" + mi) do confine :kernel => [ :sunos, :freebsd, :openbsd, :netbsd, :linux ] From c1d937cea47c7d18dbf6d75ac4af906880e86677 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Tue, 16 Sep 2008 08:46:57 +1000 Subject: [PATCH 0274/3753] Fixed #1575 - CentOS fix for Facter SPEC file --- CHANGELOG | 2 ++ conf/redhat/facter.spec | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index ee3a2a6b50..d0a0f5e60f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ 1.5.x: + Fixed #1575 - CentOS fix for Facter SPEC file + Fixed #1569 - createpackage.sh bug Fixed #1567 - createpackage.sh output diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index 92e5d5ae2e..4433cbfe47 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -1,6 +1,6 @@ %{!?ruby_sitelibdir: %define ruby_sitelibdir %(ruby -rrbconfig -e 'puts Config::CONFIG["sitelibdir"]')} -%define has_ruby_abi 0%{?fedora:%fedora} >= 5 || 0%{?rhel:%rhel} >= 5 +%define has_ruby_abi 0%{?fedora:%fedora} >= 5 || 0%{?rhel:%rhel} >= 5 || 0%{?centos:%centos} >= 5 %define has_ruby_noarch %has_ruby_abi Summary: Ruby module for collecting simple facts about a host operating system From a86577c89bc09d9b9033c190b22694e313b2d42e Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Tue, 16 Sep 2008 12:46:13 -0500 Subject: [PATCH 0275/3753] Fixing the GPL/LGPL incompatibility by choosing the oldest-mentioned license (GPL). Signed-off-by: Luke Kanies --- COPYING | 458 ------------------------------------- LICENSE | 683 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 670 insertions(+), 471 deletions(-) delete mode 100644 COPYING diff --git a/COPYING b/COPYING deleted file mode 100644 index 3b473dbfc0..0000000000 --- a/COPYING +++ /dev/null @@ -1,458 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 2.1, February 1999 - - Copyright (C) 1991, 1999 Free Software Foundation, Inc. - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - -[This is the first released version of the Lesser GPL. It also counts - as the successor of the GNU Library Public License, version 2, hence - the version number 2.1.] - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -Licenses are intended to guarantee your freedom to share and change -free software--to make sure the software is free for all its users. - - This license, the Lesser General Public License, applies to some -specially designated software packages--typically libraries--of the -Free Software Foundation and other authors who decide to use it. You -can use it too, but we suggest you first think carefully about whether -this license or the ordinary General Public License is the better -strategy to use in any particular case, based on the explanations below. - - When we speak of free software, we are referring to freedom of use, -not price. Our General Public Licenses are designed to make sure that -you have the freedom to distribute copies of free software (and charge -for this service if you wish); that you receive source code or can get -it if you want it; that you can change the software and use pieces of -it in new free programs; and that you are informed that you can do -these things. - - To protect your rights, we need to make restrictions that forbid -distributors to deny you these rights or to ask you to surrender these -rights. These restrictions translate to certain responsibilities for -you if you distribute copies of the library or if you modify it. - - For example, if you distribute copies of the library, whether gratis -or for a fee, you must give the recipients all the rights that we gave -you. You must make sure that they, too, receive or can get the source -code. If you link other code with the library, you must provide -complete object files to the recipients, so that they can relink them -with the library after making changes to the library and recompiling -it. And you must show them these terms so they know their rights. - - We protect your rights with a two-step method: (1) we copyright the -library, and (2) we offer you this license, which gives you legal -permission to copy, distribute and/or modify the library. - - To protect each distributor, we want to make it very clear that -there is no warranty for the free library. Also, if the library is -modified by someone else and passed on, the recipients should know -that what they have is not the original version, so that the original -author's reputation will not be affected by problems that might be -introduced by others. - - Finally, software patents pose a constant threat to the existence of -any free program. We wish to make sure that a company cannot -effectively restrict the users of a free program by obtaining a -restrictive license from a patent holder. Therefore, we insist that -any patent license obtained for a version of the library must be -consistent with the full freedom of use specified in this license. - - Most GNU software, including some libraries, is covered by the -ordinary GNU General Public License. This license, the GNU Lesser -General Public License, applies to certain designated libraries, and -is quite different from the ordinary General Public License. We use -this license for certain libraries in order to permit linking those -libraries into non-free programs. - - When a program is linked with a library, whether statically or using -a shared library, the combination of the two is legally speaking a -combined work, a derivative of the original library. The ordinary -General Public License therefore permits such linking only if the -entire combination fits its criteria of freedom. The Lesser General -Public License permits more lax criteria for linking other code with -the library. - - We call this license the "Lesser" General Public License because it -does Less to protect the user's freedom than the ordinary General -Public License. It also provides other free software developers Less -of an advantage over competing non-free programs. These disadvantages -are the reason we use the ordinary General Public License for many -libraries. However, the Lesser license provides advantages in certain -special circumstances. - - For example, on rare occasions, there may be a special need to -encourage the widest possible use of a certain library, so that it becomes -a de-facto standard. To achieve this, non-free programs must be -allowed to use the library. A more frequent case is that a free -library does the same job as widely used non-free libraries. In this -case, there is little to gain by limiting the free library to free -software only, so we use the Lesser General Public License. - - In other cases, permission to use a particular library in non-free -programs enables a greater number of people to use a large body of -free software. For example, permission to use the GNU C Library in -non-free programs enables many more people to use the whole GNU -operating system, as well as its variant, the GNU/Linux operating -system. - - Although the Lesser General Public License is Less protective of the -users' freedom, it does ensure that the user of a program that is -linked with the Library has the freedom and the wherewithal to run -that program using a modified version of the Library. - - The precise terms and conditions for copying, distribution and -modification follow. Pay close attention to the difference between a -"work based on the library" and a "work that uses the library". The -former contains code derived from the library, whereas the latter must -be combined with the library in order to run. - - GNU LESSER GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License Agreement applies to any software library or other -program which contains a notice placed by the copyright holder or -other authorized party saying it may be distributed under the terms of -this Lesser General Public License (also called "this License"). -Each licensee is addressed as "you". - - A "library" means a collection of software functions and/or data -prepared so as to be conveniently linked with application programs -(which use some of those functions and data) to form executables. - - The "Library", below, refers to any such software library or work -which has been distributed under these terms. A "work based on the -Library" means either the Library or any derivative work under -copyright law: that is to say, a work containing the Library or a -portion of it, either verbatim or with modifications and/or translated -straightforwardly into another language. (Hereinafter, translation is -included without limitation in the term "modification".) - - "Source code" for a work means the preferred form of the work for -making modifications to it. For a library, complete source code means -all the source code for all modules it contains, plus any associated -interface definition files, plus the scripts used to control compilation -and installation of the library. - - Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running a program using the Library is not restricted, and output from -such a program is covered only if its contents constitute a work based -on the Library (independent of the use of the Library in a tool for -writing it). Whether that is true depends on what the Library does -and what the program that uses the Library does. - - 1. You may copy and distribute verbatim copies of the Library's -complete source code as you receive it, in any medium, provided that -you conspicuously and appropriately publish on each copy an -appropriate copyright notice and disclaimer of warranty; keep intact -all the notices that refer to this License and to the absence of any -warranty; and distribute a copy of this License along with the -Library. - - You may charge a fee for the physical act of transferring a copy, -and you may at your option offer warranty protection in exchange for a -fee. - - 2. You may modify your copy or copies of the Library or any portion -of it, thus forming a work based on the Library, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) The modified work must itself be a software library. - - b) You must cause the files modified to carry prominent notices - stating that you changed the files and the date of any change. - - c) You must cause the whole of the work to be licensed at no - charge to all third parties under the terms of this License. - - d) If a facility in the modified Library refers to a function or a - table of data to be supplied by an application program that uses - the facility, other than as an argument passed when the facility - is invoked, then you must make a good faith effort to ensure that, - in the event an application does not supply such function or - table, the facility still operates, and performs whatever part of - its purpose remains meaningful. - - (For example, a function in a library to compute square roots has - a purpose that is entirely well-defined independent of the - application. Therefore, Subsection 2d requires that any - application-supplied function or table used by this function must - be optional: if the application does not supply it, the square - root function must still compute square roots.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Library, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Library, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote -it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Library. - -In addition, mere aggregation of another work not based on the Library -with the Library (or with a work based on the Library) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may opt to apply the terms of the ordinary GNU General Public -License instead of this License to a given copy of the Library. To do -this, you must alter all the notices that refer to this License, so -that they refer to the ordinary GNU General Public License, version 2, -instead of to this License. (If a newer version than version 2 of the -ordinary GNU General Public License has appeared, then you can specify -that version instead if you wish.) Do not make any other change in -these notices. - - Once this change is made in a given copy, it is irreversible for -that copy, so the ordinary GNU General Public License applies to all -subsequent copies and derivative works made from that copy. - - This option is useful when you wish to copy part of the code of -the Library into a program that is not a library. - - 4. You may copy and distribute the Library (or a portion or -derivative of it, under Section 2) in object code or executable form -under the terms of Sections 1 and 2 above provided that you accompany -it with the complete corresponding machine-readable source code, which -must be distributed under the terms of Sections 1 and 2 above on a -medium customarily used for software interchange. - - If distribution of object code is made by offering access to copy -from a designated place, then offering equivalent access to copy the -source code from the same place satisfies the requirement to -distribute the source code, even though third parties are not -compelled to copy the source along with the object code. - - 5. A program that contains no derivative of any portion of the -Library, but is designed to work with the Library by being compiled or -linked with it, is called a "work that uses the Library". Such a -work, in isolation, is not a derivative work of the Library, and -therefore falls outside the scope of this License. - - However, linking a "work that uses the Library" with the Library -creates an executable that is a derivative of the Library (because it -contains portions of the Library), rather than a "work that uses the -library". The executable is therefore covered by this License. -Section 6 states terms for distribution of such executables. - - When a "work that uses the Library" uses material from a header file -that is part of the Library, the object code for the work may be a -derivative work of the Library even though the source code is not. -Whether this is true is especially significant if the work can be -linked without the Library, or if the work is itself a library. The -threshold for this to be true is not precisely defined by law. - - If such an object file uses only numerical parameters, data -structure layouts and accessors, and small macros and small inline -functions (ten lines or less in length), then the use of the object -file is unrestricted, regardless of whether it is legally a derivative -work. (Executables containing this object code plus portions of the -Library will still fall under Section 6.) - - Otherwise, if the work is a derivative of the Library, you may -distribute the object code for the work under the terms of Section 6. -Any executables containing that work also fall under Section 6, -whether or not they are linked directly with the Library itself. - - 6. As an exception to the Sections above, you may also combine or -link a "work that uses the Library" with the Library to produce a -work containing portions of the Library, and distribute that work -under terms of your choice, provided that the terms permit -modification of the work for the customer's own use and reverse -engineering for debugging such modifications. - - You must give prominent notice with each copy of the work that the -Library is used in it and that the Library and its use are covered by -this License. You must supply a copy of this License. If the work -during execution displays copyright notices, you must include the -copyright notice for the Library among them, as well as a reference -directing the user to the copy of this License. Also, you must do one -of these things: - - a) Accompany the work with the complete corresponding - machine-readable source code for the Library including whatever - changes were used in the work (which must be distributed under - Sections 1 and 2 above); and, if the work is an executable linked - with the Library, with the complete machine-readable "work that - uses the Library", as object code and/or source code, so that the - user can modify the Library and then relink to produce a modified - executable containing the modified Library. (It is understood - that the user who changes the contents of definitions files in the - Library will not necessarily be able to recompile the application - to use the modified definitions.) - - b) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (1) uses at run time a - copy of the library already present on the user's computer system, - rather than copying library functions into the executable, and (2) - will operate properly with a modified version of the library, if - the user installs one, as long as the modified version is - interface-compatible with the version that the work was made with. - - c) Accompany the work with a written offer, valid for at - least three years, to give the same user the materials - specified in Subsection 6a, above, for a charge no more - than the cost of performing this distribution. - - d) If distribution of the work is made by offering access to copy - from a designated place, offer equivalent access to copy the above - specified materials from the same place. - - e) Verify that the user has already received a copy of these - materials or that you have already sent this user a copy. - - For an executable, the required form of the "work that uses the -Library" must include any data and utility programs needed for -reproducing the executable from it. However, as a special exception, -the materials to be distributed need not include anything that is -normally distributed (in either source or binary form) with the major -components (compiler, kernel, and so on) of the operating system on -which the executable runs, unless that component itself accompanies -the executable. - - It may happen that this requirement contradicts the license -restrictions of other proprietary libraries that do not normally -accompany the operating system. Such a contradiction means you cannot -use both them and the Library together in an executable that you -distribute. - - 7. You may place library facilities that are a work based on the -Library side-by-side in a single library together with other library -facilities not covered by this License, and distribute such a combined -library, provided that the separate distribution of the work based on -the Library and of the other library facilities is otherwise -permitted, and provided that you do these two things: - - a) Accompany the combined library with a copy of the same work - based on the Library, uncombined with any other library - facilities. This must be distributed under the terms of the - Sections above. - - b) Give prominent notice with the combined library of the fact - that part of it is a work based on the Library, and explaining - where to find the accompanying uncombined form of the same work. - - 8. You may not copy, modify, sublicense, link with, or distribute -the Library except as expressly provided under this License. Any -attempt otherwise to copy, modify, sublicense, link with, or -distribute the Library is void, and will automatically terminate your -rights under this License. However, parties who have received copies, -or rights, from you under this License will not have their licenses -terminated so long as such parties remain in full compliance. - - 9. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Library or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Library (or any work based on the -Library), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Library or works based on it. - - 10. Each time you redistribute the Library (or any work based on the -Library), the recipient automatically receives a license from the -original licensor to copy, distribute, link with or modify the Library -subject to these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties with -this License. - - 11. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Library at all. For example, if a patent -license would not permit royalty-free redistribution of the Library by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Library. - -If any portion of this section is held invalid or unenforceable under any -particular circumstance, the balance of the section is intended to apply, -and the section as a whole is intended to apply in other circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 12. If the distribution and/or use of the Library is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Library under this License may add -an explicit geographical distribution limitation excluding those countries, -so that distribution is permitted only in or among countries not thus -excluded. In such case, this License incorporates the limitation as if -written in the body of this License. - - 13. The Free Software Foundation may publish revised and/or new -versions of the Lesser General Public License from time to time. -Such new versions will be similar in spirit to the present version, -but may differ in detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Library -specifies a version number of this License which applies to it and -"any later version", you have the option of following the terms and -conditions either of that version or of any later version published by -the Free Software Foundation. If the Library does not specify a -license version number, you may choose any version ever published by -the Free Software Foundation. - - 14. If you wish to incorporate parts of the Library into other free -programs whose distribution conditions are incompatible with these, -write to the author to ask for permission. For software which is -copyrighted by the Free Software Foundation, write to the Free -Software Foundation; we sometimes make exceptions for this. Our -decision will be guided by the two goals of preserving the free status -of all derivatives of our free software and of promoting the sharing -and reuse of software generally. - - NO WARRANTY - - 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO -WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. -EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR -OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY -KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE -LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME -THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN -WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY -AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU -FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR -CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE -LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING -RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A -FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF -SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGES. - - END OF TERMS AND CONDITIONS diff --git a/LICENSE b/LICENSE index 308f4e000d..94a9ed024d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,17 +1,674 @@ -Facter - Host Fact Detection and Reporting. Copyright (C) 2005 Reductive Labs LLC + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 -Reductive Labs can be contacted at: info@reductivelabs.com + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. -This program and entire repository is free software; you can -redistribute it and/or modify it under the terms of the GNU -General Public License as published by the Free Software -Foundation; either version 2 of the License, or any later version. + Preamble -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. + The GNU General Public License is a free, copyleft license for +software and other kinds of works. -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. From 20650acb784a9463b6b233f7f437af24cc8af422 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sat, 20 Sep 2008 11:51:11 +1000 Subject: [PATCH 0276/3753] Fixes #1582 - Fix MAC address reporting for Linux bonding slave interfaces --- CHANGELOG | 2 ++ lib/facter/util/ip.rb | 57 ++++++++++++++++++++++++++++++++----------- 2 files changed, 45 insertions(+), 14 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d0a0f5e60f..9ab7d688e5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ 1.5.x: + Fixes #1582 - Fix MAC address reporting for Linux bonding slave interfaces + Fixed #1575 - CentOS fix for Facter SPEC file Fixed #1569 - createpackage.sh bug diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index f6efdc27ea..544985838d 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -37,6 +37,25 @@ def self.get_single_interface_output(interface) output end + def self.get_bonding_master(interface) + if Facter.value(:kernel) != 'Linux' + return nil + end + # We need ip instead of ifconfig because it will show us + # the bonding master device. + if not FileTest.executable?("/sbin/ip") + return nil + end + regex = /SLAVE[,>].* (bond[0-9]+)/ + ethbond = regex.match(%x{/sbin/ip link show #{interface}}) + if ethbond + device = ethbond[1] + else + device = nil + end + device + end + def self.get_interface_value(interface, label) @@ -66,23 +85,33 @@ def self.get_interface_value(interface, label) regex = mask end - output_int = get_single_interface_output(interface) + # Linux changes the MAC address reported via ifconfig when an ethernet interface + # becomes a slave of a bonding device to the master MAC address. + # We have to dig a bit to get the original/real MAC address of the interface. + bonddev = get_bonding_master(interface) + if label == 'macaddress' and bonddev + bondinfo = IO.readlines("/proc/net/bonding/#{bonddev}") + hwaddrre = /^Slave Interface: #{interface}\n[^\n].+?\nPermanent HW addr: (([0-9a-fA-F]{2}:?)*)$/m + value = hwaddrre.match(bondinfo.to_s)[1].upcase + else + output_int = get_single_interface_output(interface) - if interface != "lo" && interface != "lo0" - output_int.each { |s| - if s =~ regex - value = $1 - if label == 'netmask' && Facter.value(:kernel) == "SunOS" - value = value.scan(/../).collect do |byte| byte.to_i(16) end.join('.') + if interface != "lo" && interface != "lo0" + output_int.each { |s| + if s =~ regex + value = $1 + if label == 'netmask' && Facter.value(:kernel) == "SunOS" + value = value.scan(/../).collect do |byte| byte.to_i(16) end.join('.') + end + tmp1.push(value) end - tmp1.push(value) - end - } - end + } + end - if tmp1 - value = tmp1.shift - end + if tmp1 + value = tmp1.shift + end + end end end From 84b83c453e68badb1de60f91ae2945fb4f83d922 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Wed, 24 Sep 2008 14:00:06 -0500 Subject: [PATCH 0277/3753] Fixed #1509 - Fixed version recognition for SLES. --- CHANGELOG | 2 ++ lib/facter/operatingsystemrelease.rb | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 9ab7d688e5..4b0b55a96a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ 1.5.x: + Fixed #1509 - Fixed version recognition for SLES. + Fixes #1582 - Fix MAC address reporting for Linux bonding slave interfaces Fixed #1575 - CentOS fix for Facter SPEC file diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index dd8a8cccf7..a785db6dd6 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -64,7 +64,7 @@ if releasefile =~ /^PATCHLEVEL\s*=\s*(\d+)/ releaseminor = $1 else - releaseminor = 0 + releaseminor = "0" end releasemajor + "." + releaseminor else From 4d7b44cf141f9c8d390c3470a18b16c3f036b28d Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Mon, 29 Sep 2008 12:07:51 -0500 Subject: [PATCH 0278/3753] Fixed #1619 - Applying patch by seanmil, adding support for SLES. Signed-off-by: Luke Kanies --- CHANGELOG | 2 ++ lib/facter/hardwareisa.rb | 2 +- lib/facter/id.rb | 2 +- lib/facter/lsbmajdistrelease.rb | 2 +- lib/facter/macaddress.rb | 2 +- lib/facter/uniqueid.rb | 2 +- 6 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 4b0b55a96a..afd863f6e9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ 1.5.x: + Fixed #1619 - Applying patch by seanmil, adding support for SLES. + Fixed #1509 - Fixed version recognition for SLES. Fixes #1582 - Fix MAC address reporting for Linux bonding slave interfaces diff --git a/lib/facter/hardwareisa.rb b/lib/facter/hardwareisa.rb index 4dce535c48..5f7de43a72 100644 --- a/lib/facter/hardwareisa.rb +++ b/lib/facter/hardwareisa.rb @@ -1,4 +1,4 @@ Facter.add(:hardwareisa) do setcode 'uname -p', '/bin/sh' - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE Debian Gentoo FreeBSD OpenBSD NetBSD} + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE SLES Debian Gentoo FreeBSD OpenBSD NetBSD} end diff --git a/lib/facter/id.rb b/lib/facter/id.rb index 41db22f48a..15b021adad 100644 --- a/lib/facter/id.rb +++ b/lib/facter/id.rb @@ -1,4 +1,4 @@ Facter.add(:id) do - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE Debian Gentoo AIX} + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE SLES Debian Gentoo AIX} setcode "whoami" end diff --git a/lib/facter/lsbmajdistrelease.rb b/lib/facter/lsbmajdistrelease.rb index 44f3b9b511..4b2bac8caf 100644 --- a/lib/facter/lsbmajdistrelease.rb +++ b/lib/facter/lsbmajdistrelease.rb @@ -3,7 +3,7 @@ require 'facter' Facter.add("lsbmajdistrelease") do - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE Debian Gentoo} + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE SLES Debian Gentoo} setcode do if /(\d*)\./i =~ Facter.value(:lsbdistrelease) result=$1 diff --git a/lib/facter/macaddress.rb b/lib/facter/macaddress.rb index 11dceb00b4..28ded4ca9b 100644 --- a/lib/facter/macaddress.rb +++ b/lib/facter/macaddress.rb @@ -1,5 +1,5 @@ Facter.add(:macaddress) do - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE Debian Gentoo Ubuntu} + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE SLES Debian Gentoo Ubuntu} setcode do ether = [] output = %x{/sbin/ifconfig -a} diff --git a/lib/facter/uniqueid.rb b/lib/facter/uniqueid.rb index d1553c5fca..b86321c370 100644 --- a/lib/facter/uniqueid.rb +++ b/lib/facter/uniqueid.rb @@ -1,4 +1,4 @@ Facter.add(:uniqueid) do setcode 'hostid', '/bin/sh' - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE Debian Gentoo AIX} + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE SLES Debian Gentoo AIX} end From de39f6c85dca61499876d81873fd8089a6457b04 Mon Sep 17 00:00:00 2001 From: David Lutterkort Date: Wed, 15 Oct 2008 11:43:29 -0700 Subject: [PATCH 0279/3753] Revamp domain resolution Facts are not necessarily evaluated in the order in which they are added. Merge all the domain facts without a confine into one fact that checks the various sources for domain names in the right order. --- lib/facter/domain.rb | 73 +++++++++++++------------------------------- 1 file changed, 21 insertions(+), 52 deletions(-) diff --git a/lib/facter/domain.rb b/lib/facter/domain.rb index 57b04f6b85..b1bba4d698 100644 --- a/lib/facter/domain.rb +++ b/lib/facter/domain.rb @@ -1,67 +1,36 @@ Facter.add(:domain) do setcode do - # First force the hostname to be checked + # Get the domain from various sources; the order of these + # steps is important + Facter.value(:hostname) + next $domain if defined? $domain and ! $domain.nil? + + domain = Facter::Util::Resolution.exec('dnsdomainname') + next domain if domain =~ /.+\..+/ + + domain = Facter::Util::Resolution.exec('domainname') + next domain if domain =~ /.+\..+/ - # Now check to see if it set the domain - if defined? $domain and ! $domain.nil? - $domain - else - nil - end - end -end -# Look for the DNS domain name command first. -Facter.add(:domain) do - setcode do - domain = Facter::Util::Resolution.exec('dnsdomainname') or nil - # make sure it's a real domain - if domain and domain =~ /.+\..+/ - domain - else - nil - end - end -end -Facter.add(:domain) do - setcode do - domain = Facter::Util::Resolution.exec('domainname') or nil - # make sure it's a real domain - if domain and domain =~ /.+\..+/ - domain - else - nil - end - end -end -Facter.add(:domain) do - setcode do - value = nil if FileTest.exists?("/etc/resolv.conf") + domain = nil + search = nil File.open("/etc/resolv.conf") { |file| - # is the domain set? file.each { |line| if line =~ /domain\s+(\S+)/ - value = $1 - break + domain = $1 + elsif line =~ /search\s+(\S+)/ + search = $1 end } } - ! value and File.open("/etc/resolv.conf") { |file| - # is the search path set? - file.each { |line| - if line =~ /search\s+(\S+)/ - value = $1 - break - end - } - } - value - else - nil + next domain if domain + next search if search end + nil end end + Facter.add(:domain) do confine :kernel => :windows setcode do @@ -69,9 +38,9 @@ domain = "" wmi = WIN32OLE.connect("winmgmts://") query = "select DNSDomain from Win32_NetworkAdapterConfiguration where IPEnabled = True" - wmi.ExecQuery(query).each { |nic| + wmi.ExecQuery(query).each { |nic| domain = nic.DNSDomain - break + break } domain end From 6393e82e86def14891b204803aba156059736749 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Tue, 7 Oct 2008 11:29:10 +1100 Subject: [PATCH 0280/3753] Fixed #1634 - Update virtual fact to differentiate OpenVZ hardware nodes and virtual environments --- CHANGELOG | 6 +++++- lib/facter/virtual.rb | 10 +++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index afd863f6e9..6a2c06f97a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,9 +1,13 @@ 1.5.x: Fixed #1619 - Applying patch by seanmil, adding support for SLES. + + Fixed #1634 - Update virtual fact to differentiate OpenVZ + hardware nodes and virtual environments Fixed #1509 - Fixed version recognition for SLES. - Fixes #1582 - Fix MAC address reporting for Linux bonding slave interfaces + Fixes #1582 - Fix MAC address reporting for Linux bonding + slave interfaces Fixed #1575 - CentOS fix for Facter SPEC file diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 4404ae9cb4..fd05d43346 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -8,7 +8,15 @@ setcode do if FileTest.exists?("/proc/user_beancounters") - result = "openvz" + # openvz. can be hardware node or virtual environment + # read the init process' status file, it has laxer permissions + # than /proc/user_beancounters (so this won't fail as non-root) + txt = File.read("/proc/1/status") + if txt =~ /^envID:[[:blank:]]+0$/mi + result = "openvzhn" + else + result = "openvzve" + end end if FileTest.exists?("/proc/xen/capabilities") && FileTest.readable?("/proc/xen/capabilities") From 051c8437586758870e6898918f10a05b217587c0 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Tue, 21 Oct 2008 09:06:54 +1100 Subject: [PATCH 0281/3753] Removed ENV path setting from virtual.rb --- lib/facter/virtual.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index fd05d43346..17d6538292 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -1,8 +1,6 @@ Facter.add("virtual") do confine :kernel => %w{Linux FreeBSD OpenBSD} - ENV["PATH"]="/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/sbin:/usr/local/bin" - result = "physical" setcode do From 8a38aa5fba57ec1c8f5d7636912a69afc7cc5b2a Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Tue, 21 Oct 2008 10:26:29 +1100 Subject: [PATCH 0282/3753] Added Ubuntu to a variety of confines --- CHANGELOG | 2 ++ lib/facter/hardwareisa.rb | 2 +- lib/facter/id.rb | 2 +- lib/facter/lsbmajdistrelease.rb | 2 +- lib/facter/uniqueid.rb | 2 +- 5 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 6a2c06f97a..0c581e8af2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ 1.5.x: + Added Ubuntu to a variety of confines + Fixed #1619 - Applying patch by seanmil, adding support for SLES. Fixed #1634 - Update virtual fact to differentiate OpenVZ diff --git a/lib/facter/hardwareisa.rb b/lib/facter/hardwareisa.rb index 5f7de43a72..6a67d9624e 100644 --- a/lib/facter/hardwareisa.rb +++ b/lib/facter/hardwareisa.rb @@ -1,4 +1,4 @@ Facter.add(:hardwareisa) do setcode 'uname -p', '/bin/sh' - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE SLES Debian Gentoo FreeBSD OpenBSD NetBSD} + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE SLES Debian Ubuntu Gentoo FreeBSD OpenBSD NetBSD} end diff --git a/lib/facter/id.rb b/lib/facter/id.rb index 15b021adad..6157991ede 100644 --- a/lib/facter/id.rb +++ b/lib/facter/id.rb @@ -1,4 +1,4 @@ Facter.add(:id) do - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE SLES Debian Gentoo AIX} + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE SLES Debian Ubuntu Gentoo AIX} setcode "whoami" end diff --git a/lib/facter/lsbmajdistrelease.rb b/lib/facter/lsbmajdistrelease.rb index 4b2bac8caf..f84c3c15ec 100644 --- a/lib/facter/lsbmajdistrelease.rb +++ b/lib/facter/lsbmajdistrelease.rb @@ -3,7 +3,7 @@ require 'facter' Facter.add("lsbmajdistrelease") do - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE SLES Debian Gentoo} + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE SLES Debian Ubuntu Gentoo} setcode do if /(\d*)\./i =~ Facter.value(:lsbdistrelease) result=$1 diff --git a/lib/facter/uniqueid.rb b/lib/facter/uniqueid.rb index b86321c370..b199865fd4 100644 --- a/lib/facter/uniqueid.rb +++ b/lib/facter/uniqueid.rb @@ -1,4 +1,4 @@ Facter.add(:uniqueid) do setcode 'hostid', '/bin/sh' - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE SLES Debian Gentoo AIX} + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE SLES Debian Ubuntu Gentoo AIX} end From 4e707c6b536f7c7ddaedaeb4dae84e3f898426e5 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Wed, 22 Oct 2008 21:55:40 +1100 Subject: [PATCH 0283/3753] Fixed #1650 - OS X package creation script should be more selective about cleaning out prior versions --- CHANGELOG | 5 ++++- conf/osx/preflight | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 0c581e8af2..e432110979 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,7 @@ -1.5.x: +1.5.3: + Fixed #1650 - OS X package creation script should be more selective + about cleaning out prior versions + Added Ubuntu to a variety of confines Fixed #1619 - Applying patch by seanmil, adding support for SLES. diff --git a/conf/osx/preflight b/conf/osx/preflight index 400a2616f7..9b2c07e2f9 100755 --- a/conf/osx/preflight +++ b/conf/osx/preflight @@ -8,4 +8,5 @@ # ${3} is the destination volume so that this works correctly # when being installed to volumes other than the current OS. -rm -Rf "${3}{SITELIBDIR}/facter*" +/bin/rm -Rf "${3}{SITELIBDIR}/facter" +/bin/rm -Rf "${3}{SITELIBDIR/facter.rb" From 43d0aeadbd003fa6b0787084d77e86f48e5dab1d Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Tue, 28 Oct 2008 11:53:05 +1100 Subject: [PATCH 0284/3753] Fixed #1697 - Typo in ipaddress.rb causes timeout under Solaris 10 SPARC --- lib/facter/ipaddress.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/ipaddress.rb b/lib/facter/ipaddress.rb index b18b7c99fc..d7496f04ba 100644 --- a/lib/facter/ipaddress.rb +++ b/lib/facter/ipaddress.rb @@ -25,7 +25,7 @@ # we need Hostname to exist for this to work host = nil if host = Facter::Util::Resolution.exec("host #{hostname}") - host = host.chomp.split(/\s/) + list = host.chomp.split(/\s/) if defined? list[-1] and list[-1] =~ /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ list[-1] From fd07cd25dbecfc5b6e4531e8b2af70ffcb01b419 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Wed, 5 Nov 2008 12:29:44 +1100 Subject: [PATCH 0285/3753] Removed EPM task --- Rakefile | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/Rakefile b/Rakefile index 0968c1f86e..c4f449f6a3 100644 --- a/Rakefile +++ b/Rakefile @@ -27,9 +27,6 @@ project = Rake::RedLabProject.new("facter") do |p| 'etc/*' ] - #p.epmhosts = %w{culain} - #p.rpmhost = "fedora1" - #p.sunpkghost = "sol10b" end project.mkgemtask do |gem| @@ -42,13 +39,6 @@ project.mkgemtask do |gem| gem.author = "Luke Kanies" end -if project.has?(:epm) - project.mkepmtask do |task| - task.bins = FileList.new("bin/facter") - task.rubylibs = FileList.new('lib/**/*') - end -end - task :archive do raise ArgumentError, "You must specify the archive name by setting ARCHIVE; e.g., ARCHIVE=1.5.1rc1" unless archive = ENV["ARCHIVE"] From f9a346addd533257b8dd242bec025dfe550a5e18 Mon Sep 17 00:00:00 2001 From: David Lutterkort Date: Fri, 7 Nov 2008 10:41:19 -0800 Subject: [PATCH 0286/3753] Sync specfile with latest from Fedora --- conf/redhat/facter.spec | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index 4433cbfe47..efcb7afb8b 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -1,19 +1,19 @@ %{!?ruby_sitelibdir: %define ruby_sitelibdir %(ruby -rrbconfig -e 'puts Config::CONFIG["sitelibdir"]')} -%define has_ruby_abi 0%{?fedora:%fedora} >= 5 || 0%{?rhel:%rhel} >= 5 || 0%{?centos:%centos} >= 5 +%define has_ruby_abi 0%{?fedora} || 0%{?rhel} >= 5 %define has_ruby_noarch %has_ruby_abi Summary: Ruby module for collecting simple facts about a host operating system Name: facter Version: 1.5.2 Release: 1%{?dist} -License: GPL +License: GPLv2+ Group: System Environment/Base URL: http://reductivelabs.com/projects/facter Source0: http://reductivelabs.com/downloads/facter/%{name}-%{version}.tgz BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) %if %has_ruby_noarch -BuildArchitectures: noarch +BuildArch: noarch %endif Requires: ruby >= 1.8.1 @@ -23,7 +23,7 @@ Requires: ruby(abi) = 1.8 %endif BuildRequires: ruby >= 1.8.1 -%description +%description Ruby module for collecting simple facts about a host Operating system. Some of the facts are preconfigured, such as the hostname and the operating system. Additional facts can be added through simple Ruby scripts @@ -36,7 +36,7 @@ sed -i -e 's@^#!.*$@#! /usr/bin/ruby@' bin/facter %install rm -rf %{buildroot} -mkdir %{buildroot} +mkdir -p %{buildroot} %{__install} -d -m0755 %{buildroot}%{ruby_sitelibdir} %{__install} -d -m0755 %{buildroot}%{ruby_sitelibdir}/facter @@ -60,18 +60,34 @@ rm -rf %{buildroot} %{_bindir}/facter %{ruby_sitelibdir}/facter.rb %{ruby_sitelibdir}/facter -%{ruby_sitelibdir}/facter/util -%{ruby_sitelibdir}/facter/util/plist -%{ruby_sitelibdir}/facter/util/*.rb -%{ruby_sitelibdir}/facter/util/plist/*.rb %doc CHANGELOG COPYING INSTALL LICENSE README %changelog +* Tue Sep 09 2008 Todd Zullinger - 1.5.2-1 +- New version +- Simplify spec file checking for Fedora and RHEL versions + +* Mon Sep 8 2008 David Lutterkort - 1.5.1-1 +- New version + +* Thu Jul 17 2008 David Lutterkort - 1.5.0-3 +- Change 'mkdir' in install to 'mkdir -p' + +* Thu Jul 17 2008 David Lutterkort - 1.5.0-2 +- Remove files that were listed twice in files section + * Mon May 19 2008 James Turnbull - 1.5.0-1 - New version - Added util and plist files +* Mon Sep 24 2007 David Lutterkort - 1.3.8-1 +- Update license tag +- Copy all of lib/ into ruby_sitelibdir + +* Thu Mar 29 2007 David Lutterkort - 1.3.7-1 +- New version + * Fri Jan 19 2007 David Lutterkort - 1.3.6-1 - New version From 7ddea77c2dd469333430a41f5257e0be0de9be29 Mon Sep 17 00:00:00 2001 From: Martin Englund Date: Wed, 12 Nov 2008 15:03:14 +0100 Subject: [PATCH 0287/3753] Fix for #1727 - id fact should not rely on whoami on Solaris Signed-off-by: Martin Englund --- lib/facter/id.rb | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/facter/id.rb b/lib/facter/id.rb index 6157991ede..d62009605f 100644 --- a/lib/facter/id.rb +++ b/lib/facter/id.rb @@ -1,4 +1,15 @@ Facter.add(:id) do - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE SLES Debian Ubuntu Gentoo AIX} + confine :operatingsystem => %w{Linux Fedora RedHat CentOS SuSE SLES Debian Ubuntu Gentoo AIX} setcode "whoami" end + +Facter.add(:id) do + confine :operatingsystem => %w{Solaris} + setcode do + if %x{id} =~ /^uid=\d+\((\S+)\)/ + $1 + else + nil + end + end +end From 0fe4611b9983bc3a36d58a54d2bc30b355199742 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Thu, 27 Nov 2008 00:45:09 +1100 Subject: [PATCH 0288/3753] Added ci namespace and Rake tasks --- CHANGELOG | 2 ++ Rakefile | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index e432110979..c7430548de 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ 1.5.3: + Added ci namespace and Rake tasks + Fixed #1650 - OS X package creation script should be more selective about cleaning out prior versions diff --git a/Rakefile b/Rakefile index c4f449f6a3..0f6d2b8629 100644 --- a/Rakefile +++ b/Rakefile @@ -44,3 +44,22 @@ task :archive do sh "git archive --format=tar --prefix=facter-#{archive}/ HEAD | gzip -c > facter-#{archive}.tgz" end + +namespace :ci do + + desc "Run the CI prep tasks" + task :prep do + require 'rubygems' + gem 'ci_reporter' + require 'ci/reporter/rake/rspec' + require 'ci/reporter/rake/test_unit' + ENV['CI_REPORTS'] = 'results' + end + + desc "Run CI RSpec tests" + task :spec => [:prep, 'ci:setup:rspec'] do + sh "cd spec; rake all; exit 0" + end + +end + From 8247304034ae99c715c5cbd3e78931435e04ba89 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Mon, 1 Dec 2008 13:14:24 +1100 Subject: [PATCH 0289/3753] Fixed errors on unrecognised option in binary --- CHANGELOG | 2 ++ bin/facter | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index c7430548de..07a422c3cd 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ 1.5.3: + Fixed errors on unrecognised option in binary + Added ci namespace and Rake tasks Fixed #1650 - OS X package creation script should be more selective diff --git a/bin/facter b/bin/facter index 60cde6d84b..902bed93cc 100755 --- a/bin/facter +++ b/bin/facter @@ -75,7 +75,8 @@ options = { :yaml => false } -result.each { |opt,arg| +begin + result.each { |opt,arg| case opt when "--version" puts "%s" % Facter.version @@ -101,7 +102,10 @@ result.each { |opt,arg| $stderr.puts "Invalid option '#{opt}'" exit(12) end -} + } +rescue + exit(12) +end names = [] From 85b2a55cb245fbdc381e2f00349a6987d167e945 Mon Sep 17 00:00:00 2001 From: rchanter Date: Tue, 9 Dec 2008 04:45:35 -0700 Subject: [PATCH 0290/3753] minor fix to operatingsystemversion to correctly parse /etc/release on OpenSolaris 2008.11. --- lib/facter/operatingsystemrelease.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index a785db6dd6..addd7f2545 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -76,7 +76,7 @@ Facter.add(:operatingsystemrelease) do confine :operatingsystem => %w{Solaris} setcode do - full_release = File.readlines("/etc/release").to_s.match(/Solaris \w+ [\w\/]+ ([^_]+_[^_]+)/).to_a.last.chomp("wos") + full_release = File.readlines("/etc/release").to_s.match(/Solaris (\w+ )?[\w\/]+ ([^_]+_[^_]+)/).to_a.last.to_s.chomp("wos") if full_release =~ /^s(\d+)\w(_\w\d)+/ $1 + $2 else From 99833a1b5b32e87dc73f66d16fc57ba91c070b13 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Mon, 22 Dec 2008 19:39:18 +1100 Subject: [PATCH 0291/3753] Fixed #1793 - Added more Solaris 10 facts --- CHANGELOG | 2 ++ lib/facter/manufacturer.rb | 11 ++++++++++- lib/facter/util/manufacturer.rb | 18 +++++++++++++----- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 07a422c3cd..5d0cd040fb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ 1.5.3: + Fixed #1793 - Added more Solaris 10 facts + Fixed errors on unrecognised option in binary Added ci namespace and Rake tasks diff --git a/lib/facter/manufacturer.rb b/lib/facter/manufacturer.rb index 1fbc38d868..b217fdc4d1 100644 --- a/lib/facter/manufacturer.rb +++ b/lib/facter/manufacturer.rb @@ -5,6 +5,15 @@ require 'facter/util/manufacturer' -query = { 'System Information' => [ 'Manufacturer:', 'Product Name:' , 'Serial Number:'], 'Chassis Information' => 'Type:'} +query = { + '[Ss]ystem [Ii]nformation' => [ + { 'Manufacturer:' => 'manufacturer' }, + { 'Product(?: Name)?:' => 'productname' }, + { 'Serial Number:' => 'serialnumber' } + ], + '(Chassis Information|system enclosure or chassis)' => [ + { '(?:Chassis )?Type:' => 'type' } + ] +} Facter::Manufacturer.dmi_find_system_info(query) diff --git a/lib/facter/util/manufacturer.rb b/lib/facter/util/manufacturer.rb index 665fa76fa4..657901dd77 100644 --- a/lib/facter/util/manufacturer.rb +++ b/lib/facter/util/manufacturer.rb @@ -3,6 +3,7 @@ module Facter::Manufacturer def self.dmi_find_system_info(name) + splitstr="Handle" case Facter.value(:kernel) when 'Linux' return nil unless FileTest.exists?("/usr/sbin/dmidecode") @@ -16,16 +17,22 @@ def self.dmi_find_system_info(name) return nil unless FileTest.exists?("/usr/pkg/sbin/dmidecode") output=%x{/usr/pkg/sbin/dmidecode 2>/dev/null} + when 'SunOS' + return nil unless FileTest.exists?("/usr/sbin/smbios") + splitstr="ID SIZE TYPE" + output=%x{/usr/sbin/smbios 2>/dev/null} + else return end name.each_pair do |key,v| - v.each do |value| - output.split("Handle").each do |line| - if line =~ /#{key}/ and line =~ /#{value} ([-\w].*)\n*./ + v.each do |v2| + v2.each_pair do |value,facterkey| + output.split(splitstr).each do |line| + if line =~ /#{key}/ and ( line =~ /#{value} 0x\d+ \(([-\w].*)\)\n*./ or line =~ /#{value} ([-\w].*)\n*./ ) result = $1 - Facter.add(value.chomp(':').gsub(' ','')) do - confine :kernel => [ :linux, :freebsd, :netbsd, :openbsd ] + Facter.add(facterkey) do + confine :kernel => [ :linux, :freebsd, :netbsd, :openbsd, :sunos ] setcode do result end @@ -36,4 +43,5 @@ def self.dmi_find_system_info(name) end end end +end From a70184a1bd94a045c5e62b2e6a355da34ddf697f Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Mon, 22 Dec 2008 19:42:03 +1100 Subject: [PATCH 0292/3753] Fixed #1791 - support for virtual fact on Solaris 10 --- CHANGELOG | 2 ++ lib/facter/virtual.rb | 26 +++++++++++++++++--------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 5d0cd040fb..a714e8115a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ 1.5.3: + Fixed #1791 - support for virtual fact on Solaris 10 + Fixed #1793 - Added more Solaris 10 facts Fixed errors on unrecognised option in binary diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 17d6538292..db8dc33465 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -1,5 +1,5 @@ Facter.add("virtual") do - confine :kernel => %w{Linux FreeBSD OpenBSD} + confine :kernel => %w{Linux FreeBSD OpenBSD SunOS} result = "physical" @@ -27,22 +27,30 @@ end if result == "physical" - lspciexists = system "which lspci > /dev/null 2>&1" - if $?.exitstatus == 0 - output = %x{lspci} + path = %x{which lspci 2> /dev/null}.chomp + if path !~ /no lspci/ + output = %x{#{path}} output.each {|p| # --- look for the vmware video card to determine if it is virtual => vmware. # --- 00:0f.0 VGA compatible controller: VMware Inc [VMware SVGA II] PCI Display Adapter result = "vmware" if p =~ /VM[wW]are/ } else - dmidecodeexists = system "which dmidecode > /dev/null 2>&1" - if $?.exitstatus == 0 - outputd = %x{dmidecode} - outputd.each {|pd| + path = %x{which dmidecode 2> /dev/null}.chomp + if path !~ /no dmidecode/ + output = %x{#{path}} + output.each {|pd| result = "vmware" if pd =~ /VMware|Parallels/ } - end + else + path = %x{which prtdiag 2> /dev/null}.chomp + if path !~ /no prtdiag/ + output = %x{#{path}} + output.each {|pd| + result = "vmware" if pd =~ /VMware|Parallels/ + } + end + end end end From e6d987d333d79e11dd8782fad1286d8348419842 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Tue, 30 Dec 2008 18:16:58 -0600 Subject: [PATCH 0293/3753] Fixing #1761 - Solaris no longer uses /etc/release Applying patch by andy. Signed-off-by: Luke Kanies --- lib/facter/operatingsystemrelease.rb | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index addd7f2545..586d104a16 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -76,12 +76,7 @@ Facter.add(:operatingsystemrelease) do confine :operatingsystem => %w{Solaris} setcode do - full_release = File.readlines("/etc/release").to_s.match(/Solaris (\w+ )?[\w\/]+ ([^_]+_[^_]+)/).to_a.last.to_s.chomp("wos") - if full_release =~ /^s(\d+)\w(_\w\d)+/ - $1 + $2 - else - full_release - end + release = Facter::Util::Resolution.exec('uname -v') end end From 01754f6e42222147b751264d5ca7f0e03f250c1c Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Thu, 15 Jan 2009 11:30:56 -0600 Subject: [PATCH 0294/3753] Removing the vendor/ gems. Signed-off-by: Luke Kanies --- spec/spec_helper.rb | 11 +- vendor/gems/mocha-0.5.6/._RELEASE | Bin 176 -> 0 bytes vendor/gems/mocha-0.5.6/._Rakefile | Bin 177 -> 0 bytes vendor/gems/mocha-0.5.6/COPYING | 3 - vendor/gems/mocha-0.5.6/MIT-LICENSE | 7 - vendor/gems/mocha-0.5.6/README | 35 - vendor/gems/mocha-0.5.6/RELEASE | 188 --- vendor/gems/mocha-0.5.6/Rakefile | 149 --- vendor/gems/mocha-0.5.6/examples/._misc.rb | Bin 177 -> 0 bytes vendor/gems/mocha-0.5.6/examples/._mocha.rb | Bin 177 -> 0 bytes vendor/gems/mocha-0.5.6/examples/._stubba.rb | Bin 178 -> 0 bytes vendor/gems/mocha-0.5.6/examples/misc.rb | 44 - vendor/gems/mocha-0.5.6/examples/mocha.rb | 26 - vendor/gems/mocha-0.5.6/examples/stubba.rb | 65 - vendor/gems/mocha-0.5.6/lib/._mocha.rb | Bin 177 -> 0 bytes vendor/gems/mocha-0.5.6/lib/mocha.rb | 19 - .../lib/mocha/._any_instance_method.rb | Bin 177 -> 0 bytes .../mocha-0.5.6/lib/mocha/._auto_verify.rb | Bin 176 -> 0 bytes .../gems/mocha-0.5.6/lib/mocha/._central.rb | Bin 177 -> 0 bytes .../mocha-0.5.6/lib/mocha/._class_method.rb | Bin 178 -> 0 bytes .../mocha-0.5.6/lib/mocha/._deprecation.rb | Bin 177 -> 0 bytes .../mocha-0.5.6/lib/mocha/._expectation.rb | Bin 178 -> 0 bytes .../lib/mocha/._expectation_error.rb | Bin 176 -> 0 bytes .../lib/mocha/._expectation_list.rb | Bin 178 -> 0 bytes .../mocha-0.5.6/lib/mocha/._infinite_range.rb | Bin 177 -> 0 bytes .../gems/mocha-0.5.6/lib/mocha/._inspect.rb | Bin 176 -> 0 bytes .../lib/mocha/._instance_method.rb | Bin 176 -> 0 bytes .../gems/mocha-0.5.6/lib/mocha/._metaclass.rb | Bin 176 -> 0 bytes .../mocha-0.5.6/lib/mocha/._method_matcher.rb | Bin 177 -> 0 bytes .../lib/mocha/._missing_expectation.rb | Bin 177 -> 0 bytes vendor/gems/mocha-0.5.6/lib/mocha/._mock.rb | Bin 178 -> 0 bytes vendor/gems/mocha-0.5.6/lib/mocha/._object.rb | Bin 178 -> 0 bytes .../lib/mocha/._parameter_matchers.rb | Bin 177 -> 0 bytes .../lib/mocha/._parameters_matcher.rb | Bin 177 -> 0 bytes .../lib/mocha/._pretty_parameters.rb | Bin 176 -> 0 bytes .../mocha-0.5.6/lib/mocha/._return_values.rb | Bin 177 -> 0 bytes .../gems/mocha-0.5.6/lib/mocha/._sequence.rb | Bin 178 -> 0 bytes .../lib/mocha/._setup_and_teardown.rb | Bin 176 -> 0 bytes .../lib/mocha/._single_return_value.rb | Bin 178 -> 0 bytes .../mocha-0.5.6/lib/mocha/._standalone.rb | Bin 178 -> 0 bytes .../lib/mocha/._test_case_adapter.rb | Bin 178 -> 0 bytes .../lib/mocha/._yield_parameters.rb | Bin 178 -> 0 bytes .../lib/mocha/any_instance_method.rb | 35 - .../gems/mocha-0.5.6/lib/mocha/auto_verify.rb | 118 -- vendor/gems/mocha-0.5.6/lib/mocha/central.rb | 35 - .../mocha-0.5.6/lib/mocha/class_method.rb | 66 -- .../gems/mocha-0.5.6/lib/mocha/deprecation.rb | 22 - .../mocha-0.5.6/lib/mocha/exception_raiser.rb | 17 - .../gems/mocha-0.5.6/lib/mocha/expectation.rb | 384 ------ .../lib/mocha/expectation_error.rb | 15 - .../mocha-0.5.6/lib/mocha/expectation_list.rb | 46 - .../mocha-0.5.6/lib/mocha/infinite_range.rb | 25 - vendor/gems/mocha-0.5.6/lib/mocha/inspect.rb | 39 - .../mocha-0.5.6/lib/mocha/instance_method.rb | 8 - vendor/gems/mocha-0.5.6/lib/mocha/is_a.rb | 9 - .../gems/mocha-0.5.6/lib/mocha/metaclass.rb | 7 - .../mocha-0.5.6/lib/mocha/method_matcher.rb | 21 - .../lib/mocha/missing_expectation.rb | 17 - vendor/gems/mocha-0.5.6/lib/mocha/mock.rb | 202 ---- .../mocha-0.5.6/lib/mocha/multiple_yields.rb | 20 - .../gems/mocha-0.5.6/lib/mocha/no_yields.rb | 11 - vendor/gems/mocha-0.5.6/lib/mocha/object.rb | 110 -- .../lib/mocha/parameter_matchers.rb | 25 - .../lib/mocha/parameter_matchers/._all_of.rb | Bin 178 -> 0 bytes .../lib/mocha/parameter_matchers/._any_of.rb | Bin 178 -> 0 bytes .../parameter_matchers/._any_parameters.rb | Bin 178 -> 0 bytes .../mocha/parameter_matchers/._anything.rb | Bin 176 -> 0 bytes .../lib/mocha/parameter_matchers/._base.rb | Bin 176 -> 0 bytes .../lib/mocha/parameter_matchers/._equals.rb | Bin 176 -> 0 bytes .../mocha/parameter_matchers/._has_entries.rb | Bin 178 -> 0 bytes .../mocha/parameter_matchers/._has_entry.rb | Bin 178 -> 0 bytes .../lib/mocha/parameter_matchers/._has_key.rb | Bin 176 -> 0 bytes .../mocha/parameter_matchers/._has_value.rb | Bin 178 -> 0 bytes .../mocha/parameter_matchers/._includes.rb | Bin 178 -> 0 bytes .../mocha/parameter_matchers/._instance_of.rb | Bin 176 -> 0 bytes .../lib/mocha/parameter_matchers/._is_a.rb | Bin 177 -> 0 bytes .../lib/mocha/parameter_matchers/._kind_of.rb | Bin 176 -> 0 bytes .../lib/mocha/parameter_matchers/._not.rb | Bin 177 -> 0 bytes .../lib/mocha/parameter_matchers/._object.rb | Bin 176 -> 0 bytes .../mocha/parameter_matchers/._optionally.rb | Bin 177 -> 0 bytes .../parameter_matchers/._regexp_matches.rb | Bin 176 -> 0 bytes .../lib/mocha/parameter_matchers/all_of.rb | 42 - .../lib/mocha/parameter_matchers/any_of.rb | 47 - .../parameter_matchers/any_parameters.rb | 40 - .../lib/mocha/parameter_matchers/anything.rb | 33 - .../lib/mocha/parameter_matchers/base.rb | 15 - .../lib/mocha/parameter_matchers/equals.rb | 42 - .../mocha/parameter_matchers/has_entries.rb | 42 - .../lib/mocha/parameter_matchers/has_entry.rb | 55 - .../lib/mocha/parameter_matchers/has_key.rb | 42 - .../lib/mocha/parameter_matchers/has_value.rb | 42 - .../lib/mocha/parameter_matchers/includes.rb | 40 - .../mocha/parameter_matchers/instance_of.rb | 42 - .../lib/mocha/parameter_matchers/is_a.rb | 42 - .../lib/mocha/parameter_matchers/kind_of.rb | 42 - .../lib/mocha/parameter_matchers/not.rb | 42 - .../lib/mocha/parameter_matchers/object.rb | 9 - .../mocha/parameter_matchers/optionally.rb | 33 - .../parameter_matchers/regexp_matches.rb | 43 - .../lib/mocha/parameters_matcher.rb | 37 - .../lib/mocha/pretty_parameters.rb | 28 - .../mocha-0.5.6/lib/mocha/return_values.rb | 34 - vendor/gems/mocha-0.5.6/lib/mocha/sequence.rb | 42 - .../lib/mocha/setup_and_teardown.rb | 23 - .../lib/mocha/single_return_value.rb | 24 - .../mocha-0.5.6/lib/mocha/single_yield.rb | 18 - .../gems/mocha-0.5.6/lib/mocha/standalone.rb | 32 - vendor/gems/mocha-0.5.6/lib/mocha/stub.rb | 18 - .../lib/mocha/test_case_adapter.rb | 49 - .../mocha-0.5.6/lib/mocha/yield_parameters.rb | 31 - .../gems/mocha-0.5.6/lib/mocha_standalone.rb | 2 - vendor/gems/mocha-0.5.6/lib/stubba.rb | 2 - .../test/._deprecation_disabler.rb | Bin 177 -> 0 bytes .../mocha-0.5.6/test/._execution_point.rb | Bin 176 -> 0 bytes .../gems/mocha-0.5.6/test/._method_definer.rb | Bin 177 -> 0 bytes vendor/gems/mocha-0.5.6/test/._test_helper.rb | Bin 177 -> 0 bytes vendor/gems/mocha-0.5.6/test/._test_runner.rb | Bin 178 -> 0 bytes ...pected_invocation_count_acceptance_test.rb | Bin 176 -> 0 bytes .../acceptance/._mocha_acceptance_test.rb | Bin 177 -> 0 bytes ..._with_initializer_block_acceptance_test.rb | Bin 177 -> 0 bytes ...mocked_methods_dispatch_acceptance_test.rb | Bin 177 -> 0 bytes .../._optional_parameters_acceptance_test.rb | Bin 177 -> 0 bytes .../._parameter_matcher_acceptance_test.rb | Bin 178 -> 0 bytes .../._partial_mocks_acceptance_test.rb | Bin 177 -> 0 bytes .../acceptance/._sequence_acceptance_test.rb | Bin 178 -> 0 bytes .../._standalone_acceptance_test.rb | Bin 176 -> 0 bytes .../acceptance/._stubba_acceptance_test.rb | Bin 178 -> 0 bytes ...pected_invocation_count_acceptance_test.rb | 187 --- .../test/acceptance/mocha_acceptance_test.rb | 98 -- ..._with_initializer_block_acceptance_test.rb | 44 - ...mocked_methods_dispatch_acceptance_test.rb | 71 -- .../optional_parameters_acceptance_test.rb | 63 - .../parameter_matcher_acceptance_test.rb | 117 -- .../partial_mocks_acceptance_test.rb | 40 - .../acceptance/sequence_acceptance_test.rb | 179 --- .../acceptance/standalone_acceptance_test.rb | 131 --- .../test/acceptance/stubba_acceptance_test.rb | 102 -- .../test/active_record_test_case.rb | 36 - .../mocha-0.5.6/test/deprecation_disabler.rb | 15 - .../gems/mocha-0.5.6/test/execution_point.rb | 34 - .../._mocha_test_result_integration_test.rb | Bin 178 -> 0 bytes .../integration/._stubba_integration_test.rb | Bin 178 -> 0 bytes .../._stubba_test_result_integration_test.rb | Bin 178 -> 0 bytes .../mocha_test_result_integration_test.rb | 105 -- .../integration/stubba_integration_test.rb | 89 -- .../stubba_test_result_integration_test.rb | 85 -- .../gems/mocha-0.5.6/test/method_definer.rb | 18 - vendor/gems/mocha-0.5.6/test/test_helper.rb | 12 - vendor/gems/mocha-0.5.6/test/test_runner.rb | 31 - .../test/unit/._any_instance_method_test.rb | Bin 176 -> 0 bytes .../test/unit/._auto_verify_test.rb | Bin 177 -> 0 bytes .../mocha-0.5.6/test/unit/._central_test.rb | Bin 177 -> 0 bytes .../test/unit/._class_method_test.rb | Bin 178 -> 0 bytes .../test/unit/._expectation_error_test.rb | Bin 178 -> 0 bytes .../test/unit/._expectation_list_test.rb | Bin 177 -> 0 bytes .../test/unit/._expectation_test.rb | Bin 179 -> 0 bytes .../test/unit/._hash_inspect_test.rb | Bin 177 -> 0 bytes .../test/unit/._method_matcher_test.rb | Bin 177 -> 0 bytes .../test/unit/._missing_expectation_test.rb | Bin 178 -> 0 bytes .../gems/mocha-0.5.6/test/unit/._mock_test.rb | Bin 177 -> 0 bytes .../test/unit/._object_inspect_test.rb | Bin 178 -> 0 bytes .../test/unit/._parameters_matcher_test.rb | Bin 176 -> 0 bytes .../mocha-0.5.6/test/unit/._sequence_test.rb | Bin 178 -> 0 bytes .../test/unit/any_instance_method_test.rb | 126 -- .../test/unit/array_inspect_test.rb | 16 - .../mocha-0.5.6/test/unit/auto_verify_test.rb | 129 -- .../mocha-0.5.6/test/unit/central_test.rb | 124 -- .../test/unit/class_method_test.rb | 200 ---- .../test/unit/date_time_inspect_test.rb | 21 - .../test/unit/expectation_error_test.rb | 24 - .../test/unit/expectation_list_test.rb | 75 -- .../test/unit/expectation_raiser_test.rb | 28 - .../mocha-0.5.6/test/unit/expectation_test.rb | 483 -------- .../test/unit/hash_inspect_test.rb | 16 - .../test/unit/infinite_range_test.rb | 53 - .../mocha-0.5.6/test/unit/metaclass_test.rb | 22 - .../test/unit/method_matcher_test.rb | 23 - .../test/unit/missing_expectation_test.rb | 42 - .../gems/mocha-0.5.6/test/unit/mock_test.rb | 323 ----- .../test/unit/multiple_yields_test.rb | 18 - .../mocha-0.5.6/test/unit/no_yield_test.rb | 18 - .../test/unit/object_inspect_test.rb | 37 - .../gems/mocha-0.5.6/test/unit/object_test.rb | 165 --- .../unit/parameter_matchers/._all_of_test.rb | Bin 178 -> 0 bytes .../unit/parameter_matchers/._any_of_test.rb | Bin 177 -> 0 bytes .../parameter_matchers/._anything_test.rb | Bin 177 -> 0 bytes .../parameter_matchers/._has_entries_test.rb | Bin 177 -> 0 bytes .../parameter_matchers/._has_entry_test.rb | Bin 178 -> 0 bytes .../unit/parameter_matchers/._has_key_test.rb | Bin 178 -> 0 bytes .../parameter_matchers/._has_value_test.rb | Bin 177 -> 0 bytes .../parameter_matchers/._includes_test.rb | Bin 177 -> 0 bytes .../parameter_matchers/._instance_of_test.rb | Bin 178 -> 0 bytes .../unit/parameter_matchers/._is_a_test.rb | Bin 177 -> 0 bytes .../unit/parameter_matchers/._kind_of_test.rb | Bin 178 -> 0 bytes .../unit/parameter_matchers/._not_test.rb | Bin 177 -> 0 bytes .../._regexp_matches_test.rb | Bin 178 -> 0 bytes .../unit/parameter_matchers/._stub_matcher.rb | Bin 178 -> 0 bytes .../unit/parameter_matchers/all_of_test.rb | 26 - .../unit/parameter_matchers/any_of_test.rb | 26 - .../unit/parameter_matchers/anything_test.rb | 21 - .../parameter_matchers/has_entries_test.rb | 30 - .../unit/parameter_matchers/has_entry_test.rb | 40 - .../unit/parameter_matchers/has_key_test.rb | 25 - .../unit/parameter_matchers/has_value_test.rb | 25 - .../unit/parameter_matchers/includes_test.rb | 25 - .../parameter_matchers/instance_of_test.rb | 25 - .../test/unit/parameter_matchers/is_a_test.rb | 25 - .../unit/parameter_matchers/kind_of_test.rb | 25 - .../test/unit/parameter_matchers/not_test.rb | 26 - .../parameter_matchers/regexp_matches_test.rb | 25 - .../unit/parameter_matchers/stub_matcher.rb | 23 - .../test/unit/parameters_matcher_test.rb | 121 -- .../test/unit/return_values_test.rb | 63 - .../mocha-0.5.6/test/unit/sequence_test.rb | 104 -- .../test/unit/setup_and_teardown_test.rb | 76 -- .../test/unit/single_return_value_test.rb | 33 - .../test/unit/single_yield_test.rb | 18 - .../test/unit/string_inspect_test.rb | 11 - .../gems/mocha-0.5.6/test/unit/stub_test.rb | 24 - .../test/unit/yield_parameters_test.rb | 93 -- vendor/gems/rspec/CHANGES | 1045 ----------------- vendor/gems/rspec/MIT-LICENSE | 20 - vendor/gems/rspec/README | 71 -- vendor/gems/rspec/Rakefile | 279 ----- vendor/gems/rspec/TODO | 2 - vendor/gems/rspec/UPGRADE | 31 - vendor/gems/rspec/bin/spec | 4 - vendor/gems/rspec/bin/spec_translator | 8 - .../pure/autogenerated_docstrings_example.rb | 19 - .../examples/pure/before_and_after_example.rb | 40 - .../rspec/examples/pure/behave_as_example.rb | 45 - .../pure/custom_expectation_matchers.rb | 54 - .../rspec/examples/pure/custom_formatter.rb | 12 - .../gems/rspec/examples/pure/dynamic_spec.rb | 9 - .../gems/rspec/examples/pure/file_accessor.rb | 19 - .../rspec/examples/pure/file_accessor_spec.rb | 38 - .../gems/rspec/examples/pure/greeter_spec.rb | 31 - .../examples/pure/helper_method_example.rb | 14 - .../gems/rspec/examples/pure/io_processor.rb | 8 - .../rspec/examples/pure/io_processor_spec.rb | 21 - .../gems/rspec/examples/pure/legacy_spec.rb | 11 - .../rspec/examples/pure/mocking_example.rb | 27 - .../pure/multi_threaded_behaviour_runner.rb | 28 - .../examples/pure/nested_classes_example.rb | 36 - .../examples/pure/partial_mock_example.rb | 28 - .../rspec/examples/pure/pending_example.rb | 20 - .../rspec/examples/pure/predicate_example.rb | 27 - vendor/gems/rspec/examples/pure/priority.txt | 1 - .../pure/shared_example_group_example.rb | 81 -- .../examples/pure/shared_stack_examples.rb | 38 - .../gems/rspec/examples/pure/spec_helper.rb | 3 - vendor/gems/rspec/examples/pure/stack.rb | 36 - vendor/gems/rspec/examples/pure/stack_spec.rb | 63 - .../stack_spec_with_nested_example_groups.rb | 67 -- .../rspec/examples/pure/stubbing_example.rb | 69 -- vendor/gems/rspec/examples/stories/adder.rb | 13 - vendor/gems/rspec/examples/stories/addition | 34 - .../gems/rspec/examples/stories/addition.rb | 9 - .../gems/rspec/examples/stories/calculator.rb | 65 - .../examples/stories/game-of-life/README.txt | 21 - .../game-of-life/behaviour/everything.rb | 6 - .../behaviour/examples/examples.rb | 3 - .../behaviour/examples/game_behaviour.rb | 35 - .../behaviour/examples/grid_behaviour.rb | 66 -- .../CellsWithLessThanTwoNeighboursDie.story | 21 - .../CellsWithMoreThanThreeNeighboursDie.story | 21 - ...SpacesWithThreeNeighboursCreateACell.story | 42 - .../behaviour/stories/ICanCreateACell.story | 42 - .../behaviour/stories/ICanKillACell.story | 17 - .../behaviour/stories/TheGridWraps.story | 53 - .../behaviour/stories/create_a_cell.rb | 52 - .../game-of-life/behaviour/stories/helper.rb | 6 - .../behaviour/stories/kill_a_cell.rb | 26 - .../game-of-life/behaviour/stories/steps.rb | 5 - .../game-of-life/behaviour/stories/stories.rb | 3 - .../behaviour/stories/stories.txt | 22 - .../examples/stories/game-of-life/life.rb | 3 - .../stories/game-of-life/life/game.rb | 23 - .../stories/game-of-life/life/grid.rb | 43 - vendor/gems/rspec/examples/stories/helper.rb | 9 - .../examples/stories/steps/addition_steps.rb | 18 - vendor/gems/rspec/failing_examples/README.txt | 7 - .../rspec/failing_examples/diffing_spec.rb | 36 - ...ailing_autogenerated_docstrings_example.rb | 19 - .../failing_examples/failure_in_setup.rb | 10 - .../failing_examples/failure_in_teardown.rb | 10 - .../rspec/failing_examples/mocking_example.rb | 40 - .../failing_examples/mocking_with_flexmock.rb | 26 - .../failing_examples/mocking_with_mocha.rb | 25 - .../rspec/failing_examples/mocking_with_rr.rb | 27 - .../failing_examples/partial_mock_example.rb | 20 - .../failing_examples/predicate_example.rb | 29 - .../rspec/failing_examples/raising_example.rb | 47 - .../rspec/failing_examples/spec_helper.rb | 3 - .../failing_examples/syntax_error_example.rb | 7 - .../gems/rspec/failing_examples/team_spec.rb | 44 - .../failing_examples/timeout_behaviour.rb | 7 - vendor/gems/rspec/lib/autotest/discover.rb | 3 - vendor/gems/rspec/lib/autotest/rspec.rb | 74 -- vendor/gems/rspec/lib/spec.rb | 30 - vendor/gems/rspec/lib/spec/example.rb | 12 - .../rspec/lib/spec/example/configuration.rb | 144 --- vendor/gems/rspec/lib/spec/example/errors.rb | 9 - .../rspec/lib/spec/example/example_group.rb | 16 - .../lib/spec/example/example_group_factory.rb | 62 - .../lib/spec/example/example_group_methods.rb | 424 ------- .../rspec/lib/spec/example/example_matcher.rb | 42 - .../rspec/lib/spec/example/example_methods.rb | 106 -- .../lib/spec/example/module_reopening_fix.rb | 21 - vendor/gems/rspec/lib/spec/example/pending.rb | 18 - .../lib/spec/example/shared_example_group.rb | 58 - vendor/gems/rspec/lib/spec/expectations.rb | 56 - .../lib/spec/expectations/differs/default.rb | 66 -- .../rspec/lib/spec/expectations/errors.rb | 12 - .../rspec/lib/spec/expectations/extensions.rb | 2 - .../spec/expectations/extensions/object.rb | 71 -- .../extensions/string_and_symbol.rb | 17 - .../rspec/lib/spec/expectations/handler.rb | 52 - vendor/gems/rspec/lib/spec/extensions.rb | 3 - .../gems/rspec/lib/spec/extensions/class.rb | 24 - vendor/gems/rspec/lib/spec/extensions/main.rb | 102 -- .../gems/rspec/lib/spec/extensions/object.rb | 10 - vendor/gems/rspec/lib/spec/interop/test.rb | 12 - .../lib/spec/interop/test/unit/autorunner.rb | 6 - .../lib/spec/interop/test/unit/testcase.rb | 61 - .../lib/spec/interop/test/unit/testresult.rb | 6 - .../interop/test/unit/testsuite_adapter.rb | 34 - .../test/unit/ui/console/testrunner.rb | 61 - vendor/gems/rspec/lib/spec/matchers.rb | 156 --- vendor/gems/rspec/lib/spec/matchers/be.rb | 224 ---- .../gems/rspec/lib/spec/matchers/be_close.rb | 37 - vendor/gems/rspec/lib/spec/matchers/change.rb | 144 --- vendor/gems/rspec/lib/spec/matchers/eql.rb | 43 - vendor/gems/rspec/lib/spec/matchers/equal.rb | 43 - vendor/gems/rspec/lib/spec/matchers/exist.rb | 17 - vendor/gems/rspec/lib/spec/matchers/has.rb | 44 - vendor/gems/rspec/lib/spec/matchers/have.rb | 145 --- .../gems/rspec/lib/spec/matchers/include.rb | 70 -- vendor/gems/rspec/lib/spec/matchers/match.rb | 41 - .../lib/spec/matchers/operator_matcher.rb | 73 -- .../rspec/lib/spec/matchers/raise_error.rb | 109 -- .../rspec/lib/spec/matchers/respond_to.rb | 45 - .../gems/rspec/lib/spec/matchers/satisfy.rb | 47 - .../rspec/lib/spec/matchers/simple_matcher.rb | 29 - .../rspec/lib/spec/matchers/throw_symbol.rb | 74 -- vendor/gems/rspec/lib/spec/mocks.rb | 211 ---- .../mocks/argument_constraint_matchers.rb | 27 - .../lib/spec/mocks/argument_expectation.rb | 183 --- .../rspec/lib/spec/mocks/error_generator.rb | 84 -- vendor/gems/rspec/lib/spec/mocks/errors.rb | 10 - .../rspec/lib/spec/mocks/extensions/object.rb | 3 - .../lib/spec/mocks/message_expectation.rb | 267 ----- vendor/gems/rspec/lib/spec/mocks/methods.rb | 39 - vendor/gems/rspec/lib/spec/mocks/mock.rb | 50 - .../gems/rspec/lib/spec/mocks/order_group.rb | 29 - vendor/gems/rspec/lib/spec/mocks/proxy.rb | 170 --- vendor/gems/rspec/lib/spec/mocks/space.rb | 28 - .../gems/rspec/lib/spec/mocks/spec_methods.rb | 38 - vendor/gems/rspec/lib/spec/rake/spectask.rb | 235 ---- .../gems/rspec/lib/spec/rake/verify_rcov.rb | 52 - vendor/gems/rspec/lib/spec/runner.rb | 202 ---- .../lib/spec/runner/backtrace_tweaker.rb | 57 - .../spec/runner/class_and_arguments_parser.rb | 16 - .../rspec/lib/spec/runner/command_line.rb | 28 - .../rspec/lib/spec/runner/drb_command_line.rb | 20 - .../lib/spec/runner/example_group_runner.rb | 59 - .../spec/runner/formatter/base_formatter.rb | 78 -- .../runner/formatter/base_text_formatter.rb | 130 -- .../failing_example_groups_formatter.rb | 31 - .../formatter/failing_examples_formatter.rb | 20 - .../spec/runner/formatter/html_formatter.rb | 333 ------ .../runner/formatter/profile_formatter.rb | 47 - .../formatter/progress_bar_formatter.rb | 30 - .../runner/formatter/snippet_extractor.rb | 52 - .../runner/formatter/specdoc_formatter.rb | 39 - .../runner/formatter/story/html_formatter.rb | 128 -- .../formatter/story/plain_text_formatter.rb | 131 --- .../runner/formatter/text_mate_formatter.rb | 16 - .../rspec/lib/spec/runner/heckle_runner.rb | 72 -- .../spec/runner/heckle_runner_unsupported.rb | 10 - .../rspec/lib/spec/runner/option_parser.rb | 201 ---- vendor/gems/rspec/lib/spec/runner/options.rb | 286 ----- vendor/gems/rspec/lib/spec/runner/reporter.rb | 143 --- .../gems/rspec/lib/spec/runner/spec_parser.rb | 71 -- vendor/gems/rspec/lib/spec/story.rb | 10 - .../gems/rspec/lib/spec/story/extensions.rb | 3 - .../rspec/lib/spec/story/extensions/main.rb | 86 -- .../rspec/lib/spec/story/extensions/regexp.rb | 9 - .../rspec/lib/spec/story/extensions/string.rb | 9 - .../rspec/lib/spec/story/given_scenario.rb | 14 - vendor/gems/rspec/lib/spec/story/runner.rb | 56 - .../story/runner/plain_text_story_runner.rb | 48 - .../spec/story/runner/scenario_collector.rb | 18 - .../lib/spec/story/runner/scenario_runner.rb | 46 - .../lib/spec/story/runner/story_mediator.rb | 123 -- .../lib/spec/story/runner/story_parser.rb | 227 ---- .../lib/spec/story/runner/story_runner.rb | 68 -- vendor/gems/rspec/lib/spec/story/scenario.rb | 14 - vendor/gems/rspec/lib/spec/story/step.rb | 58 - .../gems/rspec/lib/spec/story/step_group.rb | 89 -- .../gems/rspec/lib/spec/story/step_mother.rb | 37 - vendor/gems/rspec/lib/spec/story/story.rb | 42 - vendor/gems/rspec/lib/spec/story/world.rb | 125 -- vendor/gems/rspec/lib/spec/translator.rb | 114 -- vendor/gems/rspec/lib/spec/version.rb | 22 - .../rspec/plugins/mock_frameworks/flexmock.rb | 23 - .../rspec/plugins/mock_frameworks/mocha.rb | 19 - .../gems/rspec/plugins/mock_frameworks/rr.rb | 21 - .../rspec/plugins/mock_frameworks/rspec.rb | 18 - .../gems/rspec/pre_commit/lib/pre_commit.rb | 4 - .../rspec/pre_commit/lib/pre_commit/core.rb | 50 - .../pre_commit/lib/pre_commit/pre_commit.rb | 54 - .../rspec/pre_commit/lib/pre_commit/rspec.rb | 111 -- .../lib/pre_commit/rspec_on_rails.rb | 313 ----- .../spec/pre_commit/pre_commit_spec.rb | 15 - .../spec/pre_commit/rspec_on_rails_spec.rb | 36 - .../gems/rspec/pre_commit/spec/spec_helper.rb | 3 - .../gems/rspec/pre_commit/spec/spec_suite.rb | 11 - vendor/gems/rspec/rake_tasks/examples.rake | 7 - .../rspec/rake_tasks/examples_with_rcov.rake | 9 - .../failing_examples_with_html.rake | 9 - vendor/gems/rspec/rake_tasks/verify_rcov.rake | 7 - vendor/gems/rspec/spec/README.jruby | 15 - .../gems/rspec/spec/autotest/discover_spec.rb | 19 - vendor/gems/rspec/spec/autotest/rspec_spec.rb | 195 --- vendor/gems/rspec/spec/autotest_helper.rb | 6 - vendor/gems/rspec/spec/autotest_matchers.rb | 47 - vendor/gems/rspec/spec/rspec_suite.rb | 7 - vendor/gems/rspec/spec/ruby_forker.rb | 13 - vendor/gems/rspec/spec/spec.opts | 6 - .../spec/spec/example/configuration_spec.rb | 282 ----- .../example_group_class_definition_spec.rb | 48 - .../example/example_group_factory_spec.rb | 129 -- .../example/example_group_methods_spec.rb | 489 -------- .../spec/spec/example/example_group_spec.rb | 711 ----------- .../spec/spec/example/example_matcher_spec.rb | 96 -- .../spec/spec/example/example_methods_spec.rb | 104 -- .../spec/spec/example/example_runner_spec.rb | 194 --- .../rspec/spec/spec/example/example_spec.rb | 53 - .../spec/example/nested_example_group_spec.rb | 59 - .../spec/spec/example/pending_module_spec.rb | 31 - .../spec/example/predicate_matcher_spec.rb | 21 - .../spec/example/shared_example_group_spec.rb | 265 ----- .../example/subclassing_example_group_spec.rb | 25 - .../spec/expectations/differs/default_spec.rb | 109 -- .../expectations/extensions/object_spec.rb | 107 -- .../spec/spec/expectations/fail_with_spec.rb | 71 -- .../rspec/spec/spec/extensions/main_spec.rb | 76 -- .../test/unit/resources/spec_that_fails.rb | 10 - .../test/unit/resources/spec_that_passes.rb | 10 - .../test/unit/resources/spec_with_errors.rb | 10 - .../unit/resources/test_case_that_fails.rb | 10 - .../unit/resources/test_case_that_passes.rb | 10 - .../unit/resources/test_case_with_errors.rb | 10 - .../testsuite_adapter_spec_with_test_unit.rb | 38 - .../spec/spec/interop/test/unit/spec_spec.rb | 45 - .../test/unit/test_unit_spec_helper.rb | 14 - .../spec/interop/test/unit/testcase_spec.rb | 45 - .../test/unit/testsuite_adapter_spec.rb | 9 - .../rspec/spec/spec/matchers/be_close_spec.rb | 39 - .../gems/rspec/spec/spec/matchers/be_spec.rb | 224 ---- .../rspec/spec/spec/matchers/change_spec.rb | 319 ----- .../matchers/description_generation_spec.rb | 153 --- .../gems/rspec/spec/spec/matchers/eql_spec.rb | 28 - .../rspec/spec/spec/matchers/equal_spec.rb | 28 - .../rspec/spec/spec/matchers/exist_spec.rb | 57 - .../rspec/spec/spec/matchers/handler_spec.rb | 129 -- .../gems/rspec/spec/spec/matchers/has_spec.rb | 37 - .../rspec/spec/spec/matchers/have_spec.rb | 291 ----- .../rspec/spec/spec/matchers/include_spec.rb | 45 - .../rspec/spec/spec/matchers/match_spec.rb | 37 - .../spec/matchers/matcher_methods_spec.rb | 78 -- .../matchers/mock_constraint_matchers_spec.rb | 24 - .../spec/matchers/operator_matcher_spec.rb | 158 --- .../spec/spec/matchers/raise_error_spec.rb | 191 --- .../spec/spec/matchers/respond_to_spec.rb | 54 - .../rspec/spec/spec/matchers/satisfy_spec.rb | 36 - .../spec/spec/matchers/simple_matcher_spec.rb | 31 - .../spec/spec/matchers/throw_symbol_spec.rb | 54 - .../spec/mocks/any_number_of_times_spec.rb | 29 - .../spec/mocks/argument_expectation_spec.rb | 23 - .../rspec/spec/spec/mocks/at_least_spec.rb | 97 -- .../rspec/spec/spec/mocks/at_most_spec.rb | 93 -- .../spec/spec/mocks/bug_report_10260_spec.rb | 8 - .../rspec/spec/spec/mocks/bug_report_10263.rb | 24 - .../spec/spec/mocks/bug_report_11545_spec.rb | 31 - .../spec/spec/mocks/bug_report_15719_spec.rb | 30 - .../spec/spec/mocks/bug_report_7611_spec.rb | 19 - .../spec/spec/mocks/bug_report_7805_spec.rb | 22 - .../spec/spec/mocks/bug_report_8165_spec.rb | 31 - .../spec/spec/mocks/bug_report_8302_spec.rb | 26 - .../failing_mock_argument_constraints_spec.rb | 115 -- .../spec/spec/mocks/mock_ordering_spec.rb | 84 -- .../rspec/spec/spec/mocks/mock_space_spec.rb | 54 - .../gems/rspec/spec/spec/mocks/mock_spec.rb | 475 -------- .../spec/mocks/multiple_return_value_spec.rb | 113 -- .../spec/spec/mocks/null_object_mock_spec.rb | 40 - .../rspec/spec/spec/mocks/once_counts_spec.rb | 53 - .../spec/spec/mocks/options_hash_spec.rb | 45 - .../spec/spec/mocks/partial_mock_spec.rb | 106 -- .../partial_mock_using_mocks_directly_spec.rb | 66 -- .../passing_mock_argument_constraints_spec.rb | 154 --- .../spec/spec/mocks/precise_counts_spec.rb | 52 - .../spec/spec/mocks/record_messages_spec.rb | 26 - .../gems/rspec/spec/spec/mocks/stub_spec.rb | 181 --- .../spec/spec/mocks/twice_counts_spec.rb | 67 -- .../rspec/spec/spec/package/bin_spec_spec.rb | 14 - .../runner/class_and_argument_parser_spec.rb | 23 - .../spec/spec/runner/command_line_spec.rb | 147 --- .../spec/spec/runner/drb_command_line_spec.rb | 92 -- .../rspec/spec/spec/runner/empty_file.txt | 0 .../gems/rspec/spec/spec/runner/examples.txt | 2 - .../spec/runner/execution_context_spec.rb | 31 - vendor/gems/rspec/spec/spec/runner/failed.txt | 3 - .../failing_example_groups_formatter_spec.rb | 44 - .../failing_examples_formatter_spec.rb | 33 - .../formatter/html_formatted-1.8.4.html | 365 ------ .../formatter/html_formatted-1.8.5-jruby.html | 387 ------ .../formatter/html_formatted-1.8.5.html | 371 ------ .../formatter/html_formatted-1.8.6-jruby.html | 381 ------ .../formatter/html_formatted-1.8.6.html | 365 ------ .../runner/formatter/html_formatter_spec.rb | 66 -- .../formatter/profile_formatter_spec.rb | 65 - .../formatter/progress_bar_formatter_spec.rb | 127 -- .../formatter/snippet_extractor_spec.rb | 18 - .../formatter/spec_mate_formatter_spec.rb | 103 -- .../formatter/specdoc_formatter_spec.rb | 126 -- .../formatter/story/html_formatter_spec.rb | 61 - .../story/plain_text_formatter_spec.rb | 335 ------ .../formatter/text_mate_formatted-1.8.4.html | 365 ------ .../formatter/text_mate_formatted-1.8.6.html | 365 ------ .../spec/spec/runner/heckle_runner_spec.rb | 78 -- .../rspec/spec/spec/runner/heckler_spec.rb | 13 - .../runner/noisy_backtrace_tweaker_spec.rb | 45 - .../spec/spec/runner/option_parser_spec.rb | 378 ------ .../rspec/spec/spec/runner/options_spec.rb | 364 ------ .../spec/runner/output_one_time_fixture.rb | 7 - .../runner/output_one_time_fixture_runner.rb | 8 - .../spec/spec/runner/output_one_time_spec.rb | 16 - .../runner/quiet_backtrace_tweaker_spec.rb | 56 - .../rspec/spec/spec/runner/reporter_spec.rb | 189 --- .../rspec/spec/spec/runner/resources/a_bar.rb | 0 .../rspec/spec/spec/runner/resources/a_foo.rb | 0 .../spec/spec/runner/resources/a_spec.rb | 1 - vendor/gems/rspec/spec/spec/runner/spec.opts | 2 - .../runner/spec_parser/spec_parser_fixture.rb | 70 -- .../spec/spec/runner/spec_parser_spec.rb | 79 -- .../rspec/spec/spec/runner/spec_spaced.opts | 2 - vendor/gems/rspec/spec/spec/runner_spec.rb | 11 - vendor/gems/rspec/spec/spec/spec_classes.rb | 133 --- vendor/gems/rspec/spec/spec/story/builders.rb | 46 - .../spec/spec/story/extensions/main_spec.rb | 161 --- .../rspec/spec/spec/story/extensions_spec.rb | 14 - .../spec/spec/story/given_scenario_spec.rb | 27 - .../runner/plain_text_story_runner_spec.rb | 92 -- .../story/runner/scenario_collector_spec.rb | 27 - .../spec/story/runner/scenario_runner_spec.rb | 142 --- .../spec/story/runner/story_mediator_spec.rb | 133 --- .../spec/story/runner/story_parser_spec.rb | 384 ------ .../spec/story/runner/story_runner_spec.rb | 256 ---- .../gems/rspec/spec/spec/story/runner_spec.rb | 106 -- .../rspec/spec/spec/story/scenario_spec.rb | 20 - .../rspec/spec/spec/story/step_group_spec.rb | 157 --- .../rspec/spec/spec/story/step_mother_spec.rb | 72 -- .../gems/rspec/spec/spec/story/step_spec.rb | 200 ---- .../rspec/spec/spec/story/story_helper.rb | 2 - .../gems/rspec/spec/spec/story/story_spec.rb | 86 -- .../gems/rspec/spec/spec/story/world_spec.rb | 423 ------- .../gems/rspec/spec/spec/translator_spec.rb | 265 ----- vendor/gems/rspec/spec/spec_helper.rb | 103 -- vendor/gems/rspec/stories/all.rb | 5 - .../example_groups/autogenerated_docstrings | 45 - .../example_group_with_should_methods | 17 - .../stories/example_groups/nested_groups | 17 - .../gems/rspec/stories/example_groups/output | 25 - .../rspec/stories/example_groups/stories.rb | 7 - vendor/gems/rspec/stories/helper.rb | 6 - .../interop/examples_and_tests_together | 30 - vendor/gems/rspec/stories/interop/stories.rb | 7 - .../interop/test_case_with_should_methods | 17 - .../gems/rspec/stories/pending_stories/README | 3 - .../stories/resources/helpers/cmdline.rb | 9 - .../stories/resources/helpers/story_helper.rb | 16 - .../stories/resources/matchers/smart_match.rb | 37 - .../spec/example_group_with_should_methods.rb | 12 - .../stories/resources/spec/simple_spec.rb | 8 - .../stories/resources/steps/running_rspec.rb | 50 - .../resources/stories/failing_story.rb | 15 - .../resources/test/spec_and_test_together.rb | 57 - .../test/test_case_with_should_methods.rb | 30 - 590 files changed, 1 insertion(+), 34886 deletions(-) delete mode 100644 vendor/gems/mocha-0.5.6/._RELEASE delete mode 100644 vendor/gems/mocha-0.5.6/._Rakefile delete mode 100644 vendor/gems/mocha-0.5.6/COPYING delete mode 100644 vendor/gems/mocha-0.5.6/MIT-LICENSE delete mode 100644 vendor/gems/mocha-0.5.6/README delete mode 100644 vendor/gems/mocha-0.5.6/RELEASE delete mode 100644 vendor/gems/mocha-0.5.6/Rakefile delete mode 100644 vendor/gems/mocha-0.5.6/examples/._misc.rb delete mode 100644 vendor/gems/mocha-0.5.6/examples/._mocha.rb delete mode 100644 vendor/gems/mocha-0.5.6/examples/._stubba.rb delete mode 100644 vendor/gems/mocha-0.5.6/examples/misc.rb delete mode 100644 vendor/gems/mocha-0.5.6/examples/mocha.rb delete mode 100644 vendor/gems/mocha-0.5.6/examples/stubba.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/._mocha.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._any_instance_method.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._auto_verify.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._central.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._class_method.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._deprecation.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._expectation.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._expectation_error.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._expectation_list.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._infinite_range.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._inspect.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._instance_method.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._metaclass.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._method_matcher.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._missing_expectation.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._mock.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._object.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._parameter_matchers.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._parameters_matcher.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._pretty_parameters.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._return_values.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._sequence.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._setup_and_teardown.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._single_return_value.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._standalone.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._test_case_adapter.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/._yield_parameters.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/any_instance_method.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/auto_verify.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/central.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/class_method.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/deprecation.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/exception_raiser.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/expectation.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/expectation_error.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/expectation_list.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/infinite_range.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/inspect.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/instance_method.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/is_a.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/metaclass.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/method_matcher.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/missing_expectation.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/mock.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/multiple_yields.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/no_yields.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/object.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._all_of.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._any_of.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._any_parameters.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._anything.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._base.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._equals.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._has_entries.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._has_entry.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._has_key.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._has_value.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._includes.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._instance_of.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._is_a.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._kind_of.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._not.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._object.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._optionally.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._regexp_matches.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/all_of.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/any_of.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/any_parameters.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/anything.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/base.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/equals.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/has_entries.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/has_entry.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/has_key.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/has_value.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/includes.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/instance_of.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/is_a.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/kind_of.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/not.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/object.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/optionally.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/regexp_matches.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/parameters_matcher.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/pretty_parameters.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/return_values.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/sequence.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/setup_and_teardown.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/single_return_value.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/single_yield.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/standalone.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/stub.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/test_case_adapter.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha/yield_parameters.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/mocha_standalone.rb delete mode 100644 vendor/gems/mocha-0.5.6/lib/stubba.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/._deprecation_disabler.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/._execution_point.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/._method_definer.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/._test_helper.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/._test_runner.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/acceptance/._expected_invocation_count_acceptance_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/acceptance/._mocha_acceptance_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/acceptance/._mock_with_initializer_block_acceptance_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/acceptance/._mocked_methods_dispatch_acceptance_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/acceptance/._optional_parameters_acceptance_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/acceptance/._parameter_matcher_acceptance_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/acceptance/._partial_mocks_acceptance_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/acceptance/._sequence_acceptance_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/acceptance/._standalone_acceptance_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/acceptance/._stubba_acceptance_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/acceptance/expected_invocation_count_acceptance_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/acceptance/mocha_acceptance_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/acceptance/mock_with_initializer_block_acceptance_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/acceptance/mocked_methods_dispatch_acceptance_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/acceptance/optional_parameters_acceptance_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/acceptance/parameter_matcher_acceptance_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/acceptance/partial_mocks_acceptance_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/acceptance/sequence_acceptance_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/acceptance/standalone_acceptance_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/acceptance/stubba_acceptance_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/active_record_test_case.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/deprecation_disabler.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/execution_point.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/integration/._mocha_test_result_integration_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/integration/._stubba_integration_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/integration/._stubba_test_result_integration_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/integration/mocha_test_result_integration_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/integration/stubba_integration_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/integration/stubba_test_result_integration_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/method_definer.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/test_helper.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/test_runner.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/._any_instance_method_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/._auto_verify_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/._central_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/._class_method_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/._expectation_error_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/._expectation_list_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/._expectation_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/._hash_inspect_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/._method_matcher_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/._missing_expectation_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/._mock_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/._object_inspect_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/._parameters_matcher_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/._sequence_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/any_instance_method_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/array_inspect_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/auto_verify_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/central_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/class_method_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/date_time_inspect_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/expectation_error_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/expectation_list_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/expectation_raiser_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/expectation_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/hash_inspect_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/infinite_range_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/metaclass_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/method_matcher_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/missing_expectation_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/mock_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/multiple_yields_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/no_yield_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/object_inspect_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/object_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._all_of_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._any_of_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._anything_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._has_entries_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._has_entry_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._has_key_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._has_value_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._includes_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._instance_of_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._is_a_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._kind_of_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._not_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._regexp_matches_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._stub_matcher.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/all_of_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/any_of_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/anything_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/has_entries_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/has_entry_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/has_key_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/has_value_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/includes_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/instance_of_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/is_a_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/kind_of_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/not_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/regexp_matches_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/stub_matcher.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/parameters_matcher_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/return_values_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/sequence_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/setup_and_teardown_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/single_return_value_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/single_yield_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/string_inspect_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/stub_test.rb delete mode 100644 vendor/gems/mocha-0.5.6/test/unit/yield_parameters_test.rb delete mode 100644 vendor/gems/rspec/CHANGES delete mode 100644 vendor/gems/rspec/MIT-LICENSE delete mode 100644 vendor/gems/rspec/README delete mode 100644 vendor/gems/rspec/Rakefile delete mode 100644 vendor/gems/rspec/TODO delete mode 100644 vendor/gems/rspec/UPGRADE delete mode 100755 vendor/gems/rspec/bin/spec delete mode 100755 vendor/gems/rspec/bin/spec_translator delete mode 100644 vendor/gems/rspec/examples/pure/autogenerated_docstrings_example.rb delete mode 100644 vendor/gems/rspec/examples/pure/before_and_after_example.rb delete mode 100755 vendor/gems/rspec/examples/pure/behave_as_example.rb delete mode 100644 vendor/gems/rspec/examples/pure/custom_expectation_matchers.rb delete mode 100644 vendor/gems/rspec/examples/pure/custom_formatter.rb delete mode 100644 vendor/gems/rspec/examples/pure/dynamic_spec.rb delete mode 100644 vendor/gems/rspec/examples/pure/file_accessor.rb delete mode 100644 vendor/gems/rspec/examples/pure/file_accessor_spec.rb delete mode 100644 vendor/gems/rspec/examples/pure/greeter_spec.rb delete mode 100644 vendor/gems/rspec/examples/pure/helper_method_example.rb delete mode 100644 vendor/gems/rspec/examples/pure/io_processor.rb delete mode 100644 vendor/gems/rspec/examples/pure/io_processor_spec.rb delete mode 100644 vendor/gems/rspec/examples/pure/legacy_spec.rb delete mode 100644 vendor/gems/rspec/examples/pure/mocking_example.rb delete mode 100644 vendor/gems/rspec/examples/pure/multi_threaded_behaviour_runner.rb delete mode 100644 vendor/gems/rspec/examples/pure/nested_classes_example.rb delete mode 100644 vendor/gems/rspec/examples/pure/partial_mock_example.rb delete mode 100644 vendor/gems/rspec/examples/pure/pending_example.rb delete mode 100644 vendor/gems/rspec/examples/pure/predicate_example.rb delete mode 100644 vendor/gems/rspec/examples/pure/priority.txt delete mode 100644 vendor/gems/rspec/examples/pure/shared_example_group_example.rb delete mode 100644 vendor/gems/rspec/examples/pure/shared_stack_examples.rb delete mode 100644 vendor/gems/rspec/examples/pure/spec_helper.rb delete mode 100644 vendor/gems/rspec/examples/pure/stack.rb delete mode 100644 vendor/gems/rspec/examples/pure/stack_spec.rb delete mode 100644 vendor/gems/rspec/examples/pure/stack_spec_with_nested_example_groups.rb delete mode 100644 vendor/gems/rspec/examples/pure/stubbing_example.rb delete mode 100644 vendor/gems/rspec/examples/stories/adder.rb delete mode 100644 vendor/gems/rspec/examples/stories/addition delete mode 100644 vendor/gems/rspec/examples/stories/addition.rb delete mode 100644 vendor/gems/rspec/examples/stories/calculator.rb delete mode 100644 vendor/gems/rspec/examples/stories/game-of-life/README.txt delete mode 100644 vendor/gems/rspec/examples/stories/game-of-life/behaviour/everything.rb delete mode 100644 vendor/gems/rspec/examples/stories/game-of-life/behaviour/examples/examples.rb delete mode 100644 vendor/gems/rspec/examples/stories/game-of-life/behaviour/examples/game_behaviour.rb delete mode 100644 vendor/gems/rspec/examples/stories/game-of-life/behaviour/examples/grid_behaviour.rb delete mode 100644 vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/CellsWithLessThanTwoNeighboursDie.story delete mode 100644 vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/CellsWithMoreThanThreeNeighboursDie.story delete mode 100644 vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/EmptySpacesWithThreeNeighboursCreateACell.story delete mode 100644 vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/ICanCreateACell.story delete mode 100644 vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/ICanKillACell.story delete mode 100644 vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/TheGridWraps.story delete mode 100644 vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/create_a_cell.rb delete mode 100644 vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/helper.rb delete mode 100644 vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/kill_a_cell.rb delete mode 100644 vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/steps.rb delete mode 100644 vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/stories.rb delete mode 100644 vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/stories.txt delete mode 100644 vendor/gems/rspec/examples/stories/game-of-life/life.rb delete mode 100644 vendor/gems/rspec/examples/stories/game-of-life/life/game.rb delete mode 100644 vendor/gems/rspec/examples/stories/game-of-life/life/grid.rb delete mode 100644 vendor/gems/rspec/examples/stories/helper.rb delete mode 100644 vendor/gems/rspec/examples/stories/steps/addition_steps.rb delete mode 100644 vendor/gems/rspec/failing_examples/README.txt delete mode 100644 vendor/gems/rspec/failing_examples/diffing_spec.rb delete mode 100644 vendor/gems/rspec/failing_examples/failing_autogenerated_docstrings_example.rb delete mode 100644 vendor/gems/rspec/failing_examples/failure_in_setup.rb delete mode 100644 vendor/gems/rspec/failing_examples/failure_in_teardown.rb delete mode 100644 vendor/gems/rspec/failing_examples/mocking_example.rb delete mode 100644 vendor/gems/rspec/failing_examples/mocking_with_flexmock.rb delete mode 100644 vendor/gems/rspec/failing_examples/mocking_with_mocha.rb delete mode 100644 vendor/gems/rspec/failing_examples/mocking_with_rr.rb delete mode 100644 vendor/gems/rspec/failing_examples/partial_mock_example.rb delete mode 100644 vendor/gems/rspec/failing_examples/predicate_example.rb delete mode 100644 vendor/gems/rspec/failing_examples/raising_example.rb delete mode 100644 vendor/gems/rspec/failing_examples/spec_helper.rb delete mode 100644 vendor/gems/rspec/failing_examples/syntax_error_example.rb delete mode 100644 vendor/gems/rspec/failing_examples/team_spec.rb delete mode 100644 vendor/gems/rspec/failing_examples/timeout_behaviour.rb delete mode 100644 vendor/gems/rspec/lib/autotest/discover.rb delete mode 100644 vendor/gems/rspec/lib/autotest/rspec.rb delete mode 100644 vendor/gems/rspec/lib/spec.rb delete mode 100644 vendor/gems/rspec/lib/spec/example.rb delete mode 100755 vendor/gems/rspec/lib/spec/example/configuration.rb delete mode 100644 vendor/gems/rspec/lib/spec/example/errors.rb delete mode 100644 vendor/gems/rspec/lib/spec/example/example_group.rb delete mode 100755 vendor/gems/rspec/lib/spec/example/example_group_factory.rb delete mode 100644 vendor/gems/rspec/lib/spec/example/example_group_methods.rb delete mode 100755 vendor/gems/rspec/lib/spec/example/example_matcher.rb delete mode 100644 vendor/gems/rspec/lib/spec/example/example_methods.rb delete mode 100644 vendor/gems/rspec/lib/spec/example/module_reopening_fix.rb delete mode 100644 vendor/gems/rspec/lib/spec/example/pending.rb delete mode 100644 vendor/gems/rspec/lib/spec/example/shared_example_group.rb delete mode 100644 vendor/gems/rspec/lib/spec/expectations.rb delete mode 100644 vendor/gems/rspec/lib/spec/expectations/differs/default.rb delete mode 100644 vendor/gems/rspec/lib/spec/expectations/errors.rb delete mode 100644 vendor/gems/rspec/lib/spec/expectations/extensions.rb delete mode 100644 vendor/gems/rspec/lib/spec/expectations/extensions/object.rb delete mode 100644 vendor/gems/rspec/lib/spec/expectations/extensions/string_and_symbol.rb delete mode 100644 vendor/gems/rspec/lib/spec/expectations/handler.rb delete mode 100755 vendor/gems/rspec/lib/spec/extensions.rb delete mode 100644 vendor/gems/rspec/lib/spec/extensions/class.rb delete mode 100644 vendor/gems/rspec/lib/spec/extensions/main.rb delete mode 100755 vendor/gems/rspec/lib/spec/extensions/object.rb delete mode 100644 vendor/gems/rspec/lib/spec/interop/test.rb delete mode 100644 vendor/gems/rspec/lib/spec/interop/test/unit/autorunner.rb delete mode 100644 vendor/gems/rspec/lib/spec/interop/test/unit/testcase.rb delete mode 100644 vendor/gems/rspec/lib/spec/interop/test/unit/testresult.rb delete mode 100644 vendor/gems/rspec/lib/spec/interop/test/unit/testsuite_adapter.rb delete mode 100644 vendor/gems/rspec/lib/spec/interop/test/unit/ui/console/testrunner.rb delete mode 100644 vendor/gems/rspec/lib/spec/matchers.rb delete mode 100644 vendor/gems/rspec/lib/spec/matchers/be.rb delete mode 100644 vendor/gems/rspec/lib/spec/matchers/be_close.rb delete mode 100644 vendor/gems/rspec/lib/spec/matchers/change.rb delete mode 100644 vendor/gems/rspec/lib/spec/matchers/eql.rb delete mode 100644 vendor/gems/rspec/lib/spec/matchers/equal.rb delete mode 100644 vendor/gems/rspec/lib/spec/matchers/exist.rb delete mode 100644 vendor/gems/rspec/lib/spec/matchers/has.rb delete mode 100644 vendor/gems/rspec/lib/spec/matchers/have.rb delete mode 100644 vendor/gems/rspec/lib/spec/matchers/include.rb delete mode 100644 vendor/gems/rspec/lib/spec/matchers/match.rb delete mode 100755 vendor/gems/rspec/lib/spec/matchers/operator_matcher.rb delete mode 100644 vendor/gems/rspec/lib/spec/matchers/raise_error.rb delete mode 100644 vendor/gems/rspec/lib/spec/matchers/respond_to.rb delete mode 100644 vendor/gems/rspec/lib/spec/matchers/satisfy.rb delete mode 100644 vendor/gems/rspec/lib/spec/matchers/simple_matcher.rb delete mode 100644 vendor/gems/rspec/lib/spec/matchers/throw_symbol.rb delete mode 100644 vendor/gems/rspec/lib/spec/mocks.rb delete mode 100644 vendor/gems/rspec/lib/spec/mocks/argument_constraint_matchers.rb delete mode 100644 vendor/gems/rspec/lib/spec/mocks/argument_expectation.rb delete mode 100644 vendor/gems/rspec/lib/spec/mocks/error_generator.rb delete mode 100644 vendor/gems/rspec/lib/spec/mocks/errors.rb delete mode 100644 vendor/gems/rspec/lib/spec/mocks/extensions/object.rb delete mode 100644 vendor/gems/rspec/lib/spec/mocks/message_expectation.rb delete mode 100644 vendor/gems/rspec/lib/spec/mocks/methods.rb delete mode 100644 vendor/gems/rspec/lib/spec/mocks/mock.rb delete mode 100644 vendor/gems/rspec/lib/spec/mocks/order_group.rb delete mode 100644 vendor/gems/rspec/lib/spec/mocks/proxy.rb delete mode 100644 vendor/gems/rspec/lib/spec/mocks/space.rb delete mode 100644 vendor/gems/rspec/lib/spec/mocks/spec_methods.rb delete mode 100644 vendor/gems/rspec/lib/spec/rake/spectask.rb delete mode 100644 vendor/gems/rspec/lib/spec/rake/verify_rcov.rb delete mode 100644 vendor/gems/rspec/lib/spec/runner.rb delete mode 100644 vendor/gems/rspec/lib/spec/runner/backtrace_tweaker.rb delete mode 100644 vendor/gems/rspec/lib/spec/runner/class_and_arguments_parser.rb delete mode 100644 vendor/gems/rspec/lib/spec/runner/command_line.rb delete mode 100644 vendor/gems/rspec/lib/spec/runner/drb_command_line.rb delete mode 100644 vendor/gems/rspec/lib/spec/runner/example_group_runner.rb delete mode 100644 vendor/gems/rspec/lib/spec/runner/formatter/base_formatter.rb delete mode 100644 vendor/gems/rspec/lib/spec/runner/formatter/base_text_formatter.rb delete mode 100644 vendor/gems/rspec/lib/spec/runner/formatter/failing_example_groups_formatter.rb delete mode 100644 vendor/gems/rspec/lib/spec/runner/formatter/failing_examples_formatter.rb delete mode 100644 vendor/gems/rspec/lib/spec/runner/formatter/html_formatter.rb delete mode 100644 vendor/gems/rspec/lib/spec/runner/formatter/profile_formatter.rb delete mode 100644 vendor/gems/rspec/lib/spec/runner/formatter/progress_bar_formatter.rb delete mode 100644 vendor/gems/rspec/lib/spec/runner/formatter/snippet_extractor.rb delete mode 100644 vendor/gems/rspec/lib/spec/runner/formatter/specdoc_formatter.rb delete mode 100644 vendor/gems/rspec/lib/spec/runner/formatter/story/html_formatter.rb delete mode 100644 vendor/gems/rspec/lib/spec/runner/formatter/story/plain_text_formatter.rb delete mode 100644 vendor/gems/rspec/lib/spec/runner/formatter/text_mate_formatter.rb delete mode 100644 vendor/gems/rspec/lib/spec/runner/heckle_runner.rb delete mode 100644 vendor/gems/rspec/lib/spec/runner/heckle_runner_unsupported.rb delete mode 100644 vendor/gems/rspec/lib/spec/runner/option_parser.rb delete mode 100644 vendor/gems/rspec/lib/spec/runner/options.rb delete mode 100644 vendor/gems/rspec/lib/spec/runner/reporter.rb delete mode 100644 vendor/gems/rspec/lib/spec/runner/spec_parser.rb delete mode 100644 vendor/gems/rspec/lib/spec/story.rb delete mode 100644 vendor/gems/rspec/lib/spec/story/extensions.rb delete mode 100644 vendor/gems/rspec/lib/spec/story/extensions/main.rb delete mode 100644 vendor/gems/rspec/lib/spec/story/extensions/regexp.rb delete mode 100644 vendor/gems/rspec/lib/spec/story/extensions/string.rb delete mode 100644 vendor/gems/rspec/lib/spec/story/given_scenario.rb delete mode 100644 vendor/gems/rspec/lib/spec/story/runner.rb delete mode 100644 vendor/gems/rspec/lib/spec/story/runner/plain_text_story_runner.rb delete mode 100644 vendor/gems/rspec/lib/spec/story/runner/scenario_collector.rb delete mode 100644 vendor/gems/rspec/lib/spec/story/runner/scenario_runner.rb delete mode 100644 vendor/gems/rspec/lib/spec/story/runner/story_mediator.rb delete mode 100644 vendor/gems/rspec/lib/spec/story/runner/story_parser.rb delete mode 100644 vendor/gems/rspec/lib/spec/story/runner/story_runner.rb delete mode 100644 vendor/gems/rspec/lib/spec/story/scenario.rb delete mode 100644 vendor/gems/rspec/lib/spec/story/step.rb delete mode 100644 vendor/gems/rspec/lib/spec/story/step_group.rb delete mode 100644 vendor/gems/rspec/lib/spec/story/step_mother.rb delete mode 100644 vendor/gems/rspec/lib/spec/story/story.rb delete mode 100644 vendor/gems/rspec/lib/spec/story/world.rb delete mode 100644 vendor/gems/rspec/lib/spec/translator.rb delete mode 100644 vendor/gems/rspec/lib/spec/version.rb delete mode 100644 vendor/gems/rspec/plugins/mock_frameworks/flexmock.rb delete mode 100644 vendor/gems/rspec/plugins/mock_frameworks/mocha.rb delete mode 100644 vendor/gems/rspec/plugins/mock_frameworks/rr.rb delete mode 100644 vendor/gems/rspec/plugins/mock_frameworks/rspec.rb delete mode 100644 vendor/gems/rspec/pre_commit/lib/pre_commit.rb delete mode 100644 vendor/gems/rspec/pre_commit/lib/pre_commit/core.rb delete mode 100644 vendor/gems/rspec/pre_commit/lib/pre_commit/pre_commit.rb delete mode 100644 vendor/gems/rspec/pre_commit/lib/pre_commit/rspec.rb delete mode 100644 vendor/gems/rspec/pre_commit/lib/pre_commit/rspec_on_rails.rb delete mode 100644 vendor/gems/rspec/pre_commit/spec/pre_commit/pre_commit_spec.rb delete mode 100644 vendor/gems/rspec/pre_commit/spec/pre_commit/rspec_on_rails_spec.rb delete mode 100644 vendor/gems/rspec/pre_commit/spec/spec_helper.rb delete mode 100644 vendor/gems/rspec/pre_commit/spec/spec_suite.rb delete mode 100644 vendor/gems/rspec/rake_tasks/examples.rake delete mode 100644 vendor/gems/rspec/rake_tasks/examples_with_rcov.rake delete mode 100644 vendor/gems/rspec/rake_tasks/failing_examples_with_html.rake delete mode 100644 vendor/gems/rspec/rake_tasks/verify_rcov.rake delete mode 100644 vendor/gems/rspec/spec/README.jruby delete mode 100644 vendor/gems/rspec/spec/autotest/discover_spec.rb delete mode 100644 vendor/gems/rspec/spec/autotest/rspec_spec.rb delete mode 100644 vendor/gems/rspec/spec/autotest_helper.rb delete mode 100644 vendor/gems/rspec/spec/autotest_matchers.rb delete mode 100644 vendor/gems/rspec/spec/rspec_suite.rb delete mode 100644 vendor/gems/rspec/spec/ruby_forker.rb delete mode 100644 vendor/gems/rspec/spec/spec.opts delete mode 100755 vendor/gems/rspec/spec/spec/example/configuration_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/example/example_group_class_definition_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/example/example_group_factory_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/example/example_group_methods_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/example/example_group_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/example/example_matcher_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/example/example_methods_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/example/example_runner_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/example/example_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/example/nested_example_group_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/example/pending_module_spec.rb delete mode 100755 vendor/gems/rspec/spec/spec/example/predicate_matcher_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/example/shared_example_group_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/example/subclassing_example_group_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/expectations/differs/default_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/expectations/extensions/object_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/expectations/fail_with_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/extensions/main_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/interop/test/unit/resources/spec_that_fails.rb delete mode 100644 vendor/gems/rspec/spec/spec/interop/test/unit/resources/spec_that_passes.rb delete mode 100644 vendor/gems/rspec/spec/spec/interop/test/unit/resources/spec_with_errors.rb delete mode 100644 vendor/gems/rspec/spec/spec/interop/test/unit/resources/test_case_that_fails.rb delete mode 100644 vendor/gems/rspec/spec/spec/interop/test/unit/resources/test_case_that_passes.rb delete mode 100644 vendor/gems/rspec/spec/spec/interop/test/unit/resources/test_case_with_errors.rb delete mode 100644 vendor/gems/rspec/spec/spec/interop/test/unit/resources/testsuite_adapter_spec_with_test_unit.rb delete mode 100644 vendor/gems/rspec/spec/spec/interop/test/unit/spec_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/interop/test/unit/test_unit_spec_helper.rb delete mode 100644 vendor/gems/rspec/spec/spec/interop/test/unit/testcase_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/interop/test/unit/testsuite_adapter_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/matchers/be_close_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/matchers/be_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/matchers/change_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/matchers/description_generation_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/matchers/eql_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/matchers/equal_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/matchers/exist_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/matchers/handler_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/matchers/has_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/matchers/have_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/matchers/include_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/matchers/match_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/matchers/matcher_methods_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/matchers/mock_constraint_matchers_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/matchers/operator_matcher_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/matchers/raise_error_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/matchers/respond_to_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/matchers/satisfy_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/matchers/simple_matcher_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/matchers/throw_symbol_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/mocks/any_number_of_times_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/mocks/argument_expectation_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/mocks/at_least_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/mocks/at_most_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/mocks/bug_report_10260_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/mocks/bug_report_10263.rb delete mode 100644 vendor/gems/rspec/spec/spec/mocks/bug_report_11545_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/mocks/bug_report_15719_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/mocks/bug_report_7611_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/mocks/bug_report_7805_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/mocks/bug_report_8165_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/mocks/bug_report_8302_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/mocks/failing_mock_argument_constraints_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/mocks/mock_ordering_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/mocks/mock_space_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/mocks/mock_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/mocks/multiple_return_value_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/mocks/null_object_mock_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/mocks/once_counts_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/mocks/options_hash_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/mocks/partial_mock_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/mocks/partial_mock_using_mocks_directly_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/mocks/passing_mock_argument_constraints_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/mocks/precise_counts_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/mocks/record_messages_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/mocks/stub_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/mocks/twice_counts_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/package/bin_spec_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/runner/class_and_argument_parser_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/runner/command_line_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/runner/drb_command_line_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/runner/empty_file.txt delete mode 100644 vendor/gems/rspec/spec/spec/runner/examples.txt delete mode 100644 vendor/gems/rspec/spec/spec/runner/execution_context_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/runner/failed.txt delete mode 100644 vendor/gems/rspec/spec/spec/runner/formatter/failing_example_groups_formatter_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/runner/formatter/failing_examples_formatter_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/runner/formatter/html_formatted-1.8.4.html delete mode 100644 vendor/gems/rspec/spec/spec/runner/formatter/html_formatted-1.8.5-jruby.html delete mode 100644 vendor/gems/rspec/spec/spec/runner/formatter/html_formatted-1.8.5.html delete mode 100644 vendor/gems/rspec/spec/spec/runner/formatter/html_formatted-1.8.6-jruby.html delete mode 100644 vendor/gems/rspec/spec/spec/runner/formatter/html_formatted-1.8.6.html delete mode 100644 vendor/gems/rspec/spec/spec/runner/formatter/html_formatter_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/runner/formatter/profile_formatter_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/runner/formatter/progress_bar_formatter_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/runner/formatter/snippet_extractor_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/runner/formatter/spec_mate_formatter_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/runner/formatter/specdoc_formatter_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/runner/formatter/story/html_formatter_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/runner/formatter/story/plain_text_formatter_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/runner/formatter/text_mate_formatted-1.8.4.html delete mode 100644 vendor/gems/rspec/spec/spec/runner/formatter/text_mate_formatted-1.8.6.html delete mode 100644 vendor/gems/rspec/spec/spec/runner/heckle_runner_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/runner/heckler_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/runner/noisy_backtrace_tweaker_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/runner/option_parser_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/runner/options_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/runner/output_one_time_fixture.rb delete mode 100644 vendor/gems/rspec/spec/spec/runner/output_one_time_fixture_runner.rb delete mode 100644 vendor/gems/rspec/spec/spec/runner/output_one_time_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/runner/quiet_backtrace_tweaker_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/runner/reporter_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/runner/resources/a_bar.rb delete mode 100644 vendor/gems/rspec/spec/spec/runner/resources/a_foo.rb delete mode 100644 vendor/gems/rspec/spec/spec/runner/resources/a_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/runner/spec.opts delete mode 100644 vendor/gems/rspec/spec/spec/runner/spec_parser/spec_parser_fixture.rb delete mode 100644 vendor/gems/rspec/spec/spec/runner/spec_parser_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/runner/spec_spaced.opts delete mode 100644 vendor/gems/rspec/spec/spec/runner_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/spec_classes.rb delete mode 100644 vendor/gems/rspec/spec/spec/story/builders.rb delete mode 100644 vendor/gems/rspec/spec/spec/story/extensions/main_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/story/extensions_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/story/given_scenario_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/story/runner/plain_text_story_runner_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/story/runner/scenario_collector_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/story/runner/scenario_runner_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/story/runner/story_mediator_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/story/runner/story_parser_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/story/runner/story_runner_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/story/runner_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/story/scenario_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/story/step_group_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/story/step_mother_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/story/step_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/story/story_helper.rb delete mode 100644 vendor/gems/rspec/spec/spec/story/story_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/story/world_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec/translator_spec.rb delete mode 100644 vendor/gems/rspec/spec/spec_helper.rb delete mode 100644 vendor/gems/rspec/stories/all.rb delete mode 100644 vendor/gems/rspec/stories/example_groups/autogenerated_docstrings delete mode 100644 vendor/gems/rspec/stories/example_groups/example_group_with_should_methods delete mode 100644 vendor/gems/rspec/stories/example_groups/nested_groups delete mode 100644 vendor/gems/rspec/stories/example_groups/output delete mode 100644 vendor/gems/rspec/stories/example_groups/stories.rb delete mode 100644 vendor/gems/rspec/stories/helper.rb delete mode 100644 vendor/gems/rspec/stories/interop/examples_and_tests_together delete mode 100644 vendor/gems/rspec/stories/interop/stories.rb delete mode 100644 vendor/gems/rspec/stories/interop/test_case_with_should_methods delete mode 100644 vendor/gems/rspec/stories/pending_stories/README delete mode 100644 vendor/gems/rspec/stories/resources/helpers/cmdline.rb delete mode 100644 vendor/gems/rspec/stories/resources/helpers/story_helper.rb delete mode 100644 vendor/gems/rspec/stories/resources/matchers/smart_match.rb delete mode 100644 vendor/gems/rspec/stories/resources/spec/example_group_with_should_methods.rb delete mode 100644 vendor/gems/rspec/stories/resources/spec/simple_spec.rb delete mode 100644 vendor/gems/rspec/stories/resources/steps/running_rspec.rb delete mode 100644 vendor/gems/rspec/stories/resources/stories/failing_story.rb delete mode 100644 vendor/gems/rspec/stories/resources/test/spec_and_test_together.rb delete mode 100644 vendor/gems/rspec/stories/resources/test/test_case_with_should_methods.rb diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 9f91c97557..34992d75ef 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -3,16 +3,7 @@ $LOAD_PATH.unshift("#{dir}/") $LOAD_PATH.unshift("#{dir}/../lib") -# include any gems in vendor/gems -Dir["#{dir}/../vendor/gems/**"].each do |path| - libpath = File.join(path, "lib") - if File.directory?(libpath) - $LOAD_PATH.unshift(libpath) - else - $LOAD_PATH.unshift(path) - end -end - +require 'rubygems' require 'mocha' require 'spec' require 'facter' diff --git a/vendor/gems/mocha-0.5.6/._RELEASE b/vendor/gems/mocha-0.5.6/._RELEASE deleted file mode 100644 index 12bf79cb2e6581f54b117e5e6287cb0de7648084..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 176 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aUBqY_#1$j2;dkJ62u_IcMhl&q+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r2<(d)(W)% Dzy%mg diff --git a/vendor/gems/mocha-0.5.6/._Rakefile b/vendor/gems/mocha-0.5.6/._Rakefile deleted file mode 100644 index 22220c1ee8e5ecf3f1cfd2111329578866a2f715..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_IcLAsrq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!WSS#dY=A{Ce=GF?e E0NQ#Oj{pDw diff --git a/vendor/gems/mocha-0.5.6/COPYING b/vendor/gems/mocha-0.5.6/COPYING deleted file mode 100644 index 8f74d7116a..0000000000 --- a/vendor/gems/mocha-0.5.6/COPYING +++ /dev/null @@ -1,3 +0,0 @@ -Copyright Revieworld Ltd. 2006 - -You may use, copy and redistribute this library under the same terms as Ruby itself (see http://www.ruby-lang.org/en/LICENSE.txt) or under the MIT license (see MIT-LICENSE file). diff --git a/vendor/gems/mocha-0.5.6/MIT-LICENSE b/vendor/gems/mocha-0.5.6/MIT-LICENSE deleted file mode 100644 index fa4efe793a..0000000000 --- a/vendor/gems/mocha-0.5.6/MIT-LICENSE +++ /dev/null @@ -1,7 +0,0 @@ -Copyright (c) 2006 Revieworld Ltd. - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/README b/vendor/gems/mocha-0.5.6/README deleted file mode 100644 index 262c6ec27c..0000000000 --- a/vendor/gems/mocha-0.5.6/README +++ /dev/null @@ -1,35 +0,0 @@ -= Mocha - -Mocha is a library for mocking and stubbing using a syntax like that of JMock[http://www.jmock.org], and SchMock[http://rubyforge.org/projects/schmock]. Most commonly Mocha is used in conjunction with Test::Unit[http://www.ruby-doc.org/core/classes/Test/Unit.html], but it can be used in other contexts. - -One of its main advantages is that it allows you to mock and stub methods on _real_ (non-mock) classes and instances. You can for example stub ActiveRecord[http://api.rubyonrails.com/classes/ActiveRecord/Base.html] instance methods like +create+, +save+, +destroy+ and even class methods like +find+ to avoid hitting the database in unit tests. - -Mocha provides a unified, simple and readable syntax for both traditional mocking and for mocking with _real_ objects. - -Mocha has been harvested from projects at Reevoo[http://www.reevoo.com] by me (James[http://blog.floehopper.org]) and my colleagues Ben[http://www.reevoo.com/blogs/bengriffiths], Chris[http://blog.seagul.co.uk] and Paul[http://po-ru.com]. Mocha is in use on real-world Rails[http://www.rubyonrails.org] projects. - -== Download and Installation - -Install the gem with the following command... - - $ gem install mocha - -Or install the Rails[http://www.rubyonrails.org] plugin... - - $ script/plugin install svn://rubyforge.org/var/svn/mocha/trunk - -Or download Mocha from here - http://rubyforge.org/projects/mocha - -== Examples - -* Quick Start - {Usage Examples}[link:examples/misc.html] -* Traditional mocking - {Star Trek Example}[link:examples/mocha.html] -* Setting expectations on real classes - {Order Example}[link:examples/stubba.html] -* More examples on {Floehopper's Blog}[http://blog.floehopper.org] -* {Mailing List Archives}[http://rubyforge.org/pipermail/mocha-developer/] - -== License - -Copyright Revieworld Ltd. 2006 - -You may use, copy and redistribute this library under the same terms as {Ruby itself}[http://www.ruby-lang.org/en/LICENSE.txt] or under the {MIT license}[http://mocha.rubyforge.org/files/MIT-LICENSE.html]. \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/RELEASE b/vendor/gems/mocha-0.5.6/RELEASE deleted file mode 100644 index 0e8fb85737..0000000000 --- a/vendor/gems/mocha-0.5.6/RELEASE +++ /dev/null @@ -1,188 +0,0 @@ -= 0.5.5 (r167) - -- Renamed Matches parameter matcher to RegexpMatches for clarity. -- Added noframes tag to rdoc index to assist Google. - -= 0.5.4 (r166) - -- Added matches parameter matcher for matching regular expressions. - -= 0.5.3 (r165) - -- Attempt to fix packaging problems by switching to newer version (1.15.1) of gnutar and setting COPY_EXTENDED_ATTRIBUTES_DISABLE environment variable. -- Removed unused ExpectationSequenceError exception. -- Added instance_of and kind_of parameter matchers. -- Added Google Webmaster meta tag to rdoc template header. -- Put Google Webmaster meta tag in the right header i.e. the one for the index page. - -= 0.5.2 (r159) - -- Fix bug 11885 - "never doesn't work with stub_everything" submitted by Alexander Lang. In fixing this bug, also fixed undiscoverd bug where expected & actual invocation counts were being incorrectly reported which seems to have been introduced when fixes were added for invocation dispatch (see MockedMethodDispatchAcceptanceTest). -- Previously when an expectation did not allow more invocations, it was treated as not matching. Now we prefer matching expectations which allow more invocations, but still match expectations which cannot allow more invocations. I think this may be overcomplicating things, but let's see how it goes. - -= 0.5.1 (r149) - -- Fixed bug #11583 "Mocha 0.5.0 throwing unexpected warnings". Also switched on ruby warning for all rake test tasks. Fixed majority of warnings, but some left to fix. - -= 0.5.0 (r147) - -- Parameter Matchers - I’ve added a few Hamcrest-style parameter matchers which are designed to be used inside Expectation#with. The following matchers are currently available: anything(), includes(), has_key(), has_value(), has_entry(), all_of() & any_of(). More to follow soon. The idea is eventually to get rid of the nasty parameter_block option on Expectation#with. - - object = mock() - object.expects(:method).with(has_key('key_1')) - object.method('key_1' => 1, 'key_2' => 2) - # no verification error raised - - object = mock() - object.expects(:method).with(has_key('key_1')) - object.method('key_2' => 2) - # verification error raised, because method was not called with Hash containing key: 'key_1' - -- Values Returned and Exceptions Raised on Consecutive Invocations - Allow multiple calls to Expectation#returns and Expectation#raises to build up a sequence of responses to invocations on the mock. Added syntactic sugar method Expectation#then to allow more readable expectations. - - object = mock() - object.stubs(:method).returns(1, 2).then.raises(Exception).then.returns(4) - object.method # => 1 - object.method # => 2 - object.method # => raises exception of class Exception - object.method # => 4 - -- Yields on Consecutive Invocations - Allow multiple calls to yields on single expectation to allow yield parameters to be specified for consecutive invocations. - - object = mock() - object.stubs(:method).yields(1, 2).then.yields(3) - object.method { |*values| p values } # => [1, 2] - object.method { |*values| p values } # => [3] - -- Multiple Yields on Single Invocation - Added Expectation#multiple_yields to allow a mocked or stubbed method to yield multiple times for a single invocation. - - object = mock() - object.stubs(:method).multiple_yields([1, 2], [3]) - object.method { |*values| p values } # => [1, 2] # => [3] - -- Invocation Dispatch - Expectations were already being matched in reverse order i.e. the most recently defined one was being found first. This is still the case, but we now stop matching an expectation when its maximum number of expected invocations is reached. c.f. JMock v1. A stub will never stop matching by default. Hopefully this means we can soon get rid of the need to pass a Proc to Expectation#returns. - - object = mock() - object.stubs(:method).returns(2) - object.expects(:method).once.returns(1) - object.method # => 1 - object.method # => 2 - object.method # => 2 - # no verification error raised - - # The following should still work... - - Time.stubs(:now).returns(Time.parse('Mon Jan 01 00:00:00 UTC 2007')) - Time.now # => Mon Jan 01 00:00:00 UTC 2007 - Time.stubs(:now).returns(Time.parse('Thu Feb 01 00:00:00 UTC 2007')) - Time.now # => Thu Feb 01 00:00:00 UTC 2007 - -- Deprecate passing an instance of Proc to Expectation#returns. -- Explicitly include all Rakefile dependencies in project. -- Fixed old Stubba example. -- Fix so that it is possible for a stubbed method to raise an Interrupt exception without a message in Ruby 1.8.6 -- Added responds_like and quacks_like. -- Capture standard object methods before Mocha adds any. -- Added Expectation#once method to make interface less surprising. -- Use Rake::TestTask to run tests. Created three separate tasks to run unit, integration & acceptance tests. Split inspect_test into one file per TestCase. Deleted superfluous all_tests file. -- Fiddled with mocha_inspect and tests to give more sensible results on x86 platform. -- Fixed bug #7834 "infinite_range.rb makes incorrect assumption about to_f" logged by James Moore. - -= 0.4.0 (r92) - -- Allow naming of mocks (patch from Chris Roos). -- Specify multiple return values for consecutive calls. -- Improved consistency of expectation error messages. -- Allow mocking of Object instance methods e.g. kind_of?, type. -- Provide aliased versions of #expects and #stubs to allow mocking of these methods. -- Added at_least, at_most, at_most_once methods to expectation. -- Allow expects and stubs to take a hash of method and return values. -- Eliminate warning: "instance variable @yield not initialized" (patch from Xavier Shay). -- Restore instance methods on partial mocks (patch from Chris Roos). -- Allow stubbing of a method with non-word characters in its name (patch from Paul Battley). -- Removed coupling to Test::Unit. -- Allow specified exception instance to be raised (patch from Chris Roos). -- Make mock object_id appear in hex like normal Ruby inspect (patch from Paul Battley). -- Fix path to object.rb in rdoc rake task (patch from Tomas Pospisek). -- Reverse order in which expectations are matched, so that last expectation is matched first. This allows e.g. a call to #stubs to be effectively overridden by a call to #expects (patch from Tobias Lutke). -- Stubba & SmartTestCase modules incorporated into Mocha module so only need to require 'mocha' - no longer need to require 'stubba'. -- AutoMocha removed. - -= 0.3.3 - -- Quick bug fix to restore instance methods on partial mocks (for Kevin Clark). - -= 0.3.2 - -- Examples added. - -= 0.3.1 - -- Dual licensing with MIT license added. - -= 0.3.0 - -* Rails plugin. -* Auto-verify for expectations on concrete classes. -* Include each expectation verification in the test result assertion count. -* Filter out noise from assertion backtraces. -* Point assertion backtrace to line where failing expectation was created. -* New yields method for expectations. -* Create stubs which stub all method calls. -* Mocks now respond_to? expected methods. - -= 0.2.1 - -* Rename MochaAcceptanceTest::Rover#move method to avoid conflict with Rake (in Ruby 1.8.4 only?) - -= 0.2.0 - -* Small change to SetupAndTeardown#teardown_stubs suggested by Luke Redpath (http://www.lukeredpath.co.uk) to allow use of Stubba with RSpec (http://rspec.rubyforge.org). -* Reorganized directory structure and extracted addition of setup and teardown methods into SmartTestCase mini-library. -* Addition of auto-verify for Mocha (but not Stubba). This means there is more significance in the choice of expects or stubs in that any expects on a mock will automatically get verified. - -So instead of... - - wotsit = Mocha.new - wotsit.expects(:thingummy).with(5).returns(10) - doobrey = Doobrey.new(wotsit) - doobrey.hoojamaflip - wotsit.verify - -you need to do... - - wotsit = mock() - wotsit.expects(:thingummy).with(5).returns(10) - doobrey = Doobrey.new(wotsit) - doobrey.hoojamaflip - # no need to verify - -There are also shortcuts as follows... - -instead of... - - wotsit = Mocha.new - wotsit.expects(:thingummy).returns(10) - wotsit.expects(:summat).returns(25) - -you can have... - - wotsit = mock(:thingummy => 5, :summat => 25) - -and instead of... - - wotsit = Mocha.new - wotsit.stubs(:thingummy).returns(10) - wotsit.stubs(:summat).returns(25) - -you can have... - - wotsit = stub(:thingummy => 5, :summat => 25) - -= 0.1.2 - -* Minor tweaks - -= 0.1.1 - -* Initial release. diff --git a/vendor/gems/mocha-0.5.6/Rakefile b/vendor/gems/mocha-0.5.6/Rakefile deleted file mode 100644 index 2e2f7287a6..0000000000 --- a/vendor/gems/mocha-0.5.6/Rakefile +++ /dev/null @@ -1,149 +0,0 @@ -require 'rubygems' -require 'rake/rdoctask' -require 'rake/gempackagetask' -require 'rake/testtask' -require 'rake/contrib/sshpublisher' - -module Mocha - VERSION = "0.5.6" -end - -desc "Run all tests" -task :default => :test_all - -task :test_all => [:test_unit, :test_integration, :test_acceptance] - -desc "Run unit tests" -Rake::TestTask.new(:test_unit) do |t| - t.libs << 'test' - t.test_files = FileList['test/unit/**/*_test.rb'] - t.verbose = true - t.warning = true -end - -desc "Run integration tests" -Rake::TestTask.new(:test_integration) do |t| - t.libs << 'test' - t.test_files = FileList['test/integration/*_test.rb'] - t.verbose = true - t.warning = true -end - -desc "Run acceptance tests" -Rake::TestTask.new(:test_acceptance) do |t| - t.libs << 'test' - t.test_files = FileList['test/acceptance/*_test.rb'] - t.verbose = true - t.warning = true -end - -desc 'Generate RDoc' -Rake::RDocTask.new do |task| - task.main = 'README' - task.title = "Mocha #{Mocha::VERSION}" - task.rdoc_dir = 'doc' - task.template = File.expand_path(File.join(File.dirname(__FILE__), "templates", "html_with_google_analytics")) - task.rdoc_files.include('README', 'RELEASE', 'COPYING', 'MIT-LICENSE', 'agiledox.txt', 'lib/mocha/auto_verify.rb', 'lib/mocha/mock.rb', 'lib/mocha/expectation.rb', 'lib/mocha/object.rb', 'lib/mocha/parameter_matchers.rb', 'lib/mocha/parameter_matchers') -end -task :rdoc => :examples - -desc "Upload RDoc to RubyForge" -task :publish_rdoc => [:rdoc, :examples] do - Rake::SshDirPublisher.new("jamesmead@rubyforge.org", "/var/www/gforge-projects/mocha", "doc").upload -end - -desc "Generate agiledox-like documentation for tests" -file 'agiledox.txt' do - File.open('agiledox.txt', 'w') do |output| - tests = FileList['test/**/*_test.rb'] - tests.each do |file| - m = %r".*/([^/].*)_test.rb".match(file) - output << m[1]+" should:\n" - test_definitions = File::readlines(file).select {|line| line =~ /.*def test.*/} - test_definitions.sort.each do |definition| - m = %r"test_(should_)?(.*)".match(definition) - output << " - "+m[2].gsub(/_/," ") << "\n" - end - end - end -end - -desc "Convert example ruby files to syntax-highlighted html" -task :examples do - $:.unshift File.expand_path(File.join(File.dirname(__FILE__), "vendor", "coderay-0.7.4.215", "lib")) - require 'coderay' - mkdir_p 'doc/examples' - File.open('doc/examples/coderay.css', 'w') do |output| - output << CodeRay::Encoders[:html]::CSS.new.stylesheet - end - ['mocha', 'stubba', 'misc'].each do |filename| - File.open("doc/examples/#{filename}.html", 'w') do |file| - file << "" - file << "" - file << %q() - file << "" - file << "" - file << CodeRay.scan_file("examples/#{filename}.rb").html.div - file << "" - file << "" - end - end -end - -Gem::manage_gems - -specification = Gem::Specification.new do |s| - s.name = "mocha" - s.summary = "Mocking and stubbing library" - s.version = Mocha::VERSION - s.platform = Gem::Platform::RUBY - s.author = 'James Mead' - s.description = <<-EOF - Mocking and stubbing library with JMock/SchMock syntax, which allows mocking and stubbing of methods on real (non-mock) classes. - EOF - s.email = 'mocha-developer@rubyforge.org' - s.homepage = 'http://mocha.rubyforge.org/' - s.rubyforge_project = 'mocha' - - s.has_rdoc = true - s.extra_rdoc_files = ['README', 'COPYING'] - s.rdoc_options << '--title' << 'Mocha' << '--main' << 'README' << '--line-numbers' - - s.autorequire = 'mocha' - s.add_dependency('rake') - s.files = FileList['{lib,test,examples}/**/*.rb', '[A-Z]*'].exclude('TODO').to_a -end - -Rake::GemPackageTask.new(specification) do |package| - package.need_zip = true - package.need_tar = true -end - -task :verify_user do - raise "RUBYFORGE_USER environment variable not set!" unless ENV['RUBYFORGE_USER'] -end - -task :verify_password do - raise "RUBYFORGE_PASSWORD environment variable not set!" unless ENV['RUBYFORGE_PASSWORD'] -end - -desc "Publish package files on RubyForge." -task :publish_packages => [:verify_user, :verify_password, :package] do - $:.unshift File.expand_path(File.join(File.dirname(__FILE__), "vendor", "meta_project-0.4.15", "lib")) - require 'meta_project' - require 'rake/contrib/xforge' - release_files = FileList[ - "pkg/mocha-#{Mocha::VERSION}.gem", - "pkg/mocha-#{Mocha::VERSION}.tgz", - "pkg/mocha-#{Mocha::VERSION}.zip" - ] - - Rake::XForge::Release.new(MetaProject::Project::XForge::RubyForge.new('mocha')) do |release| - release.user_name = ENV['RUBYFORGE_USER'] - release.password = ENV['RUBYFORGE_PASSWORD'] - release.files = release_files.to_a - release.release_name = "Mocha #{Mocha::VERSION}" - release.release_changes = '' - release.release_notes = '' - end -end diff --git a/vendor/gems/mocha-0.5.6/examples/._misc.rb b/vendor/gems/mocha-0.5.6/examples/._misc.rb deleted file mode 100644 index c425bb341e324c548c9571bac49be9016ffeb58b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_Iw*{yaq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV#CTPx&b=A{CemevZj E0MRNKb^rhX diff --git a/vendor/gems/mocha-0.5.6/examples/._mocha.rb b/vendor/gems/mocha-0.5.6/examples/._mocha.rb deleted file mode 100644 index 0c1998db864501a11760efab1f2ed3e42879ce44..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_Iw+W~eq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r2<)orq&9z E0MN7;X8-^I diff --git a/vendor/gems/mocha-0.5.6/examples/._stubba.rb b/vendor/gems/mocha-0.5.6/examples/._stubba.rb deleted file mode 100644 index 4eae8fa0f126a6cc0f1c74b55dda6c5dc57d7935..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_Iw*jaWq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eaNqS}Wva=A{CehL+X} FwE*e;7^eUL diff --git a/vendor/gems/mocha-0.5.6/examples/misc.rb b/vendor/gems/mocha-0.5.6/examples/misc.rb deleted file mode 100644 index 1cb8b55d02..0000000000 --- a/vendor/gems/mocha-0.5.6/examples/misc.rb +++ /dev/null @@ -1,44 +0,0 @@ -require 'test/unit' -require 'rubygems' -require 'mocha' - -class MiscExampleTest < Test::Unit::TestCase - - def test_mocking_a_class_method - product = Product.new - Product.expects(:find).with(1).returns(product) - assert_equal product, Product.find(1) - end - - def test_mocking_an_instance_method_on_a_real_object - product = Product.new - product.expects(:save).returns(true) - assert product.save - end - - def test_stubbing_instance_methods_on_real_objects - prices = [stub(:pence => 1000), stub(:pence => 2000)] - product = Product.new - product.stubs(:prices).returns(prices) - assert_equal [1000, 2000], product.prices.collect {|p| p.pence} - end - - def test_stubbing_an_instance_method_on_all_instances_of_a_class - Product.any_instance.stubs(:name).returns('stubbed_name') - product = Product.new - assert_equal 'stubbed_name', product.name - end - - def test_traditional_mocking - object = mock() - object.expects(:expected_method).with(:p1, :p2).returns(:result) - assert_equal :result, object.expected_method(:p1, :p2) - end - - def test_shortcuts - object = stub(:method1 => :result1, :method2 => :result2) - assert_equal :result1, object.method1 - assert_equal :result2, object.method2 - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/examples/mocha.rb b/vendor/gems/mocha-0.5.6/examples/mocha.rb deleted file mode 100644 index 863270d531..0000000000 --- a/vendor/gems/mocha-0.5.6/examples/mocha.rb +++ /dev/null @@ -1,26 +0,0 @@ -class Enterprise - - def initialize(dilithium) - @dilithium = dilithium - end - - def go(warp_factor) - warp_factor.times { @dilithium.nuke(:anti_matter) } - end - -end - -require 'test/unit' -require 'rubygems' -require 'mocha' - -class EnterpriseTest < Test::Unit::TestCase - - def test_should_boldly_go - dilithium = mock() - dilithium.expects(:nuke).with(:anti_matter).at_least_once # auto-verified at end of test - enterprise = Enterprise.new(dilithium) - enterprise.go(2) - end - -end diff --git a/vendor/gems/mocha-0.5.6/examples/stubba.rb b/vendor/gems/mocha-0.5.6/examples/stubba.rb deleted file mode 100644 index 2788d1b623..0000000000 --- a/vendor/gems/mocha-0.5.6/examples/stubba.rb +++ /dev/null @@ -1,65 +0,0 @@ -class Order - - attr_accessor :shipped_on - - def total_cost - line_items.inject(0) { |total, line_item| total + line_item.price } + shipping_cost - end - - def total_weight - line_items.inject(0) { |total, line_item| total + line_item.weight } - end - - def shipping_cost - total_weight * 5 + 10 - end - - class << self - - def find_all - # Database.connection.execute('select * from orders... - end - - def number_shipped_since(date) - find_all.select { |order| order.shipped_on > date }.length - end - - def unshipped_value - find_all.inject(0) { |total, order| order.shipped_on ? total : total + order.total_cost } - end - - end - -end - -require 'test/unit' -require 'rubygems' -require 'mocha' - -class OrderTest < Test::Unit::TestCase - - # illustrates stubbing instance method - def test_should_calculate_shipping_cost_based_on_total_weight - order = Order.new - order.stubs(:total_weight).returns(10) - assert_equal 60, order.shipping_cost - end - - # illustrates stubbing class method - def test_should_count_number_of_orders_shipped_after_specified_date - now = Time.now; week_in_secs = 7 * 24 * 60 * 60 - order_1 = Order.new; order_1.shipped_on = now - 1 * week_in_secs - order_2 = Order.new; order_2.shipped_on = now - 3 * week_in_secs - Order.stubs(:find_all).returns([order_1, order_2]) - assert_equal 1, Order.number_shipped_since(now - 2 * week_in_secs) - end - - # illustrates stubbing instance method for all instances of a class - def test_should_calculate_value_of_unshipped_orders - Order.stubs(:find_all).returns([Order.new, Order.new, Order.new]) - Order.any_instance.stubs(:shipped_on).returns(nil) - Order.any_instance.stubs(:total_cost).returns(10) - assert_equal 30, Order.unshipped_value - end - -end diff --git a/vendor/gems/mocha-0.5.6/lib/._mocha.rb b/vendor/gems/mocha-0.5.6/lib/._mocha.rb deleted file mode 100644 index cf6ff4839c69367829fbee64b519be58845cf54e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_I%>`5n(lG;w zCDF7oBE&_L^K>83Qh_W(V{3(4 E0GE*$zyJUM diff --git a/vendor/gems/mocha-0.5.6/lib/mocha.rb b/vendor/gems/mocha-0.5.6/lib/mocha.rb deleted file mode 100644 index 58571156a1..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha.rb +++ /dev/null @@ -1,19 +0,0 @@ -require 'mocha_standalone' -require 'mocha/test_case_adapter' - -require 'test/unit/testcase' - -module Test - - module Unit - - class TestCase - - include Mocha::Standalone - include Mocha::TestCaseAdapter - - end - - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/._any_instance_method.rb b/vendor/gems/mocha-0.5.6/lib/mocha/._any_instance_method.rb deleted file mode 100644 index 00711bd393d7440125504f76da6c58860a753fc9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_I^%JNRq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eY09E97M6r2<(-Ce{kI E0OzY1vH$=8 diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/._auto_verify.rb b/vendor/gems/mocha-0.5.6/lib/mocha/._auto_verify.rb deleted file mode 100644 index b0167dfe3156b7babcdb19a5807770ddd7ea68a9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 176 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aUBqY_#1$j2;dkJ62zeT=O$1oNXHBy zmO#_Sh!7V|&d=4$O-wGz&rK{zE!Im;EJ`h5s7}t$Db3AOuvIXyR>;ZBONFp%0T)~u AOaK4? diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/._central.rb b/vendor/gems/mocha-0.5.6/lib/mocha/._central.rb deleted file mode 100644 index 8515018e0656138eb59d19f7ed1812c53b30a919..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_Ibs4A>q+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!WSS#dY=A{CeX4VR| E0NNZFjQ{`u diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/._class_method.rb b/vendor/gems/mocha-0.5.6/lib/mocha/._class_method.rb deleted file mode 100644 index 66871a56faca565acc4a345e91af10fd19dcfddb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62zc=Hyo%Gq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eaNsSu5mZ=A{CeCZ^U3 FwE!6W87}|; diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/._deprecation.rb b/vendor/gems/mocha-0.5.6/lib/mocha/._deprecation.rb deleted file mode 100644 index 3e11a4d5ab15cb4d388ee2419cbc42b228d9035d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_Ibswk{q+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!XE97M6r2<(-hSmzT E0N$7wkN^Mx diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/._expectation.rb b/vendor/gems/mocha-0.5.6/lib/mocha/._expectation.rb deleted file mode 100644 index 2deed467d54e3ca0fcc6d8dac52cb60875e4f08d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_q2GR-AF$0LD z(6lik#6^?ybM>83Qb9~3YlT_> D)5REI diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/._expectation_error.rb b/vendor/gems/mocha-0.5.6/lib/mocha/._expectation_error.rb deleted file mode 100644 index dbb6154b38922123992e4fc918d5e2ecc7ecb73e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 176 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aUBqY_#1$j2;dkJ62u_qQVmoJ(lG;w zCD61nBE&_L^K>83QX#Bb0Jr8C AF8}}l diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/._expectation_list.rb b/vendor/gems/mocha-0.5.6/lib/mocha/._expectation_list.rb deleted file mode 100644 index 21a5f32b4898923015bcbed6256af35694ab1398..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_IbrPr)q+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eaNsSS#dY=A{CeMn={O FwE*!37`6Za diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/._infinite_range.rb b/vendor/gems/mocha-0.5.6/lib/mocha/._infinite_range.rb deleted file mode 100644 index 07e0b709a3a640c9d7eba69487fe4be6fa7f871e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_IbsDG?q+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r2<)orq&9z E0N1`4d;kCd diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/._inspect.rb b/vendor/gems/mocha-0.5.6/lib/mocha/._inspect.rb deleted file mode 100644 index 28d27abdeed5937054911d3af74c27541a9253b5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 176 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aUBqY_#1$j2;dkJ62u_I^%AHQq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r2<*T)(W)% D$DkNu diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/._instance_method.rb b/vendor/gems/mocha-0.5.6/lib/mocha/._instance_method.rb deleted file mode 100644 index c11d0ea30cef4ed2ccf78cbb8ce98285e1285f56..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 176 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aUBqY_#1$j2;dkJ62u_I^%STSq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r9xP>0LDNV ATL1t6 diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/._metaclass.rb b/vendor/gems/mocha-0.5.6/lib/mocha/._metaclass.rb deleted file mode 100644 index 3ab4a8b5f77e91f62c806906d99b7a9a3f40b54d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 176 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aUBqY_#1$j2;dkJ62u_I^%tlVq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r2<(-)(W)% D&OjJ- diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/._method_matcher.rb b/vendor/gems/mocha-0.5.6/lib/mocha/._method_matcher.rb deleted file mode 100644 index 392843f65b0810da5bee3c2415e860b1fe5461ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62zc+eHl%u59_O{^7a E0Tt*OI{*Lx diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/._missing_expectation.rb b/vendor/gems/mocha-0.5.6/lib/mocha/._missing_expectation.rb deleted file mode 100644 index 4049e36b45b30e6d6feb481c27162efd38350e71..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_qW(HIW(lG;w zCDF7oBE&_L^K$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62zeT=N3>YNXHBy zmO|6Uh!7V|&d=4$O-wGz&rK{zE!Im;EJ`h5s7}t$Db3AOuvIXyR>;ZBO9iqFElsQy FY5`7E8N~nq diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/._object.rb b/vendor/gems/mocha-0.5.6/lib/mocha/._object.rb deleted file mode 100644 index 2fa785b575b334363ee3f792cad3984c8f0b7f3f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_I^%kfUq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r2<)o2By{u FwE+E&7}Ed% diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/._parameter_matchers.rb b/vendor/gems/mocha-0.5.6/lib/mocha/._parameter_matchers.rb deleted file mode 100644 index 1a81dea1e3d10f95460284b37c8614e22218e267..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_I^&Y4cq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r2<)oCe{kI E0OQgap8x;= diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/._parameters_matcher.rb b/vendor/gems/mocha-0.5.6/lib/mocha/._parameters_matcher.rb deleted file mode 100644 index de250a8e2d948908ebede6dc16df94925696bf93..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62zc+eK}AmNXHBy zmPFIWh!7V|&d=4$O-wGz&rK{zE!Im;EJ`h5s7}t$Db3AOuvIX#v{uN;%u59_&8-z` E0Tz=PL;wH) diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/._pretty_parameters.rb b/vendor/gems/mocha-0.5.6/lib/mocha/._pretty_parameters.rb deleted file mode 100644 index 0a4f4854bfff396ad47bcceffdc108ff7a5ae3d5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 176 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aUBqY_#1$j2;dkJ62u_I^%CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r9xP>0LtbV AX#fBK diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/._return_values.rb b/vendor/gems/mocha-0.5.6/lib/mocha/._return_values.rb deleted file mode 100644 index 0d41af3d697355d5f7b8f065a42e77be0226ff53..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62zc=Hw>s0q+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r2<)orq&9z E0R8i_@% diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/._sequence.rb b/vendor/gems/mocha-0.5.6/lib/mocha/._sequence.rb deleted file mode 100644 index af2494888fa1d0a66576760a7df22e8aaf4a4d04..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62zdz!VXjl(lG;w zrO>o7BE&_L^K$Vqox1Ojhs@R)|o50+1L3ClDI}aUBqY_#1$j2;dkJ62u_IbrGl(q+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r2<*z)(W)% DzqlAq diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/._single_return_value.rb b/vendor/gems/mocha-0.5.6/lib/mocha/._single_return_value.rb deleted file mode 100644 index fec4c56f502aad54a7aa369db2780f31850d0a8f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_I^&hAdq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!WS}Wva=B0v|rq&9z E00V&-?*IS* diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/._standalone.rb b/vendor/gems/mocha-0.5.6/lib/mocha/._standalone.rb deleted file mode 100644 index f33f028a2a6240ae6d9889524af832565f4e2c11..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_Ibrq--q+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!WS}Wva=B0v|M%D_o E0QDdk!T$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_I^%bZTq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eVzqSS#dY=A{CehL+X} FwE+L67~uc_ diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/._yield_parameters.rb b/vendor/gems/mocha-0.5.6/lib/mocha/._yield_parameters.rb deleted file mode 100644 index a9646d7bbf769cc282d30d135c55d3b7a291620d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62zc=Hx#H8q+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eVzrTPx&b=B0v|rq&9z E02k63BLDyZ diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/any_instance_method.rb b/vendor/gems/mocha-0.5.6/lib/mocha/any_instance_method.rb deleted file mode 100644 index 4d55293b9a..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/any_instance_method.rb +++ /dev/null @@ -1,35 +0,0 @@ -require 'mocha/class_method' - -module Mocha - - class AnyInstanceMethod < ClassMethod - - def unstub - remove_new_method - restore_original_method - stubbee.any_instance.reset_mocha - end - - def mock - stubbee.any_instance.mocha - end - - def hide_original_method - stubbee.class_eval "alias_method :#{hidden_method}, :#{method}" if stubbee.method_defined?(method) - end - - def define_new_method - stubbee.class_eval "def #{method}(*args, &block); self.class.any_instance.mocha.method_missing(:#{method}, *args, &block); end" - end - - def remove_new_method - stubbee.class_eval "remove_method :#{method}" - end - - def restore_original_method - stubbee.class_eval "alias_method :#{method}, :#{hidden_method}; remove_method :#{hidden_method}" if stubbee.method_defined?(hidden_method) - end - - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/auto_verify.rb b/vendor/gems/mocha-0.5.6/lib/mocha/auto_verify.rb deleted file mode 100644 index 896648bcdc..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/auto_verify.rb +++ /dev/null @@ -1,118 +0,0 @@ -require 'mocha/mock' -require 'mocha/sequence' - -module Mocha # :nodoc: - - # Methods added to TestCase allowing creation of traditional mock objects. - # - # Mocks created this way will have their expectations automatically verified at the end of the test. - # - # See Mock for methods on mock objects. - module AutoVerify - - def mocks # :nodoc: - @mocks ||= [] - end - - def reset_mocks # :nodoc: - @mocks = nil - end - - # :call-seq: mock(name, &block) -> mock object - # mock(expected_methods = {}, &block) -> mock object - # mock(name, expected_methods = {}, &block) -> mock object - # - # Creates a mock object. - # - # +name+ is a +String+ identifier for the mock object. - # - # +expected_methods+ is a +Hash+ with expected method name symbols as keys and corresponding return values as values. - # - # +block+ is a block to be evaluated against the mock object instance, giving an alernative way to set up expectations & stubs. - # - # Note that (contrary to expectations set up by #stub) these expectations must be fulfilled during the test. - # def test_product - # product = mock('ipod_product', :manufacturer => 'ipod', :price => 100) - # assert_equal 'ipod', product.manufacturer - # assert_equal 100, product.price - # # an error will be raised unless both Product#manufacturer and Product#price have been called - # end - def mock(*arguments, &block) - name = arguments.shift if arguments.first.is_a?(String) - expectations = arguments.shift || {} - mock = Mock.new(name, &block) - mock.expects(expectations) - mocks << mock - mock - end - - # :call-seq: stub(name, &block) -> mock object - # stub(stubbed_methods = {}, &block) -> mock object - # stub(name, stubbed_methods = {}, &block) -> mock object - # - # Creates a mock object. - # - # +name+ is a +String+ identifier for the mock object. - # - # +stubbed_methods+ is a +Hash+ with stubbed method name symbols as keys and corresponding return values as values. - # - # +block+ is a block to be evaluated against the mock object instance, giving an alernative way to set up expectations & stubs. - # - # Note that (contrary to expectations set up by #mock) these expectations need not be fulfilled during the test. - # def test_product - # product = stub('ipod_product', :manufacturer => 'ipod', :price => 100) - # assert_equal 'ipod', product.manufacturer - # assert_equal 100, product.price - # # an error will not be raised even if Product#manufacturer and Product#price have not been called - # end - def stub(*arguments, &block) - name = arguments.shift if arguments.first.is_a?(String) - expectations = arguments.shift || {} - stub = Mock.new(name, &block) - stub.stubs(expectations) - mocks << stub - stub - end - - # :call-seq: stub_everything(name, &block) -> mock object - # stub_everything(stubbed_methods = {}, &block) -> mock object - # stub_everything(name, stubbed_methods = {}, &block) -> mock object - # - # Creates a mock object that accepts calls to any method. - # - # By default it will return +nil+ for any method call. - # - # +block+ is a block to be evaluated against the mock object instance, giving an alernative way to set up expectations & stubs. - # - # +name+ and +stubbed_methods+ work in the same way as for #stub. - # def test_product - # product = stub_everything('ipod_product', :price => 100) - # assert_nil product.manufacturer - # assert_nil product.any_old_method - # assert_equal 100, product.price - # end - def stub_everything(*arguments, &block) - name = arguments.shift if arguments.first.is_a?(String) - expectations = arguments.shift || {} - stub = Mock.new(name, &block) - stub.stub_everything - stub.stubs(expectations) - mocks << stub - stub - end - - def verify_mocks # :nodoc: - mocks.each { |mock| mock.verify { yield if block_given? } } - end - - def teardown_mocks # :nodoc: - reset_mocks - end - - def sequence(name) # :nodoc: - Sequence.new(name) - end - - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/central.rb b/vendor/gems/mocha-0.5.6/lib/mocha/central.rb deleted file mode 100644 index 0445f2151d..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/central.rb +++ /dev/null @@ -1,35 +0,0 @@ -module Mocha - - class Central - - attr_accessor :stubba_methods - - def initialize - self.stubba_methods = [] - end - - def stub(method) - unless stubba_methods.include?(method) - method.stub - stubba_methods.push method - end - end - - def verify_all(&block) - unique_mocks.each { |mock| mock.verify(&block) } - end - - def unique_mocks - stubba_methods.inject({}) { |mocks, method| mocks[method.mock.__id__] = method.mock; mocks }.values - end - - def unstub_all - while stubba_methods.length > 0 - method = stubba_methods.pop - method.unstub - end - end - - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/class_method.rb b/vendor/gems/mocha-0.5.6/lib/mocha/class_method.rb deleted file mode 100644 index e2178be171..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/class_method.rb +++ /dev/null @@ -1,66 +0,0 @@ -require 'mocha/metaclass' - -module Mocha - - class ClassMethod - - attr_reader :stubbee, :method - - def initialize(stubbee, method) - @stubbee, @method = stubbee, method - end - - def stub - hide_original_method - define_new_method - end - - def unstub - remove_new_method - restore_original_method - stubbee.reset_mocha - end - - def mock - stubbee.mocha - end - - def hide_original_method - stubbee.__metaclass__.class_eval "alias_method :#{hidden_method}, :#{method}" if stubbee.__metaclass__.method_defined?(method) - end - - def define_new_method - stubbee.__metaclass__.class_eval "def #{method}(*args, &block); mocha.method_missing(:#{method}, *args, &block); end" - end - - def remove_new_method - stubbee.__metaclass__.class_eval "remove_method :#{method}" - end - - def restore_original_method - stubbee.__metaclass__.class_eval "alias_method :#{method}, :#{hidden_method}; remove_method :#{hidden_method}" if stubbee.__metaclass__.method_defined?(hidden_method) - end - - def hidden_method - if RUBY_VERSION < '1.9' - method_name = method.to_s.gsub(/\W/) { |s| "_substituted_character_#{s[0]}_" } - else - method_name = method.to_s.gsub(/\W/) { |s| "_substituted_character_#{s.ord}_" } - end - "__stubba__#{method_name}__stubba__" - end - - def eql?(other) - return false unless (other.class == self.class) - (stubbee == other.stubbee) and (method == other.method) - end - - alias_method :==, :eql? - - def to_s - "#{stubbee}.#{method}" - end - - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/deprecation.rb b/vendor/gems/mocha-0.5.6/lib/mocha/deprecation.rb deleted file mode 100644 index 7448510ec7..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/deprecation.rb +++ /dev/null @@ -1,22 +0,0 @@ -module Mocha - - class Deprecation - - class << self - - attr_accessor :mode, :messages - - def warning(message) - @messages << message - $stderr.puts "Mocha deprecation warning: #{message}" unless mode == :disabled - $stderr.puts caller.join("\n ") if mode == :debug - end - - end - - self.mode = :enabled - self.messages = [] - - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/exception_raiser.rb b/vendor/gems/mocha-0.5.6/lib/mocha/exception_raiser.rb deleted file mode 100644 index 266e209a22..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/exception_raiser.rb +++ /dev/null @@ -1,17 +0,0 @@ -module Mocha # :nodoc: - - class ExceptionRaiser # :nodoc: - - def initialize(exception, message) - @exception, @message = exception, message - end - - def evaluate - raise @exception, @exception.to_s if @exception == Interrupt - raise @exception, @message if @message - raise @exception - end - - end - -end diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/expectation.rb b/vendor/gems/mocha-0.5.6/lib/mocha/expectation.rb deleted file mode 100644 index e3da2533f3..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/expectation.rb +++ /dev/null @@ -1,384 +0,0 @@ -require 'mocha/infinite_range' -require 'mocha/method_matcher' -require 'mocha/parameters_matcher' -require 'mocha/expectation_error' -require 'mocha/return_values' -require 'mocha/exception_raiser' -require 'mocha/yield_parameters' -require 'mocha/is_a' - -module Mocha # :nodoc: - - # Methods on expectations returned from Mock#expects, Mock#stubs, Object#expects and Object#stubs. - class Expectation - - # :call-seq: times(range) -> expectation - # - # Modifies expectation so that the number of calls to the expected method must be within a specific +range+. - # - # +range+ can be specified as an exact integer or as a range of integers - # object = mock() - # object.expects(:expected_method).times(3) - # 3.times { object.expected_method } - # # => verify succeeds - # - # object = mock() - # object.expects(:expected_method).times(3) - # 2.times { object.expected_method } - # # => verify fails - # - # object = mock() - # object.expects(:expected_method).times(2..4) - # 3.times { object.expected_method } - # # => verify succeeds - # - # object = mock() - # object.expects(:expected_method).times(2..4) - # object.expected_method - # # => verify fails - def times(range) - @expected_count = range - self - end - - # :call-seq: once() -> expectation - # - # Modifies expectation so that the expected method must be called exactly once. - # Note that this is the default behaviour for an expectation, but you may wish to use it for clarity/emphasis. - # object = mock() - # object.expects(:expected_method).once - # object.expected_method - # # => verify succeeds - # - # object = mock() - # object.expects(:expected_method).once - # object.expected_method - # object.expected_method - # # => verify fails - # - # object = mock() - # object.expects(:expected_method).once - # # => verify fails - def once() - times(1) - self - end - - # :call-seq: never() -> expectation - # - # Modifies expectation so that the expected method must never be called. - # object = mock() - # object.expects(:expected_method).never - # object.expected_method - # # => verify fails - # - # object = mock() - # object.expects(:expected_method).never - # object.expected_method - # # => verify succeeds - def never - times(0) - self - end - - # :call-seq: at_least(minimum_number_of_times) -> expectation - # - # Modifies expectation so that the expected method must be called at least a +minimum_number_of_times+. - # object = mock() - # object.expects(:expected_method).at_least(2) - # 3.times { object.expected_method } - # # => verify succeeds - # - # object = mock() - # object.expects(:expected_method).at_least(2) - # object.expected_method - # # => verify fails - def at_least(minimum_number_of_times) - times(Range.at_least(minimum_number_of_times)) - self - end - - # :call-seq: at_least_once() -> expectation - # - # Modifies expectation so that the expected method must be called at least once. - # object = mock() - # object.expects(:expected_method).at_least_once - # object.expected_method - # # => verify succeeds - # - # object = mock() - # object.expects(:expected_method).at_least_once - # # => verify fails - def at_least_once() - at_least(1) - self - end - - # :call-seq: at_most(maximum_number_of_times) -> expectation - # - # Modifies expectation so that the expected method must be called at most a +maximum_number_of_times+. - # object = mock() - # object.expects(:expected_method).at_most(2) - # 2.times { object.expected_method } - # # => verify succeeds - # - # object = mock() - # object.expects(:expected_method).at_most(2) - # 3.times { object.expected_method } - # # => verify fails - def at_most(maximum_number_of_times) - times(Range.at_most(maximum_number_of_times)) - self - end - - # :call-seq: at_most_once() -> expectation - # - # Modifies expectation so that the expected method must be called at most once. - # object = mock() - # object.expects(:expected_method).at_most_once - # object.expected_method - # # => verify succeeds - # - # object = mock() - # object.expects(:expected_method).at_most_once - # 2.times { object.expected_method } - # # => verify fails - def at_most_once() - at_most(1) - self - end - - # :call-seq: with(*expected_parameters, &matching_block) -> expectation - # - # Modifies expectation so that the expected method must be called with +expected_parameters+. - # object = mock() - # object.expects(:expected_method).with(:param1, :param2) - # object.expected_method(:param1, :param2) - # # => verify succeeds - # - # object = mock() - # object.expects(:expected_method).with(:param1, :param2) - # object.expected_method(:param3) - # # => verify fails - # May be used with parameter matchers in Mocha::ParameterMatchers. - # - # If a +matching_block+ is given, the block is called with the parameters passed to the expected method. - # The expectation is matched if the block evaluates to +true+. - # object = mock() - # object.expects(:expected_method).with() { |value| value % 4 == 0 } - # object.expected_method(16) - # # => verify succeeds - # - # object = mock() - # object.expects(:expected_method).with() { |value| value % 4 == 0 } - # object.expected_method(17) - # # => verify fails - def with(*expected_parameters, &matching_block) - @parameters_matcher = ParametersMatcher.new(expected_parameters, &matching_block) - self - end - - # :call-seq: yields(*parameters) -> expectation - # - # Modifies expectation so that when the expected method is called, it yields with the specified +parameters+. - # object = mock() - # object.expects(:expected_method).yields('result') - # yielded_value = nil - # object.expected_method { |value| yielded_value = value } - # yielded_value # => 'result' - # May be called multiple times on the same expectation for consecutive invocations. Also see Expectation#then. - # object = mock() - # object.stubs(:expected_method).yields(1).then.yields(2) - # yielded_values_from_first_invocation = [] - # yielded_values_from_second_invocation = [] - # object.expected_method { |value| yielded_values_from_first_invocation << value } # first invocation - # object.expected_method { |value| yielded_values_from_second_invocation << value } # second invocation - # yielded_values_from_first_invocation # => [1] - # yielded_values_from_second_invocation # => [2] - def yields(*parameters) - @yield_parameters.add(*parameters) - self - end - - # :call-seq: multiple_yields(*parameter_groups) -> expectation - # - # Modifies expectation so that when the expected method is called, it yields multiple times per invocation with the specified +parameter_groups+. - # object = mock() - # object.expects(:expected_method).multiple_yields(['result_1', 'result_2'], ['result_3']) - # yielded_values = [] - # object.expected_method { |*values| yielded_values << values } - # yielded_values # => [['result_1', 'result_2'], ['result_3]] - # May be called multiple times on the same expectation for consecutive invocations. Also see Expectation#then. - # object = mock() - # object.stubs(:expected_method).multiple_yields([1, 2], [3]).then.multiple_yields([4], [5, 6]) - # yielded_values_from_first_invocation = [] - # yielded_values_from_second_invocation = [] - # object.expected_method { |*values| yielded_values_from_first_invocation << values } # first invocation - # object.expected_method { |*values| yielded_values_from_second_invocation << values } # second invocation - # yielded_values_from_first_invocation # => [[1, 2], [3]] - # yielded_values_from_second_invocation # => [[4], [5, 6]] - def multiple_yields(*parameter_groups) - @yield_parameters.multiple_add(*parameter_groups) - self - end - - # :call-seq: returns(value) -> expectation - # returns(*values) -> expectation - # - # Modifies expectation so that when the expected method is called, it returns the specified +value+. - # object = mock() - # object.stubs(:stubbed_method).returns('result') - # object.stubbed_method # => 'result' - # object.stubbed_method # => 'result' - # If multiple +values+ are given, these are returned in turn on consecutive calls to the method. - # object = mock() - # object.stubs(:stubbed_method).returns(1, 2) - # object.stubbed_method # => 1 - # object.stubbed_method # => 2 - # May be called multiple times on the same expectation. Also see Expectation#then. - # object = mock() - # object.stubs(:expected_method).returns(1, 2).then.returns(3) - # object.expected_method # => 1 - # object.expected_method # => 2 - # object.expected_method # => 3 - # May be called in conjunction with Expectation#raises on the same expectation. - # object = mock() - # object.stubs(:expected_method).returns(1, 2).then.raises(Exception) - # object.expected_method # => 1 - # object.expected_method # => 2 - # object.expected_method # => raises exception of class Exception1 - # If +value+ is a +Proc+, then the expected method will return the result of calling Proc#call. - # - # This usage is _deprecated_. - # Use explicit multiple return values and/or multiple expectations instead. - # - # A +Proc+ instance will be treated the same as any other value in a future release. - # object = mock() - # object.stubs(:stubbed_method).returns(lambda { rand(100) }) - # object.stubbed_method # => 41 - # object.stubbed_method # => 77 - def returns(*values) - @return_values += ReturnValues.build(*values) - self - end - - # :call-seq: raises(exception = RuntimeError, message = nil) -> expectation - # - # Modifies expectation so that when the expected method is called, it raises the specified +exception+ with the specified +message+. - # object = mock() - # object.expects(:expected_method).raises(Exception, 'message') - # object.expected_method # => raises exception of class Exception and with message 'message' - # May be called multiple times on the same expectation. Also see Expectation#then. - # object = mock() - # object.stubs(:expected_method).raises(Exception1).then.raises(Exception2) - # object.expected_method # => raises exception of class Exception1 - # object.expected_method # => raises exception of class Exception2 - # May be called in conjunction with Expectation#returns on the same expectation. - # object = mock() - # object.stubs(:expected_method).raises(Exception).then.returns(2, 3) - # object.expected_method # => raises exception of class Exception1 - # object.expected_method # => 2 - # object.expected_method # => 3 - def raises(exception = RuntimeError, message = nil) - @return_values += ReturnValues.new(ExceptionRaiser.new(exception, message)) - self - end - - # :call-seq: then() -> expectation - # - # Syntactic sugar to improve readability. Has no effect on state of the expectation. - # object = mock() - # object.stubs(:expected_method).returns(1, 2).then.raises(Exception).then.returns(4) - # object.expected_method # => 1 - # object.expected_method # => 2 - # object.expected_method # => raises exception of class Exception - # object.expected_method # => 4 - def then - self - end - - # :stopdoc: - - def in_sequence(*sequences) - sequences.each { |sequence| sequence.constrain_as_next_in_sequence(self) } - self - end - - attr_reader :backtrace - - def initialize(mock, expected_method_name, backtrace = nil) - @mock = mock - @method_matcher = MethodMatcher.new(expected_method_name) - @parameters_matcher = ParametersMatcher.new - @ordering_constraints = [] - @expected_count, @invoked_count = 1, 0 - @return_values = ReturnValues.new - @yield_parameters = YieldParameters.new - @backtrace = backtrace || caller - end - - def add_ordering_constraint(ordering_constraint) - @ordering_constraints << ordering_constraint - end - - def in_correct_order? - @ordering_constraints.all? { |ordering_constraint| ordering_constraint.allows_invocation_now? } - end - - def matches_method?(method_name) - @method_matcher.match?(method_name) - end - - def match?(actual_method_name, *actual_parameters) - @method_matcher.match?(actual_method_name) && @parameters_matcher.match?(actual_parameters) && in_correct_order? - end - - def invocations_allowed? - if @expected_count.is_a?(Range) then - @invoked_count < @expected_count.last - else - @invoked_count < @expected_count - end - end - - def satisfied? - if @expected_count.is_a?(Range) then - @invoked_count >= @expected_count.first - else - @invoked_count >= @expected_count - end - end - - def invoke - @invoked_count += 1 - if block_given? then - @yield_parameters.next_invocation.each do |yield_parameters| - yield(*yield_parameters) - end - end - @return_values.next - end - - def verify - yield(self) if block_given? - unless (@expected_count === @invoked_count) then - error = ExpectationError.new(error_message(@expected_count, @invoked_count), backtrace) - raise error - end - end - - def method_signature - signature = "#{@mock.mocha_inspect}.#{@method_matcher.mocha_inspect}#{@parameters_matcher.mocha_inspect}" - signature << "; #{@ordering_constraints.map { |oc| oc.mocha_inspect }.join("; ")}" unless @ordering_constraints.empty? - signature - end - - def error_message(expected_count, actual_count) - "#{method_signature} - expected calls: #{expected_count.mocha_inspect}, actual calls: #{actual_count}" - end - - # :startdoc: - - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/expectation_error.rb b/vendor/gems/mocha-0.5.6/lib/mocha/expectation_error.rb deleted file mode 100644 index 705571b854..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/expectation_error.rb +++ /dev/null @@ -1,15 +0,0 @@ -module Mocha - - class ExpectationError < StandardError - - LIB_DIRECTORY = File.expand_path(File.join(File.dirname(__FILE__), "..")) + File::SEPARATOR - - def initialize(message = nil, backtrace = [], lib_directory = LIB_DIRECTORY) - super(message) - filtered_backtrace = backtrace.reject { |location| Regexp.new(lib_directory).match(File.expand_path(location)) } - set_backtrace(filtered_backtrace) - end - - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/expectation_list.rb b/vendor/gems/mocha-0.5.6/lib/mocha/expectation_list.rb deleted file mode 100644 index 5ca13d5afe..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/expectation_list.rb +++ /dev/null @@ -1,46 +0,0 @@ -module Mocha # :nodoc: - - class ExpectationList - - def initialize - @expectations = [] - end - - def add(expectation) - @expectations << expectation - expectation - end - - def matches_method?(method_name) - @expectations.any? { |expectation| expectation.matches_method?(method_name) } - end - - def similar(method_name) - @expectations.select { |expectation| expectation.matches_method?(method_name) } - end - - def detect(method_name, *arguments) - expectations = @expectations.reverse.select { |e| e.match?(method_name, *arguments) } - expectation = expectations.detect { |e| e.invocations_allowed? } - expectation || expectations.first - end - - def verify(&block) - @expectations.each { |expectation| expectation.verify(&block) } - end - - def to_a - @expectations - end - - def to_set - @expectations.to_set - end - - def length - @expectations.length - end - - end - -end diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/infinite_range.rb b/vendor/gems/mocha-0.5.6/lib/mocha/infinite_range.rb deleted file mode 100644 index 05dfe559e2..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/infinite_range.rb +++ /dev/null @@ -1,25 +0,0 @@ -class Range - - def self.at_least(minimum_value) - Range.new(minimum_value, infinite) - end - - def self.at_most(maximum_value) - Range.new(-infinite, maximum_value, false) - end - - def self.infinite - 1/0.0 - end - - def mocha_inspect - if first.respond_to?(:to_f) and first.to_f.infinite? then - return "at most #{last}" - elsif last.respond_to?(:to_f) and last.to_f.infinite? then - return "at least #{first}" - else - to_s - end - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/inspect.rb b/vendor/gems/mocha-0.5.6/lib/mocha/inspect.rb deleted file mode 100644 index ad82ef70e6..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/inspect.rb +++ /dev/null @@ -1,39 +0,0 @@ -require 'date' - -class Object - def mocha_inspect - address = self.__id__ * 2 - address += 0x100000000 if address < 0 - inspect =~ /#" : inspect - end -end - -class String - def mocha_inspect - inspect.gsub(/\"/, "'") - end -end - -class Array - def mocha_inspect - "[#{collect { |member| member.mocha_inspect }.join(', ')}]" - end -end - -class Hash - def mocha_inspect - "{#{collect { |key, value| "#{key.mocha_inspect} => #{value.mocha_inspect}" }.join(', ')}}" - end -end - -class Time - def mocha_inspect - "#{inspect} (#{to_f} secs)" - end -end - -class Date - def mocha_inspect - to_s - end -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/instance_method.rb b/vendor/gems/mocha-0.5.6/lib/mocha/instance_method.rb deleted file mode 100644 index f0d4b04b8a..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/instance_method.rb +++ /dev/null @@ -1,8 +0,0 @@ -require 'mocha/class_method' - -module Mocha - - class InstanceMethod < ClassMethod - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/is_a.rb b/vendor/gems/mocha-0.5.6/lib/mocha/is_a.rb deleted file mode 100644 index ee23c86a91..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/is_a.rb +++ /dev/null @@ -1,9 +0,0 @@ -class Object - - # :stopdoc: - - alias_method :__is_a__, :is_a? - - # :startdoc: - -end diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/metaclass.rb b/vendor/gems/mocha-0.5.6/lib/mocha/metaclass.rb deleted file mode 100644 index f78fb892b2..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/metaclass.rb +++ /dev/null @@ -1,7 +0,0 @@ -class Object - - def __metaclass__ - class << self; self; end - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/method_matcher.rb b/vendor/gems/mocha-0.5.6/lib/mocha/method_matcher.rb deleted file mode 100644 index 6ce5f6d576..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/method_matcher.rb +++ /dev/null @@ -1,21 +0,0 @@ -module Mocha - - class MethodMatcher - - attr_reader :expected_method_name - - def initialize(expected_method_name) - @expected_method_name = expected_method_name - end - - def match?(actual_method_name) - @expected_method_name == actual_method_name - end - - def mocha_inspect - "#{@expected_method_name}" - end - - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/missing_expectation.rb b/vendor/gems/mocha-0.5.6/lib/mocha/missing_expectation.rb deleted file mode 100644 index ccff6bff42..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/missing_expectation.rb +++ /dev/null @@ -1,17 +0,0 @@ -require 'mocha/expectation' - -module Mocha # :nodoc: - - class MissingExpectation < Expectation # :nodoc: - - def verify - message = error_message(0, 1) - similar_expectations = @mock.expectations.similar(@method_matcher.expected_method_name) - method_signatures = similar_expectations.map { |expectation| expectation.method_signature } - message << "\nSimilar expectations:\n#{method_signatures.join("\n")}" unless method_signatures.empty? - raise ExpectationError.new(message, backtrace) - end - - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/mock.rb b/vendor/gems/mocha-0.5.6/lib/mocha/mock.rb deleted file mode 100644 index 59193e728c..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/mock.rb +++ /dev/null @@ -1,202 +0,0 @@ -require 'mocha/expectation' -require 'mocha/expectation_list' -require 'mocha/stub' -require 'mocha/missing_expectation' -require 'mocha/metaclass' - -module Mocha # :nodoc: - - # Traditional mock object. - # - # Methods return an Expectation which can be further modified by methods on Expectation. - class Mock - - # :call-seq: expects(method_name) -> expectation - # expects(method_names) -> last expectation - # - # Adds an expectation that a method identified by +method_name+ symbol must be called exactly once with any parameters. - # Returns the new expectation which can be further modified by methods on Expectation. - # object = mock() - # object.expects(:method1) - # object.method1 - # # no error raised - # - # object = mock() - # object.expects(:method1) - # # error raised, because method1 not called exactly once - # If +method_names+ is a +Hash+, an expectation will be set up for each entry using the key as +method_name+ and value as +return_value+. - # object = mock() - # object.expects(:method1 => :result1, :method2 => :result2) - # - # # exactly equivalent to - # - # object = mock() - # object.expects(:method1).returns(:result1) - # object.expects(:method2).returns(:result2) - # - # Aliased by \_\_expects\_\_ - def expects(method_name_or_hash, backtrace = nil) - if method_name_or_hash.is_a?(Hash) then - method_name_or_hash.each do |method_name, return_value| - ensure_method_not_already_defined(method_name) - @expectations.add(Expectation.new(self, method_name, backtrace).returns(return_value)) - end - else - ensure_method_not_already_defined(method_name_or_hash) - @expectations.add(Expectation.new(self, method_name_or_hash, backtrace)) - end - end - - # :call-seq: stubs(method_name) -> expectation - # stubs(method_names) -> last expectation - # - # Adds an expectation that a method identified by +method_name+ symbol may be called any number of times with any parameters. - # Returns the new expectation which can be further modified by methods on Expectation. - # object = mock() - # object.stubs(:method1) - # object.method1 - # object.method1 - # # no error raised - # If +method_names+ is a +Hash+, an expectation will be set up for each entry using the key as +method_name+ and value as +return_value+. - # object = mock() - # object.stubs(:method1 => :result1, :method2 => :result2) - # - # # exactly equivalent to - # - # object = mock() - # object.stubs(:method1).returns(:result1) - # object.stubs(:method2).returns(:result2) - # - # Aliased by \_\_stubs\_\_ - def stubs(method_name_or_hash, backtrace = nil) - if method_name_or_hash.is_a?(Hash) then - method_name_or_hash.each do |method_name, return_value| - ensure_method_not_already_defined(method_name) - @expectations.add(Stub.new(self, method_name, backtrace).returns(return_value)) - end - else - ensure_method_not_already_defined(method_name_or_hash) - @expectations.add(Stub.new(self, method_name_or_hash, backtrace)) - end - end - - # :call-seq: responds_like(responder) -> mock - # - # Constrains the +mock+ so that it can only expect or stub methods to which +responder+ responds. The constraint is only applied at method invocation time. - # - # A +NoMethodError+ will be raised if the +responder+ does not respond_to? a method invocation (even if the method has been expected or stubbed). - # - # The +mock+ will delegate its respond_to? method to the +responder+. - # class Sheep - # def chew(grass); end - # def self.number_of_legs; end - # end - # - # sheep = mock('sheep') - # sheep.expects(:chew) - # sheep.expects(:foo) - # sheep.respond_to?(:chew) # => true - # sheep.respond_to?(:foo) # => true - # sheep.chew - # sheep.foo - # # no error raised - # - # sheep = mock('sheep') - # sheep.responds_like(Sheep.new) - # sheep.expects(:chew) - # sheep.expects(:foo) - # sheep.respond_to?(:chew) # => true - # sheep.respond_to?(:foo) # => false - # sheep.chew - # sheep.foo # => raises NoMethodError exception - # - # sheep_class = mock('sheep_class') - # sheep_class.responds_like(Sheep) - # sheep_class.stubs(:number_of_legs).returns(4) - # sheep_class.expects(:foo) - # sheep_class.respond_to?(:number_of_legs) # => true - # sheep_class.respond_to?(:foo) # => false - # assert_equal 4, sheep_class.number_of_legs - # sheep_class.foo # => raises NoMethodError exception - # - # Aliased by +quacks_like+ - def responds_like(object) - @responder = object - self - end - - # :stopdoc: - - def initialize(name = nil, &block) - @mock_name = name - @expectations = ExpectationList.new - @everything_stubbed = false - @responder = nil - instance_eval(&block) if block - end - - attr_reader :everything_stubbed, :expectations - - alias_method :__expects__, :expects - - alias_method :__stubs__, :stubs - - alias_method :quacks_like, :responds_like - - def add_expectation(expectation) - @expectations.add(expectation) - end - - def stub_everything - @everything_stubbed = true - end - - def method_missing(symbol, *arguments, &block) - if @responder and not @responder.respond_to?(symbol) - raise NoMethodError, "undefined method `#{symbol}' for #{self.mocha_inspect} which responds like #{@responder.mocha_inspect}" - end - matching_expectation = @expectations.detect(symbol, *arguments) - if matching_expectation then - matching_expectation.invoke(&block) - elsif @everything_stubbed then - return - else - unexpected_method_called(symbol, *arguments) - end - end - - def respond_to?(symbol) - if @responder then - @responder.respond_to?(symbol) - else - @expectations.matches_method?(symbol) - end - end - - def unexpected_method_called(symbol, *arguments) - MissingExpectation.new(self, symbol).with(*arguments).verify - end - - def verify(&block) - @expectations.verify(&block) - end - - def mocha_inspect - address = self.__id__ * 2 - address += 0x100000000 if address < 0 - @mock_name ? "#" : "#" - end - - def inspect - mocha_inspect - end - - def ensure_method_not_already_defined(method_name) - self.__metaclass__.send(:undef_method, method_name) if self.__metaclass__.method_defined?(method_name) - end - - # :startdoc: - - end - -end diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/multiple_yields.rb b/vendor/gems/mocha-0.5.6/lib/mocha/multiple_yields.rb deleted file mode 100644 index 8186c30768..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/multiple_yields.rb +++ /dev/null @@ -1,20 +0,0 @@ -module Mocha # :nodoc: - - class MultipleYields # :nodoc: - - attr_reader :parameter_groups - - def initialize(*parameter_groups) - @parameter_groups = parameter_groups - end - - def each - @parameter_groups.each do |parameter_group| - yield(parameter_group) - end - end - - end - -end - diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/no_yields.rb b/vendor/gems/mocha-0.5.6/lib/mocha/no_yields.rb deleted file mode 100644 index b0fba415dc..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/no_yields.rb +++ /dev/null @@ -1,11 +0,0 @@ -module Mocha # :nodoc: - - class NoYields # :nodoc: - - def each - end - - end - -end - diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/object.rb b/vendor/gems/mocha-0.5.6/lib/mocha/object.rb deleted file mode 100644 index 7ccdbad0de..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/object.rb +++ /dev/null @@ -1,110 +0,0 @@ -require 'mocha/mock' -require 'mocha/instance_method' -require 'mocha/class_method' -require 'mocha/any_instance_method' - -# Methods added all objects to allow mocking and stubbing on real objects. -# -# Methods return a Mocha::Expectation which can be further modified by methods on Mocha::Expectation. -class Object - - def mocha # :nodoc: - @mocha ||= Mocha::Mock.new - end - - def reset_mocha # :nodoc: - @mocha = nil - end - - def stubba_method # :nodoc: - Mocha::InstanceMethod - end - - def stubba_object # :nodoc: - self - end - - # :call-seq: expects(symbol) -> expectation - # - # Adds an expectation that a method identified by +symbol+ must be called exactly once with any parameters. - # Returns the new expectation which can be further modified by methods on Mocha::Expectation. - # product = Product.new - # product.expects(:save).returns(true) - # assert_equal false, product.save - # - # The original implementation of Product#save is replaced temporarily. - # - # The original implementation of Product#save is restored at the end of the test. - def expects(symbol) - method = stubba_method.new(stubba_object, symbol) - $stubba.stub(method) - mocha.expects(symbol, caller) - end - - # :call-seq: stubs(symbol) -> expectation - # - # Adds an expectation that a method identified by +symbol+ may be called any number of times with any parameters. - # Returns the new expectation which can be further modified by methods on Mocha::Expectation. - # product = Product.new - # product.stubs(:save).returns(true) - # assert_equal false, product.save - # - # The original implementation of Product#save is replaced temporarily. - # - # The original implementation of Product#save is restored at the end of the test. - def stubs(symbol) - method = stubba_method.new(stubba_object, symbol) - $stubba.stub(method) - mocha.stubs(symbol, caller) - end - - def verify # :nodoc: - mocha.verify - end - -end - -class Module # :nodoc: - - def stubba_method - Mocha::ClassMethod - end - -end - -class Class - - def stubba_method # :nodoc: - Mocha::ClassMethod - end - - class AnyInstance # :nodoc: - - def initialize(klass) - @stubba_object = klass - end - - def stubba_method - Mocha::AnyInstanceMethod - end - - def stubba_object - @stubba_object - end - - end - - # :call-seq: any_instance -> mock object - # - # Returns a mock object which will detect calls to any instance of this class. - # Product.any_instance.stubs(:save).returns(false) - # product_1 = Product.new - # assert_equal false, product_1.save - # product_2 = Product.new - # assert_equal false, product_2.save - def any_instance - @any_instance ||= AnyInstance.new(self) - end - -end - diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers.rb deleted file mode 100644 index a110479985..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers.rb +++ /dev/null @@ -1,25 +0,0 @@ -module Mocha - - # Used as parameters for Expectation#with to restrict the parameter values which will match the expectation. - module ParameterMatchers; end - -end - -require 'mocha/parameter_matchers/object' - -require 'mocha/parameter_matchers/all_of' -require 'mocha/parameter_matchers/any_of' -require 'mocha/parameter_matchers/any_parameters' -require 'mocha/parameter_matchers/anything' -require 'mocha/parameter_matchers/equals' -require 'mocha/parameter_matchers/has_entry' -require 'mocha/parameter_matchers/has_entries' -require 'mocha/parameter_matchers/has_key' -require 'mocha/parameter_matchers/has_value' -require 'mocha/parameter_matchers/includes' -require 'mocha/parameter_matchers/instance_of' -require 'mocha/parameter_matchers/is_a' -require 'mocha/parameter_matchers/kind_of' -require 'mocha/parameter_matchers/not' -require 'mocha/parameter_matchers/optionally' -require 'mocha/parameter_matchers/regexp_matches' diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._all_of.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._all_of.rb deleted file mode 100644 index 41edbb869b4ad190641f241e7b212bef483c4087..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_IwF0OVq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eaMATPx&b=A{Ce#s=03 FwE*SK7?c12 diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._any_of.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._any_of.rb deleted file mode 100644 index ff228f5c75072ce5ee41ccdaaf16445b5c5f32f7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_IwFIaXq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eVzsSu5mZ=A{CeM&{NE FwE*Nv7?J=0 diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._any_parameters.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._any_parameters.rb deleted file mode 100644 index 7f2968c8c6d997023ec0422b8603d6e37dc24728..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62zd!a093mq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eVzqTPx&b=A{Ce#zxi( FwE*^^7{mYo diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._anything.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._anything.rb deleted file mode 100644 index 5efdf5440275dc1b24bb5534b9cc0c64e279ed43..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 176 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aUBqY_#1$j2;dkJ62u_IH3O&=qyt1t zplM@7h>IrY=j!DqCKu)BCYGcY>m?@^rIs*MC+Fvs=H@BbDi~NRN Cw-^ur diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._base.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._base.rb deleted file mode 100644 index fc871fdda5edbfa6524410d4126a07e2404ac40d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 176 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aUBqY_#1$j2;dkJ62zd!C>83QX#Bb0EIgj AmjD0& diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._equals.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._equals.rb deleted file mode 100644 index c0dfb4cf5a4962c81701bf7207ad88a8ddfad066..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 176 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aUBqY_#1$j2;dkJ62zeTUk<1gq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r2<(-)(W)% D+CUhV diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._has_entries.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._has_entries.rb deleted file mode 100644 index 622858418fbf041f646ecbdd644b72bb652c8d93..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62zdnWCu_wNXHBy zmO|6Uh!7V|&d=4$O-wGz&rK{zE!Im;EJ`h5s7}t$Db3AOuvIWIwN}W<%u59_jZLf- FY5^sI8BqWL diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._has_entry.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._has_entry.rb deleted file mode 100644 index fe7b51cfa88e05e8ef96f85e801dac051dc03af6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_IwF;;dq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eaM9SS#dY=B0v|=GF?e E0On5^nE(I) diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._has_key.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._has_key.rb deleted file mode 100644 index e045911bef2de7a8ac0e31d2a7797d0c293cb5c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 176 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aUBqY_#1$j2;dkJ62u_IH4CT|q+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r2<(-)(W)% Dtzj4v diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._has_value.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._has_value.rb deleted file mode 100644 index a9565f2d4d2562e21065dca42e77594df230dc5c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_IH4ms1q+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eaNqTPx&b=A{CehKAM( FwE*IM7>fV^ diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._includes.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._includes.rb deleted file mode 100644 index 0139a2dd2b78a73c1e677ec791b52f3d71725620..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_IH3z5^q+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eVzqS}Wva=A{CehGy0Z FwE*F17={1< diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._instance_of.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._instance_of.rb deleted file mode 100644 index 718e3769737ae0bfec609f313a41129d193815d2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 176 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aUBqY_#1$j2;dkJ62u_IwE(CTq+K$elU GLM;HVUl$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_IwG5~fq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r2<(-mevZj E0Lx_$Vqox1Ojhs@R)|o50+1L3ClDI}aUBqY_#1$j2;dkJ62u_IwFsybq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r2<(-)(W)% Dun8C% diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._not.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._not.rb deleted file mode 100644 index 72ad06517106205b279f6e2cf83f433a6c497bc6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62zeRY(7vaNXHBy zmPFIWh!7V|&d=4$O-wGz&rK{zE!Im;EJ`h5s7}t$Db3AOuvM_MR>;ZBO9irw4XhPv E0T!+qIRF3v diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._object.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._object.rb deleted file mode 100644 index 5b117348ebedbbb924998203474adfb1f3185b16..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 176 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aUBqY_#1$j2;dkJ62zd!C>83QX#Bb0D$Tj AiU0rr diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._optionally.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._optionally.rb deleted file mode 100644 index 2177fc74a4d34810c8d8d53d5e3afa43a431813e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62zeT*9)i=q+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r2<)orq&9z E00Pt)`2YX_ diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._regexp_matches.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/._regexp_matches.rb deleted file mode 100644 index 2b30b084a2dd04443a5800c6a5525839c83c20a3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 176 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aUBqY_#1$j2;dkJ62u_IH4Uf~q+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r2<(-)(W)% DtPL0r diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/all_of.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/all_of.rb deleted file mode 100644 index 369eb4340a..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/all_of.rb +++ /dev/null @@ -1,42 +0,0 @@ -require 'mocha/parameter_matchers/base' - -module Mocha - - module ParameterMatchers - - # :call-seq: all_of -> parameter_matcher - # - # Matches if all +matchers+ match. - # object = mock() - # object.expects(:method_1).with(all_of(includes(1), includes(3))) - # object.method_1([1, 3]) - # # no error raised - # - # object = mock() - # object.expects(:method_1).with(all_of(includes(1), includes(3))) - # object.method_1([1, 2]) - # # error raised, because method_1 was not called with object including 1 and 3 - def all_of(*matchers) - AllOf.new(*matchers) - end - - class AllOf < Base # :nodoc: - - def initialize(*matchers) - @matchers = matchers - end - - def matches?(available_parameters) - parameter = available_parameters.shift - @matchers.all? { |matcher| matcher.matches?([parameter]) } - end - - def mocha_inspect - "all_of(#{@matchers.map { |matcher| matcher.mocha_inspect }.join(", ") })" - end - - end - - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/any_of.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/any_of.rb deleted file mode 100644 index dd254b12f4..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/any_of.rb +++ /dev/null @@ -1,47 +0,0 @@ -require 'mocha/parameter_matchers/base' - -module Mocha - - module ParameterMatchers - - # :call-seq: any_of -> parameter_matcher - # - # Matches if any +matchers+ match. - # object = mock() - # object.expects(:method_1).with(any_of(1, 3)) - # object.method_1(1) - # # no error raised - # - # object = mock() - # object.expects(:method_1).with(any_of(1, 3)) - # object.method_1(3) - # # no error raised - # - # object = mock() - # object.expects(:method_1).with(any_of(1, 3)) - # object.method_1(2) - # # error raised, because method_1 was not called with 1 or 3 - def any_of(*matchers) - AnyOf.new(*matchers) - end - - class AnyOf < Base # :nodoc: - - def initialize(*matchers) - @matchers = matchers - end - - def matches?(available_parameters) - parameter = available_parameters.shift - @matchers.any? { |matcher| matcher.matches?([parameter]) } - end - - def mocha_inspect - "any_of(#{@matchers.map { |matcher| matcher.mocha_inspect }.join(", ") })" - end - - end - - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/any_parameters.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/any_parameters.rb deleted file mode 100644 index 11dae83edb..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/any_parameters.rb +++ /dev/null @@ -1,40 +0,0 @@ -require 'mocha/parameter_matchers/base' - -module Mocha - - module ParameterMatchers - - # :call-seq: any_parameters() -> parameter_matcher - # - # Matches any parameters. - # object = mock() - # object.expects(:method_1).with(any_parameters) - # object.method_1(1, 2, 3, 4) - # # no error raised - # - # object = mock() - # object.expects(:method_1).with(any_parameters) - # object.method_1(5, 6, 7, 8, 9, 0) - # # no error raised - def any_parameters - AnyParameters.new - end - - class AnyParameters < Base # :nodoc: - - def matches?(available_parameters) - while available_parameters.length > 0 do - available_parameters.shift - end - return true - end - - def mocha_inspect - "any_parameters" - end - - end - - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/anything.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/anything.rb deleted file mode 100644 index e82ef86a00..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/anything.rb +++ /dev/null @@ -1,33 +0,0 @@ -require 'mocha/parameter_matchers/base' - -module Mocha - - module ParameterMatchers - - # :call-seq: anything -> parameter_matcher - # - # Matches any object. - # object = mock() - # object.expects(:method_1).with(anything) - # object.method_1('foo') - # # no error raised - def anything - Anything.new - end - - class Anything < Base # :nodoc: - - def matches?(available_parameters) - available_parameters.shift - return true - end - - def mocha_inspect - "anything" - end - - end - - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/base.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/base.rb deleted file mode 100644 index 6aaec51a16..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/base.rb +++ /dev/null @@ -1,15 +0,0 @@ -module Mocha - - module ParameterMatchers - - class Base # :nodoc: - - def to_matcher - self - end - - end - - end - -end diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/equals.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/equals.rb deleted file mode 100644 index bdc61a0f5e..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/equals.rb +++ /dev/null @@ -1,42 +0,0 @@ -require 'mocha/parameter_matchers/base' - -module Mocha - - module ParameterMatchers - - # :call-seq: equals(value) -> parameter_matcher - # - # Matches +Object+ equalling +value+. - # object = mock() - # object.expects(:method_1).with(equals(2)) - # object.method_1(2) - # # no error raised - # - # object = mock() - # object.expects(:method_1).with(equals(2)) - # object.method_1(3) - # # error raised, because method_1 was not called with Object equalling 3 - def equals(value) - Equals.new(value) - end - - class Equals < Base # :nodoc: - - def initialize(value) - @value = value - end - - def matches?(available_parameters) - parameter = available_parameters.shift - parameter == @value - end - - def mocha_inspect - @value.mocha_inspect - end - - end - - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/has_entries.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/has_entries.rb deleted file mode 100644 index 30cf025a59..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/has_entries.rb +++ /dev/null @@ -1,42 +0,0 @@ -require 'mocha/parameter_matchers/base' - -module Mocha - - module ParameterMatchers - - # :call-seq: has_entries(entries) -> parameter_matcher - # - # Matches +Hash+ containing all +entries+. - # object = mock() - # object.expects(:method_1).with(has_entries('key_1' => 1, 'key_2' => 2)) - # object.method_1('key_1' => 1, 'key_2' => 2, 'key_3' => 3) - # # no error raised - # - # object = mock() - # object.expects(:method_1).with(has_entries('key_1' => 1, 'key_2' => 2)) - # object.method_1('key_1' => 1, 'key_2' => 99) - # # error raised, because method_1 was not called with Hash containing entries: 'key_1' => 1, 'key_2' => 2 - def has_entries(entries) - HasEntries.new(entries) - end - - class HasEntries < Base # :nodoc: - - def initialize(entries) - @entries = entries - end - - def matches?(available_parameters) - parameter = available_parameters.shift - @entries.all? { |key, value| parameter[key] == value } - end - - def mocha_inspect - "has_entries(#{@entries.mocha_inspect})" - end - - end - - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/has_entry.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/has_entry.rb deleted file mode 100644 index b6459613d2..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/has_entry.rb +++ /dev/null @@ -1,55 +0,0 @@ -require 'mocha/parameter_matchers/base' - -module Mocha - - module ParameterMatchers - - # :call-seq: has_entry(key, value) -> parameter_matcher - # has_entry(key => value) -> parameter_matcher - # - # Matches +Hash+ containing entry with +key+ and +value+. - # object = mock() - # object.expects(:method_1).with(has_entry('key_1', 1)) - # object.method_1('key_1' => 1, 'key_2' => 2) - # # no error raised - # - # object = mock() - # object.expects(:method_1).with(has_entry('key_1' => 1)) - # object.method_1('key_1' => 1, 'key_2' => 2) - # # no error raised - # - # object = mock() - # object.expects(:method_1).with(has_entry('key_1', 1)) - # object.method_1('key_1' => 2, 'key_2' => 1) - # # error raised, because method_1 was not called with Hash containing entry: 'key_1' => 1 - # - # object = mock() - # object.expects(:method_1).with(has_entry('key_1' => 1)) - # object.method_1('key_1' => 2, 'key_2' => 1) - # # error raised, because method_1 was not called with Hash containing entry: 'key_1' => 1 - def has_entry(*options) - key, value = options.shift, options.shift - key, value = key.to_a[0][0..1] if key.is_a?(Hash) - HasEntry.new(key, value) - end - - class HasEntry < Base # :nodoc: - - def initialize(key, value) - @key, @value = key, value - end - - def matches?(available_parameters) - parameter = available_parameters.shift - parameter[@key] == @value - end - - def mocha_inspect - "has_entry(#{@key.mocha_inspect}, #{@value.mocha_inspect})" - end - - end - - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/has_key.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/has_key.rb deleted file mode 100644 index 2478152195..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/has_key.rb +++ /dev/null @@ -1,42 +0,0 @@ -require 'mocha/parameter_matchers/base' - -module Mocha - - module ParameterMatchers - - # :call-seq: has_key(key) -> parameter_matcher - # - # Matches +Hash+ containing +key+. - # object = mock() - # object.expects(:method_1).with(has_key('key_1')) - # object.method_1('key_1' => 1, 'key_2' => 2) - # # no error raised - # - # object = mock() - # object.expects(:method_1).with(has_key('key_1')) - # object.method_1('key_2' => 2) - # # error raised, because method_1 was not called with Hash containing key: 'key_1' - def has_key(key) - HasKey.new(key) - end - - class HasKey < Base # :nodoc: - - def initialize(key) - @key = key - end - - def matches?(available_parameters) - parameter = available_parameters.shift - parameter.keys.include?(@key) - end - - def mocha_inspect - "has_key(#{@key.mocha_inspect})" - end - - end - - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/has_value.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/has_value.rb deleted file mode 100644 index 2c6fe7c5e4..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/has_value.rb +++ /dev/null @@ -1,42 +0,0 @@ -require 'mocha/parameter_matchers/base' - -module Mocha - - module ParameterMatchers - - # :call-seq: has_value(value) -> parameter_matcher - # - # Matches +Hash+ containing +value+. - # object = mock() - # object.expects(:method_1).with(has_value(1)) - # object.method_1('key_1' => 1, 'key_2' => 2) - # # no error raised - # - # object = mock() - # object.expects(:method_1).with(has_value(1)) - # object.method_1('key_2' => 2) - # # error raised, because method_1 was not called with Hash containing value: 1 - def has_value(value) - HasValue.new(value) - end - - class HasValue < Base # :nodoc: - - def initialize(value) - @value = value - end - - def matches?(available_parameters) - parameter = available_parameters.shift - parameter.values.include?(@value) - end - - def mocha_inspect - "has_value(#{@value.mocha_inspect})" - end - - end - - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/includes.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/includes.rb deleted file mode 100644 index 4539a5c440..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/includes.rb +++ /dev/null @@ -1,40 +0,0 @@ -require 'mocha/parameter_matchers/base' - -module Mocha - - module ParameterMatchers - - # :call-seq: includes(item) -> parameter_matcher - # - # Matches any object that responds true to include?(item) - # object = mock() - # object.expects(:method_1).with(includes('foo')) - # object.method_1(['foo', 'bar']) - # # no error raised - # - # object.method_1(['baz']) - # # error raised, because ['baz'] does not include 'foo'. - def includes(item) - Includes.new(item) - end - - class Includes < Base # :nodoc: - - def initialize(item) - @item = item - end - - def matches?(available_parameters) - parameter = available_parameters.shift - return parameter.include?(@item) - end - - def mocha_inspect - "includes(#{@item.mocha_inspect})" - end - - end - - end - -end diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/instance_of.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/instance_of.rb deleted file mode 100644 index 49b4a478d8..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/instance_of.rb +++ /dev/null @@ -1,42 +0,0 @@ -require 'mocha/parameter_matchers/base' - -module Mocha - - module ParameterMatchers - - # :call-seq: instance_of(klass) -> parameter_matcher - # - # Matches any object that is an instance of +klass+ - # object = mock() - # object.expects(:method_1).with(instance_of(String)) - # object.method_1('string') - # # no error raised - # - # object = mock() - # object.expects(:method_1).with(instance_of(String)) - # object.method_1(99) - # # error raised, because method_1 was not called with an instance of String - def instance_of(klass) - InstanceOf.new(klass) - end - - class InstanceOf < Base # :nodoc: - - def initialize(klass) - @klass = klass - end - - def matches?(available_parameters) - parameter = available_parameters.shift - parameter.instance_of?(@klass) - end - - def mocha_inspect - "instance_of(#{@klass.mocha_inspect})" - end - - end - - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/is_a.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/is_a.rb deleted file mode 100644 index a721db5238..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/is_a.rb +++ /dev/null @@ -1,42 +0,0 @@ -require 'mocha/parameter_matchers/base' - -module Mocha - - module ParameterMatchers - - # :call-seq: is_a(klass) -> parameter_matcher - # - # Matches any object that is a +klass+ - # object = mock() - # object.expects(:method_1).with(is_a(Integer)) - # object.method_1(99) - # # no error raised - # - # object = mock() - # object.expects(:method_1).with(is_a(Integer)) - # object.method_1('string') - # # error raised, because method_1 was not called with an Integer - def is_a(klass) - IsA.new(klass) - end - - class IsA < Base # :nodoc: - - def initialize(klass) - @klass = klass - end - - def matches?(available_parameters) - parameter = available_parameters.shift - parameter.is_a?(@klass) - end - - def mocha_inspect - "is_a(#{@klass.mocha_inspect})" - end - - end - - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/kind_of.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/kind_of.rb deleted file mode 100644 index 710d709d0a..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/kind_of.rb +++ /dev/null @@ -1,42 +0,0 @@ -require 'mocha/parameter_matchers/base' - -module Mocha - - module ParameterMatchers - - # :call-seq: kind_of(klass) -> parameter_matcher - # - # Matches any object that is a kind of +klass+ - # object = mock() - # object.expects(:method_1).with(kind_of(Integer)) - # object.method_1(99) - # # no error raised - # - # object = mock() - # object.expects(:method_1).with(kind_of(Integer)) - # object.method_1('string') - # # error raised, because method_1 was not called with a kind of Integer - def kind_of(klass) - KindOf.new(klass) - end - - class KindOf < Base # :nodoc: - - def initialize(klass) - @klass = klass - end - - def matches?(available_parameters) - parameter = available_parameters.shift - parameter.kind_of?(@klass) - end - - def mocha_inspect - "kind_of(#{@klass.mocha_inspect})" - end - - end - - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/not.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/not.rb deleted file mode 100644 index ec48ade3d2..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/not.rb +++ /dev/null @@ -1,42 +0,0 @@ -require 'mocha/parameter_matchers/base' - -module Mocha - - module ParameterMatchers - - # :call-seq: Not(matcher) -> parameter_matcher - # - # Matches if +matcher+ does not match. - # object = mock() - # object.expects(:method_1).with(Not(includes(1))) - # object.method_1([0, 2, 3]) - # # no error raised - # - # object = mock() - # object.expects(:method_1).with(Not(includes(1))) - # object.method_1([0, 1, 2, 3]) - # # error raised, because method_1 was not called with object not including 1 - def Not(matcher) - Not.new(matcher) - end - - class Not < Base # :nodoc: - - def initialize(matcher) - @matcher = matcher - end - - def matches?(available_parameters) - parameter = available_parameters.shift - !@matcher.matches?([parameter]) - end - - def mocha_inspect - "Not(#{@matcher.mocha_inspect})" - end - - end - - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/object.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/object.rb deleted file mode 100644 index f3a639bfa2..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/object.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'mocha/parameter_matchers/equals' - -class Object - - def to_matcher - Mocha::ParameterMatchers::Equals.new(self) - end - -end diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/optionally.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/optionally.rb deleted file mode 100644 index bb96251875..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/optionally.rb +++ /dev/null @@ -1,33 +0,0 @@ -module Mocha - - module ParameterMatchers - - def optionally(*matchers) - Optionally.new(*matchers) - end - - class Optionally < Base # :nodoc: - - def initialize(*parameters) - @matchers = parameters.map { |parameter| parameter.to_matcher } - end - - def matches?(available_parameters) - index = 0 - while (available_parameters.length > 0) && (index < @matchers.length) do - matcher = @matchers[index] - return false unless matcher.matches?(available_parameters) - index += 1 - end - return true - end - - def mocha_inspect - "optionally(#{@matchers.map { |matcher| matcher.mocha_inspect }.join(", ") })" - end - - end - - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/regexp_matches.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/regexp_matches.rb deleted file mode 100644 index cc46436eb0..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/parameter_matchers/regexp_matches.rb +++ /dev/null @@ -1,43 +0,0 @@ -require 'mocha/parameter_matchers/base' - -module Mocha - - module ParameterMatchers - - # :call-seq: regexp_matches(regexp) -> parameter_matcher - # - # Matches any object that matches the regular expression, +regexp+. - # object = mock() - # object.expects(:method_1).with(regexp_matches(/e/)) - # object.method_1('hello') - # # no error raised - # - # object = mock() - # object.expects(:method_1).with(regexp_matches(/a/)) - # object.method_1('hello') - # # error raised, because method_1 was not called with a parameter that matched the - # # regular expression - def regexp_matches(regexp) - RegexpMatches.new(regexp) - end - - class RegexpMatches < Base # :nodoc: - - def initialize(regexp) - @regexp = regexp - end - - def matches?(available_parameters) - parameter = available_parameters.shift - parameter =~ @regexp - end - - def mocha_inspect - "regexp_matches(#{@regexp.mocha_inspect})" - end - - end - - end - -end diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/parameters_matcher.rb b/vendor/gems/mocha-0.5.6/lib/mocha/parameters_matcher.rb deleted file mode 100644 index d43ae43756..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/parameters_matcher.rb +++ /dev/null @@ -1,37 +0,0 @@ -require 'mocha/inspect' -require 'mocha/parameter_matchers' - -module Mocha - - class ParametersMatcher - - def initialize(expected_parameters = [ParameterMatchers::AnyParameters.new], &matching_block) - @expected_parameters, @matching_block = expected_parameters, matching_block - end - - def match?(actual_parameters = []) - if @matching_block - return @matching_block.call(*actual_parameters) - else - return parameters_match?(actual_parameters) - end - end - - def parameters_match?(actual_parameters) - matchers.all? { |matcher| matcher.matches?(actual_parameters) } && (actual_parameters.length == 0) - end - - def mocha_inspect - signature = matchers.mocha_inspect - signature = signature.gsub(/^\[|\]$/, '') - signature = signature.gsub(/^\{|\}$/, '') if matchers.length == 1 - "(#{signature})" - end - - def matchers - @expected_parameters.map { |parameter| parameter.to_matcher } - end - - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/pretty_parameters.rb b/vendor/gems/mocha-0.5.6/lib/mocha/pretty_parameters.rb deleted file mode 100644 index 59ed636f89..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/pretty_parameters.rb +++ /dev/null @@ -1,28 +0,0 @@ -require 'mocha/inspect' - -module Mocha - - class PrettyParameters - - def initialize(params) - @params = params - @params_string = params.mocha_inspect - end - - def pretty - remove_outer_array_braces! - remove_outer_hash_braces! - @params_string - end - - def remove_outer_array_braces! - @params_string = @params_string.gsub(/^\[|\]$/, '') - end - - def remove_outer_hash_braces! - @params_string = @params_string.gsub(/^\{|\}$/, '') if @params.length == 1 - end - - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/return_values.rb b/vendor/gems/mocha-0.5.6/lib/mocha/return_values.rb deleted file mode 100644 index b4852c5f6c..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/return_values.rb +++ /dev/null @@ -1,34 +0,0 @@ -require 'mocha/single_return_value' - -module Mocha # :nodoc: - - class ReturnValues # :nodoc: - - def self.build(*values) - new(*values.map { |value| SingleReturnValue.new(value) }) - end - - attr_accessor :values - - def initialize(*values) - @values = values - end - - def next - case @values.length - when 0 - nil - when 1 - @values.first.evaluate - else - @values.shift.evaluate - end - end - - def +(other) - self.class.new(*(@values + other.values)) - end - - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/sequence.rb b/vendor/gems/mocha-0.5.6/lib/mocha/sequence.rb deleted file mode 100644 index ed9852e0c2..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/sequence.rb +++ /dev/null @@ -1,42 +0,0 @@ -module Mocha # :nodoc: - - class Sequence - - class InSequenceOrderingConstraint - - def initialize(sequence, index) - @sequence, @index = sequence, index - end - - def allows_invocation_now? - @sequence.satisfied_to_index?(@index) - end - - def mocha_inspect - "in sequence #{@sequence.mocha_inspect}" - end - - end - - def initialize(name) - @name = name - @expectations = [] - end - - def constrain_as_next_in_sequence(expectation) - index = @expectations.length - @expectations << expectation - expectation.add_ordering_constraint(InSequenceOrderingConstraint.new(self, index)) - end - - def satisfied_to_index?(index) - @expectations[0...index].all? { |expectation| expectation.satisfied? } - end - - def mocha_inspect - "#{@name.mocha_inspect}" - end - - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/setup_and_teardown.rb b/vendor/gems/mocha-0.5.6/lib/mocha/setup_and_teardown.rb deleted file mode 100644 index 034ce1d6b6..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/setup_and_teardown.rb +++ /dev/null @@ -1,23 +0,0 @@ -require 'mocha/central' - -module Mocha - - module SetupAndTeardown - - def setup_stubs - $stubba = Mocha::Central.new - end - - def verify_stubs - $stubba.verify_all { yield if block_given? } if $stubba - end - - def teardown_stubs - if $stubba then - $stubba.unstub_all - $stubba = nil - end - end - - end -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/single_return_value.rb b/vendor/gems/mocha-0.5.6/lib/mocha/single_return_value.rb deleted file mode 100644 index f420b8b8cf..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/single_return_value.rb +++ /dev/null @@ -1,24 +0,0 @@ -require 'mocha/is_a' -require 'mocha/deprecation' - -module Mocha # :nodoc: - - class SingleReturnValue # :nodoc: - - def initialize(value) - @value = value - end - - def evaluate - if @value.__is_a__(Proc) then - message = 'use of Expectation#returns with instance of Proc - see Expectation#returns RDoc for alternatives' - Deprecation.warning(message) - @value.call - else - @value - end - end - - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/single_yield.rb b/vendor/gems/mocha-0.5.6/lib/mocha/single_yield.rb deleted file mode 100644 index 5af5716213..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/single_yield.rb +++ /dev/null @@ -1,18 +0,0 @@ -module Mocha # :nodoc: - - class SingleYield # :nodoc: - - attr_reader :parameters - - def initialize(*parameters) - @parameters = parameters - end - - def each - yield(@parameters) - end - - end - -end - diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/standalone.rb b/vendor/gems/mocha-0.5.6/lib/mocha/standalone.rb deleted file mode 100644 index 8e3a7cefcb..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/standalone.rb +++ /dev/null @@ -1,32 +0,0 @@ -require 'mocha/auto_verify' -require 'mocha/parameter_matchers' -require 'mocha/setup_and_teardown' - -module Mocha - - module Standalone - - include AutoVerify - include ParameterMatchers - include SetupAndTeardown - - def mocha_setup - setup_stubs - end - - def mocha_verify(&block) - verify_mocks(&block) - verify_stubs(&block) - end - - def mocha_teardown - begin - teardown_mocks - ensure - teardown_stubs - end - end - - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/stub.rb b/vendor/gems/mocha-0.5.6/lib/mocha/stub.rb deleted file mode 100644 index 1b3cccb8a6..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/stub.rb +++ /dev/null @@ -1,18 +0,0 @@ -require 'mocha/expectation' - -module Mocha # :nodoc: - - class Stub < Expectation # :nodoc: - - def initialize(mock, method_name, backtrace = nil) - super - @expected_count = Range.at_least(0) - end - - def verify - true - end - - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/test_case_adapter.rb b/vendor/gems/mocha-0.5.6/lib/mocha/test_case_adapter.rb deleted file mode 100644 index dc7e33b689..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/test_case_adapter.rb +++ /dev/null @@ -1,49 +0,0 @@ -require 'mocha/expectation_error' - -module Mocha - - module TestCaseAdapter - - def self.included(base) - base.class_eval do - - alias_method :run_before_mocha_test_case_adapter, :run - - def run(result) - yield(Test::Unit::TestCase::STARTED, name) - @_result = result - begin - mocha_setup - begin - setup - __send__(@method_name) - mocha_verify { add_assertion } - rescue Mocha::ExpectationError => e - add_failure(e.message, e.backtrace) - rescue Test::Unit::AssertionFailedError => e - add_failure(e.message, e.backtrace) - rescue StandardError, ScriptError - add_error($!) - ensure - begin - teardown - rescue Test::Unit::AssertionFailedError => e - add_failure(e.message, e.backtrace) - rescue StandardError, ScriptError - add_error($!) - end - end - ensure - mocha_teardown - end - result.add_run - yield(Test::Unit::TestCase::FINISHED, name) - end - - end - - end - - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha/yield_parameters.rb b/vendor/gems/mocha-0.5.6/lib/mocha/yield_parameters.rb deleted file mode 100644 index cb58985080..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha/yield_parameters.rb +++ /dev/null @@ -1,31 +0,0 @@ -require 'mocha/no_yields' -require 'mocha/single_yield' -require 'mocha/multiple_yields' - -module Mocha # :nodoc: - - class YieldParameters # :nodoc: - - def initialize - @parameter_groups = [] - end - - def next_invocation - case @parameter_groups.length - when 0; NoYields.new - when 1; @parameter_groups.first - else @parameter_groups.shift - end - end - - def add(*parameters) - @parameter_groups << SingleYield.new(*parameters) - end - - def multiple_add(*parameter_groups) - @parameter_groups << MultipleYields.new(*parameter_groups) - end - - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/lib/mocha_standalone.rb b/vendor/gems/mocha-0.5.6/lib/mocha_standalone.rb deleted file mode 100644 index ce605811a5..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/mocha_standalone.rb +++ /dev/null @@ -1,2 +0,0 @@ -require 'mocha/standalone' -require 'mocha/object' diff --git a/vendor/gems/mocha-0.5.6/lib/stubba.rb b/vendor/gems/mocha-0.5.6/lib/stubba.rb deleted file mode 100644 index eade747f60..0000000000 --- a/vendor/gems/mocha-0.5.6/lib/stubba.rb +++ /dev/null @@ -1,2 +0,0 @@ -# for backwards compatibility -require 'mocha' \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/._deprecation_disabler.rb b/vendor/gems/mocha-0.5.6/test/._deprecation_disabler.rb deleted file mode 100644 index 64a1d063ad9bd1810b28f48b53d10f8bb76eb695..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_IB@I*x(lG;w zCDF7oBE&_L^K>83Qh_W(6KjQ9 E0G!7c&Hw-a diff --git a/vendor/gems/mocha-0.5.6/test/._execution_point.rb b/vendor/gems/mocha-0.5.6/test/._execution_point.rb deleted file mode 100644 index dd624fd16f8feae7c2c2c567245b1ed03d2beacc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 176 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aUBqY_#1$j2;dkJ62u_Ir3h3C(lG;w zCD61nBE&_L^K>83QX#Bb0EH|U AmjD0& diff --git a/vendor/gems/mocha-0.5.6/test/._method_definer.rb b/vendor/gems/mocha-0.5.6/test/._method_definer.rb deleted file mode 100644 index d51c1adf493adce28d877590a8fb4caca43ad511..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_Ir3_RG(lG;w zCDF7oBE&_L^K>83Qh_W(AhQ+# DpE?)M diff --git a/vendor/gems/mocha-0.5.6/test/._test_helper.rb b/vendor/gems/mocha-0.5.6/test/._test_helper.rb deleted file mode 100644 index ef386d597ccdd4c023e6dc03f6196364e480edf3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_IB?(jt(lG;w zL2P8e2;@M;MU(S$^>P!Fi}G_5OHzyVk`s$kOBkw?^K(jb^Av0qObo3Rax(K$flMH) F766-97s>zt diff --git a/vendor/gems/mocha-0.5.6/test/._test_runner.rb b/vendor/gems/mocha-0.5.6/test/._test_runner.rb deleted file mode 100644 index 614cc3f52b544402d0f68569852928eeac54cb0d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_IB@a{z(lG;w zrO>o7BE&_L^K$Vqox1Ojhs@R)|o50+1L3ClDI}aUBqY_#1$j2;dkJ62zdzvKFWmq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r9xP>0Jjtv AEdT%j diff --git a/vendor/gems/mocha-0.5.6/test/acceptance/._mocha_acceptance_test.rb b/vendor/gems/mocha-0.5.6/test/acceptance/._mocha_acceptance_test.rb deleted file mode 100644 index 39f36f8772b1e54e0abeef50dfada45a0b1d3ffd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62zb*+Y3|*(lG;w zCDF7oBE&_L^K>83Qh_WB18ap^ E0MF?dY5)KL diff --git a/vendor/gems/mocha-0.5.6/test/acceptance/._mock_with_initializer_block_acceptance_test.rb b/vendor/gems/mocha-0.5.6/test/acceptance/._mock_with_initializer_block_acceptance_test.rb deleted file mode 100644 index 8e3a0e715c7240cf22a001636d61391ff8c76207..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62zeT=PFPsNXHBy zmPFIWh!7V|&d=4$O-wGz&rK{zE!Im;EJ`h5s7}t$Db3AOuvIXyR>;ZBO9iqFEv*%5 E0Wm)rhX4Qo diff --git a/vendor/gems/mocha-0.5.6/test/acceptance/._mocked_methods_dispatch_acceptance_test.rb b/vendor/gems/mocha-0.5.6/test/acceptance/._mocked_methods_dispatch_acceptance_test.rb deleted file mode 100644 index de0671d7e33a6e5d1b7cc46caef5152f8c442656..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_I#RF6d(lG;w zCDF7oBE&_L^K>83Qh_W(Lu-Xv E0GHkuzW@LL diff --git a/vendor/gems/mocha-0.5.6/test/acceptance/._optional_parameters_acceptance_test.rb b/vendor/gems/mocha-0.5.6/test/acceptance/._optional_parameters_acceptance_test.rb deleted file mode 100644 index b21de1fbcabbd5bd51641ff36067367a47e70510..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62zeT*8`{&q+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r2<)|M%D_o E00J-=_y7O^ diff --git a/vendor/gems/mocha-0.5.6/test/acceptance/._parameter_matcher_acceptance_test.rb b/vendor/gems/mocha-0.5.6/test/acceptance/._parameter_matcher_acceptance_test.rb deleted file mode 100644 index 421ef5a2964d9913641dd036d59e1e512653909c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62zdn$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62zdz$_!Kr(lG;w zCDF7oBE&_L^K>83Qh_XEOKXK% E0GM?b#Q*>R diff --git a/vendor/gems/mocha-0.5.6/test/acceptance/._sequence_acceptance_test.rb b/vendor/gems/mocha-0.5.6/test/acceptance/._sequence_acceptance_test.rb deleted file mode 100644 index 8bb33ec9fa28302261235a3aa27f347c811aa49b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62zdz{0*oSq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV#BTPx&b=A{CerWV!; FwEzR081w)D diff --git a/vendor/gems/mocha-0.5.6/test/acceptance/._standalone_acceptance_test.rb b/vendor/gems/mocha-0.5.6/test/acceptance/._standalone_acceptance_test.rb deleted file mode 100644 index 68a7236cd365c83b3213cab92c1bd182103b29df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 176 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aUBqY_#1$j2;dkJ62u_I#R60c(lG;w zCD61nBE&_L^K>83QX#Bb0DFrU AdH?_b diff --git a/vendor/gems/mocha-0.5.6/test/acceptance/._stubba_acceptance_test.rb b/vendor/gems/mocha-0.5.6/test/acceptance/._stubba_acceptance_test.rb deleted file mode 100644 index 5140f122893bf1503e3a40f4fed29737fba5b4c6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_I#RgOg(lG;w zrO>o7BE&_L^K.method(any_parameters) - expected calls: 0, actual calls: 1'], failure_messages - end - - def test_should_pass_if_method_is_expected_twice_and_is_called_twice - test_result = run_test do - mock = mock('mock') - mock.expects(:method).times(2) - 2.times { mock.method } - end - assert_passed(test_result) - end - - def test_should_fail_if_method_is_expected_twice_but_is_called_once - test_result = run_test do - mock = mock('mock') - mock.expects(:method).times(2) - 1.times { mock.method } - end - assert_failed(test_result) - failure_messages = test_result.failures.map { |failure| failure.message } - assert_equal ['#.method(any_parameters) - expected calls: 2, actual calls: 1'], failure_messages - end - - def test_should_fail_if_method_is_expected_twice_but_is_called_three_times - test_result = run_test do - mock = mock('mock') - mock.expects(:method).times(2) - 3.times { mock.method } - end - assert_failed(test_result) - failure_messages = test_result.failures.map { |failure| failure.message } - assert_equal ['#.method(any_parameters) - expected calls: 2, actual calls: 3'], failure_messages - end - - def test_should_pass_if_method_is_expected_between_two_and_four_times_and_is_called_twice - test_result = run_test do - mock = mock('mock') - mock.expects(:method).times(2..4) - 2.times { mock.method } - end - assert_passed(test_result) - end - - def test_should_pass_if_method_is_expected_between_two_and_four_times_and_is_called_three_times - test_result = run_test do - mock = mock('mock') - mock.expects(:method).times(2..4) - 3.times { mock.method } - end - assert_passed(test_result) - end - - def test_should_pass_if_method_is_expected_between_two_and_four_times_and_is_called_four_times - test_result = run_test do - mock = mock('mock') - mock.expects(:method).times(2..4) - 4.times { mock.method } - end - assert_passed(test_result) - end - - def test_should_fail_if_method_is_expected_between_two_and_four_times_and_is_called_once - test_result = run_test do - mock = mock('mock') - mock.expects(:method).times(2..4) - 1.times { mock.method } - end - assert_failed(test_result) - failure_messages = test_result.failures.map { |failure| failure.message } - assert_equal ['#.method(any_parameters) - expected calls: 2..4, actual calls: 1'], failure_messages - end - - def test_should_fail_if_method_is_expected_between_two_and_four_times_and_is_called_five_times - test_result = run_test do - mock = mock('mock') - mock.expects(:method).times(2..4) - 5.times { mock.method } - end - assert_failed(test_result) - failure_messages = test_result.failures.map { |failure| failure.message } - assert_equal ['#.method(any_parameters) - expected calls: 2..4, actual calls: 5'], failure_messages - end - - def test_should_pass_if_method_is_expected_at_least_once_and_is_called_once - test_result = run_test do - mock = mock('mock') - mock.expects(:method).at_least_once - 1.times { mock.method } - end - assert_passed(test_result) - end - - def test_should_pass_if_method_is_expected_at_least_once_and_is_called_twice - test_result = run_test do - mock = mock('mock') - mock.expects(:method).at_least_once - 2.times { mock.method } - end - assert_passed(test_result) - end - - def test_should_fail_if_method_is_expected_at_least_once_but_is_never_called - test_result = run_test do - mock = mock('mock') - mock.expects(:method).at_least_once - 0.times { mock.method } - end - assert_failed(test_result) - failure_messages = test_result.failures.map { |failure| failure.message } - assert_equal ['#.method(any_parameters) - expected calls: at least 1, actual calls: 0'], failure_messages - end - - def test_should_pass_if_method_is_expected_at_most_once_and_is_never_called - test_result = run_test do - mock = mock('mock') - mock.expects(:method).at_most_once - 0.times { mock.method } - end - assert_passed(test_result) - end - - def test_should_pass_if_method_is_expected_at_most_once_and_called_once - test_result = run_test do - mock = mock('mock') - mock.expects(:method).at_most_once - 1.times { mock.method } - end - assert_passed(test_result) - end - - def test_should_fail_if_method_is_expected_at_most_once_but_is_called_twice - test_result = run_test do - mock = mock('mock') - mock.expects(:method).at_most_once - 2.times { mock.method } - end - assert_failed(test_result) - failure_messages = test_result.failures.map { |failure| failure.message } - assert_equal ['#.method(any_parameters) - expected calls: at most 1, actual calls: 2'], failure_messages - end - - def test_should_pass_if_method_is_never_expected_and_is_never_called_even_if_everything_is_stubbed - test_result = run_test do - stub = stub_everything('stub') - stub.expects(:method).never - 0.times { stub.method } - end - assert_passed(test_result) - end - - def test_should_fail_if_method_is_never_expected_but_is_called_once_even_if_everything_is_stubbed - test_result = run_test do - stub = stub_everything('stub') - stub.expects(:method).never - 1.times { stub.method } - end - assert_failed(test_result) - failure_messages = test_result.failures.map { |failure| failure.message } - assert_equal ['#.method(any_parameters) - expected calls: 0, actual calls: 1'], failure_messages - end - -end diff --git a/vendor/gems/mocha-0.5.6/test/acceptance/mocha_acceptance_test.rb b/vendor/gems/mocha-0.5.6/test/acceptance/mocha_acceptance_test.rb deleted file mode 100644 index 4e38b4e574..0000000000 --- a/vendor/gems/mocha-0.5.6/test/acceptance/mocha_acceptance_test.rb +++ /dev/null @@ -1,98 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "test_helper") -require 'mocha' - -class MochaAcceptanceTest < Test::Unit::TestCase - - class Rover - - def initialize(left_track, right_track, steps_per_metre, steps_per_degree) - @left_track, @right_track, @steps_per_metre, @steps_per_degree = left_track, right_track, steps_per_metre, steps_per_degree - end - - def forward(metres) - @left_track.step(metres * @steps_per_metre) - @right_track.step(metres * @steps_per_metre) - wait - end - - def backward(metres) - forward(-metres) - end - - def left(degrees) - @left_track.step(-degrees * @steps_per_degree) - @right_track.step(+degrees * @steps_per_degree) - wait - end - - def right(degrees) - left(-degrees) - end - - def wait - while (@left_track.moving? or @right_track.moving?); end - end - - end - - def test_should_step_both_tracks_forward_ten_steps - left_track = mock('left_track') - right_track = mock('right_track') - steps_per_metre = 5 - rover = Rover.new(left_track, right_track, steps_per_metre, nil) - - left_track.expects(:step).with(10) - right_track.expects(:step).with(10) - - left_track.stubs(:moving?).returns(false) - right_track.stubs(:moving?).returns(false) - - rover.forward(2) - end - - def test_should_step_both_tracks_backward_ten_steps - left_track = mock('left_track') - right_track = mock('right_track') - steps_per_metre = 5 - rover = Rover.new(left_track, right_track, steps_per_metre, nil) - - left_track.expects(:step).with(-10) - right_track.expects(:step).with(-10) - - left_track.stubs(:moving?).returns(false) - right_track.stubs(:moving?).returns(false) - - rover.backward(2) - end - - def test_should_step_left_track_forwards_five_steps_and_right_track_backwards_five_steps - left_track = mock('left_track') - right_track = mock('right_track') - steps_per_degree = 5.0 / 90.0 - rover = Rover.new(left_track, right_track, nil, steps_per_degree) - - left_track.expects(:step).with(+5) - right_track.expects(:step).with(-5) - - left_track.stubs(:moving?).returns(false) - right_track.stubs(:moving?).returns(false) - - rover.right(90) - end - - def test_should_step_left_track_backwards_five_steps_and_right_track_forwards_five_steps - left_track = mock('left_track') - right_track = mock('right_track') - steps_per_degree = 5.0 / 90.0 - rover = Rover.new(left_track, right_track, nil, steps_per_degree) - - left_track.expects(:step).with(-5) - right_track.expects(:step).with(+5) - - left_track.stubs(:moving?).returns(false) - right_track.stubs(:moving?).returns(false) - - rover.left(90) - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/acceptance/mock_with_initializer_block_acceptance_test.rb b/vendor/gems/mocha-0.5.6/test/acceptance/mock_with_initializer_block_acceptance_test.rb deleted file mode 100644 index 51488e61fd..0000000000 --- a/vendor/gems/mocha-0.5.6/test/acceptance/mock_with_initializer_block_acceptance_test.rb +++ /dev/null @@ -1,44 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "test_helper") -require 'mocha' -require 'test_runner' - -class MockWithInitializerBlockAcceptanceTest < Test::Unit::TestCase - - include TestRunner - - def test_should_expect_two_method_invocations_and_receive_both_of_them - test_result = run_test do - mock = mock() do - expects(:method_1) - expects(:method_2) - end - mock.method_1 - mock.method_2 - end - assert_passed(test_result) - end - - def test_should_expect_two_method_invocations_but_receive_only_one_of_them - test_result = run_test do - mock = mock() do - expects(:method_1) - expects(:method_2) - end - mock.method_1 - end - assert_failed(test_result) - end - - def test_should_stub_methods - test_result = run_test do - mock = mock() do - stubs(:method_1).returns(1) - stubs(:method_2).returns(2) - end - assert_equal 1, mock.method_1 - assert_equal 2, mock.method_2 - end - assert_passed(test_result) - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/acceptance/mocked_methods_dispatch_acceptance_test.rb b/vendor/gems/mocha-0.5.6/test/acceptance/mocked_methods_dispatch_acceptance_test.rb deleted file mode 100644 index d77021553d..0000000000 --- a/vendor/gems/mocha-0.5.6/test/acceptance/mocked_methods_dispatch_acceptance_test.rb +++ /dev/null @@ -1,71 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "test_helper") -require 'mocha' -require 'test_runner' - -class MockedMethodDispatchAcceptanceTest < Test::Unit::TestCase - - include TestRunner - - def test_should_find_latest_matching_expectation - test_result = run_test do - mock = mock() - mock.stubs(:method).returns(1) - mock.stubs(:method).returns(2) - assert_equal 2, mock.method - assert_equal 2, mock.method - assert_equal 2, mock.method - end - assert_passed(test_result) - end - - def test_should_find_latest_expectation_which_has_not_stopped_matching - test_result = run_test do - mock = mock() - mock.stubs(:method).returns(1) - mock.stubs(:method).once.returns(2) - assert_equal 2, mock.method - assert_equal 1, mock.method - assert_equal 1, mock.method - end - assert_passed(test_result) - end - - def test_should_keep_finding_later_stub_and_so_never_satisfy_earlier_expectation - test_result = run_test do - mock = mock() - mock.expects(:method).returns(1) - mock.stubs(:method).returns(2) - assert_equal 2, mock.method - assert_equal 2, mock.method - assert_equal 2, mock.method - end - assert_failed(test_result) - end - - def test_should_find_later_expectation_until_it_stops_matching_then_find_earlier_stub - test_result = run_test do - mock = mock() - mock.stubs(:method).returns(1) - mock.expects(:method).returns(2) - assert_equal 2, mock.method - assert_equal 1, mock.method - assert_equal 1, mock.method - end - assert_passed(test_result) - end - - def test_should_find_latest_expectation_with_range_of_expected_invocation_count_which_has_not_stopped_matching - test_result = run_test do - mock = mock() - mock.stubs(:method).returns(1) - mock.stubs(:method).times(2..3).returns(2) - assert_equal 2, mock.method - assert_equal 2, mock.method - assert_equal 2, mock.method - assert_equal 1, mock.method - assert_equal 1, mock.method - end - assert_passed(test_result) - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/acceptance/optional_parameters_acceptance_test.rb b/vendor/gems/mocha-0.5.6/test/acceptance/optional_parameters_acceptance_test.rb deleted file mode 100644 index 3a6f8322ef..0000000000 --- a/vendor/gems/mocha-0.5.6/test/acceptance/optional_parameters_acceptance_test.rb +++ /dev/null @@ -1,63 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "test_helper") -require 'mocha' -require 'test_runner' - -class OptionalParameterMatcherAcceptanceTest < Test::Unit::TestCase - - include TestRunner - - def test_should_pass_if_all_required_parameters_match_and_no_optional_parameters_are_supplied - test_result = run_test do - mock = mock() - mock.expects(:method).with(1, 2, optionally(3, 4)) - mock.method(1, 2) - end - assert_passed(test_result) - end - - def test_should_pass_if_all_required_and_optional_parameters_match_and_some_optional_parameters_are_supplied - test_result = run_test do - mock = mock() - mock.expects(:method).with(1, 2, optionally(3, 4)) - mock.method(1, 2, 3) - end - assert_passed(test_result) - end - - def test_should_pass_if_all_required_and_optional_parameters_match_and_all_optional_parameters_are_supplied - test_result = run_test do - mock = mock() - mock.expects(:method).with(1, 2, optionally(3, 4)) - mock.method(1, 2, 3, 4) - end - assert_passed(test_result) - end - - def test_should_fail_if_all_required_and_optional_parameters_match_but_too_many_optional_parameters_are_supplied - test_result = run_test do - mock = mock() - mock.expects(:method).with(1, 2, optionally(3, 4)) - mock.method(1, 2, 3, 4, 5) - end - assert_failed(test_result) - end - - def test_should_fail_if_all_required_parameters_match_but_some_optional_parameters_do_not_match - test_result = run_test do - mock = mock() - mock.expects(:method).with(1, 2, optionally(3, 4)) - mock.method(1, 2, 4) - end - assert_failed(test_result) - end - - def test_should_fail_if_all_required_parameters_match_but_no_optional_parameters_match - test_result = run_test do - mock = mock() - mock.expects(:method).with(1, 2, optionally(3, 4)) - mock.method(1, 2, 4, 5) - end - assert_failed(test_result) - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/acceptance/parameter_matcher_acceptance_test.rb b/vendor/gems/mocha-0.5.6/test/acceptance/parameter_matcher_acceptance_test.rb deleted file mode 100644 index c23880d16f..0000000000 --- a/vendor/gems/mocha-0.5.6/test/acceptance/parameter_matcher_acceptance_test.rb +++ /dev/null @@ -1,117 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "test_helper") -require 'mocha' -require 'test_runner' - -class ParameterMatcherAcceptanceTest < Test::Unit::TestCase - - include TestRunner - - def test_should_match_hash_parameter_with_specified_key - test_result = run_test do - mock = mock() - mock.expects(:method).with(has_key(:key_1)) - mock.method(:key_1 => 'value_1', :key_2 => 'value_2') - end - assert_passed(test_result) - end - - def test_should_not_match_hash_parameter_with_specified_key - test_result = run_test do - mock = mock() - mock.expects(:method).with(has_key(:key_1)) - mock.method(:key_2 => 'value_2') - end - assert_failed(test_result) - end - - def test_should_match_hash_parameter_with_specified_value - test_result = run_test do - mock = mock() - mock.expects(:method).with(has_value('value_1')) - mock.method(:key_1 => 'value_1', :key_2 => 'value_2') - end - assert_passed(test_result) - end - - def test_should_not_match_hash_parameter_with_specified_value - test_result = run_test do - mock = mock() - mock.expects(:method).with(has_value('value_1')) - mock.method(:key_2 => 'value_2') - end - assert_failed(test_result) - end - - def test_should_match_hash_parameter_with_specified_key_value_pair - test_result = run_test do - mock = mock() - mock.expects(:method).with(has_entry(:key_1, 'value_1')) - mock.method(:key_1 => 'value_1', :key_2 => 'value_2') - end - assert_passed(test_result) - end - - def test_should_not_match_hash_parameter_with_specified_key_value_pair - test_result = run_test do - mock = mock() - mock.expects(:method).with(has_entry(:key_1, 'value_2')) - mock.method(:key_1 => 'value_1', :key_2 => 'value_2') - end - assert_failed(test_result) - end - - def test_should_match_hash_parameter_with_specified_hash_entry - test_result = run_test do - mock = mock() - mock.expects(:method).with(has_entry(:key_1 => 'value_1')) - mock.method(:key_1 => 'value_1', :key_2 => 'value_2') - end - assert_passed(test_result) - end - - def test_should_not_match_hash_parameter_with_specified_hash_entry - test_result = run_test do - mock = mock() - mock.expects(:method).with(has_entry(:key_1 => 'value_2')) - mock.method(:key_1 => 'value_1', :key_2 => 'value_2') - end - assert_failed(test_result) - end - - def test_should_match_hash_parameter_with_specified_entries - test_result = run_test do - mock = mock() - mock.expects(:method).with(has_entries(:key_1 => 'value_1', :key_2 => 'value_2')) - mock.method(:key_1 => 'value_1', :key_2 => 'value_2', :key_3 => 'value_3') - end - assert_passed(test_result) - end - - def test_should_not_match_hash_parameter_with_specified_entries - test_result = run_test do - mock = mock() - mock.expects(:method).with(has_entries(:key_1 => 'value_1', :key_2 => 'value_2')) - mock.method(:key_1 => 'value_1', :key_2 => 'value_3') - end - assert_failed(test_result) - end - - def test_should_match_parameter_that_matches_regular_expression - test_result = run_test do - mock = mock() - mock.expects(:method).with(regexp_matches(/meter/)) - mock.method('this parameter should match') - end - assert_passed(test_result) - end - - def test_should_not_match_parameter_that_does_not_match_regular_expression - test_result = run_test do - mock = mock() - mock.expects(:method).with(regexp_matches(/something different/)) - mock.method('this parameter should not match') - end - assert_failed(test_result) - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/acceptance/partial_mocks_acceptance_test.rb b/vendor/gems/mocha-0.5.6/test/acceptance/partial_mocks_acceptance_test.rb deleted file mode 100644 index 20fc7b84ec..0000000000 --- a/vendor/gems/mocha-0.5.6/test/acceptance/partial_mocks_acceptance_test.rb +++ /dev/null @@ -1,40 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "test_helper") -require 'mocha' -require 'test_runner' - -class PartialMockAcceptanceTest < Test::Unit::TestCase - - include TestRunner - - def test_should_pass_if_all_expectations_are_satisfied - test_result = run_test do - partial_mock_one = "partial_mock_one" - partial_mock_two = "partial_mock_two" - - partial_mock_one.expects(:first) - partial_mock_one.expects(:second) - partial_mock_two.expects(:third) - - partial_mock_one.first - partial_mock_one.second - partial_mock_two.third - end - assert_passed(test_result) - end - - def test_should_fail_if_all_expectations_are_not_satisfied - test_result = run_test do - partial_mock_one = "partial_mock_one" - partial_mock_two = "partial_mock_two" - - partial_mock_one.expects(:first) - partial_mock_one.expects(:second) - partial_mock_two.expects(:third) - - partial_mock_one.first - partial_mock_two.third - end - assert_failed(test_result) - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/acceptance/sequence_acceptance_test.rb b/vendor/gems/mocha-0.5.6/test/acceptance/sequence_acceptance_test.rb deleted file mode 100644 index 3be6e7d757..0000000000 --- a/vendor/gems/mocha-0.5.6/test/acceptance/sequence_acceptance_test.rb +++ /dev/null @@ -1,179 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "test_helper") -require 'mocha' -require 'test_runner' - -class SequenceAcceptanceTest < Test::Unit::TestCase - - include TestRunner - - def test_should_constrain_invocations_to_occur_in_expected_order - test_result = run_test do - mock = mock() - sequence = sequence('one') - - mock.expects(:first).in_sequence(sequence) - mock.expects(:second).in_sequence(sequence) - - mock.second - end - assert_failed(test_result) - end - - def test_should_allow_invocations_in_sequence - test_result = run_test do - mock = mock() - sequence = sequence('one') - - mock.expects(:first).in_sequence(sequence) - mock.expects(:second).in_sequence(sequence) - - mock.first - mock.second - end - assert_passed(test_result) - end - - def test_should_constrain_invocations_to_occur_in_expected_order_even_if_expected_on_different_mocks - test_result = run_test do - mock_one = mock('1') - mock_two = mock('2') - sequence = sequence('one') - - mock_one.expects(:first).in_sequence(sequence) - mock_two.expects(:second).in_sequence(sequence) - - mock_two.second - end - assert_failed(test_result) - end - - def test_should_allow_invocations_in_sequence_even_if_expected_on_different_mocks - test_result = run_test do - mock_one = mock('1') - mock_two = mock('2') - sequence = sequence('one') - - mock_one.expects(:first).in_sequence(sequence) - mock_two.expects(:second).in_sequence(sequence) - - mock_one.first - mock_two.second - end - assert_passed(test_result) - end - - def test_should_constrain_invocations_to_occur_in_expected_order_even_if_expected_on_partial_mocks - test_result = run_test do - partial_mock_one = "1" - partial_mock_two = "2" - sequence = sequence('one') - - partial_mock_one.expects(:first).in_sequence(sequence) - partial_mock_two.expects(:second).in_sequence(sequence) - - partial_mock_two.second - end - assert_failed(test_result) - end - - def test_should_allow_invocations_in_sequence_even_if_expected_on_partial_mocks - test_result = run_test do - partial_mock_one = "1" - partial_mock_two = "2" - sequence = sequence('one') - - partial_mock_one.expects(:first).in_sequence(sequence) - partial_mock_two.expects(:second).in_sequence(sequence) - - partial_mock_one.first - partial_mock_two.second - end - assert_passed(test_result) - end - - def test_should_allow_stub_expectations_to_be_skipped_in_sequence - test_result = run_test do - mock = mock() - sequence = sequence('one') - - mock.expects(:first).in_sequence(sequence) - s = mock.stubs(:second).in_sequence(sequence) - mock.expects(:third).in_sequence(sequence) - - mock.first - mock.third - end - assert_passed(test_result) - end - - def test_should_regard_sequences_as_independent_of_each_other - test_result = run_test do - mock = mock() - sequence_one = sequence('one') - sequence_two = sequence('two') - - mock.expects(:first).in_sequence(sequence_one) - mock.expects(:second).in_sequence(sequence_one) - - mock.expects(:third).in_sequence(sequence_two) - mock.expects(:fourth).in_sequence(sequence_two) - - mock.first - mock.third - mock.second - mock.fourth - end - assert_passed(test_result) - end - - def test_should_include_sequence_in_failure_message - test_result = run_test do - mock = mock() - sequence = sequence('one') - - mock.expects(:first).in_sequence(sequence) - mock.expects(:second).in_sequence(sequence) - - mock.second - end - assert_failed(test_result) - assert_match Regexp.new("in sequence 'one'"), test_result.failures.first.message - end - - def test_should_allow_expectations_to_be_in_more_than_one_sequence - test_result = run_test do - mock = mock() - sequence_one = sequence('one') - sequence_two = sequence('two') - - mock.expects(:first).in_sequence(sequence_one) - mock.expects(:second).in_sequence(sequence_two) - mock.expects(:three).in_sequence(sequence_one).in_sequence(sequence_two) - - mock.first - mock.three - end - assert_failed(test_result) - assert_match Regexp.new("in sequence 'one'"), test_result.failures.first.message - assert_match Regexp.new("in sequence 'two'"), test_result.failures.first.message - end - - def test_should_have_shortcut_for_expectations_to_be_in_more_than_one_sequence - test_result = run_test do - mock = mock() - sequence_one = sequence('one') - sequence_two = sequence('two') - - mock.expects(:first).in_sequence(sequence_one) - mock.expects(:second).in_sequence(sequence_two) - mock.expects(:three).in_sequence(sequence_one, sequence_two) - - mock.first - mock.three - end - assert_failed(test_result) - assert_match Regexp.new("in sequence 'one'"), test_result.failures.first.message - assert_match Regexp.new("in sequence 'two'"), test_result.failures.first.message - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/acceptance/standalone_acceptance_test.rb b/vendor/gems/mocha-0.5.6/test/acceptance/standalone_acceptance_test.rb deleted file mode 100644 index 1e101d7ca6..0000000000 --- a/vendor/gems/mocha-0.5.6/test/acceptance/standalone_acceptance_test.rb +++ /dev/null @@ -1,131 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "test_helper") -require 'mocha_standalone' - -class NotATestUnitAssertionFailedError < StandardError -end - -class NotATestUnitTestCase - - include Mocha::Standalone - - attr_reader :assertion_count - - def initialize - @assertion_count = 0 - end - - def run(test_method) - mocha_setup - begin - prepare - begin - send(test_method) - mocha_verify { @assertion_count += 1 } - rescue Mocha::ExpectationError => e - new_error = NotATestUnitAssertionFailedError.new(e.message) - new_error.set_backtrace(e.backtrace) - raise new_error - ensure - cleanup - end - ensure - mocha_teardown - end - end - - def prepare - end - - def cleanup - end - -end - -class SampleTest < NotATestUnitTestCase - - def mocha_with_fulfilled_expectation - mockee = mock() - mockee.expects(:blah) - mockee.blah - end - - def mocha_with_unfulfilled_expectation - mockee = mock() - mockee.expects(:blah) - end - - def mocha_with_unexpected_invocation - mockee = mock() - mockee.blah - end - - def stubba_with_fulfilled_expectation - stubbee = Class.new { define_method(:blah) {} }.new - stubbee.expects(:blah) - stubbee.blah - end - - def stubba_with_unfulfilled_expectation - stubbee = Class.new { define_method(:blah) {} }.new - stubbee.expects(:blah) - end - - def mocha_with_matching_parameter - mockee = mock() - mockee.expects(:blah).with(has_key(:wibble)) - mockee.blah(:wibble => 1) - end - - def mocha_with_non_matching_parameter - mockee = mock() - mockee.expects(:blah).with(has_key(:wibble)) - mockee.blah(:wobble => 2) - end - -end - -require 'test/unit' - -class StandaloneAcceptanceTest < Test::Unit::TestCase - - attr_reader :sample_test - - def setup - @sample_test = SampleTest.new - end - - def test_should_pass_mocha_test - assert_nothing_raised { sample_test.run(:mocha_with_fulfilled_expectation) } - assert_equal 1, sample_test.assertion_count - end - - def test_should_fail_mocha_test_due_to_unfulfilled_exception - assert_raises(NotATestUnitAssertionFailedError) { sample_test.run(:mocha_with_unfulfilled_expectation) } - assert_equal 1, sample_test.assertion_count - end - - def test_should_fail_mocha_test_due_to_unexpected_invocation - assert_raises(NotATestUnitAssertionFailedError) { sample_test.run(:mocha_with_unexpected_invocation) } - assert_equal 0, sample_test.assertion_count - end - - def test_should_pass_stubba_test - assert_nothing_raised { sample_test.run(:stubba_with_fulfilled_expectation) } - assert_equal 1, sample_test.assertion_count - end - - def test_should_fail_stubba_test - assert_raises(NotATestUnitAssertionFailedError) { sample_test.run(:stubba_with_unfulfilled_expectation) } - assert_equal 1, sample_test.assertion_count - end - - def test_should_pass_mocha_test_with_matching_parameter - assert_nothing_raised { sample_test.run(:mocha_with_matching_parameter) } - assert_equal 1, sample_test.assertion_count - end - - def test_should_fail_mocha_test_with_non_matching_parameter - assert_raises(NotATestUnitAssertionFailedError) { sample_test.run(:mocha_with_non_matching_parameter) } - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/acceptance/stubba_acceptance_test.rb b/vendor/gems/mocha-0.5.6/test/acceptance/stubba_acceptance_test.rb deleted file mode 100644 index 93d8d12593..0000000000 --- a/vendor/gems/mocha-0.5.6/test/acceptance/stubba_acceptance_test.rb +++ /dev/null @@ -1,102 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "test_helper") -require 'mocha' - -class Widget - - def model - 'original_model' - end - - class << self - - def find(options) - [] - end - - def create(attributes) - Widget.new - end - - end - -end - -module Thingy - - def self.wotsit - :hoojamaflip - end - -end - -class StubbaAcceptanceTest < Test::Unit::TestCase - - def test_should_stub_instance_method - widget = Widget.new - widget.expects(:model).returns('different_model') - assert_equal 'different_model', widget.model - end - - def test_should_stub_module_method - should_stub_module_method - end - - def test_should_stub_module_method_again - should_stub_module_method - end - - def test_should_stub_class_method - should_stub_class_method - end - - def test_should_stub_class_method_again - should_stub_class_method - end - - def test_should_stub_instance_method_on_any_instance_of_a_class - should_stub_instance_method_on_any_instance_of_a_class - end - - def test_should_stub_instance_method_on_any_instance_of_a_class_again - should_stub_instance_method_on_any_instance_of_a_class - end - - def test_should_stub_two_different_class_methods - should_stub_two_different_class_methods - end - - def test_should_stub_two_different_class_methods_again - should_stub_two_different_class_methods - end - - private - - def should_stub_module_method - Thingy.expects(:wotsit).returns(:dooda) - assert_equal :dooda, Thingy.wotsit - end - - def should_stub_class_method - widgets = [Widget.new] - Widget.expects(:find).with(:all).returns(widgets) - assert_equal widgets, Widget.find(:all) - end - - def should_stub_two_different_class_methods - found_widgets = [Widget.new] - created_widget = Widget.new - Widget.expects(:find).with(:all).returns(found_widgets) - Widget.expects(:create).with(:model => 'wombat').returns(created_widget) - assert_equal found_widgets, Widget.find(:all) - assert_equal created_widget, Widget.create(:model => 'wombat') - end - - def should_stub_instance_method_on_any_instance_of_a_class - Widget.any_instance.expects(:model).at_least_once.returns('another_model') - widget_1 = Widget.new - widget_2 = Widget.new - assert_equal 'another_model', widget_1.model - assert_equal 'another_model', widget_2.model - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/active_record_test_case.rb b/vendor/gems/mocha-0.5.6/test/active_record_test_case.rb deleted file mode 100644 index ae65073800..0000000000 --- a/vendor/gems/mocha-0.5.6/test/active_record_test_case.rb +++ /dev/null @@ -1,36 +0,0 @@ -module ActiveRecordTestCase - - def setup_with_fixtures - methods_called << :setup_with_fixtures - end - - alias_method :setup, :setup_with_fixtures - - def teardown_with_fixtures - methods_called << :teardown_with_fixtures - end - - alias_method :teardown, :teardown_with_fixtures - - def self.method_added(method) - case method.to_s - when 'setup' - unless method_defined?(:setup_without_fixtures) - alias_method :setup_without_fixtures, :setup - define_method(:setup) do - setup_with_fixtures - setup_without_fixtures - end - end - when 'teardown' - unless method_defined?(:teardown_without_fixtures) - alias_method :teardown_without_fixtures, :teardown - define_method(:teardown) do - teardown_without_fixtures - teardown_with_fixtures - end - end - end - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/deprecation_disabler.rb b/vendor/gems/mocha-0.5.6/test/deprecation_disabler.rb deleted file mode 100644 index c57fb3c9ac..0000000000 --- a/vendor/gems/mocha-0.5.6/test/deprecation_disabler.rb +++ /dev/null @@ -1,15 +0,0 @@ -require 'mocha/deprecation' - -module DeprecationDisabler - - def disable_deprecations - original_mode = Mocha::Deprecation.mode - Mocha::Deprecation.mode = :disabled - begin - yield - ensure - Mocha::Deprecation.mode = original_mode - end - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/execution_point.rb b/vendor/gems/mocha-0.5.6/test/execution_point.rb deleted file mode 100644 index 33c85699ee..0000000000 --- a/vendor/gems/mocha-0.5.6/test/execution_point.rb +++ /dev/null @@ -1,34 +0,0 @@ -class ExecutionPoint - - attr_reader :backtrace - - def self.current - new(caller) - end - - def initialize(backtrace) - @backtrace = backtrace - end - - def file_name - /\A(.*?):\d+/.match(@backtrace.first)[1] - end - - def line_number - Integer(/\A.*?:(\d+)/.match(@backtrace.first)[1]) - end - - def ==(other) - return false unless other.is_a?(ExecutionPoint) - (file_name == other.file_name) and (line_number == other.line_number) - end - - def to_s - "file: #{file_name} line: #{line_number}" - end - - def inspect - to_s - end - -end diff --git a/vendor/gems/mocha-0.5.6/test/integration/._mocha_test_result_integration_test.rb b/vendor/gems/mocha-0.5.6/test/integration/._mocha_test_result_integration_test.rb deleted file mode 100644 index 78cbfbe92cf4bdfdcd96a35906d36e3394626341..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_I`4FfSq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV#ASS#dY=B0v|2G$C- E0QjC5$N&HU diff --git a/vendor/gems/mocha-0.5.6/test/integration/._stubba_integration_test.rb b/vendor/gems/mocha-0.5.6/test/integration/._stubba_integration_test.rb deleted file mode 100644 index c88497c4567d6fced4c7e7234045adf527a38b3c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_Ic^{}0q+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eaNsSS#dY=A{Ce=0IjG E0QgB5(f|Me diff --git a/vendor/gems/mocha-0.5.6/test/integration/._stubba_test_result_integration_test.rb b/vendor/gems/mocha-0.5.6/test/integration/._stubba_test_result_integration_test.rb deleted file mode 100644 index 931f6315d8528c050cf7d6cd66f098cb254635f5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_Ic^9Y@q+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eVzsTPx&b=A{CeW|r0p FwE*{f7|{R# diff --git a/vendor/gems/mocha-0.5.6/test/integration/mocha_test_result_integration_test.rb b/vendor/gems/mocha-0.5.6/test/integration/mocha_test_result_integration_test.rb deleted file mode 100644 index d5f29e8451..0000000000 --- a/vendor/gems/mocha-0.5.6/test/integration/mocha_test_result_integration_test.rb +++ /dev/null @@ -1,105 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "test_helper") -require 'mocha/standalone' -require 'mocha/test_case_adapter' -require 'execution_point' - -class MochaTestResultIntegrationTest < Test::Unit::TestCase - - def test_should_include_expectation_verification_in_assertion_count - test_result = run_test do - object = mock() - object.expects(:message) - object.message - end - assert_equal 1, test_result.assertion_count - end - - def test_should_include_assertions_in_assertion_count - test_result = run_test do - assert true - end - assert_equal 1, test_result.assertion_count - end - - def test_should_not_include_stubbing_expectation_verification_in_assertion_count - test_result = run_test do - object = mock() - object.stubs(:message) - object.message - end - assert_equal 0, test_result.assertion_count - end - - def test_should_include_expectation_verification_failure_in_failure_count - test_result = run_test do - object = mock() - object.expects(:message) - end - assert_equal 1, test_result.failure_count - end - - def test_should_include_unexpected_verification_failure_in_failure_count - test_result = run_test do - object = mock() - object.message - end - assert_equal 1, test_result.failure_count - end - - def test_should_include_assertion_failure_in_failure_count - test_result = run_test do - flunk - end - assert_equal 1, test_result.failure_count - end - - def test_should_display_backtrace_indicating_line_number_where_expects_was_called - test_result = Test::Unit::TestResult.new - faults = [] - test_result.add_listener(Test::Unit::TestResult::FAULT, &lambda { |fault| faults << fault }) - execution_point = nil - run_test(test_result) do - object = mock() - execution_point = ExecutionPoint.current; object.expects(:message) - end - assert_equal 1, faults.length - assert_equal execution_point, ExecutionPoint.new(faults.first.location) - end - - def test_should_display_backtrace_indicating_line_number_where_unexpected_method_was_called - test_result = Test::Unit::TestResult.new - faults = [] - test_result.add_listener(Test::Unit::TestResult::FAULT, &lambda { |fault| faults << fault }) - execution_point = nil - run_test(test_result) do - object = mock() - execution_point = ExecutionPoint.current; object.message - end - assert_equal 1, faults.length - assert_equal execution_point, ExecutionPoint.new(faults.first.location) - end - - def test_should_display_backtrace_indicating_line_number_where_failing_assertion_was_called - test_result = Test::Unit::TestResult.new - faults = [] - test_result.add_listener(Test::Unit::TestResult::FAULT, &lambda { |fault| faults << fault }) - execution_point = nil - run_test(test_result) do - execution_point = ExecutionPoint.current; flunk - end - assert_equal 1, faults.length - assert_equal execution_point, ExecutionPoint.new(faults.first.location) - end - - def run_test(test_result = Test::Unit::TestResult.new, &block) - test_class = Class.new(Test::Unit::TestCase) do - include Mocha::Standalone - include Mocha::TestCaseAdapter - define_method(:test_me, &block) - end - test = test_class.new(:test_me) - test.run(test_result) {} - test_result - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/integration/stubba_integration_test.rb b/vendor/gems/mocha-0.5.6/test/integration/stubba_integration_test.rb deleted file mode 100644 index 4285c179ad..0000000000 --- a/vendor/gems/mocha-0.5.6/test/integration/stubba_integration_test.rb +++ /dev/null @@ -1,89 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "test_helper") - -require 'mocha/object' -require 'mocha/test_case_adapter' -require 'mocha/standalone' - -class StubbaIntegrationTest < Test::Unit::TestCase - - class DontMessWithMe - def self.my_class_method - :original_return_value - end - def my_instance_method - :original_return_value - end - end - - def test_should_stub_class_method_within_test - test = build_test do - DontMessWithMe.expects(:my_class_method).returns(:new_return_value) - assert_equal :new_return_value, DontMessWithMe.my_class_method - end - - test_result = Test::Unit::TestResult.new - test.run(test_result) {} - assert test_result.passed? - end - - def test_should_leave_stubbed_class_method_unchanged_after_test - test = build_test do - DontMessWithMe.expects(:my_class_method).returns(:new_return_value) - end - - test.run(Test::Unit::TestResult.new) {} - assert_equal :original_return_value, DontMessWithMe.my_class_method - end - - def test_should_reset_class_expectations_after_test - test = build_test do - DontMessWithMe.expects(:my_class_method) - end - - test.run(Test::Unit::TestResult.new) {} - assert_equal 0, DontMessWithMe.mocha.expectations.length - end - - def test_should_stub_instance_method_within_test - instance = DontMessWithMe.new - test = build_test do - instance.expects(:my_instance_method).returns(:new_return_value) - assert_equal :new_return_value, instance.my_instance_method - end - test_result = Test::Unit::TestResult.new - test.run(test_result) {} - assert test_result.passed? - end - - def test_should_leave_stubbed_instance_method_unchanged_after_test - instance = DontMessWithMe.new - test = build_test do - instance.expects(:my_instance_method).returns(:new_return_value) - end - - test.run(Test::Unit::TestResult.new) {} - assert_equal :original_return_value, instance.my_instance_method - end - - def test_should_reset_instance_expectations_after_test - instance = DontMessWithMe.new - test = build_test do - instance.expects(:my_instance_method).returns(:new_return_value) - end - - test.run(Test::Unit::TestResult.new) {} - assert_equal 0, instance.mocha.expectations.length - end - - private - - def build_test(&block) - test_class = Class.new(Test::Unit::TestCase) do - include Mocha::Standalone - include Mocha::TestCaseAdapter - define_method(:test_me, &block) - end - test_class.new(:test_me) - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/integration/stubba_test_result_integration_test.rb b/vendor/gems/mocha-0.5.6/test/integration/stubba_test_result_integration_test.rb deleted file mode 100644 index 34264e7c6f..0000000000 --- a/vendor/gems/mocha-0.5.6/test/integration/stubba_test_result_integration_test.rb +++ /dev/null @@ -1,85 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "test_helper") -require 'mocha/object' -require 'mocha/standalone' -require 'mocha/test_case_adapter' -require 'execution_point' - -class StubbaTestResultIntegrationTest < Test::Unit::TestCase - - def test_should_include_expectation_verification_in_assertion_count - test_result = run_test do - object = Class.new { def message; end }.new - object.expects(:message) - object.message - end - assert_equal 1, test_result.assertion_count - end - - def test_should_include_assertions_in_assertion_count - test_result = run_test do - assert true - end - assert_equal 1, test_result.assertion_count - end - - def test_should_not_include_stubbing_expectation_verification_in_assertion_count - test_result = run_test do - object = Class.new { def message; end }.new - object.stubs(:message) - object.message - end - assert_equal 0, test_result.assertion_count - end - - def test_should_include_expectation_verification_failure_in_failure_count - test_result = run_test do - object = Class.new { def message; end }.new - object.expects(:message) - end - assert_equal 1, test_result.failure_count - end - - def test_should_include_assertion_failure_in_failure_count - test_result = run_test do - flunk - end - assert_equal 1, test_result.failure_count - end - - def test_should_display_backtrace_indicating_line_number_where_expects_was_called - test_result = Test::Unit::TestResult.new - faults = [] - test_result.add_listener(Test::Unit::TestResult::FAULT, &lambda { |fault| faults << fault }) - execution_point = nil - run_test(test_result) do - object = Class.new { def message; end }.new - execution_point = ExecutionPoint.current; object.expects(:message) - end - assert_equal 1, faults.length - assert_equal execution_point, ExecutionPoint.new(faults.first.location) - end - - def test_should_display_backtrace_indicating_line_number_where_failing_assertion_was_called - test_result = Test::Unit::TestResult.new - faults = [] - test_result.add_listener(Test::Unit::TestResult::FAULT, &lambda { |fault| faults << fault }) - execution_point = nil - run_test(test_result) do - execution_point = ExecutionPoint.current; flunk - end - assert_equal 1, faults.length - assert_equal execution_point, ExecutionPoint.new(faults.first.location) - end - - def run_test(test_result = Test::Unit::TestResult.new, &block) - test_class = Class.new(Test::Unit::TestCase) do - include Mocha::Standalone - include Mocha::TestCaseAdapter - define_method(:test_me, &block) - end - test = test_class.new(:test_me) - test.run(test_result) {} - test_result - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/method_definer.rb b/vendor/gems/mocha-0.5.6/test/method_definer.rb deleted file mode 100644 index 1aef8868b2..0000000000 --- a/vendor/gems/mocha-0.5.6/test/method_definer.rb +++ /dev/null @@ -1,18 +0,0 @@ -require 'mocha/metaclass' - -class Object - - def define_instance_method(method_symbol, &block) - __metaclass__.send(:define_method, method_symbol, block) - end - - def replace_instance_method(method_symbol, &block) - raise "Cannot replace #{method_symbol} as #{self} does not respond to it." unless self.respond_to?(method_symbol) - define_instance_method(method_symbol, &block) - end - - def define_instance_accessor(*symbols) - symbols.each { |symbol| __metaclass__.send(:attr_accessor, symbol) } - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/test_helper.rb b/vendor/gems/mocha-0.5.6/test/test_helper.rb deleted file mode 100644 index dc04942739..0000000000 --- a/vendor/gems/mocha-0.5.6/test/test_helper.rb +++ /dev/null @@ -1,12 +0,0 @@ -unless defined?(STANDARD_OBJECT_PUBLIC_INSTANCE_METHODS) - STANDARD_OBJECT_PUBLIC_INSTANCE_METHODS = Object.public_instance_methods -end - -$:.unshift File.expand_path(File.join(File.dirname(__FILE__), "..", "lib")) -$:.unshift File.expand_path(File.join(File.dirname(__FILE__))) -$:.unshift File.expand_path(File.join(File.dirname(__FILE__), 'unit')) -$:.unshift File.expand_path(File.join(File.dirname(__FILE__), 'unit', 'parameter_matchers')) -$:.unshift File.expand_path(File.join(File.dirname(__FILE__), 'integration')) -$:.unshift File.expand_path(File.join(File.dirname(__FILE__), 'acceptance')) - -require 'test/unit' \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/test_runner.rb b/vendor/gems/mocha-0.5.6/test/test_runner.rb deleted file mode 100644 index fbadd92977..0000000000 --- a/vendor/gems/mocha-0.5.6/test/test_runner.rb +++ /dev/null @@ -1,31 +0,0 @@ -require 'test/unit/testresult' -require 'test/unit/testcase' -require 'mocha/standalone' -require 'mocha/test_case_adapter' - -module TestRunner - - def run_test(test_result = Test::Unit::TestResult.new, &block) - test_class = Class.new(Test::Unit::TestCase) do - include Mocha::Standalone - include Mocha::TestCaseAdapter - define_method(:test_me, &block) - end - test = test_class.new(:test_me) - test.run(test_result) {} - class << test_result - attr_reader :failures, :errors - end - test_result - end - - def assert_passed(test_result) - flunk "Test failed unexpectedly with message: #{test_result.failures}" if test_result.failure_count > 0 - flunk "Test failed unexpectedly with message: #{test_result.errors}" if test_result.error_count > 0 - end - - def assert_failed(test_result) - flunk "Test passed unexpectedly" if test_result.passed? - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/._any_instance_method_test.rb b/vendor/gems/mocha-0.5.6/test/unit/._any_instance_method_test.rb deleted file mode 100644 index 83d91fdf20c1f3ca8ee5a2756b411005afb4d683..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 176 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aUBqY_#1$j2;dkJ62u_IxeKTiq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r2<(N)(W)% Dx5F4W diff --git a/vendor/gems/mocha-0.5.6/test/unit/._auto_verify_test.rb b/vendor/gems/mocha-0.5.6/test/unit/._auto_verify_test.rb deleted file mode 100644 index 9052afc9513144878946cc2cce4f63278c5a9977..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_IxeTZjq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r2<)o7S;;2 E0Luj!SpWb4 diff --git a/vendor/gems/mocha-0.5.6/test/unit/._central_test.rb b/vendor/gems/mocha-0.5.6/test/unit/._central_test.rb deleted file mode 100644 index a8cc77d1a779c8767877d83d8572910aff9c548f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_IIR~f|q+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!VE97M6r2<)I#?}h8 E0LRQ2Q2+n{ diff --git a/vendor/gems/mocha-0.5.6/test/unit/._class_method_test.rb b/vendor/gems/mocha-0.5.6/test/unit/._class_method_test.rb deleted file mode 100644 index 30c6ce8de5a1b5666c287b3c42eb66333d511455..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_IxdEsYq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r2<)omgd$9 FwE*a_7@z$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_$q!OqUq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV#CS}Wva=A{Ceh8ETe FwE!WB8AbpA diff --git a/vendor/gems/mocha-0.5.6/test/unit/._expectation_list_test.rb b/vendor/gems/mocha-0.5.6/test/unit/._expectation_list_test.rb deleted file mode 100644 index 753636a0f13c64752be4f0477055d2a8af15227a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_IIRU5?q+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r2<)|=GF?e E0K>5uMgRZ+ diff --git a/vendor/gems/mocha-0.5.6/test/unit/._expectation_test.rb b/vendor/gems/mocha-0.5.6/test/unit/._expectation_test.rb deleted file mode 100644 index b92a766c09bb840c61f314fa4d039f13e5ee3037..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 179 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aU&3e_?v;42;dkJ62u_IISr^3q+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eVzsSS#dY=A{CeCg$eW G3bg?FiWt8D diff --git a/vendor/gems/mocha-0.5.6/test/unit/._hash_inspect_test.rb b/vendor/gems/mocha-0.5.6/test/unit/._hash_inspect_test.rb deleted file mode 100644 index 2d092d906064c29ed787810ce053be518e1fdc37..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_Ixecfkq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r2<)oKxQof D&_x(w diff --git a/vendor/gems/mocha-0.5.6/test/unit/._method_matcher_test.rb b/vendor/gems/mocha-0.5.6/test/unit/._method_matcher_test.rb deleted file mode 100644 index 5de8376739280cf862658d7c4641f03c6167e48b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62zc+eJfBYNXHBy zmPFIWh!7V|&d=4$O-wGz&rK{zE!Im;EJ`h5s7}t$Db3AOuvIXyR>;ZBO9iqFEv*%5 E0UJyiMgRZ+ diff --git a/vendor/gems/mocha-0.5.6/test/unit/._missing_expectation_test.rb b/vendor/gems/mocha-0.5.6/test/unit/._missing_expectation_test.rb deleted file mode 100644 index 166d7827f65eb49ad994a01d9842aff883c628c2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_qrUz6C(lG;w zrO>o7BE&_L^KpF diff --git a/vendor/gems/mocha-0.5.6/test/unit/._mock_test.rb b/vendor/gems/mocha-0.5.6/test/unit/._mock_test.rb deleted file mode 100644 index e9005d16736b8b0e2fd115458e71029d6cde499a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62zc=HwdT{q+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!UE97M6r2<)I=GF?e E0Q|-n?f?J) diff --git a/vendor/gems/mocha-0.5.6/test/unit/._object_inspect_test.rb b/vendor/gems/mocha-0.5.6/test/unit/._object_inspect_test.rb deleted file mode 100644 index e39ba31349470d112d3c6e04e8167d5dd51c00c2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62zc=Hw35@q+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eVzsTPx&b=A{CeMi$lz FwE!2a87Ke% diff --git a/vendor/gems/mocha-0.5.6/test/unit/._parameters_matcher_test.rb b/vendor/gems/mocha-0.5.6/test/unit/._parameters_matcher_test.rb deleted file mode 100644 index ca04d98e7b6eb9bcf28b7cef6b19e82818dff079..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 176 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aUBqY_#1$j2;dkJ62zc+eLGMoNXHBy zmO#_Sh!7V|&d=4$O-wGz&rK{zE!Im;EJ`h5s7}t$Db3AOuvIXyR>;ZBO9irwtQBej D0SFlj diff --git a/vendor/gems/mocha-0.5.6/test/unit/._sequence_test.rb b/vendor/gems/mocha-0.5.6/test/unit/._sequence_test.rb deleted file mode 100644 index ec9e9d76e6646e1860b40235cab64546a4e137c3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62zdzDg#ss(lG;w zrO>o7BE&_L^K>83Qh_W(10cH= E0J=*U2mk;8 diff --git a/vendor/gems/mocha-0.5.6/test/unit/any_instance_method_test.rb b/vendor/gems/mocha-0.5.6/test/unit/any_instance_method_test.rb deleted file mode 100644 index 804fcde2bd..0000000000 --- a/vendor/gems/mocha-0.5.6/test/unit/any_instance_method_test.rb +++ /dev/null @@ -1,126 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "test_helper") -require 'method_definer' -require 'mocha/mock' -require 'mocha/any_instance_method' - -class AnyInstanceMethodTest < Test::Unit::TestCase - - include Mocha - - def test_should_hide_original_method - klass = Class.new { def method_x; end } - method = AnyInstanceMethod.new(klass, :method_x) - hidden_method_x = method.hidden_method.to_sym - - method.hide_original_method - - assert klass.method_defined?(hidden_method_x) - end - - def test_should_not_hide_original_method_if_it_is_not_defined - klass = Class.new - method = AnyInstanceMethod.new(klass, :method_x) - hidden_method_x = method.hidden_method.to_sym - - method.hide_original_method - - assert_equal false, klass.method_defined?(hidden_method_x) - end - - def test_should_define_a_new_method - klass = Class.new { def method_x; end } - method = AnyInstanceMethod.new(klass, :method_x) - mocha = Mock.new - mocha.expects(:method_x).with(:param1, :param2).returns(:result) - any_instance = Object.new - any_instance.define_instance_method(:mocha) { mocha } - klass.define_instance_method(:any_instance) { any_instance } - - method.hide_original_method - method.define_new_method - - instance = klass.new - result = instance.method_x(:param1, :param2) - - assert_equal :result, result - mocha.verify - end - - def test_should_restore_original_method - klass = Class.new { def method_x; end } - method = AnyInstanceMethod.new(klass, :method_x) - hidden_method_x = method.hidden_method.to_sym - klass.send(:define_method, hidden_method_x, Proc.new { :original_result }) - - method.remove_new_method - method.restore_original_method - - instance = klass.new - assert_equal :original_result, instance.method_x - assert !klass.method_defined?(hidden_method_x) - end - - def test_should_not_restore_original_method_if_hidden_method_not_defined - klass = Class.new { def method_x; :new_result; end } - method = AnyInstanceMethod.new(klass, :method_x) - - method.restore_original_method - - instance = klass.new - assert_equal :new_result, instance.method_x - end - - def test_should_call_remove_new_method - klass = Class.new { def method_x; end } - any_instance = Mock.new - any_instance.stubs(:reset_mocha) - klass.define_instance_method(:any_instance) { any_instance } - method = AnyInstanceMethod.new(klass, :method_x) - method.replace_instance_method(:restore_original_method) { } - method.define_instance_accessor(:remove_called) - method.replace_instance_method(:remove_new_method) { self.remove_called = true } - - method.unstub - - assert method.remove_called - end - - def test_should_call_restore_original_method - klass = Class.new { def method_x; end } - any_instance = Mock.new - any_instance.stubs(:reset_mocha) - klass.define_instance_method(:any_instance) { any_instance } - method = AnyInstanceMethod.new(klass, :method_x) - method.replace_instance_method(:remove_new_method) { } - method.define_instance_accessor(:restore_called) - method.replace_instance_method(:restore_original_method) { self.restore_called = true } - - method.unstub - - assert method.restore_called - end - - def test_should_call_reset_mocha - klass = Class.new { def method_x; end } - any_instance = Class.new { attr_accessor :mocha_was_reset; def reset_mocha; self.mocha_was_reset = true; end }.new - klass.define_instance_method(:any_instance) { any_instance } - method = AnyInstanceMethod.new(klass, :method_x) - method.replace_instance_method(:remove_new_method) { } - method.replace_instance_method(:restore_original_method) { } - - method.unstub - - assert any_instance.mocha_was_reset - end - - def test_should_return_any_instance_mocha_for_stubbee - mocha = Object.new - any_instance = Object.new - any_instance.define_instance_method(:mocha) { mocha } - stubbee = Class.new - stubbee.define_instance_method(:any_instance) { any_instance } - method = AnyInstanceMethod.new(stubbee, :method_name) - assert_equal stubbee.any_instance.mocha, method.mock - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/array_inspect_test.rb b/vendor/gems/mocha-0.5.6/test/unit/array_inspect_test.rb deleted file mode 100644 index 9cc06a4564..0000000000 --- a/vendor/gems/mocha-0.5.6/test/unit/array_inspect_test.rb +++ /dev/null @@ -1,16 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "test_helper") -require 'mocha/inspect' - -class ArrayInstanceTest < Test::Unit::TestCase - - def test_should_use_inspect - array = [1, 2] - assert_equal array.inspect, array.mocha_inspect - end - - def test_should_use_mocha_inspect_on_each_item - array = [1, 2, "chris"] - assert_equal "[1, 2, 'chris']", array.mocha_inspect - end - -end diff --git a/vendor/gems/mocha-0.5.6/test/unit/auto_verify_test.rb b/vendor/gems/mocha-0.5.6/test/unit/auto_verify_test.rb deleted file mode 100644 index 10a6124576..0000000000 --- a/vendor/gems/mocha-0.5.6/test/unit/auto_verify_test.rb +++ /dev/null @@ -1,129 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "test_helper") -require 'mocha/auto_verify' -require 'method_definer' - -class AutoVerifyTest < Test::Unit::TestCase - - attr_reader :test_case - - def setup - @test_case = Object.new - class << test_case - include Mocha::AutoVerify - end - end - - def test_should_build_mock - mock = test_case.mock - assert mock.is_a?(Mocha::Mock) - end - - def test_should_add_expectations_to_mock - mock = test_case.mock(:method_1 => 'result_1', :method_2 => 'result_2') - assert_equal 'result_1', mock.method_1 - assert_equal 'result_2', mock.method_2 - end - - def test_should_build_stub - stub = test_case.stub - assert stub.is_a?(Mocha::Mock) - end - - def test_should_add_expectation_to_stub - stub = test_case.stub(:method_1 => 'result_1', :method_2 => 'result_2') - assert_equal 'result_1', stub.method_1 - assert_equal 'result_2', stub.method_2 - end - - def test_should_build_stub_that_stubs_all_methods - stub = test_case.stub_everything - assert stub.everything_stubbed - end - - def test_should_add_expectations_to_stub_that_stubs_all_methods - stub = test_case.stub_everything(:method_1 => 'result_1', :method_2 => 'result_2') - assert_equal 'result_1', stub.method_1 - assert_equal 'result_2', stub.method_2 - end - - def test_should_always_new_mock - assert_not_equal test_case.mock, test_case.mock - end - - def test_should_always_build_new_stub - assert_not_equal test_case.stub, test_case.stub - end - - def test_should_always_build_new_stub_that_stubs_all_methods - assert_not_equal test_case.stub, test_case.stub - end - - def test_should_store_each_new_mock - expected = Array.new(3) { test_case.mock } - assert_equal expected, test_case.mocks - end - - def test_should_store_each_new_stub - expected = Array.new(3) { test_case.stub } - assert_equal expected, test_case.mocks - end - - def test_should_store_each_new_stub_that_stubs_all_methods - expected = Array.new(3) { test_case.stub_everything } - assert_equal expected, test_case.mocks - end - - def test_should_verify_each_mock - mocks = Array.new(3) do - mock = Object.new - mock.define_instance_accessor(:verify_called) - class << mock - def verify(&block) - self.verify_called = true - end - end - mock - end - test_case.replace_instance_method(:mocks) { mocks } - test_case.verify_mocks - assert mocks.all? { |mock| mock.verify_called } - end - - def test_should_yield_to_block_for_each_assertion - mock_class = Class.new do - def verify(&block); yield; end - end - mock = mock_class.new - test_case.replace_instance_method(:mocks) { [mock] } - yielded = false - test_case.verify_mocks { yielded = true } - assert yielded - end - - def test_should_reset_mocks_on_teardown - mock = Class.new { define_method(:verify) {} }.new - test_case.mocks << mock - test_case.teardown_mocks - assert test_case.mocks.empty? - end - - def test_should_create_named_mock - mock = test_case.mock('named_mock') - assert_equal '#', mock.mocha_inspect - end - - def test_should_create_named_stub - stub = test_case.stub('named_stub') - assert_equal '#', stub.mocha_inspect - end - - def test_should_create_named_stub_that_stubs_all_methods - stub = test_case.stub_everything('named_stub') - assert_equal '#', stub.mocha_inspect - end - - def test_should_build_sequence - assert_not_nil test_case.sequence('name') - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/central_test.rb b/vendor/gems/mocha-0.5.6/test/unit/central_test.rb deleted file mode 100644 index 2cc8345915..0000000000 --- a/vendor/gems/mocha-0.5.6/test/unit/central_test.rb +++ /dev/null @@ -1,124 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "test_helper") - -require 'mocha/central' -require 'mocha/mock' -require 'method_definer' - -class CentralTest < Test::Unit::TestCase - - include Mocha - - def test_should_start_with_empty_stubba_methods - stubba = Central.new - - assert_equal [], stubba.stubba_methods - end - - def test_should_stub_method_if_not_already_stubbed - method = Mock.new - method.expects(:stub) - stubba = Central.new - - stubba.stub(method) - - method.verify - end - - def test_should_not_stub_method_if_already_stubbed - method = Mock.new - method.expects(:stub).times(0) - stubba = Central.new - stubba_methods = Mock.new - stubba_methods.stubs(:include?).with(method).returns(true) - stubba.stubba_methods = stubba_methods - - stubba.stub(method) - - method.verify - end - - def test_should_record_method - method = Mock.new - method.expects(:stub) - stubba = Central.new - - stubba.stub(method) - - assert_equal [method], stubba.stubba_methods - end - - def test_should_unstub_all_methods - stubba = Central.new - method_1 = Mock.new - method_1.expects(:unstub) - method_2 = Mock.new - method_2.expects(:unstub) - stubba.stubba_methods = [method_1, method_2] - - stubba.unstub_all - - assert_equal [], stubba.stubba_methods - method_1.verify - method_2.verify - end - - def test_should_collect_mocks_from_all_methods - method_1 = Mock.new - method_1.stubs(:mock).returns(:mock_1) - - method_2 = Mock.new - method_2.stubs(:mock).returns(:mock_2) - - stubba = Central.new - stubba.stubba_methods = [method_1, method_2] - - assert_equal 2, stubba.unique_mocks.length - assert stubba.unique_mocks.include?(:mock_1) - assert stubba.unique_mocks.include?(:mock_2) - end - - def test_should_return_unique_mochas - method_1 = Mock.new - method_1.stubs(:mock).returns(:mock_1) - - method_2 = Mock.new - method_2.stubs(:mock).returns(:mock_1) - - stubba = Central.new - stubba.stubba_methods = [method_1, method_2] - - assert_equal [:mock_1], stubba.unique_mocks - end - - def test_should_call_verify_on_all_unique_mocks - mock_class = Class.new do - attr_accessor :verify_called - def verify - self.verify_called = true - end - end - mocks = [mock_class.new, mock_class.new] - stubba = Central.new - stubba.replace_instance_method(:unique_mocks) { mocks } - - stubba.verify_all - - assert mocks.all? { |mock| mock.verify_called } - end - - def test_should_call_verify_on_all_unique_mochas - mock_class = Class.new do - def verify(&block) - yield if block_given? - end - end - stubba = Central.new - stubba.replace_instance_method(:unique_mocks) { [mock_class.new] } - yielded = false - - stubba.verify_all { yielded = true } - - assert yielded - end - -end diff --git a/vendor/gems/mocha-0.5.6/test/unit/class_method_test.rb b/vendor/gems/mocha-0.5.6/test/unit/class_method_test.rb deleted file mode 100644 index 95d0599085..0000000000 --- a/vendor/gems/mocha-0.5.6/test/unit/class_method_test.rb +++ /dev/null @@ -1,200 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "test_helper") -require 'method_definer' -require 'mocha/mock' - -require 'mocha/class_method' - -class ClassMethodTest < Test::Unit::TestCase - - include Mocha - - def test_should_provide_hidden_version_of_method_name_starting_with_prefix - method = ClassMethod.new(nil, :original_method_name) - assert_match(/^__stubba__/, method.hidden_method) - end - - def test_should_provide_hidden_version_of_method_name_ending_with_suffix - method = ClassMethod.new(nil, :original_method_name) - assert_match(/__stubba__$/, method.hidden_method) - end - - def test_should_provide_hidden_version_of_method_name_including_original_method_name - method = ClassMethod.new(nil, :original_method_name) - assert_match(/original_method_name/, method.hidden_method) - end - - def test_should_provide_hidden_version_of_method_name_substituting_question_mark - method = ClassMethod.new(nil, :question_mark?) - assert_no_match(/\?/, method.hidden_method) - assert_match(/question_mark_substituted_character_63/, method.hidden_method) - end - - def test_should_provide_hidden_version_of_method_name_substituting_exclamation_mark - method = ClassMethod.new(nil, :exclamation_mark!) - assert_no_match(/!/, method.hidden_method) - assert_match(/exclamation_mark_substituted_character_33/, method.hidden_method) - end - - def test_should_provide_hidden_version_of_method_name_substituting_equals_sign - method = ClassMethod.new(nil, :equals_sign=) - assert_no_match(/\=/, method.hidden_method) - assert_match(/equals_sign_substituted_character_61/, method.hidden_method) - end - - def test_should_provide_hidden_version_of_method_name_substituting_brackets - method = ClassMethod.new(nil, :[]) - assert_no_match(/\[\]/, method.hidden_method) - assert_match(/substituted_character_91__substituted_character_93/, method.hidden_method) - end - - def test_should_provide_hidden_version_of_method_name_substituting_plus_sign - method = ClassMethod.new(nil, :+) - assert_no_match(/\+/, method.hidden_method) - assert_match(/substituted_character_43/, method.hidden_method) - end - - def test_should_hide_original_method - klass = Class.new { def self.method_x; end } - method = ClassMethod.new(klass, :method_x) - hidden_method_x = method.hidden_method - - method.hide_original_method - - assert klass.respond_to?(hidden_method_x) - end - - def test_should_respond_to_original_method_name_after_original_method_has_been_hidden - klass = Class.new { def self.original_method_name; end } - method = ClassMethod.new(klass, :original_method_name) - hidden_method_x = method.hidden_method - - method.hide_original_method - - assert klass.respond_to?(:original_method_name) - end - - def test_should_not_hide_original_method_if_method_not_defined - klass = Class.new - method = ClassMethod.new(klass, :method_x) - hidden_method_x = method.hidden_method - - method.hide_original_method - - assert_equal false, klass.respond_to?(hidden_method_x) - end - - def test_should_define_a_new_method_which_should_call_mocha_method_missing - klass = Class.new { def self.method_x; end } - mocha = Mocha::Mock.new - klass.define_instance_method(:mocha) { mocha } - mocha.expects(:method_x).with(:param1, :param2).returns(:result) - method = ClassMethod.new(klass, :method_x) - - method.hide_original_method - method.define_new_method - result = klass.method_x(:param1, :param2) - - assert_equal :result, result - mocha.verify - end - - def test_should_remove_new_method - klass = Class.new { def self.method_x; end } - method = ClassMethod.new(klass, :method_x) - - method.remove_new_method - - assert_equal false, klass.respond_to?(:method_x) - end - - def test_should_restore_original_method - klass = Class.new { def self.method_x; end } - method = ClassMethod.new(klass, :method_x) - hidden_method_x = method.hidden_method.to_sym - klass.define_instance_method(hidden_method_x) { :original_result } - - method.remove_new_method - method.restore_original_method - - assert_equal :original_result, klass.method_x - assert_equal false, klass.respond_to?(hidden_method_x) - end - - def test_should_not_restore_original_method_if_hidden_method_is_not_defined - klass = Class.new { def self.method_x; :new_result; end } - method = ClassMethod.new(klass, :method_x) - - method.restore_original_method - - assert_equal :new_result, klass.method_x - end - - def test_should_call_hide_original_method - klass = Class.new { def self.method_x; end } - method = ClassMethod.new(klass, :method_x) - method.hide_original_method - method.define_instance_accessor(:hide_called) - method.replace_instance_method(:hide_original_method) { self.hide_called = true } - - method.stub - - assert method.hide_called - end - - def test_should_call_define_new_method - klass = Class.new { def self.method_x; end } - method = ClassMethod.new(klass, :method_x) - method.define_instance_accessor(:define_called) - method.replace_instance_method(:define_new_method) { self.define_called = true } - - method.stub - - assert method.define_called - end - - def test_should_call_remove_new_method - klass = Class.new { def self.method_x; end } - klass.define_instance_method(:reset_mocha) { } - method = ClassMethod.new(klass, :method_x) - method.define_instance_accessor(:remove_called) - method.replace_instance_method(:remove_new_method) { self.remove_called = true } - - method.unstub - - assert method.remove_called - end - - def test_should_call_restore_original_method - klass = Class.new { def self.method_x; end } - klass.define_instance_method(:reset_mocha) { } - method = ClassMethod.new(klass, :method_x) - method.define_instance_accessor(:restore_called) - method.replace_instance_method(:restore_original_method) { self.restore_called = true } - - method.unstub - - assert method.restore_called - end - - def test_should_call_reset_mocha - klass = Class.new { def self.method_x; end } - klass.define_instance_accessor(:reset_called) - klass.define_instance_method(:reset_mocha) { self.reset_called = true } - method = ClassMethod.new(klass, :method_x) - method.replace_instance_method(:restore_original_method) { } - - method.unstub - - assert klass.reset_called - end - - def test_should_return_mock_for_stubbee - mocha = Object.new - stubbee = Object.new - stubbee.define_instance_accessor(:mocha) { mocha } - stubbee.mocha = nil - method = ClassMethod.new(stubbee, :method_name) - assert_equal stubbee.mocha, method.mock - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/date_time_inspect_test.rb b/vendor/gems/mocha-0.5.6/test/unit/date_time_inspect_test.rb deleted file mode 100644 index 8a9b2ee029..0000000000 --- a/vendor/gems/mocha-0.5.6/test/unit/date_time_inspect_test.rb +++ /dev/null @@ -1,21 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "test_helper") -require 'mocha/inspect' - -class TimeDateInspectTest < Test::Unit::TestCase - - def test_should_use_include_date_in_seconds - time = Time.now - assert_equal "#{time.inspect} (#{time.to_f} secs)", time.mocha_inspect - end - - def test_should_use_to_s_for_date - date = Date.new(2006, 1, 1) - assert_equal date.to_s, date.mocha_inspect - end - - def test_should_use_to_s_for_datetime - datetime = DateTime.new(2006, 1, 1) - assert_equal datetime.to_s, datetime.mocha_inspect - end - -end diff --git a/vendor/gems/mocha-0.5.6/test/unit/expectation_error_test.rb b/vendor/gems/mocha-0.5.6/test/unit/expectation_error_test.rb deleted file mode 100644 index 6206acf649..0000000000 --- a/vendor/gems/mocha-0.5.6/test/unit/expectation_error_test.rb +++ /dev/null @@ -1,24 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "test_helper") -require 'mocha/expectation_error' - -class ExpectationErrorTest < Test::Unit::TestCase - - include Mocha - - def test_should_exclude_mocha_locations_from_backtrace - mocha_lib = "/username/workspace/mocha_wibble/lib/" - backtrace = [ mocha_lib + 'exclude/me/1', mocha_lib + 'exclude/me/2', '/keep/me', mocha_lib + 'exclude/me/3'] - expectation_error = ExpectationError.new(nil, backtrace, mocha_lib) - assert_equal ['/keep/me'], expectation_error.backtrace - end - - def test_should_determine_path_for_mocha_lib_directory - assert_match Regexp.new("/lib/$"), ExpectationError::LIB_DIRECTORY - end - - def test_should_set_error_message - expectation_error = ExpectationError.new('message') - assert_equal 'message', expectation_error.message - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/expectation_list_test.rb b/vendor/gems/mocha-0.5.6/test/unit/expectation_list_test.rb deleted file mode 100644 index 59dd410a1f..0000000000 --- a/vendor/gems/mocha-0.5.6/test/unit/expectation_list_test.rb +++ /dev/null @@ -1,75 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "test_helper") -require 'mocha/expectation_list' -require 'mocha/expectation' -require 'set' -require 'method_definer' - -class ExpectationListTest < Test::Unit::TestCase - - include Mocha - - def test_should_return_added_expectation - expectation_list = ExpectationList.new - expectation = Expectation.new(nil, :my_method) - assert_same expectation, expectation_list.add(expectation) - end - - def test_should_find_matching_expectation - expectation_list = ExpectationList.new - expectation1 = Expectation.new(nil, :my_method).with(:argument1, :argument2) - expectation2 = Expectation.new(nil, :my_method).with(:argument3, :argument4) - expectation_list.add(expectation1) - expectation_list.add(expectation2) - assert_same expectation2, expectation_list.detect(:my_method, :argument3, :argument4) - end - - def test_should_find_most_recent_matching_expectation - expectation_list = ExpectationList.new - expectation1 = Expectation.new(nil, :my_method).with(:argument1, :argument2) - expectation2 = Expectation.new(nil, :my_method).with(:argument1, :argument2) - expectation_list.add(expectation1) - expectation_list.add(expectation2) - assert_same expectation2, expectation_list.detect(:my_method, :argument1, :argument2) - end - - def test_should_find_most_recent_matching_expectation_but_give_preference_to_those_allowing_invocations - expectation_list = ExpectationList.new - expectation1 = Expectation.new(nil, :my_method) - expectation2 = Expectation.new(nil, :my_method) - expectation1.define_instance_method(:invocations_allowed?) { true } - expectation2.define_instance_method(:invocations_allowed?) { false } - expectation_list.add(expectation1) - expectation_list.add(expectation2) - assert_same expectation1, expectation_list.detect(:my_method) - end - - def test_should_find_most_recent_matching_expectation_if_no_matching_expectations_allow_invocations - expectation_list = ExpectationList.new - expectation1 = Expectation.new(nil, :my_method) - expectation2 = Expectation.new(nil, :my_method) - expectation1.define_instance_method(:invocations_allowed?) { false } - expectation2.define_instance_method(:invocations_allowed?) { false } - expectation_list.add(expectation1) - expectation_list.add(expectation2) - assert_same expectation2, expectation_list.detect(:my_method) - end - - def test_should_find_expectations_for_the_same_method_no_matter_what_the_arguments - expectation_list = ExpectationList.new - expectation1 = Expectation.new(nil, :my_method).with(:argument1, :argument2) - expectation_list.add(expectation1) - expectation2 = Expectation.new(nil, :my_method).with(:argument3, :argument4) - expectation_list.add(expectation2) - assert_equal [expectation1, expectation2].to_set, expectation_list.similar(:my_method).to_set - end - - def test_should_ignore_expectations_for_different_methods - expectation_list = ExpectationList.new - expectation1 = Expectation.new(nil, :method1).with(:argument1, :argument2) - expectation_list.add(expectation1) - expectation2 = Expectation.new(nil, :method2).with(:argument1, :argument2) - expectation_list.add(expectation2) - assert_equal [expectation2], expectation_list.similar(:method2) - end - -end diff --git a/vendor/gems/mocha-0.5.6/test/unit/expectation_raiser_test.rb b/vendor/gems/mocha-0.5.6/test/unit/expectation_raiser_test.rb deleted file mode 100644 index 3b46d8fd87..0000000000 --- a/vendor/gems/mocha-0.5.6/test/unit/expectation_raiser_test.rb +++ /dev/null @@ -1,28 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "test_helper") - -require 'mocha/exception_raiser' - -class ExceptionRaiserTest < Test::Unit::TestCase - - include Mocha - - def test_should_raise_exception_with_specified_class_and_default_message - exception_class = Class.new(StandardError) - raiser = ExceptionRaiser.new(exception_class, nil) - exception = assert_raises(exception_class) { raiser.evaluate } - assert_equal exception_class.to_s, exception.message - end - - def test_should_raise_exception_with_specified_class_and_message - exception_class = Class.new(StandardError) - raiser = ExceptionRaiser.new(exception_class, 'message') - exception = assert_raises(exception_class) { raiser.evaluate } - assert_equal 'message', exception.message - end - - def test_should_raise_interrupt_exception_with_default_message_so_it_works_in_ruby_1_8_6 - raiser = ExceptionRaiser.new(Interrupt, nil) - assert_raises(Interrupt) { raiser.evaluate } - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/expectation_test.rb b/vendor/gems/mocha-0.5.6/test/unit/expectation_test.rb deleted file mode 100644 index cdb38eb9b7..0000000000 --- a/vendor/gems/mocha-0.5.6/test/unit/expectation_test.rb +++ /dev/null @@ -1,483 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "test_helper") -require 'method_definer' -require 'mocha/expectation' -require 'mocha/sequence' -require 'execution_point' -require 'deprecation_disabler' - -class ExpectationTest < Test::Unit::TestCase - - include Mocha - include DeprecationDisabler - - def new_expectation - Expectation.new(nil, :expected_method) - end - - def test_should_match_calls_to_same_method_with_any_parameters - assert new_expectation.match?(:expected_method, 1, 2, 3) - end - - def test_should_match_calls_to_same_method_with_exactly_zero_parameters - expectation = new_expectation.with() - assert expectation.match?(:expected_method) - end - - def test_should_not_match_calls_to_same_method_with_more_than_zero_parameters - expectation = new_expectation.with() - assert !expectation.match?(:expected_method, 1, 2, 3) - end - - def test_should_match_calls_to_same_method_with_expected_parameter_values - expectation = new_expectation.with(1, 2, 3) - assert expectation.match?(:expected_method, 1, 2, 3) - end - - def test_should_match_calls_to_same_method_with_parameters_constrained_as_expected - expectation = new_expectation.with() {|x, y, z| x + y == z} - assert expectation.match?(:expected_method, 1, 2, 3) - end - - def test_should_not_match_calls_to_different_method_with_parameters_constrained_as_expected - expectation = new_expectation.with() {|x, y, z| x + y == z} - assert !expectation.match?(:different_method, 1, 2, 3) - end - - def test_should_not_match_calls_to_different_methods_with_no_parameters - assert !new_expectation.match?(:unexpected_method) - end - - def test_should_not_match_calls_to_same_method_with_too_few_parameters - expectation = new_expectation.with(1, 2, 3) - assert !expectation.match?(:unexpected_method, 1, 2) - end - - def test_should_not_match_calls_to_same_method_with_too_many_parameters - expectation = new_expectation.with(1, 2) - assert !expectation.match?(:unexpected_method, 1, 2, 3) - end - - def test_should_not_match_calls_to_same_method_with_unexpected_parameter_values - expectation = new_expectation.with(1, 2, 3) - assert !expectation.match?(:unexpected_method, 1, 0, 3) - end - - def test_should_not_match_calls_to_same_method_with_parameters_not_constrained_as_expected - expectation = new_expectation.with() {|x, y, z| x + y == z} - assert !expectation.match?(:expected_method, 1, 0, 3) - end - - def test_should_allow_invocations_until_expected_invocation_count_is_one_and_actual_invocation_count_would_be_two - expectation = new_expectation.times(1) - assert expectation.invocations_allowed? - expectation.invoke - assert !expectation.invocations_allowed? - end - - def test_should_allow_invocations_until_expected_invocation_count_is_two_and_actual_invocation_count_would_be_three - expectation = new_expectation.times(2) - assert expectation.invocations_allowed? - expectation.invoke - assert expectation.invocations_allowed? - expectation.invoke - assert !expectation.invocations_allowed? - end - - def test_should_allow_invocations_until_expected_invocation_count_is_a_range_from_two_to_three_and_actual_invocation_count_would_be_four - expectation = new_expectation.times(2..3) - assert expectation.invocations_allowed? - expectation.invoke - assert expectation.invocations_allowed? - expectation.invoke - assert expectation.invocations_allowed? - expectation.invoke - assert !expectation.invocations_allowed? - end - - def test_should_store_provided_backtrace - backtrace = Object.new - expectation = Expectation.new(nil, :expected_method, backtrace) - assert_equal backtrace, expectation.backtrace - end - - def test_should_default_backtrace_to_caller - execution_point = ExecutionPoint.current; expectation = Expectation.new(nil, :expected_method) - assert_equal execution_point, ExecutionPoint.new(expectation.backtrace) - end - - def test_should_not_yield - yielded = false - new_expectation.invoke() { yielded = true } - assert_equal false, yielded - end - - def test_should_yield_no_parameters - expectation = new_expectation().yields() - yielded_parameters = nil - expectation.invoke() { |*parameters| yielded_parameters = parameters } - assert_equal Array.new, yielded_parameters - end - - def test_should_yield_with_specified_parameters - expectation = new_expectation().yields(1, 2, 3) - yielded_parameters = nil - expectation.invoke() { |*parameters| yielded_parameters = parameters } - assert_equal [1, 2, 3], yielded_parameters - end - - def test_should_yield_different_parameters_on_consecutive_invocations - expectation = new_expectation().yields(1, 2, 3).yields(4, 5) - yielded_parameters = [] - expectation.invoke() { |*parameters| yielded_parameters << parameters } - expectation.invoke() { |*parameters| yielded_parameters << parameters } - assert_equal [[1, 2, 3], [4, 5]], yielded_parameters - end - - def test_should_yield_multiple_times_for_single_invocation - expectation = new_expectation().multiple_yields([1, 2, 3], [4, 5]) - yielded_parameters = [] - expectation.invoke() { |*parameters| yielded_parameters << parameters } - assert_equal [[1, 2, 3], [4, 5]], yielded_parameters - end - - def test_should_yield_multiple_times_for_first_invocation_and_once_for_second_invocation - expectation = new_expectation().multiple_yields([1, 2, 3], [4, 5]).then.yields(6, 7) - yielded_parameters = [] - expectation.invoke() { |*parameters| yielded_parameters << parameters } - expectation.invoke() { |*parameters| yielded_parameters << parameters } - assert_equal [[1, 2, 3], [4, 5], [6, 7]], yielded_parameters - end - - def test_should_return_specified_value - expectation = new_expectation.returns(99) - assert_equal 99, expectation.invoke - end - - def test_should_return_same_specified_value_multiple_times - expectation = new_expectation.returns(99) - assert_equal 99, expectation.invoke - assert_equal 99, expectation.invoke - end - - def test_should_return_specified_values_on_consecutive_calls - expectation = new_expectation.returns(99, 100, 101) - assert_equal 99, expectation.invoke - assert_equal 100, expectation.invoke - assert_equal 101, expectation.invoke - end - - def test_should_return_specified_values_on_consecutive_calls_even_if_values_are_modified - values = [99, 100, 101] - expectation = new_expectation.returns(*values) - values.shift - assert_equal 99, expectation.invoke - assert_equal 100, expectation.invoke - assert_equal 101, expectation.invoke - end - - def test_should_return_nil_by_default - assert_nil new_expectation.invoke - end - - def test_should_return_nil_if_no_value_specified - expectation = new_expectation.returns() - assert_nil expectation.invoke - end - - def test_should_return_evaluated_proc - proc = lambda { 99 } - expectation = new_expectation.returns(proc) - result = nil - disable_deprecations { result = expectation.invoke } - assert_equal 99, result - end - - def test_should_return_evaluated_proc_without_using_is_a_method - proc = lambda { 99 } - proc.define_instance_accessor(:called) - proc.called = false - proc.replace_instance_method(:is_a?) { self.called = true; true} - expectation = new_expectation.returns(proc) - disable_deprecations { expectation.invoke } - assert_equal false, proc.called - end - - def test_should_raise_runtime_exception - expectation = new_expectation.raises - assert_raise(RuntimeError) { expectation.invoke } - end - - def test_should_raise_custom_exception - exception = Class.new(Exception) - expectation = new_expectation.raises(exception) - assert_raise(exception) { expectation.invoke } - end - - def test_should_raise_same_instance_of_custom_exception - exception_klass = Class.new(StandardError) - expected_exception = exception_klass.new - expectation = new_expectation.raises(expected_exception) - actual_exception = assert_raise(exception_klass) { expectation.invoke } - assert_same expected_exception, actual_exception - end - - def test_should_use_the_default_exception_message - expectation = new_expectation.raises(Exception) - exception = assert_raise(Exception) { expectation.invoke } - assert_equal Exception.new.message, exception.message - end - - def test_should_raise_custom_exception_with_message - exception_msg = "exception message" - expectation = new_expectation.raises(Exception, exception_msg) - exception = assert_raise(Exception) { expectation.invoke } - assert_equal exception_msg, exception.message - end - - def test_should_return_values_then_raise_exception - expectation = new_expectation.returns(1, 2).then.raises() - assert_equal 1, expectation.invoke - assert_equal 2, expectation.invoke - assert_raise(RuntimeError) { expectation.invoke } - end - - def test_should_raise_exception_then_return_values - expectation = new_expectation.raises().then.returns(1, 2) - assert_raise(RuntimeError) { expectation.invoke } - assert_equal 1, expectation.invoke - assert_equal 2, expectation.invoke - end - - def test_should_not_raise_error_on_verify_if_expected_call_was_made - expectation = new_expectation - expectation.invoke - assert_nothing_raised(ExpectationError) { - expectation.verify - } - end - - def test_should_raise_error_on_verify_if_call_expected_once_but_invoked_twice - expectation = new_expectation.once - expectation.invoke - expectation.invoke - assert_raises(ExpectationError) { - expectation.verify - } - end - - def test_should_raise_error_on_verify_if_call_expected_once_but_not_invoked - expectation = new_expectation.once - assert_raises(ExpectationError) { - expectation.verify - } - end - - def test_should_not_raise_error_on_verify_if_call_expected_once_and_invoked_once - expectation = new_expectation.once - expectation.invoke - assert_nothing_raised(ExpectationError) { - expectation.verify - } - end - - def test_should_not_raise_error_on_verify_if_expected_call_was_made_at_least_once - expectation = new_expectation.at_least_once - 3.times {expectation.invoke} - assert_nothing_raised(ExpectationError) { - expectation.verify - } - end - - def test_should_raise_error_on_verify_if_expected_call_was_not_made_at_least_once - expectation = new_expectation.with(1, 2, 3).at_least_once - e = assert_raise(ExpectationError) { - expectation.verify - } - assert_match(/expected calls: at least 1, actual calls: 0/i, e.message) - end - - def test_should_not_raise_error_on_verify_if_expected_call_was_made_expected_number_of_times - expectation = new_expectation.times(2) - 2.times {expectation.invoke} - assert_nothing_raised(ExpectationError) { - expectation.verify - } - end - - def test_should_expect_call_not_to_be_made - expectation = new_expectation - expectation.define_instance_accessor(:how_many_times) - expectation.replace_instance_method(:times) { |how_many_times| self.how_many_times = how_many_times } - expectation.never - assert_equal 0, expectation.how_many_times - end - - def test_should_raise_error_on_verify_if_expected_call_was_made_too_few_times - expectation = new_expectation.times(2) - 1.times {expectation.invoke} - e = assert_raise(ExpectationError) { - expectation.verify - } - assert_match(/expected calls: 2, actual calls: 1/i, e.message) - end - - def test_should_raise_error_on_verify_if_expected_call_was_made_too_many_times - expectation = new_expectation.times(2) - 3.times {expectation.invoke} - assert_raise(ExpectationError) { - expectation.verify - } - end - - def test_should_yield_self_to_block - expectation = new_expectation - expectation.invoke - yielded_expectation = nil - expectation.verify { |x| yielded_expectation = x } - assert_equal expectation, yielded_expectation - end - - def test_should_yield_to_block_before_raising_exception - yielded = false - assert_raise(ExpectationError) { - new_expectation.verify { |x| yielded = true } - } - assert yielded - end - - def test_should_store_backtrace_from_point_where_expectation_was_created - execution_point = ExecutionPoint.current; expectation = Expectation.new(nil, :expected_method) - assert_equal execution_point, ExecutionPoint.new(expectation.backtrace) - end - - def test_should_set_backtrace_on_assertion_failed_error_to_point_where_expectation_was_created - execution_point = ExecutionPoint.current; expectation = Expectation.new(nil, :expected_method) - error = assert_raise(ExpectationError) { - expectation.verify - } - assert_equal execution_point, ExecutionPoint.new(error.backtrace) - end - - def test_should_display_expectation_in_exception_message - options = [:a, :b, {:c => 1, :d => 2}] - expectation = new_expectation.with(*options) - exception = assert_raise(ExpectationError) { expectation.verify } - assert exception.message.include?(expectation.method_signature) - end - - class FakeMock - - def initialize(name) - @name = name - end - - def mocha_inspect - @name - end - - end - - def test_should_raise_error_with_message_indicating_which_method_was_expected_to_be_called_on_which_mock_object_with_which_parameters_and_in_what_sequences - mock = FakeMock.new('mock') - sequence_one = Sequence.new('one') - sequence_two = Sequence.new('two') - expectation = Expectation.new(mock, :expected_method).with(1, 2, {'a' => true, :b => false}, [1, 2, 3]).in_sequence(sequence_one, sequence_two) - e = assert_raise(ExpectationError) { expectation.verify } - assert_match "mock.expected_method(1, 2, {'a' => true, :b => false}, [1, 2, 3]); in sequence 'one'; in sequence 'two'", e.message - end - - class FakeConstraint - - def initialize(allows_invocation_now) - @allows_invocation_now = allows_invocation_now - end - - def allows_invocation_now? - @allows_invocation_now - end - - end - - def test_should_be_in_correct_order_if_all_ordering_constraints_allow_invocation_now - constraint_one = FakeConstraint.new(allows_invocation_now = true) - constraint_two = FakeConstraint.new(allows_invocation_now = true) - expectation = Expectation.new(nil, :method_one) - expectation.add_ordering_constraint(constraint_one) - expectation.add_ordering_constraint(constraint_two) - assert expectation.in_correct_order? - end - - def test_should_not_be_in_correct_order_if_one_ordering_constraint_does_not_allow_invocation_now - constraint_one = FakeConstraint.new(allows_invocation_now = true) - constraint_two = FakeConstraint.new(allows_invocation_now = false) - expectation = Expectation.new(nil, :method_one) - expectation.add_ordering_constraint(constraint_one) - expectation.add_ordering_constraint(constraint_two) - assert !expectation.in_correct_order? - end - - def test_should_match_if_all_ordering_constraints_allow_invocation_now - constraint_one = FakeConstraint.new(allows_invocation_now = true) - constraint_two = FakeConstraint.new(allows_invocation_now = true) - expectation = Expectation.new(nil, :method_one) - expectation.add_ordering_constraint(constraint_one) - expectation.add_ordering_constraint(constraint_two) - assert expectation.match?(:method_one) - end - - def test_should_not_match_if_one_ordering_constraints_does_not_allow_invocation_now - constraint_one = FakeConstraint.new(allows_invocation_now = true) - constraint_two = FakeConstraint.new(allows_invocation_now = false) - expectation = Expectation.new(nil, :method_one) - expectation.add_ordering_constraint(constraint_one) - expectation.add_ordering_constraint(constraint_two) - assert !expectation.match?(:method_one) - end - - def test_should_not_be_satisfied_when_required_invocation_has_not_been_made - expectation = Expectation.new(nil, :method_one).times(1) - assert !expectation.satisfied? - end - - def test_should_be_satisfied_when_required_invocation_has_been_made - expectation = Expectation.new(nil, :method_one).times(1) - expectation.invoke - assert expectation.satisfied? - end - - def test_should_not_be_satisfied_when_minimum_number_of_invocations_has_not_been_made - expectation = Expectation.new(nil, :method_one).at_least(2) - expectation.invoke - assert !expectation.satisfied? - end - - def test_should_be_satisfied_when_minimum_number_of_invocations_has_been_made - expectation = Expectation.new(nil, :method_one).at_least(2) - 2.times { expectation.invoke } - assert expectation.satisfied? - end - - class FakeSequence - - attr_reader :expectations - - def initialize - @expectations = [] - end - - def constrain_as_next_in_sequence(expectation) - @expectations << expectation - end - - end - - def test_should_tell_sequences_to_constrain_expectation_as_next_in_sequence - sequence_one = FakeSequence.new - sequence_two = FakeSequence.new - expectation = Expectation.new(nil, :method_one) - assert_equal expectation, expectation.in_sequence(sequence_one, sequence_two) - assert_equal [expectation], sequence_one.expectations - assert_equal [expectation], sequence_two.expectations - end - -end diff --git a/vendor/gems/mocha-0.5.6/test/unit/hash_inspect_test.rb b/vendor/gems/mocha-0.5.6/test/unit/hash_inspect_test.rb deleted file mode 100644 index 15ad415447..0000000000 --- a/vendor/gems/mocha-0.5.6/test/unit/hash_inspect_test.rb +++ /dev/null @@ -1,16 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "test_helper") -require 'mocha/inspect' - -class HashInspectTest < Test::Unit::TestCase - - def test_should_keep_spacing_between_key_value - hash = {:a => true} - assert_equal '{:a => true}', hash.mocha_inspect - end - - def test_should_use_mocha_inspect_on_each_item - hash = {:a => 'mocha'} - assert_equal "{:a => 'mocha'}", hash.mocha_inspect - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/infinite_range_test.rb b/vendor/gems/mocha-0.5.6/test/unit/infinite_range_test.rb deleted file mode 100644 index 7b4c8a4cb6..0000000000 --- a/vendor/gems/mocha-0.5.6/test/unit/infinite_range_test.rb +++ /dev/null @@ -1,53 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "test_helper") -require 'mocha/infinite_range' -require 'date' - -class InfiniteRangeTest < Test::Unit::TestCase - - def test_should_include_values_at_or_above_minimum - range = Range.at_least(10) - assert(range === 10) - assert(range === 11) - assert(range === 1000000) - end - - def test_should_not_include_values_below_minimum - range = Range.at_least(10) - assert_false(range === 0) - assert_false(range === 9) - assert_false(range === -11) - end - - def test_should_be_human_readable_description_for_at_least - assert_equal "at least 10", Range.at_least(10).mocha_inspect - end - - def test_should_include_values_at_or_below_maximum - range = Range.at_most(10) - assert(range === 10) - assert(range === 0) - assert(range === -1000000) - end - - def test_should_not_include_values_above_maximum - range = Range.at_most(10) - assert_false(range === 11) - assert_false(range === 1000000) - end - - def test_should_be_human_readable_description_for_at_most - assert_equal "at most 10", Range.at_most(10).mocha_inspect - end - - def test_should_be_same_as_standard_to_string - assert_equal((1..10).to_s, (1..10).mocha_inspect) - assert_equal((1...10).to_s, (1...10).mocha_inspect) - date_range = Range.new(Date.parse('2006-01-01'), Date.parse('2007-01-01')) - assert_equal date_range.to_s, date_range.mocha_inspect - end - - def assert_false(condition) - assert(!condition) - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/metaclass_test.rb b/vendor/gems/mocha-0.5.6/test/unit/metaclass_test.rb deleted file mode 100644 index 956bcb45b0..0000000000 --- a/vendor/gems/mocha-0.5.6/test/unit/metaclass_test.rb +++ /dev/null @@ -1,22 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "test_helper") -require 'mocha/metaclass' - -class MetaclassTest < Test::Unit::TestCase - - def test_should_return_objects_singleton_class - object = Object.new - assert_raises(NoMethodError) { object.success? } - - object = Object.new - assert object.__metaclass__.ancestors.include?(Object) - assert object.__metaclass__.ancestors.include?(Kernel) - assert object.__metaclass__.is_a?(Class) - - object.__metaclass__.class_eval { def success?; true; end } - assert object.success? - - object = Object.new - assert_raises(NoMethodError) { object.success? } - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/method_matcher_test.rb b/vendor/gems/mocha-0.5.6/test/unit/method_matcher_test.rb deleted file mode 100644 index 0167433e49..0000000000 --- a/vendor/gems/mocha-0.5.6/test/unit/method_matcher_test.rb +++ /dev/null @@ -1,23 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "test_helper") -require 'mocha/method_matcher' - -class MethodMatcherTest < Test::Unit::TestCase - - include Mocha - - def test_should_match_if_actual_method_name_is_same_as_expected_method_name - method_matcher = MethodMatcher.new(:method_name) - assert method_matcher.match?(:method_name) - end - - def test_should_not_match_if_actual_method_name_is_not_same_as_expected_method_name - method_matcher = MethodMatcher.new(:method_name) - assert !method_matcher.match?(:different_method_name) - end - - def test_should_describe_what_method_is_expected - method_matcher = MethodMatcher.new(:method_name) - assert_equal "method_name", method_matcher.mocha_inspect - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/missing_expectation_test.rb b/vendor/gems/mocha-0.5.6/test/unit/missing_expectation_test.rb deleted file mode 100644 index 9d3b45aa76..0000000000 --- a/vendor/gems/mocha-0.5.6/test/unit/missing_expectation_test.rb +++ /dev/null @@ -1,42 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "test_helper") - -require 'mocha/missing_expectation' -require 'mocha/mock' - -class MissingExpectationTest < Test::Unit::TestCase - - include Mocha - - def test_should_report_similar_expectations - mock = Mock.new - expectation_1 = mock.expects(:method_one).with(1) - expectation_2 = mock.expects(:method_one).with(1, 1) - expectation_3 = mock.expects(:method_two).with(2) - - missing_expectation = MissingExpectation.new(mock, :method_one) - exception = assert_raise(ExpectationError) { missing_expectation.verify } - - expected_message = [ - "#{missing_expectation.error_message(0, 1)}", - "Similar expectations:", - "#{expectation_1.method_signature}", - "#{expectation_2.method_signature}" - ].join("\n") - - assert_equal expected_message, exception.message - end - - def test_should_not_report_similar_expectations_if_there_are_none - mock = Mock.new - mock.expects(:method_two).with(2) - mock.expects(:method_two).with(2, 2) - - missing_expectation = MissingExpectation.new(mock, :method_one) - exception = assert_raise(ExpectationError) { missing_expectation.verify } - - expected_message = "#{missing_expectation.error_message(0, 1)}" - - assert_equal expected_message, exception.message - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/mock_test.rb b/vendor/gems/mocha-0.5.6/test/unit/mock_test.rb deleted file mode 100644 index f844bc81d1..0000000000 --- a/vendor/gems/mocha-0.5.6/test/unit/mock_test.rb +++ /dev/null @@ -1,323 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "test_helper") -require 'mocha/mock' -require 'mocha/expectation_error' -require 'set' - -class MockTest < Test::Unit::TestCase - - include Mocha - - def test_should_set_single_expectation - mock = Mock.new - mock.expects(:method1).returns(1) - assert_nothing_raised(ExpectationError) do - assert_equal 1, mock.method1 - end - end - - def test_should_build_and_store_expectations - mock = Mock.new - expectation = mock.expects(:method1) - assert_not_nil expectation - assert_equal [expectation], mock.expectations.to_a - end - - def test_should_not_stub_everything_by_default - mock = Mock.new - assert_equal false, mock.everything_stubbed - end - - def test_should_stub_everything - mock = Mock.new - mock.stub_everything - assert_equal true, mock.everything_stubbed - end - - def test_should_display_object_id_for_mocha_inspect_if_mock_has_no_name - mock = Mock.new - assert_match Regexp.new("^#$"), mock.mocha_inspect - end - - def test_should_display_name_for_mocha_inspect_if_mock_has_name - mock = Mock.new('named_mock') - assert_equal "#", mock.mocha_inspect - end - - def test_should_display_object_id_for_inspect_if_mock_has_no_name - mock = Mock.new - assert_match Regexp.new("^#$"), mock.inspect - end - - def test_should_display_name_for_inspect_if_mock_has_name - mock = Mock.new('named_mock') - assert_equal "#", mock.inspect - end - - def test_should_be_able_to_extend_mock_object_with_module - mock = Mock.new - assert_nothing_raised(ExpectationError) { mock.extend(Module.new) } - end - - def test_should_be_equal - mock = Mock.new - assert_equal true, mock.eql?(mock) - end - - if RUBY_VERSION < '1.9' - OBJECT_METHODS = STANDARD_OBJECT_PUBLIC_INSTANCE_METHODS.reject { |m| m =~ /^__.*__$/ } - else - OBJECT_METHODS = STANDARD_OBJECT_PUBLIC_INSTANCE_METHODS.reject { |m| m =~ /^__.*__$/ || m == :object_id } - end - - def test_should_be_able_to_mock_standard_object_methods - mock = Mock.new - OBJECT_METHODS.each { |method| mock.__expects__(method.to_sym).returns(method) } - OBJECT_METHODS.each { |method| assert_equal method, mock.__send__(method.to_sym) } - assert_nothing_raised(ExpectationError) { mock.verify } - end - - def test_should_be_able_to_stub_standard_object_methods - mock = Mock.new - OBJECT_METHODS.each { |method| mock.__stubs__(method.to_sym).returns(method) } - OBJECT_METHODS.each { |method| assert_equal method, mock.__send__(method.to_sym) } - end - - def test_should_create_and_add_expectations - mock = Mock.new - expectation1 = mock.expects(:method1) - expectation2 = mock.expects(:method2) - assert_equal [expectation1, expectation2].to_set, mock.expectations.to_set - end - - def test_should_pass_backtrace_into_expectation - mock = Mock.new - backtrace = Object.new - expectation = mock.expects(:method1, backtrace) - assert_equal backtrace, expectation.backtrace - end - - def test_should_pass_backtrace_into_stub - mock = Mock.new - backtrace = Object.new - stub = mock.stubs(:method1, backtrace) - assert_equal backtrace, stub.backtrace - end - - def test_should_create_and_add_stubs - mock = Mock.new - stub1 = mock.stubs(:method1) - stub2 = mock.stubs(:method2) - assert_equal [stub1, stub2].to_set, mock.expectations.to_set - end - - def test_should_invoke_expectation_and_return_result - mock = Mock.new - mock.expects(:my_method).returns(:result) - result = mock.my_method - assert_equal :result, result - end - - def test_should_not_raise_error_if_stubbing_everything - mock = Mock.new - mock.stub_everything - result = nil - assert_nothing_raised(ExpectationError) do - result = mock.unexpected_method - end - assert_nil result - end - - def test_should_raise_assertion_error_for_unexpected_method_call - mock = Mock.new - error = assert_raise(ExpectationError) do - mock.unexpected_method_called(:my_method, :argument1, :argument2) - end - assert_match(/my_method/, error.message) - assert_match(/argument1/, error.message) - assert_match(/argument2/, error.message) - end - - def test_should_indicate_unexpected_method_called - mock = Mock.new - class << mock - attr_accessor :symbol, :arguments - def unexpected_method_called(symbol, *arguments) - self.symbol, self.arguments = symbol, arguments - end - end - mock.my_method(:argument1, :argument2) - assert_equal :my_method, mock.symbol - assert_equal [:argument1, :argument2], mock.arguments - end - - def test_should_verify_that_all_expectations_have_been_fulfilled - mock = Mock.new - mock.expects(:method1) - mock.expects(:method2) - mock.method1 - assert_raise(ExpectationError) do - mock.verify - end - end - - def test_should_report_possible_expectations - mock = Mock.new - mock.expects(:expected_method).with(1) - exception = assert_raise(ExpectationError) { mock.expected_method(2) } - assert_equal "#{mock.mocha_inspect}.expected_method(2) - expected calls: 0, actual calls: 1\nSimilar expectations:\n#{mock.mocha_inspect}.expected_method(1)", exception.message - end - - def test_should_pass_block_through_to_expectations_verify_method - mock = Mock.new - expected_expectation = mock.expects(:method1) - mock.method1 - expectations = [] - mock.verify() { |expectation| expectations << expectation } - assert_equal [expected_expectation], expectations - end - - def test_should_yield_supplied_parameters_to_block - mock = Mock.new - parameters_for_yield = [1, 2, 3] - mock.expects(:method1).yields(*parameters_for_yield) - yielded_parameters = nil - mock.method1() { |*parameters| yielded_parameters = parameters } - assert_equal parameters_for_yield, yielded_parameters - end - - def test_should_set_up_multiple_expectations_with_return_values - mock = Mock.new - mock.expects(:method1 => :result1, :method2 => :result2) - assert_equal :result1, mock.method1 - assert_equal :result2, mock.method2 - end - - def test_should_set_up_multiple_stubs_with_return_values - mock = Mock.new - mock.stubs(:method1 => :result1, :method2 => :result2) - assert_equal :result1, mock.method1 - assert_equal :result2, mock.method2 - end - - def test_should_keep_returning_specified_value_for_stubs - mock = Mock.new - mock.stubs(:method1).returns(1) - assert_equal 1, mock.method1 - assert_equal 1, mock.method1 - end - - def test_should_keep_returning_specified_value_for_expects - mock = Mock.new - mock.expects(:method1).times(2).returns(1) - assert_equal 1, mock.method1 - assert_equal 1, mock.method1 - end - - def test_should_match_most_recent_call_to_expects - mock = Mock.new - mock.expects(:method1).returns(0) - mock.expects(:method1).returns(1) - assert_equal 1, mock.method1 - end - - def test_should_match_most_recent_call_to_stubs - mock = Mock.new - mock.stubs(:method1).returns(0) - mock.stubs(:method1).returns(1) - assert_equal 1, mock.method1 - end - - def test_should_match_most_recent_call_to_stubs_or_expects - mock = Mock.new - mock.stubs(:method1).returns(0) - mock.expects(:method1).returns(1) - assert_equal 1, mock.method1 - end - - def test_should_match_most_recent_call_to_expects_or_stubs - mock = Mock.new - mock.expects(:method1).returns(0) - mock.stubs(:method1).returns(1) - assert_equal 1, mock.method1 - end - - def test_should_respond_to_expected_method - mock = Mock.new - mock.expects(:method1) - assert_equal true, mock.respond_to?(:method1) - end - - def test_should_not_respond_to_unexpected_method - mock = Mock.new - assert_equal false, mock.respond_to?(:method1) - end - - def test_should_respond_to_methods_which_the_responder_does_responds_to - instance = Class.new do - define_method(:respond_to?) { |symbol| true } - end.new - mock = Mock.new - mock.responds_like(instance) - assert_equal true, mock.respond_to?(:invoked_method) - end - - def test_should_not_respond_to_methods_which_the_responder_does_not_responds_to - instance = Class.new do - define_method(:respond_to?) { |symbol| false } - end.new - mock = Mock.new - mock.responds_like(instance) - assert_equal false, mock.respond_to?(:invoked_method) - end - - def test_should_return_itself_to_allow_method_chaining - mock = Mock.new - assert_same mock.responds_like(Object.new), mock - end - - def test_should_not_raise_no_method_error_if_mock_is_not_restricted_to_respond_like_a_responder - instance = Class.new do - define_method(:respond_to?) { true } - end.new - mock = Mock.new - mock.stubs(:invoked_method) - assert_nothing_raised(NoMethodError) { mock.invoked_method } - end - - def test_should_not_raise_no_method_error_if_responder_does_respond_to_invoked_method - instance = Class.new do - define_method(:respond_to?) { |symbol| true } - end.new - mock = Mock.new - mock.responds_like(instance) - mock.stubs(:invoked_method) - assert_nothing_raised(NoMethodError) { mock.invoked_method } - end - - def test_should_raise_no_method_error_if_responder_does_not_respond_to_invoked_method - instance = Class.new do - define_method(:respond_to?) { |symbol| false } - define_method(:mocha_inspect) { 'mocha_inspect' } - end.new - mock = Mock.new - mock.responds_like(instance) - mock.stubs(:invoked_method) - assert_raises(NoMethodError) { mock.invoked_method } - end - - def test_should_raise_no_method_error_with_message_indicating_that_mock_is_constrained_to_respond_like_responder - instance = Class.new do - define_method(:respond_to?) { |symbol| false } - define_method(:mocha_inspect) { 'mocha_inspect' } - end.new - mock = Mock.new - mock.responds_like(instance) - mock.stubs(:invoked_method) - begin - mock.invoked_method - rescue NoMethodError => e - assert_match(/which responds like mocha_inspect/, e.message) - end - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/multiple_yields_test.rb b/vendor/gems/mocha-0.5.6/test/unit/multiple_yields_test.rb deleted file mode 100644 index 65724a8fbc..0000000000 --- a/vendor/gems/mocha-0.5.6/test/unit/multiple_yields_test.rb +++ /dev/null @@ -1,18 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "test_helper") - -require 'mocha/multiple_yields' - -class MultipleYieldsTest < Test::Unit::TestCase - - include Mocha - - def test_should_provide_parameters_for_multiple_yields_in_single_invocation - parameter_group = MultipleYields.new([1, 2, 3], [4, 5]) - parameter_groups = [] - parameter_group.each do |parameters| - parameter_groups << parameters - end - assert_equal [[1, 2, 3], [4, 5]], parameter_groups - end - -end diff --git a/vendor/gems/mocha-0.5.6/test/unit/no_yield_test.rb b/vendor/gems/mocha-0.5.6/test/unit/no_yield_test.rb deleted file mode 100644 index 544d1ef257..0000000000 --- a/vendor/gems/mocha-0.5.6/test/unit/no_yield_test.rb +++ /dev/null @@ -1,18 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "test_helper") - -require 'mocha/no_yields' - -class NoYieldsTest < Test::Unit::TestCase - - include Mocha - - def test_should_provide_parameters_for_no_yields_in_single_invocation - parameter_group = NoYields.new - parameter_groups = [] - parameter_group.each do |parameters| - parameter_groups << parameters - end - assert_equal [], parameter_groups - end - -end diff --git a/vendor/gems/mocha-0.5.6/test/unit/object_inspect_test.rb b/vendor/gems/mocha-0.5.6/test/unit/object_inspect_test.rb deleted file mode 100644 index 56d84a96dd..0000000000 --- a/vendor/gems/mocha-0.5.6/test/unit/object_inspect_test.rb +++ /dev/null @@ -1,37 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "test_helper") -require 'mocha/inspect' -require 'method_definer' - -class ObjectInspectTest < Test::Unit::TestCase - - def test_should_return_default_string_representation_of_object_not_including_instance_variables - object = Object.new - class << object - attr_accessor :attribute - end - object.attribute = 'instance_variable' - assert_match Regexp.new("^#$"), object.mocha_inspect - assert_no_match(/instance_variable/, object.mocha_inspect) - end - - def test_should_return_customized_string_representation_of_object - object = Object.new - class << object - define_method(:inspect) { 'custom_inspect' } - end - assert_equal 'custom_inspect', object.mocha_inspect - end - - def test_should_use_underscored_id_instead_of_object_id_or_id_so_that_they_can_be_stubbed - object = Object.new - object.define_instance_accessor(:called) - object.called = false - object.replace_instance_method(:object_id) { self.called = true; 1 } - if RUBY_VERSION < '1.9' - object.replace_instance_method(:id) { self.called = true; 1 } - end - object.mocha_inspect - assert_equal false, object.called - end - -end diff --git a/vendor/gems/mocha-0.5.6/test/unit/object_test.rb b/vendor/gems/mocha-0.5.6/test/unit/object_test.rb deleted file mode 100644 index 660b7d24f2..0000000000 --- a/vendor/gems/mocha-0.5.6/test/unit/object_test.rb +++ /dev/null @@ -1,165 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "test_helper") -require 'mocha/mock' -require 'method_definer' - -require 'mocha/object' - -class ObjectTest < Test::Unit::TestCase - - include Mocha - - def test_should_build_mocha - instance = Object.new - mocha = instance.mocha - assert_not_nil mocha - assert mocha.is_a?(Mock) - end - - def test_should_reuse_existing_mocha - instance = Object.new - mocha_1 = instance.mocha - mocha_2 = instance.mocha - assert_equal mocha_1, mocha_2 - end - - def test_should_reset_mocha - instance = Object.new - assert_nil instance.reset_mocha - end - - def test_should_stub_instance_method - instance = Object.new - $stubba = Mock.new - $stubba.expects(:stub).with(Mocha::InstanceMethod.new(instance, :method1)) - instance.expects(:method1) - $stubba.verify - end - - def test_should_build_and_store_expectation - instance = Object.new - $stubba = Mock.new - $stubba.stubs(:stub) - expectation = instance.expects(:method1) - assert_equal [expectation], instance.mocha.expectations.to_a - end - - def test_should_verify_expectations - instance = Object.new - $stubba = Mock.new - $stubba.stubs(:stub) - instance.expects(:method1).with(:value1, :value2) - assert_raise(ExpectationError) { instance.verify } - end - - def test_should_pass_backtrace_into_expects - instance = Object.new - $stubba = Mock.new - $stubba.stubs(:stub) - mocha = Object.new - mocha.define_instance_accessor(:expects_parameters) - mocha.define_instance_method(:expects) { |*parameters| self.expects_parameters = parameters } - backtrace = Object.new - instance.define_instance_method(:mocha) { mocha } - instance.define_instance_method(:caller) { backtrace } - instance.expects(:method1) - assert_equal [:method1, backtrace], mocha.expects_parameters - end - - def test_should_pass_backtrace_into_stubs - instance = Object.new - $stubba = Mock.new - $stubba.stubs(:stub) - mocha = Object.new - mocha.define_instance_accessor(:stubs_parameters) - mocha.define_instance_method(:stubs) { |*parameters| self.stubs_parameters = parameters } - backtrace = Object.new - instance.define_instance_method(:mocha) { mocha } - instance.define_instance_method(:caller) { backtrace } - instance.stubs(:method1) - assert_equal [:method1, backtrace], mocha.stubs_parameters - end - - def test_should_build_any_instance_object - klass = Class.new - any_instance = klass.any_instance - assert_not_nil any_instance - assert any_instance.is_a?(Class::AnyInstance) - end - - def test_should_return_same_any_instance_object - klass = Class.new - any_instance_1 = klass.any_instance - any_instance_2 = klass.any_instance - assert_equal any_instance_1, any_instance_2 - end - - def test_should_stub_class_method - klass = Class.new - $stubba = Mock.new - $stubba.expects(:stub).with(Mocha::ClassMethod.new(klass, :method1)) - klass.expects(:method1) - $stubba.verify - end - - def test_should_build_and_store_class_method_expectation - klass = Class.new - $stubba = Mock.new - $stubba.stubs(:stub) - expectation = klass.expects(:method1) - assert_equal [expectation], klass.mocha.expectations.to_a - end - - def test_should_stub_module_method - mod = Module.new - $stubba = Mock.new - $stubba.expects(:stub).with(Mocha::ClassMethod.new(mod, :method1)) - mod.expects(:method1) - $stubba.verify - end - - def test_should_build_and_store_module_method_expectation - mod = Module.new - $stubba = Mock.new - $stubba.stubs(:stub) - expectation = mod.expects(:method1) - assert_equal [expectation], mod.mocha.expectations.to_a - end - - def test_should_use_stubba_instance_method_for_object - assert_equal Mocha::InstanceMethod, Object.new.stubba_method - end - - def test_should_use_stubba_class_method_for_module - assert_equal Mocha::ClassMethod, Module.new.stubba_method - end - - def test_should_use_stubba_class_method_for_class - assert_equal Mocha::ClassMethod, Class.new.stubba_method - end - - def test_should_use_stubba_class_method_for_any_instance - assert_equal Mocha::AnyInstanceMethod, Class::AnyInstance.new(nil).stubba_method - end - - def test_should_stub_self_for_object - object = Object.new - assert_equal object, object.stubba_object - end - - def test_should_stub_self_for_module - mod = Module.new - assert_equal mod, mod.stubba_object - end - - def test_should_stub_self_for_class - klass = Class.new - assert_equal klass, klass.stubba_object - end - - def test_should_stub_relevant_class_for_any_instance - klass = Class.new - any_instance = Class::AnyInstance.new(klass) - assert_equal klass, any_instance.stubba_object - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._all_of_test.rb b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._all_of_test.rb deleted file mode 100644 index 8494bfb378b61b7fe643a1ed2bf1570639fff0b4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_ISq@YR(lG;w zrO>o7BE&_L^K$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_ISqxMP(lG;w zCDF7oBE&_L^K>83Qh_WZQ)`7< E0J;enCjbBd diff --git a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._anything_test.rb b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._anything_test.rb deleted file mode 100644 index bbb511c34aa624f5b911cfe4f9e2c77e12f1fa6e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_InF>@2(lG;w zCDF7oBE&_L^K>83Qh_WZ18ap^ E0JT9F6aWAK diff --git a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._has_entries_test.rb b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._has_entries_test.rb deleted file mode 100644 index d519eaa005882a8e2e0e5079cec76af8678aed83..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62zdnrUa-Iq+CMFl<=O&h<7V9M^7NwRjR43=>l;-9s*eV!XE97M6r2<(-mevZj E0QUSC-T(jq diff --git a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._has_entry_test.rb b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._has_entry_test.rb deleted file mode 100644 index e38bb38b7bf1776fbc716d9f2a8235a0f81146cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_ISqW4M(lG;w zrO>o7BE&_L^K$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_InF&-1(lG;w zrO>o7BE&_L^K$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_InF~}3(lG;w zCDF7oBE&_L^K>83Qh_WZAhQ+# Dx5OA3 diff --git a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._includes_test.rb b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._includes_test.rb deleted file mode 100644 index ca7d6d9cabaffe2247703f5c79ff17b772a34c87..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_InGIA5(lG;w zCDF7oBE&_L^K>83Qh_W(Gi!xf E0Jkd`8~^|S diff --git a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._instance_of_test.rb b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._instance_of_test.rb deleted file mode 100644 index 14618a7f4b33153f6bb8d30c23674656545fec09..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_InGaM7(lG;w zrO>o7BE&_L^K$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62u_ISqfAN(lG;w zCDF7oBE&_L^K>83Qh_WZQ)`7< E0J?}6CIA2c diff --git a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._kind_of_test.rb b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._kind_of_test.rb deleted file mode 100644 index 773437881bab61f27ba96c330b7acbe6314b4059..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_ISqM}L(lG;w zrO>o7BE&_L^K$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1(k2;dkJ62zeR>>yAnNXHBy zmPFIWh!7V|&d=4$O-wGz&rK{zE!Im;EJ`h5s7}t$Db3AOuvIX%R>;ZBO9iqF&8!t_ E0U_)eTL1t6 diff --git a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._regexp_matches_test.rb b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/._regexp_matches_test.rb deleted file mode 100644 index 2ceba479a60005a174d74e7b852c0ad9579c21b0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 178 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_InG944(lG;w zrO>o7BE&_L^K$Vqox1Ojhs@R)|o50+1L3ClDI}aRU&8_?v*32;dkJ62u_InGRG6(lG;w zrO>o7BE&_L^K 'y'}]) - end - - def test_should_describe_matcher - matcher = anything - assert_equal "anything", matcher.mocha_inspect - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/has_entries_test.rb b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/has_entries_test.rb deleted file mode 100644 index cb85265f8f..0000000000 --- a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/has_entries_test.rb +++ /dev/null @@ -1,30 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "..", "test_helper") - -require 'mocha/parameter_matchers/has_entries' -require 'mocha/inspect' - -class HasEntriesTest < Test::Unit::TestCase - - include Mocha::ParameterMatchers - - def test_should_match_hash_including_specified_entries - matcher = has_entries(:key_1 => 'value_1', :key_2 => 'value_2') - assert matcher.matches?([{ :key_1 => 'value_1', :key_2 => 'value_2', :key_3 => 'value_3' }]) - end - - def test_should_not_match_hash_not_including_specified_entries - matcher = has_entries(:key_1 => 'value_2', :key_2 => 'value_2', :key_3 => 'value_3') - assert !matcher.matches?([{ :key_1 => 'value_1', :key_2 => 'value_2' }]) - end - - def test_should_describe_matcher - matcher = has_entries(:key_1 => 'value_1', :key_2 => 'value_2') - description = matcher.mocha_inspect - matches = /has_entries\((.*)\)/.match(description) - assert_not_nil matches[0] - entries = eval(matches[1]) - assert_equal 'value_1', entries[:key_1] - assert_equal 'value_2', entries[:key_2] - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/has_entry_test.rb b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/has_entry_test.rb deleted file mode 100644 index 3717b33b02..0000000000 --- a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/has_entry_test.rb +++ /dev/null @@ -1,40 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "..", "test_helper") - -require 'mocha/parameter_matchers/has_entry' -require 'mocha/inspect' - -class HasEntryTest < Test::Unit::TestCase - - include Mocha::ParameterMatchers - - def test_should_match_hash_including_specified_key_value_pair - matcher = has_entry(:key_1, 'value_1') - assert matcher.matches?([{ :key_1 => 'value_1', :key_2 => 'value_2' }]) - end - - def test_should_not_match_hash_not_including_specified_key_value_pair - matcher = has_entry(:key_1, 'value_2') - assert !matcher.matches?([{ :key_1 => 'value_1', :key_2 => 'value_2' }]) - end - - def test_should_match_hash_including_specified_entry - matcher = has_entry(:key_1 => 'value_1') - assert matcher.matches?([{ :key_1 => 'value_1', :key_2 => 'value_2' }]) - end - - def test_should_not_match_hash_not_including_specified_entry - matcher = has_entry(:key_1 => 'value_2') - assert !matcher.matches?([{ :key_1 => 'value_1', :key_2 => 'value_2' }]) - end - - def test_should_describe_matcher_with_key_value_pair - matcher = has_entry(:key_1, 'value_1') - assert_equal "has_entry(:key_1, 'value_1')", matcher.mocha_inspect - end - - def test_should_describe_matcher_with_entry - matcher = has_entry(:key_1 => 'value_1') - assert_equal "has_entry(:key_1, 'value_1')", matcher.mocha_inspect - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/has_key_test.rb b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/has_key_test.rb deleted file mode 100644 index bc9f5065da..0000000000 --- a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/has_key_test.rb +++ /dev/null @@ -1,25 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "..", "test_helper") - -require 'mocha/parameter_matchers/has_key' -require 'mocha/inspect' - -class HasKeyTest < Test::Unit::TestCase - - include Mocha::ParameterMatchers - - def test_should_match_hash_including_specified_key - matcher = has_key(:key_1) - assert matcher.matches?([{ :key_1 => 1, :key_2 => 2 }]) - end - - def test_should_not_match_hash_not_including_specified_key - matcher = has_key(:key_1) - assert !matcher.matches?([{ :key_2 => 2 }]) - end - - def test_should_describe_matcher - matcher = has_key(:key) - assert_equal 'has_key(:key)', matcher.mocha_inspect - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/has_value_test.rb b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/has_value_test.rb deleted file mode 100644 index 6c8957fd05..0000000000 --- a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/has_value_test.rb +++ /dev/null @@ -1,25 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "..", "test_helper") - -require 'mocha/parameter_matchers/has_value' -require 'mocha/inspect' - -class HasValueTest < Test::Unit::TestCase - - include Mocha::ParameterMatchers - - def test_should_match_hash_including_specified_value - matcher = has_value('value_1') - assert matcher.matches?([{ :key_1 => 'value_1', :key_2 => 'value_2' }]) - end - - def test_should_not_match_hash_not_including_specified_value - matcher = has_value('value_1') - assert !matcher.matches?([{ :key_2 => 'value_2' }]) - end - - def test_should_describe_matcher - matcher = has_value('value_1') - assert_equal "has_value('value_1')", matcher.mocha_inspect - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/includes_test.rb b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/includes_test.rb deleted file mode 100644 index 70fb649d24..0000000000 --- a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/includes_test.rb +++ /dev/null @@ -1,25 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "..", "test_helper") - -require 'mocha/parameter_matchers/includes' -require 'mocha/inspect' - -class IncludesTest < Test::Unit::TestCase - - include Mocha::ParameterMatchers - - def test_should_match_object_including_value - matcher = includes(:x) - assert matcher.matches?([[:x, :y, :z]]) - end - - def test_should_not_match_object_that_does_not_include_value - matcher = includes(:not_included) - assert !matcher.matches?([[:x, :y, :z]]) - end - - def test_should_describe_matcher - matcher = includes(:x) - assert_equal "includes(:x)", matcher.mocha_inspect - end - -end diff --git a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/instance_of_test.rb b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/instance_of_test.rb deleted file mode 100644 index 415b79a483..0000000000 --- a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/instance_of_test.rb +++ /dev/null @@ -1,25 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "..", "test_helper") - -require 'mocha/parameter_matchers/instance_of' -require 'mocha/inspect' - -class InstanceOfTest < Test::Unit::TestCase - - include Mocha::ParameterMatchers - - def test_should_match_object_that_is_an_instance_of_specified_class - matcher = instance_of(String) - assert matcher.matches?(['string']) - end - - def test_should_not_match_object_that_is_not_an_instance_of_specified_class - matcher = instance_of(String) - assert !matcher.matches?([99]) - end - - def test_should_describe_matcher - matcher = instance_of(String) - assert_equal "instance_of(String)", matcher.mocha_inspect - end - -end diff --git a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/is_a_test.rb b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/is_a_test.rb deleted file mode 100644 index c9ef91965f..0000000000 --- a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/is_a_test.rb +++ /dev/null @@ -1,25 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "..", "test_helper") - -require 'mocha/parameter_matchers/is_a' -require 'mocha/inspect' - -class IsATest < Test::Unit::TestCase - - include Mocha::ParameterMatchers - - def test_should_match_object_that_is_a_specified_class - matcher = is_a(Integer) - assert matcher.matches?([99]) - end - - def test_should_not_match_object_that_is_not_a_specified_class - matcher = is_a(Integer) - assert !matcher.matches?(['string']) - end - - def test_should_describe_matcher - matcher = is_a(Integer) - assert_equal "is_a(Integer)", matcher.mocha_inspect - end - -end diff --git a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/kind_of_test.rb b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/kind_of_test.rb deleted file mode 100644 index 1167e5c9e0..0000000000 --- a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/kind_of_test.rb +++ /dev/null @@ -1,25 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "..", "test_helper") - -require 'mocha/parameter_matchers/kind_of' -require 'mocha/inspect' - -class KindOfTest < Test::Unit::TestCase - - include Mocha::ParameterMatchers - - def test_should_match_object_that_is_a_kind_of_specified_class - matcher = kind_of(Integer) - assert matcher.matches?([99]) - end - - def test_should_not_match_object_that_is_not_a_kind_of_specified_class - matcher = kind_of(Integer) - assert !matcher.matches?(['string']) - end - - def test_should_describe_matcher - matcher = kind_of(Integer) - assert_equal "kind_of(Integer)", matcher.mocha_inspect - end - -end diff --git a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/not_test.rb b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/not_test.rb deleted file mode 100644 index 4cb6790a9d..0000000000 --- a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/not_test.rb +++ /dev/null @@ -1,26 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "..", "test_helper") - -require 'mocha/parameter_matchers/not' -require 'mocha/inspect' -require 'stub_matcher' - -class NotTest < Test::Unit::TestCase - - include Mocha::ParameterMatchers - - def test_should_match_if_matcher_does_not_match - matcher = Not(Stub::Matcher.new(false)) - assert matcher.matches?(['any_old_value']) - end - - def test_should_not_match_if_matcher_does_match - matcher = Not(Stub::Matcher.new(true)) - assert !matcher.matches?(['any_old_value']) - end - - def test_should_describe_matcher - matcher = Not(Stub::Matcher.new(true)) - assert_equal 'Not(matcher(true))', matcher.mocha_inspect - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/regexp_matches_test.rb b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/regexp_matches_test.rb deleted file mode 100644 index a8294bfe05..0000000000 --- a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/regexp_matches_test.rb +++ /dev/null @@ -1,25 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "..", "test_helper") - -require 'mocha/parameter_matchers/regexp_matches' -require 'mocha/inspect' - -class MatchesTest < Test::Unit::TestCase - - include Mocha::ParameterMatchers - - def test_should_match_parameter_matching_regular_expression - matcher = regexp_matches(/oo/) - assert matcher.matches?(['foo']) - end - - def test_should_not_match_parameter_not_matching_regular_expression - matcher = regexp_matches(/oo/) - assert !matcher.matches?(['bar']) - end - - def test_should_describe_matcher - matcher = regexp_matches(/oo/) - assert_equal "regexp_matches(/oo/)", matcher.mocha_inspect - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/stub_matcher.rb b/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/stub_matcher.rb deleted file mode 100644 index 920ced23bd..0000000000 --- a/vendor/gems/mocha-0.5.6/test/unit/parameter_matchers/stub_matcher.rb +++ /dev/null @@ -1,23 +0,0 @@ -module Stub - - class Matcher - - attr_accessor :value - - def initialize(matches) - @matches = matches - end - - def matches?(available_parameters) - value = available_parameters.shift - @value = value - @matches - end - - def mocha_inspect - "matcher(#{@matches})" - end - - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/parameters_matcher_test.rb b/vendor/gems/mocha-0.5.6/test/unit/parameters_matcher_test.rb deleted file mode 100644 index 612805e454..0000000000 --- a/vendor/gems/mocha-0.5.6/test/unit/parameters_matcher_test.rb +++ /dev/null @@ -1,121 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "test_helper") -require 'mocha/parameters_matcher' - -class ParametersMatcherTest < Test::Unit::TestCase - - include Mocha - - def test_should_match_any_actual_parameters_if_no_expected_parameters_specified - parameters_matcher = ParametersMatcher.new - assert parameters_matcher.match?(actual_parameters = [1, 2, 3]) - end - - def test_should_match_if_actual_parameters_are_same_as_expected_parameters - parameters_matcher = ParametersMatcher.new(expected_parameters = [4, 5, 6]) - assert parameters_matcher.match?(actual_parameters = [4, 5, 6]) - end - - def test_should_not_match_if_actual_parameters_are_different_from_expected_parameters - parameters_matcher = ParametersMatcher.new(expected_parameters = [4, 5, 6]) - assert !parameters_matcher.match?(actual_parameters = [1, 2, 3]) - end - - def test_should_not_match_if_there_are_less_actual_parameters_than_expected_parameters - parameters_matcher = ParametersMatcher.new(expected_parameters = [4, 5, 6]) - assert !parameters_matcher.match?(actual_parameters = [4, 5]) - end - - def test_should_not_match_if_there_are_more_actual_parameters_than_expected_parameters - parameters_matcher = ParametersMatcher.new(expected_parameters = [4, 5]) - assert !parameters_matcher.match?(actual_parameters = [4, 5, 6]) - end - - def test_should_not_match_if_not_all_required_parameters_are_supplied - optionals = ParameterMatchers::Optionally.new(6, 7) - parameters_matcher = ParametersMatcher.new(expected_parameters = [4, 5, optionals]) - assert !parameters_matcher.match?(actual_parameters = [4]) - end - - def test_should_match_if_all_required_parameters_match_and_no_optional_parameters_are_supplied - optionals = ParameterMatchers::Optionally.new(6, 7) - parameters_matcher = ParametersMatcher.new(expected_parameters = [4, 5, optionals]) - assert parameters_matcher.match?(actual_parameters = [4, 5]) - end - - def test_should_match_if_all_required_and_optional_parameters_match_and_some_optional_parameters_are_supplied - optionals = ParameterMatchers::Optionally.new(6, 7) - parameters_matcher = ParametersMatcher.new(expected_parameters = [4, 5, optionals]) - assert parameters_matcher.match?(actual_parameters = [4, 5, 6]) - end - - def test_should_match_if_all_required_and_optional_parameters_match_and_all_optional_parameters_are_supplied - optionals = ParameterMatchers::Optionally.new(6, 7) - parameters_matcher = ParametersMatcher.new(expected_parameters = [4, 5, optionals]) - assert parameters_matcher.match?(actual_parameters = [4, 5, 6, 7]) - end - - def test_should_not_match_if_all_required_and_optional_parameters_match_but_too_many_optional_parameters_are_supplied - optionals = ParameterMatchers::Optionally.new(6, 7) - parameters_matcher = ParametersMatcher.new(expected_parameters = [4, 5, optionals]) - assert !parameters_matcher.match?(actual_parameters = [4, 5, 6, 7, 8]) - end - - def test_should_not_match_if_all_required_parameters_match_but_some_optional_parameters_do_not_match - optionals = ParameterMatchers::Optionally.new(6, 7) - parameters_matcher = ParametersMatcher.new(expected_parameters = [4, 5, optionals]) - assert !parameters_matcher.match?(actual_parameters = [4, 5, 6, 0]) - end - - def test_should_not_match_if_some_required_parameters_do_not_match_although_all_optional_parameters_do_match - optionals = ParameterMatchers::Optionally.new(6, 7) - parameters_matcher = ParametersMatcher.new(expected_parameters = [4, 5, optionals]) - assert !parameters_matcher.match?(actual_parameters = [4, 0, 6]) - end - - def test_should_not_match_if_all_required_parameters_match_but_no_optional_parameters_match - optionals = ParameterMatchers::Optionally.new(6, 7) - parameters_matcher = ParametersMatcher.new(expected_parameters = [4, 5, optionals]) - assert !parameters_matcher.match?(actual_parameters = [4, 5, 0, 0]) - end - - def test_should_match_if_actual_parameters_satisfy_matching_block - parameters_matcher = ParametersMatcher.new { |x, y| x + y == 3 } - assert parameters_matcher.match?(actual_parameters = [1, 2]) - end - - def test_should_not_match_if_actual_parameters_do_not_satisfy_matching_block - parameters_matcher = ParametersMatcher.new { |x, y| x + y == 3 } - assert !parameters_matcher.match?(actual_parameters = [2, 3]) - end - - def test_should_remove_outer_array_braces - params = [1, 2, [3, 4]] - parameters_matcher = ParametersMatcher.new(params) - assert_equal '(1, 2, [3, 4])', parameters_matcher.mocha_inspect - end - - def test_should_display_numeric_arguments_as_is - params = [1, 2, 3] - parameters_matcher = ParametersMatcher.new(params) - assert_equal '(1, 2, 3)', parameters_matcher.mocha_inspect - end - - def test_should_remove_curly_braces_if_hash_is_only_argument - params = [{:a => 1, :z => 2}] - parameters_matcher = ParametersMatcher.new(params) - assert_nil parameters_matcher.mocha_inspect.index('{') - assert_nil parameters_matcher.mocha_inspect.index('}') - end - - def test_should_not_remove_curly_braces_if_hash_is_not_the_only_argument - params = [1, {:a => 1}] - parameters_matcher = ParametersMatcher.new(params) - assert_equal '(1, {:a => 1})', parameters_matcher.mocha_inspect - end - - def test_should_indicate_that_matcher_will_match_any_actual_parameters - parameters_matcher = ParametersMatcher.new - assert_equal '(any_parameters)', parameters_matcher.mocha_inspect - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/return_values_test.rb b/vendor/gems/mocha-0.5.6/test/unit/return_values_test.rb deleted file mode 100644 index 01ddfbcd5b..0000000000 --- a/vendor/gems/mocha-0.5.6/test/unit/return_values_test.rb +++ /dev/null @@ -1,63 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "test_helper") - -require 'mocha/return_values' - -class ReturnValuesTest < Test::Unit::TestCase - - include Mocha - - def test_should_return_nil - values = ReturnValues.new - assert_nil values.next - end - - def test_should_keep_returning_nil - values = ReturnValues.new - values.next - assert_nil values.next - assert_nil values.next - end - - def test_should_return_evaluated_single_return_value - values = ReturnValues.new(SingleReturnValue.new('value')) - assert_equal 'value', values.next - end - - def test_should_keep_returning_evaluated_single_return_value - values = ReturnValues.new(SingleReturnValue.new('value')) - values.next - assert_equal 'value', values.next - assert_equal 'value', values.next - end - - def test_should_return_consecutive_evaluated_single_return_values - values = ReturnValues.new(SingleReturnValue.new('value_1'), SingleReturnValue.new('value_2')) - assert_equal 'value_1', values.next - assert_equal 'value_2', values.next - end - - def test_should_keep_returning_last_of_consecutive_evaluated_single_return_values - values = ReturnValues.new(SingleReturnValue.new('value_1'), SingleReturnValue.new('value_2')) - values.next - values.next - assert_equal 'value_2', values.next - assert_equal 'value_2', values.next - end - - def test_should_build_single_return_values_for_each_values - values = ReturnValues.build('value_1', 'value_2', 'value_3').values - assert_equal 'value_1', values[0].evaluate - assert_equal 'value_2', values[1].evaluate - assert_equal 'value_3', values[2].evaluate - end - - def test_should_combine_two_sets_of_return_values - values_1 = ReturnValues.build('value_1') - values_2 = ReturnValues.build('value_2a', 'value_2b') - values = (values_1 + values_2).values - assert_equal 'value_1', values[0].evaluate - assert_equal 'value_2a', values[1].evaluate - assert_equal 'value_2b', values[2].evaluate - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/sequence_test.rb b/vendor/gems/mocha-0.5.6/test/unit/sequence_test.rb deleted file mode 100644 index 544b3fe9c1..0000000000 --- a/vendor/gems/mocha-0.5.6/test/unit/sequence_test.rb +++ /dev/null @@ -1,104 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "test_helper") -require 'mocha/sequence' -require 'mocha/expectation' - -class SequenceTest < Test::Unit::TestCase - - include Mocha - - class FakeExpectation - - attr_reader :ordering_constraints - - def initialize(satisfied = false) - @satisfied = satisfied - @ordering_constraints = [] - end - - def add_ordering_constraint(ordering_constraint) - @ordering_constraints << ordering_constraint - end - - def satisfied? - @satisfied - end - - end - - def test_should_be_satisfied_if_no_expectations_added - sequence = Sequence.new('name') - assert sequence.satisfied_to_index?(0) - end - - def test_should_be_satisfied_if_one_unsatisfied_expectations_added_but_it_is_not_included_by_index - sequence = Sequence.new('name') - expectation = FakeExpectation.new(satisfied = false) - sequence.constrain_as_next_in_sequence(expectation) - assert sequence.satisfied_to_index?(0) - end - - def test_should_not_be_satisfied_if_one_unsatisfied_expectations_added_and_it_is_included_by_index - sequence = Sequence.new('name') - expectation = FakeExpectation.new(satisfied = false) - sequence.constrain_as_next_in_sequence(expectation) - assert !sequence.satisfied_to_index?(1) - end - - def test_should_be_satisfied_if_one_satisfied_expectations_added_and_it_is_included_by_index - sequence = Sequence.new('name') - expectation = FakeExpectation.new(satisfied = true) - sequence.constrain_as_next_in_sequence(expectation) - assert sequence.satisfied_to_index?(1) - end - - def test_should_not_be_satisfied_if_one_satisfied_and_one_unsatisfied_expectation_added_and_both_are_included_by_index - sequence = Sequence.new('name') - expectation_one = FakeExpectation.new(satisfied = true) - expectation_two = FakeExpectation.new(satisfied = false) - sequence.constrain_as_next_in_sequence(expectation_one) - sequence.constrain_as_next_in_sequence(expectation_two) - assert !sequence.satisfied_to_index?(2) - end - - def test_should_be_satisfied_if_two_satisfied_expectations_added_and_both_are_included_by_index - sequence = Sequence.new('name') - expectation_one = FakeExpectation.new(satisfied = true) - expectation_two = FakeExpectation.new(satisfied = true) - sequence.constrain_as_next_in_sequence(expectation_one) - sequence.constrain_as_next_in_sequence(expectation_two) - assert sequence.satisfied_to_index?(2) - end - - def test_should_add_ordering_constraint_to_expectation - sequence = Sequence.new('name') - expectation = FakeExpectation.new - sequence.constrain_as_next_in_sequence(expectation) - assert_equal 1, expectation.ordering_constraints.length - end - - def test_should_not_allow_invocation_of_second_method_when_first_n_sequence_has_not_been_invoked - sequence = Sequence.new('name') - expectation_one = FakeExpectation.new(satisfied = false) - expectation_two = FakeExpectation.new(satisfied = false) - sequence.constrain_as_next_in_sequence(expectation_one) - sequence.constrain_as_next_in_sequence(expectation_two) - assert !expectation_two.ordering_constraints[0].allows_invocation_now? - end - - def test_should_allow_invocation_of_second_method_when_first_in_sequence_has_been_invoked - sequence = Sequence.new('name') - expectation_one = FakeExpectation.new(satisfied = true) - expectation_two = FakeExpectation.new(satisfied = false) - sequence.constrain_as_next_in_sequence(expectation_one) - sequence.constrain_as_next_in_sequence(expectation_two) - assert expectation_two.ordering_constraints[0].allows_invocation_now? - end - - def test_should_describe_ordering_constraint_as_being_part_of_named_sequence - sequence = Sequence.new('wibble') - expectation = FakeExpectation.new - sequence.constrain_as_next_in_sequence(expectation) - assert_equal "in sequence 'wibble'", expectation.ordering_constraints[0].mocha_inspect - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/setup_and_teardown_test.rb b/vendor/gems/mocha-0.5.6/test/unit/setup_and_teardown_test.rb deleted file mode 100644 index 83247888e1..0000000000 --- a/vendor/gems/mocha-0.5.6/test/unit/setup_and_teardown_test.rb +++ /dev/null @@ -1,76 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "test_helper") -require 'mocha/mock' - -require 'mocha/setup_and_teardown' - -class SetupAndTeardownTest < Test::Unit::TestCase - - include Mocha - - def test_should_instantiate_new_stubba - test_case = stubbed_test_case_class.new - test_case.setup_stubs - - assert $stubba - assert $stubba.is_a?(Mocha::Central) - end - - def test_should_verify_all_expectations - test_case = stubbed_test_case_class.new - stubba = Mock.new - stubba.expects(:verify_all) - $stubba = stubba - - test_case.verify_stubs - - stubba.verify - end - - def test_should_yield_to_block_for_each_assertion - test_case = stubbed_test_case_class.new - $stubba = Mock.new - $stubba.stubs(:verify_all).yields - yielded = false - - test_case.verify_stubs { yielded = true } - - assert_equal true, yielded - end - - def test_should_unstub_all_stubbed_methods - test_case = stubbed_test_case_class.new - stubba = Mock.new - stubba.expects(:unstub_all) - $stubba = stubba - - test_case.teardown_stubs - - stubba.verify - end - - def test_should_set_stubba_to_nil - test_case = stubbed_test_case_class.new - $stubba = Mock.new - $stubba.stubs(:unstub_all) - - test_case.teardown_stubs - - assert_nil $stubba - end - - def test_should_not_raise_exception_if_no_stubba_central_available - test_case = stubbed_test_case_class.new - $stubba = nil - assert_nothing_raised { test_case.teardown_stubs } - end - - private - - def stubbed_test_case_class - Class.new do - include Mocha::SetupAndTeardown - end - end - -end - diff --git a/vendor/gems/mocha-0.5.6/test/unit/single_return_value_test.rb b/vendor/gems/mocha-0.5.6/test/unit/single_return_value_test.rb deleted file mode 100644 index 9d3d799b0d..0000000000 --- a/vendor/gems/mocha-0.5.6/test/unit/single_return_value_test.rb +++ /dev/null @@ -1,33 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "test_helper") - -require 'mocha/single_return_value' -require 'deprecation_disabler' - -class SingleReturnValueTest < Test::Unit::TestCase - - include Mocha - include DeprecationDisabler - - def test_should_return_value - value = SingleReturnValue.new('value') - assert_equal 'value', value.evaluate - end - - def test_should_return_result_of_calling_proc - proc = lambda { 'value' } - value = SingleReturnValue.new(proc) - result = nil - disable_deprecations { result = value.evaluate } - assert_equal 'value', result - end - - def test_should_indicate_deprecated_use_of_expectation_returns_method - proc = lambda {} - value = SingleReturnValue.new(proc) - Deprecation.messages = [] - disable_deprecations { value.evaluate } - expected_message = "use of Expectation#returns with instance of Proc - see Expectation#returns RDoc for alternatives" - assert_equal [expected_message], Deprecation.messages - end - -end diff --git a/vendor/gems/mocha-0.5.6/test/unit/single_yield_test.rb b/vendor/gems/mocha-0.5.6/test/unit/single_yield_test.rb deleted file mode 100644 index 12bd0a2fd7..0000000000 --- a/vendor/gems/mocha-0.5.6/test/unit/single_yield_test.rb +++ /dev/null @@ -1,18 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "test_helper") - -require 'mocha/single_yield' - -class SingleYieldTest < Test::Unit::TestCase - - include Mocha - - def test_should_provide_parameters_for_single_yield_in_single_invocation - parameter_group = SingleYield.new(1, 2, 3) - parameter_groups = [] - parameter_group.each do |parameters| - parameter_groups << parameters - end - assert_equal [[1, 2, 3]], parameter_groups - end - -end diff --git a/vendor/gems/mocha-0.5.6/test/unit/string_inspect_test.rb b/vendor/gems/mocha-0.5.6/test/unit/string_inspect_test.rb deleted file mode 100644 index 43b9c4e32c..0000000000 --- a/vendor/gems/mocha-0.5.6/test/unit/string_inspect_test.rb +++ /dev/null @@ -1,11 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "test_helper") -require 'mocha/inspect' - -class StringInspectTest < Test::Unit::TestCase - - def test_should_replace_escaped_quotes_with_single_quote - string = "my_string" - assert_equal "'my_string'", string.mocha_inspect - end - -end diff --git a/vendor/gems/mocha-0.5.6/test/unit/stub_test.rb b/vendor/gems/mocha-0.5.6/test/unit/stub_test.rb deleted file mode 100644 index a225963b97..0000000000 --- a/vendor/gems/mocha-0.5.6/test/unit/stub_test.rb +++ /dev/null @@ -1,24 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "test_helper") -require 'mocha/stub' - -class StubTest < Test::Unit::TestCase - - include Mocha - - def test_should_always_verify_successfully - stub = Stub.new(nil, :expected_method) - assert stub.verify - stub.invoke - assert stub.verify - end - - def test_should_match_successfully_for_any_number_of_invocations - stub = Stub.new(nil, :expected_method) - assert stub.match?(:expected_method) - stub.invoke - assert stub.match?(:expected_method) - stub.invoke - assert stub.match?(:expected_method) - end - -end \ No newline at end of file diff --git a/vendor/gems/mocha-0.5.6/test/unit/yield_parameters_test.rb b/vendor/gems/mocha-0.5.6/test/unit/yield_parameters_test.rb deleted file mode 100644 index 4e93f1336d..0000000000 --- a/vendor/gems/mocha-0.5.6/test/unit/yield_parameters_test.rb +++ /dev/null @@ -1,93 +0,0 @@ -require File.join(File.dirname(__FILE__), "..", "test_helper") - -require 'mocha/yield_parameters' -require 'mocha/no_yields' -require 'mocha/single_yield' -require 'mocha/multiple_yields' - -class YieldParametersTest < Test::Unit::TestCase - - include Mocha - - def test_should_return_null_yield_parameter_group_by_default - yield_parameters = YieldParameters.new - assert yield_parameters.next_invocation.is_a?(NoYields) - end - - def test_should_return_single_yield_parameter_group - yield_parameters = YieldParameters.new - yield_parameters.add(1, 2, 3) - parameter_group = yield_parameters.next_invocation - assert parameter_group.is_a?(SingleYield) - assert_equal [1, 2, 3], parameter_group.parameters - end - - def test_should_keep_returning_single_yield_parameter_group - yield_parameters = YieldParameters.new - yield_parameters.add(1, 2, 3) - yield_parameters.next_invocation - parameter_group = yield_parameters.next_invocation - assert parameter_group.is_a?(SingleYield) - assert_equal [1, 2, 3], parameter_group.parameters - parameter_group = yield_parameters.next_invocation - assert parameter_group.is_a?(SingleYield) - assert_equal [1, 2, 3], parameter_group.parameters - end - - def test_should_return_consecutive_single_yield_parameter_groups - yield_parameters = YieldParameters.new - yield_parameters.add(1, 2, 3) - yield_parameters.add(4, 5) - parameter_group = yield_parameters.next_invocation - assert parameter_group.is_a?(SingleYield) - assert_equal [1, 2, 3], parameter_group.parameters - parameter_group = yield_parameters.next_invocation - assert parameter_group.is_a?(SingleYield) - assert_equal [4, 5], parameter_group.parameters - end - - def test_should_return_multiple_yield_parameter_group - yield_parameters = YieldParameters.new - yield_parameters.multiple_add([1, 2, 3], [4, 5]) - parameter_group = yield_parameters.next_invocation - assert parameter_group.is_a?(MultipleYields) - assert_equal [[1, 2, 3], [4, 5]], parameter_group.parameter_groups - end - - def test_should_keep_returning_multiple_yield_parameter_group - yield_parameters = YieldParameters.new - yield_parameters.multiple_add([1, 2, 3], [4, 5]) - yield_parameters.next_invocation - parameter_group = yield_parameters.next_invocation - assert parameter_group.is_a?(MultipleYields) - assert_equal [[1, 2, 3], [4, 5]], parameter_group.parameter_groups - parameter_group = yield_parameters.next_invocation - assert parameter_group.is_a?(MultipleYields) - assert_equal [[1, 2, 3], [4, 5]], parameter_group.parameter_groups - end - - def test_should_return_consecutive_multiple_yield_parameter_groups - yield_parameters = YieldParameters.new - yield_parameters.multiple_add([1, 2, 3], [4, 5]) - yield_parameters.multiple_add([6, 7], [8, 9, 0]) - parameter_group = yield_parameters.next_invocation - assert parameter_group.is_a?(MultipleYields) - assert_equal [[1, 2, 3], [4, 5]], parameter_group.parameter_groups - parameter_group = yield_parameters.next_invocation - assert parameter_group.is_a?(MultipleYields) - assert_equal [[6, 7], [8, 9, 0]], parameter_group.parameter_groups - end - - def test_should_return_consecutive_single_and_multiple_yield_parameter_groups - yield_parameters = YieldParameters.new - yield_parameters.add(1, 2, 3) - yield_parameters.multiple_add([4, 5, 6], [7, 8]) - parameter_group = yield_parameters.next_invocation - assert parameter_group.is_a?(SingleYield) - assert_equal [1, 2, 3], parameter_group.parameters - parameter_group = yield_parameters.next_invocation - assert parameter_group.is_a?(MultipleYields) - assert_equal [[4, 5, 6], [7, 8]], parameter_group.parameter_groups - end - -end \ No newline at end of file diff --git a/vendor/gems/rspec/CHANGES b/vendor/gems/rspec/CHANGES deleted file mode 100644 index 887c9d8570..0000000000 --- a/vendor/gems/rspec/CHANGES +++ /dev/null @@ -1,1045 +0,0 @@ -== Version 1.1.3 - -Maintenance release. - -* Tightened up exceptions list in autotest/rails_spec. Closes #264. -* Applied patch from Ryan Davis for ZenTest-3.9.0 compatibility -* Applied patch from Kero to add step_upcoming to story listeners. Closes #253. -* Fixed bug where the wrong named error was not always caught by "should raise_error" -* Applied patch from Luis Lavena: No coloured output on Windows due missing RUBYOPT. Closes #244. -* Applied patch from Craig Demyanovich to add support for "should_not render_template" to rspec_on_rails. Closes #241. -* Added --pattern (-p for short) option to control what files get loaded. Defaults to '**/*_spec.rb' -* Exit with non-0 exit code if examples *or tests* (in test/unit interop mode) fail. Closes #203. -* Moved at_exit hook to a method in Spec::Runner which only runs if specs get loaded. Closes #242. -* Applied patch from kakutani ensuring that base_view_path gets cleared after each view example. Closes #235. -* More tweaks to regexp step names -* Fixed focused specs in nested ExampleGroups. Closes #225. - -== Version 1.1.2 - -Minor bug fixes/enhancements. - -* RSpec's Autotest subclasses compatible with ZenTest-3.8.0 (thanks to Ryan Davis for making it easier on Autotest subs). -* Applied patch from idl to add spec/lib to rake stats. Closes #226. -* calling setup_fixtures and teardown_fixtures for Rails >= r8570. Closes #219. -* Applied patch from Josh Knowles using ActiveSupport's Inflector (when available) to make 'should have' read a bit better. Closes #197. -* Fixed regression in 1.1 that caused failing examples to fail to generate their own names. Closes #209. -* Applied doc patch from Jens Krämer for capturing content_for -* Applied patch from Alexander Lang to clean up story steps after each story. Closes #198. -* Applied patch from Josh Knowles to support 'string_or_response.should have_text(...)'. Closes #193. -* Applied patch from Ian Dees to quiet the Story Runner backtrace. Closes #183. -* Complete support for defining steps with regexp 'names'. - -== Version 1.1.1 - -Bug fix release. - -* Fix regression in 1.1.0 that caused transactions to not get rolled back between examples. -* Applied patch from Bob Cotton to reintroduce ExampleGroup.description_options. Closes LH[#186] - -== Version 1.1.0 - -The "tell me a story and go nest yourself" release. - -* Applied patch from Mike Vincent to handle generators rails > 2.0.1. Closes LH[#181] -* Formatter.pending signature changed so it gets passed an ExampleGroup instance instead of the name ( LH[#180]) -* Fixed LH[#180] Spec::Rails::Example::ModelExampleGroup and friends show up in rspec/rails output -* Spec::Rails no longer loads ActiveRecord extensions if it's disabled in config/boot.rb -* Applied LH[#178] small annoyances running specs with warnings enabled (Patch from Mikko Lehtonen) -* Tighter integration with Rails fixtures. Take advantage of fixture caching to get performance improvements (Thanks to Pat Maddox, Nick Kallen, Jonathan Barnes, and Curtis) - -== Version 1.1.0-RC1 - -Textmate Bundle users - this release adds a new RSpec bundle that highlights describe, it, before and after and -provides navigation to descriptions and examples (rather than classes and methods). When you first install this, -it is going to try to hijack all of your .rb files. All you need to do is open a .rb file that does not end with -'spec.rb' and change the bundle selection from RSpec to Ruby. TextMate will do the right thing from then on. - -Shortcuts for tab-activated snippets all follow the TextMate convention of 2 or 3 letters of the first word, followed by the first letter of each subsequent word. So "should have_at_least" would be triggered by shhal. - -We reduced the scope for running spec directories, files, a single file or individual spec in TextMate to source.ruby.rspec. This allowed us to restore the standard Ruby shortcuts: - -CMD-R runs all the specs in one file -CMD-SHIFT-R runs an individual spec -CMD-OPT-R runs any files or directories selected in the TextMate drawer - -rspec_on_rails users - don't forget to run script/generate rspec - -* Added shared_examples_for method, which you can (should) use instead of describe Foo, :shared => true -* Applied LH[#168] Fix describe Object, "description contains a # in it" (Patch from Martin Emde) -* Applied LH[#15] Reverse loading of ActionView::Base helper modules (Patch from Mark Van Holstyn) -* Applied LH[#149] Update contribute page to point towards lighthouse (Patch from Josh Knowles) -* Applied LH[#142] verify_rcov fails with latest rcov (Patch from Kyle Hargraves) -* Applied LH[#10] Allow stubs to yield and return values (Patch from Pat Maddox) -* Fixed LH[#139] version.rb in trunk missing svn last changed number -* Applied LH[#14] Adding support for by_at_least/by_at_most in Change matcher (Patch from Saimon Moore) -* Applied LH[#12] Fix for TM when switching to alternate file (Patch from Trevor Squires) -* Applied LH[#133] ExampleMatcher should match against before(:all) (Patch from Bob Cotton) -* Applied LH[#134] Only load spec inside spec_helper.rb (Patch from Mark Van Holstyn) -* RSpec now bails immediately if there are examples with identical names. -* Applied LH[#132] Plain Text stories should support Given and Given: (Patch from Jarkko Laine) -* Applied patch from Pat Maddox: Story Mediator - the glue that binds the plain text story parser with the rest of the system -* Applied LH[#16] Have SimpleMatchers expose their description for specdocs (Patch from Bryan Helmkamp) -* Stories now support --colour -* Changed the DSL modules to Example (i.e. Spec::Example instead of Spec::DSL) -* Applied [#15608] Story problem if parenthesis used in Given, When, Then or And (Patch from Sinclair Bain) -* Applied [#15659] GivenScenario fails when it is a RailsStory (Patch from Nathan Sutton) -* Fixed [#15639] rcov exclusion configuration. (Spec::Rails projects can configure rcov with spec/rcov.opts) -* The rdoc formatter (--format rdoc) is gone. It was buggy and noone was using it. -* Changed Spec::DSL::Behaviour to Spec::DSL::ExampleGroup -* Changed Spec::DSL::SharedBehaviour to Spec::DSL::SharedExampleGroup -* Applied [#14023] Small optimization for heavily proxied objects. (Patch from Ian Leitch) -* Applied [#13943] ProfileFormatter (Top 10 slowest examples) (Patch from Ian Leitch) -* Fixed [#15232] heckle is not working correctly in trunk (as of r2801) -* Applied [#14399] Show pending reasons in HTML report (Patch from Bryan Helmkamp) -* Discovered fixed: [#10263] mock "leak" when setting an expectation in a block passed to mock#should_receive -* Fixed [#14671] Spec::DSL::ExampleRunner gives "NO NAME because of --dry-run" for every example for 'rake spec:doc' -* Fixed [#14543] rspec_scaffold broken with Rails 2.0 -* Removed Patch [#10577] Rails with Oracle breaks 0.9.2 - was no longer necessary since we moved describe to the Main object (instead of Object) -* Fixed [#14527] specs run twice on rails 1.2.4 and rspec/rspec_on_rails trunk -* Applied [#14043] Change output ordering to show pending before errors (Patch from Mike Mangino) -* Applied [#14095] Don't have ./script/generate rspec create previous_failures.txt (Patch from Bryan Helmkamp) -* Applied [#14254] Improved error handling for Object#should and Object#should_not (Patch from Antti Tarvainen) -* Applied [#14186] Remove dead code from message_expecation.rb (Patch from Antti Tarvainen) -* Applied [#14183] Tiny improvement on mock_spec.rb (Patch from Antti Tarvainen) -* Applied [#14208] Fix to Mock#method_missing raising NameErrors instead of MockExpectationErrors (Patch from Antti Tarvainen) -* Applied [#14255] Fixed examples in mock_spec.rb and shared_behaviour_spec.rb (Patch from Antti Tarvainen) -* Applied [#14362] partially mocking objects that define == can blow up (Patch from Pat Maddox) -* test_ methods with an arity of 0 defined in a describe block or Example object will be run as an Example, providing a seamless transition from Test::Unit -* Removed BehaviourRunner -* Fixed [#13969] Spec Failures on Trunk w/ Autotest -* Applied [#14156] False positives with should_not (Patch from Antti Tarvainen) -* Applied [#14170] route_for and params_from internal specs fixed (Patch from Antti Tarvainen) -* Fixed [#14166] Cannot build trunk -* Applied [#14142] Fix for bug #11602: Nested #have_tag specifications fails on the wrong line number (Patch from Antti Tarvainen) -* Removed warn_if_no_files argument and feature -* Steps (Given/When/Then) with no blocks are treated as pending -* Applied [#13913] Scenario should treat no code block as pending (Patch from Evan Light) -* Fixed [#13370] Weird mock expectation error (Patch from Mike Mangino) -* Applied [#13952] Fix for performance regression introduced in r2096 (Patch from Ian Leitch) -* Applied [#13881] Dynamically include Helpers that are included on ActionView::Base (Patch from Brandon Keepers) -* Applied [#13833] ActionView::Helpers::JavaScriptMacrosHelper removed after 1.2.3 (Patch from Yurii Rashkovskii) -* Applied [#13814] RSpec on Rails w/ fixture-scenarios (Patch from Shintaro Kakutani) -* Add ability to define Example subclass instead of using describe -* Applied Patch from James Edward Gray II to improve syntax highlighting in TextMate -* Fixed [#13579] NoMethodError not raised for missing helper methods -* Fixed [#13713] form helper method 'select' can not be called when calling custom helper methods from specs -* Example subclasses Test::Unit::TestCase -* Added stub_everything method to create a stub that will return itself for any message it doesn't understand -* Added stories directory with stories/all.rb and stories/helper.rb when you script/generate rspec -* Applied [#13554] Add "And" so you can say Given... And... When... Then... And... -* Applied [#11254] RSpec syntax coloring and function pop-up integration in TextMate (Patch from Wincent Colaiuta) -* Applied [#13143] ActionView::Helpers::RecordIdentificationHelper should be included if present (Patch from Jay Levitt) -* Applied [#13567] patch to allow stubs to yield consecutive values (Patch from Rupert Voelcker) -* Applied [#13559] reverse version of route_for (Patch from Rupert Voelcker) -* Added [#13532] /lib specs should get base EvalContext -* Applied [#13451] Add a null_object option to mock_model (Patch from James Deville) -* Applied [#11919] Making non-implemented specs easy in textmate (Patch from Scott Taylor) -* Applied [#13274] ThrowSymbol recognized a NameError triggered by Kernel#method_missing as a thrown Symbol -* Applied [#12722] the alternate file command does not work in rails views due to scope (Patch from Carl Porth) -* Behaviour is now a Module that is used by Example class methods and SharedBehaviour -* Added ExampleDefinition -* Added story runner framework based on rbehave [#12628] -* Applied [#13336] Helper directory incorrect for rake stats in statsetup task (Patch from Curtis Miller) -* Applied [#13339] Add the ability for spec_parser to parse describes with :behaviour_type set (Patch from Will Leinweber and Dav Yaginuma) -* Fixed [#13271] incorrect behaviour with expect_render and stub_render -* Applied [#13129] Fix failing specs in spec_distributed (Patch from Bob Cotton) -* Applied [#13118] Rinda support for Spec::Distributed (Patch from Bob Cotton) -* Removed BehaviourEval -* Removed Behaviour#inherit -* Moved implementation of install_dependencies to example_rails_app -* Renamed RSPEC_DEPS to VENDOR_DEPS -* Added Example#not_implemented? -* You can now stub!(:msg).with(specific args) -* describe("A", Hash, "with one element") will generate description "A Hash with one element" (Tip from Ola Bini) -* Applied [#13016] [DOC] Point out that view specs render, well, a view (Patch from Jay Levitt) -* Applied [#13078] Develop rspec with autotest (Patch from Scott Taylor) -* Fixed [#13065] Named routes throw a NoMethodError in Helper specs (Patches from James Deville and Mike Mangino) -* Added (back) the verbose attribute in Spec::Rake::SpecTask -* Changed documentation to point at the new http svn URL, which is more accessible. - -== Version 1.0.8 - -Another bugfix release - this time to resolve the version mismatch - -== Version 1.0.7 - -Quick bugfix release to ensure that you don't have to have the rspec gem installed -in order to use autotest with rspec_on_rails. - -* Fixed [#13015] autotest gives failure in 'spec_command' after upgrade 1.0.5 to 1.0.6 - -== Version 1.0.6 - -The "holy cow, batman, it's been a long time since we released and there are a ton of bug -fixes, patches and even new features" release. - -Warning: Spec::Rails users: In fixing 11508, we've removed the raise_controller_errors method. As long as you -follow the upgrade instructions and run 'script/generate rspec' you'll be fine, but if you skip this -step you need to manually go into spec_helper.rb and remove the call to that method (if present - it -might not be if you haven't upgraded in a while). - -Warning: Implementors of custom formatters. Formatters will now be sent an Example object instead of just a -String for #example_started, #example_passed and #example_failed. In certain scenarios -(Spec::Ui with Spec::Distributed), the formatter must ask the Example for its sequence number instead of -keeping track of a sequence number internal to the formatter. Most of you shouldn't need to upgrade -your formatters though - the Example#to_s method returns the example name/description, so you should be -able to use the passed Example instance as if it were a String. - -* Applied [#12986] Autotest Specs + Refactoring (Patch from Scott Tayler) -* Added a #close method to formatters, which allows them to gracefully close streams. -* Applied [#12935] Remove requirement that mocha must be installed as a gem when used as mocking framework. (Patch from Ryan Kinderman). -* Fixed [#12893] RSpec's Autotest should work with rspec's trunk -* Fixed [#12865] Partial mock error when object has an @options instance var -* Applied [#12701] Allow checking of content captured with content_for in view specs (Patch from Jens Kr�mer) -* Applied [#12817] Cannot include same shared behaviour when required with absolute paths (Patch from Ian Leitch) -* Applied [#12719] rspec_on_rails should not include pagination helper (Patch from Matthijs Langenberg) -* Fixed [#12714] helper spec not finding rails core helpers -* Applied [#12611] should_not redirect_to implementation (Patch from Yurii Rashkovskii) -* Applied [#12682] Not correctly aliasing original 'stub!' and 'should_receive' methods for ApplicationController (Patch from Matthijs Langenberg) -* Disabled controller.should_receive(:render) and controller.stub!(:render). Use expect_render or stub_render instead. -* Applied [#12484] Allow a Behaviour's Description to flow through to the Formatter (Patch from Bob Cotton) -* Fixed [#12448] The spec:plugins rake task from rspec_on_rails should ignore specs from the rspec_on_rails plugin -* Applied [#12300] rr integration (patch from Kyle Hargraves) -* Implemented [#12284] mock_with :rr (integration with RR mock framework: http://rubyforge.org/projects/pivotalrb/) -* Applied [#12237] (tiny) added full path to mate in switch_command (Patch from Carl Porth) -* Formatters will now be sent an Example object instead of just a String for certain methods -* All Spec::Rake::SpecTask attributes can now be procs, which allows for lazy evaluation. -* Changed the Spec::Ui interfaces slightly. See examples. -* Applied [#12174] mishandling of paths with spaces in spec_mate switch_command (Patch from Carl Porth) -* Implemented [#8315] File "Go to..." functionality -* Applied [#11917] Cleaner Spec::Ui error for failed Selenium connection (Patch from Ian Dees) -* Applied [#11888] rspec_on_rails spews out warnings when assert_select is used with an XML response (Patch from Ian Leitch) -* Applied [#12010] Nicer failure message formatting (Patch from Wincent Colaiuta) -* Applied [#12156] smooth open mate patch (Patch from Ienaga Eiji) -* Applied [#10577] Rails with Oracle breaks 0.9.2. (Patch from Sinclair Bain) -* Fixed [#12079] auto-generated example name incomplete: should have 1 error on ....] -* Applied [#12066] Docfix for mocks/mocks.page (Patch from Kyle Hargraves) -* Fixed [#11891] script/generate rspec_controller fails to create appropriate views (from templates) on edge rails -* Applied [#11921] Adds the correct controller_name from derived_controller_name() to the ViewExampleGroupController (Patch from Eloy Duran) -* Fixed [#11903] config.include with behaviour_type 'hash' does not work -* Examples without blocks and pending is now reported with a P instead of a * -* Pending blocks that now pass are rendered blue -* New behaviour for after: If an after block raises an error, the other ones will still run instead of bailing at the first. -* Made it possible to run spec from RSpec.tmbundle with --drb against a Rails spec_server. -* Applied [#11868] Add ability for pending to optionally hold a failing block and to fail when it passes (Patch from Bob Cotton) -* Fixed [#11843] watir_behaviour missing from spec_ui gem -* Added 'switch between source and spec file' command in Spec::Mate (based on code from Ruy Asan) -* Applied [#11509] Documentation - RSpec requires hpricot -* Applied [#11807] Daemonize spec_server and rake tasks to manage them. (patch from Kyosuke MOROHASHI) -* Added pending(message) method -* Fixed [#11777] should render_template doesn't check paths correctly -* Fixed [#11749] Use of 'rescue => e' does not catch all exceptions -* Fixed [#11793] should raise_error('with a message') does not work correctly -* Fixed [#11774] Mocks should respond to :kind_of? in the same way they respond to :is_a? -* Fixed [#11508] Exceptions are not raised for Controller Specs (removed experimental raise_controller_errors) -* Applied [#11615] Partial mock methods give ambiguous failures when given a method name as a String (Patch from Jay Phillips) -* Fixed [#11545] Rspec doesn't handle should_receive on ActiveRecord associations (Patch from Ian White) -* Fixed [#11514] configuration.use_transactional_fixtures is ALWAYS true, regardless of assignment -* Improved generated RESTful controller examples to cover both successful and unsuccessful POST and PUT -* Changed TextMate snippets for controllers to pass controller class names to #describe rather than controller_name. -* Changed TextMate snippets for mocks to use no_args() and any_args() instead of the deprecated Symbols. -* Applied [#11500] Documentation: no rails integration specs in 1.0 -* Renamed SpecMate's shortcuts for running all examples and focused examples to avoid conflicts (CMD-d and CMD-i) -* Added a TextMate snippet for custom matchers, lifted from Geoffrey Grosenbach's RSpec peepcode show. -* The translator translates mock constraints to the new matchers that were introduced in 1.0.4 -* Documented environment variables for Spec::Rake::SpecTask. Renamed SPECOPTS and RCOVOPTS to SPEC_OPTS and RCOV_OPTS. -* Fixed [#10534] Windows: undefined method 'controller_name' - -== Version 1.0.5 -Bug fixes. Autotest plugin tweaks. - -* Fixed [#11378] fix to 10814 broke drb (re-opened #10814) -* Fixed [#11223] Unable to access flash from rails helper specs -* Fixed [#11337] autotest runs specs redundantly -* Fixed [#11258] windows: autotest won't run -* Applied [#11253] Tweaks to autotest file mappings (Patch from Wincent Colaiuta) -* Applied [#11252] Should be able to re-load file containing shared behaviours without raising an exception (Patch from Wincent Colaiuta) -* Fixed [#11247] standalone autotest doesn't work because of unneeded autotest.rb -* Applied [#11221] Autotest support does not work w/o Rails Gem installed (Patch from Josh Knowles) - -== Version 1.0.4 -The getting ready for JRuby release. - -* Fixed [#11181] behaviour_type scoping of config.before(:each) is not working -* added mock argument constraint matchers (anything(), boolean(), an_instance_of(Type)) which work with rspec or mocha -* added mock argument constraint matchers (any_args(), no_args()) which only work with rspec -* deprecated rspec's symbol mock argument constraint matchers (:any_args, :no_args, :anything, :boolean, :numeric, :string) -* Added tarball of rspec_on_rails to the release build to support folks working behind a firewall that blocks svn access. -* Fixed [#11137] rspec incorrectly handles flash after resetting the session -* Fixed [#11143] Views code for ActionController::Base#render broke between 1.0.0 and 1.0.3 on Rails Edge r6731 -* Added raise_controller_errors for controller examples in Spec::Rails - -== Version 1.0.3 -Bug fixes. - -* Fixed [#11104] Website uses old specify notation -* Applied [#11101] StringHelpers.starts_with?(prefix) assumes a string parameter for _prefix_ -* Removed 'rescue nil' which was hiding errors in controller examples. -* Fixed [#11075] controller specs fail when using mocha without integrated_views -* Fixed problem with redirect_to failing incorrectly against edge rails. -* Fixed [#11082] RspecResourceGenerator should be RspecScaffoldGenerator -* Fixed [#10959] Focused Examples do not work for Behaviour defined with constant with modules - -== Version 1.0.2 -This is just to align the version numbers in rspec and rspec_on_rails. - -== Version 1.0.1 -This is a maintenance release with mostly cleaning up, and one minor enhancement - -Modules are automatically included when described directly. - -* Renamed Spec::Rails' rspec_resource generator to rspec_scaffold. -* Removed Spec::Rails' be_feed matcher since it's based on assert_select_feed which is not part of Rails (despite that docs for assert_select_encoded says it is). -* describe(SomeModule) will include that module in the examples. Like for Spec::Rails helpers, but now also in core. -* Header in HTML report will be yellow instead of red if there is one failed example -* Applied [#10951] Odd instance variable name in rspec_model template (patch from Kyle Hargraves) -* Improved integration with autotest (Patches from Ryan Davis and David Goodland) -* Some small fixes to make all specs run on JRuby. - -== Version 1.0.0 -The stake in the ground release. This represents a commitment to the API as it is. No significant -backwards compatibility changes in the API are expected after this release. - -* Fixed [#10923] have_text matcher does not support should_not -* Fixed [#10673] should > and should >= broken -* Applied [#10921] Allow verify_rcov to accept greater than threshold coverage %'s via configuration -* Applied [#10920] Added support for not implemented examples (Patch from Chad Humphries and Ken Barker) -* Patch to allow not implemented examples. This works by not providing a block to the example. (Patch from Chad Humphries, Ken Barker) -* Yanked support for Rails 1.1.6 in Spec::Rails -* RSpec.tmbundle uses CMD-SHIFT-R to run focused examples now. -* Spec::Rails now bundles a spec:rcov task by default (suggestion from Kurt Schrader) -* Fixed [#10814] Runner loads shared code, test cases require them again -* Fixed [#10753] Global before and after -* Fixed [#10774] Allow before and after to be specified in config II -* Refactored Spec::Ui examples to use new global before and after blocks. -* Added instructions about how to get Selenium working with Spec::Ui (spec_ui/examples/selenium/README.txt) -* Fixed [#10805] selenium.rb missing from gem? -* Added rdocs explaining how to deal with errors in Rails' controller actions -* Applied [#10770] Finer grained includes. -* Fixed [#10747] Helper methods defined in shared specs are not visible when shared spec is used -* Fixed [#10748] Shared descriptions in separate files causes 'already exists' error -* Applied [#10698] Running with --drb executes specs twice (patch from Ruy Asan) -* Fixed [#10871] 0.9.4 - Focussed spec runner fails to run specs in descriptions with type and string when there is no leading space in the string - -== Version 0.9.4 -This release introduces massive improvements to Spec::Ui - the user interface functional testing -extension to RSpec. There are also some minor bug fixes to the RSpec core. - -* Massive improvements to Spec::Ui. Complete support for all Watir's ie.xxx(how, what) methods. Inline screenshots and HTML. -* Reactivated --timeout, which had mysteriously been deactivated in a recent release. -* Fixed [#10669] Kernel#describe override does not cover Kernel#context -* Applied [#10636] Added spec for OptionParser in Runner (Patch from Scott Taylor) -* Added [#10516] should_include should be able to accept multiple items -* Applied [#10631] redirect_to matcher doesn't respect request.host (Patch from Tim Lucas) -* Each formatter now flushes their own IO. This is to avoid buffering of output. -* Fixed [#10670] IVarProxy#delete raises exception when instance variable does not exist - -== Version 0.9.3 -This is a bugfix release. - -* Fixed [#10594] Failing Custom Matcher show NAME NOT GENERATED description -* describe(SomeType, "#message") will not add a space: "SomeType#message" (likewise for '.') -* describe(SomeType, "message") will have a decription with a space: "SomeType message" -* Applied [#10566] prepend_before and prepend_after callbacks -* Applied [#10567] Call setup and teardown using before and after callbacks - -== Version 0.9.2 -This is a quick maintenance release. - -* Added some website love -* Fixed [#10542] reverse predicate matcher syntax -* Added a spec:translate Rake task to make 0.9 translation easier with Spec:Rails -* Better translation of should_redirect_to -* Fixed --colour support for Windows. This is a regression that was introduced in 0.9.1 -* Applied [#10460] Make SpecRunner easier to instantiate without using commandline args - -== Version 0.9.1 - -This release introduces #describe and #it (aliased as #context and #specify for -backwards compatibility). This allows you to express specs like this: - - describe SomeClass do # Creates a Behaviour - it "should do something" do # Creates an Example - end - end - -The command line features four new options that give you more control over what specs -are being run and in what order. This can be used to verify that your specs are -independent (by running in opposite order with --reverse). It can also be used to cut -down feedback time by running the most recently modified specs first (--loadby mtime --reverse). - -Further, --example replaces the old --spec option, and it can now take a file name of -spec names as an alternative to just a spec name. The --format failing_examples:file.txt -option allows you to output an --example compatible file, which makes it possible to only -rerun the specs that failed in the last run. Spec::Rails uses all of these four options -by default to optimise your RSpec experience. - -There is now a simple configuration model. For Spec::Rails, you do something like this: - - Spec::Runner.configure do |config| - config.use_transactional_fixtures = true - config.use_instantiated_fixtures = false - config.fixture_path = RAILS_ROOT + '/spec/fixtures' - end - -You can now use mocha or flexmock with RSpec if you prefer either to -RSpec's own mock framework. Just put this: - - Spec::Runner.configure do |config| - config.mock_with :mocha - end - -or this: - - Spec::Runner.configure do |config| - config.mock_with :flexmock - end - -in a file that is loaded before your specs. You can also -configure included modules and predicate_matchers: - - Spec::Runner.configure do |config| - config.include SomeModule - config.predicate_matchers[:does_something?] = :do_something - end - -See Spec::DSL::Behaviour for more on predicate_matchers - -* Sugar FREE! -* Added [10434 ] Please Make -s synonymous with -e for autotest compat. This is temporary until autotest uses -e instead of -s. -* Fixed [#10133] custom predicate matchers -* Applied [#10473] Add should exist (new matcher) - Patch from Bret Pettichord -* Added another formatter: failing_behaviours. Writes the names of the failing behaviours for use with --example. -* Applied [#10315] Patch to fix pre_commit bug 10313 - pre_commit_rails: doesn't always build correctly (Patch from Antii Tarvainen) -* Applied [#10245] Patch to HTML escape the behavior name when using HTML Formatter (Patch from Josh Knowles) -* Applied [#10410] redirect_to does not behave consistently with regards to query string parameter ordering (Patch from Nicholas Evans) -* Applied [#9605] Patch for ER 9472, shared behaviour (Patch by Bob Cotton) -* The '--format rdoc' option no longer causes a dry-run by default. --dry-run must be used explicitly. -* It's possible to specify the output file in the --format option (See explanation in --help) -* Several --format options may be specified to output several formats in one run. -* The --out option is gone. Use --format html:path/to/my.html instead (or similar). -* Spec::Runner::Formatter::BaseTextFormatter#initialize only takes one argument - an IO. dry_run and color are setters. -* Made Spec::Ui *much* easier to install. It will be released separately. Check out trunk/spec_ui/examples -* HTML reports now include a syntax highlighted snippet of the source code where the spec failed (needs the syntax gem) -* Added [#10262] Better Helper testing of Erb evaluation block helpers -* Added [#9735] support flexmock (thanks to Jim Weirich for his modifications to flexmock to support this) -* Spec::Rails controller specs will no longer let mock exception ripple through to the response. -* Fixed [#9260] IvarProxy does not act like a hash. -* Applied [#9458] The rspec_scaffold generator does not take into account class nesting (Patch from Steve Tendon) -* Applied [#9132] Rakefile spec:doc can fail without preparing database (Patch from Steve Ross) -* Applied [#9678] Custom runner command line switch, and multi-threaded runner (Patch from Bob Cotton) -* Applied [#9926] Rakefile - RSPEC_DEPS constant as an Array of Hashes instead of an Array of Arrays (Patch from Scott Taylor) -* Applied [#9925] Changed ".rhtml" to "template" in REST spec generator (Patch from Scott Taylor) -* Applied [#9852] Patch for RSpec's Website using Webgen 0.4.2 (Patch from Scott Taylor) -* Fixed [#6523] Run rspec on rails without a db -* Fixed [#9295] rake spec should run anything in the spec directory (not just rspec's standard dirs) -* Added [#9786] infer controller and helper names from the described type -* Fixed [#7795] form_tag renders action='http://github.com/view_spec' in view specs -* Fixed [#9767] rspec_on_rails should not define rescue_action on controllers -* Fixed [#9421] --line doesn't work with behaviours that use class names -* Fixed [#9760] rspec generators incompatible with changes to edge rails -* Added [#9786] infer controller and helper names from the described type -* Applied a simplified version of [#9282] Change to allow running specs from textmate with rspec installed as a rails plugin (and no rspec gem installed) -* Applied [#9700] Make Spec::DSL::Example#name public / Add a --timeout switch. A great way to prevent specs from getting slow. -* In Rails, script/generate rspec will generate a spec.opts file that optimises faster/more efficient running of specs. -* Added [#9522] support using rspec's expectations with test/unit -* Moved rspec_on_rails up to the project root, simplifying the download url -* Fixed [#8103] RSpec not installing spec script correctly. -* The --spec option is replaced by the --example option. -* The --loadby option no longer supports a file argument. Use --example file_name instead. -* The --example option can now take a file name as an argument. The file should contain example names. -* Internal classes are named Behaviour/Example (rather than Context/Specification). -* You can now use mocha by saying config.mock_with :mocha in a spec_helper -* before_context_eval is replaced by before_eval. -* Applied [#9509] allow spaced options in spec.opts -* Applied [#9510] Added File for Ruby 1.8.6 -* Applied [#9511] Clarification to README file in spec/ -* Moved all of the Spec::Rails specs down to the plugins directory - now you can run the specs after you install. -* Updated RSpec.tmbundle to the 0.9 syntax and replaced context/specify with describe/it. -* Applied [#9232] ActionController::Base#render is sometimes protected (patch from Dan Manges) -* Added --reverse option, allowing contexts/specs to be run in reverse order. -* Added --loadby option, allowing better control over load order for spec files. mtime and file.txt supported. -* Implemented [#8696] --order option (see --reverse and --loadby) -* Added describe/it as aliases for context/specify - suggestion from Dan North. -* Applied [#7637] [PATCH] add skip-migration option to rspec_scaffold generator -* Added [#9167] string.should have_tag -* Changed script/rails_spec_server to script/spec_server and added script/spec (w/ path to vendor/plugins/rspec) -* Fixed [#8897] Error when mixing controller spec with/without integrated views and using template system other than rhtml -* Updated sample app specs to 0.9 syntax -* Updated generated specs to 0.9 syntax -* Applied [#8994] trunk: generated names for be_ specs (Multiple patches from Yurii Rashkovskii) -* Applied [#9983]: Allow before and after to be called in BehaviourEval. This is useful for shared examples. - -== Version 0.8.2 - -Replaced assert_select fork with an assert_select wrapper for have_tag. This means that "should have_rjs" no longer supports :hide or :effect, but you can still use should_have_rjs for those. - -== Version 0.8.1 - -Quick "in house" bug-fix - -== Version 0.8.0 - -This release introduces a new approach to handling expectations using Expression Matchers. - -See Upgrade[http://rspec.rubyforge.org/upgrade.html], Spec::Expectations, Spec::Matchers and RELEASE-PLAN for more info. - -This release also improves the spec command line by adding DRb support and making it possible to -store command line options in a file. This means a more flexible RSpec experience with Rails, -Rake and editor plugins like TextMate. - -It also sports myriad new features, bug fixes, patches and general goodness: - -* Fixed [#8928] rspec_on_rails 0.8.0-RC1 controller tests make double call to setup_with_fixtures -* Fixed [#8925] Documentation bug in 0.8.0RC1 rspec website -* Applied [#8132] [PATCH] RSpec breaks "rake db:sessions:create" in a rails project that has the rspec_on_rails plugin (Patch from Erik Kastner) -* Fixed [#8789] --line and --spec not working when the context has parenhesis in the name -* Added [#8783] auto generate spec names from last expectation -* --heckle now fails if the heckled class or module is not found. -* Fixed [#8771] Spec::Mocks::BaseExpectation#with converts hash params to array of arrays with #collect -* Fixed [#8750] should[_not]_include backwards compatibility between 0.8.0-RC1 and 0.7.5.1 broken -* Fixed [#8646] Context Runner does not report on Non standard exceptions and return a 0 return code -* RSpec on Rails' spec_helper.rb will only force RAILS_ENV to test if it was not specified on the command line. -* Fixed [#5485] proc#should_raise and proc#should_not_raise output -* Added [#8484] should_receive with blocks -* Applied [#8218] heckle_runner.rb doesn't work with heckle >= 1.2.0 (Patch from Michal Kwiatkowski) -* Fixed [#8240] Cryptic error message when no controller_name -* Applied [#7461] [PATCH] Contexts don't call Module::included when they include a module -* Removed unintended block of test/unit assertions in rspec_on_rails - they should all, in theory, now be accessible -* Added mock_model method to RSpec on Rails, which stubs common methods. Based on http://metaclass.org/2006/12/22/making-a-mockery-of-activerecord -* Fixed [#8165] Partial Mock Errors when respond_to? is true but the method is not in the object -* Fixed [#7611] Partial Mocks override Subclass methods -* Fixed [#8302] Strange side effect when mocking a class method -* Applied [#8316] to_param should return a stringified key in resource generator's controller spec (Patch from Chris Anderson) -* Applied [#8216] shortcut for creating object stub -* Applied [#8008] Correct generated specs for view when calling resource generator (Patch from Jonathan Tron) -* Fixed [#7754] Command-R fails to run spec in TextMate (added instruction from Luke Redpath to the website) -* Fixed [#7826] RSpect.tmbundle web page out of date. -* RSpec on Rails specs are now running against RoR 1.2.1 and 1.2.2 -* rspec_scaffold now generates specs for views -* In a Rails app, RSpec core is only loaded when RAILS_ENV==test (init.rb) -* Added support for target.should arbitrary_expectation_handler and target.should_not arbitrary_expectation_handler -* Fixed [#7533] Spec suite fails and the process exits with a code 0 -* Fixed [#7565] Subsequent stub! calls for method fail to override the first call to method -* Applied [#7524] Incorrect Documentation for 'pattern' in Rake task (patch from Stephen Duncan) -* Fixed [#7409] default fixtures do not appear to run. -* Fixed [#7507] "render..and return" doesn't return -* Fixed [#7509] rcov/rspec incorrectly includes boot.rb (Patch from Courtenay) -* Fixed [#7506] unnecessary complex output on failure of response.should be_redirect -* Applied [#6098] Make scaffold_resource generator. Based on code from Pat Maddox. -* The drbspec command is gone. Use spec --drb instead. -* The drb option is gone from the Rake task. Pass --drb to spec_opts instead. -* New -X/--drb option for running specs against a server like spec/rails' script/rails_spec_server -* New -O/--options and -G/--generate flags for file-based options (handy for spec/rails) -* Applied [#7339] Turn off caching in HTML reports -* Applied [#7419] "c option for colorizing output does not work with rails_spec" (Patch from Shintaro Kakutani) -* Applied [#7406] [PATCH] 0.7.5 rspec_on_rails loads fixtures into development database (Patch from Wilson Bilkovich) -* Applied [#7387] Allow stubs to return consecutive values (Patch from Pat Maddox) -* Applied [#7393] Fix for rake task (Patch from Pat Maddox) -* Reinstated support for response.should_render (in addition to controller.should_render) - -== Version 0.7.5.1 - -Bug fix release to allow downloads of rspec gem using rubygems 0.9.1. - -== Version 0.7.5 -This release adds support for Heckle - Seattle'rb's code mutation tool. -There are also several bug fixes to the RSpec core and the RSpec on Rails plugin. - -* Removed svn:externals on rails versions and plugins -* Applied [#7345] Adding context_setup and context_teardown, with specs and 100% rcov -* Applied [#7320] [PATCH] Allow XHR requests in controller specs to render RJS templates -* Applied [#7319] Migration code uses drop_column when it should use remove_column (patch from Pat Maddox) -* Added support for Heckle -* Applied [#7282] dump results even if spec is interrupted (patch from Kouhei Sutou) -* Applied [#7277] model.should_have(n).errors_on(:attribute) (patch from Wilson Bilkovich) -* Applied [#7270] RSpec render_partial colliding with simply_helpful (patch from David Goodlad) -* Added [#7250] stubs should support throwing -* Added [#7249] stubs should support yielding -* Fixed [#6760] fatal error when accessing nested finders in rspec -* Fixed [#7179] script/generate rspec_scaffold generates incorrect helper name -* Added preliminary support for assert_select (response.should_have) -* Fixed [#6971] and_yield does not work when the arity is -1 -* Fixed [#6898] Can we separate rspec from the plugins? -* Added [#7025] should_change should accept a block -* Applied [#6989] partials with locals (patch from Micah Martin) -* Applied [#7023] Typo in team.page - -== Version 0.7.4 - -This release features a complete redesign of the reports generated with --format html. -As usual there are many bug fixes - mostly related to spec/rails. - -* Applied [#7010] Fixes :spacer_template does not work w/ view spec (patch from Shintaro Kakutani) -* Applied [#6798] ensure two ':' in the first backtrace line for Emacs's 'next-error' command (patch from Kouhei Sutou) -* Added Much nicer reports to generated website -* Much nicer reports with --format --html (patch from Luke Redpath) -* Applied [#6959] Calls to render and redirect in controllers should return true -* Fixed [#6981] helper method is not available in partial template. -* Added [#6978] mock should tell you the expected and actual args when receiving the right message with the wrong args -* Added the possibility to tweak the output of the HtmlFormatter (by overriding extra_failure_content). -* Fixed [#6936] View specs don't include ApplicationHelper by default -* Fixed [#6903] Rendering a partial in a view makes the view spec blow up -* Added callback library from Brian Takita -* Added [#6925] support controller.should_render :action_name -* Fixed [#6884] intermittent errors related to method binding -* Fixed [#6870] rspec on edge rails spec:controller fixture loading fails -* Using obj.inspect for all messages -* Improved performance by getting rid of instance_exec (instance_eval is good enough because we never need to pass it args) - -== Version 0.7.3 - -Almost normal bug fix/new feature release. - -A couple of things you need to change in your rails specs: -# spec_helper.rb is a little different (see http://rspec.rubyforge.org/upgrade.html) -# use controller.should_render before OR after the action (controller.should_have_rendered is deprecated) - -* Applied [#6577] messy mock backtrace when frozen to edge rails (patch from Jay Levitt) -* Fixed [#6674] rspec_on_rails fails on @session deprecation warning -* Fixed [#6780] routing() was failing...fix included - works for 1.1.6 and edge (1.2) -* Fixed [#6835] bad message with arbitrary predicate -* Added [#6731] Partial templates rendered -* Fixed [#6713] helper methods not rendered in view tests? -* Fixed [#6707] cannot run controller / helper tests via rails_spec or spec only works with rake -* Applied [#6417] lambda {...}.should_change(receiver, :message) (patch from Wilson Bilkovich) -* Eliminated dependency on ZenTest -* Fixed [#6650] Reserved characters in the TextMate bundle break svn on Win32 -* Fixed [#6643] script/generate rspec_controller: invalid symbol generation for 'controller_name' for *modularized* controllers -* The script/rails_spec command has been moved to bin/drbspec in RSpec core (installed by the gem) - -== Version 0.7.2 - -This release introduces a brand new RSpec bundle for TextMate, plus some small bugfixes. - -* Packaged RSpec.tmbundle.tgz as part of the distro -* Fixed [#6593] Add moving progress bar to HtmlFormatter using Javascript -* Applied [#6265] should_raise should accept an Exception object -* Fixed [#6616] Can't run Rails specs with RSpec.tmbundle -* Fixed [#6411] Can't run Rails specs with ruby -* Added [#6589] New -l --line option. This is useful for IDE/editor runners/extensions. -* Fixed [#6615] controller.should_render_rjs should support :partial => 'path/to/template' - -== Version 0.7.1 - -Bug fixes and a couple o' new features. - -* Fixed [#6575] Parse error in aliasing the partial mock original method (patch by Brian Takita) -* Fixed [#6277] debris left by stubbing (trunk) [submitted by dastels] (fixed by fix to [#6575]) -* Fixed [#6575] Parse error in aliasing the partial mock original method -* Fixed [#6555] should_have_tag does not match documentation -* Fixed [#6567] SyntaxError should not stop entire run -* Fixed [#6558] integrated views look for template even when redirected -* Fixed [#6547] response.should be_redirect broken in 0.7.0 -* Applied [#6471] Easy way to spec routes -* Applied [#6587] Rspec on Rails displays "Spec::Rails::ContextFactory" as context name -* Applied [#6514] Document has trivial typos. -* Added [#6560] controller.session should be available before the action -* Added support for should_have_rjs :visual_effect -* Different printing and colours for unmet expectations (red) and other exceptions (magenta) -* Simplified method_missing on mock_methods to make it less invasive on partial mocks. - -== Version 0.7.0 - -This is the "Grow up and eat your own dog food release". RSpec is now used on itself and -we're no longer using Test::Unit to test it. Although, we are still extending Test::Unit -for the rails plugin (indirectly - through ZenTest) - -IMPORTANT NOTE: THIS RELEASE IS NOT 100% BACKWARDS COMPATIBLE TO 0.6.x - -There are a few changes that will require that you change your existing specs. - -RSpec now handles equality exactly like ruby does: - -# actual.should_equal(expected) will pass if actual.equal?(expected) returns true -# actual.should eql(expected) will pass if actual.eql?(expected) returns true -# actual.should == expected will pass if actual == expected) returns true - -At the high level, eql? implies equivalence, while equal? implies object identity. For more -information on how ruby deals w/ equality, you should do this: - -ri equal? - -or look at this: - -http://www.ruby-doc.org/core/classes/Object.html#M001057 - -Also, we left in should_be as a synonym for should_equal, so the only specs that should break are the -ones using should_equal (which used to use == instead of .equal?). - -Lastly, should_be used to handle true and false differently from any other values. We've removed -this special handling, so now actual.should_be true will fail for any value other than true (it -used to pass for any non-nil, non-false value), and actual.should_be false will fail for any -value other than false (it used to pass for nil or false). - -Here's what you'll need to do to update your specs: -# search for "should_equal" and replace with "should_eql" -# run specs - -If any specs still fail, they are probably related to should be_true or should_be_false using -non-boolean values. Those you'll just have to inspect manually and adjust appropriately (sorry!). - --------------------------------------------------- -Specifying multiple return values in mocks now works like this: - -mock.should_receive(:message).and_return(1,2,3) - -It used to work like this: - -mock.should_receive(:message).and_return([1,2,3]) - -but we decided that was counter intuitive and otherwise lame. - -Here's what you'll need to do to update your specs: -# search for "and_return([" -# get rid of the "[" and "]" - --------------------------------------------------- -RSpec on Rails now supports the following (thanks to ZenTest upon which it is built): - -# Separate specs for models, views, controllers and helpers -# Controller specs are completely decoupled from the views by default (though you can tell them to couple themselves if you prefer) -# View specs are completely decoupled from app-specific controllers - -See http://rspec.rubyforge.org/documentation/rails/index.html for more information --------------------------------------------------- -As usual, there are also other new features and bug fixes: - -* Added lots of documentation on mocks/stubs and the rails plugin. -* Added support for assigns[key] syntax for controller specs (to align w/ pre-existing syntax for view specs) -* Added support for controller.should_redirect_to -* RSpec on Rails automatically checks whether it's compatible with the installed RSpec -* Applied [#6393] rspec_on_rails uses deprecated '@response' instead of the accessor -* RSpec now has 100% spec coverage(!) -* Added support for stubbing and partial mocking -* Progress (....F..F.) is now coloured. Tweaked patch from KAKUTANI Shintaro. -* Backtrace now excludes the rcov runner (/usr/local/bin/rcov) -* Fixed [#5539] predicates do not work w/ rails -* Added [#6091] support for Regexp matching messages sent to should_raise -* Added [#6333] support for Regexp matching in mock arguments -* Applied [#6283] refactoring of diff support to allow selectable formats and custom differs -* Fixed [#5564] "ruby spec_file.rb" doesn't work the same way as "spec spec_file.rb" -* Fixed [#6056] Multiple output of failing-spec notice -* Fixed [#6233] Colours in specdoc -* Applied [#6207] Allows --diff option to diff target and expected's #inspect output (Patch by Lachie Cox) -* Fixed [#6203] Failure messages are misleading - consider using inspect. -* Added [#6334] subject.should_have_xyz will try to call subject.has_xyz? - use this for hash.should_have_key(key) -* Fixed [#6017] Rake task should ignore empty or non-existent spec-dirs - -== Version 0.6.4 - -In addition to a number of bug fixes and patches, this release begins to formalize the support for -RSpec on Rails. - -* Added Christopher Petrilli's TextMate bundle to vendor/textmate/RSpec.tmbundle -* Fixed [#5909], once again supporting multi_word_predicates -* Applied [#5873] - response.should_have_rjs (initial patch from Jake Howerton, based on ARTS by Kevin Clark) -* Added generation of view specs for rspec_on_rails -* Applied [#5815] active_record_subclass.should_have(3).records -* Added support in "rake stats" for view specs (in spec/views) -* Applied [#5801] QuickRef.pdf should say RSpec, not rSpec -* Applied [#5728] rails_spec_runner fails on Windows (Patch from Lindsay Evans). -* Applied [#5708] RSpec Rails plugin rspec_controller generator makes specs that do not parse. -* Cleaned up RSpec on Rails so it doesn't pollute as much during bootstrapping. -* Added support for response.should_have_tag and response.should_not_have_tag (works just like assert_tag in rails) -* Added new -c, --colour, --color option for colourful (red/green) output. Inspired from Pat Eyler's Redgreen gem. -* Added examples for Watir and Selenium under the gem's vendor directory. -* Renamed rails_spec_runner to rails_spec_server (as referred to in the docs) -* Added support for trying a plural for arbitrary predicates. E.g. Album.should_exist(:name => "Hey Jude") will call Album.exists?(:name => "Hey Jude") -* Added support for should_have to work with methods taking args returning a collection. E.g. @dave.should_have(3).albums_i_have_that_this_guy_doesnt(@aslak) -* Added [#5570] should_not_receive(:msg).with(:specific, "args") -* Applied [#5065] to support using define_method rather than method_missing to capture expected messages on mocks. Thanks to Eero Saynatkari for the tip that made it work. -* Restructured directories and Modules in order to separate rspec into three distinct Modules: Spec::Expectations, Spec::Runner and Spec::Mocks. This will allow us to more easily integrate other mock frameworks and/or allow test/unit users to take advantage of the expectation API. -* Applied [#5620] support any boolean method and arbitrary comparisons (5.should_be < 6) (Patch from Mike Williams) - -== Version 0.6.3 - -This release fixes some minor bugs related to RSpec on Rails -Note that if you upgrade a rails app with this version of the rspec_on_rails plugin -you should remove your lib/tasks/rspec.rake if it exists. - -* Backtraces from drb (and other standard ruby libraries) are now stripped from backtraces. -* Applied [#5557] Put rspec.rake into the task directory of the RSpec on Rails plugin (Patch from Daniel Siemssen) -* Applied [#5556] rails_spec_server loads environment.rb twice (Patch from Daniel Siemssen) - -== Version 0.6.2 -This release fixes a couple of regressions with the rake task that were introduced in the previous version (0.6.1) - -* Fixed [#5518] ruby -w: warnings in 0.6.1 -* Applied [#5525] fix rake task path to spec tool for gem-installed rspec (patch from Riley Lynch) -* Fixed a teensey regression with the rake task - introduced in 0.6.1. The spec command is now quoted so it works on windows. - -== Version 0.6.1 -This is the "fix the most annoying bugs release" of RSpec. There are 9 bugfixes this time. -Things that may break backwards compatibility: -1) Spec::Rake::SpecTask no longer has the options attribute. Use ruby_opts, spec_opts and rcov_opts instead. - -* Fixed [#4891] RCOV task failing on windows -* Fixed [#4896] Shouldn't modify user's $LOAD_PATH (Tip from Gavin Sinclair) -* Fixed [#5369] ruby -w: warnings in RSpec 0.5.16 (Tip from Suraj Kurapati) -* Applied [#5141] ExampleMatcher doesn't escape strings before matching (Patch from Nikolai Weibull). -* Fixed [#5224] Move 'require diff-lcs' from test_helper.rb to diff_test.rb (Tip from Chris Roos) -* Applied [#5449] Rake stats for specs (Patch from Nick Sieger) -* Applied [#5468, #5058] Fix spec runner to correctly run controller specs (Patch from Daniel Siemssen) -* Applied fixes to rails_spec_server to improve its ability to run several times. (Patch from Daniel Siemssen) -* Changed RCov::VerifyTask to fail if the coverage is above the threshold. This is to ensure it gets bumped when coverage improves. - -== Version 0.6.0 -This release makes an official commitment to underscore_syntax (with no more support for dot.syntax) - -* Fixed bug (5292) that caused mock argument matching to fail -* Converted ALL tests to use underscore syntax -* Fixed all remaining problems with underscores revealed by converting all the tests to underscores -* Enhanced sugar to support combinations of methods (i.e. once.and_return) -* Simplified helper structure taking advantage of dot/underscore combos (i.e. should.be.an_instance_of, which can be expressed as should be_an_instance_of) -* Added support for at_most in mocks -* Added support for should_not_receive(:msg) (will be removing should_receive(:msg).never some time soon) -* Added support for should_have_exactly(5).items_in_collection - -== Version 0.5.16 -This release improves Rails support and test2spec translation. - -* Fixed underscore problems that occurred when RSpec was used in Rails -* Simplified the Rails support by packaging it as a plugin instead of a generator gem. -* Fixed [#5063] 'rspec_on_rails' require line in spec_helper.rb -* Added pre_commit rake task to reduce risk of regressions. Useful for RSpec developers and patchers. -* Added failure_message to RSpec Rake task -* test2spec now defines converted helper methods outside of the setup block (bug #5057). - -== Version 0.5.15 -This release removes a prematurely added feature that shouldn't have been added. - -* Removed support for differences that was added in 0.5.14. The functionality is not aligned with RSpec's vision. - -== Version 0.5.14 -This release introduces better ways to extend specs, improves some of the core API and -a experimental support for faster rails specs. - -* Added proc methods for specifying differences (increments and decrements). See difference_test.rb -* Methods can now be defined alongside specs. This obsoletes the need for defining methods in setup. (Patch #5002 from Brian Takita) -* Sugar (underscores) now works correctly with should be_a_kind_of and should be_an_instance_of -* Added support for include and inherit in contexts. (Patch #4971 from Brian Takita) -* Added rails_spec and rails_spec_server for faster specs on rails (still buggy - help needed) -* Fixed bug that caused should_render to break if given a :symbol (in Rails) -* Added support for comparing exception message in should_raise and should_not_raise - -== Version 0.5.13 -This release fixes some subtle bugs in the mock API. - -* Use fully-qualified class name of Exceptions in failure message. Easier to debug that way. -* Fixed a bug that caused mocks to yield a one-element array (rather than the element) when one yield arg specified. -* Mocks not raise AmbiguousReturnError if an explicit return is used at the same time as an expectation block. -* Blocks passed to yielding mocks can now raise without causing mock verification to fail. - -== Version 0.5.12 -This release adds diff support for failure messages, a HTML formatter plus some other -minor enhancements. - -* Added HTML formatter. -* Added fail_on_error option to spectask. -* Added support for diffing, using the diff-lcs Rubygem (#2648). -* Remove RSpec on Rails files from backtrace (#4694). -* All of RSpec's own tests run successfully after translation with test2spec. -* Added --verbose mode for test2spec - useful for debugging when classes fail to translate. -* Output of various formatters is now flushed - to get more continuous output. - -== Version 0.5.11 -This release makes test2spec usable with Rails (with some manual steps). -See http://rspec.rubyforge.org/tools/rails.html for more details - -* test2spec now correctly translates bodies of helper methods (non- test_*, setup and teardown ones). -* Added more documentation about how to get test2spec to work with Rails. - -== Version 0.5.10 -This version features a second rewrite of test2spec - hopefully better than the previous one. - -* Improved test2spec's internals. It now transforms the syntax tree before writing out the code. - -== Version 0.5.9 -This release improves test2spec by allowing more control over the output - -* Added --template option to test2spec, which allows for custom output driven by ERB -* Added --quiet option to test2spec -* Removed unnecessary dependency on RubyToC - -== Version 0.5.8 -This release features a new Test::Unit to RSpec translation tool. -Also note that the RubyGem of the previous release (0.5.7) was corrupt. -We're close to being able to translate all of RSpec's own Test::Unit -tests and have them run successfully! - -* Updated test2spec documentation. -* Replaced old test2rspec with a new test2spec, which is based on ParseTree and RubyInline. - -== Version 0.5.7 -This release changes examples and documentation to recommend underscores rather than dots, -and addresses some bugfixes and changes to the spec commandline. - -* spec DIR now works correctly, recursing down and slurping all *.rb files -* All documentation and examples are now using '_' instead of '.' -* Custom external formatters can now be specified via --require and --format. - -== Version 0.5.6 -This release fixes a bug in the Rails controller generator - -* The controller generator did not write correct source code (missing 'do'). Fixed. - -== Version 0.5.5 -This release adds initial support for Ruby on Rails in the rspec_generator gem. - -* [Rails] Reorganised Lachie's original code to be a generator packaged as a gem rather than a plugin. -* [Rails] Imported code from http://lachie.info/svn/projects/rails_plugins/rspec_on_rails (Written by Lachie Cox) -* Remove stack trace lines from TextMate's Ruby bundle -* Better error message from spectask when no spec files are found. - -== Version 0.5.4 -The "the tutorial is ahead of the gem" release - -* Support for running a single spec with --spec -* Exitcode is now 1 unless all specs pass, in which case it's 0. -* -v, --version now both mean the same thing -* For what was verbose output (-v), use --format specdoc or -f s -* --format rdoc always runs in dry-run mode -* Removed --doc and added --format and --dry-run -* Refactored towards more pluggable formatters -* Use webgen's execute tag when generating website (more accurate) -* Fixed incorrect quoting of spec_opts in SpecTask -* Added patch to enable underscored shoulds like 1.should_equal(1) - patch from Rich Kilmer -* Removed most inherited instance method from Mock, making more methods mockable. -* Made the RCovVerify task part of the standard toolset. -* Documented Rake task and how to use it with Rcov -* Implemented tags for website (hooking into ERB, RedCloth and syntax) -* RSpec Rake task now takes spec_opts and out params so it can be used for doc generation -* RCov integration for RSpec Rake task (#4058) -* Group all results instead of printing them several times (#4057) -* Mocks can now yield -* Various improvements to error reporting (including #4191) -* backtrace excludes rspec code - use -b to include it -* split examples into examples (passing) and failing_examples - -== Version 0.5.3 -The "hurry up, CoR is in two days" release. - -* Don't run rcov by default -* Make separate task for running tests with RCov -* Added Rake task to fail build if coverage drops below a certain threshold -* Even more failure output cleanup (simplification) -* Added duck_type constraint for mocks - -== Version 0.5.2 -This release has minor improvements to the commandline and fixes some gem warnings - -* Readded README to avoid RDoc warnings -* Added --version switch to commandline -* More changes to the mock API - -== Version 0.5.1 -This release is the first release of RSpec with a new website. It will look better soon. - -* Added initial documentation for API -* Added website based on webgen -* Modified test task to use rcov -* Deleted unused code (thanks, rcov!) -* Various changes to the mock API, -* Various improvements to failure reporting - -== Version 0.5.0 -This release introduces a new API and obsolesces previous versions. - -* Moved source code to separate subfolders -* Added new DSL runner based on instance_exec -* Added spike for testdox/rdoc generation -* merge Astels' and Chelimsky's work on ShouldHelper -* this would be 0.5.0 if I updated the documentation -* it breaks all of your existing specifications. We're not sorry. - -== Version 0.3.2 - -The "srbaker is an idiot" release. - -* also forgot to update the path to the actual Subversion repository -* this should be it - -== Version 0.3.1 - -This is just 0.3.0, but with the TUTORIAL added to the documentation list. - -* forgot to include TUTORIAL in the documentation - -== Version 0.3.0 - -It's been a while since last release, lots of new stuff is available. For instance: - -* improvements to the runners -* addition of should_raise expectation (thanks to Brian Takita) -* some documentation improvements -* RSpec usable as a DSL - -== Version 0.2.0 - -This release provides a tutorial for new users wishing to get started with -RSpec, and many improvements. - -* improved reporting in the spec runner output -* update the examples to the new mock api -* added TUTORIAL, a getting started document for new users of RSpec - -== Version 0.1.7 - -This release improves installation and documentation, mock integration and error reporting. - -* Comparison errors now print the class name too. -* Mocks now take an optional +options+ parameter to specify behaviour. -* Removed __expects in favour of should_receive -* Added line number reporting in mock error messages for unreceived message expectations. -* Added should_match and should_not_match. -* Added a +mock+ method to Spec::Context which will create mocks that autoverify (no need to call __verify). -* Mocks now require names in the constructor to ensure sensible error messages. -* Made 'spec' executable and updated usage instructions in README accordingly. -* Made more parts of the Spec::Context API private to avoid accidental usage. -* Added more RDoc to Spec::Context. - -== Version 0.1.6 - -More should methods. - -* Added should_match and should_not_match. - -== Version 0.1.5 - -Included examples and tests in gem. - -== Version 0.1.4 - -More tests on block based Mock expectations. - -== Version 0.1.3 - -Improved mocking: - -* block based Mock expectations. - -== Version 0.1.2 - -This release adds some improvements to the mock API and minor syntax improvements - -* Added Mock.should_expect for a more consistent DSL. -* Added MockExpectation.and_returns for a better DSL. -* Made Mock behave as a null object after a call to Mock.ignore_missing -* Internal syntax improvements. -* Improved exception trace by adding exception class name to error message. -* Renamed some tests for better consistency. - -== Version 0.1.1 - -This release adds some shoulds and improves error reporting - -* Added should be_same_as and should_not be_same_as. -* Improved error reporting for comparison expectations. - -== Version 0.1.0 - -This is the first preview release of RSpec, a Behaviour-Driven Development library for Ruby - -* Added Rake script with tasks for gems, rdoc etc. -* Added an XForge task to make release go easier. diff --git a/vendor/gems/rspec/MIT-LICENSE b/vendor/gems/rspec/MIT-LICENSE deleted file mode 100644 index 1d11ea59e4..0000000000 --- a/vendor/gems/rspec/MIT-LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -Copyright (c) 2005-2007 The RSpec Development Team - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/gems/rspec/README b/vendor/gems/rspec/README deleted file mode 100644 index 0683b0deb6..0000000000 --- a/vendor/gems/rspec/README +++ /dev/null @@ -1,71 +0,0 @@ -== RSpec - -RSpec is a Behaviour Driven Development framework with tools to express User Stories -with Executable Scenarios and Executable Examples at the code level. - -RSpec ships with several modules: - -Spec::Story provides a framework for expressing User Stories - -Spec::Example provides a framework for expressing code Examples - -Spec::Matchers provides Expression Matchers for use with Spec::Expectations -and Spec::Mocks. - -Spec::Expectations supports setting expectations on your objects so you -can do things like: - - result.should equal(expected_result) - -Spec::Mocks supports creating Mock Objects, Stubs, and adding Mock/Stub -behaviour to your existing objects. - -== Installation - -The simplest approach is to install the gem: - - gem install -r rspec #mac users must sudo - -== Building the RSpec gem -If you prefer to build the gem locally, check out source from svn://rubyforge.org/var/svn/rspec/trunk. Then -do the following: - - rake gem - gem install pkg/rspec-0.x.x.gem (you may have to sudo) - -== Running RSpec's specs -In order to run RSpec's full suite of specs (rake pre_commit) you must install the following gems: - -* rake # Runs the build script -* rcov # Verifies that the code is 100% covered by specs -* webby # Generates the static HTML website -* syntax # Required to highlight ruby code -* diff-lcs # Required if you use the --diff switch -* win32console # Required by the --colour switch if you're on Windows -* meta_project # Required in order to make releases at RubyForge -* heckle # Required if you use the --heckle switch -* hpricot # Used for parsing HTML from the HTML output formatter in RSpec's own specs - -Once those are all installed, you should be able to run the suite with the following steps: - -* svn co svn://rubyforge.org/var/svn/rspec/trunk rspec -* cd rspec -* rake install_dependencies -* cd example_rails_app -* export RSPEC_RAILS_VERSION=1.2.3 -* rake rspec:generate_mysql_config -* mysql -u root < db/mysql_setup.sql -* cd .. -* rake pre_commit - -Note that RSpec itself - once built - doesn't have any dependencies outside the Ruby core -and stdlib - with a few exceptions: - -* The spec command line uses diff-lcs when --diff is specified. -* The spec command line uses heckle when --heckle is specified. -* The Spec::Rake::SpecTask needs RCov if RCov is enabled in the task. - -See http://rspec.rubyforge.org for further documentation. - -== Contributing - diff --git a/vendor/gems/rspec/Rakefile b/vendor/gems/rspec/Rakefile deleted file mode 100644 index c60d5d466c..0000000000 --- a/vendor/gems/rspec/Rakefile +++ /dev/null @@ -1,279 +0,0 @@ -$:.unshift('lib') -require 'rubygems' -require 'rake/gempackagetask' -require 'rake/contrib/rubyforgepublisher' -require 'rake/clean' -require 'rake/rdoctask' -require 'rake/testtask' -require 'spec/version' -dir = File.dirname(__FILE__) -$LOAD_PATH.unshift(File.expand_path("#{dir}/pre_commit/lib")) -require "pre_commit" - -# Some of the tasks are in separate files since they are also part of the website documentation -load File.dirname(__FILE__) + '/rake_tasks/examples.rake' -load File.dirname(__FILE__) + '/rake_tasks/examples_with_rcov.rake' -load File.dirname(__FILE__) + '/rake_tasks/failing_examples_with_html.rake' -load File.dirname(__FILE__) + '/rake_tasks/verify_rcov.rake' - -PKG_NAME = "rspec" -PKG_VERSION = Spec::VERSION::STRING -PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}" -PKG_FILES = FileList[ - '[A-Z]*', - 'lib/**/*.rb', - 'spec/**/*', - 'examples/**/*', - 'failing_examples/**/*', - 'plugins/**/*', - 'stories/**/*', - 'pre_commit/**/*', - 'rake_tasks/**/*' -] - -task :default => [:verify_rcov] -task :verify_rcov => [:spec, :stories] - -desc "Run all specs" -Spec::Rake::SpecTask.new do |t| - t.spec_files = FileList['spec/**/*_spec.rb'] - t.spec_opts = ['--options', 'spec/spec.opts'] - unless ENV['NO_RCOV'] - t.rcov = true - t.rcov_dir = '../doc/output/coverage' - t.rcov_opts = ['--exclude', 'spec\/spec,bin\/spec,examples,\/var\/lib\/gems,\/Library\/Ruby,\.autotest'] - end -end - -desc "Run all stories" -task :stories do - html = 'story_server/prototype/rspec_stories.html' - ruby "stories/all.rb --colour --format plain --format html:#{html}" - unless IO.read(html) =~ //m - raise 'highlighted parameters are broken in story HTML' - end -end - -desc "Run all specs and store html output in doc/output/report.html" -Spec::Rake::SpecTask.new('spec_html') do |t| - t.spec_files = FileList['spec/**/*_spec.rb', '../../RSpec.tmbundle/Support/spec/*_spec.rb'] - t.spec_opts = ['--format html:../doc/output/report.html','--backtrace'] -end - -desc "Run all failing examples" -Spec::Rake::SpecTask.new('failing_examples') do |t| - t.spec_files = FileList['failing_examples/**/*_spec.rb'] -end - -desc 'Generate RDoc' -rd = Rake::RDocTask.new do |rdoc| - rdoc.rdoc_dir = '../doc/output/rdoc' - rdoc.options << '--title' << 'RSpec' << '--line-numbers' << '--inline-source' << '--main' << 'README' - rdoc.rdoc_files.include('README', 'CHANGES', 'MIT-LICENSE', 'UPGRADE', 'lib/**/*.rb') -end - -spec = Gem::Specification.new do |s| - s.name = PKG_NAME - s.version = PKG_VERSION - s.summary = Spec::VERSION::DESCRIPTION - s.description = <<-EOF - RSpec is a behaviour driven development (BDD) framework for Ruby. RSpec was - created in response to Dave Astels' article _A New Look at Test Driven Development_ - which can be read at: http://daveastels.com/index.php?p=5 RSpec is intended to - provide the features discussed in Dave's article. - EOF - - s.files = PKG_FILES.to_a - s.require_path = 'lib' - - s.has_rdoc = true - s.rdoc_options = rd.options - s.extra_rdoc_files = rd.rdoc_files.reject { |fn| fn =~ /\.rb$|^EXAMPLES.rd$/ }.to_a - - s.bindir = 'bin' - s.executables = ['spec', 'spec_translator'] - s.default_executable = 'spec' - s.author = "RSpec Development Team" - s.email = "rspec-devel@rubyforge.org" - s.homepage = "http://rspec.rubyforge.org/" - s.platform = Gem::Platform::RUBY - s.rubyforge_project = "rspec" -end - -Rake::GemPackageTask.new(spec) do |pkg| - pkg.need_zip = true - pkg.need_tar = true -end - -def egrep(pattern) - Dir['**/*.rb'].each do |fn| - count = 0 - open(fn) do |f| - while line = f.gets - count += 1 - if line =~ pattern - puts "#{fn}:#{count}:#{line}" - end - end - end - end -end - -desc "Look for TODO and FIXME tags in the code" -task :todo do - egrep /(FIXME|TODO|TBD)/ -end - -task :clobber do - core.clobber -end - -task :release => [:clobber, :verify_committed, :verify_user, :spec, :publish_packages, :tag, :publish_news] - -desc "Verifies that there is no uncommitted code" -task :verify_committed do - IO.popen('svn stat') do |io| - io.each_line do |line| - raise "\n!!! Do a svn commit first !!!\n\n" if line =~ /^\s*M\s*/ - end - end -end - -desc "Creates a tag in svn" -task :tag do - from = `svn info #{File.dirname(__FILE__)}`.match(/URL: (.*)\/rspec/n)[1] - to = from.gsub(/trunk/, "tags/#{Spec::VERSION::TAG}") - current = from.gsub(/trunk/, "tags/CURRENT") - - puts "Creating tag in SVN" - tag_cmd = "svn cp #{from} #{to} -m \"Tag release #{Spec::VERSION::FULL_VERSION}\"" - `#{tag_cmd}` ; raise "ERROR: #{tag_cmd}" unless $? == 0 - - puts "Removing CURRENT" - remove_current_cmd = "svn rm #{current} -m \"Remove tags/CURRENT\"" - `#{remove_current_cmd}` ; raise "ERROR: #{remove_current_cmd}" unless $? == 0 - - puts "Re-Creating CURRENT" - create_current_cmd = "svn cp #{to} #{current} -m \"Copy #{Spec::VERSION::TAG} to tags/CURRENT\"" - `#{create_current_cmd}` ; "ERROR: #{create_current_cmd}" unless $? == 0 -end - -desc "Run this task before you commit. You should see 'OK TO COMMIT'" -task(:pre_commit) {core.pre_commit} - -desc "Build the website, but do not publish it" -task(:website) {core.website} - -task(:rdoc_rails) {core.rdoc_rails} - -task :verify_user do - raise "RUBYFORGE_USER environment variable not set!" unless ENV['RUBYFORGE_USER'] -end - -desc "Upload Website to RubyForge" -task :publish_website => [:verify_user, :website] do - unless Spec::VERSION::RELEASE_CANDIDATE - publisher = Rake::SshDirPublisher.new( - "rspec-website@rubyforge.org", - "/var/www/gforge-projects/#{PKG_NAME}", - "../doc/output" - ) - publisher.upload - else - puts "** Not publishing packages to RubyForge - this is a prerelease" - end -end - -desc "Upload Website archive to RubyForge" -task :archive_website => [:verify_user, :website] do - publisher = Rake::SshDirPublisher.new( - "rspec-website@rubyforge.org", - "/var/www/gforge-projects/#{PKG_NAME}/#{Spec::VERSION::TAG}", - "../doc/output" - ) - publisher.upload -end - -desc "Package the Rails plugin" -task :package_rspec_on_rails do - mkdir 'pkg' rescue nil - rm_rf 'pkg/rspec_on_rails' rescue nil - `svn export ../rspec_on_rails pkg/rspec_on_rails-#{PKG_VERSION}` - Dir.chdir 'pkg' do - `tar cvzf rspec_on_rails-#{PKG_VERSION}.tgz rspec_on_rails-#{PKG_VERSION}` - end -end -task :pkg => :package_rspec_on_rails - -desc "Package the RSpec.tmbundle" -task :package_tmbundle do - mkdir 'pkg' rescue nil - rm_rf 'pkg/RSpec.tmbundle' rescue nil - `svn export ../RSpec.tmbundle pkg/RSpec.tmbundle` - Dir.chdir 'pkg' do - `tar cvzf RSpec-#{PKG_VERSION}.tmbundle.tgz RSpec.tmbundle` - end -end -task :pkg => :package_tmbundle - -desc "Publish gem+tgz+zip on RubyForge. You must make sure lib/version.rb is aligned with the CHANGELOG file" -task :publish_packages => [:verify_user, :package] do - release_files = FileList[ - "pkg/#{PKG_FILE_NAME}.gem", - "pkg/#{PKG_FILE_NAME}.tgz", - "pkg/rspec_on_rails-#{PKG_VERSION}.tgz", - "pkg/#{PKG_FILE_NAME}.zip", - "pkg/RSpec-#{PKG_VERSION}.tmbundle.tgz" - ] - unless Spec::VERSION::RELEASE_CANDIDATE - require 'meta_project' - require 'rake/contrib/xforge' - - Rake::XForge::Release.new(MetaProject::Project::XForge::RubyForge.new(PKG_NAME)) do |xf| - # Never hardcode user name and password in the Rakefile! - xf.user_name = ENV['RUBYFORGE_USER'] - xf.files = release_files.to_a - xf.release_name = "RSpec #{PKG_VERSION}" - end - else - puts "SINCE THIS IS A PRERELEASE, FILES ARE UPLOADED WITH SSH, NOT TO THE RUBYFORGE FILE SECTION" - puts "YOU MUST TYPE THE PASSWORD #{release_files.length} TIMES..." - - host = "rspec-website@rubyforge.org" - remote_dir = "/var/www/gforge-projects/#{PKG_NAME}" - - publisher = Rake::SshFilePublisher.new( - host, - remote_dir, - File.dirname(__FILE__), - *release_files - ) - publisher.upload - - puts "UPLADED THE FOLLOWING FILES:" - release_files.each do |file| - name = file.match(/pkg\/(.*)/)[1] - puts "* http://rspec.rubyforge.org/#{name}" - end - - puts "They are not linked to anywhere, so don't forget to tell people!" - end -end - -desc "Publish news on RubyForge" -task :publish_news => [:verify_user] do - unless Spec::VERSION::RELEASE_CANDIDATE - require 'meta_project' - require 'rake/contrib/xforge' - Rake::XForge::NewsPublisher.new(MetaProject::Project::XForge::RubyForge.new(PKG_NAME)) do |news| - # Never hardcode user name and password in the Rakefile! - news.user_name = ENV['RUBYFORGE_USER'] - end - else - puts "** Not publishing news to RubyForge - this is a prerelease" - end -end - -def core - PreCommit::Core.new(self) -end diff --git a/vendor/gems/rspec/TODO b/vendor/gems/rspec/TODO deleted file mode 100644 index 250bb66c22..0000000000 --- a/vendor/gems/rspec/TODO +++ /dev/null @@ -1,2 +0,0 @@ -=== Before releasing 1.1.0: - diff --git a/vendor/gems/rspec/UPGRADE b/vendor/gems/rspec/UPGRADE deleted file mode 100644 index 0a2c6f5286..0000000000 --- a/vendor/gems/rspec/UPGRADE +++ /dev/null @@ -1,31 +0,0 @@ -= Upgrading existing code to RSpec-0.9 - -== General (see below for Spec::Rails specifics) - -=== New Syntax for should and should_not - -* Use translator (should get 90% of your code) -* Manually fix "parenthesis" warnings - -=== Change before_context_eval to before_eval - -before_context_eval is an un-published hook used by -Spec::Rails to create specialized behaviour contexts. -Most of you don't need to change this, but for those -who have exploited it, you'll need to change it to -before_eval. - -== Spec::Rails - -=== spec_helper.rb - -We've added a new way to configure Spec::Runner to do -things like use_transactional_fixtures and use_instantiated_fixtures. -You'll need to update spec/spec_helper.rb accordingly. You can either -just re-generate it: - - script/generate rspec - -Or modify spec_helper.rb based on the template, which can be found at: - - vendor/plugins/rspec_on_rails/generators/rspec/templates/spec_helper.rb \ No newline at end of file diff --git a/vendor/gems/rspec/bin/spec b/vendor/gems/rspec/bin/spec deleted file mode 100755 index 283176d765..0000000000 --- a/vendor/gems/rspec/bin/spec +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env ruby -$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib")) -require 'spec' -exit ::Spec::Runner::CommandLine.run(rspec_options) diff --git a/vendor/gems/rspec/bin/spec_translator b/vendor/gems/rspec/bin/spec_translator deleted file mode 100755 index abd50b7431..0000000000 --- a/vendor/gems/rspec/bin/spec_translator +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env ruby -raise "\n\nUsage: spec_translator from_dir to_dir\n\n" if ARGV.size != 2 -$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib")) -require 'spec/translator' -t = ::Spec::Translator.new -from = ARGV[0] -to = ARGV[1] -t.translate(from, to) diff --git a/vendor/gems/rspec/examples/pure/autogenerated_docstrings_example.rb b/vendor/gems/rspec/examples/pure/autogenerated_docstrings_example.rb deleted file mode 100644 index a4928ef4a8..0000000000 --- a/vendor/gems/rspec/examples/pure/autogenerated_docstrings_example.rb +++ /dev/null @@ -1,19 +0,0 @@ -require File.dirname(__FILE__) + '/spec_helper' - -# Run spec w/ -fs to see the output of this file - -describe "Examples with no descriptions" do - - # description is auto-generated as "should equal(5)" based on the last #should - it do - 3.should equal(3) - 5.should equal(5) - end - - it { 3.should be < 5 } - - it { ["a"].should include("a") } - - it { [1,2,3].should respond_to(:size) } - -end diff --git a/vendor/gems/rspec/examples/pure/before_and_after_example.rb b/vendor/gems/rspec/examples/pure/before_and_after_example.rb deleted file mode 100644 index 7db6274ef9..0000000000 --- a/vendor/gems/rspec/examples/pure/before_and_after_example.rb +++ /dev/null @@ -1,40 +0,0 @@ -require File.dirname(__FILE__) + '/spec_helper' -$global = 0 - -describe "State created in before(:all)" do - before :all do - @sideeffect = 1 - $global +=1 - end - - before :each do - @isolated = 1 - end - - it "should be accessible from example" do - @sideeffect.should == 1 - $global.should == 1 - @isolated.should == 1 - - @sideeffect += 1 - @isolated += 1 - end - - it "should not have sideffects" do - @sideeffect.should == 1 - $global.should == 2 - @isolated.should == 1 - - @sideeffect += 1 - @isolated += 1 - end - - after :each do - $global += 1 - end - - after :all do - $global.should == 3 - $global = 0 - end -end diff --git a/vendor/gems/rspec/examples/pure/behave_as_example.rb b/vendor/gems/rspec/examples/pure/behave_as_example.rb deleted file mode 100755 index e95d1469ae..0000000000 --- a/vendor/gems/rspec/examples/pure/behave_as_example.rb +++ /dev/null @@ -1,45 +0,0 @@ -require File.dirname(__FILE__) + '/spec_helper' - -def behave_as_electric_musician - respond_to(:read_notes, :turn_down_amp) -end - -def behave_as_musician - respond_to(:read_notes) -end - -module BehaveAsExample - - class BluesGuitarist - def read_notes; end - def turn_down_amp; end - end - - class RockGuitarist - def read_notes; end - def turn_down_amp; end - end - - class ClassicGuitarist - def read_notes; end - end - - describe BluesGuitarist do - it "should behave as guitarist" do - BluesGuitarist.new.should behave_as_electric_musician - end - end - - describe RockGuitarist do - it "should behave as guitarist" do - RockGuitarist.new.should behave_as_electric_musician - end - end - - describe ClassicGuitarist do - it "should not behave as guitarist" do - ClassicGuitarist.new.should behave_as_musician - end - end - -end diff --git a/vendor/gems/rspec/examples/pure/custom_expectation_matchers.rb b/vendor/gems/rspec/examples/pure/custom_expectation_matchers.rb deleted file mode 100644 index 075bb542dc..0000000000 --- a/vendor/gems/rspec/examples/pure/custom_expectation_matchers.rb +++ /dev/null @@ -1,54 +0,0 @@ -module AnimalSpecHelper - class Eat - def initialize(food) - @food = food - end - - def matches?(animal) - @animal = animal - @animal.eats?(@food) - end - - def failure_message - "expected #{@animal} to eat #{@food}, but it does not" - end - - def negative_failure_message - "expected #{@animal} not to eat #{@food}, but it does" - end - end - - def eat(food) - Eat.new(food) - end -end - -module Animals - class Animal - def eats?(food) - return foods_i_eat.include?(food) - end - end - - class Mouse < Animal - def foods_i_eat - [:cheese] - end - end - - describe Mouse do - include AnimalSpecHelper - before(:each) do - @mouse = Animals::Mouse.new - end - - it "should eat cheese" do - @mouse.should eat(:cheese) - end - - it "should not eat cat" do - @mouse.should_not eat(:cat) - end - end - -end diff --git a/vendor/gems/rspec/examples/pure/custom_formatter.rb b/vendor/gems/rspec/examples/pure/custom_formatter.rb deleted file mode 100644 index c449fdc2e8..0000000000 --- a/vendor/gems/rspec/examples/pure/custom_formatter.rb +++ /dev/null @@ -1,12 +0,0 @@ -require File.dirname(__FILE__) + '/spec_helper' -require 'spec/runner/formatter/progress_bar_formatter' - -# Example of a formatter with custom bactrace printing. Run me with: -# ruby bin/spec failing_examples -r examples/custom_formatter.rb -f CustomFormatter -class CustomFormatter < Spec::Runner::Formatter::ProgressBarFormatter - def backtrace_line(line) - line.gsub(/([^:]*\.rb):(\d*)/) do - "#{$1}:#{$2} " - end - end -end diff --git a/vendor/gems/rspec/examples/pure/dynamic_spec.rb b/vendor/gems/rspec/examples/pure/dynamic_spec.rb deleted file mode 100644 index 15d473d61f..0000000000 --- a/vendor/gems/rspec/examples/pure/dynamic_spec.rb +++ /dev/null @@ -1,9 +0,0 @@ -require File.dirname(__FILE__) + '/spec_helper' - -describe "Some integers" do - (1..10).each do |n| - it "The root of #{n} square should be #{n}" do - Math.sqrt(n*n).should == n - end - end -end diff --git a/vendor/gems/rspec/examples/pure/file_accessor.rb b/vendor/gems/rspec/examples/pure/file_accessor.rb deleted file mode 100644 index ff6fb743c8..0000000000 --- a/vendor/gems/rspec/examples/pure/file_accessor.rb +++ /dev/null @@ -1,19 +0,0 @@ -require File.dirname(__FILE__) + '/spec_helper' -class FileAccessor - def open_and_handle_with(pathname, processor) - pathname.open do |io| - processor.process(io) - end - end -end - -if __FILE__ == $0 - require File.dirname(__FILE__) + '/io_processor' - require 'pathname' - - accessor = FileAccessor.new - io_processor = IoProcessor.new - file = Pathname.new ARGV[0] - - accessor.open_and_handle_with(file, io_processor) -end diff --git a/vendor/gems/rspec/examples/pure/file_accessor_spec.rb b/vendor/gems/rspec/examples/pure/file_accessor_spec.rb deleted file mode 100644 index 628d4c0b06..0000000000 --- a/vendor/gems/rspec/examples/pure/file_accessor_spec.rb +++ /dev/null @@ -1,38 +0,0 @@ -require File.dirname(__FILE__) + '/spec_helper' -require File.dirname(__FILE__) + '/file_accessor' -require 'stringio' - -describe "A FileAccessor" do - # This sequence diagram illustrates what this spec specifies. - # - # +--------------+ +----------+ +-------------+ - # | FileAccessor | | Pathname | | IoProcessor | - # +--------------+ +----------+ +-------------+ - # | | | - # open_and_handle_with | | | - # -------------------->| | open | | - # | |--------------->| | | - # | | io | | | - # | |<...............| | | - # | | | process(io) | - # | |---------------------------------->| | - # | | | | | - # | |<..................................| | - # | | | - # - it "should open a file and pass it to the processor's process method" do - # This is the primary actor - accessor = FileAccessor.new - - # These are the primary actor's neighbours, which we mock. - file = mock "Pathname" - io_processor = mock "IoProcessor" - - io = StringIO.new "whatever" - file.should_receive(:open).and_yield io - io_processor.should_receive(:process).with(io) - - accessor.open_and_handle_with(file, io_processor) - end - -end diff --git a/vendor/gems/rspec/examples/pure/greeter_spec.rb b/vendor/gems/rspec/examples/pure/greeter_spec.rb deleted file mode 100644 index ec7669dcc3..0000000000 --- a/vendor/gems/rspec/examples/pure/greeter_spec.rb +++ /dev/null @@ -1,31 +0,0 @@ -require File.dirname(__FILE__) + '/spec_helper' -# greeter.rb -# -# Based on http://glu.ttono.us/articles/2006/12/19/tormenting-your-tests-with-heckle -# -# Run with: -# -# spec greeter_spec.rb --heckle Greeter -# -class Greeter - def initialize(person = nil) - @person = person - end - - def greet - @person.nil? ? "Hi there!" : "Hi #{@person}!" - end -end - -describe "Greeter" do - it "should say Hi to person" do - greeter = Greeter.new("Kevin") - greeter.greet.should == "Hi Kevin!" - end - - it "should say Hi to nobody" do - greeter = Greeter.new - # Uncomment the next line to make Heckle happy - #greeter.greet.should == "Hi there!" - end -end diff --git a/vendor/gems/rspec/examples/pure/helper_method_example.rb b/vendor/gems/rspec/examples/pure/helper_method_example.rb deleted file mode 100644 index d97f19e65b..0000000000 --- a/vendor/gems/rspec/examples/pure/helper_method_example.rb +++ /dev/null @@ -1,14 +0,0 @@ -require File.dirname(__FILE__) + '/spec_helper' - -module HelperMethodExample - describe "an example group with helper a method" do - def helper_method - "received call" - end - - it "should make that method available to specs" do - helper_method.should == "received call" - end - end -end - diff --git a/vendor/gems/rspec/examples/pure/io_processor.rb b/vendor/gems/rspec/examples/pure/io_processor.rb deleted file mode 100644 index 6b15147b65..0000000000 --- a/vendor/gems/rspec/examples/pure/io_processor.rb +++ /dev/null @@ -1,8 +0,0 @@ -class DataTooShort < StandardError; end - -class IoProcessor - # Does some fancy stuff unless the length of +io+ is shorter than 32 - def process(io) - raise DataTooShort if io.read.length < 32 - end -end diff --git a/vendor/gems/rspec/examples/pure/io_processor_spec.rb b/vendor/gems/rspec/examples/pure/io_processor_spec.rb deleted file mode 100644 index 5cab7bf317..0000000000 --- a/vendor/gems/rspec/examples/pure/io_processor_spec.rb +++ /dev/null @@ -1,21 +0,0 @@ -require File.dirname(__FILE__) + '/spec_helper' -require File.dirname(__FILE__) + '/io_processor' -require 'stringio' - -describe "An IoProcessor" do - before(:each) do - @processor = IoProcessor.new - end - - it "should raise nothing when the file is exactly 32 bytes" do - lambda { - @processor.process(StringIO.new("z"*32)) - }.should_not raise_error - end - - it "should raise an exception when the file length is less than 32 bytes" do - lambda { - @processor.process(StringIO.new("z"*31)) - }.should raise_error(DataTooShort) - end -end diff --git a/vendor/gems/rspec/examples/pure/legacy_spec.rb b/vendor/gems/rspec/examples/pure/legacy_spec.rb deleted file mode 100644 index c86369515d..0000000000 --- a/vendor/gems/rspec/examples/pure/legacy_spec.rb +++ /dev/null @@ -1,11 +0,0 @@ -require File.dirname(__FILE__) + '/spec_helper' -context "A legacy spec" do - setup do - end - - specify "should work fine" do - end - - teardown do - end -end diff --git a/vendor/gems/rspec/examples/pure/mocking_example.rb b/vendor/gems/rspec/examples/pure/mocking_example.rb deleted file mode 100644 index 6adbef59d0..0000000000 --- a/vendor/gems/rspec/examples/pure/mocking_example.rb +++ /dev/null @@ -1,27 +0,0 @@ -require File.dirname(__FILE__) + '/spec_helper' - -describe "A consumer of a mock" do - it "should be able to send messages to the mock" do - mock = mock("poke me") - mock.should_receive(:poke) - mock.poke - end -end - -describe "a mock" do - it "should be able to mock the same message twice w/ different args" do - mock = mock("mock") - mock.should_receive(:msg).with(:arg1).and_return(:val1) - mock.should_receive(:msg).with(:arg2).and_return(:val2) - mock.msg(:arg1).should eql(:val1) - mock.msg(:arg2).should eql(:val2) - end - - it "should be able to mock the same message twice w/ different args in reverse order" do - mock = mock("mock") - mock.should_receive(:msg).with(:arg1).and_return(:val1) - mock.should_receive(:msg).with(:arg2).and_return(:val2) - mock.msg(:arg2).should eql(:val2) - mock.msg(:arg1).should eql(:val1) - end -end diff --git a/vendor/gems/rspec/examples/pure/multi_threaded_behaviour_runner.rb b/vendor/gems/rspec/examples/pure/multi_threaded_behaviour_runner.rb deleted file mode 100644 index 36edcd497a..0000000000 --- a/vendor/gems/rspec/examples/pure/multi_threaded_behaviour_runner.rb +++ /dev/null @@ -1,28 +0,0 @@ -class MultiThreadedExampleGroupRunner < Spec::Runner::ExampleGroupRunner - def initialize(options, arg) - super(options) - # configure these - @thread_count = 4 - @thread_wait = 0 - end - - def run - @threads = [] - q = Queue.new - example_groups.each { |b| q << b} - success = true - @thread_count.times do - @threads << Thread.new(q) do |queue| - while not queue.empty? - example_group = queue.pop - success &= example_group.suite.run(nil) - end - end - sleep @thread_wait - end - @threads.each {|t| t.join} - success - end -end - -MultiThreadedBehaviourRunner = MultiThreadedExampleGroupRunner \ No newline at end of file diff --git a/vendor/gems/rspec/examples/pure/nested_classes_example.rb b/vendor/gems/rspec/examples/pure/nested_classes_example.rb deleted file mode 100644 index abe43b0a66..0000000000 --- a/vendor/gems/rspec/examples/pure/nested_classes_example.rb +++ /dev/null @@ -1,36 +0,0 @@ -require File.dirname(__FILE__) + '/spec_helper' -require File.dirname(__FILE__) + '/stack' - -class StackExamples < Spec::ExampleGroup - describe(Stack) - before(:each) do - @stack = Stack.new - end -end - -class EmptyStackExamples < StackExamples - describe("when empty") - it "should be empty" do - @stack.should be_empty - end -end - -class AlmostFullStackExamples < StackExamples - describe("when almost full") - before(:each) do - (1..9).each {|n| @stack.push n} - end - it "should be full" do - @stack.should_not be_full - end -end - -class FullStackExamples < StackExamples - describe("when full") - before(:each) do - (1..10).each {|n| @stack.push n} - end - it "should be full" do - @stack.should be_full - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/examples/pure/partial_mock_example.rb b/vendor/gems/rspec/examples/pure/partial_mock_example.rb deleted file mode 100644 index 841ec88474..0000000000 --- a/vendor/gems/rspec/examples/pure/partial_mock_example.rb +++ /dev/null @@ -1,28 +0,0 @@ -require File.dirname(__FILE__) + '/spec_helper' - -class MockableClass - def self.find id - return :original_return - end -end - -describe "A partial mock" do - - it "should work at the class level" do - MockableClass.should_receive(:find).with(1).and_return {:stub_return} - MockableClass.find(1).should equal(:stub_return) - end - - it "should revert to the original after each spec" do - MockableClass.find(1).should equal(:original_return) - end - - it "can be mocked w/ ordering" do - MockableClass.should_receive(:msg_1).ordered - MockableClass.should_receive(:msg_2).ordered - MockableClass.should_receive(:msg_3).ordered - MockableClass.msg_1 - MockableClass.msg_2 - MockableClass.msg_3 - end -end diff --git a/vendor/gems/rspec/examples/pure/pending_example.rb b/vendor/gems/rspec/examples/pure/pending_example.rb deleted file mode 100644 index 13f3d00c4c..0000000000 --- a/vendor/gems/rspec/examples/pure/pending_example.rb +++ /dev/null @@ -1,20 +0,0 @@ -require File.dirname(__FILE__) + '/spec_helper' - -describe "pending example (using pending method)" do - it %Q|should be reported as "PENDING: for some reason"| do - pending("for some reason") - end -end - -describe "pending example (with no block)" do - it %Q|should be reported as "PENDING: Not Yet Implemented"| -end - -describe "pending example (with block for pending)" do - it %Q|should have a failing block, passed to pending, reported as "PENDING: for some reason"| do - pending("for some reason") do - raise "some reason" - end - end -end - diff --git a/vendor/gems/rspec/examples/pure/predicate_example.rb b/vendor/gems/rspec/examples/pure/predicate_example.rb deleted file mode 100644 index 1202bb6707..0000000000 --- a/vendor/gems/rspec/examples/pure/predicate_example.rb +++ /dev/null @@ -1,27 +0,0 @@ -require File.dirname(__FILE__) + '/spec_helper' - -class BddFramework - def intuitive? - true - end - - def adopted_quickly? - true - end -end - -describe "BDD framework" do - - before(:each) do - @bdd_framework = BddFramework.new - end - - it "should be adopted quickly" do - @bdd_framework.should be_adopted_quickly - end - - it "should be intuitive" do - @bdd_framework.should be_intuitive - end - -end diff --git a/vendor/gems/rspec/examples/pure/priority.txt b/vendor/gems/rspec/examples/pure/priority.txt deleted file mode 100644 index 5b00064e2c..0000000000 --- a/vendor/gems/rspec/examples/pure/priority.txt +++ /dev/null @@ -1 +0,0 @@ -examples/custom_expectation_matchers.rb \ No newline at end of file diff --git a/vendor/gems/rspec/examples/pure/shared_example_group_example.rb b/vendor/gems/rspec/examples/pure/shared_example_group_example.rb deleted file mode 100644 index fb81af1ecc..0000000000 --- a/vendor/gems/rspec/examples/pure/shared_example_group_example.rb +++ /dev/null @@ -1,81 +0,0 @@ -require File.dirname(__FILE__) + '/spec_helper' - -module SharedExampleGroupExample - class OneThing - def what_things_do - "stuff" - end - end - - class AnotherThing - def what_things_do - "stuff" - end - end - - class YetAnotherThing - def what_things_do - "stuff" - end - end - - # A SharedExampleGroup is an example group that doesn't get run. - # You can create one like this: - share_examples_for "most things" do - def helper_method - "helper method" - end - - it "should do what things do" do - @thing.what_things_do.should == "stuff" - end - end - - # A SharedExampleGroup is also module. If you create one like this - # it gets assigned to the constant AllThings - share_as :MostThings do - def helper_method - "helper method" - end - - it "should do what things do" do - @thing.what_things_do.should == "stuff" - end - end - - describe OneThing do - # Now you can include the shared example group like this, which - # feels more like what you might say ... - it_should_behave_like "most things" - - before(:each) { @thing = OneThing.new } - - it "should have access to helper methods defined in the shared example group" do - helper_method.should == "helper method" - end - end - - describe AnotherThing do - # ... or you can include the example group like this, which - # feels more like the programming language we love. - it_should_behave_like MostThings - - before(:each) { @thing = AnotherThing.new } - - it "should have access to helper methods defined in the shared example group" do - helper_method.should == "helper method" - end - end - - describe YetAnotherThing do - # ... or you can include the example group like this, which - # feels more like the programming language we love. - include MostThings - - before(:each) { @thing = AnotherThing.new } - - it "should have access to helper methods defined in the shared example group" do - helper_method.should == "helper method" - end - end -end diff --git a/vendor/gems/rspec/examples/pure/shared_stack_examples.rb b/vendor/gems/rspec/examples/pure/shared_stack_examples.rb deleted file mode 100644 index 7a08162508..0000000000 --- a/vendor/gems/rspec/examples/pure/shared_stack_examples.rb +++ /dev/null @@ -1,38 +0,0 @@ -require File.join(File.dirname(__FILE__), *%w[spec_helper]) - -shared_examples_for "non-empty Stack" do - - it { @stack.should_not be_empty } - - it "should return the top item when sent #peek" do - @stack.peek.should == @last_item_added - end - - it "should NOT remove the top item when sent #peek" do - @stack.peek.should == @last_item_added - @stack.peek.should == @last_item_added - end - - it "should return the top item when sent #pop" do - @stack.pop.should == @last_item_added - end - - it "should remove the top item when sent #pop" do - @stack.pop.should == @last_item_added - unless @stack.empty? - @stack.pop.should_not == @last_item_added - end - end - -end - -shared_examples_for "non-full Stack" do - - it { @stack.should_not be_full } - - it "should add to the top when sent #push" do - @stack.push "newly added top item" - @stack.peek.should == "newly added top item" - end - -end \ No newline at end of file diff --git a/vendor/gems/rspec/examples/pure/spec_helper.rb b/vendor/gems/rspec/examples/pure/spec_helper.rb deleted file mode 100644 index 1e880796cb..0000000000 --- a/vendor/gems/rspec/examples/pure/spec_helper.rb +++ /dev/null @@ -1,3 +0,0 @@ -lib_path = File.expand_path("#{File.dirname(__FILE__)}/../../lib") -$LOAD_PATH.unshift lib_path unless $LOAD_PATH.include?(lib_path) -require 'spec' diff --git a/vendor/gems/rspec/examples/pure/stack.rb b/vendor/gems/rspec/examples/pure/stack.rb deleted file mode 100644 index 407173f7b0..0000000000 --- a/vendor/gems/rspec/examples/pure/stack.rb +++ /dev/null @@ -1,36 +0,0 @@ -class StackUnderflowError < RuntimeError -end - -class StackOverflowError < RuntimeError -end - -class Stack - - def initialize - @items = [] - end - - def push object - raise StackOverflowError if @items.length == 10 - @items.push object - end - - def pop - raise StackUnderflowError if @items.empty? - @items.delete @items.last - end - - def peek - raise StackUnderflowError if @items.empty? - @items.last - end - - def empty? - @items.empty? - end - - def full? - @items.length == 10 - end - -end diff --git a/vendor/gems/rspec/examples/pure/stack_spec.rb b/vendor/gems/rspec/examples/pure/stack_spec.rb deleted file mode 100644 index 2a769da00a..0000000000 --- a/vendor/gems/rspec/examples/pure/stack_spec.rb +++ /dev/null @@ -1,63 +0,0 @@ -require File.dirname(__FILE__) + '/spec_helper' -require File.dirname(__FILE__) + "/stack" -require File.dirname(__FILE__) + '/shared_stack_examples' - -describe Stack, " (empty)" do - before(:each) do - @stack = Stack.new - end - - # NOTE that this one auto-generates the description "should be empty" - it { @stack.should be_empty } - - it_should_behave_like "non-full Stack" - - it "should complain when sent #peek" do - lambda { @stack.peek }.should raise_error(StackUnderflowError) - end - - it "should complain when sent #pop" do - lambda { @stack.pop }.should raise_error(StackUnderflowError) - end -end - -describe Stack, " (with one item)" do - before(:each) do - @stack = Stack.new - @stack.push 3 - @last_item_added = 3 - end - - it_should_behave_like "non-empty Stack" - it_should_behave_like "non-full Stack" - -end - -describe Stack, " (with one item less than capacity)" do - before(:each) do - @stack = Stack.new - (1..9).each { |i| @stack.push i } - @last_item_added = 9 - end - - it_should_behave_like "non-empty Stack" - it_should_behave_like "non-full Stack" -end - -describe Stack, " (full)" do - before(:each) do - @stack = Stack.new - (1..10).each { |i| @stack.push i } - @last_item_added = 10 - end - - # NOTE that this one auto-generates the description "should be full" - it { @stack.should be_full } - - it_should_behave_like "non-empty Stack" - - it "should complain on #push" do - lambda { @stack.push Object.new }.should raise_error(StackOverflowError) - end - -end diff --git a/vendor/gems/rspec/examples/pure/stack_spec_with_nested_example_groups.rb b/vendor/gems/rspec/examples/pure/stack_spec_with_nested_example_groups.rb deleted file mode 100644 index 05f6ad4647..0000000000 --- a/vendor/gems/rspec/examples/pure/stack_spec_with_nested_example_groups.rb +++ /dev/null @@ -1,67 +0,0 @@ -require File.dirname(__FILE__) + '/spec_helper' -require File.dirname(__FILE__) + '/stack' -require File.dirname(__FILE__) + '/shared_stack_examples' - -describe Stack do - - before(:each) do - @stack = Stack.new - end - - describe "(empty)" do - - it { @stack.should be_empty } - - it_should_behave_like "non-full Stack" - - it "should complain when sent #peek" do - lambda { @stack.peek }.should raise_error(StackUnderflowError) - end - - it "should complain when sent #pop" do - lambda { @stack.pop }.should raise_error(StackUnderflowError) - end - - end - - describe "(with one item)" do - - before(:each) do - @stack.push 3 - @last_item_added = 3 - end - - it_should_behave_like "non-empty Stack" - it_should_behave_like "non-full Stack" - - end - - describe "(with one item less than capacity)" do - - before(:each) do - (1..9).each { |i| @stack.push i } - @last_item_added = 9 - end - - it_should_behave_like "non-empty Stack" - it_should_behave_like "non-full Stack" - end - - describe "(full)" do - - before(:each) do - (1..10).each { |i| @stack.push i } - @last_item_added = 10 - end - - it { @stack.should be_full } - - it_should_behave_like "non-empty Stack" - - it "should complain on #push" do - lambda { @stack.push Object.new }.should raise_error(StackOverflowError) - end - - end - -end diff --git a/vendor/gems/rspec/examples/pure/stubbing_example.rb b/vendor/gems/rspec/examples/pure/stubbing_example.rb deleted file mode 100644 index 31354aec6a..0000000000 --- a/vendor/gems/rspec/examples/pure/stubbing_example.rb +++ /dev/null @@ -1,69 +0,0 @@ -require File.dirname(__FILE__) + '/spec_helper' - -describe "A consumer of a stub" do - it "should be able to stub methods on any Object" do - obj = Object.new - obj.stub!(:foobar).and_return {:return_value} - obj.foobar.should equal(:return_value) - end -end - -class StubbableClass - def self.find id - return :original_return - end -end - -describe "A stubbed method on a class" do - it "should return the stubbed value" do - StubbableClass.stub!(:find).and_return(:stub_return) - StubbableClass.find(1).should equal(:stub_return) - end - - it "should revert to the original method after each spec" do - StubbableClass.find(1).should equal(:original_return) - end - - it "can stub! and mock the same message" do - StubbableClass.stub!(:msg).and_return(:stub_value) - StubbableClass.should_receive(:msg).with(:arg).and_return(:mock_value) - - StubbableClass.msg.should equal(:stub_value) - StubbableClass.msg(:other_arg).should equal(:stub_value) - StubbableClass.msg(:arg).should equal(:mock_value) - StubbableClass.msg(:another_arg).should equal(:stub_value) - StubbableClass.msg(:yet_another_arg).should equal(:stub_value) - StubbableClass.msg.should equal(:stub_value) - end -end - -describe "A mock" do - it "can stub!" do - mock = mock("stubbing mock") - mock.stub!(:msg).and_return(:value) - (1..10).each {mock.msg.should equal(:value)} - end - - it "can stub! and mock" do - mock = mock("stubbing mock") - mock.stub!(:stub_message).and_return(:stub_value) - mock.should_receive(:mock_message).once.and_return(:mock_value) - (1..10).each {mock.stub_message.should equal(:stub_value)} - mock.mock_message.should equal(:mock_value) - (1..10).each {mock.stub_message.should equal(:stub_value)} - end - - it "can stub! and mock the same message" do - mock = mock("stubbing mock") - mock.stub!(:msg).and_return(:stub_value) - mock.should_receive(:msg).with(:arg).and_return(:mock_value) - mock.msg.should equal(:stub_value) - mock.msg(:other_arg).should equal(:stub_value) - mock.msg(:arg).should equal(:mock_value) - mock.msg(:another_arg).should equal(:stub_value) - mock.msg(:yet_another_arg).should equal(:stub_value) - mock.msg.should equal(:stub_value) - end -end - - diff --git a/vendor/gems/rspec/examples/stories/adder.rb b/vendor/gems/rspec/examples/stories/adder.rb deleted file mode 100644 index 0b027b0ffb..0000000000 --- a/vendor/gems/rspec/examples/stories/adder.rb +++ /dev/null @@ -1,13 +0,0 @@ -class Adder - def initialize - @addends = [] - end - - def <<(val) - @addends << val - end - - def sum - @addends.inject(0) { |sum_so_far, val| sum_so_far + val } - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/examples/stories/addition b/vendor/gems/rspec/examples/stories/addition deleted file mode 100644 index 58f0929900..0000000000 --- a/vendor/gems/rspec/examples/stories/addition +++ /dev/null @@ -1,34 +0,0 @@ -This is a story about a calculator. The text up here above the Story: declaration -won't be processed, so you can write whatever you wish! - -Story: simple addition - - As an accountant - I want to add numbers - So that I can count beans - - Scenario: add one plus one - Given an addend of 1 - And an addend of 1 - - When the addends are addeds - - Then the sum should be 3 - And the corks should be popped - - Scenario: add two plus five - Given an addend of 2 - And an addend of 5 - - When the addends are added - - Then the sum should be 7 - Then it should snow - - Scenario: add three more - GivenScenario add two plus five - And an addend of 3 - - When the addends are added - - Then the sum should be 10 diff --git a/vendor/gems/rspec/examples/stories/addition.rb b/vendor/gems/rspec/examples/stories/addition.rb deleted file mode 100644 index e43f5cf39a..0000000000 --- a/vendor/gems/rspec/examples/stories/addition.rb +++ /dev/null @@ -1,9 +0,0 @@ -require File.join(File.dirname(__FILE__), "helper") -require File.join(File.dirname(__FILE__), "adder") - -# with_steps_for :addition, :more_addition do -with_steps_for :addition, :more_addition do - # Then("the corks should be popped") { } - run File.expand_path(__FILE__).gsub(".rb","") -end - diff --git a/vendor/gems/rspec/examples/stories/calculator.rb b/vendor/gems/rspec/examples/stories/calculator.rb deleted file mode 100644 index 390437c552..0000000000 --- a/vendor/gems/rspec/examples/stories/calculator.rb +++ /dev/null @@ -1,65 +0,0 @@ -$:.push File.join(File.dirname(__FILE__), *%w[.. .. lib]) -require 'spec' - -class AdditionMatchers < Spec::Story::StepGroup - steps do |add| - add.given("an addend of $addend") do |addend| - @adder ||= Adder.new - @adder << addend.to_i - end - end -end - -steps = AdditionMatchers.new do |add| - add.then("the sum should be $sum") do |sum| - @sum.should == sum.to_i - end -end - -steps.when("they are added") do - @sum = @adder.sum -end - -# This Story uses steps (see above) instead of blocks -# passed to Given, When and Then - -Story "addition", %{ - As an accountant - I want to add numbers - So that I can count some beans -}, :steps => steps do - Scenario "2 + 3" do - Given "an addend of 2" - And "an addend of 3" - When "they are added" - Then "the sum should be 5" - end - - # This scenario uses GivenScenario, which silently runs - # all the steps in a previous scenario. - - Scenario "add 4 more" do - GivenScenario "2 + 3" - Given "an addend of 4" - When "they are added" - Then "the sum should be 9" - end -end - -# And the class that makes the story pass - -class Adder - def << addend - addends << addend - end - - def sum - @addends.inject(0) do |result, addend| - result + addend.to_i - end - end - - def addends - @addends ||= [] - end -end diff --git a/vendor/gems/rspec/examples/stories/game-of-life/README.txt b/vendor/gems/rspec/examples/stories/game-of-life/README.txt deleted file mode 100644 index 9624ad4118..0000000000 --- a/vendor/gems/rspec/examples/stories/game-of-life/README.txt +++ /dev/null @@ -1,21 +0,0 @@ -John Conway's Game of Life - -The Rules ---------- -The Game of Life was invented by John Conway (as you might have gathered). -The game is played on a field of cells, each of which has eight neighbors (adjacent cells). -A cell is either occupied (by an organism) or not. -The rules for deriving a generation from the previous one are these: - -Survival --------- -If an occupied cell has 2 or 3 neighbors, the organism survives to the next generation. - -Death ------ -If an occupied cell has 0, 1, 4, 5, 6, 7, or 8 occupied neighbors, the organism dies -(0, 1: of loneliness; 4 thru 8: of overcrowding). - -Birth ------ -If an unoccupied cell has 3 occupied neighbors, it becomes occupied. diff --git a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/everything.rb b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/everything.rb deleted file mode 100644 index 90a281da52..0000000000 --- a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/everything.rb +++ /dev/null @@ -1,6 +0,0 @@ -$:.unshift File.join(File.dirname(__FILE__), '..', '..', '..', '..', 'lib') -$:.unshift File.join(File.dirname(__FILE__), '..') - -require 'spec' -require 'behaviour/examples/examples' -require 'behaviour/stories/stories' diff --git a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/examples/examples.rb b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/examples/examples.rb deleted file mode 100644 index 1cadfb3c1b..0000000000 --- a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/examples/examples.rb +++ /dev/null @@ -1,3 +0,0 @@ -require 'spec' -require 'behaviour/examples/game_behaviour' -require 'behaviour/examples/grid_behaviour' diff --git a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/examples/game_behaviour.rb b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/examples/game_behaviour.rb deleted file mode 100644 index ff5b357f0c..0000000000 --- a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/examples/game_behaviour.rb +++ /dev/null @@ -1,35 +0,0 @@ -require 'life' - -describe Game do - it 'should have a grid' do - # given - game = Game.new(5, 5) - - # then - game.grid.should be_kind_of(Grid) - end - - it 'should create a cell' do - # given - game = Game.new(2, 2) - expected_grid = Grid.from_string( 'X. ..' ) - - # when - game.create_at(0, 0) - - # then - game.grid.should == expected_grid - end - - it 'should destroy a cell' do - # given - game = Game.new(2,2) - game.grid = Grid.from_string('X. ..') - - # when - game.destroy_at(0,0) - - # then - game.grid.should == Grid.from_string('.. ..') - end -end diff --git a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/examples/grid_behaviour.rb b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/examples/grid_behaviour.rb deleted file mode 100644 index 5be3af5199..0000000000 --- a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/examples/grid_behaviour.rb +++ /dev/null @@ -1,66 +0,0 @@ -describe Grid do - it 'should be empty when created' do - # given - expected_contents = [ - [0, 0, 0], - [0, 0, 0] - ] - grid = Grid.new(2, 3) - - # when - contents = grid.contents - - # then - contents.should == expected_contents - end - - it 'should compare equal based on its contents' do - # given - grid1 = Grid.new(2, 3) - grid2 = Grid.new(2, 3) - - # then - grid1.should == grid2 - end - - it 'should be able to replace its contents' do - # given - grid = Grid.new(2,2) - new_contents = [[0,1,0], [1,0,1]] - - # when - grid.contents = new_contents - - # then - grid.contents.should == new_contents - grid.rows.should == 2 - grid.columns.should == 3 - end - - it 'should add an organism' do - # given - grid = Grid.new(2, 2) - expected = Grid.new(2, 2) - expected.contents = [[1,0],[0,0]] - - # when - grid.create_at(0,0) - - # then - grid.should == expected - end - - it 'should create itself from a string' do - # given - expected = Grid.new 3, 3 - expected.create_at(0,0) - expected.create_at(1,0) - expected.create_at(2,2) - - # when - actual = Grid.from_string "X.. X.. ..X" - - # then - actual.should == expected - end -end diff --git a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/CellsWithLessThanTwoNeighboursDie.story b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/CellsWithLessThanTwoNeighboursDie.story deleted file mode 100644 index 8374e86c5e..0000000000 --- a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/CellsWithLessThanTwoNeighboursDie.story +++ /dev/null @@ -1,21 +0,0 @@ -Story: cells with less than two neighbours die - -As a game producer -I want cells with less than two neighbours to die -So that I can illustrate how the game works to people with money - -Scenario: cells with zero or one neighbour die - -Given the grid looks like -........ -.XX.XX.. -.XX..... -....X... -........ -When the next step occurs -Then the grid should look like -........ -.XX..... -.XX..... -........ -........ diff --git a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/CellsWithMoreThanThreeNeighboursDie.story b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/CellsWithMoreThanThreeNeighboursDie.story deleted file mode 100644 index 0d30b59be4..0000000000 --- a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/CellsWithMoreThanThreeNeighboursDie.story +++ /dev/null @@ -1,21 +0,0 @@ -Story: cells with more than three neighbours die - -As a game producer -I want cells with more than three neighbours to die -So that I can show the people with money how we are getting on - -Scenario: blink - -Given the grid looks like -..... -...XX -...XX -.XX.. -.XX.. -When the next step occurs -Then the grid should look like -..... -...XX -....X -.X... -.XX.. diff --git a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/EmptySpacesWithThreeNeighboursCreateACell.story b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/EmptySpacesWithThreeNeighboursCreateACell.story deleted file mode 100644 index cbc248e73a..0000000000 --- a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/EmptySpacesWithThreeNeighboursCreateACell.story +++ /dev/null @@ -1,42 +0,0 @@ -Story: Empty spaces with three neighbours create a cell - -As a game producer -I want empty cells with three neighbours to die -So that I have a minimum feature set to ship - -Scenario: the glider - -Given the grid looks like -...X.. -..X... -..XXX. -...... -...... -When the next step occurs -Then the grid should look like -...... -..X.X. -..XX.. -...X.. -...... -When the next step occurs -Then the grid should look like -...... -..X... -..X.X. -..XX.. -...... -When the next step occurs -Then the grid should look like -...... -...X.. -.XX... -..XX.. -...... -When the next step occurs -Then the grid should look like -...... -..X... -.X.... -.XXX.. -...... diff --git a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/ICanCreateACell.story b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/ICanCreateACell.story deleted file mode 100644 index 88895cb69b..0000000000 --- a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/ICanCreateACell.story +++ /dev/null @@ -1,42 +0,0 @@ -Story: I can create a cell - -As a game producer -I want to create a cell -So that I can show the grid to people - -Scenario: nothing to see here - -Given a 3 x 3 game -Then the grid should look like -... -... -... - -Scenario: all on its lonesome - -Given a 3 x 3 game -When I create a cell at 1, 1 -Then the grid should look like -... -.X. -... - -Scenario: the grid has three cells - -Given a 3 x 3 game -When I create a cell at 0, 0 -and I create a cell at 0, 1 -and I create a cell at 2, 2 -Then the grid should look like -XX. -... -..X - -Scenario: more cells more more - -Given the grid has three cells -When I create a celll at 3, 1 -Then the grid should look like -XX. -..X -..X diff --git a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/ICanKillACell.story b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/ICanKillACell.story deleted file mode 100644 index a9cf1ac64f..0000000000 --- a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/ICanKillACell.story +++ /dev/null @@ -1,17 +0,0 @@ -Story: I can kill a cell - -As a game producer -I want to kill a cell -So that when I make a mistake I dont have to start again - -Scenario: bang youre dead - -Given the grid looks like -XX. -.X. -..X -When I destroy the cell at 0, 1 -Then the grid should look like -X.. -.X. -..X diff --git a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/TheGridWraps.story b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/TheGridWraps.story deleted file mode 100644 index aeeede77d3..0000000000 --- a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/TheGridWraps.story +++ /dev/null @@ -1,53 +0,0 @@ -Story: The grid wraps - -As a game player -I want the grid to wrap -So that untidy stuff at the edges is avoided - -Scenario: crowded in the corners - -Given the grid looks like -X.X -... -X.X -When the next step is taken -Then the grid should look like -X.X -... -X.X - - -Scenario: the glider returns - -Given the glider -...... -..X... -.X.... -.XXX.. -...... -When the next step is taken -and the next step is taken -and the next step is taken -and the next step is taken -Then the grid should look like -...... -...... -.X.... -X..... -XXX... -When the next step is taken -Then the grid should look like -.X.... -...... -...... -X.X... -XX.... -When the next step is taken -Then the grid should look like -XX.... -...... -...... -X..... -X.X... - - diff --git a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/create_a_cell.rb b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/create_a_cell.rb deleted file mode 100644 index 81f86baba3..0000000000 --- a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/create_a_cell.rb +++ /dev/null @@ -1,52 +0,0 @@ -require File.join(File.dirname(__FILE__), *%w[helper]) - -Story "I can create a cell", - %(As a game producer - I want to create a cell - So that I can show the grid to people), :steps_for => :life do - - Scenario "nothing to see here" do - Given "a game with dimensions", 3, 3 do |rows,cols| - @game = Game.new(rows,cols) - end - - Then "the grid should look like", %( - ... - ... - ... - ) - end - - Scenario "all on its lonesome" do - Given "a game with dimensions", 2, 2 - When "I create a cell at", 1, 1 do |row,col| - @game.create_at(row,col) - end - Then "the grid should look like", %( - .. - .X - ) - end - - Scenario "the grid has three cells" do - Given "a game with dimensions", 3, 3 - When "I create a cell at", 0, 0 - When "I create a cell at", 0, 1 - When "I create a cell at", 2, 2 - Then "the grid should look like", %( - XX. - ... - ..X - ) - end - - Scenario "more cells more more" do - GivenScenario "the grid has three cells" - When "I create a cell at", 2, 0 - Then "the grid should look like", %( - XX. - ... - X.X - ) - end -end diff --git a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/helper.rb b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/helper.rb deleted file mode 100644 index 70ed21ec52..0000000000 --- a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/helper.rb +++ /dev/null @@ -1,6 +0,0 @@ -dir = File.dirname(__FILE__) -$LOAD_PATH.unshift(File.expand_path("#{dir}/../../../../../../rspec/lib")) -require 'spec' -$LOAD_PATH.unshift(File.expand_path("#{dir}/../../")) -require "#{dir}/../../life" -require File.join(File.dirname(__FILE__), *%w[steps]) \ No newline at end of file diff --git a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/kill_a_cell.rb b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/kill_a_cell.rb deleted file mode 100644 index 7ae2d912db..0000000000 --- a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/kill_a_cell.rb +++ /dev/null @@ -1,26 +0,0 @@ -require File.join(File.dirname(__FILE__), *%w[helper]) - -Story 'I can kill a cell', - %(As a game producer - I want to kill a cell - So that when I make a mistake I don't have to start again), :steps_for => :life do - - Scenario "bang, you're dead" do - - Given 'a game that looks like', %( - XX. - .X. - ..X - ) do |dots| - @game = Game.from_string dots - end - When 'I destroy the cell at', 0, 1 do |row,col| - @game.destroy_at(row,col) - end - Then 'the grid should look like', %( - X.. - .X. - ..X - ) - end -end diff --git a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/steps.rb b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/steps.rb deleted file mode 100644 index 793590d707..0000000000 --- a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/steps.rb +++ /dev/null @@ -1,5 +0,0 @@ -steps_for :life do - Then "the grid should look like" do |dots| - @game.grid.should == Grid.from_string(dots) - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/stories.rb b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/stories.rb deleted file mode 100644 index e60fe01de4..0000000000 --- a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/stories.rb +++ /dev/null @@ -1,3 +0,0 @@ -require File.join(File.dirname(__FILE__), *%w[helper]) -require 'behaviour/stories/create_a_cell' -require 'behaviour/stories/kill_a_cell' diff --git a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/stories.txt b/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/stories.txt deleted file mode 100644 index d8f809be3c..0000000000 --- a/vendor/gems/rspec/examples/stories/game-of-life/behaviour/stories/stories.txt +++ /dev/null @@ -1,22 +0,0 @@ -Story: Show the game field - As a game player - I want to see the field - so that I can observe the progress of the organisms - -Scenario: an empty field - Given a new game starts - When the game displays the field - Then the field should be empty - - - - - -StoryBuilder story = stories.createStory().called("a story") - .asA("person") - .iWant("to do something") - .soThat("I can rule the world"); -story.addScenario().called("happy path").as() - .given("some context") - .when("some event happens") - .then("expect some outcome"); diff --git a/vendor/gems/rspec/examples/stories/game-of-life/life.rb b/vendor/gems/rspec/examples/stories/game-of-life/life.rb deleted file mode 100644 index 88263bd004..0000000000 --- a/vendor/gems/rspec/examples/stories/game-of-life/life.rb +++ /dev/null @@ -1,3 +0,0 @@ -$: << File.dirname(__FILE__) -require 'life/game' -require 'life/grid' diff --git a/vendor/gems/rspec/examples/stories/game-of-life/life/game.rb b/vendor/gems/rspec/examples/stories/game-of-life/life/game.rb deleted file mode 100644 index 5411b01bf1..0000000000 --- a/vendor/gems/rspec/examples/stories/game-of-life/life/game.rb +++ /dev/null @@ -1,23 +0,0 @@ -class Game - attr_accessor :grid - def initialize(rows,cols) - @grid = Grid.new(rows, cols) - end - - def create_at(row,col) - @grid.create_at(row,col) - end - - def destroy_at(row,col) - @grid.destroy_at(row, col) - end - - def self.from_string(dots) - grid = Grid.from_string(dots) - game = new(grid.rows, grid.columns) - game.instance_eval do - @grid = grid - end - return game - end -end diff --git a/vendor/gems/rspec/examples/stories/game-of-life/life/grid.rb b/vendor/gems/rspec/examples/stories/game-of-life/life/grid.rb deleted file mode 100644 index aca23087c1..0000000000 --- a/vendor/gems/rspec/examples/stories/game-of-life/life/grid.rb +++ /dev/null @@ -1,43 +0,0 @@ -class Grid - - attr_accessor :contents - - def initialize(rows, cols) - @contents = [] - rows.times do @contents << [0] * cols end - end - - def rows - @contents.size - end - - def columns - @contents[0].size - end - - def ==(other) - self.contents == other.contents - end - - def create_at(row,col) - @contents[row][col] = 1 - end - - def destroy_at(row,col) - @contents[row][col] = 0 - end - - def self.from_string(str) - row_strings = str.split(' ') - grid = new(row_strings.size, row_strings[0].size) - - row_strings.each_with_index do |row, row_index| - row_chars = row.split(//) - row_chars.each_with_index do |col_char, col_index| - grid.create_at(row_index, col_index) if col_char == 'X' - end - end - return grid - end - -end diff --git a/vendor/gems/rspec/examples/stories/helper.rb b/vendor/gems/rspec/examples/stories/helper.rb deleted file mode 100644 index 2e825b2785..0000000000 --- a/vendor/gems/rspec/examples/stories/helper.rb +++ /dev/null @@ -1,9 +0,0 @@ -$:.unshift File.join(File.dirname(__FILE__), '..', '..', 'lib') -require 'spec/story' - -# won't have to do this once plain_text_story_runner is moved into the library -# require File.join(File.dirname(__FILE__), "plain_text_story_runner") - -Dir[File.join(File.dirname(__FILE__), "steps/*.rb")].each do |file| - require file -end \ No newline at end of file diff --git a/vendor/gems/rspec/examples/stories/steps/addition_steps.rb b/vendor/gems/rspec/examples/stories/steps/addition_steps.rb deleted file mode 100644 index 3f27095a9f..0000000000 --- a/vendor/gems/rspec/examples/stories/steps/addition_steps.rb +++ /dev/null @@ -1,18 +0,0 @@ -require File.expand_path("#{File.dirname(__FILE__)}/../helper") - -# This creates steps for :addition -steps_for(:addition) do - Given("an addend of $addend") do |addend| - @adder ||= Adder.new - @adder << addend.to_i - end -end - -# This appends to them -steps_for(:addition) do - When("the addends are added") { @sum = @adder.sum } -end - -steps_for(:more_addition) do - Then("the sum should be $sum") { |sum| @sum.should == sum.to_i } -end diff --git a/vendor/gems/rspec/failing_examples/README.txt b/vendor/gems/rspec/failing_examples/README.txt deleted file mode 100644 index 38c667d929..0000000000 --- a/vendor/gems/rspec/failing_examples/README.txt +++ /dev/null @@ -1,7 +0,0 @@ -"Why have failing examples?", you might ask. - -They allow us to see failure messages. RSpec wants to provide meaningful and helpful failure messages. The failures in this directory not only provide you a way of seeing the failure messages, but they provide RSpec's own specs a way of describing what they should look like and ensuring they stay correct. - -To see the types of messages you can expect, stand in this directory and run: - -../bin/spec ./*.rb \ No newline at end of file diff --git a/vendor/gems/rspec/failing_examples/diffing_spec.rb b/vendor/gems/rspec/failing_examples/diffing_spec.rb deleted file mode 100644 index 85e13e8c04..0000000000 --- a/vendor/gems/rspec/failing_examples/diffing_spec.rb +++ /dev/null @@ -1,36 +0,0 @@ -describe "Running specs with --diff" do - it "should print diff of different strings" do - uk = <<-EOF -RSpec is a -behaviour driven development -framework for Ruby -EOF - usa = <<-EOF -RSpec is a -behavior driven development -framework for Ruby -EOF - usa.should == uk - end - - class Animal - def initialize(name,species) - @name,@species = name,species - end - - def inspect - <<-EOA - - EOA - end - end - - it "should print diff of different objects' pretty representation" do - expected = Animal.new "bob", "giraffe" - actual = Animal.new "bob", "tortoise" - expected.should eql(actual) - end -end diff --git a/vendor/gems/rspec/failing_examples/failing_autogenerated_docstrings_example.rb b/vendor/gems/rspec/failing_examples/failing_autogenerated_docstrings_example.rb deleted file mode 100644 index 8a7d2490ed..0000000000 --- a/vendor/gems/rspec/failing_examples/failing_autogenerated_docstrings_example.rb +++ /dev/null @@ -1,19 +0,0 @@ -require File.dirname(__FILE__) + '/spec_helper' - -# Run spec w/ -fs to see the output of this file - -describe "Failing examples with no descriptions" do - - # description is auto-generated as "should equal(5)" based on the last #should - it do - 3.should equal(2) - 5.should equal(5) - end - - it { 3.should be > 5 } - - it { ["a"].should include("b") } - - it { [1,2,3].should_not respond_to(:size) } - -end diff --git a/vendor/gems/rspec/failing_examples/failure_in_setup.rb b/vendor/gems/rspec/failing_examples/failure_in_setup.rb deleted file mode 100644 index 2a807a99a3..0000000000 --- a/vendor/gems/rspec/failing_examples/failure_in_setup.rb +++ /dev/null @@ -1,10 +0,0 @@ -describe "This example" do - - before(:each) do - NonExistentClass.new - end - - it "should be listed as failing in setup" do - end - -end diff --git a/vendor/gems/rspec/failing_examples/failure_in_teardown.rb b/vendor/gems/rspec/failing_examples/failure_in_teardown.rb deleted file mode 100644 index 6458ea2b89..0000000000 --- a/vendor/gems/rspec/failing_examples/failure_in_teardown.rb +++ /dev/null @@ -1,10 +0,0 @@ -describe "This example" do - - it "should be listed as failing in teardown" do - end - - after(:each) do - NonExistentClass.new - end - -end diff --git a/vendor/gems/rspec/failing_examples/mocking_example.rb b/vendor/gems/rspec/failing_examples/mocking_example.rb deleted file mode 100644 index caf2db0364..0000000000 --- a/vendor/gems/rspec/failing_examples/mocking_example.rb +++ /dev/null @@ -1,40 +0,0 @@ -require File.dirname(__FILE__) + '/spec_helper' - -describe "Mocker" do - - it "should be able to call mock()" do - mock = mock("poke me") - mock.should_receive(:poke) - mock.poke - end - - it "should fail when expected message not received" do - mock = mock("poke me") - mock.should_receive(:poke) - end - - it "should fail when messages are received out of order" do - mock = mock("one two three") - mock.should_receive(:one).ordered - mock.should_receive(:two).ordered - mock.should_receive(:three).ordered - mock.one - mock.three - mock.two - end - - it "should get yelled at when sending unexpected messages" do - mock = mock("don't talk to me") - mock.should_not_receive(:any_message_at_all) - mock.any_message_at_all - end - - it "has a bug we need to fix" do - pending "here is the bug" do - # Actually, no. It's fixed. This will fail because it passes :-) - mock = mock("Bug") - mock.should_receive(:hello) - mock.hello - end - end -end diff --git a/vendor/gems/rspec/failing_examples/mocking_with_flexmock.rb b/vendor/gems/rspec/failing_examples/mocking_with_flexmock.rb deleted file mode 100644 index 6e79ece0ee..0000000000 --- a/vendor/gems/rspec/failing_examples/mocking_with_flexmock.rb +++ /dev/null @@ -1,26 +0,0 @@ -# stub frameworks like to gum up Object, so this is deliberately -# set NOT to run so that you don't accidentally run it when you -# run this dir. - -# To run it, stand in this directory and say: -# -# RUN_FLEXMOCK_EXAMPLE=true ruby ../bin/spec mocking_with_flexmock.rb - -if ENV['RUN_FLEXMOCK_EXAMPLE'] - Spec::Runner.configure do |config| - config.mock_with :flexmock - end - - describe "Flexmocks" do - it "should fail when the expected message is received with wrong arguments" do - m = flexmock("now flex!") - m.should_receive(:msg).with("arg").once - m.msg("other arg") - end - - it "should fail when the expected message is not received at all" do - m = flexmock("now flex!") - m.should_receive(:msg).with("arg").once - end - end -end diff --git a/vendor/gems/rspec/failing_examples/mocking_with_mocha.rb b/vendor/gems/rspec/failing_examples/mocking_with_mocha.rb deleted file mode 100644 index f14a1a3e5c..0000000000 --- a/vendor/gems/rspec/failing_examples/mocking_with_mocha.rb +++ /dev/null @@ -1,25 +0,0 @@ -# stub frameworks like to gum up Object, so this is deliberately -# set NOT to run so that you don't accidentally run it when you -# run this dir. - -# To run it, stand in this directory and say: -# -# RUN_MOCHA_EXAMPLE=true ruby ../bin/spec mocking_with_mocha.rb - -if ENV['RUN_MOCHA_EXAMPLE'] - Spec::Runner.configure do |config| - config.mock_with :mocha - end - describe "Mocha framework" do - it "should should be made available by saying config.mock_with :mocha" do - m = mock() - m.expects(:msg).with("arg") - m.msg - end - it "should should be made available by saying config.mock_with :mocha" do - o = Object.new - o.expects(:msg).with("arg") - o.msg - end - end -end diff --git a/vendor/gems/rspec/failing_examples/mocking_with_rr.rb b/vendor/gems/rspec/failing_examples/mocking_with_rr.rb deleted file mode 100644 index 0d2b4fe044..0000000000 --- a/vendor/gems/rspec/failing_examples/mocking_with_rr.rb +++ /dev/null @@ -1,27 +0,0 @@ -# stub frameworks like to gum up Object, so this is deliberately -# set NOT to run so that you don't accidentally run it when you -# run this dir. - -# To run it, stand in this directory and say: -# -# RUN_RR_EXAMPLE=true ruby ../bin/spec mocking_with_rr.rb - -if ENV['RUN_RR_EXAMPLE'] - Spec::Runner.configure do |config| - config.mock_with :rr - end - describe "RR framework" do - it "should should be made available by saying config.mock_with :rr" do - o = Object.new - mock(o).msg("arg") - o.msg - end - it "should should be made available by saying config.mock_with :rr" do - o = Object.new - mock(o) do |m| - m.msg("arg") - end - o.msg - end - end -end diff --git a/vendor/gems/rspec/failing_examples/partial_mock_example.rb b/vendor/gems/rspec/failing_examples/partial_mock_example.rb deleted file mode 100644 index 6d05540556..0000000000 --- a/vendor/gems/rspec/failing_examples/partial_mock_example.rb +++ /dev/null @@ -1,20 +0,0 @@ -require File.dirname(__FILE__) + '/spec_helper' - -class MockableClass - def self.find id - return :original_return - end -end - -describe "A partial mock" do - - it "should work at the class level (but fail here due to the type mismatch)" do - MockableClass.should_receive(:find).with(1).and_return {:stub_return} - MockableClass.find("1").should equal(:stub_return) - end - - it "should revert to the original after each spec" do - MockableClass.find(1).should equal(:original_return) - end - -end diff --git a/vendor/gems/rspec/failing_examples/predicate_example.rb b/vendor/gems/rspec/failing_examples/predicate_example.rb deleted file mode 100644 index 53b6367e2d..0000000000 --- a/vendor/gems/rspec/failing_examples/predicate_example.rb +++ /dev/null @@ -1,29 +0,0 @@ -require File.dirname(__FILE__) + '/spec_helper' - -class BddFramework - def intuitive? - true - end - - def adopted_quickly? - #this will cause failures because it reallly SHOULD be adopted quickly - false - end -end - -describe "BDD framework" do - - before(:each) do - @bdd_framework = BddFramework.new - end - - it "should be adopted quickly" do - #this will fail because it reallly SHOULD be adopted quickly - @bdd_framework.should be_adopted_quickly - end - - it "should be intuitive" do - @bdd_framework.should be_intuitive - end - -end diff --git a/vendor/gems/rspec/failing_examples/raising_example.rb b/vendor/gems/rspec/failing_examples/raising_example.rb deleted file mode 100644 index e40b51ec8f..0000000000 --- a/vendor/gems/rspec/failing_examples/raising_example.rb +++ /dev/null @@ -1,47 +0,0 @@ -describe "This example" do - - it "should show that a NoMethodError is raised but an Exception was expected" do - proc { ''.nonexistent_method }.should raise_error - end - - it "should pass" do - proc { ''.nonexistent_method }.should raise_error(NoMethodError) - end - - it "should show that a NoMethodError is raised but a SyntaxError was expected" do - proc { ''.nonexistent_method }.should raise_error(SyntaxError) - end - - it "should show that nothing is raised when SyntaxError was expected" do - proc { }.should raise_error(SyntaxError) - end - - it "should show that a NoMethodError is raised but a Exception was expected" do - proc { ''.nonexistent_method }.should_not raise_error - end - - it "should show that a NoMethodError is raised" do - proc { ''.nonexistent_method }.should_not raise_error(NoMethodError) - end - - it "should also pass" do - proc { ''.nonexistent_method }.should_not raise_error(SyntaxError) - end - - it "should show that a NoMethodError is raised when nothing expected" do - proc { ''.nonexistent_method }.should_not raise_error(Exception) - end - - it "should show that the wrong message was received" do - proc { raise StandardError.new("what is an enterprise?") }.should raise_error(StandardError, "not this") - end - - it "should show that the unexpected error/message was thrown" do - proc { raise StandardError.new("abc") }.should_not raise_error(StandardError, "abc") - end - - it "should pass too" do - proc { raise StandardError.new("abc") }.should_not raise_error(StandardError, "xyz") - end - -end diff --git a/vendor/gems/rspec/failing_examples/spec_helper.rb b/vendor/gems/rspec/failing_examples/spec_helper.rb deleted file mode 100644 index f8d657554f..0000000000 --- a/vendor/gems/rspec/failing_examples/spec_helper.rb +++ /dev/null @@ -1,3 +0,0 @@ -lib_path = File.expand_path("#{File.dirname(__FILE__)}/../lib") -$LOAD_PATH.unshift lib_path unless $LOAD_PATH.include?(lib_path) -require "spec" diff --git a/vendor/gems/rspec/failing_examples/syntax_error_example.rb b/vendor/gems/rspec/failing_examples/syntax_error_example.rb deleted file mode 100644 index c9bb907742..0000000000 --- a/vendor/gems/rspec/failing_examples/syntax_error_example.rb +++ /dev/null @@ -1,7 +0,0 @@ -describe "when passing a block to a matcher" do - it "you should use {} instead of do/end" do - Object.new.should satisfy do - "this block is being passed to #should instead of #satisfy - use {} instead" - end - end -end diff --git a/vendor/gems/rspec/failing_examples/team_spec.rb b/vendor/gems/rspec/failing_examples/team_spec.rb deleted file mode 100644 index 41a44e5518..0000000000 --- a/vendor/gems/rspec/failing_examples/team_spec.rb +++ /dev/null @@ -1,44 +0,0 @@ -require File.dirname(__FILE__) + '/spec_helper' - - -class Team - attr_reader :players - def initialize - @players = Players.new - end -end - -class Players - def initialize - @players = [] - end - def size - @players.size - end - def include? player - raise "player must be a string" unless player.is_a?(String) - @players.include? player - end -end - -describe "A new team" do - - before(:each) do - @team = Team.new - end - - it "should have 3 players (failing example)" do - @team.should have(3).players - end - - it "should include some player (failing example)" do - @team.players.should include("Some Player") - end - - it "should include 5 (failing example)" do - @team.players.should include(5) - end - - it "should have no players" - -end diff --git a/vendor/gems/rspec/failing_examples/timeout_behaviour.rb b/vendor/gems/rspec/failing_examples/timeout_behaviour.rb deleted file mode 100644 index 18221365fd..0000000000 --- a/vendor/gems/rspec/failing_examples/timeout_behaviour.rb +++ /dev/null @@ -1,7 +0,0 @@ -require File.dirname(__FILE__) + '/spec_helper' - -describe "Something really slow" do - it "should be failed by RSpec when it takes longer than --timeout" do - sleep(2) - end -end diff --git a/vendor/gems/rspec/lib/autotest/discover.rb b/vendor/gems/rspec/lib/autotest/discover.rb deleted file mode 100644 index 81914c3b7a..0000000000 --- a/vendor/gems/rspec/lib/autotest/discover.rb +++ /dev/null @@ -1,3 +0,0 @@ -Autotest.add_discovery do - "rspec" if File.exist?('spec') -end diff --git a/vendor/gems/rspec/lib/autotest/rspec.rb b/vendor/gems/rspec/lib/autotest/rspec.rb deleted file mode 100644 index 9c97d2e0d9..0000000000 --- a/vendor/gems/rspec/lib/autotest/rspec.rb +++ /dev/null @@ -1,74 +0,0 @@ -require 'autotest' - -Autotest.add_hook :initialize do |at| - at.clear_mappings - # watch out: Ruby bug (1.8.6): - # %r(/) != /\// - at.add_mapping(%r%^spec/.*\.rb$%) { |filename, _| - filename - } - at.add_mapping(%r%^lib/(.*)\.rb$%) { |_, m| - ["spec/#{m[1]}_spec.rb"] - } - at.add_mapping(%r%^spec/(spec_helper|shared/.*)\.rb$%) { - at.files_matching %r%^spec/.*_spec\.rb$% - } -end - -class RspecCommandError < StandardError; end - -class Autotest::Rspec < Autotest - - def initialize - super - - self.failed_results_re = /^\d+\)\n(?:\e\[\d*m)?(?:.*?Error in )?'([^\n]*)'(?: FAILED)?(?:\e\[\d*m)?\n(.*?)\n\n/m - self.completed_re = /\Z/ # FIX: some sort of summary line at the end? - end - - def consolidate_failures(failed) - filters = Hash.new { |h,k| h[k] = [] } - failed.each do |spec, failed_trace| - if f = test_files_for(failed).find { |f| failed_trace =~ Regexp.new(f) } then - filters[f] << spec - break - end - end - return filters - end - - def make_test_cmd(files_to_test) - return "#{ruby} -S #{spec_command} #{add_options_if_present} #{files_to_test.keys.flatten.join(' ')}" - end - - def add_options_if_present - File.exist?("spec/spec.opts") ? "-O spec/spec.opts " : "" - end - - # Finds the proper spec command to use. Precendence is set in the - # lazily-evaluated method spec_commands. Alias + Override that in - # ~/.autotest to provide a different spec command then the default - # paths provided. - def spec_command(separator=File::ALT_SEPARATOR) - unless defined? @spec_command then - @spec_command = spec_commands.find { |cmd| File.exists? cmd } - - raise RspecCommandError, "No spec command could be found!" unless @spec_command - - @spec_command.gsub! File::SEPARATOR, separator if separator - end - @spec_command - end - - # Autotest will look for spec commands in the following - # locations, in this order: - # - # * bin/spec - # * default spec bin/loader installed in Rubygems - def spec_commands - [ - File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'bin', 'spec')), - File.join(Config::CONFIG['bindir'], 'spec') - ] - end -end diff --git a/vendor/gems/rspec/lib/spec.rb b/vendor/gems/rspec/lib/spec.rb deleted file mode 100644 index c143aa885c..0000000000 --- a/vendor/gems/rspec/lib/spec.rb +++ /dev/null @@ -1,30 +0,0 @@ -require 'spec/version' -require 'spec/matchers' -require 'spec/expectations' -require 'spec/example' -require 'spec/extensions' -require 'spec/runner' - -if Object.const_defined?(:Test); \ - require 'spec/interop/test'; \ -end - -module Spec - class << self - def run? - @run || rspec_options.examples_run? - end - - def run; \ - return true if run?; \ - result = rspec_options.run_examples; \ - @run = true; \ - result; \ - end - attr_writer :run - - def exit?; \ - !Object.const_defined?(:Test) || Test::Unit.run?; \ - end - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/lib/spec/example.rb b/vendor/gems/rspec/lib/spec/example.rb deleted file mode 100644 index 39ff76b99e..0000000000 --- a/vendor/gems/rspec/lib/spec/example.rb +++ /dev/null @@ -1,12 +0,0 @@ -require 'timeout' -require 'forwardable' -require 'spec/example/pending' -require 'spec/example/module_reopening_fix' -require 'spec/example/example_group_methods' -require 'spec/example/example_methods' -require 'spec/example/example_group' -require 'spec/example/shared_example_group' -require 'spec/example/example_group_factory' -require 'spec/example/errors' -require 'spec/example/configuration' -require 'spec/example/example_matcher' diff --git a/vendor/gems/rspec/lib/spec/example/configuration.rb b/vendor/gems/rspec/lib/spec/example/configuration.rb deleted file mode 100755 index 6741847270..0000000000 --- a/vendor/gems/rspec/lib/spec/example/configuration.rb +++ /dev/null @@ -1,144 +0,0 @@ -module Spec - module Example - class Configuration - # Chooses what mock framework to use. Example: - # - # Spec::Runner.configure do |config| - # config.mock_with :rspec, :mocha, :flexmock, or :rr - # end - # - # To use any other mock framework, you'll have to provide - # your own adapter. This is simply a module that responds to - # setup_mocks_for_rspec, verify_mocks_for_rspec and teardown_mocks_for_rspec. - # These are your hooks into the lifecycle of a given example. RSpec will - # call setup_mocks_for_rspec before running anything else in each Example. - # After executing the #after methods, RSpec will then call verify_mocks_for_rspec - # and teardown_mocks_for_rspec (this is guaranteed to run even if there are - # failures in verify_mocks_for_rspec). - # - # Once you've defined this module, you can pass that to mock_with: - # - # Spec::Runner.configure do |config| - # config.mock_with MyMockFrameworkAdapter - # end - # - def mock_with(mock_framework) - @mock_framework = case mock_framework - when Symbol - mock_framework_path(mock_framework.to_s) - else - mock_framework - end - end - - def mock_framework # :nodoc: - @mock_framework ||= mock_framework_path("rspec") - end - - # Declares modules to be included in all example groups (describe blocks). - # - # config.include(My::Bottle, My::Cup) - # - # If you want to restrict the inclusion to a subset of all the example groups then - # specify this in a Hash as the last argument: - # - # config.include(My::Pony, My::Horse, :type => :farm) - # - # Only example groups that have that type will get the modules included: - # - # describe "Downtown", :type => :city do - # # Will *not* get My::Pony and My::Horse included - # end - # - # describe "Old Mac Donald", :type => :farm do - # # *Will* get My::Pony and My::Horse included - # end - # - def include(*args) - args << {} unless Hash === args.last - modules, options = args_and_options(*args) - required_example_group = get_type_from_options(options) - required_example_group = required_example_group.to_sym if required_example_group - modules.each do |mod| - ExampleGroupFactory.get(required_example_group).send(:include, mod) - end - end - - # Defines global predicate matchers. Example: - # - # config.predicate_matchers[:swim] = :can_swim? - # - # This makes it possible to say: - # - # person.should swim # passes if person.should_swim? returns true - # - def predicate_matchers - @predicate_matchers ||= {} - end - - # Prepends a global before block to all example groups. - # See #append_before for filtering semantics. - def prepend_before(*args, &proc) - scope, options = scope_and_options(*args) - example_group = ExampleGroupFactory.get( - get_type_from_options(options) - ) - example_group.prepend_before(scope, &proc) - end - # Appends a global before block to all example groups. - # - # If you want to restrict the block to a subset of all the example groups then - # specify this in a Hash as the last argument: - # - # config.prepend_before(:all, :type => :farm) - # - # or - # - # config.prepend_before(:type => :farm) - # - def append_before(*args, &proc) - scope, options = scope_and_options(*args) - example_group = ExampleGroupFactory.get( - get_type_from_options(options) - ) - example_group.append_before(scope, &proc) - end - alias_method :before, :append_before - - # Prepends a global after block to all example groups. - # See #append_before for filtering semantics. - def prepend_after(*args, &proc) - scope, options = scope_and_options(*args) - example_group = ExampleGroupFactory.get( - get_type_from_options(options) - ) - example_group.prepend_after(scope, &proc) - end - alias_method :after, :prepend_after - # Appends a global after block to all example groups. - # See #append_before for filtering semantics. - def append_after(*args, &proc) - scope, options = scope_and_options(*args) - example_group = ExampleGroupFactory.get( - get_type_from_options(options) - ) - example_group.append_after(scope, &proc) - end - - private - - def scope_and_options(*args) - args, options = args_and_options(*args) - scope = (args[0] || :each), options - end - - def get_type_from_options(options) - options[:type] || options[:behaviour_type] - end - - def mock_framework_path(framework_name) - File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "plugins", "mock_frameworks", framework_name)) - end - end - end -end diff --git a/vendor/gems/rspec/lib/spec/example/errors.rb b/vendor/gems/rspec/lib/spec/example/errors.rb deleted file mode 100644 index c6cb22453f..0000000000 --- a/vendor/gems/rspec/lib/spec/example/errors.rb +++ /dev/null @@ -1,9 +0,0 @@ -module Spec - module Example - class ExamplePendingError < StandardError - end - - class PendingExampleFixedError < StandardError - end - end -end diff --git a/vendor/gems/rspec/lib/spec/example/example_group.rb b/vendor/gems/rspec/lib/spec/example/example_group.rb deleted file mode 100644 index d6e156f934..0000000000 --- a/vendor/gems/rspec/lib/spec/example/example_group.rb +++ /dev/null @@ -1,16 +0,0 @@ -module Spec - module Example - # The superclass for all regular RSpec examples. - class ExampleGroup - extend Spec::Example::ExampleGroupMethods - include Spec::Example::ExampleMethods - - def initialize(defined_description, &implementation) - @_defined_description = defined_description - @_implementation = implementation - end - end - end -end - -Spec::ExampleGroup = Spec::Example::ExampleGroup diff --git a/vendor/gems/rspec/lib/spec/example/example_group_factory.rb b/vendor/gems/rspec/lib/spec/example/example_group_factory.rb deleted file mode 100755 index 0414a3b967..0000000000 --- a/vendor/gems/rspec/lib/spec/example/example_group_factory.rb +++ /dev/null @@ -1,62 +0,0 @@ -module Spec - module Example - class ExampleGroupFactory - class << self - def reset - @example_group_types = nil - default(ExampleGroup) - end - - # Registers an example group class +klass+ with the symbol - # +type+. For example: - # - # Spec::Example::ExampleGroupFactory.register(:farm, Spec::Farm::Example::FarmExampleGroup) - # - # This will cause Main#describe from a file living in - # spec/farm to create example group instances of type - # Spec::Farm::Example::FarmExampleGroup. - def register(id, example_group_class) - @example_group_types[id] = example_group_class - end - - # Sets the default ExampleGroup class - def default(example_group_class) - old = @example_group_types - @example_group_types = Hash.new(example_group_class) - @example_group_types.merge(old) if old - end - - def get(id=nil) - if @example_group_types.values.include?(id) - id - else - @example_group_types[id] - end - end - - def create_example_group(*args, &block) - opts = Hash === args.last ? args.last : {} - if opts[:shared] - SharedExampleGroup.new(*args, &block) - else - superclass = determine_superclass(opts) - superclass.describe(*args, &block) - end - end - - protected - - def determine_superclass(opts) - id = if opts[:type] - opts[:type] - elsif opts[:spec_path] =~ /spec(\\|\/)(#{@example_group_types.keys.join('|')})/ - $2 == '' ? nil : $2.to_sym - end - get(id) - end - - end - self.reset - end - end -end diff --git a/vendor/gems/rspec/lib/spec/example/example_group_methods.rb b/vendor/gems/rspec/lib/spec/example/example_group_methods.rb deleted file mode 100644 index a348bc74ba..0000000000 --- a/vendor/gems/rspec/lib/spec/example/example_group_methods.rb +++ /dev/null @@ -1,424 +0,0 @@ -module Spec - module Example - - module ExampleGroupMethods - class << self - def description_text(*args) - args.inject("") do |result, arg| - result << " " unless (result == "" || arg.to_s =~ /^(\s|\.|#)/) - result << arg.to_s - end - end - end - - attr_reader :description_text, :description_args, :description_options, :spec_path, :registration_binding_block - - def inherited(klass) - super - klass.register {} - Spec::Runner.register_at_exit_hook - end - - # Makes the describe/it syntax available from a class. For example: - # - # class StackSpec < Spec::ExampleGroup - # describe Stack, "with no elements" - # - # before - # @stack = Stack.new - # end - # - # it "should raise on pop" do - # lambda{ @stack.pop }.should raise_error - # end - # end - # - def describe(*args, &example_group_block) - if example_group_block - self.subclass("Subclass") do - describe(*args) - module_eval(&example_group_block) - end - else - set_description(*args) - before_eval - self - end - end - - # Use this to pull in examples from shared example groups. - # See Spec::Runner for information about shared example groups. - def it_should_behave_like(shared_example_group) - case shared_example_group - when SharedExampleGroup - include shared_example_group - else - example_group = SharedExampleGroup.find_shared_example_group(shared_example_group) - unless example_group - raise RuntimeError.new("Shared Example Group '#{shared_example_group}' can not be found") - end - include(example_group) - end - end - - # :call-seq: - # predicate_matchers[matcher_name] = method_on_object - # predicate_matchers[matcher_name] = [method1_on_object, method2_on_object] - # - # Dynamically generates a custom matcher that will match - # a predicate on your class. RSpec provides a couple of these - # out of the box: - # - # exist (or state expectations) - # File.should exist("path/to/file") - # - # an_instance_of (for mock argument constraints) - # mock.should_receive(:message).with(an_instance_of(String)) - # - # == Examples - # - # class Fish - # def can_swim? - # true - # end - # end - # - # describe Fish do - # predicate_matchers[:swim] = :can_swim? - # it "should swim" do - # Fish.new.should swim - # end - # end - def predicate_matchers - @predicate_matchers ||= {:an_instance_of => :is_a?} - end - - # Creates an instance of Spec::Example::Example and adds - # it to a collection of examples of the current example group. - def it(description=nil, &implementation) - e = new(description, &implementation) - example_objects << e - e - end - - alias_method :specify, :it - - # Use this to temporarily disable an example. - def xit(description=nil, opts={}, &block) - Kernel.warn("Example disabled: #{description}") - end - - def run - examples = examples_to_run - return true if examples.empty? - reporter.add_example_group(self) - return dry_run(examples) if dry_run? - - plugin_mock_framework - define_methods_from_predicate_matchers - - success, before_all_instance_variables = run_before_all - success, after_all_instance_variables = execute_examples(success, before_all_instance_variables, examples) - success = run_after_all(success, after_all_instance_variables) - end - - def description - result = ExampleGroupMethods.description_text(*description_parts) - if result.nil? || result == "" - return to_s - else - result - end - end - - def described_type - description_parts.find {|part| part.is_a?(Module)} - end - - def description_parts #:nodoc: - parts = [] - execute_in_class_hierarchy do |example_group| - parts << example_group.description_args - end - parts.flatten.compact - end - - def set_description(*args) - args, options = args_and_options(*args) - @description_args = args - @description_options = options - @description_text = ExampleGroupMethods.description_text(*args) - @spec_path = File.expand_path(options[:spec_path]) if options[:spec_path] - if described_type.class == Module - include described_type - end - self - end - - def examples #:nodoc: - examples = example_objects.dup - add_method_examples(examples) - rspec_options.reverse ? examples.reverse : examples - end - - def number_of_examples #:nodoc: - examples.length - end - - # Registers a block to be executed before each example. - # This method prepends +block+ to existing before blocks. - def prepend_before(*args, &block) - scope, options = scope_and_options(*args) - parts = before_parts_from_scope(scope) - parts.unshift(block) - end - - # Registers a block to be executed before each example. - # This method appends +block+ to existing before blocks. - def append_before(*args, &block) - scope, options = scope_and_options(*args) - parts = before_parts_from_scope(scope) - parts << block - end - alias_method :before, :append_before - - # Registers a block to be executed after each example. - # This method prepends +block+ to existing after blocks. - def prepend_after(*args, &block) - scope, options = scope_and_options(*args) - parts = after_parts_from_scope(scope) - parts.unshift(block) - end - alias_method :after, :prepend_after - - # Registers a block to be executed after each example. - # This method appends +block+ to existing after blocks. - def append_after(*args, &block) - scope, options = scope_and_options(*args) - parts = after_parts_from_scope(scope) - parts << block - end - - def remove_after(scope, &block) - after_each_parts.delete(block) - end - - # Deprecated. Use before(:each) - def setup(&block) - before(:each, &block) - end - - # Deprecated. Use after(:each) - def teardown(&block) - after(:each, &block) - end - - def before_all_parts # :nodoc: - @before_all_parts ||= [] - end - - def after_all_parts # :nodoc: - @after_all_parts ||= [] - end - - def before_each_parts # :nodoc: - @before_each_parts ||= [] - end - - def after_each_parts # :nodoc: - @after_each_parts ||= [] - end - - # Only used from RSpec's own examples - def reset # :nodoc: - @before_all_parts = nil - @after_all_parts = nil - @before_each_parts = nil - @after_each_parts = nil - end - - def register(®istration_binding_block) - @registration_binding_block = registration_binding_block - rspec_options.add_example_group self - end - - def unregister #:nodoc: - rspec_options.remove_example_group self - end - - def registration_backtrace - eval("caller", registration_binding_block.binding) - end - - def run_before_each(example) - execute_in_class_hierarchy do |example_group| - example.eval_each_fail_fast(example_group.before_each_parts) - end - end - - def run_after_each(example) - execute_in_class_hierarchy(:superclass_first) do |example_group| - example.eval_each_fail_slow(example_group.after_each_parts) - end - end - - private - def dry_run(examples) - examples.each do |example| - rspec_options.reporter.example_started(example) - rspec_options.reporter.example_finished(example) - end - return true - end - - def run_before_all - before_all = new("before(:all)") - begin - execute_in_class_hierarchy do |example_group| - before_all.eval_each_fail_fast(example_group.before_all_parts) - end - return [true, before_all.instance_variable_hash] - rescue Exception => e - reporter.failure(before_all, e) - return [false, before_all.instance_variable_hash] - end - end - - def execute_examples(success, instance_variables, examples) - return [success, instance_variables] unless success - - after_all_instance_variables = instance_variables - examples.each do |example_group_instance| - success &= example_group_instance.execute(rspec_options, instance_variables) - after_all_instance_variables = example_group_instance.instance_variable_hash - end - return [success, after_all_instance_variables] - end - - def run_after_all(success, instance_variables) - after_all = new("after(:all)") - after_all.set_instance_variables_from_hash(instance_variables) - execute_in_class_hierarchy(:superclass_first) do |example_group| - after_all.eval_each_fail_slow(example_group.after_all_parts) - end - return success - rescue Exception => e - reporter.failure(after_all, e) - return false - end - - def examples_to_run - all_examples = examples - return all_examples unless specified_examples? - all_examples.reject do |example| - matcher = ExampleMatcher.new(description.to_s, example.description) - !matcher.matches?(specified_examples) - end - end - - def specified_examples? - specified_examples && !specified_examples.empty? - end - - def specified_examples - rspec_options.examples - end - - def reporter - rspec_options.reporter - end - - def dry_run? - rspec_options.dry_run - end - - def example_objects - @example_objects ||= [] - end - - def execute_in_class_hierarchy(superclass_last=false) - classes = [] - current_class = self - while is_example_group?(current_class) - superclass_last ? classes << current_class : classes.unshift(current_class) - current_class = current_class.superclass - end - superclass_last ? classes << ExampleMethods : classes.unshift(ExampleMethods) - - classes.each do |example_group| - yield example_group - end - end - - def is_example_group?(klass) - Module === klass && klass.kind_of?(ExampleGroupMethods) - end - - def plugin_mock_framework - case mock_framework = Spec::Runner.configuration.mock_framework - when Module - include mock_framework - else - require Spec::Runner.configuration.mock_framework - include Spec::Plugins::MockFramework - end - end - - def define_methods_from_predicate_matchers # :nodoc: - all_predicate_matchers = predicate_matchers.merge( - Spec::Runner.configuration.predicate_matchers - ) - all_predicate_matchers.each_pair do |matcher_method, method_on_object| - define_method matcher_method do |*args| - eval("be_#{method_on_object.to_s.gsub('?','')}(*args)") - end - end - end - - def scope_and_options(*args) - args, options = args_and_options(*args) - scope = (args[0] || :each), options - end - - def before_parts_from_scope(scope) - case scope - when :each; before_each_parts - when :all; before_all_parts - end - end - - def after_parts_from_scope(scope) - case scope - when :each; after_each_parts - when :all; after_all_parts - end - end - - def before_eval - end - - def add_method_examples(examples) - instance_methods.sort.each do |method_name| - if example_method?(method_name) - examples << new(method_name) do - __send__(method_name) - end - end - end - end - - def example_method?(method_name) - should_method?(method_name) - end - - def should_method?(method_name) - !(method_name =~ /^should(_not)?$/) && - method_name =~ /^should/ && ( - instance_method(method_name).arity == 0 || - instance_method(method_name).arity == -1 - ) - end - end - - end -end diff --git a/vendor/gems/rspec/lib/spec/example/example_matcher.rb b/vendor/gems/rspec/lib/spec/example/example_matcher.rb deleted file mode 100755 index 435eabf520..0000000000 --- a/vendor/gems/rspec/lib/spec/example/example_matcher.rb +++ /dev/null @@ -1,42 +0,0 @@ -module Spec - module Example - class ExampleMatcher - def initialize(example_group_description, example_name) - @example_group_description = example_group_description - @example_name = example_name - end - - def matches?(specified_examples) - specified_examples.each do |specified_example| - return true if matches_literal_example?(specified_example) || matches_example_not_considering_modules?(specified_example) - end - false - end - - protected - def matches_literal_example?(specified_example) - specified_example =~ /(^#{example_group_regex} #{example_regexp}$|^#{example_group_regex}$|^#{example_group_with_before_all_regexp}$|^#{example_regexp}$)/ - end - - def matches_example_not_considering_modules?(specified_example) - specified_example =~ /(^#{example_group_regex_not_considering_modules} #{example_regexp}$|^#{example_group_regex_not_considering_modules}$|^#{example_regexp}$)/ - end - - def example_group_regex - Regexp.escape(@example_group_description) - end - - def example_group_with_before_all_regexp - Regexp.escape("#{@example_group_description} before(:all)") - end - - def example_group_regex_not_considering_modules - Regexp.escape(@example_group_description.split('::').last) - end - - def example_regexp - Regexp.escape(@example_name) - end - end - end -end diff --git a/vendor/gems/rspec/lib/spec/example/example_methods.rb b/vendor/gems/rspec/lib/spec/example/example_methods.rb deleted file mode 100644 index babd31dfae..0000000000 --- a/vendor/gems/rspec/lib/spec/example/example_methods.rb +++ /dev/null @@ -1,106 +0,0 @@ -module Spec - module Example - module ExampleMethods - extend ExampleGroupMethods - extend ModuleReopeningFix - - PENDING_EXAMPLE_BLOCK = lambda { - raise Spec::Example::ExamplePendingError.new("Not Yet Implemented") - } - - def execute(options, instance_variables) - options.reporter.example_started(self) - set_instance_variables_from_hash(instance_variables) - - execution_error = nil - Timeout.timeout(options.timeout) do - begin - before_example - run_with_description_capturing - rescue Exception => e - execution_error ||= e - end - begin - after_example - rescue Exception => e - execution_error ||= e - end - end - - options.reporter.example_finished(self, execution_error) - success = execution_error.nil? || ExamplePendingError === execution_error - end - - def instance_variable_hash - instance_variables.inject({}) do |variable_hash, variable_name| - variable_hash[variable_name] = instance_variable_get(variable_name) - variable_hash - end - end - - def violated(message="") - raise Spec::Expectations::ExpectationNotMetError.new(message) - end - - def eval_each_fail_fast(procs) #:nodoc: - procs.each do |proc| - instance_eval(&proc) - end - end - - def eval_each_fail_slow(procs) #:nodoc: - first_exception = nil - procs.each do |proc| - begin - instance_eval(&proc) - rescue Exception => e - first_exception ||= e - end - end - raise first_exception if first_exception - end - - def description - @_defined_description || @_matcher_description || "NO NAME" - end - - def set_instance_variables_from_hash(ivars) - ivars.each do |variable_name, value| - # Ruby 1.9 requires variable.to_s on the next line - unless ['@_implementation', '@_defined_description', '@_matcher_description', '@method_name'].include?(variable_name.to_s) - instance_variable_set variable_name, value - end - end - end - - def run_with_description_capturing - begin - return instance_eval(&(@_implementation || PENDING_EXAMPLE_BLOCK)) - ensure - @_matcher_description = Spec::Matchers.generated_description - Spec::Matchers.clear_generated_description - end - end - - def implementation_backtrace - eval("caller", @_implementation) - end - - protected - include Matchers - include Pending - - def before_example - setup_mocks_for_rspec - self.class.run_before_each(self) - end - - def after_example - self.class.run_after_each(self) - verify_mocks_for_rspec - ensure - teardown_mocks_for_rspec - end - end - end -end diff --git a/vendor/gems/rspec/lib/spec/example/module_reopening_fix.rb b/vendor/gems/rspec/lib/spec/example/module_reopening_fix.rb deleted file mode 100644 index dc01dd6668..0000000000 --- a/vendor/gems/rspec/lib/spec/example/module_reopening_fix.rb +++ /dev/null @@ -1,21 +0,0 @@ -module Spec - module Example - # This is a fix for ...Something in Ruby 1.8.6??... (Someone fill in here please - Aslak) - module ModuleReopeningFix - def child_modules - @child_modules ||= [] - end - - def included(mod) - child_modules << mod - end - - def include(mod) - super - child_modules.each do |child_module| - child_module.__send__(:include, mod) - end - end - end - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/lib/spec/example/pending.rb b/vendor/gems/rspec/lib/spec/example/pending.rb deleted file mode 100644 index b1f27c8667..0000000000 --- a/vendor/gems/rspec/lib/spec/example/pending.rb +++ /dev/null @@ -1,18 +0,0 @@ -module Spec - module Example - module Pending - def pending(message = "TODO") - if block_given? - begin - yield - rescue Exception => e - raise Spec::Example::ExamplePendingError.new(message) - end - raise Spec::Example::PendingExampleFixedError.new("Expected pending '#{message}' to fail. No Error was raised.") - else - raise Spec::Example::ExamplePendingError.new(message) - end - end - end - end -end diff --git a/vendor/gems/rspec/lib/spec/example/shared_example_group.rb b/vendor/gems/rspec/lib/spec/example/shared_example_group.rb deleted file mode 100644 index a6fd6211cf..0000000000 --- a/vendor/gems/rspec/lib/spec/example/shared_example_group.rb +++ /dev/null @@ -1,58 +0,0 @@ -module Spec - module Example - class SharedExampleGroup < Module - class << self - def add_shared_example_group(new_example_group) - guard_against_redefining_existing_example_group(new_example_group) - shared_example_groups << new_example_group - end - - def find_shared_example_group(example_group_description) - shared_example_groups.find do |b| - b.description == example_group_description - end - end - - def shared_example_groups - # TODO - this needs to be global, or at least accessible from - # from subclasses of Example in a centralized place. I'm not loving - # this as a solution, but it works for now. - $shared_example_groups ||= [] - end - - private - def guard_against_redefining_existing_example_group(new_example_group) - existing_example_group = find_shared_example_group(new_example_group.description) - return unless existing_example_group - return if new_example_group.equal?(existing_example_group) - return if spec_path(new_example_group) == spec_path(existing_example_group) - raise ArgumentError.new("Shared Example '#{existing_example_group.description}' already exists") - end - - def spec_path(example_group) - File.expand_path(example_group.spec_path) - end - end - include ExampleGroupMethods - public :include - - def initialize(*args, &example_group_block) - describe(*args) - @example_group_block = example_group_block - self.class.add_shared_example_group(self) - end - - def included(mod) # :nodoc: - mod.module_eval(&@example_group_block) - end - - def execute_in_class_hierarchy(superclass_last=false) - classes = [self] - superclass_last ? classes << ExampleMethods : classes.unshift(ExampleMethods) - classes.each do |example_group| - yield example_group - end - end - end - end -end diff --git a/vendor/gems/rspec/lib/spec/expectations.rb b/vendor/gems/rspec/lib/spec/expectations.rb deleted file mode 100644 index 65ea474251..0000000000 --- a/vendor/gems/rspec/lib/spec/expectations.rb +++ /dev/null @@ -1,56 +0,0 @@ -require 'spec/matchers' -require 'spec/expectations/errors' -require 'spec/expectations/extensions' -require 'spec/expectations/handler' - -module Spec - - # Spec::Expectations lets you set expectations on your objects. - # - # result.should == 37 - # team.should have(11).players_on_the_field - # - # == How Expectations work. - # - # Spec::Expectations adds two methods to Object: - # - # should(matcher=nil) - # should_not(matcher=nil) - # - # Both methods take an optional Expression Matcher (See Spec::Matchers). - # - # When +should+ receives an Expression Matcher, it calls matches?(self). If - # it returns +true+, the spec passes and execution continues. If it returns - # +false+, then the spec fails with the message returned by matcher.failure_message. - # - # Similarly, when +should_not+ receives a matcher, it calls matches?(self). If - # it returns +false+, the spec passes and execution continues. If it returns - # +true+, then the spec fails with the message returned by matcher.negative_failure_message. - # - # RSpec ships with a standard set of useful matchers, and writing your own - # matchers is quite simple. See Spec::Matchers for details. - module Expectations - class << self - attr_accessor :differ - - # raises a Spec::Expectations::ExpectationNotMetError with message - # - # When a differ has been assigned and fail_with is passed - # expected and target, passes them - # to the differ to append a diff message to the failure message. - def fail_with(message, expected=nil, target=nil) # :nodoc: - if Array === message && message.length == 3 - message, expected, target = message[0], message[1], message[2] - end - unless (differ.nil? || expected.nil? || target.nil?) - if expected.is_a?(String) - message << "\nDiff:" << self.differ.diff_as_string(target.to_s, expected) - elsif !target.is_a?(Proc) - message << "\nDiff:" << self.differ.diff_as_object(target, expected) - end - end - Kernel::raise(Spec::Expectations::ExpectationNotMetError.new(message)) - end - end - end -end diff --git a/vendor/gems/rspec/lib/spec/expectations/differs/default.rb b/vendor/gems/rspec/lib/spec/expectations/differs/default.rb deleted file mode 100644 index a5eb1bb890..0000000000 --- a/vendor/gems/rspec/lib/spec/expectations/differs/default.rb +++ /dev/null @@ -1,66 +0,0 @@ -begin - require 'rubygems' - require 'diff/lcs' #necessary due to loading bug on some machines - not sure why - DaC - require 'diff/lcs/hunk' -rescue LoadError ; raise "You must gem install diff-lcs to use diffing" ; end - -require 'pp' - -module Spec - module Expectations - module Differs - - # TODO add some rdoc - class Default - def initialize(options) - @options = options - end - - # This is snagged from diff/lcs/ldiff.rb (which is a commandline tool) - def diff_as_string(data_old, data_new) - data_old = data_old.split(/\n/).map! { |e| e.chomp } - data_new = data_new.split(/\n/).map! { |e| e.chomp } - output = "" - diffs = Diff::LCS.diff(data_old, data_new) - return output if diffs.empty? - oldhunk = hunk = nil - file_length_difference = 0 - diffs.each do |piece| - begin - hunk = Diff::LCS::Hunk.new(data_old, data_new, piece, context_lines, - file_length_difference) - file_length_difference = hunk.file_length_difference - next unless oldhunk - # Hunks may overlap, which is why we need to be careful when our - # diff includes lines of context. Otherwise, we might print - # redundant lines. - if (context_lines > 0) and hunk.overlaps?(oldhunk) - hunk.unshift(oldhunk) - else - output << oldhunk.diff(format) - end - ensure - oldhunk = hunk - output << "\n" - end - end - #Handle the last remaining hunk - output << oldhunk.diff(format) << "\n" - end - - def diff_as_object(target,expected) - diff_as_string(PP.pp(target,""), PP.pp(expected,"")) - end - - protected - def format - @options.diff_format - end - - def context_lines - @options.context_lines - end - end - end - end -end diff --git a/vendor/gems/rspec/lib/spec/expectations/errors.rb b/vendor/gems/rspec/lib/spec/expectations/errors.rb deleted file mode 100644 index 1fabd105d5..0000000000 --- a/vendor/gems/rspec/lib/spec/expectations/errors.rb +++ /dev/null @@ -1,12 +0,0 @@ -module Spec - module Expectations - # If Test::Unit is loaed, we'll use its error as baseclass, so that Test::Unit - # will report unmet RSpec expectations as failures rather than errors. - superclass = ['Test::Unit::AssertionFailedError', '::StandardError'].map do |c| - eval(c) rescue nil - end.compact.first - - class ExpectationNotMetError < superclass - end - end -end diff --git a/vendor/gems/rspec/lib/spec/expectations/extensions.rb b/vendor/gems/rspec/lib/spec/expectations/extensions.rb deleted file mode 100644 index 60c9b9e7da..0000000000 --- a/vendor/gems/rspec/lib/spec/expectations/extensions.rb +++ /dev/null @@ -1,2 +0,0 @@ -require 'spec/expectations/extensions/object' -require 'spec/expectations/extensions/string_and_symbol' diff --git a/vendor/gems/rspec/lib/spec/expectations/extensions/object.rb b/vendor/gems/rspec/lib/spec/expectations/extensions/object.rb deleted file mode 100644 index a3925bbee9..0000000000 --- a/vendor/gems/rspec/lib/spec/expectations/extensions/object.rb +++ /dev/null @@ -1,71 +0,0 @@ -module Spec - module Expectations - # rspec adds #should and #should_not to every Object (and, - # implicitly, every Class). - module ObjectExpectations - # :call-seq: - # should(matcher) - # should == expected - # should === expected - # should =~ expected - # - # receiver.should(matcher) - # => Passes if matcher.matches?(receiver) - # - # receiver.should == expected #any value - # => Passes if (receiver == expected) - # - # receiver.should === expected #any value - # => Passes if (receiver === expected) - # - # receiver.should =~ regexp - # => Passes if (receiver =~ regexp) - # - # See Spec::Matchers for more information about matchers - # - # == Warning - # - # NOTE that this does NOT support receiver.should != expected. - # Instead, use receiver.should_not == expected - def should(matcher = :default_parameter, &block) - if :default_parameter == matcher - Spec::Matchers::PositiveOperatorMatcher.new(self) - else - ExpectationMatcherHandler.handle_matcher(self, matcher, &block) - end - end - - # :call-seq: - # should_not(matcher) - # should_not == expected - # should_not === expected - # should_not =~ expected - # - # receiver.should_not(matcher) - # => Passes unless matcher.matches?(receiver) - # - # receiver.should_not == expected - # => Passes unless (receiver == expected) - # - # receiver.should_not === expected - # => Passes unless (receiver === expected) - # - # receiver.should_not =~ regexp - # => Passes unless (receiver =~ regexp) - # - # See Spec::Matchers for more information about matchers - def should_not(matcher = :default_parameter, &block) - if :default_parameter == matcher - Spec::Matchers::NegativeOperatorMatcher.new(self) - else - NegativeExpectationMatcherHandler.handle_matcher(self, matcher, &block) - end - end - - end - end -end - -class Object - include Spec::Expectations::ObjectExpectations -end diff --git a/vendor/gems/rspec/lib/spec/expectations/extensions/string_and_symbol.rb b/vendor/gems/rspec/lib/spec/expectations/extensions/string_and_symbol.rb deleted file mode 100644 index 29cfbddfae..0000000000 --- a/vendor/gems/rspec/lib/spec/expectations/extensions/string_and_symbol.rb +++ /dev/null @@ -1,17 +0,0 @@ -module Spec - module Expectations - module StringHelpers - def starts_with?(prefix) - to_s[0..(prefix.to_s.length - 1)] == prefix.to_s - end - end - end -end - -class String - include Spec::Expectations::StringHelpers -end - -class Symbol - include Spec::Expectations::StringHelpers -end diff --git a/vendor/gems/rspec/lib/spec/expectations/handler.rb b/vendor/gems/rspec/lib/spec/expectations/handler.rb deleted file mode 100644 index e6dce0846d..0000000000 --- a/vendor/gems/rspec/lib/spec/expectations/handler.rb +++ /dev/null @@ -1,52 +0,0 @@ -module Spec - module Expectations - class InvalidMatcherError < ArgumentError; end - - module MatcherHandlerHelper - def describe_matcher(matcher) - matcher.respond_to?(:description) ? matcher.description : "[#{matcher.class.name} does not provide a description]" - end - end - - class ExpectationMatcherHandler - class << self - include MatcherHandlerHelper - def handle_matcher(actual, matcher, &block) - unless matcher.respond_to?(:matches?) - raise InvalidMatcherError, "Expected a matcher, got #{matcher.inspect}." - end - - match = matcher.matches?(actual, &block) - ::Spec::Matchers.generated_description = "should #{describe_matcher(matcher)}" - Spec::Expectations.fail_with(matcher.failure_message) unless match - end - end - end - - class NegativeExpectationMatcherHandler - class << self - include MatcherHandlerHelper - def handle_matcher(actual, matcher, &block) - unless matcher.respond_to?(:matches?) - raise InvalidMatcherError, "Expected a matcher, got #{matcher.inspect}." - end - - unless matcher.respond_to?(:negative_failure_message) - Spec::Expectations.fail_with( -<<-EOF -Matcher does not support should_not. -See Spec::Matchers for more information -about matchers. -EOF -) - end - match = matcher.matches?(actual, &block) - ::Spec::Matchers.generated_description = "should not #{describe_matcher(matcher)}" - Spec::Expectations.fail_with(matcher.negative_failure_message) if match - end - end - end - - end -end - diff --git a/vendor/gems/rspec/lib/spec/extensions.rb b/vendor/gems/rspec/lib/spec/extensions.rb deleted file mode 100755 index 9a313d0e78..0000000000 --- a/vendor/gems/rspec/lib/spec/extensions.rb +++ /dev/null @@ -1,3 +0,0 @@ -require 'spec/extensions/object' -require 'spec/extensions/class' -require 'spec/extensions/main' diff --git a/vendor/gems/rspec/lib/spec/extensions/class.rb b/vendor/gems/rspec/lib/spec/extensions/class.rb deleted file mode 100644 index 30730f87e5..0000000000 --- a/vendor/gems/rspec/lib/spec/extensions/class.rb +++ /dev/null @@ -1,24 +0,0 @@ -class Class - # Creates a new subclass of self, with a name "under" our own name. - # Example: - # - # x = Foo::Bar.subclass('Zap'){} - # x.name # => Foo::Bar::Zap_1 - # x.superclass.name # => Foo::Bar - def subclass(base_name, &body) - klass = Class.new(self) - class_name = "#{base_name}_#{class_count!}" - instance_eval do - const_set(class_name, klass) - end - klass.instance_eval(&body) - klass - end - - private - def class_count! - @class_count ||= 0 - @class_count += 1 - @class_count - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/lib/spec/extensions/main.rb b/vendor/gems/rspec/lib/spec/extensions/main.rb deleted file mode 100644 index 281cbf8797..0000000000 --- a/vendor/gems/rspec/lib/spec/extensions/main.rb +++ /dev/null @@ -1,102 +0,0 @@ -module Spec - module Extensions - module Main - # Creates and returns a class that includes the ExampleGroupMethods - # module. Which ExampleGroup type is created depends on the directory of the file - # calling this method. For example, Spec::Rails will use different - # classes for specs living in spec/models, - # spec/helpers, spec/views and - # spec/controllers. - # - # It is also possible to override autodiscovery of the example group - # type with an options Hash as the last argument: - # - # describe "name", :type => :something_special do ... - # - # The reason for using different behaviour classes is to have different - # matcher methods available from within the describe block. - # - # See Spec::Example::ExampleFactory#register for details about how to - # register special implementations. - # - def describe(*args, &block) - raise ArgumentError if args.empty? - raise ArgumentError unless block - args << {} unless Hash === args.last - args.last[:spec_path] = caller(0)[1] - Spec::Example::ExampleGroupFactory.create_example_group(*args, &block) - end - alias :context :describe - - # Creates an example group that can be shared by other example groups - # - # == Examples - # - # share_examples_for "All Editions" do - # it "all editions behaviour" ... - # end - # - # describe SmallEdition do - # it_should_behave_like "All Editions" - # - # it "should do small edition stuff" do - # ... - # end - # end - def share_examples_for(name, &block) - describe(name, :shared => true, &block) - end - - alias :shared_examples_for :share_examples_for - - # Creates a Shared Example Group and assigns it to a constant - # - # share_as :AllEditions do - # it "should do all editions stuff" ... - # end - # - # describe SmallEdition do - # it_should_behave_like AllEditions - # - # it "should do small edition stuff" do - # ... - # end - # end - # - # And, for those of you who prefer to use something more like Ruby, you - # can just include the module directly - # - # describe SmallEdition do - # include AllEditions - # - # it "should do small edition stuff" do - # ... - # end - # end - def share_as(name, &block) - begin - Object.const_set(name, share_examples_for(name, &block)) - rescue NameError => e - raise NameError.new(e.message + "\nThe first argument to share_as must be a legal name for a constant\n") - end - end - - private - - def rspec_options - $rspec_options ||= begin; \ - parser = ::Spec::Runner::OptionParser.new(STDERR, STDOUT); \ - parser.order!(ARGV); \ - $rspec_options = parser.options; \ - end - $rspec_options - end - - def init_rspec_options(options) - $rspec_options = options if $rspec_options.nil? - end - end - end -end - -include Spec::Extensions::Main \ No newline at end of file diff --git a/vendor/gems/rspec/lib/spec/extensions/object.rb b/vendor/gems/rspec/lib/spec/extensions/object.rb deleted file mode 100755 index e9f6364e26..0000000000 --- a/vendor/gems/rspec/lib/spec/extensions/object.rb +++ /dev/null @@ -1,10 +0,0 @@ -class Object - def args_and_options(*args) - options = Hash === args.last ? args.pop : {} - return args, options - end - - def metaclass - class << self; self; end - end -end diff --git a/vendor/gems/rspec/lib/spec/interop/test.rb b/vendor/gems/rspec/lib/spec/interop/test.rb deleted file mode 100644 index afa16137b4..0000000000 --- a/vendor/gems/rspec/lib/spec/interop/test.rb +++ /dev/null @@ -1,12 +0,0 @@ -require 'test/unit' -require 'test/unit/testresult' - -require 'spec/interop/test/unit/testcase' -require 'spec/interop/test/unit/testsuite_adapter' -require 'spec/interop/test/unit/autorunner' -require 'spec/interop/test/unit/testresult' -require 'spec/interop/test/unit/ui/console/testrunner' - -Spec::Example::ExampleGroupFactory.default(Test::Unit::TestCase) - -Test::Unit.run = true diff --git a/vendor/gems/rspec/lib/spec/interop/test/unit/autorunner.rb b/vendor/gems/rspec/lib/spec/interop/test/unit/autorunner.rb deleted file mode 100644 index 3944e6995a..0000000000 --- a/vendor/gems/rspec/lib/spec/interop/test/unit/autorunner.rb +++ /dev/null @@ -1,6 +0,0 @@ -class Test::Unit::AutoRunner - remove_method :process_args - def process_args(argv) - true - end -end diff --git a/vendor/gems/rspec/lib/spec/interop/test/unit/testcase.rb b/vendor/gems/rspec/lib/spec/interop/test/unit/testcase.rb deleted file mode 100644 index b32a820c1b..0000000000 --- a/vendor/gems/rspec/lib/spec/interop/test/unit/testcase.rb +++ /dev/null @@ -1,61 +0,0 @@ -require 'test/unit/testcase' - -module Test - module Unit - # This extension of the standard Test::Unit::TestCase makes RSpec - # available from within, so that you can do things like: - # - # require 'test/unit' - # require 'spec' - # - # class MyTest < Test::Unit::TestCase - # it "should work with Test::Unit assertions" do - # assert_equal 4, 2+1 - # end - # - # def test_should_work_with_rspec_expectations - # (3+1).should == 5 - # end - # end - # - # See also Spec::Example::ExampleGroup - class TestCase - extend Spec::Example::ExampleGroupMethods - include Spec::Example::ExampleMethods - - before(:each) {setup} - after(:each) {teardown} - - class << self - def suite - Test::Unit::TestSuiteAdapter.new(self) - end - - def example_method?(method_name) - should_method?(method_name) || test_method?(method_name) - end - - def test_method?(method_name) - method_name =~ /^test[_A-Z]./ && ( - instance_method(method_name).arity == 0 || - instance_method(method_name).arity == -1 - ) - end - end - - def initialize(defined_description, &implementation) - @_defined_description = defined_description - @_implementation = implementation - - @_result = ::Test::Unit::TestResult.new - # @method_name is important to set here because it "complies" with Test::Unit's interface. - # Some Test::Unit extensions depend on @method_name being present. - @method_name = @_defined_description - end - - def run(ignore_this_argument=nil) - super() - end - end - end -end diff --git a/vendor/gems/rspec/lib/spec/interop/test/unit/testresult.rb b/vendor/gems/rspec/lib/spec/interop/test/unit/testresult.rb deleted file mode 100644 index 1386dc7281..0000000000 --- a/vendor/gems/rspec/lib/spec/interop/test/unit/testresult.rb +++ /dev/null @@ -1,6 +0,0 @@ -class Test::Unit::TestResult - alias_method :tu_passed?, :passed? - def passed? - return tu_passed? & ::Spec.run - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/lib/spec/interop/test/unit/testsuite_adapter.rb b/vendor/gems/rspec/lib/spec/interop/test/unit/testsuite_adapter.rb deleted file mode 100644 index 7c0ed092dc..0000000000 --- a/vendor/gems/rspec/lib/spec/interop/test/unit/testsuite_adapter.rb +++ /dev/null @@ -1,34 +0,0 @@ -module Test - module Unit - class TestSuiteAdapter < TestSuite - attr_reader :example_group, :examples - alias_method :tests, :examples - def initialize(example_group) - @example_group = example_group - @examples = example_group.examples - end - - def name - example_group.description - end - - def run(*args) - return true unless args.empty? - example_group.run - end - - def size - example_group.number_of_examples - end - - def delete(example) - examples.delete example - end - - def empty? - examples.empty? - end - end - end -end - diff --git a/vendor/gems/rspec/lib/spec/interop/test/unit/ui/console/testrunner.rb b/vendor/gems/rspec/lib/spec/interop/test/unit/ui/console/testrunner.rb deleted file mode 100644 index 8e9995e02d..0000000000 --- a/vendor/gems/rspec/lib/spec/interop/test/unit/ui/console/testrunner.rb +++ /dev/null @@ -1,61 +0,0 @@ -require 'test/unit/ui/console/testrunner' - -module Test - module Unit - module UI - module Console - class TestRunner - - alias_method :started_without_rspec, :started - def started_with_rspec(result) - @result = result - @need_to_output_started = true - end - alias_method :started, :started_with_rspec - - alias_method :test_started_without_rspec, :test_started - def test_started_with_rspec(name) - if @need_to_output_started - if @rspec_io - @rspec_io.rewind - output(@rspec_io.read) - end - output("Started") - @need_to_output_started = false - end - test_started_without_rspec(name) - end - alias_method :test_started, :test_started_with_rspec - - alias_method :test_finished_without_rspec, :test_finished - def test_finished_with_rspec(name) - test_finished_without_rspec(name) - @ran_test = true - end - alias_method :test_finished, :test_finished_with_rspec - - alias_method :finished_without_rspec, :finished - def finished_with_rspec(elapsed_time) - @ran_test ||= false - if @ran_test - finished_without_rspec(elapsed_time) - end - end - alias_method :finished, :finished_with_rspec - - alias_method :setup_mediator_without_rspec, :setup_mediator - def setup_mediator_with_rspec - orig_io = @io - @io = StringIO.new - setup_mediator_without_rspec - ensure - @rspec_io = @io - @io = orig_io - end - alias_method :setup_mediator, :setup_mediator_with_rspec - - end - end - end - end -end diff --git a/vendor/gems/rspec/lib/spec/matchers.rb b/vendor/gems/rspec/lib/spec/matchers.rb deleted file mode 100644 index afae5ae5f1..0000000000 --- a/vendor/gems/rspec/lib/spec/matchers.rb +++ /dev/null @@ -1,156 +0,0 @@ -require 'spec/matchers/simple_matcher' -require 'spec/matchers/be' -require 'spec/matchers/be_close' -require 'spec/matchers/change' -require 'spec/matchers/eql' -require 'spec/matchers/equal' -require 'spec/matchers/exist' -require 'spec/matchers/has' -require 'spec/matchers/have' -require 'spec/matchers/include' -require 'spec/matchers/match' -require 'spec/matchers/raise_error' -require 'spec/matchers/respond_to' -require 'spec/matchers/satisfy' -require 'spec/matchers/throw_symbol' -require 'spec/matchers/operator_matcher' - -module Spec - - # RSpec ships with a number of useful Expression Matchers. An Expression Matcher - # is any object that responds to the following methods: - # - # matches?(actual) - # failure_message - # negative_failure_message #optional - # description #optional - # - # See Spec::Expectations to learn how to use these as Expectation Matchers. - # See Spec::Mocks to learn how to use them as Mock Argument Constraints. - # - # == Predicates - # - # In addition to those Expression Matchers that are defined explicitly, RSpec will - # create custom Matchers on the fly for any arbitrary predicate, giving your specs - # a much more natural language feel. - # - # A Ruby predicate is a method that ends with a "?" and returns true or false. - # Common examples are +empty?+, +nil?+, and +instance_of?+. - # - # All you need to do is write +should be_+ followed by the predicate without - # the question mark, and RSpec will figure it out from there. For example: - # - # [].should be_empty => [].empty? #passes - # [].should_not be_empty => [].empty? #fails - # - # In addtion to prefixing the predicate matchers with "be_", you can also use "be_a_" - # and "be_an_", making your specs read much more naturally: - # - # "a string".should be_an_instance_of(String) =>"a string".instance_of?(String) #passes - # - # 3.should be_a_kind_of(Fixnum) => 3.kind_of?(Numeric) #passes - # 3.should be_a_kind_of(Numeric) => 3.kind_of?(Numeric) #passes - # 3.should be_an_instance_of(Fixnum) => 3.instance_of?(Fixnum) #passes - # 3.should_not be_instance_of(Numeric) => 3.instance_of?(Numeric) #fails - # - # RSpec will also create custom matchers for predicates like +has_key?+. To - # use this feature, just state that the object should have_key(:key) and RSpec will - # call has_key?(:key) on the target. For example: - # - # {:a => "A"}.should have_key(:a) => {:a => "A"}.has_key?(:a) #passes - # {:a => "A"}.should have_key(:b) => {:a => "A"}.has_key?(:b) #fails - # - # You can use this feature to invoke any predicate that begins with "has_", whether it is - # part of the Ruby libraries (like +Hash#has_key?+) or a method you wrote on your own class. - # - # == Custom Expectation Matchers - # - # When you find that none of the stock Expectation Matchers provide a natural - # feeling expectation, you can very easily write your own. - # - # For example, imagine that you are writing a game in which players can - # be in various zones on a virtual board. To specify that bob should - # be in zone 4, you could say: - # - # bob.current_zone.should eql(Zone.new("4")) - # - # But you might find it more expressive to say: - # - # bob.should be_in_zone("4") - # - # and/or - # - # bob.should_not be_in_zone("3") - # - # To do this, you would need to write a class like this: - # - # class BeInZone - # def initialize(expected) - # @expected = expected - # end - # def matches?(target) - # @target = target - # @target.current_zone.eql?(Zone.new(@expected)) - # end - # def failure_message - # "expected #{@target.inspect} to be in Zone #{@expected}" - # end - # def negative_failure_message - # "expected #{@target.inspect} not to be in Zone #{@expected}" - # end - # end - # - # ... and a method like this: - # - # def be_in_zone(expected) - # BeInZone.new(expected) - # end - # - # And then expose the method to your specs. This is normally done - # by including the method and the class in a module, which is then - # included in your spec: - # - # module CustomGameMatchers - # class BeInZone - # ... - # end - # - # def be_in_zone(expected) - # ... - # end - # end - # - # describe "Player behaviour" do - # include CustomGameMatchers - # ... - # end - # - # or you can include in globally in a spec_helper.rb file required - # from your spec file(s): - # - # Spec::Runner.configure do |config| - # config.include(CustomGameMatchers) - # end - # - module Matchers - module ModuleMethods - attr_accessor :generated_description - - def clear_generated_description - self.generated_description = nil - end - end - - extend ModuleMethods - - def method_missing(sym, *args, &block) # :nodoc: - return Matchers::Be.new(sym, *args) if sym.starts_with?("be_") - return Matchers::Has.new(sym, *args) if sym.starts_with?("have_") - super - end - - class MatcherError < StandardError - end - - end -end diff --git a/vendor/gems/rspec/lib/spec/matchers/be.rb b/vendor/gems/rspec/lib/spec/matchers/be.rb deleted file mode 100644 index 2b25b11f4d..0000000000 --- a/vendor/gems/rspec/lib/spec/matchers/be.rb +++ /dev/null @@ -1,224 +0,0 @@ -module Spec - module Matchers - - class Be #:nodoc: - def initialize(*args) - if args.empty? - @expected = :satisfy_if - else - @expected = parse_expected(args.shift) - end - @args = args - @comparison = "" - end - - def matches?(actual) - @actual = actual - if handling_predicate? - begin - return @result = actual.__send__(predicate, *@args) - rescue => predicate_error - # This clause should be empty, but rcov will not report it as covered - # unless something (anything) is executed within the clause - rcov_error_report = "http://eigenclass.org/hiki.rb?rcov-0.8.0" - end - - # This supports should_exist > target.exists? in the old world. - # We should consider deprecating that ability as in the new world - # you can't write "should exist" unless you have your own custom matcher. - begin - return @result = actual.__send__(present_tense_predicate, *@args) - rescue - raise predicate_error - end - else - return match_or_compare - end - end - - def failure_message - return "expected #{@comparison}#{expected}, got #{@actual.inspect}" unless handling_predicate? - return "expected #{predicate}#{args_to_s} to return true, got #{@result.inspect}" - end - - def negative_failure_message - return "expected not #{expected}, got #{@actual.inspect}" unless handling_predicate? - return "expected #{predicate}#{args_to_s} to return false, got #{@result.inspect}" - end - - def expected - return "if to be satisfied" if @expected == :satisfy_if - return true if @expected == :true - return false if @expected == :false - return "nil" if @expected == :nil - return @expected.inspect - end - - def match_or_compare - return @actual ? true : false if @expected == :satisfy_if - return @actual == true if @expected == :true - return @actual == false if @expected == :false - return @actual.nil? if @expected == :nil - return @actual < @expected if @less_than - return @actual <= @expected if @less_than_or_equal - return @actual >= @expected if @greater_than_or_equal - return @actual > @expected if @greater_than - return @actual == @expected if @double_equal - return @actual === @expected if @triple_equal - return @actual.equal?(@expected) - end - - def ==(expected) - @prefix = "be " - @double_equal = true - @comparison = "== " - @expected = expected - self - end - - def ===(expected) - @prefix = "be " - @triple_equal = true - @comparison = "=== " - @expected = expected - self - end - - def <(expected) - @prefix = "be " - @less_than = true - @comparison = "< " - @expected = expected - self - end - - def <=(expected) - @prefix = "be " - @less_than_or_equal = true - @comparison = "<= " - @expected = expected - self - end - - def >=(expected) - @prefix = "be " - @greater_than_or_equal = true - @comparison = ">= " - @expected = expected - self - end - - def >(expected) - @prefix = "be " - @greater_than = true - @comparison = "> " - @expected = expected - self - end - - def description - "#{prefix_to_sentence}#{comparison}#{expected_to_sentence}#{args_to_sentence}" - end - - private - def parse_expected(expected) - if Symbol === expected - @handling_predicate = true - ["be_an_","be_a_","be_"].each do |prefix| - if expected.starts_with?(prefix) - @prefix = prefix - return "#{expected.to_s.sub(@prefix,"")}".to_sym - end - end - end - @prefix = "" - return expected - end - - def handling_predicate? - return false if [:true, :false, :nil].include?(@expected) - return @handling_predicate - end - - def predicate - "#{@expected.to_s}?".to_sym - end - - def present_tense_predicate - "#{@expected.to_s}s?".to_sym - end - - def args_to_s - return "" if @args.empty? - inspected_args = @args.collect{|a| a.inspect} - return "(#{inspected_args.join(', ')})" - end - - def comparison - @comparison - end - - def expected_to_sentence - split_words(@expected) - end - - def prefix_to_sentence - split_words(@prefix) - end - - def split_words(sym) - sym.to_s.gsub(/_/,' ') - end - - def args_to_sentence - case @args.length - when 0 - "" - when 1 - " #{@args[0]}" - else - " #{@args[0...-1].join(', ')} and #{@args[-1]}" - end - end - - end - - # :call-seq: - # should be - # should be_true - # should be_false - # should be_nil - # should be_arbitrary_predicate(*args) - # should_not be_nil - # should_not be_arbitrary_predicate(*args) - # - # Given true, false, or nil, will pass if actual is - # true, false or nil (respectively). Given no args means - # the caller should satisfy an if condition (to be or not to be). - # - # Predicates are any Ruby method that ends in a "?" and returns true or false. - # Given be_ followed by arbitrary_predicate (without the "?"), RSpec will match - # convert that into a query against the target object. - # - # The arbitrary_predicate feature will handle any predicate - # prefixed with "be_an_" (e.g. be_an_instance_of), "be_a_" (e.g. be_a_kind_of) - # or "be_" (e.g. be_empty), letting you choose the prefix that best suits the predicate. - # - # == Examples - # - # target.should be - # target.should be_true - # target.should be_false - # target.should be_nil - # target.should_not be_nil - # - # collection.should be_empty #passes if target.empty? - # "this string".should be_an_intance_of(String) - # - # target.should_not be_empty #passes unless target.empty? - # target.should_not be_old_enough(16) #passes unless target.old_enough?(16) - def be(*args) - Matchers::Be.new(*args) - end - end -end diff --git a/vendor/gems/rspec/lib/spec/matchers/be_close.rb b/vendor/gems/rspec/lib/spec/matchers/be_close.rb deleted file mode 100644 index 7763eb97ec..0000000000 --- a/vendor/gems/rspec/lib/spec/matchers/be_close.rb +++ /dev/null @@ -1,37 +0,0 @@ -module Spec - module Matchers - - class BeClose #:nodoc: - def initialize(expected, delta) - @expected = expected - @delta = delta - end - - def matches?(actual) - @actual = actual - (@actual - @expected).abs < @delta - end - - def failure_message - "expected #{@expected} +/- (< #{@delta}), got #{@actual}" - end - - def description - "be close to #{@expected} (within +- #{@delta})" - end - end - - # :call-seq: - # should be_close(expected, delta) - # should_not be_close(expected, delta) - # - # Passes if actual == expected +/- delta - # - # == Example - # - # result.should be_close(3.0, 0.5) - def be_close(expected, delta) - Matchers::BeClose.new(expected, delta) - end - end -end diff --git a/vendor/gems/rspec/lib/spec/matchers/change.rb b/vendor/gems/rspec/lib/spec/matchers/change.rb deleted file mode 100644 index 784e516edf..0000000000 --- a/vendor/gems/rspec/lib/spec/matchers/change.rb +++ /dev/null @@ -1,144 +0,0 @@ -module Spec - module Matchers - - #Based on patch from Wilson Bilkovich - class Change #:nodoc: - def initialize(receiver=nil, message=nil, &block) - @receiver = receiver - @message = message - @block = block - end - - def matches?(target, &block) - if block - raise MatcherError.new(<<-EOF -block passed to should or should_not change must use {} instead of do/end -EOF -) - end - @target = target - execute_change - return false if @from && (@from != @before) - return false if @to && (@to != @after) - return (@before + @amount == @after) if @amount - return ((@after - @before) >= @minimum) if @minimum - return ((@after - @before) <= @maximum) if @maximum - return @before != @after - end - - def execute_change - @before = @block.nil? ? @receiver.send(@message) : @block.call - @target.call - @after = @block.nil? ? @receiver.send(@message) : @block.call - end - - def failure_message - if @to - "#{result} should have been changed to #{@to.inspect}, but is now #{@after.inspect}" - elsif @from - "#{result} should have initially been #{@from.inspect}, but was #{@before.inspect}" - elsif @amount - "#{result} should have been changed by #{@amount.inspect}, but was changed by #{actual_delta.inspect}" - elsif @minimum - "#{result} should have been changed by at least #{@minimum.inspect}, but was changed by #{actual_delta.inspect}" - elsif @maximum - "#{result} should have been changed by at most #{@maximum.inspect}, but was changed by #{actual_delta.inspect}" - else - "#{result} should have changed, but is still #{@before.inspect}" - end - end - - def result - @message || "result" - end - - def actual_delta - @after - @before - end - - def negative_failure_message - "#{result} should not have changed, but did change from #{@before.inspect} to #{@after.inspect}" - end - - def by(amount) - @amount = amount - self - end - - def by_at_least(minimum) - @minimum = minimum - self - end - - def by_at_most(maximum) - @maximum = maximum - self - end - - def to(to) - @to = to - self - end - - def from (from) - @from = from - self - end - end - - # :call-seq: - # should change(receiver, message, &block) - # should change(receiver, message, &block).by(value) - # should change(receiver, message, &block).from(old).to(new) - # should_not change(receiver, message, &block) - # - # Allows you to specify that a Proc will cause some value to change. - # - # == Examples - # - # lambda { - # team.add_player(player) - # }.should change(roster, :count) - # - # lambda { - # team.add_player(player) - # }.should change(roster, :count).by(1) - # - # lambda { - # team.add_player(player) - # }.should change(roster, :count).by_at_least(1) - # - # lambda { - # team.add_player(player) - # }.should change(roster, :count).by_at_most(1) - # - # string = "string" - # lambda { - # string.reverse - # }.should change { string }.from("string").to("gnirts") - # - # lambda { - # person.happy_birthday - # }.should change(person, :birthday).from(32).to(33) - # - # lambda { - # employee.develop_great_new_social_networking_app - # }.should change(employee, :title).from("Mail Clerk").to("CEO") - # - # Evaluates +receiver.message+ or +block+ before and - # after it evaluates the c object (generated by the lambdas in the examples above). - # - # Then compares the values before and after the +receiver.message+ and - # evaluates the difference compared to the expected difference. - # - # == Warning - # +should_not+ +change+ only supports the form with no subsequent calls to - # +by+, +by_at_least+, +by_at_most+, +to+ or +from+. - # - # blocks passed to +should+ +change+ and +should_not+ +change+ - # must use the {} form (do/end is not supported) - def change(target=nil, message=nil, &block) - Matchers::Change.new(target, message, &block) - end - end -end diff --git a/vendor/gems/rspec/lib/spec/matchers/eql.rb b/vendor/gems/rspec/lib/spec/matchers/eql.rb deleted file mode 100644 index 280ca5454a..0000000000 --- a/vendor/gems/rspec/lib/spec/matchers/eql.rb +++ /dev/null @@ -1,43 +0,0 @@ -module Spec - module Matchers - - class Eql #:nodoc: - def initialize(expected) - @expected = expected - end - - def matches?(actual) - @actual = actual - @actual.eql?(@expected) - end - - def failure_message - return "expected #{@expected.inspect}, got #{@actual.inspect} (using .eql?)", @expected, @actual - end - - def negative_failure_message - return "expected #{@actual.inspect} not to equal #{@expected.inspect} (using .eql?)", @expected, @actual - end - - def description - "eql #{@expected.inspect}" - end - end - - # :call-seq: - # should eql(expected) - # should_not eql(expected) - # - # Passes if actual and expected are of equal value, but not necessarily the same object. - # - # See http://www.ruby-doc.org/core/classes/Object.html#M001057 for more information about equality in Ruby. - # - # == Examples - # - # 5.should eql(5) - # 5.should_not eql(3) - def eql(expected) - Matchers::Eql.new(expected) - end - end -end diff --git a/vendor/gems/rspec/lib/spec/matchers/equal.rb b/vendor/gems/rspec/lib/spec/matchers/equal.rb deleted file mode 100644 index 4bfc749516..0000000000 --- a/vendor/gems/rspec/lib/spec/matchers/equal.rb +++ /dev/null @@ -1,43 +0,0 @@ -module Spec - module Matchers - - class Equal #:nodoc: - def initialize(expected) - @expected = expected - end - - def matches?(actual) - @actual = actual - @actual.equal?(@expected) - end - - def failure_message - return "expected #{@expected.inspect}, got #{@actual.inspect} (using .equal?)", @expected, @actual - end - - def negative_failure_message - return "expected #{@actual.inspect} not to equal #{@expected.inspect} (using .equal?)", @expected, @actual - end - - def description - "equal #{@expected.inspect}" - end - end - - # :call-seq: - # should equal(expected) - # should_not equal(expected) - # - # Passes if actual and expected are the same object (object identity). - # - # See http://www.ruby-doc.org/core/classes/Object.html#M001057 for more information about equality in Ruby. - # - # == Examples - # - # 5.should equal(5) #Fixnums are equal - # "5".should_not equal("5") #Strings that look the same are not the same object - def equal(expected) - Matchers::Equal.new(expected) - end - end -end diff --git a/vendor/gems/rspec/lib/spec/matchers/exist.rb b/vendor/gems/rspec/lib/spec/matchers/exist.rb deleted file mode 100644 index a5a9111323..0000000000 --- a/vendor/gems/rspec/lib/spec/matchers/exist.rb +++ /dev/null @@ -1,17 +0,0 @@ -module Spec - module Matchers - class Exist - def matches? actual - @actual = actual - @actual.exist? - end - def failure_message - "expected #{@actual.inspect} to exist, but it doesn't." - end - def negative_failure_message - "expected #{@actual.inspect} to not exist, but it does." - end - end - def exist; Exist.new; end - end -end diff --git a/vendor/gems/rspec/lib/spec/matchers/has.rb b/vendor/gems/rspec/lib/spec/matchers/has.rb deleted file mode 100644 index cc5a250b87..0000000000 --- a/vendor/gems/rspec/lib/spec/matchers/has.rb +++ /dev/null @@ -1,44 +0,0 @@ -module Spec - module Matchers - - class Has #:nodoc: - def initialize(sym, *args) - @sym = sym - @args = args - end - - def matches?(target) - @target = target - begin - return target.send(predicate, *@args) - rescue => @error - # This clause should be empty, but rcov will not report it as covered - # unless something (anything) is executed within the clause - rcov_error_report = "http://eigenclass.org/hiki.rb?rcov-0.8.0" - end - return false - end - - def failure_message - raise @error if @error - "expected ##{predicate}(#{@args[0].inspect}) to return true, got false" - end - - def negative_failure_message - raise @error if @error - "expected ##{predicate}(#{@args[0].inspect}) to return false, got true" - end - - def description - "have key #{@args[0].inspect}" - end - - private - def predicate - "#{@sym.to_s.sub("have_","has_")}?".to_sym - end - - end - - end -end diff --git a/vendor/gems/rspec/lib/spec/matchers/have.rb b/vendor/gems/rspec/lib/spec/matchers/have.rb deleted file mode 100644 index 47454e3be1..0000000000 --- a/vendor/gems/rspec/lib/spec/matchers/have.rb +++ /dev/null @@ -1,145 +0,0 @@ -module Spec - module Matchers - - class Have #:nodoc: - def initialize(expected, relativity=:exactly) - @expected = (expected == :no ? 0 : expected) - @relativity = relativity - end - - def relativities - @relativities ||= { - :exactly => "", - :at_least => "at least ", - :at_most => "at most " - } - end - - def method_missing(sym, *args, &block) - @collection_name = sym - @plural_collection_name = Inflector.pluralize(sym.to_s) if Object.const_defined?(:Inflector) - @args = args - @block = block - self - end - - def matches?(collection_owner) - if collection_owner.respond_to?(@collection_name) - collection = collection_owner.send(@collection_name, *@args, &@block) - elsif (@plural_collection_name && collection_owner.respond_to?(@plural_collection_name)) - collection = collection_owner.send(@plural_collection_name, *@args, &@block) - elsif (collection_owner.respond_to?(:length) || collection_owner.respond_to?(:size)) - collection = collection_owner - else - collection_owner.send(@collection_name, *@args, &@block) - end - @actual = collection.size if collection.respond_to?(:size) - @actual = collection.length if collection.respond_to?(:length) - raise not_a_collection if @actual.nil? - return @actual >= @expected if @relativity == :at_least - return @actual <= @expected if @relativity == :at_most - return @actual == @expected - end - - def not_a_collection - "expected #{@collection_name} to be a collection but it does not respond to #length or #size" - end - - def failure_message - "expected #{relative_expectation} #{@collection_name}, got #{@actual}" - end - - def negative_failure_message - if @relativity == :exactly - return "expected target not to have #{@expected} #{@collection_name}, got #{@actual}" - elsif @relativity == :at_most - return <<-EOF -Isn't life confusing enough? -Instead of having to figure out the meaning of this: - should_not have_at_most(#{@expected}).#{@collection_name} -We recommend that you use this instead: - should have_at_least(#{@expected + 1}).#{@collection_name} -EOF - elsif @relativity == :at_least - return <<-EOF -Isn't life confusing enough? -Instead of having to figure out the meaning of this: - should_not have_at_least(#{@expected}).#{@collection_name} -We recommend that you use this instead: - should have_at_most(#{@expected - 1}).#{@collection_name} -EOF - end - end - - def description - "have #{relative_expectation} #{@collection_name}" - end - - private - - def relative_expectation - "#{relativities[@relativity]}#{@expected}" - end - end - - # :call-seq: - # should have(number).named_collection__or__sugar - # should_not have(number).named_collection__or__sugar - # - # Passes if receiver is a collection with the submitted - # number of items OR if the receiver OWNS a collection - # with the submitted number of items. - # - # If the receiver OWNS the collection, you must use the name - # of the collection. So if a Team instance has a - # collection named #players, you must use that name - # to set the expectation. - # - # If the receiver IS the collection, you can use any name - # you like for named_collection. We'd recommend using - # either "elements", "members", or "items" as these are all - # standard ways of describing the things IN a collection. - # - # This also works for Strings, letting you set an expectation - # about its length - # - # == Examples - # - # # Passes if team.players.size == 11 - # team.should have(11).players - # - # # Passes if [1,2,3].length == 3 - # [1,2,3].should have(3).items #"items" is pure sugar - # - # # Passes if "this string".length == 11 - # "this string".should have(11).characters #"characters" is pure sugar - def have(n) - Matchers::Have.new(n) - end - alias :have_exactly :have - - # :call-seq: - # should have_at_least(number).items - # - # Exactly like have() with >=. - # - # == Warning - # - # +should_not+ +have_at_least+ is not supported - def have_at_least(n) - Matchers::Have.new(n, :at_least) - end - - # :call-seq: - # should have_at_most(number).items - # - # Exactly like have() with <=. - # - # == Warning - # - # +should_not+ +have_at_most+ is not supported - def have_at_most(n) - Matchers::Have.new(n, :at_most) - end - end -end diff --git a/vendor/gems/rspec/lib/spec/matchers/include.rb b/vendor/gems/rspec/lib/spec/matchers/include.rb deleted file mode 100644 index 5476f97d89..0000000000 --- a/vendor/gems/rspec/lib/spec/matchers/include.rb +++ /dev/null @@ -1,70 +0,0 @@ -module Spec - module Matchers - - class Include #:nodoc: - - def initialize(*expecteds) - @expecteds = expecteds - end - - def matches?(actual) - @actual = actual - @expecteds.each do |expected| - return false unless actual.include?(expected) - end - true - end - - def failure_message - _message - end - - def negative_failure_message - _message("not ") - end - - def description - "include #{_pretty_print(@expecteds)}" - end - - private - def _message(maybe_not="") - "expected #{@actual.inspect} #{maybe_not}to include #{_pretty_print(@expecteds)}" - end - - def _pretty_print(array) - result = "" - array.each_with_index do |item, index| - if index < (array.length - 2) - result << "#{item.inspect}, " - elsif index < (array.length - 1) - result << "#{item.inspect} and " - else - result << "#{item.inspect}" - end - end - result - end - end - - # :call-seq: - # should include(expected) - # should_not include(expected) - # - # Passes if actual includes expected. This works for - # collections and Strings. You can also pass in multiple args - # and it will only pass if all args are found in collection. - # - # == Examples - # - # [1,2,3].should include(3) - # [1,2,3].should include(2,3) #would pass - # [1,2,3].should include(2,3,4) #would fail - # [1,2,3].should_not include(4) - # "spread".should include("read") - # "spread".should_not include("red") - def include(*expected) - Matchers::Include.new(*expected) - end - end -end diff --git a/vendor/gems/rspec/lib/spec/matchers/match.rb b/vendor/gems/rspec/lib/spec/matchers/match.rb deleted file mode 100644 index 61ab52429c..0000000000 --- a/vendor/gems/rspec/lib/spec/matchers/match.rb +++ /dev/null @@ -1,41 +0,0 @@ -module Spec - module Matchers - - class Match #:nodoc: - def initialize(expected) - @expected = expected - end - - def matches?(actual) - @actual = actual - return true if actual =~ @expected - return false - end - - def failure_message - return "expected #{@actual.inspect} to match #{@expected.inspect}", @expected, @actual - end - - def negative_failure_message - return "expected #{@actual.inspect} not to match #{@expected.inspect}", @expected, @actual - end - - def description - "match #{@expected.inspect}" - end - end - - # :call-seq: - # should match(regexp) - # should_not match(regexp) - # - # Given a Regexp, passes if actual =~ regexp - # - # == Examples - # - # email.should match(/^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i) - def match(regexp) - Matchers::Match.new(regexp) - end - end -end diff --git a/vendor/gems/rspec/lib/spec/matchers/operator_matcher.rb b/vendor/gems/rspec/lib/spec/matchers/operator_matcher.rb deleted file mode 100755 index dd23a0994f..0000000000 --- a/vendor/gems/rspec/lib/spec/matchers/operator_matcher.rb +++ /dev/null @@ -1,73 +0,0 @@ -module Spec - module Matchers - class BaseOperatorMatcher - attr_reader :generated_description - - def initialize(target) - @target = target - end - - def ==(expected) - @expected = expected - __delegate_method_missing_to_target("==", expected) - end - - def ===(expected) - @expected = expected - __delegate_method_missing_to_target("===", expected) - end - - def =~(expected) - @expected = expected - __delegate_method_missing_to_target("=~", expected) - end - - def >(expected) - @expected = expected - __delegate_method_missing_to_target(">", expected) - end - - def >=(expected) - @expected = expected - __delegate_method_missing_to_target(">=", expected) - end - - def <(expected) - @expected = expected - __delegate_method_missing_to_target("<", expected) - end - - def <=(expected) - @expected = expected - __delegate_method_missing_to_target("<=", expected) - end - - def fail_with_message(message) - Spec::Expectations.fail_with(message, @expected, @target) - end - - end - - class PositiveOperatorMatcher < BaseOperatorMatcher #:nodoc: - - def __delegate_method_missing_to_target(operator, expected) - ::Spec::Matchers.generated_description = "should #{operator} #{expected.inspect}" - return if @target.send(operator, expected) - return fail_with_message("expected: #{expected.inspect},\n got: #{@target.inspect} (using #{operator})") if ['==','===', '=~'].include?(operator) - return fail_with_message("expected: #{operator} #{expected.inspect},\n got: #{operator.gsub(/./, ' ')} #{@target.inspect}") - end - - end - - class NegativeOperatorMatcher < BaseOperatorMatcher #:nodoc: - - def __delegate_method_missing_to_target(operator, expected) - ::Spec::Matchers.generated_description = "should not #{operator} #{expected.inspect}" - return unless @target.send(operator, expected) - return fail_with_message("expected not: #{operator} #{expected.inspect},\n got: #{operator.gsub(/./, ' ')} #{@target.inspect}") - end - - end - - end -end diff --git a/vendor/gems/rspec/lib/spec/matchers/raise_error.rb b/vendor/gems/rspec/lib/spec/matchers/raise_error.rb deleted file mode 100644 index 65eb4dddae..0000000000 --- a/vendor/gems/rspec/lib/spec/matchers/raise_error.rb +++ /dev/null @@ -1,109 +0,0 @@ -module Spec - module Matchers - - class RaiseError #:nodoc: - def initialize(error_or_message=Exception, message=nil) - if String === error_or_message - @expected_error = Exception - @expected_message = error_or_message - else - @expected_error = error_or_message - @expected_message = message - end - end - - def matches?(proc) - @raised_expected_error = false - @raised_other = false - begin - proc.call - rescue @expected_error => @actual_error - if @expected_message.nil? - @raised_expected_error = true - else - verify_message - end - rescue Exception => @actual_error - @raised_other = true - ensure - return @raised_expected_error - end - end - - def verify_message - case @expected_message - when Regexp - if @expected_message =~ @actual_error.message - @raised_expected_error = true - else - @raised_other = true - end - else - if @expected_message == @actual_error.message - @raised_expected_error = true - else - @raised_other = true - end - end - end - - def failure_message - return "expected #{expected_error}#{actual_error}" if @raised_other || !@raised_expected_error - end - - def negative_failure_message - "expected no #{expected_error}#{actual_error}" - end - - def description - "raise #{expected_error}" - end - - private - def expected_error - case @expected_message - when nil - @expected_error - when Regexp - "#{@expected_error} with message matching #{@expected_message.inspect}" - else - "#{@expected_error} with #{@expected_message.inspect}" - end - end - - def actual_error - @actual_error.nil? ? " but nothing was raised" : ", got #{@actual_error.inspect}" - end - end - - # :call-seq: - # should raise_error() - # should raise_error(NamedError) - # should raise_error(NamedError, String) - # should raise_error(NamedError, Regexp) - # should_not raise_error() - # should_not raise_error(NamedError) - # should_not raise_error(NamedError, String) - # should_not raise_error(NamedError, Regexp) - # - # With no args, matches if any error is raised. - # With a named error, matches only if that specific error is raised. - # With a named error and messsage specified as a String, matches only if both match. - # With a named error and messsage specified as a Regexp, matches only if both match. - # - # == Examples - # - # lambda { do_something_risky }.should raise_error - # lambda { do_something_risky }.should raise_error(PoorRiskDecisionError) - # lambda { do_something_risky }.should raise_error(PoorRiskDecisionError, "that was too risky") - # lambda { do_something_risky }.should raise_error(PoorRiskDecisionError, /oo ri/) - # - # lambda { do_something_risky }.should_not raise_error - # lambda { do_something_risky }.should_not raise_error(PoorRiskDecisionError) - # lambda { do_something_risky }.should_not raise_error(PoorRiskDecisionError, "that was too risky") - # lambda { do_something_risky }.should_not raise_error(PoorRiskDecisionError, /oo ri/) - def raise_error(error=Exception, message=nil) - Matchers::RaiseError.new(error, message) - end - end -end diff --git a/vendor/gems/rspec/lib/spec/matchers/respond_to.rb b/vendor/gems/rspec/lib/spec/matchers/respond_to.rb deleted file mode 100644 index 3d23422aa6..0000000000 --- a/vendor/gems/rspec/lib/spec/matchers/respond_to.rb +++ /dev/null @@ -1,45 +0,0 @@ -module Spec - module Matchers - - class RespondTo #:nodoc: - def initialize(*names) - @names = names - @names_not_responded_to = [] - end - - def matches?(target) - @names.each do |name| - unless target.respond_to?(name) - @names_not_responded_to << name - end - end - return @names_not_responded_to.empty? - end - - def failure_message - "expected target to respond to #{@names_not_responded_to.collect {|name| name.inspect }.join(', ')}" - end - - def negative_failure_message - "expected target not to respond to #{@names.collect {|name| name.inspect }.join(', ')}" - end - - def description - "respond to ##{@names.to_s}" - end - end - - # :call-seq: - # should respond_to(*names) - # should_not respond_to(*names) - # - # Matches if the target object responds to all of the names - # provided. Names can be Strings or Symbols. - # - # == Examples - # - def respond_to(*names) - Matchers::RespondTo.new(*names) - end - end -end diff --git a/vendor/gems/rspec/lib/spec/matchers/satisfy.rb b/vendor/gems/rspec/lib/spec/matchers/satisfy.rb deleted file mode 100644 index 6c0ca95bc5..0000000000 --- a/vendor/gems/rspec/lib/spec/matchers/satisfy.rb +++ /dev/null @@ -1,47 +0,0 @@ -module Spec - module Matchers - - class Satisfy #:nodoc: - def initialize(&block) - @block = block - end - - def matches?(actual, &block) - @block = block if block - @actual = actual - @block.call(actual) - end - - def failure_message - "expected #{@actual} to satisfy block" - end - - def negative_failure_message - "expected #{@actual} not to satisfy block" - end - end - - # :call-seq: - # should satisfy {} - # should_not satisfy {} - # - # Passes if the submitted block returns true. Yields target to the - # block. - # - # Generally speaking, this should be thought of as a last resort when - # you can't find any other way to specify the behaviour you wish to - # specify. - # - # If you do find yourself in such a situation, you could always write - # a custom matcher, which would likely make your specs more expressive. - # - # == Examples - # - # 5.should satisfy { |n| - # n > 3 - # } - def satisfy(&block) - Matchers::Satisfy.new(&block) - end - end -end diff --git a/vendor/gems/rspec/lib/spec/matchers/simple_matcher.rb b/vendor/gems/rspec/lib/spec/matchers/simple_matcher.rb deleted file mode 100644 index ac547d06a0..0000000000 --- a/vendor/gems/rspec/lib/spec/matchers/simple_matcher.rb +++ /dev/null @@ -1,29 +0,0 @@ -module Spec - module Matchers - class SimpleMatcher - attr_reader :description - - def initialize(description, &match_block) - @description = description - @match_block = match_block - end - - def matches?(actual) - @actual = actual - return @match_block.call(@actual) - end - - def failure_message() - return %[expected #{@description.inspect} but got #{@actual.inspect}] - end - - def negative_failure_message() - return %[expected not to get #{@description.inspect}, but got #{@actual.inspect}] - end - end - - def simple_matcher(message, &match_block) - SimpleMatcher.new(message, &match_block) - end - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/lib/spec/matchers/throw_symbol.rb b/vendor/gems/rspec/lib/spec/matchers/throw_symbol.rb deleted file mode 100644 index c74d844366..0000000000 --- a/vendor/gems/rspec/lib/spec/matchers/throw_symbol.rb +++ /dev/null @@ -1,74 +0,0 @@ -module Spec - module Matchers - - class ThrowSymbol #:nodoc: - def initialize(expected=nil) - @expected = expected - @actual = nil - end - - def matches?(proc) - begin - proc.call - rescue NameError => e - raise e unless e.message =~ /uncaught throw/ - @actual = e.name.to_sym - ensure - if @expected.nil? - return @actual.nil? ? false : true - else - return @actual == @expected - end - end - end - - def failure_message - if @actual - "expected #{expected}, got #{@actual.inspect}" - else - "expected #{expected} but nothing was thrown" - end - end - - def negative_failure_message - if @expected - "expected #{expected} not to be thrown" - else - "expected no Symbol, got :#{@actual}" - end - end - - def description - "throw #{expected}" - end - - private - - def expected - @expected.nil? ? "a Symbol" : @expected.inspect - end - - end - - # :call-seq: - # should throw_symbol() - # should throw_symbol(:sym) - # should_not throw_symbol() - # should_not throw_symbol(:sym) - # - # Given a Symbol argument, matches if a proc throws the specified Symbol. - # - # Given no argument, matches if a proc throws any Symbol. - # - # == Examples - # - # lambda { do_something_risky }.should throw_symbol - # lambda { do_something_risky }.should throw_symbol(:that_was_risky) - # - # lambda { do_something_risky }.should_not throw_symbol - # lambda { do_something_risky }.should_not throw_symbol(:that_was_risky) - def throw_symbol(sym=nil) - Matchers::ThrowSymbol.new(sym) - end - end -end diff --git a/vendor/gems/rspec/lib/spec/mocks.rb b/vendor/gems/rspec/lib/spec/mocks.rb deleted file mode 100644 index 9f9cd215bd..0000000000 --- a/vendor/gems/rspec/lib/spec/mocks.rb +++ /dev/null @@ -1,211 +0,0 @@ -require 'spec/mocks/methods' -require 'spec/mocks/argument_constraint_matchers' -require 'spec/mocks/spec_methods' -require 'spec/mocks/proxy' -require 'spec/mocks/mock' -require 'spec/mocks/argument_expectation' -require 'spec/mocks/message_expectation' -require 'spec/mocks/order_group' -require 'spec/mocks/errors' -require 'spec/mocks/error_generator' -require 'spec/mocks/extensions/object' -require 'spec/mocks/space' - - -module Spec - # == Mocks and Stubs - # - # RSpec will create Mock Objects and Stubs for you at runtime, or attach stub/mock behaviour - # to any of your real objects (Partial Mock/Stub). Because the underlying implementation - # for mocks and stubs is the same, you can intermingle mock and stub - # behaviour in either dynamically generated mocks or your pre-existing classes. - # There is a semantic difference in how they are created, however, - # which can help clarify the role it is playing within a given spec. - # - # == Mock Objects - # - # Mocks are objects that allow you to set and verify expectations that they will - # receive specific messages during run time. They are very useful for specifying how the subject of - # the spec interacts with its collaborators. This approach is widely known as "interaction - # testing". - # - # Mocks are also very powerful as a design tool. As you are - # driving the implementation of a given class, Mocks provide an anonymous - # collaborator that can change in behaviour as quickly as you can write an expectation in your - # spec. This flexibility allows you to design the interface of a collaborator that often - # does not yet exist. As the shape of the class being specified becomes more clear, so do the - # requirements for its collaborators - often leading to the discovery of new types that are - # needed in your system. - # - # Read Endo-Testing[http://www.mockobjects.com/files/endotesting.pdf] for a much - # more in depth description of this process. - # - # == Stubs - # - # Stubs are objects that allow you to set "stub" responses to - # messages. As Martin Fowler points out on his site, - # mocks_arent_stubs[http://www.martinfowler.com/articles/mocksArentStubs.html]. - # Paraphrasing Fowler's paraphrasing - # of Gerard Meszaros: Stubs provide canned responses to messages they might receive in a test, while - # mocks allow you to specify and, subsquently, verify that certain messages should be received during - # the execution of a test. - # - # == Partial Mocks/Stubs - # - # RSpec also supports partial mocking/stubbing, allowing you to add stub/mock behaviour - # to instances of your existing classes. This is generally - # something to be avoided, because changes to the class can have ripple effects on - # seemingly unrelated specs. When specs fail due to these ripple effects, the fact - # that some methods are being mocked can make it difficult to understand why a - # failure is occurring. - # - # That said, partials do allow you to expect and - # verify interactions with class methods such as +#find+ and +#create+ - # on Ruby on Rails model classes. - # - # == Further Reading - # - # There are many different viewpoints about the meaning of mocks and stubs. If you are interested - # in learning more, here is some recommended reading: - # - # * Mock Objects: http://www.mockobjects.com/ - # * Endo-Testing: http://www.mockobjects.com/files/endotesting.pdf - # * Mock Roles, Not Objects: http://www.mockobjects.com/files/mockrolesnotobjects.pdf - # * Test Double Patterns: http://xunitpatterns.com/Test%20Double%20Patterns.html - # * Mocks aren't stubs: http://www.martinfowler.com/articles/mocksArentStubs.html - # - # == Creating a Mock - # - # You can create a mock in any specification (or setup) using: - # - # mock(name, options={}) - # - # The optional +options+ argument is a +Hash+. Currently the only supported - # option is +:null_object+. Setting this to true instructs the mock to ignore - # any messages it hasn’t been told to expect – and quietly return itself. For example: - # - # mock("person", :null_object => true) - # - # == Creating a Stub - # - # You can create a stub in any specification (or setup) using: - # - # stub(name, stub_methods_and_values_hash) - # - # For example, if you wanted to create an object that always returns - # "More?!?!?!" to "please_sir_may_i_have_some_more" you would do this: - # - # stub("Mr Sykes", :please_sir_may_i_have_some_more => "More?!?!?!") - # - # == Creating a Partial Mock - # - # You don't really "create" a partial mock, you simply add method stubs and/or - # mock expectations to existing classes and objects: - # - # Factory.should_receive(:find).with(id).and_return(value) - # obj.stub!(:to_i).and_return(3) - # etc ... - # - # == Expecting Messages - # - # my_mock.should_receive(:sym) - # my_mock.should_not_receive(:sym) - # - # == Expecting Arguments - # - # my_mock.should_receive(:sym).with(*args) - # my_mock.should_not_receive(:sym).with(*args) - # - # == Argument Constraints using Expression Matchers - # - # Arguments that are passed to #with are compared with actual arguments received - # using == by default. In cases in which you want to specify things about the arguments - # rather than the arguments themselves, you can use any of the Expression Matchers. - # They don't all make syntactic sense (they were primarily designed for use with - # Spec::Expectations), but you are free to create your own custom Spec::Matchers. - # - # Spec::Mocks does provide one additional Matcher method named #ducktype. - # - # In addition, Spec::Mocks adds some keyword Symbols that you can use to - # specify certain kinds of arguments: - # - # my_mock.should_receive(:sym).with(no_args()) - # my_mock.should_receive(:sym).with(any_args()) - # my_mock.should_receive(:sym).with(1, an_instance_of(Numeric), "b") #2nd argument can any type of Numeric - # my_mock.should_receive(:sym).with(1, boolean(), "b") #2nd argument can true or false - # my_mock.should_receive(:sym).with(1, /abc/, "b") #2nd argument can be any String matching the submitted Regexp - # my_mock.should_receive(:sym).with(1, anything(), "b") #2nd argument can be anything at all - # my_mock.should_receive(:sym).with(1, ducktype(:abs, :div), "b") - # #2nd argument can be object that responds to #abs and #div - # - # == Receive Counts - # - # my_mock.should_receive(:sym).once - # my_mock.should_receive(:sym).twice - # my_mock.should_receive(:sym).exactly(n).times - # my_mock.should_receive(:sym).at_least(:once) - # my_mock.should_receive(:sym).at_least(:twice) - # my_mock.should_receive(:sym).at_least(n).times - # my_mock.should_receive(:sym).at_most(:once) - # my_mock.should_receive(:sym).at_most(:twice) - # my_mock.should_receive(:sym).at_most(n).times - # my_mock.should_receive(:sym).any_number_of_times - # - # == Ordering - # - # my_mock.should_receive(:sym).ordered - # my_mock.should_receive(:other_sym).ordered - # #This will fail if the messages are received out of order - # - # == Setting Reponses - # - # Whether you are setting a mock expectation or a simple stub, you can tell the - # object precisely how to respond: - # - # my_mock.should_receive(:sym).and_return(value) - # my_mock.should_receive(:sym).exactly(3).times.and_return(value1, value2, value3) - # # returns value1 the first time, value2 the second, etc - # my_mock.should_receive(:sym).and_return { ... } #returns value returned by the block - # my_mock.should_receive(:sym).and_raise(error) - # #error can be an instantiated object or a class - # #if it is a class, it must be instantiable with no args - # my_mock.should_receive(:sym).and_throw(:sym) - # my_mock.should_receive(:sym).and_yield(values,to,yield) - # my_mock.should_receive(:sym).and_yield(values,to,yield).and_yield(some,other,values,this,time) - # # for methods that yield to a block multiple times - # - # Any of these responses can be applied to a stub as well, but stubs do - # not support any qualifiers about the message received (i.e. you can't specify arguments - # or receive counts): - # - # my_mock.stub!(:sym).and_return(value) - # my_mock.stub!(:sym).and_return(value1, value2, value3) - # my_mock.stub!(:sym).and_raise(error) - # my_mock.stub!(:sym).and_throw(:sym) - # my_mock.stub!(:sym).and_yield(values,to,yield) - # my_mock.stub!(:sym).and_yield(values,to,yield).and_yield(some,other,values,this,time) - # - # == Arbitrary Handling - # - # Once in a while you'll find that the available expectations don't solve the - # particular problem you are trying to solve. Imagine that you expect the message - # to come with an Array argument that has a specific length, but you don't care - # what is in it. You could do this: - # - # my_mock.should_receive(:sym) do |arg| - # arg.should be_an_istance_of(Array) - # arg.length.should == 7 - # end - # - # Note that this would fail if the number of arguments received was different from - # the number of block arguments (in this case 1). - # - # == Combining Expectation Details - # - # Combining the message name with specific arguments, receive counts and responses - # you can get quite a bit of detail in your expectations: - # - # my_mock.should_receive(:<<).with("illegal value").once.and_raise(ArgumentError) - module Mocks - end -end diff --git a/vendor/gems/rspec/lib/spec/mocks/argument_constraint_matchers.rb b/vendor/gems/rspec/lib/spec/mocks/argument_constraint_matchers.rb deleted file mode 100644 index 0e47770821..0000000000 --- a/vendor/gems/rspec/lib/spec/mocks/argument_constraint_matchers.rb +++ /dev/null @@ -1,27 +0,0 @@ -module Spec - module Mocks - module ArgumentConstraintMatchers - - # Shortcut for creating an instance of Spec::Mocks::DuckTypeArgConstraint - def duck_type(*args) - DuckTypeArgConstraint.new(*args) - end - - def any_args - AnyArgsConstraint.new - end - - def anything - AnyArgConstraint.new(nil) - end - - def boolean - BooleanArgConstraint.new(nil) - end - - def no_args - NoArgsConstraint.new - end - end - end -end diff --git a/vendor/gems/rspec/lib/spec/mocks/argument_expectation.rb b/vendor/gems/rspec/lib/spec/mocks/argument_expectation.rb deleted file mode 100644 index 34a1d4d039..0000000000 --- a/vendor/gems/rspec/lib/spec/mocks/argument_expectation.rb +++ /dev/null @@ -1,183 +0,0 @@ -module Spec - module Mocks - - class MatcherConstraint - def initialize(matcher) - @matcher = matcher - end - - def matches?(value) - @matcher.matches?(value) - end - end - - class LiteralArgConstraint - def initialize(literal) - @literal_value = literal - end - - def matches?(value) - @literal_value == value - end - end - - class RegexpArgConstraint - def initialize(regexp) - @regexp = regexp - end - - def matches?(value) - return value =~ @regexp unless value.is_a?(Regexp) - value == @regexp - end - end - - class AnyArgConstraint - def initialize(ignore) - end - - def ==(other) - true - end - - # TODO - need this? - def matches?(value) - true - end - end - - class AnyArgsConstraint - def description - "any args" - end - end - - class NoArgsConstraint - def description - "no args" - end - - def ==(args) - args == [] - end - end - - class NumericArgConstraint - def initialize(ignore) - end - - def matches?(value) - value.is_a?(Numeric) - end - end - - class BooleanArgConstraint - def initialize(ignore) - end - - def ==(value) - matches?(value) - end - - def matches?(value) - return true if value.is_a?(TrueClass) - return true if value.is_a?(FalseClass) - false - end - end - - class StringArgConstraint - def initialize(ignore) - end - - def matches?(value) - value.is_a?(String) - end - end - - class DuckTypeArgConstraint - def initialize(*methods_to_respond_to) - @methods_to_respond_to = methods_to_respond_to - end - - def matches?(value) - @methods_to_respond_to.all? { |sym| value.respond_to?(sym) } - end - - def description - "duck_type" - end - end - - class ArgumentExpectation - attr_reader :args - @@constraint_classes = Hash.new { |hash, key| LiteralArgConstraint} - @@constraint_classes[:anything] = AnyArgConstraint - @@constraint_classes[:numeric] = NumericArgConstraint - @@constraint_classes[:boolean] = BooleanArgConstraint - @@constraint_classes[:string] = StringArgConstraint - - def initialize(args) - @args = args - if [:any_args] == args - @expected_params = nil - warn_deprecated(:any_args.inspect, "any_args()") - elsif args.length == 1 && args[0].is_a?(AnyArgsConstraint) then @expected_params = nil - elsif [:no_args] == args - @expected_params = [] - warn_deprecated(:no_args.inspect, "no_args()") - elsif args.length == 1 && args[0].is_a?(NoArgsConstraint) then @expected_params = [] - else @expected_params = process_arg_constraints(args) - end - end - - def process_arg_constraints(constraints) - constraints.collect do |constraint| - convert_constraint(constraint) - end - end - - def warn_deprecated(deprecated_method, instead) - Kernel.warn "The #{deprecated_method} constraint is deprecated. Use #{instead} instead." - end - - def convert_constraint(constraint) - if [:anything, :numeric, :boolean, :string].include?(constraint) - case constraint - when :anything - instead = "anything()" - when :boolean - instead = "boolean()" - when :numeric - instead = "an_instance_of(Numeric)" - when :string - instead = "an_instance_of(String)" - end - warn_deprecated(constraint.inspect, instead) - return @@constraint_classes[constraint].new(constraint) - end - return MatcherConstraint.new(constraint) if is_matcher?(constraint) - return RegexpArgConstraint.new(constraint) if constraint.is_a?(Regexp) - return LiteralArgConstraint.new(constraint) - end - - def is_matcher?(obj) - return obj.respond_to?(:matches?) && obj.respond_to?(:description) - end - - def check_args(args) - return true if @expected_params.nil? - return true if @expected_params == args - return constraints_match?(args) - end - - def constraints_match?(args) - return false if args.length != @expected_params.length - @expected_params.each_index { |i| return false unless @expected_params[i].matches?(args[i]) } - return true - end - - end - - end -end diff --git a/vendor/gems/rspec/lib/spec/mocks/error_generator.rb b/vendor/gems/rspec/lib/spec/mocks/error_generator.rb deleted file mode 100644 index 01d8f720d5..0000000000 --- a/vendor/gems/rspec/lib/spec/mocks/error_generator.rb +++ /dev/null @@ -1,84 +0,0 @@ -module Spec - module Mocks - class ErrorGenerator - attr_writer :opts - - def initialize(target, name) - @target = target - @name = name - end - - def opts - @opts ||= {} - end - - def raise_unexpected_message_error(sym, *args) - __raise "#{intro} received unexpected message :#{sym}#{arg_message(*args)}" - end - - def raise_unexpected_message_args_error(expectation, *args) - expected_args = format_args(*expectation.expected_args) - actual_args = args.empty? ? "(no args)" : format_args(*args) - __raise "#{intro} expected #{expectation.sym.inspect} with #{expected_args} but received it with #{actual_args}" - end - - def raise_expectation_error(sym, expected_received_count, actual_received_count, *args) - __raise "#{intro} expected :#{sym}#{arg_message(*args)} #{count_message(expected_received_count)}, but received it #{count_message(actual_received_count)}" - end - - def raise_out_of_order_error(sym) - __raise "#{intro} received :#{sym} out of order" - end - - def raise_block_failed_error(sym, detail) - __raise "#{intro} received :#{sym} but passed block failed with: #{detail}" - end - - def raise_missing_block_error(args_to_yield) - __raise "#{intro} asked to yield |#{arg_list(*args_to_yield)}| but no block was passed" - end - - def raise_wrong_arity_error(args_to_yield, arity) - __raise "#{intro} yielded |#{arg_list(*args_to_yield)}| to block with arity of #{arity}" - end - - private - def intro - @name ? "Mock '#{@name}'" : @target.inspect - end - - def __raise(message) - message = opts[:message] unless opts[:message].nil? - Kernel::raise(Spec::Mocks::MockExpectationError, message) - end - - def arg_message(*args) - " with " + format_args(*args) - end - - def format_args(*args) - return "(no args)" if args.empty? || args == [:no_args] - return "(any args)" if args == [:any_args] - "(" + arg_list(*args) + ")" - end - - def arg_list(*args) - args.collect do |arg| - arg.respond_to?(:description) ? arg.description : arg.inspect - end.join(", ") - end - - def count_message(count) - return "at least #{pretty_print(count.abs)}" if count < 0 - return pretty_print(count) - end - - def pretty_print(count) - return "once" if count == 1 - return "twice" if count == 2 - return "#{count} times" - end - - end - end -end diff --git a/vendor/gems/rspec/lib/spec/mocks/errors.rb b/vendor/gems/rspec/lib/spec/mocks/errors.rb deleted file mode 100644 index 68fdfe006f..0000000000 --- a/vendor/gems/rspec/lib/spec/mocks/errors.rb +++ /dev/null @@ -1,10 +0,0 @@ -module Spec - module Mocks - class MockExpectationError < StandardError - end - - class AmbiguousReturnError < StandardError - end - end -end - diff --git a/vendor/gems/rspec/lib/spec/mocks/extensions/object.rb b/vendor/gems/rspec/lib/spec/mocks/extensions/object.rb deleted file mode 100644 index 4b75310663..0000000000 --- a/vendor/gems/rspec/lib/spec/mocks/extensions/object.rb +++ /dev/null @@ -1,3 +0,0 @@ -class Object - include Spec::Mocks::Methods -end diff --git a/vendor/gems/rspec/lib/spec/mocks/message_expectation.rb b/vendor/gems/rspec/lib/spec/mocks/message_expectation.rb deleted file mode 100644 index 6bd2f1c32c..0000000000 --- a/vendor/gems/rspec/lib/spec/mocks/message_expectation.rb +++ /dev/null @@ -1,267 +0,0 @@ -module Spec - module Mocks - - class BaseExpectation - attr_reader :sym - - def initialize(error_generator, expectation_ordering, expected_from, sym, method_block, expected_received_count=1, opts={}) - @error_generator = error_generator - @error_generator.opts = opts - @expected_from = expected_from - @sym = sym - @method_block = method_block - @return_block = nil - @actual_received_count = 0 - @expected_received_count = expected_received_count - @args_expectation = ArgumentExpectation.new([AnyArgsConstraint.new]) - @consecutive = false - @exception_to_raise = nil - @symbol_to_throw = nil - @order_group = expectation_ordering - @at_least = nil - @at_most = nil - @args_to_yield = [] - end - - def expected_args - @args_expectation.args - end - - def and_return(*values, &return_block) - Kernel::raise AmbiguousReturnError unless @method_block.nil? - case values.size - when 0 then value = nil - when 1 then value = values[0] - else - value = values - @consecutive = true - @expected_received_count = values.size if !ignoring_args? && - @expected_received_count < values.size - end - @return_block = block_given? ? return_block : lambda { value } - # Ruby 1.9 - see where this is used below - @ignore_args = !block_given? - end - - # :call-seq: - # and_raise() - # and_raise(Exception) #any exception class - # and_raise(exception) #any exception object - # - # == Warning - # - # When you pass an exception class, the MessageExpectation will - # raise an instance of it, creating it with +new+. If the exception - # class initializer requires any parameters, you must pass in an - # instance and not the class. - def and_raise(exception=Exception) - @exception_to_raise = exception - end - - def and_throw(symbol) - @symbol_to_throw = symbol - end - - def and_yield(*args) - @args_to_yield << args - self - end - - def matches(sym, args) - @sym == sym and @args_expectation.check_args(args) - end - - def invoke(args, block) - @order_group.handle_order_constraint self - - begin - Kernel::raise @exception_to_raise unless @exception_to_raise.nil? - Kernel::throw @symbol_to_throw unless @symbol_to_throw.nil? - - if !@method_block.nil? - default_return_val = invoke_method_block(args) - elsif @args_to_yield.size > 0 - default_return_val = invoke_with_yield(block) - else - default_return_val = nil - end - - if @consecutive - return invoke_consecutive_return_block(args, block) - elsif @return_block - return invoke_return_block(args, block) - else - return default_return_val - end - ensure - @actual_received_count += 1 - end - end - - protected - - def invoke_method_block(args) - begin - @method_block.call(*args) - rescue => detail - @error_generator.raise_block_failed_error @sym, detail.message - end - end - - def invoke_with_yield(block) - if block.nil? - @error_generator.raise_missing_block_error @args_to_yield - end - @args_to_yield.each do |args_to_yield_this_time| - if block.arity > -1 && args_to_yield_this_time.length != block.arity - @error_generator.raise_wrong_arity_error args_to_yield_this_time, block.arity - end - block.call(*args_to_yield_this_time) - end - end - - def invoke_consecutive_return_block(args, block) - args << block unless block.nil? - value = @return_block.call(*args) - - index = [@actual_received_count, value.size-1].min - value[index] - end - - def invoke_return_block(args, block) - args << block unless block.nil? - # Ruby 1.9 - when we set @return_block to return values - # regardless of arguments, any arguments will result in - # a "wrong number of arguments" error - if @ignore_args - @return_block.call() - else - @return_block.call(*args) - end - end - end - - class MessageExpectation < BaseExpectation - - def matches_name_but_not_args(sym, args) - @sym == sym and not @args_expectation.check_args(args) - end - - def verify_messages_received - return if ignoring_args? || matches_exact_count? || - matches_at_least_count? || matches_at_most_count? - - generate_error - rescue Spec::Mocks::MockExpectationError => error - error.backtrace.insert(0, @expected_from) - Kernel::raise error - end - - def ignoring_args? - @expected_received_count == :any - end - - def matches_at_least_count? - @at_least && @actual_received_count >= @expected_received_count - end - - def matches_at_most_count? - @at_most && @actual_received_count <= @expected_received_count - end - - def matches_exact_count? - @expected_received_count == @actual_received_count - end - - def generate_error - @error_generator.raise_expectation_error(@sym, @expected_received_count, @actual_received_count, *@args_expectation.args) - end - - def with(*args, &block) - @method_block = block if block - @args_expectation = ArgumentExpectation.new(args) - self - end - - def exactly(n) - set_expected_received_count :exactly, n - self - end - - def at_least(n) - set_expected_received_count :at_least, n - self - end - - def at_most(n) - set_expected_received_count :at_most, n - self - end - - def times(&block) - @method_block = block if block - self - end - - def any_number_of_times(&block) - @method_block = block if block - @expected_received_count = :any - self - end - - def never - @expected_received_count = 0 - self - end - - def once(&block) - @method_block = block if block - @expected_received_count = 1 - self - end - - def twice(&block) - @method_block = block if block - @expected_received_count = 2 - self - end - - def ordered(&block) - @method_block = block if block - @order_group.register(self) - @ordered = true - self - end - - def negative_expectation_for?(sym) - return false - end - - protected - def set_expected_received_count(relativity, n) - @at_least = (relativity == :at_least) - @at_most = (relativity == :at_most) - @expected_received_count = case n - when Numeric - n - when :once - 1 - when :twice - 2 - end - end - - end - - class NegativeMessageExpectation < MessageExpectation - def initialize(message, expectation_ordering, expected_from, sym, method_block) - super(message, expectation_ordering, expected_from, sym, method_block, 0) - end - - def negative_expectation_for?(sym) - return @sym == sym - end - end - - end -end diff --git a/vendor/gems/rspec/lib/spec/mocks/methods.rb b/vendor/gems/rspec/lib/spec/mocks/methods.rb deleted file mode 100644 index d9fa324d3f..0000000000 --- a/vendor/gems/rspec/lib/spec/mocks/methods.rb +++ /dev/null @@ -1,39 +0,0 @@ -module Spec - module Mocks - module Methods - def should_receive(sym, opts={}, &block) - __mock_proxy.add_message_expectation(opts[:expected_from] || caller(1)[0], sym.to_sym, opts, &block) - end - - def should_not_receive(sym, &block) - __mock_proxy.add_negative_message_expectation(caller(1)[0], sym.to_sym, &block) - end - - def stub!(sym, opts={}) - __mock_proxy.add_stub(caller(1)[0], sym.to_sym, opts) - end - - def received_message?(sym, *args, &block) #:nodoc: - __mock_proxy.received_message?(sym.to_sym, *args, &block) - end - - def rspec_verify #:nodoc: - __mock_proxy.verify - end - - def rspec_reset #:nodoc: - __mock_proxy.reset - end - - private - - def __mock_proxy - if Mock === self - @mock_proxy ||= Proxy.new(self, @name, @options) - else - @mock_proxy ||= Proxy.new(self, self.class.name) - end - end - end - end -end diff --git a/vendor/gems/rspec/lib/spec/mocks/mock.rb b/vendor/gems/rspec/lib/spec/mocks/mock.rb deleted file mode 100644 index f029b1b8f1..0000000000 --- a/vendor/gems/rspec/lib/spec/mocks/mock.rb +++ /dev/null @@ -1,50 +0,0 @@ -module Spec - module Mocks - class Mock - include Methods - - # Creates a new mock with a +name+ (that will be used in error messages only) - # == Options: - # * :null_object - if true, the mock object acts as a forgiving null object allowing any message to be sent to it. - def initialize(name, stubs_and_options={}) - @name = name - @options = parse_options(stubs_and_options) - assign_stubs(stubs_and_options) - end - - # This allows for comparing the mock to other objects that proxy - # such as ActiveRecords belongs_to proxy objects - # By making the other object run the comparison, we're sure the call gets delegated to the proxy target - # This is an unfortunate side effect from ActiveRecord, but this should be safe unless the RHS redefines == in a nonsensical manner - def ==(other) - other == __mock_proxy - end - - def method_missing(sym, *args, &block) - __mock_proxy.instance_eval {@messages_received << [sym, args, block]} - begin - return self if __mock_proxy.null_object? - super(sym, *args, &block) - rescue NameError - __mock_proxy.raise_unexpected_message_error sym, *args - end - end - - def inspect - "#<#{self.class}:#{sprintf '0x%x', self.object_id} @name=#{@name.inspect}>" - end - - private - - def parse_options(options) - options.has_key?(:null_object) ? {:null_object => options.delete(:null_object)} : {} - end - - def assign_stubs(stubs) - stubs.each_pair do |message, response| - stub!(message).and_return(response) - end - end - end - end -end diff --git a/vendor/gems/rspec/lib/spec/mocks/order_group.rb b/vendor/gems/rspec/lib/spec/mocks/order_group.rb deleted file mode 100644 index 9983207eb7..0000000000 --- a/vendor/gems/rspec/lib/spec/mocks/order_group.rb +++ /dev/null @@ -1,29 +0,0 @@ -module Spec - module Mocks - class OrderGroup - def initialize error_generator - @error_generator = error_generator - @ordering = Array.new - end - - def register(expectation) - @ordering << expectation - end - - def ready_for?(expectation) - return @ordering.first == expectation - end - - def consume - @ordering.shift - end - - def handle_order_constraint expectation - return unless @ordering.include? expectation - return consume if ready_for?(expectation) - @error_generator.raise_out_of_order_error expectation.sym - end - - end - end -end diff --git a/vendor/gems/rspec/lib/spec/mocks/proxy.rb b/vendor/gems/rspec/lib/spec/mocks/proxy.rb deleted file mode 100644 index 03db3b1134..0000000000 --- a/vendor/gems/rspec/lib/spec/mocks/proxy.rb +++ /dev/null @@ -1,170 +0,0 @@ -module Spec - module Mocks - class Proxy - DEFAULT_OPTIONS = { - :null_object => false, - } - - def initialize(target, name, options={}) - @target = target - @name = name - @error_generator = ErrorGenerator.new target, name - @expectation_ordering = OrderGroup.new @error_generator - @expectations = [] - @messages_received = [] - @stubs = [] - @proxied_methods = [] - @options = options ? DEFAULT_OPTIONS.dup.merge(options) : DEFAULT_OPTIONS - end - - def null_object? - @options[:null_object] - end - - def add_message_expectation(expected_from, sym, opts={}, &block) - __add sym - @expectations << MessageExpectation.new(@error_generator, @expectation_ordering, expected_from, sym, block_given? ? block : nil, 1, opts) - @expectations.last - end - - def add_negative_message_expectation(expected_from, sym, &block) - __add sym - @expectations << NegativeMessageExpectation.new(@error_generator, @expectation_ordering, expected_from, sym, block_given? ? block : nil) - @expectations.last - end - - def add_stub(expected_from, sym, opts={}) - __add sym - @stubs.unshift MessageExpectation.new(@error_generator, @expectation_ordering, expected_from, sym, nil, :any, opts) - @stubs.first - end - - def verify #:nodoc: - verify_expectations - ensure - reset - end - - def reset - clear_expectations - clear_stubs - reset_proxied_methods - clear_proxied_methods - end - - def received_message?(sym, *args, &block) - @messages_received.any? {|array| array == [sym, args, block]} - end - - def has_negative_expectation?(sym) - @expectations.detect {|expectation| expectation.negative_expectation_for?(sym)} - end - - def message_received(sym, *args, &block) - if expectation = find_matching_expectation(sym, *args) - expectation.invoke(args, block) - elsif stub = find_matching_method_stub(sym, *args) - stub.invoke([], block) - elsif expectation = find_almost_matching_expectation(sym, *args) - raise_unexpected_message_args_error(expectation, *args) unless has_negative_expectation?(sym) unless null_object? - else - @target.send :method_missing, sym, *args, &block - end - end - - def raise_unexpected_message_args_error(expectation, *args) - @error_generator.raise_unexpected_message_args_error expectation, *args - end - - def raise_unexpected_message_error(sym, *args) - @error_generator.raise_unexpected_message_error sym, *args - end - - private - - def __add(sym) - $rspec_mocks.add(@target) unless $rspec_mocks.nil? - define_expected_method(sym) - end - - def define_expected_method(sym) - if target_responds_to?(sym) && !metaclass.method_defined?(munge(sym)) - munged_sym = munge(sym) - metaclass.instance_eval do - alias_method munged_sym, sym if method_defined?(sym.to_s) - end - @proxied_methods << sym - end - - metaclass_eval(<<-EOF, __FILE__, __LINE__) - def #{sym}(*args, &block) - __mock_proxy.message_received :#{sym}, *args, &block - end - EOF - end - - def target_responds_to?(sym) - return @target.send(munge(:respond_to?),sym) if @already_proxied_respond_to - return @already_proxied_respond_to = true if sym == :respond_to? - return @target.respond_to?(sym) - end - - def munge(sym) - "proxied_by_rspec__#{sym.to_s}".to_sym - end - - def clear_expectations - @expectations.clear - end - - def clear_stubs - @stubs.clear - end - - def clear_proxied_methods - @proxied_methods.clear - end - - def metaclass_eval(str, filename, lineno) - metaclass.class_eval(str, filename, lineno) - end - - def metaclass - (class << @target; self; end) - end - - def verify_expectations - @expectations.each do |expectation| - expectation.verify_messages_received - end - end - - def reset_proxied_methods - @proxied_methods.each do |sym| - munged_sym = munge(sym) - metaclass.instance_eval do - if method_defined?(munged_sym.to_s) - alias_method sym, munged_sym - undef_method munged_sym - else - undef_method sym - end - end - end - end - - def find_matching_expectation(sym, *args) - @expectations.find {|expectation| expectation.matches(sym, args)} - end - - def find_almost_matching_expectation(sym, *args) - @expectations.find {|expectation| expectation.matches_name_but_not_args(sym, args)} - end - - def find_matching_method_stub(sym, *args) - @stubs.find {|stub| stub.matches(sym, args)} - end - - end - end -end diff --git a/vendor/gems/rspec/lib/spec/mocks/space.rb b/vendor/gems/rspec/lib/spec/mocks/space.rb deleted file mode 100644 index 3e13224c70..0000000000 --- a/vendor/gems/rspec/lib/spec/mocks/space.rb +++ /dev/null @@ -1,28 +0,0 @@ -module Spec - module Mocks - class Space - def add(obj) - mocks << obj unless mocks.detect {|m| m.equal? obj} - end - - def verify_all - mocks.each do |mock| - mock.rspec_verify - end - end - - def reset_all - mocks.each do |mock| - mock.rspec_reset - end - mocks.clear - end - - private - - def mocks - @mocks ||= [] - end - end - end -end diff --git a/vendor/gems/rspec/lib/spec/mocks/spec_methods.rb b/vendor/gems/rspec/lib/spec/mocks/spec_methods.rb deleted file mode 100644 index d92a4cedd8..0000000000 --- a/vendor/gems/rspec/lib/spec/mocks/spec_methods.rb +++ /dev/null @@ -1,38 +0,0 @@ -module Spec - module Mocks - module ExampleMethods - include Spec::Mocks::ArgumentConstraintMatchers - - # Shortcut for creating an instance of Spec::Mocks::Mock. - # - # +name+ is used for failure reporting, so you should use the - # role that the mock is playing in the example. - # - # +stubs_and_options+ lets you assign options and stub values - # at the same time. The only option available is :null_object. - # Anything else is treated as a stub value. - # - # == Examples - # - # stub_thing = mock("thing", :a => "A") - # stub_thing.a == "A" => true - # - # stub_person = stub("thing", :name => "Joe", :email => "joe@domain.com") - # stub_person.name => "Joe" - # stub_person.email => "joe@domain.com" - def mock(name, stubs_and_options={}) - Spec::Mocks::Mock.new(name, stubs_and_options) - end - - alias :stub :mock - - # Shortcut for creating a mock object that will return itself in response - # to any message it receives that it hasn't been explicitly instructed - # to respond to. - def stub_everything(name = 'stub') - mock(name, :null_object => true) - end - - end - end -end diff --git a/vendor/gems/rspec/lib/spec/rake/spectask.rb b/vendor/gems/rspec/lib/spec/rake/spectask.rb deleted file mode 100644 index 781c151a4c..0000000000 --- a/vendor/gems/rspec/lib/spec/rake/spectask.rb +++ /dev/null @@ -1,235 +0,0 @@ -#!/usr/bin/env ruby - -# Define a task library for running RSpec contexts. - -require 'rake' -require 'rake/tasklib' - -module Spec - module Rake - - # A Rake task that runs a set of specs. - # - # Example: - # - # Spec::Rake::SpecTask.new do |t| - # t.warning = true - # t.rcov = true - # end - # - # This will create a task that can be run with: - # - # rake spec - # - # If rake is invoked with a "SPEC=filename" command line option, - # then the list of spec files will be overridden to include only the - # filename specified on the command line. This provides an easy way - # to run just one spec. - # - # If rake is invoked with a "SPEC_OPTS=options" command line option, - # then the given options will override the value of the +spec_opts+ - # attribute. - # - # If rake is invoked with a "RCOV_OPTS=options" command line option, - # then the given options will override the value of the +rcov_opts+ - # attribute. - # - # Examples: - # - # rake spec # run specs normally - # rake spec SPEC=just_one_file.rb # run just one spec file. - # rake spec SPEC_OPTS="--diff" # enable diffing - # rake spec RCOV_OPTS="--aggregate myfile.txt" # see rcov --help for details - # - # Each attribute of this task may be a proc. This allows for lazy evaluation, - # which is sometimes handy if you want to defer the evaluation of an attribute value - # until the task is run (as opposed to when it is defined). - # - # This task can also be used to run existing Test::Unit tests and get RSpec - # output, for example like this: - # - # require 'rubygems' - # require 'spec/rake/spectask' - # Spec::Rake::SpecTask.new do |t| - # t.ruby_opts = ['-rtest/unit'] - # t.spec_files = FileList['test/**/*_test.rb'] - # end - # - class SpecTask < ::Rake::TaskLib - class << self - def attr_accessor(*names) - super(*names) - names.each do |name| - module_eval "def #{name}() evaluate(@#{name}) end" # Allows use of procs - end - end - end - - # Name of spec task. (default is :spec) - attr_accessor :name - - # Array of directories to be added to $LOAD_PATH before running the - # specs. Defaults to [''] - attr_accessor :libs - - # If true, requests that the specs be run with the warning flag set. - # E.g. warning=true implies "ruby -w" used to run the specs. Defaults to false. - attr_accessor :warning - - # Glob pattern to match spec files. (default is 'spec/**/*_spec.rb') - # Setting the SPEC environment variable overrides this. - attr_accessor :pattern - - # Array of commandline options to pass to RSpec. Defaults to []. - # Setting the SPEC_OPTS environment variable overrides this. - attr_accessor :spec_opts - - # Whether or not to use RCov (default is false) - # See http://eigenclass.org/hiki.rb?rcov - attr_accessor :rcov - - # Array of commandline options to pass to RCov. Defaults to ['--exclude', 'lib\/spec,bin\/spec']. - # Ignored if rcov=false - # Setting the RCOV_OPTS environment variable overrides this. - attr_accessor :rcov_opts - - # Directory where the RCov report is written. Defaults to "coverage" - # Ignored if rcov=false - attr_accessor :rcov_dir - - # Array of commandline options to pass to ruby. Defaults to []. - attr_accessor :ruby_opts - - # Whether or not to fail Rake when an error occurs (typically when specs fail). - # Defaults to true. - attr_accessor :fail_on_error - - # A message to print to stderr when there are failures. - attr_accessor :failure_message - - # Where RSpec's output is written. Defaults to STDOUT. - # DEPRECATED. Use --format FORMAT:WHERE in spec_opts. - attr_accessor :out - - # Explicitly define the list of spec files to be included in a - # spec. +spec_files+ is expected to be an array of file names (a - # FileList is acceptable). If both +pattern+ and +spec_files+ are - # used, then the list of spec files is the union of the two. - # Setting the SPEC environment variable overrides this. - attr_accessor :spec_files - - # Use verbose output. If this is set to true, the task will print - # the executed spec command to stdout. Defaults to false. - attr_accessor :verbose - - # Defines a new task, using the name +name+. - def initialize(name=:spec) - @name = name - @libs = [File.expand_path(File.dirname(__FILE__) + '/../../../lib')] - @pattern = nil - @spec_files = nil - @spec_opts = [] - @warning = false - @ruby_opts = [] - @fail_on_error = true - @rcov = false - @rcov_opts = ['--exclude', 'lib\/spec,bin\/spec,config\/boot.rb'] - @rcov_dir = "coverage" - - yield self if block_given? - @pattern = 'spec/**/*_spec.rb' if pattern.nil? && spec_files.nil? - define - end - - def define # :nodoc: - spec_script = File.expand_path(File.dirname(__FILE__) + '/../../../bin/spec') - - lib_path = libs.join(File::PATH_SEPARATOR) - actual_name = Hash === name ? name.keys.first : name - unless ::Rake.application.last_comment - desc "Run specs" + (rcov ? " using RCov" : "") - end - task name do - RakeFileUtils.verbose(verbose) do - unless spec_file_list.empty? - # ruby [ruby_opts] -Ilib -S rcov [rcov_opts] bin/spec -- examples [spec_opts] - # or - # ruby [ruby_opts] -Ilib bin/spec examples [spec_opts] - cmd = "ruby " - - rb_opts = ruby_opts.clone - rb_opts << "-I\"#{lib_path}\"" - rb_opts << "-S rcov" if rcov - rb_opts << "-w" if warning - cmd << rb_opts.join(" ") - cmd << " " - cmd << rcov_option_list - cmd << %[ -o "#{rcov_dir}" ] if rcov - #cmd << %Q|"#{spec_script}"| - cmd << " " - cmd << "-- " if rcov - cmd << spec_file_list.collect { |fn| %["#{fn}"] }.join(' ') - cmd << " " - cmd << spec_option_list - if out - cmd << " " - cmd << %Q| > "#{out}"| - STDERR.puts "The Spec::Rake::SpecTask#out attribute is DEPRECATED and will be removed in a future version. Use --format FORMAT:WHERE instead." - end - if verbose - puts cmd - end - unless system(cmd) - STDERR.puts failure_message if failure_message - raise("Command #{cmd} failed") if fail_on_error - end - end - end - end - - if rcov - desc "Remove rcov products for #{actual_name}" - task paste("clobber_", actual_name) do - rm_r rcov_dir rescue nil - end - - clobber_task = paste("clobber_", actual_name) - task :clobber => [clobber_task] - - task actual_name => clobber_task - end - self - end - - def rcov_option_list # :nodoc: - return "" unless rcov - ENV['RCOV_OPTS'] || rcov_opts.join(" ") || "" - end - - def spec_option_list # :nodoc: - STDERR.puts "RSPECOPTS is DEPRECATED and will be removed in a future version. Use SPEC_OPTS instead." if ENV['RSPECOPTS'] - ENV['SPEC_OPTS'] || ENV['RSPECOPTS'] || spec_opts.join(" ") || "" - end - - def evaluate(o) # :nodoc: - case o - when Proc then o.call - else o - end - end - - def spec_file_list # :nodoc: - if ENV['SPEC'] - FileList[ ENV['SPEC'] ] - else - result = [] - result += spec_files.to_a if spec_files - result += FileList[ pattern ].to_a if pattern - FileList[result] - end - end - - end - end -end - diff --git a/vendor/gems/rspec/lib/spec/rake/verify_rcov.rb b/vendor/gems/rspec/lib/spec/rake/verify_rcov.rb deleted file mode 100644 index 3328f9e9a7..0000000000 --- a/vendor/gems/rspec/lib/spec/rake/verify_rcov.rb +++ /dev/null @@ -1,52 +0,0 @@ -module RCov - # A task that can verify that the RCov coverage doesn't - # drop below a certain threshold. It should be run after - # running Spec::Rake::SpecTask. - class VerifyTask < Rake::TaskLib - # Name of the task. Defaults to :verify_rcov - attr_accessor :name - - # Path to the index.html file generated by RCov, which - # is the file containing the total coverage. - # Defaults to 'coverage/index.html' - attr_accessor :index_html - - # Whether or not to output details. Defaults to true. - attr_accessor :verbose - - # The threshold value (in percent) for coverage. If the - # actual coverage is not equal to this value, the task will raise an - # exception. - attr_accessor :threshold - - # Require the threshold value be met exactly. This is the default. - attr_accessor :require_exact_threshold - - def initialize(name=:verify_rcov) - @name = name - @index_html = 'coverage/index.html' - @verbose = true - @require_exact_threshold = true - yield self if block_given? - raise "Threshold must be set" if @threshold.nil? - define - end - - def define - desc "Verify that rcov coverage is at least #{threshold}%" - task @name do - total_coverage = nil - - File.open(index_html).each_line do |line| - if line =~ /(\d+\.\d+)%<\/tt>/ - total_coverage = eval($1) - break - end - end - puts "Coverage: #{total_coverage}% (threshold: #{threshold}%)" if verbose - raise "Coverage must be at least #{threshold}% but was #{total_coverage}%" if total_coverage < threshold - raise "Coverage has increased above the threshold of #{threshold}% to #{total_coverage}%. You should update your threshold value." if (total_coverage > threshold) and require_exact_threshold - end - end - end -end diff --git a/vendor/gems/rspec/lib/spec/runner.rb b/vendor/gems/rspec/lib/spec/runner.rb deleted file mode 100644 index 97ef95bd2a..0000000000 --- a/vendor/gems/rspec/lib/spec/runner.rb +++ /dev/null @@ -1,202 +0,0 @@ -require 'spec/runner/options' -require 'spec/runner/option_parser' -require 'spec/runner/example_group_runner' -require 'spec/runner/command_line' -require 'spec/runner/drb_command_line' -require 'spec/runner/backtrace_tweaker' -require 'spec/runner/reporter' -require 'spec/runner/spec_parser' -require 'spec/runner/class_and_arguments_parser' - -module Spec - # == ExampleGroups and Examples - # - # Rather than expressing examples in classes, RSpec uses a custom DSLL (DSL light) to - # describe groups of examples. - # - # A ExampleGroup is the equivalent of a fixture in xUnit-speak. It is a metaphor for the context - # in which you will run your executable example - a set of known objects in a known starting state. - # We begin be describing - # - # describe Account do - # - # before do - # @account = Account.new - # end - # - # it "should have a balance of $0" do - # @account.balance.should == Money.new(0, :dollars) - # end - # - # end - # - # We use the before block to set up the Example (given), and then the #it method to - # hold the example code that expresses the event (when) and the expected outcome (then). - # - # == Helper Methods - # - # A primary goal of RSpec is to keep the examples clear. We therefore prefer - # less indirection than you might see in xUnit examples and in well factored, DRY production code. We feel - # that duplication is OK if removing it makes it harder to understand an example without - # having to look elsewhere to understand its context. - # - # That said, RSpec does support some level of encapsulating common code in helper - # methods that can exist within a context or within an included module. - # - # == Setup and Teardown - # - # You can use before and after within a Example. Both methods take an optional - # scope argument so you can run the block before :each example or before :all examples - # - # describe "..." do - # before :all do - # ... - # end - # - # before :each do - # ... - # end - # - # it "should do something" do - # ... - # end - # - # it "should do something else" do - # ... - # end - # - # after :each do - # ... - # end - # - # after :all do - # ... - # end - # - # end - # - # The before :each block will run before each of the examples, once for each example. Likewise, - # the after :each block will run after each of the examples. - # - # It is also possible to specify a before :all and after :all - # block that will run only once for each behaviour, respectively before the first before :each - # and after the last after :each. The use of these is generally discouraged, because it - # introduces dependencies between the examples. Still, it might prove useful for very expensive operations - # if you know what you are doing. - # - # == Local helper methods - # - # You can include local helper methods by simply expressing them within a context: - # - # describe "..." do - # - # it "..." do - # helper_method - # end - # - # def helper_method - # ... - # end - # - # end - # - # == Included helper methods - # - # You can include helper methods in multiple contexts by expressing them within - # a module, and then including that module in your context: - # - # module AccountExampleHelperMethods - # def helper_method - # ... - # end - # end - # - # describe "A new account" do - # include AccountExampleHelperMethods - # before do - # @account = Account.new - # end - # - # it "should have a balance of $0" do - # helper_method - # @account.balance.should eql(Money.new(0, :dollars)) - # end - # end - # - # == Shared Example Groups - # - # You can define a shared Example Group, that may be used on other groups - # - # share_examples_for "All Editions" do - # it "all editions behaviour" ... - # end - # - # describe SmallEdition do - # it_should_behave_like "All Editions" - # - # it "should do small edition stuff" do - # ... - # end - # end - # - # You can also assign the shared group to a module and include that - # - # share_as :AllEditions do - # it "should do all editions stuff" ... - # end - # - # describe SmallEdition do - # it_should_behave_like AllEditions - # - # it "should do small edition stuff" do - # ... - # end - # end - # - # And, for those of you who prefer to use something more like Ruby, you - # can just include the module directly - # - # describe SmallEdition do - # include AllEditions - # - # it "should do small edition stuff" do - # ... - # end - # end - module Runner - class << self - def configuration # :nodoc: - @configuration ||= Spec::Example::Configuration.new - end - - # Use this to configure various configurable aspects of - # RSpec: - # - # Spec::Runner.configure do |configuration| - # # Configure RSpec here - # end - # - # The yielded configuration object is a - # Spec::Example::Configuration instance. See its RDoc - # for details about what you can do with it. - # - def configure - yield configuration - end - - def register_at_exit_hook # :nodoc: - $spec_runner_at_exit_hook_registered ||= nil - unless $spec_runner_at_exit_hook_registered - at_exit do - unless $! || Spec.run?; \ - success = Spec.run; \ - exit success if Spec.exit?; \ - end - end - $spec_runner_at_exit_hook_registered = true - end - end - - end - end -end diff --git a/vendor/gems/rspec/lib/spec/runner/backtrace_tweaker.rb b/vendor/gems/rspec/lib/spec/runner/backtrace_tweaker.rb deleted file mode 100644 index 5fd2fb99fc..0000000000 --- a/vendor/gems/rspec/lib/spec/runner/backtrace_tweaker.rb +++ /dev/null @@ -1,57 +0,0 @@ -module Spec - module Runner - class BacktraceTweaker - def clean_up_double_slashes(line) - line.gsub!('//','/') - end - end - - class NoisyBacktraceTweaker < BacktraceTweaker - def tweak_backtrace(error) - return if error.backtrace.nil? - error.backtrace.each do |line| - clean_up_double_slashes(line) - end - end - end - - # Tweaks raised Exceptions to mask noisy (unneeded) parts of the backtrace - class QuietBacktraceTweaker < BacktraceTweaker - unless defined?(IGNORE_PATTERNS) - root_dir = File.expand_path(File.join(__FILE__, '..', '..', '..', '..')) - spec_files = Dir["#{root_dir}/lib/*"].map do |path| - subpath = path[root_dir.length..-1] - /#{subpath}/ - end - IGNORE_PATTERNS = spec_files + [ - /\/lib\/ruby\//, - /bin\/spec:/, - /bin\/rcov:/, - /lib\/rspec_on_rails/, - /vendor\/rails/, - # TextMate's Ruby and RSpec plugins - /Ruby\.tmbundle\/Support\/tmruby.rb:/, - /RSpec\.tmbundle\/Support\/lib/, - /temp_textmate\./, - /mock_frameworks\/rspec/, - /spec_server/ - ] - end - - def tweak_backtrace(error) - return if error.backtrace.nil? - error.backtrace.collect! do |line| - clean_up_double_slashes(line) - IGNORE_PATTERNS.each do |ignore| - if line =~ ignore - line = nil - break - end - end - line - end - error.backtrace.compact! - end - end - end -end diff --git a/vendor/gems/rspec/lib/spec/runner/class_and_arguments_parser.rb b/vendor/gems/rspec/lib/spec/runner/class_and_arguments_parser.rb deleted file mode 100644 index 65dc4519c8..0000000000 --- a/vendor/gems/rspec/lib/spec/runner/class_and_arguments_parser.rb +++ /dev/null @@ -1,16 +0,0 @@ -module Spec - module Runner - class ClassAndArgumentsParser - class << self - def parse(s) - if s =~ /([a-zA-Z_]+(?:::[a-zA-Z_]+)*):?(.*)/ - arg = $2 == "" ? nil : $2 - [$1, arg] - else - raise "Couldn't parse #{s.inspect}" - end - end - end - end - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/lib/spec/runner/command_line.rb b/vendor/gems/rspec/lib/spec/runner/command_line.rb deleted file mode 100644 index 9849c48532..0000000000 --- a/vendor/gems/rspec/lib/spec/runner/command_line.rb +++ /dev/null @@ -1,28 +0,0 @@ -require 'spec/runner/option_parser' - -module Spec - module Runner - # Facade to run specs without having to fork a new ruby process (using `spec ...`) - class CommandLine - class << self - # Runs specs. +argv+ is the commandline args as per the spec commandline API, +err+ - # and +out+ are the streams output will be written to. - def run(instance_rspec_options) - # NOTE - this call to init_rspec_options is not spec'd, but neither is any of this - # swapping of $rspec_options. That is all here to enable rspec to run against itself - # and maintain coverage in a single process. Therefore, DO NOT mess with this stuff - # unless you know what you are doing! - init_rspec_options(instance_rspec_options) - orig_rspec_options = rspec_options - begin - $rspec_options = instance_rspec_options - return $rspec_options.run_examples - ensure - ::Spec.run = true - $rspec_options = orig_rspec_options - end - end - end - end - end -end diff --git a/vendor/gems/rspec/lib/spec/runner/drb_command_line.rb b/vendor/gems/rspec/lib/spec/runner/drb_command_line.rb deleted file mode 100644 index 6c340cfea9..0000000000 --- a/vendor/gems/rspec/lib/spec/runner/drb_command_line.rb +++ /dev/null @@ -1,20 +0,0 @@ -require "drb/drb" - -module Spec - module Runner - # Facade to run specs by connecting to a DRB server - class DrbCommandLine - # Runs specs on a DRB server. Note that this API is similar to that of - # CommandLine - making it possible for clients to use both interchangeably. - def self.run(options) - begin - DRb.start_service - spec_server = DRbObject.new_with_uri("druby://localhost:8989") - spec_server.run(options.argv, options.error_stream, options.output_stream) - rescue DRb::DRbConnError => e - options.error_stream.puts "No server is running" - end - end - end - end -end diff --git a/vendor/gems/rspec/lib/spec/runner/example_group_runner.rb b/vendor/gems/rspec/lib/spec/runner/example_group_runner.rb deleted file mode 100644 index 7275c6a882..0000000000 --- a/vendor/gems/rspec/lib/spec/runner/example_group_runner.rb +++ /dev/null @@ -1,59 +0,0 @@ -module Spec - module Runner - class ExampleGroupRunner - def initialize(options) - @options = options - end - - def load_files(files) - # It's important that loading files (or choosing not to) stays the - # responsibility of the ExampleGroupRunner. Some implementations (like) - # the one using DRb may choose *not* to load files, but instead tell - # someone else to do it over the wire. - files.each do |file| - load file - end - end - - def run - prepare - success = true - example_groups.each do |example_group| - success = success & example_group.run - end - return success - ensure - finish - end - - protected - def prepare - reporter.start(number_of_examples) - example_groups.reverse! if reverse - end - - def finish - reporter.end - reporter.dump - end - - def reporter - @options.reporter - end - - def reverse - @options.reverse - end - - def example_groups - @options.example_groups - end - - def number_of_examples - @options.number_of_examples - end - end - # TODO: BT - Deprecate BehaviourRunner? - BehaviourRunner = ExampleGroupRunner - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/lib/spec/runner/formatter/base_formatter.rb b/vendor/gems/rspec/lib/spec/runner/formatter/base_formatter.rb deleted file mode 100644 index c8647cf509..0000000000 --- a/vendor/gems/rspec/lib/spec/runner/formatter/base_formatter.rb +++ /dev/null @@ -1,78 +0,0 @@ -module Spec - module Runner - module Formatter - # Baseclass for formatters that implements all required methods as no-ops. - class BaseFormatter - attr_accessor :example_group, :options, :where - def initialize(options, where) - @options = options - @where = where - end - - # This method is invoked before any examples are run, right after - # they have all been collected. This can be useful for special - # formatters that need to provide progress on feedback (graphical ones) - # - # This method will only be invoked once, and the next one to be invoked - # is #add_example_group - def start(example_count) - end - - # This method is invoked at the beginning of the execution of each example_group. - # +name+ is the name of the example_group and +first+ is true if it is the - # first example_group - otherwise it's false. - # - # The next method to be invoked after this is #example_failed or #example_finished - def add_example_group(example_group) - @example_group = example_group - end - - # This method is invoked when an +example+ starts. - def example_started(example) - end - - # This method is invoked when an +example+ passes. - def example_passed(example) - end - - # This method is invoked when an +example+ fails, i.e. an exception occurred - # inside it (such as a failed should or other exception). +counter+ is the - # sequence number of the failure (starting at 1) and +failure+ is the associated - # Failure object. - def example_failed(example, counter, failure) - end - - # This method is invoked when an example is not yet implemented (i.e. has not - # been provided a block), or when an ExamplePendingError is raised. - # +message+ is the message from the ExamplePendingError, if it exists, or the - # default value of "Not Yet Implemented" - def example_pending(example_group_description, example, message) - end - - # This method is invoked after all of the examples have executed. The next method - # to be invoked after this one is #dump_failure (once for each failed example), - def start_dump - end - - # Dumps detailed information about an example failure. - # This method is invoked for each failed example after all examples have run. +counter+ is the sequence number - # of the associated example. +failure+ is a Failure object, which contains detailed - # information about the failure. - def dump_failure(counter, failure) - end - - # This method is invoked after the dumping of examples and failures. - def dump_summary(duration, example_count, failure_count, pending_count) - end - - # This gets invoked after the summary if option is set to do so. - def dump_pending - end - - # This method is invoked at the very end. Allows the formatter to clean up, like closing open streams. - def close - end - end - end - end -end diff --git a/vendor/gems/rspec/lib/spec/runner/formatter/base_text_formatter.rb b/vendor/gems/rspec/lib/spec/runner/formatter/base_text_formatter.rb deleted file mode 100644 index 859b2641d1..0000000000 --- a/vendor/gems/rspec/lib/spec/runner/formatter/base_text_formatter.rb +++ /dev/null @@ -1,130 +0,0 @@ -require 'spec/runner/formatter/base_formatter' - -module Spec - module Runner - module Formatter - # Baseclass for text-based formatters. Can in fact be used for - # non-text based ones too - just ignore the +output+ constructor - # argument. - class BaseTextFormatter < BaseFormatter - attr_reader :output, :pending_examples - # Creates a new instance that will write to +where+. If +where+ is a - # String, output will be written to the File with that name, otherwise - # +where+ is exected to be an IO (or an object that responds to #puts and #write). - def initialize(options, where) - super - if where.is_a?(String) - @output = File.open(where, 'w') - elsif where == STDOUT - @output = Kernel - def @output.flush - STDOUT.flush - end - else - @output = where - end - @pending_examples = [] - end - - def example_pending(example_group_description, example, message) - @pending_examples << ["#{example_group_description} #{example.description}", message] - end - - def dump_failure(counter, failure) - @output.puts - @output.puts "#{counter.to_s})" - @output.puts colourise("#{failure.header}\n#{failure.exception.message}", failure) - @output.puts format_backtrace(failure.exception.backtrace) - @output.flush - end - - def colourise(s, failure) - if(failure.expectation_not_met?) - red(s) - elsif(failure.pending_fixed?) - blue(s) - else - magenta(s) - end - end - - def dump_summary(duration, example_count, failure_count, pending_count) - return if dry_run? - @output.puts - @output.puts "Finished in #{duration} seconds" - @output.puts - - summary = "#{example_count} example#{'s' unless example_count == 1}, #{failure_count} failure#{'s' unless failure_count == 1}" - summary << ", #{pending_count} pending" if pending_count > 0 - - if failure_count == 0 - if pending_count > 0 - @output.puts yellow(summary) - else - @output.puts green(summary) - end - else - @output.puts red(summary) - end - @output.flush - end - - def dump_pending - unless @pending_examples.empty? - @output.puts - @output.puts "Pending:" - @pending_examples.each do |pending_example| - @output.puts "#{pending_example[0]} (#{pending_example[1]})" - end - end - @output.flush - end - - def close - if IO === @output - @output.close - end - end - - def format_backtrace(backtrace) - return "" if backtrace.nil? - backtrace.map { |line| backtrace_line(line) }.join("\n") - end - - protected - - def colour? - @options.colour ? true : false - end - - def dry_run? - @options.dry_run ? true : false - end - - def backtrace_line(line) - line.sub(/\A([^:]+:\d+)$/, '\\1:') - end - - def colour(text, colour_code) - return text unless colour? && output_to_tty? - "#{colour_code}#{text}\e[0m" - end - - def output_to_tty? - begin - @output == Kernel || @output.tty? - rescue NoMethodError - false - end - end - - def green(text); colour(text, "\e[32m"); end - def red(text); colour(text, "\e[31m"); end - def magenta(text); colour(text, "\e[35m"); end - def yellow(text); colour(text, "\e[33m"); end - def blue(text); colour(text, "\e[34m"); end - - end - end - end -end diff --git a/vendor/gems/rspec/lib/spec/runner/formatter/failing_example_groups_formatter.rb b/vendor/gems/rspec/lib/spec/runner/formatter/failing_example_groups_formatter.rb deleted file mode 100644 index 5a46079833..0000000000 --- a/vendor/gems/rspec/lib/spec/runner/formatter/failing_example_groups_formatter.rb +++ /dev/null @@ -1,31 +0,0 @@ -require 'spec/runner/formatter/base_text_formatter' - -module Spec - module Runner - module Formatter - class FailingExampleGroupsFormatter < BaseTextFormatter - def add_example_group(example_group) - super - @example_group_description_parts = example_group.description_parts - end - - def example_failed(example, counter, failure) - if @example_group_description_parts - description_parts = @example_group_description_parts.collect do |description| - description =~ /(.*) \(druby.*\)$/ ? $1 : description - end - @output.puts ::Spec::Example::ExampleGroupMethods.description_text(*description_parts) - @output.flush - @example_group_description_parts = nil - end - end - - def dump_failure(counter, failure) - end - - def dump_summary(duration, example_count, failure_count, pending_count) - end - end - end - end -end diff --git a/vendor/gems/rspec/lib/spec/runner/formatter/failing_examples_formatter.rb b/vendor/gems/rspec/lib/spec/runner/formatter/failing_examples_formatter.rb deleted file mode 100644 index e3a271c8b9..0000000000 --- a/vendor/gems/rspec/lib/spec/runner/formatter/failing_examples_formatter.rb +++ /dev/null @@ -1,20 +0,0 @@ -require 'spec/runner/formatter/base_text_formatter' - -module Spec - module Runner - module Formatter - class FailingExamplesFormatter < BaseTextFormatter - def example_failed(example, counter, failure) - @output.puts "#{example_group.description} #{example.description}" - @output.flush - end - - def dump_failure(counter, failure) - end - - def dump_summary(duration, example_count, failure_count, pending_count) - end - end - end - end -end diff --git a/vendor/gems/rspec/lib/spec/runner/formatter/html_formatter.rb b/vendor/gems/rspec/lib/spec/runner/formatter/html_formatter.rb deleted file mode 100644 index ad153c8dc2..0000000000 --- a/vendor/gems/rspec/lib/spec/runner/formatter/html_formatter.rb +++ /dev/null @@ -1,333 +0,0 @@ -require 'erb' -require 'spec/runner/formatter/base_text_formatter' - -module Spec - module Runner - module Formatter - class HtmlFormatter < BaseTextFormatter - include ERB::Util # for the #h method - - def initialize(options, output) - super - @current_example_group_number = 0 - @current_example_number = 0 - end - - # The number of the currently running example_group - def current_example_group_number - @current_example_group_number - end - - # The number of the currently running example (a global counter) - def current_example_number - @current_example_number - end - - def start(example_count) - @example_count = example_count - - @output.puts html_header - @output.puts report_header - @output.flush - end - - def add_example_group(example_group) - super - @example_group_red = false - @example_group_red = false - @current_example_group_number += 1 - unless current_example_group_number == 1 - @output.puts " " - @output.puts "
    " - end - @output.puts "
    " - @output.puts "
    " - @output.puts "
    #{h(example_group.description)}
    " - @output.flush - end - - def start_dump - @output.puts "
    " - @output.puts "
    " - @output.flush - end - - def example_started(example) - @current_example_number += 1 - end - - def example_passed(example) - move_progress - @output.puts "
    #{h(example.description)}
    " - @output.flush - end - - def example_failed(example, counter, failure) - extra = extra_failure_content(failure) - failure_style = failure.pending_fixed? ? 'pending_fixed' : 'failed' - @output.puts " " unless @header_red - @header_red = true - @output.puts " " unless @example_group_red - @example_group_red = true - move_progress - @output.puts "
    " - @output.puts " #{h(example.description)}" - @output.puts "
    " - @output.puts "
    #{h(failure.exception.message)}
    " unless failure.exception.nil? - @output.puts "
    #{format_backtrace(failure.exception.backtrace)}
    " unless failure.exception.nil? - @output.puts extra unless extra == "" - @output.puts "
    " - @output.puts "
    " - @output.flush - end - - def example_pending(example_group_description, example, message) - @output.puts " " unless @header_red - @output.puts " " unless @example_group_red - move_progress - @output.puts "
    #{h(example.description)} (PENDING: #{h(message)})
    " - @output.flush - end - - # Override this method if you wish to output extra HTML for a failed spec. For example, you - # could output links to images or other files produced during the specs. - # - def extra_failure_content(failure) - require 'spec/runner/formatter/snippet_extractor' - @snippet_extractor ||= SnippetExtractor.new - "
    #{@snippet_extractor.snippet(failure.exception)}
    " - end - - def move_progress - @output.puts " " - @output.flush - end - - def percent_done - result = 100.0 - if @example_count != 0 - result = ((current_example_number).to_f / @example_count.to_f * 1000).to_i / 10.0 - end - result - end - - def dump_failure(counter, failure) - end - - def dump_summary(duration, example_count, failure_count, pending_count) - if dry_run? - totals = "This was a dry-run" - else - totals = "#{example_count} example#{'s' unless example_count == 1}, #{failure_count} failure#{'s' unless failure_count == 1}" - totals << ", #{pending_count} pending" if pending_count > 0 - end - @output.puts "" - @output.puts "" - @output.puts "
    " - @output.puts "" - @output.puts "" - @output.puts "" - @output.flush - end - - def html_header - <<-EOF - - - - - RSpec results - - - - - - -EOF - end - - def report_header - <<-EOF -
    - - - -
    -

    RSpec Results

    - -
    -

     

    -

     

    -
    -
    - -
    -EOF - end - - def global_scripts - <<-EOF -function moveProgressBar(percentDone) { - document.getElementById("rspec-header").style.width = percentDone +"%"; -} -function makeRed(element_id) { - document.getElementById(element_id).style.background = '#C40D0D'; - document.getElementById(element_id).style.color = '#FFFFFF'; -} - -function makeYellow(element_id) { - if (element_id == "rspec-header" && document.getElementById(element_id).style.background != '#C40D0D') - { - document.getElementById(element_id).style.background = '#FAF834'; - document.getElementById(element_id).style.color = '#000000'; - } - else - { - document.getElementById(element_id).style.background = '#FAF834'; - document.getElementById(element_id).style.color = '#000000'; - } -} -EOF - end - - def global_styles - <<-EOF -#rspec-header { - background: #65C400; color: #fff; -} - -.rspec-report h1 { - margin: 0px 10px 0px 10px; - padding: 10px; - font-family: "Lucida Grande", Helvetica, sans-serif; - font-size: 1.8em; -} - -#summary { - margin: 0; padding: 5px 10px; - font-family: "Lucida Grande", Helvetica, sans-serif; - text-align: right; - position: absolute; - top: 0px; - right: 0px; -} - -#summary p { - margin: 0 0 0 2px; -} - -#summary #totals { - font-size: 1.2em; -} - -.example_group { - margin: 0 10px 5px; - background: #fff; -} - -dl { - margin: 0; padding: 0 0 5px; - font: normal 11px "Lucida Grande", Helvetica, sans-serif; -} - -dt { - padding: 3px; - background: #65C400; - color: #fff; - font-weight: bold; -} - -dd { - margin: 5px 0 5px 5px; - padding: 3px 3px 3px 18px; -} - -dd.spec.passed { - border-left: 5px solid #65C400; - border-bottom: 1px solid #65C400; - background: #DBFFB4; color: #3D7700; -} - -dd.spec.failed { - border-left: 5px solid #C20000; - border-bottom: 1px solid #C20000; - color: #C20000; background: #FFFBD3; -} - -dd.spec.not_implemented { - border-left: 5px solid #FAF834; - border-bottom: 1px solid #FAF834; - background: #FCFB98; color: #131313; -} - -dd.spec.pending_fixed { - border-left: 5px solid #0000C2; - border-bottom: 1px solid #0000C2; - color: #0000C2; background: #D3FBFF; -} - -.backtrace { - color: #000; - font-size: 12px; -} - -a { - color: #BE5C00; -} - -/* Ruby code, style similar to vibrant ink */ -.ruby { - font-size: 12px; - font-family: monospace; - color: white; - background-color: black; - padding: 0.1em 0 0.2em 0; -} - -.ruby .keyword { color: #FF6600; } -.ruby .constant { color: #339999; } -.ruby .attribute { color: white; } -.ruby .global { color: white; } -.ruby .module { color: white; } -.ruby .class { color: white; } -.ruby .string { color: #66FF00; } -.ruby .ident { color: white; } -.ruby .method { color: #FFCC00; } -.ruby .number { color: white; } -.ruby .char { color: white; } -.ruby .comment { color: #9933CC; } -.ruby .symbol { color: white; } -.ruby .regex { color: #44B4CC; } -.ruby .punct { color: white; } -.ruby .escape { color: white; } -.ruby .interp { color: white; } -.ruby .expr { color: white; } - -.ruby .offending { background-color: gray; } -.ruby .linenum { - width: 75px; - padding: 0.1em 1em 0.2em 0; - color: #000000; - background-color: #FFFBD3; -} -EOF - end - end - end - end -end diff --git a/vendor/gems/rspec/lib/spec/runner/formatter/profile_formatter.rb b/vendor/gems/rspec/lib/spec/runner/formatter/profile_formatter.rb deleted file mode 100644 index 3784f3ac77..0000000000 --- a/vendor/gems/rspec/lib/spec/runner/formatter/profile_formatter.rb +++ /dev/null @@ -1,47 +0,0 @@ -require 'spec/runner/formatter/progress_bar_formatter' - -module Spec - module Runner - module Formatter - class ProfileFormatter < ProgressBarFormatter - - def initialize(options, where) - super - @example_times = [] - end - - def start(count) - @output.puts "Profiling enabled." - end - - def example_started(example) - @time = Time.now - end - - def example_passed(example) - super - @example_times << [ - example_group.description, - example.description, - Time.now - @time - ] - end - - def start_dump - super - @output.puts "\n\nTop 10 slowest examples:\n" - - @example_times = @example_times.sort_by do |description, example, time| - time - end.reverse - - @example_times[0..9].each do |description, example, time| - @output.print red(sprintf("%.7f", time)) - @output.puts " #{description} #{example}" - end - @output.flush - end - end - end - end -end diff --git a/vendor/gems/rspec/lib/spec/runner/formatter/progress_bar_formatter.rb b/vendor/gems/rspec/lib/spec/runner/formatter/progress_bar_formatter.rb deleted file mode 100644 index 8d0e504327..0000000000 --- a/vendor/gems/rspec/lib/spec/runner/formatter/progress_bar_formatter.rb +++ /dev/null @@ -1,30 +0,0 @@ -require 'spec/runner/formatter/base_text_formatter' - -module Spec - module Runner - module Formatter - class ProgressBarFormatter < BaseTextFormatter - def example_failed(example, counter, failure) - @output.print colourise('F', failure) - @output.flush - end - - def example_passed(example) - @output.print green('.') - @output.flush - end - - def example_pending(example_group_description, example, message) - super - @output.print yellow('P') - @output.flush - end - - def start_dump - @output.puts - @output.flush - end - end - end - end -end diff --git a/vendor/gems/rspec/lib/spec/runner/formatter/snippet_extractor.rb b/vendor/gems/rspec/lib/spec/runner/formatter/snippet_extractor.rb deleted file mode 100644 index 41119fe463..0000000000 --- a/vendor/gems/rspec/lib/spec/runner/formatter/snippet_extractor.rb +++ /dev/null @@ -1,52 +0,0 @@ -module Spec - module Runner - module Formatter - # This class extracts code snippets by looking at the backtrace of the passed error - class SnippetExtractor #:nodoc: - class NullConverter; def convert(code, pre); code; end; end #:nodoc: - begin; require 'rubygems'; require 'syntax/convertors/html'; @@converter = Syntax::Convertors::HTML.for_syntax "ruby"; rescue LoadError => e; @@converter = NullConverter.new; end - - def snippet(error) - raw_code, line = snippet_for(error.backtrace[0]) - highlighted = @@converter.convert(raw_code, false) - highlighted << "\n# gem install syntax to get syntax highlighting" if @@converter.is_a?(NullConverter) - post_process(highlighted, line) - end - - def snippet_for(error_line) - if error_line =~ /(.*):(\d+)/ - file = $1 - line = $2.to_i - [lines_around(file, line), line] - else - ["# Couldn't get snippet for #{error_line}", 1] - end - end - - def lines_around(file, line) - if File.file?(file) - lines = File.open(file).read.split("\n") - min = [0, line-3].max - max = [line+1, lines.length-1].min - selected_lines = [] - selected_lines.join("\n") - lines[min..max].join("\n") - else - "# Couldn't get snippet for #{file}" - end - end - - def post_process(highlighted, offending_line) - new_lines = [] - highlighted.split("\n").each_with_index do |line, i| - new_line = "#{offending_line+i-2}#{line}" - new_line = "#{new_line}" if i == 2 - new_lines << new_line - end - new_lines.join("\n") - end - - end - end - end -end diff --git a/vendor/gems/rspec/lib/spec/runner/formatter/specdoc_formatter.rb b/vendor/gems/rspec/lib/spec/runner/formatter/specdoc_formatter.rb deleted file mode 100644 index f426dc9487..0000000000 --- a/vendor/gems/rspec/lib/spec/runner/formatter/specdoc_formatter.rb +++ /dev/null @@ -1,39 +0,0 @@ -require 'spec/runner/formatter/base_text_formatter' - -module Spec - module Runner - module Formatter - class SpecdocFormatter < BaseTextFormatter - def add_example_group(example_group) - super - output.puts - output.puts example_group.description - output.flush - end - - def example_failed(example, counter, failure) - message = if failure.expectation_not_met? - "- #{example.description} (FAILED - #{counter})" - else - "- #{example.description} (ERROR - #{counter})" - end - - output.puts(failure.expectation_not_met? ? red(message) : magenta(message)) - output.flush - end - - def example_passed(example) - message = "- #{example.description}" - output.puts green(message) - output.flush - end - - def example_pending(example_group_description, example, message) - super - output.puts yellow("- #{example.description} (PENDING: #{message})") - output.flush - end - end - end - end -end diff --git a/vendor/gems/rspec/lib/spec/runner/formatter/story/html_formatter.rb b/vendor/gems/rspec/lib/spec/runner/formatter/story/html_formatter.rb deleted file mode 100644 index 5a81346837..0000000000 --- a/vendor/gems/rspec/lib/spec/runner/formatter/story/html_formatter.rb +++ /dev/null @@ -1,128 +0,0 @@ -require 'erb' -require 'spec/runner/formatter/base_text_formatter' - -module Spec - module Runner - module Formatter - module Story - class HtmlFormatter < BaseTextFormatter - include ERB::Util - - def run_started(count) - @output.puts <<-EOF - - - - - Stories - - - - - - - - - -
    -EOF - end - - def collected_steps(steps) - unless steps.empty? - @output.puts "
      " - steps.each do |step| - @output.puts "
    • #{step}
    • " - end - @output.puts "
    " - end - end - - def run_ended - @output.puts <<-EOF -
    - - -EOF - end - - def story_started(title, narrative) - @output.puts <<-EOF -
    -
    Story: #{h title}
    -
    -

    - #{h(narrative).split("\n").join("
    ")} -

    -EOF - end - - def story_ended(title, narrative) - @output.puts <<-EOF -
    -
    -EOF - end - - def scenario_started(story_title, scenario_name) - @output.puts <<-EOF -
    -
    Scenario: #{h scenario_name}
    -
    -
      -EOF - end - - def scenario_ended - @output.puts <<-EOF -
    -
    -
    -EOF - end - - def found_scenario(type, description) - end - - def scenario_succeeded(story_title, scenario_name) - scenario_ended - end - - def scenario_pending(story_title, scenario_name, reason) - scenario_ended - end - - def scenario_failed(story_title, scenario_name, err) - scenario_ended - end - - def step_upcoming(type, description, *args) - end - - def step_succeeded(type, description, *args) - print_step('passed', type, description, *args) # TODO: uses succeeded CSS class - end - - def step_pending(type, description, *args) - print_step('pending', type, description, *args) - end - - def step_failed(type, description, *args) - print_step('failed', type, description, *args) - end - - def print_step(klass, type, description, *args) - spans = args.map { |arg| "#{arg}" } - desc_string = description.step_name - arg_regexp = description.arg_regexp - i = -1 - inner = type.to_s.capitalize + ' ' + desc_string.gsub(arg_regexp) { |param| spans[i+=1] } - @output.puts "
  • #{inner}
  • " - end - end - end - end - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/lib/spec/runner/formatter/story/plain_text_formatter.rb b/vendor/gems/rspec/lib/spec/runner/formatter/story/plain_text_formatter.rb deleted file mode 100644 index ad7a2fa734..0000000000 --- a/vendor/gems/rspec/lib/spec/runner/formatter/story/plain_text_formatter.rb +++ /dev/null @@ -1,131 +0,0 @@ -require 'spec/runner/formatter/base_text_formatter' - -module Spec - module Runner - module Formatter - module Story - class PlainTextFormatter < BaseTextFormatter - def initialize(options, where) - super - @successful_scenario_count = 0 - @pending_scenario_count = 0 - @failed_scenarios = [] - @pending_steps = [] - @previous_type = nil - end - - def run_started(count) - @count = count - @output.puts "Running #@count scenarios\n\n" - end - - def story_started(title, narrative) - @current_story_title = title - @output.puts "Story: #{title}\n\n" - narrative.each_line do |line| - @output.print " " - @output.print line - end - end - - def story_ended(title, narrative) - @output.puts - @output.puts - end - - def scenario_started(story_title, scenario_name) - @current_scenario_name = scenario_name - @scenario_already_failed = false - @output.print "\n\n Scenario: #{scenario_name}" - @scenario_ok = true - end - - def scenario_succeeded(story_title, scenario_name) - @successful_scenario_count += 1 - end - - def scenario_failed(story_title, scenario_name, err) - @options.backtrace_tweaker.tweak_backtrace(err) - @failed_scenarios << [story_title, scenario_name, err] unless @scenario_already_failed - @scenario_already_failed = true - end - - def scenario_pending(story_title, scenario_name, msg) - @pending_scenario_count += 1 unless @scenario_already_failed - @scenario_already_failed = true - end - - def run_ended - @output.puts "#@count scenarios: #@successful_scenario_count succeeded, #{@failed_scenarios.size} failed, #@pending_scenario_count pending" - unless @pending_steps.empty? - @output.puts "\nPending Steps:" - @pending_steps.each_with_index do |pending, i| - story_name, scenario_name, msg = pending - @output.puts "#{i+1}) #{story_name} (#{scenario_name}): #{msg}" - end - end - unless @failed_scenarios.empty? - @output.print "\nFAILURES:" - @failed_scenarios.each_with_index do |failure, i| - title, scenario_name, err = failure - @output.print %[ - #{i+1}) #{title} (#{scenario_name}) FAILED - #{err.class}: #{err.message} - #{err.backtrace.join("\n")} -] - end - end - end - - def step_upcoming(type, description, *args) - end - - def step_succeeded(type, description, *args) - found_step(type, description, false, *args) - end - - def step_pending(type, description, *args) - found_step(type, description, false, *args) - @pending_steps << [@current_story_title, @current_scenario_name, description] - @output.print " (PENDING)" - @scenario_ok = false - end - - def step_failed(type, description, *args) - found_step(type, description, true, *args) - @output.print red(@scenario_ok ? " (FAILED)" : " (SKIPPED)") - @scenario_ok = false - end - - def collected_steps(steps) - end - - def method_missing(sym, *args, &block) #:nodoc: - # noop - ignore unknown messages - end - - private - - def found_step(type, description, failed, *args) - desc_string = description.step_name - arg_regexp = description.arg_regexp - text = if(type == @previous_type) - "\n And " - else - "\n\n #{type.to_s.capitalize} " - end - i = -1 - text << desc_string.gsub(arg_regexp) { |param| args[i+=1] } - @output.print(failed ? red(text) : green(text)) - - if type == :'given scenario' - @previous_type = :given - else - @previous_type = type - end - end - end - end - end - end -end diff --git a/vendor/gems/rspec/lib/spec/runner/formatter/text_mate_formatter.rb b/vendor/gems/rspec/lib/spec/runner/formatter/text_mate_formatter.rb deleted file mode 100644 index 4c0a9c7de1..0000000000 --- a/vendor/gems/rspec/lib/spec/runner/formatter/text_mate_formatter.rb +++ /dev/null @@ -1,16 +0,0 @@ -require 'spec/runner/formatter/html_formatter' - -module Spec - module Runner - module Formatter - # Formats backtraces so they're clickable by TextMate - class TextMateFormatter < HtmlFormatter - def backtrace_line(line) - line.gsub(/([^:]*\.rb):(\d*)/) do - "#{$1}:#{$2} " - end - end - end - end - end -end diff --git a/vendor/gems/rspec/lib/spec/runner/heckle_runner.rb b/vendor/gems/rspec/lib/spec/runner/heckle_runner.rb deleted file mode 100644 index 7695fe7946..0000000000 --- a/vendor/gems/rspec/lib/spec/runner/heckle_runner.rb +++ /dev/null @@ -1,72 +0,0 @@ -begin - require 'rubygems' - require 'heckle' -rescue LoadError ; raise "You must gem install heckle to use --heckle" ; end - -module Spec - module Runner - # Creates a new Heckler configured to heckle all methods in the classes - # whose name matches +filter+ - class HeckleRunner - def initialize(filter, heckle_class=Heckler) - @filter = filter - @heckle_class = heckle_class - end - - # Runs all the example groups held by +rspec_options+ once for each of the - # methods in the matched classes. - def heckle_with - if @filter =~ /(.*)[#\.](.*)/ - heckle_method($1, $2) - else - heckle_class_or_module(@filter) - end - end - - def heckle_method(class_name, method_name) - verify_constant(class_name) - heckle = @heckle_class.new(class_name, method_name, rspec_options) - heckle.validate - end - - def heckle_class_or_module(class_or_module_name) - verify_constant(class_or_module_name) - pattern = /^#{class_or_module_name}/ - classes = [] - ObjectSpace.each_object(Class) do |klass| - classes << klass if klass.name =~ pattern - end - - classes.each do |klass| - klass.instance_methods(false).each do |method_name| - heckle = @heckle_class.new(klass.name, method_name, rspec_options) - heckle.validate - end - end - end - - def verify_constant(name) - begin - # This is defined in Heckle - name.to_class - rescue - raise "Heckling failed - \"#{name}\" is not a known class or module" - end - end - end - - #Supports Heckle 1.2 and prior (earlier versions used Heckle::Base) - class Heckler < (Heckle.const_defined?(:Base) ? Heckle::Base : Heckle) - def initialize(klass_name, method_name, rspec_options) - super(klass_name, method_name) - @rspec_options = rspec_options - end - - def tests_pass? - success = @rspec_options.run_examples - success - end - - end - end -end diff --git a/vendor/gems/rspec/lib/spec/runner/heckle_runner_unsupported.rb b/vendor/gems/rspec/lib/spec/runner/heckle_runner_unsupported.rb deleted file mode 100644 index 02aa379537..0000000000 --- a/vendor/gems/rspec/lib/spec/runner/heckle_runner_unsupported.rb +++ /dev/null @@ -1,10 +0,0 @@ -module Spec - module Runner - # Dummy implementation for Windows that just fails (Heckle is not supported on Windows) - class HeckleRunner - def initialize(filter) - raise "Heckle not supported on Windows" - end - end - end -end diff --git a/vendor/gems/rspec/lib/spec/runner/option_parser.rb b/vendor/gems/rspec/lib/spec/runner/option_parser.rb deleted file mode 100644 index 4bc84c8126..0000000000 --- a/vendor/gems/rspec/lib/spec/runner/option_parser.rb +++ /dev/null @@ -1,201 +0,0 @@ -require 'optparse' -require 'stringio' - -module Spec - module Runner - class OptionParser < ::OptionParser - class << self - def parse(args, err, out) - parser = new(err, out) - parser.parse(args) - parser.options - end - end - - attr_reader :options - - OPTIONS = { - :pattern => ["-p", "--pattern [PATTERN]","Limit files loaded to those matching this pattern. Defaults to '**/*_spec.rb'", - "Separate multiple patterns with commas.", - "Applies only to directories named on the command line (files", - "named explicitly on the command line will be loaded regardless)."], - :diff => ["-D", "--diff [FORMAT]", "Show diff of objects that are expected to be equal when they are not", - "Builtin formats: unified|u|context|c", - "You can also specify a custom differ class", - "(in which case you should also specify --require)"], - :colour => ["-c", "--colour", "--color", "Show coloured (red/green) output"], - :example => ["-e", "--example [NAME|FILE_NAME]", "Execute example(s) with matching name(s). If the argument is", - "the path to an existing file (typically generated by a previous", - "run using --format failing_examples:file.txt), then the examples", - "on each line of thatfile will be executed. If the file is empty,", - "all examples will be run (as if --example was not specified).", - " ", - "If the argument is not an existing file, then it is treated as", - "an example name directly, causing RSpec to run just the example", - "matching that name"], - :specification => ["-s", "--specification [NAME]", "DEPRECATED - use -e instead", "(This will be removed when autotest works with -e)"], - :line => ["-l", "--line LINE_NUMBER", Integer, "Execute behaviout or specification at given line.", - "(does not work for dynamically generated specs)"], - :format => ["-f", "--format FORMAT[:WHERE]", "Specifies what format to use for output. Specify WHERE to tell", - "the formatter where to write the output. All built-in formats", - "expect WHERE to be a file name, and will write to STDOUT if it's", - "not specified. The --format option may be specified several times", - "if you want several outputs", - " ", - "Builtin formats for examples: ", - "progress|p : Text progress", - "profile|o : Text progress with profiling of 10 slowest examples", - "specdoc|s : Example doc as text", - "html|h : A nice HTML report", - "failing_examples|e : Write all failing examples - input for --example", - "failing_example_groups|g : Write all failing example groups - input for --example", - " ", - "Builtin formats for stories: ", - "plain|p : Plain Text", - "html|h : A nice HTML report", - " ", - "FORMAT can also be the name of a custom formatter class", - "(in which case you should also specify --require to load it)"], - :require => ["-r", "--require FILE", "Require FILE before running specs", - "Useful for loading custom formatters or other extensions.", - "If this option is used it must come before the others"], - :backtrace => ["-b", "--backtrace", "Output full backtrace"], - :loadby => ["-L", "--loadby STRATEGY", "Specify the strategy by which spec files should be loaded.", - "STRATEGY can currently only be 'mtime' (File modification time)", - "By default, spec files are loaded in alphabetical order if --loadby", - "is not specified."], - :reverse => ["-R", "--reverse", "Run examples in reverse order"], - :timeout => ["-t", "--timeout FLOAT", "Interrupt and fail each example that doesn't complete in the", - "specified time"], - :heckle => ["-H", "--heckle CODE", "If all examples pass, this will mutate the classes and methods", - "identified by CODE little by little and run all the examples again", - "for each mutation. The intent is that for each mutation, at least", - "one example *should* fail, and RSpec will tell you if this is not the", - "case. CODE should be either Some::Module, Some::Class or", - "Some::Fabulous#method}"], - :dry_run => ["-d", "--dry-run", "Invokes formatters without executing the examples."], - :options_file => ["-O", "--options PATH", "Read options from a file"], - :generate_options => ["-G", "--generate-options PATH", "Generate an options file for --options"], - :runner => ["-U", "--runner RUNNER", "Use a custom Runner."], - :drb => ["-X", "--drb", "Run examples via DRb. (For example against script/spec_server)"], - :version => ["-v", "--version", "Show version"], - :help => ["-h", "--help", "You're looking at it"] - } - - def initialize(err, out) - super() - @error_stream = err - @out_stream = out - @options = Options.new(@error_stream, @out_stream) - - @file_factory = File - - self.banner = "Usage: spec (FILE|DIRECTORY|GLOB)+ [options]" - self.separator "" - on(*OPTIONS[:pattern]) {|pattern| @options.filename_pattern = pattern} - on(*OPTIONS[:diff]) {|diff| @options.parse_diff(diff)} - on(*OPTIONS[:colour]) {@options.colour = true} - on(*OPTIONS[:example]) {|example| @options.parse_example(example)} - on(*OPTIONS[:specification]) {|example| @options.parse_example(example)} - on(*OPTIONS[:line]) {|line_number| @options.line_number = line_number.to_i} - on(*OPTIONS[:format]) {|format| @options.parse_format(format)} - on(*OPTIONS[:require]) {|requires| invoke_requires(requires)} - on(*OPTIONS[:backtrace]) {@options.backtrace_tweaker = NoisyBacktraceTweaker.new} - on(*OPTIONS[:loadby]) {|loadby| @options.loadby = loadby} - on(*OPTIONS[:reverse]) {@options.reverse = true} - on(*OPTIONS[:timeout]) {|timeout| @options.timeout = timeout.to_f} - on(*OPTIONS[:heckle]) {|heckle| @options.load_heckle_runner(heckle)} - on(*OPTIONS[:dry_run]) {@options.dry_run = true} - on(*OPTIONS[:options_file]) {|options_file| parse_options_file(options_file)} - on(*OPTIONS[:generate_options]) do |options_file| - end - on(*OPTIONS[:runner]) do |runner| - @options.user_input_for_runner = runner - end - on(*OPTIONS[:drb]) {} - on(*OPTIONS[:version]) {parse_version} - on_tail(*OPTIONS[:help]) {parse_help} - end - - def order!(argv, &blk) - @argv = argv - @options.argv = @argv.dup - return if parse_generate_options - return if parse_drb - - super(@argv) do |file| - @options.files << file - blk.call(file) if blk - end - - @options - end - - protected - def invoke_requires(requires) - requires.split(",").each do |file| - require file - end - end - - def parse_options_file(options_file) - option_file_args = IO.readlines(options_file).map {|l| l.chomp.split " "}.flatten - @argv.push(*option_file_args) - end - - def parse_generate_options - # Remove the --generate-options option and the argument before writing to file - options_file = nil - ['-G', '--generate-options'].each do |option| - if index = @argv.index(option) - @argv.delete_at(index) - options_file = @argv.delete_at(index) - end - end - - if options_file - write_generated_options(options_file) - return true - else - return false - end - end - - def write_generated_options(options_file) - File.open(options_file, 'w') do |io| - io.puts @argv.join("\n") - end - @out_stream.puts "\nOptions written to #{options_file}. You can now use these options with:" - @out_stream.puts "spec --options #{options_file}" - @options.examples_should_not_be_run - end - - def parse_drb - is_drb = false - argv = @options.argv - is_drb ||= argv.delete(OPTIONS[:drb][0]) - is_drb ||= argv.delete(OPTIONS[:drb][1]) - return nil unless is_drb - @options.examples_should_not_be_run - DrbCommandLine.run( - self.class.parse(argv, @error_stream, @out_stream) - ) - true - end - - def parse_version - @out_stream.puts ::Spec::VERSION::DESCRIPTION - exit if stdout? - end - - def parse_help - @out_stream.puts self - exit if stdout? - end - - def stdout? - @out_stream == $stdout - end - end - end -end diff --git a/vendor/gems/rspec/lib/spec/runner/options.rb b/vendor/gems/rspec/lib/spec/runner/options.rb deleted file mode 100644 index 108749c424..0000000000 --- a/vendor/gems/rspec/lib/spec/runner/options.rb +++ /dev/null @@ -1,286 +0,0 @@ -module Spec - module Runner - class Options - FILE_SORTERS = { - 'mtime' => lambda {|file_a, file_b| File.mtime(file_b) <=> File.mtime(file_a)} - } - - EXAMPLE_FORMATTERS = { # Load these lazily for better speed - 'specdoc' => ['spec/runner/formatter/specdoc_formatter', 'Formatter::SpecdocFormatter'], - 's' => ['spec/runner/formatter/specdoc_formatter', 'Formatter::SpecdocFormatter'], - 'html' => ['spec/runner/formatter/html_formatter', 'Formatter::HtmlFormatter'], - 'h' => ['spec/runner/formatter/html_formatter', 'Formatter::HtmlFormatter'], - 'progress' => ['spec/runner/formatter/progress_bar_formatter', 'Formatter::ProgressBarFormatter'], - 'p' => ['spec/runner/formatter/progress_bar_formatter', 'Formatter::ProgressBarFormatter'], - 'failing_examples' => ['spec/runner/formatter/failing_examples_formatter', 'Formatter::FailingExamplesFormatter'], - 'e' => ['spec/runner/formatter/failing_examples_formatter', 'Formatter::FailingExamplesFormatter'], -'failing_example_groups' => ['spec/runner/formatter/failing_example_groups_formatter', 'Formatter::FailingExampleGroupsFormatter'], - 'g' => ['spec/runner/formatter/failing_example_groups_formatter', 'Formatter::FailingExampleGroupsFormatter'], - 'profile' => ['spec/runner/formatter/profile_formatter', 'Formatter::ProfileFormatter'], - 'o' => ['spec/runner/formatter/profile_formatter', 'Formatter::ProfileFormatter'], - 'textmate' => ['spec/runner/formatter/text_mate_formatter', 'Formatter::TextMateFormatter'] - } - - STORY_FORMATTERS = { - 'plain' => ['spec/runner/formatter/story/plain_text_formatter', 'Formatter::Story::PlainTextFormatter'], - 'p' => ['spec/runner/formatter/story/plain_text_formatter', 'Formatter::Story::PlainTextFormatter'], - 'html' => ['spec/runner/formatter/story/html_formatter', 'Formatter::Story::HtmlFormatter'], - 'h' => ['spec/runner/formatter/story/html_formatter', 'Formatter::Story::HtmlFormatter'] - } - - attr_accessor( - :filename_pattern, - :backtrace_tweaker, - :context_lines, - :diff_format, - :dry_run, - :profile, - :examples, - :heckle_runner, - :line_number, - :loadby, - :reporter, - :reverse, - :timeout, - :verbose, - :user_input_for_runner, - :error_stream, - :output_stream, - # TODO: BT - Figure out a better name - :argv - ) - attr_reader :colour, :differ_class, :files, :example_groups - - def initialize(error_stream, output_stream) - @error_stream = error_stream - @output_stream = output_stream - @filename_pattern = "**/*_spec.rb" - @backtrace_tweaker = QuietBacktraceTweaker.new - @examples = [] - @colour = false - @profile = false - @dry_run = false - @reporter = Reporter.new(self) - @context_lines = 3 - @diff_format = :unified - @files = [] - @example_groups = [] - @examples_run = false - @examples_should_be_run = nil - @user_input_for_runner = nil - end - - def add_example_group(example_group) - @example_groups << example_group - end - - def remove_example_group(example_group) - @example_groups.delete(example_group) - end - - def run_examples - return true unless examples_should_be_run? - runner = custom_runner || ExampleGroupRunner.new(self) - - runner.load_files(files_to_load) - if example_groups.empty? - true - else - set_spec_from_line_number if line_number - success = runner.run - @examples_run = true - heckle if heckle_runner - success - end - end - - def examples_run? - @examples_run - end - - def examples_should_not_be_run - @examples_should_be_run = false - end - - def colour=(colour) - @colour = colour - if @colour && RUBY_PLATFORM =~ /win32/ ;\ - begin ;\ - require 'rubygems' ;\ - require 'Win32/Console/ANSI' ;\ - rescue LoadError ;\ - warn "You must 'gem install win32console' to use colour on Windows" ;\ - @colour = false ;\ - end - end - end - - def parse_diff(format) - case format - when :context, 'context', 'c' - @diff_format = :context - default_differ - when :unified, 'unified', 'u', '', nil - @diff_format = :unified - default_differ - else - @diff_format = :custom - self.differ_class = load_class(format, 'differ', '--diff') - end - end - - def parse_example(example) - if(File.file?(example)) - @examples = File.open(example).read.split("\n") - else - @examples = [example] - end - end - - def parse_format(format_arg) - format, where = ClassAndArgumentsParser.parse(format_arg) - unless where - raise "When using several --format options only one of them can be without a file" if @out_used - where = @output_stream - @out_used = true - end - @format_options ||= [] - @format_options << [format, where] - end - - def formatters - @format_options ||= [['progress', @output_stream]] - @formatters ||= load_formatters(@format_options, EXAMPLE_FORMATTERS) - end - - def story_formatters - @format_options ||= [['plain', @output_stream]] - @formatters ||= load_formatters(@format_options, STORY_FORMATTERS) - end - - def load_formatters(format_options, formatters) - format_options.map do |format, where| - formatter_type = if formatters[format] - require formatters[format][0] - eval(formatters[format][1], binding, __FILE__, __LINE__) - else - load_class(format, 'formatter', '--format') - end - formatter_type.new(self, where) - end - end - - def load_heckle_runner(heckle) - suffix = [/mswin/, /java/].detect{|p| p =~ RUBY_PLATFORM} ? '_unsupported' : '' - require "spec/runner/heckle_runner#{suffix}" - @heckle_runner = HeckleRunner.new(heckle) - end - - def number_of_examples - @example_groups.inject(0) do |sum, example_group| - sum + example_group.number_of_examples - end - end - - def files_to_load - result = [] - sorted_files.each do |file| - if File.directory?(file) - filename_pattern.split(",").each do |pattern| - result += Dir[File.expand_path("#{file}/#{pattern.strip}")] - end - elsif File.file?(file) - result << file - else - raise "File or directory not found: #{file}" - end - end - result - end - - protected - def examples_should_be_run? - return @examples_should_be_run unless @examples_should_be_run.nil? - @examples_should_be_run = true - end - - def differ_class=(klass) - return unless klass - @differ_class = klass - Spec::Expectations.differ = self.differ_class.new(self) - end - - def load_class(name, kind, option) - if name =~ /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ - arg = $2 == "" ? nil : $2 - [$1, arg] - else - m = "#{name.inspect} is not a valid class name" - @error_stream.puts m - raise m - end - begin - eval(name, binding, __FILE__, __LINE__) - rescue NameError => e - @error_stream.puts "Couldn't find #{kind} class #{name}" - @error_stream.puts "Make sure the --require option is specified *before* #{option}" - if $_spec_spec ; raise e ; else exit(1) ; end - end - end - - def custom_runner - return nil unless custom_runner? - klass_name, arg = ClassAndArgumentsParser.parse(user_input_for_runner) - runner_type = load_class(klass_name, 'behaviour runner', '--runner') - return runner_type.new(self, arg) - end - - def custom_runner? - return user_input_for_runner ? true : false - end - - def heckle - returns = self.heckle_runner.heckle_with - self.heckle_runner = nil - returns - end - - def sorted_files - return sorter ? files.sort(&sorter) : files - end - - def sorter - FILE_SORTERS[loadby] - end - - def default_differ - require 'spec/expectations/differs/default' - self.differ_class = Spec::Expectations::Differs::Default - end - - def set_spec_from_line_number - if examples.empty? - if files.length == 1 - if File.directory?(files[0]) - error_stream.puts "You must specify one file, not a directory when using the --line option" - exit(1) if stderr? - else - example = SpecParser.new.spec_name_for(files[0], line_number) - @examples = [example] - end - else - error_stream.puts "Only one file can be specified when using the --line option: #{files.inspect}" - exit(3) if stderr? - end - else - error_stream.puts "You cannot use both --line and --example" - exit(4) if stderr? - end - end - - def stderr? - @error_stream == $stderr - end - end - end -end diff --git a/vendor/gems/rspec/lib/spec/runner/reporter.rb b/vendor/gems/rspec/lib/spec/runner/reporter.rb deleted file mode 100644 index cfc511baf2..0000000000 --- a/vendor/gems/rspec/lib/spec/runner/reporter.rb +++ /dev/null @@ -1,143 +0,0 @@ -module Spec - module Runner - class Reporter - attr_reader :options, :example_groups - - def initialize(options) - @options = options - @options.reporter = self - clear - end - - def add_example_group(example_group) - formatters.each do |f| - f.add_example_group(example_group) - end - example_groups << example_group - end - - def example_started(example) - formatters.each{|f| f.example_started(example)} - end - - def example_finished(example, error=nil) - @examples << example - - if error.nil? - example_passed(example) - elsif Spec::Example::ExamplePendingError === error - example_pending(example_groups.last, example, error.message) - else - example_failed(example, error) - end - end - - def failure(example, error) - backtrace_tweaker.tweak_backtrace(error) - example_name = "#{example_groups.last.description} #{example.description}" - failure = Failure.new(example_name, error) - @failures << failure - formatters.each do |f| - f.example_failed(example, @failures.length, failure) - end - end - alias_method :example_failed, :failure - - def start(number_of_examples) - clear - @start_time = Time.new - formatters.each{|f| f.start(number_of_examples)} - end - - def end - @end_time = Time.new - end - - # Dumps the summary and returns the total number of failures - def dump - formatters.each{|f| f.start_dump} - dump_pending - dump_failures - formatters.each do |f| - f.dump_summary(duration, @examples.length, @failures.length, @pending_count) - f.close - end - @failures.length - end - - private - - def formatters - @options.formatters - end - - def backtrace_tweaker - @options.backtrace_tweaker - end - - def clear - @example_groups = [] - @failures = [] - @pending_count = 0 - @examples = [] - @start_time = nil - @end_time = nil - end - - def dump_failures - return if @failures.empty? - @failures.inject(1) do |index, failure| - formatters.each{|f| f.dump_failure(index, failure)} - index + 1 - end - end - def dump_pending - formatters.each{|f| f.dump_pending} - end - - def duration - return @end_time - @start_time unless (@end_time.nil? or @start_time.nil?) - return "0.0" - end - - def example_passed(example) - formatters.each{|f| f.example_passed(example)} - end - - def example_pending(example_group, example, message="Not Yet Implemented") - @pending_count += 1 - formatters.each do |f| - f.example_pending(example_group.description, example, message) - end - end - - class Failure - attr_reader :exception - - def initialize(example_name, exception) - @example_name = example_name - @exception = exception - end - - def header - if expectation_not_met? - "'#{@example_name}' FAILED" - elsif pending_fixed? - "'#{@example_name}' FIXED" - else - "#{@exception.class.name} in '#{@example_name}'" - end - end - - def pending_fixed? - @exception.is_a?(Spec::Example::PendingExampleFixedError) - end - - def expectation_not_met? - @exception.is_a?(Spec::Expectations::ExpectationNotMetError) - end - - end - end - end -end diff --git a/vendor/gems/rspec/lib/spec/runner/spec_parser.rb b/vendor/gems/rspec/lib/spec/runner/spec_parser.rb deleted file mode 100644 index 8beb384e90..0000000000 --- a/vendor/gems/rspec/lib/spec/runner/spec_parser.rb +++ /dev/null @@ -1,71 +0,0 @@ -module Spec - module Runner - # Parses a spec file and finds the nearest example for a given line number. - class SpecParser - attr_reader :best_match - - def initialize - @best_match = {} - end - - def spec_name_for(file, line_number) - best_match.clear - file = File.expand_path(file) - rspec_options.example_groups.each do |example_group| - consider_example_groups_for_best_match example_group, file, line_number - - example_group.examples.each do |example| - consider_example_for_best_match example, example_group, file, line_number - end - end - if best_match[:example_group] - if best_match[:example] - "#{best_match[:example_group].description} #{best_match[:example].description}" - else - best_match[:example_group].description - end - else - nil - end - end - - protected - - def consider_example_groups_for_best_match(example_group, file, line_number) - parsed_backtrace = parse_backtrace(example_group.registration_backtrace) - parsed_backtrace.each do |example_file, example_line| - if is_best_match?(file, line_number, example_file, example_line) - best_match.clear - best_match[:example_group] = example_group - best_match[:line] = example_line - end - end - end - - def consider_example_for_best_match(example, example_group, file, line_number) - parsed_backtrace = parse_backtrace(example.implementation_backtrace) - parsed_backtrace.each do |example_file, example_line| - if is_best_match?(file, line_number, example_file, example_line) - best_match.clear - best_match[:example_group] = example_group - best_match[:example] = example - best_match[:line] = example_line - end - end - end - - def is_best_match?(file, line_number, example_file, example_line) - file == File.expand_path(example_file) && - example_line <= line_number && - example_line > best_match[:line].to_i - end - - def parse_backtrace(backtrace) - backtrace.collect do |trace_line| - split_line = trace_line.split(':') - [split_line[0], Integer(split_line[1])] - end - end - end - end -end diff --git a/vendor/gems/rspec/lib/spec/story.rb b/vendor/gems/rspec/lib/spec/story.rb deleted file mode 100644 index bc6960a28e..0000000000 --- a/vendor/gems/rspec/lib/spec/story.rb +++ /dev/null @@ -1,10 +0,0 @@ -require 'spec' -require 'spec/story/extensions' -require 'spec/story/given_scenario' -require 'spec/story/runner' -require 'spec/story/scenario' -require 'spec/story/step' -require 'spec/story/step_group' -require 'spec/story/step_mother' -require 'spec/story/story' -require 'spec/story/world' diff --git a/vendor/gems/rspec/lib/spec/story/extensions.rb b/vendor/gems/rspec/lib/spec/story/extensions.rb deleted file mode 100644 index dc7dd11407..0000000000 --- a/vendor/gems/rspec/lib/spec/story/extensions.rb +++ /dev/null @@ -1,3 +0,0 @@ -require 'spec/story/extensions/main' -require 'spec/story/extensions/string' -require 'spec/story/extensions/regexp' diff --git a/vendor/gems/rspec/lib/spec/story/extensions/main.rb b/vendor/gems/rspec/lib/spec/story/extensions/main.rb deleted file mode 100644 index 6336b630c5..0000000000 --- a/vendor/gems/rspec/lib/spec/story/extensions/main.rb +++ /dev/null @@ -1,86 +0,0 @@ -module Spec - module Story - module Extensions - module Main - def Story(title, narrative, params = {}, &body) - ::Spec::Story::Runner.story_runner.Story(title, narrative, params, &body) - end - - # Calling this deprecated is silly, since it hasn't been released yet. But, for - # those who are reading this - this will be deleted before the 1.1 release. - def run_story(*args, &block) - runner = Spec::Story::Runner::PlainTextStoryRunner.new(*args) - runner.instance_eval(&block) if block - runner.run - end - - # Creates (or appends to an existing) a namespaced group of steps for use in Stories - # - # == Examples - # - # # Creating a new group - # steps_for :forms do - # When("user enters $value in the $field field") do ... end - # When("user submits the $form form") do ... end - # end - def steps_for(tag, &block) - steps = rspec_story_steps[tag] - steps.instance_eval(&block) if block - steps - end - - # Creates a context for running a Plain Text Story with specific groups of Steps. - # Also supports adding arbitrary steps that will only be accessible to - # the Story being run. - # - # == Examples - # - # # Run a Story with one group of steps - # with_steps_for :checking_accounts do - # run File.dirname(__FILE__) + "/withdraw_cash" - # end - # - # # Run a Story, adding steps that are only available for this Story - # with_steps_for :accounts do - # Given "user is logged in as account administrator" - # run File.dirname(__FILE__) + "/reconcile_accounts" - # end - # - # # Run a Story with steps from two groups - # with_steps_for :checking_accounts, :savings_accounts do - # run File.dirname(__FILE__) + "/transfer_money" - # end - # - # # Run a Story with a specific Story extension - # with_steps_for :login, :navigation do - # run File.dirname(__FILE__) + "/user_changes_password", :type => RailsStory - # end - def with_steps_for(*tags, &block) - steps = Spec::Story::StepGroup.new do - extend StoryRunnerStepGroupAdapter - end - tags.each {|tag| steps << rspec_story_steps[tag]} - steps.instance_eval(&block) if block - steps - end - - private - - module StoryRunnerStepGroupAdapter - def run(path, options={}) - runner = Spec::Story::Runner::PlainTextStoryRunner.new(path, options) - runner.steps << self - runner.run - end - end - - def rspec_story_steps # :nodoc: - $rspec_story_steps ||= Spec::Story::StepGroupHash.new - end - - end - end - end -end - -include Spec::Story::Extensions::Main \ No newline at end of file diff --git a/vendor/gems/rspec/lib/spec/story/extensions/regexp.rb b/vendor/gems/rspec/lib/spec/story/extensions/regexp.rb deleted file mode 100644 index 7955b4c333..0000000000 --- a/vendor/gems/rspec/lib/spec/story/extensions/regexp.rb +++ /dev/null @@ -1,9 +0,0 @@ -class Regexp - def step_name - self.source - end - - def arg_regexp - ::Spec::Story::Step::PARAM_OR_GROUP_PATTERN - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/lib/spec/story/extensions/string.rb b/vendor/gems/rspec/lib/spec/story/extensions/string.rb deleted file mode 100644 index 896578def4..0000000000 --- a/vendor/gems/rspec/lib/spec/story/extensions/string.rb +++ /dev/null @@ -1,9 +0,0 @@ -class String - def step_name - self - end - - def arg_regexp - ::Spec::Story::Step::PARAM_PATTERN - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/lib/spec/story/given_scenario.rb b/vendor/gems/rspec/lib/spec/story/given_scenario.rb deleted file mode 100644 index 88c51f9811..0000000000 --- a/vendor/gems/rspec/lib/spec/story/given_scenario.rb +++ /dev/null @@ -1,14 +0,0 @@ -module Spec - module Story - class GivenScenario - def initialize(name) - @name = name - end - - def perform(instance, ignore_name) - scenario = Runner::StoryRunner.scenario_from_current_story(@name) - Runner::ScenarioRunner.new.run(scenario, instance) - end - end - end -end diff --git a/vendor/gems/rspec/lib/spec/story/runner.rb b/vendor/gems/rspec/lib/spec/story/runner.rb deleted file mode 100644 index 1f6dd9e7a6..0000000000 --- a/vendor/gems/rspec/lib/spec/story/runner.rb +++ /dev/null @@ -1,56 +0,0 @@ -require 'spec/story/runner/scenario_collector.rb' -require 'spec/story/runner/scenario_runner.rb' -require 'spec/story/runner/story_runner.rb' -require 'spec/story/runner/story_parser.rb' -require 'spec/story/runner/story_mediator.rb' -require 'spec/story/runner/plain_text_story_runner.rb' - -module Spec - module Story - module Runner - class << self - def run_options # :nodoc: - @run_options ||= ::Spec::Runner::OptionParser.parse(ARGV, $stderr, $stdout) - end - - def story_runner # :nodoc: - unless @story_runner - @story_runner = StoryRunner.new(scenario_runner, world_creator) - run_options.story_formatters.each do |formatter| - register_listener(formatter) - end - Runner.register_exit_hook - end - @story_runner - end - - def scenario_runner # :nodoc: - @scenario_runner ||= ScenarioRunner.new - end - - def world_creator # :nodoc: - @world_creator ||= World - end - - # Use this to register a customer output formatter. - def register_listener(listener) - story_runner.add_listener(listener) # run_started, story_started, story_ended, #run_ended - world_creator.add_listener(listener) # found_scenario, step_succeeded, step_failed, step_failed - scenario_runner.add_listener(listener) # scenario_started, scenario_succeeded, scenario_pending, scenario_failed - end - - def register_exit_hook # :nodoc: - at_exit do - Runner.story_runner.run_stories unless $! - end - # TODO exit with non-zero status if run fails - end - - def dry_run - run_options.dry_run - end - - end - end - end -end diff --git a/vendor/gems/rspec/lib/spec/story/runner/plain_text_story_runner.rb b/vendor/gems/rspec/lib/spec/story/runner/plain_text_story_runner.rb deleted file mode 100644 index 8d34ea2d27..0000000000 --- a/vendor/gems/rspec/lib/spec/story/runner/plain_text_story_runner.rb +++ /dev/null @@ -1,48 +0,0 @@ -module Spec - module Story - module Runner - class PlainTextStoryRunner - # You can initialize a PlainTextStoryRunner with the path to the - # story file or a block, in which you can define the path using load. - # - # == Examples - # - # PlainTextStoryRunner.new('path/to/file') - # - # PlainTextStoryRunner.new do |runner| - # runner.load 'path/to/file' - # end - def initialize(*args) - @options = Hash === args.last ? args.pop : {} - @story_file = args.empty? ? nil : args.shift - yield self if block_given? - end - - def []=(key, value) - @options[key] = value - end - - def load(path) - @story_file = path - end - - def run - raise "You must set a path to the file with the story. See the RDoc." if @story_file.nil? - mediator = Spec::Story::Runner::StoryMediator.new(steps, Spec::Story::Runner.story_runner, @options) - parser = Spec::Story::Runner::StoryParser.new(mediator) - - story_text = File.read(@story_file) - parser.parse(story_text.split("\n")) - - mediator.run_stories - end - - def steps - @step_group ||= Spec::Story::StepGroup.new - yield @step_group if block_given? - @step_group - end - end - end - end -end diff --git a/vendor/gems/rspec/lib/spec/story/runner/scenario_collector.rb b/vendor/gems/rspec/lib/spec/story/runner/scenario_collector.rb deleted file mode 100644 index 78339fd228..0000000000 --- a/vendor/gems/rspec/lib/spec/story/runner/scenario_collector.rb +++ /dev/null @@ -1,18 +0,0 @@ -module Spec - module Story - module Runner - class ScenarioCollector - attr_accessor :scenarios - - def initialize(story) - @story = story - @scenarios = [] - end - - def Scenario(name, &body) - @scenarios << Scenario.new(@story, name, &body) - end - end - end - end -end diff --git a/vendor/gems/rspec/lib/spec/story/runner/scenario_runner.rb b/vendor/gems/rspec/lib/spec/story/runner/scenario_runner.rb deleted file mode 100644 index aee52e4129..0000000000 --- a/vendor/gems/rspec/lib/spec/story/runner/scenario_runner.rb +++ /dev/null @@ -1,46 +0,0 @@ -module Spec - module Story - module Runner - class ScenarioRunner - def initialize - @listeners = [] - end - - def run(scenario, world) - @listeners.each { |l| l.scenario_started(scenario.story.title, scenario.name) } - run_story_ignoring_scenarios(scenario.story, world) - - world.start_collecting_errors - world.instance_eval(&scenario.body) - if world.errors.empty? - @listeners.each { |l| l.scenario_succeeded(scenario.story.title, scenario.name) } - else - if Spec::Example::ExamplePendingError === (e = world.errors.first) - @listeners.each { |l| l.scenario_pending(scenario.story.title, scenario.name, e.message) } - else - @listeners.each { |l| l.scenario_failed(scenario.story.title, scenario.name, e) } - end - end - end - - def add_listener(listener) - @listeners << listener - end - - private - - def run_story_ignoring_scenarios(story, world) - class << world - def Scenario(name, &block) - # do nothing - end - end - story.run_in(world) - class << world - remove_method(:Scenario) - end - end - end - end - end -end diff --git a/vendor/gems/rspec/lib/spec/story/runner/story_mediator.rb b/vendor/gems/rspec/lib/spec/story/runner/story_mediator.rb deleted file mode 100644 index 1f4744b9f8..0000000000 --- a/vendor/gems/rspec/lib/spec/story/runner/story_mediator.rb +++ /dev/null @@ -1,123 +0,0 @@ - module Spec - module Story - module Runner - - class StoryMediator - def initialize(step_group, runner, options={}) - @step_group = step_group - @stories = [] - @runner = runner - @options = options - end - - def stories - @stories.collect { |p| p.to_proc } - end - - def create_story(title, narrative) - @stories << Story.new(title, narrative, @step_group, @options) - end - - def create_scenario(title) - current_story.add_scenario Scenario.new(title) - end - - def create_given(name) - current_scenario.add_step Step.new('Given', name) - end - - def create_given_scenario(name) - current_scenario.add_step Step.new('GivenScenario', name) - end - - def create_when(name) - current_scenario.add_step Step.new('When', name) - end - - def create_then(name) - current_scenario.add_step Step.new('Then', name) - end - - def run_stories - stories.each { |story| @runner.instance_eval(&story) } - end - - private - def current_story - @stories.last - end - - def current_scenario - current_story.current_scenario - end - - class Story - def initialize(title, narrative, step_group, options) - @title = title - @narrative = narrative - @scenarios = [] - @step_group = step_group - @options = options - end - - def to_proc - title = @title - narrative = @narrative - scenarios = @scenarios.collect { |scenario| scenario.to_proc } - options = @options.merge(:steps => @step_group) - lambda do - Story title, narrative, options do - scenarios.each { |scenario| instance_eval(&scenario) } - end - end - end - - def add_scenario(scenario) - @scenarios << scenario - end - - def current_scenario - @scenarios.last - end - end - - class Scenario - def initialize(name) - @name = name - @steps = [] - end - - def to_proc - name = @name - steps = @steps.collect { |step| step.to_proc } - lambda do - Scenario name do - steps.each { |step| instance_eval(&step) } - end - end - end - - def add_step(step) - @steps << step - end - end - - class Step - def initialize(type, name) - @type = type - @name = name - end - - def to_proc - type = @type - name = @name - lambda do - send(type, name) - end - end - end - end - - end - end -end diff --git a/vendor/gems/rspec/lib/spec/story/runner/story_parser.rb b/vendor/gems/rspec/lib/spec/story/runner/story_parser.rb deleted file mode 100644 index d454df8cb7..0000000000 --- a/vendor/gems/rspec/lib/spec/story/runner/story_parser.rb +++ /dev/null @@ -1,227 +0,0 @@ -module Spec - module Story - module Runner - - class IllegalStepError < StandardError - def initialize(state, event) - super("Illegal attempt to create a #{event} after a #{state}") - end - end - - class StoryParser - def initialize(story_mediator) - @story_mediator = story_mediator - @current_story_lines = [] - transition_to(:starting_state) - end - - def parse(lines) - lines.reject! {|line| line == ""} - until lines.empty? - process_line(lines.shift) - end - @state.eof - end - - def process_line(line) - line.strip! - case line - when /^Story: / then @state.story(line) - when /^Scenario: / then @state.scenario(line) - when /^Given:? / then @state.given(line) - when /^GivenScenario:? / then @state.given_scenario(line) - when /^When:? / then @state.event(line) - when /^Then:? / then @state.outcome(line) - when /^And:? / then @state.one_more_of_the_same(line) - else @state.other(line) - end - end - - def init_story(title) - @current_story_lines.clear - add_story_line(title) - end - - def add_story_line(line) - @current_story_lines << line - end - - def create_story() - unless @current_story_lines.empty? - @story_mediator.create_story(@current_story_lines[0].gsub("Story: ",""), @current_story_lines[1..-1].join("\n")) - @current_story_lines.clear - end - end - - def create_scenario(title) - @story_mediator.create_scenario(title.gsub("Scenario: ","")) - end - - def create_given(name) - @story_mediator.create_given(name) - end - - def create_given_scenario(name) - @story_mediator.create_given_scenario(name) - end - - def create_when(name) - @story_mediator.create_when(name) - end - - def create_then(name) - @story_mediator.create_then(name) - end - - def transition_to(key) - @state = states[key] - end - - def states - @states ||= { - :starting_state => StartingState.new(self), - :story_state => StoryState.new(self), - :scenario_state => ScenarioState.new(self), - :given_state => GivenState.new(self), - :when_state => WhenState.new(self), - :then_state => ThenState.new(self) - } - end - - class State - def initialize(parser) - @parser = parser - end - - def story(line) - @parser.init_story(line) - @parser.transition_to(:story_state) - end - - def scenario(line) - @parser.create_scenario(line) - @parser.transition_to(:scenario_state) - end - - def given(line) - @parser.create_given(remove_tag_from(:given, line)) - @parser.transition_to(:given_state) - end - - def given_scenario(line) - @parser.create_given_scenario(remove_tag_from(:givenscenario, line)) - @parser.transition_to(:given_state) - end - - def event(line) - @parser.create_when(remove_tag_from(:when, line)) - @parser.transition_to(:when_state) - end - - def outcome(line) - @parser.create_then(remove_tag_from(:then, line)) - @parser.transition_to(:then_state) - end - - def remove_tag_from(tag, line) - tokens = line.split - # validation of tag can go here - tokens[0].downcase.match(/#{tag.to_s}:?/) ? - (tokens[1..-1].join(' ')) : line - end - - def eof - end - - def other(line) - # no-op - supports header text before the first story in a file - end - end - - class StartingState < State - def initialize(parser) - @parser = parser - end - end - - class StoryState < State - def one_more_of_the_same(line) - other(line) - end - - def story(line) - @parser.create_story - @parser.add_story_line(line) - end - - def scenario(line) - @parser.create_story - @parser.create_scenario(line) - @parser.transition_to(:scenario_state) - end - - def given(line) - other(line) - end - - def event(line) - other(line) - end - - def outcome(line) - other(line) - end - - def other(line) - @parser.add_story_line(line) - end - - def eof - @parser.create_story - end - end - - class ScenarioState < State - def one_more_of_the_same(line) - raise IllegalStepError.new("Scenario", "And") - end - - def scenario(line) - @parser.create_scenario(line) - end - end - - class GivenState < State - def one_more_of_the_same(line) - @parser.create_given(remove_tag_from(:and, line)) - end - - def given(line) - @parser.create_given(remove_tag_from(:given, line)) - end - end - - class WhenState < State - def one_more_of_the_same(line) - @parser.create_when(remove_tag_from(:and ,line)) - end - - def event(line) - @parser.create_when(remove_tag_from(:when ,line)) - end - end - - class ThenState < State - def one_more_of_the_same(line) - @parser.create_then(remove_tag_from(:and ,line)) - end - - def outcome(line) - @parser.create_then(remove_tag_from(:then ,line)) - end - end - - end - end - end -end diff --git a/vendor/gems/rspec/lib/spec/story/runner/story_runner.rb b/vendor/gems/rspec/lib/spec/story/runner/story_runner.rb deleted file mode 100644 index f9eeb9ac12..0000000000 --- a/vendor/gems/rspec/lib/spec/story/runner/story_runner.rb +++ /dev/null @@ -1,68 +0,0 @@ -module Spec - module Story - module Runner - class StoryRunner - class << self - attr_accessor :current_story_runner - - def scenario_from_current_story(scenario_name) - current_story_runner.scenario_from_current_story(scenario_name) - end - end - - attr_accessor :stories, :scenarios, :current_story - - def initialize(scenario_runner, world_creator = World) - StoryRunner.current_story_runner = self - @scenario_runner = scenario_runner - @world_creator = world_creator - @stories = [] - @scenarios_by_story = {} - @scenarios = [] - @listeners = [] - end - - def Story(title, narrative, params = {}, &body) - story = Story.new(title, narrative, params, &body) - @stories << story - - # collect scenarios - collector = ScenarioCollector.new(story) - story.run_in(collector) - @scenarios += collector.scenarios - @scenarios_by_story[story.title] = collector.scenarios - end - - def run_stories - return if @stories.empty? - @listeners.each { |l| l.run_started(scenarios.size) } - @stories.each do |story| - story.assign_steps_to(World) - @current_story = story - @listeners.each { |l| l.story_started(story.title, story.narrative) } - scenarios = @scenarios_by_story[story.title] - scenarios.each do |scenario| - type = story[:type] || Object - args = story[:args] || [] - world = @world_creator.create(type, *args) - @scenario_runner.run(scenario, world) - end - @listeners.each { |l| l.story_ended(story.title, story.narrative) } - World.step_mother.clear - end - unique_steps = (World.step_names.collect {|n| Regexp === n ? n.source : n.to_s}).uniq.sort - @listeners.each { |l| l.collected_steps(unique_steps) } - @listeners.each { |l| l.run_ended } - end - - def add_listener(listener) - @listeners << listener - end - - def scenario_from_current_story(scenario_name) - @scenarios_by_story[@current_story.title].find {|s| s.name == scenario_name } - end - end - end - end -end diff --git a/vendor/gems/rspec/lib/spec/story/scenario.rb b/vendor/gems/rspec/lib/spec/story/scenario.rb deleted file mode 100644 index d83b3eeb8c..0000000000 --- a/vendor/gems/rspec/lib/spec/story/scenario.rb +++ /dev/null @@ -1,14 +0,0 @@ - -module Spec - module Story - class Scenario - attr_accessor :name, :body, :story - - def initialize(story, name, &body) - @story = story - @name = name - @body = body - end - end - end -end diff --git a/vendor/gems/rspec/lib/spec/story/step.rb b/vendor/gems/rspec/lib/spec/story/step.rb deleted file mode 100644 index ee9ede0578..0000000000 --- a/vendor/gems/rspec/lib/spec/story/step.rb +++ /dev/null @@ -1,58 +0,0 @@ -module Spec - module Story - class Step - PARAM_PATTERN = /(\$\w*)/ - PARAM_OR_GROUP_PATTERN = /(\$\w*)|\(.*?\)/ - - attr_reader :name - def initialize(name, &block) - @name = name - assign_expression(name) - init_module(name, &block) - end - - def perform(instance, *args) - instance.extend(@mod) - instance.__send__(sanitize(@name), *args) - end - - def init_module(name, &block) - sanitized_name = sanitize(name) - @mod = Module.new do - define_method(sanitized_name, &block) - end - end - - def sanitize(a_string_or_regexp) - return a_string_or_regexp.source if Regexp == a_string_or_regexp - a_string_or_regexp.to_s - end - - - def matches?(name) - !(matches = name.match(@expression)).nil? - end - - def parse_args(name) - name.match(@expression)[1..-1] - end - - private - - def assign_expression(string_or_regexp) - if String === string_or_regexp - expression = string_or_regexp.dup - expression.gsub! '(', '\(' - expression.gsub! ')', '\)' - elsif Regexp === string_or_regexp - expression = string_or_regexp.source - end - while expression =~ PARAM_PATTERN - expression.gsub!($1, "(.*?)") - end - @expression = Regexp.new("^#{expression}$") - end - - end - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/lib/spec/story/step_group.rb b/vendor/gems/rspec/lib/spec/story/step_group.rb deleted file mode 100644 index cae558c40d..0000000000 --- a/vendor/gems/rspec/lib/spec/story/step_group.rb +++ /dev/null @@ -1,89 +0,0 @@ -module Spec - module Story - - class StepGroupHash < Hash - def initialize - super do |h,k| - h[k] = Spec::Story::StepGroup.new - end - end - end - - class StepGroup - def self.steps(&block) - @step_group ||= StepGroup.new(false) - @step_group.instance_eval(&block) if block - @step_group - end - - def initialize(init_defaults=true, &block) - @hash_of_lists_of_steps = Hash.new {|h, k| h[k] = []} - if init_defaults - self.class.steps.add_to(self) - end - instance_eval(&block) if block - end - - def find(type, name) - @hash_of_lists_of_steps[type].each do |step| - return step if step.matches?(name) - end - return nil - end - - def GivenScenario(name, &block) - create_matcher(:given_scenario, name, &block) - end - - def Given(name, &block) - create_matcher(:given, name, &block) - end - - def When(name, &block) - create_matcher(:when, name, &block) - end - - def Then(name, &block) - create_matcher(:then, name, &block) - end - - alias :given_scenario :GivenScenario - alias :given :Given - alias :when :When - alias :then :Then - - def add(type, steps) - (@hash_of_lists_of_steps[type] << steps).flatten! - end - - def clear - @hash_of_lists_of_steps.clear - end - - def empty? - [:given_scenario, :given, :when, :then].each do |type| - return false unless @hash_of_lists_of_steps[type].empty? - end - return true - end - - def add_to(other_step_matchers) - [:given_scenario, :given, :when, :then].each do |type| - other_step_matchers.add(type, @hash_of_lists_of_steps[type]) - end - end - - def <<(other_step_matchers) - other_step_matchers.add_to(self) if other_step_matchers.respond_to?(:add_to) - end - - # TODO - make me private - def create_matcher(type, name, &block) - matcher = Step.new(name, &block) - @hash_of_lists_of_steps[type] << matcher - matcher - end - - end - end -end diff --git a/vendor/gems/rspec/lib/spec/story/step_mother.rb b/vendor/gems/rspec/lib/spec/story/step_mother.rb deleted file mode 100644 index a2e84e310c..0000000000 --- a/vendor/gems/rspec/lib/spec/story/step_mother.rb +++ /dev/null @@ -1,37 +0,0 @@ -module Spec - module Story - class StepMother - def initialize - @steps = StepGroup.new - end - - def use(new_step_group) - @steps << new_step_group - end - - def store(type, step) - @steps.add(type, step) - end - - def find(type, name) - if @steps.find(type, name).nil? - @steps.add(type, - Step.new(name) do - raise Spec::Example::ExamplePendingError.new("Unimplemented step: #{name}") - end - ) - end - @steps.find(type, name) - end - - def clear - @steps.clear - end - - def empty? - @steps.empty? - end - - end - end -end diff --git a/vendor/gems/rspec/lib/spec/story/story.rb b/vendor/gems/rspec/lib/spec/story/story.rb deleted file mode 100644 index 112e9414b5..0000000000 --- a/vendor/gems/rspec/lib/spec/story/story.rb +++ /dev/null @@ -1,42 +0,0 @@ -module Spec - module Story - class Story - attr_reader :title, :narrative - - def initialize(title, narrative, params = {}, &body) - @body = body - @title = title - @narrative = narrative - @params = params - end - - def [](key) - @params[key] - end - - def run_in(obj) - obj.instance_eval(&@body) - end - - def assign_steps_to(assignee) - if @params[:steps] - assignee.use(@params[:steps]) - else - case keys = @params[:steps_for] - when Symbol - keys = [keys] - when nil - keys = [] - end - keys.each do |key| - assignee.use(steps_for(key)) - end - end - end - - def steps_for(key) - $rspec_story_steps[key] - end - end - end -end diff --git a/vendor/gems/rspec/lib/spec/story/world.rb b/vendor/gems/rspec/lib/spec/story/world.rb deleted file mode 100644 index 93b6e1d15d..0000000000 --- a/vendor/gems/rspec/lib/spec/story/world.rb +++ /dev/null @@ -1,125 +0,0 @@ -require 'rubygems' -require 'spec/expectations' -require 'spec/matchers' -require 'spec/example/pending' - -module Spec - module Story -=begin - A World represents the actual instance a scenario will run in. - - The runner ensures any instance variables and methods defined anywhere - in a story block are available to all the scenarios. This includes - variables that are created or referenced inside Given, When and Then - blocks. -=end - module World - include ::Spec::Example::Pending - include ::Spec::Matchers - # store steps and listeners in the singleton metaclass. - # This serves both to keep them out of the way of runtime Worlds - # and to make them available to all instances. - class << self - def create(cls = Object, *args) - cls.new(*args).extend(World) - end - - def listeners - @listeners ||= [] - end - - def add_listener(listener) - listeners() << listener - end - - def step_mother - @step_mother ||= StepMother.new - end - - def use(steps) - step_mother.use(steps) - end - - def step_names - @step_names ||= [] - end - - def run_given_scenario_with_suspended_listeners(world, type, name, scenario) - current_listeners = Array.new(listeners) - begin - listeners.each { |l| l.found_scenario(type, name) } - @listeners.clear - scenario.perform(world, name) unless ::Spec::Story::Runner.dry_run - ensure - @listeners.replace(current_listeners) - end - end - - def store_and_call(world, type, name, *args, &block) - if block_given? - step_mother.store(type, Step.new(name, &block)) - end - step = step_mother.find(type, name) - - step_name = step.name - step_names << step_name - - # It's important to have access to the parsed args here, so - # we can give them to the listeners. The HTML reporter needs - # the args so it can style them. See the generated output in - # story_server/prototype/rspec_stories.html (generated by rake stories) - args = step.parse_args(name) if args.empty? - begin - listeners.each { |l| l.step_upcoming(type, step_name, *args) } - step.perform(world, *args) unless ::Spec::Story::Runner.dry_run - listeners.each { |l| l.step_succeeded(type, step_name, *args) } - rescue Exception => e - case e - when Spec::Example::ExamplePendingError - @listeners.each { |l| l.step_pending(type, step_name, *args) } - else - @listeners.each { |l| l.step_failed(type, step_name, *args) } - end - errors << e - end - end - - def errors - @errors ||= [] - end - end # end of class << self - - def start_collecting_errors - errors.clear - end - - def errors - World.errors - end - - def GivenScenario(name) - World.run_given_scenario_with_suspended_listeners(self, :'given scenario', name, GivenScenario.new(name)) - @__previous_step = :given - end - - def Given(name, *args, &block) - World.store_and_call self, :given, name, *args, &block - @__previous_step = :given - end - - def When(name, *args, &block) - World.store_and_call self, :when, name, *args, &block - @__previous_step = :when - end - - def Then(name, *args, &block) - World.store_and_call self, :then, name, *args, &block - @__previous_step = :then - end - - def And(name, *args, &block) - World.store_and_call self, @__previous_step, name, *args, &block - end - end - end -end diff --git a/vendor/gems/rspec/lib/spec/translator.rb b/vendor/gems/rspec/lib/spec/translator.rb deleted file mode 100644 index c1e07eda4b..0000000000 --- a/vendor/gems/rspec/lib/spec/translator.rb +++ /dev/null @@ -1,114 +0,0 @@ -require 'fileutils' - -module Spec - class Translator - def translate(from, to) - from = File.expand_path(from) - to = File.expand_path(to) - if File.directory?(from) - translate_dir(from, to) - elsif(from =~ /\.rb$/) - translate_file(from, to) - end - end - - def translate_dir(from, to) - FileUtils.mkdir_p(to) unless File.directory?(to) - Dir["#{from}/*"].each do |sub_from| - path = sub_from[from.length+1..-1] - sub_to = File.join(to, path) - translate(sub_from, sub_to) - end - end - - def translate_file(from, to) - translation = "" - File.open(from) do |io| - io.each_line do |line| - translation << translate_line(line) - end - end - File.open(to, "w") do |io| - io.write(translation) - end - end - - def translate_line(line) - # Translate deprecated mock constraints - line.gsub!(/:any_args/, 'any_args') - line.gsub!(/:anything/, 'anything') - line.gsub!(/:boolean/, 'boolean') - line.gsub!(/:no_args/, 'no_args') - line.gsub!(/:numeric/, 'an_instance_of(Numeric)') - line.gsub!(/:string/, 'an_instance_of(String)') - - return line if line =~ /(should_not|should)_receive/ - - line.gsub!(/(^\s*)context([\s*|\(]['|"|A-Z])/, '\1describe\2') - line.gsub!(/(^\s*)specify([\s*|\(]['|"|A-Z])/, '\1it\2') - line.gsub!(/(^\s*)context_setup(\s*[do|\{])/, '\1before(:all)\2') - line.gsub!(/(^\s*)context_teardown(\s*[do|\{])/, '\1after(:all)\2') - line.gsub!(/(^\s*)setup(\s*[do|\{])/, '\1before(:each)\2') - line.gsub!(/(^\s*)teardown(\s*[do|\{])/, '\1after(:each)\2') - - if line =~ /(.*\.)(should_not|should)(?:_be)(?!_)(.*)/m - pre = $1 - should = $2 - post = $3 - be_or_equal = post =~ /(<|>)/ ? "be" : "equal" - - return "#{pre}#{should} #{be_or_equal}#{post}" - end - - if line =~ /(.*\.)(should_not|should)_(?!not)\s*(.*)/m - pre = $1 - should = $2 - post = $3 - - post.gsub!(/^raise/, 'raise_error') - post.gsub!(/^throw/, 'throw_symbol') - - unless standard_matcher?(post) - post = "be_#{post}" - end - - # Add parenthesis - post.gsub!(/^(\w+)\s+([\w|\.|\,|\(.*\)|\'|\"|\:|@| ]+)(\})/, '\1(\2)\3') # inside a block - post.gsub!(/^(redirect_to)\s+(.*)/, '\1(\2)') # redirect_to, which often has http: - post.gsub!(/^(\w+)\s+([\w|\.|\,|\(.*\)|\{.*\}|\'|\"|\:|@| ]+)/, '\1(\2)') - post.gsub!(/(\s+\))/, ')') - post.gsub!(/\)\}/, ') }') - post.gsub!(/^(\w+)\s+(\/.*\/)/, '\1(\2)') #regexps - line = "#{pre}#{should} #{post}" - end - - line - end - - def standard_matcher?(matcher) - patterns = [ - /^be/, - /^be_close/, - /^eql/, - /^equal/, - /^has/, - /^have/, - /^change/, - /^include/, - /^match/, - /^raise_error/, - /^respond_to/, - /^redirect_to/, - /^satisfy/, - /^throw_symbol/, - # Extra ones that we use in spec_helper - /^pass/, - /^fail/, - /^fail_with/, - ] - matched = patterns.detect{ |p| matcher =~ p } - !matched.nil? - end - - end -end diff --git a/vendor/gems/rspec/lib/spec/version.rb b/vendor/gems/rspec/lib/spec/version.rb deleted file mode 100644 index 26c15e9196..0000000000 --- a/vendor/gems/rspec/lib/spec/version.rb +++ /dev/null @@ -1,22 +0,0 @@ -module Spec - module VERSION - unless defined? MAJOR - MAJOR = 1 - MINOR = 1 - TINY = 3 - RELEASE_CANDIDATE = nil - - BUILD_TIME_UTC = 20080131122909 - - STRING = [MAJOR, MINOR, TINY].join('.') - TAG = "REL_#{[MAJOR, MINOR, TINY, RELEASE_CANDIDATE].compact.join('_')}".upcase.gsub(/\.|-/, '_') - FULL_VERSION = "#{[MAJOR, MINOR, TINY, RELEASE_CANDIDATE].compact.join('.')} (build #{BUILD_TIME_UTC})" - - NAME = "RSpec" - URL = "http://rspec.rubyforge.org/" - - DESCRIPTION = "#{NAME}-#{FULL_VERSION} - BDD for Ruby\n#{URL}" - end - end -end - diff --git a/vendor/gems/rspec/plugins/mock_frameworks/flexmock.rb b/vendor/gems/rspec/plugins/mock_frameworks/flexmock.rb deleted file mode 100644 index 6875a5222b..0000000000 --- a/vendor/gems/rspec/plugins/mock_frameworks/flexmock.rb +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env ruby -# -# Created by Jim Weirich on 2007-04-10. -# Copyright (c) 2007. All rights reserved. - -require 'flexmock/rspec' - -module Spec - module Plugins - module MockFramework - include FlexMock::MockContainer - def setup_mocks_for_rspec - # No setup required - end - def verify_mocks_for_rspec - flexmock_verify - end - def teardown_mocks_for_rspec - flexmock_close - end - end - end -end diff --git a/vendor/gems/rspec/plugins/mock_frameworks/mocha.rb b/vendor/gems/rspec/plugins/mock_frameworks/mocha.rb deleted file mode 100644 index 69d11636c9..0000000000 --- a/vendor/gems/rspec/plugins/mock_frameworks/mocha.rb +++ /dev/null @@ -1,19 +0,0 @@ -require 'mocha/standalone' -require 'mocha/object' - -module Spec - module Plugins - module MockFramework - include Mocha::Standalone - def setup_mocks_for_rspec - mocha_setup - end - def verify_mocks_for_rspec - mocha_verify - end - def teardown_mocks_for_rspec - mocha_teardown - end - end - end -end diff --git a/vendor/gems/rspec/plugins/mock_frameworks/rr.rb b/vendor/gems/rspec/plugins/mock_frameworks/rr.rb deleted file mode 100644 index c019c18a13..0000000000 --- a/vendor/gems/rspec/plugins/mock_frameworks/rr.rb +++ /dev/null @@ -1,21 +0,0 @@ -require 'rr' - -patterns = ::Spec::Runner::QuietBacktraceTweaker::IGNORE_PATTERNS -patterns.push(RR::Errors::BACKTRACE_IDENTIFIER) - -module Spec - module Plugins - module MockFramework - include RR::Extensions::InstanceMethods - def setup_mocks_for_rspec - RR::Space.instance.reset - end - def verify_mocks_for_rspec - RR::Space.instance.verify_doubles - end - def teardown_mocks_for_rspec - RR::Space.instance.reset - end - end - end -end diff --git a/vendor/gems/rspec/plugins/mock_frameworks/rspec.rb b/vendor/gems/rspec/plugins/mock_frameworks/rspec.rb deleted file mode 100644 index ce215ace24..0000000000 --- a/vendor/gems/rspec/plugins/mock_frameworks/rspec.rb +++ /dev/null @@ -1,18 +0,0 @@ -require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "lib", "spec", "mocks")) - -module Spec - module Plugins - module MockFramework - include Spec::Mocks::ExampleMethods - def setup_mocks_for_rspec - $rspec_mocks ||= Spec::Mocks::Space.new - end - def verify_mocks_for_rspec - $rspec_mocks.verify_all - end - def teardown_mocks_for_rspec - $rspec_mocks.reset_all - end - end - end -end diff --git a/vendor/gems/rspec/pre_commit/lib/pre_commit.rb b/vendor/gems/rspec/pre_commit/lib/pre_commit.rb deleted file mode 100644 index 2f3480834b..0000000000 --- a/vendor/gems/rspec/pre_commit/lib/pre_commit.rb +++ /dev/null @@ -1,4 +0,0 @@ -require "pre_commit/pre_commit" -require "pre_commit/rspec" -require "pre_commit/core" -require "pre_commit/rspec_on_rails" diff --git a/vendor/gems/rspec/pre_commit/lib/pre_commit/core.rb b/vendor/gems/rspec/pre_commit/lib/pre_commit/core.rb deleted file mode 100644 index 420cc0c756..0000000000 --- a/vendor/gems/rspec/pre_commit/lib/pre_commit/core.rb +++ /dev/null @@ -1,50 +0,0 @@ -class PreCommit::Core < PreCommit - def pre_commit - rake_invoke :examples - website - end - - def website(run_webby=true) - clobber - rake_invoke :verify_rcov - rake_invoke :spec_html - webby - rake_invoke :failing_examples_with_html - rdoc - rdoc_rails - end - - def clobber - rm_rf '../doc/output' - rm_rf 'translated_specs' - end - - def webby - Dir.chdir '../doc' do - output = silent_sh('rake rebuild 2>&1') - if shell_error?(output) - raise "ERROR while generating web site: #{output}" - end - - spec_page = File.expand_path('output/documentation/tools/spec.html') - spec_page_content = File.open(spec_page).read - unless spec_page_content =~/\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\.\./m - raise "#{'!'*400}\nIt seems like the output in the generated documentation is broken (no dots: ......)\n. Look in #{spec_page}" - end - end - end - - def rdoc - Dir.chdir '../rspec' do - rake = (PLATFORM == "i386-mswin32") ? "rake.cmd" : "rake" - `#{rake} rdoc` - end - end - - def rdoc_rails - Dir.chdir '../rspec_on_rails' do - rake = (PLATFORM == "i386-mswin32") ? "rake.cmd" : "rake" - `#{rake} rdoc` - end - end -end diff --git a/vendor/gems/rspec/pre_commit/lib/pre_commit/pre_commit.rb b/vendor/gems/rspec/pre_commit/lib/pre_commit/pre_commit.rb deleted file mode 100644 index 80f958b917..0000000000 --- a/vendor/gems/rspec/pre_commit/lib/pre_commit/pre_commit.rb +++ /dev/null @@ -1,54 +0,0 @@ -class PreCommit - attr_reader :actor - def initialize(actor) - @actor = actor - end - - protected - def rake_invoke(task_name) - Rake::Task[task_name].invoke - end - - def rake_sh(task_name, env_hash={}) - env = env_hash.collect{|key, value| "#{key}=#{value}"}.join(' ') - rake = (PLATFORM == "i386-mswin32") ? "rake.bat" : "rake" - cmd = "#{rake} #{task_name} #{env} --trace" - output = silent_sh(cmd) - puts output - if shell_error?(output) - raise "ERROR while running rake: #{cmd}" - end - end - - def silent_sh(cmd, &block) - output = nil - IO.popen(cmd) do |io| - output = io.read - output.each_line do |line| - block.call(line) if block - end - end - output - end - - def shell_error?(output) - output =~ /ERROR/n || error_code? - end - - def error_code? - $?.exitstatus != 0 - end - - def root_dir - dir = File.dirname(__FILE__) - File.expand_path("#{dir}/../../../..") - end - - def method_missing(method_name, *args, &block) - if actor.respond_to?(method_name) - actor.send(method_name, *args, &block) - else - super - end - end -end diff --git a/vendor/gems/rspec/pre_commit/lib/pre_commit/rspec.rb b/vendor/gems/rspec/pre_commit/lib/pre_commit/rspec.rb deleted file mode 100644 index 5078c72d0e..0000000000 --- a/vendor/gems/rspec/pre_commit/lib/pre_commit/rspec.rb +++ /dev/null @@ -1,111 +0,0 @@ -class PreCommit::Rspec < PreCommit - def pre_commit - check_for_gem_dependencies - fix_cr_lf - touch_revision_storing_files - pre_commit_core - pre_commit_textmate_bundle - pre_commit_rails - ok_to_commit - end - - def check_for_gem_dependencies - require "rubygems" - gem 'rake' - gem 'webby' - gem 'coderay' - gem 'RedCloth' - gem 'syntax' - gem 'diff-lcs' - gem 'heckle' unless PLATFORM == "i386-mswin32" - gem 'hpricot' - end - - def fix_cr_lf - files = FileList['**/*.rb']. - exclude('example_rails_app/vendor/**'). - exclude('rspec/translated_specs/**') - $\="\n" - files.each do |f| - raw_content = File.read(f) - fixed_content = "" - raw_content.each_line do |line| - fixed_content << line - end - unless raw_content == fixed_content - File.open(f, "w") do |io| - io.print fixed_content - end - end - end - end - - # TODO - move me up to the project root - def touch_revision_storing_files - files = [ - 'rspec/lib/spec/version.rb', - 'rspec_on_rails/lib/spec/rails/version.rb' - ] - build_time_utc = Time.now.utc.strftime('%Y%m%d%H%M%S') - files.each do |path| - abs_path = File.join(root_dir, path) - content = File.open(abs_path).read - touched_content = content.gsub(/BUILD_TIME_UTC = (\d*)/, "BUILD_TIME_UTC = #{build_time_utc}") - File.open(abs_path, 'w') do |io| - io.write touched_content - end - end - end - - def pre_commit_core - Dir.chdir 'rspec' do - rake = (PLATFORM == "i386-mswin32") ? "rake.bat" : "rake" - system("#{rake} pre_commit --verbose --trace") - raise "RSpec Core pre_commit failed" if error_code? - end - end - - def pre_commit_textmate_bundle - Dir.chdir 'RSpec.tmbundle/Support' do - rake = (PLATFORM == "i386-mswin32") ? "rake.bat" : "rake" - system("#{rake} spec --verbose --trace") - raise "RSpec Textmate Bundle specs failed" if error_code? - end - end - - def install_dependencies - Dir.chdir 'example_rails_app' do - rake_sh("-f Multirails.rake install_dependencies") - end - end - - def update_dependencies - Dir.chdir 'example_rails_app' do - rake_sh("-f Multirails.rake update_dependencies") - end - end - - def pre_commit_rails - Dir.chdir 'example_rails_app' do - rake = (PLATFORM == "i386-mswin32") ? "rake.cmd" : "rake" - cmd = "#{rake} -f Multirails.rake pre_commit --trace" - system(cmd) - if error_code? - message = <<-EOF - ############################################################ - RSpec on Rails Plugin pre_commit failed. For more info: - - cd example_rails_app - #{cmd} - - ############################################################ - EOF - raise message.gsub(/^ /, '') - end - end - end - - def ok_to_commit - puts "OK TO COMMIT" - end -end diff --git a/vendor/gems/rspec/pre_commit/lib/pre_commit/rspec_on_rails.rb b/vendor/gems/rspec/pre_commit/lib/pre_commit/rspec_on_rails.rb deleted file mode 100644 index 7879537638..0000000000 --- a/vendor/gems/rspec/pre_commit/lib/pre_commit/rspec_on_rails.rb +++ /dev/null @@ -1,313 +0,0 @@ -class PreCommit::RspecOnRails < PreCommit - def pre_commit - install_plugins - check_dependencies - used_railses = [] - VENDOR_DEPS.each do |dependency| - rails_dir = File.expand_path(dependency[:checkout_path]) - rails_version = rails_version_from_dir(rails_dir) - begin - rspec_pre_commit(rails_version, false) - used_railses << rails_version - rescue Exception => e - unless rails_version == 'edge' - raise e - end - end - end - uninstall_plugins - puts "All specs passed against the following released versions of Rails: #{used_railses.join(", ")}" - unless used_railses.include?('edge') - puts "There were errors running pre_commit against edge" - end - end - - def rails_version_from_dir(rails_dir) - File.basename(rails_dir) - end - - def rspec_pre_commit(rails_version=ENV['RSPEC_RAILS_VERSION'],uninstall=true) - puts "#####################################################" - puts "running pre_commit against rails #{rails_version}" - puts "#####################################################" - ENV['RSPEC_RAILS_VERSION'] = rails_version - cleanup(uninstall) - ensure_db_config - clobber_sqlite_data - install_plugins - generate_rspec - - generate_login_controller - create_purchase - - rake_sh "spec" - rake_sh "spec:plugins:rspec_on_rails" - - # TODO - why is this necessary? Shouldn't the specs leave - # a clean DB? - rake_sh "db:test:prepare" - sh "ruby vendor/plugins/rspec_on_rails/stories/all.rb" - cleanup(uninstall) - end - - def cleanup(uninstall=true) - revert_routes - rm_generated_login_controller_files - destroy_purchase - uninstall_plugins if uninstall - end - - def revert_routes - output = silent_sh("cp config/routes.rb.bak config/routes.rb") - raise "Error reverting routes.rb" if shell_error?(output) - end - - def create_purchase - generate_purchase - migrate_up - end - - def install_plugins - install_rspec_on_rails_plugin - install_rspec_plugin - end - - def install_rspec_on_rails_plugin - rm_rf 'vendor/plugins/rspec_on_rails' - copy '../rspec_on_rails', 'vendor/plugins/' - end - - def install_rspec_plugin - rm_rf 'vendor/plugins/rspec' - copy '../rspec', 'vendor/plugins/' - end - - def uninstall_plugins - rm_rf 'vendor/plugins/rspec_on_rails' - rm_rf 'vendor/plugins/rspec' - rm_rf 'script/spec' - rm_rf 'script/spec_server' - rm_rf 'spec/spec_helper.rb' - rm_rf 'spec/spec.opts' - rm_rf 'spec/rcov.opts' - end - - def copy(source, target) - output = silent_sh("cp -R #{File.expand_path(source)} #{File.expand_path(target)}") - raise "Error installing rspec" if shell_error?(output) - end - - def generate_rspec - result = silent_sh("ruby script/generate rspec --force") - if error_code? || result =~ /^Missing/ - raise "Failed to generate rspec environment:\n#{result}" - end - end - - def ensure_db_config - config_path = 'config/database.yml' - unless File.exists?(config_path) - message = <<-EOF - ##################################################### - Could not find #{config_path} - - You can get rake to generate this file for you using either of: - rake rspec:generate_mysql_config - rake rspec:generate_sqlite3_config - - If you use mysql, you'll need to create dev and test - databases and users for each. To do this, standing - in rspec_on_rails, log into mysql as root and then... - mysql> source db/mysql_setup.sql; - - There is also a teardown script that will remove - the databases and users: - mysql> source db/mysql_teardown.sql; - ##################################################### - EOF - raise message.gsub(/^ /, '') - end - end - - def generate_mysql_config - copy 'config/database.mysql.yml', 'config/database.yml' - end - - def generate_sqlite3_config - copy 'config/database.sqlite3.yml', 'config/database.yml' - end - - def clobber_db_config - rm 'config/database.yml' - end - - def clobber_sqlite_data - rm_rf 'db/*.db' - end - - def generate_purchase - generator = "ruby script/generate rspec_scaffold purchase order_id:integer created_at:datetime amount:decimal keyword:string description:text --force" - notice = <<-EOF - ##################################################### - #{generator} - ##################################################### - EOF - puts notice.gsub(/^ /, '') - result = silent_sh(generator) - if error_code? || result =~ /not/ - raise "rspec_scaffold failed. #{result}" - end - end - - def purchase_migration_version - "005" - end - - def migrate_up - rake_sh "db:migrate" - end - - def destroy_purchase - migrate_down - rm_generated_purchase_files - end - - def migrate_down - notice = <<-EOF - ##################################################### - Migrating down and reverting config/routes.rb - ##################################################### - EOF - puts notice.gsub(/^ /, '') - rake_sh "db:migrate", 'VERSION' => (purchase_migration_version.to_i - 1) - output = silent_sh("cp config/routes.rb.bak config/routes.rb") - raise "revert failed: #{output}" if error_code? - end - - def rm_generated_purchase_files - puts "#####################################################" - puts "Removing generated files:" - generated_files = %W{ - app/helpers/purchases_helper.rb - app/models/purchase.rb - app/controllers/purchases_controller.rb - app/views/purchases - db/migrate/#{purchase_migration_version}_create_purchases.rb - spec/models/purchase_spec.rb - spec/helpers/purchases_helper_spec.rb - spec/controllers/purchases_controller_spec.rb - spec/controllers/purchases_routing_spec.rb - spec/fixtures/purchases.yml - spec/views/purchases - } - generated_files.each do |file| - rm_rf file - end - puts "#####################################################" - end - - def generate_login_controller - generator = "ruby script/generate rspec_controller login signup login logout --force" - notice = <<-EOF - ##################################################### - #{generator} - ##################################################### - EOF - puts notice.gsub(/^ /, '') - result = silent_sh(generator) - if error_code? || result =~ /not/ - raise "rspec_scaffold failed. #{result}" - end - end - - def rm_generated_login_controller_files - puts "#####################################################" - puts "Removing generated files:" - generated_files = %W{ - app/helpers/login_helper.rb - app/controllers/login_controller.rb - app/views/login - spec/helpers/login_helper_spec.rb - spec/controllers/login_controller_spec.rb - spec/views/login - } - generated_files.each do |file| - rm_rf file - end - puts "#####################################################" - end - - def install_dependencies - VENDOR_DEPS.each do |dep| - puts "\nChecking for #{dep[:name]} ..." - dest = dep[:checkout_path] - if File.exists?(dest) - puts "#{dep[:name]} already installed" - else - cmd = "svn co #{dep[:url]} #{dest}" - puts "Installing #{dep[:name]}" - puts "This may take a while." - puts cmd - system(cmd) - puts "Done!" - end - end - puts - end - - def check_dependencies - VENDOR_DEPS.each do |dep| - unless File.exist?(dep[:checkout_path]) - raise "There is no checkout of #{dep[:checkout_path]}. Please run rake install_dependencies" - end - # Verify that the current working copy is right - if `svn info #{dep[:checkout_path]}` =~ /^URL: (.*)/ - actual_url = $1 - if actual_url != dep[:url] - raise "Your working copy in #{dep[:checkout_path]} points to \n#{actual_url}\nIt has moved to\n#{dep[:url]}\nPlease delete the working copy and run rake install_dependencies" - end - end - end - end - - def update_dependencies - check_dependencies - VENDOR_DEPS.each do |dep| - next if dep[:tagged?] # - puts "\nUpdating #{dep[:name]} ..." - dest = dep[:checkout_path] - system("svn cleanup #{dest}") - cmd = "svn up #{dest}" - puts cmd - system(cmd) - puts "Done!" - end - end - - VENDOR_DEPS = [ - { - :checkout_path => "vendor/rails/2.0.2", - :name => "rails 2.0.2", - :url => "http://dev.rubyonrails.org/svn/rails/tags/rel_2-0-2", - :tagged? => true - }, - { - :checkout_path => "vendor/rails/1.2.6", - :name => "rails 1.2.6", - :url => "http://dev.rubyonrails.org/svn/rails/tags/rel_1-2-6", - :tagged? => true - }, - { - :checkout_path => "vendor/rails/1.2.3", - :name => "rails 1.2.3", - :url => "http://dev.rubyonrails.org/svn/rails/tags/rel_1-2-3", - :tagged? => true - }, - { - :checkout_path => "vendor/rails/edge", - :name => "edge rails", - :url => "http://svn.rubyonrails.org/rails/trunk", - :tagged? => false - } - ] -end diff --git a/vendor/gems/rspec/pre_commit/spec/pre_commit/pre_commit_spec.rb b/vendor/gems/rspec/pre_commit/spec/pre_commit/pre_commit_spec.rb deleted file mode 100644 index 5d1c8f9b95..0000000000 --- a/vendor/gems/rspec/pre_commit/spec/pre_commit/pre_commit_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -require File.dirname(__FILE__) + '/../spec_helper.rb' - -## -# This is not a complete specification of PreCommit, but -# just a collection of bug fix regression tests. -describe "The helper method PreCommit#silent_sh" do - before do - @pre_commit = PreCommit.new(nil) - end - - # bug in r1802 - it "should return the command output" do - @pre_commit.send(:silent_sh, "echo foo").should ==("foo\n") - end -end diff --git a/vendor/gems/rspec/pre_commit/spec/pre_commit/rspec_on_rails_spec.rb b/vendor/gems/rspec/pre_commit/spec/pre_commit/rspec_on_rails_spec.rb deleted file mode 100644 index 1932fff865..0000000000 --- a/vendor/gems/rspec/pre_commit/spec/pre_commit/rspec_on_rails_spec.rb +++ /dev/null @@ -1,36 +0,0 @@ -require File.dirname(__FILE__) + '/../spec_helper.rb' -require 'fileutils' - -include FileUtils - -## -# This is not a complete specification of PreCommit.RSpecOnRails, but -# just a collection of bug fix regression tests. -describe "RSpecOnRails pre_commit" do - before do - @original_dir = File.expand_path(FileUtils.pwd) - @rails_app_dir = File.expand_path(File.dirname(__FILE__) + "/../../../example_rails_app/") - - Dir.chdir(@rails_app_dir) - rm_rf('vendor/plugins/rspec_on_rails') - system("svn export ../rspec_on_rails vendor/plugins/rspec_on_rails") - - @pre_commit = PreCommit::RspecOnRails.new(nil) - end - - after do - rm('db/migrate/888_create_purchases.rb', :force => true) - @pre_commit.destroy_purchase - Dir.chdir(@original_dir) - end - - # bug in r1802 - it "should fail noisily if there is a migration name conflict" do - touch('db/migrate/888_create_purchases.rb') - lambda { @pre_commit.generate_purchase }.should raise_error - end - - it "should not fail if tests run ok" do - lambda { @pre_commit.generate_purchase }.should_not raise_error - end -end diff --git a/vendor/gems/rspec/pre_commit/spec/spec_helper.rb b/vendor/gems/rspec/pre_commit/spec/spec_helper.rb deleted file mode 100644 index b7e5f3d44d..0000000000 --- a/vendor/gems/rspec/pre_commit/spec/spec_helper.rb +++ /dev/null @@ -1,3 +0,0 @@ -$LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib' -require 'pre_commit' - diff --git a/vendor/gems/rspec/pre_commit/spec/spec_suite.rb b/vendor/gems/rspec/pre_commit/spec/spec_suite.rb deleted file mode 100644 index a8c7c07ace..0000000000 --- a/vendor/gems/rspec/pre_commit/spec/spec_suite.rb +++ /dev/null @@ -1,11 +0,0 @@ -class SpecSuite - def run - system("ruby rspec/spec/rspec_suite.rb") || raise("Rspec Suite FAILED") - system("ruby rspec_on_rails/spec/rails_suite.rb") || raise("Rails Suite FAILED") - system("ruby cached_example_rails_app/spec/rails_app_suite.rb") || raise("Rails App Suite FAILED") - end -end - -if $0 == __FILE__ - SpecSuite.new.run -end diff --git a/vendor/gems/rspec/rake_tasks/examples.rake b/vendor/gems/rspec/rake_tasks/examples.rake deleted file mode 100644 index 32d0ad0e69..0000000000 --- a/vendor/gems/rspec/rake_tasks/examples.rake +++ /dev/null @@ -1,7 +0,0 @@ -require 'rake' -require 'spec/rake/spectask' - -desc "Run all examples" -Spec::Rake::SpecTask.new('examples') do |t| - t.spec_files = FileList['examples/**/*.rb'] -end diff --git a/vendor/gems/rspec/rake_tasks/examples_with_rcov.rake b/vendor/gems/rspec/rake_tasks/examples_with_rcov.rake deleted file mode 100644 index 4bf35c6b8b..0000000000 --- a/vendor/gems/rspec/rake_tasks/examples_with_rcov.rake +++ /dev/null @@ -1,9 +0,0 @@ -require 'rake' -require 'spec/rake/spectask' - -desc "Run all examples with RCov" -Spec::Rake::SpecTask.new('examples_with_rcov') do |t| - t.spec_files = FileList['examples/**/*.rb'] - t.rcov = true - t.rcov_opts = ['--exclude', 'examples'] -end \ No newline at end of file diff --git a/vendor/gems/rspec/rake_tasks/failing_examples_with_html.rake b/vendor/gems/rspec/rake_tasks/failing_examples_with_html.rake deleted file mode 100644 index 34549583dd..0000000000 --- a/vendor/gems/rspec/rake_tasks/failing_examples_with_html.rake +++ /dev/null @@ -1,9 +0,0 @@ -require 'rake' -require 'spec/rake/spectask' - -desc "Generate HTML report for failing examples" -Spec::Rake::SpecTask.new('failing_examples_with_html') do |t| - t.spec_files = FileList['failing_examples/**/*.rb'] - t.spec_opts = ["--format", "html:../doc/output/documentation/tools/failing_examples.html", "--diff"] - t.fail_on_error = false -end \ No newline at end of file diff --git a/vendor/gems/rspec/rake_tasks/verify_rcov.rake b/vendor/gems/rspec/rake_tasks/verify_rcov.rake deleted file mode 100644 index a90a266df4..0000000000 --- a/vendor/gems/rspec/rake_tasks/verify_rcov.rake +++ /dev/null @@ -1,7 +0,0 @@ -require 'rake' -require 'spec/rake/verify_rcov' - -RCov::VerifyTask.new(:verify_rcov => :spec) do |t| - t.threshold = 100.0 # Make sure you have rcov 0.7 or higher! - t.index_html = '../doc/output/coverage/index.html' -end diff --git a/vendor/gems/rspec/spec/README.jruby b/vendor/gems/rspec/spec/README.jruby deleted file mode 100644 index 7eddb5671a..0000000000 --- a/vendor/gems/rspec/spec/README.jruby +++ /dev/null @@ -1,15 +0,0 @@ -= Running specs on JRuby = - -svn co http://svn.codehaus.org/jruby/trunk jruby -cd jruby/jruby -ant clean -ant -# put JRuby's bin dir on your PATH -jruby -S gem install rake --no-ri --no-rdoc -jruby -S gem install diff-lcs -jruby -S gem install syntax -cd ../testsuites/rspec -mkdir target -jruby -S rake checkout_code -cd target/rspec -jruby bin/spec spec -c diff --git a/vendor/gems/rspec/spec/autotest/discover_spec.rb b/vendor/gems/rspec/spec/autotest/discover_spec.rb deleted file mode 100644 index da5cb14456..0000000000 --- a/vendor/gems/rspec/spec/autotest/discover_spec.rb +++ /dev/null @@ -1,19 +0,0 @@ -require File.dirname(__FILE__) + "/../autotest_helper" - -module DiscoveryHelper - def load_discovery - require File.dirname(__FILE__) + "/../../lib/autotest/discover" - end -end - - -class Autotest - describe Rspec, "discovery" do - include DiscoveryHelper - - it "should add the rspec autotest plugin" do - Autotest.should_receive(:add_discovery).and_yield - load_discovery - end - end -end diff --git a/vendor/gems/rspec/spec/autotest/rspec_spec.rb b/vendor/gems/rspec/spec/autotest/rspec_spec.rb deleted file mode 100644 index 67ee7fbef5..0000000000 --- a/vendor/gems/rspec/spec/autotest/rspec_spec.rb +++ /dev/null @@ -1,195 +0,0 @@ -require File.dirname(__FILE__) + "/../autotest_helper" - -class Autotest - - module AutotestHelper - def rspec_output - <<-HERE -.............PPF - -1) -'false should be false' FAILED -expected: true, - got: false (using ==) -./spec/autotest/rspec_spec.rb:203: - -Finished in 0.158674 seconds - -16 examples, 1 failure, 2 pending - -Pending: -Autotest::Rspec handling failed results should return an array of failed examples and errors (TODO) -Autotest::Rspec tests/specs for a given file should find all the specs for a given file (TODO) -HERE - end - - - def common_setup - @proc = mock Proc - @kernel = mock Kernel - @kernel.stub!(:proc).and_return @proc - - File.stub!(:exists).and_return true - @windows_alt_separator = "\\" - @posix_separator = '/' - - @rspec_output = rspec_output - end - end - - describe Rspec, "rspec_commands" do - it "should contain the various commands, ordered by preference" do - Rspec.new.spec_commands.should == [ - File.expand_path("#{File.dirname(__FILE__)}/../../bin/spec"), - "#{Config::CONFIG['bindir']}/spec" - ] - end - end - - describe Rspec, "selection of rspec command" do - include AutotestHelper - - before :each do - common_setup - @rspec_autotest = Rspec.new - end - - it "should try to find the spec command if it exists in ./bin and use it above everything else" do - File.stub!(:exists?).and_return true - - spec_path = File.expand_path("#{File.dirname(__FILE__)}/../../bin/spec") - File.should_receive(:exists?).with(spec_path).and_return true - @rspec_autotest.spec_command.should == spec_path - end - - it "should otherwise select the default spec command in gem_dir/bin/spec" do - @rspec_autotest.stub!(:spec_commands).and_return ["/foo/spec"] - Config::CONFIG.stub!(:[]).and_return "/foo" - File.should_receive(:exists?).with("/foo/spec").and_return(true) - - @rspec_autotest.spec_command.should == "/foo/spec" - end - - it "should raise an error if no spec command is found at all" do - File.stub!(:exists?).and_return false - - lambda { - @rspec_autotest.spec_command - }.should raise_error(RspecCommandError, "No spec command could be found!") - end - - end - - describe Rspec, "selection of rspec command (windows compatibility issues)" do - include AutotestHelper - - before :each do - common_setup - end - - it "should use the ALT_SEPARATOR if it is non-nil" do - @rspec_autotest = Rspec.new - spec_command = File.expand_path("#{File.dirname(__FILE__)}/../../bin/spec") - @rspec_autotest.stub!(:spec_commands).and_return [spec_command] - @rspec_autotest.spec_command(@windows_alt_separator).should == spec_command.gsub('/', @windows_alt_separator) - end - - it "should not use the ALT_SEPATOR if it is nil" do - @windows_alt_separator = nil - @rspec_autotest = Rspec.new - spec_command = File.expand_path("#{File.dirname(__FILE__)}/../../bin/spec") - @rspec_autotest.stub!(:spec_commands).and_return [spec_command] - @rspec_autotest.spec_command.should == spec_command - end - end - - describe Rspec, "adding spec.opts --options" do - before :each do - @rspec_autotest = Rspec.new - end - - it "should return the command line option to add spec.opts if the options file exists" do - File.stub!(:exist?).and_return true - @rspec_autotest.add_options_if_present.should == "-O spec/spec.opts " - end - - it "should return an empty string if no spec.opts exists" do - File.stub!(:exist?).and_return false - Rspec.new.add_options_if_present.should == "" - end - end - - describe Rspec do - before :each do - @rspec_autotest = Rspec.new - @rspec_autotest.stub!(:ruby).and_return "ruby" - @rspec_autotest.stub!(:add_options_if_present).and_return "-O spec/spec.opts" - - @ruby = @rspec_autotest.ruby - @spec_command = @rspec_autotest.spec_command - @options = @rspec_autotest.add_options_if_present - @files_to_test = { - :spec => ["file_one", "file_two"] - } - # this is not the inner representation of Autotest! - @rspec_autotest.stub!(:files_to_test).and_return @files_to_test - @files_to_test.stub!(:keys).and_return @files_to_test[:spec] - @to_test = @files_to_test.keys.flatten.join ' ' - end - - it "should make the apropriate test command" do - @rspec_autotest.make_test_cmd(@files_to_test).should == "#{@ruby} -S #{@spec_command} #{@options} #{@to_test}" - end - end - - describe Rspec, "mappings" do - - before(:each) do - @lib_file = "lib/something.rb" - @spec_file = "spec/something_spec.rb" - @rspec_autotest = Rspec.new - @rspec_autotest.hook :initialize - end - - it "should find the spec file for a given lib file" do - @rspec_autotest.should map_specs([@spec_file]).to(@lib_file) - end - - it "should find the spec file if given a spec file" do - @rspec_autotest.should map_specs([@spec_file]).to(@spec_file) - end - - it "should only find the file if the file is being tracked (in @file)" do - @rspec_autotest.should map_specs([]).to("lib/untracked_file") - end - end - - describe Rspec, "consolidating failures" do - include AutotestHelper - - before :each do - common_setup - @rspec_autotest = Rspec.new - - @spec_file = "./spec/autotest/rspec_spec.rb" - @rspec_autotest.instance_variable_set("@files", {@spec_file => Time.now}) - @rspec_autotest.stub!(:find_files_to_test).and_return true - end - - it "should return no failures if no failures were given in the output" do - @rspec_autotest.consolidate_failures([[]]).should == {} - end - - it "should return a hash with the spec filename => spec name for each failure or error" do - @rspec_autotest.stub!(:test_files_for).and_return "./spec/autotest/rspec_spec.rb" - foo = [ - [ - "false should be false", - "expected: true,\n got: false (using ==)\n./spec/autotest/rspec_spec.rb:203:" - ] - ] - @rspec_autotest.consolidate_failures(foo).should == {@spec_file => ["false should be false"]} - end - - end -end diff --git a/vendor/gems/rspec/spec/autotest_helper.rb b/vendor/gems/rspec/spec/autotest_helper.rb deleted file mode 100644 index 1b6c6002b3..0000000000 --- a/vendor/gems/rspec/spec/autotest_helper.rb +++ /dev/null @@ -1,6 +0,0 @@ -require "rubygems" -require 'autotest' -dir = File.dirname(__FILE__) -require "#{dir}/spec_helper" -require File.expand_path("#{dir}/../lib/autotest/rspec") -require "#{dir}/autotest_matchers" diff --git a/vendor/gems/rspec/spec/autotest_matchers.rb b/vendor/gems/rspec/spec/autotest_matchers.rb deleted file mode 100644 index 5e23452e26..0000000000 --- a/vendor/gems/rspec/spec/autotest_matchers.rb +++ /dev/null @@ -1,47 +0,0 @@ -module Spec - module Matchers - class AutotestMappingMatcher - def initialize(specs) - @specs = specs - end - - def to(file) - @file = file - self - end - - def matches?(autotest) - @autotest = prepare autotest - @actual = autotest.test_files_for(@file) - @actual == @specs - end - - def failure_message - "expected #{@autotest.class} to map #{@specs.inspect} to #{@file.inspect}\ngot #{@actual.inspect}" - end - - private - def prepare autotest - stub_found_files autotest - stub_find_order autotest - autotest - end - - def stub_found_files autotest - found_files = @specs.inject({}){|h,f| h[f] = Time.at(0)} - autotest.stub!(:find_files).and_return(found_files) - end - - def stub_find_order autotest - find_order = @specs.dup << @file - autotest.instance_eval { @find_order = find_order } - end - - end - - def map_specs(specs) - AutotestMappingMatcher.new(specs) - end - - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/rspec_suite.rb b/vendor/gems/rspec/spec/rspec_suite.rb deleted file mode 100644 index abd016a6df..0000000000 --- a/vendor/gems/rspec/spec/rspec_suite.rb +++ /dev/null @@ -1,7 +0,0 @@ -if __FILE__ == $0 - dir = File.dirname(__FILE__) - Dir["#{dir}/**/*_spec.rb"].reverse.each do |file| -# puts "require '#{file}'" - require file - end -end diff --git a/vendor/gems/rspec/spec/ruby_forker.rb b/vendor/gems/rspec/spec/ruby_forker.rb deleted file mode 100644 index 6ab038750c..0000000000 --- a/vendor/gems/rspec/spec/ruby_forker.rb +++ /dev/null @@ -1,13 +0,0 @@ -require 'rbconfig' - -module RubyForker - # Forks a ruby interpreter with same type as ourself. - # juby will fork jruby, ruby will fork ruby etc. - def ruby(args, stderr=nil) - config = ::Config::CONFIG - interpreter = File::join(config['bindir'], config['ruby_install_name']) + config['EXEEXT'] - cmd = "#{interpreter} #{args}" - cmd << " 2> #{stderr}" unless stderr.nil? - `#{cmd}` - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec.opts b/vendor/gems/rspec/spec/spec.opts deleted file mode 100644 index 48e51f93b9..0000000000 --- a/vendor/gems/rspec/spec/spec.opts +++ /dev/null @@ -1,6 +0,0 @@ ---colour ---format -profile ---timeout -20 ---diff \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/example/configuration_spec.rb b/vendor/gems/rspec/spec/spec/example/configuration_spec.rb deleted file mode 100755 index 5b4a6049e6..0000000000 --- a/vendor/gems/rspec/spec/spec/example/configuration_spec.rb +++ /dev/null @@ -1,282 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -module Spec - module Example - - describe Configuration do - before(:each) do - @config = Configuration.new - @example_group = mock("example_group") - end - - describe "#mock_with" do - - it "should default mock framework to rspec" do - @config.mock_framework.should =~ /\/plugins\/mock_frameworks\/rspec$/ - end - - it "should let you set rspec mocking explicitly" do - @config.mock_with(:rspec) - @config.mock_framework.should =~ /\/plugins\/mock_frameworks\/rspec$/ - end - - it "should let you set mocha" do - @config.mock_with(:mocha) - @config.mock_framework.should =~ /\/plugins\/mock_frameworks\/mocha$/ - end - - it "should let you set flexmock" do - @config.mock_with(:flexmock) - @config.mock_framework.should =~ /\/plugins\/mock_frameworks\/flexmock$/ - end - - it "should let you set rr" do - @config.mock_with(:rr) - @config.mock_framework.should =~ /\/plugins\/mock_frameworks\/rr$/ - end - - it "should let you set an arbitrary adapter module" do - adapter = Module.new - @config.mock_with(adapter) - @config.mock_framework.should == adapter - end - end - - describe "#include" do - - before do - @original_configuration = Spec::Runner.configuration - spec_configuration = @config - Spec::Runner.instance_eval {@configuration = spec_configuration} - @example_group_class = Class.new(ExampleGroup) do - class << self - def this_class_has_special_methods - end - end - end - ExampleGroupFactory.register(:foobar, @example_group_class) - end - - after do - original_configuration = @original_configuration - Spec::Runner.instance_eval {@configuration = original_configuration} - ExampleGroupFactory.reset - end - - it "should include the submitted module in ExampleGroup subclasses" do - mod = Module.new - @config.include mod - Class.new(@example_group_class).included_modules.should include(mod) - end - - it "should let you define modules to be included for a specific type" do - mod = Module.new - @config.include mod, :type => :foobar - Class.new(@example_group_class).included_modules.should include(mod) - end - - it "should not include modules in a type they are not intended for" do - mod = Module.new - @other_example_group_class = Class.new(ExampleGroup) - ExampleGroupFactory.register(:baz, @other_example_group_class) - - @config.include mod, :type => :foobar - - Class.new(@other_example_group_class).included_modules.should_not include(mod) - end - - end - - end - - describe Configuration do - - before(:each) do - @config = Configuration.new - @special_example_group = Class.new(ExampleGroup) - @special_child_example_group = Class.new(@special_example_group) - @nonspecial_example_group = Class.new(ExampleGroup) - ExampleGroupFactory.register(:special, @special_example_group) - ExampleGroupFactory.register(:special_child, @special_child_example_group) - ExampleGroupFactory.register(:non_special, @nonspecial_example_group) - @example_group = @special_child_example_group.describe "Special Example Group" - @unselected_example_group = Class.new(@nonspecial_example_group).describe "Non Special Example Group" - end - - after(:each) do - ExampleGroupFactory.reset - end - - describe "#prepend_before" do - it "prepends the before block on all instances of the passed in type" do - order = [] - @config.prepend_before(:all) do - order << :prepend__before_all - end - @config.prepend_before(:all, :type => :special) do - order << :special_prepend__before_all - end - @config.prepend_before(:all, :type => :special_child) do - order << :special_child_prepend__before_all - end - @config.prepend_before(:each) do - order << :prepend__before_each - end - @config.prepend_before(:each, :type => :special) do - order << :special_prepend__before_each - end - @config.prepend_before(:each, :type => :special_child) do - order << :special_child_prepend__before_each - end - @config.prepend_before(:all, :type => :non_special) do - order << :special_prepend__before_all - end - @config.prepend_before(:each, :type => :non_special) do - order << :special_prepend__before_each - end - @example_group.it "calls prepend_before" do - end - - @example_group.run - order.should == [ - :prepend__before_all, - :special_prepend__before_all, - :special_child_prepend__before_all, - :prepend__before_each, - :special_prepend__before_each, - :special_child_prepend__before_each - ] - end - end - - describe "#append_before" do - - it "calls append_before on the type" do - order = [] - @config.append_before(:all) do - order << :append_before_all - end - @config.append_before(:all, :type => :special) do - order << :special_append_before_all - end - @config.append_before(:all, :type => :special_child) do - order << :special_child_append_before_all - end - @config.append_before(:each) do - order << :append_before_each - end - @config.append_before(:each, :type => :special) do - order << :special_append_before_each - end - @config.append_before(:each, :type => :special_child) do - order << :special_child_append_before_each - end - @config.append_before(:all, :type => :non_special) do - order << :special_append_before_all - end - @config.append_before(:each, :type => :non_special) do - order << :special_append_before_each - end - @example_group.it "calls append_before" do - end - - @example_group.run - order.should == [ - :append_before_all, - :special_append_before_all, - :special_child_append_before_all, - :append_before_each, - :special_append_before_each, - :special_child_append_before_each - ] - end - end - - describe "#prepend_after" do - - it "prepends the after block on all instances of the passed in type" do - order = [] - @config.prepend_after(:all) do - order << :prepend__after_all - end - @config.prepend_after(:all, :type => :special) do - order << :special_prepend__after_all - end - @config.prepend_after(:all, :type => :special) do - order << :special_child_prepend__after_all - end - @config.prepend_after(:each) do - order << :prepend__after_each - end - @config.prepend_after(:each, :type => :special) do - order << :special_prepend__after_each - end - @config.prepend_after(:each, :type => :special) do - order << :special_child_prepend__after_each - end - @config.prepend_after(:all, :type => :non_special) do - order << :special_prepend__after_all - end - @config.prepend_after(:each, :type => :non_special) do - order << :special_prepend__after_each - end - @example_group.it "calls prepend_after" do - end - - @example_group.run - order.should == [ - :special_child_prepend__after_each, - :special_prepend__after_each, - :prepend__after_each, - :special_child_prepend__after_all, - :special_prepend__after_all, - :prepend__after_all - ] - end - end - - describe "#append_after" do - - it "calls append_after on the type" do - order = [] - @config.append_after(:all) do - order << :append__after_all - end - @config.append_after(:all, :type => :special) do - order << :special_append__after_all - end - @config.append_after(:all, :type => :special_child) do - order << :special_child_append__after_all - end - @config.append_after(:each) do - order << :append__after_each - end - @config.append_after(:each, :type => :special) do - order << :special_append__after_each - end - @config.append_after(:each, :type => :special_child) do - order << :special_child_append__after_each - end - @config.append_after(:all, :type => :non_special) do - order << :non_special_append_after_all - end - @config.append_after(:each, :type => :non_special) do - order << :non_special_append_after_each - end - @example_group.it "calls append_after" do - end - - @example_group.run - order.should == [ - :special_child_append__after_each, - :special_append__after_each, - :append__after_each, - :special_child_append__after_all, - :special_append__after_all, - :append__after_all - ] - end - end - end - end -end diff --git a/vendor/gems/rspec/spec/spec/example/example_group_class_definition_spec.rb b/vendor/gems/rspec/spec/spec/example/example_group_class_definition_spec.rb deleted file mode 100644 index 0b00e13974..0000000000 --- a/vendor/gems/rspec/spec/spec/example/example_group_class_definition_spec.rb +++ /dev/null @@ -1,48 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper' - -module Spec - module Example - class ExampleGroupSubclass < ExampleGroup - class << self - attr_accessor :examples_ran - end - - @@klass_variable_set = true - CONSTANT = :foobar - - before do - @instance_variable = :hello - end - - it "should run" do - self.class.examples_ran = true - end - - it "should have access to instance variables" do - @instance_variable.should == :hello - end - - it "should have access to class variables" do - @@klass_variable_set.should == true - end - - it "should have access to constants" do - CONSTANT.should == :foobar - end - - it "should have access to methods defined in the Example Group" do - a_method.should == 22 - end - - def a_method - 22 - end - end - - describe ExampleGroupSubclass do - it "should run" do - ExampleGroupSubclass.examples_ran.should be_true - end - end - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/example/example_group_factory_spec.rb b/vendor/gems/rspec/spec/spec/example/example_group_factory_spec.rb deleted file mode 100644 index 3b50011f70..0000000000 --- a/vendor/gems/rspec/spec/spec/example/example_group_factory_spec.rb +++ /dev/null @@ -1,129 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper' - -module Spec - module Example - describe ExampleGroupFactory, "with :foobar registered as custom type" do - - before do - @example_group = Class.new(ExampleGroup) - ExampleGroupFactory.register(:foobar, @example_group) - end - - after do - ExampleGroupFactory.reset - end - - it "should #get the default ExampleGroup type when passed nil" do - ExampleGroupFactory.get(nil).should == ExampleGroup - end - - it "should #get the default ExampleGroup for unregistered non-nil values" do - ExampleGroupFactory.get(:does_not_exist).should == ExampleGroup - end - - it "should #get custom type for :foobar" do - ExampleGroupFactory.get(:foobar).should == @example_group - end - - it "should #get the actual type when that is passed in" do - ExampleGroupFactory.get(@example_group).should == @example_group - end - - end - - describe ExampleGroupFactory, "#create_example_group" do - it "should create a uniquely named class" do - example_group = Spec::Example::ExampleGroupFactory.create_example_group("example_group") {} - example_group.name.should =~ /Spec::Example::ExampleGroup::Subclass_\d+/ - end - - it "should create a Spec::Example::Example subclass by default" do - example_group = Spec::Example::ExampleGroupFactory.create_example_group("example_group") {} - example_group.superclass.should == Spec::Example::ExampleGroup - end - - it "should create a Spec::Example::Example when :type => :default" do - example_group = Spec::Example::ExampleGroupFactory.create_example_group( - "example_group", :type => :default - ) {} - example_group.superclass.should == Spec::Example::ExampleGroup - end - - it "should create a Spec::Example::Example when :type => :default" do - example_group = Spec::Example::ExampleGroupFactory.create_example_group( - "example_group", :type => :default - ) {} - example_group.superclass.should == Spec::Example::ExampleGroup - end - - it "should create specified type when :type => :something_other_than_default" do - klass = Class.new(ExampleGroup) do - def initialize(*args, &block); end - end - Spec::Example::ExampleGroupFactory.register(:something_other_than_default, klass) - example_group = Spec::Example::ExampleGroupFactory.create_example_group( - "example_group", :type => :something_other_than_default - ) {} - example_group.superclass.should == klass - end - - it "should create a type indicated by :spec_path" do - klass = Class.new(ExampleGroup) do - def initialize(*args, &block); end - end - Spec::Example::ExampleGroupFactory.register(:something_other_than_default, klass) - example_group = Spec::Example::ExampleGroupFactory.create_example_group( - "example_group", :spec_path => "./spec/something_other_than_default/some_spec.rb" - ) {} - example_group.superclass.should == klass - end - - it "should create a type indicated by :spec_path (with spec_path generated by caller on windows)" do - klass = Class.new(ExampleGroup) do - def initialize(*args, &block); end - end - Spec::Example::ExampleGroupFactory.register(:something_other_than_default, klass) - example_group = Spec::Example::ExampleGroupFactory.create_example_group( - "example_group", :spec_path => "./spec\\something_other_than_default\\some_spec.rb" - ) {} - example_group.superclass.should == klass - end - - it "should create and register a Spec::Example::Example if :shared => true" do - shared_example_group = Spec::Example::ExampleGroupFactory.create_example_group( - "name", :spec_path => '/blah/spec/models/blah.rb', :type => :controller, :shared => true - ) {} - shared_example_group.should be_an_instance_of(Spec::Example::SharedExampleGroup) - SharedExampleGroup.shared_example_groups.should include(shared_example_group) - end - - it "should favor the :type over the :spec_path" do - klass = Class.new(ExampleGroup) do - def initialize(*args, &block); end - end - Spec::Example::ExampleGroupFactory.register(:something_other_than_default, klass) - example_group = Spec::Example::ExampleGroupFactory.create_example_group( - "name", :spec_path => '/blah/spec/models/blah.rb', :type => :something_other_than_default - ) {} - example_group.superclass.should == klass - end - - it "should register ExampleGroup by default" do - example_group = Spec::Example::ExampleGroupFactory.create_example_group("The ExampleGroup") do - end - rspec_options.example_groups.should include(example_group) - end - - it "should enable unregistering of ExampleGroups" do - example_group = Spec::Example::ExampleGroupFactory.create_example_group("The ExampleGroup") do - unregister - end - rspec_options.example_groups.should_not include(example_group) - end - - after(:each) do - Spec::Example::ExampleGroupFactory.reset - end - end - end -end diff --git a/vendor/gems/rspec/spec/spec/example/example_group_methods_spec.rb b/vendor/gems/rspec/spec/spec/example/example_group_methods_spec.rb deleted file mode 100644 index 2b6d660fe1..0000000000 --- a/vendor/gems/rspec/spec/spec/example/example_group_methods_spec.rb +++ /dev/null @@ -1,489 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper' - -module Spec - module Example - describe 'ExampleGroupMethods' do - it_should_behave_like "sandboxed rspec_options" - attr_reader :example_group, :result, :reporter - before(:each) do - options.formatters << mock("formatter", :null_object => true) - options.backtrace_tweaker = mock("backtrace_tweaker", :null_object => true) - @reporter = FakeReporter.new(@options) - options.reporter = reporter - @example_group = Class.new(ExampleGroup) do - describe("ExampleGroup") - it "does nothing" - end - class << example_group - public :include - end - @result = nil - end - - after(:each) do - ExampleGroup.reset - end - - describe "#describe" do - attr_reader :child_example_group - before do - @child_example_group = @example_group.describe("Another ExampleGroup") do - it "should pass" do - true.should be_true - end - end - end - - it "should create a subclass of the ExampleGroup when passed a block" do - child_example_group.superclass.should == @example_group - @options.example_groups.should include(child_example_group) - end - - it "should not inherit examples" do - child_example_group.examples.length.should == 1 - end - end - - describe "#it" do - it "should should create an example instance" do - lambda { - @example_group.it("") - }.should change { @example_group.examples.length }.by(1) - end - end - - describe "#xit" do - before(:each) do - Kernel.stub!(:warn) - end - - it "should NOT should create an example instance" do - lambda { - @example_group.xit("") - }.should_not change(@example_group.examples, :length) - end - - it "should warn that it is disabled" do - Kernel.should_receive(:warn).with("Example disabled: foo") - @example_group.xit("foo") - end - end - - describe "#examples" do - it "should have Examples" do - example_group = Class.new(ExampleGroup) do - describe('example') - it "should pass" do - 1.should == 1 - end - end - example_group.examples.length.should == 1 - example_group.examples.first.description.should == "should pass" - end - - it "should not include methods that begin with test (only when TU interop is loaded)" do - example_group = Class.new(ExampleGroup) do - describe('example') - def test_any_args(*args) - true.should be_true - end - def test_something - 1.should == 1 - end - def test - raise "This is not a real test" - end - def testify - raise "This is not a real test" - end - end - example_group.examples.length.should == 0 - example_group.run.should be_true - end - - it "should include methods that begin with should and has an arity of 0 in suite" do - example_group = Class.new(ExampleGroup) do - describe('example') - def shouldCamelCase - true.should be_true - end - def should_any_args(*args) - true.should be_true - end - def should_something - 1.should == 1 - end - def should_not_something - 1.should_not == 2 - end - def should - raise "This is not a real example" - end - def should_not - raise "This is not a real example" - end - end - example_group = example_group.dup - example_group.examples.length.should == 4 - descriptions = example_group.examples.collect {|example| example.description}.sort - descriptions.should include("shouldCamelCase") - descriptions.should include("should_any_args") - descriptions.should include("should_something") - descriptions.should include("should_not_something") - end - - it "should not include methods that begin with test_ and has an arity > 0 in suite" do - example_group = Class.new(ExampleGroup) do - describe('example') - def test_invalid(foo) - 1.should == 1 - end - def testInvalidCamelCase(foo) - 1.should == 1 - end - end - example_group.examples.length.should == 0 - end - - it "should not include methods that begin with should_ and has an arity > 0 in suite" do - example_group = Class.new(ExampleGroup) do - describe('example') - def should_invalid(foo) - 1.should == 2 - end - def shouldInvalidCamelCase(foo) - 1.should == 3 - end - def should_not_invalid(foo) - 1.should == 4 - end - def should_valid - 1.should == 1 - end - end - example_group.examples.length.should == 1 - example_group.run.should be_true - end - - it "should run should_methods" do - example_group = Class.new(ExampleGroup) do - def should_valid - 1.should == 2 - end - end - example_group.examples.length.should == 1 - example_group.run.should be_false - end - end - - describe "#set_description" do - attr_reader :example_group - before do - class << example_group - public :set_description - end - end - - describe "#set_description(String)" do - before(:each) do - example_group.set_description("abc") - end - - specify ".description should return the String passed into .set_description" do - example_group.description.should == "abc" - end - - specify ".described_type should provide nil as its type" do - example_group.described_type.should be_nil - end - end - - describe "#set_description(Type)" do - before(:each) do - example_group.set_description(ExampleGroup) - end - - specify ".description should return a String representation of that type (fully qualified) as its name" do - example_group.description.should == "Spec::Example::ExampleGroup" - end - - specify ".described_type should return the passed in type" do - example_group.described_type.should == Spec::Example::ExampleGroup - end - end - - describe "#set_description(String, Type)" do - before(:each) do - example_group.set_description("behaving", ExampleGroup) - end - - specify ".description should return String then space then Type" do - example_group.description.should == "behaving Spec::Example::ExampleGroup" - end - - specify ".described_type should return the passed in type" do - example_group.described_type.should == Spec::Example::ExampleGroup - end - end - - describe "#set_description(Type, String not starting with a space)" do - before(:each) do - example_group.set_description(ExampleGroup, "behaving") - end - - specify ".description should return the Type then space then String" do - example_group.description.should == "Spec::Example::ExampleGroup behaving" - end - end - - describe "#set_description(Type, String starting with .)" do - before(:each) do - example_group.set_description(ExampleGroup, ".behaving") - end - - specify ".description should return the Type then String" do - example_group.description.should == "Spec::Example::ExampleGroup.behaving" - end - end - - describe "#set_description(Type, String containing .)" do - before(:each) do - example_group.set_description(ExampleGroup, "calling a.b") - end - - specify ".description should return the Type then space then String" do - example_group.description.should == "Spec::Example::ExampleGroup calling a.b" - end - end - - describe "#set_description(Type, String starting with .)" do - before(:each) do - example_group.set_description(ExampleGroup, ".behaving") - end - - specify "should return the Type then String" do - example_group.description.should == "Spec::Example::ExampleGroup.behaving" - end - end - - describe "#set_description(Type, String containing .)" do - before(:each) do - example_group.set_description(ExampleGroup, "is #1") - end - - specify ".description should return the Type then space then String" do - example_group.description.should == "Spec::Example::ExampleGroup is #1" - end - end - - describe "#set_description(String, Type, String)" do - before(:each) do - example_group.set_description("A", Hash, "with one entry") - end - - specify ".description should return the first String then space then Type then second String" do - example_group.description.should == "A Hash with one entry" - end - end - - describe "#set_description(Hash representing options)" do - before(:each) do - example_group.set_description(:a => "b", :spec_path => "blah") - end - - it ".spec_path should expand the passed in :spec_path option passed into the constructor" do - example_group.spec_path.should == File.expand_path("blah") - end - - it ".description_options should return all the options passed in" do - example_group.description_options.should == {:a => "b", :spec_path => "blah"} - end - - end - end - - describe "#description" do - it "should return the same description instance for each call" do - example_group.description.should eql(example_group.description) - end - - it "should not add a space when description_text begins with #" do - child_example_group = Class.new(example_group) do - describe("#foobar", "Does something") - end - child_example_group.description.should == "ExampleGroup#foobar Does something" - end - - it "should not add a space when description_text begins with ." do - child_example_group = Class.new(example_group) do - describe(".foobar", "Does something") - end - child_example_group.description.should == "ExampleGroup.foobar Does something" - end - - it "should return the class name if nil" do - example_group.set_description(nil) - example_group.description.should =~ /Class:/ - end - - it "should return the class name if nil" do - example_group.set_description("") - example_group.description.should =~ /Class:/ - end - end - - describe "#description_parts" do - it "should return an Array of the current class description args" do - example_group.description_parts.should == [example_group.description] - end - - it "should return an Array of the description args from each class in the hierarchy" do - child_example_group = Class.new(example_group) - child_example_group.describe("Child", ExampleGroup) - child_example_group.description.should_not be_empty - - grand_child_example_group = Class.new(child_example_group) - grand_child_example_group.describe("GrandChild", ExampleGroup) - grand_child_example_group.description.should_not be_empty - - grand_child_example_group.description_parts.should == [ - "ExampleGroup", - "Child", - Spec::Example::ExampleGroup, - "GrandChild", - Spec::Example::ExampleGroup - ] - end - end - - describe "#described_type" do - it "should return passed in type" do - child_example_group = Class.new(example_group) do - describe Object - end - child_example_group.described_type.should == Object - end - - it "should return #described_type of superclass when no passed in type" do - parent_example_group = Class.new(ExampleGroup) do - describe Object, "#foobar" - end - child_example_group = Class.new(parent_example_group) do - describe "not a type" - end - child_example_group.described_type.should == Object - end - end - - describe "#remove_after" do - it "should unregister a given after(:each) block" do - after_all_ran = false - @example_group.it("example") {} - proc = Proc.new { after_all_ran = true } - ExampleGroup.after(:each, &proc) - @example_group.run - after_all_ran.should be_true - - after_all_ran = false - ExampleGroup.remove_after(:each, &proc) - @example_group.run - after_all_ran.should be_false - end - end - - describe "#include" do - it "should have accessible class methods from included module" do - mod1_method_called = false - mod1 = Module.new do - class_methods = Module.new do - define_method :mod1_method do - mod1_method_called = true - end - end - - metaclass.class_eval do - define_method(:included) do |receiver| - receiver.extend class_methods - end - end - end - - mod2_method_called = false - mod2 = Module.new do - class_methods = Module.new do - define_method :mod2_method do - mod2_method_called = true - end - end - - metaclass.class_eval do - define_method(:included) do |receiver| - receiver.extend class_methods - end - end - end - - @example_group.include mod1, mod2 - - @example_group.mod1_method - @example_group.mod2_method - mod1_method_called.should be_true - mod2_method_called.should be_true - end - end - - describe "#number_of_examples" do - it "should count number of specs" do - proc do - @example_group.it("one") {} - @example_group.it("two") {} - @example_group.it("three") {} - @example_group.it("four") {} - end.should change {@example_group.number_of_examples}.by(4) - end - end - - describe "#class_eval" do - it "should allow constants to be defined" do - example_group = Class.new(ExampleGroup) do - describe('example') - FOO = 1 - it "should reference FOO" do - FOO.should == 1 - end - end - example_group.run - Object.const_defined?(:FOO).should == false - end - end - - describe '#register' do - it "should add ExampleGroup to set of ExampleGroups to be run" do - options.example_groups.delete(example_group) - options.example_groups.should_not include(example_group) - - example_group.register {} - options.example_groups.should include(example_group) - end - end - - describe '#unregister' do - before do - options.example_groups.should include(example_group) - end - - it "should remove ExampleGroup from set of ExampleGroups to be run" do - example_group.unregister - options.example_groups.should_not include(example_group) - end - end - - describe "#registration_backtrace" do - it "returns the backtrace of where the ExampleGroup was registered" do - example_group = Class.new(ExampleGroup) - example_group.registration_backtrace.join("\n").should include("#{__FILE__}:#{__LINE__-1}") - end - end - end - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/example/example_group_spec.rb b/vendor/gems/rspec/spec/spec/example/example_group_spec.rb deleted file mode 100644 index 93e558a97b..0000000000 --- a/vendor/gems/rspec/spec/spec/example/example_group_spec.rb +++ /dev/null @@ -1,711 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper' - -module Spec - module Example - class ExampleModuleScopingSpec < ExampleGroup - describe ExampleGroup, "via a class definition" - - module Foo - module Bar - def self.loaded? - true - end - end - end - include Foo - - it "should understand module scoping" do - Bar.should be_loaded - end - - @@foo = 1 - - it "should allow class variables to be defined" do - @@foo.should == 1 - end - end - - class ExampleClassVariablePollutionSpec < ExampleGroup - describe ExampleGroup, "via a class definition without a class variable" - - it "should not retain class variables from other Example classes" do - proc do - @@foo - end.should raise_error - end - end - - describe ExampleGroup, "#pending" do - it "should raise a Pending error when its block fails" do - block_ran = false - lambda { - pending("something") do - block_ran = true - raise "something wrong with my example" - end - }.should raise_error(Spec::Example::ExamplePendingError, "something") - block_ran.should == true - end - - it "should raise Spec::Example::PendingExampleFixedError when its block does not fail" do - block_ran = false - lambda { - pending("something") do - block_ran = true - end - }.should raise_error(Spec::Example::PendingExampleFixedError, "Expected pending 'something' to fail. No Error was raised.") - block_ran.should == true - end - end - - describe ExampleGroup, "#run with failure in example", :shared => true do - it "should add an example failure to the TestResult" do - example_group.run.should be_false - end - end - - describe ExampleGroup, "#run" do - it_should_behave_like "sandboxed rspec_options" - attr_reader :example_group, :formatter, :reporter - before :each do - @formatter = mock("formatter", :null_object => true) - options.formatters << formatter - options.backtrace_tweaker = mock("backtrace_tweaker", :null_object => true) - @reporter = FakeReporter.new(options) - options.reporter = reporter - @example_group = Class.new(ExampleGroup) do - describe("example") - it "does nothing" do - end - end - class << example_group - public :include - end - end - - after :each do - ExampleGroup.reset - end - - it "should not run when there are no examples" do - example_group = Class.new(ExampleGroup) do - describe("Foobar") - end - example_group.examples.should be_empty - - reporter = mock("Reporter") - reporter.should_not_receive(:add_example_group) - example_group.run - end - - describe "when before_each fails" do - before(:each) do - $example_ran = $after_each_ran = false - @example_group = describe("Foobar") do - before(:each) {raise} - it "should not be run" do - $example_ran = true - end - after(:each) do - $after_each_ran = true - end - end - end - - it "should not run example block" do - example_group.run - $example_ran.should be_false - end - - it "should run after_each" do - example_group.run - $after_each_ran.should be_true - end - - it "should report failure location when in before_each" do - reporter.should_receive(:example_finished) do |example_group, error| - error.message.should eql("in before_each") - end - example_group.run - end - end - - describe ExampleGroup, "#run on dry run" do - before do - @options.dry_run = true - end - - it "should not run before(:all) or after(:all)" do - before_all_ran = false - after_all_ran = false - ExampleGroup.before(:all) { before_all_ran = true } - ExampleGroup.after(:all) { after_all_ran = true } - example_group.it("should") {} - example_group.run - before_all_ran.should be_false - after_all_ran.should be_false - end - - it "should not run example" do - example_ran = false - example_group.it("should") {example_ran = true} - example_group.run - example_ran.should be_false - end - end - - describe ExampleGroup, "#run with specified examples" do - attr_reader :examples_that_were_run - before do - @examples_that_were_run = [] - end - - describe "when specified_examples matches entire ExampleGroup" do - before do - examples_that_were_run = @examples_that_were_run - @example_group = Class.new(ExampleGroup) do - describe("the ExampleGroup") - it("should be run") do - examples_that_were_run << 'should be run' - end - - it("should also be run") do - examples_that_were_run << 'should also be run' - end - end - options.examples = ["the ExampleGroup"] - end - - it "should not run the Examples in the ExampleGroup" do - example_group.run - examples_that_were_run.should == ['should be run', 'should also be run'] - end - end - - describe ExampleGroup, "#run when specified_examples matches only Example description" do - before do - examples_that_were_run = @examples_that_were_run - @example_group = Class.new(ExampleGroup) do - describe("example") - it("should be run") do - examples_that_were_run << 'should be run' - end - end - options.examples = ["should be run"] - end - - it "should not run the example" do - example_group.run - examples_that_were_run.should == ['should be run'] - end - end - - describe ExampleGroup, "#run when specified_examples does not match an Example description" do - before do - examples_that_were_run = @examples_that_were_run - @example_group = Class.new(ExampleGroup) do - describe("example") - it("should be something else") do - examples_that_were_run << 'should be something else' - end - end - options.examples = ["does not match anything"] - end - - it "should not run the example" do - example_group.run - examples_that_were_run.should == [] - end - end - - describe ExampleGroup, "#run when specified_examples matches an Example description" do - before do - examples_that_were_run = @examples_that_were_run - @example_group = Class.new(ExampleGroup) do - describe("example") - it("should be run") do - examples_that_were_run << 'should be run' - end - it("should not be run") do - examples_that_were_run << 'should not be run' - end - end - options.examples = ["should be run"] - end - - it "should run only the example, when there in only one" do - example_group.run - examples_that_were_run.should == ["should be run"] - end - - it "should run only the one example" do - example_group.run - examples_that_were_run.should == ["should be run"] end - end - end - - describe ExampleGroup, "#run with success" do - before do - @special_example_group = Class.new(ExampleGroup) - ExampleGroupFactory.register(:special, @special_example_group) - @not_special_example_group = Class.new(ExampleGroup) - ExampleGroupFactory.register(:not_special, @not_special_example_group) - end - - after do - ExampleGroupFactory.reset - end - - it "should send reporter add_example_group" do - example_group.run - reporter.example_groups.should == [example_group] - end - - it "should run example on run" do - example_ran = false - example_group.it("should") {example_ran = true} - example_group.run - example_ran.should be_true - end - - it "should run before(:all) block only once" do - before_all_run_count_run_count = 0 - example_group.before(:all) {before_all_run_count_run_count += 1} - example_group.it("test") {true} - example_group.it("test2") {true} - example_group.run - before_all_run_count_run_count.should == 1 - end - - it "should run after(:all) block only once" do - after_all_run_count = 0 - example_group.after(:all) {after_all_run_count += 1} - example_group.it("test") {true} - example_group.it("test2") {true} - example_group.run - after_all_run_count.should == 1 - @reporter.rspec_verify - end - - it "after(:all) should have access to all instance variables defined in before(:all)" do - context_instance_value_in = "Hello there" - context_instance_value_out = "" - example_group.before(:all) { @instance_var = context_instance_value_in } - example_group.after(:all) { context_instance_value_out = @instance_var } - example_group.it("test") {true} - example_group.run - context_instance_value_in.should == context_instance_value_out - end - - it "should copy instance variables from before(:all)'s execution context into spec's execution context" do - context_instance_value_in = "Hello there" - context_instance_value_out = "" - example_group.before(:all) { @instance_var = context_instance_value_in } - example_group.it("test") {context_instance_value_out = @instance_var} - example_group.run - context_instance_value_in.should == context_instance_value_out - end - - it "should not add global before callbacks for untargetted example_group" do - fiddle = [] - - ExampleGroup.before(:all) { fiddle << "Example.before(:all)" } - ExampleGroup.prepend_before(:all) { fiddle << "Example.prepend_before(:all)" } - @special_example_group.before(:each) { fiddle << "Example.before(:each, :type => :special)" } - @special_example_group.prepend_before(:each) { fiddle << "Example.prepend_before(:each, :type => :special)" } - @special_example_group.before(:all) { fiddle << "Example.before(:all, :type => :special)" } - @special_example_group.prepend_before(:all) { fiddle << "Example.prepend_before(:all, :type => :special)" } - - example_group = Class.new(ExampleGroup) do - describe("I'm not special", :type => :not_special) - it "does nothing" - end - example_group.run - fiddle.should == [ - 'Example.prepend_before(:all)', - 'Example.before(:all)', - ] - end - - it "should add global before callbacks for targetted example_groups" do - fiddle = [] - - ExampleGroup.before(:all) { fiddle << "Example.before(:all)" } - ExampleGroup.prepend_before(:all) { fiddle << "Example.prepend_before(:all)" } - @special_example_group.before(:each) { fiddle << "special.before(:each, :type => :special)" } - @special_example_group.prepend_before(:each) { fiddle << "special.prepend_before(:each, :type => :special)" } - @special_example_group.before(:all) { fiddle << "special.before(:all, :type => :special)" } - @special_example_group.prepend_before(:all) { fiddle << "special.prepend_before(:all, :type => :special)" } - @special_example_group.append_before(:each) { fiddle << "special.append_before(:each, :type => :special)" } - - example_group = Class.new(@special_example_group).describe("I'm a special example_group") {} - example_group.it("test") {true} - example_group.run - fiddle.should == [ - 'Example.prepend_before(:all)', - 'Example.before(:all)', - 'special.prepend_before(:all, :type => :special)', - 'special.before(:all, :type => :special)', - 'special.prepend_before(:each, :type => :special)', - 'special.before(:each, :type => :special)', - 'special.append_before(:each, :type => :special)', - ] - end - - it "should order before callbacks from global to local" do - fiddle = [] - ExampleGroup.prepend_before(:all) { fiddle << "Example.prepend_before(:all)" } - ExampleGroup.before(:all) { fiddle << "Example.before(:all)" } - example_group.prepend_before(:all) { fiddle << "prepend_before(:all)" } - example_group.before(:all) { fiddle << "before(:all)" } - example_group.prepend_before(:each) { fiddle << "prepend_before(:each)" } - example_group.before(:each) { fiddle << "before(:each)" } - example_group.run - fiddle.should == [ - 'Example.prepend_before(:all)', - 'Example.before(:all)', - 'prepend_before(:all)', - 'before(:all)', - 'prepend_before(:each)', - 'before(:each)' - ] - end - - it "should order after callbacks from local to global" do - fiddle = [] - example_group.after(:each) { fiddle << "after(:each)" } - example_group.append_after(:each) { fiddle << "append_after(:each)" } - example_group.after(:all) { fiddle << "after(:all)" } - example_group.append_after(:all) { fiddle << "append_after(:all)" } - ExampleGroup.after(:all) { fiddle << "Example.after(:all)" } - ExampleGroup.append_after(:all) { fiddle << "Example.append_after(:all)" } - example_group.run - fiddle.should == [ - 'after(:each)', - 'append_after(:each)', - 'after(:all)', - 'append_after(:all)', - 'Example.after(:all)', - 'Example.append_after(:all)' - ] - end - - it "should have accessible instance methods from included module" do - mod1_method_called = false - mod1 = Module.new do - define_method :mod1_method do - mod1_method_called = true - end - end - - mod2_method_called = false - mod2 = Module.new do - define_method :mod2_method do - mod2_method_called = true - end - end - - example_group.include mod1, mod2 - - example_group.it("test") do - mod1_method - mod2_method - end - example_group.run - mod1_method_called.should be_true - mod2_method_called.should be_true - end - - it "should include targetted modules included using configuration" do - mod1 = Module.new - mod2 = Module.new - mod3 = Module.new - Spec::Runner.configuration.include(mod1, mod2) - Spec::Runner.configuration.include(mod3, :type => :not_special) - - example_group = Class.new(@special_example_group).describe("I'm special", :type => :special) do - it "does nothing" - end - example_group.run - - example_group.included_modules.should include(mod1) - example_group.included_modules.should include(mod2) - example_group.included_modules.should_not include(mod3) - end - - it "should include any predicate_matchers included using configuration" do - $included_predicate_matcher_found = false - Spec::Runner.configuration.predicate_matchers[:do_something] = :does_something? - example_group = Class.new(ExampleGroup) do - describe('example') - it "should respond to do_something" do - $included_predicate_matcher_found = respond_to?(:do_something) - end - end - example_group.run - $included_predicate_matcher_found.should be(true) - end - - it "should use a mock framework set up in config" do - mod = Module.new do - class << self - def included(mod) - $included_module = mod - end - end - - def teardown_mocks_for_rspec - $torn_down = true - end - end - - begin - $included_module = nil - $torn_down = true - Spec::Runner.configuration.mock_with mod - - example_group = Class.new(ExampleGroup) do - describe('example') - it "does nothing" - end - example_group.run - - $included_module.should_not be_nil - $torn_down.should == true - ensure - Spec::Runner.configuration.mock_with :rspec - end - end - end - - describe ExampleGroup, "#run with pending example that has a failing assertion" do - before do - example_group.it("should be pending") do - pending("Example fails") {false.should be_true} - end - end - - it "should send example_pending to formatter" do - @formatter.should_receive(:example_pending).with("example", "should be pending", "Example fails") - example_group.run - end - end - - describe ExampleGroup, "#run with pending example that does not have a failing assertion" do - it_should_behave_like "Spec::Example::ExampleGroup#run with failure in example" - - before do - example_group.it("should be pending") do - pending("Example passes") {true.should be_true} - end - end - - it "should send example_pending to formatter" do - @formatter.should_receive(:example_pending).with("example", "should be pending", "Example passes") - example_group.run - end - end - - describe ExampleGroup, "#run when before(:all) fails" do - it_should_behave_like "Spec::Example::ExampleGroup#run with failure in example" - - before do - ExampleGroup.before(:all) { raise NonStandardError, "before(:all) failure" } - end - - it "should not run any example" do - spec_ran = false - example_group.it("test") {spec_ran = true} - example_group.run - spec_ran.should be_false - end - - it "should run ExampleGroup after(:all)" do - after_all_ran = false - ExampleGroup.after(:all) { after_all_ran = true } - example_group.run - after_all_ran.should be_true - end - - it "should run example_group after(:all)" do - after_all_ran = false - example_group.after(:all) { after_all_ran = true } - example_group.run - after_all_ran.should be_true - end - - it "should supply before(:all) as description" do - @reporter.should_receive(:failure) do |example, error| - example.description.should eql("before(:all)") - error.message.should eql("before(:all) failure") - end - - example_group.it("test") {true} - example_group.run - end - end - - describe ExampleGroup, "#run when before(:each) fails" do - it_should_behave_like "Spec::Example::ExampleGroup#run with failure in example" - - before do - ExampleGroup.before(:each) { raise NonStandardError } - end - - it "should run after(:all)" do - after_all_ran = false - ExampleGroup.after(:all) { after_all_ran = true } - example_group.run - after_all_ran.should be_true - end - end - - describe ExampleGroup, "#run when any example fails" do - it_should_behave_like "Spec::Example::ExampleGroup#run with failure in example" - - before do - example_group.it("should") { raise NonStandardError } - end - - it "should run after(:all)" do - after_all_ran = false - ExampleGroup.after(:all) { after_all_ran = true } - example_group.run - after_all_ran.should be_true - end - end - - describe ExampleGroup, "#run when first after(:each) block fails" do - it_should_behave_like "Spec::Example::ExampleGroup#run with failure in example" - - before do - class << example_group - attr_accessor :first_after_ran, :second_after_ran - end - example_group.first_after_ran = false - example_group.second_after_ran = false - - example_group.after(:each) do - self.class.second_after_ran = true - end - example_group.after(:each) do - self.class.first_after_ran = true - raise "first" - end - end - - it "should run second after(:each) block" do - reporter.should_receive(:example_finished) do |example, error| - example.should equal(example) - error.message.should eql("first") - end - example_group.run - example_group.first_after_ran.should be_true - example_group.second_after_ran.should be_true - end - end - - describe ExampleGroup, "#run when first before(:each) block fails" do - it_should_behave_like "Spec::Example::ExampleGroup#run with failure in example" - - before do - class << example_group - attr_accessor :first_before_ran, :second_before_ran - end - example_group.first_before_ran = false - example_group.second_before_ran = false - - example_group.before(:each) do - self.class.first_before_ran = true - raise "first" - end - example_group.before(:each) do - self.class.second_before_ran = true - end - end - - it "should not run second before(:each)" do - reporter.should_receive(:example_finished) do |name, error| - error.message.should eql("first") - end - example_group.run - example_group.first_before_ran.should be_true - example_group.second_before_ran.should be_false - end - end - - describe ExampleGroup, "#run when failure in after(:all)" do - it_should_behave_like "Spec::Example::ExampleGroup#run with failure in example" - - before do - ExampleGroup.after(:all) { raise NonStandardError, "in after(:all)" } - end - - it "should return false" do - example_group.run.should be_false - end - end - end - - class ExampleSubclass < ExampleGroup - end - - describe ExampleGroup, "subclasses" do - after do - ExampleGroupFactory.reset - end - - it "should have access to the described_type" do - example_group = Class.new(ExampleSubclass) do - describe(Array) - end - example_group.send(:described_type).should == Array - end - - it "should concat descriptions when nested" do - example_group = Class.new(ExampleSubclass) do - describe(Array) - $nested_group = describe("when empty") do - end - end - $nested_group.description.to_s.should == "Array when empty" - end - end - - describe Enumerable do - def each(&block) - ["4", "2", "1"].each(&block) - end - - it "should be included in examples because it is a module" do - map{|e| e.to_i}.should == [4,2,1] - end - end - - describe "An", Enumerable, "as a second argument" do - def each(&block) - ["4", "2", "1"].each(&block) - end - - it "should be included in examples because it is a module" do - map{|e| e.to_i}.should == [4,2,1] - end - end - - describe Enumerable do - describe "as the parent of nested example groups" do - it "should be included in examples because it is a module" do - pending("need to make sure nested groups know the described type") do - map{|e| e.to_i}.should == [4,2,1] - end - end - end - end - - describe String do - it"should not be included in examples because it is not a module" do - lambda{self.map}.should raise_error(NoMethodError, /undefined method `map' for/) - end - end - end -end diff --git a/vendor/gems/rspec/spec/spec/example/example_matcher_spec.rb b/vendor/gems/rspec/spec/spec/example/example_matcher_spec.rb deleted file mode 100644 index ea0dfe019c..0000000000 --- a/vendor/gems/rspec/spec/spec/example/example_matcher_spec.rb +++ /dev/null @@ -1,96 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -module Spec - module Example - module ExampleMatcherSpecHelper - class MatchDescription - def initialize(description) - @description = description - end - - def matches?(matcher) - matcher.matches?(@description) - end - - def failure_message - "expected matcher.matches?(#{@description.inspect}) to return true, got false" - end - - def negative_failure_message - "expected matcher.matches?(#{@description.inspect}) to return false, got true" - end - end - def match_description(description) - MatchDescription.new(description) - end - end - - describe ExampleMatcher, "#matches?" do - include ExampleMatcherSpecHelper - - it "should match correct example_group and example" do - matcher = ExampleMatcher.new("example_group", "example") - matcher.should match_description("example_group example") - end - - it "should not match wrong example" do - matcher = ExampleMatcher.new("example_group", "other example") - matcher.should_not match_description("example_group example") - end - - it "should not match wrong example_group" do - matcher = ExampleMatcher.new("other example_group", "example") - matcher.should_not match_description("example_group example") - end - - it "should match example only" do - matcher = ExampleMatcher.new("example_group", "example") - matcher.should match_description("example") - end - - it "should match example_group only" do - matcher = ExampleMatcher.new("example_group", "example") - matcher.should match_description("example_group") - end - - it "should match example_group ending with before(:all)" do - matcher = ExampleMatcher.new("example_group", "example") - matcher.should match_description("example_group before(:all)") - end - - it "should escape regexp chars" do - matcher = ExampleMatcher.new("(con|text)", "[example]") - matcher.should_not match_description("con p") - end - - it "should match when example_group is modularized" do - matcher = ExampleMatcher.new("MyModule::MyClass", "example") - matcher.should match_description("MyClass example") - end - end - - describe ExampleMatcher, "#matches? normal case" do - it "matches when passed in example matches" do - matcher = ExampleMatcher.new("Foo", "bar") - matcher.matches?(["no match", "Foo bar"]).should == true - end - - it "does not match when no passed in examples match" do - matcher = ExampleMatcher.new("Foo", "bar") - matcher.matches?(["no match1", "no match2"]).should == false - end - end - - describe ExampleMatcher, "#matches? where description has '::' in it" do - it "matches when passed in example matches" do - matcher = ExampleMatcher.new("Foo::Bar", "baz") - matcher.matches?(["no match", "Foo::Bar baz"]).should == true - end - - it "does not match when no passed in examples match" do - matcher = ExampleMatcher.new("Foo::Bar", "baz") - matcher.matches?(["no match1", "no match2"]).should == false - end - end - end -end diff --git a/vendor/gems/rspec/spec/spec/example/example_methods_spec.rb b/vendor/gems/rspec/spec/spec/example/example_methods_spec.rb deleted file mode 100644 index c18522808f..0000000000 --- a/vendor/gems/rspec/spec/spec/example/example_methods_spec.rb +++ /dev/null @@ -1,104 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper' - -module Spec - module Example - module ModuleThatIsReopened - end - - module ExampleMethods - include ModuleThatIsReopened - end - - module ModuleThatIsReopened - def module_that_is_reopened_method - end - end - - describe ExampleMethods do - describe "with an included module that is reopened" do - it "should have repoened methods" do - method(:module_that_is_reopened_method).should_not be_nil - end - end - - describe "lifecycle" do - before do - @options = ::Spec::Runner::Options.new(StringIO.new, StringIO.new) - @options.formatters << mock("formatter", :null_object => true) - @options.backtrace_tweaker = mock("backtrace_tweaker", :null_object => true) - @reporter = FakeReporter.new(@options) - @options.reporter = @reporter - - ExampleMethods.before_all_parts.should == [] - ExampleMethods.before_each_parts.should == [] - ExampleMethods.after_each_parts.should == [] - ExampleMethods.after_all_parts.should == [] - def ExampleMethods.count - @count ||= 0 - @count = @count + 1 - @count - end - end - - after do - ExampleMethods.instance_variable_set("@before_all_parts", []) - ExampleMethods.instance_variable_set("@before_each_parts", []) - ExampleMethods.instance_variable_set("@after_each_parts", []) - ExampleMethods.instance_variable_set("@after_all_parts", []) - end - - it "should pass before and after callbacks to all ExampleGroup subclasses" do - ExampleMethods.before(:all) do - ExampleMethods.count.should == 1 - end - - ExampleMethods.before(:each) do - ExampleMethods.count.should == 2 - end - - ExampleMethods.after(:each) do - ExampleMethods.count.should == 3 - end - - ExampleMethods.after(:all) do - ExampleMethods.count.should == 4 - end - - @example_group = Class.new(ExampleGroup) do - it "should use ExampleMethods callbacks" do - end - end - @example_group.run - ExampleMethods.count.should == 5 - end - - describe "run_with_description_capturing" do - before(:each) do - @example_group = Class.new(ExampleGroup) do end - @example = @example_group.new("foo", &(lambda { 2.should == 2 })) - @example.run_with_description_capturing - end - - it "should provide the generated description" do - @example.instance_eval { @_matcher_description }.should == "should == 2" - end - - it "should clear the global generated_description" do - Spec::Matchers.generated_description.should == nil - end - end - end - - describe "#implementation_backtrace" do - it "returns the backtrace of where the implementation was defined" do - example_group = Class.new(ExampleGroup) do - it "should use ExampleMethods callbacks" do - end - end - example = example_group.examples.first - example.implementation_backtrace.join("\n").should include("#{__FILE__}:#{__LINE__-4}") - end - end - end - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/example/example_runner_spec.rb b/vendor/gems/rspec/spec/spec/example/example_runner_spec.rb deleted file mode 100644 index 1b5abdf0f9..0000000000 --- a/vendor/gems/rspec/spec/spec/example/example_runner_spec.rb +++ /dev/null @@ -1,194 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -module Spec - module Example - # describe "Spec::Example::ExampleRunner", "#run", :shared => true do - # before(:each) do - # @options = ::Spec::Runner::Options.new(StringIO.new, StringIO.new) - # @reporter = ::Spec::Runner::Reporter.new(@options) - # @options.reporter = @reporter - # @example_group_class = Class.new(ExampleGroup) do - # plugin_mock_framework - # describe("Some Examples") - # end - # end - # - # def create_runner(example_definition) - # example = @example_group_class.new(example_definition) - # runner = ExampleGroup.new(@options, example) - # runner.stub!(:verify_mocks) - # runner.stub!(:teardown_mocks) - # runner - # end - # end - # - # describe ExampleRunner, "#run with blank passing example" do - # it_should_behave_like "Spec::Example::ExampleRunner#run" - # - # before do - # @e = @example_group_class.it("example") {} - # @runner = create_runner(@e) - # end - # - # it "should send reporter example_started" do - # @reporter.should_receive(:example_started).with(equal(@e)) - # @runner.run - # end - # - # it "should report its name for dry run" do - # @options.dry_run = true - # @reporter.should_receive(:example_finished).with(equal(@e), nil) - # @runner.run - # end - # - # it "should report success" do - # @reporter.should_receive(:example_finished).with(equal(@e), nil) - # @runner.run - # end - # end - # - # describe ExampleRunner, "#run with a failing example" do - # predicate_matchers[:is_a] = [:is_a?] - # it_should_behave_like "Spec::Example::ExampleRunner#run" - # - # before do - # @e = @example_group_class.it("example") do - # (2+2).should == 5 - # end - # @runner = create_runner(@e) - # end - # - # it "should report failure due to failure" do - # @reporter.should_receive(:example_finished).with( - # equal(@e), - # is_a(Spec::Expectations::ExpectationNotMetError) - # ) - # @runner.run - # end - # end - # - # describe ExampleRunner, "#run with a erroring example" do - # it_should_behave_like "Spec::Example::ExampleRunner#run" - # - # before do - # @error = error = NonStandardError.new("in body") - # @example_definition = @example_group_class.it("example") do - # raise(error) - # end - # @runner = create_runner(@example_definition) - # end - # - # it "should report failure due to error" do - # @reporter.should_receive(:example_finished).with( - # equal(@example_definition), - # @error - # ) - # @runner.run - # end - # - # it "should run after_each block" do - # @example_group_class.after(:each) do - # raise("in after_each") - # end - # @reporter.should_receive(:example_finished) do |example_definition, error| - # example_definition.should equal(@example_definition) - # error.message.should eql("in body") - # end - # @runner.run - # end - # end - # - # describe ExampleRunner, "#run where after_each fails" do - # it_should_behave_like "Spec::Example::ExampleRunner#run" - # - # before do - # @example_ran = example_ran = false - # @example_definition = @example_group_class.it("should not run") do - # example_ran = true - # end - # @runner = create_runner(@example_definition) - # @example_group_class.after(:each) { raise(NonStandardError.new("in after_each")) } - # end - # - # it "should report failure location when in after_each" do - # @reporter.should_receive(:example_finished) do |example_definition, error| - # example_definition.should equal(@example_definition) - # error.message.should eql("in after_each") - # end - # @runner.run - # end - # end - # - # describe ExampleRunner, "#run with use cases" do - # predicate_matchers[:is_a] = [:is_a?] - # it_should_behave_like "Spec::Example::ExampleRunner#run" - # - # it "should report NO NAME when told to use generated description with --dry-run" do - # @options.dry_run = true - # example_definition = @example_group_class.it() do - # 5.should == 5 - # end - # runner = create_runner(example_definition) - # - # @reporter.should_receive(:example_finished) do |example_definition, error| - # example_definition.description.should == "NO NAME (Because of --dry-run)" - # end - # runner.run - # end - # - # it "should report given name if present with --dry-run" do - # @options.dry_run = true - # example_definition = @example_group_class.it("example name") do - # 5.should == 5 - # end - # runner = create_runner(example_definition) - # - # @reporter.should_receive(:example_finished) do |example_definition, error| - # example_definition.description.should == "example name" - # end - # runner.run - # end - # - # it "should report NO NAME when told to use generated description with no expectations" do - # example_definition = @example_group_class.it() {} - # runner = create_runner(example_definition) - # @reporter.should_receive(:example_finished) do |example, error| - # example.description.should == "NO NAME (Because there were no expectations)" - # end - # runner.run - # end - # - # it "should report NO NAME when told to use generated description and matcher fails" do - # example_definition = @example_group_class.it() do - # 5.should "" # Has no matches? method.. - # end - # runner = create_runner(example_definition) - # - # @reporter.should_receive(:example_finished) do |example, error| - # example_definition.description.should == "NO NAME (Because of Error raised in matcher)" - # end - # runner.run - # end - # - # it "should report generated description when told to and it is available" do - # example_definition = @example_group_class.it() { - # 5.should == 5 - # } - # runner = create_runner(example_definition) - # - # @reporter.should_receive(:example_finished) do |example_definition, error| - # example_definition.description.should == "should == 5" - # end - # runner.run - # end - # - # it "should unregister description_generated callback (lest a memory leak should build up)" do - # example_definition = @example_group_class.it("something") - # runner = create_runner(example_definition) - # - # Spec::Matchers.should_receive(:example_finished) - # runner.run - # end - # end - end -end diff --git a/vendor/gems/rspec/spec/spec/example/example_spec.rb b/vendor/gems/rspec/spec/spec/example/example_spec.rb deleted file mode 100644 index c8125b4471..0000000000 --- a/vendor/gems/rspec/spec/spec/example/example_spec.rb +++ /dev/null @@ -1,53 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper' - -module Spec - module Example - # describe Example do - # before(:each) do - # @example = Example.new "example" do - # foo - # end - # end - # - # it "should tell you its docstring" do - # @example.description.should == "example" - # end - # - # it "should execute its block in the context provided" do - # context = Class.new do - # def foo - # "foo" - # end - # end.new - # @example.run_in(context).should == "foo" - # end - # end - # - # describe Example, "#description" do - # it "should default to NO NAME when not passed anything when there are no matchers" do - # example = Example.new {} - # example.run_in(Object.new) - # example.description.should == "NO NAME" - # end - # - # it "should default to NO NAME description (Because of --dry-run) when passed nil and there are no matchers" do - # example = Example.new(nil) {} - # example.run_in(Object.new) - # example.description.should == "NO NAME" - # end - # - # it "should allow description to be overridden" do - # example = Example.new("Test description") - # example.description.should == "Test description" - # end - # - # it "should use description generated from matcher when there is no passed in description" do - # example = Example.new(nil) do - # 1.should == 1 - # end - # example.run_in(Object.new) - # example.description.should == "should == 1" - # end - # end - end -end diff --git a/vendor/gems/rspec/spec/spec/example/nested_example_group_spec.rb b/vendor/gems/rspec/spec/spec/example/nested_example_group_spec.rb deleted file mode 100644 index 35e8a98908..0000000000 --- a/vendor/gems/rspec/spec/spec/example/nested_example_group_spec.rb +++ /dev/null @@ -1,59 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper' - -module Spec - module Example - describe 'Nested Example Groups' do - parent = self - - def count - @count ||= 0 - @count = @count + 1 - @count - end - - before(:all) do - count.should == 1 - end - - before(:all) do - count.should == 2 - end - - before(:each) do - count.should == 3 - end - - before(:each) do - count.should == 4 - end - - it "should run before(:all), before(:each), example, after(:each), after(:all) in order" do - count.should == 5 - end - - after(:each) do - count.should == 7 - end - - after(:each) do - count.should == 6 - end - - after(:all) do - count.should == 9 - end - - after(:all) do - count.should == 8 - end - - describe 'nested example group' do - self.superclass.should == parent - - it "should run all before and after callbacks" do - count.should == 5 - end - end - end - end -end diff --git a/vendor/gems/rspec/spec/spec/example/pending_module_spec.rb b/vendor/gems/rspec/spec/spec/example/pending_module_spec.rb deleted file mode 100644 index c3ab0126b5..0000000000 --- a/vendor/gems/rspec/spec/spec/example/pending_module_spec.rb +++ /dev/null @@ -1,31 +0,0 @@ -module Spec - module Example - describe Pending do - - it 'should raise an ExamplePendingError if no block is supplied' do - lambda { - include Pending - pending "TODO" - }.should raise_error(ExamplePendingError, /TODO/) - end - - it 'should raise an ExamplePendingError if a supplied block fails as expected' do - lambda { - include Pending - pending "TODO" do - raise "oops" - end - }.should raise_error(ExamplePendingError, /TODO/) - end - - it 'should raise a PendingExampleFixedError if a supplied block starts working' do - lambda { - include Pending - pending "TODO" do - # success! - end - }.should raise_error(PendingExampleFixedError, /TODO/) - end - end - end -end diff --git a/vendor/gems/rspec/spec/spec/example/predicate_matcher_spec.rb b/vendor/gems/rspec/spec/spec/example/predicate_matcher_spec.rb deleted file mode 100755 index 7c4638b4bf..0000000000 --- a/vendor/gems/rspec/spec/spec/example/predicate_matcher_spec.rb +++ /dev/null @@ -1,21 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper' - -module Spec - module Example - class Fish - def can_swim?(distance_in_yards) - distance_in_yards < 1000 - end - end - - describe "predicate_matcher[method_on_object] = matcher_method" do - predicate_matchers[:swim] = :can_swim? - it "should match matcher_method if method_on_object returns true" do - swim(100).matches?(Fish.new).should be_true - end - it "should not match matcher_method if method_on_object returns false" do - swim(10000).matches?(Fish.new).should be_false - end - end - end -end diff --git a/vendor/gems/rspec/spec/spec/example/shared_example_group_spec.rb b/vendor/gems/rspec/spec/spec/example/shared_example_group_spec.rb deleted file mode 100644 index 803536ab57..0000000000 --- a/vendor/gems/rspec/spec/spec/example/shared_example_group_spec.rb +++ /dev/null @@ -1,265 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper' - -module Spec - module Example - describe ExampleGroup, "with :shared => true" do - it_should_behave_like "sandboxed rspec_options" - attr_reader :formatter, :example_group - before(:each) do - @formatter = Spec::Mocks::Mock.new("formatter", :null_object => true) - options.formatters << formatter - @example_group = Class.new(ExampleGroup).describe("example_group") - class << example_group - public :include - end - end - - after(:each) do - @formatter.rspec_verify - @example_group = nil - $shared_example_groups.clear unless $shared_example_groups.nil? - end - - def make_shared_example_group(name, opts=nil, &block) - example_group = SharedExampleGroup.new(name, :shared => true, &block) - SharedExampleGroup.add_shared_example_group(example_group) - example_group - end - - def non_shared_example_group() - @non_shared_example_group ||= Class.new(ExampleGroup).describe("example_group") - end - - it "should accept an optional options hash" do - lambda { Class.new(ExampleGroup).describe("context") }.should_not raise_error(Exception) - lambda { Class.new(ExampleGroup).describe("context", :shared => true) }.should_not raise_error(Exception) - end - - it "should return all shared example_groups" do - b1 = make_shared_example_group("b1", :shared => true) {} - b2 = make_shared_example_group("b2", :shared => true) {} - - b1.should_not be(nil) - b2.should_not be(nil) - - SharedExampleGroup.find_shared_example_group("b1").should equal(b1) - SharedExampleGroup.find_shared_example_group("b2").should equal(b2) - end - - it "should register as shared example_group" do - example_group = make_shared_example_group("example_group") {} - SharedExampleGroup.shared_example_groups.should include(example_group) - end - - it "should not be shared when not configured as shared" do - example_group = non_shared_example_group - SharedExampleGroup.shared_example_groups.should_not include(example_group) - end - - it "should complain when adding a second shared example_group with the same description" do - describe "shared example_group", :shared => true do - end - lambda do - describe "shared example_group", :shared => true do - end - end.should raise_error(ArgumentError) - end - - it "should NOT complain when adding the same shared example_group instance again" do - shared_example_group = Class.new(ExampleGroup).describe("shared example_group", :shared => true) - SharedExampleGroup.add_shared_example_group(shared_example_group) - SharedExampleGroup.add_shared_example_group(shared_example_group) - end - - it "should NOT complain when adding the same shared example_group again (i.e. file gets reloaded)" do - lambda do - 2.times do - describe "shared example_group which gets loaded twice", :shared => true do - end - end - end.should_not raise_error(ArgumentError) - end - - it "should NOT complain when adding the same shared example_group in same file with different absolute path" do - shared_example_group_1 = Class.new(ExampleGroup).describe( - "shared example_group", - :shared => true, - :spec_path => "/my/spec/a/../shared.rb" - ) - shared_example_group_2 = Class.new(ExampleGroup).describe( - "shared example_group", - :shared => true, - :spec_path => "/my/spec/b/../shared.rb" - ) - - SharedExampleGroup.add_shared_example_group(shared_example_group_1) - SharedExampleGroup.add_shared_example_group(shared_example_group_2) - end - - it "should complain when adding a different shared example_group with the same name in a different file with the same basename" do - shared_example_group_1 = Class.new(ExampleGroup).describe( - "shared example_group", - :shared => true, - :spec_path => "/my/spec/a/shared.rb" - ) - shared_example_group_2 = Class.new(ExampleGroup).describe( - "shared example_group", - :shared => true, - :spec_path => "/my/spec/b/shared.rb" - ) - - SharedExampleGroup.add_shared_example_group(shared_example_group_1) - lambda do - SharedExampleGroup.add_shared_example_group(shared_example_group_2) - end.should raise_error(ArgumentError, /already exists/) - end - - it "should add examples to current example_group using it_should_behave_like" do - shared_example_group = make_shared_example_group("shared example_group") do - it("shared example") {} - it("shared example 2") {} - end - - example_group.it("example") {} - example_group.number_of_examples.should == 1 - example_group.it_should_behave_like("shared example_group") - example_group.number_of_examples.should == 3 - end - - it "should add examples to current example_group using include" do - shared_example_group = describe "all things", :shared => true do - it "should do stuff" do end - end - - example_group = describe "one thing" do - include shared_example_group - end - - example_group.number_of_examples.should == 1 - end - - it "should add examples to current example_group using it_should_behave_like with a module" do - AllThings = describe "all things", :shared => true do - it "should do stuff" do end - end - - example_group = describe "one thing" do - it_should_behave_like AllThings - end - - example_group.number_of_examples.should == 1 - end - - it "should run shared examples" do - shared_example_ran = false - shared_example_group = make_shared_example_group("shared example_group") do - it("shared example") { shared_example_ran = true } - end - - example_ran = false - - example_group.it_should_behave_like("shared example_group") - example_group.it("example") {example_ran = true} - example_group.run - example_ran.should be_true - shared_example_ran.should be_true - end - - it "should run setup and teardown from shared example_group" do - shared_setup_ran = false - shared_teardown_ran = false - shared_example_group = make_shared_example_group("shared example_group") do - before { shared_setup_ran = true } - after { shared_teardown_ran = true } - it("shared example") { shared_example_ran = true } - end - - example_ran = false - - example_group.it_should_behave_like("shared example_group") - example_group.it("example") {example_ran = true} - example_group.run - example_ran.should be_true - shared_setup_ran.should be_true - shared_teardown_ran.should be_true - end - - it "should run before(:all) and after(:all) only once from shared example_group" do - shared_before_all_run_count = 0 - shared_after_all_run_count = 0 - shared_example_group = make_shared_example_group("shared example_group") do - before(:all) { shared_before_all_run_count += 1} - after(:all) { shared_after_all_run_count += 1} - it("shared example") { shared_example_ran = true } - end - - example_ran = false - - example_group.it_should_behave_like("shared example_group") - example_group.it("example") {example_ran = true} - example_group.run - example_ran.should be_true - shared_before_all_run_count.should == 1 - shared_after_all_run_count.should == 1 - end - - it "should include modules, included into shared example_group, into current example_group" do - @formatter.should_receive(:add_example_group).with(any_args) - - shared_example_group = make_shared_example_group("shared example_group") do - it("shared example") { shared_example_ran = true } - end - - mod1_method_called = false - mod1 = Module.new do - define_method :mod1_method do - mod1_method_called = true - end - end - - mod2_method_called = false - mod2 = Module.new do - define_method :mod2_method do - mod2_method_called = true - end - end - - shared_example_group.include mod2 - - example_group.it_should_behave_like("shared example_group") - example_group.include mod1 - - example_group.it("test") do - mod1_method - mod2_method - end - example_group.run - mod1_method_called.should be_true - mod2_method_called.should be_true - end - - it "should make methods defined in the shared example_group available in consuming example_group" do - shared_example_group = make_shared_example_group("shared example_group xyz") do - def a_shared_helper_method - "this got defined in a shared example_group" - end - end - example_group.it_should_behave_like("shared example_group xyz") - success = false - example_group.it("should access a_shared_helper_method") do - a_shared_helper_method - success = true - end - example_group.run - success.should be_true - end - - it "should raise when named shared example_group can not be found" do - lambda { - example_group.it_should_behave_like("non-existent shared example group") - violated - }.should raise_error("Shared Example Group 'non-existent shared example group' can not be found") - end - end - end -end diff --git a/vendor/gems/rspec/spec/spec/example/subclassing_example_group_spec.rb b/vendor/gems/rspec/spec/spec/example/subclassing_example_group_spec.rb deleted file mode 100644 index 888f2ceb3d..0000000000 --- a/vendor/gems/rspec/spec/spec/example/subclassing_example_group_spec.rb +++ /dev/null @@ -1,25 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper' - -module Spec - module Example - class GrandParentExampleGroup < Spec::Example::ExampleGroup - describe "Grandparent ExampleGroup" - end - - class ParentExampleGroup < GrandParentExampleGroup - describe "Parent ExampleGroup" - it "should bar" do - end - end - - class ChildExampleGroup < ParentExampleGroup - describe "Child ExampleGroup" - it "should bam" do - end - end - - describe ChildExampleGroup do - - end - end -end diff --git a/vendor/gems/rspec/spec/spec/expectations/differs/default_spec.rb b/vendor/gems/rspec/spec/spec/expectations/differs/default_spec.rb deleted file mode 100644 index ea720846b8..0000000000 --- a/vendor/gems/rspec/spec/spec/expectations/differs/default_spec.rb +++ /dev/null @@ -1,109 +0,0 @@ -require File.dirname(__FILE__) + '/../../../spec_helper.rb' - -module Spec - module Fixtures - class Animal - def initialize(name,species) - @name,@species = name,species - end - - def inspect - <<-EOA - - EOA - end - end - end -end - -describe "Diff" do - before(:each) do - @options = ::Spec::Runner::Options.new(StringIO.new, StringIO.new) - @differ = Spec::Expectations::Differs::Default.new(@options) - end - - it "should output unified diff of two strings" do - expected="foo\nbar\nzap\nthis\nis\nsoo\nvery\nvery\nequal\ninsert\na\nline\n" - actual="foo\nzap\nbar\nthis\nis\nsoo\nvery\nvery\nequal\ninsert\na\nanother\nline\n" - expected_diff="\n\n@@ -1,6 +1,6 @@\n foo\n-bar\n zap\n+bar\n this\n is\n soo\n@@ -9,5 +9,6 @@\n equal\n insert\n a\n+another\n line\n" - diff = @differ.diff_as_string(expected, actual) - diff.should eql(expected_diff) - end - - it "should output unified diff message of two arrays" do - expected = [ :foo, 'bar', :baz, 'quux', :metasyntactic, 'variable', :delta, 'charlie', :width, 'quite wide' ] - actual = [ :foo, 'bar', :baz, 'quux', :metasyntactic, 'variable', :delta, 'tango' , :width, 'very wide' ] - - expected_diff = <<'EOD' - - -@@ -5,7 +5,7 @@ - :metasyntactic, - "variable", - :delta, -- "charlie", -+ "tango", - :width, -- "quite wide"] -+ "very wide"] -EOD - - - diff = @differ.diff_as_object(expected,actual) - diff.should == expected_diff - end - - it "should output unified diff message of two objects" do - expected = Spec::Fixtures::Animal.new "bob", "giraffe" - actual = Spec::Fixtures::Animal.new "bob", "tortoise" - - expected_diff = <<'EOD' - -@@ -1,5 +1,5 @@ - -EOD - - diff = @differ.diff_as_object(expected,actual) - diff.should == expected_diff - end - -end - - -describe "Diff in context format" do - before(:each) do - @options = Spec::Runner::Options.new(StringIO.new, StringIO.new) - @options.diff_format = :context - @differ = Spec::Expectations::Differs::Default.new(@options) - end - - it "should output unified diff message of two objects" do - expected = Spec::Fixtures::Animal.new "bob", "giraffe" - actual = Spec::Fixtures::Animal.new "bob", "tortoise" - - expected_diff = <<'EOD' - -*************** -*** 1,5 **** - ---- 1,5 ---- - -EOD - - diff = @differ.diff_as_object(expected,actual) - diff.should == expected_diff - end -end diff --git a/vendor/gems/rspec/spec/spec/expectations/extensions/object_spec.rb b/vendor/gems/rspec/spec/spec/expectations/extensions/object_spec.rb deleted file mode 100644 index 0d9335bdb4..0000000000 --- a/vendor/gems/rspec/spec/spec/expectations/extensions/object_spec.rb +++ /dev/null @@ -1,107 +0,0 @@ -require File.dirname(__FILE__) + '/../../../spec_helper.rb' - -describe Object, "#should" do - before(:each) do - @target = "target" - @matcher = mock("matcher") - @matcher.stub!(:matches?).and_return(true) - @matcher.stub!(:failure_message) - end - - it "should accept and interact with a matcher" do - @matcher.should_receive(:matches?).with(@target).and_return(true) - @target.should @matcher - end - - it "should ask for a failure_message when matches? returns false" do - @matcher.should_receive(:matches?).with(@target).and_return(false) - @matcher.should_receive(:failure_message).and_return("the failure message") - lambda { - @target.should @matcher - }.should fail_with("the failure message") - end - - it "should raise error if it receives false directly" do - lambda { - @target.should false - }.should raise_error(Spec::Expectations::InvalidMatcherError) - end - - it "should raise error if it receives false (evaluated)" do - lambda { - @target.should eql?("foo") - }.should raise_error(Spec::Expectations::InvalidMatcherError) - end - - it "should raise error if it receives true" do - lambda { - @target.should true - }.should raise_error(Spec::Expectations::InvalidMatcherError) - end - - it "should raise error if it receives nil" do - lambda { - @target.should nil - }.should raise_error(Spec::Expectations::InvalidMatcherError) - end - - it "should raise error if it receives no argument and it is not used as a left side of an operator" do - pending "Is it even possible to catch this?" - lambda { - @target.should - }.should raise_error(Spec::Expectations::InvalidMatcherError) - end -end - -describe Object, "#should_not" do - before(:each) do - @target = "target" - @matcher = mock("matcher") - end - - it "should accept and interact with a matcher" do - @matcher.should_receive(:matches?).with(@target).and_return(false) - @matcher.stub!(:negative_failure_message) - - @target.should_not @matcher - end - - it "should ask for a negative_failure_message when matches? returns true" do - @matcher.should_receive(:matches?).with(@target).and_return(true) - @matcher.should_receive(:negative_failure_message).and_return("the negative failure message") - lambda { - @target.should_not @matcher - }.should fail_with("the negative failure message") - end - - it "should raise error if it receives false directly" do - lambda { - @target.should_not false - }.should raise_error(Spec::Expectations::InvalidMatcherError) - end - - it "should raise error if it receives false (evaluated)" do - lambda { - @target.should_not eql?("foo") - }.should raise_error(Spec::Expectations::InvalidMatcherError) - end - - it "should raise error if it receives true" do - lambda { - @target.should_not true - }.should raise_error(Spec::Expectations::InvalidMatcherError) - end - - it "should raise error if it receives nil" do - lambda { - @target.should_not nil - }.should raise_error(Spec::Expectations::InvalidMatcherError) - end - - it "should raise error if it receives no argument and it is not used as a left side of an operator" do - pending "Is it even possible to catch this?" - lambda { - @target.should_not - }.should raise_error(Spec::Expectations::InvalidMatcherError) - end -end diff --git a/vendor/gems/rspec/spec/spec/expectations/fail_with_spec.rb b/vendor/gems/rspec/spec/spec/expectations/fail_with_spec.rb deleted file mode 100644 index 4c369ce3ab..0000000000 --- a/vendor/gems/rspec/spec/spec/expectations/fail_with_spec.rb +++ /dev/null @@ -1,71 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -describe Spec::Expectations, "#fail_with with no diff" do - before(:each) do - @old_differ = Spec::Expectations.differ - Spec::Expectations.differ = nil - end - - it "should handle just a message" do - lambda { - Spec::Expectations.fail_with "the message" - }.should fail_with("the message") - end - - it "should handle an Array" do - lambda { - Spec::Expectations.fail_with ["the message","expected","actual"] - }.should fail_with("the message") - end - - after(:each) do - Spec::Expectations.differ = @old_differ - end -end - -describe Spec::Expectations, "#fail_with with diff" do - before(:each) do - @old_differ = Spec::Expectations.differ - @differ = mock("differ") - Spec::Expectations.differ = @differ - end - - it "should not call differ if no expected/actual" do - lambda { - Spec::Expectations.fail_with "the message" - }.should fail_with("the message") - end - - it "should call differ if expected/actual are presented separately" do - @differ.should_receive(:diff_as_string).and_return("diff") - lambda { - Spec::Expectations.fail_with "the message", "expected", "actual" - }.should fail_with("the message\nDiff:diff") - end - - it "should call differ if expected/actual are not strings" do - @differ.should_receive(:diff_as_object).and_return("diff") - lambda { - Spec::Expectations.fail_with "the message", :expected, :actual - }.should fail_with("the message\nDiff:diff") - end - - it "should not call differ if expected or actual are procs" do - @differ.should_not_receive(:diff_as_string) - @differ.should_not_receive(:diff_as_object) - lambda { - Spec::Expectations.fail_with "the message", lambda {}, lambda {} - }.should fail_with("the message") - end - - it "should call differ if expected/actual are presented in an Array with message" do - @differ.should_receive(:diff_as_string).with("actual","expected").and_return("diff") - lambda { - Spec::Expectations.fail_with(["the message", "expected", "actual"]) - }.should fail_with(/the message\nDiff:diff/) - end - - after(:each) do - Spec::Expectations.differ = @old_differ - end -end diff --git a/vendor/gems/rspec/spec/spec/extensions/main_spec.rb b/vendor/gems/rspec/spec/spec/extensions/main_spec.rb deleted file mode 100644 index aabb616e97..0000000000 --- a/vendor/gems/rspec/spec/spec/extensions/main_spec.rb +++ /dev/null @@ -1,76 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -module Spec - module Extensions - describe Main do - it_should_behave_like "sandboxed rspec_options" - before(:each) do - @main = Class.new do; include Main; end - end - - after(:each) do - $rspec_story_steps = @original_rspec_story_steps - end - - it "should create an Options object" do - @main.send(:rspec_options).should be_instance_of(Spec::Runner::Options) - @main.send(:rspec_options).should === $rspec_options - end - - specify {@main.should respond_to(:describe)} - specify {@main.should respond_to(:context)} - - it "should raise when no block given to describe" do - lambda { @main.describe "foo" }.should raise_error(ArgumentError) - end - - it "should raise when no description given to describe" do - lambda { @main.describe do; end }.should raise_error(ArgumentError) - end - - it "should registered ExampleGroups by default" do - example_group = @main.describe("The ExampleGroup") do end - rspec_options.example_groups.should include(example_group) - end - - it "should not run unregistered ExampleGroups" do - example_group = @main.describe("The ExampleGroup") do - unregister - end - - rspec_options.example_groups.should_not include(example_group) - end - - it "should create a shared ExampleGroup with share_examples_for" do - group = @main.share_examples_for "all things" do end - group.should be_an_instance_of(Spec::Example::SharedExampleGroup) - end - - describe "#share_as" do - before(:each) do - $share_as_examples_example_module_number ||= 1 - $share_as_examples_example_module_number += 1 - t = Time.new.to_i - @group_name = "Group#{$share_as_examples_example_module_number}" - end - - it "should create a shared ExampleGroup" do - group = @main.share_as @group_name do end - group.should be_an_instance_of(Spec::Example::SharedExampleGroup) - end - - it "should create a constant that points to a Module" do - group = @main.share_as @group_name do end - Object.const_get(@group_name).should equal(group) - end - - it "should bark if you pass it something not-constantizable" do - lambda do - @group = @main.share_as "Non Constant" do end - end.should raise_error(NameError, /The first argument to share_as must be a legal name for a constant/) - end - - end - end - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/interop/test/unit/resources/spec_that_fails.rb b/vendor/gems/rspec/spec/spec/interop/test/unit/resources/spec_that_fails.rb deleted file mode 100644 index d6f5564bf1..0000000000 --- a/vendor/gems/rspec/spec/spec/interop/test/unit/resources/spec_that_fails.rb +++ /dev/null @@ -1,10 +0,0 @@ -rspec_lib = File.dirname(__FILE__) + "/../../../../../../lib" -$:.unshift rspec_lib unless $:.include?(rspec_lib) -require 'test/unit' -require 'spec' - -describe "example group with failures" do - it "should fail" do - false.should be_true - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/interop/test/unit/resources/spec_that_passes.rb b/vendor/gems/rspec/spec/spec/interop/test/unit/resources/spec_that_passes.rb deleted file mode 100644 index ccd2488bc8..0000000000 --- a/vendor/gems/rspec/spec/spec/interop/test/unit/resources/spec_that_passes.rb +++ /dev/null @@ -1,10 +0,0 @@ -rspec_lib = File.dirname(__FILE__) + "/../../../../../../lib" -$:.unshift rspec_lib unless $:.include?(rspec_lib) -require 'test/unit' -require 'spec' - -describe "example group with passing examples" do - it "should pass" do - true.should be_true - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/interop/test/unit/resources/spec_with_errors.rb b/vendor/gems/rspec/spec/spec/interop/test/unit/resources/spec_with_errors.rb deleted file mode 100644 index 71427dbaad..0000000000 --- a/vendor/gems/rspec/spec/spec/interop/test/unit/resources/spec_with_errors.rb +++ /dev/null @@ -1,10 +0,0 @@ -rspec_lib = File.dirname(__FILE__) + "/../../../../../../lib" -$:.unshift rspec_lib unless $:.include?(rspec_lib) -require 'test/unit' -require 'spec' - -describe "example group with errors" do - it "should raise errors" do - raise "error raised in example group with errors" - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/interop/test/unit/resources/test_case_that_fails.rb b/vendor/gems/rspec/spec/spec/interop/test/unit/resources/test_case_that_fails.rb deleted file mode 100644 index 3fb6515a89..0000000000 --- a/vendor/gems/rspec/spec/spec/interop/test/unit/resources/test_case_that_fails.rb +++ /dev/null @@ -1,10 +0,0 @@ -rspec_lib = File.dirname(__FILE__) + "/../../../../../../lib" -$:.unshift rspec_lib unless $:.include?(rspec_lib) -require 'test/unit' -require 'spec' - -class TestCaseThatFails < Test::Unit::TestCase - def test_that_fails - false.should be_true - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/interop/test/unit/resources/test_case_that_passes.rb b/vendor/gems/rspec/spec/spec/interop/test/unit/resources/test_case_that_passes.rb deleted file mode 100644 index 69239c0b5a..0000000000 --- a/vendor/gems/rspec/spec/spec/interop/test/unit/resources/test_case_that_passes.rb +++ /dev/null @@ -1,10 +0,0 @@ -rspec_lib = File.dirname(__FILE__) + "/../../../../../../lib" -$:.unshift rspec_lib unless $:.include?(rspec_lib) -require 'test/unit' -require 'spec' - -class TestCaseThatPasses < Test::Unit::TestCase - def test_that_passes - true.should be_true - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/interop/test/unit/resources/test_case_with_errors.rb b/vendor/gems/rspec/spec/spec/interop/test/unit/resources/test_case_with_errors.rb deleted file mode 100644 index 35dcb6b2e5..0000000000 --- a/vendor/gems/rspec/spec/spec/interop/test/unit/resources/test_case_with_errors.rb +++ /dev/null @@ -1,10 +0,0 @@ -rspec_lib = File.dirname(__FILE__) + "/../../../../../../lib" -$:.unshift rspec_lib unless $:.include?(rspec_lib) -require 'test/unit' -require 'spec' - -class TestCaseWithErrors < Test::Unit::TestCase - def test_with_error - raise "error raised in TestCaseWithErrors" - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/interop/test/unit/resources/testsuite_adapter_spec_with_test_unit.rb b/vendor/gems/rspec/spec/spec/interop/test/unit/resources/testsuite_adapter_spec_with_test_unit.rb deleted file mode 100644 index 0c2167a991..0000000000 --- a/vendor/gems/rspec/spec/spec/interop/test/unit/resources/testsuite_adapter_spec_with_test_unit.rb +++ /dev/null @@ -1,38 +0,0 @@ -rspec_lib = File.dirname(__FILE__) + "/../../../../../../lib" -$:.unshift rspec_lib unless $:.include?(rspec_lib) -require "test/unit" -require "spec" - -module Test - module Unit - describe TestSuiteAdapter do - def create_adapter(group) - TestSuiteAdapter.new(group) - end - - describe "#size" do - it "should return the number of examples in the example group" do - group = Class.new(Spec::ExampleGroup) do - describe("some examples") - it("bar") {} - it("baz") {} - end - adapter = create_adapter(group) - adapter.size.should == 2 - end - end - - describe "#delete" do - it "should do nothing" do - group = Class.new(Spec::ExampleGroup) do - describe("Some Examples") - it("does something") {} - end - adapter = create_adapter(group) - adapter.delete(adapter.examples.first) - adapter.should be_empty - end - end - end - end -end diff --git a/vendor/gems/rspec/spec/spec/interop/test/unit/spec_spec.rb b/vendor/gems/rspec/spec/spec/interop/test/unit/spec_spec.rb deleted file mode 100644 index 8a1e1300c5..0000000000 --- a/vendor/gems/rspec/spec/spec/interop/test/unit/spec_spec.rb +++ /dev/null @@ -1,45 +0,0 @@ -require File.dirname(__FILE__) + '/test_unit_spec_helper' - -describe "ExampleGroup with test/unit/interop" do - include TestUnitSpecHelper - - before(:each) do - @dir = File.dirname(__FILE__) + "/resources" - end - - describe "with passing examples" do - it "should output 0 failures" do - output = ruby("#{@dir}/spec_that_passes.rb") - output.should include("1 example, 0 failures") - end - - it "should return an exit code of 0" do - ruby("#{@dir}/spec_that_passes.rb") - $?.should == 0 - end - end - - describe "with failing examples" do - it "should output 1 failure" do - output = ruby("#{@dir}/spec_that_fails.rb") - output.should include("1 example, 1 failure") - end - - it "should return an exit code of 256" do - ruby("#{@dir}/spec_that_fails.rb") - $?.should == 256 - end - end - - describe "with example that raises an error" do - it "should output 1 failure" do - output = ruby("#{@dir}/spec_with_errors.rb") - output.should include("1 example, 1 failure") - end - - it "should return an exit code of 256" do - ruby("#{@dir}/spec_with_errors.rb") - $?.should == 256 - end - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/interop/test/unit/test_unit_spec_helper.rb b/vendor/gems/rspec/spec/spec/interop/test/unit/test_unit_spec_helper.rb deleted file mode 100644 index 04d5d27135..0000000000 --- a/vendor/gems/rspec/spec/spec/interop/test/unit/test_unit_spec_helper.rb +++ /dev/null @@ -1,14 +0,0 @@ -require File.dirname(__FILE__) + '/../../../../spec_helper' -require File.dirname(__FILE__) + '/../../../../ruby_forker' - -module TestUnitSpecHelper - include RubyForker - - def run_script(file_name) - output = ruby(file_name) - if !$?.success? || output.include?("FAILED") || output.include?("Error") - raise output - end - output - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/interop/test/unit/testcase_spec.rb b/vendor/gems/rspec/spec/spec/interop/test/unit/testcase_spec.rb deleted file mode 100644 index f40111a584..0000000000 --- a/vendor/gems/rspec/spec/spec/interop/test/unit/testcase_spec.rb +++ /dev/null @@ -1,45 +0,0 @@ -require File.dirname(__FILE__) + '/test_unit_spec_helper' - -describe "Test::Unit::TestCase" do - include TestUnitSpecHelper - - before(:each) do - @dir = File.dirname(__FILE__) + "/resources" - end - - describe "with passing test case" do - it "should output 0 failures" do - output = ruby("#{@dir}/test_case_that_passes.rb") - output.should include("1 example, 0 failures") - end - - it "should return an exit code of 0" do - ruby("#{@dir}/test_case_that_passes.rb") - $?.should == 0 - end - end - - describe "with failing test case" do - it "should output 1 failure" do - output = ruby("#{@dir}/test_case_that_fails.rb") - output.should include("1 example, 1 failure") - end - - it "should return an exit code of 256" do - ruby("#{@dir}/test_case_that_fails.rb") - $?.should == 256 - end - end - - describe "with test case that raises an error" do - it "should output 1 failure" do - output = ruby("#{@dir}/test_case_with_errors.rb") - output.should include("1 example, 1 failure") - end - - it "should return an exit code of 256" do - ruby("#{@dir}/test_case_with_errors.rb") - $?.should == 256 - end - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/interop/test/unit/testsuite_adapter_spec.rb b/vendor/gems/rspec/spec/spec/interop/test/unit/testsuite_adapter_spec.rb deleted file mode 100644 index 722126bc98..0000000000 --- a/vendor/gems/rspec/spec/spec/interop/test/unit/testsuite_adapter_spec.rb +++ /dev/null @@ -1,9 +0,0 @@ -require File.dirname(__FILE__) + '/test_unit_spec_helper' - -describe "TestSuiteAdapter" do - include TestUnitSpecHelper - it "should pass" do - dir = File.dirname(__FILE__) - run_script "#{dir}/resources/testsuite_adapter_spec_with_test_unit.rb" - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/matchers/be_close_spec.rb b/vendor/gems/rspec/spec/spec/matchers/be_close_spec.rb deleted file mode 100644 index d8452d408e..0000000000 --- a/vendor/gems/rspec/spec/spec/matchers/be_close_spec.rb +++ /dev/null @@ -1,39 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' -module Spec - module Matchers - describe BeClose do - it "should match when value == target" do - BeClose.new(5.0, 0.5).matches?(5.0).should be_true - end - it "should match when value < (target + delta)" do - BeClose.new(5.0, 0.5).matches?(5.49).should be_true - end - it "should match when value > (target - delta)" do - BeClose.new(5.0, 0.5).matches?(4.51).should be_true - end - it "should not match when value == (target - delta)" do - BeClose.new(5.0, 0.5).matches?(4.5).should be_false - end - it "should not match when value < (target - delta)" do - BeClose.new(5.0, 0.5).matches?(4.49).should be_false - end - it "should not match when value == (target + delta)" do - BeClose.new(5.0, 0.5).matches?(5.5).should be_false - end - it "should not match when value > (target + delta)" do - BeClose.new(5.0, 0.5).matches?(5.51).should be_false - end - it "should provide a useful failure message" do - #given - matcher = BeClose.new(5.0, 0.5) - #when - matcher.matches?(5.51) - #then - matcher.failure_message.should == "expected 5.0 +/- (< 0.5), got 5.51" - end - it "should describe itself" do - BeClose.new(5.0, 0.5).description.should == "be close to 5.0 (within +- 0.5)" - end - end - end -end diff --git a/vendor/gems/rspec/spec/spec/matchers/be_spec.rb b/vendor/gems/rspec/spec/spec/matchers/be_spec.rb deleted file mode 100644 index d40036c79c..0000000000 --- a/vendor/gems/rspec/spec/spec/matchers/be_spec.rb +++ /dev/null @@ -1,224 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -describe "should be_predicate" do - it "should pass when actual returns true for :predicate?" do - actual = stub("actual", :happy? => true) - actual.should be_happy - end - - it "should pass when actual returns true for :predicates? (present tense)" do - actual = stub("actual", :exists? => true) - actual.should be_exist - end - - it "should fail when actual returns false for :predicate?" do - actual = stub("actual", :happy? => false) - lambda { - actual.should be_happy - }.should fail_with("expected happy? to return true, got false") - end - - it "should fail when actual does not respond to :predicate?" do - lambda { - Object.new.should be_happy - }.should raise_error(NameError) - end -end - -describe "should_not be_predicate" do - it "should pass when actual returns false for :sym?" do - actual = stub("actual", :happy? => false) - actual.should_not be_happy - end - - it "should fail when actual returns true for :sym?" do - actual = stub("actual", :happy? => true) - lambda { - actual.should_not be_happy - }.should fail_with("expected happy? to return false, got true") - end - - it "should fail when actual does not respond to :sym?" do - lambda { - Object.new.should_not be_happy - }.should raise_error(NameError) - end -end - -describe "should be_predicate(*args)" do - it "should pass when actual returns true for :predicate?(*args)" do - actual = mock("actual") - actual.should_receive(:older_than?).with(3).and_return(true) - actual.should be_older_than(3) - end - - it "should fail when actual returns false for :predicate?(*args)" do - actual = mock("actual") - actual.should_receive(:older_than?).with(3).and_return(false) - lambda { - actual.should be_older_than(3) - }.should fail_with("expected older_than?(3) to return true, got false") - end - - it "should fail when actual does not respond to :predicate?" do - lambda { - Object.new.should be_older_than(3) - }.should raise_error(NameError) - end -end - -describe "should_not be_predicate(*args)" do - it "should pass when actual returns false for :predicate?(*args)" do - actual = mock("actual") - actual.should_receive(:older_than?).with(3).and_return(false) - actual.should_not be_older_than(3) - end - - it "should fail when actual returns true for :predicate?(*args)" do - actual = mock("actual") - actual.should_receive(:older_than?).with(3).and_return(true) - lambda { - actual.should_not be_older_than(3) - }.should fail_with("expected older_than?(3) to return false, got true") - end - - it "should fail when actual does not respond to :predicate?" do - lambda { - Object.new.should_not be_older_than(3) - }.should raise_error(NameError) - end -end - -describe "should be_true" do - it "should pass when actual equal(true)" do - true.should be_true - end - - it "should fail when actual equal(false)" do - lambda { - false.should be_true - }.should fail_with("expected true, got false") - end -end - -describe "should be_false" do - it "should pass when actual equal(false)" do - false.should be_false - end - - it "should fail when actual equal(true)" do - lambda { - true.should be_false - }.should fail_with("expected false, got true") - end -end - -describe "should be_nil" do - it "should pass when actual is nil" do - nil.should be_nil - end - - it "should fail when actual is not nil" do - lambda { - :not_nil.should be_nil - }.should fail_with("expected nil, got :not_nil") - end -end - -describe "should_not be_nil" do - it "should pass when actual is not nil" do - :not_nil.should_not be_nil - end - - it "should fail when actual is nil" do - lambda { - nil.should_not be_nil - }.should fail_with("expected not nil, got nil") - end -end - -describe "should be <" do - it "should pass when < operator returns true" do - 3.should be < 4 - end - - it "should fail when < operator returns false" do - lambda { 3.should be < 3 }.should fail_with("expected < 3, got 3") - end -end - -describe "should be <=" do - it "should pass when <= operator returns true" do - 3.should be <= 4 - 4.should be <= 4 - end - - it "should fail when <= operator returns false" do - lambda { 3.should be <= 2 }.should fail_with("expected <= 2, got 3") - end -end - -describe "should be >=" do - it "should pass when >= operator returns true" do - 4.should be >= 4 - 5.should be >= 4 - end - - it "should fail when >= operator returns false" do - lambda { 3.should be >= 4 }.should fail_with("expected >= 4, got 3") - end -end - -describe "should be >" do - it "should pass when > operator returns true" do - 5.should be > 4 - end - - it "should fail when > operator returns false" do - lambda { 3.should be > 4 }.should fail_with("expected > 4, got 3") - end -end - -describe "should be ==" do - it "should pass when == operator returns true" do - 5.should be == 5 - end - - it "should fail when == operator returns false" do - lambda { 3.should be == 4 }.should fail_with("expected == 4, got 3") - end -end - -describe "should be ===" do - it "should pass when === operator returns true" do - Hash.should be === Hash.new - end - - it "should fail when === operator returns false" do - lambda { Hash.should be === "not a hash" }.should fail_with(%[expected === "not a hash", got Hash]) - end -end - -describe "should be" do - it "should pass if actual is true or a set value" do - true.should be - 1.should be - end - - it "should fail if actual is false" do - lambda {false.should be}.should fail_with("expected if to be satisfied, got false") - end - - it "should fail if actual is nil" do - lambda {nil.should be}.should fail_with("expected if to be satisfied, got nil") - end -end - -describe "should be(value)" do - it "should pass if actual.equal?(value)" do - 5.should be(5) - end - it "should fail if !actual.equal?(value)" do - lambda { 5.should be(6) }.should fail_with("expected 6, got 5") - end -end diff --git a/vendor/gems/rspec/spec/spec/matchers/change_spec.rb b/vendor/gems/rspec/spec/spec/matchers/change_spec.rb deleted file mode 100644 index d95aa6da41..0000000000 --- a/vendor/gems/rspec/spec/spec/matchers/change_spec.rb +++ /dev/null @@ -1,319 +0,0 @@ -#Based on patch from Wilson Bilkovich - -require File.dirname(__FILE__) + '/../../spec_helper.rb' -class SomethingExpected - attr_accessor :some_value -end - -describe "should change(actual, message)" do - before(:each) do - @instance = SomethingExpected.new - @instance.some_value = 5 - end - - it "should pass when actual is modified by the block" do - lambda {@instance.some_value = 6}.should change(@instance, :some_value) - end - - it "should fail when actual is not modified by the block" do - lambda do - lambda {}.should change(@instance, :some_value) - end.should fail_with("some_value should have changed, but is still 5") - end -end - -describe "should_not change(actual, message)" do - before(:each) do - @instance = SomethingExpected.new - @instance.some_value = 5 - end - - it "should pass when actual is not modified by the block" do - lambda { }.should_not change(@instance, :some_value) - end - - it "should fail when actual is not modified by the block" do - lambda do - lambda {@instance.some_value = 6}.should_not change(@instance, :some_value) - end.should fail_with("some_value should not have changed, but did change from 5 to 6") - end -end - -describe "should change { block }" do - before(:each) do - @instance = SomethingExpected.new - @instance.some_value = 5 - end - - it "should pass when actual is modified by the block" do - lambda {@instance.some_value = 6}.should change { @instance.some_value } - end - - it "should fail when actual is not modified by the block" do - lambda do - lambda {}.should change{ @instance.some_value } - end.should fail_with("result should have changed, but is still 5") - end - - it "should warn if passed a block using do/end" do - lambda do - lambda {}.should change do - end - end.should raise_error(Spec::Matchers::MatcherError, /block passed to should or should_not/) - end -end - -describe "should_not change { block }" do - before(:each) do - @instance = SomethingExpected.new - @instance.some_value = 5 - end - - it "should pass when actual is modified by the block" do - lambda {}.should_not change{ @instance.some_value } - end - - it "should fail when actual is not modified by the block" do - lambda do - lambda {@instance.some_value = 6}.should_not change { @instance.some_value } - end.should fail_with("result should not have changed, but did change from 5 to 6") - end - - it "should warn if passed a block using do/end" do - lambda do - lambda {}.should_not change do - end - end.should raise_error(Spec::Matchers::MatcherError, /block passed to should or should_not/) - end -end - -describe "should change(actual, message).by(expected)" do - before(:each) do - @instance = SomethingExpected.new - @instance.some_value = 5 - end - - it "should pass when attribute is changed by expected amount" do - lambda { @instance.some_value += 1 }.should change(@instance, :some_value).by(1) - end - - it "should fail when the attribute is changed by unexpected amount" do - lambda do - lambda { @instance.some_value += 2 }.should change(@instance, :some_value).by(1) - end.should fail_with("some_value should have been changed by 1, but was changed by 2") - end - - it "should fail when the attribute is changed by unexpected amount in the opposite direction" do - lambda do - lambda { @instance.some_value -= 1 }.should change(@instance, :some_value).by(1) - end.should fail_with("some_value should have been changed by 1, but was changed by -1") - end -end - -describe "should change{ block }.by(expected)" do - before(:each) do - @instance = SomethingExpected.new - @instance.some_value = 5 - end - - it "should pass when attribute is changed by expected amount" do - lambda { @instance.some_value += 1 }.should change{@instance.some_value}.by(1) - end - - it "should fail when the attribute is changed by unexpected amount" do - lambda do - lambda { @instance.some_value += 2 }.should change{@instance.some_value}.by(1) - end.should fail_with("result should have been changed by 1, but was changed by 2") - end - - it "should fail when the attribute is changed by unexpected amount in the opposite direction" do - lambda do - lambda { @instance.some_value -= 1 }.should change{@instance.some_value}.by(1) - end.should fail_with("result should have been changed by 1, but was changed by -1") - end -end - -describe "should change(actual, message).by_at_least(expected)" do - before(:each) do - @instance = SomethingExpected.new - @instance.some_value = 5 - end - - it "should pass when attribute is changed by greater than the expected amount" do - lambda { @instance.some_value += 2 }.should change(@instance, :some_value).by_at_least(1) - end - - it "should pass when attribute is changed by the expected amount" do - lambda { @instance.some_value += 2 }.should change(@instance, :some_value).by_at_least(2) - end - - it "should fail when the attribute is changed by less than the expected amount" do - lambda do - lambda { @instance.some_value += 1 }.should change(@instance, :some_value).by_at_least(2) - end.should fail_with("some_value should have been changed by at least 2, but was changed by 1") - end - -end - -describe "should change{ block }.by_at_least(expected)" do - before(:each) do - @instance = SomethingExpected.new - @instance.some_value = 5 - end - - it "should pass when attribute is changed by greater than expected amount" do - lambda { @instance.some_value += 2 }.should change{@instance.some_value}.by_at_least(1) - end - - it "should pass when attribute is changed by the expected amount" do - lambda { @instance.some_value += 2 }.should change{@instance.some_value}.by_at_least(2) - end - - it "should fail when the attribute is changed by less than the unexpected amount" do - lambda do - lambda { @instance.some_value += 1 }.should change{@instance.some_value}.by_at_least(2) - end.should fail_with("result should have been changed by at least 2, but was changed by 1") - end -end - - -describe "should change(actual, message).by_at_most(expected)" do - before(:each) do - @instance = SomethingExpected.new - @instance.some_value = 5 - end - - it "should pass when attribute is changed by less than the expected amount" do - lambda { @instance.some_value += 2 }.should change(@instance, :some_value).by_at_most(3) - end - - it "should pass when attribute is changed by the expected amount" do - lambda { @instance.some_value += 2 }.should change(@instance, :some_value).by_at_most(2) - end - - it "should fail when the attribute is changed by greater than the expected amount" do - lambda do - lambda { @instance.some_value += 2 }.should change(@instance, :some_value).by_at_most(1) - end.should fail_with("some_value should have been changed by at most 1, but was changed by 2") - end - -end - -describe "should change{ block }.by_at_most(expected)" do - before(:each) do - @instance = SomethingExpected.new - @instance.some_value = 5 - end - - it "should pass when attribute is changed by less than expected amount" do - lambda { @instance.some_value += 2 }.should change{@instance.some_value}.by_at_most(3) - end - - it "should pass when attribute is changed by the expected amount" do - lambda { @instance.some_value += 2 }.should change{@instance.some_value}.by_at_most(2) - end - - it "should fail when the attribute is changed by greater than the unexpected amount" do - lambda do - lambda { @instance.some_value += 2 }.should change{@instance.some_value}.by_at_most(1) - end.should fail_with("result should have been changed by at most 1, but was changed by 2") - end -end - -describe "should change(actual, message).from(old)" do - before(:each) do - @instance = SomethingExpected.new - @instance.some_value = 'string' - end - - it "should pass when attribute is == to expected value before executing block" do - lambda { @instance.some_value = "astring" }.should change(@instance, :some_value).from("string") - end - - it "should fail when attribute is not == to expected value before executing block" do - lambda do - lambda { @instance.some_value = "knot" }.should change(@instance, :some_value).from("cat") - end.should fail_with("some_value should have initially been \"cat\", but was \"string\"") - end -end - -describe "should change{ block }.from(old)" do - before(:each) do - @instance = SomethingExpected.new - @instance.some_value = 'string' - end - - it "should pass when attribute is == to expected value before executing block" do - lambda { @instance.some_value = "astring" }.should change{@instance.some_value}.from("string") - end - - it "should fail when attribute is not == to expected value before executing block" do - lambda do - lambda { @instance.some_value = "knot" }.should change{@instance.some_value}.from("cat") - end.should fail_with("result should have initially been \"cat\", but was \"string\"") - end -end - -describe "should change(actual, message).to(new)" do - before(:each) do - @instance = SomethingExpected.new - @instance.some_value = 'string' - end - - it "should pass when attribute is == to expected value after executing block" do - lambda { @instance.some_value = "cat" }.should change(@instance, :some_value).to("cat") - end - - it "should fail when attribute is not == to expected value after executing block" do - lambda do - lambda { @instance.some_value = "cat" }.should change(@instance, :some_value).from("string").to("dog") - end.should fail_with("some_value should have been changed to \"dog\", but is now \"cat\"") - end -end - -describe "should change{ block }.to(new)" do - before(:each) do - @instance = SomethingExpected.new - @instance.some_value = 'string' - end - - it "should pass when attribute is == to expected value after executing block" do - lambda { @instance.some_value = "cat" }.should change{@instance.some_value}.to("cat") - end - - it "should fail when attribute is not == to expected value after executing block" do - lambda do - lambda { @instance.some_value = "cat" }.should change{@instance.some_value}.from("string").to("dog") - end.should fail_with("result should have been changed to \"dog\", but is now \"cat\"") - end -end - -describe "should change(actual, message).from(old).to(new)" do - before(:each) do - @instance = SomethingExpected.new - @instance.some_value = 'string' - end - - it "should pass when #to comes before #from" do - lambda { @instance.some_value = "cat" }.should change(@instance, :some_value).to("cat").from("string") - end - - it "should pass when #from comes before #to" do - lambda { @instance.some_value = "cat" }.should change(@instance, :some_value).from("string").to("cat") - end -end - -describe "should change{ block }.from(old).to(new)" do - before(:each) do - @instance = SomethingExpected.new - @instance.some_value = 'string' - end - - it "should pass when #to comes before #from" do - lambda { @instance.some_value = "cat" }.should change{@instance.some_value}.to("cat").from("string") - end - - it "should pass when #from comes before #to" do - lambda { @instance.some_value = "cat" }.should change{@instance.some_value}.from("string").to("cat") - end -end diff --git a/vendor/gems/rspec/spec/spec/matchers/description_generation_spec.rb b/vendor/gems/rspec/spec/spec/matchers/description_generation_spec.rb deleted file mode 100644 index c494e2165f..0000000000 --- a/vendor/gems/rspec/spec/spec/matchers/description_generation_spec.rb +++ /dev/null @@ -1,153 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -describe "Matchers should be able to generate their own descriptions" do - after(:each) do - Spec::Matchers.clear_generated_description - end - - it "should == expected" do - "this".should == "this" - Spec::Matchers.generated_description.should == "should == \"this\"" - end - - it "should not == expected" do - "this".should_not == "that" - Spec::Matchers.generated_description.should == "should not == \"that\"" - end - - it "should be empty (arbitrary predicate)" do - [].should be_empty - Spec::Matchers.generated_description.should == "should be empty" - end - - it "should not be empty (arbitrary predicate)" do - [1].should_not be_empty - Spec::Matchers.generated_description.should == "should not be empty" - end - - it "should be true" do - true.should be_true - Spec::Matchers.generated_description.should == "should be true" - end - - it "should be false" do - false.should be_false - Spec::Matchers.generated_description.should == "should be false" - end - - it "should be nil" do - nil.should be_nil - Spec::Matchers.generated_description.should == "should be nil" - end - - it "should be > n" do - 5.should be > 3 - Spec::Matchers.generated_description.should == "should be > 3" - end - - it "should be predicate arg1, arg2 and arg3" do - 5.0.should be_between(0,10) - Spec::Matchers.generated_description.should == "should be between 0 and 10" - end - - it "should be_few_words predicate should be transformed to 'be few words'" do - 5.should be_kind_of(Fixnum) - Spec::Matchers.generated_description.should == "should be kind of Fixnum" - end - - it "should preserve a proper prefix for be predicate" do - 5.should be_a_kind_of(Fixnum) - Spec::Matchers.generated_description.should == "should be a kind of Fixnum" - 5.should be_an_instance_of(Fixnum) - Spec::Matchers.generated_description.should == "should be an instance of Fixnum" - end - - it "should equal" do - expected = "expected" - expected.should equal(expected) - Spec::Matchers.generated_description.should == "should equal \"expected\"" - end - - it "should_not equal" do - 5.should_not equal(37) - Spec::Matchers.generated_description.should == "should not equal 37" - end - - it "should eql" do - "string".should eql("string") - Spec::Matchers.generated_description.should == "should eql \"string\"" - end - - it "should not eql" do - "a".should_not eql(:a) - Spec::Matchers.generated_description.should == "should not eql :a" - end - - it "should have_key" do - {:a => "a"}.should have_key(:a) - Spec::Matchers.generated_description.should == "should have key :a" - end - - it "should have n items" do - team.should have(3).players - Spec::Matchers.generated_description.should == "should have 3 players" - end - - it "should have at least n items" do - team.should have_at_least(2).players - Spec::Matchers.generated_description.should == "should have at least 2 players" - end - - it "should have at most n items" do - team.should have_at_most(4).players - Spec::Matchers.generated_description.should == "should have at most 4 players" - end - - it "should include" do - [1,2,3].should include(3) - Spec::Matchers.generated_description.should == "should include 3" - end - - it "should match" do - "this string".should match(/this string/) - Spec::Matchers.generated_description.should == "should match /this string/" - end - - it "should raise_error" do - lambda { raise }.should raise_error - Spec::Matchers.generated_description.should == "should raise Exception" - end - - it "should raise_error with type" do - lambda { raise }.should raise_error(RuntimeError) - Spec::Matchers.generated_description.should == "should raise RuntimeError" - end - - it "should raise_error with type and message" do - lambda { raise "there was an error" }.should raise_error(RuntimeError, "there was an error") - Spec::Matchers.generated_description.should == "should raise RuntimeError with \"there was an error\"" - end - - it "should respond_to" do - [].should respond_to(:insert) - Spec::Matchers.generated_description.should == "should respond to #insert" - end - - it "should throw symbol" do - lambda { throw :what_a_mess }.should throw_symbol - Spec::Matchers.generated_description.should == "should throw a Symbol" - end - - it "should throw symbol (with named symbol)" do - lambda { throw :what_a_mess }.should throw_symbol(:what_a_mess) - Spec::Matchers.generated_description.should == "should throw :what_a_mess" - end - - def team - Class.new do - def players - [1,2,3] - end - end.new - end -end diff --git a/vendor/gems/rspec/spec/spec/matchers/eql_spec.rb b/vendor/gems/rspec/spec/spec/matchers/eql_spec.rb deleted file mode 100644 index 3f265d7000..0000000000 --- a/vendor/gems/rspec/spec/spec/matchers/eql_spec.rb +++ /dev/null @@ -1,28 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -module Spec - module Matchers - describe Eql do - it "should match when actual.eql?(expected)" do - Eql.new(1).matches?(1).should be_true - end - it "should not match when !actual.eql?(expected)" do - Eql.new(1).matches?(2).should be_false - end - it "should describe itself" do - matcher = Eql.new(1) - matcher.description.should == "eql 1" - end - it "should provide message, expected and actual on #failure_message" do - matcher = Eql.new("1") - matcher.matches?(1) - matcher.failure_message.should == ["expected \"1\", got 1 (using .eql?)", "1", 1] - end - it "should provide message, expected and actual on #negative_failure_message" do - matcher = Eql.new(1) - matcher.matches?(1) - matcher.negative_failure_message.should == ["expected 1 not to equal 1 (using .eql?)", 1, 1] - end - end - end -end diff --git a/vendor/gems/rspec/spec/spec/matchers/equal_spec.rb b/vendor/gems/rspec/spec/spec/matchers/equal_spec.rb deleted file mode 100644 index 7667bdc388..0000000000 --- a/vendor/gems/rspec/spec/spec/matchers/equal_spec.rb +++ /dev/null @@ -1,28 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -module Spec - module Matchers - describe Equal do - it "should match when actual.equal?(expected)" do - Equal.new(1).matches?(1).should be_true - end - it "should not match when !actual.equal?(expected)" do - Equal.new("1").matches?("1").should be_false - end - it "should describe itself" do - matcher = Equal.new(1) - matcher.description.should == "equal 1" - end - it "should provide message, expected and actual on #failure_message" do - matcher = Equal.new("1") - matcher.matches?(1) - matcher.failure_message.should == ["expected \"1\", got 1 (using .equal?)", "1", 1] - end - it "should provide message, expected and actual on #negative_failure_message" do - matcher = Equal.new(1) - matcher.matches?(1) - matcher.negative_failure_message.should == ["expected 1 not to equal 1 (using .equal?)", 1, 1] - end - end - end -end diff --git a/vendor/gems/rspec/spec/spec/matchers/exist_spec.rb b/vendor/gems/rspec/spec/spec/matchers/exist_spec.rb deleted file mode 100644 index 0a509726eb..0000000000 --- a/vendor/gems/rspec/spec/spec/matchers/exist_spec.rb +++ /dev/null @@ -1,57 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -class Substance - def initialize exists, description - @exists = exists - @description = description - end - def exist? - @exists - end - def inspect - @description - end -end - -class SubstanceTester - include Spec::Matchers - def initialize substance - @substance = substance - end - def should_exist - @substance.should exist - end -end - -describe "should exist," do - - before(:each) do - @real = Substance.new true, 'something real' - @imaginary = Substance.new false, 'something imaginary' - end - - describe "within an example group" do - - it "should pass if target exists" do - @real.should exist - end - - it "should fail if target does not exist" do - lambda { @imaginary.should exist }.should fail - end - - it "should pass if target doesn't exist" do - lambda { @real.should_not exist }.should fail - end - end - - describe "outside of an example group" do - - it "should pass if target exists" do - real_tester = SubstanceTester.new @real - real_tester.should_exist - end - - end - -end diff --git a/vendor/gems/rspec/spec/spec/matchers/handler_spec.rb b/vendor/gems/rspec/spec/spec/matchers/handler_spec.rb deleted file mode 100644 index ad4fe6f856..0000000000 --- a/vendor/gems/rspec/spec/spec/matchers/handler_spec.rb +++ /dev/null @@ -1,129 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -module ExampleExpectations - - class ArbitraryMatcher - def initialize(*args, &block) - if args.last.is_a? Hash - @expected = args.last[:expected] - end - if block_given? - @expected = block.call - end - @block = block - end - - def matches?(target) - @target = target - return @expected == target - end - - def with(new_value) - @expected = new_value - self - end - - def failure_message - "expected #{@expected}, got #{@target}" - end - - def negative_failure_message - "expected not #{@expected}, got #{@target}" - end - end - - class PositiveOnlyMatcher < ArbitraryMatcher - undef negative_failure_message rescue nil - end - - def arbitrary_matcher(*args, &block) - ArbitraryMatcher.new(*args, &block) - end - - def positive_only_matcher(*args, &block) - PositiveOnlyMatcher.new(*args, &block) - end - -end - -module Spec - module Expectations - describe ExpectationMatcherHandler, ".handle_matcher" do - it "should ask the matcher if it matches" do - matcher = mock("matcher") - actual = Object.new - matcher.should_receive(:matches?).with(actual).and_return(true) - ExpectationMatcherHandler.handle_matcher(actual, matcher) - end - - it "should explain when the matcher parameter is not a matcher" do - begin - nonmatcher = mock("nonmatcher") - actual = Object.new - ExpectationMatcherHandler.handle_matcher(actual, nonmatcher) - rescue Spec::Expectations::InvalidMatcherError => e - end - - e.message.should =~ /^Expected a matcher, got / - end - end - - describe NegativeExpectationMatcherHandler, ".handle_matcher" do - it "should explain when matcher does not support should_not" do - matcher = mock("matcher") - matcher.stub!(:matches?) - actual = Object.new - lambda { - NegativeExpectationMatcherHandler.handle_matcher(actual, matcher) - }.should fail_with(/Matcher does not support should_not.\n/) - end - - it "should ask the matcher if it matches" do - matcher = mock("matcher") - actual = Object.new - matcher.stub!(:negative_failure_message) - matcher.should_receive(:matches?).with(actual).and_return(false) - NegativeExpectationMatcherHandler.handle_matcher(actual, matcher) - end - - it "should explain when the matcher parameter is not a matcher" do - begin - nonmatcher = mock("nonmatcher") - actual = Object.new - NegativeExpectationMatcherHandler.handle_matcher(actual, nonmatcher) - rescue Spec::Expectations::InvalidMatcherError => e - end - - e.message.should =~ /^Expected a matcher, got / - end - end - - describe ExpectationMatcherHandler do - include ExampleExpectations - - it "should handle submitted args" do - 5.should arbitrary_matcher(:expected => 5) - 5.should arbitrary_matcher(:expected => "wrong").with(5) - lambda { 5.should arbitrary_matcher(:expected => 4) }.should fail_with("expected 4, got 5") - lambda { 5.should arbitrary_matcher(:expected => 5).with(4) }.should fail_with("expected 4, got 5") - 5.should_not arbitrary_matcher(:expected => 4) - 5.should_not arbitrary_matcher(:expected => 5).with(4) - lambda { 5.should_not arbitrary_matcher(:expected => 5) }.should fail_with("expected not 5, got 5") - lambda { 5.should_not arbitrary_matcher(:expected => 4).with(5) }.should fail_with("expected not 5, got 5") - end - - it "should handle the submitted block" do - 5.should arbitrary_matcher { 5 } - 5.should arbitrary_matcher(:expected => 4) { 5 } - 5.should arbitrary_matcher(:expected => 4).with(5) { 3 } - end - - it "should explain when matcher does not support should_not" do - lambda { - 5.should_not positive_only_matcher(:expected => 5) - }.should fail_with(/Matcher does not support should_not.\n/) - end - - end - end -end diff --git a/vendor/gems/rspec/spec/spec/matchers/has_spec.rb b/vendor/gems/rspec/spec/spec/matchers/has_spec.rb deleted file mode 100644 index 47f048ebf0..0000000000 --- a/vendor/gems/rspec/spec/spec/matchers/has_spec.rb +++ /dev/null @@ -1,37 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -describe "should have_sym(*args)" do - it "should pass if #has_sym?(*args) returns true" do - {:a => "A"}.should have_key(:a) - end - - it "should fail if #has_sym?(*args) returns false" do - lambda { - {:b => "B"}.should have_key(:a) - }.should fail_with("expected #has_key?(:a) to return true, got false") - end - - it "should fail if target does not respond to #has_sym?" do - lambda { - Object.new.should have_key(:a) - }.should raise_error(NoMethodError) - end -end - -describe "should_not have_sym(*args)" do - it "should pass if #has_sym?(*args) returns false" do - {:a => "A"}.should_not have_key(:b) - end - - it "should fail if #has_sym?(*args) returns true" do - lambda { - {:a => "A"}.should_not have_key(:a) - }.should fail_with("expected #has_key?(:a) to return false, got true") - end - - it "should fail if target does not respond to #has_sym?" do - lambda { - Object.new.should have_key(:a) - }.should raise_error(NoMethodError) - end -end diff --git a/vendor/gems/rspec/spec/spec/matchers/have_spec.rb b/vendor/gems/rspec/spec/spec/matchers/have_spec.rb deleted file mode 100644 index 27083c294e..0000000000 --- a/vendor/gems/rspec/spec/spec/matchers/have_spec.rb +++ /dev/null @@ -1,291 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -module HaveSpecHelper - def create_collection_owner_with(n) - owner = Spec::Expectations::Helper::CollectionOwner.new - (1..n).each do |n| - owner.add_to_collection_with_length_method(n) - owner.add_to_collection_with_size_method(n) - end - owner - end -end - -describe "should have(n).items" do - include HaveSpecHelper - - it "should pass if target has a collection of items with n members" do - owner = create_collection_owner_with(3) - owner.should have(3).items_in_collection_with_length_method - owner.should have(3).items_in_collection_with_size_method - end - - it "should convert :no to 0" do - owner = create_collection_owner_with(0) - owner.should have(:no).items_in_collection_with_length_method - owner.should have(:no).items_in_collection_with_size_method - end - - it "should fail if target has a collection of items with < n members" do - owner = create_collection_owner_with(3) - lambda { - owner.should have(4).items_in_collection_with_length_method - }.should fail_with("expected 4 items_in_collection_with_length_method, got 3") - lambda { - owner.should have(4).items_in_collection_with_size_method - }.should fail_with("expected 4 items_in_collection_with_size_method, got 3") - end - - it "should fail if target has a collection of items with > n members" do - owner = create_collection_owner_with(3) - lambda { - owner.should have(2).items_in_collection_with_length_method - }.should fail_with("expected 2 items_in_collection_with_length_method, got 3") - lambda { - owner.should have(2).items_in_collection_with_size_method - }.should fail_with("expected 2 items_in_collection_with_size_method, got 3") - end -end - -describe 'should have(1).item when Inflector is defined' do - include HaveSpecHelper - - before do - unless Object.const_defined?(:Inflector) - class Inflector - def self.pluralize(string) - string.to_s + 's' - end - end - end - end - - it 'should pluralize the collection name' do - owner = create_collection_owner_with(1) - owner.should have(1).item - end -end - -describe "should have(n).items where result responds to items but returns something other than a collection" do - it "should provide a meaningful error" do - owner = Class.new do - def items - Object.new - end - end.new - lambda do - owner.should have(3).items - end.should raise_error("expected items to be a collection but it does not respond to #length or #size") - end -end - -describe "should_not have(n).items" do - include HaveSpecHelper - - it "should pass if target has a collection of items with < n members" do - owner = create_collection_owner_with(3) - owner.should_not have(4).items_in_collection_with_length_method - owner.should_not have(4).items_in_collection_with_size_method - end - - it "should pass if target has a collection of items with > n members" do - owner = create_collection_owner_with(3) - owner.should_not have(2).items_in_collection_with_length_method - owner.should_not have(2).items_in_collection_with_size_method - end - - it "should fail if target has a collection of items with n members" do - owner = create_collection_owner_with(3) - lambda { - owner.should_not have(3).items_in_collection_with_length_method - }.should fail_with("expected target not to have 3 items_in_collection_with_length_method, got 3") - lambda { - owner.should_not have(3).items_in_collection_with_size_method - }.should fail_with("expected target not to have 3 items_in_collection_with_size_method, got 3") - end -end - -describe "should have_exactly(n).items" do - include HaveSpecHelper - - it "should pass if target has a collection of items with n members" do - owner = create_collection_owner_with(3) - owner.should have_exactly(3).items_in_collection_with_length_method - owner.should have_exactly(3).items_in_collection_with_size_method - end - - it "should convert :no to 0" do - owner = create_collection_owner_with(0) - owner.should have_exactly(:no).items_in_collection_with_length_method - owner.should have_exactly(:no).items_in_collection_with_size_method - end - - it "should fail if target has a collection of items with < n members" do - owner = create_collection_owner_with(3) - lambda { - owner.should have_exactly(4).items_in_collection_with_length_method - }.should fail_with("expected 4 items_in_collection_with_length_method, got 3") - lambda { - owner.should have_exactly(4).items_in_collection_with_size_method - }.should fail_with("expected 4 items_in_collection_with_size_method, got 3") - end - - it "should fail if target has a collection of items with > n members" do - owner = create_collection_owner_with(3) - lambda { - owner.should have_exactly(2).items_in_collection_with_length_method - }.should fail_with("expected 2 items_in_collection_with_length_method, got 3") - lambda { - owner.should have_exactly(2).items_in_collection_with_size_method - }.should fail_with("expected 2 items_in_collection_with_size_method, got 3") - end -end - -describe "should have_at_least(n).items" do - include HaveSpecHelper - - it "should pass if target has a collection of items with n members" do - owner = create_collection_owner_with(3) - owner.should have_at_least(3).items_in_collection_with_length_method - owner.should have_at_least(3).items_in_collection_with_size_method - end - - it "should pass if target has a collection of items with > n members" do - owner = create_collection_owner_with(3) - owner.should have_at_least(2).items_in_collection_with_length_method - owner.should have_at_least(2).items_in_collection_with_size_method - end - - it "should fail if target has a collection of items with < n members" do - owner = create_collection_owner_with(3) - lambda { - owner.should have_at_least(4).items_in_collection_with_length_method - }.should fail_with("expected at least 4 items_in_collection_with_length_method, got 3") - lambda { - owner.should have_at_least(4).items_in_collection_with_size_method - }.should fail_with("expected at least 4 items_in_collection_with_size_method, got 3") - end - - it "should provide educational negative failure messages" do - #given - owner = create_collection_owner_with(3) - length_matcher = have_at_least(3).items_in_collection_with_length_method - size_matcher = have_at_least(3).items_in_collection_with_size_method - - #when - length_matcher.matches?(owner) - size_matcher.matches?(owner) - - #then - length_matcher.negative_failure_message.should == <<-EOF -Isn't life confusing enough? -Instead of having to figure out the meaning of this: - should_not have_at_least(3).items_in_collection_with_length_method -We recommend that you use this instead: - should have_at_most(2).items_in_collection_with_length_method -EOF - - size_matcher.negative_failure_message.should == <<-EOF -Isn't life confusing enough? -Instead of having to figure out the meaning of this: - should_not have_at_least(3).items_in_collection_with_size_method -We recommend that you use this instead: - should have_at_most(2).items_in_collection_with_size_method -EOF - end -end - -describe "should have_at_most(n).items" do - include HaveSpecHelper - - it "should pass if target has a collection of items with n members" do - owner = create_collection_owner_with(3) - owner.should have_at_most(3).items_in_collection_with_length_method - owner.should have_at_most(3).items_in_collection_with_size_method - end - - it "should fail if target has a collection of items with > n members" do - owner = create_collection_owner_with(3) - lambda { - owner.should have_at_most(2).items_in_collection_with_length_method - }.should fail_with("expected at most 2 items_in_collection_with_length_method, got 3") - lambda { - owner.should have_at_most(2).items_in_collection_with_size_method - }.should fail_with("expected at most 2 items_in_collection_with_size_method, got 3") - end - - it "should pass if target has a collection of items with < n members" do - owner = create_collection_owner_with(3) - owner.should have_at_most(4).items_in_collection_with_length_method - owner.should have_at_most(4).items_in_collection_with_size_method - end - - it "should provide educational negative failure messages" do - #given - owner = create_collection_owner_with(3) - length_matcher = have_at_most(3).items_in_collection_with_length_method - size_matcher = have_at_most(3).items_in_collection_with_size_method - - #when - length_matcher.matches?(owner) - size_matcher.matches?(owner) - - #then - length_matcher.negative_failure_message.should == <<-EOF -Isn't life confusing enough? -Instead of having to figure out the meaning of this: - should_not have_at_most(3).items_in_collection_with_length_method -We recommend that you use this instead: - should have_at_least(4).items_in_collection_with_length_method -EOF - - size_matcher.negative_failure_message.should == <<-EOF -Isn't life confusing enough? -Instead of having to figure out the meaning of this: - should_not have_at_most(3).items_in_collection_with_size_method -We recommend that you use this instead: - should have_at_least(4).items_in_collection_with_size_method -EOF - end -end - -describe "have(n).items(args, block)" do - it "should pass args to target" do - target = mock("target") - target.should_receive(:items).with("arg1","arg2").and_return([1,2,3]) - target.should have(3).items("arg1","arg2") - end - - it "should pass block to target" do - target = mock("target") - block = lambda { 5 } - target.should_receive(:items).with("arg1","arg2", block).and_return([1,2,3]) - target.should have(3).items("arg1","arg2", block) - end -end - -describe "have(n).items where target IS a collection" do - it "should reference the number of items IN the collection" do - [1,2,3].should have(3).items - end - - it "should fail when the number of items IN the collection is not as expected" do - lambda { [1,2,3].should have(7).items }.should fail_with("expected 7 items, got 3") - end -end - -describe "have(n).characters where target IS a String" do - it "should pass if the length is correct" do - "this string".should have(11).characters - end - - it "should fail if the length is incorrect" do - lambda { "this string".should have(12).characters }.should fail_with("expected 12 characters, got 11") - end -end - -describe "have(n).things on an object which is not a collection nor contains one" do - it "should fail" do - lambda { Object.new.should have(2).things }.should raise_error(NoMethodError, /undefined method `things' for #" do - - it "should pass if > passes" do - 4.should > 3 - end - - it "should fail if > fails" do - Spec::Expectations.should_receive(:fail_with).with(%[expected: > 5,\n got: 4], 5, 4) - 4.should > 5 - end - -end - -describe "should >=" do - - it "should pass if >= passes" do - 4.should > 3 - 4.should >= 4 - end - - it "should fail if > fails" do - Spec::Expectations.should_receive(:fail_with).with(%[expected: >= 5,\n got: 4], 5, 4) - 4.should >= 5 - end - -end - -describe "should <" do - - it "should pass if < passes" do - 4.should < 5 - end - - it "should fail if > fails" do - Spec::Expectations.should_receive(:fail_with).with(%[expected: < 3,\n got: 4], 3, 4) - 4.should < 3 - end - -end - -describe "should <=" do - - it "should pass if <= passes" do - 4.should <= 5 - 4.should <= 4 - end - - it "should fail if > fails" do - Spec::Expectations.should_receive(:fail_with).with(%[expected: <= 3,\n got: 4], 3, 4) - 4.should <= 3 - end - -end - diff --git a/vendor/gems/rspec/spec/spec/matchers/raise_error_spec.rb b/vendor/gems/rspec/spec/spec/matchers/raise_error_spec.rb deleted file mode 100644 index 7cabf81e87..0000000000 --- a/vendor/gems/rspec/spec/spec/matchers/raise_error_spec.rb +++ /dev/null @@ -1,191 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -describe "should raise_error" do - it "should pass if anything is raised" do - lambda {raise}.should raise_error - end - - it "should fail if nothing is raised" do - lambda { - lambda {}.should raise_error - }.should fail_with("expected Exception but nothing was raised") - end -end - -describe "should_not raise_error" do - it "should pass if nothing is raised" do - lambda {}.should_not raise_error - end - - it "should fail if anything is raised" do - lambda { - lambda {raise}.should_not raise_error - }.should fail_with("expected no Exception, got RuntimeError") - end -end - -describe "should raise_error(message)" do - it "should pass if RuntimeError is raised with the right message" do - lambda {raise 'blah'}.should raise_error('blah') - end - it "should pass if any other error is raised with the right message" do - lambda {raise NameError.new('blah')}.should raise_error('blah') - end - it "should fail if RuntimeError error is raised with the wrong message" do - lambda do - lambda {raise 'blarg'}.should raise_error('blah') - end.should fail_with("expected Exception with \"blah\", got #") - end - it "should fail if any other error is raised with the wrong message" do - lambda do - lambda {raise NameError.new('blarg')}.should raise_error('blah') - end.should fail_with("expected Exception with \"blah\", got #") - end -end - -describe "should_not raise_error(message)" do - it "should pass if RuntimeError error is raised with the different message" do - lambda {raise 'blarg'}.should_not raise_error('blah') - end - it "should pass if any other error is raised with the wrong message" do - lambda {raise NameError.new('blarg')}.should_not raise_error('blah') - end - it "should fail if RuntimeError is raised with message" do - lambda do - lambda {raise 'blah'}.should_not raise_error('blah') - end.should fail_with(%Q|expected no Exception with "blah", got #|) - end - it "should fail if any other error is raised with message" do - lambda do - lambda {raise NameError.new('blah')}.should_not raise_error('blah') - end.should fail_with(%Q|expected no Exception with "blah", got #|) - end -end - -describe "should raise_error(NamedError)" do - it "should pass if named error is raised" do - lambda { non_existent_method }.should raise_error(NameError) - end - - it "should fail if nothing is raised" do - lambda { - lambda { }.should raise_error(NameError) - }.should fail_with("expected NameError but nothing was raised") - end - - it "should fail if another error is raised (NameError)" do - lambda { - lambda { raise }.should raise_error(NameError) - }.should fail_with("expected NameError, got RuntimeError") - end - - it "should fail if another error is raised (NameError)" do - lambda { - lambda { load "non/existent/file" }.should raise_error(NameError) - }.should fail_with(/expected NameError, got #") - end -end - -describe "should raise_error(NamedError, error_message) with Regexp" do - it "should pass if named error is raised with matching message" do - lambda { raise "example message" }.should raise_error(RuntimeError, /ample mess/) - end - - it "should fail if nothing is raised" do - lambda { - lambda {}.should raise_error(RuntimeError, /ample mess/) - }.should fail_with("expected RuntimeError with message matching /ample mess/ but nothing was raised") - end - - it "should fail if incorrect error is raised" do - lambda { - lambda { raise }.should raise_error(NameError, /ample mess/) - }.should fail_with("expected NameError with message matching /ample mess/, got RuntimeError") - end - - it "should fail if correct error is raised with incorrect message" do - lambda { - lambda { raise RuntimeError.new("not the example message") }.should raise_error(RuntimeError, /less than ample mess/) - }.should fail_with("expected RuntimeError with message matching /less than ample mess/, got #") - end -end - -describe "should_not raise_error(NamedError, error_message) with Regexp" do - it "should pass if nothing is raised" do - lambda {}.should_not raise_error(RuntimeError, /ample mess/) - end - - it "should pass if a different error is raised" do - lambda { raise }.should_not raise_error(NameError, /ample mess/) - end - - it "should pass if same error is raised with non-matching message" do - lambda { raise RuntimeError.new("non matching message") }.should_not raise_error(RuntimeError, /ample mess/) - end - - it "should fail if named error is raised with matching message" do - lambda { - lambda { raise "example message" }.should_not raise_error(RuntimeError, /ample mess/) - }.should fail_with("expected no RuntimeError with message matching /ample mess/, got #") - end -end diff --git a/vendor/gems/rspec/spec/spec/matchers/respond_to_spec.rb b/vendor/gems/rspec/spec/spec/matchers/respond_to_spec.rb deleted file mode 100644 index 2cdbbcd635..0000000000 --- a/vendor/gems/rspec/spec/spec/matchers/respond_to_spec.rb +++ /dev/null @@ -1,54 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -describe "should respond_to(:sym)" do - - it "should pass if target responds to :sym" do - Object.new.should respond_to(:methods) - end - - it "should fail target does not respond to :sym" do - lambda { - Object.new.should respond_to(:some_method) - }.should fail_with("expected target to respond to :some_method") - end - -end - -describe "should respond_to(message1, message2)" do - - it "should pass if target responds to both messages" do - Object.new.should respond_to('methods', 'inspect') - end - - it "should fail target does not respond to first message" do - lambda { - Object.new.should respond_to('method_one', 'inspect') - }.should fail_with('expected target to respond to "method_one"') - end - - it "should fail target does not respond to second message" do - lambda { - Object.new.should respond_to('inspect', 'method_one') - }.should fail_with('expected target to respond to "method_one"') - end - - it "should fail target does not respond to either message" do - lambda { - Object.new.should respond_to('method_one', 'method_two') - }.should fail_with('expected target to respond to "method_one", "method_two"') - end -end - -describe "should_not respond_to(:sym)" do - - it "should pass if target does not respond to :sym" do - Object.new.should_not respond_to(:some_method) - end - - it "should fail target responds to :sym" do - lambda { - Object.new.should_not respond_to(:methods) - }.should fail_with("expected target not to respond to :methods") - end - -end diff --git a/vendor/gems/rspec/spec/spec/matchers/satisfy_spec.rb b/vendor/gems/rspec/spec/spec/matchers/satisfy_spec.rb deleted file mode 100644 index 7e8d6f972f..0000000000 --- a/vendor/gems/rspec/spec/spec/matchers/satisfy_spec.rb +++ /dev/null @@ -1,36 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -describe "should satisfy { block }" do - it "should pass if block returns true" do - true.should satisfy { |val| val } - true.should satisfy do |val| - val - end - end - - it "should fail if block returns false" do - lambda { - false.should satisfy { |val| val } - }.should fail_with("expected false to satisfy block") - lambda do - false.should satisfy do |val| - val - end - end.should fail_with("expected false to satisfy block") - end -end - -describe "should_not satisfy { block }" do - it "should pass if block returns false" do - false.should_not satisfy { |val| val } - false.should_not satisfy do |val| - val - end - end - - it "should fail if block returns true" do - lambda { - true.should_not satisfy { |val| val } - }.should fail_with("expected true not to satisfy block") - end -end diff --git a/vendor/gems/rspec/spec/spec/matchers/simple_matcher_spec.rb b/vendor/gems/rspec/spec/spec/matchers/simple_matcher_spec.rb deleted file mode 100644 index b731af92d9..0000000000 --- a/vendor/gems/rspec/spec/spec/matchers/simple_matcher_spec.rb +++ /dev/null @@ -1,31 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper' - -module Spec - module Matchers - describe SimpleMatcher do - it "should match pass match arg to block" do - actual = nil - matcher = simple_matcher("message") do |given| actual = given end - matcher.matches?("foo") - actual.should == "foo" - end - - it "should provide a stock failure message" do - matcher = simple_matcher("thing") do end - matcher.matches?("other") - matcher.failure_message.should =~ /expected \"thing\" but got \"other\"/ - end - - it "should provide a stock negative failure message" do - matcher = simple_matcher("thing") do end - matcher.matches?("other") - matcher.negative_failure_message.should =~ /expected not to get \"thing\", but got \"other\"/ - end - - it "should provide a description" do - matcher = simple_matcher("thing") do end - matcher.description.should =="thing" - end - end - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/matchers/throw_symbol_spec.rb b/vendor/gems/rspec/spec/spec/matchers/throw_symbol_spec.rb deleted file mode 100644 index 74595659a2..0000000000 --- a/vendor/gems/rspec/spec/spec/matchers/throw_symbol_spec.rb +++ /dev/null @@ -1,54 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -module Spec - module Matchers - describe ThrowSymbol, "(constructed with no Symbol)" do - before(:each) { @matcher = ThrowSymbol.new } - - it "should match if any Symbol is thrown" do - @matcher.matches?(lambda{ throw :sym }).should be_true - end - it "should not match if no Symbol is thrown" do - @matcher.matches?(lambda{ }).should be_false - end - it "should provide a failure message" do - @matcher.matches?(lambda{}) - @matcher.failure_message.should == "expected a Symbol but nothing was thrown" - end - it "should provide a negative failure message" do - @matcher.matches?(lambda{ throw :sym}) - @matcher.negative_failure_message.should == "expected no Symbol, got :sym" - end - end - - describe ThrowSymbol, "(constructed with a Symbol)" do - before(:each) { @matcher = ThrowSymbol.new(:sym) } - - it "should match if correct Symbol is thrown" do - @matcher.matches?(lambda{ throw :sym }).should be_true - end - it "should not match if no Symbol is thrown" do - @matcher.matches?(lambda{ }).should be_false - end - it "should not match if correct Symbol is thrown" do - @matcher.matches?(lambda{ throw :other_sym }).should be_false - @matcher.failure_message.should == "expected :sym, got :other_sym" - end - it "should provide a failure message when no Symbol is thrown" do - @matcher.matches?(lambda{}) - @matcher.failure_message.should == "expected :sym but nothing was thrown" - end - it "should provide a failure message when wrong Symbol is thrown" do - @matcher.matches?(lambda{ throw :other_sym }) - @matcher.failure_message.should == "expected :sym, got :other_sym" - end - it "should provide a negative failure message" do - @matcher.matches?(lambda{ throw :sym }) - @matcher.negative_failure_message.should == "expected :sym not to be thrown" - end - it "should only match NameErrors raised by uncaught throws" do - @matcher.matches?(lambda{ sym }).should be_false - end - end - end -end diff --git a/vendor/gems/rspec/spec/spec/mocks/any_number_of_times_spec.rb b/vendor/gems/rspec/spec/spec/mocks/any_number_of_times_spec.rb deleted file mode 100644 index 3f50dcfc5e..0000000000 --- a/vendor/gems/rspec/spec/spec/mocks/any_number_of_times_spec.rb +++ /dev/null @@ -1,29 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -module Spec - module Mocks - - describe "AnyNumberOfTimes" do - before(:each) do - @mock = Mock.new("test mock") - end - - it "should pass if any number of times method is called many times" do - @mock.should_receive(:random_call).any_number_of_times - (1..10).each do - @mock.random_call - end - end - - it "should pass if any number of times method is called once" do - @mock.should_receive(:random_call).any_number_of_times - @mock.random_call - end - - it "should pass if any number of times method is not called" do - @mock.should_receive(:random_call).any_number_of_times - end - end - - end -end diff --git a/vendor/gems/rspec/spec/spec/mocks/argument_expectation_spec.rb b/vendor/gems/rspec/spec/spec/mocks/argument_expectation_spec.rb deleted file mode 100644 index 2bebbdd4fc..0000000000 --- a/vendor/gems/rspec/spec/spec/mocks/argument_expectation_spec.rb +++ /dev/null @@ -1,23 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -module Spec - module Mocks - describe ArgumentExpectation do - it "should consider an object that responds to #matches? and #description to be a matcher" do - argument_expecatation = Spec::Mocks::ArgumentExpectation.new([]) - obj = mock("matcher") - obj.should_receive(:respond_to?).with(:matches?).and_return(true) - obj.should_receive(:respond_to?).with(:description).and_return(true) - argument_expecatation.is_matcher?(obj).should be_true - end - - it "should NOT consider an object that only responds to #matches? to be a matcher" do - argument_expecatation = Spec::Mocks::ArgumentExpectation.new([]) - obj = mock("matcher") - obj.should_receive(:respond_to?).with(:matches?).and_return(true) - obj.should_receive(:respond_to?).with(:description).and_return(false) - argument_expecatation.is_matcher?(obj).should be_false - end - end - end -end diff --git a/vendor/gems/rspec/spec/spec/mocks/at_least_spec.rb b/vendor/gems/rspec/spec/spec/mocks/at_least_spec.rb deleted file mode 100644 index 01b133dc3b..0000000000 --- a/vendor/gems/rspec/spec/spec/mocks/at_least_spec.rb +++ /dev/null @@ -1,97 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -module Spec - module Mocks - describe "at_least" do - before(:each) do - @mock = Mock.new("test mock") - end - - it "should fail if method is never called" do - @mock.should_receive(:random_call).at_least(4).times - lambda do - @mock.rspec_verify - end.should raise_error(MockExpectationError) - end - - it "should fail when called less than n times" do - @mock.should_receive(:random_call).at_least(4).times - @mock.random_call - @mock.random_call - @mock.random_call - lambda do - @mock.rspec_verify - end.should raise_error(MockExpectationError) - end - - it "should fail when at least once method is never called" do - @mock.should_receive(:random_call).at_least(:once) - lambda do - @mock.rspec_verify - end.should raise_error(MockExpectationError) - end - - it "should fail when at least twice method is called once" do - @mock.should_receive(:random_call).at_least(:twice) - @mock.random_call - lambda do - @mock.rspec_verify - end.should raise_error(MockExpectationError) - end - - it "should fail when at least twice method is never called" do - @mock.should_receive(:random_call).at_least(:twice) - lambda do - @mock.rspec_verify - end.should raise_error(MockExpectationError) - end - - it "should pass when at least n times method is called exactly n times" do - @mock.should_receive(:random_call).at_least(4).times - @mock.random_call - @mock.random_call - @mock.random_call - @mock.random_call - @mock.rspec_verify - end - - it "should pass when at least n times method is called n plus 1 times" do - @mock.should_receive(:random_call).at_least(4).times - @mock.random_call - @mock.random_call - @mock.random_call - @mock.random_call - @mock.random_call - @mock.rspec_verify - end - - it "should pass when at least once method is called once" do - @mock.should_receive(:random_call).at_least(:once) - @mock.random_call - @mock.rspec_verify - end - - it "should pass when at least once method is called twice" do - @mock.should_receive(:random_call).at_least(:once) - @mock.random_call - @mock.random_call - @mock.rspec_verify - end - - it "should pass when at least twice method is called three times" do - @mock.should_receive(:random_call).at_least(:twice) - @mock.random_call - @mock.random_call - @mock.random_call - @mock.rspec_verify - end - - it "should pass when at least twice method is called twice" do - @mock.should_receive(:random_call).at_least(:twice) - @mock.random_call - @mock.random_call - @mock.rspec_verify - end - end - end -end diff --git a/vendor/gems/rspec/spec/spec/mocks/at_most_spec.rb b/vendor/gems/rspec/spec/spec/mocks/at_most_spec.rb deleted file mode 100644 index f3c5e2150d..0000000000 --- a/vendor/gems/rspec/spec/spec/mocks/at_most_spec.rb +++ /dev/null @@ -1,93 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -module Spec - module Mocks - describe "at_most" do - before(:each) do - @mock = Mock.new("test mock") - end - - it "should fail when at most n times method is called n plus 1 times" do - @mock.should_receive(:random_call).at_most(4).times - @mock.random_call - @mock.random_call - @mock.random_call - @mock.random_call - @mock.random_call - lambda do - @mock.rspec_verify - end.should raise_error(MockExpectationError) - end - - it "should fail when at most once method is called twice" do - @mock.should_receive(:random_call).at_most(:once) - @mock.random_call - @mock.random_call - lambda do - @mock.rspec_verify - end.should raise_error(MockExpectationError) - end - - it "should fail when at most twice method is called three times" do - @mock.should_receive(:random_call).at_most(:twice) - @mock.random_call - @mock.random_call - @mock.random_call - lambda do - @mock.rspec_verify - end.should raise_error(MockExpectationError) - end - - it "should pass when at most n times method is called exactly n times" do - @mock.should_receive(:random_call).at_most(4).times - @mock.random_call - @mock.random_call - @mock.random_call - @mock.random_call - @mock.rspec_verify - end - - it "should pass when at most n times method is called less than n times" do - @mock.should_receive(:random_call).at_most(4).times - @mock.random_call - @mock.random_call - @mock.random_call - @mock.rspec_verify - end - - it "should pass when at most n times method is never called" do - @mock.should_receive(:random_call).at_most(4).times - @mock.rspec_verify - end - - it "should pass when at most once method is called once" do - @mock.should_receive(:random_call).at_most(:once) - @mock.random_call - @mock.rspec_verify - end - - it "should pass when at most once method is never called" do - @mock.should_receive(:random_call).at_most(:once) - @mock.rspec_verify - end - - it "should pass when at most twice method is called once" do - @mock.should_receive(:random_call).at_most(:twice) - @mock.random_call - @mock.rspec_verify - end - - it "should pass when at most twice method is called twice" do - @mock.should_receive(:random_call).at_most(:twice) - @mock.random_call - @mock.random_call - @mock.rspec_verify - end - - it "should pass when at most twice method is never called" do - @mock.should_receive(:random_call).at_most(:twice) - @mock.rspec_verify - end - end - end -end diff --git a/vendor/gems/rspec/spec/spec/mocks/bug_report_10260_spec.rb b/vendor/gems/rspec/spec/spec/mocks/bug_report_10260_spec.rb deleted file mode 100644 index 2f7b5803d5..0000000000 --- a/vendor/gems/rspec/spec/spec/mocks/bug_report_10260_spec.rb +++ /dev/null @@ -1,8 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -describe "An RSpec Mock" do - it "should hide internals in its inspect representation" do - m = mock('cup') - m.inspect.should =~ /#/ - end -end diff --git a/vendor/gems/rspec/spec/spec/mocks/bug_report_10263.rb b/vendor/gems/rspec/spec/spec/mocks/bug_report_10263.rb deleted file mode 100644 index e321922573..0000000000 --- a/vendor/gems/rspec/spec/spec/mocks/bug_report_10263.rb +++ /dev/null @@ -1,24 +0,0 @@ -describe "Mock" do - before do - @mock = mock("test mock") - end - - specify "when one example has an expectation (non-mock) inside the block passed to the mock" do - @mock.should_receive(:msg) do |b| - b.should be_true #this call exposes the problem - end - @mock.msg(false) rescue nil - end - - specify "then the next example should behave as expected instead of saying" do - @mock.should_receive(:foobar) - @mock.foobar - @mock.rspec_verify - begin - @mock.foobar - rescue Exception => e - e.message.should == "Mock 'test mock' received unexpected message :foobar with (no args)" - end - end -end - diff --git a/vendor/gems/rspec/spec/spec/mocks/bug_report_11545_spec.rb b/vendor/gems/rspec/spec/spec/mocks/bug_report_11545_spec.rb deleted file mode 100644 index 8a334afa56..0000000000 --- a/vendor/gems/rspec/spec/spec/mocks/bug_report_11545_spec.rb +++ /dev/null @@ -1,31 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -class LiarLiarPantsOnFire - def respond_to?(sym) - true - end - - def self.respond_to?(sym) - true - end -end - -describe 'should_receive' do - before(:each) do - @liar = LiarLiarPantsOnFire.new - end - - it "should work when object lies about responding to a method" do - @liar.should_receive(:something) - @liar.something - end - - it 'should work when class lies about responding to a method' do - LiarLiarPantsOnFire.should_receive(:something) - LiarLiarPantsOnFire.something - end - - it 'should cleanup after itself' do - LiarLiarPantsOnFire.metaclass.instance_methods.should_not include("something") - end -end diff --git a/vendor/gems/rspec/spec/spec/mocks/bug_report_15719_spec.rb b/vendor/gems/rspec/spec/spec/mocks/bug_report_15719_spec.rb deleted file mode 100644 index 82d49ea97c..0000000000 --- a/vendor/gems/rspec/spec/spec/mocks/bug_report_15719_spec.rb +++ /dev/null @@ -1,30 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -module Spec - module Mocks - describe "mock failure" do - - it "should tell you when it receives the right message with the wrong args" do - m = mock("foo") - m.should_receive(:bar).with("message") - lambda { - m.bar("different message") - }.should raise_error(Spec::Mocks::MockExpectationError, %Q{Mock 'foo' expected :bar with ("message") but received it with ("different message")}) - m.bar("message") # allows the spec to pass - end - - it "should tell you when it receives the right message with the wrong args if you stub the method" do - pending("fix bug 15719") - # NOTE - for whatever reason, if you use a the block style of pending here, - # rcov gets unhappy. Don't know why yet. - m = mock("foo") - m.stub!(:bar) - m.should_receive(:bar).with("message") - lambda { - m.bar("different message") - }.should raise_error(Spec::Mocks::MockExpectationError, %Q{Mock 'foo' expected :bar with ("message") but received it with ("different message")}) - m.bar("message") # allows the spec to pass - end - end - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/mocks/bug_report_7611_spec.rb b/vendor/gems/rspec/spec/spec/mocks/bug_report_7611_spec.rb deleted file mode 100644 index 6c9705bccb..0000000000 --- a/vendor/gems/rspec/spec/spec/mocks/bug_report_7611_spec.rb +++ /dev/null @@ -1,19 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -module Bug7611 - class Foo - end - - class Bar < Foo - end - - describe "A Partial Mock" do - it "should respect subclasses" do - Foo.stub!(:new).and_return(Object.new) - end - - it "should" do - Bar.new.class.should == Bar - end - end -end diff --git a/vendor/gems/rspec/spec/spec/mocks/bug_report_7805_spec.rb b/vendor/gems/rspec/spec/spec/mocks/bug_report_7805_spec.rb deleted file mode 100644 index f7edfac175..0000000000 --- a/vendor/gems/rspec/spec/spec/mocks/bug_report_7805_spec.rb +++ /dev/null @@ -1,22 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -module Bug7805 - #This is really a duplicate of 8302 - - describe "Stubs should correctly restore module methods" do - it "1 - stub the open method" do - File.stub!(:open).and_return("something") - File.open.should == "something" - end - it "2 - use File.open to create example.txt" do - filename = "#{File.dirname(__FILE__)}/example-#{Time.new.to_i}.txt" - File.exist?(filename).should be_false - file = File.open(filename,'w') - file.close - File.exist?(filename).should be_true - File.delete(filename) - File.exist?(filename).should be_false - end - end - -end diff --git a/vendor/gems/rspec/spec/spec/mocks/bug_report_8165_spec.rb b/vendor/gems/rspec/spec/spec/mocks/bug_report_8165_spec.rb deleted file mode 100644 index 7edc3c076a..0000000000 --- a/vendor/gems/rspec/spec/spec/mocks/bug_report_8165_spec.rb +++ /dev/null @@ -1,31 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -describe "An object where respond_to? is true and does not have method" do - # When should_receive(:sym) is sent to any object, the Proxy sends - # respond_to?(:sym) to that object to see if the method should be proxied. - # - # If respond_to? itself is proxied, then when the Proxy sends respond_to? - # to the object, the proxy is invoked and responds yes (if so set in the spec). - # When the object does NOT actually respond to :sym, an exception is thrown - # when trying to proxy it. - # - # The fix was to keep track of whether :respond_to? had been proxied and, if - # so, call the munged copy of :respond_to? on the object. - - it "should not raise an exception for Object" do - obj = Object.new - obj.should_receive(:respond_to?).with(:foobar).and_return(true) - obj.should_receive(:foobar).and_return(:baz) - obj.respond_to?(:foobar).should be_true - obj.foobar.should == :baz - end - - it "should not raise an exception for mock" do - obj = mock("obj") - obj.should_receive(:respond_to?).with(:foobar).and_return(true) - obj.should_receive(:foobar).and_return(:baz) - obj.respond_to?(:foobar).should be_true - obj.foobar.should == :baz - end - -end diff --git a/vendor/gems/rspec/spec/spec/mocks/bug_report_8302_spec.rb b/vendor/gems/rspec/spec/spec/mocks/bug_report_8302_spec.rb deleted file mode 100644 index a41df43d84..0000000000 --- a/vendor/gems/rspec/spec/spec/mocks/bug_report_8302_spec.rb +++ /dev/null @@ -1,26 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -module Bug8302 - class Foo - def Foo.class_method(arg) - end - - def instance_bar(arg) - end - end - - describe "Bug report 8302:" do - it "class method is not restored correctly when proxied" do - Foo.should_not_receive(:class_method).with(Array.new) - Foo.rspec_verify - Foo.class_method(Array.new) - end - - it "instance method is not restored correctly when proxied" do - foo = Foo.new - foo.should_not_receive(:instance_bar).with(Array.new) - foo.rspec_verify - foo.instance_bar(Array.new) - end - end -end diff --git a/vendor/gems/rspec/spec/spec/mocks/failing_mock_argument_constraints_spec.rb b/vendor/gems/rspec/spec/spec/mocks/failing_mock_argument_constraints_spec.rb deleted file mode 100644 index db6dcea346..0000000000 --- a/vendor/gems/rspec/spec/spec/mocks/failing_mock_argument_constraints_spec.rb +++ /dev/null @@ -1,115 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -module Spec - module Mocks - describe "failing MockArgumentConstraints" do - before(:each) do - @mock = mock("test mock") - @reporter = Mock.new("reporter", :null_object => true) - end - - after(:each) do - @mock.rspec_reset - end - - it "should reject non boolean" do - @mock.should_receive(:random_call).with(boolean()) - lambda do - @mock.random_call("false") - end.should raise_error(MockExpectationError) - end - - it "should reject non numeric" do - @mock.should_receive(:random_call).with(an_instance_of(Numeric)) - lambda do - @mock.random_call("1") - end.should raise_error(MockExpectationError) - end - - it "should reject non string" do - @mock.should_receive(:random_call).with(an_instance_of(String)) - lambda do - @mock.random_call(123) - end.should raise_error(MockExpectationError) - end - - it "should reject goose when expecting a duck" do - @mock.should_receive(:random_call).with(duck_type(:abs, :div)) - lambda { @mock.random_call("I don't respond to :abs or :div") }.should raise_error(MockExpectationError) - end - - it "should fail if regexp does not match submitted string" do - @mock.should_receive(:random_call).with(/bcd/) - lambda { @mock.random_call("abc") }.should raise_error(MockExpectationError) - end - - it "should fail if regexp does not match submitted regexp" do - @mock.should_receive(:random_call).with(/bcd/) - lambda { @mock.random_call(/bcde/) }.should raise_error(MockExpectationError) - end - - it "should fail for a hash w/ wrong values" do - @mock.should_receive(:random_call).with(:a => "b", :c => "d") - lambda do - @mock.random_call(:a => "b", :c => "e") - end.should raise_error(MockExpectationError, /Mock 'test mock' expected :random_call with \(\{(:a=>\"b\", :c=>\"d\"|:c=>\"d\", :a=>\"b\")\}\) but received it with \(\{(:a=>\"b\", :c=>\"e\"|:c=>\"e\", :a=>\"b\")\}\)/) - end - - it "should fail for a hash w/ wrong keys" do - @mock.should_receive(:random_call).with(:a => "b", :c => "d") - lambda do - @mock.random_call("a" => "b", "c" => "d") - end.should raise_error(MockExpectationError, /Mock 'test mock' expected :random_call with \(\{(:a=>\"b\", :c=>\"d\"|:c=>\"d\", :a=>\"b\")\}\) but received it with \(\{(\"a\"=>\"b\", \"c\"=>\"d\"|\"c\"=>\"d\", \"a\"=>\"b\")\}\)/) - end - - it "should match against a Matcher" do - lambda do - @mock.should_receive(:msg).with(equal(3)) - @mock.msg(37) - end.should raise_error(MockExpectationError, "Mock 'test mock' expected :msg with (equal 3) but received it with (37)") - end - - it "should fail no_args with one arg" do - lambda do - @mock.should_receive(:msg).with(no_args) - @mock.msg(37) - end.should raise_error(MockExpectationError, "Mock 'test mock' expected :msg with (no args) but received it with (37)") - end - end - - describe "failing deprecated MockArgumentConstraints" do - before(:each) do - @mock = mock("test mock") - @reporter = Mock.new("reporter", :null_object => true) - Kernel.stub!(:warn) - end - - after(:each) do - @mock.rspec_reset - end - - it "should reject non boolean" do - @mock.should_receive(:random_call).with(:boolean) - lambda do - @mock.random_call("false") - end.should raise_error(MockExpectationError) - end - - it "should reject non numeric" do - @mock.should_receive(:random_call).with(:numeric) - lambda do - @mock.random_call("1") - end.should raise_error(MockExpectationError) - end - - it "should reject non string" do - @mock.should_receive(:random_call).with(:string) - lambda do - @mock.random_call(123) - end.should raise_error(MockExpectationError) - end - - - end - end -end diff --git a/vendor/gems/rspec/spec/spec/mocks/mock_ordering_spec.rb b/vendor/gems/rspec/spec/spec/mocks/mock_ordering_spec.rb deleted file mode 100644 index 919da2970b..0000000000 --- a/vendor/gems/rspec/spec/spec/mocks/mock_ordering_spec.rb +++ /dev/null @@ -1,84 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper' - -module Spec - module Mocks - - describe "Mock ordering" do - - before do - @mock = mock("test mock") - end - - after do - @mock.rspec_reset - end - - it "should pass two calls in order" do - @mock.should_receive(:one).ordered - @mock.should_receive(:two).ordered - @mock.one - @mock.two - @mock.rspec_verify - end - - it "should pass three calls in order" do - @mock.should_receive(:one).ordered - @mock.should_receive(:two).ordered - @mock.should_receive(:three).ordered - @mock.one - @mock.two - @mock.three - @mock.rspec_verify - end - - it "should fail if second call comes first" do - @mock.should_receive(:one).ordered - @mock.should_receive(:two).ordered - lambda do - @mock.two - end.should raise_error(MockExpectationError, "Mock 'test mock' received :two out of order") - end - - it "should fail if third call comes first" do - @mock.should_receive(:one).ordered - @mock.should_receive(:two).ordered - @mock.should_receive(:three).ordered - @mock.one - lambda do - @mock.three - end.should raise_error(MockExpectationError, "Mock 'test mock' received :three out of order") - end - - it "should fail if third call comes second" do - @mock.should_receive(:one).ordered - @mock.should_receive(:two).ordered - @mock.should_receive(:three).ordered - @mock.one - lambda do - @mock.three - end.should raise_error(MockExpectationError, "Mock 'test mock' received :three out of order") - end - - it "should ignore order of non ordered calls" do - @mock.should_receive(:ignored_0) - @mock.should_receive(:ordered_1).ordered - @mock.should_receive(:ignored_1) - @mock.should_receive(:ordered_2).ordered - @mock.should_receive(:ignored_2) - @mock.should_receive(:ignored_3) - @mock.should_receive(:ordered_3).ordered - @mock.should_receive(:ignored_4) - @mock.ignored_3 - @mock.ordered_1 - @mock.ignored_0 - @mock.ordered_2 - @mock.ignored_4 - @mock.ignored_2 - @mock.ordered_3 - @mock.ignored_1 - @mock.rspec_verify - end - - end - end -end diff --git a/vendor/gems/rspec/spec/spec/mocks/mock_space_spec.rb b/vendor/gems/rspec/spec/spec/mocks/mock_space_spec.rb deleted file mode 100644 index 23ffd01bc3..0000000000 --- a/vendor/gems/rspec/spec/spec/mocks/mock_space_spec.rb +++ /dev/null @@ -1,54 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' -require 'spec/mocks' - -module Spec - module Mocks - describe Space do - before :each do - @space = Space.new - klazz = Class.new do - def rspec_verify - @verified = true - end - def verified? - @verified - end - def rspec_reset - @reset = true - end - def reset? - @reset - end - end - @m1 = klazz.new - @m2 = klazz.new - end - it "should verify all mocks within" do - @space.add(@m1) - @space.add(@m2) - @space.verify_all - @m1.should be_verified - @m2.should be_verified - end - it "should reset all mocks within" do - @space.add(m1 = mock("mock1")) - @space.add(m2 = mock("mock2")) - m1.should_receive(:rspec_reset) - m2.should_receive(:rspec_reset) - @space.reset_all - end - it "should clear internal mocks on reset_all" do - @space.add(m = mock("mock")) - @space.reset_all - @space.instance_eval { mocks.empty? }.should be_true - end - it "should only add an instance once" do - @space.add(m1 = mock("mock1")) - @space.add(m1) - m1.should_receive(:rspec_verify) - @space.verify_all - end - end - end -end - diff --git a/vendor/gems/rspec/spec/spec/mocks/mock_spec.rb b/vendor/gems/rspec/spec/spec/mocks/mock_spec.rb deleted file mode 100644 index 85a71e327b..0000000000 --- a/vendor/gems/rspec/spec/spec/mocks/mock_spec.rb +++ /dev/null @@ -1,475 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper' - -module Spec - module Mocks - describe Mock do - - before(:each) do - @mock = mock("test mock") - end - - after(:each) do - @mock.rspec_reset - end - - it "should report line number of expectation of unreceived message" do - expected_error_line = __LINE__; @mock.should_receive(:wont_happen).with("x", 3) - begin - @mock.rspec_verify - violated - rescue MockExpectationError => e - # NOTE - this regexp ended w/ $, but jruby adds extra info at the end of the line - e.backtrace[0].should match(/#{File.basename(__FILE__)}:#{expected_error_line}/) - end - end - - it "should pass when not receiving message specified as not to be received" do - @mock.should_not_receive(:not_expected) - @mock.rspec_verify - end - - it "should pass when receiving message specified as not to be received with different args" do - @mock.should_not_receive(:message).with("unwanted text") - @mock.should_receive(:message).with("other text") - @mock.message "other text" - @mock.rspec_verify - end - - it "should fail when receiving message specified as not to be received" do - @mock.should_not_receive(:not_expected) - @mock.not_expected - lambda { - @mock.rspec_verify - violated - }.should raise_error(MockExpectationError, "Mock 'test mock' expected :not_expected with (any args) 0 times, but received it once") - end - - it "should fail when receiving message specified as not to be received with args" do - @mock.should_not_receive(:not_expected).with("unexpected text") - @mock.not_expected("unexpected text") - lambda { - @mock.rspec_verify - violated - }.should raise_error(MockExpectationError, "Mock 'test mock' expected :not_expected with (\"unexpected text\") 0 times, but received it once") - end - - it "should pass when receiving message specified as not to be received with wrong args" do - @mock.should_not_receive(:not_expected).with("unexpected text") - @mock.not_expected "really unexpected text" - @mock.rspec_verify - end - - it "should allow block to calculate return values" do - @mock.should_receive(:something).with("a","b","c").and_return { |a,b,c| c+b+a } - @mock.something("a","b","c").should == "cba" - @mock.rspec_verify - end - - it "should allow parameter as return value" do - @mock.should_receive(:something).with("a","b","c").and_return("booh") - @mock.something("a","b","c").should == "booh" - @mock.rspec_verify - end - - it "should return nil if no return value set" do - @mock.should_receive(:something).with("a","b","c") - @mock.something("a","b","c").should be_nil - @mock.rspec_verify - end - - it "should raise exception if args dont match when method called" do - @mock.should_receive(:something).with("a","b","c").and_return("booh") - lambda { - @mock.something("a","d","c") - violated - }.should raise_error(MockExpectationError, "Mock 'test mock' expected :something with (\"a\", \"b\", \"c\") but received it with (\"a\", \"d\", \"c\")") - end - - it "should fail if unexpected method called" do - lambda { - @mock.something("a","b","c") - violated - }.should raise_error(MockExpectationError, "Mock 'test mock' received unexpected message :something with (\"a\", \"b\", \"c\")") - end - - it "should use block for expectation if provided" do - @mock.should_receive(:something) do | a, b | - a.should == "a" - b.should == "b" - "booh" - end - @mock.something("a", "b").should == "booh" - @mock.rspec_verify - end - - it "should fail if expectation block fails" do - @mock.should_receive(:something) {| bool | bool.should be_true} - lambda { - @mock.something false - }.should raise_error(MockExpectationError, /Mock 'test mock' received :something but passed block failed with: expected true, got false/) - end - - it "should fail right away when method defined as never is received" do - pending "Used to pass (false positive). Which one is wrong, the spec or the actual behavior?" - - @mock.should_receive(:not_expected).never - lambda { - @mock.not_expected - }.should raise_error(MockExpectationError, "Mock 'test mock' expected :not_expected 0 times, but received it 1 times") - end - - it "should eventually fail when method defined as never is received" do - @mock.should_receive(:not_expected).never - @mock.not_expected - - lambda { - @mock.rspec_verify - }.should raise_error(MockExpectationError, "Mock 'test mock' expected :not_expected with (any args) 0 times, but received it once") - end - - it "should raise when told to" do - @mock.should_receive(:something).and_raise(RuntimeError) - lambda do - @mock.something - end.should raise_error(RuntimeError) - end - - it "should raise passed an Exception instance" do - error = RuntimeError.new("error message") - @mock.should_receive(:something).and_raise(error) - lambda { - @mock.something - }.should raise_error(RuntimeError, "error message") - end - - it "should raise RuntimeError with passed message" do - @mock.should_receive(:something).and_raise("error message") - lambda { - @mock.something - }.should raise_error(RuntimeError, "error message") - end - - it "should not raise when told to if args dont match" do - @mock.should_receive(:something).with(2).and_raise(RuntimeError) - lambda { - @mock.something 1 - }.should raise_error(MockExpectationError) - end - - it "should throw when told to" do - @mock.should_receive(:something).and_throw(:blech) - lambda { - @mock.something - }.should throw_symbol(:blech) - end - - it "should raise when explicit return and block constrained" do - lambda { - @mock.should_receive(:fruit) do |colour| - :strawberry - end.and_return :apple - }.should raise_error(AmbiguousReturnError) - end - - it "should ignore args on any args" do - @mock.should_receive(:something).at_least(:once).with(any_args) - @mock.something - @mock.something 1 - @mock.something "a", 2 - @mock.something [], {}, "joe", 7 - @mock.rspec_verify - end - - it "should fail on no args if any args received" do - @mock.should_receive(:something).with(no_args()) - lambda { - @mock.something 1 - }.should raise_error(MockExpectationError, "Mock 'test mock' expected :something with (no args) but received it with (1)") - end - - it "should fail when args are expected but none are received" do - @mock.should_receive(:something).with(1) - lambda { - @mock.something - }.should raise_error(MockExpectationError, "Mock 'test mock' expected :something with (1) but received it with (no args)") - end - - it "should yield 0 args to blocks that take a variable number of arguments" do - @mock.should_receive(:yield_back).with(no_args()).once.and_yield - a = nil - @mock.yield_back {|*a|} - a.should == [] - @mock.rspec_verify - end - - it "should yield 0 args multiple times to blocks that take a variable number of arguments" do - @mock.should_receive(:yield_back).once.with(no_args()).once.and_yield. - and_yield - a = nil - b = [] - @mock.yield_back {|*a| b << a} - b.should == [ [], [] ] - @mock.rspec_verify - end - - it "should yield one arg to blocks that take a variable number of arguments" do - @mock.should_receive(:yield_back).with(no_args()).once.and_yield(99) - a = nil - @mock.yield_back {|*a|} - a.should == [99] - @mock.rspec_verify - end - - it "should yield one arg 3 times consecutively to blocks that take a variable number of arguments" do - @mock.should_receive(:yield_back).once.with(no_args()).once.and_yield(99). - and_yield(43). - and_yield("something fruity") - a = nil - b = [] - @mock.yield_back {|*a| b << a} - b.should == [[99], [43], ["something fruity"]] - @mock.rspec_verify - end - - it "should yield many args to blocks that take a variable number of arguments" do - @mock.should_receive(:yield_back).with(no_args()).once.and_yield(99, 27, "go") - a = nil - @mock.yield_back {|*a|} - a.should == [99, 27, "go"] - @mock.rspec_verify - end - - it "should yield many args 3 times consecutively to blocks that take a variable number of arguments" do - @mock.should_receive(:yield_back).once.with(no_args()).once.and_yield(99, :green, "go"). - and_yield("wait", :amber). - and_yield("stop", 12, :red) - a = nil - b = [] - @mock.yield_back {|*a| b << a} - b.should == [[99, :green, "go"], ["wait", :amber], ["stop", 12, :red]] - @mock.rspec_verify - end - - it "should yield single value" do - @mock.should_receive(:yield_back).with(no_args()).once.and_yield(99) - a = nil - @mock.yield_back {|a|} - a.should == 99 - @mock.rspec_verify - end - - it "should yield single value 3 times consecutively" do - @mock.should_receive(:yield_back).once.with(no_args()).once.and_yield(99). - and_yield(43). - and_yield("something fruity") - a = nil - b = [] - @mock.yield_back {|a| b << a} - b.should == [99, 43, "something fruity"] - @mock.rspec_verify - end - - it "should yield two values" do - @mock.should_receive(:yield_back).with(no_args()).once.and_yield('wha', 'zup') - a, b = nil - @mock.yield_back {|a,b|} - a.should == 'wha' - b.should == 'zup' - @mock.rspec_verify - end - - it "should yield two values 3 times consecutively" do - @mock.should_receive(:yield_back).once.with(no_args()).once.and_yield('wha', 'zup'). - and_yield('not', 'down'). - and_yield(14, 65) - a, b = nil - c = [] - @mock.yield_back {|a,b| c << [a, b]} - c.should == [['wha', 'zup'], ['not', 'down'], [14, 65]] - @mock.rspec_verify - end - - it "should fail when calling yielding method with wrong arity" do - @mock.should_receive(:yield_back).with(no_args()).once.and_yield('wha', 'zup') - lambda { - @mock.yield_back {|a|} - }.should raise_error(MockExpectationError, "Mock 'test mock' yielded |\"wha\", \"zup\"| to block with arity of 1") - end - - it "should fail when calling yielding method consecutively with wrong arity" do - @mock.should_receive(:yield_back).once.with(no_args()).once.and_yield('wha', 'zup'). - and_yield('down'). - and_yield(14, 65) - lambda { - a, b = nil - c = [] - @mock.yield_back {|a,b| c << [a, b]} - }.should raise_error(MockExpectationError, "Mock 'test mock' yielded |\"down\"| to block with arity of 2") - end - - it "should fail when calling yielding method without block" do - @mock.should_receive(:yield_back).with(no_args()).once.and_yield('wha', 'zup') - lambda { - @mock.yield_back - }.should raise_error(MockExpectationError, "Mock 'test mock' asked to yield |[\"wha\", \"zup\"]| but no block was passed") - end - - it "should be able to mock send" do - @mock.should_receive(:send).with(any_args) - @mock.send 'hi' - @mock.rspec_verify - end - - it "should be able to raise from method calling yielding mock" do - @mock.should_receive(:yield_me).and_yield 44 - - lambda { - @mock.yield_me do |x| - raise "Bang" - end - }.should raise_error(StandardError, "Bang") - - @mock.rspec_verify - end - - it "should clear expectations after verify" do - @mock.should_receive(:foobar) - @mock.foobar - @mock.rspec_verify - lambda { - @mock.foobar - }.should raise_error(MockExpectationError, "Mock 'test mock' received unexpected message :foobar with (no args)") - end - - it "should restore objects to their original state on rspec_reset" do - mock = mock("this is a mock") - mock.should_receive(:blah) - mock.rspec_reset - mock.rspec_verify #should throw if reset didn't work - end - - it "should work even after method_missing starts raising NameErrors instead of NoMethodErrors" do - # Object#method_missing throws either NameErrors or NoMethodErrors. - # - # On a fresh ruby program Object#method_missing: - # * raises a NoMethodError when called directly - # * raises a NameError when called indirectly - # - # Once Object#method_missing has been called at least once (on any object) - # it starts behaving differently: - # * raises a NameError when called directly - # * raises a NameError when called indirectly - # - # There was a bug in Mock#method_missing that relied on the fact - # that calling Object#method_missing directly raises a NoMethodError. - # This example tests that the bug doesn't exist anymore. - - - # Ensures that method_missing always raises NameErrors. - a_method_that_doesnt_exist rescue - - - @mock.should_receive(:foobar) - @mock.foobar - @mock.rspec_verify - - lambda { @mock.foobar }.should_not raise_error(NameError) - lambda { @mock.foobar }.should raise_error(MockExpectationError) - end - - it "should temporarily replace a method stub on a mock" do - @mock.stub!(:msg).and_return(:stub_value) - @mock.should_receive(:msg).with(:arg).and_return(:mock_value) - @mock.msg(:arg).should equal(:mock_value) - @mock.msg.should equal(:stub_value) - @mock.msg.should equal(:stub_value) - @mock.rspec_verify - end - - it "should temporarily replace a method stub on a non-mock" do - non_mock = Object.new - non_mock.stub!(:msg).and_return(:stub_value) - non_mock.should_receive(:msg).with(:arg).and_return(:mock_value) - non_mock.msg(:arg).should equal(:mock_value) - non_mock.msg.should equal(:stub_value) - non_mock.msg.should equal(:stub_value) - non_mock.rspec_verify - end - - it "should assign stub return values" do - mock = Mock.new('name', :message => :response) - mock.message.should == :response - end - end - - describe "a mock message receiving a block" do - before(:each) do - @mock = mock("mock") - @calls = 0 - end - - def add_call - @calls = @calls + 1 - end - - it "should call the block after #should_receive" do - @mock.should_receive(:foo) { add_call } - - @mock.foo - - @calls.should == 1 - end - - it "should call the block after #once" do - @mock.should_receive(:foo).once { add_call } - - @mock.foo - - @calls.should == 1 - end - - it "should call the block after #twice" do - @mock.should_receive(:foo).twice { add_call } - - @mock.foo - @mock.foo - - @calls.should == 2 - end - - it "should call the block after #times" do - @mock.should_receive(:foo).exactly(10).times { add_call } - - (1..10).each { @mock.foo } - - @calls.should == 10 - end - - it "should call the block after #any_number_of_times" do - @mock.should_receive(:foo).any_number_of_times { add_call } - - (1..7).each { @mock.foo } - - @calls.should == 7 - end - - it "should call the block after #with" do - @mock.should_receive(:foo).with(:arg) { add_call } - - @mock.foo(:arg) - - @calls.should == 1 - end - - it "should call the block after #ordered" do - @mock.should_receive(:foo).ordered { add_call } - @mock.should_receive(:bar).ordered { add_call } - - @mock.foo - @mock.bar - - @calls.should == 2 - end - end - end -end diff --git a/vendor/gems/rspec/spec/spec/mocks/multiple_return_value_spec.rb b/vendor/gems/rspec/spec/spec/mocks/multiple_return_value_spec.rb deleted file mode 100644 index 3e26b73f44..0000000000 --- a/vendor/gems/rspec/spec/spec/mocks/multiple_return_value_spec.rb +++ /dev/null @@ -1,113 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper' - -module Spec - module Mocks - describe "a Mock expectation with multiple return values and no specified count" do - before(:each) do - @mock = Mock.new("mock") - @return_values = ["1",2,Object.new] - @mock.should_receive(:message).and_return(@return_values[0],@return_values[1],@return_values[2]) - end - - it "should return values in order to consecutive calls" do - @mock.message.should == @return_values[0] - @mock.message.should == @return_values[1] - @mock.message.should == @return_values[2] - @mock.rspec_verify - end - - it "should complain when there are too few calls" do - third = Object.new - @mock.message.should == @return_values[0] - @mock.message.should == @return_values[1] - lambda { @mock.rspec_verify }.should raise_error(MockExpectationError, "Mock 'mock' expected :message with (any args) 3 times, but received it twice") - end - - it "should complain when there are too many calls" do - third = Object.new - @mock.message.should == @return_values[0] - @mock.message.should == @return_values[1] - @mock.message.should == @return_values[2] - @mock.message.should == @return_values[2] - lambda { @mock.rspec_verify }.should raise_error(MockExpectationError, "Mock 'mock' expected :message with (any args) 3 times, but received it 4 times") - end - end - - describe "a Mock expectation with multiple return values with a specified count equal to the number of values" do - before(:each) do - @mock = Mock.new("mock") - @return_values = ["1",2,Object.new] - @mock.should_receive(:message).exactly(3).times.and_return(@return_values[0],@return_values[1],@return_values[2]) - end - - it "should return values in order to consecutive calls" do - @mock.message.should == @return_values[0] - @mock.message.should == @return_values[1] - @mock.message.should == @return_values[2] - @mock.rspec_verify - end - - it "should complain when there are too few calls" do - third = Object.new - @mock.message.should == @return_values[0] - @mock.message.should == @return_values[1] - lambda { @mock.rspec_verify }.should raise_error(MockExpectationError, "Mock 'mock' expected :message with (any args) 3 times, but received it twice") - end - - it "should complain when there are too many calls" do - third = Object.new - @mock.message.should == @return_values[0] - @mock.message.should == @return_values[1] - @mock.message.should == @return_values[2] - @mock.message.should == @return_values[2] - lambda { @mock.rspec_verify }.should raise_error(MockExpectationError, "Mock 'mock' expected :message with (any args) 3 times, but received it 4 times") - end - end - - describe "a Mock expectation with multiple return values specifying at_least less than the number of values" do - before(:each) do - @mock = Mock.new("mock") - @mock.should_receive(:message).at_least(:twice).with(no_args).and_return(11, 22) - end - - it "should use last return value for subsequent calls" do - @mock.message.should equal(11) - @mock.message.should equal(22) - @mock.message.should equal(22) - @mock.rspec_verify - end - - it "should fail when called less than the specified number" do - @mock.message.should equal(11) - lambda { @mock.rspec_verify }.should raise_error(MockExpectationError, "Mock 'mock' expected :message with (no args) twice, but received it once") - end - end - describe "a Mock expectation with multiple return values with a specified count larger than the number of values" do - before(:each) do - @mock = Mock.new("mock") - @mock.should_receive(:message).exactly(3).times.and_return(11, 22) - end - - it "should use last return value for subsequent calls" do - @mock.message.should equal(11) - @mock.message.should equal(22) - @mock.message.should equal(22) - @mock.rspec_verify - end - - it "should fail when called less than the specified number" do - @mock.message.should equal(11) - lambda { @mock.rspec_verify }.should raise_error(MockExpectationError, "Mock 'mock' expected :message with (any args) 3 times, but received it once") - end - - it "should fail when called greater than the specified number" do - @mock.message.should equal(11) - @mock.message.should equal(22) - @mock.message.should equal(22) - @mock.message.should equal(22) - lambda { @mock.rspec_verify }.should raise_error(MockExpectationError, "Mock 'mock' expected :message with (any args) 3 times, but received it 4 times") - end - end - end -end - diff --git a/vendor/gems/rspec/spec/spec/mocks/null_object_mock_spec.rb b/vendor/gems/rspec/spec/spec/mocks/null_object_mock_spec.rb deleted file mode 100644 index 57e8ca31cf..0000000000 --- a/vendor/gems/rspec/spec/spec/mocks/null_object_mock_spec.rb +++ /dev/null @@ -1,40 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -module Spec - module Mocks - describe "a mock acting as a NullObject" do - before(:each) do - @mock = Mock.new("null_object", :null_object => true) - end - - it "should allow explicit expectation" do - @mock.should_receive(:something) - @mock.something - end - - it "should fail verification when explicit exception not met" do - lambda do - @mock.should_receive(:something) - @mock.rspec_verify - end.should raise_error(MockExpectationError) - end - - it "should ignore unexpected methods" do - @mock.random_call("a", "d", "c") - @mock.rspec_verify - end - - it "should expected message with different args first" do - @mock.should_receive(:message).with(:expected_arg) - @mock.message(:unexpected_arg) - @mock.message(:expected_arg) - end - - it "should expected message with different args second" do - @mock.should_receive(:message).with(:expected_arg) - @mock.message(:expected_arg) - @mock.message(:unexpected_arg) - end - end - end -end diff --git a/vendor/gems/rspec/spec/spec/mocks/once_counts_spec.rb b/vendor/gems/rspec/spec/spec/mocks/once_counts_spec.rb deleted file mode 100644 index 2c15d5c2e3..0000000000 --- a/vendor/gems/rspec/spec/spec/mocks/once_counts_spec.rb +++ /dev/null @@ -1,53 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -module Spec - module Mocks - describe "OnceCounts" do - before(:each) do - @mock = mock("test mock") - end - - it "once should fail when called once with wrong args" do - @mock.should_receive(:random_call).once.with("a", "b", "c") - lambda do - @mock.random_call("d", "e", "f") - end.should raise_error(MockExpectationError) - @mock.rspec_reset - end - - it "once should fail when called twice" do - @mock.should_receive(:random_call).once - @mock.random_call - @mock.random_call - lambda do - @mock.rspec_verify - end.should raise_error(MockExpectationError) - end - - it "once should fail when not called" do - @mock.should_receive(:random_call).once - lambda do - @mock.rspec_verify - end.should raise_error(MockExpectationError) - end - - it "once should pass when called once" do - @mock.should_receive(:random_call).once - @mock.random_call - @mock.rspec_verify - end - - it "once should pass when called once with specified args" do - @mock.should_receive(:random_call).once.with("a", "b", "c") - @mock.random_call("a", "b", "c") - @mock.rspec_verify - end - - it "once should pass when called once with unspecified args" do - @mock.should_receive(:random_call).once - @mock.random_call("a", "b", "c") - @mock.rspec_verify - end - end - end -end diff --git a/vendor/gems/rspec/spec/spec/mocks/options_hash_spec.rb b/vendor/gems/rspec/spec/spec/mocks/options_hash_spec.rb deleted file mode 100644 index 0bfab26d75..0000000000 --- a/vendor/gems/rspec/spec/spec/mocks/options_hash_spec.rb +++ /dev/null @@ -1,45 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -module Spec - module Mocks - describe "calling :should_receive with an options hash" do - it_should_behave_like "sandboxed rspec_options" - attr_reader :reporter, :example_group - before do - @reporter = ::Spec::Runner::Reporter.new(options) - @example_group = Class.new(::Spec::Example::ExampleGroup) do - plugin_mock_framework - describe("Some Examples") - end - reporter.add_example_group example_group - end - - it "should report the file and line submitted with :expected_from" do - example_definition = example_group.it "spec" do - mock = Spec::Mocks::Mock.new("a mock") - mock.should_receive(:message, :expected_from => "/path/to/blah.ext:37") - mock.rspec_verify - end - example = example_group.new(example_definition) - - reporter.should_receive(:example_finished) do |spec, error| - error.backtrace.detect {|line| line =~ /\/path\/to\/blah.ext:37/}.should_not be_nil - end - example.execute(options, {}) - end - - it "should use the message supplied with :message" do - example_definition = @example_group.it "spec" do - mock = Spec::Mocks::Mock.new("a mock") - mock.should_receive(:message, :message => "recebi nada") - mock.rspec_verify - end - example = @example_group.new(example_definition) - @reporter.should_receive(:example_finished) do |spec, error| - error.message.should == "recebi nada" - end - example.execute(@options, {}) - end - end - end -end diff --git a/vendor/gems/rspec/spec/spec/mocks/partial_mock_spec.rb b/vendor/gems/rspec/spec/spec/mocks/partial_mock_spec.rb deleted file mode 100644 index d7e5944c4e..0000000000 --- a/vendor/gems/rspec/spec/spec/mocks/partial_mock_spec.rb +++ /dev/null @@ -1,106 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -module Spec - module Mocks - describe "using a Partial Mock," do - before(:each) do - @object = Object.new - end - - it "should name the class in the failure message" do - @object.should_receive(:foo) - lambda do - @object.rspec_verify - end.should raise_error(Spec::Mocks::MockExpectationError, /Object/) - end - - it "should not conflict with @options in the object" do - @object.instance_eval { @options = Object.new } - @object.should_receive(:blah) - @object.blah - end - - it "should_not_receive should mock out the method" do - @object.should_not_receive(:fuhbar) - @object.fuhbar - lambda do - @object.rspec_verify - end.should raise_error(Spec::Mocks::MockExpectationError) - end - - it "should_not_receive should return a negative message expectation" do - @object.should_not_receive(:foobar).should be_kind_of(NegativeMessageExpectation) - end - - it "should_receive should mock out the method" do - @object.should_receive(:foobar).with(:test_param).and_return(1) - @object.foobar(:test_param).should equal(1) - end - - it "should_receive should handle a hash" do - @object.should_receive(:foobar).with(:key => "value").and_return(1) - @object.foobar(:key => "value").should equal(1) - end - - it "should_receive should handle an inner hash" do - hash = {:a => {:key => "value"}} - @object.should_receive(:foobar).with(:key => "value").and_return(1) - @object.foobar(hash[:a]).should equal(1) - end - - it "should_receive should return a message expectation" do - @object.should_receive(:foobar).should be_kind_of(MessageExpectation) - @object.foobar - end - - it "should_receive should verify method was called" do - @object.should_receive(:foobar).with(:test_param).and_return(1) - lambda do - @object.rspec_verify - end.should raise_error(Spec::Mocks::MockExpectationError) - end - - it "should_receive should also take a String argument" do - @object.should_receive('foobar') - @object.foobar - end - - it "should_not_receive should also take a String argument" do - @object.should_not_receive('foobar') - @object.foobar - lambda do - @object.rspec_verify - end.should raise_error(Spec::Mocks::MockExpectationError) - end - - it "should use report nil in the error message" do - @this_will_resolve_to_nil.should_receive(:foobar) - lambda do - @this_will_resolve_to_nil.rspec_verify - end.should raise_error(Spec::Mocks::MockExpectationError, /NilClass.*expected :foobar with/) - end - end - - describe "Partially mocking an object that defines ==, after another mock has been defined" do - before(:each) do - stub("existing mock", :foo => :foo) - end - - class PartiallyMockedEquals - attr_reader :val - def initialize(val) - @val = val - end - - def ==(other) - @val == other.val - end - end - - it "should not raise an error when stubbing the object" do - o = PartiallyMockedEquals.new :foo - lambda { o.stub!(:bar) }.should_not raise_error(NoMethodError) - end - end - end -end diff --git a/vendor/gems/rspec/spec/spec/mocks/partial_mock_using_mocks_directly_spec.rb b/vendor/gems/rspec/spec/spec/mocks/partial_mock_using_mocks_directly_spec.rb deleted file mode 100644 index c857d83801..0000000000 --- a/vendor/gems/rspec/spec/spec/mocks/partial_mock_using_mocks_directly_spec.rb +++ /dev/null @@ -1,66 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -module Spec -module Mocks -describe "PartialMockUsingMocksDirectly" do - before(:each) do - - klass=Class.new - klass.class_eval do - def existing_method - :original_value - end - end - @obj = klass.new - - end - - # See http://rubyforge.org/tracker/index.php?func=detail&aid=10263&group_id=797&atid=3149 - # specify "should clear expectations on verify" do - # @obj.should_receive(:msg) - # @obj.msg - # @obj.rspec_verify - # lambda do - # @obj.msg - # end.should raise_error(NoMethodError) - # - # end - it "should fail when expected message is not received" do - @obj.should_receive(:msg) - lambda do - @obj.rspec_verify - end.should raise_error(MockExpectationError) - - end - it "should fail when message is received with incorrect args" do - @obj.should_receive(:msg).with(:correct_arg) - lambda do - @obj.msg(:incorrect_arg) - end.should raise_error(MockExpectationError) - @obj.msg(:correct_arg) - - end - it "should pass when expected message is received" do - @obj.should_receive(:msg) - @obj.msg - @obj.rspec_verify - - end - it "should pass when message is received with correct args" do - @obj.should_receive(:msg).with(:correct_arg) - @obj.msg(:correct_arg) - @obj.rspec_verify - - end - it "should revert to original method if existed" do - @obj.existing_method.should equal(:original_value) - @obj.should_receive(:existing_method).and_return(:mock_value) - @obj.existing_method.should equal(:mock_value) - @obj.rspec_verify - @obj.existing_method.should equal(:original_value) - - end - -end -end -end diff --git a/vendor/gems/rspec/spec/spec/mocks/passing_mock_argument_constraints_spec.rb b/vendor/gems/rspec/spec/spec/mocks/passing_mock_argument_constraints_spec.rb deleted file mode 100644 index 6de0a58f48..0000000000 --- a/vendor/gems/rspec/spec/spec/mocks/passing_mock_argument_constraints_spec.rb +++ /dev/null @@ -1,154 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -module Spec - module Mocks - describe "mock argument constraints", :shared => true do - before(:each) do - @mock = Mock.new("test mock") - Kernel.stub!(:warn) - end - - after(:each) do - @mock.rspec_verify - end - end - - describe Methods, "handling argument constraints with DEPRECATED symbols" do - it_should_behave_like "mock argument constraints" - - it "should accept true as boolean" do - @mock.should_receive(:random_call).with(:boolean) - @mock.random_call(true) - end - - it "should accept false as boolean" do - @mock.should_receive(:random_call).with(:boolean) - @mock.random_call(false) - end - - it "should accept fixnum as numeric" do - @mock.should_receive(:random_call).with(:numeric) - @mock.random_call(1) - end - - it "should accept float as numeric" do - @mock.should_receive(:random_call).with(:numeric) - @mock.random_call(1.5) - end - - it "should accept string as anything" do - @mock.should_receive(:random_call).with("a", :anything, "c") - @mock.random_call("a", "whatever", "c") - end - - it "should match string" do - @mock.should_receive(:random_call).with(:string) - @mock.random_call("a string") - end - - it "should match no args against any_args" do - @mock.should_receive(:random_call).with(:any_args) - @mock.random_call("a string") - end - - it "should match no args against no_args" do - @mock.should_receive(:random_call).with(:no_args) - @mock.random_call - end - end - - describe Methods, "handling argument constraints" do - it_should_behave_like "mock argument constraints" - - it "should accept true as boolean()" do - @mock.should_receive(:random_call).with(boolean()) - @mock.random_call(true) - end - - it "should accept false as boolean()" do - @mock.should_receive(:random_call).with(boolean()) - @mock.random_call(false) - end - - it "should accept fixnum as an_instance_of(Numeric)" do - @mock.should_receive(:random_call).with(an_instance_of(Numeric)) - @mock.random_call(1) - end - - it "should accept float as an_instance_of(Numeric)" do - @mock.should_receive(:random_call).with(an_instance_of(Numeric)) - @mock.random_call(1.5) - end - - it "should accept string as anything()" do - @mock.should_receive(:random_call).with("a", anything(), "c") - @mock.random_call("a", "whatever", "c") - end - - it "should match duck type with one method" do - @mock.should_receive(:random_call).with(duck_type(:length)) - @mock.random_call([]) - end - - it "should match duck type with two methods" do - @mock.should_receive(:random_call).with(duck_type(:abs, :div)) - @mock.random_call(1) - end - - it "should match no args against any_args()" do - @mock.should_receive(:random_call).with(any_args) - @mock.random_call() - end - - it "should match one arg against any_args()" do - @mock.should_receive(:random_call).with(any_args) - @mock.random_call("a string") - end - - it "should match no args against no_args()" do - @mock.should_receive(:random_call).with(no_args) - @mock.random_call() - end - end - - describe Methods, "handling non-constraint arguments" do - - it "should match non special symbol (can be removed when deprecated symbols are removed)" do - @mock.should_receive(:random_call).with(:some_symbol) - @mock.random_call(:some_symbol) - end - - it "should match string against regexp" do - @mock.should_receive(:random_call).with(/bcd/) - @mock.random_call("abcde") - end - - it "should match regexp against regexp" do - @mock.should_receive(:random_call).with(/bcd/) - @mock.random_call(/bcd/) - end - - it "should match against a hash submitted and received by value" do - @mock.should_receive(:random_call).with(:a => "a", :b => "b") - @mock.random_call(:a => "a", :b => "b") - end - - it "should match against a hash submitted by reference and received by value" do - opts = {:a => "a", :b => "b"} - @mock.should_receive(:random_call).with(opts) - @mock.random_call(:a => "a", :b => "b") - end - - it "should match against a hash submitted by value and received by reference" do - opts = {:a => "a", :b => "b"} - @mock.should_receive(:random_call).with(:a => "a", :b => "b") - @mock.random_call(opts) - end - - it "should match against a Matcher" do - @mock.should_receive(:msg).with(equal(37)) - @mock.msg(37) - end - end - end -end diff --git a/vendor/gems/rspec/spec/spec/mocks/precise_counts_spec.rb b/vendor/gems/rspec/spec/spec/mocks/precise_counts_spec.rb deleted file mode 100644 index ba3898943c..0000000000 --- a/vendor/gems/rspec/spec/spec/mocks/precise_counts_spec.rb +++ /dev/null @@ -1,52 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -module Spec - module Mocks - describe "PreciseCounts" do - before(:each) do - @mock = mock("test mock") - end - - it "should fail when exactly n times method is called less than n times" do - @mock.should_receive(:random_call).exactly(3).times - @mock.random_call - @mock.random_call - lambda do - @mock.rspec_verify - end.should raise_error(MockExpectationError) - end - - it "should fail when exactly n times method is never called" do - @mock.should_receive(:random_call).exactly(3).times - lambda do - @mock.rspec_verify - end.should raise_error(MockExpectationError) - end - - it "should pass if exactly n times method is called exactly n times" do - @mock.should_receive(:random_call).exactly(3).times - @mock.random_call - @mock.random_call - @mock.random_call - @mock.rspec_verify - end - - it "should pass multiple calls with different args and counts" do - @mock.should_receive(:random_call).twice.with(1) - @mock.should_receive(:random_call).once.with(2) - @mock.random_call(1) - @mock.random_call(2) - @mock.random_call(1) - @mock.rspec_verify - end - - it "should pass mutiple calls with different args" do - @mock.should_receive(:random_call).once.with(1) - @mock.should_receive(:random_call).once.with(2) - @mock.random_call(1) - @mock.random_call(2) - @mock.rspec_verify - end - end - end -end diff --git a/vendor/gems/rspec/spec/spec/mocks/record_messages_spec.rb b/vendor/gems/rspec/spec/spec/mocks/record_messages_spec.rb deleted file mode 100644 index ec247726dd..0000000000 --- a/vendor/gems/rspec/spec/spec/mocks/record_messages_spec.rb +++ /dev/null @@ -1,26 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper' - -module Spec - module Mocks - describe "a mock" do - before(:each) do - @mock = mock("mock", :null_object => true) - end - it "should answer false for received_message? when no messages received" do - @mock.received_message?(:message).should be_false - end - it "should answer true for received_message? when message received" do - @mock.message - @mock.received_message?(:message).should be_true - end - it "should answer true for received_message? when message received with correct args" do - @mock.message 1,2,3 - @mock.received_message?(:message, 1,2,3).should be_true - end - it "should answer false for received_message? when message received with incorrect args" do - @mock.message 1,2,3 - @mock.received_message?(:message, 1,2).should be_false - end - end - end -end diff --git a/vendor/gems/rspec/spec/spec/mocks/stub_spec.rb b/vendor/gems/rspec/spec/spec/mocks/stub_spec.rb deleted file mode 100644 index d6e23d71e3..0000000000 --- a/vendor/gems/rspec/spec/spec/mocks/stub_spec.rb +++ /dev/null @@ -1,181 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -module Spec - module Mocks - describe "A method stub" do - before(:each) do - @class = Class.new do - def self.existing_class_method - :original_value - end - - def existing_instance_method - :original_value - end - end - @instance = @class.new - end - - it "should return expected value when expected message is received" do - @instance.stub!(:msg).and_return(:return_value) - @instance.msg.should equal(:return_value) - @instance.rspec_verify - end - - it "should ignore when expected message is received" do - @instance.stub!(:msg) - @instance.msg - lambda do - @instance.rspec_verify - end.should_not raise_error - end - - it "should ignore when message is received with args" do - @instance.stub!(:msg) - @instance.msg(:an_arg) - lambda do - @instance.rspec_verify - end.should_not raise_error - end - - it "should ignore when expected message is not received" do - @instance.stub!(:msg) - lambda do - @instance.rspec_verify - end.should_not raise_error - end - - it "should clear itself when verified" do - @instance.stub!(:this_should_go).and_return(:blah) - @instance.this_should_go.should == :blah - @instance.rspec_verify - lambda do - @instance.this_should_go - end.should raise_error(NameError) - end - - it "should return values in order to consecutive calls" do - return_values = ["1",2,Object.new] - @instance.stub!(:msg).and_return(return_values[0],return_values[1],return_values[2]) - @instance.msg.should == return_values[0] - @instance.msg.should == return_values[1] - @instance.msg.should == return_values[2] - end - - it "should keep returning last value in consecutive calls" do - return_values = ["1",2,Object.new] - @instance.stub!(:msg).and_return(return_values[0],return_values[1],return_values[2]) - @instance.msg.should == return_values[0] - @instance.msg.should == return_values[1] - @instance.msg.should == return_values[2] - @instance.msg.should == return_values[2] - @instance.msg.should == return_values[2] - end - - it "should revert to original instance method if there is one" do - @instance.existing_instance_method.should equal(:original_value) - @instance.stub!(:existing_instance_method).and_return(:mock_value) - @instance.existing_instance_method.should equal(:mock_value) - @instance.rspec_verify - @instance.existing_instance_method.should equal(:original_value) - end - - it "should revert to original class method if there is one" do - @class.existing_class_method.should equal(:original_value) - @class.stub!(:existing_class_method).and_return(:mock_value) - @class.existing_class_method.should equal(:mock_value) - @class.rspec_verify - @class.existing_class_method.should equal(:original_value) - end - - it "should yield a specified object" do - @instance.stub!(:method_that_yields).and_yield(:yielded_obj) - current_value = :value_before - @instance.method_that_yields {|val| current_value = val} - current_value.should == :yielded_obj - @instance.rspec_verify - end - - it "should yield multiple times with multiple calls to and_yield" do - @instance.stub!(:method_that_yields_multiple_times).and_yield(:yielded_value). - and_yield(:another_value) - current_value = [] - @instance.method_that_yields_multiple_times {|val| current_value << val} - current_value.should == [:yielded_value, :another_value] - @instance.rspec_verify - end - - it "should yield a specified object and return another specified object" do - yielded_obj = mock("my mock") - yielded_obj.should_receive(:foo).with(:bar) - @instance.stub!(:method_that_yields_and_returns).and_yield(yielded_obj).and_return(:baz) - @instance.method_that_yields_and_returns { |o| o.foo :bar }.should == :baz - end - - it "should throw when told to" do - @mock.stub!(:something).and_throw(:up) - lambda do - @mock.something - end.should throw_symbol(:up) - end - - it "should override a pre-existing stub" do - @stub.stub!(:existing_instance_method).and_return(:updated_stub_value) - @stub.existing_instance_method.should == :updated_stub_value - end - - it "should limit " do - @stub.stub!(:foo).with("bar") - @stub.should_receive(:foo).with("baz") - @stub.foo("bar") - @stub.foo("baz") - end - end - - describe "A method stub with args" do - before(:each) do - @stub = Object.new - @stub.stub!(:foo).with("bar") - end - - it "should not complain if not called" do - end - - it "should not complain if called with arg" do - @stub.foo("bar") - end - - it "should complain if called with no arg" do - lambda do - @stub.foo - end.should raise_error - end - - it "should complain if called with other arg" do - lambda do - @stub.foo("other") - end.should raise_error - end - - it "should not complain if also mocked w/ different args" do - @stub.should_receive(:foo).with("baz") - @stub.foo("bar") - @stub.foo("baz") - end - - it "should complain if also mocked w/ different args AND called w/ a 3rd set of args" do - @stub.should_receive(:foo).with("baz") - @stub.foo("bar") - @stub.foo("baz") - lambda do - @stub.foo("other") - end.should raise_error - end - - it "should support options" do - @stub.stub!(:foo, :expected_from => "bar") - end - end - - end -end diff --git a/vendor/gems/rspec/spec/spec/mocks/twice_counts_spec.rb b/vendor/gems/rspec/spec/spec/mocks/twice_counts_spec.rb deleted file mode 100644 index d07e45736a..0000000000 --- a/vendor/gems/rspec/spec/spec/mocks/twice_counts_spec.rb +++ /dev/null @@ -1,67 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -module Spec - module Mocks - describe "TwiceCounts" do - before(:each) do - @mock = mock("test mock") - end - - it "twice should fail when call count is higher than expected" do - @mock.should_receive(:random_call).twice - @mock.random_call - @mock.random_call - @mock.random_call - lambda do - @mock.rspec_verify - end.should raise_error(MockExpectationError) - end - - it "twice should fail when call count is lower than expected" do - @mock.should_receive(:random_call).twice - @mock.random_call - lambda do - @mock.rspec_verify - end.should raise_error(MockExpectationError) - end - - it "twice should fail when called twice with wrong args on the first call" do - @mock.should_receive(:random_call).twice.with("1", 1) - lambda do - @mock.random_call(1, "1") - end.should raise_error(MockExpectationError) - @mock.rspec_reset - end - - it "twice should fail when called twice with wrong args on the second call" do - @mock.should_receive(:random_call).twice.with("1", 1) - @mock.random_call("1", 1) - lambda do - @mock.random_call(1, "1") - end.should raise_error(MockExpectationError) - @mock.rspec_reset - end - - it "twice should pass when called twice" do - @mock.should_receive(:random_call).twice - @mock.random_call - @mock.random_call - @mock.rspec_verify - end - - it "twice should pass when called twice with specified args" do - @mock.should_receive(:random_call).twice.with("1", 1) - @mock.random_call("1", 1) - @mock.random_call("1", 1) - @mock.rspec_verify - end - - it "twice should pass when called twice with unspecified args" do - @mock.should_receive(:random_call).twice - @mock.random_call("1") - @mock.random_call(1) - @mock.rspec_verify - end - end - end -end diff --git a/vendor/gems/rspec/spec/spec/package/bin_spec_spec.rb b/vendor/gems/rspec/spec/spec/package/bin_spec_spec.rb deleted file mode 100644 index 44bfd96a08..0000000000 --- a/vendor/gems/rspec/spec/spec/package/bin_spec_spec.rb +++ /dev/null @@ -1,14 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper' -require File.dirname(__FILE__) + '/../../ruby_forker' - -describe "The bin/spec script" do - include RubyForker - - it "should have no warnings" do - pending "Hangs on JRuby" if PLATFORM =~ /java/ - spec_path = "#{File.dirname(__FILE__)}/../../../bin/spec" - - output = ruby "-w #{spec_path} --help 2>&1" - output.should_not =~ /warning/n - end -end diff --git a/vendor/gems/rspec/spec/spec/runner/class_and_argument_parser_spec.rb b/vendor/gems/rspec/spec/spec/runner/class_and_argument_parser_spec.rb deleted file mode 100644 index b4e9e7f535..0000000000 --- a/vendor/gems/rspec/spec/spec/runner/class_and_argument_parser_spec.rb +++ /dev/null @@ -1,23 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -module Spec - module Runner - describe ClassAndArgumentsParser, ".parse" do - - it "should use a single : to separate class names from arguments" do - ClassAndArgumentsParser.parse('Foo').should == ['Foo', nil] - ClassAndArgumentsParser.parse('Foo:arg').should == ['Foo', 'arg'] - ClassAndArgumentsParser.parse('Foo::Bar::Zap:arg').should == ['Foo::Bar::Zap', 'arg'] - ClassAndArgumentsParser.parse('Foo:arg1,arg2').should == ['Foo', 'arg1,arg2'] - ClassAndArgumentsParser.parse('Foo::Bar::Zap:arg1,arg2').should == ['Foo::Bar::Zap', 'arg1,arg2'] - ClassAndArgumentsParser.parse('Foo::Bar::Zap:drb://foo,drb://bar').should == ['Foo::Bar::Zap', 'drb://foo,drb://bar'] - end - - it "should raise an error when passed an empty string" do - lambda do - ClassAndArgumentsParser.parse('') - end.should raise_error("Couldn't parse \"\"") - end - end - end -end diff --git a/vendor/gems/rspec/spec/spec/runner/command_line_spec.rb b/vendor/gems/rspec/spec/spec/runner/command_line_spec.rb deleted file mode 100644 index 3c3be8ceaa..0000000000 --- a/vendor/gems/rspec/spec/spec/runner/command_line_spec.rb +++ /dev/null @@ -1,147 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -module Spec - module Runner - describe CommandLine, ".run" do - it_should_behave_like "sandboxed rspec_options" - attr_reader :options, :err, :out - before do - @err = options.error_stream - @out = options.output_stream - end - - it "should run directory" do - file = File.dirname(__FILE__) + '/../../../examples/pure' - Spec::Runner::CommandLine.run(OptionParser.parse([file,"-p","**/*.rb"], @err, @out)) - - @out.rewind - @out.read.should =~ /\d+ examples, 0 failures, 3 pending/n - end - - it "should run file" do - file = File.dirname(__FILE__) + '/../../../failing_examples/predicate_example.rb' - Spec::Runner::CommandLine.run(OptionParser.parse([file], @err, @out)) - - @out.rewind - @out.read.should =~ /2 examples, 1 failure/n - end - - it "should raise when file does not exist" do - file = File.dirname(__FILE__) + '/doesntexist' - - lambda { - Spec::Runner::CommandLine.run(OptionParser.parse([file], @err, @out)) - }.should raise_error - end - - it "should return true when in --generate-options mode" do - # NOTE - this used to say /dev/null but jruby hangs on that for some reason - Spec::Runner::CommandLine.run( - OptionParser.parse(['--generate-options', '/tmp/foo'], @err, @out) - ).should be_true - end - - it "should dump even if Interrupt exception is occurred" do - example_group = Class.new(::Spec::Example::ExampleGroup) do - describe("example_group") - it "no error" do - end - - it "should interrupt" do - raise Interrupt, "I'm interrupting" - end - end - - options = ::Spec::Runner::Options.new(@err, @out) - ::Spec::Runner::Options.should_receive(:new).with(@err, @out).and_return(options) - options.reporter.should_receive(:dump) - options.add_example_group(example_group) - - Spec::Runner::CommandLine.run(OptionParser.parse([], @err, @out)) - end - - it "should heckle when options have heckle_runner" do - example_group = Class.new(::Spec::Example::ExampleGroup).describe("example_group") do - it "no error" do - end - end - options = ::Spec::Runner::Options.new(@err, @out) - ::Spec::Runner::Options.should_receive(:new).with(@err, @out).and_return(options) - options.add_example_group example_group - - heckle_runner = mock("heckle_runner") - heckle_runner.should_receive(:heckle_with) - $rspec_mocks.__send__(:mocks).delete(heckle_runner) - - options.heckle_runner = heckle_runner - options.add_example_group(example_group) - - Spec::Runner::CommandLine.run(OptionParser.parse([], @err, @out)) - heckle_runner.rspec_verify - end - - it "should run examples backwards if options.reverse is true" do - options = ::Spec::Runner::Options.new(@err, @out) - ::Spec::Runner::Options.should_receive(:new).with(@err, @out).and_return(options) - options.reverse = true - - b1 = Class.new(Spec::Example::ExampleGroup) - b2 = Class.new(Spec::Example::ExampleGroup) - - b2.should_receive(:run).ordered - b1.should_receive(:run).ordered - - options.add_example_group(b1) - options.add_example_group(b2) - - Spec::Runner::CommandLine.run(OptionParser.parse([], @err, @out)) - end - - it "should pass its ExampleGroup to the reporter" do - example_group = Class.new(::Spec::Example::ExampleGroup).describe("example_group") do - it "should" do - end - end - options = ::Spec::Runner::Options.new(@err, @out) - options.add_example_group(example_group) - - ::Spec::Runner::Options.should_receive(:new).with(@err, @out).and_return(options) - options.reporter.should_receive(:add_example_group).with(example_group) - - Spec::Runner::CommandLine.run(OptionParser.parse([], @err, @out)) - end - - it "runs only selected Examples when options.examples is set" do - options = ::Spec::Runner::Options.new(@err, @out) - ::Spec::Runner::Options.should_receive(:new).with(@err, @out).and_return(options) - - options.examples << "example_group should" - should_has_run = false - should_not_has_run = false - example_group = Class.new(::Spec::Example::ExampleGroup).describe("example_group") do - it "should" do - should_has_run = true - end - it "should not" do - should_not_has_run = true - end - end - - options.reporter.should_receive(:add_example_group).with(example_group) - - options.add_example_group example_group - Spec::Runner::CommandLine.run(OptionParser.parse([], @err, @out)) - - should_has_run.should be_true - should_not_has_run.should be_false - end - - it "sets Spec.run to true" do - ::Spec.run = false - ::Spec.should_not be_run - Spec::Runner::CommandLine.run(OptionParser.parse([], @err, @out)) - ::Spec.should be_run - end - end - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/runner/drb_command_line_spec.rb b/vendor/gems/rspec/spec/spec/runner/drb_command_line_spec.rb deleted file mode 100644 index 760ec37a9a..0000000000 --- a/vendor/gems/rspec/spec/spec/runner/drb_command_line_spec.rb +++ /dev/null @@ -1,92 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -module Spec - module Runner - describe DrbCommandLine, "without running local server" do - unless Config::CONFIG['ruby_install_name'] == 'jruby' - it "should print error when there is no running local server" do - err = StringIO.new - out = StringIO.new - DrbCommandLine.run(OptionParser.parse(['--version'], err, out)) - - err.rewind - err.read.should =~ /No server is running/ - end - end - end - - class DrbCommandLineSpec < ::Spec::Example::ExampleGroup - describe DrbCommandLine, "with local server" - - class CommandLineForSpec - def self.run(argv, stderr, stdout) - exit Spec::Runner::CommandLine.run(OptionParser.parse(argv, stderr, stdout)) - end - end - - unless Config::CONFIG['ruby_install_name'] == 'jruby' - before(:all) do - DRb.start_service("druby://localhost:8989", CommandLineForSpec) - @@drb_example_file_counter = 0 - end - - before(:each) do - create_dummy_spec_file - @@drb_example_file_counter = @@drb_example_file_counter + 1 - end - - after(:each) do - File.delete(@dummy_spec_filename) - end - - after(:all) do - DRb.stop_service - end - - it "should run against local server" do - out = run_spec_via_druby(['--version']) - out.should =~ /RSpec/n - end - - it "should output green colorized text when running with --colour option" do - out = run_spec_via_druby(["--colour", @dummy_spec_filename]) - out.should =~ /\e\[32m/n - end - - it "should output red colorized text when running with -c option" do - out = run_spec_via_druby(["-c", @dummy_spec_filename]) - out.should =~ /\e\[31m/n - end - - def create_dummy_spec_file - @dummy_spec_filename = File.expand_path(File.dirname(__FILE__)) + "/_dummy_spec#{@@drb_example_file_counter}.rb" - File.open(@dummy_spec_filename, 'w') do |f| - f.write %{ - describe "DUMMY CONTEXT for 'DrbCommandLine with -c option'" do - it "should be output with green bar" do - true.should be_true - end - - it "should be output with red bar" do - violated("I want to see a red bar!") - end - end - } - end - end - - def run_spec_via_druby(argv) - err, out = StringIO.new, StringIO.new - out.instance_eval do - def tty?; true end - end - options = ::Spec::Runner::Options.new(err, out) - options.argv = argv - Spec::Runner::DrbCommandLine.run(options) - out.rewind; out.read - end - end - - end - end -end diff --git a/vendor/gems/rspec/spec/spec/runner/empty_file.txt b/vendor/gems/rspec/spec/spec/runner/empty_file.txt deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/vendor/gems/rspec/spec/spec/runner/examples.txt b/vendor/gems/rspec/spec/spec/runner/examples.txt deleted file mode 100644 index 2fcbd355d6..0000000000 --- a/vendor/gems/rspec/spec/spec/runner/examples.txt +++ /dev/null @@ -1,2 +0,0 @@ -Sir, if you were my husband, I would poison your drink. -Madam, if you were my wife, I would drink it. \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/runner/execution_context_spec.rb b/vendor/gems/rspec/spec/spec/runner/execution_context_spec.rb deleted file mode 100644 index 82e7447c1e..0000000000 --- a/vendor/gems/rspec/spec/spec/runner/execution_context_spec.rb +++ /dev/null @@ -1,31 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -describe "ExecutionContext" do - - it "should provide duck_type()" do - dt = duck_type(:length) - dt.should be_an_instance_of(Spec::Mocks::DuckTypeArgConstraint) - dt.matches?([]).should be_true - end - - it "should violate when violated()" do - lambda do - violated - end.should raise_error(Spec::Expectations::ExpectationNotMetError) - end - - it "should provide mock()" do - mock("thing").should be_an_instance_of(Spec::Mocks::Mock) - end - - it "should provide stub()" do - thing_stub = stub("thing").should be_an_instance_of(Spec::Mocks::Mock) - end - - it "should add method stubs to stub()" do - thing_stub = stub("thing", :a => "A", :b => "B") - thing_stub.a.should == "A" - thing_stub.b.should == "B" - end - -end diff --git a/vendor/gems/rspec/spec/spec/runner/failed.txt b/vendor/gems/rspec/spec/spec/runner/failed.txt deleted file mode 100644 index 07c5442cfe..0000000000 --- a/vendor/gems/rspec/spec/spec/runner/failed.txt +++ /dev/null @@ -1,3 +0,0 @@ -heckler_spec.rb -command_line_spec.rb -reporter_spec.rb \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/runner/formatter/failing_example_groups_formatter_spec.rb b/vendor/gems/rspec/spec/spec/runner/formatter/failing_example_groups_formatter_spec.rb deleted file mode 100644 index a08b6e86d5..0000000000 --- a/vendor/gems/rspec/spec/spec/runner/formatter/failing_example_groups_formatter_spec.rb +++ /dev/null @@ -1,44 +0,0 @@ -require File.dirname(__FILE__) + '/../../../spec_helper' -require 'spec/runner/formatter/failing_example_groups_formatter' - -module Spec - module Runner - module Formatter - describe FailingExampleGroupsFormatter do - attr_reader :example_group, :formatter, :io - - before(:each) do - @io = StringIO.new - options = mock('options') - @formatter = FailingExampleGroupsFormatter.new(options, io) - @example_group = Class.new(::Spec::Example::ExampleGroup) - end - - it "should add example name for each failure" do - formatter.add_example_group(Class.new(ExampleGroup).describe("b 1")) - formatter.example_failed("e 1", nil, Reporter::Failure.new(nil, RuntimeError.new)) - formatter.add_example_group(Class.new(ExampleGroup).describe("b 2")) - formatter.example_failed("e 2", nil, Reporter::Failure.new(nil, RuntimeError.new)) - formatter.example_failed("e 3", nil, Reporter::Failure.new(nil, RuntimeError.new)) - io.string.should == "b 1\nb 2\n" - end - - it "should delimit ExampleGroup superclass descriptions with :" do - parent_example_group = Class.new(example_group).describe("Parent") - child_example_group = Class.new(parent_example_group).describe("#child_method") - grand_child_example_group = Class.new(child_example_group).describe("GrandChild") - - formatter.add_example_group(grand_child_example_group) - formatter.example_failed("failure", nil, Reporter::Failure.new(nil, RuntimeError.new)) - io.string.should == "Parent#child_method GrandChild\n" - end - - it "should remove druby url, which is used by Spec::Distributed" do - @formatter.add_example_group(Class.new(ExampleGroup).describe("something something (druby://99.99.99.99:99)")) - @formatter.example_failed("e 1", nil, Reporter::Failure.new(nil, RuntimeError.new)) - io.string.should == "something something\n" - end - end - end - end -end diff --git a/vendor/gems/rspec/spec/spec/runner/formatter/failing_examples_formatter_spec.rb b/vendor/gems/rspec/spec/spec/runner/formatter/failing_examples_formatter_spec.rb deleted file mode 100644 index fda64f95ff..0000000000 --- a/vendor/gems/rspec/spec/spec/runner/formatter/failing_examples_formatter_spec.rb +++ /dev/null @@ -1,33 +0,0 @@ -require File.dirname(__FILE__) + '/../../../spec_helper' -require 'spec/runner/formatter/failing_examples_formatter' - -module Spec - module Runner - module Formatter - describe FailingExamplesFormatter do - before(:each) do - @io = StringIO.new - options = mock('options') - @formatter = FailingExamplesFormatter.new(options, @io) - end - - it "should add example name for each failure" do - example_group_1 = Class.new(ExampleGroup).describe("A") - example_group_2 = Class.new(example_group_1).describe("B") - - @formatter.add_example_group(example_group_1) - @formatter.example_failed(example_group_1.it("a1"){}, nil, Reporter::Failure.new(nil, RuntimeError.new)) - @formatter.add_example_group(example_group_2) - @formatter.example_failed(example_group_2.it("b2"){}, nil, Reporter::Failure.new(nil, RuntimeError.new)) - @formatter.example_failed(example_group_2.it("b3"){}, nil, Reporter::Failure.new(nil, RuntimeError.new)) - @io.string.should eql(<<-EOF -A a1 -A B b2 -A B b3 -EOF -) - end - end - end - end -end diff --git a/vendor/gems/rspec/spec/spec/runner/formatter/html_formatted-1.8.4.html b/vendor/gems/rspec/spec/spec/runner/formatter/html_formatted-1.8.4.html deleted file mode 100644 index 9cc458fdb7..0000000000 --- a/vendor/gems/rspec/spec/spec/runner/formatter/html_formatted-1.8.4.html +++ /dev/null @@ -1,365 +0,0 @@ - - - - - RSpec results - - - - - - -
    - - - -
    -

    RSpec Results

    - -
    -

     

    -

     

    -
    -
    - -
    -
    -
    -
    Mocker
    - -
    should be able to call mock()
    - - - -
    - should fail when expected message not received -
    -
    Mock 'poke me' expected :poke with (any args) once, but received it 0 times
    -
    ./failing_examples/mocking_example.rb:13:
    -./spec/spec/runner/formatter/html_formatter_spec.rb:24:
    -./spec/spec/runner/formatter/html_formatter_spec.rb:20:
    -
    11  it "should fail when expected message not received" do
    -12    mock = mock("poke me")
    -13    mock.should_receive(:poke)
    -14  end
    -15  
    -
    -
    - -
    - should fail when messages are received out of order -
    -
    Mock 'one two three' received :three out of order
    -
    ./failing_examples/mocking_example.rb:22:
    -./spec/spec/runner/formatter/html_formatter_spec.rb:24:
    -./spec/spec/runner/formatter/html_formatter_spec.rb:20:
    -
    20    mock.should_receive(:three).ordered
    -21    mock.one
    -22    mock.three
    -23    mock.two
    -24  end
    -
    -
    - -
    - should get yelled at when sending unexpected messages -
    -
    Mock 'don't talk to me' expected :any_message_at_all with (any args) 0 times, but received it once
    -
    ./failing_examples/mocking_example.rb:28:
    -./spec/spec/runner/formatter/html_formatter_spec.rb:24:
    -./spec/spec/runner/formatter/html_formatter_spec.rb:20:
    -
    26  it "should get yelled at when sending unexpected messages" do
    -27    mock = mock("don't talk to me")
    -28    mock.should_not_receive(:any_message_at_all)
    -29    mock.any_message_at_all
    -30  end
    -
    -
    - -
    - has a bug we need to fix -
    -
    Expected pending 'here is the bug' to fail. No Error was raised.
    -
    ./failing_examples/mocking_example.rb:33:
    -./spec/spec/runner/formatter/html_formatter_spec.rb:24:
    -./spec/spec/runner/formatter/html_formatter_spec.rb:20:
    -
    31
    -32  it "has a bug we need to fix" do
    -33    pending "here is the bug" do
    -34      # Actually, no. It's fixed. This will fail because it passes :-)
    -35      mock = mock("Bug")
    -
    -
    -
    -
    -
    -
    -
    Running specs with --diff
    - - -
    - should print diff of different strings -
    -
    expected: "RSpec is a\nbehaviour driven development\nframework for Ruby\n",
    -     got: "RSpec is a\nbehavior driven development\nframework for Ruby\n" (using ==)
    -Diff:
    -@@ -1,4 +1,4 @@
    - RSpec is a
    --behavior driven development
    -+behaviour driven development
    - framework for Ruby
    -
    -
    ./failing_examples/diffing_spec.rb:13:
    -./spec/spec/runner/formatter/html_formatter_spec.rb:24:
    -./spec/spec/runner/formatter/html_formatter_spec.rb:20:
    -
    11framework for Ruby
    -12EOF
    -13    usa.should == uk
    -14  end
    -
    -
    - -
    - should print diff of different objects' pretty representation -
    -
    expected <Animal
    -name=bob,
    -species=tortoise
    ->
    -, got <Animal
    -name=bob,
    -species=giraffe
    ->
    - (using .eql?)
    -Diff:
    -@@ -1,5 +1,5 @@
    - <Animal
    - name=bob,
    --species=giraffe
    -+species=tortoise
    - >
    -
    -
    ./failing_examples/diffing_spec.rb:34:
    -./spec/spec/runner/formatter/html_formatter_spec.rb:24:
    -./spec/spec/runner/formatter/html_formatter_spec.rb:20:
    -
    32    expected = Animal.new "bob", "giraffe"
    -33    actual   = Animal.new "bob", "tortoise"
    -34    expected.should eql(actual)
    -35  end
    -36end
    -
    -
    -
    -
    -
    -
    -
    A consumer of a stub
    - -
    should be able to stub methods on any Object
    -
    -
    -
    -
    -
    A stubbed method on a class
    - -
    should return the stubbed value
    - -
    should revert to the original method after each spec
    - -
    can stub! and mock the same message
    -
    -
    -
    -
    -
    A mock
    - -
    can stub!
    - -
    can stub! and mock
    - -
    can stub! and mock the same message
    -
    -
    -
    -
    -
    pending example (using pending method)
    - - -
    should be reported as "PENDING: for some reason" (PENDING: for some reason)
    -
    -
    -
    -
    -
    pending example (with no block)
    - - -
    should be reported as "PENDING: Not Yet Implemented" (PENDING: Not Yet Implemented)
    -
    -
    -
    -
    -
    pending example (with block for pending)
    - - -
    should have a failing block, passed to pending, reported as "PENDING: for some reason" (PENDING: for some reason)
    -
    -
    - - -
    -
    - - diff --git a/vendor/gems/rspec/spec/spec/runner/formatter/html_formatted-1.8.5-jruby.html b/vendor/gems/rspec/spec/spec/runner/formatter/html_formatted-1.8.5-jruby.html deleted file mode 100644 index 8bf1ed9cd6..0000000000 --- a/vendor/gems/rspec/spec/spec/runner/formatter/html_formatted-1.8.5-jruby.html +++ /dev/null @@ -1,387 +0,0 @@ - - - - - RSpec results - - - - - - -
    - - - -
    -

    RSpec Results

    - -
    -

     

    -

     

    -
    -
    - -
    -
    -
    -
    Mocker
    - -
    should be able to call mock()
    - - - -
    - should fail when expected message not received -
    -
    Mock 'poke me' expected :poke with (any args) once, but received it 0 times
    -
    /Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./failing_examples/mocking_example.rb:13:in `should_receive'
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:24:in `run'
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:20:in `chdir'
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:20:in `chdir'
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:20:in `instance_eval'
    -
    11  it "should fail when expected message not received" do
    -12    mock = mock("poke me")
    -13    mock.should_receive(:poke)
    -14  end
    -15  
    -16# gem install syntax to get syntax highlighting
    -
    -
    - -
    - should fail when messages are received out of order -
    -
    Mock 'one two three' received :three out of order
    -
    /Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./failing_examples/mocking_example.rb:22:in `three'
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./failing_examples/mocking_example.rb:16:in `instance_eval'
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:24:in `run'
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:20:in `chdir'
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:20:in `chdir'
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:20:in `instance_eval'
    -
    20    mock.should_receive(:three).ordered
    -21    mock.one
    -22    mock.three
    -23    mock.two
    -24  end
    -25# gem install syntax to get syntax highlighting
    -
    -
    - -
    - should get yelled at when sending unexpected messages -
    -
    Mock 'don't talk to me' expected :any_message_at_all with (any args) 0 times, but received it once
    -
    /Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./failing_examples/mocking_example.rb:28:in `should_not_receive'
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:24:in `run'
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:20:in `chdir'
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:20:in `chdir'
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:20:in `instance_eval'
    -
    26  it "should get yelled at when sending unexpected messages" do
    -27    mock = mock("don't talk to me")
    -28    mock.should_not_receive(:any_message_at_all)
    -29    mock.any_message_at_all
    -30  end
    -31# gem install syntax to get syntax highlighting
    -
    -
    - -
    - has a bug we need to fix -
    -
    Expected pending 'here is the bug' to fail. No Error was raised.
    -
    /Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./failing_examples/mocking_example.rb:33:in `pending'
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./failing_examples/mocking_example.rb:33:in `instance_eval'
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:24:in `run'
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:20:in `chdir'
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:20:in `chdir'
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:20:in `instance_eval'
    -
    31
    -32  it "has a bug we need to fix" do
    -33    pending "here is the bug" do
    -34      # Actually, no. It's fixed. This will fail because it passes :-)
    -35      mock = mock("Bug")
    -36# gem install syntax to get syntax highlighting
    -
    -
    -
    -
    -
    -
    -
    Running specs with --diff
    - - -
    - should print diff of different strings -
    -
    expected: "RSpec is a\nbehaviour driven development\nframework for Ruby\n",
    -     got: "RSpec is a\nbehavior driven development\nframework for Ruby\n" (using ==)
    -Diff:
    -@@ -1,4 +1,4 @@
    - RSpec is a
    --behavior driven development
    -+behaviour driven development
    - framework for Ruby
    -
    -
    /Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./failing_examples/diffing_spec.rb:13:in `=='
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:24:in `run'
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:20:in `chdir'
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:20:in `chdir'
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:20:in `instance_eval'
    -
    11framework for Ruby
    -12EOF
    -13    usa.should == uk
    -14  end
    -15
    -16# gem install syntax to get syntax highlighting
    -
    -
    - -
    - should print diff of different objects' pretty representation -
    -
    expected <Animal
    -name=bob,
    -species=tortoise
    ->
    -, got <Animal
    -name=bob,
    -species=giraffe
    ->
    - (using .eql?)
    -Diff:
    -@@ -1,5 +1,5 @@
    - <Animal
    - name=bob,
    --species=giraffe
    -+species=tortoise
    - >
    -
    -
    /Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./failing_examples/diffing_spec.rb:34:in `should'
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./failing_examples/diffing_spec.rb:31:in `instance_eval'
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:24:in `run'
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:20:in `chdir'
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:20:in `chdir'
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/spec/spec/runner/formatter/html_formatter_spec.rb:20:in `instance_eval'
    -
    32    expected = Animal.new "bob", "giraffe"
    -33    actual   = Animal.new "bob", "tortoise"
    -34    expected.should eql(actual)
    -35  end
    -36end
    -37# gem install syntax to get syntax highlighting
    -
    -
    -
    -
    -
    -
    -
    A consumer of a stub
    - -
    should be able to stub methods on any Object
    -
    -
    -
    -
    -
    A stubbed method on a class
    - -
    should return the stubbed value
    - -
    should revert to the original method after each spec
    - -
    can stub! and mock the same message
    -
    -
    -
    -
    -
    A mock
    - -
    can stub!
    - -
    can stub! and mock
    - -
    can stub! and mock the same message
    -
    -
    -
    -
    -
    pending example (using pending method)
    - - -
    should be reported as "PENDING: for some reason" (PENDING: for some reason)
    -
    -
    -
    -
    -
    pending example (with no block)
    - - -
    should be reported as "PENDING: Not Yet Implemented" (PENDING: Not Yet Implemented)
    -
    -
    -
    -
    -
    pending example (with block for pending)
    - - -
    should have a failing block, passed to pending, reported as "PENDING: for some reason" (PENDING: for some reason)
    -
    -
    - - -
    -
    - - diff --git a/vendor/gems/rspec/spec/spec/runner/formatter/html_formatted-1.8.5.html b/vendor/gems/rspec/spec/spec/runner/formatter/html_formatted-1.8.5.html deleted file mode 100644 index cda7226bfe..0000000000 --- a/vendor/gems/rspec/spec/spec/runner/formatter/html_formatted-1.8.5.html +++ /dev/null @@ -1,371 +0,0 @@ - - - - - RSpec results - - - - - - -
    - - - -
    -

    RSpec Results

    - -
    -

     

    -

     

    -
    -
    - -
    -
    -
    -
    Mocker
    - -
    should be able to call mock()
    - - - -
    - should fail when expected message not received -
    -
    Mock 'poke me' expected :poke with (any args) once, but received it 0 times
    -
    ./failing_examples/mocking_example.rb:13:
    -./spec/spec/runner/formatter/html_formatter_spec.rb:17:
    -./spec/spec/runner/formatter/html_formatter_spec.rb:13:in `chdir'
    -./spec/spec/runner/formatter/html_formatter_spec.rb:13:
    -
    11  it "should fail when expected message not received" do
    -12    mock = mock("poke me")
    -13    mock.should_receive(:poke)
    -14  end
    -15  
    -
    -
    - -
    - should fail when messages are received out of order -
    -
    Mock 'one two three' received :three out of order
    -
    ./failing_examples/mocking_example.rb:22:
    -./spec/spec/runner/formatter/html_formatter_spec.rb:17:
    -./spec/spec/runner/formatter/html_formatter_spec.rb:13:in `chdir'
    -./spec/spec/runner/formatter/html_formatter_spec.rb:13:
    -
    20    mock.should_receive(:three).ordered
    -21    mock.one
    -22    mock.three
    -23    mock.two
    -24  end
    -
    -
    - -
    - should get yelled at when sending unexpected messages -
    -
    Mock 'don't talk to me' expected :any_message_at_all with (any args) 0 times, but received it once
    -
    ./failing_examples/mocking_example.rb:28:
    -./spec/spec/runner/formatter/html_formatter_spec.rb:17:
    -./spec/spec/runner/formatter/html_formatter_spec.rb:13:in `chdir'
    -./spec/spec/runner/formatter/html_formatter_spec.rb:13:
    -
    26  it "should get yelled at when sending unexpected messages" do
    -27    mock = mock("don't talk to me")
    -28    mock.should_not_receive(:any_message_at_all)
    -29    mock.any_message_at_all
    -30  end
    -
    -
    - -
    - has a bug we need to fix -
    -
    Expected pending 'here is the bug' to fail. No Error was raised.
    -
    ./failing_examples/mocking_example.rb:33:
    -./spec/spec/runner/formatter/html_formatter_spec.rb:17:
    -./spec/spec/runner/formatter/html_formatter_spec.rb:13:in `chdir'
    -./spec/spec/runner/formatter/html_formatter_spec.rb:13:
    -
    31
    -32  it "has a bug we need to fix" do
    -33    pending "here is the bug" do
    -34      # Actually, no. It's fixed. This will fail because it passes :-)
    -35      mock = mock("Bug")
    -
    -
    -
    -
    -
    -
    -
    Running specs with --diff
    - - -
    - should print diff of different strings -
    -
    expected: "RSpec is a\nbehaviour driven development\nframework for Ruby\n",
    -     got: "RSpec is a\nbehavior driven development\nframework for Ruby\n" (using ==)
    -Diff:
    -@@ -1,4 +1,4 @@
    - RSpec is a
    --behavior driven development
    -+behaviour driven development
    - framework for Ruby
    -
    -
    ./failing_examples/diffing_spec.rb:13:
    -./spec/spec/runner/formatter/html_formatter_spec.rb:17:
    -./spec/spec/runner/formatter/html_formatter_spec.rb:13:in `chdir'
    -./spec/spec/runner/formatter/html_formatter_spec.rb:13:
    -
    11framework for Ruby
    -12EOF
    -13    usa.should == uk
    -14  end
    -
    -
    - -
    - should print diff of different objects' pretty representation -
    -
    expected <Animal
    -name=bob,
    -species=tortoise
    ->
    -, got <Animal
    -name=bob,
    -species=giraffe
    ->
    - (using .eql?)
    -Diff:
    -@@ -1,5 +1,5 @@
    - <Animal
    - name=bob,
    --species=giraffe
    -+species=tortoise
    - >
    -
    -
    ./failing_examples/diffing_spec.rb:34:
    -./spec/spec/runner/formatter/html_formatter_spec.rb:17:
    -./spec/spec/runner/formatter/html_formatter_spec.rb:13:in `chdir'
    -./spec/spec/runner/formatter/html_formatter_spec.rb:13:
    -
    32    expected = Animal.new "bob", "giraffe"
    -33    actual   = Animal.new "bob", "tortoise"
    -34    expected.should eql(actual)
    -35  end
    -36end
    -
    -
    -
    -
    -
    -
    -
    A consumer of a stub
    - -
    should be able to stub methods on any Object
    -
    -
    -
    -
    -
    A stubbed method on a class
    - -
    should return the stubbed value
    - -
    should revert to the original method after each spec
    - -
    can stub! and mock the same message
    -
    -
    -
    -
    -
    A mock
    - -
    can stub!
    - -
    can stub! and mock
    - -
    can stub! and mock the same message
    -
    -
    -
    -
    -
    pending example (using pending method)
    - - -
    should be reported as "PENDING: for some reason" (PENDING: for some reason)
    -
    -
    -
    -
    -
    pending example (with no block)
    - - -
    should be reported as "PENDING: Not Yet Implemented" (PENDING: Not Yet Implemented)
    -
    -
    -
    -
    -
    pending example (with block for pending)
    - - -
    should have a failing block, passed to pending, reported as "PENDING: for some reason" (PENDING: for some reason)
    -
    -
    - - -
    -
    - - diff --git a/vendor/gems/rspec/spec/spec/runner/formatter/html_formatted-1.8.6-jruby.html b/vendor/gems/rspec/spec/spec/runner/formatter/html_formatted-1.8.6-jruby.html deleted file mode 100644 index 4666218653..0000000000 --- a/vendor/gems/rspec/spec/spec/runner/formatter/html_formatted-1.8.6-jruby.html +++ /dev/null @@ -1,381 +0,0 @@ - - - - - RSpec results - - - - - - -
    - - - -
    -

    RSpec Results

    - -
    -

     

    -

     

    -
    -
    - -
    -
    -
    -
    Mocker
    - -
    should be able to call mock()
    - - - -
    - should fail when expected message not received -
    -
    Mock 'poke me' expected :poke with (any args) once, but received it 0 times
    -
    /Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./failing_examples/mocking_example.rb:13:
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:28:
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:24:in `chdir'
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:24:
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:24:in `instance_eval'
    -
    11  it "should fail when expected message not received" do
    -12    mock = mock("poke me")
    -13    mock.should_receive(:poke)
    -14  end
    -15  
    -
    -
    - -
    - should fail when messages are received out of order -
    -
    Mock 'one two three' received :three out of order
    -
    /Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./failing_examples/mocking_example.rb:22:
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./failing_examples/mocking_example.rb:16:in `instance_eval'
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:28:
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:24:in `chdir'
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:24:
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:24:in `instance_eval'
    -
    20    mock.should_receive(:three).ordered
    -21    mock.one
    -22    mock.three
    -23    mock.two
    -24  end
    -
    -
    - -
    - should get yelled at when sending unexpected messages -
    -
    Mock 'don't talk to me' expected :any_message_at_all with (any args) 0 times, but received it once
    -
    /Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./failing_examples/mocking_example.rb:28:
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:28:
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:24:in `chdir'
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:24:
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:24:in `instance_eval'
    -
    26  it "should get yelled at when sending unexpected messages" do
    -27    mock = mock("don't talk to me")
    -28    mock.should_not_receive(:any_message_at_all)
    -29    mock.any_message_at_all
    -30  end
    -
    -
    - -
    - has a bug we need to fix -
    -
    Expected pending 'here is the bug' to fail. No Error was raised.
    -
    /Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./failing_examples/mocking_example.rb:33:
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./failing_examples/mocking_example.rb:33:in `instance_eval'
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:28:
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:24:in `chdir'
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:24:
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:24:in `instance_eval'
    -
    31
    -32  it "has a bug we need to fix" do
    -33    pending "here is the bug" do
    -34      # Actually, no. It's fixed. This will fail because it passes :-)
    -35      mock = mock("Bug")
    -
    -
    -
    -
    -
    -
    -
    Running specs with --diff
    - - -
    - should print diff of different strings -
    -
    expected: "RSpec is a\nbehaviour driven development\nframework for Ruby\n",
    -     got: "RSpec is a\nbehavior driven development\nframework for Ruby\n" (using ==)
    -Diff:
    -@@ -1,4 +1,4 @@
    - RSpec is a
    --behavior driven development
    -+behaviour driven development
    - framework for Ruby
    -
    -
    /Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./failing_examples/diffing_spec.rb:13:
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./failing_examples/diffing_spec.rb:2:in `instance_eval'
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:28:
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:24:in `chdir'
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:24:
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:24:in `instance_eval'
    -
    11framework for Ruby
    -12EOF
    -13    usa.should == uk
    -14  end
    -
    -
    - -
    - should print diff of different objects' pretty representation -
    -
    expected <Animal
    -name=bob,
    -species=tortoise
    ->
    -, got <Animal
    -name=bob,
    -species=giraffe
    ->
    - (using .eql?)
    -Diff:
    -@@ -1,5 +1,5 @@
    - <Animal
    - name=bob,
    --species=giraffe
    -+species=tortoise
    - >
    -
    -
    /Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./failing_examples/diffing_spec.rb:34:
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./failing_examples/diffing_spec.rb:31:in `instance_eval'
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:28:
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:24:in `chdir'
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:24:
    -/Users/david/projects/ruby/jruby/testsuites/rspec/target/rspec/./spec/spec/runner/formatter/html_formatter_spec.rb:24:in `instance_eval'
    -
    32    expected = Animal.new "bob", "giraffe"
    -33    actual   = Animal.new "bob", "tortoise"
    -34    expected.should eql(actual)
    -35  end
    -36end
    -
    -
    -
    -
    -
    -
    -
    A consumer of a stub
    - -
    should be able to stub methods on any Object
    -
    -
    -
    -
    -
    A stubbed method on a class
    - -
    should return the stubbed value
    - -
    should revert to the original method after each spec
    - -
    can stub! and mock the same message
    -
    -
    -
    -
    -
    A mock
    - -
    can stub!
    - -
    can stub! and mock
    - -
    can stub! and mock the same message
    -
    -
    -
    -
    -
    pending example (using pending method)
    - - -
    should be reported as "PENDING: for some reason" (PENDING: for some reason)
    -
    -
    -
    -
    -
    pending example (with no block)
    - - -
    should be reported as "PENDING: Not Yet Implemented" (PENDING: Not Yet Implemented)
    -
    -
    -
    -
    -
    pending example (with block for pending)
    - - -
    should have a failing block, passed to pending, reported as "PENDING: for some reason" (PENDING: for some reason)
    -
    -
    - - -
    -
    - - diff --git a/vendor/gems/rspec/spec/spec/runner/formatter/html_formatted-1.8.6.html b/vendor/gems/rspec/spec/spec/runner/formatter/html_formatted-1.8.6.html deleted file mode 100644 index 511495bcd0..0000000000 --- a/vendor/gems/rspec/spec/spec/runner/formatter/html_formatted-1.8.6.html +++ /dev/null @@ -1,365 +0,0 @@ - - - - - RSpec results - - - - - - -
    - - - -
    -

    RSpec Results

    - -
    -

     

    -

     

    -
    -
    - -
    -
    -
    -
    Mocker
    - -
    should be able to call mock()
    - - - -
    - should fail when expected message not received -
    -
    Mock 'poke me' expected :poke with (any args) once, but received it 0 times
    -
    ./failing_examples/mocking_example.rb:13:
    -./spec/spec/runner/formatter/html_formatter_spec.rb:18:
    -./spec/spec/runner/formatter/html_formatter_spec.rb:14:in `chdir'
    -./spec/spec/runner/formatter/html_formatter_spec.rb:14:
    -
    11  it "should fail when expected message not received" do
    -12    mock = mock("poke me")
    -13    mock.should_receive(:poke)
    -14  end
    -15  
    -
    -
    - -
    - should fail when messages are received out of order -
    -
    Mock 'one two three' received :three out of order
    -
    ./failing_examples/mocking_example.rb:22:
    -./spec/spec/runner/formatter/html_formatter_spec.rb:18:
    -./spec/spec/runner/formatter/html_formatter_spec.rb:14:in `chdir'
    -./spec/spec/runner/formatter/html_formatter_spec.rb:14:
    -
    20    mock.should_receive(:three).ordered
    -21    mock.one
    -22    mock.three
    -23    mock.two
    -24  end
    -
    -
    - -
    - should get yelled at when sending unexpected messages -
    -
    Mock 'don't talk to me' expected :any_message_at_all with (any args) 0 times, but received it once
    -
    ./failing_examples/mocking_example.rb:28:
    -./spec/spec/runner/formatter/html_formatter_spec.rb:18:
    -./spec/spec/runner/formatter/html_formatter_spec.rb:14:in `chdir'
    -./spec/spec/runner/formatter/html_formatter_spec.rb:14:
    -
    26  it "should get yelled at when sending unexpected messages" do
    -27    mock = mock("don't talk to me")
    -28    mock.should_not_receive(:any_message_at_all)
    -29    mock.any_message_at_all
    -30  end
    -
    -
    - -
    - has a bug we need to fix -
    -
    Expected pending 'here is the bug' to fail. No Error was raised.
    - -
    31
    -32  it "has a bug we need to fix" do
    -33    pending "here is the bug" do
    -34      # Actually, no. It's fixed. This will fail because it passes :-)
    -35      mock = mock("Bug")
    -
    -
    -
    -
    -
    -
    -
    Running specs with --diff
    - - -
    - should print diff of different strings -
    -
    expected: "RSpec is a\nbehaviour driven development\nframework for Ruby\n",
    -     got: "RSpec is a\nbehavior driven development\nframework for Ruby\n" (using ==)
    -Diff:
    -@@ -1,4 +1,4 @@
    - RSpec is a
    --behavior driven development
    -+behaviour driven development
    - framework for Ruby
    -
    - -
    11framework for Ruby
    -12EOF
    -13    usa.should == uk
    -14  end
    -
    -
    - -
    - should print diff of different objects' pretty representation -
    -
    expected <Animal
    -name=bob,
    -species=tortoise
    ->
    -, got <Animal
    -name=bob,
    -species=giraffe
    ->
    - (using .eql?)
    -Diff:
    -@@ -1,5 +1,5 @@
    - <Animal
    - name=bob,
    --species=giraffe
    -+species=tortoise
    - >
    -
    -
    ./failing_examples/mocking_example.rb:33:
    -./spec/spec/runner/formatter/html_formatter_spec.rb:18:
    -./spec/spec/runner/formatter/html_formatter_spec.rb:14:in `chdir'
    -./spec/spec/runner/formatter/html_formatter_spec.rb:14:
    -
    32    expected = Animal.new "bob", "giraffe"
    -33    actual   = Animal.new "bob", "tortoise"
    -34    expected.should eql(actual)
    -35  end
    -36end
    -
    -
    -
    -
    -
    -
    -
    A consumer of a stub
    - -
    should be able to stub methods on any Object
    -
    -
    -
    -
    -
    A stubbed method on a class
    - -
    should return the stubbed value
    - -
    should revert to the original method after each spec
    - -
    can stub! and mock the same message
    -
    -
    -
    -
    -
    A mock
    - -
    can stub!
    - -
    can stub! and mock
    - -
    can stub! and mock the same message
    -
    -
    -
    -
    -
    pending example (using pending method)
    - - -
    should be reported as "PENDING: for some reason" (PENDING: for some reason)
    -
    -
    -
    -
    -
    pending example (with no block)
    - - -
    should be reported as "PENDING: Not Yet Implemented" (PENDING: Not Yet Implemented)
    -
    -
    -
    -
    -
    pending example (with block for pending)
    - - -
    should have a failing block, passed to pending, reported as "PENDING: for some reason" (PENDING: for some reason)
    -
    -
    - - -
    -
    - - diff --git a/vendor/gems/rspec/spec/spec/runner/formatter/html_formatter_spec.rb b/vendor/gems/rspec/spec/spec/runner/formatter/html_formatter_spec.rb deleted file mode 100644 index 5ba39f0e9f..0000000000 --- a/vendor/gems/rspec/spec/spec/runner/formatter/html_formatter_spec.rb +++ /dev/null @@ -1,66 +0,0 @@ -require File.dirname(__FILE__) + '/../../../spec_helper' -require 'hpricot' # Needed to compare generated with wanted HTML -require 'spec/runner/formatter/html_formatter' - -module Spec - module Runner - module Formatter - describe HtmlFormatter do - ['--diff', '--dry-run'].each do |opt| - def jruby? - PLATFORM == 'java' - end - - it "should produce HTML identical to the one we designed manually with #{opt}" do - root = File.expand_path(File.dirname(__FILE__) + '/../../../..') - suffix = jruby? ? '-jruby' : '' - expected_file = File.dirname(__FILE__) + "/html_formatted-#{::VERSION}#{suffix}.html" - raise "There is no HTML file with expected content for this platform: #{expected_file}" unless File.file?(expected_file) - expected_html = File.read(expected_file) - unless jruby? - raise "There should be no absolute paths in html_formatted.html!!" if (expected_html =~ /\/Users/n || expected_html =~ /\/home/n) - end - - Dir.chdir(root) do - args = ['failing_examples/mocking_example.rb', 'failing_examples/diffing_spec.rb', 'examples/pure/stubbing_example.rb', 'examples/pure/pending_example.rb', '--format', 'html', opt] - err = StringIO.new - out = StringIO.new - CommandLine.run( - OptionParser.parse(args, err, out) - ) - - seconds = /\d+\.\d+ seconds/ - html = out.string.gsub seconds, 'x seconds' - expected_html.gsub! seconds, 'x seconds' - - if opt == '--diff' - # Uncomment this line temporarily in order to overwrite the expected with actual. - # Use with care!!! - # File.open(expected_file, 'w') {|io| io.write(html)} - - doc = Hpricot(html) - backtraces = doc.search("div.backtrace").collect {|e| e.at("/pre").inner_html} - doc.search("div.backtrace").remove - - expected_doc = Hpricot(expected_html) - expected_backtraces = expected_doc.search("div.backtrace").collect {|e| e.at("/pre").inner_html} - expected_doc.search("div.backtrace").remove - - doc.inner_html.should == expected_doc.inner_html - - expected_backtraces.each_with_index do |expected_line, i| - expected_path, expected_line_number, expected_suffix = expected_line.split(':') - actual_path, actual_line_number, actual_suffix = backtraces[i].split(':') - File.expand_path(actual_path).should == File.expand_path(expected_path) - actual_line_number.should == expected_line_number - end - else - html.should =~ /This was a dry-run/m - end - end - end - end - end - end - end -end diff --git a/vendor/gems/rspec/spec/spec/runner/formatter/profile_formatter_spec.rb b/vendor/gems/rspec/spec/spec/runner/formatter/profile_formatter_spec.rb deleted file mode 100644 index 9818054110..0000000000 --- a/vendor/gems/rspec/spec/spec/runner/formatter/profile_formatter_spec.rb +++ /dev/null @@ -1,65 +0,0 @@ -require File.dirname(__FILE__) + '/../../../spec_helper.rb' -require 'spec/runner/formatter/profile_formatter' - -module Spec - module Runner - module Formatter - describe ProfileFormatter do - attr_reader :io, :formatter - before(:each) do - @io = StringIO.new - options = mock('options') - options.stub!(:colour).and_return(true) - @formatter = ProfileFormatter.new(options, io) - end - - it "should print a heading" do - formatter.start(0) - io.string.should eql("Profiling enabled.\n") - end - - it "should record the current time when starting a new example" do - now = Time.now - Time.stub!(:now).and_return(now) - formatter.example_started('should foo') - formatter.instance_variable_get("@time").should == now - end - - it "should correctly record a passed example" do - now = Time.now - Time.stub!(:now).and_return(now) - parent_example_group = Class.new(ExampleGroup).describe('Parent') - child_example_group = Class.new(parent_example_group).describe('Child') - - formatter.add_example_group(child_example_group) - - formatter.example_started('when foo') - Time.stub!(:now).and_return(now+1) - formatter.example_passed(stub('foo', :description => 'i like ice cream')) - - formatter.start_dump - io.string.should include('Parent Child') - end - - it "should sort the results in descending order" do - formatter.instance_variable_set("@example_times", [['a', 'a', 0.1], ['b', 'b', 0.3], ['c', 'c', 0.2]]) - formatter.start_dump - formatter.instance_variable_get("@example_times").should == [ ['b', 'b', 0.3], ['c', 'c', 0.2], ['a', 'a', 0.1]] - end - - it "should print the top 10 results" do - example_group = Class.new(::Spec::Example::ExampleGroup).describe("ExampleGroup") - formatter.add_example_group(example_group) - formatter.instance_variable_set("@time", Time.now) - - 15.times do - formatter.example_passed(stub('foo', :description => 'i like ice cream')) - end - - io.should_receive(:print).exactly(10) - formatter.start_dump - end - end - end - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/runner/formatter/progress_bar_formatter_spec.rb b/vendor/gems/rspec/spec/spec/runner/formatter/progress_bar_formatter_spec.rb deleted file mode 100644 index 127a617c1c..0000000000 --- a/vendor/gems/rspec/spec/spec/runner/formatter/progress_bar_formatter_spec.rb +++ /dev/null @@ -1,127 +0,0 @@ -require File.dirname(__FILE__) + '/../../../spec_helper.rb' -require 'spec/runner/formatter/progress_bar_formatter' - -module Spec - module Runner - module Formatter - describe ProgressBarFormatter do - before(:each) do - @io = StringIO.new - @options = mock('options') - @options.stub!(:dry_run).and_return(false) - @options.stub!(:colour).and_return(false) - @formatter = ProgressBarFormatter.new(@options, @io) - end - - it "should produce line break on start dump" do - @formatter.start_dump - @io.string.should eql("\n") - end - - it "should produce standard summary without pending when pending has a 0 count" do - @formatter.dump_summary(3, 2, 1, 0) - @io.string.should eql("\nFinished in 3 seconds\n\n2 examples, 1 failure\n") - end - - it "should produce standard summary" do - @formatter.example_pending("example_group", ExampleGroup.new("example"), "message") - @io.rewind - @formatter.dump_summary(3, 2, 1, 1) - @io.string.should eql(%Q| -Finished in 3 seconds - -2 examples, 1 failure, 1 pending -|) - end - - it "should push green dot for passing spec" do - @io.should_receive(:tty?).and_return(true) - @options.should_receive(:colour).and_return(true) - @formatter.example_passed("spec") - @io.string.should == "\e[32m.\e[0m" - end - - it "should push red F for failure spec" do - @io.should_receive(:tty?).and_return(true) - @options.should_receive(:colour).and_return(true) - @formatter.example_failed("spec", 98, Reporter::Failure.new("c s", Spec::Expectations::ExpectationNotMetError.new)) - @io.string.should eql("\e[31mF\e[0m") - end - - it "should push magenta F for error spec" do - @io.should_receive(:tty?).and_return(true) - @options.should_receive(:colour).and_return(true) - @formatter.example_failed("spec", 98, Reporter::Failure.new("c s", RuntimeError.new)) - @io.string.should eql("\e[35mF\e[0m") - end - - it "should push blue F for fixed pending spec" do - @io.should_receive(:tty?).and_return(true) - @options.should_receive(:colour).and_return(true) - @formatter.example_failed("spec", 98, Reporter::Failure.new("c s", Spec::Example::PendingExampleFixedError.new)) - @io.string.should eql("\e[34mF\e[0m") - end - - it "should push nothing on start" do - @formatter.start(4) - @io.string.should eql("") - end - - it "should ensure two ':' in the first backtrace" do - backtrace = ["/tmp/x.rb:1", "/tmp/x.rb:2", "/tmp/x.rb:3"] - @formatter.format_backtrace(backtrace).should eql(<<-EOE.rstrip) -/tmp/x.rb:1: -/tmp/x.rb:2: -/tmp/x.rb:3: -EOE - - backtrace = ["/tmp/x.rb:1: message", "/tmp/x.rb:2", "/tmp/x.rb:3"] - @formatter.format_backtrace(backtrace).should eql(<<-EOE.rstrip) -/tmp/x.rb:1: message -/tmp/x.rb:2: -/tmp/x.rb:3: -EOE - end - - it "should dump pending" do - @formatter.example_pending("example_group", ExampleGroup.new("example"), "message") - @formatter.dump_pending - @io.string.should =~ /Pending\:\nexample_group example \(message\)\n/ - end - end - - describe "ProgressBarFormatter outputting to custom out" do - before(:each) do - @out = mock("out") - @options = mock('options') - @out.stub!(:puts) - @formatter = ProgressBarFormatter.new(@options, @out) - @formatter.class.send :public, :output_to_tty? - end - - after(:each) do - @formatter.class.send :protected, :output_to_tty? - end - - it "should not throw NoMethodError on output_to_tty?" do - @out.should_receive(:tty?).and_raise(NoMethodError) - @formatter.output_to_tty?.should be_false - end - end - - describe ProgressBarFormatter, "dry run" do - before(:each) do - @io = StringIO.new - options = mock('options') - options.stub!(:dry_run).and_return(true) - @formatter = ProgressBarFormatter.new(options, @io) - end - - it "should not produce summary on dry run" do - @formatter.dump_summary(3, 2, 1, 0) - @io.string.should eql("") - end - end - end - end -end diff --git a/vendor/gems/rspec/spec/spec/runner/formatter/snippet_extractor_spec.rb b/vendor/gems/rspec/spec/spec/runner/formatter/snippet_extractor_spec.rb deleted file mode 100644 index 4bb2f15853..0000000000 --- a/vendor/gems/rspec/spec/spec/runner/formatter/snippet_extractor_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -require File.dirname(__FILE__) + '/../../../spec_helper.rb' -require 'spec/runner/formatter/snippet_extractor' - -module Spec - module Runner - module Formatter - describe SnippetExtractor do - it "should fall back on a default message when it doesn't understand a line" do - SnippetExtractor.new.snippet_for("blech").should == ["# Couldn't get snippet for blech", 1] - end - - it "should fall back on a default message when it doesn't find the file" do - SnippetExtractor.new.lines_around("blech", 8).should == "# Couldn't get snippet for blech" - end - end - end - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/runner/formatter/spec_mate_formatter_spec.rb b/vendor/gems/rspec/spec/spec/runner/formatter/spec_mate_formatter_spec.rb deleted file mode 100644 index e782254e25..0000000000 --- a/vendor/gems/rspec/spec/spec/runner/formatter/spec_mate_formatter_spec.rb +++ /dev/null @@ -1,103 +0,0 @@ -require File.dirname(__FILE__) + '/../../../spec_helper' -require 'hpricot' # Needed to compare generated with wanted HTML -require 'spec/runner/formatter/text_mate_formatter' - -module Spec - module Runner - module Formatter - describe TextMateFormatter do - attr_reader :root, :suffix, :expected_file - before do - @root = File.expand_path(File.dirname(__FILE__) + '/../../../..') - @suffix = jruby? ? '-jruby' : '' - @expected_file = File.dirname(__FILE__) + "/text_mate_formatted-#{::VERSION}#{suffix}.html" - end - - def jruby? - PLATFORM == 'java' - end - - def produces_html_identical_to_manually_designed_document(opt) - root = File.expand_path(File.dirname(__FILE__) + '/../../../..') - - Dir.chdir(root) do - args = [ - 'failing_examples/mocking_example.rb', - 'failing_examples/diffing_spec.rb', - 'examples/pure/stubbing_example.rb', - 'examples/pure/pending_example.rb', - '--format', - 'textmate', - opt - ] - err = StringIO.new - out = StringIO.new - options = ::Spec::Runner::OptionParser.parse(args, err, out) - Spec::Runner::CommandLine.run(options) - - yield(out.string) - end - end - - # # Uncomment this spec temporarily in order to overwrite the expected with actual. - # # Use with care!!! - # describe TextMateFormatter, "functional spec file generator" do - # it "generates a new comparison file" do - # Dir.chdir(root) do - # args = ['failing_examples/mocking_example.rb', 'failing_examples/diffing_spec.rb', 'examples/pure/stubbing_example.rb', 'examples/pure/pending_example.rb', '--format', 'textmate', '--diff'] - # err = StringIO.new - # out = StringIO.new - # Spec::Runner::CommandLine.run( - # ::Spec::Runner::OptionParser.parse(args, err, out) - # ) - # - # seconds = /\d+\.\d+ seconds/ - # html = out.string.gsub seconds, 'x seconds' - # - # File.open(expected_file, 'w') {|io| io.write(html)} - # end - # end - # end - - describe "functional spec using --diff" do - it "should produce HTML identical to the one we designed manually with --diff" do - produces_html_identical_to_manually_designed_document("--diff") do |html| - suffix = jruby? ? '-jruby' : '' - expected_file = File.dirname(__FILE__) + "/text_mate_formatted-#{::VERSION}#{suffix}.html" - unless File.file?(expected_file) - raise "There is no HTML file with expected content for this platform: #{expected_file}" - end - expected_html = File.read(expected_file) - - seconds = /\d+\.\d+ seconds/ - html.gsub! seconds, 'x seconds' - expected_html.gsub! seconds, 'x seconds' - - doc = Hpricot(html) - backtraces = doc.search("div.backtrace/a") - doc.search("div.backtrace").remove - - expected_doc = Hpricot(expected_html) - expected_doc.search("div.backtrace").remove - - doc.inner_html.should == expected_doc.inner_html - - backtraces.each do |backtrace_link| - backtrace_link[:href].should include("txmt://open?url=") - end - end - end - - end - - describe "functional spec using --dry-run" do - it "should produce HTML identical to the one we designed manually with --dry-run" do - produces_html_identical_to_manually_designed_document("--dry-run") do |html, expected_html| - html.should =~ /This was a dry-run/m - end - end - end - end - end - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/runner/formatter/specdoc_formatter_spec.rb b/vendor/gems/rspec/spec/spec/runner/formatter/specdoc_formatter_spec.rb deleted file mode 100644 index 79995309d9..0000000000 --- a/vendor/gems/rspec/spec/spec/runner/formatter/specdoc_formatter_spec.rb +++ /dev/null @@ -1,126 +0,0 @@ -require File.dirname(__FILE__) + '/../../../spec_helper.rb' -require 'spec/runner/formatter/specdoc_formatter' - -module Spec - module Runner - module Formatter - describe SpecdocFormatter do - it_should_behave_like "sandboxed rspec_options" - attr_reader :io, :options, :formatter, :example_group - before(:each) do - @io = StringIO.new - options.stub!(:dry_run).and_return(false) - options.stub!(:colour).and_return(false) - @formatter = SpecdocFormatter.new(options, io) - @example_group = Class.new(::Spec::Example::ExampleGroup).describe("ExampleGroup") - end - - describe "where ExampleGroup has no superclasss with a description" do - before do - formatter.add_example_group(example_group) - end - - it "should produce standard summary without pending when pending has a 0 count" do - formatter.dump_summary(3, 2, 1, 0) - io.string.should have_example_group_output("\nFinished in 3 seconds\n\n2 examples, 1 failure\n") - end - - it "should produce standard summary" do - formatter.dump_summary(3, 2, 1, 4) - io.string.should have_example_group_output("\nFinished in 3 seconds\n\n2 examples, 1 failure, 4 pending\n") - end - - it "should push ExampleGroup name" do - io.string.should eql("\nExampleGroup\n") - end - - it "when having an error, should push failing spec name and failure number" do - formatter.example_failed( - example_group.it("spec"), - 98, - Reporter::Failure.new("c s", RuntimeError.new) - ) - io.string.should have_example_group_output("- spec (ERROR - 98)\n") - end - - it "when having an expectation failure, should push failing spec name and failure number" do - formatter.example_failed( - example_group.it("spec"), - 98, - Reporter::Failure.new("c s", Spec::Expectations::ExpectationNotMetError.new) - ) - io.string.should have_example_group_output("- spec (FAILED - 98)\n") - end - - it "should push nothing on start" do - formatter.start(5) - io.string.should have_example_group_output("") - end - - it "should push nothing on start dump" do - formatter.start_dump - io.string.should have_example_group_output("") - end - - it "should push passing spec name" do - formatter.example_passed(example_group.it("spec")) - io.string.should have_example_group_output("- spec\n") - end - - it "should push pending example name and message" do - formatter.example_pending('example_group', ExampleGroup.new("example"), 'reason') - io.string.should have_example_group_output("- example (PENDING: reason)\n") - end - - it "should dump pending" do - formatter.example_pending('example_group', ExampleGroup.new("example"), 'reason') - io.rewind - formatter.dump_pending - io.string.should =~ /Pending\:\nexample_group example \(reason\)\n/ - end - - def have_example_group_output(expected_output) - expected = "\nExampleGroup\n#{expected_output}" - ::Spec::Matchers::SimpleMatcher.new(expected) do |actual| - actual == expected - end - end - end - - describe "where ExampleGroup has two superclasses with a description" do - attr_reader :child_example_group, :grand_child_example_group - before do - @child_example_group = Class.new(example_group).describe("Child ExampleGroup") - @grand_child_example_group = Class.new(child_example_group).describe("GrandChild ExampleGroup") - formatter.add_example_group(grand_child_example_group) - end - - specify "when having an error, should push failing spec name and failure number" do - formatter.example_failed( - example_group.it("spec"), - 98, - Reporter::Failure.new("c s", RuntimeError.new) - ) - io.string.should have_nested_example_group_output("- spec (ERROR - 98)\n") - end - - specify "when having an expectation failure, should push failing spec name and failure number" do - formatter.example_failed( - example_group.it("spec"), - 98, - Reporter::Failure.new("c s", Spec::Expectations::ExpectationNotMetError.new) - ) - io.string.should have_nested_example_group_output("- spec (FAILED - 98)\n") - end - - def have_nested_example_group_output(expected_output) - expected_full_output = "\nExampleGroup Child ExampleGroup GrandChild ExampleGroup\n#{expected_output}" - ::Spec::Matchers::SimpleMatcher.new(expected_full_output) do |actual| - actual == expected_full_output - end - end - end - end - end - end -end diff --git a/vendor/gems/rspec/spec/spec/runner/formatter/story/html_formatter_spec.rb b/vendor/gems/rspec/spec/spec/runner/formatter/story/html_formatter_spec.rb deleted file mode 100644 index 37fb7c6704..0000000000 --- a/vendor/gems/rspec/spec/spec/runner/formatter/story/html_formatter_spec.rb +++ /dev/null @@ -1,61 +0,0 @@ -require File.dirname(__FILE__) + '/../../../../spec_helper.rb' -require 'spec/runner/formatter/story/html_formatter' - -module Spec - module Runner - module Formatter - module Story - describe HtmlFormatter do - before :each do - @out = StringIO.new - @options = mock('options') - @reporter = HtmlFormatter.new(@options, @out) - end - - it "should just be poked at" do - @reporter.run_started(1) - @reporter.story_started('story_title', 'narrative') - - @reporter.scenario_started('story_title', 'succeeded_scenario_name') - @reporter.step_succeeded('given', 'succeded_step', 'one', 'two') - @reporter.scenario_succeeded('story_title', 'succeeded_scenario_name') - - @reporter.scenario_started('story_title', 'pending_scenario_name') - @reporter.step_pending('when', 'pending_step', 'un', 'deux') - @reporter.scenario_pending('story_title', 'pending_scenario_name', 'not done') - - @reporter.scenario_started('story_title', 'failed_scenario_name') - @reporter.step_failed('then', 'failed_step', 'en', 'to') - @reporter.scenario_failed('story_title', 'failed_scenario_name', NameError.new('sup')) - - @reporter.scenario_started('story_title', 'scenario_with_given_scenario_name') - @reporter.found_scenario('given scenario', 'succeeded_scenario_name') - - @reporter.story_ended('story_title', 'narrative') - @reporter.run_ended - end - - it "should create spans for params" do - @reporter.step_succeeded('given', 'a $coloured $animal', 'brown', 'dog') - @out.string.should == "
  • Given a brown dog
  • \n" - end - - it 'should create spanes for params in regexp steps' do - @reporter.step_succeeded :given, /a (pink|blue) (.*)/, 'brown', 'dog' - @out.string.should == "
  • Given a brown dog
  • \n" - end - - it "should create a ul for collected_steps" do - @reporter.collected_steps(['Given a $coloured $animal', 'Given a $n legged eel']) - @out.string.should == (<<-EOF) - -EOF - end - end - end - end - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/runner/formatter/story/plain_text_formatter_spec.rb b/vendor/gems/rspec/spec/spec/runner/formatter/story/plain_text_formatter_spec.rb deleted file mode 100644 index 9632b06061..0000000000 --- a/vendor/gems/rspec/spec/spec/runner/formatter/story/plain_text_formatter_spec.rb +++ /dev/null @@ -1,335 +0,0 @@ -require File.dirname(__FILE__) + '/../../../../spec_helper.rb' -require 'spec/runner/formatter/story/plain_text_formatter' - -module Spec - module Runner - module Formatter - module Story - describe PlainTextFormatter do - before :each do - # given - @out = StringIO.new - @tweaker = mock('tweaker') - @tweaker.stub!(:tweak_backtrace) - @options = mock('options') - @options.stub!(:colour).and_return(false) - @options.stub!(:backtrace_tweaker).and_return(@tweaker) - @formatter = PlainTextFormatter.new(@options, @out) - end - - it 'should summarize the number of scenarios when the run ends' do - # when - @formatter.run_started(3) - @formatter.scenario_started(nil, nil) - @formatter.scenario_succeeded('story', 'scenario1') - @formatter.scenario_started(nil, nil) - @formatter.scenario_succeeded('story', 'scenario2') - @formatter.scenario_started(nil, nil) - @formatter.scenario_succeeded('story', 'scenario3') - @formatter.run_ended - - # then - @out.string.should include('3 scenarios') - end - - it 'should summarize the number of successful scenarios when the run ends' do - # when - @formatter.run_started(3) - @formatter.scenario_started(nil, nil) - @formatter.scenario_succeeded('story', 'scenario1') - @formatter.scenario_started(nil, nil) - @formatter.scenario_succeeded('story', 'scenario2') - @formatter.scenario_started(nil, nil) - @formatter.scenario_succeeded('story', 'scenario3') - @formatter.run_ended - - # then - @out.string.should include('3 scenarios: 3 succeeded') - end - - it 'should summarize the number of failed scenarios when the run ends' do - # when - @formatter.run_started(3) - @formatter.scenario_started(nil, nil) - @formatter.scenario_succeeded('story', 'scenario1') - @formatter.scenario_started(nil, nil) - @formatter.scenario_failed('story', 'scenario2', exception_from { raise RuntimeError, 'oops' }) - @formatter.scenario_started(nil, nil) - @formatter.scenario_failed('story', 'scenario3', exception_from { raise RuntimeError, 'oops' }) - @formatter.run_ended - - # then - @out.string.should include("3 scenarios: 1 succeeded, 2 failed") - end - - it 'should end cleanly (no characters on the last line) with successes' do - # when - @formatter.run_started(1) - @formatter.scenario_started(nil, nil) - @formatter.scenario_succeeded('story', 'scenario') - @formatter.run_ended - - # then - @out.string.should =~ /\n\z/ - end - - it 'should end cleanly (no characters on the last line) with failures' do - # when - @formatter.run_started(1) - @formatter.scenario_started(nil, nil) - @formatter.scenario_failed('story', 'scenario', exception_from { raise RuntimeError, 'oops' }) - @formatter.run_ended - - # then - @out.string.should =~ /\n\z/ - end - - it 'should end cleanly (no characters on the last line) with pending steps' do - # when - @formatter.run_started(1) - @formatter.scenario_started(nil, nil) - @formatter.step_pending(:then, 'do pend') - @formatter.scenario_pending('story', 'scenario', exception_from { raise RuntimeError, 'oops' }) - @formatter.run_ended - - # then - @out.string.should =~ /\n\z/ - end - - it 'should summarize the number of pending scenarios when the run ends' do - # when - @formatter.run_started(3) - @formatter.scenario_started(nil, nil) - @formatter.scenario_succeeded('story', 'scenario1') - @formatter.scenario_started(nil, nil) - @formatter.scenario_pending('story', 'scenario2', 'message') - @formatter.scenario_started(nil, nil) - @formatter.scenario_pending('story', 'scenario3', 'message') - @formatter.run_ended - - # then - @out.string.should include("3 scenarios: 1 succeeded, 0 failed, 2 pending") - end - - it "should only count the first failure in one scenario" do - # when - @formatter.run_started(3) - @formatter.scenario_started(nil, nil) - @formatter.scenario_succeeded('story', 'scenario1') - @formatter.scenario_started(nil, nil) - @formatter.scenario_failed('story', 'scenario2', exception_from { raise RuntimeError, 'oops' }) - @formatter.scenario_failed('story', 'scenario2', exception_from { raise RuntimeError, 'oops again' }) - @formatter.scenario_started(nil, nil) - @formatter.scenario_failed('story', 'scenario3', exception_from { raise RuntimeError, 'oops' }) - @formatter.run_ended - - # then - @out.string.should include("3 scenarios: 1 succeeded, 2 failed") - end - - it "should only count the first pending in one scenario" do - # when - @formatter.run_started(3) - @formatter.scenario_started(nil, nil) - @formatter.scenario_succeeded('story', 'scenario1') - @formatter.scenario_started(nil, nil) - @formatter.scenario_pending('story', 'scenario2', 'because ...') - @formatter.scenario_pending('story', 'scenario2', 'because ...') - @formatter.scenario_started(nil, nil) - @formatter.scenario_pending('story', 'scenario3', 'because ...') - @formatter.run_ended - - # then - @out.string.should include("3 scenarios: 1 succeeded, 0 failed, 2 pending") - end - - it "should only count a failure before the first pending in one scenario" do - # when - @formatter.run_started(3) - @formatter.scenario_started(nil, nil) - @formatter.scenario_succeeded('story', 'scenario1') - @formatter.scenario_started(nil, nil) - @formatter.scenario_pending('story', 'scenario2', exception_from { raise RuntimeError, 'oops' }) - @formatter.scenario_failed('story', 'scenario2', exception_from { raise RuntimeError, 'oops again' }) - @formatter.scenario_started(nil, nil) - @formatter.scenario_failed('story', 'scenario3', exception_from { raise RuntimeError, 'oops' }) - @formatter.run_ended - - # then - @out.string.should include("3 scenarios: 1 succeeded, 1 failed, 1 pending") - end - - it 'should produce details of the first failure each failed scenario when the run ends' do - # when - @formatter.run_started(3) - @formatter.scenario_started(nil, nil) - @formatter.scenario_succeeded('story', 'scenario1') - @formatter.scenario_started(nil, nil) - @formatter.scenario_failed('story', 'scenario2', exception_from { raise RuntimeError, 'oops2' }) - @formatter.scenario_failed('story', 'scenario2', exception_from { raise RuntimeError, 'oops2 - this one should not appear' }) - @formatter.scenario_started(nil, nil) - @formatter.scenario_failed('story', 'scenario3', exception_from { raise RuntimeError, 'oops3' }) - @formatter.run_ended - - # then - @out.string.should include("FAILURES:\n") - @out.string.should include("1) story (scenario2) FAILED") - @out.string.should include("RuntimeError: oops2") - @out.string.should_not include("RuntimeError: oops2 - this one should not appear") - @out.string.should include("2) story (scenario3) FAILED") - @out.string.should include("RuntimeError: oops3") - end - - it 'should produce details of each pending step when the run ends' do - # when - @formatter.run_started(2) - @formatter.story_started('story 1', 'narrative') - @formatter.scenario_started('story 1', 'scenario 1') - @formatter.step_pending(:given, 'todo 1', []) - @formatter.story_started('story 2', 'narrative') - @formatter.scenario_started('story 2', 'scenario 2') - @formatter.step_pending(:given, 'todo 2', []) - @formatter.run_ended - - # then - @out.string.should include("Pending Steps:\n") - @out.string.should include("1) story 1 (scenario 1): todo 1") - @out.string.should include("2) story 2 (scenario 2): todo 2") - end - - it 'should document a story title and narrative' do - # when - @formatter.story_started 'story', 'narrative' - - # then - @out.string.should include("Story: story\n\n narrative") - end - - it 'should document a scenario name' do - # when - @formatter.scenario_started 'story', 'scenario' - - # then - @out.string.should include("\n\n Scenario: scenario") - end - - it 'should document a step by sentence-casing its name' do - # when - @formatter.step_succeeded :given, 'a context' - @formatter.step_succeeded :when, 'an event' - @formatter.step_succeeded :then, 'an outcome' - - # then - @out.string.should include("\n\n Given a context\n\n When an event\n\n Then an outcome") - end - - it 'should document additional givens using And' do - # when - @formatter.step_succeeded :given, 'step 1' - @formatter.step_succeeded :given, 'step 2' - @formatter.step_succeeded :given, 'step 3' - - # then - @out.string.should include(" Given step 1\n And step 2\n And step 3") - end - - it 'should document additional events using And' do - # when - @formatter.step_succeeded :when, 'step 1' - @formatter.step_succeeded :when, 'step 2' - @formatter.step_succeeded :when, 'step 3' - - # then - @out.string.should include(" When step 1\n And step 2\n And step 3") - end - - it 'should document additional outcomes using And' do - # when - @formatter.step_succeeded :then, 'step 1' - @formatter.step_succeeded :then, 'step 2' - @formatter.step_succeeded :then, 'step 3' - - # then - @out.string.should include(" Then step 1\n And step 2\n And step 3") - end - - it 'should document a GivenScenario followed by a Given using And' do - # when - @formatter.step_succeeded :'given scenario', 'a scenario' - @formatter.step_succeeded :given, 'a context' - - # then - @out.string.should include(" Given scenario a scenario\n And a context") - end - - it 'should document steps with replaced params' do - @formatter.step_succeeded :given, 'a $coloured dog with $n legs', 'pink', 21 - @out.string.should include(" Given a pink dog with 21 legs") - end - - it 'should document regexp steps with replaced params' do - @formatter.step_succeeded :given, /a (pink|blue) dog with (.*) legs/, 'pink', 21 - @out.string.should include(" Given a pink dog with 21 legs") - end - - it "should append PENDING for the first pending step" do - @formatter.scenario_started('','') - @formatter.step_pending(:given, 'a context') - - @out.string.should include('Given a context (PENDING)') - end - - it "should append PENDING for pending after already pending" do - @formatter.scenario_started('','') - @formatter.step_pending(:given, 'a context') - @formatter.step_pending(:when, 'I say hey') - - @out.string.should include('When I say hey (PENDING)') - end - - it "should append FAILED for the first failiure" do - @formatter.scenario_started('','') - @formatter.step_failed(:given, 'a context') - - @out.string.should include('Given a context (FAILED)') - end - - it "should append SKIPPED for the second failiure" do - @formatter.scenario_started('','') - @formatter.step_failed(:given, 'a context') - @formatter.step_failed(:when, 'I say hey') - - @out.string.should include('When I say hey (SKIPPED)') - end - - it "should append SKIPPED for the a failiure after PENDING" do - @formatter.scenario_started('','') - @formatter.step_pending(:given, 'a context') - @formatter.step_failed(:when, 'I say hey') - - @out.string.should include('When I say hey (SKIPPED)') - end - - it 'should print some white space after each story' do - # when - @formatter.story_ended 'title', 'narrative' - - # then - @out.string.should include("\n\n") - end - - it "should print nothing for collected_steps" do - @formatter.collected_steps(['Given a $coloured $animal', 'Given a $n legged eel']) - @out.string.should == ("") - end - - it "should ignore messages it doesn't care about" do - lambda { - @formatter.this_method_does_not_exist - }.should_not raise_error - end - end - end - end - end -end diff --git a/vendor/gems/rspec/spec/spec/runner/formatter/text_mate_formatted-1.8.4.html b/vendor/gems/rspec/spec/spec/runner/formatter/text_mate_formatted-1.8.4.html deleted file mode 100644 index 3f263747a8..0000000000 --- a/vendor/gems/rspec/spec/spec/runner/formatter/text_mate_formatted-1.8.4.html +++ /dev/null @@ -1,365 +0,0 @@ - - - - - RSpec results - - - - - - -
    - - - -
    -

    RSpec Results

    - -
    -

     

    -

     

    -
    -
    - -
    -
    -
    -
    Mocker
    - -
    should be able to call mock()
    - - - -
    - should fail when expected message not received -
    -
    Mock 'poke me' expected :poke with (any args) once, but received it 0 times
    - -
    11  it "should fail when expected message not received" do
    -12    mock = mock("poke me")
    -13    mock.should_receive(:poke)
    -14  end
    -15  
    -
    -
    - -
    - should fail when messages are received out of order -
    -
    Mock 'one two three' received :three out of order
    - -
    20    mock.should_receive(:three).ordered
    -21    mock.one
    -22    mock.three
    -23    mock.two
    -24  end
    -
    -
    - -
    - should get yelled at when sending unexpected messages -
    -
    Mock 'don't talk to me' expected :any_message_at_all with (any args) 0 times, but received it once
    - -
    26  it "should get yelled at when sending unexpected messages" do
    -27    mock = mock("don't talk to me")
    -28    mock.should_not_receive(:any_message_at_all)
    -29    mock.any_message_at_all
    -30  end
    -
    -
    - -
    - has a bug we need to fix -
    -
    Expected pending 'here is the bug' to fail. No Error was raised.
    - -
    31
    -32  it "has a bug we need to fix" do
    -33    pending "here is the bug" do
    -34      # Actually, no. It's fixed. This will fail because it passes :-)
    -35      mock = mock("Bug")
    -
    -
    -
    -
    -
    -
    -
    Running specs with --diff
    - - -
    - should print diff of different strings -
    -
    expected: "RSpec is a\nbehaviour driven development\nframework for Ruby\n",
    -     got: "RSpec is a\nbehavior driven development\nframework for Ruby\n" (using ==)
    -Diff:
    -@@ -1,4 +1,4 @@
    - RSpec is a
    --behavior driven development
    -+behaviour driven development
    - framework for Ruby
    -
    - -
    11framework for Ruby
    -12EOF
    -13    usa.should == uk
    -14  end
    -
    -
    - -
    - should print diff of different objects' pretty representation -
    -
    expected <Animal
    -name=bob,
    -species=tortoise
    ->
    -, got <Animal
    -name=bob,
    -species=giraffe
    ->
    - (using .eql?)
    -Diff:
    -@@ -1,5 +1,5 @@
    - <Animal
    - name=bob,
    --species=giraffe
    -+species=tortoise
    - >
    -
    - -
    32    expected = Animal.new "bob", "giraffe"
    -33    actual   = Animal.new "bob", "tortoise"
    -34    expected.should eql(actual)
    -35  end
    -36end
    -
    -
    -
    -
    -
    -
    -
    A consumer of a stub
    - -
    should be able to stub methods on any Object
    -
    -
    -
    -
    -
    A stubbed method on a class
    - -
    should return the stubbed value
    - -
    should revert to the original method after each spec
    - -
    can stub! and mock the same message
    -
    -
    -
    -
    -
    A mock
    - -
    can stub!
    - -
    can stub! and mock
    - -
    can stub! and mock the same message
    -
    -
    -
    -
    -
    pending example (using pending method)
    - - -
    should be reported as "PENDING: for some reason" (PENDING: for some reason)
    -
    -
    -
    -
    -
    pending example (with no block)
    - - -
    should be reported as "PENDING: Not Yet Implemented" (PENDING: Not Yet Implemented)
    -
    -
    -
    -
    -
    pending example (with block for pending)
    - - -
    should have a failing block, passed to pending, reported as "PENDING: for some reason" (PENDING: for some reason)
    -
    -
    - - -
    -
    - - diff --git a/vendor/gems/rspec/spec/spec/runner/formatter/text_mate_formatted-1.8.6.html b/vendor/gems/rspec/spec/spec/runner/formatter/text_mate_formatted-1.8.6.html deleted file mode 100644 index 8a2b12e7de..0000000000 --- a/vendor/gems/rspec/spec/spec/runner/formatter/text_mate_formatted-1.8.6.html +++ /dev/null @@ -1,365 +0,0 @@ - - - - - RSpec results - - - - - - -
    - - - -
    -

    RSpec Results

    - -
    -

     

    -

     

    -
    -
    - -
    -
    -
    -
    Mocker
    - -
    should be able to call mock()
    - - - -
    - should fail when expected message not received -
    -
    Mock 'poke me' expected :poke with (any args) once, but received it 0 times
    -
    ./failing_examples/mocking_example.rb:13:
    -./spec/spec/runner/formatter/html_formatter_spec.rb:18:
    -./spec/spec/runner/formatter/html_formatter_spec.rb:14:in `chdir'
    -./spec/spec/runner/formatter/html_formatter_spec.rb:14:
    -
    11  it "should fail when expected message not received" do
    -12    mock = mock("poke me")
    -13    mock.should_receive(:poke)
    -14  end
    -15  
    -
    -
    - -
    - should fail when messages are received out of order -
    -
    Mock 'one two three' received :three out of order
    -
    ./failing_examples/mocking_example.rb:22:
    -./spec/spec/runner/formatter/html_formatter_spec.rb:18:
    -./spec/spec/runner/formatter/html_formatter_spec.rb:14:in `chdir'
    -./spec/spec/runner/formatter/html_formatter_spec.rb:14:
    -
    20    mock.should_receive(:three).ordered
    -21    mock.one
    -22    mock.three
    -23    mock.two
    -24  end
    -
    -
    - -
    - should get yelled at when sending unexpected messages -
    -
    Mock 'don't talk to me' expected :any_message_at_all with (any args) 0 times, but received it once
    -
    ./failing_examples/mocking_example.rb:28:
    -./spec/spec/runner/formatter/html_formatter_spec.rb:18:
    -./spec/spec/runner/formatter/html_formatter_spec.rb:14:in `chdir'
    -./spec/spec/runner/formatter/html_formatter_spec.rb:14:
    -
    26  it "should get yelled at when sending unexpected messages" do
    -27    mock = mock("don't talk to me")
    -28    mock.should_not_receive(:any_message_at_all)
    -29    mock.any_message_at_all
    -30  end
    -
    -
    - -
    - has a bug we need to fix -
    -
    Expected pending 'here is the bug' to fail. No Error was raised.
    - -
    31
    -32  it "has a bug we need to fix" do
    -33    pending "here is the bug" do
    -34      # Actually, no. It's fixed. This will fail because it passes :-)
    -35      mock = mock("Bug")
    -
    -
    -
    -
    -
    -
    -
    Running specs with --diff
    - - -
    - should print diff of different strings -
    -
    expected: "RSpec is a\nbehaviour driven development\nframework for Ruby\n",
    -     got: "RSpec is a\nbehavior driven development\nframework for Ruby\n" (using ==)
    -Diff:
    -@@ -1,4 +1,4 @@
    - RSpec is a
    --behavior driven development
    -+behaviour driven development
    - framework for Ruby
    -
    - -
    11framework for Ruby
    -12EOF
    -13    usa.should == uk
    -14  end
    -
    -
    - -
    - should print diff of different objects' pretty representation -
    -
    expected <Animal
    -name=bob,
    -species=tortoise
    ->
    -, got <Animal
    -name=bob,
    -species=giraffe
    ->
    - (using .eql?)
    -Diff:
    -@@ -1,5 +1,5 @@
    - <Animal
    - name=bob,
    --species=giraffe
    -+species=tortoise
    - >
    -
    -
    ./failing_examples/mocking_example.rb:33:
    -./spec/spec/runner/formatter/html_formatter_spec.rb:18:
    -./spec/spec/runner/formatter/html_formatter_spec.rb:14:in `chdir'
    -./spec/spec/runner/formatter/html_formatter_spec.rb:14:
    -
    32    expected = Animal.new "bob", "giraffe"
    -33    actual   = Animal.new "bob", "tortoise"
    -34    expected.should eql(actual)
    -35  end
    -36end
    -
    -
    -
    -
    -
    -
    -
    A consumer of a stub
    - -
    should be able to stub methods on any Object
    -
    -
    -
    -
    -
    A stubbed method on a class
    - -
    should return the stubbed value
    - -
    should revert to the original method after each spec
    - -
    can stub! and mock the same message
    -
    -
    -
    -
    -
    A mock
    - -
    can stub!
    - -
    can stub! and mock
    - -
    can stub! and mock the same message
    -
    -
    -
    -
    -
    pending example (using pending method)
    - - -
    should be reported as "PENDING: for some reason" (PENDING: for some reason)
    -
    -
    -
    -
    -
    pending example (with no block)
    - - -
    should be reported as "PENDING: Not Yet Implemented" (PENDING: Not Yet Implemented)
    -
    -
    -
    -
    -
    pending example (with block for pending)
    - - -
    should have a failing block, passed to pending, reported as "PENDING: for some reason" (PENDING: for some reason)
    -
    -
    - - -
    -
    - - diff --git a/vendor/gems/rspec/spec/spec/runner/heckle_runner_spec.rb b/vendor/gems/rspec/spec/spec/runner/heckle_runner_spec.rb deleted file mode 100644 index 539d908c2d..0000000000 --- a/vendor/gems/rspec/spec/spec/runner/heckle_runner_spec.rb +++ /dev/null @@ -1,78 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' -unless [/mswin/, /java/].detect{|p| p =~ RUBY_PLATFORM} - require 'spec/runner/heckle_runner' - - module Foo - class Bar - def one; end - def two; end - end - - class Zap - def three; end - def four; end - end - end - - describe "HeckleRunner" do - before(:each) do - @heckle = mock("heckle", :null_object => true) - @heckle_class = mock("heckle_class") - end - - it "should heckle all methods in all classes in a module" do - @heckle_class.should_receive(:new).with("Foo::Bar", "one", rspec_options).and_return(@heckle) - @heckle_class.should_receive(:new).with("Foo::Bar", "two", rspec_options).and_return(@heckle) - @heckle_class.should_receive(:new).with("Foo::Zap", "three", rspec_options).and_return(@heckle) - @heckle_class.should_receive(:new).with("Foo::Zap", "four", rspec_options).and_return(@heckle) - - heckle_runner = Spec::Runner::HeckleRunner.new("Foo", @heckle_class) - heckle_runner.heckle_with - end - - it "should heckle all methods in a class" do - @heckle_class.should_receive(:new).with("Foo::Bar", "one", rspec_options).and_return(@heckle) - @heckle_class.should_receive(:new).with("Foo::Bar", "two", rspec_options).and_return(@heckle) - - heckle_runner = Spec::Runner::HeckleRunner.new("Foo::Bar", @heckle_class) - heckle_runner.heckle_with - end - - it "should fail heckling when the class is not found" do - lambda do - heckle_runner = Spec::Runner::HeckleRunner.new("Foo::Bob", @heckle_class) - heckle_runner.heckle_with - end.should raise_error(StandardError, "Heckling failed - \"Foo::Bob\" is not a known class or module") - end - - it "should heckle specific method in a class (with #)" do - @heckle_class.should_receive(:new).with("Foo::Bar", "two", rspec_options).and_return(@heckle) - - heckle_runner = Spec::Runner::HeckleRunner.new("Foo::Bar#two", @heckle_class) - heckle_runner.heckle_with - end - - it "should heckle specific method in a class (with .)" do - @heckle_class.should_receive(:new).with("Foo::Bar", "two", rspec_options).and_return(@heckle) - - heckle_runner = Spec::Runner::HeckleRunner.new("Foo::Bar.two", @heckle_class) - heckle_runner.heckle_with - end - end - - describe "Heckler" do - it "should say yes to tests_pass? if specs pass" do - options = mock("options", :null_object => true) - options.should_receive(:run_examples).and_return(true) - heckler = Spec::Runner::Heckler.new("Foo", nil, options) - heckler.tests_pass?.should be_true - end - - it "should say no to tests_pass? if specs fail" do - options = mock("options", :null_object => true) - options.should_receive(:run_examples).and_return(false) - heckler = Spec::Runner::Heckler.new("Foo", nil, options) - heckler.tests_pass?.should be_false - end - end -end diff --git a/vendor/gems/rspec/spec/spec/runner/heckler_spec.rb b/vendor/gems/rspec/spec/spec/runner/heckler_spec.rb deleted file mode 100644 index 7cf6606ec6..0000000000 --- a/vendor/gems/rspec/spec/spec/runner/heckler_spec.rb +++ /dev/null @@ -1,13 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' -unless [/mswin/, /java/].detect{|p| p =~ RUBY_PLATFORM} - require 'spec/runner/heckle_runner' - - describe "Heckler" do - it "should run examples on tests_pass?" do - options = Spec::Runner::Options.new(StringIO.new, StringIO.new) - options.should_receive(:run_examples).with().and_return(&options.method(:run_examples)) - heckler = Spec::Runner::Heckler.new('Array', 'push', options) - heckler.tests_pass? - end - end -end diff --git a/vendor/gems/rspec/spec/spec/runner/noisy_backtrace_tweaker_spec.rb b/vendor/gems/rspec/spec/spec/runner/noisy_backtrace_tweaker_spec.rb deleted file mode 100644 index e097f2ec0d..0000000000 --- a/vendor/gems/rspec/spec/spec/runner/noisy_backtrace_tweaker_spec.rb +++ /dev/null @@ -1,45 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -module Spec - module Runner - describe "NoisyBacktraceTweaker" do - before(:each) do - @error = RuntimeError.new - @tweaker = NoisyBacktraceTweaker.new - end - - it "should leave anything in lib spec dir" do - ["expectations", "mocks", "runner", "stubs"].each do |child| - @error.set_backtrace(["/lib/spec/#{child}/anything.rb"]) - @tweaker.tweak_backtrace(@error) - @error.backtrace.should_not be_empty - end - end - - it "should leave anything in spec dir" do - @error.set_backtrace(["/lib/spec/expectations/anything.rb"]) - @tweaker.tweak_backtrace(@error) - @error.backtrace.should_not be_empty - end - - it "should leave bin spec" do - @error.set_backtrace(["bin/spec:"]) - @tweaker.tweak_backtrace(@error) - @error.backtrace.should_not be_empty - end - - it "should not barf on nil backtrace" do - lambda do - @tweaker.tweak_backtrace(@error) - end.should_not raise_error - end - - it "should clean up double slashes" do - @error.set_backtrace(["/a//b/c//d.rb"]) - @tweaker.tweak_backtrace(@error) - @error.backtrace.should include("/a/b/c/d.rb") - end - - end - end -end diff --git a/vendor/gems/rspec/spec/spec/runner/option_parser_spec.rb b/vendor/gems/rspec/spec/spec/runner/option_parser_spec.rb deleted file mode 100644 index 71619b8fc1..0000000000 --- a/vendor/gems/rspec/spec/spec/runner/option_parser_spec.rb +++ /dev/null @@ -1,378 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' -require 'fileutils' - -describe "OptionParser" do - before(:each) do - @out = StringIO.new - @err = StringIO.new - @parser = Spec::Runner::OptionParser.new(@err, @out) - end - - def parse(args) - @parser.parse(args) - @parser.options - end - - it "should accept files to include" do - options = parse(["--pattern", "foo"]) - options.filename_pattern.should == "foo" - end - - it "should accept dry run option" do - options = parse(["--dry-run"]) - options.dry_run.should be_true - end - - it "should eval and use custom formatter when none of the builtins" do - options = parse(["--format", "Custom::Formatter"]) - options.formatters[0].class.should be(Custom::Formatter) - end - - it "should support formatters with relative and absolute paths, even on windows" do - options = parse([ - "--format", "Custom::Formatter:C:\\foo\\bar", - "--format", "Custom::Formatter:foo/bar", - "--format", "Custom::Formatter:foo\\bar", - "--format", "Custom::Formatter:/foo/bar" - ]) - options.formatters[0].where.should eql("C:\\foo\\bar") - options.formatters[1].where.should eql("foo/bar") - options.formatters[2].where.should eql("foo\\bar") - options.formatters[3].where.should eql("/foo/bar") - end - - it "should not be verbose by default" do - options = parse([]) - options.verbose.should be_nil - end - - it "should not use colour by default" do - options = parse([]) - options.colour.should == false - end - - it "should print help to stdout if no args" do - pending 'A regression since 1.0.8' do - options = parse([]) - @out.rewind - @out.read.should match(/Usage: spec \(FILE\|DIRECTORY\|GLOB\)\+ \[options\]/m) - end - end - - it "should print help to stdout" do - options = parse(["--help"]) - @out.rewind - @out.read.should match(/Usage: spec \(FILE\|DIRECTORY\|GLOB\)\+ \[options\]/m) - end - - it "should print instructions about how to require missing formatter" do - lambda do - options = parse(["--format", "Custom::MissingFormatter"]) - options.formatters - end.should raise_error(NameError) - @err.string.should match(/Couldn't find formatter class Custom::MissingFormatter/n) - end - - it "should print version to stdout" do - options = parse(["--version"]) - @out.rewind - @out.read.should match(/RSpec-\d+\.\d+\.\d+.*\(build \d+\) - BDD for Ruby\nhttp:\/\/rspec.rubyforge.org\/\n/n) - end - - it "should require file when require specified" do - lambda do - parse(["--require", "whatever"]) - end.should raise_error(LoadError) - end - - it "should support c option" do - options = parse(["-c"]) - options.colour.should be_true - end - - it "should support queens colour option" do - options = parse(["--colour"]) - options.colour.should be_true - end - - it "should support us color option" do - options = parse(["--color"]) - options.colour.should be_true - end - - it "should support single example with -e option" do - options = parse(["-e", "something or other"]) - options.examples.should eql(["something or other"]) - end - - it "should support single example with -s option (will be removed when autotest supports -e)" do - options = parse(["-s", "something or other"]) - options.examples.should eql(["something or other"]) - end - - it "should support single example with --example option" do - options = parse(["--example", "something or other"]) - options.examples.should eql(["something or other"]) - end - - it "should read several example names from file if --example is given an existing file name" do - options = parse(["--example", File.dirname(__FILE__) + '/examples.txt']) - options.examples.should eql([ - "Sir, if you were my husband, I would poison your drink.", - "Madam, if you were my wife, I would drink it."]) - end - - it "should read no examples if given an empty file" do - options = parse(["--example", File.dirname(__FILE__) + '/empty_file.txt']) - options.examples.should eql([]) - end - - it "should use html formatter when format is h" do - options = parse(["--format", "h"]) - options.formatters[0].class.should equal(Spec::Runner::Formatter::HtmlFormatter) - end - - it "should use html story formatter when format is h" do - options = parse(["--format", "h"]) - options.story_formatters[0].class.should equal(Spec::Runner::Formatter::Story::HtmlFormatter) - end - - it "should use html formatter when format is html" do - options = parse(["--format", "html"]) - options.formatters[0].class.should equal(Spec::Runner::Formatter::HtmlFormatter) - end - - it "should use html story formatter when format is html" do - options = parse(["--format", "html"]) - options.story_formatters[0].class.should equal(Spec::Runner::Formatter::Story::HtmlFormatter) - end - - it "should use html formatter with explicit output when format is html:test.html" do - FileUtils.rm 'test.html' if File.exist?('test.html') - options = parse(["--format", "html:test.html"]) - options.formatters # creates the file - File.should be_exist('test.html') - options.formatters[0].class.should equal(Spec::Runner::Formatter::HtmlFormatter) - options.formatters[0].close - FileUtils.rm 'test.html' - end - - it "should use noisy backtrace tweaker with b option" do - options = parse(["-b"]) - options.backtrace_tweaker.should be_instance_of(Spec::Runner::NoisyBacktraceTweaker) - end - - it "should use noisy backtrace tweaker with backtrace option" do - options = parse(["--backtrace"]) - options.backtrace_tweaker.should be_instance_of(Spec::Runner::NoisyBacktraceTweaker) - end - - it "should use quiet backtrace tweaker by default" do - options = parse([]) - options.backtrace_tweaker.should be_instance_of(Spec::Runner::QuietBacktraceTweaker) - end - - it "should use progress bar formatter by default" do - options = parse([]) - options.formatters[0].class.should equal(Spec::Runner::Formatter::ProgressBarFormatter) - end - - it "should use specdoc formatter when format is s" do - options = parse(["--format", "s"]) - options.formatters[0].class.should equal(Spec::Runner::Formatter::SpecdocFormatter) - end - - it "should use specdoc formatter when format is specdoc" do - options = parse(["--format", "specdoc"]) - options.formatters[0].class.should equal(Spec::Runner::Formatter::SpecdocFormatter) - end - - it "should support diff option when format is not specified" do - options = parse(["--diff"]) - options.diff_format.should == :unified - end - - it "should use unified diff format option when format is unified" do - options = parse(["--diff", "unified"]) - options.diff_format.should == :unified - options.differ_class.should equal(Spec::Expectations::Differs::Default) - end - - it "should use context diff format option when format is context" do - options = parse(["--diff", "context"]) - options.diff_format.should == :context - options.differ_class.should == Spec::Expectations::Differs::Default - end - - it "should use custom diff format option when format is a custom format" do - Spec::Expectations.differ.should_not be_instance_of(Custom::Differ) - - options = parse(["--diff", "Custom::Differ"]) - options.parse_diff "Custom::Differ" - options.diff_format.should == :custom - options.differ_class.should == Custom::Differ - Spec::Expectations.differ.should be_instance_of(Custom::Differ) - end - - it "should print instructions about how to fix missing differ" do - lambda { parse(["--diff", "Custom::MissingFormatter"]) }.should raise_error(NameError) - @err.string.should match(/Couldn't find differ class Custom::MissingFormatter/n) - end - - describe "when attempting a focussed spec" do - attr_reader :file, :dir - before do - @original_rspec_options = $rspec_options - @file = "#{File.dirname(__FILE__)}/spec_parser/spec_parser_fixture.rb" - @dir = File.dirname(file) - end - - after do - $rspec_options = @original_rspec_options - end - - def parse(args) - options = super - $rspec_options = options - options.filename_pattern = "*_fixture.rb" - options - end - - it "should support --line to identify spec" do - options = parse([file, "--line", "13"]) - options.line_number.should == 13 - options.examples.should be_empty - options.run_examples - options.examples.should eql(["d"]) - end - - it "should fail with error message if file is dir along with --line" do - options = parse([dir, "--line", "169"]) - options.line_number.should == 169 - options.run_examples - @err.string.should match(/You must specify one file, not a directory when using the --line option/n) - end - - it "should fail with error message if file does not exist along with --line" do - options = parse(["some file", "--line", "169"]) - proc do - options.run_examples - end.should raise_error - end - - it "should fail with error message if more than one files are specified along with --line" do - options = parse([file, file, "--line", "169"]) - options.run_examples - @err.string.should match(/Only one file can be specified when using the --line option/n) - end - - it "should fail with error message if --example and --line are used simultaneously" do - options = parse([file, "--example", "some example", "--line", "169"]) - options.run_examples - @err.string.should match(/You cannot use both --line and --example/n) - end - end - - if [/mswin/, /java/].detect{|p| p =~ RUBY_PLATFORM} - it "should barf when --heckle is specified (and platform is windows)" do - lambda do - options = parse(["--heckle", "Spec"]) - end.should raise_error(StandardError, "Heckle not supported on Windows") - end - else - it "should heckle when --heckle is specified (and platform is not windows)" do - options = parse(["--heckle", "Spec"]) - options.heckle_runner.should be_instance_of(Spec::Runner::HeckleRunner) - end - end - - it "should read options from file when --options is specified" do - options = parse(["--options", File.dirname(__FILE__) + "/spec.opts"]) - options.diff_format.should_not be_nil - options.colour.should be_true - end - - it "should default the formatter to ProgressBarFormatter when using options file" do - options = parse(["--options", File.dirname(__FILE__) + "/spec.opts"]) - options.formatters.first.should be_instance_of(::Spec::Runner::Formatter::ProgressBarFormatter) - end - - it "should read spaced and multi-line options from file when --options is specified" do - options = parse(["--options", File.dirname(__FILE__) + "/spec_spaced.opts"]) - options.diff_format.should_not be_nil - options.colour.should be_true - options.formatters.first.should be_instance_of(::Spec::Runner::Formatter::SpecdocFormatter) - end - - it "should save config to file when --generate-options is specified" do - FileUtils.rm 'test.spec.opts' if File.exist?('test.spec.opts') - options = parse(["--colour", "--generate-options", "test.spec.opts", "--diff"]) - IO.read('test.spec.opts').should == "--colour\n--diff\n" - FileUtils.rm 'test.spec.opts' - end - - it "should save config to file when -G is specified" do - FileUtils.rm 'test.spec.opts' if File.exist?('test.spec.opts') - options = parse(["--colour", "-G", "test.spec.opts", "--diff"]) - IO.read('test.spec.opts').should == "--colour\n--diff\n" - FileUtils.rm 'test.spec.opts' - end - - it "when --drb is specified, calls DrbCommandLine all of the other ARGV arguments" do - options = Spec::Runner::OptionParser.parse([ - "some/spec.rb", "--diff", "--colour" - ], @err, @out) - Spec::Runner::DrbCommandLine.should_receive(:run).and_return do |options| - options.argv.should == ["some/spec.rb", "--diff", "--colour"] - end - parse(["some/spec.rb", "--diff", "--drb", "--colour"]) - end - - it "should reverse spec order when --reverse is specified" do - options = parse(["some/spec.rb", "--reverse"]) - end - - it "should set an mtime comparator when --loadby mtime" do - options = parse(["--loadby", 'mtime']) - runner = Spec::Runner::ExampleGroupRunner.new(options) - Spec::Runner::ExampleGroupRunner.should_receive(:new). - with(options). - and_return(runner) - runner.should_receive(:load_files).with(["most_recent_spec.rb", "command_line_spec.rb"]) - - Dir.chdir(File.dirname(__FILE__)) do - options.files << 'command_line_spec.rb' - options.files << 'most_recent_spec.rb' - FileUtils.touch "most_recent_spec.rb" - options.run_examples - FileUtils.rm "most_recent_spec.rb" - end - end - - it "should use the standard runner by default" do - runner = ::Spec::Runner::ExampleGroupRunner.new(@parser.options) - ::Spec::Runner::ExampleGroupRunner.should_receive(:new). - with(@parser.options). - and_return(runner) - options = parse([]) - options.run_examples - end - - it "should use a custom runner when given" do - runner = Custom::ExampleGroupRunner.new(@parser.options, nil) - Custom::ExampleGroupRunner.should_receive(:new). - with(@parser.options, nil). - and_return(runner) - options = parse(["--runner", "Custom::ExampleGroupRunner"]) - options.run_examples - end - - it "should use a custom runner with extra options" do - runner = Custom::ExampleGroupRunner.new(@parser.options, 'something') - Custom::ExampleGroupRunner.should_receive(:new). - with(@parser.options, 'something'). - and_return(runner) - options = parse(["--runner", "Custom::ExampleGroupRunner:something"]) - options.run_examples - end -end diff --git a/vendor/gems/rspec/spec/spec/runner/options_spec.rb b/vendor/gems/rspec/spec/spec/runner/options_spec.rb deleted file mode 100644 index 6f0893751c..0000000000 --- a/vendor/gems/rspec/spec/spec/runner/options_spec.rb +++ /dev/null @@ -1,364 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -module Spec - module Runner - describe Options do - before(:each) do - @err = StringIO.new('') - @out = StringIO.new('') - @options = Options.new(@err, @out) - end - - after(:each) do - Spec::Expectations.differ = nil - end - - describe "#examples" do - it "should default to empty array" do - @options.examples.should == [] - end - end - - describe "#include_pattern" do - it "should default to '**/*_spec.rb'" do - @options.filename_pattern.should == "**/*_spec.rb" - end - end - - describe "#files_to_load" do - - it "should load files not following pattern if named explicitly" do - file = File.expand_path(File.dirname(__FILE__) + "/resources/a_bar.rb") - @options.files << file - @options.files_to_load.should include(file) - end - - describe "with default --pattern" do - it "should load files named _spec.rb" do - dir = File.expand_path(File.dirname(__FILE__) + "/resources/") - @options.files << dir - @options.files_to_load.should == ["#{dir}/a_spec.rb"] - end - end - - describe "with explicit pattern (single)" do - before(:each) do - @options.filename_pattern = "**/*_foo.rb" - end - - it "should load files following pattern" do - file = File.expand_path(File.dirname(__FILE__) + "/resources/a_foo.rb") - @options.files << file - @options.files_to_load.should include(file) - end - - it "should load files in directories following pattern" do - dir = File.expand_path(File.dirname(__FILE__) + "/resources") - @options.files << dir - @options.files_to_load.should include("#{dir}/a_foo.rb") - end - - it "should not load files in directories not following pattern" do - dir = File.expand_path(File.dirname(__FILE__) + "/resources") - @options.files << dir - @options.files_to_load.should_not include("#{dir}/a_bar.rb") - end - end - - describe "with explicit pattern (comma,separated,values)" do - - before(:each) do - @options.filename_pattern = "**/*_foo.rb,**/*_bar.rb" - end - - it "should support comma separated values" do - dir = File.expand_path(File.dirname(__FILE__) + "/resources") - @options.files << dir - @options.files_to_load.should include("#{dir}/a_foo.rb") - @options.files_to_load.should include("#{dir}/a_bar.rb") - end - - it "should support comma separated values with spaces" do - dir = File.expand_path(File.dirname(__FILE__) + "/resources") - @options.files << dir - @options.files_to_load.should include("#{dir}/a_foo.rb") - @options.files_to_load.should include("#{dir}/a_bar.rb") - end - - end - - end - - describe "#backtrace_tweaker" do - it "should default to QuietBacktraceTweaker" do - @options.backtrace_tweaker.class.should == QuietBacktraceTweaker - end - end - - describe "#dry_run" do - it "should default to false" do - @options.dry_run.should == false - end - end - - describe "#context_lines" do - it "should default to 3" do - @options.context_lines.should == 3 - end - end - - describe "#parse_diff with nil" do - before(:each) do - @options.parse_diff nil - end - - it "should make diff_format unified" do - @options.diff_format.should == :unified - end - - it "should set Spec::Expectations.differ to be a default differ" do - Spec::Expectations.differ.class.should == - ::Spec::Expectations::Differs::Default - end - end - - describe "#parse_diff with 'unified'" do - before(:each) do - @options.parse_diff 'unified' - end - - it "should make diff_format unified and uses default differ_class" do - @options.diff_format.should == :unified - @options.differ_class.should equal(Spec::Expectations::Differs::Default) - end - - it "should set Spec::Expectations.differ to be a default differ" do - Spec::Expectations.differ.class.should == - ::Spec::Expectations::Differs::Default - end - end - - describe "#parse_diff with 'context'" do - before(:each) do - @options.parse_diff 'context' - end - - it "should make diff_format context and uses default differ_class" do - @options.diff_format.should == :context - @options.differ_class.should == Spec::Expectations::Differs::Default - end - - it "should set Spec::Expectations.differ to be a default differ" do - Spec::Expectations.differ.class.should == - ::Spec::Expectations::Differs::Default - end - end - - describe "#parse_diff with Custom::Differ" do - before(:each) do - @options.parse_diff 'Custom::Differ' - end - - it "should use custom differ_class" do - @options.diff_format.should == :custom - @options.differ_class.should == Custom::Differ - Spec::Expectations.differ.should be_instance_of(Custom::Differ) - end - - it "should set Spec::Expectations.differ to be a default differ" do - Spec::Expectations.differ.class.should == - ::Custom::Differ - end - end - - describe "#parse_diff with missing class name" do - it "should raise error" do - lambda { @options.parse_diff "Custom::MissingDiffer" }.should raise_error(NameError) - @err.string.should match(/Couldn't find differ class Custom::MissingDiffer/n) - end - end - - describe "#parse_example" do - it "with argument thats not a file path, sets argument as the example" do - example = "something or other" - File.file?(example).should == false - @options.parse_example example - @options.examples.should eql(["something or other"]) - end - - it "with argument that is a file path, sets examples to contents of the file" do - example = "#{File.dirname(__FILE__)}/examples.txt" - File.should_receive(:file?).with(example).and_return(true) - file = StringIO.new("Sir, if you were my husband, I would poison your drink.\nMadam, if you were my wife, I would drink it.") - File.should_receive(:open).with(example).and_return(file) - - @options.parse_example example - @options.examples.should eql([ - "Sir, if you were my husband, I would poison your drink.", - "Madam, if you were my wife, I would drink it." - ]) - end - end - - describe "#examples_should_not_be_run" do - it "should cause #run_examples to return true and do nothing" do - @options.examples_should_not_be_run - ExampleGroupRunner.should_not_receive(:new) - - @options.run_examples.should be_true - end - end - - describe "#load_class" do - it "should raise error when not class name" do - lambda do - @options.send(:load_class, 'foo', 'fruit', '--food') - end.should raise_error('"foo" is not a valid class name') - end - end - - describe "#reporter" do - it "returns a Reporter" do - @options.reporter.should be_instance_of(Reporter) - @options.reporter.options.should === @options - end - end - - describe "#add_example_group affecting passed in example_group" do - it "runs all examples when options.examples is nil" do - example_1_has_run = false - example_2_has_run = false - @example_group = Class.new(::Spec::Example::ExampleGroup).describe("Some Examples") do - it "runs 1" do - example_1_has_run = true - end - it "runs 2" do - example_2_has_run = true - end - end - - @options.examples = nil - - @options.add_example_group @example_group - @options.run_examples - example_1_has_run.should be_true - example_2_has_run.should be_true - end - - it "keeps all example_definitions when options.examples is empty" do - example_1_has_run = false - example_2_has_run = false - @example_group = Class.new(::Spec::Example::ExampleGroup).describe("Some Examples") do - it "runs 1" do - example_1_has_run = true - end - it "runs 2" do - example_2_has_run = true - end - end - - @options.examples = [] - - @options.add_example_group @example_group - @options.run_examples - example_1_has_run.should be_true - example_2_has_run.should be_true - end - end - - describe "#add_example_group affecting example_group" do - it "adds example_group when example_group has example_definitions and is not shared" do - @example_group = Class.new(::Spec::Example::ExampleGroup).describe("Some Examples") do - it "uses this example_group" do - end - end - - @options.number_of_examples.should == 0 - @options.add_example_group @example_group - @options.number_of_examples.should == 1 - @options.example_groups.length.should == 1 - end - end - - describe "#remove_example_group" do - it "should remove the ExampleGroup from the list of ExampleGroups" do - @example_group = Class.new(::Spec::Example::ExampleGroup).describe("Some Examples") do - end - @options.add_example_group @example_group - @options.example_groups.should include(@example_group) - - @options.remove_example_group @example_group - @options.example_groups.should_not include(@example_group) - end - end - - describe "#run_examples" do - it "should use the standard runner by default" do - runner = ::Spec::Runner::ExampleGroupRunner.new(@options) - ::Spec::Runner::ExampleGroupRunner.should_receive(:new). - with(@options). - and_return(runner) - @options.user_input_for_runner = nil - - @options.run_examples - end - - it "should use a custom runner when given" do - runner = Custom::ExampleGroupRunner.new(@options, nil) - Custom::ExampleGroupRunner.should_receive(:new). - with(@options, nil). - and_return(runner) - @options.user_input_for_runner = "Custom::ExampleGroupRunner" - - @options.run_examples - end - - it "should use a custom runner with extra options" do - runner = Custom::ExampleGroupRunner.new(@options, 'something') - Custom::ExampleGroupRunner.should_receive(:new). - with(@options, 'something'). - and_return(runner) - @options.user_input_for_runner = "Custom::ExampleGroupRunner:something" - - @options.run_examples - end - - describe "when there are examples" do - before(:each) do - @options.add_example_group Class.new(::Spec::Example::ExampleGroup) - @options.formatters << Formatter::BaseTextFormatter.new(@options, @out) - end - - it "runs the Examples and outputs the result" do - @options.run_examples - @out.string.should include("0 examples, 0 failures") - end - - it "sets #examples_run? to true" do - @options.examples_run?.should be_false - @options.run_examples - @options.examples_run?.should be_true - end - end - - describe "when there are no examples" do - before(:each) do - @options.formatters << Formatter::BaseTextFormatter.new(@options, @out) - end - - it "does not run Examples and does not output a result" do - @options.run_examples - @out.string.should_not include("examples") - @out.string.should_not include("failures") - end - - it "sets #examples_run? to false" do - @options.examples_run?.should be_false - @options.run_examples - @options.examples_run?.should be_false - end - end - end - end - end -end diff --git a/vendor/gems/rspec/spec/spec/runner/output_one_time_fixture.rb b/vendor/gems/rspec/spec/spec/runner/output_one_time_fixture.rb deleted file mode 100644 index 444730dc3e..0000000000 --- a/vendor/gems/rspec/spec/spec/runner/output_one_time_fixture.rb +++ /dev/null @@ -1,7 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -describe "Running an Example" do - it "should not output twice" do - true.should be_true - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/runner/output_one_time_fixture_runner.rb b/vendor/gems/rspec/spec/spec/runner/output_one_time_fixture_runner.rb deleted file mode 100644 index a0e61316ee..0000000000 --- a/vendor/gems/rspec/spec/spec/runner/output_one_time_fixture_runner.rb +++ /dev/null @@ -1,8 +0,0 @@ -dir = File.dirname(__FILE__) -require "#{dir}/../../spec_helper" - -triggering_double_output = rspec_options -options = Spec::Runner::OptionParser.parse( - ["#{dir}/output_one_time_fixture.rb"], $stderr, $stdout -) -Spec::Runner::CommandLine.run(options) diff --git a/vendor/gems/rspec/spec/spec/runner/output_one_time_spec.rb b/vendor/gems/rspec/spec/spec/runner/output_one_time_spec.rb deleted file mode 100644 index 8f67a380aa..0000000000 --- a/vendor/gems/rspec/spec/spec/runner/output_one_time_spec.rb +++ /dev/null @@ -1,16 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -module Spec - module Runner - describe CommandLine do - it "should not output twice" do - dir = File.dirname(__FILE__) - Dir.chdir("#{dir}/../../..") do - output =`ruby #{dir}/output_one_time_fixture_runner.rb` - output.should include("1 example, 0 failures") - output.should_not include("0 examples, 0 failures") - end - end - end - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/runner/quiet_backtrace_tweaker_spec.rb b/vendor/gems/rspec/spec/spec/runner/quiet_backtrace_tweaker_spec.rb deleted file mode 100644 index e47b6c735e..0000000000 --- a/vendor/gems/rspec/spec/spec/runner/quiet_backtrace_tweaker_spec.rb +++ /dev/null @@ -1,56 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -module Spec - module Runner - describe "QuietBacktraceTweaker" do - before(:each) do - @error = RuntimeError.new - @tweaker = QuietBacktraceTweaker.new - end - - it "should not barf on nil backtrace" do - lambda do - @tweaker.tweak_backtrace(@error) - end.should_not raise_error - end - - it "should remove anything from textmate ruby bundle" do - @error.set_backtrace(["/Applications/TextMate.app/Contents/SharedSupport/Bundles/Ruby.tmbundle/Support/tmruby.rb:147"]) - @tweaker.tweak_backtrace(@error) - @error.backtrace.should be_empty - end - - it "should remove anything in lib spec dir" do - ["expectations", "mocks", "runner"].each do |child| - element="/lib/spec/#{child}/anything.rb" - @error.set_backtrace([element]) - @tweaker.tweak_backtrace(@error) - unless (@error.backtrace.empty?) - raise("Should have tweaked away '#{element}'") - end - end - end - - it "should remove mock_frameworks/rspec" do - element = "mock_frameworks/rspec" - @error.set_backtrace([element]) - @tweaker.tweak_backtrace(@error) - unless (@error.backtrace.empty?) - raise("Should have tweaked away '#{element}'") - end - end - - it "should remove bin spec" do - @error.set_backtrace(["bin/spec:"]) - @tweaker.tweak_backtrace(@error) - @error.backtrace.should be_empty - end - - it "should clean up double slashes" do - @error.set_backtrace(["/a//b/c//d.rb"]) - @tweaker.tweak_backtrace(@error) - @error.backtrace.should include("/a/b/c/d.rb") - end - end - end -end diff --git a/vendor/gems/rspec/spec/spec/runner/reporter_spec.rb b/vendor/gems/rspec/spec/spec/runner/reporter_spec.rb deleted file mode 100644 index 52377e7f30..0000000000 --- a/vendor/gems/rspec/spec/spec/runner/reporter_spec.rb +++ /dev/null @@ -1,189 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -module Spec - module Runner - describe Reporter do - attr_reader :formatter_output, :options, :backtrace_tweaker, :formatter, :reporter, :example_group - before(:each) do - @formatter_output = StringIO.new - @options = Options.new(StringIO.new, StringIO.new) - @backtrace_tweaker = stub("backtrace tweaker", :tweak_backtrace => nil) - options.backtrace_tweaker = backtrace_tweaker - @formatter = ::Spec::Runner::Formatter::BaseTextFormatter.new(options, formatter_output) - options.formatters << formatter - @reporter = Reporter.new(options) - @example_group = create_example_group("example_group") - reporter.add_example_group example_group - end - - def failure - Mocks::DuckTypeArgConstraint.new(:header, :exception) - end - - def create_example_group(description_text) - example_group = Class.new(Spec::Example::ExampleGroup) - example_group.describe description_text - example_group - end - - it "should assign itself as the reporter to options" do - options.reporter.should equal(@reporter) - end - - it "should tell formatter when example_group is added" do - formatter.should_receive(:add_example_group).with(example_group) - reporter.add_example_group(example_group) - end - - it "should handle multiple example_groups with same name" do - formatter.should_receive(:add_example_group).exactly(3).times - formatter.should_receive(:example_started).exactly(3).times - formatter.should_receive(:example_passed).exactly(3).times - formatter.should_receive(:start_dump) - formatter.should_receive(:dump_pending) - formatter.should_receive(:close).with(no_args) - formatter.should_receive(:dump_summary).with(anything(), 3, 0, 0) - reporter.add_example_group(create_example_group("example_group")) - reporter.example_started("spec 1") - reporter.example_finished("spec 1") - reporter.add_example_group(create_example_group("example_group")) - reporter.example_started("spec 2") - reporter.example_finished("spec 2") - reporter.add_example_group(create_example_group("example_group")) - reporter.example_started("spec 3") - reporter.example_finished("spec 3") - reporter.dump - end - - it "should handle multiple examples with the same name" do - error=RuntimeError.new - passing = ExampleGroup.new("example") - failing = ExampleGroup.new("example") - - formatter.should_receive(:add_example_group).exactly(2).times - formatter.should_receive(:example_passed).with(passing).exactly(2).times - formatter.should_receive(:example_failed).with(failing, 1, failure) - formatter.should_receive(:example_failed).with(failing, 2, failure) - formatter.should_receive(:dump_failure).exactly(2).times - formatter.should_receive(:start_dump) - formatter.should_receive(:dump_pending) - formatter.should_receive(:close).with(no_args) - formatter.should_receive(:dump_summary).with(anything(), 4, 2, 0) - backtrace_tweaker.should_receive(:tweak_backtrace).twice - - reporter.add_example_group(create_example_group("example_group")) - reporter.example_finished(passing) - reporter.example_finished(failing, error) - - reporter.add_example_group(create_example_group("example_group")) - reporter.example_finished(passing) - reporter.example_finished(failing, error) - reporter.dump - end - - it "should push stats to formatter even with no data" do - formatter.should_receive(:start_dump) - formatter.should_receive(:dump_pending) - formatter.should_receive(:dump_summary).with(anything(), 0, 0, 0) - formatter.should_receive(:close).with(no_args) - reporter.dump - end - - it "should push time to formatter" do - formatter.should_receive(:start).with(5) - formatter.should_receive(:start_dump) - formatter.should_receive(:dump_pending) - formatter.should_receive(:close).with(no_args) - formatter.should_receive(:dump_summary) do |time, a, b| - time.to_s.should match(/[0-9].[0-9|e|-]+/) - end - reporter.start(5) - reporter.end - reporter.dump - end - - describe Reporter, "reporting one passing example" do - it "should tell formatter example passed" do - formatter.should_receive(:example_passed) - reporter.example_finished("example") - end - - it "should not delegate to backtrace tweaker" do - formatter.should_receive(:example_passed) - backtrace_tweaker.should_not_receive(:tweak_backtrace) - reporter.example_finished("example") - end - - it "should account for passing example in stats" do - formatter.should_receive(:example_passed) - formatter.should_receive(:start_dump) - formatter.should_receive(:dump_pending) - formatter.should_receive(:dump_summary).with(anything(), 1, 0, 0) - formatter.should_receive(:close).with(no_args) - reporter.example_finished("example") - reporter.dump - end - end - - describe Reporter, "reporting one failing example" do - it "should tell formatter that example failed" do - formatter.should_receive(:example_failed) - reporter.example_finished(example_group, RuntimeError.new) - end - - it "should delegate to backtrace tweaker" do - formatter.should_receive(:example_failed) - backtrace_tweaker.should_receive(:tweak_backtrace) - reporter.example_finished(ExampleGroup.new("example"), RuntimeError.new) - end - - it "should account for failing example in stats" do - example = ExampleGroup.new("example") - formatter.should_receive(:example_failed).with(example, 1, failure) - formatter.should_receive(:start_dump) - formatter.should_receive(:dump_pending) - formatter.should_receive(:dump_failure).with(1, anything()) - formatter.should_receive(:dump_summary).with(anything(), 1, 1, 0) - formatter.should_receive(:close).with(no_args) - reporter.example_finished(example, RuntimeError.new) - reporter.dump - end - - end - - describe Reporter, "reporting one pending example (ExamplePendingError)" do - it "should tell formatter example is pending" do - example = ExampleGroup.new("example") - formatter.should_receive(:example_pending).with(example_group.description, example, "reason") - formatter.should_receive(:add_example_group).with(example_group) - reporter.add_example_group(example_group) - reporter.example_finished(example, Spec::Example::ExamplePendingError.new("reason")) - end - - it "should account for pending example in stats" do - example = ExampleGroup.new("example") - formatter.should_receive(:example_pending).with(example_group.description, example, "reason") - formatter.should_receive(:start_dump) - formatter.should_receive(:dump_pending) - formatter.should_receive(:dump_summary).with(anything(), 1, 0, 1) - formatter.should_receive(:close).with(no_args) - formatter.should_receive(:add_example_group).with(example_group) - reporter.add_example_group(example_group) - reporter.example_finished(example, Spec::Example::ExamplePendingError.new("reason")) - reporter.dump - end - end - - describe Reporter, "reporting one pending example (PendingExampleFixedError)" do - it "should tell formatter pending example is fixed" do - formatter.should_receive(:example_failed) do |name, counter, failure| - failure.header.should == "'example_group example' FIXED" - end - formatter.should_receive(:add_example_group).with(example_group) - reporter.add_example_group(example_group) - reporter.example_finished(ExampleGroup.new("example"), Spec::Example::PendingExampleFixedError.new("reason")) - end - end - end - end -end diff --git a/vendor/gems/rspec/spec/spec/runner/resources/a_bar.rb b/vendor/gems/rspec/spec/spec/runner/resources/a_bar.rb deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/vendor/gems/rspec/spec/spec/runner/resources/a_foo.rb b/vendor/gems/rspec/spec/spec/runner/resources/a_foo.rb deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/vendor/gems/rspec/spec/spec/runner/resources/a_spec.rb b/vendor/gems/rspec/spec/spec/runner/resources/a_spec.rb deleted file mode 100644 index d9b67cc767..0000000000 --- a/vendor/gems/rspec/spec/spec/runner/resources/a_spec.rb +++ /dev/null @@ -1 +0,0 @@ -# Empty - used by ../options_spec.rb \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/runner/spec.opts b/vendor/gems/rspec/spec/spec/runner/spec.opts deleted file mode 100644 index fd816a4247..0000000000 --- a/vendor/gems/rspec/spec/spec/runner/spec.opts +++ /dev/null @@ -1,2 +0,0 @@ ---diff ---colour \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/runner/spec_parser/spec_parser_fixture.rb b/vendor/gems/rspec/spec/spec/runner/spec_parser/spec_parser_fixture.rb deleted file mode 100644 index 14602d934b..0000000000 --- a/vendor/gems/rspec/spec/spec/runner/spec_parser/spec_parser_fixture.rb +++ /dev/null @@ -1,70 +0,0 @@ -require File.dirname(__FILE__) + '/../../../spec_helper.rb' - -describe "c" do - - it "1" do - end - - it "2" do - end - -end - -describe "d" do - - it "3" do - end - - it "4" do - end - -end - -class SpecParserSubject -end - -describe SpecParserSubject do - - it "5" do - end - -end - -describe SpecParserSubject, "described" do - - it "6" do - end - -end - -describe SpecParserSubject, "described", :something => :something_else do - - it "7" do - end - -end - -describe "described", :something => :something_else do - - it "8" do - end - -end - -describe "e" do - - it "9" do - end - - it "10" do - end - - describe "f" do - it "11" do - end - - it "12" do - end - end - -end diff --git a/vendor/gems/rspec/spec/spec/runner/spec_parser_spec.rb b/vendor/gems/rspec/spec/spec/runner/spec_parser_spec.rb deleted file mode 100644 index 3d8d9c2e92..0000000000 --- a/vendor/gems/rspec/spec/spec/runner/spec_parser_spec.rb +++ /dev/null @@ -1,79 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper.rb' - -describe "SpecParser" do - attr_reader :parser, :file - before(:each) do - @parser = Spec::Runner::SpecParser.new - @file = "#{File.dirname(__FILE__)}/spec_parser/spec_parser_fixture.rb" - require file - end - - it "should find spec name for 'specify' at same line" do - parser.spec_name_for(file, 5).should == "c 1" - end - - it "should find spec name for 'specify' at end of spec line" do - parser.spec_name_for(file, 6).should == "c 1" - end - - it "should find context for 'context' above all specs" do - parser.spec_name_for(file, 4).should == "c" - end - - it "should find spec name for 'it' at same line" do - parser.spec_name_for(file, 15).should == "d 3" - end - - it "should find spec name for 'it' at end of spec line" do - parser.spec_name_for(file, 16).should == "d 3" - end - - it "should find context for 'describe' above all specs" do - parser.spec_name_for(file, 14).should == "d" - end - - it "should find nearest example name between examples" do - parser.spec_name_for(file, 7).should == "c 1" - end - - it "should find nothing outside a context" do - parser.spec_name_for(file, 2).should be_nil - end - - it "should find context name for type" do - parser.spec_name_for(file, 26).should == "SpecParserSubject" - end - - it "should find context and spec name for type" do - parser.spec_name_for(file, 28).should == "SpecParserSubject 5" - end - - it "should find context and description for type" do - parser.spec_name_for(file, 33).should == "SpecParserSubject described" - end - - it "should find context and description and example for type" do - parser.spec_name_for(file, 36).should == "SpecParserSubject described 6" - end - - it "should find context and description for type with modifications" do - parser.spec_name_for(file, 40).should == "SpecParserSubject described" - end - - it "should find context and described and example for type with modifications" do - parser.spec_name_for(file, 43).should == "SpecParserSubject described 7" - end - - it "should find example group" do - parser.spec_name_for(file, 47).should == "described" - end - - it "should find example" do - parser.spec_name_for(file, 50).should == "described 8" - end - - it "should find nested example" do - parser.spec_name_for(file, 63).should == "e f 11" - end - -end diff --git a/vendor/gems/rspec/spec/spec/runner/spec_spaced.opts b/vendor/gems/rspec/spec/spec/runner/spec_spaced.opts deleted file mode 100644 index 6b3efd20f2..0000000000 --- a/vendor/gems/rspec/spec/spec/runner/spec_spaced.opts +++ /dev/null @@ -1,2 +0,0 @@ ---diff --colour ---format s \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/runner_spec.rb b/vendor/gems/rspec/spec/spec/runner_spec.rb deleted file mode 100644 index d75e661115..0000000000 --- a/vendor/gems/rspec/spec/spec/runner_spec.rb +++ /dev/null @@ -1,11 +0,0 @@ -require File.dirname(__FILE__) + '/../spec_helper.rb' - -module Spec - describe Runner, ".configure" do - it "should yield global configuration" do - Spec::Runner.configure do |config| - config.should equal(Spec::Runner.configuration) - end - end - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/spec_classes.rb b/vendor/gems/rspec/spec/spec/spec_classes.rb deleted file mode 100644 index c8900a789f..0000000000 --- a/vendor/gems/rspec/spec/spec/spec_classes.rb +++ /dev/null @@ -1,133 +0,0 @@ -# This file contains various classes used by the specs. -module Spec - module Expectations - class Person - attr_reader :name - def initialize name - @name = name - end - def == other - return @name == other.name - end - end - - class ClassWithMultiWordPredicate - def multi_word_predicate? - true - end - end - - module Helper - class CollectionWithSizeMethod - def initialize; @list = []; end - def size; @list.size; end - def push(item); @list.push(item); end - end - - class CollectionWithLengthMethod - def initialize; @list = []; end - def length; @list.size; end - def push(item); @list.push(item); end - end - - class CollectionOwner - attr_reader :items_in_collection_with_size_method, :items_in_collection_with_length_method - - def initialize - @items_in_collection_with_size_method = CollectionWithSizeMethod.new - @items_in_collection_with_length_method = CollectionWithLengthMethod.new - end - - def add_to_collection_with_size_method(item) - @items_in_collection_with_size_method.push(item) - end - - def add_to_collection_with_length_method(item) - @items_in_collection_with_length_method.push(item) - end - - def items_for(arg) - return [1, 2, 3] if arg == 'a' - [1] - end - - def items - @items_in_collection_with_size_method - end - end - - class HandCodedMock - include Spec::Matchers - def initialize(return_val) - @return_val = return_val - @funny_called = false - end - - def funny? - @funny_called = true - @return_val - end - - def hungry?(a, b, c) - a.should equal(1) - b.should equal(2) - c.should equal(3) - @funny_called = true - @return_val - end - - def exists? - @return_val - end - - def multi_word_predicate? - @return_val - end - - def rspec_verify - @funny_called.should be_true - end - end - class ClassWithUnqueriedPredicate - attr_accessor :foo - def initialize(foo) - @foo = foo - end - end - end - end -end - -module Custom - require 'spec/runner/formatter/base_text_formatter' - class Formatter < Spec::Runner::Formatter::BaseTextFormatter - attr_reader :options, :where - - def initialize(options, where) - @options = options - @where = where - end - end - - class BadFormatter < Spec::Runner::Formatter::BaseTextFormatter - attr_reader :where - - def initialize(options, where) - bad_method - end - end - - class Differ - attr_reader :options - def initialize(options) - @options = options - end - - def diff_as_object(target, expected) - "" - end - end -end - -class FakeReporter < Spec::Runner::Reporter -end diff --git a/vendor/gems/rspec/spec/spec/story/builders.rb b/vendor/gems/rspec/spec/spec/story/builders.rb deleted file mode 100644 index 77d50d53ee..0000000000 --- a/vendor/gems/rspec/spec/spec/story/builders.rb +++ /dev/null @@ -1,46 +0,0 @@ -module Spec - module Story - class StoryBuilder - def initialize - @title = 'a story' - @narrative = 'narrative' - end - - def title(value) - @title = value - self - end - - def narrative(value) - @narrative = value - self - end - - def to_story(&block) - block = lambda {} unless block_given? - Story.new @title, @narrative, &block - end - end - - class ScenarioBuilder - def initialize - @name = 'a scenario' - @story = StoryBuilder.new.to_story - end - - def name(value) - @name = value - self - end - - def story(value) - @story = value - self - end - - def to_scenario(&block) - Scenario.new @story, @name, &block - end - end - end -end diff --git a/vendor/gems/rspec/spec/spec/story/extensions/main_spec.rb b/vendor/gems/rspec/spec/spec/story/extensions/main_spec.rb deleted file mode 100644 index acdc341ce1..0000000000 --- a/vendor/gems/rspec/spec/spec/story/extensions/main_spec.rb +++ /dev/null @@ -1,161 +0,0 @@ -require File.dirname(__FILE__) + '/../../../spec_helper' - -module Spec - module Story - module Extensions - describe "the main object extended with Main", :shared => true do - before(:each) do - @main = Class.new do; include Main; end - @original_rspec_story_steps, $rspec_story_steps = $rspec_story_steps, nil - end - - after(:each) do - $rspec_story_steps = @original_rspec_story_steps - end - - def have_step(type, name) - return simple_matcher(%[step group containing a #{type} named #{name.inspect}]) do |actual| - Spec::Story::Step === actual.find(type, name) - end - end - end - - describe Main, "#run_story" do - it_should_behave_like "the main object extended with Main" - - it "should create a PlainTextStoryRunner with run_story" do - Spec::Story::Runner::PlainTextStoryRunner.should_receive(:new).and_return(mock("runner", :null_object => true)) - @main.run_story - end - - it "should yield the runner if arity == 1" do - File.should_receive(:read).with("some/path").and_return("Story: foo") - $main_spec_runner = nil - @main.run_story("some/path") do |runner| - $main_spec_runner = runner - end - $main_spec_runner.should be_an_instance_of(Spec::Story::Runner::PlainTextStoryRunner) - end - - it "should run in the runner if arity == 0" do - File.should_receive(:read).with("some/path").and_return("Story: foo") - $main_spec_runner = nil - @main.run_story("some/path") do - $main_spec_runner = self - end - $main_spec_runner.should be_an_instance_of(Spec::Story::Runner::PlainTextStoryRunner) - end - - it "should tell the PlainTextStoryRunner to run with run_story" do - runner = mock("runner") - Spec::Story::Runner::PlainTextStoryRunner.should_receive(:new).and_return(runner) - runner.should_receive(:run) - @main.run_story - end - end - - describe Main, "#steps_for" do - it_should_behave_like "the main object extended with Main" - - it "should have no steps for a non existent key" do - @main.steps_for(:key).find(:given, "foo").should be_nil - end - - it "should create steps for a key" do - $main_spec_invoked = false - @main.steps_for(:key) do - Given("foo") { - $main_spec_invoked = true - } - end - @main.steps_for(:key).find(:given, "foo").perform(Object.new, "foo") - $main_spec_invoked.should be_true - end - - it "should append steps to steps_for a given key" do - @main.steps_for(:key) do - Given("first") {} - end - @main.steps_for(:key) do - Given("second") {} - end - @main.steps_for(:key).should have_step(:given, "first") - @main.steps_for(:key).should have_step(:given, "second") - end - end - - describe Main, "#with_steps_for adding new steps" do - it_should_behave_like "the main object extended with Main" - - it "should result in a group containing pre-existing steps and newly defined steps" do - first_group = @main.steps_for(:key) do - Given("first") {} - end - second_group = @main.with_steps_for(:key) do - Given("second") {} - end - - second_group.should have_step(:given, "first") - second_group.should have_step(:given, "second") - end - - it "should not add its steps to the existing group" do - first_group = @main.steps_for(:key) do - Given("first") {} - end - second_group = @main.with_steps_for(:key) do - Given("second") {} - end - - first_group.should have_step(:given, "first") - first_group.should_not have_step(:given, "second") - end - end - - describe Main, "#with_steps_for running a story" do - it_should_behave_like "the main object extended with Main" - - before(:each) do - @runner = mock("runner") - @runner_step_group = StepGroup.new - @runner.stub!(:steps).and_return(@runner_step_group) - @runner.stub!(:run) - Spec::Story::Runner::PlainTextStoryRunner.stub!(:new).and_return(@runner) - end - - it "should create a PlainTextStoryRunner with a path" do - Spec::Story::Runner::PlainTextStoryRunner.should_receive(:new).with('path/to/file',{}).and_return(@runner) - @main.with_steps_for(:foo) do - run 'path/to/file' - end - end - - it "should create a PlainTextStoryRunner with a path and options" do - Spec::Story::Runner::PlainTextStoryRunner.should_receive(:new).with(anything,{:bar => :baz}).and_return(@runner) - @main.with_steps_for(:foo) do - run 'path/to/file', :bar => :baz - end - end - - it "should pass the group it creates to the runner's steps" do - steps = @main.steps_for(:ice_cream) do - Given("vanilla") {} - end - @main.with_steps_for(:ice_cream) do - run 'foo' - end - @runner_step_group.should have_step(:given, "vanilla") - end - - it "should run a story" do - @runner.should_receive(:run) - Spec::Story::Runner::PlainTextStoryRunner.should_receive(:new).and_return(@runner) - @main.with_steps_for(:foo) do - run 'path/to/file' - end - end - - end - end - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/story/extensions_spec.rb b/vendor/gems/rspec/spec/spec/story/extensions_spec.rb deleted file mode 100644 index 612ddc72f7..0000000000 --- a/vendor/gems/rspec/spec/spec/story/extensions_spec.rb +++ /dev/null @@ -1,14 +0,0 @@ -require File.dirname(__FILE__) + '/story_helper' - -require 'spec/story' - -describe Kernel, "#Story" do - before(:each) do - Kernel.stub!(:at_exit) - end - - it "should delegate to ::Spec::Story::Runner.story_runner" do - ::Spec::Story::Runner.story_runner.should_receive(:Story) - story = Story("title","narrative"){} - end -end diff --git a/vendor/gems/rspec/spec/spec/story/given_scenario_spec.rb b/vendor/gems/rspec/spec/spec/story/given_scenario_spec.rb deleted file mode 100644 index a688f88d5c..0000000000 --- a/vendor/gems/rspec/spec/spec/story/given_scenario_spec.rb +++ /dev/null @@ -1,27 +0,0 @@ -require File.dirname(__FILE__) + '/story_helper' - -module Spec - module Story - describe GivenScenario do - it 'should execute a scenario from the current story in its world' do - # given - class MyWorld - attr :scenario_ran - end - instance = World.create(MyWorld) - scenario = ScenarioBuilder.new.to_scenario do - @scenario_ran = true - end - Runner::StoryRunner.should_receive(:scenario_from_current_story).with('scenario name').and_return(scenario) - - step = GivenScenario.new 'scenario name' - - # when - step.perform(instance, nil) - - # then - instance.scenario_ran.should be_true - end - end - end -end diff --git a/vendor/gems/rspec/spec/spec/story/runner/plain_text_story_runner_spec.rb b/vendor/gems/rspec/spec/spec/story/runner/plain_text_story_runner_spec.rb deleted file mode 100644 index 1d5f2e0c33..0000000000 --- a/vendor/gems/rspec/spec/spec/story/runner/plain_text_story_runner_spec.rb +++ /dev/null @@ -1,92 +0,0 @@ -require File.dirname(__FILE__) + '/../story_helper' - -module Spec - module Story - module Runner - describe PlainTextStoryRunner do - before(:each) do - StoryParser.stub!(:new).and_return(@parser = mock("parser")) - @parser.stub!(:parse).and_return([]) - File.stub!(:read).with("path").and_return("this\nand that") - end - - it "should provide access to steps" do - runner = PlainTextStoryRunner.new("path") - - runner.steps do |add| - add.given("baz") {} - end - - runner.steps.find(:given, "baz").should_not be_nil - end - - it "should parse a story file" do - runner = PlainTextStoryRunner.new("path") - - during { - runner.run - }.expect { - @parser.should_receive(:parse).with(["this", "and that"]) - } - end - - it "should build up a mediator with its own steps and the singleton story_runner" do - runner = PlainTextStoryRunner.new("path") - Spec::Story::Runner.should_receive(:story_runner).and_return(story_runner = mock("story runner")) - Spec::Story::Runner::StoryMediator.should_receive(:new).with(runner.steps, story_runner, {}). - and_return(mediator = stub("mediator", :run_stories => nil)) - runner.run - end - - it "should build up a parser with the mediator" do - runner = PlainTextStoryRunner.new("path") - Spec::Story::Runner.should_receive(:story_runner).and_return(story_runner = mock("story runner")) - Spec::Story::Runner::StoryMediator.should_receive(:new).and_return(mediator = stub("mediator", :run_stories => nil)) - Spec::Story::Runner::StoryParser.should_receive(:new).with(mediator).and_return(@parser) - runner.run - end - - it "should tell the mediator to run the stories" do - runner = PlainTextStoryRunner.new("path") - mediator = mock("mediator") - Spec::Story::Runner::StoryMediator.should_receive(:new).and_return(mediator) - mediator.should_receive(:run_stories) - runner.run - end - - it "should accept a block instead of a path" do - runner = PlainTextStoryRunner.new do |runner| - runner.load("path/to/story") - end - File.should_receive(:read).with("path/to/story").and_return("this\nand that") - runner.run - end - - it "should tell you if you try to run with no path set" do - runner = PlainTextStoryRunner.new - lambda { - runner.run - }.should raise_error(RuntimeError, "You must set a path to the file with the story. See the RDoc.") - end - - it "should pass options to the mediator" do - runner = PlainTextStoryRunner.new("path", :foo => :bar) - Spec::Story::Runner::StoryMediator.should_receive(:new). - with(anything, anything, :foo => :bar). - and_return(mediator = stub("mediator", :run_stories => nil)) - runner.run - end - - it "should provide access to its options" do - runner = PlainTextStoryRunner.new("path") - runner[:foo] = :bar - Spec::Story::Runner::StoryMediator.should_receive(:new). - with(anything, anything, :foo => :bar). - and_return(mediator = stub("mediator", :run_stories => nil)) - runner.run - end - - end - end - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/story/runner/scenario_collector_spec.rb b/vendor/gems/rspec/spec/spec/story/runner/scenario_collector_spec.rb deleted file mode 100644 index 042c41e8df..0000000000 --- a/vendor/gems/rspec/spec/spec/story/runner/scenario_collector_spec.rb +++ /dev/null @@ -1,27 +0,0 @@ -require File.dirname(__FILE__) + '/../story_helper' - -module Spec - module Story - module Runner - describe ScenarioCollector do - it 'should construct scenarios with the supplied story' do - # given - story = stub_everything('story') - scenario_collector = ScenarioCollector.new(story) - - # when - scenario_collector.Scenario 'scenario1' do end - scenario_collector.Scenario 'scenario2' do end - scenarios = scenario_collector.scenarios - - # then - scenario_collector.should have(2).scenarios - scenarios.first.name.should == 'scenario1' - scenarios.first.story.should equal(story) - scenarios.last.name.should == 'scenario2' - scenarios.last.story.should equal(story) - end - end - end - end -end diff --git a/vendor/gems/rspec/spec/spec/story/runner/scenario_runner_spec.rb b/vendor/gems/rspec/spec/spec/story/runner/scenario_runner_spec.rb deleted file mode 100644 index a69ed4a996..0000000000 --- a/vendor/gems/rspec/spec/spec/story/runner/scenario_runner_spec.rb +++ /dev/null @@ -1,142 +0,0 @@ -require File.dirname(__FILE__) + '/../story_helper' - -module Spec - module Story - module Runner - describe ScenarioRunner do - it 'should run a scenario in its story' do - # given - world = stub_everything - scenario_runner = ScenarioRunner.new - $answer = nil - story = Story.new 'story', 'narrative' do - @answer = 42 # this should be available to the scenario - end - scenario = Scenario.new story, 'scenario' do - $answer = @answer - end - - # when - scenario_runner.run(scenario, world) - - # then - $answer.should == 42 - end - - it 'should allow scenarios to share methods' do - # given - world = stub_everything - $shared_invoked = 0 - story = Story.new 'story', 'narrative' do - def shared - $shared_invoked += 1 - end - end - scenario1 = Scenario.new story, 'scenario1' do - shared() - end - scenario2 = Scenario.new story, 'scenario2' do - shared() - end - scenario_runner = ScenarioRunner.new - - # when - scenario_runner.run(scenario1, world) - scenario_runner.run(scenario2, world) - - # then - $shared_invoked.should == 2 - end - - it 'should notify listeners when a scenario starts' do - # given - world = stub_everything - story = Story.new 'story', 'narrative' do end - scenario = Scenario.new story, 'scenario1' do - # succeeds - end - scenario_runner = ScenarioRunner.new - mock_listener1 = stub_everything('listener1') - mock_listener2 = stub_everything('listener2') - scenario_runner.add_listener(mock_listener1) - scenario_runner.add_listener(mock_listener2) - - # expect - mock_listener1.should_receive(:scenario_started).with('story', 'scenario1') - mock_listener2.should_receive(:scenario_started).with('story', 'scenario1') - - # when - scenario_runner.run(scenario, world) - - # then - end - - it 'should notify listeners when a scenario succeeds' do - # given - world = stub_everything('world') - story = Story.new 'story', 'narrative' do end - scenario = Scenario.new story, 'scenario1' do - # succeeds - end - scenario_runner = ScenarioRunner.new - mock_listener1 = stub_everything('listener1') - mock_listener2 = stub_everything('listener2') - scenario_runner.add_listener(mock_listener1) - scenario_runner.add_listener(mock_listener2) - - # expect - mock_listener1.should_receive(:scenario_succeeded).with('story', 'scenario1') - mock_listener2.should_receive(:scenario_succeeded).with('story', 'scenario1') - - # when - scenario_runner.run(scenario, world) - - # then - end - - it 'should notify listeners ONCE when a scenario raises an error' do - # given - error = RuntimeError.new('oops') - story = Story.new 'title', 'narrative' do end - scenario = Scenario.new story, 'scenario1' do - end - scenario_runner = ScenarioRunner.new - mock_listener = stub_everything('listener') - scenario_runner.add_listener(mock_listener) - world = stub_everything - - # expect - world.should_receive(:errors).twice.and_return([error, error]) - mock_listener.should_receive(:scenario_failed).with('title', 'scenario1', error).once - - # when - scenario_runner.run scenario, world - - # then - end - - it 'should notify listeners when a scenario is pending' do - # given - pending_error = Spec::Example::ExamplePendingError.new('todo') - story = Story.new 'title', 'narrative' do end - scenario = Scenario.new story, 'scenario1' do - end - scenario_runner = ScenarioRunner.new - mock_listener = mock('listener') - scenario_runner.add_listener(mock_listener) - world = stub_everything - - # expect - world.should_receive(:errors).twice.and_return([pending_error, pending_error]) - mock_listener.should_receive(:scenario_started).with('title', 'scenario1') - mock_listener.should_receive(:scenario_pending).with('title', 'scenario1', 'todo').once - - # when - scenario_runner.run scenario, world - - # then - end - end - end - end -end diff --git a/vendor/gems/rspec/spec/spec/story/runner/story_mediator_spec.rb b/vendor/gems/rspec/spec/spec/story/runner/story_mediator_spec.rb deleted file mode 100644 index 4192e483a8..0000000000 --- a/vendor/gems/rspec/spec/spec/story/runner/story_mediator_spec.rb +++ /dev/null @@ -1,133 +0,0 @@ -require File.dirname(__FILE__) + '/../story_helper' - -module Spec - module Story - module Runner - - describe StoryMediator do - before(:each) do - $story_mediator_spec_value = nil - @step_group = StepGroup.new - @step_group.create_matcher(:given, "given") { $story_mediator_spec_value = "given matched" } - @step_group.create_matcher(:when, "when") { $story_mediator_spec_value = "when matched" } - @step_group.create_matcher(:then, "then") { $story_mediator_spec_value = "then matched" } - - @scenario_runner = ScenarioRunner.new - @runner = StoryRunner.new @scenario_runner - @mediator = StoryMediator.new @step_group, @runner - end - - def run_stories - @mediator.run_stories - @runner.run_stories - end - - it "should have no stories" do - @mediator.stories.should be_empty - end - - it "should create two stories" do - @mediator.create_story "story title", "story narrative" - @mediator.create_story "story title 2", "story narrative 2" - run_stories - - @runner.should have(2).stories - @runner.stories.first.title.should == "story title" - @runner.stories.first.narrative.should == "story narrative" - @runner.stories.last.title.should == "story title 2" - @runner.stories.last.narrative.should == "story narrative 2" - end - - it "should create a scenario" do - @mediator.create_story "title", "narrative" - @mediator.create_scenario "scenario name" - run_stories - - @runner.should have(1).scenarios - @runner.scenarios.first.name.should == "scenario name" - @runner.scenarios.first.story.should == @runner.stories.first - end - - it "should create a given scenario step if one matches" do - pending("need to untangle the dark mysteries of the story runner - something needs to get stubbed here") do - story = @mediator.create_story "title", "narrative" - @mediator.create_scenario "previous scenario" - @mediator.create_scenario "current scenario" - @mediator.create_given_scenario "previous scenario" - run_stories - - $story_mediator_spec_value.should == "previous scenario matched" - end - end - - it "should create a given step if one matches" do - @mediator.create_story "title", "narrative" - @mediator.create_scenario "scenario" - @mediator.create_given "given" - run_stories - - $story_mediator_spec_value.should == "given matched" - end - - it "should create a pending step if no given step matches" do - @mediator.create_story "title", "narrative" - @mediator.create_scenario "scenario" - @mediator.create_given "no match" - mock_listener = stub_everything("listener") - mock_listener.should_receive(:scenario_pending).with("title", "scenario", "Unimplemented step: no match") - @scenario_runner.add_listener mock_listener - run_stories - end - - it "should create a when step if one matches" do - @mediator.create_story "title", "narrative" - @mediator.create_scenario "scenario" - @mediator.create_when "when" - run_stories - - $story_mediator_spec_value.should == "when matched" - end - - it "should create a pending step if no when step matches" do - @mediator.create_story "title", "narrative" - @mediator.create_scenario "scenario" - @mediator.create_when "no match" - mock_listener = stub_everything("listener") - mock_listener.should_receive(:scenario_pending).with("title", "scenario", "Unimplemented step: no match") - @scenario_runner.add_listener mock_listener - run_stories - end - - it "should create a then step if one matches" do - @mediator.create_story "title", "narrative" - @mediator.create_scenario "scenario" - @mediator.create_then "then" - run_stories - - $story_mediator_spec_value.should == "then matched" - end - - it "should create a pending step if no 'then' step matches" do - @mediator.create_story "title", "narrative" - @mediator.create_scenario "scenario" - @mediator.create_then "no match" - mock_listener = stub_everything("listener") - mock_listener.should_receive(:scenario_pending).with("title", "scenario", "Unimplemented step: no match") - @scenario_runner.add_listener mock_listener - run_stories - end - - it "should pass options to the stories it creates" do - @mediator = StoryMediator.new @step_group, @runner, :foo => :bar - @mediator.create_story "story title", "story narrative" - - run_stories - - @runner.stories.first[:foo].should == :bar - end - - end - - end - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/story/runner/story_parser_spec.rb b/vendor/gems/rspec/spec/spec/story/runner/story_parser_spec.rb deleted file mode 100644 index 5efc8fd184..0000000000 --- a/vendor/gems/rspec/spec/spec/story/runner/story_parser_spec.rb +++ /dev/null @@ -1,384 +0,0 @@ -require File.dirname(__FILE__) + '/../story_helper' - -module Spec - module Story - module Runner - - describe StoryParser do - before(:each) do - @story_mediator = mock("story_mediator") - @parser = StoryParser.new(@story_mediator) - end - - it "should parse no lines" do - @parser.parse([]) - end - - it "should ignore text before the first Story: begins" do - @story_mediator.should_not_receive(:create_scenario) - @story_mediator.should_not_receive(:create_given) - @story_mediator.should_not_receive(:create_when) - @story_mediator.should_not_receive(:create_then) - @story_mediator.should_receive(:create_story).with("simple addition", "") - @parser.parse(["Here is a bunch of text", "about a calculator and all the things", "that it will do", "Story: simple addition"]) - end - - it "should create a story" do - @story_mediator.should_receive(:create_story).with("simple addition", "") - @parser.parse(["Story: simple addition"]) - end - - it "should create a story when line has leading spaces" do - @story_mediator.should_receive(:create_story).with("simple addition", "") - @parser.parse([" Story: simple addition"]) - end - - it "should add a one line narrative to the story" do - @story_mediator.should_receive(:create_story).with("simple addition","narrative") - @parser.parse(["Story: simple addition","narrative"]) - end - - it "should add a multi line narrative to the story" do - @story_mediator.should_receive(:create_story).with("simple addition","narrative line 1\nline 2\nline 3") - @parser.parse(["Story: simple addition","narrative line 1", "line 2", "line 3"]) - end - - it "should exclude blank lines from the narrative" do - @story_mediator.should_receive(:create_story).with("simple addition","narrative line 1\nline 2") - @parser.parse(["Story: simple addition","narrative line 1", "", "line 2"]) - end - - it "should exclude Scenario from the narrative" do - @story_mediator.should_receive(:create_story).with("simple addition","narrative line 1\nline 2") - @story_mediator.should_receive(:create_scenario) - @parser.parse(["Story: simple addition","narrative line 1", "line 2", "Scenario: add one plus one"]) - end - - end - - describe StoryParser, "in Story state" do - before(:each) do - @story_mediator = mock("story_mediator") - @parser = StoryParser.new(@story_mediator) - @story_mediator.stub!(:create_story) - end - - it "should create a second Story for Story" do - @story_mediator.should_receive(:create_story).with("number two","") - @parser.parse(["Story: s", "Story: number two"]) - end - - it "should include And in the narrative" do - @story_mediator.should_receive(:create_story).with("s","And foo") - @story_mediator.should_receive(:create_scenario).with("bar") - @parser.parse(["Story: s", "And foo", "Scenario: bar"]) - end - - it "should create a Scenario for Scenario" do - @story_mediator.should_receive(:create_scenario).with("number two") - @parser.parse(["Story: s", "Scenario: number two"]) - end - - it "should include Given in the narrative" do - @story_mediator.should_receive(:create_story).with("s","Given foo") - @story_mediator.should_receive(:create_scenario).with("bar") - @parser.parse(["Story: s", "Given foo", "Scenario: bar"]) - end - - it "should include Given: in the narrative" do - @story_mediator.should_receive(:create_story).with("s","Given: foo") - @story_mediator.should_receive(:create_scenario).with("bar") - @parser.parse(["Story: s", "Given: foo", "Scenario: bar"]) - end - - it "should include When in the narrative" do - @story_mediator.should_receive(:create_story).with("s","When foo") - @story_mediator.should_receive(:create_scenario).with("bar") - @parser.parse(["Story: s", "When foo", "Scenario: bar"]) - end - - it "should include Then in the narrative" do - @story_mediator.should_receive(:create_story).with("s","Then foo") - @story_mediator.should_receive(:create_scenario).with("bar") - @parser.parse(["Story: s", "Then foo", "Scenario: bar"]) - end - - it "should include other in the story" do - @story_mediator.should_receive(:create_story).with("s","narrative") - @parser.parse(["Story: s", "narrative"]) - end - end - - describe StoryParser, "in Scenario state" do - before(:each) do - @story_mediator = mock("story_mediator") - @parser = StoryParser.new(@story_mediator) - @story_mediator.stub!(:create_story) - @story_mediator.stub!(:create_scenario) - end - - it "should create a Story for Story" do - @story_mediator.should_receive(:create_story).with("number two","") - @parser.parse(["Story: s", "Scenario: s", "Story: number two"]) - end - - it "should create a Scenario for Scenario" do - @story_mediator.should_receive(:create_scenario).with("number two") - @parser.parse(["Story: s", "Scenario: s", "Scenario: number two"]) - end - - it "should raise for And" do - lambda { - @parser.parse(["Story: s", "Scenario: s", "And second"]) - }.should raise_error(IllegalStepError, /^Illegal attempt to create a And after a Scenario/) - end - - it "should create a Given for Given" do - @story_mediator.should_receive(:create_given).with("gift") - @parser.parse(["Story: s", "Scenario: s", "Given gift"]) - end - - it "should create a Given for Given:" do - @story_mediator.should_receive(:create_given).with("gift") - @parser.parse(["Story: s", "Scenario: s", "Given: gift"]) - end - - it "should create a GivenScenario for GivenScenario" do - @story_mediator.should_receive(:create_given_scenario).with("previous") - @parser.parse(["Story: s", "Scenario: s", "GivenScenario previous"]) - end - - it "should create a GivenScenario for GivenScenario:" do - @story_mediator.should_receive(:create_given_scenario).with("previous") - @parser.parse(["Story: s", "Scenario: s", "GivenScenario: previous"]) - end - - it "should transition to Given state after GivenScenario" do - @story_mediator.stub!(:create_given_scenario) - @parser.parse(["Story: s", "Scenario: s", "GivenScenario previous"]) - @parser.instance_eval{@state}.should be_an_instance_of(StoryParser::GivenState) - end - - it "should transition to Given state after GivenScenario:" do - @story_mediator.stub!(:create_given_scenario) - @parser.parse(["Story: s", "Scenario: s", "GivenScenario: previous"]) - @parser.instance_eval{@state}.should be_an_instance_of(StoryParser::GivenState) - end - - it "should create a When for When" do - @story_mediator.should_receive(:create_when).with("ever") - @parser.parse(["Story: s", "Scenario: s", "When ever"]) - end - - it "should create a When for When:" do - @story_mediator.should_receive(:create_when).with("ever") - @parser.parse(["Story: s", "Scenario: s", "When: ever"]) - end - - it "should create a Then for Then" do - @story_mediator.should_receive(:create_then).with("and there") - @parser.parse(["Story: s", "Scenario: s", "Then and there"]) - end - - it "should create a Then for Then:" do - @story_mediator.should_receive(:create_then).with("and there") - @parser.parse(["Story: s", "Scenario: s", "Then: and there"]) - end - - it "should ignore other" do - @parser.parse(["Story: s", "Scenario: s", "this is ignored"]) - end - end - - describe StoryParser, "in Given state" do - before(:each) do - @story_mediator = mock("story_mediator") - @parser = StoryParser.new(@story_mediator) - @story_mediator.stub!(:create_story) - @story_mediator.stub!(:create_scenario) - @story_mediator.should_receive(:create_given).with("first") - end - - it "should create a Story for Story" do - @story_mediator.should_receive(:create_story).with("number two","") - @parser.parse(["Story: s", "Scenario: s", "Given first", "Story: number two"]) - end - - it "should create a Scenario for Scenario" do - @story_mediator.should_receive(:create_scenario).with("number two") - @parser.parse(["Story: s", "Scenario: s", "Given first", "Scenario: number two"]) - end - - it "should create a second Given for Given" do - @story_mediator.should_receive(:create_given).with("second") - @parser.parse(["Story: s", "Scenario: s", "Given first", "Given second"]) - end - - it "should create a second Given for And" do - @story_mediator.should_receive(:create_given).with("second") - @parser.parse(["Story: s", "Scenario: s", "Given: first", "And second"]) - end - - it "should create a second Given for And:" do - @story_mediator.should_receive(:create_given).with("second") - @parser.parse(["Story: s", "Scenario: s", "Given first", "And: second"]) - end - - it "should create a When for When" do - @story_mediator.should_receive(:create_when).with("ever") - @parser.parse(["Story: s", "Scenario: s", "Given first", "When ever"]) - end - - it "should create a When for When:" do - @story_mediator.should_receive(:create_when).with("ever") - @parser.parse(["Story: s", "Scenario: s", "Given first", "When: ever"]) - end - - it "should create a Then for Then" do - @story_mediator.should_receive(:create_then).with("and there") - @parser.parse(["Story: s", "Scenario: s", "Given first", "Then and there"]) - end - - it "should create a Then for Then:" do - @story_mediator.should_receive(:create_then).with("and there") - @parser.parse(["Story: s", "Scenario: s", "Given first", "Then: and there"]) - end - - it "should ignore other" do - @parser.parse(["Story: s", "Scenario: s", "Given first", "this is ignored"]) - end - end - - describe StoryParser, "in When state" do - before(:each) do - @story_mediator = mock("story_mediator") - @parser = StoryParser.new(@story_mediator) - @story_mediator.stub!(:create_story) - @story_mediator.stub!(:create_scenario) - @story_mediator.should_receive(:create_given).with("first") - @story_mediator.should_receive(:create_when).with("else") - end - - it "should create a Story for Story" do - @story_mediator.should_receive(:create_story).with("number two","") - @parser.parse(["Story: s", "Scenario: s", "Given first", "When: else", "Story: number two"]) - end - - it "should create a Scenario for Scenario" do - @story_mediator.should_receive(:create_scenario).with("number two") - @parser.parse(["Story: s", "Scenario: s", "Given first", "When else", "Scenario: number two"]) - end - - it "should create Given for Given" do - @story_mediator.should_receive(:create_given).with("second") - @parser.parse(["Story: s", "Scenario: s", "Given first", "When else", "Given second"]) - end - - it "should create Given for Given:" do - @story_mediator.should_receive(:create_given).with("second") - @parser.parse(["Story: s", "Scenario: s", "Given first", "When else", "Given: second"]) - end - - it "should create a second When for When" do - @story_mediator.should_receive(:create_when).with("ever") - @parser.parse(["Story: s", "Scenario: s", "Given first", "When else", "When ever"]) - end - - it "should create a second When for When:" do - @story_mediator.should_receive(:create_when).with("ever") - @parser.parse(["Story: s", "Scenario: s", "Given: first", "When: else", "When: ever"]) - end - - it "should create a second When for And" do - @story_mediator.should_receive(:create_when).with("ever") - @parser.parse(["Story: s", "Scenario: s", "Given first", "When else", "And ever"]) - end - - it "should create a second When for And:" do - @story_mediator.should_receive(:create_when).with("ever") - @parser.parse(["Story: s", "Scenario: s", "Given: first", "When: else", "And: ever"]) - end - - it "should create a Then for Then" do - @story_mediator.should_receive(:create_then).with("and there") - @parser.parse(["Story: s", "Scenario: s", "Given first", "When else", "Then and there"]) - end - - it "should create a Then for Then:" do - @story_mediator.should_receive(:create_then).with("and there") - @parser.parse(["Story: s", "Scenario: s", "Given: first", "When: else", "Then: and there"]) - end - - it "should ignore other" do - @parser.parse(["Story: s", "Scenario: s", "Given first", "When else", "this is ignored"]) - end - end - - describe StoryParser, "in Then state" do - before(:each) do - @story_mediator = mock("story_mediator") - @parser = StoryParser.new(@story_mediator) - @story_mediator.stub!(:create_story) - @story_mediator.stub!(:create_scenario) - @story_mediator.should_receive(:create_given).with("first") - @story_mediator.should_receive(:create_when).with("else") - @story_mediator.should_receive(:create_then).with("what") - end - - it "should create a Story for Story" do - @story_mediator.should_receive(:create_story).with("number two","") - @parser.parse(["Story: s", "Scenario: s", "Given first", "When else", "Then what", "Story: number two"]) - end - - it "should create a Scenario for Scenario" do - @story_mediator.should_receive(:create_scenario).with("number two") - @parser.parse(["Story: s", "Scenario: s", "Given first", "When else", "Then what", "Scenario: number two"]) - end - - it "should create Given for Given" do - @story_mediator.should_receive(:create_given).with("second") - @parser.parse(["Story: s", "Scenario: s", "Given first", "When else", "Then what", "Given second"]) - end - - it "should create Given for Given:" do - @story_mediator.should_receive(:create_given).with("second") - @parser.parse(["Story: s", "Scenario: s", "Given: first", "When: else", "Then: what", "Given: second"]) - end - - it "should create When for When" do - @story_mediator.should_receive(:create_when).with("ever") - @parser.parse(["Story: s", "Scenario: s", "Given first", "When else", "Then what", "When ever"]) - end - - it "should create When for When:" do - @story_mediator.should_receive(:create_when).with("ever") - @parser.parse(["Story: s", "Scenario: s", "Given: first", "When: else", "Then: what", "When: ever"]) - end - - it "should create a Then for Then" do - @story_mediator.should_receive(:create_then).with("and there") - @parser.parse(["Story: s", "Scenario: s", "Given first", "When else", "Then what", "Then and there"]) - end - - it "should create a Then for Then:" do - @story_mediator.should_receive(:create_then).with("and there") - @parser.parse(["Story: s", "Scenario: s", "Given: first", "When: else", "Then: what", "Then: and there"]) - end - - it "should create a second Then for And" do - @story_mediator.should_receive(:create_then).with("ever") - @parser.parse(["Story: s", "Scenario: s", "Given first", "When else", "Then what", "And ever"]) - end - - it "should create a second Then for And:" do - @story_mediator.should_receive(:create_then).with("ever") - @parser.parse(["Story: s", "Scenario: s", "Given: first", "When: else", "Then: what", "And: ever"]) - end - - it "should ignore other" do - @parser.parse(["Story: s", "Scenario: s", "Given first", "When else", "Then what", "this is ignored"]) - end - end - end - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/spec/spec/story/runner/story_runner_spec.rb b/vendor/gems/rspec/spec/spec/story/runner/story_runner_spec.rb deleted file mode 100644 index 0fc46405a1..0000000000 --- a/vendor/gems/rspec/spec/spec/story/runner/story_runner_spec.rb +++ /dev/null @@ -1,256 +0,0 @@ -require File.dirname(__FILE__) + '/../story_helper' - -module Spec - module Story - module Runner - describe StoryRunner do - it 'should collect all the stories' do - # given - story_runner = StoryRunner.new(stub('scenario_runner')) - - # when - story_runner.Story 'title1', 'narrative1' do end - story_runner.Story 'title2', 'narrative2' do end - stories = story_runner.stories - - # then - story_runner.should have(2).stories - stories.first.title.should == 'title1' - stories.first.narrative.should == 'narrative1' - stories.last.title.should == 'title2' - stories.last.narrative.should == 'narrative2' - end - - it 'should gather all the scenarios in the stories' do - # given - story_runner = StoryRunner.new(stub('scenario_runner')) - - # when - story_runner.Story "story1", "narrative1" do - Scenario "scenario1" do end - Scenario "scenario2" do end - end - story_runner.Story "story2", "narrative2" do - Scenario "scenario3" do end - end - scenarios = story_runner.scenarios - - # then - story_runner.should have(3).scenarios - scenarios[0].name.should == 'scenario1' - scenarios[1].name.should == 'scenario2' - scenarios[2].name.should == 'scenario3' - end - - # captures worlds passed into a ScenarioRunner - class ScenarioWorldCatcher - attr_accessor :worlds - def run(scenario, world) - (@worlds ||= []) << world - end - end - - it 'should run each scenario in a separate object' do - # given - scenario_world_catcher = ScenarioWorldCatcher.new - story_runner = StoryRunner.new(scenario_world_catcher) - story_runner.Story 'story', 'narrative' do - Scenario 'scenario1' do end - Scenario 'scenario2' do end - end - - # when - story_runner.run_stories - - # then - worlds = scenario_world_catcher.worlds - scenario_world_catcher.should have(2).worlds - worlds[0].should_not == worlds[1] - end - - it 'should use the provided world creator to create worlds' do - # given - stub_scenario_runner = stub_everything - mock_world_creator = mock('world creator') - story_runner = StoryRunner.new(stub_scenario_runner, mock_world_creator) - story_runner.Story 'story', 'narrative' do - Scenario 'scenario1' do end - Scenario 'scenario2' do end - end - - # expect - mock_world_creator.should_receive(:create).twice - - # when - story_runner.run_stories - - # then - end - - it 'should notify listeners of the scenario count when the run starts' do - # given - story_runner = StoryRunner.new(stub_everything) - mock_listener1 = stub_everything('listener1') - mock_listener2 = stub_everything('listener2') - story_runner.add_listener(mock_listener1) - story_runner.add_listener(mock_listener2) - - story_runner.Story 'story1', 'narrative1' do - Scenario 'scenario1' do end - end - story_runner.Story 'story2', 'narrative2' do - Scenario 'scenario2' do end - Scenario 'scenario3' do end - end - - # expect - mock_listener1.should_receive(:run_started).with(3) - mock_listener2.should_receive(:run_started).with(3) - - # when - story_runner.run_stories - - # then - end - - it 'should notify listeners when a story starts' do - # given - story_runner = StoryRunner.new(stub_everything) - mock_listener1 = stub_everything('listener1') - mock_listener2 = stub_everything('listener2') - story_runner.add_listener(mock_listener1) - story_runner.add_listener(mock_listener2) - - story_runner.Story 'story1', 'narrative1' do - Scenario 'scenario1' do end - end - story_runner.Story 'story2', 'narrative2' do - Scenario 'scenario2' do end - Scenario 'scenario3' do end - end - - # expect - mock_listener1.should_receive(:story_started).with('story1', 'narrative1') - mock_listener1.should_receive(:story_ended).with('story1', 'narrative1') - mock_listener2.should_receive(:story_started).with('story2', 'narrative2') - mock_listener2.should_receive(:story_ended).with('story2', 'narrative2') - - # when - story_runner.run_stories - - # then - end - - it 'should notify listeners when the run ends' do - # given - story_runner = StoryRunner.new(stub_everything) - mock_listener1 = stub_everything('listener1') - mock_listener2 = stub_everything('listener2') - story_runner.add_listener mock_listener1 - story_runner.add_listener mock_listener2 - story_runner.Story 'story1', 'narrative1' do - Scenario 'scenario1' do end - end - - # expect - mock_listener1.should_receive(:run_ended) - mock_listener2.should_receive(:run_ended) - - # when - story_runner.run_stories - - # then - end - - it 'should run a story in an instance of a specified class' do - # given - scenario_world_catcher = ScenarioWorldCatcher.new - story_runner = StoryRunner.new(scenario_world_catcher) - story_runner.Story 'title', 'narrative', :type => String do - Scenario 'scenario' do end - end - - # when - story_runner.run_stories - - # then - scenario_world_catcher.worlds[0].should be_kind_of(String) - scenario_world_catcher.worlds[0].should be_kind_of(World) - end - - it 'should pass initialization params through to the constructed instance' do - # given - scenario_world_catcher = ScenarioWorldCatcher.new - story_runner = StoryRunner.new(scenario_world_catcher) - story_runner.Story 'title', 'narrative', :type => Array, :args => [3] do - Scenario 'scenario' do end - end - - # when - story_runner.run_stories - - # then - scenario_world_catcher.worlds[0].should be_kind_of(Array) - scenario_world_catcher.worlds[0].size.should == 3 - end - - it 'should find a scenario in the current story by name' do - # given - story_runner = StoryRunner.new(ScenarioRunner.new) - $scenario = nil - - story_runner.Story 'title', 'narrative' do - Scenario 'first scenario' do - end - Scenario 'second scenario' do - $scenario = StoryRunner.scenario_from_current_story 'first scenario' - end - end - - # when - story_runner.run_stories - - # then - $scenario.name.should == 'first scenario' - end - - it "should clean the steps between stories" do - #given - story_runner = StoryRunner.new(ScenarioRunner.new) - result = mock 'result' - - step1 = Step.new('step') do - result.one - end - steps1 = StepGroup.new - steps1.add :when, step1 - - story_runner.Story 'title', 'narrative', :steps => steps1 do - Scenario 'first scenario' do - When 'step' - end - end - - step2 = Step.new('step') do - result.two - end - steps2 = StepGroup.new - steps2.add :when, step2 - - story_runner.Story 'title2', 'narrative', :steps => steps2 do - Scenario 'second scenario' do - When 'step' - end - end - - #then - result.should_receive(:one) - result.should_receive(:two) - - #when - story_runner.run_stories - end - end - end - end -end diff --git a/vendor/gems/rspec/spec/spec/story/runner_spec.rb b/vendor/gems/rspec/spec/spec/story/runner_spec.rb deleted file mode 100644 index 81e8526404..0000000000 --- a/vendor/gems/rspec/spec/spec/story/runner_spec.rb +++ /dev/null @@ -1,106 +0,0 @@ -require File.dirname(__FILE__) + '/story_helper' - -module Spec - module Story - describe Runner, "module" do - def dev_null - io = StringIO.new - def io.write(str) - str.to_s.size - end - return io - end - - before :each do - Kernel.stub!(:at_exit) - @stdout, $stdout = $stdout, dev_null - @argv = Array.new(ARGV) - @runner_module = Runner.dup - @world_creator = World.dup - @runner_module.module_eval { @run_options = @story_runner = @scenario_runner = @world_creator = nil } - end - - after :each do - $stdout = @stdout - ARGV.replace @argv - @runner_module.module_eval { @run_options = @story_runner = @scenario_runner = @world_creator = nil } - end - - it 'should wire up a singleton StoryRunner' do - @runner_module.story_runner.should_not be_nil - end - - it 'should set its options based on ARGV' do - # given - ARGV << '--dry-run' - - # when - options = @runner_module.run_options - - # then - options.dry_run.should be_true - end - - it 'should add a reporter to the runner classes' do - # given - story_runner = mock('story runner', :null_object => true) - scenario_runner = mock('scenario runner', :null_object => true) - world_creator = mock('world', :null_object => true) - - @runner_module::class_eval { @world_creator = world_creator } - @runner_module::StoryRunner.stub!(:new).and_return(story_runner) - @runner_module::ScenarioRunner.stub!(:new).and_return(scenario_runner) - - # expect - world_creator.should_receive(:add_listener).with(an_instance_of(Spec::Runner::Formatter::Story::PlainTextFormatter)) - story_runner.should_receive(:add_listener).with(an_instance_of(Spec::Runner::Formatter::Story::PlainTextFormatter)) - scenario_runner.should_receive(:add_listener).with(an_instance_of(Spec::Runner::Formatter::Story::PlainTextFormatter)) - - # when - @runner_module.story_runner - end - - it 'should add a documenter to the runner classes if one is specified' do - # given - ARGV << "--format" << "html" - story_runner = mock('story runner', :null_object => true) - scenario_runner = mock('scenario runner', :null_object => true) - world_creator = mock('world', :null_object => true) - - @runner_module::class_eval { @world_creator = world_creator } - @runner_module::StoryRunner.stub!(:new).and_return(story_runner) - @runner_module::ScenarioRunner.stub!(:new).and_return(scenario_runner) - - # expect - world_creator.should_receive(:add_listener).with(an_instance_of(Spec::Runner::Formatter::Story::HtmlFormatter)) - story_runner.should_receive(:add_listener).with(an_instance_of(Spec::Runner::Formatter::Story::HtmlFormatter)) - scenario_runner.should_receive(:add_listener).with(an_instance_of(Spec::Runner::Formatter::Story::HtmlFormatter)) - - # when - @runner_module.story_runner - end - - it 'should add any registered listener to the runner classes' do - # given - ARGV << "--format" << "html" - story_runner = mock('story runner', :null_object => true) - scenario_runner = mock('scenario runner', :null_object => true) - world_creator = mock('world', :null_object => true) - - @runner_module::class_eval { @world_creator = world_creator } - @runner_module::StoryRunner.stub!(:new).and_return(story_runner) - @runner_module::ScenarioRunner.stub!(:new).and_return(scenario_runner) - - listener = Object.new - - # expect - world_creator.should_receive(:add_listener).with(listener) - story_runner.should_receive(:add_listener).with(listener) - scenario_runner.should_receive(:add_listener).with(listener) - - # when - @runner_module.register_listener listener - end - end - end -end diff --git a/vendor/gems/rspec/spec/spec/story/scenario_spec.rb b/vendor/gems/rspec/spec/spec/story/scenario_spec.rb deleted file mode 100644 index 0cf7aff309..0000000000 --- a/vendor/gems/rspec/spec/spec/story/scenario_spec.rb +++ /dev/null @@ -1,20 +0,0 @@ -require File.dirname(__FILE__) + '/story_helper' - -module Spec - module Story - describe Scenario do - it 'should not raise an error if no body is supplied' do - # given - story = StoryBuilder.new.to_story - - # when - error = exception_from do - Scenario.new story, 'name' - end - - # then - error.should be_nil - end - end - end -end diff --git a/vendor/gems/rspec/spec/spec/story/step_group_spec.rb b/vendor/gems/rspec/spec/spec/story/step_group_spec.rb deleted file mode 100644 index dd28bfa263..0000000000 --- a/vendor/gems/rspec/spec/spec/story/step_group_spec.rb +++ /dev/null @@ -1,157 +0,0 @@ -require File.dirname(__FILE__) + '/story_helper' - -module Spec - module Story - describe StepGroup do - before(:each) do - @step_group = StepGroup.new - end - - it "should not find a matcher if empty" do - @step_group.find(:given, "this and that").should be_nil - end - - it "should create a given_scenario matcher" do - step = @step_group.given_scenario("this and that") {} - @step_group.find(:given_scenario, "this and that").should_not be_nil - @step_group.find(:given_scenario, "this and that").should equal(step) - end - - it "should create a given matcher" do - step = @step_group.given("this and that") {} - @step_group.find(:given, "this and that").should equal(step) - end - - it "should create a when matcher" do - step = @step_group.when("this and that") {} - @step_group.find(:when, "this and that").should equal(step) - end - - it "should create a them matcher" do - step = @step_group.then("this and that") {} - @step_group.find(:then, "this and that").should equal(step) - end - - it "should add a matcher object" do - step = Step.new("this and that") {} - @step_group.add(:given, step) - @step_group.find(:given, "this and that").should equal(step) - end - - it "should add it matchers to another StepGroup (with one given)" do - source = StepGroup.new - target = StepGroup.new - step = source.given("this and that") {} - source.add_to target - target.find(:given, "this and that").should equal(step) - end - - it "should add it matchers to another StepGroup (with some of each type)" do - source = StepGroup.new - target = StepGroup.new - given_scenario = source.given_scenario("1") {} - given = source.given("1") {} - when1 = source.when("1") {} - when2 = source.when("2") {} - then1 = source.then("1") {} - then2 = source.then("2") {} - then3 = source.then("3") {} - source.add_to target - target.find(:given_scenario, "1").should equal(given_scenario) - target.find(:given, "1").should equal(given) - target.find(:when, "1").should equal(when1) - target.find(:when, "2").should equal(when2) - target.find(:then, "1").should equal(then1) - target.find(:then, "2").should equal(then2) - target.find(:then, "3").should equal(then3) - end - - it "should append another collection" do - matchers_to_append = StepGroup.new - step = matchers_to_append.given("this and that") {} - @step_group << matchers_to_append - @step_group.find(:given, "this and that").should equal(step) - end - - it "should append several other collections" do - matchers_to_append = StepGroup.new - more_matchers_to_append = StepGroup.new - first_matcher = matchers_to_append.given("this and that") {} - second_matcher = more_matchers_to_append.given("and the other") {} - @step_group << matchers_to_append - @step_group << more_matchers_to_append - @step_group.find(:given, "this and that").should equal(first_matcher) - @step_group.find(:given, "and the other").should equal(second_matcher) - end - - it "should yield itself on initialization" do - begin - $step_group_spec_step = nil - matchers = StepGroup.new do |matchers| - $step_group_spec_step = matchers.given("foo") {} - end - $step_group_spec_step.matches?("foo").should be_true - ensure - $step_group_spec_step = nil - end - end - - it "should support defaults" do - class StepGroupSubclass < StepGroup - steps do |add| - add.given("foo") {} - end - end - StepGroupSubclass.new.find(:given, "foo").should_not be_nil - end - - it "should create a Given" do - sub = Class.new(StepGroup).new - step = sub.Given("foo") {} - sub.find(:given, "foo").should == step - end - - it "should create a When" do - sub = Class.new(StepGroup).new - step = sub.When("foo") {} - sub.find(:when, "foo").should == step - end - - it "should create a Then" do - sub = Class.new(StepGroup).new - step = sub.Then("foo") {} - sub.find(:then, "foo").should == step - end - - it "should create steps in a block" do - sub = Class.new(StepGroup).new do - Given("a given") {} - When("a when") {} - Then("a then") {} - end - sub.find(:given, "a given").should_not be_nil - sub.find(:when, "a when").should_not be_nil - sub.find(:then, "a then").should_not be_nil - end - - it "should clear itself" do - step = @step_group.given("this and that") {} - @step_group.clear - @step_group.find(:given, "this and that").should be_nil - end - - it "should tell you when it is empty" do - @step_group.should be_empty - end - - it "should tell you when it is not empty" do - @step_group.given("this and that") {} - @step_group.should_not be_empty - end - - it "should handle << nil" do - @step_group << nil - end - end - end -end diff --git a/vendor/gems/rspec/spec/spec/story/step_mother_spec.rb b/vendor/gems/rspec/spec/spec/story/step_mother_spec.rb deleted file mode 100644 index 64efd7a6af..0000000000 --- a/vendor/gems/rspec/spec/spec/story/step_mother_spec.rb +++ /dev/null @@ -1,72 +0,0 @@ -require File.dirname(__FILE__) + '/story_helper' - -module Spec - module Story - describe StepMother do - it 'should store a step by name and type' do - # given - step_mother = StepMother.new - step = Step.new("a given", &lambda {}) - step_mother.store(:given, step) - - # when - found = step_mother.find(:given, "a given") - - # then - found.should == step - end - - it 'should NOT raise an error if a step is missing' do - # given - step_mother = StepMother.new - - # then - lambda do - # when - step_mother.find(:given, "doesn't exist") - end.should_not raise_error - end - - it "should create a default step which raises a pending error" do - # given - step_mother = StepMother.new - - # when - step = step_mother.find(:given, "doesn't exist") - - # then - step.should be_an_instance_of(Step) - - lambda do - step.perform(Object.new, "doesn't exist") - end.should raise_error(Spec::Example::ExamplePendingError, /Unimplemented/) - end - - it 'should clear itself' do - # given - step_mother = StepMother.new - step = Step.new("a given") do end - step_mother.store(:given, step) - - # when - step_mother.clear - - # then - step_mother.should be_empty - end - - it "should use assigned steps" do - step_mother = StepMother.new - - step = Step.new('step') {} - step_group = StepGroup.new - step_group.add(:given, step) - - step_mother.use(step_group) - - step_mother.find(:given, "step").should equal(step) - end - - end - end -end diff --git a/vendor/gems/rspec/spec/spec/story/step_spec.rb b/vendor/gems/rspec/spec/spec/story/step_spec.rb deleted file mode 100644 index 0b6e515e93..0000000000 --- a/vendor/gems/rspec/spec/spec/story/step_spec.rb +++ /dev/null @@ -1,200 +0,0 @@ -require File.dirname(__FILE__) + '/story_helper' - -module Spec - module Story - describe Step, "matching" do - it "should match a text string" do - step = Step.new("this text") {} - step.matches?("this text").should be_true - end - - it "should not match a text string that does not start the same" do - step = Step.new("this text") {} - step.matches?("Xthis text").should be_false - end - - it "should not match a text string that does not end the same" do - step = Step.new("this text") {} - step.matches?("this textX").should be_false - end - - it "should match a text string with a param" do - step = Step.new("this $param text") {} - step.matches?("this anything text").should be_true - end - - it "should not be greedy" do - step = Step.new("enter $value for $key") {} - step.parse_args("enter 3 for keys for a piano").should == ['3','keys for a piano'] - end - - it "should match a text string with 3 params" do - step = Step.new("1 $one 2 $two 3 $three 4") {} - step.matches?("1 a 2 b 3 c 4").should be_true - end - - it "should match a text string with a param at the beginning" do - step = Step.new("$one 2 3") {} - step.matches?("a 2 3").should be_true - end - - it "should match a text string with a param at the end" do - step = Step.new("1 2 $three") {} - step.matches?("1 2 c").should be_true - end - - it "should not match a different string" do - step = Step.new("this text") {} - step.matches?("other text").should be_false - end - - it "should match a regexp" do - step = Step.new(/this text/) {} - step.matches?("this text").should be_true - end - - it "should match a regexp with a match group" do - step = Step.new(/this (.*) text/) {} - step.matches?("this anything text").should be_true - end - - it "should match a regexp with a named variable" do - step = Step.new(/this $variable text/) {} - step.matches?("this anything text").should be_true - end - - it "should not match a non matching regexp" do - step = Step.new(/this (.*) text/) {} - step.matches?("other anything text").should be_false - end - - it "should not match a non matching regexp with a named variable" do - step = Step.new(/this $variable text/) {} - step.matches?("other anything text").should be_false - end - - it "should not get bogged down by parens in strings" do - step = Step.new("before () after") {} - step.matches?("before () after").should be_true - end - - it "should match any option of an alteration" do - step = Step.new(/(he|she) is cool/) {} - step.matches?("he is cool").should be_true - step.matches?("she is cool").should be_true - end - - it "should match alteration as well as a variable" do - step = Step.new(/(he|she) is (.*)/) {} - step.matches?("he is cool").should be_true - step.parse_args("he is cool").should == ['he', 'cool'] - end - - it "should match alteration as well as a named variable" do - step = Step.new(/(he|she) is $adjective/) {} - step.matches?("he is cool").should be_true - step.parse_args("he is cool").should == ['he', 'cool'] - end - - it "should match alteration as well as a anonymous and named variable" do - step = Step.new(/(he|she) is (.*?) $adjective/) {} - step.matches?("he is very cool").should be_true - step.parse_args("he is very cool").should == ['he', 'very', 'cool'] - end - - end - - describe Step do - it "should make complain with no block" do - lambda { - step = Step.new("foo") - }.should raise_error - end - - it "should perform itself on an object" do - # given - $instance = nil - step = Step.new 'step' do - $instance = self - end - instance = Object.new - - # when - step.perform(instance, "step") - - # then - $instance.should == instance - end - - it "should perform itself with one parameter with match expression" do - # given - $result = nil - step = Step.new 'an account with $count dollars' do |count| - $result = count - end - instance = Object.new - - # when - args = step.parse_args("an account with 3 dollars") - step.perform(instance, *args) - - # then - $result.should == "3" - end - - it "should perform itself with one parameter without a match expression" do - # given - $result = nil - step = Step.new 'an account with a balance of' do |amount| - $result = amount - end - instance = Object.new - - # when - step.perform(instance, 20) - - # then - $result.should == 20 - end - - it "should perform itself with 2 parameters" do - # given - $account_type = nil - $amount = nil - step = Step.new 'a $account_type account with $amount dollars' do |account_type, amount| - $account_type = account_type - $amount = amount - end - instance = Object.new - - # when - args = step.parse_args("a savings account with 3 dollars") - step.perform(instance, *args) - - # then - $account_type.should == "savings" - $amount.should == "3" - end - - it "should perform itself when defined with a regexp with 2 parameters" do - # given - $pronoun = nil - $adjective = nil - step = Step.new /(he|she) is (.*)/ do |pronoun, adjective| - $pronoun = pronoun - $adjective = adjective - end - instance = Object.new - - # when - args = step.parse_args("he is cool") - step.perform(instance, *args) - - # then - $pronoun.should == "he" - $adjective.should == "cool" - end - - end - end -end diff --git a/vendor/gems/rspec/spec/spec/story/story_helper.rb b/vendor/gems/rspec/spec/spec/story/story_helper.rb deleted file mode 100644 index bb906f2557..0000000000 --- a/vendor/gems/rspec/spec/spec/story/story_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -require File.dirname(__FILE__) + '/../../spec_helper' -require File.dirname(__FILE__) + '/builders' diff --git a/vendor/gems/rspec/spec/spec/story/story_spec.rb b/vendor/gems/rspec/spec/spec/story/story_spec.rb deleted file mode 100644 index 21257e9a72..0000000000 --- a/vendor/gems/rspec/spec/spec/story/story_spec.rb +++ /dev/null @@ -1,86 +0,0 @@ -require File.dirname(__FILE__) + '/story_helper' - -module Spec - module Story - describe Story do - it 'should run itself in a given object' do - # given - $instance = nil - story = Story.new 'title', 'narrative' do - $instance = self - end - object = Object.new - - # when - story.run_in(object) - - # then - $instance.should be(object) - end - - it 'should not raise an error if no block is supplied' do - # when - error = exception_from do - Story.new 'title', 'narrative' - end - - # then - error.should be_nil - end - - it "should raise when error raised running in another object" do - #given - story = Story.new 'title', 'narrative' do - raise "this is raised in the story" - end - object = Object.new - - # when/then - lambda do - story.run_in(object) - end.should raise_error - end - - it "should use the steps it is told to using a StepGroup" do - story = Story.new("title", "narrative", :steps => steps = StepGroup.new) do end - assignee = mock("assignee") - assignee.should_receive(:use).with(steps) - story.assign_steps_to(assignee) - end - - it "should use the steps it is told to using a key" do - begin - orig_rspec_story_steps = $rspec_story_steps - $rspec_story_steps = StepGroupHash.new - $rspec_story_steps[:foo] = steps = Object.new - - story = Story.new("title", "narrative", :steps_for => :foo) do end - assignee = mock("assignee") - - assignee.should_receive(:use).with(steps) - story.assign_steps_to(assignee) - ensure - $rspec_story_steps = orig_rspec_story_steps - end - end - - it "should use the steps it is told to using multiple keys" do - begin - orig_rspec_story_steps = $rspec_story_steps - $rspec_story_steps = StepGroupHash.new - $rspec_story_steps[:foo] = foo_steps = Object.new - $rspec_story_steps[:bar] = bar_steps = Object.new - - story = Story.new("title", "narrative", :steps_for => [:foo, :bar]) do end - assignee = mock("assignee") - - assignee.should_receive(:use).with(foo_steps) - assignee.should_receive(:use).with(bar_steps) - story.assign_steps_to(assignee) - ensure - $rspec_story_steps = orig_rspec_story_steps - end - end - end - end -end diff --git a/vendor/gems/rspec/spec/spec/story/world_spec.rb b/vendor/gems/rspec/spec/spec/story/world_spec.rb deleted file mode 100644 index f5113dc423..0000000000 --- a/vendor/gems/rspec/spec/spec/story/world_spec.rb +++ /dev/null @@ -1,423 +0,0 @@ -require File.dirname(__FILE__) + '/story_helper' - -require 'spec/story' - -module Spec - module Story - describe World do - before :each do - World.listeners.clear - end - - after :each do - World.listeners.clear - World.step_mother.clear - end - - it 'should create an object that mixes in a World' do - # when - obj = World::create - - # then - obj.should be_kind_of(World) - end - - it 'should create a World from any object type' do - # when - obj = World::create String - - # then - obj.should be_kind_of(String) - obj.should be_kind_of(World) - end - - it 'should pass arguments to #new when creating an object of a specified type that mixes in a world' do - # given - Thing = Struct.new(:name, :age) - - # when - obj = World::create Thing, "David", "I'm not telling" - - # then - obj.should be_an_instance_of(Thing) - obj.name.should == "David" - obj.age.should == "I'm not telling" - obj.should be_kind_of(World) - end - - def ensure_world_executes_step(&block) - # given - obj = World::create - $step_ran = false - - # when - obj.instance_eval(&block) - - # then - $step_ran.should be_true - end - - it 'should execute a Given, When or Then step' do - ensure_world_executes_step do - Given 'a given' do - $step_ran = true - end - end - - ensure_world_executes_step do - When 'an event' do - $step_ran = true - end - end - - ensure_world_executes_step do - Then 'an outcome' do - $step_ran = true - end - end - end - - it 'should interpret Given... And... as multiple givens' do - # given - world = World.create - $steps = [] - - # when - world.instance_eval do - Given 'step 1' do - $steps << 1 - end - And 'step 2' do - $steps << 2 - end - end - - # then - $steps.should == [1,2] - World.step_mother.find(:given, 'step 1').should_not be_nil - World.step_mother.find(:given, 'step 2').should_not be_nil - end - - it 'should interpret When... And... as multiple events' do - # given - world = World.create - $steps = [] - - # when - world.instance_eval do - When 'step 1' do - $steps << 1 - end - And 'step 2' do - $steps << 2 - end - end - - # then - $steps.should == [1,2] - World.step_mother.find(:when, 'step 1').should_not be_nil - World.step_mother.find(:when, 'step 2').should_not be_nil - end - - it 'should interpret Then... And... as multiple outcomes' do - # given - world = World.create - $steps = [] - - # when - world.instance_eval do - Then 'step 1' do - $steps << 1 - end - And 'step 2' do - $steps << 2 - end - end - - # then - $steps.should == [1,2] - World.step_mother.find(:then, 'step 1').should_not be_nil - World.step_mother.find(:then, 'step 2').should_not be_nil - end - - it 'should reuse a given across scenarios' do - # given - $num_invoked = 0 - a_world = World::create - a_world.instance_eval do - Given 'a given' do - $num_invoked += 1 - end - end - another_world = World::create - - # when - another_world.instance_eval do - Given 'a given' # without a body - end - - # then - $num_invoked.should == 2 - end - - it 'should reuse an event across scenarios' do - # given - $num_invoked = 0 - a_world = World::create - a_world.instance_eval do - When 'an event' do - $num_invoked += 1 - end - end - - another_world = World::create - - # when - another_world.instance_eval do - When 'an event' # without a body - end - - # then - $num_invoked.should == 2 - end - - it 'should reuse an outcome across scenarios' do - # given - $num_invoked = 0 - a_world = World::create - a_world.instance_eval do - Then 'an outcome' do - $num_invoked += 1 - end - end - - another_world = World::create - - # when - another_world.instance_eval do - Then 'an outcome' # without a body - end - - # then - $num_invoked.should == 2 - end - - it 'should preserve instance variables between steps within a scenario' do - # given - world = World::create - $first = nil - $second = nil - - # when - world.instance_eval do - Given 'given' do - @first = 'first' - end - When 'event' do - @second = @first # from given - end - Then 'outcome' do - $first = @first # from given - $second = @second # from event - end - end - - # then - $first.should == 'first' - $second.should == 'first' - end - - it 'should invoke a reused step in the new object instance' do - # given - $instances = [] - $debug = true - world1 = World.create - world1.instance_eval do - Given 'a given' do - $instances << self.__id__ - end - end - world2 = World.create - - # when - world2.instance_eval do - Given 'a given' # reused - Then 'an outcome' do - $instances << __id__ - end - end - $debug = false - # then - $instances.should == [ world1.__id__, world2.__id__, world2.__id__ ] - end - - def ensure_world_collects_error(expected_error, &block) - # given - world = World.create - # $error = nil - - # when - world.start_collecting_errors - world.instance_eval(&block) - - # then - world.should have(1).errors - world.errors[0].should be_kind_of(expected_error) - end - - it 'should collect a failure from a Given step' do - ensure_world_collects_error RuntimeError do - Given 'a given' do - raise RuntimeError, "oops" - end - end - end - - it 'should collect a failure from a When step' do - ensure_world_collects_error RuntimeError do - When 'an event' do - raise RuntimeError, "oops" - end - end - end - - it 'should collect a failure from a Then step' do - ensure_world_collects_error RuntimeError do - Then 'an outcome' do - raise RuntimeError, "oops" - end - end - end - - it 'should inform listeners when it runs a Given, When or Then step' do - # given - world = World.create - mock_listener1 = mock('listener1') - mock_listener2 = mock('listener2') - World.add_listener(mock_listener1) - World.add_listener(mock_listener2) - - # expect - mock_listener1.should_receive(:step_upcoming).with(:given, 'a context') - mock_listener1.should_receive(:step_succeeded).with(:given, 'a context') - mock_listener1.should_receive(:step_upcoming).with(:when, 'an event') - mock_listener1.should_receive(:step_succeeded).with(:when, 'an event') - mock_listener1.should_receive(:step_upcoming).with(:then, 'an outcome') - mock_listener1.should_receive(:step_succeeded).with(:then, 'an outcome') - - mock_listener2.should_receive(:step_upcoming).with(:given, 'a context') - mock_listener2.should_receive(:step_succeeded).with(:given, 'a context') - mock_listener2.should_receive(:step_upcoming).with(:when, 'an event') - mock_listener2.should_receive(:step_succeeded).with(:when, 'an event') - mock_listener2.should_receive(:step_upcoming).with(:then, 'an outcome') - mock_listener2.should_receive(:step_succeeded).with(:then, 'an outcome') - - # when - world.instance_eval do - Given 'a context' do end - When 'an event' do end - Then 'an outcome' do end - end - - # then - end - - it 'should tell listeners but not execute the step in dry-run mode' do - # given - Runner.stub!(:dry_run).and_return(true) - mock_listener = mock('listener') - World.add_listener(mock_listener) - $step_invoked = false - world = World.create - - # expect - mock_listener.should_receive(:step_upcoming).with(:given, 'a context') - mock_listener.should_receive(:step_succeeded).with(:given, 'a context') - - # when - world.instance_eval do - Given 'a context' do - $step_invoked = true - end - end - - # then - $step_invoked.should be(false) - end - - it 'should suppress listeners while it runs a GivenScenario' do - # given - $scenario_ran = false - - scenario = ScenarioBuilder.new.name('a scenario').to_scenario do - $scenario_ran = true - Given 'given' do end - When 'event' do end - Then 'outcome' do end - end - - given_scenario = GivenScenario.new('a scenario') - Runner::StoryRunner.should_receive(:scenario_from_current_story). - with('a scenario').and_return(scenario) - - world = World.create - listener = mock('listener') - World.add_listener(listener) - - # expect - listener.should_receive(:found_scenario).with(:'given scenario', 'a scenario') - listener.should_receive(:step_succeeded).never.with(:given, 'given') - listener.should_receive(:step_succeeded).never.with(:when, 'event') - listener.should_receive(:step_succeeded).never.with(:then, 'outcome') - - # when - world.GivenScenario 'a scenario' - - # then - $scenario_ran.should be_true - end - - it 'should interpret GivenScenario... And... as multiple givens' do - # given - world = World.create - $steps = [] - - scenario = ScenarioBuilder.new.name('a scenario').to_scenario do - $steps << 1 - end - Runner::StoryRunner.should_receive(:scenario_from_current_story). - with('a scenario').and_return(scenario) - - # when - world.instance_eval do - GivenScenario 'a scenario' - And 'step 2' do - $steps << 2 - end - end - - # then - $steps.should == [1,2] - World.step_mother.find(:given, 'step 2').should_not be_nil - end - - it 'should provide rspec matchers' do - # given - world = World.create - - # then - world.instance_eval do - 'hello'.should match(/^hello$/) - end - end - - it "should use assigned matchers" do - world = World.create - - World.should_receive(:use).with(steps = Object.new) - - World.use(steps) - end - end - end -end diff --git a/vendor/gems/rspec/spec/spec/translator_spec.rb b/vendor/gems/rspec/spec/spec/translator_spec.rb deleted file mode 100644 index 01293d9ee4..0000000000 --- a/vendor/gems/rspec/spec/spec/translator_spec.rb +++ /dev/null @@ -1,265 +0,0 @@ -require File.dirname(__FILE__) + '/../spec_helper.rb' -require 'spec/translator' - -describe "Translator" do - before do - @t = Spec::Translator.new - end - - it "should translate files" do - from = File.dirname(__FILE__) + '/..' - to = "#{Dir.tmpdir}/translated_specs" - @t.translate_dir(from, to) - end - - it "should translate context_setup do" do - @t.translate_line( - "context_setup do\n" - ).should eql( - "before(:all) do\n" - ) - end - - it "should translate context_setup {foo}" do - @t.translate_line( - "context_setup {foo}\n" - ).should eql( - "before(:all) {foo}\n" - ) - end - - it "should translate context ' to describe '" do - @t.translate_line( - "context 'Translator' do\n" - ).should eql( - "describe 'Translator' do\n" - ) - end - - it 'should translate context " to describe "' do - @t.translate_line( - 'context "Translator"' - ).should eql( - 'describe "Translator"' - ) - end - - it 'should translate spaces then context " to describe "' do - @t.translate_line( - ' context "Translator"' - ).should eql( - ' describe "Translator"' - ) - end - - it "should not translate context=foo" do - @t.translate_line(' context=foo').should eql(' context=foo') - end - - it "should not translate context = foo" do - @t.translate_line(' context = foo').should eql(' context = foo') - end - - it "should not translate context = foo" do - @t.translate_line(' context = foo').should eql(' context = foo') - end - - it "should translate should_be_close" do - @t.translate_line('5.0.should_be_close(5.0, 0.5)').should eql('5.0.should be_close(5.0, 0.5)') - end - - it "should translate should_not_raise" do - @t.translate_line('lambda { self.call }.should_not_raise').should eql('lambda { self.call }.should_not raise_error') - end - - it "should translate should_throw" do - @t.translate_line('lambda { self.call }.should_throw').should eql('lambda { self.call }.should throw_symbol') - end - - it "should not translate 0.9 should_not" do - @t.translate_line('@target.should_not @matcher').should eql('@target.should_not @matcher') - end - - it "should leave should_not_receive" do - @t.translate_line('@mock.should_not_receive(:not_expected).with("unexpected text")').should eql('@mock.should_not_receive(:not_expected).with("unexpected text")') - end - - it "should leave should_receive" do - @t.translate_line('@mock.should_receive(:not_expected).with("unexpected text")').should eql('@mock.should_receive(:not_expected).with("unexpected text")') - end - - it "should translate multi word predicates" do - @t.translate_line('foo.should_multi_word_predicate').should eql('foo.should be_multi_word_predicate') - end - - it "should translate multi word predicates prefixed with be" do - @t.translate_line('foo.should_be_multi_word_predicate').should eql('foo.should be_multi_word_predicate') - end - - it "should translate be(expected) to equal(expected)" do - @t.translate_line('foo.should_be :cool').should eql('foo.should equal :cool') - end - - it "should translate instance_of" do - @t.translate_line('5.should_be_an_instance_of(Integer)').should eql('5.should be_an_instance_of(Integer)') - end - - it "should translate should_be <" do - @t.translate_line('3.should_be < 4').should eql('3.should be < 4') - end - - it "should translate should_be <=" do - @t.translate_line('3.should_be <= 4').should eql('3.should be <= 4') - end - - it "should translate should_be >=" do - @t.translate_line('4.should_be >= 3').should eql('4.should be >= 3') - end - - it "should translate should_be >" do - @t.translate_line('4.should_be > 3').should eql('4.should be > 3') - end - - it "should translate should_be_happy" do - @t.translate_line("4.should_be_happy").should eql("4.should be_happy") - end - - it "should translate custom method taking regexp with parenthesis" do - @t.translate_line("@browser.should_contain_text(/Sn.rrunger og annet rusk/)").should eql("@browser.should be_contain_text(/Sn.rrunger og annet rusk/)") - end - - it "should translate custom method taking regexp without parenthesis" do - @t.translate_line("@browser.should_contain_text /Sn.rrunger og annet rusk/\n").should eql("@browser.should be_contain_text(/Sn.rrunger og annet rusk/)\n") - end - - it "should translate should_not_be_nil" do - @t.translate_line("foo.should_not_be_nil\n").should eql("foo.should_not be_nil\n") - end - - it "should translate kind of" do - @t.translate_line('@object.should_be_kind_of(MessageExpectation)').should( - eql('@object.should be_kind_of(MessageExpectation)')) - end - - it "should translate should_be_true" do - @t.translate_line("foo.should_be_true\n").should eql("foo.should be_true\n") - end - - # [#9674] spec_translate incorrectly handling shoud_match, when regexp in a var, in a block - # http://rubyforge.org/tracker/?func=detail&atid=3149&aid=9674&group_id=797 - it "should translate should_match on a regexp, in a var, in a block" do - @t.translate_line("collection.each { |c| c.should_match a_regexp_in_a_var }\n").should eql("collection.each { |c| c.should match(a_regexp_in_a_var) }\n") - @t.translate_line("collection.each{|c| c.should_match a_regexp_in_a_var}\n").should eql("collection.each{|c| c.should match(a_regexp_in_a_var) }\n") - end - - # From Rubinius specs - it "should translate close_to without parens" do - @t.translate_line("end.should_be_close 3.14159_26535_89793_23846, TOLERANCE\n").should eql("end.should be_close(3.14159_26535_89793_23846, TOLERANCE)\n") - end - - # [#9882] 0.9 Beta 1 - translator bugs - # http://rubyforge.org/tracker/index.php?func=detail&aid=9882&group_id=797&atid=3149 - it "should support symbol arguments" do - @t.translate_line( - "lambda { sequence.parse('bar') }.should_throw :ZeroWidthParseSuccess\n" - ).should eql( - "lambda { sequence.parse('bar') }.should throw_symbol(:ZeroWidthParseSuccess)\n" - ) - end - - # [#9882] 0.9 Beta 1 - translator bugs - # http://rubyforge.org/tracker/index.php?func=detail&aid=9882&group_id=797&atid=3149 - it "should support instance var arguments" do - @t.translate_line( - "a.should_eql @local" - ).should eql( - "a.should eql(@local)" - ) - end - - # [#9882] 0.9 Beta 1 - translator bugs - # http://rubyforge.org/tracker/index.php?func=detail&aid=9882&group_id=797&atid=3149 - it "should support lambdas as expecteds" do - @t.translate_line( - "@parslet.should_not_eql lambda { nil }.to_parseable" - ).should eql( - "@parslet.should_not eql(lambda { nil }.to_parseable)" - ) - end - - # [#9882] 0.9 Beta 1 - translator bugs - # http://rubyforge.org/tracker/index.php?func=detail&aid=9882&group_id=797&atid=3149 - it "should support fully qualified names" do - @t.translate_line( - "results.should_be_kind_of SimpleASTLanguage::Identifier" - ).should eql( - "results.should be_kind_of(SimpleASTLanguage::Identifier)" - ) - end - - # [#9882] 0.9 Beta 1 - translator bugs - # http://rubyforge.org/tracker/index.php?func=detail&aid=9882&group_id=797&atid=3149 - # it "should leave whitespace between expression and comments" do - # @t.translate_line( - # "lambda { @instance.foo = foo }.should_raise NoMethodError # no writer defined" - # ).should eql( - # "lambda { @instance.foo = foo }.should raise_error(NoMethodError) # no writer defined" - # ) - # end - - it "should translate redirects" do - @t.translate_line( - "controller.should_redirect_to 'http://not_existing_domain_for_novalis.test.host/404.html'" - ).should eql( - "controller.should redirect_to('http://not_existing_domain_for_novalis.test.host/404.html')" - ) - end - - it "should translate :any_args" do - @t.translate_line( - "mock.should_receive(:foo).with(:any_args)" - ).should eql( - "mock.should_receive(:foo).with(any_args)" - ) - end - - it "should translate :anything" do - @t.translate_line( - "mock.should_receive(:foo).with(:anything)" - ).should eql( - "mock.should_receive(:foo).with(anything)" - ) - end - - it "should translate :boolean" do - @t.translate_line( - "mock.should_receive(:foo).with(:boolean)" - ).should eql( - "mock.should_receive(:foo).with(boolean)" - ) - end - - it "should translate :no_args" do - @t.translate_line( - "mock.should_receive(:foo).with(:no_args)" - ).should eql( - "mock.should_receive(:foo).with(no_args)" - ) - end - - it "should translate :numeric" do - @t.translate_line( - "mock.should_receive(:foo).with(:numeric)" - ).should eql( - "mock.should_receive(:foo).with(an_instance_of(Numeric))" - ) - end - - it "should translate :string" do - @t.translate_line( - "mock.should_receive(:foo).with(:string)" - ).should eql( - "mock.should_receive(:foo).with(an_instance_of(String))" - ) - end -end diff --git a/vendor/gems/rspec/spec/spec_helper.rb b/vendor/gems/rspec/spec/spec_helper.rb deleted file mode 100644 index 1318176d55..0000000000 --- a/vendor/gems/rspec/spec/spec_helper.rb +++ /dev/null @@ -1,103 +0,0 @@ -require 'stringio' - -dir = File.dirname(__FILE__) -lib_path = File.expand_path("#{dir}/../lib") -$LOAD_PATH.unshift lib_path unless $LOAD_PATH.include?(lib_path) -$_spec_spec = true # Prevents Kernel.exit in various places - -require 'spec' -require 'spec/mocks' -require 'spec/story' -spec_classes_path = File.expand_path("#{dir}/../spec/spec/spec_classes") -require spec_classes_path unless $LOAD_PATH.include?(spec_classes_path) -require File.dirname(__FILE__) + '/../lib/spec/expectations/differs/default' - -module Spec - module Matchers - def fail - raise_error(Spec::Expectations::ExpectationNotMetError) - end - - def fail_with(message) - raise_error(Spec::Expectations::ExpectationNotMetError, message) - end - - class Pass - def matches?(proc, &block) - begin - proc.call - true - rescue Exception => @error - false - end - end - - def failure_message - @error.message + "\n" + @error.backtrace.join("\n") - end - end - - def pass - Pass.new - end - - class CorrectlyOrderedMockExpectation - def initialize(&event) - @event = event - end - - def expect(&expectations) - expectations.call - @event.call - end - end - - def during(&block) - CorrectlyOrderedMockExpectation.new(&block) - end - end -end - -class NonStandardError < Exception; end - -module Custom - class ExampleGroupRunner - attr_reader :options, :arg - def initialize(options, arg) - @options, @arg = options, arg - end - - def load_files(files) - end - - def run - end - end -end - -def exception_from(&block) - exception = nil - begin - yield - rescue StandardError => e - exception = e - end - exception -end - -describe "sandboxed rspec_options", :shared => true do - attr_reader :options - - before(:all) do - @original_rspec_options = $rspec_options - end - - before(:each) do - @options = ::Spec::Runner::Options.new(StringIO.new, StringIO.new) - $rspec_options = options - end - - after do - $rspec_options = @original_rspec_options - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/stories/all.rb b/vendor/gems/rspec/stories/all.rb deleted file mode 100644 index c2428fdf8c..0000000000 --- a/vendor/gems/rspec/stories/all.rb +++ /dev/null @@ -1,5 +0,0 @@ -require File.join(File.dirname(__FILE__), *%w[helper]) - -["example_groups","interop"].each do |dir| - require File.join(File.dirname(__FILE__), "#{dir}/stories") -end diff --git a/vendor/gems/rspec/stories/example_groups/autogenerated_docstrings b/vendor/gems/rspec/stories/example_groups/autogenerated_docstrings deleted file mode 100644 index b3ff689980..0000000000 --- a/vendor/gems/rspec/stories/example_groups/autogenerated_docstrings +++ /dev/null @@ -1,45 +0,0 @@ -Story: autogenerated docstrings - - As an RSpec user - I want examples to generate their own names - So that I can reduce duplication between example names and example code - - Scenario: run passing examples with ruby - Given the file ../../examples/pure/autogenerated_docstrings_example.rb - - When I run it with the ruby interpreter -fs - - Then the stdout should match /should equal 5/ - And the stdout should match /should be < 5/ - And the stdout should match /should include "a"/ - And the stdout should match /should respond to #size/ - - Scenario: run failing examples with ruby - Given the file ../../failing_examples/failing_autogenerated_docstrings_example.rb - - When I run it with the ruby interpreter -fs - - Then the stdout should match /should equal 2/ - And the stdout should match /should be > 5/ - And the stdout should match /should include "b"/ - And the stdout should match /should not respond to #size/ - - Scenario: run passing examples with spec - Given the file ../../examples/pure/autogenerated_docstrings_example.rb - - When I run it with the spec script -fs - - Then the stdout should match /should equal 5/ - And the stdout should match /should be < 5/ - And the stdout should match /should include "a"/ - And the stdout should match /should respond to #size/ - - Scenario: run failing examples with spec - Given the file ../../failing_examples/failing_autogenerated_docstrings_example.rb - - When I run it with the spec script -fs - - Then the stdout should match /should equal 2/ - And the stdout should match /should be > 5/ - And the stdout should match /should include "b"/ - And the stdout should match /should not respond to #size/ diff --git a/vendor/gems/rspec/stories/example_groups/example_group_with_should_methods b/vendor/gems/rspec/stories/example_groups/example_group_with_should_methods deleted file mode 100644 index 3d2bc61eb7..0000000000 --- a/vendor/gems/rspec/stories/example_groups/example_group_with_should_methods +++ /dev/null @@ -1,17 +0,0 @@ -Story: Spec::ExampleGroup with should methods - - As an RSpec adopter accustomed to classes and methods - I want to use should_* methods in an ExampleGroup - So that I use RSpec with classes and methods that look more like RSpec examples - - Scenario: Run with ruby - Given the file spec/example_group_with_should_methods.rb - When I run it with the ruby interpreter - Then the exit code should be 256 - And the stdout should match "2 examples, 1 failure" - - Scenario: Run with spec - Given the file spec/example_group_with_should_methods.rb - When I run it with the spec script - Then the exit code should be 256 - And the stdout should match "2 examples, 1 failure" diff --git a/vendor/gems/rspec/stories/example_groups/nested_groups b/vendor/gems/rspec/stories/example_groups/nested_groups deleted file mode 100644 index ede978563c..0000000000 --- a/vendor/gems/rspec/stories/example_groups/nested_groups +++ /dev/null @@ -1,17 +0,0 @@ -Story: Nested example groups - - As an RSpec user - I want to nest examples groups - So that I can better organize my examples - - Scenario: Run with ruby - Given the file ../../examples/pure/stack_spec_with_nested_example_groups.rb - When I run it with the ruby interpreter -fs - Then the stdout should match /Stack \(empty\)/ - And the stdout should match /Stack \(full\)/ - - Scenario: Run with ruby - Given the file ../../examples/pure/stack_spec_with_nested_example_groups.rb - When I run it with the spec script -fs - Then the stdout should match /Stack \(empty\)/ - And the stdout should match /Stack \(full\)/ diff --git a/vendor/gems/rspec/stories/example_groups/output b/vendor/gems/rspec/stories/example_groups/output deleted file mode 100644 index 4947bdcaff..0000000000 --- a/vendor/gems/rspec/stories/example_groups/output +++ /dev/null @@ -1,25 +0,0 @@ -Story: Getting correct output - - As an RSpec user - I want to see output only once - So that I don't get confused - - Scenario: Run with ruby - Given the file spec/simple_spec.rb - When I run it with the ruby interpreter - Then the exit code should be 0 - And the stdout should not match /\d+ tests, \d+ assertions, \d+ failures, \d+ errors/m - And the stdout should match "1 example, 0 failures" - - Scenario: Run with CommandLine object - Given the file spec/simple_spec.rb - When I run it with the CommandLine object - Then the exit code should be 0 - And the stdout should not match "Loaded suite" - And the stdout should not match /\d+ tests, \d+ assertions, \d+ failures, \d+ errors/m - And the stdout should match "1 example, 0 failures" - - Scenario: Tweak backtrace - Given the file stories/failing_story.rb - When I run it with the ruby interpreter - Then the stdout should not match /\/lib\/spec\// diff --git a/vendor/gems/rspec/stories/example_groups/stories.rb b/vendor/gems/rspec/stories/example_groups/stories.rb deleted file mode 100644 index e45882a93b..0000000000 --- a/vendor/gems/rspec/stories/example_groups/stories.rb +++ /dev/null @@ -1,7 +0,0 @@ -require File.join(File.dirname(__FILE__), *%w[.. helper]) - -with_steps_for :running_rspec do - Dir["#{File.dirname(__FILE__)}/*"].each do |file| - run file if File.file?(file) && !(file =~ /\.rb$/) - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/stories/helper.rb b/vendor/gems/rspec/stories/helper.rb deleted file mode 100644 index d9a105e761..0000000000 --- a/vendor/gems/rspec/stories/helper.rb +++ /dev/null @@ -1,6 +0,0 @@ -$LOAD_PATH.unshift File.expand_path("#{File.dirname(__FILE__)}/../lib") -require 'spec' -require 'tempfile' -require File.join(File.dirname(__FILE__), *%w[resources matchers smart_match]) -require File.join(File.dirname(__FILE__), *%w[resources helpers story_helper]) -require File.join(File.dirname(__FILE__), *%w[resources steps running_rspec]) diff --git a/vendor/gems/rspec/stories/interop/examples_and_tests_together b/vendor/gems/rspec/stories/interop/examples_and_tests_together deleted file mode 100644 index 6583f89c6c..0000000000 --- a/vendor/gems/rspec/stories/interop/examples_and_tests_together +++ /dev/null @@ -1,30 +0,0 @@ -Story: Spec and test together - - As an RSpec adopter with existing Test::Unit tests - I want to run a few specs alongside my existing Test::Unit tests - So that I can experience a smooth, gradual migration path - - Scenario: Run with ruby - Given the file test/spec_and_test_together.rb - - When I run it with the ruby interpreter -fs - - Then the exit code should be 256 - And the stdout should match "ATest" - And the stdout should match "Test::Unit::AssertionFailedError in 'An Example should fail with assert'" - And the stdout should match "'An Example should fail with should' FAILED" - And the stdout should match "10 examples, 6 failures" - And the stdout should match /expected: 40,\s*got: 4/m - And the stdout should match /expected: 50,\s*got: 5/m - Scenario: Run with spec - Given the file test/spec_and_test_together.rb - - When I run it with the spec script -fs - - Then the exit code should be 256 - Ands the stdout should match "ATest" - And the stdout should match "Test::Unit::AssertionFailedError in 'An Example should fail with assert'" - And the stdout should match "'An Example should fail with should' FAILED" - And the stdout should match "10 examples, 6 failures" - And the stdout should match /expected: 40,\s*got: 4/m - And the stdout should match /expected: 50,\s*got: 5/m diff --git a/vendor/gems/rspec/stories/interop/stories.rb b/vendor/gems/rspec/stories/interop/stories.rb deleted file mode 100644 index e45882a93b..0000000000 --- a/vendor/gems/rspec/stories/interop/stories.rb +++ /dev/null @@ -1,7 +0,0 @@ -require File.join(File.dirname(__FILE__), *%w[.. helper]) - -with_steps_for :running_rspec do - Dir["#{File.dirname(__FILE__)}/*"].each do |file| - run file if File.file?(file) && !(file =~ /\.rb$/) - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/stories/interop/test_case_with_should_methods b/vendor/gems/rspec/stories/interop/test_case_with_should_methods deleted file mode 100644 index d618c50e26..0000000000 --- a/vendor/gems/rspec/stories/interop/test_case_with_should_methods +++ /dev/null @@ -1,17 +0,0 @@ -Story: Test::Unit::TestCase extended by rspec with should methods - - As an RSpec adopter with existing Test::Unit tests - I want to use should_* methods in a Test::Unit::TestCase - So that I use RSpec with classes and methods that look more like RSpec examples - - Scenario: Run with ruby - Given the file test/test_case_with_should_methods.rb - When I run it with the ruby interpreter - Then the exit code should be 256 - And the stdout should match "5 examples, 3 failures" - - Scenario: Run with spec - Given the file test/test_case_with_should_methods.rb - When I run it with the spec script - Then the exit code should be 256 - And the stdout should match "5 examples, 3 failures" diff --git a/vendor/gems/rspec/stories/pending_stories/README b/vendor/gems/rspec/stories/pending_stories/README deleted file mode 100644 index 2f9b443142..0000000000 --- a/vendor/gems/rspec/stories/pending_stories/README +++ /dev/null @@ -1,3 +0,0 @@ -This directory contains stories that are currently not passing -because they are new or they represent regressions. - diff --git a/vendor/gems/rspec/stories/resources/helpers/cmdline.rb b/vendor/gems/rspec/stories/resources/helpers/cmdline.rb deleted file mode 100644 index ab86bd3e19..0000000000 --- a/vendor/gems/rspec/stories/resources/helpers/cmdline.rb +++ /dev/null @@ -1,9 +0,0 @@ -$:.push File.join(File.dirname(__FILE__), *%w[.. .. .. lib]) -require 'spec' - -# Uncommenting next line will break the output story (no output!!) -# rspec_options -options = Spec::Runner::OptionParser.parse( - ARGV, $stderr, $stdout -) -Spec::Runner::CommandLine.run(options) diff --git a/vendor/gems/rspec/stories/resources/helpers/story_helper.rb b/vendor/gems/rspec/stories/resources/helpers/story_helper.rb deleted file mode 100644 index e0aef5b453..0000000000 --- a/vendor/gems/rspec/stories/resources/helpers/story_helper.rb +++ /dev/null @@ -1,16 +0,0 @@ -require 'spec/story' -require File.dirname(__FILE__) + '/../../../spec/ruby_forker' - -module StoryHelper - include RubyForker - - def spec(args, stderr) - ruby("#{File.dirname(__FILE__) + '/../../../bin/spec'} #{args}", stderr) - end - - def cmdline(args, stderr) - ruby("#{File.dirname(__FILE__) + '/../../resources/helpers/cmdline.rb'} #{args}", stderr) - end - - Spec::Story::World.send :include, self -end diff --git a/vendor/gems/rspec/stories/resources/matchers/smart_match.rb b/vendor/gems/rspec/stories/resources/matchers/smart_match.rb deleted file mode 100644 index 7b1672bc0e..0000000000 --- a/vendor/gems/rspec/stories/resources/matchers/smart_match.rb +++ /dev/null @@ -1,37 +0,0 @@ -module Spec - module Matchers - class SmartMatch - def initialize(expected) - @expected = expected - end - - def matches?(actual) - @actual = actual - # Satisfy expectation here. Return false or raise an error if it's not met. - - if @expected =~ /^\/.*\/?$/ || @expected =~ /^".*"$/ - regex_or_string = eval(@expected) - if Regexp === regex_or_string - (@actual =~ regex_or_string) ? true : false - else - @actual.index(regex_or_string) != nil - end - else - false - end - end - - def failure_message - "expected #{@actual.inspect} to smart_match #{@expected.inspect}, but it didn't" - end - - def negative_failure_message - "expected #{@actual.inspect} not to smart_match #{@expected.inspect}, but it did" - end - end - - def smart_match(expected) - SmartMatch.new(expected) - end - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/stories/resources/spec/example_group_with_should_methods.rb b/vendor/gems/rspec/stories/resources/spec/example_group_with_should_methods.rb deleted file mode 100644 index 4c0505d2a0..0000000000 --- a/vendor/gems/rspec/stories/resources/spec/example_group_with_should_methods.rb +++ /dev/null @@ -1,12 +0,0 @@ -$:.push File.join(File.dirname(__FILE__), *%w[.. .. .. lib]) -require 'spec' - -class MySpec < Spec::ExampleGroup - def should_pass_with_should - 1.should == 1 - end - - def should_fail_with_should - 1.should == 2 - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/stories/resources/spec/simple_spec.rb b/vendor/gems/rspec/stories/resources/spec/simple_spec.rb deleted file mode 100644 index 2fb67ba49a..0000000000 --- a/vendor/gems/rspec/stories/resources/spec/simple_spec.rb +++ /dev/null @@ -1,8 +0,0 @@ -$:.push File.join(File.dirname(__FILE__), *%w[.. .. .. lib]) -require 'spec' - -describe "Running an Example" do - it "should not output twice" do - true.should be_true - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/stories/resources/steps/running_rspec.rb b/vendor/gems/rspec/stories/resources/steps/running_rspec.rb deleted file mode 100644 index 496847fe4d..0000000000 --- a/vendor/gems/rspec/stories/resources/steps/running_rspec.rb +++ /dev/null @@ -1,50 +0,0 @@ -steps_for :running_rspec do - - Given("the file $relative_path") do |relative_path| - @path = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "resources", relative_path)) - unless File.exist?(@path) - raise "could not find file at #{@path}" - end - end - - When("I run it with the $interpreter") do |interpreter| - stderr_file = Tempfile.new('rspec') - stderr_file.close - @stdout = case(interpreter) - when /^ruby interpreter/ - args = interpreter.gsub('ruby interpreter','') - ruby("#{@path}#{args}", stderr_file.path) - when /^spec script/ - args = interpreter.gsub('spec script','') - spec("#{@path}#{args}", stderr_file.path) - when 'CommandLine object' then cmdline(@path, stderr_file.path) - else raise "Unknown interpreter: #{interpreter}" - end - @stderr = IO.read(stderr_file.path) - @exit_code = $?.to_i - end - - Then("the exit code should be $exit_code") do |exit_code| - if @exit_code != exit_code.to_i - raise "Did not exit with #{exit_code}, but with #{@exit_code}. Standard error:\n#{@stderr}" - end - end - - Then("the $stream should match $regex") do |stream, string_or_regex| - written = case(stream) - when 'stdout' then @stdout - when 'stderr' then @stderr - else raise "Unknown stream: #{stream}" - end - written.should smart_match(string_or_regex) - end - - Then("the $stream should not match $regex") do |stream, string_or_regex| - written = case(stream) - when 'stdout' then @stdout - when 'stderr' then @stderr - else raise "Unknown stream: #{stream}" - end - written.should_not smart_match(string_or_regex) - end -end diff --git a/vendor/gems/rspec/stories/resources/stories/failing_story.rb b/vendor/gems/rspec/stories/resources/stories/failing_story.rb deleted file mode 100644 index cc9506c660..0000000000 --- a/vendor/gems/rspec/stories/resources/stories/failing_story.rb +++ /dev/null @@ -1,15 +0,0 @@ -$:.push File.join(File.dirname(__FILE__), *%w[.. .. .. lib]) - -require 'spec/story' - -Story "Failing story", -%(As an RSpec user - I want a failing test - So that I can observe the output) do - - Scenario "Failing scenario" do - Then "true should be false" do - true.should == false - end - end -end diff --git a/vendor/gems/rspec/stories/resources/test/spec_and_test_together.rb b/vendor/gems/rspec/stories/resources/test/spec_and_test_together.rb deleted file mode 100644 index eb2b4e0740..0000000000 --- a/vendor/gems/rspec/stories/resources/test/spec_and_test_together.rb +++ /dev/null @@ -1,57 +0,0 @@ -$:.push File.join(File.dirname(__FILE__), *%w[.. .. .. lib]) -require 'spec' -# TODO - this should not be necessary, ay? -require 'spec/interop/test' - -describe "An Example" do - it "should pass with assert" do - assert true - end - - it "should fail with assert" do - assert false - end - - it "should pass with should" do - 1.should == 1 - end - - it "should fail with should" do - 1.should == 2 - end -end - -class ATest < Test::Unit::TestCase - def test_should_pass_with_assert - assert true - end - - def test_should_fail_with_assert - assert false - end - - def test_should_pass_with_should - 1.should == 1 - end - - def test_should_fail_with_should - 1.should == 2 - end - - def setup - @from_setup ||= 3 - @from_setup += 1 - end - - def test_should_fail_with_setup_method_variable - @from_setup.should == 40 - end - - before do - @from_before = @from_setup + 1 - end - - def test_should_fail_with_before_block_variable - @from_before.should == 50 - end -end \ No newline at end of file diff --git a/vendor/gems/rspec/stories/resources/test/test_case_with_should_methods.rb b/vendor/gems/rspec/stories/resources/test/test_case_with_should_methods.rb deleted file mode 100644 index 3912429e3f..0000000000 --- a/vendor/gems/rspec/stories/resources/test/test_case_with_should_methods.rb +++ /dev/null @@ -1,30 +0,0 @@ -$:.push File.join(File.dirname(__FILE__), *%w[.. .. .. lib]) -require 'test/unit' -require 'spec' -require 'spec/interop/test' - -class MySpec < Test::Unit::TestCase - def should_pass_with_should - 1.should == 1 - end - - def should_fail_with_should - 1.should == 2 - end - - def should_pass_with_assert - assert true - end - - def should_fail_with_assert - assert false - end - - def test - raise "This is not a real test" - end - - def test_ify - raise "This is a real test" - end -end \ No newline at end of file From 074eda99ffd186a667ec3f50fbb6d4e267f585cb Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Thu, 15 Jan 2009 11:41:51 -0600 Subject: [PATCH 0295/3753] Fixing autotest, now that vendor/ is gone Signed-off-by: Luke Kanies --- autotest/facter_rspec.rb | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/autotest/facter_rspec.rb b/autotest/facter_rspec.rb index bd057a8e75..668409bad7 100644 --- a/autotest/facter_rspec.rb +++ b/autotest/facter_rspec.rb @@ -38,18 +38,8 @@ end class Autotest::FacterRspec < Autotest::Rspec - # Autotest will look for spec commands in the following - # locations, in this order: - # - # * bin/spec - # * default spec bin/loader installed in Rubygems - # * our local vendor/gems/rspec/bin/spec - def spec_commands - [ - File.join('vendor', 'gems', 'rspec', 'bin', 'spec') , - File.join('bin', 'spec'), - File.join(Config::CONFIG['bindir'], 'spec') - ] - end - + def spec_commands + ENV["AUTOTEST"] = "true" + ENV["PATH"].split(":").collect { |dir| File.join(dir, "spec") } + end end From 91e25b9cc12fcb5e8d684b6258aec20735b992e1 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Fri, 2 Jan 2009 15:08:26 -0600 Subject: [PATCH 0296/3753] Fixing indentation everywhere Signed-off-by: Luke Kanies --- lib/facter.rb | 10 +- lib/facter/Cfkey.rb | 4 +- lib/facter/fqdn.rb | 3 +- lib/facter/ipmess.rb | 46 ++++---- lib/facter/kernelrelease.rb | 4 +- lib/facter/kernelversion.rb | 8 +- lib/facter/lsb.rb | 2 +- lib/facter/macaddress.rb | 52 ++++----- lib/facter/manufacturer.rb | 2 +- lib/facter/operatingsystemrelease.rb | 16 +-- lib/facter/processor.rb | 2 +- lib/facter/puppetversion.rb | 4 +- lib/facter/util/fact.rb | 2 +- lib/facter/util/ip.rb | 151 ++++++++++++++------------- lib/facter/util/macosx.rb | 49 +++++---- lib/facter/util/manufacturer.rb | 3 +- lib/facter/util/netmask.rb | 44 ++++---- lib/facter/util/plist.rb | 2 +- lib/facter/virtual.rb | 133 ++++++++++++----------- spec/unit/facter.rb | 2 +- spec/unit/util/collection.rb | 4 +- spec/unit/util/ip.rb | 5 +- spec/unit/util/loader.rb | 2 +- 23 files changed, 272 insertions(+), 278 deletions(-) diff --git a/lib/facter.rb b/lib/facter.rb index 46a97176da..e764255dbb 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -1,20 +1,20 @@ #-- # Copyright 2006 Luke Kanies -# +# # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. -# +# # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. -# +# # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -# +# #-- module Facter @@ -153,7 +153,7 @@ def self.debugging(bit) case bit when TrueClass: @@debug = 1 when FalseClass: @@debug = 0 - when Fixnum: + when Fixnum: if bit > 0 @@debug = 1 else diff --git a/lib/facter/Cfkey.rb b/lib/facter/Cfkey.rb index 4c17a0a126..a4e0c11798 100644 --- a/lib/facter/Cfkey.rb +++ b/lib/facter/Cfkey.rb @@ -22,7 +22,7 @@ "/var/cfengine/ppkeys/localhost.pub", "/var/lib/cfengine/ppkeys/localhost.pub", "/var/lib/cfengine2/ppkeys/localhost.pub" - ].each { |file| + ].each do |file| if FileTest.file?(file) File.open(file) { |openfile| value = openfile.readlines.reject { |line| @@ -35,7 +35,7 @@ if value break end - } + end value end diff --git a/lib/facter/fqdn.rb b/lib/facter/fqdn.rb index 1fcc764d76..5ebc5f5c0b 100644 --- a/lib/facter/fqdn.rb +++ b/lib/facter/fqdn.rb @@ -1,4 +1,5 @@ -Facter.add(:fqdn) do setcode do +Facter.add(:fqdn) do + setcode do host = Facter.value(:hostname) domain = Facter.value(:domain) if host and domain diff --git a/lib/facter/ipmess.rb b/lib/facter/ipmess.rb index e8793006bc..c61b1ec448 100644 --- a/lib/facter/ipmess.rb +++ b/lib/facter/ipmess.rb @@ -3,7 +3,7 @@ # # Original concept Copyright (C) 2007 psychedelys # Update and *BSD support (C) 2007 James Turnbull -# +# require 'facter/util/ip' @@ -14,33 +14,33 @@ end end -case Facter.value(:kernel) - when 'SunOS', 'Linux', 'OpenBSD', 'NetBSD', 'FreeBSD' - Facter::IPAddress.get_interfaces.each do |interface| - mi = interface.gsub(/[:.]/, '_') +case Facter.value(:kernel) +when 'SunOS', 'Linux', 'OpenBSD', 'NetBSD', 'FreeBSD' + Facter::IPAddress.get_interfaces.each do |interface| + mi = interface.gsub(/[:.]/, '_') - Facter.add("ipaddress_" + mi) do - confine :kernel => [ :sunos, :freebsd, :openbsd, :netbsd, :linux ] - setcode do - label = 'ipaddress' - Facter::IPAddress.get_interface_value(interface, label) + Facter.add("ipaddress_" + mi) do + confine :kernel => [ :sunos, :freebsd, :openbsd, :netbsd, :linux ] + setcode do + label = 'ipaddress' + Facter::IPAddress.get_interface_value(interface, label) + end end - end - Facter.add("macaddress_" + mi) do - confine :kernel => [ :sunos, :freebsd, :openbsd, :netbsd, :linux ] - setcode do - label = 'macaddress' - Facter::IPAddress.get_interface_value(interface, label) + Facter.add("macaddress_" + mi) do + confine :kernel => [ :sunos, :freebsd, :openbsd, :netbsd, :linux ] + setcode do + label = 'macaddress' + Facter::IPAddress.get_interface_value(interface, label) + end end - end - Facter.add("netmask_" + mi) do - confine :kernel => [ :sunos, :freebsd, :openbsd, :netbsd, :linux ] - setcode do - label = 'netmask' - Facter::IPAddress.get_interface_value(interface, label) + Facter.add("netmask_" + mi) do + confine :kernel => [ :sunos, :freebsd, :openbsd, :netbsd, :linux ] + setcode do + label = 'netmask' + Facter::IPAddress.get_interface_value(interface, label) + end end end - end end diff --git a/lib/facter/kernelrelease.rb b/lib/facter/kernelrelease.rb index 5dccf8f984..2f6ae45119 100644 --- a/lib/facter/kernelrelease.rb +++ b/lib/facter/kernelrelease.rb @@ -14,10 +14,10 @@ version = "" connection_string = "winmgmts://./root/cimv2" wmi = WIN32OLE.connect(connection_string) - wmi.ExecQuery("SELECT Version from Win32_OperatingSystem").each { |ole| + wmi.ExecQuery("SELECT Version from Win32_OperatingSystem").each do |ole| version = "#{ole.Version}" break - } + end version end end diff --git a/lib/facter/kernelversion.rb b/lib/facter/kernelversion.rb index 7d2b31d53c..4abb6d6b70 100644 --- a/lib/facter/kernelversion.rb +++ b/lib/facter/kernelversion.rb @@ -1,5 +1,5 @@ Facter.add("kernelversion") do - setcode do - Facter['kernelrelease'].value.split('-')[0] - end -end + setcode do + Facter['kernelrelease'].value.split('-')[0] + end +end diff --git a/lib/facter/lsb.rb b/lib/facter/lsb.rb index 2a1ca6eefc..fc07437fda 100644 --- a/lib/facter/lsb.rb +++ b/lib/facter/lsb.rb @@ -27,7 +27,7 @@ @@lsbtime = Time.now @@lsbdata = Facter::Util::Resolution.exec('lsb_release -a 2>/dev/null') end - + if pattern.match(@@lsbdata) $1 else diff --git a/lib/facter/macaddress.rb b/lib/facter/macaddress.rb index 28ded4ca9b..e048209bc7 100644 --- a/lib/facter/macaddress.rb +++ b/lib/facter/macaddress.rb @@ -3,9 +3,9 @@ setcode do ether = [] output = %x{/sbin/ifconfig -a} - output.each {|s| - ether.push($1) if s =~ /(?:ether|HWaddr) (\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/ - } + output.each do |s| + ether.push($1) if s =~ /(?:ether|HWaddr) (\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/ + end ether[0] end end @@ -15,11 +15,11 @@ setcode do ether = [] output = %x{/sbin/ifconfig} - output.each {|s| - if s =~ /(?:ether|lladdr)\s+(\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ - ether.push($1) - end - } + output.each do |s| + if s =~ /(?:ether|lladdr)\s+(\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ + ether.push($1) + end + end ether[0] end end @@ -30,12 +30,12 @@ ether = nil output = %x{/sbin/ifconfig} - output.split(/^\S/).each { |str| + output.split(/^\S/).each do |str| if str =~ /10baseT/ # we're wired str =~ /ether (\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ ether = $1 end - } + end ether end @@ -47,19 +47,19 @@ ether = [] ip = nil output = %x{/usr/sbin/ifconfig -a} - output.each { |str| + output.each do |str| if str =~ /([a-z]+\d+): flags=/ devname = $1 unless devname =~ /lo0/ - output2 = %x{/usr/bin/entstat #{devname}} - output2.each { |str2| - if str2 =~ /^Hardware Address: (\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/ - ether.push($1) - end - } + output2 = %x{/usr/bin/entstat #{devname}} + output2.each do |str2| + if str2 =~ /^Hardware Address: (\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/ + ether.push($1) + end + end end end - } + end ether[0] end end @@ -67,13 +67,13 @@ Facter.add(:macaddress) do confine :kernel => %w(windows) setcode do - ether = [] - output = %x{ipconfig /all} - output.split(/\r\n/).each do |str| - if str =~ /.*Physical Address.*: (\w{1,2}-\w{1,2}-\w{1,2}-\w{1,2}-\w{1,2}-\w{1,2})/ - ether.push($1.gsub(/-/, ":")) - end - end - ether[0] + ether = [] + output = %x{ipconfig /all} + output.split(/\r\n/).each do |str| + if str =~ /.*Physical Address.*: (\w{1,2}-\w{1,2}-\w{1,2}-\w{1,2}-\w{1,2}-\w{1,2})/ + ether.push($1.gsub(/-/, ":")) + end + end + ether[0] end end diff --git a/lib/facter/manufacturer.rb b/lib/facter/manufacturer.rb index b217fdc4d1..d2b13b99bb 100644 --- a/lib/facter/manufacturer.rb +++ b/lib/facter/manufacturer.rb @@ -16,4 +16,4 @@ ] } -Facter::Manufacturer.dmi_find_system_info(query) +Facter::Manufacturer.dmi_find_system_info(query) diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index 586d104a16..f88278a134 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -29,11 +29,11 @@ Facter.add(:operatingsystemrelease) do confine :operatingsystem => %w{CentOS} setcode do - centos_release = Facter::Util::Resolution.exec("sed -r -e 's/CentOS release //' -e 's/ \((Branch|Final)\)//' /etc/redhat-release") + centos_release = Facter::Util::Resolution.exec("sed -r -e 's/CentOS release //' -e 's/ \((Branch|Final)\)//' /etc/redhat-release") if centos_release =~ /5/ - release = Facter::Util::Resolution.exec('rpm -q --qf \'%{VERSION}.%{RELEASE}\' centos-release | cut -d. -f1,2') + release = Facter::Util::Resolution.exec('rpm -q --qf \'%{VERSION}.%{RELEASE}\' centos-release | cut -d. -f1,2') else - release = centos_release + release = centos_release end end end @@ -42,16 +42,16 @@ confine :operatingsystem => %w{Debian} setcode do release = Facter::Util::Resolution.exec('cat /etc/debian_version') - end + end end Facter.add(:operatingsystemrelease) do confine :operatingsystem => %w{Ubuntu} setcode do release = Facter::Util::Resolution.exec('cat /etc/issue') - if release =~ /Ubuntu (\d+.\d+)/ - $1 - end + if release =~ /Ubuntu (\d+.\d+)/ + $1 + end end end @@ -76,7 +76,7 @@ Facter.add(:operatingsystemrelease) do confine :operatingsystem => %w{Solaris} setcode do - release = Facter::Util::Resolution.exec('uname -v') + release = Facter::Util::Resolution.exec('uname -v') end end diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb index afc604e220..154cced912 100644 --- a/lib/facter/processor.rb +++ b/lib/facter/processor.rb @@ -46,7 +46,7 @@ procs.each do |proc| if proc =~ /^proc(\d+)/ processor_num = $1.to_i - # Not retrieving the frequency since AIX 4.3.3 doesn't support the + # Not retrieving the frequency since AIX 4.3.3 doesn't support the # attribute and some people still use the OS. proctype = Facter::Util::Resolution.exec('lsattr -El proc0 -a type') if proctype =~ /^type\s+(\S+)\s+/ diff --git a/lib/facter/puppetversion.rb b/lib/facter/puppetversion.rb index 66fcfe8b21..d2eb1f3061 100644 --- a/lib/facter/puppetversion.rb +++ b/lib/facter/puppetversion.rb @@ -1,10 +1,10 @@ Facter.add(:puppetversion) do - setcode { + setcode do begin require 'puppet' Puppet::PUPPETVERSION.to_s rescue LoadError nil end - } + end end diff --git a/lib/facter/util/fact.rb b/lib/facter/util/fact.rb index ca2ba033ea..cfdfb602c9 100644 --- a/lib/facter/util/fact.rb +++ b/lib/facter/util/fact.rb @@ -102,7 +102,7 @@ def searching? def searching if searching? Facter.debug "Caught recursion on %s" % @name - + # return a cached value if we've got it if @value return @value diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index 544985838d..3dbfcda68a 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -1,27 +1,27 @@ +# A base module for collecting IP-related +# information from all kinds of platforms. module Facter::IPAddress - def self.get_interfaces - - int = nil - - output = Facter::IPAddress.get_all_interface_output() - # We get lots of warnings on platforms that don't get an output - # made. - if output - int = output.scan(/^\w+[.:]?\d+/) - else - [] - end - + int = nil + + output = Facter::IPAddress.get_all_interface_output() + + # We get lots of warnings on platforms that don't get an output + # made. + if output + int = output.scan(/^\w+[.:]?\d+/) + else + [] + end end def self.get_all_interface_output case Facter.value(:kernel) - when 'Linux', 'OpenBSD', 'NetBSD', 'FreeBSD' - output = %x{/sbin/ifconfig -a} - when 'SunOS' - output = %x{/usr/sbin/ifconfig -a} + when 'Linux', 'OpenBSD', 'NetBSD', 'FreeBSD' + output = %x{/sbin/ifconfig -a} + when 'SunOS' + output = %x{/usr/sbin/ifconfig -a} end output end @@ -29,9 +29,9 @@ def self.get_all_interface_output def self.get_single_interface_output(interface) output = "" case Facter.value(:kernel) - when 'Linux', 'OpenBSD', 'NetBSD', 'FreeBSD' - output = %x{/sbin/ifconfig #{interface}} - when 'SunOS' + when 'Linux', 'OpenBSD', 'NetBSD', 'FreeBSD' + output = %x{/sbin/ifconfig #{interface}} + when 'SunOS' output = %x{/usr/sbin/ifconfig #{interface}} end output @@ -43,75 +43,76 @@ def self.get_bonding_master(interface) end # We need ip instead of ifconfig because it will show us # the bonding master device. - if not FileTest.executable?("/sbin/ip") + if not FileTest.executable?("/sbin/ip") return nil end regex = /SLAVE[,>].* (bond[0-9]+)/ - ethbond = regex.match(%x{/sbin/ip link show #{interface}}) - if ethbond + ethbond = regex.match(%x{/sbin/ip link show #{interface}}) + if ethbond device = ethbond[1] else device = nil end device end - + def self.get_interface_value(interface, label) - - tmp1 = [] + tmp1 = [] - case Facter.value(:kernel) - when 'Linux' - addr = /inet addr:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ - mac = /(?:ether|HWaddr)\s+(\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/ - mask = /Mask:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ - when 'OpenBSD', 'NetBSD', 'FreeBSD' - addr = /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ - mac = /(?:ether|lladdr)\s+(\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ - mask = /netmask\s+(\w{10})/ - when 'SunOS' - addr = /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ - mac = /(?:ether|lladdr)\s+(\w?\w:\w?\w:\w?\w:\w?\w:\w?\w:\w?\w)/ - mask = /netmask\s+(\w{8})/ - end + # LAK:NOTE This is pretty ugly - two case statements being used for most of the + # logic. These should be pulled into a small dispatch table. We don't have tests for this code, + # though, so it's not exactly trivial to do so. + case Facter.value(:kernel) + when 'Linux' + addr = /inet addr:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + mac = /(?:ether|HWaddr)\s+(\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/ + mask = /Mask:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + when 'OpenBSD', 'NetBSD', 'FreeBSD' + addr = /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + mac = /(?:ether|lladdr)\s+(\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ + mask = /netmask\s+(\w{10})/ + when 'SunOS' + addr = /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + mac = /(?:ether|lladdr)\s+(\w?\w:\w?\w:\w?\w:\w?\w:\w?\w:\w?\w)/ + mask = /netmask\s+(\w{8})/ + end + + case label + when 'ipaddress' + regex = addr + when 'macaddress' + regex = mac + when 'netmask' + regex = mask + end - case label - when 'ipaddress' - regex = addr - when 'macaddress' - regex = mac - when 'netmask' - regex = mask - end + # Linux changes the MAC address reported via ifconfig when an ethernet interface + # becomes a slave of a bonding device to the master MAC address. + # We have to dig a bit to get the original/real MAC address of the interface. + bonddev = get_bonding_master(interface) + if label == 'macaddress' and bonddev + bondinfo = IO.readlines("/proc/net/bonding/#{bonddev}") + hwaddrre = /^Slave Interface: #{interface}\n[^\n].+?\nPermanent HW addr: (([0-9a-fA-F]{2}:?)*)$/m + value = hwaddrre.match(bondinfo.to_s)[1].upcase + else + output_int = get_single_interface_output(interface) - # Linux changes the MAC address reported via ifconfig when an ethernet interface - # becomes a slave of a bonding device to the master MAC address. - # We have to dig a bit to get the original/real MAC address of the interface. - bonddev = get_bonding_master(interface) - if label == 'macaddress' and bonddev - bondinfo = IO.readlines("/proc/net/bonding/#{bonddev}") - hwaddrre = /^Slave Interface: #{interface}\n[^\n].+?\nPermanent HW addr: (([0-9a-fA-F]{2}:?)*)$/m - value = hwaddrre.match(bondinfo.to_s)[1].upcase - else - output_int = get_single_interface_output(interface) - - if interface != "lo" && interface != "lo0" - output_int.each { |s| - if s =~ regex - value = $1 - if label == 'netmask' && Facter.value(:kernel) == "SunOS" - value = value.scan(/../).collect do |byte| byte.to_i(16) end.join('.') - end - tmp1.push(value) - end - } - end + if interface != "lo" && interface != "lo0" + output_int.each { |s| + if s =~ regex + value = $1 + if label == 'netmask' && Facter.value(:kernel) == "SunOS" + value = value.scan(/../).collect do |byte| byte.to_i(16) end.join('.') + end + tmp1.push(value) + end + } + end - if tmp1 - value = tmp1.shift - end + if tmp1 + value = tmp1.shift + end + end end - - end end diff --git a/lib/facter/util/macosx.rb b/lib/facter/util/macosx.rb index 1e596d86f9..2704a7c3ba 100644 --- a/lib/facter/util/macosx.rb +++ b/lib/facter/util/macosx.rb @@ -17,34 +17,33 @@ ## module Facter::Macosx - require 'thread' - require 'facter/util/plist' + require 'thread' + require 'facter/util/plist' - # JJM I'd really like to dynamically generate these methods - # by looking at the _name key of the _items dict for each _dataType + # JJM I'd really like to dynamically generate these methods + # by looking at the _name key of the _items dict for each _dataType - def self.hardware_overview - # JJM Perhaps we should cache the XML data in a "class" level object. - top_level_plist = Plist::parse_xml %x{/usr/sbin/system_profiler -xml SPHardwareDataType} - system_hardware = top_level_plist[0]['_items'][0] - system_hardware.delete '_name' - system_hardware - end + def self.hardware_overview + # JJM Perhaps we should cache the XML data in a "class" level object. + top_level_plist = Plist::parse_xml %x{/usr/sbin/system_profiler -xml SPHardwareDataType} + system_hardware = top_level_plist[0]['_items'][0] + system_hardware.delete '_name' + system_hardware + end - # SPSoftwareDataType - def self.os_overview - top_level_plist = Plist::parse_xml %x{/usr/sbin/system_profiler -xml SPSoftwareDataType} - os_stuff = top_level_plist[0]['_items'][0] - os_stuff.delete '_name' - os_stuff - end + # SPSoftwareDataType + def self.os_overview + top_level_plist = Plist::parse_xml %x{/usr/sbin/system_profiler -xml SPSoftwareDataType} + os_stuff = top_level_plist[0]['_items'][0] + os_stuff.delete '_name' + os_stuff + end - def self.sw_vers - ver = Hash.new - [ "productName", "productVersion", "buildVersion" ].each do |option| - ver["macosx_#{option}"] = %x{sw_vers -#{option}}.strip + def self.sw_vers + ver = Hash.new + [ "productName", "productVersion", "buildVersion" ].each do |option| + ver["macosx_#{option}"] = %x{sw_vers -#{option}}.strip + end + ver end - ver - end end - diff --git a/lib/facter/util/manufacturer.rb b/lib/facter/util/manufacturer.rb index 657901dd77..ff12e7e3f3 100644 --- a/lib/facter/util/manufacturer.rb +++ b/lib/facter/util/manufacturer.rb @@ -42,6 +42,5 @@ def self.dmi_find_system_info(name) end end end + end end -end - diff --git a/lib/facter/util/netmask.rb b/lib/facter/util/netmask.rb index 71696ad40d..dcbb79547c 100644 --- a/lib/facter/util/netmask.rb +++ b/lib/facter/util/netmask.rb @@ -1,36 +1,34 @@ module Facter::NetMask - -def self.get_netmask + def self.get_netmask netmask = nil; ipregex = %r{(\d{1,3}\.){3}\d{1,3}} ops = nil case Facter.value(:kernel) - when 'Linux' - ops = { - :ifconfig => '/sbin/ifconfig', - :regex => %r{\s+ inet\saddr: #{Facter.ipaddress} .*? Mask: (#{ipregex})}x, - :munge => nil, - } - when 'SunOS' - ops = { - :ifconfig => '/usr/sbin/ifconfig -a', - :regex => %r{\s+ inet\s+? #{Facter.ipaddress} \+? mask (\w{8})}x, - :munge => Proc.new { |mask| mask.scan(/../).collect do |byte| byte.to_i(16) end.join('.') } - } + when 'Linux' + ops = { + :ifconfig => '/sbin/ifconfig', + :regex => %r{\s+ inet\saddr: #{Facter.ipaddress} .*? Mask: (#{ipregex})}x, + :munge => nil, + } + when 'SunOS' + ops = { + :ifconfig => '/usr/sbin/ifconfig -a', + :regex => %r{\s+ inet\s+? #{Facter.ipaddress} \+? mask (\w{8})}x, + :munge => Proc.new { |mask| mask.scan(/../).collect do |byte| byte.to_i(16) end.join('.') } + } end %x{#{ops[:ifconfig]}}.split(/\n/).collect do |line| - matches = line.match(ops[:regex]) - if !matches.nil? - if ops[:munge].nil? - netmask = matches[1] - else - netmask = ops[:munge].call(matches[1]) - end + matches = line.match(ops[:regex]) + if !matches.nil? + if ops[:munge].nil? + netmask = matches[1] + else + netmask = ops[:munge].call(matches[1]) end + end end netmask -end - + end end diff --git a/lib/facter/util/plist.rb b/lib/facter/util/plist.rb index 32e9e2bf7f..54f5248fba 100644 --- a/lib/facter/util/plist.rb +++ b/lib/facter/util/plist.rb @@ -18,7 +18,7 @@ require 'facter/util/plist/parser' module Plist - VERSION = '3.0.0' + VERSION = '3.0.0' end # $Id: plist.rb 1781 2006-10-16 01:01:35Z luke $ diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index db8dc33465..fdb340faf7 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -1,76 +1,75 @@ Facter.add("virtual") do - confine :kernel => %w{Linux FreeBSD OpenBSD SunOS} - - result = "physical" - - setcode do + confine :kernel => %w{Linux FreeBSD OpenBSD SunOS} - if FileTest.exists?("/proc/user_beancounters") - # openvz. can be hardware node or virtual environment - # read the init process' status file, it has laxer permissions - # than /proc/user_beancounters (so this won't fail as non-root) - txt = File.read("/proc/1/status") - if txt =~ /^envID:[[:blank:]]+0$/mi - result = "openvzhn" - else - result = "openvzve" - end - end + result = "physical" - if FileTest.exists?("/proc/xen/capabilities") && FileTest.readable?("/proc/xen/capabilities") - txt = File.read("/proc/xen/capabilities") - if txt =~ /control_d/i - result = "xen0" - else - result = "xenu" - end - end + setcode do + if FileTest.exists?("/proc/user_beancounters") + # openvz. can be hardware node or virtual environment + # read the init process' status file, it has laxer permissions + # than /proc/user_beancounters (so this won't fail as non-root) + txt = File.read("/proc/1/status") + if txt =~ /^envID:[[:blank:]]+0$/mi + result = "openvzhn" + else + result = "openvzve" + end + end - if result == "physical" - path = %x{which lspci 2> /dev/null}.chomp - if path !~ /no lspci/ - output = %x{#{path}} - output.each {|p| - # --- look for the vmware video card to determine if it is virtual => vmware. - # --- 00:0f.0 VGA compatible controller: VMware Inc [VMware SVGA II] PCI Display Adapter - result = "vmware" if p =~ /VM[wW]are/ - } - else - path = %x{which dmidecode 2> /dev/null}.chomp - if path !~ /no dmidecode/ - output = %x{#{path}} - output.each {|pd| - result = "vmware" if pd =~ /VMware|Parallels/ - } - else - path = %x{which prtdiag 2> /dev/null}.chomp - if path !~ /no prtdiag/ - output = %x{#{path}} - output.each {|pd| - result = "vmware" if pd =~ /VMware|Parallels/ - } - end - end - end - end + if FileTest.exists?("/proc/xen/capabilities") && FileTest.readable?("/proc/xen/capabilities") + txt = File.read("/proc/xen/capabilities") + if txt =~ /control_d/i + result = "xen0" + else + result = "xenu" + end + end - # VMware server 1.0.3 rpm places vmware-vmx in this place, other versions or platforms may not. - if FileTest.exists?("/usr/lib/vmware/bin/vmware-vmx") - result = "vmware_server" - end + if result == "physical" + path = %x{which lspci 2> /dev/null}.chomp + if path !~ /no lspci/ + output = %x{#{path}} + output.each do |p| + # --- look for the vmware video card to determine if it is virtual => vmware. + # --- 00:0f.0 VGA compatible controller: VMware Inc [VMware SVGA II] PCI Display Adapter + result = "vmware" if p =~ /VM[wW]are/ + end + else + path = %x{which dmidecode 2> /dev/null}.chomp + if path !~ /no dmidecode/ + output = %x{#{path}} + output.each do |pd| + result = "vmware" if pd =~ /VMware|Parallels/ + end + else + path = %x{which prtdiag 2> /dev/null}.chomp + if path !~ /no prtdiag/ + output = %x{#{path}} + output.each do |pd| + result = "vmware" if pd =~ /VMware|Parallels/ + end + end + end + end + end - mountexists = system "which mount > /dev/null 2>&1" - if $?.exitstatus == 0 - output = %x{mount} - output.each {|p| - result = "vserver" if p =~ /\/dev\/hdv1/ - } - end + # VMware server 1.0.3 rpm places vmware-vmx in this place, other versions or platforms may not. + if FileTest.exists?("/usr/lib/vmware/bin/vmware-vmx") + result = "vmware_server" + end - if FileTest.directory?('/proc/virtual') - result = "vserver_host" - end + mountexists = system "which mount > /dev/null 2>&1" + if $?.exitstatus == 0 + output = %x{mount} + output.each do |p| + result = "vserver" if p =~ /\/dev\/hdv1/ + end + end - result - end + if FileTest.directory?('/proc/virtual') + result = "vserver_host" + end + + result + end end diff --git a/spec/unit/facter.rb b/spec/unit/facter.rb index 05d99c8871..bdef726dda 100755 --- a/spec/unit/facter.rb +++ b/spec/unit/facter.rb @@ -3,7 +3,7 @@ require File.dirname(__FILE__) + '/../spec_helper' describe Facter do - + it "should have a version" do Facter.version.should =~ /^[0-9]+(\.[0-9]+)*$/ end diff --git a/spec/unit/util/collection.rb b/spec/unit/util/collection.rb index 7585a52547..7baef9687c 100755 --- a/spec/unit/util/collection.rb +++ b/spec/unit/util/collection.rb @@ -114,7 +114,7 @@ it "should be case-insensitive" do @coll.fact("yayness").should equal(@fact) end - + it "should treat strings and symbols equivalently" do @coll.fact(:yayness).should equal(@fact) end @@ -156,7 +156,7 @@ it "should be case-insensitive" do @coll.value("yayness").should_not be_nil end - + it "should treat strings and symbols equivalently" do @coll.value(:yayness).should_not be_nil end diff --git a/spec/unit/util/ip.rb b/spec/unit/util/ip.rb index e27531e405..059bdd13d1 100644 --- a/spec/unit/util/ip.rb +++ b/spec/unit/util/ip.rb @@ -5,7 +5,6 @@ require 'facter/util/ip' describe Facter::IPAddress do - it "should return a list of interfaces" do Facter::IPAddress.should respond_to(:get_interfaces) end @@ -23,7 +22,7 @@ end it "should return a value for a specific interface" do - Facter::IPAddress.should respond_to(:get_interface_value) + Facter::IPAddress.should respond_to(:get_interface_value) end it "should return a human readable netmask on Solaris" do @@ -35,6 +34,4 @@ Facter::IPAddress.get_interface_value("e1000g0", "netmask").should == "255.255.255.0" end - end - diff --git a/spec/unit/util/loader.rb b/spec/unit/util/loader.rb index b9b4306301..945d3af09f 100755 --- a/spec/unit/util/loader.rb +++ b/spec/unit/util/loader.rb @@ -83,7 +83,7 @@ def with_env(values) @loader.load(:testing) end end - + it "should load any files in the search path with names matching the fact name" do @loader.expects(:search_path).returns %w{/one/dir /two/dir} FileTest.stubs(:exist?).returns false From aa56886b1143d49173f4878a41107f55fece529c Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Sun, 4 Jan 2009 17:29:59 -0600 Subject: [PATCH 0297/3753] Refactoring the IP support, and fixing #1846. I've made the IPMess stuff a lot less messy, and refactored a lot of the util/ip module, including naming it more sensibly. The biggest changes are that I moved the big case statement into a constant and then used a bit of dispatch-style logic to use it, and I eliminated a bunch of duplicate code in the ipmess.rb file. Added some test data for FreeBSD and fixed a bug in my map logic pointed out by Paul Nasrat. I've also fixed #1846, in that the interface list now s/:/_/g. Signed-off-by: Luke Kanies --- lib/facter/ipmess.rb | 39 ++++------- lib/facter/util/ip.rb | 78 ++++++++++++++-------- spec/unit/data/6.0-STABLE_FreeBSD_ifconfig | 12 ++++ spec/unit/ipmess.rb | 19 ++++++ spec/unit/util/ip.rb | 48 ++++++++++--- 5 files changed, 133 insertions(+), 63 deletions(-) create mode 100644 spec/unit/data/6.0-STABLE_FreeBSD_ifconfig create mode 100644 spec/unit/ipmess.rb diff --git a/lib/facter/ipmess.rb b/lib/facter/ipmess.rb index c61b1ec448..2887b64d04 100644 --- a/lib/facter/ipmess.rb +++ b/lib/facter/ipmess.rb @@ -7,39 +7,26 @@ require 'facter/util/ip' +# Note that most of this only works on a fixed list of platforms; notably, Darwin +# is missing. + Facter.add(:interfaces) do - confine :kernel => [ :sunos, :freebsd, :openbsd, :netbsd, :linux ] + confine :kernel => Facter::Util::IP.supported_platforms setcode do - Facter::IPAddress.get_interfaces.join(",") + Facter::Util::IP.get_interfaces.collect { |iface| Facter::Util::IP.alphafy(iface) }.join(",") end end -case Facter.value(:kernel) -when 'SunOS', 'Linux', 'OpenBSD', 'NetBSD', 'FreeBSD' - Facter::IPAddress.get_interfaces.each do |interface| - mi = interface.gsub(/[:.]/, '_') - - Facter.add("ipaddress_" + mi) do - confine :kernel => [ :sunos, :freebsd, :openbsd, :netbsd, :linux ] - setcode do - label = 'ipaddress' - Facter::IPAddress.get_interface_value(interface, label) - end - end - - Facter.add("macaddress_" + mi) do - confine :kernel => [ :sunos, :freebsd, :openbsd, :netbsd, :linux ] - setcode do - label = 'macaddress' - Facter::IPAddress.get_interface_value(interface, label) - end - end +Facter::Util::IP.get_interfaces.each do |interface| + mi = Facter::Util::IP.alphafy(interface) - Facter.add("netmask_" + mi) do - confine :kernel => [ :sunos, :freebsd, :openbsd, :netbsd, :linux ] + # Make a fact for each detail of each interface. Yay. + # There's no point in confining these facts, since we wouldn't be able to create + # them if we weren't running on a supported platform. + %w{ipaddress macaddress netmask}.each do |label| + Facter.add(label + "_" + mi) do setcode do - label = 'netmask' - Facter::IPAddress.get_interface_value(interface, label) + Facter::Util::IP.get_interface_value(interface, label) end end end diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index 3dbfcda68a..598b08435c 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -1,11 +1,48 @@ # A base module for collecting IP-related # information from all kinds of platforms. -module Facter::IPAddress - def self.get_interfaces +module Facter::Util::IP + # A map of all the different regexes that work for + # a given platform or set of platforms. + REGEX_MAP = { + :linux => { + :ipaddress => /inet addr:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, + :macaddress => /(?:ether|HWaddr)\s+(\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/, + :netmask => /Mask:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + }, + :bsd => { + :aliases => [:openbsd, :netbsd, :freebsd], + :ipaddress => /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, + :macaddress => /(?:ether|lladdr)\s+(\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/, + :netmask => /netmask\s+(\w{10})/ + }, + :sunos => { + :addr => /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, + :macaddress => /(?:ether|lladdr)\s+(\w?\w:\w?\w:\w?\w:\w?\w:\w?\w:\w?\w)/, + :netmask => /netmask\s+(\w{8})/ + } + } + + # Convert an interface name into purely alpha characters. + def self.alphafy(interface) + interface.gsub(/[:.]/, '_') + end + + def self.supported_platforms + REGEX_MAP.inject([]) do |result, tmp| + key, map = tmp + if map[:aliases] + result += map[:aliases] + else + result << key + end + result + end + end + def self.get_interfaces int = nil - output = Facter::IPAddress.get_all_interface_output() + output = Facter::Util::IP.get_all_interface_output() # We get lots of warnings on platforms that don't get an output # made. @@ -60,33 +97,16 @@ def self.get_bonding_master(interface) def self.get_interface_value(interface, label) tmp1 = [] - # LAK:NOTE This is pretty ugly - two case statements being used for most of the - # logic. These should be pulled into a small dispatch table. We don't have tests for this code, - # though, so it's not exactly trivial to do so. - case Facter.value(:kernel) - when 'Linux' - addr = /inet addr:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ - mac = /(?:ether|HWaddr)\s+(\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/ - mask = /Mask:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ - when 'OpenBSD', 'NetBSD', 'FreeBSD' - addr = /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ - mac = /(?:ether|lladdr)\s+(\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ - mask = /netmask\s+(\w{10})/ - when 'SunOS' - addr = /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ - mac = /(?:ether|lladdr)\s+(\w?\w:\w?\w:\w?\w:\w?\w:\w?\w:\w?\w)/ - mask = /netmask\s+(\w{8})/ - end + kernel = Facter.value(:kernel).downcase.to_sym - case label - when 'ipaddress' - regex = addr - when 'macaddress' - regex = mac - when 'netmask' - regex = mask + # If it's not directly in the map or aliased in the map, then we don't know how to deal with it. + unless map = REGEX_MAP[kernel] || REGEX_MAP.values.find { |tmp| tmp[:aliases] and tmp[:aliases].include?(kernel) } + return [] end + # Pull the correct regex out of the map. + regex = map[label.to_sym] + # Linux changes the MAC address reported via ifconfig when an ethernet interface # becomes a slave of a bonding device to the master MAC address. # We have to dig a bit to get the original/real MAC address of the interface. @@ -99,7 +119,7 @@ def self.get_interface_value(interface, label) output_int = get_single_interface_output(interface) if interface != "lo" && interface != "lo0" - output_int.each { |s| + output_int.each do |s| if s =~ regex value = $1 if label == 'netmask' && Facter.value(:kernel) == "SunOS" @@ -107,7 +127,7 @@ def self.get_interface_value(interface, label) end tmp1.push(value) end - } + end end if tmp1 diff --git a/spec/unit/data/6.0-STABLE_FreeBSD_ifconfig b/spec/unit/data/6.0-STABLE_FreeBSD_ifconfig new file mode 100644 index 0000000000..3339a45588 --- /dev/null +++ b/spec/unit/data/6.0-STABLE_FreeBSD_ifconfig @@ -0,0 +1,12 @@ +fxp0: flags=8843 mtu 1500 + options=b + inet x.x.58.26 netmask 0xfffffff8 broadcast x.x.58.31 + inet x.x.58.27 netmask 0xffffffff broadcast x.x.58.27 + inet x.x.58.28 netmask 0xffffffff broadcast x.x.58.28 + inet x.x.58.29 netmask 0xffffffff broadcast x.x.58.29 + inet x.x.58.30 netmask 0xffffffff broadcast x.x.58.30 + ether 00:0e:0c:68:67:7c + media: Ethernet autoselect (100baseTX ) + status: active +lo0: flags=8049 mtu 16384 + inet 127.0.0.1 netmask 0xff000000 \ No newline at end of file diff --git a/spec/unit/ipmess.rb b/spec/unit/ipmess.rb new file mode 100644 index 0000000000..a67216c13c --- /dev/null +++ b/spec/unit/ipmess.rb @@ -0,0 +1,19 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../spec_helper' + +require 'facter' + +describe "Messy IP facts" do + before do + Facter.loadfacts + end + + it "should replace the ':' in an interface list with '_'" do + # So we look supported + Facter.fact(:kernel).stubs(:value).returns("SunOS") + + Facter::Util::IP.expects(:get_interfaces).returns %w{eth0:1 eth1:2} + Facter.fact(:interfaces).value.should == %{eth0_1,eth1_2} + end +end diff --git a/spec/unit/util/ip.rb b/spec/unit/util/ip.rb index 059bdd13d1..73cf37c702 100644 --- a/spec/unit/util/ip.rb +++ b/spec/unit/util/ip.rb @@ -4,34 +4,66 @@ require 'facter/util/ip' -describe Facter::IPAddress do +describe Facter::Util::IP do + [:freebsd, :linux, :netbsd, :openbsd, :sunos].each do |platform| + it "should be supported on #{platform}" do + Facter::Util::IP.supported_platforms.should be_include(platform) + end + end + it "should return a list of interfaces" do - Facter::IPAddress.should respond_to(:get_interfaces) + Facter::Util::IP.should respond_to(:get_interfaces) end it "should return an empty list of interfaces on an unknown kernel" do Facter.stubs(:value).returns("UnknownKernel") - Facter::IPAddress.get_interfaces().should == [] + Facter::Util::IP.get_interfaces().should == [] end it "should return a list with a single interface on Linux with a single interface" do sample_output_file = File.dirname(__FILE__) + '/../data/linux_ifconfig_all_with_single_interface' linux_ifconfig = File.new(sample_output_file).read() - Facter::IPAddress.stubs(:get_all_interface_output).returns(linux_ifconfig) - Facter::IPAddress.get_interfaces().should == ["eth0"] + Facter::Util::IP.stubs(:get_all_interface_output).returns(linux_ifconfig) + Facter::Util::IP.get_interfaces().should == ["eth0"] end + it "should return a value for a specific interface" do - Facter::IPAddress.should respond_to(:get_interface_value) + Facter::Util::IP.should respond_to(:get_interface_value) + end + + it "should not return interface information for unsupported platforms" do + Facter.stubs(:value).with(:kernel).returns("bleah") + Facter::Util::IP.get_interface_value("e1000g0", "netmask").should == [] + end + + it "should return interface information for directly supported platforms" do + sample_output_file = File.dirname(__FILE__) + "/../data/solaris_ifconfig_single_interface" + solaris_ifconfig_interface = File.new(sample_output_file).read() + + Facter::Util::IP.expects(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("SunOS") + + Facter::Util::IP.get_interface_value("e1000g0", "netmask").should == "255.255.255.0" + end + + it "should return interface information for platforms supported via an alias" do + sample_output_file = File.dirname(__FILE__) + "/../data/6.0-STABLE_FreeBSD_ifconfig" + ifconfig_interface = File.new(sample_output_file).read() + + Facter::Util::IP.expects(:get_single_interface_output).with("fxp0").returns(ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("FreeBSD") + + Facter::Util::IP.get_interface_value("fxp0", "macaddress").should == "00:0e:0c:68:67:7c" end it "should return a human readable netmask on Solaris" do sample_output_file = File.dirname(__FILE__) + "/../data/solaris_ifconfig_single_interface" solaris_ifconfig_interface = File.new(sample_output_file).read() - Facter::IPAddress.expects(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) + Facter::Util::IP.expects(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) Facter.stubs(:value).with(:kernel).returns("SunOS") - Facter::IPAddress.get_interface_value("e1000g0", "netmask").should == "255.255.255.0" + Facter::Util::IP.get_interface_value("e1000g0", "netmask").should == "255.255.255.0" end end From c149b49712517319e1dfeb89b137b6bc99914123 Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Thu, 15 Jan 2009 19:53:46 +0000 Subject: [PATCH 0298/3753] Fix bug #1870 and add interface fact support for darwin systems --- lib/facter/util/ip.rb | 6 +++--- spec/unit/data/Mac_OS_X_10.5.5_ifconfig | 26 +++++++++++++++++++++++++ spec/unit/util/ip.rb | 12 +++++++++++- 3 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 spec/unit/data/Mac_OS_X_10.5.5_ifconfig diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index 598b08435c..35dadb66aa 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -10,7 +10,7 @@ module Facter::Util::IP :netmask => /Mask:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ }, :bsd => { - :aliases => [:openbsd, :netbsd, :freebsd], + :aliases => [:openbsd, :netbsd, :freebsd, :darwin], :ipaddress => /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, :macaddress => /(?:ether|lladdr)\s+(\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/, :netmask => /netmask\s+(\w{10})/ @@ -55,7 +55,7 @@ def self.get_interfaces def self.get_all_interface_output case Facter.value(:kernel) - when 'Linux', 'OpenBSD', 'NetBSD', 'FreeBSD' + when 'Linux', 'OpenBSD', 'NetBSD', 'FreeBSD', 'Darwin' output = %x{/sbin/ifconfig -a} when 'SunOS' output = %x{/usr/sbin/ifconfig -a} @@ -66,7 +66,7 @@ def self.get_all_interface_output def self.get_single_interface_output(interface) output = "" case Facter.value(:kernel) - when 'Linux', 'OpenBSD', 'NetBSD', 'FreeBSD' + when 'Linux', 'OpenBSD', 'NetBSD', 'FreeBSD', 'Darwin' output = %x{/sbin/ifconfig #{interface}} when 'SunOS' output = %x{/usr/sbin/ifconfig #{interface}} diff --git a/spec/unit/data/Mac_OS_X_10.5.5_ifconfig b/spec/unit/data/Mac_OS_X_10.5.5_ifconfig new file mode 100644 index 0000000000..33f66097b7 --- /dev/null +++ b/spec/unit/data/Mac_OS_X_10.5.5_ifconfig @@ -0,0 +1,26 @@ +lo0: flags=8049 mtu 16384 + inet6 fe80::1%lo0 prefixlen 64 scopeid 0x1 + inet 127.0.0.1 netmask 0xff000000 + inet6 ::1 prefixlen 128 +gif0: flags=8010 mtu 1280 +stf0: flags=0<> mtu 1280 +en0: flags=8863 mtu 1500 + ether 00:1b:63:ae:02:66 + media: autoselect status: inactive + supported media: autoselect 10baseT/UTP 10baseT/UTP 10baseT/UTP 10baseT/UTP 100baseTX 100baseTX 100baseTX 100baseTX 1000baseT 1000baseT 1000baseT none +fw0: flags=8863 mtu 4078 + lladdr 00:1e:52:ff:fe:31:1a:80 + media: autoselect status: inactive + supported media: autoselect +en1: flags=8863 mtu 1500 + inet6 fe80::21e:52ff:fe70:d7b6%en1 prefixlen 64 scopeid 0x6 + inet 192.168.0.4 netmask 0xffffff00 broadcast 192.168.0.255 + ether 00:1e:52:70:d7:b6 + media: autoselect status: active + supported media: autoselect +vmnet8: flags=8863 mtu 1500 + inet 172.16.15.1 netmask 0xffffff00 broadcast 172.16.15.255 + ether 00:50:56:c0:00:08 +vmnet1: flags=8863 mtu 1500 + inet 192.168.89.1 netmask 0xffffff00 broadcast 192.168.89.255 + ether 00:50:56:c0:00:01 diff --git a/spec/unit/util/ip.rb b/spec/unit/util/ip.rb index 73cf37c702..af7e109d1e 100644 --- a/spec/unit/util/ip.rb +++ b/spec/unit/util/ip.rb @@ -5,7 +5,7 @@ require 'facter/util/ip' describe Facter::Util::IP do - [:freebsd, :linux, :netbsd, :openbsd, :sunos].each do |platform| + [:freebsd, :linux, :netbsd, :openbsd, :sunos, :darwin].each do |platform| it "should be supported on #{platform}" do Facter::Util::IP.supported_platforms.should be_include(platform) end @@ -57,6 +57,16 @@ Facter::Util::IP.get_interface_value("fxp0", "macaddress").should == "00:0e:0c:68:67:7c" end + it "should return interface information for OS X" do + sample_output_file = File.dirname(__FILE__) + "/../data/Mac_OS_X_10.5.5_ifconfig" + ifconfig_interface = File.new(sample_output_file).read() + + Facter::Util::IP.expects(:get_single_interface_output).with("en1").returns(ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("Darwin") + + Facter::Util::IP.get_interface_value("en1", "macaddress").should == "00:1b:63:ae:02:66" + end + it "should return a human readable netmask on Solaris" do sample_output_file = File.dirname(__FILE__) + "/../data/solaris_ifconfig_single_interface" solaris_ifconfig_interface = File.new(sample_output_file).read() From db4facecf6bd8a8e4629165aafa813652bf2d53d Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Thu, 22 Jan 2009 11:02:16 +1100 Subject: [PATCH 0299/3753] Fixed autotest on win32 --- CHANGELOG | 4 ++++ autotest/facter_rspec.rb | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index a714e8115a..91ae3d42a0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,8 @@ 1.5.3: + Fixed autotest on win32 + + Fixed #1870 - Added interface support for Darwin + Fixed #1791 - support for virtual fact on Solaris 10 Fixed #1793 - Added more Solaris 10 facts diff --git a/autotest/facter_rspec.rb b/autotest/facter_rspec.rb index 668409bad7..0ec65b06f2 100644 --- a/autotest/facter_rspec.rb +++ b/autotest/facter_rspec.rb @@ -40,6 +40,6 @@ class Autotest::FacterRspec < Autotest::Rspec def spec_commands ENV["AUTOTEST"] = "true" - ENV["PATH"].split(":").collect { |dir| File.join(dir, "spec") } + ENV["PATH"].split(File::PATH_SEPARATOR).collect { |dir| File.join(dir, "spec") } end end From 02c2912fb94edd37d6d7001e28714c11d5939f6d Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Sat, 24 Jan 2009 12:52:07 +0000 Subject: [PATCH 0300/3753] Refactor - rename ipmess to interfaces --- lib/facter/{ipmess.rb => interfaces.rb} | 2 +- spec/unit/{ipmess.rb => interfaces.rb} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename lib/facter/{ipmess.rb => interfaces.rb} (98%) rename spec/unit/{ipmess.rb => interfaces.rb} (92%) diff --git a/lib/facter/ipmess.rb b/lib/facter/interfaces.rb similarity index 98% rename from lib/facter/ipmess.rb rename to lib/facter/interfaces.rb index 2887b64d04..bf0cc6ef0b 100644 --- a/lib/facter/ipmess.rb +++ b/lib/facter/interfaces.rb @@ -1,4 +1,4 @@ -# ipmess.rb +# interfaces.rb # Try to get additional Facts about the machine's network interfaces # # Original concept Copyright (C) 2007 psychedelys diff --git a/spec/unit/ipmess.rb b/spec/unit/interfaces.rb similarity index 92% rename from spec/unit/ipmess.rb rename to spec/unit/interfaces.rb index a67216c13c..49d5d1f6fa 100644 --- a/spec/unit/ipmess.rb +++ b/spec/unit/interfaces.rb @@ -4,7 +4,7 @@ require 'facter' -describe "Messy IP facts" do +describe "Per Interface IP facts" do before do Facter.loadfacts end From c2de35f1329dbaf6c843ff0a4967955ce989f2f0 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Mon, 26 Jan 2009 14:39:35 +1100 Subject: [PATCH 0301/3753] Added uptime facts --- CHANGELOG | 2 ++ lib/facter/uptime.rb | 19 +++++++++++++++++++ lib/facter/util/uptime.rb | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+) create mode 100644 lib/facter/uptime.rb create mode 100644 lib/facter/util/uptime.rb diff --git a/CHANGELOG b/CHANGELOG index 91ae3d42a0..4bd94e9ca3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ 1.5.3: + Added uptime facts + Fixed autotest on win32 Fixed #1870 - Added interface support for Darwin diff --git a/lib/facter/uptime.rb b/lib/facter/uptime.rb new file mode 100644 index 0000000000..0ccd84480b --- /dev/null +++ b/lib/facter/uptime.rb @@ -0,0 +1,19 @@ +require 'facter/util/uptime' + +Facter.add(:uptime) do + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE SLES Debian Ubuntu Gentoo AIX} + setcode do + Facter::Util::Uptime.get_uptime_simple + end +end + +uptime = Facter::Util::Uptime.get_uptime + +%w{days hours seconds}.each do |label| + Facter.add("uptime_" + label) do + confine :kernel => 'Linux' + setcode do + Facter::Util::Uptime.get_uptime_period(uptime, label) + end + end +end diff --git a/lib/facter/util/uptime.rb b/lib/facter/util/uptime.rb new file mode 100644 index 0000000000..7c53b2cb04 --- /dev/null +++ b/lib/facter/util/uptime.rb @@ -0,0 +1,32 @@ +# A module to gather uptime facts +# +module Facter::Util::Uptime + + def self.get_uptime_simple + time = Facter::Util::Resolution.exec('uptime') + if time =~ /up\s*(\d+\s\w+)/ + $1 + else + "unknown" + end + end + + def self.get_uptime + uptime, idletime = File.open("/proc/uptime").gets.split(" ") + uptime_seconds = uptime.to_i + end + + def self.get_uptime_period(seconds, label) + + case label + when 'days' + value = seconds / 86400 + when 'hours' + value = seconds / 3600 + when 'seconds' + seconds + end + + end + +end From da52e30971ebb6935c64777e4a91cb2cbb066db9 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Tue, 27 Jan 2009 10:23:26 +1100 Subject: [PATCH 0302/3753] Fixed #1870 - Format all subnet masks as human-readable --- CHANGELOG | 2 ++ lib/facter/util/ip.rb | 9 +++++++-- spec/unit/util/ip.rb | 17 +++++++++++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 4bd94e9ca3..cb6d10b0e5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ 1.5.3: + Fixed #1870 - Format all subnet masks as human-readable + Added uptime facts Fixed autotest on win32 diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index 35dadb66aa..a7f0a854b5 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -13,7 +13,7 @@ module Facter::Util::IP :aliases => [:openbsd, :netbsd, :freebsd, :darwin], :ipaddress => /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, :macaddress => /(?:ether|lladdr)\s+(\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/, - :netmask => /netmask\s+(\w{10})/ + :netmask => /netmask\s+0x(\w{8})/ }, :sunos => { :addr => /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, @@ -27,6 +27,11 @@ def self.alphafy(interface) interface.gsub(/[:.]/, '_') end + def self.convert_from_hex?(kernel) + kernels_to_convert = [:sunos, :openbsd, :netbsd, :freebsd, :darwin] + kernels_to_convert.include?(kernel) + end + def self.supported_platforms REGEX_MAP.inject([]) do |result, tmp| key, map = tmp @@ -122,7 +127,7 @@ def self.get_interface_value(interface, label) output_int.each do |s| if s =~ regex value = $1 - if label == 'netmask' && Facter.value(:kernel) == "SunOS" + if label == 'netmask' && convert_from_hex?(kernel) value = value.scan(/../).collect do |byte| byte.to_i(16) end.join('.') end tmp1.push(value) diff --git a/spec/unit/util/ip.rb b/spec/unit/util/ip.rb index af7e109d1e..4f6e2c0c18 100644 --- a/spec/unit/util/ip.rb +++ b/spec/unit/util/ip.rb @@ -76,4 +76,21 @@ Facter::Util::IP.get_interface_value("e1000g0", "netmask").should == "255.255.255.0" end + + it "should return a human readable netmask on Darwin" do + sample_output_file = File.dirname(__FILE__) + "/../data/darwin_ifconfig_single_interface" + + darwin_ifconfig_interface = File.new(sample_output_file).read() + + Facter::Util::IP.expects(:get_single_interface_output).with("en1").returns(darwin_ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("Darwin") + + Facter::Util::IP.get_interface_value("en1", "netmask").should == "255.255.255.0" + end + + [:freebsd, :netbsd, :openbsd, :sunos, :darwin].each do |platform| + it "should require conversion from hex on #{platform}" do + Facter::Util::IP.convert_from_hex?(platform).should == true + end + end end From fab9d1c63f0a3671dc5f8fa0233823a59f1bce06 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Tue, 27 Jan 2009 19:37:54 +1100 Subject: [PATCH 0303/3753] Added network fact --- CHANGELOG | 2 ++ lib/facter/interfaces.rb | 3 +-- lib/facter/network.rb | 10 ++++++++++ lib/facter/util/ip.rb | 13 +++++++++++++ spec/unit/data/darwin_ifconfig_single_interface | 6 ++++++ 5 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 lib/facter/network.rb create mode 100644 spec/unit/data/darwin_ifconfig_single_interface diff --git a/CHANGELOG b/CHANGELOG index cb6d10b0e5..4719b406d2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ 1.5.3: + Added network fact + Fixed #1870 - Format all subnet masks as human-readable Added uptime facts diff --git a/lib/facter/interfaces.rb b/lib/facter/interfaces.rb index bf0cc6ef0b..12392153ac 100644 --- a/lib/facter/interfaces.rb +++ b/lib/facter/interfaces.rb @@ -18,13 +18,12 @@ end Facter::Util::IP.get_interfaces.each do |interface| - mi = Facter::Util::IP.alphafy(interface) # Make a fact for each detail of each interface. Yay. # There's no point in confining these facts, since we wouldn't be able to create # them if we weren't running on a supported platform. %w{ipaddress macaddress netmask}.each do |label| - Facter.add(label + "_" + mi) do + Facter.add(label + "_" + Facter::Util::IP.alphafy(interface)) do setcode do Facter::Util::IP.get_interface_value(interface, label) end diff --git a/lib/facter/network.rb b/lib/facter/network.rb new file mode 100644 index 0000000000..513282a282 --- /dev/null +++ b/lib/facter/network.rb @@ -0,0 +1,10 @@ +require 'facter/util/ip' + +Facter::Util::IP.get_interfaces.each do |interface| + + Facter.add("network_" + Facter::Util::IP.alphafy(interface)) do + setcode do + Facter::Util::IP.get_network_value(interface) + end + end +end diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index a7f0a854b5..6c01033d09 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -140,4 +140,17 @@ def self.get_interface_value(interface, label) end end end + + def self.get_network_value(interface) + require 'ipaddr' + + ipaddress = get_interface_value(interface, "ipaddress") + netmask = get_interface_value(interface, "netmask") + + if ipaddress && netmask + ip = IPAddr.new(ipaddress, Socket::AF_INET) + subnet = IPAddr.new(netmask, Socket::AF_INET) + network = ip.mask(subnet.to_s) + end + end end diff --git a/spec/unit/data/darwin_ifconfig_single_interface b/spec/unit/data/darwin_ifconfig_single_interface new file mode 100644 index 0000000000..d1e8bd315a --- /dev/null +++ b/spec/unit/data/darwin_ifconfig_single_interface @@ -0,0 +1,6 @@ +en1: flags=8863 mtu 1500 + inet6 fe80::21c:b3ff:febe:81c9%en1 prefixlen 64 scopeid 0x6 + inet 10.0.0.101 netmask 0xffffff00 broadcast 10.0.0.255 + ether 00:1c:b3:be:81:c9 + media: autoselect status: active + supported media: autoselect From 0bcdb71f88cb66140b344d38fd94ce2f161dcd2c Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Tue, 27 Jan 2009 17:58:47 -0600 Subject: [PATCH 0304/3753] Fixing #1854 - Adding ArchLinux support Used patch by miah. Signed-off-by: Luke Kanies --- lib/facter/operatingsystem.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index 51e6248a34..5bff78d778 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -18,6 +18,8 @@ "Mandriva" elsif FileTest.exists?("/etc/mandrake-release") "Mandrake" + elsif FileTest.exists?("/etc/arch-release") + "Archlinux" elsif FileTest.exists?("/etc/redhat-release") txt = File.read("/etc/redhat-release") if txt =~ /centos/i From 5f202c9aaa24581b235d3d9613b3e6ec6ac91d34 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Tue, 27 Jan 2009 18:00:50 -0600 Subject: [PATCH 0305/3753] Fixed #1867 - Fixed OpenSuSE detection Applied patch by miah. Signed-off-by: Luke Kanies --- lib/facter/operatingsystem.rb | 2 ++ lib/facter/operatingsystemrelease.rb | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index 5bff78d778..258fcde489 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -31,6 +31,8 @@ txt = File.read("/etc/SuSE-release") if txt =~ /^SUSE LINUX Enterprise Server/i "SLES" + elsif txt =~ /^openSUSE/i + "OpenSuSE" else "SuSE" end diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index f88278a134..bbf530aefb 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -56,13 +56,15 @@ end Facter.add(:operatingsystemrelease) do - confine :operatingsystem => %w{SLES} + confine :operatingsystem => %w{SLES OpenSuSE} setcode do releasefile = Facter::Util::Resolution.exec('cat /etc/SuSE-release') if releasefile =~ /^VERSION\s*=\s*(\d+)/ releasemajor = $1 if releasefile =~ /^PATCHLEVEL\s*=\s*(\d+)/ releaseminor = $1 + elsif releasefile =~ /^VERSION\s=.*.(\d+)/ + releaseminor = $1 else releaseminor = "0" end From 1f1fa9bcecfb6d137219819c9aeefdc41f3f9dce Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Tue, 27 Jan 2009 17:50:21 -0600 Subject: [PATCH 0306/3753] Fixing #1838 - profiler failures don't throw exceptions Also added tests to the macosx code; it's much cleaner and actually tested now. Signed-off-by: Luke Kanies --- lib/facter/util/macosx.rb | 34 +++++++++++++++++++--------- spec/unit/util/macosx.rb | 47 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 10 deletions(-) create mode 100755 spec/unit/util/macosx.rb diff --git a/lib/facter/util/macosx.rb b/lib/facter/util/macosx.rb index 2704a7c3ba..d32d257196 100644 --- a/lib/facter/util/macosx.rb +++ b/lib/facter/util/macosx.rb @@ -19,24 +19,38 @@ module Facter::Macosx require 'thread' require 'facter/util/plist' + require 'facter/util/resolution' # JJM I'd really like to dynamically generate these methods # by looking at the _name key of the _items dict for each _dataType + def self.profiler_xml(data_field) + Facter::Util::Resolution.exec("/usr/sbin/system_profiler -xml #{data_field}") + end + + def self.intern_xml(xml) + return nil unless xml + Plist::parse_xml(xml) + end + + # Return an xml result, modified as we need it. + def self.profiler_data(data_field) + begin + return nil unless parsed_xml = intern_xml(profiler_xml(data_field)) + return nil unless data = parsed_xml[0]['_items'][0] + data.delete '_name' + data + rescue + return nil + end + end + def self.hardware_overview - # JJM Perhaps we should cache the XML data in a "class" level object. - top_level_plist = Plist::parse_xml %x{/usr/sbin/system_profiler -xml SPHardwareDataType} - system_hardware = top_level_plist[0]['_items'][0] - system_hardware.delete '_name' - system_hardware + profiler_data("SPHardwareDataType") end - # SPSoftwareDataType def self.os_overview - top_level_plist = Plist::parse_xml %x{/usr/sbin/system_profiler -xml SPSoftwareDataType} - os_stuff = top_level_plist[0]['_items'][0] - os_stuff.delete '_name' - os_stuff + profiler_data("SPSoftwareDataType") end def self.sw_vers diff --git a/spec/unit/util/macosx.rb b/spec/unit/util/macosx.rb new file mode 100755 index 0000000000..01e862ddfc --- /dev/null +++ b/spec/unit/util/macosx.rb @@ -0,0 +1,47 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'facter/util/macosx' + +describe Facter::Macosx do + it "should be able to retrieve profiler data as xml for a given data field" do + Facter::Util::Resolution.expects(:exec).with("/usr/sbin/system_profiler -xml foo").returns "yay" + Facter::Macosx.profiler_xml("foo").should == "yay" + end + + it "should use PList to convert xml to data structures" do + Plist.expects(:parse_xml).with("foo").returns "bar" + + Facter::Macosx.intern_xml("foo").should == "bar" + end + + describe "when collecting profiler data" do + it "should return the first value in the '_items' hash in the first value of the results of the system_profiler data, with the '_name' field removed, if the profiler returns data" do + @result = [ + '_items' => [ + {'_name' => "foo", "yay" => "bar"} + ] + ] + Facter::Macosx.expects(:profiler_xml).with("foo").returns "eh" + Facter::Macosx.expects(:intern_xml).with("eh").returns @result + Facter::Macosx.profiler_data("foo").should == {"yay" => "bar"} + end + + it "should return nil if an exception is thrown during parsing of xml" do + Facter::Macosx.expects(:profiler_xml).with("foo").returns "eh" + Facter::Macosx.expects(:intern_xml).with("eh").raises "boo!" + Facter::Macosx.profiler_data("foo").should be_nil + end + end + + it "should return the profiler data for 'SPHardwareDataType' as the hardware information" do + Facter::Macosx.expects(:profiler_data).with("SPHardwareDataType").returns "eh" + Facter::Macosx.hardware_overview.should == "eh" + end + + it "should return the profiler data for 'SPSoftwareDataType' as the os information" do + Facter::Macosx.expects(:profiler_data).with("SPSoftwareDataType").returns "eh" + Facter::Macosx.os_overview.should == "eh" + end +end From a82f476382e0a4bad90044883922a069a87f6beb Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Tue, 27 Jan 2009 17:51:39 -0600 Subject: [PATCH 0307/3753] Renaming Facter::Macosx to Facter::Util::Macosx This name change just reflects the file location. Signed-off-by: Luke Kanies --- lib/facter/util/macosx.rb | 2 +- spec/unit/util/macosx.rb | 26 +++++++++++++------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/facter/util/macosx.rb b/lib/facter/util/macosx.rb index d32d257196..6e7986ee7f 100644 --- a/lib/facter/util/macosx.rb +++ b/lib/facter/util/macosx.rb @@ -16,7 +16,7 @@ ## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA ## -module Facter::Macosx +module Facter::Util::Macosx require 'thread' require 'facter/util/plist' require 'facter/util/resolution' diff --git a/spec/unit/util/macosx.rb b/spec/unit/util/macosx.rb index 01e862ddfc..a54301318e 100755 --- a/spec/unit/util/macosx.rb +++ b/spec/unit/util/macosx.rb @@ -4,16 +4,16 @@ require 'facter/util/macosx' -describe Facter::Macosx do +describe Facter::Util::Macosx do it "should be able to retrieve profiler data as xml for a given data field" do Facter::Util::Resolution.expects(:exec).with("/usr/sbin/system_profiler -xml foo").returns "yay" - Facter::Macosx.profiler_xml("foo").should == "yay" + Facter::Util::Macosx.profiler_xml("foo").should == "yay" end it "should use PList to convert xml to data structures" do Plist.expects(:parse_xml).with("foo").returns "bar" - Facter::Macosx.intern_xml("foo").should == "bar" + Facter::Util::Macosx.intern_xml("foo").should == "bar" end describe "when collecting profiler data" do @@ -23,25 +23,25 @@ {'_name' => "foo", "yay" => "bar"} ] ] - Facter::Macosx.expects(:profiler_xml).with("foo").returns "eh" - Facter::Macosx.expects(:intern_xml).with("eh").returns @result - Facter::Macosx.profiler_data("foo").should == {"yay" => "bar"} + Facter::Util::Macosx.expects(:profiler_xml).with("foo").returns "eh" + Facter::Util::Macosx.expects(:intern_xml).with("eh").returns @result + Facter::Util::Macosx.profiler_data("foo").should == {"yay" => "bar"} end it "should return nil if an exception is thrown during parsing of xml" do - Facter::Macosx.expects(:profiler_xml).with("foo").returns "eh" - Facter::Macosx.expects(:intern_xml).with("eh").raises "boo!" - Facter::Macosx.profiler_data("foo").should be_nil + Facter::Util::Macosx.expects(:profiler_xml).with("foo").returns "eh" + Facter::Util::Macosx.expects(:intern_xml).with("eh").raises "boo!" + Facter::Util::Macosx.profiler_data("foo").should be_nil end end it "should return the profiler data for 'SPHardwareDataType' as the hardware information" do - Facter::Macosx.expects(:profiler_data).with("SPHardwareDataType").returns "eh" - Facter::Macosx.hardware_overview.should == "eh" + Facter::Util::Macosx.expects(:profiler_data).with("SPHardwareDataType").returns "eh" + Facter::Util::Macosx.hardware_overview.should == "eh" end it "should return the profiler data for 'SPSoftwareDataType' as the os information" do - Facter::Macosx.expects(:profiler_data).with("SPSoftwareDataType").returns "eh" - Facter::Macosx.os_overview.should == "eh" + Facter::Util::Macosx.expects(:profiler_data).with("SPSoftwareDataType").returns "eh" + Facter::Util::Macosx.os_overview.should == "eh" end end From a194c91df1c95a6e2cae33ad80aa6027aa9e68b7 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Tue, 27 Jan 2009 17:53:38 -0600 Subject: [PATCH 0308/3753] Adding mail_patches rake task Signed-off-by: Luke Kanies --- Rakefile | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/Rakefile b/Rakefile index 0f6d2b8629..c53f8f6772 100644 --- a/Rakefile +++ b/Rakefile @@ -63,3 +63,38 @@ namespace :ci do end +desc "Send patch information to the puppet-dev list" +task :mail_patches do + if Dir.glob("00*.patch").length > 0 + raise "Patches already exist matching '00*.patch'; clean up first" + end + + unless %x{git status} =~ /On branch (.+)/ + raise "Could not get branch from 'git status'" + end + branch = $1 + + unless branch =~ %r{^([^\/]+)/([^\/]+)/([^\/]+)$} + raise "Branch name does not follow // model; cannot autodetect parent branch" + end + + type, parent, name = $1, $2, $3 + + # Create all of the patches + sh "git format-patch -C -M -s -n #{parent}..HEAD" + + # And then mail them out. + + # If we've got more than one patch, add --compose + if Dir.glob("00*.patch").length > 1 + compose = "--compose" + else + compose = "" + end + + # Now send the mail. + sh "git send-email #{compose} --no-chain-reply-to --no-signed-off-by-cc --suppress-from --no-thread --to puppet-dev@googlegroups.com 00*.patch" + + # Finally, clean up the patches + sh "rm 00*.patch" +end From 23289bd8c30455965fcf678f2f2410ed5ea9027b Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Wed, 28 Jan 2009 12:18:09 +1100 Subject: [PATCH 0309/3753] Fixed uptime refactor issues on non-Linux platforms Signed-off-by: James Turnbull --- lib/facter/uptime.rb | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/facter/uptime.rb b/lib/facter/uptime.rb index 0ccd84480b..808bcec269 100644 --- a/lib/facter/uptime.rb +++ b/lib/facter/uptime.rb @@ -7,13 +7,14 @@ end end -uptime = Facter::Util::Uptime.get_uptime +if FileTest.exists?("/proc/uptime") + uptime = Facter::Util::Uptime.get_uptime -%w{days hours seconds}.each do |label| - Facter.add("uptime_" + label) do - confine :kernel => 'Linux' - setcode do - Facter::Util::Uptime.get_uptime_period(uptime, label) - end + %w{days hours seconds}.each do |label| + Facter.add("uptime_" + label) do + setcode do + Facter::Util::Uptime.get_uptime_period(uptime, label) + end end -end + end +end From a73e803ffaf0b3d009f683b55ace4ffaac823a6e Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Tue, 27 Jan 2009 22:18:51 -0600 Subject: [PATCH 0310/3753] Fixing the usage of the macosx util module; I somehow missed renaming it here Signed-off-by: Luke Kanies --- lib/facter/macosx.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/facter/macosx.rb b/lib/facter/macosx.rb index e7573b0c29..3841c50045 100644 --- a/lib/facter/macosx.rb +++ b/lib/facter/macosx.rb @@ -25,7 +25,7 @@ require 'facter/util/macosx' if Facter.value(:kernel) == "Darwin" - Facter::Macosx.hardware_overview.each do |fact, value| + Facter::Util::Macosx.hardware_overview.each do |fact, value| Facter.add("sp_#{fact}") do confine :kernel => :darwin setcode do @@ -34,7 +34,7 @@ end end - Facter::Macosx.os_overview.each do |fact, value| + Facter::Util::Macosx.os_overview.each do |fact, value| Facter.add("sp_#{fact}") do confine :kernel => :darwin setcode do @@ -43,7 +43,7 @@ end end - Facter::Macosx.sw_vers.each do |fact, value| + Facter::Util::Macosx.sw_vers.each do |fact, value| Facter.add(fact) do confine :kernel => :darwin setcode do From b86a1fba1ed419c6a3ebbb8cc642e46d40c448bd Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Wed, 28 Jan 2009 19:46:08 +0100 Subject: [PATCH 0311/3753] Updated to version 1.5.3 --- lib/facter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter.rb b/lib/facter.rb index e764255dbb..18e43c1d6c 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -27,7 +27,7 @@ module Util; end include Comparable include Enumerable - FACTERVERSION = '1.5.2' + FACTERVERSION = '1.5.3' # = Facter # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. From d9eef194d81fc7e5212bb67b72edd5f39df9ece9 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Fri, 30 Jan 2009 21:28:39 +1100 Subject: [PATCH 0312/3753] Added timezone fact --- lib/facter/timezone.rb | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 lib/facter/timezone.rb diff --git a/lib/facter/timezone.rb b/lib/facter/timezone.rb new file mode 100644 index 0000000000..e01c5e0981 --- /dev/null +++ b/lib/facter/timezone.rb @@ -0,0 +1,5 @@ +Facter.add("timezone") do + setcode do + Time.new.zone + end +end From ccafc00614200d3af301e0dd42332056fe288da3 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Tue, 3 Feb 2009 18:53:42 +1100 Subject: [PATCH 0313/3753] Fixed #1926 - IPAddr to_s issue --- CHANGELOG | 3 +++ lib/facter/network.rb | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 4719b406d2..44070428bd 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,6 @@ +1.6.0: + Fixed #1926 - IPAddr to_s issue + 1.5.3: Added network fact diff --git a/lib/facter/network.rb b/lib/facter/network.rb index 513282a282..b7e335d8b1 100644 --- a/lib/facter/network.rb +++ b/lib/facter/network.rb @@ -4,7 +4,7 @@ Facter.add("network_" + Facter::Util::IP.alphafy(interface)) do setcode do - Facter::Util::IP.get_network_value(interface) + Facter::Util::IP.get_network_value(interface).to_s end end end From effb82f0815f19e3ec933e9e7465d4fc2ee1e7c8 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Tue, 3 Feb 2009 21:32:16 +1100 Subject: [PATCH 0314/3753] Cleaner fix for #1926 --- lib/facter/network.rb | 2 +- lib/facter/util/ip.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/facter/network.rb b/lib/facter/network.rb index b7e335d8b1..513282a282 100644 --- a/lib/facter/network.rb +++ b/lib/facter/network.rb @@ -4,7 +4,7 @@ Facter.add("network_" + Facter::Util::IP.alphafy(interface)) do setcode do - Facter::Util::IP.get_network_value(interface).to_s + Facter::Util::IP.get_network_value(interface) end end end diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index 6c01033d09..1f07f58665 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -150,7 +150,7 @@ def self.get_network_value(interface) if ipaddress && netmask ip = IPAddr.new(ipaddress, Socket::AF_INET) subnet = IPAddr.new(netmask, Socket::AF_INET) - network = ip.mask(subnet.to_s) + network = ip.mask(subnet.to_s).to_s end end end From d93ca69ddf5194438f06a770dcde943c0f0b635d Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Tue, 3 Feb 2009 22:09:17 +1100 Subject: [PATCH 0315/3753] Fixed Ubuntu operatingsystem identification --- CHANGELOG | 4 +++- lib/facter/operatingsystem.rb | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 44070428bd..bc55d3d096 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,8 @@ -1.6.0: +1.5.4: Fixed #1926 - IPAddr to_s issue + Fixed Ubuntu operatingsystem identification + 1.5.3: Added network fact diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index 258fcde489..918f119a25 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -1,3 +1,5 @@ +require 'facter/lsb' + Facter.add(:operatingsystem) do confine :kernel => :sunos setcode do "Solaris" end From 4dcd0128f8ef4096a30708abeb72d113015a7065 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Tue, 3 Feb 2009 22:20:59 +1100 Subject: [PATCH 0316/3753] Fixed generic uptime fact --- CHANGELOG | 2 ++ lib/facter/util/uptime.rb | 2 ++ 2 files changed, 4 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index bc55d3d096..e9652d3994 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,6 +3,8 @@ Fixed Ubuntu operatingsystem identification + Fixed generic uptime fact + 1.5.3: Added network fact diff --git a/lib/facter/util/uptime.rb b/lib/facter/util/uptime.rb index 7c53b2cb04..1be3b980d4 100644 --- a/lib/facter/util/uptime.rb +++ b/lib/facter/util/uptime.rb @@ -6,6 +6,8 @@ def self.get_uptime_simple time = Facter::Util::Resolution.exec('uptime') if time =~ /up\s*(\d+\s\w+)/ $1 + elsif time =~ /up\s*(\d+:\d+)/ + $1 + " hours" else "unknown" end From b85ab0a894f84b6c2211c42898b5b2f127ccb930 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Wed, 4 Feb 2009 11:33:58 +1100 Subject: [PATCH 0317/3753] Fixed #1924 - Fixed lo / lo:0 local interface matching --- CHANGELOG | 1 + lib/facter/util/ip.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index e9652d3994..b0ac1308e1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,6 +5,7 @@ Fixed generic uptime fact + Fixed #1924 - Fixed lo / lo:0 local interface matching 1.5.3: Added network fact diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index 1f07f58665..4d4d936033 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -123,7 +123,7 @@ def self.get_interface_value(interface, label) else output_int = get_single_interface_output(interface) - if interface != "lo" && interface != "lo0" + if interface != /^lo[0:]?\d?/ output_int.each do |s| if s =~ regex value = $1 From 063e4dc53ba86dc4a933462082dc30cd0987599a Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Wed, 4 Feb 2009 11:40:34 +1100 Subject: [PATCH 0318/3753] Fixed #1850 - Facter updates for Ruby 1.9 --- CHANGELOG | 2 ++ lib/facter.rb | 8 ++++---- lib/facter/architecture.rb | 4 ++-- lib/facter/kernel.rb | 2 +- lib/facter/util/fact.rb | 2 +- lib/facter/util/plist/generator.rb | 6 +++--- 6 files changed, 13 insertions(+), 11 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index b0ac1308e1..c44901c99c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ 1.5.4: + Fixed #1850 - Facter updates for Ruby 1.9 + Fixed #1926 - IPAddr to_s issue Fixed Ubuntu operatingsystem identification diff --git a/lib/facter.rb b/lib/facter.rb index 18e43c1d6c..c147673fe3 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -151,15 +151,15 @@ def self.clear def self.debugging(bit) if bit case bit - when TrueClass: @@debug = 1 - when FalseClass: @@debug = 0 - when Fixnum: + when TrueClass; @@debug = 1 + when FalseClass; @@debug = 0 + when Fixnum if bit > 0 @@debug = 1 else @@debug = 0 end - when String: + when String; if bit.downcase == 'off' @@debug = 0 else diff --git a/lib/facter/architecture.rb b/lib/facter/architecture.rb index 3665a7fb99..bc9910a330 100644 --- a/lib/facter/architecture.rb +++ b/lib/facter/architecture.rb @@ -4,9 +4,9 @@ model = Facter.value(:hardwaremodel) case model # most linuxen use "x86_64" - when 'x86_64': + when 'x86_64' Facter.value(:operatingsystem) == "Debian" ? "amd64" : model; - when /(i[3456]86|pentium)/: "i386" + when /(i[3456]86|pentium)/; "i386" else model end diff --git a/lib/facter/kernel.rb b/lib/facter/kernel.rb index 5a0eb19b09..d37260799d 100644 --- a/lib/facter/kernel.rb +++ b/lib/facter/kernel.rb @@ -2,7 +2,7 @@ setcode do require 'rbconfig' case Config::CONFIG['host_os'] - when /mswin/i then 'windows' + when /mswin/i; 'windows' else Facter::Util::Resolution.exec("uname -s") end end diff --git a/lib/facter/util/fact.rb b/lib/facter/util/fact.rb index cfdfb602c9..e78ed97d32 100644 --- a/lib/facter/util/fact.rb +++ b/lib/facter/util/fact.rb @@ -14,7 +14,7 @@ def initialize(name, options = {}) # worst we'll have one. If we add more, this should be made more efficient. options.each do |name, value| case name - when :ldapname: self.ldapname = value + when :ldapname; self.ldapname = value else raise ArgumentError, "Invalid fact option '%s'" % name end diff --git a/lib/facter/util/plist/generator.rb b/lib/facter/util/plist/generator.rb index f6aba29c38..9e44b80147 100644 --- a/lib/facter/util/plist/generator.rb +++ b/lib/facter/util/plist/generator.rb @@ -155,9 +155,9 @@ def self.wrap(contents) def self.element_type(item) return case item - when String, Symbol: 'string' - when Fixnum, Bignum, Integer: 'integer' - when Float: 'real' + when String, Symbol; 'string' + when Fixnum, Bignum, Integer; 'integer' + when Float; 'real' else raise "Don't know about this data type... something must be wrong!" end From f4bc74d1590ab219d3589dc8c2d11acd062a3d63 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Tue, 3 Feb 2009 18:40:43 -0600 Subject: [PATCH 0319/3753] Fixing #1927 - failing facts don't kill Facter Facts that raise exceptions just return nil now. Signed-off-by: Luke Kanies --- CHANGELOG | 2 ++ lib/facter/util/resolution.rb | 3 +++ spec/unit/util/resolution.rb | 6 ++++++ 3 files changed, 11 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index c44901c99c..c60317e8df 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ 1.5.4: + Fixed #1927 - failing facts don't kill Facter + Fixed #1850 - Facter updates for Ruby 1.9 Fixed #1926 - IPAddr to_s issue diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index b50c9e98bc..0dfb3b274d 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -127,6 +127,9 @@ def value # of the timeout. Thread.new { Process.waitall } return nil + rescue => details + warn "Could not retrieve %s: %s" % [self.name, details] + return nil end return nil if result == "" diff --git a/spec/unit/util/resolution.rb b/spec/unit/util/resolution.rb index 643be33f6b..35581e5e10 100755 --- a/spec/unit/util/resolution.rb +++ b/spec/unit/util/resolution.rb @@ -87,6 +87,12 @@ end describe "and the code is a block" do + it "should warn but not fail if the code fails" do + @resolve.setcode { raise "feh" } + @resolve.expects(:warn) + @resolve.value.should be_nil + end + it "should return the value returned by the block" do @resolve.setcode { "yayness" } @resolve.value.should == "yayness" From 072643736fcfd37d3c2c42d6d9841f05ff391319 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Wed, 4 Feb 2009 22:17:28 +1100 Subject: [PATCH 0320/3753] Updated README --- README | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README b/README index 4e28d9d2c1..5866b7096f 100644 --- a/README +++ b/README @@ -3,8 +3,6 @@ system. These facts are mostly strings (i.e., not numbers), and are things like the output of 'uname', public ssh and cfengine keys, the number of processors, etc. -It currently cannot collect very much information, but it is architected to be -both OS and OS version specific. +See bin/facter for an example of the interface. -See bin/facter or http://reductivelabs.com/trac/enhost for an example of the -interface. +See http://reductivelabs.com/projects/facter/ for more details. From e52f962f44e98719212d788699ebaa6eb4d61904 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Tue, 10 Feb 2009 02:14:55 +1100 Subject: [PATCH 0321/3753] Added Reductive Labs build library --- Rakefile | 2 + tasks/rake/redlabpackage.rb | 265 ++++++++++++++++++ tasks/rake/reductive.rb | 538 ++++++++++++++++++++++++++++++++++++ 3 files changed, 805 insertions(+) create mode 100644 tasks/rake/redlabpackage.rb create mode 100644 tasks/rake/reductive.rb diff --git a/Rakefile b/Rakefile index c53f8f6772..b302c0cc83 100644 --- a/Rakefile +++ b/Rakefile @@ -1,5 +1,7 @@ # Rakefile for facter +$LOAD_PATH << File.join(File.dirname(__FILE__), 'tasks') + begin require 'rake/reductive' rescue LoadError diff --git a/tasks/rake/redlabpackage.rb b/tasks/rake/redlabpackage.rb new file mode 100644 index 0000000000..2de8005b63 --- /dev/null +++ b/tasks/rake/redlabpackage.rb @@ -0,0 +1,265 @@ +#!/usr/bin/env ruby + +# A raw platform for creating packages. + +require 'rbconfig' +require 'rake' +require 'rake/tasklib' + +# The PackageTask will create the following targets: +# +# [:clobber_package] +# Delete all the package files. This target is automatically +# added to the main clobber target. +# +# [:repackage] +# Rebuild the package files from scratch, even if they are not out +# of date. +# +# ["package_dir/name-version.tgz"] +# Create a gzipped tar package (if need_tar is true). +# +# ["package_dir/name-version.tar.gz"] +# Create a gzipped tar package (if need_tar_gz is true). +# +# ["package_dir/name-version.tar.bz2"] +# Create a bzip2'd tar package (if need_tar_bz2 is true). +# +# ["package_dir/name-version.zip"] +# Create a zip package archive (if need_zip is true). +# +# Example: +# +# Rake::PackageTask.new("rake", "1.2.3") do |p| +# p.need_tar = true +# p.package_files.include("lib/**/*.rb") +# end +# +class Rake::RedLabPackageTask < Rake::TaskLib + # The different directory types we can manage. + DIRTYPES = { + :bindir => :bins, + :sbindir => :sbins, + :sitelibdir => :rubylibs + } + + # Name of the package (from the GEM Spec). + attr_accessor :name + + # Version of the package (e.g. '1.3.2'). + attr_accessor :version + + # Directory used to store the package files (default is 'pkg'). + attr_accessor :package_dir + + # The directory to which to publish packages and html and such. + attr_accessor :publishdir + + # The package-specific publishing directory + attr_accessor :pkgpublishdir + + # The Product name. Defaults to a capitalized version of the + # package name + attr_accessor :product + + # The copyright message. + attr_accessor :copyright + + # The vendor. + attr_accessor :vendor + + # The license file. Defaults to COPYING. + attr_accessor :license + + # The readme file. Defaults to README. + attr_accessor :readme + + # The description. + attr_accessor :description + + # The summary. + attr_accessor :summary + + # The directory in which to put the binaries. Defaults to the system + # default. + attr_accessor :bindir + + # The executables. + attr_accessor :bins + + # The directory in which to put the system binaries. Defaults to the + # system default. + attr_accessor :sbindir + + # The system binaries. + attr_accessor :sbins + + # The libraries. + attr_accessor :rubylibs + + # The directory in which to put Ruby libraries. Defaults to the + # system site_dir. + attr_accessor :sitelibdir + + # The URL for the package. + attr_accessor :url + + # The source for the package. + attr_accessor :source + + # Our operating system. + attr_reader :os + + # Add a required package. + def add_dependency(name, version = nil) + @requires[name] = version + end + + # Create the tasks defined by this task library. + def define + fail "Version required (or :noversion)" if @version.nil? + @version = nil if :noversion == @version + + directory pkgdest + file pkgdest => self.package_dir + + directory self.package_dir + + self.mkcopytasks + + self + end + + # Return the list of files associated with a dirname. + def files(dirname) + if @dirtypes.include?(dirname) + return self.send(@dirtypes[dirname]) + else + raise "Could not find directory type %s" % dirname + end + end + + # Create a Package Task with the given name and version. + def initialize(name=nil, version=nil) + # Theoretically, one could eventually add directory types here. + @dirtypes = DIRTYPES.dup + + @requires = {} + + @name = name + @version = version + @package_dir = 'pkg' + @product = name.capitalize + + @bindir = Config::CONFIG["bindir"] + @sbindir = Config::CONFIG["sbindir"] + @sitelibdir = Config::CONFIG["sitelibdir"] + + @license = "COPYING" + @readme = "README" + + yield self if block_given? + + define unless name.nil? + + # Make sure they've provided everything necessary. + %w{copyright vendor description}.each do |attr| + unless self.send(attr) + raise "You must provide the attribute %s" % attr + end + end + end + + # Make tasks for copying/linking all of the necessary files. + def mkcopytasks + basedir = pkgdest() + + tasks = [] + + # Iterate across all of the file locations... + @dirtypes.each do |dirname, filemethod| + tname = ("copy" + dirname.to_s).intern + + dir = self.send(dirname) + + reqs = [] + + # This is where we're putting the files. + targetdir = self.targetdir(dirname) + + # Make sure our target directories exist + directory targetdir + file targetdir => basedir + + # Get the file list and remove the leading directory. + files = self.files(dirname) or next + + reqs = [] + files.each do |sourcefile| + # The file without the basedir. This is necessary because + # files are created with the path from ".", but they often + # have 'lib' changed to 'site_ruby' or something similar. + destfile = File.join(targetdir, sourcefile.sub(/^\w+\//, '')) + reqs << destfile + + # Make sure the base directory is listed as a prereq + sourcedir = File.dirname(sourcefile) + destdir = nil + unless sourcedir == "." + destdir = File.dirname(destfile) + reqs << destdir + directory(destdir) + end + + # Now make the task associated with creating the object in + # question. + if FileTest.directory?(sourcefile) + directory(destfile) + else + file(destfile => sourcefile) do + if FileTest.exists?(destfile) + if File.stat(sourcefile) > File.stat(destfile) + rm_f destfile + safe_ln(sourcefile, destfile) + end + else + safe_ln(sourcefile, destfile) + end + end + + # If we've set the destdir, then list it as a prereq. + if destdir + file destfile => destdir + end + end + end + + # And create a task for each one + task tname => reqs + + # And then mark our task as a prereq + tasks << tname + end + + task :copycode => [self.package_dir, pkgdest] + + task :copycode => tasks do + puts "Finished copying" + end + end + + # Where we're copying a given type of file. + def targetdir(dirname) + File.join(pkgdest(), self.send(dirname)).sub("//", "/") + end + + private + + def package_name + @version ? "#{@name}-#{@version}" : @name + end + + def package_dir_path + "#{package_dir}/#{package_name}" + end +end diff --git a/tasks/rake/reductive.rb b/tasks/rake/reductive.rb new file mode 100644 index 0000000000..2fbd2b4d9b --- /dev/null +++ b/tasks/rake/reductive.rb @@ -0,0 +1,538 @@ +#!/usr/bin/env ruby + +# The tasks associated with building Reductive Labs projects + +require 'rbconfig' +require 'rake' +require 'rake/tasklib' + +require 'rake/clean' +require 'rake/testtask' + +$features = {} + +begin + require 'rubygems' + require 'rake/gempackagetask' + $features[:gem] = true +rescue Exception + $features[:gem] = false + $stderr.puts "No Gems; skipping" + nil +end + +begin + require 'rdoc/rdoc' + $features[:rdoc] = true +rescue => detail + $features[:rdoc] = false + puts "No rdoc: %s" % detail +end + +if $features[:rdoc] + require 'rake/rdoctask' +end + +# Create all of the standard targets for a Reductive Labs project. +# NOTE: The reason so many of the rake tasks are generated, rather than being +# declared directly, is that they need information from the project instance. +# Any rake task with an instance variable (e.g., @name or @version) needs +# to have that variable assigned *before* the task is defined. Suckage. +class Rake::RedLabProject < Rake::TaskLib + # The project name. + attr_accessor :name + + # The project version. + attr_accessor :version + + # The directory to which to publish packages and html and such. + attr_accessor :publishdir + + # The package-specific publishing directory + attr_accessor :pkgpublishdir + + # Create a Gem file. + attr_accessor :mkgem + + # The hosts to run all of our tests on. + attr_accessor :testhosts + + # The summary of this project. + attr_accessor :summary + + # The description of this project. + attr_accessor :description + + # The author of this project. + attr_accessor :author + + # A Contact email address. + attr_accessor :email + + # The URL for the project. + attr_accessor :url + + # Where to get the source code. + attr_accessor :source + + # Who the vendor is. + attr_accessor :vendor + + # The copyright for this project + attr_accessor :copyright + + # The RubyForge project. + attr_accessor :rfproject + + # The list of files. Only used for gem tasks. + attr_writer :filelist + + # The directory in which to store packages. Defaults to "pkg". + attr_accessor :package_dir + + # The default task. Defaults to the 'alltests' task. + attr_accessor :defaulttask + + # The defined requirements + attr_reader :requires + + # The file containing the version string. + attr_accessor :versionfile + + # Print messages on stdout + def announce(msg = nil) + puts msg + end + + # Print messages on stderr + def warn(msg = nil) + $stderr.puts msg + end + + def add_dependency(name, version) + @requires[name] = version + end + + # Where we'll be putting the code. + def codedir + unless defined? @codedir + @codedir = File.join(self.package_dir, "#{@name}-#{@version}") + end + + return @codedir + end + + # Retrieve the current version from the code. + def currentversion + unless defined? @currentversion + ver = %x{ruby -Ilib ./bin/#{@name} --version}.chomp + if $? == 0 and ver != "" + @currentversion = ver + else + warn "Could not retrieve current version; using 0.0.0" + @currentversion = "0.0.0" + end + end + + return @currentversion + end + + # Define all of our package tasks. We just search through all of our + # defined methods and call anything that's listed as making tasks. + def define + self.methods.find_all { |method| method.to_s =~ /^mktask/ }.each { |method| + self.send(method) + } + end + + def egrep(pattern) + Dir['**/*.rb'].each do |fn| + count = 0 + open(fn) do |f| + while line = f.gets + count += 1 + if line =~ pattern + puts "#{fn}:#{count}:#{line}" + end + end + end + end + end + + # List all of the files. + def filelist + unless defined? @createdfilelist + # If they passed in a file list as an array, then create a FileList + # object out of it. + if defined? @filelist + unless @filelist.is_a? FileList + @filelist = FileList[@filelist] + end + else + # Use a default file list. + @filelist = FileList[ + 'install.rb', + '[A-Z]*', + 'lib/**/*.rb', + 'test/**/*.rb', + 'bin/**/*', + 'ext/**/*', + 'examples/**/*', + 'conf/**/*' + ] + end + @filelist.delete_if {|item| item.include?(".git")} + + @createdfilelist = true + end + + @filelist + end + + def has?(feature) + feature = feature.intern if feature.is_a? String + if $features.include?(feature) + return $features[feature] + else + return true + end + end + + def initialize(name, version = nil) + @name = name + + if ENV['REL'] + @version = ENV['REL'] + else + @version = version || self.currentversion + end + + @defaulttask = :alltests + @publishdir = "/opt/rl/docroots/reductivelabs.com/htdocs/downloads" + @pkgpublishdir = "#{@publishdir}/#{@name}" + + @email = "dev@reductivelabs.com" + @url = "http://reductivelabs.com/projects/#{@name}" + @source = "http://reductivelabs.com/downloads/#{@name}/#{@name}-#{@version}.tgz" + @vendor = "Reductive Labs, LLC" + @copyright = "Copyright 2003-2008, Reductive Labs, LLC. Some Rights Reserved." + @rfproject = @name + + @defaulttask = :package + + @package_dir = "pkg" + + @requires = {} + + @versionfile = "lib/#{@name}.rb" + + CLOBBER.include('doc/*') + + yield self if block_given? + define if block_given? + end + + def mktaskhtml + if $features[:rdoc] + Rake::RDocTask.new(:html) { |rdoc| + rdoc.rdoc_dir = 'html' + rdoc.template = 'html' + rdoc.title = @name.capitalize + rdoc.options << '--line-numbers' << '--inline-source' << + '--main' << 'README' + rdoc.rdoc_files.include('README', 'COPYING', 'CHANGELOG') + rdoc.rdoc_files.include('lib/**/*.rb') + CLEAN.include("html") + } + + # Publish the html. + task :publish => [:package, :html] do + puts Dir.getwd + sh %{cp -r html #{self.pkgpublishdir}/apidocs} + end + else + warn "No rdoc; skipping html" + end + end + + # Create a release task. + def mktaskrelease + desc "Make a new release" + task :release => [ + :prerelease, + :clobber, + :update_version, + :commit_newversion, + :trac_version, + :tag, # tag everything before we make a bunch of extra dirs + :html, + :package, + :publish + ] do + + announce + announce "**************************************************************" + announce "* Release #{@version} Complete." + announce "* Packages ready to upload." + announce "**************************************************************" + announce + end + end + + # Do any prerelease work. + def mktaskprerelease + # Validate that everything is ready to go for a release. + task :prerelease do + announce + announce "**************************************************************" + announce "* Making Release #{@version}" + announce "* (current version #{self.currentversion})" + announce "**************************************************************" + announce + + # Is a release number supplied? + unless ENV['REL'] + warn "You must provide a release number when releasing" + fail "Usage: rake release REL=x.y.z [REUSE=tag_suffix]" + end + + # Is the release different than the current release. + # (or is REUSE set?) + if @version == self.currentversion && ! ENV['REUSE'] + fail "Current version is #{@version}, must specify REUSE=tag_suffix to reuse version" + end + + # Are all source files checked in? + if ENV['RELTEST'] + announce "Release Task Testing, skipping checked-in file test" + else + announce "Checking for unchecked-in files..." + data = %x{git status} + unless data.include?("nothing to commit") + fail "git status is not clean ... do you have unchecked-in files?" + end + announce "No outstanding checkins found ... OK" + end + end + end + + # Create the task to update versions. + def mktaskupdateversion + task :update_version => [:prerelease] do + if @version == self.currentversion + announce "No version change ... skipping version update" + else + announce "Updating #{@versionfile} version to #{@version}" + open(@versionfile) do |rakein| + open("#{@versionfile}.new", "w") do |rakeout| + rakein.each do |line| + if line =~ /^(\s*)#{@name.upcase}VERSION\s*=\s*/ + rakeout.puts "#{$1}#{@name.upcase}VERSION = '#{@version}'" + else + rakeout.puts line + end + end + end + end + mv "#{@versionfile}.new", @versionfile + + end + end + + desc "Commit the new versions to SVN." + task :commit_newversion => [:update_version] do + if ENV['RELTEST'] + announce "Release Task Testing, skipping commiting of new version" + else + sh %{git commit -m "Updated to version #{@version}" #{@versionfile}} + end + end + end + + def mktasktrac_version + task :trac_version => [:update_version] do + tracpath = "/opt/rl/trac/#{@name}" + + unless FileTest.exists?(tracpath) + announce "No Trac instance at %s" % tracpath + else + output = %x{sudo trac-admin #{tracpath} version list}.chomp.split("\n") + versions = {} + output[3..-1].each do |line| + name, time = line.chomp.split(/\s+/) + versions[name] = time + end + + if versions.include?(@version) + announce "Version #{@version} already in Trac" + else + announce "Adding #{@name} version #{@version} to Trac" + date = [Time.now.year.to_s, + Time.now.month.to_s, + Time.now.day.to_s].join("-") + system("sudo trac-admin #{tracpath} version add #{@version} #{date}") + end + end + end + end + + # Create the tag task. + def mktasktag + desc "Tag all the files with the latest release number (REL=x.y.z)" + task :tag => [:prerelease] do + reltag = @version + announce "Tagging with [#{reltag}]" + + if ENV['RELTEST'] + announce "Release Task Testing, skipping tagging" + else + sh %{git tag #{reltag}} + end + end + end + + # Create the task for testing across all hosts. + def mktaskhosttest + desc "Test Puppet on each test host" + task :hosttest do + out = "" + TESTHOSTS.each { |host| + puts "testing %s" % host + cwd = Dir.getwd + file = "/tmp/#{@name}-#{host}test.out" + system("ssh #{host} 'cd git/#{@name}/test; sudo rake' 2>&1 >#{file}") + + if $? != 0 + puts "%s failed; output is in %s" % [host, file] + end + } + end + end + + def mktaskri + # Create a task to build the RDOC documentation tree. + + #Rake::RDocTask.new("ri") { |rdoc| + # #rdoc.rdoc_dir = 'html' + # #rdoc.template = 'html' + # rdoc.title = "Puppet" + # rdoc.options << '--ri' << '--line-numbers' << '--inline-source' << '--main' << 'README' + # rdoc.rdoc_files.include('README', 'COPYING', 'CHANGELOG') + # rdoc.rdoc_files.include('lib/**/*.rb', 'doc/**/*.rdoc') + #} + + if $features[:rdoc] + task :ri do |ri| + files = ['README', 'COPYING', 'CHANGELOG'] + Dir.glob('lib/**/*.rb') + puts "files are \n%s" % files.join("\n") + begin + ri = RDoc::RDoc.new + ri.document(["--ri-site"] + files) + rescue RDoc::RDocError => detail + puts "Failed to build docs: %s" % detail + return nil + rescue LoadError + puts "Missing rdoc; cannot build documentation" + return nil + end + end + else + warn "No rdoc; skipping ri." + end + end + + desc "Install the application using the standard install.rb script" + task :install do + ruby "install.rb" + end + + def mktaskdefault + if dtask = self.defaulttask + desc "Default task" + task :default => dtask + end + end + + desc "Run all unit tests." + task :alltests do + if FileTest.exists?("test/Rakefile") + sh %{cd test; rake} + else + Dir.chdir("test") do + Dir.entries(".").find_all { |f| f =~ /\.rb/ }.each do |f| + sh %{ruby #{f}} + end + end + end + end + + desc "List all ruby files" + task :rubyfiles do + puts Dir['**/*.rb'].reject { |fn| fn =~ /^pkg/ } + puts Dir['**/bin/*'].reject { |fn| fn =~ /svn|(~$)|(\.rb$)/ } + end + + desc "Look for TODO and FIXME tags in the code" + task :todo do + egrep "/#.*(FIXME|TODO|TBD)/" + end + + # This task requires extra information from the Rake file. + def mkgemtask + # ==================================================================== + # Create a task that will package the Rake software into distributable + # tar, zip and gem files. + if ! defined?(Gem) + puts "Package Target requires RubyGEMs" + else + spec = Gem::Specification.new { |s| + + #### Basic information. + + s.name = self.name + s.version = self.version + s.summary = self.summary + s.description = self.description + s.platform = Gem::Platform::RUBY + + #### Dependencies and requirements. + + # I'd love to explicitly list all of the libraries that I need, + # but gems seem to only be able to handle dependencies on other + # gems, which is, um, stupid. + self.requires.each do |name, version| + s.add_dependency(name, ">= #{version}") + end + + s.files = filelist.to_a + + #### Signing key and cert chain + #s.signing_key = '/..../gem-private_key.pem' + #s.cert_chain = ['gem-public_cert.pem'] + + #### Author and project details. + + s.author = [self.author] + s.email = self.email + s.homepage = self.url + s.rubyforge_project = self.rfproject + + yield s + } + + Rake::GemPackageTask.new(spec) { |pkg| + pkg.need_tar = true + } + + desc "Copy the newly created package into the downloads directory" + task :publish => [:package] do + puts Dir.getwd + sh %{cp pkg/#{@name}-#{@version}.gem #{self.publishdir}/gems} + sh %{gem generate_index -d #{self.publishdir}} + sh %{cp pkg/#{@name}-#{@version}.tgz #{self.pkgpublishdir}} + sh %{ln -sf #{@name}-#{@version}.tgz #{self.pkgpublishdir}/#{@name}-latest.tgz} + end + CLEAN.include("pkg") + end + end +end From a932a69bcb5a09765d750dddd52fdfb16e0729de Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Tue, 10 Feb 2009 15:12:48 +1100 Subject: [PATCH 0322/3753] Added README.rst for Facter --- README.rst | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 README.rst diff --git a/README.rst b/README.rst new file mode 100644 index 0000000000..3b69d0910e --- /dev/null +++ b/README.rst @@ -0,0 +1,26 @@ +Facter +====== + +This package is largely meant to be a library for collecting facts about your +system. These facts are mostly strings (i.e., not numbers), and are things +like the output of ``uname``, public ssh and cfengine keys, the number of +processors, etc. + +See ``bin/facter`` for an example of the interface. + +Running Facter +++++++++++++++ + +Run the ``facter`` binary on the command for a full list of facts supported on your host. + +Adding your own facts ++++++++++++++++++++++ + +See the `Adding Facts`_ wiki page for details of how to add your own custom facts to Facter. + +Further Information ++++++++++++++++++++ + +See http://reductivelabs.com/projects/facter/ for more details. + +.. _Adding Facts: http://reductivelabs.com/trac/puppet/wiki/AddingFacts From 552f1504ae4ae1253554afea2fcfb9f6ac79914a Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Wed, 11 Feb 2009 14:42:16 +1100 Subject: [PATCH 0323/3753] Added support for Oracle Enterprise Linux to operatingsystem and operatingsystemrelease --- CHANGELOG | 3 +++ lib/facter/operatingsystem.rb | 2 ++ lib/facter/operatingsystemrelease.rb | 12 ++++++++++++ 3 files changed, 17 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index c60317e8df..8191fe29cb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,7 @@ 1.5.4: + Added support for Oracle Enterprise Linux to operatingsystem + and operatingsystemrelease + Fixed #1927 - failing facts don't kill Facter Fixed #1850 - Facter updates for Ruby 1.9 diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index 918f119a25..700ff1ff11 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -22,6 +22,8 @@ "Mandrake" elsif FileTest.exists?("/etc/arch-release") "Archlinux" + elsif FileTest.exists?("/etc/enterprise-release") + "OEL" elsif FileTest.exists?("/etc/redhat-release") txt = File.read("/etc/redhat-release") if txt =~ /centos/i diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index bbf530aefb..0afa96142d 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -26,6 +26,18 @@ end end +Facter.add(:operatingsystemrelease) do + confine :operatingsystem => :oel + setcode do + File::open("/etc/enterprise-release", "r") do |f| + line = f.readline.chomp + if line =~ /release (\d+)/ + $1 + end + end + end +end + Facter.add(:operatingsystemrelease) do confine :operatingsystem => %w{CentOS} setcode do From 04389db8b4a3a8fe4e737dea48c48afd0d821cf1 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Wed, 11 Feb 2009 15:05:22 +1100 Subject: [PATCH 0324/3753] Added support for Oracle VM Server to operatingsystem and operatingsystemrelease --- CHANGELOG | 3 +++ lib/facter/operatingsystem.rb | 2 ++ lib/facter/operatingsystemrelease.rb | 12 ++++++++++++ 3 files changed, 17 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 8191fe29cb..e1cb38860c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,7 @@ 1.5.4: + Added support for Oracle VM Server to operatingsystem + and operatingsystemrelease + Added support for Oracle Enterprise Linux to operatingsystem and operatingsystemrelease diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index 700ff1ff11..72f25f585b 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -24,6 +24,8 @@ "Archlinux" elsif FileTest.exists?("/etc/enterprise-release") "OEL" + elsif FileTest.exists?("/etc/ovs-release") + "OVS" elsif FileTest.exists?("/etc/redhat-release") txt = File.read("/etc/redhat-release") if txt =~ /centos/i diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index 0afa96142d..e52d47a2e5 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -38,6 +38,18 @@ end end +Facter.add(:operatingsystemrelease) do + confine :operatingsystem => :ovs + setcode do + File::open("/etc/ovs-release", "r") do |f| + line = f.readline.chomp + if line =~ /release (\d+)/ + $1 + end + end + end +end + Facter.add(:operatingsystemrelease) do confine :operatingsystem => %w{CentOS} setcode do From 94ea80731205da4503ace16a83dd418b43cd3bfb Mon Sep 17 00:00:00 2001 From: "Joel W. Shea" Date: Thu, 5 Feb 2009 16:52:17 +1100 Subject: [PATCH 0325/3753] This commit refs #1555, #1898 and fixes #1761 a --- CHANGELOG | 5 +++++ lib/facter/kernelversion.rb | 5 +++++ lib/facter/operatingsystemrelease.rb | 7 ------- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index e1cb38860c..b23adfbd60 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,9 @@ 1.5.4: + Changes to Solaris facts: + operatingsystemrelease == kernel release or uname -r + kernelrelease == uname -r + kernelversion == uname -v + Added support for Oracle VM Server to operatingsystem and operatingsystemrelease diff --git a/lib/facter/kernelversion.rb b/lib/facter/kernelversion.rb index 4abb6d6b70..cac6c145c7 100644 --- a/lib/facter/kernelversion.rb +++ b/lib/facter/kernelversion.rb @@ -3,3 +3,8 @@ Facter['kernelrelease'].value.split('-')[0] end end + +Facter.add("kernelversion") do + confine :kernel => :sunos + setcode 'uname -v' +end diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index e52d47a2e5..1ea2e7541c 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -99,13 +99,6 @@ end end -Facter.add(:operatingsystemrelease) do - confine :operatingsystem => %w{Solaris} - setcode do - release = Facter::Util::Resolution.exec('uname -v') - end -end - Facter.add(:operatingsystemrelease) do setcode do Facter[:kernelrelease].value end end From a99d04358533b1cac29ed777440e2608ba0a01c9 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Fri, 13 Feb 2009 16:00:15 +1100 Subject: [PATCH 0326/3753] Fixed #1966 - Added physicalprocessorcount fact --- CHANGELOG | 4 +++- lib/facter/physicalprocessorcount.rb | 7 +++++++ 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 lib/facter/physicalprocessorcount.rb diff --git a/CHANGELOG b/CHANGELOG index b23adfbd60..fc0d4d7f5f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,7 @@ 1.5.4: - Changes to Solaris facts: + Fixed #1966 - Added physicalprocessorcount fact + + Fixed #1761 - changes to Solaris facts: operatingsystemrelease == kernel release or uname -r kernelrelease == uname -r kernelversion == uname -v diff --git a/lib/facter/physicalprocessorcount.rb b/lib/facter/physicalprocessorcount.rb new file mode 100644 index 0000000000..8fe643e3ca --- /dev/null +++ b/lib/facter/physicalprocessorcount.rb @@ -0,0 +1,7 @@ +Facter.add("physicalprocessorcount") do + confine :kernel => :linux + + setcode do + ppcount = Facter::Util::Resolution.exec('grep "physical id" /proc/cpuinfo|cut -d: -f 2|sort -u|wc -l') + end +end From 91d8cb759baca3322bc5807a87a199f0aef82c6f Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Tue, 17 Feb 2009 00:59:39 +0100 Subject: [PATCH 0327/3753] Updated to version 1.5.4 --- lib/facter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter.rb b/lib/facter.rb index c147673fe3..c73f714b63 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -27,7 +27,7 @@ module Util; end include Comparable include Enumerable - FACTERVERSION = '1.5.3' + FACTERVERSION = '1.5.4' # = Facter # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. From 86b01bf61b143e03647f5983cbc99beae8744704 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sat, 28 Feb 2009 02:45:57 +1100 Subject: [PATCH 0328/3753] Fixed #2032 - file.open hanging on /proc/uptime on some platform --- CHANGELOG | 3 +++ lib/facter/util/uptime.rb | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index fc0d4d7f5f..ac03ca6469 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,6 @@ +1.5.x: + Fixed #2032 - file.open hanging on /proc/uptime on some platform + 1.5.4: Fixed #1966 - Added physicalprocessorcount fact diff --git a/lib/facter/util/uptime.rb b/lib/facter/util/uptime.rb index 1be3b980d4..ebba275faf 100644 --- a/lib/facter/util/uptime.rb +++ b/lib/facter/util/uptime.rb @@ -14,7 +14,8 @@ def self.get_uptime_simple end def self.get_uptime - uptime, idletime = File.open("/proc/uptime").gets.split(" ") + r = IO.popen("/bin/cat /proc/uptime") + uptime, idletime = r.readline.split(" ") uptime_seconds = uptime.to_i end From 6b904a0953177744c533b456e73fe78d08198285 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sat, 28 Feb 2009 09:27:22 +1100 Subject: [PATCH 0329/3753] Added EC2 facts --- CHANGELOG | 8 +++++--- lib/facter/ec2.rb | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 lib/facter/ec2.rb diff --git a/CHANGELOG b/CHANGELOG index ac03ca6469..d7131e14ea 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,8 @@ -1.5.x: - Fixed #2032 - file.open hanging on /proc/uptime on some platform - +1.6.0: + Added EC2 facts + + Fixed #2032 - file.open hanging on /proc/uptime on some platform + 1.5.4: Fixed #1966 - Added physicalprocessorcount fact diff --git a/lib/facter/ec2.rb b/lib/facter/ec2.rb new file mode 100644 index 0000000000..ef84757ff7 --- /dev/null +++ b/lib/facter/ec2.rb @@ -0,0 +1,35 @@ +# Changelog: +# Original facts - Tim Dysinger +# Updated and added can_connect? function - KurtBe + +require 'open-uri' +require 'timeout' + +def can_connect?(ip,port,wait_sec=2) + Timeout::timeout(wait_sec) {open(ip, port)} + return true +rescue + return false +end + + +def metadata(id = "") + open("http://169.254.169.254/2008-02-01/meta-data/#{id||=''}").read. + split("\n").each do |o| + key = "#{id}#{o.gsub(/\=.*$/, '/')}" + if key[-1..-1] != '/' + value = open("http://169.254.169.254/2008-02-01/meta-data/#{key}").read. + split("\n") + value = value.size>1 ? value : value.first + symbol = "ec2_#{key.gsub(/\-|\//, '_')}".to_sym + Facter.add(symbol) { setcode { value } } + else + metadata(key) + end + end +end + +if can_connect?("169.254.169.254","80") + metadata +end + From 9bc174faffcdb412e07e2dde1686cdec9a4d638c Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sat, 28 Feb 2009 09:28:47 +1100 Subject: [PATCH 0330/3753] Further fix #2032 - close IO --- lib/facter/util/uptime.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/facter/util/uptime.rb b/lib/facter/util/uptime.rb index ebba275faf..76c44648e4 100644 --- a/lib/facter/util/uptime.rb +++ b/lib/facter/util/uptime.rb @@ -16,6 +16,7 @@ def self.get_uptime_simple def self.get_uptime r = IO.popen("/bin/cat /proc/uptime") uptime, idletime = r.readline.split(" ") + r.close uptime_seconds = uptime.to_i end From b6f0f9949be9056ea8823b10b86a2b9ea848fad7 Mon Sep 17 00:00:00 2001 From: Ian Taylor Date: Tue, 17 Feb 2009 19:22:54 -0500 Subject: [PATCH 0331/3753] more consistent indentation and alignment. also removal of trailing whitespace --- CHANGELOG | 46 +-- Rakefile | 36 +-- autotest/discover.rb | 4 +- autotest/facter_rspec.rb | 20 +- autotest/rspec.rb | 110 +++---- bin/facter | 50 +-- conf/osx/PackageInfo.plist | 60 ++-- conf/osx/createpackage.sh | 34 +- install.rb | 452 +++++++++++++-------------- lib/facter.rb | 22 +- lib/facter/kernel.rb | 4 +- lib/facter/lsb.rb | 10 +- lib/facter/manufacturer.rb | 16 +- lib/facter/memory.rb | 9 +- lib/facter/netmask.rb | 8 +- lib/facter/network.rb | 9 +- lib/facter/operatingsystem.rb | 8 +- lib/facter/operatingsystemrelease.rb | 4 +- lib/facter/timezone.rb | 2 +- lib/facter/uniqueid.rb | 2 +- lib/facter/uptime.rb | 6 +- lib/facter/util/ip.rb | 22 +- lib/facter/util/manufacturer.rb | 74 ++--- lib/facter/util/plist/generator.rb | 360 ++++++++++----------- lib/facter/util/plist/parser.rb | 325 ++++++++++--------- lib/facter/util/uptime.rb | 18 +- tasks/rake/redlabpackage.rb | 18 +- tasks/rake/reductive.rb | 12 +- 28 files changed, 868 insertions(+), 873 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d7131e14ea..51b2d1c3a0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,4 @@ -1.6.0: +1.5.x: Added EC2 facts Fixed #2032 - file.open hanging on /proc/uptime on some platform @@ -8,13 +8,13 @@ Fixed #1761 - changes to Solaris facts: operatingsystemrelease == kernel release or uname -r - kernelrelease == uname -r + kernelrelease == uname -r kernelversion == uname -v Added support for Oracle VM Server to operatingsystem and operatingsystemrelease - Added support for Oracle Enterprise Linux to operatingsystem + Added support for Oracle Enterprise Linux to operatingsystem and operatingsystemrelease Fixed #1927 - failing facts don't kill Facter @@ -47,19 +47,19 @@ Added ci namespace and Rake tasks - Fixed #1650 - OS X package creation script should be more selective + Fixed #1650 - OS X package creation script should be more selective about cleaning out prior versions Added Ubuntu to a variety of confines Fixed #1619 - Applying patch by seanmil, adding support for SLES. - - Fixed #1634 - Update virtual fact to differentiate OpenVZ + + Fixed #1634 - Update virtual fact to differentiate OpenVZ hardware nodes and virtual environments Fixed #1509 - Fixed version recognition for SLES. - Fixes #1582 - Fix MAC address reporting for Linux bonding + Fixes #1582 - Fix MAC address reporting for Linux bonding slave interfaces Fixed #1575 - CentOS fix for Facter SPEC file @@ -115,8 +115,8 @@ Fixes #1467 - macaddress not set on Ubuntu - Enabled a number of Windows facts - operating system, domain, ipaddress, macaddress, - kernel, ipconfig and others + Enabled a number of Windows facts - operating system, domain, ipaddress, macaddress, + kernel, ipconfig and others 1.5.0: Fixed Rakefile to include additional files including tests et al @@ -145,7 +145,7 @@ Add lsbmajdistrelease fact for CentOS and Red Hat - Updated Red Hat spec file for new version + Updated Red Hat spec file for new version The 'facter' executable now has an option (-p|--puppet) for loading the Puppet libraries, which gives it access to Puppet's facts. @@ -157,22 +157,22 @@ Significantly refactored Facter's internals, including creating tests for all internal classes. - A netmask fact has been added closing ticket #46. It only returns the - netmask of the primary interface (in the same behaviour as the ipaddress + A netmask fact has been added closing ticket #46. It only returns the + netmask of the primary interface (in the same behaviour as the ipaddress and macaddress facts). - Facts to return multiple interfaces on a host have also been updated. - If you have multiple interfaces on Linux, *BSD, or Solaris/SunOS you will - now get facts for each interface's IP address, MAC address and netmask. + Facts to return multiple interfaces on a host have also been updated. + If you have multiple interfaces on Linux, *BSD, or Solaris/SunOS you will + now get facts for each interface's IP address, MAC address and netmask. The facts will be structured like: ipaddress_int = 10.0.0.x macaddress_int = xx:xx:xx:xx netmask_int = 255.255.255.0 - Facter now identifies Ubuntu hosts and their releases using the + Facter now identifies Ubuntu hosts and their releases using the operatingsystem and operatingsystemrelease facts. - The Debian operatingsystemrelease fact now correctly returns the current + The Debian operatingsystemrelease fact now correctly returns the current Debian release. Fixed ticket #48 - ioperatingsystem and operatingsystemrelease for CentOS @@ -186,16 +186,16 @@ Fixed ticket #45 - Added netmask.rb closing ticket #46 + Added netmask.rb closing ticket #46 1.3.8: Fixed Rdoc::usage bug on CentOS 5 - closed Puppet #753 and Facter #40 - Added support to return multiple interfaces and their IP addresses and - MAC addressess as facts. Returns interface_interfacename and - macaddress_interfacename. Existing ipaddress and macaddress facts are + Added support to return multiple interfaces and their IP addresses and + MAC addressess as facts. Returns interface_interfacename and + macaddress_interfacename. Existing ipaddress and macaddress facts are unchanged and still returned. Currently Linux only. Closes #6. - + Added macaddress fact support for FreeBSD and OpenBSD - closes #37 Added hardwareisa support for *BSD platforms - closed #38 @@ -289,4 +289,4 @@ adding new fact resolution mechanisms at run-time. 1.0b1: - Initial release. + Initial release. diff --git a/Rakefile b/Rakefile index b302c0cc83..f78fac2179 100644 --- a/Rakefile +++ b/Rakefile @@ -2,7 +2,7 @@ $LOAD_PATH << File.join(File.dirname(__FILE__), 'tasks') -begin +begin require 'rake/reductive' rescue LoadError $stderr.puts "You must have the Reductive build library in your RUBYLIB." @@ -12,16 +12,16 @@ end project = Rake::RedLabProject.new("facter") do |p| p.summary = "Facter collects Operating system facts." p.description = <<-EOF - Facter is a module for collecting simple facts about a host + Facter is a module for collecting simple facts about a host Operating system. EOF p.filelist = [ 'install.rb', '[A-Z]*', - 'bin/**/*', + 'bin/**/*', 'lib/facter.rb', - 'lib/**/*.rb', + 'lib/**/*.rb', 'test/**/*.rb', 'spec/**/*', 'conf/**/*', @@ -48,21 +48,19 @@ task :archive do end namespace :ci do + desc "Run the CI prep tasks" + task :prep do + require 'rubygems' + gem 'ci_reporter' + require 'ci/reporter/rake/rspec' + require 'ci/reporter/rake/test_unit' + ENV['CI_REPORTS'] = 'results' + end - desc "Run the CI prep tasks" - task :prep do - require 'rubygems' - gem 'ci_reporter' - require 'ci/reporter/rake/rspec' - require 'ci/reporter/rake/test_unit' - ENV['CI_REPORTS'] = 'results' - end - - desc "Run CI RSpec tests" - task :spec => [:prep, 'ci:setup:rspec'] do - sh "cd spec; rake all; exit 0" - end - + desc "Run CI RSpec tests" + task :spec => [:prep, 'ci:setup:rspec'] do + sh "cd spec; rake all; exit 0" + end end desc "Send patch information to the puppet-dev list" @@ -75,7 +73,7 @@ task :mail_patches do raise "Could not get branch from 'git status'" end branch = $1 - + unless branch =~ %r{^([^\/]+)/([^\/]+)/([^\/]+)$} raise "Branch name does not follow // model; cannot autodetect parent branch" end diff --git a/autotest/discover.rb b/autotest/discover.rb index 7cf09163a2..030d07f559 100644 --- a/autotest/discover.rb +++ b/autotest/discover.rb @@ -1,9 +1,9 @@ require 'autotest' Autotest.add_discovery do - "rspec" + "rspec" end Autotest.add_discovery do - "facter" + "facter" end diff --git a/autotest/facter_rspec.rb b/autotest/facter_rspec.rb index 0ec65b06f2..90e646d81c 100644 --- a/autotest/facter_rspec.rb +++ b/autotest/facter_rspec.rb @@ -16,25 +16,25 @@ # force a complete re-run for all of these: - # main facter lib - at.add_mapping(%r!^lib/facter\.rb$!) { |filename, _| + # main facter lib + at.add_mapping(%r!^lib/facter\.rb$!) { |filename, _| at.files_matching %r!spec/(unit|integration)/.*\.rb! - } + } - # the spec_helper - at.add_mapping(%r!^spec/spec_helper\.rb$!) { |filename, _| + # the spec_helper + at.add_mapping(%r!^spec/spec_helper\.rb$!) { |filename, _| at.files_matching %r!spec/(unit|integration)/.*\.rb! - } + } # the facter spec libraries - at.add_mapping(%r!^spec/lib/spec.*!) { |filename, _| + at.add_mapping(%r!^spec/lib/spec.*!) { |filename, _| at.files_matching %r!spec/(unit|integration)/.*\.rb! - } + } # the monkey patches for rspec - at.add_mapping(%r!^spec/lib/monkey_patches/.*!) { |filename, _| + at.add_mapping(%r!^spec/lib/monkey_patches/.*!) { |filename, _| at.files_matching %r!spec/(unit|integration)/.*\.rb! - } + } end class Autotest::FacterRspec < Autotest::Rspec diff --git a/autotest/rspec.rb b/autotest/rspec.rb index ebafbfe915..e395dfecf6 100644 --- a/autotest/rspec.rb +++ b/autotest/rspec.rb @@ -1,74 +1,74 @@ require 'autotest' Autotest.add_hook :initialize do |at| - at.clear_mappings - # watch out: Ruby bug (1.8.6): - # %r(/) != /\// - at.add_mapping(%r%^spec/.*\.rb$%) { |filename, _| - filename - } - at.add_mapping(%r%^lib/(.*)\.rb$%) { |_, m| - ["spec/#{m[1]}_spec.rb"] - } - at.add_mapping(%r%^spec/(spec_helper|shared/.*)\.rb$%) { - at.files_matching %r{^spec/.*_spec\.rb$} - } + at.clear_mappings + # watch out: Ruby bug (1.8.6): + # %r(/) != /\// + at.add_mapping(%r%^spec/.*\.rb$%) { |filename, _| + filename + } + at.add_mapping(%r%^lib/(.*)\.rb$%) { |_, m| + ["spec/#{m[1]}_spec.rb"] + } + at.add_mapping(%r%^spec/(spec_helper|shared/.*)\.rb$%) { + at.files_matching %r{^spec/.*_spec\.rb$} + } end class RspecCommandError < StandardError; end class Autotest::Rspec < Autotest - def initialize - super + def initialize + super - self.failed_results_re = /^\d+\)\n(?:\e\[\d*m)?(?:.*?Error in )?'([^\n]*)'(?: FAILED)?(?:\e\[\d*m)?\n(.*?)\n\n/m - self.completed_re = /\Z/ # FIX: some sort of summary line at the end? - end + self.failed_results_re = /^\d+\)\n(?:\e\[\d*m)?(?:.*?Error in )?'([^\n]*)'(?: FAILED)?(?:\e\[\d*m)?\n(.*?)\n\n/m + self.completed_re = /\Z/ # FIX: some sort of summary line at the end? + end - def consolidate_failures(failed) - filters = Hash.new { |h,k| h[k] = [] } - failed.each do |spec, failed_trace| - if f = test_files_for(failed).find { |f| failed_trace =~ Regexp.new(f) } then - filters[f] << spec - break - end + def consolidate_failures(failed) + filters = Hash.new { |h,k| h[k] = [] } + failed.each do |spec, failed_trace| + if f = test_files_for(failed).find { |f| failed_trace =~ Regexp.new(f) } then + filters[f] << spec + break + end + end + return filters end - return filters - end - def make_test_cmd(files_to_test) - return "#{ruby} -S #{spec_command} #{add_options_if_present} #{files_to_test.keys.flatten.join(' ')}" - end - - def add_options_if_present - File.exist?("spec/spec.opts") ? "-O spec/spec.opts " : "" - end + def make_test_cmd(files_to_test) + return "#{ruby} -S #{spec_command} #{add_options_if_present} #{files_to_test.keys.flatten.join(' ')}" + end + + def add_options_if_present + File.exist?("spec/spec.opts") ? "-O spec/spec.opts " : "" + end - # Finds the proper spec command to use. Precendence is set in the - # lazily-evaluated method spec_commands. Alias + Override that in - # ~/.autotest to provide a different spec command then the default - # paths provided. - def spec_command(separator=File::ALT_SEPARATOR) - unless defined? @spec_command then - @spec_command = spec_commands.find { |cmd| File.exists? cmd } + # Finds the proper spec command to use. Precendence is set in the + # lazily-evaluated method spec_commands. Alias + Override that in + # ~/.autotest to provide a different spec command then the default + # paths provided. + def spec_command(separator=File::ALT_SEPARATOR) + unless defined? @spec_command then + @spec_command = spec_commands.find { |cmd| File.exists? cmd } - raise RspecCommandError, "No spec command could be found!" unless @spec_command + raise RspecCommandError, "No spec command could be found!" unless @spec_command - @spec_command.gsub! File::SEPARATOR, separator if separator + @spec_command.gsub! File::SEPARATOR, separator if separator + end + @spec_command end - @spec_command - end - # Autotest will look for spec commands in the following - # locations, in this order: - # - # * bin/spec - # * default spec bin/loader installed in Rubygems - def spec_commands - [ - File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'bin', 'spec')), - File.join(Config::CONFIG['bindir'], 'spec') - ] - end + # Autotest will look for spec commands in the following + # locations, in this order: + # + # * bin/spec + # * default spec bin/loader installed in Rubygems + def spec_commands + [ + File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'bin', 'spec')), + File.join(Config::CONFIG['bindir'], 'spec') + ] + end end diff --git a/bin/facter b/bin/facter index 902bed93cc..6ee5b482af 100755 --- a/bin/facter +++ b/bin/facter @@ -1,4 +1,4 @@ -#!/usr/bin/env ruby +#!/usr/bin/env ruby # # = Synopsis # @@ -15,7 +15,7 @@ # about a system from within the shell or within Ruby. # # If no facts are specifically asked for, then all facts will be returned. -# +# # = Options # # debug:: @@ -63,34 +63,34 @@ $debug = 0 config = nil result = GetoptLong.new( - [ "--version", "-v", GetoptLong::NO_ARGUMENT ], - [ "--help", "-h", GetoptLong::NO_ARGUMENT ], - [ "--debug", "-d", GetoptLong::NO_ARGUMENT ], - [ "--yaml", "-y", GetoptLong::NO_ARGUMENT ], - [ "--config", "-c", GetoptLong::REQUIRED_ARGUMENT ], - [ "--puppet", "-p", GetoptLong::NO_ARGUMENT ] + [ "--version", "-v", GetoptLong::NO_ARGUMENT ], + [ "--help", "-h", GetoptLong::NO_ARGUMENT ], + [ "--debug", "-d", GetoptLong::NO_ARGUMENT ], + [ "--yaml", "-y", GetoptLong::NO_ARGUMENT ], + [ "--config", "-c", GetoptLong::REQUIRED_ARGUMENT ], + [ "--puppet", "-p", GetoptLong::NO_ARGUMENT ] ) options = { :yaml => false } -begin - result.each { |opt,arg| - case opt +begin + result.each { |opt,arg| + case opt when "--version" - puts "%s" % Facter.version - exit + puts "%s" % Facter.version + exit when "--puppet" - begin + begin require 'puppet' rescue LoadError => detail $stderr.puts "Could not load Puppet: %s" % detail end when "--yaml" - options[:yaml] = true + options[:yaml] = true when "--debug" - Facter.debugging(1) + Facter.debugging(1) when "--help" if $haveusage RDoc::usage && exit @@ -101,24 +101,24 @@ begin else $stderr.puts "Invalid option '#{opt}'" exit(12) - end - } + end + } rescue - exit(12) + exit(12) end names = [] unless config.nil? - File.open(config) { |file| - names = file.readlines.collect { |line| - line.chomp - } - } + File.open(config) { |file| + names = file.readlines.collect { |line| + line.chomp + } + } end ARGV.each { |item| - names.push item + names.push item } if names.empty? diff --git a/conf/osx/PackageInfo.plist b/conf/osx/PackageInfo.plist index 38b76d86d3..6668e2efaf 100644 --- a/conf/osx/PackageInfo.plist +++ b/conf/osx/PackageInfo.plist @@ -2,35 +2,35 @@ - CFBundleIdentifier - com.reductivelabs.facter - CFBundleShortVersionString - {SHORTVERSION} - IFMajorVersion - {MAJORVERSION} - IFMinorVersion - {MINORVERSION} - IFPkgFlagAllowBackRev - - IFPkgFlagAuthorizationAction - RootAuthorization - IFPkgFlagDefaultLocation - / - IFPkgFlagFollowLinks - - IFPkgFlagInstallFat - - IFPkgFlagIsRequired - - IFPkgFlagOverwritePermissions - - IFPkgFlagRelocatable - - IFPkgFlagRestartAction - None - IFPkgFlagRootVolumeOnly - - IFPkgFlagUpdateInstalledLanguages - + CFBundleIdentifier + com.reductivelabs.facter + CFBundleShortVersionString + {SHORTVERSION} + IFMajorVersion + {MAJORVERSION} + IFMinorVersion + {MINORVERSION} + IFPkgFlagAllowBackRev + + IFPkgFlagAuthorizationAction + RootAuthorization + IFPkgFlagDefaultLocation + / + IFPkgFlagFollowLinks + + IFPkgFlagInstallFat + + IFPkgFlagIsRequired + + IFPkgFlagOverwritePermissions + + IFPkgFlagRelocatable + + IFPkgFlagRestartAction + None + IFPkgFlagRootVolumeOnly + + IFPkgFlagUpdateInstalledLanguages + diff --git a/conf/osx/createpackage.sh b/conf/osx/createpackage.sh index 4e99c91624..768380e967 100755 --- a/conf/osx/createpackage.sh +++ b/conf/osx/createpackage.sh @@ -7,13 +7,13 @@ # Last Updated: 2008-07-31 # # Copyright 2008 Google Inc. -# +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -30,7 +30,7 @@ PREFLIGHT="preflight" function find_installer() { - # we walk up three directories to make this executable from the root, + # we walk up three directories to make this executable from the root, # root/conf or root/conf/osx if [ -f "./${INSTALLRB}" ]; then installer="$(pwd)/${INSTALLRB}" @@ -64,7 +64,7 @@ function prepare_package() { # to look at for package creation and substitue the version strings out. # Major/Minor versions can only be integers, so we have "1" and "50" for # facter version 1.5 - # Note too that for 10.5 compatibility this Info.plist *must* be set to + # Note too that for 10.5 compatibility this Info.plist *must* be set to # follow symlinks. VER1=$(echo ${facter_version} | awk -F "." '{print $1}') VER2=$(echo ${facter_version} | awk -F "." '{print $2}') @@ -75,12 +75,12 @@ function prepare_package() { sed -i '' "s/{SHORTVERSION}/${facter_version}/g" "${pkgtemp}/${PROTO_PLIST}" sed -i '' "s/{MAJORVERSION}/${major_version}/g" "${pkgtemp}/${PROTO_PLIST}" sed -i '' "s/{MINORVERSION}/${minor_version}/g" "${pkgtemp}/${PROTO_PLIST}" - + # We need to create a preflight script to remove traces of previous # facter installs due to limitations in Apple's pkg format. mkdir "${pkgtemp}/scripts" cp "${facter_root}/conf/osx/${PREFLIGHT}" "${pkgtemp}/scripts" - + # substitute in the sitelibdir specified above on the assumption that this # is where any previous facter install exists that should be cleaned out. sed -i '' "s|{SITELIBDIR}|${SITELIBDIR}|g" "${pkgtemp}/scripts/${PREFLIGHT}" @@ -124,44 +124,44 @@ function main() { fi find_installer - + if [ ! "${installer}" ]; then echo "Unable to find ${INSTALLRB}" cleanup_and_exit 1 fi find_facter_root - + if [ ! "${facter_root}" ]; then echo "Unable to find facter repository root." cleanup_and_exit 1 fi - + pkgroot=$(mktemp -d -t facterpkg) - + if [ ! "${pkgroot}" ]; then echo "Unable to create temporary package root." cleanup_and_exit 1 fi - + pkgtemp=$(mktemp -d -t factertmp) - + if [ ! "${pkgtemp}" ]; then echo "Unable to create temporary package root." cleanup_and_exit 1 fi - + install_facter get_facter_version - + if [ ! "${facter_version}" ]; then echo "Unable to retrieve facter version" cleanup_and_exit 1 fi - + prepare_package create_package - + cleanup_and_exit 0 } diff --git a/install.rb b/install.rb index 3e0d810102..d38aedea0b 100755 --- a/install.rb +++ b/install.rb @@ -81,33 +81,33 @@ def glob(list) tests = glob(%w{tests/**/*.rb}) def do_bins(bins, target, strip = 's?bin/') - bins.each do |bf| - obf = bf.gsub(/#{strip}/, '') - install_binfile(bf, obf, target) - end + bins.each do |bf| + obf = bf.gsub(/#{strip}/, '') + install_binfile(bf, obf, target) + end end def do_libs(libs, strip = 'lib/') - libs.each do |lf| - olf = File.join(InstallOptions.site_dir, lf.gsub(/#{strip}/, '')) - op = File.dirname(olf) - File.makedirs(op, true) - File.chmod(0755, op) - File.install(lf, olf, 0755, true) - end + libs.each do |lf| + olf = File.join(InstallOptions.site_dir, lf.gsub(/#{strip}/, '')) + op = File.dirname(olf) + File.makedirs(op, true) + File.chmod(0755, op) + File.install(lf, olf, 0755, true) + end end def do_man(man, strip = 'man/') - man.each do |mf| - omf = File.join(InstallOptions.man_dir, mf.gsub(/#{strip}/, '')) - om = File.dirname(omf) - File.makedirs(om, true) - File.chmod(0644, om) - File.install(mf, omf, 0644, true) - gzip = %x{which gzip} - gzip.chomp! - %x{#{gzip} -f #{omf}} - end + man.each do |mf| + omf = File.join(InstallOptions.man_dir, mf.gsub(/#{strip}/, '')) + om = File.dirname(omf) + File.makedirs(om, true) + File.chmod(0644, om) + File.install(mf, omf, 0644, true) + gzip = %x{which gzip} + gzip.chomp! + %x{#{gzip} -f #{omf}} + end end # Verify that all of the prereqs are installed @@ -126,158 +126,158 @@ def check_prereqs # Prepare the file installation. # def prepare_installation - # Only try to do docs if we're sure they have rdoc - if $haverdoc - InstallOptions.rdoc = true - if RUBY_PLATFORM == "i386-mswin32" + # Only try to do docs if we're sure they have rdoc + if $haverdoc + InstallOptions.rdoc = true + if RUBY_PLATFORM == "i386-mswin32" + InstallOptions.ri = false + else + InstallOptions.ri = true + end + else + InstallOptions.rdoc = false InstallOptions.ri = false - else - InstallOptions.ri = true - end - else - InstallOptions.rdoc = false - InstallOptions.ri = false - end - - - if $haveman - InstallOptions.man = true - if RUBY_PLATFORM == "i386-mswin32" - InstallOptions.man = false - end - else - InstallOptions.man = false - end - - InstallOptions.tests = true - - ARGV.options do |opts| - opts.banner = "Usage: #{File.basename($0)} [options]" - opts.separator "" - opts.on('--[no-]rdoc', 'Prevents the creation of RDoc output.', 'Default on.') do |onrdoc| - InstallOptions.rdoc = onrdoc - end - opts.on('--[no-]ri', 'Prevents the creation of RI output.', 'Default off on mswin32.') do |onri| - InstallOptions.ri = onri end - opts.on('--[no-]man', 'Presents the creation of man pages.', 'Default on.') do |onman| - InstallOptions.man = onman - end - opts.on('--[no-]tests', 'Prevents the execution of unit tests.', 'Default on.') do |ontest| - InstallOptions.tests = ontest - end - opts.on('--destdir[=OPTIONAL]', 'Installation prefix for all targets', 'Default essentially /') do |destdir| - InstallOptions.destdir = destdir - end - opts.on('--bindir[=OPTIONAL]', 'Installation directory for binaries', 'overrides Config::CONFIG["bindir"]') do |bindir| - InstallOptions.bindir = bindir + + + if $haveman + InstallOptions.man = true + if RUBY_PLATFORM == "i386-mswin32" + InstallOptions.man = false + end + else + InstallOptions.man = false end - opts.on('--sbindir[=OPTIONAL]', 'Installation directory for system binaries', 'overrides Config::CONFIG["sbindir"]') do |sbindir| - InstallOptions.sbindir = sbindir + + InstallOptions.tests = true + + ARGV.options do |opts| + opts.banner = "Usage: #{File.basename($0)} [options]" + opts.separator "" + opts.on('--[no-]rdoc', 'Prevents the creation of RDoc output.', 'Default on.') do |onrdoc| + InstallOptions.rdoc = onrdoc + end + opts.on('--[no-]ri', 'Prevents the creation of RI output.', 'Default off on mswin32.') do |onri| + InstallOptions.ri = onri + end + opts.on('--[no-]man', 'Presents the creation of man pages.', 'Default on.') do |onman| + InstallOptions.man = onman + end + opts.on('--[no-]tests', 'Prevents the execution of unit tests.', 'Default on.') do |ontest| + InstallOptions.tests = ontest + end + opts.on('--destdir[=OPTIONAL]', 'Installation prefix for all targets', 'Default essentially /') do |destdir| + InstallOptions.destdir = destdir + end + opts.on('--bindir[=OPTIONAL]', 'Installation directory for binaries', 'overrides Config::CONFIG["bindir"]') do |bindir| + InstallOptions.bindir = bindir + end + opts.on('--sbindir[=OPTIONAL]', 'Installation directory for system binaries', 'overrides Config::CONFIG["sbindir"]') do |sbindir| + InstallOptions.sbindir = sbindir + end + opts.on('--sitelibdir[=OPTIONAL]', 'Installation directory for libraries', 'overrides Config::CONFIG["sitelibdir"]') do |sitelibdir| + InstallOptions.sitelibdir = sitelibdir + end + opts.on('--mandir[=OPTIONAL]', 'Installation directory for man pages', 'overrides Config::CONFIG["mandir"]') do |mandir| + InstallOptions.mandir = mandir + end + opts.on('--quick', 'Performs a quick installation. Only the', 'installation is done.') do |quick| + InstallOptions.rdoc = false + InstallOptions.ri = false + InstallOptions.tests = false + end + opts.on('--full', 'Performs a full installation. All', 'optional installation steps are run.') do |full| + InstallOptions.rdoc = true + InstallOptions.ri = true + InstallOptions.tests = true + end + opts.separator("") + opts.on_tail('--help', "Shows this help text.") do + $stderr.puts opts + exit + end + + opts.parse! end - opts.on('--sitelibdir[=OPTIONAL]', 'Installation directory for libraries', 'overrides Config::CONFIG["sitelibdir"]') do |sitelibdir| - InstallOptions.sitelibdir = sitelibdir + + tmpdirs = [".", ENV['TMP'], ENV['TEMP'], "/tmp", "/var/tmp"] + + version = [Config::CONFIG["MAJOR"], Config::CONFIG["MINOR"]].join(".") + libdir = File.join(Config::CONFIG["libdir"], "ruby", version) + + # Mac OS X 10.5 declares bindir and sbindir as + # /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin + # /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/sbin + # which is not generally where people expect executables to be installed + if RUBY_PLATFORM == "universal-darwin9.0" + Config::CONFIG['bindir'] = "/usr/bin" + Config::CONFIG['sbindir'] = "/usr/sbin" end - opts.on('--mandir[=OPTIONAL]', 'Installation directory for man pages', 'overrides Config::CONFIG["mandir"]') do |mandir| - InstallOptions.mandir = mandir + + if not InstallOptions.bindir.nil? + bindir = InstallOptions.bindir + else + bindir = Config::CONFIG['bindir'] end - opts.on('--quick', 'Performs a quick installation. Only the', 'installation is done.') do |quick| - InstallOptions.rdoc = false - InstallOptions.ri = false - InstallOptions.tests = false + + if not InstallOptions.sbindir.nil? + sbindir = InstallOptions.sbindir + else + sbindir = Config::CONFIG['sbindir'] end - opts.on('--full', 'Performs a full installation. All', 'optional installation steps are run.') do |full| - InstallOptions.rdoc = true - InstallOptions.ri = true - InstallOptions.tests = true + + if not InstallOptions.sitelibdir.nil? + sitelibdir = InstallOptions.sitelibdir + else + sitelibdir = Config::CONFIG["sitelibdir"] + if sitelibdir.nil? + sitelibdir = $:.find { |x| x =~ /site_ruby/ } + if sitelibdir.nil? + sitelibdir = File.join(libdir, "site_ruby") + elsif sitelibdir !~ Regexp.quote(version) + sitelibdir = File.join(sitelibdir, version) + end + end end - opts.separator("") - opts.on_tail('--help', "Shows this help text.") do - $stderr.puts opts - exit + + if not InstallOptions.mandir.nil? + mandir = InstallOptions.mandir + else + mandir = Config::CONFIG['mandir'] end - opts.parse! - end - - tmpdirs = [".", ENV['TMP'], ENV['TEMP'], "/tmp", "/var/tmp"] - - version = [Config::CONFIG["MAJOR"], Config::CONFIG["MINOR"]].join(".") - libdir = File.join(Config::CONFIG["libdir"], "ruby", version) - - # Mac OS X 10.5 declares bindir and sbindir as - # /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin - # /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/sbin - # which is not generally where people expect executables to be installed - if RUBY_PLATFORM == "universal-darwin9.0" - Config::CONFIG['bindir'] = "/usr/bin" - Config::CONFIG['sbindir'] = "/usr/sbin" - end - - if not InstallOptions.bindir.nil? - bindir = InstallOptions.bindir - else - bindir = Config::CONFIG['bindir'] - end - - if not InstallOptions.sbindir.nil? - sbindir = InstallOptions.sbindir - else - sbindir = Config::CONFIG['sbindir'] - end - - if not InstallOptions.sitelibdir.nil? - sitelibdir = InstallOptions.sitelibdir - else - sitelibdir = Config::CONFIG["sitelibdir"] - if sitelibdir.nil? - sitelibdir = $:.find { |x| x =~ /site_ruby/ } - if sitelibdir.nil? - sitelibdir = File.join(libdir, "site_ruby") - elsif sitelibdir !~ Regexp.quote(version) - sitelibdir = File.join(sitelibdir, version) - end + # To be deprecated once people move over to using --destdir option + if (destdir = ENV['DESTDIR']) + bindir = "#{destdir}#{bindir}" + sbindir = "#{destdir}#{sbindir}" + mandir = "#{destdir}#{mandir}" + sitelibdir = "#{destdir}#{sitelibdir}" + + FileUtils.makedirs(bindir) + FileUtils.makedirs(sbindir) + FileUtils.makedirs(mandir) + FileUtils.makedirs(sitelibdir) + # This is the new way forward + elsif (destdir = InstallOptions.destdir) + bindir = "#{destdir}#{bindir}" + sbindir = "#{destdir}#{sbindir}" + mandir = "#{destdir}#{mandir}" + sitelibdir = "#{destdir}#{sitelibdir}" + + FileUtils.makedirs(bindir) + FileUtils.makedirs(sbindir) + FileUtils.makedirs(mandir) + FileUtils.makedirs(sitelibdir) end - end - - if not InstallOptions.mandir.nil? - mandir = InstallOptions.mandir - else - mandir = Config::CONFIG['mandir'] - end - - # To be deprecated once people move over to using --destdir option - if (destdir = ENV['DESTDIR']) - bindir = "#{destdir}#{bindir}" - sbindir = "#{destdir}#{sbindir}" - mandir = "#{destdir}#{mandir}" - sitelibdir = "#{destdir}#{sitelibdir}" - - FileUtils.makedirs(bindir) - FileUtils.makedirs(sbindir) - FileUtils.makedirs(mandir) - FileUtils.makedirs(sitelibdir) - # This is the new way forward - elsif (destdir = InstallOptions.destdir) - bindir = "#{destdir}#{bindir}" - sbindir = "#{destdir}#{sbindir}" - mandir = "#{destdir}#{mandir}" - sitelibdir = "#{destdir}#{sitelibdir}" - - FileUtils.makedirs(bindir) - FileUtils.makedirs(sbindir) - FileUtils.makedirs(mandir) - FileUtils.makedirs(sitelibdir) - end - - tmpdirs << bindir - - InstallOptions.tmp_dirs = tmpdirs.compact - InstallOptions.site_dir = sitelibdir - InstallOptions.bin_dir = bindir - InstallOptions.sbin_dir = sbindir - InstallOptions.lib_dir = libdir - InstallOptions.man_dir = mandir + + tmpdirs << bindir + + InstallOptions.tmp_dirs = tmpdirs.compact + InstallOptions.site_dir = sitelibdir + InstallOptions.bin_dir = bindir + InstallOptions.sbin_dir = sbindir + InstallOptions.lib_dir = libdir + InstallOptions.man_dir = mandir end ## @@ -323,10 +323,10 @@ def build_man(bins) # Create binary man pages bins.each do |bin| - b = bin.gsub( "bin/", "") - %x{#{bin} --help > ./#{b}.rst} - %x{#{rst2man} ./#{b}.rst ./man/man8/#{b}.8} - File.unlink("./#{b}.rst") + b = bin.gsub( "bin/", "") + %x{#{bin} --help > ./#{b}.rst} + %x{#{rst2man} ./#{b}.rst ./man/man8/#{b}.8} + File.unlink("./#{b}.rst") end rescue SystemCallError $stderr.puts "Couldn't build man pages: " + $! @@ -335,24 +335,24 @@ def build_man(bins) end def run_tests(test_list) - begin - require 'test/unit/ui/console/testrunner' - $:.unshift "lib" - test_list.each do |test| - next if File.directory?(test) - require test - end - - tests = [] - ObjectSpace.each_object { |o| tests << o if o.kind_of?(Class) } - tests.delete_if { |o| !o.ancestors.include?(Test::Unit::TestCase) } - tests.delete_if { |o| o == Test::Unit::TestCase } - - tests.each { |test| Test::Unit::UI::Console::TestRunner.run(test) } - $:.shift - rescue LoadError - puts "Missing testrunner library; skipping tests" - end + begin + require 'test/unit/ui/console/testrunner' + $:.unshift "lib" + test_list.each do |test| + next if File.directory?(test) + require test + end + + tests = [] + ObjectSpace.each_object { |o| tests << o if o.kind_of?(Class) } + tests.delete_if { |o| !o.ancestors.include?(Test::Unit::TestCase) } + tests.delete_if { |o| o == Test::Unit::TestCase } + + tests.each { |test| Test::Unit::UI::Console::TestRunner.run(test) } + $:.shift + rescue LoadError + puts "Missing testrunner library; skipping tests" + end end ## @@ -361,57 +361,57 @@ def run_tests(test_list) # (e.g., bin/rdoc becomes rdoc); the shebang line handles running it. Under # windows, we add an '.rb' extension and let file associations do their stuff. def install_binfile(from, op_file, target) - tmp_dir = nil - InstallOptions.tmp_dirs.each do |t| - if File.directory?(t) and File.writable?(t) - tmp_dir = t - break + tmp_dir = nil + InstallOptions.tmp_dirs.each do |t| + if File.directory?(t) and File.writable?(t) + tmp_dir = t + break + end end - end - - fail "Cannot find a temporary directory" unless tmp_dir - tmp_file = File.join(tmp_dir, '_tmp') - ruby = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name']) - - File.open(from) do |ip| - File.open(tmp_file, "w") do |op| - ruby = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name']) - op.puts "#!#{ruby}" - contents = ip.readlines - if contents[0] =~ /^#!/ - contents.shift - end - op.write contents.join() + + fail "Cannot find a temporary directory" unless tmp_dir + tmp_file = File.join(tmp_dir, '_tmp') + ruby = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name']) + + File.open(from) do |ip| + File.open(tmp_file, "w") do |op| + ruby = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name']) + op.puts "#!#{ruby}" + contents = ip.readlines + if contents[0] =~ /^#!/ + contents.shift + end + op.write contents.join() + end end - end - if Config::CONFIG["target_os"] =~ /win/io and Config::CONFIG["target_os"] !~ /darwin/io - installed_wrapper = false + if Config::CONFIG["target_os"] =~ /win/io and Config::CONFIG["target_os"] !~ /darwin/io + installed_wrapper = false - if File.exists?("#{from}.bat") - FileUtils.install("#{from}.bat", File.join(target, "#{op_file}.bat"), :mode => 0755, :verbose => true) - installed_wrapper = true - end + if File.exists?("#{from}.bat") + FileUtils.install("#{from}.bat", File.join(target, "#{op_file}.bat"), :mode => 0755, :verbose => true) + installed_wrapper = true + end - if File.exists?("#{from}.cmd") - FileUtils.install("#{from}.cmd", File.join(target, "#{op_file}.cmd"), :mode => 0755, :verbose => true) - installed_wrapper = true - end + if File.exists?("#{from}.cmd") + FileUtils.install("#{from}.cmd", File.join(target, "#{op_file}.cmd"), :mode => 0755, :verbose => true) + installed_wrapper = true + end - if not installed_wrapper - tmp_file2 = File.join(tmp_dir, '_tmp_wrapper') - cwn = File.join(Config::CONFIG['bindir'], op_file) - cwv = CMD_WRAPPER.gsub('', ruby.gsub(%r{/}) { "\\" }).gsub!('', cwn.gsub(%r{/}) { "\\" } ) + if not installed_wrapper + tmp_file2 = File.join(tmp_dir, '_tmp_wrapper') + cwn = File.join(Config::CONFIG['bindir'], op_file) + cwv = CMD_WRAPPER.gsub('', ruby.gsub(%r{/}) { "\\" }).gsub!('', cwn.gsub(%r{/}) { "\\" } ) - File.open(tmp_file2, "wb") { |cw| cw.puts cwv } - FileUtils.install(tmp_file2, File.join(target, "#{op_file}.bat"), :mode => 0755, :verbose => true) + File.open(tmp_file2, "wb") { |cw| cw.puts cwv } + FileUtils.install(tmp_file2, File.join(target, "#{op_file}.bat"), :mode => 0755, :verbose => true) - File.unlink(tmp_file2) - installed_wrapper = true + File.unlink(tmp_file2) + installed_wrapper = true + end end - end - FileUtils.install(tmp_file, File.join(target, op_file), :mode => 0755, :verbose => true) - File.unlink(tmp_file) + FileUtils.install(tmp_file, File.join(target, op_file), :mode => 0755, :verbose => true) + File.unlink(tmp_file) end CMD_WRAPPER = <<-EOS diff --git a/lib/facter.rb b/lib/facter.rb index c73f714b63..b395372e74 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -28,13 +28,13 @@ module Util; end include Enumerable FACTERVERSION = '1.5.4' - # = Facter + # = Facter # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. # returns them dynamically - # == Synopsis - # + # == Synopsis + # # Generally, treat Facter as a hash: # == Example # require 'facter' @@ -49,7 +49,7 @@ module Util; end RESET = "" @@debug = 0 - # module methods + # module methods def self.collection unless defined?(@collection) and @collection @@ -147,9 +147,9 @@ def self.clear Facter.reset end - # Set debugging on or off. - def self.debugging(bit) - if bit + # Set debugging on or off. + def self.debugging(bit) + if bit case bit when TrueClass; @@debug = 1 when FalseClass; @@debug = 0 @@ -168,10 +168,10 @@ def self.debugging(bit) else @@debug = 0 end - else - @@debug = 0 - end - end + else + @@debug = 0 + end + end # Remove them all. def self.reset diff --git a/lib/facter/kernel.rb b/lib/facter/kernel.rb index d37260799d..d68aa3f837 100644 --- a/lib/facter/kernel.rb +++ b/lib/facter/kernel.rb @@ -2,8 +2,8 @@ setcode do require 'rbconfig' case Config::CONFIG['host_os'] - when /mswin/i; 'windows' - else Facter::Util::Resolution.exec("uname -s") + when /mswin/i; 'windows' + else Facter::Util::Resolution.exec("uname -s") end end end diff --git a/lib/facter/lsb.rb b/lib/facter/lsb.rb index fc07437fda..f54d75fae0 100644 --- a/lib/facter/lsb.rb +++ b/lib/facter/lsb.rb @@ -13,11 +13,11 @@ ## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA ## -{ "LSBRelease" => %r{^LSB Version:\t(.*)$}, - "LSBDistId" => %r{^Distributor ID:\t(.*)$}, - "LSBDistRelease" => %r{^Release:\t(.*)$}, - "LSBDistDescription" => %r{^Description:\t(.*)$}, - "LSBDistCodeName" => %r{^Codename:\t(.*)$} +{ "LSBRelease" => %r{^LSB Version:\t(.*)$}, + "LSBDistId" => %r{^Distributor ID:\t(.*)$}, + "LSBDistRelease" => %r{^Release:\t(.*)$}, + "LSBDistDescription" => %r{^Description:\t(.*)$}, + "LSBDistCodeName" => %r{^Codename:\t(.*)$} }.each do |fact, pattern| Facter.add(fact) do confine :kernel => :linux diff --git a/lib/facter/manufacturer.rb b/lib/facter/manufacturer.rb index d2b13b99bb..e1ac7be956 100644 --- a/lib/facter/manufacturer.rb +++ b/lib/facter/manufacturer.rb @@ -6,14 +6,14 @@ require 'facter/util/manufacturer' query = { - '[Ss]ystem [Ii]nformation' => [ - { 'Manufacturer:' => 'manufacturer' }, - { 'Product(?: Name)?:' => 'productname' }, - { 'Serial Number:' => 'serialnumber' } - ], - '(Chassis Information|system enclosure or chassis)' => [ - { '(?:Chassis )?Type:' => 'type' } - ] + '[Ss]ystem [Ii]nformation' => [ + { 'Manufacturer:' => 'manufacturer' }, + { 'Product(?: Name)?:' => 'productname' }, + { 'Serial Number:' => 'serialnumber' } + ], + '(Chassis Information|system enclosure or chassis)' => [ + { '(?:Chassis )?Type:' => 'type' } + ] } Facter::Manufacturer.dmi_find_system_info(query) diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb index 01858d88bb..688073180e 100644 --- a/lib/facter/memory.rb +++ b/lib/facter/memory.rb @@ -7,10 +7,11 @@ # require 'facter/util/memory' -{:MemorySize => "MemTotal", - :MemoryFree => "MemFree", - :SwapSize => "SwapTotal", - :SwapFree => "SwapFree"}.each do |fact, name| +{ :MemorySize => "MemTotal", + :MemoryFree => "MemFree", + :SwapSize => "SwapTotal", + :SwapFree => "SwapFree" +}.each do |fact, name| Facter.add(fact) do confine :kernel => :linux setcode do diff --git a/lib/facter/netmask.rb b/lib/facter/netmask.rb index 309ef631d8..6ccef23f4f 100644 --- a/lib/facter/netmask.rb +++ b/lib/facter/netmask.rb @@ -9,9 +9,9 @@ require 'facter/util/netmask' Facter.add("netmask") do - confine :kernel => [ :sunos, :linux ] - setcode do - Facter::NetMask.get_netmask - end + confine :kernel => [ :sunos, :linux ] + setcode do + Facter::NetMask.get_netmask + end end diff --git a/lib/facter/network.rb b/lib/facter/network.rb index 513282a282..df53ce47db 100644 --- a/lib/facter/network.rb +++ b/lib/facter/network.rb @@ -1,10 +1,9 @@ require 'facter/util/ip' Facter::Util::IP.get_interfaces.each do |interface| - - Facter.add("network_" + Facter::Util::IP.alphafy(interface)) do - setcode do - Facter::Util::IP.get_network_value(interface) - end + Facter.add("network_" + Facter::Util::IP.alphafy(interface)) do + setcode do + Facter::Util::IP.get_network_value(interface) end + end end diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index 72f25f585b..52f889b998 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -20,8 +20,8 @@ "Mandriva" elsif FileTest.exists?("/etc/mandrake-release") "Mandrake" - elsif FileTest.exists?("/etc/arch-release") - "Archlinux" + elsif FileTest.exists?("/etc/arch-release") + "Archlinux" elsif FileTest.exists?("/etc/enterprise-release") "OEL" elsif FileTest.exists?("/etc/ovs-release") @@ -37,8 +37,8 @@ txt = File.read("/etc/SuSE-release") if txt =~ /^SUSE LINUX Enterprise Server/i "SLES" - elsif txt =~ /^openSUSE/i - "OpenSuSE" + elsif txt =~ /^openSUSE/i + "OpenSuSE" else "SuSE" end diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index 1ea2e7541c..25a226d3d8 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -87,8 +87,8 @@ releasemajor = $1 if releasefile =~ /^PATCHLEVEL\s*=\s*(\d+)/ releaseminor = $1 - elsif releasefile =~ /^VERSION\s=.*.(\d+)/ - releaseminor = $1 + elsif releasefile =~ /^VERSION\s=.*.(\d+)/ + releaseminor = $1 else releaseminor = "0" end diff --git a/lib/facter/timezone.rb b/lib/facter/timezone.rb index e01c5e0981..744e06a943 100644 --- a/lib/facter/timezone.rb +++ b/lib/facter/timezone.rb @@ -1,5 +1,5 @@ Facter.add("timezone") do - setcode do + setcode do Time.new.zone end end diff --git a/lib/facter/uniqueid.rb b/lib/facter/uniqueid.rb index b199865fd4..1af4ae5d56 100644 --- a/lib/facter/uniqueid.rb +++ b/lib/facter/uniqueid.rb @@ -1,4 +1,4 @@ Facter.add(:uniqueid) do - setcode 'hostid', '/bin/sh' + setcode 'hostid', '/bin/sh' confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE SLES Debian Ubuntu Gentoo AIX} end diff --git a/lib/facter/uptime.rb b/lib/facter/uptime.rb index 808bcec269..3a3bc862c6 100644 --- a/lib/facter/uptime.rb +++ b/lib/facter/uptime.rb @@ -12,9 +12,9 @@ %w{days hours seconds}.each do |label| Facter.add("uptime_" + label) do - setcode do - Facter::Util::Uptime.get_uptime_period(uptime, label) - end + setcode do + Facter::Util::Uptime.get_uptime_period(uptime, label) + end end end end diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index 4d4d936033..a57bf08ca4 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -5,20 +5,20 @@ module Facter::Util::IP # a given platform or set of platforms. REGEX_MAP = { :linux => { - :ipaddress => /inet addr:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, - :macaddress => /(?:ether|HWaddr)\s+(\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/, - :netmask => /Mask:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + :ipaddress => /inet addr:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, + :macaddress => /(?:ether|HWaddr)\s+(\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/, + :netmask => /Mask:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ }, - :bsd => { - :aliases => [:openbsd, :netbsd, :freebsd, :darwin], - :ipaddress => /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, - :macaddress => /(?:ether|lladdr)\s+(\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/, - :netmask => /netmask\s+0x(\w{8})/ + :bsd => { + :aliases => [:openbsd, :netbsd, :freebsd, :darwin], + :ipaddress => /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, + :macaddress => /(?:ether|lladdr)\s+(\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/, + :netmask => /netmask\s+0x(\w{8})/ }, :sunos => { - :addr => /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, - :macaddress => /(?:ether|lladdr)\s+(\w?\w:\w?\w:\w?\w:\w?\w:\w?\w:\w?\w)/, - :netmask => /netmask\s+(\w{8})/ + :addr => /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, + :macaddress => /(?:ether|lladdr)\s+(\w?\w:\w?\w:\w?\w:\w?\w:\w?\w:\w?\w)/, + :netmask => /netmask\s+(\w{8})/ } } diff --git a/lib/facter/util/manufacturer.rb b/lib/facter/util/manufacturer.rb index ff12e7e3f3..954637e1ff 100644 --- a/lib/facter/util/manufacturer.rb +++ b/lib/facter/util/manufacturer.rb @@ -2,45 +2,45 @@ # Support methods for manufacturer specific facts module Facter::Manufacturer - def self.dmi_find_system_info(name) - splitstr="Handle" - case Facter.value(:kernel) - when 'Linux' - return nil unless FileTest.exists?("/usr/sbin/dmidecode") - - output=%x{/usr/sbin/dmidecode 2>/dev/null} - when 'OpenBSD', 'FreeBSD' - return nil unless FileTest.exists?("/usr/local/sbin/dmidecode") - - output=%x{/usr/local/sbin/dmidecode 2>/dev/null} - when 'NetBSD' - return nil unless FileTest.exists?("/usr/pkg/sbin/dmidecode") + def self.dmi_find_system_info(name) + splitstr="Handle" + case Facter.value(:kernel) + when 'Linux' + return nil unless FileTest.exists?("/usr/sbin/dmidecode") - output=%x{/usr/pkg/sbin/dmidecode 2>/dev/null} - when 'SunOS' - return nil unless FileTest.exists?("/usr/sbin/smbios") - splitstr="ID SIZE TYPE" - output=%x{/usr/sbin/smbios 2>/dev/null} + output=%x{/usr/sbin/dmidecode 2>/dev/null} + when 'OpenBSD', 'FreeBSD' + return nil unless FileTest.exists?("/usr/local/sbin/dmidecode") - else - return - end - name.each_pair do |key,v| - v.each do |v2| - v2.each_pair do |value,facterkey| - output.split(splitstr).each do |line| - if line =~ /#{key}/ and ( line =~ /#{value} 0x\d+ \(([-\w].*)\)\n*./ or line =~ /#{value} ([-\w].*)\n*./ ) - result = $1 - Facter.add(facterkey) do - confine :kernel => [ :linux, :freebsd, :netbsd, :openbsd, :sunos ] - setcode do - result - end - end - end - end - end - end + output=%x{/usr/local/sbin/dmidecode 2>/dev/null} + when 'NetBSD' + return nil unless FileTest.exists?("/usr/pkg/sbin/dmidecode") + + output=%x{/usr/pkg/sbin/dmidecode 2>/dev/null} + when 'SunOS' + return nil unless FileTest.exists?("/usr/sbin/smbios") + splitstr="ID SIZE TYPE" + output=%x{/usr/sbin/smbios 2>/dev/null} + + else + return + end + name.each_pair do |key,v| + v.each do |v2| + v2.each_pair do |value,facterkey| + output.split(splitstr).each do |line| + if line =~ /#{key}/ and ( line =~ /#{value} 0x\d+ \(([-\w].*)\)\n*./ or line =~ /#{value} ([-\w].*)\n*./ ) + result = $1 + Facter.add(facterkey) do + confine :kernel => [ :linux, :freebsd, :netbsd, :openbsd, :sunos ] + setcode do + result + end + end + end + end + end + end end end end diff --git a/lib/facter/util/plist/generator.rb b/lib/facter/util/plist/generator.rb index 9e44b80147..6c0796b369 100644 --- a/lib/facter/util/plist/generator.rb +++ b/lib/facter/util/plist/generator.rb @@ -7,220 +7,222 @@ #++ # See Plist::Emit. module Plist - # === Create a plist - # You can dump an object to a plist in one of two ways: - # - # * Plist::Emit.dump(obj) - # * obj.to_plist - # * This requires that you mixin the Plist::Emit module, which is already done for +Array+ and +Hash+. - # - # The following Ruby classes are converted into native plist types: - # Array, Bignum, Date, DateTime, Fixnum, Float, Hash, Integer, String, Symbol, Time, true, false - # * +Array+ and +Hash+ are both recursive; their elements will be converted into plist nodes inside the and containers (respectively). - # * +IO+ (and its descendants) and +StringIO+ objects are read from and their contents placed in a element. - # * User classes may implement +to_plist_node+ to dictate how they should be serialized; otherwise the object will be passed to Marshal.dump and the result placed in a element. - # - # For detailed usage instructions, refer to USAGE[link:files/docs/USAGE.html] and the methods documented below. - module Emit - # Helper method for injecting into classes. Calls Plist::Emit.dump with +self+. - def to_plist(envelope = true) - return Plist::Emit.dump(self, envelope) - end - - # Helper method for injecting into classes. Calls Plist::Emit.save_plist with +self+. - def save_plist(filename) - Plist::Emit.save_plist(self, filename) - end - - # The following Ruby classes are converted into native plist types: - # Array, Bignum, Date, DateTime, Fixnum, Float, Hash, Integer, String, Symbol, Time + # === Create a plist + # You can dump an object to a plist in one of two ways: # - # Write us (via RubyForge) if you think another class can be coerced safely into one of the expected plist classes. + # * Plist::Emit.dump(obj) + # * obj.to_plist + # * This requires that you mixin the Plist::Emit module, which is already done for +Array+ and +Hash+. # - # +IO+ and +StringIO+ objects are encoded and placed in elements; other objects are Marshal.dump'ed unless they implement +to_plist_node+. + # The following Ruby classes are converted into native plist types: + # Array, Bignum, Date, DateTime, Fixnum, Float, Hash, Integer, String, Symbol, Time, true, false + # * +Array+ and +Hash+ are both recursive; their elements will be converted into plist nodes inside the and containers (respectively). + # * +IO+ (and its descendants) and +StringIO+ objects are read from and their contents placed in a element. + # * User classes may implement +to_plist_node+ to dictate how they should be serialized; otherwise the object will be passed to Marshal.dump and the result placed in a element. # - # The +envelope+ parameters dictates whether or not the resultant plist fragment is wrapped in the normal XML/plist header and footer. Set it to false if you only want the fragment. - def self.dump(obj, envelope = true) - output = plist_node(obj) + # For detailed usage instructions, refer to USAGE[link:files/docs/USAGE.html] and the methods documented below. + module Emit + # Helper method for injecting into classes. Calls Plist::Emit.dump with +self+. + def to_plist(envelope = true) + return Plist::Emit.dump(self, envelope) + end - output = wrap(output) if envelope + # Helper method for injecting into classes. Calls Plist::Emit.save_plist with +self+. + def save_plist(filename) + Plist::Emit.save_plist(self, filename) + end - return output - end + # The following Ruby classes are converted into native plist types: + # Array, Bignum, Date, DateTime, Fixnum, Float, Hash, Integer, String, Symbol, Time + # + # Write us (via RubyForge) if you think another class can be coerced safely into one of the expected plist classes. + # + # +IO+ and +StringIO+ objects are encoded and placed in elements; other objects are Marshal.dump'ed unless they implement +to_plist_node+. + # + # The +envelope+ parameters dictates whether or not the resultant plist fragment is wrapped in the normal XML/plist header and footer. Set it to false if you only want the fragment. + def self.dump(obj, envelope = true) + output = plist_node(obj) - # Writes the serialized object's plist to the specified filename. - def self.save_plist(obj, filename) - File.open(filename, 'wb') do |f| - f.write(obj.to_plist) - end - end + output = wrap(output) if envelope - private - def self.plist_node(element) - output = '' - - if element.respond_to? :to_plist_node - output << element.to_plist_node - else - case element - when Array - if element.empty? - output << "\n" - else - output << tag('array') { - element.collect {|e| plist_node(e)} - } - end - when Hash - if element.empty? - output << "\n" - else - inner_tags = [] - - element.keys.sort.each do |k| - v = element[k] - inner_tags << tag('key', CGI::escapeHTML(k.to_s)) - inner_tags << plist_node(v) + return output + end + + # Writes the serialized object's plist to the specified filename. + def self.save_plist(obj, filename) + File.open(filename, 'wb') do |f| + f.write(obj.to_plist) + end + end + + private + def self.plist_node(element) + output = '' + + if element.respond_to? :to_plist_node + output << element.to_plist_node + else + case element + when Array + if element.empty? + output << "\n" + else + output << tag('array') { + element.collect {|e| plist_node(e)} + } + end + when Hash + if element.empty? + output << "\n" + else + inner_tags = [] + + element.keys.sort.each do |k| + v = element[k] + inner_tags << tag('key', CGI::escapeHTML(k.to_s)) + inner_tags << plist_node(v) + end + + output << tag('dict') { + inner_tags + } + end + when true, false + output << "<#{element}/>\n" + when Time + output << tag('date', element.utc.strftime('%Y-%m-%dT%H:%M:%SZ')) + when Date # also catches DateTime + output << tag('date', element.strftime('%Y-%m-%dT%H:%M:%SZ')) + when String, Symbol, Fixnum, Bignum, Integer, Float + output << tag(element_type(element), CGI::escapeHTML(element.to_s)) + when IO, StringIO + element.rewind + contents = element.read + # note that apple plists are wrapped at a different length then + # what ruby's base64 wraps by default. + # I used #encode64 instead of #b64encode (which allows a length arg) + # because b64encode is b0rked and ignores the length arg. + data = "\n" + Base64::encode64(contents).gsub(/\s+/, '').scan(/.{1,68}/o) { data << $& << "\n" } + output << tag('data', data) + else + output << comment( 'The element below contains a Ruby object which has been serialized with Marshal.dump.' ) + data = "\n" + Base64::encode64(Marshal.dump(element)).gsub(/\s+/, '').scan(/.{1,68}/o) { data << $& << "\n" } + output << tag('data', data ) + end end - output << tag('dict') { - inner_tags - } - end - when true, false - output << "<#{element}/>\n" - when Time - output << tag('date', element.utc.strftime('%Y-%m-%dT%H:%M:%SZ')) - when Date # also catches DateTime - output << tag('date', element.strftime('%Y-%m-%dT%H:%M:%SZ')) - when String, Symbol, Fixnum, Bignum, Integer, Float - output << tag(element_type(element), CGI::escapeHTML(element.to_s)) - when IO, StringIO - element.rewind - contents = element.read - # note that apple plists are wrapped at a different length then - # what ruby's base64 wraps by default. - # I used #encode64 instead of #b64encode (which allows a length arg) - # because b64encode is b0rked and ignores the length arg. - data = "\n" - Base64::encode64(contents).gsub(/\s+/, '').scan(/.{1,68}/o) { data << $& << "\n" } - output << tag('data', data) - else - output << comment( 'The element below contains a Ruby object which has been serialized with Marshal.dump.' ) - data = "\n" - Base64::encode64(Marshal.dump(element)).gsub(/\s+/, '').scan(/.{1,68}/o) { data << $& << "\n" } - output << tag('data', data ) + return output end - end - return output - end + def self.comment(content) + return "\n" + end - def self.comment(content) - return "\n" - end + def self.tag(type, contents = '', &block) + out = nil + + if block_given? + out = IndentedString.new + out << "<#{type}>" + out.raise_indent - def self.tag(type, contents = '', &block) - out = nil + out << block.call - if block_given? - out = IndentedString.new - out << "<#{type}>" - out.raise_indent + out.lower_indent + out << "" + else + out = "<#{type}>#{contents.to_s}\n" + end - out << block.call + return out.to_s + end - out.lower_indent - out << "" - else - out = "<#{type}>#{contents.to_s}\n" - end + def self.wrap(contents) + output = '' - return out.to_s - end + output << '' + "\n" + output << '' + "\n" + output << '' + "\n" + + output << contents + + output << '' + "\n" - def self.wrap(contents) - output = '' + return output + end - output << '' + "\n" - output << '' + "\n" - output << '' + "\n" + def self.element_type(item) + return case item + when String, Symbol; 'string' + when Fixnum, Bignum, Integer; 'integer' + when Float; 'real' + else + raise "Don't know about this data type... something must be wrong!" + end + end - output << contents + private - output << '' + "\n" + class IndentedString #:nodoc: + attr_accessor :indent_string - return output - end + @@indent_level = 0 - def self.element_type(item) - return case item - when String, Symbol; 'string' - when Fixnum, Bignum, Integer; 'integer' - when Float; 'real' - else - raise "Don't know about this data type... something must be wrong!" - end - end - private - class IndentedString #:nodoc: - attr_accessor :indent_string - - @@indent_level = 0 - - def initialize(str = "\t") - @indent_string = str - @contents = '' - end - - def to_s - return @contents - end - - def raise_indent - @@indent_level += 1 - end - - def lower_indent - @@indent_level -= 1 if @@indent_level > 0 - end - - def <<(val) - if val.is_a? Array - val.each do |f| - self << f - end - else - # if it's already indented, don't bother indenting further - unless val =~ /\A#{@indent_string}/ - indent = @indent_string * @@indent_level - - @contents << val.gsub(/^/, indent) - else - @contents << val - end - - # it already has a newline, don't add another - @contents << "\n" unless val =~ /\n$/ + def initialize(str = "\t") + @indent_string = str + @contents = '' + end + + def to_s + return @contents + end + + def raise_indent + @@indent_level += 1 + end + + def lower_indent + @@indent_level -= 1 if @@indent_level > 0 + end + + def <<(val) + if val.is_a? Array + val.each do |f| + self << f + end + else + # if it's already indented, don't bother indenting further + unless val =~ /\A#{@indent_string}/ + indent = @indent_string * @@indent_level + + @contents << val.gsub(/^/, indent) + else + @contents << val + end + + # it already has a newline, don't add another + @contents << "\n" unless val =~ /\n$/ + end + end end - end end - end end # we need to add this so sorting hash keys works properly class Symbol #:nodoc: - def <=> (other) - self.to_s <=> other.to_s - end + def <=> (other) + self.to_s <=> other.to_s + end end class Array #:nodoc: - include Plist::Emit + include Plist::Emit end class Hash #:nodoc: - include Plist::Emit + include Plist::Emit end # $Id: generator.rb 1781 2006-10-16 01:01:35Z luke $ diff --git a/lib/facter/util/plist/parser.rb b/lib/facter/util/plist/parser.rb index 24f791ff74..48e10343ec 100644 --- a/lib/facter/util/plist/parser.rb +++ b/lib/facter/util/plist/parser.rb @@ -21,207 +21,206 @@ module Plist # If you encounter such an error, or if you have a Date element which # can't be parsed into a Time object, please send your plist file to # plist@hexane.org so that I can implement the proper support. - def Plist::parse_xml( filename_or_xml ) - listener = Listener.new - #parser = REXML::Parsers::StreamParser.new(File.new(filename), listener) - parser = StreamParser.new(filename_or_xml, listener) - parser.parse - listener.result - end - - class Listener - #include REXML::StreamListener - - attr_accessor :result, :open - - def initialize - @result = nil - @open = Array.new + def Plist::parse_xml( filename_or_xml ) + listener = Listener.new + #parser = REXML::Parsers::StreamParser.new(File.new(filename), listener) + parser = StreamParser.new(filename_or_xml, listener) + parser.parse + listener.result end + class Listener + #include REXML::StreamListener - def tag_start(name, attributes) - @open.push PTag::mappings[name].new - end + attr_accessor :result, :open - def text( contents ) - @open.last.text = contents if @open.last - end + def initialize + @result = nil + @open = Array.new + end - def tag_end(name) - last = @open.pop - if @open.empty? - @result = last.to_ruby - else - @open.last.children.push last - end - end - end - class StreamParser - def initialize( filename_or_xml, listener ) - @filename_or_xml = filename_or_xml - @listener = listener - end + def tag_start(name, attributes) + @open.push PTag::mappings[name].new + end - TEXT = /([^<]+)/ - XMLDECL_PATTERN = /<\?xml\s+(.*?)\?>*/um - DOCTYPE_PATTERN = /\s*)/um - COMMENT_START = /\A/um - - - def parse - plist_tags = PTag::mappings.keys.join('|') - start_tag = /<(#{plist_tags})([^>]*)>/i - end_tag = /<\/(#{plist_tags})[^>]*>/i - - require 'strscan' - - contents = ( - if (File.exists? @filename_or_xml) - File.open(@filename_or_xml) {|f| f.read} - else - @filename_or_xml - end - ) - - @scanner = StringScanner.new( contents ) - until @scanner.eos? - if @scanner.scan(COMMENT_START) - @scanner.scan(COMMENT_END) - elsif @scanner.scan(XMLDECL_PATTERN) - elsif @scanner.scan(DOCTYPE_PATTERN) - elsif @scanner.scan(start_tag) - @listener.tag_start(@scanner[1], nil) - if (@scanner[2] =~ /\/$/) - @listener.tag_end(@scanner[1]) - end - elsif @scanner.scan(TEXT) - @listener.text(@scanner[1]) - elsif @scanner.scan(end_tag) - @listener.tag_end(@scanner[1]) - else - raise "Unimplemented element" - end - end - end - end + def text( contents ) + @open.last.text = contents if @open.last + end - class PTag - @@mappings = { } - def PTag::mappings - @@mappings + def tag_end(name) + last = @open.pop + if @open.empty? + @result = last.to_ruby + else + @open.last.children.push last + end + end end - def PTag::inherited( sub_class ) - key = sub_class.to_s.downcase - key.gsub!(/^plist::/, '' ) - key.gsub!(/^p/, '') unless key == "plist" + class StreamParser + def initialize( filename_or_xml, listener ) + @filename_or_xml = filename_or_xml + @listener = listener + end - @@mappings[key] = sub_class + TEXT = /([^<]+)/ + XMLDECL_PATTERN = /<\?xml\s+(.*?)\?>*/um + DOCTYPE_PATTERN = /\s*)/um + COMMENT_START = /\A/um + + def parse + plist_tags = PTag::mappings.keys.join('|') + start_tag = /<(#{plist_tags})([^>]*)>/i + end_tag = /<\/(#{plist_tags})[^>]*>/i + + require 'strscan' + + contents = ( + if (File.exists? @filename_or_xml) + File.open(@filename_or_xml) {|f| f.read} + else + @filename_or_xml + end + ) + + @scanner = StringScanner.new( contents ) + until @scanner.eos? + if @scanner.scan(COMMENT_START) + @scanner.scan(COMMENT_END) + elsif @scanner.scan(XMLDECL_PATTERN) + elsif @scanner.scan(DOCTYPE_PATTERN) + elsif @scanner.scan(start_tag) + @listener.tag_start(@scanner[1], nil) + if (@scanner[2] =~ /\/$/) + @listener.tag_end(@scanner[1]) + end + elsif @scanner.scan(TEXT) + @listener.text(@scanner[1]) + elsif @scanner.scan(end_tag) + @listener.tag_end(@scanner[1]) + else + raise "Unimplemented element" + end + end + end end - attr_accessor :text, :children - def initialize - @children = Array.new - end + class PTag + @@mappings = { } + def PTag::mappings + @@mappings + end - def to_ruby - raise "Unimplemented: " + self.class.to_s + "#to_ruby on #{self.inspect}" + def PTag::inherited( sub_class ) + key = sub_class.to_s.downcase + key.gsub!(/^plist::/, '' ) + key.gsub!(/^p/, '') unless key == "plist" + + @@mappings[key] = sub_class + end + + attr_accessor :text, :children + def initialize + @children = Array.new + end + + def to_ruby + raise "Unimplemented: " + self.class.to_s + "#to_ruby on #{self.inspect}" + end end - end - class PList < PTag - def to_ruby - children.first.to_ruby if children.first + class PList < PTag + def to_ruby + children.first.to_ruby if children.first + end end - end - class PDict < PTag - def to_ruby - dict = Hash.new - key = nil + class PDict < PTag + def to_ruby + dict = Hash.new + key = nil - children.each do |c| - if key.nil? - key = c.to_ruby - else - dict[key] = c.to_ruby - key = nil - end - end + children.each do |c| + if key.nil? + key = c.to_ruby + else + dict[key] = c.to_ruby + key = nil + end + end - dict + dict + end end - end - class PKey < PTag - def to_ruby - CGI::unescapeHTML(text || '') + class PKey < PTag + def to_ruby + CGI::unescapeHTML(text || '') + end end - end - class PString < PTag - def to_ruby - CGI::unescapeHTML(text || '') + class PString < PTag + def to_ruby + CGI::unescapeHTML(text || '') + end end - end - class PArray < PTag - def to_ruby - children.collect do |c| - c.to_ruby - end + class PArray < PTag + def to_ruby + children.collect do |c| + c.to_ruby + end + end end - end - class PInteger < PTag - def to_ruby - text.to_i + class PInteger < PTag + def to_ruby + text.to_i + end end - end - class PTrue < PTag - def to_ruby - true + class PTrue < PTag + def to_ruby + true + end end - end - class PFalse < PTag - def to_ruby - false + class PFalse < PTag + def to_ruby + false + end end - end - class PReal < PTag - def to_ruby - text.to_f + class PReal < PTag + def to_ruby + text.to_f + end end - end - require 'date' - class PDate < PTag - def to_ruby - DateTime.parse(text) + require 'date' + class PDate < PTag + def to_ruby + DateTime.parse(text) + end end - end - - require 'base64' - class PData < PTag - def to_ruby - data = Base64.decode64(text.gsub(/\s+/, '')) - - begin - return Marshal.load(data) - rescue Exception => e - io = StringIO.new - io.write data - io.rewind - return io - end + + require 'base64' + class PData < PTag + def to_ruby + data = Base64.decode64(text.gsub(/\s+/, '')) + + begin + return Marshal.load(data) + rescue Exception => e + io = StringIO.new + io.write data + io.rewind + return io + end + end end - end end # $Id: parser.rb 1781 2006-10-16 01:01:35Z luke $ diff --git a/lib/facter/util/uptime.rb b/lib/facter/util/uptime.rb index 76c44648e4..c1e339b942 100644 --- a/lib/facter/util/uptime.rb +++ b/lib/facter/util/uptime.rb @@ -1,16 +1,15 @@ # A module to gather uptime facts # module Facter::Util::Uptime - def self.get_uptime_simple time = Facter::Util::Resolution.exec('uptime') - if time =~ /up\s*(\d+\s\w+)/ - $1 - elsif time =~ /up\s*(\d+:\d+)/ - $1 + " hours" - else - "unknown" - end + if time =~ /up\s*(\d+\s\w+)/ + $1 + elsif time =~ /up\s*(\d+:\d+)/ + $1 + " hours" + else + "unknown" + end end def self.get_uptime @@ -21,7 +20,6 @@ def self.get_uptime end def self.get_uptime_period(seconds, label) - case label when 'days' value = seconds / 86400 @@ -30,7 +28,5 @@ def self.get_uptime_period(seconds, label) when 'seconds' seconds end - end - end diff --git a/tasks/rake/redlabpackage.rb b/tasks/rake/redlabpackage.rb index 2de8005b63..1df9c41131 100644 --- a/tasks/rake/redlabpackage.rb +++ b/tasks/rake/redlabpackage.rb @@ -17,13 +17,13 @@ # of date. # # ["package_dir/name-version.tgz"] -# Create a gzipped tar package (if need_tar is true). +# Create a gzipped tar package (if need_tar is true). # # ["package_dir/name-version.tar.gz"] -# Create a gzipped tar package (if need_tar_gz is true). +# Create a gzipped tar package (if need_tar_gz is true). # # ["package_dir/name-version.tar.bz2"] -# Create a bzip2'd tar package (if need_tar_bz2 is true). +# Create a bzip2'd tar package (if need_tar_bz2 is true). # # ["package_dir/name-version.zip"] # Create a zip package archive (if need_zip is true). @@ -38,8 +38,8 @@ class Rake::RedLabPackageTask < Rake::TaskLib # The different directory types we can manage. DIRTYPES = { - :bindir => :bins, - :sbindir => :sbins, + :bindir => :bins, + :sbindir => :sbins, :sitelibdir => :rubylibs } @@ -103,7 +103,7 @@ class Rake::RedLabPackageTask < Rake::TaskLib # The URL for the package. attr_accessor :url - + # The source for the package. attr_accessor :source @@ -139,7 +139,7 @@ def files(dirname) end end - # Create a Package Task with the given name and version. + # Create a Package Task with the given name and version. def initialize(name=nil, version=nil) # Theoretically, one could eventually add directory types here. @dirtypes = DIRTYPES.dup @@ -226,7 +226,7 @@ def mkcopytasks safe_ln(sourcefile, destfile) end end - + # If we've set the destdir, then list it as a prereq. if destdir file destfile => destdir @@ -236,7 +236,7 @@ def mkcopytasks # And create a task for each one task tname => reqs - + # And then mark our task as a prereq tasks << tname end diff --git a/tasks/rake/reductive.rb b/tasks/rake/reductive.rb index 2fbd2b4d9b..ea7072b9a4 100644 --- a/tasks/rake/reductive.rb +++ b/tasks/rake/reductive.rb @@ -269,13 +269,13 @@ def mktaskrelease :package, :publish ] do - - announce + + announce announce "**************************************************************" announce "* Release #{@version} Complete." announce "* Packages ready to upload." announce "**************************************************************" - announce + announce end end @@ -283,12 +283,12 @@ def mktaskrelease def mktaskprerelease # Validate that everything is ready to go for a release. task :prerelease do - announce + announce announce "**************************************************************" announce "* Making Release #{@version}" announce "* (current version #{self.currentversion})" announce "**************************************************************" - announce + announce # Is a release number supplied? unless ENV['REL'] @@ -467,7 +467,7 @@ def mktaskdefault end desc "List all ruby files" - task :rubyfiles do + task :rubyfiles do puts Dir['**/*.rb'].reject { |fn| fn =~ /^pkg/ } puts Dir['**/bin/*'].reject { |fn| fn =~ /svn|(~$)|(\.rb$)/ } end From c6c30a49a7f8f240b8bf88c09cbb80199b798cd3 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sat, 28 Feb 2009 09:43:36 +1100 Subject: [PATCH 0332/3753] Fixed #2035 - Missing brace for OSX preflight --- CHANGELOG | 2 ++ conf/osx/preflight | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 51b2d1c3a0..b56bbc508f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ 1.5.x: + Fixed #2035 - Missing brace for OSX preflight + Added EC2 facts Fixed #2032 - file.open hanging on /proc/uptime on some platform diff --git a/conf/osx/preflight b/conf/osx/preflight index 9b2c07e2f9..98251bfced 100755 --- a/conf/osx/preflight +++ b/conf/osx/preflight @@ -9,4 +9,4 @@ # when being installed to volumes other than the current OS. /bin/rm -Rf "${3}{SITELIBDIR}/facter" -/bin/rm -Rf "${3}{SITELIBDIR/facter.rb" +/bin/rm -Rf "${3}{SITELIBDIR}/facter.rb" From 7a30a6a3ecadab80a4547700d60f9e9b43c2c4b6 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sat, 28 Feb 2009 10:00:09 +1100 Subject: [PATCH 0333/3753] Fixed CHANGELOG --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index b56bbc508f..9532c8cb9e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,4 @@ -1.5.x: +1.6.x: Fixed #2035 - Missing brace for OSX preflight Added EC2 facts From 9722e1fdb2ceee08e1cbd4098cb2673980469d06 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sun, 1 Mar 2009 08:53:28 +1100 Subject: [PATCH 0334/3753] Fixed #2003 - Added is_virtual fact --- CHANGELOG | 2 ++ lib/facter/virtual.rb | 13 +++++++++++++ 2 files changed, 15 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 9532c8cb9e..502d09a588 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ 1.6.x: + Fixed #2003 - Added is_virtual fact + Fixed #2035 - Missing brace for OSX preflight Added EC2 facts diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index fdb340faf7..ce790c3a53 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -73,3 +73,16 @@ result end end + +Facter.add("is_virtual") do + confine :kernel => %w{Linux FreeBSD OpenBSD SunOS} + + setcode do + case Facter.value(:virtual) + when "xenu", "openvzve", "vmware" + true + else + false + end + end +end From 77fa46babc4150eb90f3fe3f9ea0e84ed8d0c9e2 Mon Sep 17 00:00:00 2001 From: duritong Date: Sun, 22 Feb 2009 01:43:48 +0100 Subject: [PATCH 0335/3753] Fix virtual fact if xen but /proc/virtual present I found a xenu system where /proc/virtual was present, so facter always reported it as vserver_host. This fix will set facter only to vserver_host if the box is still seen as physical, hence no other virtual technology have been detected. --- lib/facter/virtual.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index ce790c3a53..8150718290 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -66,7 +66,7 @@ end end - if FileTest.directory?('/proc/virtual') + if FileTest.directory?('/proc/virtual') && result=="physical" result = "vserver_host" end From ba2e470ab5f6ee767c1b0d3016627e3838006e44 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sun, 1 Mar 2009 09:09:16 +1100 Subject: [PATCH 0336/3753] Fixed #2040 - Facter should provide a macosx_productversion_major fact --- CHANGELOG | 2 ++ lib/facter/util/macosx.rb | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 502d09a588..e5a5d6c3dc 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ 1.6.x: + Fixed #2040 - Facter should provide a macosx_productversion_major fact + Fixed #2003 - Added is_virtual fact Fixed #2035 - Missing brace for OSX preflight diff --git a/lib/facter/util/macosx.rb b/lib/facter/util/macosx.rb index 6e7986ee7f..f5f83f3798 100644 --- a/lib/facter/util/macosx.rb +++ b/lib/facter/util/macosx.rb @@ -58,6 +58,11 @@ def self.sw_vers [ "productName", "productVersion", "buildVersion" ].each do |option| ver["macosx_#{option}"] = %x{sw_vers -#{option}}.strip end + productversion = ver["macosx_productVersion"] + if not productversion.nil? + ver["macosx_productversion_major"] = productversion.scan(/(\d+\.\d+)/)[0][0] + ver["macosx_productversion_minor"] = productversion.scan(/(\d+)\.(\d+)\.(\d+)/)[0].last + end ver end end From 75db918c37a9fef36c829105d1f8a99ff8bcf751 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sun, 1 Mar 2009 17:55:17 +1100 Subject: [PATCH 0337/3753] Fixed lib install permissions --- CHANGELOG | 2 ++ install.rb | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index e5a5d6c3dc..da7478df91 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ 1.6.x: + Fixed lib install permissions + Fixed #2040 - Facter should provide a macosx_productversion_major fact Fixed #2003 - Added is_virtual fact diff --git a/install.rb b/install.rb index d38aedea0b..3be89524e2 100755 --- a/install.rb +++ b/install.rb @@ -93,7 +93,7 @@ def do_libs(libs, strip = 'lib/') op = File.dirname(olf) File.makedirs(op, true) File.chmod(0755, op) - File.install(lf, olf, 0755, true) + File.install(lf, olf, 0644, true) end end From c754949993e22095912930211d3b5411e34bf1dc Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Wed, 4 Mar 2009 19:41:05 +1100 Subject: [PATCH 0338/3753] Fix for rake task for reductive-build library --- tasks/rake/reductive.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tasks/rake/reductive.rb b/tasks/rake/reductive.rb index ea7072b9a4..0896393335 100644 --- a/tasks/rake/reductive.rb +++ b/tasks/rake/reductive.rb @@ -455,10 +455,10 @@ def mktaskdefault desc "Run all unit tests." task :alltests do - if FileTest.exists?("test/Rakefile") - sh %{cd test; rake} + if FileTest.exists?("spec/Rakefile") + sh %{cd spec; rake} else - Dir.chdir("test") do + Dir.chdir("spec") do Dir.entries(".").find_all { |f| f =~ /\.rb/ }.each do |f| sh %{ruby #{f}} end From 9376e5bf883dc9460a4e19c7952c7c996f43e0e3 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sat, 7 Mar 2009 02:47:52 +1100 Subject: [PATCH 0339/3753] Fixed #2044 - virtual fact thread fix --- CHANGELOG | 2 ++ lib/facter/virtual.rb | 18 +++++++++++------- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index da7478df91..b0336aabab 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ 1.6.x: + Fixed #2044 - fixed virtual fact + Fixed lib install permissions Fixed #2040 - Facter should provide a macosx_productversion_major fact diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 8150718290..2c925325f9 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -4,6 +4,8 @@ result = "physical" setcode do + require 'thread' + if FileTest.exists?("/proc/user_beancounters") # openvz. can be hardware node or virtual environment # read the init process' status file, it has laxer permissions @@ -16,13 +18,15 @@ end end - if FileTest.exists?("/proc/xen/capabilities") && FileTest.readable?("/proc/xen/capabilities") - txt = File.read("/proc/xen/capabilities") - if txt =~ /control_d/i - result = "xen0" - else - result = "xenu" - end + Thread::exclusive do + if FileTest.exists?("/proc/xen/capabilities") && FileTest.readable?("/proc/xen/capabilities") + txt = File.read("/proc/xen/capabilities") + if txt =~ /control_d/i + result = "xen0" + else + result = "xenu" + end + end end if result == "physical" From 5d94f7fd5fde27a2cdffc918db68b2549231e23e Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sat, 7 Mar 2009 10:50:57 +1100 Subject: [PATCH 0340/3753] Fixed #2055 - SunoS Interface error --- CHANGELOG | 2 ++ lib/facter/util/ip.rb | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index b0336aabab..41d309c859 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ 1.6.x: + Fixed #2055 - SunoS Interface error + Fixed #2044 - fixed virtual fact Fixed lib install permissions diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index a57bf08ca4..5469f73ea7 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -16,7 +16,7 @@ module Facter::Util::IP :netmask => /netmask\s+0x(\w{8})/ }, :sunos => { - :addr => /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, + :ipadddress => /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, :macaddress => /(?:ether|lladdr)\s+(\w?\w:\w?\w:\w?\w:\w?\w:\w?\w:\w?\w)/, :netmask => /netmask\s+(\w{8})/ } From bee55c4ab1746ea78de8defaff29663d7ab15604 Mon Sep 17 00:00:00 2001 From: Todd Zullinger Date: Mon, 9 Mar 2009 02:09:12 -0400 Subject: [PATCH 0341/3753] Consolidate operatingsystemrelease for CentOS, Fedora, oel, ovs, and RedHat These operating systems all use a similar format for the release file. --- lib/facter/operatingsystemrelease.rb | 62 +++++----------------------- 1 file changed, 11 insertions(+), 51 deletions(-) diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index 25a226d3d8..2f76817f11 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -1,21 +1,17 @@ Facter.add(:operatingsystemrelease) do - confine :operatingsystem => :fedora + confine :operatingsystem => %w{CentOS Fedora oel ovs RedHat} setcode do - File::open("/etc/fedora-release", "r") do |f| - line = f.readline.chomp - if line =~ /\(Rawhide\)$/ - "Rawhide" - elsif line =~ /release (\d+)/ - $1 - end + case Facter.value(:operatingsystem) + when "CentOS", "RedHat" + releasefile = "/etc/redhat-release" + when "Fedora" + releasefile = "/etc/fedora-release" + when "oel" + releasefile = "/etc/enterprise-release" + when "ovs" + releasefile = "/etc/ovs-release" end - end -end - -Facter.add(:operatingsystemrelease) do - confine :operatingsystem => %w{RedHat} - setcode do - File::open("/etc/redhat-release", "r") do |f| + File::open(releasefile, "r") do |f| line = f.readline.chomp if line =~ /\(Rawhide\)$/ "Rawhide" @@ -26,42 +22,6 @@ end end -Facter.add(:operatingsystemrelease) do - confine :operatingsystem => :oel - setcode do - File::open("/etc/enterprise-release", "r") do |f| - line = f.readline.chomp - if line =~ /release (\d+)/ - $1 - end - end - end -end - -Facter.add(:operatingsystemrelease) do - confine :operatingsystem => :ovs - setcode do - File::open("/etc/ovs-release", "r") do |f| - line = f.readline.chomp - if line =~ /release (\d+)/ - $1 - end - end - end -end - -Facter.add(:operatingsystemrelease) do - confine :operatingsystem => %w{CentOS} - setcode do - centos_release = Facter::Util::Resolution.exec("sed -r -e 's/CentOS release //' -e 's/ \((Branch|Final)\)//' /etc/redhat-release") - if centos_release =~ /5/ - release = Facter::Util::Resolution.exec('rpm -q --qf \'%{VERSION}.%{RELEASE}\' centos-release | cut -d. -f1,2') - else - release = centos_release - end - end -end - Facter.add(:operatingsystemrelease) do confine :operatingsystem => %w{Debian} setcode do From 82d97e25f14f0b92187b6ac695119ac5076c0810 Mon Sep 17 00:00:00 2001 From: Todd Zullinger Date: Mon, 9 Mar 2009 02:12:32 -0400 Subject: [PATCH 0342/3753] Fix operatingsystemrelease on Red Hat based distros This allows operatingsystemrelease to properly determine the release for various Red Hat based distros, including the point portion of the release. --- lib/facter/operatingsystemrelease.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index 2f76817f11..18199bd114 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -15,7 +15,7 @@ line = f.readline.chomp if line =~ /\(Rawhide\)$/ "Rawhide" - elsif line =~ /release (\d+)/ + elsif line =~ /release (\d[\d.]*)/ $1 end end From 8def362b73ffc218b68f6953bdcc3c8540caf5dd Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Tue, 10 Mar 2009 23:05:19 +1100 Subject: [PATCH 0343/3753] Fixed #2063 - added kernelmajversion fact --- CHANGELOG | 2 ++ lib/facter/kernelmajversion.rb | 5 +++++ 2 files changed, 7 insertions(+) create mode 100644 lib/facter/kernelmajversion.rb diff --git a/CHANGELOG b/CHANGELOG index 41d309c859..952f88d206 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ 1.6.x: + Fixed #2063 - added kernelmajversion fact + Fixed #2055 - SunoS Interface error Fixed #2044 - fixed virtual fact diff --git a/lib/facter/kernelmajversion.rb b/lib/facter/kernelmajversion.rb new file mode 100644 index 0000000000..32fd7aabbe --- /dev/null +++ b/lib/facter/kernelmajversion.rb @@ -0,0 +1,5 @@ +Facter.add("kernelmajversion") do + setcode do + Facter.value(:kernelversion).split('.')[0..1].join('.') + end +end From add6d4763b82ff45c7582f15fab84867ac2ef1a3 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Tue, 17 Mar 2009 20:51:29 +1100 Subject: [PATCH 0344/3753] Fixed #2081 - Fixed interfaces fact for vlan subinterfaces --- CHANGELOG | 2 ++ lib/facter/util/ip.rb | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 952f88d206..b6dababfc8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ 1.6.x: + Fixed #2081 - Fixed interfaces fact for vlan subinterfaces + Fixed #2063 - added kernelmajversion fact Fixed #2055 - SunoS Interface error diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index 5469f73ea7..55b5e92fbb 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -52,7 +52,7 @@ def self.get_interfaces # We get lots of warnings on platforms that don't get an output # made. if output - int = output.scan(/^\w+[.:]?\d+/) + int = output.scan(/^\w+[.:]?\d+[.:]?\d*/) else [] end From 89a3aa8097c2e4690c835905dec49df2bc333b30 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Tue, 17 Mar 2009 23:45:50 +1100 Subject: [PATCH 0345/3753] Fix to stdout in resolution.rb --- lib/facter/util/resolution.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 0dfb3b274d..04d5e47376 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -16,7 +16,7 @@ def self.have_which if Config::CONFIG['host_os'] =~ /mswin/ @have_which = false else - %x{which which 2>/dev/null} + %x{which which >/dev/null 2>&1} @have_which = ($? == 0) end end @@ -33,7 +33,7 @@ def self.exec(code, interpreter = "/bin/sh") if binary !~ /^\// path = %x{which #{binary} 2>/dev/null}.chomp # we don't have the binary necessary - return nil if path == "" + return nil if path == "" or path.match(/Command not found\./) else path = binary end From a6d6ba5454a805cf95a7aa4def0c12afc180b958 Mon Sep 17 00:00:00 2001 From: Peter Meier Date: Tue, 10 Mar 2009 21:33:29 +0100 Subject: [PATCH 0346/3753] Use resultion.exec util instead of which checks Use rather our util to exec commands than implementing our own hack. --- lib/facter/virtual.rb | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 2c925325f9..97d7cba235 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -30,25 +30,22 @@ end if result == "physical" - path = %x{which lspci 2> /dev/null}.chomp - if path !~ /no lspci/ - output = %x{#{path}} + output = Facter::Util::Resolution.exec('lspci') + if not output.nil? output.each do |p| # --- look for the vmware video card to determine if it is virtual => vmware. # --- 00:0f.0 VGA compatible controller: VMware Inc [VMware SVGA II] PCI Display Adapter result = "vmware" if p =~ /VM[wW]are/ end else - path = %x{which dmidecode 2> /dev/null}.chomp - if path !~ /no dmidecode/ - output = %x{#{path}} + output = Facter::Util::Resolution.exec('dmidecode') + if not output.nil? output.each do |pd| result = "vmware" if pd =~ /VMware|Parallels/ end else - path = %x{which prtdiag 2> /dev/null}.chomp - if path !~ /no prtdiag/ - output = %x{#{path}} + output = Facter::Util::Resolution.exec('prtdiag') + if not output.nil? output.each do |pd| result = "vmware" if pd =~ /VMware|Parallels/ end @@ -62,9 +59,8 @@ result = "vmware_server" end - mountexists = system "which mount > /dev/null 2>&1" - if $?.exitstatus == 0 - output = %x{mount} + output = Facter::Util::Resolution.exec('mount') + if not output.nil? output.each do |p| result = "vserver" if p =~ /\/dev\/hdv1/ end From 1288b26e35a2a9e126d4ae260f377d854b3c3848 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Tue, 17 Mar 2009 23:52:11 +1100 Subject: [PATCH 0347/3753] Fixed #2080 - IPAddress resolutions should be reordered --- CHANGELOG | 6 +++ lib/facter/ipaddress.rb | 82 ++++++++++++++++++++--------------------- 2 files changed, 47 insertions(+), 41 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index b6dababfc8..b0f9931fc7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,10 @@ 1.6.x: + Fixed #2080 - IPAddress resolutions should be reordered + + Fixed #2078 - ip.rb errors command not found + + Fixed #2058 - Redirecting stderr doesn't work on all systems + Fixed #2081 - Fixed interfaces fact for vlan subinterfaces Fixed #2063 - added kernelmajversion fact diff --git a/lib/facter/ipaddress.rb b/lib/facter/ipaddress.rb index d7496f04ba..4c0bfe46ff 100644 --- a/lib/facter/ipaddress.rb +++ b/lib/facter/ipaddress.rb @@ -1,44 +1,3 @@ -Facter.add(:ipaddress, :ldapname => "iphostnumber", :timeout => 2) do - setcode do - require 'resolv' - - begin - if hostname = Facter.value(:hostname) - ip = Resolv.getaddress(hostname) - unless ip == "127.0.0.1" - ip - end - else - nil - end - rescue Resolv::ResolvError - nil - rescue NoMethodError # i think this is a bug in resolv.rb? - nil - end - end -end - -Facter.add(:ipaddress, :timeout => 2) do - setcode do - if hostname = Facter.value(:hostname) - # we need Hostname to exist for this to work - host = nil - if host = Facter::Util::Resolution.exec("host #{hostname}") - list = host.chomp.split(/\s/) - if defined? list[-1] and - list[-1] =~ /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ - list[-1] - end - else - nil - end - else - nil - end - end -end - Facter.add(:ipaddress) do confine :kernel => :linux setcode do @@ -168,3 +127,44 @@ ip end end + +Facter.add(:ipaddress, :ldapname => "iphostnumber", :timeout => 2) do + setcode do + require 'resolv' + + begin + if hostname = Facter.value(:hostname) + ip = Resolv.getaddress(hostname) + unless ip == "127.0.0.1" + ip + end + else + nil + end + rescue Resolv::ResolvError + nil + rescue NoMethodError # i think this is a bug in resolv.rb? + nil + end + end +end + +Facter.add(:ipaddress, :timeout => 2) do + setcode do + if hostname = Facter.value(:hostname) + # we need Hostname to exist for this to work + host = nil + if host = Facter::Util::Resolution.exec("host #{hostname}") + list = host.chomp.split(/\s/) + if defined? list[-1] and + list[-1] =~ /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ + list[-1] + end + else + nil + end + else + nil + end + end +end From 7a819454febdf34f4384e1bc64c5b2599df4bd38 Mon Sep 17 00:00:00 2001 From: Peter Meier Date: Tue, 7 Apr 2009 18:45:21 +0200 Subject: [PATCH 0348/3753] correctly compare values - fixes #2021 this ensures we can compare all kind of objects and not only instances of strings. It compares strings in a case-insensitive manner and converts symbols to strings. introducing this behavior required that we introduce a convert util method, to ensure that we convert the value correctly. Introduced this method in other places as well. This behavior change requires that we drop one test, which have become anyway deprecated. --- lib/facter/util/confine.rb | 24 ++++++------ lib/facter/util/values.rb | 14 +++++++ spec/unit/util/confine.rb | 75 +++++++++++++++++++++++++++++++++++--- 3 files changed, 96 insertions(+), 17 deletions(-) create mode 100644 lib/facter/util/values.rb diff --git a/lib/facter/util/confine.rb b/lib/facter/util/confine.rb index a430bbe0eb..4cbb32cc20 100644 --- a/lib/facter/util/confine.rb +++ b/lib/facter/util/confine.rb @@ -1,22 +1,20 @@ # A restricting tag for fact resolution mechanisms. The tag must be true # for the resolution mechanism to be suitable. + +require 'facter/util/values' + class Facter::Util::Confine attr_accessor :fact, :values + include Facter::Util::Values + # Add the restriction. Requires the fact name, an operator, and the value # we're comparing to. def initialize(fact, *values) raise ArgumentError, "The fact name must be provided" unless fact raise ArgumentError, "One or more values must be provided" if values.empty? - fact = fact.to_s if fact.is_a? Symbol @fact = fact - @values = values.collect do |value| - if value.is_a? String - value - else - value.to_s - end - end + @values = values end def to_s @@ -29,13 +27,15 @@ def true? Facter.debug "No fact for %s" % @fact return false end - value = fact.value + value = convert(fact.value) return false if value.nil? - @values.each { |v| - return true if value.downcase == v.downcase - } + @values.each do |v| + v = convert(v) + next unless v.class == value.class + return true if value == v + end return false end end diff --git a/lib/facter/util/values.rb b/lib/facter/util/values.rb new file mode 100644 index 0000000000..ebc7614904 --- /dev/null +++ b/lib/facter/util/values.rb @@ -0,0 +1,14 @@ +# A util module for facter containing helper methods +module Facter + module Util + module Values + module_function + + def convert(value) + value = value.to_s if value.is_a?(Symbol) + value = value.downcase if value.is_a?(String) + value + end + end + end +end diff --git a/spec/unit/util/confine.rb b/spec/unit/util/confine.rb index 5c1ce3b5df..757ca269df 100755 --- a/spec/unit/util/confine.rb +++ b/spec/unit/util/confine.rb @@ -3,6 +3,9 @@ require File.dirname(__FILE__) + '/../../spec_helper' require 'facter/util/confine' +require 'facter/util/values' + +include Facter::Util::Values describe Facter::Util::Confine do it "should require a fact name" do @@ -17,10 +20,6 @@ Facter::Util::Confine.new("yay", "test", "other").values.should == ["test", "other"] end - it "should convert all values to strings" do - Facter::Util::Confine.new("yay", :test).values.should == %w{test} - end - it "should fail if no fact name is provided" do lambda { Facter::Util::Confine.new(nil, :test) }.should raise_error(ArgumentError) end @@ -35,7 +34,7 @@ describe "when evaluating" do before do - @confine = Facter::Util::Confine.new("yay", "one", "two") + @confine = Facter::Util::Confine.new("yay", "one", "two", "Four", :xy, true, 1, [3,4]) @fact = mock 'fact' Facter.stubs(:[]).returns @fact end @@ -66,10 +65,76 @@ @confine.true?.should be_true end + it "should return true if any of the provided symbol values matches the fact's value" do + @fact.stubs(:value).returns :xy + + @confine.true?.should be_true + end + + it "should return true if any of the provided integer values matches the fact's value" do + @fact.stubs(:value).returns 1 + + @confine.true?.should be_true + end + + it "should return true if any of the provided boolan values matches the fact's value" do + @fact.stubs(:value).returns true + + @confine.true?.should be_true + end + + it "should return true if any of the provided array values matches the fact's value" do + @fact.stubs(:value).returns [3,4] + + @confine.true?.should be_true + end + + it "should return true if any of the provided symbol values matches the fact's string value" do + @fact.stubs(:value).returns :one + + @confine.true?.should be_true + end + + it "should return true if any of the provided string values matches case-insensitive the fact's value" do + @fact.stubs(:value).returns "four" + + @confine.true?.should be_true + end + + it "should return true if any of the provided symbol values matches case-insensitive the fact's string value" do + @fact.stubs(:value).returns :four + + @confine.true?.should be_true + end + + it "should return true if any of the provided symbol values matches the fact's string value" do + @fact.stubs(:value).returns :Xy + + @confine.true?.should be_true + end + it "should return false if none of the provided values matches the fact's value" do @fact.stubs(:value).returns "three" @confine.true?.should be_false end + + it "should return false if none of the provided integer values matches the fact's value" do + @fact.stubs(:value).returns 2 + + @confine.true?.should be_false + end + + it "should return false if none of the provided boolan values matches the fact's value" do + @fact.stubs(:value).returns false + + @confine.true?.should be_false + end + + it "should return false if none of the provided array values matches the fact's value" do + @fact.stubs(:value).returns [1,2] + + @confine.true?.should be_false + end end end From 7fa257689b08d5c6c8e22da9da49f0679aa9b1d9 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Tue, 21 Apr 2009 07:25:10 +1000 Subject: [PATCH 0349/3753] Fixed #2132 - support for named interface aliases under linux --- CHANGELOG | 4 +++- lib/facter/util/ip.rb | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index b0f9931fc7..ac92eaf7ad 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ -1.6.x: +1.5.5: + Fixed #2132 - Support for named interfaces under Linux + Fixed #2080 - IPAddress resolutions should be reordered Fixed #2078 - ip.rb errors command not found diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index 55b5e92fbb..e06e506efb 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -52,7 +52,7 @@ def self.get_interfaces # We get lots of warnings on platforms that don't get an output # made. if output - int = output.scan(/^\w+[.:]?\d+[.:]?\d*/) + int = output.scan(/^\w+[.:]?\d+[.:]?\d*[.:]?\w*/) else [] end From d89ea7a88a93b6ce3132d9dd394b4187eb460cb2 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Mon, 20 Apr 2009 11:40:42 -0500 Subject: [PATCH 0350/3753] Fixing ifconfig warnings generated on OS X The interface collection regex was leaving trailing ':' characters on the interface names, which meant individual interfaces weren't quite right. Signed-off-by: Luke Kanies --- lib/facter/util/ip.rb | 16 ++++++---------- spec/unit/util/ip.rb | 16 +++++++++++++--- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index e06e506efb..374d9f3b00 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -45,17 +45,13 @@ def self.supported_platforms end def self.get_interfaces - int = nil + return [] unless output = Facter::Util::IP.get_all_interface_output() - output = Facter::Util::IP.get_all_interface_output() - - # We get lots of warnings on platforms that don't get an output - # made. - if output - int = output.scan(/^\w+[.:]?\d+[.:]?\d*[.:]?\w*/) - else - [] - end + # Our regex appears to be stupid, in that it leaves colons sitting + # at the end of interfaces. So, we have to trim those trailing + # characters. I tried making the regex better but supporting all + # platforms with a single regex is probably a bit too much. + output.scan(/^\w+[.:]?\d+[.:]?\d*[.:]?\w*/).collect { |i| i.sub(/:$/, '') } end def self.get_all_interface_output diff --git a/spec/unit/util/ip.rb b/spec/unit/util/ip.rb index 4f6e2c0c18..a1d2458d63 100644 --- a/spec/unit/util/ip.rb +++ b/spec/unit/util/ip.rb @@ -37,7 +37,7 @@ Facter::Util::IP.get_interface_value("e1000g0", "netmask").should == [] end - it "should return interface information for directly supported platforms" do + it "should return netmask information for Solaris" do sample_output_file = File.dirname(__FILE__) + "/../data/solaris_ifconfig_single_interface" solaris_ifconfig_interface = File.new(sample_output_file).read() @@ -47,7 +47,7 @@ Facter::Util::IP.get_interface_value("e1000g0", "netmask").should == "255.255.255.0" end - it "should return interface information for platforms supported via an alias" do + it "should return interface information for FreeBSD supported via an alias" do sample_output_file = File.dirname(__FILE__) + "/../data/6.0-STABLE_FreeBSD_ifconfig" ifconfig_interface = File.new(sample_output_file).read() @@ -57,7 +57,7 @@ Facter::Util::IP.get_interface_value("fxp0", "macaddress").should == "00:0e:0c:68:67:7c" end - it "should return interface information for OS X" do + it "should return macaddress information for OS X" do sample_output_file = File.dirname(__FILE__) + "/../data/Mac_OS_X_10.5.5_ifconfig" ifconfig_interface = File.new(sample_output_file).read() @@ -67,6 +67,16 @@ Facter::Util::IP.get_interface_value("en1", "macaddress").should == "00:1b:63:ae:02:66" end + it "should return all interfaces correctly on OS X" do + sample_output_file = File.dirname(__FILE__) + "/../data/Mac_OS_X_10.5.5_ifconfig" + ifconfig_interface = File.new(sample_output_file).read() + + Facter::Util::IP.expects(:get_all_interface_output).returns(ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("Darwin") + + Facter::Util::IP.get_interfaces().should == ["lo0", "gif0", "stf0", "en0", "fw0", "en1", "vmnet8", "vmnet1"] + end + it "should return a human readable netmask on Solaris" do sample_output_file = File.dirname(__FILE__) + "/../data/solaris_ifconfig_single_interface" solaris_ifconfig_interface = File.new(sample_output_file).read() From 516402c77f9c7d751c627db36885e12aaff62bf9 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Mon, 20 Apr 2009 11:52:43 -0500 Subject: [PATCH 0351/3753] Fixing #1918 - facter --puppet always works The problem was that Facter wasn't telling Puppet to read your puppet.conf, so if you'd set vardir or libdir in it then you didn't get the appropriate settings and thus not know where to find the facter plugins. This is a bit of a ham-handed approach, but it always works. Signed-off-by: Luke Kanies --- bin/facter | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/bin/facter b/bin/facter index 6ee5b482af..4ffd50acb3 100755 --- a/bin/facter +++ b/bin/facter @@ -58,6 +58,19 @@ rescue Exception $haveusage = false end +def load_puppet + require 'puppet' + Puppet.parse_config + + # If you've set 'vardir' but not 'libdir' in your + # puppet.conf, then the hook to add libdir to $: + # won't get triggered. This makes sure that it's setup + # correctly. + unless $LOAD_PATH.include?(Puppet[:libdir]) + $LOAD_PATH << Puppet[:libdir] + end +end + $debug = 0 config = nil @@ -83,7 +96,7 @@ begin exit when "--puppet" begin - require 'puppet' + load_puppet() rescue LoadError => detail $stderr.puts "Could not load Puppet: %s" % detail end From 9df0583dcb6f8ea6d815bceb3da33d2cb7449f08 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Wed, 22 Apr 2009 14:33:52 +1000 Subject: [PATCH 0352/3753] Added COPYING in and CHANGELOG updates --- CHANGELOG | 6 + COPYING | 340 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 346 insertions(+) create mode 100644 COPYING diff --git a/CHANGELOG b/CHANGELOG index ac92eaf7ad..274a248229 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,10 @@ 1.5.5: + Fixed #1918 - facter --puppet doesn't work + + Fixed #2011 - virtual fact reports always vserver_host if /proc/virtual + + Fixed #2021 - Returning boolean not always possible + Fixed #2132 - Support for named interfaces under Linux Fixed #2080 - IPAddress resolutions should be reordered diff --git a/COPYING b/COPYING new file mode 100644 index 0000000000..3912109b5c --- /dev/null +++ b/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. From 636a91de9304caf32d179b7de5ca6dca296a7f2c Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Mon, 27 Apr 2009 00:00:56 +1000 Subject: [PATCH 0353/3753] Partial fix for #2191 - Facter compatibility for Ruby 1.9 --- lib/facter/macaddress.rb | 12 ++++++------ lib/facter/util/ip.rb | 2 +- lib/facter/virtual.rb | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/facter/macaddress.rb b/lib/facter/macaddress.rb index e048209bc7..94d00f4e83 100644 --- a/lib/facter/macaddress.rb +++ b/lib/facter/macaddress.rb @@ -3,7 +3,7 @@ setcode do ether = [] output = %x{/sbin/ifconfig -a} - output.each do |s| + output.each_line do |s| ether.push($1) if s =~ /(?:ether|HWaddr) (\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/ end ether[0] @@ -15,7 +15,7 @@ setcode do ether = [] output = %x{/sbin/ifconfig} - output.each do |s| + output.each_line do |s| if s =~ /(?:ether|lladdr)\s+(\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ ether.push($1) end @@ -30,7 +30,7 @@ ether = nil output = %x{/sbin/ifconfig} - output.split(/^\S/).each do |str| + output.split(/^\S/).each_line do |str| if str =~ /10baseT/ # we're wired str =~ /ether (\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ ether = $1 @@ -47,12 +47,12 @@ ether = [] ip = nil output = %x{/usr/sbin/ifconfig -a} - output.each do |str| + output.each_line do |str| if str =~ /([a-z]+\d+): flags=/ devname = $1 unless devname =~ /lo0/ output2 = %x{/usr/bin/entstat #{devname}} - output2.each do |str2| + output2.each_line do |str2| if str2 =~ /^Hardware Address: (\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/ ether.push($1) end @@ -69,7 +69,7 @@ setcode do ether = [] output = %x{ipconfig /all} - output.split(/\r\n/).each do |str| + output.split(/\r\n/).each_line do |str| if str =~ /.*Physical Address.*: (\w{1,2}-\w{1,2}-\w{1,2}-\w{1,2}-\w{1,2}-\w{1,2})/ ether.push($1.gsub(/-/, ":")) end diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index 374d9f3b00..a2125ea5b1 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -120,7 +120,7 @@ def self.get_interface_value(interface, label) output_int = get_single_interface_output(interface) if interface != /^lo[0:]?\d?/ - output_int.each do |s| + output_int.each_line do |s| if s =~ regex value = $1 if label == 'netmask' && convert_from_hex?(kernel) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 97d7cba235..3ad6f8a1c0 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -32,7 +32,7 @@ if result == "physical" output = Facter::Util::Resolution.exec('lspci') if not output.nil? - output.each do |p| + output.each_line do |p| # --- look for the vmware video card to determine if it is virtual => vmware. # --- 00:0f.0 VGA compatible controller: VMware Inc [VMware SVGA II] PCI Display Adapter result = "vmware" if p =~ /VM[wW]are/ @@ -40,13 +40,13 @@ else output = Facter::Util::Resolution.exec('dmidecode') if not output.nil? - output.each do |pd| + output.each_line do |pd| result = "vmware" if pd =~ /VMware|Parallels/ end else output = Facter::Util::Resolution.exec('prtdiag') if not output.nil? - output.each do |pd| + output.each_line do |pd| result = "vmware" if pd =~ /VMware|Parallels/ end end @@ -61,7 +61,7 @@ output = Facter::Util::Resolution.exec('mount') if not output.nil? - output.each do |p| + output.each_line do |p| result = "vserver" if p =~ /\/dev\/hdv1/ end end From 8e4a68983ca7605f7fdf9ea441c26eef9ff6b42e Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sat, 2 May 2009 21:40:22 +1000 Subject: [PATCH 0354/3753] Fixes #2169 Correctly recognises dom0 and domUs --- lib/facter/virtual.rb | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 3ad6f8a1c0..78e5485fd2 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -18,17 +18,21 @@ end end - Thread::exclusive do - if FileTest.exists?("/proc/xen/capabilities") && FileTest.readable?("/proc/xen/capabilities") - txt = File.read("/proc/xen/capabilities") - if txt =~ /control_d/i - result = "xen0" - else - result = "xenu" - end - end + # new Xen domains have this in dom0 not domu :( + if FileTest.exists?("/proc/sys/xen/independent_wallclock") + result = "xenu" end - + if FileTest.exists?("/sys/bus/xen") + result = "xenu" + end + + if FileTest.exists?("/proc/xen/capabilities") + txt = File.read("/proc/xen/capabilities") + if txt =~ /control_d/i + result = "xen0" + end + end + if result == "physical" output = Facter::Util::Resolution.exec('lspci') if not output.nil? From e93b1e6a11936715727157a18e833c42acf898eb Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sun, 3 May 2009 12:43:48 +1000 Subject: [PATCH 0355/3753] Added support for ArchLinux to operatingsystem fact --- lib/facter/operatingsystem.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index 52f889b998..eacc7b3ad7 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -26,6 +26,8 @@ "OEL" elsif FileTest.exists?("/etc/ovs-release") "OVS" + elsif FileTest.exists?("/etc/arch-release") + "Arch" elsif FileTest.exists?("/etc/redhat-release") txt = File.read("/etc/redhat-release") if txt =~ /centos/i From 23a5b3db4f73e85c34dc917bc191f37b31910dcb Mon Sep 17 00:00:00 2001 From: Andreas Zuber Date: Fri, 1 May 2009 12:10:11 +0200 Subject: [PATCH 0356/3753] Fixed #2215 - Added support for SUSE Linux Enterprise Desktop to operatingsystem and operatingsystemrelease --- lib/facter/operatingsystem.rb | 2 ++ lib/facter/operatingsystemrelease.rb | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index eacc7b3ad7..704b48feb3 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -39,6 +39,8 @@ txt = File.read("/etc/SuSE-release") if txt =~ /^SUSE LINUX Enterprise Server/i "SLES" + elsif txt =~ /^SUSE LINUX Enterprise Desktop/i + "SLED" elsif txt =~ /^openSUSE/i "OpenSuSE" else diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index 25a226d3d8..d6e6e86db3 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -80,7 +80,7 @@ end Facter.add(:operatingsystemrelease) do - confine :operatingsystem => %w{SLES OpenSuSE} + confine :operatingsystem => %w{SLES SLED OpenSuSE} setcode do releasefile = Facter::Util::Resolution.exec('cat /etc/SuSE-release') if releasefile =~ /^VERSION\s*=\s*(\d+)/ From 8768371511d081555db63a89be7c1a17a69f4e0c Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sun, 10 May 2009 19:06:20 +1000 Subject: [PATCH 0357/3753] Fixed #2119 - Added support for non-global Solaris 10 zones --- CHANGELOG | 2 ++ lib/facter/virtual.rb | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 274a248229..1a0adf2179 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -7,6 +7,8 @@ Fixed #2132 - Support for named interfaces under Linux + Fixed #2119 - Added support for non-global Solaris 10 zones + Fixed #2080 - IPAddress resolutions should be reordered Fixed #2078 - ip.rb errors command not found diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 78e5485fd2..907f9ca73a 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -6,6 +6,13 @@ setcode do require 'thread' + if FileTest.exists?("/sbin/zonename") + z = %x{"/sbin/zonename"}.chomp + if z != 'global' + result = zone + end + end + if FileTest.exists?("/proc/user_beancounters") # openvz. can be hardware node or virtual environment # read the init process' status file, it has laxer permissions From f94abfccfd6687a88f62703e4005c28ec04467a1 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sun, 10 May 2009 19:33:01 +1000 Subject: [PATCH 0358/3753] Fixed #1327 - Added SELinux facts --- CHANGELOG | 2 ++ lib/facter/selinux.rb | 45 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 lib/facter/selinux.rb diff --git a/CHANGELOG b/CHANGELOG index 1a0adf2179..0f123f4559 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -7,6 +7,8 @@ Fixed #2132 - Support for named interfaces under Linux + Fixed #1327 - Added SELinux facts + Fixed #2119 - Added support for non-global Solaris 10 zones Fixed #2080 - IPAddress resolutions should be reordered diff --git a/lib/facter/selinux.rb b/lib/facter/selinux.rb new file mode 100644 index 0000000000..0e9637d93d --- /dev/null +++ b/lib/facter/selinux.rb @@ -0,0 +1,45 @@ +# Fact for SElinux +# Written by immerda admin team (admin(at)immerda.ch) + +Facter.add("selinux") do + confine :kernel => :linux + + setcode do + result = "false" + if FileTest.exists?("/selinux/enforce") + if FileTest.exists?("/proc/self/attr/current") + if (File.read("/proc/self/attr/current") != "kernel\0") + result = "true" + end + end + end + result + end +end + +Facter.add("selinux_enforced") do + confine :selinux => :true + + setcode do + result = "false" + if FileTest.exists?("/selinux/enforce") and File.read("/selinux/enforce") =~ /1/i + result = "true" + end + result + end +end + +Facter.add("selinux_policyversion") do + confine :selinux => :true + setcode do + File.read("/selinux/policyvers") + end +end + +Facter.add("selinux_mode") do + confine :selinux => :true + setcode do + %x{/usr/sbin/sestatus | /bin/grep "Policy from config file:" | awk '{print $5}'} + end +end + From 2518312500b63d818815c8cdb0c6af2c8e0f6dd7 Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Mon, 11 May 2009 06:23:59 +0100 Subject: [PATCH 0359/3753] Fix #2236 - don't use each_line on arrays This fixes up the syntax so that we can get 1.5.5 out, I've not done tests for this as we need to fixup the number of time we call out to ifconfig and the duplication with ip.rb. Paul --- lib/facter/macaddress.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/facter/macaddress.rb b/lib/facter/macaddress.rb index 94d00f4e83..f978b17167 100644 --- a/lib/facter/macaddress.rb +++ b/lib/facter/macaddress.rb @@ -30,7 +30,7 @@ ether = nil output = %x{/sbin/ifconfig} - output.split(/^\S/).each_line do |str| + output.split(/^\S/).each do |str| if str =~ /10baseT/ # we're wired str =~ /ether (\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ ether = $1 @@ -69,7 +69,7 @@ setcode do ether = [] output = %x{ipconfig /all} - output.split(/\r\n/).each_line do |str| + output.split(/\r\n/).each do |str| if str =~ /.*Physical Address.*: (\w{1,2}-\w{1,2}-\w{1,2}-\w{1,2}-\w{1,2}-\w{1,2})/ ether.push($1.gsub(/-/, ":")) end From 73e665629ce45230250afc5a354b7f9d7a0a4206 Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Sun, 10 May 2009 20:16:54 +0100 Subject: [PATCH 0360/3753] Facter fix #2231 typo Added a test to pickup typo and fix --- lib/facter/util/ip.rb | 2 +- spec/unit/util/ip.rb | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index a2125ea5b1..fb32d70a9d 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -16,7 +16,7 @@ module Facter::Util::IP :netmask => /netmask\s+0x(\w{8})/ }, :sunos => { - :ipadddress => /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, + :ipaddress => /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, :macaddress => /(?:ether|lladdr)\s+(\w?\w:\w?\w:\w?\w:\w?\w:\w?\w:\w?\w)/, :netmask => /netmask\s+(\w{8})/ } diff --git a/spec/unit/util/ip.rb b/spec/unit/util/ip.rb index a1d2458d63..6290a30cc8 100644 --- a/spec/unit/util/ip.rb +++ b/spec/unit/util/ip.rb @@ -37,6 +37,16 @@ Facter::Util::IP.get_interface_value("e1000g0", "netmask").should == [] end + it "should return ipaddress information for Solaris" do + sample_output_file = File.dirname(__FILE__) + "/../data/solaris_ifconfig_single_interface" + solaris_ifconfig_interface = File.new(sample_output_file).read() + + Facter::Util::IP.expects(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("SunOS") + + Facter::Util::IP.get_interface_value("e1000g0", "ipaddress").should == "172.16.15.138" + end + it "should return netmask information for Solaris" do sample_output_file = File.dirname(__FILE__) + "/../data/solaris_ifconfig_single_interface" solaris_ifconfig_interface = File.new(sample_output_file).read() From e101faf209aa2a0ddd6d1d6598e8d243f43386e7 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Tue, 12 May 2009 10:01:39 +1000 Subject: [PATCH 0361/3753] Fixed #2131 - Facter doesn't populate lsbmajdistrelease on OEL (also OEL/OVS and other facts) --- lib/facter/hardwareisa.rb | 2 +- lib/facter/id.rb | 2 +- lib/facter/lsbmajdistrelease.rb | 2 +- lib/facter/macaddress.rb | 2 +- lib/facter/uniqueid.rb | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/facter/hardwareisa.rb b/lib/facter/hardwareisa.rb index 6a67d9624e..45a16bca38 100644 --- a/lib/facter/hardwareisa.rb +++ b/lib/facter/hardwareisa.rb @@ -1,4 +1,4 @@ Facter.add(:hardwareisa) do setcode 'uname -p', '/bin/sh' - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE SLES Debian Ubuntu Gentoo FreeBSD OpenBSD NetBSD} + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE SLES Debian Ubuntu Gentoo FreeBSD OpenBSD NetBSD OEL OVS} end diff --git a/lib/facter/id.rb b/lib/facter/id.rb index d62009605f..0a4067d9ce 100644 --- a/lib/facter/id.rb +++ b/lib/facter/id.rb @@ -1,5 +1,5 @@ Facter.add(:id) do - confine :operatingsystem => %w{Linux Fedora RedHat CentOS SuSE SLES Debian Ubuntu Gentoo AIX} + confine :operatingsystem => %w{Linux Fedora RedHat CentOS SuSE SLES Debian Ubuntu Gentoo AIX OEL OVS} setcode "whoami" end diff --git a/lib/facter/lsbmajdistrelease.rb b/lib/facter/lsbmajdistrelease.rb index f84c3c15ec..e09159355e 100644 --- a/lib/facter/lsbmajdistrelease.rb +++ b/lib/facter/lsbmajdistrelease.rb @@ -3,7 +3,7 @@ require 'facter' Facter.add("lsbmajdistrelease") do - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE SLES Debian Ubuntu Gentoo} + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE SLES Debian Ubuntu Gentoo OEL OVS} setcode do if /(\d*)\./i =~ Facter.value(:lsbdistrelease) result=$1 diff --git a/lib/facter/macaddress.rb b/lib/facter/macaddress.rb index f978b17167..e8f40dc34b 100644 --- a/lib/facter/macaddress.rb +++ b/lib/facter/macaddress.rb @@ -1,5 +1,5 @@ Facter.add(:macaddress) do - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE SLES Debian Gentoo Ubuntu} + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE SLES Debian Gentoo Ubuntu OEL OVS} setcode do ether = [] output = %x{/sbin/ifconfig -a} diff --git a/lib/facter/uniqueid.rb b/lib/facter/uniqueid.rb index 1af4ae5d56..93f8a61e38 100644 --- a/lib/facter/uniqueid.rb +++ b/lib/facter/uniqueid.rb @@ -1,4 +1,4 @@ Facter.add(:uniqueid) do setcode 'hostid', '/bin/sh' - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE SLES Debian Ubuntu Gentoo AIX} + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE SLES Debian Ubuntu Gentoo AIX OEL OVS} end From a6adf59ec4683af8dea9af1f8a457cb5a1ae178b Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Wed, 6 May 2009 08:14:37 +0100 Subject: [PATCH 0362/3753] Facter ticket 2214 - Fix facts for OVS Added tests for operatingsystem fact covering the two simple cases and a test for this specific interaction of release files We should take some time to add tests when we're adding or changing new operatingsystem facts --- lib/facter/operatingsystem.rb | 8 +++++--- spec/unit/operatingsystem.rb | 36 +++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 spec/unit/operatingsystem.rb diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index 704b48feb3..335003a29f 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -23,9 +23,11 @@ elsif FileTest.exists?("/etc/arch-release") "Archlinux" elsif FileTest.exists?("/etc/enterprise-release") - "OEL" - elsif FileTest.exists?("/etc/ovs-release") - "OVS" + if FileTest.exists?("/etc/ovs-release") + "OVS" + else + "OEL" + end elsif FileTest.exists?("/etc/arch-release") "Arch" elsif FileTest.exists?("/etc/redhat-release") diff --git a/spec/unit/operatingsystem.rb b/spec/unit/operatingsystem.rb new file mode 100644 index 0000000000..4c3fb3bbe5 --- /dev/null +++ b/spec/unit/operatingsystem.rb @@ -0,0 +1,36 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../spec_helper' + +require 'facter' + +describe "Operating System fact" do + + + after do + Facter.clear + end + + it "should default to the kernel name" do + Facter.fact(:kernel).stubs(:value).returns("Nutmeg") + + Facter.fact(:operatingsystem).value.should == "Nutmeg" + end + + it "should be Solaris for SunOS" do + Facter.fact(:kernel).stubs(:value).returns("SunOS") + + Facter.fact(:operatingsystem).value.should == "Solaris" + end + + it "should identify Oracle VM as OVS" do + + Facter.fact(:kernel).stubs(:value).returns("Linux") + FileTest.stubs(:exists?).returns false + + FileTest.expects(:exists?).with("/etc/ovs-release").returns true + FileTest.expects(:exists?).with("/etc/enterprise-release").returns true + + Facter.fact(:operatingsystem).value.should == "OVS" + end +end From 7f3d237823a8e6817a770a83848578cc6e6250e6 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Tue, 12 May 2009 10:21:29 +1000 Subject: [PATCH 0363/3753] Cleaned up Rakefile and removed requirement for Reductive Labs build library --- CHANGELOG | 10 + Rakefile | 102 ++++--- tasks/rake/redlabpackage.rb | 265 ------------------ tasks/rake/reductive.rb | 538 ------------------------------------ 4 files changed, 59 insertions(+), 856 deletions(-) delete mode 100644 tasks/rake/redlabpackage.rb delete mode 100644 tasks/rake/reductive.rb diff --git a/CHANGELOG b/CHANGELOG index 0f123f4559..bd9420da0a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,14 @@ 1.5.5: + Cleaned up Rakefile and removed requirement for Reductive Labs build library + + Fixed #2131 (Closed): Facter doesn't populate lsbmajdistrelease on OEL + + Fixed #2214 (Closed): Identify Oracle VM properly + + Fixed #2231: ipaddress_pcn0 fact no longer exists on Solaris systems + + Fixed #2236 (Closed): macaddress fact uses each_line on arrays + Fixed #1918 - facter --puppet doesn't work Fixed #2011 - virtual fact reports always vserver_host if /proc/virtual diff --git a/Rakefile b/Rakefile index f78fac2179..5fa7c48661 100644 --- a/Rakefile +++ b/Rakefile @@ -1,67 +1,63 @@ # Rakefile for facter -$LOAD_PATH << File.join(File.dirname(__FILE__), 'tasks') - -begin - require 'rake/reductive' -rescue LoadError - $stderr.puts "You must have the Reductive build library in your RUBYLIB." - exit(14) +require 'facter' +require 'rake' +require 'rake/packagetask' +require 'rake/gempackagetask' + +FILES = FileList[ + '[A-Z]*', + 'bin/**/*', + 'lib/**/*', + 'conf/**/*', + 'etc/**/*', +] + +spec = Gem::Specification.new do |spec| + spec.platform = Gem::Platform::RUBY + spec.name = 'facter' + spec.files = FILES.to_a + spec.version = Facter::FACTERVERSION + spec.summary = 'Facter, a system inventory tool' + spec.author = 'Reductive Labs' + spec.email = 'puppet@reductivelabs.com' + spec.homepage = 'http://reductivelabs.com/' + spec.rubyforge_project = 'facter' + spec.has_rdoc = true + spec.rdoc_options << + '--title' << 'Facter - System Inventory Tool' << + '--main' << 'README' << + '--line-numbers' end -project = Rake::RedLabProject.new("facter") do |p| - p.summary = "Facter collects Operating system facts." - p.description = <<-EOF - Facter is a module for collecting simple facts about a host - Operating system. - EOF - - p.filelist = [ - 'install.rb', - '[A-Z]*', - 'bin/**/*', - 'lib/facter.rb', - 'lib/**/*.rb', - 'test/**/*.rb', - 'spec/**/*', - 'conf/**/*', - 'documentation/**/*', - 'etc/*' - ] - +Rake::PackageTask.new("facter", Facter::FACTERVERSION) do |pkg| + pkg.package_dir = 'pkg' + pkg.need_tar_gz = true + pkg.package_files = FILES.to_a end -project.mkgemtask do |gem| - gem.require_path = 'lib' # Use these for libraries. - - gem.bindir = "bin" # Use these for applications. - gem.executables = ["facter"] - gem.default_executable = "facter" - - gem.author = "Luke Kanies" +Rake::GemPackageTask.new(spec) do |pkg| end -task :archive do - raise ArgumentError, "You must specify the archive name by setting ARCHIVE; e.g., ARCHIVE=1.5.1rc1" unless archive = ENV["ARCHIVE"] - - sh "git archive --format=tar --prefix=facter-#{archive}/ HEAD | gzip -c > facter-#{archive}.tgz" +desc "Run the specs under spec/" +task :spec do + require 'spec' + require 'spec/rake/spectask' + # require 'rcov' + Spec::Rake::SpecTask.new do |t| + t.spec_opts = ['--format','s', '--loadby','mtime'] + t.spec_files = FileList['spec/**/*.rb'] + end end -namespace :ci do - desc "Run the CI prep tasks" - task :prep do - require 'rubygems' - gem 'ci_reporter' - require 'ci/reporter/rake/rspec' - require 'ci/reporter/rake/test_unit' - ENV['CI_REPORTS'] = 'results' - end +require 'rubygems' +gem 'ci_reporter' +require 'ci/reporter/rake/rspec' +require 'ci/reporter/rake/test_unit' +ENV['CI_REPORTS'] = 'results' - desc "Run CI RSpec tests" - task :spec => [:prep, 'ci:setup:rspec'] do - sh "cd spec; rake all; exit 0" - end -end +desc "Run CI RSpec tests" +task :ci_spec => ['ci:setup:rspec', :spec] desc "Send patch information to the puppet-dev list" task :mail_patches do diff --git a/tasks/rake/redlabpackage.rb b/tasks/rake/redlabpackage.rb deleted file mode 100644 index 1df9c41131..0000000000 --- a/tasks/rake/redlabpackage.rb +++ /dev/null @@ -1,265 +0,0 @@ -#!/usr/bin/env ruby - -# A raw platform for creating packages. - -require 'rbconfig' -require 'rake' -require 'rake/tasklib' - -# The PackageTask will create the following targets: -# -# [:clobber_package] -# Delete all the package files. This target is automatically -# added to the main clobber target. -# -# [:repackage] -# Rebuild the package files from scratch, even if they are not out -# of date. -# -# ["package_dir/name-version.tgz"] -# Create a gzipped tar package (if need_tar is true). -# -# ["package_dir/name-version.tar.gz"] -# Create a gzipped tar package (if need_tar_gz is true). -# -# ["package_dir/name-version.tar.bz2"] -# Create a bzip2'd tar package (if need_tar_bz2 is true). -# -# ["package_dir/name-version.zip"] -# Create a zip package archive (if need_zip is true). -# -# Example: -# -# Rake::PackageTask.new("rake", "1.2.3") do |p| -# p.need_tar = true -# p.package_files.include("lib/**/*.rb") -# end -# -class Rake::RedLabPackageTask < Rake::TaskLib - # The different directory types we can manage. - DIRTYPES = { - :bindir => :bins, - :sbindir => :sbins, - :sitelibdir => :rubylibs - } - - # Name of the package (from the GEM Spec). - attr_accessor :name - - # Version of the package (e.g. '1.3.2'). - attr_accessor :version - - # Directory used to store the package files (default is 'pkg'). - attr_accessor :package_dir - - # The directory to which to publish packages and html and such. - attr_accessor :publishdir - - # The package-specific publishing directory - attr_accessor :pkgpublishdir - - # The Product name. Defaults to a capitalized version of the - # package name - attr_accessor :product - - # The copyright message. - attr_accessor :copyright - - # The vendor. - attr_accessor :vendor - - # The license file. Defaults to COPYING. - attr_accessor :license - - # The readme file. Defaults to README. - attr_accessor :readme - - # The description. - attr_accessor :description - - # The summary. - attr_accessor :summary - - # The directory in which to put the binaries. Defaults to the system - # default. - attr_accessor :bindir - - # The executables. - attr_accessor :bins - - # The directory in which to put the system binaries. Defaults to the - # system default. - attr_accessor :sbindir - - # The system binaries. - attr_accessor :sbins - - # The libraries. - attr_accessor :rubylibs - - # The directory in which to put Ruby libraries. Defaults to the - # system site_dir. - attr_accessor :sitelibdir - - # The URL for the package. - attr_accessor :url - - # The source for the package. - attr_accessor :source - - # Our operating system. - attr_reader :os - - # Add a required package. - def add_dependency(name, version = nil) - @requires[name] = version - end - - # Create the tasks defined by this task library. - def define - fail "Version required (or :noversion)" if @version.nil? - @version = nil if :noversion == @version - - directory pkgdest - file pkgdest => self.package_dir - - directory self.package_dir - - self.mkcopytasks - - self - end - - # Return the list of files associated with a dirname. - def files(dirname) - if @dirtypes.include?(dirname) - return self.send(@dirtypes[dirname]) - else - raise "Could not find directory type %s" % dirname - end - end - - # Create a Package Task with the given name and version. - def initialize(name=nil, version=nil) - # Theoretically, one could eventually add directory types here. - @dirtypes = DIRTYPES.dup - - @requires = {} - - @name = name - @version = version - @package_dir = 'pkg' - @product = name.capitalize - - @bindir = Config::CONFIG["bindir"] - @sbindir = Config::CONFIG["sbindir"] - @sitelibdir = Config::CONFIG["sitelibdir"] - - @license = "COPYING" - @readme = "README" - - yield self if block_given? - - define unless name.nil? - - # Make sure they've provided everything necessary. - %w{copyright vendor description}.each do |attr| - unless self.send(attr) - raise "You must provide the attribute %s" % attr - end - end - end - - # Make tasks for copying/linking all of the necessary files. - def mkcopytasks - basedir = pkgdest() - - tasks = [] - - # Iterate across all of the file locations... - @dirtypes.each do |dirname, filemethod| - tname = ("copy" + dirname.to_s).intern - - dir = self.send(dirname) - - reqs = [] - - # This is where we're putting the files. - targetdir = self.targetdir(dirname) - - # Make sure our target directories exist - directory targetdir - file targetdir => basedir - - # Get the file list and remove the leading directory. - files = self.files(dirname) or next - - reqs = [] - files.each do |sourcefile| - # The file without the basedir. This is necessary because - # files are created with the path from ".", but they often - # have 'lib' changed to 'site_ruby' or something similar. - destfile = File.join(targetdir, sourcefile.sub(/^\w+\//, '')) - reqs << destfile - - # Make sure the base directory is listed as a prereq - sourcedir = File.dirname(sourcefile) - destdir = nil - unless sourcedir == "." - destdir = File.dirname(destfile) - reqs << destdir - directory(destdir) - end - - # Now make the task associated with creating the object in - # question. - if FileTest.directory?(sourcefile) - directory(destfile) - else - file(destfile => sourcefile) do - if FileTest.exists?(destfile) - if File.stat(sourcefile) > File.stat(destfile) - rm_f destfile - safe_ln(sourcefile, destfile) - end - else - safe_ln(sourcefile, destfile) - end - end - - # If we've set the destdir, then list it as a prereq. - if destdir - file destfile => destdir - end - end - end - - # And create a task for each one - task tname => reqs - - # And then mark our task as a prereq - tasks << tname - end - - task :copycode => [self.package_dir, pkgdest] - - task :copycode => tasks do - puts "Finished copying" - end - end - - # Where we're copying a given type of file. - def targetdir(dirname) - File.join(pkgdest(), self.send(dirname)).sub("//", "/") - end - - private - - def package_name - @version ? "#{@name}-#{@version}" : @name - end - - def package_dir_path - "#{package_dir}/#{package_name}" - end -end diff --git a/tasks/rake/reductive.rb b/tasks/rake/reductive.rb deleted file mode 100644 index 0896393335..0000000000 --- a/tasks/rake/reductive.rb +++ /dev/null @@ -1,538 +0,0 @@ -#!/usr/bin/env ruby - -# The tasks associated with building Reductive Labs projects - -require 'rbconfig' -require 'rake' -require 'rake/tasklib' - -require 'rake/clean' -require 'rake/testtask' - -$features = {} - -begin - require 'rubygems' - require 'rake/gempackagetask' - $features[:gem] = true -rescue Exception - $features[:gem] = false - $stderr.puts "No Gems; skipping" - nil -end - -begin - require 'rdoc/rdoc' - $features[:rdoc] = true -rescue => detail - $features[:rdoc] = false - puts "No rdoc: %s" % detail -end - -if $features[:rdoc] - require 'rake/rdoctask' -end - -# Create all of the standard targets for a Reductive Labs project. -# NOTE: The reason so many of the rake tasks are generated, rather than being -# declared directly, is that they need information from the project instance. -# Any rake task with an instance variable (e.g., @name or @version) needs -# to have that variable assigned *before* the task is defined. Suckage. -class Rake::RedLabProject < Rake::TaskLib - # The project name. - attr_accessor :name - - # The project version. - attr_accessor :version - - # The directory to which to publish packages and html and such. - attr_accessor :publishdir - - # The package-specific publishing directory - attr_accessor :pkgpublishdir - - # Create a Gem file. - attr_accessor :mkgem - - # The hosts to run all of our tests on. - attr_accessor :testhosts - - # The summary of this project. - attr_accessor :summary - - # The description of this project. - attr_accessor :description - - # The author of this project. - attr_accessor :author - - # A Contact email address. - attr_accessor :email - - # The URL for the project. - attr_accessor :url - - # Where to get the source code. - attr_accessor :source - - # Who the vendor is. - attr_accessor :vendor - - # The copyright for this project - attr_accessor :copyright - - # The RubyForge project. - attr_accessor :rfproject - - # The list of files. Only used for gem tasks. - attr_writer :filelist - - # The directory in which to store packages. Defaults to "pkg". - attr_accessor :package_dir - - # The default task. Defaults to the 'alltests' task. - attr_accessor :defaulttask - - # The defined requirements - attr_reader :requires - - # The file containing the version string. - attr_accessor :versionfile - - # Print messages on stdout - def announce(msg = nil) - puts msg - end - - # Print messages on stderr - def warn(msg = nil) - $stderr.puts msg - end - - def add_dependency(name, version) - @requires[name] = version - end - - # Where we'll be putting the code. - def codedir - unless defined? @codedir - @codedir = File.join(self.package_dir, "#{@name}-#{@version}") - end - - return @codedir - end - - # Retrieve the current version from the code. - def currentversion - unless defined? @currentversion - ver = %x{ruby -Ilib ./bin/#{@name} --version}.chomp - if $? == 0 and ver != "" - @currentversion = ver - else - warn "Could not retrieve current version; using 0.0.0" - @currentversion = "0.0.0" - end - end - - return @currentversion - end - - # Define all of our package tasks. We just search through all of our - # defined methods and call anything that's listed as making tasks. - def define - self.methods.find_all { |method| method.to_s =~ /^mktask/ }.each { |method| - self.send(method) - } - end - - def egrep(pattern) - Dir['**/*.rb'].each do |fn| - count = 0 - open(fn) do |f| - while line = f.gets - count += 1 - if line =~ pattern - puts "#{fn}:#{count}:#{line}" - end - end - end - end - end - - # List all of the files. - def filelist - unless defined? @createdfilelist - # If they passed in a file list as an array, then create a FileList - # object out of it. - if defined? @filelist - unless @filelist.is_a? FileList - @filelist = FileList[@filelist] - end - else - # Use a default file list. - @filelist = FileList[ - 'install.rb', - '[A-Z]*', - 'lib/**/*.rb', - 'test/**/*.rb', - 'bin/**/*', - 'ext/**/*', - 'examples/**/*', - 'conf/**/*' - ] - end - @filelist.delete_if {|item| item.include?(".git")} - - @createdfilelist = true - end - - @filelist - end - - def has?(feature) - feature = feature.intern if feature.is_a? String - if $features.include?(feature) - return $features[feature] - else - return true - end - end - - def initialize(name, version = nil) - @name = name - - if ENV['REL'] - @version = ENV['REL'] - else - @version = version || self.currentversion - end - - @defaulttask = :alltests - @publishdir = "/opt/rl/docroots/reductivelabs.com/htdocs/downloads" - @pkgpublishdir = "#{@publishdir}/#{@name}" - - @email = "dev@reductivelabs.com" - @url = "http://reductivelabs.com/projects/#{@name}" - @source = "http://reductivelabs.com/downloads/#{@name}/#{@name}-#{@version}.tgz" - @vendor = "Reductive Labs, LLC" - @copyright = "Copyright 2003-2008, Reductive Labs, LLC. Some Rights Reserved." - @rfproject = @name - - @defaulttask = :package - - @package_dir = "pkg" - - @requires = {} - - @versionfile = "lib/#{@name}.rb" - - CLOBBER.include('doc/*') - - yield self if block_given? - define if block_given? - end - - def mktaskhtml - if $features[:rdoc] - Rake::RDocTask.new(:html) { |rdoc| - rdoc.rdoc_dir = 'html' - rdoc.template = 'html' - rdoc.title = @name.capitalize - rdoc.options << '--line-numbers' << '--inline-source' << - '--main' << 'README' - rdoc.rdoc_files.include('README', 'COPYING', 'CHANGELOG') - rdoc.rdoc_files.include('lib/**/*.rb') - CLEAN.include("html") - } - - # Publish the html. - task :publish => [:package, :html] do - puts Dir.getwd - sh %{cp -r html #{self.pkgpublishdir}/apidocs} - end - else - warn "No rdoc; skipping html" - end - end - - # Create a release task. - def mktaskrelease - desc "Make a new release" - task :release => [ - :prerelease, - :clobber, - :update_version, - :commit_newversion, - :trac_version, - :tag, # tag everything before we make a bunch of extra dirs - :html, - :package, - :publish - ] do - - announce - announce "**************************************************************" - announce "* Release #{@version} Complete." - announce "* Packages ready to upload." - announce "**************************************************************" - announce - end - end - - # Do any prerelease work. - def mktaskprerelease - # Validate that everything is ready to go for a release. - task :prerelease do - announce - announce "**************************************************************" - announce "* Making Release #{@version}" - announce "* (current version #{self.currentversion})" - announce "**************************************************************" - announce - - # Is a release number supplied? - unless ENV['REL'] - warn "You must provide a release number when releasing" - fail "Usage: rake release REL=x.y.z [REUSE=tag_suffix]" - end - - # Is the release different than the current release. - # (or is REUSE set?) - if @version == self.currentversion && ! ENV['REUSE'] - fail "Current version is #{@version}, must specify REUSE=tag_suffix to reuse version" - end - - # Are all source files checked in? - if ENV['RELTEST'] - announce "Release Task Testing, skipping checked-in file test" - else - announce "Checking for unchecked-in files..." - data = %x{git status} - unless data.include?("nothing to commit") - fail "git status is not clean ... do you have unchecked-in files?" - end - announce "No outstanding checkins found ... OK" - end - end - end - - # Create the task to update versions. - def mktaskupdateversion - task :update_version => [:prerelease] do - if @version == self.currentversion - announce "No version change ... skipping version update" - else - announce "Updating #{@versionfile} version to #{@version}" - open(@versionfile) do |rakein| - open("#{@versionfile}.new", "w") do |rakeout| - rakein.each do |line| - if line =~ /^(\s*)#{@name.upcase}VERSION\s*=\s*/ - rakeout.puts "#{$1}#{@name.upcase}VERSION = '#{@version}'" - else - rakeout.puts line - end - end - end - end - mv "#{@versionfile}.new", @versionfile - - end - end - - desc "Commit the new versions to SVN." - task :commit_newversion => [:update_version] do - if ENV['RELTEST'] - announce "Release Task Testing, skipping commiting of new version" - else - sh %{git commit -m "Updated to version #{@version}" #{@versionfile}} - end - end - end - - def mktasktrac_version - task :trac_version => [:update_version] do - tracpath = "/opt/rl/trac/#{@name}" - - unless FileTest.exists?(tracpath) - announce "No Trac instance at %s" % tracpath - else - output = %x{sudo trac-admin #{tracpath} version list}.chomp.split("\n") - versions = {} - output[3..-1].each do |line| - name, time = line.chomp.split(/\s+/) - versions[name] = time - end - - if versions.include?(@version) - announce "Version #{@version} already in Trac" - else - announce "Adding #{@name} version #{@version} to Trac" - date = [Time.now.year.to_s, - Time.now.month.to_s, - Time.now.day.to_s].join("-") - system("sudo trac-admin #{tracpath} version add #{@version} #{date}") - end - end - end - end - - # Create the tag task. - def mktasktag - desc "Tag all the files with the latest release number (REL=x.y.z)" - task :tag => [:prerelease] do - reltag = @version - announce "Tagging with [#{reltag}]" - - if ENV['RELTEST'] - announce "Release Task Testing, skipping tagging" - else - sh %{git tag #{reltag}} - end - end - end - - # Create the task for testing across all hosts. - def mktaskhosttest - desc "Test Puppet on each test host" - task :hosttest do - out = "" - TESTHOSTS.each { |host| - puts "testing %s" % host - cwd = Dir.getwd - file = "/tmp/#{@name}-#{host}test.out" - system("ssh #{host} 'cd git/#{@name}/test; sudo rake' 2>&1 >#{file}") - - if $? != 0 - puts "%s failed; output is in %s" % [host, file] - end - } - end - end - - def mktaskri - # Create a task to build the RDOC documentation tree. - - #Rake::RDocTask.new("ri") { |rdoc| - # #rdoc.rdoc_dir = 'html' - # #rdoc.template = 'html' - # rdoc.title = "Puppet" - # rdoc.options << '--ri' << '--line-numbers' << '--inline-source' << '--main' << 'README' - # rdoc.rdoc_files.include('README', 'COPYING', 'CHANGELOG') - # rdoc.rdoc_files.include('lib/**/*.rb', 'doc/**/*.rdoc') - #} - - if $features[:rdoc] - task :ri do |ri| - files = ['README', 'COPYING', 'CHANGELOG'] + Dir.glob('lib/**/*.rb') - puts "files are \n%s" % files.join("\n") - begin - ri = RDoc::RDoc.new - ri.document(["--ri-site"] + files) - rescue RDoc::RDocError => detail - puts "Failed to build docs: %s" % detail - return nil - rescue LoadError - puts "Missing rdoc; cannot build documentation" - return nil - end - end - else - warn "No rdoc; skipping ri." - end - end - - desc "Install the application using the standard install.rb script" - task :install do - ruby "install.rb" - end - - def mktaskdefault - if dtask = self.defaulttask - desc "Default task" - task :default => dtask - end - end - - desc "Run all unit tests." - task :alltests do - if FileTest.exists?("spec/Rakefile") - sh %{cd spec; rake} - else - Dir.chdir("spec") do - Dir.entries(".").find_all { |f| f =~ /\.rb/ }.each do |f| - sh %{ruby #{f}} - end - end - end - end - - desc "List all ruby files" - task :rubyfiles do - puts Dir['**/*.rb'].reject { |fn| fn =~ /^pkg/ } - puts Dir['**/bin/*'].reject { |fn| fn =~ /svn|(~$)|(\.rb$)/ } - end - - desc "Look for TODO and FIXME tags in the code" - task :todo do - egrep "/#.*(FIXME|TODO|TBD)/" - end - - # This task requires extra information from the Rake file. - def mkgemtask - # ==================================================================== - # Create a task that will package the Rake software into distributable - # tar, zip and gem files. - if ! defined?(Gem) - puts "Package Target requires RubyGEMs" - else - spec = Gem::Specification.new { |s| - - #### Basic information. - - s.name = self.name - s.version = self.version - s.summary = self.summary - s.description = self.description - s.platform = Gem::Platform::RUBY - - #### Dependencies and requirements. - - # I'd love to explicitly list all of the libraries that I need, - # but gems seem to only be able to handle dependencies on other - # gems, which is, um, stupid. - self.requires.each do |name, version| - s.add_dependency(name, ">= #{version}") - end - - s.files = filelist.to_a - - #### Signing key and cert chain - #s.signing_key = '/..../gem-private_key.pem' - #s.cert_chain = ['gem-public_cert.pem'] - - #### Author and project details. - - s.author = [self.author] - s.email = self.email - s.homepage = self.url - s.rubyforge_project = self.rfproject - - yield s - } - - Rake::GemPackageTask.new(spec) { |pkg| - pkg.need_tar = true - } - - desc "Copy the newly created package into the downloads directory" - task :publish => [:package] do - puts Dir.getwd - sh %{cp pkg/#{@name}-#{@version}.gem #{self.publishdir}/gems} - sh %{gem generate_index -d #{self.publishdir}} - sh %{cp pkg/#{@name}-#{@version}.tgz #{self.pkgpublishdir}} - sh %{ln -sf #{@name}-#{@version}.tgz #{self.pkgpublishdir}/#{@name}-latest.tgz} - end - CLEAN.include("pkg") - end - end -end From 831d937a88e6de9e2e4ff8c3bfe025abfd2ac99a Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Wed, 13 May 2009 11:12:29 +1000 Subject: [PATCH 0364/3753] Refactor #2154 - Modified patch from Benedikt Bohm to simplify openvz and vserver detection --- lib/facter/virtual.rb | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 907f9ca73a..203d306565 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -13,18 +13,25 @@ end end - if FileTest.exists?("/proc/user_beancounters") - # openvz. can be hardware node or virtual environment - # read the init process' status file, it has laxer permissions - # than /proc/user_beancounters (so this won't fail as non-root) - txt = File.read("/proc/1/status") - if txt =~ /^envID:[[:blank:]]+0$/mi + if FileTest.exists?("/proc/vz/veinfo") + if FileTest.exists?("/proc/vz/version") result = "openvzhn" else result = "openvzve" end end + if FileTest.exists?("/proc/self/status") + txt = File.read("/proc/self/status") + if txt =~ /^(s_context|VxID):[[:blank:]]*[1-9]/ + result = "vserver" + end + end + + if FileTest.exists?("/proc/virtual") + result = "vserver_host" + end + # new Xen domains have this in dom0 not domu :( if FileTest.exists?("/proc/sys/xen/independent_wallclock") result = "xenu" @@ -70,17 +77,6 @@ result = "vmware_server" end - output = Facter::Util::Resolution.exec('mount') - if not output.nil? - output.each_line do |p| - result = "vserver" if p =~ /\/dev\/hdv1/ - end - end - - if FileTest.directory?('/proc/virtual') && result=="physical" - result = "vserver_host" - end - result end end From aecac08aa3c27c8b7e1441d726a9711da7f6dff4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benedikt=20B=C3=B6hm?= Date: Tue, 12 May 2009 08:11:46 +0100 Subject: [PATCH 0365/3753] Fix #2155 - architecture facts on Gentoo This corrects the architecture results for Gentoo on x86 and amd64 Patch from redmine --- lib/facter/architecture.rb | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/lib/facter/architecture.rb b/lib/facter/architecture.rb index bc9910a330..44fc97f419 100644 --- a/lib/facter/architecture.rb +++ b/lib/facter/architecture.rb @@ -4,9 +4,20 @@ model = Facter.value(:hardwaremodel) case model # most linuxen use "x86_64" - when 'x86_64' - Facter.value(:operatingsystem) == "Debian" ? "amd64" : model; - when /(i[3456]86|pentium)/; "i386" + when "x86_64" + case Facter.value(:operatingsystem) + when "Debian", "Gentoo" + "amd64" + else + model + end + when /(i[3456]86|pentium)/ + case Facter.value(:operatingsystem) + when "Gentoo" + "x86" + else + "i386" + end else model end From 00b192a727c3b3c573e103b78acaad2ba6215ac1 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Tue, 12 May 2009 21:31:30 +1000 Subject: [PATCH 0366/3753] Added SELinux tests --- spec/unit/selinux.rb | 48 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 spec/unit/selinux.rb diff --git a/spec/unit/selinux.rb b/spec/unit/selinux.rb new file mode 100644 index 0000000000..8afa463eb9 --- /dev/null +++ b/spec/unit/selinux.rb @@ -0,0 +1,48 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../spec_helper' + +require 'facter' + +describe "SELinux facts" do + + + after do + Facter.clear + end + + it "should return true if SELinux enabled" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + + FileTest.stubs(:exists?).returns false + File.stubs(:read).with("/proc/self/attr/current").returns("notkernel") + + FileTest.expects(:exists?).with("/selinux/enforce").returns true + FileTest.expects(:exists?).with("/proc/self/attr/current").returns true + File.expects(:read).with("/proc/self/attr/current").returns("kernel") + + Facter.fact(:selinux).value.should == "true" + end + + it "should return true if SELinux policy enabled" do + Facter.fact(:selinux).stubs(:value).returns("true") + + FileTest.stubs(:exists?).returns false + File.stubs(:read).with("/selinux/enforce").returns("0") + + FileTest.expects(:exists?).with("/selinux/enforce").returns true + File.expects(:read).with("/selinux/enforce").returns("1") + + Facter.fact(:selinux_enforced).value.should == "true" + end + + it "should return an SELinux policy version" do + Facter.fact(:selinux).stubs(:value).returns("true") + + File.stubs(:read).with("/selinux/policyvers").returns("") + + File.expects(:read).with("/selinux/policyvers").returns("1") + + Facter.fact(:selinux_policyversion).value.should == "1" + end +end From 2fb91caa311ea20f8342289050c367f30a610827 Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Thu, 7 May 2009 08:00:08 +0100 Subject: [PATCH 0367/3753] Tests for #2227 - multiple interfaces on Darwin This seems to have been fixed functionally on master (in /Users/pnasrat/Development/facter/spec) Finished in 1.604955 seconds 183 examples, 0 failures However I want to take the opportunity to add tests to prevent regressions Paul --- .../data/darwin_ifconfig_all_with_multiple_interfaces | 10 ++++++++++ spec/unit/util/ip.rb | 6 ++++++ 2 files changed, 16 insertions(+) create mode 100644 spec/unit/data/darwin_ifconfig_all_with_multiple_interfaces diff --git a/spec/unit/data/darwin_ifconfig_all_with_multiple_interfaces b/spec/unit/data/darwin_ifconfig_all_with_multiple_interfaces new file mode 100644 index 0000000000..fb94751eea --- /dev/null +++ b/spec/unit/data/darwin_ifconfig_all_with_multiple_interfaces @@ -0,0 +1,10 @@ +lo0: flags=8049 mtu 16384 + inet6 fe80::1%lo0 prefixlen 64 scopeid 0x1 + inet 127.0.0.1 netmask 0xff000000 + inet6 ::1 prefixlen 128 +en0: flags=8863 mtu 1500 + inet6 fe80::223:6cff:fe99:602b%en1 prefixlen 64 scopeid 0x5 + inet 192.168.0.10 netmask 0xffffff00 broadcast 192.168.0.255 + ether 00:23:6c:99:60:2b + media: autoselect status: active + supported media: autoselect diff --git a/spec/unit/util/ip.rb b/spec/unit/util/ip.rb index 6290a30cc8..512c07b3f2 100644 --- a/spec/unit/util/ip.rb +++ b/spec/unit/util/ip.rb @@ -27,6 +27,12 @@ Facter::Util::IP.get_interfaces().should == ["eth0"] end + it "should return a list two interfaces on Darwin with two interfaces" do + sample_output_file = File.dirname(__FILE__) + '/../data/darwin_ifconfig_all_with_multiple_interfaces' + darwin_ifconfig = File.new(sample_output_file).read() + Facter::Util::IP.stubs(:get_all_interface_output).returns(darwin_ifconfig) + Facter::Util::IP.get_interfaces().should == ["lo0", "en0"] + end it "should return a value for a specific interface" do Facter::Util::IP.should respond_to(:get_interface_value) From 56760d34f070db4d7bb8e5fcfb7939fe3074bf49 Mon Sep 17 00:00:00 2001 From: Jim Pirzyk Date: Fri, 15 May 2009 08:27:23 +0100 Subject: [PATCH 0368/3753] Facter #2120 - Solaris support for Facter[virtual] Initial support for virtual vmware fact on Solaris Tested on VMWare Fusion and OpenSolaris Paul --- lib/facter/virtual.rb | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 203d306565..37381d4f77 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -48,25 +48,31 @@ end if result == "physical" - output = Facter::Util::Resolution.exec('lspci') - if not output.nil? - output.each_line do |p| - # --- look for the vmware video card to determine if it is virtual => vmware. - # --- 00:0f.0 VGA compatible controller: VMware Inc [VMware SVGA II] PCI Display Adapter - result = "vmware" if p =~ /VM[wW]are/ - end + output = Facter::Util::Resolution.exec('vmware-checkvm') + if $?.exitstatus == 0 + result = "vmware" else - output = Facter::Util::Resolution.exec('dmidecode') + output = Facter::Util::Resolution.exec('lspci') if not output.nil? - output.each_line do |pd| - result = "vmware" if pd =~ /VMware|Parallels/ + output.each_line do |p| + # --- look for the vmware video card to determine if it is virtual => vmware. + # --- 00:0f.0 VGA compatible controller: VMware Inc [VMware SVGA II] PCI Display Adapter + result = "vmware" if p =~ /VM[wW]are/ end else - output = Facter::Util::Resolution.exec('prtdiag') + output = Facter::Util::Resolution.exec('dmidecode') if not output.nil? output.each_line do |pd| result = "vmware" if pd =~ /VMware|Parallels/ end + elsif Facter[:kernel].value == 'SunOS' and Facter[:kernelrelease].value == '5.10' + # prtdiag only works on Solaris 10 x86 hosts + output = Facter::Util::Resolution.exec('prtdiag') + if not output.nil? + output.each_line do |pd| + result = "vmware" if pd =~ /VMware|Parallels/ + end + end end end end From 7995d053aa95047b7e1d842c156bb73fbbee4545 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Tue, 19 May 2009 08:08:20 +1000 Subject: [PATCH 0369/3753] Bumped release to 1.5.5rc2 --- Rakefile | 2 +- lib/facter.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Rakefile b/Rakefile index 5fa7c48661..636c9eab76 100644 --- a/Rakefile +++ b/Rakefile @@ -1,6 +1,6 @@ # Rakefile for facter -require 'facter' +require './lib/facter.rb' require 'rake' require 'rake/packagetask' require 'rake/gempackagetask' diff --git a/lib/facter.rb b/lib/facter.rb index b395372e74..a6a7e292b1 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -27,7 +27,7 @@ module Util; end include Comparable include Enumerable - FACTERVERSION = '1.5.4' + FACTERVERSION = '1.5.5' # = Facter # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. From b37d683b7cdb062b5e8a835a82b211a46866e119 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Wed, 20 May 2009 02:20:57 +1000 Subject: [PATCH 0370/3753] Added install.rb to Rakefile package task --- Rakefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Rakefile b/Rakefile index 636c9eab76..3026dec357 100644 --- a/Rakefile +++ b/Rakefile @@ -7,6 +7,7 @@ require 'rake/gempackagetask' FILES = FileList[ '[A-Z]*', + 'install.rb', 'bin/**/*', 'lib/**/*', 'conf/**/*', From 253fef1bd16cf96e76b8d920a4f37e6b94255027 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Wed, 20 May 2009 15:45:54 +1000 Subject: [PATCH 0371/3753] Added spec files to package list Fixed CI rake tasks --- Rakefile | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/Rakefile b/Rakefile index 3026dec357..132b1a853c 100644 --- a/Rakefile +++ b/Rakefile @@ -12,6 +12,7 @@ FILES = FileList[ 'lib/**/*', 'conf/**/*', 'etc/**/*', + 'spec/**/*' ] spec = Gem::Specification.new do |spec| @@ -51,14 +52,21 @@ task :spec do end end -require 'rubygems' -gem 'ci_reporter' -require 'ci/reporter/rake/rspec' -require 'ci/reporter/rake/test_unit' -ENV['CI_REPORTS'] = 'results' +desc "Prep CI RSpec tests" +task :ci_prep do + require 'rubygems' + begin + gem 'ci_reporter' + require 'ci/reporter/rake/rspec' + require 'ci/reporter/rake/test_unit' + ENV['CI_REPORTS'] = 'results' + rescue LoadError + puts 'Missing ci_reporter gem. You must have the ci_reporter gem installed to run the CI spec tests' + end +end -desc "Run CI RSpec tests" -task :ci_spec => ['ci:setup:rspec', :spec] +desc "Run the CI RSpec tests" +task :ci_spec => [:ci_prep, 'ci:setup:rpsec', :spec] desc "Send patch information to the puppet-dev list" task :mail_patches do From 48aa1358d155f91e744011db6db2d41f3feed5bd Mon Sep 17 00:00:00 2001 From: Todd Zullinger Date: Tue, 19 May 2009 21:13:50 -0400 Subject: [PATCH 0372/3753] Fix operatingsystemrelease for CentOS < 5 On older CentOS releases, a sed command is used to parse the release number from /etc/redhat-release. However, the command lacked the proper amount of backslashes to escape the parenthesis. Signed-off-by: Paul Nasrat --- lib/facter/operatingsystemrelease.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index d6e6e86db3..4393bfa1e2 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -53,7 +53,7 @@ Facter.add(:operatingsystemrelease) do confine :operatingsystem => %w{CentOS} setcode do - centos_release = Facter::Util::Resolution.exec("sed -r -e 's/CentOS release //' -e 's/ \((Branch|Final)\)//' /etc/redhat-release") + centos_release = Facter::Util::Resolution.exec("sed -r -e 's/CentOS release //' -e 's/ \\((Branch|Final)\\)//' /etc/redhat-release") if centos_release =~ /5/ release = Facter::Util::Resolution.exec('rpm -q --qf \'%{VERSION}.%{RELEASE}\' centos-release | cut -d. -f1,2') else From b533e78689951cdf5989cb1014680958c903ab9e Mon Sep 17 00:00:00 2001 From: Todd Zullinger Date: Tue, 19 May 2009 21:16:49 -0400 Subject: [PATCH 0373/3753] Tighten operatingsystemrelease regex on CentOS < 5 The previous check for /5/ matched releases like 4.5, which is not the intent. The previous check was introduced in 095eb15e, and changed the pattern from /^5^/. Using /^5/ to match when the release begins with 5 seems saner. Signed-off-by: Paul Nasrat --- lib/facter/operatingsystemrelease.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index 4393bfa1e2..0864b4d050 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -54,7 +54,7 @@ confine :operatingsystem => %w{CentOS} setcode do centos_release = Facter::Util::Resolution.exec("sed -r -e 's/CentOS release //' -e 's/ \\((Branch|Final)\\)//' /etc/redhat-release") - if centos_release =~ /5/ + if centos_release =~ /^5/ release = Facter::Util::Resolution.exec('rpm -q --qf \'%{VERSION}.%{RELEASE}\' centos-release | cut -d. -f1,2') else release = centos_release From 68e0b243d2098c7e83187a119baabeaac5acb81d Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Fri, 22 May 2009 08:20:46 +0100 Subject: [PATCH 0374/3753] Fix #2278 Revert fix for 2120 Facter #2120 - Solaris support for Facter[virtual] This reverts commit 56760d34f070db4d7bb8e5fcfb7939fe3074bf49. This patch is broken as $? global will report last run process in the case of no vmware-checkvm binary --- lib/facter/virtual.rb | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 37381d4f77..203d306565 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -48,31 +48,25 @@ end if result == "physical" - output = Facter::Util::Resolution.exec('vmware-checkvm') - if $?.exitstatus == 0 - result = "vmware" + output = Facter::Util::Resolution.exec('lspci') + if not output.nil? + output.each_line do |p| + # --- look for the vmware video card to determine if it is virtual => vmware. + # --- 00:0f.0 VGA compatible controller: VMware Inc [VMware SVGA II] PCI Display Adapter + result = "vmware" if p =~ /VM[wW]are/ + end else - output = Facter::Util::Resolution.exec('lspci') + output = Facter::Util::Resolution.exec('dmidecode') if not output.nil? - output.each_line do |p| - # --- look for the vmware video card to determine if it is virtual => vmware. - # --- 00:0f.0 VGA compatible controller: VMware Inc [VMware SVGA II] PCI Display Adapter - result = "vmware" if p =~ /VM[wW]are/ + output.each_line do |pd| + result = "vmware" if pd =~ /VMware|Parallels/ end else - output = Facter::Util::Resolution.exec('dmidecode') + output = Facter::Util::Resolution.exec('prtdiag') if not output.nil? output.each_line do |pd| result = "vmware" if pd =~ /VMware|Parallels/ end - elsif Facter[:kernel].value == 'SunOS' and Facter[:kernelrelease].value == '5.10' - # prtdiag only works on Solaris 10 x86 hosts - output = Facter::Util::Resolution.exec('prtdiag') - if not output.nil? - output.each_line do |pd| - result = "vmware" if pd =~ /VMware|Parallels/ - end - end end end end From 365cb8ef85e0929c3a5ed4f7f300191accb5fb51 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Fri, 22 May 2009 23:28:30 +1000 Subject: [PATCH 0375/3753] CHANGELOG updates --- CHANGELOG | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index bd9420da0a..da39837f54 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,8 @@ 1.5.5: + Bug #2291 (Closed): Fix operatingsystemrelease for CentOS < 5 + + Bug #2278 (Closed): Virtual fact incorrect on Solaris sparc + Cleaned up Rakefile and removed requirement for Reductive Labs build library Fixed #2131 (Closed): Facter doesn't populate lsbmajdistrelease on OEL From dad4569dd715e4cd6176329503e8502768bca47b Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Fri, 22 May 2009 23:47:52 +1000 Subject: [PATCH 0376/3753] Added path to Rakefile --- Rakefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Rakefile b/Rakefile index 132b1a853c..fd8306c23a 100644 --- a/Rakefile +++ b/Rakefile @@ -1,5 +1,7 @@ # Rakefile for facter +$: << File.expand_path('lib') + require './lib/facter.rb' require 'rake' require 'rake/packagetask' From d97a63ed0aec1a9541be6754f6cc0efddc3d01f5 Mon Sep 17 00:00:00 2001 From: Todd Zullinger Date: Fri, 22 May 2009 19:59:04 -0400 Subject: [PATCH 0377/3753] Sync rpm spec file with latest from Fedora/EPEL The spec file now uses the install.rb script rather than manually installing files. --- conf/redhat/facter.spec | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index efcb7afb8b..b907d5b4e8 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -5,12 +5,12 @@ Summary: Ruby module for collecting simple facts about a host operating system Name: facter -Version: 1.5.2 +Version: 1.5.5 Release: 1%{?dist} License: GPLv2+ Group: System Environment/Base URL: http://reductivelabs.com/projects/facter -Source0: http://reductivelabs.com/downloads/facter/%{name}-%{version}.tgz +Source0: http://reductivelabs.com/downloads/facter/%{name}-%{version}.tar.gz BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) %if %has_ruby_noarch BuildArch: noarch @@ -32,24 +32,10 @@ operating system. Additional facts can be added through simple Ruby scripts %setup -q %build -sed -i -e 's@^#!.*$@#! /usr/bin/ruby@' bin/facter %install rm -rf %{buildroot} -mkdir -p %{buildroot} - -%{__install} -d -m0755 %{buildroot}%{ruby_sitelibdir} -%{__install} -d -m0755 %{buildroot}%{ruby_sitelibdir}/facter -%{__install} -d -m0755 %{buildroot}%{ruby_sitelibdir}/facter/util -%{__install} -d -m0755 %{buildroot}%{ruby_sitelibdir}/facter/util/plist -%{__install} -d -m0755 %{buildroot}%{_bindir} -%{__install} -d -m0755 %{buildroot}%{_docdir}/%{name}-%{version} - -%{__install} -p -m0644 lib/*.rb %{buildroot}%{ruby_sitelibdir} -%{__install} -p -m0644 lib/facter/*.rb %{buildroot}%{ruby_sitelibdir}/facter -%{__install} -p -m0644 lib/facter/util/*.rb %{buildroot}%{ruby_sitelibdir}/facter/util -%{__install} -p -m0644 lib/facter/util/plist/*.rb %{buildroot}%{ruby_sitelibdir}/facter/util/plist -%{__install} -p -m0755 bin/facter %{buildroot}%{_bindir} +ruby install.rb --destdir=%{buildroot} --quick --no-rdoc %clean rm -rf %{buildroot} @@ -64,6 +50,14 @@ rm -rf %{buildroot} %changelog +* Fri May 22 2009 Todd Zullinger - 1.5.5-1 +- Update to 1.5.5 +- Drop upstreamed libperms patch + +* Sat Feb 28 2009 Todd Zullinger - 1.5.4-1 +- New version +- Use upstream install script + * Tue Sep 09 2008 Todd Zullinger - 1.5.2-1 - New version - Simplify spec file checking for Fedora and RHEL versions From 96c015cb651e0e3781755cac2974c02c6bb831ae Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Thu, 28 May 2009 07:16:00 +1000 Subject: [PATCH 0378/3753] Added spec.executables to Facter gemspec in Rakefile --- Rakefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Rakefile b/Rakefile index fd8306c23a..cf824f5f6c 100644 --- a/Rakefile +++ b/Rakefile @@ -21,6 +21,7 @@ spec = Gem::Specification.new do |spec| spec.platform = Gem::Platform::RUBY spec.name = 'facter' spec.files = FILES.to_a + spec.executables = %w{facter} spec.version = Facter::FACTERVERSION spec.summary = 'Facter, a system inventory tool' spec.author = 'Reductive Labs' From 806f49f4b4aa44471d62bd2e1e3c48ed7c11992e Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Mon, 1 Jun 2009 07:38:30 +1000 Subject: [PATCH 0379/3753] Added facter branding to format patch command --- Rakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index cf824f5f6c..21e8d6ee51 100644 --- a/Rakefile +++ b/Rakefile @@ -89,7 +89,7 @@ task :mail_patches do type, parent, name = $1, $2, $3 # Create all of the patches - sh "git format-patch -C -M -s -n #{parent}..HEAD" + sh "git format-patch -C -M -s -n --subject-prefix='PATCH/facter' #{parent}..HEAD" # And then mail them out. From ba44f08870a6cd647c4981439d816f48fe6b3ad8 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Wed, 3 Jun 2009 07:39:23 +1000 Subject: [PATCH 0380/3753] Removed --no-thread and --no-chain-reply-to from rake mail_patches task --- Rakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index 21e8d6ee51..0976b9ee73 100644 --- a/Rakefile +++ b/Rakefile @@ -101,7 +101,7 @@ task :mail_patches do end # Now send the mail. - sh "git send-email #{compose} --no-chain-reply-to --no-signed-off-by-cc --suppress-from --no-thread --to puppet-dev@googlegroups.com 00*.patch" + sh "git send-email #{compose} --no-signed-off-by-cc --suppress-from --to puppet-dev@googlegroups.com 00*.patch" # Finally, clean up the patches sh "rm 00*.patch" From dcdd5ceee36750bda99652969ea0cb8c5680e5b9 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Thu, 4 Jun 2009 21:59:42 +1000 Subject: [PATCH 0381/3753] Fixes #2307 - Minor fix for zone in virtual.rb --- lib/facter/virtual.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 203d306565..72bfe519b4 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -9,7 +9,7 @@ if FileTest.exists?("/sbin/zonename") z = %x{"/sbin/zonename"}.chomp if z != 'global' - result = zone + result = 'zone' end end From f4cb619a5d5575996f59f3834acb603ad167f169 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Fri, 5 Jun 2009 08:06:31 +1000 Subject: [PATCH 0382/3753] Updated CHANGELOG and bumped version for 1.5.6 --- CHANGELOG | 5 +++++ lib/facter.rb | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index da39837f54..141661da75 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,8 @@ +1.5.6: + Bug #2303: Add executable facter in spec + + Bug #2307: undefined local variable or method `zone' + 1.5.5: Bug #2291 (Closed): Fix operatingsystemrelease for CentOS < 5 diff --git a/lib/facter.rb b/lib/facter.rb index a6a7e292b1..d75f275929 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -27,7 +27,7 @@ module Util; end include Comparable include Enumerable - FACTERVERSION = '1.5.5' + FACTERVERSION = '1.5.6' # = Facter # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. From 6d7141065d87533a5c187bb1acc1206e3fc8b0b5 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sat, 27 Jun 2009 08:53:32 +1000 Subject: [PATCH 0383/3753] Fixed CI spec task --- Rakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index 0976b9ee73..244549e08b 100644 --- a/Rakefile +++ b/Rakefile @@ -69,7 +69,7 @@ task :ci_prep do end desc "Run the CI RSpec tests" -task :ci_spec => [:ci_prep, 'ci:setup:rpsec', :spec] +task :ci_spec => [:ci_prep, 'ci:setup:rspec', :spec] desc "Send patch information to the puppet-dev list" task :mail_patches do From efc30e7426a0c63678b46d88654e6e824c5b061e Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Sat, 11 Jul 2009 09:16:59 +0100 Subject: [PATCH 0384/3753] Change spec output to enable broken build debugging --- spec/spec.opts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/spec.opts b/spec/spec.opts index 2f9bf0d0ac..695852c2f7 100644 --- a/spec/spec.opts +++ b/spec/spec.opts @@ -1,3 +1,5 @@ +--format +s --colour --loadby mtime From 95e5fea3c7cbc9dbc6a5c77358593e872649d1df Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Sat, 11 Jul 2009 09:19:24 +0100 Subject: [PATCH 0385/3753] Fix broken ci build with explicit clearing before tests --- spec/unit/operatingsystem.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/spec/unit/operatingsystem.rb b/spec/unit/operatingsystem.rb index 4c3fb3bbe5..de86230a9d 100644 --- a/spec/unit/operatingsystem.rb +++ b/spec/unit/operatingsystem.rb @@ -5,7 +5,10 @@ require 'facter' describe "Operating System fact" do - + + before do + Facter.clear + end after do Facter.clear From 51c6e3da4b6f3f65aa0f1acc8bfddf383ff00036 Mon Sep 17 00:00:00 2001 From: Joe McDonagh Date: Tue, 14 Jul 2009 07:08:27 +0100 Subject: [PATCH 0386/3753] Issue #2314 OpenBSD sysctl Use OpenBSD sysctl for manufacturer facts. --- lib/facter/manufacturer.rb | 33 ++++++++++++++++++++++----------- lib/facter/util/manufacturer.rb | 15 +++++++++++++-- 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/lib/facter/manufacturer.rb b/lib/facter/manufacturer.rb index e1ac7be956..9d66465cc8 100644 --- a/lib/facter/manufacturer.rb +++ b/lib/facter/manufacturer.rb @@ -5,15 +5,26 @@ require 'facter/util/manufacturer' -query = { - '[Ss]ystem [Ii]nformation' => [ - { 'Manufacturer:' => 'manufacturer' }, - { 'Product(?: Name)?:' => 'productname' }, - { 'Serial Number:' => 'serialnumber' } - ], - '(Chassis Information|system enclosure or chassis)' => [ - { '(?:Chassis )?Type:' => 'type' } - ] -} +if Facter.value(:kernel) == "OpenBSD" + mfg_keys = { + 'hw.vendor' => 'manufacturer', + 'hw.product' => 'productname', + 'hw.serialno' => 'serialnumber' + } + + Facter::Manufacturer.sysctl_find_system_info(mfg_keys) +else + query = { + '[Ss]ystem [Ii]nformation' => [ + { 'Manufacturer:' => 'manufacturer' }, + { 'Product(?: Name)?:' => 'productname' }, + { 'Serial Number:' => 'serialnumber' } + ], + '(Chassis Information|system enclosure or chassis)' => [ + { '(?:Chassis )?Type:' => 'type' } + ] + } + + Facter::Manufacturer.dmi_find_system_info(query) +end -Facter::Manufacturer.dmi_find_system_info(query) diff --git a/lib/facter/util/manufacturer.rb b/lib/facter/util/manufacturer.rb index 954637e1ff..c609a12ccd 100644 --- a/lib/facter/util/manufacturer.rb +++ b/lib/facter/util/manufacturer.rb @@ -9,7 +9,7 @@ def self.dmi_find_system_info(name) return nil unless FileTest.exists?("/usr/sbin/dmidecode") output=%x{/usr/sbin/dmidecode 2>/dev/null} - when 'OpenBSD', 'FreeBSD' + when 'FreeBSD' return nil unless FileTest.exists?("/usr/local/sbin/dmidecode") output=%x{/usr/local/sbin/dmidecode 2>/dev/null} @@ -32,7 +32,7 @@ def self.dmi_find_system_info(name) if line =~ /#{key}/ and ( line =~ /#{value} 0x\d+ \(([-\w].*)\)\n*./ or line =~ /#{value} ([-\w].*)\n*./ ) result = $1 Facter.add(facterkey) do - confine :kernel => [ :linux, :freebsd, :netbsd, :openbsd, :sunos ] + confine :kernel => [ :linux, :freebsd, :netbsd, :sunos ] setcode do result end @@ -43,4 +43,15 @@ def self.dmi_find_system_info(name) end end end + + def self.sysctl_find_system_info(name) + name.each do |sysctlkey,facterkey| + Facter.add(facterkey) do + confine :kernel => :openbsd + setcode do + Facter::Util::Resolution.exec("sysctl -n " + sysctlkey) + end + end + end + end end From 6c9fec53138da877605f8f658ed0f5730ccd6743 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Wed, 15 Jul 2009 08:30:34 +1000 Subject: [PATCH 0387/3753] Added path fact --- lib/facter/path.rb | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 lib/facter/path.rb diff --git a/lib/facter/path.rb b/lib/facter/path.rb new file mode 100644 index 0000000000..03907c0665 --- /dev/null +++ b/lib/facter/path.rb @@ -0,0 +1,5 @@ +Facter.add(:path) do + setcode do + ENV['PATH'] + end +end From c02d3b66eb2878968befea64c3a3ab69dc9e5de8 Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Thu, 28 May 2009 08:15:01 +0100 Subject: [PATCH 0388/3753] Issue #2292 Add tests for virtual facts Add tests and utility for virtual fact detection --- lib/facter/util/virtual.rb | 27 +++++++++++++++ lib/facter/virtual.rb | 35 +++++++------------- spec/unit/util/virtual_spec.rb | 60 ++++++++++++++++++++++++++++++++++ spec/unit/virtual_spec.rb | 49 +++++++++++++++++++++++++++ 4 files changed, 147 insertions(+), 24 deletions(-) create mode 100644 lib/facter/util/virtual.rb create mode 100644 spec/unit/util/virtual_spec.rb create mode 100644 spec/unit/virtual_spec.rb diff --git a/lib/facter/util/virtual.rb b/lib/facter/util/virtual.rb new file mode 100644 index 0000000000..8db57a3fbf --- /dev/null +++ b/lib/facter/util/virtual.rb @@ -0,0 +1,27 @@ +module Facter::Util::Virtual + def self.openvz? + FileTest.exists?("/proc/vz/veinfo") + end + + def self.openvz_type + return nil unless self.openvz? + if FileTest.exists?("/proc/vz/version") + result = "openvzhn" + else + result = "openvzve" + end + end + + def self.zone? + z = Facter::Util::Resolution.exec("/sbin/zonename") + return false unless z + return z.chomp != 'global' + end + + def self.vserver? + return false unless FileTest.exists?("/proc/self/status") + txt = File.read("/proc/self/status") + return true if txt =~ /^(s_context|VxID):[[:blank:]]*[1-9]/ + return false + end +end diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 72bfe519b4..299ebb4373 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -1,32 +1,19 @@ +require 'facter/util/virtual' + Facter.add("virtual") do confine :kernel => %w{Linux FreeBSD OpenBSD SunOS} result = "physical" setcode do - require 'thread' - if FileTest.exists?("/sbin/zonename") - z = %x{"/sbin/zonename"}.chomp - if z != 'global' - result = 'zone' - end - end + result = "zone" if Facter::Util::Virtual.zone? - if FileTest.exists?("/proc/vz/veinfo") - if FileTest.exists?("/proc/vz/version") - result = "openvzhn" - else - result = "openvzve" - end + if Facter::Util::Virtual.openvz? + result = Facter::Util::Virtual.openvz_type() end - if FileTest.exists?("/proc/self/status") - txt = File.read("/proc/self/status") - if txt =~ /^(s_context|VxID):[[:blank:]]*[1-9]/ - result = "vserver" - end - end + result = "vserver" if Facter::Util::Virtual.vserver? if FileTest.exists?("/proc/virtual") result = "vserver_host" @@ -34,19 +21,19 @@ # new Xen domains have this in dom0 not domu :( if FileTest.exists?("/proc/sys/xen/independent_wallclock") - result = "xenu" + result = "xenu" end if FileTest.exists?("/sys/bus/xen") - result = "xenu" + result = "xenu" end if FileTest.exists?("/proc/xen/capabilities") txt = File.read("/proc/xen/capabilities") if txt =~ /control_d/i - result = "xen0" + result = "xen0" end end - + if result == "physical" output = Facter::Util::Resolution.exec('lspci') if not output.nil? @@ -86,7 +73,7 @@ setcode do case Facter.value(:virtual) - when "xenu", "openvzve", "vmware" + when "xenu", "openvzve", "vmware" true else false diff --git a/spec/unit/util/virtual_spec.rb b/spec/unit/util/virtual_spec.rb new file mode 100644 index 0000000000..3552c4516c --- /dev/null +++ b/spec/unit/util/virtual_spec.rb @@ -0,0 +1,60 @@ +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'facter/util/virtual' + +describe Facter::Util::Virtual do + + after do + Facter.clear + end + it "should detect openvz" do + FileTest.stubs(:exists?).with("/proc/vz/veinfo").returns(true) + Facter::Util::Virtual.should be_openvz + end + + it "should identify openvzhn when version file exists" do + Facter::Util::Virtual.stubs(:openvz?).returns(true) + FileTest.stubs(:exists?).with("/proc/vz/version").returns(true) + Facter::Util::Virtual.openvz_type().should == "openvzhn" + end + + it "should identify openvzve when no version file exists" do + Facter::Util::Virtual.stubs(:openvz?).returns(true) + FileTest.stubs(:exists?).with("/proc/vz/version").returns(false) + Facter::Util::Virtual.openvz_type().should == "openvzve" + end + + it "should identify Solaris zones when non-global zone" do + Facter::Util::Resolution.stubs(:exec).with("/sbin/zonename").returns("somezone") + Facter::Util::Virtual.should be_zone + end + + it "should not identify Solaris zones when global zone" do + Facter::Util::Resolution.stubs(:exec).with("/sbin/zonename").returns("global") + Facter::Util::Virtual.should_not be_zone + end + + it "should not detect vserver if no self status" do + FileTest.stubs(:exists?).with("/proc/self/status").returns(false) + Facter::Util::Virtual.should_not be_vserver + end + + it "should detect vserver when vxid present in process status" do + FileTest.stubs(:exists?).with("/proc/self/status").returns(true) + File.stubs(:read).with("/proc/self/status").returns("VxID: 42\n") + Facter::Util::Virtual.should be_vserver + end + + it "should detect vserver when s_context present in process status" do + FileTest.stubs(:exists?).with("/proc/self/status").returns(true) + File.stubs(:read).with("/proc/self/status").returns("s_context: 42\n") + Facter::Util::Virtual.should be_vserver + end + + it "should not detect vserver when vserver flags not present in process status" do + FileTest.stubs(:exists?).with("/proc/self/status").returns(true) + File.stubs(:read).with("/proc/self/status").returns("wibble: 42\n") + Facter::Util::Virtual.should_not be_vserver + end + +end diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb new file mode 100644 index 0000000000..68cd258846 --- /dev/null +++ b/spec/unit/virtual_spec.rb @@ -0,0 +1,49 @@ +require File.dirname(__FILE__) + '/../spec_helper' + +require 'facter' +require 'facter/util/virtual' + +describe "Virtual fact" do + + after do + Facter.clear + end + + it "should be zone on Solaris when a zone" do + Facter.fact(:kernel).stubs(:value).returns("SunOS") + Facter::Util::Virtual.stubs(:zone?).returns(true) + Facter.fact(:virtual).value.should == "zone" + end + +end + +describe "is_virtual fact" do + + after do + Facter.clear + end + + it "should be virtual when running on xen" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:virtual).stubs(:value).returns("xenu") + Facter.fact(:is_virtual).value.should == true + end + + it "should be false when running on xen0" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:virtual).stubs(:value).returns("xen0") + Facter.fact(:is_virtual).value.should == false + end + + it "should be true when running on vmware" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:virtual).stubs(:value).returns("vmware") + Facter.fact(:is_virtual).value.should == true + end + + it "should be true when running on openvz" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:virtual).stubs(:value).returns("openvzve") + Facter.fact(:is_virtual).value.should == true + end +end From f3ad66f69979cd107025b6edd07bbe6c958d447b Mon Sep 17 00:00:00 2001 From: Nigel Kersten Date: Thu, 23 Jul 2009 08:21:18 -0700 Subject: [PATCH 0389/3753] Update install.rb to cope with all OS X versions, not just 10.5 --- install.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/install.rb b/install.rb index 3be89524e2..ec16c4b047 100755 --- a/install.rb +++ b/install.rb @@ -205,13 +205,14 @@ def prepare_installation version = [Config::CONFIG["MAJOR"], Config::CONFIG["MINOR"]].join(".") libdir = File.join(Config::CONFIG["libdir"], "ruby", version) - # Mac OS X 10.5 declares bindir and sbindir as + # Mac OS X 10.5 and higher declare bindir and sbindir as # /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin # /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/sbin # which is not generally where people expect executables to be installed - if RUBY_PLATFORM == "universal-darwin9.0" - Config::CONFIG['bindir'] = "/usr/bin" - Config::CONFIG['sbindir'] = "/usr/sbin" + # These settings are appropriate defaults for all OS X versions. + if RUBY_PLATFORM =~ /^universal-darwin[\d\.]+$/ + Config::CONFIG['bindir'] = "/usr/bin" + Config::CONFIG['sbindir'] = "/usr/sbin" end if not InstallOptions.bindir.nil? From be9e484c0daaf4befb0dfbcf85bda08ce6c1effd Mon Sep 17 00:00:00 2001 From: Nigel Kersten Date: Thu, 23 Jul 2009 08:34:47 -0700 Subject: [PATCH 0390/3753] Update OS X minor version fact to cope with '10.x' values and provide test coverage switch %x{} call to Facter::Util::Resolution.exec for better testing --- lib/facter/util/macosx.rb | 10 +++++++--- spec/unit/util/macosx.rb | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/lib/facter/util/macosx.rb b/lib/facter/util/macosx.rb index f5f83f3798..6754f18e00 100644 --- a/lib/facter/util/macosx.rb +++ b/lib/facter/util/macosx.rb @@ -56,12 +56,16 @@ def self.os_overview def self.sw_vers ver = Hash.new [ "productName", "productVersion", "buildVersion" ].each do |option| - ver["macosx_#{option}"] = %x{sw_vers -#{option}}.strip + ver["macosx_#{option}"] = Facter::Util::Resolution.exec("/usr/bin/sw_vers -#{option}").strip end productversion = ver["macosx_productVersion"] if not productversion.nil? - ver["macosx_productversion_major"] = productversion.scan(/(\d+\.\d+)/)[0][0] - ver["macosx_productversion_minor"] = productversion.scan(/(\d+)\.(\d+)\.(\d+)/)[0].last + versions = productversion.scan(/(\d+)\.(\d+)\.*(\d*)/)[0] + ver["macosx_productversion_major"] = "#{versions[0]}.#{versions[1]}" + if versions[2].empty? # 10.x should be treated as 10.x.0 + versions[2] = "0" + end + ver["macosx_productversion_minor"] = versions[2] end ver end diff --git a/spec/unit/util/macosx.rb b/spec/unit/util/macosx.rb index a54301318e..283fe75d2e 100755 --- a/spec/unit/util/macosx.rb +++ b/spec/unit/util/macosx.rb @@ -44,4 +44,38 @@ Facter::Util::Macosx.expects(:profiler_data).with("SPSoftwareDataType").returns "eh" Facter::Util::Macosx.os_overview.should == "eh" end + + describe "when working out software version" do + + before do + Facter::Util::Resolution.expects(:exec).with("/usr/bin/sw_vers -productName").returns "Mac OS X" + Facter::Util::Resolution.expects(:exec).with("/usr/bin/sw_vers -buildVersion").returns "9J62" + end + + it "should have called sw_vers three times when determining software version" do + Facter::Util::Resolution.expects(:exec).with("/usr/bin/sw_vers -productVersion").returns "10.5.7" + Facter::Util::Macosx.sw_vers + end + + it "should return a hash with the correct keys when determining software version" do + Facter::Util::Resolution.expects(:exec).with("/usr/bin/sw_vers -productVersion").returns "10.5.7" + Facter::Util::Macosx.sw_vers.keys.sort.should == ["macosx_productName", + "macosx_buildVersion", + "macosx_productversion_minor", + "macosx_productversion_major", + "macosx_productVersion"].sort + end + + it "should split a product version of 'x.y.z' into separate hash entries correctly" do + Facter::Util::Resolution.expects(:exec).with("/usr/bin/sw_vers -productVersion").returns "1.2.3" + sw_vers = Facter::Util::Macosx.sw_vers + sw_vers["macosx_productversion_major"].should == "1.2" + sw_vers["macosx_productversion_minor"].should == "3" + end + + it "should treat a product version of 'x.y' as 'x.y.0" do + Facter::Util::Resolution.expects(:exec).with("/usr/bin/sw_vers -productVersion").returns "2.3" + Facter::Util::Macosx.sw_vers["macosx_productversion_minor"].should == "0" + end + end end From fe41fb80dda8c96814b7a57875331b731dd04395 Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Tue, 11 Aug 2009 17:45:40 +0100 Subject: [PATCH 0391/3753] Fix #2470 - duplicate entries in interfaces fact Solaris orders inet and inet6 seperately. This tests for and fixes this by uniqueing the list. Will probably need work when we get to ipv6 support. --- lib/facter/util/ip.rb | 2 +- .../data/solaris_ifconfig_all_with_multiple_interfaces | 8 ++++++++ spec/unit/util/ip.rb | 7 +++++++ 3 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 spec/unit/data/solaris_ifconfig_all_with_multiple_interfaces diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index fb32d70a9d..9fb70343ea 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -51,7 +51,7 @@ def self.get_interfaces # at the end of interfaces. So, we have to trim those trailing # characters. I tried making the regex better but supporting all # platforms with a single regex is probably a bit too much. - output.scan(/^\w+[.:]?\d+[.:]?\d*[.:]?\w*/).collect { |i| i.sub(/:$/, '') } + output.scan(/^\w+[.:]?\d+[.:]?\d*[.:]?\w*/).collect { |i| i.sub(/:$/, '') }.uniq end def self.get_all_interface_output diff --git a/spec/unit/data/solaris_ifconfig_all_with_multiple_interfaces b/spec/unit/data/solaris_ifconfig_all_with_multiple_interfaces new file mode 100644 index 0000000000..f04ad5df0d --- /dev/null +++ b/spec/unit/data/solaris_ifconfig_all_with_multiple_interfaces @@ -0,0 +1,8 @@ +lo0: flags=2001000849 mtu 8232 index 1 + inet 127.0.0.1 netmask ff000000 +e1000g0: flags=201004843 mtu 1500 index 2 + inet 192.168.162.130 netmask ffffff00 broadcast 192.168.162.255 +lo0: flags=2002000849 mtu 8252 index 1 + inet6 ::1/128 +e1000g0: flags=202004841 mtu 1500 index 2 + inet6 fe80::20c:29ff:fe09:627e/10 diff --git a/spec/unit/util/ip.rb b/spec/unit/util/ip.rb index 512c07b3f2..60ec09e6d1 100644 --- a/spec/unit/util/ip.rb +++ b/spec/unit/util/ip.rb @@ -34,6 +34,13 @@ Facter::Util::IP.get_interfaces().should == ["lo0", "en0"] end + it "should return a list two interfaces on Solaris with two interfaces multiply reporting" do + sample_output_file = File.dirname(__FILE__) + '/../data/solaris_ifconfig_all_with_multiple_interfaces' + solaris_ifconfig = File.new(sample_output_file).read() + Facter::Util::IP.stubs(:get_all_interface_output).returns(solaris_ifconfig) + Facter::Util::IP.get_interfaces().should == ["lo0", "e1000g0"] + end + it "should return a value for a specific interface" do Facter::Util::IP.should respond_to(:get_interface_value) end From 0e0483ae3f00d408766a850823e3120caedfb9fc Mon Sep 17 00:00:00 2001 From: Diego Algorta Date: Thu, 13 Aug 2009 14:49:12 -0300 Subject: [PATCH 0392/3753] Fix bug where you'd get an 'undefined method' error if trying to access a fact's value when collection has not being yet initialized. --- lib/facter.rb | 2 +- spec/unit/facter.rb | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/facter.rb b/lib/facter.rb index d75f275929..6e05495eba 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -120,7 +120,7 @@ def method_missing(name, *args) name = name.to_s.sub(/\?$/,'') end - if fact = @collection.fact(name) + if fact = collection.fact(name) if question value = fact.value.downcase args.each do |arg| diff --git a/spec/unit/facter.rb b/spec/unit/facter.rb index bdef726dda..f248aa7673 100755 --- a/spec/unit/facter.rb +++ b/spec/unit/facter.rb @@ -82,6 +82,22 @@ end end + describe "when asked for a fact as an undefined Facter class method" do + describe "and the collection is already initialized" do + it "should return the fact's value" do + Facter.collection + Facter.ipaddress.should == Facter['ipaddress'].value + end + end + + describe "and the collection has been just reset" do + it "should return the fact's value" do + Facter.reset + Facter.ipaddress.should == Facter['ipaddress'].value + end + end + end + describe "when passed code as a block" do it "should execute the provided block" do Facter.add("block_testing") { setcode { "foo" } } From 5982deb61a954852534c54e1d968f3d4b156e787 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Tue, 18 Aug 2009 21:28:55 +1000 Subject: [PATCH 0393/3753] Added default Rake task --- Rakefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Rakefile b/Rakefile index 244549e08b..c02846c7b0 100644 --- a/Rakefile +++ b/Rakefile @@ -44,6 +44,10 @@ end Rake::GemPackageTask.new(spec) do |pkg| end +task :default do + sh %{rake -T} +end + desc "Run the specs under spec/" task :spec do require 'spec' From 7d4a5f921695d9502641e6da36213d912d54df5c Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Tue, 18 Aug 2009 21:34:34 +1000 Subject: [PATCH 0394/3753] Updated Rakefile and moved Rake tasks to tasks/rake directory --- Rakefile | 55 ++---------------------------------- tasks/rake/changlog.rake | 10 +++++++ tasks/rake/ci.rake | 17 +++++++++++ tasks/rake/dailybuild.rake | 9 ++++++ tasks/rake/mail_patches.rake | 35 +++++++++++++++++++++++ tasks/rake/metrics.rake | 6 ++++ 6 files changed, 80 insertions(+), 52 deletions(-) create mode 100644 tasks/rake/changlog.rake create mode 100644 tasks/rake/ci.rake create mode 100644 tasks/rake/dailybuild.rake create mode 100644 tasks/rake/mail_patches.rake create mode 100644 tasks/rake/metrics.rake diff --git a/Rakefile b/Rakefile index c02846c7b0..373d92a71e 100644 --- a/Rakefile +++ b/Rakefile @@ -1,6 +1,9 @@ # Rakefile for facter $: << File.expand_path('lib') +$LOAD_PATH << File.join(File.dirname(__FILE__), 'tasks') + +Dir['tasks/**/*.rake'].each { |t| load t } require './lib/facter.rb' require 'rake' @@ -58,55 +61,3 @@ task :spec do t.spec_files = FileList['spec/**/*.rb'] end end - -desc "Prep CI RSpec tests" -task :ci_prep do - require 'rubygems' - begin - gem 'ci_reporter' - require 'ci/reporter/rake/rspec' - require 'ci/reporter/rake/test_unit' - ENV['CI_REPORTS'] = 'results' - rescue LoadError - puts 'Missing ci_reporter gem. You must have the ci_reporter gem installed to run the CI spec tests' - end -end - -desc "Run the CI RSpec tests" -task :ci_spec => [:ci_prep, 'ci:setup:rspec', :spec] - -desc "Send patch information to the puppet-dev list" -task :mail_patches do - if Dir.glob("00*.patch").length > 0 - raise "Patches already exist matching '00*.patch'; clean up first" - end - - unless %x{git status} =~ /On branch (.+)/ - raise "Could not get branch from 'git status'" - end - branch = $1 - - unless branch =~ %r{^([^\/]+)/([^\/]+)/([^\/]+)$} - raise "Branch name does not follow // model; cannot autodetect parent branch" - end - - type, parent, name = $1, $2, $3 - - # Create all of the patches - sh "git format-patch -C -M -s -n --subject-prefix='PATCH/facter' #{parent}..HEAD" - - # And then mail them out. - - # If we've got more than one patch, add --compose - if Dir.glob("00*.patch").length > 1 - compose = "--compose" - else - compose = "" - end - - # Now send the mail. - sh "git send-email #{compose} --no-signed-off-by-cc --suppress-from --to puppet-dev@googlegroups.com 00*.patch" - - # Finally, clean up the patches - sh "rm 00*.patch" -end diff --git a/tasks/rake/changlog.rake b/tasks/rake/changlog.rake new file mode 100644 index 0000000000..0427e41bd6 --- /dev/null +++ b/tasks/rake/changlog.rake @@ -0,0 +1,10 @@ +desc "Create a ChangeLog based on git commits." +task :changelog do + CHANGELOG_DIR = "#{Dir.pwd}" + mkdir(CHANGELOG_DIR) unless File.directory?(CHANGELOG_DIR) + change_body=`git log --pretty=format:'%aD%n%an <%ae>%n%s%n'` + File.open(File.join(CHANGELOG_DIR, "ChangeLog"), 'w') do |f| + f << change_body + end +end + diff --git a/tasks/rake/ci.rake b/tasks/rake/ci.rake new file mode 100644 index 0000000000..1a79b98dec --- /dev/null +++ b/tasks/rake/ci.rake @@ -0,0 +1,17 @@ +desc "Prep CI RSpec tests" +task :ci_prep do + require 'rubygems' + begin + gem 'ci_reporter' + require 'ci/reporter/rake/rspec' + require 'ci/reporter/rake/test_unit' + ENV['CI_REPORTS'] = 'results' + rescue LoadError + puts 'Missing ci_reporter gem. You must have the ci_reporter gem installed to run the CI spec tests' + end +end + +desc "Run the CI RSpec tests" +task :ci_spec => [:ci_prep, 'ci:setup:rspec', :spec] do + sh "exit 0" +end diff --git a/tasks/rake/dailybuild.rake b/tasks/rake/dailybuild.rake new file mode 100644 index 0000000000..709bd482d0 --- /dev/null +++ b/tasks/rake/dailybuild.rake @@ -0,0 +1,9 @@ +desc "Create a Facter daily build" +task :daily => :changelog do + version = "facter" + "-" + Time.now.localtime.strftime("%Y%m%d") + sh "git archive --format=tar --prefix=#{version}/ HEAD^{tree} >#{version}.tar" + sh "pax -waf #{version}.tar -s ':^:#{version}/:' ChangeLog" + sh "rm ChangeLog" + sh "gzip -f -9 #{version}.tar" +end + diff --git a/tasks/rake/mail_patches.rake b/tasks/rake/mail_patches.rake new file mode 100644 index 0000000000..6375a2295f --- /dev/null +++ b/tasks/rake/mail_patches.rake @@ -0,0 +1,35 @@ +desc "Send patch information to the puppet-dev list" +task :mail_patches do + if Dir.glob("00*.patch").length > 0 + raise "Patches already exist matching '00*.patch'; clean up first" + end + + unless %x{git status} =~ /On branch (.+)/ + raise "Could not get branch from 'git status'" + end + branch = $1 + + unless branch =~ %r{^([^\/]+)/([^\/]+)/([^\/]+)$} + raise "Branch name does not follow // model; cannot autodetect parent branch" + end + + type, parent, name = $1, $2, $3 + + # Create all of the patches + sh "git format-patch -C -M -s -n --subject-prefix='PATCH/facter' #{parent}..HEAD" + + # And then mail them out. + + # If we've got more than one patch, add --compose + if Dir.glob("00*.patch").length > 1 + compose = "--compose" + else + compose = "" + end + + # Now send the mail. + sh "git send-email #{compose} --no-signed-off-by-cc --suppress-from --to puppet-dev@googlegroups.com 00*.patch" + + # Finally, clean up the patches + sh "rm 00*.patch" +end diff --git a/tasks/rake/metrics.rake b/tasks/rake/metrics.rake new file mode 100644 index 0000000000..63af552571 --- /dev/null +++ b/tasks/rake/metrics.rake @@ -0,0 +1,6 @@ +begin + require 'metric_fu' +rescue LoadError + # Metric-fu not installed + # http://metric-fu.rubyforge.org/ +end From 9d846b45a8422e0391c4f0f4b1835af196958ae4 Mon Sep 17 00:00:00 2001 From: Jim Pirzyk Date: Sun, 23 Aug 2009 09:57:25 -0500 Subject: [PATCH 0395/3753] Fix #2306 netmask and ipaddress on SunOS and BSDs --- lib/facter/ipaddress.rb | 4 ++-- lib/facter/netmask.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/facter/ipaddress.rb b/lib/facter/ipaddress.rb index 4c0bfe46ff..95cdc6403e 100644 --- a/lib/facter/ipaddress.rb +++ b/lib/facter/ipaddress.rb @@ -19,7 +19,7 @@ end Facter.add(:ipaddress) do - confine :kernel => %w{FreeBSD OpenBSD solaris} + confine :kernel => %w{FreeBSD OpenBSD} setcode do ip = nil output = %x{/sbin/ifconfig} @@ -39,7 +39,7 @@ end Facter.add(:ipaddress) do - confine :kernel => %w{NetBSD} + confine :kernel => %w{NetBSD SunOS} setcode do ip = nil output = %x{/sbin/ifconfig -a} diff --git a/lib/facter/netmask.rb b/lib/facter/netmask.rb index 6ccef23f4f..e4ddbc5672 100644 --- a/lib/facter/netmask.rb +++ b/lib/facter/netmask.rb @@ -9,7 +9,7 @@ require 'facter/util/netmask' Facter.add("netmask") do - confine :kernel => [ :sunos, :linux ] + confine :kernel => [ :sunos, :linux, :freebsd, :openbsd, :netbsd ] setcode do Facter::NetMask.get_netmask end From 33be9e0f5afdd81e32f2b4654f90d86b93278744 Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Sun, 23 Aug 2009 09:59:08 -0500 Subject: [PATCH 0396/3753] Add Darwin netmask support on top of Jim's patch --- lib/facter/netmask.rb | 2 +- lib/facter/util/netmask.rb | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/facter/netmask.rb b/lib/facter/netmask.rb index e4ddbc5672..bad364832c 100644 --- a/lib/facter/netmask.rb +++ b/lib/facter/netmask.rb @@ -9,7 +9,7 @@ require 'facter/util/netmask' Facter.add("netmask") do - confine :kernel => [ :sunos, :linux, :freebsd, :openbsd, :netbsd ] + confine :kernel => [ :sunos, :linux, :freebsd, :openbsd, :netbsd, :darwin ] setcode do Facter::NetMask.get_netmask end diff --git a/lib/facter/util/netmask.rb b/lib/facter/util/netmask.rb index dcbb79547c..160cdbde17 100644 --- a/lib/facter/util/netmask.rb +++ b/lib/facter/util/netmask.rb @@ -14,7 +14,13 @@ def self.get_netmask when 'SunOS' ops = { :ifconfig => '/usr/sbin/ifconfig -a', - :regex => %r{\s+ inet\s+? #{Facter.ipaddress} \+? mask (\w{8})}x, + :regex => %r{\s+ inet \s #{Facter.ipaddress} \s netmask \s (\w{8})}x, + :munge => Proc.new { |mask| mask.scan(/../).collect do |byte| byte.to_i(16) end.join('.') } + } + when 'FreeBSD','NetBSD','OpenBSD', 'Darwin' + ops = { + :ifconfig => '/sbin/ifconfig -a', + :regex => %r{\s+ inet \s #{Facter.ipaddress} \s netmask \s 0x(\w{8})}x, :munge => Proc.new { |mask| mask.scan(/../).collect do |byte| byte.to_i(16) end.join('.') } } end From 9515a403b2a3350b3b6eb2c1578c5871e9588ac2 Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Sun, 23 Aug 2009 09:09:44 -0500 Subject: [PATCH 0397/3753] Issue #2548 netblock fact We already have a network fact it's just missing a test. Paul --- spec/unit/util/ip.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/spec/unit/util/ip.rb b/spec/unit/util/ip.rb index 60ec09e6d1..e97b5df0d1 100644 --- a/spec/unit/util/ip.rb +++ b/spec/unit/util/ip.rb @@ -70,6 +70,16 @@ Facter::Util::IP.get_interface_value("e1000g0", "netmask").should == "255.255.255.0" end + it "should return calculated network information for Solaris" do + sample_output_file = File.dirname(__FILE__) + "/../data/solaris_ifconfig_single_interface" + solaris_ifconfig_interface = File.new(sample_output_file).read() + + Facter::Util::IP.stubs(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("SunOS") + + Facter::Util::IP.get_network_value("e1000g0").should == "172.16.15.0" + end + it "should return interface information for FreeBSD supported via an alias" do sample_output_file = File.dirname(__FILE__) + "/../data/6.0-STABLE_FreeBSD_ifconfig" ifconfig_interface = File.new(sample_output_file).read() From 49470cf776f2c23cabec00b68b85a1264a3f7b48 Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Sat, 5 Sep 2009 06:45:50 +0100 Subject: [PATCH 0398/3753] Fix broken solaris zone tests on EC2 This cleans up xen and vserver detection to enable us to stub out so when we happen to be running tests on xen we don't report as that. More cleanup is needed in this area but this should give us a green build. This renames the tests to be consistent with current naming convention --- lib/facter/util/virtual.rb | 16 ++++++++ lib/facter/virtual.rb | 39 +++++++++---------- .../unit/util/{virtual_spec.rb => virtual.rb} | 36 +++++++++++++++++ spec/unit/{virtual_spec.rb => virtual.rb} | 2 + 4 files changed, 73 insertions(+), 20 deletions(-) rename spec/unit/util/{virtual_spec.rb => virtual.rb} (58%) rename spec/unit/{virtual_spec.rb => virtual.rb} (92%) diff --git a/lib/facter/util/virtual.rb b/lib/facter/util/virtual.rb index 8db57a3fbf..0c3fb731ac 100644 --- a/lib/facter/util/virtual.rb +++ b/lib/facter/util/virtual.rb @@ -24,4 +24,20 @@ def self.vserver? return true if txt =~ /^(s_context|VxID):[[:blank:]]*[1-9]/ return false end + + def self.vserver_type + if self.vserver? + if FileTest.exists?("/proc/virtual") + "vserver_host" + else + "vserver" + end + end + end + + def self.xen? + ["/proc/sys/xen", "/sys/bus/xen", "/proc/xen" ].detect do |f| + FileTest.exists?(f) + end + end end diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 299ebb4373..78413a9488 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -13,24 +13,24 @@ result = Facter::Util::Virtual.openvz_type() end - result = "vserver" if Facter::Util::Virtual.vserver? - - if FileTest.exists?("/proc/virtual") - result = "vserver_host" + if Facter::Util::Virtual.vserver? + result = Facter::Util::Virtual.vserver_type() end - # new Xen domains have this in dom0 not domu :( - if FileTest.exists?("/proc/sys/xen/independent_wallclock") - result = "xenu" - end - if FileTest.exists?("/sys/bus/xen") - result = "xenu" - end + if Facter::Util::Virtual.xen? + # new Xen domains have this in dom0 not domu :( + if FileTest.exists?("/proc/sys/xen/independent_wallclock") + result = "xenu" + end + if FileTest.exists?("/sys/bus/xen") + result = "xenu" + end - if FileTest.exists?("/proc/xen/capabilities") - txt = File.read("/proc/xen/capabilities") - if txt =~ /control_d/i - result = "xen0" + if FileTest.exists?("/proc/xen/capabilities") + txt = File.read("/proc/xen/capabilities") + if txt =~ /control_d/i + result = "xen0" + end end end @@ -57,11 +57,10 @@ end end end - end - - # VMware server 1.0.3 rpm places vmware-vmx in this place, other versions or platforms may not. - if FileTest.exists?("/usr/lib/vmware/bin/vmware-vmx") - result = "vmware_server" + # VMware server 1.0.3 rpm places vmware-vmx in this place, other versions or platforms may not. + if FileTest.exists?("/usr/lib/vmware/bin/vmware-vmx") + result = "vmware_server" + end end result diff --git a/spec/unit/util/virtual_spec.rb b/spec/unit/util/virtual.rb similarity index 58% rename from spec/unit/util/virtual_spec.rb rename to spec/unit/util/virtual.rb index 3552c4516c..1f1c0f8d20 100644 --- a/spec/unit/util/virtual_spec.rb +++ b/spec/unit/util/virtual.rb @@ -57,4 +57,40 @@ Facter::Util::Virtual.should_not be_vserver end + it "should identify vserver_host when /proc/virtual exists" do + Facter::Util::Virtual.expects(:vserver?).returns(true) + FileTest.stubs(:exists?).with("/proc/virtual").returns(true) + Facter::Util::Virtual.vserver_type().should == "vserver_host" + end + + it "should identify vserver_type as vserver when /proc/virtual does not exist" do + Facter::Util::Virtual.expects(:vserver?).returns(true) + FileTest.stubs(:exists?).with("/proc/virtual").returns(false) + Facter::Util::Virtual.vserver_type().should == "vserver" + end + + it "should detect xen when /proc/sys/xen exists" do + FileTest.expects(:exists?).with("/proc/sys/xen").returns(true) + Facter::Util::Virtual.should be_xen + end + + it "should detect xen when /sys/bus/xen exists" do + FileTest.expects(:exists?).with("/proc/sys/xen").returns(false) + FileTest.expects(:exists?).with("/sys/bus/xen").returns(true) + Facter::Util::Virtual.should be_xen + end + + it "should detect xen when /proc/xen exists" do + FileTest.expects(:exists?).with("/proc/sys/xen").returns(false) + FileTest.expects(:exists?).with("/sys/bus/xen").returns(false) + FileTest.expects(:exists?).with("/proc/xen").returns(true) + Facter::Util::Virtual.should be_xen + end + + it "should not detect xen when no sysfs/proc xen directories exist" do + FileTest.expects(:exists?).with("/proc/sys/xen").returns(false) + FileTest.expects(:exists?).with("/sys/bus/xen").returns(false) + FileTest.expects(:exists?).with("/proc/xen").returns(false) + Facter::Util::Virtual.should_not be_xen + end end diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual.rb similarity index 92% rename from spec/unit/virtual_spec.rb rename to spec/unit/virtual.rb index 68cd258846..cc72ffc1b1 100644 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual.rb @@ -12,6 +12,8 @@ it "should be zone on Solaris when a zone" do Facter.fact(:kernel).stubs(:value).returns("SunOS") Facter::Util::Virtual.stubs(:zone?).returns(true) + Facter::Util::Virtual.stubs(:vserver?).returns(false) + Facter::Util::Virtual.stubs(:xen?).returns(false) Facter.fact(:virtual).value.should == "zone" end From bfe8a2a9e7a03c2a09273ef74d59e2843f5359ae Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Sat, 5 Sep 2009 21:35:01 +0100 Subject: [PATCH 0399/3753] Fix 2455 - improve error handling on fact load Fix facts added with empty blocks by handling calls to value when setcode not called Ensure we handle load failures more gracefully --- lib/facter/util/loader.rb | 6 +++++- lib/facter/util/resolution.rb | 1 + spec/unit/util/loader.rb | 10 ++++++++++ spec/unit/util/resolution.rb | 21 +++++++++++++++++++++ 4 files changed, 37 insertions(+), 1 deletion(-) diff --git a/lib/facter/util/loader.rb b/lib/facter/util/loader.rb index c6013cc42e..ac90c48e4d 100644 --- a/lib/facter/util/loader.rb +++ b/lib/facter/util/loader.rb @@ -69,7 +69,11 @@ def load_dir(dir) def load_file(file) # We have to specify Kernel.load, because we have a load method. - Kernel.load(file) + begin + Kernel.load(file) + rescue ScriptError => detail + warn "Error loading fact #{file} #{detail}" + end end # Load facts from the environment. If no name is provided, diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 04d5e47376..b9e28e8d84 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -111,6 +111,7 @@ def to_s # How we get a value for our resolution mechanism. def value result = nil + return result if @code == nil and @interpreter == nil begin Timeout.timeout(limit) do if @code.is_a?(Proc) diff --git a/spec/unit/util/loader.rb b/spec/unit/util/loader.rb index 945d3af09f..0a280208b1 100755 --- a/spec/unit/util/loader.rb +++ b/spec/unit/util/loader.rb @@ -201,6 +201,16 @@ def with_env(values) @loader.load_all end + it "should not raise an exception when a file is unloadable" do + @loader.expects(:search_path).returns %w{/one/dir} + Dir.expects(:entries).with("/one/dir").returns %w{a.rb} + + Kernel.expects(:load).with("/one/dir/a.rb").raises(LoadError) + @loader.expects(:warn) + + lambda { @loader.load_all }.should_not raise_error + end + it "should load all facts from the environment" do Facter.expects(:add).with('one') Facter.expects(:add).with('two') diff --git a/spec/unit/util/resolution.rb b/spec/unit/util/resolution.rb index 35581e5e10..d4bb7811b6 100755 --- a/spec/unit/util/resolution.rb +++ b/spec/unit/util/resolution.rb @@ -25,6 +25,14 @@ Facter::Util::Resolution.new("yay").limit.should == 0 end + it "should default to nil for code" do + Facter::Util::Resolution.new("yay").code.should be_nil + end + + it "should default to nil for interpreter" do + Facter::Util::Resolution.new("yay").interpreter.should be_nil + end + it "should provide a 'limit' method that returns the timeout" do res = Facter::Util::Resolution.new("yay") res.timeout = "testing" @@ -71,6 +79,13 @@ @resolve = Facter::Util::Resolution.new("yay") end + describe "and setcode has not been called" do + it "should return nil" do + Facter::Util::Resolution.expects(:exec).with(nil, nil).never + @resolve.value.should be_nil + end + end + describe "and the code is a string" do it "should return the result of executing the code with the interpreter" do @resolve.setcode "/bin/foo" @@ -103,11 +118,17 @@ @resolve.value.should be_nil end + it "should return nil if the value is an empty block" do + @resolve.setcode { "" } + @resolve.value.should be_nil + end + it "should use its limit method to determine the timeout, to avoid conflict when a 'timeout' method exists for some other reason" do @resolve.expects(:timeout).never @resolve.expects(:limit).returns "foo" Timeout.expects(:timeout).with("foo") + @resolve.setcode { sleep 2; "raise This is a test"} @resolve.value end From 7623e254fa35a8c15a49b63ea06d28ca2a1a40d4 Mon Sep 17 00:00:00 2001 From: Kurt Keller Date: Sat, 5 Sep 2009 15:45:43 +0100 Subject: [PATCH 0400/3753] Fix errors when alias IP's are defined --- lib/facter/util/ip.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index 9fb70343ea..5941d38c31 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -84,6 +84,13 @@ def self.get_bonding_master(interface) if not FileTest.executable?("/sbin/ip") return nil end + # A bonding interface can never be an alias interface. Alias + # interfaces do have a colon in their name and the ip link show + # command throws an error message when we pass it an alias + # interface. + if interface =~ /:/ + return nil + end regex = /SLAVE[,>].* (bond[0-9]+)/ ethbond = regex.match(%x{/sbin/ip link show #{interface}}) if ethbond From eb3a8a768119005931003161299a5fb218357b08 Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Wed, 9 Sep 2009 15:01:10 +0100 Subject: [PATCH 0401/3753] Issue #2414 - add unit test This adds a unit test on top of Kurt's patch --- spec/unit/util/ip.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/spec/unit/util/ip.rb b/spec/unit/util/ip.rb index e97b5df0d1..222ce9a008 100644 --- a/spec/unit/util/ip.rb +++ b/spec/unit/util/ip.rb @@ -130,6 +130,12 @@ Facter::Util::IP.get_interface_value("en1", "netmask").should == "255.255.255.0" end + + it "should not get bonding master on interface aliases" do + Facter.stubs(:value).with(:kernel).returns("Linux") + + Facter::Util::IP.get_bonding_master("eth0:1").should be_nil + end [:freebsd, :netbsd, :openbsd, :sunos, :darwin].each do |platform| it "should require conversion from hex on #{platform}" do From 5bc8db33bf1fe672375b4666464c3fad06f093ec Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Fri, 11 Sep 2009 14:22:11 +1000 Subject: [PATCH 0402/3753] Incremented version and updated CHANGELOG --- CHANGELOG | 14 ++++++++++++++ lib/facter.rb | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 141661da75..678a8ff9d7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,17 @@ +1.5.7 + * Bug #2306: Facter[netmask] does not exist on Solaris and FreeBSD systems + * Bug #2312: The operatingsystemrelease fact does not account for non-integer Fedora and RHEL releases. + * Bug #2314: OpenBSD uses dmidecode for manufacturer info, should use native sysctl facility + * Bug #2407: Broken CI build + * Bug #2414: errors when alias IP's are defined + * Bug #2415: patch for first-rev Mac OS X versions + * Bug #2431: Facter binary installs in wrong location on Mac OS 10.6 developer seed + * Bug #2455: Misleading/ambiguous error message on invalid fact code should be changed + * Bug #2470: interfaces fact has duplicate entries under Solaris + * Bug #2535: undefined method when trying to access a fact's value through the Facter class method shortcut and @collection is not initialized + * Feature #2548: Netblock fact + * Refactor #2292: Add tests for virtual.rb + 1.5.6: Bug #2303: Add executable facter in spec diff --git a/lib/facter.rb b/lib/facter.rb index 6e05495eba..3c69a94812 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -27,7 +27,7 @@ module Util; end include Comparable include Enumerable - FACTERVERSION = '1.5.6' + FACTERVERSION = '1.5.7' # = Facter # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. From 4bc05e92017604c27719bfe02b4e5973be2e16a4 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Fri, 11 Sep 2009 14:23:50 +1000 Subject: [PATCH 0403/3753] Added new format ChangeLog --- ChangeLog | 1707 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1707 insertions(+) create mode 100644 ChangeLog diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000000..c888a7dc16 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,1707 @@ +Fri, 11 Sep 2009 14:22:11 +1000 +James Turnbull +Incremented version and updated CHANGELOG + +Wed, 9 Sep 2009 15:01:10 +0100 +Paul Nasrat +Issue #2414 - add unit test + +Sat, 5 Sep 2009 15:45:43 +0100 +Kurt Keller +Fix errors when alias IP's are defined + +Sat, 5 Sep 2009 21:35:01 +0100 +Paul Nasrat +Fix 2455 - improve error handling on fact load + +Sat, 5 Sep 2009 06:45:50 +0100 +Paul Nasrat +Fix broken solaris zone tests on EC2 + +Sun, 23 Aug 2009 09:09:44 -0500 +Paul Nasrat +Issue #2548 netblock fact + +Sun, 23 Aug 2009 09:59:08 -0500 +Paul Nasrat +Add Darwin netmask support on top of Jim's patch + +Sun, 23 Aug 2009 09:57:25 -0500 +Jim Pirzyk +Fix #2306 netmask and ipaddress on SunOS and BSDs + +Tue, 18 Aug 2009 21:34:34 +1000 +James Turnbull +Updated Rakefile and moved Rake tasks to tasks/rake directory + +Tue, 18 Aug 2009 21:28:55 +1000 +James Turnbull +Added default Rake task + +Thu, 13 Aug 2009 14:49:12 -0300 +Diego Algorta +Fix bug where you'd get an 'undefined method' error if trying to access a fact's value when collection has not being yet initialized. + +Tue, 11 Aug 2009 17:45:40 +0100 +Paul Nasrat +Fix #2470 - duplicate entries in interfaces fact + +Thu, 23 Jul 2009 08:34:47 -0700 +Nigel Kersten +Update OS X minor version fact to cope with '10.x' values and provide test coverage + +Thu, 23 Jul 2009 08:21:18 -0700 +Nigel Kersten +Update install.rb to cope with all OS X versions, not just 10.5 + +Sat, 18 Jul 2009 09:51:29 +0100 +Paul Nasrat +Merge commit 'pnasrat/tickets/master/2292' into merge + +Thu, 28 May 2009 08:15:01 +0100 +Paul Nasrat +Issue #2292 Add tests for virtual facts + +Wed, 15 Jul 2009 08:30:34 +1000 +James Turnbull +Added path fact + +Tue, 14 Jul 2009 07:08:27 +0100 +Joe McDonagh +Issue #2314 OpenBSD sysctl + +Tue, 14 Jul 2009 08:02:04 +0100 +Todd Zullinger +Fix #2060 and cleanup operatingsystemrelease + +Sat, 11 Jul 2009 09:19:24 +0100 +Paul Nasrat +Fix broken ci build with explicit clearing before tests + +Sat, 11 Jul 2009 09:16:59 +0100 +Paul Nasrat +Change spec output to enable broken build debugging + +Sat, 27 Jun 2009 08:53:32 +1000 +James Turnbull +Fixed CI spec task + +Fri, 5 Jun 2009 08:06:31 +1000 +James Turnbull +Updated CHANGELOG and bumped version for 1.5.6 + +Thu, 4 Jun 2009 21:59:42 +1000 +James Turnbull +Fixes #2307 - Minor fix for zone in virtual.rb + +Wed, 3 Jun 2009 07:39:23 +1000 +James Turnbull +Removed --no-thread and --no-chain-reply-to from rake mail_patches task + +Mon, 1 Jun 2009 07:38:30 +1000 +James Turnbull +Added facter branding to format patch command + +Thu, 28 May 2009 07:16:00 +1000 +James Turnbull +Added spec.executables to Facter gemspec in Rakefile + +Fri, 22 May 2009 19:59:04 -0400 +Todd Zullinger +Sync rpm spec file with latest from Fedora/EPEL + +Fri, 22 May 2009 23:47:52 +1000 +James Turnbull +Added path to Rakefile + +Fri, 22 May 2009 23:28:30 +1000 +James Turnbull +CHANGELOG updates + +Fri, 22 May 2009 08:20:46 +0100 +Paul Nasrat +Fix #2278 Revert fix for 2120 + +Tue, 19 May 2009 21:16:49 -0400 +Todd Zullinger +Tighten operatingsystemrelease regex on CentOS < 5 + +Tue, 19 May 2009 21:13:50 -0400 +Todd Zullinger +Fix operatingsystemrelease for CentOS < 5 + +Wed, 20 May 2009 15:45:54 +1000 +James Turnbull +Added spec files to package list + +Wed, 20 May 2009 02:20:57 +1000 +James Turnbull +Added install.rb to Rakefile package task + +Tue, 19 May 2009 08:08:20 +1000 +James Turnbull +Bumped release to 1.5.5rc2 + +Fri, 15 May 2009 08:27:23 +0100 +Jim Pirzyk +Facter #2120 - Solaris support for Facter[virtual] + +Thu, 7 May 2009 08:00:08 +0100 +Paul Nasrat +Tests for #2227 - multiple interfaces on Darwin + +Tue, 12 May 2009 21:31:30 +1000 +James Turnbull +Added SELinux tests + +Tue, 12 May 2009 08:11:46 +0100 +Benedikt Böhm +Fix #2155 - architecture facts on Gentoo + +Wed, 13 May 2009 11:12:29 +1000 +James Turnbull +Refactor #2154 - Modified patch from Benedikt Bohm to simplify openvz and vserver detection + +Tue, 12 May 2009 10:21:29 +1000 +James Turnbull +Cleaned up Rakefile and removed requirement for Reductive Labs build library + +Wed, 6 May 2009 08:14:37 +0100 +Paul Nasrat +Facter ticket 2214 - Fix facts for OVS + +Tue, 12 May 2009 10:01:39 +1000 +James Turnbull +Fixed #2131 - Facter doesn't populate lsbmajdistrelease on OEL (also OEL/OVS and other facts) + +Sun, 10 May 2009 20:16:54 +0100 +Paul Nasrat +Facter fix #2231 typo + +Mon, 11 May 2009 06:23:59 +0100 +Paul Nasrat +Fix #2236 - don't use each_line on arrays + +Sun, 10 May 2009 19:33:01 +1000 +James Turnbull +Fixed #1327 - Added SELinux facts + +Sun, 10 May 2009 19:06:20 +1000 +James Turnbull +Fixed #2119 - Added support for non-global Solaris 10 zones + +Fri, 1 May 2009 12:10:11 +0200 +Andreas Zuber +Fixed #2215 - Added support for SUSE Linux Enterprise Desktop to operatingsystem and operatingsystemrelease + +Sun, 3 May 2009 12:43:48 +1000 +James Turnbull +Added support for ArchLinux to operatingsystem fact + +Sat, 2 May 2009 21:40:22 +1000 +James Turnbull +Fixes #2169 Correctly recognises dom0 and domUs + +Mon, 27 Apr 2009 00:00:56 +1000 +James Turnbull +Partial fix for #2191 - Facter compatibility for Ruby 1.9 + +Wed, 22 Apr 2009 14:33:52 +1000 +James Turnbull +Added COPYING in and CHANGELOG updates + +Mon, 20 Apr 2009 11:52:43 -0500 +Luke Kanies +Fixing #1918 - facter --puppet always works + +Mon, 20 Apr 2009 11:40:42 -0500 +Luke Kanies +Fixing ifconfig warnings generated on OS X + +Tue, 21 Apr 2009 07:25:10 +1000 +James Turnbull +Fixed #2132 - support for named interface aliases under linux + +Tue, 7 Apr 2009 18:45:21 +0200 +Peter Meier +correctly compare values - fixes #2021 + +Tue, 17 Mar 2009 23:52:11 +1100 +James Turnbull +Fixed #2080 - IPAddress resolutions should be reordered + +Tue, 10 Mar 2009 21:33:29 +0100 +Peter Meier +Use resultion.exec util instead of which checks + +Tue, 17 Mar 2009 23:45:50 +1100 +James Turnbull +Fix to stdout in resolution.rb + +Tue, 17 Mar 2009 20:51:29 +1100 +James Turnbull +Fixed #2081 - Fixed interfaces fact for vlan subinterfaces + +Tue, 10 Mar 2009 23:05:19 +1100 +James Turnbull +Fixed #2063 - added kernelmajversion fact + +Mon, 9 Mar 2009 02:12:32 -0400 +Todd Zullinger +Fix operatingsystemrelease on Red Hat based distros + +Mon, 9 Mar 2009 02:09:12 -0400 +Todd Zullinger +Consolidate operatingsystemrelease for CentOS, Fedora, oel, ovs, and RedHat + +Sat, 7 Mar 2009 10:50:57 +1100 +James Turnbull +Fixed #2055 - SunoS Interface error + +Sat, 7 Mar 2009 02:47:52 +1100 +James Turnbull +Fixed #2044 - virtual fact thread fix + +Wed, 4 Mar 2009 19:41:05 +1100 +James Turnbull +Fix for rake task for reductive-build library + +Sun, 1 Mar 2009 17:55:17 +1100 +James Turnbull +Fixed lib install permissions + +Sun, 1 Mar 2009 09:09:16 +1100 +James Turnbull +Fixed #2040 - Facter should provide a macosx_productversion_major fact + +Sun, 22 Feb 2009 01:43:48 +0100 +duritong +Fix virtual fact if xen but /proc/virtual present + +Sun, 1 Mar 2009 08:53:28 +1100 +James Turnbull +Fixed #2003 - Added is_virtual fact + +Sat, 28 Feb 2009 10:00:09 +1100 +James Turnbull +Fixed CHANGELOG + +Sat, 28 Feb 2009 09:43:36 +1100 +James Turnbull +Fixed #2035 - Missing brace for OSX preflight + +Tue, 17 Feb 2009 19:22:54 -0500 +Ian Taylor +more consistent indentation and alignment. also removal of trailing whitespace + +Sat, 28 Feb 2009 09:28:47 +1100 +James Turnbull +Further fix #2032 - close IO + +Sat, 28 Feb 2009 09:27:22 +1100 +James Turnbull +Added EC2 facts + +Sat, 28 Feb 2009 02:45:57 +1100 +James Turnbull +Fixed #2032 - file.open hanging on /proc/uptime on some platform + +Tue, 17 Feb 2009 00:59:39 +0100 +Luke Kanies +Updated to version 1.5.4 + +Fri, 13 Feb 2009 16:00:15 +1100 +James Turnbull +Fixed #1966 - Added physicalprocessorcount fact + +Thu, 5 Feb 2009 16:52:17 +1100 +Joel W. Shea +This commit refs #1555, #1898 and fixes #1761 + +Wed, 11 Feb 2009 15:05:22 +1100 +James Turnbull +Added support for Oracle VM Server to operatingsystem and operatingsystemrelease + +Wed, 11 Feb 2009 14:42:16 +1100 +James Turnbull +Added support for Oracle Enterprise Linux to operatingsystem + +Tue, 10 Feb 2009 15:12:48 +1100 +James Turnbull +Added README.rst for Facter + +Tue, 10 Feb 2009 02:14:55 +1100 +James Turnbull +Added Reductive Labs build library + +Wed, 4 Feb 2009 22:17:28 +1100 +James Turnbull +Updated README + +Tue, 3 Feb 2009 18:40:43 -0600 +Luke Kanies +Fixing #1927 - failing facts don't kill Facter + +Wed, 4 Feb 2009 11:40:34 +1100 +James Turnbull +Fixed #1850 - Facter updates for Ruby 1.9 + +Wed, 4 Feb 2009 11:33:58 +1100 +James Turnbull +Fixed #1924 - Fixed lo / lo:0 local interface matching + +Tue, 3 Feb 2009 22:20:59 +1100 +James Turnbull +Fixed generic uptime fact + +Tue, 3 Feb 2009 22:09:17 +1100 +James Turnbull +Fixed Ubuntu operatingsystem identification + +Tue, 3 Feb 2009 21:32:16 +1100 +James Turnbull +Cleaner fix for #1926 + +Tue, 3 Feb 2009 18:53:42 +1100 +James Turnbull +Fixed #1926 - IPAddr to_s issue + +Fri, 30 Jan 2009 21:28:39 +1100 +James Turnbull +Added timezone fact + +Wed, 28 Jan 2009 19:46:08 +0100 +Luke Kanies +Updated to version 1.5.3 + +Tue, 27 Jan 2009 22:18:51 -0600 +Luke Kanies +Fixing the usage of the macosx util module; I somehow missed renaming it here + +Wed, 28 Jan 2009 12:18:09 +1100 +James Turnbull +Fixed uptime refactor issues on non-Linux platforms + +Tue, 27 Jan 2009 17:53:38 -0600 +Luke Kanies +Adding mail_patches rake task + +Tue, 27 Jan 2009 17:51:39 -0600 +Luke Kanies +Renaming Facter::Macosx to Facter::Util::Macosx + +Tue, 27 Jan 2009 17:50:21 -0600 +Luke Kanies +Fixing #1838 - profiler failures don't throw exceptions + +Tue, 27 Jan 2009 18:00:50 -0600 +Luke Kanies +Fixed #1867 - Fixed OpenSuSE detection + +Tue, 27 Jan 2009 17:58:47 -0600 +Luke Kanies +Fixing #1854 - Adding ArchLinux support + +Tue, 27 Jan 2009 19:37:54 +1100 +James Turnbull +Added network fact + +Tue, 27 Jan 2009 10:23:26 +1100 +James Turnbull +Fixed #1870 - Format all subnet masks as human-readable + +Mon, 26 Jan 2009 14:39:35 +1100 +James Turnbull +Added uptime facts + +Sat, 24 Jan 2009 12:52:07 +0000 +Paul Nasrat +Refactor - rename ipmess to interfaces + +Thu, 22 Jan 2009 11:02:16 +1100 +James Turnbull +Fixed autotest on win32 + +Thu, 15 Jan 2009 19:53:46 +0000 +Paul Nasrat +Fix bug #1870 and add interface fact support for darwin systems + +Sun, 4 Jan 2009 17:29:59 -0600 +Luke Kanies +Refactoring the IP support, and fixing #1846. + +Fri, 2 Jan 2009 15:08:26 -0600 +Luke Kanies +Fixing indentation everywhere + +Thu, 15 Jan 2009 11:41:51 -0600 +Luke Kanies +Fixing autotest, now that vendor/ is gone + +Thu, 15 Jan 2009 11:30:56 -0600 +Luke Kanies +Removing the vendor/ gems. + +Tue, 30 Dec 2008 18:16:58 -0600 +Luke Kanies +Fixing #1761 - Solaris no longer uses /etc/release + +Mon, 22 Dec 2008 19:42:03 +1100 +James Turnbull +Fixed #1791 - support for virtual fact on Solaris 10 + +Mon, 22 Dec 2008 19:39:18 +1100 +James Turnbull +Fixed #1793 - Added more Solaris 10 facts + +Tue, 9 Dec 2008 04:45:35 -0700 +rchanter +minor fix to operatingsystemversion to correctly parse /etc/release on OpenSolaris 2008.11. + +Mon, 1 Dec 2008 13:14:24 +1100 +James Turnbull +Fixed errors on unrecognised option in binary + +Thu, 27 Nov 2008 00:45:09 +1100 +James Turnbull +Added ci namespace and Rake tasks + +Thu, 13 Nov 2008 14:18:58 -0800 +Luke Kanies +Merge commit 'martin/tickets/1727' + +Wed, 12 Nov 2008 15:03:14 +0100 +Martin Englund +Fix for #1727 - id fact should not rely on whoami on Solaris + +Fri, 7 Nov 2008 10:41:19 -0800 +David Lutterkort +Sync specfile with latest from Fedora + +Wed, 5 Nov 2008 12:29:44 +1100 +James Turnbull +Removed EPM task + +Tue, 28 Oct 2008 11:53:05 +1100 +James Turnbull +Fixed #1697 - Typo in ipaddress.rb causes timeout under Solaris 10 SPARC + +Wed, 22 Oct 2008 21:55:40 +1100 +James Turnbull +Fixed #1650 - OS X package creation script should be more selective about cleaning out prior versions + +Tue, 21 Oct 2008 10:26:29 +1100 +James Turnbull +Added Ubuntu to a variety of confines + +Tue, 21 Oct 2008 09:06:54 +1100 +James Turnbull +Removed ENV path setting from virtual.rb + +Tue, 7 Oct 2008 11:29:10 +1100 +James Turnbull +Fixed #1634 - Update virtual fact to differentiate OpenVZ hardware nodes and virtual environments + +Wed, 15 Oct 2008 16:05:36 -0500 +Luke Kanies +Merge commit 'lutter/ticket/1654' + +Wed, 15 Oct 2008 11:43:29 -0700 +David Lutterkort +Revamp domain resolution + +Mon, 29 Sep 2008 12:07:51 -0500 +Luke Kanies +Fixed #1619 - Applying patch by seanmil, adding support for SLES. + +Wed, 24 Sep 2008 14:00:06 -0500 +Luke Kanies +Fixed #1509 - Fixed version recognition for SLES. + +Sat, 20 Sep 2008 11:51:11 +1000 +James Turnbull +Fixes #1582 - Fix MAC address reporting for Linux bonding slave interfaces + +Fri, 19 Sep 2008 11:49:14 +1000 +James Turnbull +Merge branch 'master' of git://reductivelabs.com/facter + +Tue, 16 Sep 2008 12:46:13 -0500 +Luke Kanies +Fixing the GPL/LGPL incompatibility by choosing the oldest-mentioned license (GPL). + +Tue, 16 Sep 2008 08:46:57 +1000 +James Turnbull +Fixed #1575 - CentOS fix for Facter SPEC file + +Tue, 16 Sep 2008 08:45:36 +1000 +James Turnbull +Merge branch 'master' of git://reductivelabs.com/facter + +Fri, 12 Sep 2008 01:10:41 +1000 +James Turnbull +Fixed #1547 - finally killed dots in IP address facts + +Fri, 12 Sep 2008 01:05:18 +1000 +James Turnbull +Fixed #1567 - fixed createpackage.sh output + +Fri, 12 Sep 2008 01:01:20 +1000 +James Turnbull +Fixed #1569 - createpackage.rb bug + +Tue, 9 Sep 2008 05:00:34 +0200 +Luke Kanies +Updated to version 1.5.2 + +Tue, 9 Sep 2008 05:00:34 +0200 +Luke Kanies +Updated to version 1.5.2 + +Tue, 9 Sep 2008 05:00:03 +0200 +Luke Kanies +Merge branch 'master' of git@reductivelabs.com:facter + +Mon, 8 Sep 2008 23:13:04 +1000 +James Turnbull +Fixes #1558 - Adjusted virtual fact to allow non-root users to execute it + +Sat, 6 Sep 2008 09:59:36 +1000 +James Turnbull +Fixes #1562 - Removed facter from PREREQS + +Fri, 5 Sep 2008 10:22:23 +1000 +James Turnbull +Fixed #1558 - Updated virtual fact for xenu and xen0 + +Fri, 5 Sep 2008 10:07:35 +1000 +James Turnbull +Fixed #1555 - added operatingsystemrelease for Solaris + +Fri, 5 Sep 2008 10:00:41 +1000 +James Turnbull +Fixed #1559 - update to dmidecode fact + +Wed, 3 Sep 2008 05:37:08 +1000 +James Turnbull +Fixed . dot escaping + +Wed, 27 Aug 2008 07:10:07 +0200 +Luke Kanies +Merge branch 'master' of git@reductivelabs.com:facter + +Wed, 27 Aug 2008 06:16:35 +0200 +Luke Kanies +Updated to version 1.5.1 + +Wed, 27 Aug 2008 06:16:35 +0200 +Luke Kanies +Updated to version 1.5.1 + +Wed, 27 Aug 2008 06:16:35 +0200 +Luke Kanies +Updated to version 1.5.1 + +Tue, 19 Aug 2008 02:55:41 +0200 +Luke Kanies +Adding a rake task for creating an archive. + +Mon, 18 Aug 2008 19:15:24 -0500 +Luke Kanies +Added a Process.waitall thread when there's a timeout, to avoid zombies. + +Mon, 18 Aug 2008 18:50:54 -0500 +Luke Kanies +Set the timeout for the host-based and resolv-based resolutions to 2. + +Mon, 18 Aug 2008 18:41:51 -0500 +Luke Kanies +Updating changelog for previous two commits + +Mon, 18 Aug 2008 18:40:32 -0500 +Luke Kanies +Applied patch by josb to fix CentOS version detection. + +Mon, 18 Aug 2008 18:38:11 -0500 +Luke Kanies +Merge commit 'pnasrat/tickets/1422' + +Mon, 18 Aug 2008 12:12:25 +0100 +Paul Nasrat +Facter fix #1422, no default timeout + +Sun, 17 Aug 2008 11:36:58 -0500 +Luke Kanies +Adding better SuSE detection for both operatingsystem and release. + +Sat, 16 Aug 2008 16:23:25 -0500 +Luke Kanies +Merge commit 'pnasrat/ticket/1425' + +Wed, 13 Aug 2008 17:25:30 +0100 +Paul Nasrat +Add unit rspec tests for ticket 1425 + +Wed, 13 Aug 2008 15:55:49 +0100 +Paul Nasrat +Extract ifconfig output to data directory + +Mon, 11 Aug 2008 13:44:23 +0100 +Paul Nasrat +Add sample test and strawman solution for IP parsing code + +Mon, 11 Aug 2008 12:20:36 +0100 +Paul Nasrat +Add module level tests for Facter::IPAddress + +Sat, 9 Aug 2008 07:31:31 +1000 +James Turnbull +Fixes #1492 - added kernelversion fact + +Fri, 8 Aug 2008 15:07:13 +0100 +Paul Nasrat +Fix ticket 1425 on Solaris + +Wed, 6 Aug 2008 18:46:12 +0100 +Paul Nasrat +Remove duplicated code paths + +Tue, 5 Aug 2008 13:46:35 -0700 +Nigel Kersten +fix terrible error with overwriting permissions + +Mon, 4 Aug 2008 07:53:36 +1000 +James Turnbull +Fixed #1490 - Added virtual fact + +Fri, 1 Aug 2008 20:45:25 -0700 +Nigel Kersten +Feature #1487: Package creation scripts for Mac OS X + +Fri, 1 Aug 2008 09:23:25 -0500 +Luke Kanies +Modified the operatingsystem fact for Debian so it looks in + +Thu, 31 Jul 2008 17:04:45 -0700 +Nigel Kersten +Feature #1478: Allow specification of --bindir --sbindir --sitelibdir --mandir --destdir in install.rb + +Thu, 31 Jul 2008 08:51:02 -0700 +Nigel Kersten +Feature #1475: CONFIG['bindir'] CONFIG['sbindir'] have undesirable defaults on OS X 10.5 + +Thu, 31 Jul 2008 10:54:47 -0500 +Luke Kanies +Merge commit 'pnasrat/ticket/1434' + +Thu, 31 Jul 2008 10:51:55 -0500 +Luke Kanies +Merge commit 'pnasrat/ticket/1436' + +Wed, 30 Jul 2008 22:35:58 +1000 +James Turnbull +Fixes #1467 - macaddress not set on Ubuntu + +Fri, 18 Jul 2008 07:06:27 +0100 +Paul Nasrat +Don't try and run lsb_release on windows + +Thu, 17 Jul 2008 18:00:51 +0100 +Paul Nasrat +Bug #1434 Don't execute which on windows + +Thu, 17 Jul 2008 14:54:52 +0100 +Paul Nasrat +Use rbconfig to detect host cpu + +Thu, 17 Jul 2008 13:44:10 +0100 +Paul Nasrat +Get DNSDomain from WMI to set domain + +Thu, 17 Jul 2008 13:31:18 +0100 +Paul Nasrat +Set macaddress on windows platform + +Thu, 17 Jul 2008 13:21:47 +0100 +Paul Nasrat +Get kernel version via wmi + +Thu, 17 Jul 2008 12:46:14 +0100 +Paul Nasrat +Use ipconfig to determine ip address + +Thu, 17 Jul 2008 12:27:54 +0100 +Paul Nasrat +Use rbconfig to detect windows as no uname binary + +Wed, 9 Jul 2008 22:40:33 +1000 +James Turnbull +Fixed Rakefile to include additional files including tests et al + +Wed, 9 Jul 2008 17:56:48 +1000 +James Turnbull +Adjusted version to be in line with previous standard + +Tue, 8 Jul 2008 23:52:15 +1000 +James Turnbull +Merge branch 'master' of git://reductivelabs.com/facter + +Tue, 8 Jul 2008 06:44:14 +0200 +Luke Kanies +Adding (apparently now required) author info to the gem spec + +Tue, 8 Jul 2008 14:36:57 +1000 +James Turnbull +Merge branch 'master' of git://reductivelabs.com/facter + +Tue, 8 Jul 2008 14:28:06 +1000 +James Turnbull +Merge branch 'master' of git://reductivelabs.com/facter + +Tue, 8 Jul 2008 06:27:29 +0200 +Luke Kanies +Updated to version 1.5 + +Tue, 8 Jul 2008 06:27:28 +0200 +Luke Kanies +Updated to version 1.5 + +Tue, 8 Jul 2008 06:27:16 +0200 +Luke Kanies +Updating the changelog for 1.5 + +Mon, 7 Jul 2008 23:21:08 -0500 +Luke Kanies +Merge commit 'turnbull/master' + +Tue, 8 Jul 2008 14:21:04 +1000 +James Turnbull +Merge branch 'master' of git://reductivelabs.com/facter + +Tue, 8 Jul 2008 14:19:58 +1000 +James Turnbull +Fixed formatting + +Mon, 7 Jul 2008 23:13:25 -0500 +Luke Kanies +Fixed #1400 - OperatingSystemRelease should now work on CentOS + +Tue, 1 Jul 2008 10:17:16 -0500 +Luke Kanies +Adding a default case for the manufacturer information. + +Sun, 22 Jun 2008 09:50:34 +1000 +James Turnbull +Further fixes #1378 - updated dmidecode for NetBSD + +Sat, 21 Jun 2008 12:49:46 +1000 +James Turnbull +Fixes #1378 - update manufacter.rb facts to support BSD + +Sat, 21 Jun 2008 11:55:29 +1000 +James Turnbull +Partial fix for #1345 - BSD interfaces with aliases now select the first address by default + +Fri, 20 Jun 2008 10:23:02 -0500 +Luke Kanies +Retaining 'timeout' as the settor, but using 'limit' as the getter. + +Thu, 19 Jun 2008 21:34:52 -0700 +Steven Hajducko +Changed 'timeout' option to 'limit' + +Thu, 19 Jun 2008 21:44:44 -0500 +Luke Kanies +Setting the timeout for the puppetversion fact to 1.5. + +Thu, 19 Jun 2008 21:44:34 -0500 +Luke Kanies +Fixing some warnings in various classes. + +Fri, 20 Jun 2008 11:26:08 +1000 +James Turnbull +Merge branch 'tickets/1.5/1376' of git://github.com/haji/facter + +Thu, 19 Jun 2008 17:58:36 -0700 +Steven Hajducko +Fixes #1376 - Display memory facts for AIX + +Thu, 19 Jun 2008 11:02:31 -0700 +Steven Hajducko +Added processorcount and type facts to AIX + +Wed, 18 Jun 2008 09:49:30 +1000 +James Turnbull +Fixes #1334 - Forced Facter to use LANG=C + +Wed, 18 Jun 2008 09:33:31 +1000 +James Turnbull +Fixes #1357 - change ps syntax for OSX and BSD + +Tue, 17 Jun 2008 17:16:24 -0500 +Luke Kanies +Merge branch 'master' of git://github.com/jamtur01/facter + +Tue, 17 Jun 2008 10:59:52 -0500 +Luke Kanies +Rejustifying all of the whitespace in the facts, yay. + +Tue, 17 Jun 2008 10:55:22 -0500 +Luke Kanies +Refactoring how recursive searches are detected. + +Tue, 17 Jun 2008 10:54:23 -0500 +Luke Kanies +Refactored so each fact resolution can specify a separate timeout, + +Mon, 16 Jun 2008 16:12:20 -0700 +Steve Hajducko +Retrieve hardwaremodel for AIX from sys0 modelname. + +Mon, 9 Jun 2008 16:34:47 -0500 +Luke Kanies +Merge commit 'turnbull/master' + +Mon, 9 Jun 2008 02:37:30 +1000 +James Turnbull +Refactered ipmess.rb and util/ip.rb to support separate *BSD logic for *BSD aliased interfaces. + +Mon, 2 Jun 2008 18:57:56 -0700 +Luke Kanies +Merge branch 'master' of git@reductivelabs.com:facter + +Mon, 2 Jun 2008 18:54:43 -0700 +Luke Kanies +Merge branch 'master' of git://github.com/jamtur01/facter + +Wed, 28 May 2008 20:38:22 +1000 +James Turnbull +Refactor of netmask fact - fixes ticket #66 + +Sun, 25 May 2008 19:18:07 -0500 +Luke Kanies +Testing gitosis + +Thu, 22 May 2008 17:39:04 +1000 +James Turnbull +Fixes for ticket #60 + +Tue, 20 May 2008 23:26:14 -0500 +Luke Kanies +Removing old test/unit tests. + +Tue, 20 May 2008 23:12:06 -0500 +Luke Kanies +Adding a timeout to fact retrieval, fixing #56. + +Mon, 19 May 2008 22:29:40 -0500 +Luke Kanies +Reverting the version. + +Mon, 19 May 2008 11:56:51 +1000 +James Turnbull +Updated CHANGELOG + +Mon, 19 May 2008 11:52:34 +1000 +James Turnbull +Added LSB Major Dist Release fact fixing #41 + +Mon, 19 May 2008 10:44:39 +1000 +James Turnbull +Added support for AIX fixing ticket #56 + +Mon, 19 May 2008 10:29:36 +1000 +James Turnbull +Updated Red Hat spec file for new version and files + +Mon, 19 May 2008 10:22:58 +1000 +James Turnbull +Incremented version number to 1.5 + +Fri, 16 May 2008 14:57:26 -0500 +Luke Kanies +Adding a --puppet option to facter to load Puppet facts. + +Fri, 16 May 2008 10:38:15 -0500 +Luke Kanies +Switching to a search path registration system. + +Fri, 16 May 2008 10:26:56 -0500 +Luke Kanies +Moving the puppet-related loading tests to an integration test. + +Fri, 16 May 2008 09:46:17 -0500 +Luke Kanies +Retrieval of fact values now autoloads facts. + +Sat, 17 May 2008 00:32:34 +1000 +James Turnbull +Updated version. Moved most facts to seperate files. + +Thu, 15 May 2008 15:40:02 -0500 +Luke Kanies +Facter no longer loads all facts by default. + +Thu, 15 May 2008 15:27:57 -0500 +Luke Kanies +Moving the version and ruby facts to a separate file. + +Thu, 15 May 2008 15:24:29 -0500 +Luke Kanies +Switching Facter to using the new loader. + +Thu, 15 May 2008 15:21:59 -0500 +Luke Kanies +Fixing the last few occurrences of Facter::Resolution instead of Facter::Util::Resolution. + +Thu, 15 May 2008 14:55:51 -0500 +Luke Kanies +Fixing the test so it doesn't break other tests. + +Thu, 15 May 2008 14:30:29 -0500 +Luke Kanies +Moving all of the support classes to util/. + +Thu, 15 May 2008 14:18:26 -0500 +Luke Kanies +Creating a 'loader' class to handle loading facts for the collection. + +Wed, 14 May 2008 10:17:18 -0500 +Luke Kanies +Adding the 'each' method back into Facter. + +Wed, 14 May 2008 09:36:32 -0500 +Luke Kanies +Updating the executable to not use Facter.each. + +Wed, 14 May 2008 00:32:41 -0500 +Luke Kanies +Fixing warnings and interfaces. + +Wed, 14 May 2008 00:24:24 -0500 +Luke Kanies +Moving Facter's container behaviour into a separate class. + +Tue, 13 May 2008 23:49:48 -0500 +Luke Kanies +Splitting the instance code into a Fact class. + +Tue, 13 May 2008 22:07:18 -0500 +Luke Kanies +Adding all of the tests for the Facter::Resolution class. + +Tue, 13 May 2008 21:28:34 -0500 +Luke Kanies +Reorganizing my new tests so they match the autotest discovery. + +Tue, 13 May 2008 21:23:22 -0500 +Luke Kanies +Simplifying Confine a bit + +Tue, 13 May 2008 21:22:20 -0500 +Luke Kanies +Splitting the different classes in Facter up, and adding some tests. + +Tue, 13 May 2008 20:49:04 -0500 +Luke Kanies +Adding autotest hooks + +Tue, 13 May 2008 20:48:52 -0500 +Luke Kanies +fixing whitespace + +Thu, 20 Mar 2008 12:12:27 +1100 +James Turnbull +Closes #1145 - fixed bad interface names by replacing : with _ + +Tue, 19 Feb 2008 15:20:02 +1100 +James Turnbull +Updated CHANGELOG + +Mon, 18 Feb 2008 17:16:39 -0600 +Luke Kanies +Merge commit 'turnbull/master' + +Sun, 17 Feb 2008 17:04:47 -0600 +Luke Kanies +Merge branch 'os_split' + +Sun, 17 Feb 2008 17:00:39 -0600 +Luke Kanies +removing .swp file + +Sun, 17 Feb 2008 16:15:10 -0600 +Luke Kanies +Switching from test/unit to rspec, and fixing a couple + +Sat, 16 Feb 2008 21:56:01 +1100 +James Turnbull +Fixed Solaris detection of lo0 for ticket #46 + +Sat, 16 Feb 2008 18:20:38 +1100 +James Turnbull +Added require util ip.rb file + +Sat, 16 Feb 2008 18:20:07 +1100 +James Turnbull +Fixed #46 - refactor ipmess.rb + +Fri, 8 Feb 2008 16:15:02 +1100 +James Turnbull +Added new files + +Fri, 8 Feb 2008 16:14:26 +1100 +James Turnbull +Further updates to split facts and move support functions + +Fri, 8 Feb 2008 14:14:24 +1100 +James Turnbull +Split out facts from facter.rb and moved all support code to util + +Thu, 7 Feb 2008 15:26:46 +1100 +James Turnbull +Added support for multiple interfaces, macaddress and netmask facts for Linux, *BSD, and Solaris + +Sun, 3 Feb 2008 04:45:11 +1100 +James Turnbull +Fixed conflict merge + +Sun, 3 Feb 2008 04:32:55 +1100 +James Turnbull +Revert "Fixed ticket #50 - added selinux facts" + +Sun, 3 Feb 2008 04:24:40 +1100 +James Turnbull +Added Ubuntu operatingsystem and operatingsystemrelease fact support + +Sat, 2 Feb 2008 17:04:41 +1100 +James Turnbull +Added Debian release version support + +Sun, 13 Jan 2008 01:53:23 +1100 +James Turnbull +Fixed ticket #50 - added selinux facts + +Sat, 22 Dec 2007 15:10:02 +1100 +James Turnbull +Fixed ticket #48 - CentOS operatingsystemrelease fact now reporting correct value + +Sun, 9 Dec 2007 19:38:04 +1100 +James Turnbull +Added Mandrake support for operatingsystem fact - closed ticket #47 + +Thu, 8 Nov 2007 08:20:37 +1100 +James Turnbull +Added index to imess.rb fixing Ticket #43. + +Mon, 5 Nov 2007 20:40:31 +1100 +James Turnbull +Fixed ticket #44 + +Mon, 24 Sep 2007 09:01:38 +0200 +Luke Kanies +Updated to version 1.3.8 + +Mon, 24 Sep 2007 09:01:38 +0200 +Luke Kanies +Updated to version 1.3.8 + +Mon, 24 Sep 2007 09:00:23 +0200 +Luke Kanies +Updating version in changelog + +Mon, 24 Sep 2007 09:00:05 +0200 +Luke Kanies +Merge branch 'master' of /opt/rl/git/facter + +Mon, 24 Sep 2007 08:59:28 +0200 +Luke Kanies +Removing the package hosts, so packages are no longer created at all + +Tue, 18 Sep 2007 13:06:39 +1000 +James Turnbull +Updated CHANGELOG + +Tue, 18 Sep 2007 11:33:22 +1000 +James Turnbull +Added require for rdoc/ri/ri_paths to address Puppet #753 and Facter #40 + +Sun, 16 Sep 2007 10:26:45 +1000 +James Turnbull +Revert "Adjusted :kernel confine to make it more in line with others" + +Sun, 16 Sep 2007 10:13:58 +1000 +root +Adjusted :kernel confine to make it more in line with others + +Thu, 13 Sep 2007 13:53:30 +1000 +James Turnbull +Updated CHANGELOG + +Thu, 13 Sep 2007 13:38:18 +1000 +James Turnbull +Added support to return multiple interfaces and their IP addresses as facts. Existing ipaddress fact which returns IP address of first interface on node is still available. Currently Linux only. Closes #6 + +Thu, 13 Sep 2007 08:51:11 +1000 +root +Added macaddress fact support for FreeBSD and OpenBSD - closes #37 + +Tue, 17 Jul 2007 20:29:17 +0000 +luke +making the install script executable + +Tue, 17 Jul 2007 20:03:35 +0000 +luke +Drastically speeding up the lsb data retrieval, and refactoring the dmidecode data so it is a bit cleaner and does not produce extraneous output or errors + +Mon, 25 Jun 2007 17:35:31 +0000 +lutter +Set operatingsystemrelease to the major release on RHEL and Fedora + +Sun, 24 Jun 2007 03:09:25 +0000 +lutter +Remove tabs; don't fail if dmidecode doesn't return expected information + +Mon, 18 Jun 2007 21:10:10 +0000 +luke +Adding manufacturer code, as requested by digant on the Puppet Trac site. + +Thu, 14 Jun 2007 22:09:41 +0000 +josb +Add YAML output option to the help text. + +Wed, 13 Jun 2007 18:33:06 +0000 +mccune +Fixed problem with executing system_profiler and sw_vers on non Darwin hosts. + +Wed, 13 Jun 2007 17:58:46 +0000 +mccune +Fixed problem where facter referenced puppet plist utility library. + +Wed, 13 Jun 2007 17:40:45 +0000 +mccune +Added a bunch of information from system_profiler -xml. In particular, sp_serial_number is interesting. Also added values from sw_vers, to get the commonly used Mac OS X version and build identifier. + +Mon, 11 Jun 2007 22:01:13 +0000 +luke +Setting the ldapname so it is guaranteed to be a string + +Tue, 5 Jun 2007 18:08:16 +0000 +luke +Applying patch from Valentin Vidic, fixing open filehandles + +Wed, 21 Mar 2007 16:44:44 +0000 +luke +Updated to version 1.3.7 + +Wed, 21 Mar 2007 16:44:43 +0000 +luke +Updated to version 1.3.7 + +Wed, 21 Mar 2007 16:35:51 +0000 +luke +Using consistent naming internally; I previously had essentially random quoting and case, but it is now all lower-case symbols. It should behave the same externally. + +Wed, 21 Mar 2007 15:47:47 +0000 +luke +Applying patch from #36 by psychedelys + +Wed, 21 Mar 2007 15:44:25 +0000 +luke +Fixing Facter.flush + +Wed, 21 Mar 2007 15:40:47 +0000 +luke +Fixing #33 -- we now only return the first mac address + +Wed, 21 Mar 2007 15:24:05 +0000 +luke +Applying patch from Adam Jacob that makes FACTERLIB work + +Sat, 24 Feb 2007 15:44:59 +0000 +luke +Applying patch from #35. + +Tue, 30 Jan 2007 18:17:03 +0000 +ajax +Fixing bug where an up interface not in active use was being selected as the canonical IP instead of using the IP attached to the interface assigned the default route. + +Tue, 23 Jan 2007 18:11:46 +0000 +lutter +Sync with Fedora specfile + +Mon, 22 Jan 2007 16:06:26 +0000 +luke +updating docs a bit + +Fri, 19 Jan 2007 22:37:24 +0000 +luke +Updated to version 1.3.6 + +Fri, 19 Jan 2007 22:37:23 +0000 +luke +Updated to version 1.3.6 + +Fri, 19 Jan 2007 22:36:53 +0000 +luke +disabling solaris package generation for facter + +Fri, 19 Jan 2007 22:32:41 +0000 +luke +updating changelog for 1.3.6 + +Thu, 4 Jan 2007 04:57:53 +0000 +luke +Applying patch from #29. + +Thu, 4 Jan 2007 04:30:42 +0000 +luke +Fixing ssh key facts so they only include the key, not the type. + +Tue, 21 Nov 2006 02:07:46 +0000 +lutter +Make specfile work for FC < 5 and RHEL < 5 + +Wed, 8 Nov 2006 20:07:57 +0000 +lutter +Reconciling with Fedora specfile + +Wed, 8 Nov 2006 19:52:34 +0000 +lutter +Do not try and check the command if which is not available; fixes trac #30 + +Fri, 29 Sep 2006 16:13:13 +0000 +luke +Updated to version 1.3.5 + +Fri, 29 Sep 2006 16:13:12 +0000 +luke +Updated to version 1.3.5 + +Fri, 29 Sep 2006 16:13:11 +0000 +luke +Updated to version 1.3.5 + +Fri, 29 Sep 2006 16:12:00 +0000 +luke +Fixing #26 -- using Resolution.exec instead of executing directly, and also calling lsb_release for every fact, instead of just once at startup + +Fri, 22 Sep 2006 05:24:25 +0000 +luke +Updated to version 1.3.4 + +Fri, 22 Sep 2006 05:24:23 +0000 +luke +Updated to version 1.3.4 + +Fri, 22 Sep 2006 05:24:22 +0000 +luke +Updated to version 1.3.4 + +Fri, 22 Sep 2006 05:24:06 +0000 +luke +updating changelog for 1.3.4 + +Wed, 20 Sep 2006 15:46:22 +0000 +luke +Adding patch from #21, adding lsb_release facts + +Tue, 19 Sep 2006 06:55:52 +0000 +luke +Adding yaml support, as requested in #24 + +Tue, 19 Sep 2006 06:50:21 +0000 +luke +applying patch from #18. + +Tue, 19 Sep 2006 06:47:45 +0000 +luke +Fixing facter so it does not fail when an unknown fact is asked for + +Tue, 19 Sep 2006 06:08:23 +0000 +luke +Sorting the facts when they are all output + +Tue, 19 Sep 2006 06:06:56 +0000 +luke +Adding fqdn fact + +Thu, 14 Sep 2006 21:37:03 +0000 +luke +Fixing #20. I just made sure that the Domain fact cchecks the hostname first, so that if the hostname is an fqdn it will set the domain from that. + +Thu, 14 Sep 2006 21:20:12 +0000 +luke +Applying patch from #22 + +Thu, 14 Sep 2006 21:17:33 +0000 +luke +Applying patch in #23. + +Thu, 14 Sep 2006 21:12:23 +0000 +luke +Applying memfree patch from #17. + +Mon, 31 Jul 2006 20:59:22 +0000 +luke +updates + +Sun, 2 Jul 2006 20:17:44 +0000 +luke +doc updates + +Sun, 2 Jul 2006 20:14:26 +0000 +luke +doc updates + +Sun, 2 Jul 2006 19:27:57 +0000 +luke +updates + +Sun, 2 Jul 2006 19:19:54 +0000 +luke +adding docs + +Sun, 2 Jul 2006 19:19:26 +0000 +luke +adding docs + +Wed, 28 Jun 2006 17:38:19 +0000 +luke +Updated to version 1.3.3 + +Wed, 28 Jun 2006 17:38:18 +0000 +luke +Updated to version 1.3.3 + +Wed, 28 Jun 2006 17:38:06 +0000 +luke +updating changelog for 1.3.3 + +Wed, 28 Jun 2006 17:34:05 +0000 +luke +Adding the ability to retrieve facts from the environment. + +Tue, 27 Jun 2006 05:35:08 +0000 +luke +Updated to version 1.3.2 + +Tue, 27 Jun 2006 05:35:06 +0000 +luke +Updated to version 1.3.2 + +Tue, 27 Jun 2006 05:34:07 +0000 +luke +simple packaging updaets + +Tue, 27 Jun 2006 05:33:41 +0000 +luke +Adding thread exclusivity to memory and cpu reading + +Tue, 27 Jun 2006 05:28:17 +0000 +luke +Re-adding these files, since Matt has found a solution to the hanging problem. + +Mon, 26 Jun 2006 22:18:08 +0000 +luke +removing processor.rb in case it has the same problems as the memory file + +Mon, 26 Jun 2006 22:17:24 +0000 +luke +Deleting this file until the hanging problems are resolved + +Tue, 20 Jun 2006 01:19:19 +0000 +luke +fixing license issues + +Tue, 20 Jun 2006 01:07:49 +0000 +luke +fixing spec file again + +Tue, 20 Jun 2006 00:54:25 +0000 +luke +Updated to version 1.3.1 + +Tue, 20 Jun 2006 00:54:23 +0000 +luke +Updated to version 1.3.1 + +Tue, 20 Jun 2006 00:54:20 +0000 +luke +Updated to version 1.3.1 + +Mon, 19 Jun 2006 19:04:48 +0000 +luke +adding a call to dnsdomainname before domainname + +Mon, 19 Jun 2006 18:12:13 +0000 +luke +Fixing #15. Just adding rescue blocks around the load statements. + +Mon, 12 Jun 2006 17:36:03 +0000 +luke +updating for 1.3 + +Mon, 12 Jun 2006 13:17:18 +0000 +lutter +Updated for use with latest Fedora ruby packages + +Fri, 9 Jun 2006 14:58:20 +0000 +luke +Updated to version 1.3 + +Fri, 9 Jun 2006 14:58:18 +0000 +luke +Updated to version 1.3 + +Fri, 9 Jun 2006 14:58:17 +0000 +luke +Updated to version 1.3 + +Wed, 31 May 2006 18:30:59 +0000 +luke +fixing installer so it does not install batch files on darwin + +Wed, 31 May 2006 18:23:04 +0000 +luke +trying to fix facterbin rubylib setting + +Wed, 31 May 2006 18:03:28 +0000 +luke +fixing test so that it works even if rubylib is not set + +Wed, 31 May 2006 17:43:48 +0000 +luke +Adding tagging frameworks back into Facter, and adding the ability to specify tags to the to_hash method so that you only receive facts tagged with specific tags + +Wed, 31 May 2006 16:44:05 +0000 +luke +fixing the linux processor stuff so it only gets called on linux + +Wed, 31 May 2006 00:45:39 +0000 +luke +changing the syntax of the fact confines + +Wed, 31 May 2006 00:41:17 +0000 +luke +Adding some documentation to the binary + +Wed, 31 May 2006 00:04:50 +0000 +luke +Adding rubysitedir fact, as requested in #13. Also, switching the output when one fact is asked for, so it only produces the single value, with no => symbol. + +Tue, 30 May 2006 23:57:01 +0000 +luke +fixing test to ignore differences in memory + +Tue, 30 May 2006 23:55:33 +0000 +luke +Switching "tag" to "confine", because it is a more appropriate term. I will also add "tags", but they will be used for creating fact collections. + +Tue, 30 May 2006 23:49:26 +0000 +luke +Adding patch from #11, with slight modifications. + +Tue, 30 May 2006 23:44:06 +0000 +luke +Adding patch from #11, with slight modifications. + +Tue, 30 May 2006 23:15:00 +0000 +luke +Adding the ability to specify tags as hashes or arrays, as requested in #112. + +Tue, 30 May 2006 22:54:26 +0000 +luke +Getting rid of the autoload method entirely; facts are now only loaded at startup. + +Tue, 30 May 2006 22:33:22 +0000 +luke +fixing linux memory stuff + +Tue, 30 May 2006 22:28:04 +0000 +luke +accepting patch in #10, although with more abstraction, and creating a module for the memory functions + +Tue, 30 May 2006 22:12:04 +0000 +luke +Accepting the patch in #9, with some modifications. + +Thu, 25 May 2006 19:32:51 +0000 +luke +adding solaris pkg stuff + +Tue, 23 May 2006 20:56:51 +0000 +luke +Updated to version 1.2.1 + +Tue, 23 May 2006 20:56:36 +0000 +luke +fixing small bug that only occurs with gems + +Tue, 23 May 2006 19:45:52 +0000 +luke +Updated to version 1.2.0 + +Tue, 23 May 2006 19:45:38 +0000 +luke +Adding final autoloading work. + +Tue, 23 May 2006 19:19:21 +0000 +luke +updating changelog for 1.2.0 + +Tue, 23 May 2006 18:33:05 +0000 +luke +adding another test for the exe + +Tue, 23 May 2006 18:25:30 +0000 +luke +Adding ruby, puppet, and facter version facts + +Tue, 23 May 2006 15:43:09 +0000 +luke +Fixing install and tests so that there are no errors, hopefully. + +Tue, 23 May 2006 15:14:11 +0000 +luke +Added "architecture" fact, added the ability to autoload facts from separate files, and added the ability to retrieve fact values via a method for each fact. + +Tue, 16 May 2006 16:01:57 +0000 +luke +Accepting the patch from #5 + +Tue, 14 Mar 2006 21:17:12 +0000 +luke +Removing ruby as a prereq + +Tue, 14 Mar 2006 07:10:43 +0000 +luke +Converting rakefile to the new build system + +Tue, 14 Mar 2006 03:25:22 +0000 +lutter +Minor changes for hte Fedora Extras review + +Thu, 2 Mar 2006 21:28:06 +0000 +luke +fixing rake file to build and copy rpms automatically + +Tue, 28 Feb 2006 16:23:46 +0000 +luke +updating changelog for 1.1.4 + +Tue, 28 Feb 2006 16:03:42 +0000 +luke +Updated to version 1.1.4 + +Tue, 28 Feb 2006 16:03:30 +0000 +luke +Fixing installer to put the facter executable in /usr/bin instead of / + +Thu, 23 Feb 2006 20:17:48 +0000 +luke +Updated to version 1.1.3 + +Thu, 23 Feb 2006 20:17:35 +0000 +luke +adding 1.1.3 changelog + +Thu, 23 Feb 2006 20:17:02 +0000 +luke +Identifying centos + +Sat, 18 Feb 2006 02:23:06 +0000 +luke +updates + +Fri, 17 Feb 2006 19:56:04 +0000 +luke +Updated to version 1.1.2 + +Fri, 17 Feb 2006 19:55:53 +0000 +luke +Adding ldapname capabilities + +Thu, 9 Feb 2006 18:26:10 +0000 +lutter +Automatically update version and release in the specfile for new releases + +Thu, 9 Feb 2006 18:25:31 +0000 +lutter +Fix specfile in accordance with Fedora Extras guidelines + +Wed, 25 Jan 2006 19:43:37 +0000 +luke +RPM creation now works + +Wed, 25 Jan 2006 19:07:00 +0000 +luke +Working on packaging + +Tue, 10 Jan 2006 22:57:50 +0000 +luke +Updated to version 1.1.1 + +Tue, 10 Jan 2006 22:57:44 +0000 +luke +Fixing bug when a fact with no resolutions is asked for + +Tue, 10 Jan 2006 22:56:16 +0000 +luke +Fixing bug when a fact with no resolutions is asked for + +Mon, 9 Jan 2006 19:55:54 +0000 +luke +Updated to version 1.1.0 + +Mon, 9 Jan 2006 19:55:40 +0000 +luke +Redoing how tags work. + +Wed, 4 Jan 2006 01:35:25 +0000 +luke +updating everything to essentially disable docs generation + +Tue, 3 Jan 2006 19:07:38 +0000 +luke +Adding Release tag + +Tue, 3 Jan 2006 19:05:32 +0000 +luke +adding extra "return nil" statements, and hopefully fixing the test for cygwin + +Tue, 3 Jan 2006 18:53:10 +0000 +luke +Updated to version 1.0.2 + +Tue, 3 Jan 2006 18:52:20 +0000 +luke +adding changelog + +Tue, 3 Jan 2006 18:51:31 +0000 +luke +adding fixes Eric Sorenson found with cygwin + +Fri, 30 Dec 2005 20:09:58 +0000 +luke +updates + +Tue, 22 Nov 2005 15:25:08 +0000 +luke +Updated to version 1.0.1 + +Tue, 22 Nov 2005 15:22:53 +0000 +luke +Modified version + +Wed, 16 Nov 2005 17:45:43 +0000 +luke +removing filehandle-based tests + +Wed, 16 Nov 2005 17:29:21 +0000 +luke +updating INSTALL with patch from ian + +Mon, 29 Aug 2005 21:30:45 +0000 +luke +moving things to the trunk From 247204854c2637fedcb0f2a8db0e53281381e4cb Mon Sep 17 00:00:00 2001 From: Todd Zullinger Date: Wed, 6 May 2009 13:50:30 -0400 Subject: [PATCH 0404/3753] Clarify licensing as GPLv2 (or any later version) --- COPYING | 25 +-- LICENSE | 683 ++------------------------------------------------------ 2 files changed, 25 insertions(+), 683 deletions(-) diff --git a/COPYING b/COPYING index 3912109b5c..d511905c16 100644 --- a/COPYING +++ b/COPYING @@ -1,8 +1,8 @@ GNU GENERAL PUBLIC LICENSE Version 2, June 1991 - Copyright (C) 1989, 1991 Free Software Foundation, Inc. - 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. @@ -15,7 +15,7 @@ software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by -the GNU Library General Public License instead.) You can apply it to +the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not @@ -55,7 +55,7 @@ patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. - + GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION @@ -110,7 +110,7 @@ above, provided that you also meet all of these conditions: License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) - + These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in @@ -168,7 +168,7 @@ access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. - + 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is @@ -225,7 +225,7 @@ impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. - + 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License @@ -278,7 +278,7 @@ PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS - + How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest @@ -303,10 +303,9 @@ the "copyright" line and a pointer to where the full notice is found. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. @@ -336,5 +335,5 @@ necessary. Here is a sample; alter the names: This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Library General +library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. diff --git a/LICENSE b/LICENSE index 94a9ed024d..308f4e000d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,674 +1,17 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 +Facter - Host Fact Detection and Reporting. Copyright (C) 2005 Reductive Labs LLC - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. +Reductive Labs can be contacted at: info@reductivelabs.com - Preamble +This program and entire repository is free software; you can +redistribute it and/or modify it under the terms of the GNU +General Public License as published by the Free Software +Foundation; either version 2 of the License, or any later version. - The GNU General Public License is a free, copyleft license for -software and other kinds of works. +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA From 71944542558bcc6d60e63b52a9e93e692995f04b Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Thu, 17 Sep 2009 16:11:57 +1000 Subject: [PATCH 0405/3753] Updated CI Rake task --- tasks/rake/ci.rake | 1 - 1 file changed, 1 deletion(-) diff --git a/tasks/rake/ci.rake b/tasks/rake/ci.rake index 1a79b98dec..e0e5aed8da 100644 --- a/tasks/rake/ci.rake +++ b/tasks/rake/ci.rake @@ -13,5 +13,4 @@ end desc "Run the CI RSpec tests" task :ci_spec => [:ci_prep, 'ci:setup:rspec', :spec] do - sh "exit 0" end From 8398238fee3daa230179365b6f52dc0f892d9eee Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Thu, 17 Sep 2009 16:16:00 +1000 Subject: [PATCH 0406/3753] Added rcov support to spec task --- Rakefile | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/Rakefile b/Rakefile index 373d92a71e..f89f3ef97e 100644 --- a/Rakefile +++ b/Rakefile @@ -55,9 +55,17 @@ desc "Run the specs under spec/" task :spec do require 'spec' require 'spec/rake/spectask' - # require 'rcov' - Spec::Rake::SpecTask.new do |t| - t.spec_opts = ['--format','s', '--loadby','mtime'] + begin + require 'rcov' + rescue LoadError + end + + Spec::Rake::SpecTask.new do |t| + t.spec_opts = ['--format','s', '--loadby','mtime'] t.spec_files = FileList['spec/**/*.rb'] - end + if defined?(Rcov) + t.rcov = true + t.rcov_opts = ['--exclude', 'spec/*,test/*,results/*,/usr/lib/*'] + end + end end From 07dca60aee47e1920e3790eb9b4951aaa1832d83 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Fri, 18 Sep 2009 10:10:55 +1000 Subject: [PATCH 0407/3753] Added additional exclusion to rcov process --- Rakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index f89f3ef97e..e3549ca822 100644 --- a/Rakefile +++ b/Rakefile @@ -65,7 +65,7 @@ task :spec do t.spec_files = FileList['spec/**/*.rb'] if defined?(Rcov) t.rcov = true - t.rcov_opts = ['--exclude', 'spec/*,test/*,results/*,/usr/lib/*'] + t.rcov_opts = ['--exclude', 'spec/*,test/*,results/*,/usr/lib/*,/usr/local/lib/*'] end end end From 3a39dd8353b6308cf49522990104cc63e55d7cda Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Fri, 25 Sep 2009 13:26:33 +1000 Subject: [PATCH 0408/3753] Updated ChangeLog and task --- CHANGELOG | 852 +++++++++++++++++++++++---------------- ChangeLog | 20 + tasks/rake/changlog.rake | 11 +- 3 files changed, 523 insertions(+), 360 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 678a8ff9d7..a9c4d3f08b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,359 +1,497 @@ 1.5.7 - * Bug #2306: Facter[netmask] does not exist on Solaris and FreeBSD systems - * Bug #2312: The operatingsystemrelease fact does not account for non-integer Fedora and RHEL releases. - * Bug #2314: OpenBSD uses dmidecode for manufacturer info, should use native sysctl facility - * Bug #2407: Broken CI build - * Bug #2414: errors when alias IP's are defined - * Bug #2415: patch for first-rev Mac OS X versions - * Bug #2431: Facter binary installs in wrong location on Mac OS 10.6 developer seed - * Bug #2455: Misleading/ambiguous error message on invalid fact code should be changed - * Bug #2470: interfaces fact has duplicate entries under Solaris - * Bug #2535: undefined method when trying to access a fact's value through the Facter class method shortcut and @collection is not initialized - * Feature #2548: Netblock fact - * Refactor #2292: Add tests for virtual.rb +===== +07dca60 Added additional exclusion to rcov process +8398238 Added rcov support to spec task +7194454 Updated CI Rake task +2472048 Clarify licensing as GPLv2 (or any later version) +4bc05e9 Added new format ChangeLog +5bc8db3 Incremented version and updated CHANGELOG +eb3a8a7 Issue #2414 - add unit test +7623e25 Fix errors when alias IP's are defined +bfe8a2a Fix 2455 - improve error handling on fact load +49470cf Fix broken solaris zone tests on EC2 +9515a40 Issue #2548 netblock fact +33be9e0 Add Darwin netmask support on top of Jim's patch +9d846b4 Fix #2306 netmask and ipaddress on SunOS and BSDs +7d4a5f9 Updated Rakefile and moved Rake tasks to tasks/rake directory +5982deb Added default Rake task +0e0483a Fix bug where you'd get an 'undefined method' error if trying to access a fact's value when collection has not being yet initialized. +fe41fb8 Fix #2470 - duplicate entries in interfaces fact +be9e484 Update OS X minor version fact to cope with '10.x' values and provide test coverage +f3ad66f Update install.rb to cope with all OS X versions, not just 10.5 +c02d3b6 Issue #2292 Add tests for virtual facts +6c9fec5 Added path fact +51c6e3d Issue #2314 OpenBSD sysctl +95e5fea Fix broken ci build with explicit clearing before tests +efc30e7 Change spec output to enable broken build debugging +6d71410 Fixed CI spec task +82d97e2 Fix operatingsystemrelease on Red Hat based distros +bee55c4 Consolidate operatingsystemrelease for CentOS, Fedora, oel, ovs, and RedHat + +1.5.6 +===== +f4cb619 Updated CHANGELOG and bumped version for 1.5.6 +dcdd5ce Fixes #2307 - Minor fix for zone in virtual.rb +ba44f08 Removed --no-thread and --no-chain-reply-to from rake mail_patches task +806f49f Added facter branding to format patch command +96c015c Added spec.executables to Facter gemspec in Rakefile +d97a63e Sync rpm spec file with latest from Fedora/EPEL +dad4569 Added path to Rakefile +365cb8e CHANGELOG updates +68e0b24 Fix #2278 Revert fix for 2120 +b533e78 Tighten operatingsystemrelease regex on CentOS < 5 +48aa135 Fix operatingsystemrelease for CentOS < 5 +253fef1 Added spec files to package list +b37d683 Added install.rb to Rakefile package task +7995d05 Bumped release to 1.5.5rc2 + +1.5.5rc2 +======== +1de8891 Bumped release to 1.5.5rc2 + +1.5.5 +===== +dad4569 Added path to Rakefile +365cb8e CHANGELOG updates +68e0b24 Fix #2278 Revert fix for 2120 +b533e78 Tighten operatingsystemrelease regex on CentOS < 5 +48aa135 Fix operatingsystemrelease for CentOS < 5 +253fef1 Added spec files to package list +b37d683 Added install.rb to Rakefile package task +7995d05 Bumped release to 1.5.5rc2 +56760d3 Facter #2120 - Solaris support for Facter[virtual] +2fb91ca Tests for #2227 - multiple interfaces on Darwin +00b192a Added SELinux tests +aecac08 Fix #2155 - architecture facts on Gentoo +831d937 Refactor #2154 - Modified patch from Benedikt Bohm to simplify openvz and vserver detection +7f3d237 Cleaned up Rakefile and removed requirement for Reductive Labs build library +a6adf59 Facter ticket 2214 - Fix facts for OVS +e101faf Fixed #2131 - Facter doesn't populate lsbmajdistrelease on OEL (also OEL/OVS and other facts) +73e6656 Facter fix #2231 typo +2518312 Fix #2236 - don't use each_line on arrays +f94abfc Fixed #1327 - Added SELinux facts +8768371 Fixed #2119 - Added support for non-global Solaris 10 zones +23a5b3d Fixed #2215 - Added support for SUSE Linux Enterprise Desktop to operatingsystem and operatingsystemrelease +e93b1e6 Added support for ArchLinux to operatingsystem fact +8e4a689 Fixes #2169 Correctly recognises dom0 and domUs +636a91d Partial fix for #2191 - Facter compatibility for Ruby 1.9 +9df0583 Added COPYING in and CHANGELOG updates +516402c Fixing #1918 - facter --puppet always works +d89ea7a Fixing ifconfig warnings generated on OS X +7fa2576 Fixed #2132 - support for named interface aliases under linux +7a81945 correctly compare values - fixes #2021 +1288b26 Fixed #2080 - IPAddress resolutions should be reordered +a6d6ba5 Use resultion.exec util instead of which checks +89a3aa8 Fix to stdout in resolution.rb +add6d47 Fixed #2081 - Fixed interfaces fact for vlan subinterfaces +8def362 Fixed #2063 - added kernelmajversion fact +5d94f7f Fixed #2055 - SunoS Interface error +9376e5b Fixed #2044 - virtual fact thread fix +c754949 Fix for rake task for reductive-build library +75db918 Fixed lib install permissions +ba2e470 Fixed #2040 - Facter should provide a macosx_productversion_major fact +77fa46b Fix virtual fact if xen but /proc/virtual present +9722e1f Fixed #2003 - Added is_virtual fact +7a30a6a Fixed CHANGELOG +c6c30a4 Fixed #2035 - Missing brace for OSX preflight +b6f0f99 more consistent indentation and alignment. also removal of trailing whitespace +9bc174f Further fix #2032 - close IO +6b904a0 Added EC2 facts +86b01bf Fixed #2032 - file.open hanging on /proc/uptime on some platform +91d8cb7 Updated to version 1.5.4 +a99d043 Fixed #1966 - Added physicalprocessorcount fact +94ea807 This commit refs #1555, #1898 and fixes #1761 +04389db Added support for Oracle VM Server to operatingsystem and operatingsystemrelease +552f150 Added support for Oracle Enterprise Linux to operatingsystem +a932a69 Added README.rst for Facter +e52f962 Added Reductive Labs build library +0726437 Updated README + +1.5.4 +===== +91d8cb7 Updated to version 1.5.4 +a99d043 Fixed #1966 - Added physicalprocessorcount fact +94ea807 This commit refs #1555, #1898 and fixes #1761 +04389db Added support for Oracle VM Server to operatingsystem and operatingsystemrelease +552f150 Added support for Oracle Enterprise Linux to operatingsystem +a932a69 Added README.rst for Facter +e52f962 Added Reductive Labs build library +0726437 Updated README +f4bc74d Fixing #1927 - failing facts don't kill Facter +063e4dc Fixed #1850 - Facter updates for Ruby 1.9 +b85ab0a Fixed #1924 - Fixed lo / lo:0 local interface matching +4dcd012 Fixed generic uptime fact +d93ca69 Fixed Ubuntu operatingsystem identification +effb82f Cleaner fix for #1926 +ccafc00 Fixed #1926 - IPAddr to_s issue +d9eef19 Added timezone fact + +1.5.3 +===== +b86a1fb Updated to version 1.5.3 +a73e803 Fixing the usage of the macosx util module; I somehow missed renaming it here +23289bd Fixed uptime refactor issues on non-Linux platforms +a194c91 Adding mail_patches rake task +a82f476 Renaming Facter::Macosx to Facter::Util::Macosx +1f1fa9b Fixing #1838 - profiler failures don't throw exceptions +5f202c9 Fixed #1867 - Fixed OpenSuSE detection +0bcdb71 Fixing #1854 - Adding ArchLinux support +fab9d1c Added network fact +da52e30 Fixed #1870 - Format all subnet masks as human-readable +c2de35f Added uptime facts +02c2912 Refactor - rename ipmess to interfaces +db4face Fixed autotest on win32 +c149b49 Fix bug #1870 and add interface fact support for darwin systems +aa56886 Refactoring the IP support, and fixing #1846. +91e25b9 Fixing indentation everywhere +074eda9 Fixing autotest, now that vendor/ is gone +01754f6 Removing the vendor/ gems. +e6d987d Fixing #1761 - Solaris no longer uses /etc/release +a70184a Fixed #1791 - support for virtual fact on Solaris 10 +99833a1 Fixed #1793 - Added more Solaris 10 facts +85b2a55 minor fix to operatingsystemversion to correctly parse /etc/release on OpenSolaris 2008.11. +8247304 Fixed errors on unrecognised option in binary +0fe4611 Added ci namespace and Rake tasks +7ddea77 Fix for #1727 - id fact should not rely on whoami on Solaris +f9a346a Sync specfile with latest from Fedora +fd07cd2 Removed EPM task +43d0aea Fixed #1697 - Typo in ipaddress.rb causes timeout under Solaris 10 SPARC +4e707c6 Fixed #1650 - OS X package creation script should be more selective about cleaning out prior versions +8a38aa5 Added Ubuntu to a variety of confines +051c843 Removed ENV path setting from virtual.rb +6393e82 Fixed #1634 - Update virtual fact to differentiate OpenVZ hardware nodes and virtual environments +de39f6c Revamp domain resolution +4d7b44c Fixed #1619 - Applying patch by seanmil, adding support for SLES. +84b83c4 Fixed #1509 - Fixed version recognition for SLES. +20650ac Fixes #1582 - Fix MAC address reporting for Linux bonding slave interfaces +a86577c Fixing the GPL/LGPL incompatibility by choosing the oldest-mentioned license (GPL). +c1d937c Fixed #1575 - CentOS fix for Facter SPEC file +1d00253 Fixed #1547 - finally killed dots in IP address facts +9c9c79a Fixed #1567 - fixed createpackage.sh output +d4cf657 Fixed #1569 - createpackage.rb bug + +1.5.2 +===== +a80779b Updated to version 1.5.2 +0e49580 Updated to version 1.5.2 +6e0a1f3 Fixes #1558 - Adjusted virtual fact to allow non-root users to execute it +4998d3b Fixes #1562 - Removed facter from PREREQS +0fac704 Fixed #1558 - Updated virtual fact for xenu and xen0 +5c50bc3 Fixed #1555 - added operatingsystemrelease for Solaris +e503857 Fixed #1559 - update to dmidecode fact +518393e Fixed . dot escaping +0356b6e Updated to version 1.5.1 + +1.5.1 +===== +bff615c Updated to version 1.5.1 +c2eb5ba Updated to version 1.5.1 +bc35a3b Adding a rake task for creating an archive. +d24504e Added a Process.waitall thread when there's a timeout, to avoid zombies. +bd87aa0 Set the timeout for the host-based and resolv-based resolutions to 2. +e6aa39f Updating changelog for previous two commits +095eb15 Applied patch by josb to fix CentOS version detection. +422dd11 Facter fix #1422, no default timeout +ca93b81 Adding better SuSE detection for both operatingsystem and release. +b7be581 Add unit rspec tests for ticket 1425 +af81fb3 Extract ifconfig output to data directory +2546c53 Add sample test and strawman solution for IP parsing code +b33d8c6 Add module level tests for Facter::IPAddress +590a3d0 Fixes #1492 - added kernelversion fact +d8b708b Fix ticket 1425 on Solaris +b91ee5e Remove duplicated code paths +df8fc8c fix terrible error with overwriting permissions +91ca4ab Fixed #1490 - Added virtual fact +ff45c86 Feature #1487: Package creation scripts for Mac OS X +9b42182 Modified the operatingsystem fact for Debian so it looks in +e1023de Feature #1478: Allow specification of --bindir --sbindir --sitelibdir --mandir --destdir in install.rb +845ae94 Feature #1475: CONFIG['bindir'] CONFIG['sbindir'] have undesirable defaults on OS X 10.5 +a12608e Fixes #1467 - macaddress not set on Ubuntu +d999d95 Don't try and run lsb_release on windows +1eb94d3 Bug #1434 Don't execute which on windows +bb235e3 Use rbconfig to detect host cpu +3f180b3 Get DNSDomain from WMI to set domain +dc7363e Set macaddress on windows platform +0df872b Get kernel version via wmi +3ea1905 Use ipconfig to determine ip address +5e09ea1 Use rbconfig to detect windows as no uname binary +ded53b0 Fixed Rakefile to include additional files including tests et al +1cf98d1 Adjusted version to be in line with previous standard +ff0e90b Adding (apparently now required) author info to the gem spec +7042c46 Updated to version 1.5 +e98efd3 Updated to version 1.5 +d49d63c Updating the changelog for 1.5 +88fe243 Fixed formatting +8c91649 Fixed #1400 - OperatingSystemRelease should now work on CentOS +927b3a1 Adding a default case for the manufacturer information. +9b464de Further fixes #1378 - updated dmidecode for NetBSD +a44d6c3 Fixes #1378 - update manufacter.rb facts to support BSD +9581190 Partial fix for #1345 - BSD interfaces with aliases now select the first address by default +2ef2041 Retaining 'timeout' as the settor, but using 'limit' as the getter. +e22b408 Changed 'timeout' option to 'limit' +145cee2 Setting the timeout for the puppetversion fact to 1.5. +40a9c1d Fixing some warnings in various classes. +0303885 Fixes #1376 - Display memory facts for AIX +2ac29ac Added processorcount and type facts to AIX +0b0892d Fixes #1334 - Forced Facter to use LANG=C +def18b5 Fixes #1357 - change ps syntax for OSX and BSD +ce7b74c Rejustifying all of the whitespace in the facts, yay. +2ee5d29 Refactoring how recursive searches are detected. +d322df9 Refactored so each fact resolution can specify a separate timeout, +9a1882e Retrieve hardwaremodel for AIX from sys0 modelname. +b574c6a Refactered ipmess.rb and util/ip.rb to support separate *BSD logic for *BSD aliased interfaces. +d9bd388 Refactor of netmask fact - fixes ticket #66 +09bc48c Testing gitosis +f9961c7 Fixes for ticket #60 +a12d3d8 Removing old test/unit tests. +400bab9 Adding a timeout to fact retrieval, fixing #56. +d235f26 Reverting the version. +7e84cdb Updated CHANGELOG +a5a72bd Added LSB Major Dist Release fact fixing #41 +fc6d1c9 Added support for AIX fixing ticket #56 +17f916f Updated Red Hat spec file for new version and files +86e0708 Incremented version number to 1.5 +edbfc44 Adding a --puppet option to facter to load Puppet facts. +bb41db0 Switching to a search path registration system. +07a3d47 Moving the puppet-related loading tests to an integration test. +03258eb Retrieval of fact values now autoloads facts. +e02b0b3 Updated version. Moved most facts to seperate files. +9c91a6d Facter no longer loads all facts by default. +aaaf767 Moving the version and ruby facts to a separate file. +f1acbc0 Switching Facter to using the new loader. +bb92493 Fixing the last few occurrences of Facter::Resolution instead of Facter::Util::Resolution. +dcfc171 Fixing the test so it doesn't break other tests. +1ba2bed Moving all of the support classes to util/. +be0a803 Creating a 'loader' class to handle loading facts for the collection. +cc9e221 Adding the 'each' method back into Facter. +48b8744 Updating the executable to not use Facter.each. +5889e43 Fixing warnings and interfaces. +bfc4996 Moving Facter's container behaviour into a separate class. +e3c1fda Splitting the instance code into a Fact class. +121d291 Adding all of the tests for the Facter::Resolution class. +8971979 Reorganizing my new tests so they match the autotest discovery. +b8de4e4 Simplifying Confine a bit +c5492c2 Splitting the different classes in Facter up, and adding some tests. +4f39ec8 Adding autotest hooks +fef9b7d fixing whitespace +567549b Closes #1145 - fixed bad interface names by replacing : with _ +d449472 Updated CHANGELOG +bd3b316 removing .swp file +e11edfb Switching from test/unit to rspec, and fixing a couple +1a5ba71 Fixed Solaris detection of lo0 for ticket #46 +92b43e0 Added require util ip.rb file +0c4ac42 Fixed #46 - refactor ipmess.rb +a633aeb Added new files +c312df8 Further updates to split facts and move support functions +df4636a Split out facts from facter.rb and moved all support code to util +4bb9ed4 Added support for multiple interfaces, macaddress and netmask facts for Linux, *BSD, and Solaris +64f9fe9 Fixed conflict merge +2b06799 Revert "Fixed ticket #50 - added selinux facts" +ecc1f0c Added Ubuntu operatingsystem and operatingsystemrelease fact support +96cf3d6 Added Debian release version support +b3962ef Fixed ticket #50 - added selinux facts +d7d82fc Fixed ticket #48 - CentOS operatingsystemrelease fact now reporting correct value +2af364c Added Mandrake support for operatingsystem fact - closed ticket #47 +85fbf8f Added index to imess.rb fixing Ticket #43. +be7c30b Fixed ticket #44 + +1.3.8 +===== +74621b5 Updated to version 1.3.8 +7f1c840 Updated to version 1.3.8 +4d83f6f Updating version in changelog +00ab1f3 Removing the package hosts, so packages are no longer created at all +57c76dd Updated CHANGELOG +b28ce1b Added require for rdoc/ri/ri_paths to address Puppet #753 and Facter #40 +dce6245 Revert "Adjusted :kernel confine to make it more in line with others" +c5e6f60 Adjusted :kernel confine to make it more in line with others +a4698ce Updated CHANGELOG +8b08d5f Added support to return multiple interfaces and their IP addresses as facts. Existing ipaddress fact which returns IP address of first interface on node is still available. Currently Linux only. Closes #6 +6113375 Added macaddress fact support for FreeBSD and OpenBSD - closes #37 +8426aaf making the install script executable +f35ee22 Drastically speeding up the lsb data retrieval, and refactoring the dmidecode data so it is a bit cleaner and does not produce extraneous output or errors +20986d9 Set operatingsystemrelease to the major release on RHEL and Fedora +43b5640 Remove tabs; don't fail if dmidecode doesn't return expected information +68449a9 Adding manufacturer code, as requested by digant on the Puppet Trac site. +750a0c6 Add YAML output option to the help text. +8a67e32 Fixed problem with executing system_profiler and sw_vers on non Darwin hosts. +43933dd Fixed problem where facter referenced puppet plist utility library. +46d9bed Added a bunch of information from system_profiler -xml. In particular, sp_serial_number is interesting. Also added values from sw_vers, to get the commonly used Mac OS X version and build identifier. +86e3d8e Setting the ldapname so it is guaranteed to be a string +b582612 Applying patch from Valentin Vidic, fixing open filehandles +09261ac Updated to version 1.3.7 +94ca0c3 Updated to version 1.3.7 + +1.3.7 +===== +3e12345 Adding release tag REL_1_3_7 +a329b65 Using consistent naming internally; I previously had essentially random quoting and case, but it is now all lower-case symbols. It should behave the same externally. +4880c69 Applying patch from #36 by psychedelys +11cff7b Fixing Facter.flush +df57cec Fixing #33 -- we now only return the first mac address +31039dc Applying patch from Adam Jacob that makes FACTERLIB work +392d8f2 Applying patch from #35. +824f91c Fixing bug where an up interface not in active use was being selected as the canonical IP instead of using the IP attached to the interface assigned the default route. +38cd613 Sync with Fedora specfile +4cf0016 updating docs a bit +c1a02be Updated to version 1.3.6 +b333df2 Updated to version 1.3.6 + +1.3.6 +===== +ce5258b Adding release tag REL_1_3_6 +cc672ba disabling solaris package generation for facter +067dc2c updating changelog for 1.3.6 +b013e21 Applying patch from #29. +7b665cc Fixing ssh key facts so they only include the key, not the type. +0674780 Make specfile work for FC < 5 and RHEL < 5 +ea65bdd Reconciling with Fedora specfile +4dc1c37 Do not try and check the command if which is not available; fixes trac #30 +c7a9e19 Updated to version 1.3.5 +bae0b49 Updated to version 1.3.5 +9a73c72 Updated to version 1.3.5 + +1.3.5 +===== +5192d94 Adding release tag REL_1_3_5 +4339b46 Fixing #26 -- using Resolution.exec instead of executing directly, and also calling lsb_release for every fact, instead of just once at startup +82fd890 Updated to version 1.3.4 +95352bc Updated to version 1.3.4 +677c986 Updated to version 1.3.4 + +1.3.4 +===== +e882251 Adding release tag REL_1_3_4 +ca498a2 updating changelog for 1.3.4 +d75744b Adding patch from #21, adding lsb_release facts +fe0f2f2 Adding yaml support, as requested in #24 +4abbce9 applying patch from #18. +7407e0c Fixing facter so it does not fail when an unknown fact is asked for +e2185ce Sorting the facts when they are all output +c96cf6a Adding fqdn fact +fc9331a Fixing #20. I just made sure that the Domain fact cchecks the hostname first, so that if the hostname is an fqdn it will set the domain from that. +07a42e6 Applying patch from #22 +610fb5d Applying patch in #23. +3569253 Applying memfree patch from #17. +b9beaa8 updates +722e6f2 doc updates +e2337bd doc updates +2987d50 updates +044f19c adding docs +6f01dec adding docs +4c04592 Updated to version 1.3.3 +474d65d Updated to version 1.3.3 + +1.3.3 +===== +f3333f3 Adding release tag REL_1_3_3 +682b97a updating changelog for 1.3.3 +747d45a Adding the ability to retrieve facts from the environment. +86fdc87 Updated to version 1.3.2 +c4659bd Updated to version 1.3.2 + +1.3.2 +===== +3869edf Adding release tag REL_1_3_3 +ea96381 simple packaging updaets +c2aa508 Adding thread exclusivity to memory and cpu reading +ace180f Re-adding these files, since Matt has found a solution to the hanging problem. +ba2e189 removing processor.rb in case it has the same problems as the memory file +9f14df9 Deleting this file until the hanging problems are resolved +157f68e fixing license issues +a0a33e6 fixing spec file again +31caa08 Updated to version 1.3.1 +8ad0323 Updated to version 1.3.1 +5e34a1f Updated to version 1.3.1 + +1.3.1 +===== +60be696 Adding release tag REL_1_3_1 +73aeade adding a call to dnsdomainname before domainname +6ac796d Fixing #15. Just adding rescue blocks around the load statements. +81f451b updating for 1.3 +b543152 Updated for use with latest Fedora ruby packages +15f2f44 Updated to version 1.3 +15931ef Updated to version 1.3 +261d909 Updated to version 1.3 +539d593 fixing installer so it does not install batch files on darwin +4c1d5e0 trying to fix facterbin rubylib setting +7f2504d fixing test so that it works even if rubylib is not set +75b1835 Adding tagging frameworks back into Facter, and adding the ability to specify tags to the to_hash method so that you only receive facts tagged with specific tags +4296f1f fixing the linux processor stuff so it only gets called on linux +558d05a changing the syntax of the fact confines +9908628 Adding some documentation to the binary +a15c8f5 Adding rubysitedir fact, as requested in #13. Also, switching the output when one fact is asked for, so it only produces the single value, with no => symbol. +ee7d3ca fixing test to ignore differences in memory +5ae066b Switching "tag" to "confine", because it is a more appropriate term. I will also add "tags", but they will be used for creating fact collections. +c7cfd08 Adding patch from #11, with slight modifications. +59cea90 Adding patch from #11, with slight modifications. +f3cc5e3 Adding the ability to specify tags as hashes or arrays, as requested in #112. +01d37d9 Getting rid of the autoload method entirely; facts are now only loaded at startup. +3a0181e fixing linux memory stuff +6932a95 accepting patch in #10, although with more abstraction, and creating a module for the memory functions +165a401 Accepting the patch in #9, with some modifications. +af062c6 adding solaris pkg stuff +8794e46 Updated to version 1.2.1 + +1.2.1 +===== +77344ea Adding release tag REL_1_2_1 +b208f47 fixing small bug that only occurs with gems +999929e Updated to version 1.2.0 + +1.2.0 +===== +afe3c30 Adding release tag REL_1_2_0 +99b61e7 Adding final autoloading work. +97f1a5e updating changelog for 1.2.0 +87bbd50 adding another test for the exe +f745454 Adding ruby, puppet, and facter version facts +6c01e04 Fixing install and tests so that there are no errors, hopefully. +22bd24b Added "architecture" fact, added the ability to autoload facts from separate files, and added the ability to retrieve fact values via a method for each fact. +fe782b9 Accepting the patch from #5 +6c37a20 Removing ruby as a prereq +c78d113 Converting rakefile to the new build system +fadc8c5 Minor changes for hte Fedora Extras review +e3e4a03 fixing rake file to build and copy rpms automatically +46996fa updating changelog for 1.1.4 +aab8687 Updated to version 1.1.4 + +1.1.4 +===== +571683b Adding Release tag REL_1_1_4 +0b7dce7 Fixing installer to put the facter executable in /usr/bin instead of / +3c71757 Updated to version 1.1.3 + +1.1.3 +===== +d494ac2 Adding Release tag REL_1_1_3 +3a230a0 adding 1.1.3 changelog +2e407d4 Identifying centos +cc4a943 updates +4579f8f Updated to version 1.1.2 + +1.1.2 +===== +9279ca8 Adding Release tag REL_1_1_2 +d36885f Adding ldapname capabilities +a4309b4 Automatically update version and release in the specfile for new releases +2d84edd Fix specfile in accordance with Fedora Extras guidelines +2c0999e RPM creation now works +62c050a Working on packaging +2c99812 Updated to version 1.1.1 + +1.1.1 +===== +6fef6af Adding Release tag REL_1_1_1 +35ed5f4 Fixing bug when a fact with no resolutions is asked for +a295c73 Fixing bug when a fact with no resolutions is asked for +5a0bd4a Updated to version 1.1.0 + +1.1.0 +===== +81657d1 Adding Release tag REL_1_1_0 +1ed4216 Redoing how tags work. +d9c86d5 updating everything to essentially disable docs generation +64a86db Adding Release tag -1.5.6: - Bug #2303: Add executable facter in spec - - Bug #2307: undefined local variable or method `zone' - -1.5.5: - Bug #2291 (Closed): Fix operatingsystemrelease for CentOS < 5 - - Bug #2278 (Closed): Virtual fact incorrect on Solaris sparc - - Cleaned up Rakefile and removed requirement for Reductive Labs build library - - Fixed #2131 (Closed): Facter doesn't populate lsbmajdistrelease on OEL - - Fixed #2214 (Closed): Identify Oracle VM properly - - Fixed #2231: ipaddress_pcn0 fact no longer exists on Solaris systems - - Fixed #2236 (Closed): macaddress fact uses each_line on arrays - - Fixed #1918 - facter --puppet doesn't work - - Fixed #2011 - virtual fact reports always vserver_host if /proc/virtual - - Fixed #2021 - Returning boolean not always possible - - Fixed #2132 - Support for named interfaces under Linux - - Fixed #1327 - Added SELinux facts - - Fixed #2119 - Added support for non-global Solaris 10 zones - - Fixed #2080 - IPAddress resolutions should be reordered - - Fixed #2078 - ip.rb errors command not found - - Fixed #2058 - Redirecting stderr doesn't work on all systems - - Fixed #2081 - Fixed interfaces fact for vlan subinterfaces - - Fixed #2063 - added kernelmajversion fact - - Fixed #2055 - SunoS Interface error - - Fixed #2044 - fixed virtual fact - - Fixed lib install permissions - - Fixed #2040 - Facter should provide a macosx_productversion_major fact - - Fixed #2003 - Added is_virtual fact - - Fixed #2035 - Missing brace for OSX preflight - - Added EC2 facts - - Fixed #2032 - file.open hanging on /proc/uptime on some platform - -1.5.4: - Fixed #1966 - Added physicalprocessorcount fact - - Fixed #1761 - changes to Solaris facts: - operatingsystemrelease == kernel release or uname -r - kernelrelease == uname -r - kernelversion == uname -v - - Added support for Oracle VM Server to operatingsystem - and operatingsystemrelease - - Added support for Oracle Enterprise Linux to operatingsystem - and operatingsystemrelease - - Fixed #1927 - failing facts don't kill Facter - - Fixed #1850 - Facter updates for Ruby 1.9 - - Fixed #1926 - IPAddr to_s issue - - Fixed Ubuntu operatingsystem identification - - Fixed generic uptime fact - - Fixed #1924 - Fixed lo / lo:0 local interface matching -1.5.3: - Added network fact - - Fixed #1870 - Format all subnet masks as human-readable - - Added uptime facts - - Fixed autotest on win32 - - Fixed #1870 - Added interface support for Darwin - - Fixed #1791 - support for virtual fact on Solaris 10 - - Fixed #1793 - Added more Solaris 10 facts - - Fixed errors on unrecognised option in binary - - Added ci namespace and Rake tasks - - Fixed #1650 - OS X package creation script should be more selective - about cleaning out prior versions - - Added Ubuntu to a variety of confines - - Fixed #1619 - Applying patch by seanmil, adding support for SLES. - - Fixed #1634 - Update virtual fact to differentiate OpenVZ - hardware nodes and virtual environments - - Fixed #1509 - Fixed version recognition for SLES. - - Fixes #1582 - Fix MAC address reporting for Linux bonding - slave interfaces - - Fixed #1575 - CentOS fix for Facter SPEC file - - Fixed #1569 - createpackage.sh bug - - Fixed #1567 - createpackage.sh output - - Fixed #1547 - finally killed dots in IP address facts - -1.5.2: - Fixed #1562 - Removed facter from PREREQS - - Fixed #1558 - Updated virtual fact for xenu and xen0 - - Fixed #1555 - Ddded operatingsystemrelease for Solaris - - Fixed #1559 - Update to dmidecode fact - -1.5.1: - Added a Process.waitall thread when there's a timeout, to avoid zombies. - - Set the timeout the host-based and resolve-based resolutions to 2. - - Fixed #1495 - CentOS version detection is now better. - - Fixed #1422 - Facter now defaults to 0 timeout. - - Fixes #1492 - added kernelversion fact - - Added virtual fact - - Modified the operatingsystem fact for Debian so it looks in - /etc/debian_version instead of /proc/version. - - Fixes #1467 - macaddress not set on Ubuntu - - Adding a rake task for creating an archive. - - Adding better SuSE detection for both operatingsystem and release. - - Add sample test and strawman solution for IP parsing code - - Add module level tests for Facter::IPAddress - - Fixed #1425 - Solaris - - Feature #1487: Package creation scripts for Mac OS X - - Feature #1478: Allow specification of --bindir --sbindir --sitelibdir --mandir - - Feature #1475: CONFIG['bindir'] CONFIG['sbindir'] have undesirable defaults on - - Fixes #1467 - macaddress not set on Ubuntu - - Enabled a number of Windows facts - operating system, domain, ipaddress, macaddress, - kernel, ipconfig and others - -1.5.0: - Fixed Rakefile to include additional files including tests et al - - Fixed #1400 - OperatingSystemRelease should now work on CentOS - - Changed 'timeout' option to 'limit' to avoid scope issue - - Fixes #1376 - Display memory facts for AIX - - Fixes #1334 - Forced Facter to use LANG=C - - Fixes #1357 - Change ps syntax for OSX and BSD - - Set the timeout on the AIX kernelrelease fact to 5 seconds. - - Refactored so each fact resolution can specify a separate timeout, - but the default is still 0.5. - - Refactered ipmess.rb and util/ip.rb to support separate *BSD logic for - *BSD aliased interfaces. - - Updated dmidecode facts fixing ticket #60 - - Added AIX support for some facts - - Add lsbmajdistrelease fact for CentOS and Red Hat - - Updated Red Hat spec file for new version - The 'facter' executable now has an option (-p|--puppet) for loading the - Puppet libraries, which gives it access to Puppet's facts. - - Added autoloading to Facter with a default of not loading all facts, - which results in a significant speedup when only one fact is sought. - Facts are autoloaded in either a single file named after the fact or - in any file in a directory named after the fact. - - Significantly refactored Facter's internals, including creating tests - for all internal classes. - - A netmask fact has been added closing ticket #46. It only returns the - netmask of the primary interface (in the same behaviour as the ipaddress - and macaddress facts). - - Facts to return multiple interfaces on a host have also been updated. - If you have multiple interfaces on Linux, *BSD, or Solaris/SunOS you will - now get facts for each interface's IP address, MAC address and netmask. - The facts will be structured like: - ipaddress_int = 10.0.0.x - macaddress_int = xx:xx:xx:xx - netmask_int = 255.255.255.0 - - Facter now identifies Ubuntu hosts and their releases using the - operatingsystem and operatingsystemrelease facts. - - The Debian operatingsystemrelease fact now correctly returns the current - Debian release. - - Fixed ticket #48 - ioperatingsystem and operatingsystemrelease for CentOS - - Fixed ticket #44 and allowed support for Xen multiple interfaces and aliased - interfaces. Supports both Linux and *BSD. - - Added interfaces fact to add as index for ip/MAC address facts - - Added Mandrake support for operatingsystem fact - closed ticket #47 - - Fixed ticket #45 - - Added netmask.rb closing ticket #46 - -1.3.8: - Fixed Rdoc::usage bug on CentOS 5 - closed Puppet #753 and Facter #40 - - Added support to return multiple interfaces and their IP addresses and - MAC addressess as facts. Returns interface_interfacename and - macaddress_interfacename. Existing ipaddress and macaddress facts are - unchanged and still returned. Currently Linux only. Closes #6. - - Added macaddress fact support for FreeBSD and OpenBSD - closes #37 - - Added hardwareisa support for *BSD platforms - closed #38 - - Facter now detects the Mandriva distribution - closes #39 - - Facter now correctly detects ipaddress on NetBSD - closes #42 - -1.3.7: - A couple of small bugfixes, including fixing Facter.flush so it correctly - flushes cached values, and the mac address fact only returns one - value, not all of them. - - Converted all of the fact names to symbols, rather than the somewhat - random case used previously. When the facts are converted to a hash, - they still convert the fact name to a string. - -1.3.6: - A bugfix release, including fixes for #29, and #30. Also fixed - the SSH keys so they only have the key, not the type or description. - -1.3.5: - A bugfix release. - -1.3.4: - Added many new facts, including LSB facts. - - Fixed a few small bugs, notably the error you could get when asking - for a non-existent fact. - -1.3.3: - Added thread exclusivity to memory and processor facts. - - Added the ability to retrieve facts by pulling them out of the shell - environment. - -1.3.2: - Temporarily disabled memory and processor facts since they might cause hangs. - -1.3.1: - Fixed autoloading so that it catches any errors in loaded libraries. - -1.3: - Significant internal refactoring, such as replacing 'tag' with 'confine', and - reusing 'tag' for semantic purposes. - - Made autoloading of facts better. - -1.2.1: - Fixed a "bug" that occurs if there's a file named "facter" in your - ruby search path (as opposed to directory). - -1.2.0: - Added RubyVersion, FacterVersion, and PuppetVersion facts. - - Added autoload capabilities, so you can add facts without modifying - the core library. - - Added the ability to retrieve facts by treating them as a method on the - Facter class, e.g., Facter.operatingsystem. - - Added a to_hash method to Facter, which retrieves all facts as a hash. - -1.1.4: - Fixing installer bug. - -1.1.3: - Identifying CentOS correctly. - -1.1.2: - Added 'ldapname' attribute, so Facts can be easily converted to - LDAP. - -1.1.1: - Fixed a bug that occurs when you ask for the value of an unregistered - fact. - -1.1.0: - Added support for OpenBSD (and probably NetBSD and FreeBSD), and significantly - refactored (heh) how facts and resolution mechanisms are added. - -1.0.2: - Added SuSE distro - Added initial support for Cygwin, thanks to contributions from Eric Sorenson - -1.0.1: - Added 'id' fact, which basically returns 'whoami'. - -1.0: - Rewrote entirely. It's much simpler to use, and now supports - adding new fact resolution mechanisms at run-time. - -1.0b1: - Initial release. diff --git a/ChangeLog b/ChangeLog index c888a7dc16..413a8806b5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,23 @@ +Fri, 18 Sep 2009 10:10:55 +1000 +James Turnbull +Added additional exclusion to rcov process + +Thu, 17 Sep 2009 16:16:00 +1000 +James Turnbull +Added rcov support to spec task + +Thu, 17 Sep 2009 16:11:57 +1000 +James Turnbull +Updated CI Rake task + +Wed, 6 May 2009 13:50:30 -0400 +Todd Zullinger +Clarify licensing as GPLv2 (or any later version) + +Fri, 11 Sep 2009 14:23:50 +1000 +James Turnbull +Added new format ChangeLog + Fri, 11 Sep 2009 14:22:11 +1000 James Turnbull Incremented version and updated CHANGELOG diff --git a/tasks/rake/changlog.rake b/tasks/rake/changlog.rake index 0427e41bd6..adeeb03a46 100644 --- a/tasks/rake/changlog.rake +++ b/tasks/rake/changlog.rake @@ -1,10 +1,15 @@ desc "Create a ChangeLog based on git commits." task :changelog do + begin + gitc = %x{which git-changelog} + rescue + puts "This task needs the git-changelog binary - http://github.com/ReinH/git-changelog" + end + CHANGELOG_DIR = "#{Dir.pwd}" mkdir(CHANGELOG_DIR) unless File.directory?(CHANGELOG_DIR) - change_body=`git log --pretty=format:'%aD%n%an <%ae>%n%s%n'` - File.open(File.join(CHANGELOG_DIR, "ChangeLog"), 'w') do |f| + change_body = `git-changelog --no-limit -a` + File.open(File.join(CHANGELOG_DIR, "CHANGELOG"), 'w') do |f| f << change_body end end - From 9aef69edabae9866877c669bde290f22a7bb1010 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sun, 4 Oct 2009 04:54:50 +1100 Subject: [PATCH 0409/3753] Removed all ChangeLog --- CHANGELOG | 1 + ChangeLog | 1727 ----------------------------------------------------- 2 files changed, 1 insertion(+), 1727 deletions(-) delete mode 100644 ChangeLog diff --git a/CHANGELOG b/CHANGELOG index a9c4d3f08b..9fc5ef2c1a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,6 @@ 1.5.7 ===== +3a39dd8 Updated ChangeLog and task 07dca60 Added additional exclusion to rcov process 8398238 Added rcov support to spec task 7194454 Updated CI Rake task diff --git a/ChangeLog b/ChangeLog deleted file mode 100644 index 413a8806b5..0000000000 --- a/ChangeLog +++ /dev/null @@ -1,1727 +0,0 @@ -Fri, 18 Sep 2009 10:10:55 +1000 -James Turnbull -Added additional exclusion to rcov process - -Thu, 17 Sep 2009 16:16:00 +1000 -James Turnbull -Added rcov support to spec task - -Thu, 17 Sep 2009 16:11:57 +1000 -James Turnbull -Updated CI Rake task - -Wed, 6 May 2009 13:50:30 -0400 -Todd Zullinger -Clarify licensing as GPLv2 (or any later version) - -Fri, 11 Sep 2009 14:23:50 +1000 -James Turnbull -Added new format ChangeLog - -Fri, 11 Sep 2009 14:22:11 +1000 -James Turnbull -Incremented version and updated CHANGELOG - -Wed, 9 Sep 2009 15:01:10 +0100 -Paul Nasrat -Issue #2414 - add unit test - -Sat, 5 Sep 2009 15:45:43 +0100 -Kurt Keller -Fix errors when alias IP's are defined - -Sat, 5 Sep 2009 21:35:01 +0100 -Paul Nasrat -Fix 2455 - improve error handling on fact load - -Sat, 5 Sep 2009 06:45:50 +0100 -Paul Nasrat -Fix broken solaris zone tests on EC2 - -Sun, 23 Aug 2009 09:09:44 -0500 -Paul Nasrat -Issue #2548 netblock fact - -Sun, 23 Aug 2009 09:59:08 -0500 -Paul Nasrat -Add Darwin netmask support on top of Jim's patch - -Sun, 23 Aug 2009 09:57:25 -0500 -Jim Pirzyk -Fix #2306 netmask and ipaddress on SunOS and BSDs - -Tue, 18 Aug 2009 21:34:34 +1000 -James Turnbull -Updated Rakefile and moved Rake tasks to tasks/rake directory - -Tue, 18 Aug 2009 21:28:55 +1000 -James Turnbull -Added default Rake task - -Thu, 13 Aug 2009 14:49:12 -0300 -Diego Algorta -Fix bug where you'd get an 'undefined method' error if trying to access a fact's value when collection has not being yet initialized. - -Tue, 11 Aug 2009 17:45:40 +0100 -Paul Nasrat -Fix #2470 - duplicate entries in interfaces fact - -Thu, 23 Jul 2009 08:34:47 -0700 -Nigel Kersten -Update OS X minor version fact to cope with '10.x' values and provide test coverage - -Thu, 23 Jul 2009 08:21:18 -0700 -Nigel Kersten -Update install.rb to cope with all OS X versions, not just 10.5 - -Sat, 18 Jul 2009 09:51:29 +0100 -Paul Nasrat -Merge commit 'pnasrat/tickets/master/2292' into merge - -Thu, 28 May 2009 08:15:01 +0100 -Paul Nasrat -Issue #2292 Add tests for virtual facts - -Wed, 15 Jul 2009 08:30:34 +1000 -James Turnbull -Added path fact - -Tue, 14 Jul 2009 07:08:27 +0100 -Joe McDonagh -Issue #2314 OpenBSD sysctl - -Tue, 14 Jul 2009 08:02:04 +0100 -Todd Zullinger -Fix #2060 and cleanup operatingsystemrelease - -Sat, 11 Jul 2009 09:19:24 +0100 -Paul Nasrat -Fix broken ci build with explicit clearing before tests - -Sat, 11 Jul 2009 09:16:59 +0100 -Paul Nasrat -Change spec output to enable broken build debugging - -Sat, 27 Jun 2009 08:53:32 +1000 -James Turnbull -Fixed CI spec task - -Fri, 5 Jun 2009 08:06:31 +1000 -James Turnbull -Updated CHANGELOG and bumped version for 1.5.6 - -Thu, 4 Jun 2009 21:59:42 +1000 -James Turnbull -Fixes #2307 - Minor fix for zone in virtual.rb - -Wed, 3 Jun 2009 07:39:23 +1000 -James Turnbull -Removed --no-thread and --no-chain-reply-to from rake mail_patches task - -Mon, 1 Jun 2009 07:38:30 +1000 -James Turnbull -Added facter branding to format patch command - -Thu, 28 May 2009 07:16:00 +1000 -James Turnbull -Added spec.executables to Facter gemspec in Rakefile - -Fri, 22 May 2009 19:59:04 -0400 -Todd Zullinger -Sync rpm spec file with latest from Fedora/EPEL - -Fri, 22 May 2009 23:47:52 +1000 -James Turnbull -Added path to Rakefile - -Fri, 22 May 2009 23:28:30 +1000 -James Turnbull -CHANGELOG updates - -Fri, 22 May 2009 08:20:46 +0100 -Paul Nasrat -Fix #2278 Revert fix for 2120 - -Tue, 19 May 2009 21:16:49 -0400 -Todd Zullinger -Tighten operatingsystemrelease regex on CentOS < 5 - -Tue, 19 May 2009 21:13:50 -0400 -Todd Zullinger -Fix operatingsystemrelease for CentOS < 5 - -Wed, 20 May 2009 15:45:54 +1000 -James Turnbull -Added spec files to package list - -Wed, 20 May 2009 02:20:57 +1000 -James Turnbull -Added install.rb to Rakefile package task - -Tue, 19 May 2009 08:08:20 +1000 -James Turnbull -Bumped release to 1.5.5rc2 - -Fri, 15 May 2009 08:27:23 +0100 -Jim Pirzyk -Facter #2120 - Solaris support for Facter[virtual] - -Thu, 7 May 2009 08:00:08 +0100 -Paul Nasrat -Tests for #2227 - multiple interfaces on Darwin - -Tue, 12 May 2009 21:31:30 +1000 -James Turnbull -Added SELinux tests - -Tue, 12 May 2009 08:11:46 +0100 -Benedikt Böhm -Fix #2155 - architecture facts on Gentoo - -Wed, 13 May 2009 11:12:29 +1000 -James Turnbull -Refactor #2154 - Modified patch from Benedikt Bohm to simplify openvz and vserver detection - -Tue, 12 May 2009 10:21:29 +1000 -James Turnbull -Cleaned up Rakefile and removed requirement for Reductive Labs build library - -Wed, 6 May 2009 08:14:37 +0100 -Paul Nasrat -Facter ticket 2214 - Fix facts for OVS - -Tue, 12 May 2009 10:01:39 +1000 -James Turnbull -Fixed #2131 - Facter doesn't populate lsbmajdistrelease on OEL (also OEL/OVS and other facts) - -Sun, 10 May 2009 20:16:54 +0100 -Paul Nasrat -Facter fix #2231 typo - -Mon, 11 May 2009 06:23:59 +0100 -Paul Nasrat -Fix #2236 - don't use each_line on arrays - -Sun, 10 May 2009 19:33:01 +1000 -James Turnbull -Fixed #1327 - Added SELinux facts - -Sun, 10 May 2009 19:06:20 +1000 -James Turnbull -Fixed #2119 - Added support for non-global Solaris 10 zones - -Fri, 1 May 2009 12:10:11 +0200 -Andreas Zuber -Fixed #2215 - Added support for SUSE Linux Enterprise Desktop to operatingsystem and operatingsystemrelease - -Sun, 3 May 2009 12:43:48 +1000 -James Turnbull -Added support for ArchLinux to operatingsystem fact - -Sat, 2 May 2009 21:40:22 +1000 -James Turnbull -Fixes #2169 Correctly recognises dom0 and domUs - -Mon, 27 Apr 2009 00:00:56 +1000 -James Turnbull -Partial fix for #2191 - Facter compatibility for Ruby 1.9 - -Wed, 22 Apr 2009 14:33:52 +1000 -James Turnbull -Added COPYING in and CHANGELOG updates - -Mon, 20 Apr 2009 11:52:43 -0500 -Luke Kanies -Fixing #1918 - facter --puppet always works - -Mon, 20 Apr 2009 11:40:42 -0500 -Luke Kanies -Fixing ifconfig warnings generated on OS X - -Tue, 21 Apr 2009 07:25:10 +1000 -James Turnbull -Fixed #2132 - support for named interface aliases under linux - -Tue, 7 Apr 2009 18:45:21 +0200 -Peter Meier -correctly compare values - fixes #2021 - -Tue, 17 Mar 2009 23:52:11 +1100 -James Turnbull -Fixed #2080 - IPAddress resolutions should be reordered - -Tue, 10 Mar 2009 21:33:29 +0100 -Peter Meier -Use resultion.exec util instead of which checks - -Tue, 17 Mar 2009 23:45:50 +1100 -James Turnbull -Fix to stdout in resolution.rb - -Tue, 17 Mar 2009 20:51:29 +1100 -James Turnbull -Fixed #2081 - Fixed interfaces fact for vlan subinterfaces - -Tue, 10 Mar 2009 23:05:19 +1100 -James Turnbull -Fixed #2063 - added kernelmajversion fact - -Mon, 9 Mar 2009 02:12:32 -0400 -Todd Zullinger -Fix operatingsystemrelease on Red Hat based distros - -Mon, 9 Mar 2009 02:09:12 -0400 -Todd Zullinger -Consolidate operatingsystemrelease for CentOS, Fedora, oel, ovs, and RedHat - -Sat, 7 Mar 2009 10:50:57 +1100 -James Turnbull -Fixed #2055 - SunoS Interface error - -Sat, 7 Mar 2009 02:47:52 +1100 -James Turnbull -Fixed #2044 - virtual fact thread fix - -Wed, 4 Mar 2009 19:41:05 +1100 -James Turnbull -Fix for rake task for reductive-build library - -Sun, 1 Mar 2009 17:55:17 +1100 -James Turnbull -Fixed lib install permissions - -Sun, 1 Mar 2009 09:09:16 +1100 -James Turnbull -Fixed #2040 - Facter should provide a macosx_productversion_major fact - -Sun, 22 Feb 2009 01:43:48 +0100 -duritong -Fix virtual fact if xen but /proc/virtual present - -Sun, 1 Mar 2009 08:53:28 +1100 -James Turnbull -Fixed #2003 - Added is_virtual fact - -Sat, 28 Feb 2009 10:00:09 +1100 -James Turnbull -Fixed CHANGELOG - -Sat, 28 Feb 2009 09:43:36 +1100 -James Turnbull -Fixed #2035 - Missing brace for OSX preflight - -Tue, 17 Feb 2009 19:22:54 -0500 -Ian Taylor -more consistent indentation and alignment. also removal of trailing whitespace - -Sat, 28 Feb 2009 09:28:47 +1100 -James Turnbull -Further fix #2032 - close IO - -Sat, 28 Feb 2009 09:27:22 +1100 -James Turnbull -Added EC2 facts - -Sat, 28 Feb 2009 02:45:57 +1100 -James Turnbull -Fixed #2032 - file.open hanging on /proc/uptime on some platform - -Tue, 17 Feb 2009 00:59:39 +0100 -Luke Kanies -Updated to version 1.5.4 - -Fri, 13 Feb 2009 16:00:15 +1100 -James Turnbull -Fixed #1966 - Added physicalprocessorcount fact - -Thu, 5 Feb 2009 16:52:17 +1100 -Joel W. Shea -This commit refs #1555, #1898 and fixes #1761 - -Wed, 11 Feb 2009 15:05:22 +1100 -James Turnbull -Added support for Oracle VM Server to operatingsystem and operatingsystemrelease - -Wed, 11 Feb 2009 14:42:16 +1100 -James Turnbull -Added support for Oracle Enterprise Linux to operatingsystem - -Tue, 10 Feb 2009 15:12:48 +1100 -James Turnbull -Added README.rst for Facter - -Tue, 10 Feb 2009 02:14:55 +1100 -James Turnbull -Added Reductive Labs build library - -Wed, 4 Feb 2009 22:17:28 +1100 -James Turnbull -Updated README - -Tue, 3 Feb 2009 18:40:43 -0600 -Luke Kanies -Fixing #1927 - failing facts don't kill Facter - -Wed, 4 Feb 2009 11:40:34 +1100 -James Turnbull -Fixed #1850 - Facter updates for Ruby 1.9 - -Wed, 4 Feb 2009 11:33:58 +1100 -James Turnbull -Fixed #1924 - Fixed lo / lo:0 local interface matching - -Tue, 3 Feb 2009 22:20:59 +1100 -James Turnbull -Fixed generic uptime fact - -Tue, 3 Feb 2009 22:09:17 +1100 -James Turnbull -Fixed Ubuntu operatingsystem identification - -Tue, 3 Feb 2009 21:32:16 +1100 -James Turnbull -Cleaner fix for #1926 - -Tue, 3 Feb 2009 18:53:42 +1100 -James Turnbull -Fixed #1926 - IPAddr to_s issue - -Fri, 30 Jan 2009 21:28:39 +1100 -James Turnbull -Added timezone fact - -Wed, 28 Jan 2009 19:46:08 +0100 -Luke Kanies -Updated to version 1.5.3 - -Tue, 27 Jan 2009 22:18:51 -0600 -Luke Kanies -Fixing the usage of the macosx util module; I somehow missed renaming it here - -Wed, 28 Jan 2009 12:18:09 +1100 -James Turnbull -Fixed uptime refactor issues on non-Linux platforms - -Tue, 27 Jan 2009 17:53:38 -0600 -Luke Kanies -Adding mail_patches rake task - -Tue, 27 Jan 2009 17:51:39 -0600 -Luke Kanies -Renaming Facter::Macosx to Facter::Util::Macosx - -Tue, 27 Jan 2009 17:50:21 -0600 -Luke Kanies -Fixing #1838 - profiler failures don't throw exceptions - -Tue, 27 Jan 2009 18:00:50 -0600 -Luke Kanies -Fixed #1867 - Fixed OpenSuSE detection - -Tue, 27 Jan 2009 17:58:47 -0600 -Luke Kanies -Fixing #1854 - Adding ArchLinux support - -Tue, 27 Jan 2009 19:37:54 +1100 -James Turnbull -Added network fact - -Tue, 27 Jan 2009 10:23:26 +1100 -James Turnbull -Fixed #1870 - Format all subnet masks as human-readable - -Mon, 26 Jan 2009 14:39:35 +1100 -James Turnbull -Added uptime facts - -Sat, 24 Jan 2009 12:52:07 +0000 -Paul Nasrat -Refactor - rename ipmess to interfaces - -Thu, 22 Jan 2009 11:02:16 +1100 -James Turnbull -Fixed autotest on win32 - -Thu, 15 Jan 2009 19:53:46 +0000 -Paul Nasrat -Fix bug #1870 and add interface fact support for darwin systems - -Sun, 4 Jan 2009 17:29:59 -0600 -Luke Kanies -Refactoring the IP support, and fixing #1846. - -Fri, 2 Jan 2009 15:08:26 -0600 -Luke Kanies -Fixing indentation everywhere - -Thu, 15 Jan 2009 11:41:51 -0600 -Luke Kanies -Fixing autotest, now that vendor/ is gone - -Thu, 15 Jan 2009 11:30:56 -0600 -Luke Kanies -Removing the vendor/ gems. - -Tue, 30 Dec 2008 18:16:58 -0600 -Luke Kanies -Fixing #1761 - Solaris no longer uses /etc/release - -Mon, 22 Dec 2008 19:42:03 +1100 -James Turnbull -Fixed #1791 - support for virtual fact on Solaris 10 - -Mon, 22 Dec 2008 19:39:18 +1100 -James Turnbull -Fixed #1793 - Added more Solaris 10 facts - -Tue, 9 Dec 2008 04:45:35 -0700 -rchanter -minor fix to operatingsystemversion to correctly parse /etc/release on OpenSolaris 2008.11. - -Mon, 1 Dec 2008 13:14:24 +1100 -James Turnbull -Fixed errors on unrecognised option in binary - -Thu, 27 Nov 2008 00:45:09 +1100 -James Turnbull -Added ci namespace and Rake tasks - -Thu, 13 Nov 2008 14:18:58 -0800 -Luke Kanies -Merge commit 'martin/tickets/1727' - -Wed, 12 Nov 2008 15:03:14 +0100 -Martin Englund -Fix for #1727 - id fact should not rely on whoami on Solaris - -Fri, 7 Nov 2008 10:41:19 -0800 -David Lutterkort -Sync specfile with latest from Fedora - -Wed, 5 Nov 2008 12:29:44 +1100 -James Turnbull -Removed EPM task - -Tue, 28 Oct 2008 11:53:05 +1100 -James Turnbull -Fixed #1697 - Typo in ipaddress.rb causes timeout under Solaris 10 SPARC - -Wed, 22 Oct 2008 21:55:40 +1100 -James Turnbull -Fixed #1650 - OS X package creation script should be more selective about cleaning out prior versions - -Tue, 21 Oct 2008 10:26:29 +1100 -James Turnbull -Added Ubuntu to a variety of confines - -Tue, 21 Oct 2008 09:06:54 +1100 -James Turnbull -Removed ENV path setting from virtual.rb - -Tue, 7 Oct 2008 11:29:10 +1100 -James Turnbull -Fixed #1634 - Update virtual fact to differentiate OpenVZ hardware nodes and virtual environments - -Wed, 15 Oct 2008 16:05:36 -0500 -Luke Kanies -Merge commit 'lutter/ticket/1654' - -Wed, 15 Oct 2008 11:43:29 -0700 -David Lutterkort -Revamp domain resolution - -Mon, 29 Sep 2008 12:07:51 -0500 -Luke Kanies -Fixed #1619 - Applying patch by seanmil, adding support for SLES. - -Wed, 24 Sep 2008 14:00:06 -0500 -Luke Kanies -Fixed #1509 - Fixed version recognition for SLES. - -Sat, 20 Sep 2008 11:51:11 +1000 -James Turnbull -Fixes #1582 - Fix MAC address reporting for Linux bonding slave interfaces - -Fri, 19 Sep 2008 11:49:14 +1000 -James Turnbull -Merge branch 'master' of git://reductivelabs.com/facter - -Tue, 16 Sep 2008 12:46:13 -0500 -Luke Kanies -Fixing the GPL/LGPL incompatibility by choosing the oldest-mentioned license (GPL). - -Tue, 16 Sep 2008 08:46:57 +1000 -James Turnbull -Fixed #1575 - CentOS fix for Facter SPEC file - -Tue, 16 Sep 2008 08:45:36 +1000 -James Turnbull -Merge branch 'master' of git://reductivelabs.com/facter - -Fri, 12 Sep 2008 01:10:41 +1000 -James Turnbull -Fixed #1547 - finally killed dots in IP address facts - -Fri, 12 Sep 2008 01:05:18 +1000 -James Turnbull -Fixed #1567 - fixed createpackage.sh output - -Fri, 12 Sep 2008 01:01:20 +1000 -James Turnbull -Fixed #1569 - createpackage.rb bug - -Tue, 9 Sep 2008 05:00:34 +0200 -Luke Kanies -Updated to version 1.5.2 - -Tue, 9 Sep 2008 05:00:34 +0200 -Luke Kanies -Updated to version 1.5.2 - -Tue, 9 Sep 2008 05:00:03 +0200 -Luke Kanies -Merge branch 'master' of git@reductivelabs.com:facter - -Mon, 8 Sep 2008 23:13:04 +1000 -James Turnbull -Fixes #1558 - Adjusted virtual fact to allow non-root users to execute it - -Sat, 6 Sep 2008 09:59:36 +1000 -James Turnbull -Fixes #1562 - Removed facter from PREREQS - -Fri, 5 Sep 2008 10:22:23 +1000 -James Turnbull -Fixed #1558 - Updated virtual fact for xenu and xen0 - -Fri, 5 Sep 2008 10:07:35 +1000 -James Turnbull -Fixed #1555 - added operatingsystemrelease for Solaris - -Fri, 5 Sep 2008 10:00:41 +1000 -James Turnbull -Fixed #1559 - update to dmidecode fact - -Wed, 3 Sep 2008 05:37:08 +1000 -James Turnbull -Fixed . dot escaping - -Wed, 27 Aug 2008 07:10:07 +0200 -Luke Kanies -Merge branch 'master' of git@reductivelabs.com:facter - -Wed, 27 Aug 2008 06:16:35 +0200 -Luke Kanies -Updated to version 1.5.1 - -Wed, 27 Aug 2008 06:16:35 +0200 -Luke Kanies -Updated to version 1.5.1 - -Wed, 27 Aug 2008 06:16:35 +0200 -Luke Kanies -Updated to version 1.5.1 - -Tue, 19 Aug 2008 02:55:41 +0200 -Luke Kanies -Adding a rake task for creating an archive. - -Mon, 18 Aug 2008 19:15:24 -0500 -Luke Kanies -Added a Process.waitall thread when there's a timeout, to avoid zombies. - -Mon, 18 Aug 2008 18:50:54 -0500 -Luke Kanies -Set the timeout for the host-based and resolv-based resolutions to 2. - -Mon, 18 Aug 2008 18:41:51 -0500 -Luke Kanies -Updating changelog for previous two commits - -Mon, 18 Aug 2008 18:40:32 -0500 -Luke Kanies -Applied patch by josb to fix CentOS version detection. - -Mon, 18 Aug 2008 18:38:11 -0500 -Luke Kanies -Merge commit 'pnasrat/tickets/1422' - -Mon, 18 Aug 2008 12:12:25 +0100 -Paul Nasrat -Facter fix #1422, no default timeout - -Sun, 17 Aug 2008 11:36:58 -0500 -Luke Kanies -Adding better SuSE detection for both operatingsystem and release. - -Sat, 16 Aug 2008 16:23:25 -0500 -Luke Kanies -Merge commit 'pnasrat/ticket/1425' - -Wed, 13 Aug 2008 17:25:30 +0100 -Paul Nasrat -Add unit rspec tests for ticket 1425 - -Wed, 13 Aug 2008 15:55:49 +0100 -Paul Nasrat -Extract ifconfig output to data directory - -Mon, 11 Aug 2008 13:44:23 +0100 -Paul Nasrat -Add sample test and strawman solution for IP parsing code - -Mon, 11 Aug 2008 12:20:36 +0100 -Paul Nasrat -Add module level tests for Facter::IPAddress - -Sat, 9 Aug 2008 07:31:31 +1000 -James Turnbull -Fixes #1492 - added kernelversion fact - -Fri, 8 Aug 2008 15:07:13 +0100 -Paul Nasrat -Fix ticket 1425 on Solaris - -Wed, 6 Aug 2008 18:46:12 +0100 -Paul Nasrat -Remove duplicated code paths - -Tue, 5 Aug 2008 13:46:35 -0700 -Nigel Kersten -fix terrible error with overwriting permissions - -Mon, 4 Aug 2008 07:53:36 +1000 -James Turnbull -Fixed #1490 - Added virtual fact - -Fri, 1 Aug 2008 20:45:25 -0700 -Nigel Kersten -Feature #1487: Package creation scripts for Mac OS X - -Fri, 1 Aug 2008 09:23:25 -0500 -Luke Kanies -Modified the operatingsystem fact for Debian so it looks in - -Thu, 31 Jul 2008 17:04:45 -0700 -Nigel Kersten -Feature #1478: Allow specification of --bindir --sbindir --sitelibdir --mandir --destdir in install.rb - -Thu, 31 Jul 2008 08:51:02 -0700 -Nigel Kersten -Feature #1475: CONFIG['bindir'] CONFIG['sbindir'] have undesirable defaults on OS X 10.5 - -Thu, 31 Jul 2008 10:54:47 -0500 -Luke Kanies -Merge commit 'pnasrat/ticket/1434' - -Thu, 31 Jul 2008 10:51:55 -0500 -Luke Kanies -Merge commit 'pnasrat/ticket/1436' - -Wed, 30 Jul 2008 22:35:58 +1000 -James Turnbull -Fixes #1467 - macaddress not set on Ubuntu - -Fri, 18 Jul 2008 07:06:27 +0100 -Paul Nasrat -Don't try and run lsb_release on windows - -Thu, 17 Jul 2008 18:00:51 +0100 -Paul Nasrat -Bug #1434 Don't execute which on windows - -Thu, 17 Jul 2008 14:54:52 +0100 -Paul Nasrat -Use rbconfig to detect host cpu - -Thu, 17 Jul 2008 13:44:10 +0100 -Paul Nasrat -Get DNSDomain from WMI to set domain - -Thu, 17 Jul 2008 13:31:18 +0100 -Paul Nasrat -Set macaddress on windows platform - -Thu, 17 Jul 2008 13:21:47 +0100 -Paul Nasrat -Get kernel version via wmi - -Thu, 17 Jul 2008 12:46:14 +0100 -Paul Nasrat -Use ipconfig to determine ip address - -Thu, 17 Jul 2008 12:27:54 +0100 -Paul Nasrat -Use rbconfig to detect windows as no uname binary - -Wed, 9 Jul 2008 22:40:33 +1000 -James Turnbull -Fixed Rakefile to include additional files including tests et al - -Wed, 9 Jul 2008 17:56:48 +1000 -James Turnbull -Adjusted version to be in line with previous standard - -Tue, 8 Jul 2008 23:52:15 +1000 -James Turnbull -Merge branch 'master' of git://reductivelabs.com/facter - -Tue, 8 Jul 2008 06:44:14 +0200 -Luke Kanies -Adding (apparently now required) author info to the gem spec - -Tue, 8 Jul 2008 14:36:57 +1000 -James Turnbull -Merge branch 'master' of git://reductivelabs.com/facter - -Tue, 8 Jul 2008 14:28:06 +1000 -James Turnbull -Merge branch 'master' of git://reductivelabs.com/facter - -Tue, 8 Jul 2008 06:27:29 +0200 -Luke Kanies -Updated to version 1.5 - -Tue, 8 Jul 2008 06:27:28 +0200 -Luke Kanies -Updated to version 1.5 - -Tue, 8 Jul 2008 06:27:16 +0200 -Luke Kanies -Updating the changelog for 1.5 - -Mon, 7 Jul 2008 23:21:08 -0500 -Luke Kanies -Merge commit 'turnbull/master' - -Tue, 8 Jul 2008 14:21:04 +1000 -James Turnbull -Merge branch 'master' of git://reductivelabs.com/facter - -Tue, 8 Jul 2008 14:19:58 +1000 -James Turnbull -Fixed formatting - -Mon, 7 Jul 2008 23:13:25 -0500 -Luke Kanies -Fixed #1400 - OperatingSystemRelease should now work on CentOS - -Tue, 1 Jul 2008 10:17:16 -0500 -Luke Kanies -Adding a default case for the manufacturer information. - -Sun, 22 Jun 2008 09:50:34 +1000 -James Turnbull -Further fixes #1378 - updated dmidecode for NetBSD - -Sat, 21 Jun 2008 12:49:46 +1000 -James Turnbull -Fixes #1378 - update manufacter.rb facts to support BSD - -Sat, 21 Jun 2008 11:55:29 +1000 -James Turnbull -Partial fix for #1345 - BSD interfaces with aliases now select the first address by default - -Fri, 20 Jun 2008 10:23:02 -0500 -Luke Kanies -Retaining 'timeout' as the settor, but using 'limit' as the getter. - -Thu, 19 Jun 2008 21:34:52 -0700 -Steven Hajducko -Changed 'timeout' option to 'limit' - -Thu, 19 Jun 2008 21:44:44 -0500 -Luke Kanies -Setting the timeout for the puppetversion fact to 1.5. - -Thu, 19 Jun 2008 21:44:34 -0500 -Luke Kanies -Fixing some warnings in various classes. - -Fri, 20 Jun 2008 11:26:08 +1000 -James Turnbull -Merge branch 'tickets/1.5/1376' of git://github.com/haji/facter - -Thu, 19 Jun 2008 17:58:36 -0700 -Steven Hajducko -Fixes #1376 - Display memory facts for AIX - -Thu, 19 Jun 2008 11:02:31 -0700 -Steven Hajducko -Added processorcount and type facts to AIX - -Wed, 18 Jun 2008 09:49:30 +1000 -James Turnbull -Fixes #1334 - Forced Facter to use LANG=C - -Wed, 18 Jun 2008 09:33:31 +1000 -James Turnbull -Fixes #1357 - change ps syntax for OSX and BSD - -Tue, 17 Jun 2008 17:16:24 -0500 -Luke Kanies -Merge branch 'master' of git://github.com/jamtur01/facter - -Tue, 17 Jun 2008 10:59:52 -0500 -Luke Kanies -Rejustifying all of the whitespace in the facts, yay. - -Tue, 17 Jun 2008 10:55:22 -0500 -Luke Kanies -Refactoring how recursive searches are detected. - -Tue, 17 Jun 2008 10:54:23 -0500 -Luke Kanies -Refactored so each fact resolution can specify a separate timeout, - -Mon, 16 Jun 2008 16:12:20 -0700 -Steve Hajducko -Retrieve hardwaremodel for AIX from sys0 modelname. - -Mon, 9 Jun 2008 16:34:47 -0500 -Luke Kanies -Merge commit 'turnbull/master' - -Mon, 9 Jun 2008 02:37:30 +1000 -James Turnbull -Refactered ipmess.rb and util/ip.rb to support separate *BSD logic for *BSD aliased interfaces. - -Mon, 2 Jun 2008 18:57:56 -0700 -Luke Kanies -Merge branch 'master' of git@reductivelabs.com:facter - -Mon, 2 Jun 2008 18:54:43 -0700 -Luke Kanies -Merge branch 'master' of git://github.com/jamtur01/facter - -Wed, 28 May 2008 20:38:22 +1000 -James Turnbull -Refactor of netmask fact - fixes ticket #66 - -Sun, 25 May 2008 19:18:07 -0500 -Luke Kanies -Testing gitosis - -Thu, 22 May 2008 17:39:04 +1000 -James Turnbull -Fixes for ticket #60 - -Tue, 20 May 2008 23:26:14 -0500 -Luke Kanies -Removing old test/unit tests. - -Tue, 20 May 2008 23:12:06 -0500 -Luke Kanies -Adding a timeout to fact retrieval, fixing #56. - -Mon, 19 May 2008 22:29:40 -0500 -Luke Kanies -Reverting the version. - -Mon, 19 May 2008 11:56:51 +1000 -James Turnbull -Updated CHANGELOG - -Mon, 19 May 2008 11:52:34 +1000 -James Turnbull -Added LSB Major Dist Release fact fixing #41 - -Mon, 19 May 2008 10:44:39 +1000 -James Turnbull -Added support for AIX fixing ticket #56 - -Mon, 19 May 2008 10:29:36 +1000 -James Turnbull -Updated Red Hat spec file for new version and files - -Mon, 19 May 2008 10:22:58 +1000 -James Turnbull -Incremented version number to 1.5 - -Fri, 16 May 2008 14:57:26 -0500 -Luke Kanies -Adding a --puppet option to facter to load Puppet facts. - -Fri, 16 May 2008 10:38:15 -0500 -Luke Kanies -Switching to a search path registration system. - -Fri, 16 May 2008 10:26:56 -0500 -Luke Kanies -Moving the puppet-related loading tests to an integration test. - -Fri, 16 May 2008 09:46:17 -0500 -Luke Kanies -Retrieval of fact values now autoloads facts. - -Sat, 17 May 2008 00:32:34 +1000 -James Turnbull -Updated version. Moved most facts to seperate files. - -Thu, 15 May 2008 15:40:02 -0500 -Luke Kanies -Facter no longer loads all facts by default. - -Thu, 15 May 2008 15:27:57 -0500 -Luke Kanies -Moving the version and ruby facts to a separate file. - -Thu, 15 May 2008 15:24:29 -0500 -Luke Kanies -Switching Facter to using the new loader. - -Thu, 15 May 2008 15:21:59 -0500 -Luke Kanies -Fixing the last few occurrences of Facter::Resolution instead of Facter::Util::Resolution. - -Thu, 15 May 2008 14:55:51 -0500 -Luke Kanies -Fixing the test so it doesn't break other tests. - -Thu, 15 May 2008 14:30:29 -0500 -Luke Kanies -Moving all of the support classes to util/. - -Thu, 15 May 2008 14:18:26 -0500 -Luke Kanies -Creating a 'loader' class to handle loading facts for the collection. - -Wed, 14 May 2008 10:17:18 -0500 -Luke Kanies -Adding the 'each' method back into Facter. - -Wed, 14 May 2008 09:36:32 -0500 -Luke Kanies -Updating the executable to not use Facter.each. - -Wed, 14 May 2008 00:32:41 -0500 -Luke Kanies -Fixing warnings and interfaces. - -Wed, 14 May 2008 00:24:24 -0500 -Luke Kanies -Moving Facter's container behaviour into a separate class. - -Tue, 13 May 2008 23:49:48 -0500 -Luke Kanies -Splitting the instance code into a Fact class. - -Tue, 13 May 2008 22:07:18 -0500 -Luke Kanies -Adding all of the tests for the Facter::Resolution class. - -Tue, 13 May 2008 21:28:34 -0500 -Luke Kanies -Reorganizing my new tests so they match the autotest discovery. - -Tue, 13 May 2008 21:23:22 -0500 -Luke Kanies -Simplifying Confine a bit - -Tue, 13 May 2008 21:22:20 -0500 -Luke Kanies -Splitting the different classes in Facter up, and adding some tests. - -Tue, 13 May 2008 20:49:04 -0500 -Luke Kanies -Adding autotest hooks - -Tue, 13 May 2008 20:48:52 -0500 -Luke Kanies -fixing whitespace - -Thu, 20 Mar 2008 12:12:27 +1100 -James Turnbull -Closes #1145 - fixed bad interface names by replacing : with _ - -Tue, 19 Feb 2008 15:20:02 +1100 -James Turnbull -Updated CHANGELOG - -Mon, 18 Feb 2008 17:16:39 -0600 -Luke Kanies -Merge commit 'turnbull/master' - -Sun, 17 Feb 2008 17:04:47 -0600 -Luke Kanies -Merge branch 'os_split' - -Sun, 17 Feb 2008 17:00:39 -0600 -Luke Kanies -removing .swp file - -Sun, 17 Feb 2008 16:15:10 -0600 -Luke Kanies -Switching from test/unit to rspec, and fixing a couple - -Sat, 16 Feb 2008 21:56:01 +1100 -James Turnbull -Fixed Solaris detection of lo0 for ticket #46 - -Sat, 16 Feb 2008 18:20:38 +1100 -James Turnbull -Added require util ip.rb file - -Sat, 16 Feb 2008 18:20:07 +1100 -James Turnbull -Fixed #46 - refactor ipmess.rb - -Fri, 8 Feb 2008 16:15:02 +1100 -James Turnbull -Added new files - -Fri, 8 Feb 2008 16:14:26 +1100 -James Turnbull -Further updates to split facts and move support functions - -Fri, 8 Feb 2008 14:14:24 +1100 -James Turnbull -Split out facts from facter.rb and moved all support code to util - -Thu, 7 Feb 2008 15:26:46 +1100 -James Turnbull -Added support for multiple interfaces, macaddress and netmask facts for Linux, *BSD, and Solaris - -Sun, 3 Feb 2008 04:45:11 +1100 -James Turnbull -Fixed conflict merge - -Sun, 3 Feb 2008 04:32:55 +1100 -James Turnbull -Revert "Fixed ticket #50 - added selinux facts" - -Sun, 3 Feb 2008 04:24:40 +1100 -James Turnbull -Added Ubuntu operatingsystem and operatingsystemrelease fact support - -Sat, 2 Feb 2008 17:04:41 +1100 -James Turnbull -Added Debian release version support - -Sun, 13 Jan 2008 01:53:23 +1100 -James Turnbull -Fixed ticket #50 - added selinux facts - -Sat, 22 Dec 2007 15:10:02 +1100 -James Turnbull -Fixed ticket #48 - CentOS operatingsystemrelease fact now reporting correct value - -Sun, 9 Dec 2007 19:38:04 +1100 -James Turnbull -Added Mandrake support for operatingsystem fact - closed ticket #47 - -Thu, 8 Nov 2007 08:20:37 +1100 -James Turnbull -Added index to imess.rb fixing Ticket #43. - -Mon, 5 Nov 2007 20:40:31 +1100 -James Turnbull -Fixed ticket #44 - -Mon, 24 Sep 2007 09:01:38 +0200 -Luke Kanies -Updated to version 1.3.8 - -Mon, 24 Sep 2007 09:01:38 +0200 -Luke Kanies -Updated to version 1.3.8 - -Mon, 24 Sep 2007 09:00:23 +0200 -Luke Kanies -Updating version in changelog - -Mon, 24 Sep 2007 09:00:05 +0200 -Luke Kanies -Merge branch 'master' of /opt/rl/git/facter - -Mon, 24 Sep 2007 08:59:28 +0200 -Luke Kanies -Removing the package hosts, so packages are no longer created at all - -Tue, 18 Sep 2007 13:06:39 +1000 -James Turnbull -Updated CHANGELOG - -Tue, 18 Sep 2007 11:33:22 +1000 -James Turnbull -Added require for rdoc/ri/ri_paths to address Puppet #753 and Facter #40 - -Sun, 16 Sep 2007 10:26:45 +1000 -James Turnbull -Revert "Adjusted :kernel confine to make it more in line with others" - -Sun, 16 Sep 2007 10:13:58 +1000 -root -Adjusted :kernel confine to make it more in line with others - -Thu, 13 Sep 2007 13:53:30 +1000 -James Turnbull -Updated CHANGELOG - -Thu, 13 Sep 2007 13:38:18 +1000 -James Turnbull -Added support to return multiple interfaces and their IP addresses as facts. Existing ipaddress fact which returns IP address of first interface on node is still available. Currently Linux only. Closes #6 - -Thu, 13 Sep 2007 08:51:11 +1000 -root -Added macaddress fact support for FreeBSD and OpenBSD - closes #37 - -Tue, 17 Jul 2007 20:29:17 +0000 -luke -making the install script executable - -Tue, 17 Jul 2007 20:03:35 +0000 -luke -Drastically speeding up the lsb data retrieval, and refactoring the dmidecode data so it is a bit cleaner and does not produce extraneous output or errors - -Mon, 25 Jun 2007 17:35:31 +0000 -lutter -Set operatingsystemrelease to the major release on RHEL and Fedora - -Sun, 24 Jun 2007 03:09:25 +0000 -lutter -Remove tabs; don't fail if dmidecode doesn't return expected information - -Mon, 18 Jun 2007 21:10:10 +0000 -luke -Adding manufacturer code, as requested by digant on the Puppet Trac site. - -Thu, 14 Jun 2007 22:09:41 +0000 -josb -Add YAML output option to the help text. - -Wed, 13 Jun 2007 18:33:06 +0000 -mccune -Fixed problem with executing system_profiler and sw_vers on non Darwin hosts. - -Wed, 13 Jun 2007 17:58:46 +0000 -mccune -Fixed problem where facter referenced puppet plist utility library. - -Wed, 13 Jun 2007 17:40:45 +0000 -mccune -Added a bunch of information from system_profiler -xml. In particular, sp_serial_number is interesting. Also added values from sw_vers, to get the commonly used Mac OS X version and build identifier. - -Mon, 11 Jun 2007 22:01:13 +0000 -luke -Setting the ldapname so it is guaranteed to be a string - -Tue, 5 Jun 2007 18:08:16 +0000 -luke -Applying patch from Valentin Vidic, fixing open filehandles - -Wed, 21 Mar 2007 16:44:44 +0000 -luke -Updated to version 1.3.7 - -Wed, 21 Mar 2007 16:44:43 +0000 -luke -Updated to version 1.3.7 - -Wed, 21 Mar 2007 16:35:51 +0000 -luke -Using consistent naming internally; I previously had essentially random quoting and case, but it is now all lower-case symbols. It should behave the same externally. - -Wed, 21 Mar 2007 15:47:47 +0000 -luke -Applying patch from #36 by psychedelys - -Wed, 21 Mar 2007 15:44:25 +0000 -luke -Fixing Facter.flush - -Wed, 21 Mar 2007 15:40:47 +0000 -luke -Fixing #33 -- we now only return the first mac address - -Wed, 21 Mar 2007 15:24:05 +0000 -luke -Applying patch from Adam Jacob that makes FACTERLIB work - -Sat, 24 Feb 2007 15:44:59 +0000 -luke -Applying patch from #35. - -Tue, 30 Jan 2007 18:17:03 +0000 -ajax -Fixing bug where an up interface not in active use was being selected as the canonical IP instead of using the IP attached to the interface assigned the default route. - -Tue, 23 Jan 2007 18:11:46 +0000 -lutter -Sync with Fedora specfile - -Mon, 22 Jan 2007 16:06:26 +0000 -luke -updating docs a bit - -Fri, 19 Jan 2007 22:37:24 +0000 -luke -Updated to version 1.3.6 - -Fri, 19 Jan 2007 22:37:23 +0000 -luke -Updated to version 1.3.6 - -Fri, 19 Jan 2007 22:36:53 +0000 -luke -disabling solaris package generation for facter - -Fri, 19 Jan 2007 22:32:41 +0000 -luke -updating changelog for 1.3.6 - -Thu, 4 Jan 2007 04:57:53 +0000 -luke -Applying patch from #29. - -Thu, 4 Jan 2007 04:30:42 +0000 -luke -Fixing ssh key facts so they only include the key, not the type. - -Tue, 21 Nov 2006 02:07:46 +0000 -lutter -Make specfile work for FC < 5 and RHEL < 5 - -Wed, 8 Nov 2006 20:07:57 +0000 -lutter -Reconciling with Fedora specfile - -Wed, 8 Nov 2006 19:52:34 +0000 -lutter -Do not try and check the command if which is not available; fixes trac #30 - -Fri, 29 Sep 2006 16:13:13 +0000 -luke -Updated to version 1.3.5 - -Fri, 29 Sep 2006 16:13:12 +0000 -luke -Updated to version 1.3.5 - -Fri, 29 Sep 2006 16:13:11 +0000 -luke -Updated to version 1.3.5 - -Fri, 29 Sep 2006 16:12:00 +0000 -luke -Fixing #26 -- using Resolution.exec instead of executing directly, and also calling lsb_release for every fact, instead of just once at startup - -Fri, 22 Sep 2006 05:24:25 +0000 -luke -Updated to version 1.3.4 - -Fri, 22 Sep 2006 05:24:23 +0000 -luke -Updated to version 1.3.4 - -Fri, 22 Sep 2006 05:24:22 +0000 -luke -Updated to version 1.3.4 - -Fri, 22 Sep 2006 05:24:06 +0000 -luke -updating changelog for 1.3.4 - -Wed, 20 Sep 2006 15:46:22 +0000 -luke -Adding patch from #21, adding lsb_release facts - -Tue, 19 Sep 2006 06:55:52 +0000 -luke -Adding yaml support, as requested in #24 - -Tue, 19 Sep 2006 06:50:21 +0000 -luke -applying patch from #18. - -Tue, 19 Sep 2006 06:47:45 +0000 -luke -Fixing facter so it does not fail when an unknown fact is asked for - -Tue, 19 Sep 2006 06:08:23 +0000 -luke -Sorting the facts when they are all output - -Tue, 19 Sep 2006 06:06:56 +0000 -luke -Adding fqdn fact - -Thu, 14 Sep 2006 21:37:03 +0000 -luke -Fixing #20. I just made sure that the Domain fact cchecks the hostname first, so that if the hostname is an fqdn it will set the domain from that. - -Thu, 14 Sep 2006 21:20:12 +0000 -luke -Applying patch from #22 - -Thu, 14 Sep 2006 21:17:33 +0000 -luke -Applying patch in #23. - -Thu, 14 Sep 2006 21:12:23 +0000 -luke -Applying memfree patch from #17. - -Mon, 31 Jul 2006 20:59:22 +0000 -luke -updates - -Sun, 2 Jul 2006 20:17:44 +0000 -luke -doc updates - -Sun, 2 Jul 2006 20:14:26 +0000 -luke -doc updates - -Sun, 2 Jul 2006 19:27:57 +0000 -luke -updates - -Sun, 2 Jul 2006 19:19:54 +0000 -luke -adding docs - -Sun, 2 Jul 2006 19:19:26 +0000 -luke -adding docs - -Wed, 28 Jun 2006 17:38:19 +0000 -luke -Updated to version 1.3.3 - -Wed, 28 Jun 2006 17:38:18 +0000 -luke -Updated to version 1.3.3 - -Wed, 28 Jun 2006 17:38:06 +0000 -luke -updating changelog for 1.3.3 - -Wed, 28 Jun 2006 17:34:05 +0000 -luke -Adding the ability to retrieve facts from the environment. - -Tue, 27 Jun 2006 05:35:08 +0000 -luke -Updated to version 1.3.2 - -Tue, 27 Jun 2006 05:35:06 +0000 -luke -Updated to version 1.3.2 - -Tue, 27 Jun 2006 05:34:07 +0000 -luke -simple packaging updaets - -Tue, 27 Jun 2006 05:33:41 +0000 -luke -Adding thread exclusivity to memory and cpu reading - -Tue, 27 Jun 2006 05:28:17 +0000 -luke -Re-adding these files, since Matt has found a solution to the hanging problem. - -Mon, 26 Jun 2006 22:18:08 +0000 -luke -removing processor.rb in case it has the same problems as the memory file - -Mon, 26 Jun 2006 22:17:24 +0000 -luke -Deleting this file until the hanging problems are resolved - -Tue, 20 Jun 2006 01:19:19 +0000 -luke -fixing license issues - -Tue, 20 Jun 2006 01:07:49 +0000 -luke -fixing spec file again - -Tue, 20 Jun 2006 00:54:25 +0000 -luke -Updated to version 1.3.1 - -Tue, 20 Jun 2006 00:54:23 +0000 -luke -Updated to version 1.3.1 - -Tue, 20 Jun 2006 00:54:20 +0000 -luke -Updated to version 1.3.1 - -Mon, 19 Jun 2006 19:04:48 +0000 -luke -adding a call to dnsdomainname before domainname - -Mon, 19 Jun 2006 18:12:13 +0000 -luke -Fixing #15. Just adding rescue blocks around the load statements. - -Mon, 12 Jun 2006 17:36:03 +0000 -luke -updating for 1.3 - -Mon, 12 Jun 2006 13:17:18 +0000 -lutter -Updated for use with latest Fedora ruby packages - -Fri, 9 Jun 2006 14:58:20 +0000 -luke -Updated to version 1.3 - -Fri, 9 Jun 2006 14:58:18 +0000 -luke -Updated to version 1.3 - -Fri, 9 Jun 2006 14:58:17 +0000 -luke -Updated to version 1.3 - -Wed, 31 May 2006 18:30:59 +0000 -luke -fixing installer so it does not install batch files on darwin - -Wed, 31 May 2006 18:23:04 +0000 -luke -trying to fix facterbin rubylib setting - -Wed, 31 May 2006 18:03:28 +0000 -luke -fixing test so that it works even if rubylib is not set - -Wed, 31 May 2006 17:43:48 +0000 -luke -Adding tagging frameworks back into Facter, and adding the ability to specify tags to the to_hash method so that you only receive facts tagged with specific tags - -Wed, 31 May 2006 16:44:05 +0000 -luke -fixing the linux processor stuff so it only gets called on linux - -Wed, 31 May 2006 00:45:39 +0000 -luke -changing the syntax of the fact confines - -Wed, 31 May 2006 00:41:17 +0000 -luke -Adding some documentation to the binary - -Wed, 31 May 2006 00:04:50 +0000 -luke -Adding rubysitedir fact, as requested in #13. Also, switching the output when one fact is asked for, so it only produces the single value, with no => symbol. - -Tue, 30 May 2006 23:57:01 +0000 -luke -fixing test to ignore differences in memory - -Tue, 30 May 2006 23:55:33 +0000 -luke -Switching "tag" to "confine", because it is a more appropriate term. I will also add "tags", but they will be used for creating fact collections. - -Tue, 30 May 2006 23:49:26 +0000 -luke -Adding patch from #11, with slight modifications. - -Tue, 30 May 2006 23:44:06 +0000 -luke -Adding patch from #11, with slight modifications. - -Tue, 30 May 2006 23:15:00 +0000 -luke -Adding the ability to specify tags as hashes or arrays, as requested in #112. - -Tue, 30 May 2006 22:54:26 +0000 -luke -Getting rid of the autoload method entirely; facts are now only loaded at startup. - -Tue, 30 May 2006 22:33:22 +0000 -luke -fixing linux memory stuff - -Tue, 30 May 2006 22:28:04 +0000 -luke -accepting patch in #10, although with more abstraction, and creating a module for the memory functions - -Tue, 30 May 2006 22:12:04 +0000 -luke -Accepting the patch in #9, with some modifications. - -Thu, 25 May 2006 19:32:51 +0000 -luke -adding solaris pkg stuff - -Tue, 23 May 2006 20:56:51 +0000 -luke -Updated to version 1.2.1 - -Tue, 23 May 2006 20:56:36 +0000 -luke -fixing small bug that only occurs with gems - -Tue, 23 May 2006 19:45:52 +0000 -luke -Updated to version 1.2.0 - -Tue, 23 May 2006 19:45:38 +0000 -luke -Adding final autoloading work. - -Tue, 23 May 2006 19:19:21 +0000 -luke -updating changelog for 1.2.0 - -Tue, 23 May 2006 18:33:05 +0000 -luke -adding another test for the exe - -Tue, 23 May 2006 18:25:30 +0000 -luke -Adding ruby, puppet, and facter version facts - -Tue, 23 May 2006 15:43:09 +0000 -luke -Fixing install and tests so that there are no errors, hopefully. - -Tue, 23 May 2006 15:14:11 +0000 -luke -Added "architecture" fact, added the ability to autoload facts from separate files, and added the ability to retrieve fact values via a method for each fact. - -Tue, 16 May 2006 16:01:57 +0000 -luke -Accepting the patch from #5 - -Tue, 14 Mar 2006 21:17:12 +0000 -luke -Removing ruby as a prereq - -Tue, 14 Mar 2006 07:10:43 +0000 -luke -Converting rakefile to the new build system - -Tue, 14 Mar 2006 03:25:22 +0000 -lutter -Minor changes for hte Fedora Extras review - -Thu, 2 Mar 2006 21:28:06 +0000 -luke -fixing rake file to build and copy rpms automatically - -Tue, 28 Feb 2006 16:23:46 +0000 -luke -updating changelog for 1.1.4 - -Tue, 28 Feb 2006 16:03:42 +0000 -luke -Updated to version 1.1.4 - -Tue, 28 Feb 2006 16:03:30 +0000 -luke -Fixing installer to put the facter executable in /usr/bin instead of / - -Thu, 23 Feb 2006 20:17:48 +0000 -luke -Updated to version 1.1.3 - -Thu, 23 Feb 2006 20:17:35 +0000 -luke -adding 1.1.3 changelog - -Thu, 23 Feb 2006 20:17:02 +0000 -luke -Identifying centos - -Sat, 18 Feb 2006 02:23:06 +0000 -luke -updates - -Fri, 17 Feb 2006 19:56:04 +0000 -luke -Updated to version 1.1.2 - -Fri, 17 Feb 2006 19:55:53 +0000 -luke -Adding ldapname capabilities - -Thu, 9 Feb 2006 18:26:10 +0000 -lutter -Automatically update version and release in the specfile for new releases - -Thu, 9 Feb 2006 18:25:31 +0000 -lutter -Fix specfile in accordance with Fedora Extras guidelines - -Wed, 25 Jan 2006 19:43:37 +0000 -luke -RPM creation now works - -Wed, 25 Jan 2006 19:07:00 +0000 -luke -Working on packaging - -Tue, 10 Jan 2006 22:57:50 +0000 -luke -Updated to version 1.1.1 - -Tue, 10 Jan 2006 22:57:44 +0000 -luke -Fixing bug when a fact with no resolutions is asked for - -Tue, 10 Jan 2006 22:56:16 +0000 -luke -Fixing bug when a fact with no resolutions is asked for - -Mon, 9 Jan 2006 19:55:54 +0000 -luke -Updated to version 1.1.0 - -Mon, 9 Jan 2006 19:55:40 +0000 -luke -Redoing how tags work. - -Wed, 4 Jan 2006 01:35:25 +0000 -luke -updating everything to essentially disable docs generation - -Tue, 3 Jan 2006 19:07:38 +0000 -luke -Adding Release tag - -Tue, 3 Jan 2006 19:05:32 +0000 -luke -adding extra "return nil" statements, and hopefully fixing the test for cygwin - -Tue, 3 Jan 2006 18:53:10 +0000 -luke -Updated to version 1.0.2 - -Tue, 3 Jan 2006 18:52:20 +0000 -luke -adding changelog - -Tue, 3 Jan 2006 18:51:31 +0000 -luke -adding fixes Eric Sorenson found with cygwin - -Fri, 30 Dec 2005 20:09:58 +0000 -luke -updates - -Tue, 22 Nov 2005 15:25:08 +0000 -luke -Updated to version 1.0.1 - -Tue, 22 Nov 2005 15:22:53 +0000 -luke -Modified version - -Wed, 16 Nov 2005 17:45:43 +0000 -luke -removing filehandle-based tests - -Wed, 16 Nov 2005 17:29:21 +0000 -luke -updating INSTALL with patch from ian - -Mon, 29 Aug 2005 21:30:45 +0000 -luke -moving things to the trunk From 5b95a128f369615cb7d7c08f8a9047cc9b44d3f3 Mon Sep 17 00:00:00 2001 From: Nigel Kersten Date: Wed, 7 Oct 2009 07:03:42 -0700 Subject: [PATCH 0410/3753] Fixes #2704. Problem finding install.rb three levels up --- conf/osx/createpackage.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/osx/createpackage.sh b/conf/osx/createpackage.sh index 768380e967..623eb316df 100755 --- a/conf/osx/createpackage.sh +++ b/conf/osx/createpackage.sh @@ -37,7 +37,7 @@ function find_installer() { elif [ -f "../${INSTALLRB}" ]; then installer="$(pwd)/../${INSTALLRB}" elif [ -f "../../${INSTALLRB}" ]; then - installer="$(pwd)/../${INSTALLRB}" + installer="$(pwd)/../../${INSTALLRB}" else installer="" fi From 5412eab12df89a7a701ffea4f5dfb98d0f56985e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phan=20Gorget?= Date: Thu, 5 Nov 2009 00:51:34 +0100 Subject: [PATCH 0411/3753] Fixed : 2788 - ftools missing in Ruby 1.9 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphan Gorget --- install.rb | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/install.rb b/install.rb index ec16c4b047..1280f04e77 100755 --- a/install.rb +++ b/install.rb @@ -35,7 +35,13 @@ require 'rbconfig' require 'find' require 'fileutils' -require 'ftools' # apparently on some system ftools doesn't get loaded +begin + require 'ftools' # apparently on some system ftools doesn't get loaded + $haveftools = true +rescue LoadError + puts "ftools not found. Using FileUtils instead.." + $haveftools = false +end require 'optparse' require 'ostruct' @@ -91,9 +97,15 @@ def do_libs(libs, strip = 'lib/') libs.each do |lf| olf = File.join(InstallOptions.site_dir, lf.gsub(/#{strip}/, '')) op = File.dirname(olf) - File.makedirs(op, true) - File.chmod(0755, op) - File.install(lf, olf, 0644, true) + if $haveftools + File.makedirs(op, true) + File.chmod(0755, op) + File.install(lf, olf, 0644, true) + else + FileUtils.makedirs(op, {:mode => 0755, :verbose => true}) + FileUtils.chmod(0755, op) + FileUtils.install(lf, olf, {:mode => 0644, :verbose => true}) + end end end From b2c1ca56ebc7ec78c71a1fbf6026411494214e67 Mon Sep 17 00:00:00 2001 From: Nigel Kersten Date: Thu, 5 Nov 2009 07:36:04 -0800 Subject: [PATCH 0412/3753] Add docs to Mac OS X package creation script and clean out old docs in the preflight --- conf/osx/createpackage.sh | 11 +++++++++++ conf/osx/preflight | 4 ++++ 2 files changed, 15 insertions(+) diff --git a/conf/osx/createpackage.sh b/conf/osx/createpackage.sh index 623eb316df..fc76b62db2 100755 --- a/conf/osx/createpackage.sh +++ b/conf/osx/createpackage.sh @@ -54,6 +54,17 @@ function install_facter() { chown -R root:admin "${pkgroot}" } +function install_docs() { + echo "Installing docs to ${pkgroot}" + docdir="${pkgroot}/usr/share/doc/facter" + mkdir -p "${docdir}" + for docfile in ChangeLog COPYING LICENSE README README.rst TODO; do + install -m 0644 "${facter_root}/${docfile}" "${docdir}" + done + chown -R root:wheel "${docdir}" + chmod 0755 "${docdir}" +} + function get_facter_version() { facter_version=$(RUBYLIB="${pkgroot}/${SITELIBDIR}:${RUBYLIB}" ruby -e "require 'facter'; puts Facter.version") } diff --git a/conf/osx/preflight b/conf/osx/preflight index 98251bfced..8066bf47d2 100755 --- a/conf/osx/preflight +++ b/conf/osx/preflight @@ -10,3 +10,7 @@ /bin/rm -Rf "${3}{SITELIBDIR}/facter" /bin/rm -Rf "${3}{SITELIBDIR}/facter.rb" + +# remove old doc files + +/bin/rm -Rf "${3}/usr/share/doc/facter" \ No newline at end of file From 810980659d86a30cc9dde6018a4749f659fe2d00 Mon Sep 17 00:00:00 2001 From: Peter Meier Date: Sat, 7 Nov 2009 12:10:29 +0100 Subject: [PATCH 0413/3753] introduce a warn mechanism for debugging We can now warn messages that will be passed to Kernel.warn if debugging is enabled. --- lib/facter.rb | 13 +++++++- spec/unit/facter.rb | 73 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/lib/facter.rb b/lib/facter.rb index 3c69a94812..ac06f68df6 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -68,11 +68,15 @@ def self.debug(string) if string.nil? return end - if @@debug != 0 + if self.debugging? puts GREEN + string + RESET end end + def self.debugging? + @@debug != 0 + end + # Return a fact object by name. If you use this, you still have to call # 'value' on it to retrieve the actual value. def self.[](name) @@ -173,6 +177,13 @@ def self.debugging(bit) end end + + def self.warn(msg) + if Facter.debugging? and msg and not msg.empty? + msg.each { |line| Kernel.warn line } + end + end + # Remove them all. def self.reset @collection = nil diff --git a/spec/unit/facter.rb b/spec/unit/facter.rb index f248aa7673..9f7b4cf9cf 100755 --- a/spec/unit/facter.rb +++ b/spec/unit/facter.rb @@ -131,6 +131,79 @@ Facter.should respond_to(:search_path) end + it "should have a method to query debugging mode" do + Facter.should respond_to(:debugging?) + end + + it "should have a method to warn" do + Facter.should respond_to(:warn) + end + + describe "when warning" do + it "should warn if debugging is enabled" do + Facter.debugging(true) + Kernel.stubs(:warn) + Kernel.expects(:warn).with('foo') + Facter.warn('foo') + end + + it "should not warn if debugging is enabled but nil is passed" do + Facter.debugging(true) + Kernel.stubs(:warn) + Kernel.expects(:warn).never + Facter.warn(nil) + end + + it "should not warn if debugging is enabled but an empyt string is passed" do + Facter.debugging(true) + Kernel.stubs(:warn) + Kernel.expects(:warn).never + Facter.warn('') + end + + it "should not warn if debugging is disabled" do + Facter.debugging(false) + Kernel.stubs(:warn) + Kernel.expects(:warn).never + Facter.warn('foo') + end + + it "should warn for any given element for an array if debugging is enabled" do + Facter.debugging(true) + Kernel.stubs(:warn) + Kernel.expects(:warn).with('foo') + Kernel.expects(:warn).with('bar') + Facter.warn( ['foo','bar']) + end + end + + describe "when setting debugging mode" do + it "should have debugging enabled using 1" do + Facter.debugging(1) + Facter.should be_debugging + end + it "should have debugging enabled using true" do + Facter.debugging(true) + Facter.should be_debugging + end + it "should have debugging enabled using any string except off" do + Facter.debugging('aaaaa') + Facter.should be_debugging + end + it "should have debugging disabled using 0" do + Facter.debugging(0) + Facter.should_not be_debugging + end + it "should have debugging disabled using false" do + Facter.debugging(false) + Facter.should_not be_debugging + end + it "should have debugging disabled using the string 'off'" do + Facter.debugging('off') + Facter.should_not be_debugging + end + end + describe "when registering directories to search" do after { Facter.instance_variable_set("@search_path", []) } From 33fb7709404e706801683e6c47ab7a0a5a1884b1 Mon Sep 17 00:00:00 2001 From: Peter Meier Date: Sat, 7 Nov 2009 12:10:56 +0100 Subject: [PATCH 0414/3753] use popen3 in Resolution.exec to catch stderr So far messages to stderr haven't been catched by Facter::Util::Resolution.exec and were insted printed out to stderr. This will cause facter and even puppet to print to stderr themself, which is not very nice when running puppetd by cron, as you might get every run a mail if a command outputs to stderr. We are now wrapping the command execution with Open3.popen3 to catch stderr and passing them to the new introduced Facter.warn method. We are also catching multiline outputs chomping newlines and returning an array if there have been more than one line. Otherwise we return an array containing the different lines. This prevents in general cases as described in #2766 and should handle command execution in a bit saner way. --- lib/facter/util/resolution.rb | 23 +++++++++++++----- spec/unit/util/resolution.rb | 44 +++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 6 deletions(-) diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index b9e28e8d84..f6afce667c 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -7,6 +7,7 @@ require 'timeout' require 'rbconfig' +require 'open3' class Facter::Util::Resolution attr_accessor :interpreter, :code, :name, :timeout @@ -42,17 +43,27 @@ def self.exec(code, interpreter = "/bin/sh") end out = nil + err = nil begin - out = %x{#{code}}.chomp + Open3.popen3(code) do |stdin,stdout,stderr| + out = self.parse_output(stdout.readlines) + err = self.parse_output(stderr.readlines) + end rescue => detail $stderr.puts detail return nil end - if out == "" - return nil - else - return out - end + Facter.warn(err) + + return nil if out == "" + out + end + + def self.parse_output(output) + return nil unless output and output.size > 0 + result = output.collect{|line| line.chomp } + return result.first unless result.size > 1 + result end # Add a new confine to the resolution mechanism. diff --git a/spec/unit/util/resolution.rb b/spec/unit/util/resolution.rb index d4bb7811b6..27cb1504ab 100755 --- a/spec/unit/util/resolution.rb +++ b/spec/unit/util/resolution.rb @@ -227,10 +227,54 @@ Facter::Util::Resolution.should respond_to(:exec) end + it "should have a class method to parse output" do + Facter::Util::Resolution.should respond_to(:parse_output) + end + # It's not possible, AFAICT, to mock %x{}, so I can't really test this bit. describe "when executing code" do it "should fail if any interpreter other than /bin/sh is requested" do lambda { Facter::Util::Resolution.exec("/something", "/bin/perl") }.should raise_error(ArgumentError) end + + it "should produce stderr content as a warning" do + stdout = stdin = stub('fh', :readlines => ["aaa\n"]) + stderr = stub('stderr', :readlines => %w{my content}) + Open3.expects(:popen3).with("/bin/true").yields(stdin, stdout, stderr) + + Facter.expects(:warn).with(['my','content']) + Facter::Util::Resolution.exec("/bin/true").should == 'aaa' + end + + it "should produce nil as a warning if nothing is printed to stderr" do + stdout = stdin = stub('fh', :readlines => ["aaa\n"]) + stderr = stub('stderr', :readlines => []) + Open3.expects(:popen3).with("/bin/true").yields(stdin, stdout, stderr) + + Facter.expects(:warn).with(nil) + Facter::Util::Resolution.exec("/bin/true").should == 'aaa' + end + end + + describe "when parsing output" do + it "should return nil on nil" do + Facter::Util::Resolution.parse_output(nil).should be_nil + end + + it "should return nil on empty string" do + Facter::Util::Resolution.parse_output('').should be_nil + end + + it "should return nil on an empty array" do + Facter::Util::Resolution.parse_output([]).should be_nil + end + + it "should return a string on a 1 size array" do + Facter::Util::Resolution.parse_output(["aaa\n"]).should == "aaa" + end + + it "should return an array with chomped new lines on an array" do + result = Facter::Util::Resolution.parse_output(["aaa\n","bbb\n","ccc\n"]).should == [ "aaa", "bbb", "ccc" ] + end end end From 68fc123580dd54fb19a684c8b78bff9b19f0467a Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sat, 14 Nov 2009 17:47:39 +1100 Subject: [PATCH 0415/3753] Added package signing task --- tasks/rake/sign.rake | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 tasks/rake/sign.rake diff --git a/tasks/rake/sign.rake b/tasks/rake/sign.rake new file mode 100644 index 0000000000..be5697ba2e --- /dev/null +++ b/tasks/rake/sign.rake @@ -0,0 +1,14 @@ +desc "Sign the package with the Reductive Labs release key" +task :sign_packages do + +version = Facter::FACTERVERSION + +# Sign package + +sh "gpg --homedir $HOME/release_key --detach-sign --output pkg/facter-#{version}.tar.gz.sign --armor pkg/facter-#{version}.tar.gz" + +# Sign gem + +sh "gpg --homedir $HOME/release_key --detach-sign --output pkg/facter-#{version}.gem.sign --armor pkg/facter-#{version}.gem" + +end From feecd393a6d6d94dcea913a3fdf7bb48d9f2e493 Mon Sep 17 00:00:00 2001 From: Ricky Zhou Date: Mon, 21 Dec 2009 15:34:54 -0500 Subject: [PATCH 0416/3753] Only ignore IPs starting with 127. --- lib/facter/ipaddress.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/facter/ipaddress.rb b/lib/facter/ipaddress.rb index 95cdc6403e..dd0d4183eb 100644 --- a/lib/facter/ipaddress.rb +++ b/lib/facter/ipaddress.rb @@ -7,7 +7,7 @@ output.split(/^\S/).each { |str| if str =~ /inet addr:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ tmp = $1 - unless tmp =~ /127\./ + unless tmp =~ /^127\./ ip = tmp break end @@ -27,7 +27,7 @@ output.split(/^\S/).each { |str| if str =~ /inet ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ tmp = $1 - unless tmp =~ /127\./ + unless tmp =~ /^127\./ ip = tmp break end @@ -47,7 +47,7 @@ output.split(/^\S/).each { |str| if str =~ /inet ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ tmp = $1 - unless tmp =~ /127\./ + unless tmp =~ /^127\./ ip = tmp break end @@ -78,7 +78,7 @@ output.split(/^\S/).each { |str| if str =~ /inet ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ tmp = $1 - unless tmp =~ /127\./ + unless tmp =~ /^127\./ ip = tmp break end @@ -98,7 +98,7 @@ output.split(/^\S/).each { |str| if str =~ /inet ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ tmp = $1 - unless tmp =~ /127\./ + unless tmp =~ /^127\./ ip = tmp break end @@ -118,7 +118,7 @@ output.split(/^\S/).each { |str| if str =~ /IP Address.*: ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ tmp = $1 - unless tmp =~ /127\./ + unless tmp =~ /^127\./ ip = tmp break end From 356cf15a72027773d38db5ef74e6861345e32b56 Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Sun, 10 Jan 2010 17:22:30 +0000 Subject: [PATCH 0417/3753] Remove whitespace in DMI facts (#3008, #3011) In addition to the stripping of the output of these facts this patchset: Refactor - Extracted function to enable easier testing. Tests - data driven tests for the dmidecode/smbios fact. Paul --- lib/facter/util/manufacturer.rb | 17 +++--- spec/unit/data/linux_dmidecode_with_spaces | 60 ++++++++++++++++++++++ spec/unit/util/manufacturer.rb | 26 ++++++++++ 3 files changed, 97 insertions(+), 6 deletions(-) create mode 100644 spec/unit/data/linux_dmidecode_with_spaces create mode 100644 spec/unit/util/manufacturer.rb diff --git a/lib/facter/util/manufacturer.rb b/lib/facter/util/manufacturer.rb index c609a12ccd..dd503f9270 100644 --- a/lib/facter/util/manufacturer.rb +++ b/lib/facter/util/manufacturer.rb @@ -2,8 +2,8 @@ # Support methods for manufacturer specific facts module Facter::Manufacturer - def self.dmi_find_system_info(name) - splitstr="Handle" + + def self.get_dmi_table() case Facter.value(:kernel) when 'Linux' return nil unless FileTest.exists?("/usr/sbin/dmidecode") @@ -19,18 +19,23 @@ def self.dmi_find_system_info(name) output=%x{/usr/pkg/sbin/dmidecode 2>/dev/null} when 'SunOS' return nil unless FileTest.exists?("/usr/sbin/smbios") - splitstr="ID SIZE TYPE" - output=%x{/usr/sbin/smbios 2>/dev/null} + output=%x{/usr/sbin/smbios 2>/dev/null} else - return + output=nil end + return output + end + + def self.dmi_find_system_info(name) + splitstr= Facter.value(:kernel) == 'SunOS' ? "ID SIZE TYPE" : "Handle" + output = self.get_dmi_table() name.each_pair do |key,v| v.each do |v2| v2.each_pair do |value,facterkey| output.split(splitstr).each do |line| if line =~ /#{key}/ and ( line =~ /#{value} 0x\d+ \(([-\w].*)\)\n*./ or line =~ /#{value} ([-\w].*)\n*./ ) - result = $1 + result = $1.strip Facter.add(facterkey) do confine :kernel => [ :linux, :freebsd, :netbsd, :sunos ] setcode do diff --git a/spec/unit/data/linux_dmidecode_with_spaces b/spec/unit/data/linux_dmidecode_with_spaces new file mode 100644 index 0000000000..0d77386d5a --- /dev/null +++ b/spec/unit/data/linux_dmidecode_with_spaces @@ -0,0 +1,60 @@ +# dmidecode 2.2 +SMBIOS 2.3 present. +32 structures occupying 994 bytes. +Table at 0x000F0800. +Handle 0x0000 + DMI type 0, 20 bytes. + BIOS Information + Vendor: Award Software International, Inc. + Version: 6.00 PG + Release Date: 01/03/2003 + Address: 0xE0000 + Runtime Size: 128 kB + ROM Size: 256 kB + Characteristics: + ISA is supported + PCI is supported + PNP is supported + APM is supported + BIOS is upgradeable + BIOS shadowing is allowed + ESCD support is available + Boot from CD is supported + Selectable boot is supported + BIOS ROM is socketed + EDD is supported + 5.25"/360 KB floppy services are supported (int 13h) + 5.25"/1.2 MB floppy services are supported (int 13h) + 3.5"/720 KB floppy services are supported (int 13h) + 3.5"/2.88 MB floppy services are supported (int 13h) + Print screen service is supported (int 5h) + 8042 keyboard services are supported (int 9h) + Serial services are supported (int 14h) + Printer services are supported (int 17h) + CGA/mono video services are supported (int 10h) + ACPI is supported + USB legacy is supported + AGP is supported + LS-120 boot is supported + ATAPI Zip drive boot is supported +Handle 0x0001 + DMI type 1, 25 bytes. + System Information + Manufacturer: MICRO-STAR INTERNATIONAL CO., LTD + Product Name: MS-6754 + Version: + Serial Number: + UUID: Not Present + Wake-up Type: Power Switch +Handle 0x0002 + DMI type 2, 8 bytes. + Base Board Information + Manufacturer: MICRO-STAR INTERNATIONAL CO., LTD + Product Name: MS-6754 + Version: + Serial Number: + +Handle 0x001F + DMI type 127, 4 bytes. + End Of Table + diff --git a/spec/unit/util/manufacturer.rb b/spec/unit/util/manufacturer.rb new file mode 100644 index 0000000000..47a0c98ab5 --- /dev/null +++ b/spec/unit/util/manufacturer.rb @@ -0,0 +1,26 @@ +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'facter/util/manufacturer' + +describe Facter::Manufacturer do + it "should return the system DMI table" do + Facter::Manufacturer.should respond_to(:get_dmi_table) + end + + it "should return nil on non-supported operating systems" do + Facter.stubs(:value).with(:kernel).returns("SomeThing") + Facter::Manufacturer.get_dmi_table().should be_nil + end + + it "should strip white space on dmi output with spaces" do + sample_output_file = File.dirname(__FILE__) + "/../data/linux_dmidecode_with_spaces" + dmidecode_output = File.new(sample_output_file).read() + Facter::Manufacturer.expects(:get_dmi_table).returns(dmidecode_output) + Facter.fact(:kernel).stubs(:value).returns("Linux") + + query = { '[Ss]ystem [Ii]nformation' => [ { 'Product(?: Name)?:' => 'productname' } ] } + + Facter::Manufacturer.dmi_find_system_info(query) + Facter.value(:productname).should == "MS-6754" + end +end \ No newline at end of file From 50cef83753273bb326a3282f33f0089504d91319 Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Sun, 17 Jan 2010 16:00:26 +0000 Subject: [PATCH 0418/3753] Fix missing error case --- lib/facter/util/manufacturer.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/facter/util/manufacturer.rb b/lib/facter/util/manufacturer.rb index dd503f9270..d0152268a3 100644 --- a/lib/facter/util/manufacturer.rb +++ b/lib/facter/util/manufacturer.rb @@ -30,6 +30,7 @@ def self.get_dmi_table() def self.dmi_find_system_info(name) splitstr= Facter.value(:kernel) == 'SunOS' ? "ID SIZE TYPE" : "Handle" output = self.get_dmi_table() + return if output.nil? name.each_pair do |key,v| v.each do |v2| v2.each_pair do |value,facterkey| From f4269d90316b32334cc3bc972737acde379e6262 Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Sun, 17 Jan 2010 16:08:16 +0000 Subject: [PATCH 0419/3753] Fix #2746 - add architecture support for GNU/kFreeBSD --- lib/facter/architecture.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/facter/architecture.rb b/lib/facter/architecture.rb index 44fc97f419..abd54da977 100644 --- a/lib/facter/architecture.rb +++ b/lib/facter/architecture.rb @@ -1,12 +1,12 @@ Facter.add(:architecture) do - confine :kernel => :linux + confine :kernel => [:linux, :"gnu/kfreebsd"] setcode do model = Facter.value(:hardwaremodel) case model # most linuxen use "x86_64" when "x86_64" case Facter.value(:operatingsystem) - when "Debian", "Gentoo" + when "Debian", "Gentoo", "GNU/kFreeBSD" "amd64" else model From 7750f036539afa4ad04aef676ab27c551e97ad3e Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Sun, 17 Jan 2010 17:02:38 +0000 Subject: [PATCH 0420/3753] Fix #2341 - stricter handling of dmidecode split This adds a test to ensure we are not prematurely splitting on Handle --- lib/facter/util/manufacturer.rb | 2 +- spec/unit/util/manufacturer.rb | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/lib/facter/util/manufacturer.rb b/lib/facter/util/manufacturer.rb index d0152268a3..baf24bc8bb 100644 --- a/lib/facter/util/manufacturer.rb +++ b/lib/facter/util/manufacturer.rb @@ -28,7 +28,7 @@ def self.get_dmi_table() end def self.dmi_find_system_info(name) - splitstr= Facter.value(:kernel) == 'SunOS' ? "ID SIZE TYPE" : "Handle" + splitstr= Facter.value(:kernel) == 'SunOS' ? "ID SIZE TYPE" : "/^Handle/" output = self.get_dmi_table() return if output.nil? name.each_pair do |key,v| diff --git a/spec/unit/util/manufacturer.rb b/spec/unit/util/manufacturer.rb index 47a0c98ab5..11d207d2f1 100644 --- a/spec/unit/util/manufacturer.rb +++ b/spec/unit/util/manufacturer.rb @@ -23,4 +23,26 @@ Facter::Manufacturer.dmi_find_system_info(query) Facter.value(:productname).should == "MS-6754" end + + it "should not split on dmi keys containing the string Handle" do + dmidecode_output = <<-eos +Handle 0x1000, DMI type 16, 15 bytes +Physical Memory Array + Location: System Board Or Motherboard + Use: System Memory + Error Correction Type: None + Maximum Capacity: 4 GB + Error Information Handle: Not Provided + Number Of Devices: 2 + +Handle 0x001F + DMI type 127, 4 bytes. + End Of Table + eos + Facter::Manufacturer.expects(:get_dmi_table).returns(dmidecode_output) + Facter.fact(:kernel).stubs(:value).returns("Linux") + query = { 'Physical Memory Array' => [ { 'Number Of Devices:' => 'ramslots'}]} + Facter::Manufacturer.dmi_find_system_info(query) + Facter.value(:ramslots).should == "2" + end end \ No newline at end of file From 86447c884a47948a4ef6757ff7e7d2e0faf93ff5 Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Sun, 28 Feb 2010 13:50:52 +0000 Subject: [PATCH 0421/3753] Revert "use popen3 in Resolution.exec" This reverts commit 33fb7709404e706801683e6c47ab7a0a5a1884b1. This has broken master for some facts (eg OS X facts), it also breaks facter platform support on win32 as popen3 does not work there. I'd like to get master working, backlog of patches applied and revisit exec. Paul --- lib/facter/util/resolution.rb | 23 +++++------------- spec/unit/util/resolution.rb | 44 ----------------------------------- 2 files changed, 6 insertions(+), 61 deletions(-) diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index f6afce667c..b9e28e8d84 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -7,7 +7,6 @@ require 'timeout' require 'rbconfig' -require 'open3' class Facter::Util::Resolution attr_accessor :interpreter, :code, :name, :timeout @@ -43,27 +42,17 @@ def self.exec(code, interpreter = "/bin/sh") end out = nil - err = nil begin - Open3.popen3(code) do |stdin,stdout,stderr| - out = self.parse_output(stdout.readlines) - err = self.parse_output(stderr.readlines) - end + out = %x{#{code}}.chomp rescue => detail $stderr.puts detail return nil end - Facter.warn(err) - - return nil if out == "" - out - end - - def self.parse_output(output) - return nil unless output and output.size > 0 - result = output.collect{|line| line.chomp } - return result.first unless result.size > 1 - result + if out == "" + return nil + else + return out + end end # Add a new confine to the resolution mechanism. diff --git a/spec/unit/util/resolution.rb b/spec/unit/util/resolution.rb index 27cb1504ab..d4bb7811b6 100755 --- a/spec/unit/util/resolution.rb +++ b/spec/unit/util/resolution.rb @@ -227,54 +227,10 @@ Facter::Util::Resolution.should respond_to(:exec) end - it "should have a class method to parse output" do - Facter::Util::Resolution.should respond_to(:parse_output) - end - # It's not possible, AFAICT, to mock %x{}, so I can't really test this bit. describe "when executing code" do it "should fail if any interpreter other than /bin/sh is requested" do lambda { Facter::Util::Resolution.exec("/something", "/bin/perl") }.should raise_error(ArgumentError) end - - it "should produce stderr content as a warning" do - stdout = stdin = stub('fh', :readlines => ["aaa\n"]) - stderr = stub('stderr', :readlines => %w{my content}) - Open3.expects(:popen3).with("/bin/true").yields(stdin, stdout, stderr) - - Facter.expects(:warn).with(['my','content']) - Facter::Util::Resolution.exec("/bin/true").should == 'aaa' - end - - it "should produce nil as a warning if nothing is printed to stderr" do - stdout = stdin = stub('fh', :readlines => ["aaa\n"]) - stderr = stub('stderr', :readlines => []) - Open3.expects(:popen3).with("/bin/true").yields(stdin, stdout, stderr) - - Facter.expects(:warn).with(nil) - Facter::Util::Resolution.exec("/bin/true").should == 'aaa' - end - end - - describe "when parsing output" do - it "should return nil on nil" do - Facter::Util::Resolution.parse_output(nil).should be_nil - end - - it "should return nil on empty string" do - Facter::Util::Resolution.parse_output('').should be_nil - end - - it "should return nil on an empty array" do - Facter::Util::Resolution.parse_output([]).should be_nil - end - - it "should return a string on a 1 size array" do - Facter::Util::Resolution.parse_output(["aaa\n"]).should == "aaa" - end - - it "should return an array with chomped new lines on an array" do - result = Facter::Util::Resolution.parse_output(["aaa\n","bbb\n","ccc\n"]).should == [ "aaa", "bbb", "ccc" ] - end end end From dca615c98b864d75e2ac5899d98d04a2bd979eba Mon Sep 17 00:00:00 2001 From: Ohad Levy Date: Mon, 18 Jan 2010 10:42:43 +0800 Subject: [PATCH 0422/3753] fixes #2573, #2085, #1291 - fixes domain and fqdn facts resolution This patch removes the relationship between the domain fact and LDAP/NIS domains. domain fact relates to DNS domain - this will avoid the confusion caused by the LDAP/NIS domain (which might be different to the DNS domain name). Additionally, if hostname is already in long form, it won't try to build the fqdn fact from hostname and domain. --- lib/facter/domain.rb | 8 ++++---- lib/facter/fqdn.rb | 4 ++++ lib/facter/hostname.rb | 7 ++++--- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/lib/facter/domain.rb b/lib/facter/domain.rb index b1bba4d698..5dfead0d41 100644 --- a/lib/facter/domain.rb +++ b/lib/facter/domain.rb @@ -4,14 +4,14 @@ # steps is important Facter.value(:hostname) - next $domain if defined? $domain and ! $domain.nil? + # try to fetch the domain from hostname if long hostname is used. + if defined? $fqdn and $fqdn =~ /^([\w-]+)\.(.+)$/ + next $2 + end domain = Facter::Util::Resolution.exec('dnsdomainname') next domain if domain =~ /.+\..+/ - domain = Facter::Util::Resolution.exec('domainname') - next domain if domain =~ /.+\..+/ - if FileTest.exists?("/etc/resolv.conf") domain = nil search = nil diff --git a/lib/facter/fqdn.rb b/lib/facter/fqdn.rb index 5ebc5f5c0b..6271995cb3 100644 --- a/lib/facter/fqdn.rb +++ b/lib/facter/fqdn.rb @@ -1,5 +1,9 @@ Facter.add(:fqdn) do setcode do + # try to fetch the fqdn from hostname if long hostname is used. + Facter.value(:hostname) + next $fqdn if defined? $fqdn and ! $fqdn.nil? + host = Facter.value(:hostname) domain = Facter.value(:domain) if host and domain diff --git a/lib/facter/hostname.rb b/lib/facter/hostname.rb index 188efa4cda..c3ca968d87 100644 --- a/lib/facter/hostname.rb +++ b/lib/facter/hostname.rb @@ -1,12 +1,13 @@ Facter.add(:hostname, :ldapname => "cn") do setcode do + require 'socket' hostname = nil - name = Facter::Util::Resolution.exec('hostname') or nil + name = Socket.gethostbyname(Socket.gethostname).first if name if name =~ /^([\w-]+)\.(.+)$/ hostname = $1 - # the Domain class uses this - $domain = $2 + # the FQDN/Domain facts use this + $fqdn = name else hostname = name end From 62b6773a63bb96273fbcb6f79bec524fb67df075 Mon Sep 17 00:00:00 2001 From: John Ferlito Date: Sun, 14 Feb 2010 18:07:43 +1100 Subject: [PATCH 0423/3753] Add kvm support to virtual fact Based on initial patch by James Turnbull --- lib/facter/util/virtual.rb | 17 +++++++++++++++++ lib/facter/virtual.rb | 6 +++++- spec/unit/util/virtual.rb | 7 +++++++ spec/unit/virtual.rb | 6 ++++++ 4 files changed, 35 insertions(+), 1 deletion(-) diff --git a/lib/facter/util/virtual.rb b/lib/facter/util/virtual.rb index 0c3fb731ac..ca7c367f1c 100644 --- a/lib/facter/util/virtual.rb +++ b/lib/facter/util/virtual.rb @@ -40,4 +40,21 @@ def self.xen? FileTest.exists?(f) end end + + def self.kvm? + if FileTest.exists?("/proc/cpuinfo") + txt = File.read("/proc/cpuinfo") + return true if txt =~ /QEMU Virtual CPU/ + end + return false + end + + def self.kvm_type + # TODO Tell the difference between kvm and qemu + # Can't work out a way to do this at the moment that doesn't + # require a special binary + "kvm" + end + + end diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 78413a9488..3f02003619 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -34,6 +34,10 @@ end end + if Facter::Util::Virtual.kvm? + result = Facter::Util::Virtual.kvm_type() + end + if result == "physical" output = Facter::Util::Resolution.exec('lspci') if not output.nil? @@ -72,7 +76,7 @@ setcode do case Facter.value(:virtual) - when "xenu", "openvzve", "vmware" + when "xenu", "openvzve", "vmware", "kvm" true else false diff --git a/spec/unit/util/virtual.rb b/spec/unit/util/virtual.rb index 1f1c0f8d20..de339b88b1 100644 --- a/spec/unit/util/virtual.rb +++ b/spec/unit/util/virtual.rb @@ -93,4 +93,11 @@ FileTest.expects(:exists?).with("/proc/xen").returns(false) Facter::Util::Virtual.should_not be_xen end + + it "should detect kvm" do + FileTest.stubs(:exists?).with("/proc/cpuinfo").returns(true) + File.stubs(:read).with("/proc/cpuinfo").returns("model name : QEMU Virtual CPU version 0.9.1\n") + Facter::Util::Virtual.should be_kvm + end + end diff --git a/spec/unit/virtual.rb b/spec/unit/virtual.rb index cc72ffc1b1..fe9988e413 100644 --- a/spec/unit/virtual.rb +++ b/spec/unit/virtual.rb @@ -48,4 +48,10 @@ Facter.fact(:virtual).stubs(:value).returns("openvzve") Facter.fact(:is_virtual).value.should == true end + + it "should be true when running on kvm" do + Facter.fact(:virtual).stubs(:value).returns("kvm") + Facter.fact(:is_virtual).value.should == true + end + end From 8bf8cb59fcc0b298e744781e3adb4cd1fdf3a9ac Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Mon, 12 Apr 2010 11:33:02 +1000 Subject: [PATCH 0424/3753] Fixes #3397 - is_virtual fact does not detect Linux-VServer Thanks to Benedikt Bohm for the fix. --- lib/facter/virtual.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 3f02003619..39e450ec07 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -76,7 +76,7 @@ setcode do case Facter.value(:virtual) - when "xenu", "openvzve", "vmware", "kvm" + when "xenu", "openvzve", "vmware", "kvm", "vserver" true else false From aeee83cd3ae7e8740a9ae04de71cbcc2057dc864 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Mon, 12 Apr 2010 11:43:54 +1000 Subject: [PATCH 0425/3753] Fixed #3410 - Warnings in rake spec --- Rakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index e3549ca822..67d4c90f39 100644 --- a/Rakefile +++ b/Rakefile @@ -5,7 +5,7 @@ $LOAD_PATH << File.join(File.dirname(__FILE__), 'tasks') Dir['tasks/**/*.rake'].each { |t| load t } -require './lib/facter.rb' +require 'facter.rb' require 'rake' require 'rake/packagetask' require 'rake/gempackagetask' From 8ea33eb80c117370312dc22391f0e6f31a7ccd22 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Mon, 12 Apr 2010 11:48:06 +1000 Subject: [PATCH 0426/3753] Fixed #3447 - OVS and OEL not matching in operatingsystemrelease --- lib/facter/operatingsystemrelease.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index 2138d86781..358496ab44 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -6,9 +6,9 @@ releasefile = "/etc/redhat-release" when "Fedora" releasefile = "/etc/fedora-release" - when "oel" + when "OEL" releasefile = "/etc/enterprise-release" - when "ovs" + when "OVS" releasefile = "/etc/ovs-release" end File::open(releasefile, "r") do |f| From b5a8de0e8f4583c27d776cabe7686f41edb552fb Mon Sep 17 00:00:00 2001 From: Martin Englund Date: Tue, 23 Mar 2010 11:50:39 +0100 Subject: [PATCH 0427/3753] Fix for #3411 install.rb should not put "." first in the tmp_dirs Signed-off-by: Martin Englund --- install.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install.rb b/install.rb index 1280f04e77..d1b3f1804a 100755 --- a/install.rb +++ b/install.rb @@ -212,7 +212,7 @@ def prepare_installation opts.parse! end - tmpdirs = [".", ENV['TMP'], ENV['TEMP'], "/tmp", "/var/tmp"] + tmpdirs = [ENV['TMP'], ENV['TEMP'], "/tmp", "/var/tmp", "."] version = [Config::CONFIG["MAJOR"], Config::CONFIG["MINOR"]].join(".") libdir = File.join(Config::CONFIG["libdir"], "ruby", version) From 84d3d9f2abbe1f1a113103fe92d7fac39b734c9d Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sat, 24 Apr 2010 15:42:37 +1000 Subject: [PATCH 0428/3753] Fixed #3445 - Facter does not handle solaris branded zones properly Thanks to Pavol Dilung for the fix. --- lib/facter/util/virtual.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/facter/util/virtual.rb b/lib/facter/util/virtual.rb index ca7c367f1c..80f4e2c7d7 100644 --- a/lib/facter/util/virtual.rb +++ b/lib/facter/util/virtual.rb @@ -13,6 +13,7 @@ def self.openvz_type end def self.zone? + return true if FileTest.directory?("/.SUNWnative") z = Facter::Util::Resolution.exec("/sbin/zonename") return false unless z return z.chomp != 'global' From 2f016f387367d6e52504194098333d1692e33c67 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sat, 24 Apr 2010 15:45:01 +1000 Subject: [PATCH 0429/3753] Fixed #3541 - Ruby 1.9: broken unittest, unexpected invocation: Process.waitall() Thanks to Jos Backus for the fix. --- spec/unit/util/resolution.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/unit/util/resolution.rb b/spec/unit/util/resolution.rb index d4bb7811b6..7cbfb31948 100755 --- a/spec/unit/util/resolution.rb +++ b/spec/unit/util/resolution.rb @@ -136,6 +136,7 @@ @resolve.expects(:warn) @resolve.timeout = 0.1 @resolve.setcode { sleep 2; raise "This is a test" } + Thread.expects(:new).yields @resolve.value.should be_nil end From 802e6c24a0968deb0b45402d73c850f045b70c46 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sat, 24 Apr 2010 15:46:45 +1000 Subject: [PATCH 0430/3753] Fixed #3542 - Ruby 1.9: broken unittest, String#each no longer exists Thanks to Jos Backus for the fix. --- lib/facter.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/facter.rb b/lib/facter.rb index ac06f68df6..46e2e99ed8 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -180,6 +180,7 @@ def self.debugging(bit) def self.warn(msg) if Facter.debugging? and msg and not msg.empty? + msg = [msg] unless msg.respond_to? :each msg.each { |line| Kernel.warn line } end end From 97879f9f576484b54fde41326a87129a1fe4f8b6 Mon Sep 17 00:00:00 2001 From: Bostjan Skufca Date: Fri, 8 Jan 2010 09:49:28 +0100 Subject: [PATCH 0431/3753] Added support for Slackware in operatingsystem and operatingsystemrelease --- lib/facter/operatingsystem.rb | 2 ++ lib/facter/operatingsystemrelease.rb | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index 335003a29f..d909fba214 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -48,6 +48,8 @@ else "SuSE" end + elsif FileTest.exists?("/etc/slackware-version") + "Slackware" end end end diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index 358496ab44..91f40a7c42 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -59,6 +59,16 @@ end end +Facter.add(:operatingsystemrelease) do + confine :operatingsystem => %w{Slackware} + setcode do + release = Facter::Util::Resolution.exec('cat /etc/slackware-version') + if release =~ /Slackware ([0-9.]+)/ + $1 + end + end +end + Facter.add(:operatingsystemrelease) do setcode do Facter[:kernelrelease].value end end From e19024bbef18a4a2053537415bfe16bfaff00b8a Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sat, 24 Apr 2010 15:54:09 +1000 Subject: [PATCH 0432/3753] Fixed #2938 - interfaces that don't match ^\w+[.:]?\d+ are ignored Thanks to Tim Sharpe for the fix --- lib/facter/util/ip.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index 5941d38c31..25acf3abe1 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -24,7 +24,7 @@ module Facter::Util::IP # Convert an interface name into purely alpha characters. def self.alphafy(interface) - interface.gsub(/[:.]/, '_') + interface.gsub(/[-:.]/, '_') end def self.convert_from_hex?(kernel) @@ -51,7 +51,7 @@ def self.get_interfaces # at the end of interfaces. So, we have to trim those trailing # characters. I tried making the regex better but supporting all # platforms with a single regex is probably a bit too much. - output.scan(/^\w+[.:]?\d+[.:]?\d*[.:]?\w*/).collect { |i| i.sub(/:$/, '') }.uniq + output.scan(/^[-\w]+[.:]?\d+[.:]?\d*[.:]?\w*/).collect { |i| i.sub(/:$/, '') }.uniq end def self.get_all_interface_output From 9a00eaeb3a26fa32f9fda2bf3ff733f5214864ba Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sat, 24 Apr 2010 16:15:22 +1000 Subject: [PATCH 0433/3753] Fixed #2313 - Somewhat essential hardware facts not available on OpenBSD, patch included Thanks to Joe McDonagh for the patch. --- lib/facter/architecture.rb | 8 ++++++++ lib/facter/memory.rb | 41 ++++++++++++++++++++++++++++++++++++++ lib/facter/processor.rb | 16 +++++++++++++++ 3 files changed, 65 insertions(+) diff --git a/lib/facter/architecture.rb b/lib/facter/architecture.rb index abd54da977..e4aaeba5e2 100644 --- a/lib/facter/architecture.rb +++ b/lib/facter/architecture.rb @@ -23,3 +23,11 @@ end end end + +Facter.add(:architecture) do + confine :kernel => :openbsd + setcode do + architecture = Facter.value(:hardwaremodel) + end +end + diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb index 688073180e..f0f0d45c7f 100644 --- a/lib/facter/memory.rb +++ b/lib/facter/memory.rb @@ -44,3 +44,44 @@ end end end + +if Facter.value(:kernel) == "OpenBSD" + swap = Facter::Util::Resolution.exec('swapctl -l | sed 1d') + swapfree, swaptotal = 0, 0 + swap.each do |dev| + if dev =~ /^\S+\s+(\S+)\s+\S+\s+(\S+)\s+.*$/ + swaptotal += $1.to_i + swapfree += $2.to_i + end + end + + Facter.add("SwapSize") do + confine :kernel => :openbsd + setcode do + Facter::Memory.scale_number(swaptotal.to_f,"kB") + end + end + + Facter.add("SwapFree") do + confine :kernel => :openbsd + setcode do + Facter::Memory.scale_number(swapfree.to_f,"kB") + end + end + + Facter.add("MemoryFree") do + confine :kernel => :openbsd + memfree = Facter::Util::Resolution.exec("vmstat | tail -n 1 | awk '{ print $5 }'") + setcode do + Facter::Memory.scale_number(memfree.to_f,"kB") + end + end + + Facter.add("MemoryTotal") do + confine :kernel => :openbsd + memtotal = Facter::Util::Resolution.exec("sysctl hw.physmem | cut -d'=' -f2") + setcode do + Facter::Memory.scale_number(memtotal.to_f,"") + end + end +end diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb index 154cced912..53998ff418 100644 --- a/lib/facter/processor.rb +++ b/lib/facter/processor.rb @@ -72,3 +72,19 @@ end end end + +if Facter.value(:kernel) == "OpenBSD" + Facter.add("Processor") do + confine :kernel => :openbsd + setcode do + Facter::Util::Resolution.exec("uname -p") + end + end + + Facter.add("ProcessorCount") do + confine :kernel => :openbsd + setcode do + Facter::Util::Resolution.exec("sysctl hw.ncpu | cut -d'=' -f2") + end + end +end From 25bf5c2d7af12136fc20aa77b75ff880fb04344c Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Mon, 26 Apr 2010 07:26:14 +0100 Subject: [PATCH 0434/3753] Fix virtual unit test on non-linux by stubbing kernel Signed-off-by: Paul Nasrat --- spec/unit/virtual.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/unit/virtual.rb b/spec/unit/virtual.rb index fe9988e413..7dbd1465fe 100644 --- a/spec/unit/virtual.rb +++ b/spec/unit/virtual.rb @@ -50,6 +50,7 @@ end it "should be true when running on kvm" do + Facter.fact(:kernel).stubs(:value).returns("Linux") Facter.fact(:virtual).stubs(:value).returns("kvm") Facter.fact(:is_virtual).value.should == true end From c5b8d3b9c2621649d0559ed87a3d5493d706288a Mon Sep 17 00:00:00 2001 From: Marc Fournier Date: Fri, 7 May 2010 08:46:36 +0200 Subject: [PATCH 0435/3753] Fixes #3740 - split dmi output on regex Splitting dmi output on the string "/^Handle/" didn't work, and caused the function to match the wrong key if it was found more than once. The intended behaviour of the function is restored by splitting the dmi output on the regex /^Handle/. --- lib/facter/util/manufacturer.rb | 2 +- spec/unit/util/manufacturer.rb | 40 ++++++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/lib/facter/util/manufacturer.rb b/lib/facter/util/manufacturer.rb index baf24bc8bb..61d617a14b 100644 --- a/lib/facter/util/manufacturer.rb +++ b/lib/facter/util/manufacturer.rb @@ -28,7 +28,7 @@ def self.get_dmi_table() end def self.dmi_find_system_info(name) - splitstr= Facter.value(:kernel) == 'SunOS' ? "ID SIZE TYPE" : "/^Handle/" + splitstr= Facter.value(:kernel) == 'SunOS' ? "ID SIZE TYPE" : /^Handle/ output = self.get_dmi_table() return if output.nil? name.each_pair do |key,v| diff --git a/spec/unit/util/manufacturer.rb b/spec/unit/util/manufacturer.rb index 11d207d2f1..f9ec81f1b8 100644 --- a/spec/unit/util/manufacturer.rb +++ b/spec/unit/util/manufacturer.rb @@ -45,4 +45,42 @@ Facter::Manufacturer.dmi_find_system_info(query) Facter.value(:ramslots).should == "2" end -end \ No newline at end of file + + it "should match the key in the defined section and not the first one found" do + dmidecode_output = <<-eos +Handle 0x000C, DMI type 7, 19 bytes +Cache Information + Socket Designation: Internal L2 Cache + Configuration: Enabled, Socketed, Level 2 + Operational Mode: Write Back + Location: Internal + Installed Size: 4096 KB + Maximum Size: 4096 KB + Supported SRAM Types: + Burst + Installed SRAM Type: Burst + Speed: Unknown + Error Correction Type: Single-bit ECC + System Type: Unified + Associativity: 8-way Set-associative + +Handle 0x1000, DMI type 16, 15 bytes +Physical Memory Array + Location: System Board Or Motherboard + Use: System Memory + Error Correction Type: None + Maximum Capacity: 4 GB + Error Information Handle: Not Provided + Number Of Devices: 2 + +Handle 0x001F + DMI type 127, 4 bytes. + End Of Table + eos + Facter::Manufacturer.expects(:get_dmi_table).returns(dmidecode_output) + Facter.fact(:kernel).stubs(:value).returns("Linux") + query = { 'Physical Memory Array' => [ { 'Location:' => 'ramlocation'}]} + Facter::Manufacturer.dmi_find_system_info(query) + Facter.value(:ramlocation).should == "System Board Or Motherboard" + end +end From 6c87917b23a4607bbca7fba721b211d150037920 Mon Sep 17 00:00:00 2001 From: Marc Fournier Date: Fri, 7 May 2010 09:05:56 +0200 Subject: [PATCH 0436/3753] Fixed failing test introduced by previous commit --- lib/facter/util/manufacturer.rb | 2 +- spec/unit/util/manufacturer.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/facter/util/manufacturer.rb b/lib/facter/util/manufacturer.rb index 61d617a14b..b781e12d09 100644 --- a/lib/facter/util/manufacturer.rb +++ b/lib/facter/util/manufacturer.rb @@ -35,7 +35,7 @@ def self.dmi_find_system_info(name) v.each do |v2| v2.each_pair do |value,facterkey| output.split(splitstr).each do |line| - if line =~ /#{key}/ and ( line =~ /#{value} 0x\d+ \(([-\w].*)\)\n*./ or line =~ /#{value} ([-\w].*)\n*./ ) + if line =~ /#{key}/ and ( line =~ /#{value} 0x\d+ \(([-\w].*)\)\n*/ or line =~ /#{value} ([-\w].*)\n*/ ) result = $1.strip Facter.add(facterkey) do confine :kernel => [ :linux, :freebsd, :netbsd, :sunos ] diff --git a/spec/unit/util/manufacturer.rb b/spec/unit/util/manufacturer.rb index f9ec81f1b8..74660f7227 100644 --- a/spec/unit/util/manufacturer.rb +++ b/spec/unit/util/manufacturer.rb @@ -33,7 +33,7 @@ Error Correction Type: None Maximum Capacity: 4 GB Error Information Handle: Not Provided - Number Of Devices: 2 + Number Of Devices: 123 Handle 0x001F DMI type 127, 4 bytes. @@ -43,7 +43,7 @@ Facter.fact(:kernel).stubs(:value).returns("Linux") query = { 'Physical Memory Array' => [ { 'Number Of Devices:' => 'ramslots'}]} Facter::Manufacturer.dmi_find_system_info(query) - Facter.value(:ramslots).should == "2" + Facter.value(:ramslots).should == "123" end it "should match the key in the defined section and not the first one found" do From d109def6aff3bccfbd79e4ee83479fe8f1a2a397 Mon Sep 17 00:00:00 2001 From: "R.I. Pienaar" Date: Thu, 27 May 2010 11:17:39 +0100 Subject: [PATCH 0437/3753] Fix #1365 - load all facts via cli This changes the CLI to load all facts. --- bin/facter | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/facter b/bin/facter index 4ffd50acb3..a6cb717e75 100755 --- a/bin/facter +++ b/bin/facter @@ -134,9 +134,9 @@ ARGV.each { |item| names.push item } -if names.empty? - facts = Facter.to_hash -else +facts = Facter.to_hash + +unless names.empty? facts = {} names.each { |name| begin From 73dcbb941f145aac05ea4f0df9a7a14787de27d9 Mon Sep 17 00:00:00 2001 From: Bernhard Furtmueller Date: Fri, 11 Sep 2009 01:09:22 -0400 Subject: [PATCH 0438/3753] Fixed #2355 read hang on /proc/xen/capabilties on RHEL 4.7 --- lib/facter/virtual.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 39e450ec07..c6d0f22f06 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -25,9 +25,9 @@ if FileTest.exists?("/sys/bus/xen") result = "xenu" end - + if FileTest.exists?("/proc/xen/capabilities") - txt = File.read("/proc/xen/capabilities") + txt = Facter::Util::Resolution.exec("cat /proc/xen/capabilities") if txt =~ /control_d/i result = "xen0" end From d4b8401dc4b2323a0c162e59e3f98b355d2c3c40 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sat, 12 Jun 2010 04:54:04 +1000 Subject: [PATCH 0439/3753] Merged Jos Backus patch to remove requirement for ftools altogether --- install.rb | 25 ++++++------------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/install.rb b/install.rb index d1b3f1804a..a40e2927bd 100755 --- a/install.rb +++ b/install.rb @@ -35,13 +35,6 @@ require 'rbconfig' require 'find' require 'fileutils' -begin - require 'ftools' # apparently on some system ftools doesn't get loaded - $haveftools = true -rescue LoadError - puts "ftools not found. Using FileUtils instead.." - $haveftools = false -end require 'optparse' require 'ostruct' @@ -97,15 +90,9 @@ def do_libs(libs, strip = 'lib/') libs.each do |lf| olf = File.join(InstallOptions.site_dir, lf.gsub(/#{strip}/, '')) op = File.dirname(olf) - if $haveftools - File.makedirs(op, true) - File.chmod(0755, op) - File.install(lf, olf, 0644, true) - else - FileUtils.makedirs(op, {:mode => 0755, :verbose => true}) - FileUtils.chmod(0755, op) - FileUtils.install(lf, olf, {:mode => 0644, :verbose => true}) - end + FileUtils.makedirs(op, {:mode => 0755, :verbose => true}) + FileUtils.chmod(0755, op) + FileUtils.install(lf, olf, {:mode => 0644, :verbose => true}) end end @@ -113,9 +100,9 @@ def do_man(man, strip = 'man/') man.each do |mf| omf = File.join(InstallOptions.man_dir, mf.gsub(/#{strip}/, '')) om = File.dirname(omf) - File.makedirs(om, true) - File.chmod(0644, om) - File.install(mf, omf, 0644, true) + FileUtils.makedirs(om, {:mode => 0755, :verbose => true}) + FileUtils.chmod(0755, om) + FileUtils.install(mf, omf, {:mode => 0644, :verbose => true}) gzip = %x{which gzip} gzip.chomp! %x{#{gzip} -f #{omf}} From ffcae46df5d5336995348544139540c0e564ae2e Mon Sep 17 00:00:00 2001 From: Jonas Genannt Date: Mon, 12 Apr 2010 20:23:31 +0200 Subject: [PATCH 0440/3753] Fixed #3403 - Added fact to query vlans; added spec test --- lib/facter/util/vlans.rb | 24 ++++++++++++++++++++++++ lib/facter/vlans.rb | 8 ++++++++ spec/unit/data/linux_vlan_config | 6 ++++++ spec/unit/util/vlans.rb | 14 ++++++++++++++ 4 files changed, 52 insertions(+) create mode 100644 lib/facter/util/vlans.rb create mode 100644 lib/facter/vlans.rb create mode 100644 spec/unit/data/linux_vlan_config create mode 100644 spec/unit/util/vlans.rb diff --git a/lib/facter/util/vlans.rb b/lib/facter/util/vlans.rb new file mode 100644 index 0000000000..6d226ff053 --- /dev/null +++ b/lib/facter/util/vlans.rb @@ -0,0 +1,24 @@ +# A module to gather vlan facts +# +module Facter::Util::Vlans + def self.get_vlan_config + output = "" + if File.exists?('/proc/net/vlan/config') and File.readable?('/proc/net/vlan/config') + output = File.open('/proc/net/vlan/config').read + end + output + end + + def self.get_vlans + vlans = Array.new + if self.get_vlan_config + self.get_vlan_config.each do |line| + if line =~ /^([0-9A-Za-z]+)\.([0-9]+) / + vlans.insert(-1, $~[2]) if $~[2] + end + end + end + + vlans.join(',') + end +end diff --git a/lib/facter/vlans.rb b/lib/facter/vlans.rb new file mode 100644 index 0000000000..d65bdd8741 --- /dev/null +++ b/lib/facter/vlans.rb @@ -0,0 +1,8 @@ +require 'facter/util/vlans' + + Facter.add("vlans") do + confine :kernel => :linux + setcode do + Facter::Util::Vlans.get_vlans + end + end diff --git a/spec/unit/data/linux_vlan_config b/spec/unit/data/linux_vlan_config new file mode 100644 index 0000000000..3aa7dbd03e --- /dev/null +++ b/spec/unit/data/linux_vlan_config @@ -0,0 +1,6 @@ +VLAN Dev name | VLAN ID +Name-Type: VLAN_NAME_TYPE_RAW_PLUS_VID_NO_PAD +eth0.400 | 400 | eth0 +eth0.300 | 300 | eth0 +eth0.200 | 200 | eth0 +eth0.100 | 100 | eth0 diff --git a/spec/unit/util/vlans.rb b/spec/unit/util/vlans.rb new file mode 100644 index 0000000000..e06a2afd6c --- /dev/null +++ b/spec/unit/util/vlans.rb @@ -0,0 +1,14 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'facter/util/vlans' + +describe Facter::Util::Vlans do + it "should return a list of vlans on Linux" do + sample_output_file = File.dirname(__FILE__) + '/../data/linux_vlan_config' + linux_vlanconfig = File.new(sample_output_file).read(); + Facter::Util::Vlans.stubs(:get_vlan_config).returns(linux_vlanconfig) + Facter::Util::Vlans.get_vlans().should == %{400,300,200,100} + end +end From 83b3ea6abbd1d382a6738fa731f9f7409867e135 Mon Sep 17 00:00:00 2001 From: David Schmitt Date: Mon, 14 Jun 2010 17:05:20 +0200 Subject: [PATCH 0441/3753] Fixed #3393 - Updates to Facter for MS Windows This patch is originally by Daniel Berger , I changed using Facter.value instead of repeatedly testing Config['host_os'], removed Resolution::which, and fixed the specs. Thanks to Paul Nasrat for helping with cross-platform debugging. Signed-off-by: David Schmitt --- lib/facter/ipaddress.rb | 31 +++++++++----------- lib/facter/kernel.rb | 6 ++-- lib/facter/macaddress.rb | 29 +++++++++++++------ lib/facter/util/resolution.rb | 45 ++++++++++++++++++++++------- spec/unit/util/resolution.rb | 54 ++++++++++++++++++++++++++++------- 5 files changed, 115 insertions(+), 50 deletions(-) diff --git a/lib/facter/ipaddress.rb b/lib/facter/ipaddress.rb index dd0d4183eb..7c62aa19aa 100644 --- a/lib/facter/ipaddress.rb +++ b/lib/facter/ipaddress.rb @@ -111,30 +111,25 @@ Facter.add(:ipaddress) do confine :kernel => %w{windows} - setcode do - ip = nil - output = %x{ipconfig} - - output.split(/^\S/).each { |str| - if str =~ /IP Address.*: ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ - tmp = $1 - unless tmp =~ /^127\./ - ip = tmp - break - end - end - } - ip - end + require 'socket' + IPSocket.getaddress(Socket.gethostname) end Facter.add(:ipaddress, :ldapname => "iphostnumber", :timeout => 2) do setcode do - require 'resolv' - + if Facter.value(:kernel) == 'windows' + require 'win32/resolv' + else + require 'resolv' + end + begin if hostname = Facter.value(:hostname) - ip = Resolv.getaddress(hostname) + if Facter.value(:kernel) == 'windows' + ip = Win32::Resolv.get_resolv_info.last[0] + else + ip = Resolv.getaddress(hostname) + end unless ip == "127.0.0.1" ip end diff --git a/lib/facter/kernel.rb b/lib/facter/kernel.rb index d68aa3f837..66f21cea2e 100644 --- a/lib/facter/kernel.rb +++ b/lib/facter/kernel.rb @@ -2,8 +2,10 @@ setcode do require 'rbconfig' case Config::CONFIG['host_os'] - when /mswin/i; 'windows' - else Facter::Util::Resolution.exec("uname -s") + when /mswin|win32|dos|cygwin|mingw/i + 'windows' + else + Facter::Util::Resolution.exec("uname -s") end end end diff --git a/lib/facter/macaddress.rb b/lib/facter/macaddress.rb index e8f40dc34b..889feeafe0 100644 --- a/lib/facter/macaddress.rb +++ b/lib/facter/macaddress.rb @@ -67,13 +67,26 @@ Facter.add(:macaddress) do confine :kernel => %w(windows) setcode do - ether = [] - output = %x{ipconfig /all} - output.split(/\r\n/).each do |str| - if str =~ /.*Physical Address.*: (\w{1,2}-\w{1,2}-\w{1,2}-\w{1,2}-\w{1,2}-\w{1,2})/ - ether.push($1.gsub(/-/, ":")) - end - end - ether[0] + require 'win32ole' + require 'socket' + + ether = nil + host = Socket.gethostname + connect_string = "winmgmts://#{host}/root/cimv2" + + wmi = WIN32OLE.connect(connect_string) + + query = %Q{ + select * + from Win32_NetworkAdapterConfiguration + where IPEnabled = True + } + + wmi.ExecQuery(query).each{ |nic| + ether = nic.MacAddress + break + } + + ether end end diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index b9e28e8d84..f837f64e94 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -11,9 +11,13 @@ class Facter::Util::Resolution attr_accessor :interpreter, :code, :name, :timeout + WINDOWS = Config::CONFIG['host_os'] =~ /mswin|win32|dos|mingw|cygwin/i + + INTERPRETER = WINDOWS ? 'cmd.exe' : '/bin/sh' + def self.have_which if ! defined?(@have_which) or @have_which.nil? - if Config::CONFIG['host_os'] =~ /mswin/ + if Facter.value(:kernel) == 'windows' @have_which = false else %x{which which >/dev/null 2>&1} @@ -23,31 +27,50 @@ def self.have_which @have_which end - # Execute a chunk of code. - def self.exec(code, interpreter = "/bin/sh") - raise ArgumentError, "non-sh interpreters are not currently supported" unless interpreter == "/bin/sh" - binary = code.split(/\s+/).shift - - if have_which + # Execute a program and return the output of that program. + # + # Returns nil if the program can't be found, or if there is a problem + # executing the code. + # + def self.exec(code, interpreter = INTERPRETER) + raise ArgumentError, "invalid interpreter" unless interpreter == INTERPRETER + + # Try to guess whether the specified code can be executed by looking at the + # first word. If it cannot be found on the PATH defer on resolving the fact + # by returning nil. + # This only fails on shell built-ins, most of which are masked by stuff in + # /bin or of dubious value anyways. In the worst case, "sh -c 'builtin'" can + # be used to work around this limitation + # + # Windows' %x{} throws Errno::ENOENT when the command is not found, so we + # can skip the check there. This is good, since builtins cannot be found + # elsewhere. + if have_which and !WINDOWS path = nil - if binary !~ /^\// + binary = code.split.first + if code =~ /^\// + path = binary + else path = %x{which #{binary} 2>/dev/null}.chomp # we don't have the binary necessary return nil if path == "" or path.match(/Command not found\./) - else - path = binary end return nil unless FileTest.exists?(path) end out = nil + begin out = %x{#{code}}.chomp + rescue Errno::ENOENT => detail + # command not found on Windows + return nil rescue => detail $stderr.puts detail return nil end + if out == "" return nil else @@ -86,7 +109,7 @@ def limit def setcode(string = nil, interp = nil, &block) if string @code = string - @interpreter = interp || "/bin/sh" + @interpreter = interp || INTERPRETER else unless block_given? raise ArgumentError, "You must pass either code or a block" diff --git a/spec/unit/util/resolution.rb b/spec/unit/util/resolution.rb index 7cbfb31948..396f800f9c 100755 --- a/spec/unit/util/resolution.rb +++ b/spec/unit/util/resolution.rb @@ -44,9 +44,10 @@ @resolve = Facter::Util::Resolution.new("yay") end - it "should default to /bin/sh as the interpreter if a string is provided" do + it "should default to the detected interpreter if a string is provided" do + Facter::Util::Resolution::INTERPRETER = "/bin/bar" @resolve.setcode "foo" - @resolve.interpreter.should == "/bin/sh" + @resolve.interpreter.should == "/bin/bar" end it "should set the code to any provided string" do @@ -87,17 +88,44 @@ end describe "and the code is a string" do - it "should return the result of executing the code with the interpreter" do - @resolve.setcode "/bin/foo" - Facter::Util::Resolution.expects(:exec).with("/bin/foo", "/bin/sh").returns "yup" - - @resolve.value.should == "yup" + describe "on windows" do + before do + Facter::Util::Resolution::WINDOWS = true + Facter::Util::Resolution::INTERPRETER = "cmd.exe" + end + + it "should return the result of executing the code with the interpreter" do + @resolve.setcode "/bin/foo" + Facter::Util::Resolution.expects(:exec).once.with("/bin/foo", "cmd.exe").returns "yup" + + @resolve.value.should == "yup" + end + + it "should return nil if the value is an empty string" do + @resolve.setcode "/bin/foo" + Facter::Util::Resolution.expects(:exec).once.returns "" + @resolve.value.should be_nil + end end - it "should return nil if the value is an empty string" do - @resolve.setcode "/bin/foo" - Facter::Util::Resolution.stubs(:exec).returns "" - @resolve.value.should be_nil + describe "on non-windows systems" do + before do + Facter::Util::Resolution::WINDOWS = false + Facter::Util::Resolution::INTERPRETER = "/bin/sh" + end + + it "should return the result of executing the code with the interpreter" do + @resolve.setcode "/bin/foo" + Facter::Util::Resolution.expects(:exec).once.with("/bin/foo", "/bin/sh").returns "yup" + + @resolve.value.should == "yup" + end + + it "should return nil if the value is an empty string" do + @resolve.setcode "/bin/foo" + Facter::Util::Resolution.expects(:exec).once.returns "" + @resolve.value.should be_nil + end end end @@ -233,5 +261,9 @@ it "should fail if any interpreter other than /bin/sh is requested" do lambda { Facter::Util::Resolution.exec("/something", "/bin/perl") }.should raise_error(ArgumentError) end + + it "should execute the binary" do + Facter::Util::Resolution.exec("echo foo").should == "foo" + end end end From 8106bc3f000d0e18969c4ec142ef84fbc780b30a Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Fri, 11 Jun 2010 09:24:24 +1000 Subject: [PATCH 0442/3753] Adding HP-UX support to Facter's IP facts Includes Rspec tests --- lib/facter/util/ip.rb | 18 +++++- spec/unit/data/hpux_ifconfig_single_interface | 3 + spec/unit/data/hpux_netstat_all_interfaces | 6 ++ spec/unit/util/ip.rb | 61 ++++++++++++++++++- 4 files changed, 83 insertions(+), 5 deletions(-) create mode 100644 spec/unit/data/hpux_ifconfig_single_interface create mode 100644 spec/unit/data/hpux_netstat_all_interfaces mode change 100644 => 100755 spec/unit/util/ip.rb diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index 25acf3abe1..366303c80e 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -16,9 +16,14 @@ module Facter::Util::IP :netmask => /netmask\s+0x(\w{8})/ }, :sunos => { - :ipaddress => /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, + :ipaddress => /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, :macaddress => /(?:ether|lladdr)\s+(\w?\w:\w?\w:\w?\w:\w?\w:\w?\w:\w?\w)/, :netmask => /netmask\s+(\w{8})/ + }, + :"hp-ux" => { + :ipaddress => /\s+inet (\S+)\s.*/, + :macaddress => /(\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/, + :netmask => /.*\s+netmask (\S+)\s.*/ } } @@ -28,7 +33,7 @@ def self.alphafy(interface) end def self.convert_from_hex?(kernel) - kernels_to_convert = [:sunos, :openbsd, :netbsd, :freebsd, :darwin] + kernels_to_convert = [:sunos, :openbsd, :netbsd, :freebsd, :darwin, :"hp-ux"] kernels_to_convert.include?(kernel) end @@ -60,6 +65,8 @@ def self.get_all_interface_output output = %x{/sbin/ifconfig -a} when 'SunOS' output = %x{/usr/sbin/ifconfig -a} + when 'HP-UX' + output = %x{/bin/netstat -i} end output end @@ -71,6 +78,12 @@ def self.get_single_interface_output(interface) output = %x{/sbin/ifconfig #{interface}} when 'SunOS' output = %x{/usr/sbin/ifconfig #{interface}} + when 'HP-UX' + mac = "" + ifc = %x{/usr/sbin/ifconfig #{interface}} + %x{/usr/sbin/lanscan}.scan(/(\dx\S+).*UP\s+(\w+\d+)/).each {|i| mac = i[0] if i.include?(interface) } + mac = mac.sub(/0x(\S+)/,'\1').scan(/../).join(":") + output = ifc + "\n" + mac end output end @@ -101,7 +114,6 @@ def self.get_bonding_master(interface) device end - def self.get_interface_value(interface, label) tmp1 = [] diff --git a/spec/unit/data/hpux_ifconfig_single_interface b/spec/unit/data/hpux_ifconfig_single_interface new file mode 100644 index 0000000000..ff54eb8c75 --- /dev/null +++ b/spec/unit/data/hpux_ifconfig_single_interface @@ -0,0 +1,3 @@ +lan0: flags=1843 + inet 168.24.80.71 netmask ffffff00 broadcast 168.24.80.255 +00:13:21:BD:9C:B7 diff --git a/spec/unit/data/hpux_netstat_all_interfaces b/spec/unit/data/hpux_netstat_all_interfaces new file mode 100644 index 0000000000..7745fa8701 --- /dev/null +++ b/spec/unit/data/hpux_netstat_all_interfaces @@ -0,0 +1,6 @@ +Name Mtu Network Address Ipkts Ierrs Opkts Oerrs Coll +lan1 1500 15.12.0.0 host1.default.com + 572527659 0 1129421249 0 0 +lan0 1500 172.54.85.0 host2.default.com + 519222647 0 329127145 0 0 +lo0 4136 loopback localhost 14281117 0 14281125 0 0 diff --git a/spec/unit/util/ip.rb b/spec/unit/util/ip.rb old mode 100644 new mode 100755 index 222ce9a008..a9aae76331 --- a/spec/unit/util/ip.rb +++ b/spec/unit/util/ip.rb @@ -5,7 +5,7 @@ require 'facter/util/ip' describe Facter::Util::IP do - [:freebsd, :linux, :netbsd, :openbsd, :sunos, :darwin].each do |platform| + [:freebsd, :linux, :netbsd, :openbsd, :sunos, :darwin, :"hp-ux"].each do |platform| it "should be supported on #{platform}" do Facter::Util::IP.supported_platforms.should be_include(platform) end @@ -41,6 +41,13 @@ Facter::Util::IP.get_interfaces().should == ["lo0", "e1000g0"] end + it "should return a list three interfaces on HP-UX with three interfaces multiply reporting" do + sample_output_file = File.dirname(__FILE__) + '/../data/hpux_netstat_all_interfaces' + hpux_netstat = File.new(sample_output_file).read() + Facter::Util::IP.stubs(:get_all_interface_output).returns(hpux_netstat) + Facter::Util::IP.get_interfaces().should == ["lan1", "lan0", "lo0"] + end + it "should return a value for a specific interface" do Facter::Util::IP.should respond_to(:get_interface_value) end @@ -80,6 +87,46 @@ Facter::Util::IP.get_network_value("e1000g0").should == "172.16.15.0" end + it "should return ipaddress information for HP-UX" do + sample_output_file = File.dirname(__FILE__) + "/../data/hpux_ifconfig_single_interface" + hpux_ifconfig_interface = File.new(sample_output_file).read() + + Facter::Util::IP.expects(:get_single_interface_output).with("lan0").returns(hpux_ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("HP-UX") + + Facter::Util::IP.get_interface_value("lan0", "ipaddress").should == "168.24.80.71" + end + + it "should return macaddress information for HP-UX" do + sample_output_file = File.dirname(__FILE__) + "/../data/hpux_ifconfig_single_interface" + hpux_ifconfig_interface = File.new(sample_output_file).read() + + Facter::Util::IP.expects(:get_single_interface_output).with("lan0").returns(hpux_ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("HP-UX") + + Facter::Util::IP.get_interface_value("lan0", "macaddress").should == "00:13:21:BD:9C:B7" + end + + it "should return netmask information for HP-UX" do + sample_output_file = File.dirname(__FILE__) + "/../data/hpux_ifconfig_single_interface" + hpux_ifconfig_interface = File.new(sample_output_file).read() + + Facter::Util::IP.expects(:get_single_interface_output).with("lan0").returns(hpux_ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("HP-UX") + + Facter::Util::IP.get_interface_value("lan0", "netmask").should == "255.255.255.0" + end + + it "should return calculated network information for HP-UX" do + sample_output_file = File.dirname(__FILE__) + "/../data/hpux_ifconfig_single_interface" + hpux_ifconfig_interface = File.new(sample_output_file).read() + + Facter::Util::IP.stubs(:get_single_interface_output).with("lan0").returns(hpux_ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("HP-UX") + + Facter::Util::IP.get_network_value("lan0").should == "168.24.80.0" + end + it "should return interface information for FreeBSD supported via an alias" do sample_output_file = File.dirname(__FILE__) + "/../data/6.0-STABLE_FreeBSD_ifconfig" ifconfig_interface = File.new(sample_output_file).read() @@ -120,6 +167,16 @@ Facter::Util::IP.get_interface_value("e1000g0", "netmask").should == "255.255.255.0" end + it "should return a human readable netmask on HP-UX" do + sample_output_file = File.dirname(__FILE__) + "/../data/hpux_ifconfig_single_interface" + hpux_ifconfig_interface = File.new(sample_output_file).read() + + Facter::Util::IP.expects(:get_single_interface_output).with("lan0").returns(hpux_ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("HP-UX") + + Facter::Util::IP.get_interface_value("lan0", "netmask").should == "255.255.255.0" + end + it "should return a human readable netmask on Darwin" do sample_output_file = File.dirname(__FILE__) + "/../data/darwin_ifconfig_single_interface" @@ -137,7 +194,7 @@ Facter::Util::IP.get_bonding_master("eth0:1").should be_nil end - [:freebsd, :netbsd, :openbsd, :sunos, :darwin].each do |platform| + [:freebsd, :netbsd, :openbsd, :sunos, :darwin, :"hp-ux"].each do |platform| it "should require conversion from hex on #{platform}" do Facter::Util::IP.convert_from_hex?(platform).should == true end From 1bd2ca29d8fd7d11e75096ceeeb704fe887cad31 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Thu, 10 Jun 2010 16:13:46 +1000 Subject: [PATCH 0443/3753] Fixed #3929 - Added user confine to AIX memory facts --- lib/facter/memory.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb index f0f0d45c7f..06640e637b 100644 --- a/lib/facter/memory.rb +++ b/lib/facter/memory.rb @@ -20,16 +20,16 @@ end end -if Facter.value(:kernel) == "AIX" +if Facter.value(:kernel) == "AIX" and Facter.value(:id) == "root" swap = Facter::Util::Resolution.exec('swap -l') swapfree, swaptotal = 0, 0 swap.each do |dev| - if dev =~ /^\/\S+\s.*\s+(\S+)MB\s+(\S+)MB/ - swaptotal += $1.to_i - swapfree += $2.to_i - end - end - + if dev =~ /^\/\S+\s.*\s+(\S+)MB\s+(\S+)MB/ + swaptotal += $1.to_i + swapfree += $2.to_i + end + end + Facter.add("SwapSize") do confine :kernel => :aix setcode do From b2c21145885c15abc43b3641fcf903e13a859565 Mon Sep 17 00:00:00 2001 From: Nick Lewis Date: Wed, 21 Jul 2010 18:33:50 -0700 Subject: [PATCH 0444/3753] Properly wrapped the windows ipaddress fact in a setcode block. --- lib/facter/ipaddress.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/facter/ipaddress.rb b/lib/facter/ipaddress.rb index 7c62aa19aa..a08f26b4aa 100644 --- a/lib/facter/ipaddress.rb +++ b/lib/facter/ipaddress.rb @@ -111,8 +111,10 @@ Facter.add(:ipaddress) do confine :kernel => %w{windows} - require 'socket' - IPSocket.getaddress(Socket.gethostname) + setcode do + require 'socket' + IPSocket.getaddress(Socket.gethostname) + end end Facter.add(:ipaddress, :ldapname => "iphostnumber", :timeout => 2) do From 82286e44cf91e2e8305ba03e7e183ff98593099f Mon Sep 17 00:00:00 2001 From: Jiri Kubicek Date: Sun, 25 Jul 2010 03:44:41 +0200 Subject: [PATCH 0445/3753] Fix #4352 - Support for detecting virtuals (jails) on FreeBSD There was no support for detecting FreeBSD jails as a virtual in facter. This patch detects jail by getting "security.jail.jailed" kernel state via sysctl. Signed-off-by: Jiri Kubicek --- lib/facter/util/virtual.rb | 3 +++ lib/facter/virtual.rb | 6 +++++- spec/unit/util/virtual.rb | 10 ++++++++++ spec/unit/virtual.rb | 6 ++++++ 4 files changed, 24 insertions(+), 1 deletion(-) diff --git a/lib/facter/util/virtual.rb b/lib/facter/util/virtual.rb index 80f4e2c7d7..900375f4cc 100644 --- a/lib/facter/util/virtual.rb +++ b/lib/facter/util/virtual.rb @@ -57,5 +57,8 @@ def self.kvm_type "kvm" end + def self.jail? + Facter::Util::Resolution.exec("/sbin/sysctl -n security.jail.jailed") == "1" + end end diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index c6d0f22f06..c14a715d62 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -38,6 +38,10 @@ result = Facter::Util::Virtual.kvm_type() end + if Facter.value(:kernel)=="FreeBSD" + result = "jail" if Facter::Util::Virtual.jail? + end + if result == "physical" output = Facter::Util::Resolution.exec('lspci') if not output.nil? @@ -76,7 +80,7 @@ setcode do case Facter.value(:virtual) - when "xenu", "openvzve", "vmware", "kvm", "vserver" + when "xenu", "openvzve", "vmware", "kvm", "vserver", "jail" true else false diff --git a/spec/unit/util/virtual.rb b/spec/unit/util/virtual.rb index de339b88b1..f1ccf1e458 100644 --- a/spec/unit/util/virtual.rb +++ b/spec/unit/util/virtual.rb @@ -100,4 +100,14 @@ Facter::Util::Virtual.should be_kvm end + it "should identify FreeBSD jail when in jail" do + Facter::Util::Resolution.stubs(:exec).with("/sbin/sysctl -n security.jail.jailed").returns("1") + Facter::Util::Virtual.should be_jail + end + + it "should not identify FreeBSD jail when not in jail" do + Facter::Util::Resolution.stubs(:exec).with("/sbin/sysctl -n security.jail.jailed").returns("0") + Facter::Util::Virtual.should_not be_jail + end + end diff --git a/spec/unit/virtual.rb b/spec/unit/virtual.rb index 7dbd1465fe..311001ddb3 100644 --- a/spec/unit/virtual.rb +++ b/spec/unit/virtual.rb @@ -55,4 +55,10 @@ Facter.fact(:is_virtual).value.should == true end + it "should be true when running in jail" do + Facter.fact(:kernel).stubs(:value).returns("FreeBSD") + Facter.fact(:virtual).stubs(:value).returns("jail") + Facter.fact(:is_virtual).value.should == true + end + end From faaa169d73e5132b7f2ea491461690324c1133ab Mon Sep 17 00:00:00 2001 From: Jiri Kubicek Date: Mon, 26 Jul 2010 00:08:16 +0200 Subject: [PATCH 0446/3753] Fix #4352 - Support for detecting KVM virtuals on FreeBSD There was no support for detecting FreeBSD running in KVM as a virtual in facter. This patch detects KVM by getting "hw.model" kernel state via sysctl. Jails running in KVM are also correctly detected as "jail" not "kvm". Signed-off-by: Jiri Kubicek --- lib/facter/util/virtual.rb | 9 +++++---- spec/unit/util/virtual.rb | 6 ++++++ spec/unit/virtual.rb | 7 +++++++ 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/lib/facter/util/virtual.rb b/lib/facter/util/virtual.rb index 900375f4cc..2d18c330a6 100644 --- a/lib/facter/util/virtual.rb +++ b/lib/facter/util/virtual.rb @@ -43,11 +43,12 @@ def self.xen? end def self.kvm? - if FileTest.exists?("/proc/cpuinfo") - txt = File.read("/proc/cpuinfo") - return true if txt =~ /QEMU Virtual CPU/ + txt = if FileTest.exists?("/proc/cpuinfo") + File.read("/proc/cpuinfo") + elsif Facter.value(:kernel)=="FreeBSD" + Facter::Util::Resolution.exec("/sbin/sysctl -n hw.model") end - return false + (txt =~ /QEMU Virtual CPU/) ? true : false end def self.kvm_type diff --git a/spec/unit/util/virtual.rb b/spec/unit/util/virtual.rb index f1ccf1e458..5b59cf94ff 100644 --- a/spec/unit/util/virtual.rb +++ b/spec/unit/util/virtual.rb @@ -100,6 +100,12 @@ Facter::Util::Virtual.should be_kvm end + it "should detect kvm on FreeBSD" do + Facter.fact(:kernel).stubs(:value).returns("FreeBSD") + Facter::Util::Resolution.stubs(:exec).with("/sbin/sysctl -n hw.model").returns("QEMU Virtual CPU version 0.12.4") + Facter::Util::Virtual.should be_kvm + end + it "should identify FreeBSD jail when in jail" do Facter::Util::Resolution.stubs(:exec).with("/sbin/sysctl -n security.jail.jailed").returns("1") Facter::Util::Virtual.should be_jail diff --git a/spec/unit/virtual.rb b/spec/unit/virtual.rb index 311001ddb3..8ee843bb7b 100644 --- a/spec/unit/virtual.rb +++ b/spec/unit/virtual.rb @@ -17,6 +17,13 @@ Facter.fact(:virtual).value.should == "zone" end + it "should be jail on FreeBSD when a jail in kvm" do + Facter.fact(:kernel).stubs(:value).returns("FreeBSD") + Facter::Util::Virtual.stubs(:jail?).returns(true) + Facter::Util::Virtual.stubs(:kvm?).returns(true) + Facter.fact(:virtual).value.should == "jail" + end + end describe "is_virtual fact" do From ce7bd9fb322d89b73aca3b8b652764c5ed3188c3 Mon Sep 17 00:00:00 2001 From: Rein Henrichs Date: Tue, 3 Aug 2010 10:42:49 -0700 Subject: [PATCH 0447/3753] Refactor rakefile to use spec.ops, separate rcov task --- Rakefile | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Rakefile b/Rakefile index 67d4c90f39..51b4d13a23 100644 --- a/Rakefile +++ b/Rakefile @@ -3,6 +3,13 @@ $: << File.expand_path('lib') $LOAD_PATH << File.join(File.dirname(__FILE__), 'tasks') +require 'spec' +require 'spec/rake/spectask' +begin + require 'rcov' +rescue LoadError +end + Dir['tasks/**/*.rake'].each { |t| load t } require 'facter.rb' @@ -51,21 +58,14 @@ task :default do sh %{rake -T} end -desc "Run the specs under spec/" -task :spec do - require 'spec' - require 'spec/rake/spectask' - begin - require 'rcov' - rescue LoadError - end +Spec::Rake::SpecTask.new(:spec) do |t| + t.spec_files = FileList['spec/**/*.rb'] +end - Spec::Rake::SpecTask.new do |t| - t.spec_opts = ['--format','s', '--loadby','mtime'] - t.spec_files = FileList['spec/**/*.rb'] - if defined?(Rcov) - t.rcov = true - t.rcov_opts = ['--exclude', 'spec/*,test/*,results/*,/usr/lib/*,/usr/local/lib/*'] - end - end +Spec::Rake::SpecTask.new('spec:rcov') do |t| + t.spec_files = FileList['spec/**/*.rb'] + if defined?(Rcov) + t.rcov = true + t.rcov_opts = ['--exclude', 'spec/*,test/*,results/*,/usr/lib/*,/usr/local/lib/*,gems/*'] + end end From a2bcacdc54fc9e9446bd5b084e70d60aaaeeebd2 Mon Sep 17 00:00:00 2001 From: Rein Henrichs Date: Mon, 2 Aug 2010 17:28:45 -0700 Subject: [PATCH 0448/3753] [#2330] Uptime should not make redundant system calls Rewrite of uptime facts and supporting utility methods. Works on unix, BSD, windows. No longer makes redundant system calls. Uses Facter::Util::Uptime utility methods: * Implemented uptime_seconds_unix using /proc/uptime or who -b on unix, sysctl on BSD. Added unit tests for the behaviors of get_uptime_seconds_unix: read from proc/uptime, read uptime from "sysctl -b kern.boottime", read uptime from "who -b", and return nil if nothing else works. * Implemented uptime_seconds_win using the Win32 API. Facts implemented: * uptime_{seconds,hours,days} Returns the respective integer value. * uptime Returns human readable uptime statistic that preserves original behavior. Examples: 3 days 1 day 5:08 hours 0:35 hours --- lib/facter/uptime.rb | 31 +++--- lib/facter/uptime_days.rb | 7 ++ lib/facter/uptime_hours.rb | 7 ++ lib/facter/uptime_seconds.rb | 10 ++ lib/facter/util/uptime.rb | 66 ++++++++----- spec/fixtures/uptime/sysctl_kern_boottime | Bin 0 -> 16 bytes spec/fixtures/uptime/ubuntu_proc_uptime | 1 + spec/fixtures/uptime/who_b_boottime | 1 + spec/spec_helper.rb | 2 + spec/unit/uptime.rb | 112 ++++++++++++++++++++++ spec/unit/util/uptime.rb | 53 ++++++++++ 11 files changed, 253 insertions(+), 37 deletions(-) create mode 100644 lib/facter/uptime_days.rb create mode 100644 lib/facter/uptime_hours.rb create mode 100644 lib/facter/uptime_seconds.rb create mode 100644 spec/fixtures/uptime/sysctl_kern_boottime create mode 100644 spec/fixtures/uptime/ubuntu_proc_uptime create mode 100644 spec/fixtures/uptime/who_b_boottime create mode 100644 spec/unit/uptime.rb create mode 100644 spec/unit/util/uptime.rb diff --git a/lib/facter/uptime.rb b/lib/facter/uptime.rb index 3a3bc862c6..56a959ba25 100644 --- a/lib/facter/uptime.rb +++ b/lib/facter/uptime.rb @@ -1,20 +1,23 @@ require 'facter/util/uptime' Facter.add(:uptime) do - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE SLES Debian Ubuntu Gentoo AIX} - setcode do - Facter::Util::Uptime.get_uptime_simple - end -end + setcode do + seconds = Facter.fact(:uptime_seconds).value -if FileTest.exists?("/proc/uptime") - uptime = Facter::Util::Uptime.get_uptime + unless seconds + "unknown" + else + days = seconds / (60 * 60 * 24) + hours = seconds / (60 * 60) % 24 + minutes = seconds / 60 % 60 - %w{days hours seconds}.each do |label| - Facter.add("uptime_" + label) do - setcode do - Facter::Util::Uptime.get_uptime_period(uptime, label) - end - end - end + case days + when 0 then "#{hours}:#{"%02d" % minutes} hours" + when 1 then '1 day' + else "#{days} days" + end + end + + end end + diff --git a/lib/facter/uptime_days.rb b/lib/facter/uptime_days.rb new file mode 100644 index 0000000000..add305c59f --- /dev/null +++ b/lib/facter/uptime_days.rb @@ -0,0 +1,7 @@ +Facter.add(:uptime_days) do + setcode do + hours = Facter.value(:uptime_hours) + hours && hours / 24 # hours in day + end +end + diff --git a/lib/facter/uptime_hours.rb b/lib/facter/uptime_hours.rb new file mode 100644 index 0000000000..ce691d25fa --- /dev/null +++ b/lib/facter/uptime_hours.rb @@ -0,0 +1,7 @@ +Facter.add(:uptime_hours) do + setcode do + seconds = Facter.value(:uptime_seconds) + seconds && seconds / (60 * 60) # seconds in hour + end +end + diff --git a/lib/facter/uptime_seconds.rb b/lib/facter/uptime_seconds.rb new file mode 100644 index 0000000000..14bb573a4c --- /dev/null +++ b/lib/facter/uptime_seconds.rb @@ -0,0 +1,10 @@ +require 'facter/util/uptime' + +Facter.add(:uptime_seconds) do + setcode { Facter::Util::Uptime.get_uptime_seconds_unix } +end + +Facter.add(:uptime_seconds) do + confine :kernel => :windows + setcode { Facter::Util::Uptime.get_uptime_seconds_win } +end diff --git a/lib/facter/util/uptime.rb b/lib/facter/util/uptime.rb index c1e339b942..f8a17db29a 100644 --- a/lib/facter/util/uptime.rb +++ b/lib/facter/util/uptime.rb @@ -1,32 +1,52 @@ +require 'time' + # A module to gather uptime facts # module Facter::Util::Uptime - def self.get_uptime_simple - time = Facter::Util::Resolution.exec('uptime') - if time =~ /up\s*(\d+\s\w+)/ - $1 - elsif time =~ /up\s*(\d+:\d+)/ - $1 + " hours" - else - "unknown" + def self.get_uptime_seconds_unix + uptime_proc_uptime or uptime_sysctl or uptime_who_dash_b + end + + def self.get_uptime_seconds_win + require 'Win32API' + getTickCount = Win32API.new("kernel32", "GetTickCount", nil, 'L') + compute_uptime(Time.at(getTickCount.call() / 1000.0)) + end + + private + + def self.uptime_proc_uptime + if File.exists? uptime_file + r = File.read uptime_file + r.split(" ").first.to_i + end + end + + def self.uptime_sysctl + if (output = `#{uptime_sysctl_cmd}`) and $?.success? + compute_uptime(Time.at(output.unpack('L').first)) + end + end + + def self.uptime_who_dash_b + if (output = `#{uptime_who_cmd}`) and $?.success? + compute_uptime(Time.parse(output)) end end - def self.get_uptime - r = IO.popen("/bin/cat /proc/uptime") - uptime, idletime = r.readline.split(" ") - r.close - uptime_seconds = uptime.to_i + def self.compute_uptime(time) + (Time.now - time).to_i + end + + def self.uptime_file + "/proc/uptime" + end + + def self.uptime_sysctl_cmd + 'sysctl -b kern.boottime 2>/dev/null' end - def self.get_uptime_period(seconds, label) - case label - when 'days' - value = seconds / 86400 - when 'hours' - value = seconds / 3600 - when 'seconds' - seconds - end + def self.uptime_who_cmd + 'who -b 2>/dev/null' end -end +end diff --git a/spec/fixtures/uptime/sysctl_kern_boottime b/spec/fixtures/uptime/sysctl_kern_boottime new file mode 100644 index 0000000000000000000000000000000000000000..0c54fe44aab83f38cd6526a020a16d9d924e49a5 GIT binary patch literal 16 RcmeBv9O}aW1sZm9LIELm17`pL literal 0 HcmV?d00001 diff --git a/spec/fixtures/uptime/ubuntu_proc_uptime b/spec/fixtures/uptime/ubuntu_proc_uptime new file mode 100644 index 0000000000..7e8ed74752 --- /dev/null +++ b/spec/fixtures/uptime/ubuntu_proc_uptime @@ -0,0 +1 @@ +5097686.63 40756306.43 diff --git a/spec/fixtures/uptime/who_b_boottime b/spec/fixtures/uptime/who_b_boottime new file mode 100644 index 0000000000..9b29dcd830 --- /dev/null +++ b/spec/fixtures/uptime/who_b_boottime @@ -0,0 +1 @@ +reboot ~ Aug 1 14:13 diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 34992d75ef..c8bd547b50 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,5 +1,7 @@ dir = File.expand_path(File.dirname(__FILE__)) +SPECDIR = dir + $LOAD_PATH.unshift("#{dir}/") $LOAD_PATH.unshift("#{dir}/../lib") diff --git a/spec/unit/uptime.rb b/spec/unit/uptime.rb new file mode 100644 index 0000000000..19a55fe085 --- /dev/null +++ b/spec/unit/uptime.rb @@ -0,0 +1,112 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../spec_helper' + +require 'facter' +require 'facter/util/uptime' + +describe "uptime facts:" do + before { Facter.clear } + after { Facter.clear } + + context "when uptime information is available" do + describe "uptime" do + test_cases = [ + [60 * 60 * 24 * 3, '3 days'], + [60 * 60 * 24 * 3 + 25, '3 days'], + [60 * 60 * 24 * 1, '1 day'], + [60 * 60 * 24 * 1 + 25, '1 day'], + [60 * (60 * 3 + 45), '3:45 hours'], + [60 * (60 * 3 + 4), '3:04 hours'], + [60 * 60, '1:00 hours'], + [60 * 35, '0:35 hours'] + ] + + test_cases.each do |seconds, expected| + it "should return #{expected.inspect} for #{seconds} seconds" do + Facter::Util::Uptime.stubs(:get_uptime_seconds_unix).returns(seconds) + Facter::Util::Uptime.stubs(:get_uptime_seconds_win).returns(seconds) + + Facter.fact(:uptime).value.should == expected + end + end + end + + end + + context "when uptime information is available" do + before do + Facter::Util::Uptime.stubs(:get_uptime_seconds_unix).returns(60 * 60 * 24 + 23) + Facter::Util::Uptime.stubs(:get_uptime_seconds_win).returns(60 * 60 * 24 + 23) + end + + describe "uptime_seconds" do + it "should return the uptime in seconds" do + Facter.fact(:uptime_seconds).value.should == 60 * 60 * 24 + 23 + end + end + + describe "uptime_hours" do + it "should return the uptime in hours" do + Facter.fact(:uptime_hours).value.should == 24 + end + end + + describe "uptime_days" do + it "should return the uptime in days" do + Facter.fact(:uptime_days).value.should == 1 + end + end + end + + context "when uptime information is not available" do + before do + Facter::Util::Uptime.stubs(:get_uptime_seconds_unix).returns(nil) + Facter::Util::Uptime.stubs(:get_uptime_seconds_win).returns(nil) + $stderr, @old = StringIO.new, $stderr + end + + after do + $stderr = @old + end + + describe "uptime" do + it "should return 'unknown'" do + Facter.fact(:uptime).value.should == "unknown" + end + end + + describe "uptime_seconds" do + it "should return nil" do + Facter.fact(:uptime_seconds).value.should == nil + end + + it "should not print a warn message to stderr" do + Facter.fact(:uptime_seconds).value + $stderr.string.should == "" + end + end + + describe "uptime_hours" do + it "should return nil" do + Facter.fact(:uptime_hours).value.should == nil + end + + it "should not print a warn message to stderr" do + Facter.fact(:uptime_hours).value + $stderr.string.should == "" + end + end + + describe "uptime_days" do + it "should return nil" do + Facter.fact(:uptime_days).value.should == nil + end + + it "should not print a warn message to stderr" do + Facter.fact(:uptime_days).value + $stderr.string.should == "" + end + end + end +end diff --git a/spec/unit/util/uptime.rb b/spec/unit/util/uptime.rb new file mode 100644 index 0000000000..9ba6665170 --- /dev/null +++ b/spec/unit/util/uptime.rb @@ -0,0 +1,53 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'facter/util/uptime' + +describe Facter::Util::Uptime do + + describe ".get_uptime_seconds_unix" do + context "when /proc/uptime is available" do + before do + uptime_file = File.join(SPECDIR, "fixtures", "uptime", "ubuntu_proc_uptime") + Facter::Util::Uptime.stubs(:uptime_file).returns(uptime_file) + end + + it "should return the uptime in seconds as an integer" do + Facter::Util::Uptime.get_uptime_seconds_unix.should == 5097686 + end + + end + + it "should use sysctl kern.boottime when /proc/uptime not available" do + nonexistent_file = '/non/existent/file' + File.exists?(nonexistent_file).should == false + Facter::Util::Uptime.stubs(:uptime_file).returns(nonexistent_file) + sysctl_output_file = File.join(SPECDIR, 'fixtures', 'uptime', 'sysctl_kern_boottime') # Aug 01 14:13:47 -0700 2010 + Facter::Util::Uptime.stubs(:uptime_sysctl_cmd).returns("cat #{sysctl_output_file}") + Time.stubs(:now).returns Time.parse("Aug 01 15:13:47 -0700 2010") # one hour later + Facter::Util::Uptime.get_uptime_seconds_unix.should == 60 * 60 + end + + it "should use who -b when neither /proc/uptime nor sysctl kern.boottime is available" do + nonexistent_file = '/non/existent/file' + File.exists?(nonexistent_file).should == false + Facter::Util::Uptime.stubs(:uptime_file).returns(nonexistent_file) + Facter::Util::Uptime.stubs(:uptime_sysctl_cmd).returns("cat #{nonexistent_file}") + who_b_output_file = File.join(SPECDIR, 'fixtures', 'uptime', 'who_b_boottime') # Aug 1 14:13 + Facter::Util::Uptime.stubs(:uptime_who_cmd).returns("cat #{who_b_output_file}") + Time.stubs(:now).returns Time.parse("Aug 01 15:13") # one hour later + Facter::Util::Uptime.get_uptime_seconds_unix.should == 60 * 60 + end + + it "should return nil when none of /proc/uptime, sysctl kern.boottime, or who -b is available" do + nonexistent_file = '/non/existent/file' + File.exists?(nonexistent_file).should == false + Facter::Util::Uptime.stubs(:uptime_file).returns(nonexistent_file) + Facter::Util::Uptime.stubs(:uptime_sysctl_cmd).returns("cat #{nonexistent_file}") + Facter::Util::Uptime.stubs(:uptime_who_cmd).returns("cat #{nonexistent_file}") + Facter::Util::Uptime.get_uptime_seconds_unix.should == nil + end + end + +end From 67f6604e96f04f07fea1a452ae6a19f1d158405d Mon Sep 17 00:00:00 2001 From: Rein Henrichs Date: Tue, 3 Aug 2010 17:05:04 -0700 Subject: [PATCH 0449/3753] [#4062] Implement operating system facts for MeeGo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements operatingsystem and operatingsystemrelease for MeeGo From Rohan McGovern's patch: MeeGo ( http://meego.com/ ) uses an /etc/meego-release file similar to the files found on many Linux distros. Currently, MeeGo falls back on “Linux” as the operatingsystem fact. It’d be useful to have a proper value. Attached patch implements it. Signed-off-by: Rein Henrichs --- lib/facter/operatingsystem.rb | 2 ++ lib/facter/operatingsystemrelease.rb | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index d909fba214..c5a3dc1a65 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -20,6 +20,8 @@ "Mandriva" elsif FileTest.exists?("/etc/mandrake-release") "Mandrake" + elsif FileTest.exists?("/etc/meego-release") + "MeeGo" elsif FileTest.exists?("/etc/arch-release") "Archlinux" elsif FileTest.exists?("/etc/enterprise-release") diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index 91f40a7c42..30f2989ace 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -1,11 +1,13 @@ Facter.add(:operatingsystemrelease) do - confine :operatingsystem => %w{CentOS Fedora oel ovs RedHat} + confine :operatingsystem => %w{CentOS Fedora oel ovs RedHat MeeGo} setcode do case Facter.value(:operatingsystem) when "CentOS", "RedHat" releasefile = "/etc/redhat-release" when "Fedora" releasefile = "/etc/fedora-release" + when "MeeGo" + releasefile = "/etc/meego-release" when "OEL" releasefile = "/etc/enterprise-release" when "OVS" From be411c00ee47e62bf0408dbb7c93c8dc6a9d9381 Mon Sep 17 00:00:00 2001 From: Marc Fournier Date: Tue, 11 May 2010 09:12:08 +0200 Subject: [PATCH 0450/3753] Facter::Manufacturer - test for SunOS and FreeBSD This test compares a fact made with dmidecode (linux and bsd) with one using smbios (solaris). --- spec/unit/data/freebsd_dmidecode | 42 +++++++++++++++++++++++++++++++ spec/unit/data/opensolaris_smbios | 33 ++++++++++++++++++++++++ spec/unit/util/manufacturer.rb | 23 +++++++++++++++++ 3 files changed, 98 insertions(+) create mode 100644 spec/unit/data/freebsd_dmidecode create mode 100644 spec/unit/data/opensolaris_smbios diff --git a/spec/unit/data/freebsd_dmidecode b/spec/unit/data/freebsd_dmidecode new file mode 100644 index 0000000000..d7659421d2 --- /dev/null +++ b/spec/unit/data/freebsd_dmidecode @@ -0,0 +1,42 @@ +# dmidecode 2.10 +SMBIOS 2.5 present. +5 structures occupying 352 bytes. +Table at 0x000E1000. + +Handle 0x0000, DMI type 0, 20 bytes +BIOS Information + Vendor: innotek GmbH + Version: VirtualBox + Release Date: 12/01/2006 + Address: 0xE0000 + Runtime Size: 128 kB + ROM Size: 128 kB + Characteristics: + ISA is supported + PCI is supported + Boot from CD is supported + Selectable boot is supported + 8042 keyboard services are supported (int 9h) + CGA/mono video services are supported (int 10h) + ACPI is supported + +Handle 0x0001, DMI type 1, 27 bytes +System Information + Manufacturer: innotek GmbH + Product Name: VirtualBox + Version: 1.2 + Serial Number: 0 + UUID: 3BD58031-AE9E-4F06-8A57-941942861939 + Wake-up Type: Power Switch + SKU Number: Not Specified + Family: Virtual Machine + +Handle 0x0003, DMI type 126, 13 bytes +Inactive + +Handle 0x0002, DMI type 126, 7 bytes +Inactive + +Handle 0xFEFF, DMI type 127, 147 bytes +End Of Table + diff --git a/spec/unit/data/opensolaris_smbios b/spec/unit/data/opensolaris_smbios new file mode 100644 index 0000000000..68f1004970 --- /dev/null +++ b/spec/unit/data/opensolaris_smbios @@ -0,0 +1,33 @@ +ID SIZE TYPE +0 54 SMB_TYPE_BIOS (BIOS information) + + Vendor: innotek GmbH + Version String: VirtualBox + Release Date: 12/01/2006 + Address Segment: 0xe000 + ROM Size: 131072 bytes + Image Size: 131072 bytes + Characteristics: 0x48018090 + SMB_BIOSFL_ISA (ISA is supported) + SMB_BIOSFL_PCI (PCI is supported) + SMB_BIOSFL_CDBOOT (Boot from CD is supported) + SMB_BIOSFL_SELBOOT (Selectable Boot supported) + SMB_BIOSFL_I9_KBD (int 0x9 8042 keyboard svcs) + SMB_BIOSFL_I10_CGA (int 0x10 CGA svcs) + Characteristics Extension Byte 1: 0x1 + SMB_BIOSXB1_ACPI (ACPI is supported) + Characteristics Extension Byte 2: 0x0 + +ID SIZE TYPE +1 72 SMB_TYPE_SYSTEM (system information) + + Manufacturer: innotek GmbH + Product: VirtualBox + Version: 1.2 + Serial Number: 0 + + UUID: cf4bff06-0b33-4891-bda0-5ec17bea5511 + Wake-Up Event: 0x6 (power switch) + SKU Number: + Family: Virtual Machine + diff --git a/spec/unit/util/manufacturer.rb b/spec/unit/util/manufacturer.rb index 74660f7227..07e3ffb2b2 100644 --- a/spec/unit/util/manufacturer.rb +++ b/spec/unit/util/manufacturer.rb @@ -83,4 +83,27 @@ Facter::Manufacturer.dmi_find_system_info(query) Facter.value(:ramlocation).should == "System Board Or Motherboard" end + + def find_product_name(os) + output_file = case os + when "FreeBSD": File.dirname(__FILE__) + "/../data/freebsd_dmidecode" + when "SunOS" : File.dirname(__FILE__) + "/../data/opensolaris_smbios" + end + + output = File.new(output_file).read() + query = { '[Ss]ystem [Ii]nformation' => [ { 'Product(?: Name)?:' => "product_name_#{os}" } ] } + + Facter.fact(:kernel).stubs(:value).returns(os) + Facter::Manufacturer.expects(:get_dmi_table).returns(output) + + Facter::Manufacturer.dmi_find_system_info(query) + + return Facter.value("product_name_#{os}") + end + + it "should return the same result with smbios than with dmidecode" do + find_product_name("FreeBSD").should_not == nil + find_product_name("FreeBSD").should == find_product_name("SunOS") + end + end From e9a60bc1d403d280016402199a3d396078577d2c Mon Sep 17 00:00:00 2001 From: Marc Fournier Date: Wed, 12 May 2010 12:51:14 +0200 Subject: [PATCH 0451/3753] Facter::Manufacturer - sunos test + simplified regex The 2nd regex (with the 0x\d+) got removed because it appears to never be used, even on SunOS, for which it has been introduced (99833a1b / #1793). The 3dn regex is changed to clearly confine the value between mandatory newlines/whitespaces. --- lib/facter/util/manufacturer.rb | 2 +- spec/unit/util/manufacturer.rb | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/facter/util/manufacturer.rb b/lib/facter/util/manufacturer.rb index b781e12d09..112380b6f5 100644 --- a/lib/facter/util/manufacturer.rb +++ b/lib/facter/util/manufacturer.rb @@ -35,7 +35,7 @@ def self.dmi_find_system_info(name) v.each do |v2| v2.each_pair do |value,facterkey| output.split(splitstr).each do |line| - if line =~ /#{key}/ and ( line =~ /#{value} 0x\d+ \(([-\w].*)\)\n*/ or line =~ /#{value} ([-\w].*)\n*/ ) + if line =~ /#{key}/ and line =~ /\n\s+#{value} (.+)\n/ result = $1.strip Facter.add(facterkey) do confine :kernel => [ :linux, :freebsd, :netbsd, :sunos ] diff --git a/spec/unit/util/manufacturer.rb b/spec/unit/util/manufacturer.rb index 07e3ffb2b2..c0d4b1345b 100644 --- a/spec/unit/util/manufacturer.rb +++ b/spec/unit/util/manufacturer.rb @@ -24,6 +24,18 @@ Facter.value(:productname).should == "MS-6754" end + it "should handle output from smbios when run under sunos" do + sample_output_file = File.dirname(__FILE__) + "/../data/opensolaris_smbios" + smbios_output = File.new(sample_output_file).read() + Facter::Manufacturer.expects(:get_dmi_table).returns(smbios_output) + Facter.fact(:kernel).stubs(:value).returns("SunOS") + + query = { 'BIOS information' => [ { 'Release Date:' => 'reldate' } ] } + + Facter::Manufacturer.dmi_find_system_info(query) + Facter.value(:reldate).should == "12/01/2006" + end + it "should not split on dmi keys containing the string Handle" do dmidecode_output = <<-eos Handle 0x1000, DMI type 16, 15 bytes From b7fe98952af64c98bf7e82a1ea31f6223d78b6a6 Mon Sep 17 00:00:00 2001 From: Rein Henrichs Date: Thu, 5 Aug 2010 15:31:50 -0700 Subject: [PATCH 0452/3753] [#2330] Update uptime calculation to use /bin/cat * Per #4466, Ruby has trouble reading files in /proc [1]. The alternative is to use `bin/cat`. * Also refactored methods to explicitly redirect standard error to /dev/null for *nix and BSD system calls. [1] http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/155745 --- lib/facter/util/uptime.rb | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/facter/util/uptime.rb b/lib/facter/util/uptime.rb index f8a17db29a..353ebf5e8f 100644 --- a/lib/facter/util/uptime.rb +++ b/lib/facter/util/uptime.rb @@ -16,20 +16,19 @@ def self.get_uptime_seconds_win private def self.uptime_proc_uptime - if File.exists? uptime_file - r = File.read uptime_file - r.split(" ").first.to_i + if output = `/bin/cat #{uptime_file} 2>/dev/null` and $?.success? + output.chomp.split(" ").first.to_i end end def self.uptime_sysctl - if (output = `#{uptime_sysctl_cmd}`) and $?.success? + if output = `#{uptime_sysctl_cmd} 2>/dev/null` and $?.success? compute_uptime(Time.at(output.unpack('L').first)) end end def self.uptime_who_dash_b - if (output = `#{uptime_who_cmd}`) and $?.success? + if output = `#{uptime_who_cmd} 2>/dev/null` and $?.success? compute_uptime(Time.parse(output)) end end @@ -43,10 +42,10 @@ def self.uptime_file end def self.uptime_sysctl_cmd - 'sysctl -b kern.boottime 2>/dev/null' + 'sysctl -b kern.boottime' end def self.uptime_who_cmd - 'who -b 2>/dev/null' + 'who -b' end end From bff84c2ac73db3880f7004a0defc745fa0b1d37c Mon Sep 17 00:00:00 2001 From: Rein Henrichs Date: Fri, 6 Aug 2010 12:13:09 -0700 Subject: [PATCH 0453/3753] [#4156] Applying patch by Kai Patch from the ticket[1] checks for /proc/vz instead of /proc/vz/vzinfo. NOTE that this causes a spec failure since the spec was not changed to match. [1] http://projects.reductivelabs.com/issues/4156 Signed-off-by: Rein Henrichs --- lib/facter/util/virtual.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/util/virtual.rb b/lib/facter/util/virtual.rb index 2d18c330a6..5f70d4287b 100644 --- a/lib/facter/util/virtual.rb +++ b/lib/facter/util/virtual.rb @@ -1,6 +1,6 @@ module Facter::Util::Virtual def self.openvz? - FileTest.exists?("/proc/vz/veinfo") + FileTest.directory?("/proc/vz") end def self.openvz_type From e02be1d34fd374c1d345fb16da9e5d33fa2118fd Mon Sep 17 00:00:00 2001 From: Rein Henrichs Date: Fri, 6 Aug 2010 12:14:27 -0700 Subject: [PATCH 0454/3753] [#4156] Updating spec to match Kai's change Updating the spec to check for the existence of /proc/vz to match the change in the previously applied patch. Signed-off-by: Rein Henrichs --- spec/unit/util/virtual.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/unit/util/virtual.rb b/spec/unit/util/virtual.rb index 5b59cf94ff..1e31a2f748 100644 --- a/spec/unit/util/virtual.rb +++ b/spec/unit/util/virtual.rb @@ -8,7 +8,7 @@ Facter.clear end it "should detect openvz" do - FileTest.stubs(:exists?).with("/proc/vz/veinfo").returns(true) + FileTest.stubs(:directory?).with("/proc/vz").returns(true) Facter::Util::Virtual.should be_openvz end From 016cf037435154fc901ac93962f3ba464432c62c Mon Sep 17 00:00:00 2001 From: Rein Henrichs Date: Wed, 11 Aug 2010 11:13:59 -0700 Subject: [PATCH 0455/3753] [#3703] Fix macaddress fact for Darwin * With tests for 9.8.0, 10.3.0 and 10.6.4 --- lib/facter/macaddress.rb | 16 +---- lib/facter/util/macaddress.rb | 28 +++++++++ spec/fixtures/.DS_Store | Bin 0 -> 6148 bytes spec/fixtures/ifconfig/centos_5_5 | 17 +++++ spec/fixtures/ifconfig/centos_5_5_eth0 | 8 +++ spec/fixtures/ifconfig/darwin_10_3_0 | 26 ++++++++ spec/fixtures/ifconfig/darwin_10_3_0_en0 | 6 ++ spec/fixtures/ifconfig/darwin_10_6_4 | 28 +++++++++ spec/fixtures/ifconfig/darwin_10_6_4_en1 | 6 ++ spec/fixtures/ifconfig/darwin_9_8_0 | 26 ++++++++ spec/fixtures/ifconfig/darwin_9_8_0_en0 | 6 ++ spec/fixtures/ifconfig/fedora_10 | 36 +++++++++++ spec/fixtures/ifconfig/fedora_10_eth0 | 9 +++ spec/fixtures/ifconfig/fedora_13 | 18 ++++++ spec/fixtures/ifconfig/fedora_13_eth0 | 9 +++ spec/fixtures/ifconfig/fedora_8 | 38 ++++++++++++ spec/fixtures/ifconfig/fedora_8_eth0 | 9 +++ spec/fixtures/ifconfig/freebsd_6_0 | 12 ++++ spec/fixtures/ifconfig/open_solaris_10 | 12 ++++ spec/fixtures/ifconfig/open_solaris_b132 | 20 ++++++ spec/fixtures/ifconfig/ubuntu_7_04 | 38 ++++++++++++ spec/fixtures/ifconfig/ubuntu_7_04_eth0 | 9 +++ spec/fixtures/netstat/.DS_Store | Bin 0 -> 6148 bytes spec/fixtures/netstat/centos_5_5 | 5 ++ spec/fixtures/netstat/darwin_10_3_0 | 35 +++++++++++ spec/fixtures/netstat/darwin_10_6_4 | 29 +++++++++ spec/fixtures/netstat/darwin_9_8_0 | 28 +++++++++ spec/fixtures/netstat/fedora_10 | 7 +++ spec/fixtures/netstat/open_solaris_10 | 16 +++++ spec/fixtures/netstat/open_solaris_b132 | 17 +++++ spec/fixtures/netstat/ubuntu_7_04 | 7 +++ spec/unit/util/macaddress.rb | 75 +++++++++++++++++++++++ 32 files changed, 583 insertions(+), 13 deletions(-) create mode 100644 lib/facter/util/macaddress.rb create mode 100644 spec/fixtures/.DS_Store create mode 100644 spec/fixtures/ifconfig/centos_5_5 create mode 100644 spec/fixtures/ifconfig/centos_5_5_eth0 create mode 100644 spec/fixtures/ifconfig/darwin_10_3_0 create mode 100644 spec/fixtures/ifconfig/darwin_10_3_0_en0 create mode 100644 spec/fixtures/ifconfig/darwin_10_6_4 create mode 100644 spec/fixtures/ifconfig/darwin_10_6_4_en1 create mode 100644 spec/fixtures/ifconfig/darwin_9_8_0 create mode 100644 spec/fixtures/ifconfig/darwin_9_8_0_en0 create mode 100644 spec/fixtures/ifconfig/fedora_10 create mode 100644 spec/fixtures/ifconfig/fedora_10_eth0 create mode 100644 spec/fixtures/ifconfig/fedora_13 create mode 100644 spec/fixtures/ifconfig/fedora_13_eth0 create mode 100644 spec/fixtures/ifconfig/fedora_8 create mode 100644 spec/fixtures/ifconfig/fedora_8_eth0 create mode 100644 spec/fixtures/ifconfig/freebsd_6_0 create mode 100644 spec/fixtures/ifconfig/open_solaris_10 create mode 100644 spec/fixtures/ifconfig/open_solaris_b132 create mode 100644 spec/fixtures/ifconfig/ubuntu_7_04 create mode 100644 spec/fixtures/ifconfig/ubuntu_7_04_eth0 create mode 100644 spec/fixtures/netstat/.DS_Store create mode 100644 spec/fixtures/netstat/centos_5_5 create mode 100644 spec/fixtures/netstat/darwin_10_3_0 create mode 100644 spec/fixtures/netstat/darwin_10_6_4 create mode 100644 spec/fixtures/netstat/darwin_9_8_0 create mode 100644 spec/fixtures/netstat/fedora_10 create mode 100644 spec/fixtures/netstat/open_solaris_10 create mode 100644 spec/fixtures/netstat/open_solaris_b132 create mode 100644 spec/fixtures/netstat/ubuntu_7_04 create mode 100644 spec/unit/util/macaddress.rb diff --git a/lib/facter/macaddress.rb b/lib/facter/macaddress.rb index 889feeafe0..adc2c64f68 100644 --- a/lib/facter/macaddress.rb +++ b/lib/facter/macaddress.rb @@ -1,3 +1,5 @@ +require 'facter/util/macaddress' + Facter.add(:macaddress) do confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE SLES Debian Gentoo Ubuntu OEL OVS} setcode do @@ -26,19 +28,7 @@ Facter.add(:macaddress) do confine :kernel => :darwin - setcode do - ether = nil - output = %x{/sbin/ifconfig} - - output.split(/^\S/).each do |str| - if str =~ /10baseT/ # we're wired - str =~ /ether (\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ - ether = $1 - end - end - - ether - end + setcode { Facter::Util::Macaddress::Darwin.macaddress } end Facter.add(:macaddress) do diff --git a/lib/facter/util/macaddress.rb b/lib/facter/util/macaddress.rb new file mode 100644 index 0000000000..fc0a0432db --- /dev/null +++ b/lib/facter/util/macaddress.rb @@ -0,0 +1,28 @@ +# A module to gather macaddress facts +# +module Facter::Util::Macaddress + + module Darwin + def self.macaddress + iface = default_interface + Facter.warn "Could not find a default route. Using first non-loopback interface" if iface.empty? + + macaddress = `#{ifconfig_command} #{iface} | /usr/bin/awk '/ether/{print $2;exit}'`.chomp + macaddress.empty? ? nil : macaddress + end + + def self.default_interface + `#{netstat_command} | /usr/bin/awk '/^default/{print $6}'`.chomp + end + + private + + def self.netstat_command + '/usr/sbin/netstat -rn' + end + + def self.ifconfig_command + '/sbin/ifconfig' + end + end +end diff --git a/spec/fixtures/.DS_Store b/spec/fixtures/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..175869e309b0b116b0904542f489699795a14283 GIT binary patch literal 6148 zcmeH~O-{o=427Sef=Fz-WI0#h2BWH+fC~WqL6v|YwcTgOwR!fqC@5vi0zFHfmwN0O zv2fVgp%@!;47}Y=b zeVlR4;06cm=}$QL(Wk8|9Pz>M4sY0uH_l(Bs0fIF2#A0PhyV$&XS;PbO|^=E2#CNp z0slS}x@%1xn#QMtON;>28N*?Gj#+|QJV33fLsLd*)=H_Z)N;hIR?c|IyqY>RZRM~W zJ}hsxoKP%o=lLblVYR7N5fFi&1bp_U&AtEM(|?)&Jx3-2BJfWM*lhW*T=3R8^KfD#+j`E5>bHAnzO{1LgD5vU=fOC mtu 16384 + inet6 ::1 prefixlen 128 + inet6 fe80::1%lo0 prefixlen 64 scopeid 0x1 + inet 127.0.0.1 netmask 0xff000000 +gif0: flags=8010 mtu 1280 +stf0: flags=0<> mtu 1280 +en0: flags=8863 mtu 1500 + ether 00:17:f2:06:e3:c2 + inet6 fe80::217:f2ff:fe06:e3c2%en0 prefixlen 64 scopeid 0x4 + inet 100.100.104.12 netmask 0xffffff00 broadcast 100.100.104.255 + media: autoselect (1000baseT ) + status: active +en1: flags=8863 mtu 1500 + ether 00:17:f2:06:e3:c3 + media: autoselect + status: inactive +fw0: flags=8863 mtu 2030 + lladdr 00:16:cb:ff:fe:76:6e:be + media: autoselect + status: inactive +vmnet8: flags=8863 mtu 1500 + ether 00:50:56:c0:00:08 + inet 172.16.95.1 netmask 0xffffff00 broadcast 172.16.95.255 +vmnet1: flags=8863 mtu 1500 + ether 00:50:56:c0:00:01 + inet 172.16.201.1 netmask 0xffffff00 broadcast 172.16.201.255 \ No newline at end of file diff --git a/spec/fixtures/ifconfig/darwin_10_3_0_en0 b/spec/fixtures/ifconfig/darwin_10_3_0_en0 new file mode 100644 index 0000000000..1e33f712a7 --- /dev/null +++ b/spec/fixtures/ifconfig/darwin_10_3_0_en0 @@ -0,0 +1,6 @@ +en0: flags=8863 mtu 1500 + ether 00:17:f2:06:e3:c2 + inet6 fe80::217:f2ff:fe06:e3c2%en0 prefixlen 64 scopeid 0x4 + inet 100.100.104.12 netmask 0xffffff00 broadcast 100.100.104.255 + media: autoselect (1000baseT ) + status: active \ No newline at end of file diff --git a/spec/fixtures/ifconfig/darwin_10_6_4 b/spec/fixtures/ifconfig/darwin_10_6_4 new file mode 100644 index 0000000000..9fa04b00dd --- /dev/null +++ b/spec/fixtures/ifconfig/darwin_10_6_4 @@ -0,0 +1,28 @@ +lo0: flags=8049 mtu 16384 + inet6 ::1 prefixlen 128 + inet6 fe80::1%lo0 prefixlen 64 scopeid 0x1 + inet 127.0.0.1 netmask 0xff000000 +gif0: flags=8010 mtu 1280 +stf0: flags=0<> mtu 1280 +en0: flags=8863 mtu 1500 + ether 58:b0:35:fa:08:b1 + media: autoselect + status: inactive +en1: flags=8863 mtu 1500 + ether 58:b0:35:7f:25:b3 + inet6 fe80::5ab0:35ff:fe7f:25b3%en1 prefixlen 64 scopeid 0x5 + inet 192.168.100.230 netmask 0xffffff00 broadcast 192.168.100.255 + media: + status: active +fw0: flags=8863 mtu 4078 + lladdr c4:2c:03:ff:fe:fc:c8:aa + media: autoselect + status: inactive +vboxnet0: flags=8842 mtu 1500 + ether 0a:00:27:00:00:00 +vmnet1: flags=8863 mtu 1500 + ether 00:50:56:c0:00:01 + inet 172.16.135.1 netmask 0xffffff00 broadcast 172.16.135.255 +vmnet8: flags=8863 mtu 1500 + ether 00:50:56:c0:00:08 + inet 172.16.38.1 netmask 0xffffff00 broadcast 172.16.38.255 diff --git a/spec/fixtures/ifconfig/darwin_10_6_4_en1 b/spec/fixtures/ifconfig/darwin_10_6_4_en1 new file mode 100644 index 0000000000..10e6c48dda --- /dev/null +++ b/spec/fixtures/ifconfig/darwin_10_6_4_en1 @@ -0,0 +1,6 @@ +en1: flags=8863 mtu 1500 + ether 58:b0:35:7f:25:b3 + inet6 fe80::5ab0:35ff:fe7f:25b3%en1 prefixlen 64 scopeid 0x5 + inet 192.168.100.230 netmask 0xffffff00 broadcast 192.168.100.255 + media: + status: active diff --git a/spec/fixtures/ifconfig/darwin_9_8_0 b/spec/fixtures/ifconfig/darwin_9_8_0 new file mode 100644 index 0000000000..80f5d94cea --- /dev/null +++ b/spec/fixtures/ifconfig/darwin_9_8_0 @@ -0,0 +1,26 @@ +lo0: flags=8049 mtu 16384 + inet6 fe80::1%lo0 prefixlen 64 scopeid 0x1 + inet 127.0.0.1 netmask 0xff000000 + inet6 ::1 prefixlen 128 +gif0: flags=8010 mtu 1280 +stf0: flags=0<> mtu 1280 +en0: flags=8863 mtu 1500 + inet6 fe80::217:f2ff:fe06:e42e%en0 prefixlen 64 scopeid 0x4 + inet 100.100.107.4 netmask 0xffffff00 broadcast 100.100.107.255 + ether 00:17:f2:06:e4:2e + media: autoselect (1000baseT ) status: active + supported media: autoselect 10baseT/UTP 10baseT/UTP 10baseT/UTP 10baseT/UTP 100baseTX 100baseTX 100baseTX 100baseTX 1000baseT 1000baseT 1000baseT +en1: flags=8863 mtu 1500 + ether 00:17:f2:06:e4:2f + media: autoselect status: inactive + supported media: autoselect 10baseT/UTP 10baseT/UTP 10baseT/UTP 10baseT/UTP 100baseTX 100baseTX 100baseTX 100baseTX 1000baseT 1000baseT 1000baseT +fw0: flags=8863 mtu 2030 + lladdr 00:16:cb:ff:fe:76:66:f2 + media: autoselect status: inactive + supported media: autoselect +vmnet8: flags=8863 mtu 1500 + inet 192.168.210.1 netmask 0xffffff00 broadcast 192.168.210.255 + ether 00:50:56:c0:00:08 +vmnet1: flags=8863 mtu 1500 + inet 192.168.61.1 netmask 0xffffff00 broadcast 192.168.61.255 + ether 00:50:56:c0:00:01 \ No newline at end of file diff --git a/spec/fixtures/ifconfig/darwin_9_8_0_en0 b/spec/fixtures/ifconfig/darwin_9_8_0_en0 new file mode 100644 index 0000000000..658ae777a0 --- /dev/null +++ b/spec/fixtures/ifconfig/darwin_9_8_0_en0 @@ -0,0 +1,6 @@ +en0: flags=8863 mtu 1500 + inet6 fe80::217:f2ff:fe06:e42e%en0 prefixlen 64 scopeid 0x4 + inet 100.100.107.4 netmask 0xffffff00 broadcast 100.100.107.255 + ether 00:17:f2:06:e4:2e + media: autoselect (1000baseT ) status: active + supported media: autoselect 10baseT/UTP 10baseT/UTP 10baseT/UTP 10baseT/UTP 100baseTX 100baseTX 100baseTX 100baseTX 1000baseT 1000baseT 1000baseT \ No newline at end of file diff --git a/spec/fixtures/ifconfig/fedora_10 b/spec/fixtures/ifconfig/fedora_10 new file mode 100644 index 0000000000..42b06ca64c --- /dev/null +++ b/spec/fixtures/ifconfig/fedora_10 @@ -0,0 +1,36 @@ +eth0 Link encap:Ethernet HWaddr 00:17:F2:06:E4:26 + inet addr:100.100.108.9 Bcast:100.100.108.255 Mask:255.255.255.0 + inet6 addr: fe80::217:f2ff:fe06:e426/64 Scope:Link + UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 + RX packets:47711100 errors:0 dropped:0 overruns:0 frame:0 + TX packets:45446436 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:1000 + RX bytes:17037006736 (15.8 GiB) TX bytes:9198820680 (8.5 GiB) + Memory:92c20000-92c40000 + +lo Link encap:Local Loopback + inet addr:127.0.0.1 Mask:255.0.0.0 + inet6 addr: ::1/128 Scope:Host + UP LOOPBACK RUNNING MTU:16436 Metric:1 + RX packets:366974 errors:0 dropped:0 overruns:0 frame:0 + TX packets:366974 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:0 + RX bytes:33867944 (32.2 MiB) TX bytes:33867944 (32.2 MiB) + +vmnet1 Link encap:Ethernet HWaddr 00:50:56:C0:00:01 + inet addr:172.16.131.1 Bcast:172.16.131.255 Mask:255.255.255.0 + inet6 addr: fe80::250:56ff:fec0:1/64 Scope:Link + UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 + RX packets:0 errors:0 dropped:0 overruns:0 frame:0 + TX packets:20 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:1000 + RX bytes:0 (0.0 b) TX bytes:0 (0.0 b) + +vmnet8 Link encap:Ethernet HWaddr 00:50:56:C0:00:08 + inet addr:192.168.183.1 Bcast:192.168.183.255 Mask:255.255.255.0 + inet6 addr: fe80::250:56ff:fec0:8/64 Scope:Link + UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 + RX packets:0 errors:0 dropped:0 overruns:0 frame:0 + TX packets:20 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:1000 + RX bytes:0 (0.0 b) TX bytes:0 (0.0 b) \ No newline at end of file diff --git a/spec/fixtures/ifconfig/fedora_10_eth0 b/spec/fixtures/ifconfig/fedora_10_eth0 new file mode 100644 index 0000000000..fa957d9094 --- /dev/null +++ b/spec/fixtures/ifconfig/fedora_10_eth0 @@ -0,0 +1,9 @@ +eth0 Link encap:Ethernet HWaddr 00:17:F2:06:E4:26 + inet addr:100.100.108.9 Bcast:100.100.108.255 Mask:255.255.255.0 + inet6 addr: fe80::217:f2ff:fe06:e426/64 Scope:Link + UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 + RX packets:47711864 errors:0 dropped:0 overruns:0 frame:0 + TX packets:45447195 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:1000 + RX bytes:17037147877 (15.8 GiB) TX bytes:9198873602 (8.5 GiB) + Memory:92c20000-92c40000 \ No newline at end of file diff --git a/spec/fixtures/ifconfig/fedora_13 b/spec/fixtures/ifconfig/fedora_13 new file mode 100644 index 0000000000..23491dfb23 --- /dev/null +++ b/spec/fixtures/ifconfig/fedora_13 @@ -0,0 +1,18 @@ +eth0 Link encap:Ethernet HWaddr 00:17:F2:0D:9B:A8 + inet addr:100.100.108.27 Bcast:100.100.108.255 Mask:255.255.255.0 + inet6 addr: fe80::217:f2ff:fe0d:9ba8/64 Scope:Link + UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 + RX packets:1813272 errors:0 dropped:0 overruns:0 frame:0 + TX packets:1539207 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:1000 + RX bytes:1729725903 (1.6 GiB) TX bytes:1606599540 (1.4 GiB) + Memory:f2c20000-f2c40000 + +lo Link encap:Local Loopback + inet addr:127.0.0.1 Mask:255.0.0.0 + inet6 addr: ::1/128 Scope:Host + UP LOOPBACK RUNNING MTU:16436 Metric:1 + RX packets:482 errors:0 dropped:0 overruns:0 frame:0 + TX packets:482 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:0 + RX bytes:48488 (47.3 KiB) TX bytes:48488 (47.3 KiB) \ No newline at end of file diff --git a/spec/fixtures/ifconfig/fedora_13_eth0 b/spec/fixtures/ifconfig/fedora_13_eth0 new file mode 100644 index 0000000000..ca7afd9865 --- /dev/null +++ b/spec/fixtures/ifconfig/fedora_13_eth0 @@ -0,0 +1,9 @@ +eth0 Link encap:Ethernet HWaddr 00:17:F2:0D:9B:A8 + inet addr:100.100.108.27 Bcast:100.100.108.255 Mask:255.255.255.0 + inet6 addr: fe80::217:f2ff:fe0d:9ba8/64 Scope:Link + UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 + RX packets:1813275 errors:0 dropped:0 overruns:0 frame:0 + TX packets:1539208 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:1000 + RX bytes:1729726282 (1.6 GiB) TX bytes:1606599653 (1.4 GiB) + Memory:f2c20000-f2c40000 \ No newline at end of file diff --git a/spec/fixtures/ifconfig/fedora_8 b/spec/fixtures/ifconfig/fedora_8 new file mode 100644 index 0000000000..f54caa4b87 --- /dev/null +++ b/spec/fixtures/ifconfig/fedora_8 @@ -0,0 +1,38 @@ +eth0 Link encap:Ethernet HWaddr 00:18:F3:F6:33:E5 + inet addr:64.71.191.242 Bcast:64.71.191.247 Mask:255.255.255.248 + inet6 addr: fe80::218:f3ff:fef6:33e5/64 Scope:Link + UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 + RX packets:834422948 errors:0 dropped:0 overruns:0 frame:0 + TX packets:1241890353 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:1000 + RX bytes:3777804409 (3.5 GiB) TX bytes:2061587234 (1.9 GiB) + Interrupt:21 + +eth0:0 Link encap:Ethernet HWaddr 00:18:F3:F6:33:E5 + inet addr:64.71.191.243 Bcast:64.71.191.247 Mask:255.255.255.248 + UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 + Interrupt:21 + +eth0:1 Link encap:Ethernet HWaddr 00:18:F3:F6:33:E5 + inet addr:64.71.191.244 Bcast:64.71.191.247 Mask:255.255.255.248 + UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 + Interrupt:21 + +eth0:2 Link encap:Ethernet HWaddr 00:18:F3:F6:33:E5 + inet addr:64.71.191.245 Bcast:64.71.191.247 Mask:255.255.255.248 + UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 + Interrupt:21 + +eth0:3 Link encap:Ethernet HWaddr 00:18:F3:F6:33:E5 + inet addr:64.71.191.246 Bcast:64.71.191.247 Mask:255.255.255.248 + UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 + Interrupt:21 + +lo Link encap:Local Loopback + inet addr:127.0.0.1 Mask:255.0.0.0 + inet6 addr: ::1/128 Scope:Host + UP LOOPBACK RUNNING MTU:16436 Metric:1 + RX packets:59385736 errors:0 dropped:0 overruns:0 frame:0 + TX packets:59385736 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:0 + RX bytes:3519026710 (3.2 GiB) TX bytes:3519026710 (3.2 GiB) \ No newline at end of file diff --git a/spec/fixtures/ifconfig/fedora_8_eth0 b/spec/fixtures/ifconfig/fedora_8_eth0 new file mode 100644 index 0000000000..a7789dc077 --- /dev/null +++ b/spec/fixtures/ifconfig/fedora_8_eth0 @@ -0,0 +1,9 @@ +eth0 Link encap:Ethernet HWaddr 00:18:F3:F6:33:E5 + inet addr:64.71.191.242 Bcast:64.71.191.247 Mask:255.255.255.248 + inet6 addr: fe80::218:f3ff:fef6:33e5/64 Scope:Link + UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 + RX packets:834423032 errors:0 dropped:0 overruns:0 frame:0 + TX packets:1241890434 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:1000 + RX bytes:3777811119 (3.5 GiB) TX bytes:2061606027 (1.9 GiB) + Interrupt:21 \ No newline at end of file diff --git a/spec/fixtures/ifconfig/freebsd_6_0 b/spec/fixtures/ifconfig/freebsd_6_0 new file mode 100644 index 0000000000..3339a45588 --- /dev/null +++ b/spec/fixtures/ifconfig/freebsd_6_0 @@ -0,0 +1,12 @@ +fxp0: flags=8843 mtu 1500 + options=b + inet x.x.58.26 netmask 0xfffffff8 broadcast x.x.58.31 + inet x.x.58.27 netmask 0xffffffff broadcast x.x.58.27 + inet x.x.58.28 netmask 0xffffffff broadcast x.x.58.28 + inet x.x.58.29 netmask 0xffffffff broadcast x.x.58.29 + inet x.x.58.30 netmask 0xffffffff broadcast x.x.58.30 + ether 00:0e:0c:68:67:7c + media: Ethernet autoselect (100baseTX ) + status: active +lo0: flags=8049 mtu 16384 + inet 127.0.0.1 netmask 0xff000000 \ No newline at end of file diff --git a/spec/fixtures/ifconfig/open_solaris_10 b/spec/fixtures/ifconfig/open_solaris_10 new file mode 100644 index 0000000000..ed5dcbe632 --- /dev/null +++ b/spec/fixtures/ifconfig/open_solaris_10 @@ -0,0 +1,12 @@ +lo0: flags=2001000849 mtu 8232 index 1 + inet 127.0.0.1 netmask ff000000 +hme0: flags=1000843 mtu 1500 index 2 + inet 192.168.2.83 netmask ffffff00 broadcast 192.168.2.255 + ether 8:0:20:d1:6d:79 +lo0: flags=2002000849 mtu 8252 index 1 + inet6 ::1/128 +hme0: flags=2000841 mtu 1500 index 2 + inet6 fe80::a00:20ff:fed1:6d79/10 + ether 8:0:20:d1:6d:79 +hme0:1: flags=2080841 mtu 1500 index 2 + inet6 2404:130:0:1000:a00:20ff:fed1:6d79/64 \ No newline at end of file diff --git a/spec/fixtures/ifconfig/open_solaris_b132 b/spec/fixtures/ifconfig/open_solaris_b132 new file mode 100644 index 0000000000..c674a41239 --- /dev/null +++ b/spec/fixtures/ifconfig/open_solaris_b132 @@ -0,0 +1,20 @@ +lo0: flags=2001000849 mtu 8232 index 1 + inet 127.0.0.1 netmask ff000000 +bge0: flags=1100943 mtu 1500 index 2 + inet 202.78.241.97 netmask fffffff8 broadcast 202.78.241.103 + ether 0:1e:c9:43:55:f9 +int0: flags=1100843 mtu 9000 index 3 + inet 172.31.255.254 netmask ffffff00 broadcast 172.31.255.255 + ether 2:8:20:89:75:75 +lo0: flags=2002000849 mtu 8252 index 1 + inet6 ::1/128 +bge0: flags=2100941 mtu 1500 index 2 + inet6 fe80::ff:0/10 + ether 0:1e:c9:43:55:f9 +bge0:1: flags=2180941 mtu 1500 index 2 + inet6 2404:130:40:10::ff:0/64 +int0: flags=2100841 mtu 9000 index 3 + inet6 fe80::ff:0/10 + ether 2:8:20:89:75:75 +int0:1: flags=2180841 mtu 9000 index 3 + inet6 2404:130:40:18::ff:0/64 \ No newline at end of file diff --git a/spec/fixtures/ifconfig/ubuntu_7_04 b/spec/fixtures/ifconfig/ubuntu_7_04 new file mode 100644 index 0000000000..8310576401 --- /dev/null +++ b/spec/fixtures/ifconfig/ubuntu_7_04 @@ -0,0 +1,38 @@ +ath0 Link encap:Ethernet HWaddr 00:17:F2:49:E0:E6 + inet6 addr: fe80::217:f2ff:fe49:e0e6/64 Scope:Link + UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 + RX packets:0 errors:0 dropped:0 overruns:0 frame:0 + TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:0 + RX bytes:0 (0.0 b) TX bytes:0 (0.0 b) + +ath0:avah Link encap:Ethernet HWaddr 00:17:F2:49:E0:E6 + inet addr:169.254.10.213 Bcast:169.254.255.255 Mask:255.255.0.0 + UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 + +eth0 Link encap:Ethernet HWaddr 00:16:CB:A6:D4:3A + inet addr:100.100.106.200 Bcast:100.100.106.255 Mask:255.255.255.0 + inet6 addr: fe80::216:cbff:fea6:d43a/64 Scope:Link + UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 + RX packets:1070 errors:0 dropped:0 overruns:0 frame:0 + TX packets:236 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:1000 + RX bytes:69905 (68.2 KiB) TX bytes:28435 (27.7 KiB) + Interrupt:17 + +lo Link encap:Local Loopback + inet addr:127.0.0.1 Mask:255.0.0.0 + inet6 addr: ::1/128 Scope:Host + UP LOOPBACK RUNNING MTU:16436 Metric:1 + RX packets:33 errors:0 dropped:0 overruns:0 frame:0 + TX packets:33 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:0 + RX bytes:3067 (2.9 KiB) TX bytes:3067 (2.9 KiB) + +wifi0 Link encap:UNSPEC HWaddr 00-17-F2-49-E0-E6-00-00-00-00-00-00-00-00-00-00 + UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 + RX packets:2655 errors:0 dropped:0 overruns:0 frame:24795 + TX packets:193 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:199 + RX bytes:754290 (736.6 KiB) TX bytes:8878 (8.6 KiB) + Interrupt:16 \ No newline at end of file diff --git a/spec/fixtures/ifconfig/ubuntu_7_04_eth0 b/spec/fixtures/ifconfig/ubuntu_7_04_eth0 new file mode 100644 index 0000000000..b9fc759303 --- /dev/null +++ b/spec/fixtures/ifconfig/ubuntu_7_04_eth0 @@ -0,0 +1,9 @@ +eth0 Link encap:Ethernet HWaddr 00:16:CB:A6:D4:3A + inet addr:100.100.106.200 Bcast:100.100.106.255 Mask:255.255.255.0 + inet6 addr: fe80::216:cbff:fea6:d43a/64 Scope:Link + UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 + RX packets:1131 errors:0 dropped:0 overruns:0 frame:0 + TX packets:280 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:1000 + RX bytes:73859 (72.1 KiB) TX bytes:35243 (34.4 KiB) + Interrupt:17 \ No newline at end of file diff --git a/spec/fixtures/netstat/.DS_Store b/spec/fixtures/netstat/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 Date: Wed, 11 Aug 2010 15:00:52 -0700 Subject: [PATCH 0456/3753] Removing stupid .DS_Store files :( --- spec/fixtures/.DS_Store | Bin 6148 -> 0 bytes spec/fixtures/netstat/.DS_Store | Bin 6148 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 spec/fixtures/.DS_Store delete mode 100644 spec/fixtures/netstat/.DS_Store diff --git a/spec/fixtures/.DS_Store b/spec/fixtures/.DS_Store deleted file mode 100644 index 175869e309b0b116b0904542f489699795a14283..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeH~O-{o=427Sef=Fz-WI0#h2BWH+fC~WqL6v|YwcTgOwR!fqC@5vi0zFHfmwN0O zv2fVgp%@!;47}Y=b zeVlR4;06cm=}$QL(Wk8|9Pz>M4sY0uH_l(Bs0fIF2#A0PhyV$&XS;PbO|^=E2#CNp z0slS}x@%1xn#QMtON;>28N*?Gj#+|QJV33fLsLd*)=H_Z)N;hIR?c|IyqY>RZRM~W zJ}hsxoKP%o=lLblVYR7N5fFi&1bp_U&AtEM(|?)&Jx3-2BJfWM*lhW*T=3R8^KfD#+j`E5>bHAnzO{1LgD5vU=fOCH1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 Date: Wed, 18 Aug 2010 02:50:52 +1000 Subject: [PATCH 0457/3753] Updated CHANGELOG rake task --- tasks/rake/changlog.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks/rake/changlog.rake b/tasks/rake/changlog.rake index adeeb03a46..0c2f1d26c5 100644 --- a/tasks/rake/changlog.rake +++ b/tasks/rake/changlog.rake @@ -8,7 +8,7 @@ task :changelog do CHANGELOG_DIR = "#{Dir.pwd}" mkdir(CHANGELOG_DIR) unless File.directory?(CHANGELOG_DIR) - change_body = `git-changelog --no-limit -a` + change_body = `git-changelog --limit=99999` File.open(File.join(CHANGELOG_DIR, "CHANGELOG"), 'w') do |f| f << change_body end From 98ef5e85528669a9ff224de8ac03dd3385665cad Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Wed, 18 Aug 2010 02:51:59 +1000 Subject: [PATCH 0458/3753] Updated CHANGELOG for 1.5.8rc1 --- CHANGELOG | 117 +++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 89 insertions(+), 28 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 9fc5ef2c1a..445a3ab0d7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,53 @@ +1.5.8rc1 +======== +4398b36 Updated CHANGELOG rake task +e02be1d [#4156] Updating spec to match Kai's change +bff84c2 [#4156] Applying patch by Kai +b7fe989 [#2330] Update uptime calculation to use /bin/cat +e9a60bc Facter::Manufacturer - sunos test + simplified regex +be411c0 Facter::Manufacturer - test for SunOS and FreeBSD +67f6604 [#4062] Implement operating system facts for MeeGo +a2bcacd [#2330] Uptime should not make redundant system calls +ce7bd9f Refactor rakefile to use spec.ops, separate rcov task +faaa169 Fix #4352 - Support for detecting KVM virtuals on FreeBSD +82286e4 Fix #4352 - Support for detecting virtuals (jails) on FreeBSD +b2c2114 Properly wrapped the windows ipaddress fact in a setcode block. +1bd2ca2 Fixed #3929 - Added user confine to AIX memory facts +8106bc3 Adding HP-UX support to Facter's IP facts +83b3ea6 Fixed #3393 - Updates to Facter for MS Windows +ffcae46 Fixed #3403 - Added fact to query vlans; added spec test +d4b8401 Merged Jos Backus patch to remove requirement for ftools altogether +73dcbb9 Fixed #2355 read hang on /proc/xen/capabilties on RHEL 4.7 +d109def Fix #1365 - load all facts via cli +6c87917 Fixed failing test introduced by previous commit +c5b8d3b Fixes #3740 - split dmi output on regex +25bf5c2 Fix virtual unit test on non-linux by stubbing kernel +9a00eae Fixed #2313 - Somewhat essential hardware facts not available on OpenBSD, patch included +e19024b Fixed #2938 - interfaces that don't match ^\w+[.:]?\d+ are ignored +97879f9 Added support for Slackware in operatingsystem and operatingsystemrelease +802e6c2 Fixed #3542 - Ruby 1.9: broken unittest, String#each no longer exists +2f016f3 Fixed #3541 - Ruby 1.9: broken unittest, unexpected invocation: Process.waitall() +84d3d9f Fixed #3445 - Facter does not handle solaris branded zones properly +b5a8de0 Fix for #3411 install.rb should not put "." first in the tmp_dirs +8ea33eb Fixed #3447 - OVS and OEL not matching in operatingsystemrelease +aeee83c Fixed #3410 - Warnings in rake spec +8bf8cb5 Fixes #3397 - is_virtual fact does not detect Linux-VServer +62b6773 Add kvm support to virtual fact +dca615c fixes #2573, #2085, #1291 - fixes domain and fqdn facts resolution +86447c8 Revert "use popen3 in Resolution.exec" +7750f03 Fix #2341 - stricter handling of dmidecode split +f4269d9 Fix #2746 - add architecture support for GNU/kFreeBSD +50cef83 Fix missing error case +356cf15 Remove whitespace in DMI facts (#3008, #3011) +feecd39 Only ignore IPs starting with 127. +68fc123 Added package signing task +33fb770 use popen3 in Resolution.exec to catch stderr +8109806 introduce a warn mechanism for debugging +b2c1ca5 Add docs to Mac OS X package creation script and clean out old docs in the preflight +5412eab Fixed : 2788 - ftools missing in Ruby 1.9 +5b95a12 Fixes #2704. Problem finding install.rb three levels up +9aef69e Removed all ChangeLog + 1.5.7 ===== 3a39dd8 Updated ChangeLog and task @@ -5,6 +55,9 @@ 8398238 Added rcov support to spec task 7194454 Updated CI Rake task 2472048 Clarify licensing as GPLv2 (or any later version) + +1.5.7rc1 +======== 4bc05e9 Added new format ChangeLog 5bc8db3 Incremented version and updated CHANGELOG eb3a8a7 Issue #2414 - add unit test @@ -18,7 +71,7 @@ bfe8a2a Fix 2455 - improve error handling on fact load 5982deb Added default Rake task 0e0483a Fix bug where you'd get an 'undefined method' error if trying to access a fact's value when collection has not being yet initialized. fe41fb8 Fix #2470 - duplicate entries in interfaces fact -be9e484 Update OS X minor version fact to cope with '10.x' values and provide test coverage +be9e484 Update OS X minor version fact to cope with '10.x' values and provide test coverage switch %x{} call to Facter::Util::Resolution.exec for better testing f3ad66f Update install.rb to cope with all OS X versions, not just 10.5 c02d3b6 Issue #2292 Add tests for virtual facts 6c9fec5 Added path fact @@ -37,29 +90,21 @@ ba44f08 Removed --no-thread and --no-chain-reply-to from rake mail_patches task 806f49f Added facter branding to format patch command 96c015c Added spec.executables to Facter gemspec in Rakefile d97a63e Sync rpm spec file with latest from Fedora/EPEL + +1.5.5 +===== dad4569 Added path to Rakefile 365cb8e CHANGELOG updates 68e0b24 Fix #2278 Revert fix for 2120 b533e78 Tighten operatingsystemrelease regex on CentOS < 5 48aa135 Fix operatingsystemrelease for CentOS < 5 -253fef1 Added spec files to package list +253fef1 Added spec files to package list Fixed CI rake tasks b37d683 Added install.rb to Rakefile package task 7995d05 Bumped release to 1.5.5rc2 1.5.5rc2 ======== 1de8891 Bumped release to 1.5.5rc2 - -1.5.5 -===== -dad4569 Added path to Rakefile -365cb8e CHANGELOG updates -68e0b24 Fix #2278 Revert fix for 2120 -b533e78 Tighten operatingsystemrelease regex on CentOS < 5 -48aa135 Fix operatingsystemrelease for CentOS < 5 -253fef1 Added spec files to package list -b37d683 Added install.rb to Rakefile package task -7995d05 Bumped release to 1.5.5rc2 56760d3 Facter #2120 - Solaris support for Facter[virtual] 2fb91ca Tests for #2227 - multiple interfaces on Darwin 00b192a Added SELinux tests @@ -99,14 +144,6 @@ b6f0f99 more consistent indentation and alignment. also removal of trailing whi 9bc174f Further fix #2032 - close IO 6b904a0 Added EC2 facts 86b01bf Fixed #2032 - file.open hanging on /proc/uptime on some platform -91d8cb7 Updated to version 1.5.4 -a99d043 Fixed #1966 - Added physicalprocessorcount fact -94ea807 This commit refs #1555, #1898 and fixes #1761 -04389db Added support for Oracle VM Server to operatingsystem and operatingsystemrelease -552f150 Added support for Oracle Enterprise Linux to operatingsystem -a932a69 Added README.rst for Facter -e52f962 Added Reductive Labs build library -0726437 Updated README 1.5.4 ===== @@ -114,10 +151,13 @@ e52f962 Added Reductive Labs build library a99d043 Fixed #1966 - Added physicalprocessorcount fact 94ea807 This commit refs #1555, #1898 and fixes #1761 04389db Added support for Oracle VM Server to operatingsystem and operatingsystemrelease -552f150 Added support for Oracle Enterprise Linux to operatingsystem +552f150 Added support for Oracle Enterprise Linux to operatingsystem and operatingsystemrelease a932a69 Added README.rst for Facter e52f962 Added Reductive Labs build library 0726437 Updated README + +1.5.4rc1 +======== f4bc74d Fixing #1927 - failing facts don't kill Facter 063e4dc Fixed #1850 - Facter updates for Ruby 1.9 b85ab0a Fixed #1924 - Fixed lo / lo:0 local interface matching @@ -131,7 +171,7 @@ d9eef19 Added timezone fact ===== b86a1fb Updated to version 1.5.3 a73e803 Fixing the usage of the macosx util module; I somehow missed renaming it here -23289bd Fixed uptime refactor issues on non-Linux platforms +23289bd Fixed uptime refactor issues on non-Linux platforms Signed-off-by: James Turnbull a194c91 Adding mail_patches rake task a82f476 Renaming Facter::Macosx to Facter::Util::Macosx 1f1fa9b Fixing #1838 - profiler failures don't throw exceptions @@ -153,7 +193,7 @@ a70184a Fixed #1791 - support for virtual fact on Solaris 10 85b2a55 minor fix to operatingsystemversion to correctly parse /etc/release on OpenSolaris 2008.11. 8247304 Fixed errors on unrecognised option in binary 0fe4611 Added ci namespace and Rake tasks -7ddea77 Fix for #1727 - id fact should not rely on whoami on Solaris +7ddea77 Fix for #1727 - id fact should not rely on whoami on Solaris Signed-off-by: Martin Englund f9a346a Sync specfile with latest from Fedora fd07cd2 Removed EPM task 43d0aea Fixed #1697 - Typo in ipaddress.rb causes timeout under Solaris 10 SPARC @@ -204,7 +244,7 @@ b91ee5e Remove duplicated code paths df8fc8c fix terrible error with overwriting permissions 91ca4ab Fixed #1490 - Added virtual fact ff45c86 Feature #1487: Package creation scripts for Mac OS X -9b42182 Modified the operatingsystem fact for Debian so it looks in +9b42182 Modified the operatingsystem fact for Debian so it looks in /etc/debian_version instead of /proc/version. e1023de Feature #1478: Allow specification of --bindir --sbindir --sitelibdir --mandir --destdir in install.rb 845ae94 Feature #1475: CONFIG['bindir'] CONFIG['sbindir'] have undesirable defaults on OS X 10.5 a12608e Fixes #1467 - macaddress not set on Ubuntu @@ -219,6 +259,9 @@ dc7363e Set macaddress on windows platform ded53b0 Fixed Rakefile to include additional files including tests et al 1cf98d1 Adjusted version to be in line with previous standard ff0e90b Adding (apparently now required) author info to the gem spec + +1.5 +=== 7042c46 Updated to version 1.5 e98efd3 Updated to version 1.5 d49d63c Updating the changelog for 1.5 @@ -238,7 +281,7 @@ e22b408 Changed 'timeout' option to 'limit' def18b5 Fixes #1357 - change ps syntax for OSX and BSD ce7b74c Rejustifying all of the whitespace in the facts, yay. 2ee5d29 Refactoring how recursive searches are detected. -d322df9 Refactored so each fact resolution can specify a separate timeout, +d322df9 Refactored so each fact resolution can specify a separate timeout, but the default is still 0.5. 9a1882e Retrieve hardwaremodel for AIX from sys0 modelname. b574c6a Refactered ipmess.rb and util/ip.rb to support separate *BSD logic for *BSD aliased interfaces. d9bd388 Refactor of netmask fact - fixes ticket #66 @@ -278,7 +321,7 @@ fef9b7d fixing whitespace 567549b Closes #1145 - fixed bad interface names by replacing : with _ d449472 Updated CHANGELOG bd3b316 removing .swp file -e11edfb Switching from test/unit to rspec, and fixing a couple +e11edfb Switching from test/unit to rspec, and fixing a couple of small test failures. 1a5ba71 Fixed Solaris detection of lo0 for ticket #46 92b43e0 Added require util ip.rb file 0c4ac42 Fixed #46 - refactor ipmess.rb @@ -308,7 +351,7 @@ dce6245 Revert "Adjusted :kernel confine to make it more in line with others" c5e6f60 Adjusted :kernel confine to make it more in line with others a4698ce Updated CHANGELOG 8b08d5f Added support to return multiple interfaces and their IP addresses as facts. Existing ipaddress fact which returns IP address of first interface on node is still available. Currently Linux only. Closes #6 -6113375 Added macaddress fact support for FreeBSD and OpenBSD - closes #37 +6113375 Added macaddress fact support for FreeBSD and OpenBSD - closes #37 Added hardwareisa support for *BSD platforms - closed #38 Facter now detects the Mandriva distribution - closes #39 Facter now correctly detects ipaddress on NetBSD - closes #42 8426aaf making the install script executable f35ee22 Drastically speeding up the lsb data retrieval, and refactoring the dmidecode data so it is a bit cleaner and does not produce extraneous output or errors 20986d9 Set operatingsystemrelease to the major release on RHEL and Fedora @@ -415,6 +458,10 @@ b543152 Updated for use with latest Fedora ruby packages 15f2f44 Updated to version 1.3 15931ef Updated to version 1.3 261d909 Updated to version 1.3 + +1.3 +=== +92c48b9 Adding release tag REL_1_3 539d593 fixing installer so it does not install batch files on darwin 4c1d5e0 trying to fix facterbin rubylib setting 7f2504d fixing test so that it works even if rubylib is not set @@ -496,3 +543,17 @@ a295c73 Fixing bug when a fact with no resolutions is asked for d9c86d5 updating everything to essentially disable docs generation 64a86db Adding Release tag +1.0.2 +===== +8c91fb1 adding release tag +1dc02f9 adding extra "return nil" statements, and hopefully fixing the test for cygwin +b542ec5 Updated to version 1.0.2 +f1c8f10 adding changelog +7df3411 adding fixes Eric Sorenson found with cygwin +c646434 updates +fe90bf1 Updated to version 1.0.1 +3f0186d Modified version +58538d2 removing filehandle-based tests +8cb9662 updating INSTALL with patch from ian +7cec936 moving things to the trunk + From f280703f4c8cbde85bb92a79864cdfb965d64ffb Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Wed, 18 Aug 2010 04:56:06 +1000 Subject: [PATCH 0459/3753] Incremented version to 1.5.8 --- lib/facter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter.rb b/lib/facter.rb index 46e2e99ed8..42d5371d3f 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -27,7 +27,7 @@ module Util; end include Comparable include Enumerable - FACTERVERSION = '1.5.7' + FACTERVERSION = '1.5.8' # = Facter # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. From 81ccb4882d557e37127d81edfc82218189b57314 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Wed, 18 Aug 2010 04:58:41 +1000 Subject: [PATCH 0460/3753] Removed references to Reductive Labs in the Rakefile --- Rakefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Rakefile b/Rakefile index 51b4d13a23..1e8fd29ce3 100644 --- a/Rakefile +++ b/Rakefile @@ -34,9 +34,9 @@ spec = Gem::Specification.new do |spec| spec.executables = %w{facter} spec.version = Facter::FACTERVERSION spec.summary = 'Facter, a system inventory tool' - spec.author = 'Reductive Labs' - spec.email = 'puppet@reductivelabs.com' - spec.homepage = 'http://reductivelabs.com/' + spec.author = 'Puppet Labs' + spec.email = 'info@puppetlabs.com' + spec.homepage = 'http://puppetlabs.com/' spec.rubyforge_project = 'facter' spec.has_rdoc = true spec.rdoc_options << From 51bcebe38cab6088c901f1006339bbe40a36d161 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Wed, 18 Aug 2010 05:02:45 +1000 Subject: [PATCH 0461/3753] Fixed Rakefile package task version detection --- Rakefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index 1e8fd29ce3..e472e3ae70 100644 --- a/Rakefile +++ b/Rakefile @@ -12,11 +12,14 @@ end Dir['tasks/**/*.rake'].each { |t| load t } -require 'facter.rb' require 'rake' require 'rake/packagetask' require 'rake/gempackagetask' +module Facter + FACTERVERSION = File.read('lib/facter.rb')[/FACTERVERSION *= *'(.*)'/,1] or fail "Couldn't find FACTERVERSION" +end + FILES = FileList[ '[A-Z]*', 'install.rb', From f67ec05c018a3fe602c17383966d67b4e05bad6b Mon Sep 17 00:00:00 2001 From: Rein Henrichs Date: Wed, 18 Aug 2010 11:28:51 -0700 Subject: [PATCH 0462/3753] [#4567] Add ext/facter-diff to compare output of 2 versions Example: ./ext/facter-diff 1.5.5 1.5.8rc1 --- ext/facter-diff | 73 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100755 ext/facter-diff diff --git a/ext/facter-diff b/ext/facter-diff new file mode 100755 index 0000000000..361b80b682 --- /dev/null +++ b/ext/facter-diff @@ -0,0 +1,73 @@ +#!/usr/bin/env sh +# +# Output the difference between a facter command run on two different versions +# of facter. Uses unified diff format. + +OPTIONS_SPEC="\ +facter-diff [options] [fact]... + +Example: + + ./ext/facter-diff 1.5.7 1.0.2 + +-- +h,help Display this help" + +. "$(git --exec-path)/git-sh-setup" +eval "$(echo "$OPTIONS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?)" +trap 'err=$?; cleanup; exit $err' 0 + +cleanup() { + test $origin && git checkout "$origin" &> /dev/null +} + +facter() { + ruby -Ilib bin/facter "$@" +} + +log_facter_run() { + local ref=$1 && shift + local tmpfile=$1 && shift + + git checkout -f "$ref" &> /dev/null || + die "fatal: unable to checkout $ref" + facter "$@" > $tmpfile +} + +verify_revision() { + git rev-parse --verify --quiet "$1" > /dev/null || + die "fatal: '$1' is not a valid revision" +} + +test $1 = "--" && shift # git rev-parse seems to leave the -- in +test $# -eq 0 && usage + +test $# -gt 1 || + die "fatal: must specify two revisions" + +status=$(git status -s) +test -z "$status" || + die "fatal: $0 cannot be used with a dirty working copy" + +origin=$(git name-rev --name-only HEAD) +test -n "$origin" || + die "fatal: $0 cannot be used unless currently on a branch" + +test -x "bin/facter" || + die "fatal: $0 must be run from the project root" + +ref1="$1" && shift +ref2="$1" && shift + +verify_revision $ref1 +verify_revision $ref2 + +tmpfile1="/tmp/$(basename $0).$$.1.tmp" +tmpfile2="/tmp/$(basename $0).$$.2.tmp" + +log_facter_run $ref1 $tmpfile1 $@ +log_facter_run $ref2 $tmpfile2 $@ + +git checkout -f "$origin" &> /dev/null + +diff --label "$ref1" --label "$ref2" -u $tmpfile1 $tmpfile2 From a85f2b0f5c43f3b2324d06310a1d673438840e89 Mon Sep 17 00:00:00 2001 From: Rein Henrichs Date: Tue, 10 Aug 2010 15:25:22 -0700 Subject: [PATCH 0463/3753] [#2865] Fix reporting of virtual facts Regexp tested the s_context or VxID field if /proc/self/status and returned false for 0 and true for any other number. 0 indicates a host, which is still virtual. Fix changes regexp to correctly report hosts as virtual. Tested against vserver 2.1 and 2.3. --- lib/facter/util/virtual.rb | 2 +- .../proc_self_status/vserver_2_1/guest | 37 ++++++++++++++++++ .../virtual/proc_self_status/vserver_2_1/host | 36 +++++++++++++++++ .../proc_self_status/vserver_2_3/guest | 39 +++++++++++++++++++ .../virtual/proc_self_status/vserver_2_3/host | 39 +++++++++++++++++++ spec/unit/util/virtual.rb | 20 ++++++++++ 6 files changed, 172 insertions(+), 1 deletion(-) create mode 100644 spec/fixtures/virtual/proc_self_status/vserver_2_1/guest create mode 100644 spec/fixtures/virtual/proc_self_status/vserver_2_1/host create mode 100644 spec/fixtures/virtual/proc_self_status/vserver_2_3/guest create mode 100644 spec/fixtures/virtual/proc_self_status/vserver_2_3/host diff --git a/lib/facter/util/virtual.rb b/lib/facter/util/virtual.rb index 5f70d4287b..bc00e1cd1c 100644 --- a/lib/facter/util/virtual.rb +++ b/lib/facter/util/virtual.rb @@ -22,7 +22,7 @@ def self.zone? def self.vserver? return false unless FileTest.exists?("/proc/self/status") txt = File.read("/proc/self/status") - return true if txt =~ /^(s_context|VxID):[[:blank:]]*[1-9]/ + return true if txt =~ /^(s_context|VxID):[[:blank:]]*[0-9]/ return false end diff --git a/spec/fixtures/virtual/proc_self_status/vserver_2_1/guest b/spec/fixtures/virtual/proc_self_status/vserver_2_1/guest new file mode 100644 index 0000000000..760cc24d26 --- /dev/null +++ b/spec/fixtures/virtual/proc_self_status/vserver_2_1/guest @@ -0,0 +1,37 @@ +Name: cat +State: R (running) +SleepAVG: 58% +Tgid: 24671 +Pid: 24671 +PPid: 24670 +TracerPid: 0 +Uid: 0 0 0 0 +Gid: 0 0 0 0 +FDSize: 32 +Groups: 0 +VmPeak: 1580 kB +VmSize: 1580 kB +VmLck: 0 kB +VmHWM: 372 kB +VmRSS: 372 kB +VmData: 152 kB +VmStk: 88 kB +VmExe: 16 kB +VmLib: 1280 kB +VmPTE: 12 kB +Threads: 1 +SigQ: 0/4294967295 +SigPnd: 0000000000000000 +ShdPnd: 0000000000000000 +SigBlk: 0000000000000000 +SigIgn: 0000000000000000 +SigCgt: 0000000000000000 +CapInh: 0000000000000000 +CapPrm: 00000000344c04ff +CapEff: 00000000344c04ff +s_context: 40074 +ctxflags: 1602020010 +initpid: 0 +ipv4root: 4a00007f/ffffffff 4a24f6d5/00ffffff +ipv4root_bcast: 00000000 + diff --git a/spec/fixtures/virtual/proc_self_status/vserver_2_1/host b/spec/fixtures/virtual/proc_self_status/vserver_2_1/host new file mode 100644 index 0000000000..61a0845b7a --- /dev/null +++ b/spec/fixtures/virtual/proc_self_status/vserver_2_1/host @@ -0,0 +1,36 @@ +Name: cat +State: R (running) +SleepAVG: 88% +Tgid: 24625 +Pid: 24625 +PPid: 24618 +TracerPid: 0 +Uid: 47000 47000 47000 47000 +Gid: 4733 4733 4733 4733 +FDSize: 32 +Groups: 0 4733 +VmPeak: 1768 kB +VmSize: 1768 kB +VmLck: 0 kB +VmHWM: 396 kB +VmRSS: 396 kB +VmData: 160 kB +VmStk: 88 kB +VmExe: 28 kB +VmLib: 1468 kB +VmPTE: 12 kB +Threads: 1 +SigQ: 0/4294967295 +SigPnd: 0000000000000000 +ShdPnd: 0000000000000000 +SigBlk: 0000000000000000 +SigIgn: 0000000000000000 +SigCgt: 0000000000000000 +CapInh: 0000000000000000 +CapPrm: 0000000000000000 +CapEff: 0000000000000000 +s_context: 0 +ctxflags: none +initpid: none +ipv4root: 0 +ipv4root_bcast: 0 diff --git a/spec/fixtures/virtual/proc_self_status/vserver_2_3/guest b/spec/fixtures/virtual/proc_self_status/vserver_2_3/guest new file mode 100644 index 0000000000..7567c38dea --- /dev/null +++ b/spec/fixtures/virtual/proc_self_status/vserver_2_3/guest @@ -0,0 +1,39 @@ +Name: cat +State: R (running) +Tgid: 21149 +Pid: 21149 +PPid: 21142 +TracerPid: 0 +Uid: 0 0 0 0 +Gid: 0 0 0 0 +FDSize: 64 +Groups: 0 +VmPeak: 1564 kB +VmSize: 1564 kB +VmLck: 0 kB +VmHWM: 384 kB +VmRSS: 384 kB +VmData: 160 kB +VmStk: 84 kB +VmExe: 16 kB +VmLib: 1284 kB +VmPTE: 20 kB +Threads: 1 +SigQ: 0/71680 +SigPnd: 0000000000000000 +ShdPnd: 0000000000000000 +SigBlk: 0000000000000000 +SigIgn: 0000000000000000 +SigCgt: 0000000000000000 +CapInh: 0000000000000000 +CapPrm: fffffffffffffeff +CapEff: fffffffffffffeff +CapBnd: fffffffffffffeff +Cpus_allowed: ff +Cpus_allowed_list: 0-7 +Mems_allowed: 1 +Mems_allowed_list: 0 +VxID: 40128 +NxID: 40128 +voluntary_ctxt_switches: 1 +nonvoluntary_ctxt_switches: 0 diff --git a/spec/fixtures/virtual/proc_self_status/vserver_2_3/host b/spec/fixtures/virtual/proc_self_status/vserver_2_3/host new file mode 100644 index 0000000000..15b17e9ec7 --- /dev/null +++ b/spec/fixtures/virtual/proc_self_status/vserver_2_3/host @@ -0,0 +1,39 @@ +Name: cat +State: R (running) +Tgid: 21074 +Pid: 21074 +PPid: 21020 +TracerPid: 0 +Uid: 47000 47000 47000 47000 +Gid: 4733 4733 4733 4733 +FDSize: 64 +Groups: 0 4733 +VmPeak: 3800 kB +VmSize: 3800 kB +VmLck: 0 kB +VmHWM: 468 kB +VmRSS: 468 kB +VmData: 176 kB +VmStk: 84 kB +VmExe: 32 kB +VmLib: 1432 kB +VmPTE: 28 kB +Threads: 1 +SigQ: 0/71680 +SigPnd: 0000000000000000 +ShdPnd: 0000000000000000 +SigBlk: 0000000000000000 +SigIgn: 0000000000000000 +SigCgt: 0000000000000000 +CapInh: 0000000000000000 +CapPrm: 0000000000000000 +CapEff: 0000000000000000 +CapBnd: fffffffffffffeff +Cpus_allowed: ff +Cpus_allowed_list: 0-7 +Mems_allowed: 1 +Mems_allowed_list: 0 +VxID: 0 +NxID: 0 +voluntary_ctxt_switches: 2 +nonvoluntary_ctxt_switches: 0 diff --git a/spec/unit/util/virtual.rb b/spec/unit/util/virtual.rb index 1e31a2f748..2477ad6179 100644 --- a/spec/unit/util/virtual.rb +++ b/spec/unit/util/virtual.rb @@ -57,6 +57,26 @@ Facter::Util::Virtual.should_not be_vserver end + fixture_path = File.join(SPECDIR, 'fixtures', 'virtual', 'proc_self_status') + + test_cases = [ + [File.join(fixture_path, 'vserver_2_1', 'guest'), true, 'vserver 2.1 guest'], + [File.join(fixture_path, 'vserver_2_1', 'host'), true, 'vserver 2.1 host'], + [File.join(fixture_path, 'vserver_2_3', 'guest'), true, 'vserver 2.3 guest'], + [File.join(fixture_path, 'vserver_2_3', 'host'), true, 'vserver 2.3 host'] + ] + + test_cases.each do |status_file, expected, description| + context "with /proc/self/status from #{description}" do + it "should detect vserver as #{expected.inspect}" do + status = File.read(status_file) + FileTest.stubs(:exists?).with("/proc/self/status").returns(true) + File.stubs(:read).with("/proc/self/status").returns(status) + Facter::Util::Virtual.vserver?.should == expected + end + end + end + it "should identify vserver_host when /proc/virtual exists" do Facter::Util::Virtual.expects(:vserver?).returns(true) FileTest.stubs(:exists?).with("/proc/virtual").returns(true) From b6c0a6b717e85cae813275b9feaeb409b8701c5b Mon Sep 17 00:00:00 2001 From: William Van Hevelingen Date: Wed, 18 Aug 2010 15:40:24 -0700 Subject: [PATCH 0464/3753] Fix for bug #4569 * getTickCount.call() is not an epoch time value so compute_uptime is not necessary Signed-off-by: William Van Hevelingen --- lib/facter/util/uptime.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/util/uptime.rb b/lib/facter/util/uptime.rb index 353ebf5e8f..b62d7e9993 100644 --- a/lib/facter/util/uptime.rb +++ b/lib/facter/util/uptime.rb @@ -10,7 +10,7 @@ def self.get_uptime_seconds_unix def self.get_uptime_seconds_win require 'Win32API' getTickCount = Win32API.new("kernel32", "GetTickCount", nil, 'L') - compute_uptime(Time.at(getTickCount.call() / 1000.0)) + (getTickCount.call() / 1000.0).to_i end private From 01a515fc4d6af84fa99e42475a95d846e09343d0 Mon Sep 17 00:00:00 2001 From: Rein Henrichs Date: Wed, 18 Aug 2010 15:50:39 -0700 Subject: [PATCH 0465/3753] [#4289] operatingsystemrelease fact for oel, ovs When Facter returns operatingsystem as "oel" or "ovs", the operatingsystemrelease fact does not catch these properly, causing an error. Specs added to catch failure and case statement updated to catch "oel" and "ovs" as well as "OEL" and "OVS" --- lib/facter/operatingsystemrelease.rb | 4 +-- spec/unit/operatingsystemrelease.rb | 39 ++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 spec/unit/operatingsystemrelease.rb diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index 30f2989ace..280208ba50 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -8,9 +8,9 @@ releasefile = "/etc/fedora-release" when "MeeGo" releasefile = "/etc/meego-release" - when "OEL" + when "OEL", "oel" releasefile = "/etc/enterprise-release" - when "OVS" + when "OVS", "ovs" releasefile = "/etc/ovs-release" end File::open(releasefile, "r") do |f| diff --git a/spec/unit/operatingsystemrelease.rb b/spec/unit/operatingsystemrelease.rb new file mode 100644 index 0000000000..31d4ae8f76 --- /dev/null +++ b/spec/unit/operatingsystemrelease.rb @@ -0,0 +1,39 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../spec_helper' + +require 'facter' + +describe "Operating System Release fact" do + + before do + Facter.clear + end + + after do + Facter.clear + end + + test_cases = { + "CentOS" => "/etc/redhat-release", + "RedHat" => "/etc/redhat-release", + "Fedora" => "/etc/fedora-release", + "MeeGo" => "/etc/meego-release", + "OEL" => "/etc/enterprise-release", + "oel" => "/etc/enterprise-release", + "OVS" => "/etc/ovs-release", + "ovs" => "/etc/ovs-release" + } + + test_cases.each do |system, file| + context "with operatingsystem reported as #{system.inspect}" do + it "should read the #{file.inspect} file" do + Facter.fact(:operatingsystem).stubs(:value).returns(system) + + File.expects(:open).with(file, "r").at_least(1) + + Facter.fact(:operatingsystemrelease).value + end + end + end +end From 9c9cabd7ecb458643447d60d56e2fefe947f23c2 Mon Sep 17 00:00:00 2001 From: William Van Hevelingen Date: Thu, 19 Aug 2010 01:41:13 -0700 Subject: [PATCH 0466/3753] Better fix for Bug 4569: Uptime Fact is incorrect on Windows Patch removes reliance on clock ticks and instead queries for last boot time and subtracts from Time.now Signed-off-by: William Van Hevelingen --- lib/facter/util/uptime.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/facter/util/uptime.rb b/lib/facter/util/uptime.rb index b62d7e9993..6c60aceec7 100644 --- a/lib/facter/util/uptime.rb +++ b/lib/facter/util/uptime.rb @@ -8,9 +8,12 @@ def self.get_uptime_seconds_unix end def self.get_uptime_seconds_win - require 'Win32API' - getTickCount = Win32API.new("kernel32", "GetTickCount", nil, 'L') - (getTickCount.call() / 1000.0).to_i + require 'win32ole' + wmi = WIN32OLE.connect("winmgmts://") + query = wmi.ExecQuery("select * from Win32_OperatingSystem") + last_boot = "" + query.each { |x| last_boot = x.LastBootupTime} + self.compute_uptime(Time.parse(last_boot.split('.').first)) end private From e7df4c0f60b9ddfbd3e39e5c693c85e54ce65db1 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sat, 21 Aug 2010 02:12:11 +1000 Subject: [PATCH 0467/3753] Updated CHANGELOG for 1.5.8rc2 --- CHANGELOG | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 445a3ab0d7..c8c7d2f5d6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,15 @@ +1.5.8rc2 +======== +9c9cabd Better fix for Bug 4569: Uptime Fact is incorrect on Windows +01a515f [#4289] operatingsystemrelease fact for oel, ovs +b6c0a6b Fix for bug #4569 +51bcebe Fixed Rakefile package task version detection +81ccb48 Removed references to Reductive Labs in the Rakefile + 1.5.8rc1 ======== +f280703 Incremented version to 1.5.8 +98ef5e8 Updated CHANGELOG for 1.5.8rc1 4398b36 Updated CHANGELOG rake task e02be1d [#4156] Updating spec to match Kai's change bff84c2 [#4156] Applying patch by Kai From 32c0cb002a27e3defca17822c9688c8206b931ab Mon Sep 17 00:00:00 2001 From: Rein Henrichs Date: Mon, 23 Aug 2010 14:55:39 -0700 Subject: [PATCH 0468/3753] [#4594] Revert "fixes #2573, #2085, #1291..." This is a revert of ticket #2573, which introduces a reverse DNS lookup to the resolution of fqdn. As per #3898, this does not corrently return the fqdn on all systems. This reverts commit dca615c98b864d75e2ac5899d98d04a2bd979eba. --- lib/facter/domain.rb | 8 ++++---- lib/facter/fqdn.rb | 4 ---- lib/facter/hostname.rb | 7 +++---- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/lib/facter/domain.rb b/lib/facter/domain.rb index 5dfead0d41..b1bba4d698 100644 --- a/lib/facter/domain.rb +++ b/lib/facter/domain.rb @@ -4,14 +4,14 @@ # steps is important Facter.value(:hostname) - # try to fetch the domain from hostname if long hostname is used. - if defined? $fqdn and $fqdn =~ /^([\w-]+)\.(.+)$/ - next $2 - end + next $domain if defined? $domain and ! $domain.nil? domain = Facter::Util::Resolution.exec('dnsdomainname') next domain if domain =~ /.+\..+/ + domain = Facter::Util::Resolution.exec('domainname') + next domain if domain =~ /.+\..+/ + if FileTest.exists?("/etc/resolv.conf") domain = nil search = nil diff --git a/lib/facter/fqdn.rb b/lib/facter/fqdn.rb index 6271995cb3..5ebc5f5c0b 100644 --- a/lib/facter/fqdn.rb +++ b/lib/facter/fqdn.rb @@ -1,9 +1,5 @@ Facter.add(:fqdn) do setcode do - # try to fetch the fqdn from hostname if long hostname is used. - Facter.value(:hostname) - next $fqdn if defined? $fqdn and ! $fqdn.nil? - host = Facter.value(:hostname) domain = Facter.value(:domain) if host and domain diff --git a/lib/facter/hostname.rb b/lib/facter/hostname.rb index c3ca968d87..188efa4cda 100644 --- a/lib/facter/hostname.rb +++ b/lib/facter/hostname.rb @@ -1,13 +1,12 @@ Facter.add(:hostname, :ldapname => "cn") do setcode do - require 'socket' hostname = nil - name = Socket.gethostbyname(Socket.gethostname).first + name = Facter::Util::Resolution.exec('hostname') or nil if name if name =~ /^([\w-]+)\.(.+)$/ hostname = $1 - # the FQDN/Domain facts use this - $fqdn = name + # the Domain class uses this + $domain = $2 else hostname = name end From fca886136ab103bba5aa851687f7f97a6b95f8bd Mon Sep 17 00:00:00 2001 From: Rein Henrichs Date: Tue, 24 Aug 2010 12:16:25 -0700 Subject: [PATCH 0469/3753] [#4594] Reintroduce fix for #1291 from original patch The reverted patch contains a fix for #1291 that is desired. This commit reintroduces that change. --- lib/facter/domain.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/facter/domain.rb b/lib/facter/domain.rb index b1bba4d698..29bb204d52 100644 --- a/lib/facter/domain.rb +++ b/lib/facter/domain.rb @@ -9,9 +9,6 @@ domain = Facter::Util::Resolution.exec('dnsdomainname') next domain if domain =~ /.+\..+/ - domain = Facter::Util::Resolution.exec('domainname') - next domain if domain =~ /.+\..+/ - if FileTest.exists?("/etc/resolv.conf") domain = nil search = nil From 3671c9fce6cd6bbedf206f9c1c3611468b7887da Mon Sep 17 00:00:00 2001 From: Rein Henrichs Date: Tue, 24 Aug 2010 14:22:35 -0700 Subject: [PATCH 0470/3753] [#4583] Refactor uptime to use Resolution.exec Resolution.exec used to ensure that any shell errors are suppressed. --- lib/facter/util/uptime.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/facter/util/uptime.rb b/lib/facter/util/uptime.rb index 6c60aceec7..9a59d303b2 100644 --- a/lib/facter/util/uptime.rb +++ b/lib/facter/util/uptime.rb @@ -19,19 +19,19 @@ def self.get_uptime_seconds_win private def self.uptime_proc_uptime - if output = `/bin/cat #{uptime_file} 2>/dev/null` and $?.success? + if output = Facter::Util::Resolution.exec("/bin/cat #{uptime_file} 2>/dev/null") output.chomp.split(" ").first.to_i end end def self.uptime_sysctl - if output = `#{uptime_sysctl_cmd} 2>/dev/null` and $?.success? + if output = Facter::Util::Resolution.exec("#{uptime_sysctl_cmd} 2>/dev/null") compute_uptime(Time.at(output.unpack('L').first)) end end def self.uptime_who_dash_b - if output = `#{uptime_who_cmd} 2>/dev/null` and $?.success? + if output = Facter::Util::Resolution.exec("#{uptime_who_cmd} 2>/dev/null") compute_uptime(Time.parse(output)) end end From ca2da36988c4d9fb2afbfc3f15c295df9b8cd05f Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sat, 28 Aug 2010 09:55:52 +1000 Subject: [PATCH 0471/3753] Updated install.rb and created man page --- install.rb | 6 ------ man/man8/facter.8 | 54 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 6 deletions(-) create mode 100644 man/man8/facter.8 diff --git a/install.rb b/install.rb index a40e2927bd..bbf4007c8a 100755 --- a/install.rb +++ b/install.rb @@ -316,12 +316,6 @@ def build_man(bins) # Locate rst2man rst2man = %x{which rst2man.py} rst2man.chomp! - # Create puppet.conf.8 man page - %x{bin/puppetdoc --reference configuration > ./puppet.conf.rst} - %x{#{rst2man} ./puppet.conf.rst ./man/man8/puppet.conf.8} - File.unlink("./puppet.conf.rst") - - # Create binary man pages bins.each do |bin| b = bin.gsub( "bin/", "") %x{#{bin} --help > ./#{b}.rst} diff --git a/man/man8/facter.8 b/man/man8/facter.8 new file mode 100644 index 0000000000..ff24b890e2 --- /dev/null +++ b/man/man8/facter.8 @@ -0,0 +1,54 @@ +.TH "" "" "" +.SH NAME + \- +.\" Man page generated from reStructeredText. +. +.SH SYNOPSIS +.sp +Collect and display facts about the system. +.SH USAGE +.INDENT 0.0 +.INDENT 3.5 +.sp +facter [\-d|\-\-debug] [\-h|\-\-help] [\-p|\-\-puppet] [\-v|\-\-version] [\-y|\-\-yaml] [fact] [fact] [...] +.UNINDENT +.UNINDENT +.SH DESCRIPTION +.sp +Collect and display facts about the current system. The library behind +Facter is easy to expand, making Facter an easy way to collect +information about a system from within the shell or within Ruby. +.sp +If no facts are specifically asked for, then all facts will be returned. +.SH OPTIONS +.sp +debug: Enable debugging. +.sp +help: Print this help message +.INDENT 0.0 +.TP +.B puppet: Load the Puppet libraries, thus allowing Facter to load +. +Puppet\-specific facts. +.UNINDENT +.sp +version: Print the version and exit. +.sp +yaml: Emit facts in YAML format. +.SH EXAMPLE +.INDENT 0.0 +.INDENT 3.5 +.sp +facter kernel +.UNINDENT +.UNINDENT +.SH AUTHOR +.sp +Luke Kanies +.SH COPYRIGHT +.sp +Copyright (c) 2006 Reductive Labs, LLC Licensed under the GNU Public +License +.\" Generated by docutils manpage writer. +.\" +. From 3d287ac5e3393fa979d3494fc01e18370d0392f2 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sat, 28 Aug 2010 09:57:21 +1000 Subject: [PATCH 0472/3753] Updated CHANGELOG for 1.5.8 --- CHANGELOG | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index c8c7d2f5d6..13b23f07b2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,10 @@ -1.5.8rc2 -======== +1.5.8 +===== +ca2da36 Updated install.rb and created man page +3671c9f [#4583] Refactor uptime to use Resolution.exec +fca8861 [#4594] Reintroduce fix for #1291 from original patch +32c0cb0 [#4594] Revert "fixes #2573, #2085, #1291..." +e7df4c0 Updated CHANGELOG for 1.5.8rc2 9c9cabd Better fix for Bug 4569: Uptime Fact is incorrect on Windows 01a515f [#4289] operatingsystemrelease fact for oel, ovs b6c0a6b Fix for bug #4569 From ff473ef496e350459d0366b3713f45f74afbf9d7 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sat, 28 Aug 2010 10:00:07 +1000 Subject: [PATCH 0473/3753] Updated signing rake task --- tasks/rake/sign.rake | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tasks/rake/sign.rake b/tasks/rake/sign.rake index be5697ba2e..f0e9d83219 100644 --- a/tasks/rake/sign.rake +++ b/tasks/rake/sign.rake @@ -1,14 +1,14 @@ -desc "Sign the package with the Reductive Labs release key" +desc "Sign the package with the Puppet Labs release key" task :sign_packages do version = Facter::FACTERVERSION # Sign package -sh "gpg --homedir $HOME/release_key --detach-sign --output pkg/facter-#{version}.tar.gz.sign --armor pkg/facter-#{version}.tar.gz" +sh "gpg --homedir $HOME/pl_release_key --detach-sign --output pkg/facter-#{version}.tar.gz.sign --armor pkg/facter-#{version}.tar.gz" # Sign gem -sh "gpg --homedir $HOME/release_key --detach-sign --output pkg/facter-#{version}.gem.sign --armor pkg/facter-#{version}.gem" +sh "gpg --homedir $HOME/pl_release_key --detach-sign --output pkg/facter-#{version}.gem.sign --armor pkg/facter-#{version}.gem" end From 725dce0812831e8195b48143c2eac5bd57d8a819 Mon Sep 17 00:00:00 2001 From: Todd Zullinger Date: Sat, 28 Aug 2010 10:53:45 -0400 Subject: [PATCH 0474/3753] Rename Reductive Labs to Puppet Labs --- INSTALL | 2 +- README | 2 +- README.rst | 6 +++--- conf/redhat/facter.spec | 4 ++-- conf/solaris/pkginfo | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/INSTALL b/INSTALL index c9726642dd..7c82ecc7db 100644 --- a/INSTALL +++ b/INSTALL @@ -1,5 +1,5 @@ Run 'ruby install.rb' or use one of the distributed gem files at -http://reductivelabs.com/downloads/gems . +http://puppetlabs.com/downloads/gems . install.rb should successfully install; let me know if it doesn't. diff --git a/README b/README index 5866b7096f..683833f6aa 100644 --- a/README +++ b/README @@ -5,4 +5,4 @@ processors, etc. See bin/facter for an example of the interface. -See http://reductivelabs.com/projects/facter/ for more details. +See http://www.puppetlabs.com/puppet/related-projects/facter for more details. diff --git a/README.rst b/README.rst index 3b69d0910e..2b71136431 100644 --- a/README.rst +++ b/README.rst @@ -16,11 +16,11 @@ Run the ``facter`` binary on the command for a full list of facts supported on y Adding your own facts +++++++++++++++++++++ -See the `Adding Facts`_ wiki page for details of how to add your own custom facts to Facter. +See the `Adding Facts`_ page for details of how to add your own custom facts to Facter. Further Information +++++++++++++++++++ -See http://reductivelabs.com/projects/facter/ for more details. +See http://www.puppetlabs.com/puppet/related-projects/facter for more details. -.. _Adding Facts: http://reductivelabs.com/trac/puppet/wiki/AddingFacts +.. _Adding Facts: http://docs.puppetlabs.com/guides/custom_facts.html diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index b907d5b4e8..bdb7d43b3f 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -9,8 +9,8 @@ Version: 1.5.5 Release: 1%{?dist} License: GPLv2+ Group: System Environment/Base -URL: http://reductivelabs.com/projects/facter -Source0: http://reductivelabs.com/downloads/facter/%{name}-%{version}.tar.gz +URL: http://www.puppetlabs.com/puppet/related-projects/%{name}/ +Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}.tar.gz BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) %if %has_ruby_noarch BuildArch: noarch diff --git a/conf/solaris/pkginfo b/conf/solaris/pkginfo index 05eef1fa28..968a459675 100644 --- a/conf/solaris/pkginfo +++ b/conf/solaris/pkginfo @@ -2,6 +2,6 @@ PKG=CSWfacter NAME=facter - System Fact Gatherer VERSION=1.3.5 CATEGORY=application -VENDOR=http://reductivelabs.com/projects/facter -HOTLINE=http://reductivelabs.com/cgi-bin/facter.cgi +VENDOR=http://www.puppetlabs.com/puppet/related-projects/facter +HOTLINE=http://puppetlabs.com/cgi-bin/facter.cgi EMAIL=luke@madstop.com From 889e1504c101b36741141ef95cf5cbdfedd95e56 Mon Sep 17 00:00:00 2001 From: Todd Zullinger Date: Sat, 28 Aug 2010 10:56:06 -0400 Subject: [PATCH 0475/3753] Sync rpm spec file from Fedora/EPEL --- conf/redhat/facter.spec | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index bdb7d43b3f..1148fd3edd 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -5,12 +5,14 @@ Summary: Ruby module for collecting simple facts about a host operating system Name: facter -Version: 1.5.5 +Version: 1.5.8 Release: 1%{?dist} License: GPLv2+ Group: System Environment/Base URL: http://www.puppetlabs.com/puppet/related-projects/%{name}/ Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}.tar.gz +Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}.tar.gz.sign + BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) %if %has_ruby_noarch BuildArch: noarch @@ -50,6 +52,19 @@ rm -rf %{buildroot} %changelog +* Sat Aug 28 2010 Todd Zullinger - 1.5.8-1 +- Update to 1.5.8 + +* Fri Sep 25 2009 Todd Zullinger - 1.5.7-1 +- Update to 1.5.7 +- Update #508037 patch from upstream ticket + +* Wed Aug 12 2009 Jeroen van Meeuwen - 1.5.5-3 +- Fix #508037 or upstream #2355 + +* Fri Jul 24 2009 Fedora Release Engineering - 1.5.5-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_12_Mass_Rebuild + * Fri May 22 2009 Todd Zullinger - 1.5.5-1 - Update to 1.5.5 - Drop upstreamed libperms patch @@ -58,6 +73,9 @@ rm -rf %{buildroot} - New version - Use upstream install script +* Tue Feb 24 2009 Fedora Release Engineering - 1.5.2-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_11_Mass_Rebuild + * Tue Sep 09 2008 Todd Zullinger - 1.5.2-1 - New version - Simplify spec file checking for Fedora and RHEL versions From 1125e1e050a3807df2ada4d8cf2d36c22a75ed2b Mon Sep 17 00:00:00 2001 From: Hans de Graaff Date: Sat, 18 Sep 2010 17:31:11 +0200 Subject: [PATCH 0476/3753] Make sure FreeBSD spec also works on systems that have /proc/cpuinfo. --- spec/unit/util/virtual.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/unit/util/virtual.rb b/spec/unit/util/virtual.rb index 1e31a2f748..cc528d122e 100644 --- a/spec/unit/util/virtual.rb +++ b/spec/unit/util/virtual.rb @@ -101,6 +101,7 @@ end it "should detect kvm on FreeBSD" do + FileTest.stubs(:exists?).with("/proc/cpuinfo").returns(false) Facter.fact(:kernel).stubs(:value).returns("FreeBSD") Facter::Util::Resolution.stubs(:exec).with("/sbin/sysctl -n hw.model").returns("QEMU Virtual CPU version 0.12.4") Facter::Util::Virtual.should be_kvm From 7ecba715c4b19b2ab1f181b2b1bad256b2cdf512 Mon Sep 17 00:00:00 2001 From: Rein Henrichs Date: Mon, 4 Oct 2010 13:47:39 -0700 Subject: [PATCH 0477/3753] (#4567) Retain detached HEAD state Allow facter-diff to start in and return to a detached HEAD --- ext/facter-diff | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/facter-diff b/ext/facter-diff index 361b80b682..d74f3167c7 100755 --- a/ext/facter-diff +++ b/ext/facter-diff @@ -49,9 +49,9 @@ status=$(git status -s) test -z "$status" || die "fatal: $0 cannot be used with a dirty working copy" -origin=$(git name-rev --name-only HEAD) -test -n "$origin" || - die "fatal: $0 cannot be used unless currently on a branch" +origin=$(git rev-parse --symbolic-full-name HEAD) +test "$origin" = "HEAD" && + origin=$(git rev-parse HEAD) test -x "bin/facter" || die "fatal: $0 must be run from the project root" From b8b7123a3170afd73723ea386b95ba01c0f23279 Mon Sep 17 00:00:00 2001 From: Rein Henrichs Date: Mon, 4 Oct 2010 13:49:19 -0700 Subject: [PATCH 0478/3753] (#4567) Remove unnecessary or non-portable redirects - Remove &> redirects, which are bash 4 only - Use git checkout -q instead of redirects to preserve fatal errors --- ext/facter-diff | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ext/facter-diff b/ext/facter-diff index d74f3167c7..66163183e9 100755 --- a/ext/facter-diff +++ b/ext/facter-diff @@ -1,3 +1,4 @@ + #!/usr/bin/env sh # # Output the difference between a facter command run on two different versions @@ -18,7 +19,7 @@ eval "$(echo "$OPTIONS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?) trap 'err=$?; cleanup; exit $err' 0 cleanup() { - test $origin && git checkout "$origin" &> /dev/null + test $origin && git checkout -q "$origin" } facter() { @@ -29,7 +30,7 @@ log_facter_run() { local ref=$1 && shift local tmpfile=$1 && shift - git checkout -f "$ref" &> /dev/null || + git checkout -qf "$ref" || die "fatal: unable to checkout $ref" facter "$@" > $tmpfile } @@ -68,6 +69,6 @@ tmpfile2="/tmp/$(basename $0).$$.2.tmp" log_facter_run $ref1 $tmpfile1 $@ log_facter_run $ref2 $tmpfile2 $@ -git checkout -f "$origin" &> /dev/null +git checkout -f "$origin" > /dev/null 2>&1 diff --label "$ref1" --label "$ref2" -u $tmpfile1 $tmpfile2 From ebcb81be7408cfb8bd2bcb86f98ae6a98a6c70a5 Mon Sep 17 00:00:00 2001 From: Rein Henrichs Date: Tue, 17 Aug 2010 16:22:43 -0700 Subject: [PATCH 0479/3753] [#4558] Refactor facter binary using optparse Simplify the binary by moving all application specific code into a new Facter::Application module. This module is then refactored to use OptionParser and to simplify invocation logic, while maintaining existing behavior. --- bin/facter | 132 +++----------------------------------- lib/facter/application.rb | 99 ++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 123 deletions(-) create mode 100644 lib/facter/application.rb diff --git a/bin/facter b/bin/facter index a6cb717e75..e9dfc7c1c9 100755 --- a/bin/facter +++ b/bin/facter @@ -18,11 +18,8 @@ # # = Options # -# debug:: -# Enable debugging. -# -# help:: -# Print this help message +# yaml:: +# Emit facts in YAML format. # # puppet:: # Load the Puppet libraries, thus allowing Facter to load Puppet-specific facts. @@ -30,8 +27,11 @@ # version:: # Print the version and exit. # -# yaml:: -# Emit facts in YAML format. +# help:: +# Print this help message. +# +# debug:: +# Enable debugging. # # = Example # @@ -46,120 +46,6 @@ # Copyright (c) 2006 Reductive Labs, LLC # Licensed under the GNU Public License -require 'getoptlong' -require 'facter' - -$haveusage = true - -begin - require 'rdoc/ri/ri_paths' - require 'rdoc/usage' -rescue Exception - $haveusage = false -end - -def load_puppet - require 'puppet' - Puppet.parse_config - - # If you've set 'vardir' but not 'libdir' in your - # puppet.conf, then the hook to add libdir to $: - # won't get triggered. This makes sure that it's setup - # correctly. - unless $LOAD_PATH.include?(Puppet[:libdir]) - $LOAD_PATH << Puppet[:libdir] - end -end - -$debug = 0 - -config = nil - -result = GetoptLong.new( - [ "--version", "-v", GetoptLong::NO_ARGUMENT ], - [ "--help", "-h", GetoptLong::NO_ARGUMENT ], - [ "--debug", "-d", GetoptLong::NO_ARGUMENT ], - [ "--yaml", "-y", GetoptLong::NO_ARGUMENT ], - [ "--config", "-c", GetoptLong::REQUIRED_ARGUMENT ], - [ "--puppet", "-p", GetoptLong::NO_ARGUMENT ] -) - -options = { - :yaml => false -} - -begin - result.each { |opt,arg| - case opt - when "--version" - puts "%s" % Facter.version - exit - when "--puppet" - begin - load_puppet() - rescue LoadError => detail - $stderr.puts "Could not load Puppet: %s" % detail - end - when "--yaml" - options[:yaml] = true - when "--debug" - Facter.debugging(1) - when "--help" - if $haveusage - RDoc::usage && exit - else - puts "No help available unless you have RDoc::usage installed" - exit - end - else - $stderr.puts "Invalid option '#{opt}'" - exit(12) - end - } -rescue - exit(12) -end - -names = [] - -unless config.nil? - File.open(config) { |file| - names = file.readlines.collect { |line| - line.chomp - } - } -end - -ARGV.each { |item| - names.push item -} - -facts = Facter.to_hash - -unless names.empty? - facts = {} - names.each { |name| - begin - facts[name] = Facter.value(name) - rescue => error - STDERR.puts "Could not retrieve %s: #{error}" % name - exit 10 - end - } -end - -if options[:yaml] - require 'yaml' - puts YAML.dump(facts) - exit(0) -end +require 'facter/application' -facts.sort { |a, b| a[0].to_s <=> b[0].to_s }.each { |name,value| - if facts.length == 1 - unless value.nil? - puts value - end - else - puts "%s => %s" % [name,value] - end -} +Facter::Application.run(ARGV) diff --git a/lib/facter/application.rb b/lib/facter/application.rb new file mode 100644 index 0000000000..51dbd14a41 --- /dev/null +++ b/lib/facter/application.rb @@ -0,0 +1,99 @@ +module Facter + module Application + def self.run(argv) + require 'optparse' + require 'facter' + + options = parse(argv) + + # Accept fact names to return from the command line + names = argv + + # Create the facts hash that is printed to standard out + if names.empty? + facts = Facter.to_hash + else + facts = {} + names.each { |name| + begin + facts[name] = Facter.value(name) + rescue => error + $stderr.puts "Could not retrieve #{name}: #{error}" + exit 10 + end + } + end + + # Print the facts as YAML and exit + if options[:yaml] + require 'yaml' + puts YAML.dump(facts) + exit(0) + end + + # Print the value of a single fact, otherwise print a list sorted by fact + # name and separated by "=>" + if facts.length == 1 + if value = facts.values.first + puts value + end + else + facts.sort_by{ |fact| fact.first }.each do |name,value| + puts "#{name} => #{value}" + end + end + + rescue => e + $stderr.puts "Error: #{e}" + exit(12) + end + + private + + def self.parse(argv) + options = {} + OptionParser.new do |opts| + opts.on("-y", "--yaml") { |v| options[:yaml] = v } + + opts.on("-d", "--debug") { |v| Facter.debugging(1) } + opts.on("-p", "--puppet") { |v| load_puppet } + + opts.on_tail("-v", "--version") do + puts Facter.version + exit(0) + end + + opts.on_tail("-h", "--help") do + begin + require 'rdoc/ri/ri_paths' + require 'rdoc/usage' + puts RDoc.usage + ensure + exit + end + end + end.parse! + + options + rescue OptionParser::InvalidOption => e + $stderr.puts e.message + exit(12) + end + + def self.load_puppet + require 'puppet' + Puppet.parse_config + + # If you've set 'vardir' but not 'libdir' in your + # puppet.conf, then the hook to add libdir to $: + # won't get triggered. This makes sure that it's setup + # correctly. + unless $LOAD_PATH.include?(Puppet[:libdir]) + $LOAD_PATH << Puppet[:libdir] + end + rescue LoadError => detail + $stderr.puts "Could not load Puppet: #{detail}" + end + + end +end From b5c85de39edbb083d562dafd96fe507fbcd2f43d Mon Sep 17 00:00:00 2001 From: Rein Henrichs Date: Tue, 17 Aug 2010 18:02:07 -0700 Subject: [PATCH 0480/3753] [#4563] Add a --trace option to the binary --- bin/facter | 3 +++ lib/facter/application.rb | 9 +++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/bin/facter b/bin/facter index e9dfc7c1c9..328d5fc36d 100755 --- a/bin/facter +++ b/bin/facter @@ -33,6 +33,9 @@ # debug:: # Enable debugging. # +# trace:: +# Enable backtraces. +# # = Example # # facter kernel diff --git a/lib/facter/application.rb b/lib/facter/application.rb index 51dbd14a41..b80d07c2b8 100644 --- a/lib/facter/application.rb +++ b/lib/facter/application.rb @@ -44,8 +44,12 @@ def self.run(argv) end rescue => e - $stderr.puts "Error: #{e}" - exit(12) + if options && options[:trace] + raise e + else + $stderr.puts "Error: #{e}" + exit(12) + end end private @@ -54,6 +58,7 @@ def self.parse(argv) options = {} OptionParser.new do |opts| opts.on("-y", "--yaml") { |v| options[:yaml] = v } + opts.on( "--trace") { |v| options[:trace] = v } opts.on("-d", "--debug") { |v| Facter.debugging(1) } opts.on("-p", "--puppet") { |v| load_puppet } From 7210429e2adb35794fb334196a6141b71f519fbc Mon Sep 17 00:00:00 2001 From: Rein Henrichs Date: Tue, 17 Aug 2010 16:22:43 -0700 Subject: [PATCH 0481/3753] [#4558] Refactor facter binary using optparse Simplify the binary by moving all application specific code into a new Facter::Application module. This module is then refactored to use OptionParser and to simplify invocation logic, while maintaining existing behavior. --- bin/facter | 132 +++----------------------------------- lib/facter/application.rb | 99 ++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 123 deletions(-) create mode 100644 lib/facter/application.rb diff --git a/bin/facter b/bin/facter index a6cb717e75..e9dfc7c1c9 100755 --- a/bin/facter +++ b/bin/facter @@ -18,11 +18,8 @@ # # = Options # -# debug:: -# Enable debugging. -# -# help:: -# Print this help message +# yaml:: +# Emit facts in YAML format. # # puppet:: # Load the Puppet libraries, thus allowing Facter to load Puppet-specific facts. @@ -30,8 +27,11 @@ # version:: # Print the version and exit. # -# yaml:: -# Emit facts in YAML format. +# help:: +# Print this help message. +# +# debug:: +# Enable debugging. # # = Example # @@ -46,120 +46,6 @@ # Copyright (c) 2006 Reductive Labs, LLC # Licensed under the GNU Public License -require 'getoptlong' -require 'facter' - -$haveusage = true - -begin - require 'rdoc/ri/ri_paths' - require 'rdoc/usage' -rescue Exception - $haveusage = false -end - -def load_puppet - require 'puppet' - Puppet.parse_config - - # If you've set 'vardir' but not 'libdir' in your - # puppet.conf, then the hook to add libdir to $: - # won't get triggered. This makes sure that it's setup - # correctly. - unless $LOAD_PATH.include?(Puppet[:libdir]) - $LOAD_PATH << Puppet[:libdir] - end -end - -$debug = 0 - -config = nil - -result = GetoptLong.new( - [ "--version", "-v", GetoptLong::NO_ARGUMENT ], - [ "--help", "-h", GetoptLong::NO_ARGUMENT ], - [ "--debug", "-d", GetoptLong::NO_ARGUMENT ], - [ "--yaml", "-y", GetoptLong::NO_ARGUMENT ], - [ "--config", "-c", GetoptLong::REQUIRED_ARGUMENT ], - [ "--puppet", "-p", GetoptLong::NO_ARGUMENT ] -) - -options = { - :yaml => false -} - -begin - result.each { |opt,arg| - case opt - when "--version" - puts "%s" % Facter.version - exit - when "--puppet" - begin - load_puppet() - rescue LoadError => detail - $stderr.puts "Could not load Puppet: %s" % detail - end - when "--yaml" - options[:yaml] = true - when "--debug" - Facter.debugging(1) - when "--help" - if $haveusage - RDoc::usage && exit - else - puts "No help available unless you have RDoc::usage installed" - exit - end - else - $stderr.puts "Invalid option '#{opt}'" - exit(12) - end - } -rescue - exit(12) -end - -names = [] - -unless config.nil? - File.open(config) { |file| - names = file.readlines.collect { |line| - line.chomp - } - } -end - -ARGV.each { |item| - names.push item -} - -facts = Facter.to_hash - -unless names.empty? - facts = {} - names.each { |name| - begin - facts[name] = Facter.value(name) - rescue => error - STDERR.puts "Could not retrieve %s: #{error}" % name - exit 10 - end - } -end - -if options[:yaml] - require 'yaml' - puts YAML.dump(facts) - exit(0) -end +require 'facter/application' -facts.sort { |a, b| a[0].to_s <=> b[0].to_s }.each { |name,value| - if facts.length == 1 - unless value.nil? - puts value - end - else - puts "%s => %s" % [name,value] - end -} +Facter::Application.run(ARGV) diff --git a/lib/facter/application.rb b/lib/facter/application.rb new file mode 100644 index 0000000000..51dbd14a41 --- /dev/null +++ b/lib/facter/application.rb @@ -0,0 +1,99 @@ +module Facter + module Application + def self.run(argv) + require 'optparse' + require 'facter' + + options = parse(argv) + + # Accept fact names to return from the command line + names = argv + + # Create the facts hash that is printed to standard out + if names.empty? + facts = Facter.to_hash + else + facts = {} + names.each { |name| + begin + facts[name] = Facter.value(name) + rescue => error + $stderr.puts "Could not retrieve #{name}: #{error}" + exit 10 + end + } + end + + # Print the facts as YAML and exit + if options[:yaml] + require 'yaml' + puts YAML.dump(facts) + exit(0) + end + + # Print the value of a single fact, otherwise print a list sorted by fact + # name and separated by "=>" + if facts.length == 1 + if value = facts.values.first + puts value + end + else + facts.sort_by{ |fact| fact.first }.each do |name,value| + puts "#{name} => #{value}" + end + end + + rescue => e + $stderr.puts "Error: #{e}" + exit(12) + end + + private + + def self.parse(argv) + options = {} + OptionParser.new do |opts| + opts.on("-y", "--yaml") { |v| options[:yaml] = v } + + opts.on("-d", "--debug") { |v| Facter.debugging(1) } + opts.on("-p", "--puppet") { |v| load_puppet } + + opts.on_tail("-v", "--version") do + puts Facter.version + exit(0) + end + + opts.on_tail("-h", "--help") do + begin + require 'rdoc/ri/ri_paths' + require 'rdoc/usage' + puts RDoc.usage + ensure + exit + end + end + end.parse! + + options + rescue OptionParser::InvalidOption => e + $stderr.puts e.message + exit(12) + end + + def self.load_puppet + require 'puppet' + Puppet.parse_config + + # If you've set 'vardir' but not 'libdir' in your + # puppet.conf, then the hook to add libdir to $: + # won't get triggered. This makes sure that it's setup + # correctly. + unless $LOAD_PATH.include?(Puppet[:libdir]) + $LOAD_PATH << Puppet[:libdir] + end + rescue LoadError => detail + $stderr.puts "Could not load Puppet: #{detail}" + end + + end +end From 8c4d0cd4d25534f0f585773b6b5ff70b8a5617f9 Mon Sep 17 00:00:00 2001 From: Rein Henrichs Date: Mon, 4 Oct 2010 12:18:02 -0700 Subject: [PATCH 0482/3753] (#4558) Fail with message on --help errors If rdoc/usage fails to load, tell the user why and fail. If another failure happens, report the error message and fail. Paired With: Jacob Helwig Signed-off-by: Rein Henrichs --- lib/facter/application.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/facter/application.rb b/lib/facter/application.rb index 51dbd14a41..36d070be2c 100644 --- a/lib/facter/application.rb +++ b/lib/facter/application.rb @@ -68,8 +68,13 @@ def self.parse(argv) require 'rdoc/ri/ri_paths' require 'rdoc/usage' puts RDoc.usage - ensure exit + rescue LoadError + $stderr.puts "No help available unless your RDoc has RDoc.usage" + exit(1) + rescue => e + $stderr.puts "fatal: #{e}" + exit(1) end end end.parse! From e6bfdf9bf7a0d929dc7d882d3ea3bcb7372b75e0 Mon Sep 17 00:00:00 2001 From: William Van Hevelingen Date: Wed, 18 Aug 2010 15:40:24 -0700 Subject: [PATCH 0483/3753] Fix for bug #4569 * getTickCount.call() is not an epoch time value so compute_uptime is not necessary Signed-off-by: William Van Hevelingen --- lib/facter/util/uptime.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/util/uptime.rb b/lib/facter/util/uptime.rb index 353ebf5e8f..b62d7e9993 100644 --- a/lib/facter/util/uptime.rb +++ b/lib/facter/util/uptime.rb @@ -10,7 +10,7 @@ def self.get_uptime_seconds_unix def self.get_uptime_seconds_win require 'Win32API' getTickCount = Win32API.new("kernel32", "GetTickCount", nil, 'L') - compute_uptime(Time.at(getTickCount.call() / 1000.0)) + (getTickCount.call() / 1000.0).to_i end private From 11544c16e514353f5fde5361a55cf04feb91fc64 Mon Sep 17 00:00:00 2001 From: Rein Henrichs Date: Wed, 18 Aug 2010 15:50:39 -0700 Subject: [PATCH 0484/3753] [#4289] operatingsystemrelease fact for oel, ovs When Facter returns operatingsystem as "oel" or "ovs", the operatingsystemrelease fact does not catch these properly, causing an error. Specs added to catch failure and case statement updated to catch "oel" and "ovs" as well as "OEL" and "OVS" --- lib/facter/operatingsystemrelease.rb | 4 +-- spec/unit/operatingsystemrelease.rb | 39 ++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 spec/unit/operatingsystemrelease.rb diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index 30f2989ace..280208ba50 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -8,9 +8,9 @@ releasefile = "/etc/fedora-release" when "MeeGo" releasefile = "/etc/meego-release" - when "OEL" + when "OEL", "oel" releasefile = "/etc/enterprise-release" - when "OVS" + when "OVS", "ovs" releasefile = "/etc/ovs-release" end File::open(releasefile, "r") do |f| diff --git a/spec/unit/operatingsystemrelease.rb b/spec/unit/operatingsystemrelease.rb new file mode 100644 index 0000000000..31d4ae8f76 --- /dev/null +++ b/spec/unit/operatingsystemrelease.rb @@ -0,0 +1,39 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../spec_helper' + +require 'facter' + +describe "Operating System Release fact" do + + before do + Facter.clear + end + + after do + Facter.clear + end + + test_cases = { + "CentOS" => "/etc/redhat-release", + "RedHat" => "/etc/redhat-release", + "Fedora" => "/etc/fedora-release", + "MeeGo" => "/etc/meego-release", + "OEL" => "/etc/enterprise-release", + "oel" => "/etc/enterprise-release", + "OVS" => "/etc/ovs-release", + "ovs" => "/etc/ovs-release" + } + + test_cases.each do |system, file| + context "with operatingsystem reported as #{system.inspect}" do + it "should read the #{file.inspect} file" do + Facter.fact(:operatingsystem).stubs(:value).returns(system) + + File.expects(:open).with(file, "r").at_least(1) + + Facter.fact(:operatingsystemrelease).value + end + end + end +end From 244d2f13d0c911081d1e99365a1770f2022b839f Mon Sep 17 00:00:00 2001 From: William Van Hevelingen Date: Thu, 19 Aug 2010 01:41:13 -0700 Subject: [PATCH 0485/3753] Better fix for Bug 4569: Uptime Fact is incorrect on Windows Patch removes reliance on clock ticks and instead queries for last boot time and subtracts from Time.now Signed-off-by: William Van Hevelingen --- lib/facter/util/uptime.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/facter/util/uptime.rb b/lib/facter/util/uptime.rb index b62d7e9993..6c60aceec7 100644 --- a/lib/facter/util/uptime.rb +++ b/lib/facter/util/uptime.rb @@ -8,9 +8,12 @@ def self.get_uptime_seconds_unix end def self.get_uptime_seconds_win - require 'Win32API' - getTickCount = Win32API.new("kernel32", "GetTickCount", nil, 'L') - (getTickCount.call() / 1000.0).to_i + require 'win32ole' + wmi = WIN32OLE.connect("winmgmts://") + query = wmi.ExecQuery("select * from Win32_OperatingSystem") + last_boot = "" + query.each { |x| last_boot = x.LastBootupTime} + self.compute_uptime(Time.parse(last_boot.split('.').first)) end private From 1f387a5970d942fc297da791b1422adc80fc474a Mon Sep 17 00:00:00 2001 From: Rein Henrichs Date: Mon, 23 Aug 2010 16:13:21 -0700 Subject: [PATCH 0486/3753] [#4552] Apply patch from Dean Wilson --- bin/facter | 15 +++++++++------ lib/facter.rb | 31 +++++++++++++++++++++++++++++++ lib/facter/application.rb | 2 +- lib/facter/util/resolution.rb | 9 +++++++++ spec/unit/facter.rb | 27 +++++++++++++++++++++++++++ 5 files changed, 77 insertions(+), 7 deletions(-) diff --git a/bin/facter b/bin/facter index e9dfc7c1c9..4e09939a36 100755 --- a/bin/facter +++ b/bin/facter @@ -21,18 +21,21 @@ # yaml:: # Emit facts in YAML format. # +# debug:: +# Enable debugging. +# +# timing:: +# Enable timing. +# +# help:: +# Print this help message +# # puppet:: # Load the Puppet libraries, thus allowing Facter to load Puppet-specific facts. # # version:: # Print the version and exit. # -# help:: -# Print this help message. -# -# debug:: -# Enable debugging. -# # = Example # # facter kernel diff --git a/lib/facter.rb b/lib/facter.rb index 42d5371d3f..661831ff8b 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -48,6 +48,7 @@ module Util; end GREEN = "" RESET = "" @@debug = 0 + @@timing = 0 # module methods @@ -77,6 +78,20 @@ def self.debugging? @@debug != 0 end + # show the timing information + def self.show_time(string) + if string.nil? + return + end + if self.timing? + puts GREEN + string + RESET + end + end + + def self.timing? + @@timing != 0 + end + # Return a fact object by name. If you use this, you still have to call # 'value' on it to retrieve the actual value. def self.[](name) @@ -177,6 +192,22 @@ def self.debugging(bit) end end + # Set timing on or off. + def self.timing(bit) + if bit + case bit + when TrueClass; @@timing = 1 + when Fixnum + if bit > 0 + @@timing = 1 + else + @@timing = 0 + end + end + else + @@timing = 0 + end + end def self.warn(msg) if Facter.debugging? and msg and not msg.empty? diff --git a/lib/facter/application.rb b/lib/facter/application.rb index 36d070be2c..c222e7ae81 100644 --- a/lib/facter/application.rb +++ b/lib/facter/application.rb @@ -54,8 +54,8 @@ def self.parse(argv) options = {} OptionParser.new do |opts| opts.on("-y", "--yaml") { |v| options[:yaml] = v } - opts.on("-d", "--debug") { |v| Facter.debugging(1) } + opts.on("-t", "--timing") { |v| Facter.timing(1) } opts.on("-p", "--puppet") { |v| load_puppet } opts.on_tail("-v", "--version") do diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index f837f64e94..875b654abd 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -135,6 +135,9 @@ def to_s def value result = nil return result if @code == nil and @interpreter == nil + + starttime = Time.now.to_i + begin Timeout.timeout(limit) do if @code.is_a?(Proc) @@ -156,6 +159,12 @@ def value return nil end + finishtime = Time.now.to_i + + if Facter.timing? + Facter.show_time "Executing #{self.name} took #{finishtime - starttime} seconds" + end + return nil if result == "" return result end diff --git a/spec/unit/facter.rb b/spec/unit/facter.rb index 9f7b4cf9cf..20f9ed1625 100755 --- a/spec/unit/facter.rb +++ b/spec/unit/facter.rb @@ -135,6 +135,14 @@ Facter.should respond_to(:debugging?) end + it "should have a method to query timing mode" do + Facter.should respond_to(:timing?) + end + + it "should have a method to show timing information" do + Facter.should respond_to(:show_time) + end + it "should have a method to warn" do Facter.should respond_to(:warn) end @@ -204,6 +212,25 @@ end end + describe "when setting timing mode" do + it "should have timing enabled using 1" do + Facter.timing(1) + Facter.should be_timing + end + it "should have timing enabled using true" do + Facter.timing(true) + Facter.should be_timing + end + it "should have timing disabled using 0" do + Facter.timing(0) + Facter.should_not be_timing + end + it "should have timing disabled using false" do + Facter.timing(false) + Facter.should_not be_timing + end + end + describe "when registering directories to search" do after { Facter.instance_variable_set("@search_path", []) } From 07f186d5a858412c0608c368806825e1e44efd4c Mon Sep 17 00:00:00 2001 From: Rein Henrichs Date: Mon, 23 Aug 2010 16:28:27 -0700 Subject: [PATCH 0487/3753] [#4552] Updating --timing to report in milliseconds instead of seconds --- lib/facter.rb | 7 +------ lib/facter/util/resolution.rb | 10 ++++------ 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/lib/facter.rb b/lib/facter.rb index 661831ff8b..f48138ab06 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -80,12 +80,7 @@ def self.debugging? # show the timing information def self.show_time(string) - if string.nil? - return - end - if self.timing? - puts GREEN + string + RESET - end + puts "#{GREEN}#{string}#{RESET}" if string and Facter.timing? end def self.timing? diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 875b654abd..4a99c35236 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -136,7 +136,7 @@ def value result = nil return result if @code == nil and @interpreter == nil - starttime = Time.now.to_i + starttime = Time.now.to_f begin Timeout.timeout(limit) do @@ -159,11 +159,9 @@ def value return nil end - finishtime = Time.now.to_i - - if Facter.timing? - Facter.show_time "Executing #{self.name} took #{finishtime - starttime} seconds" - end + finishtime = Time.now.to_f + ms = (finishtime - starttime) * 1000 + Facter.show_time "#{self.name}: #{"%.2f" % ms}ms" return nil if result == "" return result From d62b01394888c0aefa87aa10f51d3bf21363300d Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Thu, 7 Oct 2010 09:12:28 -0700 Subject: [PATCH 0488/3753] Issue #4889 Fact values should all be strings Fix is_virtual fact to return strings rather than bools. --- lib/facter/virtual.rb | 4 ++-- spec/unit/virtual.rb | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index c14a715d62..af530e5e5a 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -81,9 +81,9 @@ setcode do case Facter.value(:virtual) when "xenu", "openvzve", "vmware", "kvm", "vserver", "jail" - true + "true" else - false + "false" end end end diff --git a/spec/unit/virtual.rb b/spec/unit/virtual.rb index 8ee843bb7b..80cd0d9530 100644 --- a/spec/unit/virtual.rb +++ b/spec/unit/virtual.rb @@ -35,37 +35,37 @@ it "should be virtual when running on xen" do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter.fact(:virtual).stubs(:value).returns("xenu") - Facter.fact(:is_virtual).value.should == true + Facter.fact(:is_virtual).value.should == "true" end it "should be false when running on xen0" do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter.fact(:virtual).stubs(:value).returns("xen0") - Facter.fact(:is_virtual).value.should == false + Facter.fact(:is_virtual).value.should == "false" end it "should be true when running on vmware" do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter.fact(:virtual).stubs(:value).returns("vmware") - Facter.fact(:is_virtual).value.should == true + Facter.fact(:is_virtual).value.should == "true" end it "should be true when running on openvz" do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter.fact(:virtual).stubs(:value).returns("openvzve") - Facter.fact(:is_virtual).value.should == true + Facter.fact(:is_virtual).value.should == "true" end it "should be true when running on kvm" do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter.fact(:virtual).stubs(:value).returns("kvm") - Facter.fact(:is_virtual).value.should == true + Facter.fact(:is_virtual).value.should == "true" end it "should be true when running in jail" do Facter.fact(:kernel).stubs(:value).returns("FreeBSD") Facter.fact(:virtual).stubs(:value).returns("jail") - Facter.fact(:is_virtual).value.should == true + Facter.fact(:is_virtual).value.should == "true" end end From f4da5285f1cedfed476eed37d40e1404e2801c48 Mon Sep 17 00:00:00 2001 From: Rein Henrichs Date: Thu, 7 Oct 2010 10:06:14 -0700 Subject: [PATCH 0489/3753] maint: Fix merge error --- lib/facter/application.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/facter/application.rb b/lib/facter/application.rb index 11a29c6e04..9de9249365 100644 --- a/lib/facter/application.rb +++ b/lib/facter/application.rb @@ -73,8 +73,6 @@ def self.parse(argv) require 'rdoc/ri/ri_paths' require 'rdoc/usage' puts RDoc.usage - ensure - exit rescue LoadError $stderr.puts "No help available unless your RDoc has RDoc.usage" exit(1) From f2e66b6f6828af18811294777408c88045ff93fd Mon Sep 17 00:00:00 2001 From: Paul Berry Date: Mon, 18 Oct 2010 14:36:34 -0700 Subject: [PATCH 0490/3753] (#5031) Remove redundant puts from RDoc.usage RDoc.usage prints the usage string. As a result, `puts RDoc.usage` is redundant and unnecessary. See http://ruby-doc.org/core/classes/RDoc.html#M004706. --- lib/facter/application.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/application.rb b/lib/facter/application.rb index 9de9249365..56827ec599 100644 --- a/lib/facter/application.rb +++ b/lib/facter/application.rb @@ -72,7 +72,7 @@ def self.parse(argv) begin require 'rdoc/ri/ri_paths' require 'rdoc/usage' - puts RDoc.usage + RDoc.usage # print usage and exit rescue LoadError $stderr.puts "No help available unless your RDoc has RDoc.usage" exit(1) From 7cec60af2fbbcb654b0b415fb441d0ee2f197699 Mon Sep 17 00:00:00 2001 From: Stefan Schulte Date: Wed, 3 Nov 2010 23:39:41 +0100 Subject: [PATCH 0491/3753] (#5016) is_virtual should be true on solaris zones While the fact virtual recognised solaris zones before, the is_virtual fact did not. With this patch applied is_virtual returns true on (non-global) zones --- lib/facter/virtual.rb | 2 +- spec/unit/virtual.rb | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index c14a715d62..c72b02b54a 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -80,7 +80,7 @@ setcode do case Facter.value(:virtual) - when "xenu", "openvzve", "vmware", "kvm", "vserver", "jail" + when "xenu", "openvzve", "vmware", "kvm", "vserver", "jail", "zone" true else false diff --git a/spec/unit/virtual.rb b/spec/unit/virtual.rb index 8ee843bb7b..5c6a4872cb 100644 --- a/spec/unit/virtual.rb +++ b/spec/unit/virtual.rb @@ -68,4 +68,10 @@ Facter.fact(:is_virtual).value.should == true end + it "should be true when running in zone" do + Facter.fact(:kernel).stubs(:value).returns("SunOS") + Facter.fact(:virtual).stubs(:value).returns("zone") + Facter.fact(:is_virtual).value.should == true + end + end From 43e203c688399ac86d337514aecb6fa0c9def683 Mon Sep 17 00:00:00 2001 From: Stefan Schulte Date: Wed, 3 Nov 2010 21:55:58 +0100 Subject: [PATCH 0492/3753] (#5040) fact virtual should detect hpvm With HP-UX you can build virtual machines that are often refered to as HP-VMs. This patch detecs HP-VMs by introducing a new function hpvm? that will check the output of /usr/bin/getconf MACHINE_MODEL. This should not depend on any tools that might be not installed. If inside a HP-VM the command will say something like ia64 hp server Integrity Virtual Machine while on real hardware the output could be ia64 hp server rx660 so searching for "Virtual Machine" should work. Currently it only works if the guest is also running HP-UX. (I guess this is the most common usecase). --- lib/facter/util/virtual.rb | 4 ++++ lib/facter/virtual.rb | 10 +++++++--- spec/unit/util/virtual.rb | 11 +++++++++++ spec/unit/virtual.rb | 11 +++++++++++ 4 files changed, 33 insertions(+), 3 deletions(-) diff --git a/lib/facter/util/virtual.rb b/lib/facter/util/virtual.rb index 5f70d4287b..5a03117967 100644 --- a/lib/facter/util/virtual.rb +++ b/lib/facter/util/virtual.rb @@ -62,4 +62,8 @@ def self.jail? Facter::Util::Resolution.exec("/sbin/sysctl -n security.jail.jailed") == "1" end + def self.hpvm? + Facter::Util::Resolution.exec("/usr/bin/getconf MACHINE_MODEL").chomp =~ /Virtual Machine/ + end + end diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index c14a715d62..faefb29a67 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -1,7 +1,7 @@ require 'facter/util/virtual' Facter.add("virtual") do - confine :kernel => %w{Linux FreeBSD OpenBSD SunOS} + confine :kernel => %w{Linux FreeBSD OpenBSD SunOS HP-UX} result = "physical" @@ -9,6 +9,10 @@ result = "zone" if Facter::Util::Virtual.zone? + if Facter.value(:kernel)=="HP-UX" + result = "hpvm" if Facter::Util::Virtual.hpvm? + end + if Facter::Util::Virtual.openvz? result = Facter::Util::Virtual.openvz_type() end @@ -76,11 +80,11 @@ end Facter.add("is_virtual") do - confine :kernel => %w{Linux FreeBSD OpenBSD SunOS} + confine :kernel => %w{Linux FreeBSD OpenBSD SunOS HP-UX} setcode do case Facter.value(:virtual) - when "xenu", "openvzve", "vmware", "kvm", "vserver", "jail" + when "xenu", "openvzve", "vmware", "kvm", "vserver", "jail", "zone", "hpvm" true else false diff --git a/spec/unit/util/virtual.rb b/spec/unit/util/virtual.rb index 1e31a2f748..57ef440320 100644 --- a/spec/unit/util/virtual.rb +++ b/spec/unit/util/virtual.rb @@ -116,4 +116,15 @@ Facter::Util::Virtual.should_not be_jail end + it "should detect hpvm on HP-UX" do + Facter.fact(:kernel).stubs(:value).returns("HP-UX") + Facter::Util::Resolution.stubs(:exec).with("/usr/bin/getconf MACHINE_MODEL").returns('ia64 hp server Integrity Virtual Machine') + Facter::Util::Virtual.should be_hpvm + end + + it "should not detect hpvm on HP-UX when not in hpvm" do + Facter.fact(:kernel).stubs(:value).returns("HP-UX") + Facter::Util::Resolution.stubs(:exec).with("/usr/bin/getconf MACHINE_MODEL").returns('ia64 hp server rx660') + Facter::Util::Virtual.should_not be_hpvm + end end diff --git a/spec/unit/virtual.rb b/spec/unit/virtual.rb index 8ee843bb7b..ee673beb55 100644 --- a/spec/unit/virtual.rb +++ b/spec/unit/virtual.rb @@ -24,6 +24,11 @@ Facter.fact(:virtual).value.should == "jail" end + it "should be hpvm on HP-UX when in HP-VM" do + Facter.fact(:kernel).stubs(:value).returns("HP-UX") + Facter::Util::Virtual.stubs(:hpvm?).returns(true) + Facter.fact(:virtual).value.should == "hpvm" + end end describe "is_virtual fact" do @@ -68,4 +73,10 @@ Facter.fact(:is_virtual).value.should == true end + it "should be true when running on hp-vm" do + Facter.fact(:kernel).stubs(:value).returns("HP-UX") + Facter.fact(:virtual).stubs(:value).returns("hpvm") + Facter.fact(:is_virtual).value.should == true + end + end From 1fa87a9c1ed9f9a841936615d27c61dc1d2d7fce Mon Sep 17 00:00:00 2001 From: "John E. Vincent" Date: Thu, 4 Nov 2010 00:07:06 -0400 Subject: [PATCH 0493/3753] JSON support. Works in 1.9.1. Warnings in 1.9.2. LoadError on 1.8.7 for some reason --- bin/facter | 5 ++++- lib/facter/application.rb | 13 +++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/bin/facter b/bin/facter index 9d3b49c615..622d72e42d 100755 --- a/bin/facter +++ b/bin/facter @@ -6,7 +6,7 @@ # # = Usage # -# facter [-d|--debug] [-h|--help] [-p|--puppet] [-v|--version] [-y|--yaml] [fact] [fact] [...] +# facter [-d|--debug] [-h|--help] [-p|--puppet] [-v|--version] [-y|--yaml] [-j|--json] [fact] [fact] [...] # # = Description # @@ -21,6 +21,9 @@ # yaml:: # Emit facts in YAML format. # +# json:: +# Emit facts in JSON format. +# # puppet:: # Load the Puppet libraries, thus allowing Facter to load Puppet-specific facts. # diff --git a/lib/facter/application.rb b/lib/facter/application.rb index 9de9249365..9d6bc34553 100644 --- a/lib/facter/application.rb +++ b/lib/facter/application.rb @@ -31,6 +31,18 @@ def self.run(argv) exit(0) end + # Print the facts as JSON and exit + if options[:json] + begin + require 'json' + puts JSON.dump(facts) + exit(0) + rescue LoadError + $stderr.puts "You do not have JSON support in your version of Ruby. JSON output disabled" + exit(1) + end + end + # Print the value of a single fact, otherwise print a list sorted by fact # name and separated by "=>" if facts.length == 1 @@ -58,6 +70,7 @@ def self.parse(argv) options = {} OptionParser.new do |opts| opts.on("-y", "--yaml") { |v| options[:yaml] = v } + opts.on("-j", "--json") { |v| options[:json] = v } opts.on( "--trace") { |v| options[:trace] = v } opts.on("-d", "--debug") { |v| Facter.debugging(1) } opts.on("-t", "--timing") { |v| Facter.timing(1) } From f007a9d5719cacf0538c9155cc90de86e69d65df Mon Sep 17 00:00:00 2001 From: Rein Henrichs Date: Mon, 18 Oct 2010 15:45:54 -0700 Subject: [PATCH 0494/3753] (#4989) Add xendomains fact Parses `/usr/sbin/xm list` and returns a comma-separated list of domains. Based on a patch submitted by Jonas Genannt. --- lib/facter/util/xendomains.rb | 10 ++++++++++ lib/facter/xendomains.rb | 10 ++++++++++ spec/unit/data/xendomains | 4 ++++ spec/unit/util/xendomains.rb | 23 +++++++++++++++++++++++ 4 files changed, 47 insertions(+) create mode 100644 lib/facter/util/xendomains.rb create mode 100644 lib/facter/xendomains.rb create mode 100644 spec/unit/data/xendomains create mode 100644 spec/unit/util/xendomains.rb diff --git a/lib/facter/util/xendomains.rb b/lib/facter/util/xendomains.rb new file mode 100644 index 0000000000..4f590a8114 --- /dev/null +++ b/lib/facter/util/xendomains.rb @@ -0,0 +1,10 @@ +# A module to gather running Xen Domains +# +module Facter::Util::Xendomains + def self.get_domains + if xm_list = Facter::Util::Resolution.exec('/usr/sbin/xm list') + domains = xm_list.split("\n").reject { |line| line =~ /^(Name|Domain-0)/ } + domains.map { |line| line.split(/\s/)[0] }.join(',') + end + end +end diff --git a/lib/facter/xendomains.rb b/lib/facter/xendomains.rb new file mode 100644 index 0000000000..972ac90df9 --- /dev/null +++ b/lib/facter/xendomains.rb @@ -0,0 +1,10 @@ +require 'facter/util/xendomains' + +Facter.add("xendomains") do + confine :kernel => %w{Linux FreeBSD OpenBSD SunOS} + confine :virtual => 'xen0' + + setcode do + Facter::Util::Xendomains.get_domains + end +end diff --git a/spec/unit/data/xendomains b/spec/unit/data/xendomains new file mode 100644 index 0000000000..9b112bcbe4 --- /dev/null +++ b/spec/unit/data/xendomains @@ -0,0 +1,4 @@ +Name ID Mem VCPUs State Time(s) +Domain-0 0 656 4 r----- 48140.9 +web01 48 512 2 -b---- 97651.5 +mailserver 53 512 4 -b---- 7536.1 diff --git a/spec/unit/util/xendomains.rb b/spec/unit/util/xendomains.rb new file mode 100644 index 0000000000..a0fa34561a --- /dev/null +++ b/spec/unit/util/xendomains.rb @@ -0,0 +1,23 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'facter/util/xendomains' + +describe Facter::Util::Xendomains do + describe ".get_domains" do + it "should return a list of running Xen Domains on Xen0" do + sample_output_file = File.dirname(__FILE__) + '/../data/xendomains' + xen0_domains = File.read(sample_output_file) + Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/xm list').returns(xen0_domains) + Facter::Util::Xendomains.get_domains.should == %{web01,mailserver} + end + + context "when xm list isn't executable" do + it "should be nil" do + Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/xm list').returns(nil) + Facter::Util::Xendomains.get_domains.should == nil + end + end + end +end From 1eef8424c21f6efb8d70d720834e995c20a6b2b4 Mon Sep 17 00:00:00 2001 From: Paul Berry Date: Fri, 12 Nov 2010 10:26:31 -0800 Subject: [PATCH 0495/3753] Maint: add "Local-branch:" info to mails sent by "rake mail_patches" --- tasks/rake/mail_patches.rake | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tasks/rake/mail_patches.rake b/tasks/rake/mail_patches.rake index 6375a2295f..be8dda128b 100644 --- a/tasks/rake/mail_patches.rake +++ b/tasks/rake/mail_patches.rake @@ -18,10 +18,21 @@ task :mail_patches do # Create all of the patches sh "git format-patch -C -M -s -n --subject-prefix='PATCH/facter' #{parent}..HEAD" + # Add info to the patches + additional_info = "Local-branch: #{branch}\n" + files = Dir.glob("00*.patch") + files.each do |file| + contents = File.read(file) + contents.sub!(/^---\n/, "---\n#{additional_info}") + File.open(file, 'w') do |file_handle| + file_handle.print contents + end + end + # And then mail them out. # If we've got more than one patch, add --compose - if Dir.glob("00*.patch").length > 1 + if files.length > 1 compose = "--compose" else compose = "" From 89da00153031c227b5c58db655f4f76f4182dbe7 Mon Sep 17 00:00:00 2001 From: Matt Robinson Date: Fri, 12 Nov 2010 13:46:58 -0800 Subject: [PATCH 0496/3753] maint: require rubygems so hudson can run the specs --- Rakefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Rakefile b/Rakefile index e472e3ae70..ba939bee2a 100644 --- a/Rakefile +++ b/Rakefile @@ -3,6 +3,7 @@ $: << File.expand_path('lib') $LOAD_PATH << File.join(File.dirname(__FILE__), 'tasks') +require 'rubygems' require 'spec' require 'spec/rake/spectask' begin From 9d990797e9f787aca5bbcc3f1698d9cd00336bf6 Mon Sep 17 00:00:00 2001 From: Nick Lewis Date: Mon, 22 Nov 2010 11:25:14 -0800 Subject: [PATCH 0497/3753] maint: Fix spec failures caused by having a space in the path to facter's source --- spec/unit/util/macaddress.rb | 6 +++--- spec/unit/util/uptime.rb | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/spec/unit/util/macaddress.rb b/spec/unit/util/macaddress.rb index 8349b360fd..1ccca18fae 100644 --- a/spec/unit/util/macaddress.rb +++ b/spec/unit/util/macaddress.rb @@ -25,7 +25,7 @@ context "when netstat has a default interface" do before do - Facter::Util::Macaddress::Darwin.stubs(:netstat_command).returns("cat #{netstat_file}") + Facter::Util::Macaddress::Darwin.stubs(:netstat_command).returns("cat \"#{netstat_file}\"") end it "should return the default interface name" do @@ -40,7 +40,7 @@ before do Facter.stubs(:warn) Facter::Util::Macaddress::Darwin.stubs(:default_interface).returns('') - Facter::Util::Macaddress::Darwin.stubs(:ifconfig_command).returns("cat #{ifconfig_file}") + Facter::Util::Macaddress::Darwin.stubs(:ifconfig_command).returns("cat \"#{ifconfig_file}\"") end it "should return the macaddress of the default interface" do @@ -52,7 +52,7 @@ context "when netstat does not have a default interface" do before do Facter::Util::Macaddress::Darwin.stubs(:default_interface).returns("") - Facter::Util::Macaddress::Darwin.stubs(:ifconfig_command).returns("cat #{ifconfig_file_no_iface}") + Facter::Util::Macaddress::Darwin.stubs(:ifconfig_command).returns("cat \"#{ifconfig_file_no_iface}\"") end it "should warn about the lack of default" do diff --git a/spec/unit/util/uptime.rb b/spec/unit/util/uptime.rb index 9ba6665170..72c97ed7b5 100644 --- a/spec/unit/util/uptime.rb +++ b/spec/unit/util/uptime.rb @@ -10,7 +10,7 @@ context "when /proc/uptime is available" do before do uptime_file = File.join(SPECDIR, "fixtures", "uptime", "ubuntu_proc_uptime") - Facter::Util::Uptime.stubs(:uptime_file).returns(uptime_file) + Facter::Util::Uptime.stubs(:uptime_file).returns("\"#{uptime_file}\"") end it "should return the uptime in seconds as an integer" do @@ -24,7 +24,7 @@ File.exists?(nonexistent_file).should == false Facter::Util::Uptime.stubs(:uptime_file).returns(nonexistent_file) sysctl_output_file = File.join(SPECDIR, 'fixtures', 'uptime', 'sysctl_kern_boottime') # Aug 01 14:13:47 -0700 2010 - Facter::Util::Uptime.stubs(:uptime_sysctl_cmd).returns("cat #{sysctl_output_file}") + Facter::Util::Uptime.stubs(:uptime_sysctl_cmd).returns("cat \"#{sysctl_output_file}\"") Time.stubs(:now).returns Time.parse("Aug 01 15:13:47 -0700 2010") # one hour later Facter::Util::Uptime.get_uptime_seconds_unix.should == 60 * 60 end @@ -35,7 +35,7 @@ Facter::Util::Uptime.stubs(:uptime_file).returns(nonexistent_file) Facter::Util::Uptime.stubs(:uptime_sysctl_cmd).returns("cat #{nonexistent_file}") who_b_output_file = File.join(SPECDIR, 'fixtures', 'uptime', 'who_b_boottime') # Aug 1 14:13 - Facter::Util::Uptime.stubs(:uptime_who_cmd).returns("cat #{who_b_output_file}") + Facter::Util::Uptime.stubs(:uptime_who_cmd).returns("cat \"#{who_b_output_file}\"") Time.stubs(:now).returns Time.parse("Aug 01 15:13") # one hour later Facter::Util::Uptime.get_uptime_seconds_unix.should == 60 * 60 end From 5b561e3b02b1dbbfb705566f62c34b404b63d7b4 Mon Sep 17 00:00:00 2001 From: Dominic Cleal Date: Fri, 19 Nov 2010 09:35:18 +0000 Subject: [PATCH 0498/3753] (#5325) Manufacturer and product name on SPARC Use prtdiag output on Solaris/SPARC to determine manufacturer and productname as smbios is unavailable. --- lib/facter/manufacturer.rb | 2 ++ lib/facter/util/manufacturer.rb | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/lib/facter/manufacturer.rb b/lib/facter/manufacturer.rb index 9d66465cc8..cbbb88b5bb 100644 --- a/lib/facter/manufacturer.rb +++ b/lib/facter/manufacturer.rb @@ -13,6 +13,8 @@ } Facter::Manufacturer.sysctl_find_system_info(mfg_keys) +elsif Facter.value(:kernel) == "SunOS" and Facter.value(:hardwareisa) == "sparc" + Facter::Manufacturer.prtdiag_sparc_find_system_info() else query = { '[Ss]ystem [Ii]nformation' => [ diff --git a/lib/facter/util/manufacturer.rb b/lib/facter/util/manufacturer.rb index 112380b6f5..5ac0585d0e 100644 --- a/lib/facter/util/manufacturer.rb +++ b/lib/facter/util/manufacturer.rb @@ -60,4 +60,24 @@ def self.sysctl_find_system_info(name) end end end + + def self.prtdiag_sparc_find_system_info() + # Parses prtdiag for a SPARC architecture string, won't work with Solaris x86 + output = Facter::Util::Resolution.exec('/usr/sbin/prtdiag') + + # System Configuration: Sun Microsystems sun4u Sun SPARC Enterprise M3000 Server + sysconfig = output.split("\n")[0] + if sysconfig =~ /^System Configuration:\s+(.+?)\s+(sun\d+\S+)\s+(.+)/ then + Facter.add('manufacturer') do + setcode do + $1 + end + end + Facter.add('productname') do + setcode do + $3 + end + end + end + end end From 9332f8a69fcf1f623f57f808085af3e1aafabc38 Mon Sep 17 00:00:00 2001 From: Matt Robinson Date: Mon, 22 Nov 2010 15:41:35 -0800 Subject: [PATCH 0499/3753] (#5325) Add tests for SPARC manufacturer and product name Just tests the regex, won't actually catch problems if prtdiag doesn't return output like our test. Paired-with: Nick Lewis --- spec/unit/util/manufacturer.rb | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/spec/unit/util/manufacturer.rb b/spec/unit/util/manufacturer.rb index c0d4b1345b..291a6ffc49 100644 --- a/spec/unit/util/manufacturer.rb +++ b/spec/unit/util/manufacturer.rb @@ -3,6 +3,10 @@ require 'facter/util/manufacturer' describe Facter::Manufacturer do + before :each do + Facter.clear + end + it "should return the system DMI table" do Facter::Manufacturer.should respond_to(:get_dmi_table) end @@ -12,6 +16,13 @@ Facter::Manufacturer.get_dmi_table().should be_nil end + it "should parse prtdiag output" do + Facter::Util::Resolution.stubs(:exec).returns("System Configuration: Sun Microsystems sun4u Sun SPARC Enterprise M3000 Server") + Facter::Manufacturer.prtdiag_sparc_find_system_info() + Facter.value(:manufacturer).should == "Sun Microsystems" + Facter.value(:productname).should == "Sun SPARC Enterprise M3000 Server" + end + it "should strip white space on dmi output with spaces" do sample_output_file = File.dirname(__FILE__) + "/../data/linux_dmidecode_with_spaces" dmidecode_output = File.new(sample_output_file).read() @@ -23,7 +34,7 @@ Facter::Manufacturer.dmi_find_system_info(query) Facter.value(:productname).should == "MS-6754" end - + it "should handle output from smbios when run under sunos" do sample_output_file = File.dirname(__FILE__) + "/../data/opensolaris_smbios" smbios_output = File.new(sample_output_file).read() From 739040f234c356133e2cb72318f687e8c0e56b9b Mon Sep 17 00:00:00 2001 From: Donavan Miller Date: Tue, 30 Nov 2010 10:35:17 -0800 Subject: [PATCH 0500/3753] (#4754) Add support for Darwin and Parallels VM to "virtual" fact Adds support for Parallels VM guest detection with existing operating systems. Detects Parallels based on hardware vendor name and pci id. The Parallels vendor id does not seem to be listed in most pci.ids. Adds resolution for "virtual" fact in the Darwin kernel. This uses the existing Facter::Util::Macosx module to resolve system profiler data. Both vendor name and vendor id values are checked. Resolution appears to vary based on VM Host product. Signed-off-by: donavanm --- lib/facter/virtual.rb | 35 ++++++++++++++--- spec/unit/virtual.rb | 91 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+), 6 deletions(-) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index a8afb60116..a3b7163ece 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -1,5 +1,23 @@ require 'facter/util/virtual' +Facter.add("virtual") do + confine :kernel => "Darwin" + + setcode do + require 'facter/util/macosx' + result = "physical" + output = Facter::Util::Macosx.profiler_data("SPDisplaysDataType") + if output.is_a?(Hash) + result = "parallels" if output["spdisplays_vendor-id"] =~ /0x1ab8/ + result = "parallels" if output["spdisplays_vendor"] =~ /[Pp]arallels/ + result = "vmware" if output["spdisplays_vendor-id"] =~ /0x15ad/ + result = "vmware" if output["spdisplays_vendor"] =~ /VM[wW]are/ + end + result + end +end + + Facter.add("virtual") do confine :kernel => %w{Linux FreeBSD OpenBSD SunOS HP-UX} @@ -53,18 +71,23 @@ # --- look for the vmware video card to determine if it is virtual => vmware. # --- 00:0f.0 VGA compatible controller: VMware Inc [VMware SVGA II] PCI Display Adapter result = "vmware" if p =~ /VM[wW]are/ + # --- look for pci vendor id used by Parallels video card + # --- 01:00.0 VGA compatible controller: Unknown device 1ab8:4005 + result = "parallels" if p =~ /1ab8:|[Pp]arallels/ end else output = Facter::Util::Resolution.exec('dmidecode') if not output.nil? output.each_line do |pd| - result = "vmware" if pd =~ /VMware|Parallels/ + result = "parallels" if pd =~ /Parallels/ + result = "vmware" if pd =~ /VMware/ end else output = Facter::Util::Resolution.exec('prtdiag') if not output.nil? output.each_line do |pd| - result = "vmware" if pd =~ /VMware|Parallels/ + result = "parallels" if pd =~ /Parallels/ + result = "vmware" if pd =~ /VMware/ end end end @@ -78,15 +101,15 @@ result end end - + Facter.add("is_virtual") do - confine :kernel => %w{Linux FreeBSD OpenBSD SunOS HP-UX} + confine :kernel => %w{Linux FreeBSD OpenBSD SunOS HP-UX Darwin} setcode do case Facter.value(:virtual) - when "xenu", "openvzve", "vmware", "kvm", "vserver", "jail", "zone", "hpvm" + when "xenu", "openvzve", "vmware", "kvm", "vserver", "jail", "zone", "hpvm", "parallels" "true" - else + else "false" end end diff --git a/spec/unit/virtual.rb b/spec/unit/virtual.rb index a7767c9f2e..d607960bf2 100644 --- a/spec/unit/virtual.rb +++ b/spec/unit/virtual.rb @@ -2,6 +2,7 @@ require 'facter' require 'facter/util/virtual' +require 'facter/util/macosx' describe "Virtual fact" do @@ -29,6 +30,91 @@ Facter::Util::Virtual.stubs(:hpvm?).returns(true) Facter.fact(:virtual).value.should == "hpvm" end + + describe "on Darwin" do + it "should be parallels with Parallels vendor id" do + Facter.fact(:kernel).stubs(:value).returns("Darwin") + Facter::Util::Macosx.stubs(:profiler_data).returns({ "spdisplays_vendor-id" => "0x1ab8" }) + Facter.fact(:virtual).value.should == "parallels" + end + + it "should be parallels with Parallels vendor name" do + Facter.fact(:kernel).stubs(:value).returns("Darwin") + Facter::Util::Macosx.stubs(:profiler_data).returns({ "spdisplays_vendor" => "Parallels" }) + Facter.fact(:virtual).value.should == "parallels" + end + + it "should be vmware with VMWare vendor id" do + Facter.fact(:kernel).stubs(:value).returns("Darwin") + Facter::Util::Macosx.stubs(:profiler_data).returns({ "spdisplays_vendor-id" => "0x15ad" }) + Facter.fact(:virtual).value.should == "vmware" + end + + it "should be vmware with VMWare vendor name" do + Facter.fact(:kernel).stubs(:value).returns("Darwin") + Facter::Util::Macosx.stubs(:profiler_data).returns({ "spdisplays_vendor" => "VMWare" }) + Facter.fact(:virtual).value.should == "vmware" + end + end + + describe "on Linux" do + before do + Facter::Util::Virtual.stubs(:zone?).returns(false) + Facter::Util::Virtual.stubs(:openvz?).returns(false) + Facter::Util::Virtual.stubs(:vserver?).returns(false) + Facter::Util::Virtual.stubs(:xen?).returns(false) + Facter::Util::Virtual.stubs(:kvm?).returns(false) + end + + it "should be parallels with Parallels vendor id from lspci" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter::Util::Resolution.stubs(:exec).with('lspci').returns("01:00.0 VGA compatible controller: Unknown device 1ab8:4005") + Facter.fact(:virtual).value.should == "parallels" + end + + it "should be parallels with Parallels vendor name from lspci" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter::Util::Resolution.stubs(:exec).with('lspci').returns("01:00.0 VGA compatible controller: Parallels Display Adapter") + Facter.fact(:virtual).value.should == "parallels" + end + + it "should be vmware with VMware vendor name from lspci" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter::Util::Resolution.stubs(:exec).with('lspci').returns("00:0f.0 VGA compatible controller: VMware Inc [VMware SVGA II] PCI Display Adapter") + Facter.fact(:virtual).value.should == "vmware" + end + + it "should be vmware with VMWare vendor name from dmidecode" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) + Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("On Board Device 1 Information\nType: Video\nStatus: Disabled\nDescription: VMware SVGA II") + Facter.fact(:virtual).value.should == "vmware" + end + + it "should be parallels with Parallels vendor name from dmidecode" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) + Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("On Board Device Information\nType: Video\nStatus: Disabled\nDescription: Parallels Video Adapter") + Facter.fact(:virtual).value.should == "parallels" + end + + it "should be vmware with VMWare vendor name from prtdiag" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) + Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns(nil) + Facter::Util::Resolution.stubs(:exec).with('prtdiag').returns("System Configuration: VMware, Inc. VMware Virtual Platform") + Facter.fact(:virtual).value.should == "vmware" + end + + it "should be parallels with Parallels vendor name from prtdiag" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) + Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns(nil) + Facter::Util::Resolution.stubs(:exec).with('prtdiag').returns("System Configuration: Parallels Virtual Platform") + Facter.fact(:virtual).value.should == "parallels" + end + end + end describe "is_virtual fact" do @@ -85,4 +171,9 @@ Facter.fact(:is_virtual).value.should == "true" end + it "should be true when running on parallels" do + Facter.fact(:kernel).stubs(:value).returns("Darwin") + Facter.fact(:virtual).stubs(:value).returns("parallels") + Facter.fact(:is_virtual).value.should == "true" + end end From 1985528c9e70095116f84d4f4849c149602fffec Mon Sep 17 00:00:00 2001 From: Matt Robinson Date: Tue, 30 Nov 2010 10:59:42 -0800 Subject: [PATCH 0501/3753] (#4754) Change is_virtual logic to not enumerate virtual types While looking at the patch for adding parallels to the virtual types David Schmitt noticed that it might be easier just to list the types that are NOT virtual since there's fewer of them. Paired-with: Nick Lewis --- lib/facter/virtual.rb | 3 +-- spec/unit/virtual.rb | 6 ++++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index a3b7163ece..8412a0a26c 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -106,8 +106,7 @@ confine :kernel => %w{Linux FreeBSD OpenBSD SunOS HP-UX Darwin} setcode do - case Facter.value(:virtual) - when "xenu", "openvzve", "vmware", "kvm", "vserver", "jail", "zone", "hpvm", "parallels" + if Facter.value(:virtual) != "physical" && Facter.value(:virtual) != "xen0" "true" else "false" diff --git a/spec/unit/virtual.rb b/spec/unit/virtual.rb index d607960bf2..9e8e358d34 100644 --- a/spec/unit/virtual.rb +++ b/spec/unit/virtual.rb @@ -135,6 +135,12 @@ Facter.fact(:is_virtual).value.should == "false" end + it "should be false when running on physical" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:virtual).stubs(:value).returns("physical") + Facter.fact(:is_virtual).value.should == "false" + end + it "should be true when running on vmware" do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter.fact(:virtual).stubs(:value).returns("vmware") From c40fc078537a8c104ae19db5348302fe405c5f4b Mon Sep 17 00:00:00 2001 From: Dominic Cleal Date: Mon, 29 Nov 2010 15:07:19 +0000 Subject: [PATCH 0502/3753] (#1423) Memory facts for Solaris Add total memory from prtconf output, free from vmstat plus swap free and total from swap -l listing. --- lib/facter/memory.rb | 51 +++++++++++++++++++++++++++++++++------ lib/facter/util/memory.rb | 12 +++++++++ 2 files changed, 56 insertions(+), 7 deletions(-) diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb index 06640e637b..f744c3f11b 100644 --- a/lib/facter/memory.rb +++ b/lib/facter/memory.rb @@ -69,13 +69,7 @@ end end - Facter.add("MemoryFree") do - confine :kernel => :openbsd - memfree = Facter::Util::Resolution.exec("vmstat | tail -n 1 | awk '{ print $5 }'") - setcode do - Facter::Memory.scale_number(memfree.to_f,"kB") - end - end + Facter::Memory.vmstat_find_free_memory() Facter.add("MemoryTotal") do confine :kernel => :openbsd @@ -85,3 +79,46 @@ end end end + +if Facter.value(:kernel) == "SunOS" + swap = Facter::Util::Resolution.exec('/usr/sbin/swap -l') + swapfree, swaptotal = 0, 0 + swap.each do |dev| + if dev =~ /^\/\S+\s.*\s+(\d+)\s+(\d+)$/ + swaptotal += $1.to_i / 2 + swapfree += $2.to_i / 2 + end + end + + Facter.add("SwapSize") do + confine :kernel => :sunos + setcode do + Facter::Memory.scale_number(swaptotal.to_f,"kB") + end + end + + Facter.add("SwapFree") do + confine :kernel => :sunos + setcode do + Facter::Memory.scale_number(swapfree.to_f,"kB") + end + end + + # Total memory size available from prtconf + pconf = Facter::Util::Resolution.exec('/usr/sbin/prtconf') + phymem = "" + pconf.each do |line| + if line =~ /^Memory size:\s+(\d+) Megabytes/ + phymem = $1 + end + end + + Facter.add("MemorySize") do + confine :kernel => :sunos + setcode do + Facter::Memory.scale_number(phymem.to_f,"MB") + end + end + + Facter::Memory.vmstat_find_free_memory() +end diff --git a/lib/facter/util/memory.rb b/lib/facter/util/memory.rb index 200449198a..43abec6de7 100644 --- a/lib/facter/util/memory.rb +++ b/lib/facter/util/memory.rb @@ -50,5 +50,17 @@ def self.scale_number(size, multiplier) return "%.2f %s" % [size, s] end + + def self.vmstat_find_free_memory() + row = Facter::Util::Resolution.exec('vmstat').split("\n")[-1] + if row =~ /^\s*\d+\s*\d+\s*\d+\s*\d+\s*(\d+)/ + Facter.add("MemoryFree") do + memfree = $1 + setcode do + Facter::Memory.scale_number(memfree.to_f, "kB") + end + end + end + end end From f0cc2c0fde73f473cda1c0cdf95b7b6b583af5ac Mon Sep 17 00:00:00 2001 From: William Van Hevelingen Date: Fri, 20 Aug 2010 01:14:18 -0700 Subject: [PATCH 0503/3753] (#4575) win32 support for manufacturer, productname, & serialnumber Signed-off-by: William Van Hevelingen --- lib/facter/manufacturer.rb | 7 +++++++ lib/facter/util/manufacturer.rb | 17 +++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/lib/facter/manufacturer.rb b/lib/facter/manufacturer.rb index cbbb88b5bb..4f2df98d86 100644 --- a/lib/facter/manufacturer.rb +++ b/lib/facter/manufacturer.rb @@ -15,6 +15,13 @@ Facter::Manufacturer.sysctl_find_system_info(mfg_keys) elsif Facter.value(:kernel) == "SunOS" and Facter.value(:hardwareisa) == "sparc" Facter::Manufacturer.prtdiag_sparc_find_system_info() +elsif Facter.value(:kernel) == "windows" + win32_keys = { + 'manufacturer' => ['Manufacturer', 'Bios'], + 'serialNumber' => ['Serialnumber', 'Bios'], + 'productname' => ['Name', 'ComputerSystemProduct'] + } + Facter::Manufacturer.win32_find_system_info(win32_keys) else query = { '[Ss]ystem [Ii]nformation' => [ diff --git a/lib/facter/util/manufacturer.rb b/lib/facter/util/manufacturer.rb index 5ac0585d0e..22359eafb9 100644 --- a/lib/facter/util/manufacturer.rb +++ b/lib/facter/util/manufacturer.rb @@ -80,4 +80,21 @@ def self.prtdiag_sparc_find_system_info() end end end + + def self.win32_find_system_info(name) + require 'win32ole' + value = "" + wmi = WIN32OLE.connect("winmgmts://") + name.each do |facterkey, win32key| + query = wmi.ExecQuery("select * from Win32_#{win32key.last}") + Facter.add(facterkey) do + confine :kernel => :windows + setcode do + query.each { |x| value = x.__send__( (win32key.first).to_sym) } + value + end + end + end + end + end From cbbfe55a7f0508c09d3b58fa8b8b95654c78bc06 Mon Sep 17 00:00:00 2001 From: Jacob Helwig Date: Mon, 29 Nov 2010 00:49:10 -0800 Subject: [PATCH 0504/3753] Refactor util/uptime.rb tests to reduce duplication using contexts Reviewed-by: Paul Berry --- spec/unit/util/uptime.rb | 61 ++++++++++++++++++++++------------------ 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/spec/unit/util/uptime.rb b/spec/unit/util/uptime.rb index 72c97ed7b5..796b3c2a76 100644 --- a/spec/unit/util/uptime.rb +++ b/spec/unit/util/uptime.rb @@ -19,35 +19,42 @@ end - it "should use sysctl kern.boottime when /proc/uptime not available" do - nonexistent_file = '/non/existent/file' - File.exists?(nonexistent_file).should == false - Facter::Util::Uptime.stubs(:uptime_file).returns(nonexistent_file) - sysctl_output_file = File.join(SPECDIR, 'fixtures', 'uptime', 'sysctl_kern_boottime') # Aug 01 14:13:47 -0700 2010 - Facter::Util::Uptime.stubs(:uptime_sysctl_cmd).returns("cat \"#{sysctl_output_file}\"") - Time.stubs(:now).returns Time.parse("Aug 01 15:13:47 -0700 2010") # one hour later - Facter::Util::Uptime.get_uptime_seconds_unix.should == 60 * 60 - end + context "when /proc/uptime is not available" do + before :each do + @nonexistent_file = '/non/existent/file' + File.exists?(@nonexistent_file).should == false + Facter::Util::Uptime.stubs(:uptime_file).returns(@nonexistent_file) + end - it "should use who -b when neither /proc/uptime nor sysctl kern.boottime is available" do - nonexistent_file = '/non/existent/file' - File.exists?(nonexistent_file).should == false - Facter::Util::Uptime.stubs(:uptime_file).returns(nonexistent_file) - Facter::Util::Uptime.stubs(:uptime_sysctl_cmd).returns("cat #{nonexistent_file}") - who_b_output_file = File.join(SPECDIR, 'fixtures', 'uptime', 'who_b_boottime') # Aug 1 14:13 - Facter::Util::Uptime.stubs(:uptime_who_cmd).returns("cat \"#{who_b_output_file}\"") - Time.stubs(:now).returns Time.parse("Aug 01 15:13") # one hour later - Facter::Util::Uptime.get_uptime_seconds_unix.should == 60 * 60 - end + it "should use 'sysctl kern.boottime'" do + sysctl_output_file = File.join(SPECDIR, 'fixtures', 'uptime', 'sysctl_kern_boottime') # Aug 01 14:13:47 -0700 2010 + Facter::Util::Uptime.stubs(:uptime_sysctl_cmd).returns("cat \"#{sysctl_output_file}\"") + Time.stubs(:now).returns Time.parse("Aug 01 15:13:47 -0700 2010") # one hour later + Facter::Util::Uptime.get_uptime_seconds_unix.should == 60 * 60 + end - it "should return nil when none of /proc/uptime, sysctl kern.boottime, or who -b is available" do - nonexistent_file = '/non/existent/file' - File.exists?(nonexistent_file).should == false - Facter::Util::Uptime.stubs(:uptime_file).returns(nonexistent_file) - Facter::Util::Uptime.stubs(:uptime_sysctl_cmd).returns("cat #{nonexistent_file}") - Facter::Util::Uptime.stubs(:uptime_who_cmd).returns("cat #{nonexistent_file}") - Facter::Util::Uptime.get_uptime_seconds_unix.should == nil + context "nor is 'sysctl kern.boottime'" do + before :each do + Facter::Util::Uptime.stubs(:uptime_sysctl_cmd).returns("cat \"#{@nonexistent_file}\"") + end + + it "should use 'who -b'" do + who_b_output_file = File.join(SPECDIR, 'fixtures', 'uptime', 'who_b_boottime') # Aug 1 14:13 + Facter::Util::Uptime.stubs(:uptime_who_cmd).returns("cat \"#{who_b_output_file}\"") + Time.stubs(:now).returns Time.parse("Aug 01 15:13") # one hour later + Facter::Util::Uptime.get_uptime_seconds_unix.should == 60 * 60 + end + + context "nor is 'who -b'" do + before :each do + Facter::Util::Uptime.stubs(:uptime_who_cmd).returns("cat \"#{@nonexistent_file}\"") + end + + it "should return nil" do + Facter::Util::Uptime.get_uptime_seconds_unix.should == nil + end + end + end end end - end From af9134c2d8911ee8bdd68ba95c64d51429ed6e16 Mon Sep 17 00:00:00 2001 From: Jacob Helwig Date: Mon, 29 Nov 2010 01:08:57 -0800 Subject: [PATCH 0505/3753] (#5086) Try using kstat before falling back to 'who -b' to determine uptime. 'who -b' doesn't report the year of the last system boot on (at least) Solaris 10, and OpenSolaris 2009.06. Try using 'kstat -p unix:::boot_time', which reports as seconds since the epoch on these systems before falling back to 'who -b'. Reviewed-by: Paul Berry --- lib/facter/util/uptime.rb | 12 +++++++++++- spec/fixtures/uptime/kstat_boot_time | 1 + spec/unit/util/uptime.rb | 29 ++++++++++++++++++++-------- 3 files changed, 33 insertions(+), 9 deletions(-) create mode 100644 spec/fixtures/uptime/kstat_boot_time diff --git a/lib/facter/util/uptime.rb b/lib/facter/util/uptime.rb index 9a59d303b2..4e6a7b6d7e 100644 --- a/lib/facter/util/uptime.rb +++ b/lib/facter/util/uptime.rb @@ -4,7 +4,7 @@ # module Facter::Util::Uptime def self.get_uptime_seconds_unix - uptime_proc_uptime or uptime_sysctl or uptime_who_dash_b + uptime_proc_uptime or uptime_sysctl or uptime_kstat or uptime_who_dash_b end def self.get_uptime_seconds_win @@ -30,6 +30,12 @@ def self.uptime_sysctl end end + def self.uptime_kstat + if output = Facter::Util::Resolution.exec("#{uptime_kstat_cmd} 2>/dev/null") + compute_uptime(Time.at(output.chomp.split(/\s/).last.to_i)) + end + end + def self.uptime_who_dash_b if output = Facter::Util::Resolution.exec("#{uptime_who_cmd} 2>/dev/null") compute_uptime(Time.parse(output)) @@ -48,6 +54,10 @@ def self.uptime_sysctl_cmd 'sysctl -b kern.boottime' end + def self.uptime_kstat_cmd + 'kstat -p unix:::boot_time' + end + def self.uptime_who_cmd 'who -b' end diff --git a/spec/fixtures/uptime/kstat_boot_time b/spec/fixtures/uptime/kstat_boot_time new file mode 100644 index 0000000000..1c0a9c5d2e --- /dev/null +++ b/spec/fixtures/uptime/kstat_boot_time @@ -0,0 +1 @@ +unix:0:system_misc:boot_time 1236919980 diff --git a/spec/unit/util/uptime.rb b/spec/unit/util/uptime.rb index 796b3c2a76..b7e3089702 100644 --- a/spec/unit/util/uptime.rb +++ b/spec/unit/util/uptime.rb @@ -38,20 +38,33 @@ Facter::Util::Uptime.stubs(:uptime_sysctl_cmd).returns("cat \"#{@nonexistent_file}\"") end - it "should use 'who -b'" do - who_b_output_file = File.join(SPECDIR, 'fixtures', 'uptime', 'who_b_boottime') # Aug 1 14:13 - Facter::Util::Uptime.stubs(:uptime_who_cmd).returns("cat \"#{who_b_output_file}\"") - Time.stubs(:now).returns Time.parse("Aug 01 15:13") # one hour later + it "should use 'kstat -p unix:::boot_time'" do + kstat_output_file = File.join(SPECDIR, 'fixtures', 'uptime', 'kstat_boot_time') # unix:0:system_misc:boot_time 1236919980 + Facter::Util::Uptime.stubs(:uptime_kstat_cmd).returns("cat \"#{kstat_output_file}\"") + Time.stubs(:now).returns Time.at(1236923580) #one hour later Facter::Util::Uptime.get_uptime_seconds_unix.should == 60 * 60 end - context "nor is 'who -b'" do + context "nor is 'kstat -p unix:::boot_time'" do before :each do - Facter::Util::Uptime.stubs(:uptime_who_cmd).returns("cat \"#{@nonexistent_file}\"") + Facter::Util::Uptime.stubs(:uptime_kstat_cmd).returns("cat \"#{@nonexistent_file}\"") end - it "should return nil" do - Facter::Util::Uptime.get_uptime_seconds_unix.should == nil + it "should use 'who -b'" do + who_b_output_file = File.join(SPECDIR, 'fixtures', 'uptime', 'who_b_boottime') # Aug 1 14:13 + Facter::Util::Uptime.stubs(:uptime_who_cmd).returns("cat \"#{who_b_output_file}\"") + Time.stubs(:now).returns Time.parse("Aug 01 15:13") # one hour later + Facter::Util::Uptime.get_uptime_seconds_unix.should == 60 * 60 + end + + context "nor is 'who -b'" do + before :each do + Facter::Util::Uptime.stubs(:uptime_who_cmd).returns("cat \"#{@nonexistent_file}\"") + end + + it "should return nil" do + Facter::Util::Uptime.get_uptime_seconds_unix.should == nil + end end end end From a4fe4598b681b75f5dc66e8193cefbf069cac728 Mon Sep 17 00:00:00 2001 From: Daniel Pittman Date: Wed, 26 Jan 2011 22:48:55 -0800 Subject: [PATCH 0506/3753] Refactor #6044 -- port testing to rspec2 We have moved to rspec2 for puppet, and facter should follow suit. --- Rakefile | 14 ++++++++------ spec/Rakefile | 18 ------------------ spec/spec_helper.rb | 4 ++-- spec/unit/operatingsystemrelease.rb | 2 +- spec/unit/uptime.rb | 6 +++--- spec/unit/util/macaddress.rb | 10 +++++----- spec/unit/util/uptime.rb | 10 +++++----- spec/unit/util/virtual.rb | 2 +- spec/unit/util/xendomains.rb | 2 +- 9 files changed, 26 insertions(+), 42 deletions(-) delete mode 100644 spec/Rakefile mode change 100644 => 100755 spec/unit/operatingsystemrelease.rb mode change 100644 => 100755 spec/unit/uptime.rb mode change 100644 => 100755 spec/unit/util/macaddress.rb mode change 100644 => 100755 spec/unit/util/uptime.rb mode change 100644 => 100755 spec/unit/util/xendomains.rb diff --git a/Rakefile b/Rakefile index ba939bee2a..9b79613f2c 100644 --- a/Rakefile +++ b/Rakefile @@ -4,8 +4,8 @@ $: << File.expand_path('lib') $LOAD_PATH << File.join(File.dirname(__FILE__), 'tasks') require 'rubygems' -require 'spec' -require 'spec/rake/spectask' +require 'rspec' +require 'rspec/core/rake_task' begin require 'rcov' rescue LoadError @@ -62,12 +62,14 @@ task :default do sh %{rake -T} end -Spec::Rake::SpecTask.new(:spec) do |t| - t.spec_files = FileList['spec/**/*.rb'] +RSpec::Core::RakeTask.new do |t| + t.pattern ='spec/{unit,integration}/**/*.rb' + t.fail_on_error = false end -Spec::Rake::SpecTask.new('spec:rcov') do |t| - t.spec_files = FileList['spec/**/*.rb'] +RSpec::Core::RakeTask.new('spec:rcov') do |t| + t.pattern ='spec/{unit,integration}/**/*.rb' + t.fail_on_error = false if defined?(Rcov) t.rcov = true t.rcov_opts = ['--exclude', 'spec/*,test/*,results/*,/usr/lib/*,/usr/local/lib/*,gems/*'] diff --git a/spec/Rakefile b/spec/Rakefile deleted file mode 100644 index e2996f64f1..0000000000 --- a/spec/Rakefile +++ /dev/null @@ -1,18 +0,0 @@ -require File.join(File.dirname(__FILE__), "spec_helper.rb") -require 'rake' -require 'spec/rake/spectask' - -basedir = File.dirname(__FILE__) -puppetlibdir = File.join(basedir, "../lib") -puppettestlibdir = File.join(basedir, "../test/lib") -speclibdir = File.join(basedir, "lib") - -libs = [puppetlibdir, puppettestlibdir, speclibdir] -desc "Run all specs" -Spec::Rake::SpecTask.new('all') do |t| - t.spec_files = FileList['integration/**/*.rb', 'unit/**/*.rb'] - t.libs = libs - t.spec_opts = ['--options', 'spec.opts'] -end - -task :default => [:all] diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index c8bd547b50..d9db4455b3 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -7,12 +7,12 @@ require 'rubygems' require 'mocha' -require 'spec' +require 'rspec' require 'facter' # load any monkey-patches Dir["#{dir}/monkey_patches/*.rb"].map { |file| require file } -Spec::Runner.configure do |config| +RSpec.configure do |config| config.mock_with :mocha end diff --git a/spec/unit/operatingsystemrelease.rb b/spec/unit/operatingsystemrelease.rb old mode 100644 new mode 100755 index 31d4ae8f76..5c821c34a8 --- a/spec/unit/operatingsystemrelease.rb +++ b/spec/unit/operatingsystemrelease.rb @@ -26,7 +26,7 @@ } test_cases.each do |system, file| - context "with operatingsystem reported as #{system.inspect}" do + describe "with operatingsystem reported as #{system.inspect}" do it "should read the #{file.inspect} file" do Facter.fact(:operatingsystem).stubs(:value).returns(system) diff --git a/spec/unit/uptime.rb b/spec/unit/uptime.rb old mode 100644 new mode 100755 index 19a55fe085..fc592e31b0 --- a/spec/unit/uptime.rb +++ b/spec/unit/uptime.rb @@ -9,7 +9,7 @@ before { Facter.clear } after { Facter.clear } - context "when uptime information is available" do + describe "when uptime information is available" do describe "uptime" do test_cases = [ [60 * 60 * 24 * 3, '3 days'], @@ -34,7 +34,7 @@ end - context "when uptime information is available" do + describe "when uptime information is available" do before do Facter::Util::Uptime.stubs(:get_uptime_seconds_unix).returns(60 * 60 * 24 + 23) Facter::Util::Uptime.stubs(:get_uptime_seconds_win).returns(60 * 60 * 24 + 23) @@ -59,7 +59,7 @@ end end - context "when uptime information is not available" do + describe "when uptime information is not available" do before do Facter::Util::Uptime.stubs(:get_uptime_seconds_unix).returns(nil) Facter::Util::Uptime.stubs(:get_uptime_seconds_win).returns(nil) diff --git a/spec/unit/util/macaddress.rb b/spec/unit/util/macaddress.rb old mode 100644 new mode 100755 index 1ccca18fae..09794ecbff --- a/spec/unit/util/macaddress.rb +++ b/spec/unit/util/macaddress.rb @@ -4,7 +4,7 @@ require 'facter/util/macaddress' -context "Darwin" do +describe "Darwin" do test_cases = [ # version, iface, real macaddress, fallback macaddress ["9.8.0", 'en0', "00:17:f2:06:e4:2e", "00:17:f2:06:e4:2e"], @@ -17,12 +17,12 @@ ifconfig_file_no_iface = File.join(SPECDIR, "fixtures", "ifconfig", "darwin_#{version.tr('.', '_')}") ifconfig_file = "#{ifconfig_file_no_iface}_#{default_iface}" - context "version #{version}" do + describe "version #{version}" do describe Facter::Util::Macaddress::Darwin do describe ".default_interface" do - context "when netstat has a default interface" do + describe "when netstat has a default interface" do before do Facter::Util::Macaddress::Darwin.stubs(:netstat_command).returns("cat \"#{netstat_file}\"") @@ -36,7 +36,7 @@ end describe ".macaddress" do - context "when netstat has a default interface" do + describe "when netstat has a default interface" do before do Facter.stubs(:warn) Facter::Util::Macaddress::Darwin.stubs(:default_interface).returns('') @@ -49,7 +49,7 @@ end - context "when netstat does not have a default interface" do + describe "when netstat does not have a default interface" do before do Facter::Util::Macaddress::Darwin.stubs(:default_interface).returns("") Facter::Util::Macaddress::Darwin.stubs(:ifconfig_command).returns("cat \"#{ifconfig_file_no_iface}\"") diff --git a/spec/unit/util/uptime.rb b/spec/unit/util/uptime.rb old mode 100644 new mode 100755 index b7e3089702..c10485680b --- a/spec/unit/util/uptime.rb +++ b/spec/unit/util/uptime.rb @@ -7,7 +7,7 @@ describe Facter::Util::Uptime do describe ".get_uptime_seconds_unix" do - context "when /proc/uptime is available" do + describe "when /proc/uptime is available" do before do uptime_file = File.join(SPECDIR, "fixtures", "uptime", "ubuntu_proc_uptime") Facter::Util::Uptime.stubs(:uptime_file).returns("\"#{uptime_file}\"") @@ -19,7 +19,7 @@ end - context "when /proc/uptime is not available" do + describe "when /proc/uptime is not available" do before :each do @nonexistent_file = '/non/existent/file' File.exists?(@nonexistent_file).should == false @@ -33,7 +33,7 @@ Facter::Util::Uptime.get_uptime_seconds_unix.should == 60 * 60 end - context "nor is 'sysctl kern.boottime'" do + describe "nor is 'sysctl kern.boottime'" do before :each do Facter::Util::Uptime.stubs(:uptime_sysctl_cmd).returns("cat \"#{@nonexistent_file}\"") end @@ -45,7 +45,7 @@ Facter::Util::Uptime.get_uptime_seconds_unix.should == 60 * 60 end - context "nor is 'kstat -p unix:::boot_time'" do + describe "nor is 'kstat -p unix:::boot_time'" do before :each do Facter::Util::Uptime.stubs(:uptime_kstat_cmd).returns("cat \"#{@nonexistent_file}\"") end @@ -57,7 +57,7 @@ Facter::Util::Uptime.get_uptime_seconds_unix.should == 60 * 60 end - context "nor is 'who -b'" do + describe "nor is 'who -b'" do before :each do Facter::Util::Uptime.stubs(:uptime_who_cmd).returns("cat \"#{@nonexistent_file}\"") end diff --git a/spec/unit/util/virtual.rb b/spec/unit/util/virtual.rb index 72186f7607..66dc98c777 100644 --- a/spec/unit/util/virtual.rb +++ b/spec/unit/util/virtual.rb @@ -67,7 +67,7 @@ ] test_cases.each do |status_file, expected, description| - context "with /proc/self/status from #{description}" do + describe "with /proc/self/status from #{description}" do it "should detect vserver as #{expected.inspect}" do status = File.read(status_file) FileTest.stubs(:exists?).with("/proc/self/status").returns(true) diff --git a/spec/unit/util/xendomains.rb b/spec/unit/util/xendomains.rb old mode 100644 new mode 100755 index a0fa34561a..bd9c5d5442 --- a/spec/unit/util/xendomains.rb +++ b/spec/unit/util/xendomains.rb @@ -13,7 +13,7 @@ Facter::Util::Xendomains.get_domains.should == %{web01,mailserver} end - context "when xm list isn't executable" do + describe "when xm list isn't executable" do it "should be nil" do Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/xm list').returns(nil) Facter::Util::Xendomains.get_domains.should == nil From b39f8923e9772c2e8c99ca9351114d63d2858cc9 Mon Sep 17 00:00:00 2001 From: Daniel Pittman Date: Thu, 27 Jan 2011 17:14:23 -0800 Subject: [PATCH 0507/3753] Refactor #6044 -- require spec_helper with a consistent path. Because we pull in spec_helper in individual tests, we need to use a consistent path or Ruby will evaluate it multiple times. Make the path consistent by expanding it before require. --- spec/integration/facter.rb | 2 +- spec/unit/facter.rb | 2 +- spec/unit/interfaces.rb | 2 +- spec/unit/operatingsystem.rb | 2 +- spec/unit/operatingsystemrelease.rb | 2 +- spec/unit/selinux.rb | 2 +- spec/unit/uptime.rb | 2 +- spec/unit/util/collection.rb | 2 +- spec/unit/util/confine.rb | 2 +- spec/unit/util/fact.rb | 2 +- spec/unit/util/ip.rb | 2 +- spec/unit/util/loader.rb | 2 +- spec/unit/util/macaddress.rb | 2 +- spec/unit/util/macosx.rb | 2 +- spec/unit/util/manufacturer.rb | 2 +- spec/unit/util/resolution.rb | 2 +- spec/unit/util/uptime.rb | 2 +- spec/unit/util/virtual.rb | 2 +- spec/unit/util/vlans.rb | 2 +- spec/unit/util/xendomains.rb | 2 +- spec/unit/virtual.rb | 2 +- 21 files changed, 21 insertions(+), 21 deletions(-) mode change 100644 => 100755 spec/unit/interfaces.rb mode change 100644 => 100755 spec/unit/operatingsystem.rb mode change 100644 => 100755 spec/unit/selinux.rb mode change 100644 => 100755 spec/unit/util/vlans.rb diff --git a/spec/integration/facter.rb b/spec/integration/facter.rb index 79a1f0f5cc..8351de116a 100755 --- a/spec/integration/facter.rb +++ b/spec/integration/facter.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../spec_helper' +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') describe Facter do before do diff --git a/spec/unit/facter.rb b/spec/unit/facter.rb index 20f9ed1625..e63bc7683b 100755 --- a/spec/unit/facter.rb +++ b/spec/unit/facter.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../spec_helper' +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') describe Facter do diff --git a/spec/unit/interfaces.rb b/spec/unit/interfaces.rb old mode 100644 new mode 100755 index 49d5d1f6fa..8b295d67cb --- a/spec/unit/interfaces.rb +++ b/spec/unit/interfaces.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../spec_helper' +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') require 'facter' diff --git a/spec/unit/operatingsystem.rb b/spec/unit/operatingsystem.rb old mode 100644 new mode 100755 index de86230a9d..be83916810 --- a/spec/unit/operatingsystem.rb +++ b/spec/unit/operatingsystem.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../spec_helper' +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') require 'facter' diff --git a/spec/unit/operatingsystemrelease.rb b/spec/unit/operatingsystemrelease.rb index 5c821c34a8..1cfb4ac713 100755 --- a/spec/unit/operatingsystemrelease.rb +++ b/spec/unit/operatingsystemrelease.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../spec_helper' +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') require 'facter' diff --git a/spec/unit/selinux.rb b/spec/unit/selinux.rb old mode 100644 new mode 100755 index 8afa463eb9..43fd5bf679 --- a/spec/unit/selinux.rb +++ b/spec/unit/selinux.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../spec_helper' +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') require 'facter' diff --git a/spec/unit/uptime.rb b/spec/unit/uptime.rb index fc592e31b0..bd695fac43 100755 --- a/spec/unit/uptime.rb +++ b/spec/unit/uptime.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../spec_helper' +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') require 'facter' require 'facter/util/uptime' diff --git a/spec/unit/util/collection.rb b/spec/unit/util/collection.rb index 7baef9687c..86b602f32b 100755 --- a/spec/unit/util/collection.rb +++ b/spec/unit/util/collection.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../../spec_helper' +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') require 'facter/util/collection' diff --git a/spec/unit/util/confine.rb b/spec/unit/util/confine.rb index 757ca269df..147c70d405 100755 --- a/spec/unit/util/confine.rb +++ b/spec/unit/util/confine.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../../spec_helper' +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') require 'facter/util/confine' require 'facter/util/values' diff --git a/spec/unit/util/fact.rb b/spec/unit/util/fact.rb index 16520327bd..db086703ce 100755 --- a/spec/unit/util/fact.rb +++ b/spec/unit/util/fact.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../../spec_helper' +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') require 'facter/util/fact' diff --git a/spec/unit/util/ip.rb b/spec/unit/util/ip.rb index a9aae76331..d87b4b9318 100755 --- a/spec/unit/util/ip.rb +++ b/spec/unit/util/ip.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../../spec_helper' +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') require 'facter/util/ip' diff --git a/spec/unit/util/loader.rb b/spec/unit/util/loader.rb index 0a280208b1..0bb823e6e4 100755 --- a/spec/unit/util/loader.rb +++ b/spec/unit/util/loader.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../../spec_helper' +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') require 'facter/util/loader' diff --git a/spec/unit/util/macaddress.rb b/spec/unit/util/macaddress.rb index 09794ecbff..98215c4381 100755 --- a/spec/unit/util/macaddress.rb +++ b/spec/unit/util/macaddress.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../../spec_helper' +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') require 'facter/util/macaddress' diff --git a/spec/unit/util/macosx.rb b/spec/unit/util/macosx.rb index 283fe75d2e..44ba460294 100755 --- a/spec/unit/util/macosx.rb +++ b/spec/unit/util/macosx.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../../spec_helper' +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') require 'facter/util/macosx' diff --git a/spec/unit/util/manufacturer.rb b/spec/unit/util/manufacturer.rb index 291a6ffc49..07473db9ff 100644 --- a/spec/unit/util/manufacturer.rb +++ b/spec/unit/util/manufacturer.rb @@ -1,4 +1,4 @@ -require File.dirname(__FILE__) + '/../../spec_helper' +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') require 'facter/util/manufacturer' diff --git a/spec/unit/util/resolution.rb b/spec/unit/util/resolution.rb index 396f800f9c..581d0e17f1 100755 --- a/spec/unit/util/resolution.rb +++ b/spec/unit/util/resolution.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../../spec_helper' +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') require 'facter/util/resolution' diff --git a/spec/unit/util/uptime.rb b/spec/unit/util/uptime.rb index c10485680b..8d3980cfa6 100755 --- a/spec/unit/util/uptime.rb +++ b/spec/unit/util/uptime.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../../spec_helper' +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') require 'facter/util/uptime' diff --git a/spec/unit/util/virtual.rb b/spec/unit/util/virtual.rb index 66dc98c777..12ba0ac0db 100644 --- a/spec/unit/util/virtual.rb +++ b/spec/unit/util/virtual.rb @@ -1,4 +1,4 @@ -require File.dirname(__FILE__) + '/../../spec_helper' +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') require 'facter/util/virtual' diff --git a/spec/unit/util/vlans.rb b/spec/unit/util/vlans.rb old mode 100644 new mode 100755 index e06a2afd6c..0331234993 --- a/spec/unit/util/vlans.rb +++ b/spec/unit/util/vlans.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../../spec_helper' +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') require 'facter/util/vlans' diff --git a/spec/unit/util/xendomains.rb b/spec/unit/util/xendomains.rb index bd9c5d5442..dc7e17852f 100755 --- a/spec/unit/util/xendomains.rb +++ b/spec/unit/util/xendomains.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../../spec_helper' +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') require 'facter/util/xendomains' diff --git a/spec/unit/virtual.rb b/spec/unit/virtual.rb index 9e8e358d34..a152b409b5 100644 --- a/spec/unit/virtual.rb +++ b/spec/unit/virtual.rb @@ -1,4 +1,4 @@ -require File.dirname(__FILE__) + '/../spec_helper' +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') require 'facter' require 'facter/util/virtual' From 7a8be1677314ce3db6ae6590ae7d32a10605c8d3 Mon Sep 17 00:00:00 2001 From: Daniel Pittman Date: Thu, 27 Jan 2011 17:17:52 -0800 Subject: [PATCH 0508/3753] Refactor #6044 -- use _spec.rb as the pattern for spec tests. Rename all the spec tests to follow the rspec convention of *_spec.rb rather than unadorned *.rb; this also makes it easier to work with them consistently without using the Rakefile support that customizes that. --- Rakefile | 4 ++-- spec/integration/{facter.rb => facter_spec.rb} | 0 spec/unit/{facter.rb => facter_spec.rb} | 0 spec/unit/{interfaces.rb => interfaces_spec.rb} | 0 spec/unit/{operatingsystem.rb => operatingsystem_spec.rb} | 0 ...eratingsystemrelease.rb => operatingsystemrelease_spec.rb} | 0 spec/unit/{selinux.rb => selinux_spec.rb} | 0 spec/unit/{uptime.rb => uptime_spec.rb} | 0 spec/unit/util/{collection.rb => collection_spec.rb} | 0 spec/unit/util/{confine.rb => confine_spec.rb} | 0 spec/unit/util/{fact.rb => fact_spec.rb} | 0 spec/unit/util/{ip.rb => ip_spec.rb} | 0 spec/unit/util/{loader.rb => loader_spec.rb} | 0 spec/unit/util/{macaddress.rb => macaddress_spec.rb} | 0 spec/unit/util/{macosx.rb => macosx_spec.rb} | 0 spec/unit/util/{manufacturer.rb => manufacturer_spec.rb} | 0 spec/unit/util/{resolution.rb => resolution_spec.rb} | 0 spec/unit/util/{uptime.rb => uptime_spec.rb} | 0 spec/unit/util/{virtual.rb => virtual_spec.rb} | 0 spec/unit/util/{vlans.rb => vlans_spec.rb} | 0 spec/unit/util/{xendomains.rb => xendomains_spec.rb} | 0 spec/unit/{virtual.rb => virtual_spec.rb} | 0 22 files changed, 2 insertions(+), 2 deletions(-) rename spec/integration/{facter.rb => facter_spec.rb} (100%) rename spec/unit/{facter.rb => facter_spec.rb} (100%) rename spec/unit/{interfaces.rb => interfaces_spec.rb} (100%) rename spec/unit/{operatingsystem.rb => operatingsystem_spec.rb} (100%) rename spec/unit/{operatingsystemrelease.rb => operatingsystemrelease_spec.rb} (100%) rename spec/unit/{selinux.rb => selinux_spec.rb} (100%) rename spec/unit/{uptime.rb => uptime_spec.rb} (100%) rename spec/unit/util/{collection.rb => collection_spec.rb} (100%) rename spec/unit/util/{confine.rb => confine_spec.rb} (100%) rename spec/unit/util/{fact.rb => fact_spec.rb} (100%) rename spec/unit/util/{ip.rb => ip_spec.rb} (100%) rename spec/unit/util/{loader.rb => loader_spec.rb} (100%) rename spec/unit/util/{macaddress.rb => macaddress_spec.rb} (100%) rename spec/unit/util/{macosx.rb => macosx_spec.rb} (100%) rename spec/unit/util/{manufacturer.rb => manufacturer_spec.rb} (100%) rename spec/unit/util/{resolution.rb => resolution_spec.rb} (100%) rename spec/unit/util/{uptime.rb => uptime_spec.rb} (100%) rename spec/unit/util/{virtual.rb => virtual_spec.rb} (100%) rename spec/unit/util/{vlans.rb => vlans_spec.rb} (100%) rename spec/unit/util/{xendomains.rb => xendomains_spec.rb} (100%) rename spec/unit/{virtual.rb => virtual_spec.rb} (100%) diff --git a/Rakefile b/Rakefile index 9b79613f2c..9d7d90614e 100644 --- a/Rakefile +++ b/Rakefile @@ -63,12 +63,12 @@ task :default do end RSpec::Core::RakeTask.new do |t| - t.pattern ='spec/{unit,integration}/**/*.rb' + t.pattern ='spec/{unit,integration}/**/*_spec.rb' t.fail_on_error = false end RSpec::Core::RakeTask.new('spec:rcov') do |t| - t.pattern ='spec/{unit,integration}/**/*.rb' + t.pattern ='spec/{unit,integration}/**/*_spec.rb' t.fail_on_error = false if defined?(Rcov) t.rcov = true diff --git a/spec/integration/facter.rb b/spec/integration/facter_spec.rb similarity index 100% rename from spec/integration/facter.rb rename to spec/integration/facter_spec.rb diff --git a/spec/unit/facter.rb b/spec/unit/facter_spec.rb similarity index 100% rename from spec/unit/facter.rb rename to spec/unit/facter_spec.rb diff --git a/spec/unit/interfaces.rb b/spec/unit/interfaces_spec.rb similarity index 100% rename from spec/unit/interfaces.rb rename to spec/unit/interfaces_spec.rb diff --git a/spec/unit/operatingsystem.rb b/spec/unit/operatingsystem_spec.rb similarity index 100% rename from spec/unit/operatingsystem.rb rename to spec/unit/operatingsystem_spec.rb diff --git a/spec/unit/operatingsystemrelease.rb b/spec/unit/operatingsystemrelease_spec.rb similarity index 100% rename from spec/unit/operatingsystemrelease.rb rename to spec/unit/operatingsystemrelease_spec.rb diff --git a/spec/unit/selinux.rb b/spec/unit/selinux_spec.rb similarity index 100% rename from spec/unit/selinux.rb rename to spec/unit/selinux_spec.rb diff --git a/spec/unit/uptime.rb b/spec/unit/uptime_spec.rb similarity index 100% rename from spec/unit/uptime.rb rename to spec/unit/uptime_spec.rb diff --git a/spec/unit/util/collection.rb b/spec/unit/util/collection_spec.rb similarity index 100% rename from spec/unit/util/collection.rb rename to spec/unit/util/collection_spec.rb diff --git a/spec/unit/util/confine.rb b/spec/unit/util/confine_spec.rb similarity index 100% rename from spec/unit/util/confine.rb rename to spec/unit/util/confine_spec.rb diff --git a/spec/unit/util/fact.rb b/spec/unit/util/fact_spec.rb similarity index 100% rename from spec/unit/util/fact.rb rename to spec/unit/util/fact_spec.rb diff --git a/spec/unit/util/ip.rb b/spec/unit/util/ip_spec.rb similarity index 100% rename from spec/unit/util/ip.rb rename to spec/unit/util/ip_spec.rb diff --git a/spec/unit/util/loader.rb b/spec/unit/util/loader_spec.rb similarity index 100% rename from spec/unit/util/loader.rb rename to spec/unit/util/loader_spec.rb diff --git a/spec/unit/util/macaddress.rb b/spec/unit/util/macaddress_spec.rb similarity index 100% rename from spec/unit/util/macaddress.rb rename to spec/unit/util/macaddress_spec.rb diff --git a/spec/unit/util/macosx.rb b/spec/unit/util/macosx_spec.rb similarity index 100% rename from spec/unit/util/macosx.rb rename to spec/unit/util/macosx_spec.rb diff --git a/spec/unit/util/manufacturer.rb b/spec/unit/util/manufacturer_spec.rb similarity index 100% rename from spec/unit/util/manufacturer.rb rename to spec/unit/util/manufacturer_spec.rb diff --git a/spec/unit/util/resolution.rb b/spec/unit/util/resolution_spec.rb similarity index 100% rename from spec/unit/util/resolution.rb rename to spec/unit/util/resolution_spec.rb diff --git a/spec/unit/util/uptime.rb b/spec/unit/util/uptime_spec.rb similarity index 100% rename from spec/unit/util/uptime.rb rename to spec/unit/util/uptime_spec.rb diff --git a/spec/unit/util/virtual.rb b/spec/unit/util/virtual_spec.rb similarity index 100% rename from spec/unit/util/virtual.rb rename to spec/unit/util/virtual_spec.rb diff --git a/spec/unit/util/vlans.rb b/spec/unit/util/vlans_spec.rb similarity index 100% rename from spec/unit/util/vlans.rb rename to spec/unit/util/vlans_spec.rb diff --git a/spec/unit/util/xendomains.rb b/spec/unit/util/xendomains_spec.rb similarity index 100% rename from spec/unit/util/xendomains.rb rename to spec/unit/util/xendomains_spec.rb diff --git a/spec/unit/virtual.rb b/spec/unit/virtual_spec.rb similarity index 100% rename from spec/unit/virtual.rb rename to spec/unit/virtual_spec.rb From b88a088c6e53ef96914280e6937b9b9214b6c64b Mon Sep 17 00:00:00 2001 From: Rick Bradley Date: Sun, 12 Dec 2010 20:10:53 -0600 Subject: [PATCH 0509/3753] (#5510) Facter should load custom fact definitions in filename order. Ruby's Dir.entries will return files in different orders depending on the OS and/or filesystem. As a result Facter::Util::Loader will load ruby custom fact definitions in different orders on different platforms. Specs to expose the bugs, and code to ensure that custom fact files are loaded in alphabetical order. Addresses redmine issue #5510 http://projects.puppetlabs.com/issues/5510 Signed-off-by: Rick Bradley Signed-off-by: Daniel Pittman --- lib/facter/util/loader.rb | 4 ++-- spec/unit/util/loader_spec.rb | 43 +++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/lib/facter/util/loader.rb b/lib/facter/util/loader.rb index ac90c48e4d..2d2d9e8a0e 100644 --- a/lib/facter/util/loader.rb +++ b/lib/facter/util/loader.rb @@ -30,7 +30,7 @@ def load_all search_path.each do |dir| next unless FileTest.directory?(dir) - Dir.entries(dir).each do |file| + Dir.entries(dir).sort.each do |file| path = File.join(dir, file) if File.directory?(path) load_dir(path) @@ -62,7 +62,7 @@ def search_path def load_dir(dir) return if dir =~ /\/\.+$/ or dir =~ /\/util$/ or dir =~ /\/lib$/ - Dir.entries(dir).find_all { |f| f =~ /\.rb$/ }.each do |file| + Dir.entries(dir).find_all { |f| f =~ /\.rb$/ }.sort.each do |file| load_file(File.join(dir, file)) end end diff --git a/spec/unit/util/loader_spec.rb b/spec/unit/util/loader_spec.rb index 0bb823e6e4..eed533a4fe 100755 --- a/spec/unit/util/loader_spec.rb +++ b/spec/unit/util/loader_spec.rb @@ -4,6 +4,20 @@ require 'facter/util/loader' + +# loader subclass for making assertions about file/directory ordering +class TestLoader < Facter::Util::Loader + def loaded_files + @loaded_files ||= [] + end + + def load_file(file) + loaded_files << file + super + end +end + + describe Facter::Util::Loader do def with_env(values) old = {} @@ -96,6 +110,21 @@ def with_env(values) @loader.load(:testing) end + it 'should load any ruby files in directories matching the fact name in the search path in sorted order regardless of the order returned by Dir.entries' do + @loader = TestLoader.new + + @loader.stubs(:search_path).returns %w{/one/dir} + FileTest.stubs(:exist?).returns false + FileTest.stubs(:directory?).with("/one/dir/testing").returns true + @loader.stubs(:search_path).returns %w{/one/dir} + + Dir.stubs(:entries).with("/one/dir/testing").returns %w{foo.rb bar.rb} + %w{/one/dir/testing/foo.rb /one/dir/testing/bar.rb}.each { |f| File.stubs(:directory?).with(f).returns false } + + @loader.load(:testing) + @loader.loaded_files.should == %w{/one/dir/testing/bar.rb /one/dir/testing/foo.rb} + end + it "should load any ruby files in directories matching the fact name in the search path" do @loader.expects(:search_path).returns %w{/one/dir} FileTest.stubs(:exist?).returns false @@ -166,6 +195,20 @@ def with_env(values) @loader.load_all end + it 'should load all files in sorted order for any given directory regardless of the order returned by Dir.entries' do + @loader = TestLoader.new + + @loader.stubs(:search_path).returns %w{/one/dir} + Dir.stubs(:entries).with("/one/dir").returns %w{foo.rb bar.rb} + + %w{/one/dir}.each { |f| File.stubs(:directory?).with(f).returns true } + %w{/one/dir/foo.rb /one/dir/bar.rb}.each { |f| File.stubs(:directory?).with(f).returns false } + + @loader.load_all + + @loader.loaded_files.should == %w{/one/dir/bar.rb /one/dir/foo.rb} + end + it "should not load files in the util subdirectory" do @loader.expects(:search_path).returns %w{/one/dir} From 0d7a2e683125028758ae91d3754c0466fd068cf3 Mon Sep 17 00:00:00 2001 From: Marc Fournier Date: Fri, 10 Sep 2010 19:02:44 +0200 Subject: [PATCH 0510/3753] Fix #4755: add support for GNU/kFreeBSD platform where missing. Merged manually to the current state of the art. Minimal conflicts resolved by adding both Darwin and GNU/KFreeBSD to the confine lines. Author: Marc Fournier Signed-off-by: Daniel Pittman Signed-off-by: Rick Bradley --- lib/facter/hardwareisa.rb | 2 +- lib/facter/id.rb | 2 +- lib/facter/lsb.rb | 2 +- lib/facter/lsbmajdistrelease.rb | 2 +- lib/facter/macaddress.rb | 2 +- lib/facter/memory.rb | 2 +- lib/facter/netmask.rb | 2 +- lib/facter/processor.rb | 6 ++-- lib/facter/uniqueid.rb | 2 +- lib/facter/util/ip.rb | 10 +++---- lib/facter/util/manufacturer.rb | 4 +-- lib/facter/util/netmask.rb | 2 +- lib/facter/util/virtual.rb | 6 +++- lib/facter/virtual.rb | 6 ++-- spec/unit/data/debian_kfreebsd_ifconfig | 40 +++++++++++++++++++++++++ spec/unit/util/ip_spec.rb | 30 ++++++++++++++++++- spec/unit/util/virtual_spec.rb | 6 ++-- 17 files changed, 100 insertions(+), 26 deletions(-) create mode 100644 spec/unit/data/debian_kfreebsd_ifconfig diff --git a/lib/facter/hardwareisa.rb b/lib/facter/hardwareisa.rb index 45a16bca38..23f7d08872 100644 --- a/lib/facter/hardwareisa.rb +++ b/lib/facter/hardwareisa.rb @@ -1,4 +1,4 @@ Facter.add(:hardwareisa) do setcode 'uname -p', '/bin/sh' - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE SLES Debian Ubuntu Gentoo FreeBSD OpenBSD NetBSD OEL OVS} + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE SLES Debian Ubuntu Gentoo FreeBSD OpenBSD NetBSD OEL OVS GNU/kFreeBSD} end diff --git a/lib/facter/id.rb b/lib/facter/id.rb index 0a4067d9ce..c2ab3b7d99 100644 --- a/lib/facter/id.rb +++ b/lib/facter/id.rb @@ -1,5 +1,5 @@ Facter.add(:id) do - confine :operatingsystem => %w{Linux Fedora RedHat CentOS SuSE SLES Debian Ubuntu Gentoo AIX OEL OVS} + confine :operatingsystem => %w{Linux Fedora RedHat CentOS SuSE SLES Debian Ubuntu Gentoo AIX OEL OVS GNU/kFreeBSD} setcode "whoami" end diff --git a/lib/facter/lsb.rb b/lib/facter/lsb.rb index f54d75fae0..4b98466b30 100644 --- a/lib/facter/lsb.rb +++ b/lib/facter/lsb.rb @@ -20,7 +20,7 @@ "LSBDistCodeName" => %r{^Codename:\t(.*)$} }.each do |fact, pattern| Facter.add(fact) do - confine :kernel => :linux + confine :kernel => [ :linux, :"gnu/kfreebsd" ] setcode do unless defined?(@@lsbdata) and defined?(@@lsbtime) and (Time.now.to_i - @@lsbtime.to_i < 5) type = nil diff --git a/lib/facter/lsbmajdistrelease.rb b/lib/facter/lsbmajdistrelease.rb index e09159355e..997e7efd90 100644 --- a/lib/facter/lsbmajdistrelease.rb +++ b/lib/facter/lsbmajdistrelease.rb @@ -3,7 +3,7 @@ require 'facter' Facter.add("lsbmajdistrelease") do - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE SLES Debian Ubuntu Gentoo OEL OVS} + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE SLES Debian Ubuntu Gentoo OEL OVS GNU/kFreeBSD} setcode do if /(\d*)\./i =~ Facter.value(:lsbdistrelease) result=$1 diff --git a/lib/facter/macaddress.rb b/lib/facter/macaddress.rb index adc2c64f68..4a12384d81 100644 --- a/lib/facter/macaddress.rb +++ b/lib/facter/macaddress.rb @@ -1,7 +1,7 @@ require 'facter/util/macaddress' Facter.add(:macaddress) do - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE SLES Debian Gentoo Ubuntu OEL OVS} + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE SLES Debian Gentoo Ubuntu OEL OVS GNU/kFreeBSD} setcode do ether = [] output = %x{/sbin/ifconfig -a} diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb index f744c3f11b..86adc7f111 100644 --- a/lib/facter/memory.rb +++ b/lib/facter/memory.rb @@ -13,7 +13,7 @@ :SwapFree => "SwapFree" }.each do |fact, name| Facter.add(fact) do - confine :kernel => :linux + confine :kernel => [ :linux, :"gnu/kfreebsd" ] setcode do Facter::Memory.meminfo_number(name) end diff --git a/lib/facter/netmask.rb b/lib/facter/netmask.rb index bad364832c..d6d125e2af 100644 --- a/lib/facter/netmask.rb +++ b/lib/facter/netmask.rb @@ -9,7 +9,7 @@ require 'facter/util/netmask' Facter.add("netmask") do - confine :kernel => [ :sunos, :linux, :freebsd, :openbsd, :netbsd, :darwin ] + confine :kernel => [ :sunos, :linux, :freebsd, :openbsd, :netbsd, :darwin, :"gnu/kfreebsd" ] setcode do Facter::NetMask.get_netmask end diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb index 53998ff418..ac75867169 100644 --- a/lib/facter/processor.rb +++ b/lib/facter/processor.rb @@ -7,7 +7,7 @@ require 'thread' -if Facter.value(:kernel) == "Linux" +if ["Linux", "GNU/kFreeBSD"].include? Facter.value(:kernel) processor_num = -1 processor_list = [] Thread::exclusive do @@ -22,7 +22,7 @@ end Facter.add("ProcessorCount") do - confine :kernel => :linux + confine :kernel => [ :linux, :"gnu/kfreebsd" ] setcode do processor_list.length.to_s end @@ -30,7 +30,7 @@ processor_list.each_with_index do |desc, i| Facter.add("Processor#{i}") do - confine :kernel => :linux + confine :kernel => [ :linux, :"gnu/kfreebsd" ] setcode do desc end diff --git a/lib/facter/uniqueid.rb b/lib/facter/uniqueid.rb index 93f8a61e38..aaeaa123d5 100644 --- a/lib/facter/uniqueid.rb +++ b/lib/facter/uniqueid.rb @@ -1,4 +1,4 @@ Facter.add(:uniqueid) do setcode 'hostid', '/bin/sh' - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE SLES Debian Ubuntu Gentoo AIX OEL OVS} + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE SLES Debian Ubuntu Gentoo AIX OEL OVS GNU/kFreeBSD} end diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index 366303c80e..62d50a4703 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -10,9 +10,9 @@ module Facter::Util::IP :netmask => /Mask:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ }, :bsd => { - :aliases => [:openbsd, :netbsd, :freebsd, :darwin], + :aliases => [:openbsd, :netbsd, :freebsd, :darwin, :"gnu/kfreebsd"], :ipaddress => /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, - :macaddress => /(?:ether|lladdr)\s+(\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/, + :macaddress => /(?:ether|lladdr)\s+(\w?\w:\w?\w:\w?\w:\w?\w:\w?\w:\w?\w)/, :netmask => /netmask\s+0x(\w{8})/ }, :sunos => { @@ -33,7 +33,7 @@ def self.alphafy(interface) end def self.convert_from_hex?(kernel) - kernels_to_convert = [:sunos, :openbsd, :netbsd, :freebsd, :darwin, :"hp-ux"] + kernels_to_convert = [:sunos, :openbsd, :netbsd, :freebsd, :darwin, :"hp-ux", :"gnu/kfreebsd"] kernels_to_convert.include?(kernel) end @@ -61,7 +61,7 @@ def self.get_interfaces def self.get_all_interface_output case Facter.value(:kernel) - when 'Linux', 'OpenBSD', 'NetBSD', 'FreeBSD', 'Darwin' + when 'Linux', 'OpenBSD', 'NetBSD', 'FreeBSD', 'Darwin', 'GNU/kFreeBSD' output = %x{/sbin/ifconfig -a} when 'SunOS' output = %x{/usr/sbin/ifconfig -a} @@ -74,7 +74,7 @@ def self.get_all_interface_output def self.get_single_interface_output(interface) output = "" case Facter.value(:kernel) - when 'Linux', 'OpenBSD', 'NetBSD', 'FreeBSD', 'Darwin' + when 'Linux', 'OpenBSD', 'NetBSD', 'FreeBSD', 'Darwin', 'GNU/kFreeBSD' output = %x{/sbin/ifconfig #{interface}} when 'SunOS' output = %x{/usr/sbin/ifconfig #{interface}} diff --git a/lib/facter/util/manufacturer.rb b/lib/facter/util/manufacturer.rb index 22359eafb9..8e9bde2129 100644 --- a/lib/facter/util/manufacturer.rb +++ b/lib/facter/util/manufacturer.rb @@ -5,7 +5,7 @@ module Facter::Manufacturer def self.get_dmi_table() case Facter.value(:kernel) - when 'Linux' + when 'Linux', 'GNU/kFreeBSD' return nil unless FileTest.exists?("/usr/sbin/dmidecode") output=%x{/usr/sbin/dmidecode 2>/dev/null} @@ -38,7 +38,7 @@ def self.dmi_find_system_info(name) if line =~ /#{key}/ and line =~ /\n\s+#{value} (.+)\n/ result = $1.strip Facter.add(facterkey) do - confine :kernel => [ :linux, :freebsd, :netbsd, :sunos ] + confine :kernel => [ :linux, :freebsd, :netbsd, :sunos, :"gnu/kfreebsd" ] setcode do result end diff --git a/lib/facter/util/netmask.rb b/lib/facter/util/netmask.rb index 160cdbde17..5dcc576fcf 100644 --- a/lib/facter/util/netmask.rb +++ b/lib/facter/util/netmask.rb @@ -17,7 +17,7 @@ def self.get_netmask :regex => %r{\s+ inet \s #{Facter.ipaddress} \s netmask \s (\w{8})}x, :munge => Proc.new { |mask| mask.scan(/../).collect do |byte| byte.to_i(16) end.join('.') } } - when 'FreeBSD','NetBSD','OpenBSD', 'Darwin' + when 'FreeBSD','NetBSD','OpenBSD', 'Darwin', 'GNU/kFreeBSD' ops = { :ifconfig => '/sbin/ifconfig -a', :regex => %r{\s+ inet \s #{Facter.ipaddress} \s netmask \s 0x(\w{8})}x, diff --git a/lib/facter/util/virtual.rb b/lib/facter/util/virtual.rb index e94070d27d..129448ea16 100644 --- a/lib/facter/util/virtual.rb +++ b/lib/facter/util/virtual.rb @@ -59,7 +59,11 @@ def self.kvm_type end def self.jail? - Facter::Util::Resolution.exec("/sbin/sysctl -n security.jail.jailed") == "1" + path = case Facter.value(:kernel) + when "FreeBSD": "/sbin" + when "GNU/kFreeBSD": "/bin" + end + Facter::Util::Resolution.exec("#{path}/sysctl -n security.jail.jailed") == "1" end def self.hpvm? diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 8412a0a26c..47c9504539 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -19,7 +19,7 @@ Facter.add("virtual") do - confine :kernel => %w{Linux FreeBSD OpenBSD SunOS HP-UX} + confine :kernel => %w{Linux FreeBSD OpenBSD SunOS HP-UX GNU/kFreeBSD} result = "physical" @@ -60,7 +60,7 @@ result = Facter::Util::Virtual.kvm_type() end - if Facter.value(:kernel)=="FreeBSD" + if ["FreeBSD", "GNU/kFreeBSD"].include? Facter.value(:kernel) result = "jail" if Facter::Util::Virtual.jail? end @@ -103,7 +103,7 @@ end Facter.add("is_virtual") do - confine :kernel => %w{Linux FreeBSD OpenBSD SunOS HP-UX Darwin} + confine :kernel => %w{Linux FreeBSD OpenBSD SunOS HP-UX Darwin GNU/kFreeBSD} setcode do if Facter.value(:virtual) != "physical" && Facter.value(:virtual) != "xen0" diff --git a/spec/unit/data/debian_kfreebsd_ifconfig b/spec/unit/data/debian_kfreebsd_ifconfig new file mode 100644 index 0000000000..44a3b62ad7 --- /dev/null +++ b/spec/unit/data/debian_kfreebsd_ifconfig @@ -0,0 +1,40 @@ +em0: flags=8843 metric 0 mtu 1500 + options=209b + ether 0:11:a:59:67:90 + inet6 fe80::211:aff:fe59:6790%em0 prefixlen 64 scopeid 0x1 + nd6 options=3 + media: Ethernet autoselect (1000baseT ) + status: active +em1: flags=8843 metric 0 mtu 1500 + options=209b + ether 0:11:a:59:67:91 + inet6 fe80::211:aff:fe59:6791%em1 prefixlen 64 scopeid 0x2 + inet 192.168.10.10 netmask 0xffffff00 broadcast 192.168.10.255 + nd6 options=3 + media: Ethernet autoselect (100baseTX ) + status: active +bge0: flags=8802 metric 0 mtu 1500 + options=8009b + ether 0:14:c2:3f:ea:e4 + media: Ethernet autoselect (none) + status: no carrier +bge1: flags=8802 metric 0 mtu 1500 + options=8009b + ether 0:14:c2:3f:ea:e3 + media: Ethernet autoselect (none) + status: no carrier +lo0: flags=8049 metric 0 mtu 16384 + options=3 + inet6 ::1 prefixlen 128 + inet6 fe80::1%lo0 prefixlen 64 scopeid 0x5 + inet 127.0.0.1 netmask 0xff000000 + nd6 options=3 +vlan0: flags=8843 metric 0 mtu 1500 + options=3 + ether 0:11:a:59:67:90 + inet6 fe80::211:aff:fe59:6790%vlan0 prefixlen 64 scopeid 0x6 + inet 192.168.192.2 netmask 0xffffff00 broadcast 192.168.192.255 + nd6 options=3 + media: Ethernet autoselect (1000baseT ) + status: active + vlan: 192 parent interface: em0 diff --git a/spec/unit/util/ip_spec.rb b/spec/unit/util/ip_spec.rb index d87b4b9318..ceceb3fc2a 100755 --- a/spec/unit/util/ip_spec.rb +++ b/spec/unit/util/ip_spec.rb @@ -5,7 +5,7 @@ require 'facter/util/ip' describe Facter::Util::IP do - [:freebsd, :linux, :netbsd, :openbsd, :sunos, :darwin, :"hp-ux"].each do |platform| + [:freebsd, :linux, :netbsd, :openbsd, :sunos, :darwin, :"hp-ux", :"gnu/kfreebsd"].each do |platform| it "should be supported on #{platform}" do Facter::Util::IP.supported_platforms.should be_include(platform) end @@ -48,6 +48,13 @@ Facter::Util::IP.get_interfaces().should == ["lan1", "lan0", "lo0"] end + it "should return a list of six interfaces on a GNU/kFreeBSD with six interfaces" do + sample_output_file = File.dirname(__FILE__) + '/../data/debian_kfreebsd_ifconfig' + kfreebsd_ifconfig = File.new(sample_output_file).read() + Facter::Util::IP.stubs(:get_all_interface_output).returns(kfreebsd_ifconfig) + Facter::Util::IP.get_interfaces().should == ["em0", "em1", "bge0", "bge1", "lo0", "vlan0"] + end + it "should return a value for a specific interface" do Facter::Util::IP.should respond_to(:get_interface_value) end @@ -107,6 +114,16 @@ Facter::Util::IP.get_interface_value("lan0", "macaddress").should == "00:13:21:BD:9C:B7" end + it "should return macaddress with leading zeros stripped off for GNU/kFreeBSD" do + sample_output_file = File.dirname(__FILE__) + "/../data/debian_kfreebsd_ifconfig" + kfreebsd_ifconfig = File.new(sample_output_file).read() + + Facter::Util::IP.expects(:get_single_interface_output).with("em0").returns(kfreebsd_ifconfig) + Facter.stubs(:value).with(:kernel).returns("GNU/kFreeBSD") + + Facter::Util::IP.get_interface_value("em0", "macaddress").should == "0:11:a:59:67:90" + end + it "should return netmask information for HP-UX" do sample_output_file = File.dirname(__FILE__) + "/../data/hpux_ifconfig_single_interface" hpux_ifconfig_interface = File.new(sample_output_file).read() @@ -188,6 +205,17 @@ Facter::Util::IP.get_interface_value("en1", "netmask").should == "255.255.255.0" end + it "should return a human readable netmask on GNU/kFreeBSD" do + sample_output_file = File.dirname(__FILE__) + "/../data/debian_kfreebsd_ifconfig" + + kfreebsd_ifconfig = File.new(sample_output_file).read() + + Facter::Util::IP.expects(:get_single_interface_output).with("em1").returns(kfreebsd_ifconfig) + Facter.stubs(:value).with(:kernel).returns("GNU/kFreeBSD") + + Facter::Util::IP.get_interface_value("em1", "netmask").should == "255.255.255.0" + end + it "should not get bonding master on interface aliases" do Facter.stubs(:value).with(:kernel).returns("Linux") diff --git a/spec/unit/util/virtual_spec.rb b/spec/unit/util/virtual_spec.rb index 12ba0ac0db..aa2de5af1b 100644 --- a/spec/unit/util/virtual_spec.rb +++ b/spec/unit/util/virtual_spec.rb @@ -128,12 +128,14 @@ end it "should identify FreeBSD jail when in jail" do + Facter.fact(:kernel).stubs(:value).returns("FreeBSD") Facter::Util::Resolution.stubs(:exec).with("/sbin/sysctl -n security.jail.jailed").returns("1") Facter::Util::Virtual.should be_jail end - it "should not identify FreeBSD jail when not in jail" do - Facter::Util::Resolution.stubs(:exec).with("/sbin/sysctl -n security.jail.jailed").returns("0") + it "should not identify GNU/kFreeBSD jail when not in jail" do + Facter.fact(:kernel).stubs(:value).returns("GNU/kFreeBSD") + Facter::Util::Resolution.stubs(:exec).with("/bin/sysctl -n security.jail.jailed").returns("0") Facter::Util::Virtual.should_not be_jail end From f5bf0f51de6237bd4e63cc22dd4d63bd773952e6 Mon Sep 17 00:00:00 2001 From: Daniel Pittman Date: Thu, 17 Feb 2011 12:49:22 -0800 Subject: [PATCH 0511/3753] (#6360) Flush Facter top level cache before every test case. This adds a suite-wide "Facter.clear" call before :each test case to ensure that we can't accidentally cache data when we are invoked using top level Facter interfaces to get at data. Paired-with: Jacob Helwig --- spec/spec_helper.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index d9db4455b3..28e7b72c3b 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -14,5 +14,10 @@ Dir["#{dir}/monkey_patches/*.rb"].map { |file| require file } RSpec.configure do |config| - config.mock_with :mocha + config.mock_with :mocha + + # Ensure that we don't accidentally cache between test cases. + config.before :each do + Facter.clear + end end From 77eb512a85619764a713744108116756969c6c3d Mon Sep 17 00:00:00 2001 From: Daniel Pittman Date: Thu, 17 Feb 2011 14:20:16 -0800 Subject: [PATCH 0512/3753] (#2270) Remove DWIM code from ipaddress on Darwin. This mirrors the behaviour of the newer code, and eliminates some DWIM work we do on a single, specific platform that should, better, be done on a global platform. This would be reintroduced globally, applied to all platforms, or otherwise rebuilt from a consistent base of specified behaviour. Paired-With: Matt Robinson Paired-With: Max Martin --- lib/facter/ipaddress.rb | 33 +-------------------------------- 1 file changed, 1 insertion(+), 32 deletions(-) diff --git a/lib/facter/ipaddress.rb b/lib/facter/ipaddress.rb index a08f26b4aa..08a5dc891a 100644 --- a/lib/facter/ipaddress.rb +++ b/lib/facter/ipaddress.rb @@ -19,7 +19,7 @@ end Facter.add(:ipaddress) do - confine :kernel => %w{FreeBSD OpenBSD} + confine :kernel => %w{FreeBSD OpenBSD Darwin} setcode do ip = nil output = %x{/sbin/ifconfig} @@ -58,37 +58,6 @@ end end -Facter.add(:ipaddress) do - confine :kernel => %w{darwin} - setcode do - ip = nil - iface = "" - output = %x{/usr/sbin/netstat -rn} - if output =~ /^default\s*\S*\s*\S*\s*\S*\s*\S*\s*(\S*).*/ - iface = $1 - else - warn "Could not find a default route. Using first non-loopback interface" - end - if(iface != "") - output = %x{/sbin/ifconfig #{iface}} - else - output = %x{/sbin/ifconfig} - end - - output.split(/^\S/).each { |str| - if str =~ /inet ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ - tmp = $1 - unless tmp =~ /^127\./ - ip = tmp - break - end - end - } - - ip - end -end - Facter.add(:ipaddress) do confine :kernel => %w{AIX} setcode do From ea2948395e4eed1a33f767df60ae28133c94442e Mon Sep 17 00:00:00 2001 From: Daniel Pittman Date: Tue, 1 Feb 2011 17:38:46 -0800 Subject: [PATCH 0513/3753] (#2270) add IPv6 support to facter core. Now that IPocalypse has happened, IPv6 support in Facter core would be nice to have. So, we add the appropriate code to start handling that. The ipaddress6 fact as supplied included some smart code to try determining the "primary" address using DNS to resolve the AAAA record for the host FQDN. While this was smart, it actually didn't work: facter prefers the longest confine list, so the *stupid* mechanisms that were kernel-specific would override the smarter and more portable mechanisms. We strip that code out for now, which also brings this into line with the existing ipaddress fact; improving both would be good, but it should be uniform. Paired-With: Matt Robinson Paired-With: Max Martin --- lib/facter/interfaces.rb | 2 +- lib/facter/ipaddress6.rb | 68 ++++++++++++++++++++++++++++++++++++++++ lib/facter/util/ip.rb | 3 ++ 3 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 lib/facter/ipaddress6.rb diff --git a/lib/facter/interfaces.rb b/lib/facter/interfaces.rb index 12392153ac..4fbaef1966 100644 --- a/lib/facter/interfaces.rb +++ b/lib/facter/interfaces.rb @@ -22,7 +22,7 @@ # Make a fact for each detail of each interface. Yay. # There's no point in confining these facts, since we wouldn't be able to create # them if we weren't running on a supported platform. - %w{ipaddress macaddress netmask}.each do |label| + %w{ipaddress ipaddress6 macaddress netmask}.each do |label| Facter.add(label + "_" + Facter::Util::IP.alphafy(interface)) do setcode do Facter::Util::IP.get_interface_value(interface, label) diff --git a/lib/facter/ipaddress6.rb b/lib/facter/ipaddress6.rb new file mode 100644 index 0000000000..547d636a73 --- /dev/null +++ b/lib/facter/ipaddress6.rb @@ -0,0 +1,68 @@ +# Cody Herriges +# +# Used the ipaddress fact that is already part of +# Facter as a template. + +# OS dependant code that parses the output of various networking +# tools and currently not very intelligent. Returns the first +# non-loopback and non-linklocal address found in the ouput unless +# a default route can be mapped to a routeable interface. Guessing +# an interface is currently only possible with BSD type systems +# to many assumptions have to be made on other platforms to make +# this work with the current code. Most code ported or modeled +# after the ipaddress fact for the sake of similar functionality +# and familiar mechanics. +Facter.add(:ipaddress6) do + confine :kernel => :linux + setcode do + ip = nil + output = Facter::Util::Resolution.exec('/sbin/ifconfig') + + output.scan(/inet6 addr: ((?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/).each { |str| + str = str.to_s + unless str =~ /fe80.*/ or str == "::1" + ip = str + end + } + + ip + + end +end + +Facter.add(:ipaddress6) do + confine :kernel => %w{SunOS} + setcode do + output = Facter::Util::Resolution.exec('/usr/sbin/ifconfig -a') + ip = nil + + output.scan(/inet6 ((?>[0-9,a-f,A-F]*\:{0,2})+[0-9,a-f,A-F]{0,4})/).each { |str| + str = str.to_s + unless str =~ /fe80.*/ or str == "::1" + ip = str + end + } + + ip + + end +end + +Facter.add(:ipaddress6) do + confine :kernel => %w{Darwin FreeBSD OpenBSD} + setcode do + output = Facter::Util::Resolution.exec('/sbin/ifconfig -a') + ip = nil + + output.scan(/inet6 ((?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/).each do |str| + str = str.to_s + unless str =~ /fe80.*/ or str == "::1" + ip = str + break + end + end + + ip + end +end + diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index 62d50a4703..23eeb9cdb4 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -6,17 +6,20 @@ module Facter::Util::IP REGEX_MAP = { :linux => { :ipaddress => /inet addr:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, + :ipaddress6 => /inet6 addr: ((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/, :macaddress => /(?:ether|HWaddr)\s+(\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/, :netmask => /Mask:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ }, :bsd => { :aliases => [:openbsd, :netbsd, :freebsd, :darwin, :"gnu/kfreebsd"], :ipaddress => /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, + :ipaddress6 => /inet6 ((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/, :macaddress => /(?:ether|lladdr)\s+(\w?\w:\w?\w:\w?\w:\w?\w:\w?\w:\w?\w)/, :netmask => /netmask\s+0x(\w{8})/ }, :sunos => { :ipaddress => /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, + :ipaddress6 => /inet6 ((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/, :macaddress => /(?:ether|lladdr)\s+(\w?\w:\w?\w:\w?\w:\w?\w:\w?\w:\w?\w)/, :netmask => /netmask\s+(\w{8})/ }, From cb25119b130337e5a9fff0c142ba18c55ebf6059 Mon Sep 17 00:00:00 2001 From: Daniel Pittman Date: Tue, 1 Feb 2011 17:48:54 -0800 Subject: [PATCH 0514/3753] (#2270) add testing for the new ipaddress6 feature This stubs out the platform side of the code, and uses fixtures emitting the right output from the interface configuration tools. Paired-With: Matt Robinson Paired-With: Max Martin --- .../bsd_ifconfig_all_with_multiple_interfaces | 18 ++++++++++ ...rwin_ifconfig_all_with_multiple_interfaces | 23 ++++++++++++ ...inux_ifconfig_all_with_multiple_interfaces | 19 ++++++++++ ...unos_ifconfig_all_with_multiple_interfaces | 10 ++++++ spec/unit/ipaddress6_spec.rb | 36 +++++++++++++++++++ 5 files changed, 106 insertions(+) create mode 100644 spec/fixtures/ifconfig/bsd_ifconfig_all_with_multiple_interfaces create mode 100644 spec/fixtures/ifconfig/darwin_ifconfig_all_with_multiple_interfaces create mode 100644 spec/fixtures/ifconfig/linux_ifconfig_all_with_multiple_interfaces create mode 100644 spec/fixtures/ifconfig/sunos_ifconfig_all_with_multiple_interfaces create mode 100755 spec/unit/ipaddress6_spec.rb diff --git a/spec/fixtures/ifconfig/bsd_ifconfig_all_with_multiple_interfaces b/spec/fixtures/ifconfig/bsd_ifconfig_all_with_multiple_interfaces new file mode 100644 index 0000000000..d5bff49462 --- /dev/null +++ b/spec/fixtures/ifconfig/bsd_ifconfig_all_with_multiple_interfaces @@ -0,0 +1,18 @@ +bge0: flags=8843 metric 0 mtu 1500 + options=9b + ether 00:0b:db:93:09:67 + inet 131.252.208.203 netmask 0xffffff00 broadcast 131.252.208.255 + inet6 fe80::20b:dbff:fe93:967%bge0 prefixlen 64 scopeid 0x1 + inet6 2610:10:20:208:20b:dbff:fe93:967 prefixlen 64 autoconf + media: Ethernet autoselect (1000baseT ) + status: active +bge1: flags=8802 metric 0 mtu 1500 + options=9b + ether 00:0b:db:93:09:68 + media: Ethernet autoselect (none) + status: no carrier +lo0: flags=8049 metric 0 mtu 16384 + options=3 + inet 127.0.0.1 netmask 0xff000000 + inet6 ::1 prefixlen 128 + inet6 fe80::1%lo0 prefixlen 64 scopeid 0x3 diff --git a/spec/fixtures/ifconfig/darwin_ifconfig_all_with_multiple_interfaces b/spec/fixtures/ifconfig/darwin_ifconfig_all_with_multiple_interfaces new file mode 100644 index 0000000000..a55999c3c1 --- /dev/null +++ b/spec/fixtures/ifconfig/darwin_ifconfig_all_with_multiple_interfaces @@ -0,0 +1,23 @@ +lo0: flags=8049 mtu 16384 + inet6 ::1 prefixlen 128 + inet6 fe80::1%lo0 prefixlen 64 scopeid 0x1 + inet 127.0.0.1 netmask 0xff000000 +gif0: flags=8010 mtu 1280 +stf0: flags=0<> mtu 1280 +en0: flags=8863 mtu 1500 + ether 00:23:32:d5:ee:34 + inet6 fe80::223:32ff:fed5:ee34%en0 prefixlen 64 scopeid 0x4 + inet6 2610:10:20:209:223:32ff:fed5:ee34 prefixlen 64 autoconf + inet 131.252.209.140 netmask 0xffffff00 broadcast 131.252.209.255 + media: autoselect (100baseTX ) + status: active +en1: flags=8863 mtu 1500 + ether 00:11:33:22:55:44 + inet6 fe80::211:33ff:fe22:5544%en1 prefixlen 64 scopeid 0x5 + inet 131.252.246.129 netmask 0xfffffe00 broadcast 131.252.247.255 + media: autoselect + status: active +fw0: flags=8863 mtu 4078 + lladdr 00:23:32:ff:fe:d5:ee:34 + media: autoselect + status: inactive diff --git a/spec/fixtures/ifconfig/linux_ifconfig_all_with_multiple_interfaces b/spec/fixtures/ifconfig/linux_ifconfig_all_with_multiple_interfaces new file mode 100644 index 0000000000..d944694d19 --- /dev/null +++ b/spec/fixtures/ifconfig/linux_ifconfig_all_with_multiple_interfaces @@ -0,0 +1,19 @@ +eth0 Link encap:Ethernet HWaddr 00:12:3f:be:22:01 + inet addr:131.252.209.153 Bcast:131.252.209.255 Mask:255.255.255.0 + inet6 addr: 2610:10:20:209:212:3fff:febe:2201/64 Scope:Global + inet6 addr: fe80::212:3fff:febe:2201/64 Scope:Link + UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 + RX packets:20793317 errors:0 dropped:0 overruns:0 frame:0 + TX packets:19583281 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:1000 + RX bytes:1723593481 (1.7 GB) TX bytes:283377282 (283.3 MB) + Interrupt:16 + +lo Link encap:Local Loopback + inet addr:127.0.0.1 Mask:255.0.0.0 + inet6 addr: ::1/128 Scope:Host + UP LOOPBACK RUNNING MTU:16436 Metric:1 + RX packets:31809 errors:0 dropped:0 overruns:0 frame:0 + TX packets:31809 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:0 + RX bytes:2075836 (2.0 MB) TX bytes:2075836 (2.0 MB) diff --git a/spec/fixtures/ifconfig/sunos_ifconfig_all_with_multiple_interfaces b/spec/fixtures/ifconfig/sunos_ifconfig_all_with_multiple_interfaces new file mode 100644 index 0000000000..c30efe0869 --- /dev/null +++ b/spec/fixtures/ifconfig/sunos_ifconfig_all_with_multiple_interfaces @@ -0,0 +1,10 @@ +lo0: flags=2001000849 mtu 8232 index 1 + inet 127.0.0.1 netmask ff000000 +bge0: flags=1000843 mtu 1500 index 2 + inet 131.252.209.59 netmask ffffff00 broadcast 131.252.209.255 +lo0: flags=2002000849 mtu 8252 index 1 + inet6 ::1/128 +bge0: flags=2000841 mtu 1500 index 2 + inet6 fe80::203:baff:fe27:a7c/10 +bge0:1: flags=2080841 mtu 1500 index 2 + inet6 2610:10:20:209:203:baff:fe27:a7c/64 diff --git a/spec/unit/ipaddress6_spec.rb b/spec/unit/ipaddress6_spec.rb new file mode 100755 index 0000000000..d5070230fd --- /dev/null +++ b/spec/unit/ipaddress6_spec.rb @@ -0,0 +1,36 @@ +#!/usr/bin/env ruby + +$basedir = File.expand_path(File.dirname(__FILE__) + '/..') +require File.join($basedir, 'spec_helper') + +require 'facter' + +def ifconfig_fixture(filename) + ifconfig = File.new(File.join($basedir, 'fixtures', 'ifconfig', filename)).read +end + +describe "IPv6 address fact" do + it "should return ipaddress6 information for Darwin" do + Facter::Util::Resolution.stubs(:exec).with('uname -s').returns('Darwin') + Facter::Util::Resolution.stubs(:exec).with('/sbin/ifconfig -a'). + returns(ifconfig_fixture('darwin_ifconfig_all_with_multiple_interfaces')) + + Facter.value(:ipaddress6).should == "2610:10:20:209:223:32ff:fed5:ee34" + end + + it "should return ipaddress6 information for Linux" do + Facter::Util::Resolution.stubs(:exec).with('uname -s').returns('Linux') + Facter::Util::Resolution.stubs(:exec).with('/sbin/ifconfig'). + returns(ifconfig_fixture('linux_ifconfig_all_with_multiple_interfaces')) + + Facter.value(:ipaddress6).should == "2610:10:20:209:212:3fff:febe:2201" + end + + it "should return ipaddress6 information for Solaris" do + Facter::Util::Resolution.stubs(:exec).with('uname -s').returns('SunOS') + Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/ifconfig -a'). + returns(ifconfig_fixture('sunos_ifconfig_all_with_multiple_interfaces')) + + Facter.value(:ipaddress6).should == "2610:10:20:209:203:baff:fe27:a7c" + end +end From eb5d6fca87990a7c1cd0442b70da3792059fe6dd Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Wed, 2 Mar 2011 00:24:11 +1100 Subject: [PATCH 0515/3753] Fixed #6525 - Test failures on Ruby 1.9.x --- spec/unit/util/manufacturer_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/unit/util/manufacturer_spec.rb b/spec/unit/util/manufacturer_spec.rb index 07473db9ff..c3b372ea00 100644 --- a/spec/unit/util/manufacturer_spec.rb +++ b/spec/unit/util/manufacturer_spec.rb @@ -109,8 +109,8 @@ def find_product_name(os) output_file = case os - when "FreeBSD": File.dirname(__FILE__) + "/../data/freebsd_dmidecode" - when "SunOS" : File.dirname(__FILE__) + "/../data/opensolaris_smbios" + when "FreeBSD" then File.dirname(__FILE__) + "/../data/freebsd_dmidecode" + when "SunOS" then File.dirname(__FILE__) + "/../data/opensolaris_smbios" end output = File.new(output_file).read() From 3e6217d63c73c720a7d697d810f556adf13f134b Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Wed, 2 Mar 2011 00:16:02 +1100 Subject: [PATCH 0516/3753] Fixes #6521 and other Ruby 1.9 issues Tested on Ruby 1.9.2p180 --- lib/facter/lsb.rb | 8 ++++---- lib/facter/util/virtual.rb | 4 ++-- lib/facter/util/vlans.rb | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/facter/lsb.rb b/lib/facter/lsb.rb index 4b98466b30..bf4b9db706 100644 --- a/lib/facter/lsb.rb +++ b/lib/facter/lsb.rb @@ -22,13 +22,13 @@ Facter.add(fact) do confine :kernel => [ :linux, :"gnu/kfreebsd" ] setcode do - unless defined?(@@lsbdata) and defined?(@@lsbtime) and (Time.now.to_i - @@lsbtime.to_i < 5) + unless defined?(lsbdata) and defined?(lsbtime) and (Time.now.to_i - lsbtime.to_i < 5) type = nil - @@lsbtime = Time.now - @@lsbdata = Facter::Util::Resolution.exec('lsb_release -a 2>/dev/null') + lsbtime = Time.now + lsbdata = Facter::Util::Resolution.exec('lsb_release -a 2>/dev/null') end - if pattern.match(@@lsbdata) + if pattern.match(lsbdata) $1 else nil diff --git a/lib/facter/util/virtual.rb b/lib/facter/util/virtual.rb index 129448ea16..06b1b6db9e 100644 --- a/lib/facter/util/virtual.rb +++ b/lib/facter/util/virtual.rb @@ -60,8 +60,8 @@ def self.kvm_type def self.jail? path = case Facter.value(:kernel) - when "FreeBSD": "/sbin" - when "GNU/kFreeBSD": "/bin" + when "FreeBSD" then "/sbin" + when "GNU/kFreeBSD" then "/bin" end Facter::Util::Resolution.exec("#{path}/sysctl -n security.jail.jailed") == "1" end diff --git a/lib/facter/util/vlans.rb b/lib/facter/util/vlans.rb index 6d226ff053..2b2a72f454 100644 --- a/lib/facter/util/vlans.rb +++ b/lib/facter/util/vlans.rb @@ -12,7 +12,7 @@ def self.get_vlan_config def self.get_vlans vlans = Array.new if self.get_vlan_config - self.get_vlan_config.each do |line| + self.get_vlan_config.each_line do |line| if line =~ /^([0-9A-Za-z]+)\.([0-9]+) / vlans.insert(-1, $~[2]) if $~[2] end @@ -21,4 +21,4 @@ def self.get_vlans vlans.join(',') end -end +end From 84fa3c440edefd117ce8a7fa5fb13d76b66ec7db Mon Sep 17 00:00:00 2001 From: Matt Date: Sat, 5 Mar 2011 15:34:04 -0600 Subject: [PATCH 0517/3753] (#6525) change semicolons to 'then' in case statement for ruby 1.9.2 compatibility --- lib/facter/util/virtual.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/facter/util/virtual.rb b/lib/facter/util/virtual.rb index 129448ea16..06b1b6db9e 100644 --- a/lib/facter/util/virtual.rb +++ b/lib/facter/util/virtual.rb @@ -60,8 +60,8 @@ def self.kvm_type def self.jail? path = case Facter.value(:kernel) - when "FreeBSD": "/sbin" - when "GNU/kFreeBSD": "/bin" + when "FreeBSD" then "/sbin" + when "GNU/kFreeBSD" then "/bin" end Facter::Util::Resolution.exec("#{path}/sysctl -n security.jail.jailed") == "1" end From d6ce08a4d7c7e0a7396cc2eacd32d18d9c95542d Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Mon, 7 Mar 2011 06:47:45 +1100 Subject: [PATCH 0518/3753] Fixed #6611 - Fixed broken HPVM test and rationalised test structure --- spec/unit/virtual_spec.rb | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index a152b409b5..5f63dab270 100644 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -6,9 +6,14 @@ describe "Virtual fact" do - after do - Facter.clear - end + before do + Facter::Util::Virtual.stubs(:zone?).returns(false) + Facter::Util::Virtual.stubs(:openvz?).returns(false) + Facter::Util::Virtual.stubs(:vserver?).returns(false) + Facter::Util::Virtual.stubs(:xen?).returns(false) + Facter::Util::Virtual.stubs(:kvm?).returns(false) + Facter::Util::Virtual.stubs(:hpvm?).returns(false) + end it "should be zone on Solaris when a zone" do Facter.fact(:kernel).stubs(:value).returns("SunOS") @@ -58,13 +63,6 @@ end describe "on Linux" do - before do - Facter::Util::Virtual.stubs(:zone?).returns(false) - Facter::Util::Virtual.stubs(:openvz?).returns(false) - Facter::Util::Virtual.stubs(:vserver?).returns(false) - Facter::Util::Virtual.stubs(:xen?).returns(false) - Facter::Util::Virtual.stubs(:kvm?).returns(false) - end it "should be parallels with Parallels vendor id from lspci" do Facter.fact(:kernel).stubs(:value).returns("Linux") @@ -114,15 +112,10 @@ Facter.fact(:virtual).value.should == "parallels" end end - end describe "is_virtual fact" do - after do - Facter.clear - end - it "should be virtual when running on xen" do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter.fact(:virtual).stubs(:value).returns("xenu") From 7dd730d4cd7aebf0552647632e709570f15831ec Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Mon, 7 Mar 2011 11:16:44 +1100 Subject: [PATCH 0519/3753] Fixed #5699 - Added virtual support for s390x/Zlinux Patch modified from Hector Rivas --- lib/facter/util/virtual.rb | 3 +++ lib/facter/virtual.rb | 4 ++++ spec/unit/virtual_spec.rb | 20 ++++++++++++++++++-- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/lib/facter/util/virtual.rb b/lib/facter/util/virtual.rb index 06b1b6db9e..4355451c7b 100644 --- a/lib/facter/util/virtual.rb +++ b/lib/facter/util/virtual.rb @@ -70,4 +70,7 @@ def self.hpvm? Facter::Util::Resolution.exec("/usr/bin/getconf MACHINE_MODEL").chomp =~ /Virtual Machine/ end + def self.zlinux? + "zlinux" + end end diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 47c9504539..a5954cd87d 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -31,6 +31,10 @@ result = "hpvm" if Facter::Util::Virtual.hpvm? end + if Facter.value(:architecture)=="s390x" + result = "zlinux" if Facter::Util::Virtual.zlinux? + end + if Facter::Util::Virtual.openvz? result = Facter::Util::Virtual.openvz_type() end diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index 5f63dab270..f5a5c3c893 100644 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -5,7 +5,6 @@ require 'facter/util/macosx' describe "Virtual fact" do - before do Facter::Util::Virtual.stubs(:zone?).returns(false) Facter::Util::Virtual.stubs(:openvz?).returns(false) @@ -13,6 +12,7 @@ Facter::Util::Virtual.stubs(:xen?).returns(false) Facter::Util::Virtual.stubs(:kvm?).returns(false) Facter::Util::Virtual.stubs(:hpvm?).returns(false) + Facter::Util::Virtual.stubs(:zlinux?).returns(false) end it "should be zone on Solaris when a zone" do @@ -36,6 +36,13 @@ Facter.fact(:virtual).value.should == "hpvm" end + it "should be zlinux on s390x" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:architecture).stubs(:value).returns("s390x") + Facter::Util::Virtual.stubs(:zlinux?).returns(true) + Facter.fact(:virtual).value.should == "zlinux" + end + describe "on Darwin" do it "should be parallels with Parallels vendor id" do Facter.fact(:kernel).stubs(:value).returns("Darwin") @@ -63,7 +70,10 @@ end describe "on Linux" do - + before do + Facter.fact(:architecture).stubs(:value).returns(true) + end + it "should be parallels with Parallels vendor id from lspci" do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter::Util::Resolution.stubs(:exec).with('lspci').returns("01:00.0 VGA compatible controller: Unknown device 1ab8:4005") @@ -170,6 +180,12 @@ Facter.fact(:is_virtual).value.should == "true" end + it "should be true when running on S390" do + Facter.fact(:architecture).stubs(:value).returns("s390x") + Facter.fact(:virtual).stubs(:value).returns("zlinux") + Facter.fact(:is_virtual).value.should == "true" + end + it "should be true when running on parallels" do Facter.fact(:kernel).stubs(:value).returns("Darwin") Facter.fact(:virtual).stubs(:value).returns("parallels") From 52026ee4dbf5544419e118422161c7cc8ec1ead8 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Mon, 7 Mar 2011 11:59:58 +1100 Subject: [PATCH 0520/3753] Fixed #5699 - Added processorcount support for S390x Patch from Hector Rivas --- lib/facter/processor.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb index ac75867169..c71bad413f 100644 --- a/lib/facter/processor.rb +++ b/lib/facter/processor.rb @@ -17,6 +17,9 @@ elsif l =~ /model name\s+:\s+(.*)\s*$/ processor_list[processor_num] = $1 unless processor_num == -1 processor_num = -1 + elsif l =~ /processor\s+(\d+):\s+(.*)/ + processor_num = $1.to_i + processor_list[processor_num] = $2 unless processor_num == -1 end end end @@ -80,9 +83,9 @@ Facter::Util::Resolution.exec("uname -p") end end - + Facter.add("ProcessorCount") do - confine :kernel => :openbsd + confine :kernel => :openbsd setcode do Facter::Util::Resolution.exec("sysctl hw.ncpu | cut -d'=' -f2") end From dd5d5bfc2b549e49b065de740572cb901079bf12 Mon Sep 17 00:00:00 2001 From: William Van Hevelingen Date: Sun, 27 Feb 2011 22:36:15 -0800 Subject: [PATCH 0521/3753] (#4925) - MS Windows doesn't do man pages Added logic to prevent installation of man pages on windows Signed-off-by: William Van Hevelingen --- install.rb | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/install.rb b/install.rb index bbf4007c8a..eb91e7c9fc 100755 --- a/install.rb +++ b/install.rb @@ -97,16 +97,20 @@ def do_libs(libs, strip = 'lib/') end def do_man(man, strip = 'man/') + if (InstallOptions.man == true) man.each do |mf| - omf = File.join(InstallOptions.man_dir, mf.gsub(/#{strip}/, '')) - om = File.dirname(omf) - FileUtils.makedirs(om, {:mode => 0755, :verbose => true}) - FileUtils.chmod(0755, om) - FileUtils.install(mf, omf, {:mode => 0644, :verbose => true}) - gzip = %x{which gzip} - gzip.chomp! - %x{#{gzip} -f #{omf}} + omf = File.join(InstallOptions.man_dir, mf.gsub(/#{strip}/, '')) + om = File.dirname(omf) + FileUtils.makedirs(om, {:mode => 0755, :verbose => true}) + FileUtils.chmod(0755, om) + FileUtils.install(mf, omf, {:mode => 0644, :verbose => true}) + gzip = %x{which gzip} + gzip.chomp! + %x{#{gzip} -f #{omf}} end + else + puts "Skipping Man Page Generation" + end end # Verify that all of the prereqs are installed @@ -121,6 +125,10 @@ def check_prereqs } end +def is_windows? + RUBY_PLATFORM.to_s.match(/mswin|win32|dos|cygwin|mingw/) +end + ## # Prepare the file installation. # @@ -128,7 +136,7 @@ def prepare_installation # Only try to do docs if we're sure they have rdoc if $haverdoc InstallOptions.rdoc = true - if RUBY_PLATFORM == "i386-mswin32" + if is_windows? InstallOptions.ri = false else InstallOptions.ri = true @@ -141,7 +149,7 @@ def prepare_installation if $haveman InstallOptions.man = true - if RUBY_PLATFORM == "i386-mswin32" + if is_windows? InstallOptions.man = false end else From 3c7841e8d10ee8cd409e15cc85683ef15fbca6d9 Mon Sep 17 00:00:00 2001 From: William Van Hevelingen Date: Thu, 23 Dec 2010 18:05:08 -0800 Subject: [PATCH 0522/3753] (#5666) windows support for facter/id.rb Signed-off-by: William Van Hevelingen --- lib/facter/id.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/id.rb b/lib/facter/id.rb index c2ab3b7d99..c2c359454b 100644 --- a/lib/facter/id.rb +++ b/lib/facter/id.rb @@ -1,5 +1,5 @@ Facter.add(:id) do - confine :operatingsystem => %w{Linux Fedora RedHat CentOS SuSE SLES Debian Ubuntu Gentoo AIX OEL OVS GNU/kFreeBSD} + confine :operatingsystem => %w{Linux Fedora RedHat CentOS SuSE SLES Debian Ubuntu Gentoo AIX OEL OVS GNU/kFreeBSD windows} setcode "whoami" end From 2e06cdcc820ad045f345579901d3d9f04530e0c6 Mon Sep 17 00:00:00 2001 From: Rick Bradley Date: Sun, 6 Mar 2011 21:38:49 -0600 Subject: [PATCH 0523/3753] (#6615) fix missing stub calls in loader specs It's not clear to me how these are now needed and apparently weren't when we first checked this in. These stubs will let the specs continue on so we can check the load order, which was the original intent of (#5510) which caused the introduction of these specs. Signed-off-by: Rick Bradley --- spec/unit/util/loader_spec.rb | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/spec/unit/util/loader_spec.rb b/spec/unit/util/loader_spec.rb index eed533a4fe..90530e83b0 100755 --- a/spec/unit/util/loader_spec.rb +++ b/spec/unit/util/loader_spec.rb @@ -119,7 +119,10 @@ def with_env(values) @loader.stubs(:search_path).returns %w{/one/dir} Dir.stubs(:entries).with("/one/dir/testing").returns %w{foo.rb bar.rb} - %w{/one/dir/testing/foo.rb /one/dir/testing/bar.rb}.each { |f| File.stubs(:directory?).with(f).returns false } + %w{/one/dir/testing/foo.rb /one/dir/testing/bar.rb}.each do |f| + File.stubs(:directory?).with(f).returns false + Kernel.stubs(:load).with(f) + end @loader.load(:testing) @loader.loaded_files.should == %w{/one/dir/testing/bar.rb /one/dir/testing/foo.rb} @@ -202,7 +205,11 @@ def with_env(values) Dir.stubs(:entries).with("/one/dir").returns %w{foo.rb bar.rb} %w{/one/dir}.each { |f| File.stubs(:directory?).with(f).returns true } - %w{/one/dir/foo.rb /one/dir/bar.rb}.each { |f| File.stubs(:directory?).with(f).returns false } + + %w{/one/dir/foo.rb /one/dir/bar.rb}.each do |f| + File.stubs(:directory?).with(f).returns false + Kernel.expects(:load).with(f) + end @loader.load_all From 93461d9ba2f798beedf67ca28d4dee3da1a7c4cf Mon Sep 17 00:00:00 2001 From: Dominic Cleal Date: Thu, 20 Jan 2011 16:40:33 +0000 Subject: [PATCH 0524/3753] Fixed #5950 - Solaris ipaddress incorrect after bonding failure --- lib/facter/ipaddress.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/ipaddress.rb b/lib/facter/ipaddress.rb index 08a5dc891a..d5634087f5 100644 --- a/lib/facter/ipaddress.rb +++ b/lib/facter/ipaddress.rb @@ -47,7 +47,7 @@ output.split(/^\S/).each { |str| if str =~ /inet ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ tmp = $1 - unless tmp =~ /^127\./ + unless tmp =~ /^127\./ or tmp == "0.0.0.0" ip = tmp break end From ba2601f6eeb0428bf46dbb312241077c6aa37754 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Tue, 8 Mar 2011 09:30:03 +1100 Subject: [PATCH 0525/3753] Fix for #6495 - Updated interface detection 1. Fixed IP return to not filter lo/localhost and return it as a proper interface 2. Fixed HP-UX netstat return to remove extraneous first line of naming. 3. Updated tests to reflect changes --- lib/facter/util/ip.rb | 18 ++++++++---------- spec/unit/data/hpux_netstat_all_interfaces | 9 +++------ spec/unit/util/ip_spec.rb | 20 ++++++++++---------- 3 files changed, 21 insertions(+), 26 deletions(-) diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index 23eeb9cdb4..e4370dc7c3 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -59,7 +59,7 @@ def self.get_interfaces # at the end of interfaces. So, we have to trim those trailing # characters. I tried making the regex better but supporting all # platforms with a single regex is probably a bit too much. - output.scan(/^[-\w]+[.:]?\d+[.:]?\d*[.:]?\w*/).collect { |i| i.sub(/:$/, '') }.uniq + output.scan(/^\S+/).collect { |i| i.sub(/:$/, '') }.uniq end def self.get_all_interface_output @@ -69,7 +69,7 @@ def self.get_all_interface_output when 'SunOS' output = %x{/usr/sbin/ifconfig -a} when 'HP-UX' - output = %x{/bin/netstat -i} + output = %x{/bin/netstat -in | sed -e 1d} end output end @@ -141,15 +141,13 @@ def self.get_interface_value(interface, label) else output_int = get_single_interface_output(interface) - if interface != /^lo[0:]?\d?/ - output_int.each_line do |s| - if s =~ regex - value = $1 + output_int.each_line do |s| + if s =~ regex + value = $1 if label == 'netmask' && convert_from_hex?(kernel) value = value.scan(/../).collect do |byte| byte.to_i(16) end.join('.') end - tmp1.push(value) - end + tmp1.push(value) end end @@ -158,13 +156,13 @@ def self.get_interface_value(interface, label) end end end - + def self.get_network_value(interface) require 'ipaddr' ipaddress = get_interface_value(interface, "ipaddress") netmask = get_interface_value(interface, "netmask") - + if ipaddress && netmask ip = IPAddr.new(ipaddress, Socket::AF_INET) subnet = IPAddr.new(netmask, Socket::AF_INET) diff --git a/spec/unit/data/hpux_netstat_all_interfaces b/spec/unit/data/hpux_netstat_all_interfaces index 7745fa8701..0e8f9dc0bf 100644 --- a/spec/unit/data/hpux_netstat_all_interfaces +++ b/spec/unit/data/hpux_netstat_all_interfaces @@ -1,6 +1,3 @@ -Name Mtu Network Address Ipkts Ierrs Opkts Oerrs Coll -lan1 1500 15.12.0.0 host1.default.com - 572527659 0 1129421249 0 0 -lan0 1500 172.54.85.0 host2.default.com - 519222647 0 329127145 0 0 -lo0 4136 loopback localhost 14281117 0 14281125 0 0 +lan1 1500 192.168.100.0 192.168.100.182 12964 0 900 0 0 +lan0 1500 192.168.100.0 192.168.100.181 12964 0 715 0 0 +lo0 4136 127.0.0.0 127.0.0.1 98 0 98 0 0 diff --git a/spec/unit/util/ip_spec.rb b/spec/unit/util/ip_spec.rb index ceceb3fc2a..1a545b885a 100755 --- a/spec/unit/util/ip_spec.rb +++ b/spec/unit/util/ip_spec.rb @@ -20,11 +20,11 @@ Facter::Util::IP.get_interfaces().should == [] end - it "should return a list with a single interface on Linux with a single interface" do + it "should return a list with a single interface and the loopback interface on Linux with a single interface" do sample_output_file = File.dirname(__FILE__) + '/../data/linux_ifconfig_all_with_single_interface' linux_ifconfig = File.new(sample_output_file).read() Facter::Util::IP.stubs(:get_all_interface_output).returns(linux_ifconfig) - Facter::Util::IP.get_interfaces().should == ["eth0"] + Facter::Util::IP.get_interfaces().should == ["eth0", "lo"] end it "should return a list two interfaces on Darwin with two interfaces" do @@ -46,7 +46,7 @@ hpux_netstat = File.new(sample_output_file).read() Facter::Util::IP.stubs(:get_all_interface_output).returns(hpux_netstat) Facter::Util::IP.get_interfaces().should == ["lan1", "lan0", "lo0"] - end + end it "should return a list of six interfaces on a GNU/kFreeBSD with six interfaces" do sample_output_file = File.dirname(__FILE__) + '/../data/debian_kfreebsd_ifconfig' @@ -102,7 +102,7 @@ Facter.stubs(:value).with(:kernel).returns("HP-UX") Facter::Util::IP.get_interface_value("lan0", "ipaddress").should == "168.24.80.71" - end + end it "should return macaddress information for HP-UX" do sample_output_file = File.dirname(__FILE__) + "/../data/hpux_ifconfig_single_interface" @@ -112,7 +112,7 @@ Facter.stubs(:value).with(:kernel).returns("HP-UX") Facter::Util::IP.get_interface_value("lan0", "macaddress").should == "00:13:21:BD:9C:B7" - end + end it "should return macaddress with leading zeros stripped off for GNU/kFreeBSD" do sample_output_file = File.dirname(__FILE__) + "/../data/debian_kfreebsd_ifconfig" @@ -132,7 +132,7 @@ Facter.stubs(:value).with(:kernel).returns("HP-UX") Facter::Util::IP.get_interface_value("lan0", "netmask").should == "255.255.255.0" - end + end it "should return calculated network information for HP-UX" do sample_output_file = File.dirname(__FILE__) + "/../data/hpux_ifconfig_single_interface" @@ -142,7 +142,7 @@ Facter.stubs(:value).with(:kernel).returns("HP-UX") Facter::Util::IP.get_network_value("lan0").should == "168.24.80.0" - end + end it "should return interface information for FreeBSD supported via an alias" do sample_output_file = File.dirname(__FILE__) + "/../data/6.0-STABLE_FreeBSD_ifconfig" @@ -192,7 +192,7 @@ Facter.stubs(:value).with(:kernel).returns("HP-UX") Facter::Util::IP.get_interface_value("lan0", "netmask").should == "255.255.255.0" - end + end it "should return a human readable netmask on Darwin" do sample_output_file = File.dirname(__FILE__) + "/../data/darwin_ifconfig_single_interface" @@ -204,7 +204,7 @@ Facter::Util::IP.get_interface_value("en1", "netmask").should == "255.255.255.0" end - + it "should return a human readable netmask on GNU/kFreeBSD" do sample_output_file = File.dirname(__FILE__) + "/../data/debian_kfreebsd_ifconfig" @@ -218,7 +218,7 @@ it "should not get bonding master on interface aliases" do Facter.stubs(:value).with(:kernel).returns("Linux") - + Facter::Util::IP.get_bonding_master("eth0:1").should be_nil end From 214da73ac765dbff83831b2674d304046bbd3749 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Mon, 7 Mar 2011 20:25:14 +1100 Subject: [PATCH 0526/3753] Fixed #5485 - Updated selinux_mode fact 1. Added tested 2. Refactored to use F:U:R.exec 3. Chomp trailing newline --- lib/facter/selinux.rb | 11 +++++++---- spec/unit/data/selinux_sestatus | 4 ++++ spec/unit/selinux_spec.rb | 15 +++++++++++++-- 3 files changed, 24 insertions(+), 6 deletions(-) create mode 100644 spec/unit/data/selinux_sestatus diff --git a/lib/facter/selinux.rb b/lib/facter/selinux.rb index 0e9637d93d..ee663a0367 100644 --- a/lib/facter/selinux.rb +++ b/lib/facter/selinux.rb @@ -4,7 +4,7 @@ Facter.add("selinux") do confine :kernel => :linux - setcode do + setcode do result = "false" if FileTest.exists?("/selinux/enforce") if FileTest.exists?("/proc/self/attr/current") @@ -31,7 +31,7 @@ Facter.add("selinux_policyversion") do confine :selinux => :true - setcode do + setcode do File.read("/selinux/policyvers") end end @@ -39,7 +39,10 @@ Facter.add("selinux_mode") do confine :selinux => :true setcode do - %x{/usr/sbin/sestatus | /bin/grep "Policy from config file:" | awk '{print $5}'} + mode = Facter::Util::Resolution.exec('/usr/sbin/sestatus') + mode.each_line do |l| + mode = $1 if l =~ /^Current Mode:\s+(\w+)$/ + end + mode.chomp end end - diff --git a/spec/unit/data/selinux_sestatus b/spec/unit/data/selinux_sestatus new file mode 100644 index 0000000000..b16777f05c --- /dev/null +++ b/spec/unit/data/selinux_sestatus @@ -0,0 +1,4 @@ +SELinux status: enabled +SELinuxfs mount: /selinux +Current Mode: permissive +Policy version: 16 diff --git a/spec/unit/selinux_spec.rb b/spec/unit/selinux_spec.rb index 43fd5bf679..2af95836c6 100755 --- a/spec/unit/selinux_spec.rb +++ b/spec/unit/selinux_spec.rb @@ -31,11 +31,11 @@ File.stubs(:read).with("/selinux/enforce").returns("0") FileTest.expects(:exists?).with("/selinux/enforce").returns true - File.expects(:read).with("/selinux/enforce").returns("1") + File.expects(:read).with("/selinux/enforce").returns("1") Facter.fact(:selinux_enforced).value.should == "true" end - + it "should return an SELinux policy version" do Facter.fact(:selinux).stubs(:value).returns("true") @@ -45,4 +45,15 @@ Facter.fact(:selinux_policyversion).value.should == "1" end + + it "should return the SELinux policy mode" do + Facter.fact(:selinux).stubs(:value).returns("true") + + sample_output_file = File.dirname(__FILE__) + '/data/selinux_sestatus' + selinux_sestatus = File.read(sample_output_file) + + Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/sestatus').returns(selinux_sestatus) + + Facter.fact(:selinux_mode).value.should == "permissive" + end end From 868e7ba51dd9511e3f23af65a51c0fc7392a76d2 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 8 Mar 2011 16:11:46 -0800 Subject: [PATCH 0527/3753] (#5485) Made selinux_mode fact work Changed regular expression to be case insensitive for cross platform compatibility. --- lib/facter/selinux.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/facter/selinux.rb b/lib/facter/selinux.rb index ee663a0367..73e323953e 100644 --- a/lib/facter/selinux.rb +++ b/lib/facter/selinux.rb @@ -39,10 +39,9 @@ Facter.add("selinux_mode") do confine :selinux => :true setcode do + result = 'unknown' mode = Facter::Util::Resolution.exec('/usr/sbin/sestatus') - mode.each_line do |l| - mode = $1 if l =~ /^Current Mode:\s+(\w+)$/ - end - mode.chomp + mode.each_line { |l| result = $1 if l =~ /^Current mode\:\s+(\w+)$/i } + result.chomp end end From 6d6d8daf2b75844d035012695db73e4c67df17a0 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 8 Mar 2011 13:23:34 -0800 Subject: [PATCH 0528/3753] (#2721) Merged patch from Brane GraAnar - Adds support for Slamd64 and Bluewhite64 for the operatingsystem fact - Adds support for Slamd64 and Bluewhite64 for the operatingsystemrelease fact --- lib/facter/operatingsystem.rb | 8 ++++++-- lib/facter/operatingsystemrelease.rb | 26 +++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index c5a3dc1a65..061e18e3cb 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -50,8 +50,12 @@ else "SuSE" end - elsif FileTest.exists?("/etc/slackware-version") - "Slackware" + elsif FileTest.exists?("/etc/bluewhite64-version") + "Bluewhite64" + elsif FileTest.exists?("/etc/slamd64-version") + "Slamd64" + elsif FileTest.exists?("/etc/slackware-version") + "Slackware" end end end diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index 280208ba50..ac9be65ee7 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -72,5 +72,29 @@ end Facter.add(:operatingsystemrelease) do - setcode do Facter[:kernelrelease].value end + confine :operatingsystem => %w{Bluewhite64} + setcode do + releasefile = Facter::Util::Resolution.exec('cat /etc/bluewhite64-version') + if releasefile =~ /^\s*\w+\s+(\d+)\.(\d+)/ + $1 + "." + $2 + else + "unknown" + end + end +end + +Facter.add(:operatingsystemrelease) do + confine :operatingsystem => %w{Slamd64} + setcode do + releasefile = Facter::Util::Resolution.exec('cat /etc/slamd64-version') + if releasefile =~ /^\s*\w+\s+(\d+)\.(\d+)/ + $1 + "." + $2 + else + "unknown" + end + end +end + +Facter.add(:operatingsystemrelease) do + setcode do Facter[:kernelrelease].value end end From dea6f78ace56df3459dd182300cc9618df712317 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Fri, 11 Mar 2011 10:42:41 +1100 Subject: [PATCH 0529/3753] Further fix to #5485 - SELinux facts 1. Added new facts for all values returned by the sestatus command 2. Updated legacy selinux_mode fact with former value 3. Added note and ticket #6677 to remove legacy fact at Facter 2.0.0 4. Added tests for new facts and legacy fact --- lib/facter/selinux.rb | 33 +++++++++++++++++++++++++++++++- spec/unit/data/selinux_sestatus | 2 ++ spec/unit/selinux_spec.rb | 34 +++++++++++++++++++++++++++++++-- 3 files changed, 66 insertions(+), 3 deletions(-) diff --git a/lib/facter/selinux.rb b/lib/facter/selinux.rb index 73e323953e..9fab4276b8 100644 --- a/lib/facter/selinux.rb +++ b/lib/facter/selinux.rb @@ -36,7 +36,7 @@ end end -Facter.add("selinux_mode") do +Facter.add("selinux_current_mode") do confine :selinux => :true setcode do result = 'unknown' @@ -45,3 +45,34 @@ result.chomp end end + +Facter.add("selinux_config_mode") do + confine :selinux => :true + setcode do + result = 'unknown' + mode = Facter::Util::Resolution.exec('/usr/sbin/sestatus') + mode.each_line { |l| result = $1 if l =~ /^Mode from config file\:\s+(\w+)$/i } + result.chomp + end +end + +Facter.add("selinux_config_policy") do + confine :selinux => :true + setcode do + result = 'unknown' + mode = Facter::Util::Resolution.exec('/usr/sbin/sestatus') + mode.each_line { |l| result = $1 if l =~ /^Policy from config file\:\s+(\w+)$/i } + result.chomp + end +end + +# This is a legacy fact which returns the old selinux_mode fact value to prevent +# breakages of existing manifests. It should be removed at the next major release. +# See ticket #6677. + +Facter.add("selinux_mode") do + confine :selinux => :true + setcode do + Facter.value(:selinux_config_policy) + end +end diff --git a/spec/unit/data/selinux_sestatus b/spec/unit/data/selinux_sestatus index b16777f05c..50cea132e3 100644 --- a/spec/unit/data/selinux_sestatus +++ b/spec/unit/data/selinux_sestatus @@ -1,4 +1,6 @@ SELinux status: enabled SELinuxfs mount: /selinux Current Mode: permissive +Mode from config file: permissive Policy version: 16 +Policy from config file: targeted diff --git a/spec/unit/selinux_spec.rb b/spec/unit/selinux_spec.rb index 2af95836c6..d8209587ad 100755 --- a/spec/unit/selinux_spec.rb +++ b/spec/unit/selinux_spec.rb @@ -46,7 +46,7 @@ Facter.fact(:selinux_policyversion).value.should == "1" end - it "should return the SELinux policy mode" do + it "should return the SELinux current mode" do Facter.fact(:selinux).stubs(:value).returns("true") sample_output_file = File.dirname(__FILE__) + '/data/selinux_sestatus' @@ -54,6 +54,36 @@ Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/sestatus').returns(selinux_sestatus) - Facter.fact(:selinux_mode).value.should == "permissive" + Facter.fact(:selinux_current_mode).value.should == "permissive" + end + + it "should return the SELinux mode from the configuration file" do + Facter.fact(:selinux).stubs(:value).returns("true") + + sample_output_file = File.dirname(__FILE__) + '/data/selinux_sestatus' + selinux_sestatus = File.read(sample_output_file) + + Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/sestatus').returns(selinux_sestatus) + + Facter.fact(:selinux_config_mode).value.should == "permissive" + end + + it "should return the SELinux configuration file policy" do + Facter.fact(:selinux).stubs(:value).returns("true") + + sample_output_file = File.dirname(__FILE__) + '/data/selinux_sestatus' + selinux_sestatus = File.read(sample_output_file) + + Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/sestatus').returns(selinux_sestatus) + + Facter.fact(:selinux_config_policy).value.should == "targeted" + end + + it "should ensure legacy selinux_mode facts returns same value as selinux_config_policy fact" do + Facter.fact(:selinux).stubs(:value).returns("true") + + Facter.fact(:selinux_config_policy).stubs(:value).returns("targeted") + + Facter.fact(:selinux_mode).value.should == "targeted" end end From d718af449fc30585fcc672861077ec9045a4ef03 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Fri, 11 Mar 2011 11:51:43 +1100 Subject: [PATCH 0530/3753] Fix #6679 - Added Scientific Linux to operatingsystem fact Thanks to James Goddard for the patch --- lib/facter/operatingsystem.rb | 2 ++ spec/unit/operatingsystem_spec.rb | 12 ++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index c5a3dc1a65..bda8ba3efb 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -36,6 +36,8 @@ txt = File.read("/etc/redhat-release") if txt =~ /centos/i "CentOS" + elsif txt =~ /scientific/i + "Scientific" else "RedHat" end diff --git a/spec/unit/operatingsystem_spec.rb b/spec/unit/operatingsystem_spec.rb index be83916810..73b36492e6 100755 --- a/spec/unit/operatingsystem_spec.rb +++ b/spec/unit/operatingsystem_spec.rb @@ -9,23 +9,23 @@ before do Facter.clear end - + after do Facter.clear end - + it "should default to the kernel name" do Facter.fact(:kernel).stubs(:value).returns("Nutmeg") Facter.fact(:operatingsystem).value.should == "Nutmeg" end - + it "should be Solaris for SunOS" do Facter.fact(:kernel).stubs(:value).returns("SunOS") - + Facter.fact(:operatingsystem).value.should == "Solaris" end - + it "should identify Oracle VM as OVS" do Facter.fact(:kernel).stubs(:value).returns("Linux") @@ -33,7 +33,7 @@ FileTest.expects(:exists?).with("/etc/ovs-release").returns true FileTest.expects(:exists?).with("/etc/enterprise-release").returns true - + Facter.fact(:operatingsystem).value.should == "OVS" end end From 2e48e189910f75cc1a6586bbecda27ecbd0afc8e Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Mon, 14 Mar 2011 13:57:32 +1100 Subject: [PATCH 0531/3753] Fixed #6695 - Updated id fact for Darwin et al --- lib/facter/id.rb | 11 ++--------- spec/unit/id_spec.rb | 27 +++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 9 deletions(-) create mode 100755 spec/unit/id_spec.rb diff --git a/lib/facter/id.rb b/lib/facter/id.rb index c2c359454b..1c4228469f 100644 --- a/lib/facter/id.rb +++ b/lib/facter/id.rb @@ -1,15 +1,8 @@ Facter.add(:id) do - confine :operatingsystem => %w{Linux Fedora RedHat CentOS SuSE SLES Debian Ubuntu Gentoo AIX OEL OVS GNU/kFreeBSD windows} setcode "whoami" end Facter.add(:id) do - confine :operatingsystem => %w{Solaris} - setcode do - if %x{id} =~ /^uid=\d+\((\S+)\)/ - $1 - else - nil - end - end + confine :kernel => :SunOS + setcode "/usr/xpg4/bin/id -un" end diff --git a/spec/unit/id_spec.rb b/spec/unit/id_spec.rb new file mode 100755 index 0000000000..a9e1797d73 --- /dev/null +++ b/spec/unit/id_spec.rb @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') + +describe "id fact" do + + kernel = [ 'Linux', 'Darwin', 'windows', 'FreeBSD', 'OpenBSD', 'NetBSD', 'AIX', 'HP-UX' ] + + kernel.each do |k| + describe "with kernel reported as #{k}" do + it "should return the current user" do + Facter::Util::Resolution.stubs(:exec).with('uname -s').returns(k) + Facter::Util::Resolution.stubs(:exec).with('lsb_release -a 2>/dev/null').returns('foo') + Facter::Util::Resolution.expects(:exec).once.with('whoami', '/bin/sh').returns 'bar' + + Facter.fact(:id).value.should == 'bar' + end + end + end + + it "should return the current user on Solaris" do + Facter::Util::Resolution.stubs(:exec).with('uname -s').returns('SunOS') + Facter::Util::Resolution.expects(:exec).once.with('/usr/xpg4/bin/id -un', '/bin/sh').returns 'bar' + + Facter.fact(:id).value.should == 'bar' + end +end From aa959df77ac353c25672f3a234298f5ae63a3a79 Mon Sep 17 00:00:00 2001 From: Ben Hughes Date: Mon, 14 Mar 2011 15:53:45 +1100 Subject: [PATCH 0532/3753] Remove Solaris from the list of confined systems. It won't get the original lsb facts, and it's nonsensical too. --- lib/facter/lsbmajdistrelease.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/lsbmajdistrelease.rb b/lib/facter/lsbmajdistrelease.rb index 997e7efd90..34a2f1ea3d 100644 --- a/lib/facter/lsbmajdistrelease.rb +++ b/lib/facter/lsbmajdistrelease.rb @@ -3,7 +3,7 @@ require 'facter' Facter.add("lsbmajdistrelease") do - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE SLES Debian Ubuntu Gentoo OEL OVS GNU/kFreeBSD} + confine :operatingsystem => %w{Linux Fedora RedHat CentOS SuSE SLES Debian Ubuntu Gentoo OEL OVS GNU/kFreeBSD} setcode do if /(\d*)\./i =~ Facter.value(:lsbdistrelease) result=$1 From 8d71db38d458186fb5512f41eae55c0c947804ac Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Tue, 15 Mar 2011 18:39:04 +1100 Subject: [PATCH 0533/3753] Fixed #6616 - Stubbing in VMware tests on Linux --- lib/facter/virtual.rb | 2 +- spec/unit/virtual_spec.rb | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 47c9504539..cf84f7502b 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -92,7 +92,7 @@ end end end - # VMware server 1.0.3 rpm places vmware-vmx in this place, other versions or platforms may not. + if FileTest.exists?("/usr/lib/vmware/bin/vmware-vmx") result = "vmware_server" end diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index 5f63dab270..c1bc3cb7a5 100644 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -64,6 +64,10 @@ describe "on Linux" do + before do + FileTest.expects(:exists?).with("/usr/lib/vmware/bin/vmware-vmx").returns false + end + it "should be parallels with Parallels vendor id from lspci" do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter::Util::Resolution.stubs(:exec).with('lspci').returns("01:00.0 VGA compatible controller: Unknown device 1ab8:4005") From 1207765dde99a84ca49a99058c66db7bfa5ca0dd Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 15 Mar 2011 14:57:20 -0700 Subject: [PATCH 0534/3753] (#6719) Restricts virtualization types for zones - The virtual fact will only check if a system is a zone if the operating system is solaris. --- lib/facter/virtual.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index a5954cd87d..e9571df729 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -25,7 +25,9 @@ setcode do - result = "zone" if Facter::Util::Virtual.zone? + if Facter::Util::Virtual.zone? and Facter(:operatingsystem) == "Solaris" + result = "zone" + end if Facter.value(:kernel)=="HP-UX" result = "hpvm" if Facter::Util::Virtual.hpvm? From ffd80ac4e48285121e6557a06172d47d86270a76 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 8 Mar 2011 12:59:40 -0800 Subject: [PATCH 0535/3753] (#5011) Adds swap statistics for OSX - Added swapfree and swapsize facts - Tests will now run correctly for osx swap tests --- lib/facter/memory.rb | 30 +++++++++++++++++++++++++++ spec/unit/memory_spec.rb | 45 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 spec/unit/memory_spec.rb diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb index 86adc7f111..aa67702570 100644 --- a/lib/facter/memory.rb +++ b/lib/facter/memory.rb @@ -20,6 +20,36 @@ end end +Facter.add("SwapSize") do + confine :kernel => :Darwin + setcode do + swap = Facter::Util::Resolution.exec('sysctl vm.swapusage') + swaptotal = 0 + if swap =~ /total = (\S+)/ then swaptotal = $1; end + swaptotal + end +end + +Facter.add("SwapFree") do + confine :kernel => :Darwin + setcode do + swap = Facter::Util::Resolution.exec('sysctl vm.swapusage') + swapfree = 0 + if swap =~ /free = (\S+)/ then swapfree = $1; end + swapfree + end +end + +Facter.add("SwapEncrypted") do + confine :kernel => :Darwin + setcode do + swap = Facter::Util::Resolution.exec('sysctl vm.swapusage') + encrypted = false + if swap =~ /\(encrypted\)/ then encrypted = true; end + encrypted + end +end + if Facter.value(:kernel) == "AIX" and Facter.value(:id) == "root" swap = Facter::Util::Resolution.exec('swap -l') swapfree, swaptotal = 0, 0 diff --git a/spec/unit/memory_spec.rb b/spec/unit/memory_spec.rb new file mode 100644 index 0000000000..5cae8cb29a --- /dev/null +++ b/spec/unit/memory_spec.rb @@ -0,0 +1,45 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') + +require 'facter' + +describe "Memory facts" do + before do + Facter.loadfacts + end + + after do + Facter.clear + end + + it "should return the current swap size" do + + Facter.fact(:kernel).stubs(:value).returns("Darwin") + Facter::Util::Resolution.stubs(:exec).with('sysctl vm.swapusage').returns("vm.swapusage: total = 64.00M used = 0.00M free = 64.00M (encrypted)") + swapusage = "vm.swapusage: total = 64.00M used = 0.00M free = 64.00M (encrypted)" + + if swapusage =~ /total = (\S+).*/ + Facter.fact(:swapfree).value.should == $1 + end + end + + it "should return the current swap free" do + Facter.fact(:kernel).stubs(:value).returns("Darwin") + Facter::Util::Resolution.stubs(:exec).with('sysctl vm.swapusage').returns("vm.swapusage: total = 64.00M used = 0.00M free = 64.00M (encrypted)") + swapusage = "vm.swapusage: total = 64.00M used = 0.00M free = 64.00M (encrypted)" + + if swapusage =~ /free = (\S+).*/ + Facter.fact(:swapfree).value.should == $1 + end + end + + it "should return whether swap is encrypted" do + Facter.fact(:kernel).stubs(:value).returns("Darwin") + Facter::Util::Resolution.stubs(:exec).with('sysctl vm.swapusage').returns("vm.swapusage: total = 64.00M used = 0.00M free = 64.00M (encrypted)") + swapusage = "vm.swapusage: total = 64.00M used = 0.00M free = 64.00M (encrypted)" + + swapusage =~ /\(encrypted\)/ + Facter.fact(:swapencrypted).value.should == true + end +end From 4eb64fe100118f9add401ec2f67812e714f98210 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Thu, 17 Mar 2011 02:36:36 +1100 Subject: [PATCH 0536/3753] Fixed #6719 Typo --- lib/facter/virtual.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index e9571df729..12269d2f30 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -25,7 +25,7 @@ setcode do - if Facter::Util::Virtual.zone? and Facter(:operatingsystem) == "Solaris" + if Facter::Util::Virtual.zone? and Facter.value(:operatingsystem) == "Solaris" result = "zone" end From 458a22d472334e4239397f3077e6366a9d7e9973 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Thu, 17 Mar 2011 02:39:58 +1100 Subject: [PATCH 0537/3753] Incremented release to 1.5.9 --- lib/facter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter.rb b/lib/facter.rb index f48138ab06..63eea375e6 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -27,7 +27,7 @@ module Util; end include Comparable include Enumerable - FACTERVERSION = '1.5.8' + FACTERVERSION = '1.5.9' # = Facter # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. From d0f0f63f91a0eff72c1c528a6b5fb325bd9001d4 Mon Sep 17 00:00:00 2001 From: Ben H Date: Tue, 15 Mar 2011 17:33:26 +1100 Subject: [PATCH 0538/3753] (#6327) Memory facts should be available on Mac Darwin There's no easy defined way of getting memory information from the command line. Copying mainly the OpenBSD facts, but having to pull in memory free from the vm_stat utility, and parsing the weird vm.swapusage sysctl value for swap. Parsing "top -l 1 -n 0" seemed an option, but that took over a second to run each time, so this appears cheaper. --- lib/facter/memory.rb | 37 +++++++++++++++++++++++++++++++++++++ lib/facter/util/memory.rb | 31 +++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb index aa67702570..9dd2e2929b 100644 --- a/lib/facter/memory.rb +++ b/lib/facter/memory.rb @@ -110,6 +110,43 @@ end end +if Facter.value(:kernel) == "Darwin" + swap = Facter::Util::Resolution.exec('sysctl vm.swapusage') + swapfree, swaptotal = 0, 0 + unless swap.empty? + # Parse the line: + # vm.swapusage: total = 128.00M used = 0.37M free = 127.63M (encrypted) + if swap =~ /total\s=\s(\S+)\s+used\s=\s(\S+)\s+free\s=\s(\S+)\s/ + swaptotal += $1.to_i + swapfree += $3.to_i + end + end + + Facter.add("SwapSize") do + confine :kernel => :Darwin + setcode do + Facter::Memory.scale_number(swaptotal.to_f,"MB") + end + end + + Facter.add("SwapFree") do + confine :kernel => :Darwin + setcode do + Facter::Memory.scale_number(swapfree.to_f,"MB") + end + end + + Facter::Memory.vmstat_darwin_find_free_memory() + + Facter.add("MemoryTotal") do + confine :kernel => :Darwin + memtotal = Facter::Util::Resolution.exec("sysctl hw.memsize | cut -d':' -f2") + setcode do + Facter::Memory.scale_number(memtotal.to_f,"") + end + end +end + if Facter.value(:kernel) == "SunOS" swap = Facter::Util::Resolution.exec('/usr/sbin/swap -l') swapfree, swaptotal = 0, 0 diff --git a/lib/facter/util/memory.rb b/lib/facter/util/memory.rb index 43abec6de7..029b117451 100644 --- a/lib/facter/util/memory.rb +++ b/lib/facter/util/memory.rb @@ -62,5 +62,36 @@ def self.vmstat_find_free_memory() end end end + + # Darwin had to be different. It's generally opaque with how much RAM it is + # using, and this figure could be improved upon too I fear. + # Parses the output of "vm_stat", takes the pages free & pages speculative + # and multiples that by the page size (also given in output). Ties in with + # what activity monitor outputs for free memory. + def self.vmstat_darwin_find_free_memory() + + memfree = 0 + pagesize = 0 + memspecfree = 0 + + vmstats = Facter::Util::Resolution.exec('vm_stat') + vmstats.each do |vmline| + case + when vmline =~ /page\ssize\sof\s(\d+)\sbytes/ + pagesize = $1.to_i + when vmline =~ /^Pages\sfree:\s+(\d+)\./ + memfree = $1.to_i + when vmline =~ /^Pages\sspeculative:\s+(\d+)\./ + memspecfree = $1.to_i + end + end + + freemem = ( memfree + memspecfree ) * pagesize + Facter.add("MemoryFree") do + setcode do + Facter::Memory.scale_number(freemem.to_f, "") + end + end + end end From e917e1a7f47b4399f5d1dd32dd5cd8ad5da86b64 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Tue, 15 Mar 2011 18:09:35 +1100 Subject: [PATCH 0539/3753] Fixed #3087 - Identify VMWare Added support for VMWareESX and ESXi to operatingsystem and operatingsystemrelease facts Added appropriate tests --- lib/facter/operatingsystem.rb | 9 ++++++++ lib/facter/operatingsystemrelease.rb | 12 ++++++++++- spec/unit/operatingsystem_spec.rb | 16 ++++++++++++++- spec/unit/operatingsystemrelease_spec.rb | 26 ++++++++++++++++-------- 4 files changed, 53 insertions(+), 10 deletions(-) diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index 1675909dd0..25eeb00edb 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -32,6 +32,8 @@ end elsif FileTest.exists?("/etc/arch-release") "Arch" + elsif FileTest.exists?("/etc/vmware-release") + "VMWareESX" elsif FileTest.exists?("/etc/redhat-release") txt = File.read("/etc/redhat-release") if txt =~ /centos/i @@ -62,6 +64,13 @@ end end +Facter.add(:operatingsystem) do + confine :kernel => "VMkernel" + setcode do + "ESXi" + end +end + Facter.add(:operatingsystem) do # Default to just returning the kernel as the operating system setcode do Facter[:kernel].value end diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index ac9be65ee7..da1b76ab3f 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -83,6 +83,16 @@ end end +Facter.add(:operatingsystemrelease) do + confine :operatingsystem => %w{VMwareESX} + setcode do + release = Facter::Util::Resolution.exec('vmware -v') + if release =~ /VMware ESX .*?(\d.*)/ + $1 + end + end +end + Facter.add(:operatingsystemrelease) do confine :operatingsystem => %w{Slamd64} setcode do @@ -96,5 +106,5 @@ end Facter.add(:operatingsystemrelease) do - setcode do Facter[:kernelrelease].value end + setcode do Facter[:kernelrelease].value end end diff --git a/spec/unit/operatingsystem_spec.rb b/spec/unit/operatingsystem_spec.rb index 73b36492e6..91cd311613 100755 --- a/spec/unit/operatingsystem_spec.rb +++ b/spec/unit/operatingsystem_spec.rb @@ -26,8 +26,13 @@ Facter.fact(:operatingsystem).value.should == "Solaris" end - it "should identify Oracle VM as OVS" do + it "should be ESXi for VMkernel" do + Facter.fact(:kernel).stubs(:value).returns("VMkernel") + + Facter.fact(:operatingsystem).value.should == "ESXi" + end + it "should identify Oracle VM as OVS" do Facter.fact(:kernel).stubs(:value).returns("Linux") FileTest.stubs(:exists?).returns false @@ -36,4 +41,13 @@ Facter.fact(:operatingsystem).value.should == "OVS" end + + it "should identify VMWare ESX" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + FileTest.stubs(:exists?).returns false + + FileTest.expects(:exists?).with("/etc/vmware-release").returns true + + Facter.fact(:operatingsystem).value.should == "VMWareESX" + end end diff --git a/spec/unit/operatingsystemrelease_spec.rb b/spec/unit/operatingsystemrelease_spec.rb index 1cfb4ac713..739a20a8de 100755 --- a/spec/unit/operatingsystemrelease_spec.rb +++ b/spec/unit/operatingsystemrelease_spec.rb @@ -15,14 +15,14 @@ end test_cases = { - "CentOS" => "/etc/redhat-release", - "RedHat" => "/etc/redhat-release", - "Fedora" => "/etc/fedora-release", - "MeeGo" => "/etc/meego-release", - "OEL" => "/etc/enterprise-release", - "oel" => "/etc/enterprise-release", - "OVS" => "/etc/ovs-release", - "ovs" => "/etc/ovs-release" + "CentOS" => "/etc/redhat-release", + "RedHat" => "/etc/redhat-release", + "Fedora" => "/etc/fedora-release", + "MeeGo" => "/etc/meego-release", + "OEL" => "/etc/enterprise-release", + "oel" => "/etc/enterprise-release", + "OVS" => "/etc/ovs-release", + "ovs" => "/etc/ovs-release", } test_cases.each do |system, file| @@ -36,4 +36,14 @@ end end end + + it "for VMWareESX it should run the vmware -v command" do + Facter.fact(:kernel).stubs(:value).returns("VMkernel") + Facter.fact(:kernelrelease).stubs(:value).returns("4.1.0") + Facter.fact(:operatingsystem).stubs(:value).returns("VMwareESX") + + Facter::Util::Resolution.stubs(:exec).with('vmware -v').returns('foo') + + Facter.fact(:operatingsystemrelease).value + end end From 5b6f4fa4aae419bd2d65e1af6e185123dd432ac6 Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Sun, 28 Jun 2009 11:22:28 +0100 Subject: [PATCH 0540/3753] Discussion on ec2 facts - #2346 The EC2 fact is completely broken at the moment: * Timeout::Error isn't caught by rescue (due to how it inherits) * The issue of wrong open semantics outlined here, this is causing hidden immediate failure * The fact is going to cause a 2 second wait to every facter run Whilst the following patch fixes the first two, I'm not sure we want to take the timeout hit, we also want to add tests as even simple ruby code can get logic errors such as the open(). Signed-off-by: Paul Nasrat --- lib/facter/ec2.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/facter/ec2.rb b/lib/facter/ec2.rb index ef84757ff7..ea29d14de8 100644 --- a/lib/facter/ec2.rb +++ b/lib/facter/ec2.rb @@ -6,9 +6,12 @@ require 'timeout' def can_connect?(ip,port,wait_sec=2) - Timeout::timeout(wait_sec) {open(ip, port)} + url = "http://#{ip}:#{port}/" + Timeout::timeout(wait_sec) {open(url)} return true -rescue +rescue Timeout::Error + return false +rescue return false end From 0411d2eacbfad9d07977dea8975b604b7cdd1aa5 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Tue, 8 Mar 2011 20:18:30 +1100 Subject: [PATCH 0541/3753] Fixed #2346 - Part 1: Added arp fact for Linux Added facts arp (like the ipaddress etc) facts Added facts arp_interfacename --- lib/facter/arp.rb | 22 ++++++++++++++++++++++ lib/facter/util/ip.rb | 7 +++++++ spec/unit/util/ip_spec.rb | 7 +++++++ 3 files changed, 36 insertions(+) create mode 100644 lib/facter/arp.rb diff --git a/lib/facter/arp.rb b/lib/facter/arp.rb new file mode 100644 index 0000000000..f4b7709aee --- /dev/null +++ b/lib/facter/arp.rb @@ -0,0 +1,22 @@ +require 'facter/util/ip' + +Facter.add(:arp) do + confine :kernel => :linux + setcode do + arp = [] + output = %x{/usr/sbin/arp -a} + output.each_line do |s| + arp.push($1) if s =~ /^\S+\s\S+\s\S+\s(\S+)\s\S+\s\S+\s\S+$/ + end + arp[0] + end +end + +Facter::Util::IP.get_interfaces.each do |interface| + Facter.add("arp_" + Facter::Util::IP.alphafy(interface)) do + confine :kernel => :linux + setcode do + Facter::Util::IP.get_arp_value(interface) + end + end +end diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index e4370dc7c3..8b6dbf46f8 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -169,4 +169,11 @@ def self.get_network_value(interface) network = ip.mask(subnet.to_s).to_s end end + + def self.get_arp_value(interface) + arp = Facter::Util::Resolution.exec("arp -en -i #{interface} | sed -e 1d") + if arp =~ /^\S+\s+\w+\s+(\S+)\s+\w\s+\S+$/ + return $1 + end + end end diff --git a/spec/unit/util/ip_spec.rb b/spec/unit/util/ip_spec.rb index 1a545b885a..0374e75c77 100755 --- a/spec/unit/util/ip_spec.rb +++ b/spec/unit/util/ip_spec.rb @@ -227,4 +227,11 @@ Facter::Util::IP.convert_from_hex?(platform).should == true end end + + it "should return an arp address on Linux" do + Facter.stubs(:value).with(:kernel).returns("Linux") + + Facter::Util::IP.expects(:get_arp_value).with("eth0").returns("00:00:0c:9f:f0:04") + Facter::Util::IP.get_arp_value("eth0").should == "00:00:0c:9f:f0:04" + end end From d62e079489c07201cb343f2ca109fecd62d6e567 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Tue, 8 Mar 2011 21:24:54 +1100 Subject: [PATCH 0542/3753] Fixed #2346 - A much cleverer EC2 fact The fact now checks for an EC2 ARP or in the EU Zone for an EC2 MAC This should mean the fact's return is much more robust The fact also now supports returning userdata (which is a bit ugly given Facter returns strings but a good bookmark for refactor in 2.0) --- lib/facter/ec2.rb | 82 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 64 insertions(+), 18 deletions(-) diff --git a/lib/facter/ec2.rb b/lib/facter/ec2.rb index ea29d14de8..29b2a1c291 100644 --- a/lib/facter/ec2.rb +++ b/lib/facter/ec2.rb @@ -1,27 +1,46 @@ -# Changelog: -# Original facts - Tim Dysinger -# Updated and added can_connect? function - KurtBe +# Original fact Tim Dysinger +# Additional work from KurtBe +# Additional work for Paul Nasrat +# Additional work modelled on Ohai EC2 fact require 'open-uri' -require 'timeout' - -def can_connect?(ip,port,wait_sec=2) - url = "http://#{ip}:#{port}/" - Timeout::timeout(wait_sec) {open(url)} - return true -rescue Timeout::Error - return false -rescue - return false -end +require 'socket' + +EC2_ADDR = "169.254.169.254" +EC2_METADATA_URL = "http://#{EC2_ADDR}/2008-02-01/meta-data" +EC2_USERDATA_URL = "http://#{EC2_ADDR}/2008-02-01/user-data" +def can_metadata_connect?(addr, port, timeout=2) + t = Socket.new(Socket::Constants::AF_INET, Socket::Constants::SOCK_STREAM, 0) + saddr = Socket.pack_sockaddr_in(port, addr) + connected = false + + begin + t.connect_nonblock(saddr) + rescue Errno::EINPROGRESS + r,w,e = IO::select(nil,[t],nil,timeout) + if !w.nil? + connected = true + else + begin + t.connect_nonblock(saddr) + rescue Errno::EISCONN + t.close + connected = true + rescue SystemCallError + end + end + rescue SystemCallError + end + connected +end def metadata(id = "") - open("http://169.254.169.254/2008-02-01/meta-data/#{id||=''}").read. + open("#{EC2_METADATA_URL}/#{id||=''}").read. split("\n").each do |o| key = "#{id}#{o.gsub(/\=.*$/, '/')}" if key[-1..-1] != '/' - value = open("http://169.254.169.254/2008-02-01/meta-data/#{key}").read. + value = open("#{EC2_METADATA_URL}/#{key}").read. split("\n") value = value.size>1 ? value : value.first symbol = "ec2_#{key.gsub(/\-|\//, '_')}".to_sym @@ -32,7 +51,34 @@ def metadata(id = "") end end -if can_connect?("169.254.169.254","80") - metadata +def userdata() + # assumes the only expected error is the 404 if there's no user-data + begin + value = OpenURI.open_uri("#{EC2_USERDATA_URL}/").read.split + Facter.add(:ec2_userdata) { setcode { value } } + rescue OpenURI::HTTPError + end end +def has_euca_mac? + if Facter.value(:macaddress) =~ /^[dD]0:0[dD]:/ + return true + else + return false + end +end + +def has_ec2_arp? + if Facter.value(:arp) == 'fe:ff:ff:ff:ff:ff' + return true + else + return false + end +end + +if (has_euca_mac? || has_ec2_arp?) && can_metadata_connect?(EC2_ADDR,80) + metadata + userdata +else + Facter.debug "Not an EC2 host" +end From 43f82efd24564862952a715cba4424d6f0c92e48 Mon Sep 17 00:00:00 2001 From: Matt Robinson Date: Wed, 16 Mar 2011 17:26:28 -0700 Subject: [PATCH 0543/3753] Update CHANGELOG for 1.5.9rc1 --- CHANGELOG | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 13b23f07b2..e719cd4a17 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,74 @@ +1.5.9rc1 +======== +458a22d Incremented release to 1.5.9 +4eb64fe Fixed #6719 Typo +ffd80ac (#5011) Adds swap statistics for OSX +1207765 (#6719) Restricts virtualization types for zones +8d71db3 Fixed #6616 - Stubbing in VMware tests on Linux +aa959df Remove Solaris from the list of confined systems. It won't get the original lsb facts, and it's nonsensical too. +d718af4 Fix #6679 - Added Scientific Linux to operatingsystem fact +dea6f78 Further fix to #5485 - SELinux facts +6d6d8da (#2721) Merged patch from Brane GraAnar +868e7ba (#5485) Made selinux_mode fact work +214da73 Fixed #5485 - Updated selinux_mode fact +ba2601f Fix for #6495 - Updated interface detection +93461d9 Fixed #5950 - Solaris ipaddress incorrect after bonding failure +2e06cdc (#6615) fix missing stub calls in loader specs +3c7841e (#5666) windows support for facter/id.rb +dd5d5bf (#4925) - MS Windows doesn't do man pages +52026ee Fixed #5699 - Added processorcount support for S390x +7dd730d Fixed #5699 - Added virtual support for s390x/Zlinux +d6ce08a Fixed #6611 - Fixed broken HPVM test and rationalised test structure +84fa3c4 (#6525) change semicolons to 'then' in case statement for ruby 1.9.2 compatibility +3e6217d Fixes #6521 and other Ruby 1.9 issues +eb5d6fc Fixed #6525 - Test failures on Ruby 1.9.x +cb25119 (#2270) add testing for the new ipaddress6 feature +ea29483 (#2270) add IPv6 support to facter core. +77eb512 (#2270) Remove DWIM code from ipaddress on Darwin. +f5bf0f5 (#6360) Flush Facter top level cache before every test case. +0d7a2e6 Fix #4755: add support for GNU/kFreeBSD platform where missing. +b88a088 (#5510) Facter should load custom fact definitions in filename order. +7a8be16 Refactor #6044 -- use _spec.rb as the pattern for spec tests. +b39f892 Refactor #6044 -- require spec_helper with a consistent path. +a4fe459 Refactor #6044 -- port testing to rspec2 +af9134c (#5086) Try using kstat before falling back to 'who -b' to determine uptime. +cbbfe55 Refactor util/uptime.rb tests to reduce duplication using contexts +f0cc2c0 (#4575) win32 support for manufacturer, productname, & serialnumber +c40fc07 (#1423) Memory facts for Solaris +1985528 (#4754) Change is_virtual logic to not enumerate virtual types +739040f (#4754) Add support for Darwin and Parallels VM to "virtual" fact +9332f8a (#5325) Add tests for SPARC manufacturer and product name +5b561e3 (#5325) Manufacturer and product name on SPARC +9d99079 maint: Fix spec failures caused by having a space in the path to facter's source +89da001 maint: require rubygems so hudson can run the specs +1eef842 Maint: add "Local-branch:" info to mails sent by "rake mail_patches" +f007a9d (#4989) Add xendomains fact +1fa87a9 JSON support. Works in 1.9.1. Warnings in 1.9.2. LoadError on 1.8.7 for some reason +43e203c (#5040) fact virtual should detect hpvm +7cec60a (#5016) is_virtual should be true on solaris zones +f2e66b6 (#5031) Remove redundant puts from RDoc.usage +f4da528 maint: Fix merge error +d62b013 Issue #4889 Fact values should all be strings +07f186d [#4552] Updating --timing to report in milliseconds instead of seconds +1f387a5 [#4552] Apply patch from Dean Wilson +244d2f1 Better fix for Bug 4569: Uptime Fact is incorrect on Windows +11544c1 [#4289] operatingsystemrelease fact for oel, ovs +e6bfdf9 Fix for bug #4569 +8c4d0cd (#4558) Fail with message on --help errors +7210429 [#4558] Refactor facter binary using optparse +b5c85de [#4563] Add a --trace option to the binary +ebcb81b [#4558] Refactor facter binary using optparse +b8b7123 (#4567) Remove unnecessary or non-portable redirects +7ecba71 (#4567) Retain detached HEAD state +1125e1e Make sure FreeBSD spec also works on systems that have /proc/cpuinfo. +889e150 Sync rpm spec file from Fedora/EPEL +725dce0 Rename Reductive Labs to Puppet Labs +ff473ef Updated signing rake task +a85f2b0 [#2865] Fix reporting of virtual facts +f67ec05 [#4567] Add ext/facter-diff to compare output of 2 versions +4050acc Removing stupid .DS_Store files :( +016cf03 [#3703] Fix macaddress fact for Darwin + 1.5.8 ===== ca2da36 Updated install.rb and created man page From 3f0a3404894ea1835877856e20d60be4023706fc Mon Sep 17 00:00:00 2001 From: Ben Hughes Date: Thu, 17 Mar 2011 12:23:44 +1100 Subject: [PATCH 0544/3753] (#6716) fix facter issues on OSX with ipv6 in macaddress.rb. Due to "netstat -rn" returning multiple protocols (IPv4 and IPv6) the "default_interface" can get more than one entry in to it, causing the macaddress resolving to break. This limits it to just one interface. --- lib/facter/util/macaddress.rb | 2 +- .../fixtures/ifconfig/darwin_10_6_6_dualstack | 8 +++++ .../ifconfig/darwin_10_6_6_dualstack_en1 | 7 ++++ spec/fixtures/netstat/darwin_10_6_6_dualstack | 34 +++++++++++++++++++ spec/unit/util/macaddress_spec.rb | 3 +- 5 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 spec/fixtures/ifconfig/darwin_10_6_6_dualstack create mode 100644 spec/fixtures/ifconfig/darwin_10_6_6_dualstack_en1 create mode 100644 spec/fixtures/netstat/darwin_10_6_6_dualstack diff --git a/lib/facter/util/macaddress.rb b/lib/facter/util/macaddress.rb index fc0a0432db..f6bae142a2 100644 --- a/lib/facter/util/macaddress.rb +++ b/lib/facter/util/macaddress.rb @@ -12,7 +12,7 @@ def self.macaddress end def self.default_interface - `#{netstat_command} | /usr/bin/awk '/^default/{print $6}'`.chomp + `#{netstat_command} | /usr/bin/awk '/^default/{print $6;exit}'`.chomp end private diff --git a/spec/fixtures/ifconfig/darwin_10_6_6_dualstack b/spec/fixtures/ifconfig/darwin_10_6_6_dualstack new file mode 100644 index 0000000000..819f56e7b5 --- /dev/null +++ b/spec/fixtures/ifconfig/darwin_10_6_6_dualstack @@ -0,0 +1,8 @@ +lo0: flags=8049 mtu 16384 + inet 127.0.0.1 netmask 0xff000000 + inet6 ::1 prefixlen 128 + inet6 fe80::1%lo0 prefixlen 64 scopeid 0x1 +en0: flags=8863 mtu 1500 + ether 00:25:4b:ca:56:72 + media: autoselect + status: inactive diff --git a/spec/fixtures/ifconfig/darwin_10_6_6_dualstack_en1 b/spec/fixtures/ifconfig/darwin_10_6_6_dualstack_en1 new file mode 100644 index 0000000000..eabb1f6a3c --- /dev/null +++ b/spec/fixtures/ifconfig/darwin_10_6_6_dualstack_en1 @@ -0,0 +1,7 @@ +en1: flags=8863 mtu 1500 + ether 00:25:00:48:19:ef + inet6 fe80::225:ff:fe48:19ef%en1 prefixlen 64 scopeid 0x5 + inet 192.168.1.207 netmask 0xffffff00 broadcast 192.168.1.255 + inet6 2000:44b4:32:400::1 prefixlen 64 + media: autoselect + status: active diff --git a/spec/fixtures/netstat/darwin_10_6_6_dualstack b/spec/fixtures/netstat/darwin_10_6_6_dualstack new file mode 100644 index 0000000000..14d937d261 --- /dev/null +++ b/spec/fixtures/netstat/darwin_10_6_6_dualstack @@ -0,0 +1,34 @@ +Routing tables + +Internet: +Destination Gateway Flags Refs Use Netif Expire +default 192.168.1.254 UGSc 38 0 en1 +127 127.0.0.1 UCS 0 0 lo0 +127.0.0.1 127.0.0.1 UH 14 1044474 lo0 +169.254 link#5 UCS 0 0 en1 +192.168.1 link#5 UCS 2 0 en1 +192.168.1.207 127.0.0.1 UHS 0 1 lo0 +192.168.1.220 e0:f8:47:98:85:71 UHLWI 0 0 en1 135 +192.168.1.254 0:4:ed:66:13:cc UHLWI 42 134 en1 1158 + +Internet6: +Destination Gateway Flags Netif Expire +default 2000:44b4:61::16e UGSc tun0 +::1 ::1 UH lo0 +2000:44b4:61::16e 2000:44b4:61::16f UH tun0 +2000:44b4:61::16f link#9 UHL lo0 +2000:44b4:62:480::/64 link#5 UC en1 +2000:44b4:62:480::/60 ::1 UGSc lo0 +2000:44b4:62:480::1 0:25:0:48:19:ef UHL lo0 +fe80::%lo0/64 fe80::1%lo0 Uc lo0 +fe80::1%lo0 link#1 UHL lo0 +fe80::%en1/64 link#5 UC en1 +fe80::225:ff:fe48:19ef%en1 0:25:0:48:19:ef UHL lo0 +fe80::a00:27ff:fe02:bcb5%en1 8:0:27:2:bc:b5 UHLW en1 +fe80::a932:c76f:9c2e:ead8%en1 0:1e:2a:b3:9b:66 UHLW en1 +fe80::e2f8:47ff:fe98:8571%en1 e0:f8:47:98:85:71 UHLW en1 +fe80::225:4bff:feca:5672%tun0 link#9 UHL lo0 +ff01::/32 ::1 Um lo0 +ff02::/32 ::1 UmC lo0 +ff02::/32 link#5 UmC en1 +ff02::/32 fe80::225:4bff:feca:5672%tun0 UmC tun0 diff --git a/spec/unit/util/macaddress_spec.rb b/spec/unit/util/macaddress_spec.rb index 98215c4381..d255d20a0b 100755 --- a/spec/unit/util/macaddress_spec.rb +++ b/spec/unit/util/macaddress_spec.rb @@ -9,7 +9,8 @@ # version, iface, real macaddress, fallback macaddress ["9.8.0", 'en0', "00:17:f2:06:e4:2e", "00:17:f2:06:e4:2e"], ["10.3.0", 'en0', "00:17:f2:06:e3:c2", "00:17:f2:06:e3:c2"], - ["10.6.4", 'en1', "58:b0:35:7f:25:b3", "58:b0:35:fa:08:b1"] + ["10.6.4", 'en1', "58:b0:35:7f:25:b3", "58:b0:35:fa:08:b1"], + ["10.6.6_dualstack", "en1" , "00:25:00:48:19:ef" , "00:25:4b:ca:56:72"] ] test_cases.each do |version, default_iface, macaddress, fallback_macaddress| From 2fb831671a75d644c2efd48cfadfadecb9606b54 Mon Sep 17 00:00:00 2001 From: Jacob Helwig Date: Thu, 17 Mar 2011 15:03:28 -0700 Subject: [PATCH 0545/3753] Clean up indentation, and alignment in macaddress_spec.rb This changes the spec file from 4-space indentations to 2-space indentations to match the style guide[1]. [1] http://projects.puppetlabs.com/projects/puppet/wiki/Development_Lifecycle#Style --- spec/unit/util/macaddress_spec.rb | 110 ++++++++++++++---------------- 1 file changed, 51 insertions(+), 59 deletions(-) diff --git a/spec/unit/util/macaddress_spec.rb b/spec/unit/util/macaddress_spec.rb index d255d20a0b..4764b052e0 100755 --- a/spec/unit/util/macaddress_spec.rb +++ b/spec/unit/util/macaddress_spec.rb @@ -5,72 +5,64 @@ require 'facter/util/macaddress' describe "Darwin" do - test_cases = [ - # version, iface, real macaddress, fallback macaddress - ["9.8.0", 'en0', "00:17:f2:06:e4:2e", "00:17:f2:06:e4:2e"], - ["10.3.0", 'en0', "00:17:f2:06:e3:c2", "00:17:f2:06:e3:c2"], - ["10.6.4", 'en1', "58:b0:35:7f:25:b3", "58:b0:35:fa:08:b1"], - ["10.6.6_dualstack", "en1" , "00:25:00:48:19:ef" , "00:25:4b:ca:56:72"] - ] - - test_cases.each do |version, default_iface, macaddress, fallback_macaddress| - netstat_file = File.join(SPECDIR, "fixtures", "netstat", "darwin_#{version.tr('.', '_')}") - ifconfig_file_no_iface = File.join(SPECDIR, "fixtures", "ifconfig", "darwin_#{version.tr('.', '_')}") - ifconfig_file = "#{ifconfig_file_no_iface}_#{default_iface}" - - describe "version #{version}" do - - describe Facter::Util::Macaddress::Darwin do - - describe ".default_interface" do - describe "when netstat has a default interface" do - - before do - Facter::Util::Macaddress::Darwin.stubs(:netstat_command).returns("cat \"#{netstat_file}\"") - end - - it "should return the default interface name" do - Facter::Util::Macaddress::Darwin.default_interface.should == default_iface - end - end - - end - - describe ".macaddress" do - describe "when netstat has a default interface" do - before do - Facter.stubs(:warn) - Facter::Util::Macaddress::Darwin.stubs(:default_interface).returns('') - Facter::Util::Macaddress::Darwin.stubs(:ifconfig_command).returns("cat \"#{ifconfig_file}\"") - end - - it "should return the macaddress of the default interface" do - Facter::Util::Macaddress::Darwin.macaddress.should == macaddress - end - - end + test_cases = [ + # version, iface, real macaddress, fallback macaddress + ["9.8.0", 'en0', "00:17:f2:06:e4:2e", "00:17:f2:06:e4:2e"], + ["10.3.0", 'en0', "00:17:f2:06:e3:c2", "00:17:f2:06:e3:c2"], + ["10.6.4", 'en1', "58:b0:35:7f:25:b3", "58:b0:35:fa:08:b1"], + ["10.6.6_dualstack", "en1", "00:25:00:48:19:ef", "00:25:4b:ca:56:72"] + ] + + test_cases.each do |version, default_iface, macaddress, fallback_macaddress| + netstat_file = File.join(SPECDIR, "fixtures", "netstat", "darwin_#{version.tr('.', '_')}") + ifconfig_file_no_iface = File.join(SPECDIR, "fixtures", "ifconfig", "darwin_#{version.tr('.', '_')}") + ifconfig_file = "#{ifconfig_file_no_iface}_#{default_iface}" + + describe "version #{version}" do + describe Facter::Util::Macaddress::Darwin do + describe ".default_interface" do + describe "when netstat has a default interface" do + before do + Facter::Util::Macaddress::Darwin.stubs(:netstat_command).returns("cat \"#{netstat_file}\"") + end - describe "when netstat does not have a default interface" do - before do - Facter::Util::Macaddress::Darwin.stubs(:default_interface).returns("") - Facter::Util::Macaddress::Darwin.stubs(:ifconfig_command).returns("cat \"#{ifconfig_file_no_iface}\"") - end + it "should return the default interface name" do + Facter::Util::Macaddress::Darwin.default_interface.should == default_iface + end + end + end - it "should warn about the lack of default" do - Facter.expects(:warn).with("Could not find a default route. Using first non-loopback interface") - Facter::Util::Macaddress::Darwin.stubs(:default_interface).returns('') - Facter::Util::Macaddress::Darwin.macaddress - end + describe ".macaddress" do + describe "when netstat has a default interface" do + before do + Facter.stubs(:warn) + Facter::Util::Macaddress::Darwin.stubs(:default_interface).returns('') + Facter::Util::Macaddress::Darwin.stubs(:ifconfig_command).returns("cat \"#{ifconfig_file}\"") + end - it "should return the macaddress of the first non-loopback interface" do - Facter::Util::Macaddress::Darwin.macaddress.should == fallback_macaddress - end + it "should return the macaddress of the default interface" do + Facter::Util::Macaddress::Darwin.macaddress.should == macaddress + end + end - end - end + describe "when netstat does not have a default interface" do + before do + Facter::Util::Macaddress::Darwin.stubs(:default_interface).returns("") + Facter::Util::Macaddress::Darwin.stubs(:ifconfig_command).returns("cat \"#{ifconfig_file_no_iface}\"") + end + it "should warn about the lack of default" do + Facter.expects(:warn).with("Could not find a default route. Using first non-loopback interface") + Facter::Util::Macaddress::Darwin.stubs(:default_interface).returns('') + Facter::Util::Macaddress::Darwin.macaddress end + it "should return the macaddress of the first non-loopback interface" do + Facter::Util::Macaddress::Darwin.macaddress.should == fallback_macaddress + end + end end + end end + end end From 50b9b3f5bfdeefd6055a1c1fefd5f0005f95879c Mon Sep 17 00:00:00 2001 From: Jacob Helwig Date: Thu, 17 Mar 2011 17:14:34 -0700 Subject: [PATCH 0546/3753] Updated CHANGELOG for 1.5.9rc2 --- CHANGELOG | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index e719cd4a17..1f9374fc88 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,14 @@ +1.5.9rc2 +======== +2fb8316 Clean up indentation, and alignment in macaddress_spec.rb +3f0a340 (#6716) fix facter issues on OSX with ipv6 in macaddress.rb. +d62e079 Fixed #2346 - A much cleverer EC2 fact +0411d2e Fixed #2346 - Part 1: Added arp fact for Linux +5b6f4fa Discussion on ec2 facts - #2346 +e917e1a Fixed #3087 - Identify VMWare +d0f0f63 (#6327) Memory facts should be available on Mac Darwin +2e48e18 Fixed #6695 - Updated id fact for Darwin et al + 1.5.9rc1 ======== 458a22d Incremented release to 1.5.9 From 3eb9410f8f3f03730748ca0d8fa2c33b69e8a413 Mon Sep 17 00:00:00 2001 From: Todd Zullinger Date: Fri, 18 Mar 2011 08:40:48 -0400 Subject: [PATCH 0547/3753] arp: Cleanup indendation --- lib/facter/arp.rb | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/facter/arp.rb b/lib/facter/arp.rb index f4b7709aee..65cf4c3467 100644 --- a/lib/facter/arp.rb +++ b/lib/facter/arp.rb @@ -1,22 +1,22 @@ require 'facter/util/ip' Facter.add(:arp) do - confine :kernel => :linux - setcode do - arp = [] - output = %x{/usr/sbin/arp -a} - output.each_line do |s| - arp.push($1) if s =~ /^\S+\s\S+\s\S+\s(\S+)\s\S+\s\S+\s\S+$/ - end - arp[0] - end + confine :kernel => :linux + setcode do + arp = [] + output = %x{/usr/sbin/arp -a} + output.each_line do |s| + arp.push($1) if s =~ /^\S+\s\S+\s\S+\s(\S+)\s\S+\s\S+\s\S+$/ + end + arp[0] + end end Facter::Util::IP.get_interfaces.each do |interface| - Facter.add("arp_" + Facter::Util::IP.alphafy(interface)) do + Facter.add("arp_" + Facter::Util::IP.alphafy(interface)) do confine :kernel => :linux - setcode do - Facter::Util::IP.get_arp_value(interface) - end + setcode do + Facter::Util::IP.get_arp_value(interface) end + end end From f39d48745dc6b54f33d505bfe33e875089b2907d Mon Sep 17 00:00:00 2001 From: Todd Zullinger Date: Fri, 18 Mar 2011 08:42:17 -0400 Subject: [PATCH 0548/3753] (#6763) Use Facter::Util::Resolution.exec for arp The arp command is in /sbin on Fedora/RHEL, not /usr/sbin. Using Facter::Util::Resolution.exec is preferable to hard-coding the path. --- lib/facter/arp.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/facter/arp.rb b/lib/facter/arp.rb index 65cf4c3467..5035ad08e6 100644 --- a/lib/facter/arp.rb +++ b/lib/facter/arp.rb @@ -4,9 +4,11 @@ confine :kernel => :linux setcode do arp = [] - output = %x{/usr/sbin/arp -a} - output.each_line do |s| - arp.push($1) if s =~ /^\S+\s\S+\s\S+\s(\S+)\s\S+\s\S+\s\S+$/ + output = Facter::Util::Resolution.exec('arp -a') + if not output.nil? + output.each_line do |s| + arp.push($1) if s =~ /^\S+\s\S+\s\S+\s(\S+)\s\S+\s\S+\s\S+$/ + end end arp[0] end From def33221bd7f20315205e9d63abe4ae8ece00f5e Mon Sep 17 00:00:00 2001 From: Todd Zullinger Date: Mon, 21 Mar 2011 11:18:07 -0400 Subject: [PATCH 0549/3753] (#6795) xendomains: Ignore error output from xm list If xend is not running, xm list writes to stderr and facter propagates this to the user. Redirect stderr to /dev/null. --- lib/facter/util/xendomains.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/util/xendomains.rb b/lib/facter/util/xendomains.rb index 4f590a8114..6b0d403ba8 100644 --- a/lib/facter/util/xendomains.rb +++ b/lib/facter/util/xendomains.rb @@ -2,7 +2,7 @@ # module Facter::Util::Xendomains def self.get_domains - if xm_list = Facter::Util::Resolution.exec('/usr/sbin/xm list') + if xm_list = Facter::Util::Resolution.exec('/usr/sbin/xm list 2>/dev/null') domains = xm_list.split("\n").reject { |line| line =~ /^(Name|Domain-0)/ } domains.map { |line| line.split(/\s/)[0] }.join(',') end From 3db1cd0558f68a678399b3878715d7d346de06b5 Mon Sep 17 00:00:00 2001 From: Jacob Helwig Date: Mon, 21 Mar 2011 11:41:19 -0700 Subject: [PATCH 0550/3753] Updated CHANGELOG for 1.5.9rc3 --- CHANGELOG | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 1f9374fc88..6d300be6d7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,9 @@ +1.5.9rc3 +======== +def3322 (#6795) xendomains: Ignore error output from xm list +f39d487 (#6763) Use Facter::Util::Resolution.exec for arp +3eb9410 arp: Cleanup indendation + 1.5.9rc2 ======== 2fb8316 Clean up indentation, and alignment in macaddress_spec.rb From 09b9f9bd84e09f8ca9bb7d4e611082c047fac94d Mon Sep 17 00:00:00 2001 From: Jacob Helwig Date: Mon, 21 Mar 2011 11:54:55 -0700 Subject: [PATCH 0551/3753] (#6795) Update tests to reflect changed exec --- spec/unit/util/xendomains_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/unit/util/xendomains_spec.rb b/spec/unit/util/xendomains_spec.rb index dc7e17852f..5562d267a8 100755 --- a/spec/unit/util/xendomains_spec.rb +++ b/spec/unit/util/xendomains_spec.rb @@ -9,13 +9,13 @@ it "should return a list of running Xen Domains on Xen0" do sample_output_file = File.dirname(__FILE__) + '/../data/xendomains' xen0_domains = File.read(sample_output_file) - Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/xm list').returns(xen0_domains) + Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/xm list 2>/dev/null').returns(xen0_domains) Facter::Util::Xendomains.get_domains.should == %{web01,mailserver} end describe "when xm list isn't executable" do it "should be nil" do - Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/xm list').returns(nil) + Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/xm list 2>/dev/null').returns(nil) Facter::Util::Xendomains.get_domains.should == nil end end From 76f544b261ec1c9712105773946fec7e6d76b4e9 Mon Sep 17 00:00:00 2001 From: Jacob Helwig Date: Mon, 21 Mar 2011 11:58:32 -0700 Subject: [PATCH 0552/3753] Updated CHANGELOG for 1.5.9rc4 --- CHANGELOG | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 6d300be6d7..0112360306 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,7 @@ +1.5.9rc4 +======== +09b9f9b (#6795) Update tests to reflect changed exec + 1.5.9rc3 ======== def3322 (#6795) xendomains: Ignore error output from xm list From 72996ff6d01c6891bad241c45a5555e1a639ddd5 Mon Sep 17 00:00:00 2001 From: Matt Robinson Date: Tue, 22 Mar 2011 21:37:10 -0700 Subject: [PATCH 0553/3753] maint: cleanup whitespace --- lib/facter/memory.rb | 36 ++++++++++++++++++------------------ lib/facter/util/memory.rb | 1 - 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb index 9dd2e2929b..0aa5e882a1 100644 --- a/lib/facter/memory.rb +++ b/lib/facter/memory.rb @@ -23,30 +23,30 @@ Facter.add("SwapSize") do confine :kernel => :Darwin setcode do - swap = Facter::Util::Resolution.exec('sysctl vm.swapusage') - swaptotal = 0 - if swap =~ /total = (\S+)/ then swaptotal = $1; end - swaptotal + swap = Facter::Util::Resolution.exec('sysctl vm.swapusage') + swaptotal = 0 + if swap =~ /total = (\S+)/ then swaptotal = $1; end + swaptotal end end Facter.add("SwapFree") do confine :kernel => :Darwin setcode do - swap = Facter::Util::Resolution.exec('sysctl vm.swapusage') - swapfree = 0 - if swap =~ /free = (\S+)/ then swapfree = $1; end - swapfree + swap = Facter::Util::Resolution.exec('sysctl vm.swapusage') + swapfree = 0 + if swap =~ /free = (\S+)/ then swapfree = $1; end + swapfree end end Facter.add("SwapEncrypted") do confine :kernel => :Darwin setcode do - swap = Facter::Util::Resolution.exec('sysctl vm.swapusage') - encrypted = false - if swap =~ /\(encrypted\)/ then encrypted = true; end - encrypted + swap = Facter::Util::Resolution.exec('sysctl vm.swapusage') + encrypted = false + if swap =~ /\(encrypted\)/ then encrypted = true; end + encrypted end end @@ -57,9 +57,9 @@ if dev =~ /^\/\S+\s.*\s+(\S+)MB\s+(\S+)MB/ swaptotal += $1.to_i swapfree += $2.to_i - end - end - + end + end + Facter.add("SwapSize") do confine :kernel => :aix setcode do @@ -154,9 +154,9 @@ if dev =~ /^\/\S+\s.*\s+(\d+)\s+(\d+)$/ swaptotal += $1.to_i / 2 swapfree += $2.to_i / 2 - end - end - + end + end + Facter.add("SwapSize") do confine :kernel => :sunos setcode do diff --git a/lib/facter/util/memory.rb b/lib/facter/util/memory.rb index 029b117451..b7ad198dbc 100644 --- a/lib/facter/util/memory.rb +++ b/lib/facter/util/memory.rb @@ -94,4 +94,3 @@ def self.vmstat_darwin_find_free_memory() end end end - From fd4f31c3748d7e59b00473355a12fc232d173eab Mon Sep 17 00:00:00 2001 From: Matt Robinson Date: Tue, 22 Mar 2011 21:53:18 -0700 Subject: [PATCH 0554/3753] (#6817) Fix for Ruby 1.9 by calling .each_line on a string --- lib/facter/util/memory.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/util/memory.rb b/lib/facter/util/memory.rb index b7ad198dbc..d86a4239d4 100644 --- a/lib/facter/util/memory.rb +++ b/lib/facter/util/memory.rb @@ -75,7 +75,7 @@ def self.vmstat_darwin_find_free_memory() memspecfree = 0 vmstats = Facter::Util::Resolution.exec('vm_stat') - vmstats.each do |vmline| + vmstats.each_line do |vmline| case when vmline =~ /page\ssize\sof\s(\d+)\sbytes/ pagesize = $1.to_i From 56b5f10e876b29f9a2fd1906087ebb3cb5d47035 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 28 Mar 2011 11:01:19 -0700 Subject: [PATCH 0555/3753] (#6613) Switch solaris macaddress fact to netstat - ifconfig on solaris will only return the mac address if run as root. netstat -np will provide the information to any user. --- lib/facter/macaddress.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/facter/macaddress.rb b/lib/facter/macaddress.rb index 4a12384d81..bf23ef606f 100644 --- a/lib/facter/macaddress.rb +++ b/lib/facter/macaddress.rb @@ -12,6 +12,18 @@ end end +Facter.add(:macaddress) do + confine :operatingsystem => "Solaris" + setcode do + ether = [] + output = Facter::Util::Resolution.exec("/usr/bin/netstat -np") + output.each_line do |s| + ether.push($1) if s =~ /(?:SPLA)\s+(\w{2}:\w{2}:\w{2}:\w{2}:\w{2}:\w{2})/ + end + ether[0] + end +end + Facter.add(:macaddress) do confine :operatingsystem => %w{FreeBSD OpenBSD} setcode do From 1063753df8938d4ea350199a4dcedd6c08c2d092 Mon Sep 17 00:00:00 2001 From: Jacob Helwig Date: Mon, 28 Mar 2011 11:08:29 -0700 Subject: [PATCH 0556/3753] (#6862) Add a default subject for the mail_patches rake task This will prevent committers to forget to write cover letter subject when rake mail_patches needs to send multiple e-mails. It defaults to the "type" and "branch name" of the patch-set. This is taken from Brice's patch against Puppet (37f9ca09135330ed180fb68d9295a4967a5cc857). Signed-off-by: Brice Figureau --- tasks/rake/mail_patches.rake | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tasks/rake/mail_patches.rake b/tasks/rake/mail_patches.rake index be8dda128b..1346faa316 100644 --- a/tasks/rake/mail_patches.rake +++ b/tasks/rake/mail_patches.rake @@ -34,12 +34,14 @@ task :mail_patches do # If we've got more than one patch, add --compose if files.length > 1 compose = "--compose" + subject = %Q{--subject "#{type} #{name} against #{parent}"} else compose = "" + subject = "" end # Now send the mail. - sh "git send-email #{compose} --no-signed-off-by-cc --suppress-from --to puppet-dev@googlegroups.com 00*.patch" + sh "git send-email #{compose} #{subject} --no-signed-off-by-cc --suppress-from --to puppet-dev@googlegroups.com 00*.patch" # Finally, clean up the patches sh "rm 00*.patch" From 06eb3f57f48a57881e8dadc19714366eed5add18 Mon Sep 17 00:00:00 2001 From: Ben Hughes Date: Tue, 29 Mar 2011 13:38:44 +1100 Subject: [PATCH 0557/3753] (#6883) Update Facter install.rb to be slightly more informative. Give slightly more information to the user when installing facter and the pre-requisite Ruby libraries are not installed. In the case of OpenSSL, the user can have OpenSSL installed, and not the gem/library, but the error message given in no way hints to this. --- install.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install.rb b/install.rb index eb91e7c9fc..d4793eb479 100755 --- a/install.rb +++ b/install.rb @@ -119,7 +119,7 @@ def check_prereqs begin require pre rescue LoadError - puts "Could not load %s; cannot install" % pre + puts "Could not load #{pre} Ruby library; cannot install" exit -1 end } From 7c082702c72c168f3e739b9bc1c7562b0aa15ddd Mon Sep 17 00:00:00 2001 From: Ben H Date: Sun, 13 Mar 2011 14:07:37 +1100 Subject: [PATCH 0558/3753] (#5394) Document each Facter fact. Document all the builtin Facter facts in puppetdoc/rdoc format. This is laying the ground work for using a tool like puppet doc, or puppet describe but for facter, so you can see what a fact is for and how it resolves this. This is the "leg work" of documenting the actual facts, and the syntax of them may change in future. --- lib/facter/Cfkey.rb | 11 ++++++++ lib/facter/architecture.rb | 13 ++++++++++ lib/facter/domain.rb | 20 +++++++++++++++ lib/facter/facterversion.rb | 9 +++++++ lib/facter/fqdn.rb | 11 ++++++++ lib/facter/hardwareisa.rb | 12 +++++++++ lib/facter/hardwaremodel.rb | 13 ++++++++++ lib/facter/hostname.rb | 13 ++++++++++ lib/facter/id.rb | 13 ++++++++++ lib/facter/interfaces.rb | 9 +++++++ lib/facter/ipaddress.rb | 24 ++++++++++++++++++ lib/facter/ipaddress6.rb | 27 +++++++++++++------- lib/facter/iphostnumber.rb | 11 ++++++++ lib/facter/kernel.rb | 11 ++++++++ lib/facter/kernelmajversion.rb | 10 ++++++++ lib/facter/kernelrelease.rb | 13 ++++++++++ lib/facter/kernelversion.rb | 12 +++++++++ lib/facter/lsb.rb | 13 ++++++++++ lib/facter/lsbmajdistrelease.rb | 12 +++++++++ lib/facter/macaddress.rb | 9 +++++++ lib/facter/macosx.rb | 13 ++++++++++ lib/facter/manufacturer.rb | 13 ++++++++++ lib/facter/memory.rb | 17 +++++++++++++ lib/facter/netmask.rb | 9 +++++++ lib/facter/network.rb | 12 +++++++++ lib/facter/operatingsystem.rb | 13 ++++++++++ lib/facter/operatingsystemrelease.rb | 17 +++++++++++++ lib/facter/path.rb | 9 +++++++ lib/facter/physicalprocessorcount.rb | 11 ++++++++ lib/facter/processor.rb | 15 ++++++++++- lib/facter/ps.rb | 12 +++++++++ lib/facter/puppetversion.rb | 10 ++++++++ lib/facter/rubysitedir.rb | 10 ++++++++ lib/facter/rubyversion.rb | 9 +++++++ lib/facter/selinux.rb | 9 +++++++ lib/facter/ssh.rb | 9 +++++++ lib/facter/timezone.rb | 9 +++++++ lib/facter/uptime.rb | 11 ++++++++ lib/facter/uptime_days.rb | 9 +++++++ lib/facter/uptime_hours.rb | 9 +++++++ lib/facter/uptime_seconds.rb | 14 ++++++++++ lib/facter/virtual.rb | 38 ++++++++++++++++++++++++++++ lib/facter/vlans.rb | 10 ++++++++ lib/facter/xendomains.rb | 11 ++++++++ 44 files changed, 555 insertions(+), 10 deletions(-) diff --git a/lib/facter/Cfkey.rb b/lib/facter/Cfkey.rb index a4e0c11798..83533921d5 100644 --- a/lib/facter/Cfkey.rb +++ b/lib/facter/Cfkey.rb @@ -1,3 +1,14 @@ +# Fact: Cfkey +# +# Purpose: Return the public key(s) for CFengine. +# +# Resolution: +# Tries each file of standard localhost.pub & cfkey.pub locations, +# checks if they appear to be a public key, and then join them all together. +# +# Caveats: +# + ## Cfkey.rb ## Facts related to cfengine ## diff --git a/lib/facter/architecture.rb b/lib/facter/architecture.rb index e4aaeba5e2..cd606a2034 100644 --- a/lib/facter/architecture.rb +++ b/lib/facter/architecture.rb @@ -1,3 +1,16 @@ +# Fact: architecture +# +# Purpose: +# Return the CPU hardware architecture. +# +# Resolution: +# On OpenBSD, Linux and Debian's kfreebsd, use the hardwaremodel fact. +# Gentoo and Debian call "x86_86" "amd64". +# Gentoo also calls "i386" "x86". +# +# Caveats: +# + Facter.add(:architecture) do confine :kernel => [:linux, :"gnu/kfreebsd"] setcode do diff --git a/lib/facter/domain.rb b/lib/facter/domain.rb index 29bb204d52..2a79754caf 100644 --- a/lib/facter/domain.rb +++ b/lib/facter/domain.rb @@ -1,3 +1,23 @@ +# Fact: domain +# +# Purpose: +# Return the host's primary DNS domain name. +# +# Resolution: +# On UNIX (excluding Darwin), first try and use the hostname fact, +# which uses the hostname system command, and then parse the output +# of that. +# Failing that it tries the dnsdomainname system command. +# Failing that it uses /etc/resolv.conf and takes the domain from that, or as +# a final resort, the search from that. +# Otherwise returns nil. +# +# On Windows uses the win32ole gem and winmgmts to get the DNSDomain value +# from the Win32 networking stack. +# +# Caveats: +# + Facter.add(:domain) do setcode do # Get the domain from various sources; the order of these diff --git a/lib/facter/facterversion.rb b/lib/facter/facterversion.rb index 0b1cfda024..574da999ff 100644 --- a/lib/facter/facterversion.rb +++ b/lib/facter/facterversion.rb @@ -1,3 +1,12 @@ +# Fact: facterversion +# +# Purpose: returns the version of the facter module. +# +# Resolution: Uses the version constant. +# +# Caveats: +# + Facter.add(:facterversion) do setcode { Facter::FACTERVERSION.to_s } end diff --git a/lib/facter/fqdn.rb b/lib/facter/fqdn.rb index 5ebc5f5c0b..090ca63060 100644 --- a/lib/facter/fqdn.rb +++ b/lib/facter/fqdn.rb @@ -1,3 +1,14 @@ +# Fact: fqdn +# +# Purpose: Returns the fully qualified domain name of the host. +# +# Resolution: Simply joins the hostname fact with the domain name fact. +# +# Caveats: No attempt is made to check that the two facts are accurate or that +# the two facts go together. At no point is there any DNS resolution made +# either. +# + Facter.add(:fqdn) do setcode do host = Facter.value(:hostname) diff --git a/lib/facter/hardwareisa.rb b/lib/facter/hardwareisa.rb index 23f7d08872..9d0830e1fb 100644 --- a/lib/facter/hardwareisa.rb +++ b/lib/facter/hardwareisa.rb @@ -1,3 +1,15 @@ +# Fact: hardwareisa +# +# Purpose: +# Returns hardware processor type. +# +# Resolution: +# On Solaris, Linux and the BSDs simply uses the output of "uname -p" +# +# Caveats: +# Some linuxes return unknown to uname -p with relative ease. +# + Facter.add(:hardwareisa) do setcode 'uname -p', '/bin/sh' confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE SLES Debian Ubuntu Gentoo FreeBSD OpenBSD NetBSD OEL OVS GNU/kFreeBSD} diff --git a/lib/facter/hardwaremodel.rb b/lib/facter/hardwaremodel.rb index 6201bddd7b..8f52fefed3 100644 --- a/lib/facter/hardwaremodel.rb +++ b/lib/facter/hardwaremodel.rb @@ -1,3 +1,16 @@ +# Fact: hardwaremodel +# +# Purpose: +# Returns the hardware model of the system. +# +# Resolution: +# Uses purely "uname -m" on all platforms other than AIX and Windows. +# On AIX uses the parsed "modelname" output of "lsattr -El sys0 -a modelname". +# On Windows uses the 'host_cpu' pulled out of Ruby's config. +# +# Caveats: +# + Facter.add(:hardwaremodel) do setcode 'uname -m' end diff --git a/lib/facter/hostname.rb b/lib/facter/hostname.rb index 188efa4cda..0dcd01c69b 100644 --- a/lib/facter/hostname.rb +++ b/lib/facter/hostname.rb @@ -1,3 +1,16 @@ +# Fact: hostname +# +# Purpose: Return the system's short hostname. +# +# Resolution: +# On all system bar Darwin, parses the output of the "hostname" system command +# to everything before the first period. +# On Darwin, uses the system configuration util to get the LocalHostName +# variable. +# +# Caveats: +# + Facter.add(:hostname, :ldapname => "cn") do setcode do hostname = nil diff --git a/lib/facter/id.rb b/lib/facter/id.rb index 1c4228469f..cc0a0a6b65 100644 --- a/lib/facter/id.rb +++ b/lib/facter/id.rb @@ -1,3 +1,16 @@ +# Fact: id +# +# Purpose: Internal fact used to specity the program to return the currently +# running user id. +# +# Resolution: +# On all Unixes bar Solaris, just returns "whoami". +# On Solaris, parses the output of the "id" command to grab the username, as +# Solaris doesn't have the whoami command. +# +# Caveats: +# + Facter.add(:id) do setcode "whoami" end diff --git a/lib/facter/interfaces.rb b/lib/facter/interfaces.rb index 4fbaef1966..04b1c198fa 100644 --- a/lib/facter/interfaces.rb +++ b/lib/facter/interfaces.rb @@ -1,3 +1,12 @@ +# Fact: interfaces +# +# Purpose: +# +# Resolution: +# +# Caveats: +# + # interfaces.rb # Try to get additional Facts about the machine's network interfaces # diff --git a/lib/facter/ipaddress.rb b/lib/facter/ipaddress.rb index d5634087f5..52605042fe 100644 --- a/lib/facter/ipaddress.rb +++ b/lib/facter/ipaddress.rb @@ -1,3 +1,27 @@ +# Fact: ipaddress +# +# Purpose: Return the main IP address for a host. +# +# Resolution: +# On the Unixes does an ifconfig, and returns the first non 127.0.0.0/8 +# subnetted IP it finds. +# On Windows, it attempts to use the socket library and resolve the machine's +# hostname via DNS. +# +# On LDAP based hosts it tries to use either the win32/resolv library to +# resolve the hostname to an IP address, or on Unix, it uses the resolv +# library. +# +# As a fall back for undefined systems, it tries to run the "host" command to +# resolve the machine's hostname using the system DNS. +# +# Caveats: +# DNS resolution relies on working DNS infrastructure and resolvers on the +# host system. +# The ifconfig parsing purely takes the first IP address it finds without any +# checking this is a useful IP address. +# + Facter.add(:ipaddress) do confine :kernel => :linux setcode do diff --git a/lib/facter/ipaddress6.rb b/lib/facter/ipaddress6.rb index 547d636a73..b494b9d63e 100644 --- a/lib/facter/ipaddress6.rb +++ b/lib/facter/ipaddress6.rb @@ -1,17 +1,26 @@ +# Fact: ipaddress6 +# +# Purpose: Returns the "main" IPv6 IP address of a system. +# +# Resolution: +# OS dependant code that parses the output of various networking +# tools and currently not very intelligent. Returns the first +# non-loopback and non-linklocal address found in the ouput unless +# a default route can be mapped to a routeable interface. Guessing +# an interface is currently only possible with BSD type systems +# to many assumptions have to be made on other platforms to make +# this work with the current code. Most code ported or modeled +# after the ipaddress fact for the sake of similar functionality +# and familiar mechanics. +# +# Caveats: +# + # Cody Herriges # # Used the ipaddress fact that is already part of # Facter as a template. -# OS dependant code that parses the output of various networking -# tools and currently not very intelligent. Returns the first -# non-loopback and non-linklocal address found in the ouput unless -# a default route can be mapped to a routeable interface. Guessing -# an interface is currently only possible with BSD type systems -# to many assumptions have to be made on other platforms to make -# this work with the current code. Most code ported or modeled -# after the ipaddress fact for the sake of similar functionality -# and familiar mechanics. Facter.add(:ipaddress6) do confine :kernel => :linux setcode do diff --git a/lib/facter/iphostnumber.rb b/lib/facter/iphostnumber.rb index bc384325fe..cddaadd1e6 100644 --- a/lib/facter/iphostnumber.rb +++ b/lib/facter/iphostnumber.rb @@ -1,3 +1,14 @@ +# Fact: iphostnumber +# +# Purpose: On selected versions of Darwin, returns the host's IP address. +# +# Resolution: +# Uses either the scutil program to get the localhost name, or parses output +# of ifconfig for a MAC address. +# +# Caveats: +# + Facter.add(:iphostnumber) do confine :kernel => :darwin, :kernelrelease => "R6" setcode do diff --git a/lib/facter/kernel.rb b/lib/facter/kernel.rb index 66f21cea2e..f457e01e35 100644 --- a/lib/facter/kernel.rb +++ b/lib/facter/kernel.rb @@ -1,3 +1,14 @@ +# Fact: kernel +# +# Purpose: Returns the operating system's name. +# +# Resolution: +# Uses Ruby's rbconfig to find host_os, if that is a Windows derivative, the +# returns 'windows', otherwise returns "uname -s" verbatim. +# +# Caveats: +# + Facter.add(:kernel) do setcode do require 'rbconfig' diff --git a/lib/facter/kernelmajversion.rb b/lib/facter/kernelmajversion.rb index 32fd7aabbe..84f71d4504 100644 --- a/lib/facter/kernelmajversion.rb +++ b/lib/facter/kernelmajversion.rb @@ -1,3 +1,13 @@ +# Fact: kernelmajversion +# +# Purpose: Return the operating system's release number's major value. +# +# Resolution: +# Takes the first 2 elements of the kernel version as delimited by periods. +# +# Caveats: +# + Facter.add("kernelmajversion") do setcode do Facter.value(:kernelversion).split('.')[0..1].join('.') diff --git a/lib/facter/kernelrelease.rb b/lib/facter/kernelrelease.rb index 2f6ae45119..a6f9c2c48e 100644 --- a/lib/facter/kernelrelease.rb +++ b/lib/facter/kernelrelease.rb @@ -1,3 +1,16 @@ +# Fact: kernelrelease +# +# Purpose: Return the operating system's release number. +# +# Resolution: +# On AIX returns the output from the "oslevel -s" system command. +# On Windows based systems, uses the win32ole gem to query Windows Management +# for the 'Win32_OperatingSystem' value. +# Otherwise uses the output of "uname -r" system command. +# +# Caveats: +# + Facter.add(:kernelrelease) do setcode 'uname -r' end diff --git a/lib/facter/kernelversion.rb b/lib/facter/kernelversion.rb index cac6c145c7..7e0d95cef1 100644 --- a/lib/facter/kernelversion.rb +++ b/lib/facter/kernelversion.rb @@ -1,3 +1,15 @@ +# Fact: kernelversion +# +# Purpose: Return the operating system's kernel version. +# +# Resolution: +# On Solaris and SunOS based machines, returns the output of "uname -v". +# Otherwise returns the 'kernerlversion' fact up to the first '-'. This may be +# the entire 'kernelversion' fact in many cases. +# +# Caveats: +# + Facter.add("kernelversion") do setcode do Facter['kernelrelease'].value.split('-')[0] diff --git a/lib/facter/lsb.rb b/lib/facter/lsb.rb index bf4b9db706..107419ab11 100644 --- a/lib/facter/lsb.rb +++ b/lib/facter/lsb.rb @@ -1,3 +1,16 @@ +# Fact: lsb +# +# Purpose: Return Linux Standard Base information for the host. +# +# Resolution: +# Uses the lsb_release system command and parses the output with a series of +# regular expressions. +# +# Caveats: +# Only works on Linux (and the kfreebsd derivative) systems. +# Requires the lsb_release program, which may not be installed by default. +# Also is as only as accurate as that program outputs. + ## lsb.rb ## Facts related to Linux Standard Base (LSB) ## diff --git a/lib/facter/lsbmajdistrelease.rb b/lib/facter/lsbmajdistrelease.rb index 34a2f1ea3d..365954166b 100644 --- a/lib/facter/lsbmajdistrelease.rb +++ b/lib/facter/lsbmajdistrelease.rb @@ -1,3 +1,15 @@ +# Fact: lsbmajdistrelease +# +# Purpose: Returns the major version of the operation system version as gleaned +# from the lsbdistrelease fact. +# +# Resolution: +# Parses the lsbdistrelease fact for numbers followed by a period and +# returns those, or just the lsbdistrelease fact if none were found. +# +# Caveats: +# + # lsbmajdistrelease.rb # require 'facter' diff --git a/lib/facter/macaddress.rb b/lib/facter/macaddress.rb index 4a12384d81..fa9e5672b1 100644 --- a/lib/facter/macaddress.rb +++ b/lib/facter/macaddress.rb @@ -1,3 +1,12 @@ +# Fact: macaddress +# +# Purpose: +# +# Resolution: +# +# Caveats: +# + require 'facter/util/macaddress' Facter.add(:macaddress) do diff --git a/lib/facter/macosx.rb b/lib/facter/macosx.rb index 3841c50045..f5289df35c 100644 --- a/lib/facter/macosx.rb +++ b/lib/facter/macosx.rb @@ -1,3 +1,16 @@ +# Fact: macosx +# +# Purpose: +# Returns a number of Mac specific facts, from system profiler and +# sw_vers. +# +# Resolution: +# Uses util/macosx.rb to do the fact reconnaissance, then outputs them +# preceded by 'sp_' +# +# Caveats: +# + # # macosx.rb # Additional Facts coming from Mac OS X system_profiler command diff --git a/lib/facter/manufacturer.rb b/lib/facter/manufacturer.rb index 4f2df98d86..26aef5fe08 100644 --- a/lib/facter/manufacturer.rb +++ b/lib/facter/manufacturer.rb @@ -1,3 +1,16 @@ +# Fact: manufacturer +# +# Purpose: Return the hardware manufacturer information about the hardware. +# +# Resolution: +# On OpenBSD, queries sysctl values, via a util class. +# On SunOS Sparc, uses prtdiag via a util class. +# On Windows, queries the system via a util class. +# Uses the 'util/manufacturer.rb' for fallback parsing. +# +# Caveats: +# + # manufacturer.rb # Facts related to hardware manufacturer # diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb index 0aa5e882a1..992f2ade4c 100644 --- a/lib/facter/memory.rb +++ b/lib/facter/memory.rb @@ -1,3 +1,20 @@ +# Fact: memory +# +# Purpose: Return information about memory and swap usage. +# +# Resolution: +# On Linuxes, uses Facter::Memory.meminfo_number from +# 'facter/util/memory.rb' +# On AIX, parses "swap -l" for swap values only. +# On OpenBSD, it parses "swapctl -l" for swap values, vmstat via a module for +# free memory, and "sysctl hw.physmem" for maximum memory. +# On Solaris, use "swap -l" for swap values, and parsing prtconf for maximum +# memory, and again, the vmstat module for free memory. +# +# Caveats: +# Some BSD platforms aren't covered at all. AIX is missing memory values. +# + # memory.rb # Additional Facts for memory/swap usage # diff --git a/lib/facter/netmask.rb b/lib/facter/netmask.rb index d6d125e2af..fbb84f59ee 100644 --- a/lib/facter/netmask.rb +++ b/lib/facter/netmask.rb @@ -1,3 +1,12 @@ +# Fact: netmask +# +# Purpose: Returns the netmask for the main interfaces. +# +# Resolution: Uses the facter/util/netmask library routines. +# +# Caveats: +# + # netmask.rb # Find the netmask of the primary ipaddress # Copyright (C) 2007 David Schmitt diff --git a/lib/facter/network.rb b/lib/facter/network.rb index df53ce47db..d4faaac16b 100644 --- a/lib/facter/network.rb +++ b/lib/facter/network.rb @@ -1,3 +1,15 @@ +# Fact: network +# +# Purpose: +# Get IP, network and netmask information for available network +# interfacs. +# +# Resolution: +# Uses 'facter/util/ip' to enumerate interfaces and return their information. +# +# Caveats: +# + require 'facter/util/ip' Facter::Util::IP.get_interfaces.each do |interface| diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index 25eeb00edb..a90c283554 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -1,3 +1,16 @@ +# Fact: operatingsystem +# +# Purpose: Return the name of the operating system. +# +# Resolution: +# If the kernel is a Linux kernel, check for the existence of a selection of +# files in /etc/ to find the specific flavour. +# On SunOS based kernels, return Solaris. +# On systems other than Linux, use the kernel value. +# +# Caveats: +# + require 'facter/lsb' Facter.add(:operatingsystem) do diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index da1b76ab3f..347fe7f05a 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -1,3 +1,20 @@ +# Fact: operatingsystemrelease +# +# Purpose: Returns the release of the operating system. +# +# Resolution: +# On RedHat derivatives, returns their '/etc/-release' file. +# On Debian, returns '/etc/debian_version'. +# On Ubuntu, parses '/etc/issue' for the release version. +# On Suse, derivatives, parses '/etc/SuSE-release' for a selection of version +# information. +# On Slackware, parses '/etc/slackware-version'. +# +# On all remaining systems, returns the 'kernelrelease' value. +# +# Caveats: +# + Facter.add(:operatingsystemrelease) do confine :operatingsystem => %w{CentOS Fedora oel ovs RedHat MeeGo} setcode do diff --git a/lib/facter/path.rb b/lib/facter/path.rb index 03907c0665..71df6cdf53 100644 --- a/lib/facter/path.rb +++ b/lib/facter/path.rb @@ -1,3 +1,12 @@ +# Fact: path +# +# Purpose: Returns the $PATH variable. +# +# Resolution: Gets $PATH from the environment. +# +# Caveats: +# + Facter.add(:path) do setcode do ENV['PATH'] diff --git a/lib/facter/physicalprocessorcount.rb b/lib/facter/physicalprocessorcount.rb index 8fe643e3ca..9c59614bf9 100644 --- a/lib/facter/physicalprocessorcount.rb +++ b/lib/facter/physicalprocessorcount.rb @@ -1,3 +1,14 @@ +# Fact: physicalprocessorcount +# +# Purpose: Return the number of physical processors. +# +# Resolution: +# On linux, parses the output of '/proc/cpuinfo' for the number of unique +# lines with "physical id" in them. +# +# Caveats: +# + Facter.add("physicalprocessorcount") do confine :kernel => :linux diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb index c71bad413f..ec196b2172 100644 --- a/lib/facter/processor.rb +++ b/lib/facter/processor.rb @@ -1,5 +1,18 @@ +# Fact: processor +# +# Purpose: +# Additional Facts about the machine's CPUs. +# +# Resolution: +# On Linux and kFreeBSD, parse '/proc/cpuinfo' for each processor. +# On AIX, parse the output of 'lsdev' for it's processor section. +# On OpenBSD, use 'uname -p' and the sysctl variable for 'hw.ncpu' for CPU +# count. +# +# Caveats: +# + # processor.rb -# Additional Facts about the machine's CPUs # # Copyright (C) 2006 Mooter Media Ltd # Author: Matthew Palmer diff --git a/lib/facter/ps.rb b/lib/facter/ps.rb index e7bcdae984..ef803fb870 100644 --- a/lib/facter/ps.rb +++ b/lib/facter/ps.rb @@ -1,3 +1,15 @@ +# Fact: ps +# +# Purpose: Internal fact for what to use to list all processes. Used by +# Service{} type in Puppet. +# +# Resolution: +# Assumes "ps -ef" for all operating systems other than BSD derivatives, where +# it uses "ps auxwww" +# +# Caveats: +# + Facter.add(:ps) do setcode do 'ps -ef' end end diff --git a/lib/facter/puppetversion.rb b/lib/facter/puppetversion.rb index d2eb1f3061..01422f03d1 100644 --- a/lib/facter/puppetversion.rb +++ b/lib/facter/puppetversion.rb @@ -1,3 +1,13 @@ +# Fact: puppetversion +# +# Purpose: Return the version of puppet installed. +# +# Resolution: +# Requres puppet via Ruby and returns it's version constant. +# +# Caveats: +# + Facter.add(:puppetversion) do setcode do begin diff --git a/lib/facter/rubysitedir.rb b/lib/facter/rubysitedir.rb index c205322033..9973329237 100644 --- a/lib/facter/rubysitedir.rb +++ b/lib/facter/rubysitedir.rb @@ -1,3 +1,13 @@ +# Fact: rubysitedir +# +# Purpose: Returns Ruby's site library directory. +# +# Resolution: Works out the version to major/minor (1.8, 1.9, etc), then joins +# that with all the $: library paths. +# +# Caveats: +# + Facter.add :rubysitedir do setcode do version = RUBY_VERSION.to_s.sub(/\.\d+$/, '') diff --git a/lib/facter/rubyversion.rb b/lib/facter/rubyversion.rb index 48f5cc8842..e578400ed3 100644 --- a/lib/facter/rubyversion.rb +++ b/lib/facter/rubyversion.rb @@ -1,3 +1,12 @@ +# Fact: rubyversion +# +# Purpose: Returns the version of Ruby facter is running under. +# +# Resolution: Returns RUBY_VERSION. +# +# Caveats: +# + Facter.add(:rubyversion) do setcode { RUBY_VERSION.to_s } end diff --git a/lib/facter/selinux.rb b/lib/facter/selinux.rb index 9fab4276b8..1555da0b3d 100644 --- a/lib/facter/selinux.rb +++ b/lib/facter/selinux.rb @@ -1,3 +1,12 @@ +# Fact: selinux +# +# Purpose: +# +# Resolution: +# +# Caveats: +# + # Fact for SElinux # Written by immerda admin team (admin(at)immerda.ch) diff --git a/lib/facter/ssh.rb b/lib/facter/ssh.rb index 318e9d44c3..beb5692843 100644 --- a/lib/facter/ssh.rb +++ b/lib/facter/ssh.rb @@ -1,3 +1,12 @@ +# Fact: ssh +# +# Purpose: +# +# Resolution: +# +# Caveats: +# + ## ssh.rb ## Facts related to SSH ## diff --git a/lib/facter/timezone.rb b/lib/facter/timezone.rb index 744e06a943..462c9ba67c 100644 --- a/lib/facter/timezone.rb +++ b/lib/facter/timezone.rb @@ -1,3 +1,12 @@ +# Fact: timezone +# +# Purpose: Return the machine's time zone. +# +# Resolution: Uses's Ruby's Time module's Time.new call. +# +# Caveats: +# + Facter.add("timezone") do setcode do Time.new.zone diff --git a/lib/facter/uptime.rb b/lib/facter/uptime.rb index 56a959ba25..7aeeb3986b 100644 --- a/lib/facter/uptime.rb +++ b/lib/facter/uptime.rb @@ -1,3 +1,14 @@ +# Fact: uptime +# +# Purpose: return the system uptime in a human readable format. +# +# Resolution: +# Does basic maths on the "uptime_seconds" fact to return a count of +# days, hours and minutes of uptime +# +# Caveats: +# + require 'facter/util/uptime' Facter.add(:uptime) do diff --git a/lib/facter/uptime_days.rb b/lib/facter/uptime_days.rb index add305c59f..25af966d49 100644 --- a/lib/facter/uptime_days.rb +++ b/lib/facter/uptime_days.rb @@ -1,3 +1,12 @@ +# Fact: uptime_days +# +# Purpose: Return purely number of days of uptime. +# +# Resolution: Divides uptime_hours fact by 24. +# +# Caveats: +# + Facter.add(:uptime_days) do setcode do hours = Facter.value(:uptime_hours) diff --git a/lib/facter/uptime_hours.rb b/lib/facter/uptime_hours.rb index ce691d25fa..d02ac3201b 100644 --- a/lib/facter/uptime_hours.rb +++ b/lib/facter/uptime_hours.rb @@ -1,3 +1,12 @@ +# Fact: uptime_hours +# +# Purpose: Return purely number of hours of uptime. +# +# Resolution: Divides uptime_seconds fact by 3600. +# +# Caveats: +# + Facter.add(:uptime_hours) do setcode do seconds = Facter.value(:uptime_seconds) diff --git a/lib/facter/uptime_seconds.rb b/lib/facter/uptime_seconds.rb index 14bb573a4c..9793e41610 100644 --- a/lib/facter/uptime_seconds.rb +++ b/lib/facter/uptime_seconds.rb @@ -1,3 +1,17 @@ +# Fact: uptime_seconds +# +# Purpose: Return purely number of seconds of uptime. +# +# Resolution: +# Using the 'facter/util/uptime.rb' module, try a verity of methods to acquire +# the uptime on Unix. +# +# On Windows, the module calculates the uptime by the "LastBootupTime" Windows +# management value. +# +# Caveats: +# + require 'facter/util/uptime' Facter.add(:uptime_seconds) do diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 468ab7767a..02802e6440 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -1,3 +1,30 @@ +# Fact: virtual +# +# Purpose: Determine if the system's hardware is real or virtualised. +# +# Resolution: +# Assumes physical unless proven otherwise. +# +# On Darwin, use the macosx util module to acquire the SPDisplaysDataType, +# from that parse it to see if it's VMWare or Parallels pretending to be the +# display. +# +# On Linux, BSD, Solaris and HPUX: +# Much of the logic here is obscured behind util/virtual.rb, which rather +# than document here, which would encourage drift, just refer to it. +# The Xen tests in here rely on /sys and /proc, and check for the presence and +# contents of files in there. +# If after all the other tests, it's still seen as physical, then it tries to +# parse the output of the "lspci", "dmidecode" and "prtdiag" and parses them +# for obvious signs of being under VMWare or Parallels. +# Finally it checks for the existence of vmware-vmx, which would hint it's +# VMWare. +# +# Caveats: +# Virtualbox detection isn't implemented. +# Many checks rely purely on existence of files. +# + require 'facter/util/virtual' Facter.add("virtual") do @@ -108,6 +135,17 @@ end end +# Fact: is_virtual +# +# Purpose: returning true or false for if a machine is virtualised or not. +# +# Resolution: The Xen domain 0 machine is virtualised to a degree, but is generally +# not viewed as being a virtual machine. This checks that the machine is not +# physical nor xen0, if that is the case, it is virtual. +# +# Caveats: +# + Facter.add("is_virtual") do confine :kernel => %w{Linux FreeBSD OpenBSD SunOS HP-UX Darwin GNU/kFreeBSD} diff --git a/lib/facter/vlans.rb b/lib/facter/vlans.rb index d65bdd8741..8c485a43b3 100644 --- a/lib/facter/vlans.rb +++ b/lib/facter/vlans.rb @@ -1,3 +1,13 @@ +# Fact: vlans +# +# Purpose: On Linux, return a list of all the VLANs on the system. +# +# Resolution: On Linux only, checks for and reads /proc/net/vlan/config and +# parses it. +# +# Caveats: +# + require 'facter/util/vlans' Facter.add("vlans") do diff --git a/lib/facter/xendomains.rb b/lib/facter/xendomains.rb index 972ac90df9..5bcde05879 100644 --- a/lib/facter/xendomains.rb +++ b/lib/facter/xendomains.rb @@ -1,3 +1,14 @@ +# Fact: xendomains +# +# Purpose: Return the list of Xen domains on the Dom0. +# +# Resolution: +# On a Xen Dom0 host, return a list of Xen domains using the 'util/xendomains' +# library. +# +# Caveats: +# + require 'facter/util/xendomains' Facter.add("xendomains") do From 0b5b546e304fcf15a60f6489f89509e3bd2620f4 Mon Sep 17 00:00:00 2001 From: Ben Hughes Date: Tue, 29 Mar 2011 13:38:44 +1100 Subject: [PATCH 0559/3753] (#6883) Update Facter install.rb to be slightly more informative. Give slightly more information to the user when installing facter and the pre-requisite Ruby libraries are not installed. In the case of OpenSSL, the user can have OpenSSL installed, and not the gem/library, but the error message given in no way hints to this. --- install.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install.rb b/install.rb index eb91e7c9fc..d4793eb479 100755 --- a/install.rb +++ b/install.rb @@ -119,7 +119,7 @@ def check_prereqs begin require pre rescue LoadError - puts "Could not load %s; cannot install" % pre + puts "Could not load #{pre} Ruby library; cannot install" exit -1 end } From e42e57c1a5674d77dfa14e10ade9592f5d2052e7 Mon Sep 17 00:00:00 2001 From: Michael Kincaid Date: Thu, 31 Mar 2011 16:04:57 -0700 Subject: [PATCH 0560/3753] (#3856) Add virtualbox detection via lspci (graphics card), dmidecode, and prtdiag for Solaris and corresponding tests. Darwin case is not handled yet. --- lib/facter/virtual.rb | 5 +++++ spec/unit/virtual_spec.rb | 27 +++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 02802e6440..7c649ba891 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -104,6 +104,9 @@ # --- look for the vmware video card to determine if it is virtual => vmware. # --- 00:0f.0 VGA compatible controller: VMware Inc [VMware SVGA II] PCI Display Adapter result = "vmware" if p =~ /VM[wW]are/ + # --- look for virtualbox video card to determine if it is virtual => virtualbox. + # --- 00:02.0 VGA compatible controller: InnoTek Systemberatung GmbH VirtualBox Graphics Adapter + result = "virtualbox" if p =~ /VirtualBox/ # --- look for pci vendor id used by Parallels video card # --- 01:00.0 VGA compatible controller: Unknown device 1ab8:4005 result = "parallels" if p =~ /1ab8:|[Pp]arallels/ @@ -114,6 +117,7 @@ output.each_line do |pd| result = "parallels" if pd =~ /Parallels/ result = "vmware" if pd =~ /VMware/ + result = "virtualbox" if pd =~ /VirtualBox/ end else output = Facter::Util::Resolution.exec('prtdiag') @@ -121,6 +125,7 @@ output.each_line do |pd| result = "parallels" if pd =~ /Parallels/ result = "vmware" if pd =~ /VMware/ + result = "virtualbox" if pd =~ /VirtualBox/ end end end diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index 7e50847c46..ac0a9bb4db 100644 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -94,6 +94,12 @@ Facter.fact(:virtual).value.should == "vmware" end + it "should be virtualbox with VirtualBox vendor name from lspci" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter::Util::Resolution.stubs(:exec).with('lspci').returns("00:02.0 VGA compatible controller: InnoTek Systemberatung GmbH VirtualBox Graphics Adapter") + Facter.fact(:virtual).value.should == "virtualbox" + end + it "should be vmware with VMWare vendor name from dmidecode" do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) @@ -108,6 +114,13 @@ Facter.fact(:virtual).value.should == "parallels" end + it "should be virtualbox with VirtualBox vendor name from dmidecode" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) + Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("BIOS Information\nVendor: innotek GmbH\nVersion: VirtualBox\n\nSystem Information\nManufacturer: innotek GmbH\nProduct Name: VirtualBox\nFamily: Virtual Machine") + Facter.fact(:virtual).value.should == "virtualbox" + end + it "should be vmware with VMWare vendor name from prtdiag" do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) @@ -123,6 +136,14 @@ Facter::Util::Resolution.stubs(:exec).with('prtdiag').returns("System Configuration: Parallels Virtual Platform") Facter.fact(:virtual).value.should == "parallels" end + + it "should be virtualbox with VirtualBox vendor name from prtdiag" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) + Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns(nil) + Facter::Util::Resolution.stubs(:exec).with('prtdiag').returns("System Configuration: innotek GmbH VirtualBox") + Facter.fact(:virtual).value.should == "virtualbox" + end end end @@ -152,6 +173,12 @@ Facter.fact(:is_virtual).value.should == "true" end + it "should be true when running on virtualbox" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:virtual).stubs(:value).returns("virtualbox") + Facter.fact(:is_virtual).value.should == "true" + end + it "should be true when running on openvz" do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter.fact(:virtual).stubs(:value).returns("openvzve") From f6c9927b6450894fc7036881eab5ff2fb9524e71 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Fri, 1 Apr 2011 10:44:22 -0700 Subject: [PATCH 0561/3753] (#6719) Corrected faulty logic in bugfix --- lib/facter/virtual.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index e9571df729..3c2d10f0a2 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -25,7 +25,7 @@ setcode do - if Facter::Util::Virtual.zone? and Facter(:operatingsystem) == "Solaris" + if Facter.value(:operatingsystem) == "Solaris" and Facter::Util::Virtual.zone? result = "zone" end From 861c2b20f44caf4d4f5a1188663f5b82a3435836 Mon Sep 17 00:00:00 2001 From: Matt Robinson Date: Tue, 22 Mar 2011 21:37:10 -0700 Subject: [PATCH 0562/3753] maint: cleanup whitespace --- lib/facter/memory.rb | 36 ++++++++++++++++++------------------ lib/facter/util/memory.rb | 1 - 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb index 9dd2e2929b..0aa5e882a1 100644 --- a/lib/facter/memory.rb +++ b/lib/facter/memory.rb @@ -23,30 +23,30 @@ Facter.add("SwapSize") do confine :kernel => :Darwin setcode do - swap = Facter::Util::Resolution.exec('sysctl vm.swapusage') - swaptotal = 0 - if swap =~ /total = (\S+)/ then swaptotal = $1; end - swaptotal + swap = Facter::Util::Resolution.exec('sysctl vm.swapusage') + swaptotal = 0 + if swap =~ /total = (\S+)/ then swaptotal = $1; end + swaptotal end end Facter.add("SwapFree") do confine :kernel => :Darwin setcode do - swap = Facter::Util::Resolution.exec('sysctl vm.swapusage') - swapfree = 0 - if swap =~ /free = (\S+)/ then swapfree = $1; end - swapfree + swap = Facter::Util::Resolution.exec('sysctl vm.swapusage') + swapfree = 0 + if swap =~ /free = (\S+)/ then swapfree = $1; end + swapfree end end Facter.add("SwapEncrypted") do confine :kernel => :Darwin setcode do - swap = Facter::Util::Resolution.exec('sysctl vm.swapusage') - encrypted = false - if swap =~ /\(encrypted\)/ then encrypted = true; end - encrypted + swap = Facter::Util::Resolution.exec('sysctl vm.swapusage') + encrypted = false + if swap =~ /\(encrypted\)/ then encrypted = true; end + encrypted end end @@ -57,9 +57,9 @@ if dev =~ /^\/\S+\s.*\s+(\S+)MB\s+(\S+)MB/ swaptotal += $1.to_i swapfree += $2.to_i - end - end - + end + end + Facter.add("SwapSize") do confine :kernel => :aix setcode do @@ -154,9 +154,9 @@ if dev =~ /^\/\S+\s.*\s+(\d+)\s+(\d+)$/ swaptotal += $1.to_i / 2 swapfree += $2.to_i / 2 - end - end - + end + end + Facter.add("SwapSize") do confine :kernel => :sunos setcode do diff --git a/lib/facter/util/memory.rb b/lib/facter/util/memory.rb index 029b117451..b7ad198dbc 100644 --- a/lib/facter/util/memory.rb +++ b/lib/facter/util/memory.rb @@ -94,4 +94,3 @@ def self.vmstat_darwin_find_free_memory() end end end - From e056218cc6f7fd7a40923ec6472d6020e220a81f Mon Sep 17 00:00:00 2001 From: Matt Robinson Date: Tue, 22 Mar 2011 21:53:18 -0700 Subject: [PATCH 0563/3753] (#6817) Fix for Ruby 1.9 by calling .each_line on a string --- lib/facter/util/memory.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/util/memory.rb b/lib/facter/util/memory.rb index b7ad198dbc..d86a4239d4 100644 --- a/lib/facter/util/memory.rb +++ b/lib/facter/util/memory.rb @@ -75,7 +75,7 @@ def self.vmstat_darwin_find_free_memory() memspecfree = 0 vmstats = Facter::Util::Resolution.exec('vm_stat') - vmstats.each do |vmline| + vmstats.each_line do |vmline| case when vmline =~ /page\ssize\sof\s(\d+)\sbytes/ pagesize = $1.to_i From d6967a0630dec168059316426b0eb4228b27e451 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 28 Mar 2011 11:01:19 -0700 Subject: [PATCH 0564/3753] (#6613) Switch solaris macaddress fact to netstat - ifconfig on solaris will only return the mac address if run as root. netstat -np will provide the information to any user. --- lib/facter/macaddress.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/facter/macaddress.rb b/lib/facter/macaddress.rb index 4a12384d81..bf23ef606f 100644 --- a/lib/facter/macaddress.rb +++ b/lib/facter/macaddress.rb @@ -12,6 +12,18 @@ end end +Facter.add(:macaddress) do + confine :operatingsystem => "Solaris" + setcode do + ether = [] + output = Facter::Util::Resolution.exec("/usr/bin/netstat -np") + output.each_line do |s| + ether.push($1) if s =~ /(?:SPLA)\s+(\w{2}:\w{2}:\w{2}:\w{2}:\w{2}:\w{2})/ + end + ether[0] + end +end + Facter.add(:macaddress) do confine :operatingsystem => %w{FreeBSD OpenBSD} setcode do From af4947c603b8f6610d861f303a7813cbc5ecf5e7 Mon Sep 17 00:00:00 2001 From: Jacob Helwig Date: Mon, 28 Mar 2011 11:08:29 -0700 Subject: [PATCH 0565/3753] (#6862) Add a default subject for the mail_patches rake task This will prevent committers to forget to write cover letter subject when rake mail_patches needs to send multiple e-mails. It defaults to the "type" and "branch name" of the patch-set. This is taken from Brice's patch against Puppet (37f9ca09135330ed180fb68d9295a4967a5cc857). Signed-off-by: Brice Figureau --- tasks/rake/mail_patches.rake | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tasks/rake/mail_patches.rake b/tasks/rake/mail_patches.rake index be8dda128b..1346faa316 100644 --- a/tasks/rake/mail_patches.rake +++ b/tasks/rake/mail_patches.rake @@ -34,12 +34,14 @@ task :mail_patches do # If we've got more than one patch, add --compose if files.length > 1 compose = "--compose" + subject = %Q{--subject "#{type} #{name} against #{parent}"} else compose = "" + subject = "" end # Now send the mail. - sh "git send-email #{compose} --no-signed-off-by-cc --suppress-from --to puppet-dev@googlegroups.com 00*.patch" + sh "git send-email #{compose} #{subject} --no-signed-off-by-cc --suppress-from --to puppet-dev@googlegroups.com 00*.patch" # Finally, clean up the patches sh "rm 00*.patch" From d31e3f9ee6b36e707a189569132bcdcea79f20f6 Mon Sep 17 00:00:00 2001 From: Ben H Date: Sun, 13 Mar 2011 14:07:37 +1100 Subject: [PATCH 0566/3753] (#5394) Document each Facter fact. Document all the builtin Facter facts in puppetdoc/rdoc format. This is laying the ground work for using a tool like puppet doc, or puppet describe but for facter, so you can see what a fact is for and how it resolves this. This is the "leg work" of documenting the actual facts, and the syntax of them may change in future. --- lib/facter/Cfkey.rb | 11 ++++++++ lib/facter/architecture.rb | 13 ++++++++++ lib/facter/domain.rb | 20 +++++++++++++++ lib/facter/facterversion.rb | 9 +++++++ lib/facter/fqdn.rb | 11 ++++++++ lib/facter/hardwareisa.rb | 12 +++++++++ lib/facter/hardwaremodel.rb | 13 ++++++++++ lib/facter/hostname.rb | 13 ++++++++++ lib/facter/id.rb | 13 ++++++++++ lib/facter/interfaces.rb | 9 +++++++ lib/facter/ipaddress.rb | 24 ++++++++++++++++++ lib/facter/ipaddress6.rb | 27 +++++++++++++------- lib/facter/iphostnumber.rb | 11 ++++++++ lib/facter/kernel.rb | 11 ++++++++ lib/facter/kernelmajversion.rb | 10 ++++++++ lib/facter/kernelrelease.rb | 13 ++++++++++ lib/facter/kernelversion.rb | 12 +++++++++ lib/facter/lsb.rb | 13 ++++++++++ lib/facter/lsbmajdistrelease.rb | 12 +++++++++ lib/facter/macaddress.rb | 9 +++++++ lib/facter/macosx.rb | 13 ++++++++++ lib/facter/manufacturer.rb | 13 ++++++++++ lib/facter/memory.rb | 17 +++++++++++++ lib/facter/netmask.rb | 9 +++++++ lib/facter/network.rb | 12 +++++++++ lib/facter/operatingsystem.rb | 13 ++++++++++ lib/facter/operatingsystemrelease.rb | 17 +++++++++++++ lib/facter/path.rb | 9 +++++++ lib/facter/physicalprocessorcount.rb | 11 ++++++++ lib/facter/processor.rb | 15 ++++++++++- lib/facter/ps.rb | 12 +++++++++ lib/facter/puppetversion.rb | 10 ++++++++ lib/facter/rubysitedir.rb | 10 ++++++++ lib/facter/rubyversion.rb | 9 +++++++ lib/facter/selinux.rb | 9 +++++++ lib/facter/ssh.rb | 9 +++++++ lib/facter/timezone.rb | 9 +++++++ lib/facter/uptime.rb | 11 ++++++++ lib/facter/uptime_days.rb | 9 +++++++ lib/facter/uptime_hours.rb | 9 +++++++ lib/facter/uptime_seconds.rb | 14 ++++++++++ lib/facter/virtual.rb | 38 ++++++++++++++++++++++++++++ lib/facter/vlans.rb | 10 ++++++++ lib/facter/xendomains.rb | 11 ++++++++ 44 files changed, 555 insertions(+), 10 deletions(-) diff --git a/lib/facter/Cfkey.rb b/lib/facter/Cfkey.rb index a4e0c11798..83533921d5 100644 --- a/lib/facter/Cfkey.rb +++ b/lib/facter/Cfkey.rb @@ -1,3 +1,14 @@ +# Fact: Cfkey +# +# Purpose: Return the public key(s) for CFengine. +# +# Resolution: +# Tries each file of standard localhost.pub & cfkey.pub locations, +# checks if they appear to be a public key, and then join them all together. +# +# Caveats: +# + ## Cfkey.rb ## Facts related to cfengine ## diff --git a/lib/facter/architecture.rb b/lib/facter/architecture.rb index e4aaeba5e2..cd606a2034 100644 --- a/lib/facter/architecture.rb +++ b/lib/facter/architecture.rb @@ -1,3 +1,16 @@ +# Fact: architecture +# +# Purpose: +# Return the CPU hardware architecture. +# +# Resolution: +# On OpenBSD, Linux and Debian's kfreebsd, use the hardwaremodel fact. +# Gentoo and Debian call "x86_86" "amd64". +# Gentoo also calls "i386" "x86". +# +# Caveats: +# + Facter.add(:architecture) do confine :kernel => [:linux, :"gnu/kfreebsd"] setcode do diff --git a/lib/facter/domain.rb b/lib/facter/domain.rb index 29bb204d52..2a79754caf 100644 --- a/lib/facter/domain.rb +++ b/lib/facter/domain.rb @@ -1,3 +1,23 @@ +# Fact: domain +# +# Purpose: +# Return the host's primary DNS domain name. +# +# Resolution: +# On UNIX (excluding Darwin), first try and use the hostname fact, +# which uses the hostname system command, and then parse the output +# of that. +# Failing that it tries the dnsdomainname system command. +# Failing that it uses /etc/resolv.conf and takes the domain from that, or as +# a final resort, the search from that. +# Otherwise returns nil. +# +# On Windows uses the win32ole gem and winmgmts to get the DNSDomain value +# from the Win32 networking stack. +# +# Caveats: +# + Facter.add(:domain) do setcode do # Get the domain from various sources; the order of these diff --git a/lib/facter/facterversion.rb b/lib/facter/facterversion.rb index 0b1cfda024..574da999ff 100644 --- a/lib/facter/facterversion.rb +++ b/lib/facter/facterversion.rb @@ -1,3 +1,12 @@ +# Fact: facterversion +# +# Purpose: returns the version of the facter module. +# +# Resolution: Uses the version constant. +# +# Caveats: +# + Facter.add(:facterversion) do setcode { Facter::FACTERVERSION.to_s } end diff --git a/lib/facter/fqdn.rb b/lib/facter/fqdn.rb index 5ebc5f5c0b..090ca63060 100644 --- a/lib/facter/fqdn.rb +++ b/lib/facter/fqdn.rb @@ -1,3 +1,14 @@ +# Fact: fqdn +# +# Purpose: Returns the fully qualified domain name of the host. +# +# Resolution: Simply joins the hostname fact with the domain name fact. +# +# Caveats: No attempt is made to check that the two facts are accurate or that +# the two facts go together. At no point is there any DNS resolution made +# either. +# + Facter.add(:fqdn) do setcode do host = Facter.value(:hostname) diff --git a/lib/facter/hardwareisa.rb b/lib/facter/hardwareisa.rb index 23f7d08872..9d0830e1fb 100644 --- a/lib/facter/hardwareisa.rb +++ b/lib/facter/hardwareisa.rb @@ -1,3 +1,15 @@ +# Fact: hardwareisa +# +# Purpose: +# Returns hardware processor type. +# +# Resolution: +# On Solaris, Linux and the BSDs simply uses the output of "uname -p" +# +# Caveats: +# Some linuxes return unknown to uname -p with relative ease. +# + Facter.add(:hardwareisa) do setcode 'uname -p', '/bin/sh' confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE SLES Debian Ubuntu Gentoo FreeBSD OpenBSD NetBSD OEL OVS GNU/kFreeBSD} diff --git a/lib/facter/hardwaremodel.rb b/lib/facter/hardwaremodel.rb index 6201bddd7b..8f52fefed3 100644 --- a/lib/facter/hardwaremodel.rb +++ b/lib/facter/hardwaremodel.rb @@ -1,3 +1,16 @@ +# Fact: hardwaremodel +# +# Purpose: +# Returns the hardware model of the system. +# +# Resolution: +# Uses purely "uname -m" on all platforms other than AIX and Windows. +# On AIX uses the parsed "modelname" output of "lsattr -El sys0 -a modelname". +# On Windows uses the 'host_cpu' pulled out of Ruby's config. +# +# Caveats: +# + Facter.add(:hardwaremodel) do setcode 'uname -m' end diff --git a/lib/facter/hostname.rb b/lib/facter/hostname.rb index 188efa4cda..0dcd01c69b 100644 --- a/lib/facter/hostname.rb +++ b/lib/facter/hostname.rb @@ -1,3 +1,16 @@ +# Fact: hostname +# +# Purpose: Return the system's short hostname. +# +# Resolution: +# On all system bar Darwin, parses the output of the "hostname" system command +# to everything before the first period. +# On Darwin, uses the system configuration util to get the LocalHostName +# variable. +# +# Caveats: +# + Facter.add(:hostname, :ldapname => "cn") do setcode do hostname = nil diff --git a/lib/facter/id.rb b/lib/facter/id.rb index 1c4228469f..cc0a0a6b65 100644 --- a/lib/facter/id.rb +++ b/lib/facter/id.rb @@ -1,3 +1,16 @@ +# Fact: id +# +# Purpose: Internal fact used to specity the program to return the currently +# running user id. +# +# Resolution: +# On all Unixes bar Solaris, just returns "whoami". +# On Solaris, parses the output of the "id" command to grab the username, as +# Solaris doesn't have the whoami command. +# +# Caveats: +# + Facter.add(:id) do setcode "whoami" end diff --git a/lib/facter/interfaces.rb b/lib/facter/interfaces.rb index 4fbaef1966..04b1c198fa 100644 --- a/lib/facter/interfaces.rb +++ b/lib/facter/interfaces.rb @@ -1,3 +1,12 @@ +# Fact: interfaces +# +# Purpose: +# +# Resolution: +# +# Caveats: +# + # interfaces.rb # Try to get additional Facts about the machine's network interfaces # diff --git a/lib/facter/ipaddress.rb b/lib/facter/ipaddress.rb index d5634087f5..52605042fe 100644 --- a/lib/facter/ipaddress.rb +++ b/lib/facter/ipaddress.rb @@ -1,3 +1,27 @@ +# Fact: ipaddress +# +# Purpose: Return the main IP address for a host. +# +# Resolution: +# On the Unixes does an ifconfig, and returns the first non 127.0.0.0/8 +# subnetted IP it finds. +# On Windows, it attempts to use the socket library and resolve the machine's +# hostname via DNS. +# +# On LDAP based hosts it tries to use either the win32/resolv library to +# resolve the hostname to an IP address, or on Unix, it uses the resolv +# library. +# +# As a fall back for undefined systems, it tries to run the "host" command to +# resolve the machine's hostname using the system DNS. +# +# Caveats: +# DNS resolution relies on working DNS infrastructure and resolvers on the +# host system. +# The ifconfig parsing purely takes the first IP address it finds without any +# checking this is a useful IP address. +# + Facter.add(:ipaddress) do confine :kernel => :linux setcode do diff --git a/lib/facter/ipaddress6.rb b/lib/facter/ipaddress6.rb index 547d636a73..b494b9d63e 100644 --- a/lib/facter/ipaddress6.rb +++ b/lib/facter/ipaddress6.rb @@ -1,17 +1,26 @@ +# Fact: ipaddress6 +# +# Purpose: Returns the "main" IPv6 IP address of a system. +# +# Resolution: +# OS dependant code that parses the output of various networking +# tools and currently not very intelligent. Returns the first +# non-loopback and non-linklocal address found in the ouput unless +# a default route can be mapped to a routeable interface. Guessing +# an interface is currently only possible with BSD type systems +# to many assumptions have to be made on other platforms to make +# this work with the current code. Most code ported or modeled +# after the ipaddress fact for the sake of similar functionality +# and familiar mechanics. +# +# Caveats: +# + # Cody Herriges # # Used the ipaddress fact that is already part of # Facter as a template. -# OS dependant code that parses the output of various networking -# tools and currently not very intelligent. Returns the first -# non-loopback and non-linklocal address found in the ouput unless -# a default route can be mapped to a routeable interface. Guessing -# an interface is currently only possible with BSD type systems -# to many assumptions have to be made on other platforms to make -# this work with the current code. Most code ported or modeled -# after the ipaddress fact for the sake of similar functionality -# and familiar mechanics. Facter.add(:ipaddress6) do confine :kernel => :linux setcode do diff --git a/lib/facter/iphostnumber.rb b/lib/facter/iphostnumber.rb index bc384325fe..cddaadd1e6 100644 --- a/lib/facter/iphostnumber.rb +++ b/lib/facter/iphostnumber.rb @@ -1,3 +1,14 @@ +# Fact: iphostnumber +# +# Purpose: On selected versions of Darwin, returns the host's IP address. +# +# Resolution: +# Uses either the scutil program to get the localhost name, or parses output +# of ifconfig for a MAC address. +# +# Caveats: +# + Facter.add(:iphostnumber) do confine :kernel => :darwin, :kernelrelease => "R6" setcode do diff --git a/lib/facter/kernel.rb b/lib/facter/kernel.rb index 66f21cea2e..f457e01e35 100644 --- a/lib/facter/kernel.rb +++ b/lib/facter/kernel.rb @@ -1,3 +1,14 @@ +# Fact: kernel +# +# Purpose: Returns the operating system's name. +# +# Resolution: +# Uses Ruby's rbconfig to find host_os, if that is a Windows derivative, the +# returns 'windows', otherwise returns "uname -s" verbatim. +# +# Caveats: +# + Facter.add(:kernel) do setcode do require 'rbconfig' diff --git a/lib/facter/kernelmajversion.rb b/lib/facter/kernelmajversion.rb index 32fd7aabbe..84f71d4504 100644 --- a/lib/facter/kernelmajversion.rb +++ b/lib/facter/kernelmajversion.rb @@ -1,3 +1,13 @@ +# Fact: kernelmajversion +# +# Purpose: Return the operating system's release number's major value. +# +# Resolution: +# Takes the first 2 elements of the kernel version as delimited by periods. +# +# Caveats: +# + Facter.add("kernelmajversion") do setcode do Facter.value(:kernelversion).split('.')[0..1].join('.') diff --git a/lib/facter/kernelrelease.rb b/lib/facter/kernelrelease.rb index 2f6ae45119..a6f9c2c48e 100644 --- a/lib/facter/kernelrelease.rb +++ b/lib/facter/kernelrelease.rb @@ -1,3 +1,16 @@ +# Fact: kernelrelease +# +# Purpose: Return the operating system's release number. +# +# Resolution: +# On AIX returns the output from the "oslevel -s" system command. +# On Windows based systems, uses the win32ole gem to query Windows Management +# for the 'Win32_OperatingSystem' value. +# Otherwise uses the output of "uname -r" system command. +# +# Caveats: +# + Facter.add(:kernelrelease) do setcode 'uname -r' end diff --git a/lib/facter/kernelversion.rb b/lib/facter/kernelversion.rb index cac6c145c7..7e0d95cef1 100644 --- a/lib/facter/kernelversion.rb +++ b/lib/facter/kernelversion.rb @@ -1,3 +1,15 @@ +# Fact: kernelversion +# +# Purpose: Return the operating system's kernel version. +# +# Resolution: +# On Solaris and SunOS based machines, returns the output of "uname -v". +# Otherwise returns the 'kernerlversion' fact up to the first '-'. This may be +# the entire 'kernelversion' fact in many cases. +# +# Caveats: +# + Facter.add("kernelversion") do setcode do Facter['kernelrelease'].value.split('-')[0] diff --git a/lib/facter/lsb.rb b/lib/facter/lsb.rb index bf4b9db706..107419ab11 100644 --- a/lib/facter/lsb.rb +++ b/lib/facter/lsb.rb @@ -1,3 +1,16 @@ +# Fact: lsb +# +# Purpose: Return Linux Standard Base information for the host. +# +# Resolution: +# Uses the lsb_release system command and parses the output with a series of +# regular expressions. +# +# Caveats: +# Only works on Linux (and the kfreebsd derivative) systems. +# Requires the lsb_release program, which may not be installed by default. +# Also is as only as accurate as that program outputs. + ## lsb.rb ## Facts related to Linux Standard Base (LSB) ## diff --git a/lib/facter/lsbmajdistrelease.rb b/lib/facter/lsbmajdistrelease.rb index 34a2f1ea3d..365954166b 100644 --- a/lib/facter/lsbmajdistrelease.rb +++ b/lib/facter/lsbmajdistrelease.rb @@ -1,3 +1,15 @@ +# Fact: lsbmajdistrelease +# +# Purpose: Returns the major version of the operation system version as gleaned +# from the lsbdistrelease fact. +# +# Resolution: +# Parses the lsbdistrelease fact for numbers followed by a period and +# returns those, or just the lsbdistrelease fact if none were found. +# +# Caveats: +# + # lsbmajdistrelease.rb # require 'facter' diff --git a/lib/facter/macaddress.rb b/lib/facter/macaddress.rb index bf23ef606f..54c3c84834 100644 --- a/lib/facter/macaddress.rb +++ b/lib/facter/macaddress.rb @@ -1,3 +1,12 @@ +# Fact: macaddress +# +# Purpose: +# +# Resolution: +# +# Caveats: +# + require 'facter/util/macaddress' Facter.add(:macaddress) do diff --git a/lib/facter/macosx.rb b/lib/facter/macosx.rb index 3841c50045..f5289df35c 100644 --- a/lib/facter/macosx.rb +++ b/lib/facter/macosx.rb @@ -1,3 +1,16 @@ +# Fact: macosx +# +# Purpose: +# Returns a number of Mac specific facts, from system profiler and +# sw_vers. +# +# Resolution: +# Uses util/macosx.rb to do the fact reconnaissance, then outputs them +# preceded by 'sp_' +# +# Caveats: +# + # # macosx.rb # Additional Facts coming from Mac OS X system_profiler command diff --git a/lib/facter/manufacturer.rb b/lib/facter/manufacturer.rb index 4f2df98d86..26aef5fe08 100644 --- a/lib/facter/manufacturer.rb +++ b/lib/facter/manufacturer.rb @@ -1,3 +1,16 @@ +# Fact: manufacturer +# +# Purpose: Return the hardware manufacturer information about the hardware. +# +# Resolution: +# On OpenBSD, queries sysctl values, via a util class. +# On SunOS Sparc, uses prtdiag via a util class. +# On Windows, queries the system via a util class. +# Uses the 'util/manufacturer.rb' for fallback parsing. +# +# Caveats: +# + # manufacturer.rb # Facts related to hardware manufacturer # diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb index 0aa5e882a1..992f2ade4c 100644 --- a/lib/facter/memory.rb +++ b/lib/facter/memory.rb @@ -1,3 +1,20 @@ +# Fact: memory +# +# Purpose: Return information about memory and swap usage. +# +# Resolution: +# On Linuxes, uses Facter::Memory.meminfo_number from +# 'facter/util/memory.rb' +# On AIX, parses "swap -l" for swap values only. +# On OpenBSD, it parses "swapctl -l" for swap values, vmstat via a module for +# free memory, and "sysctl hw.physmem" for maximum memory. +# On Solaris, use "swap -l" for swap values, and parsing prtconf for maximum +# memory, and again, the vmstat module for free memory. +# +# Caveats: +# Some BSD platforms aren't covered at all. AIX is missing memory values. +# + # memory.rb # Additional Facts for memory/swap usage # diff --git a/lib/facter/netmask.rb b/lib/facter/netmask.rb index d6d125e2af..fbb84f59ee 100644 --- a/lib/facter/netmask.rb +++ b/lib/facter/netmask.rb @@ -1,3 +1,12 @@ +# Fact: netmask +# +# Purpose: Returns the netmask for the main interfaces. +# +# Resolution: Uses the facter/util/netmask library routines. +# +# Caveats: +# + # netmask.rb # Find the netmask of the primary ipaddress # Copyright (C) 2007 David Schmitt diff --git a/lib/facter/network.rb b/lib/facter/network.rb index df53ce47db..d4faaac16b 100644 --- a/lib/facter/network.rb +++ b/lib/facter/network.rb @@ -1,3 +1,15 @@ +# Fact: network +# +# Purpose: +# Get IP, network and netmask information for available network +# interfacs. +# +# Resolution: +# Uses 'facter/util/ip' to enumerate interfaces and return their information. +# +# Caveats: +# + require 'facter/util/ip' Facter::Util::IP.get_interfaces.each do |interface| diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index 25eeb00edb..a90c283554 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -1,3 +1,16 @@ +# Fact: operatingsystem +# +# Purpose: Return the name of the operating system. +# +# Resolution: +# If the kernel is a Linux kernel, check for the existence of a selection of +# files in /etc/ to find the specific flavour. +# On SunOS based kernels, return Solaris. +# On systems other than Linux, use the kernel value. +# +# Caveats: +# + require 'facter/lsb' Facter.add(:operatingsystem) do diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index da1b76ab3f..347fe7f05a 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -1,3 +1,20 @@ +# Fact: operatingsystemrelease +# +# Purpose: Returns the release of the operating system. +# +# Resolution: +# On RedHat derivatives, returns their '/etc/-release' file. +# On Debian, returns '/etc/debian_version'. +# On Ubuntu, parses '/etc/issue' for the release version. +# On Suse, derivatives, parses '/etc/SuSE-release' for a selection of version +# information. +# On Slackware, parses '/etc/slackware-version'. +# +# On all remaining systems, returns the 'kernelrelease' value. +# +# Caveats: +# + Facter.add(:operatingsystemrelease) do confine :operatingsystem => %w{CentOS Fedora oel ovs RedHat MeeGo} setcode do diff --git a/lib/facter/path.rb b/lib/facter/path.rb index 03907c0665..71df6cdf53 100644 --- a/lib/facter/path.rb +++ b/lib/facter/path.rb @@ -1,3 +1,12 @@ +# Fact: path +# +# Purpose: Returns the $PATH variable. +# +# Resolution: Gets $PATH from the environment. +# +# Caveats: +# + Facter.add(:path) do setcode do ENV['PATH'] diff --git a/lib/facter/physicalprocessorcount.rb b/lib/facter/physicalprocessorcount.rb index 8fe643e3ca..9c59614bf9 100644 --- a/lib/facter/physicalprocessorcount.rb +++ b/lib/facter/physicalprocessorcount.rb @@ -1,3 +1,14 @@ +# Fact: physicalprocessorcount +# +# Purpose: Return the number of physical processors. +# +# Resolution: +# On linux, parses the output of '/proc/cpuinfo' for the number of unique +# lines with "physical id" in them. +# +# Caveats: +# + Facter.add("physicalprocessorcount") do confine :kernel => :linux diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb index c71bad413f..ec196b2172 100644 --- a/lib/facter/processor.rb +++ b/lib/facter/processor.rb @@ -1,5 +1,18 @@ +# Fact: processor +# +# Purpose: +# Additional Facts about the machine's CPUs. +# +# Resolution: +# On Linux and kFreeBSD, parse '/proc/cpuinfo' for each processor. +# On AIX, parse the output of 'lsdev' for it's processor section. +# On OpenBSD, use 'uname -p' and the sysctl variable for 'hw.ncpu' for CPU +# count. +# +# Caveats: +# + # processor.rb -# Additional Facts about the machine's CPUs # # Copyright (C) 2006 Mooter Media Ltd # Author: Matthew Palmer diff --git a/lib/facter/ps.rb b/lib/facter/ps.rb index e7bcdae984..ef803fb870 100644 --- a/lib/facter/ps.rb +++ b/lib/facter/ps.rb @@ -1,3 +1,15 @@ +# Fact: ps +# +# Purpose: Internal fact for what to use to list all processes. Used by +# Service{} type in Puppet. +# +# Resolution: +# Assumes "ps -ef" for all operating systems other than BSD derivatives, where +# it uses "ps auxwww" +# +# Caveats: +# + Facter.add(:ps) do setcode do 'ps -ef' end end diff --git a/lib/facter/puppetversion.rb b/lib/facter/puppetversion.rb index d2eb1f3061..01422f03d1 100644 --- a/lib/facter/puppetversion.rb +++ b/lib/facter/puppetversion.rb @@ -1,3 +1,13 @@ +# Fact: puppetversion +# +# Purpose: Return the version of puppet installed. +# +# Resolution: +# Requres puppet via Ruby and returns it's version constant. +# +# Caveats: +# + Facter.add(:puppetversion) do setcode do begin diff --git a/lib/facter/rubysitedir.rb b/lib/facter/rubysitedir.rb index c205322033..9973329237 100644 --- a/lib/facter/rubysitedir.rb +++ b/lib/facter/rubysitedir.rb @@ -1,3 +1,13 @@ +# Fact: rubysitedir +# +# Purpose: Returns Ruby's site library directory. +# +# Resolution: Works out the version to major/minor (1.8, 1.9, etc), then joins +# that with all the $: library paths. +# +# Caveats: +# + Facter.add :rubysitedir do setcode do version = RUBY_VERSION.to_s.sub(/\.\d+$/, '') diff --git a/lib/facter/rubyversion.rb b/lib/facter/rubyversion.rb index 48f5cc8842..e578400ed3 100644 --- a/lib/facter/rubyversion.rb +++ b/lib/facter/rubyversion.rb @@ -1,3 +1,12 @@ +# Fact: rubyversion +# +# Purpose: Returns the version of Ruby facter is running under. +# +# Resolution: Returns RUBY_VERSION. +# +# Caveats: +# + Facter.add(:rubyversion) do setcode { RUBY_VERSION.to_s } end diff --git a/lib/facter/selinux.rb b/lib/facter/selinux.rb index 9fab4276b8..1555da0b3d 100644 --- a/lib/facter/selinux.rb +++ b/lib/facter/selinux.rb @@ -1,3 +1,12 @@ +# Fact: selinux +# +# Purpose: +# +# Resolution: +# +# Caveats: +# + # Fact for SElinux # Written by immerda admin team (admin(at)immerda.ch) diff --git a/lib/facter/ssh.rb b/lib/facter/ssh.rb index 318e9d44c3..beb5692843 100644 --- a/lib/facter/ssh.rb +++ b/lib/facter/ssh.rb @@ -1,3 +1,12 @@ +# Fact: ssh +# +# Purpose: +# +# Resolution: +# +# Caveats: +# + ## ssh.rb ## Facts related to SSH ## diff --git a/lib/facter/timezone.rb b/lib/facter/timezone.rb index 744e06a943..462c9ba67c 100644 --- a/lib/facter/timezone.rb +++ b/lib/facter/timezone.rb @@ -1,3 +1,12 @@ +# Fact: timezone +# +# Purpose: Return the machine's time zone. +# +# Resolution: Uses's Ruby's Time module's Time.new call. +# +# Caveats: +# + Facter.add("timezone") do setcode do Time.new.zone diff --git a/lib/facter/uptime.rb b/lib/facter/uptime.rb index 56a959ba25..7aeeb3986b 100644 --- a/lib/facter/uptime.rb +++ b/lib/facter/uptime.rb @@ -1,3 +1,14 @@ +# Fact: uptime +# +# Purpose: return the system uptime in a human readable format. +# +# Resolution: +# Does basic maths on the "uptime_seconds" fact to return a count of +# days, hours and minutes of uptime +# +# Caveats: +# + require 'facter/util/uptime' Facter.add(:uptime) do diff --git a/lib/facter/uptime_days.rb b/lib/facter/uptime_days.rb index add305c59f..25af966d49 100644 --- a/lib/facter/uptime_days.rb +++ b/lib/facter/uptime_days.rb @@ -1,3 +1,12 @@ +# Fact: uptime_days +# +# Purpose: Return purely number of days of uptime. +# +# Resolution: Divides uptime_hours fact by 24. +# +# Caveats: +# + Facter.add(:uptime_days) do setcode do hours = Facter.value(:uptime_hours) diff --git a/lib/facter/uptime_hours.rb b/lib/facter/uptime_hours.rb index ce691d25fa..d02ac3201b 100644 --- a/lib/facter/uptime_hours.rb +++ b/lib/facter/uptime_hours.rb @@ -1,3 +1,12 @@ +# Fact: uptime_hours +# +# Purpose: Return purely number of hours of uptime. +# +# Resolution: Divides uptime_seconds fact by 3600. +# +# Caveats: +# + Facter.add(:uptime_hours) do setcode do seconds = Facter.value(:uptime_seconds) diff --git a/lib/facter/uptime_seconds.rb b/lib/facter/uptime_seconds.rb index 14bb573a4c..9793e41610 100644 --- a/lib/facter/uptime_seconds.rb +++ b/lib/facter/uptime_seconds.rb @@ -1,3 +1,17 @@ +# Fact: uptime_seconds +# +# Purpose: Return purely number of seconds of uptime. +# +# Resolution: +# Using the 'facter/util/uptime.rb' module, try a verity of methods to acquire +# the uptime on Unix. +# +# On Windows, the module calculates the uptime by the "LastBootupTime" Windows +# management value. +# +# Caveats: +# + require 'facter/util/uptime' Facter.add(:uptime_seconds) do diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 468ab7767a..02802e6440 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -1,3 +1,30 @@ +# Fact: virtual +# +# Purpose: Determine if the system's hardware is real or virtualised. +# +# Resolution: +# Assumes physical unless proven otherwise. +# +# On Darwin, use the macosx util module to acquire the SPDisplaysDataType, +# from that parse it to see if it's VMWare or Parallels pretending to be the +# display. +# +# On Linux, BSD, Solaris and HPUX: +# Much of the logic here is obscured behind util/virtual.rb, which rather +# than document here, which would encourage drift, just refer to it. +# The Xen tests in here rely on /sys and /proc, and check for the presence and +# contents of files in there. +# If after all the other tests, it's still seen as physical, then it tries to +# parse the output of the "lspci", "dmidecode" and "prtdiag" and parses them +# for obvious signs of being under VMWare or Parallels. +# Finally it checks for the existence of vmware-vmx, which would hint it's +# VMWare. +# +# Caveats: +# Virtualbox detection isn't implemented. +# Many checks rely purely on existence of files. +# + require 'facter/util/virtual' Facter.add("virtual") do @@ -108,6 +135,17 @@ end end +# Fact: is_virtual +# +# Purpose: returning true or false for if a machine is virtualised or not. +# +# Resolution: The Xen domain 0 machine is virtualised to a degree, but is generally +# not viewed as being a virtual machine. This checks that the machine is not +# physical nor xen0, if that is the case, it is virtual. +# +# Caveats: +# + Facter.add("is_virtual") do confine :kernel => %w{Linux FreeBSD OpenBSD SunOS HP-UX Darwin GNU/kFreeBSD} diff --git a/lib/facter/vlans.rb b/lib/facter/vlans.rb index d65bdd8741..8c485a43b3 100644 --- a/lib/facter/vlans.rb +++ b/lib/facter/vlans.rb @@ -1,3 +1,13 @@ +# Fact: vlans +# +# Purpose: On Linux, return a list of all the VLANs on the system. +# +# Resolution: On Linux only, checks for and reads /proc/net/vlan/config and +# parses it. +# +# Caveats: +# + require 'facter/util/vlans' Facter.add("vlans") do diff --git a/lib/facter/xendomains.rb b/lib/facter/xendomains.rb index 972ac90df9..5bcde05879 100644 --- a/lib/facter/xendomains.rb +++ b/lib/facter/xendomains.rb @@ -1,3 +1,14 @@ +# Fact: xendomains +# +# Purpose: Return the list of Xen domains on the Dom0. +# +# Resolution: +# On a Xen Dom0 host, return a list of Xen domains using the 'util/xendomains' +# library. +# +# Caveats: +# + require 'facter/util/xendomains' Facter.add("xendomains") do From 7c80172fa79105c642f327c59b69f23be2153bb1 Mon Sep 17 00:00:00 2001 From: Ben Hughes Date: Tue, 29 Mar 2011 13:38:44 +1100 Subject: [PATCH 0567/3753] (#6883) Update Facter install.rb to be slightly more informative. Give slightly more information to the user when installing facter and the pre-requisite Ruby libraries are not installed. In the case of OpenSSL, the user can have OpenSSL installed, and not the gem/library, but the error message given in no way hints to this. --- install.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install.rb b/install.rb index eb91e7c9fc..d4793eb479 100755 --- a/install.rb +++ b/install.rb @@ -119,7 +119,7 @@ def check_prereqs begin require pre rescue LoadError - puts "Could not load %s; cannot install" % pre + puts "Could not load #{pre} Ruby library; cannot install" exit -1 end } From 3efa9d717572d7a9a2136c50cd863ff9d4c27372 Mon Sep 17 00:00:00 2001 From: Michael Kincaid Date: Thu, 31 Mar 2011 16:04:57 -0700 Subject: [PATCH 0568/3753] (#3856) Add virtualbox detection via lspci (graphics card), dmidecode, and prtdiag for Solaris and corresponding tests. Darwin case is not handled yet. --- lib/facter/virtual.rb | 5 +++++ spec/unit/virtual_spec.rb | 27 +++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 02802e6440..7c649ba891 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -104,6 +104,9 @@ # --- look for the vmware video card to determine if it is virtual => vmware. # --- 00:0f.0 VGA compatible controller: VMware Inc [VMware SVGA II] PCI Display Adapter result = "vmware" if p =~ /VM[wW]are/ + # --- look for virtualbox video card to determine if it is virtual => virtualbox. + # --- 00:02.0 VGA compatible controller: InnoTek Systemberatung GmbH VirtualBox Graphics Adapter + result = "virtualbox" if p =~ /VirtualBox/ # --- look for pci vendor id used by Parallels video card # --- 01:00.0 VGA compatible controller: Unknown device 1ab8:4005 result = "parallels" if p =~ /1ab8:|[Pp]arallels/ @@ -114,6 +117,7 @@ output.each_line do |pd| result = "parallels" if pd =~ /Parallels/ result = "vmware" if pd =~ /VMware/ + result = "virtualbox" if pd =~ /VirtualBox/ end else output = Facter::Util::Resolution.exec('prtdiag') @@ -121,6 +125,7 @@ output.each_line do |pd| result = "parallels" if pd =~ /Parallels/ result = "vmware" if pd =~ /VMware/ + result = "virtualbox" if pd =~ /VirtualBox/ end end end diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index 7e50847c46..ac0a9bb4db 100644 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -94,6 +94,12 @@ Facter.fact(:virtual).value.should == "vmware" end + it "should be virtualbox with VirtualBox vendor name from lspci" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter::Util::Resolution.stubs(:exec).with('lspci').returns("00:02.0 VGA compatible controller: InnoTek Systemberatung GmbH VirtualBox Graphics Adapter") + Facter.fact(:virtual).value.should == "virtualbox" + end + it "should be vmware with VMWare vendor name from dmidecode" do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) @@ -108,6 +114,13 @@ Facter.fact(:virtual).value.should == "parallels" end + it "should be virtualbox with VirtualBox vendor name from dmidecode" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) + Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("BIOS Information\nVendor: innotek GmbH\nVersion: VirtualBox\n\nSystem Information\nManufacturer: innotek GmbH\nProduct Name: VirtualBox\nFamily: Virtual Machine") + Facter.fact(:virtual).value.should == "virtualbox" + end + it "should be vmware with VMWare vendor name from prtdiag" do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) @@ -123,6 +136,14 @@ Facter::Util::Resolution.stubs(:exec).with('prtdiag').returns("System Configuration: Parallels Virtual Platform") Facter.fact(:virtual).value.should == "parallels" end + + it "should be virtualbox with VirtualBox vendor name from prtdiag" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) + Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns(nil) + Facter::Util::Resolution.stubs(:exec).with('prtdiag').returns("System Configuration: innotek GmbH VirtualBox") + Facter.fact(:virtual).value.should == "virtualbox" + end end end @@ -152,6 +173,12 @@ Facter.fact(:is_virtual).value.should == "true" end + it "should be true when running on virtualbox" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:virtual).stubs(:value).returns("virtualbox") + Facter.fact(:is_virtual).value.should == "true" + end + it "should be true when running on openvz" do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter.fact(:virtual).stubs(:value).returns("openvzve") From cb52b06d4ce0bc2d7b7f6dbfb8ff2a882b63d107 Mon Sep 17 00:00:00 2001 From: Krzysztof Wilczynski Date: Sun, 2 Jan 2011 03:27:59 +0000 Subject: [PATCH 0569/3753] Fix. Using sysfs file system entries to count the number of physical CPUs. Fall-back to "/proc/cpuinfo" included for backward-compatibility with legacy systems. --- lib/facter/physicalprocessorcount.rb | 45 ++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/lib/facter/physicalprocessorcount.rb b/lib/facter/physicalprocessorcount.rb index 9c59614bf9..86d0749850 100644 --- a/lib/facter/physicalprocessorcount.rb +++ b/lib/facter/physicalprocessorcount.rb @@ -3,16 +3,49 @@ # Purpose: Return the number of physical processors. # # Resolution: -# On linux, parses the output of '/proc/cpuinfo' for the number of unique -# lines with "physical id" in them. +# Attempts to use sysfs to get the physical IDs of the processors. Falls +# back to /proc/cpuinfo and "physical id" if sysfs is not available. # # Caveats: # -Facter.add("physicalprocessorcount") do - confine :kernel => :linux +Facter.add('physicalprocessorcount') do + confine :kernel => :linux - setcode do - ppcount = Facter::Util::Resolution.exec('grep "physical id" /proc/cpuinfo|cut -d: -f 2|sort -u|wc -l') + setcode do + sysfs_cpu_directory = '/sys/devices/system/cpu' # This should always be there ... + + if File.exists?(sysfs_cpu_directory) + # + # We assume that the sysfs file system has the correct number of entries + # under the "/sys/device/system/cpu" directory and if so then we process + # content of the file "physical_package_id" located inside the "topology" + # directory in each of the per-CPU sub-directories. + # + # As per Linux Kernel documentation and the file "cputopology.txt" located + # inside the "/usr/src/linux/Documentation" directory we can find following + # short explanation: + # + # (...) + # + # 1) /sys/devices/system/cpu/cpuX/topology/physical_package_id: + # + # physical package id of cpuX. Typically corresponds to a physical + # socket number, but the actual value is architecture and platform + # dependent. + # + # (...) + # + lookup_pattern = "#{sysfs_cpu_directory}/cpu*/topology/physical_package_id" + Dir[lookup_pattern].map { |i| File.read(i).strip }.uniq.size + else + # + # Try to count number of CPUs using the proc file system next ... + # + # We assume that /proc/cpuinfo has what we need and is so then we need + # to make sure that we only count unique entries ... + # + File.read('/proc/cpuinfo').scan(/physical.+:\s(\d+)/).uniq.size end + end end From 0c4a98b2271ed5d84f2aa8783398f08502b578ee Mon Sep 17 00:00:00 2001 From: Krzysztof Wilczynski Date: Sun, 2 Jan 2011 04:24:42 +0000 Subject: [PATCH 0570/3753] Re-factor. Do not use pure-Ruby file reading against "/proc/cpuinfo" and possibly any entry under "/sys" from the sysfs file system. --- lib/facter/physicalprocessorcount.rb | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/facter/physicalprocessorcount.rb b/lib/facter/physicalprocessorcount.rb index 86d0749850..144183c734 100644 --- a/lib/facter/physicalprocessorcount.rb +++ b/lib/facter/physicalprocessorcount.rb @@ -36,8 +36,12 @@ # # (...) # - lookup_pattern = "#{sysfs_cpu_directory}/cpu*/topology/physical_package_id" - Dir[lookup_pattern].map { |i| File.read(i).strip }.uniq.size + lookup_pattern = "#{sysfs_cpu_directory}" + + "/cpu*/topology/physical_package_id" + + Facter::Util::Resolution.exec( + "cat #{lookup_pattern}" + ).scan(/\d+/).uniq.size else # # Try to count number of CPUs using the proc file system next ... @@ -45,7 +49,9 @@ # We assume that /proc/cpuinfo has what we need and is so then we need # to make sure that we only count unique entries ... # - File.read('/proc/cpuinfo').scan(/physical.+:\s(\d+)/).uniq.size + Facter::Util::Resolution.exec( + "grep 'physical.\\+:' /proc/cpuinfo" + ).scan(/\d+/).uniq.size end end end From c2ff8330f4d705ded3f251e170df4bd973af3d43 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 15 Mar 2011 17:29:19 -0700 Subject: [PATCH 0571/3753] (#5135) Refactored physicalprocessorcount - Refactored code to make testing simpler --- lib/facter/physicalprocessorcount.rb | 14 +++++---- spec/unit/physicalprocessorcount_spec.rb | 40 ++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 6 deletions(-) create mode 100644 spec/unit/physicalprocessorcount_spec.rb diff --git a/lib/facter/physicalprocessorcount.rb b/lib/facter/physicalprocessorcount.rb index 144183c734..cd5777c69b 100644 --- a/lib/facter/physicalprocessorcount.rb +++ b/lib/facter/physicalprocessorcount.rb @@ -39,9 +39,11 @@ lookup_pattern = "#{sysfs_cpu_directory}" + "/cpu*/topology/physical_package_id" - Facter::Util::Resolution.exec( - "cat #{lookup_pattern}" - ).scan(/\d+/).uniq.size + ids = Dir.glob(lookup_pattern).collect { |f| Facter::Util::Resolution.exec("cat #{f}")} + + ids = ids.join if ids.is_a?(Array) + ids.scan(/\d+/).uniq.size + else # # Try to count number of CPUs using the proc file system next ... @@ -49,9 +51,9 @@ # We assume that /proc/cpuinfo has what we need and is so then we need # to make sure that we only count unique entries ... # - Facter::Util::Resolution.exec( - "grep 'physical.\\+:' /proc/cpuinfo" - ).scan(/\d+/).uniq.size + str = Facter::Util::Resolution.exec("grep 'physical.\\+:' /proc/cpuinfo") + + if not str.nil? then str.scan(/\d+/).uniq.size; end end end end diff --git a/spec/unit/physicalprocessorcount_spec.rb b/spec/unit/physicalprocessorcount_spec.rb new file mode 100644 index 0000000000..260788bed6 --- /dev/null +++ b/spec/unit/physicalprocessorcount_spec.rb @@ -0,0 +1,40 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') + +require 'facter' + +describe "Physical processor count facts" do + before do + Facter.loadfacts + end + before do + Facter.clear + end + it "should return one physical CPU" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + File.stubs(:exists?).with('/sys/devices/system/cpu').returns(true) + Dir.stubs(:glob).with("/sys/devices/system/cpu/cpu*/topology/physical_package_id").returns("/sys/devices/system/cpu/cpu0/topology/physical_package_id") + Facter::Util::Resolution.stubs(:exec).with("cat /sys/devices/system/cpu/cpu0/topology/physical_package_id").returns("0") + + Facter.fact(:physicalprocessorcount).value.should == 1 + end + + it "should return four physical CPUs" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + File.stubs(:exists?).with('/sys/devices/system/cpu').returns(true) + Dir.stubs(:glob).with("/sys/devices/system/cpu/cpu*/topology/physical_package_id").returns(%w{ + /sys/devices/system/cpu/cpu0/topology/physical_package_id + /sys/devices/system/cpu/cpu1/topology/physical_package_id + /sys/devices/system/cpu/cpu2/topology/physical_package_id + /sys/devices/system/cpu/cpu3/topology/physical_package_id + }) + + Facter::Util::Resolution.stubs(:exec).with("cat /sys/devices/system/cpu/cpu0/topology/physical_package_id").returns("0") + Facter::Util::Resolution.stubs(:exec).with("cat /sys/devices/system/cpu/cpu1/topology/physical_package_id").returns("1") + Facter::Util::Resolution.stubs(:exec).with("cat /sys/devices/system/cpu/cpu2/topology/physical_package_id").returns("2") + Facter::Util::Resolution.stubs(:exec).with("cat /sys/devices/system/cpu/cpu3/topology/physical_package_id").returns("3") + + Facter.fact(:physicalprocessorcount).value.should == 4 + end +end From bfc16f6bb9ffbd0064300e647481aab83b7f6212 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 22 Mar 2011 11:20:27 -0700 Subject: [PATCH 0572/3753] (#2714) Added timeout to prtdiag resulution - prtdiag would hang in specific cases, subsequently hanging facter. This should kill prtdiag if it takes excessively long. --- lib/facter/virtual.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 7c649ba891..2801a43004 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -119,8 +119,11 @@ result = "vmware" if pd =~ /VMware/ result = "virtualbox" if pd =~ /VirtualBox/ end - else - output = Facter::Util::Resolution.exec('prtdiag') + elsif Facter.value(:kernel) == 'SunOS' + res = Facter::Util::Resolution.new('prtdiag') + res.timeout = 6 + res.setcode('prtdiag') + output = res.value if not output.nil? output.each_line do |pd| result = "parallels" if pd =~ /Parallels/ From 7f3e89de2d9da14bc7bef33709b79f48434eec6a Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 22 Mar 2011 12:40:16 -0700 Subject: [PATCH 0573/3753] (#2714) Fixed faulty test - Looks like some copy/paste added some linux tests that relied on prtdiag, which isn't possible. Corrected them to check against solaris. --- spec/unit/virtual_spec.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index ac0a9bb4db..d169192427 100644 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -121,8 +121,10 @@ Facter.fact(:virtual).value.should == "virtualbox" end + end + describe "on Solaris" do it "should be vmware with VMWare vendor name from prtdiag" do - Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:kernel).stubs(:value).returns("SunOS") Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns(nil) Facter::Util::Resolution.stubs(:exec).with('prtdiag').returns("System Configuration: VMware, Inc. VMware Virtual Platform") @@ -130,7 +132,7 @@ end it "should be parallels with Parallels vendor name from prtdiag" do - Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:kernel).stubs(:value).returns("SunOS") Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns(nil) Facter::Util::Resolution.stubs(:exec).with('prtdiag').returns("System Configuration: Parallels Virtual Platform") @@ -138,7 +140,7 @@ end it "should be virtualbox with VirtualBox vendor name from prtdiag" do - Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:kernel).stubs(:value).returns("SunOS") Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns(nil) Facter::Util::Resolution.stubs(:exec).with('prtdiag').returns("System Configuration: innotek GmbH VirtualBox") From 7441b32f9e062f9ca73c9d452f0559aa866e0277 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Wed, 6 Apr 2011 03:04:02 +1000 Subject: [PATCH 0574/3753] Partial fix for #6971 - Fix for virtual tests --- spec/unit/virtual_spec.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index d169192427..f69c898b3e 100644 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -73,6 +73,7 @@ before do FileTest.expects(:exists?).with("/usr/lib/vmware/bin/vmware-vmx").returns false + Facter.fact(:operatingsystem).stubs(:value).returns(true) Facter.fact(:architecture).stubs(:value).returns(true) end @@ -127,7 +128,7 @@ Facter.fact(:kernel).stubs(:value).returns("SunOS") Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns(nil) - Facter::Util::Resolution.stubs(:exec).with('prtdiag').returns("System Configuration: VMware, Inc. VMware Virtual Platform") + Facter::Util::Resolution.stubs(:exec).with('prtdiag', '/bin/sh').returns("System Configuration: VMware, Inc. VMware Virtual Platform") Facter.fact(:virtual).value.should == "vmware" end @@ -135,7 +136,7 @@ Facter.fact(:kernel).stubs(:value).returns("SunOS") Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns(nil) - Facter::Util::Resolution.stubs(:exec).with('prtdiag').returns("System Configuration: Parallels Virtual Platform") + Facter::Util::Resolution.stubs(:exec).with('prtdiag', '/bin/sh').returns("System Configuration: Parallels Virtual Platform") Facter.fact(:virtual).value.should == "parallels" end @@ -143,7 +144,7 @@ Facter.fact(:kernel).stubs(:value).returns("SunOS") Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns(nil) - Facter::Util::Resolution.stubs(:exec).with('prtdiag').returns("System Configuration: innotek GmbH VirtualBox") + Facter::Util::Resolution.stubs(:exec).with('prtdiag', '/bin/sh').returns("System Configuration: innotek GmbH VirtualBox") Facter.fact(:virtual).value.should == "virtualbox" end end From 9f4c5c6ac79821700bf4e6beee81f3d865396f4b Mon Sep 17 00:00:00 2001 From: Richard Clamp Date: Fri, 11 Mar 2011 12:23:23 +0000 Subject: [PATCH 0575/3753] (#6740) facter doesn't always respect facts in environment variables On an OSX host: $ facter operatingsystem Darwin $ facter_operatingsystem=Not_Darwin facter operatingsystem Not_Darwin But on a linux/solaris host: $ facter operatingsystem CentOS $ facter_operatingsystem=Not_CentOS facter operatingsystem CentOS As the operatingsystem fact resolution for linux-based kernels has higher precedence than the environment variable as it has more matching confines than the value from the environment variable. This patch adds from_environment to the resolution mechanism, which makes the resolution have an artificially high weight by claiming the length of its confines array is 1 billion. --- lib/facter/util/loader.rb | 1 + lib/facter/util/resolution.rb | 14 +++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/facter/util/loader.rb b/lib/facter/util/loader.rb index 2d2d9e8a0e..b6aa8deb04 100644 --- a/lib/facter/util/loader.rb +++ b/lib/facter/util/loader.rb @@ -90,6 +90,7 @@ def load_env(fact = nil) next if fact and env_name != fact Facter.add($1) do + from_environment setcode { value } end diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 4a99c35236..2ca2447bf6 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -85,17 +85,29 @@ def confine(confines) end end + # Say this resolution came from the environment + def from_environment + @from_environment = true + end + # Create a new resolution mechanism. def initialize(name) @name = name @confines = [] @value = nil @timeout = 0 + @from_environment = false end # Return the number of confines. def length - @confines.length + # If the resolution came from an environment variable + # say we're very very sure about the value of the resolution + if @from_environment + 1_000_000_000 + else + @confines.length + end end # We need this as a getter for 'timeout', because some versions From d56bca8534bd21c046fd19a7fb2f776fe3e100b4 Mon Sep 17 00:00:00 2001 From: Richard Clamp Date: Fri, 18 Mar 2011 00:10:00 +0000 Subject: [PATCH 0576/3753] refactor the mechanism for allowing for resolution ordering to be influenced renames Facter::Util::Resolution#length to weight as a more generic mechanism for allowing resolutions to state their importance --- lib/facter/util/fact.rb | 4 +--- lib/facter/util/loader.rb | 2 +- lib/facter/util/resolution.rb | 17 +++++++---------- spec/unit/util/fact_spec.rb | 22 +++++++++++----------- spec/unit/util/resolution_spec.rb | 22 ++++++++++++++++++++-- 5 files changed, 40 insertions(+), 27 deletions(-) diff --git a/lib/facter/util/fact.rb b/lib/facter/util/fact.rb index e78ed97d32..935b3c17f2 100644 --- a/lib/facter/util/fact.rb +++ b/lib/facter/util/fact.rb @@ -41,9 +41,7 @@ def add(&block) # Immediately sort the resolutions, so that we always have # a sorted list for looking up values. - # We always want to look them up in the order of number of - # confines, so the most restricted resolution always wins. - @resolves.sort! { |a, b| b.length <=> a.length } + @resolves.sort! { |a, b| b.weight <=> a.weight } return resolve end diff --git a/lib/facter/util/loader.rb b/lib/facter/util/loader.rb index b6aa8deb04..a52012c54c 100644 --- a/lib/facter/util/loader.rb +++ b/lib/facter/util/loader.rb @@ -90,7 +90,7 @@ def load_env(fact = nil) next if fact and env_name != fact Facter.add($1) do - from_environment + has_weight 1_000_000 setcode { value } end diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 2ca2447bf6..d82fab2eb1 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -85,9 +85,8 @@ def confine(confines) end end - # Say this resolution came from the environment - def from_environment - @from_environment = true + def has_weight(weight) + @weight = weight end # Create a new resolution mechanism. @@ -96,15 +95,13 @@ def initialize(name) @confines = [] @value = nil @timeout = 0 - @from_environment = false + @weight = nil end - # Return the number of confines. - def length - # If the resolution came from an environment variable - # say we're very very sure about the value of the resolution - if @from_environment - 1_000_000_000 + # Return the importance of this resolution. + def weight + if @weight + @weight else @confines.length end diff --git a/spec/unit/util/fact_spec.rb b/spec/unit/util/fact_spec.rb index db086703ce..523c855297 100755 --- a/spec/unit/util/fact_spec.rb +++ b/spec/unit/util/fact_spec.rb @@ -56,10 +56,10 @@ @fact.add { } end - it "should re-sort the resolutions by length, so the most restricted resolutions are first" do - r1 = stub 'r1', :length => 1 - r2 = stub 'r2', :length => 2 - r3 = stub 'r3', :length => 0 + it "should re-sort the resolutions by weight, so the most restricted resolutions are first" do + r1 = stub 'r1', :weight => 1 + r2 = stub 'r2', :weight => 2 + r3 = stub 'r3', :weight => 0 Facter::Util::Resolution.expects(:new).times(3).returns(r1).returns(r2).returns(r3) @fact.add { } @fact.add { } @@ -83,9 +83,9 @@ end it "should return the first value returned by a resolution" do - r1 = stub 'r1', :length => 2, :value => nil, :suitable? => true - r2 = stub 'r2', :length => 1, :value => "yay", :suitable? => true - r3 = stub 'r3', :length => 0, :value => "foo", :suitable? => true + r1 = stub 'r1', :weight => 2, :value => nil, :suitable? => true + r2 = stub 'r2', :weight => 1, :value => "yay", :suitable? => true + r3 = stub 'r3', :weight => 0, :value => "foo", :suitable? => true Facter::Util::Resolution.expects(:new).times(3).returns(r1).returns(r2).returns(r3) @fact.add { } @fact.add { } @@ -95,8 +95,8 @@ end it "should short-cut returning the value once one is found" do - r1 = stub 'r1', :length => 2, :value => "foo", :suitable? => true - r2 = stub 'r2', :length => 1, :suitable? => true # would fail if 'value' were asked for + r1 = stub 'r1', :weight => 2, :value => "foo", :suitable? => true + r2 = stub 'r2', :weight => 1, :suitable? => true # would fail if 'value' were asked for Facter::Util::Resolution.expects(:new).times(2).returns(r1).returns(r2) @fact.add { } @fact.add { } @@ -105,8 +105,8 @@ end it "should skip unsuitable resolutions" do - r1 = stub 'r1', :length => 2, :suitable? => false # would fail if 'value' were asked for' - r2 = stub 'r2', :length => 1, :value => "yay", :suitable? => true + r1 = stub 'r1', :weight => 2, :suitable? => false # would fail if 'value' were asked for' + r2 = stub 'r2', :weight => 1, :value => "yay", :suitable? => true Facter::Util::Resolution.expects(:new).times(2).returns(r1).returns(r2) @fact.add { } @fact.add { } diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index 581d0e17f1..3e13cdcc28 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -13,6 +13,10 @@ Facter::Util::Resolution.new("yay").name.should == "yay" end + it "should have a method for setting the weight" do + Facter::Util::Resolution.new("yay").should respond_to(:has_weight) + end + it "should have a method for setting the code" do Facter::Util::Resolution.new("yay").should respond_to(:setcode) end @@ -195,11 +199,25 @@ it "should provide a method for returning the number of confines" do @resolve = Facter::Util::Resolution.new("yay") @resolve.confine "one" => "foo", "two" => "fee" - @resolve.length.should == 2 + @resolve.weight.should == 2 end it "should return 0 confines when no confines have been added" do - Facter::Util::Resolution.new("yay").length.should == 0 + Facter::Util::Resolution.new("yay").weight.should == 0 + end + + it "should provide a way to set the weight" do + @resolve = Facter::Util::Resolution.new("yay") + @resolve.has_weight(45) + @resolve.weight.should == 45 + end + + it "should allow the weight to override the number of confines" do + @resolve = Facter::Util::Resolution.new("yay") + @resolve.confine "one" => "foo", "two" => "fee" + @resolve.weight.should == 2 + @resolve.has_weight(45) + @resolve.weight.should == 45 end it "should have a method for determining if it is suitable" do From bfa038de95ef3642dd0c66ad7e662b5b0f189555 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Wed, 6 Apr 2011 03:21:50 +1000 Subject: [PATCH 0577/3753] Fixed #6974 - Moved to Apache 2.0 license This is in line with our proposed plan to change from the GPL license to Apache 2.0. Please see this link for further explanation: https://groups.google.com/d/topic/puppet-users/NuspYhMpE5o/discussion --- COPYING | 339 -------------------------------------- LICENSE | 24 ++- README | 8 - README.md | 24 +++ README.rst | 26 --- TODO | 4 - bin/facter | 4 +- conf/redhat/facter.spec | 2 +- lib/facter.rb | 25 ++- lib/facter/Cfkey.rb | 11 -- lib/facter/lsb.rb | 12 -- lib/facter/macosx.rb | 11 -- lib/facter/ssh.rb | 11 -- lib/facter/util/macosx.rb | 11 -- lib/facter/util/memory.rb | 11 -- 15 files changed, 49 insertions(+), 474 deletions(-) delete mode 100644 COPYING delete mode 100644 README create mode 100644 README.md delete mode 100644 README.rst delete mode 100644 TODO diff --git a/COPYING b/COPYING deleted file mode 100644 index d511905c16..0000000000 --- a/COPYING +++ /dev/null @@ -1,339 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. diff --git a/LICENSE b/LICENSE index 308f4e000d..9cd62ce4ad 100644 --- a/LICENSE +++ b/LICENSE @@ -1,17 +1,15 @@ -Facter - Host Fact Detection and Reporting. Copyright (C) 2005 Reductive Labs LLC +Facter - Host Fact Detection and Reporting -Reductive Labs can be contacted at: info@reductivelabs.com +Copyright 2011 Puppet Labs Inc -This program and entire repository is free software; you can -redistribute it and/or modify it under the terms of the GNU -General Public License as published by the Free Software -Foundation; either version 2 of the License, or any later version. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. +http://www.apache.org/licenses/LICENSE-2.0 -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/README b/README deleted file mode 100644 index 683833f6aa..0000000000 --- a/README +++ /dev/null @@ -1,8 +0,0 @@ -This package is largely meant to be a library for collecting facts about your -system. These facts are mostly strings (i.e., not numbers), and are things -like the output of 'uname', public ssh and cfengine keys, the number of -processors, etc. - -See bin/facter for an example of the interface. - -See http://www.puppetlabs.com/puppet/related-projects/facter for more details. diff --git a/README.md b/README.md new file mode 100644 index 0000000000..e5a54534b6 --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ +Facter +====== + +This package is largely meant to be a library for collecting facts about your +system. These facts are mostly strings (i.e., not numbers), and are things +like the output of `uname`, public ssh and cfengine keys, the number of +processors, etc. + +See `bin/facter` for an example of the interface. + +Running Facter +-------------- + +Run the `facter` binary on the command for a full list of facts supported on your host. + +Adding your own facts +--------------------- + +See the [Adding Facts](http://docs.puppetlabs.com/guides/custom_facts.html) page for details of how to add your own custom facts to Facter. + +Further Information +------------------- + +See http://www.puppetlabs.com/puppet/related-projects/facter for more details. diff --git a/README.rst b/README.rst deleted file mode 100644 index 2b71136431..0000000000 --- a/README.rst +++ /dev/null @@ -1,26 +0,0 @@ -Facter -====== - -This package is largely meant to be a library for collecting facts about your -system. These facts are mostly strings (i.e., not numbers), and are things -like the output of ``uname``, public ssh and cfengine keys, the number of -processors, etc. - -See ``bin/facter`` for an example of the interface. - -Running Facter -++++++++++++++ - -Run the ``facter`` binary on the command for a full list of facts supported on your host. - -Adding your own facts -+++++++++++++++++++++ - -See the `Adding Facts`_ page for details of how to add your own custom facts to Facter. - -Further Information -+++++++++++++++++++ - -See http://www.puppetlabs.com/puppet/related-projects/facter for more details. - -.. _Adding Facts: http://docs.puppetlabs.com/guides/custom_facts.html diff --git a/TODO b/TODO deleted file mode 100644 index 9389e45f67..0000000000 --- a/TODO +++ /dev/null @@ -1,4 +0,0 @@ -More documentation. - -The ability to specify fact names with strings or symbols; right now, only -strings are supported diff --git a/bin/facter b/bin/facter index 622d72e42d..6c5d4ce09c 100755 --- a/bin/facter +++ b/bin/facter @@ -52,8 +52,8 @@ # # = Copyright # -# Copyright (c) 2006 Reductive Labs, LLC -# Licensed under the GNU Public License +# Copyright (c) 2011 Puppet Labs, Inc +# Licensed under the Apache 2.0 license require 'facter/application' diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index 1148fd3edd..99a6505f3e 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -7,7 +7,7 @@ Summary: Ruby module for collecting simple facts about a host operating system Name: facter Version: 1.5.8 Release: 1%{?dist} -License: GPLv2+ +License: Apache 2.0 Group: System Environment/Base URL: http://www.puppetlabs.com/puppet/related-projects/%{name}/ Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}.tar.gz diff --git a/lib/facter.rb b/lib/facter.rb index 63eea375e6..2073b842e4 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -1,21 +1,18 @@ -#-- -# Copyright 2006 Luke Kanies +# Facter - Host Fact Detection and Reporting # -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# Copyright 2011 Puppet Labs Inc # -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at # -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# http://www.apache.org/licenses/LICENSE-2.0 # -#-- +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. module Facter # This is just so the other classes have the constant. diff --git a/lib/facter/Cfkey.rb b/lib/facter/Cfkey.rb index 83533921d5..e54aeb0089 100644 --- a/lib/facter/Cfkey.rb +++ b/lib/facter/Cfkey.rb @@ -12,17 +12,6 @@ ## Cfkey.rb ## Facts related to cfengine ## -## This program is free software; you can redistribute it and/or -## modify it under the terms of the GNU General Public License -## as published by the Free Software Foundation (version 2 of the License) -## This program is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. -## You should have received a copy of the GNU General Public License -## along with this program; if not, write to the Free Software -## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA -## Facter.add(:Cfkey) do setcode do diff --git a/lib/facter/lsb.rb b/lib/facter/lsb.rb index 107419ab11..7beb41d271 100644 --- a/lib/facter/lsb.rb +++ b/lib/facter/lsb.rb @@ -13,18 +13,6 @@ ## lsb.rb ## Facts related to Linux Standard Base (LSB) -## -## This program is free software; you can redistribute it and/or -## modify it under the terms of the GNU General Public License -## as published by the Free Software Foundation (version 2 of the License) -## This program is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. -## You should have received a copy of the GNU General Public License -## along with this program; if not, write to the Free Software -## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA -## { "LSBRelease" => %r{^LSB Version:\t(.*)$}, "LSBDistId" => %r{^Distributor ID:\t(.*)$}, diff --git a/lib/facter/macosx.rb b/lib/facter/macosx.rb index f5289df35c..7c8562d4bd 100644 --- a/lib/facter/macosx.rb +++ b/lib/facter/macosx.rb @@ -18,17 +18,6 @@ # Copyright (C) 2007 Jeff McCune # Author: Jeff McCune # -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation (version 2 of the License) -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA - # Jeff McCune # There's a lot more information coming out of system_profiler -xml # We could add quite a bit more, but I didn't want to overload facter diff --git a/lib/facter/ssh.rb b/lib/facter/ssh.rb index beb5692843..5ae68ed0eb 100644 --- a/lib/facter/ssh.rb +++ b/lib/facter/ssh.rb @@ -10,17 +10,6 @@ ## ssh.rb ## Facts related to SSH ## -## This program is free software; you can redistribute it and/or -## modify it under the terms of the GNU General Public License -## as published by the Free Software Foundation (version 2 of the License) -## This program is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. -## You should have received a copy of the GNU General Public License -## along with this program; if not, write to the Free Software -## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA -## ["/etc/ssh","/usr/local/etc/ssh","/etc","/usr/local/etc"].each do |dir| {"SSHDSAKey" => "ssh_host_dsa_key.pub", "SSHRSAKey" => "ssh_host_rsa_key.pub"}.each do |name,file| diff --git a/lib/facter/util/macosx.rb b/lib/facter/util/macosx.rb index 6754f18e00..cc9b2df269 100644 --- a/lib/facter/util/macosx.rb +++ b/lib/facter/util/macosx.rb @@ -4,17 +4,6 @@ ## Copyright (C) 2007 Jeff McCune ## Author: Jeff McCune ## -## This program is free software; you can redistribute it and/or -## modify it under the terms of the GNU General Public License -## as published by the Free Software Foundation (version 2 of the License) -## This program is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. -## You should have received a copy of the GNU General Public License -## along with this program; if not, write to the Free Software -## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA -## module Facter::Util::Macosx require 'thread' diff --git a/lib/facter/util/memory.rb b/lib/facter/util/memory.rb index d86a4239d4..d49079d8ab 100644 --- a/lib/facter/util/memory.rb +++ b/lib/facter/util/memory.rb @@ -1,17 +1,6 @@ ## memory.rb ## Support module for memory related facts ## -## This program is free software; you can redistribute it and/or -## modify it under the terms of the GNU General Public License -## as published by the Free Software Foundation (version 2 of the License) -## This program is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. -## You should have received a copy of the GNU General Public License -## along with this program; if not, write to the Free Software -## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA -## module Facter::Memory require 'thread' From 53cd946685fe5790dbbe5b2a3964e6d861bbfdde Mon Sep 17 00:00:00 2001 From: Ohad Levy Date: Tue, 22 Mar 2011 23:04:23 +0200 Subject: [PATCH 0578/3753] Ensures that ARP facts are returned only on EC2 hosts ARP facts on large network might lead to inconstant values as we are always using the first ARP entry from the output of the ARP command Signed-off-by: Ohad Levy --- lib/facter/arp.rb | 12 ++++++++---- lib/facter/ec2.rb | 16 +++++----------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/lib/facter/arp.rb b/lib/facter/arp.rb index 5035ad08e6..0a7cf67242 100644 --- a/lib/facter/arp.rb +++ b/lib/facter/arp.rb @@ -3,14 +3,17 @@ Facter.add(:arp) do confine :kernel => :linux setcode do - arp = [] output = Facter::Util::Resolution.exec('arp -a') if not output.nil? + arp = "" output.each_line do |s| - arp.push($1) if s =~ /^\S+\s\S+\s\S+\s(\S+)\s\S+\s\S+\s\S+$/ + if s =~ /^\S+\s\S+\s\S+\s(\S+)\s\S+\s\S+\s\S+$/ + arp = $1 + break # stops on the first match + end end end - arp[0] + EC2_ARP == arp ? arp : nil end end @@ -18,7 +21,8 @@ Facter.add("arp_" + Facter::Util::IP.alphafy(interface)) do confine :kernel => :linux setcode do - Facter::Util::IP.get_arp_value(interface) + arp = Facter::Util::IP.get_arp_value(interface) + EC2_ARP == arp ? arp : nil end end end diff --git a/lib/facter/ec2.rb b/lib/facter/ec2.rb index 29b2a1c291..693e78ee40 100644 --- a/lib/facter/ec2.rb +++ b/lib/facter/ec2.rb @@ -6,9 +6,11 @@ require 'open-uri' require 'socket' -EC2_ADDR = "169.254.169.254" +EC2_ADDR = "169.254.169.254" EC2_METADATA_URL = "http://#{EC2_ADDR}/2008-02-01/meta-data" EC2_USERDATA_URL = "http://#{EC2_ADDR}/2008-02-01/user-data" +EC2_ARP = "fe:ff:ff:ff:ff:ff" +EC2_EUCA_MAC = %r{^[dD]0:0[dD]:} def can_metadata_connect?(addr, port, timeout=2) t = Socket.new(Socket::Constants::AF_INET, Socket::Constants::SOCK_STREAM, 0) @@ -61,19 +63,11 @@ def userdata() end def has_euca_mac? - if Facter.value(:macaddress) =~ /^[dD]0:0[dD]:/ - return true - else - return false - end + !!(Facter.value(:macaddress) =~ EC2_EUCA_MAC) end def has_ec2_arp? - if Facter.value(:arp) == 'fe:ff:ff:ff:ff:ff' - return true - else - return false - end + !!(Facter.value(:arp) == EC2_ARP) end if (has_euca_mac? || has_ec2_arp?) && can_metadata_connect?(EC2_ADDR,80) From 5b10173364a56458e3f2e8a44298722cda9321e7 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 5 Apr 2011 12:18:11 -0700 Subject: [PATCH 0579/3753] (#5135) Fix faulty logic in physicalprocessorcount - Was doing unnessary string manipulation when all that needed to be done was a uniq'd array. - Removed some backwards way of nil checking. --- lib/facter/physicalprocessorcount.rb | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/facter/physicalprocessorcount.rb b/lib/facter/physicalprocessorcount.rb index c6145dc8f6..22c00c1b47 100644 --- a/lib/facter/physicalprocessorcount.rb +++ b/lib/facter/physicalprocessorcount.rb @@ -39,10 +39,7 @@ lookup_pattern = "#{sysfs_cpu_directory}" + "/cpu*/topology/physical_package_id" - ids = Dir.glob(lookup_pattern).collect { |f| Facter::Util::Resolution.exec("cat #{f}")} - - ids = ids.join if ids.is_a?(Array) - ids.scan(/\d+/).uniq.size + Dir.glob(lookup_pattern).collect { |f| Facter::Util::Resolution.exec("cat #{f}")}.uniq.size else # @@ -53,7 +50,7 @@ # str = Facter::Util::Resolution.exec("grep 'physical.\\+:' /proc/cpuinfo") - if not str.nil? then str.scan(/\d+/).uniq.size; end + if str then str.scan(/\d+/).uniq.size; end end end end From acf0bb21c88edb0eacf4ab73402614c721cf2f76 Mon Sep 17 00:00:00 2001 From: Ohad Levy Date: Tue, 22 Mar 2011 23:04:23 +0200 Subject: [PATCH 0580/3753] Ensures that ARP facts are returned only on EC2 hosts ARP facts on large network might lead to inconstant values as we are always using the first ARP entry from the output of the ARP command Signed-off-by: Ohad Levy --- lib/facter/arp.rb | 12 ++++++++---- lib/facter/ec2.rb | 16 +++++----------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/lib/facter/arp.rb b/lib/facter/arp.rb index 5035ad08e6..0a7cf67242 100644 --- a/lib/facter/arp.rb +++ b/lib/facter/arp.rb @@ -3,14 +3,17 @@ Facter.add(:arp) do confine :kernel => :linux setcode do - arp = [] output = Facter::Util::Resolution.exec('arp -a') if not output.nil? + arp = "" output.each_line do |s| - arp.push($1) if s =~ /^\S+\s\S+\s\S+\s(\S+)\s\S+\s\S+\s\S+$/ + if s =~ /^\S+\s\S+\s\S+\s(\S+)\s\S+\s\S+\s\S+$/ + arp = $1 + break # stops on the first match + end end end - arp[0] + EC2_ARP == arp ? arp : nil end end @@ -18,7 +21,8 @@ Facter.add("arp_" + Facter::Util::IP.alphafy(interface)) do confine :kernel => :linux setcode do - Facter::Util::IP.get_arp_value(interface) + arp = Facter::Util::IP.get_arp_value(interface) + EC2_ARP == arp ? arp : nil end end end diff --git a/lib/facter/ec2.rb b/lib/facter/ec2.rb index 29b2a1c291..693e78ee40 100644 --- a/lib/facter/ec2.rb +++ b/lib/facter/ec2.rb @@ -6,9 +6,11 @@ require 'open-uri' require 'socket' -EC2_ADDR = "169.254.169.254" +EC2_ADDR = "169.254.169.254" EC2_METADATA_URL = "http://#{EC2_ADDR}/2008-02-01/meta-data" EC2_USERDATA_URL = "http://#{EC2_ADDR}/2008-02-01/user-data" +EC2_ARP = "fe:ff:ff:ff:ff:ff" +EC2_EUCA_MAC = %r{^[dD]0:0[dD]:} def can_metadata_connect?(addr, port, timeout=2) t = Socket.new(Socket::Constants::AF_INET, Socket::Constants::SOCK_STREAM, 0) @@ -61,19 +63,11 @@ def userdata() end def has_euca_mac? - if Facter.value(:macaddress) =~ /^[dD]0:0[dD]:/ - return true - else - return false - end + !!(Facter.value(:macaddress) =~ EC2_EUCA_MAC) end def has_ec2_arp? - if Facter.value(:arp) == 'fe:ff:ff:ff:ff:ff' - return true - else - return false - end + !!(Facter.value(:arp) == EC2_ARP) end if (has_euca_mac? || has_ec2_arp?) && can_metadata_connect?(EC2_ADDR,80) From 6b97242d5ca2cc28cc11e6f253f76fda90255d7f Mon Sep 17 00:00:00 2001 From: Jacob Helwig Date: Wed, 6 Apr 2011 16:17:57 -0700 Subject: [PATCH 0581/3753] Update CHANGELOG for 1.5.9rc5 --- CHANGELOG | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 0112360306..43382d372f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,7 @@ +1.5.9rc5 +======== +acf0bb2 Ensures that ARP facts are returned only on EC2 hosts + 1.5.9rc4 ======== 09b9f9b (#6795) Update tests to reflect changed exec From 19f96b59081558eefd613770fdf8c5f5665a150b Mon Sep 17 00:00:00 2001 From: Ben Hughes Date: Thu, 7 Apr 2011 11:33:09 +1000 Subject: [PATCH 0582/3753] (#6728) Facter improperly detects openvzve on CloudLinux systems Make the openvz check for more than just the vz directory to be sure it's OpenVZ. Update the OpenVZ fact to be slightly more resilient in it's checking of OpenVZ, so it doesn't clash with CloudLinux's LVE container system. --- lib/facter/util/virtual.rb | 2 +- spec/unit/util/virtual_spec.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/facter/util/virtual.rb b/lib/facter/util/virtual.rb index 4355451c7b..8bdde16bcd 100644 --- a/lib/facter/util/virtual.rb +++ b/lib/facter/util/virtual.rb @@ -1,6 +1,6 @@ module Facter::Util::Virtual def self.openvz? - FileTest.directory?("/proc/vz") + FileTest.directory?("/proc/vz") and FileTest.exists?( '/proc/vz/veinfo' ) end def self.openvz_type diff --git a/spec/unit/util/virtual_spec.rb b/spec/unit/util/virtual_spec.rb index aa2de5af1b..36e1bf1b03 100644 --- a/spec/unit/util/virtual_spec.rb +++ b/spec/unit/util/virtual_spec.rb @@ -9,6 +9,7 @@ end it "should detect openvz" do FileTest.stubs(:directory?).with("/proc/vz").returns(true) + FileTest.stubs(:exists?).with("/proc/vz/veinfo").returns(true) Facter::Util::Virtual.should be_openvz end From a75f0f9bc7ea1e03dc3a19fba8fae61165ab2b05 Mon Sep 17 00:00:00 2001 From: Jacob Helwig Date: Tue, 12 Apr 2011 11:15:06 -0700 Subject: [PATCH 0583/3753] (#7039) Pre-load all facts when requesting a single fact Since multiple facts can be defined in a single file and we have no way of knowing which "additional" facts are defined in which files, we pre-load all facts when we're looking for specific one. Paired-with: Max Martin --- lib/facter/application.rb | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/facter/application.rb b/lib/facter/application.rb index 6b351b1bad..bd681495bd 100644 --- a/lib/facter/application.rb +++ b/lib/facter/application.rb @@ -9,19 +9,21 @@ def self.run(argv) # Accept fact names to return from the command line names = argv - # Create the facts hash that is printed to standard out - if names.empty? - facts = Facter.to_hash - else + # Create the facts hash that is printed to standard out. + # Pre-load all of the facts, since we can have multiple facts + # per file, and since we can't know ahead of time which file a + # fact will be in, we'll need to load every file. + facts = Facter.to_hash + unless names.empty? facts = {} - names.each { |name| + names.each do |name| begin facts[name] = Facter.value(name) rescue => error $stderr.puts "Could not retrieve #{name}: #{error}" exit 10 end - } + end end # Print the facts as YAML and exit From f91c1205e4dc9a78066af8818fdb92f630aad0d9 Mon Sep 17 00:00:00 2001 From: Jeremy Katz Date: Mon, 18 Apr 2011 12:28:11 -0400 Subject: [PATCH 0584/3753] downcase arp output so that the ec2 arp is matched CentOS 5.4 arp gives the arp output as uppercase; downcase it so we're ensured a match --- lib/facter/arp.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/arp.rb b/lib/facter/arp.rb index 0a7cf67242..6269ae011a 100644 --- a/lib/facter/arp.rb +++ b/lib/facter/arp.rb @@ -8,7 +8,7 @@ arp = "" output.each_line do |s| if s =~ /^\S+\s\S+\s\S+\s(\S+)\s\S+\s\S+\s\S+$/ - arp = $1 + arp = $1.downcase break # stops on the first match end end From 21fe2176f6a688da14d8382806787b6072ae1ed1 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 14 Apr 2011 11:27:22 -0700 Subject: [PATCH 0585/3753] (#6612) Changed uptime spec to be endian agnostic The sysctl uptime fixture was little endian, which fails on sparc hardware. Added a big endian fixture and endian detection. --- .../uptime/sysctl_kern_boottime_big_endian | Bin 0 -> 20 bytes ...boottime => sysctl_kern_boottime_little_endian} | Bin spec/unit/util/uptime_spec.rb | 7 ++++++- 3 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 spec/fixtures/uptime/sysctl_kern_boottime_big_endian rename spec/fixtures/uptime/{sysctl_kern_boottime => sysctl_kern_boottime_little_endian} (100%) diff --git a/spec/fixtures/uptime/sysctl_kern_boottime_big_endian b/spec/fixtures/uptime/sysctl_kern_boottime_big_endian new file mode 100644 index 0000000000000000000000000000000000000000..6b7be22e5dc27af04ade9eca196adbe169601bf8 GIT binary patch literal 20 UcmeYXecbH>#0*dnI>$}}07irbb^rhX literal 0 HcmV?d00001 diff --git a/spec/fixtures/uptime/sysctl_kern_boottime b/spec/fixtures/uptime/sysctl_kern_boottime_little_endian similarity index 100% rename from spec/fixtures/uptime/sysctl_kern_boottime rename to spec/fixtures/uptime/sysctl_kern_boottime_little_endian diff --git a/spec/unit/util/uptime_spec.rb b/spec/unit/util/uptime_spec.rb index 8d3980cfa6..98a1a1b673 100755 --- a/spec/unit/util/uptime_spec.rb +++ b/spec/unit/util/uptime_spec.rb @@ -27,7 +27,12 @@ end it "should use 'sysctl kern.boottime'" do - sysctl_output_file = File.join(SPECDIR, 'fixtures', 'uptime', 'sysctl_kern_boottime') # Aug 01 14:13:47 -0700 2010 + if [1].pack("L") == [1].pack("V") # Determine endianness + sysctl_output_filename = 'sysctl_kern_boottime_little_endian' + else + sysctl_output_filename = 'sysctl_kern_boottime_big_endian' + end + sysctl_output_file = File.join(SPECDIR, 'fixtures', 'uptime', sysctl_output_filename) # Aug 01 14:13:47 -0700 2010 Facter::Util::Uptime.stubs(:uptime_sysctl_cmd).returns("cat \"#{sysctl_output_file}\"") Time.stubs(:now).returns Time.parse("Aug 01 15:13:47 -0700 2010") # one hour later Facter::Util::Uptime.get_uptime_seconds_unix.should == 60 * 60 From 69f98dad7969801f8b573bb120a5e0a6ea528176 Mon Sep 17 00:00:00 2001 From: Dominic Maraglia Date: Thu, 21 Apr 2011 10:39:30 -0700 Subject: [PATCH 0586/3753] Add facter test for ticket 7039 --- ...ket_7039_facter_multiple_facts_one_file.rb | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 accecptance/tests/ticket_7039_facter_multiple_facts_one_file.rb diff --git a/accecptance/tests/ticket_7039_facter_multiple_facts_one_file.rb b/accecptance/tests/ticket_7039_facter_multiple_facts_one_file.rb new file mode 100644 index 0000000000..fb78628856 --- /dev/null +++ b/accecptance/tests/ticket_7039_facter_multiple_facts_one_file.rb @@ -0,0 +1,31 @@ +test_name "#7039: Facter having issue handling multiple facts in a single file" + +fact_file= %q{ +Facter.add(:test_fact1) do + setcode do + "test fact 1" + end +end + +Facter.add(:test_fact2) do + setcode do + "test fact 2" + end +end +} + +agent1=agents.first +step "Agent: Create fact file with multiple facts" +create_remote_file(agent1, '/tmp/test_facts.rb', fact_file ) + +step "Agent: Verify test_fact1 from /tmp/test_facts.rb" +on(agent1, "export FACTERLIB=/tmp && facter --puppet test_fact1") do + fail_test "Fact 1 not returned by facter --puppet test_fact1" unless + stdout.include? 'test fact 1' +end + +step "Agent: Verify test_fact2 from /tmp/test_facts.rb" +on(agent1, "export FACTERLIB=/tmp && facter --puppet test_fact2") do + fail_test "Fact 1 not returned by facter --puppet test_fact2" unless + stdout.include? 'test fact 2' +end From 6b1cd16c7ef6ac78ffa85bfb81d193076db3e4c8 Mon Sep 17 00:00:00 2001 From: Jacob Helwig Date: Fri, 22 Apr 2011 13:56:39 -0700 Subject: [PATCH 0587/3753] (#6614) Update ipaddress6 fact to work with Ruby 1.9 Calling #to_s on an Array such as ["foo"] in Ruby 1.9 will result in the string '["foo"]', instead of stringifying the element in the array which would have given the expected result of "foo". Since the element of the array we're dealing with is already a string, we can just grab it out of the array by using #first. Paired-with: Josh Cooper --- lib/facter/ipaddress6.rb | 48 ++++++++++++++-------------------------- 1 file changed, 17 insertions(+), 31 deletions(-) diff --git a/lib/facter/ipaddress6.rb b/lib/facter/ipaddress6.rb index b494b9d63e..db3805bb36 100644 --- a/lib/facter/ipaddress6.rb +++ b/lib/facter/ipaddress6.rb @@ -21,21 +21,26 @@ # Used the ipaddress fact that is already part of # Facter as a template. +def get_address_after_token(output, token, return_first=false) + ip = nil + + output.scan(/#{token} ((?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/).each do |match| + match = match.first + unless match =~ /fe80.*/ or match == "::1" + ip = match + break if return_first + end + end + + ip +end + Facter.add(:ipaddress6) do confine :kernel => :linux setcode do - ip = nil output = Facter::Util::Resolution.exec('/sbin/ifconfig') - output.scan(/inet6 addr: ((?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/).each { |str| - str = str.to_s - unless str =~ /fe80.*/ or str == "::1" - ip = str - end - } - - ip - + get_address_after_token(output, 'inet6 addr:') end end @@ -43,17 +48,8 @@ confine :kernel => %w{SunOS} setcode do output = Facter::Util::Resolution.exec('/usr/sbin/ifconfig -a') - ip = nil - - output.scan(/inet6 ((?>[0-9,a-f,A-F]*\:{0,2})+[0-9,a-f,A-F]{0,4})/).each { |str| - str = str.to_s - unless str =~ /fe80.*/ or str == "::1" - ip = str - end - } - - ip + get_address_after_token(output, 'inet6') end end @@ -61,17 +57,7 @@ confine :kernel => %w{Darwin FreeBSD OpenBSD} setcode do output = Facter::Util::Resolution.exec('/sbin/ifconfig -a') - ip = nil - output.scan(/inet6 ((?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/).each do |str| - str = str.to_s - unless str =~ /fe80.*/ or str == "::1" - ip = str - break - end - end - - ip + get_address_after_token(output, 'inet6', true) end end - From 063582222f83bd713699c86c825ec1e628a57473 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Wed, 4 May 2011 11:02:41 +1000 Subject: [PATCH 0588/3753] Removed inappropriately uncredited Ohai method from ec2 fact --- lib/facter/arp.rb | 4 ++-- lib/facter/ec2.rb | 54 ++++++++++++++--------------------------------- 2 files changed, 18 insertions(+), 40 deletions(-) diff --git a/lib/facter/arp.rb b/lib/facter/arp.rb index 0a7cf67242..85befa44ad 100644 --- a/lib/facter/arp.rb +++ b/lib/facter/arp.rb @@ -13,7 +13,7 @@ end end end - EC2_ARP == arp ? arp : nil + "fe:ff:ff:ff:ff:ff" == arp ? arp : nil end end @@ -22,7 +22,7 @@ confine :kernel => :linux setcode do arp = Facter::Util::IP.get_arp_value(interface) - EC2_ARP == arp ? arp : nil + "fe:ff:ff:ff:ff:ff" == arp ? arp : nil end end end diff --git a/lib/facter/ec2.rb b/lib/facter/ec2.rb index 693e78ee40..c52f76b55c 100644 --- a/lib/facter/ec2.rb +++ b/lib/facter/ec2.rb @@ -4,45 +4,24 @@ # Additional work modelled on Ohai EC2 fact require 'open-uri' -require 'socket' - -EC2_ADDR = "169.254.169.254" -EC2_METADATA_URL = "http://#{EC2_ADDR}/2008-02-01/meta-data" -EC2_USERDATA_URL = "http://#{EC2_ADDR}/2008-02-01/user-data" -EC2_ARP = "fe:ff:ff:ff:ff:ff" -EC2_EUCA_MAC = %r{^[dD]0:0[dD]:} - -def can_metadata_connect?(addr, port, timeout=2) - t = Socket.new(Socket::Constants::AF_INET, Socket::Constants::SOCK_STREAM, 0) - saddr = Socket.pack_sockaddr_in(port, addr) - connected = false - - begin - t.connect_nonblock(saddr) - rescue Errno::EINPROGRESS - r,w,e = IO::select(nil,[t],nil,timeout) - if !w.nil? - connected = true - else - begin - t.connect_nonblock(saddr) - rescue Errno::EISCONN - t.close - connected = true - rescue SystemCallError - end - end - rescue SystemCallError - end - connected +require 'timeout' + +def can_connect?(wait_sec=2) + url = "http://169.254.169.254/" + Timeout::timeout(wait_sec) {open(url)} + return true + rescue Timeout::Error + return false + rescue + return false end def metadata(id = "") - open("#{EC2_METADATA_URL}/#{id||=''}").read. + open("http://169.254.169.254/2008-02-01/meta-data/#{id||=''}").read. split("\n").each do |o| key = "#{id}#{o.gsub(/\=.*$/, '/')}" if key[-1..-1] != '/' - value = open("#{EC2_METADATA_URL}/#{key}").read. + value = open("http://169.254.169.254/2008-02-01/meta-data/#{key}").read. split("\n") value = value.size>1 ? value : value.first symbol = "ec2_#{key.gsub(/\-|\//, '_')}".to_sym @@ -54,23 +33,22 @@ def metadata(id = "") end def userdata() - # assumes the only expected error is the 404 if there's no user-data begin - value = OpenURI.open_uri("#{EC2_USERDATA_URL}/").read.split + value = OpenURI.open_uri("http://169.254.169.254/2008-02-01/user-data/").read.split Facter.add(:ec2_userdata) { setcode { value } } rescue OpenURI::HTTPError end end def has_euca_mac? - !!(Facter.value(:macaddress) =~ EC2_EUCA_MAC) + !!(Facter.value(:macaddress) =~ %r{^[dD]0:0[dD]:}) end def has_ec2_arp? - !!(Facter.value(:arp) == EC2_ARP) + !!(Facter.value(:arp) == "fe:ff:ff:ff:ff:ff") end -if (has_euca_mac? || has_ec2_arp?) && can_metadata_connect?(EC2_ADDR,80) +if (has_euca_mac? || has_ec2_arp?) && can_connect? metadata userdata else From cc67a0148b97e315572cdb905476df1224a78dd5 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Wed, 4 May 2011 11:02:41 +1000 Subject: [PATCH 0589/3753] Removed inappropriately uncredited Ohai method from ec2 fact --- lib/facter/arp.rb | 4 ++-- lib/facter/ec2.rb | 54 ++++++++++++++--------------------------------- 2 files changed, 18 insertions(+), 40 deletions(-) diff --git a/lib/facter/arp.rb b/lib/facter/arp.rb index 6269ae011a..383fb48634 100644 --- a/lib/facter/arp.rb +++ b/lib/facter/arp.rb @@ -13,7 +13,7 @@ end end end - EC2_ARP == arp ? arp : nil + "fe:ff:ff:ff:ff:ff" == arp ? arp : nil end end @@ -22,7 +22,7 @@ confine :kernel => :linux setcode do arp = Facter::Util::IP.get_arp_value(interface) - EC2_ARP == arp ? arp : nil + "fe:ff:ff:ff:ff:ff" == arp ? arp : nil end end end diff --git a/lib/facter/ec2.rb b/lib/facter/ec2.rb index 693e78ee40..c52f76b55c 100644 --- a/lib/facter/ec2.rb +++ b/lib/facter/ec2.rb @@ -4,45 +4,24 @@ # Additional work modelled on Ohai EC2 fact require 'open-uri' -require 'socket' - -EC2_ADDR = "169.254.169.254" -EC2_METADATA_URL = "http://#{EC2_ADDR}/2008-02-01/meta-data" -EC2_USERDATA_URL = "http://#{EC2_ADDR}/2008-02-01/user-data" -EC2_ARP = "fe:ff:ff:ff:ff:ff" -EC2_EUCA_MAC = %r{^[dD]0:0[dD]:} - -def can_metadata_connect?(addr, port, timeout=2) - t = Socket.new(Socket::Constants::AF_INET, Socket::Constants::SOCK_STREAM, 0) - saddr = Socket.pack_sockaddr_in(port, addr) - connected = false - - begin - t.connect_nonblock(saddr) - rescue Errno::EINPROGRESS - r,w,e = IO::select(nil,[t],nil,timeout) - if !w.nil? - connected = true - else - begin - t.connect_nonblock(saddr) - rescue Errno::EISCONN - t.close - connected = true - rescue SystemCallError - end - end - rescue SystemCallError - end - connected +require 'timeout' + +def can_connect?(wait_sec=2) + url = "http://169.254.169.254/" + Timeout::timeout(wait_sec) {open(url)} + return true + rescue Timeout::Error + return false + rescue + return false end def metadata(id = "") - open("#{EC2_METADATA_URL}/#{id||=''}").read. + open("http://169.254.169.254/2008-02-01/meta-data/#{id||=''}").read. split("\n").each do |o| key = "#{id}#{o.gsub(/\=.*$/, '/')}" if key[-1..-1] != '/' - value = open("#{EC2_METADATA_URL}/#{key}").read. + value = open("http://169.254.169.254/2008-02-01/meta-data/#{key}").read. split("\n") value = value.size>1 ? value : value.first symbol = "ec2_#{key.gsub(/\-|\//, '_')}".to_sym @@ -54,23 +33,22 @@ def metadata(id = "") end def userdata() - # assumes the only expected error is the 404 if there's no user-data begin - value = OpenURI.open_uri("#{EC2_USERDATA_URL}/").read.split + value = OpenURI.open_uri("http://169.254.169.254/2008-02-01/user-data/").read.split Facter.add(:ec2_userdata) { setcode { value } } rescue OpenURI::HTTPError end end def has_euca_mac? - !!(Facter.value(:macaddress) =~ EC2_EUCA_MAC) + !!(Facter.value(:macaddress) =~ %r{^[dD]0:0[dD]:}) end def has_ec2_arp? - !!(Facter.value(:arp) == EC2_ARP) + !!(Facter.value(:arp) == "fe:ff:ff:ff:ff:ff") end -if (has_euca_mac? || has_ec2_arp?) && can_metadata_connect?(EC2_ADDR,80) +if (has_euca_mac? || has_ec2_arp?) && can_connect? metadata userdata else From 4de8b20a3d3a6e8e3cc25f8900887bfb68e496af Mon Sep 17 00:00:00 2001 From: Nigel Kersten Date: Mon, 9 May 2011 17:28:32 -0700 Subject: [PATCH 0590/3753] Updated CHANGELOG for 1.5.9rc6 --- CHANGELOG | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 43382d372f..ea3ccb87d7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,10 @@ +1.5.9rc6 +======== +cc67a01 Removed inappropriately uncredited Ohai method from ec2 fact +69f98da Add facter test for ticket 7039 +f91c120 downcase arp output so that the ec2 arp is matched +a75f0f9 (#7039) Pre-load all facts when requesting a single fact + 1.5.9rc5 ======== acf0bb2 Ensures that ARP facts are returned only on EC2 hosts From 63c8b11ea3a992a28c14470251340d2677a0a906 Mon Sep 17 00:00:00 2001 From: Michael Kincaid Date: Thu, 5 May 2011 10:25:55 -0700 Subject: [PATCH 0591/3753] (#4508) Xen HVM domU not detected as virtual --- lib/facter/virtual.rb | 5 +++++ spec/unit/virtual_spec.rb | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index a5839951fe..739d3468c8 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -110,6 +110,9 @@ # --- look for pci vendor id used by Parallels video card # --- 01:00.0 VGA compatible controller: Unknown device 1ab8:4005 result = "parallels" if p =~ /1ab8:|[Pp]arallels/ + # --- look for pci vendor id used by Xen HVM device + # --- 00:03.0 Unassigned class [ff80]: XenSource, Inc. Xen Platform Device (rev 01) + result = "xenhvm" if p =~ /XenSource/ end else output = Facter::Util::Resolution.exec('dmidecode') @@ -118,6 +121,7 @@ result = "parallels" if pd =~ /Parallels/ result = "vmware" if pd =~ /VMware/ result = "virtualbox" if pd =~ /VirtualBox/ + result = "xenhvm" if pd =~ /HVM domU/ end elsif Facter.value(:kernel) == 'SunOS' res = Facter::Util::Resolution.new('prtdiag') @@ -129,6 +133,7 @@ result = "parallels" if pd =~ /Parallels/ result = "vmware" if pd =~ /VMware/ result = "virtualbox" if pd =~ /VirtualBox/ + result = "xenhvm" if pd =~ /HVM domU/ end end end diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index f69c898b3e..942ccf1d55 100644 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -108,6 +108,19 @@ Facter.fact(:virtual).value.should == "vmware" end + it "should be xenhvm with Xen HVM vendor name from lspci" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter::Util::Resolution.stubs(:exec).with('lspci').returns("00:03.0 Unassigned class [ff80]: XenSource, Inc. Xen Platform Device (rev 01)") + Facter.fact(:virtual).value.should == "xenhvm" + end + + it "should be xenhvm with Xen HVM vendor name from dmidecode" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) + Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("System Information\nManufacturer: Xen\nProduct Name: HVM domU") + Facter.fact(:virtual).value.should == "xenhvm" + end + it "should be parallels with Parallels vendor name from dmidecode" do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) @@ -164,6 +177,12 @@ Facter.fact(:is_virtual).value.should == "false" end + it "should be true when running on xenhvm" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:virtual).stubs(:value).returns("xenhvm") + Facter.fact(:is_virtual).value.should == "true" + end + it "should be false when running on physical" do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter.fact(:virtual).stubs(:value).returns("physical") From 6fb6ee5e089cfe4adbcc5a726e6c8e533a3184ba Mon Sep 17 00:00:00 2001 From: Michael Kincaid Date: Thu, 12 May 2011 10:22:50 -0700 Subject: [PATCH 0592/3753] (#4869) Implement productname as Darwin hw.model --- lib/facter/manufacturer.rb | 5 +++++ lib/facter/util/manufacturer.rb | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/facter/manufacturer.rb b/lib/facter/manufacturer.rb index 26aef5fe08..98ed29e610 100644 --- a/lib/facter/manufacturer.rb +++ b/lib/facter/manufacturer.rb @@ -25,6 +25,11 @@ 'hw.serialno' => 'serialnumber' } + Facter::Manufacturer.sysctl_find_system_info(mfg_keys) +elsif Facter.value(:kernel) == "Darwin" + mfg_keys = { + 'hw.model' => 'productname' + } Facter::Manufacturer.sysctl_find_system_info(mfg_keys) elsif Facter.value(:kernel) == "SunOS" and Facter.value(:hardwareisa) == "sparc" Facter::Manufacturer.prtdiag_sparc_find_system_info() diff --git a/lib/facter/util/manufacturer.rb b/lib/facter/util/manufacturer.rb index 8e9bde2129..381198023a 100644 --- a/lib/facter/util/manufacturer.rb +++ b/lib/facter/util/manufacturer.rb @@ -53,7 +53,7 @@ def self.dmi_find_system_info(name) def self.sysctl_find_system_info(name) name.each do |sysctlkey,facterkey| Facter.add(facterkey) do - confine :kernel => :openbsd + confine :kernel => [:openbsd, :darwin] setcode do Facter::Util::Resolution.exec("sysctl -n " + sysctlkey) end From e84c05149f47dbe9752d3668046c7e4225e3e89d Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Thu, 19 May 2011 15:41:13 +1000 Subject: [PATCH 0593/3753] Fixed #7307 - Added serial number fact to Solaris --- lib/facter/util/manufacturer.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/facter/util/manufacturer.rb b/lib/facter/util/manufacturer.rb index 8e9bde2129..d3d0014a00 100644 --- a/lib/facter/util/manufacturer.rb +++ b/lib/facter/util/manufacturer.rb @@ -79,6 +79,12 @@ def self.prtdiag_sparc_find_system_info() end end end + + Facter.add('serialnumber') do + setcode do + Facter::Util::Resolution.exec("/usr/sbin/sneep") + end + end end def self.win32_find_system_info(name) From 024f7c99543e58ec00bb8f755a1d89d01fd5174c Mon Sep 17 00:00:00 2001 From: Jacob Helwig Date: Fri, 20 May 2011 11:10:56 -0700 Subject: [PATCH 0594/3753] Update CHANGELOG for 1.5.9 --- CHANGELOG | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index ea3ccb87d7..cbb40bc1af 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,43 +1,34 @@ -1.5.9rc6 -======== +1.5.9 +===== +4de8b20 Updated CHANGELOG for 1.5.9rc6 cc67a01 Removed inappropriately uncredited Ohai method from ec2 fact 69f98da Add facter test for ticket 7039 f91c120 downcase arp output so that the ec2 arp is matched a75f0f9 (#7039) Pre-load all facts when requesting a single fact - -1.5.9rc5 -======== +6b97242 Update CHANGELOG for 1.5.9rc5 acf0bb2 Ensures that ARP facts are returned only on EC2 hosts - -1.5.9rc4 -======== +76f544b Updated CHANGELOG for 1.5.9rc4 09b9f9b (#6795) Update tests to reflect changed exec - -1.5.9rc3 -======== +3db1cd0 Updated CHANGELOG for 1.5.9rc3 def3322 (#6795) xendomains: Ignore error output from xm list f39d487 (#6763) Use Facter::Util::Resolution.exec for arp 3eb9410 arp: Cleanup indendation - -1.5.9rc2 -======== +50b9b3f Updated CHANGELOG for 1.5.9rc2 2fb8316 Clean up indentation, and alignment in macaddress_spec.rb 3f0a340 (#6716) fix facter issues on OSX with ipv6 in macaddress.rb. +43f82ef Update CHANGELOG for 1.5.9rc1 d62e079 Fixed #2346 - A much cleverer EC2 fact 0411d2e Fixed #2346 - Part 1: Added arp fact for Linux 5b6f4fa Discussion on ec2 facts - #2346 e917e1a Fixed #3087 - Identify VMWare d0f0f63 (#6327) Memory facts should be available on Mac Darwin -2e48e18 Fixed #6695 - Updated id fact for Darwin et al - -1.5.9rc1 -======== 458a22d Incremented release to 1.5.9 4eb64fe Fixed #6719 Typo ffd80ac (#5011) Adds swap statistics for OSX 1207765 (#6719) Restricts virtualization types for zones 8d71db3 Fixed #6616 - Stubbing in VMware tests on Linux aa959df Remove Solaris from the list of confined systems. It won't get the original lsb facts, and it's nonsensical too. +2e48e18 Fixed #6695 - Updated id fact for Darwin et al d718af4 Fix #6679 - Added Scientific Linux to operatingsystem fact dea6f78 Further fix to #5485 - SELinux facts 6d6d8da (#2721) Merged patch from Brane GraAnar From 8002c240dcd42d3fe0e70b99859c76067f298cef Mon Sep 17 00:00:00 2001 From: Matt Robinson Date: Fri, 20 May 2011 11:21:07 -0700 Subject: [PATCH 0595/3753] (#7507) Fix 1.9.2 test failure Dir.glob returns an array, but a test was stubbing it to return a string. In Ruby 1.8.7 if you call enumerable methods (each, collect, etc) on a string, the string is split on \n first. This meant the poor stubbing didn't affect 1.8.7, but 1.9.2 is more strict and won't automatically convert when you call enumerable methods on strings. Paired-with: Josh Cooper --- spec/unit/physicalprocessorcount_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/unit/physicalprocessorcount_spec.rb b/spec/unit/physicalprocessorcount_spec.rb index 260788bed6..e1f7c6015e 100644 --- a/spec/unit/physicalprocessorcount_spec.rb +++ b/spec/unit/physicalprocessorcount_spec.rb @@ -14,7 +14,7 @@ it "should return one physical CPU" do Facter.fact(:kernel).stubs(:value).returns("Linux") File.stubs(:exists?).with('/sys/devices/system/cpu').returns(true) - Dir.stubs(:glob).with("/sys/devices/system/cpu/cpu*/topology/physical_package_id").returns("/sys/devices/system/cpu/cpu0/topology/physical_package_id") + Dir.stubs(:glob).with("/sys/devices/system/cpu/cpu*/topology/physical_package_id").returns(["/sys/devices/system/cpu/cpu0/topology/physical_package_id"]) Facter::Util::Resolution.stubs(:exec).with("cat /sys/devices/system/cpu/cpu0/topology/physical_package_id").returns("0") Facter.fact(:physicalprocessorcount).value.should == 1 From 2255abee7bdb9b6478ca228546e3d275dbac0ec3 Mon Sep 17 00:00:00 2001 From: Nick Lewis Date: Tue, 31 May 2011 13:14:18 -0700 Subject: [PATCH 0596/3753] (#7670) Never fail to find a fact that is present With the previous behavior, any fact which depended on another fact in a file not matching its name would fail to properly load the required fact, resulting in missing, incorrect, or inconsistent values. For instance, the operatingsystem fact depends on the lsbdistid fact found in lsb.rb. The first time the operatingsystem fact is requested, it requires lsb.rb, and so the required fact is loaded first. But if Facter is subsequently cleared and the operatingsystem fact requested again, the require will not occur, and the fact will not be automatically loaded because it doesn't match its filename. Now if a fact is requested and can't be found, we will attempt to load all the facts to find it. Such an approach appears heavy-handed, but in the case where we want a particular fact, this is the only way to make sure we've found it. In the case where we eventually want other facts, we are conveniently eagerly loading them. Paired-With: Jacob Helwig --- lib/facter/util/collection.rb | 6 +++++- spec/spec_helper.rb | 1 + spec/unit/interfaces_spec.rb | 7 ++----- spec/unit/memory_spec.rb | 4 +++- spec/unit/operatingsystem_spec.rb | 2 ++ spec/unit/util/loader_spec.rb | 4 ++++ 6 files changed, 17 insertions(+), 7 deletions(-) diff --git a/lib/facter/util/collection.rb b/lib/facter/util/collection.rb index 3f8e0f8d4c..b3d3a45c57 100644 --- a/lib/facter/util/collection.rb +++ b/lib/facter/util/collection.rb @@ -66,9 +66,13 @@ def each def fact(name) name = canonize(name) + # Try to load the fact if necessary loader.load(name) unless @facts[name] - return @facts[name] + # Try HARDER + loader.load_all unless @facts[name] + + @facts[name] end # Flush all cached values. diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 28e7b72c3b..483d4dc803 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -18,6 +18,7 @@ # Ensure that we don't accidentally cache between test cases. config.before :each do + Facter::Util::Loader.any_instance.stubs(:load_all) Facter.clear end end diff --git a/spec/unit/interfaces_spec.rb b/spec/unit/interfaces_spec.rb index 8b295d67cb..cfe4226398 100755 --- a/spec/unit/interfaces_spec.rb +++ b/spec/unit/interfaces_spec.rb @@ -3,17 +3,14 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') require 'facter' +require 'facter/util/ip' describe "Per Interface IP facts" do - before do - Facter.loadfacts - end - it "should replace the ':' in an interface list with '_'" do # So we look supported Facter.fact(:kernel).stubs(:value).returns("SunOS") - Facter::Util::IP.expects(:get_interfaces).returns %w{eth0:1 eth1:2} + Facter::Util::IP.stubs(:get_interfaces).returns %w{eth0:1 eth1:2} Facter.fact(:interfaces).value.should == %{eth0_1,eth1_2} end end diff --git a/spec/unit/memory_spec.rb b/spec/unit/memory_spec.rb index 5cae8cb29a..fe4ec367a3 100644 --- a/spec/unit/memory_spec.rb +++ b/spec/unit/memory_spec.rb @@ -6,7 +6,9 @@ describe "Memory facts" do before do - Facter.loadfacts + # We need these facts loaded, but they belong to a file with a + # different name, so load the file explicitly. + Facter.collection.loader.load(:memory) end after do diff --git a/spec/unit/operatingsystem_spec.rb b/spec/unit/operatingsystem_spec.rb index 91cd311613..9a7971d6d7 100755 --- a/spec/unit/operatingsystem_spec.rb +++ b/spec/unit/operatingsystem_spec.rb @@ -34,6 +34,7 @@ it "should identify Oracle VM as OVS" do Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.stubs(:value).with(:lsbdistid).returns(nil) FileTest.stubs(:exists?).returns false FileTest.expects(:exists?).with("/etc/ovs-release").returns true @@ -44,6 +45,7 @@ it "should identify VMWare ESX" do Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.stubs(:value).with(:lsbdistid).returns(nil) FileTest.stubs(:exists?).returns false FileTest.expects(:exists?).with("/etc/vmware-release").returns true diff --git a/spec/unit/util/loader_spec.rb b/spec/unit/util/loader_spec.rb index 90530e83b0..1bc909f3df 100755 --- a/spec/unit/util/loader_spec.rb +++ b/spec/unit/util/loader_spec.rb @@ -19,6 +19,10 @@ def load_file(file) describe Facter::Util::Loader do + before :each do + Facter::Util::Loader.any_instance.unstub(:load_all) + end + def with_env(values) old = {} values.each do |var, value| From 926e912cd4eeedacc5833457ed34e57bd06f5b1a Mon Sep 17 00:00:00 2001 From: Nick Lewis Date: Tue, 31 May 2011 13:25:43 -0700 Subject: [PATCH 0597/3753] (#7670) Stop preloading all facts in the application If a requested fact isn't found in the same location as its name, we want to load all of the facts to find it. However, to simplify that, we were previously just preloading all the facts every time. Because requesting a fact now implicitly loads all facts if necessary, we can rely on that, providing results much more quickly in the case where facts do match their filenames. Reviewed-By: Jacob Helwig --- lib/facter/application.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/facter/application.rb b/lib/facter/application.rb index bd681495bd..9b6da1d639 100644 --- a/lib/facter/application.rb +++ b/lib/facter/application.rb @@ -10,10 +10,6 @@ def self.run(argv) names = argv # Create the facts hash that is printed to standard out. - # Pre-load all of the facts, since we can have multiple facts - # per file, and since we can't know ahead of time which file a - # fact will be in, we'll need to load every file. - facts = Facter.to_hash unless names.empty? facts = {} names.each do |name| @@ -26,6 +22,9 @@ def self.run(argv) end end + # Print everything if they didn't ask for specific facts. + facts ||= Facter.to_hash + # Print the facts as YAML and exit if options[:yaml] require 'yaml' From 0c23845c48488832cf224228f93f62714ba4d4ef Mon Sep 17 00:00:00 2001 From: Nick Lewis Date: Tue, 31 May 2011 14:27:16 -0700 Subject: [PATCH 0598/3753] maint: Fix spelling of acceptance directory --- .../tests/ticket_7039_facter_multiple_facts_one_file.rb | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {accecptance => acceptance}/tests/ticket_7039_facter_multiple_facts_one_file.rb (100%) diff --git a/accecptance/tests/ticket_7039_facter_multiple_facts_one_file.rb b/acceptance/tests/ticket_7039_facter_multiple_facts_one_file.rb similarity index 100% rename from accecptance/tests/ticket_7039_facter_multiple_facts_one_file.rb rename to acceptance/tests/ticket_7039_facter_multiple_facts_one_file.rb From 9404a7a3bb8da660e26897d052cdd03c291fb0bb Mon Sep 17 00:00:00 2001 From: Nick Lewis Date: Tue, 31 May 2011 14:59:20 -0700 Subject: [PATCH 0599/3753] (#7670) Add an acceptance test This test runs only on Ubuntu machines and will reproduce the problem with these steps: * load Facter * request the operatingsystem fact * clear Facter * request the operatingsystem fact and verify it is still correct Reviewed-By: Jacob Helwig --- ...ngsystem_detection_after_clear_on_ubuntu.rb | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 acceptance/tests/operatingsystem_detection_after_clear_on_ubuntu.rb diff --git a/acceptance/tests/operatingsystem_detection_after_clear_on_ubuntu.rb b/acceptance/tests/operatingsystem_detection_after_clear_on_ubuntu.rb new file mode 100644 index 0000000000..3c7c4d9b43 --- /dev/null +++ b/acceptance/tests/operatingsystem_detection_after_clear_on_ubuntu.rb @@ -0,0 +1,18 @@ +test_name "#7670: Facter should properly detect operatingsystem on Ubuntu after a clear" + +script_contents = <<-OS_DETECT + require 'facter' + Facter['operatingsystem'].value + Facter.clear + exit Facter['operatingsystem'].value == 'Ubuntu' +OS_DETECT + +script_name = "/tmp/facter_os_detection_test_#{$$}" + +agents.each do |agent| + next unless agent['platform'].include? 'ubuntu' + + create_remote_file(agent, script_name, script_contents) + + on(agent, "ruby #{script_name}") +end From 5a4eeedb4898cf150d541b276008a6537a742af7 Mon Sep 17 00:00:00 2001 From: Michael Stahnke Date: Thu, 16 Jun 2011 12:47:40 -0700 Subject: [PATCH 0600/3753] Updating for 1.6.0rc1 Signed-off-by: Michael Stahnke --- CHANGELOG | 39 +++++++++++++++++++++++++++++++++++++++ lib/facter.rb | 2 +- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index cbb40bc1af..8f937520dd 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,42 @@ +1.6.0rc1 +=== +9404a7a (#7670) Add an acceptance test +0c23845 maint: Fix spelling of acceptance directory +926e912 (#7670) Stop preloading all facts in the application +2255abe (#7670) Never fail to find a fact that is present +8002c24 (#7507) Fix 1.9.2 test failure +0635822 Removed inappropriately uncredited Ohai method from ec2 fact +6b1cd16 (#6614) Update ipaddress6 fact to work with Ruby 1.9 +21fe217 (#6612) Changed uptime spec to be endian agnostic +19f96b5 (#6728) Facter improperly detects openvzve on CloudLinux systems +5b10173 (#5135) Fix faulty logic in physicalprocessorcount +53cd946 Ensures that ARP facts are returned only on EC2 hosts +bfa038d Fixed #6974 - Moved to Apache 2.0 license +d56bca8 refactor the mechanism for allowing for resolution ordering to be influenced +9f4c5c6 (#6740) facter doesn't always respect facts in environment variables +7441b32 Partial fix for #6971 - Fix for virtual tests +7f3e89d (#2714) Fixed faulty test +bfc16f6 (#2714) Added timeout to prtdiag resulution +c2ff833 (#5135) Refactored physicalprocessorcount +0c4a98b Re-factor. Do not use pure-Ruby file reading against "/proc/cpuinfo" and possibly any entry under "/sys" from the sysfs file system. +cb52b06 Fix. Using sysfs file system entries to count the number of physical CPUs. Fall-back to "/proc/cpuinfo" included for backward-compatibility with legacy systems. +3efa9d7 (#3856) Add virtualbox detection via lspci (graphics card), dmidecode, and prtdiag for Solaris and corresponding tests. Darwin case is not handled yet. +7c80172 (#6883) Update Facter install.rb to be slightly more informative. +d31e3f9 (#5394) Document each Facter fact. +af4947c (#6862) Add a default subject for the mail_patches rake task +d6967a0 (#6613) Switch solaris macaddress fact to netstat +e056218 (#6817) Fix for Ruby 1.9 by calling .each_line on a string +861c2b2 maint: cleanup whitespace +f6c9927 (#6719) Corrected faulty logic in bugfix +e42e57c (#3856) Add virtualbox detection via lspci (graphics card), dmidecode, and prtdiag for Solaris and corresponding tests. Darwin case is not handled yet. +0b5b546 (#6883) Update Facter install.rb to be slightly more informative. +7c08270 (#5394) Document each Facter fact. +06eb3f5 (#6883) Update Facter install.rb to be slightly more informative. +1063753 (#6862) Add a default subject for the mail_patches rake task +56b5f10 (#6613) Switch solaris macaddress fact to netstat +fd4f31c (#6817) Fix for Ruby 1.9 by calling .each_line on a string +72996ff maint: cleanup whitespace + 1.5.9 ===== 4de8b20 Updated CHANGELOG for 1.5.9rc6 diff --git a/lib/facter.rb b/lib/facter.rb index 2073b842e4..eed32f8de8 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -24,7 +24,7 @@ module Util; end include Comparable include Enumerable - FACTERVERSION = '1.5.9' + FACTERVERSION = '1.6.0' # = Facter # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. From 23bb324c71eaa1a51ddad87ab5561b00ae9749c5 Mon Sep 17 00:00:00 2001 From: Michael Stahnke Date: Thu, 23 Jun 2011 15:37:52 -0700 Subject: [PATCH 0601/3753] Readying for release of 1.6.0 Signed-off-by: Michael Stahnke --- CHANGELOG | 2 +- conf/redhat/facter.spec | 5 ++++- conf/solaris/pkginfo | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 8f937520dd..d43323e328 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,4 @@ -1.6.0rc1 +1.6.0 === 9404a7a (#7670) Add an acceptance test 0c23845 maint: Fix spelling of acceptance directory diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index 99a6505f3e..e78b655244 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -5,7 +5,7 @@ Summary: Ruby module for collecting simple facts about a host operating system Name: facter -Version: 1.5.8 +Version: 1.6.0 Release: 1%{?dist} License: Apache 2.0 Group: System Environment/Base @@ -52,6 +52,9 @@ rm -rf %{buildroot} %changelog +* Thu Jun 23 2011 Michael Stahnke - 1.6.0-1 +- Update to 1.6.0 + * Sat Aug 28 2010 Todd Zullinger - 1.5.8-1 - Update to 1.5.8 diff --git a/conf/solaris/pkginfo b/conf/solaris/pkginfo index 968a459675..262a436afd 100644 --- a/conf/solaris/pkginfo +++ b/conf/solaris/pkginfo @@ -1,6 +1,6 @@ PKG=CSWfacter NAME=facter - System Fact Gatherer -VERSION=1.3.5 +VERSION=1.6.0 CATEGORY=application VENDOR=http://www.puppetlabs.com/puppet/related-projects/facter HOTLINE=http://puppetlabs.com/cgi-bin/facter.cgi From 08b3f77ea8ad045c87194bef0a292c76eab9bb2b Mon Sep 17 00:00:00 2001 From: Dominic Cleal Date: Sun, 26 Jun 2011 20:23:54 +0100 Subject: [PATCH 0602/3753] (#7854) Add Augeas library version fact Add fact to return the Augeas version from /augeas/version. Requires the ruby-augeas binding. --- lib/facter/augeasversion.rb | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 lib/facter/augeasversion.rb diff --git a/lib/facter/augeasversion.rb b/lib/facter/augeasversion.rb new file mode 100644 index 0000000000..481b46d711 --- /dev/null +++ b/lib/facter/augeasversion.rb @@ -0,0 +1,29 @@ +# Fact: augeasversion +# +# Purpose: Report the version of the Augeas library +# +# Resolution: +# Loads ruby-augeas and reports the value of /augeas/version, the version of +# the underlying Augeas library. +# +# Caveats: +# The library version may not indicate the presence of certain lenses, +# depending on the system packages updated, nor the version of ruby-augeas +# which may affect support for the Puppet Augeas provider. +# Versions prior to 0.3.6 cannot be interrogated for their version. +# + +Facter.add(:augeasversion) do + setcode do + begin + require 'augeas' + aug = Augeas::open('/', nil, Augeas::NO_MODL_AUTOLOAD) + ver = aug.get('/augeas/version') + aug.close + ver + rescue Exception + Facter.debug('ruby-augeas not available') + end + end +end + From 15d0406531d21837c4ddac7868cdf49280186df1 Mon Sep 17 00:00:00 2001 From: Erik Hetzner Date: Wed, 29 Jun 2011 16:35:50 -0700 Subject: [PATCH 0603/3753] use each_line instead of each for strings in ruby 1.9 --- lib/facter/memory.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb index 992f2ade4c..b9edb67e39 100644 --- a/lib/facter/memory.rb +++ b/lib/facter/memory.rb @@ -70,7 +70,7 @@ if Facter.value(:kernel) == "AIX" and Facter.value(:id) == "root" swap = Facter::Util::Resolution.exec('swap -l') swapfree, swaptotal = 0, 0 - swap.each do |dev| + swap.each_line do |dev| if dev =~ /^\/\S+\s.*\s+(\S+)MB\s+(\S+)MB/ swaptotal += $1.to_i swapfree += $2.to_i @@ -95,7 +95,7 @@ if Facter.value(:kernel) == "OpenBSD" swap = Facter::Util::Resolution.exec('swapctl -l | sed 1d') swapfree, swaptotal = 0, 0 - swap.each do |dev| + swap.each_line do |dev| if dev =~ /^\S+\s+(\S+)\s+\S+\s+(\S+)\s+.*$/ swaptotal += $1.to_i swapfree += $2.to_i @@ -167,7 +167,7 @@ if Facter.value(:kernel) == "SunOS" swap = Facter::Util::Resolution.exec('/usr/sbin/swap -l') swapfree, swaptotal = 0, 0 - swap.each do |dev| + swap.each_line do |dev| if dev =~ /^\/\S+\s.*\s+(\d+)\s+(\d+)$/ swaptotal += $1.to_i / 2 swapfree += $2.to_i / 2 @@ -191,7 +191,7 @@ # Total memory size available from prtconf pconf = Facter::Util::Resolution.exec('/usr/sbin/prtconf') phymem = "" - pconf.each do |line| + pconf.each_line do |line| if line =~ /^Memory size:\s+(\d+) Megabytes/ phymem = $1 end From 0356a2a21bd0a5624875960c14295740ae7b1d11 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Thu, 7 Jul 2011 15:24:32 -0700 Subject: [PATCH 0604/3753] Maint: Fix facter install on Windows The facter install.rb script did not consistently check for the windows platform. As a result, the facter batch wrapper scripts were not being installed. Reviewed-by: Jacob Helwig --- install.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/install.rb b/install.rb index d4793eb479..ccfcb87491 100755 --- a/install.rb +++ b/install.rb @@ -58,6 +58,10 @@ $haveman = false end +$LOAD_PATH << File.join(File.dirname(__FILE__), 'lib') +require 'facter' +@operatingsystem = Facter[:operatingsystem].value + PREREQS = %w{openssl xmlrpc/client xmlrpc/server cgi} InstallOptions = OpenStruct.new @@ -126,7 +130,7 @@ def check_prereqs end def is_windows? - RUBY_PLATFORM.to_s.match(/mswin|win32|dos|cygwin|mingw/) + @operatingsystem == 'windows' end ## @@ -387,7 +391,7 @@ def install_binfile(from, op_file, target) end end - if Config::CONFIG["target_os"] =~ /win/io and Config::CONFIG["target_os"] !~ /darwin/io + if is_windows? installed_wrapper = false if File.exists?("#{from}.bat") From 9b7a41d99a77399e76ac9ee498597b4ba1822fce Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Mon, 11 Jul 2011 15:11:43 -0700 Subject: [PATCH 0605/3753] Maint: Deprecate facter resolution interpreter parameter Facter::Util::Resolution used to track the interpreter for the current system, although this interpreter was never used to execute anything. This commit removes the need to specify the interpreter parameter, and warns once if the parameter is specified or accessed via accessors (for backwards compatibility). Paired-with: Jacob Helwig --- lib/facter.rb | 9 +++++++ lib/facter/hardwareisa.rb | 2 +- lib/facter/uniqueid.rb | 2 +- lib/facter/util/resolution.rb | 19 +++++++++++---- spec/unit/facter_spec.rb | 21 ++++++++++++++++ spec/unit/util/resolution_spec.rb | 40 ++++++++++++++++++++----------- spec/unit/virtual_spec.rb | 6 ++--- 7 files changed, 76 insertions(+), 23 deletions(-) diff --git a/lib/facter.rb b/lib/facter.rb index eed32f8de8..772a9007bf 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -46,6 +46,7 @@ module Util; end RESET = "" @@debug = 0 @@timing = 0 + @@messages = {} # module methods @@ -208,6 +209,14 @@ def self.warn(msg) end end + # Warn once. + def self.warnonce(msg) + if msg and not msg.empty? and @@messages[msg].nil? + @@messages[msg] = true + Kernel.warn(msg) + end + end + # Remove them all. def self.reset @collection = nil diff --git a/lib/facter/hardwareisa.rb b/lib/facter/hardwareisa.rb index 9d0830e1fb..44e5557c36 100644 --- a/lib/facter/hardwareisa.rb +++ b/lib/facter/hardwareisa.rb @@ -11,6 +11,6 @@ # Facter.add(:hardwareisa) do - setcode 'uname -p', '/bin/sh' + setcode 'uname -p' confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE SLES Debian Ubuntu Gentoo FreeBSD OpenBSD NetBSD OEL OVS GNU/kFreeBSD} end diff --git a/lib/facter/uniqueid.rb b/lib/facter/uniqueid.rb index aaeaa123d5..90b5fff368 100644 --- a/lib/facter/uniqueid.rb +++ b/lib/facter/uniqueid.rb @@ -1,4 +1,4 @@ Facter.add(:uniqueid) do - setcode 'hostid', '/bin/sh' + setcode 'hostid' confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE SLES Debian Ubuntu Gentoo AIX OEL OVS GNU/kFreeBSD} end diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index d82fab2eb1..1d31d9e1d2 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -32,8 +32,8 @@ def self.have_which # Returns nil if the program can't be found, or if there is a problem # executing the code. # - def self.exec(code, interpreter = INTERPRETER) - raise ArgumentError, "invalid interpreter" unless interpreter == INTERPRETER + def self.exec(code, interpreter = nil) + Facter.warnonce "The interpreter parameter to 'exec' is deprecated and will be removed in a future version." if interpreter # Try to guess whether the specified code can be executed by looking at the # first word. If it cannot be found on the PATH defer on resolving the fact @@ -116,6 +116,7 @@ def limit # Set our code for returning a value. def setcode(string = nil, interp = nil, &block) + Facter.warnonce "The interpreter parameter to 'setcode' is deprecated and will be removed in a future version." if interp if string @code = string @interpreter = interp || INTERPRETER @@ -127,6 +128,16 @@ def setcode(string = nil, interp = nil, &block) end end + def interpreter + Facter.warnonce "The 'Facter::Util::Resolution.interpreter' method is deprecated and will be removed in a future version." + @interpreter + end + + def interpreter=(interp) + Facter.warnonce "The 'Facter::Util::Resolution.interpreter=' method is deprecated and will be removed in a future version." + @interpreter = interp + end + # Is this resolution mechanism suitable on the system in question? def suitable? unless defined? @suitable @@ -143,7 +154,7 @@ def to_s # How we get a value for our resolution mechanism. def value result = nil - return result if @code == nil and @interpreter == nil + return result if @code == nil starttime = Time.now.to_f @@ -152,7 +163,7 @@ def value if @code.is_a?(Proc) result = @code.call() else - result = Facter::Util::Resolution.exec(@code,@interpreter) + result = Facter::Util::Resolution.exec(@code) end end rescue Timeout::Error => detail diff --git a/spec/unit/facter_spec.rb b/spec/unit/facter_spec.rb index e63bc7683b..aea638cda8 100755 --- a/spec/unit/facter_spec.rb +++ b/spec/unit/facter_spec.rb @@ -185,6 +185,27 @@ end end + describe "when warning once" do + it "should only warn once" do + Kernel.stubs(:warnonce) + Kernel.expects(:warn).with('foo').once + Facter.warnonce('foo') + Facter.warnonce('foo') + end + + it "should not warnonce if nil is passed" do + Kernel.stubs(:warn) + Kernel.expects(:warnonce).never + Facter.warnonce(nil) + end + + it "should not warnonce if an empty string is passed" do + Kernel.stubs(:warn) + Kernel.expects(:warnonce).never + Facter.warnonce('') + end + end + describe "when setting debugging mode" do it "should have debugging enabled using 1" do Facter.debugging(1) diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index 3e13cdcc28..fca56d292b 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -34,6 +34,7 @@ end it "should default to nil for interpreter" do + Facter.expects(:warnonce).with("The 'Facter::Util::Resolution.interpreter' method is deprecated and will be removed in a future version.") Facter::Util::Resolution.new("yay").interpreter.should be_nil end @@ -45,13 +46,25 @@ describe "when setting the code" do before do + Facter.stubs(:warnonce) @resolve = Facter::Util::Resolution.new("yay") end - it "should default to the detected interpreter if a string is provided" do - Facter::Util::Resolution::INTERPRETER = "/bin/bar" - @resolve.setcode "foo" - @resolve.interpreter.should == "/bin/bar" + it "should deprecate the interpreter argument to 'setcode'" do + Facter.expects(:warnonce).with("The interpreter parameter to 'setcode' is deprecated and will be removed in a future version.") + @resolve.setcode "foo", "bar" + @resolve.interpreter.should == "bar" + end + + it "should deprecate the interpreter= method" do + Facter.expects(:warnonce).with("The 'Facter::Util::Resolution.interpreter=' method is deprecated and will be removed in a future version.") + @resolve.interpreter = "baz" + @resolve.interpreter.should == "baz" + end + + it "should deprecate the interpreter method" do + Facter.expects(:warnonce).with("The 'Facter::Util::Resolution.interpreter' method is deprecated and will be removed in a future version.") + @resolve.interpreter end it "should set the code to any provided string" do @@ -95,12 +108,11 @@ describe "on windows" do before do Facter::Util::Resolution::WINDOWS = true - Facter::Util::Resolution::INTERPRETER = "cmd.exe" end - - it "should return the result of executing the code with the interpreter" do + + it "should return the result of executing the code" do @resolve.setcode "/bin/foo" - Facter::Util::Resolution.expects(:exec).once.with("/bin/foo", "cmd.exe").returns "yup" + Facter::Util::Resolution.expects(:exec).once.with("/bin/foo").returns "yup" @resolve.value.should == "yup" end @@ -115,12 +127,11 @@ describe "on non-windows systems" do before do Facter::Util::Resolution::WINDOWS = false - Facter::Util::Resolution::INTERPRETER = "/bin/sh" end - - it "should return the result of executing the code with the interpreter" do + + it "should return the result of executing the code" do @resolve.setcode "/bin/foo" - Facter::Util::Resolution.expects(:exec).once.with("/bin/foo", "/bin/sh").returns "yup" + Facter::Util::Resolution.expects(:exec).once.with("/bin/foo").returns "yup" @resolve.value.should == "yup" end @@ -276,8 +287,9 @@ # It's not possible, AFAICT, to mock %x{}, so I can't really test this bit. describe "when executing code" do - it "should fail if any interpreter other than /bin/sh is requested" do - lambda { Facter::Util::Resolution.exec("/something", "/bin/perl") }.should raise_error(ArgumentError) + it "should deprecate the interpreter parameter" do + Facter.expects(:warnonce).with("The interpreter parameter to 'exec' is deprecated and will be removed in a future version.") + Facter::Util::Resolution.exec("/something", "/bin/perl") end it "should execute the binary" do diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index f69c898b3e..08a45692a7 100644 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -128,7 +128,7 @@ Facter.fact(:kernel).stubs(:value).returns("SunOS") Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns(nil) - Facter::Util::Resolution.stubs(:exec).with('prtdiag', '/bin/sh').returns("System Configuration: VMware, Inc. VMware Virtual Platform") + Facter::Util::Resolution.stubs(:exec).with('prtdiag').returns("System Configuration: VMware, Inc. VMware Virtual Platform") Facter.fact(:virtual).value.should == "vmware" end @@ -136,7 +136,7 @@ Facter.fact(:kernel).stubs(:value).returns("SunOS") Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns(nil) - Facter::Util::Resolution.stubs(:exec).with('prtdiag', '/bin/sh').returns("System Configuration: Parallels Virtual Platform") + Facter::Util::Resolution.stubs(:exec).with('prtdiag').returns("System Configuration: Parallels Virtual Platform") Facter.fact(:virtual).value.should == "parallels" end @@ -144,7 +144,7 @@ Facter.fact(:kernel).stubs(:value).returns("SunOS") Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns(nil) - Facter::Util::Resolution.stubs(:exec).with('prtdiag', '/bin/sh').returns("System Configuration: innotek GmbH VirtualBox") + Facter::Util::Resolution.stubs(:exec).with('prtdiag').returns("System Configuration: innotek GmbH VirtualBox") Facter.fact(:virtual).value.should == "virtualbox" end end From c1b631d25c9631f2a81412351a53a47d533f9c9d Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Mon, 11 Jul 2011 15:17:46 -0700 Subject: [PATCH 0606/3753] Maint: Refactor detection of windows platform The logic for detecting windows (including cygwin, dos, mingw, etc) was duplicated in facter kernel and resolution. This commit refactors that logic into Facter::Util::Config and updates the places that use it. Paired-with: Jacob Helwig --- lib/facter/kernel.rb | 6 +++--- lib/facter/util/config.rb | 9 +++++++++ lib/facter/util/resolution.rb | 8 +++----- spec/unit/id_spec.rb | 11 ++++++----- 4 files changed, 21 insertions(+), 13 deletions(-) create mode 100644 lib/facter/util/config.rb diff --git a/lib/facter/kernel.rb b/lib/facter/kernel.rb index f457e01e35..2c925eb61f 100644 --- a/lib/facter/kernel.rb +++ b/lib/facter/kernel.rb @@ -11,9 +11,9 @@ Facter.add(:kernel) do setcode do - require 'rbconfig' - case Config::CONFIG['host_os'] - when /mswin|win32|dos|cygwin|mingw/i + require 'facter/util/config' + + if Facter::Util::Config.is_windows? 'windows' else Facter::Util::Resolution.exec("uname -s") diff --git a/lib/facter/util/config.rb b/lib/facter/util/config.rb new file mode 100644 index 0000000000..1630eadbee --- /dev/null +++ b/lib/facter/util/config.rb @@ -0,0 +1,9 @@ +# A module to return config related data +# +module Facter::Util::Config + require 'rbconfig' + + def self.is_windows? + Config::CONFIG['host_os'] =~ /mswin|win32|dos|mingw|cygwin/i + end +end diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 1d31d9e1d2..4388c8141b 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -4,16 +4,14 @@ # confinements specified must all be true for the resolution to be # suitable. require 'facter/util/confine' +require 'facter/util/config' require 'timeout' -require 'rbconfig' class Facter::Util::Resolution attr_accessor :interpreter, :code, :name, :timeout - WINDOWS = Config::CONFIG['host_os'] =~ /mswin|win32|dos|mingw|cygwin/i - - INTERPRETER = WINDOWS ? 'cmd.exe' : '/bin/sh' + INTERPRETER = Facter::Util::Config.is_windows? ? "cmd.exe" : "/bin/sh" def self.have_which if ! defined?(@have_which) or @have_which.nil? @@ -45,7 +43,7 @@ def self.exec(code, interpreter = nil) # Windows' %x{} throws Errno::ENOENT when the command is not found, so we # can skip the check there. This is good, since builtins cannot be found # elsewhere. - if have_which and !WINDOWS + if have_which and !Facter::Util::Config.is_windows? path = nil binary = code.split.first if code =~ /^\// diff --git a/spec/unit/id_spec.rb b/spec/unit/id_spec.rb index a9e1797d73..aab343d014 100755 --- a/spec/unit/id_spec.rb +++ b/spec/unit/id_spec.rb @@ -9,9 +9,9 @@ kernel.each do |k| describe "with kernel reported as #{k}" do it "should return the current user" do - Facter::Util::Resolution.stubs(:exec).with('uname -s').returns(k) - Facter::Util::Resolution.stubs(:exec).with('lsb_release -a 2>/dev/null').returns('foo') - Facter::Util::Resolution.expects(:exec).once.with('whoami', '/bin/sh').returns 'bar' + Facter.fact(:kernel).stubs(:value).returns(k) + Facter::Util::Config.stubs(:is_windows?).returns(k == 'windows') + Facter::Util::Resolution.expects(:exec).once.with('whoami').returns 'bar' Facter.fact(:id).value.should == 'bar' end @@ -19,9 +19,10 @@ end it "should return the current user on Solaris" do + Facter::Util::Config.stubs(:is_windows?).returns(false) Facter::Util::Resolution.stubs(:exec).with('uname -s').returns('SunOS') - Facter::Util::Resolution.expects(:exec).once.with('/usr/xpg4/bin/id -un', '/bin/sh').returns 'bar' + Facter::Util::Resolution.expects(:exec).once.with('/usr/xpg4/bin/id -un').returns 'bar' Facter.fact(:id).value.should == 'bar' - end + end end From bdd9e3948bbf00a302fee9fddf8505a17e2c494b Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Mon, 11 Jul 2011 15:20:57 -0700 Subject: [PATCH 0607/3753] Maint: Fix tests to run on Windows Fix various tests that failed to run on Windows because of dependencies on unix-style command line utilities. Paired-with: Jacob Helwig --- spec/unit/ipaddress6_spec.rb | 6 ++++++ spec/unit/util/macaddress_spec.rb | 2 +- spec/unit/util/uptime_spec.rb | 8 +++++++- spec/unit/virtual_spec.rb | 1 + 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/spec/unit/ipaddress6_spec.rb b/spec/unit/ipaddress6_spec.rb index d5070230fd..8e0ccff0c1 100755 --- a/spec/unit/ipaddress6_spec.rb +++ b/spec/unit/ipaddress6_spec.rb @@ -10,6 +10,10 @@ def ifconfig_fixture(filename) end describe "IPv6 address fact" do + before do + Facter::Util::Config.stubs(:is_windows?).returns(false) + end + it "should return ipaddress6 information for Darwin" do Facter::Util::Resolution.stubs(:exec).with('uname -s').returns('Darwin') Facter::Util::Resolution.stubs(:exec).with('/sbin/ifconfig -a'). @@ -33,4 +37,6 @@ def ifconfig_fixture(filename) Facter.value(:ipaddress6).should == "2610:10:20:209:203:baff:fe27:a7c" end + + it "should return ipaddress6 information for Windows" end diff --git a/spec/unit/util/macaddress_spec.rb b/spec/unit/util/macaddress_spec.rb index 4764b052e0..8b58394032 100755 --- a/spec/unit/util/macaddress_spec.rb +++ b/spec/unit/util/macaddress_spec.rb @@ -4,7 +4,7 @@ require 'facter/util/macaddress' -describe "Darwin" do +describe "Darwin", :unless => Facter.value(:operatingsystem) == 'windows' do test_cases = [ # version, iface, real macaddress, fallback macaddress ["9.8.0", 'en0', "00:17:f2:06:e4:2e", "00:17:f2:06:e4:2e"], diff --git a/spec/unit/util/uptime_spec.rb b/spec/unit/util/uptime_spec.rb index 98a1a1b673..1c50d3185e 100755 --- a/spec/unit/util/uptime_spec.rb +++ b/spec/unit/util/uptime_spec.rb @@ -6,7 +6,7 @@ describe Facter::Util::Uptime do - describe ".get_uptime_seconds_unix" do + describe ".get_uptime_seconds_unix", :unless => Facter.value(:operatingsystem) == 'windows' do describe "when /proc/uptime is available" do before do uptime_file = File.join(SPECDIR, "fixtures", "uptime", "ubuntu_proc_uptime") @@ -75,4 +75,10 @@ end end end + + describe ".get_uptime_seconds_win", :if => Facter.value(:operatingsystem) == 'windows' do + it "should return a postive value" do + Facter::Util::Uptime.get_uptime_seconds_win.should > 0 + end + end end diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index 08a45692a7..68317af4f8 100644 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -214,6 +214,7 @@ it "should be true when running on S390" do Facter.fact(:architecture).stubs(:value).returns("s390x") + Facter.fact(:kernel).stubs(:value).returns("Linux") Facter.fact(:virtual).stubs(:value).returns("zlinux") Facter.fact(:is_virtual).value.should == "true" end From e3294504807c3c2da3944957bac78a78e9fae3c3 Mon Sep 17 00:00:00 2001 From: John Eikenberry Date: Mon, 18 Jul 2011 16:05:59 -0700 Subject: [PATCH 0608/3753] (#8247) Fixing arp DNS timeout issue. --- lib/facter/arp.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/arp.rb b/lib/facter/arp.rb index 383fb48634..2c8bde5f4b 100644 --- a/lib/facter/arp.rb +++ b/lib/facter/arp.rb @@ -3,7 +3,7 @@ Facter.add(:arp) do confine :kernel => :linux setcode do - output = Facter::Util::Resolution.exec('arp -a') + output = Facter::Util::Resolution.exec('arp -an') if not output.nil? arp = "" output.each_line do |s| From 5d9cc84ad4bf690a4e4f0b6f1d45d95c49efe51f Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Fri, 29 Jul 2011 09:58:32 -0700 Subject: [PATCH 0609/3753] (#8660) Fix destdir option on Windows Specifying the --destdir option failed on windows because the install script attempted to concatenate two absolute paths together. On Unix, this is fine, but on Windows, it fails because the colon (part of the drive specifier) is not a valid filename character. This commit adds a method to join two paths, stripping off the drive specifier, if present. Also added the deprecation warning if the DESTDIR environment variable is specified (to match the puppet install.rb script). Reviewed-by: Cameron Thomas --- install.rb | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/install.rb b/install.rb index ccfcb87491..37154ba969 100755 --- a/install.rb +++ b/install.rb @@ -260,10 +260,11 @@ def prepare_installation # To be deprecated once people move over to using --destdir option if (destdir = ENV['DESTDIR']) - bindir = "#{destdir}#{bindir}" - sbindir = "#{destdir}#{sbindir}" - mandir = "#{destdir}#{mandir}" - sitelibdir = "#{destdir}#{sitelibdir}" + warn "DESTDIR is deprecated. Use --destdir instead." + bindir = join(destdir, bindir) + sbindir = join(destdir, sbindir) + mandir = join(destdir, mandir) + sitelibdir = join(destdir, sitelibdir) FileUtils.makedirs(bindir) FileUtils.makedirs(sbindir) @@ -271,10 +272,10 @@ def prepare_installation FileUtils.makedirs(sitelibdir) # This is the new way forward elsif (destdir = InstallOptions.destdir) - bindir = "#{destdir}#{bindir}" - sbindir = "#{destdir}#{sbindir}" - mandir = "#{destdir}#{mandir}" - sitelibdir = "#{destdir}#{sitelibdir}" + bindir = join(destdir, bindir) + sbindir = join(destdir, sbindir) + mandir = join(destdir, mandir) + sitelibdir = join(destdir, sitelibdir) FileUtils.makedirs(bindir) FileUtils.makedirs(sbindir) @@ -292,6 +293,16 @@ def prepare_installation InstallOptions.man_dir = mandir end +## +# Join two paths. On Windows, dir must be converted to a relative path, +# by stripping the drive letter, but only if the basedir is not empty. +# +def join(basedir, dir) + return "#{basedir}#{dir[2..-1]}" if is_windows? and basedir.length > 0 and dir.length > 2 + + "#{basedir}#{dir}" +end + ## # Build the rdoc documentation. Also, try to build the RI documentation. # From 2ba8e7bb58eb4590ac76b043848359e315605f72 Mon Sep 17 00:00:00 2001 From: Jacob Helwig Date: Tue, 2 Aug 2011 17:34:10 -0700 Subject: [PATCH 0610/3753] Add document outlining preferred contribution methods We have historically had the preferred contribution process on the Redmine wiki, however this is not obvious to people that don't already know it is there. By adding this document to the repository itself, it becomes much easier for new contributors to find what the preferred contribution methods are. By having the preferred contribution method in the repository also means that it becomes a "curated" document, which must go through the same submission/review process that other changes to the repositories go through. Reviewed-by: Nick Fagerlund Reviewed-by: Nick Lewis Signed-off-by: Jacob Helwig --- CONTRIBUTING.md | 299 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 299 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..dd8e89720e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,299 @@ +Checklist (and a short version for the impatient) +================================================= + + * Commits: + + - Make commits of logical units. + + - Check for unnecessary whitespace with "git diff --check" before + committing. + + - Commit using Unix line endings (check the settings around "crlf" in + git-config(1)). + + - Do not check in commented out code or unneeded files. + + - The first line of the commit message should be a short + description (50 characters is the soft limit, excluding ticket + number(s)), and should skip the full stop. + + - If there is an associated Redmine ticket then the first line + should include the ticket number in the form "(#XXXX) Rest of + message". + + - The body should provide a meaningful commit message, which: + + - uses the imperative, present tense: "change", not "changed" or + "changes". + + - includes motivation for the change, and contrasts its + implementation with the previous behavior. + + - Make sure that you have tests for the bug you are fixing, or + feature you are adding. + + - Make sure the test suite passes after your commit (rake spec unit). + + * Submission: + + * Pre-requisites: + + - Make sure you have a [Redmine account](http://projects.puppetlabs.com) + + - Sign the [Contributor License Agreement](https://projects.puppetlabs.com/contributor_licenses/sign) + + * Preferred method: + + - Fork the repository on GitHub. + + - Push your changes to a topic branch in your fork of the + repository. + + - Submit a pull request to the repository in the puppetlabs + organization. + + * Alternate methods: + + - Mail patches to puppet-dev mailing list using `rake mail_patches`, + or `git-format-patch(1)` & `git-send-email(1)`. + + - Attach patches to Redmine ticket. + +The long version +================ + + 0. Decide what to base your work on. + + In general, you should always base your work on the oldest + branch that your change is relevant to. + + - A bug fix should be based on the current stable series. If the + bug is not present in the current stable release, then base it on + `master`. + + - A new feature should be based on `master`. + + - Security fixes should be based on the current maintenance series + (that is, the previous stable series). If the security issue + was not present in the maintenance series, then it should be + based on the current stable series if it was introduced there, + or on `master` if it is not yet present in a stable release. + + The current stable series is 2.7.x, and the current maintenance + series is 2.6.x. + + 1. Make separate commits for logically separate changes. + + Please break your commits down into logically consistent units + which include new or changed tests relevent to the rest of the + change. The goal of doing this is to make the diff easier to + read for whoever is reviewing your code. In general, the easier + your diff is to read, the more likely someone will be happy to + review it and get it into the code base. + + If you're going to refactor a piece of code, please do so as a + separate commit from your feature or bug fix changes. + + We also really appreciate changes that include tests to make + sure the bug isn't re-introduced, and that the feature isn't + accidentally broken. + + Describe the technical detail of the change(s). If your + description starts to get too long, that's a good sign that you + probably need to split up your commit into more finely grained + pieces. + + Commits which plainly describe the the things which help + reviewers check the patch and future developers understand the + code are much more likely to be merged in with a minimum of + bike-shedding or requested changes. Ideally, the commit message + would include information, and be in a form suitable for + inclusion in the release notes for the version of Puppet that + includes them. + + Please also check that you are not introducing any trailing + whitespaces or other "whitespace errors". You can do this by + running "git diff --check" on your changes before you commit. + + 2. Sign the Contributor License Agreement + + Before we can accept your changes, we do need a signed Puppet + Labs Contributor License Agreement (CLA). + + You can access the CLA via the + [Contributor License Agreement link](https://projects.puppetlabs.com/contributor_licenses/sign) + in the top menu bar of our Redmine instance. Once you've signed + the CLA, a badge will show up next to your name on the + [Puppet Project Overview Page](http://projects.puppetlabs.com/projects/puppet?jump=welcome), + and your name will be listed under "Contributor License Signers" + section. + + If you have any questions about the CLA, please feel free to + contact Puppet Labs via email at cla-submissions@puppetlabs.com. + + 3. Sending your patches + + We accept multiple ways of submitting your changes for + inclusion. They are listed below in order of preference. + + Please keep in mind that any method that involves sending email + to the mailing list directly requires you to be subscribed to + the mailing list, and that your first post to the list will be + held in a moderation queue. + + * GitHub Pull Requests + + To submit your changes via a GitHub pull request, we _highly_ + recommend that you have them on a topic branch, instead of + directly on "master" or one of the release, or RC branches. + It makes things much easier to keep track of, especially if + you decide to work on another thing before your first change + is merged in. + + GitHub has some pretty good + [general documentation](http://help.github.com/) on using + their site. They also have documentation on + [creating pull requests](http://help.github.com/send-pull-requests/). + + In general, after pushing your topic branch up to your + repository on GitHub, you'll switch to the branch in the + GitHub UI and click "Pull Request" towards the top of the page + in order to open a pull request. + + You'll want to make sure that you have the appropriate + destination branch in the repository under the puppetlabs + organization. This should be the same branch that you based + your changes off of. + + * Other pull requests + + If you already have a publicly accessible version of the + repository hosted elsewhere, and don't wish to or cannot use + GitHub, you can submit your change by requesting that we pull + the changes from your repository by sending an email to the + puppet-dev Google Groups mailing list. + + `git-request-pull(1)` provides a handy way to generate the text + for the email requesting that we pull your changes (and does + some helpful sanity checks in the process). + + * Mailing patches to the mailing list + + If neither of the previous methods works for you, then you can + also mail the patches inline to the puppet-dev Google Group + using either `rake mail_patches`, or by using + `git-format-patch(1)`, and `git-send-email(1)` directly. + + `rake mail_patches` handles setting the appropriate flags to + `git-format-patch(1)` and `git-send-email(1)` for you, but + doesn't allow adding any commentary between the '---', and the + diffstat in the resulting email. It also requires that you + have created your topic branch in the form + `//`. + + If you decide to use `git-format-patch(1)` and + `git-send-email(1)` directly, please be sure to use the + following flags for `git-format-patch(1)`: -C -M -s -n + --subject-prefix='PATCH/puppet' + + * Attaching patches to Redmine + + As a method of last resort you can also directly attach the + output of `git-format-patch(1)`, or `git-diff(1)` to a Redmine + ticket. + + If you are generating the diff outside of Git, please be sure + to generate a unified diff. + + 4. Update the related Redmine ticket. + + If there's a Redmine ticket associated with the change you + submitted, then you should update the ticket to include the + location of your branch, and change the status to "In Topic + Branch Pending Merge", along with any other commentary you may + wish to make. + +How to track the status of your change after it's been submitted +================================================================ + +Shortly after opening a pull request on GitHub, there should be an +automatic message sent to the puppet-dev Google Groups mailing list +notifying people of this. This notification is used to let the Puppet +development community know about your requested change to give them a +chance to review, test, and comment on the change(s). + +If you submitted your change via manually sending a pull request or +mailing the patches, then we keep track of these using +[patchwork](https://patchwork.puppetlabs.com). When code is merged +into the project it is automatically removed from patchwork, and the +Redmine ticket is manually updated with the commit SHA1. In addition, +the ticket status must be updated by the person who merges the topic +branch to a status of "Merged - Pending Release" + +We do our best to comment on or merge submitted changes within a week. +However, if there hasn't been any commentary on the pull request or +mailed patches, and it hasn't been merged in after a week, then feel +free to ask for an update by replying on the mailing list to the +automatic notification or mailed patches. It probably wasn't +intentional, and probably just slipped through the cracks. + +Additional Resources +==================== + +* [Getting additional help](http://projects.puppetlabs.com/projects/puppet/wiki/Getting_Help) + +* [Writing tests](http://projects.puppetlabs.com/projects/puppet/wiki/Development_Writing_Tests) + +* [Bug tracker (Redmine)](http://projects.puppetlabs.com) + +* [Patchwork](https://patchwork.puppetlabs.com) + +* [Contributor License Agreement](https://projects.puppetlabs.com/contributor_licenses/sign) + +* [General GitHub documentation](http://help.github.com/) + +* [GitHub pull request documentation](http://help.github.com/send-pull-requests/) + +If you have commit access to the repository +=========================================== + +Even if you have commit access to the repository, you'll still need to +go through the process above, and have someone else review and merge +in your changes. The rule is that all changes must be reviewed by a +developer on the project (that didn't write the code) to ensure that +all changes go through a code review process. + +Having someone other than the author of the topic branch recorded as +performing the merge is the record that they performed the code +review. + + * Merging topic branches + + When merging code from a topic branch into the integration branch + (Ex: master, 2.7.x, 1.6.x, etc.), there should always be a merge + commit. You can accomplish this by always providing the `--no-ff` + flag to `git merge`. + + git merge --no-ff --log tickets/master/1234-fix-something-broken + + The reason for always forcing this merge commit is that it + provides a consistent way to look up what changes & commits were + in a topic branch, whether that topic branch had one, or 500 + commits. For example, if the merge commit had an abbreviated + SHA-1 of `coffeebad`, then you could use the following `git log` + invocation to show you which commits it brought in: + + git log coffeebad^1..coffeebad^2 + + The following would show you which changes were made on the topic + branch: + + git diff coffeebad^1...coffeebad^2 + + Because we _always_ merge the topic branch into the integration + branch the first parent (`^1`) of a merge commit will be the most + recent commit on the integration branch from just before we merged + in the topic, and the second parent (`^2`) will always be the most + recent commit that was made in the topic branch. This also serves + as the record of who performed the code review, as mentioned + above. From c5d63d4fe365f0698f0f5d895ca3c457f5a007e6 Mon Sep 17 00:00:00 2001 From: Peter Meier Date: Wed, 3 Aug 2011 23:36:56 +0200 Subject: [PATCH 0611/3753] Fix #2766 - silence unknown sysctl values On certain hardware models it is possible that some sysctl values we are looking for are not present. The error messages about the missing values were printed to stderr and hence were visible to the user. These messages should not be visible, so we redirect stderr to /dev/null. --- lib/facter/util/manufacturer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/util/manufacturer.rb b/lib/facter/util/manufacturer.rb index 8e9bde2129..78f7f1843d 100644 --- a/lib/facter/util/manufacturer.rb +++ b/lib/facter/util/manufacturer.rb @@ -55,7 +55,7 @@ def self.sysctl_find_system_info(name) Facter.add(facterkey) do confine :kernel => :openbsd setcode do - Facter::Util::Resolution.exec("sysctl -n " + sysctlkey) + Facter::Util::Resolution.exec("sysctl -n #{sysctlkey} 2>/dev/null") end end end From 46cbd68ba6956774bcf0e7762f4051d9cbcac07d Mon Sep 17 00:00:00 2001 From: "Yury V. Zaytsev" Date: Thu, 4 Aug 2011 10:44:43 +0200 Subject: [PATCH 0612/3753] Fix the SPEC file (COPYING no longer shipped, README -> README.md) Signed-off-by: Yury V. Zaytsev --- conf/redhat/facter.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index e78b655244..0089725c1a 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -48,7 +48,7 @@ rm -rf %{buildroot} %{_bindir}/facter %{ruby_sitelibdir}/facter.rb %{ruby_sitelibdir}/facter -%doc CHANGELOG COPYING INSTALL LICENSE README +%doc CHANGELOG INSTALL LICENSE README.md %changelog From e8d00ec74e7041d02d24b6150d004c543000e2ce Mon Sep 17 00:00:00 2001 From: Kelsey Hightower Date: Sun, 14 Aug 2011 23:35:32 -0400 Subject: [PATCH 0613/3753] (#8964) Search mountinfo for selinux mount point In order to support existing systems which mount selinuxfs under '/selinux' and those that do not -- starting with Fedora 16 the selinuxfs will be mounted under '/sys/fs/selinux' -- this patch changes the selinux mount point from a static value of '/selinux' to the results returned from searching '/proc/self/mountinfo'. --- lib/facter/selinux.rb | 109 +++++++++++++++++++++++------------------- 1 file changed, 61 insertions(+), 48 deletions(-) diff --git a/lib/facter/selinux.rb b/lib/facter/selinux.rb index 1555da0b3d..62000d794b 100644 --- a/lib/facter/selinux.rb +++ b/lib/facter/selinux.rb @@ -10,69 +10,82 @@ # Fact for SElinux # Written by immerda admin team (admin(at)immerda.ch) -Facter.add("selinux") do - confine :kernel => :linux +sestatus_cmd = '/usr/sbin/sestatus' + +# This supports the fact that the selinux mount point is not always in the +# same location -- the selinux mount point is operating system specific. +def selinux_mount_point + if FileTest.exists?('/proc/self/mountinfo') + File.open('/proc/self/mountinfo') do |f| + f.grep(/selinuxfs/) do |line| + line.split[4] + end + end + end +end - setcode do - result = "false" - if FileTest.exists?("/selinux/enforce") - if FileTest.exists?("/proc/self/attr/current") - if (File.read("/proc/self/attr/current") != "kernel\0") - result = "true" - end - end +Facter.add("selinux") do + confine :kernel => :linux + setcode do + result = "false" + if FileTest.exists?("#{selinux_mount_point}/enforce") + if FileTest.exists?("/proc/self/attr/current") + if (File.read("/proc/self/attr/current") != "kernel\0") + result = "true" end - result + end end + result + end end Facter.add("selinux_enforced") do - confine :selinux => :true - - setcode do - result = "false" - if FileTest.exists?("/selinux/enforce") and File.read("/selinux/enforce") =~ /1/i - result = "true" - end - result + confine :selinux => :true + setcode do + result = "false" + if FileTest.exists?("#{selinux_mount_point}/enforce") and + File.read("#{selinux_mount_point}/enforce") =~ /1/i + result = "true" end + result + end end Facter.add("selinux_policyversion") do - confine :selinux => :true - setcode do - File.read("/selinux/policyvers") - end + confine :selinux => :true + setcode do + File.read("#{selinux_mount_point}/policyvers") + end end Facter.add("selinux_current_mode") do - confine :selinux => :true - setcode do - result = 'unknown' - mode = Facter::Util::Resolution.exec('/usr/sbin/sestatus') - mode.each_line { |l| result = $1 if l =~ /^Current mode\:\s+(\w+)$/i } - result.chomp - end + confine :selinux => :true + setcode do + result = 'unknown' + mode = Facter::Util::Resolution.exec(sestatus_cmd) + mode.each_line { |l| result = $1 if l =~ /^Current mode\:\s+(\w+)$/i } + result.chomp + end end Facter.add("selinux_config_mode") do - confine :selinux => :true - setcode do - result = 'unknown' - mode = Facter::Util::Resolution.exec('/usr/sbin/sestatus') - mode.each_line { |l| result = $1 if l =~ /^Mode from config file\:\s+(\w+)$/i } - result.chomp - end + confine :selinux => :true + setcode do + result = 'unknown' + mode = Facter::Util::Resolution.exec(sestatus_cmd) + mode.each_line { |l| result = $1 if l =~ /^Mode from config file\:\s+(\w+)$/i } + result.chomp + end end Facter.add("selinux_config_policy") do - confine :selinux => :true - setcode do - result = 'unknown' - mode = Facter::Util::Resolution.exec('/usr/sbin/sestatus') - mode.each_line { |l| result = $1 if l =~ /^Policy from config file\:\s+(\w+)$/i } - result.chomp - end + confine :selinux => :true + setcode do + result = 'unknown' + mode = Facter::Util::Resolution.exec(sestatus_cmd) + mode.each_line { |l| result = $1 if l =~ /^Policy from config file\:\s+(\w+)$/i } + result.chomp + end end # This is a legacy fact which returns the old selinux_mode fact value to prevent @@ -80,8 +93,8 @@ # See ticket #6677. Facter.add("selinux_mode") do - confine :selinux => :true - setcode do - Facter.value(:selinux_config_policy) - end + confine :selinux => :true + setcode do + Facter.value(:selinux_config_policy) + end end From f7daae300d5c993052dd6c49b1b5e1f3501eaa10 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 15 Aug 2011 10:58:37 -0700 Subject: [PATCH 0614/3753] (maint) Remove global var from domain and hostname The hostname and domain facts were tightly coupled because the hostname fact was setting a global variable that the domain fact was using. Removed the logic from the hostname fact that was setting the global variable and moved it into the domain fact. Added tests to verify the behavior of the hostname facts and the domain facts. --- lib/facter/domain.rb | 15 ++++---- lib/facter/hostname.rb | 13 +++---- spec/unit/domain_spec.rb | 73 ++++++++++++++++++++++++++++++++++++++ spec/unit/hostname_spec.rb | 38 ++++++++++++++++++++ 4 files changed, 122 insertions(+), 17 deletions(-) create mode 100644 spec/unit/domain_spec.rb create mode 100644 spec/unit/hostname_spec.rb diff --git a/lib/facter/domain.rb b/lib/facter/domain.rb index 2a79754caf..778e60c15b 100644 --- a/lib/facter/domain.rb +++ b/lib/facter/domain.rb @@ -23,13 +23,13 @@ # Get the domain from various sources; the order of these # steps is important - Facter.value(:hostname) - next $domain if defined? $domain and ! $domain.nil? - - domain = Facter::Util::Resolution.exec('dnsdomainname') - next domain if domain =~ /.+\..+/ - - if FileTest.exists?("/etc/resolv.conf") + if name = Facter::Util::Resolution.exec('hostname') + if name =~ /.*?\.(.+$)/ + $1 + end + elsif domain = Facter::Util::Resolution.exec('dnsdomainname') + domain if domain =~ /.+\..+/ + elsif FileTest.exists?("/etc/resolv.conf") domain = nil search = nil File.open("/etc/resolv.conf") { |file| @@ -44,7 +44,6 @@ next domain if domain next search if search end - nil end end diff --git a/lib/facter/hostname.rb b/lib/facter/hostname.rb index 0dcd01c69b..36c353f98b 100644 --- a/lib/facter/hostname.rb +++ b/lib/facter/hostname.rb @@ -14,25 +14,20 @@ Facter.add(:hostname, :ldapname => "cn") do setcode do hostname = nil - name = Facter::Util::Resolution.exec('hostname') or nil - if name - if name =~ /^([\w-]+)\.(.+)$/ + if name = Facter::Util::Resolution.exec('hostname') + if name =~ /(.*?)\./ hostname = $1 - # the Domain class uses this - $domain = $2 else hostname = name end - hostname - else - nil end + hostname end end Facter.add(:hostname) do confine :kernel => :darwin, :kernelrelease => "R7" setcode do - %x{/usr/sbin/scutil --get LocalHostName} + Facter::Util::Resolution.exec('/usr/sbin/scutil --get LocalHostName') end end diff --git a/spec/unit/domain_spec.rb b/spec/unit/domain_spec.rb new file mode 100644 index 0000000000..e1f5427b5d --- /dev/null +++ b/spec/unit/domain_spec.rb @@ -0,0 +1,73 @@ +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') + +describe "Domain name facts" do + + describe "on linux" do + before do + Facter.fact(:kernel).stubs(:value).returns("Linux") + end + + it "should use the hostname binary" do + Facter::Util::Resolution.expects(:exec).with("hostname").returns "test.example.com" + Facter.fact(:domain).value.should == "example.com" + end + + it "should fall back to the dnsdomainname binary" do + Facter::Util::Resolution.stubs(:exec).with("hostname") + Facter::Util::Resolution.expects(:exec).with("dnsdomainname").returns("example.com") + Facter.fact(:domain).value.should == "example.com" + end + + + it "should fall back to /etc/resolv.conf" do + Facter::Util::Resolution.stubs(:exec).with("hostname").at_least_once + Facter::Util::Resolution.stubs(:exec).with("dnsdomainname").at_least_once + File.expects(:open).with('/etc/resolv.conf').at_least_once + Facter.fact(:domain).value + end + + it "should attempt to resolve facts in a specific order" do + seq = sequence('domain') + Facter::Util::Resolution.stubs(:exec).with("hostname").in_sequence(seq).at_least_once + Facter::Util::Resolution.stubs(:exec).with("dnsdomainname").in_sequence(seq).at_least_once + File.expects(:open).with('/etc/resolv.conf').in_sequence(seq).at_least_once + Facter.fact(:domain).value + end + + describe "when using /etc/resolv.conf" do + before do + Facter::Util::Resolution.stubs(:exec).with("hostname") + Facter::Util::Resolution.stubs(:exec).with("dnsdomainname") + @mock_file = mock() + File.stubs(:open).with("/etc/resolv.conf").yields(@mock_file) + end + + it "should use the domain field over the search field" do + lines = [ + "nameserver 4.2.2.1", + "search example.org", + "domain example.com", + ] + @mock_file.expects(:each).multiple_yields(*lines) + Facter.fact(:domain).value.should == 'example.com' + end + + it "should fall back to the search field" do + lines = [ + "nameserver 4.2.2.1", + "search example.org", + ] + @mock_file.expects(:each).multiple_yields(*lines) + Facter.fact(:domain).value.should == 'example.org' + end + + it "should use the first domain in the search field" do + lines = [ + "search example.org example.net", + ] + @f.expects(:each).multiple_yields(*lines) + Facter.fact(:domain).value.should == 'example.org' + end + end + end +end diff --git a/spec/unit/hostname_spec.rb b/spec/unit/hostname_spec.rb new file mode 100644 index 0000000000..ea7f2bb83b --- /dev/null +++ b/spec/unit/hostname_spec.rb @@ -0,0 +1,38 @@ +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') + +describe "Hostname facts" do + + describe "on linux" do + before do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:kernelrelease).stubs(:value).returns("2.6") + end + + it "should use the hostname command" do + Facter::Util::Resolution.expects(:exec).with('hostname').at_least_once + Facter.fact(:hostname).value.should be_nil + end + + it "should use hostname as the fact if unqualified" do + Facter::Util::Resolution.stubs(:exec).with('hostname').returns('host1') + Facter.fact(:hostname).value.should == "host1" + end + + it "should truncate the domain name if qualified" do + Facter::Util::Resolution.stubs(:exec).with('hostname').returns('host1.example.com') + Facter.fact(:hostname).value.should == "host1" + end + end + + describe "on darwin release R7" do + before do + Facter.fact(:kernel).stubs(:value).returns("Darwin") + Facter.fact(:kernelrelease).stubs(:value).returns("R7") + end + + it "should use scutil to get the hostname" do + Facter::Util::Resolution.expects(:exec).with('/usr/sbin/scutil --get LocalHostName').returns("host1") + Facter.fact(:hostname).value.should == "host1" + end + end +end From cceb74beab3301f0b22b555559fa5e916f696deb Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 15 Aug 2011 13:07:44 -0700 Subject: [PATCH 0615/3753] (maint) Add stubbing and corrections for domain spec On platforms that don't use /etc/resolv.conf, the spec would fail since /etc/resolv.conf doesn't exist. Stubbed the value to return so that tests will pass on all platforms. Corrected a variable naming error. --- spec/unit/domain_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/unit/domain_spec.rb b/spec/unit/domain_spec.rb index e1f5427b5d..d18a7f6c37 100644 --- a/spec/unit/domain_spec.rb +++ b/spec/unit/domain_spec.rb @@ -5,6 +5,7 @@ describe "on linux" do before do Facter.fact(:kernel).stubs(:value).returns("Linux") + FileTest.stubs(:exists?).with("/etc/resolv.conf").returns(true) end it "should use the hostname binary" do @@ -65,7 +66,7 @@ lines = [ "search example.org example.net", ] - @f.expects(:each).multiple_yields(*lines) + @mock_file.expects(:each).multiple_yields(*lines) Facter.fact(:domain).value.should == 'example.org' end end From 00bed7a658510d85a6e832482b4058ec73dc078a Mon Sep 17 00:00:00 2001 From: Daniel Pittman Date: Wed, 17 Aug 2011 11:02:50 -0700 Subject: [PATCH 0616/3753] (#8964) Search mountinfo for selinux mount point Fix up code that fails when you don't have the SELinux mountpoint available. Signed-off-by: Daniel Pittman --- lib/facter/selinux.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/facter/selinux.rb b/lib/facter/selinux.rb index 62000d794b..60caf80cba 100644 --- a/lib/facter/selinux.rb +++ b/lib/facter/selinux.rb @@ -21,6 +21,8 @@ def selinux_mount_point line.split[4] end end + else + "/selinux" end end From b3e2274eaf26d9e9df85679d89a9b8a9eac184f7 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Mon, 15 Aug 2011 13:27:21 -0700 Subject: [PATCH 0617/3753] (#8439) Add Facter::Util::WMI module This commit adds a WMI module with methods for connecting to the WMI service and executing queries. This code is based on Nick's ASDI module in puppet, but only contains the WMI calls that we need. As part of this change, it ensures the default WMI moniker string is consistently specified as the following: winmgmts:{impersonationLevel=impersonate}!//./root/cimv2 However, the wmi_resource_uri method can be overridden to provide different behavior if necessary, e.g. different WMI namespace. This commit also refactors the various places that we were using WMI (inconsistently). Another motivation for this change is to allow the WIN32OLE.connect and ExecQuery methods to be stubbed, which wasn't possible previously. This commit also adds manufacturer spec tests for Windows, which stub the connect and execquery methods so that the tests can be run on non-Windows platforms. --- lib/facter/domain.rb | 6 ++---- lib/facter/kernelrelease.rb | 6 ++---- lib/facter/util/manufacturer.rb | 5 ++--- lib/facter/util/uptime.rb | 11 ++++++----- lib/facter/util/wmi.rb | 16 ++++++++++++++++ spec/unit/domain_spec.rb | 17 +++++++++++++++++ spec/unit/util/manufacturer_spec.rb | 21 +++++++++++++++++++++ spec/unit/util/wmi_spec.rb | 20 ++++++++++++++++++++ 8 files changed, 86 insertions(+), 16 deletions(-) create mode 100644 lib/facter/util/wmi.rb create mode 100755 spec/unit/util/wmi_spec.rb diff --git a/lib/facter/domain.rb b/lib/facter/domain.rb index 778e60c15b..1cd6ef0180 100644 --- a/lib/facter/domain.rb +++ b/lib/facter/domain.rb @@ -50,11 +50,9 @@ Facter.add(:domain) do confine :kernel => :windows setcode do - require 'win32ole' + require 'facter/util/wmi' domain = "" - wmi = WIN32OLE.connect("winmgmts://") - query = "select DNSDomain from Win32_NetworkAdapterConfiguration where IPEnabled = True" - wmi.ExecQuery(query).each { |nic| + Facter::Util::WMI.execquery("select DNSDomain from Win32_NetworkAdapterConfiguration where IPEnabled = True").each { |nic| domain = nic.DNSDomain break } diff --git a/lib/facter/kernelrelease.rb b/lib/facter/kernelrelease.rb index a6f9c2c48e..1d348262f5 100644 --- a/lib/facter/kernelrelease.rb +++ b/lib/facter/kernelrelease.rb @@ -23,11 +23,9 @@ Facter.add(:kernelrelease) do confine :kernel => %{windows} setcode do - require 'win32ole' + require 'facter/util/wmi' version = "" - connection_string = "winmgmts://./root/cimv2" - wmi = WIN32OLE.connect(connection_string) - wmi.ExecQuery("SELECT Version from Win32_OperatingSystem").each do |ole| + Facter::Util::WMI.execquery("SELECT Version from Win32_OperatingSystem").each do |ole| version = "#{ole.Version}" break end diff --git a/lib/facter/util/manufacturer.rb b/lib/facter/util/manufacturer.rb index 8e9bde2129..62ef1c3af4 100644 --- a/lib/facter/util/manufacturer.rb +++ b/lib/facter/util/manufacturer.rb @@ -82,9 +82,9 @@ def self.prtdiag_sparc_find_system_info() end def self.win32_find_system_info(name) - require 'win32ole' + require 'facter/util/wmi' value = "" - wmi = WIN32OLE.connect("winmgmts://") + wmi = Facter::Util::WMI.connect() name.each do |facterkey, win32key| query = wmi.ExecQuery("select * from Win32_#{win32key.last}") Facter.add(facterkey) do @@ -96,5 +96,4 @@ def self.win32_find_system_info(name) end end end - end diff --git a/lib/facter/util/uptime.rb b/lib/facter/util/uptime.rb index 4e6a7b6d7e..a816623db0 100644 --- a/lib/facter/util/uptime.rb +++ b/lib/facter/util/uptime.rb @@ -8,12 +8,13 @@ def self.get_uptime_seconds_unix end def self.get_uptime_seconds_win - require 'win32ole' - wmi = WIN32OLE.connect("winmgmts://") - query = wmi.ExecQuery("select * from Win32_OperatingSystem") + require 'facter/util/wmi' + last_boot = "" - query.each { |x| last_boot = x.LastBootupTime} - self.compute_uptime(Time.parse(last_boot.split('.').first)) + Facter::Util::WMI.execquery("select * from Win32_OperatingSystem").each do |x| + last_boot = x.LastBootupTime + end + self.compute_uptime(Time.parse(last_boot.split('.').first)) end private diff --git a/lib/facter/util/wmi.rb b/lib/facter/util/wmi.rb new file mode 100644 index 0000000000..f69f56359c --- /dev/null +++ b/lib/facter/util/wmi.rb @@ -0,0 +1,16 @@ +module Facter::Util::WMI + class << self + def connect(uri = wmi_resource_uri) + require 'win32ole' + WIN32OLE.connect(uri) + end + + def wmi_resource_uri( host = '.' ) + "winmgmts:{impersonationLevel=impersonate}!//#{host}/root/cimv2" + end + + def execquery(query) + connect().execquery(query) + end + end +end diff --git a/spec/unit/domain_spec.rb b/spec/unit/domain_spec.rb index d18a7f6c37..d04132ad24 100644 --- a/spec/unit/domain_spec.rb +++ b/spec/unit/domain_spec.rb @@ -71,4 +71,21 @@ end end end + + describe "on Windows" do + it "should use the DNSDomain for the first nic where ip is enabled" do + Facter.fact(:kernel).stubs(:value).returns("windows") + + nic = stubs 'nic' + nic.stubs(:DNSDomain).returns("foo.com") + + nic2 = stubs 'nic' + nic2.stubs(:DNSDomain).returns("bar.com") + + require 'facter/util/wmi' + Facter::Util::WMI.stubs(:execquery).with("select DNSDomain from Win32_NetworkAdapterConfiguration where IPEnabled = True").returns([nic, nic2]) + + Facter.fact(:domain).value.should == 'foo.com' + end + end end diff --git a/spec/unit/util/manufacturer_spec.rb b/spec/unit/util/manufacturer_spec.rb index c3b372ea00..c738811cdd 100644 --- a/spec/unit/util/manufacturer_spec.rb +++ b/spec/unit/util/manufacturer_spec.rb @@ -129,4 +129,25 @@ def find_product_name(os) find_product_name("FreeBSD").should == find_product_name("SunOS") end + it "should find information on Windows" do + Facter.fact(:kernel).stubs(:value).returns("windows") + require 'facter/util/wmi' + + bios = stubs 'bios' + bios.stubs(:Manufacturer).returns("Phoenix Technologies LTD") + bios.stubs(:Serialnumber).returns("56 4d 40 2b 4d 81 94 d6-e6 c5 56 a4 56 0c 9e 9f") + + product = stubs 'product' + product.stubs(:Name).returns("VMware Virtual Platform") + + wmi = stubs 'wmi' + wmi.stubs(:ExecQuery).with("select * from Win32_Bios").returns([bios]) + wmi.stubs(:ExecQuery).with("select * from Win32_Bios").returns([bios]) + wmi.stubs(:ExecQuery).with("select * from Win32_ComputerSystemProduct").returns([product]) + + Facter::Util::WMI.stubs(:connect).returns(wmi) + Facter.value(:manufacturer).should == "Phoenix Technologies LTD" + Facter.value(:serialnumber).should == "56 4d 40 2b 4d 81 94 d6-e6 c5 56 a4 56 0c 9e 9f" + Facter.value(:productname).should == "VMware Virtual Platform" + end end diff --git a/spec/unit/util/wmi_spec.rb b/spec/unit/util/wmi_spec.rb new file mode 100755 index 0000000000..f5757fa5bd --- /dev/null +++ b/spec/unit/util/wmi_spec.rb @@ -0,0 +1,20 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') + +require 'facter/util/wmi' + +describe Facter::Util::WMI do + let(:connection) { stub 'connection' } + + it "should default to localhost" do + Facter::Util::WMI.wmi_resource_uri.should == "winmgmts:{impersonationLevel=impersonate}!//./root/cimv2" + end + + it "should execute the query on the connection" do + Facter::Util::WMI.stubs(:connect).returns(connection) + connection.stubs(:execquery).with("select * from Win32_OperatingSystem") + + Facter::Util::WMI.execquery("select * from Win32_OperatingSystem") + end +end From 9ef56d68d256d0497bd7c8aeb2f1750aa42b8e6e Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Mon, 15 Aug 2011 13:34:40 -0700 Subject: [PATCH 0618/3753] (#8439) Implement total and free physical memory on Windows This commit adds the 'memoryfree' and 'memorytotal' facts for Windows. These values represent the amount of physical free and total memory respectively. Note that the free and total values come from different WMI objects that report memory sizes in different units. The free value reported by Win32_OperatingSystem is in kB whereas the total value as reported by Win32_ComputerSystem is in bytes. This commit does not add facts for free and total page sizes, since the total page size is associated with the Win32_PageFileSetting class, but WMI reports no instance(s) available when automatic page file management is enabled (and it is by default). --- lib/facter/memory.rb | 28 ++++++++++++++++++++++++++++ spec/unit/memory_spec.rb | 26 ++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) mode change 100644 => 100755 spec/unit/memory_spec.rb diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb index 992f2ade4c..ff5eed1816 100644 --- a/lib/facter/memory.rb +++ b/lib/facter/memory.rb @@ -206,3 +206,31 @@ Facter::Memory.vmstat_find_free_memory() end + +if Facter.value(:kernel) == "windows" + require 'facter/util/wmi' + + Facter.add("MemoryFree") do + confine :kernel => :windows + setcode do + mem = 0 + Facter::Util::WMI.execquery("select FreePhysicalMemory from Win32_OperatingSystem").each do |os| + mem = os.FreePhysicalMemory + break + end + Facter::Memory.scale_number(mem.to_f, "kB") + end + end + + Facter.add("MemoryTotal") do + confine :kernel => :windows + setcode do + mem = 0 + Facter::Util::WMI.execquery("select TotalPhysicalMemory from Win32_ComputerSystem").each do |comp| + mem = comp.TotalPhysicalMemory + break + end + Facter::Memory.scale_number(mem.to_f, "") + end + end +end diff --git a/spec/unit/memory_spec.rb b/spec/unit/memory_spec.rb old mode 100644 new mode 100755 index fe4ec367a3..7aeb9ddce2 --- a/spec/unit/memory_spec.rb +++ b/spec/unit/memory_spec.rb @@ -44,4 +44,30 @@ swapusage =~ /\(encrypted\)/ Facter.fact(:swapencrypted).value.should == true end + + describe "on Windows" do + before :each do + Facter.clear + Facter.fact(:kernel).stubs(:value).returns("windows") + Facter.collection.loader.load(:memory) + + require 'facter/util/wmi' + end + + it "should return free memory" do + os = stubs 'os' + os.stubs(:FreePhysicalMemory).returns("3415624") + Facter::Util::WMI.stubs(:execquery).returns([os]) + + Facter.fact(:MemoryFree).value.should == '3.26 GB' + end + + it "should return total memory" do + computer = stubs 'computer' + computer.stubs(:TotalPhysicalMemory).returns("4193837056") + Facter::Util::WMI.stubs(:execquery).returns([computer]) + + Facter.fact(:MemoryTotal).value.should == '3.91 GB' + end + end end From 824fac0892b5fba6282d7c7fdd45c3b614ed0903 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Mon, 15 Aug 2011 13:44:41 -0700 Subject: [PATCH 0619/3753] (#8439) Add physicalprocessorcount and processor facts on Windows This commit adds the 'physicalprocessorcount', 'processor{n}' and 'processorcount' facts. The 'physicalprocessorcount' fact is obtained by counting the number of Win32_Processor instances. Note that the WMI query does a select on just the Name property, because it is faster than doing a 'select *' On Windows 2008, each Win32_Processor represents a physical processor, and the NumberOfLogicalProcessors property (which includes both multi and/or hyperthreaded cores) represents the number of logical processors. For example, a dual-core processor, with quad-hyper threads per core, will report 1 physical processor and 8 logical processors. Note that the NumberOfCores property could be used to distinguish between multi-core and hyperthreading processors, but the fact does not distinguish between them. On Windows 2003, each Win32_Processor represents a logical processor, and the NumberOfLogicalProcessors property is not available. In this case, the physicalprocessorcount fact will be over-reported, but the number of logical processors will be correct. With that said, if this hotfix is installed, then 2003 behaves like 2008: http://support.microsoft.com/kb/932370 The Win32_Processor.Name property contains extra spaces: Intel(R) Core(TM) i7 CPU M 620 @ 2.67GHz So we 'squeeze' the output to eliminate duplicate spaces: processor0 => Intel(R) Core(TM) i7 CPU M 620 @ 2.67GHz --- lib/facter/physicalprocessorcount.rb | 8 +++ lib/facter/processor.rb | 38 ++++++++++++++ spec/unit/physicalprocessorcount_spec.rb | 9 ++++ spec/unit/processor_spec.rb | 66 ++++++++++++++++++++++++ 4 files changed, 121 insertions(+) mode change 100644 => 100755 spec/unit/physicalprocessorcount_spec.rb create mode 100755 spec/unit/processor_spec.rb diff --git a/lib/facter/physicalprocessorcount.rb b/lib/facter/physicalprocessorcount.rb index 22c00c1b47..7514febaf4 100644 --- a/lib/facter/physicalprocessorcount.rb +++ b/lib/facter/physicalprocessorcount.rb @@ -54,3 +54,11 @@ end end end + +Facter.add('physicalprocessorcount') do + confine :kernel => :windows + setcode do + require 'facter/util/wmi' + Facter::Util::WMI.execquery("select Name from Win32_Processor").count + end +end diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb index ec196b2172..ccce398d6b 100644 --- a/lib/facter/processor.rb +++ b/lib/facter/processor.rb @@ -104,3 +104,41 @@ end end end + +if Facter.value(:kernel) == "windows" + processor_list = [] + + Thread::exclusive do + require 'facter/util/wmi' + + # get each physical processor + Facter::Util::WMI.execquery("select * from Win32_Processor").each do |proc| + # not supported before 2008 + begin + processor_num = proc.NumberOfLogicalProcessors + rescue RuntimeError => e + processor_num = 1 + end + + processor_num.times do |i| + processor_list << proc.Name.squeeze(" ") + end + end + end + + processor_list.each_with_index do |name, i| + Facter.add("Processor#{i}") do + confine :kernel => :windows + setcode do + name + end + end + end + + Facter.add("ProcessorCount") do + confine :kernel => :windows + setcode do + processor_list.length.to_s + end + end +end diff --git a/spec/unit/physicalprocessorcount_spec.rb b/spec/unit/physicalprocessorcount_spec.rb old mode 100644 new mode 100755 index e1f7c6015e..3cf1ae210c --- a/spec/unit/physicalprocessorcount_spec.rb +++ b/spec/unit/physicalprocessorcount_spec.rb @@ -37,4 +37,13 @@ Facter.fact(:physicalprocessorcount).value.should == 4 end + + it "should return 4 physical CPUs on Windows" do + Facter.fact(:kernel).stubs(:value).returns("windows") + + require 'facter/util/wmi' + Facter::Util::WMI.stubs(:execquery).with("select Name from Win32_Processor").returns(Array.new(4)) + + Facter.fact(:physicalprocessorcount).value.should == 4 + end end diff --git a/spec/unit/processor_spec.rb b/spec/unit/processor_spec.rb new file mode 100755 index 0000000000..6a30c85a3c --- /dev/null +++ b/spec/unit/processor_spec.rb @@ -0,0 +1,66 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') + +require 'facter' + +describe "Processor facts" do + describe "on Windows" do + before :each do + Facter.clear + Facter.fact(:kernel).stubs(:value).returns("windows") + end + + def load(procs) + require 'facter/util/wmi' + Facter::Util::WMI.stubs(:execquery).with("select * from Win32_Processor").returns(procs) + + # processor facts belong to a file with a different name, + # so load the file explicitly (after stubbing kernel), + # but we have to stub execquery first + Facter.collection.loader.load(:processor) + end + + describe "2003" do + before :each do + proc = stubs 'proc' + proc.stubs(:NumberOfLogicalProcessors).raises(RuntimeError) + proc.stubs(:Name).returns("Intel(R) Celeron(R) processor") + + load(Array.new(2, proc)) + end + + it "should count 2 processors" do + Facter.fact(:processorcount).value.should == "2" + end + + it "should squeeze the processor name 2 times" do + 2.times do |i| + Facter.fact("processor#{i}".to_sym).value.should == "Intel(R) Celeron(R) processor" + end + end + end + + describe "2008" do + before :each do + proc = stubs 'proc' + proc.stubs(:NumberOfLogicalProcessors).returns(2) + proc.stubs(:Name).returns("Intel(R) Celeron(R) processor") + + load(Array.new(2, proc)) + end + + it "should count 4 processors" do + Facter.fact(:processorcount).value.should == "4" + end + + it "should squeeze the processor name 4 times" do + 4.times do |i| + Facter.fact("processor#{i}".to_sym).value.should == "Intel(R) Celeron(R) processor" + end + end + end + end +end + + From a347920dff3c65d616b9f0e4670534dfe167a2d5 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 25 Aug 2011 14:05:23 -0700 Subject: [PATCH 0620/3753] (#9183) Add support for Alpine linux detection Adds support for Alpine Linux operatingsystem and operatingsystemrelease facts, by relying on the presence and contents of /etc/alpine-release. Thanks to Jon Auer for this patch. Signed-off-by: Adrien Thebo --- lib/facter/operatingsystem.rb | 2 ++ lib/facter/operatingsystemrelease.rb | 7 +++++++ spec/unit/operatingsystem_spec.rb | 10 ++++++++++ spec/unit/operatingsystemrelease_spec.rb | 9 +++++++++ 4 files changed, 28 insertions(+) diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index a90c283554..396e52ba60 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -73,6 +73,8 @@ "Slamd64" elsif FileTest.exists?("/etc/slackware-version") "Slackware" + elsif FileTest.exists?("/etc/alpine-release") + "Alpine" end end end diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index 347fe7f05a..6fe4920b87 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -122,6 +122,13 @@ end end +Facter.add(:operatingsystemrelease) do + confine :operatingsystem => :Alpine + setcode do + File.read('/etc/alpine-release') + end +end + Facter.add(:operatingsystemrelease) do setcode do Facter[:kernelrelease].value end end diff --git a/spec/unit/operatingsystem_spec.rb b/spec/unit/operatingsystem_spec.rb index 9a7971d6d7..51540cb6be 100755 --- a/spec/unit/operatingsystem_spec.rb +++ b/spec/unit/operatingsystem_spec.rb @@ -52,4 +52,14 @@ Facter.fact(:operatingsystem).value.should == "VMWareESX" end + + it "should identify Alpine Linux" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + + FileTest.stubs(:exists?).returns false + + FileTest.expects(:exists?).with("/etc/alpine-release").returns true + + Facter.fact(:operatingsystem).value.should == "Alpine" + end end diff --git a/spec/unit/operatingsystemrelease_spec.rb b/spec/unit/operatingsystemrelease_spec.rb index 739a20a8de..a6efa475fb 100755 --- a/spec/unit/operatingsystemrelease_spec.rb +++ b/spec/unit/operatingsystemrelease_spec.rb @@ -46,4 +46,13 @@ Facter.fact(:operatingsystemrelease).value end + + it "for Alpine it should use the contents of /etc/alpine-release" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:operatingsystem).stubs(:value).returns("Alpine") + + File.expects(:read).with("/etc/alpine-release").returns("foo") + + Facter.fact(:operatingsystemrelease).value.should == "foo" + end end From 0721f2ffe2a4b26c850d5df25e428c40df477a02 Mon Sep 17 00:00:00 2001 From: Orion Poplawski Date: Mon, 29 Aug 2011 10:36:13 -0700 Subject: [PATCH 0621/3753] (#7682) Add complete support for Scientific Linux Provides for more complete coverage for Scientific Linux facts. Signed-off-by: Adrien Thebo --- lib/facter/hardwareisa.rb | 2 +- lib/facter/lsbmajdistrelease.rb | 2 +- lib/facter/macaddress.rb | 2 +- lib/facter/operatingsystemrelease.rb | 4 ++-- lib/facter/uniqueid.rb | 2 +- spec/unit/operatingsystemrelease_spec.rb | 17 +++++++++-------- 6 files changed, 15 insertions(+), 14 deletions(-) diff --git a/lib/facter/hardwareisa.rb b/lib/facter/hardwareisa.rb index 44e5557c36..d122d81e0a 100644 --- a/lib/facter/hardwareisa.rb +++ b/lib/facter/hardwareisa.rb @@ -12,5 +12,5 @@ Facter.add(:hardwareisa) do setcode 'uname -p' - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE SLES Debian Ubuntu Gentoo FreeBSD OpenBSD NetBSD OEL OVS GNU/kFreeBSD} + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS Scientific SuSE SLES Debian Ubuntu Gentoo FreeBSD OpenBSD NetBSD OEL OVS GNU/kFreeBSD} end diff --git a/lib/facter/lsbmajdistrelease.rb b/lib/facter/lsbmajdistrelease.rb index 365954166b..4fa68e9396 100644 --- a/lib/facter/lsbmajdistrelease.rb +++ b/lib/facter/lsbmajdistrelease.rb @@ -15,7 +15,7 @@ require 'facter' Facter.add("lsbmajdistrelease") do - confine :operatingsystem => %w{Linux Fedora RedHat CentOS SuSE SLES Debian Ubuntu Gentoo OEL OVS GNU/kFreeBSD} + confine :operatingsystem => %w{Linux Fedora RedHat CentOS Scientific SuSE SLES Debian Ubuntu Gentoo OEL OVS GNU/kFreeBSD} setcode do if /(\d*)\./i =~ Facter.value(:lsbdistrelease) result=$1 diff --git a/lib/facter/macaddress.rb b/lib/facter/macaddress.rb index 54c3c84834..1e60064f9f 100644 --- a/lib/facter/macaddress.rb +++ b/lib/facter/macaddress.rb @@ -10,7 +10,7 @@ require 'facter/util/macaddress' Facter.add(:macaddress) do - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE SLES Debian Gentoo Ubuntu OEL OVS GNU/kFreeBSD} + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS Scientific SuSE SLES Debian Gentoo Ubuntu OEL OVS GNU/kFreeBSD} setcode do ether = [] output = %x{/sbin/ifconfig -a} diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index 6fe4920b87..97085eb319 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -16,10 +16,10 @@ # Facter.add(:operatingsystemrelease) do - confine :operatingsystem => %w{CentOS Fedora oel ovs RedHat MeeGo} + confine :operatingsystem => %w{CentOS Fedora oel ovs RedHat MeeGo Scientific} setcode do case Facter.value(:operatingsystem) - when "CentOS", "RedHat" + when "CentOS", "RedHat", "Scientific" releasefile = "/etc/redhat-release" when "Fedora" releasefile = "/etc/fedora-release" diff --git a/lib/facter/uniqueid.rb b/lib/facter/uniqueid.rb index 90b5fff368..713d0a9202 100644 --- a/lib/facter/uniqueid.rb +++ b/lib/facter/uniqueid.rb @@ -1,4 +1,4 @@ Facter.add(:uniqueid) do setcode 'hostid' - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS SuSE SLES Debian Ubuntu Gentoo AIX OEL OVS GNU/kFreeBSD} + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS Scientific SuSE SLES Debian Ubuntu Gentoo AIX OEL OVS GNU/kFreeBSD} end diff --git a/spec/unit/operatingsystemrelease_spec.rb b/spec/unit/operatingsystemrelease_spec.rb index a6efa475fb..33b3c7f034 100755 --- a/spec/unit/operatingsystemrelease_spec.rb +++ b/spec/unit/operatingsystemrelease_spec.rb @@ -15,14 +15,15 @@ end test_cases = { - "CentOS" => "/etc/redhat-release", - "RedHat" => "/etc/redhat-release", - "Fedora" => "/etc/fedora-release", - "MeeGo" => "/etc/meego-release", - "OEL" => "/etc/enterprise-release", - "oel" => "/etc/enterprise-release", - "OVS" => "/etc/ovs-release", - "ovs" => "/etc/ovs-release", + "CentOS" => "/etc/redhat-release", + "RedHat" => "/etc/redhat-release", + "Scientific" => "/etc/redhat-release", + "Fedora" => "/etc/fedora-release", + "MeeGo" => "/etc/meego-release", + "OEL" => "/etc/enterprise-release", + "oel" => "/etc/enterprise-release", + "OVS" => "/etc/ovs-release", + "ovs" => "/etc/ovs-release", } test_cases.each do |system, file| From ddb67c53a684f0d4bb7bbf37ca15b0fd8f28325e Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Mon, 15 Aug 2011 14:19:19 -0700 Subject: [PATCH 0622/3753] (#8439) Move macaddress resolution on Windows Moved the code for performing macaddress resolution into a utility module. This is being done in preparation for supporting interface-specific macaddresses, e.g. macaddress_ = . As before, the 'macaddress' fact returns the macaddress of the first network adapter on which IP is enabled, which is the same behavior on other platforms. Previously, the fact was selecting all properties, but only retrieving the MACAddress property. This commit changes the query to only select the property it needs, which reduces the amount of data obtained from WMI. --- lib/facter/macaddress.rb | 28 ++++------------------------ lib/facter/util/macaddress.rb | 16 ++++++++++++++++ spec/unit/util/macaddress_spec.rb | 17 +++++++++++++++++ 3 files changed, 37 insertions(+), 24 deletions(-) diff --git a/lib/facter/macaddress.rb b/lib/facter/macaddress.rb index 54c3c84834..ec42e11a7e 100644 --- a/lib/facter/macaddress.rb +++ b/lib/facter/macaddress.rb @@ -76,28 +76,8 @@ end Facter.add(:macaddress) do - confine :kernel => %w(windows) - setcode do - require 'win32ole' - require 'socket' - - ether = nil - host = Socket.gethostname - connect_string = "winmgmts://#{host}/root/cimv2" - - wmi = WIN32OLE.connect(connect_string) - - query = %Q{ - select * - from Win32_NetworkAdapterConfiguration - where IPEnabled = True - } - - wmi.ExecQuery(query).each{ |nic| - ether = nic.MacAddress - break - } - - ether - end + confine :kernel => %w(windows) + setcode do + Facter::Util::Macaddress::Windows.macaddress + end end diff --git a/lib/facter/util/macaddress.rb b/lib/facter/util/macaddress.rb index f6bae142a2..4d7a478225 100644 --- a/lib/facter/util/macaddress.rb +++ b/lib/facter/util/macaddress.rb @@ -25,4 +25,20 @@ def self.ifconfig_command '/sbin/ifconfig' end end + + module Windows + def macaddress + require 'facter/util/wmi' + + query = "select MACAddress from Win32_NetworkAdapterConfiguration where IPEnabled = True" + + ether = nil + Facter::Util::WMI.execquery(query).each do |nic| + ether = nic.MacAddress + break + end + ether + end + module_function :macaddress + end end diff --git a/spec/unit/util/macaddress_spec.rb b/spec/unit/util/macaddress_spec.rb index 8b58394032..edaeff13c2 100755 --- a/spec/unit/util/macaddress_spec.rb +++ b/spec/unit/util/macaddress_spec.rb @@ -66,3 +66,20 @@ end end end + +describe "Windows" do + it "should return the first macaddress" do + Facter.fact(:kernel).stubs(:value).returns("windows") + + nic = stubs 'nic' + nic.stubs(:MacAddress).returns("00:0C:29:0C:9E:9F") + + nic2 = stubs 'nic' + nic2.stubs(:MacAddress).returns("00:0C:29:0C:9E:AF") + + require 'facter/util/wmi' + Facter::Util::WMI.stubs(:execquery).with("select MACAddress from Win32_NetworkAdapterConfiguration where IPEnabled = True").returns([nic, nic2]) + + Facter.fact(:macaddress).value.should == "00:0C:29:0C:9E:9F" + end +end From 7531a2b01c8d2ab16e5cffc82b9019ff0b5eb6b4 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Mon, 15 Aug 2011 14:22:28 -0700 Subject: [PATCH 0623/3753] (#8439) Add ps fact on Windows The ps fact was defaulting to 'ps -ef', which isn't valid on Windows. This commit adds a Windows-specific ps fact that returns the output of tasklist. This executable is available in XP and later. --- lib/facter/ps.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/facter/ps.rb b/lib/facter/ps.rb index ef803fb870..6c22e26778 100644 --- a/lib/facter/ps.rb +++ b/lib/facter/ps.rb @@ -18,3 +18,8 @@ confine :operatingsystem => %w{FreeBSD NetBSD OpenBSD Darwin} setcode do 'ps auxwww' end end + +Facter.add(:ps) do + confine :operatingsystem => :windows + setcode do 'tasklist.exe' end +end From 5d5848c386629e05df771265a65f632bb86c461e Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Mon, 15 Aug 2011 14:26:30 -0700 Subject: [PATCH 0624/3753] (#8439) Add ipaddress6 fact on Windows This commit adds the ipaddress6 fact, which is determined using netsh. Note that the command uses the SYSTEMROOT environment variable to handle hosts that have non-standard windows directories. The fact follows other platforms in that it returns the first non-loopback (::1) and non-linklocal (fe80.*) ipv6 address. --- lib/facter/ipaddress6.rb | 9 +++++ ...s_netsh_addresses_with_multiple_interfaces | 35 +++++++++++++++++++ spec/unit/ipaddress6_spec.rb | 15 +++++++- 3 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 spec/fixtures/netsh/windows_netsh_addresses_with_multiple_interfaces diff --git a/lib/facter/ipaddress6.rb b/lib/facter/ipaddress6.rb index db3805bb36..bcc0aee778 100644 --- a/lib/facter/ipaddress6.rb +++ b/lib/facter/ipaddress6.rb @@ -61,3 +61,12 @@ def get_address_after_token(output, token, return_first=false) get_address_after_token(output, 'inet6', true) end end + +Facter.add(:ipaddress6) do + confine :kernel => :windows + setcode do + output = Facter::Util::Resolution.exec("#{ENV['SYSTEMROOT']}/system32/netsh interface ipv6 show address level=verbose") + + get_address_after_token(output, 'Address', true) + end +end diff --git a/spec/fixtures/netsh/windows_netsh_addresses_with_multiple_interfaces b/spec/fixtures/netsh/windows_netsh_addresses_with_multiple_interfaces new file mode 100644 index 0000000000..6235f8ed62 --- /dev/null +++ b/spec/fixtures/netsh/windows_netsh_addresses_with_multiple_interfaces @@ -0,0 +1,35 @@ +Address ::1 Parameters +--------------------------------------------------------- +Interface Luid : Loopback Pseudo-Interface 1 +Scope Id : 0.0 +Valid Lifetime : infinite +Preferred Lifetime : infinite +DAD State : Preferred +Address Type : Other + +Address fe80::7128:aa90:8f2a:8375%9 Parameters +--------------------------------------------------------- +Interface Luid : Local Area Connection +Scope Id : 0.9 +Valid Lifetime : infinite +Preferred Lifetime : infinite +DAD State : Preferred +Address Type : Other + +Address fe80::5efe:172.16.138.216%11 Parameters +--------------------------------------------------------- +Interface Luid : isatap.localdomain +Scope Id : 0.11 +Valid Lifetime : infinite +Preferred Lifetime : infinite +DAD State : Deprecated +Address Type : Other + +Address 2001:0:4137:9e76:2087:77a:53ef:7527 Parameters +--------------------------------------------------------- +Interface Luid : Teredo Tunneling Pseudo-Interface +Scope Id : 0.0 +Valid Lifetime : infinite +Preferred Lifetime : infinite +DAD State : Preferred +Address Type : Public diff --git a/spec/unit/ipaddress6_spec.rb b/spec/unit/ipaddress6_spec.rb index 8e0ccff0c1..0a24029527 100755 --- a/spec/unit/ipaddress6_spec.rb +++ b/spec/unit/ipaddress6_spec.rb @@ -9,6 +9,10 @@ def ifconfig_fixture(filename) ifconfig = File.new(File.join($basedir, 'fixtures', 'ifconfig', filename)).read end +def netsh_fixture(filename) + ifconfig = File.new(File.join($basedir, 'fixtures', 'netsh', filename)).read +end + describe "IPv6 address fact" do before do Facter::Util::Config.stubs(:is_windows?).returns(false) @@ -38,5 +42,14 @@ def ifconfig_fixture(filename) Facter.value(:ipaddress6).should == "2610:10:20:209:203:baff:fe27:a7c" end - it "should return ipaddress6 information for Windows" + it "should return ipaddress6 information for Windows" do + ENV.stubs(:[]).with('SYSTEMROOT').returns('d:/windows') + Facter::Util::Config.stubs(:is_windows?).returns(true) + + fixture = netsh_fixture('windows_netsh_addresses_with_multiple_interfaces') + Facter::Util::Resolution.stubs(:exec).with('d:/windows/system32/netsh interface ipv6 show address level=verbose'). + returns(fixture) + + Facter.value(:ipaddress6).should == "2001:0:4137:9e76:2087:77a:53ef:7527" + end end From 7fb0e6aa9885a1e44496d7e87aefdeeab9241b44 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Mon, 15 Aug 2011 15:55:15 -0700 Subject: [PATCH 0625/3753] (#8439) Add interface-specific ip facts for Windows This commit adds Windows to the list of Facter::Util::IP 'supported_platforms' It adds support for the 'interfaces' fact, e.g. interfaces => Loopback_Pseudo_Interface_1,Local_Area_Connection,Teredo_Tunneling_Pseudo_Interface On Windows, the name of the interface can be edited to include non-alphanumeric characters, which are not valid fact names. This commit changes the alphafy method to ensure the returned value only includes alphanumeric characters (and underscore). And the ipaddress, netmask, network, and ipaddress6 per-interface facts, e.g. ipaddress_local_area_connection => 172.16.138.218 netmask_local_area_connection => 255.255.255.0 network_local_area_connection => 172.16.138.0 ipaddress6_teredo_tunneling_pseudo_interface => 2001:0:4137:9e76:24de:36a7:53ef:7525 Note the per-interface macaddress fact is not yet supported. Also these facts are only supported on 2008, because the output and syntax of netsh is different on 2003. Also 2003 has dependencies on the Routing and Remote Access service, which may not be running. --- lib/facter/util/ip.rb | 29 ++++- spec/unit/data/windows_netsh_all_interfaces | 12 ++ spec/unit/data/windows_netsh_single_interface | 7 ++ .../unit/data/windows_netsh_single_interface6 | 18 +++ spec/unit/interfaces_spec.rb | 7 ++ spec/unit/util/ip_spec.rb | 104 ++++++++++++++---- 6 files changed, 153 insertions(+), 24 deletions(-) create mode 100644 spec/unit/data/windows_netsh_all_interfaces create mode 100644 spec/unit/data/windows_netsh_single_interface create mode 100644 spec/unit/data/windows_netsh_single_interface6 diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index 8b6dbf46f8..7bb6029984 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -27,12 +27,17 @@ module Facter::Util::IP :ipaddress => /\s+inet (\S+)\s.*/, :macaddress => /(\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/, :netmask => /.*\s+netmask (\S+)\s.*/ + }, + :windows => { + :ipaddress => /\s+IP Address:\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, + :ipaddress6 => /Address ((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/, + :netmask => /\s+Subnet Prefix:\s+\S+\s+\(mask ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\)/ } } - # Convert an interface name into purely alpha characters. + # Convert an interface name into purely alphanumeric characters. def self.alphafy(interface) - interface.gsub(/[-:.]/, '_') + interface.gsub(/[^a-z0-9_]/i, '_') end def self.convert_from_hex?(kernel) @@ -55,6 +60,10 @@ def self.supported_platforms def self.get_interfaces return [] unless output = Facter::Util::IP.get_all_interface_output() + # windows interface names contain spaces and are quoted and can appear multiple + # times as ipv4 and ipv6 + return output.scan(/\s* connected\s*(\S.*)/).flatten.uniq if Facter.value(:kernel) == 'windows' + # Our regex appears to be stupid, in that it leaves colons sitting # at the end of interfaces. So, we have to trim those trailing # characters. I tried making the regex better but supporting all @@ -70,6 +79,9 @@ def self.get_all_interface_output output = %x{/usr/sbin/ifconfig -a} when 'HP-UX' output = %x{/bin/netstat -in | sed -e 1d} + when 'windows' + output = %x|#{ENV['SYSTEMROOT']}/system32/netsh interface ip show interface| + output += %x|#{ENV['SYSTEMROOT']}/system32/netsh interface ipv6 show interface| end output end @@ -91,6 +103,17 @@ def self.get_single_interface_output(interface) output end + def self.get_output_for_interface_and_label(interface, label) + return get_single_interface_output(interface) unless Facter.value(:kernel) == 'windows' + + if label == 'ipaddress6' + output = %x|#{ENV['SYSTEMROOT']}/system32/netsh interface ipv6 show address \"#{interface}\"| + else + output = %x|#{ENV['SYSTEMROOT']}/system32/netsh interface ip show address \"#{interface}\"| + end + output + end + def self.get_bonding_master(interface) if Facter.value(:kernel) != 'Linux' return nil @@ -139,7 +162,7 @@ def self.get_interface_value(interface, label) hwaddrre = /^Slave Interface: #{interface}\n[^\n].+?\nPermanent HW addr: (([0-9a-fA-F]{2}:?)*)$/m value = hwaddrre.match(bondinfo.to_s)[1].upcase else - output_int = get_single_interface_output(interface) + output_int = get_output_for_interface_and_label(interface, label) output_int.each_line do |s| if s =~ regex diff --git a/spec/unit/data/windows_netsh_all_interfaces b/spec/unit/data/windows_netsh_all_interfaces new file mode 100644 index 0000000000..ed9a83a441 --- /dev/null +++ b/spec/unit/data/windows_netsh_all_interfaces @@ -0,0 +1,12 @@ +Idx Met MTU State Name +--- ---------- ---------- ------------ --------------------------- + 1 50 4294967295 connected Loopback Pseudo-Interface 1 + 9 10 1500 connected Local Area Connection + + +Idx Met MTU State Name +--- ---------- ---------- ------------ --------------------------- + 1 50 4294967295 connected Loopback Pseudo-Interface 1 + 9 10 1500 connected Local Area Connection + 11 50 1280 disconnected isatap.localdomain + 12 50 1280 connected Teredo Tunneling Pseudo-Interface diff --git a/spec/unit/data/windows_netsh_single_interface b/spec/unit/data/windows_netsh_single_interface new file mode 100644 index 0000000000..f6a8b2bf98 --- /dev/null +++ b/spec/unit/data/windows_netsh_single_interface @@ -0,0 +1,7 @@ +Configuration for interface "Local Area Connection" + DHCP enabled: Yes + IP Address: 172.16.138.216 + Subnet Prefix: 172.16.138.0/24 (mask 255.255.255.0) + Default Gateway: 172.16.138.2 + Gateway Metric: 0 + InterfaceMetric: 10 diff --git a/spec/unit/data/windows_netsh_single_interface6 b/spec/unit/data/windows_netsh_single_interface6 new file mode 100644 index 0000000000..504d226a32 --- /dev/null +++ b/spec/unit/data/windows_netsh_single_interface6 @@ -0,0 +1,18 @@ + +Address fe80::2087:77a:53ef:7527%12 Parameters +--------------------------------------------------------- +Interface Luid : Teredo Tunneling Pseudo-Interface +Scope Id : 0.12 +Valid Lifetime : infinite +Preferred Lifetime : infinite +DAD State : Preferred +Address Type : Other + +Address 2001:0:4137:9e76:2087:77a:53ef:7527 Parameters +--------------------------------------------------------- +Interface Luid : Teredo Tunneling Pseudo-Interface +Scope Id : 0.0 +Valid Lifetime : infinite +Preferred Lifetime : infinite +DAD State : Preferred +Address Type : Public diff --git a/spec/unit/interfaces_spec.rb b/spec/unit/interfaces_spec.rb index cfe4226398..28a8119a64 100755 --- a/spec/unit/interfaces_spec.rb +++ b/spec/unit/interfaces_spec.rb @@ -13,4 +13,11 @@ Facter::Util::IP.stubs(:get_interfaces).returns %w{eth0:1 eth1:2} Facter.fact(:interfaces).value.should == %{eth0_1,eth1_2} end + + it "should replace non-alphanumerics in an interface list with '_'" do + Facter.fact(:kernel).stubs(:value).returns("windows") + + Facter::Util::IP.stubs(:get_interfaces).returns ["Local Area Connection", "Loopback \"Pseudo-Interface\" (#1)"] + Facter.fact(:interfaces).value.should == %{Local_Area_Connection,Loopback__Pseudo_Interface____1_} + end end diff --git a/spec/unit/util/ip_spec.rb b/spec/unit/util/ip_spec.rb index 0374e75c77..9649010a16 100755 --- a/spec/unit/util/ip_spec.rb +++ b/spec/unit/util/ip_spec.rb @@ -5,8 +5,13 @@ require 'facter/util/ip' describe Facter::Util::IP do - [:freebsd, :linux, :netbsd, :openbsd, :sunos, :darwin, :"hp-ux", :"gnu/kfreebsd"].each do |platform| + before :each do + Facter::Util::Config.stubs(:is_windows?).returns(false) + end + + [:freebsd, :linux, :netbsd, :openbsd, :sunos, :darwin, :"hp-ux", :"gnu/kfreebsd", :windows].each do |platform| it "should be supported on #{platform}" do + Facter::Util::Config.stubs(:is_windows?).returns(platform == :windows) Facter::Util::IP.supported_platforms.should be_include(platform) end end @@ -22,39 +27,47 @@ it "should return a list with a single interface and the loopback interface on Linux with a single interface" do sample_output_file = File.dirname(__FILE__) + '/../data/linux_ifconfig_all_with_single_interface' - linux_ifconfig = File.new(sample_output_file).read() + linux_ifconfig = File.read(sample_output_file) Facter::Util::IP.stubs(:get_all_interface_output).returns(linux_ifconfig) Facter::Util::IP.get_interfaces().should == ["eth0", "lo"] end it "should return a list two interfaces on Darwin with two interfaces" do sample_output_file = File.dirname(__FILE__) + '/../data/darwin_ifconfig_all_with_multiple_interfaces' - darwin_ifconfig = File.new(sample_output_file).read() + darwin_ifconfig = File.read(sample_output_file) Facter::Util::IP.stubs(:get_all_interface_output).returns(darwin_ifconfig) Facter::Util::IP.get_interfaces().should == ["lo0", "en0"] end it "should return a list two interfaces on Solaris with two interfaces multiply reporting" do sample_output_file = File.dirname(__FILE__) + '/../data/solaris_ifconfig_all_with_multiple_interfaces' - solaris_ifconfig = File.new(sample_output_file).read() + solaris_ifconfig = File.read(sample_output_file) Facter::Util::IP.stubs(:get_all_interface_output).returns(solaris_ifconfig) Facter::Util::IP.get_interfaces().should == ["lo0", "e1000g0"] end it "should return a list three interfaces on HP-UX with three interfaces multiply reporting" do sample_output_file = File.dirname(__FILE__) + '/../data/hpux_netstat_all_interfaces' - hpux_netstat = File.new(sample_output_file).read() + hpux_netstat = File.read(sample_output_file) Facter::Util::IP.stubs(:get_all_interface_output).returns(hpux_netstat) Facter::Util::IP.get_interfaces().should == ["lan1", "lan0", "lo0"] end it "should return a list of six interfaces on a GNU/kFreeBSD with six interfaces" do sample_output_file = File.dirname(__FILE__) + '/../data/debian_kfreebsd_ifconfig' - kfreebsd_ifconfig = File.new(sample_output_file).read() + kfreebsd_ifconfig = File.read(sample_output_file) Facter::Util::IP.stubs(:get_all_interface_output).returns(kfreebsd_ifconfig) Facter::Util::IP.get_interfaces().should == ["em0", "em1", "bge0", "bge1", "lo0", "vlan0"] end + it "should return a list of only connected interfaces on Windows" do + Facter.fact(:kernel).stubs(:value).returns("windows") + sample_output_file = File.dirname(__FILE__) + '/../data/windows_netsh_all_interfaces' + windows_netsh = File.read(sample_output_file) + Facter::Util::IP.stubs(:get_all_interface_output).returns(windows_netsh) + Facter::Util::IP.get_interfaces().should == ["Loopback Pseudo-Interface 1", "Local Area Connection", "Teredo Tunneling Pseudo-Interface"] + end + it "should return a value for a specific interface" do Facter::Util::IP.should respond_to(:get_interface_value) end @@ -66,7 +79,7 @@ it "should return ipaddress information for Solaris" do sample_output_file = File.dirname(__FILE__) + "/../data/solaris_ifconfig_single_interface" - solaris_ifconfig_interface = File.new(sample_output_file).read() + solaris_ifconfig_interface = File.read(sample_output_file) Facter::Util::IP.expects(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) Facter.stubs(:value).with(:kernel).returns("SunOS") @@ -76,7 +89,7 @@ it "should return netmask information for Solaris" do sample_output_file = File.dirname(__FILE__) + "/../data/solaris_ifconfig_single_interface" - solaris_ifconfig_interface = File.new(sample_output_file).read() + solaris_ifconfig_interface = File.read(sample_output_file) Facter::Util::IP.expects(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) Facter.stubs(:value).with(:kernel).returns("SunOS") @@ -86,7 +99,7 @@ it "should return calculated network information for Solaris" do sample_output_file = File.dirname(__FILE__) + "/../data/solaris_ifconfig_single_interface" - solaris_ifconfig_interface = File.new(sample_output_file).read() + solaris_ifconfig_interface = File.read(sample_output_file) Facter::Util::IP.stubs(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) Facter.stubs(:value).with(:kernel).returns("SunOS") @@ -96,7 +109,7 @@ it "should return ipaddress information for HP-UX" do sample_output_file = File.dirname(__FILE__) + "/../data/hpux_ifconfig_single_interface" - hpux_ifconfig_interface = File.new(sample_output_file).read() + hpux_ifconfig_interface = File.read(sample_output_file) Facter::Util::IP.expects(:get_single_interface_output).with("lan0").returns(hpux_ifconfig_interface) Facter.stubs(:value).with(:kernel).returns("HP-UX") @@ -106,7 +119,7 @@ it "should return macaddress information for HP-UX" do sample_output_file = File.dirname(__FILE__) + "/../data/hpux_ifconfig_single_interface" - hpux_ifconfig_interface = File.new(sample_output_file).read() + hpux_ifconfig_interface = File.read(sample_output_file) Facter::Util::IP.expects(:get_single_interface_output).with("lan0").returns(hpux_ifconfig_interface) Facter.stubs(:value).with(:kernel).returns("HP-UX") @@ -116,7 +129,7 @@ it "should return macaddress with leading zeros stripped off for GNU/kFreeBSD" do sample_output_file = File.dirname(__FILE__) + "/../data/debian_kfreebsd_ifconfig" - kfreebsd_ifconfig = File.new(sample_output_file).read() + kfreebsd_ifconfig = File.read(sample_output_file) Facter::Util::IP.expects(:get_single_interface_output).with("em0").returns(kfreebsd_ifconfig) Facter.stubs(:value).with(:kernel).returns("GNU/kFreeBSD") @@ -126,7 +139,7 @@ it "should return netmask information for HP-UX" do sample_output_file = File.dirname(__FILE__) + "/../data/hpux_ifconfig_single_interface" - hpux_ifconfig_interface = File.new(sample_output_file).read() + hpux_ifconfig_interface = File.read(sample_output_file) Facter::Util::IP.expects(:get_single_interface_output).with("lan0").returns(hpux_ifconfig_interface) Facter.stubs(:value).with(:kernel).returns("HP-UX") @@ -136,7 +149,7 @@ it "should return calculated network information for HP-UX" do sample_output_file = File.dirname(__FILE__) + "/../data/hpux_ifconfig_single_interface" - hpux_ifconfig_interface = File.new(sample_output_file).read() + hpux_ifconfig_interface = File.read(sample_output_file) Facter::Util::IP.stubs(:get_single_interface_output).with("lan0").returns(hpux_ifconfig_interface) Facter.stubs(:value).with(:kernel).returns("HP-UX") @@ -146,7 +159,7 @@ it "should return interface information for FreeBSD supported via an alias" do sample_output_file = File.dirname(__FILE__) + "/../data/6.0-STABLE_FreeBSD_ifconfig" - ifconfig_interface = File.new(sample_output_file).read() + ifconfig_interface = File.read(sample_output_file) Facter::Util::IP.expects(:get_single_interface_output).with("fxp0").returns(ifconfig_interface) Facter.stubs(:value).with(:kernel).returns("FreeBSD") @@ -156,7 +169,7 @@ it "should return macaddress information for OS X" do sample_output_file = File.dirname(__FILE__) + "/../data/Mac_OS_X_10.5.5_ifconfig" - ifconfig_interface = File.new(sample_output_file).read() + ifconfig_interface = File.read(sample_output_file) Facter::Util::IP.expects(:get_single_interface_output).with("en1").returns(ifconfig_interface) Facter.stubs(:value).with(:kernel).returns("Darwin") @@ -166,7 +179,7 @@ it "should return all interfaces correctly on OS X" do sample_output_file = File.dirname(__FILE__) + "/../data/Mac_OS_X_10.5.5_ifconfig" - ifconfig_interface = File.new(sample_output_file).read() + ifconfig_interface = File.read(sample_output_file) Facter::Util::IP.expects(:get_all_interface_output).returns(ifconfig_interface) Facter.stubs(:value).with(:kernel).returns("Darwin") @@ -176,7 +189,7 @@ it "should return a human readable netmask on Solaris" do sample_output_file = File.dirname(__FILE__) + "/../data/solaris_ifconfig_single_interface" - solaris_ifconfig_interface = File.new(sample_output_file).read() + solaris_ifconfig_interface = File.read(sample_output_file) Facter::Util::IP.expects(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) Facter.stubs(:value).with(:kernel).returns("SunOS") @@ -186,7 +199,7 @@ it "should return a human readable netmask on HP-UX" do sample_output_file = File.dirname(__FILE__) + "/../data/hpux_ifconfig_single_interface" - hpux_ifconfig_interface = File.new(sample_output_file).read() + hpux_ifconfig_interface = File.read(sample_output_file) Facter::Util::IP.expects(:get_single_interface_output).with("lan0").returns(hpux_ifconfig_interface) Facter.stubs(:value).with(:kernel).returns("HP-UX") @@ -197,7 +210,7 @@ it "should return a human readable netmask on Darwin" do sample_output_file = File.dirname(__FILE__) + "/../data/darwin_ifconfig_single_interface" - darwin_ifconfig_interface = File.new(sample_output_file).read() + darwin_ifconfig_interface = File.read(sample_output_file) Facter::Util::IP.expects(:get_single_interface_output).with("en1").returns(darwin_ifconfig_interface) Facter.stubs(:value).with(:kernel).returns("Darwin") @@ -208,7 +221,7 @@ it "should return a human readable netmask on GNU/kFreeBSD" do sample_output_file = File.dirname(__FILE__) + "/../data/debian_kfreebsd_ifconfig" - kfreebsd_ifconfig = File.new(sample_output_file).read() + kfreebsd_ifconfig = File.read(sample_output_file) Facter::Util::IP.expects(:get_single_interface_output).with("em1").returns(kfreebsd_ifconfig) Facter.stubs(:value).with(:kernel).returns("GNU/kFreeBSD") @@ -228,10 +241,59 @@ end end + [:windows].each do |platform| + it "should not require conversion from hex on #{platform}" do + Facter::Util::IP.convert_from_hex?(platform).should be_false + end + end + it "should return an arp address on Linux" do Facter.stubs(:value).with(:kernel).returns("Linux") Facter::Util::IP.expects(:get_arp_value).with("eth0").returns("00:00:0c:9f:f0:04") Facter::Util::IP.get_arp_value("eth0").should == "00:00:0c:9f:f0:04" end + + describe "on Windows" do + before :each do + Facter.stubs(:value).with(:kernel).returns("windows") + end + + it "should return ipaddress information" do + sample_output_file = File.dirname(__FILE__) + "/../data/windows_netsh_single_interface" + windows_netsh = File.read(sample_output_file) + + Facter::Util::IP.expects(:get_output_for_interface_and_label).with("Local Area Connection", "ipaddress").returns(windows_netsh) + + Facter::Util::IP.get_interface_value("Local Area Connection", "ipaddress").should == "172.16.138.216" + end + + it "should return a human readable netmask" do + sample_output_file = File.dirname(__FILE__) + "/../data/windows_netsh_single_interface" + windows_netsh = File.read(sample_output_file) + + Facter::Util::IP.expects(:get_output_for_interface_and_label).with("Local Area Connection", "netmask").returns(windows_netsh) + + Facter::Util::IP.get_interface_value("Local Area Connection", "netmask").should == "255.255.255.0" + end + + it "should return network information" do + sample_output_file = File.dirname(__FILE__) + "/../data/windows_netsh_single_interface" + windows_netsh = File.read(sample_output_file) + + Facter::Util::IP.stubs(:get_output_for_interface_and_label).with("Local Area Connection", "ipaddress").returns(windows_netsh) + Facter::Util::IP.stubs(:get_output_for_interface_and_label).with("Local Area Connection", "netmask").returns(windows_netsh) + + Facter::Util::IP.get_network_value("Local Area Connection").should == "172.16.138.0" + end + + it "should return ipaddress6 information" do + sample_output_file = File.dirname(__FILE__) + "/../data/windows_netsh_single_interface6" + windows_netsh = File.read(sample_output_file) + + Facter::Util::IP.expects(:get_output_for_interface_and_label).with("Teredo Tunneling Pseudo-Interface", "ipaddress6").returns(windows_netsh) + + Facter::Util::IP.get_interface_value("Teredo Tunneling Pseudo-Interface", "ipaddress6").should == "2001:0:4137:9e76:2087:77a:53ef:7527" + end + end end From 1414e0b97d2df54cc96e3d1cb3043d73a0e6b790 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Fri, 26 Aug 2011 09:54:05 -0700 Subject: [PATCH 0626/3753] (#9059) is_virtual should be false on vmware_server VMWare server is returned as the value of the virtual fact, but is a hypervisor. Improved the checking for physical hosts in the is_virtual fact. --- lib/facter/virtual.rb | 14 ++++++++------ spec/unit/virtual_spec.rb | 5 +++++ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index a5839951fe..e2c04e5ac8 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -147,9 +147,9 @@ # # Purpose: returning true or false for if a machine is virtualised or not. # -# Resolution: The Xen domain 0 machine is virtualised to a degree, but is generally -# not viewed as being a virtual machine. This checks that the machine is not -# physical nor xen0, if that is the case, it is virtual. +# Resolution: Hypervisors and the like may be detected as a virtual type, but +# are not actual virtual machines, or should not be treated as such. This +# determines if the host is actually virtualized. # # Caveats: # @@ -158,10 +158,12 @@ confine :kernel => %w{Linux FreeBSD OpenBSD SunOS HP-UX Darwin GNU/kFreeBSD} setcode do - if Facter.value(:virtual) != "physical" && Facter.value(:virtual) != "xen0" - "true" - else + physical_types = %w{physical xen0 vmware_server} + + if physical_types.include? Facter.value(:virtual) "false" + else + "true" end end end diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index 68317af4f8..150d693ca8 100644 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -224,4 +224,9 @@ Facter.fact(:virtual).stubs(:value).returns("parallels") Facter.fact(:is_virtual).value.should == "true" end + + it "should be false on vmware_server" do + Facter.fact(:virtual).stubs(:value).returns("vmware_server") + Facter.fact(:is_virtual).value.should == "false" + end end From f8101700a3d478c7cf6a0400583a00d51181b5b3 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Fri, 26 Aug 2011 10:15:38 -0700 Subject: [PATCH 0627/3753] (#7957) is_virtual should be false for openvz host nodes Added the value 'openvzhn' to the list of virtual types that are actually physical. Updated tests to reflect the difference between virtual environments and host nodes. Thanks to Dan Carley for this patch. Signed-off-by: Adrien Thebo --- lib/facter/virtual.rb | 2 +- spec/unit/virtual_spec.rb | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index e2c04e5ac8..ba0ca59299 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -158,7 +158,7 @@ confine :kernel => %w{Linux FreeBSD OpenBSD SunOS HP-UX Darwin GNU/kFreeBSD} setcode do - physical_types = %w{physical xen0 vmware_server} + physical_types = %w{physical xen0 vmware_server openvzhn} if physical_types.include? Facter.value(:virtual) "false" diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index 150d693ca8..995c814d5b 100644 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -182,7 +182,7 @@ Facter.fact(:is_virtual).value.should == "true" end - it "should be true when running on openvz" do + it "should be true when running on openvzve" do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter.fact(:virtual).stubs(:value).returns("openvzve") Facter.fact(:is_virtual).value.should == "true" @@ -229,4 +229,9 @@ Facter.fact(:virtual).stubs(:value).returns("vmware_server") Facter.fact(:is_virtual).value.should == "false" end + + it "should be false on openvz host nodes" do + Facter.fact(:virtual).stubs(:value).returns("openvzhn") + Facter.fact(:is_virtual).value.should == "false" + end end From 0bf827f11a6cce82a009c136a8b4e3f6dbf44c15 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 30 Aug 2011 10:42:51 -0700 Subject: [PATCH 0628/3753] (maint) Add kernel stubbing for is_virtual fact spec The kernel fact needs to be stubbed out since the virtual fact does not yet support windows. --- spec/unit/virtual_spec.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index 995c814d5b..b6e1414435 100644 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -226,11 +226,13 @@ end it "should be false on vmware_server" do + Facter.fact(:kernel).stubs(:value).returns("Linux") Facter.fact(:virtual).stubs(:value).returns("vmware_server") Facter.fact(:is_virtual).value.should == "false" end it "should be false on openvz host nodes" do + Facter.fact(:kernel).stubs(:value).returns("Linux") Facter.fact(:virtual).stubs(:value).returns("openvzhn") Facter.fact(:is_virtual).value.should == "false" end From 3eb3628749911669fb61e9c53a66885f7da36234 Mon Sep 17 00:00:00 2001 From: Andrew Elwell Date: Wed, 31 Aug 2011 19:19:01 +0200 Subject: [PATCH 0629/3753] Add Scientific Linux CERN detection to facter. Fixes #9260 Adds the SLC operatingsystem fact, and adds the SLC variant to all locations that Scientific Linux is specified. Added additional unit tests to verify that SLC would not be confused with Scientific Linux. Reviewed-by: Adrien Thebo --- lib/facter/hardwareisa.rb | 2 +- lib/facter/lsbmajdistrelease.rb | 2 +- lib/facter/macaddress.rb | 2 +- lib/facter/operatingsystem.rb | 2 ++ lib/facter/operatingsystemrelease.rb | 4 ++-- lib/facter/uniqueid.rb | 2 +- spec/unit/operatingsystem_spec.rb | 18 ++++++++++++++++++ 7 files changed, 26 insertions(+), 6 deletions(-) diff --git a/lib/facter/hardwareisa.rb b/lib/facter/hardwareisa.rb index d122d81e0a..f1f93079f5 100644 --- a/lib/facter/hardwareisa.rb +++ b/lib/facter/hardwareisa.rb @@ -12,5 +12,5 @@ Facter.add(:hardwareisa) do setcode 'uname -p' - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS Scientific SuSE SLES Debian Ubuntu Gentoo FreeBSD OpenBSD NetBSD OEL OVS GNU/kFreeBSD} + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS Scientific SLC SuSE SLES Debian Ubuntu Gentoo FreeBSD OpenBSD NetBSD OEL OVS GNU/kFreeBSD} end diff --git a/lib/facter/lsbmajdistrelease.rb b/lib/facter/lsbmajdistrelease.rb index 4fa68e9396..3b2b6cde3a 100644 --- a/lib/facter/lsbmajdistrelease.rb +++ b/lib/facter/lsbmajdistrelease.rb @@ -15,7 +15,7 @@ require 'facter' Facter.add("lsbmajdistrelease") do - confine :operatingsystem => %w{Linux Fedora RedHat CentOS Scientific SuSE SLES Debian Ubuntu Gentoo OEL OVS GNU/kFreeBSD} + confine :operatingsystem => %w{Linux Fedora RedHat CentOS Scientific SLC SuSE SLES Debian Ubuntu Gentoo OEL OVS GNU/kFreeBSD} setcode do if /(\d*)\./i =~ Facter.value(:lsbdistrelease) result=$1 diff --git a/lib/facter/macaddress.rb b/lib/facter/macaddress.rb index ba8174d59d..0e04fea67b 100644 --- a/lib/facter/macaddress.rb +++ b/lib/facter/macaddress.rb @@ -10,7 +10,7 @@ require 'facter/util/macaddress' Facter.add(:macaddress) do - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS Scientific SuSE SLES Debian Gentoo Ubuntu OEL OVS GNU/kFreeBSD} + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS Scientific SLC SuSE SLES Debian Gentoo Ubuntu OEL OVS GNU/kFreeBSD} setcode do ether = [] output = %x{/sbin/ifconfig -a} diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index 396e52ba60..c490c964a0 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -51,6 +51,8 @@ txt = File.read("/etc/redhat-release") if txt =~ /centos/i "CentOS" + elsif txt =~ /CERN/ + "SLC" elsif txt =~ /scientific/i "Scientific" else diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index 97085eb319..b0e85fae59 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -16,10 +16,10 @@ # Facter.add(:operatingsystemrelease) do - confine :operatingsystem => %w{CentOS Fedora oel ovs RedHat MeeGo Scientific} + confine :operatingsystem => %w{CentOS Fedora oel ovs RedHat MeeGo Scientific SLC} setcode do case Facter.value(:operatingsystem) - when "CentOS", "RedHat", "Scientific" + when "CentOS", "RedHat", "Scientific", "SLC" releasefile = "/etc/redhat-release" when "Fedora" releasefile = "/etc/fedora-release" diff --git a/lib/facter/uniqueid.rb b/lib/facter/uniqueid.rb index 713d0a9202..2a661a12b7 100644 --- a/lib/facter/uniqueid.rb +++ b/lib/facter/uniqueid.rb @@ -1,4 +1,4 @@ Facter.add(:uniqueid) do setcode 'hostid' - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS Scientific SuSE SLES Debian Ubuntu Gentoo AIX OEL OVS GNU/kFreeBSD} + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS Scientific SLC SuSE SLES Debian Ubuntu Gentoo AIX OEL OVS GNU/kFreeBSD} end diff --git a/spec/unit/operatingsystem_spec.rb b/spec/unit/operatingsystem_spec.rb index 51540cb6be..89904fe3d5 100755 --- a/spec/unit/operatingsystem_spec.rb +++ b/spec/unit/operatingsystem_spec.rb @@ -62,4 +62,22 @@ Facter.fact(:operatingsystem).value.should == "Alpine" end + + it "should identify Scientific Linux" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + FileTest.stubs(:exists?).returns false + + FileTest.expects(:exists?).with("/etc/redhat-release").returns true + File.expects(:read).with("/etc/redhat-release").returns("Scientific Linux SLC 5.7 (Boron)") + Facter.fact(:operatingsystem).value.should == "Scientific" + end + + it "should differentiate between Scientific Linux CERN and Scientific Linux" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + FileTest.stubs(:exists?).returns false + + FileTest.expects(:exists?).with("/etc/redhat-release").returns true + File.expects(:read).with("/etc/redhat-release").returns("Scientific Linux CERN SLC 5.7 (Boron)") + Facter.fact(:operatingsystem).value.should == "SLC" + end end From 241cddcd376113b51912c8812a49a83f28845511 Mon Sep 17 00:00:00 2001 From: Max Riveiro Date: Mon, 29 Aug 2011 00:29:12 +0400 Subject: [PATCH 0630/3753] (#6610) Fix rSpec output format rSpec 2 now respects .rspec file for it's options. There is no such "--loadby" option in rSpec 2, so it was deleted. --- .rspec | 2 ++ spec/spec.opts | 5 ----- 2 files changed, 2 insertions(+), 5 deletions(-) create mode 100644 .rspec delete mode 100644 spec/spec.opts diff --git a/.rspec b/.rspec new file mode 100644 index 0000000000..4f1425c11c --- /dev/null +++ b/.rspec @@ -0,0 +1,2 @@ +--format s +--colour diff --git a/spec/spec.opts b/spec/spec.opts deleted file mode 100644 index 695852c2f7..0000000000 --- a/spec/spec.opts +++ /dev/null @@ -1,5 +0,0 @@ ---format -s ---colour ---loadby -mtime From ec04277b1e7d20b0bf38d5ae736db4cad6972066 Mon Sep 17 00:00:00 2001 From: Michael Kincaid Date: Tue, 6 Sep 2011 13:08:27 -0700 Subject: [PATCH 0631/3753] (#4228) Ensure MAC address octets have leading zeroes. Implements Facter::Util::Macaddress.standardize and passes output values to it to ensure MAC addresses are output in a standard format. Adds some basic test coverage for macaddress.rb. --- lib/facter/macaddress.rb | 12 +++++----- lib/facter/util/macaddress.rb | 4 ++++ spec/unit/macaddress_spec.rb | 37 +++++++++++++++++++++++++++++++ spec/unit/util/macaddress_spec.rb | 10 +++++++++ 4 files changed, 57 insertions(+), 6 deletions(-) create mode 100755 spec/unit/macaddress_spec.rb diff --git a/lib/facter/macaddress.rb b/lib/facter/macaddress.rb index 0e04fea67b..cc0bf38853 100644 --- a/lib/facter/macaddress.rb +++ b/lib/facter/macaddress.rb @@ -13,11 +13,11 @@ confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS Scientific SLC SuSE SLES Debian Gentoo Ubuntu OEL OVS GNU/kFreeBSD} setcode do ether = [] - output = %x{/sbin/ifconfig -a} + output = Facter::Util::Resolution.exec("/sbin/ifconfig -a") output.each_line do |s| ether.push($1) if s =~ /(?:ether|HWaddr) (\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/ end - ether[0] + Facter::Util::Macaddress.standardize(ether[0]) end end @@ -29,7 +29,7 @@ output.each_line do |s| ether.push($1) if s =~ /(?:SPLA)\s+(\w{2}:\w{2}:\w{2}:\w{2}:\w{2}:\w{2})/ end - ether[0] + Facter::Util::Macaddress.standardize(ether[0]) end end @@ -37,13 +37,13 @@ confine :operatingsystem => %w{FreeBSD OpenBSD} setcode do ether = [] - output = %x{/sbin/ifconfig} + output = Facter::Util::Resolution.exec("/sbin/ifconfig") output.each_line do |s| if s =~ /(?:ether|lladdr)\s+(\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ ether.push($1) end end - ether[0] + Facter::Util::Macaddress.standardize(ether[0]) end end @@ -71,7 +71,7 @@ end end end - ether[0] + Facter::Util::Macaddress.standardize(ether[0]) end end diff --git a/lib/facter/util/macaddress.rb b/lib/facter/util/macaddress.rb index 4d7a478225..8c4a561826 100644 --- a/lib/facter/util/macaddress.rb +++ b/lib/facter/util/macaddress.rb @@ -2,6 +2,10 @@ # module Facter::Util::Macaddress + def self.standardize(macaddress) + macaddress.split(":").map{|x| "0#{x}"[-2..-1]}.join(":") + end + module Darwin def self.macaddress iface = default_interface diff --git a/spec/unit/macaddress_spec.rb b/spec/unit/macaddress_spec.rb new file mode 100755 index 0000000000..9411e26604 --- /dev/null +++ b/spec/unit/macaddress_spec.rb @@ -0,0 +1,37 @@ +#!/usr/bin/env ruby + +$basedir = File.expand_path(File.dirname(__FILE__) + '/..') +require File.join($basedir, 'spec_helper') + +require 'facter' + +def ifconfig_fixture(filename) + ifconfig = File.new(File.join($basedir, 'fixtures', 'ifconfig', filename)).read +end + +def netsh_fixture(filename) + ifconfig = File.new(File.join($basedir, 'fixtures', 'netsh', filename)).read +end + +describe "macaddress fact" do + before do + Facter::Util::Config.stubs(:is_windows?).returns(false) + end + + it "should return macaddress information for Linux" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter::Util::Resolution.stubs(:exec).with('/sbin/ifconfig -a'). + returns(ifconfig_fixture('linux_ifconfig_all_with_multiple_interfaces')) + + Facter.value(:macaddress).should == "00:12:3f:be:22:01" + end + + it "should return macaddress information for BSD" do + Facter.fact(:kernel).stubs(:value).returns("FreeBSD") + Facter::Util::Resolution.stubs(:exec).with('/sbin/ifconfig'). + returns(ifconfig_fixture('bsd_ifconfig_all_with_multiple_interfaces')) + + Facter.value(:macaddress).should == "00:0b:db:93:09:67" + end + +end diff --git a/spec/unit/util/macaddress_spec.rb b/spec/unit/util/macaddress_spec.rb index edaeff13c2..2448f29079 100755 --- a/spec/unit/util/macaddress_spec.rb +++ b/spec/unit/util/macaddress_spec.rb @@ -4,6 +4,16 @@ require 'facter/util/macaddress' +describe "standardized MAC address" do + it "should have zeroes added if missing" do + Facter::Util::Macaddress::standardize("0:ab:cd:e:12:3").should == "00:ab:cd:0e:12:03" + end + + it "should be identical if each octet already has two digits" do + Facter::Util::Macaddress::standardize("00:ab:cd:0e:12:03").should == "00:ab:cd:0e:12:03" + end +end + describe "Darwin", :unless => Facter.value(:operatingsystem) == 'windows' do test_cases = [ # version, iface, real macaddress, fallback macaddress From 1cb9cb6b146167d9e0a99a678f886653ee5dc115 Mon Sep 17 00:00:00 2001 From: Max Riveiro Date: Mon, 29 Aug 2011 00:37:00 +0400 Subject: [PATCH 0632/3753] (#6610) Fix Autotest proper run Autotest can be configured through the .autotest file. Also, Autotest already has an rspec2 discovery. --- autotest/facter_rspec.rb => .autotest | 10 +--- autotest/discover.rb | 9 ---- autotest/rspec.rb | 74 --------------------------- 3 files changed, 1 insertion(+), 92 deletions(-) rename autotest/facter_rspec.rb => .autotest (80%) delete mode 100644 autotest/discover.rb delete mode 100644 autotest/rspec.rb diff --git a/autotest/facter_rspec.rb b/.autotest similarity index 80% rename from autotest/facter_rspec.rb rename to .autotest index 90e646d81c..08d16d3280 100644 --- a/autotest/facter_rspec.rb +++ b/.autotest @@ -1,5 +1,4 @@ -require 'autotest' -require 'autotest/rspec' +Autotest.add_discovery { "rspec2" } Autotest.add_hook :initialize do |at| at.clear_mappings @@ -36,10 +35,3 @@ at.files_matching %r!spec/(unit|integration)/.*\.rb! } end - -class Autotest::FacterRspec < Autotest::Rspec - def spec_commands - ENV["AUTOTEST"] = "true" - ENV["PATH"].split(File::PATH_SEPARATOR).collect { |dir| File.join(dir, "spec") } - end -end diff --git a/autotest/discover.rb b/autotest/discover.rb deleted file mode 100644 index 030d07f559..0000000000 --- a/autotest/discover.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'autotest' - -Autotest.add_discovery do - "rspec" -end - -Autotest.add_discovery do - "facter" -end diff --git a/autotest/rspec.rb b/autotest/rspec.rb deleted file mode 100644 index e395dfecf6..0000000000 --- a/autotest/rspec.rb +++ /dev/null @@ -1,74 +0,0 @@ -require 'autotest' - -Autotest.add_hook :initialize do |at| - at.clear_mappings - # watch out: Ruby bug (1.8.6): - # %r(/) != /\// - at.add_mapping(%r%^spec/.*\.rb$%) { |filename, _| - filename - } - at.add_mapping(%r%^lib/(.*)\.rb$%) { |_, m| - ["spec/#{m[1]}_spec.rb"] - } - at.add_mapping(%r%^spec/(spec_helper|shared/.*)\.rb$%) { - at.files_matching %r{^spec/.*_spec\.rb$} - } -end - -class RspecCommandError < StandardError; end - -class Autotest::Rspec < Autotest - - def initialize - super - - self.failed_results_re = /^\d+\)\n(?:\e\[\d*m)?(?:.*?Error in )?'([^\n]*)'(?: FAILED)?(?:\e\[\d*m)?\n(.*?)\n\n/m - self.completed_re = /\Z/ # FIX: some sort of summary line at the end? - end - - def consolidate_failures(failed) - filters = Hash.new { |h,k| h[k] = [] } - failed.each do |spec, failed_trace| - if f = test_files_for(failed).find { |f| failed_trace =~ Regexp.new(f) } then - filters[f] << spec - break - end - end - return filters - end - - def make_test_cmd(files_to_test) - return "#{ruby} -S #{spec_command} #{add_options_if_present} #{files_to_test.keys.flatten.join(' ')}" - end - - def add_options_if_present - File.exist?("spec/spec.opts") ? "-O spec/spec.opts " : "" - end - - # Finds the proper spec command to use. Precendence is set in the - # lazily-evaluated method spec_commands. Alias + Override that in - # ~/.autotest to provide a different spec command then the default - # paths provided. - def spec_command(separator=File::ALT_SEPARATOR) - unless defined? @spec_command then - @spec_command = spec_commands.find { |cmd| File.exists? cmd } - - raise RspecCommandError, "No spec command could be found!" unless @spec_command - - @spec_command.gsub! File::SEPARATOR, separator if separator - end - @spec_command - end - - # Autotest will look for spec commands in the following - # locations, in this order: - # - # * bin/spec - # * default spec bin/loader installed in Rubygems - def spec_commands - [ - File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'bin', 'spec')), - File.join(Config::CONFIG['bindir'], 'spec') - ] - end -end From d55983e3a44ce9431e8c6e03928ae1810f54096d Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 6 Sep 2011 17:10:23 -0700 Subject: [PATCH 0633/3753] (#9178) Add Oracle Linux identification Adds support for Oracle Linux identification and enables facts that are confined by the operatingsystem fact. Reviewed-by: Hunter Haugen --- lib/facter/hardwareisa.rb | 2 +- lib/facter/lsbmajdistrelease.rb | 2 +- lib/facter/macaddress.rb | 2 +- lib/facter/operatingsystem.rb | 2 ++ lib/facter/operatingsystemrelease.rb | 4 +++- lib/facter/uniqueid.rb | 2 +- spec/unit/operatingsystemrelease_spec.rb | 19 ++++++++++--------- 7 files changed, 19 insertions(+), 14 deletions(-) diff --git a/lib/facter/hardwareisa.rb b/lib/facter/hardwareisa.rb index f1f93079f5..cf29f23f29 100644 --- a/lib/facter/hardwareisa.rb +++ b/lib/facter/hardwareisa.rb @@ -12,5 +12,5 @@ Facter.add(:hardwareisa) do setcode 'uname -p' - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS Scientific SLC SuSE SLES Debian Ubuntu Gentoo FreeBSD OpenBSD NetBSD OEL OVS GNU/kFreeBSD} + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS Scientific SLC SuSE SLES Debian Ubuntu Gentoo FreeBSD OpenBSD NetBSD OEL OracleLinux OVS GNU/kFreeBSD} end diff --git a/lib/facter/lsbmajdistrelease.rb b/lib/facter/lsbmajdistrelease.rb index 3b2b6cde3a..1d9715870c 100644 --- a/lib/facter/lsbmajdistrelease.rb +++ b/lib/facter/lsbmajdistrelease.rb @@ -15,7 +15,7 @@ require 'facter' Facter.add("lsbmajdistrelease") do - confine :operatingsystem => %w{Linux Fedora RedHat CentOS Scientific SLC SuSE SLES Debian Ubuntu Gentoo OEL OVS GNU/kFreeBSD} + confine :operatingsystem => %w{Linux Fedora RedHat CentOS Scientific SLC SuSE SLES Debian Ubuntu Gentoo OEL OracleLinux OVS GNU/kFreeBSD} setcode do if /(\d*)\./i =~ Facter.value(:lsbdistrelease) result=$1 diff --git a/lib/facter/macaddress.rb b/lib/facter/macaddress.rb index 0e04fea67b..754f7c68e2 100644 --- a/lib/facter/macaddress.rb +++ b/lib/facter/macaddress.rb @@ -10,7 +10,7 @@ require 'facter/util/macaddress' Facter.add(:macaddress) do - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS Scientific SLC SuSE SLES Debian Gentoo Ubuntu OEL OVS GNU/kFreeBSD} + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS Scientific SLC SuSE SLES Debian Gentoo Ubuntu OEL OracleLinux OVS GNU/kFreeBSD} setcode do ether = [] output = %x{/sbin/ifconfig -a} diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index c490c964a0..ff802c3652 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -37,6 +37,8 @@ "MeeGo" elsif FileTest.exists?("/etc/arch-release") "Archlinux" + elsif FileTest.exists?("/etc/oracle-release") + "OracleLinux" elsif FileTest.exists?("/etc/enterprise-release") if FileTest.exists?("/etc/ovs-release") "OVS" diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index b0e85fae59..b1fa571d79 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -16,7 +16,7 @@ # Facter.add(:operatingsystemrelease) do - confine :operatingsystem => %w{CentOS Fedora oel ovs RedHat MeeGo Scientific SLC} + confine :operatingsystem => %w{CentOS Fedora oel ovs OracleLinux RedHat MeeGo Scientific SLC} setcode do case Facter.value(:operatingsystem) when "CentOS", "RedHat", "Scientific", "SLC" @@ -25,6 +25,8 @@ releasefile = "/etc/fedora-release" when "MeeGo" releasefile = "/etc/meego-release" + when "OracleLinux" + releasefile = "/etc/oracle-release" when "OEL", "oel" releasefile = "/etc/enterprise-release" when "OVS", "ovs" diff --git a/lib/facter/uniqueid.rb b/lib/facter/uniqueid.rb index 2a661a12b7..ac130034a3 100644 --- a/lib/facter/uniqueid.rb +++ b/lib/facter/uniqueid.rb @@ -1,4 +1,4 @@ Facter.add(:uniqueid) do setcode 'hostid' - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS Scientific SLC SuSE SLES Debian Ubuntu Gentoo AIX OEL OVS GNU/kFreeBSD} + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS Scientific SLC SuSE SLES Debian Ubuntu Gentoo AIX OEL OracleLinux OVS GNU/kFreeBSD} end diff --git a/spec/unit/operatingsystemrelease_spec.rb b/spec/unit/operatingsystemrelease_spec.rb index 33b3c7f034..056f5f52b0 100755 --- a/spec/unit/operatingsystemrelease_spec.rb +++ b/spec/unit/operatingsystemrelease_spec.rb @@ -15,15 +15,16 @@ end test_cases = { - "CentOS" => "/etc/redhat-release", - "RedHat" => "/etc/redhat-release", - "Scientific" => "/etc/redhat-release", - "Fedora" => "/etc/fedora-release", - "MeeGo" => "/etc/meego-release", - "OEL" => "/etc/enterprise-release", - "oel" => "/etc/enterprise-release", - "OVS" => "/etc/ovs-release", - "ovs" => "/etc/ovs-release", + "CentOS" => "/etc/redhat-release", + "RedHat" => "/etc/redhat-release", + "Scientific" => "/etc/redhat-release", + "Fedora" => "/etc/fedora-release", + "MeeGo" => "/etc/meego-release", + "OEL" => "/etc/enterprise-release", + "oel" => "/etc/enterprise-release", + "OVS" => "/etc/ovs-release", + "ovs" => "/etc/ovs-release", + "OracleLinux" => "/etc/oracle-release", } test_cases.each do |system, file| From d28d96c98d9547fc8b50fc671f0985e52be18168 Mon Sep 17 00:00:00 2001 From: Michael Kincaid Date: Thu, 5 May 2011 10:25:55 -0700 Subject: [PATCH 0634/3753] (#4508) Xen HVM domU not detected as virtual --- lib/facter/virtual.rb | 5 +++++ spec/unit/virtual_spec.rb | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index ba0ca59299..e08f49140c 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -110,6 +110,9 @@ # --- look for pci vendor id used by Parallels video card # --- 01:00.0 VGA compatible controller: Unknown device 1ab8:4005 result = "parallels" if p =~ /1ab8:|[Pp]arallels/ + # --- look for pci vendor id used by Xen HVM device + # --- 00:03.0 Unassigned class [ff80]: XenSource, Inc. Xen Platform Device (rev 01) + result = "xenhvm" if p =~ /XenSource/ end else output = Facter::Util::Resolution.exec('dmidecode') @@ -118,6 +121,7 @@ result = "parallels" if pd =~ /Parallels/ result = "vmware" if pd =~ /VMware/ result = "virtualbox" if pd =~ /VirtualBox/ + result = "xenhvm" if pd =~ /HVM domU/ end elsif Facter.value(:kernel) == 'SunOS' res = Facter::Util::Resolution.new('prtdiag') @@ -129,6 +133,7 @@ result = "parallels" if pd =~ /Parallels/ result = "vmware" if pd =~ /VMware/ result = "virtualbox" if pd =~ /VirtualBox/ + result = "xenhvm" if pd =~ /HVM domU/ end end end diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index b6e1414435..13bb764464 100644 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -108,6 +108,19 @@ Facter.fact(:virtual).value.should == "vmware" end + it "should be xenhvm with Xen HVM vendor name from lspci" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter::Util::Resolution.stubs(:exec).with('lspci').returns("00:03.0 Unassigned class [ff80]: XenSource, Inc. Xen Platform Device (rev 01)") + Facter.fact(:virtual).value.should == "xenhvm" + end + + it "should be xenhvm with Xen HVM vendor name from dmidecode" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) + Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("System Information\nManufacturer: Xen\nProduct Name: HVM domU") + Facter.fact(:virtual).value.should == "xenhvm" + end + it "should be parallels with Parallels vendor name from dmidecode" do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) @@ -164,6 +177,12 @@ Facter.fact(:is_virtual).value.should == "false" end + it "should be true when running on xenhvm" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:virtual).stubs(:value).returns("xenhvm") + Facter.fact(:is_virtual).value.should == "true" + end + it "should be false when running on physical" do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter.fact(:virtual).stubs(:value).returns("physical") From 6d47012d59183ac2adc07eca918ef97bdde0946b Mon Sep 17 00:00:00 2001 From: Michael Kincaid Date: Thu, 12 May 2011 10:22:50 -0700 Subject: [PATCH 0635/3753] (#4869) Implement productname as Darwin hw.model --- lib/facter/manufacturer.rb | 5 +++++ lib/facter/util/manufacturer.rb | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/facter/manufacturer.rb b/lib/facter/manufacturer.rb index 26aef5fe08..98ed29e610 100644 --- a/lib/facter/manufacturer.rb +++ b/lib/facter/manufacturer.rb @@ -25,6 +25,11 @@ 'hw.serialno' => 'serialnumber' } + Facter::Manufacturer.sysctl_find_system_info(mfg_keys) +elsif Facter.value(:kernel) == "Darwin" + mfg_keys = { + 'hw.model' => 'productname' + } Facter::Manufacturer.sysctl_find_system_info(mfg_keys) elsif Facter.value(:kernel) == "SunOS" and Facter.value(:hardwareisa) == "sparc" Facter::Manufacturer.prtdiag_sparc_find_system_info() diff --git a/lib/facter/util/manufacturer.rb b/lib/facter/util/manufacturer.rb index 7ab4067115..9beb2b42bd 100644 --- a/lib/facter/util/manufacturer.rb +++ b/lib/facter/util/manufacturer.rb @@ -53,7 +53,7 @@ def self.dmi_find_system_info(name) def self.sysctl_find_system_info(name) name.each do |sysctlkey,facterkey| Facter.add(facterkey) do - confine :kernel => :openbsd + confine :kernel => [:openbsd, :darwin] setcode do Facter::Util::Resolution.exec("sysctl -n #{sysctlkey} 2>/dev/null") end From 42c547161f55250ed69e3520ae38723bb01e80a2 Mon Sep 17 00:00:00 2001 From: Matthaus Litteken Date: Wed, 7 Sep 2011 16:54:50 -0700 Subject: [PATCH 0636/3753] Updated CHANGELOG for 1.6.1rc1. --- CHANGELOG | 38 ++++++++++++++++++++++++++++++++++++++ lib/facter.rb | 2 +- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index d43323e328..73b9e2afe8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,41 @@ +1.6.1rc1 +=== +6d47012 (#4869) Implement productname as Darwin hw.model +d28d96c (#4508) Xen HVM domU not detected as virtual +d55983e (#9178) Add Oracle Linux identification +1cb9cb6 (#6610) Fix Autotest proper run +ec04277 (#4228) Ensure MAC address octets have leading zeroes. +241cddc (#6610) Fix rSpec output format +3eb3628 Add Scientific Linux CERN detection to facter. Fixes #9260 +0bf827f (maint) Add kernel stubbing for is_virtual fact spec +f810170 (#7957) is_virtual should be false for openvz host nodes +1414e0b (#9059) is_virtual should be false on vmware_server +7fb0e6a (#8439) Add interface-specific ip facts for Windows +5d5848c (#8439) Add ipaddress6 fact on Windows +7531a2b (#8439) Add ps fact on Windows +ddb67c5 (#8439) Move macaddress resolution on Windows +0721f2f (#7682) Add complete support for Scientific Linux +a347920 (#9183) Add support for Alpine linux detection +824fac0 (#8439) Add physicalprocessorcount and processor facts on Windows +9ef56d6 (#8439) Implement total and free physical memory on Windows +b3e2274 (#8439) Add Facter::Util::WMI module +00bed7a (#8964) Search mountinfo for selinux mount point +cceb74b (maint) Add stubbing and corrections for domain spec +f7daae3 (maint) Remove global var from domain and hostname +e8d00ec (#8964) Search mountinfo for selinux mount point +46cbd68 Fix the SPEC file (COPYING no longer shipped, README -> README.md) +c5d63d4 Fix #2766 - silence unknown sysctl values +2ba8e7b Add document outlining preferred contribution methods +5d9cc84 (#8660) Fix destdir option on Windows +e329450 (#8247) Fixing arp DNS timeout issue. +bdd9e39 Maint: Fix tests to run on Windows +c1b631d Maint: Refactor detection of windows platform +9b7a41d Maint: Deprecate facter resolution interpreter parameter +0356a2a Maint: Fix facter install on Windows +15d0406 use each_line instead of each for strings in ruby 1.9 +08b3f77 (#7854) Add Augeas library version fact +e84c051 Fixed #7307 - Added serial number fact to Solaris + 1.6.0 === 9404a7a (#7670) Add an acceptance test diff --git a/lib/facter.rb b/lib/facter.rb index 772a9007bf..879c4714c2 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -24,7 +24,7 @@ module Util; end include Comparable include Enumerable - FACTERVERSION = '1.6.0' + FACTERVERSION = '1.6.1rc' # = Facter # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. From 6c098cc1118a3b30f252e1b106f021b2a86153b4 Mon Sep 17 00:00:00 2001 From: Matthaus Litteken Date: Thu, 8 Sep 2011 11:47:36 -0700 Subject: [PATCH 0637/3753] Rakefile should fail on error To ensure tests that fail result in a failure for the run, fail_on_error should be set to true in the rakefile. Signed-off-by: Matthaus Litteken --- Rakefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Rakefile b/Rakefile index 9d7d90614e..1b9cb61587 100644 --- a/Rakefile +++ b/Rakefile @@ -11,7 +11,7 @@ begin rescue LoadError end -Dir['tasks/**/*.rake'].each { |t| load t } +Dir['tasks/**/*.rake'].each { |t| load t } require 'rake' require 'rake/packagetask' @@ -64,12 +64,12 @@ end RSpec::Core::RakeTask.new do |t| t.pattern ='spec/{unit,integration}/**/*_spec.rb' - t.fail_on_error = false + t.fail_on_error = true end RSpec::Core::RakeTask.new('spec:rcov') do |t| t.pattern ='spec/{unit,integration}/**/*_spec.rb' - t.fail_on_error = false + t.fail_on_error = true if defined?(Rcov) t.rcov = true t.rcov_opts = ['--exclude', 'spec/*,test/*,results/*,/usr/lib/*,/usr/local/lib/*,gems/*'] From f4ad6bfcca5612be3fdb6252a79b1025f514f407 Mon Sep 17 00:00:00 2001 From: Matthaus Litteken Date: Thu, 8 Sep 2011 11:53:44 -0700 Subject: [PATCH 0638/3753] Macaddress spec test needed operatingsystem fact The linux macaddress test needed the operating system fact stubbed out. Otherwise it was making an unexpected call to lsb_release. Signed-off-by: Matthaus Litteken --- spec/unit/macaddress_spec.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spec/unit/macaddress_spec.rb b/spec/unit/macaddress_spec.rb index 9411e26604..ef80baa07b 100755 --- a/spec/unit/macaddress_spec.rb +++ b/spec/unit/macaddress_spec.rb @@ -20,12 +20,13 @@ def netsh_fixture(filename) it "should return macaddress information for Linux" do Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:operatingsystem).stubs(:value).returns("Linux") Facter::Util::Resolution.stubs(:exec).with('/sbin/ifconfig -a'). returns(ifconfig_fixture('linux_ifconfig_all_with_multiple_interfaces')) Facter.value(:macaddress).should == "00:12:3f:be:22:01" end - + it "should return macaddress information for BSD" do Facter.fact(:kernel).stubs(:value).returns("FreeBSD") Facter::Util::Resolution.stubs(:exec).with('/sbin/ifconfig'). @@ -33,5 +34,5 @@ def netsh_fixture(filename) Facter.value(:macaddress).should == "00:0b:db:93:09:67" end - + end From 8bed5bbe16aaaa7f3c5e4ce3d6dd66bfcd2aee12 Mon Sep 17 00:00:00 2001 From: Matthaus Litteken Date: Thu, 8 Sep 2011 11:51:17 -0700 Subject: [PATCH 0639/3753] Clear messages between test runs If the spec tests are run more than once in a row, some tests will fail because messages are not reset between tests. This adds a method to facter.rb to clear_messages and calls it before each test. Also, facter version fixed to not include a string. Signed-off-by: Matthaus Litteken --- lib/facter.rb | 8 +++++++- spec/spec_helper.rb | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/facter.rb b/lib/facter.rb index 879c4714c2..d862cd3c5d 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -24,7 +24,7 @@ module Util; end include Comparable include Enumerable - FACTERVERSION = '1.6.1rc' + FACTERVERSION = '1.6.1' # = Facter # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. @@ -159,6 +159,12 @@ def self.clear Facter.reset end + # Clear all messages. Used only in testing. Can't add to self.clear + # because we don't want to warn multiple times for items that are warnonce'd + def self.clear_messages + @@messages.clear + end + # Set debugging on or off. def self.debugging(bit) if bit diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 483d4dc803..19da71b385 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -20,5 +20,6 @@ config.before :each do Facter::Util::Loader.any_instance.stubs(:load_all) Facter.clear + Facter.clear_messages end end From 7457fe535a045740e7231ee2723c268f4be97557 Mon Sep 17 00:00:00 2001 From: Matthaus Litteken Date: Thu, 8 Sep 2011 14:30:03 -0700 Subject: [PATCH 0640/3753] SELinux spec test fix for ubuntu On systems with a /proc/self/mountinfo but without an selinuxfs filesystem, the "should return an SELinux policy version" test would fail. This patch stubs it so this file doesn't exist, and the selinux mount point is correctly calculated. Signed-off-by: Matthaus Litteken --- spec/unit/selinux_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/unit/selinux_spec.rb b/spec/unit/selinux_spec.rb index d8209587ad..5d098976e3 100755 --- a/spec/unit/selinux_spec.rb +++ b/spec/unit/selinux_spec.rb @@ -38,6 +38,7 @@ it "should return an SELinux policy version" do Facter.fact(:selinux).stubs(:value).returns("true") + FileTest.stubs(:exists?).with("/proc/self/mountinfo").returns false File.stubs(:read).with("/selinux/policyvers").returns("") From 88f343cb26fca2cab1983ccc2eddb9993248e8b2 Mon Sep 17 00:00:00 2001 From: Matthaus Litteken Date: Thu, 8 Sep 2011 14:34:32 -0700 Subject: [PATCH 0641/3753] (#2344) VMware version parsing fix VMware detection should not be file base. This will cause workstation to be incorrectly dectected as server. This implements the fix suggested, using 'vmware -v' to grab the correct vmware version. It also fixed the spec tests to correctly expect the vmware -v exec. Signed-off-by: Matthaus Litteken --- lib/facter/virtual.rb | 6 +++--- spec/unit/virtual_spec.rb | 6 +++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index e08f49140c..666c3631ae 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -139,8 +139,8 @@ end end - if FileTest.exists?("/usr/lib/vmware/bin/vmware-vmx") - result = "vmware_server" + if output = Facter::Util::Resolution.exec("vmware -v") + result = output.sub(/(\S+)\s+(\S+).*/) { | text | "#{$1}_#{$2}"}.downcase end end @@ -163,7 +163,7 @@ confine :kernel => %w{Linux FreeBSD OpenBSD SunOS HP-UX Darwin GNU/kFreeBSD} setcode do - physical_types = %w{physical xen0 vmware_server openvzhn} + physical_types = %w{physical xen0 vmware_server vmware_workstation openvzhn} if physical_types.include? Facter.value(:virtual) "false" diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index 13bb764464..c5048fd52c 100644 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -72,7 +72,7 @@ describe "on Linux" do before do - FileTest.expects(:exists?).with("/usr/lib/vmware/bin/vmware-vmx").returns false + Facter::Util::Resolution.expects(:exec).with("vmware -v").returns false Facter.fact(:operatingsystem).stubs(:value).returns(true) Facter.fact(:architecture).stubs(:value).returns(true) end @@ -137,6 +137,10 @@ end describe "on Solaris" do + before(:each) do + Facter::Util::Resolution.expects(:exec).with("vmware -v").returns false + end + it "should be vmware with VMWare vendor name from prtdiag" do Facter.fact(:kernel).stubs(:value).returns("SunOS") Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) From 7edef60cd5156f0c0c86b75ed285eb66a8d97c4a Mon Sep 17 00:00:00 2001 From: Matthaus Litteken Date: Thu, 8 Sep 2011 15:50:28 -0700 Subject: [PATCH 0642/3753] Change count to length for compatibility In physicalprocessorcount, .length should be used instead of .count as .count is not supported on ruby <= 1.8.6. This was causing test failures on ruby 1.8.5. Signed-off-by: Matthaus Litteken --- lib/facter/physicalprocessorcount.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/physicalprocessorcount.rb b/lib/facter/physicalprocessorcount.rb index 7514febaf4..13e380f0e1 100644 --- a/lib/facter/physicalprocessorcount.rb +++ b/lib/facter/physicalprocessorcount.rb @@ -59,6 +59,6 @@ confine :kernel => :windows setcode do require 'facter/util/wmi' - Facter::Util::WMI.execquery("select Name from Win32_Processor").count + Facter::Util::WMI.execquery("select Name from Win32_Processor").length end end From 18cd9640ba078761ffeb461d4ce1c1b0df53396a Mon Sep 17 00:00:00 2001 From: Matthaus Litteken Date: Thu, 8 Sep 2011 16:01:24 -0700 Subject: [PATCH 0643/3753] Updated CHANGELOG for 1.6.1rc2 --- CHANGELOG | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 73b9e2afe8..07903d527d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,16 @@ +1.6.1rc2 +=== +9cdc87f Updated CHANGELOG for 1.6.1rc2 +7edef60 Change count to length for compatibility +88f343c (#2344) VMware version parsing fix +7457fe5 SELinux spec test fix for ubuntu +8bed5bb Clear messages between test runs +f4ad6bf Macaddress spec test needed operatingsystem fact +6c098cc Rakefile should fail on error + 1.6.1rc1 === +42c5471 Updated CHANGELOG for 1.6.1rc1. 6d47012 (#4869) Implement productname as Darwin hw.model d28d96c (#4508) Xen HVM domU not detected as virtual d55983e (#9178) Add Oracle Linux identification From 6db71d490de65cec2103b22bb594195020f06e3e Mon Sep 17 00:00:00 2001 From: Ken Barber Date: Tue, 13 Sep 2011 17:49:31 +0100 Subject: [PATCH 0644/3753] (#9457) Fix logic for domain fact so hostname, then dnsdomainname and finally resolv.conf is used. A recent commit changed the logic for how this fall-through logic was working. I've fixed the logic and added more coverage to pick up on this. --- lib/facter/domain.rb | 12 +++++++----- spec/unit/domain_spec.rb | 6 +++--- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/facter/domain.rb b/lib/facter/domain.rb index 1cd6ef0180..72c596ea76 100644 --- a/lib/facter/domain.rb +++ b/lib/facter/domain.rb @@ -23,12 +23,14 @@ # Get the domain from various sources; the order of these # steps is important - if name = Facter::Util::Resolution.exec('hostname') - if name =~ /.*?\.(.+$)/ + if name = Facter::Util::Resolution.exec('hostname') and + name =~ /.*?\.(.+$)/ + $1 - end - elsif domain = Facter::Util::Resolution.exec('dnsdomainname') - domain if domain =~ /.+\..+/ + elsif domain = Facter::Util::Resolution.exec('dnsdomainname') and + domain =~ /.+\..+/ + + domain elsif FileTest.exists?("/etc/resolv.conf") domain = nil search = nil diff --git a/spec/unit/domain_spec.rb b/spec/unit/domain_spec.rb index d04132ad24..e0358b3e7d 100644 --- a/spec/unit/domain_spec.rb +++ b/spec/unit/domain_spec.rb @@ -14,15 +14,15 @@ end it "should fall back to the dnsdomainname binary" do - Facter::Util::Resolution.stubs(:exec).with("hostname") + Facter::Util::Resolution.expects(:exec).with("hostname").returns("myhost") Facter::Util::Resolution.expects(:exec).with("dnsdomainname").returns("example.com") Facter.fact(:domain).value.should == "example.com" end it "should fall back to /etc/resolv.conf" do - Facter::Util::Resolution.stubs(:exec).with("hostname").at_least_once - Facter::Util::Resolution.stubs(:exec).with("dnsdomainname").at_least_once + Facter::Util::Resolution.expects(:exec).with("hostname").at_least_once.returns("myhost") + Facter::Util::Resolution.expects(:exec).with("dnsdomainname").at_least_once.returns("") File.expects(:open).with('/etc/resolv.conf').at_least_once Facter.fact(:domain).value end From 16a8cabba91b7737dfe33227e7f155738a50d60b Mon Sep 17 00:00:00 2001 From: Michael Kincaid Date: Thu, 21 Apr 2011 15:41:59 -0700 Subject: [PATCH 0645/3753] (#2747) Fix detection of xen0 vs xenu in Xen 3.2. Check for xsd_kva for dom0, rather than independent_wallclock (which is present on both dom0 and domu). Work around /proc/xen/capabilities, which is sometimes not world-readable. --- lib/facter/virtual.rb | 15 +++------------ spec/unit/virtual_spec.rb | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 666c3631ae..4032d1dc8b 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -73,20 +73,11 @@ end if Facter::Util::Virtual.xen? - # new Xen domains have this in dom0 not domu :( - if FileTest.exists?("/proc/sys/xen/independent_wallclock") + if FileTest.exists?("/proc/xen/xsd_kva") + result = "xen0" + elsif FileTest.exists?("/proc/xen/capabilities") result = "xenu" end - if FileTest.exists?("/sys/bus/xen") - result = "xenu" - end - - if FileTest.exists?("/proc/xen/capabilities") - txt = Facter::Util::Resolution.exec("cat /proc/xen/capabilities") - if txt =~ /control_d/i - result = "xen0" - end - end end if Facter::Util::Virtual.kvm? diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index c5048fd52c..3ab9f7fddf 100644 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -74,6 +74,10 @@ before do Facter::Util::Resolution.expects(:exec).with("vmware -v").returns false Facter.fact(:operatingsystem).stubs(:value).returns(true) + # Ensure the tests don't fail on Xen + FileTest.stubs(:exists?).with("/proc/sys/xen").returns false + FileTest.stubs(:exists?).with("/sys/bus/xen").returns false + FileTest.stubs(:exists?).with("/proc/xen").returns false Facter.fact(:architecture).stubs(:value).returns(true) end @@ -164,6 +168,21 @@ Facter::Util::Resolution.stubs(:exec).with('prtdiag').returns("System Configuration: innotek GmbH VirtualBox") Facter.fact(:virtual).value.should == "virtualbox" end + + it "should be xen0 with xen dom0 files in /proc" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter::Util::Virtual.expects(:xen?).returns(true) + FileTest.expects(:exists?).with("/proc/xen/xsd_kva").returns(true) + Facter.fact(:virtual).value.should == "xen0" + end + + it "should be xenu with xen domU files in /proc" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter::Util::Virtual.expects(:xen?).returns(true) + FileTest.expects(:exists?).with("/proc/xen/xsd_kva").returns(false) + FileTest.expects(:exists?).with("/proc/xen/capabilities").returns(true) + Facter.fact(:virtual).value.should == "xenu" + end end end From 82351ab6fc58fe38bf6248b5c0c3242ba02d87c8 Mon Sep 17 00:00:00 2001 From: Michael Kincaid Date: Tue, 13 Sep 2011 14:53:03 -0700 Subject: [PATCH 0646/3753] Stub out OS and HW model to avoid test failures. Only stub vmware -v (don't expect it) since it needn't be invoked if we already identified Xen or something else. --- spec/unit/virtual_spec.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index 3ab9f7fddf..c22d837841 100644 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -72,7 +72,7 @@ describe "on Linux" do before do - Facter::Util::Resolution.expects(:exec).with("vmware -v").returns false + Facter::Util::Resolution.stubs(:exec).with("vmware -v").returns false Facter.fact(:operatingsystem).stubs(:value).returns(true) # Ensure the tests don't fail on Xen FileTest.stubs(:exists?).with("/proc/sys/xen").returns false @@ -142,7 +142,7 @@ end describe "on Solaris" do before(:each) do - Facter::Util::Resolution.expects(:exec).with("vmware -v").returns false + Facter::Util::Resolution.stubs(:exec).with("vmware -v").returns false end it "should be vmware with VMWare vendor name from prtdiag" do @@ -171,6 +171,8 @@ it "should be xen0 with xen dom0 files in /proc" do Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:operatingsystem).stubs(:value).returns("Linux") + Facter.fact(:hardwaremodel).stubs(:value).returns("i386") Facter::Util::Virtual.expects(:xen?).returns(true) FileTest.expects(:exists?).with("/proc/xen/xsd_kva").returns(true) Facter.fact(:virtual).value.should == "xen0" @@ -178,6 +180,8 @@ it "should be xenu with xen domU files in /proc" do Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:operatingsystem).stubs(:value).returns("Linux") + Facter.fact(:hardwaremodel).stubs(:value).returns("i386") Facter::Util::Virtual.expects(:xen?).returns(true) FileTest.expects(:exists?).with("/proc/xen/xsd_kva").returns(false) FileTest.expects(:exists?).with("/proc/xen/capabilities").returns(true) From 4d937456e793be0830e55128f091cc5a81a77721 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 26 Jul 2011 12:00:40 -0700 Subject: [PATCH 0647/3753] (#8491) Prevent repeated loading of fact files Fact loading could recurse indefinitely if a fact file attempted to call Fact#value on a fact that was not yet defined before the current file. If Fact#value was called outside of a setcode block, it would be evaluated at load time and the loader would rescan the fact path from the beginning and would reenter the current file, continuing until the stack was full. This is a byproduct of the more exhaustive fact searching introduced in 2255abee. The resolution for this is to track the files that have been loaded and ignore subsequent attempts to load them, emulating the behavior of Kernel.require. However, since facts can be legitimately refreshed over the life of a ruby process using Facter, Facter.clear will reset the list of loaded files by destroying the fact collection, and subsequently the loader. Currently puppet agent will reload all facts preceeding a run, so normal puppet agent behavior will remain as expected. However, the facter facts terminus manually loads fact files itself and bypasses facter's search path and standard loading mechanism. While it will benefit from the recursion protection, it currently does not have a way to reset the loaded file list. --- lib/facter/util/loader.rb | 11 +++++++++++ spec/fixtures/unit/util/loader/nosuchfact.rb | 1 + spec/unit/util/loader_spec.rb | 8 ++++++++ 3 files changed, 20 insertions(+) create mode 100644 spec/fixtures/unit/util/loader/nosuchfact.rb diff --git a/lib/facter/util/loader.rb b/lib/facter/util/loader.rb index a52012c54c..29a1de08c3 100644 --- a/lib/facter/util/loader.rb +++ b/lib/facter/util/loader.rb @@ -2,6 +2,11 @@ # Load facts on demand. class Facter::Util::Loader + + def initialize + @loaded = [] + end + # Load all resolutions for a single fact. def load(fact) # Now load from the search path @@ -68,10 +73,16 @@ def load_dir(dir) end def load_file(file) + return if @loaded.include? file # We have to specify Kernel.load, because we have a load method. begin + # Store the file path so we don't try to reload it + @loaded << file Kernel.load(file) rescue ScriptError => detail + # Don't store the path if the file can't be loaded + # in case it's loadable later on. + @loaded.delete(file) warn "Error loading fact #{file} #{detail}" end end diff --git a/spec/fixtures/unit/util/loader/nosuchfact.rb b/spec/fixtures/unit/util/loader/nosuchfact.rb new file mode 100644 index 0000000000..f0ba60ae81 --- /dev/null +++ b/spec/fixtures/unit/util/loader/nosuchfact.rb @@ -0,0 +1 @@ +Facter.value(:nosuchfact) diff --git a/spec/unit/util/loader_spec.rb b/spec/unit/util/loader_spec.rb index 1bc909f3df..3fb1f04f00 100755 --- a/spec/unit/util/loader_spec.rb +++ b/spec/unit/util/loader_spec.rb @@ -280,4 +280,12 @@ def with_env(values) @loader.load_all end end + + it "should load facts on the facter search path only once" do + facterlibdir = File.expand_path(File.dirname(__FILE__) + '../../../fixtures/unit/util/loader') + with_env 'FACTERLIB' => facterlibdir do + Facter::Util::Loader.new.load_all + Facter.value(:nosuchfact).should be_nil + end + end end From ef0a9aa3e36b3714439e9e0c23828313075ba411 Mon Sep 17 00:00:00 2001 From: Matthaus Litteken Date: Wed, 14 Sep 2011 16:58:50 -0700 Subject: [PATCH 0648/3753] Updated CHANGELOG for 1.6.1rc3 --- CHANGELOG | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 07903d527d..60bd4338b0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,8 @@ +1.6.1rc3 +=== +4d93745 (#8491) Prevent repeated loading of fact files +6db71d4 (#9457) Fix logic for domain fact so hostname, then dnsdomainname and finally resolv.conf is used. + 1.6.1rc2 === 9cdc87f Updated CHANGELOG for 1.6.1rc2 From ea234170b22fbaaec20d0d517ca9afb3ba2914b0 Mon Sep 17 00:00:00 2001 From: Andrew Elwell Date: Wed, 14 Sep 2011 18:06:15 +0200 Subject: [PATCH 0649/3753] (#9295) Initial detection of Hyper-V hypervisor pulled out from video card matching and dmidecode Future plans include grabbing version info and seeing if module is loaded. dmidecode info: System Information Manufacturer: Microsoft Corporation Product Name: Virtual Machine Version: 7.0 Serial Number: ....-....-....-....-....-....-.. (removed) UUID: ........-....-....-....-............ (removed) Wake-up Type: Power Switch --- lib/facter/virtual.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 666c3631ae..a7fdb969bf 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -113,6 +113,9 @@ # --- look for pci vendor id used by Xen HVM device # --- 00:03.0 Unassigned class [ff80]: XenSource, Inc. Xen Platform Device (rev 01) result = "xenhvm" if p =~ /XenSource/ + # --- look for Hyper-V video card + # --- 00:08.0 VGA compatible controller: Microsoft Corporation Hyper-V virtual VGA + result = "hyperv" if p =~ /Microsoft Corporation Hyper-V/ end else output = Facter::Util::Resolution.exec('dmidecode') @@ -122,6 +125,7 @@ result = "vmware" if pd =~ /VMware/ result = "virtualbox" if pd =~ /VirtualBox/ result = "xenhvm" if pd =~ /HVM domU/ + result = "hyperv" if pd =~ /Product Name: Virtual Machine/ end elsif Facter.value(:kernel) == 'SunOS' res = Facter::Util::Resolution.new('prtdiag') @@ -132,7 +136,7 @@ output.each_line do |pd| result = "parallels" if pd =~ /Parallels/ result = "vmware" if pd =~ /VMware/ - result = "virtualbox" if pd =~ /VirtualBox/ + result = "virtualbox" if pd =~ /VirtualBox/ result = "xenhvm" if pd =~ /HVM domU/ end end From c4fe4158a18096b2b1be708548b147e116985469 Mon Sep 17 00:00:00 2001 From: Andrew Elwell Date: Fri, 16 Sep 2011 16:03:47 +0200 Subject: [PATCH 0650/3753] (#9295) Added spec tests for Hyper-V detection --- spec/unit/virtual_spec.rb | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index c5048fd52c..39b4a70bef 100644 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -135,6 +135,19 @@ Facter.fact(:virtual).value.should == "virtualbox" end + it "should be hyperv with Microsoft vendor name from lspci" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter::Util::Resolution.stubs(:exec).with('lspci').returns("00:08.0 VGA compatible controller: Microsoft Corporation Hyper-V virtual VGA") + Facter.fact(:virtual).value.should == "hyperv" + end + + it "should be hyperv with Microsoft vendor name from dmidecode" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) + Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("System Information\nManufacturer: Microsoft Corporation\nProduct Name: Virtual Machine") + Facter.fact(:virtual).value.should == "hyperv" + end + end describe "on Solaris" do before(:each) do @@ -259,4 +272,10 @@ Facter.fact(:virtual).stubs(:value).returns("openvzhn") Facter.fact(:is_virtual).value.should == "false" end + + it "should be true when running on hyperv" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:virtual).stubs(:value).returns("hyperv") + Facter.fact(:is_virtual).value.should == "true" + end end From 3f1a163544d6ba19c9548aa0d628c33e81034b59 Mon Sep 17 00:00:00 2001 From: Ken Barber Date: Mon, 19 Sep 2011 15:08:27 +0100 Subject: [PATCH 0651/3753] (#9593) Require rubygems to handle json output for ruby 1.8.7. --- lib/facter/application.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/facter/application.rb b/lib/facter/application.rb index 9b6da1d639..3f0f37522c 100644 --- a/lib/facter/application.rb +++ b/lib/facter/application.rb @@ -35,6 +35,7 @@ def self.run(argv) # Print the facts as JSON and exit if options[:json] begin + require 'rubygems' require 'json' puts JSON.dump(facts) exit(0) From 3117e82a87f93f7bd2ae41c6dfcf65d58ce17f4f Mon Sep 17 00:00:00 2001 From: Matthaus Litteken Date: Wed, 21 Sep 2011 10:22:38 -0700 Subject: [PATCH 0652/3753] (#9517) Fix physicalprocessorcount on windows A broken test led to a broken fact. The WMI.execquery was incorrectly stubbed to return an array when the actual WMI.execquery does not return an array. This means that length, which works on arrays, does not work with WMI.execquery. This fixes both the fact and the test. The test is unfortunately lifted to a higher level, but it has the benefit of being correct. Thanks to Eric Stonfer for the fact fix. Signed-off-by: Matthaus Litteken --- lib/facter/physicalprocessorcount.rb | 2 +- spec/unit/physicalprocessorcount_spec.rb | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/facter/physicalprocessorcount.rb b/lib/facter/physicalprocessorcount.rb index 13e380f0e1..082deacf73 100644 --- a/lib/facter/physicalprocessorcount.rb +++ b/lib/facter/physicalprocessorcount.rb @@ -59,6 +59,6 @@ confine :kernel => :windows setcode do require 'facter/util/wmi' - Facter::Util::WMI.execquery("select Name from Win32_Processor").length + Facter::Util::WMI.execquery("select Name from Win32_Processor").Count end end diff --git a/spec/unit/physicalprocessorcount_spec.rb b/spec/unit/physicalprocessorcount_spec.rb index 3cf1ae210c..8d33cb517d 100755 --- a/spec/unit/physicalprocessorcount_spec.rb +++ b/spec/unit/physicalprocessorcount_spec.rb @@ -42,7 +42,9 @@ Facter.fact(:kernel).stubs(:value).returns("windows") require 'facter/util/wmi' - Facter::Util::WMI.stubs(:execquery).with("select Name from Win32_Processor").returns(Array.new(4)) + ole = stub 'WIN32OLE' + Facter::Util::WMI.expects(:execquery).with("select Name from Win32_Processor").returns(ole) + ole.stubs(:Count).returns(4) Facter.fact(:physicalprocessorcount).value.should == 4 end From 1f009e085ecc3868db2bb59ea8098e3f9f6285de Mon Sep 17 00:00:00 2001 From: Matthaus Litteken Date: Wed, 21 Sep 2011 13:34:48 -0700 Subject: [PATCH 0653/3753] Updated CHANGELOG for 1.6.1rc4 --- CHANGELOG | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 60bd4338b0..25cf819351 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,7 @@ +1.6.1rc4 +=== +3117e82 (#9517) Fix physicalprocessorcount on windows + 1.6.1rc3 === 4d93745 (#8491) Prevent repeated loading of fact files From 51cb8e2c63d47b5362483900afafb16207b55e21 Mon Sep 17 00:00:00 2001 From: Max Riveiro Date: Sun, 25 Sep 2011 22:20:53 +0400 Subject: [PATCH 0654/3753] Cleaned up Arch Linux detection routine During operating system detection we have an unreachable elsif clause for the Arch Linux systems --- lib/facter/operatingsystem.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index ff802c3652..a29bf1069b 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -45,8 +45,6 @@ else "OEL" end - elsif FileTest.exists?("/etc/arch-release") - "Arch" elsif FileTest.exists?("/etc/vmware-release") "VMWareESX" elsif FileTest.exists?("/etc/redhat-release") From fd93c5fcfc952097dc8977488925348075536b64 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Wed, 7 Sep 2011 11:03:59 -0700 Subject: [PATCH 0655/3753] (#7996) Add solaris processor facts Adds processorcount and physicalprocessorcount facts for solaris. Added minor whitespace change for physicalprocessorcount spec that ccompanied adding a solaris section to the spec. Thanks to Merritt Krakowitzer for his contributions to this patch. --- lib/facter/physicalprocessorcount.rb | 8 + lib/facter/processor.rb | 8 + .../processorcount/solaris_kstat_cpu_info | 1216 +++++++++++++++++ spec/unit/physicalprocessorcount_spec.rb | 76 +- spec/unit/processor_spec.rb | 13 +- 5 files changed, 1285 insertions(+), 36 deletions(-) create mode 100644 spec/fixtures/processorcount/solaris_kstat_cpu_info diff --git a/lib/facter/physicalprocessorcount.rb b/lib/facter/physicalprocessorcount.rb index 082deacf73..ee9f20d4fb 100644 --- a/lib/facter/physicalprocessorcount.rb +++ b/lib/facter/physicalprocessorcount.rb @@ -62,3 +62,11 @@ Facter::Util::WMI.execquery("select Name from Win32_Processor").Count end end + +Facter.add('physicalprocessorcount') do + confine :kernel => :sunos + + setcode do + Facter::Util::Resolution.exec("/usr/sbin/psrinfo -p") + end +end diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb index ccce398d6b..f2c74387e4 100644 --- a/lib/facter/processor.rb +++ b/lib/facter/processor.rb @@ -142,3 +142,11 @@ end end end + +Facter.add("processorcount") do + confine :kernel => :sunos + setcode do + kstat = Facter::Util::Resolution.exec("/usr/bin/kstat cpu_info") + kstat.scan(/core_id\s+\d+/).uniq.length + end +end diff --git a/spec/fixtures/processorcount/solaris_kstat_cpu_info b/spec/fixtures/processorcount/solaris_kstat_cpu_info new file mode 100644 index 0000000000..3585a615fa --- /dev/null +++ b/spec/fixtures/processorcount/solaris_kstat_cpu_info @@ -0,0 +1,1216 @@ +module: cpu_info instance: 0 +name: cpu_info0 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 516 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312660.5754132 + current_clock_Hz 1165379264 + device_ID 0 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 1 + snaptime 23630289.0909192 + state on-line + state_begin 1315099346 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 1 +name: cpu_info1 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 516 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.2806834 + current_clock_Hz 1165379264 + device_ID 1 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 1 + snaptime 23630289.0927599 + state on-line + state_begin 1315099348 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 2 +name: cpu_info2 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 516 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.2828084 + current_clock_Hz 1165379264 + device_ID 2 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 1 + snaptime 23630289.0943224 + state on-line + state_begin 1315099348 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 3 +name: cpu_info3 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 516 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.284967 + current_clock_Hz 1165379264 + device_ID 3 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 1 + snaptime 23630289.0959092 + state on-line + state_begin 1315099348 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 4 +name: cpu_info4 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 516 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.2869702 + current_clock_Hz 1165379264 + device_ID 4 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 4 + snaptime 23630289.0974779 + state on-line + state_begin 1315099348 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 5 +name: cpu_info5 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 516 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.2890495 + current_clock_Hz 1165379264 + device_ID 5 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 4 + snaptime 23630289.0990405 + state on-line + state_begin 1315099348 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 6 +name: cpu_info6 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 516 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.2911274 + current_clock_Hz 1165379264 + device_ID 6 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 4 + snaptime 23630289.1006005 + state on-line + state_begin 1315099348 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 7 +name: cpu_info7 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 516 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.2932203 + current_clock_Hz 1165379264 + device_ID 7 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 4 + snaptime 23630289.1021675 + state on-line + state_begin 1315099348 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 8 +name: cpu_info8 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 524 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.2953277 + current_clock_Hz 1165379264 + device_ID 8 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 5 + snaptime 23630289.1037276 + state on-line + state_begin 1315099348 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 9 +name: cpu_info9 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 524 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.2973458 + current_clock_Hz 1165379264 + device_ID 9 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 5 + snaptime 23630289.1053163 + state on-line + state_begin 1315099348 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 10 +name: cpu_info10 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 524 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.2993978 + current_clock_Hz 1165379264 + device_ID 10 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 5 + snaptime 23630289.1068773 + state on-line + state_begin 1315099348 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 11 +name: cpu_info11 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 524 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.3015554 + current_clock_Hz 1165379264 + device_ID 11 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 5 + snaptime 23630289.1085511 + state on-line + state_begin 1315099348 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 12 +name: cpu_info12 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 524 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.3035692 + current_clock_Hz 1165379264 + device_ID 12 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 7 + snaptime 23630289.1101482 + state on-line + state_begin 1315099348 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 13 +name: cpu_info13 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 524 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.3058002 + current_clock_Hz 1165379264 + device_ID 13 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 7 + snaptime 23630289.1117123 + state on-line + state_begin 1315099348 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 14 +name: cpu_info14 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 524 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.3078818 + current_clock_Hz 1165379264 + device_ID 14 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 7 + snaptime 23630289.1132754 + state on-line + state_begin 1315099348 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 15 +name: cpu_info15 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 524 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.3099415 + current_clock_Hz 1165379264 + device_ID 15 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 7 + snaptime 23630289.1148379 + state on-line + state_begin 1315099348 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 16 +name: cpu_info16 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 531 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.3121222 + current_clock_Hz 1165379264 + device_ID 16 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 8 + snaptime 23630289.1164032 + state on-line + state_begin 1315099348 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 17 +name: cpu_info17 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 531 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.3144076 + current_clock_Hz 1165379264 + device_ID 17 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 8 + snaptime 23630289.1179674 + state on-line + state_begin 1315099348 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 18 +name: cpu_info18 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 531 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.3164902 + current_clock_Hz 1165379264 + device_ID 18 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 8 + snaptime 23630289.119694 + state on-line + state_begin 1315099348 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 19 +name: cpu_info19 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 531 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.3186612 + current_clock_Hz 1165379264 + device_ID 19 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 8 + snaptime 23630289.1212498 + state on-line + state_begin 1315099348 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 20 +name: cpu_info20 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 531 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.3208774 + current_clock_Hz 1165379264 + device_ID 20 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 10 + snaptime 23630289.1228643 + state on-line + state_begin 1315099348 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 21 +name: cpu_info21 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 531 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.3230514 + current_clock_Hz 1165379264 + device_ID 21 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 10 + snaptime 23630289.1244296 + state on-line + state_begin 1315099348 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 22 +name: cpu_info22 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 531 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.3252122 + current_clock_Hz 1165379264 + device_ID 22 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 10 + snaptime 23630289.1260106 + state on-line + state_begin 1315099348 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 23 +name: cpu_info23 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 531 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.3275665 + current_clock_Hz 1165379264 + device_ID 23 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 10 + snaptime 23630289.1276002 + state on-line + state_begin 1315099348 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 24 +name: cpu_info24 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 538 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.3297949 + current_clock_Hz 1165379264 + device_ID 24 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 11 + snaptime 23630289.1291771 + state on-line + state_begin 1315099348 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 25 +name: cpu_info25 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 538 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.3320294 + current_clock_Hz 1165379264 + device_ID 25 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 11 + snaptime 23630289.1307659 + state on-line + state_begin 1315099348 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 26 +name: cpu_info26 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 538 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.3342617 + current_clock_Hz 1165379264 + device_ID 26 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 11 + snaptime 23630289.132355 + state on-line + state_begin 1315099348 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 27 +name: cpu_info27 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 538 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.3364207 + current_clock_Hz 1165379264 + device_ID 27 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 11 + snaptime 23630289.1339703 + state on-line + state_begin 1315099348 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 28 +name: cpu_info28 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 538 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.3387068 + current_clock_Hz 1165379264 + device_ID 28 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 13 + snaptime 23630289.1355427 + state on-line + state_begin 1315099348 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 29 +name: cpu_info29 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 538 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.3410924 + current_clock_Hz 1165379264 + device_ID 29 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 13 + snaptime 23630289.1371043 + state on-line + state_begin 1315099348 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 30 +name: cpu_info30 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 538 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.343326 + current_clock_Hz 1165379264 + device_ID 30 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 13 + snaptime 23630289.1387291 + state on-line + state_begin 1315099348 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 31 +name: cpu_info31 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 538 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.3455326 + current_clock_Hz 1165379264 + device_ID 31 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 13 + snaptime 23630289.1403642 + state on-line + state_begin 1315099348 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 32 +name: cpu_info32 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 545 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.3477709 + current_clock_Hz 1165379264 + device_ID 32 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 14 + snaptime 23630289.141927 + state on-line + state_begin 1315099348 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 33 +name: cpu_info33 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 545 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.3502553 + current_clock_Hz 1165379264 + device_ID 33 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 14 + snaptime 23630289.1435087 + state on-line + state_begin 1315099348 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 34 +name: cpu_info34 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 545 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.3525534 + current_clock_Hz 1165379264 + device_ID 34 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 14 + snaptime 23630289.1451002 + state on-line + state_begin 1315099348 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 35 +name: cpu_info35 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 545 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.3548208 + current_clock_Hz 1165379264 + device_ID 35 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 14 + snaptime 23630289.1466879 + state on-line + state_begin 1315099348 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 36 +name: cpu_info36 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 545 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.3571762 + current_clock_Hz 1165379264 + device_ID 36 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 16 + snaptime 23630289.1482783 + state on-line + state_begin 1315099348 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 37 +name: cpu_info37 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 545 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.3594942 + current_clock_Hz 1165379264 + device_ID 37 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 16 + snaptime 23630289.1498468 + state on-line + state_begin 1315099348 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 38 +name: cpu_info38 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 545 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.3619156 + current_clock_Hz 1165379264 + device_ID 38 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 16 + snaptime 23630289.1514097 + state on-line + state_begin 1315099348 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 39 +name: cpu_info39 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 545 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.3641867 + current_clock_Hz 1165379264 + device_ID 39 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 16 + snaptime 23630289.1529782 + state on-line + state_begin 1315099348 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 40 +name: cpu_info40 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 552 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.3664573 + current_clock_Hz 1165379264 + device_ID 40 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 17 + snaptime 23630289.1546012 + state on-line + state_begin 1315099348 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 41 +name: cpu_info41 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 552 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.3689115 + current_clock_Hz 1165379264 + device_ID 41 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 17 + snaptime 23630289.1561584 + state on-line + state_begin 1315099348 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 42 +name: cpu_info42 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 552 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.3713041 + current_clock_Hz 1165379264 + device_ID 42 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 17 + snaptime 23630289.1577271 + state on-line + state_begin 1315099348 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 43 +name: cpu_info43 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 552 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.373675 + current_clock_Hz 1165379264 + device_ID 43 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 17 + snaptime 23630289.1592959 + state on-line + state_begin 1315099348 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 44 +name: cpu_info44 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 552 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.3760962 + current_clock_Hz 1165379264 + device_ID 44 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 19 + snaptime 23630289.1608683 + state on-line + state_begin 1315099348 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 45 +name: cpu_info45 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 552 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.3784792 + current_clock_Hz 1165379264 + device_ID 45 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 19 + snaptime 23630289.1624495 + state on-line + state_begin 1315099349 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 46 +name: cpu_info46 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 552 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.3809418 + current_clock_Hz 1165379264 + device_ID 46 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 19 + snaptime 23630289.1640098 + state on-line + state_begin 1315099349 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 47 +name: cpu_info47 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 552 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.3833624 + current_clock_Hz 1165379264 + device_ID 47 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 19 + snaptime 23630289.1655756 + state on-line + state_begin 1315099349 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 48 +name: cpu_info48 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 559 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.3857846 + current_clock_Hz 1165379264 + device_ID 48 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 20 + snaptime 23630289.1671529 + state on-line + state_begin 1315099349 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 49 +name: cpu_info49 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 559 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.3883379 + current_clock_Hz 1165379264 + device_ID 49 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 20 + snaptime 23630289.1687334 + state on-line + state_begin 1315099349 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 50 +name: cpu_info50 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 559 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.3908908 + current_clock_Hz 1165379264 + device_ID 50 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 20 + snaptime 23630289.1703504 + state on-line + state_begin 1315099349 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 51 +name: cpu_info51 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 559 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.3933023 + current_clock_Hz 1165379264 + device_ID 51 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 20 + snaptime 23630289.1719139 + state on-line + state_begin 1315099349 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 52 +name: cpu_info52 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 559 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.3957577 + current_clock_Hz 1165379264 + device_ID 52 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 22 + snaptime 23630289.1734706 + state on-line + state_begin 1315099349 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 53 +name: cpu_info53 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 559 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.3982132 + current_clock_Hz 1165379264 + device_ID 53 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 22 + snaptime 23630289.1750473 + state on-line + state_begin 1315099349 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 54 +name: cpu_info54 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 559 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.4008234 + current_clock_Hz 1165379264 + device_ID 54 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 22 + snaptime 23630289.1767452 + state on-line + state_begin 1315099349 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 55 +name: cpu_info55 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 559 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.4032588 + current_clock_Hz 1165379264 + device_ID 55 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 22 + snaptime 23630289.1783072 + state on-line + state_begin 1315099349 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 56 +name: cpu_info56 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 566 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.4057018 + current_clock_Hz 1165379264 + device_ID 56 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 23 + snaptime 23630289.1798837 + state on-line + state_begin 1315099349 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 57 +name: cpu_info57 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 566 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.4083202 + current_clock_Hz 1165379264 + device_ID 57 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 23 + snaptime 23630289.1814739 + state on-line + state_begin 1315099349 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 58 +name: cpu_info58 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 566 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.4108088 + current_clock_Hz 1165379264 + device_ID 58 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 23 + snaptime 23630289.1830437 + state on-line + state_begin 1315099349 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 59 +name: cpu_info59 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 566 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.4133075 + current_clock_Hz 1165379264 + device_ID 59 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 23 + snaptime 23630289.1846328 + state on-line + state_begin 1315099349 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 60 +name: cpu_info60 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 566 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.4157926 + current_clock_Hz 1165379264 + device_ID 60 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 25 + snaptime 23630289.1862341 + state on-line + state_begin 1315099349 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 61 +name: cpu_info61 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 566 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.4217917 + current_clock_Hz 1165379264 + device_ID 61 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 25 + snaptime 23630289.1878047 + state on-line + state_begin 1315099349 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 62 +name: cpu_info62 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 566 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.4243642 + current_clock_Hz 1165379264 + device_ID 62 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 25 + snaptime 23630289.1894041 + state on-line + state_begin 1315099349 + supported_frequencies_Hz 1165379264 + +module: cpu_info instance: 63 +name: cpu_info63 class: misc + brand UltraSPARC-T2 + chip_id 0 + clock_MHz 1165 + core_id 566 + cpu_fru hc:///component= + cpu_type sparcv9 + crtime 23312663.4268942 + current_clock_Hz 1165379264 + device_ID 63 + fpu_type sparcv9 + implementation UltraSPARC-T2 (chipid 0, clock 1165 MHz) + pg_id 25 + snaptime 23630289.1910039 + state on-line + state_begin 1315099349 + supported_frequencies_Hz 1165379264 + diff --git a/spec/unit/physicalprocessorcount_spec.rb b/spec/unit/physicalprocessorcount_spec.rb index 8d33cb517d..38d960c53c 100755 --- a/spec/unit/physicalprocessorcount_spec.rb +++ b/spec/unit/physicalprocessorcount_spec.rb @@ -1,51 +1,57 @@ -#!/usr/bin/env ruby +#!/usr/bin/env rspec require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') -require 'facter' - describe "Physical processor count facts" do - before do - Facter.loadfacts - end - before do - Facter.clear + + describe "on linux" do + before :each do + Facter.fact(:kernel).stubs(:value).returns("Linux") + File.stubs(:exists?).with('/sys/devices/system/cpu').returns(true) end + it "should return one physical CPU" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - File.stubs(:exists?).with('/sys/devices/system/cpu').returns(true) - Dir.stubs(:glob).with("/sys/devices/system/cpu/cpu*/topology/physical_package_id").returns(["/sys/devices/system/cpu/cpu0/topology/physical_package_id"]) - Facter::Util::Resolution.stubs(:exec).with("cat /sys/devices/system/cpu/cpu0/topology/physical_package_id").returns("0") + Dir.stubs(:glob).with("/sys/devices/system/cpu/cpu*/topology/physical_package_id").returns(["/sys/devices/system/cpu/cpu0/topology/physical_package_id"]) + Facter::Util::Resolution.stubs(:exec).with("cat /sys/devices/system/cpu/cpu0/topology/physical_package_id").returns("0") - Facter.fact(:physicalprocessorcount).value.should == 1 + Facter.fact(:physicalprocessorcount).value.should == 1 end it "should return four physical CPUs" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - File.stubs(:exists?).with('/sys/devices/system/cpu').returns(true) - Dir.stubs(:glob).with("/sys/devices/system/cpu/cpu*/topology/physical_package_id").returns(%w{ - /sys/devices/system/cpu/cpu0/topology/physical_package_id - /sys/devices/system/cpu/cpu1/topology/physical_package_id - /sys/devices/system/cpu/cpu2/topology/physical_package_id - /sys/devices/system/cpu/cpu3/topology/physical_package_id - }) - - Facter::Util::Resolution.stubs(:exec).with("cat /sys/devices/system/cpu/cpu0/topology/physical_package_id").returns("0") - Facter::Util::Resolution.stubs(:exec).with("cat /sys/devices/system/cpu/cpu1/topology/physical_package_id").returns("1") - Facter::Util::Resolution.stubs(:exec).with("cat /sys/devices/system/cpu/cpu2/topology/physical_package_id").returns("2") - Facter::Util::Resolution.stubs(:exec).with("cat /sys/devices/system/cpu/cpu3/topology/physical_package_id").returns("3") - - Facter.fact(:physicalprocessorcount).value.should == 4 + Dir.stubs(:glob).with("/sys/devices/system/cpu/cpu*/topology/physical_package_id").returns(%w{ + /sys/devices/system/cpu/cpu0/topology/physical_package_id + /sys/devices/system/cpu/cpu1/topology/physical_package_id + /sys/devices/system/cpu/cpu2/topology/physical_package_id + /sys/devices/system/cpu/cpu3/topology/physical_package_id + }) + + Facter::Util::Resolution.stubs(:exec).with("cat /sys/devices/system/cpu/cpu0/topology/physical_package_id").returns("0") + Facter::Util::Resolution.stubs(:exec).with("cat /sys/devices/system/cpu/cpu1/topology/physical_package_id").returns("1") + Facter::Util::Resolution.stubs(:exec).with("cat /sys/devices/system/cpu/cpu2/topology/physical_package_id").returns("2") + Facter::Util::Resolution.stubs(:exec).with("cat /sys/devices/system/cpu/cpu3/topology/physical_package_id").returns("3") + + Facter.fact(:physicalprocessorcount).value.should == 4 end + end + + describe "on windows" do + it "should return 4 physical CPUs" do + Facter.fact(:kernel).stubs(:value).returns("windows") - it "should return 4 physical CPUs on Windows" do - Facter.fact(:kernel).stubs(:value).returns("windows") + require 'facter/util/wmi' + ole = stub 'WIN32OLE' + Facter::Util::WMI.expects(:execquery).with("select Name from Win32_Processor").returns(ole) + ole.stubs(:Count).returns(4) - require 'facter/util/wmi' - ole = stub 'WIN32OLE' - Facter::Util::WMI.expects(:execquery).with("select Name from Win32_Processor").returns(ole) - ole.stubs(:Count).returns(4) + Facter.fact(:physicalprocessorcount).value.should == 4 + end + end - Facter.fact(:physicalprocessorcount).value.should == 4 + describe "on solaris" do + it "should use the output of psrinfo" do + Facter.fact(:kernel).stubs(:value).returns(:sunos) + Facter::Util::Resolution.expects(:exec).with("/usr/sbin/psrinfo -p").returns(1) + Facter.fact(:physicalprocessorcount).value.should == 1 end + end end diff --git a/spec/unit/processor_spec.rb b/spec/unit/processor_spec.rb index 6a30c85a3c..95e0e5185b 100755 --- a/spec/unit/processor_spec.rb +++ b/spec/unit/processor_spec.rb @@ -61,6 +61,17 @@ def load(procs) end end end -end + describe "on Solaris" do + before :each do + Facter.collection.loader.load(:processor) + Facter.fact(:kernel).stubs(:value).returns(:sunos) + @fixture_data = File.read(File.expand_path(File.dirname(__FILE__) + '/../fixtures/processorcount/solaris_kstat_cpu_info')) + end + it "should use kstat to retrieve the processor count" do + Facter::Util::Resolution.expects(:exec).with("/usr/bin/kstat cpu_info").returns(@fixture_data) + Facter.fact(:processorcount).value.should == 8 + end + end +end From 124a09b8ac71dbcd0e4e439d21c8b152f1bbd9a3 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 15 Aug 2011 10:52:36 -0700 Subject: [PATCH 0656/3753] (#8240) Fixed regex pattern for domain If given a search field of "search domain invalid", "invalid" would be returned as the domain, when it should have been domain, due to incorrect line anchoring. Added tests and corrected the regexes. --- lib/facter/domain.rb | 4 ++-- spec/unit/domain_spec.rb | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/lib/facter/domain.rb b/lib/facter/domain.rb index 72c596ea76..a14c3e6ed4 100644 --- a/lib/facter/domain.rb +++ b/lib/facter/domain.rb @@ -36,9 +36,9 @@ search = nil File.open("/etc/resolv.conf") { |file| file.each { |line| - if line =~ /domain\s+(\S+)/ + if line =~ /^\s*domain\s+(\S+)/ domain = $1 - elsif line =~ /search\s+(\S+)/ + elsif line =~ /^\s*search\s+(\S+)/ search = $1 end } diff --git a/spec/unit/domain_spec.rb b/spec/unit/domain_spec.rb index e0358b3e7d..54bf472b0d 100644 --- a/spec/unit/domain_spec.rb +++ b/spec/unit/domain_spec.rb @@ -69,6 +69,29 @@ @mock_file.expects(:each).multiple_yields(*lines) Facter.fact(:domain).value.should == 'example.org' end + + # Test permutations of domain and search + [ + ["domain domain", "domain"], + ["domain search", "search"], + ["search domain", "domain"], + ["search search", "search"], + ["search domain notdomain", "domain"], + [["#search notdomain","search search"], "search"], + [["# search notdomain","search search"], "search"], + [["#domain notdomain","domain domain"], "domain"], + [["# domain notdomain","domain domain"], "domain"], + ].each do |tuple| + field = tuple[0] + expect = tuple[1] + it "should return #{expect} from \"#{field}\"" do + lines = [ + field + ].flatten + @mock_file.expects(:each).multiple_yields(*lines) + Facter.fact(:domain).value.should == expect + end + end end end From 6e29ff7165b8bb63d1deeff6502a0195fa3974e5 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 25 Jul 2011 18:27:57 -0700 Subject: [PATCH 0657/3753] (#8615) ENV hash is now local to tests Added before and after hooks to store and restore environment data so that changes to the hash do not persist between runs. --- spec/spec_helper.rb | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 19da71b385..b58a5b672b 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -16,10 +16,19 @@ RSpec.configure do |config| config.mock_with :mocha - # Ensure that we don't accidentally cache between test cases. + # Ensure that we don't accidentally cache facts and environment + # between test cases. config.before :each do Facter::Util::Loader.any_instance.stubs(:load_all) Facter.clear Facter.clear_messages + @old_env = {} + ENV.each_key {|k| @old_env[k] = ENV[k]} + end + + config.after :each do + @old_env.each_pair {|k, v| ENV[k] = v} + to_remove = ENV.keys.reject {|key| @old_env.include? key } + to_remove.each {|key| ENV.delete key } end end From 83498b50cb5210a80e2245afed1751407c571696 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 27 Sep 2011 10:13:18 -0700 Subject: [PATCH 0658/3753] (#7996) Restrict solaris cpu processor detection x86_64 based solaris machines have a pkg_core_id field in output of kstat cpu_info, which denotes the core id of a specific core relative to the cpu. This could cause misreporting of processor count. The regex to count cores was restricted to prevent this. Additional x86_64 tests were added to verify this behavior. --- lib/facter/processor.rb | 2 +- ..._cpu_info => solaris-sparc-kstat-cpu-info} | 0 .../solaris-x86_64-kstat-cpu-info | 225 ++++++++++++++++++ spec/unit/processor_spec.rb | 12 +- 4 files changed, 235 insertions(+), 4 deletions(-) rename spec/fixtures/processorcount/{solaris_kstat_cpu_info => solaris-sparc-kstat-cpu-info} (100%) create mode 100644 spec/fixtures/processorcount/solaris-x86_64-kstat-cpu-info diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb index f2c74387e4..f15a2625a1 100644 --- a/lib/facter/processor.rb +++ b/lib/facter/processor.rb @@ -147,6 +147,6 @@ confine :kernel => :sunos setcode do kstat = Facter::Util::Resolution.exec("/usr/bin/kstat cpu_info") - kstat.scan(/core_id\s+\d+/).uniq.length + kstat.scan(/\bcore_id\b\s+\d+/).uniq.length end end diff --git a/spec/fixtures/processorcount/solaris_kstat_cpu_info b/spec/fixtures/processorcount/solaris-sparc-kstat-cpu-info similarity index 100% rename from spec/fixtures/processorcount/solaris_kstat_cpu_info rename to spec/fixtures/processorcount/solaris-sparc-kstat-cpu-info diff --git a/spec/fixtures/processorcount/solaris-x86_64-kstat-cpu-info b/spec/fixtures/processorcount/solaris-x86_64-kstat-cpu-info new file mode 100644 index 0000000000..8aba181ca2 --- /dev/null +++ b/spec/fixtures/processorcount/solaris-x86_64-kstat-cpu-info @@ -0,0 +1,225 @@ +module: cpu_info instance: 0 +name: cpu_info0 class: misc + brand Quad-Core AMD Opteron(tm) Processor 2356 + cache_id 0 + chip_id 0 + clock_MHz 2300 + clog_id 0 + core_id 0 + cpu_type i386 + crtime 150.118972763 + current_clock_Hz 2300000000 + current_cstate 0 + family 16 + fpu_type i387 compatible + implementation x86 (chipid 0x0 AuthenticAMD family 16 model 2 step 3 clock 2300 MHz) + model 2 + ncore_per_chip 4 + ncpu_per_chip 4 + pg_id 2 + pkg_core_id 0 + snaptime 1964970.24919915 + state on-line + state_begin 1315115687 + stepping 3 + supported_frequencies_Hz 1200000000:1400000000:1700000000:2000000000:2300000000 + supported_max_cstates 0 + vendor_id AuthenticAMD + +module: cpu_info instance: 1 +name: cpu_info1 class: misc + brand Quad-Core AMD Opteron(tm) Processor 2356 + cache_id 1 + chip_id 0 + clock_MHz 2300 + clog_id 1 + core_id 1 + cpu_type i386 + crtime 153.090853556 + current_clock_Hz 1200000000 + current_cstate 1 + family 16 + fpu_type i387 compatible + implementation x86 (chipid 0x0 AuthenticAMD family 16 model 2 step 3 clock 2300 MHz) + model 2 + ncore_per_chip 4 + ncpu_per_chip 4 + pg_id 3 + pkg_core_id 1 + snaptime 1964970.24965225 + state on-line + state_begin 1315115690 + stepping 3 + supported_frequencies_Hz 1200000000:1400000000:1700000000:2000000000:2300000000 + supported_max_cstates 0 + vendor_id AuthenticAMD + +module: cpu_info instance: 2 +name: cpu_info2 class: misc + brand Quad-Core AMD Opteron(tm) Processor 2356 + cache_id 2 + chip_id 0 + clock_MHz 2300 + clog_id 2 + core_id 2 + cpu_type i386 + crtime 153.160162766 + current_clock_Hz 1200000000 + current_cstate 1 + family 16 + fpu_type i387 compatible + implementation x86 (chipid 0x0 AuthenticAMD family 16 model 2 step 3 clock 2300 MHz) + model 2 + ncore_per_chip 4 + ncpu_per_chip 4 + pg_id 4 + pkg_core_id 2 + snaptime 1964970.24997443 + state on-line + state_begin 1315115690 + stepping 3 + supported_frequencies_Hz 1200000000:1400000000:1700000000:2000000000:2300000000 + supported_max_cstates 0 + vendor_id AuthenticAMD + +module: cpu_info instance: 3 +name: cpu_info3 class: misc + brand Quad-Core AMD Opteron(tm) Processor 2356 + cache_id 3 + chip_id 0 + clock_MHz 2300 + clog_id 3 + core_id 3 + cpu_type i386 + crtime 153.190177596 + current_clock_Hz 1200000000 + current_cstate 1 + family 16 + fpu_type i387 compatible + implementation x86 (chipid 0x0 AuthenticAMD family 16 model 2 step 3 clock 2300 MHz) + model 2 + ncore_per_chip 4 + ncpu_per_chip 4 + pg_id 5 + pkg_core_id 3 + snaptime 1964970.2502919 + state on-line + state_begin 1315115690 + stepping 3 + supported_frequencies_Hz 1200000000:1400000000:1700000000:2000000000:2300000000 + supported_max_cstates 0 + vendor_id AuthenticAMD + +module: cpu_info instance: 4 +name: cpu_info4 class: misc + brand Quad-Core AMD Opteron(tm) Processor 2356 + cache_id 4 + chip_id 1 + clock_MHz 2300 + clog_id 0 + core_id 4 + cpu_type i386 + crtime 153.2201642 + current_clock_Hz 1200000000 + current_cstate 1 + family 16 + fpu_type i387 compatible + implementation x86 (chipid 0x1 AuthenticAMD family 16 model 2 step 3 clock 2300 MHz) + model 2 + ncore_per_chip 4 + ncpu_per_chip 4 + pg_id 7 + pkg_core_id 0 + snaptime 1964970.2505907 + state on-line + state_begin 1315115690 + stepping 3 + supported_frequencies_Hz 1200000000:1400000000:1700000000:2000000000:2300000000 + supported_max_cstates 0 + vendor_id AuthenticAMD + +module: cpu_info instance: 5 +name: cpu_info5 class: misc + brand Quad-Core AMD Opteron(tm) Processor 2356 + cache_id 5 + chip_id 1 + clock_MHz 2300 + clog_id 1 + core_id 5 + cpu_type i386 + crtime 153.250190916 + current_clock_Hz 1200000000 + current_cstate 1 + family 16 + fpu_type i387 compatible + implementation x86 (chipid 0x1 AuthenticAMD family 16 model 2 step 3 clock 2300 MHz) + model 2 + ncore_per_chip 4 + ncpu_per_chip 4 + pg_id 8 + pkg_core_id 1 + snaptime 1964970.25087316 + state on-line + state_begin 1315115690 + stepping 3 + supported_frequencies_Hz 1200000000:1400000000:1700000000:2000000000:2300000000 + supported_max_cstates 0 + vendor_id AuthenticAMD + +module: cpu_info instance: 6 +name: cpu_info6 class: misc + brand Quad-Core AMD Opteron(tm) Processor 2356 + cache_id 6 + chip_id 1 + clock_MHz 2300 + clog_id 2 + core_id 6 + cpu_type i386 + crtime 153.280185962 + current_clock_Hz 1200000000 + current_cstate 1 + family 16 + fpu_type i387 compatible + implementation x86 (chipid 0x1 AuthenticAMD family 16 model 2 step 3 clock 2300 MHz) + model 2 + ncore_per_chip 4 + ncpu_per_chip 4 + pg_id 9 + pkg_core_id 2 + snaptime 1964970.25117252 + state on-line + state_begin 1315115690 + stepping 3 + supported_frequencies_Hz 1200000000:1400000000:1700000000:2000000000:2300000000 + supported_max_cstates 0 + vendor_id AuthenticAMD + +module: cpu_info instance: 7 +name: cpu_info7 class: misc + brand Quad-Core AMD Opteron(tm) Processor 2356 + cache_id 7 + chip_id 1 + clock_MHz 2300 + clog_id 3 + core_id 7 + cpu_type i386 + crtime 153.310206904 + current_clock_Hz 2300000000 + current_cstate 1 + family 16 + fpu_type i387 compatible + implementation x86 (chipid 0x1 AuthenticAMD family 16 model 2 step 3 clock 2300 MHz) + model 2 + ncore_per_chip 4 + ncpu_per_chip 4 + pg_id 10 + pkg_core_id 3 + snaptime 1964970.25143389 + state on-line + state_begin 1315115690 + stepping 3 + supported_frequencies_Hz 1200000000:1400000000:1700000000:2000000000:2300000000 + supported_max_cstates 0 + vendor_id AuthenticAMD + + diff --git a/spec/unit/processor_spec.rb b/spec/unit/processor_spec.rb index 95e0e5185b..6cbe971e0c 100755 --- a/spec/unit/processor_spec.rb +++ b/spec/unit/processor_spec.rb @@ -66,11 +66,17 @@ def load(procs) before :each do Facter.collection.loader.load(:processor) Facter.fact(:kernel).stubs(:value).returns(:sunos) - @fixture_data = File.read(File.expand_path(File.dirname(__FILE__) + '/../fixtures/processorcount/solaris_kstat_cpu_info')) end - it "should use kstat to retrieve the processor count" do - Facter::Util::Resolution.expects(:exec).with("/usr/bin/kstat cpu_info").returns(@fixture_data) + it "should detect the correct processor count on x86_64" do + fixture_data = File.read(File.expand_path(File.dirname(__FILE__) + '/../fixtures/processorcount/solaris-x86_64-kstat-cpu-info')) + Facter::Util::Resolution.expects(:exec).with("/usr/bin/kstat cpu_info").returns(fixture_data) + Facter.fact(:processorcount).value.should == 8 + end + + it "should detect the correct processor count on sparc" do + fixture_data = File.read(File.expand_path(File.dirname(__FILE__) + '/../fixtures/processorcount/solaris-sparc-kstat-cpu-info')) + Facter::Util::Resolution.expects(:exec).with("/usr/bin/kstat cpu_info").returns(fixture_data) Facter.fact(:processorcount).value.should == 8 end end From 51329b80f68d8046c777ae7582204894571d478e Mon Sep 17 00:00:00 2001 From: Michael Kincaid Date: Thu, 12 May 2011 17:45:24 -0700 Subject: [PATCH 0659/3753] (#3856) Detect VirtualBox on Darwin as well as Linux and SunOS --- lib/facter/virtual.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 95a5b642c1..578562a6ef 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -16,12 +16,11 @@ # contents of files in there. # If after all the other tests, it's still seen as physical, then it tries to # parse the output of the "lspci", "dmidecode" and "prtdiag" and parses them -# for obvious signs of being under VMWare or Parallels. +# for obvious signs of being under VMWare, Parallels or VirtualBox. # Finally it checks for the existence of vmware-vmx, which would hint it's # VMWare. # # Caveats: -# Virtualbox detection isn't implemented. # Many checks rely purely on existence of files. # @@ -39,6 +38,7 @@ result = "parallels" if output["spdisplays_vendor"] =~ /[Pp]arallels/ result = "vmware" if output["spdisplays_vendor-id"] =~ /0x15ad/ result = "vmware" if output["spdisplays_vendor"] =~ /VM[wW]are/ + result = "virtualbox" if output["spdisplays_vendor-id"] =~ /0x80ee/ end result end From 328ff7584e0353a609cba6d9fa08c0b6a26be664 Mon Sep 17 00:00:00 2001 From: Michael Kincaid Date: Thu, 7 Apr 2011 10:28:37 -0700 Subject: [PATCH 0660/3753] (#6515 and #2945) Fix processorcount for arm, sparc & ppc for linux. Previously we were unable to check processor type and count on other architectures for linux. This fix corrects that. To remove complication from the fact we have moved the logic for parsing cpuinfo and lsdev into their own static classes in Facter::Util::Processor. This is to help with stubbing and to segregate that action as now we have more conditional cases. Tests and corresponding cpuinfo fixtures have been added to test those alternative platforms as well. --- lib/facter/processor.rb | 137 +++++++++++------------ lib/facter/util/processor.rb | 88 +++++++++++++++ spec/fixtures/cpuinfo/amd64dual | 57 ++++++++++ spec/fixtures/cpuinfo/amd64quad | 79 +++++++++++++ spec/fixtures/cpuinfo/amd64solo | 23 ++++ spec/fixtures/cpuinfo/amd64tri | 86 +++++++++++++++ spec/fixtures/cpuinfo/bbg3-armel | 12 ++ spec/fixtures/cpuinfo/beaglexm-armel | 12 ++ spec/fixtures/cpuinfo/panda-armel | 17 +++ spec/fixtures/cpuinfo/ppc64 | 19 ++++ spec/fixtures/cpuinfo/sparc | 10 ++ spec/unit/processor_spec.rb | 159 ++++++++++++++++++++++++++- spec/unit/util/processor_spec.rb | 62 +++++++++++ 13 files changed, 690 insertions(+), 71 deletions(-) create mode 100644 lib/facter/util/processor.rb create mode 100644 spec/fixtures/cpuinfo/amd64dual create mode 100644 spec/fixtures/cpuinfo/amd64quad create mode 100644 spec/fixtures/cpuinfo/amd64solo create mode 100644 spec/fixtures/cpuinfo/amd64tri create mode 100644 spec/fixtures/cpuinfo/bbg3-armel create mode 100644 spec/fixtures/cpuinfo/beaglexm-armel create mode 100644 spec/fixtures/cpuinfo/panda-armel create mode 100644 spec/fixtures/cpuinfo/ppc64 create mode 100644 spec/fixtures/cpuinfo/sparc create mode 100755 spec/unit/util/processor_spec.rb diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb index f15a2625a1..ffb611803e 100644 --- a/lib/facter/processor.rb +++ b/lib/facter/processor.rb @@ -19,90 +19,87 @@ # require 'thread' +require 'facter/util/processor' -if ["Linux", "GNU/kFreeBSD"].include? Facter.value(:kernel) - processor_num = -1 - processor_list = [] - Thread::exclusive do - File.readlines("/proc/cpuinfo").each do |l| - if l =~ /processor\s+:\s+(\d+)/ - processor_num = $1.to_i - elsif l =~ /model name\s+:\s+(.*)\s*$/ - processor_list[processor_num] = $1 unless processor_num == -1 - processor_num = -1 - elsif l =~ /processor\s+(\d+):\s+(.*)/ - processor_num = $1.to_i - processor_list[processor_num] = $2 unless processor_num == -1 - end - end +Facter.add("ProcessorCount") do + confine :kernel => [ :linux, :"gnu/kfreebsd" ] + setcode do + processor_list = Facter::Util::Processor.enum_cpuinfo + + ## If this returned nothing, then don't resolve the fact + if processor_list.length != 0 + processor_list.length.to_s end + end +end - Facter.add("ProcessorCount") do - confine :kernel => [ :linux, :"gnu/kfreebsd" ] - setcode do - processor_list.length.to_s - end +Facter.add("ProcessorCount") do + confine :kernel => [ :linux, :"gnu/kfreebsd" ] + setcode do + ## The method above is preferable since it provides the description of the CPU as well + ## but if that returned 0, then we enumerate sysfs + sysfs_cpu_directory = '/sys/devices/system/cpu' + if File.exists?(sysfs_cpu_directory) + lookup_pattern = "#{sysfs_cpu_directory}" + "/cpu[0-9]*" + cpuCount = Dir.glob(lookup_pattern).length + cpuCount.to_s end + end +end - processor_list.each_with_index do |desc, i| - Facter.add("Processor#{i}") do - confine :kernel => [ :linux, :"gnu/kfreebsd" ] - setcode do - desc - end - end - end +Facter.add("ProcessorCount") do + confine :kernel => :aix + setcode do + processor_list = Facter::Util::Processor.enum_lsdev + + processor_list.length.to_s + end end -if Facter.value(:kernel) == "AIX" - processor_num = -1 - processor_list = {} - Thread::exclusive do - procs = Facter::Util::Resolution.exec('lsdev -Cc processor') - procs.each do |proc| - if proc =~ /^proc(\d+)/ - processor_num = $1.to_i - # Not retrieving the frequency since AIX 4.3.3 doesn't support the - # attribute and some people still use the OS. - proctype = Facter::Util::Resolution.exec('lsattr -El proc0 -a type') - if proctype =~ /^type\s+(\S+)\s+/ - processor_list["processor#{processor_num}"] = $1 - end - end - end +Facter.add("Processor") do + confine :kernel => :openbsd + setcode do + Facter::Util::Resolution.exec("uname -p") end +end - Facter.add("ProcessorCount") do - confine :kernel => :aix - setcode do - processor_list.length.to_s - end - end +Facter.add("ProcessorCount") do + confine :kernel => :openbsd + setcode do + Facter::Util::Resolution.exec("sysctl hw.ncpu | cut -d'=' -f2") + end +end - processor_list.each do |proc, desc| - Facter.add(proc) do - confine :kernel => :aix - setcode do - desc - end - end - end +Facter.add("ProcessorCount") do + confine :kernel => :Darwin + setcode do + Facter::Util::Resolution.exec("sysctl hw.ncpu | cut -d' ' -f2") + end end -if Facter.value(:kernel) == "OpenBSD" - Facter.add("Processor") do - confine :kernel => :openbsd - setcode do - Facter::Util::Resolution.exec("uname -p") - end - end +## We have to enumerate these outside a Facter.add block to get the processorN descriptions iteratively +## (but we need them inside the Facter.add block above for tests on processorcount to work) +processor_list = Facter::Util::Processor.enum_cpuinfo +processor_list_aix = Facter::Util::Processor.enum_lsdev - Facter.add("ProcessorCount") do - confine :kernel => :openbsd - setcode do - Facter::Util::Resolution.exec("sysctl hw.ncpu | cut -d'=' -f2") - end +if processor_list.length != 0 + processor_list.each_with_index do |desc, i| + Facter.add("Processor#{i}") do + confine :kernel => [ :linux, :"gnu/kfreebsd" ] + setcode do + desc + end end + end +elsif processor_list_aix.length != 0 + processor_list_aix.each_with_index do |desc, i| + Facter.add("Processor#{i}") do + confine :kernel => [ :aix ] + setcode do + desc + end + end + end end if Facter.value(:kernel) == "windows" diff --git a/lib/facter/util/processor.rb b/lib/facter/util/processor.rb new file mode 100644 index 0000000000..b5d44c8876 --- /dev/null +++ b/lib/facter/util/processor.rb @@ -0,0 +1,88 @@ +module Facter::Util::Processor + def self.enum_cpuinfo + processor_num = -1 + processor_list = [] + cpuinfo = "/proc/cpuinfo" + + if File.exists?(cpuinfo) + model = Facter.value(:architecture) + case model + when "x86_64", "amd64", "i386", /parisc/, "hppa", "ia64" + Thread::exclusive do + File.readlines(cpuinfo).each do |l| + if l =~ /processor\s+:\s+(\d+)/ + processor_num = $1.to_i + elsif l =~ /model name\s+:\s+(.*)\s*$/ + processor_list[processor_num] = $1 unless processor_num == -1 + processor_num = -1 + elsif l =~ /processor\s+(\d+):\s+(.*)/ + processor_num = $1.to_i + processor_list[processor_num] = $2 unless processor_num == -1 + end + end + end + + when "ppc64" + Thread::exclusive do + File.readlines(cpuinfo).each do |l| + if l =~ /processor\s+:\s+(\d+)/ + processor_num = $1.to_i + elsif l =~ /cpu\s+:\s+(.*)\s*$/ + processor_list[processor_num] = $1 unless processor_num == -1 + processor_num = -1 + end + end + end + + when /arm/ + Thread::exclusive do + File.readlines(cpuinfo).each do |l| + if l =~ /Processor\s+:\s+(.+)/ + processor_num += 1 + processor_list[processor_num] = $1.chomp + elsif l =~ /processor\s+:\s+(\d+)\s*$/ + proc_num = $1.to_i + if proc_num != 0 + processor_num += 1 + processor_list[processor_num] = processor_list[processor_num-1] + end + end + end + end + + when /sparc/ + Thread::exclusive do + File.readlines(cpuinfo).each do |l| + if l =~ /cpu\s+:\s+(.*)\s*$/ + processor_num += 1 + processor_list[processor_num] = $1 + end + end + end + end + end + processor_list + end + + def self.enum_lsdev + processor_num = -1 + processor_list = {} + Thread::exclusive do + procs = Facter::Util::Resolution.exec('lsdev -Cc processor') + if procs + procs.lines.each do |proc| + if proc =~ /^proc(\d+)/ + processor_num = $1.to_i + # Not retrieving the frequency since AIX 4.3.3 doesn't support the + # attribute and some people still use the OS. + proctype = Facter::Util::Resolution.exec('lsattr -El proc0 -a type') + if proctype =~ /^type\s+(\S+)\s+/ + processor_list[processor_num] = $1 + end + end + end + end + end + processor_list + end +end diff --git a/spec/fixtures/cpuinfo/amd64dual b/spec/fixtures/cpuinfo/amd64dual new file mode 100644 index 0000000000..101e5b5c76 --- /dev/null +++ b/spec/fixtures/cpuinfo/amd64dual @@ -0,0 +1,57 @@ +processor : 0 +vendor_id : GenuineIntel +cpu family : 6 +model : 23 +model name : Intel(R) Core(TM)2 Duo CPU P8700 @ 2.53GHz +stepping : 10 +cpu MHz : 689.302 +cache size : 6144 KB +physical id : 0 +siblings : 2 +core id : 0 +cpu cores : 2 +apicid : 0 +initial apicid : 0 +fdiv_bug : no +hlt_bug : no +f00f_bug : no +coma_bug : no +fpu : yes +fpu_exception : yes +cpuid level : 5 +wp : yes +flags : fpu vme de pse tsc msr mce cx8 apic mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht constant_tsc pni ssse3 +bogomips : 1378.60 +clflush size : 64 +cache_alignment : 64 +address sizes : 36 bits physical, 48 bits virtual +power management: + +processor : 1 +vendor_id : GenuineIntel +cpu family : 6 +model : 23 +model name : Intel(R) Core(TM)2 Duo CPU P8700 @ 2.53GHz +stepping : 10 +cpu MHz : 689.302 +cache size : 6144 KB +physical id : 0 +siblings : 2 +core id : 1 +cpu cores : 2 +apicid : 1 +initial apicid : 1 +fdiv_bug : no +hlt_bug : no +f00f_bug : no +coma_bug : no +fpu : yes +fpu_exception : yes +cpuid level : 5 +wp : yes +flags : fpu vme de pse tsc msr mce cx8 apic mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht constant_tsc pni ssse3 +bogomips : 1687.45 +clflush size : 64 +cache_alignment : 64 +address sizes : 36 bits physical, 48 bits virtual +power management: diff --git a/spec/fixtures/cpuinfo/amd64quad b/spec/fixtures/cpuinfo/amd64quad new file mode 100644 index 0000000000..36754763cf --- /dev/null +++ b/spec/fixtures/cpuinfo/amd64quad @@ -0,0 +1,79 @@ +processor : 0 +vendor_id : AuthenticAMD +cpu family : 16 +model : 4 +model name : Quad-Core AMD Opteron(tm) Processor 2374 HE +stepping : 2 +cpu MHz : 1386667.908 +cache size : 512 KB +fpu : yes +fpu_exception : yes +cpuid level : 5 +wp : yes +flags : fpu de tsc msr pae cx8 cmov pat clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt lm 3dnowext 3dnow constant_tsc rep_good nonstop_tsc pni cx16 popcnt lahf_lm cmp_legacy extapic cr8_legacy abm sse4a misalignsse 3dnowprefetch +bogomips : 4400.17 +TLB size : 1024 4K pages +clflush size : 64 +cache_alignment : 64 +address sizes : 48 bits physical, 48 bits virtual +power management: ts ttp tm stc 100mhzsteps hwpstate + +processor : 1 +vendor_id : AuthenticAMD +cpu family : 16 +model : 4 +model name : Quad-Core AMD Opteron(tm) Processor 2374 HE +stepping : 2 +cpu MHz : 1386667.908 +cache size : 512 KB +fpu : yes +fpu_exception : yes +cpuid level : 5 +wp : yes +flags : fpu de tsc msr pae cx8 cmov pat clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt lm 3dnowext 3dnow constant_tsc rep_good nonstop_tsc pni cx16 popcnt lahf_lm cmp_legacy extapic cr8_legacy abm sse4a misalignsse 3dnowprefetch +bogomips : 4400.17 +TLB size : 1024 4K pages +clflush size : 64 +cache_alignment : 64 +address sizes : 48 bits physical, 48 bits virtual +power management: ts ttp tm stc 100mhzsteps hwpstate + +processor : 2 +vendor_id : AuthenticAMD +cpu family : 16 +model : 4 +model name : Quad-Core AMD Opteron(tm) Processor 2374 HE +stepping : 2 +cpu MHz : 1386667.908 +cache size : 512 KB +fpu : yes +fpu_exception : yes +cpuid level : 5 +wp : yes +flags : fpu de tsc msr pae cx8 cmov pat clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt lm 3dnowext 3dnow constant_tsc rep_good nonstop_tsc pni cx16 popcnt lahf_lm cmp_legacy extapic cr8_legacy abm sse4a misalignsse 3dnowprefetch +bogomips : 4400.17 +TLB size : 1024 4K pages +clflush size : 64 +cache_alignment : 64 +address sizes : 48 bits physical, 48 bits virtual +power management: ts ttp tm stc 100mhzsteps hwpstate + +processor : 3 +vendor_id : AuthenticAMD +cpu family : 16 +model : 4 +model name : Quad-Core AMD Opteron(tm) Processor 2374 HE +stepping : 2 +cpu MHz : 1386667.908 +cache size : 512 KB +fpu : yes +fpu_exception : yes +cpuid level : 5 +wp : yes +flags : fpu de tsc msr pae cx8 cmov pat clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt lm 3dnowext 3dnow constant_tsc rep_good nonstop_tsc pni cx16 popcnt lahf_lm cmp_legacy extapic cr8_legacy abm sse4a misalignsse 3dnowprefetch +bogomips : 4400.17 +TLB size : 1024 4K pages +clflush size : 64 +cache_alignment : 64 +address sizes : 48 bits physical, 48 bits virtual +power management: ts ttp tm stc 100mhzsteps hwpstate diff --git a/spec/fixtures/cpuinfo/amd64solo b/spec/fixtures/cpuinfo/amd64solo new file mode 100644 index 0000000000..09c3bb465d --- /dev/null +++ b/spec/fixtures/cpuinfo/amd64solo @@ -0,0 +1,23 @@ +processor : 0 +vendor_id : GenuineIntel +cpu family : 6 +model : 23 +model name : Intel(R) Core(TM)2 Duo CPU P8700 @ 2.53GHz +stepping : 10 +cpu MHz : 600.825 +cache size : 6144 KB +fdiv_bug : no +hlt_bug : no +f00f_bug : no +coma_bug : no +fpu : yes +fpu_exception : yes +cpuid level : 5 +wp : yes +flags : fpu vme de pse tsc msr mce cx8 apic mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 constant_tsc up pni monitor ssse3 +bogomips : 1201.65 +clflush size : 64 +cache_alignment : 64 +address sizes : 36 bits physical, 48 bits virtual +power management: + diff --git a/spec/fixtures/cpuinfo/amd64tri b/spec/fixtures/cpuinfo/amd64tri new file mode 100644 index 0000000000..993c49c207 --- /dev/null +++ b/spec/fixtures/cpuinfo/amd64tri @@ -0,0 +1,86 @@ +processor : 0 +vendor_id : GenuineIntel +cpu family : 6 +model : 23 +model name : Intel(R) Core(TM)2 Duo CPU P8700 @ 2.53GHz +stepping : 10 +cpu MHz : 910.177 +cache size : 6144 KB +physical id : 0 +siblings : 3 +core id : 0 +cpu cores : 3 +apicid : 0 +initial apicid : 0 +fdiv_bug : no +hlt_bug : no +f00f_bug : no +coma_bug : no +fpu : yes +fpu_exception : yes +cpuid level : 5 +wp : yes +flags : fpu vme de pse tsc msr mce cx8 apic mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht constant_tsc pni ssse3 +bogomips : 1820.35 +clflush size : 64 +cache_alignment : 64 +address sizes : 36 bits physical, 48 bits virtual +power management: + +processor : 1 +vendor_id : GenuineIntel +cpu family : 6 +model : 23 +model name : Intel(R) Core(TM)2 Duo CPU P8700 @ 2.53GHz +stepping : 10 +cpu MHz : 910.177 +cache size : 6144 KB +physical id : 0 +siblings : 3 +core id : 1 +cpu cores : 3 +apicid : 1 +initial apicid : 1 +fdiv_bug : no +hlt_bug : no +f00f_bug : no +coma_bug : no +fpu : yes +fpu_exception : yes +cpuid level : 5 +wp : yes +flags : fpu vme de pse tsc msr mce cx8 apic mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht constant_tsc pni ssse3 +bogomips : 1875.49 +clflush size : 64 +cache_alignment : 64 +address sizes : 36 bits physical, 48 bits virtual +power management: + +processor : 2 +vendor_id : GenuineIntel +cpu family : 6 +model : 23 +model name : Intel(R) Core(TM)2 Duo CPU P8700 @ 2.53GHz +stepping : 10 +cpu MHz : 910.177 +cache size : 6144 KB +physical id : 0 +siblings : 3 +core id : 2 +cpu cores : 3 +apicid : 2 +initial apicid : 2 +fdiv_bug : no +hlt_bug : no +f00f_bug : no +coma_bug : no +fpu : yes +fpu_exception : yes +cpuid level : 5 +wp : yes +flags : fpu vme de pse tsc msr mce cx8 apic mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht constant_tsc pni ssse3 +bogomips : 1523.26 +clflush size : 64 +cache_alignment : 64 +address sizes : 36 bits physical, 48 bits virtual +power management: diff --git a/spec/fixtures/cpuinfo/bbg3-armel b/spec/fixtures/cpuinfo/bbg3-armel new file mode 100644 index 0000000000..6dd9d49a5c --- /dev/null +++ b/spec/fixtures/cpuinfo/bbg3-armel @@ -0,0 +1,12 @@ +Processor : ARMv7 Processor rev 5 (v7l) +BogoMIPS : 799.53 +Features : swp half thumb fastmult vfp edsp thumbee neon vfpv3 +CPU implementer : 0x41 +CPU architecture: 7 +CPU variant : 0x2 +CPU part : 0xc08 +CPU revision : 5 + +Hardware : Freescale MX51 Babbage Board +Revision : 51130 +Serial : 0000000000000000 diff --git a/spec/fixtures/cpuinfo/beaglexm-armel b/spec/fixtures/cpuinfo/beaglexm-armel new file mode 100644 index 0000000000..1de8986c41 --- /dev/null +++ b/spec/fixtures/cpuinfo/beaglexm-armel @@ -0,0 +1,12 @@ +Processor : ARMv7 Processor rev 2 (v7l) +BogoMIPS : 506.27 +Features : swp half thumb fastmult vfp edsp neon vfpv3 +CPU implementer : 0x41 +CPU architecture: 7 +CPU variant : 0x3 +CPU part : 0xc08 +CPU revision : 2 + +Hardware : OMAP3 Beagle Board +Revision : 0020 +Serial : 0000000000000000 diff --git a/spec/fixtures/cpuinfo/panda-armel b/spec/fixtures/cpuinfo/panda-armel new file mode 100644 index 0000000000..18b21a72bb --- /dev/null +++ b/spec/fixtures/cpuinfo/panda-armel @@ -0,0 +1,17 @@ +Processor : ARMv7 Processor rev 2 (v7l) +processor : 0 +BogoMIPS : 2013.45 + +processor : 1 +BogoMIPS : 1963.05 + +Features : swp half thumb fastmult vfp edsp thumbee neon vfpv3 +CPU implementer : 0x41 +CPU architecture: 7 +CPU variant : 0x1 +CPU part : 0xc09 +CPU revision : 2 + +Hardware : OMAP4430 Panda Board +Revision : 0020 +Serial : 0000000000000000 diff --git a/spec/fixtures/cpuinfo/ppc64 b/spec/fixtures/cpuinfo/ppc64 new file mode 100644 index 0000000000..4375a9794c --- /dev/null +++ b/spec/fixtures/cpuinfo/ppc64 @@ -0,0 +1,19 @@ +processor : 0 +cpu : PPC970FX, altivec supported +clock : 2000.000000MHz +revision : 3.0 (pvr 003c 0300) + +processor : 1 +cpu : PPC970FX, altivec supported +clock : 2000.000000MHz +revision : 3.0 (pvr 003c 0300) + +timebase : 33333333 +platform : PowerMac +model : RackMac3,1 +machine : RackMac3,1 +motherboard : RackMac3,1 MacRISC4 Power Macintosh +detected as : 339 (XServe G5) +pmac flags : 00000000 +L2 cache : 512K unified +pmac-generation : NewWorld diff --git a/spec/fixtures/cpuinfo/sparc b/spec/fixtures/cpuinfo/sparc new file mode 100644 index 0000000000..c186ac850d --- /dev/null +++ b/spec/fixtures/cpuinfo/sparc @@ -0,0 +1,10 @@ +cpu : TI UltraSparc IIIi (Jalapeno) +fpu : UltraSparc IIIi integrated FPU +prom : OBP 4.16.2 2004/10/04 18:22 +type : sun4u +ncpus probed : 1 +ncpus active : 1 +D$ parity tl1 : 0 +I$ parity tl1 : 0 +Cpu0ClkTck : 000000004fa1be00 +MMU Type : Cheetah+ diff --git a/spec/unit/processor_spec.rb b/spec/unit/processor_spec.rb index 6cbe971e0c..b939d94777 100755 --- a/spec/unit/processor_spec.rb +++ b/spec/unit/processor_spec.rb @@ -1,9 +1,14 @@ #!/usr/bin/env ruby -require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') +$basedir = File.expand_path(File.dirname(__FILE__) + '/..') +require File.join($basedir, 'spec_helper') require 'facter' +def cpuinfo_fixture(filename) + cpuinfo = File.open(File.join($basedir, 'fixtures', 'cpuinfo', filename)).readlines +end + describe "Processor facts" do describe "on Windows" do before :each do @@ -80,4 +85,156 @@ def load(procs) Facter.fact(:processorcount).value.should == 8 end end + + describe "on Unixes" do + before :each do + Facter.collection.loader.load(:processor) + end + + it "should be 1 in SPARC fixture" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:operatingsystem).stubs(:value).returns("Linux") + Facter.fact(:architecture).stubs(:value).returns("sparc") + File.stubs(:exists?).with("/proc/cpuinfo").returns(true) + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("sparc")) + + Facter.fact(:processorcount).value.should == "1" + end + + it "should be 2 in ppc64 fixture on Linux" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:architecture).stubs(:value).returns("ppc64") + File.stubs(:exists?).with("/proc/cpuinfo").returns(true) + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("ppc64")) + + Facter.fact(:processorcount).value.should == "2" + end + + it "should be 2 in panda-armel fixture on Linux" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:architecture).stubs(:value).returns("arm") + File.stubs(:exists?).with("/proc/cpuinfo").returns(true) + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("panda-armel")) + + Facter.fact(:processorcount).value.should == "2" + end + + it "should be 1 in bbg3-armel fixture on Linux" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:architecture).stubs(:value).returns("arm") + File.stubs(:exists?).with("/proc/cpuinfo").returns(true) + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("bbg3-armel")) + + Facter.fact(:processorcount).value.should == "1" + end + + it "should be 1 in beaglexm-armel fixture on Linux" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:architecture).stubs(:value).returns("arm") + File.stubs(:exists?).with("/proc/cpuinfo").returns(true) + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("beaglexm-armel")) + + Facter.fact(:processorcount).value.should == "1" + end + + it "should be 1 in amd64solo fixture on Linux" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:architecture).stubs(:value).returns("amd64") + File.stubs(:exists?).with("/proc/cpuinfo").returns(true) + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("amd64solo")) + + Facter.fact(:processorcount).value.should == "1" + end + + it "should be 2 in amd64dual fixture on Linux" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:architecture).stubs(:value).returns("amd64") + File.stubs(:exists?).with("/proc/cpuinfo").returns(true) + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("amd64dual")) + + Facter.fact(:processorcount).value.should == "2" + end + + it "should be 3 in amd64tri fixture on Linux" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:architecture).stubs(:value).returns("amd64") + File.stubs(:exists?).with("/proc/cpuinfo").returns(true) + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("amd64tri")) + + Facter.fact(:processorcount).value.should == "3" + end + + it "should be 4 in amd64quad fixture on Linux" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:architecture).stubs(:value).returns("amd64") + File.stubs(:exists?).with("/proc/cpuinfo").returns(true) + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("amd64quad")) + + Facter.fact(:processorcount).value.should == "4" + end + + it "should be 2 on dual-processor Darwin box" do + Facter.fact(:kernel).stubs(:value).returns("Darwin") + Facter::Util::Resolution.stubs(:exec).with("sysctl hw.ncpu | cut -d' ' -f2").returns('2') + + Facter.fact(:processorcount).value.should == "2" + end + + it "should be 2 on dual-processor OpenBSD box" do + Facter.fact(:kernel).stubs(:value).returns("OpenBSD") + Facter::Util::Resolution.stubs(:exec).with("sysctl hw.ncpu | cut -d'=' -f2").returns('2') + + Facter.fact(:processorcount).value.should == "2" + end + + it "should be 6 on six-processor AIX box" do + Facter.fact(:kernel).stubs(:value).returns("AIX") + Facter::Util::Resolution.stubs(:exec).with("lsdev -Cc processor").returns("proc0 Available 00-00 Processor\nproc2 Available 00-02 Processor\nproc4 Available 00-04 Processor\nproc6 Available 00-06 Processor\nproc8 Available 00-08 Processor\nproc10 Available 00-10 Processor") + Facter::Util::Resolution.stubs(:exec).with("lsattr -El proc0 -a type").returns("type PowerPC_POWER3 Processor type False") + + Facter.fact(:processorcount).value.should == "6" + end + + it "should be 2 via sysfs when cpu0 and cpu1 are present" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + File.stubs(:exists?).with('/sys/devices/system/cpu').returns(true) + ## sysfs method is only used if cpuinfo method returned no processors + File.stubs(:exists?).with("/proc/cpuinfo").returns(true) + File.stubs(:readlines).with("/proc/cpuinfo").returns("") + Dir.stubs(:glob).with("/sys/devices/system/cpu/cpu[0-9]*").returns(%w{ + /sys/devices/system/cpu/cpu0 + /sys/devices/system/cpu/cpu1 + }) + + Facter.fact(:processorcount).value.should == "2" + end + + it "should be 16 via sysfs when cpu0 through cpu15 are present" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + File.stubs(:exists?).with('/sys/devices/system/cpu').returns(true) + ## sysfs method is only used if cpuinfo method returned no processors + File.stubs(:exists?).with("/proc/cpuinfo").returns(true) + File.stubs(:readlines).with("/proc/cpuinfo").returns("") + Dir.stubs(:glob).with("/sys/devices/system/cpu/cpu[0-9]*").returns(%w{ + /sys/devices/system/cpu/cpu0 + /sys/devices/system/cpu/cpu1 + /sys/devices/system/cpu/cpu2 + /sys/devices/system/cpu/cpu3 + /sys/devices/system/cpu/cpu4 + /sys/devices/system/cpu/cpu5 + /sys/devices/system/cpu/cpu6 + /sys/devices/system/cpu/cpu7 + /sys/devices/system/cpu/cpu8 + /sys/devices/system/cpu/cpu9 + /sys/devices/system/cpu/cpu10 + /sys/devices/system/cpu/cpu11 + /sys/devices/system/cpu/cpu12 + /sys/devices/system/cpu/cpu13 + /sys/devices/system/cpu/cpu14 + /sys/devices/system/cpu/cpu15 + }) + + Facter.fact(:processorcount).value.should == "16" + end + end end diff --git a/spec/unit/util/processor_spec.rb b/spec/unit/util/processor_spec.rb new file mode 100755 index 0000000000..634b1b174a --- /dev/null +++ b/spec/unit/util/processor_spec.rb @@ -0,0 +1,62 @@ +#!/usr/bin/env ruby + +$basedir = File.expand_path(File.dirname(__FILE__) + '/../..') +require File.join($basedir, 'spec_helper') + +require 'facter/util/processor' + +def cpuinfo_fixture(filename) + cpuinfo = File.open(File.join($basedir, 'fixtures', 'cpuinfo', filename)).readlines +end + +describe Facter::Util::Processor do + it "should get the processor description from the amd64solo fixture" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:architecture).stubs(:value).returns("amd64") + File.stubs(:exists?).with("/proc/cpuinfo").returns(true) + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("amd64solo")) + + Facter::Util::Processor.enum_cpuinfo[0].should == "Intel(R) Core(TM)2 Duo CPU P8700 @ 2.53GHz" + end + + it "should get the processor descriptions from the amd64dual fixture" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:architecture).stubs(:value).returns("amd64") + File.stubs(:exists?).with("/proc/cpuinfo").returns(true) + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("amd64dual")) + + Facter::Util::Processor.enum_cpuinfo[0].should == "Intel(R) Core(TM)2 Duo CPU P8700 @ 2.53GHz" + Facter::Util::Processor.enum_cpuinfo[1].should == "Intel(R) Core(TM)2 Duo CPU P8700 @ 2.53GHz" + end + + it "should get the processor descriptions from the amd64tri fixture" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:architecture).stubs(:value).returns("amd64") + File.stubs(:exists?).with("/proc/cpuinfo").returns(true) + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("amd64tri")) + + Facter::Util::Processor.enum_cpuinfo[0].should == "Intel(R) Core(TM)2 Duo CPU P8700 @ 2.53GHz" + Facter::Util::Processor.enum_cpuinfo[1].should == "Intel(R) Core(TM)2 Duo CPU P8700 @ 2.53GHz" + Facter::Util::Processor.enum_cpuinfo[2].should == "Intel(R) Core(TM)2 Duo CPU P8700 @ 2.53GHz" + end + + it "should get the processor descriptions from the amd64quad fixture" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:architecture).stubs(:value).returns("amd64") + File.stubs(:exists?).with("/proc/cpuinfo").returns(true) + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("amd64quad")) + + Facter::Util::Processor.enum_cpuinfo[0].should == "Quad-Core AMD Opteron(tm) Processor 2374 HE" + Facter::Util::Processor.enum_cpuinfo[1].should == "Quad-Core AMD Opteron(tm) Processor 2374 HE" + Facter::Util::Processor.enum_cpuinfo[2].should == "Quad-Core AMD Opteron(tm) Processor 2374 HE" + Facter::Util::Processor.enum_cpuinfo[3].should == "Quad-Core AMD Opteron(tm) Processor 2374 HE" + end + + it "should get the processor type on AIX box" do + Facter.fact(:kernel).stubs(:value).returns("AIX") + Facter::Util::Resolution.stubs(:exec).with("lsdev -Cc processor").returns("proc0 Available 00-00 Processor\nproc2 Available 00-02 Processor\nproc4 Available 00-04 Processor\nproc6 Available 00-06 Processor\nproc8 Available 00-08 Processor\nproc10 Available 00-10 Processor") + Facter::Util::Resolution.stubs(:exec).with("lsattr -El proc0 -a type").returns("type PowerPC_POWER3 Processor type False") + + Facter::Util::Processor.enum_lsdev[0].should == "PowerPC_POWER3" + end +end From af1ef43769ba8f4c1b27e6d23b8f0ea457958ec7 Mon Sep 17 00:00:00 2001 From: Ken Barber Date: Wed, 28 Sep 2011 13:23:13 -0700 Subject: [PATCH 0661/3753] (#6515) Fix for ruby-1.8.5. Switched use of 'line.each' to 'each_line'. --- lib/facter/util/processor.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/util/processor.rb b/lib/facter/util/processor.rb index b5d44c8876..d4b4de9048 100644 --- a/lib/facter/util/processor.rb +++ b/lib/facter/util/processor.rb @@ -70,7 +70,7 @@ def self.enum_lsdev Thread::exclusive do procs = Facter::Util::Resolution.exec('lsdev -Cc processor') if procs - procs.lines.each do |proc| + procs.each_line do |proc| if proc =~ /^proc(\d+)/ processor_num = $1.to_i # Not retrieving the frequency since AIX 4.3.3 doesn't support the From 8f938c18adfa65bf88712f1f648670325b744ae5 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 7 Jun 2011 17:04:21 -0700 Subject: [PATCH 0662/3753] (#6792) Added osfamily fact. Added osfamily fact to determine if a given operating system is a derivative of a common operating system. --- lib/facter/osfamily.rb | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 lib/facter/osfamily.rb diff --git a/lib/facter/osfamily.rb b/lib/facter/osfamily.rb new file mode 100644 index 0000000000..038eaab760 --- /dev/null +++ b/lib/facter/osfamily.rb @@ -0,0 +1,31 @@ +# Fact: osfamily +# +# Purpose: Returns the operating system +# +# Resolution: +# Maps operating systems to operating system families, such as linux +# distribution derivatives. Adds mappings from specific operating systems +# to kernels in the case that it is relevant. +# +# Caveats: +# This fact is completely reliant on the operatingsystem fact, and no +# heuristics are used +# + +Facter.add(:osfamily) do + + setcode do + case Facter.value(:operatingsystem) + when "RedHat", "Fedora", "CentOS", "Scientific", "SLC", "CloudLinux", "OracleLinux", "OVS", "OEL" + "RedHat" + when "Ubuntu", "Debian" + "Debian" + when "SLES", "SLED", "OpenSuSE", "SuSE" + "Suse" + when "Solaris" + "Solaris" + else + Facter.value("kernel") + end + end +end From c647f0e42c3609668e62936a1b615f0e413a3d62 Mon Sep 17 00:00:00 2001 From: Matthaus Litteken Date: Wed, 28 Sep 2011 16:51:54 -0700 Subject: [PATCH 0663/3753] Updated CHANGELOG for 1.6.1 final. Signed-off-by: Matthaus Litteken --- CHANGELOG | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 25cf819351..b7b9e83f0a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,24 +1,17 @@ -1.6.1rc4 +1.6.1 === +1f009e0 Updated CHANGELOG for 1.6.1rc4 3117e82 (#9517) Fix physicalprocessorcount on windows - -1.6.1rc3 -=== +ef0a9aa Updated CHANGELOG for 1.6.1rc3 4d93745 (#8491) Prevent repeated loading of fact files 6db71d4 (#9457) Fix logic for domain fact so hostname, then dnsdomainname and finally resolv.conf is used. - -1.6.1rc2 -=== -9cdc87f Updated CHANGELOG for 1.6.1rc2 +18cd964 Updated CHANGELOG for 1.6.1rc2 7edef60 Change count to length for compatibility 88f343c (#2344) VMware version parsing fix 7457fe5 SELinux spec test fix for ubuntu 8bed5bb Clear messages between test runs f4ad6bf Macaddress spec test needed operatingsystem fact 6c098cc Rakefile should fail on error - -1.6.1rc1 -=== 42c5471 Updated CHANGELOG for 1.6.1rc1. 6d47012 (#4869) Implement productname as Darwin hw.model d28d96c (#4508) Xen HVM domU not detected as virtual @@ -55,6 +48,8 @@ c1b631d Maint: Refactor detection of windows platform 15d0406 use each_line instead of each for strings in ruby 1.9 08b3f77 (#7854) Add Augeas library version fact e84c051 Fixed #7307 - Added serial number fact to Solaris +6fb6ee5 (#4869) Implement productname as Darwin hw.model +63c8b11 (#4508) Xen HVM domU not detected as virtual 1.6.0 === From dfda9be074ebd42ea14fec98cca00247a891b7c4 Mon Sep 17 00:00:00 2001 From: Michael Kincaid Date: Thu, 7 Apr 2011 10:06:34 -0700 Subject: [PATCH 0664/3753] (#4980, #6470) Fix architecture in Darwin and Ubuntu Architecture now relies on the hardwaremodel fact unless special cased otherwise, such as for linux distributions that require amd64 as the expected architecture. Ubuntu was added as a special case, OpenBSD was collapsed into the current architecture fact and Darwin was added by removing the kernel confine statement for the architecture fact. --- lib/facter/architecture.rb | 10 +------ spec/unit/architecture_spec.rb | 54 ++++++++++++++++++++++++++++++++++ spec/unit/virtual_spec.rb | 3 ++ 3 files changed, 58 insertions(+), 9 deletions(-) create mode 100755 spec/unit/architecture_spec.rb diff --git a/lib/facter/architecture.rb b/lib/facter/architecture.rb index cd606a2034..5be1bcbb31 100644 --- a/lib/facter/architecture.rb +++ b/lib/facter/architecture.rb @@ -12,14 +12,13 @@ # Facter.add(:architecture) do - confine :kernel => [:linux, :"gnu/kfreebsd"] setcode do model = Facter.value(:hardwaremodel) case model # most linuxen use "x86_64" when "x86_64" case Facter.value(:operatingsystem) - when "Debian", "Gentoo", "GNU/kFreeBSD" + when "Debian", "Gentoo", "GNU/kFreeBSD", "Ubuntu" "amd64" else model @@ -37,10 +36,3 @@ end end -Facter.add(:architecture) do - confine :kernel => :openbsd - setcode do - architecture = Facter.value(:hardwaremodel) - end -end - diff --git a/spec/unit/architecture_spec.rb b/spec/unit/architecture_spec.rb new file mode 100755 index 0000000000..253de579b8 --- /dev/null +++ b/spec/unit/architecture_spec.rb @@ -0,0 +1,54 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') + +require 'facter' + +describe "Architecture fact" do + + it "should default to the hardware model" do + Facter.fact(:hardwaremodel).stubs(:value).returns("NonSpecialCasedHW") + + Facter.fact(:architecture).value.should == "NonSpecialCasedHW" + end + + os_archs = Hash.new + os_archs = { + ["Debian","x86_64"] => "amd64", + ["Gentoo","x86_64"] => "amd64", + ["GNU/kFreeBSD","x86_64"] => "amd64", + ["Ubuntu","x86_64"] => "amd64", + ["Gentoo","i386"] => "x86", + ["Gentoo","i486"] => "x86", + ["Gentoo","i586"] => "x86", + ["Gentoo","i686"] => "x86", + ["Gentoo","pentium"] => "x86", + } + generic_archs = Hash.new + generic_archs = { + "i386" => "i386", + "i486" => "i386", + "i586" => "i386", + "i686" => "i386", + "pentium" => "i386", + } + + os_archs.each do |pair, result| + it "should be #{result} if os is #{pair[0]} and hardwaremodel is #{pair[1]}" do + Facter.fact(:operatingsystem).stubs(:value).returns(pair[0]) + Facter.fact(:hardwaremodel).stubs(:value).returns(pair[1]) + + Facter.fact(:architecture).value.should == result + end + end + + generic_archs.each do |hw, result| + it "should be #{result} if hardwaremodel is #{hw}" do + Facter.fact(:hardwaremodel).stubs(:value).returns(hw) + Facter.fact(:operatingsystem).stubs(:value).returns("NonSpecialCasedOS") + + Facter.fact(:architecture).value.should == result + end + end + +end diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index fc7a9261a0..72182c0df5 100644 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -160,6 +160,7 @@ it "should be vmware with VMWare vendor name from prtdiag" do Facter.fact(:kernel).stubs(:value).returns("SunOS") + Facter.fact(:hardwaremodel).stubs(:value).returns(nil) Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns(nil) Facter::Util::Resolution.stubs(:exec).with('prtdiag').returns("System Configuration: VMware, Inc. VMware Virtual Platform") @@ -168,6 +169,7 @@ it "should be parallels with Parallels vendor name from prtdiag" do Facter.fact(:kernel).stubs(:value).returns("SunOS") + Facter.fact(:hardwaremodel).stubs(:value).returns(nil) Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns(nil) Facter::Util::Resolution.stubs(:exec).with('prtdiag').returns("System Configuration: Parallels Virtual Platform") @@ -176,6 +178,7 @@ it "should be virtualbox with VirtualBox vendor name from prtdiag" do Facter.fact(:kernel).stubs(:value).returns("SunOS") + Facter.fact(:hardwaremodel).stubs(:value).returns(nil) Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns(nil) Facter::Util::Resolution.stubs(:exec).with('prtdiag').returns("System Configuration: innotek GmbH VirtualBox") From db3c606d2bf7c3aa522e81916c5c13654619085b Mon Sep 17 00:00:00 2001 From: Ken Barber Date: Wed, 28 Sep 2011 17:26:10 -0700 Subject: [PATCH 0665/3753] (#9786) Add aliases: specs, tests, test in rake that points at 'spec'. These are just convenience aliases: * test: because it makes sense to alias test to spec * specs & tests: to support rvm specs and tests respectively. --- Rakefile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Rakefile b/Rakefile index 1b9cb61587..5b0d0729a3 100644 --- a/Rakefile +++ b/Rakefile @@ -62,6 +62,11 @@ task :default do sh %{rake -T} end +# Aliases for spec +task :test => [:spec] +task :tests => [:spec] +task :specs => [:spec] + RSpec::Core::RakeTask.new do |t| t.pattern ='spec/{unit,integration}/**/*_spec.rb' t.fail_on_error = true From b579613948586ce42b6e15cbcdc3db843bb00b60 Mon Sep 17 00:00:00 2001 From: adrienthebo Date: Tue, 31 May 2011 17:08:30 -0700 Subject: [PATCH 0666/3753] (#7726) Silence prtconf error message inside zones prtconf will output an error message when run inside a zone, which clutters up facter output. Redirected the stderr to /dev/null. --- lib/facter/memory.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb index 8c20813ec0..965a487102 100644 --- a/lib/facter/memory.rb +++ b/lib/facter/memory.rb @@ -189,7 +189,7 @@ end # Total memory size available from prtconf - pconf = Facter::Util::Resolution.exec('/usr/sbin/prtconf') + pconf = Facter::Util::Resolution.exec('/usr/sbin/prtconf 2>/dev/null') phymem = "" pconf.each_line do |line| if line =~ /^Memory size:\s+(\d+) Megabytes/ From 8605bba655a2bd2cb50c0607d99c09e390bf2d57 Mon Sep 17 00:00:00 2001 From: Ken Barber Date: Wed, 28 Sep 2011 17:55:26 -0700 Subject: [PATCH 0667/3753] (#9787) Change rspec format so we use the default, not document MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Current running rake spec in facter means we get the document output which is very verbose. Unfortunately we are forcing this in our .rspec file so you can’t override it on a user by user basis with ~/.rspec for example. I think we should not define —format, which means the default is progress (which is less verbose and better for the average Joe and hacker who just wants red light/green light) and then if people really want document format they can override this in their own ~/.rspec file. This way its the best of both worlds – more meaningful defaults and allowing user overrides. --- .rspec | 1 - 1 file changed, 1 deletion(-) diff --git a/.rspec b/.rspec index 4f1425c11c..53607ea52b 100644 --- a/.rspec +++ b/.rspec @@ -1,2 +1 @@ ---format s --colour From b3784f78b1e1c9949f7ab29f09f6ef24a2993313 Mon Sep 17 00:00:00 2001 From: Jonathan Boyett Date: Thu, 15 Sep 2011 12:46:20 -0700 Subject: [PATCH 0668/3753] add operatingsystema and operatingsystemrelease support for cloudlinux --- lib/facter/operatingsystem.rb | 2 ++ lib/facter/operatingsystemrelease.rb | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index ff802c3652..cf372201e9 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -57,6 +57,8 @@ "SLC" elsif txt =~ /scientific/i "Scientific" + elsif txt =~ /^cloudlinux/i + "CloudLinux" else "RedHat" end diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index b1fa571d79..3a535ec057 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -16,10 +16,10 @@ # Facter.add(:operatingsystemrelease) do - confine :operatingsystem => %w{CentOS Fedora oel ovs OracleLinux RedHat MeeGo Scientific SLC} + confine :operatingsystem => %w{CentOS Fedora oel ovs OracleLinux RedHat MeeGo Scientific SLC CloudLinux} setcode do case Facter.value(:operatingsystem) - when "CentOS", "RedHat", "Scientific", "SLC" + when "CentOS", "RedHat", "Scientific", "SLC", "CloudLinux" releasefile = "/etc/redhat-release" when "Fedora" releasefile = "/etc/fedora-release" From 3f99f16dd1a3eba7f3f6006989e42e2423aa2a23 Mon Sep 17 00:00:00 2001 From: Matt Dainty Date: Sat, 10 Dec 2011 23:39:03 +0000 Subject: [PATCH 0669/3753] (#11328) Add virtualisation detection for OpenBSD Extend the FreeBSD KVM detection as it's the same trick with "sysctl hw.model" and use "sysctl hw.product" to detect VMware, Parallels, VirtualBox and Xen HVM. --- lib/facter/util/virtual.rb | 2 +- lib/facter/virtual.rb | 10 ++++++++++ spec/unit/util/virtual_spec.rb | 7 +++++++ spec/unit/virtual_spec.rb | 30 ++++++++++++++++++++++++++++++ 4 files changed, 48 insertions(+), 1 deletion(-) diff --git a/lib/facter/util/virtual.rb b/lib/facter/util/virtual.rb index 01b71b37b9..e6b822127b 100644 --- a/lib/facter/util/virtual.rb +++ b/lib/facter/util/virtual.rb @@ -55,7 +55,7 @@ def self.xen? def self.kvm? txt = if FileTest.exists?("/proc/cpuinfo") File.read("/proc/cpuinfo") - elsif Facter.value(:kernel)=="FreeBSD" + elsif ["FreeBSD", "OpenBSD"].include? Facter.value(:kernel) Facter::Util::Resolution.exec("/sbin/sysctl -n hw.model") end (txt =~ /QEMU Virtual CPU/) ? true : false diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index e617359472..6e8c91f7ce 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -131,6 +131,16 @@ result = "xenhvm" if pd =~ /HVM domU/ end end + elsif Facter.value(:kernel) == 'OpenBSD' + output = Facter::Util::Resolution.exec('sysctl -n hw.product 2>/dev/null') + if not output.nil? + output.each_line do |pd| + result = "parallels" if pd =~ /Parallels/ + result = "vmware" if pd =~ /VMware/ + result = "virtualbox" if pd =~ /VirtualBox/ + result = "xenhvm" if pd =~ /HVM domU/ + end + end end end diff --git a/spec/unit/util/virtual_spec.rb b/spec/unit/util/virtual_spec.rb index 52d656824e..a4c51a9fdb 100644 --- a/spec/unit/util/virtual_spec.rb +++ b/spec/unit/util/virtual_spec.rb @@ -149,6 +149,13 @@ Facter::Util::Virtual.should be_kvm end + it "should detect kvm on OpenBSD" do + FileTest.stubs(:exists?).with("/proc/cpuinfo").returns(false) + Facter.fact(:kernel).stubs(:value).returns("OpenBSD") + Facter::Util::Resolution.stubs(:exec).with("/sbin/sysctl -n hw.model").returns('QEMU Virtual CPU version (cpu64-rhel6) ("AuthenticAMD" 686-class, 512KB L2 cache)') + Facter::Util::Virtual.should be_kvm + end + it "should identify FreeBSD jail when in jail" do Facter.fact(:kernel).stubs(:value).returns("FreeBSD") Facter::Util::Resolution.stubs(:exec).with("/sbin/sysctl -n security.jail.jailed").returns("1") diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index 47202de1e9..367ff2d17a 100644 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -204,6 +204,36 @@ Facter.fact(:virtual).value.should == "virtualbox" end end + + describe "on OpenBSD" do + before do + Facter::Util::Resolution.stubs(:exec).with("vmware -v").returns false + Facter.fact(:kernel).stubs(:value).returns("OpenBSD") + Facter.fact(:hardwaremodel).stubs(:value).returns(nil) + Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) + Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns(nil) + end + + it "should be parallels with Parallels product name from sysctl" do + Facter::Util::Resolution.stubs(:exec).with('sysctl -n hw.product 2>/dev/null').returns("Parallels Virtual Platform") + Facter.fact(:virtual).value.should == "parallels" + end + + it "should be vmware with VMware product name from sysctl" do + Facter::Util::Resolution.stubs(:exec).with('sysctl -n hw.product 2>/dev/null').returns("VMware Virtual Platform") + Facter.fact(:virtual).value.should == "vmware" + end + + it "should be virtualbox with VirtualBox product name from sysctl" do + Facter::Util::Resolution.stubs(:exec).with('sysctl -n hw.product 2>/dev/null').returns("VirtualBox") + Facter.fact(:virtual).value.should == "virtualbox" + end + + it "should be xenhvm with Xen HVM product name from sysctl" do + Facter::Util::Resolution.stubs(:exec).with('sysctl -n hw.product 2>/dev/null').returns("HVM domU") + Facter.fact(:virtual).value.should == "xenhvm" + end + end end describe "is_virtual fact" do From 4f9da1cbe14593d18cff49ace2a978ad4f7c84e9 Mon Sep 17 00:00:00 2001 From: Matt Dainty Date: Sat, 10 Dec 2011 23:39:42 +0000 Subject: [PATCH 0670/3753] (#11328) Fix uptime detection on OpenBSD Of the four or so uptime detection methods, the sysctl one is the closest however OpenBSD lacks the -b option. Using -n instead the first number in the output will be the epoch seconds, on OpenBSD this is all you get, but with Darwin (and probably the other *BSD's) you get the epoch seconds, microseconds and the pretty-printed output. New test cases added and the old ones with the endian-specific packed output are removed. --- lib/facter/util/uptime.rb | 4 ++-- .../uptime/sysctl_kern_boottime_big_endian | Bin 20 -> 0 bytes .../uptime/sysctl_kern_boottime_darwin | 1 + .../uptime/sysctl_kern_boottime_little_endian | Bin 16 -> 0 bytes .../uptime/sysctl_kern_boottime_openbsd | 1 + spec/unit/util/uptime_spec.rb | 18 ++++++++++-------- 6 files changed, 14 insertions(+), 10 deletions(-) delete mode 100644 spec/fixtures/uptime/sysctl_kern_boottime_big_endian create mode 100644 spec/fixtures/uptime/sysctl_kern_boottime_darwin delete mode 100644 spec/fixtures/uptime/sysctl_kern_boottime_little_endian create mode 100644 spec/fixtures/uptime/sysctl_kern_boottime_openbsd diff --git a/lib/facter/util/uptime.rb b/lib/facter/util/uptime.rb index 772cc87762..6a1ca22c64 100644 --- a/lib/facter/util/uptime.rb +++ b/lib/facter/util/uptime.rb @@ -27,7 +27,7 @@ def self.uptime_proc_uptime def self.uptime_sysctl if output = Facter::Util::Resolution.exec("#{uptime_sysctl_cmd} 2>/dev/null") - compute_uptime(Time.at(output.unpack('L').first)) + compute_uptime(Time.at(output.match(/\d+/)[0].to_i)) end end @@ -52,7 +52,7 @@ def self.uptime_file end def self.uptime_sysctl_cmd - 'sysctl -b kern.boottime' + 'sysctl -n kern.boottime' end def self.uptime_kstat_cmd diff --git a/spec/fixtures/uptime/sysctl_kern_boottime_big_endian b/spec/fixtures/uptime/sysctl_kern_boottime_big_endian deleted file mode 100644 index 6b7be22e5dc27af04ade9eca196adbe169601bf8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20 UcmeYXecbH>#0*dnI>$}}07irbb^rhX diff --git a/spec/fixtures/uptime/sysctl_kern_boottime_darwin b/spec/fixtures/uptime/sysctl_kern_boottime_darwin new file mode 100644 index 0000000000..db4f6f0f8d --- /dev/null +++ b/spec/fixtures/uptime/sysctl_kern_boottime_darwin @@ -0,0 +1 @@ +{ sec = 1320011547, usec = 0 } Sun Oct 30 21:52:27 2011 diff --git a/spec/fixtures/uptime/sysctl_kern_boottime_little_endian b/spec/fixtures/uptime/sysctl_kern_boottime_little_endian deleted file mode 100644 index 0c54fe44aab83f38cd6526a020a16d9d924e49a5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16 RcmeBv9O}aW1sZm9LIELm17`pL diff --git a/spec/fixtures/uptime/sysctl_kern_boottime_openbsd b/spec/fixtures/uptime/sysctl_kern_boottime_openbsd new file mode 100644 index 0000000000..c56a77adf5 --- /dev/null +++ b/spec/fixtures/uptime/sysctl_kern_boottime_openbsd @@ -0,0 +1 @@ +1323465106 diff --git a/spec/unit/util/uptime_spec.rb b/spec/unit/util/uptime_spec.rb index fe28d169dd..8c30ff7678 100755 --- a/spec/unit/util/uptime_spec.rb +++ b/spec/unit/util/uptime_spec.rb @@ -26,15 +26,17 @@ Facter::Util::Uptime.stubs(:uptime_file).returns(@nonexistent_file) end - it "should use 'sysctl kern.boottime'" do - if [1].pack("L") == [1].pack("V") # Determine endianness - sysctl_output_filename = 'sysctl_kern_boottime_little_endian' - else - sysctl_output_filename = 'sysctl_kern_boottime_big_endian' - end - sysctl_output_file = File.join(SPECDIR, 'fixtures', 'uptime', sysctl_output_filename) # Aug 01 14:13:47 -0700 2010 + it "should use 'sysctl -n kern.boottime' on OpenBSD" do + sysctl_output_file = File.join(SPECDIR, 'fixtures', 'uptime', 'sysctl_kern_boottime_openbsd') # Dec 09 21:11:46 +0000 2011 + Facter::Util::Uptime.stubs(:uptime_sysctl_cmd).returns("cat \"#{sysctl_output_file}\"") + Time.stubs(:now).returns Time.parse("Dec 09 22:11:46 +0000 2011") # one hour later + Facter::Util::Uptime.get_uptime_seconds_unix.should == 60 * 60 + end + + it "should use 'sysctl -n kern.boottime' on Darwin, etc." do + sysctl_output_file = File.join(SPECDIR, 'fixtures', 'uptime', 'sysctl_kern_boottime_darwin') # Oct 30 21:52:27 +0000 2011 Facter::Util::Uptime.stubs(:uptime_sysctl_cmd).returns("cat \"#{sysctl_output_file}\"") - Time.stubs(:now).returns Time.parse("Aug 01 15:13:47 -0700 2010") # one hour later + Time.stubs(:now).returns Time.parse("Oct 30 22:52:27 +0000 2011") # one hour later Facter::Util::Uptime.get_uptime_seconds_unix.should == 60 * 60 end From 9101e466a2119308db73c334f103a66157d9cf7b Mon Sep 17 00:00:00 2001 From: Chetan Sarva Date: Wed, 15 Jun 2011 15:48:38 -0400 Subject: [PATCH 0671/3753] (#7951) added OS support for Amazon Linux --- lib/facter/operatingsystem.rb | 2 ++ lib/facter/operatingsystemrelease.rb | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index cf372201e9..eb8ad8504a 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -81,6 +81,8 @@ "Slackware" elsif FileTest.exists?("/etc/alpine-release") "Alpine" + elsif Facter.value(:lsbdistdescription) =~ /Amazon Linux/ + "Amazon" end end end diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index 3a535ec057..972ffa8398 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -9,6 +9,7 @@ # On Suse, derivatives, parses '/etc/SuSE-release' for a selection of version # information. # On Slackware, parses '/etc/slackware-version'. +# On Amazon Linux, returns the 'lsbdistrelease' value. # # On all remaining systems, returns the 'kernelrelease' value. # @@ -131,6 +132,11 @@ end end +Facter.add(:operatingsystemrelease) do + confine :operatingsystem => %W{Amazon} + setcode do Facter[:lsbdistrelease].value end +end + Facter.add(:operatingsystemrelease) do setcode do Facter[:kernelrelease].value end end From 2c5ad52ae81153fd69fb3e6a81b814a667e11cc5 Mon Sep 17 00:00:00 2001 From: Ben Hughes Date: Thu, 7 Apr 2011 11:33:09 +1000 Subject: [PATCH 0672/3753] (#6728) Facter improperly detects openvzve on CloudLinux systems Update the OpenVZ fact to be slightly more resilient in it's checking of OpenVZ, so it doesn't clash with CloudLinux's LVE container system. It checks if /proc/vz/ actually has anything in it, which if it doesn't, is Cloudlinux. Uses the better OpenVZ detection methodology from #2242, which is reading /proc/self/status for the envID which OpenVZ uses. Conflicts: lib/facter/util/virtual.rb spec/unit/util/virtual_spec.rb --- lib/facter/util/virtual.rb | 25 ++++++++++++++++++------- spec/unit/util/virtual_spec.rb | 25 ++++++++++++++++++++----- 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/lib/facter/util/virtual.rb b/lib/facter/util/virtual.rb index 8bdde16bcd..1961432371 100644 --- a/lib/facter/util/virtual.rb +++ b/lib/facter/util/virtual.rb @@ -1,15 +1,26 @@ module Facter::Util::Virtual def self.openvz? - FileTest.directory?("/proc/vz") and FileTest.exists?( '/proc/vz/veinfo' ) + FileTest.directory?("/proc/vz") and not self.openvz_cloudlinux? end + # So one can either have #6728 work on OpenVZ or Cloudlinux. Whoo. def self.openvz_type - return nil unless self.openvz? - if FileTest.exists?("/proc/vz/version") - result = "openvzhn" - else - result = "openvzve" - end + return false unless self.openvz? + return false unless FileTest.exists?( '/proc/self/status' ) + + envid = Facter::Util::Resolution.exec( 'grep "envID" /proc/self/status' ) + if envid =~ /^envID:\s+0$/i + return 'openvzhn' + elsif envid =~ /^envID:\s+(\d+)$/i + return 'openvzve' + else + return 'unknown' + end + end + + # Cloudlinux uses OpenVZ to a degree, but always has an empty /proc/vz/ + def self.openvz_cloudlinux? + Dir.glob( '/proc/vz/*' ).empty? end def self.zone? diff --git a/spec/unit/util/virtual_spec.rb b/spec/unit/util/virtual_spec.rb index 36e1bf1b03..be22eda4ca 100644 --- a/spec/unit/util/virtual_spec.rb +++ b/spec/unit/util/virtual_spec.rb @@ -9,22 +9,37 @@ end it "should detect openvz" do FileTest.stubs(:directory?).with("/proc/vz").returns(true) - FileTest.stubs(:exists?).with("/proc/vz/veinfo").returns(true) + Dir.stubs(:glob).with("/proc/vz/*").returns(['vzquota']) Facter::Util::Virtual.should be_openvz end - it "should identify openvzhn when version file exists" do + it "should not detect openvz when /proc/vz/ is empty" do + FileTest.stubs(:directory?).with("/proc/vz").returns(true) + Dir.stubs(:glob).with("/proc/vz/*").returns([]) + Facter::Util::Virtual.should_not be_openvz + end + + it "should identify openvzhn when /proc/self/status has envID of 0" do Facter::Util::Virtual.stubs(:openvz?).returns(true) - FileTest.stubs(:exists?).with("/proc/vz/version").returns(true) + FileTest.stubs(:exists?).with("/proc/self/status").returns(true) + Facter::Util::Resolution.stubs(:exec).with('grep "envID" /proc/self/status').returns("envID: 0") Facter::Util::Virtual.openvz_type().should == "openvzhn" end - it "should identify openvzve when no version file exists" do + it "should identify openvzve when /proc/self/status has envID of 0" do Facter::Util::Virtual.stubs(:openvz?).returns(true) - FileTest.stubs(:exists?).with("/proc/vz/version").returns(false) + FileTest.stubs(:exists?).with('/proc/self/status').returns(true) + Facter::Util::Resolution.stubs(:exec).with('grep "envID" /proc/self/status').returns("envID: 666") Facter::Util::Virtual.openvz_type().should == "openvzve" end + it "should identify unknown when /proc/self/status has no envID" do + Facter::Util::Virtual.stubs(:openvz?).returns(true) + FileTest.stubs(:exists?).with('/proc/self/status').returns(true) + Facter::Util::Resolution.stubs(:exec).with('grep "envID" /proc/self/status').returns("") + Facter::Util::Virtual.openvz_type().should == "unknown" + end + it "should identify Solaris zones when non-global zone" do Facter::Util::Resolution.stubs(:exec).with("/sbin/zonename").returns("somezone") Facter::Util::Virtual.should be_zone From 0dfc4e93d6ea7e0437e6f287b70a9b6af430a9ed Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 29 Sep 2011 15:34:30 -0700 Subject: [PATCH 0673/3753] (#6728) Improve openvz/cloudlinux detection. Added more cloudlinux detection, which has /proc/lve/list present on cloudlinux hosts. Removed a default value from openvz_type detection, which could lead to a virtual value of "unknown" if the openvz check partially failed, which could cause other legitimate virtual tests to be skipped. --- lib/facter/util/virtual.rb | 7 +++---- spec/unit/util/virtual_spec.rb | 10 ++++++++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/facter/util/virtual.rb b/lib/facter/util/virtual.rb index 1961432371..0ae7c8f06b 100644 --- a/lib/facter/util/virtual.rb +++ b/lib/facter/util/virtual.rb @@ -13,14 +13,13 @@ def self.openvz_type return 'openvzhn' elsif envid =~ /^envID:\s+(\d+)$/i return 'openvzve' - else - return 'unknown' end end - # Cloudlinux uses OpenVZ to a degree, but always has an empty /proc/vz/ + # Cloudlinux uses OpenVZ to a degree, but always has an empty /proc/vz/ and + # has /proc/lve/list present def self.openvz_cloudlinux? - Dir.glob( '/proc/vz/*' ).empty? + FileTest.file?("/proc/lve/list") or Dir.glob('/proc/vz/*').empty? end def self.zone? diff --git a/spec/unit/util/virtual_spec.rb b/spec/unit/util/virtual_spec.rb index be22eda4ca..82eb71ca8a 100644 --- a/spec/unit/util/virtual_spec.rb +++ b/spec/unit/util/virtual_spec.rb @@ -13,7 +13,13 @@ Facter::Util::Virtual.should be_openvz end + it "should not detect openvz when /proc/lve/list is present" do + FileTest.stubs(:file?).with("/proc/lve/list").returns(true) + Facter::Util::Virtual.should_not be_openvz + end + it "should not detect openvz when /proc/vz/ is empty" do + FileTest.stubs(:file?).with("/proc/lve/list").returns(false) FileTest.stubs(:directory?).with("/proc/vz").returns(true) Dir.stubs(:glob).with("/proc/vz/*").returns([]) Facter::Util::Virtual.should_not be_openvz @@ -33,11 +39,11 @@ Facter::Util::Virtual.openvz_type().should == "openvzve" end - it "should identify unknown when /proc/self/status has no envID" do + it "should not attempt to identify openvz when /proc/self/status has no envID" do Facter::Util::Virtual.stubs(:openvz?).returns(true) FileTest.stubs(:exists?).with('/proc/self/status').returns(true) Facter::Util::Resolution.stubs(:exec).with('grep "envID" /proc/self/status').returns("") - Facter::Util::Virtual.openvz_type().should == "unknown" + Facter::Util::Virtual.openvz_type().should be_nil end it "should identify Solaris zones when non-global zone" do From d5511f6867f5b0523ddf11919e98ddc9ea89584b Mon Sep 17 00:00:00 2001 From: Joachim de Groot Date: Thu, 5 May 2011 17:42:53 +0200 Subject: [PATCH 0674/3753] (#9404) Add cross-fact support to facter for DragonFly BSD. The coverage for this commit includes the following facts: - ps - ip related facts - macaddress - memory* - swap* - hardwareisa - processor* --- lib/facter/hardwareisa.rb | 2 +- lib/facter/ipaddress.rb | 2 +- lib/facter/macaddress.rb | 2 +- lib/facter/memory.rb | 31 +++++++++++++++++++++++++++++++ lib/facter/netmask.rb | 2 +- lib/facter/processor.rb | 15 +++++++++++++++ lib/facter/ps.rb | 2 +- lib/facter/util/ip.rb | 8 ++++---- lib/facter/util/netmask.rb | 2 +- 9 files changed, 56 insertions(+), 10 deletions(-) diff --git a/lib/facter/hardwareisa.rb b/lib/facter/hardwareisa.rb index cf29f23f29..323983ea64 100644 --- a/lib/facter/hardwareisa.rb +++ b/lib/facter/hardwareisa.rb @@ -12,5 +12,5 @@ Facter.add(:hardwareisa) do setcode 'uname -p' - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS Scientific SLC SuSE SLES Debian Ubuntu Gentoo FreeBSD OpenBSD NetBSD OEL OracleLinux OVS GNU/kFreeBSD} + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS Scientific SLC SuSE SLES Debian Ubuntu Gentoo FreeBSD OpenBSD NetBSD DragonFly OEL OracleLinux OVS GNU/kFreeBSD} end diff --git a/lib/facter/ipaddress.rb b/lib/facter/ipaddress.rb index 52605042fe..41b22e2286 100644 --- a/lib/facter/ipaddress.rb +++ b/lib/facter/ipaddress.rb @@ -43,7 +43,7 @@ end Facter.add(:ipaddress) do - confine :kernel => %w{FreeBSD OpenBSD Darwin} + confine :kernel => %w{FreeBSD OpenBSD Darwin DragonFly} setcode do ip = nil output = %x{/sbin/ifconfig} diff --git a/lib/facter/macaddress.rb b/lib/facter/macaddress.rb index 2f4e3150f2..341654a9fc 100644 --- a/lib/facter/macaddress.rb +++ b/lib/facter/macaddress.rb @@ -34,7 +34,7 @@ end Facter.add(:macaddress) do - confine :operatingsystem => %w{FreeBSD OpenBSD} + confine :operatingsystem => %w{FreeBSD OpenBSD DragonFly} setcode do ether = [] output = Facter::Util::Resolution.exec("/sbin/ifconfig") diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb index 965a487102..107a05a6e8 100644 --- a/lib/facter/memory.rb +++ b/lib/facter/memory.rb @@ -233,4 +233,35 @@ Facter::Memory.scale_number(mem.to_f, "") end end + +if Facter.value(:kernel) == "DragonFly" + page_size = %x{/sbin/sysctl -n hw.pagesize}.to_f + swaptotal = %x{/sbin/sysctl -n vm.swap_size}.to_f * page_size + swap_anon_use = %x{/sbin/sysctl -n vm.swap_anon_use}.to_f * page_size + swap_cache_use = %x{/sbin/sysctl -n vm.swap_cache_use}.to_f * page_size + swapfree = swaptotal - swap_anon_use - swap_cache_use + + Facter.add("SwapSize") do + confine :kernel => :dragonfly + setcode do + Facter::Memory.scale_number(swaptotal.to_f,"") + end + end + + Facter.add("SwapFree") do + confine :kernel => :dragonfly + setcode do + Facter::Memory.scale_number(swapfree.to_f,"") + end + end + + Facter::Memory.vmstat_find_free_memory() + + Facter.add("MemoryTotal") do + confine :kernel => :dragonfly + memtotal = Facter::Util::Resolution.exec("sysctl -n hw.physmem") + setcode do + Facter::Memory.scale_number(memtotal.to_f,"") + end + end end diff --git a/lib/facter/netmask.rb b/lib/facter/netmask.rb index fbb84f59ee..e3979fd689 100644 --- a/lib/facter/netmask.rb +++ b/lib/facter/netmask.rb @@ -18,7 +18,7 @@ require 'facter/util/netmask' Facter.add("netmask") do - confine :kernel => [ :sunos, :linux, :freebsd, :openbsd, :netbsd, :darwin, :"gnu/kfreebsd" ] + confine :kernel => [ :sunos, :linux, :freebsd, :openbsd, :netbsd, :darwin, :"gnu/kfreebsd", :dragonfly ] setcode do Facter::NetMask.get_netmask end diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb index ffb611803e..9c7fca364b 100644 --- a/lib/facter/processor.rb +++ b/lib/facter/processor.rb @@ -138,6 +138,21 @@ processor_list.length.to_s end end + +if Facter.value(:kernel) == "DragonFly" + Facter.add("Processor") do + confine :kernel => :dragonfly + setcode do + Facter::Util::Resolution.exec("sysctl -n hw.model") + end + end + + Facter.add("ProcessorCount") do + confine :kernel => :dragonfly + setcode do + Facter::Util::Resolution.exec("sysctl -n hw.ncpu") + end + end end Facter.add("processorcount") do diff --git a/lib/facter/ps.rb b/lib/facter/ps.rb index 6c22e26778..1d4f01c4de 100644 --- a/lib/facter/ps.rb +++ b/lib/facter/ps.rb @@ -15,7 +15,7 @@ end Facter.add(:ps) do - confine :operatingsystem => %w{FreeBSD NetBSD OpenBSD Darwin} + confine :operatingsystem => %w{FreeBSD NetBSD OpenBSD Darwin DragonFly} setcode do 'ps auxwww' end end diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index 7bb6029984..0e4af16e15 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -11,7 +11,7 @@ module Facter::Util::IP :netmask => /Mask:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ }, :bsd => { - :aliases => [:openbsd, :netbsd, :freebsd, :darwin, :"gnu/kfreebsd"], + :aliases => [:openbsd, :netbsd, :freebsd, :darwin, :"gnu/kfreebsd", :dragonfly], :ipaddress => /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, :ipaddress6 => /inet6 ((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/, :macaddress => /(?:ether|lladdr)\s+(\w?\w:\w?\w:\w?\w:\w?\w:\w?\w:\w?\w)/, @@ -41,7 +41,7 @@ def self.alphafy(interface) end def self.convert_from_hex?(kernel) - kernels_to_convert = [:sunos, :openbsd, :netbsd, :freebsd, :darwin, :"hp-ux", :"gnu/kfreebsd"] + kernels_to_convert = [:sunos, :openbsd, :netbsd, :freebsd, :darwin, :"hp-ux", :"gnu/kfreebsd", :dragonfly] kernels_to_convert.include?(kernel) end @@ -73,7 +73,7 @@ def self.get_interfaces def self.get_all_interface_output case Facter.value(:kernel) - when 'Linux', 'OpenBSD', 'NetBSD', 'FreeBSD', 'Darwin', 'GNU/kFreeBSD' + when 'Linux', 'OpenBSD', 'NetBSD', 'FreeBSD', 'Darwin', 'GNU/kFreeBSD', 'DragonFly' output = %x{/sbin/ifconfig -a} when 'SunOS' output = %x{/usr/sbin/ifconfig -a} @@ -89,7 +89,7 @@ def self.get_all_interface_output def self.get_single_interface_output(interface) output = "" case Facter.value(:kernel) - when 'Linux', 'OpenBSD', 'NetBSD', 'FreeBSD', 'Darwin', 'GNU/kFreeBSD' + when 'Linux', 'OpenBSD', 'NetBSD', 'FreeBSD', 'Darwin', 'GNU/kFreeBSD', 'DragonFly' output = %x{/sbin/ifconfig #{interface}} when 'SunOS' output = %x{/usr/sbin/ifconfig #{interface}} diff --git a/lib/facter/util/netmask.rb b/lib/facter/util/netmask.rb index 5dcc576fcf..60b57d9b2c 100644 --- a/lib/facter/util/netmask.rb +++ b/lib/facter/util/netmask.rb @@ -17,7 +17,7 @@ def self.get_netmask :regex => %r{\s+ inet \s #{Facter.ipaddress} \s netmask \s (\w{8})}x, :munge => Proc.new { |mask| mask.scan(/../).collect do |byte| byte.to_i(16) end.join('.') } } - when 'FreeBSD','NetBSD','OpenBSD', 'Darwin', 'GNU/kFreeBSD' + when 'FreeBSD','NetBSD','OpenBSD', 'Darwin', 'GNU/kFreeBSD', 'DragonFly' ops = { :ifconfig => '/sbin/ifconfig -a', :regex => %r{\s+ inet \s #{Facter.ipaddress} \s netmask \s 0x(\w{8})}x, From cd0ae153a7fc997a61d47e3000d6d3290e0451ba Mon Sep 17 00:00:00 2001 From: Ken Barber Date: Thu, 29 Sep 2011 17:59:34 -0700 Subject: [PATCH 0675/3753] (#9404) Efficiency cleanups for DragonFly facts. Applied better usage of confines instead of conditionals and moved most code into setcode where applicable. --- lib/facter/memory.rb | 46 ++++++++++++++++++++--------------------- lib/facter/processor.rb | 24 ++++++++++----------- 2 files changed, 33 insertions(+), 37 deletions(-) diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb index 107a05a6e8..afc1407e1b 100644 --- a/lib/facter/memory.rb +++ b/lib/facter/memory.rb @@ -234,34 +234,32 @@ end end -if Facter.value(:kernel) == "DragonFly" - page_size = %x{/sbin/sysctl -n hw.pagesize}.to_f - swaptotal = %x{/sbin/sysctl -n vm.swap_size}.to_f * page_size - swap_anon_use = %x{/sbin/sysctl -n vm.swap_anon_use}.to_f * page_size - swap_cache_use = %x{/sbin/sysctl -n vm.swap_cache_use}.to_f * page_size - swapfree = swaptotal - swap_anon_use - swap_cache_use - - Facter.add("SwapSize") do - confine :kernel => :dragonfly - setcode do - Facter::Memory.scale_number(swaptotal.to_f,"") - end +Facter.add("SwapSize") do + confine :kernel => :dragonfly + setcode do + page_size = Facter::Util::Resolution.exec("/sbin/sysctl -n hw.pagesize").to_f + swaptotal = Facter::Util::Resolution.exec("/sbin/sysctl -n vm.swap_size").to_f * page_size + Facter::Memory.scale_number(swaptotal.to_f,"") end +end - Facter.add("SwapFree") do - confine :kernel => :dragonfly - setcode do - Facter::Memory.scale_number(swapfree.to_f,"") - end +Facter.add("SwapFree") do + confine :kernel => :dragonfly + setcode do + page_size = Facter::Util::Resolution.exec("/sbin/sysctl -n hw.pagesize").to_f + swaptotal = Facter::Util::Resolution.exec("/sbin/sysctl -n vm.swap_size").to_f * page_size + swap_anon_use = Facter::Util::Resolution.exec("/sbin/sysctl -n vm.swap_anon_use").to_f * page_size + swap_cache_use = Facter::Util::Resolution.exec("/sbin/sysctl -n vm.swap_cache_use").to_f * page_size + swapfree = swaptotal - swap_anon_use - swap_cache_use + Facter::Memory.scale_number(swapfree.to_f,"") end +end - Facter::Memory.vmstat_find_free_memory() - - Facter.add("MemoryTotal") do - confine :kernel => :dragonfly +Facter.add("MemoryTotal") do + confine :kernel => :dragonfly + setcode do + Facter::Memory.vmstat_find_free_memory() memtotal = Facter::Util::Resolution.exec("sysctl -n hw.physmem") - setcode do - Facter::Memory.scale_number(memtotal.to_f,"") - end + Facter::Memory.scale_number(memtotal.to_f,"") end end diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb index 9c7fca364b..d5a70c3dd2 100644 --- a/lib/facter/processor.rb +++ b/lib/facter/processor.rb @@ -139,20 +139,18 @@ end end -if Facter.value(:kernel) == "DragonFly" - Facter.add("Processor") do - confine :kernel => :dragonfly - setcode do - Facter::Util::Resolution.exec("sysctl -n hw.model") - end - end +Facter.add("Processor") do + confine :kernel => :dragonfly + setcode do + Facter::Util::Resolution.exec("sysctl -n hw.model") + end +end - Facter.add("ProcessorCount") do - confine :kernel => :dragonfly - setcode do - Facter::Util::Resolution.exec("sysctl -n hw.ncpu") - end - end +Facter.add("ProcessorCount") do + confine :kernel => :dragonfly + setcode do + Facter::Util::Resolution.exec("sysctl -n hw.ncpu") + end end Facter.add("processorcount") do From bce2c69077244aa8cebdb99009a8df709d979324 Mon Sep 17 00:00:00 2001 From: Joachim de Groot Date: Mon, 9 May 2011 18:27:26 +0200 Subject: [PATCH 0676/3753] (#9404) De-clumsify CPU count detection and swap detection on OpenBSD. As part of the DragonFly BSD work is was noticed that the OpenBSD implementation could benefit from the same techniques so this commit aligns that. --- lib/facter/memory.rb | 11 +++++------ lib/facter/processor.rb | 5 +++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb index afc1407e1b..f7e9f67451 100644 --- a/lib/facter/memory.rb +++ b/lib/facter/memory.rb @@ -93,13 +93,11 @@ end if Facter.value(:kernel) == "OpenBSD" - swap = Facter::Util::Resolution.exec('swapctl -l | sed 1d') + swap = Facter::Util::Resolution.exec('swapctl -s') swapfree, swaptotal = 0, 0 - swap.each_line do |dev| - if dev =~ /^\S+\s+(\S+)\s+\S+\s+(\S+)\s+.*$/ - swaptotal += $1.to_i - swapfree += $2.to_i - end + if swap =~ /^total: (\d+)k bytes allocated = \d+k used, (\d+)k available$/ + swaptotal = $1.to_i + swapfree = $2.to_i end Facter.add("SwapSize") do @@ -233,6 +231,7 @@ Facter::Memory.scale_number(mem.to_f, "") end end +end Facter.add("SwapSize") do confine :kernel => :dragonfly diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb index d5a70c3dd2..2ef7dd9f78 100644 --- a/lib/facter/processor.rb +++ b/lib/facter/processor.rb @@ -66,14 +66,14 @@ Facter.add("ProcessorCount") do confine :kernel => :openbsd setcode do - Facter::Util::Resolution.exec("sysctl hw.ncpu | cut -d'=' -f2") + Facter::Util::Resolution.exec("sysctl -n hw.ncpu") end end Facter.add("ProcessorCount") do confine :kernel => :Darwin setcode do - Facter::Util::Resolution.exec("sysctl hw.ncpu | cut -d' ' -f2") + Facter::Util::Resolution.exec("sysctl -n hw.ncpu") end end @@ -138,6 +138,7 @@ processor_list.length.to_s end end +end Facter.add("Processor") do confine :kernel => :dragonfly From 1b69791ee0c489df13207ecba55705e240eddcf6 Mon Sep 17 00:00:00 2001 From: Ken Barber Date: Thu, 29 Sep 2011 13:34:19 -0700 Subject: [PATCH 0677/3753] (#9404) Add memory & update processor facts for DragonFly and OpenBSD. Since there was no coverage for memory tests these have been added for the two OS's. Also since the mechanism for processor detection was changes this was fixed for OpenBSD. A similar mechanism was added for the new DragonFly BSD support. --- spec/unit/memory_spec.rb | 77 +++++++++++++++++++++++++++++++++++++ spec/unit/processor_spec.rb | 11 +++++- 2 files changed, 86 insertions(+), 2 deletions(-) diff --git a/spec/unit/memory_spec.rb b/spec/unit/memory_spec.rb index 7aeb9ddce2..175904f23c 100755 --- a/spec/unit/memory_spec.rb +++ b/spec/unit/memory_spec.rb @@ -45,6 +45,83 @@ Facter.fact(:swapencrypted).value.should == true end + describe "on OpenBSD" do + before :each do + Facter.clear + Facter.fact(:kernel).stubs(:value).returns("OpenBSD") + + swapusage = "total: 148342k bytes allocated = 0k used, 148342k available" + Facter::Util::Resolution.stubs(:exec).with('swapctl -s').returns(swapusage) + + vmstat = < Date: Fri, 30 Sep 2011 11:07:20 +0200 Subject: [PATCH 0678/3753] (#9830) Add sshecdsakey fact From version 5.7 onward, openssh has support for elliptic curve DSA keys[1,2]. This commit adds a fact for those keytypes. 1 - http://openssh.org/txt/release-5.7 2 - http://tools.ietf.org/html/rfc5656 --- lib/facter/ssh.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/ssh.rb b/lib/facter/ssh.rb index 5ae68ed0eb..7bdf8036f4 100644 --- a/lib/facter/ssh.rb +++ b/lib/facter/ssh.rb @@ -12,7 +12,7 @@ ## ["/etc/ssh","/usr/local/etc/ssh","/etc","/usr/local/etc"].each do |dir| - {"SSHDSAKey" => "ssh_host_dsa_key.pub", "SSHRSAKey" => "ssh_host_rsa_key.pub"}.each do |name,file| + {"SSHDSAKey" => "ssh_host_dsa_key.pub", "SSHRSAKey" => "ssh_host_rsa_key.pub", "SSHECDSAKey" => "ssh_host_ecdsa_key.pub"}.each do |name,file| Facter.add(name) do setcode do value = nil From abf636e3cff0990d40d9402549014c68ac04b7da Mon Sep 17 00:00:00 2001 From: Ken Barber Date: Mon, 19 Sep 2011 12:54:35 +0100 Subject: [PATCH 0679/3753] (#9555) Change all cases of tabs and 4 space indentation to 2 space indentation. Since 2 space indentation seems to be Puppets (and the ruby communities) standard this patch converts all incorrect indentation to 2 spaces. The fact that we were mixing the indentation was causing people to mix them within files - sometimes using 4 space, sometimes 2 space. This single change makes it consistent across all the code. --- Rakefile | 72 +-- ...ket_7039_facter_multiple_facts_one_file.rb | 20 +- documentation/custom.page | 8 +- install.rb | 572 +++++++++--------- lib/facter.rb | 422 ++++++------- lib/facter/Cfkey.rb | 48 +- lib/facter/architecture.rb | 42 +- lib/facter/domain.rb | 68 +-- lib/facter/facterversion.rb | 2 +- lib/facter/fqdn.rb | 16 +- lib/facter/hardwareisa.rb | 4 +- lib/facter/hardwaremodel.rb | 24 +- lib/facter/hostname.rb | 28 +- lib/facter/id.rb | 6 +- lib/facter/interfaces.rb | 26 +- lib/facter/ipaddress.rb | 202 +++---- lib/facter/iphostnumber.rb | 24 +- lib/facter/kernel.rb | 14 +- lib/facter/kernelmajversion.rb | 6 +- lib/facter/kernelrelease.rb | 24 +- lib/facter/kernelversion.rb | 10 +- lib/facter/lsb.rb | 28 +- lib/facter/lsbmajdistrelease.rb | 16 +- lib/facter/macaddress.rb | 88 +-- lib/facter/macosx.rb | 42 +- lib/facter/manufacturer.rb | 56 +- lib/facter/memory.rb | 268 ++++---- lib/facter/netmask.rb | 8 +- lib/facter/network.rb | 8 +- lib/facter/operatingsystem.rb | 146 ++--- lib/facter/operatingsystemrelease.rb | 162 ++--- lib/facter/path.rb | 6 +- lib/facter/processor.rb | 8 +- lib/facter/ps.rb | 6 +- lib/facter/puppetversion.rb | 14 +- lib/facter/rubysitedir.rb | 10 +- lib/facter/rubyversion.rb | 2 +- lib/facter/ssh.rb | 32 +- lib/facter/timezone.rb | 6 +- lib/facter/uniqueid.rb | 4 +- lib/facter/util/collection.rb | 192 +++--- lib/facter/util/confine.rb | 60 +- lib/facter/util/fact.rb | 190 +++--- lib/facter/util/ip.rb | 346 +++++------ lib/facter/util/loader.rb | 176 +++--- lib/facter/util/macosx.rb | 92 +-- lib/facter/util/manufacturer.rb | 156 ++--- lib/facter/util/memory.rb | 126 ++-- lib/facter/util/netmask.rb | 68 +-- lib/facter/util/plist.rb | 2 +- lib/facter/util/plist/generator.rb | 354 +++++------ lib/facter/util/plist/parser.rb | 332 +++++----- lib/facter/util/processor.rb | 2 +- lib/facter/util/resolution.rb | 308 +++++----- lib/facter/util/uptime.rb | 84 +-- lib/facter/util/values.rb | 18 +- lib/facter/util/virtual.rb | 132 ++-- lib/facter/util/vlans.rb | 34 +- lib/facter/virtual.rb | 210 +++---- lib/facter/vlans.rb | 12 +- spec/integration/facter_spec.rb | 36 +- spec/unit/memory_spec.rb | 2 +- spec/unit/util/uptime_spec.rb | 8 +- tasks/rake/changlog.rake | 22 +- tasks/rake/ci.rake | 18 +- tasks/rake/mail_patches.rake | 80 +-- tasks/rake/metrics.rake | 6 +- tasks/rake/sign.rake | 10 +- 68 files changed, 2812 insertions(+), 2812 deletions(-) diff --git a/Rakefile b/Rakefile index 5b0d0729a3..380416bb9f 100644 --- a/Rakefile +++ b/Rakefile @@ -7,7 +7,7 @@ require 'rubygems' require 'rspec' require 'rspec/core/rake_task' begin - require 'rcov' + require 'rcov' rescue LoadError end @@ -18,48 +18,48 @@ require 'rake/packagetask' require 'rake/gempackagetask' module Facter - FACTERVERSION = File.read('lib/facter.rb')[/FACTERVERSION *= *'(.*)'/,1] or fail "Couldn't find FACTERVERSION" + FACTERVERSION = File.read('lib/facter.rb')[/FACTERVERSION *= *'(.*)'/,1] or fail "Couldn't find FACTERVERSION" end FILES = FileList[ - '[A-Z]*', - 'install.rb', - 'bin/**/*', - 'lib/**/*', - 'conf/**/*', - 'etc/**/*', - 'spec/**/*' + '[A-Z]*', + 'install.rb', + 'bin/**/*', + 'lib/**/*', + 'conf/**/*', + 'etc/**/*', + 'spec/**/*' ] spec = Gem::Specification.new do |spec| - spec.platform = Gem::Platform::RUBY - spec.name = 'facter' - spec.files = FILES.to_a - spec.executables = %w{facter} - spec.version = Facter::FACTERVERSION - spec.summary = 'Facter, a system inventory tool' - spec.author = 'Puppet Labs' - spec.email = 'info@puppetlabs.com' - spec.homepage = 'http://puppetlabs.com/' - spec.rubyforge_project = 'facter' - spec.has_rdoc = true - spec.rdoc_options << - '--title' << 'Facter - System Inventory Tool' << - '--main' << 'README' << - '--line-numbers' + spec.platform = Gem::Platform::RUBY + spec.name = 'facter' + spec.files = FILES.to_a + spec.executables = %w{facter} + spec.version = Facter::FACTERVERSION + spec.summary = 'Facter, a system inventory tool' + spec.author = 'Puppet Labs' + spec.email = 'info@puppetlabs.com' + spec.homepage = 'http://puppetlabs.com/' + spec.rubyforge_project = 'facter' + spec.has_rdoc = true + spec.rdoc_options << + '--title' << 'Facter - System Inventory Tool' << + '--main' << 'README' << + '--line-numbers' end Rake::PackageTask.new("facter", Facter::FACTERVERSION) do |pkg| - pkg.package_dir = 'pkg' - pkg.need_tar_gz = true - pkg.package_files = FILES.to_a + pkg.package_dir = 'pkg' + pkg.need_tar_gz = true + pkg.package_files = FILES.to_a end Rake::GemPackageTask.new(spec) do |pkg| end task :default do - sh %{rake -T} + sh %{rake -T} end # Aliases for spec @@ -68,15 +68,15 @@ task :tests => [:spec] task :specs => [:spec] RSpec::Core::RakeTask.new do |t| - t.pattern ='spec/{unit,integration}/**/*_spec.rb' - t.fail_on_error = true + t.pattern ='spec/{unit,integration}/**/*_spec.rb' + t.fail_on_error = true end RSpec::Core::RakeTask.new('spec:rcov') do |t| - t.pattern ='spec/{unit,integration}/**/*_spec.rb' - t.fail_on_error = true - if defined?(Rcov) - t.rcov = true - t.rcov_opts = ['--exclude', 'spec/*,test/*,results/*,/usr/lib/*,/usr/local/lib/*,gems/*'] - end + t.pattern ='spec/{unit,integration}/**/*_spec.rb' + t.fail_on_error = true + if defined?(Rcov) + t.rcov = true + t.rcov_opts = ['--exclude', 'spec/*,test/*,results/*,/usr/lib/*,/usr/local/lib/*,gems/*'] + end end diff --git a/acceptance/tests/ticket_7039_facter_multiple_facts_one_file.rb b/acceptance/tests/ticket_7039_facter_multiple_facts_one_file.rb index fb78628856..76da0651a0 100644 --- a/acceptance/tests/ticket_7039_facter_multiple_facts_one_file.rb +++ b/acceptance/tests/ticket_7039_facter_multiple_facts_one_file.rb @@ -2,15 +2,15 @@ fact_file= %q{ Facter.add(:test_fact1) do - setcode do - "test fact 1" - end + setcode do + "test fact 1" + end end Facter.add(:test_fact2) do - setcode do - "test fact 2" - end + setcode do + "test fact 2" + end end } @@ -20,12 +20,12 @@ step "Agent: Verify test_fact1 from /tmp/test_facts.rb" on(agent1, "export FACTERLIB=/tmp && facter --puppet test_fact1") do - fail_test "Fact 1 not returned by facter --puppet test_fact1" unless - stdout.include? 'test fact 1' + fail_test "Fact 1 not returned by facter --puppet test_fact1" unless + stdout.include? 'test fact 1' end step "Agent: Verify test_fact2 from /tmp/test_facts.rb" on(agent1, "export FACTERLIB=/tmp && facter --puppet test_fact2") do - fail_test "Fact 1 not returned by facter --puppet test_fact2" unless - stdout.include? 'test fact 2' + fail_test "Fact 1 not returned by facter --puppet test_fact2" unless + stdout.include? 'test fact 2' end diff --git a/documentation/custom.page b/documentation/custom.page index 0708a7053c..991071c3ba 100644 --- a/documentation/custom.page +++ b/documentation/custom.page @@ -12,10 +12,10 @@ search through your environment for any variables whose names start with As a simple example, here is how I publish my home directory to Puppet: Facter.add("home") do - setcode do - ENV['HOME'] - end - end + setcode do + ENV['HOME'] + end + end I have ~/lib/ruby in my $RUBYLIB environment variable, so I just created ~/lib/ruby/facter and dropped the above code into a ``home.rb`` file diff --git a/install.rb b/install.rb index 37154ba969..a1a3884202 100755 --- a/install.rb +++ b/install.rb @@ -12,23 +12,23 @@ # In most cases, if you have a typical project layout, you will need to do # absolutely nothing to make this work for you. This layout is: # -# bin/ # executable files -- "commands" -# lib/ # the source of the library +# bin/ # executable files -- "commands" +# lib/ # the source of the library # tests/ # unit tests # # The default behaviour: # 1) Run all unit test files (ending in .rb) found in all directories under -# tests/. +# tests/. # 2) Build Rdoc documentation from all files in bin/ (excluding .bat and .cmd), -# all .rb files in lib/, ./README, ./ChangeLog, and ./Install. +# all .rb files in lib/, ./README, ./ChangeLog, and ./Install. # 3) Build ri documentation from all files in bin/ (excluding .bat and .cmd), -# and all .rb files in lib/. This is disabled by default on Win32. +# and all .rb files in lib/. This is disabled by default on Win32. # 4) Install commands from bin/ into the Ruby bin directory. On Windows, if a -# if a corresponding batch file (.bat or .cmd) exists in the bin directory, -# it will be copied over as well. Otherwise, a batch file (always .bat) will -# be created to run the specified command. +# if a corresponding batch file (.bat or .cmd) exists in the bin directory, +# it will be copied over as well. Otherwise, a batch file (always .bat) will +# be created to run the specified command. # 5) Install all library files ending in .rb from lib/ into Ruby's -# site_lib/version directory. +# site_lib/version directory. # #++ @@ -39,23 +39,23 @@ require 'ostruct' begin - require 'rdoc/rdoc' - $haverdoc = true + require 'rdoc/rdoc' + $haverdoc = true rescue LoadError - puts "Missing rdoc; skipping documentation" - $haverdoc = false + puts "Missing rdoc; skipping documentation" + $haverdoc = false end begin - if $haverdoc - rst2man = %x{which rst2man.py} - $haveman = true - else - $haveman = false - end + if $haverdoc + rst2man = %x{which rst2man.py} + $haveman = true + else + $haveman = false + end rescue - puts "Missing rst2man; skipping man page creation" - $haveman = false + puts "Missing rst2man; skipping man page creation" + $haveman = false end $LOAD_PATH << File.join(File.dirname(__FILE__), 'lib') @@ -67,66 +67,66 @@ InstallOptions = OpenStruct.new def glob(list) - g = list.map { |i| Dir.glob(i) } - g.flatten! - g.compact! - g.reject! { |e| e =~ /\.svn/ } - g + g = list.map { |i| Dir.glob(i) } + g.flatten! + g.compact! + g.reject! { |e| e =~ /\.svn/ } + g end # Set these values to what you want installed. sbins = glob(%w{sbin/*}) bins = glob(%w{bin/*}) rdoc = glob(%w{bin/* sbin/* lib/**/*.rb README README-library CHANGELOG TODO Install}).reject { |e| e=~ /\.(bat|cmd)$/ } -ri = glob(%w(bin/*.rb sbin/* lib/**/*.rb)).reject { |e| e=~ /\.(bat|cmd)$/ } +ri = glob(%w(bin/*.rb sbin/* lib/**/*.rb)).reject { |e| e=~ /\.(bat|cmd)$/ } man = glob(%w{man/man8/*}) libs = glob(%w{lib/**/*.rb lib/**/*.py}) tests = glob(%w{tests/**/*.rb}) def do_bins(bins, target, strip = 's?bin/') - bins.each do |bf| - obf = bf.gsub(/#{strip}/, '') - install_binfile(bf, obf, target) - end + bins.each do |bf| + obf = bf.gsub(/#{strip}/, '') + install_binfile(bf, obf, target) + end end def do_libs(libs, strip = 'lib/') - libs.each do |lf| - olf = File.join(InstallOptions.site_dir, lf.gsub(/#{strip}/, '')) - op = File.dirname(olf) - FileUtils.makedirs(op, {:mode => 0755, :verbose => true}) - FileUtils.chmod(0755, op) - FileUtils.install(lf, olf, {:mode => 0644, :verbose => true}) - end + libs.each do |lf| + olf = File.join(InstallOptions.site_dir, lf.gsub(/#{strip}/, '')) + op = File.dirname(olf) + FileUtils.makedirs(op, {:mode => 0755, :verbose => true}) + FileUtils.chmod(0755, op) + FileUtils.install(lf, olf, {:mode => 0644, :verbose => true}) + end end def do_man(man, strip = 'man/') if (InstallOptions.man == true) - man.each do |mf| - omf = File.join(InstallOptions.man_dir, mf.gsub(/#{strip}/, '')) - om = File.dirname(omf) - FileUtils.makedirs(om, {:mode => 0755, :verbose => true}) - FileUtils.chmod(0755, om) - FileUtils.install(mf, omf, {:mode => 0644, :verbose => true}) - gzip = %x{which gzip} - gzip.chomp! - %x{#{gzip} -f #{omf}} - end + man.each do |mf| + omf = File.join(InstallOptions.man_dir, mf.gsub(/#{strip}/, '')) + om = File.dirname(omf) + FileUtils.makedirs(om, {:mode => 0755, :verbose => true}) + FileUtils.chmod(0755, om) + FileUtils.install(mf, omf, {:mode => 0644, :verbose => true}) + gzip = %x{which gzip} + gzip.chomp! + %x{#{gzip} -f #{omf}} + end else - puts "Skipping Man Page Generation" + puts "Skipping Man Page Generation" end end # Verify that all of the prereqs are installed def check_prereqs - PREREQS.each { |pre| - begin - require pre - rescue LoadError - puts "Could not load #{pre} Ruby library; cannot install" - exit -1 - end - } + PREREQS.each { |pre| + begin + require pre + rescue LoadError + puts "Could not load #{pre} Ruby library; cannot install" + exit -1 + end + } end def is_windows? @@ -137,160 +137,160 @@ def is_windows? # Prepare the file installation. # def prepare_installation - # Only try to do docs if we're sure they have rdoc - if $haverdoc - InstallOptions.rdoc = true - if is_windows? - InstallOptions.ri = false - else - InstallOptions.ri = true - end + # Only try to do docs if we're sure they have rdoc + if $haverdoc + InstallOptions.rdoc = true + if is_windows? + InstallOptions.ri = false else - InstallOptions.rdoc = false - InstallOptions.ri = false + InstallOptions.ri = true end + else + InstallOptions.rdoc = false + InstallOptions.ri = false + end - if $haveman - InstallOptions.man = true - if is_windows? - InstallOptions.man = false - end - else - InstallOptions.man = false + if $haveman + InstallOptions.man = true + if is_windows? + InstallOptions.man = false end + else + InstallOptions.man = false + end + + InstallOptions.tests = true - InstallOptions.tests = true - - ARGV.options do |opts| - opts.banner = "Usage: #{File.basename($0)} [options]" - opts.separator "" - opts.on('--[no-]rdoc', 'Prevents the creation of RDoc output.', 'Default on.') do |onrdoc| - InstallOptions.rdoc = onrdoc - end - opts.on('--[no-]ri', 'Prevents the creation of RI output.', 'Default off on mswin32.') do |onri| - InstallOptions.ri = onri - end - opts.on('--[no-]man', 'Presents the creation of man pages.', 'Default on.') do |onman| - InstallOptions.man = onman - end - opts.on('--[no-]tests', 'Prevents the execution of unit tests.', 'Default on.') do |ontest| - InstallOptions.tests = ontest - end - opts.on('--destdir[=OPTIONAL]', 'Installation prefix for all targets', 'Default essentially /') do |destdir| - InstallOptions.destdir = destdir - end - opts.on('--bindir[=OPTIONAL]', 'Installation directory for binaries', 'overrides Config::CONFIG["bindir"]') do |bindir| - InstallOptions.bindir = bindir - end - opts.on('--sbindir[=OPTIONAL]', 'Installation directory for system binaries', 'overrides Config::CONFIG["sbindir"]') do |sbindir| - InstallOptions.sbindir = sbindir - end - opts.on('--sitelibdir[=OPTIONAL]', 'Installation directory for libraries', 'overrides Config::CONFIG["sitelibdir"]') do |sitelibdir| - InstallOptions.sitelibdir = sitelibdir - end - opts.on('--mandir[=OPTIONAL]', 'Installation directory for man pages', 'overrides Config::CONFIG["mandir"]') do |mandir| - InstallOptions.mandir = mandir - end - opts.on('--quick', 'Performs a quick installation. Only the', 'installation is done.') do |quick| - InstallOptions.rdoc = false - InstallOptions.ri = false - InstallOptions.tests = false - end - opts.on('--full', 'Performs a full installation. All', 'optional installation steps are run.') do |full| - InstallOptions.rdoc = true - InstallOptions.ri = true - InstallOptions.tests = true - end - opts.separator("") - opts.on_tail('--help', "Shows this help text.") do - $stderr.puts opts - exit - end - - opts.parse! + ARGV.options do |opts| + opts.banner = "Usage: #{File.basename($0)} [options]" + opts.separator "" + opts.on('--[no-]rdoc', 'Prevents the creation of RDoc output.', 'Default on.') do |onrdoc| + InstallOptions.rdoc = onrdoc + end + opts.on('--[no-]ri', 'Prevents the creation of RI output.', 'Default off on mswin32.') do |onri| + InstallOptions.ri = onri + end + opts.on('--[no-]man', 'Presents the creation of man pages.', 'Default on.') do |onman| + InstallOptions.man = onman + end + opts.on('--[no-]tests', 'Prevents the execution of unit tests.', 'Default on.') do |ontest| + InstallOptions.tests = ontest + end + opts.on('--destdir[=OPTIONAL]', 'Installation prefix for all targets', 'Default essentially /') do |destdir| + InstallOptions.destdir = destdir + end + opts.on('--bindir[=OPTIONAL]', 'Installation directory for binaries', 'overrides Config::CONFIG["bindir"]') do |bindir| + InstallOptions.bindir = bindir + end + opts.on('--sbindir[=OPTIONAL]', 'Installation directory for system binaries', 'overrides Config::CONFIG["sbindir"]') do |sbindir| + InstallOptions.sbindir = sbindir + end + opts.on('--sitelibdir[=OPTIONAL]', 'Installation directory for libraries', 'overrides Config::CONFIG["sitelibdir"]') do |sitelibdir| + InstallOptions.sitelibdir = sitelibdir + end + opts.on('--mandir[=OPTIONAL]', 'Installation directory for man pages', 'overrides Config::CONFIG["mandir"]') do |mandir| + InstallOptions.mandir = mandir + end + opts.on('--quick', 'Performs a quick installation. Only the', 'installation is done.') do |quick| + InstallOptions.rdoc = false + InstallOptions.ri = false + InstallOptions.tests = false + end + opts.on('--full', 'Performs a full installation. All', 'optional installation steps are run.') do |full| + InstallOptions.rdoc = true + InstallOptions.ri = true + InstallOptions.tests = true + end + opts.separator("") + opts.on_tail('--help', "Shows this help text.") do + $stderr.puts opts + exit end - tmpdirs = [ENV['TMP'], ENV['TEMP'], "/tmp", "/var/tmp", "."] + opts.parse! + end - version = [Config::CONFIG["MAJOR"], Config::CONFIG["MINOR"]].join(".") - libdir = File.join(Config::CONFIG["libdir"], "ruby", version) + tmpdirs = [ENV['TMP'], ENV['TEMP'], "/tmp", "/var/tmp", "."] - # Mac OS X 10.5 and higher declare bindir and sbindir as - # /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin - # /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/sbin - # which is not generally where people expect executables to be installed - # These settings are appropriate defaults for all OS X versions. - if RUBY_PLATFORM =~ /^universal-darwin[\d\.]+$/ - Config::CONFIG['bindir'] = "/usr/bin" - Config::CONFIG['sbindir'] = "/usr/sbin" - end + version = [Config::CONFIG["MAJOR"], Config::CONFIG["MINOR"]].join(".") + libdir = File.join(Config::CONFIG["libdir"], "ruby", version) - if not InstallOptions.bindir.nil? - bindir = InstallOptions.bindir - else - bindir = Config::CONFIG['bindir'] - end + # Mac OS X 10.5 and higher declare bindir and sbindir as + # /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin + # /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/sbin + # which is not generally where people expect executables to be installed + # These settings are appropriate defaults for all OS X versions. + if RUBY_PLATFORM =~ /^universal-darwin[\d\.]+$/ + Config::CONFIG['bindir'] = "/usr/bin" + Config::CONFIG['sbindir'] = "/usr/sbin" + end - if not InstallOptions.sbindir.nil? - sbindir = InstallOptions.sbindir - else - sbindir = Config::CONFIG['sbindir'] - end + if not InstallOptions.bindir.nil? + bindir = InstallOptions.bindir + else + bindir = Config::CONFIG['bindir'] + end - if not InstallOptions.sitelibdir.nil? - sitelibdir = InstallOptions.sitelibdir - else - sitelibdir = Config::CONFIG["sitelibdir"] - if sitelibdir.nil? - sitelibdir = $:.find { |x| x =~ /site_ruby/ } - if sitelibdir.nil? - sitelibdir = File.join(libdir, "site_ruby") - elsif sitelibdir !~ Regexp.quote(version) - sitelibdir = File.join(sitelibdir, version) - end - end - end + if not InstallOptions.sbindir.nil? + sbindir = InstallOptions.sbindir + else + sbindir = Config::CONFIG['sbindir'] + end - if not InstallOptions.mandir.nil? - mandir = InstallOptions.mandir - else - mandir = Config::CONFIG['mandir'] + if not InstallOptions.sitelibdir.nil? + sitelibdir = InstallOptions.sitelibdir + else + sitelibdir = Config::CONFIG["sitelibdir"] + if sitelibdir.nil? + sitelibdir = $:.find { |x| x =~ /site_ruby/ } + if sitelibdir.nil? + sitelibdir = File.join(libdir, "site_ruby") + elsif sitelibdir !~ Regexp.quote(version) + sitelibdir = File.join(sitelibdir, version) + end end + end - # To be deprecated once people move over to using --destdir option - if (destdir = ENV['DESTDIR']) - warn "DESTDIR is deprecated. Use --destdir instead." - bindir = join(destdir, bindir) - sbindir = join(destdir, sbindir) - mandir = join(destdir, mandir) - sitelibdir = join(destdir, sitelibdir) - - FileUtils.makedirs(bindir) - FileUtils.makedirs(sbindir) - FileUtils.makedirs(mandir) - FileUtils.makedirs(sitelibdir) - # This is the new way forward - elsif (destdir = InstallOptions.destdir) - bindir = join(destdir, bindir) - sbindir = join(destdir, sbindir) - mandir = join(destdir, mandir) - sitelibdir = join(destdir, sitelibdir) - - FileUtils.makedirs(bindir) - FileUtils.makedirs(sbindir) - FileUtils.makedirs(mandir) - FileUtils.makedirs(sitelibdir) - end + if not InstallOptions.mandir.nil? + mandir = InstallOptions.mandir + else + mandir = Config::CONFIG['mandir'] + end + + # To be deprecated once people move over to using --destdir option + if (destdir = ENV['DESTDIR']) + warn "DESTDIR is deprecated. Use --destdir instead." + bindir = join(destdir, bindir) + sbindir = join(destdir, sbindir) + mandir = join(destdir, mandir) + sitelibdir = join(destdir, sitelibdir) + + FileUtils.makedirs(bindir) + FileUtils.makedirs(sbindir) + FileUtils.makedirs(mandir) + FileUtils.makedirs(sitelibdir) + # This is the new way forward + elsif (destdir = InstallOptions.destdir) + bindir = join(destdir, bindir) + sbindir = join(destdir, sbindir) + mandir = join(destdir, mandir) + sitelibdir = join(destdir, sitelibdir) + + FileUtils.makedirs(bindir) + FileUtils.makedirs(sbindir) + FileUtils.makedirs(mandir) + FileUtils.makedirs(sitelibdir) + end - tmpdirs << bindir + tmpdirs << bindir - InstallOptions.tmp_dirs = tmpdirs.compact - InstallOptions.site_dir = sitelibdir - InstallOptions.bin_dir = bindir - InstallOptions.sbin_dir = sbindir - InstallOptions.lib_dir = libdir - InstallOptions.man_dir = mandir + InstallOptions.tmp_dirs = tmpdirs.compact + InstallOptions.site_dir = sitelibdir + InstallOptions.bin_dir = bindir + InstallOptions.sbin_dir = sbindir + InstallOptions.lib_dir = libdir + InstallOptions.man_dir = mandir end ## @@ -307,69 +307,69 @@ def join(basedir, dir) # Build the rdoc documentation. Also, try to build the RI documentation. # def build_rdoc(files) - return unless $haverdoc - begin - r = RDoc::RDoc.new - r.document(["--main", "README", "--title", - "Puppet -- Site Configuration Management", "--line-numbers"] + files) - rescue RDoc::RDocError => e - $stderr.puts e.message - rescue Exception => e - $stderr.puts "Couldn't build RDoc documentation\n#{e.message}" - end + return unless $haverdoc + begin + r = RDoc::RDoc.new + r.document(["--main", "README", "--title", + "Puppet -- Site Configuration Management", "--line-numbers"] + files) + rescue RDoc::RDocError => e + $stderr.puts e.message + rescue Exception => e + $stderr.puts "Couldn't build RDoc documentation\n#{e.message}" + end end def build_ri(files) - return unless $haverdoc - begin - ri = RDoc::RDoc.new - #ri.document(["--ri-site", "--merge"] + files) - ri.document(["--ri-site"] + files) - rescue RDoc::RDocError => e - $stderr.puts e.message - rescue Exception => e - $stderr.puts "Couldn't build Ri documentation\n#{e.message}" - $stderr.puts "Continuing with install..." - end + return unless $haverdoc + begin + ri = RDoc::RDoc.new + #ri.document(["--ri-site", "--merge"] + files) + ri.document(["--ri-site"] + files) + rescue RDoc::RDocError => e + $stderr.puts e.message + rescue Exception => e + $stderr.puts "Couldn't build Ri documentation\n#{e.message}" + $stderr.puts "Continuing with install..." + end end def build_man(bins) - return unless $haveman - begin - # Locate rst2man - rst2man = %x{which rst2man.py} - rst2man.chomp! - bins.each do |bin| - b = bin.gsub( "bin/", "") - %x{#{bin} --help > ./#{b}.rst} - %x{#{rst2man} ./#{b}.rst ./man/man8/#{b}.8} - File.unlink("./#{b}.rst") - end - rescue SystemCallError - $stderr.puts "Couldn't build man pages: " + $! - $stderr.puts "Continuing with install..." + return unless $haveman + begin + # Locate rst2man + rst2man = %x{which rst2man.py} + rst2man.chomp! + bins.each do |bin| + b = bin.gsub( "bin/", "") + %x{#{bin} --help > ./#{b}.rst} + %x{#{rst2man} ./#{b}.rst ./man/man8/#{b}.8} + File.unlink("./#{b}.rst") end + rescue SystemCallError + $stderr.puts "Couldn't build man pages: " + $! + $stderr.puts "Continuing with install..." + end end def run_tests(test_list) - begin - require 'test/unit/ui/console/testrunner' - $:.unshift "lib" - test_list.each do |test| - next if File.directory?(test) - require test - end - - tests = [] - ObjectSpace.each_object { |o| tests << o if o.kind_of?(Class) } - tests.delete_if { |o| !o.ancestors.include?(Test::Unit::TestCase) } - tests.delete_if { |o| o == Test::Unit::TestCase } - - tests.each { |test| Test::Unit::UI::Console::TestRunner.run(test) } - $:.shift - rescue LoadError - puts "Missing testrunner library; skipping tests" + begin + require 'test/unit/ui/console/testrunner' + $:.unshift "lib" + test_list.each do |test| + next if File.directory?(test) + require test end + + tests = [] + ObjectSpace.each_object { |o| tests << o if o.kind_of?(Class) } + tests.delete_if { |o| !o.ancestors.include?(Test::Unit::TestCase) } + tests.delete_if { |o| o == Test::Unit::TestCase } + + tests.each { |test| Test::Unit::UI::Console::TestRunner.run(test) } + $:.shift + rescue LoadError + puts "Missing testrunner library; skipping tests" + end end ## @@ -378,57 +378,57 @@ def run_tests(test_list) # (e.g., bin/rdoc becomes rdoc); the shebang line handles running it. Under # windows, we add an '.rb' extension and let file associations do their stuff. def install_binfile(from, op_file, target) - tmp_dir = nil - InstallOptions.tmp_dirs.each do |t| - if File.directory?(t) and File.writable?(t) - tmp_dir = t - break - end + tmp_dir = nil + InstallOptions.tmp_dirs.each do |t| + if File.directory?(t) and File.writable?(t) + tmp_dir = t + break end + end - fail "Cannot find a temporary directory" unless tmp_dir - tmp_file = File.join(tmp_dir, '_tmp') - ruby = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name']) - - File.open(from) do |ip| - File.open(tmp_file, "w") do |op| - ruby = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name']) - op.puts "#!#{ruby}" - contents = ip.readlines - if contents[0] =~ /^#!/ - contents.shift - end - op.write contents.join() - end + fail "Cannot find a temporary directory" unless tmp_dir + tmp_file = File.join(tmp_dir, '_tmp') + ruby = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name']) + + File.open(from) do |ip| + File.open(tmp_file, "w") do |op| + ruby = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name']) + op.puts "#!#{ruby}" + contents = ip.readlines + if contents[0] =~ /^#!/ + contents.shift + end + op.write contents.join() end + end - if is_windows? - installed_wrapper = false + if is_windows? + installed_wrapper = false - if File.exists?("#{from}.bat") - FileUtils.install("#{from}.bat", File.join(target, "#{op_file}.bat"), :mode => 0755, :verbose => true) - installed_wrapper = true - end + if File.exists?("#{from}.bat") + FileUtils.install("#{from}.bat", File.join(target, "#{op_file}.bat"), :mode => 0755, :verbose => true) + installed_wrapper = true + end - if File.exists?("#{from}.cmd") - FileUtils.install("#{from}.cmd", File.join(target, "#{op_file}.cmd"), :mode => 0755, :verbose => true) - installed_wrapper = true - end + if File.exists?("#{from}.cmd") + FileUtils.install("#{from}.cmd", File.join(target, "#{op_file}.cmd"), :mode => 0755, :verbose => true) + installed_wrapper = true + end - if not installed_wrapper - tmp_file2 = File.join(tmp_dir, '_tmp_wrapper') - cwn = File.join(Config::CONFIG['bindir'], op_file) - cwv = CMD_WRAPPER.gsub('', ruby.gsub(%r{/}) { "\\" }).gsub!('', cwn.gsub(%r{/}) { "\\" } ) + if not installed_wrapper + tmp_file2 = File.join(tmp_dir, '_tmp_wrapper') + cwn = File.join(Config::CONFIG['bindir'], op_file) + cwv = CMD_WRAPPER.gsub('', ruby.gsub(%r{/}) { "\\" }).gsub!('', cwn.gsub(%r{/}) { "\\" } ) - File.open(tmp_file2, "wb") { |cw| cw.puts cwv } - FileUtils.install(tmp_file2, File.join(target, "#{op_file}.bat"), :mode => 0755, :verbose => true) + File.open(tmp_file2, "wb") { |cw| cw.puts cwv } + FileUtils.install(tmp_file2, File.join(target, "#{op_file}.bat"), :mode => 0755, :verbose => true) - File.unlink(tmp_file2) - installed_wrapper = true - end + File.unlink(tmp_file2) + installed_wrapper = true end - FileUtils.install(tmp_file, File.join(target, op_file), :mode => 0755, :verbose => true) - File.unlink(tmp_file) + end + FileUtils.install(tmp_file, File.join(target, op_file), :mode => 0755, :verbose => true) + File.unlink(tmp_file) end CMD_WRAPPER = <<-EOS diff --git a/lib/facter.rb b/lib/facter.rb index d862cd3c5d..f56a52c5ba 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -15,233 +15,233 @@ # limitations under the License. module Facter - # This is just so the other classes have the constant. - module Util; end - - require 'facter/util/fact' - require 'facter/util/collection' - - include Comparable - include Enumerable - - FACTERVERSION = '1.6.1' - # = Facter - # Functions as a hash of 'facts' you might care about about your - # system, such as mac address, IP address, Video card, etc. - # returns them dynamically - - # == Synopsis - # - # Generally, treat Facter as a hash: - # == Example - # require 'facter' - # puts Facter['operatingsystem'] - # - - # Set LANG to force i18n to C - # - ENV['LANG'] = 'C' - - GREEN = "" - RESET = "" - @@debug = 0 - @@timing = 0 - @@messages = {} - - # module methods - - def self.collection - unless defined?(@collection) and @collection - @collection = Facter::Util::Collection.new - end - @collection - end - - # Return the version of the library. - def self.version - return FACTERVERSION - end - - # Add some debugging - def self.debug(string) - if string.nil? - return - end - if self.debugging? - puts GREEN + string + RESET - end - end - - def self.debugging? - @@debug != 0 - end - - # show the timing information - def self.show_time(string) - puts "#{GREEN}#{string}#{RESET}" if string and Facter.timing? - end - - def self.timing? - @@timing != 0 - end - - # Return a fact object by name. If you use this, you still have to call - # 'value' on it to retrieve the actual value. - def self.[](name) - collection.fact(name) - end - - class << self - [:fact, :flush, :list, :value].each do |method| - define_method(method) do |*args| - collection.send(method, *args) - end - end - - [:list, :to_hash].each do |method| - define_method(method) do |*args| - collection.load_all - collection.send(method, *args) - end - end - end - - - # Add a resolution mechanism for a named fact. This does not distinguish - # between adding a new fact and adding a new way to resolve a fact. - def self.add(name, options = {}, &block) - collection.add(name, options, &block) - end - - def self.each - # Make sure all facts are loaded. + # This is just so the other classes have the constant. + module Util; end + + require 'facter/util/fact' + require 'facter/util/collection' + + include Comparable + include Enumerable + + FACTERVERSION = '1.6.1' + # = Facter + # Functions as a hash of 'facts' you might care about about your + # system, such as mac address, IP address, Video card, etc. + # returns them dynamically + + # == Synopsis + # + # Generally, treat Facter as a hash: + # == Example + # require 'facter' + # puts Facter['operatingsystem'] + # + + # Set LANG to force i18n to C + # + ENV['LANG'] = 'C' + + GREEN = "" + RESET = "" + @@debug = 0 + @@timing = 0 + @@messages = {} + + # module methods + + def self.collection + unless defined?(@collection) and @collection + @collection = Facter::Util::Collection.new + end + @collection + end + + # Return the version of the library. + def self.version + return FACTERVERSION + end + + # Add some debugging + def self.debug(string) + if string.nil? + return + end + if self.debugging? + puts GREEN + string + RESET + end + end + + def self.debugging? + @@debug != 0 + end + + # show the timing information + def self.show_time(string) + puts "#{GREEN}#{string}#{RESET}" if string and Facter.timing? + end + + def self.timing? + @@timing != 0 + end + + # Return a fact object by name. If you use this, you still have to call + # 'value' on it to retrieve the actual value. + def self.[](name) + collection.fact(name) + end + + class << self + [:fact, :flush, :list, :value].each do |method| + define_method(method) do |*args| + collection.send(method, *args) + end + end + + [:list, :to_hash].each do |method| + define_method(method) do |*args| collection.load_all - - collection.each do |*args| - yield(*args) - end - end - - class << self - # Allow users to call fact names directly on the Facter class, - # either retrieving the value or comparing it to an existing value. - def method_missing(name, *args) - question = false - if name.to_s =~ /\?$/ - question = true - name = name.to_s.sub(/\?$/,'') + collection.send(method, *args) + end + end + end + + + # Add a resolution mechanism for a named fact. This does not distinguish + # between adding a new fact and adding a new way to resolve a fact. + def self.add(name, options = {}, &block) + collection.add(name, options, &block) + end + + def self.each + # Make sure all facts are loaded. + collection.load_all + + collection.each do |*args| + yield(*args) + end + end + + class << self + # Allow users to call fact names directly on the Facter class, + # either retrieving the value or comparing it to an existing value. + def method_missing(name, *args) + question = false + if name.to_s =~ /\?$/ + question = true + name = name.to_s.sub(/\?$/,'') + end + + if fact = collection.fact(name) + if question + value = fact.value.downcase + args.each do |arg| + if arg.to_s.downcase == value + return true end + end - if fact = collection.fact(name) - if question - value = fact.value.downcase - args.each do |arg| - if arg.to_s.downcase == value - return true - end - end - - # If we got this far, there was no match. - return false - else - return fact.value - end - else - # Else, fail like a normal missing method. - raise NoMethodError, "Could not find fact '%s'" % name - end + # If we got this far, there was no match. + return false + else + return fact.value end - end - - # Clear all facts. Mostly used for testing. - def self.clear - Facter.flush - Facter.reset - end - - # Clear all messages. Used only in testing. Can't add to self.clear - # because we don't want to warn multiple times for items that are warnonce'd - def self.clear_messages - @@messages.clear - end - - # Set debugging on or off. - def self.debugging(bit) - if bit - case bit - when TrueClass; @@debug = 1 - when FalseClass; @@debug = 0 - when Fixnum - if bit > 0 - @@debug = 1 - else - @@debug = 0 - end - when String; - if bit.downcase == 'off' - @@debug = 0 - else - @@debug = 1 - end - else - @@debug = 0 - end + else + # Else, fail like a normal missing method. + raise NoMethodError, "Could not find fact '%s'" % name + end + end + end + + # Clear all facts. Mostly used for testing. + def self.clear + Facter.flush + Facter.reset + end + + # Clear all messages. Used only in testing. Can't add to self.clear + # because we don't want to warn multiple times for items that are warnonce'd + def self.clear_messages + @@messages.clear + end + + # Set debugging on or off. + def self.debugging(bit) + if bit + case bit + when TrueClass; @@debug = 1 + when FalseClass; @@debug = 0 + when Fixnum + if bit > 0 + @@debug = 1 else - @@debug = 0 + @@debug = 0 end - end - - # Set timing on or off. - def self.timing(bit) - if bit - case bit - when TrueClass; @@timing = 1 - when Fixnum - if bit > 0 - @@timing = 1 - else - @@timing = 0 - end - end + when String; + if bit.downcase == 'off' + @@debug = 0 else - @@timing = 0 + @@debug = 1 end - end - - def self.warn(msg) - if Facter.debugging? and msg and not msg.empty? - msg = [msg] unless msg.respond_to? :each - msg.each { |line| Kernel.warn line } + else + @@debug = 0 + end + else + @@debug = 0 + end + end + + # Set timing on or off. + def self.timing(bit) + if bit + case bit + when TrueClass; @@timing = 1 + when Fixnum + if bit > 0 + @@timing = 1 + else + @@timing = 0 end + end + else + @@timing = 0 end + end - # Warn once. - def self.warnonce(msg) - if msg and not msg.empty? and @@messages[msg].nil? - @@messages[msg] = true - Kernel.warn(msg) - end + def self.warn(msg) + if Facter.debugging? and msg and not msg.empty? + msg = [msg] unless msg.respond_to? :each + msg.each { |line| Kernel.warn line } end + end - # Remove them all. - def self.reset - @collection = nil + # Warn once. + def self.warnonce(msg) + if msg and not msg.empty? and @@messages[msg].nil? + @@messages[msg] = true + Kernel.warn(msg) end + end - # Load all of the default facts, and then everything from disk. - def self.loadfacts - collection.load_all - end + # Remove them all. + def self.reset + @collection = nil + end - @search_path = [] + # Load all of the default facts, and then everything from disk. + def self.loadfacts + collection.load_all + end - # Register a directory to search through. - def self.search(*dirs) - @search_path += dirs - end + @search_path = [] - # Return our registered search directories. - def self.search_path - @search_path.dup - end + # Register a directory to search through. + def self.search(*dirs) + @search_path += dirs + end + + # Return our registered search directories. + def self.search_path + @search_path.dup + end end diff --git a/lib/facter/Cfkey.rb b/lib/facter/Cfkey.rb index e54aeb0089..e4e03f0420 100644 --- a/lib/facter/Cfkey.rb +++ b/lib/facter/Cfkey.rb @@ -14,29 +14,29 @@ ## Facter.add(:Cfkey) do - setcode do - value = nil - ["/usr/local/etc/cfkey.pub", - "/etc/cfkey.pub", - "/var/cfng/keys/localhost.pub", - "/var/cfengine/ppkeys/localhost.pub", - "/var/lib/cfengine/ppkeys/localhost.pub", - "/var/lib/cfengine2/ppkeys/localhost.pub" - ].each do |file| - if FileTest.file?(file) - File.open(file) { |openfile| - value = openfile.readlines.reject { |line| - line =~ /PUBLIC KEY/ - }.collect { |line| - line.chomp - }.join("") - } - end - if value - break - end - end - - value + setcode do + value = nil + ["/usr/local/etc/cfkey.pub", + "/etc/cfkey.pub", + "/var/cfng/keys/localhost.pub", + "/var/cfengine/ppkeys/localhost.pub", + "/var/lib/cfengine/ppkeys/localhost.pub", + "/var/lib/cfengine2/ppkeys/localhost.pub" + ].each do |file| + if FileTest.file?(file) + File.open(file) { |openfile| + value = openfile.readlines.reject { |line| + line =~ /PUBLIC KEY/ + }.collect { |line| + line.chomp + }.join("") + } + end + if value + break + end end + + value + end end diff --git a/lib/facter/architecture.rb b/lib/facter/architecture.rb index 5be1bcbb31..cd0017c176 100644 --- a/lib/facter/architecture.rb +++ b/lib/facter/architecture.rb @@ -12,27 +12,27 @@ # Facter.add(:architecture) do - setcode do - model = Facter.value(:hardwaremodel) - case model - # most linuxen use "x86_64" - when "x86_64" - case Facter.value(:operatingsystem) - when "Debian", "Gentoo", "GNU/kFreeBSD", "Ubuntu" - "amd64" - else - model - end - when /(i[3456]86|pentium)/ - case Facter.value(:operatingsystem) - when "Gentoo" - "x86" - else - "i386" - end - else - model - end + setcode do + model = Facter.value(:hardwaremodel) + case model + # most linuxen use "x86_64" + when "x86_64" + case Facter.value(:operatingsystem) + when "Debian", "Gentoo", "GNU/kFreeBSD", "Ubuntu" + "amd64" + else + model + end + when /(i[3456]86|pentium)/ + case Facter.value(:operatingsystem) + when "Gentoo" + "x86" + else + "i386" + end + else + model end + end end diff --git a/lib/facter/domain.rb b/lib/facter/domain.rb index a14c3e6ed4..92eafa3eeb 100644 --- a/lib/facter/domain.rb +++ b/lib/facter/domain.rb @@ -19,45 +19,45 @@ # Facter.add(:domain) do - setcode do - # Get the domain from various sources; the order of these - # steps is important + setcode do + # Get the domain from various sources; the order of these + # steps is important - if name = Facter::Util::Resolution.exec('hostname') and - name =~ /.*?\.(.+$)/ + if name = Facter::Util::Resolution.exec('hostname') and + name =~ /.*?\.(.+$)/ - $1 - elsif domain = Facter::Util::Resolution.exec('dnsdomainname') and - domain =~ /.+\..+/ + $1 + elsif domain = Facter::Util::Resolution.exec('dnsdomainname') and + domain =~ /.+\..+/ - domain - elsif FileTest.exists?("/etc/resolv.conf") - domain = nil - search = nil - File.open("/etc/resolv.conf") { |file| - file.each { |line| - if line =~ /^\s*domain\s+(\S+)/ - domain = $1 - elsif line =~ /^\s*search\s+(\S+)/ - search = $1 - end - } - } - next domain if domain - next search if search - end + domain + elsif FileTest.exists?("/etc/resolv.conf") + domain = nil + search = nil + File.open("/etc/resolv.conf") { |file| + file.each { |line| + if line =~ /^\s*domain\s+(\S+)/ + domain = $1 + elsif line =~ /^\s*search\s+(\S+)/ + search = $1 + end + } + } + next domain if domain + next search if search end + end end Facter.add(:domain) do - confine :kernel => :windows - setcode do - require 'facter/util/wmi' - domain = "" - Facter::Util::WMI.execquery("select DNSDomain from Win32_NetworkAdapterConfiguration where IPEnabled = True").each { |nic| - domain = nic.DNSDomain - break - } - domain - end + confine :kernel => :windows + setcode do + require 'facter/util/wmi' + domain = "" + Facter::Util::WMI.execquery("select DNSDomain from Win32_NetworkAdapterConfiguration where IPEnabled = True").each { |nic| + domain = nic.DNSDomain + break + } + domain + end end diff --git a/lib/facter/facterversion.rb b/lib/facter/facterversion.rb index 574da999ff..519d660329 100644 --- a/lib/facter/facterversion.rb +++ b/lib/facter/facterversion.rb @@ -8,5 +8,5 @@ # Facter.add(:facterversion) do - setcode { Facter::FACTERVERSION.to_s } + setcode { Facter::FACTERVERSION.to_s } end diff --git a/lib/facter/fqdn.rb b/lib/facter/fqdn.rb index 090ca63060..68597f35f1 100644 --- a/lib/facter/fqdn.rb +++ b/lib/facter/fqdn.rb @@ -10,13 +10,13 @@ # Facter.add(:fqdn) do - setcode do - host = Facter.value(:hostname) - domain = Facter.value(:domain) - if host and domain - [host, domain].join(".") - else - nil - end + setcode do + host = Facter.value(:hostname) + domain = Facter.value(:domain) + if host and domain + [host, domain].join(".") + else + nil end + end end diff --git a/lib/facter/hardwareisa.rb b/lib/facter/hardwareisa.rb index 323983ea64..a58a043472 100644 --- a/lib/facter/hardwareisa.rb +++ b/lib/facter/hardwareisa.rb @@ -11,6 +11,6 @@ # Facter.add(:hardwareisa) do - setcode 'uname -p' - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS Scientific SLC SuSE SLES Debian Ubuntu Gentoo FreeBSD OpenBSD NetBSD DragonFly OEL OracleLinux OVS GNU/kFreeBSD} + setcode 'uname -p' + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS Scientific SLC SuSE SLES Debian Ubuntu Gentoo FreeBSD OpenBSD NetBSD DragonFly OEL OracleLinux OVS GNU/kFreeBSD} end diff --git a/lib/facter/hardwaremodel.rb b/lib/facter/hardwaremodel.rb index 8f52fefed3..07e750129f 100644 --- a/lib/facter/hardwaremodel.rb +++ b/lib/facter/hardwaremodel.rb @@ -12,23 +12,23 @@ # Facter.add(:hardwaremodel) do - setcode 'uname -m' + setcode 'uname -m' end Facter.add(:hardwaremodel) do - confine :operatingsystem => :aix - setcode do - model = Facter::Util::Resolution.exec('lsattr -El sys0 -a modelname') - if model =~ /modelname\s(\S+)\s/ - $1 - end + confine :operatingsystem => :aix + setcode do + model = Facter::Util::Resolution.exec('lsattr -El sys0 -a modelname') + if model =~ /modelname\s(\S+)\s/ + $1 end + end end Facter.add(:hardwaremodel) do - confine :operatingsystem => :windows - setcode do - require 'rbconfig' - Config::CONFIG['host_cpu'] - end + confine :operatingsystem => :windows + setcode do + require 'rbconfig' + Config::CONFIG['host_cpu'] + end end diff --git a/lib/facter/hostname.rb b/lib/facter/hostname.rb index 36c353f98b..2371ec8796 100644 --- a/lib/facter/hostname.rb +++ b/lib/facter/hostname.rb @@ -12,22 +12,22 @@ # Facter.add(:hostname, :ldapname => "cn") do - setcode do - hostname = nil - if name = Facter::Util::Resolution.exec('hostname') - if name =~ /(.*?)\./ - hostname = $1 - else - hostname = name - end - end - hostname + setcode do + hostname = nil + if name = Facter::Util::Resolution.exec('hostname') + if name =~ /(.*?)\./ + hostname = $1 + else + hostname = name + end end + hostname + end end Facter.add(:hostname) do - confine :kernel => :darwin, :kernelrelease => "R7" - setcode do - Facter::Util::Resolution.exec('/usr/sbin/scutil --get LocalHostName') - end + confine :kernel => :darwin, :kernelrelease => "R7" + setcode do + Facter::Util::Resolution.exec('/usr/sbin/scutil --get LocalHostName') + end end diff --git a/lib/facter/id.rb b/lib/facter/id.rb index cc0a0a6b65..0d80f98972 100644 --- a/lib/facter/id.rb +++ b/lib/facter/id.rb @@ -12,10 +12,10 @@ # Facter.add(:id) do - setcode "whoami" + setcode "whoami" end Facter.add(:id) do - confine :kernel => :SunOS - setcode "/usr/xpg4/bin/id -un" + confine :kernel => :SunOS + setcode "/usr/xpg4/bin/id -un" end diff --git a/lib/facter/interfaces.rb b/lib/facter/interfaces.rb index 04b1c198fa..d02a4f7223 100644 --- a/lib/facter/interfaces.rb +++ b/lib/facter/interfaces.rb @@ -20,22 +20,22 @@ # is missing. Facter.add(:interfaces) do - confine :kernel => Facter::Util::IP.supported_platforms - setcode do - Facter::Util::IP.get_interfaces.collect { |iface| Facter::Util::IP.alphafy(iface) }.join(",") - end + confine :kernel => Facter::Util::IP.supported_platforms + setcode do + Facter::Util::IP.get_interfaces.collect { |iface| Facter::Util::IP.alphafy(iface) }.join(",") + end end Facter::Util::IP.get_interfaces.each do |interface| - # Make a fact for each detail of each interface. Yay. - # There's no point in confining these facts, since we wouldn't be able to create - # them if we weren't running on a supported platform. - %w{ipaddress ipaddress6 macaddress netmask}.each do |label| - Facter.add(label + "_" + Facter::Util::IP.alphafy(interface)) do - setcode do - Facter::Util::IP.get_interface_value(interface, label) - end - end + # Make a fact for each detail of each interface. Yay. + # There's no point in confining these facts, since we wouldn't be able to create + # them if we weren't running on a supported platform. + %w{ipaddress ipaddress6 macaddress netmask}.each do |label| + Facter.add(label + "_" + Facter::Util::IP.alphafy(interface)) do + setcode do + Facter::Util::IP.get_interface_value(interface, label) + end end + end end diff --git a/lib/facter/ipaddress.rb b/lib/facter/ipaddress.rb index 41b22e2286..42d028246c 100644 --- a/lib/facter/ipaddress.rb +++ b/lib/facter/ipaddress.rb @@ -23,138 +23,138 @@ # Facter.add(:ipaddress) do - confine :kernel => :linux - setcode do - ip = nil - output = %x{/sbin/ifconfig} + confine :kernel => :linux + setcode do + ip = nil + output = %x{/sbin/ifconfig} - output.split(/^\S/).each { |str| - if str =~ /inet addr:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ - tmp = $1 - unless tmp =~ /^127\./ - ip = tmp - break - end - end - } + output.split(/^\S/).each { |str| + if str =~ /inet addr:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + tmp = $1 + unless tmp =~ /^127\./ + ip = tmp + break + end + end + } - ip - end + ip + end end Facter.add(:ipaddress) do - confine :kernel => %w{FreeBSD OpenBSD Darwin DragonFly} - setcode do - ip = nil - output = %x{/sbin/ifconfig} + confine :kernel => %w{FreeBSD OpenBSD Darwin DragonFly} + setcode do + ip = nil + output = %x{/sbin/ifconfig} - output.split(/^\S/).each { |str| - if str =~ /inet ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ - tmp = $1 - unless tmp =~ /^127\./ - ip = tmp - break - end - end - } + output.split(/^\S/).each { |str| + if str =~ /inet ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + tmp = $1 + unless tmp =~ /^127\./ + ip = tmp + break + end + end + } - ip - end + ip + end end Facter.add(:ipaddress) do - confine :kernel => %w{NetBSD SunOS} - setcode do - ip = nil - output = %x{/sbin/ifconfig -a} + confine :kernel => %w{NetBSD SunOS} + setcode do + ip = nil + output = %x{/sbin/ifconfig -a} - output.split(/^\S/).each { |str| - if str =~ /inet ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ - tmp = $1 - unless tmp =~ /^127\./ or tmp == "0.0.0.0" - ip = tmp - break - end - end - } + output.split(/^\S/).each { |str| + if str =~ /inet ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + tmp = $1 + unless tmp =~ /^127\./ or tmp == "0.0.0.0" + ip = tmp + break + end + end + } - ip - end + ip + end end Facter.add(:ipaddress) do - confine :kernel => %w{AIX} - setcode do - ip = nil - output = %x{/usr/sbin/ifconfig -a} + confine :kernel => %w{AIX} + setcode do + ip = nil + output = %x{/usr/sbin/ifconfig -a} - output.split(/^\S/).each { |str| - if str =~ /inet ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ - tmp = $1 - unless tmp =~ /^127\./ - ip = tmp - break - end - end - } + output.split(/^\S/).each { |str| + if str =~ /inet ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + tmp = $1 + unless tmp =~ /^127\./ + ip = tmp + break + end + end + } - ip - end + ip + end end Facter.add(:ipaddress) do - confine :kernel => %w{windows} - setcode do - require 'socket' - IPSocket.getaddress(Socket.gethostname) - end + confine :kernel => %w{windows} + setcode do + require 'socket' + IPSocket.getaddress(Socket.gethostname) + end end Facter.add(:ipaddress, :ldapname => "iphostnumber", :timeout => 2) do - setcode do + setcode do + if Facter.value(:kernel) == 'windows' + require 'win32/resolv' + else + require 'resolv' + end + + begin + if hostname = Facter.value(:hostname) if Facter.value(:kernel) == 'windows' - require 'win32/resolv' + ip = Win32::Resolv.get_resolv_info.last[0] else - require 'resolv' + ip = Resolv.getaddress(hostname) end - - begin - if hostname = Facter.value(:hostname) - if Facter.value(:kernel) == 'windows' - ip = Win32::Resolv.get_resolv_info.last[0] - else - ip = Resolv.getaddress(hostname) - end - unless ip == "127.0.0.1" - ip - end - else - nil - end - rescue Resolv::ResolvError - nil - rescue NoMethodError # i think this is a bug in resolv.rb? - nil + unless ip == "127.0.0.1" + ip end + else + nil + end + rescue Resolv::ResolvError + nil + rescue NoMethodError # i think this is a bug in resolv.rb? + nil end + end end Facter.add(:ipaddress, :timeout => 2) do - setcode do - if hostname = Facter.value(:hostname) - # we need Hostname to exist for this to work - host = nil - if host = Facter::Util::Resolution.exec("host #{hostname}") - list = host.chomp.split(/\s/) - if defined? list[-1] and - list[-1] =~ /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ - list[-1] - end - else - nil - end - else - nil + setcode do + if hostname = Facter.value(:hostname) + # we need Hostname to exist for this to work + host = nil + if host = Facter::Util::Resolution.exec("host #{hostname}") + list = host.chomp.split(/\s/) + if defined? list[-1] and + list[-1] =~ /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ + list[-1] end + else + nil + end + else + nil end + end end diff --git a/lib/facter/iphostnumber.rb b/lib/facter/iphostnumber.rb index cddaadd1e6..2d22017c28 100644 --- a/lib/facter/iphostnumber.rb +++ b/lib/facter/iphostnumber.rb @@ -10,20 +10,20 @@ # Facter.add(:iphostnumber) do - confine :kernel => :darwin, :kernelrelease => "R6" - setcode do - %x{/usr/sbin/scutil --get LocalHostName} - end + confine :kernel => :darwin, :kernelrelease => "R6" + setcode do + %x{/usr/sbin/scutil --get LocalHostName} + end end Facter.add(:iphostnumber) do - confine :kernel => :darwin, :kernelrelease => "R6" - setcode do - ether = nil - output = %x{/sbin/ifconfig} + confine :kernel => :darwin, :kernelrelease => "R6" + setcode do + ether = nil + output = %x{/sbin/ifconfig} - output =~ /HWaddr (\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ - ether = $1 + output =~ /HWaddr (\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ + ether = $1 - ether - end + ether + end end diff --git a/lib/facter/kernel.rb b/lib/facter/kernel.rb index 2c925eb61f..31ac22d334 100644 --- a/lib/facter/kernel.rb +++ b/lib/facter/kernel.rb @@ -10,13 +10,13 @@ # Facter.add(:kernel) do - setcode do - require 'facter/util/config' + setcode do + require 'facter/util/config' - if Facter::Util::Config.is_windows? - 'windows' - else - Facter::Util::Resolution.exec("uname -s") - end + if Facter::Util::Config.is_windows? + 'windows' + else + Facter::Util::Resolution.exec("uname -s") end + end end diff --git a/lib/facter/kernelmajversion.rb b/lib/facter/kernelmajversion.rb index 84f71d4504..f7302d4ea6 100644 --- a/lib/facter/kernelmajversion.rb +++ b/lib/facter/kernelmajversion.rb @@ -9,7 +9,7 @@ # Facter.add("kernelmajversion") do - setcode do - Facter.value(:kernelversion).split('.')[0..1].join('.') - end + setcode do + Facter.value(:kernelversion).split('.')[0..1].join('.') + end end diff --git a/lib/facter/kernelrelease.rb b/lib/facter/kernelrelease.rb index 1d348262f5..a38b128ebf 100644 --- a/lib/facter/kernelrelease.rb +++ b/lib/facter/kernelrelease.rb @@ -12,23 +12,23 @@ # Facter.add(:kernelrelease) do - setcode 'uname -r' + setcode 'uname -r' end Facter.add(:kernelrelease) do - confine :kernel => :aix - setcode 'oslevel -s' + confine :kernel => :aix + setcode 'oslevel -s' end Facter.add(:kernelrelease) do - confine :kernel => %{windows} - setcode do - require 'facter/util/wmi' - version = "" - Facter::Util::WMI.execquery("SELECT Version from Win32_OperatingSystem").each do |ole| - version = "#{ole.Version}" - break - end - version + confine :kernel => %{windows} + setcode do + require 'facter/util/wmi' + version = "" + Facter::Util::WMI.execquery("SELECT Version from Win32_OperatingSystem").each do |ole| + version = "#{ole.Version}" + break end + version + end end diff --git a/lib/facter/kernelversion.rb b/lib/facter/kernelversion.rb index 7e0d95cef1..b8fc34c8e9 100644 --- a/lib/facter/kernelversion.rb +++ b/lib/facter/kernelversion.rb @@ -11,12 +11,12 @@ # Facter.add("kernelversion") do - setcode do - Facter['kernelrelease'].value.split('-')[0] - end + setcode do + Facter['kernelrelease'].value.split('-')[0] + end end Facter.add("kernelversion") do - confine :kernel => :sunos - setcode 'uname -v' + confine :kernel => :sunos + setcode 'uname -v' end diff --git a/lib/facter/lsb.rb b/lib/facter/lsb.rb index 7beb41d271..7cefb5ca35 100644 --- a/lib/facter/lsb.rb +++ b/lib/facter/lsb.rb @@ -20,20 +20,20 @@ "LSBDistDescription" => %r{^Description:\t(.*)$}, "LSBDistCodeName" => %r{^Codename:\t(.*)$} }.each do |fact, pattern| - Facter.add(fact) do - confine :kernel => [ :linux, :"gnu/kfreebsd" ] - setcode do - unless defined?(lsbdata) and defined?(lsbtime) and (Time.now.to_i - lsbtime.to_i < 5) - type = nil - lsbtime = Time.now - lsbdata = Facter::Util::Resolution.exec('lsb_release -a 2>/dev/null') - end + Facter.add(fact) do + confine :kernel => [ :linux, :"gnu/kfreebsd" ] + setcode do + unless defined?(lsbdata) and defined?(lsbtime) and (Time.now.to_i - lsbtime.to_i < 5) + type = nil + lsbtime = Time.now + lsbdata = Facter::Util::Resolution.exec('lsb_release -a 2>/dev/null') + end - if pattern.match(lsbdata) - $1 - else - nil - end - end + if pattern.match(lsbdata) + $1 + else + nil + end end + end end diff --git a/lib/facter/lsbmajdistrelease.rb b/lib/facter/lsbmajdistrelease.rb index 1d9715870c..df1e887c51 100644 --- a/lib/facter/lsbmajdistrelease.rb +++ b/lib/facter/lsbmajdistrelease.rb @@ -15,13 +15,13 @@ require 'facter' Facter.add("lsbmajdistrelease") do - confine :operatingsystem => %w{Linux Fedora RedHat CentOS Scientific SLC SuSE SLES Debian Ubuntu Gentoo OEL OracleLinux OVS GNU/kFreeBSD} - setcode do - if /(\d*)\./i =~ Facter.value(:lsbdistrelease) - result=$1 - else - result=Facter.value(:lsbdistrelease) - end - result + confine :operatingsystem => %w{Linux Fedora RedHat CentOS Scientific SLC SuSE SLES Debian Ubuntu Gentoo OEL OracleLinux OVS GNU/kFreeBSD} + setcode do + if /(\d*)\./i =~ Facter.value(:lsbdistrelease) + result=$1 + else + result=Facter.value(:lsbdistrelease) end + result + end end diff --git a/lib/facter/macaddress.rb b/lib/facter/macaddress.rb index 341654a9fc..25e2ef4dcf 100644 --- a/lib/facter/macaddress.rb +++ b/lib/facter/macaddress.rb @@ -10,69 +10,69 @@ require 'facter/util/macaddress' Facter.add(:macaddress) do - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS Scientific SLC SuSE SLES Debian Gentoo Ubuntu OEL OracleLinux OVS GNU/kFreeBSD} - setcode do - ether = [] - output = Facter::Util::Resolution.exec("/sbin/ifconfig -a") - output.each_line do |s| - ether.push($1) if s =~ /(?:ether|HWaddr) (\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/ - end - Facter::Util::Macaddress.standardize(ether[0]) + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS Scientific SLC SuSE SLES Debian Gentoo Ubuntu OEL OracleLinux OVS GNU/kFreeBSD} + setcode do + ether = [] + output = Facter::Util::Resolution.exec("/sbin/ifconfig -a") + output.each_line do |s| + ether.push($1) if s =~ /(?:ether|HWaddr) (\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/ end + Facter::Util::Macaddress.standardize(ether[0]) + end end Facter.add(:macaddress) do - confine :operatingsystem => "Solaris" - setcode do - ether = [] - output = Facter::Util::Resolution.exec("/usr/bin/netstat -np") - output.each_line do |s| - ether.push($1) if s =~ /(?:SPLA)\s+(\w{2}:\w{2}:\w{2}:\w{2}:\w{2}:\w{2})/ - end - Facter::Util::Macaddress.standardize(ether[0]) + confine :operatingsystem => "Solaris" + setcode do + ether = [] + output = Facter::Util::Resolution.exec("/usr/bin/netstat -np") + output.each_line do |s| + ether.push($1) if s =~ /(?:SPLA)\s+(\w{2}:\w{2}:\w{2}:\w{2}:\w{2}:\w{2})/ end + Facter::Util::Macaddress.standardize(ether[0]) + end end Facter.add(:macaddress) do - confine :operatingsystem => %w{FreeBSD OpenBSD DragonFly} - setcode do + confine :operatingsystem => %w{FreeBSD OpenBSD DragonFly} + setcode do ether = [] - output = Facter::Util::Resolution.exec("/sbin/ifconfig") - output.each_line do |s| - if s =~ /(?:ether|lladdr)\s+(\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ - ether.push($1) - end - end - Facter::Util::Macaddress.standardize(ether[0]) + output = Facter::Util::Resolution.exec("/sbin/ifconfig") + output.each_line do |s| + if s =~ /(?:ether|lladdr)\s+(\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ + ether.push($1) + end end + Facter::Util::Macaddress.standardize(ether[0]) + end end Facter.add(:macaddress) do - confine :kernel => :darwin - setcode { Facter::Util::Macaddress::Darwin.macaddress } + confine :kernel => :darwin + setcode { Facter::Util::Macaddress::Darwin.macaddress } end Facter.add(:macaddress) do - confine :kernel => %w{AIX} - setcode do - ether = [] - ip = nil - output = %x{/usr/sbin/ifconfig -a} - output.each_line do |str| - if str =~ /([a-z]+\d+): flags=/ - devname = $1 - unless devname =~ /lo0/ - output2 = %x{/usr/bin/entstat #{devname}} - output2.each_line do |str2| - if str2 =~ /^Hardware Address: (\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/ - ether.push($1) - end - end - end + confine :kernel => %w{AIX} + setcode do + ether = [] + ip = nil + output = %x{/usr/sbin/ifconfig -a} + output.each_line do |str| + if str =~ /([a-z]+\d+): flags=/ + devname = $1 + unless devname =~ /lo0/ + output2 = %x{/usr/bin/entstat #{devname}} + output2.each_line do |str2| + if str2 =~ /^Hardware Address: (\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/ + ether.push($1) end + end end - Facter::Util::Macaddress.standardize(ether[0]) + end end + Facter::Util::Macaddress.standardize(ether[0]) + end end Facter.add(:macaddress) do diff --git a/lib/facter/macosx.rb b/lib/facter/macosx.rb index 7c8562d4bd..74a0cc7b42 100644 --- a/lib/facter/macosx.rb +++ b/lib/facter/macosx.rb @@ -27,30 +27,30 @@ require 'facter/util/macosx' if Facter.value(:kernel) == "Darwin" - Facter::Util::Macosx.hardware_overview.each do |fact, value| - Facter.add("sp_#{fact}") do - confine :kernel => :darwin - setcode do - value.to_s - end - end + Facter::Util::Macosx.hardware_overview.each do |fact, value| + Facter.add("sp_#{fact}") do + confine :kernel => :darwin + setcode do + value.to_s + end end + end - Facter::Util::Macosx.os_overview.each do |fact, value| - Facter.add("sp_#{fact}") do - confine :kernel => :darwin - setcode do - value.to_s - end - end + Facter::Util::Macosx.os_overview.each do |fact, value| + Facter.add("sp_#{fact}") do + confine :kernel => :darwin + setcode do + value.to_s + end end + end - Facter::Util::Macosx.sw_vers.each do |fact, value| - Facter.add(fact) do - confine :kernel => :darwin - setcode do - value - end - end + Facter::Util::Macosx.sw_vers.each do |fact, value| + Facter.add(fact) do + confine :kernel => :darwin + setcode do + value + end end + end end diff --git a/lib/facter/manufacturer.rb b/lib/facter/manufacturer.rb index 98ed29e610..1857d09346 100644 --- a/lib/facter/manufacturer.rb +++ b/lib/facter/manufacturer.rb @@ -19,39 +19,39 @@ require 'facter/util/manufacturer' if Facter.value(:kernel) == "OpenBSD" - mfg_keys = { - 'hw.vendor' => 'manufacturer', - 'hw.product' => 'productname', - 'hw.serialno' => 'serialnumber' - } + mfg_keys = { + 'hw.vendor' => 'manufacturer', + 'hw.product' => 'productname', + 'hw.serialno' => 'serialnumber' + } - Facter::Manufacturer.sysctl_find_system_info(mfg_keys) + Facter::Manufacturer.sysctl_find_system_info(mfg_keys) elsif Facter.value(:kernel) == "Darwin" - mfg_keys = { - 'hw.model' => 'productname' - } - Facter::Manufacturer.sysctl_find_system_info(mfg_keys) + mfg_keys = { + 'hw.model' => 'productname' + } + Facter::Manufacturer.sysctl_find_system_info(mfg_keys) elsif Facter.value(:kernel) == "SunOS" and Facter.value(:hardwareisa) == "sparc" - Facter::Manufacturer.prtdiag_sparc_find_system_info() + Facter::Manufacturer.prtdiag_sparc_find_system_info() elsif Facter.value(:kernel) == "windows" - win32_keys = { - 'manufacturer' => ['Manufacturer', 'Bios'], - 'serialNumber' => ['Serialnumber', 'Bios'], - 'productname' => ['Name', 'ComputerSystemProduct'] - } - Facter::Manufacturer.win32_find_system_info(win32_keys) + win32_keys = { + 'manufacturer' => ['Manufacturer', 'Bios'], + 'serialNumber' => ['Serialnumber', 'Bios'], + 'productname' => ['Name', 'ComputerSystemProduct'] + } + Facter::Manufacturer.win32_find_system_info(win32_keys) else - query = { - '[Ss]ystem [Ii]nformation' => [ - { 'Manufacturer:' => 'manufacturer' }, - { 'Product(?: Name)?:' => 'productname' }, - { 'Serial Number:' => 'serialnumber' } - ], - '(Chassis Information|system enclosure or chassis)' => [ - { '(?:Chassis )?Type:' => 'type' } - ] - } + query = { + '[Ss]ystem [Ii]nformation' => [ + { 'Manufacturer:' => 'manufacturer' }, + { 'Product(?: Name)?:' => 'productname' }, + { 'Serial Number:' => 'serialnumber' } + ], + '(Chassis Information|system enclosure or chassis)' => [ + { '(?:Chassis )?Type:' => 'type' } + ] + } - Facter::Manufacturer.dmi_find_system_info(query) + Facter::Manufacturer.dmi_find_system_info(query) end diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb index f7e9f67451..0b7731fa59 100644 --- a/lib/facter/memory.rb +++ b/lib/facter/memory.rb @@ -29,180 +29,180 @@ :SwapSize => "SwapTotal", :SwapFree => "SwapFree" }.each do |fact, name| - Facter.add(fact) do - confine :kernel => [ :linux, :"gnu/kfreebsd" ] - setcode do - Facter::Memory.meminfo_number(name) - end + Facter.add(fact) do + confine :kernel => [ :linux, :"gnu/kfreebsd" ] + setcode do + Facter::Memory.meminfo_number(name) end + end end Facter.add("SwapSize") do - confine :kernel => :Darwin - setcode do + confine :kernel => :Darwin + setcode do swap = Facter::Util::Resolution.exec('sysctl vm.swapusage') swaptotal = 0 if swap =~ /total = (\S+)/ then swaptotal = $1; end - swaptotal - end + swaptotal + end end Facter.add("SwapFree") do - confine :kernel => :Darwin - setcode do + confine :kernel => :Darwin + setcode do swap = Facter::Util::Resolution.exec('sysctl vm.swapusage') swapfree = 0 if swap =~ /free = (\S+)/ then swapfree = $1; end - swapfree - end + swapfree + end end Facter.add("SwapEncrypted") do - confine :kernel => :Darwin - setcode do + confine :kernel => :Darwin + setcode do swap = Facter::Util::Resolution.exec('sysctl vm.swapusage') encrypted = false if swap =~ /\(encrypted\)/ then encrypted = true; end - encrypted - end + encrypted + end end if Facter.value(:kernel) == "AIX" and Facter.value(:id) == "root" - swap = Facter::Util::Resolution.exec('swap -l') - swapfree, swaptotal = 0, 0 - swap.each_line do |dev| - if dev =~ /^\/\S+\s.*\s+(\S+)MB\s+(\S+)MB/ - swaptotal += $1.to_i - swapfree += $2.to_i - end + swap = Facter::Util::Resolution.exec('swap -l') + swapfree, swaptotal = 0, 0 + swap.each_line do |dev| + if dev =~ /^\/\S+\s.*\s+(\S+)MB\s+(\S+)MB/ + swaptotal += $1.to_i + swapfree += $2.to_i end + end - Facter.add("SwapSize") do - confine :kernel => :aix - setcode do - Facter::Memory.scale_number(swaptotal.to_f,"MB") - end + Facter.add("SwapSize") do + confine :kernel => :aix + setcode do + Facter::Memory.scale_number(swaptotal.to_f,"MB") end + end - Facter.add("SwapFree") do - confine :kernel => :aix - setcode do - Facter::Memory.scale_number(swapfree.to_f,"MB") - end + Facter.add("SwapFree") do + confine :kernel => :aix + setcode do + Facter::Memory.scale_number(swapfree.to_f,"MB") end + end end if Facter.value(:kernel) == "OpenBSD" - swap = Facter::Util::Resolution.exec('swapctl -s') - swapfree, swaptotal = 0, 0 - if swap =~ /^total: (\d+)k bytes allocated = \d+k used, (\d+)k available$/ - swaptotal = $1.to_i - swapfree = $2.to_i - end + swap = Facter::Util::Resolution.exec('swapctl -s') + swapfree, swaptotal = 0, 0 + if swap =~ /^total: (\d+)k bytes allocated = \d+k used, (\d+)k available$/ + swaptotal = $1.to_i + swapfree = $2.to_i + end - Facter.add("SwapSize") do - confine :kernel => :openbsd - setcode do - Facter::Memory.scale_number(swaptotal.to_f,"kB") - end + Facter.add("SwapSize") do + confine :kernel => :openbsd + setcode do + Facter::Memory.scale_number(swaptotal.to_f,"kB") end + end - Facter.add("SwapFree") do - confine :kernel => :openbsd - setcode do - Facter::Memory.scale_number(swapfree.to_f,"kB") - end + Facter.add("SwapFree") do + confine :kernel => :openbsd + setcode do + Facter::Memory.scale_number(swapfree.to_f,"kB") end + end - Facter::Memory.vmstat_find_free_memory() + Facter::Memory.vmstat_find_free_memory() - Facter.add("MemoryTotal") do - confine :kernel => :openbsd - memtotal = Facter::Util::Resolution.exec("sysctl hw.physmem | cut -d'=' -f2") - setcode do - Facter::Memory.scale_number(memtotal.to_f,"") - end + Facter.add("MemoryTotal") do + confine :kernel => :openbsd + memtotal = Facter::Util::Resolution.exec("sysctl hw.physmem | cut -d'=' -f2") + setcode do + Facter::Memory.scale_number(memtotal.to_f,"") end + end end if Facter.value(:kernel) == "Darwin" - swap = Facter::Util::Resolution.exec('sysctl vm.swapusage') - swapfree, swaptotal = 0, 0 - unless swap.empty? - # Parse the line: - # vm.swapusage: total = 128.00M used = 0.37M free = 127.63M (encrypted) - if swap =~ /total\s=\s(\S+)\s+used\s=\s(\S+)\s+free\s=\s(\S+)\s/ - swaptotal += $1.to_i - swapfree += $3.to_i - end + swap = Facter::Util::Resolution.exec('sysctl vm.swapusage') + swapfree, swaptotal = 0, 0 + unless swap.empty? + # Parse the line: + # vm.swapusage: total = 128.00M used = 0.37M free = 127.63M (encrypted) + if swap =~ /total\s=\s(\S+)\s+used\s=\s(\S+)\s+free\s=\s(\S+)\s/ + swaptotal += $1.to_i + swapfree += $3.to_i end + end - Facter.add("SwapSize") do - confine :kernel => :Darwin - setcode do - Facter::Memory.scale_number(swaptotal.to_f,"MB") - end + Facter.add("SwapSize") do + confine :kernel => :Darwin + setcode do + Facter::Memory.scale_number(swaptotal.to_f,"MB") end + end - Facter.add("SwapFree") do - confine :kernel => :Darwin - setcode do - Facter::Memory.scale_number(swapfree.to_f,"MB") - end + Facter.add("SwapFree") do + confine :kernel => :Darwin + setcode do + Facter::Memory.scale_number(swapfree.to_f,"MB") end + end - Facter::Memory.vmstat_darwin_find_free_memory() + Facter::Memory.vmstat_darwin_find_free_memory() - Facter.add("MemoryTotal") do - confine :kernel => :Darwin - memtotal = Facter::Util::Resolution.exec("sysctl hw.memsize | cut -d':' -f2") - setcode do - Facter::Memory.scale_number(memtotal.to_f,"") - end + Facter.add("MemoryTotal") do + confine :kernel => :Darwin + memtotal = Facter::Util::Resolution.exec("sysctl hw.memsize | cut -d':' -f2") + setcode do + Facter::Memory.scale_number(memtotal.to_f,"") end + end end if Facter.value(:kernel) == "SunOS" - swap = Facter::Util::Resolution.exec('/usr/sbin/swap -l') - swapfree, swaptotal = 0, 0 - swap.each_line do |dev| - if dev =~ /^\/\S+\s.*\s+(\d+)\s+(\d+)$/ - swaptotal += $1.to_i / 2 - swapfree += $2.to_i / 2 - end + swap = Facter::Util::Resolution.exec('/usr/sbin/swap -l') + swapfree, swaptotal = 0, 0 + swap.each_line do |dev| + if dev =~ /^\/\S+\s.*\s+(\d+)\s+(\d+)$/ + swaptotal += $1.to_i / 2 + swapfree += $2.to_i / 2 end + end - Facter.add("SwapSize") do - confine :kernel => :sunos - setcode do - Facter::Memory.scale_number(swaptotal.to_f,"kB") - end + Facter.add("SwapSize") do + confine :kernel => :sunos + setcode do + Facter::Memory.scale_number(swaptotal.to_f,"kB") end + end - Facter.add("SwapFree") do - confine :kernel => :sunos - setcode do - Facter::Memory.scale_number(swapfree.to_f,"kB") - end + Facter.add("SwapFree") do + confine :kernel => :sunos + setcode do + Facter::Memory.scale_number(swapfree.to_f,"kB") end + end - # Total memory size available from prtconf - pconf = Facter::Util::Resolution.exec('/usr/sbin/prtconf 2>/dev/null') - phymem = "" - pconf.each_line do |line| - if line =~ /^Memory size:\s+(\d+) Megabytes/ - phymem = $1 - end + # Total memory size available from prtconf + pconf = Facter::Util::Resolution.exec('/usr/sbin/prtconf 2>/dev/null') + phymem = "" + pconf.each_line do |line| + if line =~ /^Memory size:\s+(\d+) Megabytes/ + phymem = $1 end + end - Facter.add("MemorySize") do - confine :kernel => :sunos - setcode do - Facter::Memory.scale_number(phymem.to_f,"MB") - end + Facter.add("MemorySize") do + confine :kernel => :sunos + setcode do + Facter::Memory.scale_number(phymem.to_f,"MB") end + end - Facter::Memory.vmstat_find_free_memory() + Facter::Memory.vmstat_find_free_memory() end if Facter.value(:kernel) == "windows" @@ -234,31 +234,31 @@ end Facter.add("SwapSize") do - confine :kernel => :dragonfly - setcode do - page_size = Facter::Util::Resolution.exec("/sbin/sysctl -n hw.pagesize").to_f - swaptotal = Facter::Util::Resolution.exec("/sbin/sysctl -n vm.swap_size").to_f * page_size - Facter::Memory.scale_number(swaptotal.to_f,"") - end + confine :kernel => :dragonfly + setcode do + page_size = Facter::Util::Resolution.exec("/sbin/sysctl -n hw.pagesize").to_f + swaptotal = Facter::Util::Resolution.exec("/sbin/sysctl -n vm.swap_size").to_f * page_size + Facter::Memory.scale_number(swaptotal.to_f,"") + end end Facter.add("SwapFree") do - confine :kernel => :dragonfly - setcode do - page_size = Facter::Util::Resolution.exec("/sbin/sysctl -n hw.pagesize").to_f - swaptotal = Facter::Util::Resolution.exec("/sbin/sysctl -n vm.swap_size").to_f * page_size - swap_anon_use = Facter::Util::Resolution.exec("/sbin/sysctl -n vm.swap_anon_use").to_f * page_size - swap_cache_use = Facter::Util::Resolution.exec("/sbin/sysctl -n vm.swap_cache_use").to_f * page_size - swapfree = swaptotal - swap_anon_use - swap_cache_use - Facter::Memory.scale_number(swapfree.to_f,"") - end + confine :kernel => :dragonfly + setcode do + page_size = Facter::Util::Resolution.exec("/sbin/sysctl -n hw.pagesize").to_f + swaptotal = Facter::Util::Resolution.exec("/sbin/sysctl -n vm.swap_size").to_f * page_size + swap_anon_use = Facter::Util::Resolution.exec("/sbin/sysctl -n vm.swap_anon_use").to_f * page_size + swap_cache_use = Facter::Util::Resolution.exec("/sbin/sysctl -n vm.swap_cache_use").to_f * page_size + swapfree = swaptotal - swap_anon_use - swap_cache_use + Facter::Memory.scale_number(swapfree.to_f,"") + end end Facter.add("MemoryTotal") do - confine :kernel => :dragonfly - setcode do - Facter::Memory.vmstat_find_free_memory() - memtotal = Facter::Util::Resolution.exec("sysctl -n hw.physmem") - Facter::Memory.scale_number(memtotal.to_f,"") - end + confine :kernel => :dragonfly + setcode do + Facter::Memory.vmstat_find_free_memory() + memtotal = Facter::Util::Resolution.exec("sysctl -n hw.physmem") + Facter::Memory.scale_number(memtotal.to_f,"") + end end diff --git a/lib/facter/netmask.rb b/lib/facter/netmask.rb index e3979fd689..bc32073334 100644 --- a/lib/facter/netmask.rb +++ b/lib/facter/netmask.rb @@ -18,9 +18,9 @@ require 'facter/util/netmask' Facter.add("netmask") do - confine :kernel => [ :sunos, :linux, :freebsd, :openbsd, :netbsd, :darwin, :"gnu/kfreebsd", :dragonfly ] - setcode do - Facter::NetMask.get_netmask - end + confine :kernel => [ :sunos, :linux, :freebsd, :openbsd, :netbsd, :darwin, :"gnu/kfreebsd", :dragonfly ] + setcode do + Facter::NetMask.get_netmask + end end diff --git a/lib/facter/network.rb b/lib/facter/network.rb index d4faaac16b..9e28c6ba0c 100644 --- a/lib/facter/network.rb +++ b/lib/facter/network.rb @@ -13,9 +13,9 @@ require 'facter/util/ip' Facter::Util::IP.get_interfaces.each do |interface| - Facter.add("network_" + Facter::Util::IP.alphafy(interface)) do - setcode do - Facter::Util::IP.get_network_value(interface) - end + Facter.add("network_" + Facter::Util::IP.alphafy(interface)) do + setcode do + Facter::Util::IP.get_network_value(interface) end + end end diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index eb8ad8504a..b1ffeb8940 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -14,87 +14,87 @@ require 'facter/lsb' Facter.add(:operatingsystem) do - confine :kernel => :sunos - setcode do "Solaris" end + confine :kernel => :sunos + setcode do "Solaris" end end Facter.add(:operatingsystem) do - confine :kernel => :linux - setcode do - if Facter.value(:lsbdistid) == "Ubuntu" - "Ubuntu" - elsif FileTest.exists?("/etc/debian_version") - "Debian" - elsif FileTest.exists?("/etc/gentoo-release") - "Gentoo" - elsif FileTest.exists?("/etc/fedora-release") - "Fedora" - elsif FileTest.exists?("/etc/mandriva-release") - "Mandriva" - elsif FileTest.exists?("/etc/mandrake-release") - "Mandrake" - elsif FileTest.exists?("/etc/meego-release") - "MeeGo" - elsif FileTest.exists?("/etc/arch-release") - "Archlinux" - elsif FileTest.exists?("/etc/oracle-release") - "OracleLinux" - elsif FileTest.exists?("/etc/enterprise-release") - if FileTest.exists?("/etc/ovs-release") - "OVS" - else - "OEL" - end - elsif FileTest.exists?("/etc/arch-release") - "Arch" - elsif FileTest.exists?("/etc/vmware-release") - "VMWareESX" - elsif FileTest.exists?("/etc/redhat-release") - txt = File.read("/etc/redhat-release") - if txt =~ /centos/i - "CentOS" - elsif txt =~ /CERN/ - "SLC" - elsif txt =~ /scientific/i - "Scientific" - elsif txt =~ /^cloudlinux/i - "CloudLinux" - else - "RedHat" - end - elsif FileTest.exists?("/etc/SuSE-release") - txt = File.read("/etc/SuSE-release") - if txt =~ /^SUSE LINUX Enterprise Server/i - "SLES" - elsif txt =~ /^SUSE LINUX Enterprise Desktop/i - "SLED" - elsif txt =~ /^openSUSE/i - "OpenSuSE" - else - "SuSE" - end - elsif FileTest.exists?("/etc/bluewhite64-version") - "Bluewhite64" - elsif FileTest.exists?("/etc/slamd64-version") - "Slamd64" - elsif FileTest.exists?("/etc/slackware-version") - "Slackware" - elsif FileTest.exists?("/etc/alpine-release") - "Alpine" - elsif Facter.value(:lsbdistdescription) =~ /Amazon Linux/ - "Amazon" - end + confine :kernel => :linux + setcode do + if Facter.value(:lsbdistid) == "Ubuntu" + "Ubuntu" + elsif FileTest.exists?("/etc/debian_version") + "Debian" + elsif FileTest.exists?("/etc/gentoo-release") + "Gentoo" + elsif FileTest.exists?("/etc/fedora-release") + "Fedora" + elsif FileTest.exists?("/etc/mandriva-release") + "Mandriva" + elsif FileTest.exists?("/etc/mandrake-release") + "Mandrake" + elsif FileTest.exists?("/etc/meego-release") + "MeeGo" + elsif FileTest.exists?("/etc/arch-release") + "Archlinux" + elsif FileTest.exists?("/etc/oracle-release") + "OracleLinux" + elsif FileTest.exists?("/etc/enterprise-release") + if FileTest.exists?("/etc/ovs-release") + "OVS" + else + "OEL" + end + elsif FileTest.exists?("/etc/arch-release") + "Arch" + elsif FileTest.exists?("/etc/vmware-release") + "VMWareESX" + elsif FileTest.exists?("/etc/redhat-release") + txt = File.read("/etc/redhat-release") + if txt =~ /centos/i + "CentOS" + elsif txt =~ /CERN/ + "SLC" + elsif txt =~ /scientific/i + "Scientific" + elsif txt =~ /^cloudlinux/i + "CloudLinux" + else + "RedHat" + end + elsif FileTest.exists?("/etc/SuSE-release") + txt = File.read("/etc/SuSE-release") + if txt =~ /^SUSE LINUX Enterprise Server/i + "SLES" + elsif txt =~ /^SUSE LINUX Enterprise Desktop/i + "SLED" + elsif txt =~ /^openSUSE/i + "OpenSuSE" + else + "SuSE" + end + elsif FileTest.exists?("/etc/bluewhite64-version") + "Bluewhite64" + elsif FileTest.exists?("/etc/slamd64-version") + "Slamd64" + elsif FileTest.exists?("/etc/slackware-version") + "Slackware" + elsif FileTest.exists?("/etc/alpine-release") + "Alpine" + elsif Facter.value(:lsbdistdescription) =~ /Amazon Linux/ + "Amazon" end + end end Facter.add(:operatingsystem) do - confine :kernel => "VMkernel" - setcode do - "ESXi" - end + confine :kernel => "VMkernel" + setcode do + "ESXi" + end end Facter.add(:operatingsystem) do - # Default to just returning the kernel as the operating system - setcode do Facter[:kernel].value end + # Default to just returning the kernel as the operating system + setcode do Facter[:kernel].value end end diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index 972ffa8398..c4d78628c5 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -17,112 +17,112 @@ # Facter.add(:operatingsystemrelease) do - confine :operatingsystem => %w{CentOS Fedora oel ovs OracleLinux RedHat MeeGo Scientific SLC CloudLinux} - setcode do - case Facter.value(:operatingsystem) - when "CentOS", "RedHat", "Scientific", "SLC", "CloudLinux" - releasefile = "/etc/redhat-release" - when "Fedora" - releasefile = "/etc/fedora-release" - when "MeeGo" - releasefile = "/etc/meego-release" - when "OracleLinux" - releasefile = "/etc/oracle-release" - when "OEL", "oel" - releasefile = "/etc/enterprise-release" - when "OVS", "ovs" - releasefile = "/etc/ovs-release" - end - File::open(releasefile, "r") do |f| - line = f.readline.chomp - if line =~ /\(Rawhide\)$/ - "Rawhide" - elsif line =~ /release (\d[\d.]*)/ - $1 - end - end + confine :operatingsystem => %w{CentOS Fedora oel ovs OracleLinux RedHat MeeGo Scientific SLC CloudLinux} + setcode do + case Facter.value(:operatingsystem) + when "CentOS", "RedHat", "Scientific", "SLC", "CloudLinux" + releasefile = "/etc/redhat-release" + when "Fedora" + releasefile = "/etc/fedora-release" + when "MeeGo" + releasefile = "/etc/meego-release" + when "OracleLinux" + releasefile = "/etc/oracle-release" + when "OEL", "oel" + releasefile = "/etc/enterprise-release" + when "OVS", "ovs" + releasefile = "/etc/ovs-release" + end + File::open(releasefile, "r") do |f| + line = f.readline.chomp + if line =~ /\(Rawhide\)$/ + "Rawhide" + elsif line =~ /release (\d[\d.]*)/ + $1 + end end + end end Facter.add(:operatingsystemrelease) do - confine :operatingsystem => %w{Debian} - setcode do - release = Facter::Util::Resolution.exec('cat /etc/debian_version') - end + confine :operatingsystem => %w{Debian} + setcode do + release = Facter::Util::Resolution.exec('cat /etc/debian_version') + end end Facter.add(:operatingsystemrelease) do - confine :operatingsystem => %w{Ubuntu} - setcode do - release = Facter::Util::Resolution.exec('cat /etc/issue') - if release =~ /Ubuntu (\d+.\d+)/ - $1 - end + confine :operatingsystem => %w{Ubuntu} + setcode do + release = Facter::Util::Resolution.exec('cat /etc/issue') + if release =~ /Ubuntu (\d+.\d+)/ + $1 end + end end Facter.add(:operatingsystemrelease) do - confine :operatingsystem => %w{SLES SLED OpenSuSE} - setcode do - releasefile = Facter::Util::Resolution.exec('cat /etc/SuSE-release') - if releasefile =~ /^VERSION\s*=\s*(\d+)/ - releasemajor = $1 - if releasefile =~ /^PATCHLEVEL\s*=\s*(\d+)/ - releaseminor = $1 - elsif releasefile =~ /^VERSION\s=.*.(\d+)/ - releaseminor = $1 - else - releaseminor = "0" - end - releasemajor + "." + releaseminor - else - "unknown" - end + confine :operatingsystem => %w{SLES SLED OpenSuSE} + setcode do + releasefile = Facter::Util::Resolution.exec('cat /etc/SuSE-release') + if releasefile =~ /^VERSION\s*=\s*(\d+)/ + releasemajor = $1 + if releasefile =~ /^PATCHLEVEL\s*=\s*(\d+)/ + releaseminor = $1 + elsif releasefile =~ /^VERSION\s=.*.(\d+)/ + releaseminor = $1 + else + releaseminor = "0" + end + releasemajor + "." + releaseminor + else + "unknown" end + end end Facter.add(:operatingsystemrelease) do - confine :operatingsystem => %w{Slackware} - setcode do - release = Facter::Util::Resolution.exec('cat /etc/slackware-version') - if release =~ /Slackware ([0-9.]+)/ - $1 - end + confine :operatingsystem => %w{Slackware} + setcode do + release = Facter::Util::Resolution.exec('cat /etc/slackware-version') + if release =~ /Slackware ([0-9.]+)/ + $1 end + end end Facter.add(:operatingsystemrelease) do - confine :operatingsystem => %w{Bluewhite64} - setcode do - releasefile = Facter::Util::Resolution.exec('cat /etc/bluewhite64-version') - if releasefile =~ /^\s*\w+\s+(\d+)\.(\d+)/ - $1 + "." + $2 - else - "unknown" - end + confine :operatingsystem => %w{Bluewhite64} + setcode do + releasefile = Facter::Util::Resolution.exec('cat /etc/bluewhite64-version') + if releasefile =~ /^\s*\w+\s+(\d+)\.(\d+)/ + $1 + "." + $2 + else + "unknown" end + end end Facter.add(:operatingsystemrelease) do - confine :operatingsystem => %w{VMwareESX} - setcode do - release = Facter::Util::Resolution.exec('vmware -v') - if release =~ /VMware ESX .*?(\d.*)/ - $1 - end + confine :operatingsystem => %w{VMwareESX} + setcode do + release = Facter::Util::Resolution.exec('vmware -v') + if release =~ /VMware ESX .*?(\d.*)/ + $1 end + end end Facter.add(:operatingsystemrelease) do - confine :operatingsystem => %w{Slamd64} - setcode do - releasefile = Facter::Util::Resolution.exec('cat /etc/slamd64-version') - if releasefile =~ /^\s*\w+\s+(\d+)\.(\d+)/ - $1 + "." + $2 - else - "unknown" - end + confine :operatingsystem => %w{Slamd64} + setcode do + releasefile = Facter::Util::Resolution.exec('cat /etc/slamd64-version') + if releasefile =~ /^\s*\w+\s+(\d+)\.(\d+)/ + $1 + "." + $2 + else + "unknown" end + end end Facter.add(:operatingsystemrelease) do @@ -133,10 +133,10 @@ end Facter.add(:operatingsystemrelease) do - confine :operatingsystem => %W{Amazon} - setcode do Facter[:lsbdistrelease].value end + confine :operatingsystem => %W{Amazon} + setcode do Facter[:lsbdistrelease].value end end Facter.add(:operatingsystemrelease) do - setcode do Facter[:kernelrelease].value end + setcode do Facter[:kernelrelease].value end end diff --git a/lib/facter/path.rb b/lib/facter/path.rb index 71df6cdf53..5c45a588e2 100644 --- a/lib/facter/path.rb +++ b/lib/facter/path.rb @@ -8,7 +8,7 @@ # Facter.add(:path) do - setcode do - ENV['PATH'] - end + setcode do + ENV['PATH'] + end end diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb index 2ef7dd9f78..1de20f7d7f 100644 --- a/lib/facter/processor.rb +++ b/lib/facter/processor.rb @@ -57,10 +57,10 @@ end Facter.add("Processor") do - confine :kernel => :openbsd - setcode do - Facter::Util::Resolution.exec("uname -p") - end + confine :kernel => :openbsd + setcode do + Facter::Util::Resolution.exec("uname -p") + end end Facter.add("ProcessorCount") do diff --git a/lib/facter/ps.rb b/lib/facter/ps.rb index 1d4f01c4de..5d8cb0ae3a 100644 --- a/lib/facter/ps.rb +++ b/lib/facter/ps.rb @@ -11,12 +11,12 @@ # Facter.add(:ps) do - setcode do 'ps -ef' end + setcode do 'ps -ef' end end Facter.add(:ps) do - confine :operatingsystem => %w{FreeBSD NetBSD OpenBSD Darwin DragonFly} - setcode do 'ps auxwww' end + confine :operatingsystem => %w{FreeBSD NetBSD OpenBSD Darwin DragonFly} + setcode do 'ps auxwww' end end Facter.add(:ps) do diff --git a/lib/facter/puppetversion.rb b/lib/facter/puppetversion.rb index 01422f03d1..837a6e889b 100644 --- a/lib/facter/puppetversion.rb +++ b/lib/facter/puppetversion.rb @@ -9,12 +9,12 @@ # Facter.add(:puppetversion) do - setcode do - begin - require 'puppet' - Puppet::PUPPETVERSION.to_s - rescue LoadError - nil - end + setcode do + begin + require 'puppet' + Puppet::PUPPETVERSION.to_s + rescue LoadError + nil end + end end diff --git a/lib/facter/rubysitedir.rb b/lib/facter/rubysitedir.rb index 9973329237..73aa2152fc 100644 --- a/lib/facter/rubysitedir.rb +++ b/lib/facter/rubysitedir.rb @@ -9,10 +9,10 @@ # Facter.add :rubysitedir do - setcode do - version = RUBY_VERSION.to_s.sub(/\.\d+$/, '') - $:.find do |dir| - dir =~ /#{File.join("site_ruby", version)}$/ - end + setcode do + version = RUBY_VERSION.to_s.sub(/\.\d+$/, '') + $:.find do |dir| + dir =~ /#{File.join("site_ruby", version)}$/ end + end end diff --git a/lib/facter/rubyversion.rb b/lib/facter/rubyversion.rb index e578400ed3..32bf25450c 100644 --- a/lib/facter/rubyversion.rb +++ b/lib/facter/rubyversion.rb @@ -8,5 +8,5 @@ # Facter.add(:rubyversion) do - setcode { RUBY_VERSION.to_s } + setcode { RUBY_VERSION.to_s } end diff --git a/lib/facter/ssh.rb b/lib/facter/ssh.rb index 7bdf8036f4..6aa7e080a8 100644 --- a/lib/facter/ssh.rb +++ b/lib/facter/ssh.rb @@ -12,20 +12,20 @@ ## ["/etc/ssh","/usr/local/etc/ssh","/etc","/usr/local/etc"].each do |dir| - {"SSHDSAKey" => "ssh_host_dsa_key.pub", "SSHRSAKey" => "ssh_host_rsa_key.pub", "SSHECDSAKey" => "ssh_host_ecdsa_key.pub"}.each do |name,file| - Facter.add(name) do - setcode do - value = nil - filepath = File.join(dir,file) - if FileTest.file?(filepath) - begin - File.open(filepath) { |f| value = f.read.chomp.split(/\s+/)[1] } - rescue - value = nil - end - end - value - end # end of proc - end # end of add - end # end of hash each + {"SSHDSAKey" => "ssh_host_dsa_key.pub", "SSHRSAKey" => "ssh_host_rsa_key.pub", "SSHECDSAKey" => "ssh_host_ecdsa_key.pub"}.each do |name,file| + Facter.add(name) do + setcode do + value = nil + filepath = File.join(dir,file) + if FileTest.file?(filepath) + begin + File.open(filepath) { |f| value = f.read.chomp.split(/\s+/)[1] } + rescue + value = nil + end + end + value + end # end of proc + end # end of add + end # end of hash each end # end of dir each diff --git a/lib/facter/timezone.rb b/lib/facter/timezone.rb index 462c9ba67c..246d2950df 100644 --- a/lib/facter/timezone.rb +++ b/lib/facter/timezone.rb @@ -8,7 +8,7 @@ # Facter.add("timezone") do - setcode do - Time.new.zone - end + setcode do + Time.new.zone + end end diff --git a/lib/facter/uniqueid.rb b/lib/facter/uniqueid.rb index ac130034a3..6c1c563bc7 100644 --- a/lib/facter/uniqueid.rb +++ b/lib/facter/uniqueid.rb @@ -1,4 +1,4 @@ Facter.add(:uniqueid) do - setcode 'hostid' - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS Scientific SLC SuSE SLES Debian Ubuntu Gentoo AIX OEL OracleLinux OVS GNU/kFreeBSD} + setcode 'hostid' + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS Scientific SLC SuSE SLES Debian Ubuntu Gentoo AIX OEL OracleLinux OVS GNU/kFreeBSD} end diff --git a/lib/facter/util/collection.rb b/lib/facter/util/collection.rb index b3d3a45c57..d165ff0252 100644 --- a/lib/facter/util/collection.rb +++ b/lib/facter/util/collection.rb @@ -5,126 +5,126 @@ # Manage which facts exist and how we access them. Largely just a wrapper # around a hash of facts. class Facter::Util::Collection - # Return a fact object by name. If you use this, you still have to call - # 'value' on it to retrieve the actual value. - def [](name) - value(name) - end - - # Add a resolution mechanism for a named fact. This does not distinguish - # between adding a new fact and adding a new way to resolve a fact. - def add(name, options = {}, &block) - name = canonize(name) + # Return a fact object by name. If you use this, you still have to call + # 'value' on it to retrieve the actual value. + def [](name) + value(name) + end - unless fact = @facts[name] - fact = Facter::Util::Fact.new(name) + # Add a resolution mechanism for a named fact. This does not distinguish + # between adding a new fact and adding a new way to resolve a fact. + def add(name, options = {}, &block) + name = canonize(name) - @facts[name] = fact - end + unless fact = @facts[name] + fact = Facter::Util::Fact.new(name) - # Set any fact-appropriate options. - options.each do |opt, value| - method = opt.to_s + "=" - if fact.respond_to?(method) - fact.send(method, value) - options.delete(opt) - end - end + @facts[name] = fact + end - if block - resolve = fact.add(&block) - # Set any resolve-appropriate options - options.each do |opt, value| - method = opt.to_s + "=" - if resolve.respond_to?(method) - resolve.send(method, value) - options.delete(opt) - end - end - end + # Set any fact-appropriate options. + options.each do |opt, value| + method = opt.to_s + "=" + if fact.respond_to?(method) + fact.send(method, value) + options.delete(opt) + end + end - unless options.empty? - raise ArgumentError, "Invalid facter option(s) %s" % options.keys.collect { |k| k.to_s }.join(",") + if block + resolve = fact.add(&block) + # Set any resolve-appropriate options + options.each do |opt, value| + method = opt.to_s + "=" + if resolve.respond_to?(method) + resolve.send(method, value) + options.delete(opt) end + end + end - return fact + unless options.empty? + raise ArgumentError, "Invalid facter option(s) %s" % options.keys.collect { |k| k.to_s }.join(",") end - include Enumerable + return fact + end - # Iterate across all of the facts. - def each - @facts.each do |name, fact| - value = fact.value - unless value.nil? - yield name.to_s, value - end - end + include Enumerable + + # Iterate across all of the facts. + def each + @facts.each do |name, fact| + value = fact.value + unless value.nil? + yield name.to_s, value + end end + end - # Return a fact by name. - def fact(name) - name = canonize(name) + # Return a fact by name. + def fact(name) + name = canonize(name) - # Try to load the fact if necessary - loader.load(name) unless @facts[name] + # Try to load the fact if necessary + loader.load(name) unless @facts[name] - # Try HARDER - loader.load_all unless @facts[name] + # Try HARDER + loader.load_all unless @facts[name] - @facts[name] - end + @facts[name] + end - # Flush all cached values. - def flush - @facts.each { |name, fact| fact.flush } - end + # Flush all cached values. + def flush + @facts.each { |name, fact| fact.flush } + end - def initialize - @facts = Hash.new - end + def initialize + @facts = Hash.new + end - # Return a list of all of the facts. - def list - return @facts.keys - end + # Return a list of all of the facts. + def list + return @facts.keys + end - # Load all known facts. - def load_all - loader.load_all - end + # Load all known facts. + def load_all + loader.load_all + end - # The thing that loads facts if we don't have them. - def loader - unless defined?(@loader) - @loader = Facter::Util::Loader.new - end - @loader + # The thing that loads facts if we don't have them. + def loader + unless defined?(@loader) + @loader = Facter::Util::Loader.new end - - # Return a hash of all of our facts. - def to_hash - @facts.inject({}) do |h, ary| - value = ary[1].value - if ! value.nil? - # For backwards compatibility, convert the fact name to a string. - h[ary[0].to_s] = value - end - h - end + @loader + end + + # Return a hash of all of our facts. + def to_hash + @facts.inject({}) do |h, ary| + value = ary[1].value + if ! value.nil? + # For backwards compatibility, convert the fact name to a string. + h[ary[0].to_s] = value + end + h end + end - def value(name) - if fact = fact(name) - fact.value - end + def value(name) + if fact = fact(name) + fact.value end + end - private + private - # Provide a consistent means of getting the exact same fact name - # every time. - def canonize(name) - name.to_s.downcase.to_sym - end + # Provide a consistent means of getting the exact same fact name + # every time. + def canonize(name) + name.to_s.downcase.to_sym + end end diff --git a/lib/facter/util/confine.rb b/lib/facter/util/confine.rb index 4cbb32cc20..1f9e11ec27 100644 --- a/lib/facter/util/confine.rb +++ b/lib/facter/util/confine.rb @@ -4,38 +4,38 @@ require 'facter/util/values' class Facter::Util::Confine - attr_accessor :fact, :values - - include Facter::Util::Values - - # Add the restriction. Requires the fact name, an operator, and the value - # we're comparing to. - def initialize(fact, *values) - raise ArgumentError, "The fact name must be provided" unless fact - raise ArgumentError, "One or more values must be provided" if values.empty? - @fact = fact - @values = values + attr_accessor :fact, :values + + include Facter::Util::Values + + # Add the restriction. Requires the fact name, an operator, and the value + # we're comparing to. + def initialize(fact, *values) + raise ArgumentError, "The fact name must be provided" unless fact + raise ArgumentError, "One or more values must be provided" if values.empty? + @fact = fact + @values = values + end + + def to_s + return "'%s' '%s'" % [@fact, @values.join(",")] + end + + # Evaluate the fact, returning true or false. + def true? + unless fact = Facter[@fact] + Facter.debug "No fact for %s" % @fact + return false end + value = convert(fact.value) - def to_s - return "'%s' '%s'" % [@fact, @values.join(",")] - end + return false if value.nil? - # Evaluate the fact, returning true or false. - def true? - unless fact = Facter[@fact] - Facter.debug "No fact for %s" % @fact - return false - end - value = convert(fact.value) - - return false if value.nil? - - @values.each do |v| - v = convert(v) - next unless v.class == value.class - return true if value == v - end - return false + @values.each do |v| + v = convert(v) + next unless v.class == value.class + return true if value == v end + return false + end end diff --git a/lib/facter/util/fact.rb b/lib/facter/util/fact.rb index 935b3c17f2..070087b3be 100644 --- a/lib/facter/util/fact.rb +++ b/lib/facter/util/fact.rb @@ -2,119 +2,119 @@ require 'facter/util/resolution' class Facter::Util::Fact - TIMEOUT = 5 + TIMEOUT = 5 + + attr_accessor :name, :ldapname + + # Create a new fact, with no resolution mechanisms. + def initialize(name, options = {}) + @name = name.to_s.downcase.intern + + # LAK:NOTE: This is slow for many options, but generally we won't have any and at + # worst we'll have one. If we add more, this should be made more efficient. + options.each do |name, value| + case name + when :ldapname; self.ldapname = value + else + raise ArgumentError, "Invalid fact option '%s'" % name + end + end - attr_accessor :name, :ldapname + @ldapname ||= @name.to_s - # Create a new fact, with no resolution mechanisms. - def initialize(name, options = {}) - @name = name.to_s.downcase.intern + @resolves = [] + @searching = false - # LAK:NOTE: This is slow for many options, but generally we won't have any and at - # worst we'll have one. If we add more, this should be made more efficient. - options.each do |name, value| - case name - when :ldapname; self.ldapname = value - else - raise ArgumentError, "Invalid fact option '%s'" % name - end - end + @value = nil + end - @ldapname ||= @name.to_s + # Add a new resolution mechanism. This requires a block, which will then + # be evaluated in the context of the new mechanism. + def add(&block) + raise ArgumentError, "You must pass a block to Fact.add" unless block_given? - @resolves = [] - @searching = false + resolve = Facter::Util::Resolution.new(@name) - @value = nil - end + resolve.instance_eval(&block) - # Add a new resolution mechanism. This requires a block, which will then - # be evaluated in the context of the new mechanism. - def add(&block) - raise ArgumentError, "You must pass a block to Fact.add" unless block_given? + @resolves << resolve - resolve = Facter::Util::Resolution.new(@name) + # Immediately sort the resolutions, so that we always have + # a sorted list for looking up values. + @resolves.sort! { |a, b| b.weight <=> a.weight } - resolve.instance_eval(&block) + return resolve + end - @resolves << resolve + # Flush any cached values. + def flush + @value = nil + @suitable = nil + end - # Immediately sort the resolutions, so that we always have - # a sorted list for looking up values. - @resolves.sort! { |a, b| b.weight <=> a.weight } + # Return the value for a given fact. Searches through all of the mechanisms + # and returns either the first value or nil. + def value + return @value if @value - return resolve + if @resolves.length == 0 + Facter.debug "No resolves for %s" % @name + return nil end - # Flush any cached values. - def flush - @value = nil - @suitable = nil - end + searching do + @value = nil - # Return the value for a given fact. Searches through all of the mechanisms - # and returns either the first value or nil. - def value - return @value if @value - - if @resolves.length == 0 - Facter.debug "No resolves for %s" % @name - return nil - end - - searching do - @value = nil - - foundsuits = false - @value = @resolves.inject(nil) { |result, resolve| - next unless resolve.suitable? - foundsuits = true - - tmp = resolve.value - - break tmp unless tmp.nil? or tmp == "" - } - - unless foundsuits - Facter.debug "Found no suitable resolves of %s for %s" % [@resolves.length, @name] - end - end - - if @value.nil? - # nothing - Facter.debug("value for %s is still nil" % @name) - return nil - else - return @value - end - end + foundsuits = false + @value = @resolves.inject(nil) { |result, resolve| + next unless resolve.suitable? + foundsuits = true - private + tmp = resolve.value - # Are we in the midst of a search? - def searching? - @searching + break tmp unless tmp.nil? or tmp == "" + } + + unless foundsuits + Facter.debug "Found no suitable resolves of %s for %s" % [@resolves.length, @name] + end + end + + if @value.nil? + # nothing + Facter.debug("value for %s is still nil" % @name) + return nil + else + return @value + end + end + + private + + # Are we in the midst of a search? + def searching? + @searching + end + + # Lock our searching process, so we never ge stuck in recursion. + def searching + if searching? + Facter.debug "Caught recursion on %s" % @name + + # return a cached value if we've got it + if @value + return @value + else + return nil + end end - # Lock our searching process, so we never ge stuck in recursion. - def searching - if searching? - Facter.debug "Caught recursion on %s" % @name - - # return a cached value if we've got it - if @value - return @value - else - return nil - end - end - - # If we've gotten this far, we're not already searching, so go ahead and do so. - @searching = true - begin - yield - ensure - @searching = false - end + # If we've gotten this far, we're not already searching, so go ahead and do so. + @searching = true + begin + yield + ensure + @searching = false end + end end diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index 0e4af16e15..b7395e08d3 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -1,202 +1,202 @@ # A base module for collecting IP-related # information from all kinds of platforms. module Facter::Util::IP - # A map of all the different regexes that work for - # a given platform or set of platforms. - REGEX_MAP = { - :linux => { - :ipaddress => /inet addr:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, - :ipaddress6 => /inet6 addr: ((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/, - :macaddress => /(?:ether|HWaddr)\s+(\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/, - :netmask => /Mask:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ - }, - :bsd => { - :aliases => [:openbsd, :netbsd, :freebsd, :darwin, :"gnu/kfreebsd", :dragonfly], - :ipaddress => /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, - :ipaddress6 => /inet6 ((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/, - :macaddress => /(?:ether|lladdr)\s+(\w?\w:\w?\w:\w?\w:\w?\w:\w?\w:\w?\w)/, - :netmask => /netmask\s+0x(\w{8})/ - }, - :sunos => { - :ipaddress => /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, - :ipaddress6 => /inet6 ((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/, - :macaddress => /(?:ether|lladdr)\s+(\w?\w:\w?\w:\w?\w:\w?\w:\w?\w:\w?\w)/, - :netmask => /netmask\s+(\w{8})/ - }, - :"hp-ux" => { - :ipaddress => /\s+inet (\S+)\s.*/, - :macaddress => /(\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/, - :netmask => /.*\s+netmask (\S+)\s.*/ - }, - :windows => { - :ipaddress => /\s+IP Address:\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, - :ipaddress6 => /Address ((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/, - :netmask => /\s+Subnet Prefix:\s+\S+\s+\(mask ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\)/ - } + # A map of all the different regexes that work for + # a given platform or set of platforms. + REGEX_MAP = { + :linux => { + :ipaddress => /inet addr:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, + :ipaddress6 => /inet6 addr: ((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/, + :macaddress => /(?:ether|HWaddr)\s+(\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/, + :netmask => /Mask:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + }, + :bsd => { + :aliases => [:openbsd, :netbsd, :freebsd, :darwin, :"gnu/kfreebsd", :dragonfly], + :ipaddress => /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, + :ipaddress6 => /inet6 ((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/, + :macaddress => /(?:ether|lladdr)\s+(\w?\w:\w?\w:\w?\w:\w?\w:\w?\w:\w?\w)/, + :netmask => /netmask\s+0x(\w{8})/ + }, + :sunos => { + :ipaddress => /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, + :ipaddress6 => /inet6 ((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/, + :macaddress => /(?:ether|lladdr)\s+(\w?\w:\w?\w:\w?\w:\w?\w:\w?\w:\w?\w)/, + :netmask => /netmask\s+(\w{8})/ + }, + :"hp-ux" => { + :ipaddress => /\s+inet (\S+)\s.*/, + :macaddress => /(\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/, + :netmask => /.*\s+netmask (\S+)\s.*/ + }, + :windows => { + :ipaddress => /\s+IP Address:\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, + :ipaddress6 => /Address ((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/, + :netmask => /\s+Subnet Prefix:\s+\S+\s+\(mask ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\)/ } - - # Convert an interface name into purely alphanumeric characters. - def self.alphafy(interface) - interface.gsub(/[^a-z0-9_]/i, '_') + } + + # Convert an interface name into purely alphanumeric characters. + def self.alphafy(interface) + interface.gsub(/[^a-z0-9_]/i, '_') + end + + def self.convert_from_hex?(kernel) + kernels_to_convert = [:sunos, :openbsd, :netbsd, :freebsd, :darwin, :"hp-ux", :"gnu/kfreebsd", :dragonfly] + kernels_to_convert.include?(kernel) + end + + def self.supported_platforms + REGEX_MAP.inject([]) do |result, tmp| + key, map = tmp + if map[:aliases] + result += map[:aliases] + else + result << key + end + result end - - def self.convert_from_hex?(kernel) - kernels_to_convert = [:sunos, :openbsd, :netbsd, :freebsd, :darwin, :"hp-ux", :"gnu/kfreebsd", :dragonfly] - kernels_to_convert.include?(kernel) + end + + def self.get_interfaces + return [] unless output = Facter::Util::IP.get_all_interface_output() + + # windows interface names contain spaces and are quoted and can appear multiple + # times as ipv4 and ipv6 + return output.scan(/\s* connected\s*(\S.*)/).flatten.uniq if Facter.value(:kernel) == 'windows' + + # Our regex appears to be stupid, in that it leaves colons sitting + # at the end of interfaces. So, we have to trim those trailing + # characters. I tried making the regex better but supporting all + # platforms with a single regex is probably a bit too much. + output.scan(/^\S+/).collect { |i| i.sub(/:$/, '') }.uniq + end + + def self.get_all_interface_output + case Facter.value(:kernel) + when 'Linux', 'OpenBSD', 'NetBSD', 'FreeBSD', 'Darwin', 'GNU/kFreeBSD', 'DragonFly' + output = %x{/sbin/ifconfig -a} + when 'SunOS' + output = %x{/usr/sbin/ifconfig -a} + when 'HP-UX' + output = %x{/bin/netstat -in | sed -e 1d} + when 'windows' + output = %x|#{ENV['SYSTEMROOT']}/system32/netsh interface ip show interface| + output += %x|#{ENV['SYSTEMROOT']}/system32/netsh interface ipv6 show interface| end - - def self.supported_platforms - REGEX_MAP.inject([]) do |result, tmp| - key, map = tmp - if map[:aliases] - result += map[:aliases] - else - result << key - end - result - end + output + end + + def self.get_single_interface_output(interface) + output = "" + case Facter.value(:kernel) + when 'Linux', 'OpenBSD', 'NetBSD', 'FreeBSD', 'Darwin', 'GNU/kFreeBSD', 'DragonFly' + output = %x{/sbin/ifconfig #{interface}} + when 'SunOS' + output = %x{/usr/sbin/ifconfig #{interface}} + when 'HP-UX' + mac = "" + ifc = %x{/usr/sbin/ifconfig #{interface}} + %x{/usr/sbin/lanscan}.scan(/(\dx\S+).*UP\s+(\w+\d+)/).each {|i| mac = i[0] if i.include?(interface) } + mac = mac.sub(/0x(\S+)/,'\1').scan(/../).join(":") + output = ifc + "\n" + mac end + output + end - def self.get_interfaces - return [] unless output = Facter::Util::IP.get_all_interface_output() - - # windows interface names contain spaces and are quoted and can appear multiple - # times as ipv4 and ipv6 - return output.scan(/\s* connected\s*(\S.*)/).flatten.uniq if Facter.value(:kernel) == 'windows' + def self.get_output_for_interface_and_label(interface, label) + return get_single_interface_output(interface) unless Facter.value(:kernel) == 'windows' - # Our regex appears to be stupid, in that it leaves colons sitting - # at the end of interfaces. So, we have to trim those trailing - # characters. I tried making the regex better but supporting all - # platforms with a single regex is probably a bit too much. - output.scan(/^\S+/).collect { |i| i.sub(/:$/, '') }.uniq + if label == 'ipaddress6' + output = %x|#{ENV['SYSTEMROOT']}/system32/netsh interface ipv6 show address \"#{interface}\"| + else + output = %x|#{ENV['SYSTEMROOT']}/system32/netsh interface ip show address \"#{interface}\"| end + output + end - def self.get_all_interface_output - case Facter.value(:kernel) - when 'Linux', 'OpenBSD', 'NetBSD', 'FreeBSD', 'Darwin', 'GNU/kFreeBSD', 'DragonFly' - output = %x{/sbin/ifconfig -a} - when 'SunOS' - output = %x{/usr/sbin/ifconfig -a} - when 'HP-UX' - output = %x{/bin/netstat -in | sed -e 1d} - when 'windows' - output = %x|#{ENV['SYSTEMROOT']}/system32/netsh interface ip show interface| - output += %x|#{ENV['SYSTEMROOT']}/system32/netsh interface ipv6 show interface| - end - output + def self.get_bonding_master(interface) + if Facter.value(:kernel) != 'Linux' + return nil end - - def self.get_single_interface_output(interface) - output = "" - case Facter.value(:kernel) - when 'Linux', 'OpenBSD', 'NetBSD', 'FreeBSD', 'Darwin', 'GNU/kFreeBSD', 'DragonFly' - output = %x{/sbin/ifconfig #{interface}} - when 'SunOS' - output = %x{/usr/sbin/ifconfig #{interface}} - when 'HP-UX' - mac = "" - ifc = %x{/usr/sbin/ifconfig #{interface}} - %x{/usr/sbin/lanscan}.scan(/(\dx\S+).*UP\s+(\w+\d+)/).each {|i| mac = i[0] if i.include?(interface) } - mac = mac.sub(/0x(\S+)/,'\1').scan(/../).join(":") - output = ifc + "\n" + mac - end - output + # We need ip instead of ifconfig because it will show us + # the bonding master device. + if not FileTest.executable?("/sbin/ip") + return nil end - - def self.get_output_for_interface_and_label(interface, label) - return get_single_interface_output(interface) unless Facter.value(:kernel) == 'windows' - - if label == 'ipaddress6' - output = %x|#{ENV['SYSTEMROOT']}/system32/netsh interface ipv6 show address \"#{interface}\"| - else - output = %x|#{ENV['SYSTEMROOT']}/system32/netsh interface ip show address \"#{interface}\"| - end - output + # A bonding interface can never be an alias interface. Alias + # interfaces do have a colon in their name and the ip link show + # command throws an error message when we pass it an alias + # interface. + if interface =~ /:/ + return nil end - - def self.get_bonding_master(interface) - if Facter.value(:kernel) != 'Linux' - return nil - end - # We need ip instead of ifconfig because it will show us - # the bonding master device. - if not FileTest.executable?("/sbin/ip") - return nil - end - # A bonding interface can never be an alias interface. Alias - # interfaces do have a colon in their name and the ip link show - # command throws an error message when we pass it an alias - # interface. - if interface =~ /:/ - return nil - end - regex = /SLAVE[,>].* (bond[0-9]+)/ - ethbond = regex.match(%x{/sbin/ip link show #{interface}}) - if ethbond - device = ethbond[1] - else - device = nil - end - device + regex = /SLAVE[,>].* (bond[0-9]+)/ + ethbond = regex.match(%x{/sbin/ip link show #{interface}}) + if ethbond + device = ethbond[1] + else + device = nil end + device + end - def self.get_interface_value(interface, label) - tmp1 = [] - - kernel = Facter.value(:kernel).downcase.to_sym + def self.get_interface_value(interface, label) + tmp1 = [] - # If it's not directly in the map or aliased in the map, then we don't know how to deal with it. - unless map = REGEX_MAP[kernel] || REGEX_MAP.values.find { |tmp| tmp[:aliases] and tmp[:aliases].include?(kernel) } - return [] - end + kernel = Facter.value(:kernel).downcase.to_sym - # Pull the correct regex out of the map. - regex = map[label.to_sym] - - # Linux changes the MAC address reported via ifconfig when an ethernet interface - # becomes a slave of a bonding device to the master MAC address. - # We have to dig a bit to get the original/real MAC address of the interface. - bonddev = get_bonding_master(interface) - if label == 'macaddress' and bonddev - bondinfo = IO.readlines("/proc/net/bonding/#{bonddev}") - hwaddrre = /^Slave Interface: #{interface}\n[^\n].+?\nPermanent HW addr: (([0-9a-fA-F]{2}:?)*)$/m - value = hwaddrre.match(bondinfo.to_s)[1].upcase - else - output_int = get_output_for_interface_and_label(interface, label) - - output_int.each_line do |s| - if s =~ regex - value = $1 - if label == 'netmask' && convert_from_hex?(kernel) - value = value.scan(/../).collect do |byte| byte.to_i(16) end.join('.') - end - tmp1.push(value) - end - end + # If it's not directly in the map or aliased in the map, then we don't know how to deal with it. + unless map = REGEX_MAP[kernel] || REGEX_MAP.values.find { |tmp| tmp[:aliases] and tmp[:aliases].include?(kernel) } + return [] + end - if tmp1 - value = tmp1.shift + # Pull the correct regex out of the map. + regex = map[label.to_sym] + + # Linux changes the MAC address reported via ifconfig when an ethernet interface + # becomes a slave of a bonding device to the master MAC address. + # We have to dig a bit to get the original/real MAC address of the interface. + bonddev = get_bonding_master(interface) + if label == 'macaddress' and bonddev + bondinfo = IO.readlines("/proc/net/bonding/#{bonddev}") + hwaddrre = /^Slave Interface: #{interface}\n[^\n].+?\nPermanent HW addr: (([0-9a-fA-F]{2}:?)*)$/m + value = hwaddrre.match(bondinfo.to_s)[1].upcase + else + output_int = get_output_for_interface_and_label(interface, label) + + output_int.each_line do |s| + if s =~ regex + value = $1 + if label == 'netmask' && convert_from_hex?(kernel) + value = value.scan(/../).collect do |byte| byte.to_i(16) end.join('.') end + tmp1.push(value) end + end + + if tmp1 + value = tmp1.shift + end end + end - def self.get_network_value(interface) - require 'ipaddr' + def self.get_network_value(interface) + require 'ipaddr' - ipaddress = get_interface_value(interface, "ipaddress") - netmask = get_interface_value(interface, "netmask") + ipaddress = get_interface_value(interface, "ipaddress") + netmask = get_interface_value(interface, "netmask") - if ipaddress && netmask - ip = IPAddr.new(ipaddress, Socket::AF_INET) - subnet = IPAddr.new(netmask, Socket::AF_INET) - network = ip.mask(subnet.to_s).to_s - end + if ipaddress && netmask + ip = IPAddr.new(ipaddress, Socket::AF_INET) + subnet = IPAddr.new(netmask, Socket::AF_INET) + network = ip.mask(subnet.to_s).to_s end + end - def self.get_arp_value(interface) - arp = Facter::Util::Resolution.exec("arp -en -i #{interface} | sed -e 1d") - if arp =~ /^\S+\s+\w+\s+(\S+)\s+\w\s+\S+$/ - return $1 - end + def self.get_arp_value(interface) + arp = Facter::Util::Resolution.exec("arp -en -i #{interface} | sed -e 1d") + if arp =~ /^\S+\s+\w+\s+(\S+)\s+\w\s+\S+$/ + return $1 end + end end diff --git a/lib/facter/util/loader.rb b/lib/facter/util/loader.rb index 29a1de08c3..4bdc82fca6 100644 --- a/lib/facter/util/loader.rb +++ b/lib/facter/util/loader.rb @@ -3,110 +3,110 @@ # Load facts on demand. class Facter::Util::Loader - def initialize - @loaded = [] + def initialize + @loaded = [] + end + + # Load all resolutions for a single fact. + def load(fact) + # Now load from the search path + shortname = fact.to_s.downcase + load_env(shortname) + + filename = shortname + ".rb" + search_path.each do |dir| + # Load individual files + file = File.join(dir, filename) + + load_file(file) if FileTest.exist?(file) + + # And load any directories matching the name + factdir = File.join(dir, shortname) + load_dir(factdir) if FileTest.directory?(factdir) end + end - # Load all resolutions for a single fact. - def load(fact) - # Now load from the search path - shortname = fact.to_s.downcase - load_env(shortname) + # Load all facts from all directories. + def load_all + return if defined?(@loaded_all) - filename = shortname + ".rb" - search_path.each do |dir| - # Load individual files - file = File.join(dir, filename) + load_env - load_file(file) if FileTest.exist?(file) + search_path.each do |dir| + next unless FileTest.directory?(dir) - # And load any directories matching the name - factdir = File.join(dir, shortname) - load_dir(factdir) if FileTest.directory?(factdir) + Dir.entries(dir).sort.each do |file| + path = File.join(dir, file) + if File.directory?(path) + load_dir(path) + elsif file =~ /\.rb$/ + load_file(File.join(dir, file)) end + end end - # Load all facts from all directories. - def load_all - return if defined?(@loaded_all) + @loaded_all = true + end - load_env - - search_path.each do |dir| - next unless FileTest.directory?(dir) - - Dir.entries(dir).sort.each do |file| - path = File.join(dir, file) - if File.directory?(path) - load_dir(path) - elsif file =~ /\.rb$/ - load_file(File.join(dir, file)) - end - end - end - - @loaded_all = true + # The list of directories we're going to search through for facts. + def search_path + result = [] + result += $LOAD_PATH.collect { |d| File.join(d, "facter") } + if ENV.include?("FACTERLIB") + result += ENV["FACTERLIB"].split(":") end - # The list of directories we're going to search through for facts. - def search_path - result = [] - result += $LOAD_PATH.collect { |d| File.join(d, "facter") } - if ENV.include?("FACTERLIB") - result += ENV["FACTERLIB"].split(":") - end - - # This allows others to register additional paths we should search. - result += Facter.search_path + # This allows others to register additional paths we should search. + result += Facter.search_path - result - end + result + end - private + private - def load_dir(dir) - return if dir =~ /\/\.+$/ or dir =~ /\/util$/ or dir =~ /\/lib$/ + def load_dir(dir) + return if dir =~ /\/\.+$/ or dir =~ /\/util$/ or dir =~ /\/lib$/ - Dir.entries(dir).find_all { |f| f =~ /\.rb$/ }.sort.each do |file| - load_file(File.join(dir, file)) - end + Dir.entries(dir).find_all { |f| f =~ /\.rb$/ }.sort.each do |file| + load_file(File.join(dir, file)) end - - def load_file(file) - return if @loaded.include? file - # We have to specify Kernel.load, because we have a load method. - begin - # Store the file path so we don't try to reload it - @loaded << file - Kernel.load(file) - rescue ScriptError => detail - # Don't store the path if the file can't be loaded - # in case it's loadable later on. - @loaded.delete(file) - warn "Error loading fact #{file} #{detail}" - end + end + + def load_file(file) + return if @loaded.include? file + # We have to specify Kernel.load, because we have a load method. + begin + # Store the file path so we don't try to reload it + @loaded << file + Kernel.load(file) + rescue ScriptError => detail + # Don't store the path if the file can't be loaded + # in case it's loadable later on. + @loaded.delete(file) + warn "Error loading fact #{file} #{detail}" end - - # Load facts from the environment. If no name is provided, - # all will be loaded. - def load_env(fact = nil) - # Load from the environment, if possible - ENV.each do |name, value| - # Skip anything that doesn't match our regex. - next unless name =~ /^facter_?(\w+)$/i - env_name = $1 - - # If a fact name was specified, skip anything that doesn't - # match it. - next if fact and env_name != fact - - Facter.add($1) do - has_weight 1_000_000 - setcode { value } - end - - # Short-cut, if we are only looking for one value. - break if fact - end + end + + # Load facts from the environment. If no name is provided, + # all will be loaded. + def load_env(fact = nil) + # Load from the environment, if possible + ENV.each do |name, value| + # Skip anything that doesn't match our regex. + next unless name =~ /^facter_?(\w+)$/i + env_name = $1 + + # If a fact name was specified, skip anything that doesn't + # match it. + next if fact and env_name != fact + + Facter.add($1) do + has_weight 1_000_000 + setcode { value } + end + + # Short-cut, if we are only looking for one value. + break if fact end + end end diff --git a/lib/facter/util/macosx.rb b/lib/facter/util/macosx.rb index cc9b2df269..dffa102a48 100644 --- a/lib/facter/util/macosx.rb +++ b/lib/facter/util/macosx.rb @@ -6,56 +6,56 @@ ## module Facter::Util::Macosx - require 'thread' - require 'facter/util/plist' - require 'facter/util/resolution' - - # JJM I'd really like to dynamically generate these methods - # by looking at the _name key of the _items dict for each _dataType - - def self.profiler_xml(data_field) - Facter::Util::Resolution.exec("/usr/sbin/system_profiler -xml #{data_field}") + require 'thread' + require 'facter/util/plist' + require 'facter/util/resolution' + + # JJM I'd really like to dynamically generate these methods + # by looking at the _name key of the _items dict for each _dataType + + def self.profiler_xml(data_field) + Facter::Util::Resolution.exec("/usr/sbin/system_profiler -xml #{data_field}") + end + + def self.intern_xml(xml) + return nil unless xml + Plist::parse_xml(xml) + end + + # Return an xml result, modified as we need it. + def self.profiler_data(data_field) + begin + return nil unless parsed_xml = intern_xml(profiler_xml(data_field)) + return nil unless data = parsed_xml[0]['_items'][0] + data.delete '_name' + data + rescue + return nil end + end - def self.intern_xml(xml) - return nil unless xml - Plist::parse_xml(xml) - end + def self.hardware_overview + profiler_data("SPHardwareDataType") + end - # Return an xml result, modified as we need it. - def self.profiler_data(data_field) - begin - return nil unless parsed_xml = intern_xml(profiler_xml(data_field)) - return nil unless data = parsed_xml[0]['_items'][0] - data.delete '_name' - data - rescue - return nil - end - end + def self.os_overview + profiler_data("SPSoftwareDataType") + end - def self.hardware_overview - profiler_data("SPHardwareDataType") + def self.sw_vers + ver = Hash.new + [ "productName", "productVersion", "buildVersion" ].each do |option| + ver["macosx_#{option}"] = Facter::Util::Resolution.exec("/usr/bin/sw_vers -#{option}").strip end - - def self.os_overview - profiler_data("SPSoftwareDataType") - end - - def self.sw_vers - ver = Hash.new - [ "productName", "productVersion", "buildVersion" ].each do |option| - ver["macosx_#{option}"] = Facter::Util::Resolution.exec("/usr/bin/sw_vers -#{option}").strip - end - productversion = ver["macosx_productVersion"] - if not productversion.nil? - versions = productversion.scan(/(\d+)\.(\d+)\.*(\d*)/)[0] - ver["macosx_productversion_major"] = "#{versions[0]}.#{versions[1]}" - if versions[2].empty? # 10.x should be treated as 10.x.0 - versions[2] = "0" - end - ver["macosx_productversion_minor"] = versions[2] - end - ver + productversion = ver["macosx_productVersion"] + if not productversion.nil? + versions = productversion.scan(/(\d+)\.(\d+)\.*(\d*)/)[0] + ver["macosx_productversion_major"] = "#{versions[0]}.#{versions[1]}" + if versions[2].empty? # 10.x should be treated as 10.x.0 + versions[2] = "0" + end + ver["macosx_productversion_minor"] = versions[2] end + ver + end end diff --git a/lib/facter/util/manufacturer.rb b/lib/facter/util/manufacturer.rb index 9beb2b42bd..d6fde05683 100644 --- a/lib/facter/util/manufacturer.rb +++ b/lib/facter/util/manufacturer.rb @@ -3,103 +3,103 @@ module Facter::Manufacturer - def self.get_dmi_table() - case Facter.value(:kernel) - when 'Linux', 'GNU/kFreeBSD' - return nil unless FileTest.exists?("/usr/sbin/dmidecode") + def self.get_dmi_table() + case Facter.value(:kernel) + when 'Linux', 'GNU/kFreeBSD' + return nil unless FileTest.exists?("/usr/sbin/dmidecode") - output=%x{/usr/sbin/dmidecode 2>/dev/null} - when 'FreeBSD' - return nil unless FileTest.exists?("/usr/local/sbin/dmidecode") + output=%x{/usr/sbin/dmidecode 2>/dev/null} + when 'FreeBSD' + return nil unless FileTest.exists?("/usr/local/sbin/dmidecode") - output=%x{/usr/local/sbin/dmidecode 2>/dev/null} - when 'NetBSD' - return nil unless FileTest.exists?("/usr/pkg/sbin/dmidecode") + output=%x{/usr/local/sbin/dmidecode 2>/dev/null} + when 'NetBSD' + return nil unless FileTest.exists?("/usr/pkg/sbin/dmidecode") - output=%x{/usr/pkg/sbin/dmidecode 2>/dev/null} - when 'SunOS' - return nil unless FileTest.exists?("/usr/sbin/smbios") + output=%x{/usr/pkg/sbin/dmidecode 2>/dev/null} + when 'SunOS' + return nil unless FileTest.exists?("/usr/sbin/smbios") - output=%x{/usr/sbin/smbios 2>/dev/null} - else - output=nil - end - return output + output=%x{/usr/sbin/smbios 2>/dev/null} + else + output=nil end + return output + end - def self.dmi_find_system_info(name) - splitstr= Facter.value(:kernel) == 'SunOS' ? "ID SIZE TYPE" : /^Handle/ - output = self.get_dmi_table() - return if output.nil? - name.each_pair do |key,v| - v.each do |v2| - v2.each_pair do |value,facterkey| - output.split(splitstr).each do |line| - if line =~ /#{key}/ and line =~ /\n\s+#{value} (.+)\n/ - result = $1.strip - Facter.add(facterkey) do - confine :kernel => [ :linux, :freebsd, :netbsd, :sunos, :"gnu/kfreebsd" ] - setcode do - result - end - end - end - end + def self.dmi_find_system_info(name) + splitstr= Facter.value(:kernel) == 'SunOS' ? "ID SIZE TYPE" : /^Handle/ + output = self.get_dmi_table() + return if output.nil? + name.each_pair do |key,v| + v.each do |v2| + v2.each_pair do |value,facterkey| + output.split(splitstr).each do |line| + if line =~ /#{key}/ and line =~ /\n\s+#{value} (.+)\n/ + result = $1.strip + Facter.add(facterkey) do + confine :kernel => [ :linux, :freebsd, :netbsd, :sunos, :"gnu/kfreebsd" ] + setcode do + result end + end end + end end + end end + end - def self.sysctl_find_system_info(name) - name.each do |sysctlkey,facterkey| - Facter.add(facterkey) do - confine :kernel => [:openbsd, :darwin] - setcode do - Facter::Util::Resolution.exec("sysctl -n #{sysctlkey} 2>/dev/null") - end - end + def self.sysctl_find_system_info(name) + name.each do |sysctlkey,facterkey| + Facter.add(facterkey) do + confine :kernel => [:openbsd, :darwin] + setcode do + Facter::Util::Resolution.exec("sysctl -n #{sysctlkey} 2>/dev/null") end + end end + end - def self.prtdiag_sparc_find_system_info() - # Parses prtdiag for a SPARC architecture string, won't work with Solaris x86 - output = Facter::Util::Resolution.exec('/usr/sbin/prtdiag') + def self.prtdiag_sparc_find_system_info() + # Parses prtdiag for a SPARC architecture string, won't work with Solaris x86 + output = Facter::Util::Resolution.exec('/usr/sbin/prtdiag') - # System Configuration: Sun Microsystems sun4u Sun SPARC Enterprise M3000 Server - sysconfig = output.split("\n")[0] - if sysconfig =~ /^System Configuration:\s+(.+?)\s+(sun\d+\S+)\s+(.+)/ then - Facter.add('manufacturer') do - setcode do - $1 - end - end - Facter.add('productname') do - setcode do - $3 - end - end + # System Configuration: Sun Microsystems sun4u Sun SPARC Enterprise M3000 Server + sysconfig = output.split("\n")[0] + if sysconfig =~ /^System Configuration:\s+(.+?)\s+(sun\d+\S+)\s+(.+)/ then + Facter.add('manufacturer') do + setcode do + $1 end - - Facter.add('serialnumber') do - setcode do - Facter::Util::Resolution.exec("/usr/sbin/sneep") - end + end + Facter.add('productname') do + setcode do + $3 end + end end - def self.win32_find_system_info(name) - require 'facter/util/wmi' - value = "" - wmi = Facter::Util::WMI.connect() - name.each do |facterkey, win32key| - query = wmi.ExecQuery("select * from Win32_#{win32key.last}") - Facter.add(facterkey) do - confine :kernel => :windows - setcode do - query.each { |x| value = x.__send__( (win32key.first).to_sym) } - value - end - end + Facter.add('serialnumber') do + setcode do + Facter::Util::Resolution.exec("/usr/sbin/sneep") + end + end + end + + def self.win32_find_system_info(name) + require 'facter/util/wmi' + value = "" + wmi = Facter::Util::WMI.connect() + name.each do |facterkey, win32key| + query = wmi.ExecQuery("select * from Win32_#{win32key.last}") + Facter.add(facterkey) do + confine :kernel => :windows + setcode do + query.each { |x| value = x.__send__( (win32key.first).to_sym) } + value end + end end + end end diff --git a/lib/facter/util/memory.rb b/lib/facter/util/memory.rb index d49079d8ab..09a8166108 100644 --- a/lib/facter/util/memory.rb +++ b/lib/facter/util/memory.rb @@ -3,83 +3,83 @@ ## module Facter::Memory - require 'thread' + require 'thread' - def self.meminfo_number(tag) - memsize = "" - Thread::exclusive do - size, scale = [0, ""] - File.readlines("/proc/meminfo").each do |l| - size, scale = [$1.to_f, $2] if l =~ /^#{tag}:\s+(\d+)\s+(\S+)/ - # MemoryFree == memfree + cached + buffers - # (assume scales are all the same as memfree) - if tag == "MemFree" && - l =~ /^(?:Buffers|Cached):\s+(\d+)\s+(?:\S+)/ - size += $1.to_f - end - end - memsize = scale_number(size, scale) + def self.meminfo_number(tag) + memsize = "" + Thread::exclusive do + size, scale = [0, ""] + File.readlines("/proc/meminfo").each do |l| + size, scale = [$1.to_f, $2] if l =~ /^#{tag}:\s+(\d+)\s+(\S+)/ + # MemoryFree == memfree + cached + buffers + # (assume scales are all the same as memfree) + if tag == "MemFree" && + l =~ /^(?:Buffers|Cached):\s+(\d+)\s+(?:\S+)/ + size += $1.to_f end - - memsize + end + memsize = scale_number(size, scale) end - def self.scale_number(size, multiplier) - suffixes = ['', 'kB', 'MB', 'GB', 'TB'] + memsize + end - s = suffixes.shift - while s != multiplier - s = suffixes.shift - end + def self.scale_number(size, multiplier) + suffixes = ['', 'kB', 'MB', 'GB', 'TB'] - while size > 1024.0 - size /= 1024.0 - s = suffixes.shift - end + s = suffixes.shift + while s != multiplier + s = suffixes.shift + end - return "%.2f %s" % [size, s] + while size > 1024.0 + size /= 1024.0 + s = suffixes.shift end - def self.vmstat_find_free_memory() - row = Facter::Util::Resolution.exec('vmstat').split("\n")[-1] - if row =~ /^\s*\d+\s*\d+\s*\d+\s*\d+\s*(\d+)/ - Facter.add("MemoryFree") do - memfree = $1 - setcode do - Facter::Memory.scale_number(memfree.to_f, "kB") - end - end + return "%.2f %s" % [size, s] + end + + def self.vmstat_find_free_memory() + row = Facter::Util::Resolution.exec('vmstat').split("\n")[-1] + if row =~ /^\s*\d+\s*\d+\s*\d+\s*\d+\s*(\d+)/ + Facter.add("MemoryFree") do + memfree = $1 + setcode do + Facter::Memory.scale_number(memfree.to_f, "kB") end + end end + end - # Darwin had to be different. It's generally opaque with how much RAM it is - # using, and this figure could be improved upon too I fear. - # Parses the output of "vm_stat", takes the pages free & pages speculative - # and multiples that by the page size (also given in output). Ties in with - # what activity monitor outputs for free memory. - def self.vmstat_darwin_find_free_memory() + # Darwin had to be different. It's generally opaque with how much RAM it is + # using, and this figure could be improved upon too I fear. + # Parses the output of "vm_stat", takes the pages free & pages speculative + # and multiples that by the page size (also given in output). Ties in with + # what activity monitor outputs for free memory. + def self.vmstat_darwin_find_free_memory() - memfree = 0 - pagesize = 0 - memspecfree = 0 + memfree = 0 + pagesize = 0 + memspecfree = 0 - vmstats = Facter::Util::Resolution.exec('vm_stat') - vmstats.each_line do |vmline| - case - when vmline =~ /page\ssize\sof\s(\d+)\sbytes/ - pagesize = $1.to_i - when vmline =~ /^Pages\sfree:\s+(\d+)\./ - memfree = $1.to_i - when vmline =~ /^Pages\sspeculative:\s+(\d+)\./ - memspecfree = $1.to_i - end - end + vmstats = Facter::Util::Resolution.exec('vm_stat') + vmstats.each_line do |vmline| + case + when vmline =~ /page\ssize\sof\s(\d+)\sbytes/ + pagesize = $1.to_i + when vmline =~ /^Pages\sfree:\s+(\d+)\./ + memfree = $1.to_i + when vmline =~ /^Pages\sspeculative:\s+(\d+)\./ + memspecfree = $1.to_i + end + end - freemem = ( memfree + memspecfree ) * pagesize - Facter.add("MemoryFree") do - setcode do - Facter::Memory.scale_number(freemem.to_f, "") - end - end + freemem = ( memfree + memspecfree ) * pagesize + Facter.add("MemoryFree") do + setcode do + Facter::Memory.scale_number(freemem.to_f, "") + end end + end end diff --git a/lib/facter/util/netmask.rb b/lib/facter/util/netmask.rb index 60b57d9b2c..b78b173ea7 100644 --- a/lib/facter/util/netmask.rb +++ b/lib/facter/util/netmask.rb @@ -1,40 +1,40 @@ module Facter::NetMask - def self.get_netmask - netmask = nil; - ipregex = %r{(\d{1,3}\.){3}\d{1,3}} + def self.get_netmask + netmask = nil; + ipregex = %r{(\d{1,3}\.){3}\d{1,3}} - ops = nil - case Facter.value(:kernel) - when 'Linux' - ops = { - :ifconfig => '/sbin/ifconfig', - :regex => %r{\s+ inet\saddr: #{Facter.ipaddress} .*? Mask: (#{ipregex})}x, - :munge => nil, - } - when 'SunOS' - ops = { - :ifconfig => '/usr/sbin/ifconfig -a', - :regex => %r{\s+ inet \s #{Facter.ipaddress} \s netmask \s (\w{8})}x, - :munge => Proc.new { |mask| mask.scan(/../).collect do |byte| byte.to_i(16) end.join('.') } - } - when 'FreeBSD','NetBSD','OpenBSD', 'Darwin', 'GNU/kFreeBSD', 'DragonFly' - ops = { - :ifconfig => '/sbin/ifconfig -a', - :regex => %r{\s+ inet \s #{Facter.ipaddress} \s netmask \s 0x(\w{8})}x, - :munge => Proc.new { |mask| mask.scan(/../).collect do |byte| byte.to_i(16) end.join('.') } - } - end + ops = nil + case Facter.value(:kernel) + when 'Linux' + ops = { + :ifconfig => '/sbin/ifconfig', + :regex => %r{\s+ inet\saddr: #{Facter.ipaddress} .*? Mask: (#{ipregex})}x, + :munge => nil, + } + when 'SunOS' + ops = { + :ifconfig => '/usr/sbin/ifconfig -a', + :regex => %r{\s+ inet \s #{Facter.ipaddress} \s netmask \s (\w{8})}x, + :munge => Proc.new { |mask| mask.scan(/../).collect do |byte| byte.to_i(16) end.join('.') } + } + when 'FreeBSD','NetBSD','OpenBSD', 'Darwin', 'GNU/kFreeBSD', 'DragonFly' + ops = { + :ifconfig => '/sbin/ifconfig -a', + :regex => %r{\s+ inet \s #{Facter.ipaddress} \s netmask \s 0x(\w{8})}x, + :munge => Proc.new { |mask| mask.scan(/../).collect do |byte| byte.to_i(16) end.join('.') } + } + end - %x{#{ops[:ifconfig]}}.split(/\n/).collect do |line| - matches = line.match(ops[:regex]) - if !matches.nil? - if ops[:munge].nil? - netmask = matches[1] - else - netmask = ops[:munge].call(matches[1]) - end - end + %x{#{ops[:ifconfig]}}.split(/\n/).collect do |line| + matches = line.match(ops[:regex]) + if !matches.nil? + if ops[:munge].nil? + netmask = matches[1] + else + netmask = ops[:munge].call(matches[1]) end - netmask + end end + netmask + end end diff --git a/lib/facter/util/plist.rb b/lib/facter/util/plist.rb index 54f5248fba..32e9e2bf7f 100644 --- a/lib/facter/util/plist.rb +++ b/lib/facter/util/plist.rb @@ -18,7 +18,7 @@ require 'facter/util/plist/parser' module Plist - VERSION = '3.0.0' + VERSION = '3.0.0' end # $Id: plist.rb 1781 2006-10-16 01:01:35Z luke $ diff --git a/lib/facter/util/plist/generator.rb b/lib/facter/util/plist/generator.rb index 6c0796b369..8f9ea3a47f 100644 --- a/lib/facter/util/plist/generator.rb +++ b/lib/facter/util/plist/generator.rb @@ -1,228 +1,228 @@ #--########################################################### -# Copyright 2006, Ben Bleything and # -# Patrick May # -# # -# Distributed under the MIT license. # +# Copyright 2006, Ben Bleything and # +# Patrick May # +# # +# Distributed under the MIT license. # ############################################################## #++ # See Plist::Emit. module Plist - # === Create a plist - # You can dump an object to a plist in one of two ways: + # === Create a plist + # You can dump an object to a plist in one of two ways: + # + # * Plist::Emit.dump(obj) + # * obj.to_plist + # * This requires that you mixin the Plist::Emit module, which is already done for +Array+ and +Hash+. + # + # The following Ruby classes are converted into native plist types: + # Array, Bignum, Date, DateTime, Fixnum, Float, Hash, Integer, String, Symbol, Time, true, false + # * +Array+ and +Hash+ are both recursive; their elements will be converted into plist nodes inside the and containers (respectively). + # * +IO+ (and its descendants) and +StringIO+ objects are read from and their contents placed in a element. + # * User classes may implement +to_plist_node+ to dictate how they should be serialized; otherwise the object will be passed to Marshal.dump and the result placed in a element. + # + # For detailed usage instructions, refer to USAGE[link:files/docs/USAGE.html] and the methods documented below. + module Emit + # Helper method for injecting into classes. Calls Plist::Emit.dump with +self+. + def to_plist(envelope = true) + return Plist::Emit.dump(self, envelope) + end + + # Helper method for injecting into classes. Calls Plist::Emit.save_plist with +self+. + def save_plist(filename) + Plist::Emit.save_plist(self, filename) + end + + # The following Ruby classes are converted into native plist types: + # Array, Bignum, Date, DateTime, Fixnum, Float, Hash, Integer, String, Symbol, Time # - # * Plist::Emit.dump(obj) - # * obj.to_plist - # * This requires that you mixin the Plist::Emit module, which is already done for +Array+ and +Hash+. + # Write us (via RubyForge) if you think another class can be coerced safely into one of the expected plist classes. # - # The following Ruby classes are converted into native plist types: - # Array, Bignum, Date, DateTime, Fixnum, Float, Hash, Integer, String, Symbol, Time, true, false - # * +Array+ and +Hash+ are both recursive; their elements will be converted into plist nodes inside the and containers (respectively). - # * +IO+ (and its descendants) and +StringIO+ objects are read from and their contents placed in a element. - # * User classes may implement +to_plist_node+ to dictate how they should be serialized; otherwise the object will be passed to Marshal.dump and the result placed in a element. + # +IO+ and +StringIO+ objects are encoded and placed in elements; other objects are Marshal.dump'ed unless they implement +to_plist_node+. # - # For detailed usage instructions, refer to USAGE[link:files/docs/USAGE.html] and the methods documented below. - module Emit - # Helper method for injecting into classes. Calls Plist::Emit.dump with +self+. - def to_plist(envelope = true) - return Plist::Emit.dump(self, envelope) - end - - # Helper method for injecting into classes. Calls Plist::Emit.save_plist with +self+. - def save_plist(filename) - Plist::Emit.save_plist(self, filename) - end + # The +envelope+ parameters dictates whether or not the resultant plist fragment is wrapped in the normal XML/plist header and footer. Set it to false if you only want the fragment. + def self.dump(obj, envelope = true) + output = plist_node(obj) - # The following Ruby classes are converted into native plist types: - # Array, Bignum, Date, DateTime, Fixnum, Float, Hash, Integer, String, Symbol, Time - # - # Write us (via RubyForge) if you think another class can be coerced safely into one of the expected plist classes. - # - # +IO+ and +StringIO+ objects are encoded and placed in elements; other objects are Marshal.dump'ed unless they implement +to_plist_node+. - # - # The +envelope+ parameters dictates whether or not the resultant plist fragment is wrapped in the normal XML/plist header and footer. Set it to false if you only want the fragment. - def self.dump(obj, envelope = true) - output = plist_node(obj) + output = wrap(output) if envelope - output = wrap(output) if envelope + return output + end - return output - end + # Writes the serialized object's plist to the specified filename. + def self.save_plist(obj, filename) + File.open(filename, 'wb') do |f| + f.write(obj.to_plist) + end + end - # Writes the serialized object's plist to the specified filename. - def self.save_plist(obj, filename) - File.open(filename, 'wb') do |f| - f.write(obj.to_plist) + private + def self.plist_node(element) + output = '' + + if element.respond_to? :to_plist_node + output << element.to_plist_node + else + case element + when Array + if element.empty? + output << "\n" + else + output << tag('array') { + element.collect {|e| plist_node(e)} + } + end + when Hash + if element.empty? + output << "\n" + else + inner_tags = [] + + element.keys.sort.each do |k| + v = element[k] + inner_tags << tag('key', CGI::escapeHTML(k.to_s)) + inner_tags << plist_node(v) end + + output << tag('dict') { + inner_tags + } + end + when true, false + output << "<#{element}/>\n" + when Time + output << tag('date', element.utc.strftime('%Y-%m-%dT%H:%M:%SZ')) + when Date # also catches DateTime + output << tag('date', element.strftime('%Y-%m-%dT%H:%M:%SZ')) + when String, Symbol, Fixnum, Bignum, Integer, Float + output << tag(element_type(element), CGI::escapeHTML(element.to_s)) + when IO, StringIO + element.rewind + contents = element.read + # note that apple plists are wrapped at a different length then + # what ruby's base64 wraps by default. + # I used #encode64 instead of #b64encode (which allows a length arg) + # because b64encode is b0rked and ignores the length arg. + data = "\n" + Base64::encode64(contents).gsub(/\s+/, '').scan(/.{1,68}/o) { data << $& << "\n" } + output << tag('data', data) + else + output << comment( 'The element below contains a Ruby object which has been serialized with Marshal.dump.' ) + data = "\n" + Base64::encode64(Marshal.dump(element)).gsub(/\s+/, '').scan(/.{1,68}/o) { data << $& << "\n" } + output << tag('data', data ) end + end - private - def self.plist_node(element) - output = '' - - if element.respond_to? :to_plist_node - output << element.to_plist_node - else - case element - when Array - if element.empty? - output << "\n" - else - output << tag('array') { - element.collect {|e| plist_node(e)} - } - end - when Hash - if element.empty? - output << "\n" - else - inner_tags = [] - - element.keys.sort.each do |k| - v = element[k] - inner_tags << tag('key', CGI::escapeHTML(k.to_s)) - inner_tags << plist_node(v) - end - - output << tag('dict') { - inner_tags - } - end - when true, false - output << "<#{element}/>\n" - when Time - output << tag('date', element.utc.strftime('%Y-%m-%dT%H:%M:%SZ')) - when Date # also catches DateTime - output << tag('date', element.strftime('%Y-%m-%dT%H:%M:%SZ')) - when String, Symbol, Fixnum, Bignum, Integer, Float - output << tag(element_type(element), CGI::escapeHTML(element.to_s)) - when IO, StringIO - element.rewind - contents = element.read - # note that apple plists are wrapped at a different length then - # what ruby's base64 wraps by default. - # I used #encode64 instead of #b64encode (which allows a length arg) - # because b64encode is b0rked and ignores the length arg. - data = "\n" - Base64::encode64(contents).gsub(/\s+/, '').scan(/.{1,68}/o) { data << $& << "\n" } - output << tag('data', data) - else - output << comment( 'The element below contains a Ruby object which has been serialized with Marshal.dump.' ) - data = "\n" - Base64::encode64(Marshal.dump(element)).gsub(/\s+/, '').scan(/.{1,68}/o) { data << $& << "\n" } - output << tag('data', data ) - end - end + return output + end - return output - end + def self.comment(content) + return "\n" + end - def self.comment(content) - return "\n" - end + def self.tag(type, contents = '', &block) + out = nil - def self.tag(type, contents = '', &block) - out = nil + if block_given? + out = IndentedString.new + out << "<#{type}>" + out.raise_indent - if block_given? - out = IndentedString.new - out << "<#{type}>" - out.raise_indent + out << block.call - out << block.call + out.lower_indent + out << "" + else + out = "<#{type}>#{contents.to_s}\n" + end - out.lower_indent - out << "" - else - out = "<#{type}>#{contents.to_s}\n" - end + return out.to_s + end - return out.to_s - end + def self.wrap(contents) + output = '' - def self.wrap(contents) - output = '' + output << '' + "\n" + output << '' + "\n" + output << '' + "\n" - output << '' + "\n" - output << '' + "\n" - output << '' + "\n" + output << contents - output << contents + output << '' + "\n" - output << '' + "\n" + return output + end - return output - end + def self.element_type(item) + return case item + when String, Symbol; 'string' + when Fixnum, Bignum, Integer; 'integer' + when Float; 'real' + else + raise "Don't know about this data type... something must be wrong!" + end + end - def self.element_type(item) - return case item - when String, Symbol; 'string' - when Fixnum, Bignum, Integer; 'integer' - when Float; 'real' - else - raise "Don't know about this data type... something must be wrong!" - end - end + private - private + class IndentedString #:nodoc: + attr_accessor :indent_string - class IndentedString #:nodoc: - attr_accessor :indent_string + @@indent_level = 0 - @@indent_level = 0 + def initialize(str = "\t") + @indent_string = str + @contents = '' + end - def initialize(str = "\t") - @indent_string = str - @contents = '' - end + def to_s + return @contents + end - def to_s - return @contents - end + def raise_indent + @@indent_level += 1 + end - def raise_indent - @@indent_level += 1 - end + def lower_indent + @@indent_level -= 1 if @@indent_level > 0 + end - def lower_indent - @@indent_level -= 1 if @@indent_level > 0 - end + def <<(val) + if val.is_a? Array + val.each do |f| + self << f + end + else + # if it's already indented, don't bother indenting further + unless val =~ /\A#{@indent_string}/ + indent = @indent_string * @@indent_level - def <<(val) - if val.is_a? Array - val.each do |f| - self << f - end - else - # if it's already indented, don't bother indenting further - unless val =~ /\A#{@indent_string}/ - indent = @indent_string * @@indent_level - - @contents << val.gsub(/^/, indent) - else - @contents << val - end - - # it already has a newline, don't add another - @contents << "\n" unless val =~ /\n$/ - end - end + @contents << val.gsub(/^/, indent) + else + @contents << val + end + + # it already has a newline, don't add another + @contents << "\n" unless val =~ /\n$/ end + end end + end end # we need to add this so sorting hash keys works properly class Symbol #:nodoc: - def <=> (other) - self.to_s <=> other.to_s - end + def <=> (other) + self.to_s <=> other.to_s + end end class Array #:nodoc: - include Plist::Emit + include Plist::Emit end class Hash #:nodoc: - include Plist::Emit + include Plist::Emit end # $Id: generator.rb 1781 2006-10-16 01:01:35Z luke $ diff --git a/lib/facter/util/plist/parser.rb b/lib/facter/util/plist/parser.rb index 48e10343ec..ffbb7ba69a 100644 --- a/lib/facter/util/plist/parser.rb +++ b/lib/facter/util/plist/parser.rb @@ -1,8 +1,8 @@ #--########################################################### -# Copyright 2006, Ben Bleything and # -# Patrick May # -# # -# Distributed under the MIT license. # +# Copyright 2006, Ben Bleything and # +# Patrick May # +# # +# Distributed under the MIT license. # ############################################################## #++ # Plist parses Mac OS X xml property list files into ruby data structures. @@ -21,206 +21,206 @@ module Plist # If you encounter such an error, or if you have a Date element which # can't be parsed into a Time object, please send your plist file to # plist@hexane.org so that I can implement the proper support. - def Plist::parse_xml( filename_or_xml ) - listener = Listener.new - #parser = REXML::Parsers::StreamParser.new(File.new(filename), listener) - parser = StreamParser.new(filename_or_xml, listener) - parser.parse - listener.result + def Plist::parse_xml( filename_or_xml ) + listener = Listener.new + #parser = REXML::Parsers::StreamParser.new(File.new(filename), listener) + parser = StreamParser.new(filename_or_xml, listener) + parser.parse + listener.result + end + + class Listener + #include REXML::StreamListener + + attr_accessor :result, :open + + def initialize + @result = nil + @open = Array.new end - class Listener - #include REXML::StreamListener - attr_accessor :result, :open - - def initialize - @result = nil - @open = Array.new - end - - - def tag_start(name, attributes) - @open.push PTag::mappings[name].new - end - - def text( contents ) - @open.last.text = contents if @open.last - end + def tag_start(name, attributes) + @open.push PTag::mappings[name].new + end - def tag_end(name) - last = @open.pop - if @open.empty? - @result = last.to_ruby - else - @open.last.children.push last - end - end + def text( contents ) + @open.last.text = contents if @open.last end - class StreamParser - def initialize( filename_or_xml, listener ) - @filename_or_xml = filename_or_xml - @listener = listener - end + def tag_end(name) + last = @open.pop + if @open.empty? + @result = last.to_ruby + else + @open.last.children.push last + end + end + end - TEXT = /([^<]+)/ - XMLDECL_PATTERN = /<\?xml\s+(.*?)\?>*/um - DOCTYPE_PATTERN = /\s*)/um - COMMENT_START = /\A/um - - def parse - plist_tags = PTag::mappings.keys.join('|') - start_tag = /<(#{plist_tags})([^>]*)>/i - end_tag = /<\/(#{plist_tags})[^>]*>/i - - require 'strscan' - - contents = ( - if (File.exists? @filename_or_xml) - File.open(@filename_or_xml) {|f| f.read} - else - @filename_or_xml - end - ) - - @scanner = StringScanner.new( contents ) - until @scanner.eos? - if @scanner.scan(COMMENT_START) - @scanner.scan(COMMENT_END) - elsif @scanner.scan(XMLDECL_PATTERN) - elsif @scanner.scan(DOCTYPE_PATTERN) - elsif @scanner.scan(start_tag) - @listener.tag_start(@scanner[1], nil) - if (@scanner[2] =~ /\/$/) - @listener.tag_end(@scanner[1]) - end - elsif @scanner.scan(TEXT) - @listener.text(@scanner[1]) - elsif @scanner.scan(end_tag) - @listener.tag_end(@scanner[1]) - else - raise "Unimplemented element" - end - end - end + class StreamParser + def initialize( filename_or_xml, listener ) + @filename_or_xml = filename_or_xml + @listener = listener end - class PTag - @@mappings = { } - def PTag::mappings - @@mappings - end + TEXT = /([^<]+)/ + XMLDECL_PATTERN = /<\?xml\s+(.*?)\?>*/um + DOCTYPE_PATTERN = /\s*)/um + COMMENT_START = /\A/um + + def parse + plist_tags = PTag::mappings.keys.join('|') + start_tag = /<(#{plist_tags})([^>]*)>/i + end_tag = /<\/(#{plist_tags})[^>]*>/i + + require 'strscan' + + contents = ( + if (File.exists? @filename_or_xml) + File.open(@filename_or_xml) {|f| f.read} + else + @filename_or_xml + end + ) + + @scanner = StringScanner.new( contents ) + until @scanner.eos? + if @scanner.scan(COMMENT_START) + @scanner.scan(COMMENT_END) + elsif @scanner.scan(XMLDECL_PATTERN) + elsif @scanner.scan(DOCTYPE_PATTERN) + elsif @scanner.scan(start_tag) + @listener.tag_start(@scanner[1], nil) + if (@scanner[2] =~ /\/$/) + @listener.tag_end(@scanner[1]) + end + elsif @scanner.scan(TEXT) + @listener.text(@scanner[1]) + elsif @scanner.scan(end_tag) + @listener.tag_end(@scanner[1]) + else + raise "Unimplemented element" + end + end + end + end - def PTag::inherited( sub_class ) - key = sub_class.to_s.downcase - key.gsub!(/^plist::/, '' ) - key.gsub!(/^p/, '') unless key == "plist" + class PTag + @@mappings = { } + def PTag::mappings + @@mappings + end - @@mappings[key] = sub_class - end + def PTag::inherited( sub_class ) + key = sub_class.to_s.downcase + key.gsub!(/^plist::/, '' ) + key.gsub!(/^p/, '') unless key == "plist" - attr_accessor :text, :children - def initialize - @children = Array.new - end + @@mappings[key] = sub_class + end - def to_ruby - raise "Unimplemented: " + self.class.to_s + "#to_ruby on #{self.inspect}" - end + attr_accessor :text, :children + def initialize + @children = Array.new end - class PList < PTag - def to_ruby - children.first.to_ruby if children.first - end + def to_ruby + raise "Unimplemented: " + self.class.to_s + "#to_ruby on #{self.inspect}" end + end - class PDict < PTag - def to_ruby - dict = Hash.new - key = nil + class PList < PTag + def to_ruby + children.first.to_ruby if children.first + end + end - children.each do |c| - if key.nil? - key = c.to_ruby - else - dict[key] = c.to_ruby - key = nil - end - end + class PDict < PTag + def to_ruby + dict = Hash.new + key = nil - dict + children.each do |c| + if key.nil? + key = c.to_ruby + else + dict[key] = c.to_ruby + key = nil end - end + end - class PKey < PTag - def to_ruby - CGI::unescapeHTML(text || '') - end + dict end + end - class PString < PTag - def to_ruby - CGI::unescapeHTML(text || '') - end + class PKey < PTag + def to_ruby + CGI::unescapeHTML(text || '') end + end - class PArray < PTag - def to_ruby - children.collect do |c| - c.to_ruby - end - end + class PString < PTag + def to_ruby + CGI::unescapeHTML(text || '') end + end - class PInteger < PTag - def to_ruby - text.to_i - end + class PArray < PTag + def to_ruby + children.collect do |c| + c.to_ruby + end end + end - class PTrue < PTag - def to_ruby - true - end + class PInteger < PTag + def to_ruby + text.to_i end + end - class PFalse < PTag - def to_ruby - false - end + class PTrue < PTag + def to_ruby + true end + end - class PReal < PTag - def to_ruby - text.to_f - end + class PFalse < PTag + def to_ruby + false end + end - require 'date' - class PDate < PTag - def to_ruby - DateTime.parse(text) - end + class PReal < PTag + def to_ruby + text.to_f end + end - require 'base64' - class PData < PTag - def to_ruby - data = Base64.decode64(text.gsub(/\s+/, '')) - - begin - return Marshal.load(data) - rescue Exception => e - io = StringIO.new - io.write data - io.rewind - return io - end - end + require 'date' + class PDate < PTag + def to_ruby + DateTime.parse(text) + end + end + + require 'base64' + class PData < PTag + def to_ruby + data = Base64.decode64(text.gsub(/\s+/, '')) + + begin + return Marshal.load(data) + rescue Exception => e + io = StringIO.new + io.write data + io.rewind + return io + end end + end end # $Id: parser.rb 1781 2006-10-16 01:01:35Z luke $ diff --git a/lib/facter/util/processor.rb b/lib/facter/util/processor.rb index d4b4de9048..8438482f2b 100644 --- a/lib/facter/util/processor.rb +++ b/lib/facter/util/processor.rb @@ -26,7 +26,7 @@ def self.enum_cpuinfo Thread::exclusive do File.readlines(cpuinfo).each do |l| if l =~ /processor\s+:\s+(\d+)/ - processor_num = $1.to_i + processor_num = $1.to_i elsif l =~ /cpu\s+:\s+(.*)\s*$/ processor_list[processor_num] = $1 unless processor_num == -1 processor_num = -1 diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 4388c8141b..0270be1194 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -9,179 +9,179 @@ require 'timeout' class Facter::Util::Resolution - attr_accessor :interpreter, :code, :name, :timeout - - INTERPRETER = Facter::Util::Config.is_windows? ? "cmd.exe" : "/bin/sh" - - def self.have_which - if ! defined?(@have_which) or @have_which.nil? - if Facter.value(:kernel) == 'windows' - @have_which = false - else - %x{which which >/dev/null 2>&1} - @have_which = ($? == 0) - end - end - @have_which + attr_accessor :interpreter, :code, :name, :timeout + + INTERPRETER = Facter::Util::Config.is_windows? ? "cmd.exe" : "/bin/sh" + + def self.have_which + if ! defined?(@have_which) or @have_which.nil? + if Facter.value(:kernel) == 'windows' + @have_which = false + else + %x{which which >/dev/null 2>&1} + @have_which = ($? == 0) + end end - - # Execute a program and return the output of that program. + @have_which + end + + # Execute a program and return the output of that program. + # + # Returns nil if the program can't be found, or if there is a problem + # executing the code. + # + def self.exec(code, interpreter = nil) + Facter.warnonce "The interpreter parameter to 'exec' is deprecated and will be removed in a future version." if interpreter + + # Try to guess whether the specified code can be executed by looking at the + # first word. If it cannot be found on the PATH defer on resolving the fact + # by returning nil. + # This only fails on shell built-ins, most of which are masked by stuff in + # /bin or of dubious value anyways. In the worst case, "sh -c 'builtin'" can + # be used to work around this limitation # - # Returns nil if the program can't be found, or if there is a problem - # executing the code. - # - def self.exec(code, interpreter = nil) - Facter.warnonce "The interpreter parameter to 'exec' is deprecated and will be removed in a future version." if interpreter - - # Try to guess whether the specified code can be executed by looking at the - # first word. If it cannot be found on the PATH defer on resolving the fact - # by returning nil. - # This only fails on shell built-ins, most of which are masked by stuff in - # /bin or of dubious value anyways. In the worst case, "sh -c 'builtin'" can - # be used to work around this limitation - # - # Windows' %x{} throws Errno::ENOENT when the command is not found, so we - # can skip the check there. This is good, since builtins cannot be found - # elsewhere. - if have_which and !Facter::Util::Config.is_windows? - path = nil - binary = code.split.first - if code =~ /^\// - path = binary - else - path = %x{which #{binary} 2>/dev/null}.chomp - # we don't have the binary necessary - return nil if path == "" or path.match(/Command not found\./) - end - - return nil unless FileTest.exists?(path) - end - - out = nil - - begin - out = %x{#{code}}.chomp - rescue Errno::ENOENT => detail - # command not found on Windows - return nil - rescue => detail - $stderr.puts detail - return nil - end - - if out == "" - return nil - else - return out - end + # Windows' %x{} throws Errno::ENOENT when the command is not found, so we + # can skip the check there. This is good, since builtins cannot be found + # elsewhere. + if have_which and !Facter::Util::Config.is_windows? + path = nil + binary = code.split.first + if code =~ /^\// + path = binary + else + path = %x{which #{binary} 2>/dev/null}.chomp + # we don't have the binary necessary + return nil if path == "" or path.match(/Command not found\./) + end + + return nil unless FileTest.exists?(path) end - # Add a new confine to the resolution mechanism. - def confine(confines) - confines.each do |fact, values| - @confines.push Facter::Util::Confine.new(fact, *values) - end - end + out = nil - def has_weight(weight) - @weight = weight + begin + out = %x{#{code}}.chomp + rescue Errno::ENOENT => detail + # command not found on Windows + return nil + rescue => detail + $stderr.puts detail + return nil end - # Create a new resolution mechanism. - def initialize(name) - @name = name - @confines = [] - @value = nil - @timeout = 0 - @weight = nil + if out == "" + return nil + else + return out end + end - # Return the importance of this resolution. - def weight - if @weight - @weight - else - @confines.length - end + # Add a new confine to the resolution mechanism. + def confine(confines) + confines.each do |fact, values| + @confines.push Facter::Util::Confine.new(fact, *values) end - - # We need this as a getter for 'timeout', because some versions - # of ruby seem to already have a 'timeout' method and we can't - # seem to override the instance methods, somehow. - def limit - @timeout + end + + def has_weight(weight) + @weight = weight + end + + # Create a new resolution mechanism. + def initialize(name) + @name = name + @confines = [] + @value = nil + @timeout = 0 + @weight = nil + end + + # Return the importance of this resolution. + def weight + if @weight + @weight + else + @confines.length end - - # Set our code for returning a value. - def setcode(string = nil, interp = nil, &block) - Facter.warnonce "The interpreter parameter to 'setcode' is deprecated and will be removed in a future version." if interp - if string - @code = string - @interpreter = interp || INTERPRETER - else - unless block_given? - raise ArgumentError, "You must pass either code or a block" - end - @code = block - end + end + + # We need this as a getter for 'timeout', because some versions + # of ruby seem to already have a 'timeout' method and we can't + # seem to override the instance methods, somehow. + def limit + @timeout + end + + # Set our code for returning a value. + def setcode(string = nil, interp = nil, &block) + Facter.warnonce "The interpreter parameter to 'setcode' is deprecated and will be removed in a future version." if interp + if string + @code = string + @interpreter = interp || INTERPRETER + else + unless block_given? + raise ArgumentError, "You must pass either code or a block" + end + @code = block end - - def interpreter - Facter.warnonce "The 'Facter::Util::Resolution.interpreter' method is deprecated and will be removed in a future version." - @interpreter + end + + def interpreter + Facter.warnonce "The 'Facter::Util::Resolution.interpreter' method is deprecated and will be removed in a future version." + @interpreter + end + + def interpreter=(interp) + Facter.warnonce "The 'Facter::Util::Resolution.interpreter=' method is deprecated and will be removed in a future version." + @interpreter = interp + end + + # Is this resolution mechanism suitable on the system in question? + def suitable? + unless defined? @suitable + @suitable = ! @confines.detect { |confine| ! confine.true? } end - def interpreter=(interp) - Facter.warnonce "The 'Facter::Util::Resolution.interpreter=' method is deprecated and will be removed in a future version." - @interpreter = interp - end + return @suitable + end - # Is this resolution mechanism suitable on the system in question? - def suitable? - unless defined? @suitable - @suitable = ! @confines.detect { |confine| ! confine.true? } - end + def to_s + return self.value() + end - return @suitable - end + # How we get a value for our resolution mechanism. + def value + result = nil + return result if @code == nil - def to_s - return self.value() - end + starttime = Time.now.to_f - # How we get a value for our resolution mechanism. - def value - result = nil - return result if @code == nil - - starttime = Time.now.to_f - - begin - Timeout.timeout(limit) do - if @code.is_a?(Proc) - result = @code.call() - else - result = Facter::Util::Resolution.exec(@code) - end - end - rescue Timeout::Error => detail - warn "Timed out seeking value for %s" % self.name - - # This call avoids zombies -- basically, create a thread that will - # dezombify all of the child processes that we're ignoring because - # of the timeout. - Thread.new { Process.waitall } - return nil - rescue => details - warn "Could not retrieve %s: %s" % [self.name, details] - return nil + begin + Timeout.timeout(limit) do + if @code.is_a?(Proc) + result = @code.call() + else + result = Facter::Util::Resolution.exec(@code) end + end + rescue Timeout::Error => detail + warn "Timed out seeking value for %s" % self.name + + # This call avoids zombies -- basically, create a thread that will + # dezombify all of the child processes that we're ignoring because + # of the timeout. + Thread.new { Process.waitall } + return nil + rescue => details + warn "Could not retrieve %s: %s" % [self.name, details] + return nil + end - finishtime = Time.now.to_f - ms = (finishtime - starttime) * 1000 - Facter.show_time "#{self.name}: #{"%.2f" % ms}ms" + finishtime = Time.now.to_f + ms = (finishtime - starttime) * 1000 + Facter.show_time "#{self.name}: #{"%.2f" % ms}ms" - return nil if result == "" - return result - end + return nil if result == "" + return result + end end diff --git a/lib/facter/util/uptime.rb b/lib/facter/util/uptime.rb index a816623db0..772cc87762 100644 --- a/lib/facter/util/uptime.rb +++ b/lib/facter/util/uptime.rb @@ -3,63 +3,63 @@ # A module to gather uptime facts # module Facter::Util::Uptime - def self.get_uptime_seconds_unix - uptime_proc_uptime or uptime_sysctl or uptime_kstat or uptime_who_dash_b - end + def self.get_uptime_seconds_unix + uptime_proc_uptime or uptime_sysctl or uptime_kstat or uptime_who_dash_b + end - def self.get_uptime_seconds_win - require 'facter/util/wmi' + def self.get_uptime_seconds_win + require 'facter/util/wmi' - last_boot = "" - Facter::Util::WMI.execquery("select * from Win32_OperatingSystem").each do |x| - last_boot = x.LastBootupTime - end - self.compute_uptime(Time.parse(last_boot.split('.').first)) + last_boot = "" + Facter::Util::WMI.execquery("select * from Win32_OperatingSystem").each do |x| + last_boot = x.LastBootupTime end + self.compute_uptime(Time.parse(last_boot.split('.').first)) + end - private + private - def self.uptime_proc_uptime - if output = Facter::Util::Resolution.exec("/bin/cat #{uptime_file} 2>/dev/null") - output.chomp.split(" ").first.to_i - end + def self.uptime_proc_uptime + if output = Facter::Util::Resolution.exec("/bin/cat #{uptime_file} 2>/dev/null") + output.chomp.split(" ").first.to_i end + end - def self.uptime_sysctl - if output = Facter::Util::Resolution.exec("#{uptime_sysctl_cmd} 2>/dev/null") - compute_uptime(Time.at(output.unpack('L').first)) - end + def self.uptime_sysctl + if output = Facter::Util::Resolution.exec("#{uptime_sysctl_cmd} 2>/dev/null") + compute_uptime(Time.at(output.unpack('L').first)) end + end - def self.uptime_kstat - if output = Facter::Util::Resolution.exec("#{uptime_kstat_cmd} 2>/dev/null") - compute_uptime(Time.at(output.chomp.split(/\s/).last.to_i)) - end + def self.uptime_kstat + if output = Facter::Util::Resolution.exec("#{uptime_kstat_cmd} 2>/dev/null") + compute_uptime(Time.at(output.chomp.split(/\s/).last.to_i)) end + end - def self.uptime_who_dash_b - if output = Facter::Util::Resolution.exec("#{uptime_who_cmd} 2>/dev/null") - compute_uptime(Time.parse(output)) - end + def self.uptime_who_dash_b + if output = Facter::Util::Resolution.exec("#{uptime_who_cmd} 2>/dev/null") + compute_uptime(Time.parse(output)) end + end - def self.compute_uptime(time) - (Time.now - time).to_i - end + def self.compute_uptime(time) + (Time.now - time).to_i + end - def self.uptime_file - "/proc/uptime" - end + def self.uptime_file + "/proc/uptime" + end - def self.uptime_sysctl_cmd - 'sysctl -b kern.boottime' - end + def self.uptime_sysctl_cmd + 'sysctl -b kern.boottime' + end - def self.uptime_kstat_cmd - 'kstat -p unix:::boot_time' - end + def self.uptime_kstat_cmd + 'kstat -p unix:::boot_time' + end - def self.uptime_who_cmd - 'who -b' - end + def self.uptime_who_cmd + 'who -b' + end end diff --git a/lib/facter/util/values.rb b/lib/facter/util/values.rb index ebc7614904..68ace3475b 100644 --- a/lib/facter/util/values.rb +++ b/lib/facter/util/values.rb @@ -1,14 +1,14 @@ # A util module for facter containing helper methods module Facter - module Util - module Values - module_function + module Util + module Values + module_function - def convert(value) - value = value.to_s if value.is_a?(Symbol) - value = value.downcase if value.is_a?(String) - value - end - end + def convert(value) + value = value.to_s if value.is_a?(Symbol) + value = value.downcase if value.is_a?(String) + value + end end + end end diff --git a/lib/facter/util/virtual.rb b/lib/facter/util/virtual.rb index 0ae7c8f06b..01b71b37b9 100644 --- a/lib/facter/util/virtual.rb +++ b/lib/facter/util/virtual.rb @@ -1,86 +1,86 @@ module Facter::Util::Virtual - def self.openvz? - FileTest.directory?("/proc/vz") and not self.openvz_cloudlinux? - end + def self.openvz? + FileTest.directory?("/proc/vz") and not self.openvz_cloudlinux? + end - # So one can either have #6728 work on OpenVZ or Cloudlinux. Whoo. - def self.openvz_type - return false unless self.openvz? - return false unless FileTest.exists?( '/proc/self/status' ) + # So one can either have #6728 work on OpenVZ or Cloudlinux. Whoo. + def self.openvz_type + return false unless self.openvz? + return false unless FileTest.exists?( '/proc/self/status' ) - envid = Facter::Util::Resolution.exec( 'grep "envID" /proc/self/status' ) - if envid =~ /^envID:\s+0$/i - return 'openvzhn' - elsif envid =~ /^envID:\s+(\d+)$/i - return 'openvzve' - end + envid = Facter::Util::Resolution.exec( 'grep "envID" /proc/self/status' ) + if envid =~ /^envID:\s+0$/i + return 'openvzhn' + elsif envid =~ /^envID:\s+(\d+)$/i + return 'openvzve' end + end - # Cloudlinux uses OpenVZ to a degree, but always has an empty /proc/vz/ and - # has /proc/lve/list present - def self.openvz_cloudlinux? - FileTest.file?("/proc/lve/list") or Dir.glob('/proc/vz/*').empty? - end + # Cloudlinux uses OpenVZ to a degree, but always has an empty /proc/vz/ and + # has /proc/lve/list present + def self.openvz_cloudlinux? + FileTest.file?("/proc/lve/list") or Dir.glob('/proc/vz/*').empty? + end - def self.zone? - return true if FileTest.directory?("/.SUNWnative") - z = Facter::Util::Resolution.exec("/sbin/zonename") - return false unless z - return z.chomp != 'global' - end + def self.zone? + return true if FileTest.directory?("/.SUNWnative") + z = Facter::Util::Resolution.exec("/sbin/zonename") + return false unless z + return z.chomp != 'global' + end - def self.vserver? - return false unless FileTest.exists?("/proc/self/status") - txt = File.read("/proc/self/status") - return true if txt =~ /^(s_context|VxID):[[:blank:]]*[0-9]/ - return false - end + def self.vserver? + return false unless FileTest.exists?("/proc/self/status") + txt = File.read("/proc/self/status") + return true if txt =~ /^(s_context|VxID):[[:blank:]]*[0-9]/ + return false + end - def self.vserver_type - if self.vserver? - if FileTest.exists?("/proc/virtual") - "vserver_host" - else - "vserver" - end - end + def self.vserver_type + if self.vserver? + if FileTest.exists?("/proc/virtual") + "vserver_host" + else + "vserver" + end end + end - def self.xen? - ["/proc/sys/xen", "/sys/bus/xen", "/proc/xen" ].detect do |f| - FileTest.exists?(f) - end + def self.xen? + ["/proc/sys/xen", "/sys/bus/xen", "/proc/xen" ].detect do |f| + FileTest.exists?(f) end + end - def self.kvm? - txt = if FileTest.exists?("/proc/cpuinfo") - File.read("/proc/cpuinfo") - elsif Facter.value(:kernel)=="FreeBSD" - Facter::Util::Resolution.exec("/sbin/sysctl -n hw.model") - end - (txt =~ /QEMU Virtual CPU/) ? true : false - end + def self.kvm? + txt = if FileTest.exists?("/proc/cpuinfo") + File.read("/proc/cpuinfo") + elsif Facter.value(:kernel)=="FreeBSD" + Facter::Util::Resolution.exec("/sbin/sysctl -n hw.model") + end + (txt =~ /QEMU Virtual CPU/) ? true : false + end - def self.kvm_type - # TODO Tell the difference between kvm and qemu - # Can't work out a way to do this at the moment that doesn't - # require a special binary - "kvm" - end + def self.kvm_type + # TODO Tell the difference between kvm and qemu + # Can't work out a way to do this at the moment that doesn't + # require a special binary + "kvm" + end - def self.jail? - path = case Facter.value(:kernel) - when "FreeBSD" then "/sbin" - when "GNU/kFreeBSD" then "/bin" - end - Facter::Util::Resolution.exec("#{path}/sysctl -n security.jail.jailed") == "1" + def self.jail? + path = case Facter.value(:kernel) + when "FreeBSD" then "/sbin" + when "GNU/kFreeBSD" then "/bin" end + Facter::Util::Resolution.exec("#{path}/sysctl -n security.jail.jailed") == "1" + end - def self.hpvm? - Facter::Util::Resolution.exec("/usr/bin/getconf MACHINE_MODEL").chomp =~ /Virtual Machine/ - end + def self.hpvm? + Facter::Util::Resolution.exec("/usr/bin/getconf MACHINE_MODEL").chomp =~ /Virtual Machine/ + end def self.zlinux? - "zlinux" + "zlinux" end end diff --git a/lib/facter/util/vlans.rb b/lib/facter/util/vlans.rb index 2b2a72f454..0a48ec890b 100644 --- a/lib/facter/util/vlans.rb +++ b/lib/facter/util/vlans.rb @@ -1,24 +1,24 @@ # A module to gather vlan facts # module Facter::Util::Vlans - def self.get_vlan_config - output = "" - if File.exists?('/proc/net/vlan/config') and File.readable?('/proc/net/vlan/config') - output = File.open('/proc/net/vlan/config').read - end - output - end + def self.get_vlan_config + output = "" + if File.exists?('/proc/net/vlan/config') and File.readable?('/proc/net/vlan/config') + output = File.open('/proc/net/vlan/config').read + end + output + end - def self.get_vlans - vlans = Array.new - if self.get_vlan_config - self.get_vlan_config.each_line do |line| - if line =~ /^([0-9A-Za-z]+)\.([0-9]+) / - vlans.insert(-1, $~[2]) if $~[2] - end - end + def self.get_vlans + vlans = Array.new + if self.get_vlan_config + self.get_vlan_config.each_line do |line| + if line =~ /^([0-9A-Za-z]+)\.([0-9]+) / + vlans.insert(-1, $~[2]) if $~[2] end - - vlans.join(',') + end end + + vlans.join(',') + end end diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 578562a6ef..d46d4a9b09 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -10,8 +10,8 @@ # display. # # On Linux, BSD, Solaris and HPUX: -# Much of the logic here is obscured behind util/virtual.rb, which rather -# than document here, which would encourage drift, just refer to it. +# Much of the logic here is obscured behind util/virtual.rb, which rather +# than document here, which would encourage drift, just refer to it. # The Xen tests in here rely on /sys and /proc, and check for the presence and # contents of files in there. # If after all the other tests, it's still seen as physical, then it tries to @@ -27,120 +27,120 @@ require 'facter/util/virtual' Facter.add("virtual") do - confine :kernel => "Darwin" - - setcode do - require 'facter/util/macosx' - result = "physical" - output = Facter::Util::Macosx.profiler_data("SPDisplaysDataType") - if output.is_a?(Hash) - result = "parallels" if output["spdisplays_vendor-id"] =~ /0x1ab8/ - result = "parallels" if output["spdisplays_vendor"] =~ /[Pp]arallels/ - result = "vmware" if output["spdisplays_vendor-id"] =~ /0x15ad/ - result = "vmware" if output["spdisplays_vendor"] =~ /VM[wW]are/ - result = "virtualbox" if output["spdisplays_vendor-id"] =~ /0x80ee/ - end - result + confine :kernel => "Darwin" + + setcode do + require 'facter/util/macosx' + result = "physical" + output = Facter::Util::Macosx.profiler_data("SPDisplaysDataType") + if output.is_a?(Hash) + result = "parallels" if output["spdisplays_vendor-id"] =~ /0x1ab8/ + result = "parallels" if output["spdisplays_vendor"] =~ /[Pp]arallels/ + result = "vmware" if output["spdisplays_vendor-id"] =~ /0x15ad/ + result = "vmware" if output["spdisplays_vendor"] =~ /VM[wW]are/ + result = "virtualbox" if output["spdisplays_vendor-id"] =~ /0x80ee/ end + result + end end Facter.add("virtual") do - confine :kernel => %w{Linux FreeBSD OpenBSD SunOS HP-UX GNU/kFreeBSD} + confine :kernel => %w{Linux FreeBSD OpenBSD SunOS HP-UX GNU/kFreeBSD} - result = "physical" + result = "physical" - setcode do + setcode do - if Facter.value(:operatingsystem) == "Solaris" and Facter::Util::Virtual.zone? - result = "zone" - end + if Facter.value(:operatingsystem) == "Solaris" and Facter::Util::Virtual.zone? + result = "zone" + end - if Facter.value(:kernel)=="HP-UX" - result = "hpvm" if Facter::Util::Virtual.hpvm? - end + if Facter.value(:kernel)=="HP-UX" + result = "hpvm" if Facter::Util::Virtual.hpvm? + end - if Facter.value(:architecture)=="s390x" - result = "zlinux" if Facter::Util::Virtual.zlinux? - end + if Facter.value(:architecture)=="s390x" + result = "zlinux" if Facter::Util::Virtual.zlinux? + end - if Facter::Util::Virtual.openvz? - result = Facter::Util::Virtual.openvz_type() - end + if Facter::Util::Virtual.openvz? + result = Facter::Util::Virtual.openvz_type() + end - if Facter::Util::Virtual.vserver? - result = Facter::Util::Virtual.vserver_type() - end + if Facter::Util::Virtual.vserver? + result = Facter::Util::Virtual.vserver_type() + end - if Facter::Util::Virtual.xen? - if FileTest.exists?("/proc/xen/xsd_kva") - result = "xen0" - elsif FileTest.exists?("/proc/xen/capabilities") - result = "xenu" - end - end + if Facter::Util::Virtual.xen? + if FileTest.exists?("/proc/xen/xsd_kva") + result = "xen0" + elsif FileTest.exists?("/proc/xen/capabilities") + result = "xenu" + end + end - if Facter::Util::Virtual.kvm? - result = Facter::Util::Virtual.kvm_type() - end + if Facter::Util::Virtual.kvm? + result = Facter::Util::Virtual.kvm_type() + end - if ["FreeBSD", "GNU/kFreeBSD"].include? Facter.value(:kernel) - result = "jail" if Facter::Util::Virtual.jail? - end + if ["FreeBSD", "GNU/kFreeBSD"].include? Facter.value(:kernel) + result = "jail" if Facter::Util::Virtual.jail? + end - if result == "physical" - output = Facter::Util::Resolution.exec('lspci') - if not output.nil? - output.each_line do |p| - # --- look for the vmware video card to determine if it is virtual => vmware. - # --- 00:0f.0 VGA compatible controller: VMware Inc [VMware SVGA II] PCI Display Adapter - result = "vmware" if p =~ /VM[wW]are/ - # --- look for virtualbox video card to determine if it is virtual => virtualbox. - # --- 00:02.0 VGA compatible controller: InnoTek Systemberatung GmbH VirtualBox Graphics Adapter - result = "virtualbox" if p =~ /VirtualBox/ - # --- look for pci vendor id used by Parallels video card - # --- 01:00.0 VGA compatible controller: Unknown device 1ab8:4005 - result = "parallels" if p =~ /1ab8:|[Pp]arallels/ - # --- look for pci vendor id used by Xen HVM device - # --- 00:03.0 Unassigned class [ff80]: XenSource, Inc. Xen Platform Device (rev 01) - result = "xenhvm" if p =~ /XenSource/ - # --- look for Hyper-V video card - # --- 00:08.0 VGA compatible controller: Microsoft Corporation Hyper-V virtual VGA - result = "hyperv" if p =~ /Microsoft Corporation Hyper-V/ - end - else - output = Facter::Util::Resolution.exec('dmidecode') - if not output.nil? - output.each_line do |pd| - result = "parallels" if pd =~ /Parallels/ - result = "vmware" if pd =~ /VMware/ - result = "virtualbox" if pd =~ /VirtualBox/ - result = "xenhvm" if pd =~ /HVM domU/ - result = "hyperv" if pd =~ /Product Name: Virtual Machine/ - end - elsif Facter.value(:kernel) == 'SunOS' - res = Facter::Util::Resolution.new('prtdiag') - res.timeout = 6 - res.setcode('prtdiag') - output = res.value - if not output.nil? - output.each_line do |pd| - result = "parallels" if pd =~ /Parallels/ - result = "vmware" if pd =~ /VMware/ - result = "virtualbox" if pd =~ /VirtualBox/ - result = "xenhvm" if pd =~ /HVM domU/ - end - end - end - end - - if output = Facter::Util::Resolution.exec("vmware -v") - result = output.sub(/(\S+)\s+(\S+).*/) { | text | "#{$1}_#{$2}"}.downcase + if result == "physical" + output = Facter::Util::Resolution.exec('lspci') + if not output.nil? + output.each_line do |p| + # --- look for the vmware video card to determine if it is virtual => vmware. + # --- 00:0f.0 VGA compatible controller: VMware Inc [VMware SVGA II] PCI Display Adapter + result = "vmware" if p =~ /VM[wW]are/ + # --- look for virtualbox video card to determine if it is virtual => virtualbox. + # --- 00:02.0 VGA compatible controller: InnoTek Systemberatung GmbH VirtualBox Graphics Adapter + result = "virtualbox" if p =~ /VirtualBox/ + # --- look for pci vendor id used by Parallels video card + # --- 01:00.0 VGA compatible controller: Unknown device 1ab8:4005 + result = "parallels" if p =~ /1ab8:|[Pp]arallels/ + # --- look for pci vendor id used by Xen HVM device + # --- 00:03.0 Unassigned class [ff80]: XenSource, Inc. Xen Platform Device (rev 01) + result = "xenhvm" if p =~ /XenSource/ + # --- look for Hyper-V video card + # --- 00:08.0 VGA compatible controller: Microsoft Corporation Hyper-V virtual VGA + result = "hyperv" if p =~ /Microsoft Corporation Hyper-V/ + end + else + output = Facter::Util::Resolution.exec('dmidecode') + if not output.nil? + output.each_line do |pd| + result = "parallels" if pd =~ /Parallels/ + result = "vmware" if pd =~ /VMware/ + result = "virtualbox" if pd =~ /VirtualBox/ + result = "xenhvm" if pd =~ /HVM domU/ + result = "hyperv" if pd =~ /Product Name: Virtual Machine/ + end + elsif Facter.value(:kernel) == 'SunOS' + res = Facter::Util::Resolution.new('prtdiag') + res.timeout = 6 + res.setcode('prtdiag') + output = res.value + if not output.nil? + output.each_line do |pd| + result = "parallels" if pd =~ /Parallels/ + result = "vmware" if pd =~ /VMware/ + result = "virtualbox" if pd =~ /VirtualBox/ + result = "xenhvm" if pd =~ /HVM domU/ end + end end - - result + end + + if output = Facter::Util::Resolution.exec("vmware -v") + result = output.sub(/(\S+)\s+(\S+).*/) { | text | "#{$1}_#{$2}"}.downcase + end end + + result + end end # Fact: is_virtual @@ -155,15 +155,15 @@ # Facter.add("is_virtual") do - confine :kernel => %w{Linux FreeBSD OpenBSD SunOS HP-UX Darwin GNU/kFreeBSD} + confine :kernel => %w{Linux FreeBSD OpenBSD SunOS HP-UX Darwin GNU/kFreeBSD} - setcode do - physical_types = %w{physical xen0 vmware_server vmware_workstation openvzhn} + setcode do + physical_types = %w{physical xen0 vmware_server vmware_workstation openvzhn} - if physical_types.include? Facter.value(:virtual) - "false" - else - "true" - end + if physical_types.include? Facter.value(:virtual) + "false" + else + "true" end + end end diff --git a/lib/facter/vlans.rb b/lib/facter/vlans.rb index 8c485a43b3..341d867c1f 100644 --- a/lib/facter/vlans.rb +++ b/lib/facter/vlans.rb @@ -10,9 +10,9 @@ require 'facter/util/vlans' - Facter.add("vlans") do - confine :kernel => :linux - setcode do - Facter::Util::Vlans.get_vlans - end - end +Facter.add("vlans") do + confine :kernel => :linux + setcode do + Facter::Util::Vlans.get_vlans + end +end diff --git a/spec/integration/facter_spec.rb b/spec/integration/facter_spec.rb index 8351de116a..cce148fd6c 100755 --- a/spec/integration/facter_spec.rb +++ b/spec/integration/facter_spec.rb @@ -3,25 +3,25 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') describe Facter do - before do - Facter.reset - end + before do + Facter.reset + end - after do - Facter.reset - end + after do + Facter.reset + end - it "should create a new collection if one does not exist" do - Facter.reset - coll = mock('coll') - Facter::Util::Collection.stubs(:new).returns coll - Facter.collection.should equal(coll) - Facter.reset - end + it "should create a new collection if one does not exist" do + Facter.reset + coll = mock('coll') + Facter::Util::Collection.stubs(:new).returns coll + Facter.collection.should equal(coll) + Facter.reset + end - it "should remove the collection when reset" do - old = Facter.collection - Facter.reset - Facter.collection.should_not equal(old) - end + it "should remove the collection when reset" do + old = Facter.collection + Facter.reset + Facter.collection.should_not equal(old) + end end diff --git a/spec/unit/memory_spec.rb b/spec/unit/memory_spec.rb index 175904f23c..28f1638f7d 100755 --- a/spec/unit/memory_spec.rb +++ b/spec/unit/memory_spec.rb @@ -42,7 +42,7 @@ swapusage = "vm.swapusage: total = 64.00M used = 0.00M free = 64.00M (encrypted)" swapusage =~ /\(encrypted\)/ - Facter.fact(:swapencrypted).value.should == true + Facter.fact(:swapencrypted).value.should == true end describe "on OpenBSD" do diff --git a/spec/unit/util/uptime_spec.rb b/spec/unit/util/uptime_spec.rb index 1c50d3185e..fe28d169dd 100755 --- a/spec/unit/util/uptime_spec.rb +++ b/spec/unit/util/uptime_spec.rb @@ -28,10 +28,10 @@ it "should use 'sysctl kern.boottime'" do if [1].pack("L") == [1].pack("V") # Determine endianness - sysctl_output_filename = 'sysctl_kern_boottime_little_endian' - else - sysctl_output_filename = 'sysctl_kern_boottime_big_endian' - end + sysctl_output_filename = 'sysctl_kern_boottime_little_endian' + else + sysctl_output_filename = 'sysctl_kern_boottime_big_endian' + end sysctl_output_file = File.join(SPECDIR, 'fixtures', 'uptime', sysctl_output_filename) # Aug 01 14:13:47 -0700 2010 Facter::Util::Uptime.stubs(:uptime_sysctl_cmd).returns("cat \"#{sysctl_output_file}\"") Time.stubs(:now).returns Time.parse("Aug 01 15:13:47 -0700 2010") # one hour later diff --git a/tasks/rake/changlog.rake b/tasks/rake/changlog.rake index 0c2f1d26c5..0dcd91c20f 100644 --- a/tasks/rake/changlog.rake +++ b/tasks/rake/changlog.rake @@ -1,15 +1,15 @@ desc "Create a ChangeLog based on git commits." task :changelog do - begin - gitc = %x{which git-changelog} - rescue - puts "This task needs the git-changelog binary - http://github.com/ReinH/git-changelog" - end + begin + gitc = %x{which git-changelog} + rescue + puts "This task needs the git-changelog binary - http://github.com/ReinH/git-changelog" + end - CHANGELOG_DIR = "#{Dir.pwd}" - mkdir(CHANGELOG_DIR) unless File.directory?(CHANGELOG_DIR) - change_body = `git-changelog --limit=99999` - File.open(File.join(CHANGELOG_DIR, "CHANGELOG"), 'w') do |f| - f << change_body - end + CHANGELOG_DIR = "#{Dir.pwd}" + mkdir(CHANGELOG_DIR) unless File.directory?(CHANGELOG_DIR) + change_body = `git-changelog --limit=99999` + File.open(File.join(CHANGELOG_DIR, "CHANGELOG"), 'w') do |f| + f << change_body + end end diff --git a/tasks/rake/ci.rake b/tasks/rake/ci.rake index e0e5aed8da..abd2754730 100644 --- a/tasks/rake/ci.rake +++ b/tasks/rake/ci.rake @@ -1,14 +1,14 @@ desc "Prep CI RSpec tests" task :ci_prep do - require 'rubygems' - begin - gem 'ci_reporter' - require 'ci/reporter/rake/rspec' - require 'ci/reporter/rake/test_unit' - ENV['CI_REPORTS'] = 'results' - rescue LoadError - puts 'Missing ci_reporter gem. You must have the ci_reporter gem installed to run the CI spec tests' - end + require 'rubygems' + begin + gem 'ci_reporter' + require 'ci/reporter/rake/rspec' + require 'ci/reporter/rake/test_unit' + ENV['CI_REPORTS'] = 'results' + rescue LoadError + puts 'Missing ci_reporter gem. You must have the ci_reporter gem installed to run the CI spec tests' + end end desc "Run the CI RSpec tests" diff --git a/tasks/rake/mail_patches.rake b/tasks/rake/mail_patches.rake index 1346faa316..7a9f2ea29f 100644 --- a/tasks/rake/mail_patches.rake +++ b/tasks/rake/mail_patches.rake @@ -1,48 +1,48 @@ desc "Send patch information to the puppet-dev list" task :mail_patches do - if Dir.glob("00*.patch").length > 0 - raise "Patches already exist matching '00*.patch'; clean up first" + if Dir.glob("00*.patch").length > 0 + raise "Patches already exist matching '00*.patch'; clean up first" + end + + unless %x{git status} =~ /On branch (.+)/ + raise "Could not get branch from 'git status'" + end + branch = $1 + + unless branch =~ %r{^([^\/]+)/([^\/]+)/([^\/]+)$} + raise "Branch name does not follow // model; cannot autodetect parent branch" + end + + type, parent, name = $1, $2, $3 + + # Create all of the patches + sh "git format-patch -C -M -s -n --subject-prefix='PATCH/facter' #{parent}..HEAD" + + # Add info to the patches + additional_info = "Local-branch: #{branch}\n" + files = Dir.glob("00*.patch") + files.each do |file| + contents = File.read(file) + contents.sub!(/^---\n/, "---\n#{additional_info}") + File.open(file, 'w') do |file_handle| + file_handle.print contents end + end - unless %x{git status} =~ /On branch (.+)/ - raise "Could not get branch from 'git status'" - end - branch = $1 - - unless branch =~ %r{^([^\/]+)/([^\/]+)/([^\/]+)$} - raise "Branch name does not follow // model; cannot autodetect parent branch" - end - - type, parent, name = $1, $2, $3 - - # Create all of the patches - sh "git format-patch -C -M -s -n --subject-prefix='PATCH/facter' #{parent}..HEAD" + # And then mail them out. - # Add info to the patches - additional_info = "Local-branch: #{branch}\n" - files = Dir.glob("00*.patch") - files.each do |file| - contents = File.read(file) - contents.sub!(/^---\n/, "---\n#{additional_info}") - File.open(file, 'w') do |file_handle| - file_handle.print contents - end - end - - # And then mail them out. - - # If we've got more than one patch, add --compose - if files.length > 1 - compose = "--compose" - subject = %Q{--subject "#{type} #{name} against #{parent}"} - else - compose = "" - subject = "" - end + # If we've got more than one patch, add --compose + if files.length > 1 + compose = "--compose" + subject = %Q{--subject "#{type} #{name} against #{parent}"} + else + compose = "" + subject = "" + end - # Now send the mail. - sh "git send-email #{compose} #{subject} --no-signed-off-by-cc --suppress-from --to puppet-dev@googlegroups.com 00*.patch" + # Now send the mail. + sh "git send-email #{compose} #{subject} --no-signed-off-by-cc --suppress-from --to puppet-dev@googlegroups.com 00*.patch" - # Finally, clean up the patches - sh "rm 00*.patch" + # Finally, clean up the patches + sh "rm 00*.patch" end diff --git a/tasks/rake/metrics.rake b/tasks/rake/metrics.rake index 63af552571..214cc996e9 100644 --- a/tasks/rake/metrics.rake +++ b/tasks/rake/metrics.rake @@ -1,6 +1,6 @@ begin - require 'metric_fu' + require 'metric_fu' rescue LoadError - # Metric-fu not installed - # http://metric-fu.rubyforge.org/ + # Metric-fu not installed + # http://metric-fu.rubyforge.org/ end diff --git a/tasks/rake/sign.rake b/tasks/rake/sign.rake index f0e9d83219..70351663b1 100644 --- a/tasks/rake/sign.rake +++ b/tasks/rake/sign.rake @@ -1,14 +1,14 @@ desc "Sign the package with the Puppet Labs release key" task :sign_packages do -version = Facter::FACTERVERSION + version = Facter::FACTERVERSION -# Sign package + # Sign package -sh "gpg --homedir $HOME/pl_release_key --detach-sign --output pkg/facter-#{version}.tar.gz.sign --armor pkg/facter-#{version}.tar.gz" + sh "gpg --homedir $HOME/pl_release_key --detach-sign --output pkg/facter-#{version}.tar.gz.sign --armor pkg/facter-#{version}.tar.gz" -# Sign gem + # Sign gem -sh "gpg --homedir $HOME/pl_release_key --detach-sign --output pkg/facter-#{version}.gem.sign --armor pkg/facter-#{version}.gem" + sh "gpg --homedir $HOME/pl_release_key --detach-sign --output pkg/facter-#{version}.gem.sign --armor pkg/facter-#{version}.gem" end From f0ccb5e7a92add944bc2a1eeb147950a2fb53547 Mon Sep 17 00:00:00 2001 From: Ken Barber Date: Mon, 3 Oct 2011 01:48:34 +0100 Subject: [PATCH 0680/3753] (#9555) Spec tests: Change all cases of tabs and 4 space indentation to 2 space indentation. Since 2 space indentation seems to be Puppets (and the ruby communities) standard this patch converts all incorrect indentation to 2 spaces. The fact that we were mixing the indentation was causing people to mix them within files - sometimes using 4 space, sometimes 2 space. This single change makes it consistent across all the code. This commit fixes any whitespace cleanups for the spec tests missed in the last commit. --- spec/unit/architecture_spec.rb | 80 ++-- spec/unit/facter_spec.rb | 440 ++++++++++---------- spec/unit/id_spec.rb | 32 +- spec/unit/interfaces_spec.rb | 12 +- spec/unit/memory_spec.rb | 210 +++++----- spec/unit/operatingsystem_spec.rb | 126 +++--- spec/unit/operatingsystemrelease_spec.rb | 86 ++-- spec/unit/selinux_spec.rb | 106 ++--- spec/unit/util/collection_spec.rb | 366 ++++++++--------- spec/unit/util/confine_spec.rb | 184 ++++----- spec/unit/util/fact_spec.rb | 192 ++++----- spec/unit/util/ip_spec.rb | 436 ++++++++++---------- spec/unit/util/loader_spec.rb | 386 +++++++++--------- spec/unit/util/macosx_spec.rb | 126 +++--- spec/unit/util/manufacturer_spec.rb | 214 +++++----- spec/unit/util/resolution_spec.rb | 470 +++++++++++----------- spec/unit/util/virtual_spec.rb | 334 +++++++-------- spec/unit/util/vlans_spec.rb | 12 +- spec/unit/virtual_spec.rb | 492 +++++++++++------------ 19 files changed, 2152 insertions(+), 2152 deletions(-) diff --git a/spec/unit/architecture_spec.rb b/spec/unit/architecture_spec.rb index 253de579b8..91e07e3dba 100755 --- a/spec/unit/architecture_spec.rb +++ b/spec/unit/architecture_spec.rb @@ -6,49 +6,49 @@ describe "Architecture fact" do - it "should default to the hardware model" do - Facter.fact(:hardwaremodel).stubs(:value).returns("NonSpecialCasedHW") - - Facter.fact(:architecture).value.should == "NonSpecialCasedHW" - end - - os_archs = Hash.new - os_archs = { - ["Debian","x86_64"] => "amd64", - ["Gentoo","x86_64"] => "amd64", - ["GNU/kFreeBSD","x86_64"] => "amd64", - ["Ubuntu","x86_64"] => "amd64", - ["Gentoo","i386"] => "x86", - ["Gentoo","i486"] => "x86", - ["Gentoo","i586"] => "x86", - ["Gentoo","i686"] => "x86", - ["Gentoo","pentium"] => "x86", - } - generic_archs = Hash.new - generic_archs = { - "i386" => "i386", - "i486" => "i386", - "i586" => "i386", - "i686" => "i386", - "pentium" => "i386", - } - - os_archs.each do |pair, result| - it "should be #{result} if os is #{pair[0]} and hardwaremodel is #{pair[1]}" do - Facter.fact(:operatingsystem).stubs(:value).returns(pair[0]) - Facter.fact(:hardwaremodel).stubs(:value).returns(pair[1]) - - Facter.fact(:architecture).value.should == result - end + it "should default to the hardware model" do + Facter.fact(:hardwaremodel).stubs(:value).returns("NonSpecialCasedHW") + + Facter.fact(:architecture).value.should == "NonSpecialCasedHW" + end + + os_archs = Hash.new + os_archs = { + ["Debian","x86_64"] => "amd64", + ["Gentoo","x86_64"] => "amd64", + ["GNU/kFreeBSD","x86_64"] => "amd64", + ["Ubuntu","x86_64"] => "amd64", + ["Gentoo","i386"] => "x86", + ["Gentoo","i486"] => "x86", + ["Gentoo","i586"] => "x86", + ["Gentoo","i686"] => "x86", + ["Gentoo","pentium"] => "x86", + } + generic_archs = Hash.new + generic_archs = { + "i386" => "i386", + "i486" => "i386", + "i586" => "i386", + "i686" => "i386", + "pentium" => "i386", + } + + os_archs.each do |pair, result| + it "should be #{result} if os is #{pair[0]} and hardwaremodel is #{pair[1]}" do + Facter.fact(:operatingsystem).stubs(:value).returns(pair[0]) + Facter.fact(:hardwaremodel).stubs(:value).returns(pair[1]) + + Facter.fact(:architecture).value.should == result end + end - generic_archs.each do |hw, result| - it "should be #{result} if hardwaremodel is #{hw}" do - Facter.fact(:hardwaremodel).stubs(:value).returns(hw) - Facter.fact(:operatingsystem).stubs(:value).returns("NonSpecialCasedOS") + generic_archs.each do |hw, result| + it "should be #{result} if hardwaremodel is #{hw}" do + Facter.fact(:hardwaremodel).stubs(:value).returns(hw) + Facter.fact(:operatingsystem).stubs(:value).returns("NonSpecialCasedOS") - Facter.fact(:architecture).value.should == result - end + Facter.fact(:architecture).value.should == result end + end end diff --git a/spec/unit/facter_spec.rb b/spec/unit/facter_spec.rb index aea638cda8..0b4612f673 100755 --- a/spec/unit/facter_spec.rb +++ b/spec/unit/facter_spec.rb @@ -4,268 +4,268 @@ describe Facter do - it "should have a version" do - Facter.version.should =~ /^[0-9]+(\.[0-9]+)*$/ + it "should have a version" do + Facter.version.should =~ /^[0-9]+(\.[0-9]+)*$/ + end + + it "should have a method for returning its collection" do + Facter.should respond_to(:collection) + end + + it "should cache the collection" do + Facter.collection.should equal(Facter.collection) + end + + it "should delegate the :flush method to the collection" do + Facter.collection.expects(:flush) + Facter.flush + end + + it "should delegate the :fact method to the collection" do + Facter.collection.expects(:fact) + Facter.fact + end + + it "should delegate the :list method to the collection" do + Facter.collection.expects(:list) + Facter.list + end + + it "should load all facts when listing" do + Facter.collection.expects(:load_all) + Facter.collection.stubs(:list) + Facter.list + end + + it "should delegate the :to_hash method to the collection" do + Facter.collection.expects(:to_hash) + Facter.to_hash + end + + it "should load all facts when calling :to_hash" do + Facter.collection.expects(:load_all) + Facter.collection.stubs(:to_hash) + Facter.to_hash + end + + it "should delegate the :value method to the collection" do + Facter.collection.expects(:value) + Facter.value + end + + it "should delegate the :each method to the collection" do + Facter.collection.expects(:each) + Facter.each + end + + it "should load all facts when calling :each" do + Facter.collection.expects(:load_all) + Facter.collection.stubs(:each) + Facter.each + end + + it "should yield to the block when using :each" do + Facter.collection.stubs(:load_all) + Facter.collection.stubs(:each).yields "foo" + result = [] + Facter.each { |f| result << f } + result.should == %w{foo} + end + + describe "when provided code as a string" do + it "should execute the code in the shell" do + Facter.add("shell_testing") do + setcode "echo yup" + end + + Facter["shell_testing"].value.should == "yup" end - - it "should have a method for returning its collection" do - Facter.should respond_to(:collection) + end + + describe "when asked for a fact as an undefined Facter class method" do + describe "and the collection is already initialized" do + it "should return the fact's value" do + Facter.collection + Facter.ipaddress.should == Facter['ipaddress'].value + end end - it "should cache the collection" do - Facter.collection.should equal(Facter.collection) + describe "and the collection has been just reset" do + it "should return the fact's value" do + Facter.reset + Facter.ipaddress.should == Facter['ipaddress'].value + end end + end - it "should delegate the :flush method to the collection" do - Facter.collection.expects(:flush) - Facter.flush - end + describe "when passed code as a block" do + it "should execute the provided block" do + Facter.add("block_testing") { setcode { "foo" } } - it "should delegate the :fact method to the collection" do - Facter.collection.expects(:fact) - Facter.fact + Facter["block_testing"].value.should == "foo" end + end - it "should delegate the :list method to the collection" do - Facter.collection.expects(:list) - Facter.list + describe Facter[:hostname] do + it "should have its ldapname set to 'cn'" do + Facter[:hostname].ldapname.should == "cn" end + end - it "should load all facts when listing" do - Facter.collection.expects(:load_all) - Facter.collection.stubs(:list) - Facter.list + describe Facter[:ipaddress] do + it "should have its ldapname set to 'iphostnumber'" do + Facter[:ipaddress].ldapname.should == "iphostnumber" end - - it "should delegate the :to_hash method to the collection" do - Facter.collection.expects(:to_hash) - Facter.to_hash - end - - it "should load all facts when calling :to_hash" do - Facter.collection.expects(:load_all) - Facter.collection.stubs(:to_hash) - Facter.to_hash + end + + # #33 Make sure we only get one mac address + it "should only return one mac address" do + Facter.value(:macaddress).should_not be_include(" ") + end + + it "should have a method for registering directories to search" do + Facter.should respond_to(:search) + end + + it "should have a method for returning the registered search directories" do + Facter.should respond_to(:search_path) + end + + it "should have a method to query debugging mode" do + Facter.should respond_to(:debugging?) + end + + it "should have a method to query timing mode" do + Facter.should respond_to(:timing?) + end + + it "should have a method to show timing information" do + Facter.should respond_to(:show_time) + end + + it "should have a method to warn" do + Facter.should respond_to(:warn) + end + + describe "when warning" do + it "should warn if debugging is enabled" do + Facter.debugging(true) + Kernel.stubs(:warn) + Kernel.expects(:warn).with('foo') + Facter.warn('foo') end - it "should delegate the :value method to the collection" do - Facter.collection.expects(:value) - Facter.value + it "should not warn if debugging is enabled but nil is passed" do + Facter.debugging(true) + Kernel.stubs(:warn) + Kernel.expects(:warn).never + Facter.warn(nil) end - it "should delegate the :each method to the collection" do - Facter.collection.expects(:each) - Facter.each + it "should not warn if debugging is enabled but an empyt string is passed" do + Facter.debugging(true) + Kernel.stubs(:warn) + Kernel.expects(:warn).never + Facter.warn('') end - it "should load all facts when calling :each" do - Facter.collection.expects(:load_all) - Facter.collection.stubs(:each) - Facter.each + it "should not warn if debugging is disabled" do + Facter.debugging(false) + Kernel.stubs(:warn) + Kernel.expects(:warn).never + Facter.warn('foo') end - it "should yield to the block when using :each" do - Facter.collection.stubs(:load_all) - Facter.collection.stubs(:each).yields "foo" - result = [] - Facter.each { |f| result << f } - result.should == %w{foo} + it "should warn for any given element for an array if debugging is enabled" do + Facter.debugging(true) + Kernel.stubs(:warn) + Kernel.expects(:warn).with('foo') + Kernel.expects(:warn).with('bar') + Facter.warn( ['foo','bar']) end - - describe "when provided code as a string" do - it "should execute the code in the shell" do - Facter.add("shell_testing") do - setcode "echo yup" - end - - Facter["shell_testing"].value.should == "yup" - end + end + + describe "when warning once" do + it "should only warn once" do + Kernel.stubs(:warnonce) + Kernel.expects(:warn).with('foo').once + Facter.warnonce('foo') + Facter.warnonce('foo') end - describe "when asked for a fact as an undefined Facter class method" do - describe "and the collection is already initialized" do - it "should return the fact's value" do - Facter.collection - Facter.ipaddress.should == Facter['ipaddress'].value - end - end - - describe "and the collection has been just reset" do - it "should return the fact's value" do - Facter.reset - Facter.ipaddress.should == Facter['ipaddress'].value - end - end + it "should not warnonce if nil is passed" do + Kernel.stubs(:warn) + Kernel.expects(:warnonce).never + Facter.warnonce(nil) end - describe "when passed code as a block" do - it "should execute the provided block" do - Facter.add("block_testing") { setcode { "foo" } } - - Facter["block_testing"].value.should == "foo" - end + it "should not warnonce if an empty string is passed" do + Kernel.stubs(:warn) + Kernel.expects(:warnonce).never + Facter.warnonce('') end + end - describe Facter[:hostname] do - it "should have its ldapname set to 'cn'" do - Facter[:hostname].ldapname.should == "cn" - end + describe "when setting debugging mode" do + it "should have debugging enabled using 1" do + Facter.debugging(1) + Facter.should be_debugging end - - describe Facter[:ipaddress] do - it "should have its ldapname set to 'iphostnumber'" do - Facter[:ipaddress].ldapname.should == "iphostnumber" - end + it "should have debugging enabled using true" do + Facter.debugging(true) + Facter.should be_debugging end - - # #33 Make sure we only get one mac address - it "should only return one mac address" do - Facter.value(:macaddress).should_not be_include(" ") + it "should have debugging enabled using any string except off" do + Facter.debugging('aaaaa') + Facter.should be_debugging end - - it "should have a method for registering directories to search" do - Facter.should respond_to(:search) + it "should have debugging disabled using 0" do + Facter.debugging(0) + Facter.should_not be_debugging end - - it "should have a method for returning the registered search directories" do - Facter.should respond_to(:search_path) + it "should have debugging disabled using false" do + Facter.debugging(false) + Facter.should_not be_debugging end - - it "should have a method to query debugging mode" do - Facter.should respond_to(:debugging?) + it "should have debugging disabled using the string 'off'" do + Facter.debugging('off') + Facter.should_not be_debugging end + end - it "should have a method to query timing mode" do - Facter.should respond_to(:timing?) + describe "when setting timing mode" do + it "should have timing enabled using 1" do + Facter.timing(1) + Facter.should be_timing end - - it "should have a method to show timing information" do - Facter.should respond_to(:show_time) + it "should have timing enabled using true" do + Facter.timing(true) + Facter.should be_timing end - - it "should have a method to warn" do - Facter.should respond_to(:warn) + it "should have timing disabled using 0" do + Facter.timing(0) + Facter.should_not be_timing end - - describe "when warning" do - it "should warn if debugging is enabled" do - Facter.debugging(true) - Kernel.stubs(:warn) - Kernel.expects(:warn).with('foo') - Facter.warn('foo') - end - - it "should not warn if debugging is enabled but nil is passed" do - Facter.debugging(true) - Kernel.stubs(:warn) - Kernel.expects(:warn).never - Facter.warn(nil) - end - - it "should not warn if debugging is enabled but an empyt string is passed" do - Facter.debugging(true) - Kernel.stubs(:warn) - Kernel.expects(:warn).never - Facter.warn('') - end - - it "should not warn if debugging is disabled" do - Facter.debugging(false) - Kernel.stubs(:warn) - Kernel.expects(:warn).never - Facter.warn('foo') - end - - it "should warn for any given element for an array if debugging is enabled" do - Facter.debugging(true) - Kernel.stubs(:warn) - Kernel.expects(:warn).with('foo') - Kernel.expects(:warn).with('bar') - Facter.warn( ['foo','bar']) - end + it "should have timing disabled using false" do + Facter.timing(false) + Facter.should_not be_timing end + end - describe "when warning once" do - it "should only warn once" do - Kernel.stubs(:warnonce) - Kernel.expects(:warn).with('foo').once - Facter.warnonce('foo') - Facter.warnonce('foo') - end - - it "should not warnonce if nil is passed" do - Kernel.stubs(:warn) - Kernel.expects(:warnonce).never - Facter.warnonce(nil) - end - - it "should not warnonce if an empty string is passed" do - Kernel.stubs(:warn) - Kernel.expects(:warnonce).never - Facter.warnonce('') - end - end + describe "when registering directories to search" do + after { Facter.instance_variable_set("@search_path", []) } - describe "when setting debugging mode" do - it "should have debugging enabled using 1" do - Facter.debugging(1) - Facter.should be_debugging - end - it "should have debugging enabled using true" do - Facter.debugging(true) - Facter.should be_debugging - end - it "should have debugging enabled using any string except off" do - Facter.debugging('aaaaa') - Facter.should be_debugging - end - it "should have debugging disabled using 0" do - Facter.debugging(0) - Facter.should_not be_debugging - end - it "should have debugging disabled using false" do - Facter.debugging(false) - Facter.should_not be_debugging - end - it "should have debugging disabled using the string 'off'" do - Facter.debugging('off') - Facter.should_not be_debugging - end + it "should allow registration of a directory" do + Facter.search "/my/dir" end - describe "when setting timing mode" do - it "should have timing enabled using 1" do - Facter.timing(1) - Facter.should be_timing - end - it "should have timing enabled using true" do - Facter.timing(true) - Facter.should be_timing - end - it "should have timing disabled using 0" do - Facter.timing(0) - Facter.should_not be_timing - end - it "should have timing disabled using false" do - Facter.timing(false) - Facter.should_not be_timing - end + it "should allow registration of multiple directories" do + Facter.search "/my/dir", "/other/dir" end - describe "when registering directories to search" do - after { Facter.instance_variable_set("@search_path", []) } - - it "should allow registration of a directory" do - Facter.search "/my/dir" - end - - it "should allow registration of multiple directories" do - Facter.search "/my/dir", "/other/dir" - end - - it "should return all registered directories when asked" do - Facter.search "/my/dir", "/other/dir" - Facter.search_path.should == %w{/my/dir /other/dir} - end + it "should return all registered directories when asked" do + Facter.search "/my/dir", "/other/dir" + Facter.search_path.should == %w{/my/dir /other/dir} end + end end diff --git a/spec/unit/id_spec.rb b/spec/unit/id_spec.rb index aab343d014..a3f0398e6b 100755 --- a/spec/unit/id_spec.rb +++ b/spec/unit/id_spec.rb @@ -4,25 +4,25 @@ describe "id fact" do - kernel = [ 'Linux', 'Darwin', 'windows', 'FreeBSD', 'OpenBSD', 'NetBSD', 'AIX', 'HP-UX' ] + kernel = [ 'Linux', 'Darwin', 'windows', 'FreeBSD', 'OpenBSD', 'NetBSD', 'AIX', 'HP-UX' ] - kernel.each do |k| - describe "with kernel reported as #{k}" do - it "should return the current user" do - Facter.fact(:kernel).stubs(:value).returns(k) - Facter::Util::Config.stubs(:is_windows?).returns(k == 'windows') - Facter::Util::Resolution.expects(:exec).once.with('whoami').returns 'bar' + kernel.each do |k| + describe "with kernel reported as #{k}" do + it "should return the current user" do + Facter.fact(:kernel).stubs(:value).returns(k) + Facter::Util::Config.stubs(:is_windows?).returns(k == 'windows') + Facter::Util::Resolution.expects(:exec).once.with('whoami').returns 'bar' - Facter.fact(:id).value.should == 'bar' - end - end + Facter.fact(:id).value.should == 'bar' + end end + end - it "should return the current user on Solaris" do - Facter::Util::Config.stubs(:is_windows?).returns(false) - Facter::Util::Resolution.stubs(:exec).with('uname -s').returns('SunOS') - Facter::Util::Resolution.expects(:exec).once.with('/usr/xpg4/bin/id -un').returns 'bar' + it "should return the current user on Solaris" do + Facter::Util::Config.stubs(:is_windows?).returns(false) + Facter::Util::Resolution.stubs(:exec).with('uname -s').returns('SunOS') + Facter::Util::Resolution.expects(:exec).once.with('/usr/xpg4/bin/id -un').returns 'bar' - Facter.fact(:id).value.should == 'bar' - end + Facter.fact(:id).value.should == 'bar' + end end diff --git a/spec/unit/interfaces_spec.rb b/spec/unit/interfaces_spec.rb index 28a8119a64..6ceca5e9ce 100755 --- a/spec/unit/interfaces_spec.rb +++ b/spec/unit/interfaces_spec.rb @@ -6,13 +6,13 @@ require 'facter/util/ip' describe "Per Interface IP facts" do - it "should replace the ':' in an interface list with '_'" do - # So we look supported - Facter.fact(:kernel).stubs(:value).returns("SunOS") + it "should replace the ':' in an interface list with '_'" do + # So we look supported + Facter.fact(:kernel).stubs(:value).returns("SunOS") - Facter::Util::IP.stubs(:get_interfaces).returns %w{eth0:1 eth1:2} - Facter.fact(:interfaces).value.should == %{eth0_1,eth1_2} - end + Facter::Util::IP.stubs(:get_interfaces).returns %w{eth0:1 eth1:2} + Facter.fact(:interfaces).value.should == %{eth0_1,eth1_2} + end it "should replace non-alphanumerics in an interface list with '_'" do Facter.fact(:kernel).stubs(:value).returns("windows") diff --git a/spec/unit/memory_spec.rb b/spec/unit/memory_spec.rb index 28f1638f7d..87a6f0cdd3 100755 --- a/spec/unit/memory_spec.rb +++ b/spec/unit/memory_spec.rb @@ -5,146 +5,146 @@ require 'facter' describe "Memory facts" do - before do - # We need these facts loaded, but they belong to a file with a - # different name, so load the file explicitly. - Facter.collection.loader.load(:memory) - end + before do + # We need these facts loaded, but they belong to a file with a + # different name, so load the file explicitly. + Facter.collection.loader.load(:memory) + end - after do - Facter.clear - end + after do + Facter.clear + end - it "should return the current swap size" do + it "should return the current swap size" do - Facter.fact(:kernel).stubs(:value).returns("Darwin") - Facter::Util::Resolution.stubs(:exec).with('sysctl vm.swapusage').returns("vm.swapusage: total = 64.00M used = 0.00M free = 64.00M (encrypted)") - swapusage = "vm.swapusage: total = 64.00M used = 0.00M free = 64.00M (encrypted)" + Facter.fact(:kernel).stubs(:value).returns("Darwin") + Facter::Util::Resolution.stubs(:exec).with('sysctl vm.swapusage').returns("vm.swapusage: total = 64.00M used = 0.00M free = 64.00M (encrypted)") + swapusage = "vm.swapusage: total = 64.00M used = 0.00M free = 64.00M (encrypted)" - if swapusage =~ /total = (\S+).*/ - Facter.fact(:swapfree).value.should == $1 - end + if swapusage =~ /total = (\S+).*/ + Facter.fact(:swapfree).value.should == $1 end + end - it "should return the current swap free" do - Facter.fact(:kernel).stubs(:value).returns("Darwin") - Facter::Util::Resolution.stubs(:exec).with('sysctl vm.swapusage').returns("vm.swapusage: total = 64.00M used = 0.00M free = 64.00M (encrypted)") - swapusage = "vm.swapusage: total = 64.00M used = 0.00M free = 64.00M (encrypted)" + it "should return the current swap free" do + Facter.fact(:kernel).stubs(:value).returns("Darwin") + Facter::Util::Resolution.stubs(:exec).with('sysctl vm.swapusage').returns("vm.swapusage: total = 64.00M used = 0.00M free = 64.00M (encrypted)") + swapusage = "vm.swapusage: total = 64.00M used = 0.00M free = 64.00M (encrypted)" - if swapusage =~ /free = (\S+).*/ - Facter.fact(:swapfree).value.should == $1 - end + if swapusage =~ /free = (\S+).*/ + Facter.fact(:swapfree).value.should == $1 end + end - it "should return whether swap is encrypted" do - Facter.fact(:kernel).stubs(:value).returns("Darwin") - Facter::Util::Resolution.stubs(:exec).with('sysctl vm.swapusage').returns("vm.swapusage: total = 64.00M used = 0.00M free = 64.00M (encrypted)") - swapusage = "vm.swapusage: total = 64.00M used = 0.00M free = 64.00M (encrypted)" + it "should return whether swap is encrypted" do + Facter.fact(:kernel).stubs(:value).returns("Darwin") + Facter::Util::Resolution.stubs(:exec).with('sysctl vm.swapusage').returns("vm.swapusage: total = 64.00M used = 0.00M free = 64.00M (encrypted)") + swapusage = "vm.swapusage: total = 64.00M used = 0.00M free = 64.00M (encrypted)" - swapusage =~ /\(encrypted\)/ - Facter.fact(:swapencrypted).value.should == true - end + swapusage =~ /\(encrypted\)/ + Facter.fact(:swapencrypted).value.should == true + end - describe "on OpenBSD" do - before :each do - Facter.clear - Facter.fact(:kernel).stubs(:value).returns("OpenBSD") + describe "on OpenBSD" do + before :each do + Facter.clear + Facter.fact(:kernel).stubs(:value).returns("OpenBSD") - swapusage = "total: 148342k bytes allocated = 0k used, 148342k available" - Facter::Util::Resolution.stubs(:exec).with('swapctl -s').returns(swapusage) + swapusage = "total: 148342k bytes allocated = 0k used, 148342k available" + Facter::Util::Resolution.stubs(:exec).with('swapctl -s').returns(swapusage) - vmstat = < "/etc/redhat-release", + "RedHat" => "/etc/redhat-release", + "Scientific" => "/etc/redhat-release", + "Fedora" => "/etc/fedora-release", + "MeeGo" => "/etc/meego-release", + "OEL" => "/etc/enterprise-release", + "oel" => "/etc/enterprise-release", + "OVS" => "/etc/ovs-release", + "ovs" => "/etc/ovs-release", + "OracleLinux" => "/etc/oracle-release", + } + + test_cases.each do |system, file| + describe "with operatingsystem reported as #{system.inspect}" do + it "should read the #{file.inspect} file" do + Facter.fact(:operatingsystem).stubs(:value).returns(system) + + File.expects(:open).with(file, "r").at_least(1) - after do - Facter.clear + Facter.fact(:operatingsystemrelease).value + end end + end - test_cases = { - "CentOS" => "/etc/redhat-release", - "RedHat" => "/etc/redhat-release", - "Scientific" => "/etc/redhat-release", - "Fedora" => "/etc/fedora-release", - "MeeGo" => "/etc/meego-release", - "OEL" => "/etc/enterprise-release", - "oel" => "/etc/enterprise-release", - "OVS" => "/etc/ovs-release", - "ovs" => "/etc/ovs-release", - "OracleLinux" => "/etc/oracle-release", - } - - test_cases.each do |system, file| - describe "with operatingsystem reported as #{system.inspect}" do - it "should read the #{file.inspect} file" do - Facter.fact(:operatingsystem).stubs(:value).returns(system) - - File.expects(:open).with(file, "r").at_least(1) - - Facter.fact(:operatingsystemrelease).value - end - end - end + it "for VMWareESX it should run the vmware -v command" do + Facter.fact(:kernel).stubs(:value).returns("VMkernel") + Facter.fact(:kernelrelease).stubs(:value).returns("4.1.0") + Facter.fact(:operatingsystem).stubs(:value).returns("VMwareESX") - it "for VMWareESX it should run the vmware -v command" do - Facter.fact(:kernel).stubs(:value).returns("VMkernel") - Facter.fact(:kernelrelease).stubs(:value).returns("4.1.0") - Facter.fact(:operatingsystem).stubs(:value).returns("VMwareESX") + Facter::Util::Resolution.stubs(:exec).with('vmware -v').returns('foo') - Facter::Util::Resolution.stubs(:exec).with('vmware -v').returns('foo') + Facter.fact(:operatingsystemrelease).value + end - Facter.fact(:operatingsystemrelease).value - end - - it "for Alpine it should use the contents of /etc/alpine-release" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:operatingsystem).stubs(:value).returns("Alpine") + it "for Alpine it should use the contents of /etc/alpine-release" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:operatingsystem).stubs(:value).returns("Alpine") - File.expects(:read).with("/etc/alpine-release").returns("foo") + File.expects(:read).with("/etc/alpine-release").returns("foo") - Facter.fact(:operatingsystemrelease).value.should == "foo" - end + Facter.fact(:operatingsystemrelease).value.should == "foo" + end end diff --git a/spec/unit/selinux_spec.rb b/spec/unit/selinux_spec.rb index 5d098976e3..25c75da067 100755 --- a/spec/unit/selinux_spec.rb +++ b/spec/unit/selinux_spec.rb @@ -7,84 +7,84 @@ describe "SELinux facts" do - after do - Facter.clear - end + after do + Facter.clear + end - it "should return true if SELinux enabled" do - Facter.fact(:kernel).stubs(:value).returns("Linux") + it "should return true if SELinux enabled" do + Facter.fact(:kernel).stubs(:value).returns("Linux") - FileTest.stubs(:exists?).returns false - File.stubs(:read).with("/proc/self/attr/current").returns("notkernel") + FileTest.stubs(:exists?).returns false + File.stubs(:read).with("/proc/self/attr/current").returns("notkernel") - FileTest.expects(:exists?).with("/selinux/enforce").returns true - FileTest.expects(:exists?).with("/proc/self/attr/current").returns true - File.expects(:read).with("/proc/self/attr/current").returns("kernel") + FileTest.expects(:exists?).with("/selinux/enforce").returns true + FileTest.expects(:exists?).with("/proc/self/attr/current").returns true + File.expects(:read).with("/proc/self/attr/current").returns("kernel") - Facter.fact(:selinux).value.should == "true" - end + Facter.fact(:selinux).value.should == "true" + end - it "should return true if SELinux policy enabled" do - Facter.fact(:selinux).stubs(:value).returns("true") + it "should return true if SELinux policy enabled" do + Facter.fact(:selinux).stubs(:value).returns("true") - FileTest.stubs(:exists?).returns false - File.stubs(:read).with("/selinux/enforce").returns("0") + FileTest.stubs(:exists?).returns false + File.stubs(:read).with("/selinux/enforce").returns("0") - FileTest.expects(:exists?).with("/selinux/enforce").returns true - File.expects(:read).with("/selinux/enforce").returns("1") + FileTest.expects(:exists?).with("/selinux/enforce").returns true + File.expects(:read).with("/selinux/enforce").returns("1") - Facter.fact(:selinux_enforced).value.should == "true" - end + Facter.fact(:selinux_enforced).value.should == "true" + end - it "should return an SELinux policy version" do - Facter.fact(:selinux).stubs(:value).returns("true") - FileTest.stubs(:exists?).with("/proc/self/mountinfo").returns false + it "should return an SELinux policy version" do + Facter.fact(:selinux).stubs(:value).returns("true") + FileTest.stubs(:exists?).with("/proc/self/mountinfo").returns false - File.stubs(:read).with("/selinux/policyvers").returns("") + File.stubs(:read).with("/selinux/policyvers").returns("") - File.expects(:read).with("/selinux/policyvers").returns("1") + File.expects(:read).with("/selinux/policyvers").returns("1") - Facter.fact(:selinux_policyversion).value.should == "1" - end + Facter.fact(:selinux_policyversion).value.should == "1" + end - it "should return the SELinux current mode" do - Facter.fact(:selinux).stubs(:value).returns("true") + it "should return the SELinux current mode" do + Facter.fact(:selinux).stubs(:value).returns("true") - sample_output_file = File.dirname(__FILE__) + '/data/selinux_sestatus' - selinux_sestatus = File.read(sample_output_file) + sample_output_file = File.dirname(__FILE__) + '/data/selinux_sestatus' + selinux_sestatus = File.read(sample_output_file) - Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/sestatus').returns(selinux_sestatus) + Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/sestatus').returns(selinux_sestatus) - Facter.fact(:selinux_current_mode).value.should == "permissive" - end + Facter.fact(:selinux_current_mode).value.should == "permissive" + end - it "should return the SELinux mode from the configuration file" do - Facter.fact(:selinux).stubs(:value).returns("true") + it "should return the SELinux mode from the configuration file" do + Facter.fact(:selinux).stubs(:value).returns("true") - sample_output_file = File.dirname(__FILE__) + '/data/selinux_sestatus' - selinux_sestatus = File.read(sample_output_file) + sample_output_file = File.dirname(__FILE__) + '/data/selinux_sestatus' + selinux_sestatus = File.read(sample_output_file) - Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/sestatus').returns(selinux_sestatus) + Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/sestatus').returns(selinux_sestatus) - Facter.fact(:selinux_config_mode).value.should == "permissive" - end + Facter.fact(:selinux_config_mode).value.should == "permissive" + end - it "should return the SELinux configuration file policy" do - Facter.fact(:selinux).stubs(:value).returns("true") + it "should return the SELinux configuration file policy" do + Facter.fact(:selinux).stubs(:value).returns("true") - sample_output_file = File.dirname(__FILE__) + '/data/selinux_sestatus' - selinux_sestatus = File.read(sample_output_file) + sample_output_file = File.dirname(__FILE__) + '/data/selinux_sestatus' + selinux_sestatus = File.read(sample_output_file) - Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/sestatus').returns(selinux_sestatus) + Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/sestatus').returns(selinux_sestatus) - Facter.fact(:selinux_config_policy).value.should == "targeted" - end + Facter.fact(:selinux_config_policy).value.should == "targeted" + end - it "should ensure legacy selinux_mode facts returns same value as selinux_config_policy fact" do - Facter.fact(:selinux).stubs(:value).returns("true") + it "should ensure legacy selinux_mode facts returns same value as selinux_config_policy fact" do + Facter.fact(:selinux).stubs(:value).returns("true") - Facter.fact(:selinux_config_policy).stubs(:value).returns("targeted") + Facter.fact(:selinux_config_policy).stubs(:value).returns("targeted") - Facter.fact(:selinux_mode).value.should == "targeted" - end + Facter.fact(:selinux_mode).value.should == "targeted" + end end diff --git a/spec/unit/util/collection_spec.rb b/spec/unit/util/collection_spec.rb index 86b602f32b..c6767278e2 100755 --- a/spec/unit/util/collection_spec.rb +++ b/spec/unit/util/collection_spec.rb @@ -5,251 +5,251 @@ require 'facter/util/collection' describe Facter::Util::Collection do - it "should have a method for adding facts" do - Facter::Util::Collection.new.should respond_to(:add) - end + it "should have a method for adding facts" do + Facter::Util::Collection.new.should respond_to(:add) + end - it "should have a method for returning a loader" do - Facter::Util::Collection.new.should respond_to(:loader) - end + it "should have a method for returning a loader" do + Facter::Util::Collection.new.should respond_to(:loader) + end - it "should use an instance of the Loader class as its loader" do - Facter::Util::Collection.new.loader.should be_instance_of(Facter::Util::Loader) - end + it "should use an instance of the Loader class as its loader" do + Facter::Util::Collection.new.loader.should be_instance_of(Facter::Util::Loader) + end - it "should cache its loader" do - coll = Facter::Util::Collection.new - coll.loader.should equal(coll.loader) - end + it "should cache its loader" do + coll = Facter::Util::Collection.new + coll.loader.should equal(coll.loader) + end - it "should have a method for loading all facts" do - Facter::Util::Collection.new.should respond_to(:load_all) + it "should have a method for loading all facts" do + Facter::Util::Collection.new.should respond_to(:load_all) + end + + it "should delegate its load_all method to its loader" do + coll = Facter::Util::Collection.new + coll.loader.expects(:load_all) + coll.load_all + end + + describe "when adding facts" do + before do + @coll = Facter::Util::Collection.new end - it "should delegate its load_all method to its loader" do - coll = Facter::Util::Collection.new - coll.loader.expects(:load_all) - coll.load_all + it "should create a new fact if no fact with the same name already exists" do + fact = mock 'fact' + Facter::Util::Fact.expects(:new).with { |name, *args| name == :myname }.returns fact + + @coll.add(:myname) end - describe "when adding facts" do - before do - @coll = Facter::Util::Collection.new - end + it "should accept options" do + @coll.add(:myname, :ldapname => "whatever") { } + end - it "should create a new fact if no fact with the same name already exists" do - fact = mock 'fact' - Facter::Util::Fact.expects(:new).with { |name, *args| name == :myname }.returns fact + it "should set any appropriate options on the fact instances" do + # Use a real fact instance, because we're using respond_to? + fact = Facter::Util::Fact.new(:myname) + fact.expects(:ldapname=).with("testing") + Facter::Util::Fact.expects(:new).with(:myname).returns fact - @coll.add(:myname) - end + @coll.add(:myname, :ldapname => "testing") + end - it "should accept options" do - @coll.add(:myname, :ldapname => "whatever") { } - end + it "should set appropriate options on the resolution instance" do + fact = Facter::Util::Fact.new(:myname) + Facter::Util::Fact.expects(:new).with(:myname).returns fact - it "should set any appropriate options on the fact instances" do - # Use a real fact instance, because we're using respond_to? - fact = Facter::Util::Fact.new(:myname) - fact.expects(:ldapname=).with("testing") - Facter::Util::Fact.expects(:new).with(:myname).returns fact + resolve = Facter::Util::Resolution.new(:myname) {} + fact.expects(:add).returns resolve - @coll.add(:myname, :ldapname => "testing") - end + @coll.add(:myname, :timeout => "myval") {} + end - it "should set appropriate options on the resolution instance" do - fact = Facter::Util::Fact.new(:myname) - Facter::Util::Fact.expects(:new).with(:myname).returns fact + it "should not pass fact-specific options to resolutions" do + fact = Facter::Util::Fact.new(:myname) + Facter::Util::Fact.expects(:new).with(:myname).returns fact - resolve = Facter::Util::Resolution.new(:myname) {} - fact.expects(:add).returns resolve + resolve = Facter::Util::Resolution.new(:myname) {} + fact.expects(:add).returns resolve - @coll.add(:myname, :timeout => "myval") {} - end + fact.expects(:ldapname=).with("foo") + resolve.expects(:timeout=).with("myval") - it "should not pass fact-specific options to resolutions" do - fact = Facter::Util::Fact.new(:myname) - Facter::Util::Fact.expects(:new).with(:myname).returns fact + @coll.add(:myname, :timeout => "myval", :ldapname => "foo") {} + end - resolve = Facter::Util::Resolution.new(:myname) {} - fact.expects(:add).returns resolve + it "should fail if invalid options are provided" do + lambda { @coll.add(:myname, :foo => :bar) }.should raise_error(ArgumentError) + end - fact.expects(:ldapname=).with("foo") - resolve.expects(:timeout=).with("myval") + describe "and a block is provided" do + it "should use the block to add a resolution to the fact" do + fact = mock 'fact' + Facter::Util::Fact.expects(:new).returns fact - @coll.add(:myname, :timeout => "myval", :ldapname => "foo") {} - end + fact.expects(:add) - it "should fail if invalid options are provided" do - lambda { @coll.add(:myname, :foo => :bar) }.should raise_error(ArgumentError) - end + @coll.add(:myname) {} + end + end + end - describe "and a block is provided" do - it "should use the block to add a resolution to the fact" do - fact = mock 'fact' - Facter::Util::Fact.expects(:new).returns fact + it "should have a method for retrieving facts by name" do + Facter::Util::Collection.new.should respond_to(:fact) + end - fact.expects(:add) + describe "when retrieving facts" do + before do + @coll = Facter::Util::Collection.new - @coll.add(:myname) {} - end - end + @fact = @coll.add("YayNess") end - it "should have a method for retrieving facts by name" do - Facter::Util::Collection.new.should respond_to(:fact) + it "should return the fact instance specified by the name" do + @coll.fact("YayNess").should equal(@fact) end - describe "when retrieving facts" do - before do - @coll = Facter::Util::Collection.new + it "should be case-insensitive" do + @coll.fact("yayness").should equal(@fact) + end - @fact = @coll.add("YayNess") - end + it "should treat strings and symbols equivalently" do + @coll.fact(:yayness).should equal(@fact) + end - it "should return the fact instance specified by the name" do - @coll.fact("YayNess").should equal(@fact) - end + it "should use its loader to try to load the fact if no fact can be found" do + @coll.loader.expects(:load).with(:testing) + @coll.fact("testing") + end - it "should be case-insensitive" do - @coll.fact("yayness").should equal(@fact) - end + it "should return nil if it cannot find or load the fact" do + @coll.loader.expects(:load).with(:testing) + @coll.fact("testing").should be_nil + end + end - it "should treat strings and symbols equivalently" do - @coll.fact(:yayness).should equal(@fact) - end + it "should have a method for returning a fact's value" do + Facter::Util::Collection.new.should respond_to(:value) + end - it "should use its loader to try to load the fact if no fact can be found" do - @coll.loader.expects(:load).with(:testing) - @coll.fact("testing") - end + describe "when returning a fact's value" do + before do + @coll = Facter::Util::Collection.new + @fact = @coll.add("YayNess") - it "should return nil if it cannot find or load the fact" do - @coll.loader.expects(:load).with(:testing) - @coll.fact("testing").should be_nil - end + @fact.stubs(:value).returns "result" end - it "should have a method for returning a fact's value" do - Facter::Util::Collection.new.should respond_to(:value) + it "should use the 'fact' method to retrieve the fact" do + @coll.expects(:fact).with(:yayness).returns @fact + @coll.value(:yayness) end - describe "when returning a fact's value" do - before do - @coll = Facter::Util::Collection.new - @fact = @coll.add("YayNess") + it "should return the result of calling :value on the fact" do + @fact.expects(:value).returns "result" - @fact.stubs(:value).returns "result" - end + @coll.value("YayNess").should == "result" + end - it "should use the 'fact' method to retrieve the fact" do - @coll.expects(:fact).with(:yayness).returns @fact - @coll.value(:yayness) - end + it "should be case-insensitive" do + @coll.value("yayness").should_not be_nil + end - it "should return the result of calling :value on the fact" do - @fact.expects(:value).returns "result" + it "should treat strings and symbols equivalently" do + @coll.value(:yayness).should_not be_nil + end + end - @coll.value("YayNess").should == "result" - end + it "should return the fact's value when the array index method is used" do + @coll = Facter::Util::Collection.new + @coll.expects(:value).with("myfact").returns "foo" + @coll["myfact"].should == "foo" + end - it "should be case-insensitive" do - @coll.value("yayness").should_not be_nil - end + it "should have a method for flushing all facts" do + @coll = Facter::Util::Collection.new + @fact = @coll.add("YayNess") - it "should treat strings and symbols equivalently" do - @coll.value(:yayness).should_not be_nil - end - end + @fact.expects(:flush) - it "should return the fact's value when the array index method is used" do - @coll = Facter::Util::Collection.new - @coll.expects(:value).with("myfact").returns "foo" - @coll["myfact"].should == "foo" - end + @coll.flush + end - it "should have a method for flushing all facts" do - @coll = Facter::Util::Collection.new - @fact = @coll.add("YayNess") + it "should have a method that returns all fact names" do + @coll = Facter::Util::Collection.new + @coll.add(:one) + @coll.add(:two) - @fact.expects(:flush) + @coll.list.sort { |a,b| a.to_s <=> b.to_s }.should == [:one, :two] + end - @coll.flush - end + it "should have a method for returning a hash of fact values" do + Facter::Util::Collection.new.should respond_to(:to_hash) + end - it "should have a method that returns all fact names" do - @coll = Facter::Util::Collection.new - @coll.add(:one) - @coll.add(:two) + describe "when returning a hash of values" do + before do + @coll = Facter::Util::Collection.new + @fact = @coll.add(:one) + @fact.stubs(:value).returns "me" + end - @coll.list.sort { |a,b| a.to_s <=> b.to_s }.should == [:one, :two] + it "should return a hash of fact names and values with the fact names as strings" do + @coll.to_hash.should == {"one" => "me"} end - it "should have a method for returning a hash of fact values" do - Facter::Util::Collection.new.should respond_to(:to_hash) + it "should not include facts that did not return a value" do + f = @coll.add(:two) + f.stubs(:value).returns nil + @coll.to_hash.should_not be_include(:two) end + end - describe "when returning a hash of values" do - before do - @coll = Facter::Util::Collection.new - @fact = @coll.add(:one) - @fact.stubs(:value).returns "me" - end + it "should have a method for iterating over all facts" do + Facter::Util::Collection.new.should respond_to(:each) + end - it "should return a hash of fact names and values with the fact names as strings" do - @coll.to_hash.should == {"one" => "me"} - end + it "should include Enumerable" do + Facter::Util::Collection.ancestors.should be_include(Enumerable) + end - it "should not include facts that did not return a value" do - f = @coll.add(:two) - f.stubs(:value).returns nil - @coll.to_hash.should_not be_include(:two) - end + describe "when iterating over facts" do + before do + @coll = Facter::Util::Collection.new + @one = @coll.add(:one) + @two = @coll.add(:two) end - it "should have a method for iterating over all facts" do - Facter::Util::Collection.new.should respond_to(:each) + it "should yield each fact name and the fact value" do + @one.stubs(:value).returns "ONE" + @two.stubs(:value).returns "TWO" + facts = {} + @coll.each do |fact, value| + facts[fact] = value + end + facts.should == {"one" => "ONE", "two" => "TWO"} end - it "should include Enumerable" do - Facter::Util::Collection.ancestors.should be_include(Enumerable) + it "should convert the fact name to a string" do + @one.stubs(:value).returns "ONE" + @two.stubs(:value).returns "TWO" + facts = {} + @coll.each do |fact, value| + fact.should be_instance_of(String) + end end - describe "when iterating over facts" do - before do - @coll = Facter::Util::Collection.new - @one = @coll.add(:one) - @two = @coll.add(:two) - end - - it "should yield each fact name and the fact value" do - @one.stubs(:value).returns "ONE" - @two.stubs(:value).returns "TWO" - facts = {} - @coll.each do |fact, value| - facts[fact] = value - end - facts.should == {"one" => "ONE", "two" => "TWO"} - end - - it "should convert the fact name to a string" do - @one.stubs(:value).returns "ONE" - @two.stubs(:value).returns "TWO" - facts = {} - @coll.each do |fact, value| - fact.should be_instance_of(String) - end - end - - it "should only yield facts that have values" do - @one.stubs(:value).returns "ONE" - @two.stubs(:value).returns nil - facts = {} - @coll.each do |fact, value| - facts[fact] = value - end + it "should only yield facts that have values" do + @one.stubs(:value).returns "ONE" + @two.stubs(:value).returns nil + facts = {} + @coll.each do |fact, value| + facts[fact] = value + end - facts.should_not be_include("two") - end + facts.should_not be_include("two") end + end end diff --git a/spec/unit/util/confine_spec.rb b/spec/unit/util/confine_spec.rb index 147c70d405..5e5706b1ad 100755 --- a/spec/unit/util/confine_spec.rb +++ b/spec/unit/util/confine_spec.rb @@ -8,133 +8,133 @@ include Facter::Util::Values describe Facter::Util::Confine do - it "should require a fact name" do - Facter::Util::Confine.new("yay", true).fact.should == "yay" + it "should require a fact name" do + Facter::Util::Confine.new("yay", true).fact.should == "yay" + end + + it "should accept a value specified individually" do + Facter::Util::Confine.new("yay", "test").values.should == ["test"] + end + + it "should accept multiple values specified at once" do + Facter::Util::Confine.new("yay", "test", "other").values.should == ["test", "other"] + end + + it "should fail if no fact name is provided" do + lambda { Facter::Util::Confine.new(nil, :test) }.should raise_error(ArgumentError) + end + + it "should fail if no values were provided" do + lambda { Facter::Util::Confine.new("yay") }.should raise_error(ArgumentError) + end + + it "should have a method for testing whether it matches" do + Facter::Util::Confine.new("yay", :test).should respond_to(:true?) + end + + describe "when evaluating" do + before do + @confine = Facter::Util::Confine.new("yay", "one", "two", "Four", :xy, true, 1, [3,4]) + @fact = mock 'fact' + Facter.stubs(:[]).returns @fact end - it "should accept a value specified individually" do - Facter::Util::Confine.new("yay", "test").values.should == ["test"] - end + it "should return false if the fact does not exist" do + Facter.expects(:[]).with("yay").returns nil - it "should accept multiple values specified at once" do - Facter::Util::Confine.new("yay", "test", "other").values.should == ["test", "other"] + @confine.true?.should be_false end - it "should fail if no fact name is provided" do - lambda { Facter::Util::Confine.new(nil, :test) }.should raise_error(ArgumentError) - end + it "should use the returned fact to get the value" do + Facter.expects(:[]).with("yay").returns @fact - it "should fail if no values were provided" do - lambda { Facter::Util::Confine.new("yay") }.should raise_error(ArgumentError) - end + @fact.expects(:value).returns nil - it "should have a method for testing whether it matches" do - Facter::Util::Confine.new("yay", :test).should respond_to(:true?) + @confine.true? end - describe "when evaluating" do - before do - @confine = Facter::Util::Confine.new("yay", "one", "two", "Four", :xy, true, 1, [3,4]) - @fact = mock 'fact' - Facter.stubs(:[]).returns @fact - end - - it "should return false if the fact does not exist" do - Facter.expects(:[]).with("yay").returns nil - - @confine.true?.should be_false - end - - it "should use the returned fact to get the value" do - Facter.expects(:[]).with("yay").returns @fact + it "should return false if the fact has no value" do + @fact.stubs(:value).returns nil - @fact.expects(:value).returns nil - - @confine.true? - end - - it "should return false if the fact has no value" do - @fact.stubs(:value).returns nil - - @confine.true?.should be_false - end + @confine.true?.should be_false + end - it "should return true if any of the provided values matches the fact's value" do - @fact.stubs(:value).returns "two" + it "should return true if any of the provided values matches the fact's value" do + @fact.stubs(:value).returns "two" - @confine.true?.should be_true - end + @confine.true?.should be_true + end - it "should return true if any of the provided symbol values matches the fact's value" do - @fact.stubs(:value).returns :xy + it "should return true if any of the provided symbol values matches the fact's value" do + @fact.stubs(:value).returns :xy - @confine.true?.should be_true - end + @confine.true?.should be_true + end - it "should return true if any of the provided integer values matches the fact's value" do - @fact.stubs(:value).returns 1 + it "should return true if any of the provided integer values matches the fact's value" do + @fact.stubs(:value).returns 1 - @confine.true?.should be_true - end + @confine.true?.should be_true + end - it "should return true if any of the provided boolan values matches the fact's value" do - @fact.stubs(:value).returns true + it "should return true if any of the provided boolan values matches the fact's value" do + @fact.stubs(:value).returns true - @confine.true?.should be_true - end + @confine.true?.should be_true + end - it "should return true if any of the provided array values matches the fact's value" do - @fact.stubs(:value).returns [3,4] + it "should return true if any of the provided array values matches the fact's value" do + @fact.stubs(:value).returns [3,4] - @confine.true?.should be_true - end + @confine.true?.should be_true + end - it "should return true if any of the provided symbol values matches the fact's string value" do - @fact.stubs(:value).returns :one + it "should return true if any of the provided symbol values matches the fact's string value" do + @fact.stubs(:value).returns :one - @confine.true?.should be_true - end + @confine.true?.should be_true + end - it "should return true if any of the provided string values matches case-insensitive the fact's value" do - @fact.stubs(:value).returns "four" + it "should return true if any of the provided string values matches case-insensitive the fact's value" do + @fact.stubs(:value).returns "four" - @confine.true?.should be_true - end + @confine.true?.should be_true + end - it "should return true if any of the provided symbol values matches case-insensitive the fact's string value" do - @fact.stubs(:value).returns :four + it "should return true if any of the provided symbol values matches case-insensitive the fact's string value" do + @fact.stubs(:value).returns :four - @confine.true?.should be_true - end + @confine.true?.should be_true + end - it "should return true if any of the provided symbol values matches the fact's string value" do - @fact.stubs(:value).returns :Xy + it "should return true if any of the provided symbol values matches the fact's string value" do + @fact.stubs(:value).returns :Xy - @confine.true?.should be_true - end + @confine.true?.should be_true + end - it "should return false if none of the provided values matches the fact's value" do - @fact.stubs(:value).returns "three" + it "should return false if none of the provided values matches the fact's value" do + @fact.stubs(:value).returns "three" - @confine.true?.should be_false - end + @confine.true?.should be_false + end - it "should return false if none of the provided integer values matches the fact's value" do - @fact.stubs(:value).returns 2 + it "should return false if none of the provided integer values matches the fact's value" do + @fact.stubs(:value).returns 2 - @confine.true?.should be_false - end + @confine.true?.should be_false + end - it "should return false if none of the provided boolan values matches the fact's value" do - @fact.stubs(:value).returns false + it "should return false if none of the provided boolan values matches the fact's value" do + @fact.stubs(:value).returns false - @confine.true?.should be_false - end + @confine.true?.should be_false + end - it "should return false if none of the provided array values matches the fact's value" do - @fact.stubs(:value).returns [1,2] + it "should return false if none of the provided array values matches the fact's value" do + @fact.stubs(:value).returns [1,2] - @confine.true?.should be_false - end + @confine.true?.should be_false end + end end diff --git a/spec/unit/util/fact_spec.rb b/spec/unit/util/fact_spec.rb index 523c855297..9b8c8d1c41 100755 --- a/spec/unit/util/fact_spec.rb +++ b/spec/unit/util/fact_spec.rb @@ -5,125 +5,125 @@ require 'facter/util/fact' describe Facter::Util::Fact do - it "should require a name" do - lambda { Facter::Util::Fact.new }.should raise_error(ArgumentError) - end + it "should require a name" do + lambda { Facter::Util::Fact.new }.should raise_error(ArgumentError) + end - it "should always downcase the name and convert it to a symbol" do - Facter::Util::Fact.new("YayNess").name.should == :yayness - end + it "should always downcase the name and convert it to a symbol" do + Facter::Util::Fact.new("YayNess").name.should == :yayness + end - it "should default to its name converted to a string as its ldapname" do - Facter::Util::Fact.new("YayNess").ldapname.should == "yayness" - end + it "should default to its name converted to a string as its ldapname" do + Facter::Util::Fact.new("YayNess").ldapname.should == "yayness" + end - it "should allow specifying the ldap name at initialization" do - Facter::Util::Fact.new("YayNess", :ldapname => "fooness").ldapname.should == "fooness" - end + it "should allow specifying the ldap name at initialization" do + Facter::Util::Fact.new("YayNess", :ldapname => "fooness").ldapname.should == "fooness" + end + + it "should fail if an unknown option is provided" do + lambda { Facter::Util::Fact.new('yay', :foo => :bar) }.should raise_error(ArgumentError) + end + + it "should have a method for adding resolution mechanisms" do + Facter::Util::Fact.new("yay").should respond_to(:add) + end + + describe "when adding resolution mechanisms" do + before do + @fact = Facter::Util::Fact.new("yay") + + @resolution = mock 'resolution' + @resolution.stub_everything - it "should fail if an unknown option is provided" do - lambda { Facter::Util::Fact.new('yay', :foo => :bar) }.should raise_error(ArgumentError) end - it "should have a method for adding resolution mechanisms" do - Facter::Util::Fact.new("yay").should respond_to(:add) + it "should fail if no block is given" do + lambda { @fact.add }.should raise_error(ArgumentError) end - describe "when adding resolution mechanisms" do - before do - @fact = Facter::Util::Fact.new("yay") + it "should create a new resolution instance" do + Facter::Util::Resolution.expects(:new).returns @resolution + + @fact.add { } + end - @resolution = mock 'resolution' - @resolution.stub_everything + it "should instance_eval the passed block on the new resolution" do + @resolution.expects(:instance_eval) - end + Facter::Util::Resolution.stubs(:new).returns @resolution - it "should fail if no block is given" do - lambda { @fact.add }.should raise_error(ArgumentError) - end + @fact.add { } + end - it "should create a new resolution instance" do - Facter::Util::Resolution.expects(:new).returns @resolution + it "should re-sort the resolutions by weight, so the most restricted resolutions are first" do + r1 = stub 'r1', :weight => 1 + r2 = stub 'r2', :weight => 2 + r3 = stub 'r3', :weight => 0 + Facter::Util::Resolution.expects(:new).times(3).returns(r1).returns(r2).returns(r3) + @fact.add { } + @fact.add { } + @fact.add { } - @fact.add { } - end + @fact.instance_variable_get("@resolves").should == [r2, r1, r3] + end + end - it "should instance_eval the passed block on the new resolution" do - @resolution.expects(:instance_eval) + it "should be able to return a value" do + Facter::Util::Fact.new("yay").should respond_to(:value) + end - Facter::Util::Resolution.stubs(:new).returns @resolution + describe "when returning a value" do + before do + @fact = Facter::Util::Fact.new("yay") + end - @fact.add { } - end + it "should return nil if there are no resolutions" do + Facter::Util::Fact.new("yay").value.should be_nil + end - it "should re-sort the resolutions by weight, so the most restricted resolutions are first" do - r1 = stub 'r1', :weight => 1 - r2 = stub 'r2', :weight => 2 - r3 = stub 'r3', :weight => 0 - Facter::Util::Resolution.expects(:new).times(3).returns(r1).returns(r2).returns(r3) - @fact.add { } - @fact.add { } - @fact.add { } + it "should return the first value returned by a resolution" do + r1 = stub 'r1', :weight => 2, :value => nil, :suitable? => true + r2 = stub 'r2', :weight => 1, :value => "yay", :suitable? => true + r3 = stub 'r3', :weight => 0, :value => "foo", :suitable? => true + Facter::Util::Resolution.expects(:new).times(3).returns(r1).returns(r2).returns(r3) + @fact.add { } + @fact.add { } + @fact.add { } - @fact.instance_variable_get("@resolves").should == [r2, r1, r3] - end + @fact.value.should == "yay" end - it "should be able to return a value" do - Facter::Util::Fact.new("yay").should respond_to(:value) + it "should short-cut returning the value once one is found" do + r1 = stub 'r1', :weight => 2, :value => "foo", :suitable? => true + r2 = stub 'r2', :weight => 1, :suitable? => true # would fail if 'value' were asked for + Facter::Util::Resolution.expects(:new).times(2).returns(r1).returns(r2) + @fact.add { } + @fact.add { } + + @fact.value end - describe "when returning a value" do - before do - @fact = Facter::Util::Fact.new("yay") - end - - it "should return nil if there are no resolutions" do - Facter::Util::Fact.new("yay").value.should be_nil - end - - it "should return the first value returned by a resolution" do - r1 = stub 'r1', :weight => 2, :value => nil, :suitable? => true - r2 = stub 'r2', :weight => 1, :value => "yay", :suitable? => true - r3 = stub 'r3', :weight => 0, :value => "foo", :suitable? => true - Facter::Util::Resolution.expects(:new).times(3).returns(r1).returns(r2).returns(r3) - @fact.add { } - @fact.add { } - @fact.add { } - - @fact.value.should == "yay" - end - - it "should short-cut returning the value once one is found" do - r1 = stub 'r1', :weight => 2, :value => "foo", :suitable? => true - r2 = stub 'r2', :weight => 1, :suitable? => true # would fail if 'value' were asked for - Facter::Util::Resolution.expects(:new).times(2).returns(r1).returns(r2) - @fact.add { } - @fact.add { } - - @fact.value - end - - it "should skip unsuitable resolutions" do - r1 = stub 'r1', :weight => 2, :suitable? => false # would fail if 'value' were asked for' - r2 = stub 'r2', :weight => 1, :value => "yay", :suitable? => true - Facter::Util::Resolution.expects(:new).times(2).returns(r1).returns(r2) - @fact.add { } - @fact.add { } - - @fact.value.should == "yay" - end - - it "should return nil if the value is the empty string" do - r1 = stub 'r1', :suitable? => true, :value => "" - Facter::Util::Resolution.expects(:new).returns r1 - @fact.add { } - - @fact.value.should be_nil - end + it "should skip unsuitable resolutions" do + r1 = stub 'r1', :weight => 2, :suitable? => false # would fail if 'value' were asked for' + r2 = stub 'r2', :weight => 1, :value => "yay", :suitable? => true + Facter::Util::Resolution.expects(:new).times(2).returns(r1).returns(r2) + @fact.add { } + @fact.add { } + + @fact.value.should == "yay" end - it "should have a method for flushing the cached fact" do - Facter::Util::Fact.new(:foo).should respond_to(:flush) + it "should return nil if the value is the empty string" do + r1 = stub 'r1', :suitable? => true, :value => "" + Facter::Util::Resolution.expects(:new).returns r1 + @fact.add { } + + @fact.value.should be_nil end + end + + it "should have a method for flushing the cached fact" do + Facter::Util::Fact.new(:foo).should respond_to(:flush) + end end diff --git a/spec/unit/util/ip_spec.rb b/spec/unit/util/ip_spec.rb index 9649010a16..50035c5569 100755 --- a/spec/unit/util/ip_spec.rb +++ b/spec/unit/util/ip_spec.rb @@ -5,295 +5,295 @@ require 'facter/util/ip' describe Facter::Util::IP do - before :each do - Facter::Util::Config.stubs(:is_windows?).returns(false) - end - - [:freebsd, :linux, :netbsd, :openbsd, :sunos, :darwin, :"hp-ux", :"gnu/kfreebsd", :windows].each do |platform| - it "should be supported on #{platform}" do - Facter::Util::Config.stubs(:is_windows?).returns(platform == :windows) - Facter::Util::IP.supported_platforms.should be_include(platform) - end - end - - it "should return a list of interfaces" do - Facter::Util::IP.should respond_to(:get_interfaces) - end + before :each do + Facter::Util::Config.stubs(:is_windows?).returns(false) + end + + [:freebsd, :linux, :netbsd, :openbsd, :sunos, :darwin, :"hp-ux", :"gnu/kfreebsd", :windows].each do |platform| + it "should be supported on #{platform}" do + Facter::Util::Config.stubs(:is_windows?).returns(platform == :windows) + Facter::Util::IP.supported_platforms.should be_include(platform) + end + end + + it "should return a list of interfaces" do + Facter::Util::IP.should respond_to(:get_interfaces) + end + + it "should return an empty list of interfaces on an unknown kernel" do + Facter.stubs(:value).returns("UnknownKernel") + Facter::Util::IP.get_interfaces().should == [] + end + + it "should return a list with a single interface and the loopback interface on Linux with a single interface" do + sample_output_file = File.dirname(__FILE__) + '/../data/linux_ifconfig_all_with_single_interface' + linux_ifconfig = File.read(sample_output_file) + Facter::Util::IP.stubs(:get_all_interface_output).returns(linux_ifconfig) + Facter::Util::IP.get_interfaces().should == ["eth0", "lo"] + end + + it "should return a list two interfaces on Darwin with two interfaces" do + sample_output_file = File.dirname(__FILE__) + '/../data/darwin_ifconfig_all_with_multiple_interfaces' + darwin_ifconfig = File.read(sample_output_file) + Facter::Util::IP.stubs(:get_all_interface_output).returns(darwin_ifconfig) + Facter::Util::IP.get_interfaces().should == ["lo0", "en0"] + end + + it "should return a list two interfaces on Solaris with two interfaces multiply reporting" do + sample_output_file = File.dirname(__FILE__) + '/../data/solaris_ifconfig_all_with_multiple_interfaces' + solaris_ifconfig = File.read(sample_output_file) + Facter::Util::IP.stubs(:get_all_interface_output).returns(solaris_ifconfig) + Facter::Util::IP.get_interfaces().should == ["lo0", "e1000g0"] + end + + it "should return a list three interfaces on HP-UX with three interfaces multiply reporting" do + sample_output_file = File.dirname(__FILE__) + '/../data/hpux_netstat_all_interfaces' + hpux_netstat = File.read(sample_output_file) + Facter::Util::IP.stubs(:get_all_interface_output).returns(hpux_netstat) + Facter::Util::IP.get_interfaces().should == ["lan1", "lan0", "lo0"] + end + + it "should return a list of six interfaces on a GNU/kFreeBSD with six interfaces" do + sample_output_file = File.dirname(__FILE__) + '/../data/debian_kfreebsd_ifconfig' + kfreebsd_ifconfig = File.read(sample_output_file) + Facter::Util::IP.stubs(:get_all_interface_output).returns(kfreebsd_ifconfig) + Facter::Util::IP.get_interfaces().should == ["em0", "em1", "bge0", "bge1", "lo0", "vlan0"] + end + + it "should return a list of only connected interfaces on Windows" do + Facter.fact(:kernel).stubs(:value).returns("windows") + sample_output_file = File.dirname(__FILE__) + '/../data/windows_netsh_all_interfaces' + windows_netsh = File.read(sample_output_file) + Facter::Util::IP.stubs(:get_all_interface_output).returns(windows_netsh) + Facter::Util::IP.get_interfaces().should == ["Loopback Pseudo-Interface 1", "Local Area Connection", "Teredo Tunneling Pseudo-Interface"] + end - it "should return an empty list of interfaces on an unknown kernel" do - Facter.stubs(:value).returns("UnknownKernel") - Facter::Util::IP.get_interfaces().should == [] - end + it "should return a value for a specific interface" do + Facter::Util::IP.should respond_to(:get_interface_value) + end - it "should return a list with a single interface and the loopback interface on Linux with a single interface" do - sample_output_file = File.dirname(__FILE__) + '/../data/linux_ifconfig_all_with_single_interface' - linux_ifconfig = File.read(sample_output_file) - Facter::Util::IP.stubs(:get_all_interface_output).returns(linux_ifconfig) - Facter::Util::IP.get_interfaces().should == ["eth0", "lo"] - end + it "should not return interface information for unsupported platforms" do + Facter.stubs(:value).with(:kernel).returns("bleah") + Facter::Util::IP.get_interface_value("e1000g0", "netmask").should == [] + end - it "should return a list two interfaces on Darwin with two interfaces" do - sample_output_file = File.dirname(__FILE__) + '/../data/darwin_ifconfig_all_with_multiple_interfaces' - darwin_ifconfig = File.read(sample_output_file) - Facter::Util::IP.stubs(:get_all_interface_output).returns(darwin_ifconfig) - Facter::Util::IP.get_interfaces().should == ["lo0", "en0"] - end + it "should return ipaddress information for Solaris" do + sample_output_file = File.dirname(__FILE__) + "/../data/solaris_ifconfig_single_interface" + solaris_ifconfig_interface = File.read(sample_output_file) - it "should return a list two interfaces on Solaris with two interfaces multiply reporting" do - sample_output_file = File.dirname(__FILE__) + '/../data/solaris_ifconfig_all_with_multiple_interfaces' - solaris_ifconfig = File.read(sample_output_file) - Facter::Util::IP.stubs(:get_all_interface_output).returns(solaris_ifconfig) - Facter::Util::IP.get_interfaces().should == ["lo0", "e1000g0"] - end + Facter::Util::IP.expects(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("SunOS") - it "should return a list three interfaces on HP-UX with three interfaces multiply reporting" do - sample_output_file = File.dirname(__FILE__) + '/../data/hpux_netstat_all_interfaces' - hpux_netstat = File.read(sample_output_file) - Facter::Util::IP.stubs(:get_all_interface_output).returns(hpux_netstat) - Facter::Util::IP.get_interfaces().should == ["lan1", "lan0", "lo0"] - end + Facter::Util::IP.get_interface_value("e1000g0", "ipaddress").should == "172.16.15.138" + end - it "should return a list of six interfaces on a GNU/kFreeBSD with six interfaces" do - sample_output_file = File.dirname(__FILE__) + '/../data/debian_kfreebsd_ifconfig' - kfreebsd_ifconfig = File.read(sample_output_file) - Facter::Util::IP.stubs(:get_all_interface_output).returns(kfreebsd_ifconfig) - Facter::Util::IP.get_interfaces().should == ["em0", "em1", "bge0", "bge1", "lo0", "vlan0"] - end + it "should return netmask information for Solaris" do + sample_output_file = File.dirname(__FILE__) + "/../data/solaris_ifconfig_single_interface" + solaris_ifconfig_interface = File.read(sample_output_file) - it "should return a list of only connected interfaces on Windows" do - Facter.fact(:kernel).stubs(:value).returns("windows") - sample_output_file = File.dirname(__FILE__) + '/../data/windows_netsh_all_interfaces' - windows_netsh = File.read(sample_output_file) - Facter::Util::IP.stubs(:get_all_interface_output).returns(windows_netsh) - Facter::Util::IP.get_interfaces().should == ["Loopback Pseudo-Interface 1", "Local Area Connection", "Teredo Tunneling Pseudo-Interface"] - end + Facter::Util::IP.expects(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("SunOS") - it "should return a value for a specific interface" do - Facter::Util::IP.should respond_to(:get_interface_value) - end + Facter::Util::IP.get_interface_value("e1000g0", "netmask").should == "255.255.255.0" + end - it "should not return interface information for unsupported platforms" do - Facter.stubs(:value).with(:kernel).returns("bleah") - Facter::Util::IP.get_interface_value("e1000g0", "netmask").should == [] - end + it "should return calculated network information for Solaris" do + sample_output_file = File.dirname(__FILE__) + "/../data/solaris_ifconfig_single_interface" + solaris_ifconfig_interface = File.read(sample_output_file) - it "should return ipaddress information for Solaris" do - sample_output_file = File.dirname(__FILE__) + "/../data/solaris_ifconfig_single_interface" - solaris_ifconfig_interface = File.read(sample_output_file) + Facter::Util::IP.stubs(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("SunOS") - Facter::Util::IP.expects(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("SunOS") + Facter::Util::IP.get_network_value("e1000g0").should == "172.16.15.0" + end - Facter::Util::IP.get_interface_value("e1000g0", "ipaddress").should == "172.16.15.138" - end + it "should return ipaddress information for HP-UX" do + sample_output_file = File.dirname(__FILE__) + "/../data/hpux_ifconfig_single_interface" + hpux_ifconfig_interface = File.read(sample_output_file) - it "should return netmask information for Solaris" do - sample_output_file = File.dirname(__FILE__) + "/../data/solaris_ifconfig_single_interface" - solaris_ifconfig_interface = File.read(sample_output_file) + Facter::Util::IP.expects(:get_single_interface_output).with("lan0").returns(hpux_ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("HP-UX") - Facter::Util::IP.expects(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("SunOS") + Facter::Util::IP.get_interface_value("lan0", "ipaddress").should == "168.24.80.71" + end - Facter::Util::IP.get_interface_value("e1000g0", "netmask").should == "255.255.255.0" - end + it "should return macaddress information for HP-UX" do + sample_output_file = File.dirname(__FILE__) + "/../data/hpux_ifconfig_single_interface" + hpux_ifconfig_interface = File.read(sample_output_file) - it "should return calculated network information for Solaris" do - sample_output_file = File.dirname(__FILE__) + "/../data/solaris_ifconfig_single_interface" - solaris_ifconfig_interface = File.read(sample_output_file) + Facter::Util::IP.expects(:get_single_interface_output).with("lan0").returns(hpux_ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("HP-UX") - Facter::Util::IP.stubs(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("SunOS") + Facter::Util::IP.get_interface_value("lan0", "macaddress").should == "00:13:21:BD:9C:B7" + end - Facter::Util::IP.get_network_value("e1000g0").should == "172.16.15.0" - end + it "should return macaddress with leading zeros stripped off for GNU/kFreeBSD" do + sample_output_file = File.dirname(__FILE__) + "/../data/debian_kfreebsd_ifconfig" + kfreebsd_ifconfig = File.read(sample_output_file) - it "should return ipaddress information for HP-UX" do - sample_output_file = File.dirname(__FILE__) + "/../data/hpux_ifconfig_single_interface" - hpux_ifconfig_interface = File.read(sample_output_file) + Facter::Util::IP.expects(:get_single_interface_output).with("em0").returns(kfreebsd_ifconfig) + Facter.stubs(:value).with(:kernel).returns("GNU/kFreeBSD") - Facter::Util::IP.expects(:get_single_interface_output).with("lan0").returns(hpux_ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("HP-UX") + Facter::Util::IP.get_interface_value("em0", "macaddress").should == "0:11:a:59:67:90" + end - Facter::Util::IP.get_interface_value("lan0", "ipaddress").should == "168.24.80.71" - end + it "should return netmask information for HP-UX" do + sample_output_file = File.dirname(__FILE__) + "/../data/hpux_ifconfig_single_interface" + hpux_ifconfig_interface = File.read(sample_output_file) - it "should return macaddress information for HP-UX" do - sample_output_file = File.dirname(__FILE__) + "/../data/hpux_ifconfig_single_interface" - hpux_ifconfig_interface = File.read(sample_output_file) + Facter::Util::IP.expects(:get_single_interface_output).with("lan0").returns(hpux_ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("HP-UX") - Facter::Util::IP.expects(:get_single_interface_output).with("lan0").returns(hpux_ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("HP-UX") + Facter::Util::IP.get_interface_value("lan0", "netmask").should == "255.255.255.0" + end - Facter::Util::IP.get_interface_value("lan0", "macaddress").should == "00:13:21:BD:9C:B7" - end + it "should return calculated network information for HP-UX" do + sample_output_file = File.dirname(__FILE__) + "/../data/hpux_ifconfig_single_interface" + hpux_ifconfig_interface = File.read(sample_output_file) - it "should return macaddress with leading zeros stripped off for GNU/kFreeBSD" do - sample_output_file = File.dirname(__FILE__) + "/../data/debian_kfreebsd_ifconfig" - kfreebsd_ifconfig = File.read(sample_output_file) + Facter::Util::IP.stubs(:get_single_interface_output).with("lan0").returns(hpux_ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("HP-UX") - Facter::Util::IP.expects(:get_single_interface_output).with("em0").returns(kfreebsd_ifconfig) - Facter.stubs(:value).with(:kernel).returns("GNU/kFreeBSD") + Facter::Util::IP.get_network_value("lan0").should == "168.24.80.0" + end - Facter::Util::IP.get_interface_value("em0", "macaddress").should == "0:11:a:59:67:90" - end + it "should return interface information for FreeBSD supported via an alias" do + sample_output_file = File.dirname(__FILE__) + "/../data/6.0-STABLE_FreeBSD_ifconfig" + ifconfig_interface = File.read(sample_output_file) - it "should return netmask information for HP-UX" do - sample_output_file = File.dirname(__FILE__) + "/../data/hpux_ifconfig_single_interface" - hpux_ifconfig_interface = File.read(sample_output_file) + Facter::Util::IP.expects(:get_single_interface_output).with("fxp0").returns(ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("FreeBSD") - Facter::Util::IP.expects(:get_single_interface_output).with("lan0").returns(hpux_ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("HP-UX") + Facter::Util::IP.get_interface_value("fxp0", "macaddress").should == "00:0e:0c:68:67:7c" + end - Facter::Util::IP.get_interface_value("lan0", "netmask").should == "255.255.255.0" - end + it "should return macaddress information for OS X" do + sample_output_file = File.dirname(__FILE__) + "/../data/Mac_OS_X_10.5.5_ifconfig" + ifconfig_interface = File.read(sample_output_file) - it "should return calculated network information for HP-UX" do - sample_output_file = File.dirname(__FILE__) + "/../data/hpux_ifconfig_single_interface" - hpux_ifconfig_interface = File.read(sample_output_file) + Facter::Util::IP.expects(:get_single_interface_output).with("en1").returns(ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("Darwin") - Facter::Util::IP.stubs(:get_single_interface_output).with("lan0").returns(hpux_ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("HP-UX") + Facter::Util::IP.get_interface_value("en1", "macaddress").should == "00:1b:63:ae:02:66" + end - Facter::Util::IP.get_network_value("lan0").should == "168.24.80.0" - end + it "should return all interfaces correctly on OS X" do + sample_output_file = File.dirname(__FILE__) + "/../data/Mac_OS_X_10.5.5_ifconfig" + ifconfig_interface = File.read(sample_output_file) - it "should return interface information for FreeBSD supported via an alias" do - sample_output_file = File.dirname(__FILE__) + "/../data/6.0-STABLE_FreeBSD_ifconfig" - ifconfig_interface = File.read(sample_output_file) + Facter::Util::IP.expects(:get_all_interface_output).returns(ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("Darwin") - Facter::Util::IP.expects(:get_single_interface_output).with("fxp0").returns(ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("FreeBSD") + Facter::Util::IP.get_interfaces().should == ["lo0", "gif0", "stf0", "en0", "fw0", "en1", "vmnet8", "vmnet1"] + end - Facter::Util::IP.get_interface_value("fxp0", "macaddress").should == "00:0e:0c:68:67:7c" - end + it "should return a human readable netmask on Solaris" do + sample_output_file = File.dirname(__FILE__) + "/../data/solaris_ifconfig_single_interface" + solaris_ifconfig_interface = File.read(sample_output_file) - it "should return macaddress information for OS X" do - sample_output_file = File.dirname(__FILE__) + "/../data/Mac_OS_X_10.5.5_ifconfig" - ifconfig_interface = File.read(sample_output_file) + Facter::Util::IP.expects(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("SunOS") - Facter::Util::IP.expects(:get_single_interface_output).with("en1").returns(ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("Darwin") + Facter::Util::IP.get_interface_value("e1000g0", "netmask").should == "255.255.255.0" + end - Facter::Util::IP.get_interface_value("en1", "macaddress").should == "00:1b:63:ae:02:66" - end + it "should return a human readable netmask on HP-UX" do + sample_output_file = File.dirname(__FILE__) + "/../data/hpux_ifconfig_single_interface" + hpux_ifconfig_interface = File.read(sample_output_file) - it "should return all interfaces correctly on OS X" do - sample_output_file = File.dirname(__FILE__) + "/../data/Mac_OS_X_10.5.5_ifconfig" - ifconfig_interface = File.read(sample_output_file) + Facter::Util::IP.expects(:get_single_interface_output).with("lan0").returns(hpux_ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("HP-UX") - Facter::Util::IP.expects(:get_all_interface_output).returns(ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("Darwin") + Facter::Util::IP.get_interface_value("lan0", "netmask").should == "255.255.255.0" + end - Facter::Util::IP.get_interfaces().should == ["lo0", "gif0", "stf0", "en0", "fw0", "en1", "vmnet8", "vmnet1"] - end + it "should return a human readable netmask on Darwin" do + sample_output_file = File.dirname(__FILE__) + "/../data/darwin_ifconfig_single_interface" - it "should return a human readable netmask on Solaris" do - sample_output_file = File.dirname(__FILE__) + "/../data/solaris_ifconfig_single_interface" - solaris_ifconfig_interface = File.read(sample_output_file) + darwin_ifconfig_interface = File.read(sample_output_file) - Facter::Util::IP.expects(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("SunOS") + Facter::Util::IP.expects(:get_single_interface_output).with("en1").returns(darwin_ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("Darwin") - Facter::Util::IP.get_interface_value("e1000g0", "netmask").should == "255.255.255.0" - end + Facter::Util::IP.get_interface_value("en1", "netmask").should == "255.255.255.0" + end - it "should return a human readable netmask on HP-UX" do - sample_output_file = File.dirname(__FILE__) + "/../data/hpux_ifconfig_single_interface" - hpux_ifconfig_interface = File.read(sample_output_file) + it "should return a human readable netmask on GNU/kFreeBSD" do + sample_output_file = File.dirname(__FILE__) + "/../data/debian_kfreebsd_ifconfig" - Facter::Util::IP.expects(:get_single_interface_output).with("lan0").returns(hpux_ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("HP-UX") + kfreebsd_ifconfig = File.read(sample_output_file) - Facter::Util::IP.get_interface_value("lan0", "netmask").should == "255.255.255.0" - end + Facter::Util::IP.expects(:get_single_interface_output).with("em1").returns(kfreebsd_ifconfig) + Facter.stubs(:value).with(:kernel).returns("GNU/kFreeBSD") - it "should return a human readable netmask on Darwin" do - sample_output_file = File.dirname(__FILE__) + "/../data/darwin_ifconfig_single_interface" + Facter::Util::IP.get_interface_value("em1", "netmask").should == "255.255.255.0" + end - darwin_ifconfig_interface = File.read(sample_output_file) + it "should not get bonding master on interface aliases" do + Facter.stubs(:value).with(:kernel).returns("Linux") - Facter::Util::IP.expects(:get_single_interface_output).with("en1").returns(darwin_ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("Darwin") + Facter::Util::IP.get_bonding_master("eth0:1").should be_nil + end - Facter::Util::IP.get_interface_value("en1", "netmask").should == "255.255.255.0" + [:freebsd, :netbsd, :openbsd, :sunos, :darwin, :"hp-ux"].each do |platform| + it "should require conversion from hex on #{platform}" do + Facter::Util::IP.convert_from_hex?(platform).should == true end + end - it "should return a human readable netmask on GNU/kFreeBSD" do - sample_output_file = File.dirname(__FILE__) + "/../data/debian_kfreebsd_ifconfig" - - kfreebsd_ifconfig = File.read(sample_output_file) - - Facter::Util::IP.expects(:get_single_interface_output).with("em1").returns(kfreebsd_ifconfig) - Facter.stubs(:value).with(:kernel).returns("GNU/kFreeBSD") - - Facter::Util::IP.get_interface_value("em1", "netmask").should == "255.255.255.0" + [:windows].each do |platform| + it "should not require conversion from hex on #{platform}" do + Facter::Util::IP.convert_from_hex?(platform).should be_false end + end - it "should not get bonding master on interface aliases" do - Facter.stubs(:value).with(:kernel).returns("Linux") + it "should return an arp address on Linux" do + Facter.stubs(:value).with(:kernel).returns("Linux") - Facter::Util::IP.get_bonding_master("eth0:1").should be_nil - end + Facter::Util::IP.expects(:get_arp_value).with("eth0").returns("00:00:0c:9f:f0:04") + Facter::Util::IP.get_arp_value("eth0").should == "00:00:0c:9f:f0:04" + end - [:freebsd, :netbsd, :openbsd, :sunos, :darwin, :"hp-ux"].each do |platform| - it "should require conversion from hex on #{platform}" do - Facter::Util::IP.convert_from_hex?(platform).should == true - end + describe "on Windows" do + before :each do + Facter.stubs(:value).with(:kernel).returns("windows") end - [:windows].each do |platform| - it "should not require conversion from hex on #{platform}" do - Facter::Util::IP.convert_from_hex?(platform).should be_false - end - end + it "should return ipaddress information" do + sample_output_file = File.dirname(__FILE__) + "/../data/windows_netsh_single_interface" + windows_netsh = File.read(sample_output_file) - it "should return an arp address on Linux" do - Facter.stubs(:value).with(:kernel).returns("Linux") + Facter::Util::IP.expects(:get_output_for_interface_and_label).with("Local Area Connection", "ipaddress").returns(windows_netsh) - Facter::Util::IP.expects(:get_arp_value).with("eth0").returns("00:00:0c:9f:f0:04") - Facter::Util::IP.get_arp_value("eth0").should == "00:00:0c:9f:f0:04" + Facter::Util::IP.get_interface_value("Local Area Connection", "ipaddress").should == "172.16.138.216" end - describe "on Windows" do - before :each do - Facter.stubs(:value).with(:kernel).returns("windows") - end - - it "should return ipaddress information" do - sample_output_file = File.dirname(__FILE__) + "/../data/windows_netsh_single_interface" - windows_netsh = File.read(sample_output_file) + it "should return a human readable netmask" do + sample_output_file = File.dirname(__FILE__) + "/../data/windows_netsh_single_interface" + windows_netsh = File.read(sample_output_file) - Facter::Util::IP.expects(:get_output_for_interface_and_label).with("Local Area Connection", "ipaddress").returns(windows_netsh) + Facter::Util::IP.expects(:get_output_for_interface_and_label).with("Local Area Connection", "netmask").returns(windows_netsh) - Facter::Util::IP.get_interface_value("Local Area Connection", "ipaddress").should == "172.16.138.216" - end - - it "should return a human readable netmask" do - sample_output_file = File.dirname(__FILE__) + "/../data/windows_netsh_single_interface" - windows_netsh = File.read(sample_output_file) - - Facter::Util::IP.expects(:get_output_for_interface_and_label).with("Local Area Connection", "netmask").returns(windows_netsh) - - Facter::Util::IP.get_interface_value("Local Area Connection", "netmask").should == "255.255.255.0" - end + Facter::Util::IP.get_interface_value("Local Area Connection", "netmask").should == "255.255.255.0" + end - it "should return network information" do - sample_output_file = File.dirname(__FILE__) + "/../data/windows_netsh_single_interface" - windows_netsh = File.read(sample_output_file) + it "should return network information" do + sample_output_file = File.dirname(__FILE__) + "/../data/windows_netsh_single_interface" + windows_netsh = File.read(sample_output_file) - Facter::Util::IP.stubs(:get_output_for_interface_and_label).with("Local Area Connection", "ipaddress").returns(windows_netsh) - Facter::Util::IP.stubs(:get_output_for_interface_and_label).with("Local Area Connection", "netmask").returns(windows_netsh) + Facter::Util::IP.stubs(:get_output_for_interface_and_label).with("Local Area Connection", "ipaddress").returns(windows_netsh) + Facter::Util::IP.stubs(:get_output_for_interface_and_label).with("Local Area Connection", "netmask").returns(windows_netsh) - Facter::Util::IP.get_network_value("Local Area Connection").should == "172.16.138.0" - end + Facter::Util::IP.get_network_value("Local Area Connection").should == "172.16.138.0" + end - it "should return ipaddress6 information" do - sample_output_file = File.dirname(__FILE__) + "/../data/windows_netsh_single_interface6" - windows_netsh = File.read(sample_output_file) + it "should return ipaddress6 information" do + sample_output_file = File.dirname(__FILE__) + "/../data/windows_netsh_single_interface6" + windows_netsh = File.read(sample_output_file) - Facter::Util::IP.expects(:get_output_for_interface_and_label).with("Teredo Tunneling Pseudo-Interface", "ipaddress6").returns(windows_netsh) + Facter::Util::IP.expects(:get_output_for_interface_and_label).with("Teredo Tunneling Pseudo-Interface", "ipaddress6").returns(windows_netsh) - Facter::Util::IP.get_interface_value("Teredo Tunneling Pseudo-Interface", "ipaddress6").should == "2001:0:4137:9e76:2087:77a:53ef:7527" - end + Facter::Util::IP.get_interface_value("Teredo Tunneling Pseudo-Interface", "ipaddress6").should == "2001:0:4137:9e76:2087:77a:53ef:7527" end + end end diff --git a/spec/unit/util/loader_spec.rb b/spec/unit/util/loader_spec.rb index 3fb1f04f00..a3019d90d9 100755 --- a/spec/unit/util/loader_spec.rb +++ b/spec/unit/util/loader_spec.rb @@ -19,273 +19,273 @@ def load_file(file) describe Facter::Util::Loader do - before :each do - Facter::Util::Loader.any_instance.unstub(:load_all) - end + before :each do + Facter::Util::Loader.any_instance.unstub(:load_all) + end - def with_env(values) - old = {} - values.each do |var, value| - if old_val = ENV[var] - old[var] = old_val - end - ENV[var] = value - end - yield - values.each do |var, value| - if old.include?(var) - ENV[var] = old[var] - else - ENV.delete(var) - end - end + def with_env(values) + old = {} + values.each do |var, value| + if old_val = ENV[var] + old[var] = old_val + end + ENV[var] = value end - - it "should have a method for loading individual facts by name" do - Facter::Util::Loader.new.should respond_to(:load) + yield + values.each do |var, value| + if old.include?(var) + ENV[var] = old[var] + else + ENV.delete(var) + end end + end - it "should have a method for loading all facts" do - Facter::Util::Loader.new.should respond_to(:load_all) - end + it "should have a method for loading individual facts by name" do + Facter::Util::Loader.new.should respond_to(:load) + end - it "should have a method for returning directories containing facts" do - Facter::Util::Loader.new.should respond_to(:search_path) - end + it "should have a method for loading all facts" do + Facter::Util::Loader.new.should respond_to(:load_all) + end - describe "when determining the search path" do - before do - @loader = Facter::Util::Loader.new - @settings = mock 'settings' - @settings.stubs(:value).returns "/eh" - end + it "should have a method for returning directories containing facts" do + Facter::Util::Loader.new.should respond_to(:search_path) + end - it "should include the facter subdirectory of all paths in ruby LOAD_PATH" do - dirs = $LOAD_PATH.collect { |d| File.join(d, "facter") } - paths = @loader.search_path + describe "when determining the search path" do + before do + @loader = Facter::Util::Loader.new + @settings = mock 'settings' + @settings.stubs(:value).returns "/eh" + end - dirs.each do |dir| - paths.should be_include(dir) - end - end + it "should include the facter subdirectory of all paths in ruby LOAD_PATH" do + dirs = $LOAD_PATH.collect { |d| File.join(d, "facter") } + paths = @loader.search_path - it "should include all search paths registered with Facter" do - Facter.expects(:search_path).returns %w{/one /two} - paths = @loader.search_path - paths.should be_include("/one") - paths.should be_include("/two") - end + dirs.each do |dir| + paths.should be_include(dir) + end + end - describe "and the FACTERLIB environment variable is set" do - it "should include all paths in FACTERLIB" do - with_env "FACTERLIB" => "/one/path:/two/path" do - paths = @loader.search_path - %w{/one/path /two/path}.each do |dir| - paths.should be_include(dir) - end - end - end - end + it "should include all search paths registered with Facter" do + Facter.expects(:search_path).returns %w{/one /two} + paths = @loader.search_path + paths.should be_include("/one") + paths.should be_include("/two") end - describe "when loading facts" do - before do - @loader = Facter::Util::Loader.new - @loader.stubs(:search_path).returns [] + describe "and the FACTERLIB environment variable is set" do + it "should include all paths in FACTERLIB" do + with_env "FACTERLIB" => "/one/path:/two/path" do + paths = @loader.search_path + %w{/one/path /two/path}.each do |dir| + paths.should be_include(dir) + end end + end + end + end - it "should load values from the matching environment variable if one is present" do - Facter.expects(:add).with("testing") + describe "when loading facts" do + before do + @loader = Facter::Util::Loader.new + @loader.stubs(:search_path).returns [] + end - with_env "facter_testing" => "yayness" do - @loader.load(:testing) - end - end + it "should load values from the matching environment variable if one is present" do + Facter.expects(:add).with("testing") - it "should load any files in the search path with names matching the fact name" do - @loader.expects(:search_path).returns %w{/one/dir /two/dir} - FileTest.stubs(:exist?).returns false - FileTest.expects(:exist?).with("/one/dir/testing.rb").returns true - FileTest.expects(:exist?).with("/two/dir/testing.rb").returns true + with_env "facter_testing" => "yayness" do + @loader.load(:testing) + end + end - Kernel.expects(:load).with("/one/dir/testing.rb") - Kernel.expects(:load).with("/two/dir/testing.rb") + it "should load any files in the search path with names matching the fact name" do + @loader.expects(:search_path).returns %w{/one/dir /two/dir} + FileTest.stubs(:exist?).returns false + FileTest.expects(:exist?).with("/one/dir/testing.rb").returns true + FileTest.expects(:exist?).with("/two/dir/testing.rb").returns true - @loader.load(:testing) - end + Kernel.expects(:load).with("/one/dir/testing.rb") + Kernel.expects(:load).with("/two/dir/testing.rb") - it 'should load any ruby files in directories matching the fact name in the search path in sorted order regardless of the order returned by Dir.entries' do - @loader = TestLoader.new + @loader.load(:testing) + end - @loader.stubs(:search_path).returns %w{/one/dir} - FileTest.stubs(:exist?).returns false - FileTest.stubs(:directory?).with("/one/dir/testing").returns true - @loader.stubs(:search_path).returns %w{/one/dir} + it 'should load any ruby files in directories matching the fact name in the search path in sorted order regardless of the order returned by Dir.entries' do + @loader = TestLoader.new - Dir.stubs(:entries).with("/one/dir/testing").returns %w{foo.rb bar.rb} - %w{/one/dir/testing/foo.rb /one/dir/testing/bar.rb}.each do |f| - File.stubs(:directory?).with(f).returns false - Kernel.stubs(:load).with(f) - end + @loader.stubs(:search_path).returns %w{/one/dir} + FileTest.stubs(:exist?).returns false + FileTest.stubs(:directory?).with("/one/dir/testing").returns true + @loader.stubs(:search_path).returns %w{/one/dir} - @loader.load(:testing) - @loader.loaded_files.should == %w{/one/dir/testing/bar.rb /one/dir/testing/foo.rb} - end + Dir.stubs(:entries).with("/one/dir/testing").returns %w{foo.rb bar.rb} + %w{/one/dir/testing/foo.rb /one/dir/testing/bar.rb}.each do |f| + File.stubs(:directory?).with(f).returns false + Kernel.stubs(:load).with(f) + end - it "should load any ruby files in directories matching the fact name in the search path" do - @loader.expects(:search_path).returns %w{/one/dir} - FileTest.stubs(:exist?).returns false - FileTest.expects(:directory?).with("/one/dir/testing").returns true + @loader.load(:testing) + @loader.loaded_files.should == %w{/one/dir/testing/bar.rb /one/dir/testing/foo.rb} + end - Dir.expects(:entries).with("/one/dir/testing").returns %w{two.rb} + it "should load any ruby files in directories matching the fact name in the search path" do + @loader.expects(:search_path).returns %w{/one/dir} + FileTest.stubs(:exist?).returns false + FileTest.expects(:directory?).with("/one/dir/testing").returns true - Kernel.expects(:load).with("/one/dir/testing/two.rb") + Dir.expects(:entries).with("/one/dir/testing").returns %w{two.rb} - @loader.load(:testing) - end + Kernel.expects(:load).with("/one/dir/testing/two.rb") - it "should not load files that don't end in '.rb'" do - @loader.expects(:search_path).returns %w{/one/dir} - FileTest.stubs(:exist?).returns false - FileTest.expects(:directory?).with("/one/dir/testing").returns true + @loader.load(:testing) + end - Dir.expects(:entries).with("/one/dir/testing").returns %w{one} + it "should not load files that don't end in '.rb'" do + @loader.expects(:search_path).returns %w{/one/dir} + FileTest.stubs(:exist?).returns false + FileTest.expects(:directory?).with("/one/dir/testing").returns true - Kernel.expects(:load).never + Dir.expects(:entries).with("/one/dir/testing").returns %w{one} - @loader.load(:testing) - end + Kernel.expects(:load).never + + @loader.load(:testing) end + end - describe "when loading all facts" do - before do - @loader = Facter::Util::Loader.new - @loader.stubs(:search_path).returns [] + describe "when loading all facts" do + before do + @loader = Facter::Util::Loader.new + @loader.stubs(:search_path).returns [] - FileTest.stubs(:directory?).returns true - end + FileTest.stubs(:directory?).returns true + end - it "should skip directories that do not exist" do - @loader.expects(:search_path).returns %w{/one/dir} + it "should skip directories that do not exist" do + @loader.expects(:search_path).returns %w{/one/dir} - FileTest.expects(:directory?).with("/one/dir").returns false + FileTest.expects(:directory?).with("/one/dir").returns false - Dir.expects(:entries).with("/one/dir").never + Dir.expects(:entries).with("/one/dir").never - @loader.load_all - end + @loader.load_all + end - it "should load all files in all search paths" do - @loader.expects(:search_path).returns %w{/one/dir /two/dir} + it "should load all files in all search paths" do + @loader.expects(:search_path).returns %w{/one/dir /two/dir} - Dir.expects(:entries).with("/one/dir").returns %w{a.rb b.rb} - Dir.expects(:entries).with("/two/dir").returns %w{c.rb d.rb} + Dir.expects(:entries).with("/one/dir").returns %w{a.rb b.rb} + Dir.expects(:entries).with("/two/dir").returns %w{c.rb d.rb} - %w{/one/dir/a.rb /one/dir/b.rb /two/dir/c.rb /two/dir/d.rb}.each { |f| Kernel.expects(:load).with(f) } + %w{/one/dir/a.rb /one/dir/b.rb /two/dir/c.rb /two/dir/d.rb}.each { |f| Kernel.expects(:load).with(f) } - @loader.load_all - end + @loader.load_all + end - it "should load all files in all subdirectories in all search paths" do - @loader.expects(:search_path).returns %w{/one/dir /two/dir} + it "should load all files in all subdirectories in all search paths" do + @loader.expects(:search_path).returns %w{/one/dir /two/dir} - Dir.expects(:entries).with("/one/dir").returns %w{a} - Dir.expects(:entries).with("/two/dir").returns %w{b} + Dir.expects(:entries).with("/one/dir").returns %w{a} + Dir.expects(:entries).with("/two/dir").returns %w{b} - %w{/one/dir/a /two/dir/b}.each { |f| File.expects(:directory?).with(f).returns true } + %w{/one/dir/a /two/dir/b}.each { |f| File.expects(:directory?).with(f).returns true } - Dir.expects(:entries).with("/one/dir/a").returns %w{c.rb} - Dir.expects(:entries).with("/two/dir/b").returns %w{d.rb} + Dir.expects(:entries).with("/one/dir/a").returns %w{c.rb} + Dir.expects(:entries).with("/two/dir/b").returns %w{d.rb} - %w{/one/dir/a/c.rb /two/dir/b/d.rb}.each { |f| Kernel.expects(:load).with(f) } + %w{/one/dir/a/c.rb /two/dir/b/d.rb}.each { |f| Kernel.expects(:load).with(f) } - @loader.load_all - end + @loader.load_all + end - it 'should load all files in sorted order for any given directory regardless of the order returned by Dir.entries' do - @loader = TestLoader.new + it 'should load all files in sorted order for any given directory regardless of the order returned by Dir.entries' do + @loader = TestLoader.new - @loader.stubs(:search_path).returns %w{/one/dir} - Dir.stubs(:entries).with("/one/dir").returns %w{foo.rb bar.rb} + @loader.stubs(:search_path).returns %w{/one/dir} + Dir.stubs(:entries).with("/one/dir").returns %w{foo.rb bar.rb} - %w{/one/dir}.each { |f| File.stubs(:directory?).with(f).returns true } + %w{/one/dir}.each { |f| File.stubs(:directory?).with(f).returns true } - %w{/one/dir/foo.rb /one/dir/bar.rb}.each do |f| - File.stubs(:directory?).with(f).returns false - Kernel.expects(:load).with(f) - end + %w{/one/dir/foo.rb /one/dir/bar.rb}.each do |f| + File.stubs(:directory?).with(f).returns false + Kernel.expects(:load).with(f) + end - @loader.load_all + @loader.load_all - @loader.loaded_files.should == %w{/one/dir/bar.rb /one/dir/foo.rb} - end + @loader.loaded_files.should == %w{/one/dir/bar.rb /one/dir/foo.rb} + end - it "should not load files in the util subdirectory" do - @loader.expects(:search_path).returns %w{/one/dir} + it "should not load files in the util subdirectory" do + @loader.expects(:search_path).returns %w{/one/dir} - Dir.expects(:entries).with("/one/dir").returns %w{util} + Dir.expects(:entries).with("/one/dir").returns %w{util} - File.expects(:directory?).with("/one/dir/util").returns true + File.expects(:directory?).with("/one/dir/util").returns true - Dir.expects(:entries).with("/one/dir/util").never + Dir.expects(:entries).with("/one/dir/util").never - @loader.load_all - end + @loader.load_all + end - it "should not load files in a lib subdirectory" do - @loader.expects(:search_path).returns %w{/one/dir} + it "should not load files in a lib subdirectory" do + @loader.expects(:search_path).returns %w{/one/dir} - Dir.expects(:entries).with("/one/dir").returns %w{lib} + Dir.expects(:entries).with("/one/dir").returns %w{lib} - File.expects(:directory?).with("/one/dir/lib").returns true + File.expects(:directory?).with("/one/dir/lib").returns true - Dir.expects(:entries).with("/one/dir/lib").never + Dir.expects(:entries).with("/one/dir/lib").never - @loader.load_all - end + @loader.load_all + end - it "should not load files in '.' or '..'" do - @loader.expects(:search_path).returns %w{/one/dir} + it "should not load files in '.' or '..'" do + @loader.expects(:search_path).returns %w{/one/dir} - Dir.expects(:entries).with("/one/dir").returns %w{. ..} + Dir.expects(:entries).with("/one/dir").returns %w{. ..} - File.expects(:entries).with("/one/dir/.").never - File.expects(:entries).with("/one/dir/..").never + File.expects(:entries).with("/one/dir/.").never + File.expects(:entries).with("/one/dir/..").never - @loader.load_all - end + @loader.load_all + end - it "should not raise an exception when a file is unloadable" do - @loader.expects(:search_path).returns %w{/one/dir} - Dir.expects(:entries).with("/one/dir").returns %w{a.rb} + it "should not raise an exception when a file is unloadable" do + @loader.expects(:search_path).returns %w{/one/dir} + Dir.expects(:entries).with("/one/dir").returns %w{a.rb} - Kernel.expects(:load).with("/one/dir/a.rb").raises(LoadError) - @loader.expects(:warn) + Kernel.expects(:load).with("/one/dir/a.rb").raises(LoadError) + @loader.expects(:warn) - lambda { @loader.load_all }.should_not raise_error - end + lambda { @loader.load_all }.should_not raise_error + end - it "should load all facts from the environment" do - Facter.expects(:add).with('one') - Facter.expects(:add).with('two') + it "should load all facts from the environment" do + Facter.expects(:add).with('one') + Facter.expects(:add).with('two') - with_env "facter_one" => "yayness", "facter_two" => "boo" do - @loader.load_all - end - end + with_env "facter_one" => "yayness", "facter_two" => "boo" do + @loader.load_all + end + end - it "should only load all facts one time" do - @loader.expects(:load_env).once - @loader.load_all - @loader.load_all - end + it "should only load all facts one time" do + @loader.expects(:load_env).once + @loader.load_all + @loader.load_all end + end - it "should load facts on the facter search path only once" do - facterlibdir = File.expand_path(File.dirname(__FILE__) + '../../../fixtures/unit/util/loader') - with_env 'FACTERLIB' => facterlibdir do - Facter::Util::Loader.new.load_all - Facter.value(:nosuchfact).should be_nil - end + it "should load facts on the facter search path only once" do + facterlibdir = File.expand_path(File.dirname(__FILE__) + '../../../fixtures/unit/util/loader') + with_env 'FACTERLIB' => facterlibdir do + Facter::Util::Loader.new.load_all + Facter.value(:nosuchfact).should be_nil end + end end diff --git a/spec/unit/util/macosx_spec.rb b/spec/unit/util/macosx_spec.rb index 44ba460294..dab4a9f688 100755 --- a/spec/unit/util/macosx_spec.rb +++ b/spec/unit/util/macosx_spec.rb @@ -5,77 +5,77 @@ require 'facter/util/macosx' describe Facter::Util::Macosx do - it "should be able to retrieve profiler data as xml for a given data field" do - Facter::Util::Resolution.expects(:exec).with("/usr/sbin/system_profiler -xml foo").returns "yay" - Facter::Util::Macosx.profiler_xml("foo").should == "yay" - end - - it "should use PList to convert xml to data structures" do - Plist.expects(:parse_xml).with("foo").returns "bar" + it "should be able to retrieve profiler data as xml for a given data field" do + Facter::Util::Resolution.expects(:exec).with("/usr/sbin/system_profiler -xml foo").returns "yay" + Facter::Util::Macosx.profiler_xml("foo").should == "yay" + end - Facter::Util::Macosx.intern_xml("foo").should == "bar" - end + it "should use PList to convert xml to data structures" do + Plist.expects(:parse_xml).with("foo").returns "bar" - describe "when collecting profiler data" do - it "should return the first value in the '_items' hash in the first value of the results of the system_profiler data, with the '_name' field removed, if the profiler returns data" do - @result = [ - '_items' => [ - {'_name' => "foo", "yay" => "bar"} - ] - ] - Facter::Util::Macosx.expects(:profiler_xml).with("foo").returns "eh" - Facter::Util::Macosx.expects(:intern_xml).with("eh").returns @result - Facter::Util::Macosx.profiler_data("foo").should == {"yay" => "bar"} - end + Facter::Util::Macosx.intern_xml("foo").should == "bar" + end - it "should return nil if an exception is thrown during parsing of xml" do - Facter::Util::Macosx.expects(:profiler_xml).with("foo").returns "eh" - Facter::Util::Macosx.expects(:intern_xml).with("eh").raises "boo!" - Facter::Util::Macosx.profiler_data("foo").should be_nil - end + describe "when collecting profiler data" do + it "should return the first value in the '_items' hash in the first value of the results of the system_profiler data, with the '_name' field removed, if the profiler returns data" do + @result = [ + '_items' => [ + {'_name' => "foo", "yay" => "bar"} + ] + ] + Facter::Util::Macosx.expects(:profiler_xml).with("foo").returns "eh" + Facter::Util::Macosx.expects(:intern_xml).with("eh").returns @result + Facter::Util::Macosx.profiler_data("foo").should == {"yay" => "bar"} end - it "should return the profiler data for 'SPHardwareDataType' as the hardware information" do - Facter::Util::Macosx.expects(:profiler_data).with("SPHardwareDataType").returns "eh" - Facter::Util::Macosx.hardware_overview.should == "eh" + it "should return nil if an exception is thrown during parsing of xml" do + Facter::Util::Macosx.expects(:profiler_xml).with("foo").returns "eh" + Facter::Util::Macosx.expects(:intern_xml).with("eh").raises "boo!" + Facter::Util::Macosx.profiler_data("foo").should be_nil end + end - it "should return the profiler data for 'SPSoftwareDataType' as the os information" do - Facter::Util::Macosx.expects(:profiler_data).with("SPSoftwareDataType").returns "eh" - Facter::Util::Macosx.os_overview.should == "eh" - end - - describe "when working out software version" do - - before do - Facter::Util::Resolution.expects(:exec).with("/usr/bin/sw_vers -productName").returns "Mac OS X" - Facter::Util::Resolution.expects(:exec).with("/usr/bin/sw_vers -buildVersion").returns "9J62" - end - - it "should have called sw_vers three times when determining software version" do - Facter::Util::Resolution.expects(:exec).with("/usr/bin/sw_vers -productVersion").returns "10.5.7" - Facter::Util::Macosx.sw_vers - end - - it "should return a hash with the correct keys when determining software version" do - Facter::Util::Resolution.expects(:exec).with("/usr/bin/sw_vers -productVersion").returns "10.5.7" - Facter::Util::Macosx.sw_vers.keys.sort.should == ["macosx_productName", - "macosx_buildVersion", - "macosx_productversion_minor", - "macosx_productversion_major", - "macosx_productVersion"].sort - end + it "should return the profiler data for 'SPHardwareDataType' as the hardware information" do + Facter::Util::Macosx.expects(:profiler_data).with("SPHardwareDataType").returns "eh" + Facter::Util::Macosx.hardware_overview.should == "eh" + end + + it "should return the profiler data for 'SPSoftwareDataType' as the os information" do + Facter::Util::Macosx.expects(:profiler_data).with("SPSoftwareDataType").returns "eh" + Facter::Util::Macosx.os_overview.should == "eh" + end + + describe "when working out software version" do - it "should split a product version of 'x.y.z' into separate hash entries correctly" do - Facter::Util::Resolution.expects(:exec).with("/usr/bin/sw_vers -productVersion").returns "1.2.3" - sw_vers = Facter::Util::Macosx.sw_vers - sw_vers["macosx_productversion_major"].should == "1.2" - sw_vers["macosx_productversion_minor"].should == "3" - end + before do + Facter::Util::Resolution.expects(:exec).with("/usr/bin/sw_vers -productName").returns "Mac OS X" + Facter::Util::Resolution.expects(:exec).with("/usr/bin/sw_vers -buildVersion").returns "9J62" + end - it "should treat a product version of 'x.y' as 'x.y.0" do - Facter::Util::Resolution.expects(:exec).with("/usr/bin/sw_vers -productVersion").returns "2.3" - Facter::Util::Macosx.sw_vers["macosx_productversion_minor"].should == "0" - end + it "should have called sw_vers three times when determining software version" do + Facter::Util::Resolution.expects(:exec).with("/usr/bin/sw_vers -productVersion").returns "10.5.7" + Facter::Util::Macosx.sw_vers + end + + it "should return a hash with the correct keys when determining software version" do + Facter::Util::Resolution.expects(:exec).with("/usr/bin/sw_vers -productVersion").returns "10.5.7" + Facter::Util::Macosx.sw_vers.keys.sort.should == ["macosx_productName", + "macosx_buildVersion", + "macosx_productversion_minor", + "macosx_productversion_major", + "macosx_productVersion"].sort + end + + it "should split a product version of 'x.y.z' into separate hash entries correctly" do + Facter::Util::Resolution.expects(:exec).with("/usr/bin/sw_vers -productVersion").returns "1.2.3" + sw_vers = Facter::Util::Macosx.sw_vers + sw_vers["macosx_productversion_major"].should == "1.2" + sw_vers["macosx_productversion_minor"].should == "3" + end + + it "should treat a product version of 'x.y' as 'x.y.0" do + Facter::Util::Resolution.expects(:exec).with("/usr/bin/sw_vers -productVersion").returns "2.3" + Facter::Util::Macosx.sw_vers["macosx_productversion_minor"].should == "0" end + end end diff --git a/spec/unit/util/manufacturer_spec.rb b/spec/unit/util/manufacturer_spec.rb index c738811cdd..ed248ca2c9 100644 --- a/spec/unit/util/manufacturer_spec.rb +++ b/spec/unit/util/manufacturer_spec.rb @@ -3,52 +3,52 @@ require 'facter/util/manufacturer' describe Facter::Manufacturer do - before :each do - Facter.clear - end - - it "should return the system DMI table" do - Facter::Manufacturer.should respond_to(:get_dmi_table) - end - - it "should return nil on non-supported operating systems" do - Facter.stubs(:value).with(:kernel).returns("SomeThing") - Facter::Manufacturer.get_dmi_table().should be_nil - end - - it "should parse prtdiag output" do - Facter::Util::Resolution.stubs(:exec).returns("System Configuration: Sun Microsystems sun4u Sun SPARC Enterprise M3000 Server") - Facter::Manufacturer.prtdiag_sparc_find_system_info() - Facter.value(:manufacturer).should == "Sun Microsystems" - Facter.value(:productname).should == "Sun SPARC Enterprise M3000 Server" - end - - it "should strip white space on dmi output with spaces" do - sample_output_file = File.dirname(__FILE__) + "/../data/linux_dmidecode_with_spaces" - dmidecode_output = File.new(sample_output_file).read() - Facter::Manufacturer.expects(:get_dmi_table).returns(dmidecode_output) - Facter.fact(:kernel).stubs(:value).returns("Linux") - - query = { '[Ss]ystem [Ii]nformation' => [ { 'Product(?: Name)?:' => 'productname' } ] } - - Facter::Manufacturer.dmi_find_system_info(query) - Facter.value(:productname).should == "MS-6754" - end - - it "should handle output from smbios when run under sunos" do - sample_output_file = File.dirname(__FILE__) + "/../data/opensolaris_smbios" - smbios_output = File.new(sample_output_file).read() - Facter::Manufacturer.expects(:get_dmi_table).returns(smbios_output) - Facter.fact(:kernel).stubs(:value).returns("SunOS") - - query = { 'BIOS information' => [ { 'Release Date:' => 'reldate' } ] } - - Facter::Manufacturer.dmi_find_system_info(query) - Facter.value(:reldate).should == "12/01/2006" - end - - it "should not split on dmi keys containing the string Handle" do - dmidecode_output = <<-eos + before :each do + Facter.clear + end + + it "should return the system DMI table" do + Facter::Manufacturer.should respond_to(:get_dmi_table) + end + + it "should return nil on non-supported operating systems" do + Facter.stubs(:value).with(:kernel).returns("SomeThing") + Facter::Manufacturer.get_dmi_table().should be_nil + end + + it "should parse prtdiag output" do + Facter::Util::Resolution.stubs(:exec).returns("System Configuration: Sun Microsystems sun4u Sun SPARC Enterprise M3000 Server") + Facter::Manufacturer.prtdiag_sparc_find_system_info() + Facter.value(:manufacturer).should == "Sun Microsystems" + Facter.value(:productname).should == "Sun SPARC Enterprise M3000 Server" + end + + it "should strip white space on dmi output with spaces" do + sample_output_file = File.dirname(__FILE__) + "/../data/linux_dmidecode_with_spaces" + dmidecode_output = File.new(sample_output_file).read() + Facter::Manufacturer.expects(:get_dmi_table).returns(dmidecode_output) + Facter.fact(:kernel).stubs(:value).returns("Linux") + + query = { '[Ss]ystem [Ii]nformation' => [ { 'Product(?: Name)?:' => 'productname' } ] } + + Facter::Manufacturer.dmi_find_system_info(query) + Facter.value(:productname).should == "MS-6754" + end + + it "should handle output from smbios when run under sunos" do + sample_output_file = File.dirname(__FILE__) + "/../data/opensolaris_smbios" + smbios_output = File.new(sample_output_file).read() + Facter::Manufacturer.expects(:get_dmi_table).returns(smbios_output) + Facter.fact(:kernel).stubs(:value).returns("SunOS") + + query = { 'BIOS information' => [ { 'Release Date:' => 'reldate' } ] } + + Facter::Manufacturer.dmi_find_system_info(query) + Facter.value(:reldate).should == "12/01/2006" + end + + it "should not split on dmi keys containing the string Handle" do + dmidecode_output = <<-eos Handle 0x1000, DMI type 16, 15 bytes Physical Memory Array Location: System Board Or Motherboard @@ -61,16 +61,16 @@ Handle 0x001F DMI type 127, 4 bytes. End Of Table - eos - Facter::Manufacturer.expects(:get_dmi_table).returns(dmidecode_output) - Facter.fact(:kernel).stubs(:value).returns("Linux") - query = { 'Physical Memory Array' => [ { 'Number Of Devices:' => 'ramslots'}]} - Facter::Manufacturer.dmi_find_system_info(query) - Facter.value(:ramslots).should == "123" - end - - it "should match the key in the defined section and not the first one found" do - dmidecode_output = <<-eos + eos + Facter::Manufacturer.expects(:get_dmi_table).returns(dmidecode_output) + Facter.fact(:kernel).stubs(:value).returns("Linux") + query = { 'Physical Memory Array' => [ { 'Number Of Devices:' => 'ramslots'}]} + Facter::Manufacturer.dmi_find_system_info(query) + Facter.value(:ramslots).should == "123" + end + + it "should match the key in the defined section and not the first one found" do + dmidecode_output = <<-eos Handle 0x000C, DMI type 7, 19 bytes Cache Information Socket Designation: Internal L2 Cache @@ -99,55 +99,55 @@ Handle 0x001F DMI type 127, 4 bytes. End Of Table - eos - Facter::Manufacturer.expects(:get_dmi_table).returns(dmidecode_output) - Facter.fact(:kernel).stubs(:value).returns("Linux") - query = { 'Physical Memory Array' => [ { 'Location:' => 'ramlocation'}]} - Facter::Manufacturer.dmi_find_system_info(query) - Facter.value(:ramlocation).should == "System Board Or Motherboard" - end - - def find_product_name(os) - output_file = case os - when "FreeBSD" then File.dirname(__FILE__) + "/../data/freebsd_dmidecode" - when "SunOS" then File.dirname(__FILE__) + "/../data/opensolaris_smbios" - end - - output = File.new(output_file).read() - query = { '[Ss]ystem [Ii]nformation' => [ { 'Product(?: Name)?:' => "product_name_#{os}" } ] } - - Facter.fact(:kernel).stubs(:value).returns(os) - Facter::Manufacturer.expects(:get_dmi_table).returns(output) - - Facter::Manufacturer.dmi_find_system_info(query) - - return Facter.value("product_name_#{os}") - end - - it "should return the same result with smbios than with dmidecode" do - find_product_name("FreeBSD").should_not == nil - find_product_name("FreeBSD").should == find_product_name("SunOS") - end - - it "should find information on Windows" do - Facter.fact(:kernel).stubs(:value).returns("windows") - require 'facter/util/wmi' - - bios = stubs 'bios' - bios.stubs(:Manufacturer).returns("Phoenix Technologies LTD") - bios.stubs(:Serialnumber).returns("56 4d 40 2b 4d 81 94 d6-e6 c5 56 a4 56 0c 9e 9f") - - product = stubs 'product' - product.stubs(:Name).returns("VMware Virtual Platform") - - wmi = stubs 'wmi' - wmi.stubs(:ExecQuery).with("select * from Win32_Bios").returns([bios]) - wmi.stubs(:ExecQuery).with("select * from Win32_Bios").returns([bios]) - wmi.stubs(:ExecQuery).with("select * from Win32_ComputerSystemProduct").returns([product]) - - Facter::Util::WMI.stubs(:connect).returns(wmi) - Facter.value(:manufacturer).should == "Phoenix Technologies LTD" - Facter.value(:serialnumber).should == "56 4d 40 2b 4d 81 94 d6-e6 c5 56 a4 56 0c 9e 9f" - Facter.value(:productname).should == "VMware Virtual Platform" - end + eos + Facter::Manufacturer.expects(:get_dmi_table).returns(dmidecode_output) + Facter.fact(:kernel).stubs(:value).returns("Linux") + query = { 'Physical Memory Array' => [ { 'Location:' => 'ramlocation'}]} + Facter::Manufacturer.dmi_find_system_info(query) + Facter.value(:ramlocation).should == "System Board Or Motherboard" + end + + def find_product_name(os) + output_file = case os + when "FreeBSD" then File.dirname(__FILE__) + "/../data/freebsd_dmidecode" + when "SunOS" then File.dirname(__FILE__) + "/../data/opensolaris_smbios" + end + + output = File.new(output_file).read() + query = { '[Ss]ystem [Ii]nformation' => [ { 'Product(?: Name)?:' => "product_name_#{os}" } ] } + + Facter.fact(:kernel).stubs(:value).returns(os) + Facter::Manufacturer.expects(:get_dmi_table).returns(output) + + Facter::Manufacturer.dmi_find_system_info(query) + + return Facter.value("product_name_#{os}") + end + + it "should return the same result with smbios than with dmidecode" do + find_product_name("FreeBSD").should_not == nil + find_product_name("FreeBSD").should == find_product_name("SunOS") + end + + it "should find information on Windows" do + Facter.fact(:kernel).stubs(:value).returns("windows") + require 'facter/util/wmi' + + bios = stubs 'bios' + bios.stubs(:Manufacturer).returns("Phoenix Technologies LTD") + bios.stubs(:Serialnumber).returns("56 4d 40 2b 4d 81 94 d6-e6 c5 56 a4 56 0c 9e 9f") + + product = stubs 'product' + product.stubs(:Name).returns("VMware Virtual Platform") + + wmi = stubs 'wmi' + wmi.stubs(:ExecQuery).with("select * from Win32_Bios").returns([bios]) + wmi.stubs(:ExecQuery).with("select * from Win32_Bios").returns([bios]) + wmi.stubs(:ExecQuery).with("select * from Win32_ComputerSystemProduct").returns([product]) + + Facter::Util::WMI.stubs(:connect).returns(wmi) + Facter.value(:manufacturer).should == "Phoenix Technologies LTD" + Facter.value(:serialnumber).should == "56 4d 40 2b 4d 81 94 d6-e6 c5 56 a4 56 0c 9e 9f" + Facter.value(:productname).should == "VMware Virtual Platform" + end end diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index fca56d292b..005ccfd321 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -5,295 +5,295 @@ require 'facter/util/resolution' describe Facter::Util::Resolution do - it "should require a name" do - lambda { Facter::Util::Resolution.new }.should raise_error(ArgumentError) + it "should require a name" do + lambda { Facter::Util::Resolution.new }.should raise_error(ArgumentError) + end + + it "should have a name" do + Facter::Util::Resolution.new("yay").name.should == "yay" + end + + it "should have a method for setting the weight" do + Facter::Util::Resolution.new("yay").should respond_to(:has_weight) + end + + it "should have a method for setting the code" do + Facter::Util::Resolution.new("yay").should respond_to(:setcode) + end + + it "should support a timeout value" do + Facter::Util::Resolution.new("yay").should respond_to(:timeout=) + end + + it "should default to a timeout of 0 seconds" do + Facter::Util::Resolution.new("yay").limit.should == 0 + end + + it "should default to nil for code" do + Facter::Util::Resolution.new("yay").code.should be_nil + end + + it "should default to nil for interpreter" do + Facter.expects(:warnonce).with("The 'Facter::Util::Resolution.interpreter' method is deprecated and will be removed in a future version.") + Facter::Util::Resolution.new("yay").interpreter.should be_nil + end + + it "should provide a 'limit' method that returns the timeout" do + res = Facter::Util::Resolution.new("yay") + res.timeout = "testing" + res.limit.should == "testing" + end + + describe "when setting the code" do + before do + Facter.stubs(:warnonce) + @resolve = Facter::Util::Resolution.new("yay") end - it "should have a name" do - Facter::Util::Resolution.new("yay").name.should == "yay" + it "should deprecate the interpreter argument to 'setcode'" do + Facter.expects(:warnonce).with("The interpreter parameter to 'setcode' is deprecated and will be removed in a future version.") + @resolve.setcode "foo", "bar" + @resolve.interpreter.should == "bar" end - it "should have a method for setting the weight" do - Facter::Util::Resolution.new("yay").should respond_to(:has_weight) + it "should deprecate the interpreter= method" do + Facter.expects(:warnonce).with("The 'Facter::Util::Resolution.interpreter=' method is deprecated and will be removed in a future version.") + @resolve.interpreter = "baz" + @resolve.interpreter.should == "baz" end - it "should have a method for setting the code" do - Facter::Util::Resolution.new("yay").should respond_to(:setcode) + it "should deprecate the interpreter method" do + Facter.expects(:warnonce).with("The 'Facter::Util::Resolution.interpreter' method is deprecated and will be removed in a future version.") + @resolve.interpreter end - it "should support a timeout value" do - Facter::Util::Resolution.new("yay").should respond_to(:timeout=) + it "should set the code to any provided string" do + @resolve.setcode "foo" + @resolve.code.should == "foo" end - it "should default to a timeout of 0 seconds" do - Facter::Util::Resolution.new("yay").limit.should == 0 + it "should set the code to any provided block" do + block = lambda { } + @resolve.setcode(&block) + @resolve.code.should equal(block) end - it "should default to nil for code" do - Facter::Util::Resolution.new("yay").code.should be_nil + it "should prefer the string over a block" do + @resolve.setcode("foo") { } + @resolve.code.should == "foo" end - it "should default to nil for interpreter" do - Facter.expects(:warnonce).with("The 'Facter::Util::Resolution.interpreter' method is deprecated and will be removed in a future version.") - Facter::Util::Resolution.new("yay").interpreter.should be_nil + it "should fail if neither a string nor block has been provided" do + lambda { @resolve.setcode }.should raise_error(ArgumentError) end + end - it "should provide a 'limit' method that returns the timeout" do - res = Facter::Util::Resolution.new("yay") - res.timeout = "testing" - res.limit.should == "testing" - end - - describe "when setting the code" do - before do - Facter.stubs(:warnonce) - @resolve = Facter::Util::Resolution.new("yay") - end - - it "should deprecate the interpreter argument to 'setcode'" do - Facter.expects(:warnonce).with("The interpreter parameter to 'setcode' is deprecated and will be removed in a future version.") - @resolve.setcode "foo", "bar" - @resolve.interpreter.should == "bar" - end + it "should be able to return a value" do + Facter::Util::Resolution.new("yay").should respond_to(:value) + end - it "should deprecate the interpreter= method" do - Facter.expects(:warnonce).with("The 'Facter::Util::Resolution.interpreter=' method is deprecated and will be removed in a future version.") - @resolve.interpreter = "baz" - @resolve.interpreter.should == "baz" - end + describe "when returning the value" do + before do + @resolve = Facter::Util::Resolution.new("yay") + end - it "should deprecate the interpreter method" do - Facter.expects(:warnonce).with("The 'Facter::Util::Resolution.interpreter' method is deprecated and will be removed in a future version.") - @resolve.interpreter - end + describe "and setcode has not been called" do + it "should return nil" do + Facter::Util::Resolution.expects(:exec).with(nil, nil).never + @resolve.value.should be_nil + end + end - it "should set the code to any provided string" do - @resolve.setcode "foo" - @resolve.code.should == "foo" + describe "and the code is a string" do + describe "on windows" do + before do + Facter::Util::Resolution::WINDOWS = true end - it "should set the code to any provided block" do - block = lambda { } - @resolve.setcode(&block) - @resolve.code.should equal(block) - end + it "should return the result of executing the code" do + @resolve.setcode "/bin/foo" + Facter::Util::Resolution.expects(:exec).once.with("/bin/foo").returns "yup" - it "should prefer the string over a block" do - @resolve.setcode("foo") { } - @resolve.code.should == "foo" + @resolve.value.should == "yup" end - it "should fail if neither a string nor block has been provided" do - lambda { @resolve.setcode }.should raise_error(ArgumentError) + it "should return nil if the value is an empty string" do + @resolve.setcode "/bin/foo" + Facter::Util::Resolution.expects(:exec).once.returns "" + @resolve.value.should be_nil end - end - - it "should be able to return a value" do - Facter::Util::Resolution.new("yay").should respond_to(:value) - end + end - describe "when returning the value" do + describe "on non-windows systems" do before do - @resolve = Facter::Util::Resolution.new("yay") + Facter::Util::Resolution::WINDOWS = false end - describe "and setcode has not been called" do - it "should return nil" do - Facter::Util::Resolution.expects(:exec).with(nil, nil).never - @resolve.value.should be_nil - end - end + it "should return the result of executing the code" do + @resolve.setcode "/bin/foo" + Facter::Util::Resolution.expects(:exec).once.with("/bin/foo").returns "yup" - describe "and the code is a string" do - describe "on windows" do - before do - Facter::Util::Resolution::WINDOWS = true - end - - it "should return the result of executing the code" do - @resolve.setcode "/bin/foo" - Facter::Util::Resolution.expects(:exec).once.with("/bin/foo").returns "yup" - - @resolve.value.should == "yup" - end - - it "should return nil if the value is an empty string" do - @resolve.setcode "/bin/foo" - Facter::Util::Resolution.expects(:exec).once.returns "" - @resolve.value.should be_nil - end - end - - describe "on non-windows systems" do - before do - Facter::Util::Resolution::WINDOWS = false - end - - it "should return the result of executing the code" do - @resolve.setcode "/bin/foo" - Facter::Util::Resolution.expects(:exec).once.with("/bin/foo").returns "yup" - - @resolve.value.should == "yup" - end - - it "should return nil if the value is an empty string" do - @resolve.setcode "/bin/foo" - Facter::Util::Resolution.expects(:exec).once.returns "" - @resolve.value.should be_nil - end - end + @resolve.value.should == "yup" end - describe "and the code is a block" do - it "should warn but not fail if the code fails" do - @resolve.setcode { raise "feh" } - @resolve.expects(:warn) - @resolve.value.should be_nil - end - - it "should return the value returned by the block" do - @resolve.setcode { "yayness" } - @resolve.value.should == "yayness" - end - - it "should return nil if the value is an empty string" do - @resolve.setcode { "" } - @resolve.value.should be_nil - end - - it "should return nil if the value is an empty block" do - @resolve.setcode { "" } - @resolve.value.should be_nil - end - - it "should use its limit method to determine the timeout, to avoid conflict when a 'timeout' method exists for some other reason" do - @resolve.expects(:timeout).never - @resolve.expects(:limit).returns "foo" - Timeout.expects(:timeout).with("foo") - - @resolve.setcode { sleep 2; "raise This is a test"} - @resolve.value - end - - it "should timeout after the provided timeout" do - @resolve.expects(:warn) - @resolve.timeout = 0.1 - @resolve.setcode { sleep 2; raise "This is a test" } - Thread.expects(:new).yields - - @resolve.value.should be_nil - end - - it "should waitall to avoid zombies if the timeout is exceeded" do - @resolve.stubs(:warn) - @resolve.timeout = 0.1 - @resolve.setcode { sleep 2; raise "This is a test" } - - Thread.expects(:new).yields - Process.expects(:waitall) - - @resolve.value - end + it "should return nil if the value is an empty string" do + @resolve.setcode "/bin/foo" + Facter::Util::Resolution.expects(:exec).once.returns "" + @resolve.value.should be_nil end + end end - it "should return its value when converted to a string" do - @resolve = Facter::Util::Resolution.new("yay") - @resolve.expects(:value).returns "myval" - @resolve.to_s.should == "myval" + describe "and the code is a block" do + it "should warn but not fail if the code fails" do + @resolve.setcode { raise "feh" } + @resolve.expects(:warn) + @resolve.value.should be_nil + end + + it "should return the value returned by the block" do + @resolve.setcode { "yayness" } + @resolve.value.should == "yayness" + end + + it "should return nil if the value is an empty string" do + @resolve.setcode { "" } + @resolve.value.should be_nil + end + + it "should return nil if the value is an empty block" do + @resolve.setcode { "" } + @resolve.value.should be_nil + end + + it "should use its limit method to determine the timeout, to avoid conflict when a 'timeout' method exists for some other reason" do + @resolve.expects(:timeout).never + @resolve.expects(:limit).returns "foo" + Timeout.expects(:timeout).with("foo") + + @resolve.setcode { sleep 2; "raise This is a test"} + @resolve.value + end + + it "should timeout after the provided timeout" do + @resolve.expects(:warn) + @resolve.timeout = 0.1 + @resolve.setcode { sleep 2; raise "This is a test" } + Thread.expects(:new).yields + + @resolve.value.should be_nil + end + + it "should waitall to avoid zombies if the timeout is exceeded" do + @resolve.stubs(:warn) + @resolve.timeout = 0.1 + @resolve.setcode { sleep 2; raise "This is a test" } + + Thread.expects(:new).yields + Process.expects(:waitall) + + @resolve.value + end end - - it "should allow the adding of confines" do - Facter::Util::Resolution.new("yay").should respond_to(:confine) + end + + it "should return its value when converted to a string" do + @resolve = Facter::Util::Resolution.new("yay") + @resolve.expects(:value).returns "myval" + @resolve.to_s.should == "myval" + end + + it "should allow the adding of confines" do + Facter::Util::Resolution.new("yay").should respond_to(:confine) + end + + it "should provide a method for returning the number of confines" do + @resolve = Facter::Util::Resolution.new("yay") + @resolve.confine "one" => "foo", "two" => "fee" + @resolve.weight.should == 2 + end + + it "should return 0 confines when no confines have been added" do + Facter::Util::Resolution.new("yay").weight.should == 0 + end + + it "should provide a way to set the weight" do + @resolve = Facter::Util::Resolution.new("yay") + @resolve.has_weight(45) + @resolve.weight.should == 45 + end + + it "should allow the weight to override the number of confines" do + @resolve = Facter::Util::Resolution.new("yay") + @resolve.confine "one" => "foo", "two" => "fee" + @resolve.weight.should == 2 + @resolve.has_weight(45) + @resolve.weight.should == 45 + end + + it "should have a method for determining if it is suitable" do + Facter::Util::Resolution.new("yay").should respond_to(:suitable?) + end + + describe "when adding confines" do + before do + @resolve = Facter::Util::Resolution.new("yay") end - it "should provide a method for returning the number of confines" do - @resolve = Facter::Util::Resolution.new("yay") - @resolve.confine "one" => "foo", "two" => "fee" - @resolve.weight.should == 2 + it "should accept a hash of fact names and values" do + lambda { @resolve.confine :one => "two" }.should_not raise_error end - it "should return 0 confines when no confines have been added" do - Facter::Util::Resolution.new("yay").weight.should == 0 - end + it "should create a Util::Confine instance for every argument in the provided hash" do + Facter::Util::Confine.expects(:new).with("one", "foo") + Facter::Util::Confine.expects(:new).with("two", "fee") - it "should provide a way to set the weight" do - @resolve = Facter::Util::Resolution.new("yay") - @resolve.has_weight(45) - @resolve.weight.should == 45 + @resolve.confine "one" => "foo", "two" => "fee" end - it "should allow the weight to override the number of confines" do - @resolve = Facter::Util::Resolution.new("yay") - @resolve.confine "one" => "foo", "two" => "fee" - @resolve.weight.should == 2 - @resolve.has_weight(45) - @resolve.weight.should == 45 - end + end - it "should have a method for determining if it is suitable" do - Facter::Util::Resolution.new("yay").should respond_to(:suitable?) + describe "when determining suitability" do + before do + @resolve = Facter::Util::Resolution.new("yay") end - describe "when adding confines" do - before do - @resolve = Facter::Util::Resolution.new("yay") - end - - it "should accept a hash of fact names and values" do - lambda { @resolve.confine :one => "two" }.should_not raise_error - end - - it "should create a Util::Confine instance for every argument in the provided hash" do - Facter::Util::Confine.expects(:new).with("one", "foo") - Facter::Util::Confine.expects(:new).with("two", "fee") - - @resolve.confine "one" => "foo", "two" => "fee" - end - + it "should always be suitable if no confines have been added" do + @resolve.should be_suitable end - describe "when determining suitability" do - before do - @resolve = Facter::Util::Resolution.new("yay") - end + it "should be unsuitable if any provided confines return false" do + confine1 = mock 'confine1', :true? => true + confine2 = mock 'confine2', :true? => false + Facter::Util::Confine.expects(:new).times(2).returns(confine1).then.returns(confine2) + @resolve.confine :one => :two, :three => :four - it "should always be suitable if no confines have been added" do - @resolve.should be_suitable - end - - it "should be unsuitable if any provided confines return false" do - confine1 = mock 'confine1', :true? => true - confine2 = mock 'confine2', :true? => false - Facter::Util::Confine.expects(:new).times(2).returns(confine1).then.returns(confine2) - @resolve.confine :one => :two, :three => :four - - @resolve.should_not be_suitable - end + @resolve.should_not be_suitable + end - it "should be suitable if all provided confines return true" do - confine1 = mock 'confine1', :true? => true - confine2 = mock 'confine2', :true? => true - Facter::Util::Confine.expects(:new).times(2).returns(confine1).then.returns(confine2) - @resolve.confine :one => :two, :three => :four + it "should be suitable if all provided confines return true" do + confine1 = mock 'confine1', :true? => true + confine2 = mock 'confine2', :true? => true + Facter::Util::Confine.expects(:new).times(2).returns(confine1).then.returns(confine2) + @resolve.confine :one => :two, :three => :four - @resolve.should be_suitable - end + @resolve.should be_suitable end + end - it "should have a class method for executing code" do - Facter::Util::Resolution.should respond_to(:exec) - end + it "should have a class method for executing code" do + Facter::Util::Resolution.should respond_to(:exec) + end - # It's not possible, AFAICT, to mock %x{}, so I can't really test this bit. - describe "when executing code" do - it "should deprecate the interpreter parameter" do - Facter.expects(:warnonce).with("The interpreter parameter to 'exec' is deprecated and will be removed in a future version.") - Facter::Util::Resolution.exec("/something", "/bin/perl") - end + # It's not possible, AFAICT, to mock %x{}, so I can't really test this bit. + describe "when executing code" do + it "should deprecate the interpreter parameter" do + Facter.expects(:warnonce).with("The interpreter parameter to 'exec' is deprecated and will be removed in a future version.") + Facter::Util::Resolution.exec("/something", "/bin/perl") + end - it "should execute the binary" do - Facter::Util::Resolution.exec("echo foo").should == "foo" - end + it "should execute the binary" do + Facter::Util::Resolution.exec("echo foo").should == "foo" end + end end diff --git a/spec/unit/util/virtual_spec.rb b/spec/unit/util/virtual_spec.rb index 82eb71ca8a..52d656824e 100644 --- a/spec/unit/util/virtual_spec.rb +++ b/spec/unit/util/virtual_spec.rb @@ -4,172 +4,172 @@ describe Facter::Util::Virtual do - after do - Facter.clear - end - it "should detect openvz" do - FileTest.stubs(:directory?).with("/proc/vz").returns(true) - Dir.stubs(:glob).with("/proc/vz/*").returns(['vzquota']) - Facter::Util::Virtual.should be_openvz - end - - it "should not detect openvz when /proc/lve/list is present" do - FileTest.stubs(:file?).with("/proc/lve/list").returns(true) - Facter::Util::Virtual.should_not be_openvz - end - - it "should not detect openvz when /proc/vz/ is empty" do - FileTest.stubs(:file?).with("/proc/lve/list").returns(false) - FileTest.stubs(:directory?).with("/proc/vz").returns(true) - Dir.stubs(:glob).with("/proc/vz/*").returns([]) - Facter::Util::Virtual.should_not be_openvz - end - - it "should identify openvzhn when /proc/self/status has envID of 0" do - Facter::Util::Virtual.stubs(:openvz?).returns(true) + after do + Facter.clear + end + it "should detect openvz" do + FileTest.stubs(:directory?).with("/proc/vz").returns(true) + Dir.stubs(:glob).with("/proc/vz/*").returns(['vzquota']) + Facter::Util::Virtual.should be_openvz + end + + it "should not detect openvz when /proc/lve/list is present" do + FileTest.stubs(:file?).with("/proc/lve/list").returns(true) + Facter::Util::Virtual.should_not be_openvz + end + + it "should not detect openvz when /proc/vz/ is empty" do + FileTest.stubs(:file?).with("/proc/lve/list").returns(false) + FileTest.stubs(:directory?).with("/proc/vz").returns(true) + Dir.stubs(:glob).with("/proc/vz/*").returns([]) + Facter::Util::Virtual.should_not be_openvz + end + + it "should identify openvzhn when /proc/self/status has envID of 0" do + Facter::Util::Virtual.stubs(:openvz?).returns(true) + FileTest.stubs(:exists?).with("/proc/self/status").returns(true) + Facter::Util::Resolution.stubs(:exec).with('grep "envID" /proc/self/status').returns("envID: 0") + Facter::Util::Virtual.openvz_type().should == "openvzhn" + end + + it "should identify openvzve when /proc/self/status has envID of 0" do + Facter::Util::Virtual.stubs(:openvz?).returns(true) + FileTest.stubs(:exists?).with('/proc/self/status').returns(true) + Facter::Util::Resolution.stubs(:exec).with('grep "envID" /proc/self/status').returns("envID: 666") + Facter::Util::Virtual.openvz_type().should == "openvzve" + end + + it "should not attempt to identify openvz when /proc/self/status has no envID" do + Facter::Util::Virtual.stubs(:openvz?).returns(true) + FileTest.stubs(:exists?).with('/proc/self/status').returns(true) + Facter::Util::Resolution.stubs(:exec).with('grep "envID" /proc/self/status').returns("") + Facter::Util::Virtual.openvz_type().should be_nil + end + + it "should identify Solaris zones when non-global zone" do + Facter::Util::Resolution.stubs(:exec).with("/sbin/zonename").returns("somezone") + Facter::Util::Virtual.should be_zone + end + + it "should not identify Solaris zones when global zone" do + Facter::Util::Resolution.stubs(:exec).with("/sbin/zonename").returns("global") + Facter::Util::Virtual.should_not be_zone + end + + it "should not detect vserver if no self status" do + FileTest.stubs(:exists?).with("/proc/self/status").returns(false) + Facter::Util::Virtual.should_not be_vserver + end + + it "should detect vserver when vxid present in process status" do + FileTest.stubs(:exists?).with("/proc/self/status").returns(true) + File.stubs(:read).with("/proc/self/status").returns("VxID: 42\n") + Facter::Util::Virtual.should be_vserver + end + + it "should detect vserver when s_context present in process status" do + FileTest.stubs(:exists?).with("/proc/self/status").returns(true) + File.stubs(:read).with("/proc/self/status").returns("s_context: 42\n") + Facter::Util::Virtual.should be_vserver + end + + it "should not detect vserver when vserver flags not present in process status" do + FileTest.stubs(:exists?).with("/proc/self/status").returns(true) + File.stubs(:read).with("/proc/self/status").returns("wibble: 42\n") + Facter::Util::Virtual.should_not be_vserver + end + + fixture_path = File.join(SPECDIR, 'fixtures', 'virtual', 'proc_self_status') + + test_cases = [ + [File.join(fixture_path, 'vserver_2_1', 'guest'), true, 'vserver 2.1 guest'], + [File.join(fixture_path, 'vserver_2_1', 'host'), true, 'vserver 2.1 host'], + [File.join(fixture_path, 'vserver_2_3', 'guest'), true, 'vserver 2.3 guest'], + [File.join(fixture_path, 'vserver_2_3', 'host'), true, 'vserver 2.3 host'] + ] + + test_cases.each do |status_file, expected, description| + describe "with /proc/self/status from #{description}" do + it "should detect vserver as #{expected.inspect}" do + status = File.read(status_file) FileTest.stubs(:exists?).with("/proc/self/status").returns(true) - Facter::Util::Resolution.stubs(:exec).with('grep "envID" /proc/self/status').returns("envID: 0") - Facter::Util::Virtual.openvz_type().should == "openvzhn" - end - - it "should identify openvzve when /proc/self/status has envID of 0" do - Facter::Util::Virtual.stubs(:openvz?).returns(true) - FileTest.stubs(:exists?).with('/proc/self/status').returns(true) - Facter::Util::Resolution.stubs(:exec).with('grep "envID" /proc/self/status').returns("envID: 666") - Facter::Util::Virtual.openvz_type().should == "openvzve" - end - - it "should not attempt to identify openvz when /proc/self/status has no envID" do - Facter::Util::Virtual.stubs(:openvz?).returns(true) - FileTest.stubs(:exists?).with('/proc/self/status').returns(true) - Facter::Util::Resolution.stubs(:exec).with('grep "envID" /proc/self/status').returns("") - Facter::Util::Virtual.openvz_type().should be_nil - end - - it "should identify Solaris zones when non-global zone" do - Facter::Util::Resolution.stubs(:exec).with("/sbin/zonename").returns("somezone") - Facter::Util::Virtual.should be_zone - end - - it "should not identify Solaris zones when global zone" do - Facter::Util::Resolution.stubs(:exec).with("/sbin/zonename").returns("global") - Facter::Util::Virtual.should_not be_zone - end - - it "should not detect vserver if no self status" do - FileTest.stubs(:exists?).with("/proc/self/status").returns(false) - Facter::Util::Virtual.should_not be_vserver - end - - it "should detect vserver when vxid present in process status" do - FileTest.stubs(:exists?).with("/proc/self/status").returns(true) - File.stubs(:read).with("/proc/self/status").returns("VxID: 42\n") - Facter::Util::Virtual.should be_vserver - end - - it "should detect vserver when s_context present in process status" do - FileTest.stubs(:exists?).with("/proc/self/status").returns(true) - File.stubs(:read).with("/proc/self/status").returns("s_context: 42\n") - Facter::Util::Virtual.should be_vserver - end - - it "should not detect vserver when vserver flags not present in process status" do - FileTest.stubs(:exists?).with("/proc/self/status").returns(true) - File.stubs(:read).with("/proc/self/status").returns("wibble: 42\n") - Facter::Util::Virtual.should_not be_vserver - end - - fixture_path = File.join(SPECDIR, 'fixtures', 'virtual', 'proc_self_status') - - test_cases = [ - [File.join(fixture_path, 'vserver_2_1', 'guest'), true, 'vserver 2.1 guest'], - [File.join(fixture_path, 'vserver_2_1', 'host'), true, 'vserver 2.1 host'], - [File.join(fixture_path, 'vserver_2_3', 'guest'), true, 'vserver 2.3 guest'], - [File.join(fixture_path, 'vserver_2_3', 'host'), true, 'vserver 2.3 host'] - ] - - test_cases.each do |status_file, expected, description| - describe "with /proc/self/status from #{description}" do - it "should detect vserver as #{expected.inspect}" do - status = File.read(status_file) - FileTest.stubs(:exists?).with("/proc/self/status").returns(true) - File.stubs(:read).with("/proc/self/status").returns(status) - Facter::Util::Virtual.vserver?.should == expected - end - end - end - - it "should identify vserver_host when /proc/virtual exists" do - Facter::Util::Virtual.expects(:vserver?).returns(true) - FileTest.stubs(:exists?).with("/proc/virtual").returns(true) - Facter::Util::Virtual.vserver_type().should == "vserver_host" - end - - it "should identify vserver_type as vserver when /proc/virtual does not exist" do - Facter::Util::Virtual.expects(:vserver?).returns(true) - FileTest.stubs(:exists?).with("/proc/virtual").returns(false) - Facter::Util::Virtual.vserver_type().should == "vserver" - end - - it "should detect xen when /proc/sys/xen exists" do - FileTest.expects(:exists?).with("/proc/sys/xen").returns(true) - Facter::Util::Virtual.should be_xen - end - - it "should detect xen when /sys/bus/xen exists" do - FileTest.expects(:exists?).with("/proc/sys/xen").returns(false) - FileTest.expects(:exists?).with("/sys/bus/xen").returns(true) - Facter::Util::Virtual.should be_xen - end - - it "should detect xen when /proc/xen exists" do - FileTest.expects(:exists?).with("/proc/sys/xen").returns(false) - FileTest.expects(:exists?).with("/sys/bus/xen").returns(false) - FileTest.expects(:exists?).with("/proc/xen").returns(true) - Facter::Util::Virtual.should be_xen - end - - it "should not detect xen when no sysfs/proc xen directories exist" do - FileTest.expects(:exists?).with("/proc/sys/xen").returns(false) - FileTest.expects(:exists?).with("/sys/bus/xen").returns(false) - FileTest.expects(:exists?).with("/proc/xen").returns(false) - Facter::Util::Virtual.should_not be_xen - end - - it "should detect kvm" do - FileTest.stubs(:exists?).with("/proc/cpuinfo").returns(true) - File.stubs(:read).with("/proc/cpuinfo").returns("model name : QEMU Virtual CPU version 0.9.1\n") - Facter::Util::Virtual.should be_kvm - end - - it "should detect kvm on FreeBSD" do - FileTest.stubs(:exists?).with("/proc/cpuinfo").returns(false) - Facter.fact(:kernel).stubs(:value).returns("FreeBSD") - Facter::Util::Resolution.stubs(:exec).with("/sbin/sysctl -n hw.model").returns("QEMU Virtual CPU version 0.12.4") - Facter::Util::Virtual.should be_kvm - end - - it "should identify FreeBSD jail when in jail" do - Facter.fact(:kernel).stubs(:value).returns("FreeBSD") - Facter::Util::Resolution.stubs(:exec).with("/sbin/sysctl -n security.jail.jailed").returns("1") - Facter::Util::Virtual.should be_jail - end - - it "should not identify GNU/kFreeBSD jail when not in jail" do - Facter.fact(:kernel).stubs(:value).returns("GNU/kFreeBSD") - Facter::Util::Resolution.stubs(:exec).with("/bin/sysctl -n security.jail.jailed").returns("0") - Facter::Util::Virtual.should_not be_jail - end - - it "should detect hpvm on HP-UX" do - Facter.fact(:kernel).stubs(:value).returns("HP-UX") - Facter::Util::Resolution.stubs(:exec).with("/usr/bin/getconf MACHINE_MODEL").returns('ia64 hp server Integrity Virtual Machine') - Facter::Util::Virtual.should be_hpvm - end - - it "should not detect hpvm on HP-UX when not in hpvm" do - Facter.fact(:kernel).stubs(:value).returns("HP-UX") - Facter::Util::Resolution.stubs(:exec).with("/usr/bin/getconf MACHINE_MODEL").returns('ia64 hp server rx660') - Facter::Util::Virtual.should_not be_hpvm - end + File.stubs(:read).with("/proc/self/status").returns(status) + Facter::Util::Virtual.vserver?.should == expected + end + end + end + + it "should identify vserver_host when /proc/virtual exists" do + Facter::Util::Virtual.expects(:vserver?).returns(true) + FileTest.stubs(:exists?).with("/proc/virtual").returns(true) + Facter::Util::Virtual.vserver_type().should == "vserver_host" + end + + it "should identify vserver_type as vserver when /proc/virtual does not exist" do + Facter::Util::Virtual.expects(:vserver?).returns(true) + FileTest.stubs(:exists?).with("/proc/virtual").returns(false) + Facter::Util::Virtual.vserver_type().should == "vserver" + end + + it "should detect xen when /proc/sys/xen exists" do + FileTest.expects(:exists?).with("/proc/sys/xen").returns(true) + Facter::Util::Virtual.should be_xen + end + + it "should detect xen when /sys/bus/xen exists" do + FileTest.expects(:exists?).with("/proc/sys/xen").returns(false) + FileTest.expects(:exists?).with("/sys/bus/xen").returns(true) + Facter::Util::Virtual.should be_xen + end + + it "should detect xen when /proc/xen exists" do + FileTest.expects(:exists?).with("/proc/sys/xen").returns(false) + FileTest.expects(:exists?).with("/sys/bus/xen").returns(false) + FileTest.expects(:exists?).with("/proc/xen").returns(true) + Facter::Util::Virtual.should be_xen + end + + it "should not detect xen when no sysfs/proc xen directories exist" do + FileTest.expects(:exists?).with("/proc/sys/xen").returns(false) + FileTest.expects(:exists?).with("/sys/bus/xen").returns(false) + FileTest.expects(:exists?).with("/proc/xen").returns(false) + Facter::Util::Virtual.should_not be_xen + end + + it "should detect kvm" do + FileTest.stubs(:exists?).with("/proc/cpuinfo").returns(true) + File.stubs(:read).with("/proc/cpuinfo").returns("model name : QEMU Virtual CPU version 0.9.1\n") + Facter::Util::Virtual.should be_kvm + end + + it "should detect kvm on FreeBSD" do + FileTest.stubs(:exists?).with("/proc/cpuinfo").returns(false) + Facter.fact(:kernel).stubs(:value).returns("FreeBSD") + Facter::Util::Resolution.stubs(:exec).with("/sbin/sysctl -n hw.model").returns("QEMU Virtual CPU version 0.12.4") + Facter::Util::Virtual.should be_kvm + end + + it "should identify FreeBSD jail when in jail" do + Facter.fact(:kernel).stubs(:value).returns("FreeBSD") + Facter::Util::Resolution.stubs(:exec).with("/sbin/sysctl -n security.jail.jailed").returns("1") + Facter::Util::Virtual.should be_jail + end + + it "should not identify GNU/kFreeBSD jail when not in jail" do + Facter.fact(:kernel).stubs(:value).returns("GNU/kFreeBSD") + Facter::Util::Resolution.stubs(:exec).with("/bin/sysctl -n security.jail.jailed").returns("0") + Facter::Util::Virtual.should_not be_jail + end + + it "should detect hpvm on HP-UX" do + Facter.fact(:kernel).stubs(:value).returns("HP-UX") + Facter::Util::Resolution.stubs(:exec).with("/usr/bin/getconf MACHINE_MODEL").returns('ia64 hp server Integrity Virtual Machine') + Facter::Util::Virtual.should be_hpvm + end + + it "should not detect hpvm on HP-UX when not in hpvm" do + Facter.fact(:kernel).stubs(:value).returns("HP-UX") + Facter::Util::Resolution.stubs(:exec).with("/usr/bin/getconf MACHINE_MODEL").returns('ia64 hp server rx660') + Facter::Util::Virtual.should_not be_hpvm + end end diff --git a/spec/unit/util/vlans_spec.rb b/spec/unit/util/vlans_spec.rb index 0331234993..6f76b73637 100755 --- a/spec/unit/util/vlans_spec.rb +++ b/spec/unit/util/vlans_spec.rb @@ -5,10 +5,10 @@ require 'facter/util/vlans' describe Facter::Util::Vlans do - it "should return a list of vlans on Linux" do - sample_output_file = File.dirname(__FILE__) + '/../data/linux_vlan_config' - linux_vlanconfig = File.new(sample_output_file).read(); - Facter::Util::Vlans.stubs(:get_vlan_config).returns(linux_vlanconfig) - Facter::Util::Vlans.get_vlans().should == %{400,300,200,100} - end + it "should return a list of vlans on Linux" do + sample_output_file = File.dirname(__FILE__) + '/../data/linux_vlan_config' + linux_vlanconfig = File.new(sample_output_file).read(); + Facter::Util::Vlans.stubs(:get_vlan_config).returns(linux_vlanconfig) + Facter::Util::Vlans.get_vlans().should == %{400,300,200,100} + end end diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index 72182c0df5..8090358c76 100644 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -6,302 +6,302 @@ describe "Virtual fact" do before do - Facter::Util::Virtual.stubs(:zone?).returns(false) - Facter::Util::Virtual.stubs(:openvz?).returns(false) - Facter::Util::Virtual.stubs(:vserver?).returns(false) - Facter::Util::Virtual.stubs(:xen?).returns(false) - Facter::Util::Virtual.stubs(:kvm?).returns(false) - Facter::Util::Virtual.stubs(:hpvm?).returns(false) - Facter::Util::Virtual.stubs(:zlinux?).returns(false) + Facter::Util::Virtual.stubs(:zone?).returns(false) + Facter::Util::Virtual.stubs(:openvz?).returns(false) + Facter::Util::Virtual.stubs(:vserver?).returns(false) + Facter::Util::Virtual.stubs(:xen?).returns(false) + Facter::Util::Virtual.stubs(:kvm?).returns(false) + Facter::Util::Virtual.stubs(:hpvm?).returns(false) + Facter::Util::Virtual.stubs(:zlinux?).returns(false) end it "should be zone on Solaris when a zone" do - Facter.fact(:kernel).stubs(:value).returns("SunOS") - Facter::Util::Virtual.stubs(:zone?).returns(true) - Facter::Util::Virtual.stubs(:vserver?).returns(false) - Facter::Util::Virtual.stubs(:xen?).returns(false) - Facter.fact(:virtual).value.should == "zone" + Facter.fact(:kernel).stubs(:value).returns("SunOS") + Facter::Util::Virtual.stubs(:zone?).returns(true) + Facter::Util::Virtual.stubs(:vserver?).returns(false) + Facter::Util::Virtual.stubs(:xen?).returns(false) + Facter.fact(:virtual).value.should == "zone" end it "should be jail on FreeBSD when a jail in kvm" do - Facter.fact(:kernel).stubs(:value).returns("FreeBSD") - Facter::Util::Virtual.stubs(:jail?).returns(true) - Facter::Util::Virtual.stubs(:kvm?).returns(true) - Facter.fact(:virtual).value.should == "jail" + Facter.fact(:kernel).stubs(:value).returns("FreeBSD") + Facter::Util::Virtual.stubs(:jail?).returns(true) + Facter::Util::Virtual.stubs(:kvm?).returns(true) + Facter.fact(:virtual).value.should == "jail" end it "should be hpvm on HP-UX when in HP-VM" do - Facter.fact(:kernel).stubs(:value).returns("HP-UX") - Facter::Util::Virtual.stubs(:hpvm?).returns(true) - Facter.fact(:virtual).value.should == "hpvm" + Facter.fact(:kernel).stubs(:value).returns("HP-UX") + Facter::Util::Virtual.stubs(:hpvm?).returns(true) + Facter.fact(:virtual).value.should == "hpvm" end it "should be zlinux on s390x" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:architecture).stubs(:value).returns("s390x") - Facter::Util::Virtual.stubs(:zlinux?).returns(true) - Facter.fact(:virtual).value.should == "zlinux" + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:architecture).stubs(:value).returns("s390x") + Facter::Util::Virtual.stubs(:zlinux?).returns(true) + Facter.fact(:virtual).value.should == "zlinux" end describe "on Darwin" do - it "should be parallels with Parallels vendor id" do - Facter.fact(:kernel).stubs(:value).returns("Darwin") - Facter::Util::Macosx.stubs(:profiler_data).returns({ "spdisplays_vendor-id" => "0x1ab8" }) - Facter.fact(:virtual).value.should == "parallels" - end - - it "should be parallels with Parallels vendor name" do - Facter.fact(:kernel).stubs(:value).returns("Darwin") - Facter::Util::Macosx.stubs(:profiler_data).returns({ "spdisplays_vendor" => "Parallels" }) - Facter.fact(:virtual).value.should == "parallels" - end - - it "should be vmware with VMWare vendor id" do - Facter.fact(:kernel).stubs(:value).returns("Darwin") - Facter::Util::Macosx.stubs(:profiler_data).returns({ "spdisplays_vendor-id" => "0x15ad" }) - Facter.fact(:virtual).value.should == "vmware" - end - - it "should be vmware with VMWare vendor name" do - Facter.fact(:kernel).stubs(:value).returns("Darwin") - Facter::Util::Macosx.stubs(:profiler_data).returns({ "spdisplays_vendor" => "VMWare" }) - Facter.fact(:virtual).value.should == "vmware" - end - end + it "should be parallels with Parallels vendor id" do + Facter.fact(:kernel).stubs(:value).returns("Darwin") + Facter::Util::Macosx.stubs(:profiler_data).returns({ "spdisplays_vendor-id" => "0x1ab8" }) + Facter.fact(:virtual).value.should == "parallels" + end - describe "on Linux" do + it "should be parallels with Parallels vendor name" do + Facter.fact(:kernel).stubs(:value).returns("Darwin") + Facter::Util::Macosx.stubs(:profiler_data).returns({ "spdisplays_vendor" => "Parallels" }) + Facter.fact(:virtual).value.should == "parallels" + end - before do - Facter::Util::Resolution.stubs(:exec).with("vmware -v").returns false - Facter.fact(:operatingsystem).stubs(:value).returns(true) - # Ensure the tests don't fail on Xen - FileTest.stubs(:exists?).with("/proc/sys/xen").returns false - FileTest.stubs(:exists?).with("/sys/bus/xen").returns false - FileTest.stubs(:exists?).with("/proc/xen").returns false - Facter.fact(:architecture).stubs(:value).returns(true) - end - - it "should be parallels with Parallels vendor id from lspci" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter::Util::Resolution.stubs(:exec).with('lspci').returns("01:00.0 VGA compatible controller: Unknown device 1ab8:4005") - Facter.fact(:virtual).value.should == "parallels" - end - - it "should be parallels with Parallels vendor name from lspci" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter::Util::Resolution.stubs(:exec).with('lspci').returns("01:00.0 VGA compatible controller: Parallels Display Adapter") - Facter.fact(:virtual).value.should == "parallels" - end - - it "should be vmware with VMware vendor name from lspci" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter::Util::Resolution.stubs(:exec).with('lspci').returns("00:0f.0 VGA compatible controller: VMware Inc [VMware SVGA II] PCI Display Adapter") - Facter.fact(:virtual).value.should == "vmware" - end - - it "should be virtualbox with VirtualBox vendor name from lspci" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter::Util::Resolution.stubs(:exec).with('lspci').returns("00:02.0 VGA compatible controller: InnoTek Systemberatung GmbH VirtualBox Graphics Adapter") - Facter.fact(:virtual).value.should == "virtualbox" - end - - it "should be vmware with VMWare vendor name from dmidecode" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) - Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("On Board Device 1 Information\nType: Video\nStatus: Disabled\nDescription: VMware SVGA II") - Facter.fact(:virtual).value.should == "vmware" - end - - it "should be xenhvm with Xen HVM vendor name from lspci" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter::Util::Resolution.stubs(:exec).with('lspci').returns("00:03.0 Unassigned class [ff80]: XenSource, Inc. Xen Platform Device (rev 01)") - Facter.fact(:virtual).value.should == "xenhvm" - end - - it "should be xenhvm with Xen HVM vendor name from dmidecode" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) - Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("System Information\nManufacturer: Xen\nProduct Name: HVM domU") - Facter.fact(:virtual).value.should == "xenhvm" - end - - it "should be parallels with Parallels vendor name from dmidecode" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) - Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("On Board Device Information\nType: Video\nStatus: Disabled\nDescription: Parallels Video Adapter") - Facter.fact(:virtual).value.should == "parallels" - end - - it "should be virtualbox with VirtualBox vendor name from dmidecode" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) - Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("BIOS Information\nVendor: innotek GmbH\nVersion: VirtualBox\n\nSystem Information\nManufacturer: innotek GmbH\nProduct Name: VirtualBox\nFamily: Virtual Machine") - Facter.fact(:virtual).value.should == "virtualbox" - end - - it "should be hyperv with Microsoft vendor name from lspci" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter::Util::Resolution.stubs(:exec).with('lspci').returns("00:08.0 VGA compatible controller: Microsoft Corporation Hyper-V virtual VGA") - Facter.fact(:virtual).value.should == "hyperv" - end - - it "should be hyperv with Microsoft vendor name from dmidecode" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) - Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("System Information\nManufacturer: Microsoft Corporation\nProduct Name: Virtual Machine") - Facter.fact(:virtual).value.should == "hyperv" - end + it "should be vmware with VMWare vendor id" do + Facter.fact(:kernel).stubs(:value).returns("Darwin") + Facter::Util::Macosx.stubs(:profiler_data).returns({ "spdisplays_vendor-id" => "0x15ad" }) + Facter.fact(:virtual).value.should == "vmware" + end + it "should be vmware with VMWare vendor name" do + Facter.fact(:kernel).stubs(:value).returns("Darwin") + Facter::Util::Macosx.stubs(:profiler_data).returns({ "spdisplays_vendor" => "VMWare" }) + Facter.fact(:virtual).value.should == "vmware" + end end - describe "on Solaris" do - before(:each) do - Facter::Util::Resolution.stubs(:exec).with("vmware -v").returns false - end - - it "should be vmware with VMWare vendor name from prtdiag" do - Facter.fact(:kernel).stubs(:value).returns("SunOS") - Facter.fact(:hardwaremodel).stubs(:value).returns(nil) - Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) - Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns(nil) - Facter::Util::Resolution.stubs(:exec).with('prtdiag').returns("System Configuration: VMware, Inc. VMware Virtual Platform") - Facter.fact(:virtual).value.should == "vmware" - end - - it "should be parallels with Parallels vendor name from prtdiag" do - Facter.fact(:kernel).stubs(:value).returns("SunOS") - Facter.fact(:hardwaremodel).stubs(:value).returns(nil) - Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) - Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns(nil) - Facter::Util::Resolution.stubs(:exec).with('prtdiag').returns("System Configuration: Parallels Virtual Platform") - Facter.fact(:virtual).value.should == "parallels" - end - - it "should be virtualbox with VirtualBox vendor name from prtdiag" do - Facter.fact(:kernel).stubs(:value).returns("SunOS") - Facter.fact(:hardwaremodel).stubs(:value).returns(nil) - Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) - Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns(nil) - Facter::Util::Resolution.stubs(:exec).with('prtdiag').returns("System Configuration: innotek GmbH VirtualBox") - Facter.fact(:virtual).value.should == "virtualbox" - end - - it "should be xen0 with xen dom0 files in /proc" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:operatingsystem).stubs(:value).returns("Linux") - Facter.fact(:hardwaremodel).stubs(:value).returns("i386") - Facter::Util::Virtual.expects(:xen?).returns(true) - FileTest.expects(:exists?).with("/proc/xen/xsd_kva").returns(true) - Facter.fact(:virtual).value.should == "xen0" - end - - it "should be xenu with xen domU files in /proc" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:operatingsystem).stubs(:value).returns("Linux") - Facter.fact(:hardwaremodel).stubs(:value).returns("i386") - Facter::Util::Virtual.expects(:xen?).returns(true) - FileTest.expects(:exists?).with("/proc/xen/xsd_kva").returns(false) - FileTest.expects(:exists?).with("/proc/xen/capabilities").returns(true) - Facter.fact(:virtual).value.should == "xenu" - end - end -end -describe "is_virtual fact" do + describe "on Linux" do - it "should be virtual when running on xen" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:virtual).stubs(:value).returns("xenu") - Facter.fact(:is_virtual).value.should == "true" + before do + Facter::Util::Resolution.stubs(:exec).with("vmware -v").returns false + Facter.fact(:operatingsystem).stubs(:value).returns(true) + # Ensure the tests don't fail on Xen + FileTest.stubs(:exists?).with("/proc/sys/xen").returns false + FileTest.stubs(:exists?).with("/sys/bus/xen").returns false + FileTest.stubs(:exists?).with("/proc/xen").returns false + Facter.fact(:architecture).stubs(:value).returns(true) end - it "should be false when running on xen0" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:virtual).stubs(:value).returns("xen0") - Facter.fact(:is_virtual).value.should == "false" + it "should be parallels with Parallels vendor id from lspci" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter::Util::Resolution.stubs(:exec).with('lspci').returns("01:00.0 VGA compatible controller: Unknown device 1ab8:4005") + Facter.fact(:virtual).value.should == "parallels" end - it "should be true when running on xenhvm" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:virtual).stubs(:value).returns("xenhvm") - Facter.fact(:is_virtual).value.should == "true" + it "should be parallels with Parallels vendor name from lspci" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter::Util::Resolution.stubs(:exec).with('lspci').returns("01:00.0 VGA compatible controller: Parallels Display Adapter") + Facter.fact(:virtual).value.should == "parallels" end - it "should be false when running on physical" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:virtual).stubs(:value).returns("physical") - Facter.fact(:is_virtual).value.should == "false" + it "should be vmware with VMware vendor name from lspci" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter::Util::Resolution.stubs(:exec).with('lspci').returns("00:0f.0 VGA compatible controller: VMware Inc [VMware SVGA II] PCI Display Adapter") + Facter.fact(:virtual).value.should == "vmware" end - it "should be true when running on vmware" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:virtual).stubs(:value).returns("vmware") - Facter.fact(:is_virtual).value.should == "true" + it "should be virtualbox with VirtualBox vendor name from lspci" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter::Util::Resolution.stubs(:exec).with('lspci').returns("00:02.0 VGA compatible controller: InnoTek Systemberatung GmbH VirtualBox Graphics Adapter") + Facter.fact(:virtual).value.should == "virtualbox" end - it "should be true when running on virtualbox" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:virtual).stubs(:value).returns("virtualbox") - Facter.fact(:is_virtual).value.should == "true" + it "should be vmware with VMWare vendor name from dmidecode" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) + Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("On Board Device 1 Information\nType: Video\nStatus: Disabled\nDescription: VMware SVGA II") + Facter.fact(:virtual).value.should == "vmware" end - it "should be true when running on openvzve" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:virtual).stubs(:value).returns("openvzve") - Facter.fact(:is_virtual).value.should == "true" + it "should be xenhvm with Xen HVM vendor name from lspci" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter::Util::Resolution.stubs(:exec).with('lspci').returns("00:03.0 Unassigned class [ff80]: XenSource, Inc. Xen Platform Device (rev 01)") + Facter.fact(:virtual).value.should == "xenhvm" end - it "should be true when running on kvm" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:virtual).stubs(:value).returns("kvm") - Facter.fact(:is_virtual).value.should == "true" + it "should be xenhvm with Xen HVM vendor name from dmidecode" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) + Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("System Information\nManufacturer: Xen\nProduct Name: HVM domU") + Facter.fact(:virtual).value.should == "xenhvm" end - it "should be true when running in jail" do - Facter.fact(:kernel).stubs(:value).returns("FreeBSD") - Facter.fact(:virtual).stubs(:value).returns("jail") - Facter.fact(:is_virtual).value.should == "true" + it "should be parallels with Parallels vendor name from dmidecode" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) + Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("On Board Device Information\nType: Video\nStatus: Disabled\nDescription: Parallels Video Adapter") + Facter.fact(:virtual).value.should == "parallels" end - it "should be true when running in zone" do - Facter.fact(:kernel).stubs(:value).returns("SunOS") - Facter.fact(:virtual).stubs(:value).returns("zone") - Facter.fact(:is_virtual).value.should == "true" + it "should be virtualbox with VirtualBox vendor name from dmidecode" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) + Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("BIOS Information\nVendor: innotek GmbH\nVersion: VirtualBox\n\nSystem Information\nManufacturer: innotek GmbH\nProduct Name: VirtualBox\nFamily: Virtual Machine") + Facter.fact(:virtual).value.should == "virtualbox" end - it "should be true when running on hp-vm" do - Facter.fact(:kernel).stubs(:value).returns("HP-UX") - Facter.fact(:virtual).stubs(:value).returns("hpvm") - Facter.fact(:is_virtual).value.should == "true" + it "should be hyperv with Microsoft vendor name from lspci" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter::Util::Resolution.stubs(:exec).with('lspci').returns("00:08.0 VGA compatible controller: Microsoft Corporation Hyper-V virtual VGA") + Facter.fact(:virtual).value.should == "hyperv" end - it "should be true when running on S390" do - Facter.fact(:architecture).stubs(:value).returns("s390x") - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:virtual).stubs(:value).returns("zlinux") - Facter.fact(:is_virtual).value.should == "true" + it "should be hyperv with Microsoft vendor name from dmidecode" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) + Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("System Information\nManufacturer: Microsoft Corporation\nProduct Name: Virtual Machine") + Facter.fact(:virtual).value.should == "hyperv" end - it "should be true when running on parallels" do - Facter.fact(:kernel).stubs(:value).returns("Darwin") - Facter.fact(:virtual).stubs(:value).returns("parallels") - Facter.fact(:is_virtual).value.should == "true" + end + describe "on Solaris" do + before(:each) do + Facter::Util::Resolution.stubs(:exec).with("vmware -v").returns false end - it "should be false on vmware_server" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:virtual).stubs(:value).returns("vmware_server") - Facter.fact(:is_virtual).value.should == "false" + it "should be vmware with VMWare vendor name from prtdiag" do + Facter.fact(:kernel).stubs(:value).returns("SunOS") + Facter.fact(:hardwaremodel).stubs(:value).returns(nil) + Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) + Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns(nil) + Facter::Util::Resolution.stubs(:exec).with('prtdiag').returns("System Configuration: VMware, Inc. VMware Virtual Platform") + Facter.fact(:virtual).value.should == "vmware" end - it "should be false on openvz host nodes" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:virtual).stubs(:value).returns("openvzhn") - Facter.fact(:is_virtual).value.should == "false" + it "should be parallels with Parallels vendor name from prtdiag" do + Facter.fact(:kernel).stubs(:value).returns("SunOS") + Facter.fact(:hardwaremodel).stubs(:value).returns(nil) + Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) + Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns(nil) + Facter::Util::Resolution.stubs(:exec).with('prtdiag').returns("System Configuration: Parallels Virtual Platform") + Facter.fact(:virtual).value.should == "parallels" + end + + it "should be virtualbox with VirtualBox vendor name from prtdiag" do + Facter.fact(:kernel).stubs(:value).returns("SunOS") + Facter.fact(:hardwaremodel).stubs(:value).returns(nil) + Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) + Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns(nil) + Facter::Util::Resolution.stubs(:exec).with('prtdiag').returns("System Configuration: innotek GmbH VirtualBox") + Facter.fact(:virtual).value.should == "virtualbox" end - it "should be true when running on hyperv" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:virtual).stubs(:value).returns("hyperv") - Facter.fact(:is_virtual).value.should == "true" + it "should be xen0 with xen dom0 files in /proc" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:operatingsystem).stubs(:value).returns("Linux") + Facter.fact(:hardwaremodel).stubs(:value).returns("i386") + Facter::Util::Virtual.expects(:xen?).returns(true) + FileTest.expects(:exists?).with("/proc/xen/xsd_kva").returns(true) + Facter.fact(:virtual).value.should == "xen0" end + + it "should be xenu with xen domU files in /proc" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:operatingsystem).stubs(:value).returns("Linux") + Facter.fact(:hardwaremodel).stubs(:value).returns("i386") + Facter::Util::Virtual.expects(:xen?).returns(true) + FileTest.expects(:exists?).with("/proc/xen/xsd_kva").returns(false) + FileTest.expects(:exists?).with("/proc/xen/capabilities").returns(true) + Facter.fact(:virtual).value.should == "xenu" + end + end +end + +describe "is_virtual fact" do + + it "should be virtual when running on xen" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:virtual).stubs(:value).returns("xenu") + Facter.fact(:is_virtual).value.should == "true" + end + + it "should be false when running on xen0" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:virtual).stubs(:value).returns("xen0") + Facter.fact(:is_virtual).value.should == "false" + end + + it "should be true when running on xenhvm" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:virtual).stubs(:value).returns("xenhvm") + Facter.fact(:is_virtual).value.should == "true" + end + + it "should be false when running on physical" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:virtual).stubs(:value).returns("physical") + Facter.fact(:is_virtual).value.should == "false" + end + + it "should be true when running on vmware" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:virtual).stubs(:value).returns("vmware") + Facter.fact(:is_virtual).value.should == "true" + end + + it "should be true when running on virtualbox" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:virtual).stubs(:value).returns("virtualbox") + Facter.fact(:is_virtual).value.should == "true" + end + + it "should be true when running on openvzve" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:virtual).stubs(:value).returns("openvzve") + Facter.fact(:is_virtual).value.should == "true" + end + + it "should be true when running on kvm" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:virtual).stubs(:value).returns("kvm") + Facter.fact(:is_virtual).value.should == "true" + end + + it "should be true when running in jail" do + Facter.fact(:kernel).stubs(:value).returns("FreeBSD") + Facter.fact(:virtual).stubs(:value).returns("jail") + Facter.fact(:is_virtual).value.should == "true" + end + + it "should be true when running in zone" do + Facter.fact(:kernel).stubs(:value).returns("SunOS") + Facter.fact(:virtual).stubs(:value).returns("zone") + Facter.fact(:is_virtual).value.should == "true" + end + + it "should be true when running on hp-vm" do + Facter.fact(:kernel).stubs(:value).returns("HP-UX") + Facter.fact(:virtual).stubs(:value).returns("hpvm") + Facter.fact(:is_virtual).value.should == "true" + end + + it "should be true when running on S390" do + Facter.fact(:architecture).stubs(:value).returns("s390x") + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:virtual).stubs(:value).returns("zlinux") + Facter.fact(:is_virtual).value.should == "true" + end + + it "should be true when running on parallels" do + Facter.fact(:kernel).stubs(:value).returns("Darwin") + Facter.fact(:virtual).stubs(:value).returns("parallels") + Facter.fact(:is_virtual).value.should == "true" + end + + it "should be false on vmware_server" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:virtual).stubs(:value).returns("vmware_server") + Facter.fact(:is_virtual).value.should == "false" + end + + it "should be false on openvz host nodes" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:virtual).stubs(:value).returns("openvzhn") + Facter.fact(:is_virtual).value.should == "false" + end + + it "should be true when running on hyperv" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:virtual).stubs(:value).returns("hyperv") + Facter.fact(:is_virtual).value.should == "true" + end end From d7c00f60c3302dbc61451173d4e49e0a01012029 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Tue, 12 Jul 2011 21:34:39 -0700 Subject: [PATCH 0681/3753] (#9852) Fixing watchr on facter Signed-off-by: Luke Kanies --- spec/watchr.rb | 125 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100755 spec/watchr.rb diff --git a/spec/watchr.rb b/spec/watchr.rb new file mode 100755 index 0000000000..9d4d3ae220 --- /dev/null +++ b/spec/watchr.rb @@ -0,0 +1,125 @@ +ENV["WATCHR"] = "1" +ENV['AUTOTEST'] = 'true' + +def run_comp(cmd) + puts cmd + results = [] + old_sync = $stdout.sync + $stdout.sync = true + line = [] + begin + open("| #{cmd}", "r") do |f| + until f.eof? do + c = f.getc + putc c + line << c + if c == ?\n + results << if RUBY_VERSION >= "1.9" then + line.join + else + line.pack "c*" + end + line.clear + end + end + end + ensure + $stdout.sync = old_sync + end + results.join +end + +def clear + #system("clear") +end + +def growl(message, status) + # Strip the color codes + message.gsub!(/\[\d+m/, '') + + growlnotify = `which growlnotify`.chomp + return if growlnotify.empty? + title = "Watchr Test Results" + image = status == :pass ? "autotest/images/pass.png" : "autotest/images/fail.png" + options = "-w -n Watchr --image '#{File.expand_path(image)}' -m '#{message}' '#{title}'" + system %(#{growlnotify} #{options} &) +end + +def file2specs(file) + %w{spec/unit spec/integration}.collect { |d| + file.sub('lib/facter', d).sub('.rb', '_spec.rb') + }.find_all { |f| + FileTest.exist?(f) + } +end + +def run_spec(command) + clear + result = run_comp(command).split("\n").last + status = result.include?('0 failures') ? :pass : :fail + growl result, status +end + +def run_spec_files(files) + files = Array(files) + return if files.empty? + opts = File.readlines('.rspec').collect { |l| l.chomp }.join(" ") + begin + run_spec("rspec --tty #{opts} #{files.join(' ')}") + rescue => detail + puts detail.backtrace + warn "Failed to run #{files.join(', ')}: #{detail}" + end +end + +def run_suite + files = files("unit") + files("integration") + opts = File.readlines('.rspec').collect { |l| l.chomp }.join(" ") + run_spec("rspec --tty #{opts} #{files.join(' ')}") +end + +def files(dir) + require 'find' + + result = [] + Find.find(File.join("spec", dir)) do |path| + result << path if path =~ /\.rb/ + end + + result +end + +watch('spec/spec_helper.rb') { run_suite } +watch(%r{^spec/(unit|integration)/.*\.rb$}) { |md| run_spec_files(md[0]) } +watch(%r{^lib/facter/(.*)\.rb$}) { |md| + run_spec_files(file2specs(md[0])) +} +watch(%r{^spec/lib/spec.*}) { |md| run_suite } +watch(%r{^spec/lib/monkey_patches/.*}) { |md| run_suite } + +# Ctrl-\ +Signal.trap 'QUIT' do + puts " --- Running all tests ---\n\n" + run_suite +end + +@interrupted = false + +# Ctrl-C +Signal.trap 'INT' do + if @interrupted + @wants_to_quit = true + abort("\n") + else + puts "Interrupt a second time to quit; wait for rerun of tests" + @interrupted = true + Kernel.sleep 1.5 + # raise Interrupt, nil # let the run loop catch it + begin + run_suite + rescue => detail + puts detail.backtrace + puts "Could not run suite: #{detail}" + end + end +end From 7c49bf8fbc7831f1c2db04b18f72a934f96d09ae Mon Sep 17 00:00:00 2001 From: Michael Stahnke Date: Mon, 3 Oct 2011 11:09:34 -0700 Subject: [PATCH 0682/3753] Updates for 1.6.2rc1 --- CHANGELOG | 31 +++++++++++++++++++++++++++++++ conf/redhat/facter.spec | 14 +++++++++----- lib/facter.rb | 2 +- 3 files changed, 41 insertions(+), 6 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index b7b9e83f0a..43769840fd 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,34 @@ +1.6.2rc1 +=== +d7c00f6 (#9852) Fixing watchr on facter +abf636e (#9555) Change all cases of tabs and 4 space indentation to 2 space indentation. +db1b5af (#9830) Add sshecdsakey fact +1b69791 (#9404) Add memory & update processor facts for DragonFly and OpenBSD. +bce2c69 (#9404) De-clumsify CPU count detection and swap detection on OpenBSD. +cd0ae15 (#9404) Efficiency cleanups for DragonFly facts. +d5511f6 (#9404) Add cross-fact support to facter for DragonFly BSD. +0dfc4e9 (#6728) Improve openvz/cloudlinux detection. +2c5ad52 (#6728) Facter improperly detects openvzve on CloudLinux systems +9101e46 (#7951) added OS support for Amazon Linux +b3784f7 add operatingsystema and operatingsystemrelease support for cloudlinux +8605bba (#9787) Change rspec format so we use the default, not document +b579613 (#7726) Silence prtconf error message inside zones +db3c606 (#9786) Add aliases: specs, tests, test in rake that points at 'spec'. +dfda9be (#4980, #6470) Fix architecture in Darwin and Ubuntu +8f938c1 (#6792) Added osfamily fact. +af1ef43 (#6515) Fix for ruby-1.8.5. Switched use of 'line.each' to 'each_line'. +328ff75 (#6515 and #2945) Fix processorcount for arm, sparc & ppc for linux. +51329b8 (#3856) Detect VirtualBox on Darwin as well as Linux and SunOS +83498b5 (#7996) Restrict solaris cpu processor detection +6e29ff7 (#8615) ENV hash is now local to tests +124a09b (#8240) Fixed regex pattern for domain +fd93c5f (#7996) Add solaris processor facts +3f1a163 (#9593) Require rubygems to handle json output for ruby 1.8.7. +c4fe415 (#9295) Added spec tests for Hyper-V detection +ea23417 (#9295) Initial detection of Hyper-V hypervisor +82351ab Stub out OS and HW model to avoid test failures. Only stub vmware -v (don't expect it) since it needn't be invoked if we already identified Xen or something else. +16a8cab (#2747) Fix detection of xen0 vs xenu in Xen 3.2. + 1.6.1 === 1f009e0 Updated CHANGELOG for 1.6.1rc4 diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index 0089725c1a..120c3aae43 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -2,16 +2,17 @@ %define has_ruby_abi 0%{?fedora} || 0%{?rhel} >= 5 %define has_ruby_noarch %has_ruby_abi +%global _ver 1.6.2rc1 Summary: Ruby module for collecting simple facts about a host operating system Name: facter -Version: 1.6.0 -Release: 1%{?dist} +Version: 1.6.2 +Release: 0.1rc%{?dist} License: Apache 2.0 Group: System Environment/Base URL: http://www.puppetlabs.com/puppet/related-projects/%{name}/ -Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}.tar.gz -Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}.tar.gz.sign +Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{_ver}.tar.gz +#Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}.tar.gz.sign BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) %if %has_ruby_noarch @@ -31,7 +32,7 @@ system. Some of the facts are preconfigured, such as the hostname and the operating system. Additional facts can be added through simple Ruby scripts %prep -%setup -q +%setup -q -n %{name}-%{_ver} %build @@ -52,6 +53,9 @@ rm -rf %{buildroot} %changelog +* Mon Oct 03 2011 Michael Stahnke - 1.6.2-0.1rc1 +- Updates for 1.6.2-0.1rc1 + * Thu Jun 23 2011 Michael Stahnke - 1.6.0-1 - Update to 1.6.0 diff --git a/lib/facter.rb b/lib/facter.rb index f56a52c5ba..914c257cce 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -24,7 +24,7 @@ module Util; end include Comparable include Enumerable - FACTERVERSION = '1.6.1' + FACTERVERSION = '1.6.2' # = Facter # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. From 0d6df28e833e8e4645ecb675b02ca7922adb7770 Mon Sep 17 00:00:00 2001 From: Ken Barber Date: Wed, 5 Oct 2011 15:29:40 +0200 Subject: [PATCH 0683/3753] (#9904) Remove windows rspec warning. There was an old reference to a constant that is no longer used in facter. This was being used to mock windows. I have changed this now to mock the newer is_windows function in Config, thereby removing a long standing warning message that occurs when running rspec. --- spec/unit/util/resolution_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index 005ccfd321..6064b3e79c 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -107,7 +107,7 @@ describe "and the code is a string" do describe "on windows" do before do - Facter::Util::Resolution::WINDOWS = true + Facter::Util::Config.stubs(:is_windows?).returns(true) end it "should return the result of executing the code" do @@ -126,7 +126,7 @@ describe "on non-windows systems" do before do - Facter::Util::Resolution::WINDOWS = false + Facter::Util::Config.stubs(:is_windows?).returns(false) end it "should return the result of executing the code" do From 7b14b773d8cc60aa075a75ff193c04f9d6dd8fa8 Mon Sep 17 00:00:00 2001 From: Ken Barber Date: Thu, 6 Oct 2011 05:37:32 +0200 Subject: [PATCH 0684/3753] (#9928) Correct readlines stubbing to return an array instead of an empty string. We were getting warnings in Ruby-1.9.2 trying to call 'each' on a string. The mistake here was to return an emptry string instead of an empty array for the /proc/cpuinfo readlines call. --- spec/unit/processor_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/unit/processor_spec.rb b/spec/unit/processor_spec.rb index 4fc0a2fa7c..95588c0500 100755 --- a/spec/unit/processor_spec.rb +++ b/spec/unit/processor_spec.rb @@ -207,7 +207,7 @@ def load(procs) File.stubs(:exists?).with('/sys/devices/system/cpu').returns(true) ## sysfs method is only used if cpuinfo method returned no processors File.stubs(:exists?).with("/proc/cpuinfo").returns(true) - File.stubs(:readlines).with("/proc/cpuinfo").returns("") + File.stubs(:readlines).with("/proc/cpuinfo").returns([]) Dir.stubs(:glob).with("/sys/devices/system/cpu/cpu[0-9]*").returns(%w{ /sys/devices/system/cpu/cpu0 /sys/devices/system/cpu/cpu1 @@ -221,7 +221,7 @@ def load(procs) File.stubs(:exists?).with('/sys/devices/system/cpu').returns(true) ## sysfs method is only used if cpuinfo method returned no processors File.stubs(:exists?).with("/proc/cpuinfo").returns(true) - File.stubs(:readlines).with("/proc/cpuinfo").returns("") + File.stubs(:readlines).with("/proc/cpuinfo").returns([]) Dir.stubs(:glob).with("/sys/devices/system/cpu/cpu[0-9]*").returns(%w{ /sys/devices/system/cpu/cpu0 /sys/devices/system/cpu/cpu1 From 9df78a1f17e2ebefa0c9526443e72ef937ba7dcd Mon Sep 17 00:00:00 2001 From: Nicolas Vigier Date: Wed, 3 Aug 2011 19:56:44 +0200 Subject: [PATCH 0685/3753] (#9929) Add support for Mageia to operatingsystem.rb and operatingsystemrelease.rb --- lib/facter/operatingsystem.rb | 2 ++ lib/facter/operatingsystemrelease.rb | 11 +++++++++++ 2 files changed, 13 insertions(+) diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index b1ffeb8940..f7740b01ad 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -81,6 +81,8 @@ "Slackware" elsif FileTest.exists?("/etc/alpine-release") "Alpine" + elsif FileTest.exists?("/etc/mageia-release") + "Mageia" elsif Facter.value(:lsbdistdescription) =~ /Amazon Linux/ "Amazon" end diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index c4d78628c5..fefe19d996 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -10,6 +10,7 @@ # information. # On Slackware, parses '/etc/slackware-version'. # On Amazon Linux, returns the 'lsbdistrelease' value. +# On Mageia, parses '/etc/mageia-release' for the release version. # # On all remaining systems, returns the 'kernelrelease' value. # @@ -91,6 +92,16 @@ end end +Facter.add(:operatingsystemrelease) do + confine :operatingsystem => %w{Mageia} + setcode do + release = Facter::Util::Resolution.exec('cat /etc/mageia-release') + if release =~ /Mageia release ([0-9.]+)/ + $1 + end + end +end + Facter.add(:operatingsystemrelease) do confine :operatingsystem => %w{Bluewhite64} setcode do From 52609b54a3d5102d00a2c3dba9456d5a1237ebcc Mon Sep 17 00:00:00 2001 From: Michael Stahnke Date: Mon, 10 Oct 2011 09:32:33 -0700 Subject: [PATCH 0686/3753] Updated CHANGELOG for 1.6.2 --- CHANGELOG | 2 +- conf/redhat/facter.spec | 7 +++++-- conf/solaris/pkginfo | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 43769840fd..9b212304f4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,4 @@ -1.6.2rc1 +1.6.2 === d7c00f6 (#9852) Fixing watchr on facter abf636e (#9555) Change all cases of tabs and 4 space indentation to 2 space indentation. diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index 120c3aae43..ede21688a5 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -2,12 +2,12 @@ %define has_ruby_abi 0%{?fedora} || 0%{?rhel} >= 5 %define has_ruby_noarch %has_ruby_abi -%global _ver 1.6.2rc1 +%global _ver 1.6.2 Summary: Ruby module for collecting simple facts about a host operating system Name: facter Version: 1.6.2 -Release: 0.1rc%{?dist} +Release: 1%{?dist} License: Apache 2.0 Group: System Environment/Base URL: http://www.puppetlabs.com/puppet/related-projects/%{name}/ @@ -53,6 +53,9 @@ rm -rf %{buildroot} %changelog +* Mon Oct 10 2011 Michael Stahnke - 1.6.2-1 +- Update to 1.6.2 + * Mon Oct 03 2011 Michael Stahnke - 1.6.2-0.1rc1 - Updates for 1.6.2-0.1rc1 diff --git a/conf/solaris/pkginfo b/conf/solaris/pkginfo index 262a436afd..58bb4f90ac 100644 --- a/conf/solaris/pkginfo +++ b/conf/solaris/pkginfo @@ -1,6 +1,6 @@ PKG=CSWfacter NAME=facter - System Fact Gatherer -VERSION=1.6.0 +VERSION=1.6.2 CATEGORY=application VENDOR=http://www.puppetlabs.com/puppet/related-projects/facter HOTLINE=http://puppetlabs.com/cgi-bin/facter.cgi From e89758de33f66384510f2547f9584c3ce7e071cb Mon Sep 17 00:00:00 2001 From: Michael Stahnke Date: Mon, 10 Oct 2011 09:32:33 -0700 Subject: [PATCH 0687/3753] Updated CHANGELOG for 1.6.2 --- CHANGELOG | 2 +- conf/redhat/facter.spec | 7 +++++-- conf/solaris/pkginfo | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 43769840fd..9b212304f4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,4 @@ -1.6.2rc1 +1.6.2 === d7c00f6 (#9852) Fixing watchr on facter abf636e (#9555) Change all cases of tabs and 4 space indentation to 2 space indentation. diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index 120c3aae43..ede21688a5 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -2,12 +2,12 @@ %define has_ruby_abi 0%{?fedora} || 0%{?rhel} >= 5 %define has_ruby_noarch %has_ruby_abi -%global _ver 1.6.2rc1 +%global _ver 1.6.2 Summary: Ruby module for collecting simple facts about a host operating system Name: facter Version: 1.6.2 -Release: 0.1rc%{?dist} +Release: 1%{?dist} License: Apache 2.0 Group: System Environment/Base URL: http://www.puppetlabs.com/puppet/related-projects/%{name}/ @@ -53,6 +53,9 @@ rm -rf %{buildroot} %changelog +* Mon Oct 10 2011 Michael Stahnke - 1.6.2-1 +- Update to 1.6.2 + * Mon Oct 03 2011 Michael Stahnke - 1.6.2-0.1rc1 - Updates for 1.6.2-0.1rc1 diff --git a/conf/solaris/pkginfo b/conf/solaris/pkginfo index 262a436afd..58bb4f90ac 100644 --- a/conf/solaris/pkginfo +++ b/conf/solaris/pkginfo @@ -1,6 +1,6 @@ PKG=CSWfacter NAME=facter - System Fact Gatherer -VERSION=1.6.0 +VERSION=1.6.2 CATEGORY=application VENDOR=http://www.puppetlabs.com/puppet/related-projects/facter HOTLINE=http://puppetlabs.com/cgi-bin/facter.cgi From ce8f5722a2f37698f4287db9dda4a5a44c82704b Mon Sep 17 00:00:00 2001 From: Garrett Honeycutt Date: Thu, 13 Oct 2011 15:21:49 -0400 Subject: [PATCH 0688/3753] (#10079) Remove trailing whitespace This patch cleans up some of the trailing whitespace and extraneous carriage returns in the code and documentation. --- LICENSE | 2 +- conf/osx/createpackage.sh | 2 +- documentation/custom.page | 2 +- install.rb | 2 +- lib/facter.rb | 2 +- lib/facter/architecture.rb | 3 +-- lib/facter/augeasversion.rb | 3 +-- lib/facter/domain.rb | 6 +++--- lib/facter/ipaddress.rb | 4 ++-- lib/facter/macaddress.rb | 2 +- lib/facter/manufacturer.rb | 2 +- lib/facter/netmask.rb | 3 --- lib/facter/network.rb | 5 ++--- lib/facter/operatingsystem.rb | 3 +-- lib/facter/operatingsystemrelease.rb | 2 +- lib/facter/processor.rb | 4 ++-- lib/facter/selinux.rb | 6 +++--- lib/facter/uptime_days.rb | 1 - lib/facter/uptime_hours.rb | 1 - lib/facter/util/processor.rb | 6 +++--- lib/facter/util/resolution.rb | 6 +++--- lib/facter/util/values.rb | 2 +- lib/facter/virtual.rb | 4 ++-- lib/facter/vlans.rb | 3 +-- lib/facter/xendomains.rb | 1 - man/man8/facter.8 | 4 ++-- spec/watchr.rb | 2 +- tasks/rake/changlog.rake | 2 +- tasks/rake/ci.rake | 2 +- tasks/rake/dailybuild.rake | 1 - 30 files changed, 38 insertions(+), 50 deletions(-) diff --git a/LICENSE b/LICENSE index 9cd62ce4ad..571ef3d435 100644 --- a/LICENSE +++ b/LICENSE @@ -11,5 +11,5 @@ http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and +See the License for the specific language governing permissions and limitations under the License. diff --git a/conf/osx/createpackage.sh b/conf/osx/createpackage.sh index fc76b62db2..7b473ad762 100755 --- a/conf/osx/createpackage.sh +++ b/conf/osx/createpackage.sh @@ -56,7 +56,7 @@ function install_facter() { function install_docs() { echo "Installing docs to ${pkgroot}" - docdir="${pkgroot}/usr/share/doc/facter" + docdir="${pkgroot}/usr/share/doc/facter" mkdir -p "${docdir}" for docfile in ChangeLog COPYING LICENSE README README.rst TODO; do install -m 0644 "${facter_root}/${docfile}" "${docdir}" diff --git a/documentation/custom.page b/documentation/custom.page index 991071c3ba..2416606e9e 100644 --- a/documentation/custom.page +++ b/documentation/custom.page @@ -15,7 +15,7 @@ As a simple example, here is how I publish my home directory to Puppet: setcode do ENV['HOME'] end - end + end I have ~/lib/ruby in my $RUBYLIB environment variable, so I just created ~/lib/ruby/facter and dropped the above code into a ``home.rb`` file diff --git a/install.rb b/install.rb index a1a3884202..065ce68260 100755 --- a/install.rb +++ b/install.rb @@ -101,7 +101,7 @@ def do_libs(libs, strip = 'lib/') end def do_man(man, strip = 'man/') - if (InstallOptions.man == true) + if (InstallOptions.man == true) man.each do |mf| omf = File.join(InstallOptions.man_dir, mf.gsub(/#{strip}/, '')) om = File.dirname(omf) diff --git a/lib/facter.rb b/lib/facter.rb index 914c257cce..23f8f98bf0 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -11,7 +11,7 @@ # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and +# See the License for the specific language governing permissions and # limitations under the License. module Facter diff --git a/lib/facter/architecture.rb b/lib/facter/architecture.rb index cd0017c176..a8ff9a3ee6 100644 --- a/lib/facter/architecture.rb +++ b/lib/facter/architecture.rb @@ -2,7 +2,7 @@ # # Purpose: # Return the CPU hardware architecture. -# +# # Resolution: # On OpenBSD, Linux and Debian's kfreebsd, use the hardwaremodel fact. # Gentoo and Debian call "x86_86" "amd64". @@ -35,4 +35,3 @@ end end end - diff --git a/lib/facter/augeasversion.rb b/lib/facter/augeasversion.rb index 481b46d711..b438e1330d 100644 --- a/lib/facter/augeasversion.rb +++ b/lib/facter/augeasversion.rb @@ -7,7 +7,7 @@ # the underlying Augeas library. # # Caveats: -# The library version may not indicate the presence of certain lenses, +# The library version may not indicate the presence of certain lenses, # depending on the system packages updated, nor the version of ruby-augeas # which may affect support for the Puppet Augeas provider. # Versions prior to 0.3.6 cannot be interrogated for their version. @@ -26,4 +26,3 @@ end end end - diff --git a/lib/facter/domain.rb b/lib/facter/domain.rb index 92eafa3eeb..0a6caa02cf 100644 --- a/lib/facter/domain.rb +++ b/lib/facter/domain.rb @@ -2,7 +2,7 @@ # # Purpose: # Return the host's primary DNS domain name. -# +# # Resolution: # On UNIX (excluding Darwin), first try and use the hostname fact, # which uses the hostname system command, and then parse the output @@ -23,11 +23,11 @@ # Get the domain from various sources; the order of these # steps is important - if name = Facter::Util::Resolution.exec('hostname') and + if name = Facter::Util::Resolution.exec('hostname') and name =~ /.*?\.(.+$)/ $1 - elsif domain = Facter::Util::Resolution.exec('dnsdomainname') and + elsif domain = Facter::Util::Resolution.exec('dnsdomainname') and domain =~ /.+\..+/ domain diff --git a/lib/facter/ipaddress.rb b/lib/facter/ipaddress.rb index 42d028246c..360becd2ea 100644 --- a/lib/facter/ipaddress.rb +++ b/lib/facter/ipaddress.rb @@ -117,13 +117,13 @@ else require 'resolv' end - + begin if hostname = Facter.value(:hostname) if Facter.value(:kernel) == 'windows' ip = Win32::Resolv.get_resolv_info.last[0] else - ip = Resolv.getaddress(hostname) + ip = Resolv.getaddress(hostname) end unless ip == "127.0.0.1" ip diff --git a/lib/facter/macaddress.rb b/lib/facter/macaddress.rb index 25e2ef4dcf..1a832ca78c 100644 --- a/lib/facter/macaddress.rb +++ b/lib/facter/macaddress.rb @@ -1,6 +1,6 @@ # Fact: macaddress # -# Purpose: +# Purpose: # # Resolution: # diff --git a/lib/facter/manufacturer.rb b/lib/facter/manufacturer.rb index 1857d09346..5c07ba954f 100644 --- a/lib/facter/manufacturer.rb +++ b/lib/facter/manufacturer.rb @@ -26,7 +26,7 @@ } Facter::Manufacturer.sysctl_find_system_info(mfg_keys) -elsif Facter.value(:kernel) == "Darwin" +elsif Facter.value(:kernel) == "Darwin" mfg_keys = { 'hw.model' => 'productname' } diff --git a/lib/facter/netmask.rb b/lib/facter/netmask.rb index bc32073334..710ebe47ac 100644 --- a/lib/facter/netmask.rb +++ b/lib/facter/netmask.rb @@ -6,7 +6,6 @@ # # Caveats: # - # netmask.rb # Find the netmask of the primary ipaddress # Copyright (C) 2007 David Schmitt @@ -14,7 +13,6 @@ # # idea and originial source by Mark 'phips' Phillips # - require 'facter/util/netmask' Facter.add("netmask") do @@ -23,4 +21,3 @@ Facter::NetMask.get_netmask end end - diff --git a/lib/facter/network.rb b/lib/facter/network.rb index 9e28c6ba0c..390895a05b 100644 --- a/lib/facter/network.rb +++ b/lib/facter/network.rb @@ -9,13 +9,12 @@ # # Caveats: # - require 'facter/util/ip' Facter::Util::IP.get_interfaces.each do |interface| Facter.add("network_" + Facter::Util::IP.alphafy(interface)) do setcode do Facter::Util::IP.get_network_value(interface) - end - end + end + end end diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index b1ffeb8940..c18672f505 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -10,7 +10,6 @@ # # Caveats: # - require 'facter/lsb' Facter.add(:operatingsystem) do @@ -55,7 +54,7 @@ "CentOS" elsif txt =~ /CERN/ "SLC" - elsif txt =~ /scientific/i + elsif txt =~ /scientific/i "Scientific" elsif txt =~ /^cloudlinux/i "CloudLinux" diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index c4d78628c5..bb414443ba 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -10,7 +10,7 @@ # information. # On Slackware, parses '/etc/slackware-version'. # On Amazon Linux, returns the 'lsbdistrelease' value. -# +# # On all remaining systems, returns the 'kernelrelease' value. # # Caveats: diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb index 1de20f7d7f..b08a02935e 100644 --- a/lib/facter/processor.rb +++ b/lib/facter/processor.rb @@ -25,7 +25,7 @@ confine :kernel => [ :linux, :"gnu/kfreebsd" ] setcode do processor_list = Facter::Util::Processor.enum_cpuinfo - + ## If this returned nothing, then don't resolve the fact if processor_list.length != 0 processor_list.length.to_s @@ -51,7 +51,7 @@ confine :kernel => :aix setcode do processor_list = Facter::Util::Processor.enum_lsdev - + processor_list.length.to_s end end diff --git a/lib/facter/selinux.rb b/lib/facter/selinux.rb index 60caf80cba..36f4b2fdcc 100644 --- a/lib/facter/selinux.rb +++ b/lib/facter/selinux.rb @@ -12,7 +12,7 @@ sestatus_cmd = '/usr/sbin/sestatus' -# This supports the fact that the selinux mount point is not always in the +# This supports the fact that the selinux mount point is not always in the # same location -- the selinux mount point is operating system specific. def selinux_mount_point if FileTest.exists?('/proc/self/mountinfo') @@ -29,7 +29,7 @@ def selinux_mount_point Facter.add("selinux") do confine :kernel => :linux setcode do - result = "false" + result = "false" if FileTest.exists?("#{selinux_mount_point}/enforce") if FileTest.exists?("/proc/self/attr/current") if (File.read("/proc/self/attr/current") != "kernel\0") @@ -90,7 +90,7 @@ def selinux_mount_point end end -# This is a legacy fact which returns the old selinux_mode fact value to prevent +# This is a legacy fact which returns the old selinux_mode fact value to prevent # breakages of existing manifests. It should be removed at the next major release. # See ticket #6677. diff --git a/lib/facter/uptime_days.rb b/lib/facter/uptime_days.rb index 25af966d49..6bf6270a75 100644 --- a/lib/facter/uptime_days.rb +++ b/lib/facter/uptime_days.rb @@ -13,4 +13,3 @@ hours && hours / 24 # hours in day end end - diff --git a/lib/facter/uptime_hours.rb b/lib/facter/uptime_hours.rb index d02ac3201b..e780751261 100644 --- a/lib/facter/uptime_hours.rb +++ b/lib/facter/uptime_hours.rb @@ -13,4 +13,3 @@ seconds && seconds / (60 * 60) # seconds in hour end end - diff --git a/lib/facter/util/processor.rb b/lib/facter/util/processor.rb index 8438482f2b..220b209a6a 100644 --- a/lib/facter/util/processor.rb +++ b/lib/facter/util/processor.rb @@ -21,7 +21,7 @@ def self.enum_cpuinfo end end end - + when "ppc64" Thread::exclusive do File.readlines(cpuinfo).each do |l| @@ -33,7 +33,7 @@ def self.enum_cpuinfo end end end - + when /arm/ Thread::exclusive do File.readlines(cpuinfo).each do |l| @@ -63,7 +63,7 @@ def self.enum_cpuinfo end processor_list end - + def self.enum_lsdev processor_num = -1 processor_list = {} diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 0270be1194..a5f65c3fc4 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -36,12 +36,12 @@ def self.exec(code, interpreter = nil) # Try to guess whether the specified code can be executed by looking at the # first word. If it cannot be found on the PATH defer on resolving the fact # by returning nil. - # This only fails on shell built-ins, most of which are masked by stuff in + # This only fails on shell built-ins, most of which are masked by stuff in # /bin or of dubious value anyways. In the worst case, "sh -c 'builtin'" can # be used to work around this limitation # - # Windows' %x{} throws Errno::ENOENT when the command is not found, so we - # can skip the check there. This is good, since builtins cannot be found + # Windows' %x{} throws Errno::ENOENT when the command is not found, so we + # can skip the check there. This is good, since builtins cannot be found # elsewhere. if have_which and !Facter::Util::Config.is_windows? path = nil diff --git a/lib/facter/util/values.rb b/lib/facter/util/values.rb index 68ace3475b..a8a5bfab64 100644 --- a/lib/facter/util/values.rb +++ b/lib/facter/util/values.rb @@ -8,7 +8,7 @@ def convert(value) value = value.to_s if value.is_a?(Symbol) value = value.downcase if value.is_a?(String) value - end + end end end end diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index d46d4a9b09..e617359472 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -133,7 +133,7 @@ end end end - + if output = Facter::Util::Resolution.exec("vmware -v") result = output.sub(/(\S+)\s+(\S+).*/) { | text | "#{$1}_#{$2}"}.downcase end @@ -148,7 +148,7 @@ # Purpose: returning true or false for if a machine is virtualised or not. # # Resolution: Hypervisors and the like may be detected as a virtual type, but -# are not actual virtual machines, or should not be treated as such. This +# are not actual virtual machines, or should not be treated as such. This # determines if the host is actually virtualized. # # Caveats: diff --git a/lib/facter/vlans.rb b/lib/facter/vlans.rb index 341d867c1f..c09e4e0e13 100644 --- a/lib/facter/vlans.rb +++ b/lib/facter/vlans.rb @@ -7,9 +7,8 @@ # # Caveats: # - require 'facter/util/vlans' - + Facter.add("vlans") do confine :kernel => :linux setcode do diff --git a/lib/facter/xendomains.rb b/lib/facter/xendomains.rb index 5bcde05879..166b4e828e 100644 --- a/lib/facter/xendomains.rb +++ b/lib/facter/xendomains.rb @@ -8,7 +8,6 @@ # # Caveats: # - require 'facter/util/xendomains' Facter.add("xendomains") do diff --git a/man/man8/facter.8 b/man/man8/facter.8 index ff24b890e2..7bfec86dca 100644 --- a/man/man8/facter.8 +++ b/man/man8/facter.8 @@ -1,6 +1,6 @@ .TH "" "" "" .SH NAME - \- + \- .\" Man page generated from reStructeredText. . .SH SYNOPSIS @@ -50,5 +50,5 @@ Luke Kanies Copyright (c) 2006 Reductive Labs, LLC Licensed under the GNU Public License .\" Generated by docutils manpage writer. -.\" +.\" . diff --git a/spec/watchr.rb b/spec/watchr.rb index 9d4d3ae220..31e39bd909 100755 --- a/spec/watchr.rb +++ b/spec/watchr.rb @@ -85,7 +85,7 @@ def files(dir) Find.find(File.join("spec", dir)) do |path| result << path if path =~ /\.rb/ end - + result end diff --git a/tasks/rake/changlog.rake b/tasks/rake/changlog.rake index 0dcd91c20f..8622c1c3d6 100644 --- a/tasks/rake/changlog.rake +++ b/tasks/rake/changlog.rake @@ -2,7 +2,7 @@ desc "Create a ChangeLog based on git commits." task :changelog do begin gitc = %x{which git-changelog} - rescue + rescue puts "This task needs the git-changelog binary - http://github.com/ReinH/git-changelog" end diff --git a/tasks/rake/ci.rake b/tasks/rake/ci.rake index abd2754730..a2b395e507 100644 --- a/tasks/rake/ci.rake +++ b/tasks/rake/ci.rake @@ -8,7 +8,7 @@ task :ci_prep do ENV['CI_REPORTS'] = 'results' rescue LoadError puts 'Missing ci_reporter gem. You must have the ci_reporter gem installed to run the CI spec tests' - end + end end desc "Run the CI RSpec tests" diff --git a/tasks/rake/dailybuild.rake b/tasks/rake/dailybuild.rake index 709bd482d0..76ce4e2e27 100644 --- a/tasks/rake/dailybuild.rake +++ b/tasks/rake/dailybuild.rake @@ -6,4 +6,3 @@ task :daily => :changelog do sh "rm ChangeLog" sh "gzip -f -9 #{version}.tar" end - From 64770d0b9fe0041ae520ffbb31810a520123cb8c Mon Sep 17 00:00:00 2001 From: Mikael Fridh Date: Fri, 21 Oct 2011 11:08:46 +0200 Subject: [PATCH 0689/3753] Try to get VirtualBox info without dmidecode /sys/devices/virtual/dmi/id/product_name:VirtualBox --- lib/facter/util/virtual.rb | 7 +++++++ lib/facter/virtual.rb | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/lib/facter/util/virtual.rb b/lib/facter/util/virtual.rb index 01b71b37b9..c1027a9cec 100644 --- a/lib/facter/util/virtual.rb +++ b/lib/facter/util/virtual.rb @@ -61,6 +61,13 @@ def self.kvm? (txt =~ /QEMU Virtual CPU/) ? true : false end + def self.virtualbox? + txt = if FileTest.exists?("/sys/devices/virtual/dmi/id/product_name") + File.read("/sys/devices/virtual/dmi/id/product_name") + end + (txt =~ /VirtualBox/) ? true : false + end + def self.kvm_type # TODO Tell the difference between kvm and qemu # Can't work out a way to do this at the moment that doesn't diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index d46d4a9b09..e88621d1ca 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -80,6 +80,10 @@ end end + if Facter::Util::Virtual.virtualbox? + result = "virtualbox" + end + if Facter::Util::Virtual.kvm? result = Facter::Util::Virtual.kvm_type() end From 6efadbb4f551b0eb07e249076d3e306a30819c56 Mon Sep 17 00:00:00 2001 From: Jason Gill Date: Sat, 22 Oct 2011 15:27:02 -0400 Subject: [PATCH 0690/3753] (#10233) Adds support for Parallels Server Bare Metal to Facter --- lib/facter/hardwareisa.rb | 2 +- lib/facter/lsbmajdistrelease.rb | 2 +- lib/facter/macaddress.rb | 2 +- lib/facter/operatingsystem.rb | 2 ++ lib/facter/operatingsystemrelease.rb | 4 ++-- lib/facter/osfamily.rb | 2 +- lib/facter/uniqueid.rb | 2 +- 7 files changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/facter/hardwareisa.rb b/lib/facter/hardwareisa.rb index a58a043472..12b24b9e87 100644 --- a/lib/facter/hardwareisa.rb +++ b/lib/facter/hardwareisa.rb @@ -12,5 +12,5 @@ Facter.add(:hardwareisa) do setcode 'uname -p' - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS Scientific SLC SuSE SLES Debian Ubuntu Gentoo FreeBSD OpenBSD NetBSD DragonFly OEL OracleLinux OVS GNU/kFreeBSD} + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS Scientific PSBM SLC SuSE SLES Debian Ubuntu Gentoo FreeBSD OpenBSD NetBSD DragonFly OEL OracleLinux OVS GNU/kFreeBSD} end diff --git a/lib/facter/lsbmajdistrelease.rb b/lib/facter/lsbmajdistrelease.rb index df1e887c51..78a7d3feb3 100644 --- a/lib/facter/lsbmajdistrelease.rb +++ b/lib/facter/lsbmajdistrelease.rb @@ -15,7 +15,7 @@ require 'facter' Facter.add("lsbmajdistrelease") do - confine :operatingsystem => %w{Linux Fedora RedHat CentOS Scientific SLC SuSE SLES Debian Ubuntu Gentoo OEL OracleLinux OVS GNU/kFreeBSD} + confine :operatingsystem => %w{Linux Fedora RedHat CentOS Scientific PSBM SLC SuSE SLES Debian Ubuntu Gentoo OEL OracleLinux OVS GNU/kFreeBSD} setcode do if /(\d*)\./i =~ Facter.value(:lsbdistrelease) result=$1 diff --git a/lib/facter/macaddress.rb b/lib/facter/macaddress.rb index 1a832ca78c..5295d409f7 100644 --- a/lib/facter/macaddress.rb +++ b/lib/facter/macaddress.rb @@ -10,7 +10,7 @@ require 'facter/util/macaddress' Facter.add(:macaddress) do - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS Scientific SLC SuSE SLES Debian Gentoo Ubuntu OEL OracleLinux OVS GNU/kFreeBSD} + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS Scientific PSBM SLC SuSE SLES Debian Gentoo Ubuntu OEL OracleLinux OVS GNU/kFreeBSD} setcode do ether = [] output = Facter::Util::Resolution.exec("/sbin/ifconfig -a") diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index c18672f505..952782137a 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -58,6 +58,8 @@ "Scientific" elsif txt =~ /^cloudlinux/i "CloudLinux" + elsif txt =~ /^Parallels Server Bare Metal/i + "PSBM" else "RedHat" end diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index bb414443ba..4cfd5a39ff 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -17,10 +17,10 @@ # Facter.add(:operatingsystemrelease) do - confine :operatingsystem => %w{CentOS Fedora oel ovs OracleLinux RedHat MeeGo Scientific SLC CloudLinux} + confine :operatingsystem => %w{CentOS Fedora oel ovs OracleLinux RedHat MeeGo Scientific SLC CloudLinux PSBM} setcode do case Facter.value(:operatingsystem) - when "CentOS", "RedHat", "Scientific", "SLC", "CloudLinux" + when "CentOS", "RedHat", "Scientific", "SLC", "CloudLinux", "PSBM" releasefile = "/etc/redhat-release" when "Fedora" releasefile = "/etc/fedora-release" diff --git a/lib/facter/osfamily.rb b/lib/facter/osfamily.rb index 038eaab760..3df391d6c1 100644 --- a/lib/facter/osfamily.rb +++ b/lib/facter/osfamily.rb @@ -16,7 +16,7 @@ setcode do case Facter.value(:operatingsystem) - when "RedHat", "Fedora", "CentOS", "Scientific", "SLC", "CloudLinux", "OracleLinux", "OVS", "OEL" + when "RedHat", "Fedora", "CentOS", "Scientific", "SLC", "CloudLinux", "PSBM", "OracleLinux", "OVS", "OEL" "RedHat" when "Ubuntu", "Debian" "Debian" diff --git a/lib/facter/uniqueid.rb b/lib/facter/uniqueid.rb index 6c1c563bc7..265dc78638 100644 --- a/lib/facter/uniqueid.rb +++ b/lib/facter/uniqueid.rb @@ -1,4 +1,4 @@ Facter.add(:uniqueid) do setcode 'hostid' - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS Scientific SLC SuSE SLES Debian Ubuntu Gentoo AIX OEL OracleLinux OVS GNU/kFreeBSD} + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS Scientific PSBM SLC SuSE SLES Debian Ubuntu Gentoo AIX OEL OracleLinux OVS GNU/kFreeBSD} end From c9db305096a8aa95a48bd717e752abccecea6009 Mon Sep 17 00:00:00 2001 From: Jeff Palmer Date: Fri, 21 Oct 2011 22:18:22 -0400 Subject: [PATCH 0691/3753] (#10228) Ascendos OS support for various facts. This patch will make various facts return the correct value on Ascendos (a new RHEL rebuild - http://www.ascendos.org/): * hardwareisa * lsbmajdistrelease * macaddress * operatingsystem * operatingsystemrelease * osfamily * uniqueid --- lib/facter/hardwareisa.rb | 2 +- lib/facter/lsbmajdistrelease.rb | 2 +- lib/facter/macaddress.rb | 2 +- lib/facter/operatingsystem.rb | 2 ++ lib/facter/operatingsystemrelease.rb | 4 ++-- lib/facter/osfamily.rb | 2 +- lib/facter/uniqueid.rb | 2 +- spec/unit/operatingsystem_spec.rb | 9 +++++++++ spec/unit/operatingsystemrelease_spec.rb | 1 + 9 files changed, 19 insertions(+), 7 deletions(-) diff --git a/lib/facter/hardwareisa.rb b/lib/facter/hardwareisa.rb index 12b24b9e87..ecb8573457 100644 --- a/lib/facter/hardwareisa.rb +++ b/lib/facter/hardwareisa.rb @@ -12,5 +12,5 @@ Facter.add(:hardwareisa) do setcode 'uname -p' - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS Scientific PSBM SLC SuSE SLES Debian Ubuntu Gentoo FreeBSD OpenBSD NetBSD DragonFly OEL OracleLinux OVS GNU/kFreeBSD} + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS Scientific PSBM SLC Ascendos SuSE SLES Debian Ubuntu Gentoo FreeBSD OpenBSD NetBSD DragonFly OEL OracleLinux OVS GNU/kFreeBSD} end diff --git a/lib/facter/lsbmajdistrelease.rb b/lib/facter/lsbmajdistrelease.rb index 78a7d3feb3..69eb5da539 100644 --- a/lib/facter/lsbmajdistrelease.rb +++ b/lib/facter/lsbmajdistrelease.rb @@ -15,7 +15,7 @@ require 'facter' Facter.add("lsbmajdistrelease") do - confine :operatingsystem => %w{Linux Fedora RedHat CentOS Scientific PSBM SLC SuSE SLES Debian Ubuntu Gentoo OEL OracleLinux OVS GNU/kFreeBSD} + confine :operatingsystem => %w{Linux Fedora RedHat CentOS Scientific PSBM SLC Ascendos SuSE SLES Debian Ubuntu Gentoo OEL OracleLinux OVS GNU/kFreeBSD} setcode do if /(\d*)\./i =~ Facter.value(:lsbdistrelease) result=$1 diff --git a/lib/facter/macaddress.rb b/lib/facter/macaddress.rb index 5295d409f7..32daf8556b 100644 --- a/lib/facter/macaddress.rb +++ b/lib/facter/macaddress.rb @@ -10,7 +10,7 @@ require 'facter/util/macaddress' Facter.add(:macaddress) do - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS Scientific PSBM SLC SuSE SLES Debian Gentoo Ubuntu OEL OracleLinux OVS GNU/kFreeBSD} + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS Scientific PSBM SLC Ascendos SuSE SLES Debian Gentoo Ubuntu OEL OracleLinux OVS GNU/kFreeBSD} setcode do ether = [] output = Facter::Util::Resolution.exec("/sbin/ifconfig -a") diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index 952782137a..5961f96a7c 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -60,6 +60,8 @@ "CloudLinux" elsif txt =~ /^Parallels Server Bare Metal/i "PSBM" + elsif txt =~ /Ascendos/i + "Ascendos" else "RedHat" end diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index 4cfd5a39ff..eaea4dccfc 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -17,10 +17,10 @@ # Facter.add(:operatingsystemrelease) do - confine :operatingsystem => %w{CentOS Fedora oel ovs OracleLinux RedHat MeeGo Scientific SLC CloudLinux PSBM} + confine :operatingsystem => %w{CentOS Fedora oel ovs OracleLinux RedHat MeeGo Scientific SLC Ascendos CloudLinux PSBM} setcode do case Facter.value(:operatingsystem) - when "CentOS", "RedHat", "Scientific", "SLC", "CloudLinux", "PSBM" + when "CentOS", "RedHat", "Scientific", "SLC", "Ascendos", "CloudLinux", "PSBM" releasefile = "/etc/redhat-release" when "Fedora" releasefile = "/etc/fedora-release" diff --git a/lib/facter/osfamily.rb b/lib/facter/osfamily.rb index 3df391d6c1..f3bb81d992 100644 --- a/lib/facter/osfamily.rb +++ b/lib/facter/osfamily.rb @@ -16,7 +16,7 @@ setcode do case Facter.value(:operatingsystem) - when "RedHat", "Fedora", "CentOS", "Scientific", "SLC", "CloudLinux", "PSBM", "OracleLinux", "OVS", "OEL" + when "RedHat", "Fedora", "CentOS", "Scientific", "SLC", "Ascendos", "CloudLinux", "PSBM", "OracleLinux", "OVS", "OEL" "RedHat" when "Ubuntu", "Debian" "Debian" diff --git a/lib/facter/uniqueid.rb b/lib/facter/uniqueid.rb index 265dc78638..c69a9e678d 100644 --- a/lib/facter/uniqueid.rb +++ b/lib/facter/uniqueid.rb @@ -1,4 +1,4 @@ Facter.add(:uniqueid) do setcode 'hostid' - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS Scientific PSBM SLC SuSE SLES Debian Ubuntu Gentoo AIX OEL OracleLinux OVS GNU/kFreeBSD} + confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS Scientific PSBM SLC Ascendos SuSE SLES Debian Ubuntu Gentoo AIX OEL OracleLinux OVS GNU/kFreeBSD} end diff --git a/spec/unit/operatingsystem_spec.rb b/spec/unit/operatingsystem_spec.rb index 04084f6c14..cba86ba91a 100755 --- a/spec/unit/operatingsystem_spec.rb +++ b/spec/unit/operatingsystem_spec.rb @@ -80,4 +80,13 @@ File.expects(:read).with("/etc/redhat-release").returns("Scientific Linux CERN SLC 5.7 (Boron)") Facter.fact(:operatingsystem).value.should == "SLC" end + + it "should identify Ascendos Linux" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + FileTest.stubs(:exists?).returns false + + FileTest.expects(:exists?).with("/etc/redhat-release").returns true + File.expects(:read).with("/etc/redhat-release").returns("Ascendos release 6.0 (Nameless)") + Facter.fact(:operatingsystem).value.should == "Ascendos" + end end diff --git a/spec/unit/operatingsystemrelease_spec.rb b/spec/unit/operatingsystemrelease_spec.rb index 189cb441ff..02de5c3510 100755 --- a/spec/unit/operatingsystemrelease_spec.rb +++ b/spec/unit/operatingsystemrelease_spec.rb @@ -25,6 +25,7 @@ "OVS" => "/etc/ovs-release", "ovs" => "/etc/ovs-release", "OracleLinux" => "/etc/oracle-release", + "Ascendos" => "/etc/redhat-release", } test_cases.each do |system, file| From b2a66a97eb518c5d6207353c18ce6056e9715995 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 24 Oct 2011 10:02:08 -0700 Subject: [PATCH 0692/3753] (#7038) Validate prtdiag output in manufacturer prtdiag cannot be run inside zones, and calling Facter::Util::Resolution.exec on it will return nil. The manufacturer utility was calling split() on nil, which was raising an exception. Added validation of prtdiag output, and simplified the regex to extract values for facts. Added more coverage for the related facts as well. --- lib/facter/util/manufacturer.rb | 5 +- .../manufacturer/solaris_sunfire_v120_prtdiag | 33 +++++ .../util/manufacturer/solaris_t5220_prtdiag | 136 ++++++++++++++++++ spec/spec_helper.rb | 5 + spec/unit/util/manufacturer_spec.rb | 20 ++- 5 files changed, 193 insertions(+), 6 deletions(-) create mode 100644 spec/fixtures/unit/util/manufacturer/solaris_sunfire_v120_prtdiag create mode 100644 spec/fixtures/unit/util/manufacturer/solaris_t5220_prtdiag diff --git a/lib/facter/util/manufacturer.rb b/lib/facter/util/manufacturer.rb index d6fde05683..7542eae1f7 100644 --- a/lib/facter/util/manufacturer.rb +++ b/lib/facter/util/manufacturer.rb @@ -63,11 +63,10 @@ def self.sysctl_find_system_info(name) def self.prtdiag_sparc_find_system_info() # Parses prtdiag for a SPARC architecture string, won't work with Solaris x86 - output = Facter::Util::Resolution.exec('/usr/sbin/prtdiag') + output = Facter::Util::Resolution.exec('/usr/sbin/prtdiag 2>/dev/null') # System Configuration: Sun Microsystems sun4u Sun SPARC Enterprise M3000 Server - sysconfig = output.split("\n")[0] - if sysconfig =~ /^System Configuration:\s+(.+?)\s+(sun\d+\S+)\s+(.+)/ then + if output and output =~ /^System Configuration:\s+(.+?)\s+(sun\d+\S+)\s+(.+)/ Facter.add('manufacturer') do setcode do $1 diff --git a/spec/fixtures/unit/util/manufacturer/solaris_sunfire_v120_prtdiag b/spec/fixtures/unit/util/manufacturer/solaris_sunfire_v120_prtdiag new file mode 100644 index 0000000000..e9949b5f5f --- /dev/null +++ b/spec/fixtures/unit/util/manufacturer/solaris_sunfire_v120_prtdiag @@ -0,0 +1,33 @@ +System Configuration: Sun Microsystems sun4u Sun Fire V120 (UltraSPARC-IIe 648MHz) +System clock frequency: 100 MHz +Memory size: 2048 Megabytes + +========================= CPUs ========================= + + Run Ecache CPU CPU +Brd CPU Module MHz MB Impl. Mask +--- --- ------- ----- ------ ------ ---- + 0 0 0 648 0.5 13 3.3 + + +========================= IO Cards ========================= + + Bus# Freq +Brd Type MHz Slot Name Model +--- ---- ---- ---- -------------------------------- ---------------------- + 0 PCI-1 33 12 ebus + 0 PCI-1 33 3 pmu-pci10b9,7101 + 0 PCI-1 33 3 lomp + 0 PCI-1 33 7 isa + 0 PCI-1 33 12 network-pci108e,1101 SUNW,pci-eri + 0 PCI-1 33 12 usb-pci108e,1103.1 + 0 PCI-1 33 13 ide-pci10b9,5229 + 0 PCI-1 33 5 network-pci108e,1101 SUNW,pci-eri + 0 PCI-1 33 5 usb-pci108e,1103.1 + 0 PCI-2 33 8 scsi-glm Symbios,53C896 + 0 PCI-2 33 8 scsi-glm Symbios,53C896 + 0 PCI-2 33 5 network-pci108e,2bad SUNW,pci-gem + + +No failures found in System +=========================== diff --git a/spec/fixtures/unit/util/manufacturer/solaris_t5220_prtdiag b/spec/fixtures/unit/util/manufacturer/solaris_t5220_prtdiag new file mode 100644 index 0000000000..91a428d8cb --- /dev/null +++ b/spec/fixtures/unit/util/manufacturer/solaris_t5220_prtdiag @@ -0,0 +1,136 @@ +System Configuration: Sun Microsystems sun4v SPARC Enterprise T5220 +Memory size: 32640 Megabytes + +================================ Virtual CPUs ================================ + + +CPU ID Frequency Implementation Status +------ --------- ---------------------- ------- +0 1165 MHz SUNW,UltraSPARC-T2 on-line +1 1165 MHz SUNW,UltraSPARC-T2 on-line +2 1165 MHz SUNW,UltraSPARC-T2 on-line +3 1165 MHz SUNW,UltraSPARC-T2 on-line +4 1165 MHz SUNW,UltraSPARC-T2 on-line +5 1165 MHz SUNW,UltraSPARC-T2 on-line +6 1165 MHz SUNW,UltraSPARC-T2 on-line +7 1165 MHz SUNW,UltraSPARC-T2 on-line +8 1165 MHz SUNW,UltraSPARC-T2 on-line +9 1165 MHz SUNW,UltraSPARC-T2 on-line +10 1165 MHz SUNW,UltraSPARC-T2 on-line +11 1165 MHz SUNW,UltraSPARC-T2 on-line +12 1165 MHz SUNW,UltraSPARC-T2 on-line +13 1165 MHz SUNW,UltraSPARC-T2 on-line +14 1165 MHz SUNW,UltraSPARC-T2 on-line +15 1165 MHz SUNW,UltraSPARC-T2 on-line +16 1165 MHz SUNW,UltraSPARC-T2 on-line +17 1165 MHz SUNW,UltraSPARC-T2 on-line +18 1165 MHz SUNW,UltraSPARC-T2 on-line +19 1165 MHz SUNW,UltraSPARC-T2 on-line +20 1165 MHz SUNW,UltraSPARC-T2 on-line +21 1165 MHz SUNW,UltraSPARC-T2 on-line +22 1165 MHz SUNW,UltraSPARC-T2 on-line +23 1165 MHz SUNW,UltraSPARC-T2 on-line +24 1165 MHz SUNW,UltraSPARC-T2 on-line +25 1165 MHz SUNW,UltraSPARC-T2 on-line +26 1165 MHz SUNW,UltraSPARC-T2 on-line +27 1165 MHz SUNW,UltraSPARC-T2 on-line +28 1165 MHz SUNW,UltraSPARC-T2 on-line +29 1165 MHz SUNW,UltraSPARC-T2 on-line +30 1165 MHz SUNW,UltraSPARC-T2 on-line +31 1165 MHz SUNW,UltraSPARC-T2 on-line +32 1165 MHz SUNW,UltraSPARC-T2 on-line +33 1165 MHz SUNW,UltraSPARC-T2 on-line +34 1165 MHz SUNW,UltraSPARC-T2 on-line +35 1165 MHz SUNW,UltraSPARC-T2 on-line +36 1165 MHz SUNW,UltraSPARC-T2 on-line +37 1165 MHz SUNW,UltraSPARC-T2 on-line +38 1165 MHz SUNW,UltraSPARC-T2 on-line +39 1165 MHz SUNW,UltraSPARC-T2 on-line +40 1165 MHz SUNW,UltraSPARC-T2 on-line +41 1165 MHz SUNW,UltraSPARC-T2 on-line +42 1165 MHz SUNW,UltraSPARC-T2 on-line +43 1165 MHz SUNW,UltraSPARC-T2 on-line +44 1165 MHz SUNW,UltraSPARC-T2 on-line +45 1165 MHz SUNW,UltraSPARC-T2 on-line +46 1165 MHz SUNW,UltraSPARC-T2 on-line +47 1165 MHz SUNW,UltraSPARC-T2 on-line +48 1165 MHz SUNW,UltraSPARC-T2 on-line +49 1165 MHz SUNW,UltraSPARC-T2 on-line +50 1165 MHz SUNW,UltraSPARC-T2 on-line +51 1165 MHz SUNW,UltraSPARC-T2 on-line +52 1165 MHz SUNW,UltraSPARC-T2 on-line +53 1165 MHz SUNW,UltraSPARC-T2 on-line +54 1165 MHz SUNW,UltraSPARC-T2 on-line +55 1165 MHz SUNW,UltraSPARC-T2 on-line +56 1165 MHz SUNW,UltraSPARC-T2 on-line +57 1165 MHz SUNW,UltraSPARC-T2 on-line +58 1165 MHz SUNW,UltraSPARC-T2 on-line +59 1165 MHz SUNW,UltraSPARC-T2 on-line +60 1165 MHz SUNW,UltraSPARC-T2 on-line +61 1165 MHz SUNW,UltraSPARC-T2 on-line +62 1165 MHz SUNW,UltraSPARC-T2 on-line +63 1165 MHz SUNW,UltraSPARC-T2 on-line + +======================= Physical Memory Configuration ======================== +Segment Table: +-------------------------------------------------------------- +Base Segment Interleave Bank Contains +Address Size Factor Size Modules +-------------------------------------------------------------- +0x0 32 GB 8 4 GB MB/CMP0/BR0/CH0/D0 + MB/CMP0/BR0/CH1/D0 + 4 GB MB/CMP0/BR0/CH0/D1 + MB/CMP0/BR0/CH1/D1 + 4 GB MB/CMP0/BR1/CH0/D0 + MB/CMP0/BR1/CH1/D0 + 4 GB MB/CMP0/BR1/CH0/D1 + MB/CMP0/BR1/CH1/D1 + 4 GB MB/CMP0/BR2/CH0/D0 + MB/CMP0/BR2/CH1/D0 + 4 GB MB/CMP0/BR2/CH0/D1 + MB/CMP0/BR2/CH1/D1 + 4 GB MB/CMP0/BR3/CH0/D0 + MB/CMP0/BR3/CH1/D0 + 4 GB MB/CMP0/BR3/CH0/D1 + MB/CMP0/BR3/CH1/D1 + + +================================ IO Devices ================================ +Slot + Bus Name + Model +Status Type Path +---------------------------------------------------------------------------- +MB/NET0 PCIE network-pciex8086,105e + /pci@0/pci@0/pci@1/pci@0/pci@2/network@0 +MB/NET1 PCIE network-pciex8086,105e + /pci@0/pci@0/pci@1/pci@0/pci@2/network@0,1 +MB/NET2 PCIE network-pciex8086,105e + /pci@0/pci@0/pci@1/pci@0/pci@3/network@0 +MB/NET3 PCIE network-pciex8086,105e + /pci@0/pci@0/pci@1/pci@0/pci@3/network@0,1 +MB/SASHBA PCIE scsi-pciex1000,58 LSI,1068E + /pci@0/pci@0/pci@2/scsi@0 +MB PCIX usb-pciclass,0c0310 + /pci@0/pci@0/pci@1/pci@0/pci@1/pci@0/usb@0 +MB PCIX usb-pciclass,0c0310 + /pci@0/pci@0/pci@1/pci@0/pci@1/pci@0/usb@0,1 +MB PCIX usb-pciclass,0c0320 + /pci@0/pci@0/pci@1/pci@0/pci@1/pci@0/usb@0,2 + +============================ Environmental Status ============================ +Fan sensors: +All fan sensors are OK. + +Temperature sensors: +All temperature sensors are OK. + +Current sensors: +All current sensors are OK. + +Voltage sensors: +All voltage sensors are OK. + +Voltage indicators: +All voltage indicators are OK. + +============================ FRU Status ============================ +All FRUs are enabled. diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index b58a5b672b..000023af86 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -2,6 +2,11 @@ SPECDIR = dir +def fixture_data(file) + File.read(File.join(SPECDIR, "fixtures", file)) +end + + $LOAD_PATH.unshift("#{dir}/") $LOAD_PATH.unshift("#{dir}/../lib") diff --git a/spec/unit/util/manufacturer_spec.rb b/spec/unit/util/manufacturer_spec.rb index ed248ca2c9..dfaab51c5f 100644 --- a/spec/unit/util/manufacturer_spec.rb +++ b/spec/unit/util/manufacturer_spec.rb @@ -16,11 +16,25 @@ Facter::Manufacturer.get_dmi_table().should be_nil end - it "should parse prtdiag output" do - Facter::Util::Resolution.stubs(:exec).returns("System Configuration: Sun Microsystems sun4u Sun SPARC Enterprise M3000 Server") + it "should parse prtdiag output on a sunfire v120" do + Facter::Util::Resolution.stubs(:exec).returns(fixture_data(File.join("unit", "util", "manufacturer", "solaris_sunfire_v120_prtdiag"))) Facter::Manufacturer.prtdiag_sparc_find_system_info() Facter.value(:manufacturer).should == "Sun Microsystems" - Facter.value(:productname).should == "Sun SPARC Enterprise M3000 Server" + Facter.value(:productname).should == "Sun Fire V120 (UltraSPARC-IIe 648MHz)" + end + + it "should parse prtdiag output on a t5220" do + Facter::Util::Resolution.stubs(:exec).returns(fixture_data(File.join("unit", "util", "manufacturer", "solaris_t5220_prtdiag"))) + Facter::Manufacturer.prtdiag_sparc_find_system_info() + Facter.value(:manufacturer).should == "Sun Microsystems" + Facter.value(:productname).should == "SPARC Enterprise T5220" + end + + it "should not set manufacturer or productname if prtdiag output is nil" do + Facter::Util::Resolution.stubs(:exec).returns(nil) + Facter::Manufacturer.prtdiag_sparc_find_system_info() + Facter.value(:manufacturer).should be_nil + Facter.value(:productname).should be_nil end it "should strip white space on dmi output with spaces" do From fe4265f3361bf83fa44a08b2b4b1f8afc9f737d4 Mon Sep 17 00:00:00 2001 From: Michael Stahnke Date: Mon, 31 Oct 2011 11:57:03 -0700 Subject: [PATCH 0693/3753] Updated CHANGELOG for 1.6.3rc1 --- CHANGELOG | 11 +++++++++++ conf/redhat/facter.spec | 15 +++++++++------ conf/solaris/pkginfo | 2 +- lib/facter.rb | 2 +- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 9b212304f4..9abfeff1b8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,14 @@ +1.6.3rc1 +=== +b2a66a9 (#7038) Validate prtdiag output in manufacturer +c9db305 (#10228) Ascendos OS support for various facts. +6efadbb (#10233) Adds support for Parallels Server Bare Metal to Facter +ce8f572 (#10079) Remove trailing whitespace +e89758d Updated CHANGELOG for 1.6.2 +7b14b77 (#9928) Correct readlines stubbing to return an array instead of an empty string. +0d6df28 (#9904) Remove windows rspec warning. +f0ccb5e (#9555) Spec tests: Change all cases of tabs and 4 space indentation to 2 space indentation. + 1.6.2 === d7c00f6 (#9852) Fixing watchr on facter diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index ede21688a5..0aa6f36fa5 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -2,17 +2,17 @@ %define has_ruby_abi 0%{?fedora} || 0%{?rhel} >= 5 %define has_ruby_noarch %has_ruby_abi -%global _ver 1.6.2 +%global _ver 1.6.3 Summary: Ruby module for collecting simple facts about a host operating system Name: facter -Version: 1.6.2 -Release: 1%{?dist} +Version: 1.6.3 +Release: 0.1rc1%{?dist} License: Apache 2.0 Group: System Environment/Base URL: http://www.puppetlabs.com/puppet/related-projects/%{name}/ -Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{_ver}.tar.gz -#Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}.tar.gz.sign +Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc1.tar.gz +#Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc1.tar.gz.asc BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) %if %has_ruby_noarch @@ -32,7 +32,7 @@ system. Some of the facts are preconfigured, such as the hostname and the operating system. Additional facts can be added through simple Ruby scripts %prep -%setup -q -n %{name}-%{_ver} +%setup -q -n %{name}-%{version}rc1 %build @@ -53,6 +53,9 @@ rm -rf %{buildroot} %changelog +* Mon Oct 31 2011 Michael Stahnke - 1.6.3-0.1rc1 +- 1.6.3 rc1 + * Mon Oct 10 2011 Michael Stahnke - 1.6.2-1 - Update to 1.6.2 diff --git a/conf/solaris/pkginfo b/conf/solaris/pkginfo index 58bb4f90ac..7c24d5985a 100644 --- a/conf/solaris/pkginfo +++ b/conf/solaris/pkginfo @@ -1,6 +1,6 @@ PKG=CSWfacter NAME=facter - System Fact Gatherer -VERSION=1.6.2 +VERSION=1.6.3rc1 CATEGORY=application VENDOR=http://www.puppetlabs.com/puppet/related-projects/facter HOTLINE=http://puppetlabs.com/cgi-bin/facter.cgi diff --git a/lib/facter.rb b/lib/facter.rb index 23f8f98bf0..35bb61f90c 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -24,7 +24,7 @@ module Util; end include Comparable include Enumerable - FACTERVERSION = '1.6.2' + FACTERVERSION = '1.6.3' # = Facter # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. From ce67c985d22fee95ae2efb554158cea9d9ee29fe Mon Sep 17 00:00:00 2001 From: Jonathan Grochowski Date: Mon, 24 Oct 2011 14:25:18 -0700 Subject: [PATCH 0694/3753] (#10251) Creating RC tarballs should be handled by rake. Previously generating tarballs for rc's required a lot of manual intervention. This fix corrects the versioning scheme so that everything is handled automatically via the rake task. --- Rakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index 380416bb9f..f0487a1de8 100644 --- a/Rakefile +++ b/Rakefile @@ -18,7 +18,7 @@ require 'rake/packagetask' require 'rake/gempackagetask' module Facter - FACTERVERSION = File.read('lib/facter.rb')[/FACTERVERSION *= *'(.*)'/,1] or fail "Couldn't find FACTERVERSION" + FACTERVERSION = `git describe`.strip end FILES = FileList[ From 95b7f24625d37082aedb8b177e0ec9245fc0e90c Mon Sep 17 00:00:00 2001 From: Ken Barber Date: Mon, 31 Oct 2011 20:36:01 +0000 Subject: [PATCH 0695/3753] (#10395) Fix manufacturer test moching for Windows Commit b2a66a97eb518c5d6207353c18ce6056e9715995 brought in changes to test for prtdiag returning nothing. Because the fact returns nothing though, on windows the tests fall back to use Windows own mechanism for detection therefore causing the test assertion to fail. It should be nil, but instead its return a real manufacturer value. To stop it from falling back to the windows mechanism, I'm simply moching the kernel fact to be 'SunOS' instead. --- spec/unit/util/manufacturer_spec.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spec/unit/util/manufacturer_spec.rb b/spec/unit/util/manufacturer_spec.rb index dfaab51c5f..e16215e29a 100644 --- a/spec/unit/util/manufacturer_spec.rb +++ b/spec/unit/util/manufacturer_spec.rb @@ -31,6 +31,9 @@ end it "should not set manufacturer or productname if prtdiag output is nil" do + # Stub kernel so we don't have windows fall through to its own mechanism + Facter.fact(:kernel).stubs(:value).returns("SunOS") + Facter::Util::Resolution.stubs(:exec).returns(nil) Facter::Manufacturer.prtdiag_sparc_find_system_info() Facter.value(:manufacturer).should be_nil From e0cb1ae32e79e093855fad722752c736cab03b2d Mon Sep 17 00:00:00 2001 From: Jacob Helwig Date: Mon, 31 Oct 2011 15:27:38 -0700 Subject: [PATCH 0696/3753] Revert "Merge branch 'ticket/10251' of git://github.com/jgrocho/facter" This reverts commit ac1c09a083116aed5df5bc41251aa54a495f348a, reversing changes made to 4c12d3a8fdfacc5f10aa483f39fafc70ea6f9930. This causes problems with running rake tasks, since rubygems will end up with a version string along the lines of '1.6.2rc1-26-gac1c09a', which it does not like at all. --- Rakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index f0487a1de8..380416bb9f 100644 --- a/Rakefile +++ b/Rakefile @@ -18,7 +18,7 @@ require 'rake/packagetask' require 'rake/gempackagetask' module Facter - FACTERVERSION = `git describe`.strip + FACTERVERSION = File.read('lib/facter.rb')[/FACTERVERSION *= *'(.*)'/,1] or fail "Couldn't find FACTERVERSION" end FILES = FileList[ From 0bad18b931766fcd3af098de80048138e35a3426 Mon Sep 17 00:00:00 2001 From: Stefan Schulte Date: Sat, 5 Nov 2011 10:41:38 +0100 Subject: [PATCH 0697/3753] (#10490) Handle case where no macaddress can be found If we do not find a macaddress when running ifconfig -a (case is described in #10490 and is reproduceable if you deconfigure all interfaces despite lo0) we get the error: Could not retrieve macaddress: private method split' called for nil:NilClass Return nil as a macaddress if no macaddress can be found. The same behaviour is already used for macaddresses on windows. --- lib/facter/util/macaddress.rb | 1 + spec/fixtures/ifconfig/linux_ifconfig_no_mac | 8 ++++ spec/fixtures/ifconfig/linux_ifconfig_venet | 24 ++++++++++ spec/unit/facter_spec.rb | 4 +- spec/unit/macaddress_spec.rb | 48 +++++++++++++++----- spec/unit/util/macaddress_spec.rb | 7 ++- 6 files changed, 78 insertions(+), 14 deletions(-) create mode 100644 spec/fixtures/ifconfig/linux_ifconfig_no_mac create mode 100644 spec/fixtures/ifconfig/linux_ifconfig_venet diff --git a/lib/facter/util/macaddress.rb b/lib/facter/util/macaddress.rb index 8c4a561826..82470ec41c 100644 --- a/lib/facter/util/macaddress.rb +++ b/lib/facter/util/macaddress.rb @@ -3,6 +3,7 @@ module Facter::Util::Macaddress def self.standardize(macaddress) + return nil unless macaddress macaddress.split(":").map{|x| "0#{x}"[-2..-1]}.join(":") end diff --git a/spec/fixtures/ifconfig/linux_ifconfig_no_mac b/spec/fixtures/ifconfig/linux_ifconfig_no_mac new file mode 100644 index 0000000000..13d6509dbd --- /dev/null +++ b/spec/fixtures/ifconfig/linux_ifconfig_no_mac @@ -0,0 +1,8 @@ +lo Link encap:Local Loopback + inet addr:127.0.0.1 Mask:255.0.0.0 + UP LOOPBACK RUNNING MTU:16436 Metric:1 + RX packets:0 errors:0 dropped:0 overruns:0 frame:0 + TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:0 + RX bytes:0 (0.0 B) TX bytes:0 (0.0 B) + diff --git a/spec/fixtures/ifconfig/linux_ifconfig_venet b/spec/fixtures/ifconfig/linux_ifconfig_venet new file mode 100644 index 0000000000..2b5cded6f9 --- /dev/null +++ b/spec/fixtures/ifconfig/linux_ifconfig_venet @@ -0,0 +1,24 @@ +lo Link encap:Local Loopback + inet addr:127.0.0.1 Mask:255.0.0.0 + inet6 addr: ::1/128 Scope:Host + UP LOOPBACK RUNNING MTU:16436 Metric:1 + RX packets:334 errors:0 dropped:0 overruns:0 frame:0 + TX packets:334 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:0 + RX bytes:16700 (16.7 KB) TX bytes:16700 (16.7 KB) + +venet0 Link encap:UNSPEC HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 + inet addr:127.0.0.1 P-t-P:127.0.0.1 Bcast:0.0.0.0 Mask:255.255.255.255 + UP BROADCAST POINTOPOINT RUNNING NOARP MTU:1500 Metric:1 + RX packets:7622207 errors:0 dropped:0 overruns:0 frame:0 + TX packets:8183436 errors:0 dropped:1 overruns:0 carrier:0 + collisions:0 txqueuelen:0 + RX bytes:2102750761 (2.1 GB) TX bytes:2795213667 (2.7 GB) + +venet0:0 Link encap:UNSPEC HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 + inet addr:XXX.XXX.XXX.XX1 P-t-P:XXX.XXX.XXX.XX1 Bcast:0.0.0.0 Mask:255.255.255.255 + UP BROADCAST POINTOPOINT RUNNING NOARP MTU:1500 Metric:1 + +venet0:1 Link encap:UNSPEC HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 + inet addr:XXX.XXX.XXX.XX2 P-t-P:XXX.XXX.XXX.XX2 Bcast:0.0.0.0 Mask:255.255.255.255 + UP BROADCAST POINTOPOINT RUNNING NOARP MTU:1500 Metric:1 diff --git a/spec/unit/facter_spec.rb b/spec/unit/facter_spec.rb index 0b4612f673..89cf673fa4 100755 --- a/spec/unit/facter_spec.rb +++ b/spec/unit/facter_spec.rb @@ -120,7 +120,9 @@ # #33 Make sure we only get one mac address it "should only return one mac address" do - Facter.value(:macaddress).should_not be_include(" ") + if macaddress = Facter.value(:macaddress) + macaddress.should_not be_include(" ") + end end it "should have a method for registering directories to search" do diff --git a/spec/unit/macaddress_spec.rb b/spec/unit/macaddress_spec.rb index ef80baa07b..b18670fa3f 100755 --- a/spec/unit/macaddress_spec.rb +++ b/spec/unit/macaddress_spec.rb @@ -18,21 +18,45 @@ def netsh_fixture(filename) Facter::Util::Config.stubs(:is_windows?).returns(false) end - it "should return macaddress information for Linux" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:operatingsystem).stubs(:value).returns("Linux") - Facter::Util::Resolution.stubs(:exec).with('/sbin/ifconfig -a'). - returns(ifconfig_fixture('linux_ifconfig_all_with_multiple_interfaces')) - - Facter.value(:macaddress).should == "00:12:3f:be:22:01" + describe "when run on Linux" do + before :each do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:operatingsystem).stubs(:value).returns("Linux") + end + + it "should return the macaddress of the first interface" do + Facter::Util::Resolution.stubs(:exec).with('/sbin/ifconfig -a'). + returns(ifconfig_fixture('linux_ifconfig_all_with_multiple_interfaces')) + + Facter.value(:macaddress).should == "00:12:3f:be:22:01" + end + + it "should return nil when no macaddress can be found" do + Facter::Util::Resolution.stubs(:exec).with('/sbin/ifconfig -a'). + returns(ifconfig_fixture('linux_ifconfig_no_mac')) + + proc { Facter.value(:macaddress) }.should_not raise_error + Facter.value(:macaddress).should be_nil + end + + # some interfaces dont have a real mac addresses (like venet inside a container) + it "should return nil when no interface has a real macaddress" do + Facter::Util::Resolution.stubs(:exec).with('/sbin/ifconfig -a'). + returns(ifconfig_fixture('linux_ifconfig_venet')) + + proc { Facter.value(:macaddress) }.should_not raise_error + Facter.value(:macaddress).should be_nil + end end - it "should return macaddress information for BSD" do - Facter.fact(:kernel).stubs(:value).returns("FreeBSD") - Facter::Util::Resolution.stubs(:exec).with('/sbin/ifconfig'). - returns(ifconfig_fixture('bsd_ifconfig_all_with_multiple_interfaces')) + describe "when run on BSD" do + it "should return macaddress information" do + Facter.fact(:kernel).stubs(:value).returns("FreeBSD") + Facter::Util::Resolution.stubs(:exec).with('/sbin/ifconfig'). + returns(ifconfig_fixture('bsd_ifconfig_all_with_multiple_interfaces')) - Facter.value(:macaddress).should == "00:0b:db:93:09:67" + Facter.value(:macaddress).should == "00:0b:db:93:09:67" + end end end diff --git a/spec/unit/util/macaddress_spec.rb b/spec/unit/util/macaddress_spec.rb index 2448f29079..4a3517aafe 100755 --- a/spec/unit/util/macaddress_spec.rb +++ b/spec/unit/util/macaddress_spec.rb @@ -8,10 +8,15 @@ it "should have zeroes added if missing" do Facter::Util::Macaddress::standardize("0:ab:cd:e:12:3").should == "00:ab:cd:0e:12:03" end - + it "should be identical if each octet already has two digits" do Facter::Util::Macaddress::standardize("00:ab:cd:0e:12:03").should == "00:ab:cd:0e:12:03" end + + it "should be nil if input is nil" do + proc { result = Facter::Util::Macaddress.standardize(nil) }.should_not raise_error + Facter::Util::Macaddress.standardize(nil).should be_nil + end end describe "Darwin", :unless => Facter.value(:operatingsystem) == 'windows' do From 047af9dd9eec80583c8ca6731e6d6da40c3f91ad Mon Sep 17 00:00:00 2001 From: Matthaus Litteken Date: Wed, 9 Nov 2011 11:04:36 -0800 Subject: [PATCH 0698/3753] Updating CHANGELOG for 1.6.3 release Signed-off-by: Matthaus Litteken --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 9abfeff1b8..6a7c55f8fc 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,4 @@ -1.6.3rc1 +1.6.3 === b2a66a9 (#7038) Validate prtdiag output in manufacturer c9db305 (#10228) Ascendos OS support for various facts. From dd704c5be2c88d6322972907b3d25f660e9593ee Mon Sep 17 00:00:00 2001 From: Jason Gill Date: Wed, 9 Nov 2011 19:23:00 -0500 Subject: [PATCH 0699/3753] (#10238) Adds support for retrieving information about block devices in Linux, with tests; updated to include sizes and be more flexible for future additions --- lib/facter/blockdevices.rb | 105 ++++++++++++++++++++++++++++++++ spec/unit/blockdevices_spec.rb | 108 +++++++++++++++++++++++++++++++++ 2 files changed, 213 insertions(+) create mode 100644 lib/facter/blockdevices.rb create mode 100644 spec/unit/blockdevices_spec.rb diff --git a/lib/facter/blockdevices.rb b/lib/facter/blockdevices.rb new file mode 100644 index 0000000000..875718e6e7 --- /dev/null +++ b/lib/facter/blockdevices.rb @@ -0,0 +1,105 @@ +# Fact: blockdevice__size +# +# Purpose: +# Return the size of a block device in bytes +# +# Resolution: +# Parse the contents of /sys/block//size to receive the size (multiplying by 512 to correct for blocks-to-bytes) +# +# Caveats: +# Only supports Linux 2.6+ at this time, due to the reliance on sysfs +# + +# Fact: blockdevice__vendor +# +# Purpose: +# Return the vendor name of block devices attached to the system +# +# Resolution: +# Parse the contents of /sys/block//device/vendor to retrieve the vendor for a device +# +# Caveats: +# Only supports Linux 2.6+ at this time, due to the reliance on sysfs +# + +# Fact: blockdevice__model +# +# Purpose: +# Return the model name of block devices attached to the system +# +# Resolution: +# Parse the contents of /sys/block//device/model to retrieve the model name/number for a device +# +# Caveats: +# Only supports Linux 2.6+ at this time, due to the reliance on sysfs +# + + +# Fact: blockdevices +# +# Purpose: +# Return a comma seperated list of block devices +# +# Resolution: +# Retrieve the block devices that were identified and iterated over in the creation of the blockdevice_ facts +# +# Caveats: +# Block devices must have been identified using sysfs information +# + +# Author: Jason Gill + +require 'facter' + +# Only Linux 2.6+ kernels support sysfs which is required to easily get device details +if Facter.value(:kernel) == 'Linux' + + sysfs_block_directory = '/sys/block/' + + blockdevices = [] + + # This should prevent any non-2.6 kernels or odd machines without sysfs support from being investigated further + if File.exist?(sysfs_block_directory) + + # Iterate over each file in the /sys/block/ directory and skip ones that do not have a device subdirectory + Dir.entries(sysfs_block_directory).each do |device| + sysfs_device_directory = sysfs_block_directory + device + "/device" + next unless File.exist?(sysfs_device_directory) + + # Add the device to the blockdevices list, which is returned as it's own fact later on + blockdevices << device + + sizefile = sysfs_block_directory + device + "/size" + vendorfile = sysfs_device_directory + "/vendor" + modelfile = sysfs_device_directory + "/model" + + if File.exist?(sizefile) + Facter.add("blockdevice_#{device}_size".to_sym) do + setcode { IO.read(sizefile).strip.to_i * 512 } + end + end + + if File.exist?(vendorfile) + Facter.add("blockdevice_#{device}_vendor".to_sym) do + setcode { IO.read(vendorfile).strip } + end + end + + if File.exist?(modelfile) + Facter.add("blockdevice_#{device}_model".to_sym) do + setcode { IO.read(modelfile).strip } + end + end + + end + + end + + # Return a comma-seperated list of block devices found + unless blockdevices.empty? + Facter.add(:blockdevices) do + setcode { blockdevices.sort.join(',') } + end + end + +end diff --git a/spec/unit/blockdevices_spec.rb b/spec/unit/blockdevices_spec.rb new file mode 100644 index 0000000000..ef5d2335d1 --- /dev/null +++ b/spec/unit/blockdevices_spec.rb @@ -0,0 +1,108 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') + +require 'facter' + +describe "Block device facts" do + + describe "on non-Linux OS" do + + it "should not exist when kernel isn't Linux" do + Facter.fact(:kernel).stubs(:value).returns("SunOS") + Facter.fact(:blockdevices).should == nil + end + + end + + describe "on Linux" do + + describe "with /sys/block/" do + + describe "with valid entries" do + before :each do + Facter.fact(:kernel).stubs(:value).returns("Linux") + File.stubs(:exist?).with('/sys/block/').returns(true) + + Dir.stubs(:entries).with("/sys/block/").returns([".", "..", "hda", "sda", "sdb"]) + + File.stubs(:exist?).with("/sys/block/./device").returns(false) + File.stubs(:exist?).with("/sys/block/../device").returns(false) + + stubdevices = [ + #device, size, vendor, model + ["hda"], + ["sda", "976773168", "ATA", "WDC WD5000AAKS-0"], + ["sdb", "8787591168", "DELL", "PERC H700"] + ] + + stubdevices.each do |device, size, vendor, model| + stubdir = "/sys/block/#{device}" + File.stubs(:exist?).with(stubdir + "/device").returns(true) + File.stubs(:exist?).with(stubdir + "/size").returns(size ? true : false) + File.stubs(:exist?).with(stubdir + "/device/model").returns(model ? true : false) + File.stubs(:exist?).with(stubdir + "/device/vendor").returns(vendor ? true : false) + IO.stubs(:read).with(stubdir + "/size").returns(size) if size + IO.stubs(:read).with(stubdir + "/device/vendor").returns(vendor) if vendor + IO.stubs(:read).with(stubdir + "/device/model").returns(model) if model + end + end + + it "should report three block devices, hda, sda, and sdb, with accurate information from sda and sdb, and without invalid . or .. entries" do + Facter.fact(:blockdevices).value.should == "hda,sda,sdb" + + # handle facts that should not exist + %w{ . .. hda }.each do |device| + Facter.fact("blockdevice_#{device}_size".to_sym).should == nil + Facter.fact("blockdevice_#{device}_vendor".to_sym).should == nil + Facter.fact("blockdevice_#{device}_model".to_sym).should == nil + end + + # handle facts that should exist + %w{ sda sdb }.each do |device| + Facter.fact("blockdevice_#{device}_size".to_sym).should_not == nil + Facter.fact("blockdevice_#{device}_vendor".to_sym).should_not == nil + Facter.fact("blockdevice_#{device}_model".to_sym).should_not == nil + end + + Facter.fact(:blockdevice_sda_model).value.should == "WDC WD5000AAKS-0" + Facter.fact(:blockdevice_sda_vendor).value.should == "ATA" + Facter.fact(:blockdevice_sda_size).value.should == 500107862016 + + Facter.fact(:blockdevice_sdb_model).value.should == "PERC H700" + Facter.fact(:blockdevice_sdb_vendor).value.should == "DELL" + Facter.fact(:blockdevice_sdb_size).value.should == 4499246678016 + + end + + end + describe "with invalid entries in /sys/block" do + before :each do + Facter.fact(:kernel).stubs(:value).returns("Linux") + File.stubs(:exist?).with('/sys/block/').returns(true) + + Dir.stubs(:entries).with("/sys/block/").returns([".", "..", "xda", "ydb"]) + + File.stubs(:exist?).with("/sys/block/./device").returns(false) + File.stubs(:exist?).with("/sys/block/../device").returns(false) + File.stubs(:exist?).with("/sys/block/xda/device").returns(false) + File.stubs(:exist?).with("/sys/block/ydb/device").returns(false) + end + + it "should not exist with invalid entries in /sys/block" do + Facter.fact(:blockdevices).should == nil + end + end + end + describe "without /sys/block/" do + + it "should not exist without /sys/block/ on Linux" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + File.stubs(:exist?).with('/sys/block/').returns(false) + Facter.fact(:blockdevices).should == nil + end + + end + + end +end \ No newline at end of file From bdbb2dad7d875ec20824854a0f41981b7fb3aa73 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Wed, 16 Nov 2011 10:32:15 -0800 Subject: [PATCH 0700/3753] (#10885) Malformed facter.bat when ruby dir contains backreferences Previously, we were substituting occurrences of and in the Windows batch wrapper scripts with the corresponding paths. However,the gsub replacement string was not escaped, so if a path contained something that looked like a backreference, e.g. C:\ruby\187, the gsub would try to replace the backreference with the corresponding capture group. Since there aren't any capture groups, the backreference was stripped from resulting facter.bat, resulting in an invalid path, C:\ruby87\bin\facter. This commit eliminates the need for gsub, since paths can contain other characters that have special meaning in a regexp, e.g. '.'. It also writes the bat file in 'text' mode so that the resulting file has '\r\n' line endings. --- install.rb | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/install.rb b/install.rb index 065ce68260..30af4f4097 100755 --- a/install.rb +++ b/install.rb @@ -418,9 +418,17 @@ def install_binfile(from, op_file, target) if not installed_wrapper tmp_file2 = File.join(tmp_dir, '_tmp_wrapper') cwn = File.join(Config::CONFIG['bindir'], op_file) - cwv = CMD_WRAPPER.gsub('', ruby.gsub(%r{/}) { "\\" }).gsub!('', cwn.gsub(%r{/}) { "\\" } ) - - File.open(tmp_file2, "wb") { |cw| cw.puts cwv } + cwv = <<-EOS +@echo off +if "%OS%"=="Windows_NT" goto WinNT +#{ruby} -x "#{cwn}" %1 %2 %3 %4 %5 %6 %7 %8 %9 +goto done +:WinNT +#{ruby} -x "#{cwn}" %* +goto done +:done +EOS + File.open(tmp_file2, "w") { |cw| cw.puts cwv } FileUtils.install(tmp_file2, File.join(target, "#{op_file}.bat"), :mode => 0755, :verbose => true) File.unlink(tmp_file2) @@ -431,17 +439,6 @@ def install_binfile(from, op_file, target) File.unlink(tmp_file) end -CMD_WRAPPER = <<-EOS -@echo off -if "%OS%"=="Windows_NT" goto WinNT - -x "" %1 %2 %3 %4 %5 %6 %7 %8 %9 -goto done -:WinNT - -x "" %* -goto done -:done -EOS - check_prereqs prepare_installation From ed814928a8d5c61e267aabb26151d37c8773af08 Mon Sep 17 00:00:00 2001 From: Jason Gill Date: Tue, 1 Nov 2011 19:46:25 -0400 Subject: [PATCH 0701/3753] (#10444) Add identification of system boards to Facter --- lib/facter/manufacturer.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/facter/manufacturer.rb b/lib/facter/manufacturer.rb index 5c07ba954f..7fe75e661d 100644 --- a/lib/facter/manufacturer.rb +++ b/lib/facter/manufacturer.rb @@ -42,6 +42,11 @@ Facter::Manufacturer.win32_find_system_info(win32_keys) else query = { + '[Bb]ase [Bb]oard [Ii]nformation' => [ + { 'Manufacturer:' => 'boardmanufacturer' }, + { 'Product(?: Name)?:' => 'boardproductname' }, + { 'Serial Number:' => 'boardserialnumber' } + ], '[Ss]ystem [Ii]nformation' => [ { 'Manufacturer:' => 'manufacturer' }, { 'Product(?: Name)?:' => 'productname' }, From a9140d549a0bfba66ce7a4643cda94209ccebeda Mon Sep 17 00:00:00 2001 From: Ken Barber Date: Sat, 19 Nov 2011 21:13:54 +0000 Subject: [PATCH 0702/3753] (#6617) Deprecate DESTDIR environment variable for installer The DESTDIR environment variable has been deprecated since release 1.5.1. This commit finally removes it completely. Instead, one should use --destdir as a switch on the command line. --- install.rb | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/install.rb b/install.rb index 30af4f4097..c7b332438c 100755 --- a/install.rb +++ b/install.rb @@ -258,20 +258,7 @@ def prepare_installation mandir = Config::CONFIG['mandir'] end - # To be deprecated once people move over to using --destdir option - if (destdir = ENV['DESTDIR']) - warn "DESTDIR is deprecated. Use --destdir instead." - bindir = join(destdir, bindir) - sbindir = join(destdir, sbindir) - mandir = join(destdir, mandir) - sitelibdir = join(destdir, sitelibdir) - - FileUtils.makedirs(bindir) - FileUtils.makedirs(sbindir) - FileUtils.makedirs(mandir) - FileUtils.makedirs(sitelibdir) - # This is the new way forward - elsif (destdir = InstallOptions.destdir) + if (destdir = InstallOptions.destdir) bindir = join(destdir, bindir) sbindir = join(destdir, sbindir) mandir = join(destdir, mandir) From 844b67b95a0cbcd965a4cbf88292e2a8032df479 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Tue, 12 Jul 2011 22:24:56 -0700 Subject: [PATCH 0703/3753] (#10964) Adding the ability to directly set values This is useful for defining facts that just have a static value, rather than those that are calculated in some way. E.g., Facter.add "foo", :value => "bar" As apposed to: Facter.add "foo" do setcode { "bar" } end Signed-off-by: Luke Kanies --- lib/facter/util/collection.rb | 17 +++++++++------- lib/facter/util/fact.rb | 7 ++----- lib/facter/util/resolution.rb | 2 ++ spec/unit/util/collection_spec.rb | 7 ++----- spec/unit/util/fact_spec.rb | 33 ++++++++++--------------------- spec/unit/util/resolution_spec.rb | 11 +++++++++++ 6 files changed, 37 insertions(+), 40 deletions(-) diff --git a/lib/facter/util/collection.rb b/lib/facter/util/collection.rb index d165ff0252..30d03d56ca 100644 --- a/lib/facter/util/collection.rb +++ b/lib/facter/util/collection.rb @@ -33,13 +33,16 @@ def add(name, options = {}, &block) if block resolve = fact.add(&block) - # Set any resolve-appropriate options - options.each do |opt, value| - method = opt.to_s + "=" - if resolve.respond_to?(method) - resolve.send(method, value) - options.delete(opt) - end + else + resolve = fact.add + end + + # Set any resolve-appropriate options + options.each do |opt, value| + method = opt.to_s + "=" + if resolve.respond_to?(method) + resolve.send(method, value) + options.delete(opt) end end diff --git a/lib/facter/util/fact.rb b/lib/facter/util/fact.rb index 070087b3be..23ccea4ae6 100644 --- a/lib/facter/util/fact.rb +++ b/lib/facter/util/fact.rb @@ -30,13 +30,10 @@ def initialize(name, options = {}) # Add a new resolution mechanism. This requires a block, which will then # be evaluated in the context of the new mechanism. - def add(&block) - raise ArgumentError, "You must pass a block to Fact.add" unless block_given? - + def add(value = nil, &block) resolve = Facter::Util::Resolution.new(@name) - resolve.instance_eval(&block) - + resolve.instance_eval(&block) if block @resolves << resolve # Immediately sort the resolutions, so that we always have diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index a5f65c3fc4..dc58cb5961 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -10,6 +10,7 @@ class Facter::Util::Resolution attr_accessor :interpreter, :code, :name, :timeout + attr_writer :value, :weight INTERPRETER = Facter::Util::Config.is_windows? ? "cmd.exe" : "/bin/sh" @@ -151,6 +152,7 @@ def to_s # How we get a value for our resolution mechanism. def value + return @value if @value result = nil return result if @code == nil diff --git a/spec/unit/util/collection_spec.rb b/spec/unit/util/collection_spec.rb index c6767278e2..65335f0f25 100755 --- a/spec/unit/util/collection_spec.rb +++ b/spec/unit/util/collection_spec.rb @@ -38,10 +38,8 @@ end it "should create a new fact if no fact with the same name already exists" do - fact = mock 'fact' - Facter::Util::Fact.expects(:new).with { |name, *args| name == :myname }.returns fact - @coll.add(:myname) + @coll.fact(:myname).name.should == :myname end it "should accept options" do @@ -51,10 +49,9 @@ it "should set any appropriate options on the fact instances" do # Use a real fact instance, because we're using respond_to? fact = Facter::Util::Fact.new(:myname) - fact.expects(:ldapname=).with("testing") - Facter::Util::Fact.expects(:new).with(:myname).returns fact @coll.add(:myname, :ldapname => "testing") + @coll.fact(:myname).ldapname.should == "testing" end it "should set appropriate options on the resolution instance" do diff --git a/spec/unit/util/fact_spec.rb b/spec/unit/util/fact_spec.rb index 9b8c8d1c41..9567cf70a9 100755 --- a/spec/unit/util/fact_spec.rb +++ b/spec/unit/util/fact_spec.rb @@ -33,39 +33,26 @@ before do @fact = Facter::Util::Fact.new("yay") - @resolution = mock 'resolution' - @resolution.stub_everything - - end - - it "should fail if no block is given" do - lambda { @fact.add }.should raise_error(ArgumentError) + @resolution = Facter::Util::Resolution.new("yay") end - it "should create a new resolution instance" do + it "should be to create a new resolution instance with a block" do Facter::Util::Resolution.expects(:new).returns @resolution @fact.add { } end - it "should instance_eval the passed block on the new resolution" do - @resolution.expects(:instance_eval) - - Facter::Util::Resolution.stubs(:new).returns @resolution - - @fact.add { } + @fact.add { + setcode { "foo" } + } + @fact.value.should == "foo" end it "should re-sort the resolutions by weight, so the most restricted resolutions are first" do - r1 = stub 'r1', :weight => 1 - r2 = stub 'r2', :weight => 2 - r3 = stub 'r3', :weight => 0 - Facter::Util::Resolution.expects(:new).times(3).returns(r1).returns(r2).returns(r3) - @fact.add { } - @fact.add { } - @fact.add { } - - @fact.instance_variable_get("@resolves").should == [r2, r1, r3] + @fact.add { self.value = "1"; self.weight = 1 } + @fact.add { self.value = "2"; self.weight = 2 } + @fact.add { self.value = "0"; self.weight = 0 } + @fact.value.should == "2" end end diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index 6064b3e79c..1c1902e3ff 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -13,6 +13,12 @@ Facter::Util::Resolution.new("yay").name.should == "yay" end + it "should be able to set the value" do + resolve = Facter::Util::Resolution.new("yay") + resolve.value = "foo" + resolve.value.should == "foo" + end + it "should have a method for setting the weight" do Facter::Util::Resolution.new("yay").should respond_to(:has_weight) end @@ -97,6 +103,11 @@ @resolve = Facter::Util::Resolution.new("yay") end + it "should return any value that has been provided" do + @resolve.value = "foo" + @resolve.value.should == "foo" + end + describe "and setcode has not been called" do it "should return nil" do Facter::Util::Resolution.expects(:exec).with(nil, nil).never From 6406c8fd6d05f585b6d1398c89012542f79e0f70 Mon Sep 17 00:00:00 2001 From: Michael Stahnke Date: Mon, 28 Nov 2011 15:06:43 -0800 Subject: [PATCH 0704/3753] (##11041) Add dmidecode as a requirement for rpm We were implicitly relying on dmidecode to determine certain facts without being certain that it was installed. This change to the rpm spec file will ensure we have dmidecode pulled in by rpm/yum as a dependency and thus causing the correct functionality on facter. Note this change only impacts EL based systems. The debian packaging is handled in another repository. Signed-off-by: Michael Stahnke --- conf/redhat/facter.spec | 1 + 1 file changed, 1 insertion(+) diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index 0aa6f36fa5..ca00f28e46 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -21,6 +21,7 @@ BuildArch: noarch Requires: ruby >= 1.8.1 Requires: which +Requires: dmidecode %if %has_ruby_abi Requires: ruby(abi) = 1.8 %endif From 0d4651948a4732da3d40e400fcce950204e0d3e9 Mon Sep 17 00:00:00 2001 From: Matthaus Litteken Date: Wed, 30 Nov 2011 17:48:12 -0800 Subject: [PATCH 0705/3753] Updated CHANGELOG for 1.6.4rc1 Signed-off-by: Matthaus Litteken --- CHANGELOG | 7 +++++++ conf/redhat/facter.spec | 7 +++++-- lib/facter.rb | 2 +- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 6a7c55f8fc..d797e2ea13 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,10 @@ +1.6.4rc1 +=== +6406c8f (##11041) Add dmidecode as a requirement for rpm +ed81492 (#10444) Add identification of system boards to Facter +bdbb2da (#10885) Malformed facter.bat when ruby dir contains backreferences +0bad18b (#10490) Handle case where no macaddress can be found + 1.6.3 === b2a66a9 (#7038) Validate prtdiag output in manufacturer diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index ca00f28e46..ba6f31545f 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -2,11 +2,11 @@ %define has_ruby_abi 0%{?fedora} || 0%{?rhel} >= 5 %define has_ruby_noarch %has_ruby_abi -%global _ver 1.6.3 +%global _ver 1.6.4 Summary: Ruby module for collecting simple facts about a host operating system Name: facter -Version: 1.6.3 +Version: 1.6.4 Release: 0.1rc1%{?dist} License: Apache 2.0 Group: System Environment/Base @@ -54,6 +54,9 @@ rm -rf %{buildroot} %changelog +* Wed Nov 30 2011 Matthaus Litteken - 1.6.4-0.1rc1 +- 1.6.4 rc1 + * Mon Oct 31 2011 Michael Stahnke - 1.6.3-0.1rc1 - 1.6.3 rc1 diff --git a/lib/facter.rb b/lib/facter.rb index 35bb61f90c..b919aa5d9d 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -24,7 +24,7 @@ module Util; end include Comparable include Enumerable - FACTERVERSION = '1.6.3' + FACTERVERSION = '1.6.4' # = Facter # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. From 7065eeeafd668a874ae2e421a0b7554043a71594 Mon Sep 17 00:00:00 2001 From: Matthaus Litteken Date: Wed, 7 Dec 2011 13:04:40 -0800 Subject: [PATCH 0706/3753] Updating CHANGELOG for 1.6.4 release --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index d797e2ea13..746c457ec8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,4 @@ -1.6.4rc1 +1.6.4 === 6406c8f (##11041) Add dmidecode as a requirement for rpm ed81492 (#10444) Add identification of system boards to Facter From 7713d950d4c5519c746d7e48ecba54a0016aad2d Mon Sep 17 00:00:00 2001 From: Stefan Schulte Date: Thu, 1 Dec 2011 20:04:10 +0100 Subject: [PATCH 0707/3753] (#11082) Add fact operatingsystemrelease for Solaris Algorithm plus sample input and desired output was provided by John Warburton --- lib/facter/operatingsystemrelease.rb | 10 ++++ spec/unit/operatingsystemrelease_spec.rb | 63 ++++++++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index 28d2519059..4d411aa6fe 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -148,6 +148,16 @@ setcode do Facter[:lsbdistrelease].value end end +Facter.add(:operatingsystemrelease) do + confine :operatingsystem => :solaris + setcode do + release = File.open('/etc/release','r') {|f| f.readline.chomp} + if match = /\s+s(\d+)[sx]?(_u\d+)?.*(?:SPARC|X86)/.match(release) + match.captures.join('') + end + end +end + Facter.add(:operatingsystemrelease) do setcode do Facter[:kernelrelease].value end end diff --git a/spec/unit/operatingsystemrelease_spec.rb b/spec/unit/operatingsystemrelease_spec.rb index 02de5c3510..00142e72e0 100755 --- a/spec/unit/operatingsystemrelease_spec.rb +++ b/spec/unit/operatingsystemrelease_spec.rb @@ -58,4 +58,67 @@ Facter.fact(:operatingsystemrelease).value.should == "foo" end + + describe "with operatingsystem reported as Solaris" do + + before :each do + Facter.fact(:kernel).stubs(:value).returns('SunOS') + Facter.fact(:operatingsystem).stubs(:value).returns('Solaris') + end + + { + 'Solaris 8 s28_38shwp2 SPARC' => '28', + 'Solaris 8 6/00 s28s_u1wos_08 SPARC' => '28_u1', + 'Solaris 8 10/00 s28s_u2wos_11b SPARC' => '28_u2', + 'Solaris 8 1/01 s28s_u3wos_08 SPARC' => '28_u3', + 'Solaris 8 4/01 s28s_u4wos_08 SPARC' => '28_u4', + 'Solaris 8 7/01 s28s_u5wos_08 SPARC' => '28_u5', + 'Solaris 8 10/01 s28s_u6wos_08a SPARC' => '28_u6', + 'Solaris 8 2/02 s28s_u7wos_08a SPARC' => '28_u7', + 'Solaris 8 HW 12/02 s28s_hw1wos_06a SPARC' => '28', + 'Solaris 8 HW 5/03 s28s_hw2wos_06a SPARC' => '28', + 'Solaris 8 HW 7/03 s28s_hw3wos_05a SPARC' => '28', + 'Solaris 8 2/04 s28s_hw4wos_05a SPARC' => '28', + 'Solaris 9 s9_58shwpl3 SPARC' => '9', + 'Solaris 9 9/02 s9s_u1wos_08b SPARC' => '9_u1', + 'Solaris 9 12/02 s9s_u2wos_10 SPARC' => '9_u2', + 'Solaris 9 4/03 s9s_u3wos_08 SPARC' => '9_u3', + 'Solaris 9 8/03 s9s_u4wos_08a SPARC' => '9_u4', + 'Solaris 9 12/03 s9s_u5wos_08b SPARC' => '9_u5', + 'Solaris 9 4/04 s9s_u6wos_08a SPARC' => '9_u6', + 'Solaris 9 9/04 s9s_u7wos_09 SPARC' => '9_u7', + 'Solaris 9 9/05 s9s_u8wos_05 SPARC' => '9_u8', + 'Solaris 9 9/05 HW s9s_u9wos_06b SPARC' => '9_u9', + 'Solaris 10 3/05 s10_74L2a SPARC' => '10', + 'Solaris 10 3/05 HW1 s10s_wos_74L2a SPARC' => '10', + 'Solaris 10 3/05 HW2 s10s_hw2wos_05 SPARC' => '10', + 'Solaris 10 1/06 s10s_u1wos_19a SPARC' => '10_u1', + 'Solaris 10 6/06 s10s_u2wos_09a SPARC' => '10_u2', + 'Solaris 10 11/06 s10s_u3wos_10 SPARC' => '10_u3', + 'Solaris 10 8/07 s10s_u4wos_12b SPARC' => '10_u4', + 'Solaris 10 5/08 s10s_u5wos_10 SPARC' => '10_u5', + 'Solaris 10 10/08 s10s_u6wos_07b SPARC' => '10_u6', + 'Solaris 10 5/09 s10s_u7wos_08 SPARC' => '10_u7', + 'Solaris 10 10/09 s10s_u8wos_08a SPARC' => '10_u8', + 'Oracle Solaris 10 9/10 s10s_u9wos_14a SPARC' => '10_u9', + 'Oracle Solaris 10 8/11 s10s_u10wos_17b SPARC' => '10_u10', + 'Solaris 10 3/05 HW1 s10x_wos_74L2a X86' => '10', + 'Solaris 10 1/06 s10x_u1wos_19a X86' => '10_u1', + 'Solaris 10 6/06 s10x_u2wos_09a X86' => '10_u2', + 'Solaris 10 11/06 s10x_u3wos_10 X86' => '10_u3', + 'Solaris 10 8/07 s10x_u4wos_12b X86' => '10_u4', + 'Solaris 10 5/08 s10x_u5wos_10 X86' => '10_u5', + 'Solaris 10 10/08 s10x_u6wos_07b X86' => '10_u6', + 'Solaris 10 5/09 s10x_u7wos_08 X86' => '10_u7', + 'Solaris 10 10/09 s10x_u8wos_08a X86' => '10_u8', + 'Oracle Solaris 10 9/10 s10x_u9wos_14a X86' => '10_u9', + 'Oracle Solaris 10 8/11 s10x_u10wos_17b X86' => '10_u10', + }.each do |fakeinput,expected_output| + it "should be able to parse a release of #{fakeinput}" do + File.stubs(:open).with('/etc/release','r').returns fakeinput + Facter.fact(:operatingsystemrelease).value.should == expected_output + end + end + + end end From 30e068a369fcb7ea1dce21e3d27747c9439f832a Mon Sep 17 00:00:00 2001 From: Stefan Schulte Date: Wed, 7 Dec 2011 11:26:53 +0100 Subject: [PATCH 0708/3753] (#11082) Add negative tests for operatingsystemrelease The current behaviour of the operatingsystemrelease fact is that we have a general operatingsystemrelease fact that will suit every platform and returns the kernelrelease fact. So if our solaris operatingsystemrelease fact does not work (/etc/release not present, empty, unexpected content) facter will try to resolve the general operatingsystemrelease fact. That is now reflected in the tests. --- spec/unit/operatingsystemrelease_spec.rb | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/spec/unit/operatingsystemrelease_spec.rb b/spec/unit/operatingsystemrelease_spec.rb index 00142e72e0..1dd391eea6 100755 --- a/spec/unit/operatingsystemrelease_spec.rb +++ b/spec/unit/operatingsystemrelease_spec.rb @@ -120,5 +120,22 @@ end end + it "should fallback to the kernelrelease fact if /etc/release is empty" do + File.stubs(:open).with('/etc/release','r').raises EOFError + Facter::Util::Resolution.any_instance.stubs(:warn) # do not pollute test output + Facter.fact(:operatingsystemrelease).value.should == Facter.fact(:kernelrelease).value + end + + it "should fallback to the kernelrelease fact if /etc/release is not present" do + File.stubs(:open).with('/etc/release','r').raises Errno::ENOENT + Facter::Util::Resolution.any_instance.stubs(:warn) # do not pollute test output + Facter.fact(:operatingsystemrelease).value.should == Facter.fact(:kernelrelease).value + end + + it "should fallback to the kernelrelease fact if /etc/release cannot be parsed" do + File.stubs(:open).with('/etc/release','r').returns 'some future release string' + Facter.fact(:operatingsystemrelease).value.should == Facter.fact(:kernelrelease).value + end + end end From 2eb4ede4eb7e5bc66fc11cbef0c9fa80c245c662 Mon Sep 17 00:00:00 2001 From: Michael Kincaid Date: Tue, 27 Sep 2011 14:29:49 -0700 Subject: [PATCH 0709/3753] (#9708) Confine facts by kernel not operating system and remove confine for hardwareisa This will allow the following facts to continue working even when new variants of Linux, BSD, etc are supported in the future. * lsbmajdistrelease * macaddress * uniqueid For hardwareisa the confine was removed entirely. On any system that returns "uname -p", the value should be this fact. On systems that don't have "uname -p", the resolution will not be used. Thus, it shouldn't be necessary to confine the resolution by kernel. Previously we were constantly requiring patches to be added whenever a new Linux distribution was added. These changes should reduce this need. The fact 'operatingsystem', 'osfamily' and 'operatingsystemrelease' will still require patches however. New spec tests were added where they were missing. --- lib/facter/hardwareisa.rb | 1 - lib/facter/lsbmajdistrelease.rb | 2 +- lib/facter/macaddress.rb | 2 +- lib/facter/uniqueid.rb | 2 +- spec/unit/hardwareisa_spec.rb | 35 +++++++++++++++++++++++++++++ spec/unit/lsbmajdistrelease_spec.rb | 14 ++++++++++++ spec/unit/uniqueid_spec.rb | 28 +++++++++++++++++++++++ 7 files changed, 80 insertions(+), 4 deletions(-) create mode 100755 spec/unit/hardwareisa_spec.rb create mode 100755 spec/unit/lsbmajdistrelease_spec.rb create mode 100755 spec/unit/uniqueid_spec.rb diff --git a/lib/facter/hardwareisa.rb b/lib/facter/hardwareisa.rb index ecb8573457..ecd5a19de3 100644 --- a/lib/facter/hardwareisa.rb +++ b/lib/facter/hardwareisa.rb @@ -12,5 +12,4 @@ Facter.add(:hardwareisa) do setcode 'uname -p' - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS Scientific PSBM SLC Ascendos SuSE SLES Debian Ubuntu Gentoo FreeBSD OpenBSD NetBSD DragonFly OEL OracleLinux OVS GNU/kFreeBSD} end diff --git a/lib/facter/lsbmajdistrelease.rb b/lib/facter/lsbmajdistrelease.rb index 69eb5da539..a40441a038 100644 --- a/lib/facter/lsbmajdistrelease.rb +++ b/lib/facter/lsbmajdistrelease.rb @@ -15,7 +15,7 @@ require 'facter' Facter.add("lsbmajdistrelease") do - confine :operatingsystem => %w{Linux Fedora RedHat CentOS Scientific PSBM SLC Ascendos SuSE SLES Debian Ubuntu Gentoo OEL OracleLinux OVS GNU/kFreeBSD} + confine :kernel => %w{Linux GNU/kFreeBSD} setcode do if /(\d*)\./i =~ Facter.value(:lsbdistrelease) result=$1 diff --git a/lib/facter/macaddress.rb b/lib/facter/macaddress.rb index 32daf8556b..6af30aab31 100644 --- a/lib/facter/macaddress.rb +++ b/lib/facter/macaddress.rb @@ -10,7 +10,7 @@ require 'facter/util/macaddress' Facter.add(:macaddress) do - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS Scientific PSBM SLC Ascendos SuSE SLES Debian Gentoo Ubuntu OEL OracleLinux OVS GNU/kFreeBSD} + confine :kernel => %w{SunOS Linux GNU/kFreeBSD} setcode do ether = [] output = Facter::Util::Resolution.exec("/sbin/ifconfig -a") diff --git a/lib/facter/uniqueid.rb b/lib/facter/uniqueid.rb index c69a9e678d..df2ed87346 100644 --- a/lib/facter/uniqueid.rb +++ b/lib/facter/uniqueid.rb @@ -1,4 +1,4 @@ Facter.add(:uniqueid) do setcode 'hostid' - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS Scientific PSBM SLC Ascendos SuSE SLES Debian Ubuntu Gentoo AIX OEL OracleLinux OVS GNU/kFreeBSD} + confine :kernel => %w{SunOS Linux AIX GNU/kFreeBSD} end diff --git a/spec/unit/hardwareisa_spec.rb b/spec/unit/hardwareisa_spec.rb new file mode 100755 index 0000000000..53595c6bb9 --- /dev/null +++ b/spec/unit/hardwareisa_spec.rb @@ -0,0 +1,35 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') + +require 'facter' + +describe "Hardwareisa fact" do + it "should match uname -p on Linux" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter::Util::Resolution.stubs(:exec).with("uname -p").returns("Inky") + + Facter.fact(:hardwareisa).value.should == "Inky" + end + + it "should match uname -p on Darwin" do + Facter.fact(:kernel).stubs(:value).returns("Darwin") + Facter::Util::Resolution.stubs(:exec).with("uname -p").returns("Blinky") + + Facter.fact(:hardwareisa).value.should == "Blinky" + end + + it "should match uname -p on SunOS" do + Facter.fact(:kernel).stubs(:value).returns("SunOS") + Facter::Util::Resolution.stubs(:exec).with("uname -p").returns("Pinky") + + Facter.fact(:hardwareisa).value.should == "Pinky" + end + + it "should match uname -p on FreeBSD" do + Facter.fact(:kernel).stubs(:value).returns("FreeBSD") + Facter::Util::Resolution.stubs(:exec).with("uname -p").returns("Clyde") + + Facter.fact(:hardwareisa).value.should == "Clyde" + end +end diff --git a/spec/unit/lsbmajdistrelease_spec.rb b/spec/unit/lsbmajdistrelease_spec.rb new file mode 100755 index 0000000000..747bcad6bd --- /dev/null +++ b/spec/unit/lsbmajdistrelease_spec.rb @@ -0,0 +1,14 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') + +require 'facter' + +describe "LSB distribution major release fact" do + it "should be derived from lsb_release" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.stubs(:value).with(:lsbdistrelease).returns("10.10") + + Facter.fact(:lsbmajdistrelease).value.should == "10" + end +end \ No newline at end of file diff --git a/spec/unit/uniqueid_spec.rb b/spec/unit/uniqueid_spec.rb new file mode 100755 index 0000000000..ff1c8e7466 --- /dev/null +++ b/spec/unit/uniqueid_spec.rb @@ -0,0 +1,28 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') + +require 'facter' + +describe "Uniqueid fact" do + it "should match hostid on Solaris" do + Facter.fact(:kernel).stubs(:value).returns("SunOS") + Facter::Util::Resolution.stubs(:exec).with("hostid").returns("Larry") + + Facter.fact(:uniqueid).value.should == "Larry" + end + + it "should match hostid on Linux" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter::Util::Resolution.stubs(:exec).with("hostid").returns("Curly") + + Facter.fact(:uniqueid).value.should == "Curly" + end + + it "should match hostid on AIX" do + Facter.fact(:kernel).stubs(:value).returns("AIX") + Facter::Util::Resolution.stubs(:exec).with("hostid").returns("Moe") + + Facter.fact(:uniqueid).value.should == "Moe" + end +end From 6201820c8814065acb0da65e808c54d4be8e2fbb Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 12 Dec 2011 14:21:30 -0800 Subject: [PATCH 0710/3753] (maint) remove redundant arch detection The operatingsystem fact had logic to test for the existence of /etc/arch-release twice. Removed the dead branch of code. --- lib/facter/operatingsystem.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index 5961f96a7c..eed6808d22 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -44,8 +44,6 @@ else "OEL" end - elsif FileTest.exists?("/etc/arch-release") - "Arch" elsif FileTest.exists?("/etc/vmware-release") "VMWareESX" elsif FileTest.exists?("/etc/redhat-release") From cb4e29412edb8af63ddfb02a3df1937b5fa6a4a6 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 7 Jun 2011 12:30:37 -0700 Subject: [PATCH 0711/3753] (#7753) Added error checking when adding resolves Added exception handling to the fact class. When adding a resolution to a fact, if an exception was thrown outside of the setcode block, facter would crash. Added handling so that if an exception is thrown, facter logs the error and discards the fact. --- lib/facter/util/collection.rb | 5 ++--- lib/facter/util/fact.rb | 19 ++++++++++++------- spec/unit/util/collection_spec.rb | 10 ++++++++++ 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/lib/facter/util/collection.rb b/lib/facter/util/collection.rb index d165ff0252..29e6714c2e 100644 --- a/lib/facter/util/collection.rb +++ b/lib/facter/util/collection.rb @@ -31,9 +31,8 @@ def add(name, options = {}, &block) end end - if block - resolve = fact.add(&block) - # Set any resolve-appropriate options + if block_given? and resolve = fact.add(&block) + # If the resolve was actually added, set any resolve-appropriate options options.each do |opt, value| method = opt.to_s + "=" if resolve.respond_to?(method) diff --git a/lib/facter/util/fact.rb b/lib/facter/util/fact.rb index 070087b3be..3f18d7f33e 100644 --- a/lib/facter/util/fact.rb +++ b/lib/facter/util/fact.rb @@ -33,17 +33,22 @@ def initialize(name, options = {}) def add(&block) raise ArgumentError, "You must pass a block to Fact.add" unless block_given? - resolve = Facter::Util::Resolution.new(@name) + begin + resolve = Facter::Util::Resolution.new(@name) - resolve.instance_eval(&block) + resolve.instance_eval(&block) - @resolves << resolve + @resolves << resolve - # Immediately sort the resolutions, so that we always have - # a sorted list for looking up values. - @resolves.sort! { |a, b| b.weight <=> a.weight } + # Immediately sort the resolutions, so that we always have + # a sorted list for looking up values. + @resolves.sort! { |a, b| b.weight <=> a.weight } - return resolve + resolve + rescue => e + Facter.warn "Unable to add resolve for #{@name}: #{e}" + nil + end end # Flush any cached values. diff --git a/spec/unit/util/collection_spec.rb b/spec/unit/util/collection_spec.rb index c6767278e2..1a6537a6c7 100755 --- a/spec/unit/util/collection_spec.rb +++ b/spec/unit/util/collection_spec.rb @@ -93,6 +93,16 @@ @coll.add(:myname) {} end + + it "should discard resolutions that throw an exception when added" do + lambda { + @coll.add('yay') do + raise + setcode { 'yay' } + end + }.should_not raise_error + @coll.value('yay').should be_nil + end end end From 6d21f905794e96c51b38c1c147427ba61a57322c Mon Sep 17 00:00:00 2001 From: Barrie Bremner Date: Sat, 10 Dec 2011 22:44:52 +0000 Subject: [PATCH 0712/3753] Move Linux specific virtual tests to correct block. - Tests for Linux had strayed into Solaris block. Sited with other Xen tests. --- spec/unit/virtual_spec.rb | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index 8090358c76..47202de1e9 100644 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -112,6 +112,25 @@ Facter.fact(:virtual).value.should == "vmware" end + it "should be xen0 with xen dom0 files in /proc" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:operatingsystem).stubs(:value).returns("Linux") + Facter.fact(:hardwaremodel).stubs(:value).returns("i386") + Facter::Util::Virtual.expects(:xen?).returns(true) + FileTest.expects(:exists?).with("/proc/xen/xsd_kva").returns(true) + Facter.fact(:virtual).value.should == "xen0" + end + + it "should be xenu with xen domU files in /proc" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:operatingsystem).stubs(:value).returns("Linux") + Facter.fact(:hardwaremodel).stubs(:value).returns("i386") + Facter::Util::Virtual.expects(:xen?).returns(true) + FileTest.expects(:exists?).with("/proc/xen/xsd_kva").returns(false) + FileTest.expects(:exists?).with("/proc/xen/capabilities").returns(true) + Facter.fact(:virtual).value.should == "xenu" + end + it "should be xenhvm with Xen HVM vendor name from lspci" do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter::Util::Resolution.stubs(:exec).with('lspci').returns("00:03.0 Unassigned class [ff80]: XenSource, Inc. Xen Platform Device (rev 01)") @@ -184,25 +203,6 @@ Facter::Util::Resolution.stubs(:exec).with('prtdiag').returns("System Configuration: innotek GmbH VirtualBox") Facter.fact(:virtual).value.should == "virtualbox" end - - it "should be xen0 with xen dom0 files in /proc" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:operatingsystem).stubs(:value).returns("Linux") - Facter.fact(:hardwaremodel).stubs(:value).returns("i386") - Facter::Util::Virtual.expects(:xen?).returns(true) - FileTest.expects(:exists?).with("/proc/xen/xsd_kva").returns(true) - Facter.fact(:virtual).value.should == "xen0" - end - - it "should be xenu with xen domU files in /proc" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:operatingsystem).stubs(:value).returns("Linux") - Facter.fact(:hardwaremodel).stubs(:value).returns("i386") - Facter::Util::Virtual.expects(:xen?).returns(true) - FileTest.expects(:exists?).with("/proc/xen/xsd_kva").returns(false) - FileTest.expects(:exists?).with("/proc/xen/capabilities").returns(true) - Facter.fact(:virtual).value.should == "xenu" - end end end From 46339963f4a2342731ef6ad4be1ec17143c5e0ce Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 12 Dec 2011 13:35:10 -0800 Subject: [PATCH 0713/3753] (#9789) Extend coverage of operatingsystem specs Added more coverage of the operatingsystem fact, since it was a bit sparse and rather critical. --- spec/unit/operatingsystem_spec.rb | 153 ++++++++++++++++++------------ 1 file changed, 91 insertions(+), 62 deletions(-) diff --git a/spec/unit/operatingsystem_spec.rb b/spec/unit/operatingsystem_spec.rb index cba86ba91a..91cafb4dcc 100755 --- a/spec/unit/operatingsystem_spec.rb +++ b/spec/unit/operatingsystem_spec.rb @@ -6,12 +6,8 @@ describe "Operating System fact" do - before do - Facter.clear - end - - after do - Facter.clear + before :each do + FileTest.stubs(:exists?).returns false end it "should default to the kernel name" do @@ -32,61 +28,94 @@ Facter.fact(:operatingsystem).value.should == "ESXi" end - it "should identify Oracle VM as OVS" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.stubs(:value).with(:lsbdistid).returns(nil) - FileTest.stubs(:exists?).returns false - - FileTest.expects(:exists?).with("/etc/ovs-release").returns true - FileTest.expects(:exists?).with("/etc/enterprise-release").returns true - - Facter.fact(:operatingsystem).value.should == "OVS" - end - - it "should identify VMWare ESX" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.stubs(:value).with(:lsbdistid).returns(nil) - FileTest.stubs(:exists?).returns false - - FileTest.expects(:exists?).with("/etc/vmware-release").returns true - - Facter.fact(:operatingsystem).value.should == "VMWareESX" - end - - it "should identify Alpine Linux" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - - FileTest.stubs(:exists?).returns false - - FileTest.expects(:exists?).with("/etc/alpine-release").returns true - - Facter.fact(:operatingsystem).value.should == "Alpine" - end - - it "should identify Scientific Linux" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - FileTest.stubs(:exists?).returns false - - FileTest.expects(:exists?).with("/etc/redhat-release").returns true - File.expects(:read).with("/etc/redhat-release").returns("Scientific Linux SLC 5.7 (Boron)") - Facter.fact(:operatingsystem).value.should == "Scientific" - end - - it "should differentiate between Scientific Linux CERN and Scientific Linux" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - FileTest.stubs(:exists?).returns false - - FileTest.expects(:exists?).with("/etc/redhat-release").returns true - File.expects(:read).with("/etc/redhat-release").returns("Scientific Linux CERN SLC 5.7 (Boron)") - Facter.fact(:operatingsystem).value.should == "SLC" - end - - it "should identify Ascendos Linux" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - FileTest.stubs(:exists?).returns false - - FileTest.expects(:exists?).with("/etc/redhat-release").returns true - File.expects(:read).with("/etc/redhat-release").returns("Ascendos release 6.0 (Nameless)") - Facter.fact(:operatingsystem).value.should == "Ascendos" + describe "on Linux" do + before :each do + Facter.fact(:kernel).stubs(:value).returns("Linux") + end + + { + "Debian" => "/etc/debian_version", + "Gentoo" => "/etc/gentoo-release", + "Fedora" => "/etc/fedora-release", + "Mandriva" => "/etc/mandriva-release", + "Mandrake" => "/etc/mandrake-release", + "MeeGo" => "/etc/meego-release", + "Archlinux" => "/etc/arch-release", + "OracleLinux" => "/etc/oracle-release", + "Alpine" => "/etc/alpine-release", + "VMWareESX" => "/etc/vmware-release", + "Bluewhite64" => "/etc/bluewhite64-version", + "Slamd64" => "/etc/slamd64-version", + "Slackware" => "/etc/slackware-version", + }.each_pair do |distribution, releasefile| + it "should be #{distribution} if #{releasefile} exists" do + FileTest.expects(:exists?).with(releasefile).returns true + Facter.fact(:operatingsystem).value.should == distribution + end + end + + describe "depending on LSB release information" do + before :each do + Facter.collection.loader.load(:lsb) + end + + it "on Ubuntu should use the lsbdistid fact" do + FileUtils.stubs(:exists?).with("/etc/debian_version").returns true + + Facter.fact(:lsbdistid).expects(:value).returns("Ubuntu") + Facter.fact(:operatingsystem).value.should == "Ubuntu" + end + + it "on Amazon Linux should use the lsbdistdescription fact" do + Facter.fact(:lsbdistdescription).stubs(:value).returns "Amazon Linux" + Facter.fact(:operatingsystem).value.should == "Amazon" + end + end + + + # Check distributions that rely on the contents of /etc/redhat-release + { + "RedHat" => "Red Hat Enterprise Linux Server release 6.0 (Santiago)", + "CentOS" => "CentOS release 5.6 (Final)", + "Scientific" => "Scientific Linux release 6.0 (Carbon)", + "SLC" => "Scientific Linux CERN SLC release 5.7 (Boron)", + "Ascendos" => "Ascendos release 6.0 (Nameless)", + "CloudLinux" => "CloudLinux Server release 5.5", + }.each_pair do |operatingsystem, string| + it "should be #{operatingsystem} based on /etc/redhat-release contents #{string}" do + FileTest.expects(:exists?).with("/etc/redhat-release").returns true + File.expects(:read).with("/etc/redhat-release").returns string + + Facter.fact(:operatingsystem).value.should == operatingsystem + end + end + + describe "Oracle variant" do + it "should be OVS if /etc/ovs-release exists" do + Facter.stubs(:value).with(:lsbdistid) + FileTest.expects(:exists?).with("/etc/enterprise-release").returns true + FileTest.expects(:exists?).with("/etc/ovs-release").returns true + Facter.fact(:operatingsystem).value.should == "OVS" + end + + it "should be OEL if /etc/ovs-release doesn't exist" do + FileTest.expects(:exists?).with("/etc/enterprise-release").returns true + FileTest.expects(:exists?).with("/etc/ovs-release").returns false + Facter.fact(:operatingsystem).value.should == "OEL" + end + end + + it "should identify VMWare ESX" do + Facter.stubs(:value).with(:lsbdistid).returns(nil) + + FileTest.expects(:exists?).with("/etc/vmware-release").returns true + Facter.fact(:operatingsystem).value.should == "VMWareESX" + end + + it "should differentiate between Scientific Linux CERN and Scientific Linux" do + FileTest.expects(:exists?).with("/etc/redhat-release").returns true + File.expects(:read).with("/etc/redhat-release").returns("Scientific Linux CERN SLC 5.7 (Boron)") + Facter.fact(:operatingsystem).value.should == "SLC" + end end end From 5cd30ebf378b7060176220905ab833ec90ec2ed4 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Fri, 18 Nov 2011 16:21:24 -0800 Subject: [PATCH 0714/3753] (#8279) Join ec2 fact output with commas ec2 facts were being concatenated, which made array data harder to use. Switched to comma delimited data. Thanks to Hunter Haugen for this patch. --- lib/facter/ec2.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/facter/ec2.rb b/lib/facter/ec2.rb index c52f76b55c..ebfdcff3b2 100644 --- a/lib/facter/ec2.rb +++ b/lib/facter/ec2.rb @@ -23,9 +23,8 @@ def metadata(id = "") if key[-1..-1] != '/' value = open("http://169.254.169.254/2008-02-01/meta-data/#{key}").read. split("\n") - value = value.size>1 ? value : value.first symbol = "ec2_#{key.gsub(/\-|\//, '_')}".to_sym - Facter.add(symbol) { setcode { value } } + Facter.add(symbol) { setcode { value.join(',') } } else metadata(key) end From a1dba380c7bbc6147bdb9f059087385e6ee1ed12 Mon Sep 17 00:00:00 2001 From: Pieter Lexis Date: Fri, 9 Dec 2011 19:19:16 +0100 Subject: [PATCH 0715/3753] (#11196) Scan all arp entries for an ec2 mac This patch now scans all arp entries for the magic EC2 mac address. At times the mac entry was being returned out of order and since we only looked at the first entry there were cases where the test would fail. It also now removes the dependency on the arp fact which has become only important to the EC2 fact anyway. This was to avoid hacking the arp fact (which was obviously built for a different purpose) just to fix this issue. --- lib/facter/ec2.rb | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/facter/ec2.rb b/lib/facter/ec2.rb index ebfdcff3b2..173daeaa50 100644 --- a/lib/facter/ec2.rb +++ b/lib/facter/ec2.rb @@ -2,6 +2,7 @@ # Additional work from KurtBe # Additional work for Paul Nasrat # Additional work modelled on Ohai EC2 fact +# Remove depedency on arp fact by Pieter Lexis require 'open-uri' require 'timeout' @@ -39,12 +40,23 @@ def userdata() end end +# Is the macaddress a eucalyptus macaddress? def has_euca_mac? !!(Facter.value(:macaddress) =~ %r{^[dD]0:0[dD]:}) end +# Is there an entry in the arp table for fe:ff:ff:ff:ff:ff, +# this is a red flag for being on amazon def has_ec2_arp? - !!(Facter.value(:arp) == "fe:ff:ff:ff:ff:ff") + arp_table = Facter::Util::Resolution.exec('arp -an') + is_amazon_arp = false + if not arp_table.nil? + arp_table.each_line do |line| + is_amazon_arp = true if line.include?('fe:ff:ff:ff:ff:ff') + break + end + end + is_amazon_arp end if (has_euca_mac? || has_ec2_arp?) && can_connect? From 5c6322af2f35c3d4049feeea5969956eabb740c9 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Fri, 28 Oct 2011 17:18:47 -0700 Subject: [PATCH 0716/3753] (maint) Joined conditional statements for domain A pair of conditions for a single if statement were on two lines, which was visually unclear when scanning the file. Joined the conditions together to make it more readable. --- lib/facter/domain.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/facter/domain.rb b/lib/facter/domain.rb index 0a6caa02cf..ff5e988c43 100644 --- a/lib/facter/domain.rb +++ b/lib/facter/domain.rb @@ -23,12 +23,12 @@ # Get the domain from various sources; the order of these # steps is important - if name = Facter::Util::Resolution.exec('hostname') and - name =~ /.*?\.(.+$)/ + if name = Facter::Util::Resolution.exec('hostname') \ + and name =~ /.*?\.(.+$)/ $1 - elsif domain = Facter::Util::Resolution.exec('dnsdomainname') and - domain =~ /.+\..+/ + elsif domain = Facter::Util::Resolution.exec('dnsdomainname') \ + and domain =~ /.+\..+/ domain elsif FileTest.exists?("/etc/resolv.conf") From 9c224d373a4830209686c650eb9fe7257a07b814 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Fri, 16 Dec 2011 15:44:44 -0800 Subject: [PATCH 0717/3753] (#11436) Unify memorysize and memorytotal facts Two different names were given for the amount of physical memory in a given node. Switched to the name of 'memorysize' for the RAM and added a fallback fact 'memorytotal' that reverts to the memorysize. --- lib/facter/memory.rb | 19 +++++++++++++++---- spec/unit/memory_spec.rb | 11 +++++++++-- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb index 0b7731fa59..2bb52ad0bf 100644 --- a/lib/facter/memory.rb +++ b/lib/facter/memory.rb @@ -116,7 +116,7 @@ Facter::Memory.vmstat_find_free_memory() - Facter.add("MemoryTotal") do + Facter.add("memorysize") do confine :kernel => :openbsd memtotal = Facter::Util::Resolution.exec("sysctl hw.physmem | cut -d'=' -f2") setcode do @@ -153,7 +153,7 @@ Facter::Memory.vmstat_darwin_find_free_memory() - Facter.add("MemoryTotal") do + Facter.add("memorysize") do confine :kernel => :Darwin memtotal = Facter::Util::Resolution.exec("sysctl hw.memsize | cut -d':' -f2") setcode do @@ -220,7 +220,7 @@ end end - Facter.add("MemoryTotal") do + Facter.add("memorysize") do confine :kernel => :windows setcode do mem = 0 @@ -254,7 +254,7 @@ end end -Facter.add("MemoryTotal") do +Facter.add("memorysize") do confine :kernel => :dragonfly setcode do Facter::Memory.vmstat_find_free_memory() @@ -262,3 +262,14 @@ Facter::Memory.scale_number(memtotal.to_f,"") end end + +# http://projects.puppetlabs.com/issues/11436 +# +# Unifying naming for the amount of physical memory in a given host. +# This fact is DEPRECATED and will be removed in Facter 2.0 per +# http://projects.puppetlabs.com/issues/11466 +Facter.add("MemoryTotal") do + setcode do + Facter.value("memorysize") + end +end diff --git a/spec/unit/memory_spec.rb b/spec/unit/memory_spec.rb index 87a6f0cdd3..39bedf234c 100755 --- a/spec/unit/memory_spec.rb +++ b/spec/unit/memory_spec.rb @@ -78,7 +78,7 @@ end it "should return the current memorysize" do - Facter.fact(:memorytotal).value.should == "254.94 MB" + Facter.fact(:memorysize).value.should == "254.94 MB" end end @@ -118,7 +118,7 @@ end it "should return the current memorysize" do - Facter.fact(:memorytotal).value.should == "237.00 MB" + Facter.fact(:memorysize).value.should == "237.00 MB" end end @@ -147,4 +147,11 @@ Facter.fact(:MemoryTotal).value.should == '3.91 GB' end end + + it "should use the memorysize fact for the memorytotal fact" do + Facter.fact("memorysize").expects(:value).once.returns "yay" + Facter::Util::Resolution.expects(:exec).never + Facter::Memory.expects(:meminfo_number).never + Facter.fact("memorytotal").value.should == "yay" + end end From d141e7e1a5f1eb6c0707e8cda668e3ecf5089330 Mon Sep 17 00:00:00 2001 From: Ken Barber Date: Sun, 18 Dec 2011 19:45:17 +0000 Subject: [PATCH 0718/3753] (maint) Fix requirement for FileUtils as operatingsystem_spec needs it now Fix #9789 introduced the use of FileUtils however there was no require line. Since later versions of rspec (2.6+) it has been requiring it, but this breaks on older rspec revisions. This patch adds it to rspec_helper so we can avoid hitting this again on tests, even individual ones. --- spec/spec_helper.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 000023af86..98846383fe 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -14,6 +14,7 @@ def fixture_data(file) require 'mocha' require 'rspec' require 'facter' +require 'fileutils' # load any monkey-patches Dir["#{dir}/monkey_patches/*.rb"].map { |file| require file } From c1604c7b0587e148a6c08f37ba04b1c7de3eec16 Mon Sep 17 00:00:00 2001 From: Ken Barber Date: Tue, 13 Dec 2011 11:51:08 +0000 Subject: [PATCH 0719/3753] (#10309) Add puppetlabs_spec helper library based on Puppets own puppet_spec helpers These additions introduces a set of spec helpers based on Puppets puppet_spec helpers. They have been renamed to puppetlabs_spec to show they are really designed to be vendored across multiple Puppetlabs based projects going forward (such as puppet modules). We hope to reduce code using this patch in our specs and provide consistent handling of common tasks that are not handled in a standard way by rspec or other helpers. The libraries provides mechanisms for: * (files) Temporary file handling * (fixtures) Fixture path and content retrieval * (matchers) Common matchers * (verbose) Kernel verbosity handling In Puppet previously we were doing a lot of setup tasks in spec_helper.rb, instead we have tried to move a lot of these tasks to its own helper. The idea is to provide a single entry point 'puppetlabs_spec_helper.rb' which can be introduced in our own 'spec_helper.rb'. The puppetlabs_spec_helper.rb library provides the following setup tasks: * includes and extends fixtures & files library so its methods can be used in convenience without declaration. * Adds a task after each test to cleanup any files that were introduced using tmpdir and tmpfile so one doesn't have to delete these temporary files themself. --- spec/puppetlabs_spec/files.rb | 57 +++++++++++++++++++++ spec/puppetlabs_spec/fixtures.rb | 49 ++++++++++++++++++ spec/puppetlabs_spec/matchers.rb | 87 ++++++++++++++++++++++++++++++++ spec/puppetlabs_spec/verbose.rb | 9 ++++ spec/puppetlabs_spec_helper.rb | 26 ++++++++++ 5 files changed, 228 insertions(+) create mode 100755 spec/puppetlabs_spec/files.rb create mode 100755 spec/puppetlabs_spec/fixtures.rb create mode 100644 spec/puppetlabs_spec/matchers.rb create mode 100755 spec/puppetlabs_spec/verbose.rb create mode 100644 spec/puppetlabs_spec_helper.rb diff --git a/spec/puppetlabs_spec/files.rb b/spec/puppetlabs_spec/files.rb new file mode 100755 index 0000000000..b31a4dc352 --- /dev/null +++ b/spec/puppetlabs_spec/files.rb @@ -0,0 +1,57 @@ +require 'fileutils' +require 'tempfile' +require 'pathname' + +# A support module for testing files. +module PuppetlabsSpec::Files + # This code exists only to support tests that run as root, pretty much. + # Once they have finally been eliminated this can all go... --daniel 2011-04-08 + def self.in_tmp(path) + tempdir = Dir.tmpdir + + Pathname.new(path).ascend do |dir| + return true if File.identical?(tempdir, dir) + end + + false + end + + def self.cleanup + $global_tempfiles ||= [] + while path = $global_tempfiles.pop do + fail "Not deleting tmpfile #{path} outside regular tmpdir" unless in_tmp(path) + + begin + FileUtils.rm_r path, :secure => true + rescue Errno::ENOENT + # nothing to do + end + end + end + + def make_absolute(path) + path = File.expand_path(path) + path[0] = 'c' if Puppet.features.microsoft_windows? + path + end + + def tmpfile(name) + # Generate a temporary file, just for the name... + source = Tempfile.new(name) + path = source.path + source.close! + + # ...record it for cleanup, + $global_tempfiles ||= [] + $global_tempfiles << File.expand_path(path) + + # ...and bam. + path + end + + def tmpdir(name) + path = tmpfile(name) + FileUtils.mkdir_p(path) + path + end +end diff --git a/spec/puppetlabs_spec/fixtures.rb b/spec/puppetlabs_spec/fixtures.rb new file mode 100755 index 0000000000..9ce0a6b097 --- /dev/null +++ b/spec/puppetlabs_spec/fixtures.rb @@ -0,0 +1,49 @@ +# This module provides some helper methods to assist with fixtures. It's +# methods are designed to help when you have a conforming fixture layout so we +# get project consistency. +module PuppetlabsSpec::Fixtures + + # Returns the joined path of the global FIXTURE_DIR plus any path given to it + def fixtures(*rest) + File.join(PuppetlabsSpec::FIXTURE_DIR, *rest) + end + + # Returns the path to your relative fixture dir. So if your spec test is + # /spec/unit/facter/foo_spec.rb then your relative dir will be + # /spec/fixture/unit/facter/foo + def my_fixture_dir + callers = caller + while line = callers.shift do + next unless found = line.match(%r{/spec/(.*)_spec\.rb:}) + return fixtures(found[1]) + end + fail "sorry, I couldn't work out your path from the caller stack!" + end + + # Given a name, returns the full path of a file from your relative fixture + # dir as returned by my_fixture_dir. + def my_fixture(name) + file = File.join(my_fixture_dir, name) + unless File.readable? file then + fail "fixture '#{name}' for #{my_fixture_dir} is not readable" + end + return file + end + + # Return the contents of the file using read when given a name. Uses + # my_fixture to work out the relative path. + def my_fixture_read(name) + File.read(my_fixture(name)) + end + + # Provides a block mechanism for iterating across the files in your fixture + # area. + def my_fixtures(glob = '*', flags = 0) + files = Dir.glob(File.join(my_fixture_dir, glob), flags) + unless files.length > 0 then + fail "fixture '#{glob}' for #{my_fixture_dir} had no files!" + end + block_given? and files.each do |file| yield file end + files + end +end diff --git a/spec/puppetlabs_spec/matchers.rb b/spec/puppetlabs_spec/matchers.rb new file mode 100644 index 0000000000..77f580330c --- /dev/null +++ b/spec/puppetlabs_spec/matchers.rb @@ -0,0 +1,87 @@ +require 'stringio' + +######################################################################## +# Backward compatibility for Jenkins outdated environment. +module RSpec + module Matchers + module BlockAliases + alias_method :to, :should unless method_defined? :to + alias_method :to_not, :should_not unless method_defined? :to_not + alias_method :not_to, :should_not unless method_defined? :not_to + end + end +end + + +######################################################################## +# Custom matchers... +RSpec::Matchers.define :have_matching_element do |expected| + match do |actual| + actual.any? { |item| item =~ expected } + end +end + + +RSpec::Matchers.define :exit_with do |expected| + actual = nil + match do |block| + begin + block.call + rescue SystemExit => e + actual = e.status + end + actual and actual == expected + end + failure_message_for_should do |block| + "expected exit with code #{expected} but " + + (actual.nil? ? " exit was not called" : "we exited with #{actual} instead") + end + failure_message_for_should_not do |block| + "expected that exit would not be called with #{expected}" + end + description do + "expect exit with #{expected}" + end +end + + +RSpec::Matchers.define :have_printed do |expected| + match do |block| + $stderr = $stdout = StringIO.new + + begin + block.call + ensure + $stdout.rewind + @actual = $stdout.read + + $stdout = STDOUT + $stderr = STDERR + end + + if @actual then + case expected + when String + @actual.include? expected + when Regexp + expected.match @actual + else + raise ArgumentError, "No idea how to match a #{@actual.class.name}" + end + end + end + + failure_message_for_should do |actual| + if actual.nil? then + "expected #{expected.inspect}, but nothing was printed" + else + "expected #{expected.inspect} to be printed; got:\n#{actual}" + end + end + + description do + "expect #{expected.inspect} to be printed" + end + + diffable +end diff --git a/spec/puppetlabs_spec/verbose.rb b/spec/puppetlabs_spec/verbose.rb new file mode 100755 index 0000000000..d9834f2d79 --- /dev/null +++ b/spec/puppetlabs_spec/verbose.rb @@ -0,0 +1,9 @@ +# Support code for running stuff with warnings disabled. +module Kernel + def with_verbose_disabled + verbose, $VERBOSE = $VERBOSE, nil + result = yield + $VERBOSE = verbose + return result + end +end diff --git a/spec/puppetlabs_spec_helper.rb b/spec/puppetlabs_spec_helper.rb new file mode 100644 index 0000000000..8250d04a8e --- /dev/null +++ b/spec/puppetlabs_spec_helper.rb @@ -0,0 +1,26 @@ +# Define the main module namespace for use by the helper modules +module PuppetlabsSpec + # FIXTURE_DIR represents the standard locations of all fixture data. Normally + # this represents /spec/fixtures. This will be used by the fixtures + # library to find relative fixture data. + FIXTURE_DIR = File.join(dir = File.expand_path(File.dirname(__FILE__)), \ + "fixtures") unless defined?(FIXTURE_DIR) +end + +# Require all necessary helper libraries so they can be used later +require 'puppetlabs_spec/files' +require 'puppetlabs_spec/fixtures' +require 'puppetlabs_spec/matchers' +require 'puppetlabs_spec/verbose' + +RSpec.configure do |config| + # Include PuppetlabsSpec helpers so they can be called at convenience + config.extend PuppetlabsSpec::Files + config.extend PuppetlabsSpec::Fixtures + config.include PuppetlabsSpec::Fixtures + + # This will cleanup any files that were created with tmpdir or tmpfile + config.after :each do + PuppetlabsSpec::Files.cleanup + end +end From d6e8523ee8ee254ec8e815f49cf34d969d7913e4 Mon Sep 17 00:00:00 2001 From: Ken Barber Date: Wed, 14 Dec 2011 21:44:20 +0000 Subject: [PATCH 0720/3753] (#10309) Integrate new PuppetlabsSpec helpers into our existing facter spec code and general spec cleanup This is a general cleanup task to achieve some standardisation across the board for the way we use fixtures and the way we manage our shared helpers. This patches achieves a few goals: * Change all occurances of bespoke fixture 'getting' to use our fixture helpers * Introduce loading of the puppetlabs spec help into our existing 'spec_helper' * Change require loading across the board to just use require 'spec_helper' * Removed Adrien T's 'fixture_data' in favour of our new fixture helpers Also a few other cleanups were achieved: * All occurances of #! headers now use #!/usr/bin/env rspec so running them as a script works as expected (and its standardised) * Added more code comments to spec_helper.rb * Some whitespace cleanups where noticed. --- spec/integration/facter_spec.rb | 4 +- spec/spec_helper.rb | 20 +++---- spec/unit/architecture_spec.rb | 6 +-- spec/unit/domain_spec.rb | 4 +- spec/unit/facter_spec.rb | 4 +- spec/unit/hostname_spec.rb | 4 +- spec/unit/id_spec.rb | 4 +- spec/unit/interfaces_spec.rb | 6 +-- spec/unit/ipaddress6_spec.rb | 11 ++-- spec/unit/macaddress_spec.rb | 11 ++-- spec/unit/memory_spec.rb | 6 +-- spec/unit/operatingsystem_spec.rb | 6 +-- spec/unit/operatingsystemrelease_spec.rb | 6 +-- spec/unit/physicalprocessorcount_spec.rb | 2 +- spec/unit/processor_spec.rb | 67 +++++++++++------------- spec/unit/selinux_spec.rb | 6 +-- spec/unit/uptime_spec.rb | 6 +-- spec/unit/util/collection_spec.rb | 5 +- spec/unit/util/confine_spec.rb | 5 +- spec/unit/util/fact_spec.rb | 5 +- spec/unit/util/ip_spec.rb | 5 +- spec/unit/util/loader_spec.rb | 6 +-- spec/unit/util/macaddress_spec.rb | 9 ++-- spec/unit/util/macosx_spec.rb | 5 +- spec/unit/util/manufacturer_spec.rb | 7 +-- spec/unit/util/processor_spec.rb | 18 +++---- spec/unit/util/resolution_spec.rb | 5 +- spec/unit/util/uptime_spec.rb | 11 ++-- spec/unit/util/virtual_spec.rb | 5 +- spec/unit/util/vlans_spec.rb | 5 +- spec/unit/util/wmi_spec.rb | 5 +- spec/unit/util/xendomains_spec.rb | 5 +- spec/unit/virtual_spec.rb | 4 +- 33 files changed, 121 insertions(+), 157 deletions(-) mode change 100644 => 100755 spec/unit/domain_spec.rb mode change 100644 => 100755 spec/unit/hostname_spec.rb mode change 100644 => 100755 spec/unit/util/manufacturer_spec.rb mode change 100644 => 100755 spec/unit/util/virtual_spec.rb mode change 100644 => 100755 spec/unit/virtual_spec.rb diff --git a/spec/integration/facter_spec.rb b/spec/integration/facter_spec.rb index cce148fd6c..0c52895699 100755 --- a/spec/integration/facter_spec.rb +++ b/spec/integration/facter_spec.rb @@ -1,6 +1,6 @@ -#!/usr/bin/env ruby +#!/usr/bin/env rspec -require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') +require 'spec_helper' describe Facter do before do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 98846383fe..4e82c21de0 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,13 +1,8 @@ +# Add the projects lib directory to our load path so we can require libraries +# within it easily. dir = File.expand_path(File.dirname(__FILE__)) SPECDIR = dir - -def fixture_data(file) - File.read(File.join(SPECDIR, "fixtures", file)) -end - - -$LOAD_PATH.unshift("#{dir}/") $LOAD_PATH.unshift("#{dir}/../lib") require 'rubygems' @@ -15,24 +10,25 @@ def fixture_data(file) require 'rspec' require 'facter' require 'fileutils' - -# load any monkey-patches -Dir["#{dir}/monkey_patches/*.rb"].map { |file| require file } +require 'puppetlabs_spec_helper' RSpec.configure do |config| config.mock_with :mocha - # Ensure that we don't accidentally cache facts and environment - # between test cases. config.before :each do + # Ensure that we don't accidentally cache facts and environment + # between test cases. Facter::Util::Loader.any_instance.stubs(:load_all) Facter.clear Facter.clear_messages + + # Store any environment variables away to be restored later @old_env = {} ENV.each_key {|k| @old_env[k] = ENV[k]} end config.after :each do + # Restore environment variables after execution of each test @old_env.each_pair {|k, v| ENV[k] = v} to_remove = ENV.keys.reject {|key| @old_env.include? key } to_remove.each {|key| ENV.delete key } diff --git a/spec/unit/architecture_spec.rb b/spec/unit/architecture_spec.rb index 91e07e3dba..5dadd8485d 100755 --- a/spec/unit/architecture_spec.rb +++ b/spec/unit/architecture_spec.rb @@ -1,8 +1,6 @@ -#!/usr/bin/env ruby +#!/usr/bin/env rspec -require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') - -require 'facter' +require 'spec_helper' describe "Architecture fact" do diff --git a/spec/unit/domain_spec.rb b/spec/unit/domain_spec.rb old mode 100644 new mode 100755 index 54bf472b0d..6ef416cb4b --- a/spec/unit/domain_spec.rb +++ b/spec/unit/domain_spec.rb @@ -1,4 +1,6 @@ -require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') +#!/usr/bin/env rspec + +require 'spec_helper' describe "Domain name facts" do diff --git a/spec/unit/facter_spec.rb b/spec/unit/facter_spec.rb index 89cf673fa4..c6cedd3705 100755 --- a/spec/unit/facter_spec.rb +++ b/spec/unit/facter_spec.rb @@ -1,6 +1,6 @@ -#!/usr/bin/env ruby +#!/usr/bin/env rspec -require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') +require 'spec_helper' describe Facter do diff --git a/spec/unit/hostname_spec.rb b/spec/unit/hostname_spec.rb old mode 100644 new mode 100755 index ea7f2bb83b..f25c96dedb --- a/spec/unit/hostname_spec.rb +++ b/spec/unit/hostname_spec.rb @@ -1,4 +1,6 @@ -require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') +#!/usr/bin/env rspec + +require 'spec_helper' describe "Hostname facts" do diff --git a/spec/unit/id_spec.rb b/spec/unit/id_spec.rb index a3f0398e6b..c708caa684 100755 --- a/spec/unit/id_spec.rb +++ b/spec/unit/id_spec.rb @@ -1,6 +1,6 @@ -#!/usr/bin/env ruby +#!/usr/bin/env rspec -require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') +require 'spec_helper' describe "id fact" do diff --git a/spec/unit/interfaces_spec.rb b/spec/unit/interfaces_spec.rb index 6ceca5e9ce..54aadc5f88 100755 --- a/spec/unit/interfaces_spec.rb +++ b/spec/unit/interfaces_spec.rb @@ -1,8 +1,6 @@ -#!/usr/bin/env ruby +#!/usr/bin/env rspec -require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') - -require 'facter' +require 'spec_helper' require 'facter/util/ip' describe "Per Interface IP facts" do diff --git a/spec/unit/ipaddress6_spec.rb b/spec/unit/ipaddress6_spec.rb index 0a24029527..747f8f3e0b 100755 --- a/spec/unit/ipaddress6_spec.rb +++ b/spec/unit/ipaddress6_spec.rb @@ -1,16 +1,13 @@ -#!/usr/bin/env ruby +#!/usr/bin/env rspec -$basedir = File.expand_path(File.dirname(__FILE__) + '/..') -require File.join($basedir, 'spec_helper') - -require 'facter' +require 'spec_helper' def ifconfig_fixture(filename) - ifconfig = File.new(File.join($basedir, 'fixtures', 'ifconfig', filename)).read + File.read(fixtures('ifconfig', filename)) end def netsh_fixture(filename) - ifconfig = File.new(File.join($basedir, 'fixtures', 'netsh', filename)).read + File.read(fixtures('netsh', filename)) end describe "IPv6 address fact" do diff --git a/spec/unit/macaddress_spec.rb b/spec/unit/macaddress_spec.rb index b18670fa3f..244bfc9136 100755 --- a/spec/unit/macaddress_spec.rb +++ b/spec/unit/macaddress_spec.rb @@ -1,16 +1,13 @@ -#!/usr/bin/env ruby +#!/usr/bin/env rspec -$basedir = File.expand_path(File.dirname(__FILE__) + '/..') -require File.join($basedir, 'spec_helper') - -require 'facter' +require 'spec_helper' def ifconfig_fixture(filename) - ifconfig = File.new(File.join($basedir, 'fixtures', 'ifconfig', filename)).read + File.read(fixtures('ifconfig', filename)) end def netsh_fixture(filename) - ifconfig = File.new(File.join($basedir, 'fixtures', 'netsh', filename)).read + File.read(fixtures('netsh', filename)) end describe "macaddress fact" do diff --git a/spec/unit/memory_spec.rb b/spec/unit/memory_spec.rb index 87a6f0cdd3..0f721d33da 100755 --- a/spec/unit/memory_spec.rb +++ b/spec/unit/memory_spec.rb @@ -1,8 +1,6 @@ -#!/usr/bin/env ruby +#!/usr/bin/env rspec -require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') - -require 'facter' +require 'spec_helper' describe "Memory facts" do before do diff --git a/spec/unit/operatingsystem_spec.rb b/spec/unit/operatingsystem_spec.rb index 91cafb4dcc..ad754468fc 100755 --- a/spec/unit/operatingsystem_spec.rb +++ b/spec/unit/operatingsystem_spec.rb @@ -1,8 +1,6 @@ -#!/usr/bin/env ruby +#!/usr/bin/env rspec -require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') - -require 'facter' +require 'spec_helper' describe "Operating System fact" do diff --git a/spec/unit/operatingsystemrelease_spec.rb b/spec/unit/operatingsystemrelease_spec.rb index 02de5c3510..76f7bf145a 100755 --- a/spec/unit/operatingsystemrelease_spec.rb +++ b/spec/unit/operatingsystemrelease_spec.rb @@ -1,8 +1,6 @@ -#!/usr/bin/env ruby +#!/usr/bin/env rspec -require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') - -require 'facter' +require 'spec_helper' describe "Operating System Release fact" do diff --git a/spec/unit/physicalprocessorcount_spec.rb b/spec/unit/physicalprocessorcount_spec.rb index 38d960c53c..8fd471a459 100755 --- a/spec/unit/physicalprocessorcount_spec.rb +++ b/spec/unit/physicalprocessorcount_spec.rb @@ -1,6 +1,6 @@ #!/usr/bin/env rspec -require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') +require 'spec_helper' describe "Physical processor count facts" do diff --git a/spec/unit/processor_spec.rb b/spec/unit/processor_spec.rb index 95588c0500..d9822421a5 100755 --- a/spec/unit/processor_spec.rb +++ b/spec/unit/processor_spec.rb @@ -1,12 +1,9 @@ -#!/usr/bin/env ruby +#!/usr/bin/env rspec -$basedir = File.expand_path(File.dirname(__FILE__) + '/..') -require File.join($basedir, 'spec_helper') - -require 'facter' +require 'spec_helper' def cpuinfo_fixture(filename) - cpuinfo = File.open(File.join($basedir, 'fixtures', 'cpuinfo', filename)).readlines + File.open(fixtures('cpuinfo', filename)).readlines end describe "Processor facts" do @@ -74,18 +71,18 @@ def load(procs) end it "should detect the correct processor count on x86_64" do - fixture_data = File.read(File.expand_path(File.dirname(__FILE__) + '/../fixtures/processorcount/solaris-x86_64-kstat-cpu-info')) + fixture_data = File.read(fixtures('processorcount','solaris-x86_64-kstat-cpu-info')) Facter::Util::Resolution.expects(:exec).with("/usr/bin/kstat cpu_info").returns(fixture_data) Facter.fact(:processorcount).value.should == 8 end it "should detect the correct processor count on sparc" do - fixture_data = File.read(File.expand_path(File.dirname(__FILE__) + '/../fixtures/processorcount/solaris-sparc-kstat-cpu-info')) + fixture_data = File.read(fixtures('processorcount','solaris-sparc-kstat-cpu-info')) Facter::Util::Resolution.expects(:exec).with("/usr/bin/kstat cpu_info").returns(fixture_data) Facter.fact(:processorcount).value.should == 8 end end - + describe "on Unixes" do before :each do Facter.collection.loader.load(:processor) @@ -106,102 +103,102 @@ def load(procs) Facter.fact(:architecture).stubs(:value).returns("ppc64") File.stubs(:exists?).with("/proc/cpuinfo").returns(true) File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("ppc64")) - + Facter.fact(:processorcount).value.should == "2" end - + it "should be 2 in panda-armel fixture on Linux" do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter.fact(:architecture).stubs(:value).returns("arm") File.stubs(:exists?).with("/proc/cpuinfo").returns(true) File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("panda-armel")) - + Facter.fact(:processorcount).value.should == "2" end - + it "should be 1 in bbg3-armel fixture on Linux" do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter.fact(:architecture).stubs(:value).returns("arm") File.stubs(:exists?).with("/proc/cpuinfo").returns(true) File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("bbg3-armel")) - + Facter.fact(:processorcount).value.should == "1" end - + it "should be 1 in beaglexm-armel fixture on Linux" do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter.fact(:architecture).stubs(:value).returns("arm") File.stubs(:exists?).with("/proc/cpuinfo").returns(true) File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("beaglexm-armel")) - + Facter.fact(:processorcount).value.should == "1" end - + it "should be 1 in amd64solo fixture on Linux" do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter.fact(:architecture).stubs(:value).returns("amd64") File.stubs(:exists?).with("/proc/cpuinfo").returns(true) File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("amd64solo")) - + Facter.fact(:processorcount).value.should == "1" end - + it "should be 2 in amd64dual fixture on Linux" do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter.fact(:architecture).stubs(:value).returns("amd64") File.stubs(:exists?).with("/proc/cpuinfo").returns(true) File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("amd64dual")) - + Facter.fact(:processorcount).value.should == "2" end - + it "should be 3 in amd64tri fixture on Linux" do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter.fact(:architecture).stubs(:value).returns("amd64") File.stubs(:exists?).with("/proc/cpuinfo").returns(true) File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("amd64tri")) - + Facter.fact(:processorcount).value.should == "3" end - + it "should be 4 in amd64quad fixture on Linux" do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter.fact(:architecture).stubs(:value).returns("amd64") File.stubs(:exists?).with("/proc/cpuinfo").returns(true) File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("amd64quad")) - + Facter.fact(:processorcount).value.should == "4" end - + it "should be 2 on dual-processor Darwin box" do Facter.fact(:kernel).stubs(:value).returns("Darwin") Facter::Util::Resolution.stubs(:exec).with("sysctl -n hw.ncpu").returns('2') - + Facter.fact(:processorcount).value.should == "2" end - + it "should be 2 on dual-processor OpenBSD box" do Facter.fact(:kernel).stubs(:value).returns("OpenBSD") Facter::Util::Resolution.stubs(:exec).with("sysctl -n hw.ncpu").returns('2') - + Facter.fact(:processorcount).value.should == "2" end it "should be 2 on dual-processor DragonFly box" do Facter.fact(:kernel).stubs(:value).returns("DragonFly") Facter::Util::Resolution.stubs(:exec).with("sysctl -n hw.ncpu").returns('2') - + Facter.fact(:processorcount).value.should == "2" end - + it "should be 6 on six-processor AIX box" do Facter.fact(:kernel).stubs(:value).returns("AIX") Facter::Util::Resolution.stubs(:exec).with("lsdev -Cc processor").returns("proc0 Available 00-00 Processor\nproc2 Available 00-02 Processor\nproc4 Available 00-04 Processor\nproc6 Available 00-06 Processor\nproc8 Available 00-08 Processor\nproc10 Available 00-10 Processor") Facter::Util::Resolution.stubs(:exec).with("lsattr -El proc0 -a type").returns("type PowerPC_POWER3 Processor type False") - + Facter.fact(:processorcount).value.should == "6" end - + it "should be 2 via sysfs when cpu0 and cpu1 are present" do Facter.fact(:kernel).stubs(:value).returns("Linux") File.stubs(:exists?).with('/sys/devices/system/cpu').returns(true) @@ -212,10 +209,10 @@ def load(procs) /sys/devices/system/cpu/cpu0 /sys/devices/system/cpu/cpu1 }) - + Facter.fact(:processorcount).value.should == "2" end - + it "should be 16 via sysfs when cpu0 through cpu15 are present" do Facter.fact(:kernel).stubs(:value).returns("Linux") File.stubs(:exists?).with('/sys/devices/system/cpu').returns(true) @@ -240,7 +237,7 @@ def load(procs) /sys/devices/system/cpu/cpu14 /sys/devices/system/cpu/cpu15 }) - + Facter.fact(:processorcount).value.should == "16" end end diff --git a/spec/unit/selinux_spec.rb b/spec/unit/selinux_spec.rb index 25c75da067..05cfc1b0d3 100755 --- a/spec/unit/selinux_spec.rb +++ b/spec/unit/selinux_spec.rb @@ -1,8 +1,6 @@ -#!/usr/bin/env ruby +#!/usr/bin/env rspec -require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') - -require 'facter' +require 'spec_helper' describe "SELinux facts" do diff --git a/spec/unit/uptime_spec.rb b/spec/unit/uptime_spec.rb index bd695fac43..e1962d9250 100755 --- a/spec/unit/uptime_spec.rb +++ b/spec/unit/uptime_spec.rb @@ -1,8 +1,6 @@ -#!/usr/bin/env ruby +#!/usr/bin/env rspec -require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') - -require 'facter' +require 'spec_helper' require 'facter/util/uptime' describe "uptime facts:" do diff --git a/spec/unit/util/collection_spec.rb b/spec/unit/util/collection_spec.rb index 1a6537a6c7..5dce6bdcf6 100755 --- a/spec/unit/util/collection_spec.rb +++ b/spec/unit/util/collection_spec.rb @@ -1,7 +1,6 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') +#!/usr/bin/env rspec +require 'spec_helper' require 'facter/util/collection' describe Facter::Util::Collection do diff --git a/spec/unit/util/confine_spec.rb b/spec/unit/util/confine_spec.rb index 5e5706b1ad..806637dc8c 100755 --- a/spec/unit/util/confine_spec.rb +++ b/spec/unit/util/confine_spec.rb @@ -1,7 +1,6 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') +#!/usr/bin/env rspec +require 'spec_helper' require 'facter/util/confine' require 'facter/util/values' diff --git a/spec/unit/util/fact_spec.rb b/spec/unit/util/fact_spec.rb index 9b8c8d1c41..b3afd84b2a 100755 --- a/spec/unit/util/fact_spec.rb +++ b/spec/unit/util/fact_spec.rb @@ -1,7 +1,6 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') +#!/usr/bin/env rspec +require 'spec_helper' require 'facter/util/fact' describe Facter::Util::Fact do diff --git a/spec/unit/util/ip_spec.rb b/spec/unit/util/ip_spec.rb index 50035c5569..bfea1cdc64 100755 --- a/spec/unit/util/ip_spec.rb +++ b/spec/unit/util/ip_spec.rb @@ -1,7 +1,6 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') +#!/usr/bin/env rspec +require 'spec_helper' require 'facter/util/ip' describe Facter::Util::IP do diff --git a/spec/unit/util/loader_spec.rb b/spec/unit/util/loader_spec.rb index a3019d90d9..3a83f16365 100755 --- a/spec/unit/util/loader_spec.rb +++ b/spec/unit/util/loader_spec.rb @@ -1,10 +1,8 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') +#!/usr/bin/env rspec +require 'spec_helper' require 'facter/util/loader' - # loader subclass for making assertions about file/directory ordering class TestLoader < Facter::Util::Loader def loaded_files diff --git a/spec/unit/util/macaddress_spec.rb b/spec/unit/util/macaddress_spec.rb index 4a3517aafe..bf9d4e55f8 100755 --- a/spec/unit/util/macaddress_spec.rb +++ b/spec/unit/util/macaddress_spec.rb @@ -1,7 +1,6 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') +#!/usr/bin/env rspec +require 'spec_helper' require 'facter/util/macaddress' describe "standardized MAC address" do @@ -29,8 +28,8 @@ ] test_cases.each do |version, default_iface, macaddress, fallback_macaddress| - netstat_file = File.join(SPECDIR, "fixtures", "netstat", "darwin_#{version.tr('.', '_')}") - ifconfig_file_no_iface = File.join(SPECDIR, "fixtures", "ifconfig", "darwin_#{version.tr('.', '_')}") + netstat_file = fixtures("netstat", "darwin_#{version.tr('.', '_')}") + ifconfig_file_no_iface = fixtures("ifconfig", "darwin_#{version.tr('.', '_')}") ifconfig_file = "#{ifconfig_file_no_iface}_#{default_iface}" describe "version #{version}" do diff --git a/spec/unit/util/macosx_spec.rb b/spec/unit/util/macosx_spec.rb index dab4a9f688..dc7bb9bde8 100755 --- a/spec/unit/util/macosx_spec.rb +++ b/spec/unit/util/macosx_spec.rb @@ -1,7 +1,6 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') +#!/usr/bin/env rspec +require 'spec_helper' require 'facter/util/macosx' describe Facter::Util::Macosx do diff --git a/spec/unit/util/manufacturer_spec.rb b/spec/unit/util/manufacturer_spec.rb old mode 100644 new mode 100755 index e16215e29a..ba18f9fbbc --- a/spec/unit/util/manufacturer_spec.rb +++ b/spec/unit/util/manufacturer_spec.rb @@ -1,5 +1,6 @@ -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') +#!/usr/bin/env rspec +require 'spec_helper' require 'facter/util/manufacturer' describe Facter::Manufacturer do @@ -17,14 +18,14 @@ end it "should parse prtdiag output on a sunfire v120" do - Facter::Util::Resolution.stubs(:exec).returns(fixture_data(File.join("unit", "util", "manufacturer", "solaris_sunfire_v120_prtdiag"))) + Facter::Util::Resolution.stubs(:exec).returns(my_fixture_read("solaris_sunfire_v120_prtdiag")) Facter::Manufacturer.prtdiag_sparc_find_system_info() Facter.value(:manufacturer).should == "Sun Microsystems" Facter.value(:productname).should == "Sun Fire V120 (UltraSPARC-IIe 648MHz)" end it "should parse prtdiag output on a t5220" do - Facter::Util::Resolution.stubs(:exec).returns(fixture_data(File.join("unit", "util", "manufacturer", "solaris_t5220_prtdiag"))) + Facter::Util::Resolution.stubs(:exec).returns(my_fixture_read("solaris_t5220_prtdiag")) Facter::Manufacturer.prtdiag_sparc_find_system_info() Facter.value(:manufacturer).should == "Sun Microsystems" Facter.value(:productname).should == "SPARC Enterprise T5220" diff --git a/spec/unit/util/processor_spec.rb b/spec/unit/util/processor_spec.rb index 634b1b174a..9867f53c55 100755 --- a/spec/unit/util/processor_spec.rb +++ b/spec/unit/util/processor_spec.rb @@ -1,12 +1,10 @@ -#!/usr/bin/env ruby - -$basedir = File.expand_path(File.dirname(__FILE__) + '/../..') -require File.join($basedir, 'spec_helper') +#!/usr/bin/env rspec +require 'spec_helper' require 'facter/util/processor' def cpuinfo_fixture(filename) - cpuinfo = File.open(File.join($basedir, 'fixtures', 'cpuinfo', filename)).readlines + File.open(fixtures('cpuinfo', filename)).readlines end describe Facter::Util::Processor do @@ -15,7 +13,7 @@ def cpuinfo_fixture(filename) Facter.fact(:architecture).stubs(:value).returns("amd64") File.stubs(:exists?).with("/proc/cpuinfo").returns(true) File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("amd64solo")) - + Facter::Util::Processor.enum_cpuinfo[0].should == "Intel(R) Core(TM)2 Duo CPU P8700 @ 2.53GHz" end @@ -24,22 +22,22 @@ def cpuinfo_fixture(filename) Facter.fact(:architecture).stubs(:value).returns("amd64") File.stubs(:exists?).with("/proc/cpuinfo").returns(true) File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("amd64dual")) - + Facter::Util::Processor.enum_cpuinfo[0].should == "Intel(R) Core(TM)2 Duo CPU P8700 @ 2.53GHz" Facter::Util::Processor.enum_cpuinfo[1].should == "Intel(R) Core(TM)2 Duo CPU P8700 @ 2.53GHz" end - + it "should get the processor descriptions from the amd64tri fixture" do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter.fact(:architecture).stubs(:value).returns("amd64") File.stubs(:exists?).with("/proc/cpuinfo").returns(true) File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("amd64tri")) - + Facter::Util::Processor.enum_cpuinfo[0].should == "Intel(R) Core(TM)2 Duo CPU P8700 @ 2.53GHz" Facter::Util::Processor.enum_cpuinfo[1].should == "Intel(R) Core(TM)2 Duo CPU P8700 @ 2.53GHz" Facter::Util::Processor.enum_cpuinfo[2].should == "Intel(R) Core(TM)2 Duo CPU P8700 @ 2.53GHz" end - + it "should get the processor descriptions from the amd64quad fixture" do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter.fact(:architecture).stubs(:value).returns("amd64") diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index 6064b3e79c..3fbf19e15d 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -1,7 +1,6 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') +#!/usr/bin/env rspec +require 'spec_helper' require 'facter/util/resolution' describe Facter::Util::Resolution do diff --git a/spec/unit/util/uptime_spec.rb b/spec/unit/util/uptime_spec.rb index 8c30ff7678..0dd2d6dc4e 100755 --- a/spec/unit/util/uptime_spec.rb +++ b/spec/unit/util/uptime_spec.rb @@ -1,7 +1,6 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') +#!/usr/bin/env rspec +require 'spec_helper' require 'facter/util/uptime' describe Facter::Util::Uptime do @@ -9,7 +8,7 @@ describe ".get_uptime_seconds_unix", :unless => Facter.value(:operatingsystem) == 'windows' do describe "when /proc/uptime is available" do before do - uptime_file = File.join(SPECDIR, "fixtures", "uptime", "ubuntu_proc_uptime") + uptime_file = fixtures("uptime", "ubuntu_proc_uptime") Facter::Util::Uptime.stubs(:uptime_file).returns("\"#{uptime_file}\"") end @@ -46,7 +45,7 @@ end it "should use 'kstat -p unix:::boot_time'" do - kstat_output_file = File.join(SPECDIR, 'fixtures', 'uptime', 'kstat_boot_time') # unix:0:system_misc:boot_time 1236919980 + kstat_output_file = fixtures('uptime', 'kstat_boot_time') # unix:0:system_misc:boot_time 1236919980 Facter::Util::Uptime.stubs(:uptime_kstat_cmd).returns("cat \"#{kstat_output_file}\"") Time.stubs(:now).returns Time.at(1236923580) #one hour later Facter::Util::Uptime.get_uptime_seconds_unix.should == 60 * 60 @@ -58,7 +57,7 @@ end it "should use 'who -b'" do - who_b_output_file = File.join(SPECDIR, 'fixtures', 'uptime', 'who_b_boottime') # Aug 1 14:13 + who_b_output_file = fixtures('uptime', 'who_b_boottime') # Aug 1 14:13 Facter::Util::Uptime.stubs(:uptime_who_cmd).returns("cat \"#{who_b_output_file}\"") Time.stubs(:now).returns Time.parse("Aug 01 15:13") # one hour later Facter::Util::Uptime.get_uptime_seconds_unix.should == 60 * 60 diff --git a/spec/unit/util/virtual_spec.rb b/spec/unit/util/virtual_spec.rb old mode 100644 new mode 100755 index a4c51a9fdb..ceda83942b --- a/spec/unit/util/virtual_spec.rb +++ b/spec/unit/util/virtual_spec.rb @@ -1,5 +1,6 @@ -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') +#!/usr/bin/env rspec +require 'spec_helper' require 'facter/util/virtual' describe Facter::Util::Virtual do @@ -79,7 +80,7 @@ Facter::Util::Virtual.should_not be_vserver end - fixture_path = File.join(SPECDIR, 'fixtures', 'virtual', 'proc_self_status') + fixture_path = fixtures('virtual', 'proc_self_status') test_cases = [ [File.join(fixture_path, 'vserver_2_1', 'guest'), true, 'vserver 2.1 guest'], diff --git a/spec/unit/util/vlans_spec.rb b/spec/unit/util/vlans_spec.rb index 6f76b73637..8a14677edc 100755 --- a/spec/unit/util/vlans_spec.rb +++ b/spec/unit/util/vlans_spec.rb @@ -1,7 +1,6 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') +#!/usr/bin/env rspec +require 'spec_helper' require 'facter/util/vlans' describe Facter::Util::Vlans do diff --git a/spec/unit/util/wmi_spec.rb b/spec/unit/util/wmi_spec.rb index f5757fa5bd..0f03a18d19 100755 --- a/spec/unit/util/wmi_spec.rb +++ b/spec/unit/util/wmi_spec.rb @@ -1,7 +1,6 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') +#!/usr/bin/env rspec +require 'spec_helper' require 'facter/util/wmi' describe Facter::Util::WMI do diff --git a/spec/unit/util/xendomains_spec.rb b/spec/unit/util/xendomains_spec.rb index 5562d267a8..66722615f3 100755 --- a/spec/unit/util/xendomains_spec.rb +++ b/spec/unit/util/xendomains_spec.rb @@ -1,7 +1,6 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') +#!/usr/bin/env rspec +require 'spec_helper' require 'facter/util/xendomains' describe Facter::Util::Xendomains do diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb old mode 100644 new mode 100755 index 367ff2d17a..6ffa1ace5d --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') +#!/usr/bin/env rspec -require 'facter' +require 'spec_helper' require 'facter/util/virtual' require 'facter/util/macosx' From d50fc48688bb5404e38b2eeeaacc75b1bc863362 Mon Sep 17 00:00:00 2001 From: Ken Barber Date: Wed, 14 Dec 2011 23:50:51 +0000 Subject: [PATCH 0721/3753] (#10309) Move all fixture data in spec/unit/data to spec/fixtures This patch moves all fixture data that was previously spread across both spec/fixtures and spec/unit/data to just spec/fixtures. This replaces all bespoke loading methods that loaded data from spec/unit/data to use our new fixture helper methods. This moves us closer to having a convention for where we store fixture data that contributors can follow. --- .../unit/selinux}/selinux_sestatus | 0 .../unit/util/ip}/6.0-STABLE_FreeBSD_ifconfig | 0 .../unit/util/ip}/Mac_OS_X_10.5.5_ifconfig | 0 ...rwin_ifconfig_all_with_multiple_interfaces | 0 .../util/ip}/darwin_ifconfig_single_interface | 0 .../unit/util/ip}/debian_kfreebsd_ifconfig | 0 .../util/ip}/hpux_ifconfig_single_interface | 0 .../unit/util/ip}/hpux_netstat_all_interfaces | 0 .../linux_ifconfig_all_with_single_interface | 0 ...aris_ifconfig_all_with_multiple_interfaces | 0 .../ip}/solaris_ifconfig_single_interface | 0 .../util/ip}/windows_netsh_all_interfaces | 0 .../util/ip}/windows_netsh_single_interface | 0 .../util/ip}/windows_netsh_single_interface6 | 0 .../unit/util/manufacturer}/freebsd_dmidecode | 0 .../manufacturer}/linux_dmidecode_with_spaces | 0 .../util/manufacturer}/opensolaris_smbios | 0 .../{ => unit/util}/uptime/kstat_boot_time | 0 .../util}/uptime/sysctl_kern_boottime_darwin | 0 .../util}/uptime/sysctl_kern_boottime_openbsd | 0 .../{ => unit/util}/uptime/ubuntu_proc_uptime | 0 .../{ => unit/util}/uptime/who_b_boottime | 0 .../unit/util/vlans}/linux_vlan_config | 0 .../unit/util/xendomains}/xendomains | 0 spec/unit/selinux_spec.rb | 9 +-- spec/unit/util/ip_spec.rb | 77 ++++++------------- spec/unit/util/manufacturer_spec.rb | 10 +-- spec/unit/util/uptime_spec.rb | 10 +-- spec/unit/util/vlans_spec.rb | 3 +- spec/unit/util/xendomains_spec.rb | 3 +- 30 files changed, 39 insertions(+), 73 deletions(-) rename spec/{unit/data => fixtures/unit/selinux}/selinux_sestatus (100%) rename spec/{unit/data => fixtures/unit/util/ip}/6.0-STABLE_FreeBSD_ifconfig (100%) rename spec/{unit/data => fixtures/unit/util/ip}/Mac_OS_X_10.5.5_ifconfig (100%) rename spec/{unit/data => fixtures/unit/util/ip}/darwin_ifconfig_all_with_multiple_interfaces (100%) rename spec/{unit/data => fixtures/unit/util/ip}/darwin_ifconfig_single_interface (100%) rename spec/{unit/data => fixtures/unit/util/ip}/debian_kfreebsd_ifconfig (100%) rename spec/{unit/data => fixtures/unit/util/ip}/hpux_ifconfig_single_interface (100%) rename spec/{unit/data => fixtures/unit/util/ip}/hpux_netstat_all_interfaces (100%) rename spec/{unit/data => fixtures/unit/util/ip}/linux_ifconfig_all_with_single_interface (100%) rename spec/{unit/data => fixtures/unit/util/ip}/solaris_ifconfig_all_with_multiple_interfaces (100%) rename spec/{unit/data => fixtures/unit/util/ip}/solaris_ifconfig_single_interface (100%) rename spec/{unit/data => fixtures/unit/util/ip}/windows_netsh_all_interfaces (100%) rename spec/{unit/data => fixtures/unit/util/ip}/windows_netsh_single_interface (100%) rename spec/{unit/data => fixtures/unit/util/ip}/windows_netsh_single_interface6 (100%) rename spec/{unit/data => fixtures/unit/util/manufacturer}/freebsd_dmidecode (100%) rename spec/{unit/data => fixtures/unit/util/manufacturer}/linux_dmidecode_with_spaces (100%) rename spec/{unit/data => fixtures/unit/util/manufacturer}/opensolaris_smbios (100%) rename spec/fixtures/{ => unit/util}/uptime/kstat_boot_time (100%) rename spec/fixtures/{ => unit/util}/uptime/sysctl_kern_boottime_darwin (100%) rename spec/fixtures/{ => unit/util}/uptime/sysctl_kern_boottime_openbsd (100%) rename spec/fixtures/{ => unit/util}/uptime/ubuntu_proc_uptime (100%) rename spec/fixtures/{ => unit/util}/uptime/who_b_boottime (100%) rename spec/{unit/data => fixtures/unit/util/vlans}/linux_vlan_config (100%) rename spec/{unit/data => fixtures/unit/util/xendomains}/xendomains (100%) diff --git a/spec/unit/data/selinux_sestatus b/spec/fixtures/unit/selinux/selinux_sestatus similarity index 100% rename from spec/unit/data/selinux_sestatus rename to spec/fixtures/unit/selinux/selinux_sestatus diff --git a/spec/unit/data/6.0-STABLE_FreeBSD_ifconfig b/spec/fixtures/unit/util/ip/6.0-STABLE_FreeBSD_ifconfig similarity index 100% rename from spec/unit/data/6.0-STABLE_FreeBSD_ifconfig rename to spec/fixtures/unit/util/ip/6.0-STABLE_FreeBSD_ifconfig diff --git a/spec/unit/data/Mac_OS_X_10.5.5_ifconfig b/spec/fixtures/unit/util/ip/Mac_OS_X_10.5.5_ifconfig similarity index 100% rename from spec/unit/data/Mac_OS_X_10.5.5_ifconfig rename to spec/fixtures/unit/util/ip/Mac_OS_X_10.5.5_ifconfig diff --git a/spec/unit/data/darwin_ifconfig_all_with_multiple_interfaces b/spec/fixtures/unit/util/ip/darwin_ifconfig_all_with_multiple_interfaces similarity index 100% rename from spec/unit/data/darwin_ifconfig_all_with_multiple_interfaces rename to spec/fixtures/unit/util/ip/darwin_ifconfig_all_with_multiple_interfaces diff --git a/spec/unit/data/darwin_ifconfig_single_interface b/spec/fixtures/unit/util/ip/darwin_ifconfig_single_interface similarity index 100% rename from spec/unit/data/darwin_ifconfig_single_interface rename to spec/fixtures/unit/util/ip/darwin_ifconfig_single_interface diff --git a/spec/unit/data/debian_kfreebsd_ifconfig b/spec/fixtures/unit/util/ip/debian_kfreebsd_ifconfig similarity index 100% rename from spec/unit/data/debian_kfreebsd_ifconfig rename to spec/fixtures/unit/util/ip/debian_kfreebsd_ifconfig diff --git a/spec/unit/data/hpux_ifconfig_single_interface b/spec/fixtures/unit/util/ip/hpux_ifconfig_single_interface similarity index 100% rename from spec/unit/data/hpux_ifconfig_single_interface rename to spec/fixtures/unit/util/ip/hpux_ifconfig_single_interface diff --git a/spec/unit/data/hpux_netstat_all_interfaces b/spec/fixtures/unit/util/ip/hpux_netstat_all_interfaces similarity index 100% rename from spec/unit/data/hpux_netstat_all_interfaces rename to spec/fixtures/unit/util/ip/hpux_netstat_all_interfaces diff --git a/spec/unit/data/linux_ifconfig_all_with_single_interface b/spec/fixtures/unit/util/ip/linux_ifconfig_all_with_single_interface similarity index 100% rename from spec/unit/data/linux_ifconfig_all_with_single_interface rename to spec/fixtures/unit/util/ip/linux_ifconfig_all_with_single_interface diff --git a/spec/unit/data/solaris_ifconfig_all_with_multiple_interfaces b/spec/fixtures/unit/util/ip/solaris_ifconfig_all_with_multiple_interfaces similarity index 100% rename from spec/unit/data/solaris_ifconfig_all_with_multiple_interfaces rename to spec/fixtures/unit/util/ip/solaris_ifconfig_all_with_multiple_interfaces diff --git a/spec/unit/data/solaris_ifconfig_single_interface b/spec/fixtures/unit/util/ip/solaris_ifconfig_single_interface similarity index 100% rename from spec/unit/data/solaris_ifconfig_single_interface rename to spec/fixtures/unit/util/ip/solaris_ifconfig_single_interface diff --git a/spec/unit/data/windows_netsh_all_interfaces b/spec/fixtures/unit/util/ip/windows_netsh_all_interfaces similarity index 100% rename from spec/unit/data/windows_netsh_all_interfaces rename to spec/fixtures/unit/util/ip/windows_netsh_all_interfaces diff --git a/spec/unit/data/windows_netsh_single_interface b/spec/fixtures/unit/util/ip/windows_netsh_single_interface similarity index 100% rename from spec/unit/data/windows_netsh_single_interface rename to spec/fixtures/unit/util/ip/windows_netsh_single_interface diff --git a/spec/unit/data/windows_netsh_single_interface6 b/spec/fixtures/unit/util/ip/windows_netsh_single_interface6 similarity index 100% rename from spec/unit/data/windows_netsh_single_interface6 rename to spec/fixtures/unit/util/ip/windows_netsh_single_interface6 diff --git a/spec/unit/data/freebsd_dmidecode b/spec/fixtures/unit/util/manufacturer/freebsd_dmidecode similarity index 100% rename from spec/unit/data/freebsd_dmidecode rename to spec/fixtures/unit/util/manufacturer/freebsd_dmidecode diff --git a/spec/unit/data/linux_dmidecode_with_spaces b/spec/fixtures/unit/util/manufacturer/linux_dmidecode_with_spaces similarity index 100% rename from spec/unit/data/linux_dmidecode_with_spaces rename to spec/fixtures/unit/util/manufacturer/linux_dmidecode_with_spaces diff --git a/spec/unit/data/opensolaris_smbios b/spec/fixtures/unit/util/manufacturer/opensolaris_smbios similarity index 100% rename from spec/unit/data/opensolaris_smbios rename to spec/fixtures/unit/util/manufacturer/opensolaris_smbios diff --git a/spec/fixtures/uptime/kstat_boot_time b/spec/fixtures/unit/util/uptime/kstat_boot_time similarity index 100% rename from spec/fixtures/uptime/kstat_boot_time rename to spec/fixtures/unit/util/uptime/kstat_boot_time diff --git a/spec/fixtures/uptime/sysctl_kern_boottime_darwin b/spec/fixtures/unit/util/uptime/sysctl_kern_boottime_darwin similarity index 100% rename from spec/fixtures/uptime/sysctl_kern_boottime_darwin rename to spec/fixtures/unit/util/uptime/sysctl_kern_boottime_darwin diff --git a/spec/fixtures/uptime/sysctl_kern_boottime_openbsd b/spec/fixtures/unit/util/uptime/sysctl_kern_boottime_openbsd similarity index 100% rename from spec/fixtures/uptime/sysctl_kern_boottime_openbsd rename to spec/fixtures/unit/util/uptime/sysctl_kern_boottime_openbsd diff --git a/spec/fixtures/uptime/ubuntu_proc_uptime b/spec/fixtures/unit/util/uptime/ubuntu_proc_uptime similarity index 100% rename from spec/fixtures/uptime/ubuntu_proc_uptime rename to spec/fixtures/unit/util/uptime/ubuntu_proc_uptime diff --git a/spec/fixtures/uptime/who_b_boottime b/spec/fixtures/unit/util/uptime/who_b_boottime similarity index 100% rename from spec/fixtures/uptime/who_b_boottime rename to spec/fixtures/unit/util/uptime/who_b_boottime diff --git a/spec/unit/data/linux_vlan_config b/spec/fixtures/unit/util/vlans/linux_vlan_config similarity index 100% rename from spec/unit/data/linux_vlan_config rename to spec/fixtures/unit/util/vlans/linux_vlan_config diff --git a/spec/unit/data/xendomains b/spec/fixtures/unit/util/xendomains/xendomains similarity index 100% rename from spec/unit/data/xendomains rename to spec/fixtures/unit/util/xendomains/xendomains diff --git a/spec/unit/selinux_spec.rb b/spec/unit/selinux_spec.rb index 05cfc1b0d3..63313a70f3 100755 --- a/spec/unit/selinux_spec.rb +++ b/spec/unit/selinux_spec.rb @@ -48,8 +48,7 @@ it "should return the SELinux current mode" do Facter.fact(:selinux).stubs(:value).returns("true") - sample_output_file = File.dirname(__FILE__) + '/data/selinux_sestatus' - selinux_sestatus = File.read(sample_output_file) + selinux_sestatus = my_fixture_read("selinux_sestatus") Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/sestatus').returns(selinux_sestatus) @@ -59,8 +58,7 @@ it "should return the SELinux mode from the configuration file" do Facter.fact(:selinux).stubs(:value).returns("true") - sample_output_file = File.dirname(__FILE__) + '/data/selinux_sestatus' - selinux_sestatus = File.read(sample_output_file) + selinux_sestatus = my_fixture_read("selinux_sestatus") Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/sestatus').returns(selinux_sestatus) @@ -70,8 +68,7 @@ it "should return the SELinux configuration file policy" do Facter.fact(:selinux).stubs(:value).returns("true") - sample_output_file = File.dirname(__FILE__) + '/data/selinux_sestatus' - selinux_sestatus = File.read(sample_output_file) + selinux_sestatus = my_fixture_read("selinux_sestatus") Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/sestatus').returns(selinux_sestatus) diff --git a/spec/unit/util/ip_spec.rb b/spec/unit/util/ip_spec.rb index bfea1cdc64..0feedf9899 100755 --- a/spec/unit/util/ip_spec.rb +++ b/spec/unit/util/ip_spec.rb @@ -25,44 +25,38 @@ end it "should return a list with a single interface and the loopback interface on Linux with a single interface" do - sample_output_file = File.dirname(__FILE__) + '/../data/linux_ifconfig_all_with_single_interface' - linux_ifconfig = File.read(sample_output_file) + linux_ifconfig = my_fixture_read("linux_ifconfig_all_with_single_interface") Facter::Util::IP.stubs(:get_all_interface_output).returns(linux_ifconfig) Facter::Util::IP.get_interfaces().should == ["eth0", "lo"] end it "should return a list two interfaces on Darwin with two interfaces" do - sample_output_file = File.dirname(__FILE__) + '/../data/darwin_ifconfig_all_with_multiple_interfaces' - darwin_ifconfig = File.read(sample_output_file) + darwin_ifconfig = my_fixture_read("darwin_ifconfig_all_with_multiple_interfaces") Facter::Util::IP.stubs(:get_all_interface_output).returns(darwin_ifconfig) Facter::Util::IP.get_interfaces().should == ["lo0", "en0"] end it "should return a list two interfaces on Solaris with two interfaces multiply reporting" do - sample_output_file = File.dirname(__FILE__) + '/../data/solaris_ifconfig_all_with_multiple_interfaces' - solaris_ifconfig = File.read(sample_output_file) + solaris_ifconfig = my_fixture_read("solaris_ifconfig_all_with_multiple_interfaces") Facter::Util::IP.stubs(:get_all_interface_output).returns(solaris_ifconfig) Facter::Util::IP.get_interfaces().should == ["lo0", "e1000g0"] end it "should return a list three interfaces on HP-UX with three interfaces multiply reporting" do - sample_output_file = File.dirname(__FILE__) + '/../data/hpux_netstat_all_interfaces' - hpux_netstat = File.read(sample_output_file) + hpux_netstat = my_fixture_read("hpux_netstat_all_interfaces") Facter::Util::IP.stubs(:get_all_interface_output).returns(hpux_netstat) Facter::Util::IP.get_interfaces().should == ["lan1", "lan0", "lo0"] end it "should return a list of six interfaces on a GNU/kFreeBSD with six interfaces" do - sample_output_file = File.dirname(__FILE__) + '/../data/debian_kfreebsd_ifconfig' - kfreebsd_ifconfig = File.read(sample_output_file) + kfreebsd_ifconfig = my_fixture_read("debian_kfreebsd_ifconfig") Facter::Util::IP.stubs(:get_all_interface_output).returns(kfreebsd_ifconfig) Facter::Util::IP.get_interfaces().should == ["em0", "em1", "bge0", "bge1", "lo0", "vlan0"] end it "should return a list of only connected interfaces on Windows" do Facter.fact(:kernel).stubs(:value).returns("windows") - sample_output_file = File.dirname(__FILE__) + '/../data/windows_netsh_all_interfaces' - windows_netsh = File.read(sample_output_file) + windows_netsh = my_fixture_read("windows_netsh_all_interfaces") Facter::Util::IP.stubs(:get_all_interface_output).returns(windows_netsh) Facter::Util::IP.get_interfaces().should == ["Loopback Pseudo-Interface 1", "Local Area Connection", "Teredo Tunneling Pseudo-Interface"] end @@ -77,8 +71,7 @@ end it "should return ipaddress information for Solaris" do - sample_output_file = File.dirname(__FILE__) + "/../data/solaris_ifconfig_single_interface" - solaris_ifconfig_interface = File.read(sample_output_file) + solaris_ifconfig_interface = my_fixture_read("solaris_ifconfig_single_interface") Facter::Util::IP.expects(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) Facter.stubs(:value).with(:kernel).returns("SunOS") @@ -87,8 +80,7 @@ end it "should return netmask information for Solaris" do - sample_output_file = File.dirname(__FILE__) + "/../data/solaris_ifconfig_single_interface" - solaris_ifconfig_interface = File.read(sample_output_file) + solaris_ifconfig_interface = my_fixture_read("solaris_ifconfig_single_interface") Facter::Util::IP.expects(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) Facter.stubs(:value).with(:kernel).returns("SunOS") @@ -97,8 +89,7 @@ end it "should return calculated network information for Solaris" do - sample_output_file = File.dirname(__FILE__) + "/../data/solaris_ifconfig_single_interface" - solaris_ifconfig_interface = File.read(sample_output_file) + solaris_ifconfig_interface = my_fixture_read("solaris_ifconfig_single_interface") Facter::Util::IP.stubs(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) Facter.stubs(:value).with(:kernel).returns("SunOS") @@ -107,8 +98,7 @@ end it "should return ipaddress information for HP-UX" do - sample_output_file = File.dirname(__FILE__) + "/../data/hpux_ifconfig_single_interface" - hpux_ifconfig_interface = File.read(sample_output_file) + hpux_ifconfig_interface = my_fixture_read("hpux_ifconfig_single_interface") Facter::Util::IP.expects(:get_single_interface_output).with("lan0").returns(hpux_ifconfig_interface) Facter.stubs(:value).with(:kernel).returns("HP-UX") @@ -117,8 +107,7 @@ end it "should return macaddress information for HP-UX" do - sample_output_file = File.dirname(__FILE__) + "/../data/hpux_ifconfig_single_interface" - hpux_ifconfig_interface = File.read(sample_output_file) + hpux_ifconfig_interface = my_fixture_read("hpux_ifconfig_single_interface") Facter::Util::IP.expects(:get_single_interface_output).with("lan0").returns(hpux_ifconfig_interface) Facter.stubs(:value).with(:kernel).returns("HP-UX") @@ -127,8 +116,7 @@ end it "should return macaddress with leading zeros stripped off for GNU/kFreeBSD" do - sample_output_file = File.dirname(__FILE__) + "/../data/debian_kfreebsd_ifconfig" - kfreebsd_ifconfig = File.read(sample_output_file) + kfreebsd_ifconfig = my_fixture_read("debian_kfreebsd_ifconfig") Facter::Util::IP.expects(:get_single_interface_output).with("em0").returns(kfreebsd_ifconfig) Facter.stubs(:value).with(:kernel).returns("GNU/kFreeBSD") @@ -137,8 +125,7 @@ end it "should return netmask information for HP-UX" do - sample_output_file = File.dirname(__FILE__) + "/../data/hpux_ifconfig_single_interface" - hpux_ifconfig_interface = File.read(sample_output_file) + hpux_ifconfig_interface = my_fixture_read("hpux_ifconfig_single_interface") Facter::Util::IP.expects(:get_single_interface_output).with("lan0").returns(hpux_ifconfig_interface) Facter.stubs(:value).with(:kernel).returns("HP-UX") @@ -147,8 +134,7 @@ end it "should return calculated network information for HP-UX" do - sample_output_file = File.dirname(__FILE__) + "/../data/hpux_ifconfig_single_interface" - hpux_ifconfig_interface = File.read(sample_output_file) + hpux_ifconfig_interface = my_fixture_read("hpux_ifconfig_single_interface") Facter::Util::IP.stubs(:get_single_interface_output).with("lan0").returns(hpux_ifconfig_interface) Facter.stubs(:value).with(:kernel).returns("HP-UX") @@ -157,8 +143,7 @@ end it "should return interface information for FreeBSD supported via an alias" do - sample_output_file = File.dirname(__FILE__) + "/../data/6.0-STABLE_FreeBSD_ifconfig" - ifconfig_interface = File.read(sample_output_file) + ifconfig_interface = my_fixture_read("6.0-STABLE_FreeBSD_ifconfig") Facter::Util::IP.expects(:get_single_interface_output).with("fxp0").returns(ifconfig_interface) Facter.stubs(:value).with(:kernel).returns("FreeBSD") @@ -167,8 +152,7 @@ end it "should return macaddress information for OS X" do - sample_output_file = File.dirname(__FILE__) + "/../data/Mac_OS_X_10.5.5_ifconfig" - ifconfig_interface = File.read(sample_output_file) + ifconfig_interface = my_fixture_read("Mac_OS_X_10.5.5_ifconfig") Facter::Util::IP.expects(:get_single_interface_output).with("en1").returns(ifconfig_interface) Facter.stubs(:value).with(:kernel).returns("Darwin") @@ -177,8 +161,7 @@ end it "should return all interfaces correctly on OS X" do - sample_output_file = File.dirname(__FILE__) + "/../data/Mac_OS_X_10.5.5_ifconfig" - ifconfig_interface = File.read(sample_output_file) + ifconfig_interface = my_fixture_read("Mac_OS_X_10.5.5_ifconfig") Facter::Util::IP.expects(:get_all_interface_output).returns(ifconfig_interface) Facter.stubs(:value).with(:kernel).returns("Darwin") @@ -187,8 +170,7 @@ end it "should return a human readable netmask on Solaris" do - sample_output_file = File.dirname(__FILE__) + "/../data/solaris_ifconfig_single_interface" - solaris_ifconfig_interface = File.read(sample_output_file) + solaris_ifconfig_interface = my_fixture_read("solaris_ifconfig_single_interface") Facter::Util::IP.expects(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) Facter.stubs(:value).with(:kernel).returns("SunOS") @@ -197,8 +179,7 @@ end it "should return a human readable netmask on HP-UX" do - sample_output_file = File.dirname(__FILE__) + "/../data/hpux_ifconfig_single_interface" - hpux_ifconfig_interface = File.read(sample_output_file) + hpux_ifconfig_interface = my_fixture_read("hpux_ifconfig_single_interface") Facter::Util::IP.expects(:get_single_interface_output).with("lan0").returns(hpux_ifconfig_interface) Facter.stubs(:value).with(:kernel).returns("HP-UX") @@ -207,9 +188,7 @@ end it "should return a human readable netmask on Darwin" do - sample_output_file = File.dirname(__FILE__) + "/../data/darwin_ifconfig_single_interface" - - darwin_ifconfig_interface = File.read(sample_output_file) + darwin_ifconfig_interface = my_fixture_read("darwin_ifconfig_single_interface") Facter::Util::IP.expects(:get_single_interface_output).with("en1").returns(darwin_ifconfig_interface) Facter.stubs(:value).with(:kernel).returns("Darwin") @@ -218,9 +197,7 @@ end it "should return a human readable netmask on GNU/kFreeBSD" do - sample_output_file = File.dirname(__FILE__) + "/../data/debian_kfreebsd_ifconfig" - - kfreebsd_ifconfig = File.read(sample_output_file) + kfreebsd_ifconfig = my_fixture_read("debian_kfreebsd_ifconfig") Facter::Util::IP.expects(:get_single_interface_output).with("em1").returns(kfreebsd_ifconfig) Facter.stubs(:value).with(:kernel).returns("GNU/kFreeBSD") @@ -259,8 +236,7 @@ end it "should return ipaddress information" do - sample_output_file = File.dirname(__FILE__) + "/../data/windows_netsh_single_interface" - windows_netsh = File.read(sample_output_file) + windows_netsh = my_fixture_read("windows_netsh_single_interface") Facter::Util::IP.expects(:get_output_for_interface_and_label).with("Local Area Connection", "ipaddress").returns(windows_netsh) @@ -268,8 +244,7 @@ end it "should return a human readable netmask" do - sample_output_file = File.dirname(__FILE__) + "/../data/windows_netsh_single_interface" - windows_netsh = File.read(sample_output_file) + windows_netsh = my_fixture_read("windows_netsh_single_interface") Facter::Util::IP.expects(:get_output_for_interface_and_label).with("Local Area Connection", "netmask").returns(windows_netsh) @@ -277,8 +252,7 @@ end it "should return network information" do - sample_output_file = File.dirname(__FILE__) + "/../data/windows_netsh_single_interface" - windows_netsh = File.read(sample_output_file) + windows_netsh = my_fixture_read("windows_netsh_single_interface") Facter::Util::IP.stubs(:get_output_for_interface_and_label).with("Local Area Connection", "ipaddress").returns(windows_netsh) Facter::Util::IP.stubs(:get_output_for_interface_and_label).with("Local Area Connection", "netmask").returns(windows_netsh) @@ -287,8 +261,7 @@ end it "should return ipaddress6 information" do - sample_output_file = File.dirname(__FILE__) + "/../data/windows_netsh_single_interface6" - windows_netsh = File.read(sample_output_file) + windows_netsh = my_fixture_read("windows_netsh_single_interface6") Facter::Util::IP.expects(:get_output_for_interface_and_label).with("Teredo Tunneling Pseudo-Interface", "ipaddress6").returns(windows_netsh) diff --git a/spec/unit/util/manufacturer_spec.rb b/spec/unit/util/manufacturer_spec.rb index ba18f9fbbc..da9bcbc286 100755 --- a/spec/unit/util/manufacturer_spec.rb +++ b/spec/unit/util/manufacturer_spec.rb @@ -42,8 +42,7 @@ end it "should strip white space on dmi output with spaces" do - sample_output_file = File.dirname(__FILE__) + "/../data/linux_dmidecode_with_spaces" - dmidecode_output = File.new(sample_output_file).read() + dmidecode_output = my_fixture_read("linux_dmidecode_with_spaces") Facter::Manufacturer.expects(:get_dmi_table).returns(dmidecode_output) Facter.fact(:kernel).stubs(:value).returns("Linux") @@ -54,8 +53,7 @@ end it "should handle output from smbios when run under sunos" do - sample_output_file = File.dirname(__FILE__) + "/../data/opensolaris_smbios" - smbios_output = File.new(sample_output_file).read() + smbios_output = my_fixture_read("opensolaris_smbios") Facter::Manufacturer.expects(:get_dmi_table).returns(smbios_output) Facter.fact(:kernel).stubs(:value).returns("SunOS") @@ -127,8 +125,8 @@ def find_product_name(os) output_file = case os - when "FreeBSD" then File.dirname(__FILE__) + "/../data/freebsd_dmidecode" - when "SunOS" then File.dirname(__FILE__) + "/../data/opensolaris_smbios" + when "FreeBSD" then my_fixture("freebsd_dmidecode") + when "SunOS" then my_fixture("opensolaris_smbios") end output = File.new(output_file).read() diff --git a/spec/unit/util/uptime_spec.rb b/spec/unit/util/uptime_spec.rb index 0dd2d6dc4e..79bb56c6c7 100755 --- a/spec/unit/util/uptime_spec.rb +++ b/spec/unit/util/uptime_spec.rb @@ -8,7 +8,7 @@ describe ".get_uptime_seconds_unix", :unless => Facter.value(:operatingsystem) == 'windows' do describe "when /proc/uptime is available" do before do - uptime_file = fixtures("uptime", "ubuntu_proc_uptime") + uptime_file = my_fixture("ubuntu_proc_uptime") Facter::Util::Uptime.stubs(:uptime_file).returns("\"#{uptime_file}\"") end @@ -26,14 +26,14 @@ end it "should use 'sysctl -n kern.boottime' on OpenBSD" do - sysctl_output_file = File.join(SPECDIR, 'fixtures', 'uptime', 'sysctl_kern_boottime_openbsd') # Dec 09 21:11:46 +0000 2011 + sysctl_output_file = my_fixture('sysctl_kern_boottime_openbsd') # Dec 09 21:11:46 +0000 2011 Facter::Util::Uptime.stubs(:uptime_sysctl_cmd).returns("cat \"#{sysctl_output_file}\"") Time.stubs(:now).returns Time.parse("Dec 09 22:11:46 +0000 2011") # one hour later Facter::Util::Uptime.get_uptime_seconds_unix.should == 60 * 60 end it "should use 'sysctl -n kern.boottime' on Darwin, etc." do - sysctl_output_file = File.join(SPECDIR, 'fixtures', 'uptime', 'sysctl_kern_boottime_darwin') # Oct 30 21:52:27 +0000 2011 + sysctl_output_file = my_fixture('sysctl_kern_boottime_darwin') # Oct 30 21:52:27 +0000 2011 Facter::Util::Uptime.stubs(:uptime_sysctl_cmd).returns("cat \"#{sysctl_output_file}\"") Time.stubs(:now).returns Time.parse("Oct 30 22:52:27 +0000 2011") # one hour later Facter::Util::Uptime.get_uptime_seconds_unix.should == 60 * 60 @@ -45,7 +45,7 @@ end it "should use 'kstat -p unix:::boot_time'" do - kstat_output_file = fixtures('uptime', 'kstat_boot_time') # unix:0:system_misc:boot_time 1236919980 + kstat_output_file = my_fixture('kstat_boot_time') # unix:0:system_misc:boot_time 1236919980 Facter::Util::Uptime.stubs(:uptime_kstat_cmd).returns("cat \"#{kstat_output_file}\"") Time.stubs(:now).returns Time.at(1236923580) #one hour later Facter::Util::Uptime.get_uptime_seconds_unix.should == 60 * 60 @@ -57,7 +57,7 @@ end it "should use 'who -b'" do - who_b_output_file = fixtures('uptime', 'who_b_boottime') # Aug 1 14:13 + who_b_output_file = my_fixture('who_b_boottime') # Aug 1 14:13 Facter::Util::Uptime.stubs(:uptime_who_cmd).returns("cat \"#{who_b_output_file}\"") Time.stubs(:now).returns Time.parse("Aug 01 15:13") # one hour later Facter::Util::Uptime.get_uptime_seconds_unix.should == 60 * 60 diff --git a/spec/unit/util/vlans_spec.rb b/spec/unit/util/vlans_spec.rb index 8a14677edc..17ae7eda35 100755 --- a/spec/unit/util/vlans_spec.rb +++ b/spec/unit/util/vlans_spec.rb @@ -5,8 +5,7 @@ describe Facter::Util::Vlans do it "should return a list of vlans on Linux" do - sample_output_file = File.dirname(__FILE__) + '/../data/linux_vlan_config' - linux_vlanconfig = File.new(sample_output_file).read(); + linux_vlanconfig = my_fixture_read("linux_vlan_config") Facter::Util::Vlans.stubs(:get_vlan_config).returns(linux_vlanconfig) Facter::Util::Vlans.get_vlans().should == %{400,300,200,100} end diff --git a/spec/unit/util/xendomains_spec.rb b/spec/unit/util/xendomains_spec.rb index 66722615f3..ff0481fae6 100755 --- a/spec/unit/util/xendomains_spec.rb +++ b/spec/unit/util/xendomains_spec.rb @@ -6,8 +6,7 @@ describe Facter::Util::Xendomains do describe ".get_domains" do it "should return a list of running Xen Domains on Xen0" do - sample_output_file = File.dirname(__FILE__) + '/../data/xendomains' - xen0_domains = File.read(sample_output_file) + xen0_domains = my_fixture_read("xendomains") Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/xm list 2>/dev/null').returns(xen0_domains) Facter::Util::Xendomains.get_domains.should == %{web01,mailserver} end From a99d87c2dab4a7ee158fc977b2ea0ff89d1db9b1 Mon Sep 17 00:00:00 2001 From: Ken Barber Date: Mon, 19 Dec 2011 23:33:35 +0000 Subject: [PATCH 0722/3753] (#10309) Rename tmpfile to tmpfilename to make function clear Renamed tmpfile to tmpfilename to make it clear that all this methods functionality achieves is to create a temporary file name which you then need to create yourself. --- spec/puppetlabs_spec/files.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/puppetlabs_spec/files.rb b/spec/puppetlabs_spec/files.rb index b31a4dc352..d64cb3fb57 100755 --- a/spec/puppetlabs_spec/files.rb +++ b/spec/puppetlabs_spec/files.rb @@ -35,7 +35,7 @@ def make_absolute(path) path end - def tmpfile(name) + def tmpfilename(name) # Generate a temporary file, just for the name... source = Tempfile.new(name) path = source.path @@ -50,7 +50,7 @@ def tmpfile(name) end def tmpdir(name) - path = tmpfile(name) + path = tmpfilename(name) FileUtils.mkdir_p(path) path end From c473e3f524ca87c61264b35abc2f8319082efd67 Mon Sep 17 00:00:00 2001 From: Ken Barber Date: Mon, 19 Dec 2011 23:53:20 +0000 Subject: [PATCH 0723/3753] (#10309) Remove the with_verbose_disabled method We can't see any direct need for this at the moment, and since Puppet doesn't use it lets shelve it for now. --- spec/puppetlabs_spec/verbose.rb | 9 --------- spec/puppetlabs_spec_helper.rb | 1 - 2 files changed, 10 deletions(-) delete mode 100755 spec/puppetlabs_spec/verbose.rb diff --git a/spec/puppetlabs_spec/verbose.rb b/spec/puppetlabs_spec/verbose.rb deleted file mode 100755 index d9834f2d79..0000000000 --- a/spec/puppetlabs_spec/verbose.rb +++ /dev/null @@ -1,9 +0,0 @@ -# Support code for running stuff with warnings disabled. -module Kernel - def with_verbose_disabled - verbose, $VERBOSE = $VERBOSE, nil - result = yield - $VERBOSE = verbose - return result - end -end diff --git a/spec/puppetlabs_spec_helper.rb b/spec/puppetlabs_spec_helper.rb index 8250d04a8e..ba2ae7ac20 100644 --- a/spec/puppetlabs_spec_helper.rb +++ b/spec/puppetlabs_spec_helper.rb @@ -11,7 +11,6 @@ module PuppetlabsSpec require 'puppetlabs_spec/files' require 'puppetlabs_spec/fixtures' require 'puppetlabs_spec/matchers' -require 'puppetlabs_spec/verbose' RSpec.configure do |config| # Include PuppetlabsSpec helpers so they can be called at convenience From 64fa8ea868f90a8d3c9bbfe7fc20e66a4c610cf5 Mon Sep 17 00:00:00 2001 From: Ken Barber Date: Wed, 21 Dec 2011 22:20:54 +0000 Subject: [PATCH 0724/3753] (maint) Fix requires for newer rspec revisions so we don't break build In newer revisions of rspec the older methodology for loading rspec: require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') Will throw an error when mixed with the new method: require 'spec_helper' This patch fixes the 4 spec files that were missing from 1.6.x when the original patch changed this (#11436). --- spec/unit/blockdevices_spec.rb | 5 ++--- spec/unit/hardwareisa_spec.rb | 3 +-- spec/unit/lsbmajdistrelease_spec.rb | 5 ++--- spec/unit/uniqueid_spec.rb | 3 +-- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/spec/unit/blockdevices_spec.rb b/spec/unit/blockdevices_spec.rb index ef5d2335d1..7fdcc9e26e 100644 --- a/spec/unit/blockdevices_spec.rb +++ b/spec/unit/blockdevices_spec.rb @@ -1,7 +1,6 @@ #!/usr/bin/env ruby -require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') - +require 'spec_helper' require 'facter' describe "Block device facts" do @@ -105,4 +104,4 @@ end end -end \ No newline at end of file +end diff --git a/spec/unit/hardwareisa_spec.rb b/spec/unit/hardwareisa_spec.rb index 53595c6bb9..f76b319ed5 100755 --- a/spec/unit/hardwareisa_spec.rb +++ b/spec/unit/hardwareisa_spec.rb @@ -1,7 +1,6 @@ #!/usr/bin/env ruby -require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') - +require 'spec_helper' require 'facter' describe "Hardwareisa fact" do diff --git a/spec/unit/lsbmajdistrelease_spec.rb b/spec/unit/lsbmajdistrelease_spec.rb index 747bcad6bd..4397c02f8f 100755 --- a/spec/unit/lsbmajdistrelease_spec.rb +++ b/spec/unit/lsbmajdistrelease_spec.rb @@ -1,7 +1,6 @@ #!/usr/bin/env ruby -require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') - +require 'spec_helper' require 'facter' describe "LSB distribution major release fact" do @@ -11,4 +10,4 @@ Facter.fact(:lsbmajdistrelease).value.should == "10" end -end \ No newline at end of file +end diff --git a/spec/unit/uniqueid_spec.rb b/spec/unit/uniqueid_spec.rb index ff1c8e7466..e9870d39fd 100755 --- a/spec/unit/uniqueid_spec.rb +++ b/spec/unit/uniqueid_spec.rb @@ -1,7 +1,6 @@ #!/usr/bin/env ruby -require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') - +require 'spec_helper' require 'facter' describe "Uniqueid fact" do From b0b5282eeed568ff98fa87a41a41f6affca99471 Mon Sep 17 00:00:00 2001 From: Michael Kincaid Date: Tue, 27 Sep 2011 14:29:49 -0700 Subject: [PATCH 0725/3753] (#9708) Confine facts by kernel not operating system and remove confine for hardwareisa This will allow the following facts to continue working even when new variants of Linux, BSD, etc are supported in the future. * lsbmajdistrelease * macaddress * uniqueid For hardwareisa the confine was removed entirely. On any system that returns "uname -p", the value should be this fact. On systems that don't have "uname -p", the resolution will not be used. Thus, it shouldn't be necessary to confine the resolution by kernel. Previously we were constantly requiring patches to be added whenever a new Linux distribution was added. These changes should reduce this need. The fact 'operatingsystem', 'osfamily' and 'operatingsystemrelease' will still require patches however. New spec tests were added where they were missing. (cherry picked from commit 2eb4ede4eb7e5bc66fc11cbef0c9fa80c245c662) --- lib/facter/hardwareisa.rb | 1 - lib/facter/lsbmajdistrelease.rb | 2 +- lib/facter/macaddress.rb | 2 +- lib/facter/uniqueid.rb | 2 +- spec/unit/hardwareisa_spec.rb | 35 +++++++++++++++++++++++++++++ spec/unit/lsbmajdistrelease_spec.rb | 14 ++++++++++++ spec/unit/uniqueid_spec.rb | 28 +++++++++++++++++++++++ 7 files changed, 80 insertions(+), 4 deletions(-) create mode 100755 spec/unit/hardwareisa_spec.rb create mode 100755 spec/unit/lsbmajdistrelease_spec.rb create mode 100755 spec/unit/uniqueid_spec.rb diff --git a/lib/facter/hardwareisa.rb b/lib/facter/hardwareisa.rb index ecb8573457..ecd5a19de3 100644 --- a/lib/facter/hardwareisa.rb +++ b/lib/facter/hardwareisa.rb @@ -12,5 +12,4 @@ Facter.add(:hardwareisa) do setcode 'uname -p' - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS Scientific PSBM SLC Ascendos SuSE SLES Debian Ubuntu Gentoo FreeBSD OpenBSD NetBSD DragonFly OEL OracleLinux OVS GNU/kFreeBSD} end diff --git a/lib/facter/lsbmajdistrelease.rb b/lib/facter/lsbmajdistrelease.rb index 69eb5da539..a40441a038 100644 --- a/lib/facter/lsbmajdistrelease.rb +++ b/lib/facter/lsbmajdistrelease.rb @@ -15,7 +15,7 @@ require 'facter' Facter.add("lsbmajdistrelease") do - confine :operatingsystem => %w{Linux Fedora RedHat CentOS Scientific PSBM SLC Ascendos SuSE SLES Debian Ubuntu Gentoo OEL OracleLinux OVS GNU/kFreeBSD} + confine :kernel => %w{Linux GNU/kFreeBSD} setcode do if /(\d*)\./i =~ Facter.value(:lsbdistrelease) result=$1 diff --git a/lib/facter/macaddress.rb b/lib/facter/macaddress.rb index 32daf8556b..6af30aab31 100644 --- a/lib/facter/macaddress.rb +++ b/lib/facter/macaddress.rb @@ -10,7 +10,7 @@ require 'facter/util/macaddress' Facter.add(:macaddress) do - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS Scientific PSBM SLC Ascendos SuSE SLES Debian Gentoo Ubuntu OEL OracleLinux OVS GNU/kFreeBSD} + confine :kernel => %w{SunOS Linux GNU/kFreeBSD} setcode do ether = [] output = Facter::Util::Resolution.exec("/sbin/ifconfig -a") diff --git a/lib/facter/uniqueid.rb b/lib/facter/uniqueid.rb index c69a9e678d..df2ed87346 100644 --- a/lib/facter/uniqueid.rb +++ b/lib/facter/uniqueid.rb @@ -1,4 +1,4 @@ Facter.add(:uniqueid) do setcode 'hostid' - confine :operatingsystem => %w{Solaris Linux Fedora RedHat CentOS Scientific PSBM SLC Ascendos SuSE SLES Debian Ubuntu Gentoo AIX OEL OracleLinux OVS GNU/kFreeBSD} + confine :kernel => %w{SunOS Linux AIX GNU/kFreeBSD} end diff --git a/spec/unit/hardwareisa_spec.rb b/spec/unit/hardwareisa_spec.rb new file mode 100755 index 0000000000..53595c6bb9 --- /dev/null +++ b/spec/unit/hardwareisa_spec.rb @@ -0,0 +1,35 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') + +require 'facter' + +describe "Hardwareisa fact" do + it "should match uname -p on Linux" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter::Util::Resolution.stubs(:exec).with("uname -p").returns("Inky") + + Facter.fact(:hardwareisa).value.should == "Inky" + end + + it "should match uname -p on Darwin" do + Facter.fact(:kernel).stubs(:value).returns("Darwin") + Facter::Util::Resolution.stubs(:exec).with("uname -p").returns("Blinky") + + Facter.fact(:hardwareisa).value.should == "Blinky" + end + + it "should match uname -p on SunOS" do + Facter.fact(:kernel).stubs(:value).returns("SunOS") + Facter::Util::Resolution.stubs(:exec).with("uname -p").returns("Pinky") + + Facter.fact(:hardwareisa).value.should == "Pinky" + end + + it "should match uname -p on FreeBSD" do + Facter.fact(:kernel).stubs(:value).returns("FreeBSD") + Facter::Util::Resolution.stubs(:exec).with("uname -p").returns("Clyde") + + Facter.fact(:hardwareisa).value.should == "Clyde" + end +end diff --git a/spec/unit/lsbmajdistrelease_spec.rb b/spec/unit/lsbmajdistrelease_spec.rb new file mode 100755 index 0000000000..747bcad6bd --- /dev/null +++ b/spec/unit/lsbmajdistrelease_spec.rb @@ -0,0 +1,14 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') + +require 'facter' + +describe "LSB distribution major release fact" do + it "should be derived from lsb_release" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.stubs(:value).with(:lsbdistrelease).returns("10.10") + + Facter.fact(:lsbmajdistrelease).value.should == "10" + end +end \ No newline at end of file diff --git a/spec/unit/uniqueid_spec.rb b/spec/unit/uniqueid_spec.rb new file mode 100755 index 0000000000..ff1c8e7466 --- /dev/null +++ b/spec/unit/uniqueid_spec.rb @@ -0,0 +1,28 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') + +require 'facter' + +describe "Uniqueid fact" do + it "should match hostid on Solaris" do + Facter.fact(:kernel).stubs(:value).returns("SunOS") + Facter::Util::Resolution.stubs(:exec).with("hostid").returns("Larry") + + Facter.fact(:uniqueid).value.should == "Larry" + end + + it "should match hostid on Linux" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter::Util::Resolution.stubs(:exec).with("hostid").returns("Curly") + + Facter.fact(:uniqueid).value.should == "Curly" + end + + it "should match hostid on AIX" do + Facter.fact(:kernel).stubs(:value).returns("AIX") + Facter::Util::Resolution.stubs(:exec).with("hostid").returns("Moe") + + Facter.fact(:uniqueid).value.should == "Moe" + end +end From 3ccac87295af86237be6712d78e1190c83ba9bb6 Mon Sep 17 00:00:00 2001 From: Ken Barber Date: Wed, 21 Dec 2011 23:33:08 +0000 Subject: [PATCH 0726/3753] (#9708) Amend requires in specs to use simple requires In a previous change to 1.6.x we changed from using a require on a path to just a shorter string. This amends the cherry-picked patch from master to use this new method and make rspec work again in more modern rspec revisions. --- spec/unit/hardwareisa_spec.rb | 3 +-- spec/unit/lsbmajdistrelease_spec.rb | 5 ++--- spec/unit/uniqueid_spec.rb | 3 +-- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/spec/unit/hardwareisa_spec.rb b/spec/unit/hardwareisa_spec.rb index 53595c6bb9..f76b319ed5 100755 --- a/spec/unit/hardwareisa_spec.rb +++ b/spec/unit/hardwareisa_spec.rb @@ -1,7 +1,6 @@ #!/usr/bin/env ruby -require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') - +require 'spec_helper' require 'facter' describe "Hardwareisa fact" do diff --git a/spec/unit/lsbmajdistrelease_spec.rb b/spec/unit/lsbmajdistrelease_spec.rb index 747bcad6bd..4397c02f8f 100755 --- a/spec/unit/lsbmajdistrelease_spec.rb +++ b/spec/unit/lsbmajdistrelease_spec.rb @@ -1,7 +1,6 @@ #!/usr/bin/env ruby -require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') - +require 'spec_helper' require 'facter' describe "LSB distribution major release fact" do @@ -11,4 +10,4 @@ Facter.fact(:lsbmajdistrelease).value.should == "10" end -end \ No newline at end of file +end diff --git a/spec/unit/uniqueid_spec.rb b/spec/unit/uniqueid_spec.rb index ff1c8e7466..e9870d39fd 100755 --- a/spec/unit/uniqueid_spec.rb +++ b/spec/unit/uniqueid_spec.rb @@ -1,7 +1,6 @@ #!/usr/bin/env ruby -require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') - +require 'spec_helper' require 'facter' describe "Uniqueid fact" do From 9401b78bc6d14054866174283f9edb6c25ba11c8 Mon Sep 17 00:00:00 2001 From: Ken Barber Date: Fri, 30 Dec 2011 01:32:18 +0000 Subject: [PATCH 0727/3753] (#11583) Switch request method to open-uri monkey patch 'open' This makes the code consistent with the rest of the EC2 fact code by just using open for http requests instead of the full open_uri method. This is to make my testing consistent and is also slightly cosmetic. I also fixed the indentation on those two lines as well. --- lib/facter/ec2.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/facter/ec2.rb b/lib/facter/ec2.rb index 173daeaa50..5f712fbf1f 100644 --- a/lib/facter/ec2.rb +++ b/lib/facter/ec2.rb @@ -34,8 +34,8 @@ def metadata(id = "") def userdata() begin - value = OpenURI.open_uri("http://169.254.169.254/2008-02-01/user-data/").read.split - Facter.add(:ec2_userdata) { setcode { value } } + value = open("http://169.254.169.254/2008-02-01/user-data/").read.split + Facter.add(:ec2_userdata) { setcode { value } } rescue OpenURI::HTTPError end end From e6cebd3b24038165f46f5b53c0d9479137f4fb10 Mon Sep 17 00:00:00 2001 From: Jonathan Boyett Date: Mon, 19 Sep 2011 11:37:16 -0700 Subject: [PATCH 0728/3753] (#9599) Add nexenta facts Adds support for Nexenta to the operatingsystem and osfamily facts. Since Nexenta is binary compatible with solaris, it has also been added into the Solaris osfamily fact. Tests and osfamily support added by Adrien Thebo --- lib/facter/operatingsystem.rb | 8 +++++++- lib/facter/osfamily.rb | 2 +- spec/unit/operatingsystem_spec.rb | 22 +++++++++++++++------- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index eed6808d22..cf15a8dd9d 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -14,7 +14,13 @@ Facter.add(:operatingsystem) do confine :kernel => :sunos - setcode do "Solaris" end + setcode do + if FileTest.exists?("/etc/debian_version") + "Nexenta" + else + "Solaris" + end + end end Facter.add(:operatingsystem) do diff --git a/lib/facter/osfamily.rb b/lib/facter/osfamily.rb index f3bb81d992..0cc3056fad 100644 --- a/lib/facter/osfamily.rb +++ b/lib/facter/osfamily.rb @@ -22,7 +22,7 @@ "Debian" when "SLES", "SLED", "OpenSuSE", "SuSE" "Suse" - when "Solaris" + when "Solaris", "Nexenta" "Solaris" else Facter.value("kernel") diff --git a/spec/unit/operatingsystem_spec.rb b/spec/unit/operatingsystem_spec.rb index ad754468fc..d00b1a9559 100755 --- a/spec/unit/operatingsystem_spec.rb +++ b/spec/unit/operatingsystem_spec.rb @@ -13,19 +13,27 @@ Facter.fact(:operatingsystem).value.should == "Nutmeg" end - - it "should be Solaris for SunOS" do - Facter.fact(:kernel).stubs(:value).returns("SunOS") - - Facter.fact(:operatingsystem).value.should == "Solaris" - end - it "should be ESXi for VMkernel" do Facter.fact(:kernel).stubs(:value).returns("VMkernel") Facter.fact(:operatingsystem).value.should == "ESXi" end + describe "on Solaris variants" do + before :each do + Facter.fact(:kernel).stubs(:value).returns("SunOS") + end + + it "should be Nexenta if /etc/debian_version is present" do + FileTest.expects(:exists?).with("/etc/debian_version").returns true + Facter.fact(:operatingsystem).value.should == "Nexenta" + end + + it "should be Solaris for SunOS if no other variants match" do + Facter.fact(:operatingsystem).value.should == "Solaris" + end + end + describe "on Linux" do before :each do Facter.fact(:kernel).stubs(:value).returns("Linux") From 82692ba107567a8ad9fce377d9b6a9513db3481d Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Fri, 30 Dec 2011 14:47:56 -0800 Subject: [PATCH 0729/3753] (#9599) Generalize zone detection All systems using the SunOS kernel can run zones, not just the solaris operating system. Generalized this for cases like Nexenta and Open Indiana. --- lib/facter/virtual.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 6e8c91f7ce..1ee41f94b4 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -52,7 +52,7 @@ setcode do - if Facter.value(:operatingsystem) == "Solaris" and Facter::Util::Virtual.zone? + if Facter.value(:kernel) == "SunOS" and Facter::Util::Virtual.zone? result = "zone" end From b51ccf009692a8dc04d154da1415f410ccc05f57 Mon Sep 17 00:00:00 2001 From: Ken Barber Date: Fri, 30 Dec 2011 01:37:22 +0000 Subject: [PATCH 0730/3753] (#11583) Add basic coverage to the ec2 fact The only goal here is to add coverage based on the current behaviour so we have something to work with when other contributors work on the code. We've only added very simple bad-case testing (ie. timeout) to match the code at the moment, however the good-case testing covers flat & structured meta-data and user-data. While we test both ec2 and eucalyptus cases, we only check basic user-data for eucalyptus for now (as the code is the same in both cases). --- spec/unit/ec2_spec.rb | 149 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100755 spec/unit/ec2_spec.rb diff --git a/spec/unit/ec2_spec.rb b/spec/unit/ec2_spec.rb new file mode 100755 index 0000000000..8eddf11f08 --- /dev/null +++ b/spec/unit/ec2_spec.rb @@ -0,0 +1,149 @@ +#!/usr/bin/env rspec + +require 'spec_helper' + +describe "ec2 facts" do + let(:api_prefix) { "http://169.254.169.254/" } + + describe "when running on ec2" do + before :each do + # Return fake kernel + Facter.stubs(:value).with(:kernel).returns("Linux") + + # Return something upon connecting to the root so the EC2 code continues + # with its evaluation. + Object.any_instance.expects(:open).with("#{api_prefix}:80/").\ + at_least_once.returns("2008-02-01\nlatest") + + # Return a non-eucalyptus mac address + Facter.expects(:value).with(:macaddress).\ + at_least_once.returns("12:31:39:04:5A:34") + + # Fake EC2 arp response + ec2arp = "? (10.240.93.1) at fe:ff:ff:ff:ff:ff [ether] on eth0\n" + Facter::Util::Resolution.expects(:exec).with("arp -an").\ + at_least_once.returns(ec2arp) + end + + it "should create flat meta-data facts" do + Object.any_instance.expects(:open).\ + with("#{api_prefix}/2008-02-01/meta-data/").\ + at_least_once.returns(StringIO.new("foo")) + + Object.any_instance.expects(:open).\ + with("#{api_prefix}/2008-02-01/meta-data/foo").\ + at_least_once.returns(StringIO.new("bar")) + + # No user-data + Object.any_instance.expects(:open).\ + with("#{api_prefix}/2008-02-01/user-data/").\ + at_least_once.returns(StringIO.new("")) + + Facter.collection.loader.load(:ec2) + Facter.fact(:ec2_foo).value.should == "bar" + end + + it "should create flat meta-data facts with comma seperation" do + Object.any_instance.expects(:open).\ + with("#{api_prefix}/2008-02-01/meta-data/").\ + at_least_once.returns(StringIO.new("foo")) + + Object.any_instance.expects(:open).\ + with("#{api_prefix}/2008-02-01/meta-data/foo").\ + at_least_once.returns(StringIO.new("bar\nbaz")) + + # No user-data + Object.any_instance.expects(:open).\ + with("#{api_prefix}/2008-02-01/user-data/").\ + at_least_once.returns(StringIO.new("")) + + Facter.collection.loader.load(:ec2) + Facter.fact(:ec2_foo).value.should == "bar,baz" + end + + it "should create structured meta-data facts" do + Object.any_instance.expects(:open).\ + with("#{api_prefix}/2008-02-01/meta-data/").\ + at_least_once.returns(StringIO.new("foo/")) + + Object.any_instance.expects(:open).\ + with("#{api_prefix}/2008-02-01/meta-data/foo/").\ + at_least_once.returns(StringIO.new("bar")) + + Object.any_instance.expects(:open).\ + with("#{api_prefix}/2008-02-01/meta-data/foo/bar").\ + at_least_once.returns(StringIO.new("baz")) + + # No user-data + Object.any_instance.expects(:open).\ + with("#{api_prefix}/2008-02-01/user-data/").\ + at_least_once.returns(StringIO.new("")) + + Facter.collection.loader.load(:ec2) + Facter.fact(:ec2_foo_bar).value.should == "baz" + end + + it "should create ec2_user_data fact" do + # No meta-data + Object.any_instance.expects(:open).\ + with("#{api_prefix}/2008-02-01/meta-data/").\ + at_least_once.returns(StringIO.new("")) + + Object.any_instance.expects(:open).\ + with("#{api_prefix}/2008-02-01/user-data/").\ + at_least_once.returns(StringIO.new("test")) + + Facter.collection.loader.load(:ec2) + Facter.fact(:ec2_userdata).value.should == ["test"] + end + end + + describe "when running on eucalyptus" do + before :each do + # Return fake kernel + Facter.stubs(:value).with(:kernel).returns("Linux") + + # Return something upon connecting to the root so the EC2 code continues + # with its evaluation. + Object.any_instance.expects(:open).with("#{api_prefix}:80/").\ + at_least_once.returns("2008-02-01\nlatest") + + # Return a eucalyptus mac address + Facter.expects(:value).with(:macaddress).\ + at_least_once.returns("d0:0d:1a:b0:a1:00") + end + + it "should create ec2_user_data fact" do + # No meta-data + Object.any_instance.expects(:open).\ + with("#{api_prefix}/2008-02-01/meta-data/").\ + at_least_once.returns(StringIO.new("")) + + Object.any_instance.expects(:open).\ + with("#{api_prefix}/2008-02-01/user-data/").\ + at_least_once.returns(StringIO.new("test")) + + Facter.collection.loader.load(:ec2) + Facter.fact(:ec2_userdata).value.should == ["test"] + end + end + + describe "when ec2 url times out" do + before :each do + # Return fake kernel + Facter.stubs(:value).with(:kernel).returns("Linux") + + # Emulate a timeout when connecting by throwing an exception + Object.any_instance.expects(:open).with("#{api_prefix}:80/").\ + at_least_once.throws(Timeout::Error) + + # Return a eucalyptus mac address + Facter.expects(:value).with(:macaddress).\ + at_least_once.returns("d0:0d:1a:b0:a1:00") + end + + it "should not raise exception" do + expect { Facter.collection.loader.load(:ec2) }.to_not raise_error + end + end +end From 6c8683a50c394f017459e24f73e67e66bcbc6d5a Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Sun, 1 Jan 2012 22:39:16 +1100 Subject: [PATCH 0731/3753] (#11660) Adds feature SSHFP (#11659) Adds ssh spec RFC4255 SSH fingerprints are now facts in the form SSHFP_RSA, SSHFP_DSA. No other algorithms are currently supported (www.iana.org/assignments/dns-sshfp-rr-parameters). Adds test cases for the ssh fact module. --- spec/unit/ssh_spec.rb | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100755 spec/unit/ssh_spec.rb diff --git a/spec/unit/ssh_spec.rb b/spec/unit/ssh_spec.rb new file mode 100755 index 0000000000..908f904f10 --- /dev/null +++ b/spec/unit/ssh_spec.rb @@ -0,0 +1,42 @@ +#!/usr/bin/env rspec + +require 'spec_helper' +require 'facter/ssh' + +describe "SSH fact" do + + + { + # fingerprints extracted from ssh-keygen -r '' -f /etc/ssh/ssh_host_dsa_key.pub + 'SSHRSAKey' => [ '/usr/local/etc/ssh_host_rsa_key.pub' , "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDrs+KtR8hjasELsyCiiBplUeIi77hEHzTSQt1ALG7N4IgtMg27ZAcq0tl2/O9ZarQuClc903pgionbM9Q98CtAIoqgJwdtsor7ETRmzwrcY/mvI7ne51UzQy4Eh9WrplfpNyg+EVO0FUC7mBcay6JY30QKasePp+g4MkwK5cuTzOCzd9up9KELonlH7tTm2L0YI4HhZugwVoTFulCAZvPICxSk1B/fEKyGSZVfY/UxZNqg9g2Wyvq5u40xQ5eO882UwhB3w4IbmRnPKcyotAcqOJxA7hToMKtEmFct+vjHE8T37w8axE/1X9mdvy8IZbkEBL1cupqqb8a8vU1QTg1z", 'SSHFP 1 1 1e4f163a1747d0d1a08a29972c9b5d94ee5705d0' ], + 'SSHDSAKey' => [ '/etc/ssh/ssh_host_dsa_key.pub' , "ssh-dss AAAAB3NzaC1kc3MAAACBAKjmRez14aZT6OKhHrsw19s7u30AdghwHFQbtC+L781YjJ3UV0/WQoZ8NaDL4ovuvW23RuO49tsqSNcVHg+PtRiN2iTVAS2h55TFhaPKhTs+i0NH3p3Ze8LNSYuz8uK7a+nTxysz47GYTHiE1ke8KXe5wGKDO1TO/MUgpDbwx72LAAAAFQD9yMJCnZMiKzA7J1RNkwvgCyBKSQAAAIAtWBAsuRM0F2fdCe+F/JmgyryQmRIT5vP8E1ww3t3ywdLHklN7UMkaEKBW/TN/jj1JOGXtZ2v5XI+0VNoNKD/7dnCGzNViRT/jjfyVi6l5UMg4Q52Gv0RXJoBJpxNqFOU2niSsy8hioyE39W6LJYWJtQozGpH/KKgkCSvxBn5hlAAAAIB1yo/YD0kQICOO0KE+UMMaKtV7FwyedFJsxsWYwZfHXGwWskf0d2+lPhd9qwdbmSvySE8Qrlvu+W+X8AipwGkItSnj16ORF8kO3lfABa+7L4BLDtumt7ybjBPcHOy3n28dd07TmMtyWvLjOb0mcxPo+TwDLtHd3L/3C1Dh41jRPg==\n", 'SSHFP 2 1 f63dfe8da99f50ffbcfa40a61161cee29d109f70' ], + 'SSHECDSAKey' => [ '/etc/ssh_host_ecdsa_key.pub' , 'ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBIuKHtgXQUIrXSVNKC7uY+ZOF7jjfqYNU7Cb/IncDOZ7jW44dxsfBzRJwS5sTHERjBinJskY87mmwY07NFF5GoE=', nil] + }.each_pair do |fact, pubkey| + Facter.clear + filename, contents, fingerprint = pubkey + pk = /AAAA\S+/.match(contents).to_s + it "'#{fact}' should be '#{pk}' based on '#{filename}' contents '#{contents}'" do + FileTest.stubs(:file?).returns false + FileTest.expects(:file?).with(filename).returns true + File.expects(:open).with(filename).at_least_once + File.expects(:read).with(filename).returns contents + + Facter.fact(fact).value.should == pk + end + Facter.clear + fp_fact = 'SSHFP_' + fact[3..-4] + it "'#{fp_fact}' should have fingerprint '#{fingerprint}' based on '#{filename}' contents '#{contents}'" do + Facter.fact(fact).stubs(:value).returns(pk) + + f = Facter.fact(fp_fact).value + if fact == 'SSHECDSAKey' + # no IANA registered RR type ids for ECDSA` + f == nil + else + f.should == fingerprint + end + end + end + + +end From 50f6da63a2040f15333540b3c61e0bce11f48e06 Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Sun, 1 Jan 2012 22:50:43 +1100 Subject: [PATCH 0732/3753] (#11660) Adds feature SSHFP Corrects previous which didn't include the right file. RFC4255 SSH fingerprints are now facts in the form SSHFP_RSA, SSHFP_DSA. No other algorithms are currently supported (www.iana.org/assignments/dns-sshfp-rr-parameters). --- lib/facter/ssh.rb | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/facter/ssh.rb b/lib/facter/ssh.rb index 6aa7e080a8..9f5f0c175d 100644 --- a/lib/facter/ssh.rb +++ b/lib/facter/ssh.rb @@ -12,11 +12,11 @@ ## ["/etc/ssh","/usr/local/etc/ssh","/etc","/usr/local/etc"].each do |dir| - {"SSHDSAKey" => "ssh_host_dsa_key.pub", "SSHRSAKey" => "ssh_host_rsa_key.pub", "SSHECDSAKey" => "ssh_host_ecdsa_key.pub"}.each do |name,file| + {"SSHDSAKey" => { :file => "ssh_host_dsa_key.pub", :sshfprrtype => 2 } , "SSHRSAKey" => { :file => "ssh_host_rsa_key.pub", :sshfprrtype => 1 }, "SSHECDSAKey" => { :file => "ssh_host_ecdsa_key.pub"} }.each do |name,key| Facter.add(name) do setcode do value = nil - filepath = File.join(dir,file) + filepath = File.join(dir,key[:file]) if FileTest.file?(filepath) begin File.open(filepath) { |f| value = f.read.chomp.split(/\s+/)[1] } @@ -27,5 +27,21 @@ value end # end of proc end # end of add + Facter.add('SSHFP_' + name[3..-4]) do + setcode do + ssh = Facter.fact(name).value + value = nil + if ssh && key[:sshfprrtype] + begin + require 'digest/sha1' + require 'base64' + value = 'SSHFP ' + key[:sshfprrtype].to_s + ' 1 ' + Digest::SHA1.hexdigest(Base64.decode64(ssh)) + rescue + value = nil + end + end # end of sshfp if + value + end # end of sshfp proc + end # end of sshfp add end # end of hash each end # end of dir each From 2de7b84e4c70a19586e847da20111c0751ed3514 Mon Sep 17 00:00:00 2001 From: Ken Barber Date: Sun, 1 Jan 2012 13:37:21 +0000 Subject: [PATCH 0733/3753] (#11661) EC2 rspec tests were using throw not raise to simulate a timeout This patch corrects that behaviour. Also, 'throws' wasn't working on our CI machines since we were using mocha 0.9.x. --- spec/unit/ec2_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/unit/ec2_spec.rb b/spec/unit/ec2_spec.rb index 8eddf11f08..4646ee35e5 100755 --- a/spec/unit/ec2_spec.rb +++ b/spec/unit/ec2_spec.rb @@ -135,7 +135,7 @@ # Emulate a timeout when connecting by throwing an exception Object.any_instance.expects(:open).with("#{api_prefix}:80/").\ - at_least_once.throws(Timeout::Error) + at_least_once.raises(Timeout::Error) # Return a eucalyptus mac address Facter.expects(:value).with(:macaddress).\ From 88c94296cf88d8dd0414e5f398432ba4a9466d6f Mon Sep 17 00:00:00 2001 From: Marcus Vinicius Ferreira Date: Mon, 2 Jan 2012 12:30:59 -0200 Subject: [PATCH 0734/3753] (#10271) Identifying 'Amazon' using '/etc/system-release' --- lib/facter/operatingsystem.rb | 2 +- spec/unit/operatingsystem_spec.rb | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index cf15a8dd9d..be4243eae5 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -88,7 +88,7 @@ "Slackware" elsif FileTest.exists?("/etc/alpine-release") "Alpine" - elsif Facter.value(:lsbdistdescription) =~ /Amazon Linux/ + elsif FileTest.exists?("/etc/system-release") "Amazon" end end diff --git a/spec/unit/operatingsystem_spec.rb b/spec/unit/operatingsystem_spec.rb index d00b1a9559..89a18c45a4 100755 --- a/spec/unit/operatingsystem_spec.rb +++ b/spec/unit/operatingsystem_spec.rb @@ -53,6 +53,7 @@ "Bluewhite64" => "/etc/bluewhite64-version", "Slamd64" => "/etc/slamd64-version", "Slackware" => "/etc/slackware-version", + "Amazon" => "/etc/system-release", }.each_pair do |distribution, releasefile| it "should be #{distribution} if #{releasefile} exists" do FileTest.expects(:exists?).with(releasefile).returns true @@ -72,10 +73,6 @@ Facter.fact(:operatingsystem).value.should == "Ubuntu" end - it "on Amazon Linux should use the lsbdistdescription fact" do - Facter.fact(:lsbdistdescription).stubs(:value).returns "Amazon Linux" - Facter.fact(:operatingsystem).value.should == "Amazon" - end end From 2bdacfbda4c1c87d72d5e63b098030449ea19803 Mon Sep 17 00:00:00 2001 From: Daniel Pittman Date: Tue, 3 Jan 2012 16:49:34 -0800 Subject: [PATCH 0735/3753] Revert "(#11660) Adds feature SSHFP" This reverts commit 50f6da63a2040f15333540b3c61e0bce11f48e06. This reverts commit 6c8683a50c394f017459e24f73e67e66bcbc6d5a. These ware submitted oddly, and I didn't notice that I merged the non-working tests as well as the correct looking code, since they had two tickets associated. Backing both out now. --- lib/facter/ssh.rb | 20 ++------------------ spec/unit/ssh_spec.rb | 42 ------------------------------------------ 2 files changed, 2 insertions(+), 60 deletions(-) delete mode 100755 spec/unit/ssh_spec.rb diff --git a/lib/facter/ssh.rb b/lib/facter/ssh.rb index 9f5f0c175d..6aa7e080a8 100644 --- a/lib/facter/ssh.rb +++ b/lib/facter/ssh.rb @@ -12,11 +12,11 @@ ## ["/etc/ssh","/usr/local/etc/ssh","/etc","/usr/local/etc"].each do |dir| - {"SSHDSAKey" => { :file => "ssh_host_dsa_key.pub", :sshfprrtype => 2 } , "SSHRSAKey" => { :file => "ssh_host_rsa_key.pub", :sshfprrtype => 1 }, "SSHECDSAKey" => { :file => "ssh_host_ecdsa_key.pub"} }.each do |name,key| + {"SSHDSAKey" => "ssh_host_dsa_key.pub", "SSHRSAKey" => "ssh_host_rsa_key.pub", "SSHECDSAKey" => "ssh_host_ecdsa_key.pub"}.each do |name,file| Facter.add(name) do setcode do value = nil - filepath = File.join(dir,key[:file]) + filepath = File.join(dir,file) if FileTest.file?(filepath) begin File.open(filepath) { |f| value = f.read.chomp.split(/\s+/)[1] } @@ -27,21 +27,5 @@ value end # end of proc end # end of add - Facter.add('SSHFP_' + name[3..-4]) do - setcode do - ssh = Facter.fact(name).value - value = nil - if ssh && key[:sshfprrtype] - begin - require 'digest/sha1' - require 'base64' - value = 'SSHFP ' + key[:sshfprrtype].to_s + ' 1 ' + Digest::SHA1.hexdigest(Base64.decode64(ssh)) - rescue - value = nil - end - end # end of sshfp if - value - end # end of sshfp proc - end # end of sshfp add end # end of hash each end # end of dir each diff --git a/spec/unit/ssh_spec.rb b/spec/unit/ssh_spec.rb deleted file mode 100755 index 908f904f10..0000000000 --- a/spec/unit/ssh_spec.rb +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env rspec - -require 'spec_helper' -require 'facter/ssh' - -describe "SSH fact" do - - - { - # fingerprints extracted from ssh-keygen -r '' -f /etc/ssh/ssh_host_dsa_key.pub - 'SSHRSAKey' => [ '/usr/local/etc/ssh_host_rsa_key.pub' , "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDrs+KtR8hjasELsyCiiBplUeIi77hEHzTSQt1ALG7N4IgtMg27ZAcq0tl2/O9ZarQuClc903pgionbM9Q98CtAIoqgJwdtsor7ETRmzwrcY/mvI7ne51UzQy4Eh9WrplfpNyg+EVO0FUC7mBcay6JY30QKasePp+g4MkwK5cuTzOCzd9up9KELonlH7tTm2L0YI4HhZugwVoTFulCAZvPICxSk1B/fEKyGSZVfY/UxZNqg9g2Wyvq5u40xQ5eO882UwhB3w4IbmRnPKcyotAcqOJxA7hToMKtEmFct+vjHE8T37w8axE/1X9mdvy8IZbkEBL1cupqqb8a8vU1QTg1z", 'SSHFP 1 1 1e4f163a1747d0d1a08a29972c9b5d94ee5705d0' ], - 'SSHDSAKey' => [ '/etc/ssh/ssh_host_dsa_key.pub' , "ssh-dss AAAAB3NzaC1kc3MAAACBAKjmRez14aZT6OKhHrsw19s7u30AdghwHFQbtC+L781YjJ3UV0/WQoZ8NaDL4ovuvW23RuO49tsqSNcVHg+PtRiN2iTVAS2h55TFhaPKhTs+i0NH3p3Ze8LNSYuz8uK7a+nTxysz47GYTHiE1ke8KXe5wGKDO1TO/MUgpDbwx72LAAAAFQD9yMJCnZMiKzA7J1RNkwvgCyBKSQAAAIAtWBAsuRM0F2fdCe+F/JmgyryQmRIT5vP8E1ww3t3ywdLHklN7UMkaEKBW/TN/jj1JOGXtZ2v5XI+0VNoNKD/7dnCGzNViRT/jjfyVi6l5UMg4Q52Gv0RXJoBJpxNqFOU2niSsy8hioyE39W6LJYWJtQozGpH/KKgkCSvxBn5hlAAAAIB1yo/YD0kQICOO0KE+UMMaKtV7FwyedFJsxsWYwZfHXGwWskf0d2+lPhd9qwdbmSvySE8Qrlvu+W+X8AipwGkItSnj16ORF8kO3lfABa+7L4BLDtumt7ybjBPcHOy3n28dd07TmMtyWvLjOb0mcxPo+TwDLtHd3L/3C1Dh41jRPg==\n", 'SSHFP 2 1 f63dfe8da99f50ffbcfa40a61161cee29d109f70' ], - 'SSHECDSAKey' => [ '/etc/ssh_host_ecdsa_key.pub' , 'ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBIuKHtgXQUIrXSVNKC7uY+ZOF7jjfqYNU7Cb/IncDOZ7jW44dxsfBzRJwS5sTHERjBinJskY87mmwY07NFF5GoE=', nil] - }.each_pair do |fact, pubkey| - Facter.clear - filename, contents, fingerprint = pubkey - pk = /AAAA\S+/.match(contents).to_s - it "'#{fact}' should be '#{pk}' based on '#{filename}' contents '#{contents}'" do - FileTest.stubs(:file?).returns false - FileTest.expects(:file?).with(filename).returns true - File.expects(:open).with(filename).at_least_once - File.expects(:read).with(filename).returns contents - - Facter.fact(fact).value.should == pk - end - Facter.clear - fp_fact = 'SSHFP_' + fact[3..-4] - it "'#{fp_fact}' should have fingerprint '#{fingerprint}' based on '#{filename}' contents '#{contents}'" do - Facter.fact(fact).stubs(:value).returns(pk) - - f = Facter.fact(fp_fact).value - if fact == 'SSHECDSAKey' - # no IANA registered RR type ids for ECDSA` - f == nil - else - f.should == fingerprint - end - end - end - - -end From 5a60ca643b5e2c52518c848d895bf235481f6194 Mon Sep 17 00:00:00 2001 From: Ken Barber Date: Tue, 3 Jan 2012 20:55:09 +0000 Subject: [PATCH 0736/3753] (#11559) Switch to RbConfig & Provide alias for RbConfig for pre-1.8.5 In Ruby 1.9.3 we started to see warning messages about the deprecated usage of the class 'Config' as apposed to 'RbConfig'. This patch switches all our code to 'RbConfig' instead. Now the constant RbConfig was aliased to Config going back to Ruby 1.8.5, but for pre-1.8.5 ruby this was not done. To support our Ruby 1.8.1 user base (due to RHEL 4 predominantly) this provides a monkey patch util library which provides the same alias (conditionally) but for older rubies instead. This methodology is seen as less intrusive and less maintainance going forward then to create conditionals everywhere, or a facter-only backwards compatible alternative to RbConfig. And since its the similar methodology adopted by Ruby core (more or less) it should be a monkey patch with low surprises. I've also added testing around the facter/util/config.rb library. Thanks to James Turnbull for the original code for this patch. --- conf/redhat/facter.spec | 2 +- install.rb | 38 ++++++++++++++++++------------- lib/facter.rb | 1 + lib/facter/hardwaremodel.rb | 2 +- lib/facter/util/config.rb | 6 ++--- lib/facter/util/monkey_patches.rb | 7 ++++++ spec/unit/util/config_spec.rb | 24 +++++++++++++++++++ 7 files changed, 59 insertions(+), 21 deletions(-) create mode 100644 lib/facter/util/monkey_patches.rb create mode 100644 spec/unit/util/config_spec.rb diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index ba6f31545f..8a47325f36 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -1,4 +1,4 @@ -%{!?ruby_sitelibdir: %define ruby_sitelibdir %(ruby -rrbconfig -e 'puts Config::CONFIG["sitelibdir"]')} +%{!?ruby_sitelibdir: %define ruby_sitelibdir %(ruby -rrbconfig -e 'puts Object.const_get(defined?(RbConfig) ? :RbConfig : :Config)::CONFIG["sitelibdir"]' %define has_ruby_abi 0%{?fedora} || 0%{?rhel} >= 5 %define has_ruby_noarch %has_ruby_abi diff --git a/install.rb b/install.rb index 30af4f4097..53f7e4ea1b 100755 --- a/install.rb +++ b/install.rb @@ -46,6 +46,12 @@ $haverdoc = false end +# Monkey patch RbConfig->Config for Rubies older then 1.8.5. +unless defined? ::RbConfig + require 'rbconfig' + ::RbConfig = ::Config +end + begin if $haverdoc rst2man = %x{which rst2man.py} @@ -180,16 +186,16 @@ def prepare_installation opts.on('--destdir[=OPTIONAL]', 'Installation prefix for all targets', 'Default essentially /') do |destdir| InstallOptions.destdir = destdir end - opts.on('--bindir[=OPTIONAL]', 'Installation directory for binaries', 'overrides Config::CONFIG["bindir"]') do |bindir| + opts.on('--bindir[=OPTIONAL]', 'Installation directory for binaries', 'overrides RbConfig::CONFIG["bindir"]') do |bindir| InstallOptions.bindir = bindir end - opts.on('--sbindir[=OPTIONAL]', 'Installation directory for system binaries', 'overrides Config::CONFIG["sbindir"]') do |sbindir| + opts.on('--sbindir[=OPTIONAL]', 'Installation directory for system binaries', 'overrides RbConfig::CONFIG["sbindir"]') do |sbindir| InstallOptions.sbindir = sbindir end - opts.on('--sitelibdir[=OPTIONAL]', 'Installation directory for libraries', 'overrides Config::CONFIG["sitelibdir"]') do |sitelibdir| + opts.on('--sitelibdir[=OPTIONAL]', 'Installation directory for libraries', 'overrides RbConfig::CONFIG["sitelibdir"]') do |sitelibdir| InstallOptions.sitelibdir = sitelibdir end - opts.on('--mandir[=OPTIONAL]', 'Installation directory for man pages', 'overrides Config::CONFIG["mandir"]') do |mandir| + opts.on('--mandir[=OPTIONAL]', 'Installation directory for man pages', 'overrides RbConfig::CONFIG["mandir"]') do |mandir| InstallOptions.mandir = mandir end opts.on('--quick', 'Performs a quick installation. Only the', 'installation is done.') do |quick| @@ -213,8 +219,8 @@ def prepare_installation tmpdirs = [ENV['TMP'], ENV['TEMP'], "/tmp", "/var/tmp", "."] - version = [Config::CONFIG["MAJOR"], Config::CONFIG["MINOR"]].join(".") - libdir = File.join(Config::CONFIG["libdir"], "ruby", version) + version = [RbConfig::CONFIG["MAJOR"], RbConfig::CONFIG["MINOR"]].join(".") + libdir = File.join(RbConfig::CONFIG["libdir"], "ruby", version) # Mac OS X 10.5 and higher declare bindir and sbindir as # /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin @@ -222,26 +228,26 @@ def prepare_installation # which is not generally where people expect executables to be installed # These settings are appropriate defaults for all OS X versions. if RUBY_PLATFORM =~ /^universal-darwin[\d\.]+$/ - Config::CONFIG['bindir'] = "/usr/bin" - Config::CONFIG['sbindir'] = "/usr/sbin" + RbConfig::CONFIG['bindir'] = "/usr/bin" + RbConfig::CONFIG['sbindir'] = "/usr/sbin" end if not InstallOptions.bindir.nil? bindir = InstallOptions.bindir else - bindir = Config::CONFIG['bindir'] + bindir = RbConfig::CONFIG['bindir'] end if not InstallOptions.sbindir.nil? sbindir = InstallOptions.sbindir else - sbindir = Config::CONFIG['sbindir'] + sbindir = RbConfig::CONFIG['sbindir'] end if not InstallOptions.sitelibdir.nil? sitelibdir = InstallOptions.sitelibdir else - sitelibdir = Config::CONFIG["sitelibdir"] + sitelibdir = RbConfig::CONFIG["sitelibdir"] if sitelibdir.nil? sitelibdir = $:.find { |x| x =~ /site_ruby/ } if sitelibdir.nil? @@ -255,7 +261,7 @@ def prepare_installation if not InstallOptions.mandir.nil? mandir = InstallOptions.mandir else - mandir = Config::CONFIG['mandir'] + mandir = RbConfig::CONFIG['mandir'] end # To be deprecated once people move over to using --destdir option @@ -373,7 +379,7 @@ def run_tests(test_list) end ## -# Install file(s) from ./bin to Config::CONFIG['bindir']. Patch it on the way +# Install file(s) from ./bin to RbConfig::CONFIG['bindir']. Patch it on the way # to insert a #! line; on a Unix install, the command is named as expected # (e.g., bin/rdoc becomes rdoc); the shebang line handles running it. Under # windows, we add an '.rb' extension and let file associations do their stuff. @@ -388,11 +394,11 @@ def install_binfile(from, op_file, target) fail "Cannot find a temporary directory" unless tmp_dir tmp_file = File.join(tmp_dir, '_tmp') - ruby = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name']) + ruby = File.join(RbConfig::CONFIG['bindir'], RbConfig::CONFIG['ruby_install_name']) File.open(from) do |ip| File.open(tmp_file, "w") do |op| - ruby = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name']) + ruby = File.join(RbConfig::CONFIG['bindir'], RbConfig::CONFIG['ruby_install_name']) op.puts "#!#{ruby}" contents = ip.readlines if contents[0] =~ /^#!/ @@ -417,7 +423,7 @@ def install_binfile(from, op_file, target) if not installed_wrapper tmp_file2 = File.join(tmp_dir, '_tmp_wrapper') - cwn = File.join(Config::CONFIG['bindir'], op_file) + cwn = File.join(RbConfig::CONFIG['bindir'], op_file) cwv = <<-EOS @echo off if "%OS%"=="Windows_NT" goto WinNT diff --git a/lib/facter.rb b/lib/facter.rb index b919aa5d9d..5cbf74de32 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -20,6 +20,7 @@ module Util; end require 'facter/util/fact' require 'facter/util/collection' + require 'facter/util/monkey_patches' include Comparable include Enumerable diff --git a/lib/facter/hardwaremodel.rb b/lib/facter/hardwaremodel.rb index 07e750129f..e21fd0e317 100644 --- a/lib/facter/hardwaremodel.rb +++ b/lib/facter/hardwaremodel.rb @@ -29,6 +29,6 @@ confine :operatingsystem => :windows setcode do require 'rbconfig' - Config::CONFIG['host_cpu'] + RbConfig::CONFIG['host_cpu'] end end diff --git a/lib/facter/util/config.rb b/lib/facter/util/config.rb index 1630eadbee..950f223184 100644 --- a/lib/facter/util/config.rb +++ b/lib/facter/util/config.rb @@ -1,9 +1,9 @@ +require 'rbconfig' + # A module to return config related data # module Facter::Util::Config - require 'rbconfig' - def self.is_windows? - Config::CONFIG['host_os'] =~ /mswin|win32|dos|mingw|cygwin/i + RbConfig::CONFIG['host_os'] =~ /mswin|win32|dos|mingw|cygwin/i end end diff --git a/lib/facter/util/monkey_patches.rb b/lib/facter/util/monkey_patches.rb new file mode 100644 index 0000000000..8d5d93f195 --- /dev/null +++ b/lib/facter/util/monkey_patches.rb @@ -0,0 +1,7 @@ +# This provides an alias for RbConfig to Config for versions of Ruby older then +# version 1.8.5. This allows us to use RbConfig in place of the older Config in +# our code and still be compatible with at least Ruby 1.8.1. +require 'rbconfig' +unless defined? ::RbConfig + ::RbConfig = ::Config +end diff --git a/spec/unit/util/config_spec.rb b/spec/unit/util/config_spec.rb new file mode 100644 index 0000000000..3eb134b4d2 --- /dev/null +++ b/spec/unit/util/config_spec.rb @@ -0,0 +1,24 @@ +#!/usr/bin/env rspec + +require 'spec_helper' +require 'facter/util/config' + +describe Facter::Util::Config do + describe "is_windows? function" do + it "should detect windows if RbConfig returns a windows OS" do + host_os = ["mswin","win32","dos","mingw","cygwin"] + host_os.each do |h| + RbConfig::CONFIG.expects(:[]).with('host_os').returns(h) + Facter::Util::Config.is_windows?.should be_true + end + end + + it "should not detect windows if RbConfig returns a non-windows OS" do + host_os = ["darwin","linux"] + host_os.each do |h| + RbConfig::CONFIG.expects(:[]).with('host_os').returns(h) + Facter::Util::Config.is_windows?.should be_false + end + end + end +end From 14cad7e4c8008f24a669b178f1669c45529d4d86 Mon Sep 17 00:00:00 2001 From: Gary Larizza Date: Tue, 10 Jan 2012 14:57:40 -0800 Subject: [PATCH 0737/3753] Build a Rake task for building Apple Packages The goal is to have our release managers build all Facter packages within Rake, and so this commit adds a rake task (apple_package) that will build a DMG-encapsulated package for OS X and put it into the pkg/apple directory off of the Facter Root. To accomplish this goal, a folder structure is created in /tmp/facter/facter-#{version} that mirrors the structure needed for Apple's Packagemaker CLI tool to build a package for OS X. Next, the necessary files are copied from the Puppet source into the structure in /tmp/facter/facter-#{version} and then packagemaker is run (targeting that folder structure) to build an initial package. Hdiutil then encapsulates that package into a DMG, and the file is finally copied into the pkg/apple directory. --- tasks/rake/apple.rake | 166 +++++++++++++++++++++++ tasks/rake/templates/prototype.plist.erb | 38 ++++++ 2 files changed, 204 insertions(+) create mode 100644 tasks/rake/apple.rake create mode 100644 tasks/rake/templates/prototype.plist.erb diff --git a/tasks/rake/apple.rake b/tasks/rake/apple.rake new file mode 100644 index 0000000000..0e6e9eb6a6 --- /dev/null +++ b/tasks/rake/apple.rake @@ -0,0 +1,166 @@ +# Title: Rake task to build Apple packages for Facter. +# Author: Gary Larizza +# Date: 12/5/2011 +# Description: This task will create a DMG-encapsulated package that will +# install Facter on OS X systems. This happens by building +# a directory tree of files that will then be fed to the +# packagemaker binary (can be installed by installing the +# XCode Tools) which will create the .pkg file. +# +require 'fileutils' +require 'erb' +require 'find' +require 'pathname' + +# Path to Binaries (Constants) +TAR = '/usr/bin/tar' +CP = '/bin/cp' +INSTALL = '/usr/bin/install' +DITTO = '/usr/bin/ditto' +PACKAGEMAKER = '/Developer/usr/bin/packagemaker' +SED = '/usr/bin/sed' + +# Setup task to populate all the variables +task :setup do + @version = `git describe`.chomp + @title = "puppet-#{@version}" + @reverse_domain = 'com.puppetlabs.puppet' + @package_major_version = @version.split('.')[0] + @package_minor_version = @version.split('.')[1] + + @version.split('.')[2].split('-')[0].split('rc')[0] + @pm_restart = 'None' + @build_date = Time.new.strftime("%Y-%m-%dT%H:%M:%SZ") +end + +# method: make_directory_tree +# description: This method sets up the directory structure that packagemaker +# needs to build a package. A prototype.plist file (holding +# package-specific options) is built from an ERB template located +# in the tasks/rake/templates directory. +def make_directory_tree + facter_tmp = '/tmp/facter' + @scratch = "#{facter_tmp}/#{@title}" + @working_tree = { + 'scripts' => "#{@scratch}/scripts", + 'resources' => "#{@scratch}/resources", + 'working' => "#{@scratch}/root", + 'payload' => "#{@scratch}/payload", + } + puts "Cleaning Tree: #{facter_tmp}" + FileUtils.rm_rf(facter_tmp) + @working_tree.each do |key,val| + puts "Creating: #{val}" + FileUtils.mkdir_p(val) + end + File.open("#{@scratch}/#{'prototype.plist'}", "w+") do |f| + f.write(ERB.new(File.read('tasks/rake/templates/prototype.plist.erb')).result()) + end +end + +# method: build_dmg +# description: This method builds a package from the directory structure in +# /tmp/facter and puts it in the +# /tmp/facter/facter-#{version}/payload directory. A DMG is +# created, using hdiutil, based on the contents of the +# /tmp/facter/facter-#{version}/payload directory. The resultant +# DMG is placed in the pkg/apple directory. +# +def build_dmg + # Local Variables + dmg_format_code = 'UDZO' + zlib_level = '9' + dmg_format_option = "-imagekey zlib-level=#{zlib_level}" + dmg_format = "#{dmg_format_code} #{dmg_format_option}" + dmg_file = "#{@title}.dmg" + package_file = "#{@title}.pkg" + pm_extra_args = '--verbose --no-recommend --no-relocate' + package_target_os = '10.4' + + # Build .pkg file + system("sudo #{PACKAGEMAKER} --root #{@working_tree['working']} \ + --id #{@reverse_domain} \ + --filter DS_Store \ + --target #{package_target_os} \ + --title #{@title} \ + --info #{@scratch}/prototype.plist \ + --scripts #{@working_tree['scripts']} \ + --resources #{@working_tree['resources']} \ + --version #{@version} \ + #{pm_extra_args} --out #{@working_tree['payload']}/#{package_file}") + + # Build .dmg file + system("sudo hdiutil create -volname #{@title} \ + -srcfolder #{@working_tree['payload']} \ + -uid 99 \ + -gid 99 \ + -ov \ + -format #{dmg_format} \ + #{dmg_file}") + + if File.directory?("#{Pathname.pwd}/pkg/apple") + FileUtils.mv("#{Pathname.pwd}/#{dmg_file}", "#{Pathname.pwd}/pkg/apple/#{dmg_file}") + puts "moved: #{dmg_file} has been moved to #{Pathname.pwd}/pkg/apple/#{dmg_file}" + else + FileUtils.mkdir_p("#{Pathname.pwd}/pkg/apple") + FileUtils.mv(dmg_file, "#{Pathname.pwd}/pkg/apple/#{dmg_file}") + puts "moved: #{dmg_file} has been moved to #{Pathname.pwd}/pkg/apple/#{dmg_file}" + end +end + +# method: pack_facter_source +# description: This method copies the facter source into a directory +# structure in /tmp/facter/facter-#{version}/root mirroring the +# structure on the target system for which the package will be +# installed. Anything installed into /tmp/facter/root will be +# installed as the package's payload. +# +def pack_facter_source + work = "#{@working_tree['working']}" + facter_source = Pathname.pwd + + # Make all necessary directories + directories = ["#{work}/usr/bin", + "#{work}/usr/share/doc/facter", + "#{work}/usr/lib/ruby/site_ruby/1.8/facter"] + FileUtils.mkdir_p(directories) + + # Install necessary files + system("#{DITTO} #{facter_source}/bin/ #{work}/usr/bin") + system("#{DITTO} #{facter_source}/lib/ #{work}/usr/lib/ruby/site_ruby/1.8/") + + # Setup a preflight script and replace variables in the files with + # the correct paths. + system("#{INSTALL} -o root -g wheel -m 644 #{facter_source}/conf/osx/preflight #{@working_tree['scripts']}") + system("#{SED} -i '' \"s\#{SITELIBDIR}\#/usr/lib/ruby/site_ruby/1.8\#g\" #{@working_tree['scripts']}/preflight") + system("#{SED} -i '' \"s\#{BINDIR}\#/usr/bin\#g\" #{@working_tree['scripts']}/preflight") + + # Install documentation (matching for files with capital letters) + Dir.foreach("#{facter_source}") do |file| + system("#{INSTALL} -o root -g wheel -m 644 #{facter_source}/#{file} #{work}/usr/share/doc/facter") if file =~ /^[A-Z][A-Z]/ + end + + # Set Permissions + executable_directories = [ "#{work}/usr/bin", ] + FileUtils.chmod_R(0755, executable_directories) + FileUtils.chown_R('root', 'wheel', directories) + FileUtils.chmod_R(0644, "#{work}/usr/lib/ruby/site_ruby/1.8/") + FileUtils.chown_R('root', 'wheel', "#{work}/usr/lib/ruby/site_ruby/1.8/") + Find.find("#{work}/usr/lib/ruby/site_ruby/1.8/") do |dir| + FileUtils.chmod(0755, dir) if File.directory?(dir) + end +end + +namespace :package do + desc "Task for building an Apple Package" + task :apple => [:setup] do + # Test for Root and Packagemaker binary + raise "Please run rake as root to build Apple Packages" unless Process.uid == 0 + raise "Packagemaker must be installed. Please install XCode Tools" unless \ + File.exists?('/Developer/usr/bin/packagemaker') + + make_directory_tree + pack_facter_source + build_dmg + FileUtils.chmod_R(0775, "#{Pathname.pwd}/pkg") + end +end diff --git a/tasks/rake/templates/prototype.plist.erb b/tasks/rake/templates/prototype.plist.erb new file mode 100644 index 0000000000..e56659a6bc --- /dev/null +++ b/tasks/rake/templates/prototype.plist.erb @@ -0,0 +1,38 @@ + + + + + CFBundleIdentifier + <%= @title %> + CFBundleShortVersionString + <%= @version %> + IFMajorVersion + <%= @package_major_version %> + IFMinorVersion + <%= @package_minor_version %> + IFPkgBuildDate + <%= @build_date %> + IFPkgFlagAllowBackRev + + IFPkgFlagAuthorizationAction + RootAuthorization + IFPkgFlagDefaultLocation + / + IFPkgFlagFollowLinks + + IFPkgFlagInstallFat + + IFPkgFlagIsRequired + + IFPkgFlagOverwritePermissions + + IFPkgFlagRelocatable + + IFPkgFlagRestartAction + <%= @pm_restart %> + IFPkgFlagRootVolumeOnly + + IFPkgFlagUpdateInstalledLanguages + + + From d1a33e5d6089289de60eda73fc431e700b5e4c8d Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Mon, 9 Jan 2012 22:22:41 -0800 Subject: [PATCH 0738/3753] (#11848) Don't hard code ruby install paths in Windows batch files Previously, the facter.bat file hard coded the path to the ruby installation, making it impossible to move the ruby install directory. This commit changes the script to use the `%~dp0` batch file modifier, which resolves to the drive letter and path of the directory of the batch file being executed. Windows XP and later all support the `%*` modifier, so this commit removes the Win 9x code paths that are not supported. --- install.rb | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/install.rb b/install.rb index 53f7e4ea1b..ab233c35a4 100755 --- a/install.rb +++ b/install.rb @@ -423,16 +423,12 @@ def install_binfile(from, op_file, target) if not installed_wrapper tmp_file2 = File.join(tmp_dir, '_tmp_wrapper') - cwn = File.join(RbConfig::CONFIG['bindir'], op_file) cwv = <<-EOS @echo off -if "%OS%"=="Windows_NT" goto WinNT -#{ruby} -x "#{cwn}" %1 %2 %3 %4 %5 %6 %7 %8 %9 -goto done -:WinNT -#{ruby} -x "#{cwn}" %* -goto done -:done +setlocal +set RUBY_BIN=%~dp0 +set RUBY_BIN=%RUBY_BIN:\\=/% +"%RUBY_BIN%ruby.exe" -x "%RUBY_BIN%facter" %* EOS File.open(tmp_file2, "w") { |cw| cw.puts cwv } FileUtils.install(tmp_file2, File.join(target, "#{op_file}.bat"), :mode => 0755, :verbose => true) From 1df5b46e0ec0bd8a42254b571ca6eb1acd814ed6 Mon Sep 17 00:00:00 2001 From: Ken Barber Date: Thu, 5 Jan 2012 22:54:06 +0000 Subject: [PATCH 0739/3753] (#11566) Add windows support for ec2 facts This patch adds support for detecting ec2 on windows. This works by modifying the linux methodology by using arp -a instead of arp -an and searching for the mac address with a hyphen delimiter (as apposed to a quote). I've added tests and a sample fixtures which adds output from arp -a from a windows machine and linux machines, on ec2 and not on ec2. I've also re-worked the decision making into a util class so the testing is much easier to write and work with, so now we can test the individual mechanism for detecting that we are in a cloud on their own. This will be much better abstracted into their own fact(s) but for now this has the least impact to solve the problem at hand. In the future this logic (and tests) can certainly be re-used if such a fact was evercreated. Thanks to Feifei Jia for contributing the original code. --- lib/facter/ec2.rb | 39 +----- lib/facter/util/ec2.rb | 49 ++++++++ spec/fixtures/unit/util/ec2/linux-arp-ec2.out | 1 + .../unit/util/ec2/linux-arp-not-ec2.out | 5 + .../util/ec2/windows-2008-arp-a-not-ec2.out | 6 + .../unit/util/ec2/windows-2008-arp-a.out | 10 ++ spec/unit/ec2_spec.rb | 117 ++++++++---------- spec/unit/util/ec2_spec.rb | 112 +++++++++++++++++ 8 files changed, 240 insertions(+), 99 deletions(-) create mode 100644 lib/facter/util/ec2.rb create mode 100644 spec/fixtures/unit/util/ec2/linux-arp-ec2.out create mode 100644 spec/fixtures/unit/util/ec2/linux-arp-not-ec2.out create mode 100644 spec/fixtures/unit/util/ec2/windows-2008-arp-a-not-ec2.out create mode 100644 spec/fixtures/unit/util/ec2/windows-2008-arp-a.out create mode 100755 spec/unit/util/ec2_spec.rb diff --git a/lib/facter/ec2.rb b/lib/facter/ec2.rb index 5f712fbf1f..2b28589a38 100644 --- a/lib/facter/ec2.rb +++ b/lib/facter/ec2.rb @@ -1,21 +1,5 @@ -# Original fact Tim Dysinger -# Additional work from KurtBe -# Additional work for Paul Nasrat -# Additional work modelled on Ohai EC2 fact -# Remove depedency on arp fact by Pieter Lexis - +require 'facter/util/ec2' require 'open-uri' -require 'timeout' - -def can_connect?(wait_sec=2) - url = "http://169.254.169.254/" - Timeout::timeout(wait_sec) {open(url)} - return true - rescue Timeout::Error - return false - rescue - return false -end def metadata(id = "") open("http://169.254.169.254/2008-02-01/meta-data/#{id||=''}").read. @@ -40,26 +24,9 @@ def userdata() end end -# Is the macaddress a eucalyptus macaddress? -def has_euca_mac? - !!(Facter.value(:macaddress) =~ %r{^[dD]0:0[dD]:}) -end - -# Is there an entry in the arp table for fe:ff:ff:ff:ff:ff, -# this is a red flag for being on amazon -def has_ec2_arp? - arp_table = Facter::Util::Resolution.exec('arp -an') - is_amazon_arp = false - if not arp_table.nil? - arp_table.each_line do |line| - is_amazon_arp = true if line.include?('fe:ff:ff:ff:ff:ff') - break - end - end - is_amazon_arp -end +if (Facter::Util::EC2.has_euca_mac? || Facter::Util::EC2.has_ec2_arp?) && + Facter::Util::EC2.can_connect? -if (has_euca_mac? || has_ec2_arp?) && can_connect? metadata userdata else diff --git a/lib/facter/util/ec2.rb b/lib/facter/util/ec2.rb new file mode 100644 index 0000000000..548803e02d --- /dev/null +++ b/lib/facter/util/ec2.rb @@ -0,0 +1,49 @@ +require 'timeout' +require 'open-uri' + +# Provide a set of utility static methods that help with resolving the EC2 +# fact. +module Facter::Util::EC2 + class << self + # Test if we can connect to the EC2 api. Return true if able to connect. + # On failure this function fails silently and returns false. + # + # The +wait_sec+ parameter provides you with an adjustable timeout. + # + def can_connect?(wait_sec=2) + url = "http://169.254.169.254/" + Timeout::timeout(wait_sec) {open(url)} + return true + rescue Timeout::Error + return false + rescue + return false + end + + # Test if this host has a mac address used by Eucalyptus clouds, which + # normally is +d0:0d+. + def has_euca_mac? + !!(Facter.value(:macaddress) =~ %r{^[dD]0:0[dD]:}) + end + + # Test if the host has an arp entry in its cache that matches the EC2 arp, + # which is normally +fe:ff:ff:ff:ff:ff+. + def has_ec2_arp? + mac_address = "fe:ff:ff:ff:ff:ff" + if Facter.value(:kernel) == 'windows' + arp_command = "arp -a" + mac_address.gsub!(":","-") + else + arp_command = "arp -an" + end + + arp_table = Facter::Util::Resolution.exec(arp_command) + if not arp_table.nil? + arp_table.each_line do |line| + return true if line.include?(mac_address) + end + end + return false + end + end +end diff --git a/spec/fixtures/unit/util/ec2/linux-arp-ec2.out b/spec/fixtures/unit/util/ec2/linux-arp-ec2.out new file mode 100644 index 0000000000..8c93002741 --- /dev/null +++ b/spec/fixtures/unit/util/ec2/linux-arp-ec2.out @@ -0,0 +1 @@ +? (10.240.93.1) at fe:ff:ff:ff:ff:ff [ether] on eth0 diff --git a/spec/fixtures/unit/util/ec2/linux-arp-not-ec2.out b/spec/fixtures/unit/util/ec2/linux-arp-not-ec2.out new file mode 100644 index 0000000000..c707162a8d --- /dev/null +++ b/spec/fixtures/unit/util/ec2/linux-arp-not-ec2.out @@ -0,0 +1,5 @@ +? (46.4.106.97) at 00:26:88:75:c1:17 [ether] on eth0 +? (10.1.2.14) at 02:00:0a:01:02:0e [ether] on virbr1 +? (10.1.2.12) at 02:00:0a:01:02:0c [ether] on virbr1 +? (10.1.2.11) at 02:00:0a:01:02:0b [ether] on virbr1 +? (10.1.2.200) at 02:00:0a:01:02:0e [ether] on virbr1 diff --git a/spec/fixtures/unit/util/ec2/windows-2008-arp-a-not-ec2.out b/spec/fixtures/unit/util/ec2/windows-2008-arp-a-not-ec2.out new file mode 100644 index 0000000000..92239b584e --- /dev/null +++ b/spec/fixtures/unit/util/ec2/windows-2008-arp-a-not-ec2.out @@ -0,0 +1,6 @@ + +Interface: 192.168.5.4 --- 0xd + Internet Address Physical Address Type + 192.168.5.1 c8-02-14-0c-5d-18 dynamic + 192.168.5.255 ff-ff-ff-ff-ff-ff static + 255.255.255.255 ff-ff-ff-ff-ff-ff static diff --git a/spec/fixtures/unit/util/ec2/windows-2008-arp-a.out b/spec/fixtures/unit/util/ec2/windows-2008-arp-a.out new file mode 100644 index 0000000000..e1623a0e30 --- /dev/null +++ b/spec/fixtures/unit/util/ec2/windows-2008-arp-a.out @@ -0,0 +1,10 @@ + +Interface: 10.32.123.54 --- 0xd + Internet Address Physical Address Type + 10.32.120.163 fe-ff-ff-ff-ff-ff dynamic + 10.32.122.1 fe-ff-ff-ff-ff-ff dynamic + 10.32.123.255 ff-ff-ff-ff-ff-ff static + 169.254.169.254 fe-ff-ff-ff-ff-ff dynamic + 224.0.0.22 01-00-5e-00-00-16 static + 224.0.0.252 01-00-5e-00-00-fc static + 255.255.255.255 ff-ff-ff-ff-ff-ff static \ No newline at end of file diff --git a/spec/unit/ec2_spec.rb b/spec/unit/ec2_spec.rb index 4646ee35e5..61209af7ca 100755 --- a/spec/unit/ec2_spec.rb +++ b/spec/unit/ec2_spec.rb @@ -1,42 +1,35 @@ #!/usr/bin/env rspec require 'spec_helper' +require 'facter/util/ec2' describe "ec2 facts" do + # This is the standard prefix for making an API call in EC2 (or fake) + # environments. let(:api_prefix) { "http://169.254.169.254/" } describe "when running on ec2" do before :each do - # Return fake kernel - Facter.stubs(:value).with(:kernel).returns("Linux") - - # Return something upon connecting to the root so the EC2 code continues - # with its evaluation. - Object.any_instance.expects(:open).with("#{api_prefix}:80/").\ - at_least_once.returns("2008-02-01\nlatest") - - # Return a non-eucalyptus mac address - Facter.expects(:value).with(:macaddress).\ - at_least_once.returns("12:31:39:04:5A:34") - - # Fake EC2 arp response - ec2arp = "? (10.240.93.1) at fe:ff:ff:ff:ff:ff [ether] on eth0\n" - Facter::Util::Resolution.expects(:exec).with("arp -an").\ - at_least_once.returns(ec2arp) + # This is an ec2 instance, not a eucalyptus instance + Facter::Util::EC2.expects(:has_euca_mac?).at_least_once.returns(false) + Facter::Util::EC2.expects(:has_ec2_arp?).at_least_once.returns(true) + + # Assume we can connect + Facter::Util::EC2.expects(:can_connect?).at_least_once.returns(true) end it "should create flat meta-data facts" do - Object.any_instance.expects(:open).\ - with("#{api_prefix}/2008-02-01/meta-data/").\ + Object.any_instance.expects(:open). + with("#{api_prefix}/2008-02-01/meta-data/"). at_least_once.returns(StringIO.new("foo")) - Object.any_instance.expects(:open).\ - with("#{api_prefix}/2008-02-01/meta-data/foo").\ + Object.any_instance.expects(:open). + with("#{api_prefix}/2008-02-01/meta-data/foo"). at_least_once.returns(StringIO.new("bar")) # No user-data - Object.any_instance.expects(:open).\ - with("#{api_prefix}/2008-02-01/user-data/").\ + Object.any_instance.expects(:open). + with("#{api_prefix}/2008-02-01/user-data/"). at_least_once.returns(StringIO.new("")) Facter.collection.loader.load(:ec2) @@ -44,17 +37,17 @@ end it "should create flat meta-data facts with comma seperation" do - Object.any_instance.expects(:open).\ - with("#{api_prefix}/2008-02-01/meta-data/").\ + Object.any_instance.expects(:open). + with("#{api_prefix}/2008-02-01/meta-data/"). at_least_once.returns(StringIO.new("foo")) - Object.any_instance.expects(:open).\ - with("#{api_prefix}/2008-02-01/meta-data/foo").\ + Object.any_instance.expects(:open). + with("#{api_prefix}/2008-02-01/meta-data/foo"). at_least_once.returns(StringIO.new("bar\nbaz")) # No user-data - Object.any_instance.expects(:open).\ - with("#{api_prefix}/2008-02-01/user-data/").\ + Object.any_instance.expects(:open). + with("#{api_prefix}/2008-02-01/user-data/"). at_least_once.returns(StringIO.new("")) Facter.collection.loader.load(:ec2) @@ -62,21 +55,21 @@ end it "should create structured meta-data facts" do - Object.any_instance.expects(:open).\ - with("#{api_prefix}/2008-02-01/meta-data/").\ + Object.any_instance.expects(:open). + with("#{api_prefix}/2008-02-01/meta-data/"). at_least_once.returns(StringIO.new("foo/")) - Object.any_instance.expects(:open).\ - with("#{api_prefix}/2008-02-01/meta-data/foo/").\ + Object.any_instance.expects(:open). + with("#{api_prefix}/2008-02-01/meta-data/foo/"). at_least_once.returns(StringIO.new("bar")) - Object.any_instance.expects(:open).\ - with("#{api_prefix}/2008-02-01/meta-data/foo/bar").\ + Object.any_instance.expects(:open). + with("#{api_prefix}/2008-02-01/meta-data/foo/bar"). at_least_once.returns(StringIO.new("baz")) # No user-data - Object.any_instance.expects(:open).\ - with("#{api_prefix}/2008-02-01/user-data/").\ + Object.any_instance.expects(:open). + with("#{api_prefix}/2008-02-01/user-data/"). at_least_once.returns(StringIO.new("")) Facter.collection.loader.load(:ec2) @@ -85,12 +78,12 @@ it "should create ec2_user_data fact" do # No meta-data - Object.any_instance.expects(:open).\ - with("#{api_prefix}/2008-02-01/meta-data/").\ + Object.any_instance.expects(:open). + with("#{api_prefix}/2008-02-01/meta-data/"). at_least_once.returns(StringIO.new("")) - Object.any_instance.expects(:open).\ - with("#{api_prefix}/2008-02-01/user-data/").\ + Object.any_instance.expects(:open). + with("#{api_prefix}/2008-02-01/user-data/"). at_least_once.returns(StringIO.new("test")) Facter.collection.loader.load(:ec2) @@ -100,17 +93,12 @@ describe "when running on eucalyptus" do before :each do - # Return fake kernel - Facter.stubs(:value).with(:kernel).returns("Linux") - - # Return something upon connecting to the root so the EC2 code continues - # with its evaluation. - Object.any_instance.expects(:open).with("#{api_prefix}:80/").\ - at_least_once.returns("2008-02-01\nlatest") + # Return false for ec2, true for eucalyptus + Facter::Util::EC2.expects(:has_euca_mac?).at_least_once.returns(true) + Facter::Util::EC2.expects(:has_ec2_arp?).never - # Return a eucalyptus mac address - Facter.expects(:value).with(:macaddress).\ - at_least_once.returns("d0:0d:1a:b0:a1:00") + # Assume we can connect + Facter::Util::EC2.expects(:can_connect?).at_least_once.returns(true) end it "should create ec2_user_data fact" do @@ -123,27 +111,30 @@ with("#{api_prefix}/2008-02-01/user-data/").\ at_least_once.returns(StringIO.new("test")) + # Force a fact load Facter.collection.loader.load(:ec2) + Facter.fact(:ec2_userdata).value.should == ["test"] end end - describe "when ec2 url times out" do - before :each do - # Return fake kernel - Facter.stubs(:value).with(:kernel).returns("Linux") + describe "when api connect test fails" do + it "should not populate ec2_userdata" do + # Emulate ec2 for now as it matters little to this test + Facter::Util::EC2.expects(:has_euca_mac?).at_least_once.returns(true) + Facter::Util::EC2.expects(:has_ec2_arp?).never + Facter::Util::EC2.expects(:can_connect?).at_least_once.returns(false) - # Emulate a timeout when connecting by throwing an exception - Object.any_instance.expects(:open).with("#{api_prefix}:80/").\ - at_least_once.raises(Timeout::Error) + # The API should never be called at this point + Object.any_instance.expects(:open). + with("#{api_prefix}/2008-02-01/meta-data/").never + Object.any_instance.expects(:open). + with("#{api_prefix}/2008-02-01/user-data/").never - # Return a eucalyptus mac address - Facter.expects(:value).with(:macaddress).\ - at_least_once.returns("d0:0d:1a:b0:a1:00") - end + # Force a fact load + Facter.collection.loader.load(:ec2) - it "should not raise exception" do - expect { Facter.collection.loader.load(:ec2) }.to_not raise_error + Facter.fact(:ec2_userdata).should == nil end end end diff --git a/spec/unit/util/ec2_spec.rb b/spec/unit/util/ec2_spec.rb new file mode 100755 index 0000000000..e911373517 --- /dev/null +++ b/spec/unit/util/ec2_spec.rb @@ -0,0 +1,112 @@ +#!/usr/bin/env rspec + +require 'spec_helper' +require 'facter/util/ec2' + +describe Facter::Util::EC2 do + # This is the standard prefix for making an API call in EC2 (or fake) + # environments. + let(:api_prefix) { "http://169.254.169.254/" } + + describe "is_ec2_arp? method" do + describe "on linux" do + before :each do + # Return fake kernel + Facter.stubs(:value).with(:kernel).returns("linux") + end + + it "should succeed if arp table contains fe:ff:ff:ff:ff:ff" do + ec2arp = my_fixture_read("linux-arp-ec2.out") + Facter::Util::Resolution.expects(:exec).with("arp -an").\ + at_least_once.returns(ec2arp) + Facter::Util::EC2.has_ec2_arp?.should == true + end + + it "should fail if arp table does not contain fe:ff:ff:ff:ff:ff" do + ec2arp = my_fixture_read("linux-arp-not-ec2.out") + Facter::Util::Resolution.expects(:exec).with("arp -an"). + at_least_once.returns(ec2arp) + Facter::Util::EC2.has_ec2_arp?.should == false + end + end + + describe "on windows" do + before :each do + # Return fake kernel + Facter.stubs(:value).with(:kernel).returns("windows") + end + + it "should succeed if arp table contains fe-ff-ff-ff-ff-ff" do + ec2arp = my_fixture_read("windows-2008-arp-a.out") + Facter::Util::Resolution.expects(:exec).with("arp -a").\ + at_least_once.returns(ec2arp) + Facter::Util::EC2.has_ec2_arp?.should == true + end + + it "should fail if arp table does not contain fe-ff-ff-ff-ff-ff" do + ec2arp = my_fixture_read("windows-2008-arp-a-not-ec2.out") + Facter::Util::Resolution.expects(:exec).with("arp -a"). + at_least_once.returns(ec2arp) + Facter::Util::EC2.has_ec2_arp?.should == false + end + end + end + + describe "is_euca_mac? method" do + it "should return true when the mac is a eucalyptus one" do + Facter.expects(:value).with(:macaddress).\ + at_least_once.returns("d0:0d:1a:b0:a1:00") + + Facter::Util::EC2.has_euca_mac?.should == true + end + + it "should return false when the mac is not a eucalyptus one" do + Facter.expects(:value).with(:macaddress).\ + at_least_once.returns("0c:1d:a0:bc:aa:02") + + Facter::Util::EC2.has_euca_mac?.should == false + end + end + + describe "can_connect? method" do + it "returns true if api responds" do + # Return something upon connecting to the root + Module.any_instance.expects(:open).with("#{api_prefix}:80/").\ + at_least_once.returns("2008-02-01\nlatest") + + Facter::Util::EC2.can_connect?.should == true + end + + describe "when connection times out" do + before :each do + # Emulate a timeout when connecting by throwing an exception + Module.any_instance.expects(:open).with("#{api_prefix}:80/").\ + at_least_once.raises(Timeout::Error) + end + + it "should not raise exception" do + expect { Facter::Util::EC2.can_connect? }.to_not raise_error + end + + it "should return false" do + Facter::Util::EC2.can_connect?.should == false + end + end + + describe "when connection is refused" do + before :each do + # Emulate a connection refused + Module.any_instance.expects(:open).with("#{api_prefix}:80/").\ + at_least_once.raises(Errno::ECONNREFUSED) + end + + it "should not raise exception" do + expect { Facter::Util::EC2.can_connect? }.to_not raise_error + end + + it "should return false" do + Facter::Util::EC2.can_connect?.should == false + end + end + end +end From 729242295f18338e34838f94a17c1641b8ab8344 Mon Sep 17 00:00:00 2001 From: Matthaus Litteken Date: Wed, 18 Jan 2012 10:08:02 -0800 Subject: [PATCH 0740/3753] Updated CHANGELOG and lib/facter.rb for 1.6.5rc1 --- CHANGELOG | 33 ++++++++++++++++++++++++++++++++- lib/facter.rb | 2 +- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 746c457ec8..b4c49913bb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,37 @@ +1.6.5rc1 +=== +1df5b46 (#11566) Add windows support for ec2 facts +d1a33e5 (#11848) Don't hard code ruby install paths in Windows batch files +14cad7e Build a Rake task for building Apple Packages +5a60ca6 (#11559) Switch to RbConfig & Provide alias for RbConfig for pre-1.8.5 +88c9429 (#10271) Identifying 'Amazon' using '/etc/system-release' +2de7b84 (#11661) EC2 rspec tests were using throw not raise to simulate a timeout +b51ccf0 (#11583) Add basic coverage to the ec2 fact +82692ba (#9599) Generalize zone detection +e6cebd3 (#9599) Add nexenta facts +9401b78 (#11583) Switch request method to open-uri monkey patch 'open' +3ccac87 (#9708) Amend requires in specs to use simple requires +b0b5282 (#9708) Confine facts by kernel not operating system and remove confine for hardwareisa +c473e3f (#10309) Remove the with_verbose_disabled method +a99d87c (#10309) Rename tmpfile to tmpfilename to make function clear +d50fc48 (#10309) Move all fixture data in spec/unit/data to spec/fixtures +d6e8523 (#10309) Integrate new PuppetlabsSpec helpers into our existing facter spec code and general spec cleanup +c1604c7 (#10309) Add puppetlabs_spec helper library based on Puppets own puppet_spec helpers +d141e7e (maint) Fix requirement for FileUtils as operatingsystem_spec needs it now +9c224d3 (#11436) Unify memorysize and memorytotal facts +5c6322a (maint) Joined conditional statements for domain +a1dba38 (#11196) Scan all arp entries for an ec2 mac +5cd30eb (#8279) Join ec2 fact output with commas +4633996 (#9789) Extend coverage of operatingsystem specs +6d21f90 Move Linux specific virtual tests to correct block. +cb4e294 (#7753) Added error checking when adding resolves +6201820 (maint) remove redundant arch detection +4f9da1c (#11328) Fix uptime detection on OpenBSD +3f99f16 (#11328) Add virtualisation detection for OpenBSD + 1.6.4 === -6406c8f (##11041) Add dmidecode as a requirement for rpm +6406c8f (#11041) Add dmidecode as a requirement for rpm ed81492 (#10444) Add identification of system boards to Facter bdbb2da (#10885) Malformed facter.bat when ruby dir contains backreferences 0bad18b (#10490) Handle case where no macaddress can be found diff --git a/lib/facter.rb b/lib/facter.rb index 5cbf74de32..552c218d92 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -25,7 +25,7 @@ module Util; end include Comparable include Enumerable - FACTERVERSION = '1.6.4' + FACTERVERSION = '1.6.5' # = Facter # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. From f47c59214fe64611878daba10dc843cf8a0d376d Mon Sep 17 00:00:00 2001 From: Nan Liu Date: Wed, 18 Jan 2012 11:20:40 -0800 Subject: [PATCH 0741/3753] (#1424) Add Solaris zonename fact. --- lib/facter/zonename.rb | 6 ++++++ spec/unit/zonename_spec.rb | 14 ++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 lib/facter/zonename.rb create mode 100644 spec/unit/zonename_spec.rb diff --git a/lib/facter/zonename.rb b/lib/facter/zonename.rb new file mode 100644 index 0000000000..42f577d651 --- /dev/null +++ b/lib/facter/zonename.rb @@ -0,0 +1,6 @@ +require 'facter' + +Facter.add('zonename') do + confine :kernel => :sunos + setcode('zonename') +end diff --git a/spec/unit/zonename_spec.rb b/spec/unit/zonename_spec.rb new file mode 100644 index 0000000000..28915c255c --- /dev/null +++ b/spec/unit/zonename_spec.rb @@ -0,0 +1,14 @@ +#!/usr/bin/env ruby + +require 'spec_helper' +require 'facter' + +describe "zonename fact" do + + it "should return global zone" do + Facter.fact(:kernel).stubs(:value).returns("SunOS") + Facter::Util::Resolution.stubs(:exec).with("zonename").returns('global') + + Facter.fact(:zonename).value.should == "global" + end +end From 26918b3e7f5645e6f0a1d140fd42ba25b7900d29 Mon Sep 17 00:00:00 2001 From: Chris Price Date: Thu, 19 Jan 2012 16:32:02 -0800 Subject: [PATCH 0742/3753] (#12012) Move "with_env" utility method from test code into lib code The utility method "with_env", which was defined locally in loader_spec.rb, is actually more generally useful (and will be used to complete the resolution of #12012). Move it out of the test code and into Facter::Util::Resolution so that it is accessible to production (non-test) code. Refactor loader_spec test accordingly. --- lib/facter/util/resolution.rb | 30 ++++++++++++++++++++++++++++++ spec/unit/util/loader_spec.rb | 26 ++++---------------------- 2 files changed, 34 insertions(+), 22 deletions(-) diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index dc58cb5961..40eeec716b 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -26,6 +26,36 @@ def self.have_which @have_which end + # + # Call this method with a block of code for which you would like to temporarily modify + # one or more environment variables; the specified values will be set for the duration + # of your block, after which the original values (if any) will be restored. + # + # [values] a Hash containing the key/value pairs of any environment variables that you + # would like to temporarily override + def self.with_env(values) + old = {} + values.each do |var, value| + # save the old value if it exists + if old_val = ENV[var] + old[var] = old_val + end + # set the new (temporary) value for the environment variable + ENV[var] = value + end + # execute the caller's block + yield + # restore the old values + values.each do |var, value| + if old.include?(var) + ENV[var] = old[var] + else + # if there was no old value, delete the key from the current environment variables hash + ENV.delete(var) + end + end + end + # Execute a program and return the output of that program. # # Returns nil if the program can't be found, or if there is a problem diff --git a/spec/unit/util/loader_spec.rb b/spec/unit/util/loader_spec.rb index 3a83f16365..765348450a 100755 --- a/spec/unit/util/loader_spec.rb +++ b/spec/unit/util/loader_spec.rb @@ -21,24 +21,6 @@ def load_file(file) Facter::Util::Loader.any_instance.unstub(:load_all) end - def with_env(values) - old = {} - values.each do |var, value| - if old_val = ENV[var] - old[var] = old_val - end - ENV[var] = value - end - yield - values.each do |var, value| - if old.include?(var) - ENV[var] = old[var] - else - ENV.delete(var) - end - end - end - it "should have a method for loading individual facts by name" do Facter::Util::Loader.new.should respond_to(:load) end @@ -76,7 +58,7 @@ def with_env(values) describe "and the FACTERLIB environment variable is set" do it "should include all paths in FACTERLIB" do - with_env "FACTERLIB" => "/one/path:/two/path" do + Facter::Util::Resolution.with_env "FACTERLIB" => "/one/path:/two/path" do paths = @loader.search_path %w{/one/path /two/path}.each do |dir| paths.should be_include(dir) @@ -95,7 +77,7 @@ def with_env(values) it "should load values from the matching environment variable if one is present" do Facter.expects(:add).with("testing") - with_env "facter_testing" => "yayness" do + Facter::Util::Resolution.with_env "facter_testing" => "yayness" do @loader.load(:testing) end end @@ -267,7 +249,7 @@ def with_env(values) Facter.expects(:add).with('one') Facter.expects(:add).with('two') - with_env "facter_one" => "yayness", "facter_two" => "boo" do + Facter::Util::Resolution.with_env "facter_one" => "yayness", "facter_two" => "boo" do @loader.load_all end end @@ -281,7 +263,7 @@ def with_env(values) it "should load facts on the facter search path only once" do facterlibdir = File.expand_path(File.dirname(__FILE__) + '../../../fixtures/unit/util/loader') - with_env 'FACTERLIB' => facterlibdir do + Facter::Util::Resolution.with_env 'FACTERLIB' => facterlibdir do Facter::Util::Loader.new.load_all Facter.value(:nosuchfact).should be_nil end From 810c465653869c6cacca009aaa477ca389314456 Mon Sep 17 00:00:00 2001 From: Chris Price Date: Thu, 19 Jan 2012 17:04:13 -0800 Subject: [PATCH 0743/3753] (#12012) Remove global override of LANG environment variable 'LANG' environment variable must be set to 'C' in order to reliably parse output of certain commands when building up core Facter facts (to prevent locale differences from affecting the format of the output). However, it does not need to happen at a global-ish scope whenever facter is 'require'd; move this behavior from the facter.rb into the local scope of the Facter::Util::Resolution.exec method; restore original value of environment variable after executing any individual command. Note that, while this behavior is better than the previous behavior (global override), it is still not possible for a custom facter fact to be written to expect any other locale besides 'C'. Eventually might prefer to find a solution that allows callers to override this behavior if desired. --- lib/facter.rb | 4 -- lib/facter/util/resolution.rb | 78 +++++++++++++++++++---------------- 2 files changed, 43 insertions(+), 39 deletions(-) diff --git a/lib/facter.rb b/lib/facter.rb index 5cbf74de32..ec7827de78 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -39,10 +39,6 @@ module Util; end # puts Facter['operatingsystem'] # - # Set LANG to force i18n to C - # - ENV['LANG'] = 'C' - GREEN = "" RESET = "" @@debug = 0 diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 40eeec716b..4913fe2dec 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -64,47 +64,55 @@ def self.with_env(values) def self.exec(code, interpreter = nil) Facter.warnonce "The interpreter parameter to 'exec' is deprecated and will be removed in a future version." if interpreter - # Try to guess whether the specified code can be executed by looking at the - # first word. If it cannot be found on the PATH defer on resolving the fact - # by returning nil. - # This only fails on shell built-ins, most of which are masked by stuff in - # /bin or of dubious value anyways. In the worst case, "sh -c 'builtin'" can - # be used to work around this limitation - # - # Windows' %x{} throws Errno::ENOENT when the command is not found, so we - # can skip the check there. This is good, since builtins cannot be found - # elsewhere. - if have_which and !Facter::Util::Config.is_windows? - path = nil - binary = code.split.first - if code =~ /^\// - path = binary - else - path = %x{which #{binary} 2>/dev/null}.chomp - # we don't have the binary necessary - return nil if path == "" or path.match(/Command not found\./) + + ## Set LANG to force i18n to C for the duration of this exec; this ensures that any code that parses the + ## output of the command can expect it to be in a consistent / predictable format / locale + with_env "LANG" => "C" do + + # Try to guess whether the specified code can be executed by looking at the + # first word. If it cannot be found on the PATH defer on resolving the fact + # by returning nil. + # This only fails on shell built-ins, most of which are masked by stuff in + # /bin or of dubious value anyways. In the worst case, "sh -c 'builtin'" can + # be used to work around this limitation + # + # Windows' %x{} throws Errno::ENOENT when the command is not found, so we + # can skip the check there. This is good, since builtins cannot be found + # elsewhere. + if have_which and !Facter::Util::Config.is_windows? + path = nil + binary = code.split.first + if code =~ /^\// + path = binary + else + path = %x{which #{binary} 2>/dev/null}.chomp + # we don't have the binary necessary + return nil if path == "" or path.match(/Command not found\./) + end + + return nil unless FileTest.exists?(path) end - return nil unless FileTest.exists?(path) - end + out = nil - out = nil + begin + out = %x{#{code}}.chomp + rescue Errno::ENOENT => detail + # command not found on Windows + return nil + rescue => detail + $stderr.puts detail + return nil + end - begin - out = %x{#{code}}.chomp - rescue Errno::ENOENT => detail - # command not found on Windows - return nil - rescue => detail - $stderr.puts detail - return nil - end + if out == "" + return nil + else + return out + end - if out == "" - return nil - else - return out end + end # Add a new confine to the resolution mechanism. From 66cbfacf775d400713321099e5e243aff429a2ba Mon Sep 17 00:00:00 2001 From: Chris Price Date: Fri, 20 Jan 2012 11:56:11 -0800 Subject: [PATCH 0744/3753] (#12012) Add spec unit tests for env/LANG changes --- spec/unit/util/resolution_spec.rb | 49 +++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index 17f172b4d1..07217924d6 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -49,6 +49,45 @@ res.limit.should == "testing" end + + describe "when overriding environment variables" do + it "should execute the caller's block with the specified env vars" do + test_env = { "LANG" => "C", "FOO" => "BAR" } + Facter::Util::Resolution.with_env test_env do + test_env.keys.each do |key| + ENV[key].should == test_env[key] + end + end + end + + it "should restore pre-existing environment variables to their previous values" do + orig_env = {} + new_env = {} + # an arbitrary sentinel value to use to temporarily set the environment vars to + sentinel_value = "Abracadabra" + + # grab some values from the existing ENV (arbitrarily choosing 3 here) + ENV.first(3).each do |key, val| + # save the original values so that we can test against them later + orig_env[key] = val + # create bogus temp values for the chosen keys + new_env[key] = sentinel_value + end + + # verify that, during the 'with_env', the new values are used + Facter::Util::Resolution.with_env new_env do + orig_env.keys.each do |key| + ENV[key].should == new_env[key] + end + end + + # verify that, after the 'with_env', the old values are restored + orig_env.keys.each do |key| + ENV[key].should == orig_env[key] + end + end + end + describe "when setting the code" do before do Facter.stubs(:warnonce) @@ -305,5 +344,15 @@ it "should execute the binary" do Facter::Util::Resolution.exec("echo foo").should == "foo" end + + it "should override the LANG environment variable" do + Facter::Util::Resolution.exec("echo $LANG").should == "C" + end + + it "should respect other overridden environment variables" do + Facter::Util::Resolution.with_env( {"FOO" => "foo"} ) do + Facter::Util::Resolution.exec("echo $FOO").should == "foo" + end + end end end From d8be0a6233a59d07dd1497d67faadcf1904dde58 Mon Sep 17 00:00:00 2001 From: Chris Price Date: Fri, 20 Jan 2012 17:15:27 -0800 Subject: [PATCH 0745/3753] (#12012) Fix ruby 1.8.5 incompatibility in new spec test --- spec/unit/util/resolution_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index 07217924d6..f3087381fa 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -67,9 +67,9 @@ sentinel_value = "Abracadabra" # grab some values from the existing ENV (arbitrarily choosing 3 here) - ENV.first(3).each do |key, val| + ENV.keys.first(3).each do |key| # save the original values so that we can test against them later - orig_env[key] = val + orig_env[key] = ENV[key] # create bogus temp values for the chosen keys new_env[key] = sentinel_value end From c93922c26fb8248529ebfd8999e17277f295e697 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Sat, 21 Jan 2012 10:47:38 -0800 Subject: [PATCH 0746/3753] (#12012) Fix ENV['LANG'] spec tests on Windows Previously, the tests were failing because there isn't an `echo.exe` on Windows. This commit just wraps the command in a `cmd.exe` shell and expands the variables using `%LANG%` syntax on Windows. --- spec/unit/util/resolution_spec.rb | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index f3087381fa..b775639a36 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -342,16 +342,22 @@ end it "should execute the binary" do - Facter::Util::Resolution.exec("echo foo").should == "foo" + Facter::Util::Resolution.exec( + Facter::Util::Config.is_windows? ? 'cmd.exe /c "echo foo"' : 'echo foo' + ).should == "foo" end it "should override the LANG environment variable" do - Facter::Util::Resolution.exec("echo $LANG").should == "C" + Facter::Util::Resolution.exec( + Facter::Util::Config.is_windows? ? 'cmd.exe /c "echo %LANG%"' : 'echo $LANG' + ).should == "C" end it "should respect other overridden environment variables" do Facter::Util::Resolution.with_env( {"FOO" => "foo"} ) do - Facter::Util::Resolution.exec("echo $FOO").should == "foo" + Facter::Util::Resolution.exec( + Facter::Util::Config.is_windows? ? 'cmd.exe /c "echo %FOO%"' : 'echo $FOO' + ).should == "foo" end end end From 2fc1b0cfdc8cee917f398d42aab93f2c271c6bd5 Mon Sep 17 00:00:00 2001 From: Tim Sharpe Date: Sun, 22 Jan 2012 17:31:45 +1100 Subject: [PATCH 0747/3753] (#9574) Add filesystem fact & tests Filesystem fact pulled in from https://github.com/kwilczynski/facter-facts/blob/master/filesystems.rb and adjusted to remove the if block that duplicated the behaviour of confine as well as the mutex, while pulling all the logic into the setcode block. http://projects.puppetlabs.com/issues/9574 --- lib/facter/filesystems.rb | 38 +++++++++++++++++++++ spec/fixtures/unit/filesystems/linux | 28 ++++++++++++++++ spec/unit/filesystems_spec.rb | 50 ++++++++++++++++++++++++++++ 3 files changed, 116 insertions(+) create mode 100644 lib/facter/filesystems.rb create mode 100644 spec/fixtures/unit/filesystems/linux create mode 100644 spec/unit/filesystems_spec.rb diff --git a/lib/facter/filesystems.rb b/lib/facter/filesystems.rb new file mode 100644 index 0000000000..a6f028cf5e --- /dev/null +++ b/lib/facter/filesystems.rb @@ -0,0 +1,38 @@ +# +# filesystems.rb +# +# This fact provides an alphabetic list of usable file systems that can +# be used for block devices like hard drives, media cards and so on ... +# +Facter.add('filesystems') do + confine :kernel => :linux + setcode do + # fuseblk can't be created and arguably isn't usable here. If you feel this + # doesn't match your use-case please raise a bug. + exclude = %w(fuseblk) + + # Make regular expression form our patterns ... + exclude = Regexp.union(*exclude.collect { |i| Regexp.new(i) }) + + # We utilise rely on "cat" for reading values from entries under "/proc". + # This is due to some problems with IO#read in Ruby and reading content of + # the "proc" file system that was reported more than once in the past ... + file_systems = [] + Facter::Util::Resolution.exec('cat /proc/filesystems 2> /dev/null').each_line do |line| + # Remove bloat ... + line.strip! + + # Line of interest should not start with "nodev" ... + next if line.empty? or line.match(/^nodev/) + + # We have something, so let us apply our device type filter ... + next if line.match(exclude) + + file_systems << line + end + file_systems.sort.join(',') + end +end + +# vim: set ts=2 sw=2 et : +# encoding: utf-8 diff --git a/spec/fixtures/unit/filesystems/linux b/spec/fixtures/unit/filesystems/linux new file mode 100644 index 0000000000..368f588fbe --- /dev/null +++ b/spec/fixtures/unit/filesystems/linux @@ -0,0 +1,28 @@ +nodev sysfs +nodev rootfs +nodev bdev +nodev proc +nodev cgroup +nodev cpuset +nodev debugfs +nodev securityfs +nodev sockfs +nodev pipefs +nodev anon_inodefs +nodev tmpfs +nodev inotifyfs +nodev devpts + ext3 + ext2 + ext4 +nodev ramfs +nodev hugetlbfs +nodev ecryptfs +nodev fuse + fuseblk +nodev fusectl +nodev mqueue + xfs +nodev binfmt_misc + vfat + iso9660 diff --git a/spec/unit/filesystems_spec.rb b/spec/unit/filesystems_spec.rb new file mode 100644 index 0000000000..832e708e77 --- /dev/null +++ b/spec/unit/filesystems_spec.rb @@ -0,0 +1,50 @@ +#!/usr/bin/env rspec + +require 'spec_helper' + +describe 'Filesystem facts' do + describe 'on non-Linux OS' do + it 'should not exist' do + Facter.fact(:kernel).stubs(:value).returns('SunOS') + Facter.fact(:filesystems).value.should == nil + end + end + + describe 'on Linux' do + before :each do + Facter.fact(:kernel).stubs(:value).returns('Linux') + fixture_data = my_fixture_read('linux') + Facter::Util::Resolution.expects(:exec) \ + .with('cat /proc/filesystems 2> /dev/null').returns(fixture_data) + Facter.collection.loader.load(:filesystems) + end + + after :each do + Facter.clear + end + + it 'should exist' do + Facter.fact(:filesystems).value.should_not == nil + end + + it 'should detect the correct number of filesystems' do + Facter.fact(:filesystems).value.split(',').length.should == 6 + end + + # Check that lines from /proc/filesystems that start with 'nodev' are + # skipped + it 'should not detect sysfs' do + Facter.fact(:filesystems).value.split(',').should_not include('sysfs') + end + + # Check that all other lines are counted as valid filesystems + it 'should detect ext4' do + Facter.fact(:filesystems).value.split(',').should include('ext4') + end + + # fuseblk is never included in the filesystem list + it 'should not detect fuseblk' do + Facter.fact(:filesystems).value.split(',').should_not include('fuseblk') + end + end +end From cb598aab3e36767abd6ce5827e47099ecb49ef67 Mon Sep 17 00:00:00 2001 From: Jeremy Katz Date: Tue, 27 Dec 2011 15:57:04 -0500 Subject: [PATCH 0748/3753] Support EC2 facts on OpenStack OpenStack exports an EC2 compatible API, so make the information available via facts by knowing that OpenStack generates mac addresses beginning with 02:16:3E --- lib/facter/ec2.rb | 4 ++-- lib/facter/util/ec2.rb | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/facter/ec2.rb b/lib/facter/ec2.rb index 2b28589a38..98c957f7dc 100644 --- a/lib/facter/ec2.rb +++ b/lib/facter/ec2.rb @@ -24,8 +24,8 @@ def userdata() end end -if (Facter::Util::EC2.has_euca_mac? || Facter::Util::EC2.has_ec2_arp?) && - Facter::Util::EC2.can_connect? +if (Facter::Util::EC2.has_euca_mac? || Facter::Util::EC2.has_openstack_mac? || + Facter::Util::EC2.has_ec2_arp?) && Facter::Util::EC2.can_connect? metadata userdata diff --git a/lib/facter/util/ec2.rb b/lib/facter/util/ec2.rb index 548803e02d..2ed6517074 100644 --- a/lib/facter/util/ec2.rb +++ b/lib/facter/util/ec2.rb @@ -26,6 +26,12 @@ def has_euca_mac? !!(Facter.value(:macaddress) =~ %r{^[dD]0:0[dD]:}) end + # Test if this host has a mac address used by OpenStack, which + # normally starts with 02:16:3E + def has_openstack_mac? + !!(Facter.value(:macaddress) =~ %r{^02:16:3[eE]}) + end + # Test if the host has an arp entry in its cache that matches the EC2 arp, # which is normally +fe:ff:ff:ff:ff:ff+. def has_ec2_arp? From 7f2a0e27e43efcb4210c7f3af2c17fccbc09fd24 Mon Sep 17 00:00:00 2001 From: Jeremy Katz Date: Sun, 22 Jan 2012 22:25:53 -0500 Subject: [PATCH 0749/3753] add a simple test for openstack ec2 facts --- spec/unit/ec2_spec.rb | 30 ++++++++++++++++++++++++++++++ spec/unit/util/ec2_spec.rb | 16 ++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/spec/unit/ec2_spec.rb b/spec/unit/ec2_spec.rb index 61209af7ca..01c54e7442 100755 --- a/spec/unit/ec2_spec.rb +++ b/spec/unit/ec2_spec.rb @@ -12,6 +12,7 @@ before :each do # This is an ec2 instance, not a eucalyptus instance Facter::Util::EC2.expects(:has_euca_mac?).at_least_once.returns(false) + Facter::Util::EC2.expects(:has_openstack_mac?).at_least_once.returns(false) Facter::Util::EC2.expects(:has_ec2_arp?).at_least_once.returns(true) # Assume we can connect @@ -95,6 +96,35 @@ before :each do # Return false for ec2, true for eucalyptus Facter::Util::EC2.expects(:has_euca_mac?).at_least_once.returns(true) + Facter::Util::EC2.expects(:has_openstack_mac?).at_least_once.returns(false) + Facter::Util::EC2.expects(:has_ec2_arp?).never + + # Assume we can connect + Facter::Util::EC2.expects(:can_connect?).at_least_once.returns(true) + end + + it "should create ec2_user_data fact" do + # No meta-data + Object.any_instance.expects(:open).\ + with("#{api_prefix}/2008-02-01/meta-data/").\ + at_least_once.returns(StringIO.new("")) + + Object.any_instance.expects(:open).\ + with("#{api_prefix}/2008-02-01/user-data/").\ + at_least_once.returns(StringIO.new("test")) + + # Force a fact load + Facter.collection.loader.load(:ec2) + + Facter.fact(:ec2_userdata).value.should == ["test"] + end + end + + describe "when running on openstack" do + before :each do + # Return false for ec2, true for eucalyptus + Facter::Util::EC2.expects(:has_openstack_mac?).at_least_once.returns(true) + Facter::Util::EC2.expects(:has_euca_mac?).at_least_once.returns(false) Facter::Util::EC2.expects(:has_ec2_arp?).never # Assume we can connect diff --git a/spec/unit/util/ec2_spec.rb b/spec/unit/util/ec2_spec.rb index e911373517..62fdcb744c 100755 --- a/spec/unit/util/ec2_spec.rb +++ b/spec/unit/util/ec2_spec.rb @@ -68,6 +68,22 @@ end end + describe "is_openstack_mac? method" do + it "should return true when the mac is an openstack one" do + Facter.expects(:value).with(:macaddress).\ + at_least_once.returns("02:16:3e:54:89:fd") + + Facter::Util::EC2.has_openstack_mac?.should == true + end + + it "should return false when the mac is not a openstack one" do + Facter.expects(:value).with(:macaddress).\ + at_least_once.returns("0c:1d:a0:bc:aa:02") + + Facter::Util::EC2.has_openstack_mac?.should == false + end + end + describe "can_connect? method" do it "returns true if api responds" do # Return something upon connecting to the root From 7d3889d51fd6ae0b33c14f17b0ab33fb17db8ba3 Mon Sep 17 00:00:00 2001 From: Daniel Pittman Date: Mon, 23 Jan 2012 00:14:19 -0800 Subject: [PATCH 0750/3753] (#12079) Fix order-dependent test failure due to odd stubbing. The submitted tests worked fine until the order they ran in was perturbed; after that, bad expectations were shown. This corrects that subtle failure. Signed-off-by: Daniel Pittman --- spec/unit/ec2_spec.rb | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/spec/unit/ec2_spec.rb b/spec/unit/ec2_spec.rb index 01c54e7442..7c7a3d137b 100755 --- a/spec/unit/ec2_spec.rb +++ b/spec/unit/ec2_spec.rb @@ -11,12 +11,12 @@ describe "when running on ec2" do before :each do # This is an ec2 instance, not a eucalyptus instance - Facter::Util::EC2.expects(:has_euca_mac?).at_least_once.returns(false) - Facter::Util::EC2.expects(:has_openstack_mac?).at_least_once.returns(false) - Facter::Util::EC2.expects(:has_ec2_arp?).at_least_once.returns(true) + Facter::Util::EC2.stubs(:has_euca_mac?).returns(false) + Facter::Util::EC2.stubs(:has_openstack_mac?).returns(false) + Facter::Util::EC2.stubs(:has_ec2_arp?).returns(true) # Assume we can connect - Facter::Util::EC2.expects(:can_connect?).at_least_once.returns(true) + Facter::Util::EC2.stubs(:can_connect?).returns(true) end it "should create flat meta-data facts" do @@ -95,12 +95,12 @@ describe "when running on eucalyptus" do before :each do # Return false for ec2, true for eucalyptus - Facter::Util::EC2.expects(:has_euca_mac?).at_least_once.returns(true) - Facter::Util::EC2.expects(:has_openstack_mac?).at_least_once.returns(false) - Facter::Util::EC2.expects(:has_ec2_arp?).never + Facter::Util::EC2.stubs(:has_euca_mac?).returns(true) + Facter::Util::EC2.stubs(:has_openstack_mac?).returns(false) + Facter::Util::EC2.stubs(:has_ec2_arp?).returns(false) # Assume we can connect - Facter::Util::EC2.expects(:can_connect?).at_least_once.returns(true) + Facter::Util::EC2.stubs(:can_connect?).returns(true) end it "should create ec2_user_data fact" do @@ -123,12 +123,12 @@ describe "when running on openstack" do before :each do # Return false for ec2, true for eucalyptus - Facter::Util::EC2.expects(:has_openstack_mac?).at_least_once.returns(true) - Facter::Util::EC2.expects(:has_euca_mac?).at_least_once.returns(false) - Facter::Util::EC2.expects(:has_ec2_arp?).never + Facter::Util::EC2.stubs(:has_openstack_mac?).returns(true) + Facter::Util::EC2.stubs(:has_euca_mac?).returns(false) + Facter::Util::EC2.stubs(:has_ec2_arp?).returns(false) # Assume we can connect - Facter::Util::EC2.expects(:can_connect?).at_least_once.returns(true) + Facter::Util::EC2.stubs(:can_connect?).returns(true) end it "should create ec2_user_data fact" do @@ -151,8 +151,8 @@ describe "when api connect test fails" do it "should not populate ec2_userdata" do # Emulate ec2 for now as it matters little to this test - Facter::Util::EC2.expects(:has_euca_mac?).at_least_once.returns(true) - Facter::Util::EC2.expects(:has_ec2_arp?).never + Facter::Util::EC2.stubs(:has_euca_mac?).returns(true) + Facter::Util::EC2.stubs(:has_ec2_arp?).never Facter::Util::EC2.expects(:can_connect?).at_least_once.returns(false) # The API should never be called at this point From 71d3d3d64bf66cc5ba51903589f83635e9966c85 Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Mon, 23 Jan 2012 15:26:49 -0800 Subject: [PATCH 0751/3753] (#12077) Add pciutils RPM dependency This patch adds pciutils as a dependency to the redhat spec file facter/conf/redhat. Facter uses both lspci and dmidecode to determine the virtual/is_virtual facts/detection of hypervisors, so it should also depend on the pciutils package. Also reference RHBZ# 783749 for addition of the same to fedora, originally noted by Dominic Cleal. Signed-off-by: Moses Mendoza --- conf/redhat/facter.spec | 1 + 1 file changed, 1 insertion(+) diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index 8a47325f36..ec77957594 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -22,6 +22,7 @@ BuildArch: noarch Requires: ruby >= 1.8.1 Requires: which Requires: dmidecode +Requires: pciutils %if %has_ruby_abi Requires: ruby(abi) = 1.8 %endif From 2e0e573f1fd856a90cc93abd97fbf18b3b082b1a Mon Sep 17 00:00:00 2001 From: Matthaus Litteken Date: Wed, 25 Jan 2012 19:26:12 +0000 Subject: [PATCH 0752/3753] Updated CHANGELOG for 1.6.5 release. --- CHANGELOG | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index b4c49913bb..55b29f5e1b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,6 @@ -1.6.5rc1 +1.6.5 === +71d3d3d (#12077) Add pciutils RPM dependency 1df5b46 (#11566) Add windows support for ec2 facts d1a33e5 (#11848) Don't hard code ruby install paths in Windows batch files 14cad7e Build a Rake task for building Apple Packages From 9b5cb2693ade7ead224a8763ba55307e91d0cad8 Mon Sep 17 00:00:00 2001 From: Matthaus Litteken Date: Wed, 25 Jan 2012 12:25:28 -0800 Subject: [PATCH 0753/3753] Updating conf/redhat/facter.spec for 1.6.5 release. --- conf/redhat/facter.spec | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index ec77957594..94f5d1b90d 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -1,17 +1,19 @@ -%{!?ruby_sitelibdir: %define ruby_sitelibdir %(ruby -rrbconfig -e 'puts Object.const_get(defined?(RbConfig) ? :RbConfig : :Config)::CONFIG["sitelibdir"]' +%{!?ruby_sitelibdir: %define ruby_sitelibdir %(ruby -rrbconfig -e 'puts Object.const_get(defined?(RbConfig) ? :RbConfig : :Config)::CONFIG["sitelibdir"]')} %define has_ruby_abi 0%{?fedora} || 0%{?rhel} >= 5 %define has_ruby_noarch %has_ruby_abi -%global _ver 1.6.4 +%global _ver 1.6.5 Summary: Ruby module for collecting simple facts about a host operating system Name: facter -Version: 1.6.4 -Release: 0.1rc1%{?dist} +Version: 1.6.5 +Release: 1%{?dist} +#Release: 0.1rc1%{?dist} License: Apache 2.0 Group: System Environment/Base -URL: http://www.puppetlabs.com/puppet/related-projects/%{name}/ -Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc1.tar.gz +URL: http://www.puppetlabs.com/puppet/related-projects/%{name} +#Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc1.tar.gz +Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}.tar.gz #Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc1.tar.gz.asc BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) @@ -34,7 +36,8 @@ system. Some of the facts are preconfigured, such as the hostname and the operating system. Additional facts can be added through simple Ruby scripts %prep -%setup -q -n %{name}-%{version}rc1 +%setup -q -n %{name}-%{version} +#%setup -q -n %{name}-%{version}rc1 %build @@ -55,6 +58,9 @@ rm -rf %{buildroot} %changelog +* Wed Jan 25 2012 Matthaus Litteken - 1.6.5-1 +- Update to 1.6.5 + * Wed Nov 30 2011 Matthaus Litteken - 1.6.4-0.1rc1 - 1.6.4 rc1 From 5c5c33073e299d36a3d332575950193910879cbc Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Wed, 25 Jan 2012 12:53:22 -0800 Subject: [PATCH 0754/3753] Changes apple rake task to reflect package name facter instead of puppet. Signed-off-by: Moses Mendoza --- tasks/rake/apple.rake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tasks/rake/apple.rake b/tasks/rake/apple.rake index 0e6e9eb6a6..e08e5a7d7c 100644 --- a/tasks/rake/apple.rake +++ b/tasks/rake/apple.rake @@ -23,8 +23,8 @@ SED = '/usr/bin/sed' # Setup task to populate all the variables task :setup do @version = `git describe`.chomp - @title = "puppet-#{@version}" - @reverse_domain = 'com.puppetlabs.puppet' + @title = "facter-#{@version}" + @reverse_domain = 'com.puppetlabs.facter' @package_major_version = @version.split('.')[0] @package_minor_version = @version.split('.')[1] + @version.split('.')[2].split('-')[0].split('rc')[0] From 9c2afdb5dfcbf0aaee80b5c90e40a40f9cf1a4ef Mon Sep 17 00:00:00 2001 From: Chris Price Date: Wed, 25 Jan 2012 11:18:51 -0800 Subject: [PATCH 0755/3753] (#12012) Fix bug with overriding LANG environment variable There is a yields? method called with_env which is intended to temporarily override environment variables and then restore them to their previous state after executing the caller's block of code. The Facter::Util::Resolution.exec() method uses this, but the yield block contained several occurrences of the "return" statement; this statement breaks out of the rest of the execution of the yields? method, and thus the environment variables were never being restored by the final stanza of the with_env method. This changeset fixes that bug. Also adds a spec test to reproduce/verify. --- lib/facter/util/resolution.rb | 28 +++++++++++++++------ spec/unit/util/resolution_spec.rb | 41 ++++++++++++++++++++++++------- 2 files changed, 52 insertions(+), 17 deletions(-) diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 4913fe2dec..70a0585d79 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -43,8 +43,8 @@ def self.with_env(values) # set the new (temporary) value for the environment variable ENV[var] = value end - # execute the caller's block - yield + # execute the caller's block, capture the return value + rv = yield # restore the old values values.each do |var, value| if old.include?(var) @@ -54,6 +54,8 @@ def self.with_env(values) ENV.delete(var) end end + # return the captured return value + rv end # Execute a program and return the output of that program. @@ -87,10 +89,14 @@ def self.exec(code, interpreter = nil) else path = %x{which #{binary} 2>/dev/null}.chomp # we don't have the binary necessary - return nil if path == "" or path.match(/Command not found\./) + # we need to use "next" here, rather than "return"... because "return" would break out of the with_env wrapper + # without performing the necessary cleanup of environment variables + next nil if path == "" or path.match(/Command not found\./) end - return nil unless FileTest.exists?(path) + # we need to use "next" here, rather than "return"... because "return" would break out of the with_env wrapper + # without performing the necessary cleanup of environment variables + next nil unless FileTest.exists?(path) end out = nil @@ -99,16 +105,22 @@ def self.exec(code, interpreter = nil) out = %x{#{code}}.chomp rescue Errno::ENOENT => detail # command not found on Windows - return nil + # we need to use "next" here, rather than "return"... because "return" would break out of the with_env wrapper + # without performing the necessary cleanup of environment variables + next nil rescue => detail $stderr.puts detail - return nil + # we need to use "next" here, rather than "return"... because "return" would break out of the with_env wrapper + # without performing the necessary cleanup of environment variables + next nil end + # we need to use "next" here, rather than "return"... because "return" would break out of the with_env wrapper + # without performing the necessary cleanup of environment variables if out == "" - return nil + next nil else - return out + next out end end diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index b775639a36..4788d45722 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -336,28 +336,51 @@ # It's not possible, AFAICT, to mock %x{}, so I can't really test this bit. describe "when executing code" do + # set up some command strings, making sure we get the right version for both unix and windows + echo_command = Facter::Util::Config.is_windows? ? 'cmd.exe /c "echo foo"' : 'echo foo' + echo_env_var_command = Facter::Util::Config.is_windows? ? 'cmd.exe /c "echo %%%s%%"' : 'echo $%s' + it "should deprecate the interpreter parameter" do Facter.expects(:warnonce).with("The interpreter parameter to 'exec' is deprecated and will be removed in a future version.") Facter::Util::Resolution.exec("/something", "/bin/perl") end + # execute a simple echo command it "should execute the binary" do - Facter::Util::Resolution.exec( - Facter::Util::Config.is_windows? ? 'cmd.exe /c "echo foo"' : 'echo foo' - ).should == "foo" + Facter::Util::Resolution.exec(echo_command).should == "foo" end it "should override the LANG environment variable" do - Facter::Util::Resolution.exec( - Facter::Util::Config.is_windows? ? 'cmd.exe /c "echo %LANG%"' : 'echo $LANG' - ).should == "C" + Facter::Util::Resolution.exec(echo_env_var_command % 'LANG').should == "C" end it "should respect other overridden environment variables" do Facter::Util::Resolution.with_env( {"FOO" => "foo"} ) do - Facter::Util::Resolution.exec( - Facter::Util::Config.is_windows? ? 'cmd.exe /c "echo %FOO%"' : 'echo $FOO' - ).should == "foo" + Facter::Util::Resolution.exec(echo_env_var_command % 'FOO').should == "foo" + end + end + + it "should restore overridden LANG environment variable after execution" do + # we're going to call with_env in a nested fashion, to make sure that the environment gets restored properly + # at each level + Facter::Util::Resolution.with_env( {"LANG" => "foo"} ) do + # Resolution.exec always overrides 'LANG' for its own execution scope + Facter::Util::Resolution.exec(echo_env_var_command % 'LANG').should == "C" + # But after 'exec' completes, we should see our value restored + ENV['LANG'].should == "foo" + # Now we'll do a nested call to with_env + Facter::Util::Resolution.with_env( {"LANG" => "bar"} ) do + # During 'exec' it should still be 'C' + Facter::Util::Resolution.exec(echo_env_var_command % 'LANG').should == "C" + # After exec it should be restored to our current value for this level of the nesting... + ENV['LANG'].should == "bar" + end + # Now we've dropped out of one level of nesting, + ENV['LANG'].should == "foo" + # Call exec one more time just for kicks + Facter::Util::Resolution.exec(echo_env_var_command % 'LANG').should == "C" + # One last check at our current nesting level. + ENV['LANG'].should == "foo" end end end From 4595c48ff9ebee2b54dd95badab1a402a4788eb1 Mon Sep 17 00:00:00 2001 From: Chris Price Date: Wed, 25 Jan 2012 15:43:17 -0800 Subject: [PATCH 0756/3753] (#12012) removed extraneous nils --- lib/facter/util/resolution.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 70a0585d79..690a8cb685 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -91,12 +91,12 @@ def self.exec(code, interpreter = nil) # we don't have the binary necessary # we need to use "next" here, rather than "return"... because "return" would break out of the with_env wrapper # without performing the necessary cleanup of environment variables - next nil if path == "" or path.match(/Command not found\./) + next if path == "" or path.match(/Command not found\./) end # we need to use "next" here, rather than "return"... because "return" would break out of the with_env wrapper # without performing the necessary cleanup of environment variables - next nil unless FileTest.exists?(path) + next unless FileTest.exists?(path) end out = nil @@ -107,18 +107,18 @@ def self.exec(code, interpreter = nil) # command not found on Windows # we need to use "next" here, rather than "return"... because "return" would break out of the with_env wrapper # without performing the necessary cleanup of environment variables - next nil + next rescue => detail $stderr.puts detail # we need to use "next" here, rather than "return"... because "return" would break out of the with_env wrapper # without performing the necessary cleanup of environment variables - next nil + next end # we need to use "next" here, rather than "return"... because "return" would break out of the with_env wrapper # without performing the necessary cleanup of environment variables if out == "" - next nil + next else next out end From f6bbe14bb0d0f9da8f4107e9c1f90bb1df0a937a Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Fri, 27 Jan 2012 11:33:05 -0800 Subject: [PATCH 0757/3753] (#12170) Adds gem spec description Without this patch, the gem spec file is missing a description attribute, which causes a warning message when generating the gem. This patch adds a description attribute to the spec with the facter tagline 'You can prove anything with facts!' Signed-off-by: Moses Mendoza --- Rakefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Rakefile b/Rakefile index 380416bb9f..063c259d5b 100644 --- a/Rakefile +++ b/Rakefile @@ -38,6 +38,7 @@ spec = Gem::Specification.new do |spec| spec.executables = %w{facter} spec.version = Facter::FACTERVERSION spec.summary = 'Facter, a system inventory tool' + spec.description = 'You can prove anything with facts!' spec.author = 'Puppet Labs' spec.email = 'info@puppetlabs.com' spec.homepage = 'http://puppetlabs.com/' From 2a862191137370cf16c2d284fe60cc9bed0e9cd8 Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Wed, 1 Feb 2012 18:38:28 +1100 Subject: [PATCH 0758/3753] (#11660) Adds feature SSHFP (#11659) Adds ssh spec RFC4255 SSH fingerprints are now facts in the form SSHFP_RSA,SSHFP_DSA, SSHFP_ECDSA. This also performs SHA2 and ECDSA that are part of the draft: http://tools.ietf.org/id/draft-os-ietf-sshfp-ecdsa-sha2 (www.iana.org/assignments/dns-sshfp-rr-parameters). Adds test cases for the ssh fact module. Reworks a bit of the ssh fact implementation so that the test framework can work. --- lib/facter/ssh.rb | 28 +++++++++++++++++++++--- spec/unit/ssh_spec.rb | 51 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 3 deletions(-) create mode 100755 spec/unit/ssh_spec.rb diff --git a/lib/facter/ssh.rb b/lib/facter/ssh.rb index 6aa7e080a8..23c65783e1 100644 --- a/lib/facter/ssh.rb +++ b/lib/facter/ssh.rb @@ -12,14 +12,14 @@ ## ["/etc/ssh","/usr/local/etc/ssh","/etc","/usr/local/etc"].each do |dir| - {"SSHDSAKey" => "ssh_host_dsa_key.pub", "SSHRSAKey" => "ssh_host_rsa_key.pub", "SSHECDSAKey" => "ssh_host_ecdsa_key.pub"}.each do |name,file| + {"SSHDSAKey" => { :file => "ssh_host_dsa_key.pub", :sshfprrtype => 2 } , "SSHRSAKey" => { :file => "ssh_host_rsa_key.pub", :sshfprrtype => 1 }, "SSHECDSAKey" => { :file => "ssh_host_ecdsa_key.pub", :sshfprrtype => 3 } }.each do |name,key| Facter.add(name) do setcode do value = nil - filepath = File.join(dir,file) + filepath = File.join(dir,key[:file]) if FileTest.file?(filepath) begin - File.open(filepath) { |f| value = f.read.chomp.split(/\s+/)[1] } + value = File.read(filepath).chomp.split(/\s+/)[1] rescue value = nil end @@ -27,5 +27,27 @@ value end # end of proc end # end of add + Facter.add('SSHFP_' + name[3..-4]) do + setcode do + ssh = Facter.fact(name).value + value = nil + if ssh && key[:sshfprrtype] + begin + require 'digest/sha1' + require 'base64' + digest = Base64.decode64(ssh) + value = 'SSHFP ' + key[:sshfprrtype].to_s + ' 1 ' + Digest::SHA1.hexdigest(digest) + begin + require 'digest/sha2' + value += "\nSSHFP " + key[:sshfprrtype].to_s + ' 2 ' + Digest::SHA256.hexdigest(digest) + rescue + end + rescue + value = nil + end + end # end of sshfp if + value + end # end of sshfp proc + end # end of sshfp add end # end of hash each end # end of dir each diff --git a/spec/unit/ssh_spec.rb b/spec/unit/ssh_spec.rb new file mode 100755 index 0000000000..79dc025451 --- /dev/null +++ b/spec/unit/ssh_spec.rb @@ -0,0 +1,51 @@ +#!/usr/bin/env rspec + +require 'spec_helper' +require 'facter/ssh' +require 'pathname' + +describe "SSH fact" do + + before do + # We need these facts loaded, but they belong to a file with a + # different name, so load the file explicitly. + Facter.collection.loader.load(:ssh) + end + + # fingerprints extracted from ssh-keygen -r '' -f /etc/ssh/ssh_host_dsa_key.pub + { 'SSHRSAKey' => [ '/usr/local/etc/ssh_host_rsa_key.pub' , "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDrs+KtR8hjasELsyCiiBplUeIi77hEHzTSQt1ALG7N4IgtMg27ZAcq0tl2/O9ZarQuClc903pgionbM9Q98CtAIoqgJwdtsor7ETRmzwrcY/mvI7ne51UzQy4Eh9WrplfpNyg+EVO0FUC7mBcay6JY30QKasePp+g4MkwK5cuTzOCzd9up9KELonlH7tTm2L0YI4HhZugwVoTFulCAZvPICxSk1B/fEKyGSZVfY/UxZNqg9g2Wyvq5u40xQ5eO882UwhB3w4IbmRnPKcyotAcqOJxA7hToMKtEmFct+vjHE8T37w8axE/1X9mdvy8IZbkEBL1cupqqb8a8vU1QTg1z", "SSHFP 1 1 1e4f163a1747d0d1a08a29972c9b5d94ee5705d0\nSSHFP 1 2 4e834c91e423d6085ed6dfb880a59e2f1b04f17c1dc17da07708af67c5ab6045" ], + 'SSHDSAKey' => [ '/etc/ssh/ssh_host_dsa_key.pub' , "ssh-dss AAAAB3NzaC1kc3MAAACBAKjmRez14aZT6OKhHrsw19s7u30AdghwHFQbtC+L781YjJ3UV0/WQoZ8NaDL4ovuvW23RuO49tsqSNcVHg+PtRiN2iTVAS2h55TFhaPKhTs+i0NH3p3Ze8LNSYuz8uK7a+nTxysz47GYTHiE1ke8KXe5wGKDO1TO/MUgpDbwx72LAAAAFQD9yMJCnZMiKzA7J1RNkwvgCyBKSQAAAIAtWBAsuRM0F2fdCe+F/JmgyryQmRIT5vP8E1ww3t3ywdLHklN7UMkaEKBW/TN/jj1JOGXtZ2v5XI+0VNoNKD/7dnCGzNViRT/jjfyVi6l5UMg4Q52Gv0RXJoBJpxNqFOU2niSsy8hioyE39W6LJYWJtQozGpH/KKgkCSvxBn5hlAAAAIB1yo/YD0kQICOO0KE+UMMaKtV7FwyedFJsxsWYwZfHXGwWskf0d2+lPhd9qwdbmSvySE8Qrlvu+W+X8AipwGkItSnj16ORF8kO3lfABa+7L4BLDtumt7ybjBPcHOy3n28dd07TmMtyWvLjOb0mcxPo+TwDLtHd3L/3C1Dh41jRPg==\n", "SSHFP 2 1 f63dfe8da99f50ffbcfa40a61161cee29d109f70\nSSHFP 2 2 5f57aa6be9baddd71b6049ed5d8639664a7ddf92ce293e3887f16ad0f2d459d9" ], + 'SSHECDSAKey' => [ '/etc/ssh_host_ecdsa_key.pub' , 'ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBIuKHtgXQUIrXSVNKC7uY+ZOF7jjfqYNU7Cb/IncDOZ7jW44dxsfBzRJwS5sTHERjBinJskY87mmwY07NFF5GoE=', "SSHFP 3 1 091a088fd3500ad9e35ce201c5101646cbf6ff98\nSSHFP 3 2 1dd2aa8f29b539337316e2862b28c196c68ffe0af78fccf9e50625635677e50f"] + }.each_pair do |fact, data| + filename, contents, fingerprint = data + pk = /AAAA\S+/.match(contents).to_s + it "'#{fact}' should be '#{pk}' based on '#{filename}' contents '#{contents}'" do + File.expects(:read).with(filename).at_least_once.returns contents + path, file = Pathname.new(filename).split + ["/etc/ssh","/usr/local/etc/ssh","/etc","/usr/local/etc"].each do |dir| + if dir != path.to_s + blockfile = (Pathname.new(dir) + file).to_s + FileTest.expects(:file?).with( blockfile ).at_least(0).returns false + end + end + FileTest.expects(:file?).with( filename ).at_least_once.returns true + + Facter.fact(fact).value.should == pk + end + fp_fact = 'SSHFP_' + fact[3..-4] + it "'#{fp_fact}' should have fingerprint '#{fingerprint}' based on '#{filename}' contents '#{contents}'" do + File.expects(:read).with(filename).at_least_once.returns contents + path, file = Pathname.new(filename).split + ["/etc/ssh","/usr/local/etc/ssh","/etc","/usr/local/etc"].each do |dir| + if dir != path.to_s + blockfile = (Pathname.new(dir) + file).to_s + FileTest.expects(:file?).with( blockfile ).at_least(0).returns false + end + end + FileTest.expects(:file?).with( filename ).at_least_once.returns true + + Facter.fact(fp_fact).value.should == fingerprint + end + end + +end From c218d841deea0dfd455178a62e97206704360884 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Wed, 1 Feb 2012 12:13:28 -0800 Subject: [PATCH 0759/3753] (#12362) Use Tempfile to generate temp files Previously, facter used ENV['TMP'], ENV['TEMP'], /tmp, etc as it's temp directory search path, using the first one that existed. It then used constant file names within the temp directory to re-write the files in ruby's bin directory, and bat wrappers on Windows. First, it leads to predictable temp file names, which is bad. Second, when installing facter via a non-interactive ssh shell, e.g. ssh ruby install.rb which is what the acceptance test harness does, the TMP and TEMP environment variables are usually not defined. So facter was always defaulting to /tmp, which doesn't work when installing facter on Windows agents during acceptance tests. This commit just changes the install script to use ruby's Tempfile to generate secure temp files that works in non-interactive shells. --- install.rb | 30 +++++++++--------------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/install.rb b/install.rb index ab233c35a4..c5c3a11d88 100755 --- a/install.rb +++ b/install.rb @@ -37,6 +37,7 @@ require 'fileutils' require 'optparse' require 'ostruct' +require 'tempfile' begin require 'rdoc/rdoc' @@ -217,8 +218,6 @@ def prepare_installation opts.parse! end - tmpdirs = [ENV['TMP'], ENV['TEMP'], "/tmp", "/var/tmp", "."] - version = [RbConfig::CONFIG["MAJOR"], RbConfig::CONFIG["MINOR"]].join(".") libdir = File.join(RbConfig::CONFIG["libdir"], "ruby", version) @@ -289,9 +288,6 @@ def prepare_installation FileUtils.makedirs(sitelibdir) end - tmpdirs << bindir - - InstallOptions.tmp_dirs = tmpdirs.compact InstallOptions.site_dir = sitelibdir InstallOptions.bin_dir = bindir InstallOptions.sbin_dir = sbindir @@ -384,20 +380,12 @@ def run_tests(test_list) # (e.g., bin/rdoc becomes rdoc); the shebang line handles running it. Under # windows, we add an '.rb' extension and let file associations do their stuff. def install_binfile(from, op_file, target) - tmp_dir = nil - InstallOptions.tmp_dirs.each do |t| - if File.directory?(t) and File.writable?(t) - tmp_dir = t - break - end - end + tmp_file = Tempfile.new('facter-binfile') - fail "Cannot find a temporary directory" unless tmp_dir - tmp_file = File.join(tmp_dir, '_tmp') ruby = File.join(RbConfig::CONFIG['bindir'], RbConfig::CONFIG['ruby_install_name']) File.open(from) do |ip| - File.open(tmp_file, "w") do |op| + File.open(tmp_file.path, "w") do |op| ruby = File.join(RbConfig::CONFIG['bindir'], RbConfig::CONFIG['ruby_install_name']) op.puts "#!#{ruby}" contents = ip.readlines @@ -422,7 +410,7 @@ def install_binfile(from, op_file, target) end if not installed_wrapper - tmp_file2 = File.join(tmp_dir, '_tmp_wrapper') + tmp_file2 = Tempfile.new('facter-wrapper') cwv = <<-EOS @echo off setlocal @@ -430,15 +418,15 @@ def install_binfile(from, op_file, target) set RUBY_BIN=%RUBY_BIN:\\=/% "%RUBY_BIN%ruby.exe" -x "%RUBY_BIN%facter" %* EOS - File.open(tmp_file2, "w") { |cw| cw.puts cwv } - FileUtils.install(tmp_file2, File.join(target, "#{op_file}.bat"), :mode => 0755, :verbose => true) + File.open(tmp_file2.path, "w") { |cw| cw.puts cwv } + FileUtils.install(tmp_file2.path, File.join(target, "#{op_file}.bat"), :mode => 0755, :verbose => true) - File.unlink(tmp_file2) + tmp_file2.unlink installed_wrapper = true end end - FileUtils.install(tmp_file, File.join(target, op_file), :mode => 0755, :verbose => true) - File.unlink(tmp_file) + FileUtils.install(tmp_file.path, File.join(target, op_file), :mode => 0755, :verbose => true) + tmp_file.unlink end check_prereqs From 976b7af24d641e589121d30e37bacc983d79cc0a Mon Sep 17 00:00:00 2001 From: Grant Heffernan Date: Wed, 1 Feb 2012 18:38:29 -0500 Subject: [PATCH 0760/3753] fix xen0/xenu detection: https://projects.puppetlabs.com/issues/10625 --- lib/facter/virtual.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 1ee41f94b4..d0c0d9a2d9 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -73,9 +73,9 @@ end if Facter::Util::Virtual.xen? - if FileTest.exists?("/proc/xen/xsd_kva") + if FileTest.exists?("/dev/xen/evtchn") result = "xen0" - elsif FileTest.exists?("/proc/xen/capabilities") + elsif FileTest.exists?("/proc/xen") result = "xenu" end end From 40efab44c23b6b8741551d327290bbd4dd250e0a Mon Sep 17 00:00:00 2001 From: Grant Heffernan Date: Wed, 1 Feb 2012 18:59:58 -0500 Subject: [PATCH 0761/3753] fix tests for virtual detection --- spec/unit/virtual_spec.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index 6ffa1ace5d..562609cbc6 100755 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -112,22 +112,22 @@ Facter.fact(:virtual).value.should == "vmware" end - it "should be xen0 with xen dom0 files in /proc" do + it "should be xen0 if exists? /dev/xen/evtchn" do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter.fact(:operatingsystem).stubs(:value).returns("Linux") Facter.fact(:hardwaremodel).stubs(:value).returns("i386") Facter::Util::Virtual.expects(:xen?).returns(true) - FileTest.expects(:exists?).with("/proc/xen/xsd_kva").returns(true) + FileTest.expects(:exists?).with("/dev/xen/evtchn").returns(true) Facter.fact(:virtual).value.should == "xen0" end - it "should be xenu with xen domU files in /proc" do + it "should be xenu if not xen0 and /proc/xen exists" do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter.fact(:operatingsystem).stubs(:value).returns("Linux") Facter.fact(:hardwaremodel).stubs(:value).returns("i386") Facter::Util::Virtual.expects(:xen?).returns(true) - FileTest.expects(:exists?).with("/proc/xen/xsd_kva").returns(false) - FileTest.expects(:exists?).with("/proc/xen/capabilities").returns(true) + FileTest.expects(:exists?).with("/dev/xen/evtchn").returns(false) + FileTest.expects(:exists?).with("/proc/xen").returns(true) Facter.fact(:virtual).value.should == "xenu" end From 0010a65f6ab9ec65ef0564558b98fd32d09792f6 Mon Sep 17 00:00:00 2001 From: Daniel Pittman Date: Wed, 1 Feb 2012 17:19:33 -0800 Subject: [PATCH 0762/3753] (#10232) Tests for VirtualBox detection via sysfs This adds some targeted testing to Facter around the VirtualBox detection code; instead of using dmidecode, this tries to read the DMI data from sysfs, and uses that to detect virtualbox. Signed-off-by: Daniel Pittman --- lib/facter/util/virtual.rb | 5 +---- spec/unit/util/virtual_spec.rb | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/lib/facter/util/virtual.rb b/lib/facter/util/virtual.rb index d622fb937f..aed961e6c8 100644 --- a/lib/facter/util/virtual.rb +++ b/lib/facter/util/virtual.rb @@ -62,10 +62,7 @@ def self.kvm? end def self.virtualbox? - txt = if FileTest.exists?("/sys/devices/virtual/dmi/id/product_name") - File.read("/sys/devices/virtual/dmi/id/product_name") - end - (txt =~ /VirtualBox/) ? true : false + File.read("/sys/devices/virtual/dmi/id/product_name") =~ /VirtualBox/ rescue false end def self.kvm_type diff --git a/spec/unit/util/virtual_spec.rb b/spec/unit/util/virtual_spec.rb index ceda83942b..b64a7f8ef4 100755 --- a/spec/unit/util/virtual_spec.rb +++ b/spec/unit/util/virtual_spec.rb @@ -180,4 +180,20 @@ Facter::Util::Resolution.stubs(:exec).with("/usr/bin/getconf MACHINE_MODEL").returns('ia64 hp server rx660') Facter::Util::Virtual.should_not be_hpvm end + + it "should be able to detect virtualbox via sysfs on Linux" do + # Fake files are always hard to stub. :/ + File.stubs(:read).with("/sys/devices/virtual/dmi/id/product_name"). + returns("VirtualBox") + + Facter::Util::Virtual.should be_virtualbox + end + + it "should be able to detect virtualbox via sysfs on Linux improperly" do + # Fake files are always hard to stub. :/ + File.stubs(:read).with("/sys/devices/virtual/dmi/id/product_name"). + returns("HP-Oracle-Sun-VMWare-funky-town") + + Facter::Util::Virtual.should_not be_virtualbox + end end From f77584f4fedf5d85a91573fd4c91ff7686bf9a07 Mon Sep 17 00:00:00 2001 From: cprice Date: Tue, 7 Feb 2012 17:28:42 -0800 Subject: [PATCH 0763/3753] (#12311) use 'ensure' to restore env vars in Resolution.with_env This will make sure that the environment variables get restored to their original values, even if the caller's yield block throws an exception or uses a "return" statement. --- lib/facter/util/resolution.rb | 24 ++++++++---------------- spec/unit/util/resolution_spec.rb | 21 +++++++++++++++++++++ 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 690a8cb685..7e266c7f37 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -45,6 +45,8 @@ def self.with_env(values) end # execute the caller's block, capture the return value rv = yield + # use an ensure block to make absolutely sure we restore the variables + ensure # restore the old values values.each do |var, value| if old.include?(var) @@ -89,14 +91,10 @@ def self.exec(code, interpreter = nil) else path = %x{which #{binary} 2>/dev/null}.chomp # we don't have the binary necessary - # we need to use "next" here, rather than "return"... because "return" would break out of the with_env wrapper - # without performing the necessary cleanup of environment variables - next if path == "" or path.match(/Command not found\./) + return if path == "" or path.match(/Command not found\./) end - # we need to use "next" here, rather than "return"... because "return" would break out of the with_env wrapper - # without performing the necessary cleanup of environment variables - next unless FileTest.exists?(path) + return unless FileTest.exists?(path) end out = nil @@ -105,22 +103,16 @@ def self.exec(code, interpreter = nil) out = %x{#{code}}.chomp rescue Errno::ENOENT => detail # command not found on Windows - # we need to use "next" here, rather than "return"... because "return" would break out of the with_env wrapper - # without performing the necessary cleanup of environment variables - next + return rescue => detail $stderr.puts detail - # we need to use "next" here, rather than "return"... because "return" would break out of the with_env wrapper - # without performing the necessary cleanup of environment variables - next + return end - # we need to use "next" here, rather than "return"... because "return" would break out of the with_env wrapper - # without performing the necessary cleanup of environment variables if out == "" - next + return else - next out + return out end end diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index 4788d45722..e48e81a8b8 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -86,6 +86,27 @@ ENV[key].should == orig_env[key] end end + + it "should not be affected by a 'return' statement in the yield block" do + @sentinel_var = :resolution_test_foo.to_s + + # the intent of this test case is to test a yield block that contains a return statement. However, it's illegal + # to use a return statement outside of a method, so we need to create one here to give scope to the 'return' + def handy_method() + ENV[@sentinel_var] = "foo" + new_env = { @sentinel_var => "bar" } + + Facter::Util::Resolution.with_env new_env do + ENV[@sentinel_var].should == "bar" + return + end + end + + handy_method() + + ENV[@sentinel_var].should == "foo" + + end end describe "when setting the code" do From 5273842fff25a5222319dede587b0601111b6562 Mon Sep 17 00:00:00 2001 From: Nan Liu Date: Wed, 18 Jan 2012 11:22:46 -0800 Subject: [PATCH 0764/3753] (#6682) Add Solaris ldom facts and update virtual detection. Support Solaris LDOM detection via virtinfo command. --- lib/facter/ldom.rb | 47 ++++++++++++++++++++++++ spec/fixtures/ldom/ldom_v1 | 6 ++++ spec/unit/ldom_spec.rb | 74 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 127 insertions(+) create mode 100644 lib/facter/ldom.rb create mode 100644 spec/fixtures/ldom/ldom_v1 create mode 100644 spec/unit/ldom_spec.rb diff --git a/lib/facter/ldom.rb b/lib/facter/ldom.rb new file mode 100644 index 0000000000..3e6f91f26a --- /dev/null +++ b/lib/facter/ldom.rb @@ -0,0 +1,47 @@ +if Facter.value(:kernel) == 'SunOS' + virtinfo = Facter::Util::Resolution.exec('virtinfo -ap') + + # Convert virtinfo parseable output format to array of arrays. + # DOMAINROLE|impl=LDoms|control=true|io=true|service=true|root=true + # DOMAINNAME|name=primary + # DOMAINUUID|uuid=8e0d6ec5-cd55-e57f-ae9f-b4cc050999a4 + # DOMAINCONTROL|name=san-t2k-6 + # DOMAINCHASSIS|serialno=0704RB0280 + # + # For keys containing multiple value such as domain role: + # ldom_{key}_{subkey} = value + # Otherwise the fact will simply be: + # ldom_{key} = value + unless virtinfo.nil? + virt_array = virtinfo.split("\n").select{|l| l =~ /^DOMAIN/ }. + collect{|l| l.split('|')} + virt_array.each do |x| + key = x[0] + value = x[1..x.size] + + if value.size == 1 + Facter.add("ldom_#{key.downcase}") do + setcode { value.first.split('=')[1] } + end + else + value.each do |y| + k = y.split('=')[0] + v = y.split('=')[1] + Facter.add("ldom_#{key.downcase}_#{k.downcase}") do + setcode { v } + end + end + end + end + + # When ldom domainrole control = false, the system is a guest, so we mark it + # as a virtual system: + Facter.add("virtual") do + confine :ldom_domainrole_control => 'false' + has_weight 10 + setcode do + Facter.value(:ldom_domainrole_impl) + end + end + end +end diff --git a/spec/fixtures/ldom/ldom_v1 b/spec/fixtures/ldom/ldom_v1 new file mode 100644 index 0000000000..f2229ccfdd --- /dev/null +++ b/spec/fixtures/ldom/ldom_v1 @@ -0,0 +1,6 @@ +VERSION 1.0 +DOMAINROLE|impl=LDoms|control=false|io=true|service=true|root=true +DOMAINNAME|name=primary +DOMAINUUID|uuid=8e0d6ec5-cd55-e57f-ae9f-b4cc050999a4 +DOMAINCONTROL|name=san-t2k-6 +DOMAINCHASSIS|serialno=0704RB0280 diff --git a/spec/unit/ldom_spec.rb b/spec/unit/ldom_spec.rb new file mode 100644 index 0000000000..0c1a6ec365 --- /dev/null +++ b/spec/unit/ldom_spec.rb @@ -0,0 +1,74 @@ +#!/usr/bin/env ruby + +require 'spec_helper' + +def ldom_fixtures(filename) + File.read(fixtures('ldom', filename)) +end + +describe "ldom fact" do + before do + Facter.fact(:kernel).stubs(:value).returns("SunOS") + end + + describe "when running on ldom hardware" do + before :each do + # For virtinfo documentation: + # http://docs.oracle.com/cd/E23824_01/html/821-1462/virtinfo-1m.html + Facter::Util::Resolution.stubs(:exec).with("virtinfo -ap"). + returns(ldom_fixtures('ldom_v1')) + Facter.collection.loader.load(:ldom) + end + + it "should return correct impl on version 1.0" do + Facter.fact(:ldom_domainrole_impl).value.should == "LDoms" + end + + it "should return correct control on version 1.0" do + Facter.fact(:ldom_domainrole_control).value.should == "false" + end + + it "should return correct io on version 1.0" do + Facter.fact(:ldom_domainrole_io).value.should == "true" + end + + it "should return correct service on version 1.0" do + Facter.fact(:ldom_domainrole_service).value.should == "true" + end + + it "should return correct root on version 1.0" do + Facter.fact(:ldom_domainrole_root).value.should == "true" + end + + it "should return correct domain name on version 1.0" do + Facter.fact(:ldom_domainname).value.should == "primary" + end + + it "should return correct uuid on version 1.0" do + Facter.fact(:ldom_domainuuid).value.should == "8e0d6ec5-cd55-e57f-ae9f-b4cc050999a4" + end + + it "should return correct ldomcontrol on version 1.0" do + Facter.fact(:ldom_domaincontrol).value.should == "san-t2k-6" + end + + it "should return correct serial on version 1.0" do + Facter.fact(:ldom_domainchassis).value.should == "0704RB0280" + end + + it "should return correct virtual on version 1.0" do + Facter.fact(:virtual).value.should == "LDoms" + end + end + + describe "when running on non ldom hardware" do + before :each do + Facter::Util::Resolution.stubs(:exec).with("virtinfo -ap").returns(nil) + Facter.collection.loader.load(:ldom) + end + + it "should return correct virtual" do + Facter.fact(:ldom_domainrole_impl).should == nil + end + end +end From c80de1cf04cfa335f0a4f650ad944c1f7d4e10fb Mon Sep 17 00:00:00 2001 From: Nan Liu Date: Wed, 18 Jan 2012 11:18:18 -0800 Subject: [PATCH 0765/3753] (#11969) Add zfs zpool version facts. * Add zfs upgrade -v and zpool upgrade -v version info. * Add appropriate spec tests. --- lib/facter/zfs_version.rb | 10 +++++ lib/facter/zpool_version.rb | 10 +++++ spec/fixtures/unit/zfs_version/solaris_10 | 10 +++++ spec/fixtures/unit/zfs_version/solaris_11 | 12 ++++++ spec/fixtures/unit/zpool_version/solaris_10 | 31 +++++++++++++++ spec/fixtures/unit/zpool_version/solaris_11 | 43 +++++++++++++++++++++ spec/unit/zfs_version_spec.rb | 42 ++++++++++++++++++++ spec/unit/zpool_version_spec.rb | 42 ++++++++++++++++++++ 8 files changed, 200 insertions(+) create mode 100644 lib/facter/zfs_version.rb create mode 100644 lib/facter/zpool_version.rb create mode 100644 spec/fixtures/unit/zfs_version/solaris_10 create mode 100644 spec/fixtures/unit/zfs_version/solaris_11 create mode 100644 spec/fixtures/unit/zpool_version/solaris_10 create mode 100644 spec/fixtures/unit/zpool_version/solaris_11 create mode 100644 spec/unit/zfs_version_spec.rb create mode 100644 spec/unit/zpool_version_spec.rb diff --git a/lib/facter/zfs_version.rb b/lib/facter/zfs_version.rb new file mode 100644 index 0000000000..06c0909cbe --- /dev/null +++ b/lib/facter/zfs_version.rb @@ -0,0 +1,10 @@ +require 'facter' + +Facter.add('zfs_version') do + confine :kernel => :sunos + + setcode do + zfs_v = Facter::Util::Resolution.exec('zfs upgrade -v') + zfs_version = zfs_v.scan(/^\s+(\d+)\s+/m).flatten.last unless zfs_v.nil? + end +end diff --git a/lib/facter/zpool_version.rb b/lib/facter/zpool_version.rb new file mode 100644 index 0000000000..47da1ffc66 --- /dev/null +++ b/lib/facter/zpool_version.rb @@ -0,0 +1,10 @@ +require 'facter' + +Facter.add('zpool_version') do + confine :kernel => :sunos + + setcode do + zpool_v = Facter::Util::Resolution.exec('zpool upgrade -v') + zpool_version = zpool_v.match(/ZFS pool version (\d+)./).captures.first unless zpool_v.nil? + end +end diff --git a/spec/fixtures/unit/zfs_version/solaris_10 b/spec/fixtures/unit/zfs_version/solaris_10 new file mode 100644 index 0000000000..900d6176bf --- /dev/null +++ b/spec/fixtures/unit/zfs_version/solaris_10 @@ -0,0 +1,10 @@ +The following filesystem versions are supported: + +VER DESCRIPTION +--- -------------------------------------------------------- + 1 Initial ZFS filesystem version + 2 Enhanced directory entries + 3 Case insensitive and SMB credentials support + +For more information on a particular version, including supported releases, +see the ZFS Administration Guide. diff --git a/spec/fixtures/unit/zfs_version/solaris_11 b/spec/fixtures/unit/zfs_version/solaris_11 new file mode 100644 index 0000000000..565e20ad68 --- /dev/null +++ b/spec/fixtures/unit/zfs_version/solaris_11 @@ -0,0 +1,12 @@ +The following filesystem versions are supported: + +VER DESCRIPTION +--- -------------------------------------------------------- + 1 Initial ZFS filesystem version + 2 Enhanced directory entries + 3 Case insensitive and SMB credentials support + 4 userquota, groupquota properties + 5 System attributes + +For more information on a particular version, including supported releases, +see the ZFS Administration Guide. diff --git a/spec/fixtures/unit/zpool_version/solaris_10 b/spec/fixtures/unit/zpool_version/solaris_10 new file mode 100644 index 0000000000..3d523471c7 --- /dev/null +++ b/spec/fixtures/unit/zpool_version/solaris_10 @@ -0,0 +1,31 @@ +This system is currently running ZFS pool version 22. + +The following versions are supported: + +VER DESCRIPTION +--- -------------------------------------------------------- + 1 Initial ZFS version + 2 Ditto blocks (replicated metadata) + 3 Hot spares and double parity RAID-Z + 4 zpool history + 5 Compression using the gzip algorithm + 6 bootfs pool property + 7 Separate intent log devices + 8 Delegated administration + 9 refquota and refreservation properties + 10 Cache devices + 11 Improved scrub performance + 12 Snapshot properties + 13 snapused property + 14 passthrough-x aclinherit + 15 user/group space accounting + 16 stmf property support + 17 Triple-parity RAID-Z + 18 Snapshot user holds + 19 Log device removal + 20 Compression using zle (zero-length encoding) + 21 Reserved + 22 Received properties + +For more information on a particular version, including supported releases, +see the ZFS Administration Guide. diff --git a/spec/fixtures/unit/zpool_version/solaris_11 b/spec/fixtures/unit/zpool_version/solaris_11 new file mode 100644 index 0000000000..4a06a2d7f0 --- /dev/null +++ b/spec/fixtures/unit/zpool_version/solaris_11 @@ -0,0 +1,43 @@ +zpool upgrade -v +This system is currently running ZFS pool version 33. + +The following versions are supported: + +VER DESCRIPTION +--- -------------------------------------------------------- + 1 Initial ZFS version + 2 Ditto blocks (replicated metadata) + 3 Hot spares and double parity RAID-Z + 4 zpool history + 5 Compression using the gzip algorithm + 6 bootfs pool property + 7 Separate intent log devices + 8 Delegated administration + 9 refquota and refreservation properties + 10 Cache devices + 11 Improved scrub performance + 12 Snapshot properties + 13 snapused property + 14 passthrough-x aclinherit + 15 user/group space accounting + 16 stmf property support + 17 Triple-parity RAID-Z + 18 Snapshot user holds + 19 Log device removal + 20 Compression using zle (zero-length encoding) + 21 Deduplication + 22 Received properties + 23 Slim ZIL + 24 System attributes + 25 Improved scrub stats + 26 Improved snapshot deletion performance + 27 Improved snapshot creation performance + 28 Multiple vdev replacements + 29 RAID-Z/mirror hybrid allocator + 30 Encryption + 31 Improved 'zfs list' performance + 32 One MB blocksize + 33 Improved share support + +For more information on a particular version, including supported releases, +see the ZFS Administration Guide. diff --git a/spec/unit/zfs_version_spec.rb b/spec/unit/zfs_version_spec.rb new file mode 100644 index 0000000000..eee097e757 --- /dev/null +++ b/spec/unit/zfs_version_spec.rb @@ -0,0 +1,42 @@ +#!/usr/bin/env ruby + +require 'spec_helper' + +describe "zfs_version fact" do + + # http://blogs.oracle.com/bobn/entry/live_upgrade_and_zfs_versioning + # + # Solaris Release ZPOOL Version ZFS Version + # Solaris 10 10/08 (u6) 10 3 + # Solaris 10 5/09 (u7) 10 3 + # Solaris 10 10/09 (u8) 15 4 + # Solaris 10 9/10 (u9) 22 4 + # Solaris 10 8/11 (u10) 29 5 + # Solaris 11 11/11 (ga) 33 5 + + describe "for Solaris" do + before :each do + Facter.fact(:kernel).stubs(:value).returns("SunOS") + end + + it "should return correct version on Solaris 10" do + Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(my_fixture_read('solaris_10')) + Facter.fact(:zfs_version).value.should == "3" + end + + it "should return correct version on Solaris 11" do + Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(my_fixture_read('solaris_11')) + Facter.fact(:zfs_version).value.should == "5" + end + + it "should return nil if zfs command is not available" do + Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(nil) + Facter.fact(:zfs_version).value.should == nil + end + end + + it "should not run on Linux" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:zfs_version).value.should == nil + end +end diff --git a/spec/unit/zpool_version_spec.rb b/spec/unit/zpool_version_spec.rb new file mode 100644 index 0000000000..88789a17ae --- /dev/null +++ b/spec/unit/zpool_version_spec.rb @@ -0,0 +1,42 @@ +#!/usr/bin/env ruby + +require 'spec_helper' + +describe "zpool_version fact" do + + # http://blogs.oracle.com/bobn/entry/live_upgrade_and_zfs_versioning + # + # Solaris Release ZPOOL Version ZFS Version + # Solaris 10 10/08 (u6) 10 3 + # Solaris 10 5/09 (u7) 10 3 + # Solaris 10 10/09 (u8) 15 4 + # Solaris 10 9/10 (u9) 22 4 + # Solaris 10 8/11 (u10) 29 5 + # Solaris 11 11/11 (ga) 33 5 + + describe "for Solaris" do + before :each do + Facter.fact(:kernel).stubs(:value).returns("SunOS") + end + + it "should return correct version on Solaris 10" do + Facter::Util::Resolution.stubs(:exec).with("zpool upgrade -v").returns(my_fixture_read('solaris_10')) + Facter.fact(:zpool_version).value.should == "22" + end + + it "should return correct version on Solaris 11" do + Facter::Util::Resolution.stubs(:exec).with("zpool upgrade -v").returns(my_fixture_read('solaris_11')) + Facter.fact(:zpool_version).value.should == "33" + end + + it "should return nil if zpool is not available" do + Facter::Util::Resolution.stubs(:exec).with("zpool upgrade -v").returns(nil) + Facter.fact(:zpool_version).value.should == nil + end + end + + it "should not run on Linux" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:zpool_version).value.should == nil + end +end From 4581b17a7d0977e00ba2bf2b8aa12119a54c54a6 Mon Sep 17 00:00:00 2001 From: Garrett Honeycutt Date: Fri, 10 Feb 2012 16:38:22 -0800 Subject: [PATCH 0766/3753] (maint) removing trailing whitespace --- lib/facter/blockdevices.rb | 10 +++++----- lib/facter/ec2.rb | 2 +- lib/facter/operatingsystemrelease.rb | 2 +- lib/facter/util/ec2.rb | 2 +- spec/unit/architecture_spec.rb | 2 +- spec/unit/blockdevices_spec.rb | 6 +++--- spec/unit/ssh_spec.rb | 2 +- spec/unit/util/macosx_spec.rb | 12 ++++++------ spec/unit/util/processor_spec.rb | 2 +- 9 files changed, 20 insertions(+), 20 deletions(-) diff --git a/lib/facter/blockdevices.rb b/lib/facter/blockdevices.rb index 875718e6e7..e0ddc48fb4 100644 --- a/lib/facter/blockdevices.rb +++ b/lib/facter/blockdevices.rb @@ -58,22 +58,22 @@ blockdevices = [] - # This should prevent any non-2.6 kernels or odd machines without sysfs support from being investigated further + # This should prevent any non-2.6 kernels or odd machines without sysfs support from being investigated further if File.exist?(sysfs_block_directory) - # Iterate over each file in the /sys/block/ directory and skip ones that do not have a device subdirectory + # Iterate over each file in the /sys/block/ directory and skip ones that do not have a device subdirectory Dir.entries(sysfs_block_directory).each do |device| sysfs_device_directory = sysfs_block_directory + device + "/device" next unless File.exist?(sysfs_device_directory) - # Add the device to the blockdevices list, which is returned as it's own fact later on + # Add the device to the blockdevices list, which is returned as it's own fact later on blockdevices << device sizefile = sysfs_block_directory + device + "/size" vendorfile = sysfs_device_directory + "/vendor" modelfile = sysfs_device_directory + "/model" - if File.exist?(sizefile) + if File.exist?(sizefile) Facter.add("blockdevice_#{device}_size".to_sym) do setcode { IO.read(sizefile).strip.to_i * 512 } end @@ -99,7 +99,7 @@ unless blockdevices.empty? Facter.add(:blockdevices) do setcode { blockdevices.sort.join(',') } - end + end end end diff --git a/lib/facter/ec2.rb b/lib/facter/ec2.rb index 98c957f7dc..392c54965a 100644 --- a/lib/facter/ec2.rb +++ b/lib/facter/ec2.rb @@ -24,7 +24,7 @@ def userdata() end end -if (Facter::Util::EC2.has_euca_mac? || Facter::Util::EC2.has_openstack_mac? || +if (Facter::Util::EC2.has_euca_mac? || Facter::Util::EC2.has_openstack_mac? || Facter::Util::EC2.has_ec2_arp?) && Facter::Util::EC2.can_connect? metadata diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index 4d411aa6fe..a0db2baff0 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -11,7 +11,7 @@ # On Slackware, parses '/etc/slackware-version'. # On Amazon Linux, returns the 'lsbdistrelease' value. # On Mageia, parses '/etc/mageia-release' for the release version. -# +# # On all remaining systems, returns the 'kernelrelease' value. # # Caveats: diff --git a/lib/facter/util/ec2.rb b/lib/facter/util/ec2.rb index 2ed6517074..bcfda60207 100644 --- a/lib/facter/util/ec2.rb +++ b/lib/facter/util/ec2.rb @@ -26,7 +26,7 @@ def has_euca_mac? !!(Facter.value(:macaddress) =~ %r{^[dD]0:0[dD]:}) end - # Test if this host has a mac address used by OpenStack, which + # Test if this host has a mac address used by OpenStack, which # normally starts with 02:16:3E def has_openstack_mac? !!(Facter.value(:macaddress) =~ %r{^02:16:3[eE]}) diff --git a/spec/unit/architecture_spec.rb b/spec/unit/architecture_spec.rb index 5dadd8485d..e0ba2f8b15 100755 --- a/spec/unit/architecture_spec.rb +++ b/spec/unit/architecture_spec.rb @@ -30,7 +30,7 @@ "i686" => "i386", "pentium" => "i386", } - + os_archs.each do |pair, result| it "should be #{result} if os is #{pair[0]} and hardwaremodel is #{pair[1]}" do Facter.fact(:operatingsystem).stubs(:value).returns(pair[0]) diff --git a/spec/unit/blockdevices_spec.rb b/spec/unit/blockdevices_spec.rb index 7fdcc9e26e..5c331721ed 100644 --- a/spec/unit/blockdevices_spec.rb +++ b/spec/unit/blockdevices_spec.rb @@ -54,20 +54,20 @@ %w{ . .. hda }.each do |device| Facter.fact("blockdevice_#{device}_size".to_sym).should == nil Facter.fact("blockdevice_#{device}_vendor".to_sym).should == nil - Facter.fact("blockdevice_#{device}_model".to_sym).should == nil + Facter.fact("blockdevice_#{device}_model".to_sym).should == nil end # handle facts that should exist %w{ sda sdb }.each do |device| Facter.fact("blockdevice_#{device}_size".to_sym).should_not == nil Facter.fact("blockdevice_#{device}_vendor".to_sym).should_not == nil - Facter.fact("blockdevice_#{device}_model".to_sym).should_not == nil + Facter.fact("blockdevice_#{device}_model".to_sym).should_not == nil end Facter.fact(:blockdevice_sda_model).value.should == "WDC WD5000AAKS-0" Facter.fact(:blockdevice_sda_vendor).value.should == "ATA" Facter.fact(:blockdevice_sda_size).value.should == 500107862016 - + Facter.fact(:blockdevice_sdb_model).value.should == "PERC H700" Facter.fact(:blockdevice_sdb_vendor).value.should == "DELL" Facter.fact(:blockdevice_sdb_size).value.should == 4499246678016 diff --git a/spec/unit/ssh_spec.rb b/spec/unit/ssh_spec.rb index 79dc025451..de368ff77f 100755 --- a/spec/unit/ssh_spec.rb +++ b/spec/unit/ssh_spec.rb @@ -11,7 +11,7 @@ # different name, so load the file explicitly. Facter.collection.loader.load(:ssh) end - + # fingerprints extracted from ssh-keygen -r '' -f /etc/ssh/ssh_host_dsa_key.pub { 'SSHRSAKey' => [ '/usr/local/etc/ssh_host_rsa_key.pub' , "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDrs+KtR8hjasELsyCiiBplUeIi77hEHzTSQt1ALG7N4IgtMg27ZAcq0tl2/O9ZarQuClc903pgionbM9Q98CtAIoqgJwdtsor7ETRmzwrcY/mvI7ne51UzQy4Eh9WrplfpNyg+EVO0FUC7mBcay6JY30QKasePp+g4MkwK5cuTzOCzd9up9KELonlH7tTm2L0YI4HhZugwVoTFulCAZvPICxSk1B/fEKyGSZVfY/UxZNqg9g2Wyvq5u40xQ5eO882UwhB3w4IbmRnPKcyotAcqOJxA7hToMKtEmFct+vjHE8T37w8axE/1X9mdvy8IZbkEBL1cupqqb8a8vU1QTg1z", "SSHFP 1 1 1e4f163a1747d0d1a08a29972c9b5d94ee5705d0\nSSHFP 1 2 4e834c91e423d6085ed6dfb880a59e2f1b04f17c1dc17da07708af67c5ab6045" ], 'SSHDSAKey' => [ '/etc/ssh/ssh_host_dsa_key.pub' , "ssh-dss AAAAB3NzaC1kc3MAAACBAKjmRez14aZT6OKhHrsw19s7u30AdghwHFQbtC+L781YjJ3UV0/WQoZ8NaDL4ovuvW23RuO49tsqSNcVHg+PtRiN2iTVAS2h55TFhaPKhTs+i0NH3p3Ze8LNSYuz8uK7a+nTxysz47GYTHiE1ke8KXe5wGKDO1TO/MUgpDbwx72LAAAAFQD9yMJCnZMiKzA7J1RNkwvgCyBKSQAAAIAtWBAsuRM0F2fdCe+F/JmgyryQmRIT5vP8E1ww3t3ywdLHklN7UMkaEKBW/TN/jj1JOGXtZ2v5XI+0VNoNKD/7dnCGzNViRT/jjfyVi6l5UMg4Q52Gv0RXJoBJpxNqFOU2niSsy8hioyE39W6LJYWJtQozGpH/KKgkCSvxBn5hlAAAAIB1yo/YD0kQICOO0KE+UMMaKtV7FwyedFJsxsWYwZfHXGwWskf0d2+lPhd9qwdbmSvySE8Qrlvu+W+X8AipwGkItSnj16ORF8kO3lfABa+7L4BLDtumt7ybjBPcHOy3n28dd07TmMtyWvLjOb0mcxPo+TwDLtHd3L/3C1Dh41jRPg==\n", "SSHFP 2 1 f63dfe8da99f50ffbcfa40a61161cee29d109f70\nSSHFP 2 2 5f57aa6be9baddd71b6049ed5d8639664a7ddf92ce293e3887f16ad0f2d459d9" ], diff --git a/spec/unit/util/macosx_spec.rb b/spec/unit/util/macosx_spec.rb index dc7bb9bde8..688cfdb4f6 100755 --- a/spec/unit/util/macosx_spec.rb +++ b/spec/unit/util/macosx_spec.rb @@ -43,19 +43,19 @@ Facter::Util::Macosx.expects(:profiler_data).with("SPSoftwareDataType").returns "eh" Facter::Util::Macosx.os_overview.should == "eh" end - + describe "when working out software version" do - + before do Facter::Util::Resolution.expects(:exec).with("/usr/bin/sw_vers -productName").returns "Mac OS X" Facter::Util::Resolution.expects(:exec).with("/usr/bin/sw_vers -buildVersion").returns "9J62" end - + it "should have called sw_vers three times when determining software version" do Facter::Util::Resolution.expects(:exec).with("/usr/bin/sw_vers -productVersion").returns "10.5.7" Facter::Util::Macosx.sw_vers end - + it "should return a hash with the correct keys when determining software version" do Facter::Util::Resolution.expects(:exec).with("/usr/bin/sw_vers -productVersion").returns "10.5.7" Facter::Util::Macosx.sw_vers.keys.sort.should == ["macosx_productName", @@ -64,14 +64,14 @@ "macosx_productversion_major", "macosx_productVersion"].sort end - + it "should split a product version of 'x.y.z' into separate hash entries correctly" do Facter::Util::Resolution.expects(:exec).with("/usr/bin/sw_vers -productVersion").returns "1.2.3" sw_vers = Facter::Util::Macosx.sw_vers sw_vers["macosx_productversion_major"].should == "1.2" sw_vers["macosx_productversion_minor"].should == "3" end - + it "should treat a product version of 'x.y' as 'x.y.0" do Facter::Util::Resolution.expects(:exec).with("/usr/bin/sw_vers -productVersion").returns "2.3" Facter::Util::Macosx.sw_vers["macosx_productversion_minor"].should == "0" diff --git a/spec/unit/util/processor_spec.rb b/spec/unit/util/processor_spec.rb index 9867f53c55..def8b21ea5 100755 --- a/spec/unit/util/processor_spec.rb +++ b/spec/unit/util/processor_spec.rb @@ -43,7 +43,7 @@ def cpuinfo_fixture(filename) Facter.fact(:architecture).stubs(:value).returns("amd64") File.stubs(:exists?).with("/proc/cpuinfo").returns(true) File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("amd64quad")) - + Facter::Util::Processor.enum_cpuinfo[0].should == "Quad-Core AMD Opteron(tm) Processor 2374 HE" Facter::Util::Processor.enum_cpuinfo[1].should == "Quad-Core AMD Opteron(tm) Processor 2374 HE" Facter::Util::Processor.enum_cpuinfo[2].should == "Quad-Core AMD Opteron(tm) Processor 2374 HE" From 824631ce78141b826c96ed0a1e5314aae329dcbf Mon Sep 17 00:00:00 2001 From: Matthaus Litteken Date: Wed, 15 Feb 2012 10:43:24 -0800 Subject: [PATCH 0767/3753] Updated CHANGELOG and lib/facter.rb for 1.6.6rc1 --- CHANGELOG | 10 ++++++++++ lib/facter.rb | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 55b29f5e1b..bdb9893cf7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,13 @@ +1.6.6rc1 +=== +c218d84 (#12362) Use Tempfile to generate temp files +f6bbe14 (#12170) Adds gem spec description Without this patch, the gem spec file is missing a description attribute, which caus +5c5c330 Changes apple rake task to reflect package name facter instead of puppet. +9b5cb26 Updating conf/redhat/facter.spec for 1.6.5 release. +7d3889d (#12079) Fix order-dependent test failure due to odd stubbing. +7f2a0e2 add a simple test for openstack ec2 facts +cb598aa Support EC2 facts on OpenStack + 1.6.5 === 71d3d3d (#12077) Add pciutils RPM dependency diff --git a/lib/facter.rb b/lib/facter.rb index 552c218d92..c960241ead 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -25,7 +25,7 @@ module Util; end include Comparable include Enumerable - FACTERVERSION = '1.6.5' + FACTERVERSION = '1.6.6' # = Facter # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. From c273d34cbb9caf02652b00cc6fbb221298b11858 Mon Sep 17 00:00:00 2001 From: Jeremy Katz Date: Wed, 15 Feb 2012 09:22:49 -0500 Subject: [PATCH 0768/3753] Make ec2 facts work on CentOS again (#12666) Refactoring the ec2 facts lost the support for CentOS where the hardware address in arp -an is uppercased. Fix and add a unit test now that there are those --- lib/facter/util/ec2.rb | 2 +- spec/fixtures/unit/util/ec2/centos-arp-ec2.out | 1 + spec/unit/util/ec2_spec.rb | 7 +++++++ 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 spec/fixtures/unit/util/ec2/centos-arp-ec2.out diff --git a/lib/facter/util/ec2.rb b/lib/facter/util/ec2.rb index 2ed6517074..376c9ca47c 100644 --- a/lib/facter/util/ec2.rb +++ b/lib/facter/util/ec2.rb @@ -46,7 +46,7 @@ def has_ec2_arp? arp_table = Facter::Util::Resolution.exec(arp_command) if not arp_table.nil? arp_table.each_line do |line| - return true if line.include?(mac_address) + return true if line.downcase.include?(mac_address) end end return false diff --git a/spec/fixtures/unit/util/ec2/centos-arp-ec2.out b/spec/fixtures/unit/util/ec2/centos-arp-ec2.out new file mode 100644 index 0000000000..24d2ec03ac --- /dev/null +++ b/spec/fixtures/unit/util/ec2/centos-arp-ec2.out @@ -0,0 +1 @@ +? (10.240.93.1) at FE:FF:FF:FF:FF:FF [ether] on eth0 \ No newline at end of file diff --git a/spec/unit/util/ec2_spec.rb b/spec/unit/util/ec2_spec.rb index 62fdcb744c..f1fbe3c073 100755 --- a/spec/unit/util/ec2_spec.rb +++ b/spec/unit/util/ec2_spec.rb @@ -22,6 +22,13 @@ Facter::Util::EC2.has_ec2_arp?.should == true end + it "should succeed if arp table contains FE:FF:FF:FF:FF:FF" do + ec2arp = my_fixture_read("centos-arp-ec2.out") + Facter::Util::Resolution.expects(:exec).with("arp -an").\ + at_least_once.returns(ec2arp) + Facter::Util::EC2.has_ec2_arp?.should == true + end + it "should fail if arp table does not contain fe:ff:ff:ff:ff:ff" do ec2arp = my_fixture_read("linux-arp-not-ec2.out") Facter::Util::Resolution.expects(:exec).with("arp -an"). From c9a3d9ea5e01c8d9a2a22aa533b90c88b19e8f00 Mon Sep 17 00:00:00 2001 From: Michael Stahnke Date: Thu, 16 Feb 2012 15:13:27 -0800 Subject: [PATCH 0769/3753] Updated CHANGELOG for 1.6.6rc2 --- CHANGELOG | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index bdb9893cf7..32f41a0245 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,8 @@ +1.6.6rc2 +=== +e046144 Updated CHANGELOG for 1.6.6rc2 +c273d34 Make ec2 facts work on CentOS again (#12666) + 1.6.6rc1 === c218d84 (#12362) Use Tempfile to generate temp files From 9ff4453b2f0843df887ae1ec2098a4336291df0c Mon Sep 17 00:00:00 2001 From: Timur Batyrshin Date: Mon, 20 Feb 2012 16:16:07 +0400 Subject: [PATCH 0770/3753] Removed exclusive threading I've experienced regular facter freezes on puppet apply. Tracing back the freezes showed that they were produced by exclusive threads. After I've removed the exclusive thread wrapper the facter stopped freezing. (FYI those were introduced by https://github.com/puppetlabs/facter/commit/c2aa5086ab55da9c708d962b84a1b85404fc6329) Note: there is a similar code in facter/processor.rb for Windows. I've left that intact as I don't quite understand the ways Windows works. --- lib/facter/util/memory.rb | 22 ++++------ lib/facter/util/processor.rb | 84 ++++++++++++++++-------------------- 2 files changed, 46 insertions(+), 60 deletions(-) diff --git a/lib/facter/util/memory.rb b/lib/facter/util/memory.rb index 09a8166108..96d8b4c03c 100644 --- a/lib/facter/util/memory.rb +++ b/lib/facter/util/memory.rb @@ -3,23 +3,19 @@ ## module Facter::Memory - require 'thread' - def self.meminfo_number(tag) memsize = "" - Thread::exclusive do - size, scale = [0, ""] - File.readlines("/proc/meminfo").each do |l| - size, scale = [$1.to_f, $2] if l =~ /^#{tag}:\s+(\d+)\s+(\S+)/ - # MemoryFree == memfree + cached + buffers - # (assume scales are all the same as memfree) - if tag == "MemFree" && - l =~ /^(?:Buffers|Cached):\s+(\d+)\s+(?:\S+)/ - size += $1.to_f - end + size, scale = [0, ""] + File.readlines("/proc/meminfo").each do |l| + size, scale = [$1.to_f, $2] if l =~ /^#{tag}:\s+(\d+)\s+(\S+)/ + # MemoryFree == memfree + cached + buffers + # (assume scales are all the same as memfree) + if tag == "MemFree" && + l =~ /^(?:Buffers|Cached):\s+(\d+)\s+(?:\S+)/ + size += $1.to_f end - memsize = scale_number(size, scale) end + memsize = scale_number(size, scale) memsize end diff --git a/lib/facter/util/processor.rb b/lib/facter/util/processor.rb index 220b209a6a..fd4c4e8d1e 100644 --- a/lib/facter/util/processor.rb +++ b/lib/facter/util/processor.rb @@ -8,55 +8,47 @@ def self.enum_cpuinfo model = Facter.value(:architecture) case model when "x86_64", "amd64", "i386", /parisc/, "hppa", "ia64" - Thread::exclusive do - File.readlines(cpuinfo).each do |l| - if l =~ /processor\s+:\s+(\d+)/ - processor_num = $1.to_i - elsif l =~ /model name\s+:\s+(.*)\s*$/ - processor_list[processor_num] = $1 unless processor_num == -1 - processor_num = -1 - elsif l =~ /processor\s+(\d+):\s+(.*)/ - processor_num = $1.to_i - processor_list[processor_num] = $2 unless processor_num == -1 - end + File.readlines(cpuinfo).each do |l| + if l =~ /processor\s+:\s+(\d+)/ + processor_num = $1.to_i + elsif l =~ /model name\s+:\s+(.*)\s*$/ + processor_list[processor_num] = $1 unless processor_num == -1 + processor_num = -1 + elsif l =~ /processor\s+(\d+):\s+(.*)/ + processor_num = $1.to_i + processor_list[processor_num] = $2 unless processor_num == -1 end end when "ppc64" - Thread::exclusive do - File.readlines(cpuinfo).each do |l| - if l =~ /processor\s+:\s+(\d+)/ - processor_num = $1.to_i - elsif l =~ /cpu\s+:\s+(.*)\s*$/ - processor_list[processor_num] = $1 unless processor_num == -1 - processor_num = -1 - end + File.readlines(cpuinfo).each do |l| + if l =~ /processor\s+:\s+(\d+)/ + processor_num = $1.to_i + elsif l =~ /cpu\s+:\s+(.*)\s*$/ + processor_list[processor_num] = $1 unless processor_num == -1 + processor_num = -1 end end when /arm/ - Thread::exclusive do - File.readlines(cpuinfo).each do |l| - if l =~ /Processor\s+:\s+(.+)/ + File.readlines(cpuinfo).each do |l| + if l =~ /Processor\s+:\s+(.+)/ + processor_num += 1 + processor_list[processor_num] = $1.chomp + elsif l =~ /processor\s+:\s+(\d+)\s*$/ + proc_num = $1.to_i + if proc_num != 0 processor_num += 1 - processor_list[processor_num] = $1.chomp - elsif l =~ /processor\s+:\s+(\d+)\s*$/ - proc_num = $1.to_i - if proc_num != 0 - processor_num += 1 - processor_list[processor_num] = processor_list[processor_num-1] - end + processor_list[processor_num] = processor_list[processor_num-1] end end end when /sparc/ - Thread::exclusive do - File.readlines(cpuinfo).each do |l| - if l =~ /cpu\s+:\s+(.*)\s*$/ - processor_num += 1 - processor_list[processor_num] = $1 - end + File.readlines(cpuinfo).each do |l| + if l =~ /cpu\s+:\s+(.*)\s*$/ + processor_num += 1 + processor_list[processor_num] = $1 end end end @@ -67,18 +59,16 @@ def self.enum_cpuinfo def self.enum_lsdev processor_num = -1 processor_list = {} - Thread::exclusive do - procs = Facter::Util::Resolution.exec('lsdev -Cc processor') - if procs - procs.each_line do |proc| - if proc =~ /^proc(\d+)/ - processor_num = $1.to_i - # Not retrieving the frequency since AIX 4.3.3 doesn't support the - # attribute and some people still use the OS. - proctype = Facter::Util::Resolution.exec('lsattr -El proc0 -a type') - if proctype =~ /^type\s+(\S+)\s+/ - processor_list[processor_num] = $1 - end + procs = Facter::Util::Resolution.exec('lsdev -Cc processor') + if procs + procs.each_line do |proc| + if proc =~ /^proc(\d+)/ + processor_num = $1.to_i + # Not retrieving the frequency since AIX 4.3.3 doesn't support the + # attribute and some people still use the OS. + proctype = Facter::Util::Resolution.exec('lsattr -El proc0 -a type') + if proctype =~ /^type\s+(\S+)\s+/ + processor_list[processor_num] = $1 end end end From 6ec28631f5d0a1d5f3a9f9c4879950089fce5ed2 Mon Sep 17 00:00:00 2001 From: Todd Zullinger Date: Wed, 15 Feb 2012 10:07:11 -0500 Subject: [PATCH 0771/3753] (#12669) Preserve timestamps when installing files Without the preserve option, ruby's FileUtils.install method uses the current time for all installed files. For backup systems, package installs, and general pedantic sysadmins, preserving timestamps makes a small improvement in the world. --- install.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/install.rb b/install.rb index c5c3a11d88..909171f21f 100755 --- a/install.rb +++ b/install.rb @@ -103,7 +103,7 @@ def do_libs(libs, strip = 'lib/') op = File.dirname(olf) FileUtils.makedirs(op, {:mode => 0755, :verbose => true}) FileUtils.chmod(0755, op) - FileUtils.install(lf, olf, {:mode => 0644, :verbose => true}) + FileUtils.install(lf, olf, {:mode => 0644, :preserve => true, :verbose => true}) end end @@ -114,7 +114,7 @@ def do_man(man, strip = 'man/') om = File.dirname(omf) FileUtils.makedirs(om, {:mode => 0755, :verbose => true}) FileUtils.chmod(0755, om) - FileUtils.install(mf, omf, {:mode => 0644, :verbose => true}) + FileUtils.install(mf, omf, {:mode => 0644, :preserve => true, :verbose => true}) gzip = %x{which gzip} gzip.chomp! %x{#{gzip} -f #{omf}} @@ -400,12 +400,12 @@ def install_binfile(from, op_file, target) installed_wrapper = false if File.exists?("#{from}.bat") - FileUtils.install("#{from}.bat", File.join(target, "#{op_file}.bat"), :mode => 0755, :verbose => true) + FileUtils.install("#{from}.bat", File.join(target, "#{op_file}.bat"), :mode => 0755, :preserve => true, :verbose => true) installed_wrapper = true end if File.exists?("#{from}.cmd") - FileUtils.install("#{from}.cmd", File.join(target, "#{op_file}.cmd"), :mode => 0755, :verbose => true) + FileUtils.install("#{from}.cmd", File.join(target, "#{op_file}.cmd"), :mode => 0755, :preserve => true, :verbose => true) installed_wrapper = true end @@ -419,13 +419,13 @@ def install_binfile(from, op_file, target) "%RUBY_BIN%ruby.exe" -x "%RUBY_BIN%facter" %* EOS File.open(tmp_file2.path, "w") { |cw| cw.puts cwv } - FileUtils.install(tmp_file2.path, File.join(target, "#{op_file}.bat"), :mode => 0755, :verbose => true) + FileUtils.install(tmp_file2.path, File.join(target, "#{op_file}.bat"), :mode => 0755, :preserve => true, :verbose => true) tmp_file2.unlink installed_wrapper = true end end - FileUtils.install(tmp_file.path, File.join(target, op_file), :mode => 0755, :verbose => true) + FileUtils.install(tmp_file.path, File.join(target, op_file), :mode => 0755, :preserve => true, :verbose => true) tmp_file.unlink end From b04414cdf984d0aa2c12aa4396dbb7f006c31988 Mon Sep 17 00:00:00 2001 From: Michael Stahnke Date: Thu, 23 Feb 2012 13:47:56 -0800 Subject: [PATCH 0772/3753] Update CHANGELOG for 1.6.6 --- CHANGELOG | 5 +---- conf/redhat/facter.spec | 8 ++++++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 32f41a0245..a2d77cb7b8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,10 +1,7 @@ -1.6.6rc2 +1.6.6 === e046144 Updated CHANGELOG for 1.6.6rc2 c273d34 Make ec2 facts work on CentOS again (#12666) - -1.6.6rc1 -=== c218d84 (#12362) Use Tempfile to generate temp files f6bbe14 (#12170) Adds gem spec description Without this patch, the gem spec file is missing a description attribute, which caus 5c5c330 Changes apple rake task to reflect package name facter instead of puppet. diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index 94f5d1b90d..fcf4e1e261 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -2,11 +2,10 @@ %define has_ruby_abi 0%{?fedora} || 0%{?rhel} >= 5 %define has_ruby_noarch %has_ruby_abi -%global _ver 1.6.5 Summary: Ruby module for collecting simple facts about a host operating system Name: facter -Version: 1.6.5 +Version: 1.6.6 Release: 1%{?dist} #Release: 0.1rc1%{?dist} License: Apache 2.0 @@ -23,6 +22,8 @@ BuildArch: noarch Requires: ruby >= 1.8.1 Requires: which +# Note: dmidecode is only available on x86 and x86_64 so this package may need to move into being +# arch specific if people are using ppc, arm, s390 etc Requires: dmidecode Requires: pciutils %if %has_ruby_abi @@ -58,6 +59,9 @@ rm -rf %{buildroot} %changelog +* Thu Feb 23 2012 Michael Stahnke - 1.6.6-1 +- Update for 1.6.6 + * Wed Jan 25 2012 Matthaus Litteken - 1.6.5-1 - Update to 1.6.5 From 515fd653572eb1296afc63810dd3e551d985dcd2 Mon Sep 17 00:00:00 2001 From: Stefan Schulte Date: Thu, 23 Feb 2012 17:11:13 +0100 Subject: [PATCH 0773/3753] (#11511) Split lsb facts into multiple files If a fact is stored in a file that does not follow the convention $factname.rb we may encounter ordering and recursion issues as seen in bugreport #11511. The concrete example was - flush clears all facts - load_all is triggered to reload facts - inside an .rb file we query the operatingsystem fact directly (say outside a Facter.add block) - the operatingsystem fact has a suitable resolver for linux which wants to query the lsbdistid fact, which is (apperently) not yet loaded (this might not even be predictable) - the loader doesnt find a lsbdistid.rb file so it triggers load_all (remember: we are still trying to get a value for operatingsystem) - the load_all does load other files (like processor.rb) that want to query the architecture fact directly (outside a Facter.add block) - the architecture fact is dependent on the operatingsystem fact, we are currently trying to resolve -> boom: recursion This commit implements one possible fix: Split the lsb facts into differnet files so the loader finds them. We therefore dont have to run load_all in the middle of a fact resolution. --- lib/facter/lsb.rb | 39 ---------------------------- lib/facter/lsbdistcodename.rb | 18 +++++++++++++ lib/facter/lsbdistdescription.rb | 21 +++++++++++++++ lib/facter/lsbdistid.rb | 18 +++++++++++++ lib/facter/lsbdistrelease.rb | 18 +++++++++++++ lib/facter/lsbrelease.rb | 18 +++++++++++++ lib/facter/operatingsystem.rb | 1 - spec/unit/lsbdistcodename_spec.rb | 25 ++++++++++++++++++ spec/unit/lsbdistdescription_spec.rb | 25 ++++++++++++++++++ spec/unit/lsbdistid_spec.rb | 25 ++++++++++++++++++ spec/unit/lsbdistrelease_spec.rb | 25 ++++++++++++++++++ spec/unit/lsbrelease.rb | 25 ++++++++++++++++++ 12 files changed, 218 insertions(+), 40 deletions(-) delete mode 100644 lib/facter/lsb.rb create mode 100644 lib/facter/lsbdistcodename.rb create mode 100644 lib/facter/lsbdistdescription.rb create mode 100644 lib/facter/lsbdistid.rb create mode 100644 lib/facter/lsbdistrelease.rb create mode 100644 lib/facter/lsbrelease.rb create mode 100755 spec/unit/lsbdistcodename_spec.rb create mode 100755 spec/unit/lsbdistdescription_spec.rb create mode 100755 spec/unit/lsbdistid_spec.rb create mode 100755 spec/unit/lsbdistrelease_spec.rb create mode 100755 spec/unit/lsbrelease.rb diff --git a/lib/facter/lsb.rb b/lib/facter/lsb.rb deleted file mode 100644 index 7cefb5ca35..0000000000 --- a/lib/facter/lsb.rb +++ /dev/null @@ -1,39 +0,0 @@ -# Fact: lsb -# -# Purpose: Return Linux Standard Base information for the host. -# -# Resolution: -# Uses the lsb_release system command and parses the output with a series of -# regular expressions. -# -# Caveats: -# Only works on Linux (and the kfreebsd derivative) systems. -# Requires the lsb_release program, which may not be installed by default. -# Also is as only as accurate as that program outputs. - -## lsb.rb -## Facts related to Linux Standard Base (LSB) - -{ "LSBRelease" => %r{^LSB Version:\t(.*)$}, - "LSBDistId" => %r{^Distributor ID:\t(.*)$}, - "LSBDistRelease" => %r{^Release:\t(.*)$}, - "LSBDistDescription" => %r{^Description:\t(.*)$}, - "LSBDistCodeName" => %r{^Codename:\t(.*)$} -}.each do |fact, pattern| - Facter.add(fact) do - confine :kernel => [ :linux, :"gnu/kfreebsd" ] - setcode do - unless defined?(lsbdata) and defined?(lsbtime) and (Time.now.to_i - lsbtime.to_i < 5) - type = nil - lsbtime = Time.now - lsbdata = Facter::Util::Resolution.exec('lsb_release -a 2>/dev/null') - end - - if pattern.match(lsbdata) - $1 - else - nil - end - end - end -end diff --git a/lib/facter/lsbdistcodename.rb b/lib/facter/lsbdistcodename.rb new file mode 100644 index 0000000000..00b514abe1 --- /dev/null +++ b/lib/facter/lsbdistcodename.rb @@ -0,0 +1,18 @@ +# Fact: lsbdistcodename +# +# Purpose: Return Linux Standard Base information for the host. +# +# Resolution: +# Uses the lsb_release system command +# +# Caveats: +# Only works on Linux (and the kfreebsd derivative) systems. +# Requires the lsb_release program, which may not be installed by default. +# Also is as only as accurate as that program outputs. + +Facter.add(:lsbdistcodename) do + confine :kernel => [ :linux, :"gnu/kfreebsd" ] + setcode do + Facter::Util::Resolution.exec('lsb_release -c -s') + end +end diff --git a/lib/facter/lsbdistdescription.rb b/lib/facter/lsbdistdescription.rb new file mode 100644 index 0000000000..fb3d760530 --- /dev/null +++ b/lib/facter/lsbdistdescription.rb @@ -0,0 +1,21 @@ +# Fact: lsbdistdescription +# +# Purpose: Return Linux Standard Base information for the host. +# +# Resolution: +# Uses the lsb_release system command +# +# Caveats: +# Only works on Linux (and the kfreebsd derivative) systems. +# Requires the lsb_release program, which may not be installed by default. +# Also is as only as accurate as that program outputs. + +Facter.add(:lsbdistdescription) do + confine :kernel => [ :linux, :"gnu/kfreebsd" ] + setcode do + if output = Facter::Util::Resolution.exec('lsb_release -d -s') + # the output may be quoted (at least it is on gentoo) + output.sub(/^"(.*)"$/,'\1') + end + end +end diff --git a/lib/facter/lsbdistid.rb b/lib/facter/lsbdistid.rb new file mode 100644 index 0000000000..61d3ac2e8b --- /dev/null +++ b/lib/facter/lsbdistid.rb @@ -0,0 +1,18 @@ +# Fact: lsbdistid +# +# Purpose: Return Linux Standard Base information for the host. +# +# Resolution: +# Uses the lsb_release system command +# +# Caveats: +# Only works on Linux (and the kfreebsd derivative) systems. +# Requires the lsb_release program, which may not be installed by default. +# Also is as only as accurate as that program outputs. + +Facter.add(:lsbdistid) do + confine :kernel => [ :linux, :"gnu/kfreebsd" ] + setcode do + Facter::Util::Resolution.exec('lsb_release -i -s') + end +end diff --git a/lib/facter/lsbdistrelease.rb b/lib/facter/lsbdistrelease.rb new file mode 100644 index 0000000000..4c8c736c7f --- /dev/null +++ b/lib/facter/lsbdistrelease.rb @@ -0,0 +1,18 @@ +# Fact: lsbdistrelease +# +# Purpose: Return Linux Standard Base information for the host. +# +# Resolution: +# Uses the lsb_release system command +# +# Caveats: +# Only works on Linux (and the kfreebsd derivative) systems. +# Requires the lsb_release program, which may not be installed by default. +# Also is as only as accurate as that program outputs. + +Facter.add(:lsbdistrelease) do + confine :kernel => [ :linux, :"gnu/kfreebsd" ] + setcode do + Facter::Util::Resolution.exec('lsb_release -r -s') + end +end diff --git a/lib/facter/lsbrelease.rb b/lib/facter/lsbrelease.rb new file mode 100644 index 0000000000..9b1d5465f9 --- /dev/null +++ b/lib/facter/lsbrelease.rb @@ -0,0 +1,18 @@ +# Fact: lsbrelease +# +# Purpose: Return Linux Standard Base information for the host. +# +# Resolution: +# Uses the lsb_release system command +# +# Caveats: +# Only works on Linux (and the kfreebsd derivative) systems. +# Requires the lsb_release program, which may not be installed by default. +# Also is as only as accurate as that program outputs. + +Facter.add(:lsbrelease) do + confine :kernel => [ :linux, :"gnu/kfreebsd" ] + setcode do + Facter::Util::Resolution.exec('lsb_release -v -s') + end +end diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index be4243eae5..550075fd45 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -10,7 +10,6 @@ # # Caveats: # -require 'facter/lsb' Facter.add(:operatingsystem) do confine :kernel => :sunos diff --git a/spec/unit/lsbdistcodename_spec.rb b/spec/unit/lsbdistcodename_spec.rb new file mode 100755 index 0000000000..fa012a88a4 --- /dev/null +++ b/spec/unit/lsbdistcodename_spec.rb @@ -0,0 +1,25 @@ +#!/usr/bin/env rspec + +require 'spec_helper' + +describe "lsbdistcodename fact" do + + [ "Linux", "GNU/kFreeBSD"].each do |kernel| + describe "on #{kernel}" do + before :each do + Facter.fact(:kernel).stubs(:value).returns kernel + end + + it "should return the codename through lsb_release -c -s" do + Facter::Util::Resolution.stubs(:exec).with('lsb_release -c -s').returns 'n/a' + Facter.fact(:lsbdistcodename).value.should == 'n/a' + end + + it "should return nil if lsb_release is not installed" do + Facter::Util::Resolution.stubs(:exec).with('lsb_release -c -s').returns nil + Facter.fact(:lsbdistcodename).value.should be_nil + end + end + end + +end diff --git a/spec/unit/lsbdistdescription_spec.rb b/spec/unit/lsbdistdescription_spec.rb new file mode 100755 index 0000000000..5202e88e86 --- /dev/null +++ b/spec/unit/lsbdistdescription_spec.rb @@ -0,0 +1,25 @@ +#!/usr/bin/env rspec + +require 'spec_helper' + +describe "lsbdistdescription fact" do + + [ "Linux", "GNU/kFreeBSD"].each do |kernel| + describe "on #{kernel}" do + before :each do + Facter.fact(:kernel).stubs(:value).returns kernel + end + + it "should return the description through lsb_release -d -s" do + Facter::Util::Resolution.stubs(:exec).with('lsb_release -d -s').returns '"Gentoo Base System release 2.1"' + Facter.fact(:lsbdistdescription).value.should == 'Gentoo Base System release 2.1' + end + + it "should return nil if lsb_release is not installed" do + Facter::Util::Resolution.stubs(:exec).with('lsb_release -d -s').returns nil + Facter.fact(:lsbdistdescription).value.should be_nil + end + end + end + +end diff --git a/spec/unit/lsbdistid_spec.rb b/spec/unit/lsbdistid_spec.rb new file mode 100755 index 0000000000..ae208a1ea9 --- /dev/null +++ b/spec/unit/lsbdistid_spec.rb @@ -0,0 +1,25 @@ +#!/usr/bin/env rspec + +require 'spec_helper' + +describe "lsbdistid fact" do + + [ "Linux", "GNU/kFreeBSD"].each do |kernel| + describe "on #{kernel}" do + before :each do + Facter.fact(:kernel).stubs(:value).returns kernel + end + + it "should return the id through lsb_release -i -s" do + Facter::Util::Resolution.stubs(:exec).with('lsb_release -i -s').returns 'Gentoo' + Facter.fact(:lsbdistid).value.should == 'Gentoo' + end + + it "should return nil if lsb_release is not installed" do + Facter::Util::Resolution.stubs(:exec).with('lsb_release -i -s').returns nil + Facter.fact(:lsbdistid).value.should be_nil + end + end + end + +end diff --git a/spec/unit/lsbdistrelease_spec.rb b/spec/unit/lsbdistrelease_spec.rb new file mode 100755 index 0000000000..5a9803d823 --- /dev/null +++ b/spec/unit/lsbdistrelease_spec.rb @@ -0,0 +1,25 @@ +#!/usr/bin/env rspec + +require 'spec_helper' + +describe "lsbdistrelease fact" do + + [ "Linux", "GNU/kFreeBSD"].each do |kernel| + describe "on #{kernel}" do + before :each do + Facter.fact(:kernel).stubs(:value).returns kernel + end + + it "should return the release through lsb_release -r -s" do + Facter::Util::Resolution.stubs(:exec).with('lsb_release -r -s').returns '2.1' + Facter.fact(:lsbdistrelease).value.should == '2.1' + end + + it "should return nil if lsb_release is not installed" do + Facter::Util::Resolution.stubs(:exec).with('lsb_release -r -s').returns nil + Facter.fact(:lsbdistrelease).value.should be_nil + end + end + end + +end diff --git a/spec/unit/lsbrelease.rb b/spec/unit/lsbrelease.rb new file mode 100755 index 0000000000..1406da6133 --- /dev/null +++ b/spec/unit/lsbrelease.rb @@ -0,0 +1,25 @@ +#!/usr/bin/env rspec + +require 'spec_helper' + +describe "lsbrelease fact" do + + [ "Linux", "GNU/kFreeBSD"].each do |kernel| + describe "on #{kernel}" do + before :each do + Facter.fact(:kernel).stubs(:value).returns kernel + end + + it "should return the release through lsb_release -v -s" do + Facter::Util::Resolution.stubs(:exec).with('lsb_release -v -s').returns 'n/a' + Facter.fact(:lsbrelease).value.should == 'n/a' + end + + it "should return nil if lsb_release is not installed" do + Facter::Util::Resolution.stubs(:exec).with('lsb_release -v -s').returns nil + Facter.fact(:lsbrelease).value.should be_nil + end + end + end + +end From a7f592456e5045a8c1a35c0c9675a3df046e387c Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Fri, 24 Feb 2012 13:09:05 -0800 Subject: [PATCH 0774/3753] (#12813) Redirect lspci output to /dev/null On linode instances, or any instance without a working /proc/bus/pci, the following output is produced on stderr: pcilib: Cannot open /proc/bus/pci lspci: Cannot find any working access method. This is very noisy over time, and does not produce anything of value. Redirecting it to /dev/null removes the issue. --- lib/facter/virtual.rb | 2 +- spec/unit/virtual_spec.rb | 42 +++++++++++++++++++-------------------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 1ee41f94b4..ea9e1bde34 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -89,7 +89,7 @@ end if result == "physical" - output = Facter::Util::Resolution.exec('lspci') + output = Facter::Util::Resolution.exec('lspci 2>/dev/null') if not output.nil? output.each_line do |p| # --- look for the vmware video card to determine if it is virtual => vmware. diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index 6ffa1ace5d..bcd840c768 100755 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -81,33 +81,33 @@ Facter.fact(:architecture).stubs(:value).returns(true) end - it "should be parallels with Parallels vendor id from lspci" do + it "should be parallels with Parallels vendor id from lspci 2>/dev/null" do Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter::Util::Resolution.stubs(:exec).with('lspci').returns("01:00.0 VGA compatible controller: Unknown device 1ab8:4005") + Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns("01:00.0 VGA compatible controller: Unknown device 1ab8:4005") Facter.fact(:virtual).value.should == "parallels" end - it "should be parallels with Parallels vendor name from lspci" do + it "should be parallels with Parallels vendor name from lspci 2>/dev/null" do Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter::Util::Resolution.stubs(:exec).with('lspci').returns("01:00.0 VGA compatible controller: Parallels Display Adapter") + Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns("01:00.0 VGA compatible controller: Parallels Display Adapter") Facter.fact(:virtual).value.should == "parallels" end - it "should be vmware with VMware vendor name from lspci" do + it "should be vmware with VMware vendor name from lspci 2>/dev/null" do Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter::Util::Resolution.stubs(:exec).with('lspci').returns("00:0f.0 VGA compatible controller: VMware Inc [VMware SVGA II] PCI Display Adapter") + Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns("00:0f.0 VGA compatible controller: VMware Inc [VMware SVGA II] PCI Display Adapter") Facter.fact(:virtual).value.should == "vmware" end - it "should be virtualbox with VirtualBox vendor name from lspci" do + it "should be virtualbox with VirtualBox vendor name from lspci 2>/dev/null" do Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter::Util::Resolution.stubs(:exec).with('lspci').returns("00:02.0 VGA compatible controller: InnoTek Systemberatung GmbH VirtualBox Graphics Adapter") + Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns("00:02.0 VGA compatible controller: InnoTek Systemberatung GmbH VirtualBox Graphics Adapter") Facter.fact(:virtual).value.should == "virtualbox" end it "should be vmware with VMWare vendor name from dmidecode" do Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) + Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("On Board Device 1 Information\nType: Video\nStatus: Disabled\nDescription: VMware SVGA II") Facter.fact(:virtual).value.should == "vmware" end @@ -131,42 +131,42 @@ Facter.fact(:virtual).value.should == "xenu" end - it "should be xenhvm with Xen HVM vendor name from lspci" do + it "should be xenhvm with Xen HVM vendor name from lspci 2>/dev/null" do Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter::Util::Resolution.stubs(:exec).with('lspci').returns("00:03.0 Unassigned class [ff80]: XenSource, Inc. Xen Platform Device (rev 01)") + Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns("00:03.0 Unassigned class [ff80]: XenSource, Inc. Xen Platform Device (rev 01)") Facter.fact(:virtual).value.should == "xenhvm" end it "should be xenhvm with Xen HVM vendor name from dmidecode" do Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) + Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("System Information\nManufacturer: Xen\nProduct Name: HVM domU") Facter.fact(:virtual).value.should == "xenhvm" end it "should be parallels with Parallels vendor name from dmidecode" do Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) + Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("On Board Device Information\nType: Video\nStatus: Disabled\nDescription: Parallels Video Adapter") Facter.fact(:virtual).value.should == "parallels" end it "should be virtualbox with VirtualBox vendor name from dmidecode" do Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) + Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("BIOS Information\nVendor: innotek GmbH\nVersion: VirtualBox\n\nSystem Information\nManufacturer: innotek GmbH\nProduct Name: VirtualBox\nFamily: Virtual Machine") Facter.fact(:virtual).value.should == "virtualbox" end - it "should be hyperv with Microsoft vendor name from lspci" do + it "should be hyperv with Microsoft vendor name from lspci 2>/dev/null" do Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter::Util::Resolution.stubs(:exec).with('lspci').returns("00:08.0 VGA compatible controller: Microsoft Corporation Hyper-V virtual VGA") + Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns("00:08.0 VGA compatible controller: Microsoft Corporation Hyper-V virtual VGA") Facter.fact(:virtual).value.should == "hyperv" end it "should be hyperv with Microsoft vendor name from dmidecode" do Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) + Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("System Information\nManufacturer: Microsoft Corporation\nProduct Name: Virtual Machine") Facter.fact(:virtual).value.should == "hyperv" end @@ -180,7 +180,7 @@ it "should be vmware with VMWare vendor name from prtdiag" do Facter.fact(:kernel).stubs(:value).returns("SunOS") Facter.fact(:hardwaremodel).stubs(:value).returns(nil) - Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) + Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns(nil) Facter::Util::Resolution.stubs(:exec).with('prtdiag').returns("System Configuration: VMware, Inc. VMware Virtual Platform") Facter.fact(:virtual).value.should == "vmware" @@ -189,7 +189,7 @@ it "should be parallels with Parallels vendor name from prtdiag" do Facter.fact(:kernel).stubs(:value).returns("SunOS") Facter.fact(:hardwaremodel).stubs(:value).returns(nil) - Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) + Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns(nil) Facter::Util::Resolution.stubs(:exec).with('prtdiag').returns("System Configuration: Parallels Virtual Platform") Facter.fact(:virtual).value.should == "parallels" @@ -198,7 +198,7 @@ it "should be virtualbox with VirtualBox vendor name from prtdiag" do Facter.fact(:kernel).stubs(:value).returns("SunOS") Facter.fact(:hardwaremodel).stubs(:value).returns(nil) - Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) + Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns(nil) Facter::Util::Resolution.stubs(:exec).with('prtdiag').returns("System Configuration: innotek GmbH VirtualBox") Facter.fact(:virtual).value.should == "virtualbox" @@ -210,7 +210,7 @@ Facter::Util::Resolution.stubs(:exec).with("vmware -v").returns false Facter.fact(:kernel).stubs(:value).returns("OpenBSD") Facter.fact(:hardwaremodel).stubs(:value).returns(nil) - Facter::Util::Resolution.stubs(:exec).with('lspci').returns(nil) + Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns(nil) end From bdbf3327500685e273bcbb042623aa87a2150e3f Mon Sep 17 00:00:00 2001 From: Chris Gardner Date: Mon, 27 Feb 2012 21:13:40 +0000 Subject: [PATCH 0775/3753] (#12720) Add Solaris CPU info to 'processorN' fact. --- lib/facter/processor.rb | 13 +- lib/facter/util/processor.rb | 19 +++ .../unit/util/processor/solaris-i86pc | 28 ++++ .../unit/util/processor/solaris-sun4u | 151 ++++++++++++++++++ spec/unit/util/processor_spec.rb | 25 ++- 5 files changed, 234 insertions(+), 2 deletions(-) create mode 100644 spec/fixtures/unit/util/processor/solaris-i86pc create mode 100644 spec/fixtures/unit/util/processor/solaris-sun4u diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb index b08a02935e..0b0f0e36f1 100644 --- a/lib/facter/processor.rb +++ b/lib/facter/processor.rb @@ -5,7 +5,8 @@ # # Resolution: # On Linux and kFreeBSD, parse '/proc/cpuinfo' for each processor. -# On AIX, parse the output of 'lsdev' for it's processor section. +# On AIX, parse the output of 'lsdev' for its processor section. +# On Solaris, parse the output of 'kstat' for each processor. # On OpenBSD, use 'uname -p' and the sysctl variable for 'hw.ncpu' for CPU # count. # @@ -81,6 +82,7 @@ ## (but we need them inside the Facter.add block above for tests on processorcount to work) processor_list = Facter::Util::Processor.enum_cpuinfo processor_list_aix = Facter::Util::Processor.enum_lsdev +processor_list_sunos = Facter::Util::Processor.enum_kstat if processor_list.length != 0 processor_list.each_with_index do |desc, i| @@ -100,6 +102,15 @@ end end end +elsif processor_list_sunos.length != 0 + processor_list_sunos.each_with_index do |desc, i| + Facter.add("Processor#{i}") do + confine :kernel => [ :sunos ] + setcode do + desc + end + end + end end if Facter.value(:kernel) == "windows" diff --git a/lib/facter/util/processor.rb b/lib/facter/util/processor.rb index 220b209a6a..3abdb7b731 100644 --- a/lib/facter/util/processor.rb +++ b/lib/facter/util/processor.rb @@ -85,4 +85,23 @@ def self.enum_lsdev end processor_list end + + def self.enum_kstat + processor_num = -1 + processor_list = [] + Thread::exclusive do + kstat = Facter::Util::Resolution.exec('/usr/bin/kstat cpu_info') + if kstat + kstat.each_line do |l| + if l =~ /cpu_info(\d+)/ + processor_num = $1.to_i + elsif l =~ /brand\s+(.*)\s*$/ + processor_list[processor_num] = $1 unless processor_num == -1 + processor_num = -1 + end + end + end + end + processor_list + end end diff --git a/spec/fixtures/unit/util/processor/solaris-i86pc b/spec/fixtures/unit/util/processor/solaris-i86pc new file mode 100644 index 0000000000..879b9b2d34 --- /dev/null +++ b/spec/fixtures/unit/util/processor/solaris-i86pc @@ -0,0 +1,28 @@ +module: cpu_info instance: 0 +name: cpu_info0 class: misc + brand Intel(r) Core(tm) i5 CPU M 450 @ 2.40GHz + cache_id 0 + chip_id 0 + clock_MHz 2375 + clog_id 0 + core_id 0 + cpu_type i386 + crtime 70.899819526 + current_clock_Hz 2374891267 + current_cstate 0 + family 6 + fpu_type i387 compatible + implementation x86 (GenuineIntel family 6 model 37 step 5 clock 2375 MHz) + model 37 + ncore_per_chip 1 + ncpu_per_chip 1 + pg_id -1 + pkg_core_id 0 + snaptime 2279.514858392 + state on-line + state_begin 1329488796 + stepping 5 + supported_frequencies_Hz 2374891267 + supported_max_cstates 1 + vendor_id GenuineIntel + diff --git a/spec/fixtures/unit/util/processor/solaris-sun4u b/spec/fixtures/unit/util/processor/solaris-sun4u new file mode 100644 index 0000000000..cdbfb705b1 --- /dev/null +++ b/spec/fixtures/unit/util/processor/solaris-sun4u @@ -0,0 +1,151 @@ +module: cpu_info instance: 0 +name: cpu_info0 class: misc + brand SPARC64-VII + chip_id 1024 + clock_MHz 2520 + core_id 0 + cpu_fru hc:///component=/MBU_A + cpu_type sparcv9 + crtime 92.5418505 + current_clock_Hz 2520000000 + device_ID 175931055444225 + fpu_type sparcv9 + implementation SPARC64-VII (portid 1024 impl 0x7 ver 0x91 clock 2520 MHz) + pg_id 1 + snaptime 15277456.9660361 + state on-line + state_begin 1314212380 + supported_frequencies_Hz 2520000000 + +module: cpu_info instance: 1 +name: cpu_info1 class: misc + brand SPARC64-VII + chip_id 1024 + clock_MHz 2520 + core_id 0 + cpu_fru hc:///component=/MBU_A + cpu_type sparcv9 + crtime 93.3416172 + current_clock_Hz 2520000000 + device_ID 175931055444225 + fpu_type sparcv9 + implementation SPARC64-VII (portid 1024 impl 0x7 ver 0x91 clock 2520 MHz) + pg_id 1 + snaptime 15277456.9667477 + state on-line + state_begin 1314212381 + supported_frequencies_Hz 2520000000 + +module: cpu_info instance: 2 +name: cpu_info2 class: misc + brand SPARC64-VII + chip_id 1024 + clock_MHz 2520 + core_id 2 + cpu_fru hc:///component=/MBU_A + cpu_type sparcv9 + crtime 93.3433262 + current_clock_Hz 2520000000 + device_ID 175931055444225 + fpu_type sparcv9 + implementation SPARC64-VII (portid 1024 impl 0x7 ver 0x91 clock 2520 MHz) + pg_id 4 + snaptime 15277456.9671387 + state on-line + state_begin 1314212381 + supported_frequencies_Hz 2520000000 + +module: cpu_info instance: 3 +name: cpu_info3 class: misc + brand SPARC64-VII + chip_id 1024 + clock_MHz 2520 + core_id 2 + cpu_fru hc:///component=/MBU_A + cpu_type sparcv9 + crtime 93.3449653 + current_clock_Hz 2520000000 + device_ID 175931055444225 + fpu_type sparcv9 + implementation SPARC64-VII (portid 1024 impl 0x7 ver 0x91 clock 2520 MHz) + pg_id 4 + snaptime 15277456.9675197 + state on-line + state_begin 1314212381 + supported_frequencies_Hz 2520000000 + +module: cpu_info instance: 4 +name: cpu_info4 class: misc + brand SPARC64-VII + chip_id 1024 + clock_MHz 2520 + core_id 4 + cpu_fru hc:///component=/MBU_A + cpu_type sparcv9 + crtime 93.3465648 + current_clock_Hz 2520000000 + device_ID 175931055444225 + fpu_type sparcv9 + implementation SPARC64-VII (portid 1024 impl 0x7 ver 0x91 clock 2520 MHz) + pg_id 5 + snaptime 15277456.9678953 + state on-line + state_begin 1314212381 + supported_frequencies_Hz 2520000000 + +module: cpu_info instance: 5 +name: cpu_info5 class: misc + brand SPARC64-VII + chip_id 1024 + clock_MHz 2520 + core_id 4 + cpu_fru hc:///component=/MBU_A + cpu_type sparcv9 + crtime 93.3481605 + current_clock_Hz 2520000000 + device_ID 175931055444225 + fpu_type sparcv9 + implementation SPARC64-VII (portid 1024 impl 0x7 ver 0x91 clock 2520 MHz) + pg_id 5 + snaptime 15277456.968269 + state on-line + state_begin 1314212381 + supported_frequencies_Hz 2520000000 + +module: cpu_info instance: 6 +name: cpu_info6 class: misc + brand SPARC64-VII + chip_id 1024 + clock_MHz 2520 + core_id 6 + cpu_fru hc:///component=/MBU_A + cpu_type sparcv9 + crtime 93.3497654 + current_clock_Hz 2520000000 + device_ID 175931055444225 + fpu_type sparcv9 + implementation SPARC64-VII (portid 1024 impl 0x7 ver 0x91 clock 2520 MHz) + pg_id 6 + snaptime 15277456.9686422 + state on-line + state_begin 1314212381 + supported_frequencies_Hz 2520000000 + +module: cpu_info instance: 7 +name: cpu_info7 class: misc + brand SPARC64-VII + chip_id 1024 + clock_MHz 2520 + core_id 6 + cpu_fru hc:///component=/MBU_A + cpu_type sparcv9 + crtime 93.3513776 + current_clock_Hz 2520000000 + device_ID 175931055444225 + fpu_type sparcv9 + implementation SPARC64-VII (portid 1024 impl 0x7 ver 0x91 clock 2520 MHz) + pg_id 6 + snaptime 15277456.9690115 + state on-line + state_begin 1314212381 + supported_frequencies_Hz 2520000000 diff --git a/spec/unit/util/processor_spec.rb b/spec/unit/util/processor_spec.rb index 9867f53c55..6e0d5b665c 100755 --- a/spec/unit/util/processor_spec.rb +++ b/spec/unit/util/processor_spec.rb @@ -43,7 +43,7 @@ def cpuinfo_fixture(filename) Facter.fact(:architecture).stubs(:value).returns("amd64") File.stubs(:exists?).with("/proc/cpuinfo").returns(true) File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("amd64quad")) - + Facter::Util::Processor.enum_cpuinfo[0].should == "Quad-Core AMD Opteron(tm) Processor 2374 HE" Facter::Util::Processor.enum_cpuinfo[1].should == "Quad-Core AMD Opteron(tm) Processor 2374 HE" Facter::Util::Processor.enum_cpuinfo[2].should == "Quad-Core AMD Opteron(tm) Processor 2374 HE" @@ -57,4 +57,27 @@ def cpuinfo_fixture(filename) Facter::Util::Processor.enum_lsdev[0].should == "PowerPC_POWER3" end + + it "should get the processor description on Solaris (x86)" do + Facter.fact(:kernel).stubs(:value).returns("SunOS") + Facter.fact(:architecture).stubs(:value).returns("i86pc") + Facter::Util::Resolution.stubs(:exec).with("/usr/bin/kstat cpu_info").returns(my_fixture_read("solaris-i86pc")) + + Facter::Util::Processor.enum_kstat[0].should == "Intel(r) Core(tm) i5 CPU M 450 @ 2.40GHz" + end + + it "should get the processor description on Solaris (SPARC64)" do + Facter.fact(:kernel).stubs(:value).returns("SunOS") + Facter.fact(:architecture).stubs(:value).returns("sun4u") + Facter::Util::Resolution.stubs(:exec).with("/usr/bin/kstat cpu_info").returns(my_fixture_read("solaris-sun4u")) + + Facter::Util::Processor.enum_kstat[0].should == "SPARC64-VII" + Facter::Util::Processor.enum_kstat[1].should == "SPARC64-VII" + Facter::Util::Processor.enum_kstat[2].should == "SPARC64-VII" + Facter::Util::Processor.enum_kstat[3].should == "SPARC64-VII" + Facter::Util::Processor.enum_kstat[4].should == "SPARC64-VII" + Facter::Util::Processor.enum_kstat[5].should == "SPARC64-VII" + Facter::Util::Processor.enum_kstat[6].should == "SPARC64-VII" + Facter::Util::Processor.enum_kstat[7].should == "SPARC64-VII" + end end From 2e7a108d478b2f23a6344ef6b0f8f8c60bd15fa5 Mon Sep 17 00:00:00 2001 From: Stefan Schulte Date: Sat, 25 Feb 2012 14:49:22 +0100 Subject: [PATCH 0776/3753] (#12790) Raise an exception if recursion is detected Facter can already detect fact dependency cycles (fact recursions). Without this patch facter will return nil (or an already cached value) as a factvalue when a recursion is detected. This can silently break things. Example Facter.add(:foo) if Facter.value(:bar) != 'baz' something happens here end end If there is now a recursion when querying the bar fact we will always execute the following codeblock (nil != baz). This may lead to strange and inexplicable results for the foo fact. The fix now changes the behaviour when a recursion is detected: Do not just print a debug message but throw an error. This way a recursion is a lot easier to detect and we can actually fix it. --- lib/facter/util/fact.rb | 11 +---------- spec/integration/facter_spec.rb | 12 ++++++++++++ 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/lib/facter/util/fact.rb b/lib/facter/util/fact.rb index d53a688d5e..1b2e14ef6f 100644 --- a/lib/facter/util/fact.rb +++ b/lib/facter/util/fact.rb @@ -100,16 +100,7 @@ def searching? # Lock our searching process, so we never ge stuck in recursion. def searching - if searching? - Facter.debug "Caught recursion on %s" % @name - - # return a cached value if we've got it - if @value - return @value - else - return nil - end - end + raise RuntimeError, "Caught recursion on #{@name}" if searching? # If we've gotten this far, we're not already searching, so go ahead and do so. @searching = true diff --git a/spec/integration/facter_spec.rb b/spec/integration/facter_spec.rb index 0c52895699..c90c60dd10 100755 --- a/spec/integration/facter_spec.rb +++ b/spec/integration/facter_spec.rb @@ -24,4 +24,16 @@ Facter.reset Facter.collection.should_not equal(old) end + + it "should raise an error if a recursion is detected" do + Facter.clear + Facter.add(:foo) do + confine :bar => 'some_value' + end + Facter.add(:bar) do + confine :foo => 'some_value' + end + lambda { Facter.value(:foo) }.should raise_error(RuntimeError, /Caught recursion on foo/) + end + end From ab9e26c2ec1508659ff86f4996d47cfc9589a787 Mon Sep 17 00:00:00 2001 From: Matthaus Litteken Date: Thu, 22 Mar 2012 13:43:57 -0700 Subject: [PATCH 0777/3753] Updating CHANGELOG and lib/facter.rb for Facter 1.6.7rc1 --- CHANGELOG | 6 ++++++ lib/facter.rb | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index a2d77cb7b8..cad595e2a7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,9 @@ +1.6.7rc1 +=== +bdbf332 (#12720) Add Solaris CPU info to 'processorN' fact. +a7f5924 (#12813) Redirect lspci output to /dev/null +6ec2863 (#12669) Preserve timestamps when installing files + 1.6.6 === e046144 Updated CHANGELOG for 1.6.6rc2 diff --git a/lib/facter.rb b/lib/facter.rb index c960241ead..23d745d72d 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -25,7 +25,7 @@ module Util; end include Comparable include Enumerable - FACTERVERSION = '1.6.6' + FACTERVERSION = '1.6.7' # = Facter # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. From 96256a35bb6143cf0299c98462d4139a6f731e82 Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Thu, 29 Mar 2012 11:37:12 -0700 Subject: [PATCH 0778/3753] Update CHANGELOG for Facter 1.6.7 --- CHANGELOG | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index cad595e2a7..fba1e3217c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,6 @@ -1.6.7rc1 +1.6.7 === +ab9e26c Updating CHANGELOG and lib/facter.rb for Facter 1.6.7rc1 bdbf332 (#12720) Add Solaris CPU info to 'processorN' fact. a7f5924 (#12813) Redirect lspci output to /dev/null 6ec2863 (#12669) Preserve timestamps when installing files From af682ccee83cfe0c7538ededf5ca3fb2bee319af Mon Sep 17 00:00:00 2001 From: Gary Larizza Date: Thu, 5 Apr 2012 14:26:28 -0700 Subject: [PATCH 0779/3753] [#11299] Replace facter/util/plist with cfpropertylist Previously, the facter/util/plist library was used for plist manipulation in OS X. This library could only handle XML plists, and thus we needed to convert every possible plist that Facter could read to XML before reading it. This incurred a great speed hit for anyone using Facter with OS X. The CFPropertyList project (https://github.com/ckruse/cfpropertylist), maintained by Christian Kruse, is a library that can not only read XML and Binary plist files, but also can use either the libxml, nokogiri, or built-in REXML library to do so. This is ideal as the libxml library can utilize c extensions to do the reading/converty and thus is quite fast. This commit introduces the CFPropertyList project into the puppet/util namespace, and refactors all code to use this library. The version committed into Facter is 2.1 of the CFPropertyList library. --- lib/facter/util/cfpropertylist.rb | 6 + lib/facter/util/cfpropertylist/LICENSE | 19 + lib/facter/util/cfpropertylist/README | 44 ++ lib/facter/util/cfpropertylist/Rakefile | 44 ++ lib/facter/util/cfpropertylist/THANKS | 7 + .../util/cfpropertylist/lib/cfpropertylist.rb | 6 + .../lib/rbBinaryCFPropertyList.rb | 562 ++++++++++++++++++ .../util/cfpropertylist/lib/rbCFPlistError.rb | 26 + .../cfpropertylist/lib/rbCFPropertyList.rb | 402 +++++++++++++ .../util/cfpropertylist/lib/rbCFTypes.rb | 244 ++++++++ .../util/cfpropertylist/lib/rbLibXMLParser.rb | 135 +++++ .../cfpropertylist/lib/rbNokogiriParser.rb | 140 +++++ .../util/cfpropertylist/lib/rbREXMLParser.rb | 136 +++++ lib/facter/util/macosx.rb | 17 +- lib/facter/util/plist.rb | 24 - lib/facter/util/plist/generator.rb | 228 ------- lib/facter/util/plist/parser.rb | 226 ------- spec/unit/util/macosx_spec.rb | 37 +- 18 files changed, 1820 insertions(+), 483 deletions(-) create mode 100644 lib/facter/util/cfpropertylist.rb create mode 100644 lib/facter/util/cfpropertylist/LICENSE create mode 100644 lib/facter/util/cfpropertylist/README create mode 100644 lib/facter/util/cfpropertylist/Rakefile create mode 100644 lib/facter/util/cfpropertylist/THANKS create mode 100644 lib/facter/util/cfpropertylist/lib/cfpropertylist.rb create mode 100644 lib/facter/util/cfpropertylist/lib/rbBinaryCFPropertyList.rb create mode 100644 lib/facter/util/cfpropertylist/lib/rbCFPlistError.rb create mode 100644 lib/facter/util/cfpropertylist/lib/rbCFPropertyList.rb create mode 100644 lib/facter/util/cfpropertylist/lib/rbCFTypes.rb create mode 100644 lib/facter/util/cfpropertylist/lib/rbLibXMLParser.rb create mode 100644 lib/facter/util/cfpropertylist/lib/rbNokogiriParser.rb create mode 100644 lib/facter/util/cfpropertylist/lib/rbREXMLParser.rb delete mode 100644 lib/facter/util/plist.rb delete mode 100644 lib/facter/util/plist/generator.rb delete mode 100644 lib/facter/util/plist/parser.rb diff --git a/lib/facter/util/cfpropertylist.rb b/lib/facter/util/cfpropertylist.rb new file mode 100644 index 0000000000..337e0bb3ae --- /dev/null +++ b/lib/facter/util/cfpropertylist.rb @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- + +require File.join(File.dirname(__FILE__), 'cfpropertylist', 'lib', 'rbCFPropertyList.rb') + + +# eof diff --git a/lib/facter/util/cfpropertylist/LICENSE b/lib/facter/util/cfpropertylist/LICENSE new file mode 100644 index 0000000000..ba6ffb28d3 --- /dev/null +++ b/lib/facter/util/cfpropertylist/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2010 Christian Kruse, + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/lib/facter/util/cfpropertylist/README b/lib/facter/util/cfpropertylist/README new file mode 100644 index 0000000000..4763f88fd3 --- /dev/null +++ b/lib/facter/util/cfpropertylist/README @@ -0,0 +1,44 @@ +CFPropertyList implementation +class to read, manipulate and write both XML and binary property list +files (plist(5)) as defined by Apple. Have a look at CFPropertyList::List +for more documentation. + +== Installation + +You could either use ruby gems and install it via + + gem install CFPropertyList + +or you could clone this repository and place it somewhere in your load path. + +== Example + require 'cfpropertylist' + + # create a arbitrary data structure of basic data types + data = { + 'name' => 'John Doe', + 'missing' => true, + 'last_seen' => Time.now, + 'friends' => ['Jane Doe','Julian Doe'], + 'likes' => { + 'me' => false + } + } + + # create CFPropertyList::List object + plist = CFPropertyList::List.new + + # call CFPropertyList.guess() to create corresponding CFType values + plist.value = CFPropertyList.guess(data) + + # write plist to file + plist.save("example.plist", CFPropertyList::List::FORMAT_BINARY) + + # … later, read it again + plist = CFPropertyList::List.new(:file => "example.plist") + data = CFPropertyList.native_types(plist.value) + +Author:: Christian Kruse (mailto:cjk@wwwtech.de) +Copyright:: Copyright (c) 2010 +License:: MIT License + diff --git a/lib/facter/util/cfpropertylist/Rakefile b/lib/facter/util/cfpropertylist/Rakefile new file mode 100644 index 0000000000..6dd90bd8a8 --- /dev/null +++ b/lib/facter/util/cfpropertylist/Rakefile @@ -0,0 +1,44 @@ +require 'rubygems' + +require 'rubygems/package_task' +require 'rdoc/task' +require 'rake/testtask' + +spec = Gem::Specification.new do |s| + s.name = "CFPropertyList" + s.version = "2.1" + s.author = "Christian Kruse" + s.email = "cjk@wwwtech.de" + s.homepage = "http://github.com/ckruse/CFPropertyList" + s.platform = Gem::Platform::RUBY + s.summary = "Read, write and manipulate both binary and XML property lists as defined by apple" + s.description = "This is a module to read, write and manipulate both binary and XML property lists as defined by apple." + s.files = FileList["lib/*"].to_a + s.require_path = "lib" + #s.autorequire = "name" + #s.test_files = FileList["{test}/**/*test.rb"].to_a + s.has_rdoc = true + s.extra_rdoc_files = ["README"] + s.add_development_dependency("rake",">=0.7.0") +end + +desc 'Generate RDoc documentation for the CFPropertyList module.' +Rake::RDocTask.new do |rdoc| + files = ['README', 'LICENSE', 'lib/*.rb'] + rdoc.rdoc_files.add(files) + rdoc.main = 'README' + rdoc.title = 'CFPropertyList RDoc' + rdoc.rdoc_dir = 'doc' + rdoc.options << '--line-numbers' << '--inline-source' << '-c utf8' +end + +Gem::PackageTask.new(spec) do |pkg| + pkg.need_tar = true +end + +Rake::TestTask.new do |test| + test.libs << 'test' + test.test_files = Dir.glob('test/test*.rb') +end + +# eof diff --git a/lib/facter/util/cfpropertylist/THANKS b/lib/facter/util/cfpropertylist/THANKS new file mode 100644 index 0000000000..c981a51af5 --- /dev/null +++ b/lib/facter/util/cfpropertylist/THANKS @@ -0,0 +1,7 @@ +Special thanks to: + +Steve Madsen for providing a lot of performance patches and bugfixes! +Have a look at his Github account: + + + diff --git a/lib/facter/util/cfpropertylist/lib/cfpropertylist.rb b/lib/facter/util/cfpropertylist/lib/cfpropertylist.rb new file mode 100644 index 0000000000..1d7d72e61b --- /dev/null +++ b/lib/facter/util/cfpropertylist/lib/cfpropertylist.rb @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- + +require File.dirname(__FILE__) + '/rbCFPropertyList.rb' + + +# eof \ No newline at end of file diff --git a/lib/facter/util/cfpropertylist/lib/rbBinaryCFPropertyList.rb b/lib/facter/util/cfpropertylist/lib/rbBinaryCFPropertyList.rb new file mode 100644 index 0000000000..817b30c3ad --- /dev/null +++ b/lib/facter/util/cfpropertylist/lib/rbBinaryCFPropertyList.rb @@ -0,0 +1,562 @@ +# -*- coding: utf-8 -*- + +module CFPropertyList + # Binary PList parser class + class Binary + # Read a binary plist file + def load(opts) + @unique_table = {} + @count_objects = 0 + @object_refs = 0 + + @written_object_count = 0 + @object_table = [] + @object_ref_size = 0 + + @offsets = [] + + fd = nil + if(opts.has_key?(:file)) + fd = File.open(opts[:file],"rb") + file = opts[:file] + else + fd = StringIO.new(opts[:data],"rb") + file = "" + end + + # first, we read the trailer: 32 byte from the end + fd.seek(-32,IO::SEEK_END) + buff = fd.read(32) + + offset_size, object_ref_size, number_of_objects, top_object, table_offset = buff.unpack "x6CCx4Nx4Nx4N" + + # after that, get the offset table + fd.seek(table_offset, IO::SEEK_SET) + coded_offset_table = fd.read(number_of_objects * offset_size) + raise CFFormatError.new("#{file}: Format error!") unless coded_offset_table.bytesize == number_of_objects * offset_size + + @count_objects = number_of_objects + + # decode offset table + formats = ["","C*","n*","(H6)*","N*"] + @offsets = coded_offset_table.unpack(formats[offset_size]) + if(offset_size == 3) + 0.upto(@offsets.size-1) { |i| @offsets[i] = @offsets[i].to_i(16) } + end + + @object_ref_size = object_ref_size + val = read_binary_object_at(file,fd,top_object) + + fd.close + val + end + + + # Convert CFPropertyList to binary format; since we have to count our objects we simply unique CFDictionary and CFArray + def to_str(opts={}) + @unique_table = {} + @count_objects = 0 + @object_refs = 0 + + @written_object_count = 0 + @object_table = [] + + @offsets = [] + + binary_str = "bplist00" + + @object_refs = count_object_refs(opts[:root]) + + opts[:root].to_binary(self) + + next_offset = 8 + offsets = @object_table.map do |object| + offset = next_offset + next_offset += object.bytesize + offset + end + binary_str << @object_table.join + + table_offset = next_offset + offset_size = Binary.bytes_needed(table_offset) + + if offset_size < 8 + # Fast path: encode the entire offset array at once. + binary_str << offsets.pack((%w(C n N N)[offset_size - 1]) + '*') + else + # Slow path: host may be little or big endian, must pack each offset + # separately. + offsets.each do |offset| + binary_str << "#{Binary.pack_it_with_size(offset_size,offset)}" + end + end + + binary_str << [offset_size, object_ref_size(@object_refs)].pack("x6CC") + binary_str << [@object_table.size].pack("x4N") + binary_str << [0].pack("x4N") + binary_str << [table_offset].pack("x4N") + + binary_str + end + + def object_ref_size object_refs + Binary.bytes_needed(object_refs) + end + + # read a „null” type (i.e. null byte, marker byte, bool value) + def read_binary_null_type(length) + case length + when 0 then 0 # null byte + when 8 then CFBoolean.new(false) + when 9 then CFBoolean.new(true) + when 15 then 15 # fill type + else + raise CFFormatError.new("unknown null type: #{length}") + end + end + protected :read_binary_null_type + + # read a binary int value + def read_binary_int(fname,fd,length) + if length > 3 + raise CFFormatError.new("Integer greater than 8 bytes: #{length}") + end + + nbytes = 1 << length + + buff = fd.read(nbytes) + + CFInteger.new( + case length + when 0 then buff.unpack("C")[0] + when 1 then buff.unpack("n")[0] + when 2 then buff.unpack("N")[0] + when 3 + hiword,loword = buff.unpack("NN") + if (hiword & 0x80000000) != 0 + # 8 byte integers are always signed, and are negative when bit 63 is + # set. Decoding into either a Fixnum or Bignum is tricky, however, + # because the size of a Fixnum varies among systems, and Ruby + # doesn't consider the number to be negative, and won't sign extend. + -(2**63 - ((hiword & 0x7fffffff) << 32 | loword)) + else + hiword << 32 | loword + end + end + ) + end + protected :read_binary_int + + # read a binary real value + def read_binary_real(fname,fd,length) + raise CFFormatError.new("Real greater than 8 bytes: #{length}") if length > 3 + + nbytes = 1 << length + buff = fd.read(nbytes) + + CFReal.new( + case length + when 0 # 1 byte float? must be an error + raise CFFormatError.new("got #{length+1} byte float, must be an error!") + when 1 # 2 byte float? must be an error + raise CFFormatError.new("got #{length+1} byte float, must be an error!") + when 2 then + buff.reverse.unpack("f")[0] + when 3 then + buff.reverse.unpack("d")[0] + else + fail "unexpected length: #{length}" + end + ) + end + protected :read_binary_real + + # read a binary date value + def read_binary_date(fname,fd,length) + raise CFFormatError.new("Date greater than 8 bytes: #{length}") if length > 3 + + nbytes = 1 << length + buff = fd.read(nbytes) + + CFDate.new( + case length + when 0 then # 1 byte CFDate is an error + raise CFFormatError.new("#{length+1} byte CFDate, error") + when 1 then # 2 byte CFDate is an error + raise CFFormatError.new("#{length+1} byte CFDate, error") + when 2 then + buff.reverse.unpack("f")[0] + when 3 then + buff.reverse.unpack("d")[0] + end, + CFDate::TIMESTAMP_APPLE + ) + end + protected :read_binary_date + + # Read a binary data value + def read_binary_data(fname,fd,length) + CFData.new(read_fd(fd, length), CFData::DATA_RAW) + end + protected :read_binary_data + + def read_fd fd, length + length > 0 ? fd.read(length) : "" + end + + # Read a binary string value + def read_binary_string(fname,fd,length) + buff = read_fd fd, length + @unique_table[buff] = true unless @unique_table.has_key?(buff) + CFString.new(buff) + end + protected :read_binary_string + + # Convert the given string from one charset to another + def Binary.charset_convert(str,from,to="UTF-8") + return str.clone.force_encoding(from).encode(to) if str.respond_to?("encode") + Iconv.conv(to,from,str) + end + + # Count characters considering character set + def Binary.charset_strlen(str,charset="UTF-8") + if str.respond_to?(:encode) + size = str.length + else + utf8_str = Iconv.conv("UTF-8",charset,str) + size = utf8_str.scan(/./mu).size + end + + # UTF-16 code units in the range D800-DBFF are the beginning of + # a surrogate pair, and count as one additional character for + # length calculation. + if charset =~ /^UTF-16/ + if str.respond_to?(:encode) + str.bytes.to_a.each_slice(2) { |pair| size += 1 if (0xd8..0xdb).include?(pair[0]) } + else + str.split('').each_slice(2) { |pair| size += 1 if ("\xd8".."\xdb").include?(pair[0]) } + end + end + + size + end + + # Read a unicode string value, coded as UTF-16BE + def read_binary_unicode_string(fname,fd,length) + # The problem is: we get the length of the string IN CHARACTERS; + # since a char in UTF-16 can be 16 or 32 bit long, we don't really know + # how long the string is in bytes + buff = fd.read(2*length) + + @unique_table[buff] = true unless @unique_table.has_key?(buff) + CFString.new(Binary.charset_convert(buff,"UTF-16BE","UTF-8")) + end + protected :read_binary_unicode_string + + # Read an binary array value, including contained objects + def read_binary_array(fname,fd,length) + ary = [] + + # first: read object refs + if(length != 0) + buff = fd.read(length * @object_ref_size) + objects = buff.unpack(@object_ref_size == 1 ? "C*" : "n*") + + # now: read objects + 0.upto(length-1) do |i| + object = read_binary_object_at(fname,fd,objects[i]) + ary.push object + end + end + + CFArray.new(ary) + end + protected :read_binary_array + + # Read a dictionary value, including contained objects + def read_binary_dict(fname,fd,length) + dict = {} + + # first: read keys + if(length != 0) then + buff = fd.read(length * @object_ref_size) + keys = buff.unpack(@object_ref_size == 1 ? "C*" : "n*") + + # second: read object refs + buff = fd.read(length * @object_ref_size) + objects = buff.unpack(@object_ref_size == 1 ? "C*" : "n*") + + # read real keys and objects + 0.upto(length-1) do |i| + key = read_binary_object_at(fname,fd,keys[i]) + object = read_binary_object_at(fname,fd,objects[i]) + dict[key.value] = object + end + end + + CFDictionary.new(dict) + end + protected :read_binary_dict + + # Read an object type byte, decode it and delegate to the correct reader function + def read_binary_object(fname,fd) + # first: read the marker byte + buff = fd.read(1) + + object_length = buff.unpack("C*") + object_length = object_length[0] & 0xF + + buff = buff.unpack("H*") + object_type = buff[0][0].chr + + if(object_type != "0" && object_length == 15) then + object_length = read_binary_object(fname,fd) + object_length = object_length.value + end + + case object_type + when '0' # null, false, true, fillbyte + read_binary_null_type(object_length) + when '1' # integer + read_binary_int(fname,fd,object_length) + when '2' # real + read_binary_real(fname,fd,object_length) + when '3' # date + read_binary_date(fname,fd,object_length) + when '4' # data + read_binary_data(fname,fd,object_length) + when '5' # byte string, usually utf8 encoded + read_binary_string(fname,fd,object_length) + when '6' # unicode string (utf16be) + read_binary_unicode_string(fname,fd,object_length) + when 'a' # array + read_binary_array(fname,fd,object_length) + when 'd' # dictionary + read_binary_dict(fname,fd,object_length) + end + end + protected :read_binary_object + + # Read an object type byte at position $pos, decode it and delegate to the correct reader function + def read_binary_object_at(fname,fd,pos) + position = @offsets[pos] + fd.seek(position,IO::SEEK_SET) + read_binary_object(fname,fd) + end + protected :read_binary_object_at + + # pack an +int+ of +nbytes+ with size + def Binary.pack_it_with_size(nbytes,int) + case nbytes + when 1 then [int].pack('c') + when 2 then [int].pack('n') + when 4 then [int].pack('N') + when 8 + [int >> 32, int & 0xFFFFFFFF].pack('NN') + else + raise CFFormatError.new("Don't know how to pack #{nbytes} byte integer") + end + end + + def Binary.pack_int_array_with_size(nbytes, array) + case nbytes + when 1 then array.pack('C*') + when 2 then array.pack('n*') + when 4 then array.pack('N*') + when 8 + array.map { |int| [int >> 32, int & 0xFFFFFFFF].pack('NN') }.join + else + raise CFFormatError.new("Don't know how to pack #{nbytes} byte integer") + end + end + + # calculate how many bytes are needed to save +count+ + def Binary.bytes_needed(count) + case + when count < 2**8 then 1 + when count < 2**16 then 2 + when count < 2**32 then 4 + when count < 2**64 then 8 + else + raise CFFormatError.new("Data size too large: #{count}") + end + end + + # Create a type byte for binary format as defined by apple + def Binary.type_bytes(type, length) + if length < 15 + [(type << 4) | length].pack('C') + else + bytes = [(type << 4) | 0xF] + if length <= 0xFF + bytes.push(0x10, length).pack('CCC') # 1 byte length + elsif length <= 0xFFFF + bytes.push(0x11, length).pack('CCn') # 2 byte length + elsif length <= 0xFFFFFFFF + bytes.push(0x12, length).pack('CCN') # 4 byte length + elsif length <= 0x7FFFFFFFFFFFFFFF + bytes.push(0x13, length >> 32, length & 0xFFFFFFFF).pack('CCNN') # 8 byte length + else + raise CFFormatError.new("Integer too large: #{int}") + end + end + end + + def count_object_refs(object) + case object + when CFArray + contained_refs = 0 + object.value.each do |element| + if CFArray === element || CFDictionary === element + contained_refs += count_object_refs(element) + end + end + return object.value.size + contained_refs + when CFDictionary + contained_refs = 0 + object.value.each_value do |value| + if CFArray === value || CFDictionary === value + contained_refs += count_object_refs(value) + end + end + return object.value.keys.size * 2 + contained_refs + else + return 0 + end + end + + def Binary.ascii_string?(str) + if str.respond_to?(:ascii_only?) + str.ascii_only? + else + str !~ /[\x80-\xFF]/mn + end + end + + # Uniques and transforms a string value to binary format and adds it to the object table + def string_to_binary(val) + val = val.to_s + + @unique_table[val] ||= begin + if !Binary.ascii_string?(val) + utf8_strlen = Binary.charset_strlen(val, "UTF-8") + val = Binary.charset_convert(val,"UTF-8","UTF-16BE") + bdata = Binary.type_bytes(0b0110, Binary.charset_strlen(val,"UTF-16BE")) + + val.force_encoding("ASCII-8BIT") if val.respond_to?("encode") + @object_table[@written_object_count] = bdata << val + else + utf8_strlen = val.bytesize + bdata = Binary.type_bytes(0b0101,val.bytesize) + @object_table[@written_object_count] = bdata << val + end + @written_object_count += 1 + @written_object_count - 1 + end + end + + # Codes an integer to binary format + def int_to_binary(value) + nbytes = 0 + nbytes = 1 if value > 0xFF # 1 byte integer + nbytes += 1 if value > 0xFFFF # 4 byte integer + nbytes += 1 if value > 0xFFFFFFFF # 8 byte integer + nbytes = 3 if value < 0 # 8 byte integer, since signed + + Binary.type_bytes(0b0001, nbytes) << + if nbytes < 3 + [value].pack( + if nbytes == 0 then "C" + elsif nbytes == 1 then "n" + else "N" + end + ) + else + # 64 bit signed integer; we need the higher and the lower 32 bit of the value + high_word = value >> 32 + low_word = value & 0xFFFFFFFF + [high_word,low_word].pack("NN") + end + end + + # Codes a real value to binary format + def real_to_binary(val) + Binary.type_bytes(0b0010,3) << [val].pack("d").reverse + end + + # Converts a numeric value to binary and adds it to the object table + def num_to_binary(value) + @object_table[@written_object_count] = + if value.is_a?(CFInteger) + int_to_binary(value.value) + else + real_to_binary(value.value) + end + + @written_object_count += 1 + @written_object_count - 1 + end + + # Convert date value (apple format) to binary and adds it to the object table + def date_to_binary(val) + val = val.getutc.to_f - CFDate::DATE_DIFF_APPLE_UNIX # CFDate is a real, number of seconds since 01/01/2001 00:00:00 GMT + + @object_table[@written_object_count] = + (Binary.type_bytes(0b0011, 3) << [val].pack("d").reverse) + + @written_object_count += 1 + @written_object_count - 1 + end + + # Convert a bool value to binary and add it to the object table + def bool_to_binary(val) + + @object_table[@written_object_count] = val ? "\x9" : "\x8" # 0x9 is 1001, type indicator for true; 0x8 is 1000, type indicator for false + @written_object_count += 1 + @written_object_count - 1 + end + + # Convert data value to binary format and add it to the object table + def data_to_binary(val) + @object_table[@written_object_count] = + (Binary.type_bytes(0b0100, val.bytesize) << val) + + @written_object_count += 1 + @written_object_count - 1 + end + + # Convert array to binary format and add it to the object table + def array_to_binary(val) + saved_object_count = @written_object_count + @written_object_count += 1 + #@object_refs += val.value.size + + values = val.value.map { |v| v.to_binary(self) } + bdata = Binary.type_bytes(0b1010, val.value.size) << + Binary.pack_int_array_with_size(object_ref_size(@object_refs), + values) + + @object_table[saved_object_count] = bdata + saved_object_count + end + + # Convert dictionary to binary format and add it to the object table + def dict_to_binary(val) + saved_object_count = @written_object_count + @written_object_count += 1 + + #@object_refs += val.value.keys.size * 2 + + keys_and_values = val.value.keys.map { |k| CFString.new(k).to_binary(self) } + keys_and_values.concat(val.value.values.map { |v| v.to_binary(self) }) + + bdata = Binary.type_bytes(0b1101,val.value.size) << + Binary.pack_int_array_with_size(object_ref_size(@object_refs), keys_and_values) + + @object_table[saved_object_count] = bdata + return saved_object_count + end + end +end + +# eof diff --git a/lib/facter/util/cfpropertylist/lib/rbCFPlistError.rb b/lib/facter/util/cfpropertylist/lib/rbCFPlistError.rb new file mode 100644 index 0000000000..07ee1c141f --- /dev/null +++ b/lib/facter/util/cfpropertylist/lib/rbCFPlistError.rb @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# +# Exceptions used: +# CFPlistError:: General base exception +# CFFormatError:: Format error +# CFTypeError:: Type error +# +# Easy and simple :-) +# +# Author:: Christian Kruse (mailto:cjk@wwwtech.de) +# Copyright:: Copyright (c) 2010 +# License:: MIT License + +# general plist error. All exceptions thrown are derived from this class. +class CFPlistError < Exception +end + +# Exception thrown when format errors occur +class CFFormatError < CFPlistError +end + +# Exception thrown when type errors occur +class CFTypeError < CFPlistError +end + +# eof diff --git a/lib/facter/util/cfpropertylist/lib/rbCFPropertyList.rb b/lib/facter/util/cfpropertylist/lib/rbCFPropertyList.rb new file mode 100644 index 0000000000..2d3c015134 --- /dev/null +++ b/lib/facter/util/cfpropertylist/lib/rbCFPropertyList.rb @@ -0,0 +1,402 @@ +# -*- coding: utf-8 -*- + +require 'kconv' +require 'date' +require 'time' + +# +# CFPropertyList implementation +# +# class to read, manipulate and write both XML and binary property list +# files (plist(5)) as defined by Apple. Have a look at CFPropertyList::List +# for more documentation. +# +# == Example +# require 'cfpropertylist' +# +# # create a arbitrary data structure of basic data types +# data = { +# 'name' => 'John Doe', +# 'missing' => true, +# 'last_seen' => Time.now, +# 'friends' => ['Jane Doe','Julian Doe'], +# 'likes' => { +# 'me' => false +# } +# } +# +# # create CFPropertyList::List object +# plist = CFPropertyList::List.new +# +# # call CFPropertyList.guess() to create corresponding CFType values +# # pass in optional :convert_unknown_to_string => true to convert things like symbols into strings. +# plist.value = CFPropertyList.guess(data) +# +# # write plist to file +# plist.save("example.plist", CFPropertyList::List::FORMAT_BINARY) +# +# # … later, read it again +# plist = CFPropertyList::List.new(:file => "example.plist") +# data = CFPropertyList.native_types(plist.value) +# +# Author:: Christian Kruse (mailto:cjk@wwwtech.de) +# Copyright:: Copyright (c) 2010 +# License:: MIT License +module CFPropertyList + # interface class for PList parsers + class ParserInterface + # load a plist + def load(opts={}) + return "" + end + + # convert a plist to string + def to_str(opts={}) + return true + end + end + + class XMLParserInterface < ParserInterface + def new_node(name) + end + + def new_text(val) + end + + def append_node(parent, child) + end + end +end + +class String + unless("".respond_to?(:blob) && "".respond_to?(:blob=)) then + # The blob status of this string (to set to true if a binary string) + attr_accessor :blob + end + + unless("".respond_to?(:blob?)) then + # Returns whether or not +str+ is a blob. + # @return [true,false] If true, this string contains binary data. If false, its a regular string + def blob? + blob + end + end + + unless("".respond_to?(:bytesize)) then + def bytesize + self.length + end + end +end + +dirname = File.dirname(__FILE__) +require dirname + '/rbCFPlistError.rb' +require dirname + '/rbCFTypes.rb' +require dirname + '/rbBinaryCFPropertyList.rb' + +require 'iconv' unless "".respond_to?("encode") + +begin + Enumerable::Enumerator.new([]) +rescue NameError => e + module Enumerable + class Enumerator + end + end +end + +begin + require dirname + '/rbLibXMLParser.rb' + try_nokogiri = false +rescue LoadError => e + try_nokogiri = true +end + +if try_nokogiri then + begin + require dirname + '/rbNokogiriParser.rb' + rescue LoadError => e + require dirname + '/rbREXMLParser.rb' + end +end + + +module CFPropertyList + # Create CFType hierarchy by guessing the correct CFType, e.g. + # + # x = { + # 'a' => ['b','c','d'] + # } + # cftypes = CFPropertyList.guess(x) + # + # pass optional options hash. Only possible value actually: + # +convert_unknown_to_string+:: Convert unknown objects to string calling to_str() + # +converter_method+:: Convert unknown objects to known objects calling +method_name+ + # + # cftypes = CFPropertyList.guess(x,:convert_unknown_to_string => true,:converter_method => :to_hash, :converter_with_opts => true) + def guess(object, options = {}) + case object + when Fixnum, Integer then CFInteger.new(object) + when Float then CFReal.new(object) + when TrueClass, FalseClass then CFBoolean.new(object) + + when String + object.blob? ? CFData.new(object, CFData::DATA_RAW) : CFString.new(object) + + when Time, DateTime, Date then CFDate.new(object) + + when Array, Enumerator, Enumerable::Enumerator + ary = Array.new + object.each do |o| + ary.push CFPropertyList.guess(o, options) + end + CFArray.new(ary) + + when Hash + hsh = Hash.new + object.each_pair do |k,v| + k = k.to_s if k.is_a?(Symbol) + hsh[k] = CFPropertyList.guess(v, options) + end + CFDictionary.new(hsh) + else + case + when Object.const_defined?('BigDecimal') && object.is_a?(BigDecimal) + CFReal.new(object) + when object.respond_to?(:read) + CFData.new(object.read(), CFData::DATA_RAW) + when options[:converter_method] && object.respond_to?(options[:converter_method]) + if options[:converter_with_opts] + CFPropertyList.guess(object.send(options[:converter_method],options),options) + else + CFPropertyList.guess(object.send(options[:converter_method]),options) + end + when options[:convert_unknown_to_string] + CFString.new(object.to_s) + else + raise CFTypeError.new("Unknown class #{object.class.to_s}. Try using :convert_unknown_to_string if you want to use unknown object types!") + end + end + end + + # Converts a CFType hiercharchy to native Ruby types + def native_types(object,keys_as_symbols=false) + return if object.nil? + + if(object.is_a?(CFDate) || object.is_a?(CFString) || object.is_a?(CFInteger) || object.is_a?(CFReal) || object.is_a?(CFBoolean)) then + return object.value + elsif(object.is_a?(CFData)) then + return object.decoded_value + elsif(object.is_a?(CFArray)) then + ary = [] + object.value.each do + |v| + ary.push CFPropertyList.native_types(v) + end + + return ary + elsif(object.is_a?(CFDictionary)) then + hsh = {} + object.value.each_pair do + |k,v| + k = k.to_sym if keys_as_symbols + hsh[k] = CFPropertyList.native_types(v) + end + + return hsh + end + end + + module_function :guess, :native_types + + # Class representing a CFPropertyList. Instanciate with #new + class List + # Format constant for binary format + FORMAT_BINARY = 1 + + # Format constant for XML format + FORMAT_XML = 2 + + # Format constant for automatic format recognizing + FORMAT_AUTO = 0 + + @@parsers = [Binary,XML] + + # Path of PropertyList + attr_accessor :filename + # Path of PropertyList + attr_accessor :format + # the root value in the plist file + attr_accessor :value + + # initialize a new CFPropertyList, arguments are: + # + # :file:: Parse a file + # :format:: Format is one of FORMAT_BINARY or FORMAT_XML. Defaults to FORMAT_AUTO + # :data:: Parse a string + # + # All arguments are optional + def initialize(opts={}) + @filename = opts[:file] + @format = opts[:format] || FORMAT_AUTO + @data = opts[:data] + + load(@filename) unless @filename.nil? + load_str(@data) unless @data.nil? + end + + # Load an XML PropertyList + # filename = nil:: The filename to read from; if nil, read from the file defined by instance variable +filename+ + def load_xml(filename=nil) + load(filename,List::FORMAT_XML) + end + + # read a binary plist file + # filename = nil:: The filename to read from; if nil, read from the file defined by instance variable +filename+ + def load_binary(filename=nil) + load(filename,List::FORMAT_BINARY) + end + + # load a plist from a XML string + # str:: The string containing the plist + def load_xml_str(str=nil) + load_str(str,List::FORMAT_XML) + end + + # load a plist from a binary string + # str:: The string containing the plist + def load_binary_str(str=nil) + load_str(str,List::FORMAT_BINARY) + end + + # load a plist from a string + # str = nil:: The string containing the plist + # format = nil:: The format of the plist + def load_str(str=nil,format=nil) + str = @data if str.nil? + format = @format if format.nil? + + @value = {} + case format + when List::FORMAT_BINARY, List::FORMAT_XML then + prsr = @@parsers[format-1].new + @value = prsr.load({:data => str}) + + when List::FORMAT_AUTO then # what we now do is ugly, but neccessary to recognize the file format + filetype = str[0..5] + version = str[6..7] + + prsr = nil + if filetype == "bplist" then + raise CFFormatError.new("Wong file version #{version}") unless version == "00" + prsr = Binary.new + else + prsr = XML.new + end + + @value = prsr.load({:data => str}) + end + end + + # Read a plist file + # file = nil:: The filename of the file to read. If nil, use +filename+ instance variable + # format = nil:: The format of the plist file. Auto-detect if nil + def load(file=nil,format=nil) + file = @filename if file.nil? + format = @format if format.nil? + @value = {} + + raise IOError.new("File #{file} not readable!") unless File.readable? file + + case format + when List::FORMAT_BINARY, List::FORMAT_XML then + prsr = @@parsers[format-1].new + @value = prsr.load({:file => file}) + + when List::FORMAT_AUTO then # what we now do is ugly, but neccessary to recognize the file format + magic_number = IO.read(file,8) + filetype = magic_number[0..5] + version = magic_number[6..7] + + prsr = nil + if filetype == "bplist" then + raise CFFormatError.new("Wong file version #{version}") unless version == "00" + prsr = Binary.new + else + prsr = XML.new + end + + @value = prsr.load({:file => file}) + end + end + + # Serialize CFPropertyList object to specified format and write it to file + # file = nil:: The filename of the file to write to. Uses +filename+ instance variable if nil + # format = nil:: The format to save in. Uses +format+ instance variable if nil + def save(file=nil,format=nil,opts={}) + format = @format if format.nil? + file = @filename if file.nil? + + raise CFFormatError.new("Format #{format} not supported, use List::FORMAT_BINARY or List::FORMAT_XML") if format != FORMAT_BINARY && format != FORMAT_XML + + if(!File.exists?(file)) then + raise IOError.new("File #{file} not writable!") unless File.writable?(File.dirname(file)) + elsif(!File.writable?(file)) then + raise IOError.new("File #{file} not writable!") + end + + opts[:root] = @value + prsr = @@parsers[format-1].new + content = prsr.to_str(opts) + + File.open(file, 'wb') { + |fd| + fd.write content + } + end + + # convert plist to string + # format = List::FORMAT_BINARY:: The format to save the plist + # opts={}:: Pass parser options + def to_str(format=List::FORMAT_BINARY,opts={}) + prsr = @@parsers[format-1].new + opts[:root] = @value + return prsr.to_str(opts) + end + end +end + +class Array + # convert an array to plist format + def to_plist(options={}) + options[:plist_format] ||= CFPropertyList::List::FORMAT_BINARY + + plist = CFPropertyList::List.new + plist.value = CFPropertyList.guess(self, options) + plist.to_str(options[:plist_format]) + end +end + +class Enumerator + # convert an array to plist format + def to_plist(options={}) + options[:plist_format] ||= CFPropertyList::List::FORMAT_BINARY + + plist = CFPropertyList::List.new + plist.value = CFPropertyList.guess(self, options) + plist.to_str(options[:plist_format]) + end +end + +class Hash + # convert a hash to plist format + def to_plist(options={}) + options[:plist_format] ||= CFPropertyList::List::FORMAT_BINARY + + plist = CFPropertyList::List.new + plist.value = CFPropertyList.guess(self, options) + plist.to_str(options[:plist_format]) + end +end + +# eof diff --git a/lib/facter/util/cfpropertylist/lib/rbCFTypes.rb b/lib/facter/util/cfpropertylist/lib/rbCFTypes.rb new file mode 100644 index 0000000000..a8754b8368 --- /dev/null +++ b/lib/facter/util/cfpropertylist/lib/rbCFTypes.rb @@ -0,0 +1,244 @@ +# -*- coding: utf-8 -*- +# +# CFTypes, e.g. CFString, CFInteger +# needed to create unambiguous plists +# +# Author:: Christian Kruse (mailto:cjk@wwwtech.de) +# Copyright:: Copyright (c) 2009 +# License:: MIT License + +require 'base64' + +module CFPropertyList + # This class defines the base class for all CFType classes + # + class CFType + # value of the type + attr_accessor :value + + def initialize(value=nil) + @value = value + end + + def to_xml(parser) + end + + def to_binary(bplist) end + end + + # This class holds string values, both, UTF-8 and UTF-16BE + # It will convert the value to UTF-16BE if necessary (i.e. if non-ascii char contained) + class CFString < CFType + # convert to XML + def to_xml(parser) + n = parser.new_node('string') + n = parser.append_node(n, parser.new_text(@value)) unless @value.nil? + n + end + + # convert to binary + def to_binary(bplist) + bplist.string_to_binary(@value); + end + end + + # This class holds integer/fixnum values + class CFInteger < CFType + # convert to XML + def to_xml(parser) + n = parser.new_node('integer') + n = parser.append_node(n, parser.new_text(@value.to_s)) + n + end + + # convert to binary + def to_binary(bplist) + bplist.num_to_binary(self) + end + end + + # This class holds float values + class CFReal < CFType + # convert to XML + def to_xml(parser) + n = parser.new_node('real') + n = parser.append_node(n, parser.new_text(@value.to_s)) + n + end + + # convert to binary + def to_binary(bplist) + bplist.num_to_binary(self) + end + end + + # This class holds Time values. While Apple uses seconds since 2001, + # the rest of the world uses seconds since 1970. So if you access value + # directly, you get the Time class. If you access via get_value you either + # geht the timestamp or the Apple timestamp + class CFDate < CFType + TIMESTAMP_APPLE = 0 + TIMESTAMP_UNIX = 1; + DATE_DIFF_APPLE_UNIX = 978307200 + + # create a XML date strimg from a time object + def CFDate.date_string(val) + # 2009-05-13T20:23:43Z + val.getutc.strftime("%Y-%m-%dT%H:%M:%SZ") + end + + # parse a XML date string + def CFDate.parse_date(val) + # 2009-05-13T20:23:43Z + val =~ %r{^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z$} + year,month,day,hour,min,sec = $1, $2, $3, $4, $5, $6 + return Time.utc(year,month,day,hour,min,sec).getlocal + end + + # set value to defined state + def initialize(value = nil,format=CFDate::TIMESTAMP_UNIX) + if(value.is_a?(Time) || value.nil?) then + @value = value.nil? ? Time.now : value + elsif value.instance_of? Date + @value = Time.utc(value.year, value.month, value.day, 0, 0, 0) + elsif value.instance_of? DateTime + @value = value.to_time.utc + else + set_value(value,format) + end + end + + # set value with timestamp, either Apple or UNIX + def set_value(value,format=CFDate::TIMESTAMP_UNIX) + if(format == CFDate::TIMESTAMP_UNIX) then + @value = Time.at(value) + else + @value = Time.at(value + CFDate::DATE_DIFF_APPLE_UNIX) + end + end + + # get timestamp, either UNIX or Apple timestamp + def get_value(format=CFDate::TIMESTAMP_UNIX) + if(format == CFDate::TIMESTAMP_UNIX) then + @value.to_i + else + @value.to_f - CFDate::DATE_DIFF_APPLE_UNIX + end + end + + # convert to XML + def to_xml(parser) + n = parser.new_node('date') + n = parser.append_node(n, parser.new_text(CFDate::date_string(@value))) + n + end + + # convert to binary + def to_binary(bplist) + bplist.date_to_binary(@value) + end + end + + # This class contains a boolean value + class CFBoolean < CFType + # convert to XML + def to_xml(parser) + parser.new_node(@value ? 'true' : 'false') + end + + # convert to binary + def to_binary(bplist) + bplist.bool_to_binary(@value); + end + end + + # This class contains binary data values + class CFData < CFType + # Base64 encoded data + DATA_BASE64 = 0 + # Raw data + DATA_RAW = 1 + + # set value to defined state, either base64 encoded or raw + def initialize(value=nil,format=DATA_BASE64) + if(format == DATA_RAW) + @raw_value = value + @raw_value.blob = true + else + @value = value + end + end + + # get base64 encoded value + def encoded_value + @value ||= "\n#{Base64.encode64(@raw_value).gsub("\n", '').scan(/.{1,76}/).join("\n")}\n" + end + + # get base64 decoded value + def decoded_value + @raw_value ||= String.new(Base64.decode64(@value)) + @raw_value.blob = true + @raw_value + end + + # convert to XML + def to_xml(parser) + n = parser.new_node('data') + n = parser.append_node(n, parser.new_text(encoded_value())) + n + end + + # convert to binary + def to_binary(bplist) + bplist.data_to_binary(decoded_value()) + end + end + + # This class contains an array of values + class CFArray < CFType + # create a new array CFType + def initialize(val=[]) + @value = val + end + + # convert to XML + def to_xml(parser) + n = parser.new_node('array') + @value.each do |v| + n = parser.append_node(n, v.to_xml(parser)) + end + n + end + + # convert to binary + def to_binary(bplist) + bplist.array_to_binary(self) + end + end + + # this class contains a hash of values + class CFDictionary < CFType + # Create new CFDictonary type. + def initialize(value={}) + @value = value + end + + # convert to XML + def to_xml(parser) + n = parser.new_node('dict') + @value.each_pair do |key, value| + k = parser.append_node(parser.new_node('key'), parser.new_text(key.to_s)) + n = parser.append_node(n, k) + n = parser.append_node(n, value.to_xml(parser)) + end + n + end + + # convert to binary + def to_binary(bplist) + bplist.dict_to_binary(self) + end + end +end + +# eof diff --git a/lib/facter/util/cfpropertylist/lib/rbLibXMLParser.rb b/lib/facter/util/cfpropertylist/lib/rbLibXMLParser.rb new file mode 100644 index 0000000000..9ffb05fdac --- /dev/null +++ b/lib/facter/util/cfpropertylist/lib/rbLibXMLParser.rb @@ -0,0 +1,135 @@ +# -*- coding: utf-8 -*- + +require 'libxml' + +module CFPropertyList + # XML parser + class XML < XMLParserInterface + # read a XML file + # opts:: + # * :file - The filename of the file to load + # * :data - The data to parse + def load(opts) + if(opts.has_key?(:file)) then + doc = LibXML::XML::Document.file(opts[:file],:options => LibXML::XML::Parser::Options::NOBLANKS|LibXML::XML::Parser::Options::NOENT) + else + doc = LibXML::XML::Document.string(opts[:data],:options => LibXML::XML::Parser::Options::NOBLANKS|LibXML::XML::Parser::Options::NOENT) + end + + root = doc.root.first + return import_xml(root) + end + + # serialize CFPropertyList object to XML + # opts = {}:: Specify options: :formatted - Use indention and line breaks + def to_str(opts={}) + doc = LibXML::XML::Document.new + + doc.root = LibXML::XML::Node.new('plist') + doc.encoding = LibXML::XML::Encoding::UTF_8 + + doc.root['version'] = '1.0' + doc.root << opts[:root].to_xml(self) + + # ugly hack, but there's no other possibility I know + str = doc.to_s(:indent => opts[:formatted]) + str1 = String.new + first = false + str.each_line do |line| + str1 << line + unless(first) then + str1 << "\n" if line =~ /^\s*<\?xml/ + end + + first = true + end + + str1.force_encoding('UTF-8') if str1.respond_to?(:force_encoding) + return str1 + end + + def new_node(name) + LibXML::XML::Node.new(name) + end + + def new_text(val) + LibXML::XML::Node.new_text(val) + end + + def append_node(parent, child) + parent << child + end + + protected + + # get the value of a DOM node + def get_value(n) + content = if n.children? + n.first.content + else + n.content + end + + content.force_encoding('UTF-8') if content.respond_to?(:force_encoding) + content + end + + # import the XML values + def import_xml(node) + ret = nil + + case node.name + when 'dict' + hsh = Hash.new + key = nil + + if node.children? then + node.children.each do |n| + next if n.text? # avoid a bug of libxml + next if n.comment? + + if n.name == "key" then + key = get_value(n) + else + raise CFFormatError.new("Format error!") if key.nil? + hsh[key] = import_xml(n) + key = nil + end + end + end + + ret = CFDictionary.new(hsh) + + when 'array' + ary = Array.new + + if node.children? then + node.children.each do |n| + ary.push import_xml(n) + end + end + + ret = CFArray.new(ary) + + when 'true' + ret = CFBoolean.new(true) + when 'false' + ret = CFBoolean.new(false) + when 'real' + ret = CFReal.new(get_value(node).to_f) + when 'integer' + ret = CFInteger.new(get_value(node).to_i) + when 'string' + ret = CFString.new(get_value(node)) + when 'data' + ret = CFData.new(get_value(node)) + when 'date' + ret = CFDate.new(CFDate.parse_date(get_value(node))) + end + + return ret + end + end +end + +# eof diff --git a/lib/facter/util/cfpropertylist/lib/rbNokogiriParser.rb b/lib/facter/util/cfpropertylist/lib/rbNokogiriParser.rb new file mode 100644 index 0000000000..ff82defc6d --- /dev/null +++ b/lib/facter/util/cfpropertylist/lib/rbNokogiriParser.rb @@ -0,0 +1,140 @@ +# -*- coding: utf-8 -*- + +require 'nokogiri' + +module CFPropertyList + # XML parser + class XML < ParserInterface + # read a XML file + # opts:: + # * :file - The filename of the file to load + # * :data - The data to parse + def load(opts) + if(opts.has_key?(:file)) then + File.open(opts[:file], "rb") { |fd| doc = Nokogiri::XML::Document.parse(fd, nil, nil, Nokogiri::XML::ParseOptions::NOBLANKS|Nokogiri::XML::ParseOptions::NOENT) } + else + doc = Nokogiri::XML::Document.parse(opts[:data], nil, nil, Nokogiri::XML::ParseOptions::NOBLANKS|Nokogiri::XML::ParseOptions::NOENT) + end + + root = doc.root.children.first + return import_xml(root) + end + + # serialize CFPropertyList object to XML + # opts = {}:: Specify options: :formatted - Use indention and line breaks + def to_str(opts={}) + doc = Nokogiri::XML::Document.new + @doc = doc + + doc.root = doc.create_element 'plist', :version => '1.0' + doc.encoding = 'UTF-8' + + doc.root << opts[:root].to_xml(self) + + # ugly hack, but there's no other possibility I know + s_opts = Nokogiri::XML::Node::SaveOptions::AS_XML + s_opts |= Nokogiri::XML::Node::SaveOptions::FORMAT if opts[:formatted] + + str = doc.serialize(:save_with => s_opts) + str1 = String.new + first = false + str.each_line do |line| + str1 << line + unless(first) then + str1 << "\n" if line =~ /^\s*<\?xml/ + end + + first = true + end + + str1.force_encoding('UTF-8') if str1.respond_to?(:force_encoding) + return str1 + end + + def new_node(name) + @doc.create_element name + end + + def new_text(val) + @doc.create_text_node val + end + + def append_node(parent, child) + parent << child + end + + protected + + # get the value of a DOM node + def get_value(n) + content = if n.children.empty? + n.content + else + n.children.first.content + end + + content.force_encoding('UTF-8') if content.respond_to?(:force_encoding) + content + end + + # import the XML values + def import_xml(node) + ret = nil + + case node.name + when 'dict' + hsh = Hash.new + key = nil + children = node.children + + unless children.empty? then + children.each do |n| + next if n.text? # avoid a bug of libxml + next if n.comment? + + if n.name == "key" then + key = get_value(n) + else + raise CFFormatError.new("Format error!") if key.nil? + hsh[key] = import_xml(n) + key = nil + end + end + end + + ret = CFDictionary.new(hsh) + + when 'array' + ary = Array.new + children = node.children + + unless children.empty? then + children.each do |n| + ary.push import_xml(n) + end + end + + ret = CFArray.new(ary) + + when 'true' + ret = CFBoolean.new(true) + when 'false' + ret = CFBoolean.new(false) + when 'real' + ret = CFReal.new(get_value(node).to_f) + when 'integer' + ret = CFInteger.new(get_value(node).to_i) + when 'string' + ret = CFString.new(get_value(node)) + when 'data' + ret = CFData.new(get_value(node)) + when 'date' + ret = CFDate.new(CFDate.parse_date(get_value(node))) + end + + return ret + end + end +end + +# eof diff --git a/lib/facter/util/cfpropertylist/lib/rbREXMLParser.rb b/lib/facter/util/cfpropertylist/lib/rbREXMLParser.rb new file mode 100644 index 0000000000..da6f8e460c --- /dev/null +++ b/lib/facter/util/cfpropertylist/lib/rbREXMLParser.rb @@ -0,0 +1,136 @@ +# -*- coding: utf-8 -*- + +require 'rexml/document' + +module CFPropertyList + # XML parser + class XML < ParserInterface + # read a XML file + # opts:: + # * :file - The filename of the file to load + # * :data - The data to parse + def load(opts) + if(opts.has_key?(:file)) then + File.open(opts[:file], "rb") { |fd| doc = REXML::Document.new(fd) } + else + doc = REXML::Document.new(opts[:data]) + end + + root = doc.root.elements[1] + return import_xml(root) + end + + # serialize CFPropertyList object to XML + # opts = {}:: Specify options: :formatted - Use indention and line breaks + def to_str(opts={}) + doc = REXML::Document.new + @doc = doc + + doc.context[:attribute_quote] = :quote + + doc.add_element 'plist', {'version' => '1.0'} + doc.root << opts[:root].to_xml(self) + + formatter = if opts[:formatted] then + f = REXML::Formatters::Pretty.new(2) + f.compact = true + f + else + REXML::Formatters::Default.new + end + + str = formatter.write(doc.root, "") + str1 = "\n\n" + str + "\n" + str1.force_encoding('UTF-8') if str1.respond_to?(:force_encoding) + + return str1 + end + + def new_node(name) + #LibXML::XML::Node.new(name) + REXML::Element.new(name) + end + + def new_text(val) + val + end + + def append_node(parent, child) + if child.is_a?(String) then + parent.add_text child + else + parent.elements << child + end + parent + end + + protected + + # get the value of a DOM node + def get_value(n) + content = n.text + + content.force_encoding('UTF-8') if content.respond_to?(:force_encoding) + content + end + + # import the XML values + def import_xml(node) + ret = nil + + case node.name + when 'dict' + hsh = Hash.new + key = nil + + if node.has_elements? then + node.elements.each do |n| + #print n.name + "\n" + next if n.name == '#text' # avoid a bug of libxml + next if n.name == '#comment' + + if n.name == "key" then + key = get_value(n) + else + raise CFFormatError.new("Format error!") if key.nil? + hsh[key] = import_xml(n) + key = nil + end + end + end + + ret = CFDictionary.new(hsh) + + when 'array' + ary = Array.new + + if node.has_elements? then + node.elements.each do |n| + ary.push import_xml(n) + end + end + + ret = CFArray.new(ary) + + when 'true' + ret = CFBoolean.new(true) + when 'false' + ret = CFBoolean.new(false) + when 'real' + ret = CFReal.new(get_value(node).to_f) + when 'integer' + ret = CFInteger.new(get_value(node).to_i) + when 'string' + ret = CFString.new(get_value(node)) + when 'data' + ret = CFData.new(get_value(node)) + when 'date' + ret = CFDate.new(CFDate.parse_date(get_value(node))) + end + + return ret + end + end +end + +# eof diff --git a/lib/facter/util/macosx.rb b/lib/facter/util/macosx.rb index dffa102a48..0b93a2ad15 100644 --- a/lib/facter/util/macosx.rb +++ b/lib/facter/util/macosx.rb @@ -7,9 +7,11 @@ module Facter::Util::Macosx require 'thread' - require 'facter/util/plist' + require 'facter/util/cfpropertylist' require 'facter/util/resolution' + Plist_Xml_Doctype = '' + # JJM I'd really like to dynamically generate these methods # by looking at the _name key of the _items dict for each _dataType @@ -19,7 +21,18 @@ def self.profiler_xml(data_field) def self.intern_xml(xml) return nil unless xml - Plist::parse_xml(xml) + bad_xml_doctype = /^.* e + fail("A plist file could not be properly read by CFPropertyList: #{e.inspect}") + end + CFPropertyList.native_types(plist.value) end # Return an xml result, modified as we need it. diff --git a/lib/facter/util/plist.rb b/lib/facter/util/plist.rb deleted file mode 100644 index 32e9e2bf7f..0000000000 --- a/lib/facter/util/plist.rb +++ /dev/null @@ -1,24 +0,0 @@ -#-- -############################################################## -# Copyright 2006, Ben Bleything and # -# Patrick May # -# # -# Distributed under the MIT license. # -############################################################## -#++ -# = Plist -# -# This is the main file for plist. Everything interesting happens in Plist and Plist::Emit. - -require 'base64' -require 'cgi' -require 'stringio' - -require 'facter/util/plist/generator' -require 'facter/util/plist/parser' - -module Plist - VERSION = '3.0.0' -end - -# $Id: plist.rb 1781 2006-10-16 01:01:35Z luke $ diff --git a/lib/facter/util/plist/generator.rb b/lib/facter/util/plist/generator.rb deleted file mode 100644 index 8f9ea3a47f..0000000000 --- a/lib/facter/util/plist/generator.rb +++ /dev/null @@ -1,228 +0,0 @@ -#--########################################################### -# Copyright 2006, Ben Bleything and # -# Patrick May # -# # -# Distributed under the MIT license. # -############################################################## -#++ -# See Plist::Emit. -module Plist - # === Create a plist - # You can dump an object to a plist in one of two ways: - # - # * Plist::Emit.dump(obj) - # * obj.to_plist - # * This requires that you mixin the Plist::Emit module, which is already done for +Array+ and +Hash+. - # - # The following Ruby classes are converted into native plist types: - # Array, Bignum, Date, DateTime, Fixnum, Float, Hash, Integer, String, Symbol, Time, true, false - # * +Array+ and +Hash+ are both recursive; their elements will be converted into plist nodes inside the and containers (respectively). - # * +IO+ (and its descendants) and +StringIO+ objects are read from and their contents placed in a element. - # * User classes may implement +to_plist_node+ to dictate how they should be serialized; otherwise the object will be passed to Marshal.dump and the result placed in a element. - # - # For detailed usage instructions, refer to USAGE[link:files/docs/USAGE.html] and the methods documented below. - module Emit - # Helper method for injecting into classes. Calls Plist::Emit.dump with +self+. - def to_plist(envelope = true) - return Plist::Emit.dump(self, envelope) - end - - # Helper method for injecting into classes. Calls Plist::Emit.save_plist with +self+. - def save_plist(filename) - Plist::Emit.save_plist(self, filename) - end - - # The following Ruby classes are converted into native plist types: - # Array, Bignum, Date, DateTime, Fixnum, Float, Hash, Integer, String, Symbol, Time - # - # Write us (via RubyForge) if you think another class can be coerced safely into one of the expected plist classes. - # - # +IO+ and +StringIO+ objects are encoded and placed in elements; other objects are Marshal.dump'ed unless they implement +to_plist_node+. - # - # The +envelope+ parameters dictates whether or not the resultant plist fragment is wrapped in the normal XML/plist header and footer. Set it to false if you only want the fragment. - def self.dump(obj, envelope = true) - output = plist_node(obj) - - output = wrap(output) if envelope - - return output - end - - # Writes the serialized object's plist to the specified filename. - def self.save_plist(obj, filename) - File.open(filename, 'wb') do |f| - f.write(obj.to_plist) - end - end - - private - def self.plist_node(element) - output = '' - - if element.respond_to? :to_plist_node - output << element.to_plist_node - else - case element - when Array - if element.empty? - output << "\n" - else - output << tag('array') { - element.collect {|e| plist_node(e)} - } - end - when Hash - if element.empty? - output << "\n" - else - inner_tags = [] - - element.keys.sort.each do |k| - v = element[k] - inner_tags << tag('key', CGI::escapeHTML(k.to_s)) - inner_tags << plist_node(v) - end - - output << tag('dict') { - inner_tags - } - end - when true, false - output << "<#{element}/>\n" - when Time - output << tag('date', element.utc.strftime('%Y-%m-%dT%H:%M:%SZ')) - when Date # also catches DateTime - output << tag('date', element.strftime('%Y-%m-%dT%H:%M:%SZ')) - when String, Symbol, Fixnum, Bignum, Integer, Float - output << tag(element_type(element), CGI::escapeHTML(element.to_s)) - when IO, StringIO - element.rewind - contents = element.read - # note that apple plists are wrapped at a different length then - # what ruby's base64 wraps by default. - # I used #encode64 instead of #b64encode (which allows a length arg) - # because b64encode is b0rked and ignores the length arg. - data = "\n" - Base64::encode64(contents).gsub(/\s+/, '').scan(/.{1,68}/o) { data << $& << "\n" } - output << tag('data', data) - else - output << comment( 'The element below contains a Ruby object which has been serialized with Marshal.dump.' ) - data = "\n" - Base64::encode64(Marshal.dump(element)).gsub(/\s+/, '').scan(/.{1,68}/o) { data << $& << "\n" } - output << tag('data', data ) - end - end - - return output - end - - def self.comment(content) - return "\n" - end - - def self.tag(type, contents = '', &block) - out = nil - - if block_given? - out = IndentedString.new - out << "<#{type}>" - out.raise_indent - - out << block.call - - out.lower_indent - out << "" - else - out = "<#{type}>#{contents.to_s}\n" - end - - return out.to_s - end - - def self.wrap(contents) - output = '' - - output << '' + "\n" - output << '' + "\n" - output << '' + "\n" - - output << contents - - output << '' + "\n" - - return output - end - - def self.element_type(item) - return case item - when String, Symbol; 'string' - when Fixnum, Bignum, Integer; 'integer' - when Float; 'real' - else - raise "Don't know about this data type... something must be wrong!" - end - end - - private - - class IndentedString #:nodoc: - attr_accessor :indent_string - - @@indent_level = 0 - - def initialize(str = "\t") - @indent_string = str - @contents = '' - end - - def to_s - return @contents - end - - def raise_indent - @@indent_level += 1 - end - - def lower_indent - @@indent_level -= 1 if @@indent_level > 0 - end - - def <<(val) - if val.is_a? Array - val.each do |f| - self << f - end - else - # if it's already indented, don't bother indenting further - unless val =~ /\A#{@indent_string}/ - indent = @indent_string * @@indent_level - - @contents << val.gsub(/^/, indent) - else - @contents << val - end - - # it already has a newline, don't add another - @contents << "\n" unless val =~ /\n$/ - end - end - end - end -end - -# we need to add this so sorting hash keys works properly -class Symbol #:nodoc: - def <=> (other) - self.to_s <=> other.to_s - end -end - -class Array #:nodoc: - include Plist::Emit -end - -class Hash #:nodoc: - include Plist::Emit -end - -# $Id: generator.rb 1781 2006-10-16 01:01:35Z luke $ diff --git a/lib/facter/util/plist/parser.rb b/lib/facter/util/plist/parser.rb deleted file mode 100644 index ffbb7ba69a..0000000000 --- a/lib/facter/util/plist/parser.rb +++ /dev/null @@ -1,226 +0,0 @@ -#--########################################################### -# Copyright 2006, Ben Bleything and # -# Patrick May # -# # -# Distributed under the MIT license. # -############################################################## -#++ -# Plist parses Mac OS X xml property list files into ruby data structures. -# -# === Load a plist file -# This is the main point of the library: -# -# r = Plist::parse_xml( filename_or_xml ) -module Plist -# Note that I don't use these two elements much: -# -# + Date elements are returned as DateTime objects. -# + Data elements are implemented as Tempfiles -# -# Plist::parse_xml will blow up if it encounters a data element. -# If you encounter such an error, or if you have a Date element which -# can't be parsed into a Time object, please send your plist file to -# plist@hexane.org so that I can implement the proper support. - def Plist::parse_xml( filename_or_xml ) - listener = Listener.new - #parser = REXML::Parsers::StreamParser.new(File.new(filename), listener) - parser = StreamParser.new(filename_or_xml, listener) - parser.parse - listener.result - end - - class Listener - #include REXML::StreamListener - - attr_accessor :result, :open - - def initialize - @result = nil - @open = Array.new - end - - - def tag_start(name, attributes) - @open.push PTag::mappings[name].new - end - - def text( contents ) - @open.last.text = contents if @open.last - end - - def tag_end(name) - last = @open.pop - if @open.empty? - @result = last.to_ruby - else - @open.last.children.push last - end - end - end - - class StreamParser - def initialize( filename_or_xml, listener ) - @filename_or_xml = filename_or_xml - @listener = listener - end - - TEXT = /([^<]+)/ - XMLDECL_PATTERN = /<\?xml\s+(.*?)\?>*/um - DOCTYPE_PATTERN = /\s*)/um - COMMENT_START = /\A/um - - def parse - plist_tags = PTag::mappings.keys.join('|') - start_tag = /<(#{plist_tags})([^>]*)>/i - end_tag = /<\/(#{plist_tags})[^>]*>/i - - require 'strscan' - - contents = ( - if (File.exists? @filename_or_xml) - File.open(@filename_or_xml) {|f| f.read} - else - @filename_or_xml - end - ) - - @scanner = StringScanner.new( contents ) - until @scanner.eos? - if @scanner.scan(COMMENT_START) - @scanner.scan(COMMENT_END) - elsif @scanner.scan(XMLDECL_PATTERN) - elsif @scanner.scan(DOCTYPE_PATTERN) - elsif @scanner.scan(start_tag) - @listener.tag_start(@scanner[1], nil) - if (@scanner[2] =~ /\/$/) - @listener.tag_end(@scanner[1]) - end - elsif @scanner.scan(TEXT) - @listener.text(@scanner[1]) - elsif @scanner.scan(end_tag) - @listener.tag_end(@scanner[1]) - else - raise "Unimplemented element" - end - end - end - end - - class PTag - @@mappings = { } - def PTag::mappings - @@mappings - end - - def PTag::inherited( sub_class ) - key = sub_class.to_s.downcase - key.gsub!(/^plist::/, '' ) - key.gsub!(/^p/, '') unless key == "plist" - - @@mappings[key] = sub_class - end - - attr_accessor :text, :children - def initialize - @children = Array.new - end - - def to_ruby - raise "Unimplemented: " + self.class.to_s + "#to_ruby on #{self.inspect}" - end - end - - class PList < PTag - def to_ruby - children.first.to_ruby if children.first - end - end - - class PDict < PTag - def to_ruby - dict = Hash.new - key = nil - - children.each do |c| - if key.nil? - key = c.to_ruby - else - dict[key] = c.to_ruby - key = nil - end - end - - dict - end - end - - class PKey < PTag - def to_ruby - CGI::unescapeHTML(text || '') - end - end - - class PString < PTag - def to_ruby - CGI::unescapeHTML(text || '') - end - end - - class PArray < PTag - def to_ruby - children.collect do |c| - c.to_ruby - end - end - end - - class PInteger < PTag - def to_ruby - text.to_i - end - end - - class PTrue < PTag - def to_ruby - true - end - end - - class PFalse < PTag - def to_ruby - false - end - end - - class PReal < PTag - def to_ruby - text.to_f - end - end - - require 'date' - class PDate < PTag - def to_ruby - DateTime.parse(text) - end - end - - require 'base64' - class PData < PTag - def to_ruby - data = Base64.decode64(text.gsub(/\s+/, '')) - - begin - return Marshal.load(data) - rescue Exception => e - io = StringIO.new - io.write data - io.rewind - return io - end - end - end -end - -# $Id: parser.rb 1781 2006-10-16 01:01:35Z luke $ diff --git a/spec/unit/util/macosx_spec.rb b/spec/unit/util/macosx_spec.rb index 688cfdb4f6..32647e2074 100755 --- a/spec/unit/util/macosx_spec.rb +++ b/spec/unit/util/macosx_spec.rb @@ -4,15 +4,46 @@ require 'facter/util/macosx' describe Facter::Util::Macosx do + let(:badplist) do + ' + + + + test + file + + ' + end + + let(:goodplist) do + ' + + + + test + file + + ' + end + it "should be able to retrieve profiler data as xml for a given data field" do Facter::Util::Resolution.expects(:exec).with("/usr/sbin/system_profiler -xml foo").returns "yay" Facter::Util::Macosx.profiler_xml("foo").should == "yay" end - it "should use PList to convert xml to data structures" do - Plist.expects(:parse_xml).with("foo").returns "bar" + it 'should correct a bad XML doctype string' do + Facter.expects(:debug).with('Had to fix plist with incorrect DOCTYPE declaration') + Facter::Util::Macosx.intern_xml(badplist) + end + + it 'should return a hash given XML data' do + test_hash = { 'test' => 'file' } + Facter::Util::Macosx.intern_xml(goodplist).should == test_hash + end - Facter::Util::Macosx.intern_xml("foo").should == "bar" + it 'should fail when trying to read invalid XML' do + expect { Facter::Util::Macosx.intern_xml('xml<--->') }.should \ + raise_error(RuntimeError, /A plist file could not be properly read by CFPropertyList/) end describe "when collecting profiler data" do From 70be9573ba3aa7af518774c3687492f5b23e8003 Mon Sep 17 00:00:00 2001 From: Stefan Schulte Date: Sat, 25 Feb 2012 15:47:14 +0100 Subject: [PATCH 0780/3753] (#12831) Fix recursion on first kernel fact resolution We encounter a recursion if we want to detect the kernel fact for the first time: The kernel codeblock calls Facter::Util::Resolution.exec("uname -s") and Facter::Util::Resolution#exec wants to detect if we can use `which` to get the full path of the command. But the method Facter::Util::Resolution#have_which tries to query the kernel fact again to check if we are on windows. Change the check in have_which so we dont have to query the kernel fact. --- lib/facter/util/resolution.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index a5f65c3fc4..8b629571c7 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -15,7 +15,7 @@ class Facter::Util::Resolution def self.have_which if ! defined?(@have_which) or @have_which.nil? - if Facter.value(:kernel) == 'windows' + if Facter::Util::Config.is_windows? @have_which = false else %x{which which >/dev/null 2>&1} From b86fe4cccbb72f25bb9b4c08403d9aac52ed5249 Mon Sep 17 00:00:00 2001 From: Stefan Schulte Date: Fri, 6 Apr 2012 22:30:59 +0200 Subject: [PATCH 0781/3753] (#12831) Add rspec tests to have_which method in Resolution Tests cases were originally provided by Ken Barber: Previously we had no coverage of this important method. This adds very basic testing, including failure testing for Windows. --- lib/facter/util/resolution.rb | 2 +- spec/unit/util/resolution_spec.rb | 34 +++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 8b629571c7..0829b100b7 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -19,7 +19,7 @@ def self.have_which @have_which = false else %x{which which >/dev/null 2>&1} - @have_which = ($? == 0) + @have_which = $?.success? end end @have_which diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index 3fbf19e15d..eba0352e67 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -295,4 +295,38 @@ Facter::Util::Resolution.exec("echo foo").should == "foo" end end + + describe "have_which" do + before :each do + Facter::Util::Resolution.instance_variable_set(:@have_which, nil) + + # we do not execute anything in the following test cases itself + # but we rely on $? to be an instance of Process::Status. So + # just execute anything here to make sure that $? is not nil + %x{echo foo} + end + + it "on windows should always return false" do + Facter::Util::Config.stubs(:is_windows?).returns(true) + Facter::Util::Resolution.expects(:`). + with('which which >/dev/null 2>&1').never + Facter::Util::Resolution.have_which.should == false + end + + it "on other platforms than windows should return true if which exists" do + Facter::Util::Config.stubs(:is_windows?).returns(false) + Facter::Util::Resolution.expects(:`). + with('which which >/dev/null 2>&1').returns('') + Process::Status.any_instance.stubs(:success?).returns true + Facter::Util::Resolution.have_which.should == true + end + + it "on other platforms than windows should return false if which returns non-zero exit code" do + Facter::Util::Config.stubs(:is_windows?).returns(false) + Facter::Util::Resolution.expects(:`). + with('which which >/dev/null 2>&1').returns('') + Process::Status.any_instance.stubs(:success?).returns false + Facter::Util::Resolution.have_which.should == false + end + end end From 74ba03ae2207bbc7f403dd9c3ab873d256f1193f Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Fri, 13 Apr 2012 09:51:02 -0700 Subject: [PATCH 0782/3753] Update CHANGELOG lib/facter.rb for 1.6.8rc1 --- CHANGELOG | 5 +++++ lib/facter.rb | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index fba1e3217c..471a08b324 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,8 @@ +1.6.8rc1 +=== +b86fe4c (#12831) Add rspec tests to have_which method in Resolution +70be957 (#12831) Fix recursion on first kernel fact resolution + 1.6.7 === ab9e26c Updating CHANGELOG and lib/facter.rb for Facter 1.6.7rc1 diff --git a/lib/facter.rb b/lib/facter.rb index 23d745d72d..b197c75c90 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -25,7 +25,7 @@ module Util; end include Comparable include Enumerable - FACTERVERSION = '1.6.7' + FACTERVERSION = '1.6.8' # = Facter # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. From 165ace476731cf9283de6e90152ae623a77e72e8 Mon Sep 17 00:00:00 2001 From: Stefan Schulte Date: Sat, 7 Apr 2012 00:33:43 +0200 Subject: [PATCH 0783/3753] (#13678) Convert command to absolute paths before executing The former behaviour of facter is to allow passing commands to Facter::Util::Resolution.exec where the executable is not an absolute pathname. To check if the executable is present on the system facter would then run %{which commandname}. As a result we end up with two shell invocations just to run a single command. The fix now tries to convert the executable of the command into an absolute pathname so we can easiliy check with the File.executable? method if we can try to run the command. The two methods `which` and `absolute_path?` are adopted from puppet which claim to run on both windows and unix systems. This is great because we do not have to distinct between the platforms in the exec method anymore. --- lib/facter/util/resolution.rb | 88 ++++++++++------ spec/unit/util/resolution_spec.rb | 167 +++++++++++++++++++++++++----- 2 files changed, 194 insertions(+), 61 deletions(-) diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 0829b100b7..b8454ecf13 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -13,18 +13,63 @@ class Facter::Util::Resolution INTERPRETER = Facter::Util::Config.is_windows? ? "cmd.exe" : "/bin/sh" - def self.have_which - if ! defined?(@have_which) or @have_which.nil? - if Facter::Util::Config.is_windows? - @have_which = false - else - %x{which which >/dev/null 2>&1} - @have_which = $?.success? + def self.search_paths + if Facter::Util::Config.is_windows? + ENV['PATH'].split(File::PATH_SEPARATOR) + else + # Make sure facter is usable even for non-root users. Most commands + # in /sbin (like ifconfig) can be run as non priviledged users as + # long as they do not modify anything - which we do not do with facter + ENV['PATH'].split(File::PATH_SEPARATOR) + [ '/sbin', '/usr/sbin' ] + end + end + + # taken from puppet: lib/puppet/util.rb + def self.which(bin) + if absolute_path?(bin) + return bin if File.executable?(bin) + else + search_paths.each do |dir| + dest = File.join(dir, bin) + if Facter::Util::Config.is_windows? && File.extname(dest).empty? + exts = ENV['PATHEXT'] + exts = exts ? exts.split(File::PATH_SEPARATOR) : %w[.COM .EXE .BAT .CMD] + exts.each do |ext| + destext = dest + ext + return destext if File.executable?(destext) + end + end + return dest if File.executable?(dest) end end - @have_which + nil + end + + # taken from puppet: lib/puppet/util.rb + def self.absolute_path?(path, platform=nil) + # Escape once for the string literal, and once for the regex. + slash = '[\\\\/]' + name = '[^\\\\/]+' + regexes = { + :windows => %r!^(([A-Z]:#{slash})|(#{slash}#{slash}#{name}#{slash}#{name})|(#{slash}#{slash}\?#{slash}#{name}))!i, + :posix => %r!^/!, + } + platform ||= Facter::Util::Config.is_windows? ? :windows : :posix + + !! (path =~ regexes[platform]) end + def self.expand_command(command) + if match = /^"([^"]+)"(?:\s+(.*))?/.match(command) + exe, arguments = match.captures + exe = which(exe) and [ "\"#{exe}\"", arguments ].compact.join(" ") + else + exe, arguments = command.split(/ /,2) + exe = which(exe) and [ exe, arguments ].compact.join(" ") + end + end + + # Execute a program and return the output of that program. # # Returns nil if the program can't be found, or if there is a problem @@ -33,37 +78,12 @@ def self.have_which def self.exec(code, interpreter = nil) Facter.warnonce "The interpreter parameter to 'exec' is deprecated and will be removed in a future version." if interpreter - # Try to guess whether the specified code can be executed by looking at the - # first word. If it cannot be found on the PATH defer on resolving the fact - # by returning nil. - # This only fails on shell built-ins, most of which are masked by stuff in - # /bin or of dubious value anyways. In the worst case, "sh -c 'builtin'" can - # be used to work around this limitation - # - # Windows' %x{} throws Errno::ENOENT when the command is not found, so we - # can skip the check there. This is good, since builtins cannot be found - # elsewhere. - if have_which and !Facter::Util::Config.is_windows? - path = nil - binary = code.split.first - if code =~ /^\// - path = binary - else - path = %x{which #{binary} 2>/dev/null}.chomp - # we don't have the binary necessary - return nil if path == "" or path.match(/Command not found\./) - end - - return nil unless FileTest.exists?(path) - end + return nil unless code = expand_command(code) out = nil begin out = %x{#{code}}.chomp - rescue Errno::ENOENT => detail - # command not found on Windows - return nil rescue => detail $stderr.puts detail return nil diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index eba0352e67..36db3a3780 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -284,6 +284,45 @@ Facter::Util::Resolution.should respond_to(:exec) end + # taken from puppet: spec/unit/util_spec.rb + describe "#absolute_path?" do + describe "when run on unix" do + before :each do + Facter::Util::Config.stubs(:is_windows?).returns false + end + + %w[/ /foo /foo/../bar //foo //Server/Foo/Bar //?/C:/foo/bar /\Server/Foo /foo//bar/baz].each do |path| + it "should return true for #{path}" do + Facter::Util::Resolution.should be_absolute_path(path) + end + end + + %w[. ./foo \foo C:/foo \\Server\Foo\Bar \\?\C:\foo\bar \/?/foo\bar \/Server/foo foo//bar/baz].each do |path| + it "should return false for #{path}" do + Facter::Util::Resolution.should_not be_absolute_path(path) + end + end + end + + describe "when run on windows" do + before :each do + Facter::Util::Config.stubs(:is_windows?).returns true + end + + %w[C:/foo C:\foo \\\\Server\Foo\Bar \\\\?\C:\foo\bar //Server/Foo/Bar //?/C:/foo/bar /\?\C:/foo\bar \/Server\Foo/Bar c:/foo//bar//baz].each do |path| + it "should return true for #{path}" do + Facter::Util::Resolution.should be_absolute_path(path) + end + end + + %w[/ . ./foo \foo /foo /foo/../bar //foo C:foo/bar foo//bar/baz].each do |path| + it "should return false for #{path}" do + Facter::Util::Resolution.should_not be_absolute_path(path) + end + end + end + end + # It's not possible, AFAICT, to mock %x{}, so I can't really test this bit. describe "when executing code" do it "should deprecate the interpreter parameter" do @@ -294,39 +333,113 @@ it "should execute the binary" do Facter::Util::Resolution.exec("echo foo").should == "foo" end - end - describe "have_which" do - before :each do - Facter::Util::Resolution.instance_variable_set(:@have_which, nil) + describe "when run on unix" do + before :each do + Facter::Util::Config.stubs(:is_windows?).returns false + end - # we do not execute anything in the following test cases itself - # but we rely on $? to be an instance of Process::Status. So - # just execute anything here to make sure that $? is not nil - %x{echo foo} - end + describe "and the binary is an absolute path" do + it "should run the command if the binary is found" do + File.expects(:executable?).with('/usr/bin/uname').returns true + Facter::Util::Resolution.expects(:`).with('/usr/bin/uname -a').returns "x86_64\n" + Facter::Util::Resolution.exec('/usr/bin/uname -a').should == 'x86_64' + end - it "on windows should always return false" do - Facter::Util::Config.stubs(:is_windows?).returns(true) - Facter::Util::Resolution.expects(:`). - with('which which >/dev/null 2>&1').never - Facter::Util::Resolution.have_which.should == false - end + # taken from the ip fact + it "should run more complicated shell expression" do + File.expects(:executable?).with('/sbin/arp').returns true + Facter::Util::Resolution.expects(:`).with('/sbin/arp -en -i eth0 | sed -e 1d').returns "some_data\n" + Facter::Util::Resolution.exec('/sbin/arp -en -i eth0 | sed -e 1d').should == 'some_data' + end - it "on other platforms than windows should return true if which exists" do - Facter::Util::Config.stubs(:is_windows?).returns(false) - Facter::Util::Resolution.expects(:`). - with('which which >/dev/null 2>&1').returns('') - Process::Status.any_instance.stubs(:success?).returns true - Facter::Util::Resolution.have_which.should == true + it "should not run the command if the binary is not present" do + File.expects(:executable?).with('/usr/bin/uname').returns false + Facter::Util::Resolution.expects(:`).with('/usr/bin/uname -a').never + Facter::Util::Resolution.exec('/usr/bin/uname -a').should be_nil + end + end + + describe "and the binary is a relative path" do + it "should always include /sbin and /usr/sbin in search path" do + Facter::Util::Resolution.search_paths.should include '/sbin' + Facter::Util::Resolution.search_paths.should include '/usr/sbin' + end + + it "should run the command if found in search path" do + Facter::Util::Resolution.stubs(:search_paths).returns ['/sbin', '/bin' ] + File.stubs(:executable?).with(File.join('/sbin','ifconfig')).returns false + File.stubs(:executable?).with(File.join('/bin','ifconfig')).returns true + Facter::Util::Resolution.expects(:`).with(File.join('/bin','ifconfig -a')).returns "done\n" + Facter::Util::Resolution.exec('ifconfig -a').should == 'done' + end + + it "should not run the command if not found in any search path" do + Facter::Util::Resolution.stubs(:search_paths).returns ['/sbin', '/bin' ] + File.stubs(:executable?).with(File.join('/sbin','ifconfig')).returns false + File.stubs(:executable?).with(File.join('/bin','ifconfig')).returns false + Facter::Util::Resolution.exec('ifconfig -a').should be_nil + end + end end - it "on other platforms than windows should return false if which returns non-zero exit code" do - Facter::Util::Config.stubs(:is_windows?).returns(false) - Facter::Util::Resolution.expects(:`). - with('which which >/dev/null 2>&1').returns('') - Process::Status.any_instance.stubs(:success?).returns false - Facter::Util::Resolution.have_which.should == false + describe "when run on windows" do + before :each do + Facter::Util::Config.stubs(:is_windows?).returns true + end + + describe "and the binary is an absolute path" do + it "should run the command if the binary is found" do + File.expects(:executable?).with('C:\foo\bar.exe').returns true + Facter::Util::Resolution.expects(:`).with('C:\foo\bar.exe /a /b /c "foo bar.txt"').returns "done\n" + Facter::Util::Resolution.exec('C:\foo\bar.exe /a /b /c "foo bar.txt"').should == 'done' + end + + it "should handle quoted binaries with spaces correctly" do + File.expects(:executable?).with('C:\foo baz\bar.exe').returns true + Facter::Util::Resolution.expects(:`).with('"C:\foo baz\bar.exe" /a /b /c "foo bar.txt"').returns "done\n" + Facter::Util::Resolution.exec('"C:\foo baz\bar.exe" /a /b /c "foo bar.txt"').should == 'done' + end + + it "should not run the command if the binary is not found" do + File.expects(:executable?).with('C:\foo\bar.exe').returns false + Facter::Util::Resolution.expects(:`).with('C:\foo\bar.exe /a /b /c "foo bar.txt"').never + Facter::Util::Resolution.exec('C:\foo\bar.exe /a /b /c "foo bar.txt"').should be_nil + end + end + + describe "and the binary is a relative path" do + it "should run the command if found in search path" do + Facter::Util::Resolution.stubs(:search_paths).returns ['C:\Windows\system32', 'C:\Windows', 'C:\Windows\System32\Wbem' ] + File.stubs(:executable?).with(File.join('C:\Windows\system32','foo.exe')).returns false + File.stubs(:executable?).with(File.join('C:\Windows','foo.exe')).returns true + File.stubs(:executable?).with(File.join('C:\Windows\System32\Wbem', 'foo.exe')).returns false + Facter::Util::Resolution.expects(:`).with(File.join('C:\Windows','foo.exe')).returns "done\n" + Facter::Util::Resolution.exec('foo.exe').should == 'done' + end + + it "should try to find the correct extension" do + ENV.stubs(:[]).with('PATHEXT').returns nil + Facter::Util::Resolution.stubs(:search_paths).returns ['C:\Windows\system32', 'C:\Windows'] + ['.COM', '.EXE', '.BAT', '.CMD', '' ].each do |ext| + File.stubs(:executable?).with(File.join('C:\Windows\system32',"foo#{ext}")).returns false + end + ['.COM', '.BAT', '.CMD', '' ].each do |ext| + File.stubs(:executable?).with(File.join('C:\Windows',"foo#{ext}")).returns false + end + File.stubs(:executable?).with(File.join('C:\Windows',"foo.EXE")).returns true + Facter::Util::Resolution.expects(:`).with(File.join('C:\Windows','foo.EXE')).returns "done\n" + Facter::Util::Resolution.exec('foo').should == 'done' + end + + it "should not run the command if not found in any search path" do + Facter::Util::Resolution.stubs(:search_paths).returns ['C:\Windows\system32', 'C:\Windows', 'C:\Windows\System32\Wbem' ] + File.stubs(:executable?).with(File.join('C:\Windows\system32','foo.exe')).returns false + File.stubs(:executable?).with(File.join('C:\Windows','foo.exe')).returns false + File.stubs(:executable?).with(File.join('C:\Windows\System32\Wbem', 'foo.exe')).returns false + Facter::Util::Resolution.exec('foo.exe').should be_nil + end + end end end end From 9086c0a0355d621b8cd601c8438cc714ffadd683 Mon Sep 17 00:00:00 2001 From: Stefan Schulte Date: Sun, 8 Apr 2012 19:43:54 +0200 Subject: [PATCH 0784/3753] (#13678) Add RDoc documentation for new methods The refactor on #13678 introduced new methods: Facter::Util::Resolution::search_paths Facter::Util::Resolution::which Facter::Util::Resolution::absolute_path? Facter::Util::Resolution::expand_command Add documentation for those. --- lib/facter/util/resolution.rb | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index b8454ecf13..9712dd63be 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -13,6 +13,9 @@ class Facter::Util::Resolution INTERPRETER = Facter::Util::Config.is_windows? ? "cmd.exe" : "/bin/sh" + # Returns the locations to be searched when looking for a binary. This + # is currently determined by the +PATH+ environment variable plus + # /sbin and /usr/sbin when run on unix def self.search_paths if Facter::Util::Config.is_windows? ENV['PATH'].split(File::PATH_SEPARATOR) @@ -24,7 +27,12 @@ def self.search_paths end end - # taken from puppet: lib/puppet/util.rb + # Determine the full path to a binary. If the supplied filename does not + # already describe an absolute path then different locations (determined + # by self.search_paths) will be searched for a match. + # + # Returns nil if no matching executable can be found otherwise returns + # the expanded pathname. def self.which(bin) if absolute_path?(bin) return bin if File.executable?(bin) @@ -45,7 +53,8 @@ def self.which(bin) nil end - # taken from puppet: lib/puppet/util.rb + # Determine in a platform-specific way whether a path is absolute. This + # defaults to the local platform if none is specified. def self.absolute_path?(path, platform=nil) # Escape once for the string literal, and once for the regex. slash = '[\\\\/]' @@ -59,6 +68,12 @@ def self.absolute_path?(path, platform=nil) !! (path =~ regexes[platform]) end + # Expand the executable of a commandline to an absolute path. The executable + # is the first word of the commandline. If the executable contains spaces, + # it has be but in double quotes to be properly recognized. + # + # Returns the commandline with the expanded binary or nil if the binary + # can't be found def self.expand_command(command) if match = /^"([^"]+)"(?:\s+(.*))?/.match(command) exe, arguments = match.captures From 55b1125560544b428f66230a95cce88582d296ba Mon Sep 17 00:00:00 2001 From: Stefan Schulte Date: Sun, 8 Apr 2012 19:48:45 +0200 Subject: [PATCH 0785/3753] (#13678) Add more unit tests for new methods There is currently one bug in `expand_command` shown by a test marked as pending: When `expand_command` is run with e.g. "foo.exe" `expand_command` tries to replace the binary foo.exe with a full path to foo. If foo is now found in a path that contains spaces, `expand_command` doesn't escape these. So `expand_command('foo.exe /a')` might return `C:\My Tools\foo.exe /a` when it probably should return `"C:\My Tools\foo.exe" /a` --- spec/unit/util/resolution_spec.rb | 140 ++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index 36db3a3780..b18c7972fa 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -323,6 +323,146 @@ end end + describe "#search_paths" do + it "should use the PATH evironment variable to determine locations on windows" do + # The reason for hacking the PATH_SEPARATOR constant is that + # a single path like C:\Windows already contains the unix PATH_SEPARATOR + # and splitting would be wrong. The other way around works because unix + # pathes normallay do not contain the windows PATH_SEPARATOR + old_separator = File::PATH_SEPARATOR + Facter::Util::Config.stubs(:is_windows?).returns true + ENV.expects(:[]).with('PATH').returns 'C:\Windows;C:\Windows\System32' + File.send(:remove_const,'PATH_SEPARATOR') + File.const_set('PATH_SEPARATOR', ';') + begin + Facter::Util::Resolution.search_paths.should == %w{C:\Windows C:\Windows\System32} + ensure + File.send(:remove_const,'PATH_SEPARATOR') + File.const_set('PATH_SEPARATOR', old_separator) + end + end + + it "should use the PATH environment variable plus /sbin and /usr/sbin on unix" do + Facter::Util::Config.stubs(:is_windows?).returns false + ENV.expects(:[]).with('PATH').returns "/bin#{File::PATH_SEPARATOR}/usr/bin" + Facter::Util::Resolution.search_paths.should == %w{/bin /usr/bin /sbin /usr/sbin} + end + end + + describe "#which" do + describe "when run on unix" do + before :each do + Facter::Util::Config.stubs(:is_windows?).returns false + Facter::Util::Resolution.stubs(:search_paths).returns [ '/bin', '/sbin', '/usr/sbin'] + end + + describe "and provided with an absolute path" do + it "should return the binary if executable" do + File.expects(:executable?).with('/opt/foo').returns true + Facter::Util::Resolution.which('/opt/foo').should == '/opt/foo' + end + + it "should return nil if the binary is not executable" do + File.expects(:executable?).with('/opt/foo').returns false + Facter::Util::Resolution.which('/opt/foo').should be_nil + end + end + + describe "and not provided with an absolute path" do + it "should return the absolute path if found" do + File.expects(:executable?).with(File.join('/bin','foo')).returns false + File.expects(:executable?).with(File.join('/sbin','foo')).returns true + File.expects(:executable?).with(File.join('/usr/sbin','foo')).never + Facter::Util::Resolution.which('foo').should == File.join('/sbin','foo') + end + + it "should return nil if not found" do + File.expects(:executable?).with(File.join('/bin','foo')).returns false + File.expects(:executable?).with(File.join('/sbin','foo')).returns false + File.expects(:executable?).with(File.join('/usr/sbin','foo')).returns false + Facter::Util::Resolution.which('foo').should be_nil + end + end + end + + describe "when run on windows" do + before :each do + Facter::Util::Config.stubs(:is_windows?).returns true + Facter::Util::Resolution.stubs(:search_paths).returns ['C:\Windows\system32', 'C:\Windows', 'C:\Windows\System32\Wbem' ] + end + + describe "and provided with an absolute path" do + it "should return the binary if executable" do + File.expects(:executable?).with('C:\Tools\foo.exe').returns true + File.expects(:executable?).with('\\\\remote\dir\foo.exe').returns true + Facter::Util::Resolution.which('C:\Tools\foo.exe').should == 'C:\Tools\foo.exe' + Facter::Util::Resolution.which('\\\\remote\dir\foo.exe').should == '\\\\remote\dir\foo.exe' + end + + it "should return nil if the binary is not executable" do + File.expects(:executable?).with('C:\Tools\foo.exe').returns false + File.expects(:executable?).with('\\\\remote\dir\foo.exe').returns false + Facter::Util::Resolution.which('C:\Tools\foo.exe').should be_nil + Facter::Util::Resolution.which('\\\\remote\dir\foo.exe').should be_nil + end + end + + describe "and not provided with an absolute path" do + it "should return the absolute path if found" do + File.expects(:executable?).with(File.join('C:\Windows\system32','foo.exe')).returns false + File.expects(:executable?).with(File.join('C:\Windows','foo.exe')).returns true + File.expects(:executable?).with(File.join('C:\Windows\System32\Wbem', 'foo.exe')).never + Facter::Util::Resolution.which('foo.exe').should == File.join('C:\Windows','foo.exe') + end + + it "should return the absolute path with file extension if found" do + ENV.stubs(:[]).with('PATHEXT').returns nil + ['.COM', '.EXE', '.BAT', '.CMD', '' ].each do |ext| + File.stubs(:executable?).with(File.join('C:\Windows\system32',"foo#{ext}")).returns false + File.stubs(:executable?).with(File.join('C:\Windows\System32\Wbem',"foo#{ext}")).returns false + end + ['.COM', '.BAT', '.CMD', '' ].each do |ext| + File.stubs(:executable?).with(File.join('C:\Windows',"foo#{ext}")).returns false + end + File.stubs(:executable?).with(File.join('C:\Windows',"foo.EXE")).returns true + + Facter::Util::Resolution.which('foo').should == File.join('C:\Windows','foo.EXE') + end + + it "should return nil if not found" do + File.expects(:executable?).with(File.join('C:\Windows\system32','foo.exe')).returns false + File.expects(:executable?).with(File.join('C:\Windows','foo.exe')).returns false + File.expects(:executable?).with(File.join('C:\Windows\System32\Wbem', 'foo.exe')).returns false + Facter::Util::Resolution.which('foo.exe').should be_nil + end + end + end + + describe "#expand_command" do + it "should expand binary" do + Facter::Util::Resolution.expects(:which).with('foo').returns '/bin/foo' + Facter::Util::Resolution.expand_command('foo -a | stuff >> /dev/null').should == '/bin/foo -a | stuff >> /dev/null' + end + + it "should expand quoted binary" do + Facter::Util::Resolution.expects(:which).with('C:\My Tools\foo.exe').returns 'C:\My Tools\foo.exe' + Facter::Util::Resolution.expand_command('"C:\My Tools\foo.exe" /a /b').should == '"C:\My Tools\foo.exe" /a /b' + end + + it "should escape spaces in path" do + pending "Not yet implemented" + Facter::Util::Resolution.expects(:which).with('foo.exe').returns 'C:\My Tools\foo.exe' + Facter::Util::Resolution.expand_command('foo.exe /a /b').should == '"C:\My Tools\foo.exe" /a /b' + end + + it "should return nil if not found" do + Facter::Util::Resolution.expects(:which).with('foo').returns nil + Facter::Util::Resolution.expand_command('foo -a | stuff >> /dev/null').should be_nil + end + end + + end + # It's not possible, AFAICT, to mock %x{}, so I can't really test this bit. describe "when executing code" do it "should deprecate the interpreter parameter" do From 121a2ab97e6fec34aca63642181365011b850ed6 Mon Sep 17 00:00:00 2001 From: Stefan Schulte Date: Sun, 8 Apr 2012 21:00:46 +0200 Subject: [PATCH 0786/3753] (#13678) Fix quoting in expand_command If the expand_command method is called with a commandline where the binary is not specified with as an absolute pathname, expand_command will determine the absolute pathname by calling the `which` method. The full pathname to the binary may include spaces (especially on windows). Since expand_command should return a commandline that is ready to be run by the shell it has to add quotes around the binary if the pathname contains spaces. --- lib/facter/util/resolution.rb | 15 ++++++++++++--- spec/unit/util/resolution_spec.rb | 22 ++++++++++++++++++++-- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 9712dd63be..0680f8700c 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -73,14 +73,23 @@ def self.absolute_path?(path, platform=nil) # it has be but in double quotes to be properly recognized. # # Returns the commandline with the expanded binary or nil if the binary - # can't be found + # can't be found. If the path to the binary contains quotes, the whole binary + # is put in quotes. def self.expand_command(command) - if match = /^"([^"]+)"(?:\s+(.*))?/.match(command) + if match = /^"(.+?)"(?:\s+(.*))?/.match(command) exe, arguments = match.captures exe = which(exe) and [ "\"#{exe}\"", arguments ].compact.join(" ") + elsif match = /^'(.+?)'(?:\s+(.*))?/.match(command) and not Facter::Util::Config.is_windows? + exe, arguments = match.captures + exe = which(exe) and [ "'#{exe}'", arguments ].compact.join(" ") else exe, arguments = command.split(/ /,2) - exe = which(exe) and [ exe, arguments ].compact.join(" ") + if exe = which(exe) + # the binary was not quoted which means it contains no spaces. But the + # full path to the binary may do so. + exe = "\"#{exe}\"" if exe =~ /\s/ + [ exe, arguments ].compact.join(" ") + end end end diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index b18c7972fa..d3a6698a82 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -444,13 +444,31 @@ Facter::Util::Resolution.expand_command('foo -a | stuff >> /dev/null').should == '/bin/foo -a | stuff >> /dev/null' end - it "should expand quoted binary" do + it "should expand single quoted binary on unix" do + Facter::Util::Config.stubs(:is_windows?).returns false + Facter::Util::Resolution.expects(:which).with('my foo').returns '/home/bob/my path/my foo' + Facter::Util::Resolution.expand_command('\'my foo\' -a').should == '\'/home/bob/my path/my foo\' -a' + end + + it "should not expand single quoted binary on windows" do + Facter::Util::Config.stubs(:is_windows?).returns true + Facter::Util::Resolution.expects(:which).with('\'C:\My').returns nil + Facter::Util::Resolution.expand_command('\'C:\My Tools\foo.exe\' /a /b').should == nil + end + + it "should expand double quoted binary on unix" do + Facter::Util::Config.stubs(:is_windows?).returns false + Facter::Util::Resolution.expects(:which).with('my foo').returns '/home/bob/my path/my foo' + Facter::Util::Resolution.expand_command('"my foo" -a').should == '"/home/bob/my path/my foo" -a' + end + + it "should expand double quoted binary on windows" do + Facter::Util::Config.stubs(:is_windows?).returns true Facter::Util::Resolution.expects(:which).with('C:\My Tools\foo.exe').returns 'C:\My Tools\foo.exe' Facter::Util::Resolution.expand_command('"C:\My Tools\foo.exe" /a /b').should == '"C:\My Tools\foo.exe" /a /b' end it "should escape spaces in path" do - pending "Not yet implemented" Facter::Util::Resolution.expects(:which).with('foo.exe').returns 'C:\My Tools\foo.exe' Facter::Util::Resolution.expand_command('foo.exe /a /b').should == '"C:\My Tools\foo.exe" /a /b' end From 60d0cd2e0edd58881799fcd2baa6b5ce797ce04b Mon Sep 17 00:00:00 2001 From: Stefan Schulte Date: Sun, 15 Apr 2012 11:35:02 +0200 Subject: [PATCH 0787/3753] (#13678) Fix spec failures on windows Facter now tries to expand commands before running them. As a sideeffect we can no longer run shell-builtins directly since facter is not able to find it in the filesystem. This broke a few spec tests that assumed we can run "echo foo" on both unix and windows. The fix now explicitly calls the shell (cmd.exe) to run the echo builtin. On Unix we already have a native echo (most likely /bin/echo). Note: The former implementation did allow windows builtins but did not allow unix buildins because the former implementation used `which` to check if we can run a command on unix and `which` does not work on builtins. --- spec/unit/facter_spec.rb | 3 ++- spec/unit/util/resolution_spec.rb | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/spec/unit/facter_spec.rb b/spec/unit/facter_spec.rb index c6cedd3705..a91c896a40 100755 --- a/spec/unit/facter_spec.rb +++ b/spec/unit/facter_spec.rb @@ -74,8 +74,9 @@ describe "when provided code as a string" do it "should execute the code in the shell" do + test_command = Facter::Util::Config.is_windows? ? 'cmd.exe /c echo yup' : 'echo yup' Facter.add("shell_testing") do - setcode "echo yup" + setcode test_command end Facter["shell_testing"].value.should == "yup" diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index d3a6698a82..7b5cefb0d1 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -489,7 +489,8 @@ end it "should execute the binary" do - Facter::Util::Resolution.exec("echo foo").should == "foo" + test_command = Facter::Util::Config.is_windows? ? 'cmd.exe /c echo foo' : 'echo foo' + Facter::Util::Resolution.exec(test_command).should == "foo" end describe "when run on unix" do From 635ccc440077a74090293dc926bc28b4d6740f93 Mon Sep 17 00:00:00 2001 From: Gary Larizza Date: Mon, 16 Apr 2012 15:07:05 -0700 Subject: [PATCH 0788/3753] Namespace CFPropertyList under Puppet::Util Previously, the code inserted into lib/puppet/util/cfpropertylist was still being namespaced as 'CFPropertyList'. Since CFPropertyList also exists as Rubygem, Puppet could potentially call a different version of CFPropertyList than the version that's been inserted into the lib/puppet/util/cfpropertylist directory. To remedy this, the library has been namespaced as Puppet::Util::CFPropertylist and all code and tests have been changed. --- .../lib/rbBinaryCFPropertyList.rb | 4 +- .../cfpropertylist/lib/rbCFPropertyList.rb | 62 +++++++++---------- .../util/cfpropertylist/lib/rbCFTypes.rb | 2 +- .../util/cfpropertylist/lib/rbLibXMLParser.rb | 4 +- .../cfpropertylist/lib/rbNokogiriParser.rb | 4 +- .../util/cfpropertylist/lib/rbREXMLParser.rb | 4 +- lib/facter/util/macosx.rb | 6 +- spec/unit/util/macosx_spec.rb | 2 +- 8 files changed, 44 insertions(+), 44 deletions(-) diff --git a/lib/facter/util/cfpropertylist/lib/rbBinaryCFPropertyList.rb b/lib/facter/util/cfpropertylist/lib/rbBinaryCFPropertyList.rb index 817b30c3ad..98f5c87d88 100644 --- a/lib/facter/util/cfpropertylist/lib/rbBinaryCFPropertyList.rb +++ b/lib/facter/util/cfpropertylist/lib/rbBinaryCFPropertyList.rb @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -module CFPropertyList +module Facter::Util::CFPropertyList # Binary PList parser class class Binary # Read a binary plist file @@ -52,7 +52,7 @@ def load(opts) end - # Convert CFPropertyList to binary format; since we have to count our objects we simply unique CFDictionary and CFArray + # Convert Facter::Util::CFPropertyList to binary format; since we have to count our objects we simply unique CFDictionary and CFArray def to_str(opts={}) @unique_table = {} @count_objects = 0 diff --git a/lib/facter/util/cfpropertylist/lib/rbCFPropertyList.rb b/lib/facter/util/cfpropertylist/lib/rbCFPropertyList.rb index 2d3c015134..cd7fcf1cf1 100644 --- a/lib/facter/util/cfpropertylist/lib/rbCFPropertyList.rb +++ b/lib/facter/util/cfpropertylist/lib/rbCFPropertyList.rb @@ -5,10 +5,10 @@ require 'time' # -# CFPropertyList implementation +# Facter::Util::CFPropertyList implementation # # class to read, manipulate and write both XML and binary property list -# files (plist(5)) as defined by Apple. Have a look at CFPropertyList::List +# files (plist(5)) as defined by Apple. Have a look at Facter::Util::CFPropertyList::List # for more documentation. # # == Example @@ -25,24 +25,24 @@ # } # } # -# # create CFPropertyList::List object -# plist = CFPropertyList::List.new +# # create Facter::Util::CFPropertyList::List object +# plist = Facter::Util::CFPropertyList::List.new # -# # call CFPropertyList.guess() to create corresponding CFType values +# # call Facter::Util::CFPropertyList.guess() to create corresponding CFType values # # pass in optional :convert_unknown_to_string => true to convert things like symbols into strings. -# plist.value = CFPropertyList.guess(data) +# plist.value = Facter::Util::CFPropertyList.guess(data) # # # write plist to file -# plist.save("example.plist", CFPropertyList::List::FORMAT_BINARY) +# plist.save("example.plist", Facter::Util::CFPropertyList::List::FORMAT_BINARY) # # # … later, read it again -# plist = CFPropertyList::List.new(:file => "example.plist") -# data = CFPropertyList.native_types(plist.value) +# plist = Facter::Util::CFPropertyList::List.new(:file => "example.plist") +# data = Facter::Util::CFPropertyList.native_types(plist.value) # # Author:: Christian Kruse (mailto:cjk@wwwtech.de) # Copyright:: Copyright (c) 2010 # License:: MIT License -module CFPropertyList +module Facter::Util::CFPropertyList # interface class for PList parsers class ParserInterface # load a plist @@ -121,19 +121,19 @@ class Enumerator end -module CFPropertyList +module Facter::Util::CFPropertyList # Create CFType hierarchy by guessing the correct CFType, e.g. # # x = { # 'a' => ['b','c','d'] # } - # cftypes = CFPropertyList.guess(x) + # cftypes = Facter::Util::CFPropertyList.guess(x) # # pass optional options hash. Only possible value actually: # +convert_unknown_to_string+:: Convert unknown objects to string calling to_str() # +converter_method+:: Convert unknown objects to known objects calling +method_name+ # - # cftypes = CFPropertyList.guess(x,:convert_unknown_to_string => true,:converter_method => :to_hash, :converter_with_opts => true) + # cftypes = Facter::Util::CFPropertyList.guess(x,:convert_unknown_to_string => true,:converter_method => :to_hash, :converter_with_opts => true) def guess(object, options = {}) case object when Fixnum, Integer then CFInteger.new(object) @@ -148,7 +148,7 @@ def guess(object, options = {}) when Array, Enumerator, Enumerable::Enumerator ary = Array.new object.each do |o| - ary.push CFPropertyList.guess(o, options) + ary.push Facter::Util::CFPropertyList.guess(o, options) end CFArray.new(ary) @@ -156,7 +156,7 @@ def guess(object, options = {}) hsh = Hash.new object.each_pair do |k,v| k = k.to_s if k.is_a?(Symbol) - hsh[k] = CFPropertyList.guess(v, options) + hsh[k] = Facter::Util::CFPropertyList.guess(v, options) end CFDictionary.new(hsh) else @@ -167,9 +167,9 @@ def guess(object, options = {}) CFData.new(object.read(), CFData::DATA_RAW) when options[:converter_method] && object.respond_to?(options[:converter_method]) if options[:converter_with_opts] - CFPropertyList.guess(object.send(options[:converter_method],options),options) + Facter::Util::CFPropertyList.guess(object.send(options[:converter_method],options),options) else - CFPropertyList.guess(object.send(options[:converter_method]),options) + Facter::Util::CFPropertyList.guess(object.send(options[:converter_method]),options) end when options[:convert_unknown_to_string] CFString.new(object.to_s) @@ -191,7 +191,7 @@ def native_types(object,keys_as_symbols=false) ary = [] object.value.each do |v| - ary.push CFPropertyList.native_types(v) + ary.push Facter::Util::CFPropertyList.native_types(v) end return ary @@ -200,7 +200,7 @@ def native_types(object,keys_as_symbols=false) object.value.each_pair do |k,v| k = k.to_sym if keys_as_symbols - hsh[k] = CFPropertyList.native_types(v) + hsh[k] = Facter::Util::CFPropertyList.native_types(v) end return hsh @@ -209,7 +209,7 @@ def native_types(object,keys_as_symbols=false) module_function :guess, :native_types - # Class representing a CFPropertyList. Instanciate with #new + # Class representing a Facter::Util::CFPropertyList. Instanciate with #new class List # Format constant for binary format FORMAT_BINARY = 1 @@ -229,7 +229,7 @@ class List # the root value in the plist file attr_accessor :value - # initialize a new CFPropertyList, arguments are: + # initialize a new Facter::Util::CFPropertyList, arguments are: # # :file:: Parse a file # :format:: Format is one of FORMAT_BINARY or FORMAT_XML. Defaults to FORMAT_AUTO @@ -330,7 +330,7 @@ def load(file=nil,format=nil) end end - # Serialize CFPropertyList object to specified format and write it to file + # Serialize Facter::Util::CFPropertyList object to specified format and write it to file # file = nil:: The filename of the file to write to. Uses +filename+ instance variable if nil # format = nil:: The format to save in. Uses +format+ instance variable if nil def save(file=nil,format=nil,opts={}) @@ -369,10 +369,10 @@ def to_str(format=List::FORMAT_BINARY,opts={}) class Array # convert an array to plist format def to_plist(options={}) - options[:plist_format] ||= CFPropertyList::List::FORMAT_BINARY + options[:plist_format] ||= Facter::Util::CFPropertyList::List::FORMAT_BINARY - plist = CFPropertyList::List.new - plist.value = CFPropertyList.guess(self, options) + plist = Facter::Util::CFPropertyList::List.new + plist.value = Facter::Util::CFPropertyList.guess(self, options) plist.to_str(options[:plist_format]) end end @@ -380,10 +380,10 @@ def to_plist(options={}) class Enumerator # convert an array to plist format def to_plist(options={}) - options[:plist_format] ||= CFPropertyList::List::FORMAT_BINARY + options[:plist_format] ||= Facter::Util::CFPropertyList::List::FORMAT_BINARY - plist = CFPropertyList::List.new - plist.value = CFPropertyList.guess(self, options) + plist = Facter::Util::CFPropertyList::List.new + plist.value = Facter::Util::CFPropertyList.guess(self, options) plist.to_str(options[:plist_format]) end end @@ -391,10 +391,10 @@ def to_plist(options={}) class Hash # convert a hash to plist format def to_plist(options={}) - options[:plist_format] ||= CFPropertyList::List::FORMAT_BINARY + options[:plist_format] ||= Facter::Util::CFPropertyList::List::FORMAT_BINARY - plist = CFPropertyList::List.new - plist.value = CFPropertyList.guess(self, options) + plist = Facter::Util::CFPropertyList::List.new + plist.value = Facter::Util::CFPropertyList.guess(self, options) plist.to_str(options[:plist_format]) end end diff --git a/lib/facter/util/cfpropertylist/lib/rbCFTypes.rb b/lib/facter/util/cfpropertylist/lib/rbCFTypes.rb index a8754b8368..547801f030 100644 --- a/lib/facter/util/cfpropertylist/lib/rbCFTypes.rb +++ b/lib/facter/util/cfpropertylist/lib/rbCFTypes.rb @@ -9,7 +9,7 @@ require 'base64' -module CFPropertyList +module Facter::Util::CFPropertyList # This class defines the base class for all CFType classes # class CFType diff --git a/lib/facter/util/cfpropertylist/lib/rbLibXMLParser.rb b/lib/facter/util/cfpropertylist/lib/rbLibXMLParser.rb index 9ffb05fdac..b27835da52 100644 --- a/lib/facter/util/cfpropertylist/lib/rbLibXMLParser.rb +++ b/lib/facter/util/cfpropertylist/lib/rbLibXMLParser.rb @@ -2,7 +2,7 @@ require 'libxml' -module CFPropertyList +module Facter::Util::CFPropertyList # XML parser class XML < XMLParserInterface # read a XML file @@ -20,7 +20,7 @@ def load(opts) return import_xml(root) end - # serialize CFPropertyList object to XML + # serialize Facter::Util::CFPropertyList object to XML # opts = {}:: Specify options: :formatted - Use indention and line breaks def to_str(opts={}) doc = LibXML::XML::Document.new diff --git a/lib/facter/util/cfpropertylist/lib/rbNokogiriParser.rb b/lib/facter/util/cfpropertylist/lib/rbNokogiriParser.rb index ff82defc6d..3edb8fe1ec 100644 --- a/lib/facter/util/cfpropertylist/lib/rbNokogiriParser.rb +++ b/lib/facter/util/cfpropertylist/lib/rbNokogiriParser.rb @@ -2,7 +2,7 @@ require 'nokogiri' -module CFPropertyList +module Facter::Util::CFPropertyList # XML parser class XML < ParserInterface # read a XML file @@ -20,7 +20,7 @@ def load(opts) return import_xml(root) end - # serialize CFPropertyList object to XML + # serialize Facter::Util::CFPropertyList object to XML # opts = {}:: Specify options: :formatted - Use indention and line breaks def to_str(opts={}) doc = Nokogiri::XML::Document.new diff --git a/lib/facter/util/cfpropertylist/lib/rbREXMLParser.rb b/lib/facter/util/cfpropertylist/lib/rbREXMLParser.rb index da6f8e460c..9b0e044c33 100644 --- a/lib/facter/util/cfpropertylist/lib/rbREXMLParser.rb +++ b/lib/facter/util/cfpropertylist/lib/rbREXMLParser.rb @@ -2,7 +2,7 @@ require 'rexml/document' -module CFPropertyList +module Facter::Util::CFPropertyList # XML parser class XML < ParserInterface # read a XML file @@ -20,7 +20,7 @@ def load(opts) return import_xml(root) end - # serialize CFPropertyList object to XML + # serialize Facter::Util::CFPropertyList object to XML # opts = {}:: Specify options: :formatted - Use indention and line breaks def to_str(opts={}) doc = REXML::Document.new diff --git a/lib/facter/util/macosx.rb b/lib/facter/util/macosx.rb index 0b93a2ad15..4dc4e4be51 100644 --- a/lib/facter/util/macosx.rb +++ b/lib/facter/util/macosx.rb @@ -26,13 +26,13 @@ def self.intern_xml(xml) xml.gsub!( bad_xml_doctype, Plist_Xml_Doctype ) Facter.debug("Had to fix plist with incorrect DOCTYPE declaration") end - plist = CFPropertyList::List.new + plist = Facter::Util::CFPropertyList::List.new begin plist.load_str(xml) rescue => e - fail("A plist file could not be properly read by CFPropertyList: #{e.inspect}") + fail("A plist file could not be properly read by Facter::Util::CFPropertyList: #{e.inspect}") end - CFPropertyList.native_types(plist.value) + Facter::Util::CFPropertyList.native_types(plist.value) end # Return an xml result, modified as we need it. diff --git a/spec/unit/util/macosx_spec.rb b/spec/unit/util/macosx_spec.rb index 32647e2074..5059ed230a 100755 --- a/spec/unit/util/macosx_spec.rb +++ b/spec/unit/util/macosx_spec.rb @@ -43,7 +43,7 @@ it 'should fail when trying to read invalid XML' do expect { Facter::Util::Macosx.intern_xml('xml<--->') }.should \ - raise_error(RuntimeError, /A plist file could not be properly read by CFPropertyList/) + raise_error(RuntimeError, /A plist file could not be properly read by Facter::Util::CFPropertyList/) end describe "when collecting profiler data" do From 0fea7b087942ae1bb0ee9eadac5f642108e2f1bc Mon Sep 17 00:00:00 2001 From: Stefan Schulte Date: Sat, 21 Apr 2012 11:59:37 +0200 Subject: [PATCH 0789/3753] maint: Add shared context for specs to imitate windows or posix Add "windows" and "posix" shared contexts to rspec. The shared context was borrowed from the puppet codebase. If you give :as_platform => :posix or :as_platform => :windows as an argument to a describe block or a context block, it will stub is_windows, File::SEPARATOR, and File::ALT_SEPARATOR. In addition to puppets shared_context it will also stub File::PATH_SEPARATOR --- spec/puppetlabs_spec/verbose.rb | 9 ++++++ spec/puppetlabs_spec_helper.rb | 1 + spec/shared_contexts/platform.rb | 55 ++++++++++++++++++++++++++++++++ spec/spec_helper.rb | 5 +++ 4 files changed, 70 insertions(+) create mode 100755 spec/puppetlabs_spec/verbose.rb create mode 100644 spec/shared_contexts/platform.rb diff --git a/spec/puppetlabs_spec/verbose.rb b/spec/puppetlabs_spec/verbose.rb new file mode 100755 index 0000000000..d9834f2d79 --- /dev/null +++ b/spec/puppetlabs_spec/verbose.rb @@ -0,0 +1,9 @@ +# Support code for running stuff with warnings disabled. +module Kernel + def with_verbose_disabled + verbose, $VERBOSE = $VERBOSE, nil + result = yield + $VERBOSE = verbose + return result + end +end diff --git a/spec/puppetlabs_spec_helper.rb b/spec/puppetlabs_spec_helper.rb index ba2ae7ac20..8250d04a8e 100644 --- a/spec/puppetlabs_spec_helper.rb +++ b/spec/puppetlabs_spec_helper.rb @@ -11,6 +11,7 @@ module PuppetlabsSpec require 'puppetlabs_spec/files' require 'puppetlabs_spec/fixtures' require 'puppetlabs_spec/matchers' +require 'puppetlabs_spec/verbose' RSpec.configure do |config| # Include PuppetlabsSpec helpers so they can be called at convenience diff --git a/spec/shared_contexts/platform.rb b/spec/shared_contexts/platform.rb new file mode 100644 index 0000000000..215f370ac9 --- /dev/null +++ b/spec/shared_contexts/platform.rb @@ -0,0 +1,55 @@ +# Contexts for stubbing platforms +# In a describe or context block, adding :as_platform => :windows or +# :as_platform => :posix will stub the relevant facter config, as well as +# the behavior of Ruby's filesystem methods by changing File::ALT_SEPARATOR. +# +# +# +shared_context "windows", :as_platform => :windows do + before :each do + Facter.fact(:operatingsystem).stubs(:value).returns('Windows') + Facter::Util::Config.stubs(:is_windows?).returns true + end + + around do |example| + file_alt_separator = File::ALT_SEPARATOR + file_path_separator = File::PATH_SEPARATOR + # prevent Ruby from warning about changing a constant + with_verbose_disabled do + File::ALT_SEPARATOR = '\\' + File::PATH_SEPARATOR = ';' + end + begin + example.run + ensure + with_verbose_disabled do + File::ALT_SEPARATOR = file_alt_separator + File::PATH_SEPARATOR = file_path_separator + end + end + end +end + +shared_context "posix", :as_platform => :posix do + before :each do + Facter::Util::Config.stubs(:is_windows?).returns false + end + + around do |example| + file_alt_separator = File::ALT_SEPARATOR + file_path_separator = File::PATH_SEPARATOR + # prevent Ruby from warning about changing a constant + with_verbose_disabled do + File::ALT_SEPARATOR = nil + File::PATH_SEPARATOR = ':' + end + begin + example.run + ensure + with_verbose_disabled do + File::ALT_SEPARATOR = file_alt_separator + File::PATH_SEPARATOR = file_path_separator + end + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 4e82c21de0..31459bcd24 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -11,6 +11,11 @@ require 'facter' require 'fileutils' require 'puppetlabs_spec_helper' +require 'pathname' + +Pathname.glob("#{dir}/shared_contexts/*.rb") do |file| + require file.relative_path_from(Pathname.new(dir)) +end RSpec.configure do |config| config.mock_with :mocha From e7e7e8f75f7d03d6cb8de4dc19e984082b5679a5 Mon Sep 17 00:00:00 2001 From: Stefan Schulte Date: Sat, 21 Apr 2012 12:28:15 +0200 Subject: [PATCH 0790/3753] (#13678) Extend spec tests for expand_command The spec tests for expand_command and exec were restructured to make use of the new shared_beviour :as_platform to have more accurate tests and were also restructured to reduce code duplication. These reveal a few open issues: * on windows, facter should continue to support shell buildins which is currently not the case * on unix, facter should quote binaries that are found in a path with spaces in single quotes, not double quotes * on windows, we trusted File.join('C:','Windows') to return C:\Windows which is not the case. File.join uses File::SEPARATOR ('/' on windows) and not File::ALT_SEPARATOR ('\' on windows). The issues will be addressed in a following commit --- spec/unit/util/resolution_spec.rb | 293 ++++++++++++------------------ 1 file changed, 121 insertions(+), 172 deletions(-) diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index 7b5cefb0d1..5a8fff0812 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -286,11 +286,7 @@ # taken from puppet: spec/unit/util_spec.rb describe "#absolute_path?" do - describe "when run on unix" do - before :each do - Facter::Util::Config.stubs(:is_windows?).returns false - end - + context "when run on unix", :as_platform => :posix do %w[/ /foo /foo/../bar //foo //Server/Foo/Bar //?/C:/foo/bar /\Server/Foo /foo//bar/baz].each do |path| it "should return true for #{path}" do Facter::Util::Resolution.should be_absolute_path(path) @@ -304,11 +300,7 @@ end end - describe "when run on windows" do - before :each do - Facter::Util::Config.stubs(:is_windows?).returns true - end - + context "when run on windows", :as_platform => :windows do %w[C:/foo C:\foo \\\\Server\Foo\Bar \\\\?\C:\foo\bar //Server/Foo/Bar //?/C:/foo/bar /\?\C:/foo\bar \/Server\Foo/Bar c:/foo//bar//baz].each do |path| it "should return true for #{path}" do Facter::Util::Resolution.should be_absolute_path(path) @@ -324,39 +316,28 @@ end describe "#search_paths" do - it "should use the PATH evironment variable to determine locations on windows" do - # The reason for hacking the PATH_SEPARATOR constant is that - # a single path like C:\Windows already contains the unix PATH_SEPARATOR - # and splitting would be wrong. The other way around works because unix - # pathes normallay do not contain the windows PATH_SEPARATOR - old_separator = File::PATH_SEPARATOR - Facter::Util::Config.stubs(:is_windows?).returns true - ENV.expects(:[]).with('PATH').returns 'C:\Windows;C:\Windows\System32' - File.send(:remove_const,'PATH_SEPARATOR') - File.const_set('PATH_SEPARATOR', ';') - begin + context "on windows", :as_platform => :windows do + it "should use the PATH environment variable to determine locations" do + ENV.expects(:[]).with('PATH').returns 'C:\Windows;C:\Windows\System32' Facter::Util::Resolution.search_paths.should == %w{C:\Windows C:\Windows\System32} - ensure - File.send(:remove_const,'PATH_SEPARATOR') - File.const_set('PATH_SEPARATOR', old_separator) end end - it "should use the PATH environment variable plus /sbin and /usr/sbin on unix" do - Facter::Util::Config.stubs(:is_windows?).returns false - ENV.expects(:[]).with('PATH').returns "/bin#{File::PATH_SEPARATOR}/usr/bin" - Facter::Util::Resolution.search_paths.should == %w{/bin /usr/bin /sbin /usr/sbin} + context "on posix", :as_platform => :posix do + it "should use the PATH environment variable plus /sbin and /usr/sbin on unix" do + ENV.expects(:[]).with('PATH').returns "/bin:/usr/bin" + Facter::Util::Resolution.search_paths.should == %w{/bin /usr/bin /sbin /usr/sbin} + end end end describe "#which" do - describe "when run on unix" do + context "when run on posix", :as_platform => :posix do before :each do - Facter::Util::Config.stubs(:is_windows?).returns false Facter::Util::Resolution.stubs(:search_paths).returns [ '/bin', '/sbin', '/usr/sbin'] end - describe "and provided with an absolute path" do + context "and provided with an absolute path" do it "should return the binary if executable" do File.expects(:executable?).with('/opt/foo').returns true Facter::Util::Resolution.which('/opt/foo').should == '/opt/foo' @@ -368,30 +349,29 @@ end end - describe "and not provided with an absolute path" do + context "and not provided with an absolute path" do it "should return the absolute path if found" do - File.expects(:executable?).with(File.join('/bin','foo')).returns false - File.expects(:executable?).with(File.join('/sbin','foo')).returns true - File.expects(:executable?).with(File.join('/usr/sbin','foo')).never - Facter::Util::Resolution.which('foo').should == File.join('/sbin','foo') + File.expects(:executable?).with('/bin/foo').returns false + File.expects(:executable?).with('/sbin/foo').returns true + File.expects(:executable?).with('/usr/sbin/foo').never + Facter::Util::Resolution.which('foo').should == '/sbin/foo' end it "should return nil if not found" do - File.expects(:executable?).with(File.join('/bin','foo')).returns false - File.expects(:executable?).with(File.join('/sbin','foo')).returns false - File.expects(:executable?).with(File.join('/usr/sbin','foo')).returns false + File.expects(:executable?).with('/bin/foo').returns false + File.expects(:executable?).with('/sbin/foo').returns false + File.expects(:executable?).with('/usr/sbin/foo').returns false Facter::Util::Resolution.which('foo').should be_nil end end end - describe "when run on windows" do + context "when run on windows", :as_platform => :windows do before :each do - Facter::Util::Config.stubs(:is_windows?).returns true Facter::Util::Resolution.stubs(:search_paths).returns ['C:\Windows\system32', 'C:\Windows', 'C:\Windows\System32\Wbem' ] end - describe "and provided with an absolute path" do + context "and provided with an absolute path" do it "should return the binary if executable" do File.expects(:executable?).with('C:\Tools\foo.exe').returns true File.expects(:executable?).with('\\\\remote\dir\foo.exe').returns true @@ -407,75 +387,93 @@ end end - describe "and not provided with an absolute path" do + context "and not provided with an absolute path" do it "should return the absolute path if found" do - File.expects(:executable?).with(File.join('C:\Windows\system32','foo.exe')).returns false - File.expects(:executable?).with(File.join('C:\Windows','foo.exe')).returns true - File.expects(:executable?).with(File.join('C:\Windows\System32\Wbem', 'foo.exe')).never - Facter::Util::Resolution.which('foo.exe').should == File.join('C:\Windows','foo.exe') + File.expects(:executable?).with('C:\Windows\system32\foo.exe').returns false + File.expects(:executable?).with('C:\Windows\foo.exe').returns true + File.expects(:executable?).with('C:\Windows\System32\Wbem\foo.exe').never + Facter::Util::Resolution.which('foo.exe').should == 'C:\Windows\foo.exe' end it "should return the absolute path with file extension if found" do ENV.stubs(:[]).with('PATHEXT').returns nil ['.COM', '.EXE', '.BAT', '.CMD', '' ].each do |ext| - File.stubs(:executable?).with(File.join('C:\Windows\system32',"foo#{ext}")).returns false - File.stubs(:executable?).with(File.join('C:\Windows\System32\Wbem',"foo#{ext}")).returns false + File.stubs(:executable?).with('C:\Windows\system32\foo'+ext).returns false + File.stubs(:executable?).with('C:\Windows\System32\Wbem\foo'+ext).returns false end ['.COM', '.BAT', '.CMD', '' ].each do |ext| - File.stubs(:executable?).with(File.join('C:\Windows',"foo#{ext}")).returns false + File.stubs(:executable?).with('C:\Windows\foo'+ext).returns false end - File.stubs(:executable?).with(File.join('C:\Windows',"foo.EXE")).returns true + File.stubs(:executable?).with('C:\Windows\foo.EXE').returns true - Facter::Util::Resolution.which('foo').should == File.join('C:\Windows','foo.EXE') + Facter::Util::Resolution.which('foo').should == 'C:\Windows\foo.EXE' end it "should return nil if not found" do - File.expects(:executable?).with(File.join('C:\Windows\system32','foo.exe')).returns false - File.expects(:executable?).with(File.join('C:\Windows','foo.exe')).returns false - File.expects(:executable?).with(File.join('C:\Windows\System32\Wbem', 'foo.exe')).returns false + File.expects(:executable?).with('C:\Windows\system32\foo.exe').returns false + File.expects(:executable?).with('C:\Windows\foo.exe').returns false + File.expects(:executable?).with('C:\Windows\System32\Wbem\foo.exe').returns false Facter::Util::Resolution.which('foo.exe').should be_nil end end end describe "#expand_command" do - it "should expand binary" do - Facter::Util::Resolution.expects(:which).with('foo').returns '/bin/foo' - Facter::Util::Resolution.expand_command('foo -a | stuff >> /dev/null').should == '/bin/foo -a | stuff >> /dev/null' - end + context "on windows", :as_platform => :windows do + it "should expand binary" do + Facter::Util::Resolution.expects(:which).with('cmd').returns 'C:\Windows\System32\cmd' + Facter::Util::Resolution.expand_command( + 'cmd /c echo foo > C:\bar' + ).should == 'C:\Windows\System32\cmd /c echo foo > C:\bar' + end - it "should expand single quoted binary on unix" do - Facter::Util::Config.stubs(:is_windows?).returns false - Facter::Util::Resolution.expects(:which).with('my foo').returns '/home/bob/my path/my foo' - Facter::Util::Resolution.expand_command('\'my foo\' -a').should == '\'/home/bob/my path/my foo\' -a' - end + it "should expand double quoted binary" do + Facter::Util::Resolution.expects(:which).with('my foo').returns 'C:\My Tools\my foo.exe' + Facter::Util::Resolution.expand_command('"my foo" /a /b').should == '"C:\My Tools\my foo.exe" /a /b' + end - it "should not expand single quoted binary on windows" do - Facter::Util::Config.stubs(:is_windows?).returns true - Facter::Util::Resolution.expects(:which).with('\'C:\My').returns nil - Facter::Util::Resolution.expand_command('\'C:\My Tools\foo.exe\' /a /b').should == nil - end + it "should not expand single quoted binary" do + Facter::Util::Resolution.expects(:which).with('\'C:\My').returns nil + Facter::Util::Resolution.expand_command('\'C:\My Tools\foo.exe\' /a /b').should be_nil + end - it "should expand double quoted binary on unix" do - Facter::Util::Config.stubs(:is_windows?).returns false - Facter::Util::Resolution.expects(:which).with('my foo').returns '/home/bob/my path/my foo' - Facter::Util::Resolution.expand_command('"my foo" -a').should == '"/home/bob/my path/my foo" -a' - end + it "should quote expanded binary if found in path with spaces" do + Facter::Util::Resolution.expects(:which).with('foo').returns 'C:\My Tools\foo.exe' + Facter::Util::Resolution.expand_command('foo /a /b').should == '"C:\My Tools\foo.exe" /a /b' + end - it "should expand double quoted binary on windows" do - Facter::Util::Config.stubs(:is_windows?).returns true - Facter::Util::Resolution.expects(:which).with('C:\My Tools\foo.exe').returns 'C:\My Tools\foo.exe' - Facter::Util::Resolution.expand_command('"C:\My Tools\foo.exe" /a /b').should == '"C:\My Tools\foo.exe" /a /b' + it "should return nil if not found" do + Facter::Util::Resolution.expects(:which).with('foo').returns nil + Facter::Util::Resolution.expand_command('foo /a | stuff >> /dev/null').should be_nil + end end - it "should escape spaces in path" do - Facter::Util::Resolution.expects(:which).with('foo.exe').returns 'C:\My Tools\foo.exe' - Facter::Util::Resolution.expand_command('foo.exe /a /b').should == '"C:\My Tools\foo.exe" /a /b' - end + context "on unix", :as_platform => :posix do + it "should expand binary" do + Facter::Util::Resolution.expects(:which).with('foo').returns '/bin/foo' + Facter::Util::Resolution.expand_command('foo -a | stuff >> /dev/null').should == '/bin/foo -a | stuff >> /dev/null' + end + + it "should expand double quoted binary" do + Facter::Util::Resolution.expects(:which).with('/tmp/my foo').returns '/tmp/my foo' + Facter::Util::Resolution.expand_command(%q{"/tmp/my foo" bar}).should == %q{"/tmp/my foo" bar} + end - it "should return nil if not found" do - Facter::Util::Resolution.expects(:which).with('foo').returns nil - Facter::Util::Resolution.expand_command('foo -a | stuff >> /dev/null').should be_nil + it "should expand single quoted binary" do + Facter::Util::Resolution.expects(:which).with('my foo').returns '/home/bob/my path/my foo' + Facter::Util::Resolution.expand_command(%q{'my foo' -a}).should == %q{'/home/bob/my path/my foo' -a} + end + + it "should quote expanded binary if found in path with spaces" do + pending + Facter::Util::Resolution.expects(:which).with('foo.sh').returns '/home/bob/my tools/foo.sh' + Facter::Util::Resolution.expand_command('foo.sh /a /b').should == %q{'/home/bob/my tools/foo.sh' /a /b} + end + + it "should return nil if not found" do + Facter::Util::Resolution.expects(:which).with('foo').returns nil + Facter::Util::Resolution.expand_command('foo -a | stuff >> /dev/null').should be_nil + end end end @@ -493,110 +491,61 @@ Facter::Util::Resolution.exec(test_command).should == "foo" end - describe "when run on unix" do - before :each do - Facter::Util::Config.stubs(:is_windows?).returns false - end - - describe "and the binary is an absolute path" do - it "should run the command if the binary is found" do - File.expects(:executable?).with('/usr/bin/uname').returns true - Facter::Util::Resolution.expects(:`).with('/usr/bin/uname -a').returns "x86_64\n" - Facter::Util::Resolution.exec('/usr/bin/uname -a').should == 'x86_64' + context "when run on unix", :as_platform => :posix do + context "binary is present" do + it "should run the command if path to binary is absolute" do + Facter::Util::Resolution.expects(:expand_command).with('/usr/bin/uname -m').returns('/usr/bin/uname -m') + Facter::Util::Resolution.expects(:`).with('/usr/bin/uname -m').returns 'x86_64' + Facter::Util::Resolution.exec('/usr/bin/uname -m').should == 'x86_64' end - # taken from the ip fact - it "should run more complicated shell expression" do - File.expects(:executable?).with('/sbin/arp').returns true - Facter::Util::Resolution.expects(:`).with('/sbin/arp -en -i eth0 | sed -e 1d').returns "some_data\n" - Facter::Util::Resolution.exec('/sbin/arp -en -i eth0 | sed -e 1d').should == 'some_data' - end - - it "should not run the command if the binary is not present" do - File.expects(:executable?).with('/usr/bin/uname').returns false - Facter::Util::Resolution.expects(:`).with('/usr/bin/uname -a').never - Facter::Util::Resolution.exec('/usr/bin/uname -a').should be_nil + it "should run the expanded command if path to binary not absolute" do + Facter::Util::Resolution.expects(:expand_command).with('uname -m').returns('/usr/bin/uname -m') + Facter::Util::Resolution.expects(:`).with('/usr/bin/uname -m').returns 'x86_64' + Facter::Util::Resolution.exec('uname -m').should == 'x86_64' end end - describe "and the binary is a relative path" do - it "should always include /sbin and /usr/sbin in search path" do - Facter::Util::Resolution.search_paths.should include '/sbin' - Facter::Util::Resolution.search_paths.should include '/usr/sbin' - end - - it "should run the command if found in search path" do - Facter::Util::Resolution.stubs(:search_paths).returns ['/sbin', '/bin' ] - File.stubs(:executable?).with(File.join('/sbin','ifconfig')).returns false - File.stubs(:executable?).with(File.join('/bin','ifconfig')).returns true - Facter::Util::Resolution.expects(:`).with(File.join('/bin','ifconfig -a')).returns "done\n" - Facter::Util::Resolution.exec('ifconfig -a').should == 'done' + context "binary is not present" do + it "should not run the command if path to binary is absolute" do + Facter::Util::Resolution.expects(:expand_command).with('/usr/bin/uname -m').returns nil + Facter::Util::Resolution.expects(:`).with('/usr/bin/uname -m').never + Facter::Util::Resolution.exec('/usr/bin/uname -m').should be_nil end - - it "should not run the command if not found in any search path" do - Facter::Util::Resolution.stubs(:search_paths).returns ['/sbin', '/bin' ] - File.stubs(:executable?).with(File.join('/sbin','ifconfig')).returns false - File.stubs(:executable?).with(File.join('/bin','ifconfig')).returns false - Facter::Util::Resolution.exec('ifconfig -a').should be_nil + it "should not run the command if path to binary is not absolute" do + Facter::Util::Resolution.expects(:expand_command).with('uname -m').returns nil + Facter::Util::Resolution.expects(:`).with('uname -m').never + Facter::Util::Resolution.exec('uname -m').should be_nil end end end - describe "when run on windows" do - before :each do - Facter::Util::Config.stubs(:is_windows?).returns true - end - - describe "and the binary is an absolute path" do - it "should run the command if the binary is found" do - File.expects(:executable?).with('C:\foo\bar.exe').returns true - Facter::Util::Resolution.expects(:`).with('C:\foo\bar.exe /a /b /c "foo bar.txt"').returns "done\n" - Facter::Util::Resolution.exec('C:\foo\bar.exe /a /b /c "foo bar.txt"').should == 'done' - end - - it "should handle quoted binaries with spaces correctly" do - File.expects(:executable?).with('C:\foo baz\bar.exe').returns true - Facter::Util::Resolution.expects(:`).with('"C:\foo baz\bar.exe" /a /b /c "foo bar.txt"').returns "done\n" - Facter::Util::Resolution.exec('"C:\foo baz\bar.exe" /a /b /c "foo bar.txt"').should == 'done' + context "when run on windows", :as_platform => :windows do + context "binary is present" do + it "should run the command if path to binary is absolute" do + Facter::Util::Resolution.expects(:expand_command).with(%q{C:\Windows\foo.exe /a /b}).returns(%q{C:\Windows\foo.exe /a /b}) + Facter::Util::Resolution.expects(:`).with(%q{C:\Windows\foo.exe /a /b}).returns 'bar' + Facter::Util::Resolution.exec(%q{C:\Windows\foo.exe /a /b}).should == 'bar' end - it "should not run the command if the binary is not found" do - File.expects(:executable?).with('C:\foo\bar.exe').returns false - Facter::Util::Resolution.expects(:`).with('C:\foo\bar.exe /a /b /c "foo bar.txt"').never - Facter::Util::Resolution.exec('C:\foo\bar.exe /a /b /c "foo bar.txt"').should be_nil + it "should run the expanded command if path to binary not absolute" do + Facter::Util::Resolution.expects(:expand_command).with(%q{foo.exe /a /b}).returns(%q{C:\Windows\foo.exe /a /b}) + Facter::Util::Resolution.expects(:`).with(%q{C:\Windows\foo.exe /a /b}).returns 'bar' + Facter::Util::Resolution.exec(%q{foo.exe /a /b}).should == 'bar' end end - describe "and the binary is a relative path" do - it "should run the command if found in search path" do - Facter::Util::Resolution.stubs(:search_paths).returns ['C:\Windows\system32', 'C:\Windows', 'C:\Windows\System32\Wbem' ] - File.stubs(:executable?).with(File.join('C:\Windows\system32','foo.exe')).returns false - File.stubs(:executable?).with(File.join('C:\Windows','foo.exe')).returns true - File.stubs(:executable?).with(File.join('C:\Windows\System32\Wbem', 'foo.exe')).returns false - Facter::Util::Resolution.expects(:`).with(File.join('C:\Windows','foo.exe')).returns "done\n" - Facter::Util::Resolution.exec('foo.exe').should == 'done' + context "binary is not present" do + it "should not run the command if path to binary is absolute" do + Facter::Util::Resolution.expects(:expand_command).with(%q{C:\Windows\foo.exe /a /b}).returns nil + Facter::Util::Resolution.expects(:`).with(%q{C:\Windows\foo.exe /a /b}).never + Facter::Util::Resolution.exec(%q{C:\Windows\foo.exe /a /b}).should be_nil end - - it "should try to find the correct extension" do - ENV.stubs(:[]).with('PATHEXT').returns nil - Facter::Util::Resolution.stubs(:search_paths).returns ['C:\Windows\system32', 'C:\Windows'] - ['.COM', '.EXE', '.BAT', '.CMD', '' ].each do |ext| - File.stubs(:executable?).with(File.join('C:\Windows\system32',"foo#{ext}")).returns false - end - ['.COM', '.BAT', '.CMD', '' ].each do |ext| - File.stubs(:executable?).with(File.join('C:\Windows',"foo#{ext}")).returns false - end - File.stubs(:executable?).with(File.join('C:\Windows',"foo.EXE")).returns true - Facter::Util::Resolution.expects(:`).with(File.join('C:\Windows','foo.EXE')).returns "done\n" - Facter::Util::Resolution.exec('foo').should == 'done' + it "should try to run the command and return output of a shell-builtin" do + pending end - - it "should not run the command if not found in any search path" do - Facter::Util::Resolution.stubs(:search_paths).returns ['C:\Windows\system32', 'C:\Windows', 'C:\Windows\System32\Wbem' ] - File.stubs(:executable?).with(File.join('C:\Windows\system32','foo.exe')).returns false - File.stubs(:executable?).with(File.join('C:\Windows','foo.exe')).returns false - File.stubs(:executable?).with(File.join('C:\Windows\System32\Wbem', 'foo.exe')).returns false - Facter::Util::Resolution.exec('foo.exe').should be_nil + it "should try to run the command and return nil if not shell-builtin" do + pending end end end From 2d164e8cd6e213874b184925897f476db8680241 Mon Sep 17 00:00:00 2001 From: Stefan Schulte Date: Sat, 21 Apr 2012 13:01:34 +0200 Subject: [PATCH 0791/3753] (#13678) Join PATHs correctly on windows On windows File.join joins with the File::SEPARATOR which is '/' on windows. While a lot of the windows API and the ruby filetests allow / as a separator we should use File::ALT_SEPARATOR ('\' on windows) to create pathnames on windows --- lib/facter/util/resolution.rb | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 0680f8700c..55d57c545e 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -39,12 +39,15 @@ def self.which(bin) else search_paths.each do |dir| dest = File.join(dir, bin) - if Facter::Util::Config.is_windows? && File.extname(dest).empty? - exts = ENV['PATHEXT'] - exts = exts ? exts.split(File::PATH_SEPARATOR) : %w[.COM .EXE .BAT .CMD] - exts.each do |ext| - destext = dest + ext - return destext if File.executable?(destext) + if Facter::Util::Config.is_windows? + dest.gsub!(File::SEPARATOR, File::ALT_SEPARATOR) + if File.extname(dest).empty? + exts = ENV['PATHEXT'] + exts = exts ? exts.split(File::PATH_SEPARATOR) : %w[.COM .EXE .BAT .CMD] + exts.each do |ext| + destext = dest + ext + return destext if File.executable?(destext) + end end end return dest if File.executable?(dest) From 8f4c0163903131062a49dfee392dd019847a02fa Mon Sep 17 00:00:00 2001 From: Stefan Schulte Date: Sat, 21 Apr 2012 13:10:13 +0200 Subject: [PATCH 0792/3753] (#13678) Single quote paths on unix with spaces If we want to execute a command foo which is found in a path with spaces we have to quote the path in order to be able to pass the command to the shell. Instead of using double quotes we should use single quotes on unix. On windows only double quotes are valid --- lib/facter/util/resolution.rb | 3 ++- spec/unit/util/resolution_spec.rb | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 55d57c545e..c0279f9247 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -90,7 +90,8 @@ def self.expand_command(command) if exe = which(exe) # the binary was not quoted which means it contains no spaces. But the # full path to the binary may do so. - exe = "\"#{exe}\"" if exe =~ /\s/ + exe = "\"#{exe}\"" if exe =~ /\s/ and Facter::Util::Config.is_windows? + exe = "'#{exe}'" if exe =~ /\s/ and not Facter::Util::Config.is_windows? [ exe, arguments ].compact.join(" ") end end diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index 5a8fff0812..31088cd5c9 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -465,7 +465,6 @@ end it "should quote expanded binary if found in path with spaces" do - pending Facter::Util::Resolution.expects(:which).with('foo.sh').returns '/home/bob/my tools/foo.sh' Facter::Util::Resolution.expand_command('foo.sh /a /b').should == %q{'/home/bob/my tools/foo.sh' /a /b} end From e1025c15ee3a01cbae537811679847571bacf619 Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Mon, 23 Apr 2012 10:31:03 -0700 Subject: [PATCH 0793/3753] Update CHANGELOG for Facter 1.6.8 --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 471a08b324..881d8fb5ba 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,4 @@ -1.6.8rc1 +1.6.8 === b86fe4c (#12831) Add rspec tests to have_which method in Resolution 70be957 (#12831) Fix recursion on first kernel fact resolution From 2842c96b602e1a3f7e4184554ff3aeaa3a9eeb70 Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Mon, 23 Apr 2012 13:42:26 -0700 Subject: [PATCH 0794/3753] Update rpm spec file This commit updates the rpm spec file to change the ruby dependencies to version 1.8.5. It also removes any conditionals for ruby abi, because we can now assume ruby abi present. The dependency on ruby is bumped to version 1.8.5, as we can also assume this ruby version, given that el4 is EOL. The facter version is bumped to 1.6.8 to reflect the latest released version. Finally, this commit removes the manual noarch build setting, as facter is now arch-specific because of its dependency on dmidecode. Signed-off-by: Moses Mendoza --- conf/redhat/facter.spec | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index fcf4e1e261..e211eac7a1 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -1,11 +1,8 @@ %{!?ruby_sitelibdir: %define ruby_sitelibdir %(ruby -rrbconfig -e 'puts Object.const_get(defined?(RbConfig) ? :RbConfig : :Config)::CONFIG["sitelibdir"]')} -%define has_ruby_abi 0%{?fedora} || 0%{?rhel} >= 5 -%define has_ruby_noarch %has_ruby_abi - Summary: Ruby module for collecting simple facts about a host operating system Name: facter -Version: 1.6.6 +Version: 1.6.8 Release: 1%{?dist} #Release: 0.1rc1%{?dist} License: Apache 2.0 @@ -16,20 +13,15 @@ Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}.tar.gz #Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc1.tar.gz.asc BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) -%if %has_ruby_noarch -BuildArch: noarch -%endif -Requires: ruby >= 1.8.1 +Requires: ruby >= 1.8.5 Requires: which # Note: dmidecode is only available on x86 and x86_64 so this package may need to move into being # arch specific if people are using ppc, arm, s390 etc Requires: dmidecode Requires: pciutils -%if %has_ruby_abi Requires: ruby(abi) = 1.8 -%endif -BuildRequires: ruby >= 1.8.1 +BuildRequires: ruby >= 1.8.5 %description Ruby module for collecting simple facts about a host Operating @@ -59,6 +51,9 @@ rm -rf %{buildroot} %changelog +* Mon Apr 23 2012 Moses Mendoza - 1.6.8-1 +- Update for 1.6.8, spec for arch-specific build, req ruby 1.8.5 + * Thu Feb 23 2012 Michael Stahnke - 1.6.6-1 - Update for 1.6.6 From 14eee2b96f0b7f08416bd9ed5087fcaa9c6d0566 Mon Sep 17 00:00:00 2001 From: Jeff Weiss Date: Fri, 27 Apr 2012 00:51:13 -0700 Subject: [PATCH 0795/3753] (#12864) Windows: get primary DNS from registry Windows does not expose the host's primary DNS suffix via WMI, only the registry. This commit adds a registry helper class and will retrieve the primary DNS suffix from the registry. In the case the primary DNS suffix does not exist or is empty, the domain fact will fall back to using WMI for any connection-specific DNS suffix. --- lib/facter/domain.rb | 15 ++++--- lib/facter/util/registry.rb | 9 ++++ spec/unit/domain_spec.rb | 38 +++++++++++++---- spec/unit/util/registry_spec.rb | 74 +++++++++++++++++++++++++++++++++ 4 files changed, 122 insertions(+), 14 deletions(-) create mode 100644 lib/facter/util/registry.rb create mode 100644 spec/unit/util/registry_spec.rb diff --git a/lib/facter/domain.rb b/lib/facter/domain.rb index ff5e988c43..2ae909a450 100644 --- a/lib/facter/domain.rb +++ b/lib/facter/domain.rb @@ -52,12 +52,17 @@ Facter.add(:domain) do confine :kernel => :windows setcode do - require 'facter/util/wmi' + require 'facter/util/registry' domain = "" - Facter::Util::WMI.execquery("select DNSDomain from Win32_NetworkAdapterConfiguration where IPEnabled = True").each { |nic| - domain = nic.DNSDomain - break - } + regvalue = Facter::Util::Registry.hklm_read('SYSTEM\CurrentControlSet\Services\Tcpip\Parameters', 'Domain') + domain = regvalue if regvalue + if domain == "" + require 'facter/util/wmi' + Facter::Util::WMI.execquery("select DNSDomain from Win32_NetworkAdapterConfiguration where IPEnabled = True").each { |nic| + domain = nic.DNSDomain + break + } + end domain end end diff --git a/lib/facter/util/registry.rb b/lib/facter/util/registry.rb new file mode 100644 index 0000000000..dc13e458cc --- /dev/null +++ b/lib/facter/util/registry.rb @@ -0,0 +1,9 @@ +module Facter::Util::Registry + class << self + def hklm_read(key, value) + require 'win32/registry' + reg = Win32::Registry::HKEY_LOCAL_MACHINE.open(key) + reg[value] + end + end +end diff --git a/spec/unit/domain_spec.rb b/spec/unit/domain_spec.rb index 6ef416cb4b..07195ca180 100755 --- a/spec/unit/domain_spec.rb +++ b/spec/unit/domain_spec.rb @@ -98,19 +98,39 @@ end describe "on Windows" do - it "should use the DNSDomain for the first nic where ip is enabled" do + before(:each) do Facter.fact(:kernel).stubs(:value).returns("windows") + require 'facter/util/registry' + end + describe "with primary dns suffix" do + before(:each) do + Facter::Util::Registry.stubs(:hklm_read).returns('baz.com') + end + it "should get the primary dns suffix" do + Facter.fact(:domain).value.should == 'baz.com' + end + it "should not execute the wmi query" do + require 'facter/util/wmi' + Facter::Util::WMI.expects(:execquery).never + Facter.fact(:domain).value + end + end + describe "without primary dns suffix" do + before(:each) do + Facter::Util::Registry.stubs(:hklm_read).returns('') + end + it "should use the DNSDomain for the first nic where ip is enabled" do + nic = stubs 'nic' + nic.stubs(:DNSDomain).returns("foo.com") - nic = stubs 'nic' - nic.stubs(:DNSDomain).returns("foo.com") - - nic2 = stubs 'nic' - nic2.stubs(:DNSDomain).returns("bar.com") + nic2 = stubs 'nic' + nic2.stubs(:DNSDomain).returns("bar.com") - require 'facter/util/wmi' - Facter::Util::WMI.stubs(:execquery).with("select DNSDomain from Win32_NetworkAdapterConfiguration where IPEnabled = True").returns([nic, nic2]) + require 'facter/util/wmi' + Facter::Util::WMI.stubs(:execquery).with("select DNSDomain from Win32_NetworkAdapterConfiguration where IPEnabled = True").returns([nic, nic2]) - Facter.fact(:domain).value.should == 'foo.com' + Facter.fact(:domain).value.should == 'foo.com' + end end end end diff --git a/spec/unit/util/registry_spec.rb b/spec/unit/util/registry_spec.rb new file mode 100644 index 0000000000..0da41a5027 --- /dev/null +++ b/spec/unit/util/registry_spec.rb @@ -0,0 +1,74 @@ +#!/usr/bin/env rspec +require 'spec_helper' +require 'facter/operatingsystem' +require 'facter/util/registry' + +describe Facter::Util::Registry do + describe "hklm_read", :if => Facter::Util::Config.is_windows? do + before(:all) do + require 'win32/registry' + end + describe "valid params" do + [ {:key => "valid_key", :value => "valid_value", :expected => "valid"}, + {:key => "valid_key", :value => "", :expected => "valid"}, + {:key => "valid_key", :value => nil, :expected => "invalid"}, + {:key => "", :value => "valid_value", :expected => "valid"}, + {:key => "", :value => "", :expected => "valid"}, + {:key => "", :value => nil, :expected => "invalid"}, + {:key => nil, :value => "valid_value", :expected => "invalid"}, + {:key => nil, :value => "", :expected => "invalid"}, + {:key => nil, :value => nil, :expected => "invalid"} + ].each do |scenario| + describe "with key #{scenario[:key] || "nil"} and value #{scenario[:value] || "nil"}" do + let :fake_registry_key do + fake = {} + fake[scenario[:value]] = scenario[:expected] + fake + end + it "should return #{scenario[:expected]} value" do + Win32::Registry::HKEY_LOCAL_MACHINE.stubs(:open).with(scenario[:key]).returns(fake_registry_key) + + Facter::Util::Registry.hklm_read(scenario[:key], scenario[:value]).should == scenario[:expected] + end + end + end + end + + describe "invalid params" do + [ {:key => "valid_key", :value => "invalid_value"}, + {:key => "valid_key", :value => ""}, + {:key => "valid_key", :value => nil}, + ].each do |scenario| + describe "with valid key and value #{scenario[:value] || "nil"}" do + let :fake_registry_key do + {} + end + it "should raise an error" do + Win32::Registry::HKEY_LOCAL_MACHINE.stubs(:open).with(scenario[:key]).returns(fake_registry_key) + + Facter::Util::Registry.hklm_read(scenario[:key], scenario[:value]).should raise_error + end + end + end + [ {:key => "invalid_key", :value => "valid_value"}, + {:key => "invalid_key", :value => ""}, + {:key => "invalid_key", :value => nil}, + {:key => "", :value => "valid_value"}, + {:key => "", :value => ""}, + {:key => "", :value => nil}, + {:key => nil, :value => "valid_value"}, + {:key => nil, :value => ""}, + {:key => nil, :value => nil} + ].each do |scenario| + describe "with invalid key #{scenario[:key] || "nil"} and value #{scenario[:value] || "nil"}" do + it "should raise an error" do + Win32::Registry::HKEY_LOCAL_MACHINE.stubs(:open).with(scenario[:key]).raises(Win32::Registry::Error, 2) + expect do + Facter::Util::Registry.hklm_read(scenario[:key], scenario[:value]) + end.to raise_error Win32::Registry::Error + end + end + end + end + end +end From fbaa8fed9bd0400eb2f75cff464e777a1f8380a1 Mon Sep 17 00:00:00 2001 From: Stefan Schulte Date: Sun, 29 Apr 2012 21:47:49 +0200 Subject: [PATCH 0796/3753] (#11511) Correct lsbrelease specfile filename All spec files should follow the convention foo_spec.rb. Rename spec/unit/lsbrelease.rb to spec/unit/lsbrelease_spec.rb. --- spec/unit/{lsbrelease.rb => lsbrelease_spec.rb} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename spec/unit/{lsbrelease.rb => lsbrelease_spec.rb} (100%) diff --git a/spec/unit/lsbrelease.rb b/spec/unit/lsbrelease_spec.rb similarity index 100% rename from spec/unit/lsbrelease.rb rename to spec/unit/lsbrelease_spec.rb From 85654b0603ce0fc8b304985b42cf227c3360bd97 Mon Sep 17 00:00:00 2001 From: Stefan Schulte Date: Sun, 29 Apr 2012 22:34:35 +0200 Subject: [PATCH 0797/3753] (#13678) Allow passing shell built-ins to exec method on windows The former exec method tried to run the command on windows no matter wether it could be found on the filesystem or not. This allowed end users to run shell-builtins with the exec method. The new exec method always tried to expand the binary first and returned nil if the binary was not found. This commit now restores the old behaviour on windows: Even if we fail to expand the command, we will try to run the command in the exact same way as it was passed to the exec method in case it is indeed a shell built-in. But we will now raise a deprecation warning. Reason for deprecating this "even if we cannot find it, just run it" behaviour: We may want to predetermine the paths where facter tries to find binaries in the future. A fall back behaviour may then lead to strange results. Most built-ins can be expressed in pure ruby anyways. --- lib/facter/util/resolution.rb | 14 +++++++++++++- spec/unit/util/resolution_spec.rb | 10 ++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index c0279f9247..3242fd8bf5 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -106,12 +106,24 @@ def self.expand_command(command) def self.exec(code, interpreter = nil) Facter.warnonce "The interpreter parameter to 'exec' is deprecated and will be removed in a future version." if interpreter - return nil unless code = expand_command(code) + if expanded_code = expand_command(code) + # if we can find the binary, we'll run the command with the expanded path to the binary + code = expanded_code + else + # if we cannot find the binary return nil on posix. On windows we'll still try to run the + # command in case it is a shell-builtin. In case it is not, windows will raise Errno::ENOENT + return nil unless Facter::Util::Config.is_windows? + return nil if absolute_path?(code) + end out = nil begin out = %x{#{code}}.chomp + Facter.warnonce 'Using Facter::Util::Resolution.exec with a shell built-in is deprecated. Most built-ins can be replaced with native ruby commands. If you really have to run a built-in, pass "cmd /c your_builtin" as a command' unless expanded_code + rescue Errno::ENOENT => detail + # command not found on Windows + return nil rescue => detail $stderr.puts detail return nil diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index 31088cd5c9..cb1faa0fe6 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -541,10 +541,16 @@ Facter::Util::Resolution.exec(%q{C:\Windows\foo.exe /a /b}).should be_nil end it "should try to run the command and return output of a shell-builtin" do - pending + Facter::Util::Resolution.expects(:expand_command).with(%q{echo foo}).returns nil + Facter::Util::Resolution.expects(:`).with(%q{echo foo}).returns 'foo' + Facter.expects(:warnonce).with('Using Facter::Util::Resolution.exec with a shell built-in is deprecated. Most built-ins can be replaced with native ruby commands. If you really have to run a built-in, pass "cmd /c your_builtin" as a command') + Facter::Util::Resolution.exec(%q{echo foo}).should == 'foo' end it "should try to run the command and return nil if not shell-builtin" do - pending + Facter::Util::Resolution.expects(:expand_command).with(%q{echo foo}).returns nil + Facter::Util::Resolution.stubs(:`).with(%q{echo foo}).raises Errno::ENOENT, 'some_error_message' + Facter.expects(:warnonce).never + Facter::Util::Resolution.exec(%q{echo foo}).should be_nil end end end From ac51593395bc926ccddd0487030020f69e8fa230 Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Mon, 30 Apr 2012 12:07:05 -0700 Subject: [PATCH 0798/3753] Wrap dmidecode/pciutils in ifarch block This commit wraps the requirement of dmidecode and pciutils in an ifarch block, because these packages are not available on all arches. Signed-off-by: Moses Mendoza --- conf/redhat/facter.spec | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index e211eac7a1..cec36fc753 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -16,10 +16,11 @@ BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) Requires: ruby >= 1.8.5 Requires: which -# Note: dmidecode is only available on x86 and x86_64 so this package may need to move into being -# arch specific if people are using ppc, arm, s390 etc +# dmidecode and pciutils are not available on all arches +%ifarch %ix86 x86_64 ia64 Requires: dmidecode Requires: pciutils +%endif Requires: ruby(abi) = 1.8 BuildRequires: ruby >= 1.8.5 @@ -51,7 +52,7 @@ rm -rf %{buildroot} %changelog -* Mon Apr 23 2012 Moses Mendoza - 1.6.8-1 +* Mon Apr 30 2012 Moses Mendoza - 1.6.8-1 - Update for 1.6.8, spec for arch-specific build, req ruby 1.8.5 * Thu Feb 23 2012 Michael Stahnke - 1.6.6-1 From 753f3a4a1659b3f1d2a4dd5a44b8f8d09b23d0e4 Mon Sep 17 00:00:00 2001 From: Jeff Weiss Date: Wed, 2 May 2012 16:52:10 -0700 Subject: [PATCH 0799/3753] Revert "(#12864) Windows: get primary DNS from registry" This reverts commit 14eee2b96f0b7f08416bd9ed5087fcaa9c6d0566 so that it can be re-targeted to 2.0.0 based on discussions on pull request 192 and within the ticket. The changes are re-targeted to master in pull request 193. --- lib/facter/domain.rb | 15 +++---- lib/facter/util/registry.rb | 9 ---- spec/unit/domain_spec.rb | 38 ++++------------- spec/unit/util/registry_spec.rb | 74 --------------------------------- 4 files changed, 14 insertions(+), 122 deletions(-) delete mode 100644 lib/facter/util/registry.rb delete mode 100644 spec/unit/util/registry_spec.rb diff --git a/lib/facter/domain.rb b/lib/facter/domain.rb index 2ae909a450..ff5e988c43 100644 --- a/lib/facter/domain.rb +++ b/lib/facter/domain.rb @@ -52,17 +52,12 @@ Facter.add(:domain) do confine :kernel => :windows setcode do - require 'facter/util/registry' + require 'facter/util/wmi' domain = "" - regvalue = Facter::Util::Registry.hklm_read('SYSTEM\CurrentControlSet\Services\Tcpip\Parameters', 'Domain') - domain = regvalue if regvalue - if domain == "" - require 'facter/util/wmi' - Facter::Util::WMI.execquery("select DNSDomain from Win32_NetworkAdapterConfiguration where IPEnabled = True").each { |nic| - domain = nic.DNSDomain - break - } - end + Facter::Util::WMI.execquery("select DNSDomain from Win32_NetworkAdapterConfiguration where IPEnabled = True").each { |nic| + domain = nic.DNSDomain + break + } domain end end diff --git a/lib/facter/util/registry.rb b/lib/facter/util/registry.rb deleted file mode 100644 index dc13e458cc..0000000000 --- a/lib/facter/util/registry.rb +++ /dev/null @@ -1,9 +0,0 @@ -module Facter::Util::Registry - class << self - def hklm_read(key, value) - require 'win32/registry' - reg = Win32::Registry::HKEY_LOCAL_MACHINE.open(key) - reg[value] - end - end -end diff --git a/spec/unit/domain_spec.rb b/spec/unit/domain_spec.rb index 07195ca180..6ef416cb4b 100755 --- a/spec/unit/domain_spec.rb +++ b/spec/unit/domain_spec.rb @@ -98,39 +98,19 @@ end describe "on Windows" do - before(:each) do + it "should use the DNSDomain for the first nic where ip is enabled" do Facter.fact(:kernel).stubs(:value).returns("windows") - require 'facter/util/registry' - end - describe "with primary dns suffix" do - before(:each) do - Facter::Util::Registry.stubs(:hklm_read).returns('baz.com') - end - it "should get the primary dns suffix" do - Facter.fact(:domain).value.should == 'baz.com' - end - it "should not execute the wmi query" do - require 'facter/util/wmi' - Facter::Util::WMI.expects(:execquery).never - Facter.fact(:domain).value - end - end - describe "without primary dns suffix" do - before(:each) do - Facter::Util::Registry.stubs(:hklm_read).returns('') - end - it "should use the DNSDomain for the first nic where ip is enabled" do - nic = stubs 'nic' - nic.stubs(:DNSDomain).returns("foo.com") - nic2 = stubs 'nic' - nic2.stubs(:DNSDomain).returns("bar.com") + nic = stubs 'nic' + nic.stubs(:DNSDomain).returns("foo.com") - require 'facter/util/wmi' - Facter::Util::WMI.stubs(:execquery).with("select DNSDomain from Win32_NetworkAdapterConfiguration where IPEnabled = True").returns([nic, nic2]) + nic2 = stubs 'nic' + nic2.stubs(:DNSDomain).returns("bar.com") - Facter.fact(:domain).value.should == 'foo.com' - end + require 'facter/util/wmi' + Facter::Util::WMI.stubs(:execquery).with("select DNSDomain from Win32_NetworkAdapterConfiguration where IPEnabled = True").returns([nic, nic2]) + + Facter.fact(:domain).value.should == 'foo.com' end end end diff --git a/spec/unit/util/registry_spec.rb b/spec/unit/util/registry_spec.rb deleted file mode 100644 index 0da41a5027..0000000000 --- a/spec/unit/util/registry_spec.rb +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/env rspec -require 'spec_helper' -require 'facter/operatingsystem' -require 'facter/util/registry' - -describe Facter::Util::Registry do - describe "hklm_read", :if => Facter::Util::Config.is_windows? do - before(:all) do - require 'win32/registry' - end - describe "valid params" do - [ {:key => "valid_key", :value => "valid_value", :expected => "valid"}, - {:key => "valid_key", :value => "", :expected => "valid"}, - {:key => "valid_key", :value => nil, :expected => "invalid"}, - {:key => "", :value => "valid_value", :expected => "valid"}, - {:key => "", :value => "", :expected => "valid"}, - {:key => "", :value => nil, :expected => "invalid"}, - {:key => nil, :value => "valid_value", :expected => "invalid"}, - {:key => nil, :value => "", :expected => "invalid"}, - {:key => nil, :value => nil, :expected => "invalid"} - ].each do |scenario| - describe "with key #{scenario[:key] || "nil"} and value #{scenario[:value] || "nil"}" do - let :fake_registry_key do - fake = {} - fake[scenario[:value]] = scenario[:expected] - fake - end - it "should return #{scenario[:expected]} value" do - Win32::Registry::HKEY_LOCAL_MACHINE.stubs(:open).with(scenario[:key]).returns(fake_registry_key) - - Facter::Util::Registry.hklm_read(scenario[:key], scenario[:value]).should == scenario[:expected] - end - end - end - end - - describe "invalid params" do - [ {:key => "valid_key", :value => "invalid_value"}, - {:key => "valid_key", :value => ""}, - {:key => "valid_key", :value => nil}, - ].each do |scenario| - describe "with valid key and value #{scenario[:value] || "nil"}" do - let :fake_registry_key do - {} - end - it "should raise an error" do - Win32::Registry::HKEY_LOCAL_MACHINE.stubs(:open).with(scenario[:key]).returns(fake_registry_key) - - Facter::Util::Registry.hklm_read(scenario[:key], scenario[:value]).should raise_error - end - end - end - [ {:key => "invalid_key", :value => "valid_value"}, - {:key => "invalid_key", :value => ""}, - {:key => "invalid_key", :value => nil}, - {:key => "", :value => "valid_value"}, - {:key => "", :value => ""}, - {:key => "", :value => nil}, - {:key => nil, :value => "valid_value"}, - {:key => nil, :value => ""}, - {:key => nil, :value => nil} - ].each do |scenario| - describe "with invalid key #{scenario[:key] || "nil"} and value #{scenario[:value] || "nil"}" do - it "should raise an error" do - Win32::Registry::HKEY_LOCAL_MACHINE.stubs(:open).with(scenario[:key]).raises(Win32::Registry::Error, 2) - expect do - Facter::Util::Registry.hklm_read(scenario[:key], scenario[:value]) - end.to raise_error Win32::Registry::Error - end - end - end - end - end -end From 5aa2a6f71ce0ccabe6097798115871dc08d9dc5e Mon Sep 17 00:00:00 2001 From: Jeff Weiss Date: Fri, 27 Apr 2012 00:51:13 -0700 Subject: [PATCH 0800/3753] (#12864) Windows: get primary DNS from registry Windows does not expose the host's primary DNS suffix via WMI, only the registry. This commit adds a registry helper class and will retrieve the primary DNS suffix from the registry. In the case the primary DNS suffix does not exist or is empty, the domain fact will fall back to using WMI for any connection-specific DNS suffix. --- lib/facter/domain.rb | 15 ++++--- lib/facter/util/registry.rb | 9 ++++ spec/unit/domain_spec.rb | 38 +++++++++++++---- spec/unit/util/registry_spec.rb | 74 +++++++++++++++++++++++++++++++++ 4 files changed, 122 insertions(+), 14 deletions(-) create mode 100644 lib/facter/util/registry.rb create mode 100644 spec/unit/util/registry_spec.rb diff --git a/lib/facter/domain.rb b/lib/facter/domain.rb index ff5e988c43..2ae909a450 100644 --- a/lib/facter/domain.rb +++ b/lib/facter/domain.rb @@ -52,12 +52,17 @@ Facter.add(:domain) do confine :kernel => :windows setcode do - require 'facter/util/wmi' + require 'facter/util/registry' domain = "" - Facter::Util::WMI.execquery("select DNSDomain from Win32_NetworkAdapterConfiguration where IPEnabled = True").each { |nic| - domain = nic.DNSDomain - break - } + regvalue = Facter::Util::Registry.hklm_read('SYSTEM\CurrentControlSet\Services\Tcpip\Parameters', 'Domain') + domain = regvalue if regvalue + if domain == "" + require 'facter/util/wmi' + Facter::Util::WMI.execquery("select DNSDomain from Win32_NetworkAdapterConfiguration where IPEnabled = True").each { |nic| + domain = nic.DNSDomain + break + } + end domain end end diff --git a/lib/facter/util/registry.rb b/lib/facter/util/registry.rb new file mode 100644 index 0000000000..dc13e458cc --- /dev/null +++ b/lib/facter/util/registry.rb @@ -0,0 +1,9 @@ +module Facter::Util::Registry + class << self + def hklm_read(key, value) + require 'win32/registry' + reg = Win32::Registry::HKEY_LOCAL_MACHINE.open(key) + reg[value] + end + end +end diff --git a/spec/unit/domain_spec.rb b/spec/unit/domain_spec.rb index 6ef416cb4b..07195ca180 100755 --- a/spec/unit/domain_spec.rb +++ b/spec/unit/domain_spec.rb @@ -98,19 +98,39 @@ end describe "on Windows" do - it "should use the DNSDomain for the first nic where ip is enabled" do + before(:each) do Facter.fact(:kernel).stubs(:value).returns("windows") + require 'facter/util/registry' + end + describe "with primary dns suffix" do + before(:each) do + Facter::Util::Registry.stubs(:hklm_read).returns('baz.com') + end + it "should get the primary dns suffix" do + Facter.fact(:domain).value.should == 'baz.com' + end + it "should not execute the wmi query" do + require 'facter/util/wmi' + Facter::Util::WMI.expects(:execquery).never + Facter.fact(:domain).value + end + end + describe "without primary dns suffix" do + before(:each) do + Facter::Util::Registry.stubs(:hklm_read).returns('') + end + it "should use the DNSDomain for the first nic where ip is enabled" do + nic = stubs 'nic' + nic.stubs(:DNSDomain).returns("foo.com") - nic = stubs 'nic' - nic.stubs(:DNSDomain).returns("foo.com") - - nic2 = stubs 'nic' - nic2.stubs(:DNSDomain).returns("bar.com") + nic2 = stubs 'nic' + nic2.stubs(:DNSDomain).returns("bar.com") - require 'facter/util/wmi' - Facter::Util::WMI.stubs(:execquery).with("select DNSDomain from Win32_NetworkAdapterConfiguration where IPEnabled = True").returns([nic, nic2]) + require 'facter/util/wmi' + Facter::Util::WMI.stubs(:execquery).with("select DNSDomain from Win32_NetworkAdapterConfiguration where IPEnabled = True").returns([nic, nic2]) - Facter.fact(:domain).value.should == 'foo.com' + Facter.fact(:domain).value.should == 'foo.com' + end end end end diff --git a/spec/unit/util/registry_spec.rb b/spec/unit/util/registry_spec.rb new file mode 100644 index 0000000000..93c8f0365a --- /dev/null +++ b/spec/unit/util/registry_spec.rb @@ -0,0 +1,74 @@ +#!/usr/bin/env rspec +require 'spec_helper' +require 'facter/operatingsystem' +require 'facter/util/registry' + +describe Facter::Util::Registry do + describe "hklm_read", :if => Facter::Util::Config.is_windows? do + before(:all) do + require 'win32/registry' + end + describe "valid params" do + [ {:key => "valid_key", :value => "valid_value", :expected => "valid"}, + {:key => "valid_key", :value => "", :expected => "valid"}, + {:key => "valid_key", :value => nil, :expected => "invalid"}, + {:key => "", :value => "valid_value", :expected => "valid"}, + {:key => "", :value => "", :expected => "valid"}, + {:key => "", :value => nil, :expected => "invalid"}, + {:key => nil, :value => "valid_value", :expected => "invalid"}, + {:key => nil, :value => "", :expected => "invalid"}, + {:key => nil, :value => nil, :expected => "invalid"} + ].each do |scenario| + describe "with key #{scenario[:key] || "nil"} and value #{scenario[:value] || "nil"}" do + let :fake_registry_key do + fake = {} + fake[scenario[:value]] = scenario[:expected] + fake + end + it "should return #{scenario[:expected]} value" do + Win32::Registry::HKEY_LOCAL_MACHINE.stubs(:open).with(scenario[:key]).returns(fake_registry_key) + + Facter::Util::Registry.hklm_read(scenario[:key], scenario[:value]).should == scenario[:expected] + end + end + end + end + + describe "invalid params" do + [ {:key => "valid_key", :value => "invalid_value"}, + {:key => "valid_key", :value => ""}, + {:key => "valid_key", :value => nil}, + ].each do |scenario| + describe "with valid key and value #{scenario[:value] || "nil"}" do + let :fake_registry_key do + {} + end + it "should raise an error" do + Win32::Registry::HKEY_LOCAL_MACHINE.stubs(:open).with(scenario[:key]).returns(fake_registry_key) + + Facter::Util::Registry.hklm_read(scenario[:key], scenario[:value]).should raise_error + end + end + end + [ {:key => "invalid_key", :value => "valid_value"}, + {:key => "invalid_key", :value => ""}, + {:key => "invalid_key", :value => nil}, + {:key => "", :value => "valid_value"}, + {:key => "", :value => ""}, + {:key => "", :value => nil}, + {:key => nil, :value => "valid_value"}, + {:key => nil, :value => ""}, + {:key => nil, :value => nil} + ].each do |scenario| + describe "with invalid key #{scenario[:key] || "nil"} and value #{scenario[:value] || "nil"}" do + it "should raise an error" do + Win32::Registry::HKEY_LOCAL_MACHINE.stubs(:open).with(scenario[:key]).raises(Win32::Registry::Error, 2) + expect do + Facter::Util::Registry.hklm_read(scenario[:key], scenario[:value]) + end.to raise_error Win32::Registry::Error + end + end + end + end + end +end From 6c46b2cc4df5edc71e250e99b4f65604e4010670 Mon Sep 17 00:00:00 2001 From: Ken Barber Date: Sun, 6 May 2012 01:07:58 +0100 Subject: [PATCH 0801/3753] (#14332) Correct stubbing on Ubuntu The tests for facter fail on Ubuntu because lsbdistid is not correctly stubbed. This patch fixes that small mistake by stubbing lsbdistid for all Linux tests, except where the test is really about testing for Ubuntu. --- spec/unit/operatingsystem_spec.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/spec/unit/operatingsystem_spec.rb b/spec/unit/operatingsystem_spec.rb index 89a18c45a4..59069a1bcd 100755 --- a/spec/unit/operatingsystem_spec.rb +++ b/spec/unit/operatingsystem_spec.rb @@ -37,6 +37,9 @@ describe "on Linux" do before :each do Facter.fact(:kernel).stubs(:value).returns("Linux") + + # Always stub lsbdistid by default, so tests work on Ubuntu + Facter.stubs(:value).with(:lsbdistid).returns(nil) end { @@ -69,7 +72,7 @@ it "on Ubuntu should use the lsbdistid fact" do FileUtils.stubs(:exists?).with("/etc/debian_version").returns true - Facter.fact(:lsbdistid).expects(:value).returns("Ubuntu") + Facter.stubs(:value).with(:lsbdistid).returns("Ubuntu") Facter.fact(:operatingsystem).value.should == "Ubuntu" end From b398bd8dc9118bfae03df69f09daf29b9e6416e2 Mon Sep 17 00:00:00 2001 From: Joachim de Groot Date: Sun, 6 May 2012 21:22:12 +0100 Subject: [PATCH 0802/3753] (#14334) Fix dmidecode based facts on DragonFly BSD --- lib/facter/util/manufacturer.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/facter/util/manufacturer.rb b/lib/facter/util/manufacturer.rb index 7542eae1f7..ecaa97febd 100644 --- a/lib/facter/util/manufacturer.rb +++ b/lib/facter/util/manufacturer.rb @@ -13,7 +13,7 @@ def self.get_dmi_table() return nil unless FileTest.exists?("/usr/local/sbin/dmidecode") output=%x{/usr/local/sbin/dmidecode 2>/dev/null} - when 'NetBSD' + when 'NetBSD', 'DragonFly' return nil unless FileTest.exists?("/usr/pkg/sbin/dmidecode") output=%x{/usr/pkg/sbin/dmidecode 2>/dev/null} @@ -38,7 +38,7 @@ def self.dmi_find_system_info(name) if line =~ /#{key}/ and line =~ /\n\s+#{value} (.+)\n/ result = $1.strip Facter.add(facterkey) do - confine :kernel => [ :linux, :freebsd, :netbsd, :sunos, :"gnu/kfreebsd" ] + confine :kernel => [ :linux, :freebsd, :netbsd, :sunos, :"gnu/kfreebsd", :dragonfly ] setcode do result end From 6b9e2e5470d7ad5c3e1edcae5588145c78abb7ea Mon Sep 17 00:00:00 2001 From: Hailee Kenney Date: Mon, 7 May 2012 16:40:45 -0700 Subject: [PATCH 0803/3753] (#7484) Domain fact should handle only TLD Prior to this commit, the domain fact executed "hostname" on all platforms, however the default on Linux is that "hostname" does not return FQDN. This causes problems when the machine only has a TLD, rather than being in a second level domain. For example, ns01.tld vs ns01.puppetlabs.com. This commit fixes that issue, and handles the case where hostname does not accept the -f option on Solaris. --- lib/facter/domain.rb | 4 +- spec/unit/domain_spec.rb | 180 ++++++++++++++++++++++----------------- 2 files changed, 103 insertions(+), 81 deletions(-) diff --git a/lib/facter/domain.rb b/lib/facter/domain.rb index 2ae909a450..6aeda809de 100644 --- a/lib/facter/domain.rb +++ b/lib/facter/domain.rb @@ -23,7 +23,9 @@ # Get the domain from various sources; the order of these # steps is important - if name = Facter::Util::Resolution.exec('hostname') \ + hostname_command = (Facter.value(:kernel) =~ /SunOS/i) ? 'hostname' : 'hostname -f' + + if name = Facter::Util::Resolution.exec(hostname_command) \ and name =~ /.*?\.(.+$)/ $1 diff --git a/spec/unit/domain_spec.rb b/spec/unit/domain_spec.rb index 07195ca180..695e6b771c 100755 --- a/spec/unit/domain_spec.rb +++ b/spec/unit/domain_spec.rb @@ -4,98 +4,118 @@ describe "Domain name facts" do - describe "on linux" do - before do - Facter.fact(:kernel).stubs(:value).returns("Linux") - FileTest.stubs(:exists?).with("/etc/resolv.conf").returns(true) - end - - it "should use the hostname binary" do - Facter::Util::Resolution.expects(:exec).with("hostname").returns "test.example.com" - Facter.fact(:domain).value.should == "example.com" - end + { :linux => {:kernel => "Linux", :hostname_command => "hostname -f"}, + :solaris => {:kernel => "SunOS", :hostname_command => "hostname"}, + :darwin => {:kernel => "Darwin", :hostname_command => "hostname -f"}, + :freebsd => {:kernel => "FreeBSD", :hostname_command => "hostname -f"}, + }.each do |key, nested_hash| - it "should fall back to the dnsdomainname binary" do - Facter::Util::Resolution.expects(:exec).with("hostname").returns("myhost") - Facter::Util::Resolution.expects(:exec).with("dnsdomainname").returns("example.com") - Facter.fact(:domain).value.should == "example.com" - end - - - it "should fall back to /etc/resolv.conf" do - Facter::Util::Resolution.expects(:exec).with("hostname").at_least_once.returns("myhost") - Facter::Util::Resolution.expects(:exec).with("dnsdomainname").at_least_once.returns("") - File.expects(:open).with('/etc/resolv.conf').at_least_once - Facter.fact(:domain).value - end - - it "should attempt to resolve facts in a specific order" do - seq = sequence('domain') - Facter::Util::Resolution.stubs(:exec).with("hostname").in_sequence(seq).at_least_once - Facter::Util::Resolution.stubs(:exec).with("dnsdomainname").in_sequence(seq).at_least_once - File.expects(:open).with('/etc/resolv.conf').in_sequence(seq).at_least_once - Facter.fact(:domain).value - end - - describe "when using /etc/resolv.conf" do + describe "on #{key}" do before do - Facter::Util::Resolution.stubs(:exec).with("hostname") - Facter::Util::Resolution.stubs(:exec).with("dnsdomainname") - @mock_file = mock() - File.stubs(:open).with("/etc/resolv.conf").yields(@mock_file) + Facter.fact(:kernel).stubs(:value).returns(nested_hash[:kernel]) + FileTest.stubs(:exists?).with("/etc/resolv.conf").returns(true) end - - it "should use the domain field over the search field" do - lines = [ - "nameserver 4.2.2.1", - "search example.org", - "domain example.com", - ] - @mock_file.expects(:each).multiple_yields(*lines) - Facter.fact(:domain).value.should == 'example.com' + + let :hostname_command do + nested_hash[:hostname_command] + end + + it "should use the hostname binary" do + Facter::Util::Resolution.expects(:exec).with(hostname_command).returns "test.example.com" + Facter.fact(:domain).value.should == "example.com" end - - it "should fall back to the search field" do - lines = [ - "nameserver 4.2.2.1", - "search example.org", - ] - @mock_file.expects(:each).multiple_yields(*lines) - Facter.fact(:domain).value.should == 'example.org' + + it "should fall back to the dnsdomainname binary" do + Facter::Util::Resolution.expects(:exec).with(hostname_command).returns("myhost") + Facter::Util::Resolution.expects(:exec).with("dnsdomainname").returns("example.com") + Facter.fact(:domain).value.should == "example.com" end - - it "should use the first domain in the search field" do - lines = [ - "search example.org example.net", - ] - @mock_file.expects(:each).multiple_yields(*lines) - Facter.fact(:domain).value.should == 'example.org' + + + it "should fall back to /etc/resolv.conf" do + Facter::Util::Resolution.expects(:exec).with(hostname_command).at_least_once.returns("myhost") + Facter::Util::Resolution.expects(:exec).with("dnsdomainname").at_least_once.returns("") + File.expects(:open).with('/etc/resolv.conf').at_least_once + Facter.fact(:domain).value + end + + it "should attempt to resolve facts in a specific order" do + seq = sequence('domain') + Facter::Util::Resolution.stubs(:exec).with(hostname_command).in_sequence(seq).at_least_once + Facter::Util::Resolution.stubs(:exec).with("dnsdomainname").in_sequence(seq).at_least_once + File.expects(:open).with('/etc/resolv.conf').in_sequence(seq).at_least_once + Facter.fact(:domain).value end + + describe "Top level domain" do + it "should find the domain name" do + Facter::Util::Resolution.expects(:exec).with(hostname_command).returns "ns01.tld" + Facter::Util::Resolution.expects(:exec).with("dnsdomainname").never + File.expects(:exists?).with('/etc/resolv.conf').never + Facter.fact(:domain).value.should == "tld" + end + end + + describe "when using /etc/resolv.conf" do + before do + Facter::Util::Resolution.stubs(:exec).with(hostname_command) + Facter::Util::Resolution.stubs(:exec).with("dnsdomainname") + @mock_file = mock() + File.stubs(:open).with("/etc/resolv.conf").yields(@mock_file) + end + + it "should use the domain field over the search field" do + lines = [ + "nameserver 4.2.2.1", + "search example.org", + "domain example.com", + ] + @mock_file.expects(:each).multiple_yields(*lines) + Facter.fact(:domain).value.should == 'example.com' + end - # Test permutations of domain and search - [ - ["domain domain", "domain"], - ["domain search", "search"], - ["search domain", "domain"], - ["search search", "search"], - ["search domain notdomain", "domain"], - [["#search notdomain","search search"], "search"], - [["# search notdomain","search search"], "search"], - [["#domain notdomain","domain domain"], "domain"], - [["# domain notdomain","domain domain"], "domain"], - ].each do |tuple| - field = tuple[0] - expect = tuple[1] - it "should return #{expect} from \"#{field}\"" do + it "should fall back to the search field" do lines = [ - field - ].flatten + "nameserver 4.2.2.1", + "search example.org", + ] @mock_file.expects(:each).multiple_yields(*lines) - Facter.fact(:domain).value.should == expect + Facter.fact(:domain).value.should == 'example.org' + end + + it "should use the first domain in the search field" do + lines = [ + "search example.org example.net", + ] + @mock_file.expects(:each).multiple_yields(*lines) + Facter.fact(:domain).value.should == 'example.org' + end + + # Test permutations of domain and search + [ + ["domain domain", "domain"], + ["domain search", "search"], + ["search domain", "domain"], + ["search search", "search"], + ["search domain notdomain", "domain"], + [["#search notdomain","search search"], "search"], + [["# search notdomain","search search"], "search"], + [["#domain notdomain","domain domain"], "domain"], + [["# domain notdomain","domain domain"], "domain"], + ].each do |tuple| + field = tuple[0] + expect = tuple[1] + it "should return #{expect} from \"#{field}\"" do + lines = [ + field + ].flatten + @mock_file.expects(:each).multiple_yields(*lines) + Facter.fact(:domain).value.should == expect + end end end end - end + end describe "on Windows" do before(:each) do From 3168927e5184ff04d564fdd8757648f973927993 Mon Sep 17 00:00:00 2001 From: Hailee Kenney Date: Wed, 9 May 2012 10:36:10 -0700 Subject: [PATCH 0804/3753] (#7484) Domain Fact should handle TLD Before this commit, using hostname -f was opt-out. With this commit it has been changed to be opt-in, to prevent problems on certain operating systems like Solaris and HP-UX. --- lib/facter/domain.rb | 10 +++++++++- spec/unit/domain_spec.rb | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/facter/domain.rb b/lib/facter/domain.rb index 6aeda809de..306ad0d265 100644 --- a/lib/facter/domain.rb +++ b/lib/facter/domain.rb @@ -23,7 +23,15 @@ # Get the domain from various sources; the order of these # steps is important - hostname_command = (Facter.value(:kernel) =~ /SunOS/i) ? 'hostname' : 'hostname -f' + # In some OS 'hostname -f' will change the hostname to '-f' + # We know that Solaris and HP-UX exhibit this behavior + # On good OS, 'hostname -f' will return the FQDN which is preferable + # Due to dangerous behavior of 'hostname -f' on old OS, we will explicitly opt-in + # 'hostname -f' --hkenney May 9, 2012 + hostname_command = 'hostname' + can_do_hostname_f = Regexp.union /Linux/i, /FreeBSD/i, /Darwin/i + hostname_command = 'hostname -f' if Facter.value(:kernel) =~ can_do_hostname_f + if name = Facter::Util::Resolution.exec(hostname_command) \ and name =~ /.*?\.(.+$)/ diff --git a/spec/unit/domain_spec.rb b/spec/unit/domain_spec.rb index 695e6b771c..9e348026ab 100755 --- a/spec/unit/domain_spec.rb +++ b/spec/unit/domain_spec.rb @@ -8,6 +8,7 @@ :solaris => {:kernel => "SunOS", :hostname_command => "hostname"}, :darwin => {:kernel => "Darwin", :hostname_command => "hostname -f"}, :freebsd => {:kernel => "FreeBSD", :hostname_command => "hostname -f"}, + :hpux => {:kernel => "HP-UX", :hostname_command => "hostname"}, }.each do |key, nested_hash| describe "on #{key}" do From 3a84a0e4c4e495b2a18eb06f11ad4a03e4419952 Mon Sep 17 00:00:00 2001 From: Hailee Kenney Date: Thu, 10 May 2012 14:42:17 -0700 Subject: [PATCH 0805/3753] (#3226) Strip whitespace from fact Prior to this commit, Facter was not removing trailing or leading whitespace from facts. This change normalizes all facts by stripping whitespace. However, since in some custom facts trailing and leading whitespace may be essential, it is possible to opt out of stripping the whitespace on certain facts. --- lib/facter/util/resolution.rb | 6 ++- spec/unit/util/resolution_spec.rb | 88 +++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 1 deletion(-) diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index a5a2035760..4e8236d1c4 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -10,7 +10,7 @@ class Facter::Util::Resolution attr_accessor :interpreter, :code, :name, :timeout - attr_writer :value, :weight + attr_writer :value, :weight, :preserve_whitespace INTERPRETER = Facter::Util::Config.is_windows? ? "cmd.exe" : "/bin/sh" @@ -225,6 +225,10 @@ def value ms = (finishtime - starttime) * 1000 Facter.show_time "#{self.name}: #{"%.2f" % ms}ms" + unless @preserve_whitespace + result = result.strip if result + end + return nil if result == "" return result end diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index 388baacf8b..c5e563f80c 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -167,6 +167,94 @@ def handy_method() @resolve.value.should == "foo" end + describe "when dealing with whitespace" do + it "should by default strip whitespace" do + @resolve.setcode {' value '} + @resolve.value.should == 'value' + end + describe "when given a string" do + [true, false + ].each do |windows| + describe "#{ (windows) ? '' : 'not' } on Windows" do + before do + Facter::Util::Config.stubs(:is_windows?).returns(windows) + end + describe "stripping whitespace" do + before do + @resolve.preserve_whitespace = false + end + [{:name => 'leading', :result => ' value', :expect => 'value'}, + {:name => 'trailing', :result => 'value ', :expect => 'value'}, + {:name => 'internal', :result => 'val ue', :expect => 'val ue'}, + {:name => 'leading and trailing', :result => ' value ', :expect => 'value'}, + {:name => 'leading and internal', :result => ' val ue', :expect => 'val ue'}, + {:name => 'trailing and internal', :result => 'val ue ', :expect => 'val ue'} + ].each do |scenario| + it "should remove outer whitespace when whitespace is #{scenario[:name]}" do + @resolve.setcode "/bin/foo" + Facter::Util::Resolution.expects(:exec).once.with("/bin/foo").returns scenario[:result] + @resolve.value.should == scenario[:expect] + end + end + end + describe "not stripping whitespace" do + before do + @resolve.preserve_whitespace = true + end + [{:name => 'leading', :result => ' value', :expect => ' value'}, + {:name => 'trailing', :result => 'value ', :expect => 'value '}, + {:name => 'internal', :result => 'val ue', :expect => 'val ue'}, + {:name => 'leading and trailing', :result => ' value ', :expect => ' value '}, + {:name => 'leading and internal', :result => ' val ue', :expect => ' val ue'}, + {:name => 'trailing and internal', :result => 'val ue ', :expect => 'val ue '} + ].each do |scenario| + it "should not remove #{scenario[:name]} whitespace" do + @resolve.setcode "/bin/foo" + Facter::Util::Resolution.expects(:exec).once.with("/bin/foo").returns scenario[:result] + @resolve.value.should == scenario[:expect] + end + end + end + end + end + end + describe "when given a block" do + describe "stripping whitespace" do + before do + @resolve.preserve_whitespace = false + end + [{:name => 'leading', :result => ' value', :expect => 'value'}, + {:name => 'trailing', :result => 'value ', :expect => 'value'}, + {:name => 'internal', :result => 'val ue', :expect => 'val ue'}, + {:name => 'leading and trailing', :result => ' value ', :expect => 'value'}, + {:name => 'leading and internal', :result => ' val ue', :expect => 'val ue'}, + {:name => 'trailing and internal', :result => 'val ue ', :expect => 'val ue'} + ].each do |scenario| + it "should remove outer whitespace when whitespace is #{scenario[:name]}" do + @resolve.setcode {scenario[:result]} + @resolve.value.should == scenario[:expect] + end + end + end + describe "not stripping whitespace" do + before do + @resolve.preserve_whitespace = true + end + [{:name => 'leading', :result => ' value', :expect => ' value'}, + {:name => 'trailing', :result => 'value ', :expect => 'value '}, + {:name => 'internal', :result => 'val ue', :expect => 'val ue'}, + {:name => 'leading and trailing', :result => ' value ', :expect => ' value '}, + {:name => 'leading and internal', :result => ' val ue', :expect => ' val ue'}, + {:name => 'trailing and internal', :result => 'val ue ', :expect => 'val ue '} + ].each do |scenario| + it "should not remove #{scenario[:name]} whitespace" do + @resolve.setcode {scenario[:result]} + @resolve.value.should == scenario[:expect] + end + end + end + end + end describe "and setcode has not been called" do it "should return nil" do Facter::Util::Resolution.expects(:exec).with(nil, nil).never From 6e7d96738b020fdb7220716fded5c5f633b4864b Mon Sep 17 00:00:00 2001 From: Hailee Kenney Date: Thu, 10 May 2012 15:58:55 -0700 Subject: [PATCH 0806/3753] (#3226) Wire up fact preserve_whitespace DSL Make specs more readable. Make `preserve_whitespace` a DSL keyword. --- lib/facter/util/resolution.rb | 8 ++++-- spec/unit/util/resolution_spec.rb | 46 +++++++++++++++++++------------ 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 4e8236d1c4..5ef1e12db4 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -10,7 +10,7 @@ class Facter::Util::Resolution attr_accessor :interpreter, :code, :name, :timeout - attr_writer :value, :weight, :preserve_whitespace + attr_writer :value, :weight INTERPRETER = Facter::Util::Config.is_windows? ? "cmd.exe" : "/bin/sh" @@ -155,6 +155,10 @@ def limit @timeout end + def preserve_whitespace + @preserve_whitespace = true + end + # Set our code for returning a value. def setcode(string = nil, interp = nil, &block) Facter.warnonce "The interpreter parameter to 'setcode' is deprecated and will be removed in a future version." if interp @@ -226,7 +230,7 @@ def value Facter.show_time "#{self.name}: #{"%.2f" % ms}ms" unless @preserve_whitespace - result = result.strip if result + result.strip! if result && result.respond_to?(:strip!) end return nil if result == "" diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index c5e563f80c..e0616e1bb4 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -172,6 +172,7 @@ def handy_method() @resolve.setcode {' value '} @resolve.value.should == 'value' end + describe "when given a string" do [true, false ].each do |windows| @@ -179,10 +180,8 @@ def handy_method() before do Facter::Util::Config.stubs(:is_windows?).returns(windows) end - describe "stripping whitespace" do - before do - @resolve.preserve_whitespace = false - end + + describe "stripping whitespace" do [{:name => 'leading', :result => ' value', :expect => 'value'}, {:name => 'trailing', :result => 'value ', :expect => 'value'}, {:name => 'internal', :result => 'val ue', :expect => 'val ue'}, @@ -190,17 +189,21 @@ def handy_method() {:name => 'leading and internal', :result => ' val ue', :expect => 'val ue'}, {:name => 'trailing and internal', :result => 'val ue ', :expect => 'val ue'} ].each do |scenario| + it "should remove outer whitespace when whitespace is #{scenario[:name]}" do @resolve.setcode "/bin/foo" Facter::Util::Resolution.expects(:exec).once.with("/bin/foo").returns scenario[:result] @resolve.value.should == scenario[:expect] end + end end + describe "not stripping whitespace" do before do - @resolve.preserve_whitespace = true + @resolve.preserve_whitespace end + [{:name => 'leading', :result => ' value', :expect => ' value'}, {:name => 'trailing', :result => 'value ', :expect => 'value '}, {:name => 'internal', :result => 'val ue', :expect => 'val ue'}, @@ -208,21 +211,21 @@ def handy_method() {:name => 'leading and internal', :result => ' val ue', :expect => ' val ue'}, {:name => 'trailing and internal', :result => 'val ue ', :expect => 'val ue '} ].each do |scenario| + it "should not remove #{scenario[:name]} whitespace" do @resolve.setcode "/bin/foo" Facter::Util::Resolution.expects(:exec).once.with("/bin/foo").returns scenario[:result] @resolve.value.should == scenario[:expect] end + end end end end - end + end + describe "when given a block" do describe "stripping whitespace" do - before do - @resolve.preserve_whitespace = false - end [{:name => 'leading', :result => ' value', :expect => 'value'}, {:name => 'trailing', :result => 'value ', :expect => 'value'}, {:name => 'internal', :result => 'val ue', :expect => 'val ue'}, @@ -230,31 +233,38 @@ def handy_method() {:name => 'leading and internal', :result => ' val ue', :expect => 'val ue'}, {:name => 'trailing and internal', :result => 'val ue ', :expect => 'val ue'} ].each do |scenario| + it "should remove outer whitespace when whitespace is #{scenario[:name]}" do @resolve.setcode {scenario[:result]} @resolve.value.should == scenario[:expect] end + end end + describe "not stripping whitespace" do before do - @resolve.preserve_whitespace = true - end + @resolve.preserve_whitespace + end + [{:name => 'leading', :result => ' value', :expect => ' value'}, - {:name => 'trailing', :result => 'value ', :expect => 'value '}, - {:name => 'internal', :result => 'val ue', :expect => 'val ue'}, - {:name => 'leading and trailing', :result => ' value ', :expect => ' value '}, - {:name => 'leading and internal', :result => ' val ue', :expect => ' val ue'}, - {:name => 'trailing and internal', :result => 'val ue ', :expect => 'val ue '} - ].each do |scenario| + {:name => 'trailing', :result => 'value ', :expect => 'value '}, + {:name => 'internal', :result => 'val ue', :expect => 'val ue'}, + {:name => 'leading and trailing', :result => ' value ', :expect => ' value '}, + {:name => 'leading and internal', :result => ' val ue', :expect => ' val ue'}, + {:name => 'trailing and internal', :result => 'val ue ', :expect => 'val ue '} + ].each do |scenario| + it "should not remove #{scenario[:name]} whitespace" do @resolve.setcode {scenario[:result]} @resolve.value.should == scenario[:expect] end + end end end - end + end + describe "and setcode has not been called" do it "should return nil" do Facter::Util::Resolution.expects(:exec).with(nil, nil).never From ceefaf1467abf535e48492e3fad52565dbf64d33 Mon Sep 17 00:00:00 2001 From: Matthaus Litteken Date: Thu, 10 May 2012 17:01:21 -0700 Subject: [PATCH 0807/3753] Update lib/facter.rb, CHANGELOG, facter.spec for 1.6.9rc1 --- CHANGELOG | 11 +++++++++++ conf/redhat/facter.spec | 20 ++++++++++++-------- lib/facter.rb | 2 +- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 881d8fb5ba..fee466dd23 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,14 @@ +1.6.9rc1 +=== +b398bd8 (#14334) Fix dmidecode based facts on DragonFly BSD +6c46b2c (#14332) Correct stubbing on Ubuntu +753f3a4 Revert "(#12864) Windows: get primary DNS from registry" +ac51593 Wrap dmidecode/pciutils in ifarch block +fbaa8fe (#11511) Correct lsbrelease specfile filename +14eee2b (#12864) Windows: get primary DNS from registry +2842c96 Update rpm spec file +515fd65 (#11511) Split lsb facts into multiple files + 1.6.8 === b86fe4c (#12831) Add rspec tests to have_which method in Resolution diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index cec36fc753..4b4470ea91 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -2,15 +2,16 @@ Summary: Ruby module for collecting simple facts about a host operating system Name: facter -Version: 1.6.8 -Release: 1%{?dist} -#Release: 0.1rc1%{?dist} +Version: 1.6.9 +#Release: 1%{?dist} +Release: 0.1rc1%{?dist} License: Apache 2.0 Group: System Environment/Base URL: http://www.puppetlabs.com/puppet/related-projects/%{name} -#Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc1.tar.gz -Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}.tar.gz -#Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc1.tar.gz.asc +Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc1.tar.gz +#Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}.tar.gz +Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc1.tar.gz.asc +#Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}.tar.gz.asc BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) @@ -30,8 +31,8 @@ system. Some of the facts are preconfigured, such as the hostname and the operating system. Additional facts can be added through simple Ruby scripts %prep -%setup -q -n %{name}-%{version} -#%setup -q -n %{name}-%{version}rc1 +#%setup -q -n %{name}-%{version} +%setup -q -n %{name}-%{version}rc1 %build @@ -52,6 +53,9 @@ rm -rf %{buildroot} %changelog +* Thu May 10 2012 Matthaus Litteken - 1.6.9-0.1rc1 +- Update for 1.6.9rc1 + * Mon Apr 30 2012 Moses Mendoza - 1.6.8-1 - Update for 1.6.8, spec for arch-specific build, req ruby 1.8.5 diff --git a/lib/facter.rb b/lib/facter.rb index b197c75c90..9b0ddd5689 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -25,7 +25,7 @@ module Util; end include Comparable include Enumerable - FACTERVERSION = '1.6.8' + FACTERVERSION = '1.6.9' # = Facter # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. From 4a2d358ce6e390038e13d529b218c727ce9a45db Mon Sep 17 00:00:00 2001 From: Jeff Weiss Date: Fri, 11 May 2012 22:35:06 -0700 Subject: [PATCH 0808/3753] (#6955) Remove relative dirs from fact search path Prior to this commit, relative directories were permitted the search path for facts, which meant that both the user and the path in which facter was run could influence which facts were located. This situation could have led in unintended code being executed. This commit forces all paths (from $LOAD_PATH, ENV["FACTORLIB"] or Facter.search_path) to explicitly be absolute paths. --- lib/facter/util/loader.rb | 13 +++++-- spec/unit/util/loader_spec.rb | 66 +++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/lib/facter/util/loader.rb b/lib/facter/util/loader.rb index 4bdc82fca6..13266d87c8 100644 --- a/lib/facter/util/loader.rb +++ b/lib/facter/util/loader.rb @@ -1,10 +1,12 @@ require 'facter' +require 'pathname' # Load facts on demand. class Facter::Util::Loader def initialize @loaded = [] + @valid_path = {} end # Load all resolutions for a single fact. @@ -53,14 +55,21 @@ def search_path result = [] result += $LOAD_PATH.collect { |d| File.join(d, "facter") } if ENV.include?("FACTERLIB") - result += ENV["FACTERLIB"].split(":") + result += ENV["FACTERLIB"].split(File::PATH_SEPARATOR) end # This allows others to register additional paths we should search. result += Facter.search_path - result + result.select { |dir| valid_search_path? dir } end + + def valid_search_path?(path) + return @valid_path[path] unless @valid_path[path].nil? + + return @valid_path[path] = Pathname.new(path).absolute? + end + private :valid_search_path? private diff --git a/spec/unit/util/loader_spec.rb b/spec/unit/util/loader_spec.rb index 765348450a..2848742136 100755 --- a/spec/unit/util/loader_spec.rb +++ b/spec/unit/util/loader_spec.rb @@ -33,6 +33,62 @@ def load_file(file) Facter::Util::Loader.new.should respond_to(:search_path) end + describe "#valid_seach_path?" do + before do + @loader = Facter::Util::Loader.new + @settings = mock 'settings' + @settings.stubs(:value).returns "/eh" + end + + it "should cache the result of a previous check" do + Pathname.any_instance.expects(:absolute?).returns(true).once + + # we explicitly want two calls here to check that we get + # the second from the cache + @loader.should be_valid_search_path "/foo" + @loader.should be_valid_search_path "/foo" + end + + [ + '.', + '..', + '...', + '.foo', + '../foo', + 'foo', + 'foo/bar', + 'foo/../bar', + ' ', + ' /', + ' \/', + ].each do |dir| + + it "should be false for relative path #{dir}" do + @loader.should_not be_valid_search_path dir + end + + end + + [ + '/.', + '/..', + '/...', + '/.foo', + '/../foo', + '/foo', + '/foo/bar', + '/foo/../bar', + '/ ', + '/ /..', + ].each do |dir| + + it "should be true for absolute path #{dir}" do + @loader.should be_valid_search_path dir + end + + end + end + describe "when determining the search path" do before do @loader = Facter::Util::Loader.new @@ -42,12 +98,22 @@ def load_file(file) it "should include the facter subdirectory of all paths in ruby LOAD_PATH" do dirs = $LOAD_PATH.collect { |d| File.join(d, "facter") } + @loader.stubs(:valid_search_path?).returns(true) paths = @loader.search_path dirs.each do |dir| paths.should be_include(dir) end end + + it "should exclude invalid search paths" do + dirs = $LOAD_PATH.collect { |d| File.join(d, "facter") } + @loader.stubs(:valid_search_path?).returns(false) + paths = @loader.search_path + dirs.each do |dir| + paths.should_not be_include(dir) + end + end it "should include all search paths registered with Facter" do Facter.expects(:search_path).returns %w{/one /two} From 9810595b04e68014ed5b587b01b75c712cbba2f5 Mon Sep 17 00:00:00 2001 From: Jeff Weiss Date: Fri, 11 May 2012 22:47:19 -0700 Subject: [PATCH 0809/3753] (#11466) Remove deprecated memorytotal fact The memorytotal fact was previously deprecated in preference to the memorysize fact. This commit now removes memorytotal. This commit is a breaking change that will force any remaining memorytotal fact users to switch to the preferred memorysize fact. --- lib/facter/memory.rb | 13 +------------ spec/unit/memory_spec.rb | 9 +-------- 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb index 2bb52ad0bf..9e97795437 100644 --- a/lib/facter/memory.rb +++ b/lib/facter/memory.rb @@ -261,15 +261,4 @@ memtotal = Facter::Util::Resolution.exec("sysctl -n hw.physmem") Facter::Memory.scale_number(memtotal.to_f,"") end -end - -# http://projects.puppetlabs.com/issues/11436 -# -# Unifying naming for the amount of physical memory in a given host. -# This fact is DEPRECATED and will be removed in Facter 2.0 per -# http://projects.puppetlabs.com/issues/11466 -Facter.add("MemoryTotal") do - setcode do - Facter.value("memorysize") - end -end +end \ No newline at end of file diff --git a/spec/unit/memory_spec.rb b/spec/unit/memory_spec.rb index 5e8523932f..c40cdf3b22 100755 --- a/spec/unit/memory_spec.rb +++ b/spec/unit/memory_spec.rb @@ -142,14 +142,7 @@ computer.stubs(:TotalPhysicalMemory).returns("4193837056") Facter::Util::WMI.stubs(:execquery).returns([computer]) - Facter.fact(:MemoryTotal).value.should == '3.91 GB' + Facter.fact(:MemorySize).value.should == '3.91 GB' end end - - it "should use the memorysize fact for the memorytotal fact" do - Facter.fact("memorysize").expects(:value).once.returns "yay" - Facter::Util::Resolution.expects(:exec).never - Facter::Memory.expects(:meminfo_number).never - Facter.fact("memorytotal").value.should == "yay" - end end From 9f01fe72b3cc13ffa9c8f2996056b51b2ce50e92 Mon Sep 17 00:00:00 2001 From: Jeff Weiss Date: Fri, 11 May 2012 23:16:11 -0700 Subject: [PATCH 0810/3753] (#12147) Remove Darwin 6-specific iphostnumber fact The iphostnumber fact only resolves on version 6 of the Darwin kernel. This commit removes a fact that only exists on an unsupported platform. (aka Jaguar). The official minimum supported version is 8 (aka Tiger). Darwin 6 was replaced with Panther in October 2003. --- lib/facter/iphostnumber.rb | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100644 lib/facter/iphostnumber.rb diff --git a/lib/facter/iphostnumber.rb b/lib/facter/iphostnumber.rb deleted file mode 100644 index 2d22017c28..0000000000 --- a/lib/facter/iphostnumber.rb +++ /dev/null @@ -1,29 +0,0 @@ -# Fact: iphostnumber -# -# Purpose: On selected versions of Darwin, returns the host's IP address. -# -# Resolution: -# Uses either the scutil program to get the localhost name, or parses output -# of ifconfig for a MAC address. -# -# Caveats: -# - -Facter.add(:iphostnumber) do - confine :kernel => :darwin, :kernelrelease => "R6" - setcode do - %x{/usr/sbin/scutil --get LocalHostName} - end -end -Facter.add(:iphostnumber) do - confine :kernel => :darwin, :kernelrelease => "R6" - setcode do - ether = nil - output = %x{/sbin/ifconfig} - - output =~ /HWaddr (\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ - ether = $1 - - ether - end -end From cb429df7fc4028e2e48dda17b9dfc92580561ac5 Mon Sep 17 00:00:00 2001 From: Daniel Pittman Date: Fri, 11 May 2012 23:32:16 -0700 Subject: [PATCH 0811/3753] Partial revert of CFPropertyList migration. While the new CFPropertyList implementation is great, the older plist implementation is used from a bunch of places in Puppet - including some package providers and other unexpected sources. Since the two projects share this code so intimately, we risk breaking more than we care about if we just eliminate this code. This restores the previous version of those files, allowing dependent code to gradually be migrated away to the newer implementation over time. Signed-off-by: Daniel Pittman --- lib/facter/util/plist.rb | 24 +++ lib/facter/util/plist/generator.rb | 228 +++++++++++++++++++++++++++++ lib/facter/util/plist/parser.rb | 226 ++++++++++++++++++++++++++++ 3 files changed, 478 insertions(+) create mode 100644 lib/facter/util/plist.rb create mode 100644 lib/facter/util/plist/generator.rb create mode 100644 lib/facter/util/plist/parser.rb diff --git a/lib/facter/util/plist.rb b/lib/facter/util/plist.rb new file mode 100644 index 0000000000..32e9e2bf7f --- /dev/null +++ b/lib/facter/util/plist.rb @@ -0,0 +1,24 @@ +#-- +############################################################## +# Copyright 2006, Ben Bleything and # +# Patrick May # +# # +# Distributed under the MIT license. # +############################################################## +#++ +# = Plist +# +# This is the main file for plist. Everything interesting happens in Plist and Plist::Emit. + +require 'base64' +require 'cgi' +require 'stringio' + +require 'facter/util/plist/generator' +require 'facter/util/plist/parser' + +module Plist + VERSION = '3.0.0' +end + +# $Id: plist.rb 1781 2006-10-16 01:01:35Z luke $ diff --git a/lib/facter/util/plist/generator.rb b/lib/facter/util/plist/generator.rb new file mode 100644 index 0000000000..8f9ea3a47f --- /dev/null +++ b/lib/facter/util/plist/generator.rb @@ -0,0 +1,228 @@ +#--########################################################### +# Copyright 2006, Ben Bleything and # +# Patrick May # +# # +# Distributed under the MIT license. # +############################################################## +#++ +# See Plist::Emit. +module Plist + # === Create a plist + # You can dump an object to a plist in one of two ways: + # + # * Plist::Emit.dump(obj) + # * obj.to_plist + # * This requires that you mixin the Plist::Emit module, which is already done for +Array+ and +Hash+. + # + # The following Ruby classes are converted into native plist types: + # Array, Bignum, Date, DateTime, Fixnum, Float, Hash, Integer, String, Symbol, Time, true, false + # * +Array+ and +Hash+ are both recursive; their elements will be converted into plist nodes inside the and containers (respectively). + # * +IO+ (and its descendants) and +StringIO+ objects are read from and their contents placed in a element. + # * User classes may implement +to_plist_node+ to dictate how they should be serialized; otherwise the object will be passed to Marshal.dump and the result placed in a element. + # + # For detailed usage instructions, refer to USAGE[link:files/docs/USAGE.html] and the methods documented below. + module Emit + # Helper method for injecting into classes. Calls Plist::Emit.dump with +self+. + def to_plist(envelope = true) + return Plist::Emit.dump(self, envelope) + end + + # Helper method for injecting into classes. Calls Plist::Emit.save_plist with +self+. + def save_plist(filename) + Plist::Emit.save_plist(self, filename) + end + + # The following Ruby classes are converted into native plist types: + # Array, Bignum, Date, DateTime, Fixnum, Float, Hash, Integer, String, Symbol, Time + # + # Write us (via RubyForge) if you think another class can be coerced safely into one of the expected plist classes. + # + # +IO+ and +StringIO+ objects are encoded and placed in elements; other objects are Marshal.dump'ed unless they implement +to_plist_node+. + # + # The +envelope+ parameters dictates whether or not the resultant plist fragment is wrapped in the normal XML/plist header and footer. Set it to false if you only want the fragment. + def self.dump(obj, envelope = true) + output = plist_node(obj) + + output = wrap(output) if envelope + + return output + end + + # Writes the serialized object's plist to the specified filename. + def self.save_plist(obj, filename) + File.open(filename, 'wb') do |f| + f.write(obj.to_plist) + end + end + + private + def self.plist_node(element) + output = '' + + if element.respond_to? :to_plist_node + output << element.to_plist_node + else + case element + when Array + if element.empty? + output << "\n" + else + output << tag('array') { + element.collect {|e| plist_node(e)} + } + end + when Hash + if element.empty? + output << "\n" + else + inner_tags = [] + + element.keys.sort.each do |k| + v = element[k] + inner_tags << tag('key', CGI::escapeHTML(k.to_s)) + inner_tags << plist_node(v) + end + + output << tag('dict') { + inner_tags + } + end + when true, false + output << "<#{element}/>\n" + when Time + output << tag('date', element.utc.strftime('%Y-%m-%dT%H:%M:%SZ')) + when Date # also catches DateTime + output << tag('date', element.strftime('%Y-%m-%dT%H:%M:%SZ')) + when String, Symbol, Fixnum, Bignum, Integer, Float + output << tag(element_type(element), CGI::escapeHTML(element.to_s)) + when IO, StringIO + element.rewind + contents = element.read + # note that apple plists are wrapped at a different length then + # what ruby's base64 wraps by default. + # I used #encode64 instead of #b64encode (which allows a length arg) + # because b64encode is b0rked and ignores the length arg. + data = "\n" + Base64::encode64(contents).gsub(/\s+/, '').scan(/.{1,68}/o) { data << $& << "\n" } + output << tag('data', data) + else + output << comment( 'The element below contains a Ruby object which has been serialized with Marshal.dump.' ) + data = "\n" + Base64::encode64(Marshal.dump(element)).gsub(/\s+/, '').scan(/.{1,68}/o) { data << $& << "\n" } + output << tag('data', data ) + end + end + + return output + end + + def self.comment(content) + return "\n" + end + + def self.tag(type, contents = '', &block) + out = nil + + if block_given? + out = IndentedString.new + out << "<#{type}>" + out.raise_indent + + out << block.call + + out.lower_indent + out << "" + else + out = "<#{type}>#{contents.to_s}\n" + end + + return out.to_s + end + + def self.wrap(contents) + output = '' + + output << '' + "\n" + output << '' + "\n" + output << '' + "\n" + + output << contents + + output << '' + "\n" + + return output + end + + def self.element_type(item) + return case item + when String, Symbol; 'string' + when Fixnum, Bignum, Integer; 'integer' + when Float; 'real' + else + raise "Don't know about this data type... something must be wrong!" + end + end + + private + + class IndentedString #:nodoc: + attr_accessor :indent_string + + @@indent_level = 0 + + def initialize(str = "\t") + @indent_string = str + @contents = '' + end + + def to_s + return @contents + end + + def raise_indent + @@indent_level += 1 + end + + def lower_indent + @@indent_level -= 1 if @@indent_level > 0 + end + + def <<(val) + if val.is_a? Array + val.each do |f| + self << f + end + else + # if it's already indented, don't bother indenting further + unless val =~ /\A#{@indent_string}/ + indent = @indent_string * @@indent_level + + @contents << val.gsub(/^/, indent) + else + @contents << val + end + + # it already has a newline, don't add another + @contents << "\n" unless val =~ /\n$/ + end + end + end + end +end + +# we need to add this so sorting hash keys works properly +class Symbol #:nodoc: + def <=> (other) + self.to_s <=> other.to_s + end +end + +class Array #:nodoc: + include Plist::Emit +end + +class Hash #:nodoc: + include Plist::Emit +end + +# $Id: generator.rb 1781 2006-10-16 01:01:35Z luke $ diff --git a/lib/facter/util/plist/parser.rb b/lib/facter/util/plist/parser.rb new file mode 100644 index 0000000000..ffbb7ba69a --- /dev/null +++ b/lib/facter/util/plist/parser.rb @@ -0,0 +1,226 @@ +#--########################################################### +# Copyright 2006, Ben Bleything and # +# Patrick May # +# # +# Distributed under the MIT license. # +############################################################## +#++ +# Plist parses Mac OS X xml property list files into ruby data structures. +# +# === Load a plist file +# This is the main point of the library: +# +# r = Plist::parse_xml( filename_or_xml ) +module Plist +# Note that I don't use these two elements much: +# +# + Date elements are returned as DateTime objects. +# + Data elements are implemented as Tempfiles +# +# Plist::parse_xml will blow up if it encounters a data element. +# If you encounter such an error, or if you have a Date element which +# can't be parsed into a Time object, please send your plist file to +# plist@hexane.org so that I can implement the proper support. + def Plist::parse_xml( filename_or_xml ) + listener = Listener.new + #parser = REXML::Parsers::StreamParser.new(File.new(filename), listener) + parser = StreamParser.new(filename_or_xml, listener) + parser.parse + listener.result + end + + class Listener + #include REXML::StreamListener + + attr_accessor :result, :open + + def initialize + @result = nil + @open = Array.new + end + + + def tag_start(name, attributes) + @open.push PTag::mappings[name].new + end + + def text( contents ) + @open.last.text = contents if @open.last + end + + def tag_end(name) + last = @open.pop + if @open.empty? + @result = last.to_ruby + else + @open.last.children.push last + end + end + end + + class StreamParser + def initialize( filename_or_xml, listener ) + @filename_or_xml = filename_or_xml + @listener = listener + end + + TEXT = /([^<]+)/ + XMLDECL_PATTERN = /<\?xml\s+(.*?)\?>*/um + DOCTYPE_PATTERN = /\s*)/um + COMMENT_START = /\A/um + + def parse + plist_tags = PTag::mappings.keys.join('|') + start_tag = /<(#{plist_tags})([^>]*)>/i + end_tag = /<\/(#{plist_tags})[^>]*>/i + + require 'strscan' + + contents = ( + if (File.exists? @filename_or_xml) + File.open(@filename_or_xml) {|f| f.read} + else + @filename_or_xml + end + ) + + @scanner = StringScanner.new( contents ) + until @scanner.eos? + if @scanner.scan(COMMENT_START) + @scanner.scan(COMMENT_END) + elsif @scanner.scan(XMLDECL_PATTERN) + elsif @scanner.scan(DOCTYPE_PATTERN) + elsif @scanner.scan(start_tag) + @listener.tag_start(@scanner[1], nil) + if (@scanner[2] =~ /\/$/) + @listener.tag_end(@scanner[1]) + end + elsif @scanner.scan(TEXT) + @listener.text(@scanner[1]) + elsif @scanner.scan(end_tag) + @listener.tag_end(@scanner[1]) + else + raise "Unimplemented element" + end + end + end + end + + class PTag + @@mappings = { } + def PTag::mappings + @@mappings + end + + def PTag::inherited( sub_class ) + key = sub_class.to_s.downcase + key.gsub!(/^plist::/, '' ) + key.gsub!(/^p/, '') unless key == "plist" + + @@mappings[key] = sub_class + end + + attr_accessor :text, :children + def initialize + @children = Array.new + end + + def to_ruby + raise "Unimplemented: " + self.class.to_s + "#to_ruby on #{self.inspect}" + end + end + + class PList < PTag + def to_ruby + children.first.to_ruby if children.first + end + end + + class PDict < PTag + def to_ruby + dict = Hash.new + key = nil + + children.each do |c| + if key.nil? + key = c.to_ruby + else + dict[key] = c.to_ruby + key = nil + end + end + + dict + end + end + + class PKey < PTag + def to_ruby + CGI::unescapeHTML(text || '') + end + end + + class PString < PTag + def to_ruby + CGI::unescapeHTML(text || '') + end + end + + class PArray < PTag + def to_ruby + children.collect do |c| + c.to_ruby + end + end + end + + class PInteger < PTag + def to_ruby + text.to_i + end + end + + class PTrue < PTag + def to_ruby + true + end + end + + class PFalse < PTag + def to_ruby + false + end + end + + class PReal < PTag + def to_ruby + text.to_f + end + end + + require 'date' + class PDate < PTag + def to_ruby + DateTime.parse(text) + end + end + + require 'base64' + class PData < PTag + def to_ruby + data = Base64.decode64(text.gsub(/\s+/, '')) + + begin + return Marshal.load(data) + rescue Exception => e + io = StringIO.new + io.write data + io.rewind + return io + end + end + end +end + +# $Id: parser.rb 1781 2006-10-16 01:01:35Z luke $ From c13a51735c0c05dd9c617626fa8c90c0f9014a36 Mon Sep 17 00:00:00 2001 From: Hailee Kenney Date: Mon, 14 May 2012 10:52:19 -0700 Subject: [PATCH 0812/3753] (maint) Fix broken Windows tests One of the test was not using the correct path seperator on Windows, so the : was changed to File::PATH_SEPARATOR, so the tests actually test the right behavior on each platform. Remove the test to see if " " is an absolute directory. It is relative on Unix but seems to be absolute on Windows (both in 1.8.7 and 1.9.3). Considering this platform discrepency and the fact that there is not valid use case for the " " directory, the test is not nessecary. --- spec/unit/util/loader_spec.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/spec/unit/util/loader_spec.rb b/spec/unit/util/loader_spec.rb index 2848742136..9bc61543c9 100755 --- a/spec/unit/util/loader_spec.rb +++ b/spec/unit/util/loader_spec.rb @@ -49,6 +49,12 @@ def load_file(file) @loader.should be_valid_search_path "/foo" end + # Used to have test for " " as a directory since that should + # be a relative directory, but on Windows in both 1.8.7 and + # 1.9.3 it is an absolute directory (WTF Windows). Considering + # we didn't have a valid use case for a " " directory, the + # test was removed. + [ '.', '..', @@ -58,7 +64,6 @@ def load_file(file) 'foo', 'foo/bar', 'foo/../bar', - ' ', ' /', ' \/', ].each do |dir| @@ -124,7 +129,7 @@ def load_file(file) describe "and the FACTERLIB environment variable is set" do it "should include all paths in FACTERLIB" do - Facter::Util::Resolution.with_env "FACTERLIB" => "/one/path:/two/path" do + Facter::Util::Resolution.with_env "FACTERLIB" => "/one/path#{File::PATH_SEPARATOR}/two/path" do paths = @loader.search_path %w{/one/path /two/path}.each do |dir| paths.should be_include(dir) From fa38c6083b78500af6c93eb4216bc5bec5f267f3 Mon Sep 17 00:00:00 2001 From: Hailee Kenney Date: Mon, 14 May 2012 14:10:02 -0700 Subject: [PATCH 0813/3753] (#14469) Strip whitespace from frozen strings In 1.9.3, strip! did not remove whitespace from frozen strings. Now using strip instead, this deals with frozen strings too, by making a copy of them. --- lib/facter/util/resolution.rb | 2 +- spec/unit/util/resolution_spec.rb | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 5ef1e12db4..63f7ea9a3a 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -230,7 +230,7 @@ def value Facter.show_time "#{self.name}: #{"%.2f" % ms}ms" unless @preserve_whitespace - result.strip! if result && result.respond_to?(:strip!) + result = result.strip if result && result.respond_to?(:strip) end return nil if result == "" diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index e0616e1bb4..553c1d854a 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -172,6 +172,13 @@ def handy_method() @resolve.setcode {' value '} @resolve.value.should == 'value' end + + it "should strip whitespace from frozen strings" do + result = ' val ue ' + result.freeze + @resolve.setcode{result} + @resolve.value.should == 'val ue' + end describe "when given a string" do [true, false From e9326d7fff2b683a19bcffdb13c04f52de412516 Mon Sep 17 00:00:00 2001 From: Hailee Kenney Date: Mon, 14 May 2012 13:37:25 -0700 Subject: [PATCH 0814/3753] (#14467) Warn when removing relative paths Due to changes made to Facter in commit 634f2f6a57b461926afcb2e07dcfe4ed659f5b0c, Facter no longer looks for facts in relative directories for security reasons. This change could result in users not getting all of their facts. This commit alerts users if a relative directory has been excluded from the search path. Due to feedback on this pull request on IRC, change message to be debug instead of warn. --- lib/facter.rb | 9 +++++++++ lib/facter/util/loader.rb | 6 +++++- spec/unit/util/loader_spec.rb | 12 +++++++++++- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/lib/facter.rb b/lib/facter.rb index 9322fc0dff..ce9ce0a948 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -44,6 +44,7 @@ module Util; end @@debug = 0 @@timing = 0 @@messages = {} + @@debug_messages = {} # module methods @@ -69,6 +70,14 @@ def self.debug(string) end end + # Debug once. + def self.debugonce(msg) + if msg and not msg.empty? and @@debug_messages[msg].nil? + @@debug_messages[msg] = true + debug(msg) + end + end + def self.debugging? @@debug != 0 end diff --git a/lib/facter/util/loader.rb b/lib/facter/util/loader.rb index 13266d87c8..99fe798817 100644 --- a/lib/facter/util/loader.rb +++ b/lib/facter/util/loader.rb @@ -61,7 +61,11 @@ def search_path # This allows others to register additional paths we should search. result += Facter.search_path - result.select { |dir| valid_search_path? dir } + result.select do |dir| + good = valid_search_path? dir + Facter.debugonce("Relative directory #{dir} removed from search path.") unless good + good + end end def valid_search_path?(path) diff --git a/spec/unit/util/loader_spec.rb b/spec/unit/util/loader_spec.rb index 9bc61543c9..75975672de 100755 --- a/spec/unit/util/loader_spec.rb +++ b/spec/unit/util/loader_spec.rb @@ -110,7 +110,17 @@ def load_file(file) paths.should be_include(dir) end end - + + it "should warn the user when an invalid search path has been excluded" do + dirs = $LOAD_PATH.collect { |d| File.join(d, "facter") } + @loader.stubs(:valid_search_path?).returns(false) + dirs.each do |dir| + Facter.expects(:debugonce).with("Relative directory #{dir} removed from search path.").once + end + paths = @loader.search_path + end + + it "should exclude invalid search paths" do dirs = $LOAD_PATH.collect { |d| File.join(d, "facter") } @loader.stubs(:valid_search_path?).returns(false) From ffe7129a6da1b6d9ab4eaac3c923fa1aa24fd08f Mon Sep 17 00:00:00 2001 From: Hailee Kenney Date: Wed, 9 May 2012 15:40:19 -0700 Subject: [PATCH 0815/3753] (#3909) Strip trailing dots from domain fact Prior to this commit, the domain could end in '.' or could not. Now, normalizing to no trailing '.' in domain fact. This also works properly for top level domains. --- lib/facter/domain.rb | 14 ++++--- spec/unit/domain_spec.rb | 91 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 97 insertions(+), 8 deletions(-) diff --git a/lib/facter/domain.rb b/lib/facter/domain.rb index 306ad0d265..079417018e 100644 --- a/lib/facter/domain.rb +++ b/lib/facter/domain.rb @@ -36,11 +36,11 @@ if name = Facter::Util::Resolution.exec(hostname_command) \ and name =~ /.*?\.(.+$)/ - $1 + return_value = $1 elsif domain = Facter::Util::Resolution.exec('dnsdomainname') \ - and domain =~ /.+\..+/ + and domain =~ /.+/ - domain + return_value = domain elsif FileTest.exists?("/etc/resolv.conf") domain = nil search = nil @@ -53,9 +53,11 @@ end } } - next domain if domain - next search if search + return_value ||= domain + return_value ||= search end + return_value = '' if return_value.nil? + return_value.gsub(/\.$/, '') end end @@ -73,6 +75,6 @@ break } end - domain + domain.gsub(/\.$/, '') end end diff --git a/spec/unit/domain_spec.rb b/spec/unit/domain_spec.rb index 9e348026ab..754718ed30 100755 --- a/spec/unit/domain_spec.rb +++ b/spec/unit/domain_spec.rb @@ -91,8 +91,21 @@ @mock_file.expects(:each).multiple_yields(*lines) Facter.fact(:domain).value.should == 'example.org' end - - # Test permutations of domain and search + + # Test permutations of domain and search, where 'domain' can be a value of + # the search keyword and the domain keyword + # and also where 'search' can be a value of the search keyword and the + # domain keyword + # For example, /etc/resolv.conf may look like: + # domain domain + # or + # search search + # or + # domain search + # + # + # Why someone would have their machines named 'www.domain' or 'www.search', I + # don't know, but we'll at least handle it properly [ ["domain domain", "domain"], ["domain search", "search"], @@ -123,23 +136,28 @@ Facter.fact(:kernel).stubs(:value).returns("windows") require 'facter/util/registry' end + describe "with primary dns suffix" do before(:each) do Facter::Util::Registry.stubs(:hklm_read).returns('baz.com') end + it "should get the primary dns suffix" do Facter.fact(:domain).value.should == 'baz.com' end + it "should not execute the wmi query" do require 'facter/util/wmi' Facter::Util::WMI.expects(:execquery).never Facter.fact(:domain).value end end + describe "without primary dns suffix" do before(:each) do Facter::Util::Registry.stubs(:hklm_read).returns('') end + it "should use the DNSDomain for the first nic where ip is enabled" do nic = stubs 'nic' nic.stubs(:DNSDomain).returns("foo.com") @@ -154,4 +172,73 @@ end end end + + describe "with trailing dots" do + describe "on Windows" do + before do + Facter.fact(:kernel).stubs(:value).returns("windows") + require 'facter/util/registry' + require 'facter/util/wmi' + end + + [{:registry => 'testdomain.', :wmi => '', :expect => 'testdomain'}, + {:registry => '', :wmi => 'testdomain.', :expect => 'testdomain'}, + ].each do |scenario| + + describe "scenarios" do + before(:each) do + Facter::Util::Registry.stubs(:hklm_read).returns(scenario[:registry]) + nic = stubs 'nic' + nic.stubs(:DNSDomain).returns(scenario[:wmi]) + Facter::Util::WMI.stubs(:execquery).with("select DNSDomain from Win32_NetworkAdapterConfiguration where IPEnabled = True").returns([nic]) + end + + it "should return #{scenario[:expect]}" do + Facter.fact(:domain).value.should == scenario[:expect] + end + + it "should remove trailing dots" do + Facter.fact(:domain).value.should_not =~ /\.$/ + end + end + end + end + + describe "on everything else" do + before do + Facter.fact(:kernel).stubs(:value).returns("Linux") + FileTest.stubs(:exists?).with("/etc/resolv.conf").returns(true) + end + + [{:hostname => 'host.testdomain.', :dnsdomainname => '', :resolve_domain => '', :resolve_search => '', :expect => 'testdomain'}, + {:hostname => '', :dnsdomainname => 'testdomain.', :resolve_domain => '', :resolve_search => '', :expect => 'testdomain'}, + {:hostname => '', :dnsdomainname => '', :resolve_domain => 'testdomain.', :resolve_search => '', :expect => 'testdomain'}, + {:hostname => '', :dnsdomainname => '', :resolve_domain => '', :resolve_search => 'testdomain.', :expect => 'testdomain'}, + {:hostname => '', :dnsdomainname => '', :resolve_domain => '', :resolve_search => '', :expect => nil} + ].each do |scenario| + + describe "scenarios" do + before(:each) do + Facter::Util::Resolution.stubs(:exec).with("hostname -f").returns(scenario[:hostname]) + Facter::Util::Resolution.stubs(:exec).with("dnsdomainname").returns(scenario[:dnsdomainname]) + @mock_file = mock() + File.stubs(:open).with("/etc/resolv.conf").yields(@mock_file) + lines = [ + "search #{scenario[:resolve_search]}", + "domain #{scenario[:resolve_domain]}", + ] + @mock_file.stubs(:each).multiple_yields(*lines) + end + + it "should remove trailing dots" do + Facter.fact(:domain).value.should_not =~ /\.$/ + end + + it "should return #{scenario[:expect]}" do + Facter.fact(:domain).value.should == scenario[:expect] + end + end + end + end + end end From 02ff878f68b773b433c306c17541fcbac3cc70f3 Mon Sep 17 00:00:00 2001 From: Daniel Pittman Date: Tue, 15 May 2012 14:27:55 -0700 Subject: [PATCH 0816/3753] Bump Facter version to 2.0.0 for the release. Signed-off-by: Daniel Pittman --- lib/facter.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/facter.rb b/lib/facter.rb index ce9ce0a948..1787309fac 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -25,7 +25,8 @@ module Util; end include Comparable include Enumerable - FACTERVERSION = '1.6.9' + FACTERVERSION = '2.0.0' + # = Facter # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. From 1dd50f49aa56f766eec343dfbfd960fba1d7b599 Mon Sep 17 00:00:00 2001 From: Patrick Carlisle Date: Tue, 15 May 2012 16:06:44 -0700 Subject: [PATCH 0817/3753] Use absolute path for $LOAD_PATH in installer As of Facter 2.0, we don't load facts from relative paths in the $LOAD_PATH. This broke the installer. --- install.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install.rb b/install.rb index 75030635e0..485a0dfee5 100755 --- a/install.rb +++ b/install.rb @@ -65,7 +65,7 @@ $haveman = false end -$LOAD_PATH << File.join(File.dirname(__FILE__), 'lib') +$LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), 'lib')) require 'facter' @operatingsystem = Facter[:operatingsystem].value From 1368bd6558659a9742d067d03a95fccb9f6717fe Mon Sep 17 00:00:00 2001 From: Matthaus Litteken Date: Tue, 15 May 2012 15:03:47 -0700 Subject: [PATCH 0818/3753] Updating CHANGELOG, conf/redhat/facter.spec for 2.0.0rc1 release. --- CHANGELOG | 54 +++++++++++++++++++++++++++++++++++++++++ conf/redhat/facter.spec | 5 +++- 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index fee466dd23..56dbe8dc5b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,57 @@ +2.0.0rc1 +=== +1dd50f4 Use absolute path for $LOAD_PATH in installer +02ff878 Bump Facter version to 2.0.0 for the release. +ffe7129 (#3909) Strip trailing dots from domain fact +e9326d7 (#14467) Warn when removing relative paths +fa38c60 (#14469) Strip whitespace from frozen strings +c13a517 (maint) Fix broken Windows tests +cb429df Partial revert of CFPropertyList migration. +9f01fe7 (#12147) Remove Darwin 6-specific iphostnumber fact +9810595 (#11466) Remove deprecated memorytotal fact +4a2d358 (#6955) Remove relative dirs from fact search path +6e7d967 (#3226) Wire up fact preserve_whitespace DSL +3a84a0e (#3226) Strip whitespace from fact +3168927 (#7484) Domain Fact should handle TLD +6b9e2e5 (#7484) Domain fact should handle only TLD +5aa2a6f (#12864) Windows: get primary DNS from registry +635ccc4 Namespace CFPropertyList under Puppet::Util +af682cc [#11299] Replace facter/util/plist with cfpropertylist +2e7a108 (#12790) Raise an exception if recursion is detected +9ff4453 Removed exclusive threading +4581b17 (maint) removing trailing whitespace +c80de1c (#11969) Add zfs zpool version facts. +5273842 (#6682) Add Solaris ldom facts and update virtual detection. +f77584f (#12311) use 'ensure' to restore env vars in Resolution.with_env +0010a65 (#10232) Tests for VirtualBox detection via sysfs +40efab4 fix tests for virtual detection +976b7af fix xen0/xenu detection: https://projects.puppetlabs.com/issues/10625 +2a86219 (#11660) Adds feature SSHFP (#11659) Adds ssh spec +4595c48 (#12012) removed extraneous nils +9c2afdb (#12012) Fix bug with overriding LANG environment variable +2fc1b0c (#9574) Add filesystem fact & tests +c93922c (#12012) Fix ENV['LANG'] spec tests on Windows +d8be0a6 (#12012) Fix ruby 1.8.5 incompatibility in new spec test +66cbfac (#12012) Add spec unit tests for env/LANG changes +810c465 (#12012) Remove global override of LANG environment variable +26918b3 (#12012) Move "with_env" utility method from test code into lib code +f47c592 (#1424) Add Solaris zonename fact. +2bdacfb Revert "(#11660) Adds feature SSHFP" +50f6da6 (#11660) Adds feature SSHFP +6c8683a (#11660) Adds feature SSHFP (#11659) Adds ssh spec +64fa8ea (maint) Fix requires for newer rspec revisions so we don't break build +2eb4ede (#9708) Confine facts by kernel not operating system and remove confine for hardwareisa +30e068a (#11082) Add negative tests for operatingsystemrelease +7713d95 (#11082) Add fact operatingsystemrelease for Solaris +844b67b (#10964) Adding the ability to directly set values +a9140d5 (#6617) Deprecate DESTDIR environment variable for installer +dd704c5 (#10238) Adds support for retrieving information about block devices in Linux, with tests; updated to include sizes and be more flexible for future additions +e0cb1ae Revert "Merge branch 'ticket/10251' of git://github.com/jgrocho/facter" +ce67c98 (#10251) Creating RC tarballs should be handled by rake. +64770d0 Try to get VirtualBox info without dmidecode +9df78a1 (#9929) Add support for Mageia to operatingsystem.rb and operatingsystemrelease.rb +51cb8e2 Cleaned up Arch Linux detection routine + 1.6.9rc1 === b398bd8 (#14334) Fix dmidecode based facts on DragonFly BSD diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index 4b4470ea91..a86b68b190 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -2,7 +2,7 @@ Summary: Ruby module for collecting simple facts about a host operating system Name: facter -Version: 1.6.9 +Version: 2.0.0 #Release: 1%{?dist} Release: 0.1rc1%{?dist} License: Apache 2.0 @@ -53,6 +53,9 @@ rm -rf %{buildroot} %changelog +* Tue May 15 2012 Matthaus Litteken - 2.0.0-0.1rc1 +- Facter 2.0.0rc1 release + * Thu May 10 2012 Matthaus Litteken - 1.6.9-0.1rc1 - Update for 1.6.9rc1 From 4eaa7c48901cd9093d436c4003cc28e417e1cb85 Mon Sep 17 00:00:00 2001 From: Hailee Kenney Date: Wed, 16 May 2012 11:08:49 -0700 Subject: [PATCH 0819/3753] (#14521) Ensure install only strips leading lib dir Prior to this commit, install stripped out all instance of lib, which was fine, but now we are vendoring code for facts, and we need that directory to exist for the vendored so that the require is correct and because that directory contains the license. --- install.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install.rb b/install.rb index 485a0dfee5..1009ad858f 100755 --- a/install.rb +++ b/install.rb @@ -99,7 +99,7 @@ def do_bins(bins, target, strip = 's?bin/') def do_libs(libs, strip = 'lib/') libs.each do |lf| - olf = File.join(InstallOptions.site_dir, lf.gsub(/#{strip}/, '')) + olf = File.join(InstallOptions.site_dir, lf.gsub(/^#{strip}/, '')) op = File.dirname(olf) FileUtils.makedirs(op, {:mode => 0755, :verbose => true}) FileUtils.chmod(0755, op) From 0fa58fcaa5c645265b629916be889143081258f8 Mon Sep 17 00:00:00 2001 From: Hailee Kenney Date: Wed, 16 May 2012 11:32:56 -0700 Subject: [PATCH 0820/3753] (#14521) Ensure install propagates licenses Prior to Facter 2.0, we weren't vendoring code that specifically required a license, so we didn't need to propagate licenses. Now Facter 2.0 is vendoring code and we need to explicitly include the license for that code. --- install.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install.rb b/install.rb index 1009ad858f..7fd442f081 100755 --- a/install.rb +++ b/install.rb @@ -87,7 +87,7 @@ def glob(list) rdoc = glob(%w{bin/* sbin/* lib/**/*.rb README README-library CHANGELOG TODO Install}).reject { |e| e=~ /\.(bat|cmd)$/ } ri = glob(%w(bin/*.rb sbin/* lib/**/*.rb)).reject { |e| e=~ /\.(bat|cmd)$/ } man = glob(%w{man/man8/*}) -libs = glob(%w{lib/**/*.rb lib/**/*.py}) +libs = glob(%w{lib/**/*.rb lib/**/*.py lib/**/LICENSE}) tests = glob(%w{tests/**/*.rb}) def do_bins(bins, target, strip = 's?bin/') From bf4c697bed9eb1f6e9031ab44debd88c5a720ad5 Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Thu, 17 May 2012 11:21:51 -0700 Subject: [PATCH 0821/3753] Update CHANGELOG, conf/redhat/facter.spec for 2.0.0rc2 --- CHANGELOG | 5 +++++ conf/redhat/facter.spec | 11 +++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 56dbe8dc5b..a2ffa151ba 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,8 @@ +2.0.0rc2 +=== +0fa58fc (#14521) Ensure install propagates licenses +4eaa7c4 (#14521) Ensure install only strips leading lib dir + 2.0.0rc1 === 1dd50f4 Use absolute path for $LOAD_PATH in installer diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index a86b68b190..bbd9cb9de3 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -4,13 +4,13 @@ Summary: Ruby module for collecting simple facts about a host operating system Name: facter Version: 2.0.0 #Release: 1%{?dist} -Release: 0.1rc1%{?dist} +Release: 0.1rc2%{?dist} License: Apache 2.0 Group: System Environment/Base URL: http://www.puppetlabs.com/puppet/related-projects/%{name} -Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc1.tar.gz +Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc2.tar.gz #Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}.tar.gz -Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc1.tar.gz.asc +Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc2.tar.gz.asc #Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}.tar.gz.asc BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) @@ -32,7 +32,7 @@ operating system. Additional facts can be added through simple Ruby scripts %prep #%setup -q -n %{name}-%{version} -%setup -q -n %{name}-%{version}rc1 +%setup -q -n %{name}-%{version}rc2 %build @@ -53,6 +53,9 @@ rm -rf %{buildroot} %changelog +* Thu May 17 2012 Moses Mendoza - 2.0.0-0.1rc2 +- Update for 2.0.0rc2 release + * Tue May 15 2012 Matthaus Litteken - 2.0.0-0.1rc1 - Facter 2.0.0rc1 release From aa074dac7f73d8232aca0090274b166f1441a5a3 Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Thu, 17 May 2012 13:51:28 -0700 Subject: [PATCH 0822/3753] Update CHANGELOG, conf/redhat/facter.spec for 1.6.9 --- CHANGELOG | 2 +- conf/redhat/facter.spec | 19 +++++++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index fee466dd23..b0713f28ae 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,4 @@ -1.6.9rc1 +1.6.9 === b398bd8 (#14334) Fix dmidecode based facts on DragonFly BSD 6c46b2c (#14332) Correct stubbing on Ubuntu diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index 4b4470ea91..02cac90ce5 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -3,15 +3,15 @@ Summary: Ruby module for collecting simple facts about a host operating system Name: facter Version: 1.6.9 -#Release: 1%{?dist} -Release: 0.1rc1%{?dist} +Release: 1%{?dist} +#Release: 0.1rc1%{?dist} License: Apache 2.0 Group: System Environment/Base URL: http://www.puppetlabs.com/puppet/related-projects/%{name} -Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc1.tar.gz -#Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}.tar.gz -Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc1.tar.gz.asc -#Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}.tar.gz.asc +#Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc1.tar.gz +Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}.tar.gz +#Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc1.tar.gz.asc +Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}.tar.gz.asc BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) @@ -31,8 +31,8 @@ system. Some of the facts are preconfigured, such as the hostname and the operating system. Additional facts can be added through simple Ruby scripts %prep -#%setup -q -n %{name}-%{version} -%setup -q -n %{name}-%{version}rc1 +%setup -q -n %{name}-%{version} +#%setup -q -n %{name}-%{version}rc1 %build @@ -53,6 +53,9 @@ rm -rf %{buildroot} %changelog +* Thu May 17 2012 Moses Mendoza - 1.6.9-1 +- Update for 1.6.9 + * Thu May 10 2012 Matthaus Litteken - 1.6.9-0.1rc1 - Update for 1.6.9rc1 From b050eb1118512488949394803b371a3d02a730ad Mon Sep 17 00:00:00 2001 From: Hailee Kenney Date: Fri, 18 May 2012 12:28:13 -0700 Subject: [PATCH 0823/3753] (#14582) Fix noise in LSB facts Redirect LSB fact's stderr to /dev/null to prevent excess noise. --- lib/facter/lsbdistcodename.rb | 2 +- lib/facter/lsbdistdescription.rb | 2 +- lib/facter/lsbdistid.rb | 2 +- lib/facter/lsbdistrelease.rb | 2 +- lib/facter/lsbrelease.rb | 2 +- spec/unit/lsbdistcodename_spec.rb | 6 +++--- spec/unit/lsbdistdescription_spec.rb | 6 +++--- spec/unit/lsbdistid_spec.rb | 8 ++++---- spec/unit/lsbdistrelease_spec.rb | 6 +++--- spec/unit/lsbrelease_spec.rb | 6 +++--- 10 files changed, 21 insertions(+), 21 deletions(-) diff --git a/lib/facter/lsbdistcodename.rb b/lib/facter/lsbdistcodename.rb index 00b514abe1..0c0f5d9131 100644 --- a/lib/facter/lsbdistcodename.rb +++ b/lib/facter/lsbdistcodename.rb @@ -13,6 +13,6 @@ Facter.add(:lsbdistcodename) do confine :kernel => [ :linux, :"gnu/kfreebsd" ] setcode do - Facter::Util::Resolution.exec('lsb_release -c -s') + Facter::Util::Resolution.exec('lsb_release -c -s 2>/dev/null') end end diff --git a/lib/facter/lsbdistdescription.rb b/lib/facter/lsbdistdescription.rb index fb3d760530..a4275b2757 100644 --- a/lib/facter/lsbdistdescription.rb +++ b/lib/facter/lsbdistdescription.rb @@ -13,7 +13,7 @@ Facter.add(:lsbdistdescription) do confine :kernel => [ :linux, :"gnu/kfreebsd" ] setcode do - if output = Facter::Util::Resolution.exec('lsb_release -d -s') + if output = Facter::Util::Resolution.exec('lsb_release -d -s 2>/dev/null') # the output may be quoted (at least it is on gentoo) output.sub(/^"(.*)"$/,'\1') end diff --git a/lib/facter/lsbdistid.rb b/lib/facter/lsbdistid.rb index 61d3ac2e8b..065ef9b2b3 100644 --- a/lib/facter/lsbdistid.rb +++ b/lib/facter/lsbdistid.rb @@ -13,6 +13,6 @@ Facter.add(:lsbdistid) do confine :kernel => [ :linux, :"gnu/kfreebsd" ] setcode do - Facter::Util::Resolution.exec('lsb_release -i -s') + Facter::Util::Resolution.exec('lsb_release -i -s 2>/dev/null') end end diff --git a/lib/facter/lsbdistrelease.rb b/lib/facter/lsbdistrelease.rb index 4c8c736c7f..ae5a9b22ed 100644 --- a/lib/facter/lsbdistrelease.rb +++ b/lib/facter/lsbdistrelease.rb @@ -13,6 +13,6 @@ Facter.add(:lsbdistrelease) do confine :kernel => [ :linux, :"gnu/kfreebsd" ] setcode do - Facter::Util::Resolution.exec('lsb_release -r -s') + Facter::Util::Resolution.exec('lsb_release -r -s 2>/dev/null') end end diff --git a/lib/facter/lsbrelease.rb b/lib/facter/lsbrelease.rb index 9b1d5465f9..a1728bb1c4 100644 --- a/lib/facter/lsbrelease.rb +++ b/lib/facter/lsbrelease.rb @@ -13,6 +13,6 @@ Facter.add(:lsbrelease) do confine :kernel => [ :linux, :"gnu/kfreebsd" ] setcode do - Facter::Util::Resolution.exec('lsb_release -v -s') + Facter::Util::Resolution.exec('lsb_release -v -s 2>/dev/null') end end diff --git a/spec/unit/lsbdistcodename_spec.rb b/spec/unit/lsbdistcodename_spec.rb index fa012a88a4..e30aadaca7 100755 --- a/spec/unit/lsbdistcodename_spec.rb +++ b/spec/unit/lsbdistcodename_spec.rb @@ -10,13 +10,13 @@ Facter.fact(:kernel).stubs(:value).returns kernel end - it "should return the codename through lsb_release -c -s" do - Facter::Util::Resolution.stubs(:exec).with('lsb_release -c -s').returns 'n/a' + it "should return the codename through lsb_release -c -s 2>/dev/null" do + Facter::Util::Resolution.stubs(:exec).with('lsb_release -c -s 2>/dev/null').returns 'n/a' Facter.fact(:lsbdistcodename).value.should == 'n/a' end it "should return nil if lsb_release is not installed" do - Facter::Util::Resolution.stubs(:exec).with('lsb_release -c -s').returns nil + Facter::Util::Resolution.stubs(:exec).with('lsb_release -c -s 2>/dev/null').returns nil Facter.fact(:lsbdistcodename).value.should be_nil end end diff --git a/spec/unit/lsbdistdescription_spec.rb b/spec/unit/lsbdistdescription_spec.rb index 5202e88e86..31f7761725 100755 --- a/spec/unit/lsbdistdescription_spec.rb +++ b/spec/unit/lsbdistdescription_spec.rb @@ -10,13 +10,13 @@ Facter.fact(:kernel).stubs(:value).returns kernel end - it "should return the description through lsb_release -d -s" do - Facter::Util::Resolution.stubs(:exec).with('lsb_release -d -s').returns '"Gentoo Base System release 2.1"' + it "should return the description through lsb_release -d -s 2>/dev/null" do + Facter::Util::Resolution.stubs(:exec).with('lsb_release -d -s 2>/dev/null').returns '"Gentoo Base System release 2.1"' Facter.fact(:lsbdistdescription).value.should == 'Gentoo Base System release 2.1' end it "should return nil if lsb_release is not installed" do - Facter::Util::Resolution.stubs(:exec).with('lsb_release -d -s').returns nil + Facter::Util::Resolution.stubs(:exec).with('lsb_release -d -s 2>/dev/null').returns nil Facter.fact(:lsbdistdescription).value.should be_nil end end diff --git a/spec/unit/lsbdistid_spec.rb b/spec/unit/lsbdistid_spec.rb index ae208a1ea9..8ead532052 100755 --- a/spec/unit/lsbdistid_spec.rb +++ b/spec/unit/lsbdistid_spec.rb @@ -10,13 +10,13 @@ Facter.fact(:kernel).stubs(:value).returns kernel end - it "should return the id through lsb_release -i -s" do - Facter::Util::Resolution.stubs(:exec).with('lsb_release -i -s').returns 'Gentoo' + it "should return the id through lsb_release -i -s 2>/dev/null" do + Facter::Util::Resolution.stubs(:exec).with('lsb_release -i -s 2>/dev/null').returns 'Gentoo' Facter.fact(:lsbdistid).value.should == 'Gentoo' end - it "should return nil if lsb_release is not installed" do - Facter::Util::Resolution.stubs(:exec).with('lsb_release -i -s').returns nil + it "should return nil if lsb_release is not installed 2>/dev/null" do + Facter::Util::Resolution.stubs(:exec).with('lsb_release -i -s 2>/dev/null').returns nil Facter.fact(:lsbdistid).value.should be_nil end end diff --git a/spec/unit/lsbdistrelease_spec.rb b/spec/unit/lsbdistrelease_spec.rb index 5a9803d823..bd21eba980 100755 --- a/spec/unit/lsbdistrelease_spec.rb +++ b/spec/unit/lsbdistrelease_spec.rb @@ -10,13 +10,13 @@ Facter.fact(:kernel).stubs(:value).returns kernel end - it "should return the release through lsb_release -r -s" do - Facter::Util::Resolution.stubs(:exec).with('lsb_release -r -s').returns '2.1' + it "should return the release through lsb_release -r -s 2>/dev/null" do + Facter::Util::Resolution.stubs(:exec).with('lsb_release -r -s 2>/dev/null').returns '2.1' Facter.fact(:lsbdistrelease).value.should == '2.1' end it "should return nil if lsb_release is not installed" do - Facter::Util::Resolution.stubs(:exec).with('lsb_release -r -s').returns nil + Facter::Util::Resolution.stubs(:exec).with('lsb_release -r -s 2>/dev/null').returns nil Facter.fact(:lsbdistrelease).value.should be_nil end end diff --git a/spec/unit/lsbrelease_spec.rb b/spec/unit/lsbrelease_spec.rb index 1406da6133..f1cd7fc2ee 100755 --- a/spec/unit/lsbrelease_spec.rb +++ b/spec/unit/lsbrelease_spec.rb @@ -10,13 +10,13 @@ Facter.fact(:kernel).stubs(:value).returns kernel end - it "should return the release through lsb_release -v -s" do - Facter::Util::Resolution.stubs(:exec).with('lsb_release -v -s').returns 'n/a' + it "should return the release through lsb_release -v -s 2>/dev/null" do + Facter::Util::Resolution.stubs(:exec).with('lsb_release -v -s 2>/dev/null').returns 'n/a' Facter.fact(:lsbrelease).value.should == 'n/a' end it "should return nil if lsb_release is not installed" do - Facter::Util::Resolution.stubs(:exec).with('lsb_release -v -s').returns nil + Facter::Util::Resolution.stubs(:exec).with('lsb_release -v -s 2>/dev/null').returns nil Facter.fact(:lsbrelease).value.should be_nil end end From d118d8162f36e0ead86d49fcacb8492d59a79095 Mon Sep 17 00:00:00 2001 From: Stefan Schulte Date: Fri, 18 May 2012 23:27:42 +0200 Subject: [PATCH 0824/3753] (#13678) Add filename extension on absolute paths on windows Running Facter::Util::Resolution.exec calls Facter::Util::Resolution.which to get the absolute pathname to a binary. If passing a relative path, puppet will check different search paths and different filename extensions (like .com, .exe) to find the absolute path on windows machines For absolute paths the former behaviour was to just return true if the path is a valid path to an executable, so "C:\Windows\System32\netsh" was treated as invalid because it misses the correct extension (netsh.exe) which caused the ipaddress6 fact to fail. Change the behaviour of Facter::Util::Resolution.which to also try out the different filename extensions if an absolute path is used. --- lib/facter/util/resolution.rb | 11 +++++++++++ spec/unit/util/resolution_spec.rb | 12 +++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 3242fd8bf5..45dac53302 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -36,6 +36,17 @@ def self.search_paths def self.which(bin) if absolute_path?(bin) return bin if File.executable?(bin) + if Facter::Util::Config.is_windows? and File.extname(bin).empty? + exts = ENV['PATHEXT'] + exts = exts ? exts.split(File::PATH_SEPARATOR) : %w[.COM .EXE .BAT .CMD] + exts.each do |ext| + destext = bin + ext + if File.executable?(destext) + Facter.warnonce("Using Facter::Util::Resolution.which with an absolute path like #{bin} but no fileextension is deprecated. Please add the correct extension (#{ext})") + return destext + end + end + end else search_paths.each do |dir| dest = File.join(dir, bin) diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index cb1faa0fe6..c3e3794bf0 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -369,6 +369,7 @@ context "when run on windows", :as_platform => :windows do before :each do Facter::Util::Resolution.stubs(:search_paths).returns ['C:\Windows\system32', 'C:\Windows', 'C:\Windows\System32\Wbem' ] + ENV.stubs(:[]).with('PATHEXT').returns nil end context "and provided with an absolute path" do @@ -379,6 +380,16 @@ Facter::Util::Resolution.which('\\\\remote\dir\foo.exe').should == '\\\\remote\dir\foo.exe' end + it "should return the binary with added extension if executable" do + ['.COM', '.BAT', '.CMD', '' ].each do |ext| + File.stubs(:executable?).with('C:\Windows\system32\netsh'+ext).returns false + end + File.expects(:executable?).with('C:\Windows\system32\netsh.EXE').returns true + + Facter.expects(:warnonce).with('Using Facter::Util::Resolution.which with an absolute path like C:\\Windows\\system32\\netsh but no fileextension is deprecated. Please add the correct extension (.EXE)') + Facter::Util::Resolution.which('C:\Windows\system32\netsh').should == 'C:\Windows\system32\netsh.EXE' + end + it "should return nil if the binary is not executable" do File.expects(:executable?).with('C:\Tools\foo.exe').returns false File.expects(:executable?).with('\\\\remote\dir\foo.exe').returns false @@ -396,7 +407,6 @@ end it "should return the absolute path with file extension if found" do - ENV.stubs(:[]).with('PATHEXT').returns nil ['.COM', '.EXE', '.BAT', '.CMD', '' ].each do |ext| File.stubs(:executable?).with('C:\Windows\system32\foo'+ext).returns false File.stubs(:executable?).with('C:\Windows\System32\Wbem\foo'+ext).returns false From 2043244049efd6e9fd61b7b1a7d638329bb43eb7 Mon Sep 17 00:00:00 2001 From: Stefan Schulte Date: Sun, 20 May 2012 20:47:34 +0200 Subject: [PATCH 0825/3753] (#13678) Remove deprecation msg triggerd by the ipaddress6 fact Facter::Util::Resolution.exec tries to get the expanded path to the binary by calling Facter::Util::Resolution.which. This works for relative commands (if the command is found in PATH). If the binary describes an absolute path on windows but lacks a filename extension Facter::Util::Resolution.which will also try to find the correct extension, so the final binary passes a File.executable? check. However, this behaviour (passing an absolute path but omit the extension) is marked as deprecated. Remove every occurence of Facter::Util::Resolution.exec where a binary is passed without the correct filename extension. --- lib/facter/ipaddress6.rb | 2 +- lib/facter/util/ip.rb | 8 ++++---- spec/unit/ipaddress6_spec.rb | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/facter/ipaddress6.rb b/lib/facter/ipaddress6.rb index bcc0aee778..16b70fb135 100644 --- a/lib/facter/ipaddress6.rb +++ b/lib/facter/ipaddress6.rb @@ -65,7 +65,7 @@ def get_address_after_token(output, token, return_first=false) Facter.add(:ipaddress6) do confine :kernel => :windows setcode do - output = Facter::Util::Resolution.exec("#{ENV['SYSTEMROOT']}/system32/netsh interface ipv6 show address level=verbose") + output = Facter::Util::Resolution.exec("#{ENV['SYSTEMROOT']}/system32/netsh.exe interface ipv6 show address level=verbose") get_address_after_token(output, 'Address', true) end diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index b7395e08d3..c45c416564 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -80,8 +80,8 @@ def self.get_all_interface_output when 'HP-UX' output = %x{/bin/netstat -in | sed -e 1d} when 'windows' - output = %x|#{ENV['SYSTEMROOT']}/system32/netsh interface ip show interface| - output += %x|#{ENV['SYSTEMROOT']}/system32/netsh interface ipv6 show interface| + output = %x|#{ENV['SYSTEMROOT']}/system32/netsh.exe interface ip show interface| + output += %x|#{ENV['SYSTEMROOT']}/system32/netsh.exe interface ipv6 show interface| end output end @@ -107,9 +107,9 @@ def self.get_output_for_interface_and_label(interface, label) return get_single_interface_output(interface) unless Facter.value(:kernel) == 'windows' if label == 'ipaddress6' - output = %x|#{ENV['SYSTEMROOT']}/system32/netsh interface ipv6 show address \"#{interface}\"| + output = %x|#{ENV['SYSTEMROOT']}/system32/netsh.exe interface ipv6 show address \"#{interface}\"| else - output = %x|#{ENV['SYSTEMROOT']}/system32/netsh interface ip show address \"#{interface}\"| + output = %x|#{ENV['SYSTEMROOT']}/system32/netsh.exe interface ip show address \"#{interface}\"| end output end diff --git a/spec/unit/ipaddress6_spec.rb b/spec/unit/ipaddress6_spec.rb index 747f8f3e0b..6b1a79b20c 100755 --- a/spec/unit/ipaddress6_spec.rb +++ b/spec/unit/ipaddress6_spec.rb @@ -44,7 +44,7 @@ def netsh_fixture(filename) Facter::Util::Config.stubs(:is_windows?).returns(true) fixture = netsh_fixture('windows_netsh_addresses_with_multiple_interfaces') - Facter::Util::Resolution.stubs(:exec).with('d:/windows/system32/netsh interface ipv6 show address level=verbose'). + Facter::Util::Resolution.stubs(:exec).with('d:/windows/system32/netsh.exe interface ipv6 show address level=verbose'). returns(fixture) Facter.value(:ipaddress6).should == "2001:0:4137:9e76:2087:77a:53ef:7527" From 5a8547d24a374aff375b1c0d00d9079270181989 Mon Sep 17 00:00:00 2001 From: Hailee Kenney Date: Mon, 21 May 2012 10:55:03 -0700 Subject: [PATCH 0826/3753] (14466) Warn when no facts found Add `warnonce` message to Facter to alert user when no facts were loaded. --- lib/facter/util/collection.rb | 3 +++ spec/unit/ec2_spec.rb | 4 ++++ spec/unit/util/collection_spec.rb | 15 +++++++++++++++ 3 files changed, 22 insertions(+) diff --git a/lib/facter/util/collection.rb b/lib/facter/util/collection.rb index 459ad44550..41d434c293 100644 --- a/lib/facter/util/collection.rb +++ b/lib/facter/util/collection.rb @@ -78,6 +78,9 @@ def fact(name) # Try HARDER loader.load_all unless @facts[name] + # Warn if no facts were loaded + Facter.warnonce("No facts loaded from #{loader.search_path.join(File::PATH_SEPARATOR)}") if @facts.length == 0 + @facts[name] end diff --git a/spec/unit/ec2_spec.rb b/spec/unit/ec2_spec.rb index 7c7a3d137b..3b69dc3801 100755 --- a/spec/unit/ec2_spec.rb +++ b/spec/unit/ec2_spec.rb @@ -149,6 +149,10 @@ end describe "when api connect test fails" do + before :each do + Facter.stubs(:warnonce) + end + it "should not populate ec2_userdata" do # Emulate ec2 for now as it matters little to this test Facter::Util::EC2.stubs(:has_euca_mac?).returns(true) diff --git a/spec/unit/util/collection_spec.rb b/spec/unit/util/collection_spec.rb index e14718c516..a4560c1f4e 100755 --- a/spec/unit/util/collection_spec.rb +++ b/spec/unit/util/collection_spec.rb @@ -258,4 +258,19 @@ facts.should_not be_include("two") end end + + describe "when no facts are loaded" do + before :each do + @coll = Facter::Util::Collection.new + @load = Facter::Util::Loader.new + @load.stubs(:load).returns nil + @load.stubs(:load_all).returns nil + @coll.stubs(:loader).returns @load + end + + it "should warn when no facts were loaded" do + Facter.expects(:warnonce).with("No facts loaded from #{@load.search_path.join(File::PATH_SEPARATOR)}").once + @coll.fact("one") + end + end end From 653e9e05c140731ae94bae6381d04b826a119c95 Mon Sep 17 00:00:00 2001 From: Hailee Kenney Date: Mon, 21 May 2012 15:29:17 -0700 Subject: [PATCH 0827/3753] (#14466) Fix style issues --- lib/facter/util/collection.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/facter/util/collection.rb b/lib/facter/util/collection.rb index 41d434c293..0e08ae1dd1 100644 --- a/lib/facter/util/collection.rb +++ b/lib/facter/util/collection.rb @@ -78,9 +78,10 @@ def fact(name) # Try HARDER loader.load_all unless @facts[name] - # Warn if no facts were loaded - Facter.warnonce("No facts loaded from #{loader.search_path.join(File::PATH_SEPARATOR)}") if @facts.length == 0 - + if @facts.empty? + Facter.warnonce("No facts loaded from #{loader.search_path.join(File::PATH_SEPARATOR)}") + end + @facts[name] end From 6cc881d95417d75fd88d6cf1016e6a426055e1f6 Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Mon, 21 May 2012 19:04:19 -0700 Subject: [PATCH 0828/3753] Use git describe in Rakefile to determine pkg ver This commit borrows from the puppet-dashboard tarball generation rake task, using git describe to generate the version string instead of using the hardcoded value in lib/facter.rb. This allows the tarball task to generate correctly named RC tarballs. Currently, packaging RCs requires the manual renaming and tarring of the generated directory. Because the built in rake/ PackageTask does not play well with long git-describe strings, it is replaced with a different implementation that can handle the longer git-describe version strings. The gem package task still uses the rake built-in, so the version string is santized for it. Signed-off-by: Moses Mendoza --- Rakefile | 51 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 12 deletions(-) diff --git a/Rakefile b/Rakefile index 063c259d5b..b565fdd8fe 100644 --- a/Rakefile +++ b/Rakefile @@ -17,10 +17,6 @@ require 'rake' require 'rake/packagetask' require 'rake/gempackagetask' -module Facter - FACTERVERSION = File.read('lib/facter.rb')[/FACTERVERSION *= *'(.*)'/,1] or fail "Couldn't find FACTERVERSION" -end - FILES = FileList[ '[A-Z]*', 'install.rb', @@ -31,12 +27,50 @@ FILES = FileList[ 'spec/**/*' ] +def get_version + `git describe`.strip +end + +# :build_environment and :tar are mostly borrowed from puppet-dashboard Rakefile +task :build_environment do + unless ENV['FORCE'] == '1' + modified = `git status --porcelain | sed -e '/^\?/d'` + if modified.split(/\n/).length != 0 + puts <<-HERE +!! ERROR: Your git working directory is not clean. You must +!! remove or commit your changes before you can create a package: + +#{`git status | grep '^#'`.chomp} + +!! To override this check, set FORCE=1 -- e.g. `rake package:deb FORCE=1` + HERE + raise + end + end +end + +desc "Create a release .tar.gz" +task :tar => :build_environment do + name = "facter" + rm_rf 'pkg/tar' + temp=`mktemp -d -t tmpXXXXXX`.strip! + version = get_version + base = "#{temp}/#{name}-#{version}/" + mkdir_p base + sh "git checkout-index -af --prefix=#{base}" + mkdir_p "pkg/tar" + sh "tar -C #{temp} -pczf #{temp}/#{name}-#{version}.tar.gz #{name}-#{version}" + mv "#{temp}/#{name}-#{version}.tar.gz", "pkg/tar" + rm_rf temp + puts "Tarball is pkg/tar/#{name}-#{version}.tar.gz" +end + spec = Gem::Specification.new do |spec| spec.platform = Gem::Platform::RUBY spec.name = 'facter' spec.files = FILES.to_a spec.executables = %w{facter} - spec.version = Facter::FACTERVERSION + spec.version = get_version.split('-')[0] spec.summary = 'Facter, a system inventory tool' spec.description = 'You can prove anything with facts!' spec.author = 'Puppet Labs' @@ -49,13 +83,6 @@ spec = Gem::Specification.new do |spec| '--main' << 'README' << '--line-numbers' end - -Rake::PackageTask.new("facter", Facter::FACTERVERSION) do |pkg| - pkg.package_dir = 'pkg' - pkg.need_tar_gz = true - pkg.package_files = FILES.to_a -end - Rake::GemPackageTask.new(spec) do |pkg| end From 6cc987ceebf00997d637ca85e859ed21e2a560b8 Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Tue, 22 May 2012 11:30:56 -0700 Subject: [PATCH 0829/3753] Update CHANGELOG, conf/redhat/facter.spec for 2.0.0rc3 --- CHANGELOG | 7 +++++++ conf/redhat/facter.spec | 11 +++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 4b48656a73..9165a2daec 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,10 @@ +2.0.0rc3 +=== +6cc881d Use git describe in Rakefile to determine pkg ver +653e9e0 (#14466) Fix style issues +5a8547d (14466) Warn when no facts found +b050eb1 (#14582) Fix noise in LSB facts + 2.0.0rc2 === 0fa58fc (#14521) Ensure install propagates licenses diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index bbd9cb9de3..47eedbc744 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -4,13 +4,13 @@ Summary: Ruby module for collecting simple facts about a host operating system Name: facter Version: 2.0.0 #Release: 1%{?dist} -Release: 0.1rc2%{?dist} +Release: 0.1rc3%{?dist} License: Apache 2.0 Group: System Environment/Base URL: http://www.puppetlabs.com/puppet/related-projects/%{name} -Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc2.tar.gz +Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc3.tar.gz #Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}.tar.gz -Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc2.tar.gz.asc +Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc3.tar.gz.asc #Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}.tar.gz.asc BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) @@ -32,7 +32,7 @@ operating system. Additional facts can be added through simple Ruby scripts %prep #%setup -q -n %{name}-%{version} -%setup -q -n %{name}-%{version}rc2 +%setup -q -n %{name}-%{version}rc3 %build @@ -53,6 +53,9 @@ rm -rf %{buildroot} %changelog +* Tue May 22 2012 Moses Mendoza - 2.0.0-0.1rc3 +- Update for 2.0.0rc3 release + * Thu May 17 2012 Moses Mendoza - 2.0.0-0.1rc2 - Update for 2.0.0rc2 release From 478386d24bb0d5c8494724d44f2e72c26ab4d4ff Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Tue, 22 May 2012 14:12:43 -0700 Subject: [PATCH 0830/3753] (#10261) Detect x64 architecture on Windows Previously, the hardwaremodel fact was using RbConfig::CONFIG['host_cpu'] for Windows, but this returns i686 on a 64-bit OS, which is incorrect. And this caused the architecture fact to be reported as i386, which is also wrong. This commit updates the hardwaremodel fact on Windows to return the appropriate cpu model, e.g. x64, i686, etc. Based on that, the architecture fact will either be x86 or x64, and can be used to install architecture-specific packages, e.g. splunk-4.2.4-110225-x64-release.msi. --- lib/facter/architecture.rb | 2 +- lib/facter/hardwaremodel.rb | 28 +++++++++++++++++++++++++-- spec/unit/architecture_spec.rb | 2 ++ spec/unit/hardwaremodel_spec.rb | 34 +++++++++++++++++++++++++++++++++ 4 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 spec/unit/hardwaremodel_spec.rb diff --git a/lib/facter/architecture.rb b/lib/facter/architecture.rb index a8ff9a3ee6..6ff63e5fc8 100644 --- a/lib/facter/architecture.rb +++ b/lib/facter/architecture.rb @@ -25,7 +25,7 @@ end when /(i[3456]86|pentium)/ case Facter.value(:operatingsystem) - when "Gentoo" + when "Gentoo", "windows" "x86" else "i386" diff --git a/lib/facter/hardwaremodel.rb b/lib/facter/hardwaremodel.rb index e21fd0e317..48b53dabbe 100644 --- a/lib/facter/hardwaremodel.rb +++ b/lib/facter/hardwaremodel.rb @@ -28,7 +28,31 @@ Facter.add(:hardwaremodel) do confine :operatingsystem => :windows setcode do - require 'rbconfig' - RbConfig::CONFIG['host_cpu'] + # http://source.winehq.org/source/include/winnt.h#L568 + # http://msdn.microsoft.com/en-us/library/windows/desktop/aa394373(v=vs.85).aspx + # http://msdn.microsoft.com/en-us/library/windows/desktop/windows.system.processorarchitecture.aspx + require 'facter/util/wmi' + model = "" + Facter::Util::WMI.execquery("select Architecture, Level from Win32_Processor").each do |cpu| + model = + case cpu.Architecture + when 11: 'neutral' # PROCESSOR_ARCHITECTURE_NEUTRAL + when 10: 'i686' # PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 + when 9: 'x64' # PROCESSOR_ARCHITECTURE_AMD64 + when 8: 'msil' # PROCESSOR_ARCHITECTURE_MSIL + when 7: 'alpha64' # PROCESSOR_ARCHITECTURE_ALPHA64 + when 6: 'ia64' # PROCESSOR_ARCHITECTURE_IA64 + when 5: 'arm' # PROCESSOR_ARCHITECTURE_ARM + when 4: 'shx' # PROCESSOR_ARCHITECTURE_SHX + when 3: 'powerpc' # PROCESSOR_ARCHITECTURE_PPC + when 2: 'alpha' # PROCESSOR_ARCHITECTURE_ALPHA + when 1: 'mips' # PROCESSOR_ARCHITECTURE_MIPS + when 0: "i#{cpu.Level}86" # PROCESSOR_ARCHITECTURE_INTEL + else 'unknown' # PROCESSOR_ARCHITECTURE_UNKNOWN + end + break + end + + model end end diff --git a/spec/unit/architecture_spec.rb b/spec/unit/architecture_spec.rb index 5dadd8485d..ae753f190b 100755 --- a/spec/unit/architecture_spec.rb +++ b/spec/unit/architecture_spec.rb @@ -21,6 +21,8 @@ ["Gentoo","i586"] => "x86", ["Gentoo","i686"] => "x86", ["Gentoo","pentium"] => "x86", + ["windows","i386"] => "x86", + ["windows","x64"] => "x64", } generic_archs = Hash.new generic_archs = { diff --git a/spec/unit/hardwaremodel_spec.rb b/spec/unit/hardwaremodel_spec.rb new file mode 100644 index 0000000000..fdbb5762af --- /dev/null +++ b/spec/unit/hardwaremodel_spec.rb @@ -0,0 +1,34 @@ +#!/usr/bin/env ruby + +require 'spec_helper' +require 'facter' + +describe "Hardwaremodel fact" do + it "should match uname -m by default" do + Facter.fact(:kernel).stubs(:value).returns("Darwin") + Facter::Util::Resolution.stubs(:exec).with("uname -m").returns("Inky") + + Facter.fact(:hardwaremodel).value.should == "Inky" + end + + describe "on Windows" do + require 'facter/util/wmi' + before :each do + Facter.fact(:kernel).stubs(:value).returns("windows") + end + + it "should detect i686" do + cpu = mock('cpu', :Architecture => 0, :Level => 6) + Facter::Util::WMI.expects(:execquery).returns([cpu]) + + Facter.fact(:hardwaremodel).value.should == "i686" + end + + it "should detect x64" do + cpu = mock('cpu', :Architecture => 9) + Facter::Util::WMI.expects(:execquery).returns([cpu]) + + Facter.fact(:hardwaremodel).value.should == "x64" + end + end +end From ab025bbb70b55e8cb8a7ded2b958594b8a885ca2 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Wed, 23 May 2012 11:08:12 -0700 Subject: [PATCH 0831/3753] Revert "Revert "(#12864) Windows: get primary DNS from registry"" This reverts commit 753f3a4a1659b3f1d2a4dd5a44b8f8d09b23d0e4. --- lib/facter/domain.rb | 15 ++++--- lib/facter/util/registry.rb | 9 ++++ spec/unit/domain_spec.rb | 38 +++++++++++++---- spec/unit/util/registry_spec.rb | 74 +++++++++++++++++++++++++++++++++ 4 files changed, 122 insertions(+), 14 deletions(-) create mode 100644 lib/facter/util/registry.rb create mode 100644 spec/unit/util/registry_spec.rb diff --git a/lib/facter/domain.rb b/lib/facter/domain.rb index ff5e988c43..2ae909a450 100644 --- a/lib/facter/domain.rb +++ b/lib/facter/domain.rb @@ -52,12 +52,17 @@ Facter.add(:domain) do confine :kernel => :windows setcode do - require 'facter/util/wmi' + require 'facter/util/registry' domain = "" - Facter::Util::WMI.execquery("select DNSDomain from Win32_NetworkAdapterConfiguration where IPEnabled = True").each { |nic| - domain = nic.DNSDomain - break - } + regvalue = Facter::Util::Registry.hklm_read('SYSTEM\CurrentControlSet\Services\Tcpip\Parameters', 'Domain') + domain = regvalue if regvalue + if domain == "" + require 'facter/util/wmi' + Facter::Util::WMI.execquery("select DNSDomain from Win32_NetworkAdapterConfiguration where IPEnabled = True").each { |nic| + domain = nic.DNSDomain + break + } + end domain end end diff --git a/lib/facter/util/registry.rb b/lib/facter/util/registry.rb new file mode 100644 index 0000000000..dc13e458cc --- /dev/null +++ b/lib/facter/util/registry.rb @@ -0,0 +1,9 @@ +module Facter::Util::Registry + class << self + def hklm_read(key, value) + require 'win32/registry' + reg = Win32::Registry::HKEY_LOCAL_MACHINE.open(key) + reg[value] + end + end +end diff --git a/spec/unit/domain_spec.rb b/spec/unit/domain_spec.rb index 6ef416cb4b..07195ca180 100755 --- a/spec/unit/domain_spec.rb +++ b/spec/unit/domain_spec.rb @@ -98,19 +98,39 @@ end describe "on Windows" do - it "should use the DNSDomain for the first nic where ip is enabled" do + before(:each) do Facter.fact(:kernel).stubs(:value).returns("windows") + require 'facter/util/registry' + end + describe "with primary dns suffix" do + before(:each) do + Facter::Util::Registry.stubs(:hklm_read).returns('baz.com') + end + it "should get the primary dns suffix" do + Facter.fact(:domain).value.should == 'baz.com' + end + it "should not execute the wmi query" do + require 'facter/util/wmi' + Facter::Util::WMI.expects(:execquery).never + Facter.fact(:domain).value + end + end + describe "without primary dns suffix" do + before(:each) do + Facter::Util::Registry.stubs(:hklm_read).returns('') + end + it "should use the DNSDomain for the first nic where ip is enabled" do + nic = stubs 'nic' + nic.stubs(:DNSDomain).returns("foo.com") - nic = stubs 'nic' - nic.stubs(:DNSDomain).returns("foo.com") - - nic2 = stubs 'nic' - nic2.stubs(:DNSDomain).returns("bar.com") + nic2 = stubs 'nic' + nic2.stubs(:DNSDomain).returns("bar.com") - require 'facter/util/wmi' - Facter::Util::WMI.stubs(:execquery).with("select DNSDomain from Win32_NetworkAdapterConfiguration where IPEnabled = True").returns([nic, nic2]) + require 'facter/util/wmi' + Facter::Util::WMI.stubs(:execquery).with("select DNSDomain from Win32_NetworkAdapterConfiguration where IPEnabled = True").returns([nic, nic2]) - Facter.fact(:domain).value.should == 'foo.com' + Facter.fact(:domain).value.should == 'foo.com' + end end end end diff --git a/spec/unit/util/registry_spec.rb b/spec/unit/util/registry_spec.rb new file mode 100644 index 0000000000..0da41a5027 --- /dev/null +++ b/spec/unit/util/registry_spec.rb @@ -0,0 +1,74 @@ +#!/usr/bin/env rspec +require 'spec_helper' +require 'facter/operatingsystem' +require 'facter/util/registry' + +describe Facter::Util::Registry do + describe "hklm_read", :if => Facter::Util::Config.is_windows? do + before(:all) do + require 'win32/registry' + end + describe "valid params" do + [ {:key => "valid_key", :value => "valid_value", :expected => "valid"}, + {:key => "valid_key", :value => "", :expected => "valid"}, + {:key => "valid_key", :value => nil, :expected => "invalid"}, + {:key => "", :value => "valid_value", :expected => "valid"}, + {:key => "", :value => "", :expected => "valid"}, + {:key => "", :value => nil, :expected => "invalid"}, + {:key => nil, :value => "valid_value", :expected => "invalid"}, + {:key => nil, :value => "", :expected => "invalid"}, + {:key => nil, :value => nil, :expected => "invalid"} + ].each do |scenario| + describe "with key #{scenario[:key] || "nil"} and value #{scenario[:value] || "nil"}" do + let :fake_registry_key do + fake = {} + fake[scenario[:value]] = scenario[:expected] + fake + end + it "should return #{scenario[:expected]} value" do + Win32::Registry::HKEY_LOCAL_MACHINE.stubs(:open).with(scenario[:key]).returns(fake_registry_key) + + Facter::Util::Registry.hklm_read(scenario[:key], scenario[:value]).should == scenario[:expected] + end + end + end + end + + describe "invalid params" do + [ {:key => "valid_key", :value => "invalid_value"}, + {:key => "valid_key", :value => ""}, + {:key => "valid_key", :value => nil}, + ].each do |scenario| + describe "with valid key and value #{scenario[:value] || "nil"}" do + let :fake_registry_key do + {} + end + it "should raise an error" do + Win32::Registry::HKEY_LOCAL_MACHINE.stubs(:open).with(scenario[:key]).returns(fake_registry_key) + + Facter::Util::Registry.hklm_read(scenario[:key], scenario[:value]).should raise_error + end + end + end + [ {:key => "invalid_key", :value => "valid_value"}, + {:key => "invalid_key", :value => ""}, + {:key => "invalid_key", :value => nil}, + {:key => "", :value => "valid_value"}, + {:key => "", :value => ""}, + {:key => "", :value => nil}, + {:key => nil, :value => "valid_value"}, + {:key => nil, :value => ""}, + {:key => nil, :value => nil} + ].each do |scenario| + describe "with invalid key #{scenario[:key] || "nil"} and value #{scenario[:value] || "nil"}" do + it "should raise an error" do + Win32::Registry::HKEY_LOCAL_MACHINE.stubs(:open).with(scenario[:key]).raises(Win32::Registry::Error, 2) + expect do + Facter::Util::Registry.hklm_read(scenario[:key], scenario[:value]) + end.to raise_error Win32::Registry::Error + end + end + end + end + end +end From 964d1f013302824467a7956961b3a6c9e523bccb Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Mon, 21 May 2012 13:55:08 -0700 Subject: [PATCH 0832/3753] (#12864) Close registry key Previously, we were not closing the registry key (until the process exits). It would be simplier to just use the block form of `open` that automatically closes the key, but it makes it harder to test so this commit explicitly closes the key. --- lib/facter/util/registry.rb | 4 +++- spec/unit/util/registry_spec.rb | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/facter/util/registry.rb b/lib/facter/util/registry.rb index dc13e458cc..e349724dbf 100644 --- a/lib/facter/util/registry.rb +++ b/lib/facter/util/registry.rb @@ -3,7 +3,9 @@ class << self def hklm_read(key, value) require 'win32/registry' reg = Win32::Registry::HKEY_LOCAL_MACHINE.open(key) - reg[value] + rval = reg[value] + reg.close + rval end end end diff --git a/spec/unit/util/registry_spec.rb b/spec/unit/util/registry_spec.rb index 0da41a5027..f13860afd8 100644 --- a/spec/unit/util/registry_spec.rb +++ b/spec/unit/util/registry_spec.rb @@ -27,13 +27,14 @@ end it "should return #{scenario[:expected]} value" do Win32::Registry::HKEY_LOCAL_MACHINE.stubs(:open).with(scenario[:key]).returns(fake_registry_key) + fake_registry_key.stubs(:close) Facter::Util::Registry.hklm_read(scenario[:key], scenario[:value]).should == scenario[:expected] end end end end - + describe "invalid params" do [ {:key => "valid_key", :value => "invalid_value"}, {:key => "valid_key", :value => ""}, @@ -45,6 +46,7 @@ end it "should raise an error" do Win32::Registry::HKEY_LOCAL_MACHINE.stubs(:open).with(scenario[:key]).returns(fake_registry_key) + fake_registry_key.stubs(:close) Facter::Util::Registry.hklm_read(scenario[:key], scenario[:value]).should raise_error end From f44ca52cb5a15d17aecdcb3c8ec288aa7a215179 Mon Sep 17 00:00:00 2001 From: Jeff Weiss Date: Wed, 23 May 2012 11:58:54 -0700 Subject: [PATCH 0833/3753] (maint) Fix hardware model fact for ruby 1.9 The case statement for the windows hardware model fact was broken because ruby 1.9 no longer supports `case x: value` syntax. If everything is on a single line, it must be `case x then value`. --- lib/facter/hardwaremodel.rb | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/lib/facter/hardwaremodel.rb b/lib/facter/hardwaremodel.rb index 48b53dabbe..752ff865fc 100644 --- a/lib/facter/hardwaremodel.rb +++ b/lib/facter/hardwaremodel.rb @@ -28,26 +28,29 @@ Facter.add(:hardwaremodel) do confine :operatingsystem => :windows setcode do + # The cryptic windows cpu architecture models are documented in these places: # http://source.winehq.org/source/include/winnt.h#L568 # http://msdn.microsoft.com/en-us/library/windows/desktop/aa394373(v=vs.85).aspx # http://msdn.microsoft.com/en-us/library/windows/desktop/windows.system.processorarchitecture.aspx + # Also, arm and neutral are included because they are valid for the upcoming + # windows 8 release. --jeffweiss 23 May 2012 require 'facter/util/wmi' model = "" Facter::Util::WMI.execquery("select Architecture, Level from Win32_Processor").each do |cpu| model = case cpu.Architecture - when 11: 'neutral' # PROCESSOR_ARCHITECTURE_NEUTRAL - when 10: 'i686' # PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 - when 9: 'x64' # PROCESSOR_ARCHITECTURE_AMD64 - when 8: 'msil' # PROCESSOR_ARCHITECTURE_MSIL - when 7: 'alpha64' # PROCESSOR_ARCHITECTURE_ALPHA64 - when 6: 'ia64' # PROCESSOR_ARCHITECTURE_IA64 - when 5: 'arm' # PROCESSOR_ARCHITECTURE_ARM - when 4: 'shx' # PROCESSOR_ARCHITECTURE_SHX - when 3: 'powerpc' # PROCESSOR_ARCHITECTURE_PPC - when 2: 'alpha' # PROCESSOR_ARCHITECTURE_ALPHA - when 1: 'mips' # PROCESSOR_ARCHITECTURE_MIPS - when 0: "i#{cpu.Level}86" # PROCESSOR_ARCHITECTURE_INTEL + when 11 then 'neutral' # PROCESSOR_ARCHITECTURE_NEUTRAL + when 10 then 'i686' # PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 + when 9 then 'x64' # PROCESSOR_ARCHITECTURE_AMD64 + when 8 then 'msil' # PROCESSOR_ARCHITECTURE_MSIL + when 7 then 'alpha64' # PROCESSOR_ARCHITECTURE_ALPHA64 + when 6 then 'ia64' # PROCESSOR_ARCHITECTURE_IA64 + when 5 then 'arm' # PROCESSOR_ARCHITECTURE_ARM + when 4 then 'shx' # PROCESSOR_ARCHITECTURE_SHX + when 3 then 'powerpc' # PROCESSOR_ARCHITECTURE_PPC + when 2 then 'alpha' # PROCESSOR_ARCHITECTURE_ALPHA + when 1 then 'mips' # PROCESSOR_ARCHITECTURE_MIPS + when 0 then "i#{cpu.Level}86" # PROCESSOR_ARCHITECTURE_INTEL else 'unknown' # PROCESSOR_ARCHITECTURE_UNKNOWN end break From f42896d92d8542a3017a0ac5141485888d15f3a9 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Wed, 23 May 2012 14:00:59 -0700 Subject: [PATCH 0834/3753] (#14764) Stub architecture fact when Windows facts run on Linux Previously the Windows specific tests were failing when run on Linux (but not Mac or Windows), because the processor fact always runs Linux, Aix, and Solaris specific commands, causing the architecture fact to be loaded, which loads the hardwaremodel, which was recently changed to use WMI to retrieve the processor architecture instead of using RbConfig. This commit just stubs the architecture fact until the processor fact can be fixed to conditionally execute those commands based on the current kernel fact. --- spec/unit/processor_spec.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/unit/processor_spec.rb b/spec/unit/processor_spec.rb index d9822421a5..6be839867e 100755 --- a/spec/unit/processor_spec.rb +++ b/spec/unit/processor_spec.rb @@ -16,6 +16,8 @@ def cpuinfo_fixture(filename) def load(procs) require 'facter/util/wmi' Facter::Util::WMI.stubs(:execquery).with("select * from Win32_Processor").returns(procs) + # This is to workaround #14674 + Facter.fact(:architecture).stubs(:value).returns("x64") # processor facts belong to a file with a different name, # so load the file explicitly (after stubbing kernel), From d6a3e915a3c521898567512754d409ff566c9b9b Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Tue, 22 May 2012 15:28:40 -0700 Subject: [PATCH 0835/3753] Make package task depend on tar in Rakfile This commit makes the built-in package task depend on :tar to ensure the tarball is built before the package task is called. --- Rakefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Rakefile b/Rakefile index b565fdd8fe..0415b7a273 100644 --- a/Rakefile +++ b/Rakefile @@ -86,6 +86,8 @@ end Rake::GemPackageTask.new(spec) do |pkg| end +task :package => :tar + task :default do sh %{rake -T} end From 332bf6484f703a7c888c3d4436915061a0751c3c Mon Sep 17 00:00:00 2001 From: Michael Moll Date: Mon, 5 Mar 2012 20:06:48 +0100 Subject: [PATCH 0836/3753] uname prints a version like "B.11.23" on HP-UX. Delete the first two characters. --- lib/facter/kernelrelease.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/facter/kernelrelease.rb b/lib/facter/kernelrelease.rb index a38b128ebf..b33ca9166f 100644 --- a/lib/facter/kernelrelease.rb +++ b/lib/facter/kernelrelease.rb @@ -20,6 +20,14 @@ setcode 'oslevel -s' end +Facter.add(:kernelrelease) do + confine :kernel => :"hp-ux" + version=Facter::Util::Resolution.exec('uname -r') + setcode do + version[2..-1] + end +end + Facter.add(:kernelrelease) do confine :kernel => %{windows} setcode do From 417d060ee8e863a452c40ed8a9a9ef2671fac057 Mon Sep 17 00:00:00 2001 From: Hailee Kenney Date: Thu, 24 May 2012 15:44:05 -0700 Subject: [PATCH 0837/3753] (#14690) Remove preceding letters from HP-UX release kernel Fix HP-UX release kernel release fact so that it removes the preceding two letters and returns the correct value. --- lib/facter/kernelrelease.rb | 6 ++-- spec/unit/kernelrelease_spec.rb | 49 +++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 spec/unit/kernelrelease_spec.rb diff --git a/lib/facter/kernelrelease.rb b/lib/facter/kernelrelease.rb index b33ca9166f..f24eca9668 100644 --- a/lib/facter/kernelrelease.rb +++ b/lib/facter/kernelrelease.rb @@ -16,14 +16,14 @@ end Facter.add(:kernelrelease) do - confine :kernel => :aix + confine :kernel => %{aix} setcode 'oslevel -s' end Facter.add(:kernelrelease) do - confine :kernel => :"hp-ux" - version=Facter::Util::Resolution.exec('uname -r') + confine :kernel => %{hp-ux} setcode do + version=Facter::Util::Resolution.exec('uname -r') version[2..-1] end end diff --git a/spec/unit/kernelrelease_spec.rb b/spec/unit/kernelrelease_spec.rb new file mode 100644 index 0000000000..aa7e8f80d2 --- /dev/null +++ b/spec/unit/kernelrelease_spec.rb @@ -0,0 +1,49 @@ +#!usr/bin/env rspec + +require 'spec_helper' + +describe "Kernel release fact" do + + describe "on Windows" do + before do + Facter.fact(:kernel).stubs(:value).returns("windows") + require 'facter/util/wmi' + version = stubs 'version' + version.stubs(:Version).returns("test_kernel") + Facter::Util::WMI.stubs(:execquery).with("SELECT Version from Win32_OperatingSystem").returns([version]) + end + it "should return the kernel release" do + Facter.fact(:kernelrelease).value.should == "test_kernel" + end + end + + describe "on AIX" do + before do + Facter.fact(:kernel).stubs(:value).returns("aix") + Facter::Util::Resolution.stubs(:exec).with('oslevel -s').returns("test_kernel") + end + it "should return the kernel release" do + Facter.fact(:kernelrelease).value.should == "test_kernel" + end + end + + describe "on HP-UX" do + before do + Facter.fact(:kernel).stubs(:value).returns("hp-ux") + Facter::Util::Resolution.stubs(:exec).with('uname -r').returns("B.11.31") + end + it "should remove preceding letters" do + Facter.fact(:kernelrelease).value.should == "11.31" + end + end + + describe "on everything else" do + before do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter::Util::Resolution.stubs(:exec).with('uname -r').returns("test_kernel") + end + it "should return the kernel release" do + Facter.fact(:kernelrelease).value.should == "test_kernel" + end + end +end \ No newline at end of file From 857674bec16d1958bd76c5bbbdfd36821dba4dc4 Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Thu, 24 May 2012 16:44:05 -0700 Subject: [PATCH 0838/3753] Update CHANGELOG, conf/redhat/facter.spec for 2.0.0rc4 --- CHANGELOG | 21 +++++++++++++++++++++ conf/redhat/facter.spec | 11 +++++++---- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 9165a2daec..7d11193268 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,24 @@ +2.0.0rc4 +=== +d6a3e91 Make package task depend on tar in Rakfile +f42896d (#14764) Stub architecture fact when Windows facts run on Linux +f44ca52 (maint) Fix hardware model fact for ruby 1.9 +964d1f0 (#12864) Close registry key +ab025bb Revert "Revert "(#12864) Windows: get primary DNS from registry"" +478386d (#10261) Detect x64 architecture on Windows +2043244 (#13678) Remove deprecation msg triggerd by the ipaddress6 fact +d118d81 (#13678) Add filename extension on absolute paths on windows +85654b0 (#13678) Allow passing shell built-ins to exec method on windows +8f4c016 (#13678) Single quote paths on unix with spaces +2d164e8 (#13678) Join PATHs correctly on windows +e7e7e8f (#13678) Extend spec tests for expand_command +0fea7b0 maint: Add shared context for specs to imitate windows or posix +60d0cd2 (#13678) Fix spec failures on windows +121a2ab (#13678) Fix quoting in expand_command +55b1125 (#13678) Add more unit tests for new methods +9086c0a (#13678) Add RDoc documentation for new methods +165ace4 (#13678) Convert command to absolute paths before executing + 2.0.0rc3 === 6cc881d Use git describe in Rakefile to determine pkg ver diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index 47eedbc744..d8c8619bc0 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -4,13 +4,13 @@ Summary: Ruby module for collecting simple facts about a host operating system Name: facter Version: 2.0.0 #Release: 1%{?dist} -Release: 0.1rc3%{?dist} +Release: 0.1rc4%{?dist} License: Apache 2.0 Group: System Environment/Base URL: http://www.puppetlabs.com/puppet/related-projects/%{name} -Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc3.tar.gz +Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc4.tar.gz #Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}.tar.gz -Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc3.tar.gz.asc +Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc4.tar.gz.asc #Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}.tar.gz.asc BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) @@ -32,7 +32,7 @@ operating system. Additional facts can be added through simple Ruby scripts %prep #%setup -q -n %{name}-%{version} -%setup -q -n %{name}-%{version}rc3 +%setup -q -n %{name}-%{version}rc4 %build @@ -53,6 +53,9 @@ rm -rf %{buildroot} %changelog +* Thu May 24 2012 Moses Mendoza - 2.0.0-0.1rc4 +- Update for 2.0.0rc4 release + * Tue May 22 2012 Moses Mendoza - 2.0.0-0.1rc3 - Update for 2.0.0rc3 release From 8001440199067df13018129bb8de886ef2c4884d Mon Sep 17 00:00:00 2001 From: Michael Moll Date: Mon, 5 Mar 2012 20:08:12 +0100 Subject: [PATCH 0839/3753] HP's ssh package for HP-UX uses /etc/opt/ssh for its configuration files --- lib/facter/ssh.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/ssh.rb b/lib/facter/ssh.rb index 23c65783e1..a259d3ccc0 100644 --- a/lib/facter/ssh.rb +++ b/lib/facter/ssh.rb @@ -11,7 +11,7 @@ ## Facts related to SSH ## -["/etc/ssh","/usr/local/etc/ssh","/etc","/usr/local/etc"].each do |dir| +["/etc/ssh","/usr/local/etc/ssh","/etc","/usr/local/etc","/etc/opt/ssh"].each do |dir| {"SSHDSAKey" => { :file => "ssh_host_dsa_key.pub", :sshfprrtype => 2 } , "SSHRSAKey" => { :file => "ssh_host_rsa_key.pub", :sshfprrtype => 1 }, "SSHECDSAKey" => { :file => "ssh_host_ecdsa_key.pub", :sshfprrtype => 3 } }.each do |name,key| Facter.add(name) do setcode do From 20f2d0e38fd0a969fdc5152306d287a05c684371 Mon Sep 17 00:00:00 2001 From: Jeff Weiss Date: Thu, 24 May 2012 22:01:40 -0700 Subject: [PATCH 0840/3753] (#14689) Fix SSH fact for HP-UX Prior to this commit, mmoll had added the location `/etc/opt/ssh` to the search path for the SSH related facts. This commit adds tests for the additional directory in the search path. Also prior to this commit, the SSH fact frighteningly re-defined the fact resolutions for each directory within the search path. This commit refactors the SSH facts to embed the search path checking inside the individual facts rather than a re-definition of the fact for each directory. --- lib/facter/ssh.rb | 74 +++++++++++++++++++++++---------------- spec/unit/ssh_spec.rb | 81 ++++++++++++++++++++++++++++--------------- 2 files changed, 98 insertions(+), 57 deletions(-) diff --git a/lib/facter/ssh.rb b/lib/facter/ssh.rb index a259d3ccc0..8d3c4bbd69 100644 --- a/lib/facter/ssh.rb +++ b/lib/facter/ssh.rb @@ -11,43 +11,59 @@ ## Facts related to SSH ## -["/etc/ssh","/usr/local/etc/ssh","/etc","/usr/local/etc","/etc/opt/ssh"].each do |dir| - {"SSHDSAKey" => { :file => "ssh_host_dsa_key.pub", :sshfprrtype => 2 } , "SSHRSAKey" => { :file => "ssh_host_rsa_key.pub", :sshfprrtype => 1 }, "SSHECDSAKey" => { :file => "ssh_host_ecdsa_key.pub", :sshfprrtype => 3 } }.each do |name,key| - Facter.add(name) do - setcode do - value = nil +{"SSHDSAKey" => { :file => "ssh_host_dsa_key.pub", :sshfprrtype => 2 } , "SSHRSAKey" => { :file => "ssh_host_rsa_key.pub", :sshfprrtype => 1 }, "SSHECDSAKey" => { :file => "ssh_host_ecdsa_key.pub", :sshfprrtype => 3 } }.each do |name,key| + + Facter.add(name) do + setcode do + value = nil + + [ "/etc/ssh", + "/usr/local/etc/ssh", + "/etc", + "/usr/local/etc", + "/etc/opt/ssh", + ].each do |dir| + filepath = File.join(dir,key[:file]) + if FileTest.file?(filepath) begin value = File.read(filepath).chomp.split(/\s+/)[1] + break rescue value = nil end end - value - end # end of proc - end # end of add - Facter.add('SSHFP_' + name[3..-4]) do - setcode do - ssh = Facter.fact(name).value - value = nil - if ssh && key[:sshfprrtype] + end + + value + end + end + + Facter.add('SSHFP_' + name[3..-4]) do + setcode do + ssh = Facter.fact(name).value + value = nil + + if ssh && key[:sshfprrtype] + begin + require 'digest/sha1' + require 'base64' + digest = Base64.decode64(ssh) + value = 'SSHFP ' + key[:sshfprrtype].to_s + ' 1 ' + Digest::SHA1.hexdigest(digest) begin - require 'digest/sha1' - require 'base64' - digest = Base64.decode64(ssh) - value = 'SSHFP ' + key[:sshfprrtype].to_s + ' 1 ' + Digest::SHA1.hexdigest(digest) - begin - require 'digest/sha2' - value += "\nSSHFP " + key[:sshfprrtype].to_s + ' 2 ' + Digest::SHA256.hexdigest(digest) - rescue - end + require 'digest/sha2' + value += "\nSSHFP " + key[:sshfprrtype].to_s + ' 2 ' + Digest::SHA256.hexdigest(digest) rescue - value = nil end - end # end of sshfp if - value - end # end of sshfp proc - end # end of sshfp add - end # end of hash each -end # end of dir each + rescue + value = nil + end + end + + value + end + + end + +end diff --git a/spec/unit/ssh_spec.rb b/spec/unit/ssh_spec.rb index de368ff77f..edd3afe4c6 100755 --- a/spec/unit/ssh_spec.rb +++ b/spec/unit/ssh_spec.rb @@ -6,6 +6,13 @@ describe "SSH fact" do + dirs = [ '/etc/ssh', + '/usr/local/etc/ssh', + '/etc', + '/usr/local/etc', + '/etc/opt/ssh', + ] + before do # We need these facts loaded, but they belong to a file with a # different name, so load the file explicitly. @@ -13,39 +20,57 @@ end # fingerprints extracted from ssh-keygen -r '' -f /etc/ssh/ssh_host_dsa_key.pub - { 'SSHRSAKey' => [ '/usr/local/etc/ssh_host_rsa_key.pub' , "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDrs+KtR8hjasELsyCiiBplUeIi77hEHzTSQt1ALG7N4IgtMg27ZAcq0tl2/O9ZarQuClc903pgionbM9Q98CtAIoqgJwdtsor7ETRmzwrcY/mvI7ne51UzQy4Eh9WrplfpNyg+EVO0FUC7mBcay6JY30QKasePp+g4MkwK5cuTzOCzd9up9KELonlH7tTm2L0YI4HhZugwVoTFulCAZvPICxSk1B/fEKyGSZVfY/UxZNqg9g2Wyvq5u40xQ5eO882UwhB3w4IbmRnPKcyotAcqOJxA7hToMKtEmFct+vjHE8T37w8axE/1X9mdvy8IZbkEBL1cupqqb8a8vU1QTg1z", "SSHFP 1 1 1e4f163a1747d0d1a08a29972c9b5d94ee5705d0\nSSHFP 1 2 4e834c91e423d6085ed6dfb880a59e2f1b04f17c1dc17da07708af67c5ab6045" ], - 'SSHDSAKey' => [ '/etc/ssh/ssh_host_dsa_key.pub' , "ssh-dss AAAAB3NzaC1kc3MAAACBAKjmRez14aZT6OKhHrsw19s7u30AdghwHFQbtC+L781YjJ3UV0/WQoZ8NaDL4ovuvW23RuO49tsqSNcVHg+PtRiN2iTVAS2h55TFhaPKhTs+i0NH3p3Ze8LNSYuz8uK7a+nTxysz47GYTHiE1ke8KXe5wGKDO1TO/MUgpDbwx72LAAAAFQD9yMJCnZMiKzA7J1RNkwvgCyBKSQAAAIAtWBAsuRM0F2fdCe+F/JmgyryQmRIT5vP8E1ww3t3ywdLHklN7UMkaEKBW/TN/jj1JOGXtZ2v5XI+0VNoNKD/7dnCGzNViRT/jjfyVi6l5UMg4Q52Gv0RXJoBJpxNqFOU2niSsy8hioyE39W6LJYWJtQozGpH/KKgkCSvxBn5hlAAAAIB1yo/YD0kQICOO0KE+UMMaKtV7FwyedFJsxsWYwZfHXGwWskf0d2+lPhd9qwdbmSvySE8Qrlvu+W+X8AipwGkItSnj16ORF8kO3lfABa+7L4BLDtumt7ybjBPcHOy3n28dd07TmMtyWvLjOb0mcxPo+TwDLtHd3L/3C1Dh41jRPg==\n", "SSHFP 2 1 f63dfe8da99f50ffbcfa40a61161cee29d109f70\nSSHFP 2 2 5f57aa6be9baddd71b6049ed5d8639664a7ddf92ce293e3887f16ad0f2d459d9" ], - 'SSHECDSAKey' => [ '/etc/ssh_host_ecdsa_key.pub' , 'ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBIuKHtgXQUIrXSVNKC7uY+ZOF7jjfqYNU7Cb/IncDOZ7jW44dxsfBzRJwS5sTHERjBinJskY87mmwY07NFF5GoE=', "SSHFP 3 1 091a088fd3500ad9e35ce201c5101646cbf6ff98\nSSHFP 3 2 1dd2aa8f29b539337316e2862b28c196c68ffe0af78fccf9e50625635677e50f"] + { 'SSHRSAKey' => [ 'ssh_host_rsa_key.pub' , "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDrs+KtR8hjasELsyCiiBplUeIi77hEHzTSQt1ALG7N4IgtMg27ZAcq0tl2/O9ZarQuClc903pgionbM9Q98CtAIoqgJwdtsor7ETRmzwrcY/mvI7ne51UzQy4Eh9WrplfpNyg+EVO0FUC7mBcay6JY30QKasePp+g4MkwK5cuTzOCzd9up9KELonlH7tTm2L0YI4HhZugwVoTFulCAZvPICxSk1B/fEKyGSZVfY/UxZNqg9g2Wyvq5u40xQ5eO882UwhB3w4IbmRnPKcyotAcqOJxA7hToMKtEmFct+vjHE8T37w8axE/1X9mdvy8IZbkEBL1cupqqb8a8vU1QTg1z", "SSHFP 1 1 1e4f163a1747d0d1a08a29972c9b5d94ee5705d0\nSSHFP 1 2 4e834c91e423d6085ed6dfb880a59e2f1b04f17c1dc17da07708af67c5ab6045" ], + 'SSHDSAKey' => [ 'ssh_host_dsa_key.pub' , "ssh-dss AAAAB3NzaC1kc3MAAACBAKjmRez14aZT6OKhHrsw19s7u30AdghwHFQbtC+L781YjJ3UV0/WQoZ8NaDL4ovuvW23RuO49tsqSNcVHg+PtRiN2iTVAS2h55TFhaPKhTs+i0NH3p3Ze8LNSYuz8uK7a+nTxysz47GYTHiE1ke8KXe5wGKDO1TO/MUgpDbwx72LAAAAFQD9yMJCnZMiKzA7J1RNkwvgCyBKSQAAAIAtWBAsuRM0F2fdCe+F/JmgyryQmRIT5vP8E1ww3t3ywdLHklN7UMkaEKBW/TN/jj1JOGXtZ2v5XI+0VNoNKD/7dnCGzNViRT/jjfyVi6l5UMg4Q52Gv0RXJoBJpxNqFOU2niSsy8hioyE39W6LJYWJtQozGpH/KKgkCSvxBn5hlAAAAIB1yo/YD0kQICOO0KE+UMMaKtV7FwyedFJsxsWYwZfHXGwWskf0d2+lPhd9qwdbmSvySE8Qrlvu+W+X8AipwGkItSnj16ORF8kO3lfABa+7L4BLDtumt7ybjBPcHOy3n28dd07TmMtyWvLjOb0mcxPo+TwDLtHd3L/3C1Dh41jRPg==\n", "SSHFP 2 1 f63dfe8da99f50ffbcfa40a61161cee29d109f70\nSSHFP 2 2 5f57aa6be9baddd71b6049ed5d8639664a7ddf92ce293e3887f16ad0f2d459d9" ], + 'SSHECDSAKey' => [ 'ssh_host_ecdsa_key.pub' , 'ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBIuKHtgXQUIrXSVNKC7uY+ZOF7jjfqYNU7Cb/IncDOZ7jW44dxsfBzRJwS5sTHERjBinJskY87mmwY07NFF5GoE=', "SSHFP 3 1 091a088fd3500ad9e35ce201c5101646cbf6ff98\nSSHFP 3 2 1dd2aa8f29b539337316e2862b28c196c68ffe0af78fccf9e50625635677e50f"] }.each_pair do |fact, data| - filename, contents, fingerprint = data - pk = /AAAA\S+/.match(contents).to_s - it "'#{fact}' should be '#{pk}' based on '#{filename}' contents '#{contents}'" do - File.expects(:read).with(filename).at_least_once.returns contents - path, file = Pathname.new(filename).split - ["/etc/ssh","/usr/local/etc/ssh","/etc","/usr/local/etc"].each do |dir| - if dir != path.to_s - blockfile = (Pathname.new(dir) + file).to_s - FileTest.expects(:file?).with( blockfile ).at_least(0).returns false + describe "#{fact}" do + let(:filename) { data[0] } + let(:contents) { data[1] } + let(:fingerprint) { data[2] } + let(:fingerprint_fact) { "SSHFP_#{fact[3..-4]}" } + let(:private_key) { /AAAA\S+/.match(contents).to_s } + + # Before we start testing, we'll say that the file + # doesn't exist in any of our search locations. + # Then, when we test a specific directory, we'll + # toggle just that one on. + # This doesn't test the search order, but it does + # make testing each of the individual cases *way* + # easier. --jeffweiss 24 May 2012 + before(:each) do + dirs.each do |dir| + full_path = File.join(dir, filename) + FileTest.stubs(:file?).with(full_path).returns false end end - FileTest.expects(:file?).with( filename ).at_least_once.returns true - - Facter.fact(fact).value.should == pk - end - fp_fact = 'SSHFP_' + fact[3..-4] - it "'#{fp_fact}' should have fingerprint '#{fingerprint}' based on '#{filename}' contents '#{contents}'" do - File.expects(:read).with(filename).at_least_once.returns contents - path, file = Pathname.new(filename).split - ["/etc/ssh","/usr/local/etc/ssh","/etc","/usr/local/etc"].each do |dir| - if dir != path.to_s - blockfile = (Pathname.new(dir) + file).to_s - FileTest.expects(:file?).with( blockfile ).at_least(0).returns false + + # Now, let's go through each and individually flip then + # on for that test. + dirs.each do |dir| + describe "when data is in #{dir}" do + let(:full_path) { File.join(dir, filename) } + before(:each) do + full_path = File.join(dir, filename) + FileTest.stubs(:file?).with(full_path).returns true + end + + it "should find in #{dir}" do + FileTest.expects(:file?).with(full_path) + Facter.fact(fact).value + end + + it "should match the contents" do + File.expects(:read).with(full_path).at_least_once.returns contents + Facter.fact(fact).value.should == private_key + end + + it "should have matching fingerprint" do + File.expects(:read).with(full_path).at_least_once.returns contents + Facter.fact(fingerprint_fact).value.should == fingerprint + end end end - FileTest.expects(:file?).with( filename ).at_least_once.returns true - - Facter.fact(fp_fact).value.should == fingerprint end end - end From b5dcc5bab3c502ac02c628565b1ab2791205613a Mon Sep 17 00:00:00 2001 From: Jeff Weiss Date: Thu, 24 May 2012 23:52:18 -0700 Subject: [PATCH 0841/3753] (#8963) Fix swap fact failure on SunOS if no swap [Ken Dreyer](https://projects.puppetlabs.com/users/4812)'s original implementation attached to ticket as patch. --- lib/facter/memory.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb index 9e97795437..d810a5a18f 100644 --- a/lib/facter/memory.rb +++ b/lib/facter/memory.rb @@ -165,10 +165,12 @@ if Facter.value(:kernel) == "SunOS" swap = Facter::Util::Resolution.exec('/usr/sbin/swap -l') swapfree, swaptotal = 0, 0 - swap.each_line do |dev| - if dev =~ /^\/\S+\s.*\s+(\d+)\s+(\d+)$/ - swaptotal += $1.to_i / 2 - swapfree += $2.to_i / 2 + unless swap.nil? + swap.each_line do |dev| + if dev =~ /^\/\S+\s.*\s+(\d+)\s+(\d+)$/ + swaptotal += $1.to_i / 2 + swapfree += $2.to_i / 2 + end end end From 8db959620ae5cd9827913c428c142bc2be365228 Mon Sep 17 00:00:00 2001 From: Jeff Weiss Date: Fri, 25 May 2012 00:43:59 -0700 Subject: [PATCH 0842/3753] (#8963) Test for Solaris memory & swap facts Prior to this commit, none of the memory facts (memorysize, memoryfree, swapsize, swapfree) had tests for Solaris. Although the community contribution from Ken Dreyer only covered the case of no swap on Solaris, tests were needed for all the cases on Solaris. --- spec/unit/memory_spec.rb | 94 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/spec/unit/memory_spec.rb b/spec/unit/memory_spec.rb index c40cdf3b22..ab1c622b49 100755 --- a/spec/unit/memory_spec.rb +++ b/spec/unit/memory_spec.rb @@ -79,6 +79,100 @@ Facter.fact(:memorysize).value.should == "254.94 MB" end end + + describe "on Solaris" do + before(:each) do + Facter.clear + Facter.fact(:kernel).stubs(:value).returns("SunOS") + + Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/prtconf 2>/dev/null').returns('Memory size: 1234 Megabytes') + + vmstat_lines = "blah blah blah\n 1 2 3 4 567890" + Facter::Util::Resolution.stubs(:exec).with('vmstat').returns(vmstat_lines) + end + + after(:each) do + Facter.clear + end + + describe "when single swap exists" do + before(:each) do + expected_size = "2345" + expected_free = "1234" + sample_swap_line = "/blah blah #{expected_size} #{expected_free}" + Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/swap -l').returns sample_swap_line + + Facter.collection.loader.load(:memory) + end + + it "should return the current memory size" do + Facter.fact(:memorysize).value.should == "1.21 GB" + end + + it "should return the current memory free" do + Facter.fact(:memoryfree).value.should == "554.58 MB" + end + + it "should return the current swap free" do + Facter.fact(:swapfree).value.should == "617.00 kB" + end + + it "should return the current swap size" do + Facter.fact(:swapsize).value.should == "1.14 MB" + end + end + + describe "when multiple swaps exist" do + before(:each) do + expected_size = "2345" + expected_free = "1234" + sample_swap_line = "/blah blah #{expected_size} #{expected_free}" + Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/swap -l').returns "#{sample_swap_line}\n#{sample_swap_line}" + + Facter.collection.loader.load(:memory) + end + + it "should return the current memory size" do + Facter.fact(:memorysize).value.should == "1.21 GB" + end + + it "should return the current memory free" do + Facter.fact(:memoryfree).value.should == "554.58 MB" + end + + it "should total the swap free" do + Facter.fact(:swapfree).value.should == "1.21 MB" + end + + it "should total the swap size" do + Facter.fact(:swapsize).value.should == "2.29 MB" + end + end + + describe "when no swap exists" do + before(:each) do + Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/swap -l').returns "" + + Facter.collection.loader.load(:memory) + end + + it "should return the current memory size" do + Facter.fact(:memorysize).value.should == "1.21 GB" + end + + it "should return the current memory free" do + Facter.fact(:memoryfree).value.should == "554.58 MB" + end + + it "should return 0 for the swap free" do + Facter.fact(:swapfree).value.should == "0.00 kB" + end + + it "should return 0 for the swap size" do + Facter.fact(:swapsize).value.should == "0.00 kB" + end + end + end describe "on DragonFly BSD" do before :each do From 7e7fce2d422601a575a0740008a91e6a476fb403 Mon Sep 17 00:00:00 2001 From: Hailee Kenney Date: Fri, 25 May 2012 10:51:45 -0700 Subject: [PATCH 0843/3753] (#14700) Add tests for kernel facts (#14700) Add tests for kernel facts Add tets to Facter for kernel facts. --- spec/unit/kernel_spec.rb | 25 +++++++++++++++++++++++++ spec/unit/kernelmajversion_spec.rb | 16 ++++++++++++++++ spec/unit/kernelversion_spec.rb | 29 +++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+) create mode 100644 spec/unit/kernel_spec.rb create mode 100644 spec/unit/kernelmajversion_spec.rb create mode 100644 spec/unit/kernelversion_spec.rb diff --git a/spec/unit/kernel_spec.rb b/spec/unit/kernel_spec.rb new file mode 100644 index 0000000000..c282cdc597 --- /dev/null +++ b/spec/unit/kernel_spec.rb @@ -0,0 +1,25 @@ +#!user/bin/env rspec + +require 'spec_helper' + +describe "Kerenel fact" do + + describe "on Windows" do + before do + Facter::Util::Config.stubs(:is_windows?).returns(true) + end + it "should return the kernel as 'windows'" do + Facter.fact(:kernel).value.should == "windows" + end + end + + describe "on everything else" do + before do + Facter::Util::Resolution.stubs(:exec).with('uname -s').returns("test_kernel") + end + it "should return the kernel using 'uname -s'" do + Facter.fact(:kernel).value.should == 'test_kernel' + end + end +end + diff --git a/spec/unit/kernelmajversion_spec.rb b/spec/unit/kernelmajversion_spec.rb new file mode 100644 index 0000000000..cd63c95709 --- /dev/null +++ b/spec/unit/kernelmajversion_spec.rb @@ -0,0 +1,16 @@ +#!user/bin/env rspec + +require 'spec_helper' + +describe "Kerenel major version fact" do + + before do + Facter.fact(:kernelversion).stubs(:value).returns("12.34.56") + end + it "should return the kernel major release using the kernel release" do + Facter.fact(:kernelmajversion).value.should == "12.34" + end +end + + + diff --git a/spec/unit/kernelversion_spec.rb b/spec/unit/kernelversion_spec.rb new file mode 100644 index 0000000000..7ce93fafa2 --- /dev/null +++ b/spec/unit/kernelversion_spec.rb @@ -0,0 +1,29 @@ +#!user/bin/env rspec + +require 'spec_helper' + +describe "Kerenel version fact" do + + describe "on Solaris/Sun OS" do + before do + Facter.fact(:kernel).stubs(:value).returns('sunos') + Facter::Util::Resolution.stubs(:exec).with('uname -v').returns("1.234.5") + end + it "should return the kernel version using 'uname -v'" do + Facter.fact(:kernelversion).value.should == "1.234.5" + end + end + + describe "on everything else" do + before do + Facter.fact(:kernelrelease).stubs(:value).returns('1.23.4-56') + end + it "should return the kernel version using kernel release" do + Facter.fact(:kernelversion).value.should == "1.23.4" + end + end +end + + + + From ec438d3dc741c6ee5d326f82a22a94d959a054bf Mon Sep 17 00:00:00 2001 From: Jeff Weiss Date: Fri, 25 May 2012 14:56:13 -0700 Subject: [PATCH 0844/3753] (#8923) Add real Solaris output to specs Previously, without access to a Solaris box, the specs had fabricated samples based on the regexes. This commit uses sample output from a real Solaris box for the output to verify the regexes are correct. --- spec/unit/memory_spec.rb | 53 ++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/spec/unit/memory_spec.rb b/spec/unit/memory_spec.rb index ab1c622b49..1f4f20bbe0 100755 --- a/spec/unit/memory_spec.rb +++ b/spec/unit/memory_spec.rb @@ -84,10 +84,19 @@ before(:each) do Facter.clear Facter.fact(:kernel).stubs(:value).returns("SunOS") + sample_prtconf = </dev/null').returns sample_prtconf - Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/prtconf 2>/dev/null').returns('Memory size: 1234 Megabytes') - - vmstat_lines = "blah blah blah\n 1 2 3 4 567890" + vmstat_lines = < Date: Fri, 25 May 2012 15:48:35 -0700 Subject: [PATCH 0845/3753] (#14690) Fix style issues --- lib/facter/kernelrelease.rb | 8 ++++---- spec/unit/kernelrelease_spec.rb | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/facter/kernelrelease.rb b/lib/facter/kernelrelease.rb index f24eca9668..182cfe8420 100644 --- a/lib/facter/kernelrelease.rb +++ b/lib/facter/kernelrelease.rb @@ -16,20 +16,20 @@ end Facter.add(:kernelrelease) do - confine :kernel => %{aix} + confine :kernel => "aix" setcode 'oslevel -s' end Facter.add(:kernelrelease) do - confine :kernel => %{hp-ux} + confine :kernel => "hp-ux" setcode do - version=Facter::Util::Resolution.exec('uname -r') + version = Facter::Util::Resolution.exec('uname -r') version[2..-1] end end Facter.add(:kernelrelease) do - confine :kernel => %{windows} + confine :kernel => "windows" setcode do require 'facter/util/wmi' version = "" diff --git a/spec/unit/kernelrelease_spec.rb b/spec/unit/kernelrelease_spec.rb index aa7e8f80d2..39edb05925 100644 --- a/spec/unit/kernelrelease_spec.rb +++ b/spec/unit/kernelrelease_spec.rb @@ -46,4 +46,4 @@ Facter.fact(:kernelrelease).value.should == "test_kernel" end end -end \ No newline at end of file +end From 21b2c294f1126fbcbba4745c2c4640c78c16a4af Mon Sep 17 00:00:00 2001 From: Hailee Kenney Date: Fri, 25 May 2012 15:28:06 -0700 Subject: [PATCH 0846/3753] (#12649) Add memory and swap values for FreeBSD Apply Jim Pirzyk's patch to fix memory fact on FreeBSD. See ticket: https://projects.puppetlabs.com/issues/12649 --- lib/facter/memory.rb | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb index d810a5a18f..2af7fe3991 100644 --- a/lib/facter/memory.rb +++ b/lib/facter/memory.rb @@ -8,6 +8,8 @@ # On AIX, parses "swap -l" for swap values only. # On OpenBSD, it parses "swapctl -l" for swap values, vmstat via a module for # free memory, and "sysctl hw.physmem" for maximum memory. +# On FreeBSD, it parses "swapinfo -k" for swap values, and parses sysctl for +# maximum memory. # On Solaris, use "swap -l" for swap values, and parsing prtconf for maximum # memory, and again, the vmstat module for free memory. # @@ -125,6 +127,43 @@ end end +if Facter.value(:kernel) == "FreeBSD" + swap = Facter::Util::Resolution.exec('swapinfo -k') + swapfree, swaptotal = 0, 0 + unless swap.empty? + # Parse the line: + # /dev/foo SIZE USAGE AVAILABLE PERCENTAGE% + if swap =~ /\S+\s+(\d+)\s+\d+\s+(\d+)\s+\d+%$/ + swaptotal += $1.to_i + swapfree += $2.to_i + end + end + + Facter.add("SwapSize") do + confine :kernel => :freebsd + setcode do + Facter::Memory.scale_number(swaptotal.to_f,"kB") + end + end + + Facter.add("SwapFree") do + confine :kernel => :freebsd + setcode do + Facter::Memory.scale_number(swapfree.to_f,"kB") + end + end + + Facter::Memory.vmstat_find_free_memory() + + Facter.add("MemoryTotal") do + confine :kernel => :freebsd + memtotal = Facter::Util::Resolution.exec("sysctl -n hw.physmem") + setcode do + Facter::Memory.scale_number(memtotal.to_f,"") + end + end +end + if Facter.value(:kernel) == "Darwin" swap = Facter::Util::Resolution.exec('sysctl vm.swapusage') swapfree, swaptotal = 0, 0 From 7c180906f3f24b70e221b991431fc1db1eba2f23 Mon Sep 17 00:00:00 2001 From: Hailee Kenney Date: Tue, 29 May 2012 16:15:44 -0700 Subject: [PATCH 0847/3753] (#12649) Add specs for FreeBSD memory fact Add tests for added FreeBSD functionality in memory fact and fix up memory fact for FreeBSD. --- lib/facter/memory.rb | 14 +++-- lib/facter/util/memory.rb | 6 ++- spec/unit/memory_spec.rb | 105 +++++++++++++++++++++++++++++++++++++- 3 files changed, 118 insertions(+), 7 deletions(-) diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb index 2af7fe3991..ab02cc157c 100644 --- a/lib/facter/memory.rb +++ b/lib/facter/memory.rb @@ -130,13 +130,15 @@ if Facter.value(:kernel) == "FreeBSD" swap = Facter::Util::Resolution.exec('swapinfo -k') swapfree, swaptotal = 0, 0 - unless swap.empty? + unless swap.nil? + swap.each_line do |device| # Parse the line: # /dev/foo SIZE USAGE AVAILABLE PERCENTAGE% - if swap =~ /\S+\s+(\d+)\s+\d+\s+(\d+)\s+\d+%$/ + if device =~ /\S+\s+(\d+)\s+\d+\s+(\d+)\s+\d+%$/ swaptotal += $1.to_i swapfree += $2.to_i end + end end Facter.add("SwapSize") do @@ -153,9 +155,13 @@ end end - Facter::Memory.vmstat_find_free_memory() + # FreeBSD had to be different and be default prints human readable + # format instead of machine readable. So using 'vmstat -H' instead + # Facter::Memory.vmstat_find_free_memory() + + Facter::Memory.vmstat_find_free_memory(["-H"]) - Facter.add("MemoryTotal") do + Facter.add("MemorySize") do confine :kernel => :freebsd memtotal = Facter::Util::Resolution.exec("sysctl -n hw.physmem") setcode do diff --git a/lib/facter/util/memory.rb b/lib/facter/util/memory.rb index 96d8b4c03c..89a0e6942a 100644 --- a/lib/facter/util/memory.rb +++ b/lib/facter/util/memory.rb @@ -36,8 +36,10 @@ def self.scale_number(size, multiplier) return "%.2f %s" % [size, s] end - def self.vmstat_find_free_memory() - row = Facter::Util::Resolution.exec('vmstat').split("\n")[-1] + def self.vmstat_find_free_memory(args = []) + cmd = 'vmstat' + cmd += (' ' + args.join(' ')) unless args.empty? + row = Facter::Util::Resolution.exec(cmd).split("\n")[-1] if row =~ /^\s*\d+\s*\d+\s*\d+\s*\d+\s*(\d+)/ Facter.add("MemoryFree") do memfree = $1 diff --git a/spec/unit/memory_spec.rb b/spec/unit/memory_spec.rb index 1f4f20bbe0..432290b1d2 100755 --- a/spec/unit/memory_spec.rb +++ b/spec/unit/memory_spec.rb @@ -224,13 +224,116 @@ Facter.fact(:memorysize).value.should == "237.00 MB" end end + + describe "on FreeBSD" do + before(:each) do + Facter.clear + Facter.fact(:kernel).stubs(:value).returns("FreeBSD") + + sample_vmstat = < Date: Fri, 9 Mar 2012 16:58:17 -0500 Subject: [PATCH 0848/3753] Make zpool_version work on FreeBSD kernels as well. --- lib/facter/zpool_version.rb | 2 +- spec/fixtures/unit/zpool_version/freebsd_8.2 | 26 ++++++++++++++ spec/fixtures/unit/zpool_version/freebsd_9.0 | 38 ++++++++++++++++++++ spec/unit/zpool_version_spec.rb | 21 +++++++++++ 4 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 spec/fixtures/unit/zpool_version/freebsd_8.2 create mode 100644 spec/fixtures/unit/zpool_version/freebsd_9.0 diff --git a/lib/facter/zpool_version.rb b/lib/facter/zpool_version.rb index 47da1ffc66..10832af7ff 100644 --- a/lib/facter/zpool_version.rb +++ b/lib/facter/zpool_version.rb @@ -1,7 +1,7 @@ require 'facter' Facter.add('zpool_version') do - confine :kernel => :sunos + confine :kernel => %w(SunOS FreeBSD GNU/kFreeBSD) setcode do zpool_v = Facter::Util::Resolution.exec('zpool upgrade -v') diff --git a/spec/fixtures/unit/zpool_version/freebsd_8.2 b/spec/fixtures/unit/zpool_version/freebsd_8.2 new file mode 100644 index 0000000000..b2eeab452f --- /dev/null +++ b/spec/fixtures/unit/zpool_version/freebsd_8.2 @@ -0,0 +1,26 @@ +This system is currently running ZFS pool version 15. + +The following versions are supported: + +VER DESCRIPTION +--- -------------------------------------------------------- + 1 Initial ZFS version + 2 Ditto blocks (replicated metadata) + 3 Hot spares and double parity RAID-Z + 4 zpool history + 5 Compression using the gzip algorithm + 6 bootfs pool property + 7 Separate intent log devices + 8 Delegated administration + 9 refquota and refreservation properties + 10 Cache devices + 11 Improved scrub performance + 12 Snapshot properties + 13 snapused property + 14 passthrough-x aclinherit + 15 user/group space accounting +For more information on a particular version, including supported releases, see: + +http://www.opensolaris.org/os/community/zfs/version/N + +Where 'N' is the version number. diff --git a/spec/fixtures/unit/zpool_version/freebsd_9.0 b/spec/fixtures/unit/zpool_version/freebsd_9.0 new file mode 100644 index 0000000000..b6bde4b061 --- /dev/null +++ b/spec/fixtures/unit/zpool_version/freebsd_9.0 @@ -0,0 +1,38 @@ +This system is currently running ZFS pool version 28. + +The following versions are supported: + +VER DESCRIPTION +--- -------------------------------------------------------- + 1 Initial ZFS version + 2 Ditto blocks (replicated metadata) + 3 Hot spares and double parity RAID-Z + 4 zpool history + 5 Compression using the gzip algorithm + 6 bootfs pool property + 7 Separate intent log devices + 8 Delegated administration + 9 refquota and refreservation properties + 10 Cache devices + 11 Improved scrub performance + 12 Snapshot properties + 13 snapused property + 14 passthrough-x aclinherit + 15 user/group space accounting + 16 stmf property support + 17 Triple-parity RAID-Z + 18 Snapshot user holds + 19 Log device removal + 20 Compression using zle (zero-length encoding) + 21 Deduplication + 22 Received properties + 23 Slim ZIL + 24 System attributes + 25 Improved scrub stats + 26 Improved snapshot deletion performance + 27 Improved snapshot creation performance + 28 Multiple vdev replacements + +For more information on a particular version, including supported releases, +see the ZFS Administration Guide. + diff --git a/spec/unit/zpool_version_spec.rb b/spec/unit/zpool_version_spec.rb index 88789a17ae..0533ac1856 100644 --- a/spec/unit/zpool_version_spec.rb +++ b/spec/unit/zpool_version_spec.rb @@ -35,6 +35,27 @@ end end + describe "for FreeBSD" do + before :each do + Facter.fact(:kernel).stubs(:value).returns("FreeBSD") + end + + it "should return correct version on FreeBSD 8.2" do + Facter::Util::Resolution.stubs(:exec).with("zpool upgrade -v").returns(my_fixture_read('freebsd_8.2')) + Facter.fact(:zpool_version).value.should == "15" + end + + it "should return correct version on FreeBSD 9.0" do + Facter::Util::Resolution.stubs(:exec).with("zpool upgrade -v").returns(my_fixture_read('freebsd_9.0')) + Facter.fact(:zpool_version).value.should == "28" + end + + it "should return nil if zpool is not available" do + Facter::Util::Resolution.stubs(:exec).with("zpool upgrade -v").returns(nil) + Facter.fact(:zpool_version).value.should == nil + end + end + it "should not run on Linux" do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter.fact(:zpool_version).value.should == nil From 6299040d9705d0da069f202b7a6ab9ef7534d8f8 Mon Sep 17 00:00:00 2001 From: Garrett Wollman Date: Fri, 9 Mar 2012 17:20:41 -0500 Subject: [PATCH 0849/3753] Allow zfs_version to work on FreeBSD as well as Solaris. --- lib/facter/zfs_version.rb | 2 +- spec/fixtures/unit/zfs_version/freebsd_8.2 | 14 ++++++++++++++ spec/fixtures/unit/zfs_version/freebsd_9.0 | 13 +++++++++++++ spec/unit/zfs_version_spec.rb | 21 +++++++++++++++++++++ 4 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 spec/fixtures/unit/zfs_version/freebsd_8.2 create mode 100644 spec/fixtures/unit/zfs_version/freebsd_9.0 diff --git a/lib/facter/zfs_version.rb b/lib/facter/zfs_version.rb index 06c0909cbe..f4f5793e32 100644 --- a/lib/facter/zfs_version.rb +++ b/lib/facter/zfs_version.rb @@ -1,7 +1,7 @@ require 'facter' Facter.add('zfs_version') do - confine :kernel => :sunos + confine :kernel => %w(SunOS FreeBSD GNU/kFreeBSD) setcode do zfs_v = Facter::Util::Resolution.exec('zfs upgrade -v') diff --git a/spec/fixtures/unit/zfs_version/freebsd_8.2 b/spec/fixtures/unit/zfs_version/freebsd_8.2 new file mode 100644 index 0000000000..7f1988fd6b --- /dev/null +++ b/spec/fixtures/unit/zfs_version/freebsd_8.2 @@ -0,0 +1,14 @@ +The following filesystem versions are supported: + +VER DESCRIPTION +--- -------------------------------------------------------- + 1 Initial ZFS filesystem version + 2 Enhanced directory entries + 3 Case insensitive and File system unique identifer (FUID) + 4 userquota, groupquota properties + +For more information on a particular version, including supported releases, see: + +http://www.opensolaris.org/os/community/zfs/version/zpl/N + +Where 'N' is the version number. diff --git a/spec/fixtures/unit/zfs_version/freebsd_9.0 b/spec/fixtures/unit/zfs_version/freebsd_9.0 new file mode 100644 index 0000000000..91d54aaf15 --- /dev/null +++ b/spec/fixtures/unit/zfs_version/freebsd_9.0 @@ -0,0 +1,13 @@ +The following filesystem versions are supported: + +VER DESCRIPTION +--- -------------------------------------------------------- + 1 Initial ZFS filesystem version + 2 Enhanced directory entries + 3 Case insensitive and File system unique identifier (FUID) + 4 userquota, groupquota properties + 5 System attributes + +For more information on a particular version, including supported releases, +see the ZFS Administration Guide. + diff --git a/spec/unit/zfs_version_spec.rb b/spec/unit/zfs_version_spec.rb index eee097e757..01ae8dce17 100644 --- a/spec/unit/zfs_version_spec.rb +++ b/spec/unit/zfs_version_spec.rb @@ -35,6 +35,27 @@ end end + describe "for FreeBSD" do + before :each do + Facter.fact(:kernel).stubs(:value).returns("FreeBSD") + end + + it "should return correct version on FreeBSD 8.2" do + Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(my_fixture_read('freebsd_8.2')) + Facter.fact(:zfs_version).value.should == "4" + end + + it "should return correct version on FreeBSD 9.0" do + Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(my_fixture_read('freebsd_9.0')) + Facter.fact(:zfs_version).value.should == "5" + end + + it "should return nil if zfs command is not available" do + Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(nil) + Facter.fact(:zfs_version).value.should == nil + end + end + it "should not run on Linux" do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter.fact(:zfs_version).value.should == nil From 16b58a24575d9b9d6bd09dcc3757736b9965d3e4 Mon Sep 17 00:00:00 2001 From: Hailee Kenney Date: Wed, 30 May 2012 12:59:09 -0700 Subject: [PATCH 0850/3753] (#14700) Fix whitespace and spelling issues --- spec/unit/kernel_spec.rb | 4 +++- spec/unit/kernelmajversion_spec.rb | 3 ++- spec/unit/kernelversion_spec.rb | 4 +++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/spec/unit/kernel_spec.rb b/spec/unit/kernel_spec.rb index c282cdc597..d2961bf666 100644 --- a/spec/unit/kernel_spec.rb +++ b/spec/unit/kernel_spec.rb @@ -2,12 +2,13 @@ require 'spec_helper' -describe "Kerenel fact" do +describe "Kernel fact" do describe "on Windows" do before do Facter::Util::Config.stubs(:is_windows?).returns(true) end + it "should return the kernel as 'windows'" do Facter.fact(:kernel).value.should == "windows" end @@ -17,6 +18,7 @@ before do Facter::Util::Resolution.stubs(:exec).with('uname -s').returns("test_kernel") end + it "should return the kernel using 'uname -s'" do Facter.fact(:kernel).value.should == 'test_kernel' end diff --git a/spec/unit/kernelmajversion_spec.rb b/spec/unit/kernelmajversion_spec.rb index cd63c95709..4448a45965 100644 --- a/spec/unit/kernelmajversion_spec.rb +++ b/spec/unit/kernelmajversion_spec.rb @@ -2,11 +2,12 @@ require 'spec_helper' -describe "Kerenel major version fact" do +describe "Kernel major version fact" do before do Facter.fact(:kernelversion).stubs(:value).returns("12.34.56") end + it "should return the kernel major release using the kernel release" do Facter.fact(:kernelmajversion).value.should == "12.34" end diff --git a/spec/unit/kernelversion_spec.rb b/spec/unit/kernelversion_spec.rb index 7ce93fafa2..a82920a62a 100644 --- a/spec/unit/kernelversion_spec.rb +++ b/spec/unit/kernelversion_spec.rb @@ -2,13 +2,14 @@ require 'spec_helper' -describe "Kerenel version fact" do +describe "Kernel version fact" do describe "on Solaris/Sun OS" do before do Facter.fact(:kernel).stubs(:value).returns('sunos') Facter::Util::Resolution.stubs(:exec).with('uname -v').returns("1.234.5") end + it "should return the kernel version using 'uname -v'" do Facter.fact(:kernelversion).value.should == "1.234.5" end @@ -18,6 +19,7 @@ before do Facter.fact(:kernelrelease).stubs(:value).returns('1.23.4-56') end + it "should return the kernel version using kernel release" do Facter.fact(:kernelversion).value.should == "1.23.4" end From d161c3ebee68a0768775815ff3089a952f39298c Mon Sep 17 00:00:00 2001 From: Hailee Kenney Date: Wed, 30 May 2012 13:32:06 -0700 Subject: [PATCH 0851/3753] (#14700) Fix kernel version spec Fix kernel version spec so it runs properly on Solaris. --- spec/unit/kernelversion_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/unit/kernelversion_spec.rb b/spec/unit/kernelversion_spec.rb index a82920a62a..ab901f3e0f 100644 --- a/spec/unit/kernelversion_spec.rb +++ b/spec/unit/kernelversion_spec.rb @@ -17,6 +17,7 @@ describe "on everything else" do before do + Facter.fact(:kernel).stubs(:value).returns('linux') Facter.fact(:kernelrelease).stubs(:value).returns('1.23.4-56') end From d88d7be62dfd1b994aa60e00651c7ac5ad30ae88 Mon Sep 17 00:00:00 2001 From: Hailee Kenney Date: Wed, 30 May 2012 14:37:40 -0700 Subject: [PATCH 0852/3753] (#14700) Fix kernel spec on Windows --- spec/unit/kernel_spec.rb | 1 + spec/unit/kernelrelease_spec.rb | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/spec/unit/kernel_spec.rb b/spec/unit/kernel_spec.rb index d2961bf666..b4b0549d02 100644 --- a/spec/unit/kernel_spec.rb +++ b/spec/unit/kernel_spec.rb @@ -16,6 +16,7 @@ describe "on everything else" do before do + Facter::Util::Config.stubs(:is_windows?).returns(false) Facter::Util::Resolution.stubs(:exec).with('uname -s').returns("test_kernel") end diff --git a/spec/unit/kernelrelease_spec.rb b/spec/unit/kernelrelease_spec.rb index 39edb05925..25cf10f168 100644 --- a/spec/unit/kernelrelease_spec.rb +++ b/spec/unit/kernelrelease_spec.rb @@ -12,6 +12,7 @@ version.stubs(:Version).returns("test_kernel") Facter::Util::WMI.stubs(:execquery).with("SELECT Version from Win32_OperatingSystem").returns([version]) end + it "should return the kernel release" do Facter.fact(:kernelrelease).value.should == "test_kernel" end @@ -22,6 +23,7 @@ Facter.fact(:kernel).stubs(:value).returns("aix") Facter::Util::Resolution.stubs(:exec).with('oslevel -s').returns("test_kernel") end + it "should return the kernel release" do Facter.fact(:kernelrelease).value.should == "test_kernel" end @@ -32,6 +34,7 @@ Facter.fact(:kernel).stubs(:value).returns("hp-ux") Facter::Util::Resolution.stubs(:exec).with('uname -r').returns("B.11.31") end + it "should remove preceding letters" do Facter.fact(:kernelrelease).value.should == "11.31" end @@ -39,9 +42,10 @@ describe "on everything else" do before do - Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:kernel).stubs(:value).returns("linux") Facter::Util::Resolution.stubs(:exec).with('uname -r').returns("test_kernel") end + it "should return the kernel release" do Facter.fact(:kernelrelease).value.should == "test_kernel" end From 56793c664d39745729e68da26ff6bdce2a848a73 Mon Sep 17 00:00:00 2001 From: Hailee Kenney Date: Fri, 1 Jun 2012 15:04:09 -0700 Subject: [PATCH 0853/3753] (#13049) Add tests for GNU/kFreeBSD Code was allowed for GNU/kFreeBSD but wasn't being explicitly tested. --- spec/unit/zfs_version_spec.rb | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/spec/unit/zfs_version_spec.rb b/spec/unit/zfs_version_spec.rb index 01ae8dce17..257be3fa49 100644 --- a/spec/unit/zfs_version_spec.rb +++ b/spec/unit/zfs_version_spec.rb @@ -35,26 +35,28 @@ end end - describe "for FreeBSD" do - before :each do - Facter.fact(:kernel).stubs(:value).returns("FreeBSD") - end + ['FreeBSD', 'GNU/kFreeBSD'].each do |kernel| + describe "for #{kernel}" do + before :each do + Facter.fact(:kernel).stubs(:value).returns("#{kernel}") + end - it "should return correct version on FreeBSD 8.2" do - Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(my_fixture_read('freebsd_8.2')) - Facter.fact(:zfs_version).value.should == "4" - end + it "should return correct version on #{kernel} 8.2" do + Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(my_fixture_read('freebsd_8.2')) + Facter.fact(:zfs_version).value.should == "4" + end - it "should return correct version on FreeBSD 9.0" do - Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(my_fixture_read('freebsd_9.0')) - Facter.fact(:zfs_version).value.should == "5" - end + it "should return correct version on #{kernel} 9.0" do + Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(my_fixture_read('freebsd_9.0')) + Facter.fact(:zfs_version).value.should == "5" + end - it "should return nil if zfs command is not available" do - Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(nil) - Facter.fact(:zfs_version).value.should == nil + it "should return nil if zfs command is not available" do + Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(nil) + Facter.fact(:zfs_version).value.should == nil + end end - end + end it "should not run on Linux" do Facter.fact(:kernel).stubs(:value).returns("Linux") From e45ffdea11050acd788529df56acfff69ef858ec Mon Sep 17 00:00:00 2001 From: Hailee Kenney Date: Fri, 1 Jun 2012 15:18:23 -0700 Subject: [PATCH 0854/3753] (#13047) Add tests for GNU/kFreeBSD Code previously allowed for GNU/kFreeBSD but did not explicitly test it. --- spec/unit/zpool_version_spec.rb | 36 +++++++++++++++++---------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/spec/unit/zpool_version_spec.rb b/spec/unit/zpool_version_spec.rb index 0533ac1856..f381361618 100644 --- a/spec/unit/zpool_version_spec.rb +++ b/spec/unit/zpool_version_spec.rb @@ -35,29 +35,31 @@ end end - describe "for FreeBSD" do - before :each do - Facter.fact(:kernel).stubs(:value).returns("FreeBSD") - end + ['FreeBSD', 'GNU/kFreeBSD'].each do |kernel| + describe "on #{kernel}" do + before :each do + Facter.fact(:kernel).stubs(:value).returns("#{kernel}") + end - it "should return correct version on FreeBSD 8.2" do - Facter::Util::Resolution.stubs(:exec).with("zpool upgrade -v").returns(my_fixture_read('freebsd_8.2')) - Facter.fact(:zpool_version).value.should == "15" - end + it "should return correct version on #{kernel} 8.2" do + Facter::Util::Resolution.stubs(:exec).with("zpool upgrade -v").returns(my_fixture_read('freebsd_8.2')) + Facter.fact(:zpool_version).value.should == "15" + end - it "should return correct version on FreeBSD 9.0" do - Facter::Util::Resolution.stubs(:exec).with("zpool upgrade -v").returns(my_fixture_read('freebsd_9.0')) - Facter.fact(:zpool_version).value.should == "28" - end + it "should return correct version on #{kernel} 9.0" do + Facter::Util::Resolution.stubs(:exec).with("zpool upgrade -v").returns(my_fixture_read('freebsd_9.0')) + Facter.fact(:zpool_version).value.should == "28" + end - it "should return nil if zpool is not available" do - Facter::Util::Resolution.stubs(:exec).with("zpool upgrade -v").returns(nil) - Facter.fact(:zpool_version).value.should == nil + it "should return nil if zpool is not available" do + Facter::Util::Resolution.stubs(:exec).with("zpool upgrade -v").returns(nil) + Facter.fact(:zpool_version).value.should == nil + end end - end - + end it "should not run on Linux" do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter.fact(:zpool_version).value.should == nil end end + From 611005baca5f22cafe6c115b59796561ff7c25fd Mon Sep 17 00:00:00 2001 From: Jeff Weiss Date: Tue, 5 Jun 2012 14:18:47 -0700 Subject: [PATCH 0855/3753] (#14827) Fix broken swap units on Mac Prior to this commit, the swap values for Mac OS X were based solely on the text returned from 'sysctl swapusage', which was something like '6144.00M'. This resulting string was passed directly on to Facter without scaling or transformation. This commit makes the swap facts on OS X consistent with the swap facts on every other OS in that the facts will be dynamically scaled and in the same format: '6.14 GB'. --- lib/facter/memory.rb | 44 +++++++------------------ spec/unit/memory_spec.rb | 70 ++++++++++++++++++++++++---------------- 2 files changed, 54 insertions(+), 60 deletions(-) diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb index ab02cc157c..d81d65e2ac 100644 --- a/lib/facter/memory.rb +++ b/lib/facter/memory.rb @@ -39,36 +39,6 @@ end end -Facter.add("SwapSize") do - confine :kernel => :Darwin - setcode do - swap = Facter::Util::Resolution.exec('sysctl vm.swapusage') - swaptotal = 0 - if swap =~ /total = (\S+)/ then swaptotal = $1; end - swaptotal - end -end - -Facter.add("SwapFree") do - confine :kernel => :Darwin - setcode do - swap = Facter::Util::Resolution.exec('sysctl vm.swapusage') - swapfree = 0 - if swap =~ /free = (\S+)/ then swapfree = $1; end - swapfree - end -end - -Facter.add("SwapEncrypted") do - confine :kernel => :Darwin - setcode do - swap = Facter::Util::Resolution.exec('sysctl vm.swapusage') - encrypted = false - if swap =~ /\(encrypted\)/ then encrypted = true; end - encrypted - end -end - if Facter.value(:kernel) == "AIX" and Facter.value(:id) == "root" swap = Facter::Util::Resolution.exec('swap -l') swapfree, swaptotal = 0, 0 @@ -176,7 +146,7 @@ unless swap.empty? # Parse the line: # vm.swapusage: total = 128.00M used = 0.37M free = 127.63M (encrypted) - if swap =~ /total\s=\s(\S+)\s+used\s=\s(\S+)\s+free\s=\s(\S+)\s/ + if swap =~ /total\s=\s(\S+)M\s+used\s=\s(\S+)M\s+free\s=\s(\S+)M\s/ swaptotal += $1.to_i swapfree += $3.to_i end @@ -200,11 +170,21 @@ Facter.add("memorysize") do confine :kernel => :Darwin - memtotal = Facter::Util::Resolution.exec("sysctl hw.memsize | cut -d':' -f2") + memtotal = Facter::Util::Resolution.exec("sysctl -n hw.memsize") setcode do Facter::Memory.scale_number(memtotal.to_f,"") end end + + Facter.add("SwapEncrypted") do + confine :kernel => :Darwin + setcode do + swap = Facter::Util::Resolution.exec('sysctl vm.swapusage') + encrypted = false + if swap =~ /\(encrypted\)/ then encrypted = true; end + encrypted + end + end end if Facter.value(:kernel) == "SunOS" diff --git a/spec/unit/memory_spec.rb b/spec/unit/memory_spec.rb index 432290b1d2..28b5e46b6a 100755 --- a/spec/unit/memory_spec.rb +++ b/spec/unit/memory_spec.rb @@ -3,44 +3,58 @@ require 'spec_helper' describe "Memory facts" do - before do - # We need these facts loaded, but they belong to a file with a - # different name, so load the file explicitly. - Facter.collection.loader.load(:memory) - end - after do Facter.clear end - it "should return the current swap size" do - - Facter.fact(:kernel).stubs(:value).returns("Darwin") - Facter::Util::Resolution.stubs(:exec).with('sysctl vm.swapusage').returns("vm.swapusage: total = 64.00M used = 0.00M free = 64.00M (encrypted)") - swapusage = "vm.swapusage: total = 64.00M used = 0.00M free = 64.00M (encrypted)" + describe "on Darwin" do + before(:each) do + Facter.fact(:kernel).stubs(:value).returns("Darwin") + Facter::Util::Resolution.stubs(:exec).with('sysctl -n hw.memsize').returns('8589934592') + sample_vm_stat = < Date: Wed, 6 Jun 2012 10:19:14 -0700 Subject: [PATCH 0856/3753] (#2066) Add memory facts fixed in MB units Previously memory facts were dynamically scaled, which was great for human readability but bad for ability to use this information programmatically. This commit adds memory facts fixed in MB which were chosen as the smallest useful unit. Memory is given without units attached. --- lib/facter/memory.rb | 154 +++++++++++++- lib/facter/util/memory.rb | 12 ++ spec/unit/memory_spec.rb | 436 ++++++++++++++++++++++++++------------ 3 files changed, 464 insertions(+), 138 deletions(-) diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb index d81d65e2ac..a1f067f5b3 100644 --- a/lib/facter/memory.rb +++ b/lib/facter/memory.rb @@ -79,6 +79,13 @@ end end + Facter.add("swapsize_mb") do + confine :kernel => :openbsd + setcode do + "%.2f" % [swaptotal.to_f / 1024.0] + end + end + Facter.add("SwapFree") do confine :kernel => :openbsd setcode do @@ -86,6 +93,13 @@ end end + Facter.add("swapfree_mb") do + confine :kernel => :openbsd + setcode do + "%.2f" % [swapfree.to_f / 1024.0] + end + end + Facter::Memory.vmstat_find_free_memory() Facter.add("memorysize") do @@ -95,6 +109,15 @@ Facter::Memory.scale_number(memtotal.to_f,"") end end + + Facter.add("memorysize_mb") do + confine :kernel => :openbsd + memtotal = Facter::Util::Resolution.exec("sysctl hw.physmem | cut -d'=' -f2") + size = + setcode do + "%.2f" % [(memtotal.to_f / 1024.0) / 1024.0] + end + end end if Facter.value(:kernel) == "FreeBSD" @@ -114,14 +137,28 @@ Facter.add("SwapSize") do confine :kernel => :freebsd setcode do - Facter::Memory.scale_number(swaptotal.to_f,"kB") + Facter::Memory.scale_number(swaptotal.to_f,"kB") + end + end + + Facter.add("swapsize_mb") do + confine :kernel => :freebsd + setcode do + "%.2f" % [swaptotal.to_f / 1024.0] end end Facter.add("SwapFree") do confine :kernel => :freebsd setcode do - Facter::Memory.scale_number(swapfree.to_f,"kB") + Facter::Memory.scale_number(swapfree.to_f,"kB") + end + end + + Facter.add("swapfree_mb") do + confine :kernel => :freebsd + setcode do + "%.2f" % [swapfree.to_f / 1024.0] end end @@ -135,7 +172,15 @@ confine :kernel => :freebsd memtotal = Facter::Util::Resolution.exec("sysctl -n hw.physmem") setcode do - Facter::Memory.scale_number(memtotal.to_f,"") + Facter::Memory.scale_number(memtotal.to_f,"") + end + end + + Facter.add("memorysize_mb") do + confine :kernel => :freebsd + memtotal = Facter::Util::Resolution.exec("sysctl -n hw.physmem") + setcode do + "%.2f" % [(memtotal.to_f / 1024.0) / 1024.0] end end end @@ -159,6 +204,13 @@ end end + Facter.add("swapsize_mb") do + confine :kernel => :Darwin + setcode do + "%.2f" % [swaptotal.to_f] + end + end + Facter.add("SwapFree") do confine :kernel => :Darwin setcode do @@ -166,6 +218,13 @@ end end + Facter.add("swapfree_mb") do + confine :kernel => :Darwin + setcode do + "%.2f" % [swapfree.to_f] + end + end + Facter::Memory.vmstat_darwin_find_free_memory() Facter.add("memorysize") do @@ -176,6 +235,14 @@ end end + Facter.add("memorysize_mb") do + confine :kernel => :Darwin + memtotal = Facter::Util::Resolution.exec("sysctl -n hw.memsize") + setcode do + "%.2f" % [(memtotal.to_f / 1024.0) / 1024.0] + end + end + Facter.add("SwapEncrypted") do confine :kernel => :Darwin setcode do @@ -206,6 +273,13 @@ end end + Facter.add("swapsize_mb") do + confine :kernel => :sunos + setcode do + "%.2f" % [swaptotal.to_f / 1024.0] + end + end + Facter.add("SwapFree") do confine :kernel => :sunos setcode do @@ -213,6 +287,13 @@ end end + Facter.add("swapfree_mb") do + confine :kernel => :sunos + setcode do + "%.2f" % [swapfree.to_f / 1024.0] + end + end + # Total memory size available from prtconf pconf = Facter::Util::Resolution.exec('/usr/sbin/prtconf 2>/dev/null') phymem = "" @@ -229,6 +310,13 @@ end end + Facter.add("memorysize_mb") do + confine :kernel => :sunos + setcode do + "%.2f" % [phymem.to_f] + end + end + Facter::Memory.vmstat_find_free_memory() end @@ -247,6 +335,18 @@ end end + Facter.add("memoryfree_mb") do + confine :kernel => :windows + setcode do + mem = 0 + Facter::Util::WMI.execquery("select FreePhysicalMemory from Win32_OperatingSystem").each do |os| + mem = os.FreePhysicalMemory + break + end + "%.2f" % [mem.to_f / 1024.0] + end + end + Facter.add("memorysize") do confine :kernel => :windows setcode do @@ -258,6 +358,18 @@ Facter::Memory.scale_number(mem.to_f, "") end end + + Facter.add("memorysize_mb") do + confine :kernel => :windows + setcode do + mem = 0 + Facter::Util::WMI.execquery("select TotalPhysicalMemory from Win32_ComputerSystem").each do |comp| + mem = comp.TotalPhysicalMemory + break + end + "%.2f" % [(mem.to_f / 1024.0) / 1024.0] + end + end end Facter.add("SwapSize") do @@ -269,6 +381,15 @@ end end +Facter.add("swapsize_mb") do + confine :kernel => :dragonfly + setcode do + page_size = Facter::Util::Resolution.exec("/sbin/sysctl -n hw.pagesize").to_f + swaptotal = Facter::Util::Resolution.exec("/sbin/sysctl -n vm.swap_size").to_f * page_size + "%.2f" % [(swaptotal.to_f / 1024.0) / 1024.0] + end +end + Facter.add("SwapFree") do confine :kernel => :dragonfly setcode do @@ -281,11 +402,34 @@ end end +Facter.add("swapfree_mb") do + confine :kernel => :dragonfly + setcode do + page_size = Facter::Util::Resolution.exec("/sbin/sysctl -n hw.pagesize").to_f + swaptotal = Facter::Util::Resolution.exec("/sbin/sysctl -n vm.swap_size").to_f * page_size + swap_anon_use = Facter::Util::Resolution.exec("/sbin/sysctl -n vm.swap_anon_use").to_f * page_size + swap_cache_use = Facter::Util::Resolution.exec("/sbin/sysctl -n vm.swap_cache_use").to_f * page_size + swapfree = swaptotal - swap_anon_use - swap_cache_use + "%.2f" % [(swapfree.to_f / 1024.0) / 1024.0] + end +end + Facter.add("memorysize") do confine :kernel => :dragonfly setcode do - Facter::Memory.vmstat_find_free_memory() memtotal = Facter::Util::Resolution.exec("sysctl -n hw.physmem") Facter::Memory.scale_number(memtotal.to_f,"") end -end \ No newline at end of file +end + +Facter.add("memorysize_mb") do + confine :kernel => :dragonfly + setcode do + memtotal = Facter::Util::Resolution.exec("sysctl -n hw.physmem") + "%.2f" % [(memtotal.to_f / 1024.0) / 1024.0] + end +end + +if Facter.value(:kernel) == "dragonfly" + Facter::Memory.vmstat_find_free_memory() +end diff --git a/lib/facter/util/memory.rb b/lib/facter/util/memory.rb index 89a0e6942a..0ccd7bf585 100644 --- a/lib/facter/util/memory.rb +++ b/lib/facter/util/memory.rb @@ -47,6 +47,12 @@ def self.vmstat_find_free_memory(args = []) Facter::Memory.scale_number(memfree.to_f, "kB") end end + Facter.add("memoryfree_mb") do + memfree = $1 + setcode do + "%.2f" % [memfree.to_f / 1024.0] + end + end end end @@ -79,5 +85,11 @@ def self.vmstat_darwin_find_free_memory() Facter::Memory.scale_number(freemem.to_f, "") end end + + Facter.add("memoryfree_mb") do + setcode do + "%.2f" % [(freemem.to_f / 1024.0) / 1024.0] + end + end end end diff --git a/spec/unit/memory_spec.rb b/spec/unit/memory_spec.rb index 28b5e46b6a..577e4016a1 100755 --- a/spec/unit/memory_spec.rb +++ b/spec/unit/memory_spec.rb @@ -36,21 +36,37 @@ Facter.fact(:swapsize).value.should == "64.00 MB" end + it "should return the current swap size in MB" do + Facter.fact(:swapsize_mb).value.should == "64.00" + end + it "should return the current swap free" do Facter.fact(:swapfree).value.should == "63.00 MB" end + it "should return the current swap free in MB" do + Facter.fact(:swapfree_mb).value.should == "63.00" + end + it "should return whether swap is encrypted" do Facter.fact(:swapencrypted).value.should == true end - it "should return the memorysize" do + it "should return the memory size" do Facter.fact(:memorysize).value.should == "8.00 GB" end + + it "should return the memory size in MB" do + Facter.fact(:memorysize_mb).value.should == "8192.00" + end - it "should return the memoryfree" do + it "should return the memory free" do Facter.fact(:memoryfree).value.should == "138.70 MB" end + + it "should return the memory free in MB" do + Facter.fact(:memoryfree_mb).value.should == "138.70" + end after(:each) do Facter.clear @@ -85,13 +101,33 @@ Facter.fact(:swapfree).value.should == "144.87 MB" end + it "should return the current swap free in MB" do + Facter.fact(:swapfree_mb).value.should == "144.87" + end + it "should return the current swap size" do Facter.fact(:swapsize).value.should == "144.87 MB" end - it "should return the current memorysize" do + it "should return the current swap size in MB" do + Facter.fact(:swapsize_mb).value.should == "144.87" + end + + it "should return the current memory free" do + Facter.fact(:memoryfree).value.should == "176.79 MB" + end + + it "should return the current memory free in MB" do + Facter.fact(:memoryfree_mb).value.should == "176.79" + end + + it "should return the current memory size" do Facter.fact(:memorysize).value.should == "254.94 MB" end + + it "should return the current memory size in MB" do + Facter.fact(:memorysize_mb).value.should == "254.94" + end end describe "on Solaris" do @@ -132,18 +168,34 @@ it "should return the current memory size" do Facter.fact(:memorysize).value.should == "2.00 GB" end + + it "should return the current memory size in MB" do + Facter.fact(:memorysize_mb).value.should == "2048.00" + end it "should return the current memory free" do Facter.fact(:memoryfree).value.should == "465.06 MB" end + it "should return the current memory free in MB" do + Facter.fact(:memoryfree_mb).value.should == "465.06" + end + it "should return the current swap free" do Facter.fact(:swapfree).value.should == "1023.99 MB" end + + it "should return the current swap free in MB" do + Facter.fact(:swapfree_mb).value.should == "1023.99" + end it "should return the current swap size" do Facter.fact(:swapsize).value.should == "1023.99 MB" end + + it "should return the current swap free in MB" do + Facter.fact(:swapsize_mb).value.should == "1023.99" + end end describe "when multiple swaps exist" do @@ -160,18 +212,34 @@ it "should return the current memory size" do Facter.fact(:memorysize).value.should == "2.00 GB" end + + it "should return the current memory size in MB" do + Facter.fact(:memorysize_mb).value.should == "2048.00" + end it "should return the current memory free" do Facter.fact(:memoryfree).value.should == "465.06 MB" end + + it "should return the current memory free in MB" do + Facter.fact(:memoryfree_mb).value.should == "465.06" + end it "should total the swap free" do Facter.fact(:swapfree).value.should == "2.00 GB" end + + it "should return the current swap free in MB" do + Facter.fact(:swapfree_mb).value.should == "2047.98" + end it "should total the swap size" do Facter.fact(:swapsize).value.should == "2.00 GB" end + + it "should return the current swap size in MB" do + Facter.fact(:swapsize_mb).value.should == "2047.98" + end end describe "when no swap exists" do @@ -184,187 +252,289 @@ it "should return the current memory size" do Facter.fact(:memorysize).value.should == "2.00 GB" end - + + it "should return the current memory size in MB" do + Facter.fact(:memorysize_mb).value.should == "2048.00" + end + it "should return the current memory free" do Facter.fact(:memoryfree).value.should == "465.06 MB" end - + + it "should return the current memory free in MB" do + Facter.fact(:memoryfree_mb).value.should == "465.06" + end + it "should return 0 for the swap free" do Facter.fact(:swapfree).value.should == "0.00 kB" end - + + it "should return 0 for the swap free in MB" do + Facter.fact(:swapfree_mb).value.should == "0.00" + end + it "should return 0 for the swap size" do Facter.fact(:swapsize).value.should == "0.00 kB" end + + it "should return 0 for the swap size in MB" do + Facter.fact(:swapsize_mb).value.should == "0.00" + end end - end - describe "on DragonFly BSD" do - before :each do - Facter.clear - Facter.fact(:kernel).stubs(:value).returns("dragonfly") + describe "on DragonFly BSD" do + before :each do + Facter.clear + Facter.fact(:kernel).stubs(:value).returns("dragonfly") - swapusage = "total: 148342k bytes allocated = 0k used, 148342k available" - Facter::Util::Resolution.stubs(:exec).with('/sbin/sysctl -n hw.pagesize').returns("4096") - Facter::Util::Resolution.stubs(:exec).with('/sbin/sysctl -n vm.swap_size').returns("128461") - Facter::Util::Resolution.stubs(:exec).with('/sbin/sysctl -n vm.swap_anon_use').returns("2635") - Facter::Util::Resolution.stubs(:exec).with('/sbin/sysctl -n vm.swap_cache_use').returns("0") + swapusage = "total: 148342k bytes allocated = 0k used, 148342k available" + Facter::Util::Resolution.stubs(:exec).with('/sbin/sysctl -n hw.pagesize').returns("4096") + Facter::Util::Resolution.stubs(:exec).with('/sbin/sysctl -n vm.swap_size').returns("128461") + Facter::Util::Resolution.stubs(:exec).with('/sbin/sysctl -n vm.swap_anon_use').returns("2635") + Facter::Util::Resolution.stubs(:exec).with('/sbin/sysctl -n vm.swap_cache_use').returns("0") - vmstat = < Date: Wed, 6 Jun 2012 10:37:13 -0700 Subject: [PATCH 0857/3753] Update lib/facter.rb, CHANGELOG, facter.spec for 1.6.10rc1 --- CHANGELOG | 23 +++++++++++++++++++++++ conf/redhat/facter.spec | 21 ++++++++++++--------- lib/facter.rb | 2 +- 3 files changed, 36 insertions(+), 10 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index b0713f28ae..63f1742196 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,26 @@ +1.6.10rc1 +=== +d6a3e91 Make package task depend on tar in Rakfile +f42896d (#14764) Stub architecture fact when Windows facts run on Linux +f44ca52 (maint) Fix hardware model fact for ruby 1.9 +964d1f0 (#12864) Close registry key +ab025bb Revert "Revert "(#12864) Windows: get primary DNS from registry"" +478386d (#10261) Detect x64 architecture on Windows +6cc881d Use git describe in Rakefile to determine pkg ver +2043244 (#13678) Remove deprecation msg triggerd by the ipaddress6 fact +d118d81 (#13678) Add filename extension on absolute paths on windows +b050eb1 (#14582) Fix noise in LSB facts +85654b0 (#13678) Allow passing shell built-ins to exec method on windows +8f4c016 (#13678) Single quote paths on unix with spaces +2d164e8 (#13678) Join PATHs correctly on windows +e7e7e8f (#13678) Extend spec tests for expand_command +0fea7b0 maint: Add shared context for specs to imitate windows or posix +60d0cd2 (#13678) Fix spec failures on windows +121a2ab (#13678) Fix quoting in expand_command +55b1125 (#13678) Add more unit tests for new methods +9086c0a (#13678) Add RDoc documentation for new methods +165ace4 (#13678) Convert command to absolute paths before executing + 1.6.9 === b398bd8 (#14334) Fix dmidecode based facts on DragonFly BSD diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index 02cac90ce5..3cdb7f88bf 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -2,16 +2,16 @@ Summary: Ruby module for collecting simple facts about a host operating system Name: facter -Version: 1.6.9 -Release: 1%{?dist} -#Release: 0.1rc1%{?dist} +Version: 1.6.10 +#Release: 1%{?dist} +Release: 0.1rc1%{?dist} License: Apache 2.0 Group: System Environment/Base URL: http://www.puppetlabs.com/puppet/related-projects/%{name} -#Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc1.tar.gz -Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}.tar.gz -#Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc1.tar.gz.asc -Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}.tar.gz.asc +Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc1.tar.gz +#Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}.tar.gz +Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc1.tar.gz.asc +#Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}.tar.gz.asc BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) @@ -31,8 +31,8 @@ system. Some of the facts are preconfigured, such as the hostname and the operating system. Additional facts can be added through simple Ruby scripts %prep -%setup -q -n %{name}-%{version} -#%setup -q -n %{name}-%{version}rc1 +#%setup -q -n %{name}-%{version} +%setup -q -n %{name}-%{version}rc1 %build @@ -53,6 +53,9 @@ rm -rf %{buildroot} %changelog +* Wed Jun 6 2012 Moses Mendoza - 1.6.10-0.1rc1 +- Update for 1.6.10rc1 + * Thu May 17 2012 Moses Mendoza - 1.6.9-1 - Update for 1.6.9 diff --git a/lib/facter.rb b/lib/facter.rb index 9b0ddd5689..91b3d75d89 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -25,7 +25,7 @@ module Util; end include Comparable include Enumerable - FACTERVERSION = '1.6.9' + FACTERVERSION = '1.6.10' # = Facter # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. From 890a90a608f6dec20b5205c78bc194bb37677d5c Mon Sep 17 00:00:00 2001 From: rlinehan Date: Thu, 7 Jun 2012 16:14:54 -0700 Subject: [PATCH 0858/3753] (#2066) Refactor memory facts Refactor memory facts to make general scaled facts based off of platform-specific MB facts. Refactor similar fact definitions and tests. Remove fact definition and creation from vmstat_find_free_memory method definition in util/memory.rb. --- lib/facter/memory.rb | 237 +++++++++++--------------------------- lib/facter/util/memory.rb | 27 +---- spec/unit/memory_spec.rb | 187 ++++-------------------------- 3 files changed, 96 insertions(+), 355 deletions(-) diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb index a1f067f5b3..134f3fdbe7 100644 --- a/lib/facter/memory.rb +++ b/lib/facter/memory.rb @@ -39,6 +39,19 @@ end end +[ "memorysize", + "memoryfree", + "swapsize", + "swapfree" +].each do |fact| + Facter.add(fact) do + setcode do + name = Facter.fact(fact + "_mb").value + Facter::Memory.scale_number(name.to_f, "MB") + end + end +end + if Facter.value(:kernel) == "AIX" and Facter.value(:id) == "root" swap = Facter::Util::Resolution.exec('swap -l') swapfree, swaptotal = 0, 0 @@ -49,17 +62,17 @@ end end - Facter.add("SwapSize") do + Facter.add("swapsize_mb") do confine :kernel => :aix setcode do - Facter::Memory.scale_number(swaptotal.to_f,"MB") + "%.2f" % [swaptotal.to_f] end end - Facter.add("SwapFree") do + Facter.add("swapfree_mb") do confine :kernel => :aix setcode do - Facter::Memory.scale_number(swapfree.to_f,"MB") + "%.2f" % [swapfree.to_f] end end end @@ -72,24 +85,25 @@ swapfree = $2.to_i end - Facter.add("SwapSize") do + Facter.add("memorysize_mb") do confine :kernel => :openbsd + memtotal = Facter::Util::Resolution.exec("sysctl hw.physmem | cut -d'=' -f2") setcode do - Facter::Memory.scale_number(swaptotal.to_f,"kB") + "%.2f" % [(memtotal.to_f / 1024.0) / 1024.0] end end - Facter.add("swapsize_mb") do + Facter.add("memoryfree_mb") do confine :kernel => :openbsd setcode do - "%.2f" % [swaptotal.to_f / 1024.0] + Facter::Memory.vmstat_find_free_memory() end - end + end - Facter.add("SwapFree") do + Facter.add("swapsize_mb") do confine :kernel => :openbsd setcode do - Facter::Memory.scale_number(swapfree.to_f,"kB") + "%.2f" % [swaptotal.to_f / 1024.0] end end @@ -99,25 +113,6 @@ "%.2f" % [swapfree.to_f / 1024.0] end end - - Facter::Memory.vmstat_find_free_memory() - - Facter.add("memorysize") do - confine :kernel => :openbsd - memtotal = Facter::Util::Resolution.exec("sysctl hw.physmem | cut -d'=' -f2") - setcode do - Facter::Memory.scale_number(memtotal.to_f,"") - end - end - - Facter.add("memorysize_mb") do - confine :kernel => :openbsd - memtotal = Facter::Util::Resolution.exec("sysctl hw.physmem | cut -d'=' -f2") - size = - setcode do - "%.2f" % [(memtotal.to_f / 1024.0) / 1024.0] - end - end end if Facter.value(:kernel) == "FreeBSD" @@ -134,55 +129,38 @@ end end - Facter.add("SwapSize") do - confine :kernel => :freebsd - setcode do - Facter::Memory.scale_number(swaptotal.to_f,"kB") - end - end - - Facter.add("swapsize_mb") do - confine :kernel => :freebsd - setcode do - "%.2f" % [swaptotal.to_f / 1024.0] - end + Facter.add("swapsize_mb") do + confine :kernel => :freebsd + setcode do + "%.2f" % [swaptotal.to_f / 1024.0] end + end - Facter.add("SwapFree") do - confine :kernel => :freebsd - setcode do - Facter::Memory.scale_number(swapfree.to_f,"kB") - end + Facter.add("swapfree_mb") do + confine :kernel => :freebsd + setcode do + "%.2f" % [swapfree.to_f / 1024.0] end + end - Facter.add("swapfree_mb") do - confine :kernel => :freebsd - setcode do - "%.2f" % [swapfree.to_f / 1024.0] - end + Facter.add("memorysize_mb") do + confine :kernel => :freebsd + memtotal = Facter::Util::Resolution.exec("sysctl -n hw.physmem") + setcode do + "%.2f" % [(memtotal.to_f / 1024.0) / 1024.0] end + end - # FreeBSD had to be different and be default prints human readable - # format instead of machine readable. So using 'vmstat -H' instead - # Facter::Memory.vmstat_find_free_memory() - - Facter::Memory.vmstat_find_free_memory(["-H"]) - - Facter.add("MemorySize") do - confine :kernel => :freebsd - memtotal = Facter::Util::Resolution.exec("sysctl -n hw.physmem") - setcode do - Facter::Memory.scale_number(memtotal.to_f,"") - end - end + # FreeBSD had to be different and be default prints human readable + # format instead of machine readable. So using 'vmstat -H' instead + # Facter::Memory.vmstat_find_free_memory() - Facter.add("memorysize_mb") do - confine :kernel => :freebsd - memtotal = Facter::Util::Resolution.exec("sysctl -n hw.physmem") - setcode do - "%.2f" % [(memtotal.to_f / 1024.0) / 1024.0] - end + Facter.add("memoryfree_mb") do + confine :kernel => :freebsd + setcode do + Facter::Memory.vmstat_find_free_memory(["-H"]) end + end end if Facter.value(:kernel) == "Darwin" @@ -197,13 +175,6 @@ end end - Facter.add("SwapSize") do - confine :kernel => :Darwin - setcode do - Facter::Memory.scale_number(swaptotal.to_f,"MB") - end - end - Facter.add("swapsize_mb") do confine :kernel => :Darwin setcode do @@ -211,13 +182,6 @@ end end - Facter.add("SwapFree") do - confine :kernel => :Darwin - setcode do - Facter::Memory.scale_number(swapfree.to_f,"MB") - end - end - Facter.add("swapfree_mb") do confine :kernel => :Darwin setcode do @@ -225,21 +189,20 @@ end end - Facter::Memory.vmstat_darwin_find_free_memory() - Facter.add("memorysize") do + Facter.add("memorysize_mb") do confine :kernel => :Darwin memtotal = Facter::Util::Resolution.exec("sysctl -n hw.memsize") setcode do - Facter::Memory.scale_number(memtotal.to_f,"") + "%.2f" % [(memtotal.to_f / 1024.0) / 1024.0] end end - Facter.add("memorysize_mb") do + Facter.add("memoryfree_mb") do confine :kernel => :Darwin - memtotal = Facter::Util::Resolution.exec("sysctl -n hw.memsize") + freemem = Facter::Memory.vmstat_darwin_find_free_memory() setcode do - "%.2f" % [(memtotal.to_f / 1024.0) / 1024.0] + "%.2f" % [(freemem.to_f / 1024.0) / 1024.0] end end @@ -266,13 +229,6 @@ end end - Facter.add("SwapSize") do - confine :kernel => :sunos - setcode do - Facter::Memory.scale_number(swaptotal.to_f,"kB") - end - end - Facter.add("swapsize_mb") do confine :kernel => :sunos setcode do @@ -280,13 +236,6 @@ end end - Facter.add("SwapFree") do - confine :kernel => :sunos - setcode do - Facter::Memory.scale_number(swapfree.to_f,"kB") - end - end - Facter.add("swapfree_mb") do confine :kernel => :sunos setcode do @@ -303,35 +252,33 @@ end end - Facter.add("MemorySize") do + Facter.add("memorysize_mb") do confine :kernel => :sunos setcode do - Facter::Memory.scale_number(phymem.to_f,"MB") + "%.2f" % [phymem.to_f] end end - Facter.add("memorysize_mb") do + Facter.add("memoryfree_mb") do confine :kernel => :sunos setcode do - "%.2f" % [phymem.to_f] + Facter::Memory.vmstat_find_free_memory() end end - - Facter::Memory.vmstat_find_free_memory() end if Facter.value(:kernel) == "windows" require 'facter/util/wmi' - Facter.add("MemoryFree") do + Facter.add("memorysize_mb") do confine :kernel => :windows setcode do mem = 0 - Facter::Util::WMI.execquery("select FreePhysicalMemory from Win32_OperatingSystem").each do |os| - mem = os.FreePhysicalMemory + Facter::Util::WMI.execquery("select TotalPhysicalMemory from Win32_ComputerSystem").each do |comp| + mem = comp.TotalPhysicalMemory break end - Facter::Memory.scale_number(mem.to_f, "kB") + "%.2f" % [(mem.to_f / 1024.0) / 1024.0] end end @@ -346,39 +293,6 @@ "%.2f" % [mem.to_f / 1024.0] end end - - Facter.add("memorysize") do - confine :kernel => :windows - setcode do - mem = 0 - Facter::Util::WMI.execquery("select TotalPhysicalMemory from Win32_ComputerSystem").each do |comp| - mem = comp.TotalPhysicalMemory - break - end - Facter::Memory.scale_number(mem.to_f, "") - end - end - - Facter.add("memorysize_mb") do - confine :kernel => :windows - setcode do - mem = 0 - Facter::Util::WMI.execquery("select TotalPhysicalMemory from Win32_ComputerSystem").each do |comp| - mem = comp.TotalPhysicalMemory - break - end - "%.2f" % [(mem.to_f / 1024.0) / 1024.0] - end - end -end - -Facter.add("SwapSize") do - confine :kernel => :dragonfly - setcode do - page_size = Facter::Util::Resolution.exec("/sbin/sysctl -n hw.pagesize").to_f - swaptotal = Facter::Util::Resolution.exec("/sbin/sysctl -n vm.swap_size").to_f * page_size - Facter::Memory.scale_number(swaptotal.to_f,"") - end end Facter.add("swapsize_mb") do @@ -390,18 +304,6 @@ end end -Facter.add("SwapFree") do - confine :kernel => :dragonfly - setcode do - page_size = Facter::Util::Resolution.exec("/sbin/sysctl -n hw.pagesize").to_f - swaptotal = Facter::Util::Resolution.exec("/sbin/sysctl -n vm.swap_size").to_f * page_size - swap_anon_use = Facter::Util::Resolution.exec("/sbin/sysctl -n vm.swap_anon_use").to_f * page_size - swap_cache_use = Facter::Util::Resolution.exec("/sbin/sysctl -n vm.swap_cache_use").to_f * page_size - swapfree = swaptotal - swap_anon_use - swap_cache_use - Facter::Memory.scale_number(swapfree.to_f,"") - end -end - Facter.add("swapfree_mb") do confine :kernel => :dragonfly setcode do @@ -414,14 +316,6 @@ end end -Facter.add("memorysize") do - confine :kernel => :dragonfly - setcode do - memtotal = Facter::Util::Resolution.exec("sysctl -n hw.physmem") - Facter::Memory.scale_number(memtotal.to_f,"") - end -end - Facter.add("memorysize_mb") do confine :kernel => :dragonfly setcode do @@ -431,5 +325,10 @@ end if Facter.value(:kernel) == "dragonfly" - Facter::Memory.vmstat_find_free_memory() + Facter.add("memoryfree_mb") do + confine :kernel => :dragonfly + setcode do + Facter::Memory.vmstat_find_free_memory() + end + end end diff --git a/lib/facter/util/memory.rb b/lib/facter/util/memory.rb index 0ccd7bf585..107e9de619 100644 --- a/lib/facter/util/memory.rb +++ b/lib/facter/util/memory.rb @@ -41,18 +41,8 @@ def self.vmstat_find_free_memory(args = []) cmd += (' ' + args.join(' ')) unless args.empty? row = Facter::Util::Resolution.exec(cmd).split("\n")[-1] if row =~ /^\s*\d+\s*\d+\s*\d+\s*\d+\s*(\d+)/ - Facter.add("MemoryFree") do - memfree = $1 - setcode do - Facter::Memory.scale_number(memfree.to_f, "kB") - end - end - Facter.add("memoryfree_mb") do - memfree = $1 - setcode do - "%.2f" % [memfree.to_f / 1024.0] - end - end + memfree = $1 + return "%.2f" % [memfree.to_f / 1024.0] end end @@ -79,17 +69,6 @@ def self.vmstat_darwin_find_free_memory() end end - freemem = ( memfree + memspecfree ) * pagesize - Facter.add("MemoryFree") do - setcode do - Facter::Memory.scale_number(freemem.to_f, "") - end - end - - Facter.add("memoryfree_mb") do - setcode do - "%.2f" % [(freemem.to_f / 1024.0) / 1024.0] - end - end + return ( memfree + memspecfree ) * pagesize end end diff --git a/spec/unit/memory_spec.rb b/spec/unit/memory_spec.rb index 577e4016a1..7905c5adf1 100755 --- a/spec/unit/memory_spec.rb +++ b/spec/unit/memory_spec.rb @@ -7,6 +7,29 @@ Facter.clear end + describe "when returning scaled sizes" do + before(:each) do + Facter.collection.loader.load(:memory) + end + + [ "memorysize", + "memoryfree", + "swapsize", + "swapfree" + ].each do |fact| + + { "200.00" => "200.00 MB", + "1536.00" => "1.50 GB", + "1572864.00" => "1.50 TB", + }.each do |mbval, scval| + it "should scale #{fact} when given #{mbval} MB" do + Facter.fact(fact + "_mb").stubs(:value).returns(mbval) + Facter.fact(fact).value.should == scval + end + end + end + end + describe "on Darwin" do before(:each) do Facter.fact(:kernel).stubs(:value).returns("Darwin") @@ -32,18 +55,10 @@ Facter.collection.loader.load(:memory) end - it "should return the current swap size" do - Facter.fact(:swapsize).value.should == "64.00 MB" - end - it "should return the current swap size in MB" do Facter.fact(:swapsize_mb).value.should == "64.00" end - it "should return the current swap free" do - Facter.fact(:swapfree).value.should == "63.00 MB" - end - it "should return the current swap free in MB" do Facter.fact(:swapfree_mb).value.should == "63.00" end @@ -51,18 +66,10 @@ it "should return whether swap is encrypted" do Facter.fact(:swapencrypted).value.should == true end - - it "should return the memory size" do - Facter.fact(:memorysize).value.should == "8.00 GB" - end it "should return the memory size in MB" do Facter.fact(:memorysize_mb).value.should == "8192.00" end - - it "should return the memory free" do - Facter.fact(:memoryfree).value.should == "138.70 MB" - end it "should return the memory free in MB" do Facter.fact(:memoryfree_mb).value.should == "138.70" @@ -97,34 +104,18 @@ Facter.clear end - it "should return the current swap free" do - Facter.fact(:swapfree).value.should == "144.87 MB" - end - it "should return the current swap free in MB" do Facter.fact(:swapfree_mb).value.should == "144.87" end - it "should return the current swap size" do - Facter.fact(:swapsize).value.should == "144.87 MB" - end - it "should return the current swap size in MB" do Facter.fact(:swapsize_mb).value.should == "144.87" end - it "should return the current memory free" do - Facter.fact(:memoryfree).value.should == "176.79 MB" - end - it "should return the current memory free in MB" do Facter.fact(:memoryfree_mb).value.should == "176.79" end - it "should return the current memory size" do - Facter.fact(:memorysize).value.should == "254.94 MB" - end - it "should return the current memory size in MB" do Facter.fact(:memorysize_mb).value.should == "254.94" end @@ -165,35 +156,19 @@ Facter.collection.loader.load(:memory) end - it "should return the current memory size" do - Facter.fact(:memorysize).value.should == "2.00 GB" - end - it "should return the current memory size in MB" do Facter.fact(:memorysize_mb).value.should == "2048.00" end - it "should return the current memory free" do - Facter.fact(:memoryfree).value.should == "465.06 MB" - end - it "should return the current memory free in MB" do Facter.fact(:memoryfree_mb).value.should == "465.06" end - it "should return the current swap free" do - Facter.fact(:swapfree).value.should == "1023.99 MB" - end - it "should return the current swap free in MB" do Facter.fact(:swapfree_mb).value.should == "1023.99" end - - it "should return the current swap size" do - Facter.fact(:swapsize).value.should == "1023.99 MB" - end - it "should return the current swap free in MB" do + it "should return the current swap size in MB" do Facter.fact(:swapsize_mb).value.should == "1023.99" end end @@ -208,34 +183,18 @@ Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/swap -l').returns sample_swap_line Facter.collection.loader.load(:memory) end - - it "should return the current memory size" do - Facter.fact(:memorysize).value.should == "2.00 GB" - end it "should return the current memory size in MB" do Facter.fact(:memorysize_mb).value.should == "2048.00" end - - it "should return the current memory free" do - Facter.fact(:memoryfree).value.should == "465.06 MB" - end it "should return the current memory free in MB" do Facter.fact(:memoryfree_mb).value.should == "465.06" end - - it "should total the swap free" do - Facter.fact(:swapfree).value.should == "2.00 GB" - end it "should return the current swap free in MB" do Facter.fact(:swapfree_mb).value.should == "2047.98" end - - it "should total the swap size" do - Facter.fact(:swapsize).value.should == "2.00 GB" - end it "should return the current swap size in MB" do Facter.fact(:swapsize_mb).value.should == "2047.98" @@ -248,35 +207,19 @@ Facter.collection.loader.load(:memory) end - - it "should return the current memory size" do - Facter.fact(:memorysize).value.should == "2.00 GB" - end it "should return the current memory size in MB" do Facter.fact(:memorysize_mb).value.should == "2048.00" end - - it "should return the current memory free" do - Facter.fact(:memoryfree).value.should == "465.06 MB" - end it "should return the current memory free in MB" do Facter.fact(:memoryfree_mb).value.should == "465.06" end - - it "should return 0 for the swap free" do - Facter.fact(:swapfree).value.should == "0.00 kB" - end - + it "should return 0 for the swap free in MB" do Facter.fact(:swapfree_mb).value.should == "0.00" end - it "should return 0 for the swap size" do - Facter.fact(:swapsize).value.should == "0.00 kB" - end - it "should return 0 for the swap size in MB" do Facter.fact(:swapsize_mb).value.should == "0.00" end @@ -309,35 +252,18 @@ Facter.clear end - it "should return the current swap free" do - Facter.fact(:swapfree).value.should == "491.51 MB" - end - it "should return the current swap free in MB" do Facter.fact(:swapfree_mb).value.should == "491.51" end - it "should return the current swap size" do - Facter.fact(:swapsize).value.should == "501.80 MB" - end - it "should return the current swap size in MB" do Facter.fact(:swapsize_mb).value.should == "501.80" end - it "should return the current memory size" do - Facter.fact(:memorysize).value.should == "237.00 MB" - end - it "should return the current memory size in MB" do Facter.fact(:memorysize_mb).value.should == "237.00" end - it "should return the current memory free" do - Facter.fact(:memoryfree).value.should == "13.61 MB" - end - - it "should return the current memory free in MB" do Facter.fact(:memoryfree_mb).value.should == "13.61" end @@ -374,33 +300,17 @@ Facter.collection.loader.load(:memory) end - it "should return the current swap free" do - Facter.fact(:swapfree).value.should == "0.00 kB" - end - it "should return the current swap free in MB" do Facter.fact(:swapfree_mb).value.should == "0.00" end - it "should return the current swap size" do - Facter.fact(:swapsize).value.should == "0.00 kB" - end - it "should return the current swap size in MB" do Facter.fact(:swapsize_mb).value.should == "0.00" end - it "should return the current memory size" do - Facter.fact(:memorysize).value.should == "1007.34 MB" - end - it "should return the current memory size in MB" do Facter.fact(:memorysize_mb).value.should == "1007.34" end - - it "should return the current memory free" do - Facter.fact(:memoryfree).value.should == "641.25 MB" - end it "should return the current memory free in MB" do Facter.fact(:memoryfree_mb).value.should == "641.25" @@ -418,33 +328,17 @@ Facter.collection.loader.load(:memory) end - it "should return the current swap free" do - Facter.fact(:swapfree).value.should == "1023.96 MB" - end - it "should return the current swap free in MB" do Facter.fact(:swapfree_mb).value.should == "1023.96" end - it "should return the current swap size" do - Facter.fact(:swapsize).value.should == "1.95 GB" - end - it "should return the current swap size in MB" do Facter.fact(:swapsize_mb).value.should == "2000.53" end - it "should return the current memory size" do - Facter.fact(:memorysize).value.should == "1007.34 MB" - end - it "should return the current memory size in MB" do Facter.fact(:memorysize_mb).value.should == "1007.34" end - - it "should return the current memory free" do - Facter.fact(:memoryfree).value.should == "641.25 MB" - end it "should return the current memory free in MB" do Facter.fact(:memoryfree_mb).value.should == "641.25" @@ -462,33 +356,18 @@ Facter.collection.loader.load(:memory) end - it "should return the current swap free" do - Facter.fact(:swapfree).value.should == "2.00 GB" - end it "should return the current swap free in MB" do Facter.fact(:swapfree_mb).value.should == "2047.93" end - - it "should return the current swap size" do - Facter.fact(:swapsize).value.should == "4.86 GB" - end it "should return the current swap size in MB" do Facter.fact(:swapsize_mb).value.should == "4977.62" end - - it "should return the current memory size" do - Facter.fact(:memorysize).value.should == "1007.34 MB" - end it "should return the current memory size in MB" do Facter.fact(:memorysize_mb).value.should == "1007.34" end - - it "should return the current memory free" do - Facter.fact(:memoryfree).value.should == "641.25 MB" - end it "should return the current memory free in MB" do Facter.fact(:memoryfree_mb).value.should == "641.25" @@ -504,14 +383,6 @@ require 'facter/util/wmi' end - it "should return free memory" do - os = stubs 'os' - os.stubs(:FreePhysicalMemory).returns("3415624") - Facter::Util::WMI.stubs(:execquery).returns([os]) - - Facter.fact(:MemoryFree).value.should == '3.26 GB' - end - it "should return free memory in MB" do os = stubs 'os' os.stubs(:FreePhysicalMemory).returns("3415624") @@ -520,14 +391,6 @@ Facter.fact(:memoryfree_mb).value.should == '3335.57' end - it "should return total memory" do - computer = stubs 'computer' - computer.stubs(:TotalPhysicalMemory).returns("4193837056") - Facter::Util::WMI.stubs(:execquery).returns([computer]) - - Facter.fact(:MemorySize).value.should == '3.91 GB' - end - it "should return total memory in MB" do computer = stubs 'computer' computer.stubs(:TotalPhysicalMemory).returns("4193837056") From 35067dc79841d6d896bf75760bc79bba3067c115 Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Fri, 8 Jun 2012 10:27:20 -0700 Subject: [PATCH 0859/3753] Bump Facter epoch to 1 This commit bumps the facter epoch to 1. This is to address the errant release of a facter 2.0rc to the Puppet Labs yum production repository, which may have been then installed unintentionally by its users. The epoch bump is in tandem with equivalent changes to Facter 1.6.9 and 2.0.0rc4. This will ensure users who intentionally installed 1.6.10 rc/ 2.0 rc keep it, while users of 1.6.9 who unintentionally installed 2.0rc are taken back to 1.6.9. Signed-off-by: Moses Mendoza --- conf/redhat/facter.spec | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index 3cdb7f88bf..9284e4773f 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -4,7 +4,8 @@ Summary: Ruby module for collecting simple facts about a host operating system Name: facter Version: 1.6.10 #Release: 1%{?dist} -Release: 0.1rc1%{?dist} +Release: 0.1rc1.2%{?dist} +Epoch: 1 License: Apache 2.0 Group: System Environment/Base URL: http://www.puppetlabs.com/puppet/related-projects/%{name} @@ -53,6 +54,9 @@ rm -rf %{buildroot} %changelog +* Fri Jun 8 2012 Moses Mendoza - 1.6.10-0.1rc1.2 +- Bump epoch to 1 to address errant Facter 2.0 rc release + * Wed Jun 6 2012 Moses Mendoza - 1.6.10-0.1rc1 - Update for 1.6.10rc1 From 1865953f321e5b5a8514280ecedd74545b390a7e Mon Sep 17 00:00:00 2001 From: rlinehan Date: Fri, 8 Jun 2012 17:02:23 -0700 Subject: [PATCH 0860/3753] (#2066) Refactor swap facts Ensure swap size and swap free are evaluated on fact resolution rather than at load time. Refactor facts to remove similar code. --- lib/facter/memory.rb | 114 ++++++-------------------------------- lib/facter/util/memory.rb | 85 +++++++++++++++++++++++++++- spec/unit/memory_spec.rb | 2 +- 3 files changed, 100 insertions(+), 101 deletions(-) diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb index 134f3fdbe7..dc64ee0179 100644 --- a/lib/facter/memory.rb +++ b/lib/facter/memory.rb @@ -52,6 +52,21 @@ end end +Facter.add("swapsize_mb") do + setcode do + swaptotal = Facter::Memory.swap_size + "%.2f" % [swaptotal] + end +end + +Facter.add("swapfree_mb") do + setcode do + swapfree = Facter::Memory.swap_free + "%.2f" % [swapfree] + end +end + + if Facter.value(:kernel) == "AIX" and Facter.value(:id) == "root" swap = Facter::Util::Resolution.exec('swap -l') swapfree, swaptotal = 0, 0 @@ -78,13 +93,6 @@ end if Facter.value(:kernel) == "OpenBSD" - swap = Facter::Util::Resolution.exec('swapctl -s') - swapfree, swaptotal = 0, 0 - if swap =~ /^total: (\d+)k bytes allocated = \d+k used, (\d+)k available$/ - swaptotal = $1.to_i - swapfree = $2.to_i - end - Facter.add("memorysize_mb") do confine :kernel => :openbsd memtotal = Facter::Util::Resolution.exec("sysctl hw.physmem | cut -d'=' -f2") @@ -99,50 +107,9 @@ Facter::Memory.vmstat_find_free_memory() end end - - Facter.add("swapsize_mb") do - confine :kernel => :openbsd - setcode do - "%.2f" % [swaptotal.to_f / 1024.0] - end - end - - Facter.add("swapfree_mb") do - confine :kernel => :openbsd - setcode do - "%.2f" % [swapfree.to_f / 1024.0] - end - end end if Facter.value(:kernel) == "FreeBSD" - swap = Facter::Util::Resolution.exec('swapinfo -k') - swapfree, swaptotal = 0, 0 - unless swap.nil? - swap.each_line do |device| - # Parse the line: - # /dev/foo SIZE USAGE AVAILABLE PERCENTAGE% - if device =~ /\S+\s+(\d+)\s+\d+\s+(\d+)\s+\d+%$/ - swaptotal += $1.to_i - swapfree += $2.to_i - end - end - end - - Facter.add("swapsize_mb") do - confine :kernel => :freebsd - setcode do - "%.2f" % [swaptotal.to_f / 1024.0] - end - end - - Facter.add("swapfree_mb") do - confine :kernel => :freebsd - setcode do - "%.2f" % [swapfree.to_f / 1024.0] - end - end - Facter.add("memorysize_mb") do confine :kernel => :freebsd memtotal = Facter::Util::Resolution.exec("sysctl -n hw.physmem") @@ -164,31 +131,6 @@ end if Facter.value(:kernel) == "Darwin" - swap = Facter::Util::Resolution.exec('sysctl vm.swapusage') - swapfree, swaptotal = 0, 0 - unless swap.empty? - # Parse the line: - # vm.swapusage: total = 128.00M used = 0.37M free = 127.63M (encrypted) - if swap =~ /total\s=\s(\S+)M\s+used\s=\s(\S+)M\s+free\s=\s(\S+)M\s/ - swaptotal += $1.to_i - swapfree += $3.to_i - end - end - - Facter.add("swapsize_mb") do - confine :kernel => :Darwin - setcode do - "%.2f" % [swaptotal.to_f] - end - end - - Facter.add("swapfree_mb") do - confine :kernel => :Darwin - setcode do - "%.2f" % [swapfree.to_f] - end - end - Facter.add("memorysize_mb") do confine :kernel => :Darwin @@ -218,31 +160,6 @@ end if Facter.value(:kernel) == "SunOS" - swap = Facter::Util::Resolution.exec('/usr/sbin/swap -l') - swapfree, swaptotal = 0, 0 - unless swap.nil? - swap.each_line do |dev| - if dev =~ /^\/\S+\s.*\s+(\d+)\s+(\d+)$/ - swaptotal += $1.to_i / 2 - swapfree += $2.to_i / 2 - end - end - end - - Facter.add("swapsize_mb") do - confine :kernel => :sunos - setcode do - "%.2f" % [swaptotal.to_f / 1024.0] - end - end - - Facter.add("swapfree_mb") do - confine :kernel => :sunos - setcode do - "%.2f" % [swapfree.to_f / 1024.0] - end - end - # Total memory size available from prtconf pconf = Facter::Util::Resolution.exec('/usr/sbin/prtconf 2>/dev/null') phymem = "" @@ -298,6 +215,7 @@ Facter.add("swapsize_mb") do confine :kernel => :dragonfly setcode do +# swaptotal = Facter::Memory.swap_total_dragonfly() page_size = Facter::Util::Resolution.exec("/sbin/sysctl -n hw.pagesize").to_f swaptotal = Facter::Util::Resolution.exec("/sbin/sysctl -n vm.swap_size").to_f * page_size "%.2f" % [(swaptotal.to_f / 1024.0) / 1024.0] diff --git a/lib/facter/util/memory.rb b/lib/facter/util/memory.rb index 107e9de619..6107877518 100644 --- a/lib/facter/util/memory.rb +++ b/lib/facter/util/memory.rb @@ -33,7 +33,7 @@ def self.scale_number(size, multiplier) s = suffixes.shift end - return "%.2f %s" % [size, s] + "%.2f %s" % [size, s] end def self.vmstat_find_free_memory(args = []) @@ -42,7 +42,7 @@ def self.vmstat_find_free_memory(args = []) row = Facter::Util::Resolution.exec(cmd).split("\n")[-1] if row =~ /^\s*\d+\s*\d+\s*\d+\s*\d+\s*(\d+)/ memfree = $1 - return "%.2f" % [memfree.to_f / 1024.0] + "%.2f" % [memfree.to_f / 1024.0] end end @@ -71,4 +71,85 @@ def self.vmstat_darwin_find_free_memory() return ( memfree + memspecfree ) * pagesize end + + def self.swap_size(kernel = Facter.value(:kernel)) + output = swap_info(kernel) + parse_swap output, kernel, :size + end + + def self.swap_free(kernel = Facter.value(:kernel)) + output = swap_info(kernel) + parse_swap output, kernel, :free + end + + def self.swap_info(kernel = Facter.value(:kernel)) + case kernel + when /OpenBSD/i + Facter::Util::Resolution.exec('swapctl -s') + when /FreeBSD/i + Facter::Util::Resolution.exec('swapinfo -k') + when /Darwin/i + Facter::Util::Resolution.exec('sysctl vm.swapusage') + when /SunOS/i + Facter::Util::Resolution.exec('/usr/sbin/swap -l') + end + end + + def self.parse_swap (output, kernel = Facter.value(:kernel), size_or_free = :size) + value_in_mb = 0.0 + value = 0 + is_size = size_or_free == :size + unless output.nil? + output.each_line do |line| + value += parse_line(line, kernel, is_size) + end + end + value_in_mb = scale_swap_value(value, kernel) + end + + # There is a lot of duplication here because of concern over being able to add + # new platforms in a reasonable manner. For all of these platforms the first + # regex corresponds to the swap size value and the second corresponds to the swap + # free value, but this may not always be the case. In Ruby 1.9.3 it is possible + # to give these names, but sadly 1.8.7 does not support this. + + def self.parse_line(line, kernel, is_size) + case kernel + when /OpenBSD/i + if line =~ /^total: (\d+)k bytes allocated = \d+k used, (\d+)k available$/ + (is_size) ? $1.to_i : $2.to_i + else + 0 + end + when /FreeBSD/i + if line =~ /\S+\s+(\d+)\s+\d+\s+(\d+)\s+\d+%$/ + (is_size) ? $1.to_i : $2.to_i + else + 0 + end + when /Darwin/i + if line =~ /total\s=\s(\S+)M\s+used\s=\s\S+M\s+free\s=\s(\S+)M\s/ + (is_size) ? $1.to_i : $2.to_i + else + 0 + end + when /SunOS/i + if line =~ /^\/\S+\s.*\s+(\d+)\s+(\d+)$/ + (is_size) ? $1.to_i : $2.to_i + else + 0 + end + end + end + + def self.scale_swap_value(value, kernel) + case kernel + when /OpenBSD/i, /FreeBSD/i + value.to_f / 1024.0 + when /SunOS/i + value.to_f / 2 / 1024.0 + else + value.to_f + end + end end diff --git a/spec/unit/memory_spec.rb b/spec/unit/memory_spec.rb index 7905c5adf1..3cd37236a7 100755 --- a/spec/unit/memory_spec.rb +++ b/spec/unit/memory_spec.rb @@ -224,6 +224,7 @@ Facter.fact(:swapsize_mb).value.should == "0.00" end end + end describe "on DragonFly BSD" do before :each do @@ -397,7 +398,6 @@ Facter::Util::WMI.stubs(:execquery).returns([computer]) Facter.fact(:memorysize_mb).value.should == '3999.55' - end end end end From a95d38c6b1a760f7fa93b639e9c6ab9a38130c70 Mon Sep 17 00:00:00 2001 From: rlinehan Date: Mon, 11 Jun 2012 14:17:50 -0700 Subject: [PATCH 0861/3753] (#2066) Add tests for AIX Add tests and refactor code for swap size and swap free for AIX. --- lib/facter/memory.rb | 32 ++------------------ lib/facter/util/memory.rb | 12 ++++++-- spec/unit/memory_spec.rb | 63 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 31 deletions(-) diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb index dc64ee0179..af7d41e314 100644 --- a/lib/facter/memory.rb +++ b/lib/facter/memory.rb @@ -47,7 +47,7 @@ Facter.add(fact) do setcode do name = Facter.fact(fact + "_mb").value - Facter::Memory.scale_number(name.to_f, "MB") + Facter::Memory.scale_number(name.to_f, "MB") if name end end end @@ -55,40 +55,14 @@ Facter.add("swapsize_mb") do setcode do swaptotal = Facter::Memory.swap_size - "%.2f" % [swaptotal] + "%.2f" % [swaptotal] if swaptotal end end Facter.add("swapfree_mb") do setcode do swapfree = Facter::Memory.swap_free - "%.2f" % [swapfree] - end -end - - -if Facter.value(:kernel) == "AIX" and Facter.value(:id) == "root" - swap = Facter::Util::Resolution.exec('swap -l') - swapfree, swaptotal = 0, 0 - swap.each_line do |dev| - if dev =~ /^\/\S+\s.*\s+(\S+)MB\s+(\S+)MB/ - swaptotal += $1.to_i - swapfree += $2.to_i - end - end - - Facter.add("swapsize_mb") do - confine :kernel => :aix - setcode do - "%.2f" % [swaptotal.to_f] - end - end - - Facter.add("swapfree_mb") do - confine :kernel => :aix - setcode do - "%.2f" % [swapfree.to_f] - end + "%.2f" % [swapfree] if swapfree end end diff --git a/lib/facter/util/memory.rb b/lib/facter/util/memory.rb index 6107877518..05adcf57b2 100644 --- a/lib/facter/util/memory.rb +++ b/lib/facter/util/memory.rb @@ -74,16 +74,18 @@ def self.vmstat_darwin_find_free_memory() def self.swap_size(kernel = Facter.value(:kernel)) output = swap_info(kernel) - parse_swap output, kernel, :size + parse_swap output, kernel, :size if output end def self.swap_free(kernel = Facter.value(:kernel)) output = swap_info(kernel) - parse_swap output, kernel, :free + parse_swap output, kernel, :free if output end def self.swap_info(kernel = Facter.value(:kernel)) case kernel + when /AIX/i + (Facter.value(:id) == "root") ? Facter::Util::Resolution.exec('swap -l') : nil when /OpenBSD/i Facter::Util::Resolution.exec('swapctl -s') when /FreeBSD/i @@ -115,6 +117,12 @@ def self.parse_swap (output, kernel = Facter.value(:kernel), size_or_free = :siz def self.parse_line(line, kernel, is_size) case kernel + when /AIX/i + if line =~ /^\/\S+\s.*\s+(\S+)MB\s+(\S+)MB/ + (is_size) ? $1.to_i : $2.to_i + else + 0 + end when /OpenBSD/i if line =~ /^total: (\d+)k bytes allocated = \d+k used, (\d+)k available$/ (is_size) ? $1.to_i : $2.to_i diff --git a/spec/unit/memory_spec.rb b/spec/unit/memory_spec.rb index 3cd37236a7..8865424346 100755 --- a/spec/unit/memory_spec.rb +++ b/spec/unit/memory_spec.rb @@ -18,6 +18,16 @@ "swapfree" ].each do |fact| + describe "when #{fact}_mb does not exist" do + before(:each) do + Facter.fact(fact + "_mb").stubs(:value).returns(nil) + end + + it "#{fact} should not exist either" do + Facter.fact(fact).value.should be_nil + end + end + { "200.00" => "200.00 MB", "1536.00" => "1.50 GB", "1572864.00" => "1.50 TB", @@ -28,6 +38,10 @@ end end end + + after(:each) do + Facter.clear + end end describe "on Darwin" do @@ -80,6 +94,55 @@ end end + describe "on AIX" do + before (:each) do + Facter.clear + Facter.fact(:kernel).stubs(:value).returns("AIX") + + swapusage = < Date: Tue, 12 Jun 2012 09:49:25 -0700 Subject: [PATCH 0862/3753] (#2066) Refactor memory size and memory free facts Add methods to util/memory.rb to retrieve memory size and memory free based on kernel type. Remove fact definitions specified for each platform that were identical, and make non-kernel specific facts when possible. This removes a lot of similar code from lib/facter/memory.rb and makes the fact definitions more readable. --- lib/facter/memory.rb | 98 +++++++-------------------------------- lib/facter/util/memory.rb | 60 ++++++++++++++++++++++-- 2 files changed, 72 insertions(+), 86 deletions(-) diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb index af7d41e314..144f0e0ef5 100644 --- a/lib/facter/memory.rb +++ b/lib/facter/memory.rb @@ -66,62 +66,21 @@ end end -if Facter.value(:kernel) == "OpenBSD" - Facter.add("memorysize_mb") do - confine :kernel => :openbsd - memtotal = Facter::Util::Resolution.exec("sysctl hw.physmem | cut -d'=' -f2") - setcode do - "%.2f" % [(memtotal.to_f / 1024.0) / 1024.0] - end - end - - Facter.add("memoryfree_mb") do - confine :kernel => :openbsd - setcode do - Facter::Memory.vmstat_find_free_memory() - end +Facter.add("memorysize_mb") do + setcode do + memtotal = Facter::Memory.mem_size + "%.2f" % [memtotal] if memtotal end end -if Facter.value(:kernel) == "FreeBSD" - Facter.add("memorysize_mb") do - confine :kernel => :freebsd - memtotal = Facter::Util::Resolution.exec("sysctl -n hw.physmem") - setcode do - "%.2f" % [(memtotal.to_f / 1024.0) / 1024.0] - end - end - - # FreeBSD had to be different and be default prints human readable - # format instead of machine readable. So using 'vmstat -H' instead - # Facter::Memory.vmstat_find_free_memory() - - Facter.add("memoryfree_mb") do - confine :kernel => :freebsd - setcode do - Facter::Memory.vmstat_find_free_memory(["-H"]) - end +Facter.add("memoryfree_mb") do + setcode do + memfree = Facter::Memory.mem_free + "%.2f" % [memfree] if memfree end end if Facter.value(:kernel) == "Darwin" - - Facter.add("memorysize_mb") do - confine :kernel => :Darwin - memtotal = Facter::Util::Resolution.exec("sysctl -n hw.memsize") - setcode do - "%.2f" % [(memtotal.to_f / 1024.0) / 1024.0] - end - end - - Facter.add("memoryfree_mb") do - confine :kernel => :Darwin - freemem = Facter::Memory.vmstat_darwin_find_free_memory() - setcode do - "%.2f" % [(freemem.to_f / 1024.0) / 1024.0] - end - end - Facter.add("SwapEncrypted") do confine :kernel => :Darwin setcode do @@ -134,26 +93,19 @@ end if Facter.value(:kernel) == "SunOS" - # Total memory size available from prtconf - pconf = Facter::Util::Resolution.exec('/usr/sbin/prtconf 2>/dev/null') - phymem = "" - pconf.each_line do |line| - if line =~ /^Memory size:\s+(\d+) Megabytes/ - phymem = $1 - end - end Facter.add("memorysize_mb") do confine :kernel => :sunos - setcode do - "%.2f" % [phymem.to_f] + # Total memory size available from prtconf + pconf = Facter::Util::Resolution.exec('/usr/sbin/prtconf 2>/dev/null') + phymem = "" + pconf.each_line do |line| + if line =~ /^Memory size:\s+(\d+) Megabytes/ + phymem = $1 + end end - end - - Facter.add("memoryfree_mb") do - confine :kernel => :sunos setcode do - Facter::Memory.vmstat_find_free_memory() + "%.2f" % [phymem.to_f] end end end @@ -189,7 +141,6 @@ Facter.add("swapsize_mb") do confine :kernel => :dragonfly setcode do -# swaptotal = Facter::Memory.swap_total_dragonfly() page_size = Facter::Util::Resolution.exec("/sbin/sysctl -n hw.pagesize").to_f swaptotal = Facter::Util::Resolution.exec("/sbin/sysctl -n vm.swap_size").to_f * page_size "%.2f" % [(swaptotal.to_f / 1024.0) / 1024.0] @@ -207,20 +158,3 @@ "%.2f" % [(swapfree.to_f / 1024.0) / 1024.0] end end - -Facter.add("memorysize_mb") do - confine :kernel => :dragonfly - setcode do - memtotal = Facter::Util::Resolution.exec("sysctl -n hw.physmem") - "%.2f" % [(memtotal.to_f / 1024.0) / 1024.0] - end -end - -if Facter.value(:kernel) == "dragonfly" - Facter.add("memoryfree_mb") do - confine :kernel => :dragonfly - setcode do - Facter::Memory.vmstat_find_free_memory() - end - end -end diff --git a/lib/facter/util/memory.rb b/lib/facter/util/memory.rb index 05adcf57b2..f427a2234f 100644 --- a/lib/facter/util/memory.rb +++ b/lib/facter/util/memory.rb @@ -15,9 +15,8 @@ def self.meminfo_number(tag) size += $1.to_f end end - memsize = scale_number(size, scale) - memsize + memsize = scale_number(size, scale) end def self.scale_number(size, multiplier) @@ -42,7 +41,6 @@ def self.vmstat_find_free_memory(args = []) row = Facter::Util::Resolution.exec(cmd).split("\n")[-1] if row =~ /^\s*\d+\s*\d+\s*\d+\s*\d+\s*(\d+)/ memfree = $1 - "%.2f" % [memfree.to_f / 1024.0] end end @@ -69,7 +67,61 @@ def self.vmstat_darwin_find_free_memory() end end - return ( memfree + memspecfree ) * pagesize + freemem = ( memfree + memspecfree ) * pagesize + end + + def self.mem_free(kernel = Facter.value(:kernel)) + output = mem_free_info(kernel) + scale_mem_free_value output, kernel + end + + def self.mem_free_info(kernel = Facter.value(:kernel)) + case kernel + when /OpenBSD/i, /SunOS/i, /Dragonfly/i + vmstat_find_free_memory() + when /FreeBSD/i + vmstat_find_free_memory(["-H"]) + when /Darwin/i + vmstat_darwin_find_free_memory() + end + end + + def self.scale_mem_free_value (value, kernel) + case kernel + when /OpenBSD/i, /FreeBSD/i, /SunOS/i, /Dragonfly/i + value.to_f / 1024.0 + when /Darwin/i + value.to_f / 1024.0 / 1024.0 + else + value.to_f + end + end + + def self.mem_size(kernel = Facter.value(:kernel)) + output = mem_size_info(kernel) + scale_mem_size_value output, kernel + end + + def self.mem_size_info(kernel = Facter.value(:kernel)) + case kernel + when /OpenBSD/i + Facter::Util::Resolution.exec("sysctl hw.physmem | cut -d'=' -f2") + when /FreeBSD/i + Facter::Util::Resolution.exec("sysctl -n hw.physmem") + when /Darwin/i + Facter::Util::Resolution.exec("sysctl -n hw.memsize") + when /Dragonfly/i + Facter::Util::Resolution.exec("sysctl -n hw.physmem") + end + end + + def self.scale_mem_size_value(value, kernel) + case kernel + when /OpenBSD/i, /FreeBSD/i, /Darwin/i, /Dragonfly/i + value.to_f / 1024.0 / 1024.0 + else + value.to_f + end end def self.swap_size(kernel = Facter.value(:kernel)) From f63dc762392d35b06540da82b3b20dafa23b3922 Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Wed, 13 Jun 2012 17:00:03 -0700 Subject: [PATCH 0863/3753] Update CHANGELOG, facter.spec for 1.6.10 --- CHANGELOG | 3 ++- conf/redhat/facter.spec | 19 +++++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 63f1742196..06025273fd 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,6 @@ -1.6.10rc1 +1.6.10 === +35067dc Bump Facter epoch to 1 d6a3e91 Make package task depend on tar in Rakfile f42896d (#14764) Stub architecture fact when Windows facts run on Linux f44ca52 (maint) Fix hardware model fact for ruby 1.9 diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index 9284e4773f..4ee477f56f 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -3,16 +3,16 @@ Summary: Ruby module for collecting simple facts about a host operating system Name: facter Version: 1.6.10 -#Release: 1%{?dist} -Release: 0.1rc1.2%{?dist} +Release: 1%{?dist} +#Release: 0.1rc1.2%{?dist} Epoch: 1 License: Apache 2.0 Group: System Environment/Base URL: http://www.puppetlabs.com/puppet/related-projects/%{name} -Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc1.tar.gz -#Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}.tar.gz -Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc1.tar.gz.asc -#Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}.tar.gz.asc +#Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc1.tar.gz +Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}.tar.gz +#Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc1.tar.gz.asc +Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}.tar.gz.asc BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) @@ -32,8 +32,8 @@ system. Some of the facts are preconfigured, such as the hostname and the operating system. Additional facts can be added through simple Ruby scripts %prep -#%setup -q -n %{name}-%{version} -%setup -q -n %{name}-%{version}rc1 +%setup -q -n %{name}-%{version} +#%setup -q -n %{name}-%{version}rc1 %build @@ -54,6 +54,9 @@ rm -rf %{buildroot} %changelog +* Wed Jun 13 2012 Moses Mendoza - 1.6.10-1 +- Update for 1.6.10 + * Fri Jun 8 2012 Moses Mendoza - 1.6.10-0.1rc1.2 - Bump epoch to 1 to address errant Facter 2.0 rc release From 38b6ecf310982fc42e8f6c0509ccaf5142aa2191 Mon Sep 17 00:00:00 2001 From: rlinehan Date: Thu, 14 Jun 2012 16:58:29 -0700 Subject: [PATCH 0864/3753] (#2066) Fix facts for Linux/kFreeBSD and add tests Make swap and memory facts in MB for Linux/kFreeBSD, as was previously done for other platforms. Also make scaled facts depend on MB versions. This should fix the problems encountered in the Jenkins test of the previously-submitted code. Add tests for memory and swap facts for Linux and kFreeBSD. --- lib/facter/memory.rb | 28 +++++++++++----------- lib/facter/util/memory.rb | 15 +++++------- spec/unit/memory_spec.rb | 49 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 22 deletions(-) diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb index 144f0e0ef5..39935ab274 100644 --- a/lib/facter/memory.rb +++ b/lib/facter/memory.rb @@ -24,20 +24,8 @@ # Author: Matthew Palmer # # -require 'facter/util/memory' -{ :MemorySize => "MemTotal", - :MemoryFree => "MemFree", - :SwapSize => "SwapTotal", - :SwapFree => "SwapFree" -}.each do |fact, name| - Facter.add(fact) do - confine :kernel => [ :linux, :"gnu/kfreebsd" ] - setcode do - Facter::Memory.meminfo_number(name) - end - end -end +require 'facter/util/memory' [ "memorysize", "memoryfree", @@ -80,6 +68,20 @@ end end +{ :memorysize_mb => "MemTotal", + :memoryfree_mb => "MemFree", + :swapsize_mb => "SwapTotal", + :swapfree_mb => "SwapFree" +}.each do |fact, name| + Facter.add(fact) do + confine :kernel => [ :linux, :"gnu/kfreebsd" ] + meminfo = Facter::Memory.meminfo_number(name) + setcode do + "%.2f" % [meminfo] + end + end +end + if Facter.value(:kernel) == "Darwin" Facter.add("SwapEncrypted") do confine :kernel => :Darwin diff --git a/lib/facter/util/memory.rb b/lib/facter/util/memory.rb index f427a2234f..7c63dd0992 100644 --- a/lib/facter/util/memory.rb +++ b/lib/facter/util/memory.rb @@ -5,18 +5,15 @@ module Facter::Memory def self.meminfo_number(tag) memsize = "" - size, scale = [0, ""] + size = [0] File.readlines("/proc/meminfo").each do |l| - size, scale = [$1.to_f, $2] if l =~ /^#{tag}:\s+(\d+)\s+(\S+)/ - # MemoryFree == memfree + cached + buffers - # (assume scales are all the same as memfree) + size = $1.to_f if l =~ /^#{tag}:\s+(\d+)\s+\S+/ if tag == "MemFree" && - l =~ /^(?:Buffers|Cached):\s+(\d+)\s+(?:\S+)/ + l =~ /^(?:Buffers|Cached):\s+(\d+)\s+\S+/ size += $1.to_f end end - - memsize = scale_number(size, scale) + size / 1024.0 end def self.scale_number(size, multiplier) @@ -155,7 +152,7 @@ def self.parse_swap (output, kernel = Facter.value(:kernel), size_or_free = :siz is_size = size_or_free == :size unless output.nil? output.each_line do |line| - value += parse_line(line, kernel, is_size) + value += parse_swap_line(line, kernel, is_size) end end value_in_mb = scale_swap_value(value, kernel) @@ -167,7 +164,7 @@ def self.parse_swap (output, kernel = Facter.value(:kernel), size_or_free = :siz # free value, but this may not always be the case. In Ruby 1.9.3 it is possible # to give these names, but sadly 1.8.7 does not support this. - def self.parse_line(line, kernel, is_size) + def self.parse_swap_line(line, kernel, is_size) case kernel when /AIX/i if line =~ /^\/\S+\s.*\s+(\S+)MB\s+(\S+)MB/ diff --git a/spec/unit/memory_spec.rb b/spec/unit/memory_spec.rb index 8865424346..4e9b2fcdf0 100755 --- a/spec/unit/memory_spec.rb +++ b/spec/unit/memory_spec.rb @@ -94,6 +94,55 @@ end end + [ "linux", + "gnu/kfreebsd", + ].each do |kernel| + + + describe "on #{kernel}" do + before(:each) do + Facter.clear + Facter.fact(:kernel).stubs(:value).returns(kernel) + meminfo = < Date: Thu, 2 Feb 2012 16:11:16 -0800 Subject: [PATCH 0865/3753] (#2157) Add external fact support Adds in the ability to load and parse external facts from /etc/facts.d Initial implementation from RI Pienaar and improvements by Luke Kanies --- lib/facter/util/directory_loader.rb | 43 +++++++++ lib/facter/util/loader.rb | 6 ++ lib/facter/util/parser.rb | 119 +++++++++++++++++++++++ spec/unit/util/directory_loader_spec.rb | 91 ++++++++++++++++++ spec/unit/util/loader_spec.rb | 51 +++++----- spec/unit/util/parser_spec.rb | 120 ++++++++++++++++++++++++ 6 files changed, 408 insertions(+), 22 deletions(-) create mode 100644 lib/facter/util/directory_loader.rb create mode 100644 lib/facter/util/parser.rb create mode 100644 spec/unit/util/directory_loader_spec.rb create mode 100644 spec/unit/util/parser_spec.rb diff --git a/lib/facter/util/directory_loader.rb b/lib/facter/util/directory_loader.rb new file mode 100644 index 0000000000..eda0e22ab2 --- /dev/null +++ b/lib/facter/util/directory_loader.rb @@ -0,0 +1,43 @@ +# A Facter plugin that loads facts from /etc/facter/facts.d. +# +# Facts can be in the form of JSON, YAML or Text files +# and any executable that returns key=value pairs. + +require 'facter/util/parser' + +class Facter::Util::DirectoryLoader + require 'yaml' + + SKIP_EXTENSIONS = %w{bak orig} + + attr_reader :directory + + def initialize(dir="/etc/facter/facts.d") + @directory = dir + end + + def entries + Dir.entries(directory).find_all{|f| parse?(f) }.sort.map {|f| File.join(directory, f) } + rescue + [] + end + + def load + entries.each do |file| + unless data = Facter::Util::Parser.new(file).results + raise "Could not interpret fact file #{file}" + end + + data.each { |p,v| Facter.add(p, :value => v) } + end + end + + private + + def parse?(file) + return false if file =~ /^\./ + ext = file.split(".")[-1] + return false if SKIP_EXTENSIONS.include?(ext) + true + end +end diff --git a/lib/facter/util/loader.rb b/lib/facter/util/loader.rb index 99fe798817..4a02d466b2 100644 --- a/lib/facter/util/loader.rb +++ b/lib/facter/util/loader.rb @@ -1,12 +1,16 @@ require 'facter' require 'pathname' +require 'facter/util/directory_loader' # Load facts on demand. class Facter::Util::Loader + attr_reader :directory_loader + def initialize @loaded = [] @valid_path = {} + @directory_loader = Facter::Util::DirectoryLoader.new end # Load all resolutions for a single fact. @@ -34,6 +38,8 @@ def load_all load_env + directory_loader.load + search_path.each do |dir| next unless FileTest.directory?(dir) diff --git a/lib/facter/util/parser.rb b/lib/facter/util/parser.rb new file mode 100644 index 0000000000..a5dee0d751 --- /dev/null +++ b/lib/facter/util/parser.rb @@ -0,0 +1,119 @@ +class Facter::Util::Parser + attr_reader :filename + + class << self + # Retrieve the set extension, if any + attr_reader :extension + end + + # Register the extension that this parser matches. + def self.matches_extension(ext) + @extension = ext + end + + def self.file_extension(filename) + File.extname(filename).sub(".", '') + end + + def self.inherited(klass) + @subclasses ||= [] + @subclasses << klass + end + + def self.matches?(filename) + raise "Must override the 'matches?' method for #{self}" unless extension + + file_extension(filename) == extension + end + + def self.subclasses + @subclasses ||= [] + @subclasses + end + + def self.which_parser(filename) + unless klass = subclasses.detect {|k| k.matches?(filename) } + raise ArgumentError, "Could not find parser for #{filename}" + end + klass + end + + def self.new(filename) + klass = which_parser(filename) + + object = klass.allocate + object.send(:initialize, filename) + + object + end + + def initialize(filename) + @filename = filename + end + + class YamlParser < self + matches_extension "yaml" + + def results + require 'yaml' + + YAML.load_file(filename) + rescue Exception => e + Facter.warn("Failed to handle #{filename} as yaml facts: #{e.class}: #{e}") + end + end + + class TextParser < self + matches_extension "txt" + + def results + result = {} + File.readlines(filename).each do |line| + + if line.chomp =~ /^(.+)=(.+)$/ + result[$1] = $2 + end + end + result + rescue Exception => e + Facter.warn("Failed to handle #{filename} as text facts: #{e.class}: #{e}") + end + end + + class JsonParser < self + matches_extension "json" + + def results + begin + require 'json' + rescue LoadError + require 'rubygems' + retry + end + + JSON.load(File.read(filename)) + end + end + + class ScriptParser < self + def self.matches?(file) + File.executable?(file) + end + + def results + output = Facter::Util::Resolution.exec(filename) + + result = {} + output.split("\n").each do |line| + if line =~ /^(.+)=(.+)$/ + result[$1] = $2 + end + end + + result + rescue Exception => e + Facter.warn("Failed to handle #{filename} as script facts: #{e.class}: #{e}") + Facter.debug(e.backtrace.join("\n\t")) + end + end +end diff --git a/spec/unit/util/directory_loader_spec.rb b/spec/unit/util/directory_loader_spec.rb new file mode 100644 index 0000000000..fa88660449 --- /dev/null +++ b/spec/unit/util/directory_loader_spec.rb @@ -0,0 +1,91 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') + +require 'facter/util/directory_loader' +require 'tempfile' + +describe Facter::Util::DirectoryLoader do + subject { Facter::Util::DirectoryLoader.new("/my/dir.d") } + + def mk_test_dir + file = Tempfile.new "testing_fact_loading_dir" + @dir = file.path + file.delete + + Dir.mkdir(@dir) + @dirs << @dir # for cleanup + + @dir + end + + def mk_test_file + file = Tempfile.new "testing_fact_loading_file" + @filename = file.path + file.delete + @files << @filename # for cleanup + + @filename + end + + before { + @files = [] + @dirs = [] + @loader = Facter::Util::DirectoryLoader.new(mk_test_dir) + } + + after do + @files.each do |file| + File.unlink(file) if File.exist?(file) + end + @dirs.each do |dir| + FileUtils.rm_f(dir) if File.exist?(dir) + end + end + + it "should make the directory available" do + @loader.directory.should be_instance_of(String) + end + + it "should default to '/etc/facter/facts.d' for the directory" do + Facter::Util::DirectoryLoader.new.directory.should == "/etc/facter/facts.d" + end + + describe "when loading facts from disk" do + it "should be able to load files from disk and set facts" do + data = {"f1" => "one", "f2" => "two"} + file = File.join(@loader.directory, "data" + ".yaml") + File.open(file, "w") { |f| f.print YAML.dump(data) } + + @loader.load + + Facter.value("f1").should == "one" + Facter.value("f2").should == "two" + end + + it "should ignore files that begin with '.'" do + file = File.join(@loader.directory, ".data.yaml") + data = {"f1" => "one", "f2" => "two"} + File.open(file, "w") { |f| f.print YAML.dump(data) } + + @loader.load + Facter.value("f1").should be_nil + end + + %w{bak orig}.each do |ext| + it "should ignore files with an extension of '#{ext}'" do + file = File.join(@loader.directory, "data" + ".#{ext}") + File.open(file, "w") { |f| f.print "foo=bar" } + + @loader.load + end + end + + it "should fail when trying to parse unknown file types" do + file = File.join(@loader.directory, "file.unknownfiletype") + File.open(file, "w") { |f| f.print "stuff=bar" } + + lambda { @loader.load }.should raise_error(ArgumentError) + end + end +end diff --git a/spec/unit/util/loader_spec.rb b/spec/unit/util/loader_spec.rb index 75975672de..0c9e974bc8 100755 --- a/spec/unit/util/loader_spec.rb +++ b/spec/unit/util/loader_spec.rb @@ -176,21 +176,21 @@ def load_file(file) end it 'should load any ruby files in directories matching the fact name in the search path in sorted order regardless of the order returned by Dir.entries' do - @loader = TestLoader.new + @loader = TestLoader.new - @loader.stubs(:search_path).returns %w{/one/dir} - FileTest.stubs(:exist?).returns false - FileTest.stubs(:directory?).with("/one/dir/testing").returns true - @loader.stubs(:search_path).returns %w{/one/dir} + @loader.stubs(:search_path).returns %w{/one/dir} + FileTest.stubs(:exist?).returns false + FileTest.stubs(:directory?).with("/one/dir/testing").returns true + @loader.stubs(:search_path).returns %w{/one/dir} - Dir.stubs(:entries).with("/one/dir/testing").returns %w{foo.rb bar.rb} - %w{/one/dir/testing/foo.rb /one/dir/testing/bar.rb}.each do |f| - File.stubs(:directory?).with(f).returns false - Kernel.stubs(:load).with(f) - end + Dir.stubs(:entries).with("/one/dir/testing").returns %w{foo.rb bar.rb} + %w{/one/dir/testing/foo.rb /one/dir/testing/bar.rb}.each do |f| + File.stubs(:directory?).with(f).returns false + Kernel.stubs(:load).with(f) + end - @loader.load(:testing) - @loader.loaded_files.should == %w{/one/dir/testing/bar.rb /one/dir/testing/foo.rb} + @loader.load(:testing) + @loader.loaded_files.should == %w{/one/dir/testing/bar.rb /one/dir/testing/foo.rb} end it "should load any ruby files in directories matching the fact name in the search path" do @@ -221,11 +221,17 @@ def load_file(file) describe "when loading all facts" do before do @loader = Facter::Util::Loader.new + @loader.directory_loader.stubs(:load) @loader.stubs(:search_path).returns [] FileTest.stubs(:directory?).returns true end + it "should load all facts from the directory loader" do + @loader.directory_loader.expects(:load) + @loader.load_all + end + it "should skip directories that do not exist" do @loader.expects(:search_path).returns %w{/one/dir} @@ -264,21 +270,22 @@ def load_file(file) end it 'should load all files in sorted order for any given directory regardless of the order returned by Dir.entries' do - @loader = TestLoader.new + @loader = TestLoader.new - @loader.stubs(:search_path).returns %w{/one/dir} - Dir.stubs(:entries).with("/one/dir").returns %w{foo.rb bar.rb} + @loader.stubs(:search_path).returns %w{/one/dir} + Dir.stubs(:entries).with("/one/dir").returns %w{foo.rb bar.rb} - %w{/one/dir}.each { |f| File.stubs(:directory?).with(f).returns true } + %w{/one/dir}.each { |f| File.stubs(:directory?).with(f).returns true } - %w{/one/dir/foo.rb /one/dir/bar.rb}.each do |f| - File.stubs(:directory?).with(f).returns false - Kernel.expects(:load).with(f) - end + %w{/one/dir/foo.rb /one/dir/bar.rb}.each do |f| + File.stubs(:directory?).with(f).returns false + Kernel.expects(:load).with(f) + end - @loader.load_all + @loader.directory_loader.stubs(:load) + @loader.load_all - @loader.loaded_files.should == %w{/one/dir/bar.rb /one/dir/foo.rb} + @loader.loaded_files.should == %w{/one/dir/bar.rb /one/dir/foo.rb} end it "should not load files in the util subdirectory" do diff --git a/spec/unit/util/parser_spec.rb b/spec/unit/util/parser_spec.rb new file mode 100644 index 0000000000..2f06176a07 --- /dev/null +++ b/spec/unit/util/parser_spec.rb @@ -0,0 +1,120 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') + +require 'facter/util/parser' +require 'tempfile' +require 'json' + +describe Facter::Util::Parser do + def mk_test_file + file = Tempfile.new "testing_fact_parser" + @filename = file.path + file.delete + @files << @filename # for cleanup + + @filename + end + + before { + @files = [] + } + + after do + @files.each do |file| + File.unlink(file) if File.exist?(file) + end + end + + it "should fail when asked to parse a file type it does not support" do + lambda { Facter::Util::Parser.new("/my/file.foobar") }.should raise_error(ArgumentError) + end + + describe "yaml" do + subject { Facter::Util::Parser::YamlParser } + it "should match the 'yaml' extension" do + subject.extension.should == "yaml" + end + + it "should return a hash of whatever is stored on disk" do + file = mk_test_file + ".yaml" + + data = {"one" => "two", "three" => "four"} + + File.open(file, "w") { |f| f.print YAML.dump(data) } + + Facter::Util::Parser.new(file).results.should == data + end + + it "should handle exceptions and warn" do + file = mk_test_file + ".yaml" + + data = {"one" => "two", "three" => "four"} + + File.open(file, "w") { |f| f.print "}" } + Facter.expects(:warn) + lambda { Facter::Util::Parser.new("/some/path/that/doesn't/exist.yaml").results }.should_not raise_error + end + end + + describe "json" do + subject { Facter::Util::Parser::JsonParser } + it "should match the 'json' extension" do + subject.extension.should == "json" + end + + it "should return a hash of whatever is stored on disk" do + file = mk_test_file + ".json" + + data = {"one" => "two", "three" => "four"} + + File.open(file, "w") { |f| f.print data.to_json } + + Facter::Util::Parser.new(file).results.should == data + end + end + + describe "txt" do + subject { Facter::Util::Parser::TextParser } + it "should match the 'txt' extension" do + subject.extension.should == "txt" + end + + it "should return a hash of whatever is stored on disk" do + file = mk_test_file + ".txt" + + data = "one=two\nthree=four\n" + + File.open(file, "w") { |f| f.print data } + + Facter::Util::Parser.new(file).results.should == {"one" => "two", "three" => "four"} + end + + it "should ignore any non-setting lines" do + file = mk_test_file + ".txt" + + data = "one=two\nfive\nthree=four\n" + + File.open(file, "w") { |f| f.print data } + + Facter::Util::Parser.new(file).results.should == {"one" => "two", "three" => "four"} + end + end + + describe "scripts" do + before do + @script = mk_test_file + data = "#!/bin/sh +echo one=two +echo three=four +" + + File.open(@script, "w") { |f| f.print data } + File.chmod(0755, @script) + end + + it "should return a hash of whatever is returned by the executable" do + Facter::Util::Parser.new(@script).results.should == {"one" => "two", "three" => "four"} + end + end +end From 19f83284cd560756d8de2bf52c1cd14f90c2eed2 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 2 Feb 2012 17:07:32 -0800 Subject: [PATCH 0866/3753] (#2157) external facts command line - bin/facter --- bin/facter | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bin/facter b/bin/facter index 6c5d4ce09c..14b29503d8 100755 --- a/bin/facter +++ b/bin/facter @@ -6,7 +6,7 @@ # # = Usage # -# facter [-d|--debug] [-h|--help] [-p|--puppet] [-v|--version] [-y|--yaml] [-j|--json] [fact] [fact] [...] +# facter [-d|--debug] [-h|--help] [-p|--puppet] [-v|--version] [-y|--yaml] [-j|--json] [--ext DIR] [fact] [fact] [...] # # = Description # @@ -42,6 +42,9 @@ # timing:: # Enable timing. # +# ext:: +# The directory to use for external facts. +# # = Example # # facter kernel From 223293c429d2d9989ec5f8d07f1ddaafd935573c Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 2 Feb 2012 17:08:36 -0800 Subject: [PATCH 0867/3753] (#2157) external facts command line - lib/facter/application.rb --- lib/facter/application.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/facter/application.rb b/lib/facter/application.rb index 3f0f37522c..9e4055b147 100644 --- a/lib/facter/application.rb +++ b/lib/facter/application.rb @@ -9,6 +9,11 @@ def self.run(argv) # Accept fact names to return from the command line names = argv + # Change location of external facts dir + if options[:ext] + Facter::Util::Config.ext_fact_dir = options[:ext] + end + # Create the facts hash that is printed to standard out. unless names.empty? facts = {} @@ -74,6 +79,7 @@ def self.parse(argv) opts.on("-y", "--yaml") { |v| options[:yaml] = v } opts.on("-j", "--json") { |v| options[:json] = v } opts.on( "--trace") { |v| options[:trace] = v } + opts.on( "--ext DIR") { |v| options[:ext] = v } opts.on("-d", "--debug") { |v| Facter.debugging(1) } opts.on("-t", "--timing") { |v| Facter.timing(1) } opts.on("-p", "--puppet") { |v| load_puppet } From 87fc73e30766ba40ea32d6a1c6f4fd8db5a7bef9 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 2 Feb 2012 17:12:33 -0800 Subject: [PATCH 0868/3753] (#2157) external facts command line - lib/facter/util/config.rb Stores the ext_fact_dir in a class variable in config.rb --- lib/facter/util/config.rb | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/lib/facter/util/config.rb b/lib/facter/util/config.rb index 950f223184..ec2377cbb1 100644 --- a/lib/facter/util/config.rb +++ b/lib/facter/util/config.rb @@ -3,7 +3,45 @@ # A module to return config related data # module Facter::Util::Config + + def self.is_mac? + Config::CONFIG['host_os'] =~ /darwin/i + end + + # Returns true if OS is windows def self.is_windows? RbConfig::CONFIG['host_os'] =~ /mswin|win32|dos|mingw|cygwin/i end + + # The basedir to use for windows data + def self.windows_data_dir + if is_windows? + # If neither environment variable is set - fail. + if not ENV["ProgramData"] and not ENV["ALLUSERSPROFILE"] then + raise "Neither environment variables ProgramData or ALLUSERSPROFILE " + + "are defined. Facter is unable to determine a default dirctory for " + + "its uses." + end + base_dir = ENV["ProgramData"] || + File.join(ENV["ALLUSERSPROFILE"], "Application Data") + File.join(base_dir, "Puppetlabs", "facter") + else + nil + end + end + + # Retrieve the external fact directory + def self.ext_fact_dir + if is_windows? + @@ext_fact_dir ||= File.join(windows_data_dir, "ext") + else + @@ext_fact_dir ||= "/usr/lib/facter/ext" + end + @@ext_fact_dir + end + + # Set the external fact directory + def self.ext_fact_dir=(path) + @@ext_fact_dir = path + end end From 45e5bf158877be5964459ba803559c8a97461dc0 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 2 Feb 2012 17:34:07 -0800 Subject: [PATCH 0869/3753] (#2157) external facts command line - lib/facter/util/directory_loader.rb Directory loader uses the config ext_data_dir thing. --- lib/facter/util/directory_loader.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/facter/util/directory_loader.rb b/lib/facter/util/directory_loader.rb index eda0e22ab2..85dfc289f3 100644 --- a/lib/facter/util/directory_loader.rb +++ b/lib/facter/util/directory_loader.rb @@ -4,16 +4,19 @@ # and any executable that returns key=value pairs. require 'facter/util/parser' +require 'facter/util/config' class Facter::Util::DirectoryLoader require 'yaml' + # A list of extensions to ignore in fact directory. SKIP_EXTENSIONS = %w{bak orig} + # Directory for fact loading attr_reader :directory - def initialize(dir="/etc/facter/facts.d") - @directory = dir + def initialize(dir = nil) + @directory = dir || Facter::Util::Config.ext_fact_dir end def entries @@ -22,6 +25,8 @@ def entries [] end + # Load facts from files in fact directory using the relevant parser classes to + # parse them. def load entries.each do |file| unless data = Facter::Util::Parser.new(file).results From 07720aa35dc69fe2a97941407080665d9352b666 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 2 Feb 2012 17:37:45 -0800 Subject: [PATCH 0870/3753] (#2157) external facts - lib/facter/util/directory_loader.rb Warn instead of failing when external facts can't be loaded, do better checkint of output. --- lib/facter/util/directory_loader.rb | 16 ++++++++++++---- lib/facter/util/parser.rb | 4 ++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/lib/facter/util/directory_loader.rb b/lib/facter/util/directory_loader.rb index 85dfc289f3..51f25dc8ff 100644 --- a/lib/facter/util/directory_loader.rb +++ b/lib/facter/util/directory_loader.rb @@ -29,11 +29,19 @@ def entries # parse them. def load entries.each do |file| - unless data = Facter::Util::Parser.new(file).results - raise "Could not interpret fact file #{file}" + parser = Facter::Util::Parser.new(file) + if parser == nil + next end - data.each { |p,v| Facter.add(p, :value => v) } + data = parser.values + if data == false + Facter.warn "Could not interpret fact file #{file}" + elsif data == {} or data == nil + Facter.warn "Fact file #{file} was parsed but returned an empty data set" + else + data.each { |p,v| Facter.add(p, :value => v) } + end end end @@ -41,7 +49,7 @@ def load def parse?(file) return false if file =~ /^\./ - ext = file.split(".")[-1] + ext = file.split(".")[-1] return false if SKIP_EXTENSIONS.include?(ext) true end diff --git a/lib/facter/util/parser.rb b/lib/facter/util/parser.rb index a5dee0d751..2311556666 100644 --- a/lib/facter/util/parser.rb +++ b/lib/facter/util/parser.rb @@ -1,3 +1,7 @@ +# This class acts as the factory and parent class for parsed +# facts such as scripts, text, json and yaml files. +# +# Parsers must subclass this class and provide their own #results method. class Facter::Util::Parser attr_reader :filename From 34a337e4ce1a93f397cdac5c2050362a1bca739a Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 2 Feb 2012 17:41:24 -0800 Subject: [PATCH 0871/3753] (#2157) parser classes can register an array of extensions - lib/facter/util/config.rb --- lib/facter/util/parser.rb | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/facter/util/parser.rb b/lib/facter/util/parser.rb index 2311556666..d75c219cc7 100644 --- a/lib/facter/util/parser.rb +++ b/lib/facter/util/parser.rb @@ -10,9 +10,16 @@ class << self attr_reader :extension end - # Register the extension that this parser matches. + # Used by subclasses. Registers +ext+ as the extension to match. + # + # For support mutliple extensions you can pass an array of extensions as + # +ext+. def self.matches_extension(ext) - @extension = ext + if ext.class == String then + @extension = ext.downcase + elsif ext.class == Array then + @extension = ext.collect {|x| x.downcase } + end end def self.file_extension(filename) @@ -27,7 +34,7 @@ def self.inherited(klass) def self.matches?(filename) raise "Must override the 'matches?' method for #{self}" unless extension - file_extension(filename) == extension + [extension].flatten.to_a.include?(file_extension(filename).downcase) end def self.subclasses From 3c123746fa42f3d14c937de64d5ae2ce9f3788a6 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 2 Feb 2012 17:48:20 -0800 Subject: [PATCH 0872/3753] (#2157) raise error if parser doesn't implement #results - lib/facter/util/parser.rb --- lib/facter/util/parser.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/facter/util/parser.rb b/lib/facter/util/parser.rb index d75c219cc7..19ad798c99 100644 --- a/lib/facter/util/parser.rb +++ b/lib/facter/util/parser.rb @@ -62,6 +62,12 @@ def initialize(filename) @filename = filename end + # This method must be overwriten by subclasses to provide + # the results (as a hash) of parsing the filename. + def results + raise "Must override the 'results' method for #{self}" + end + class YamlParser < self matches_extension "yaml" From a4317a57baf5bda8fec592f9ceaea0152028da59 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 2 Feb 2012 17:48:56 -0800 Subject: [PATCH 0873/3753] (#2157) Catch infinite infinite loop when loading json - lib/facter/util/parser.rb --- lib/facter/util/parser.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/facter/util/parser.rb b/lib/facter/util/parser.rb index 19ad798c99..dcd9b8c536 100644 --- a/lib/facter/util/parser.rb +++ b/lib/facter/util/parser.rb @@ -101,11 +101,12 @@ class JsonParser < self matches_extension "json" def results + attempts = 0 begin require 'json' - rescue LoadError - require 'rubygems' - retry + rescue LoadError => e + raise e if attempts >= 1 + attempts += 1 end JSON.load(File.read(filename)) From cab9b7d1f2f297fc037efb74906728ca8e21f96c Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 2 Feb 2012 17:51:15 -0800 Subject: [PATCH 0874/3753] (#2157) add support for scriptparser on windows - lib/facter/util/parser.rb --- lib/facter/util/parser.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/facter/util/parser.rb b/lib/facter/util/parser.rb index dcd9b8c536..9edc54cd69 100644 --- a/lib/facter/util/parser.rb +++ b/lib/facter/util/parser.rb @@ -114,8 +114,13 @@ def results end class ScriptParser < self - def self.matches?(file) - File.executable?(file) + if Facter::Util::Config.is_windows? + matches_extension %w{bat com exe} + else + # Returns true if file is executable. + def self.matches?(file) + File.executable?(file) + end end def results From 87e469132cbc8e7ff933e8e2f371e6333e627073 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 2 Feb 2012 17:55:37 -0800 Subject: [PATCH 0875/3753] (#2157) add powershell script parser - facter/util/parser.rb --- lib/facter/util/parser.rb | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/lib/facter/util/parser.rb b/lib/facter/util/parser.rb index 9edc54cd69..013d8ac8bb 100644 --- a/lib/facter/util/parser.rb +++ b/lib/facter/util/parser.rb @@ -139,4 +139,43 @@ def results Facter.debug(e.backtrace.join("\n\t")) end end + + # Executes and parses the key value output of Powershell scripts + # + # Before you can run unsigned ps1 scripts it requires a change to execution + # policy: + # + # Set-ExecutionPolicy RemoteSigned -Scope LocalMachine + # Set-ExecutionPolicy RemoteSigned -Scope CurrentUser + # + class PowershellParser < self + matches_extension "ps1" + + # Only return true if this is a windows box + def self.matches?(filename) + if Facter::Util::Config.is_windows? + super(filename) + else + return false + end + end + + # Returns a hash of facts from powershell output + def results + shell_command = "powershell -File #{filename}" + output = Facter::Util::Resolution.exec(shell_command) + + result = {} + output.split("\n").each do |line| + if line =~ /^(.+)=(.+)$/ + result[$1] = $2 + end + end + + result + rescue Exception => e + Facter.warn("Failed to handle #{filename} as powershell facts: #{e.class}: #{e}") + Facter.debug(e.backtrace.join("\n\t")) + end + end end From 45b8cf3500710082acb55c9f2b059988d052691a Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 2 Feb 2012 18:05:48 -0800 Subject: [PATCH 0876/3753] (#2157) external facts command line - install.rb Added support for external facts in install script --- install.rb | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/install.rb b/install.rb index 7fd442f081..cde513e11b 100755 --- a/install.rb +++ b/install.rb @@ -89,6 +89,7 @@ def glob(list) man = glob(%w{man/man8/*}) libs = glob(%w{lib/**/*.rb lib/**/*.py lib/**/LICENSE}) tests = glob(%w{tests/**/*.rb}) +libexec = glob(%w{libexec/ext/README}) def do_bins(bins, target, strip = 's?bin/') bins.each do |bf| @@ -124,6 +125,16 @@ def do_man(man, strip = 'man/') end end +def do_libexec(libexec, strip = 'libexec/') + libexec.each do |lf| + olf = File.join(InstallOptions.libexec_dir, lf.gsub(/#{strip}/, '')) + op = File.dirname(olf) + FileUtils.makedirs(op, {:mode => 0755, :verbose => true}) + FileUtils.chmod(0755, op) + FileUtils.install(lf, olf, {:mode => 0644, :verbose => true}) + end +end + # Verify that all of the prereqs are installed def check_prereqs PREREQS.each { |pre| @@ -199,6 +210,9 @@ def prepare_installation opts.on('--mandir[=OPTIONAL]', 'Installation directory for man pages', 'overrides RbConfig::CONFIG["mandir"]') do |mandir| InstallOptions.mandir = mandir end + opts.on('--libexecdir[=OPTIONAL]', 'Installation directory for libexec files', 'Defaults to /usr/lib/facter') do |libexecdir| + InstallOptions.libexecdir = libexecdir + end opts.on('--quick', 'Performs a quick installation. Only the', 'installation is done.') do |quick| InstallOptions.rdoc = false InstallOptions.ri = false @@ -263,16 +277,28 @@ def prepare_installation mandir = RbConfig::CONFIG['mandir'] end + if not InstallOptions.libexecdir.nil? + libexecdir = InstallOptions.libexecdir + else + if is_windows? + libexecdir = Facter::Util::Config.windows_data_dir + else + libexecdir = "/usr/lib/facter" + end + end + if (destdir = InstallOptions.destdir) bindir = join(destdir, bindir) sbindir = join(destdir, sbindir) mandir = join(destdir, mandir) sitelibdir = join(destdir, sitelibdir) + libexeclibdir = join(destdir, libexeclibdir) FileUtils.makedirs(bindir) FileUtils.makedirs(sbindir) FileUtils.makedirs(mandir) FileUtils.makedirs(sitelibdir) + FileUtils.makedirs(libexeclibdir) end InstallOptions.site_dir = sitelibdir @@ -280,6 +306,7 @@ def prepare_installation InstallOptions.sbin_dir = sbindir InstallOptions.lib_dir = libdir InstallOptions.man_dir = mandir + InstallOptions.libexec_dir = libexecdir end ## @@ -427,3 +454,4 @@ def install_binfile(from, op_file, target) do_bins(bins, InstallOptions.bin_dir) do_libs(libs) do_man(man) +do_libexec(libexec) From c22b9ff11b8a876d90af4f4906f317e4fe9ff0bb Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 2 Feb 2012 18:07:35 -0800 Subject: [PATCH 0877/3753] (#2157) Add external facts README to redhat spec --- conf/redhat/facter.spec | 1 + 1 file changed, 1 insertion(+) diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index d8c8619bc0..ddd5d647da 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -49,6 +49,7 @@ rm -rf %{buildroot} %{_bindir}/facter %{ruby_sitelibdir}/facter.rb %{ruby_sitelibdir}/facter +%{_libdir}/ext/README %doc CHANGELOG INSTALL LICENSE README.md From da1982bea8881278eff2758dbdc46d2356e0a159 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 2 Feb 2012 18:14:13 -0800 Subject: [PATCH 0878/3753] (#2157) add readme for libexec external facts --- libexec/ext/README | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 libexec/ext/README diff --git a/libexec/ext/README b/libexec/ext/README new file mode 100644 index 0000000000..1d16a39f59 --- /dev/null +++ b/libexec/ext/README @@ -0,0 +1,4 @@ +This directory is for external facts. For more information +see the documentation available here: + +http://docs.puppetlabs.com/guides/custom_facts.html From 70ea5b1291934051f8297ba9a31235883e38a607 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 2 Feb 2012 18:24:33 -0800 Subject: [PATCH 0879/3753] (#2157) Update config spec to test ext_fact_dir --- spec/unit/util/config_spec.rb | 56 +++++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 6 deletions(-) diff --git a/spec/unit/util/config_spec.rb b/spec/unit/util/config_spec.rb index 3eb134b4d2..34f59f35cf 100644 --- a/spec/unit/util/config_spec.rb +++ b/spec/unit/util/config_spec.rb @@ -1,24 +1,68 @@ -#!/usr/bin/env rspec +#!/usr/bin/env ruby -S rspec require 'spec_helper' -require 'facter/util/config' describe Facter::Util::Config do + include PuppetlabsSpec::Files + describe "is_windows? function" do - it "should detect windows if RbConfig returns a windows OS" do + it "should detect windows if Ruby Config::CONFIG['host_os'] returns a windows OS" do host_os = ["mswin","win32","dos","mingw","cygwin"] host_os.each do |h| - RbConfig::CONFIG.expects(:[]).with('host_os').returns(h) + Config::CONFIG.stubs(:[]).with('host_os').returns(h) Facter::Util::Config.is_windows?.should be_true end end - it "should not detect windows if RbConfig returns a non-windows OS" do + it "should not detect windows if Ruby Config::CONFIG['host_os'] returns a non-windows OS" do host_os = ["darwin","linux"] host_os.each do |h| - RbConfig::CONFIG.expects(:[]).with('host_os').returns(h) + Config::CONFIG.stubs(:[]).with('host_os').returns(h) Facter::Util::Config.is_windows?.should be_false end end end + + describe "is_mac? function" do + it "should detect mac if Ruby Config::CONFIG['host_os'] returns darwin" do + host_os = ["darwin"] + host_os.each do |h| + Config::CONFIG.stubs(:[]).with('host_os').returns(h) + Facter::Util::Config.is_mac?.should be_true + end + end + end + + describe "ext_fact_dir attribute" do + around :each do |example| + Facter::Util::Config.ext_fact_dir = nil + example.run + Facter::Util::Config.ext_fact_dir = nil + end + + it "should allow setting and getting" do + filename = tmpfilename('test') + Facter::Util::Config.ext_fact_dir = filename + Facter::Util::Config.ext_fact_dir.should == filename + end + + it "should return the default value for linux" do + Facter::Util::Config.stubs(:is_windows?).returns(false) + Facter::Util::Config.ext_fact_dir.should == "/usr/lib/facter/ext" + end + + it "should return the default value for windows 2008" do + Facter::Util::Config.stubs(:is_windows?).returns(true) + ENV.stubs(:[]).with("ProgramData").returns("C:\\ProgramData") + Facter::Util::Config.ext_fact_dir.should == "C:\\ProgramData/Puppetlabs/facter/ext" + end + + it "should return the default value for windows 2003R2" do + Facter::Util::Config.stubs(:is_windows?).returns(true) + ENV.stubs(:[]).with("ProgramData").returns(nil) + ENV.stubs(:[]).with("ALLUSERSPROFILE").returns("C:\\Documents and Settings\\All Users") + Facter::Util::Config.ext_fact_dir.should == "C:\\Documents and Settings\\All Users/Application Data/Puppetlabs/facter/ext" + end + end + end From 11535d0ac83d427b87ae480aab2a13263f0b2d7e Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 2 Feb 2012 18:34:57 -0800 Subject: [PATCH 0880/3753] (#2157) Used puppetlabs spec helper for directoryloader tests --- spec/unit/util/directory_loader_spec.rb | 40 +++---------------------- 1 file changed, 4 insertions(+), 36 deletions(-) diff --git a/spec/unit/util/directory_loader_spec.rb b/spec/unit/util/directory_loader_spec.rb index fa88660449..ed7941c75c 100644 --- a/spec/unit/util/directory_loader_spec.rb +++ b/spec/unit/util/directory_loader_spec.rb @@ -1,46 +1,14 @@ #!/usr/bin/env ruby -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') +require 'spec_helper' require 'facter/util/directory_loader' -require 'tempfile' describe Facter::Util::DirectoryLoader do - subject { Facter::Util::DirectoryLoader.new("/my/dir.d") } + include PuppetlabsSpec::Files - def mk_test_dir - file = Tempfile.new "testing_fact_loading_dir" - @dir = file.path - file.delete - - Dir.mkdir(@dir) - @dirs << @dir # for cleanup - - @dir - end - - def mk_test_file - file = Tempfile.new "testing_fact_loading_file" - @filename = file.path - file.delete - @files << @filename # for cleanup - - @filename - end - - before { - @files = [] - @dirs = [] - @loader = Facter::Util::DirectoryLoader.new(mk_test_dir) - } - - after do - @files.each do |file| - File.unlink(file) if File.exist?(file) - end - @dirs.each do |dir| - FileUtils.rm_f(dir) if File.exist?(dir) - end + before :each do + @loader = Facter::Util::DirectoryLoader.new(tmpdir('directory_loader')) end it "should make the directory available" do From 76104aa1a1a1dc80f2dcaaf69e9333d14d98c6f3 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 2 Feb 2012 18:35:31 -0800 Subject: [PATCH 0881/3753] (#2157) Update directory_spec to reference new libexec dir --- spec/unit/util/directory_loader_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/unit/util/directory_loader_spec.rb b/spec/unit/util/directory_loader_spec.rb index ed7941c75c..78671d0541 100644 --- a/spec/unit/util/directory_loader_spec.rb +++ b/spec/unit/util/directory_loader_spec.rb @@ -15,8 +15,8 @@ @loader.directory.should be_instance_of(String) end - it "should default to '/etc/facter/facts.d' for the directory" do - Facter::Util::DirectoryLoader.new.directory.should == "/etc/facter/facts.d" + it "should default to '/usr/lib/facter/ext' for the directory" do + Facter::Util::DirectoryLoader.new.directory.should == "/usr/lib/facter/ext" end describe "when loading facts from disk" do From 22c5a2512358f62cff85cde893da0af2ae75cdf8 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 2 Feb 2012 18:41:10 -0800 Subject: [PATCH 0882/3753] (#2157) directory_loader uses results instead of values Original implementation added values method which checked the cache and then only called results if the cache was empty. Calling results directly instead. --- lib/facter/util/directory_loader.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/util/directory_loader.rb b/lib/facter/util/directory_loader.rb index 51f25dc8ff..514c9bbb39 100644 --- a/lib/facter/util/directory_loader.rb +++ b/lib/facter/util/directory_loader.rb @@ -34,7 +34,7 @@ def load next end - data = parser.values + data = parser.results if data == false Facter.warn "Could not interpret fact file #{file}" elsif data == {} or data == nil From e0668a7ea84075b7721c34b6061bea1337149087 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Fri, 3 Feb 2012 10:24:20 -0800 Subject: [PATCH 0883/3753] (#2157) test directory loader to make sure loading non-existing directories is all cool, yo --- spec/unit/util/directory_loader_spec.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/spec/unit/util/directory_loader_spec.rb b/spec/unit/util/directory_loader_spec.rb index 78671d0541..a702fe2d40 100644 --- a/spec/unit/util/directory_loader_spec.rb +++ b/spec/unit/util/directory_loader_spec.rb @@ -19,6 +19,13 @@ Facter::Util::DirectoryLoader.new.directory.should == "/usr/lib/facter/ext" end + it "should do nothing bad when dir doesn't exist" do + fakepath = "/foobar/path" + my_loader = Facter::Util::DirectoryLoader.new(fakepath) + FileTest.exists?(my_loader.directory).should be_false + my_loader.load + end + describe "when loading facts from disk" do it "should be able to load files from disk and set facts" do data = {"f1" => "one", "f2" => "two"} From becb778d818088cfa91a5c5056726219052c1930 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Fri, 3 Feb 2012 10:58:02 -0800 Subject: [PATCH 0884/3753] (#2157) shorten spec_helper require in parser_spec.rb --- spec/unit/util/parser_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/unit/util/parser_spec.rb b/spec/unit/util/parser_spec.rb index 2f06176a07..7a2e649859 100644 --- a/spec/unit/util/parser_spec.rb +++ b/spec/unit/util/parser_spec.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') +require 'spec_helper' require 'facter/util/parser' require 'tempfile' From 75b7cea8aa7590f2da3a0135195d398b525e3e1d Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Fri, 3 Feb 2012 11:05:56 -0800 Subject: [PATCH 0885/3753] (#2157) cleanup and reformatting of directory_loader_spec --- spec/unit/util/directory_loader_spec.rb | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/spec/unit/util/directory_loader_spec.rb b/spec/unit/util/directory_loader_spec.rb index a702fe2d40..372002ecac 100644 --- a/spec/unit/util/directory_loader_spec.rb +++ b/spec/unit/util/directory_loader_spec.rb @@ -7,12 +7,10 @@ describe Facter::Util::DirectoryLoader do include PuppetlabsSpec::Files - before :each do - @loader = Facter::Util::DirectoryLoader.new(tmpdir('directory_loader')) - end + subject { Facter::Util::DirectoryLoader.new(tmpdir('directory_loader')) } it "should make the directory available" do - @loader.directory.should be_instance_of(String) + subject.directory.should be_instance_of(String) end it "should default to '/usr/lib/facter/ext' for the directory" do @@ -23,44 +21,44 @@ fakepath = "/foobar/path" my_loader = Facter::Util::DirectoryLoader.new(fakepath) FileTest.exists?(my_loader.directory).should be_false - my_loader.load + expect { my_loader.load }.should_not raise_error end describe "when loading facts from disk" do it "should be able to load files from disk and set facts" do data = {"f1" => "one", "f2" => "two"} - file = File.join(@loader.directory, "data" + ".yaml") + file = File.join(subject.directory, "data" + ".yaml") File.open(file, "w") { |f| f.print YAML.dump(data) } - @loader.load + subject.load Facter.value("f1").should == "one" Facter.value("f2").should == "two" end it "should ignore files that begin with '.'" do - file = File.join(@loader.directory, ".data.yaml") + file = File.join(subject.directory, ".data.yaml") data = {"f1" => "one", "f2" => "two"} File.open(file, "w") { |f| f.print YAML.dump(data) } - @loader.load + subject.load Facter.value("f1").should be_nil end %w{bak orig}.each do |ext| it "should ignore files with an extension of '#{ext}'" do - file = File.join(@loader.directory, "data" + ".#{ext}") + file = File.join(subject.directory, "data" + ".#{ext}") File.open(file, "w") { |f| f.print "foo=bar" } - @loader.load + subject.load end end it "should fail when trying to parse unknown file types" do - file = File.join(@loader.directory, "file.unknownfiletype") + file = File.join(subject.directory, "file.unknownfiletype") File.open(file, "w") { |f| f.print "stuff=bar" } - lambda { @loader.load }.should raise_error(ArgumentError) + expect { subject.load }.should raise_error(ArgumentError) end end end From 9285308c2cf60232c855b9cfca530e6cf53e4574 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Fri, 3 Feb 2012 11:15:47 -0800 Subject: [PATCH 0886/3753] (#2157) Converted parser spec to use PuppetlabsSpec::Files helper --- spec/unit/util/parser_spec.rb | 31 +++++++------------------------ 1 file changed, 7 insertions(+), 24 deletions(-) diff --git a/spec/unit/util/parser_spec.rb b/spec/unit/util/parser_spec.rb index 7a2e649859..58b10f5d49 100644 --- a/spec/unit/util/parser_spec.rb +++ b/spec/unit/util/parser_spec.rb @@ -7,24 +7,7 @@ require 'json' describe Facter::Util::Parser do - def mk_test_file - file = Tempfile.new "testing_fact_parser" - @filename = file.path - file.delete - @files << @filename # for cleanup - - @filename - end - - before { - @files = [] - } - - after do - @files.each do |file| - File.unlink(file) if File.exist?(file) - end - end + include PuppetlabsSpec::Files it "should fail when asked to parse a file type it does not support" do lambda { Facter::Util::Parser.new("/my/file.foobar") }.should raise_error(ArgumentError) @@ -37,7 +20,7 @@ def mk_test_file end it "should return a hash of whatever is stored on disk" do - file = mk_test_file + ".yaml" + file = tmpfilename('parser') + ".yaml" data = {"one" => "two", "three" => "four"} @@ -47,7 +30,7 @@ def mk_test_file end it "should handle exceptions and warn" do - file = mk_test_file + ".yaml" + file = tmpfilename('parser') + ".yaml" data = {"one" => "two", "three" => "four"} @@ -64,7 +47,7 @@ def mk_test_file end it "should return a hash of whatever is stored on disk" do - file = mk_test_file + ".json" + file = tmpfilename('parser') + ".json" data = {"one" => "two", "three" => "four"} @@ -81,7 +64,7 @@ def mk_test_file end it "should return a hash of whatever is stored on disk" do - file = mk_test_file + ".txt" + file = tmpfilename('parser') + ".txt" data = "one=two\nthree=four\n" @@ -91,7 +74,7 @@ def mk_test_file end it "should ignore any non-setting lines" do - file = mk_test_file + ".txt" + file = tmpfilename('parser') + ".txt" data = "one=two\nfive\nthree=four\n" @@ -103,7 +86,7 @@ def mk_test_file describe "scripts" do before do - @script = mk_test_file + @script = tmpfilename('parser') data = "#!/bin/sh echo one=two echo three=four From 6f9a10a7f534ed35962711d819d2feeb9793efb6 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Fri, 3 Feb 2012 11:17:24 -0800 Subject: [PATCH 0887/3753] (#2157) Add test coverage for parser matches? function --- spec/unit/util/parser_spec.rb | 40 +++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/spec/unit/util/parser_spec.rb b/spec/unit/util/parser_spec.rb index 58b10f5d49..3940e776f1 100644 --- a/spec/unit/util/parser_spec.rb +++ b/spec/unit/util/parser_spec.rb @@ -13,6 +13,46 @@ lambda { Facter::Util::Parser.new("/my/file.foobar") }.should raise_error(ArgumentError) end + describe "matches? function" do + it "should match extensions when subclass uses match_extension" do + class TestParser < Facter::Util::Parser + matches_extension "foobar" + end + + TestParser.matches?("myfile.foobar").should == true + end + + it "should match extensions when subclass uses match_extension with an array" do + class TestParser < Facter::Util::Parser + matches_extension ["ext1","ext2","ext3"] + end + + TestParser.matches?("myfile.ext1").should == true + TestParser.matches?("myfile.ext2").should == true + TestParser.matches?("myfile.ext3").should == true + end + + it "should match extension ignoring case on file" do + class TestParser < Facter::Util::Parser + matches_extension "ext1" + end + + TestParser.matches?("myfile.EXT1").should == true + TestParser.matches?("myfile.ExT1").should == true + TestParser.matches?("myfile.exT1").should == true + end + + it "should match extension ignoring case for match_extension" do + class TestParser < Facter::Util::Parser + matches_extension "EXT1" + end + + TestParser.matches?("myfile.EXT1").should == true + TestParser.matches?("myfile.ExT1").should == true + TestParser.matches?("myfile.exT1").should == true + end + end + describe "yaml" do subject { Facter::Util::Parser::YamlParser } it "should match the 'yaml' extension" do From 7d933fd3c19f78b7be32d84a37099d253fe3f907 Mon Sep 17 00:00:00 2001 From: Hailee Kenney Date: Mon, 11 Jun 2012 15:08:20 -0700 Subject: [PATCH 0888/3753] (#2157) Remove JSON dependencies Since we cannot guarantee that all users will have the JSON gem, execute JSON code only if the gem is present. --- lib/facter.rb | 3 ++- lib/facter/application.rb | 4 +++- lib/facter/util/parser.rb | 26 ++++++++++++++----------- spec/unit/util/directory_loader_spec.rb | 2 ++ spec/unit/util/parser_spec.rb | 25 ++++++++++++------------ 5 files changed, 35 insertions(+), 25 deletions(-) diff --git a/lib/facter.rb b/lib/facter.rb index 1787309fac..c6ff2ead2f 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -21,6 +21,7 @@ module Util; end require 'facter/util/fact' require 'facter/util/collection' require 'facter/util/monkey_patches' + require 'facter/util/json' include Comparable include Enumerable @@ -82,7 +83,7 @@ def self.debugonce(msg) def self.debugging? @@debug != 0 end - + # show the timing information def self.show_time(string) puts "#{GREEN}#{string}#{RESET}" if string and Facter.timing? diff --git a/lib/facter/application.rb b/lib/facter/application.rb index 9e4055b147..7d2cccfb97 100644 --- a/lib/facter/application.rb +++ b/lib/facter/application.rb @@ -77,7 +77,9 @@ def self.parse(argv) options = {} OptionParser.new do |opts| opts.on("-y", "--yaml") { |v| options[:yaml] = v } - opts.on("-j", "--json") { |v| options[:json] = v } + if Facter.json? + opts.on("-j", "--json") { |v| options[:json] = v } + end opts.on( "--trace") { |v| options[:trace] = v } opts.on( "--ext DIR") { |v| options[:ext] = v } opts.on("-d", "--debug") { |v| Facter.debugging(1) } diff --git a/lib/facter/util/parser.rb b/lib/facter/util/parser.rb index 013d8ac8bb..4c37de22bb 100644 --- a/lib/facter/util/parser.rb +++ b/lib/facter/util/parser.rb @@ -2,6 +2,8 @@ # facts such as scripts, text, json and yaml files. # # Parsers must subclass this class and provide their own #results method. +require 'facter/util/json' + class Facter::Util::Parser attr_reader :filename @@ -97,19 +99,21 @@ def results end end - class JsonParser < self - matches_extension "json" + if Facter.json? + class JsonParser < self + matches_extension "json" + + def results + attempts = 0 + begin + require 'json' + rescue LoadError => e + raise e if attempts >= 1 + attempts += 1 + end - def results - attempts = 0 - begin - require 'json' - rescue LoadError => e - raise e if attempts >= 1 - attempts += 1 + JSON.load(File.read(filename)) end - - JSON.load(File.read(filename)) end end diff --git a/spec/unit/util/directory_loader_spec.rb b/spec/unit/util/directory_loader_spec.rb index 372002ecac..07af94fa24 100644 --- a/spec/unit/util/directory_loader_spec.rb +++ b/spec/unit/util/directory_loader_spec.rb @@ -37,6 +37,8 @@ end it "should ignore files that begin with '.'" do + # Since we know we won't load any facts, suppress the warning message + Facter.stubs(:warnonce) file = File.join(subject.directory, ".data.yaml") data = {"f1" => "one", "f2" => "two"} File.open(file, "w") { |f| f.print YAML.dump(data) } diff --git a/spec/unit/util/parser_spec.rb b/spec/unit/util/parser_spec.rb index 3940e776f1..caa5975902 100644 --- a/spec/unit/util/parser_spec.rb +++ b/spec/unit/util/parser_spec.rb @@ -4,7 +4,6 @@ require 'facter/util/parser' require 'tempfile' -require 'json' describe Facter::Util::Parser do include PuppetlabsSpec::Files @@ -79,21 +78,23 @@ class TestParser < Facter::Util::Parser lambda { Facter::Util::Parser.new("/some/path/that/doesn't/exist.yaml").results }.should_not raise_error end end + + if Facter.json? + describe "json" do + subject { Facter::Util::Parser::JsonParser } + it "should match the 'json' extension" do + subject.extension.should == "json" + end - describe "json" do - subject { Facter::Util::Parser::JsonParser } - it "should match the 'json' extension" do - subject.extension.should == "json" - end + it "should return a hash of whatever is stored on disk" do + file = tmpfilename('parser') + ".json" - it "should return a hash of whatever is stored on disk" do - file = tmpfilename('parser') + ".json" + data = {"one" => "two", "three" => "four"} - data = {"one" => "two", "three" => "four"} + File.open(file, "w") { |f| f.print data.to_json } - File.open(file, "w") { |f| f.print data.to_json } - - Facter::Util::Parser.new(file).results.should == data + Facter::Util::Parser.new(file).results.should == data + end end end From b0eea43044015524aa735d9ef642ba39b8882b30 Mon Sep 17 00:00:00 2001 From: Hailee Kenney Date: Tue, 12 Jun 2012 09:25:26 -0700 Subject: [PATCH 0889/3753] (#2157) Add util/json.rb file to support removing JSON requirement --- lib/facter/util/json.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 lib/facter/util/json.rb diff --git a/lib/facter/util/json.rb b/lib/facter/util/json.rb new file mode 100644 index 0000000000..f71f209ddc --- /dev/null +++ b/lib/facter/util/json.rb @@ -0,0 +1,14 @@ +module Facter + + # Idealy this should be an autoloaded feature test like in Puppet, + # but that's more overhead that is needed right now. + def self.json? + begin + require 'json' + true + rescue LoadError + false + end + end +end + From 5bc46a00b8e8e14b4ad2edc319227dac7bb9a3d5 Mon Sep 17 00:00:00 2001 From: Hailee Kenney Date: Thu, 14 Jun 2012 09:39:19 -0700 Subject: [PATCH 0890/3753] (#2157) Exit facter when specified directory for external facts does not exist Prior to this commit, it was possible to specify a directory for external facts via the command line, but Facter would continue to run even if that directory was not found. Add implementation for this and improve `DirectoryLoader`. --- lib/facter/application.rb | 8 +++++- lib/facter/util/config.rb | 27 +++++++----------- lib/facter/util/directory_loader.rb | 38 ++++++++++++++++++------- lib/facter/util/loader.rb | 6 ++-- spec/spec_helper.rb | 9 ++++++ spec/unit/id_spec.rb | 11 +++---- spec/unit/ipaddress6_spec.rb | 7 +++-- spec/unit/kernel_spec.rb | 15 ++++------ spec/unit/macaddress_spec.rb | 4 ++- spec/unit/util/config_spec.rb | 19 +++---------- spec/unit/util/directory_loader_spec.rb | 19 +++++++++++-- spec/unit/util/ip_spec.rb | 6 ++-- spec/unit/util/loader_spec.rb | 11 ++++--- spec/unit/util/resolution_spec.rb | 9 ++++-- 14 files changed, 112 insertions(+), 77 deletions(-) diff --git a/lib/facter/application.rb b/lib/facter/application.rb index 7d2cccfb97..fde1bfc77c 100644 --- a/lib/facter/application.rb +++ b/lib/facter/application.rb @@ -10,8 +10,14 @@ def self.run(argv) names = argv # Change location of external facts dir + # Check here for valid ext_dir and exit program if options[:ext] - Facter::Util::Config.ext_fact_dir = options[:ext] + begin + Facter::Util::Config.ext_fact_loader = Facter::Util::DirectoryLoader.loader_for(options[:ext]) + rescue Facter::Util::DirectoryLoader::NoSuchDirectoryError => error + $stderr.puts "Specified external facts directory #{options[:ext]} does not exist." + exit(1) + end end # Create the facts hash that is printed to standard out. diff --git a/lib/facter/util/config.rb b/lib/facter/util/config.rb index ec2377cbb1..9eb7ebe69c 100644 --- a/lib/facter/util/config.rb +++ b/lib/facter/util/config.rb @@ -4,6 +4,14 @@ # module Facter::Util::Config + def self.ext_fact_loader + @ext_fact_loader || Facter::Util::DirectoryLoader.default_loader + end + + def self.ext_fact_loader=(loader) + @ext_fact_loader = loader + end + def self.is_mac? Config::CONFIG['host_os'] =~ /darwin/i end @@ -14,7 +22,7 @@ def self.is_windows? end # The basedir to use for windows data - def self.windows_data_dir + def self.data_dir if is_windows? # If neither environment variable is set - fail. if not ENV["ProgramData"] and not ENV["ALLUSERSPROFILE"] then @@ -26,22 +34,7 @@ def self.windows_data_dir File.join(ENV["ALLUSERSPROFILE"], "Application Data") File.join(base_dir, "Puppetlabs", "facter") else - nil + "/usr/lib/facter" end end - - # Retrieve the external fact directory - def self.ext_fact_dir - if is_windows? - @@ext_fact_dir ||= File.join(windows_data_dir, "ext") - else - @@ext_fact_dir ||= "/usr/lib/facter/ext" - end - @@ext_fact_dir - end - - # Set the external fact directory - def self.ext_fact_dir=(path) - @@ext_fact_dir = path - end end diff --git a/lib/facter/util/directory_loader.rb b/lib/facter/util/directory_loader.rb index 514c9bbb39..67f90cafe1 100644 --- a/lib/facter/util/directory_loader.rb +++ b/lib/facter/util/directory_loader.rb @@ -8,6 +8,9 @@ class Facter::Util::DirectoryLoader require 'yaml' + + class NoSuchDirectoryError < Exception + end # A list of extensions to ignore in fact directory. SKIP_EXTENSIONS = %w{bak orig} @@ -15,15 +18,22 @@ class Facter::Util::DirectoryLoader # Directory for fact loading attr_reader :directory - def initialize(dir = nil) - @directory = dir || Facter::Util::Config.ext_fact_dir - end - - def entries - Dir.entries(directory).find_all{|f| parse?(f) }.sort.map {|f| File.join(directory, f) } - rescue - [] + def initialize(dir) + @directory = dir end + + def self.loader_for(dir) + if File.directory?(dir) + Facter::Util::DirectoryLoader.new(dir) + else + raise NoSuchDirectoryError + end + end + + def self.default_loader + dir = File.join(Facter::Util::Config.data_dir, "ext") + Facter::Util::DirectoryLoader.new(dir) + end # Load facts from files in fact directory using the relevant parser classes to # parse them. @@ -45,11 +55,17 @@ def load end end - private +private + + def entries + Dir.entries(directory).find_all { |f| should_parse?(f) }.sort.map { |f| File.join(directory, f) } + rescue + [] + end - def parse?(file) + def should_parse?(file) return false if file =~ /^\./ - ext = file.split(".")[-1] + ext = file.split(".")[-1] return false if SKIP_EXTENSIONS.include?(ext) true end diff --git a/lib/facter/util/loader.rb b/lib/facter/util/loader.rb index 4a02d466b2..26eee9f6f1 100644 --- a/lib/facter/util/loader.rb +++ b/lib/facter/util/loader.rb @@ -5,12 +5,10 @@ # Load facts on demand. class Facter::Util::Loader - attr_reader :directory_loader - def initialize @loaded = [] @valid_path = {} - @directory_loader = Facter::Util::DirectoryLoader.new + @directory_loader = Facter::Util::Config.ext_fact_loader end # Load all resolutions for a single fact. @@ -38,7 +36,7 @@ def load_all load_env - directory_loader.load + @directory_loader.load search_path.each do |dir| next unless FileTest.directory?(dir) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 31459bcd24..0d85b3a74b 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -39,3 +39,12 @@ to_remove.each {|key| ENV.delete key } end end + +module FacterSpec + module ConfigHelper + def given_a_configuration_of(config) + Facter::Util::Config.stubs(:is_windows?).returns(config[:is_windows]) + Facter::Util::Config.stubs(:data_dir).returns(config[:data_dir] || "data_dir") + end + end +end diff --git a/spec/unit/id_spec.rb b/spec/unit/id_spec.rb index c708caa684..e1c3063323 100755 --- a/spec/unit/id_spec.rb +++ b/spec/unit/id_spec.rb @@ -3,14 +3,15 @@ require 'spec_helper' describe "id fact" do + include FacterSpec::ConfigHelper kernel = [ 'Linux', 'Darwin', 'windows', 'FreeBSD', 'OpenBSD', 'NetBSD', 'AIX', 'HP-UX' ] kernel.each do |k| describe "with kernel reported as #{k}" do it "should return the current user" do + given_a_configuration_of(:is_windows => k == 'windows') Facter.fact(:kernel).stubs(:value).returns(k) - Facter::Util::Config.stubs(:is_windows?).returns(k == 'windows') Facter::Util::Resolution.expects(:exec).once.with('whoami').returns 'bar' Facter.fact(:id).value.should == 'bar' @@ -19,10 +20,10 @@ end it "should return the current user on Solaris" do - Facter::Util::Config.stubs(:is_windows?).returns(false) - Facter::Util::Resolution.stubs(:exec).with('uname -s').returns('SunOS') - Facter::Util::Resolution.expects(:exec).once.with('/usr/xpg4/bin/id -un').returns 'bar' + given_a_configuration_of(:is_windows => false) + Facter::Util::Resolution.stubs(:exec).with('uname -s').returns('SunOS') + Facter::Util::Resolution.expects(:exec).once.with('/usr/xpg4/bin/id -un').returns 'bar' - Facter.fact(:id).value.should == 'bar' + Facter.fact(:id).value.should == 'bar' end end diff --git a/spec/unit/ipaddress6_spec.rb b/spec/unit/ipaddress6_spec.rb index 6b1a79b20c..5bca013c93 100755 --- a/spec/unit/ipaddress6_spec.rb +++ b/spec/unit/ipaddress6_spec.rb @@ -10,9 +10,12 @@ def netsh_fixture(filename) File.read(fixtures('netsh', filename)) end + describe "IPv6 address fact" do + include FacterSpec::ConfigHelper + before do - Facter::Util::Config.stubs(:is_windows?).returns(false) + given_a_configuration_of(:is_windows => false) end it "should return ipaddress6 information for Darwin" do @@ -41,7 +44,7 @@ def netsh_fixture(filename) it "should return ipaddress6 information for Windows" do ENV.stubs(:[]).with('SYSTEMROOT').returns('d:/windows') - Facter::Util::Config.stubs(:is_windows?).returns(true) + given_a_configuration_of(:is_windows => true) fixture = netsh_fixture('windows_netsh_addresses_with_multiple_interfaces') Facter::Util::Resolution.stubs(:exec).with('d:/windows/system32/netsh.exe interface ipv6 show address level=verbose'). diff --git a/spec/unit/kernel_spec.rb b/spec/unit/kernel_spec.rb index b4b0549d02..92ff41ac8e 100644 --- a/spec/unit/kernel_spec.rb +++ b/spec/unit/kernel_spec.rb @@ -3,24 +3,21 @@ require 'spec_helper' describe "Kernel fact" do + include FacterSpec::ConfigHelper describe "on Windows" do - before do - Facter::Util::Config.stubs(:is_windows?).returns(true) - end - it "should return the kernel as 'windows'" do + given_a_configuration_of(:is_windows => true, :data_dir => "data_dir") + Facter.fact(:kernel).value.should == "windows" end end describe "on everything else" do - before do - Facter::Util::Config.stubs(:is_windows?).returns(false) - Facter::Util::Resolution.stubs(:exec).with('uname -s').returns("test_kernel") - end - it "should return the kernel using 'uname -s'" do + given_a_configuration_of(:is_windows => false) + Facter::Util::Resolution.stubs(:exec).with('uname -s').returns("test_kernel") + Facter.fact(:kernel).value.should == 'test_kernel' end end diff --git a/spec/unit/macaddress_spec.rb b/spec/unit/macaddress_spec.rb index 244bfc9136..f204ceef45 100755 --- a/spec/unit/macaddress_spec.rb +++ b/spec/unit/macaddress_spec.rb @@ -11,8 +11,10 @@ def netsh_fixture(filename) end describe "macaddress fact" do + include FacterSpec::ConfigHelper + before do - Facter::Util::Config.stubs(:is_windows?).returns(false) + given_a_configuration_of(:is_windows => false) end describe "when run on Linux" do diff --git a/spec/unit/util/config_spec.rb b/spec/unit/util/config_spec.rb index 34f59f35cf..6dcc14ce5d 100644 --- a/spec/unit/util/config_spec.rb +++ b/spec/unit/util/config_spec.rb @@ -33,35 +33,24 @@ end end - describe "ext_fact_dir attribute" do - around :each do |example| - Facter::Util::Config.ext_fact_dir = nil - example.run - Facter::Util::Config.ext_fact_dir = nil - end - - it "should allow setting and getting" do - filename = tmpfilename('test') - Facter::Util::Config.ext_fact_dir = filename - Facter::Util::Config.ext_fact_dir.should == filename - end + describe "data_dir" do it "should return the default value for linux" do Facter::Util::Config.stubs(:is_windows?).returns(false) - Facter::Util::Config.ext_fact_dir.should == "/usr/lib/facter/ext" + Facter::Util::Config.data_dir.should == "/usr/lib/facter" end it "should return the default value for windows 2008" do Facter::Util::Config.stubs(:is_windows?).returns(true) ENV.stubs(:[]).with("ProgramData").returns("C:\\ProgramData") - Facter::Util::Config.ext_fact_dir.should == "C:\\ProgramData/Puppetlabs/facter/ext" + Facter::Util::Config.data_dir.should == "C:\\ProgramData/Puppetlabs/facter" end it "should return the default value for windows 2003R2" do Facter::Util::Config.stubs(:is_windows?).returns(true) ENV.stubs(:[]).with("ProgramData").returns(nil) ENV.stubs(:[]).with("ALLUSERSPROFILE").returns("C:\\Documents and Settings\\All Users") - Facter::Util::Config.ext_fact_dir.should == "C:\\Documents and Settings\\All Users/Application Data/Puppetlabs/facter/ext" + Facter::Util::Config.data_dir.should == "C:\\Documents and Settings\\All Users/Application Data/Puppetlabs/facter" end end diff --git a/spec/unit/util/directory_loader_spec.rb b/spec/unit/util/directory_loader_spec.rb index 07af94fa24..5efc707b9e 100644 --- a/spec/unit/util/directory_loader_spec.rb +++ b/spec/unit/util/directory_loader_spec.rb @@ -6,6 +6,7 @@ describe Facter::Util::DirectoryLoader do include PuppetlabsSpec::Files + include FacterSpec::ConfigHelper subject { Facter::Util::DirectoryLoader.new(tmpdir('directory_loader')) } @@ -13,9 +14,21 @@ subject.directory.should be_instance_of(String) end - it "should default to '/usr/lib/facter/ext' for the directory" do - Facter::Util::DirectoryLoader.new.directory.should == "/usr/lib/facter/ext" - end + it "defaults to ext directory in data_dir" do + path = "data_dir" + given_a_configuration_of(:data_dir => path) + Facter::Util::DirectoryLoader.default_loader.directory.should == File.join(path, "ext") + end + + it "can be created with a given directory" do + Facter::Util::DirectoryLoader.loader_for("ext").directory.should == "ext" + end + + it "raises an error when the directory does not exist" do + missing_dir = "missing" + File.stubs(:directory?).with(missing_dir).returns(false) + expect { Facter::Util::DirectoryLoader.loader_for(missing_dir) }.should raise_error Facter::Util::DirectoryLoader::NoSuchDirectoryError + end it "should do nothing bad when dir doesn't exist" do fakepath = "/foobar/path" diff --git a/spec/unit/util/ip_spec.rb b/spec/unit/util/ip_spec.rb index 0feedf9899..af7274b76c 100755 --- a/spec/unit/util/ip_spec.rb +++ b/spec/unit/util/ip_spec.rb @@ -4,13 +4,15 @@ require 'facter/util/ip' describe Facter::Util::IP do + include FacterSpec::ConfigHelper + before :each do - Facter::Util::Config.stubs(:is_windows?).returns(false) + given_a_configuration_of(:is_windows => false) end [:freebsd, :linux, :netbsd, :openbsd, :sunos, :darwin, :"hp-ux", :"gnu/kfreebsd", :windows].each do |platform| it "should be supported on #{platform}" do - Facter::Util::Config.stubs(:is_windows?).returns(platform == :windows) + given_a_configuration_of(:is_windows => platform == :windows) Facter::Util::IP.supported_platforms.should be_include(platform) end end diff --git a/spec/unit/util/loader_spec.rb b/spec/unit/util/loader_spec.rb index 0c9e974bc8..127b2e62a3 100755 --- a/spec/unit/util/loader_spec.rb +++ b/spec/unit/util/loader_spec.rb @@ -219,16 +219,19 @@ def load_file(file) end describe "when loading all facts" do - before do + before :each do + @ext_loader = mock "External Loader" + Facter::Util::Config.ext_fact_loader = @ext_loader + @ext_loader.stubs(:load) @loader = Facter::Util::Loader.new - @loader.directory_loader.stubs(:load) @loader.stubs(:search_path).returns [] FileTest.stubs(:directory?).returns true + Dir.stubs(:entries).with("/usr/lib/facter/ext").returns [] end it "should load all facts from the directory loader" do - @loader.directory_loader.expects(:load) + @ext_loader.expects(:load) @loader.load_all end @@ -282,7 +285,7 @@ def load_file(file) Kernel.expects(:load).with(f) end - @loader.directory_loader.stubs(:load) + @ext_loader.stubs(:load) @loader.load_all @loader.loaded_files.should == %w{/one/dir/bar.rb /one/dir/foo.rb} diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index 48237cd797..b4bfae77fa 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -4,6 +4,8 @@ require 'facter/util/resolution' describe Facter::Util::Resolution do + include FacterSpec::ConfigHelper + it "should require a name" do lambda { Facter::Util::Resolution.new }.should raise_error(ArgumentError) end @@ -181,11 +183,12 @@ def handy_method() end describe "when given a string" do + [true, false ].each do |windows| describe "#{ (windows) ? '' : 'not' } on Windows" do before do - Facter::Util::Config.stubs(:is_windows?).returns(windows) + given_a_configuration_of(:is_windows => windows) end describe "stripping whitespace" do @@ -282,7 +285,7 @@ def handy_method() describe "and the code is a string" do describe "on windows" do before do - Facter::Util::Config.stubs(:is_windows?).returns(true) + given_a_configuration_of(:is_windows => true) end it "should return the result of executing the code" do @@ -301,7 +304,7 @@ def handy_method() describe "on non-windows systems" do before do - Facter::Util::Config.stubs(:is_windows?).returns(false) + given_a_configuration_of(:is_windows => false) end it "should return the result of executing the code" do From ba83da9fbfd7c5a63d89c0755b3ff92477646c9b Mon Sep 17 00:00:00 2001 From: Hailee Kenney Date: Fri, 15 Jun 2012 09:28:34 -0700 Subject: [PATCH 0891/3753] (#2157) Make it possible to disable custom facts Add command line option `--no-ext` which will disable loading of external facts. --- bin/facter | 2 +- lib/facter/application.rb | 29 +++++++++++++++++++---------- lib/facter/util/loader.rb | 4 ++-- lib/facter/util/nothing_loader.rb | 15 +++++++++++++++ 4 files changed, 37 insertions(+), 13 deletions(-) create mode 100644 lib/facter/util/nothing_loader.rb diff --git a/bin/facter b/bin/facter index 14b29503d8..99ec8bb455 100755 --- a/bin/facter +++ b/bin/facter @@ -6,7 +6,7 @@ # # = Usage # -# facter [-d|--debug] [-h|--help] [-p|--puppet] [-v|--version] [-y|--yaml] [-j|--json] [--ext DIR] [fact] [fact] [...] +# facter [-d|--debug] [-h|--help] [-p|--puppet] [-v|--version] [-y|--yaml] [-j|--json] [--ext DIR] [--no-ext] [fact] [fact] [...] # # = Description # diff --git a/lib/facter/application.rb b/lib/facter/application.rb index fde1bfc77c..708cef225e 100644 --- a/lib/facter/application.rb +++ b/lib/facter/application.rb @@ -1,5 +1,21 @@ module Facter module Application + + require 'facter/util/nothing_loader' + + def self.create_directory_loader(dir) + begin + Facter::Util::Config.ext_fact_loader = Facter::Util::DirectoryLoader.loader_for(dir) + rescue Facter::Util::DirectoryLoader::NoSuchDirectoryError => error + $stderr.puts "Specified external facts directory #{dir} does not exist." + exit(1) + end + end + + def self.create_nothing_loader + Facter::Util::Config.ext_fact_loader = Facter::Util::NothingLoader.new + end + def self.run(argv) require 'optparse' require 'facter' @@ -11,15 +27,7 @@ def self.run(argv) # Change location of external facts dir # Check here for valid ext_dir and exit program - if options[:ext] - begin - Facter::Util::Config.ext_fact_loader = Facter::Util::DirectoryLoader.loader_for(options[:ext]) - rescue Facter::Util::DirectoryLoader::NoSuchDirectoryError => error - $stderr.puts "Specified external facts directory #{options[:ext]} does not exist." - exit(1) - end - end - + # Create the facts hash that is printed to standard out. unless names.empty? facts = {} @@ -87,7 +95,8 @@ def self.parse(argv) opts.on("-j", "--json") { |v| options[:json] = v } end opts.on( "--trace") { |v| options[:trace] = v } - opts.on( "--ext DIR") { |v| options[:ext] = v } + opts.on( "--ext DIR") { |v| create_directory_loader(v) ; puts("Called create_directory_loader")} + opts.on( "--no-ext") { |v| create_nothing_loader ; puts("Called create_nothing_loader") } opts.on("-d", "--debug") { |v| Facter.debugging(1) } opts.on("-t", "--timing") { |v| Facter.timing(1) } opts.on("-p", "--puppet") { |v| load_puppet } diff --git a/lib/facter/util/loader.rb b/lib/facter/util/loader.rb index 26eee9f6f1..ae8b2c0e25 100644 --- a/lib/facter/util/loader.rb +++ b/lib/facter/util/loader.rb @@ -8,7 +8,7 @@ class Facter::Util::Loader def initialize @loaded = [] @valid_path = {} - @directory_loader = Facter::Util::Config.ext_fact_loader + @ext_fact_loader = Facter::Util::Config.ext_fact_loader end # Load all resolutions for a single fact. @@ -36,7 +36,7 @@ def load_all load_env - @directory_loader.load + @ext_fact_loader.load search_path.each do |dir| next unless FileTest.directory?(dir) diff --git a/lib/facter/util/nothing_loader.rb b/lib/facter/util/nothing_loader.rb new file mode 100644 index 0000000000..dfb32ddea3 --- /dev/null +++ b/lib/facter/util/nothing_loader.rb @@ -0,0 +1,15 @@ +# An external fact loader that doesn't load anything + +# This makes it possible to disable loading +# of external facts + +module Facter + module Util + + class NothingLoader + + def load + end + end + end +end \ No newline at end of file From 9f3d8a94e719104b23db41cfc5d7a22912a45133 Mon Sep 17 00:00:00 2001 From: Hailee Kenney Date: Fri, 22 Jun 2012 17:10:27 -0700 Subject: [PATCH 0892/3753] (#2157) Make external facts compatable with facts_dot_d Prior to this commit, this implementation of external facts read from different default directories then facts_dot_d. Add functionality so that this implementation reads from the same directories so that external facts are backwards compatable. --- bin/facter | 2 +- lib/facter/application.rb | 4 ++-- lib/facter/util/config.rb | 26 ++++++++++++------------- lib/facter/util/directory_loader.rb | 9 +++++++-- spec/spec_helper.rb | 2 +- spec/unit/util/config_spec.rb | 13 ++++++------- spec/unit/util/directory_loader_spec.rb | 6 ------ 7 files changed, 30 insertions(+), 32 deletions(-) diff --git a/bin/facter b/bin/facter index 99ec8bb455..6e3b3bf321 100755 --- a/bin/facter +++ b/bin/facter @@ -6,7 +6,7 @@ # # = Usage # -# facter [-d|--debug] [-h|--help] [-p|--puppet] [-v|--version] [-y|--yaml] [-j|--json] [--ext DIR] [--no-ext] [fact] [fact] [...] +# facter [-d|--debug] [-h|--help] [-p|--puppet] [-v|--version] [-y|--yaml] [-j|--json] [--external-dir DIR] [--no-external-dir] [fact] [fact] [...] # # = Description # diff --git a/lib/facter/application.rb b/lib/facter/application.rb index 708cef225e..b640608259 100644 --- a/lib/facter/application.rb +++ b/lib/facter/application.rb @@ -95,8 +95,8 @@ def self.parse(argv) opts.on("-j", "--json") { |v| options[:json] = v } end opts.on( "--trace") { |v| options[:trace] = v } - opts.on( "--ext DIR") { |v| create_directory_loader(v) ; puts("Called create_directory_loader")} - opts.on( "--no-ext") { |v| create_nothing_loader ; puts("Called create_nothing_loader") } + opts.on( "--external-dir DIR") { |v| create_directory_loader(v) } + opts.on( "--no-external-dir") { |v| create_nothing_loader } opts.on("-d", "--debug") { |v| Facter.debugging(1) } opts.on("-t", "--timing") { |v| Facter.timing(1) } opts.on("-p", "--puppet") { |v| load_puppet } diff --git a/lib/facter/util/config.rb b/lib/facter/util/config.rb index 9eb7ebe69c..36435cdc2e 100644 --- a/lib/facter/util/config.rb +++ b/lib/facter/util/config.rb @@ -20,21 +20,21 @@ def self.is_mac? def self.is_windows? RbConfig::CONFIG['host_os'] =~ /mswin|win32|dos|mingw|cygwin/i end + + def self.windows_data_dir + if Dir.const_defined? 'COMMON_APPDATA' then + Dir::COMMON_APPDATA + else + nil + end + end - # The basedir to use for windows data - def self.data_dir - if is_windows? - # If neither environment variable is set - fail. - if not ENV["ProgramData"] and not ENV["ALLUSERSPROFILE"] then - raise "Neither environment variables ProgramData or ALLUSERSPROFILE " + - "are defined. Facter is unable to determine a default dirctory for " + - "its uses." - end - base_dir = ENV["ProgramData"] || - File.join(ENV["ALLUSERSPROFILE"], "Application Data") - File.join(base_dir, "Puppetlabs", "facter") + def self.external_facts_dirs + windows_dir = windows_data_dir + if windows_dir.nil? then + ["/etc/facter/facts.d", "/etc/puppetlabs/facter/facts.d"] else - "/usr/lib/facter" + [File.join(windows_dir, 'PuppetLabs', 'facter', 'facts.d')] end end end diff --git a/lib/facter/util/directory_loader.rb b/lib/facter/util/directory_loader.rb index 67f90cafe1..1ed64b8f5b 100644 --- a/lib/facter/util/directory_loader.rb +++ b/lib/facter/util/directory_loader.rb @@ -5,6 +5,7 @@ require 'facter/util/parser' require 'facter/util/config' +require 'facter/util/composite_loader' class Facter::Util::DirectoryLoader require 'yaml' @@ -31,8 +32,12 @@ def self.loader_for(dir) end def self.default_loader - dir = File.join(Facter::Util::Config.data_dir, "ext") - Facter::Util::DirectoryLoader.new(dir) + dir = [] + dir[0] = Facter::Util::DirectoryLoader.new(Facter::Util::Config.external_facts_dirs[0]) + if (Facter::Util::Config.external_facts_dirs.size > 1) + dir[1] = Facter::Util::DirectoryLoader.new(Facter::Util::Config.external_facts_dirs[1]) + end + Facter::Util::CompositeLoader.new(dir) end # Load facts from files in fact directory using the relevant parser classes to diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 0d85b3a74b..10cbf04f52 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -44,7 +44,7 @@ module FacterSpec module ConfigHelper def given_a_configuration_of(config) Facter::Util::Config.stubs(:is_windows?).returns(config[:is_windows]) - Facter::Util::Config.stubs(:data_dir).returns(config[:data_dir] || "data_dir") + Facter::Util::Config.stubs(:external_facts_dir).returns(config[:external_facts_dir] || "data_dir") end end end diff --git a/spec/unit/util/config_spec.rb b/spec/unit/util/config_spec.rb index 6dcc14ce5d..ffb3012ef3 100644 --- a/spec/unit/util/config_spec.rb +++ b/spec/unit/util/config_spec.rb @@ -33,24 +33,23 @@ end end - describe "data_dir" do + describe "external_facts_dirs" do it "should return the default value for linux" do Facter::Util::Config.stubs(:is_windows?).returns(false) - Facter::Util::Config.data_dir.should == "/usr/lib/facter" + Facter::Util::Config.external_facts_dirs.should == ["/etc/facter/facts.d", "/etc/puppetlabs/facter/facts.d"] end it "should return the default value for windows 2008" do Facter::Util::Config.stubs(:is_windows?).returns(true) - ENV.stubs(:[]).with("ProgramData").returns("C:\\ProgramData") - Facter::Util::Config.data_dir.should == "C:\\ProgramData/Puppetlabs/facter" + Facter::Util::Config.stubs(:windows_data_dir).returns("C:\\ProgramData") + Facter::Util::Config.external_facts_dirs.should == [File.join("C:\\ProgramData", 'PuppetLabs', 'facter', 'facts.d')] end it "should return the default value for windows 2003R2" do Facter::Util::Config.stubs(:is_windows?).returns(true) - ENV.stubs(:[]).with("ProgramData").returns(nil) - ENV.stubs(:[]).with("ALLUSERSPROFILE").returns("C:\\Documents and Settings\\All Users") - Facter::Util::Config.data_dir.should == "C:\\Documents and Settings\\All Users/Application Data/Puppetlabs/facter" + Facter::Util::Config.stubs(:windows_data_dir).returns("C:\\Documents") + Facter::Util::Config.external_facts_dirs.should == [File.join("C:\\Documents", 'PuppetLabs', 'facter', 'facts.d')] end end diff --git a/spec/unit/util/directory_loader_spec.rb b/spec/unit/util/directory_loader_spec.rb index 5efc707b9e..488577492e 100644 --- a/spec/unit/util/directory_loader_spec.rb +++ b/spec/unit/util/directory_loader_spec.rb @@ -14,12 +14,6 @@ subject.directory.should be_instance_of(String) end - it "defaults to ext directory in data_dir" do - path = "data_dir" - given_a_configuration_of(:data_dir => path) - Facter::Util::DirectoryLoader.default_loader.directory.should == File.join(path, "ext") - end - it "can be created with a given directory" do Facter::Util::DirectoryLoader.loader_for("ext").directory.should == "ext" end From e38134852c5aa37062de57f90e9c9ba82d3a6891 Mon Sep 17 00:00:00 2001 From: Hailee Kenney Date: Fri, 22 Jun 2012 17:13:59 -0700 Subject: [PATCH 0893/3753] (#2157) Add composite loader Add composite loader to enable multiple default directories. --- lib/facter/util/composite_loader.rb | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 lib/facter/util/composite_loader.rb diff --git a/lib/facter/util/composite_loader.rb b/lib/facter/util/composite_loader.rb new file mode 100644 index 0000000000..57fd4ba538 --- /dev/null +++ b/lib/facter/util/composite_loader.rb @@ -0,0 +1,20 @@ +# A composite loader that allows for more than one +# default directory loader + +require 'facter/util/directory_loader' + +class Facter::Util::CompositeLoader + + def initialize(dir_loaders) + @directory_loaders = [] + @directory_loaders[0] = dir_loaders[0] + @directory_loaders[1] = dir_loaders[1] + end + + def load + @directory_loaders[0].load + @directory_loaders[1].load + end +end + + \ No newline at end of file From 028bb31391920523a1bf778595b25e7e24c9933b Mon Sep 17 00:00:00 2001 From: Hailee Kenney Date: Mon, 25 Jun 2012 15:10:55 -0700 Subject: [PATCH 0894/3753] (#2157) Refactor parser Refactor parser so that if a file cannot be parsed, it prints a debug message instead of exiting with an error. This will take care of the issue with editor swap files. --- lib/facter/util/composite_loader.rb | 11 +- lib/facter/util/directory_loader.rb | 24 ++-- lib/facter/util/parser.rb | 146 +++++++++++------------- spec/unit/util/directory_loader_spec.rb | 6 +- spec/unit/util/loader_spec.rb | 2 +- spec/unit/util/parser_spec.rb | 75 ++++-------- 6 files changed, 114 insertions(+), 150 deletions(-) diff --git a/lib/facter/util/composite_loader.rb b/lib/facter/util/composite_loader.rb index 57fd4ba538..5dc7b284ef 100644 --- a/lib/facter/util/composite_loader.rb +++ b/lib/facter/util/composite_loader.rb @@ -1,19 +1,14 @@ # A composite loader that allows for more than one # default directory loader -require 'facter/util/directory_loader' - class Facter::Util::CompositeLoader - def initialize(dir_loaders) - @directory_loaders = [] - @directory_loaders[0] = dir_loaders[0] - @directory_loaders[1] = dir_loaders[1] + def initialize(loaders) + @loaders = loaders end def load - @directory_loaders[0].load - @directory_loaders[1].load + @loaders.each { |loader| loader.load } end end diff --git a/lib/facter/util/directory_loader.rb b/lib/facter/util/directory_loader.rb index 1ed64b8f5b..f0593b850b 100644 --- a/lib/facter/util/directory_loader.rb +++ b/lib/facter/util/directory_loader.rb @@ -1,4 +1,13 @@ -# A Facter plugin that loads facts from /etc/facter/facts.d. +# A Facter plugin that loads external facts. +# +# Default Unix Directories: +# /etc/facter/facts.d", "/etc/puppetlabs/facter/facts.d" +# +# Default Windows Direcotires: +# C:\ProgramData\PuppetLabs\facter\facts.d (2008) +# C:\Documents\PuppetLabs\facter\facts.d (2003) +# +# Can also load from command-line specified directory # # Facts can be in the form of JSON, YAML or Text files # and any executable that returns key=value pairs. @@ -20,6 +29,7 @@ class NoSuchDirectoryError < Exception attr_reader :directory def initialize(dir) + puts(dir) @directory = dir end @@ -32,19 +42,17 @@ def self.loader_for(dir) end def self.default_loader - dir = [] - dir[0] = Facter::Util::DirectoryLoader.new(Facter::Util::Config.external_facts_dirs[0]) - if (Facter::Util::Config.external_facts_dirs.size > 1) - dir[1] = Facter::Util::DirectoryLoader.new(Facter::Util::Config.external_facts_dirs[1]) - end - Facter::Util::CompositeLoader.new(dir) + loaders = Facter::Util::Config.external_facts_dirs.collect do |dir| + Facter::Util::DirectoryLoader.new(dir) + end + Facter::Util::CompositeLoader.new(loaders) end # Load facts from files in fact directory using the relevant parser classes to # parse them. def load entries.each do |file| - parser = Facter::Util::Parser.new(file) + parser = Facter::Util::Parser.parser_for(file) if parser == nil next end diff --git a/lib/facter/util/parser.rb b/lib/facter/util/parser.rb index 4c37de22bb..e07a99c0da 100644 --- a/lib/facter/util/parser.rb +++ b/lib/facter/util/parser.rb @@ -4,75 +4,47 @@ # Parsers must subclass this class and provide their own #results method. require 'facter/util/json' -class Facter::Util::Parser - attr_reader :filename - - class << self - # Retrieve the set extension, if any - attr_reader :extension - end - - # Used by subclasses. Registers +ext+ as the extension to match. - # +module Facter::Util::Parser + @parsers = [] + # For support mutliple extensions you can pass an array of extensions as # +ext+. - def self.matches_extension(ext) + def self.extension_matches?(filename, ext) if ext.class == String then - @extension = ext.downcase + extension = ext.downcase elsif ext.class == Array then - @extension = ext.collect {|x| x.downcase } + extension = ext.collect {|x| x.downcase } end + [extension].flatten.to_a.include?(file_extension(filename).downcase) end - + def self.file_extension(filename) File.extname(filename).sub(".", '') end - - def self.inherited(klass) - @subclasses ||= [] - @subclasses << klass - end - - def self.matches?(filename) - raise "Must override the 'matches?' method for #{self}" unless extension - - [extension].flatten.to_a.include?(file_extension(filename).downcase) - end - - def self.subclasses - @subclasses ||= [] - @subclasses + + def self.register(klass, &suitable) + @parsers << [klass, suitable] end - def self.which_parser(filename) - unless klass = subclasses.detect {|k| k.matches?(filename) } - raise ArgumentError, "Could not find parser for #{filename}" + def self.parser_for(filename) + registration = @parsers.detect { |k| k[1].call(filename) } + + if registration.nil? + NothingParser.new + else + registration[0].new(filename) end - klass - end - - def self.new(filename) - klass = which_parser(filename) - - object = klass.allocate - object.send(:initialize, filename) - - object end - def initialize(filename) - @filename = filename - end - - # This method must be overwriten by subclasses to provide - # the results (as a hash) of parsing the filename. - def results - raise "Must override the 'results' method for #{self}" + class Base + attr_reader :filename + + def initialize(filename) + @filename = filename + end end - class YamlParser < self - matches_extension "yaml" - + class YamlParser < Base def results require 'yaml' @@ -81,9 +53,15 @@ def results Facter.warn("Failed to handle #{filename} as yaml facts: #{e.class}: #{e}") end end + + register(YamlParser) do |filename| + extension_matches?(filename, "yaml") + end - class TextParser < self - matches_extension "txt" + class TextParser < Base + def self.matches?(filename) + extension_matches?(filename, "txt") + end def results result = {} @@ -98,10 +76,16 @@ def results Facter.warn("Failed to handle #{filename} as text facts: #{e.class}: #{e}") end end + + register(TextParser) do |filename| + extension_matches?(filename, "txt") + end if Facter.json? - class JsonParser < self - matches_extension "json" + class JsonParser < Base + def self.matches?(filename) + extension_matches?(filename, "json") + end def results attempts = 0 @@ -115,18 +99,13 @@ def results JSON.load(File.read(filename)) end end - end - - class ScriptParser < self - if Facter::Util::Config.is_windows? - matches_extension %w{bat com exe} - else - # Returns true if file is executable. - def self.matches?(file) - File.executable?(file) - end + + register(JsonParser) do |filename| + extension_matches?(filename, "json") end + end + class ScriptParser < Base def results output = Facter::Util::Resolution.exec(filename) @@ -144,6 +123,14 @@ def results end end + register(ScriptParser) do |filename| + if Facter::Util::Config.is_windows? + extension_matches?(filename, %w{bat com exe}) + else + File.executable?(filename) + end + end + # Executes and parses the key value output of Powershell scripts # # Before you can run unsigned ps1 scripts it requires a change to execution @@ -152,18 +139,7 @@ def results # Set-ExecutionPolicy RemoteSigned -Scope LocalMachine # Set-ExecutionPolicy RemoteSigned -Scope CurrentUser # - class PowershellParser < self - matches_extension "ps1" - - # Only return true if this is a windows box - def self.matches?(filename) - if Facter::Util::Config.is_windows? - super(filename) - else - return false - end - end - + class PowershellParser < Base # Returns a hash of facts from powershell output def results shell_command = "powershell -File #{filename}" @@ -182,4 +158,16 @@ def results Facter.debug(e.backtrace.join("\n\t")) end end + + register(PowershellParser) do |filename| + Facter::Util::Config.is_windows? && extension_matches?(filename, "ps1") + end + + # A parser that is used when there is no other parser that can handle the file + # The return from results indicates to the caller the file was not parsed correctly. + class NothingParser + def results + false + end + end end diff --git a/spec/unit/util/directory_loader_spec.rb b/spec/unit/util/directory_loader_spec.rb index 488577492e..1272442778 100644 --- a/spec/unit/util/directory_loader_spec.rb +++ b/spec/unit/util/directory_loader_spec.rb @@ -63,11 +63,13 @@ end end - it "should fail when trying to parse unknown file types" do + it "should warn when trying to parse unknown file types" do file = File.join(subject.directory, "file.unknownfiletype") File.open(file, "w") { |f| f.print "stuff=bar" } + + Facter.expects(:warn).with(regexp_matches(/file.unknownfiletype/)) - expect { subject.load }.should raise_error(ArgumentError) + subject.load end end end diff --git a/spec/unit/util/loader_spec.rb b/spec/unit/util/loader_spec.rb index 127b2e62a3..3e7e8a9041 100755 --- a/spec/unit/util/loader_spec.rb +++ b/spec/unit/util/loader_spec.rb @@ -227,7 +227,7 @@ def load_file(file) @loader.stubs(:search_path).returns [] FileTest.stubs(:directory?).returns true - Dir.stubs(:entries).with("/usr/lib/facter/ext").returns [] + Dir.stubs(:entries).with("/etc/facter/facts.d").returns [] end it "should load all facts from the directory loader" do diff --git a/spec/unit/util/parser_spec.rb b/spec/unit/util/parser_spec.rb index caa5975902..51d53b3879 100644 --- a/spec/unit/util/parser_spec.rb +++ b/spec/unit/util/parser_spec.rb @@ -8,56 +8,31 @@ describe Facter::Util::Parser do include PuppetlabsSpec::Files - it "should fail when asked to parse a file type it does not support" do - lambda { Facter::Util::Parser.new("/my/file.foobar") }.should raise_error(ArgumentError) - end - - describe "matches? function" do + describe "extension_matches? function" do it "should match extensions when subclass uses match_extension" do - class TestParser < Facter::Util::Parser - matches_extension "foobar" - end - - TestParser.matches?("myfile.foobar").should == true + Facter::Util::Parser.extension_matches?("myfile.foobar", "foobar").should == true end it "should match extensions when subclass uses match_extension with an array" do - class TestParser < Facter::Util::Parser - matches_extension ["ext1","ext2","ext3"] - end - - TestParser.matches?("myfile.ext1").should == true - TestParser.matches?("myfile.ext2").should == true - TestParser.matches?("myfile.ext3").should == true + Facter::Util::Parser.extension_matches?("myfile.ext1", ["ext1","ext2","ext3"]).should == true + Facter::Util::Parser.extension_matches?("myfile.ext2", ["ext1","ext2","ext3"]).should == true + Facter::Util::Parser.extension_matches?("myfile.ext3", ["ext1","ext2","ext3"]).should == true end it "should match extension ignoring case on file" do - class TestParser < Facter::Util::Parser - matches_extension "ext1" - end - - TestParser.matches?("myfile.EXT1").should == true - TestParser.matches?("myfile.ExT1").should == true - TestParser.matches?("myfile.exT1").should == true + Facter::Util::Parser.extension_matches?("myfile.EXT1", "ext1").should == true + Facter::Util::Parser.extension_matches?("myfile.ExT1", "ext1").should == true + Facter::Util::Parser.extension_matches?("myfile.exT1", "ext1").should == true end it "should match extension ignoring case for match_extension" do - class TestParser < Facter::Util::Parser - matches_extension "EXT1" - end - - TestParser.matches?("myfile.EXT1").should == true - TestParser.matches?("myfile.ExT1").should == true - TestParser.matches?("myfile.exT1").should == true + Facter::Util::Parser.extension_matches?("myfile.EXT1", "EXT1").should == true + Facter::Util::Parser.extension_matches?("myfile.ExT1", "EXT1").should == true + Facter::Util::Parser.extension_matches?("myfile.exT1", "EXT1").should == true end end describe "yaml" do - subject { Facter::Util::Parser::YamlParser } - it "should match the 'yaml' extension" do - subject.extension.should == "yaml" - end - it "should return a hash of whatever is stored on disk" do file = tmpfilename('parser') + ".yaml" @@ -65,7 +40,7 @@ class TestParser < Facter::Util::Parser File.open(file, "w") { |f| f.print YAML.dump(data) } - Facter::Util::Parser.new(file).results.should == data + Facter::Util::Parser.parser_for(file).results.should == data end it "should handle exceptions and warn" do @@ -75,17 +50,12 @@ class TestParser < Facter::Util::Parser File.open(file, "w") { |f| f.print "}" } Facter.expects(:warn) - lambda { Facter::Util::Parser.new("/some/path/that/doesn't/exist.yaml").results }.should_not raise_error + lambda { Facter::Util::Parser.parser_for("/some/path/that/doesn't/exist.yaml").results }.should_not raise_error end end if Facter.json? describe "json" do - subject { Facter::Util::Parser::JsonParser } - it "should match the 'json' extension" do - subject.extension.should == "json" - end - it "should return a hash of whatever is stored on disk" do file = tmpfilename('parser') + ".json" @@ -93,17 +63,12 @@ class TestParser < Facter::Util::Parser File.open(file, "w") { |f| f.print data.to_json } - Facter::Util::Parser.new(file).results.should == data + Facter::Util::Parser.parser_for(file).results.should == data end end end describe "txt" do - subject { Facter::Util::Parser::TextParser } - it "should match the 'txt' extension" do - subject.extension.should == "txt" - end - it "should return a hash of whatever is stored on disk" do file = tmpfilename('parser') + ".txt" @@ -111,7 +76,7 @@ class TestParser < Facter::Util::Parser File.open(file, "w") { |f| f.print data } - Facter::Util::Parser.new(file).results.should == {"one" => "two", "three" => "four"} + Facter::Util::Parser.parser_for(file).results.should == {"one" => "two", "three" => "four"} end it "should ignore any non-setting lines" do @@ -121,7 +86,7 @@ class TestParser < Facter::Util::Parser File.open(file, "w") { |f| f.print data } - Facter::Util::Parser.new(file).results.should == {"one" => "two", "three" => "four"} + Facter::Util::Parser.parser_for(file).results.should == {"one" => "two", "three" => "four"} end end @@ -138,7 +103,13 @@ class TestParser < Facter::Util::Parser end it "should return a hash of whatever is returned by the executable" do - Facter::Util::Parser.new(@script).results.should == {"one" => "two", "three" => "four"} + Facter::Util::Parser.parser_for(@script).results.should == {"one" => "two", "three" => "four"} + end + end + + describe "nothing parser" do + it "uses the nothing parser when there is no other parser" do + Facter::Util::Parser.parser_for("this.is.not.valid").results.should == false end end end From a67d440cac2f608e483099d15ea3d9a51a976124 Mon Sep 17 00:00:00 2001 From: Todd Zullinger Date: Thu, 21 Jun 2012 15:10:11 -0400 Subject: [PATCH 0895/3753] Improve rubysitedir fact With ruby-1.9.3 -- at least on Fedora 17 -- trawling through the library paths looking for the directory does not work. Using RbConfig seems more reliable and much simpler. --- lib/facter/rubysitedir.rb | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/lib/facter/rubysitedir.rb b/lib/facter/rubysitedir.rb index 73aa2152fc..930df383af 100644 --- a/lib/facter/rubysitedir.rb +++ b/lib/facter/rubysitedir.rb @@ -2,17 +2,11 @@ # # Purpose: Returns Ruby's site library directory. # -# Resolution: Works out the version to major/minor (1.8, 1.9, etc), then joins -# that with all the $: library paths. -# -# Caveats: -# + +require 'rbconfig' Facter.add :rubysitedir do setcode do - version = RUBY_VERSION.to_s.sub(/\.\d+$/, '') - $:.find do |dir| - dir =~ /#{File.join("site_ruby", version)}$/ - end + RbConfig::CONFIG["sitelibdir"] end end From 008cc6cdf79df1589c060e2a088caffc9cf6da77 Mon Sep 17 00:00:00 2001 From: Hailee Kenney Date: Fri, 29 Jun 2012 14:20:36 -0700 Subject: [PATCH 0896/3753] (#2157) Remove support for executable external facts on Windows Due to lack of tests for this functionality, and the fact that exploritory testing would require a lot of time and setup, remove support for Windows executable external facts until it can be properly tested. --- bin/facter | 2 +- lib/facter/util/composite_loader.rb | 3 +-- lib/facter/util/config.rb | 5 ++++ lib/facter/util/directory_loader.rb | 7 +++--- lib/facter/util/nothing_loader.rb | 3 ++- lib/facter/util/parser.rb | 37 +---------------------------- 6 files changed, 13 insertions(+), 44 deletions(-) diff --git a/bin/facter b/bin/facter index 6e3b3bf321..f63d6a8e8d 100755 --- a/bin/facter +++ b/bin/facter @@ -42,7 +42,7 @@ # timing:: # Enable timing. # -# ext:: +# facts.d:: # The directory to use for external facts. # # = Example diff --git a/lib/facter/util/composite_loader.rb b/lib/facter/util/composite_loader.rb index 5dc7b284ef..880ee23ef1 100644 --- a/lib/facter/util/composite_loader.rb +++ b/lib/facter/util/composite_loader.rb @@ -11,5 +11,4 @@ def load @loaders.each { |loader| loader.load } end end - - \ No newline at end of file + \ No newline at end of file diff --git a/lib/facter/util/config.rb b/lib/facter/util/config.rb index 36435cdc2e..27fb06d198 100644 --- a/lib/facter/util/config.rb +++ b/lib/facter/util/config.rb @@ -38,3 +38,8 @@ def self.external_facts_dirs end end end + +if Facter::Util::Config.is_windows? + require 'rubygems' + require 'win32/dir' +end diff --git a/lib/facter/util/directory_loader.rb b/lib/facter/util/directory_loader.rb index f0593b850b..076ec73bdd 100644 --- a/lib/facter/util/directory_loader.rb +++ b/lib/facter/util/directory_loader.rb @@ -1,11 +1,11 @@ # A Facter plugin that loads external facts. # # Default Unix Directories: -# /etc/facter/facts.d", "/etc/puppetlabs/facter/facts.d" +# /etc/facter/facts.d, /etc/puppetlbas/facter/facts.d # # Default Windows Direcotires: -# C:\ProgramData\PuppetLabs\facter\facts.d (2008) -# C:\Documents\PuppetLabs\facter\facts.d (2003) +# C:\ProgramData\Puppetlabs\facter\facts.d (2008) +# C:\Documents and Settings\All Users\Application Data\Puppetlabs\facter\facts.d (2003) # # Can also load from command-line specified directory # @@ -29,7 +29,6 @@ class NoSuchDirectoryError < Exception attr_reader :directory def initialize(dir) - puts(dir) @directory = dir end diff --git a/lib/facter/util/nothing_loader.rb b/lib/facter/util/nothing_loader.rb index dfb32ddea3..3ff513e299 100644 --- a/lib/facter/util/nothing_loader.rb +++ b/lib/facter/util/nothing_loader.rb @@ -12,4 +12,5 @@ def load end end end -end \ No newline at end of file +end + \ No newline at end of file diff --git a/lib/facter/util/parser.rb b/lib/facter/util/parser.rb index e07a99c0da..970de573c3 100644 --- a/lib/facter/util/parser.rb +++ b/lib/facter/util/parser.rb @@ -124,45 +124,10 @@ def results end register(ScriptParser) do |filename| - if Facter::Util::Config.is_windows? - extension_matches?(filename, %w{bat com exe}) - else - File.executable?(filename) - end + File.executable?(filename) end - # Executes and parses the key value output of Powershell scripts - # - # Before you can run unsigned ps1 scripts it requires a change to execution - # policy: - # - # Set-ExecutionPolicy RemoteSigned -Scope LocalMachine - # Set-ExecutionPolicy RemoteSigned -Scope CurrentUser - # - class PowershellParser < Base - # Returns a hash of facts from powershell output - def results - shell_command = "powershell -File #{filename}" - output = Facter::Util::Resolution.exec(shell_command) - - result = {} - output.split("\n").each do |line| - if line =~ /^(.+)=(.+)$/ - result[$1] = $2 - end - end - - result - rescue Exception => e - Facter.warn("Failed to handle #{filename} as powershell facts: #{e.class}: #{e}") - Facter.debug(e.backtrace.join("\n\t")) - end - end - register(PowershellParser) do |filename| - Facter::Util::Config.is_windows? && extension_matches?(filename, "ps1") - end - # A parser that is used when there is no other parser that can handle the file # The return from results indicates to the caller the file was not parsed correctly. class NothingParser From 84d8393d49dea2158428512885883e9f0ad9cdda Mon Sep 17 00:00:00 2001 From: Hailee Kenney Date: Fri, 6 Jul 2012 15:31:09 -0700 Subject: [PATCH 0897/3753] (#2157) Ensure externals always overwrite other facts Before this commit, external facts were not being loaded when a specific fact was asked for. This meant that asking for all facts would return different results than asking for an individual fact. Now, all requests for facts load all exeternal facts, and external facts have a set weight that is very high. --- lib/facter/util/collection.rb | 21 +++++++++++++--- lib/facter/util/directory_loader.rb | 8 +++++-- lib/facter/util/loader.rb | 5 +--- spec/unit/blockdevices_spec.rb | 2 ++ spec/unit/util/collection_spec.rb | 32 +++++++++++++++++++++++++ spec/unit/util/directory_loader_spec.rb | 13 +++++++++- spec/unit/util/loader_spec.rb | 12 ++-------- 7 files changed, 73 insertions(+), 20 deletions(-) diff --git a/lib/facter/util/collection.rb b/lib/facter/util/collection.rb index 0e08ae1dd1..db6f1b72a2 100644 --- a/lib/facter/util/collection.rb +++ b/lib/facter/util/collection.rb @@ -60,6 +60,7 @@ def add(name, options = {}, &block) # Iterate across all of the facts. def each + load_all @facts.each do |name, fact| value = fact.value unless value.nil? @@ -73,8 +74,8 @@ def fact(name) name = canonize(name) # Try to load the fact if necessary - loader.load(name) unless @facts[name] - + load(name) unless @facts[name] + # Try HARDER loader.load_all unless @facts[name] @@ -96,12 +97,19 @@ def initialize # Return a list of all of the facts. def list + load_all return @facts.keys end + def load(name) + loader.load(name) + ext_loader.load + end + # Load all known facts. def load_all loader.load_all + ext_loader.load end # The thing that loads facts if we don't have them. @@ -111,7 +119,14 @@ def loader end @loader end - + + def ext_loader + unless defined?(@ext_loader) + @ext_loader = Facter::Util::Config.ext_fact_loader + end + @ext_loader + end + # Return a hash of all of our facts. def to_hash @facts.inject({}) do |h, ary| diff --git a/lib/facter/util/directory_loader.rb b/lib/facter/util/directory_loader.rb index 076ec73bdd..bf38aebe0a 100644 --- a/lib/facter/util/directory_loader.rb +++ b/lib/facter/util/directory_loader.rb @@ -24,7 +24,11 @@ class NoSuchDirectoryError < Exception # A list of extensions to ignore in fact directory. SKIP_EXTENSIONS = %w{bak orig} - + + # This value makes it highly likely that external facts will take + # precedence over all other facts + EXTERNAL_FACT_WEIGHT = 10000 + # Directory for fact loading attr_reader :directory @@ -62,7 +66,7 @@ def load elsif data == {} or data == nil Facter.warn "Fact file #{file} was parsed but returned an empty data set" else - data.each { |p,v| Facter.add(p, :value => v) } + data.each { |p,v| Facter.add(p, :value => v) { has_weight(EXTERNAL_FACT_WEIGHT) } } end end end diff --git a/lib/facter/util/loader.rb b/lib/facter/util/loader.rb index ae8b2c0e25..31402bb9c2 100644 --- a/lib/facter/util/loader.rb +++ b/lib/facter/util/loader.rb @@ -8,7 +8,6 @@ class Facter::Util::Loader def initialize @loaded = [] @valid_path = {} - @ext_fact_loader = Facter::Util::Config.ext_fact_loader end # Load all resolutions for a single fact. @@ -16,7 +15,7 @@ def load(fact) # Now load from the search path shortname = fact.to_s.downcase load_env(shortname) - + filename = shortname + ".rb" search_path.each do |dir| # Load individual files @@ -36,8 +35,6 @@ def load_all load_env - @ext_fact_loader.load - search_path.each do |dir| next unless FileTest.directory?(dir) diff --git a/spec/unit/blockdevices_spec.rb b/spec/unit/blockdevices_spec.rb index 5c331721ed..54620b8e07 100644 --- a/spec/unit/blockdevices_spec.rb +++ b/spec/unit/blockdevices_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' require 'facter' +require 'facter/util/nothing_loader' describe "Block device facts" do @@ -20,6 +21,7 @@ describe "with valid entries" do before :each do + Facter::Util::Config.ext_fact_loader = Facter::Util::NothingLoader.new Facter.fact(:kernel).stubs(:value).returns("Linux") File.stubs(:exist?).with('/sys/block/').returns(true) diff --git a/spec/unit/util/collection_spec.rb b/spec/unit/util/collection_spec.rb index a4560c1f4e..942b09c101 100755 --- a/spec/unit/util/collection_spec.rb +++ b/spec/unit/util/collection_spec.rb @@ -273,4 +273,36 @@ @coll.fact("one") end end + + describe "external facts" do + before :each do + Facter::Util::Config.ext_fact_loader = SingleFactLoader.new(:test_fact, "fact value") + end + + it "loads when a specific fact is requested" do + Facter.collection.fact(:test_fact).value.should == "fact value" + end + + it "loads when facts are listed" do + Facter.collection.list.should == [:test_fact] + end + + it "loads when all facts are iterated over" do + facts = [] + Facter.collection.each { |fact_name, fact_value| facts << [fact_name, fact_value] } + + facts.should == [["test_fact", "fact value"]] + end + end + + class SingleFactLoader + def initialize(name, value) + @name = name + @value = value + end + + def load + Facter.add(@name, :value => @value) + end + end end diff --git a/spec/unit/util/directory_loader_spec.rb b/spec/unit/util/directory_loader_spec.rb index 1272442778..a19dcb7c96 100644 --- a/spec/unit/util/directory_loader_spec.rb +++ b/spec/unit/util/directory_loader_spec.rb @@ -34,7 +34,7 @@ describe "when loading facts from disk" do it "should be able to load files from disk and set facts" do data = {"f1" => "one", "f2" => "two"} - file = File.join(subject.directory, "data" + ".yaml") + file = File.join(subject.directory, "data.yaml") File.open(file, "w") { |f| f.print YAML.dump(data) } subject.load @@ -71,5 +71,16 @@ subject.load end + + it "external facts should almost always precedence over all other facts" do + Facter.add("f1", :value => "lower_weight_fact") { has_weight(Facter::Util::DirectoryLoader::EXTERNAL_FACT_WEIGHT - 1) } + data = {"f1" => "external_fact"} + file = File.join(subject.directory, "data.yaml") + File.open(file, "w") { |f| f.print YAML.dump(data) } + + subject.load + + Facter.value("f1").should == "external_fact" + end end end diff --git a/spec/unit/util/loader_spec.rb b/spec/unit/util/loader_spec.rb index 3e7e8a9041..7e3d33590c 100755 --- a/spec/unit/util/loader_spec.rb +++ b/spec/unit/util/loader_spec.rb @@ -177,7 +177,7 @@ def load_file(file) it 'should load any ruby files in directories matching the fact name in the search path in sorted order regardless of the order returned by Dir.entries' do @loader = TestLoader.new - + @loader.stubs(:search_path).returns %w{/one/dir} FileTest.stubs(:exist?).returns false FileTest.stubs(:directory?).with("/one/dir/testing").returns true @@ -194,6 +194,7 @@ def load_file(file) end it "should load any ruby files in directories matching the fact name in the search path" do + @loader.expects(:search_path).returns %w{/one/dir} FileTest.stubs(:exist?).returns false FileTest.expects(:directory?).with("/one/dir/testing").returns true @@ -220,19 +221,10 @@ def load_file(file) describe "when loading all facts" do before :each do - @ext_loader = mock "External Loader" - Facter::Util::Config.ext_fact_loader = @ext_loader - @ext_loader.stubs(:load) @loader = Facter::Util::Loader.new @loader.stubs(:search_path).returns [] FileTest.stubs(:directory?).returns true - Dir.stubs(:entries).with("/etc/facter/facts.d").returns [] - end - - it "should load all facts from the directory loader" do - @ext_loader.expects(:load) - @loader.load_all end it "should skip directories that do not exist" do From 70b801ad444d651f249717ce43808f684340d298 Mon Sep 17 00:00:00 2001 From: Hailee Kenney Date: Fri, 6 Jul 2012 16:09:09 -0700 Subject: [PATCH 0898/3753] (#2157) Removed skipping backup files Facter will now warn when skipping over backup files in external fact directories, but still run instead of failing. --- lib/facter/util/directory_loader.rb | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/lib/facter/util/directory_loader.rb b/lib/facter/util/directory_loader.rb index bf38aebe0a..dfcf229533 100644 --- a/lib/facter/util/directory_loader.rb +++ b/lib/facter/util/directory_loader.rb @@ -21,9 +21,6 @@ class Facter::Util::DirectoryLoader class NoSuchDirectoryError < Exception end - - # A list of extensions to ignore in fact directory. - SKIP_EXTENSIONS = %w{bak orig} # This value makes it highly likely that external facts will take # precedence over all other facts @@ -80,9 +77,6 @@ def entries end def should_parse?(file) - return false if file =~ /^\./ - ext = file.split(".")[-1] - return false if SKIP_EXTENSIONS.include?(ext) - true + not file.start_with?('.') end end From 0464614bbba7fb90e0ac892095a67bd445587579 Mon Sep 17 00:00:00 2001 From: Hailee Kenney Date: Fri, 6 Jul 2012 16:11:36 -0700 Subject: [PATCH 0899/3753] (#2157) Remove redundancy in directory loader spec Extract file creation/writing into a method in directory_loader_spec.rb in order to make it cleaner and more readable. --- spec/unit/util/directory_loader_spec.rb | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/spec/unit/util/directory_loader_spec.rb b/spec/unit/util/directory_loader_spec.rb index a19dcb7c96..b526c773aa 100644 --- a/spec/unit/util/directory_loader_spec.rb +++ b/spec/unit/util/directory_loader_spec.rb @@ -34,8 +34,7 @@ describe "when loading facts from disk" do it "should be able to load files from disk and set facts" do data = {"f1" => "one", "f2" => "two"} - file = File.join(subject.directory, "data.yaml") - File.open(file, "w") { |f| f.print YAML.dump(data) } + write_to_file("data.yaml", YAML.dump(data)) subject.load @@ -46,9 +45,9 @@ it "should ignore files that begin with '.'" do # Since we know we won't load any facts, suppress the warning message Facter.stubs(:warnonce) - file = File.join(subject.directory, ".data.yaml") + data = {"f1" => "one", "f2" => "two"} - File.open(file, "w") { |f| f.print YAML.dump(data) } + write_to_file(".data.yaml", YAML.dump(data)) subject.load Facter.value("f1").should be_nil @@ -56,16 +55,14 @@ %w{bak orig}.each do |ext| it "should ignore files with an extension of '#{ext}'" do - file = File.join(subject.directory, "data" + ".#{ext}") - File.open(file, "w") { |f| f.print "foo=bar" } + write_to_file("data" + ".#{ext}", "foo=bar") subject.load end end it "should warn when trying to parse unknown file types" do - file = File.join(subject.directory, "file.unknownfiletype") - File.open(file, "w") { |f| f.print "stuff=bar" } + write_to_file("file.unknownfiletype", "stuff=bar") Facter.expects(:warn).with(regexp_matches(/file.unknownfiletype/)) @@ -75,12 +72,16 @@ it "external facts should almost always precedence over all other facts" do Facter.add("f1", :value => "lower_weight_fact") { has_weight(Facter::Util::DirectoryLoader::EXTERNAL_FACT_WEIGHT - 1) } data = {"f1" => "external_fact"} - file = File.join(subject.directory, "data.yaml") - File.open(file, "w") { |f| f.print YAML.dump(data) } + write_to_file("data.yaml", YAML.dump(data)) subject.load Facter.value("f1").should == "external_fact" end end + + def write_to_file(file_name, to_write) + file = File.join(subject.directory, file_name) + File.open(file, "w") { |f| f.print to_write} + end end From ab87a2cd0be911450abc4f4e005b5786ce92c90e Mon Sep 17 00:00:00 2001 From: Michael Stahnke Date: Sun, 8 Jul 2012 08:32:01 -0700 Subject: [PATCH 0900/3753] Modify facter spec to work with Ruby 1.9 This is a minor change to allow facter 1.6.x to work with Ruby 1.9. Linux distributions have already made these changes, so I am just ensuring that yum.puppetlabs.com builds will work on distros shipping with Ruby 1.9 as their default (or only) ruby option. This package builds cleanly and ran on CentOS6 with Ruby 1.9 back-ported from Fedora 17. Signed-off-by: Michael Stahnke --- conf/redhat/facter.spec | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index 4ee477f56f..c00777b31a 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -3,7 +3,7 @@ Summary: Ruby module for collecting simple facts about a host operating system Name: facter Version: 1.6.10 -Release: 1%{?dist} +Release: 2%{?dist} #Release: 0.1rc1.2%{?dist} Epoch: 1 License: Apache 2.0 @@ -23,7 +23,7 @@ Requires: which Requires: dmidecode Requires: pciutils %endif -Requires: ruby(abi) = 1.8 +Requires: ruby(abi) >= 1.8 BuildRequires: ruby >= 1.8.5 %description @@ -50,10 +50,14 @@ rm -rf %{buildroot} %{_bindir}/facter %{ruby_sitelibdir}/facter.rb %{ruby_sitelibdir}/facter +%{_mandir}/man8/* %doc CHANGELOG INSTALL LICENSE README.md %changelog +* Sat Jul 07 2012 Michael Stahnke - 1.6.10-2 +- Attempt to build fro Ruby 1.9.3 + * Wed Jun 13 2012 Moses Mendoza - 1.6.10-1 - Update for 1.6.10 From 7c094f129da9abd1c616ae512e7009d3d01d00d2 Mon Sep 17 00:00:00 2001 From: Hailee Kenney Date: Mon, 9 Jul 2012 11:43:19 -0700 Subject: [PATCH 0901/3753] (#2157) Allow weight to be set during fact addition Previously the Facter.add() method took parameters that would be used to call attribute setters on the created resolution. This worked for the value of the resolution, but not for the weight. The weight was set after the resolution had been added to the fact, but the weight ordered facts (for deciding which resolution takes precedence) was calculated at the time the resolution was added. This meant that a weight set via an option to the add() method was ignored. This changes the sort to occur at the time the fact value is evaluated so that the weight set as an option via add() is taken into account. --- lib/facter/util/fact.rb | 68 +++++++++++++++++++++++++---------------- 1 file changed, 42 insertions(+), 26 deletions(-) diff --git a/lib/facter/util/fact.rb b/lib/facter/util/fact.rb index 1b2e14ef6f..512de4579c 100644 --- a/lib/facter/util/fact.rb +++ b/lib/facter/util/fact.rb @@ -37,10 +37,6 @@ def add(value = nil, &block) resolve.instance_eval(&block) if block @resolves << resolve - # Immediately sort the resolutions, so that we always have - # a sorted list for looking up values. - @resolves.sort! { |a, b| b.weight <=> a.weight } - resolve rescue => e Facter.warn "Unable to add resolve for #{@name}: #{e}" @@ -59,37 +55,23 @@ def flush def value return @value if @value - if @resolves.length == 0 + if @resolves.empty? Facter.debug "No resolves for %s" % @name return nil end searching do - @value = nil - - foundsuits = false - @value = @resolves.inject(nil) { |result, resolve| - next unless resolve.suitable? - foundsuits = true - tmp = resolve.value + suitable_resolutions = sort_by_weight(find_suitable_resolutions(@resolves)) + @value = find_first_real_value(suitable_resolutions) + + announce_when_no_suitable_resolution(suitable_resolutions) + announce_when_no_value_found(@value) - break tmp unless tmp.nil? or tmp == "" - } - - unless foundsuits - Facter.debug "Found no suitable resolves of %s for %s" % [@resolves.length, @name] - end + @value end + end - if @value.nil? - # nothing - Facter.debug("value for %s is still nil" % @name) - return nil - else - return @value - end - end private @@ -110,4 +92,38 @@ def searching @searching = false end end + + def find_suitable_resolutions(resolutions) + resolutions.find_all{ |resolve| resolve.suitable? } + end + + def sort_by_weight(resolutions) + resolutions.sort { |a, b| b.weight <=> a.weight } + end + + def find_first_real_value(resolutions) + resolutions.each do |resolve| + value = normalize_value(resolve.value) + if not value.nil? + return value + end + end + nil + end + + def announce_when_no_suitable_resolution(resolutions) + if resolutions.empty? + Facter.debug "Found no suitable resolves of %s for %s" % [@resolves.length, @name] + end + end + + def announce_when_no_value_found(value) + if value.nil? + Facter.debug("value for %s is still nil" % @name) + end + end + + def normalize_value(value) + value == "" ? nil : value + end end From 088c6fea5f6df21064b6fc56d4834f4e4aa199c1 Mon Sep 17 00:00:00 2001 From: Hailee Kenney Date: Mon, 9 Jul 2012 11:49:09 -0700 Subject: [PATCH 0902/3753] (#2157) Remove unused instance variable The @suitable variable was never used. --- lib/facter/util/fact.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/facter/util/fact.rb b/lib/facter/util/fact.rb index 512de4579c..dae4a3adf3 100644 --- a/lib/facter/util/fact.rb +++ b/lib/facter/util/fact.rb @@ -47,7 +47,6 @@ def add(value = nil, &block) # Flush any cached values. def flush @value = nil - @suitable = nil end # Return the value for a given fact. Searches through all of the mechanisms From e062a7742d0e684065b19211c5e3c3d3e79b23a9 Mon Sep 17 00:00:00 2001 From: Hailee Kenney Date: Mon, 9 Jul 2012 13:57:00 -0700 Subject: [PATCH 0903/3753] (#2157) Removed directory_loader.rb's dependency on facter.rb Directory loader is now passed the collection that it is to load facts to. --- lib/facter/util/collection.rb | 4 +- lib/facter/util/composite_loader.rb | 20 +++++---- lib/facter/util/config.rb | 20 ++++----- lib/facter/util/directory_loader.rb | 42 +++++++++---------- lib/facter/util/nothing_loader.rb | 20 ++++----- spec/unit/util/collection_spec.rb | 53 +++++++++++++++--------- spec/unit/util/directory_loader_spec.rb | 54 +++++++++++++------------ 7 files changed, 113 insertions(+), 100 deletions(-) diff --git a/lib/facter/util/collection.rb b/lib/facter/util/collection.rb index db6f1b72a2..65acac9f80 100644 --- a/lib/facter/util/collection.rb +++ b/lib/facter/util/collection.rb @@ -103,13 +103,13 @@ def list def load(name) loader.load(name) - ext_loader.load + ext_loader.load(self) end # Load all known facts. def load_all loader.load_all - ext_loader.load + ext_loader.load(self) end # The thing that loads facts if we don't have them. diff --git a/lib/facter/util/composite_loader.rb b/lib/facter/util/composite_loader.rb index 880ee23ef1..eb7de378f1 100644 --- a/lib/facter/util/composite_loader.rb +++ b/lib/facter/util/composite_loader.rb @@ -1,14 +1,12 @@ -# A composite loader that allows for more than one +# A composite loader that allows for more than one # default directory loader -class Facter::Util::CompositeLoader - - def initialize(loaders) +class Facter::Util::CompositeLoader + def initialize(loaders) @loaders = loaders - end - - def load - @loaders.each { |loader| loader.load } - end -end - \ No newline at end of file + end + + def load(collection) + @loaders.each { |loader| loader.load(collection) } + end +end diff --git a/lib/facter/util/config.rb b/lib/facter/util/config.rb index 27fb06d198..793788d881 100644 --- a/lib/facter/util/config.rb +++ b/lib/facter/util/config.rb @@ -4,14 +4,14 @@ # module Facter::Util::Config - def self.ext_fact_loader - @ext_fact_loader || Facter::Util::DirectoryLoader.default_loader - end - + def self.ext_fact_loader + @ext_fact_loader || Facter::Util::DirectoryLoader.default_loader + end + def self.ext_fact_loader=(loader) - @ext_fact_loader = loader - end - + @ext_fact_loader = loader + end + def self.is_mac? Config::CONFIG['host_os'] =~ /darwin/i end @@ -20,18 +20,18 @@ def self.is_mac? def self.is_windows? RbConfig::CONFIG['host_os'] =~ /mswin|win32|dos|mingw|cygwin/i end - + def self.windows_data_dir if Dir.const_defined? 'COMMON_APPDATA' then Dir::COMMON_APPDATA else nil - end + end end def self.external_facts_dirs windows_dir = windows_data_dir - if windows_dir.nil? then + if windows_dir.nil? then ["/etc/facter/facts.d", "/etc/puppetlabs/facter/facts.d"] else [File.join(windows_dir, 'PuppetLabs', 'facter', 'facts.d')] diff --git a/lib/facter/util/directory_loader.rb b/lib/facter/util/directory_loader.rb index dfcf229533..4c176ff22a 100644 --- a/lib/facter/util/directory_loader.rb +++ b/lib/facter/util/directory_loader.rb @@ -1,11 +1,11 @@ -# A Facter plugin that loads external facts. -# +# A Facter plugin that loads external facts. +# # Default Unix Directories: # /etc/facter/facts.d, /etc/puppetlbas/facter/facts.d -# -# Default Windows Direcotires: +# +# Default Windows Direcotires: # C:\ProgramData\Puppetlabs\facter\facts.d (2008) -# C:\Documents and Settings\All Users\Application Data\Puppetlabs\facter\facts.d (2003) +# C:\Documents and Settings\All Users\Application Data\Puppetlabs\facter\facts.d (2003) # # Can also load from command-line specified directory # @@ -18,39 +18,39 @@ class Facter::Util::DirectoryLoader require 'yaml' - - class NoSuchDirectoryError < Exception - end - - # This value makes it highly likely that external facts will take + + class NoSuchDirectoryError < Exception + end + + # This value makes it highly likely that external facts will take # precedence over all other facts EXTERNAL_FACT_WEIGHT = 10000 - + # Directory for fact loading attr_reader :directory def initialize(dir) @directory = dir end - + def self.loader_for(dir) - if File.directory?(dir) + if File.directory?(dir) Facter::Util::DirectoryLoader.new(dir) else - raise NoSuchDirectoryError - end - end - + raise NoSuchDirectoryError + end + end + def self.default_loader loaders = Facter::Util::Config.external_facts_dirs.collect do |dir| Facter::Util::DirectoryLoader.new(dir) end - Facter::Util::CompositeLoader.new(loaders) - end + Facter::Util::CompositeLoader.new(loaders) + end # Load facts from files in fact directory using the relevant parser classes to # parse them. - def load + def load(collection) entries.each do |file| parser = Facter::Util::Parser.parser_for(file) if parser == nil @@ -63,7 +63,7 @@ def load elsif data == {} or data == nil Facter.warn "Fact file #{file} was parsed but returned an empty data set" else - data.each { |p,v| Facter.add(p, :value => v) { has_weight(EXTERNAL_FACT_WEIGHT) } } + data.each { |p,v| collection.add(p, :value => v) { has_weight(EXTERNAL_FACT_WEIGHT) } } end end end diff --git a/lib/facter/util/nothing_loader.rb b/lib/facter/util/nothing_loader.rb index 3ff513e299..b4bb0d1428 100644 --- a/lib/facter/util/nothing_loader.rb +++ b/lib/facter/util/nothing_loader.rb @@ -1,16 +1,16 @@ -# An external fact loader that doesn't load anything +# An external fact loader that doesn't load anything -# This makes it possible to disable loading -# of external facts +# This makes it possible to disable loading +# of external facts -module Facter - module Util - - class NothingLoader +module Facter + module Util - def load - end + class NothingLoader + + def load(collection) + end end end end - \ No newline at end of file + diff --git a/spec/unit/util/collection_spec.rb b/spec/unit/util/collection_spec.rb index 942b09c101..d636eea2e0 100755 --- a/spec/unit/util/collection_spec.rb +++ b/spec/unit/util/collection_spec.rb @@ -2,8 +2,17 @@ require 'spec_helper' require 'facter/util/collection' +require 'facter/util/nothing_loader' describe Facter::Util::Collection do + before :each do + Facter::Util::Config.ext_fact_loader = Facter::Util::NothingLoader.new + end + + after :each do + Facter::Util::Config.ext_fact_loader = nil + end + it "should have a method for adding facts" do Facter::Util::Collection.new.should respond_to(:add) end @@ -259,50 +268,54 @@ end end - describe "when no facts are loaded" do - before :each do + describe "when no facts are loaded" do + before :each do @coll = Facter::Util::Collection.new - @load = Facter::Util::Loader.new - @load.stubs(:load).returns nil - @load.stubs(:load_all).returns nil - @coll.stubs(:loader).returns @load + @load = Facter::Util::Loader.new + @load.stubs(:load).returns nil + @load.stubs(:load_all).returns nil + @coll.stubs(:loader).returns @load end - - it "should warn when no facts were loaded" do + + it "should warn when no facts were loaded" do Facter.expects(:warnonce).with("No facts loaded from #{@load.search_path.join(File::PATH_SEPARATOR)}").once @coll.fact("one") - end - end - - describe "external facts" do + end + end + + describe "external facts" do before :each do Facter::Util::Config.ext_fact_loader = SingleFactLoader.new(:test_fact, "fact value") end - + + after :each do + Facter::Util::Config.ext_fact_loader = nil + end + it "loads when a specific fact is requested" do Facter.collection.fact(:test_fact).value.should == "fact value" - end + end it "loads when facts are listed" do Facter.collection.list.should == [:test_fact] end - + it "loads when all facts are iterated over" do facts = [] Facter.collection.each { |fact_name, fact_value| facts << [fact_name, fact_value] } - + facts.should == [["test_fact", "fact value"]] end end - + class SingleFactLoader def initialize(name, value) @name = name @value = value end - - def load - Facter.add(@name, :value => @value) + + def load(collection) + collection.add(@name, :value => @value) end end end diff --git a/spec/unit/util/directory_loader_spec.rb b/spec/unit/util/directory_loader_spec.rb index b526c773aa..50fac95be1 100644 --- a/spec/unit/util/directory_loader_spec.rb +++ b/spec/unit/util/directory_loader_spec.rb @@ -8,27 +8,28 @@ include PuppetlabsSpec::Files include FacterSpec::ConfigHelper + let(:collection) { Facter::Util::Collection.new } subject { Facter::Util::DirectoryLoader.new(tmpdir('directory_loader')) } it "should make the directory available" do subject.directory.should be_instance_of(String) end - it "can be created with a given directory" do + it "can be created with a given directory" do Facter::Util::DirectoryLoader.loader_for("ext").directory.should == "ext" - end - - it "raises an error when the directory does not exist" do + end + + it "raises an error when the directory does not exist" do missing_dir = "missing" File.stubs(:directory?).with(missing_dir).returns(false) expect { Facter::Util::DirectoryLoader.loader_for(missing_dir) }.should raise_error Facter::Util::DirectoryLoader::NoSuchDirectoryError - end + end it "should do nothing bad when dir doesn't exist" do fakepath = "/foobar/path" my_loader = Facter::Util::DirectoryLoader.new(fakepath) FileTest.exists?(my_loader.directory).should be_false - expect { my_loader.load }.should_not raise_error + expect { my_loader.load(collection) }.should_not raise_error end describe "when loading facts from disk" do @@ -36,52 +37,53 @@ data = {"f1" => "one", "f2" => "two"} write_to_file("data.yaml", YAML.dump(data)) - subject.load + subject.load(collection) - Facter.value("f1").should == "one" - Facter.value("f2").should == "two" + collection.value("f1").should == "one" + collection.value("f2").should == "two" end it "should ignore files that begin with '.'" do - # Since we know we won't load any facts, suppress the warning message + # Since we know we won't load any facts, suppress the warning message Facter.stubs(:warnonce) - + data = {"f1" => "one", "f2" => "two"} write_to_file(".data.yaml", YAML.dump(data)) - subject.load - Facter.value("f1").should be_nil + subject.load(collection) + + collection.value("f1").should be_nil end %w{bak orig}.each do |ext| it "should ignore files with an extension of '#{ext}'" do write_to_file("data" + ".#{ext}", "foo=bar") - subject.load + subject.load(collection) end end it "should warn when trying to parse unknown file types" do - write_to_file("file.unknownfiletype", "stuff=bar") - + write_to_file("file.unknownfiletype", "stuff=bar") + Facter.expects(:warn).with(regexp_matches(/file.unknownfiletype/)) - subject.load + subject.load(collection) end - - it "external facts should almost always precedence over all other facts" do + + it "external facts should almost always precedence over all other facts" do Facter.add("f1", :value => "lower_weight_fact") { has_weight(Facter::Util::DirectoryLoader::EXTERNAL_FACT_WEIGHT - 1) } data = {"f1" => "external_fact"} - write_to_file("data.yaml", YAML.dump(data)) + write_to_file("data.yaml", YAML.dump(data)) - subject.load + subject.load(collection) - Facter.value("f1").should == "external_fact" - end + collection.value("f1").should == "external_fact" + end end - + def write_to_file(file_name, to_write) file = File.join(subject.directory, file_name) - File.open(file, "w") { |f| f.print to_write} - end + File.open(file, "w") { |f| f.print to_write} + end end From bc0d83148268b9ea5ebf10ee973864212950a0b8 Mon Sep 17 00:00:00 2001 From: Hailee Kenney Date: Mon, 9 Jul 2012 16:28:01 -0700 Subject: [PATCH 0904/3753] (#2157) Collection is now constructed with it's loaders The collection controlling the creation of loaders made testing awkward. Now loaders are provided to the collection when facter.rb creates the collection. --- lib/facter.rb | 12 +- lib/facter/util/collection.rb | 32 ++-- lib/facter/util/loader.rb | 6 +- spec/unit/util/collection_spec.rb | 205 +++++++----------------- spec/unit/util/directory_loader_spec.rb | 10 +- 5 files changed, 90 insertions(+), 175 deletions(-) diff --git a/lib/facter.rb b/lib/facter.rb index c6ff2ead2f..334722de07 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -52,7 +52,9 @@ module Util; end def self.collection unless defined?(@collection) and @collection - @collection = Facter::Util::Collection.new + @collection = Facter::Util::Collection.new( + Facter::Util::Loader.new, + Facter::Util::Config.ext_fact_loader) end @collection end @@ -76,14 +78,14 @@ def self.debug(string) def self.debugonce(msg) if msg and not msg.empty? and @@debug_messages[msg].nil? @@debug_messages[msg] = true - debug(msg) - end - end + debug(msg) + end + end def self.debugging? @@debug != 0 end - + # show the timing information def self.show_time(string) puts "#{GREEN}#{string}#{RESET}" if string and Facter.timing? diff --git a/lib/facter/util/collection.rb b/lib/facter/util/collection.rb index 65acac9f80..cbfd65bf72 100644 --- a/lib/facter/util/collection.rb +++ b/lib/facter/util/collection.rb @@ -5,6 +5,13 @@ # Manage which facts exist and how we access them. Largely just a wrapper # around a hash of facts. class Facter::Util::Collection + + def initialize(internal_loader, external_loader) + @facts = Hash.new + @internal_loader = internal_loader + @external_loader = external_loader + end + # Return a fact object by name. If you use this, you still have to call # 'value' on it to retrieve the actual value. def [](name) @@ -75,14 +82,14 @@ def fact(name) # Try to load the fact if necessary load(name) unless @facts[name] - + # Try HARDER loader.load_all unless @facts[name] - if @facts.empty? + if @facts.empty? Facter.warnonce("No facts loaded from #{loader.search_path.join(File::PATH_SEPARATOR)}") end - + @facts[name] end @@ -91,10 +98,6 @@ def flush @facts.each { |name, fact| fact.flush } end - def initialize - @facts = Hash.new - end - # Return a list of all of the facts. def list load_all @@ -112,21 +115,14 @@ def load_all ext_loader.load(self) end - # The thing that loads facts if we don't have them. def loader - unless defined?(@loader) - @loader = Facter::Util::Loader.new - end - @loader + @internal_loader end - + def ext_loader - unless defined?(@ext_loader) - @ext_loader = Facter::Util::Config.ext_fact_loader - end - @ext_loader + @external_loader end - + # Return a hash of all of our facts. def to_hash @facts.inject({}) do |h, ary| diff --git a/lib/facter/util/loader.rb b/lib/facter/util/loader.rb index 31402bb9c2..87008ffa54 100644 --- a/lib/facter/util/loader.rb +++ b/lib/facter/util/loader.rb @@ -15,7 +15,7 @@ def load(fact) # Now load from the search path shortname = fact.to_s.downcase load_env(shortname) - + filename = shortname + ".rb" search_path.each do |dir| # Load individual files @@ -68,10 +68,10 @@ def search_path good end end - + def valid_search_path?(path) return @valid_path[path] unless @valid_path[path].nil? - + return @valid_path[path] = Pathname.new(path).absolute? end private :valid_search_path? diff --git a/spec/unit/util/collection_spec.rb b/spec/unit/util/collection_spec.rb index d636eea2e0..1f8c12ffd5 100755 --- a/spec/unit/util/collection_spec.rb +++ b/spec/unit/util/collection_spec.rb @@ -5,61 +5,37 @@ require 'facter/util/nothing_loader' describe Facter::Util::Collection do - before :each do - Facter::Util::Config.ext_fact_loader = Facter::Util::NothingLoader.new - end - - after :each do - Facter::Util::Config.ext_fact_loader = nil - end - - it "should have a method for adding facts" do - Facter::Util::Collection.new.should respond_to(:add) - end - - it "should have a method for returning a loader" do - Facter::Util::Collection.new.should respond_to(:loader) - end - - it "should use an instance of the Loader class as its loader" do - Facter::Util::Collection.new.loader.should be_instance_of(Facter::Util::Loader) - end - - it "should cache its loader" do - coll = Facter::Util::Collection.new - coll.loader.should equal(coll.loader) - end - - it "should have a method for loading all facts" do - Facter::Util::Collection.new.should respond_to(:load_all) + let(:external_loader) { Facter::Util::NothingLoader.new } + let(:internal_loader) do + load = Facter::Util::Loader.new + load.stubs(:load).returns nil + load.stubs(:load_all).returns nil + load end + let(:collection) { Facter::Util::Collection.new(internal_loader, external_loader) } it "should delegate its load_all method to its loader" do - coll = Facter::Util::Collection.new - coll.loader.expects(:load_all) - coll.load_all + internal_loader.expects(:load_all) + + collection.load_all end describe "when adding facts" do - before do - @coll = Facter::Util::Collection.new - end - it "should create a new fact if no fact with the same name already exists" do - @coll.add(:myname) - @coll.fact(:myname).name.should == :myname + collection.add(:myname) + collection.fact(:myname).name.should == :myname end it "should accept options" do - @coll.add(:myname, :ldapname => "whatever") { } + collection.add(:myname, :ldapname => "whatever") { } end it "should set any appropriate options on the fact instances" do # Use a real fact instance, because we're using respond_to? fact = Facter::Util::Fact.new(:myname) - @coll.add(:myname, :ldapname => "testing") - @coll.fact(:myname).ldapname.should == "testing" + collection.add(:myname, :ldapname => "testing") + collection.fact(:myname).ldapname.should == "testing" end it "should set appropriate options on the resolution instance" do @@ -69,7 +45,7 @@ resolve = Facter::Util::Resolution.new(:myname) {} fact.expects(:add).returns resolve - @coll.add(:myname, :timeout => "myval") {} + collection.add(:myname, :timeout => "myval") {} end it "should not pass fact-specific options to resolutions" do @@ -82,11 +58,11 @@ fact.expects(:ldapname=).with("foo") resolve.expects(:timeout=).with("myval") - @coll.add(:myname, :timeout => "myval", :ldapname => "foo") {} + collection.add(:myname, :timeout => "myval", :ldapname => "foo") {} end it "should fail if invalid options are provided" do - lambda { @coll.add(:myname, :foo => :bar) }.should raise_error(ArgumentError) + lambda { collection.add(:myname, :foo => :bar) }.should raise_error(ArgumentError) end describe "and a block is provided" do @@ -96,213 +72,156 @@ fact.expects(:add) - @coll.add(:myname) {} + collection.add(:myname) {} end it "should discard resolutions that throw an exception when added" do lambda { - @coll.add('yay') do + collection.add('yay') do raise setcode { 'yay' } end }.should_not raise_error - @coll.value('yay').should be_nil + collection.value('yay').should be_nil end end end - it "should have a method for retrieving facts by name" do - Facter::Util::Collection.new.should respond_to(:fact) - end - describe "when retrieving facts" do before do - @coll = Facter::Util::Collection.new - - @fact = @coll.add("YayNess") + @fact = collection.add("YayNess") end it "should return the fact instance specified by the name" do - @coll.fact("YayNess").should equal(@fact) + collection.fact("YayNess").should equal(@fact) end it "should be case-insensitive" do - @coll.fact("yayness").should equal(@fact) + collection.fact("yayness").should equal(@fact) end it "should treat strings and symbols equivalently" do - @coll.fact(:yayness).should equal(@fact) + collection.fact(:yayness).should equal(@fact) end it "should use its loader to try to load the fact if no fact can be found" do - @coll.loader.expects(:load).with(:testing) - @coll.fact("testing") + collection.loader.expects(:load).with(:testing) + collection.fact("testing") end it "should return nil if it cannot find or load the fact" do - @coll.loader.expects(:load).with(:testing) - @coll.fact("testing").should be_nil + collection.loader.expects(:load).with(:testing) + collection.fact("testing").should be_nil end end - it "should have a method for returning a fact's value" do - Facter::Util::Collection.new.should respond_to(:value) - end - describe "when returning a fact's value" do before do - @coll = Facter::Util::Collection.new - @fact = @coll.add("YayNess") - - @fact.stubs(:value).returns "result" - end - - it "should use the 'fact' method to retrieve the fact" do - @coll.expects(:fact).with(:yayness).returns @fact - @coll.value(:yayness) + @fact = collection.add("YayNess", :value => "result") end it "should return the result of calling :value on the fact" do - @fact.expects(:value).returns "result" - - @coll.value("YayNess").should == "result" + collection.value("YayNess").should == "result" end it "should be case-insensitive" do - @coll.value("yayness").should_not be_nil + collection.value("yayness").should == "result" end it "should treat strings and symbols equivalently" do - @coll.value(:yayness).should_not be_nil + collection.value(:yayness).should == "result" end end it "should return the fact's value when the array index method is used" do - @coll = Facter::Util::Collection.new - @coll.expects(:value).with("myfact").returns "foo" - @coll["myfact"].should == "foo" + collection.add("myfact", :value => "foo") + + collection["myfact"].should == "foo" end it "should have a method for flushing all facts" do - @coll = Facter::Util::Collection.new - @fact = @coll.add("YayNess") + fact = collection.add("YayNess") - @fact.expects(:flush) + fact.expects(:flush) - @coll.flush + collection.flush end it "should have a method that returns all fact names" do - @coll = Facter::Util::Collection.new - @coll.add(:one) - @coll.add(:two) - - @coll.list.sort { |a,b| a.to_s <=> b.to_s }.should == [:one, :two] - end + collection.add(:one) + collection.add(:two) - it "should have a method for returning a hash of fact values" do - Facter::Util::Collection.new.should respond_to(:to_hash) + collection.list.sort { |a,b| a.to_s <=> b.to_s }.should == [:one, :two] end describe "when returning a hash of values" do - before do - @coll = Facter::Util::Collection.new - @fact = @coll.add(:one) - @fact.stubs(:value).returns "me" - end - it "should return a hash of fact names and values with the fact names as strings" do - @coll.to_hash.should == {"one" => "me"} - end + collection.add(:one, :value => "me") - it "should not include facts that did not return a value" do - f = @coll.add(:two) - f.stubs(:value).returns nil - @coll.to_hash.should_not be_include(:two) + collection.to_hash.should == {"one" => "me"} end - end - it "should have a method for iterating over all facts" do - Facter::Util::Collection.new.should respond_to(:each) - end + it "should not include facts that did not return a value" do + collection.add(:two, :value => nil) - it "should include Enumerable" do - Facter::Util::Collection.ancestors.should be_include(Enumerable) + collection.to_hash.should_not be_include(:two) + end end describe "when iterating over facts" do before do - @coll = Facter::Util::Collection.new - @one = @coll.add(:one) - @two = @coll.add(:two) + collection.add(:one, :value => "ONE") + collection.add(:two, :value => "TWO") end it "should yield each fact name and the fact value" do - @one.stubs(:value).returns "ONE" - @two.stubs(:value).returns "TWO" facts = {} - @coll.each do |fact, value| + collection.each do |fact, value| facts[fact] = value end facts.should == {"one" => "ONE", "two" => "TWO"} end it "should convert the fact name to a string" do - @one.stubs(:value).returns "ONE" - @two.stubs(:value).returns "TWO" facts = {} - @coll.each do |fact, value| + collection.each do |fact, value| fact.should be_instance_of(String) end end it "should only yield facts that have values" do - @one.stubs(:value).returns "ONE" - @two.stubs(:value).returns nil + collection.add(:nil_fact, :value => nil) facts = {} - @coll.each do |fact, value| + collection.each do |fact, value| facts[fact] = value end - facts.should_not be_include("two") + facts.should_not be_include("nil_fact") end end describe "when no facts are loaded" do - before :each do - @coll = Facter::Util::Collection.new - @load = Facter::Util::Loader.new - @load.stubs(:load).returns nil - @load.stubs(:load_all).returns nil - @coll.stubs(:loader).returns @load - end - it "should warn when no facts were loaded" do - Facter.expects(:warnonce).with("No facts loaded from #{@load.search_path.join(File::PATH_SEPARATOR)}").once - @coll.fact("one") + Facter.expects(:warnonce).with("No facts loaded from #{internal_loader.search_path.join(File::PATH_SEPARATOR)}").once + + collection.fact("one") end end describe "external facts" do - before :each do - Facter::Util::Config.ext_fact_loader = SingleFactLoader.new(:test_fact, "fact value") - end - - after :each do - Facter::Util::Config.ext_fact_loader = nil - end + let(:collection) { Facter::Util::Collection.new(internal_loader, SingleFactLoader.new(:test_fact, "fact value")) } it "loads when a specific fact is requested" do - Facter.collection.fact(:test_fact).value.should == "fact value" + collection.fact(:test_fact).value.should == "fact value" end it "loads when facts are listed" do - Facter.collection.list.should == [:test_fact] + collection.list.should == [:test_fact] end it "loads when all facts are iterated over" do facts = [] - Facter.collection.each { |fact_name, fact_value| facts << [fact_name, fact_value] } + collection.each { |fact_name, fact_value| facts << [fact_name, fact_value] } facts.should == [["test_fact", "fact value"]] end diff --git a/spec/unit/util/directory_loader_spec.rb b/spec/unit/util/directory_loader_spec.rb index 50fac95be1..71295501cb 100644 --- a/spec/unit/util/directory_loader_spec.rb +++ b/spec/unit/util/directory_loader_spec.rb @@ -8,8 +8,8 @@ include PuppetlabsSpec::Files include FacterSpec::ConfigHelper - let(:collection) { Facter::Util::Collection.new } subject { Facter::Util::DirectoryLoader.new(tmpdir('directory_loader')) } + let(:collection) { Facter::Util::Collection.new(mock("internal loader"), subject) } it "should make the directory available" do subject.directory.should be_instance_of(String) @@ -44,15 +44,13 @@ end it "should ignore files that begin with '.'" do - # Since we know we won't load any facts, suppress the warning message - Facter.stubs(:warnonce) + not_to_be_used_collection = mock("collection should not be used") + not_to_be_used_collection.expects(:add).never data = {"f1" => "one", "f2" => "two"} write_to_file(".data.yaml", YAML.dump(data)) - subject.load(collection) - - collection.value("f1").should be_nil + subject.load(not_to_be_used_collection) end %w{bak orig}.each do |ext| From 03cf1d74cfd8b3b5c1200a9822fe4cc42df2bf6c Mon Sep 17 00:00:00 2001 From: Hailee Kenney Date: Tue, 10 Jul 2012 09:15:15 -0700 Subject: [PATCH 0905/3753] (#2157) Prevent external facts from attempting to parse directories Prior to this commit, external facts would attempt to execute a file if it was executable, which mean it would attempt to parse sub directories in the external fact directory it was loading from. Now it checks to make sure that what it is trying to parse is executable and is a file, which fixes this issue. --- lib/facter/util/parser.rb | 2 +- spec/unit/util/parser_spec.rb | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/facter/util/parser.rb b/lib/facter/util/parser.rb index 970de573c3..2709990fd5 100644 --- a/lib/facter/util/parser.rb +++ b/lib/facter/util/parser.rb @@ -124,7 +124,7 @@ def results end register(ScriptParser) do |filename| - File.executable?(filename) + File.executable?(filename) && File.file?(filename) end diff --git a/spec/unit/util/parser_spec.rb b/spec/unit/util/parser_spec.rb index 51d53b3879..91dea38621 100644 --- a/spec/unit/util/parser_spec.rb +++ b/spec/unit/util/parser_spec.rb @@ -4,6 +4,7 @@ require 'facter/util/parser' require 'tempfile' +require 'tmpdir.rb' describe Facter::Util::Parser do include PuppetlabsSpec::Files @@ -105,6 +106,12 @@ it "should return a hash of whatever is returned by the executable" do Facter::Util::Parser.parser_for(@script).results.should == {"one" => "two", "three" => "four"} end + + it "should not parse a directory" do + Dir.mktmpdir do |dir| + Facter::Util::Parser.parser_for(dir).results.should == false + end + end end describe "nothing parser" do From 9bc2432ae36bad1ff29ab560e2cbbc3be400387a Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Wed, 11 Jul 2012 14:14:21 -0700 Subject: [PATCH 0906/3753] (maint) Whitespace error cleanup commit This patch simply cleans up a bunch of whitespace "errors" --- lib/facter/application.rb | 20 ++--- lib/facter/util/fact.rb | 32 ++++---- lib/facter/util/nothing_loader.rb | 1 - lib/facter/util/parser.rb | 16 ++-- spec/unit/ec2_spec.rb | 8 +- spec/unit/ipaddress6_spec.rb | 2 +- spec/unit/kernel_spec.rb | 27 +++---- spec/unit/macaddress_spec.rb | 2 +- spec/unit/memory_spec.rb | 130 +++++++++++++++--------------- spec/unit/ssh_spec.rb | 32 ++++---- spec/unit/util/config_spec.rb | 4 +- spec/unit/util/ip_spec.rb | 2 +- spec/unit/util/loader_spec.rb | 3 +- spec/unit/util/resolution_spec.rb | 104 ++++++++++++------------ 14 files changed, 190 insertions(+), 193 deletions(-) diff --git a/lib/facter/application.rb b/lib/facter/application.rb index b640608259..33b73857bf 100644 --- a/lib/facter/application.rb +++ b/lib/facter/application.rb @@ -1,21 +1,21 @@ module Facter module Application - + require 'facter/util/nothing_loader' - - def self.create_directory_loader(dir) + + def self.create_directory_loader(dir) begin Facter::Util::Config.ext_fact_loader = Facter::Util::DirectoryLoader.loader_for(dir) rescue Facter::Util::DirectoryLoader::NoSuchDirectoryError => error $stderr.puts "Specified external facts directory #{dir} does not exist." exit(1) end - end - + end + def self.create_nothing_loader - Facter::Util::Config.ext_fact_loader = Facter::Util::NothingLoader.new + Facter::Util::Config.ext_fact_loader = Facter::Util::NothingLoader.new end - + def self.run(argv) require 'optparse' require 'facter' @@ -27,7 +27,7 @@ def self.run(argv) # Change location of external facts dir # Check here for valid ext_dir and exit program - + # Create the facts hash that is printed to standard out. unless names.empty? facts = {} @@ -91,9 +91,9 @@ def self.parse(argv) options = {} OptionParser.new do |opts| opts.on("-y", "--yaml") { |v| options[:yaml] = v } - if Facter.json? + if Facter.json? opts.on("-j", "--json") { |v| options[:json] = v } - end + end opts.on( "--trace") { |v| options[:trace] = v } opts.on( "--external-dir DIR") { |v| create_directory_loader(v) } opts.on( "--no-external-dir") { |v| create_nothing_loader } diff --git a/lib/facter/util/fact.rb b/lib/facter/util/fact.rb index dae4a3adf3..9279b98360 100644 --- a/lib/facter/util/fact.rb +++ b/lib/facter/util/fact.rb @@ -63,13 +63,13 @@ def value suitable_resolutions = sort_by_weight(find_suitable_resolutions(@resolves)) @value = find_first_real_value(suitable_resolutions) - + announce_when_no_suitable_resolution(suitable_resolutions) announce_when_no_value_found(@value) @value end - end + end private @@ -91,30 +91,30 @@ def searching @searching = false end end - - def find_suitable_resolutions(resolutions) + + def find_suitable_resolutions(resolutions) resolutions.find_all{ |resolve| resolve.suitable? } - end - + end + def sort_by_weight(resolutions) resolutions.sort { |a, b| b.weight <=> a.weight } - end - - def find_first_real_value(resolutions) - resolutions.each do |resolve| - value = normalize_value(resolve.value) + end + + def find_first_real_value(resolutions) + resolutions.each do |resolve| + value = normalize_value(resolve.value) if not value.nil? return value - end + end end nil - end - + end + def announce_when_no_suitable_resolution(resolutions) - if resolutions.empty? + if resolutions.empty? Facter.debug "Found no suitable resolves of %s for %s" % [@resolves.length, @name] end - end + end def announce_when_no_value_found(value) if value.nil? diff --git a/lib/facter/util/nothing_loader.rb b/lib/facter/util/nothing_loader.rb index b4bb0d1428..6e337c3ab5 100644 --- a/lib/facter/util/nothing_loader.rb +++ b/lib/facter/util/nothing_loader.rb @@ -13,4 +13,3 @@ def load(collection) end end end - diff --git a/lib/facter/util/parser.rb b/lib/facter/util/parser.rb index 2709990fd5..4f3ad21f38 100644 --- a/lib/facter/util/parser.rb +++ b/lib/facter/util/parser.rb @@ -6,7 +6,7 @@ module Facter::Util::Parser @parsers = [] - + # For support mutliple extensions you can pass an array of extensions as # +ext+. def self.extension_matches?(filename, ext) @@ -17,18 +17,18 @@ def self.extension_matches?(filename, ext) end [extension].flatten.to_a.include?(file_extension(filename).downcase) end - + def self.file_extension(filename) File.extname(filename).sub(".", '') end - + def self.register(klass, &suitable) @parsers << [klass, suitable] end def self.parser_for(filename) registration = @parsers.detect { |k| k[1].call(filename) } - + if registration.nil? NothingParser.new else @@ -38,7 +38,7 @@ def self.parser_for(filename) class Base attr_reader :filename - + def initialize(filename) @filename = filename end @@ -53,7 +53,7 @@ def results Facter.warn("Failed to handle #{filename} as yaml facts: #{e.class}: #{e}") end end - + register(YamlParser) do |filename| extension_matches?(filename, "yaml") end @@ -76,7 +76,7 @@ def results Facter.warn("Failed to handle #{filename} as text facts: #{e.class}: #{e}") end end - + register(TextParser) do |filename| extension_matches?(filename, "txt") end @@ -99,7 +99,7 @@ def results JSON.load(File.read(filename)) end end - + register(JsonParser) do |filename| extension_matches?(filename, "json") end diff --git a/spec/unit/ec2_spec.rb b/spec/unit/ec2_spec.rb index 3b69dc3801..cc3ffcad62 100755 --- a/spec/unit/ec2_spec.rb +++ b/spec/unit/ec2_spec.rb @@ -149,10 +149,10 @@ end describe "when api connect test fails" do - before :each do - Facter.stubs(:warnonce) - end - + before :each do + Facter.stubs(:warnonce) + end + it "should not populate ec2_userdata" do # Emulate ec2 for now as it matters little to this test Facter::Util::EC2.stubs(:has_euca_mac?).returns(true) diff --git a/spec/unit/ipaddress6_spec.rb b/spec/unit/ipaddress6_spec.rb index 5bca013c93..e727cc2c95 100755 --- a/spec/unit/ipaddress6_spec.rb +++ b/spec/unit/ipaddress6_spec.rb @@ -13,7 +13,7 @@ def netsh_fixture(filename) describe "IPv6 address fact" do include FacterSpec::ConfigHelper - + before do given_a_configuration_of(:is_windows => false) end diff --git a/spec/unit/kernel_spec.rb b/spec/unit/kernel_spec.rb index 92ff41ac8e..4824dde743 100644 --- a/spec/unit/kernel_spec.rb +++ b/spec/unit/kernel_spec.rb @@ -4,22 +4,21 @@ describe "Kernel fact" do include FacterSpec::ConfigHelper - - describe "on Windows" do - it "should return the kernel as 'windows'" do + + describe "on Windows" do + it "should return the kernel as 'windows'" do given_a_configuration_of(:is_windows => true, :data_dir => "data_dir") - + Facter.fact(:kernel).value.should == "windows" - end - end - - describe "on everything else" do - it "should return the kernel using 'uname -s'" do + end + end + + describe "on everything else" do + it "should return the kernel using 'uname -s'" do given_a_configuration_of(:is_windows => false) Facter::Util::Resolution.stubs(:exec).with('uname -s').returns("test_kernel") - - Facter.fact(:kernel).value.should == 'test_kernel' - end - end -end + Facter.fact(:kernel).value.should == 'test_kernel' + end + end +end diff --git a/spec/unit/macaddress_spec.rb b/spec/unit/macaddress_spec.rb index f204ceef45..46955e1d96 100755 --- a/spec/unit/macaddress_spec.rb +++ b/spec/unit/macaddress_spec.rb @@ -12,7 +12,7 @@ def netsh_fixture(filename) describe "macaddress fact" do include FacterSpec::ConfigHelper - + before do given_a_configuration_of(:is_windows => false) end diff --git a/spec/unit/memory_spec.rb b/spec/unit/memory_spec.rb index 4e9b2fcdf0..faa7c17930 100755 --- a/spec/unit/memory_spec.rb +++ b/spec/unit/memory_spec.rb @@ -22,7 +22,7 @@ before(:each) do Facter.fact(fact + "_mb").stubs(:value).returns(nil) end - + it "#{fact} should not exist either" do Facter.fact(fact).value.should be_nil end @@ -88,7 +88,7 @@ it "should return the memory free in MB" do Facter.fact(:memoryfree_mb).value.should == "138.70" end - + after(:each) do Facter.clear end @@ -117,26 +117,26 @@ INFO File.stubs(:readlines).with("/proc/meminfo").returns(meminfo.split("\n")) - + Facter.collection.loader.load(:memory) end - + after(:each) do Facter.clear end - + it "should return the current memory size in MB" do Facter.fact(:memorysize_mb).value.should == "249.91" end - + it "should return the current memory free in MB" do Facter.fact(:memoryfree_mb).value.should == "196.16" end - + it "should return the current swap size in MB" do Facter.fact(:swapsize_mb).value.should == "511.99" end - + it "should return the current swap free in MB" do Facter.fact(:swapfree_mb).value.should == "511.99" end @@ -161,7 +161,7 @@ after(:each) do Facter.clear end - + describe "when not root" do before(:each) do Facter.fact(:id).stubs(:value).returns("notroot") @@ -184,13 +184,13 @@ it "should return the current swap size in MB" do Facter.fact(:swapsize_mb).value.should == "512.00" end - + it "should return the current swap free in MB" do Facter.fact(:swapfree_mb).value.should == "508.00" end end end - + describe "on OpenBSD" do before :each do @@ -232,7 +232,7 @@ Facter.fact(:memorysize_mb).value.should == "254.94" end end - + describe "on Solaris" do before(:each) do Facter.clear @@ -244,7 +244,7 @@ PRTCONF Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/prtconf 2>/dev/null').returns sample_prtconf - + vmstat_lines = < [ 'ssh_host_rsa_key.pub' , "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDrs+KtR8hjasELsyCiiBplUeIi77hEHzTSQt1ALG7N4IgtMg27ZAcq0tl2/O9ZarQuClc903pgionbM9Q98CtAIoqgJwdtsor7ETRmzwrcY/mvI7ne51UzQy4Eh9WrplfpNyg+EVO0FUC7mBcay6JY30QKasePp+g4MkwK5cuTzOCzd9up9KELonlH7tTm2L0YI4HhZugwVoTFulCAZvPICxSk1B/fEKyGSZVfY/UxZNqg9g2Wyvq5u40xQ5eO882UwhB3w4IbmRnPKcyotAcqOJxA7hToMKtEmFct+vjHE8T37w8axE/1X9mdvy8IZbkEBL1cupqqb8a8vU1QTg1z", "SSHFP 1 1 1e4f163a1747d0d1a08a29972c9b5d94ee5705d0\nSSHFP 1 2 4e834c91e423d6085ed6dfb880a59e2f1b04f17c1dc17da07708af67c5ab6045" ], 'SSHDSAKey' => [ 'ssh_host_dsa_key.pub' , "ssh-dss AAAAB3NzaC1kc3MAAACBAKjmRez14aZT6OKhHrsw19s7u30AdghwHFQbtC+L781YjJ3UV0/WQoZ8NaDL4ovuvW23RuO49tsqSNcVHg+PtRiN2iTVAS2h55TFhaPKhTs+i0NH3p3Ze8LNSYuz8uK7a+nTxysz47GYTHiE1ke8KXe5wGKDO1TO/MUgpDbwx72LAAAAFQD9yMJCnZMiKzA7J1RNkwvgCyBKSQAAAIAtWBAsuRM0F2fdCe+F/JmgyryQmRIT5vP8E1ww3t3ywdLHklN7UMkaEKBW/TN/jj1JOGXtZ2v5XI+0VNoNKD/7dnCGzNViRT/jjfyVi6l5UMg4Q52Gv0RXJoBJpxNqFOU2niSsy8hioyE39W6LJYWJtQozGpH/KKgkCSvxBn5hlAAAAIB1yo/YD0kQICOO0KE+UMMaKtV7FwyedFJsxsWYwZfHXGwWskf0d2+lPhd9qwdbmSvySE8Qrlvu+W+X8AipwGkItSnj16ORF8kO3lfABa+7L4BLDtumt7ybjBPcHOy3n28dd07TmMtyWvLjOb0mcxPo+TwDLtHd3L/3C1Dh41jRPg==\n", "SSHFP 2 1 f63dfe8da99f50ffbcfa40a61161cee29d109f70\nSSHFP 2 2 5f57aa6be9baddd71b6049ed5d8639664a7ddf92ce293e3887f16ad0f2d459d9" ], 'SSHECDSAKey' => [ 'ssh_host_ecdsa_key.pub' , 'ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBIuKHtgXQUIrXSVNKC7uY+ZOF7jjfqYNU7Cb/IncDOZ7jW44dxsfBzRJwS5sTHERjBinJskY87mmwY07NFF5GoE=', "SSHFP 3 1 091a088fd3500ad9e35ce201c5101646cbf6ff98\nSSHFP 3 2 1dd2aa8f29b539337316e2862b28c196c68ffe0af78fccf9e50625635677e50f"] @@ -30,7 +30,7 @@ let(:fingerprint) { data[2] } let(:fingerprint_fact) { "SSHFP_#{fact[3..-4]}" } let(:private_key) { /AAAA\S+/.match(contents).to_s } - + # Before we start testing, we'll say that the file # doesn't exist in any of our search locations. # Then, when we test a specific directory, we'll @@ -44,7 +44,7 @@ FileTest.stubs(:file?).with(full_path).returns false end end - + # Now, let's go through each and individually flip then # on for that test. dirs.each do |dir| @@ -54,17 +54,17 @@ full_path = File.join(dir, filename) FileTest.stubs(:file?).with(full_path).returns true end - + it "should find in #{dir}" do FileTest.expects(:file?).with(full_path) Facter.fact(fact).value end - + it "should match the contents" do File.expects(:read).with(full_path).at_least_once.returns contents Facter.fact(fact).value.should == private_key end - + it "should have matching fingerprint" do File.expects(:read).with(full_path).at_least_once.returns contents Facter.fact(fingerprint_fact).value.should == fingerprint diff --git a/spec/unit/util/config_spec.rb b/spec/unit/util/config_spec.rb index ffb3012ef3..7f6adce13d 100644 --- a/spec/unit/util/config_spec.rb +++ b/spec/unit/util/config_spec.rb @@ -42,13 +42,13 @@ it "should return the default value for windows 2008" do Facter::Util::Config.stubs(:is_windows?).returns(true) - Facter::Util::Config.stubs(:windows_data_dir).returns("C:\\ProgramData") + Facter::Util::Config.stubs(:windows_data_dir).returns("C:\\ProgramData") Facter::Util::Config.external_facts_dirs.should == [File.join("C:\\ProgramData", 'PuppetLabs', 'facter', 'facts.d')] end it "should return the default value for windows 2003R2" do Facter::Util::Config.stubs(:is_windows?).returns(true) - Facter::Util::Config.stubs(:windows_data_dir).returns("C:\\Documents") + Facter::Util::Config.stubs(:windows_data_dir).returns("C:\\Documents") Facter::Util::Config.external_facts_dirs.should == [File.join("C:\\Documents", 'PuppetLabs', 'facter', 'facts.d')] end end diff --git a/spec/unit/util/ip_spec.rb b/spec/unit/util/ip_spec.rb index af7274b76c..76a7b171a2 100755 --- a/spec/unit/util/ip_spec.rb +++ b/spec/unit/util/ip_spec.rb @@ -5,7 +5,7 @@ describe Facter::Util::IP do include FacterSpec::ConfigHelper - + before :each do given_a_configuration_of(:is_windows => false) end diff --git a/spec/unit/util/loader_spec.rb b/spec/unit/util/loader_spec.rb index 7e3d33590c..fdb9e393ad 100755 --- a/spec/unit/util/loader_spec.rb +++ b/spec/unit/util/loader_spec.rb @@ -177,7 +177,7 @@ def load_file(file) it 'should load any ruby files in directories matching the fact name in the search path in sorted order regardless of the order returned by Dir.entries' do @loader = TestLoader.new - + @loader.stubs(:search_path).returns %w{/one/dir} FileTest.stubs(:exist?).returns false FileTest.stubs(:directory?).with("/one/dir/testing").returns true @@ -194,7 +194,6 @@ def load_file(file) end it "should load any ruby files in directories matching the fact name in the search path" do - @loader.expects(:search_path).returns %w{/one/dir} FileTest.stubs(:exist?).returns false FileTest.expects(:directory?).with("/one/dir/testing").returns true diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index b4bfae77fa..8a1d37b38e 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -5,7 +5,7 @@ describe Facter::Util::Resolution do include FacterSpec::ConfigHelper - + it "should require a name" do lambda { Facter::Util::Resolution.new }.should raise_error(ArgumentError) end @@ -170,22 +170,22 @@ def handy_method() end describe "when dealing with whitespace" do - it "should by default strip whitespace" do + it "should by default strip whitespace" do @resolve.setcode {' value '} - @resolve.value.should == 'value' - end - + @resolve.value.should == 'value' + end + it "should strip whitespace from frozen strings" do - result = ' val ue ' - result.freeze + result = ' val ue ' + result.freeze @resolve.setcode{result} @resolve.value.should == 'val ue' - end + end describe "when given a string" do - + [true, false - ].each do |windows| + ].each do |windows| describe "#{ (windows) ? '' : 'not' } on Windows" do before do given_a_configuration_of(:is_windows => windows) @@ -193,88 +193,88 @@ def handy_method() describe "stripping whitespace" do [{:name => 'leading', :result => ' value', :expect => 'value'}, - {:name => 'trailing', :result => 'value ', :expect => 'value'}, + {:name => 'trailing', :result => 'value ', :expect => 'value'}, {:name => 'internal', :result => 'val ue', :expect => 'val ue'}, - {:name => 'leading and trailing', :result => ' value ', :expect => 'value'}, - {:name => 'leading and internal', :result => ' val ue', :expect => 'val ue'}, + {:name => 'leading and trailing', :result => ' value ', :expect => 'value'}, + {:name => 'leading and internal', :result => ' val ue', :expect => 'val ue'}, {:name => 'trailing and internal', :result => 'val ue ', :expect => 'val ue'} ].each do |scenario| - it "should remove outer whitespace when whitespace is #{scenario[:name]}" do + it "should remove outer whitespace when whitespace is #{scenario[:name]}" do @resolve.setcode "/bin/foo" Facter::Util::Resolution.expects(:exec).once.with("/bin/foo").returns scenario[:result] - @resolve.value.should == scenario[:expect] - end + @resolve.value.should == scenario[:expect] + end - end - end + end + end describe "not stripping whitespace" do before do - @resolve.preserve_whitespace - end + @resolve.preserve_whitespace + end - [{:name => 'leading', :result => ' value', :expect => ' value'}, - {:name => 'trailing', :result => 'value ', :expect => 'value '}, + [{:name => 'leading', :result => ' value', :expect => ' value'}, + {:name => 'trailing', :result => 'value ', :expect => 'value '}, {:name => 'internal', :result => 'val ue', :expect => 'val ue'}, - {:name => 'leading and trailing', :result => ' value ', :expect => ' value '}, - {:name => 'leading and internal', :result => ' val ue', :expect => ' val ue'}, + {:name => 'leading and trailing', :result => ' value ', :expect => ' value '}, + {:name => 'leading and internal', :result => ' val ue', :expect => ' val ue'}, {:name => 'trailing and internal', :result => 'val ue ', :expect => 'val ue '} ].each do |scenario| - it "should not remove #{scenario[:name]} whitespace" do + it "should not remove #{scenario[:name]} whitespace" do @resolve.setcode "/bin/foo" Facter::Util::Resolution.expects(:exec).once.with("/bin/foo").returns scenario[:result] - @resolve.value.should == scenario[:expect] - end + @resolve.value.should == scenario[:expect] + end - end - end - end - end + end + end + end + end end - + describe "when given a block" do - describe "stripping whitespace" do + describe "stripping whitespace" do [{:name => 'leading', :result => ' value', :expect => 'value'}, - {:name => 'trailing', :result => 'value ', :expect => 'value'}, + {:name => 'trailing', :result => 'value ', :expect => 'value'}, {:name => 'internal', :result => 'val ue', :expect => 'val ue'}, - {:name => 'leading and trailing', :result => ' value ', :expect => 'value'}, - {:name => 'leading and internal', :result => ' val ue', :expect => 'val ue'}, + {:name => 'leading and trailing', :result => ' value ', :expect => 'value'}, + {:name => 'leading and internal', :result => ' val ue', :expect => 'val ue'}, {:name => 'trailing and internal', :result => 'val ue ', :expect => 'val ue'} ].each do |scenario| - it "should remove outer whitespace when whitespace is #{scenario[:name]}" do + it "should remove outer whitespace when whitespace is #{scenario[:name]}" do @resolve.setcode {scenario[:result]} - @resolve.value.should == scenario[:expect] + @resolve.value.should == scenario[:expect] end - end + end end - describe "not stripping whitespace" do + describe "not stripping whitespace" do before do - @resolve.preserve_whitespace + @resolve.preserve_whitespace end - - [{:name => 'leading', :result => ' value', :expect => ' value'}, - {:name => 'trailing', :result => 'value ', :expect => 'value '}, + + [{:name => 'leading', :result => ' value', :expect => ' value'}, + {:name => 'trailing', :result => 'value ', :expect => 'value '}, {:name => 'internal', :result => 'val ue', :expect => 'val ue'}, - {:name => 'leading and trailing', :result => ' value ', :expect => ' value '}, - {:name => 'leading and internal', :result => ' val ue', :expect => ' val ue'}, + {:name => 'leading and trailing', :result => ' value ', :expect => ' value '}, + {:name => 'leading and internal', :result => ' val ue', :expect => ' val ue'}, {:name => 'trailing and internal', :result => 'val ue ', :expect => 'val ue '} ].each do |scenario| - it "should not remove #{scenario[:name]} whitespace" do + it "should not remove #{scenario[:name]} whitespace" do @resolve.setcode {scenario[:result]} - @resolve.value.should == scenario[:expect] + @resolve.value.should == scenario[:expect] end - end - end - end + end + end + end end - + describe "and setcode has not been called" do it "should return nil" do Facter::Util::Resolution.expects(:exec).with(nil, nil).never From 10c39861a43095a930fd7204605098833996089c Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Wed, 11 Jul 2012 14:36:59 -0700 Subject: [PATCH 0907/3753] (maint) Add trailing newlines to all files This is a required step before doing perl -p -l -i -e s/foo/bar/ baz.rb --- conf/osx/preflight | 2 +- lib/facter/util/cfpropertylist/lib/cfpropertylist.rb | 2 +- spec/fixtures/ifconfig/centos_5_5 | 2 +- spec/fixtures/ifconfig/centos_5_5_eth0 | 2 +- spec/fixtures/ifconfig/darwin_10_3_0 | 2 +- spec/fixtures/ifconfig/darwin_10_3_0_en0 | 2 +- spec/fixtures/ifconfig/darwin_9_8_0 | 2 +- spec/fixtures/ifconfig/darwin_9_8_0_en0 | 2 +- spec/fixtures/ifconfig/fedora_10 | 2 +- spec/fixtures/ifconfig/fedora_10_eth0 | 2 +- spec/fixtures/ifconfig/fedora_13 | 2 +- spec/fixtures/ifconfig/fedora_13_eth0 | 2 +- spec/fixtures/ifconfig/fedora_8 | 2 +- spec/fixtures/ifconfig/fedora_8_eth0 | 2 +- spec/fixtures/ifconfig/freebsd_6_0 | 2 +- spec/fixtures/ifconfig/open_solaris_10 | 2 +- spec/fixtures/ifconfig/open_solaris_b132 | 2 +- spec/fixtures/ifconfig/ubuntu_7_04 | 2 +- spec/fixtures/ifconfig/ubuntu_7_04_eth0 | 2 +- spec/fixtures/netstat/centos_5_5 | 2 +- spec/fixtures/netstat/darwin_10_3_0 | 2 +- spec/fixtures/netstat/darwin_9_8_0 | 2 +- spec/fixtures/netstat/fedora_10 | 2 +- spec/fixtures/netstat/open_solaris_10 | 2 +- spec/fixtures/netstat/open_solaris_b132 | 2 +- spec/fixtures/netstat/ubuntu_7_04 | 2 +- spec/fixtures/unit/util/ec2/centos-arp-ec2.out | 2 +- spec/fixtures/unit/util/ec2/windows-2008-arp-a.out | 2 +- spec/fixtures/unit/util/ip/6.0-STABLE_FreeBSD_ifconfig | 2 +- .../unit/util/ip/linux_ifconfig_all_with_single_interface | 2 +- spec/fixtures/unit/util/ip/solaris_ifconfig_single_interface | 2 +- 31 files changed, 31 insertions(+), 31 deletions(-) diff --git a/conf/osx/preflight b/conf/osx/preflight index 8066bf47d2..096344b2bf 100755 --- a/conf/osx/preflight +++ b/conf/osx/preflight @@ -13,4 +13,4 @@ # remove old doc files -/bin/rm -Rf "${3}/usr/share/doc/facter" \ No newline at end of file +/bin/rm -Rf "${3}/usr/share/doc/facter" diff --git a/lib/facter/util/cfpropertylist/lib/cfpropertylist.rb b/lib/facter/util/cfpropertylist/lib/cfpropertylist.rb index 1d7d72e61b..29fd8a1a9f 100644 --- a/lib/facter/util/cfpropertylist/lib/cfpropertylist.rb +++ b/lib/facter/util/cfpropertylist/lib/cfpropertylist.rb @@ -3,4 +3,4 @@ require File.dirname(__FILE__) + '/rbCFPropertyList.rb' -# eof \ No newline at end of file +# eof diff --git a/spec/fixtures/ifconfig/centos_5_5 b/spec/fixtures/ifconfig/centos_5_5 index 7f57d1d311..673c780fc7 100644 --- a/spec/fixtures/ifconfig/centos_5_5 +++ b/spec/fixtures/ifconfig/centos_5_5 @@ -14,4 +14,4 @@ lo Link encap:Local Loopback RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 - RX bytes:0 (0.0 b) TX bytes:0 (0.0 b) \ No newline at end of file + RX bytes:0 (0.0 b) TX bytes:0 (0.0 b) diff --git a/spec/fixtures/ifconfig/centos_5_5_eth0 b/spec/fixtures/ifconfig/centos_5_5_eth0 index c18d9362c1..d1ea3f1409 100644 --- a/spec/fixtures/ifconfig/centos_5_5_eth0 +++ b/spec/fixtures/ifconfig/centos_5_5_eth0 @@ -5,4 +5,4 @@ eth0 Link encap:Ethernet HWaddr 16:8D:2A:15:17:91 RX packets:1914871 errors:0 dropped:0 overruns:0 frame:0 TX packets:3960 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 - RX bytes:178738891 (170.4 MiB) TX bytes:393862 (384.6 KiB) \ No newline at end of file + RX bytes:178738891 (170.4 MiB) TX bytes:393862 (384.6 KiB) diff --git a/spec/fixtures/ifconfig/darwin_10_3_0 b/spec/fixtures/ifconfig/darwin_10_3_0 index 23707ddd60..2759daa14b 100644 --- a/spec/fixtures/ifconfig/darwin_10_3_0 +++ b/spec/fixtures/ifconfig/darwin_10_3_0 @@ -23,4 +23,4 @@ vmnet8: flags=8863 mtu 1500 inet 172.16.95.1 netmask 0xffffff00 broadcast 172.16.95.255 vmnet1: flags=8863 mtu 1500 ether 00:50:56:c0:00:01 - inet 172.16.201.1 netmask 0xffffff00 broadcast 172.16.201.255 \ No newline at end of file + inet 172.16.201.1 netmask 0xffffff00 broadcast 172.16.201.255 diff --git a/spec/fixtures/ifconfig/darwin_10_3_0_en0 b/spec/fixtures/ifconfig/darwin_10_3_0_en0 index 1e33f712a7..f11391a7c8 100644 --- a/spec/fixtures/ifconfig/darwin_10_3_0_en0 +++ b/spec/fixtures/ifconfig/darwin_10_3_0_en0 @@ -3,4 +3,4 @@ en0: flags=8863 mtu 1500 inet6 fe80::217:f2ff:fe06:e3c2%en0 prefixlen 64 scopeid 0x4 inet 100.100.104.12 netmask 0xffffff00 broadcast 100.100.104.255 media: autoselect (1000baseT ) - status: active \ No newline at end of file + status: active diff --git a/spec/fixtures/ifconfig/darwin_9_8_0 b/spec/fixtures/ifconfig/darwin_9_8_0 index 80f5d94cea..db7e623368 100644 --- a/spec/fixtures/ifconfig/darwin_9_8_0 +++ b/spec/fixtures/ifconfig/darwin_9_8_0 @@ -23,4 +23,4 @@ vmnet8: flags=8863 mtu 1500 ether 00:50:56:c0:00:08 vmnet1: flags=8863 mtu 1500 inet 192.168.61.1 netmask 0xffffff00 broadcast 192.168.61.255 - ether 00:50:56:c0:00:01 \ No newline at end of file + ether 00:50:56:c0:00:01 diff --git a/spec/fixtures/ifconfig/darwin_9_8_0_en0 b/spec/fixtures/ifconfig/darwin_9_8_0_en0 index 658ae777a0..1cbfb61713 100644 --- a/spec/fixtures/ifconfig/darwin_9_8_0_en0 +++ b/spec/fixtures/ifconfig/darwin_9_8_0_en0 @@ -3,4 +3,4 @@ en0: flags=8863 mtu 1500 inet 100.100.107.4 netmask 0xffffff00 broadcast 100.100.107.255 ether 00:17:f2:06:e4:2e media: autoselect (1000baseT ) status: active - supported media: autoselect 10baseT/UTP 10baseT/UTP 10baseT/UTP 10baseT/UTP 100baseTX 100baseTX 100baseTX 100baseTX 1000baseT 1000baseT 1000baseT \ No newline at end of file + supported media: autoselect 10baseT/UTP 10baseT/UTP 10baseT/UTP 10baseT/UTP 100baseTX 100baseTX 100baseTX 100baseTX 1000baseT 1000baseT 1000baseT diff --git a/spec/fixtures/ifconfig/fedora_10 b/spec/fixtures/ifconfig/fedora_10 index 42b06ca64c..c6c15354f4 100644 --- a/spec/fixtures/ifconfig/fedora_10 +++ b/spec/fixtures/ifconfig/fedora_10 @@ -33,4 +33,4 @@ vmnet8 Link encap:Ethernet HWaddr 00:50:56:C0:00:08 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:20 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 - RX bytes:0 (0.0 b) TX bytes:0 (0.0 b) \ No newline at end of file + RX bytes:0 (0.0 b) TX bytes:0 (0.0 b) diff --git a/spec/fixtures/ifconfig/fedora_10_eth0 b/spec/fixtures/ifconfig/fedora_10_eth0 index fa957d9094..2b046fafa1 100644 --- a/spec/fixtures/ifconfig/fedora_10_eth0 +++ b/spec/fixtures/ifconfig/fedora_10_eth0 @@ -6,4 +6,4 @@ eth0 Link encap:Ethernet HWaddr 00:17:F2:06:E4:26 TX packets:45447195 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:17037147877 (15.8 GiB) TX bytes:9198873602 (8.5 GiB) - Memory:92c20000-92c40000 \ No newline at end of file + Memory:92c20000-92c40000 diff --git a/spec/fixtures/ifconfig/fedora_13 b/spec/fixtures/ifconfig/fedora_13 index 23491dfb23..5e4266f4c9 100644 --- a/spec/fixtures/ifconfig/fedora_13 +++ b/spec/fixtures/ifconfig/fedora_13 @@ -15,4 +15,4 @@ lo Link encap:Local Loopback RX packets:482 errors:0 dropped:0 overruns:0 frame:0 TX packets:482 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 - RX bytes:48488 (47.3 KiB) TX bytes:48488 (47.3 KiB) \ No newline at end of file + RX bytes:48488 (47.3 KiB) TX bytes:48488 (47.3 KiB) diff --git a/spec/fixtures/ifconfig/fedora_13_eth0 b/spec/fixtures/ifconfig/fedora_13_eth0 index ca7afd9865..1272d6232d 100644 --- a/spec/fixtures/ifconfig/fedora_13_eth0 +++ b/spec/fixtures/ifconfig/fedora_13_eth0 @@ -6,4 +6,4 @@ eth0 Link encap:Ethernet HWaddr 00:17:F2:0D:9B:A8 TX packets:1539208 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:1729726282 (1.6 GiB) TX bytes:1606599653 (1.4 GiB) - Memory:f2c20000-f2c40000 \ No newline at end of file + Memory:f2c20000-f2c40000 diff --git a/spec/fixtures/ifconfig/fedora_8 b/spec/fixtures/ifconfig/fedora_8 index f54caa4b87..ffd55f8b2d 100644 --- a/spec/fixtures/ifconfig/fedora_8 +++ b/spec/fixtures/ifconfig/fedora_8 @@ -35,4 +35,4 @@ lo Link encap:Local Loopback RX packets:59385736 errors:0 dropped:0 overruns:0 frame:0 TX packets:59385736 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 - RX bytes:3519026710 (3.2 GiB) TX bytes:3519026710 (3.2 GiB) \ No newline at end of file + RX bytes:3519026710 (3.2 GiB) TX bytes:3519026710 (3.2 GiB) diff --git a/spec/fixtures/ifconfig/fedora_8_eth0 b/spec/fixtures/ifconfig/fedora_8_eth0 index a7789dc077..0c75dc8e94 100644 --- a/spec/fixtures/ifconfig/fedora_8_eth0 +++ b/spec/fixtures/ifconfig/fedora_8_eth0 @@ -6,4 +6,4 @@ eth0 Link encap:Ethernet HWaddr 00:18:F3:F6:33:E5 TX packets:1241890434 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:3777811119 (3.5 GiB) TX bytes:2061606027 (1.9 GiB) - Interrupt:21 \ No newline at end of file + Interrupt:21 diff --git a/spec/fixtures/ifconfig/freebsd_6_0 b/spec/fixtures/ifconfig/freebsd_6_0 index 3339a45588..3335708c2f 100644 --- a/spec/fixtures/ifconfig/freebsd_6_0 +++ b/spec/fixtures/ifconfig/freebsd_6_0 @@ -9,4 +9,4 @@ fxp0: flags=8843 mtu 1500 media: Ethernet autoselect (100baseTX ) status: active lo0: flags=8049 mtu 16384 - inet 127.0.0.1 netmask 0xff000000 \ No newline at end of file + inet 127.0.0.1 netmask 0xff000000 diff --git a/spec/fixtures/ifconfig/open_solaris_10 b/spec/fixtures/ifconfig/open_solaris_10 index ed5dcbe632..76536fdc8a 100644 --- a/spec/fixtures/ifconfig/open_solaris_10 +++ b/spec/fixtures/ifconfig/open_solaris_10 @@ -9,4 +9,4 @@ hme0: flags=2000841 mtu 1500 index 2 inet6 fe80::a00:20ff:fed1:6d79/10 ether 8:0:20:d1:6d:79 hme0:1: flags=2080841 mtu 1500 index 2 - inet6 2404:130:0:1000:a00:20ff:fed1:6d79/64 \ No newline at end of file + inet6 2404:130:0:1000:a00:20ff:fed1:6d79/64 diff --git a/spec/fixtures/ifconfig/open_solaris_b132 b/spec/fixtures/ifconfig/open_solaris_b132 index c674a41239..9990a726f5 100644 --- a/spec/fixtures/ifconfig/open_solaris_b132 +++ b/spec/fixtures/ifconfig/open_solaris_b132 @@ -17,4 +17,4 @@ int0: flags=2100841 mtu 9000 index 3 inet6 fe80::ff:0/10 ether 2:8:20:89:75:75 int0:1: flags=2180841 mtu 9000 index 3 - inet6 2404:130:40:18::ff:0/64 \ No newline at end of file + inet6 2404:130:40:18::ff:0/64 diff --git a/spec/fixtures/ifconfig/ubuntu_7_04 b/spec/fixtures/ifconfig/ubuntu_7_04 index 8310576401..d1b9ea3b3c 100644 --- a/spec/fixtures/ifconfig/ubuntu_7_04 +++ b/spec/fixtures/ifconfig/ubuntu_7_04 @@ -35,4 +35,4 @@ wifi0 Link encap:UNSPEC HWaddr 00-17-F2-49-E0-E6-00-00-00-00-00-00-00-00-00 TX packets:193 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:199 RX bytes:754290 (736.6 KiB) TX bytes:8878 (8.6 KiB) - Interrupt:16 \ No newline at end of file + Interrupt:16 diff --git a/spec/fixtures/ifconfig/ubuntu_7_04_eth0 b/spec/fixtures/ifconfig/ubuntu_7_04_eth0 index b9fc759303..91eb31ca1a 100644 --- a/spec/fixtures/ifconfig/ubuntu_7_04_eth0 +++ b/spec/fixtures/ifconfig/ubuntu_7_04_eth0 @@ -6,4 +6,4 @@ eth0 Link encap:Ethernet HWaddr 00:16:CB:A6:D4:3A TX packets:280 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:73859 (72.1 KiB) TX bytes:35243 (34.4 KiB) - Interrupt:17 \ No newline at end of file + Interrupt:17 diff --git a/spec/fixtures/netstat/centos_5_5 b/spec/fixtures/netstat/centos_5_5 index f2c593e18d..2c465816a7 100644 --- a/spec/fixtures/netstat/centos_5_5 +++ b/spec/fixtures/netstat/centos_5_5 @@ -2,4 +2,4 @@ Kernel IP routing table Destination Gateway Genmask Flags MSS Window irtt Iface 100.100.100.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0 169.254.0.0 0.0.0.0 255.255.0.0 U 0 0 0 eth0 -0.0.0.0 100.100.100.1 0.0.0.0 UG 0 0 0 eth0 \ No newline at end of file +0.0.0.0 100.100.100.1 0.0.0.0 UG 0 0 0 eth0 diff --git a/spec/fixtures/netstat/darwin_10_3_0 b/spec/fixtures/netstat/darwin_10_3_0 index 62533e2183..0aa6f4c491 100644 --- a/spec/fixtures/netstat/darwin_10_3_0 +++ b/spec/fixtures/netstat/darwin_10_3_0 @@ -32,4 +32,4 @@ fe80::217:f2ff:fe06:e3c2%en0 0:17:f2:6:e3:c2 UHL ff01::/32 ::1 Um lo0 ff02::/32 ::1 UmC lo0 ff02::/32 link#4 UmC en0 -ff02::fb link#4 UHmLW en0 \ No newline at end of file +ff02::fb link#4 UHmLW en0 diff --git a/spec/fixtures/netstat/darwin_9_8_0 b/spec/fixtures/netstat/darwin_9_8_0 index d552d3d3ea..a8b8a507ba 100644 --- a/spec/fixtures/netstat/darwin_9_8_0 +++ b/spec/fixtures/netstat/darwin_9_8_0 @@ -25,4 +25,4 @@ fe80::%en0/64 link#4 UC fe80::217:f2ff:fe06:e42e%en0 0:17:f2:6:e4:2e UHL lo0 ff01::/32 ::1 U lo0 ff02::/32 fe80::1%lo0 UC lo0 -ff02::/32 link#4 UC en0 \ No newline at end of file +ff02::/32 link#4 UC en0 diff --git a/spec/fixtures/netstat/fedora_10 b/spec/fixtures/netstat/fedora_10 index 991bc3a9db..33d3f4e3db 100644 --- a/spec/fixtures/netstat/fedora_10 +++ b/spec/fixtures/netstat/fedora_10 @@ -4,4 +4,4 @@ Destination Gateway Genmask Flags MSS Window irtt Iface 192.168.183.0 0.0.0.0 255.255.255.0 U 0 0 0 vmnet8 100.100.108.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0 169.254.0.0 0.0.0.0 255.255.0.0 U 0 0 0 eth0 -0.0.0.0 100.100.108.1 0.0.0.0 UG 0 0 0 eth0 \ No newline at end of file +0.0.0.0 100.100.108.1 0.0.0.0 UG 0 0 0 eth0 diff --git a/spec/fixtures/netstat/open_solaris_10 b/spec/fixtures/netstat/open_solaris_10 index 7e50620700..c21170434a 100644 --- a/spec/fixtures/netstat/open_solaris_10 +++ b/spec/fixtures/netstat/open_solaris_10 @@ -13,4 +13,4 @@ Routing Table: IPv6 fe80::/10 fe80::a00:20ff:fed1:6d79 U 1 0 hme0 ff00::/8 fe80::a00:20ff:fed1:6d79 U 1 0 hme0 default fe80::214:22ff:fe0a:1c46 UG 1 0 hme0 -::1 ::1 UH 1 140 lo0 \ No newline at end of file +::1 ::1 UH 1 140 lo0 diff --git a/spec/fixtures/netstat/open_solaris_b132 b/spec/fixtures/netstat/open_solaris_b132 index a75fab6294..9a7ed8906a 100644 --- a/spec/fixtures/netstat/open_solaris_b132 +++ b/spec/fixtures/netstat/open_solaris_b132 @@ -14,4 +14,4 @@ Routing Table: IPv6 2404:130:40:18::/64 2404:130:40:18::ff:0 U 4 1302106 int0 fe80::/10 fe80::ff:0 U 3 16283 int0 fe80::/10 fe80::ff:0 U 5 314822 bge0 -default fe80::20d:edff:fe9d:782e UG 2 420100 bge0 \ No newline at end of file +default fe80::20d:edff:fe9d:782e UG 2 420100 bge0 diff --git a/spec/fixtures/netstat/ubuntu_7_04 b/spec/fixtures/netstat/ubuntu_7_04 index 40b80607df..4f49311530 100644 --- a/spec/fixtures/netstat/ubuntu_7_04 +++ b/spec/fixtures/netstat/ubuntu_7_04 @@ -4,4 +4,4 @@ Destination Gateway Genmask Flags MSS Window irtt Iface 169.254.0.0 0.0.0.0 255.255.0.0 U 0 0 0 ath0 169.254.0.0 0.0.0.0 255.255.0.0 U 0 0 0 eth0 0.0.0.0 100.100.106.1 0.0.0.0 UG 0 0 0 eth0 -0.0.0.0 0.0.0.0 0.0.0.0 U 0 0 0 ath0 \ No newline at end of file +0.0.0.0 0.0.0.0 0.0.0.0 U 0 0 0 ath0 diff --git a/spec/fixtures/unit/util/ec2/centos-arp-ec2.out b/spec/fixtures/unit/util/ec2/centos-arp-ec2.out index 24d2ec03ac..22ed8be7ae 100644 --- a/spec/fixtures/unit/util/ec2/centos-arp-ec2.out +++ b/spec/fixtures/unit/util/ec2/centos-arp-ec2.out @@ -1 +1 @@ -? (10.240.93.1) at FE:FF:FF:FF:FF:FF [ether] on eth0 \ No newline at end of file +? (10.240.93.1) at FE:FF:FF:FF:FF:FF [ether] on eth0 diff --git a/spec/fixtures/unit/util/ec2/windows-2008-arp-a.out b/spec/fixtures/unit/util/ec2/windows-2008-arp-a.out index e1623a0e30..7824cf3f98 100644 --- a/spec/fixtures/unit/util/ec2/windows-2008-arp-a.out +++ b/spec/fixtures/unit/util/ec2/windows-2008-arp-a.out @@ -7,4 +7,4 @@ Interface: 10.32.123.54 --- 0xd 169.254.169.254 fe-ff-ff-ff-ff-ff dynamic 224.0.0.22 01-00-5e-00-00-16 static 224.0.0.252 01-00-5e-00-00-fc static - 255.255.255.255 ff-ff-ff-ff-ff-ff static \ No newline at end of file + 255.255.255.255 ff-ff-ff-ff-ff-ff static diff --git a/spec/fixtures/unit/util/ip/6.0-STABLE_FreeBSD_ifconfig b/spec/fixtures/unit/util/ip/6.0-STABLE_FreeBSD_ifconfig index 3339a45588..3335708c2f 100644 --- a/spec/fixtures/unit/util/ip/6.0-STABLE_FreeBSD_ifconfig +++ b/spec/fixtures/unit/util/ip/6.0-STABLE_FreeBSD_ifconfig @@ -9,4 +9,4 @@ fxp0: flags=8843 mtu 1500 media: Ethernet autoselect (100baseTX ) status: active lo0: flags=8049 mtu 16384 - inet 127.0.0.1 netmask 0xff000000 \ No newline at end of file + inet 127.0.0.1 netmask 0xff000000 diff --git a/spec/fixtures/unit/util/ip/linux_ifconfig_all_with_single_interface b/spec/fixtures/unit/util/ip/linux_ifconfig_all_with_single_interface index e063b77e78..1c88454286 100644 --- a/spec/fixtures/unit/util/ip/linux_ifconfig_all_with_single_interface +++ b/spec/fixtures/unit/util/ip/linux_ifconfig_all_with_single_interface @@ -15,4 +15,4 @@ lo Link encap:Local Loopback RX packets:1630 errors:0 dropped:0 overruns:0 frame:0 TX packets:1630 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 - RX bytes:81500 (79.5 KB) TX bytes:81500 (79.5 KB) \ No newline at end of file + RX bytes:81500 (79.5 KB) TX bytes:81500 (79.5 KB) diff --git a/spec/fixtures/unit/util/ip/solaris_ifconfig_single_interface b/spec/fixtures/unit/util/ip/solaris_ifconfig_single_interface index 7a227aa5f9..a408938183 100644 --- a/spec/fixtures/unit/util/ip/solaris_ifconfig_single_interface +++ b/spec/fixtures/unit/util/ip/solaris_ifconfig_single_interface @@ -1,3 +1,3 @@ e1000g0: flags=201004843 mtu 1500 index 2 inet 172.16.15.138 netmask ffffff00 broadcast 172.16.15.255 - ether 0:c:29:c1:70:2a \ No newline at end of file + ether 0:c:29:c1:70:2a From f85eebd1ef90acfad0024ba5523f7393e91a2755 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Wed, 11 Jul 2012 14:37:45 -0700 Subject: [PATCH 0908/3753] (maint) Standardize on #!/usr/bin/env ruby -S rspec Mass search and replace in place using find * -type f -print0 | xargs -0 perl -p -l -i -e s/foo/bar/ --- spec/integration/facter_spec.rb | 2 +- spec/unit/architecture_spec.rb | 2 +- spec/unit/domain_spec.rb | 2 +- spec/unit/ec2_spec.rb | 2 +- spec/unit/facter_spec.rb | 2 +- spec/unit/filesystems_spec.rb | 2 +- spec/unit/hostname_spec.rb | 2 +- spec/unit/id_spec.rb | 2 +- spec/unit/interfaces_spec.rb | 2 +- spec/unit/ipaddress6_spec.rb | 2 +- spec/unit/kernel_spec.rb | 2 +- spec/unit/kernelmajversion_spec.rb | 2 +- spec/unit/kernelrelease_spec.rb | 2 +- spec/unit/kernelversion_spec.rb | 2 +- spec/unit/lsbdistcodename_spec.rb | 2 +- spec/unit/lsbdistdescription_spec.rb | 2 +- spec/unit/lsbdistid_spec.rb | 2 +- spec/unit/lsbdistrelease_spec.rb | 2 +- spec/unit/lsbrelease_spec.rb | 2 +- spec/unit/macaddress_spec.rb | 2 +- spec/unit/memory_spec.rb | 2 +- spec/unit/operatingsystem_spec.rb | 2 +- spec/unit/operatingsystemrelease_spec.rb | 2 +- spec/unit/physicalprocessorcount_spec.rb | 2 +- spec/unit/processor_spec.rb | 2 +- spec/unit/selinux_spec.rb | 2 +- spec/unit/ssh_spec.rb | 2 +- spec/unit/uptime_spec.rb | 2 +- spec/unit/util/collection_spec.rb | 2 +- spec/unit/util/confine_spec.rb | 2 +- spec/unit/util/ec2_spec.rb | 2 +- spec/unit/util/fact_spec.rb | 2 +- spec/unit/util/ip_spec.rb | 2 +- spec/unit/util/loader_spec.rb | 2 +- spec/unit/util/macaddress_spec.rb | 2 +- spec/unit/util/macosx_spec.rb | 2 +- spec/unit/util/manufacturer_spec.rb | 2 +- spec/unit/util/processor_spec.rb | 2 +- spec/unit/util/registry_spec.rb | 2 +- spec/unit/util/resolution_spec.rb | 2 +- spec/unit/util/uptime_spec.rb | 2 +- spec/unit/util/virtual_spec.rb | 2 +- spec/unit/util/vlans_spec.rb | 2 +- spec/unit/util/wmi_spec.rb | 2 +- spec/unit/util/xendomains_spec.rb | 2 +- spec/unit/virtual_spec.rb | 2 +- 46 files changed, 46 insertions(+), 46 deletions(-) diff --git a/spec/integration/facter_spec.rb b/spec/integration/facter_spec.rb index c90c60dd10..4fd33088ed 100755 --- a/spec/integration/facter_spec.rb +++ b/spec/integration/facter_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby -S rspec require 'spec_helper' diff --git a/spec/unit/architecture_spec.rb b/spec/unit/architecture_spec.rb index ba180b6e77..81784882de 100755 --- a/spec/unit/architecture_spec.rb +++ b/spec/unit/architecture_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby -S rspec require 'spec_helper' diff --git a/spec/unit/domain_spec.rb b/spec/unit/domain_spec.rb index 754718ed30..c772be6a3d 100755 --- a/spec/unit/domain_spec.rb +++ b/spec/unit/domain_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby -S rspec require 'spec_helper' diff --git a/spec/unit/ec2_spec.rb b/spec/unit/ec2_spec.rb index cc3ffcad62..f65eff16b0 100755 --- a/spec/unit/ec2_spec.rb +++ b/spec/unit/ec2_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby -S rspec require 'spec_helper' require 'facter/util/ec2' diff --git a/spec/unit/facter_spec.rb b/spec/unit/facter_spec.rb index a91c896a40..6526b1be72 100755 --- a/spec/unit/facter_spec.rb +++ b/spec/unit/facter_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby -S rspec require 'spec_helper' diff --git a/spec/unit/filesystems_spec.rb b/spec/unit/filesystems_spec.rb index 832e708e77..df364b0636 100644 --- a/spec/unit/filesystems_spec.rb +++ b/spec/unit/filesystems_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby -S rspec require 'spec_helper' diff --git a/spec/unit/hostname_spec.rb b/spec/unit/hostname_spec.rb index f25c96dedb..35d8bd7db0 100755 --- a/spec/unit/hostname_spec.rb +++ b/spec/unit/hostname_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby -S rspec require 'spec_helper' diff --git a/spec/unit/id_spec.rb b/spec/unit/id_spec.rb index e1c3063323..15a61b63d2 100755 --- a/spec/unit/id_spec.rb +++ b/spec/unit/id_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby -S rspec require 'spec_helper' diff --git a/spec/unit/interfaces_spec.rb b/spec/unit/interfaces_spec.rb index 54aadc5f88..c58b221fd4 100755 --- a/spec/unit/interfaces_spec.rb +++ b/spec/unit/interfaces_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby -S rspec require 'spec_helper' require 'facter/util/ip' diff --git a/spec/unit/ipaddress6_spec.rb b/spec/unit/ipaddress6_spec.rb index e727cc2c95..cf4d8bfb22 100755 --- a/spec/unit/ipaddress6_spec.rb +++ b/spec/unit/ipaddress6_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby -S rspec require 'spec_helper' diff --git a/spec/unit/kernel_spec.rb b/spec/unit/kernel_spec.rb index 4824dde743..79bc895c85 100644 --- a/spec/unit/kernel_spec.rb +++ b/spec/unit/kernel_spec.rb @@ -1,4 +1,4 @@ -#!user/bin/env rspec +#! /usr/bin/env ruby -S rspec require 'spec_helper' diff --git a/spec/unit/kernelmajversion_spec.rb b/spec/unit/kernelmajversion_spec.rb index 4448a45965..1661e02aa1 100644 --- a/spec/unit/kernelmajversion_spec.rb +++ b/spec/unit/kernelmajversion_spec.rb @@ -1,4 +1,4 @@ -#!user/bin/env rspec +#! /usr/bin/env ruby -S rspec require 'spec_helper' diff --git a/spec/unit/kernelrelease_spec.rb b/spec/unit/kernelrelease_spec.rb index 25cf10f168..99710874b1 100644 --- a/spec/unit/kernelrelease_spec.rb +++ b/spec/unit/kernelrelease_spec.rb @@ -1,4 +1,4 @@ -#!usr/bin/env rspec +#! /usr/bin/env ruby -S rspec require 'spec_helper' diff --git a/spec/unit/kernelversion_spec.rb b/spec/unit/kernelversion_spec.rb index ab901f3e0f..ea50069eb2 100644 --- a/spec/unit/kernelversion_spec.rb +++ b/spec/unit/kernelversion_spec.rb @@ -1,4 +1,4 @@ -#!user/bin/env rspec +#! /usr/bin/env ruby -S rspec require 'spec_helper' diff --git a/spec/unit/lsbdistcodename_spec.rb b/spec/unit/lsbdistcodename_spec.rb index e30aadaca7..efb14b7be8 100755 --- a/spec/unit/lsbdistcodename_spec.rb +++ b/spec/unit/lsbdistcodename_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby -S rspec require 'spec_helper' diff --git a/spec/unit/lsbdistdescription_spec.rb b/spec/unit/lsbdistdescription_spec.rb index 31f7761725..e90764c48e 100755 --- a/spec/unit/lsbdistdescription_spec.rb +++ b/spec/unit/lsbdistdescription_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby -S rspec require 'spec_helper' diff --git a/spec/unit/lsbdistid_spec.rb b/spec/unit/lsbdistid_spec.rb index 8ead532052..ae72b738e0 100755 --- a/spec/unit/lsbdistid_spec.rb +++ b/spec/unit/lsbdistid_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby -S rspec require 'spec_helper' diff --git a/spec/unit/lsbdistrelease_spec.rb b/spec/unit/lsbdistrelease_spec.rb index bd21eba980..45101e0050 100755 --- a/spec/unit/lsbdistrelease_spec.rb +++ b/spec/unit/lsbdistrelease_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby -S rspec require 'spec_helper' diff --git a/spec/unit/lsbrelease_spec.rb b/spec/unit/lsbrelease_spec.rb index f1cd7fc2ee..89d91bc630 100755 --- a/spec/unit/lsbrelease_spec.rb +++ b/spec/unit/lsbrelease_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby -S rspec require 'spec_helper' diff --git a/spec/unit/macaddress_spec.rb b/spec/unit/macaddress_spec.rb index 46955e1d96..c2b6c9473a 100755 --- a/spec/unit/macaddress_spec.rb +++ b/spec/unit/macaddress_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby -S rspec require 'spec_helper' diff --git a/spec/unit/memory_spec.rb b/spec/unit/memory_spec.rb index faa7c17930..fd4df0044e 100755 --- a/spec/unit/memory_spec.rb +++ b/spec/unit/memory_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby -S rspec require 'spec_helper' diff --git a/spec/unit/operatingsystem_spec.rb b/spec/unit/operatingsystem_spec.rb index 59069a1bcd..f6bdb0c27f 100755 --- a/spec/unit/operatingsystem_spec.rb +++ b/spec/unit/operatingsystem_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby -S rspec require 'spec_helper' diff --git a/spec/unit/operatingsystemrelease_spec.rb b/spec/unit/operatingsystemrelease_spec.rb index b12cd26dd5..318875c494 100755 --- a/spec/unit/operatingsystemrelease_spec.rb +++ b/spec/unit/operatingsystemrelease_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby -S rspec require 'spec_helper' diff --git a/spec/unit/physicalprocessorcount_spec.rb b/spec/unit/physicalprocessorcount_spec.rb index 8fd471a459..85f53b3995 100755 --- a/spec/unit/physicalprocessorcount_spec.rb +++ b/spec/unit/physicalprocessorcount_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby -S rspec require 'spec_helper' diff --git a/spec/unit/processor_spec.rb b/spec/unit/processor_spec.rb index 6be839867e..849bad358c 100755 --- a/spec/unit/processor_spec.rb +++ b/spec/unit/processor_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby -S rspec require 'spec_helper' diff --git a/spec/unit/selinux_spec.rb b/spec/unit/selinux_spec.rb index 63313a70f3..73d9c17679 100755 --- a/spec/unit/selinux_spec.rb +++ b/spec/unit/selinux_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby -S rspec require 'spec_helper' diff --git a/spec/unit/ssh_spec.rb b/spec/unit/ssh_spec.rb index 0be1eba739..a58d949546 100755 --- a/spec/unit/ssh_spec.rb +++ b/spec/unit/ssh_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby -S rspec require 'spec_helper' require 'facter/ssh' diff --git a/spec/unit/uptime_spec.rb b/spec/unit/uptime_spec.rb index e1962d9250..fd30efccf1 100755 --- a/spec/unit/uptime_spec.rb +++ b/spec/unit/uptime_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby -S rspec require 'spec_helper' require 'facter/util/uptime' diff --git a/spec/unit/util/collection_spec.rb b/spec/unit/util/collection_spec.rb index 1f8c12ffd5..84fee3dd6a 100755 --- a/spec/unit/util/collection_spec.rb +++ b/spec/unit/util/collection_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby -S rspec require 'spec_helper' require 'facter/util/collection' diff --git a/spec/unit/util/confine_spec.rb b/spec/unit/util/confine_spec.rb index 806637dc8c..7f5a07492b 100755 --- a/spec/unit/util/confine_spec.rb +++ b/spec/unit/util/confine_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby -S rspec require 'spec_helper' require 'facter/util/confine' diff --git a/spec/unit/util/ec2_spec.rb b/spec/unit/util/ec2_spec.rb index f1fbe3c073..354949b01b 100755 --- a/spec/unit/util/ec2_spec.rb +++ b/spec/unit/util/ec2_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby -S rspec require 'spec_helper' require 'facter/util/ec2' diff --git a/spec/unit/util/fact_spec.rb b/spec/unit/util/fact_spec.rb index 24c46e8dfd..72f95db0b5 100755 --- a/spec/unit/util/fact_spec.rb +++ b/spec/unit/util/fact_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby -S rspec require 'spec_helper' require 'facter/util/fact' diff --git a/spec/unit/util/ip_spec.rb b/spec/unit/util/ip_spec.rb index 76a7b171a2..e4bc056c78 100755 --- a/spec/unit/util/ip_spec.rb +++ b/spec/unit/util/ip_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby -S rspec require 'spec_helper' require 'facter/util/ip' diff --git a/spec/unit/util/loader_spec.rb b/spec/unit/util/loader_spec.rb index fdb9e393ad..04e8552a37 100755 --- a/spec/unit/util/loader_spec.rb +++ b/spec/unit/util/loader_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby -S rspec require 'spec_helper' require 'facter/util/loader' diff --git a/spec/unit/util/macaddress_spec.rb b/spec/unit/util/macaddress_spec.rb index bf9d4e55f8..c0e3456e5e 100755 --- a/spec/unit/util/macaddress_spec.rb +++ b/spec/unit/util/macaddress_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby -S rspec require 'spec_helper' require 'facter/util/macaddress' diff --git a/spec/unit/util/macosx_spec.rb b/spec/unit/util/macosx_spec.rb index 5059ed230a..d05044ff9f 100755 --- a/spec/unit/util/macosx_spec.rb +++ b/spec/unit/util/macosx_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby -S rspec require 'spec_helper' require 'facter/util/macosx' diff --git a/spec/unit/util/manufacturer_spec.rb b/spec/unit/util/manufacturer_spec.rb index da9bcbc286..c5230c3aec 100755 --- a/spec/unit/util/manufacturer_spec.rb +++ b/spec/unit/util/manufacturer_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby -S rspec require 'spec_helper' require 'facter/util/manufacturer' diff --git a/spec/unit/util/processor_spec.rb b/spec/unit/util/processor_spec.rb index 6e0d5b665c..b4482157ab 100755 --- a/spec/unit/util/processor_spec.rb +++ b/spec/unit/util/processor_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby -S rspec require 'spec_helper' require 'facter/util/processor' diff --git a/spec/unit/util/registry_spec.rb b/spec/unit/util/registry_spec.rb index f13860afd8..d7d77b5a98 100644 --- a/spec/unit/util/registry_spec.rb +++ b/spec/unit/util/registry_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby -S rspec require 'spec_helper' require 'facter/operatingsystem' require 'facter/util/registry' diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index 8a1d37b38e..ffe6b6b850 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby -S rspec require 'spec_helper' require 'facter/util/resolution' diff --git a/spec/unit/util/uptime_spec.rb b/spec/unit/util/uptime_spec.rb index 79bb56c6c7..cdfc611676 100755 --- a/spec/unit/util/uptime_spec.rb +++ b/spec/unit/util/uptime_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby -S rspec require 'spec_helper' require 'facter/util/uptime' diff --git a/spec/unit/util/virtual_spec.rb b/spec/unit/util/virtual_spec.rb index b64a7f8ef4..c5393fbb95 100755 --- a/spec/unit/util/virtual_spec.rb +++ b/spec/unit/util/virtual_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby -S rspec require 'spec_helper' require 'facter/util/virtual' diff --git a/spec/unit/util/vlans_spec.rb b/spec/unit/util/vlans_spec.rb index 17ae7eda35..8316b05bcd 100755 --- a/spec/unit/util/vlans_spec.rb +++ b/spec/unit/util/vlans_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby -S rspec require 'spec_helper' require 'facter/util/vlans' diff --git a/spec/unit/util/wmi_spec.rb b/spec/unit/util/wmi_spec.rb index 0f03a18d19..ed48ef70cf 100755 --- a/spec/unit/util/wmi_spec.rb +++ b/spec/unit/util/wmi_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby -S rspec require 'spec_helper' require 'facter/util/wmi' diff --git a/spec/unit/util/xendomains_spec.rb b/spec/unit/util/xendomains_spec.rb index ff0481fae6..391b8a4b92 100755 --- a/spec/unit/util/xendomains_spec.rb +++ b/spec/unit/util/xendomains_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby -S rspec require 'spec_helper' require 'facter/util/xendomains' diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index 5d7b1ac05e..dceabc8349 100755 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby -S rspec require 'spec_helper' require 'facter/util/virtual' From 082dba6e375a5d890f57966fff723e7613730900 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Wed, 11 Jul 2012 15:11:51 -0700 Subject: [PATCH 0909/3753] (#2157) Rename collection loader methods Without this patch the methods used to load facts do not match up with the instance variables bound to the loaders themselves. This patch simply renames the methods to match the internal and external loader instance variable names. This fixes the problem by making it explicit which loader is being used when the methods are called on the collection instance. Paired-with: Hailee Kenney --- lib/facter/util/collection.rb | 18 +++++++++--------- spec/unit/ec2_spec.rb | 14 +++++++------- spec/unit/filesystems_spec.rb | 2 +- spec/unit/ldom_spec.rb | 4 ++-- spec/unit/memory_spec.rb | 24 ++++++++++++------------ spec/unit/operatingsystem_spec.rb | 2 +- spec/unit/processor_spec.rb | 6 +++--- spec/unit/ssh_spec.rb | 4 ++-- spec/unit/util/collection_spec.rb | 4 ++-- 9 files changed, 39 insertions(+), 39 deletions(-) diff --git a/lib/facter/util/collection.rb b/lib/facter/util/collection.rb index cbfd65bf72..cb9359ba45 100644 --- a/lib/facter/util/collection.rb +++ b/lib/facter/util/collection.rb @@ -84,10 +84,10 @@ def fact(name) load(name) unless @facts[name] # Try HARDER - loader.load_all unless @facts[name] + internal_loader.load_all unless @facts[name] if @facts.empty? - Facter.warnonce("No facts loaded from #{loader.search_path.join(File::PATH_SEPARATOR)}") + Facter.warnonce("No facts loaded from #{internal_loader.search_path.join(File::PATH_SEPARATOR)}") end @facts[name] @@ -104,22 +104,22 @@ def list return @facts.keys end - def load(name) - loader.load(name) - ext_loader.load(self) + def load(name) + internal_loader.load(name) + external_loader.load(self) end # Load all known facts. def load_all - loader.load_all - ext_loader.load(self) + internal_loader.load_all + external_loader.load(self) end - def loader + def internal_loader @internal_loader end - def ext_loader + def external_loader @external_loader end diff --git a/spec/unit/ec2_spec.rb b/spec/unit/ec2_spec.rb index f65eff16b0..03712d2a60 100755 --- a/spec/unit/ec2_spec.rb +++ b/spec/unit/ec2_spec.rb @@ -33,7 +33,7 @@ with("#{api_prefix}/2008-02-01/user-data/"). at_least_once.returns(StringIO.new("")) - Facter.collection.loader.load(:ec2) + Facter.collection.internal_loader.load(:ec2) Facter.fact(:ec2_foo).value.should == "bar" end @@ -51,7 +51,7 @@ with("#{api_prefix}/2008-02-01/user-data/"). at_least_once.returns(StringIO.new("")) - Facter.collection.loader.load(:ec2) + Facter.collection.internal_loader.load(:ec2) Facter.fact(:ec2_foo).value.should == "bar,baz" end @@ -73,7 +73,7 @@ with("#{api_prefix}/2008-02-01/user-data/"). at_least_once.returns(StringIO.new("")) - Facter.collection.loader.load(:ec2) + Facter.collection.internal_loader.load(:ec2) Facter.fact(:ec2_foo_bar).value.should == "baz" end @@ -87,7 +87,7 @@ with("#{api_prefix}/2008-02-01/user-data/"). at_least_once.returns(StringIO.new("test")) - Facter.collection.loader.load(:ec2) + Facter.collection.internal_loader.load(:ec2) Facter.fact(:ec2_userdata).value.should == ["test"] end end @@ -114,7 +114,7 @@ at_least_once.returns(StringIO.new("test")) # Force a fact load - Facter.collection.loader.load(:ec2) + Facter.collection.internal_loader.load(:ec2) Facter.fact(:ec2_userdata).value.should == ["test"] end @@ -142,7 +142,7 @@ at_least_once.returns(StringIO.new("test")) # Force a fact load - Facter.collection.loader.load(:ec2) + Facter.collection.internal_loader.load(:ec2) Facter.fact(:ec2_userdata).value.should == ["test"] end @@ -166,7 +166,7 @@ with("#{api_prefix}/2008-02-01/user-data/").never # Force a fact load - Facter.collection.loader.load(:ec2) + Facter.collection.internal_loader.load(:ec2) Facter.fact(:ec2_userdata).should == nil end diff --git a/spec/unit/filesystems_spec.rb b/spec/unit/filesystems_spec.rb index df364b0636..ab52adaea7 100644 --- a/spec/unit/filesystems_spec.rb +++ b/spec/unit/filesystems_spec.rb @@ -16,7 +16,7 @@ fixture_data = my_fixture_read('linux') Facter::Util::Resolution.expects(:exec) \ .with('cat /proc/filesystems 2> /dev/null').returns(fixture_data) - Facter.collection.loader.load(:filesystems) + Facter.collection.internal_loader.load(:filesystems) end after :each do diff --git a/spec/unit/ldom_spec.rb b/spec/unit/ldom_spec.rb index 0c1a6ec365..f97d4a8765 100644 --- a/spec/unit/ldom_spec.rb +++ b/spec/unit/ldom_spec.rb @@ -17,7 +17,7 @@ def ldom_fixtures(filename) # http://docs.oracle.com/cd/E23824_01/html/821-1462/virtinfo-1m.html Facter::Util::Resolution.stubs(:exec).with("virtinfo -ap"). returns(ldom_fixtures('ldom_v1')) - Facter.collection.loader.load(:ldom) + Facter.collection.internal_loader.load(:ldom) end it "should return correct impl on version 1.0" do @@ -64,7 +64,7 @@ def ldom_fixtures(filename) describe "when running on non ldom hardware" do before :each do Facter::Util::Resolution.stubs(:exec).with("virtinfo -ap").returns(nil) - Facter.collection.loader.load(:ldom) + Facter.collection.internal_loader.load(:ldom) end it "should return correct virtual" do diff --git a/spec/unit/memory_spec.rb b/spec/unit/memory_spec.rb index fd4df0044e..459efb9557 100755 --- a/spec/unit/memory_spec.rb +++ b/spec/unit/memory_spec.rb @@ -9,7 +9,7 @@ describe "when returning scaled sizes" do before(:each) do - Facter.collection.loader.load(:memory) + Facter.collection.internal_loader.load(:memory) end [ "memorysize", @@ -66,7 +66,7 @@ Facter::Util::Resolution.stubs(:exec).with('vm_stat').returns(sample_vm_stat) Facter::Util::Resolution.stubs(:exec).with('sysctl vm.swapusage').returns("vm.swapusage: total = 64.00M used = 1.00M free = 63.00M (encrypted)") - Facter.collection.loader.load(:memory) + Facter.collection.internal_loader.load(:memory) end it "should return the current swap size in MB" do @@ -118,7 +118,7 @@ File.stubs(:readlines).with("/proc/meminfo").returns(meminfo.split("\n")) - Facter.collection.loader.load(:memory) + Facter.collection.internal_loader.load(:memory) end after(:each) do @@ -155,7 +155,7 @@ Facter::Util::Resolution.stubs(:exec).with('swap -l').returns(swapusage) - Facter.collection.loader.load(:memory) + Facter.collection.internal_loader.load(:memory) end after(:each) do @@ -209,7 +209,7 @@ Facter::Util::Resolution.stubs(:exec).with("sysctl hw.physmem | cut -d'=' -f2").returns('267321344') - Facter.collection.loader.load(:memory) + Facter.collection.internal_loader.load(:memory) end after :each do @@ -265,7 +265,7 @@ SWAP Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/swap -l').returns sample_swap_line - Facter.collection.loader.load(:memory) + Facter.collection.internal_loader.load(:memory) end it "should return the current memory size in MB" do @@ -293,7 +293,7 @@ /dev/swap2 4294967295,4294967295 16 2097136 2097136 SWAP Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/swap -l').returns sample_swap_line - Facter.collection.loader.load(:memory) + Facter.collection.internal_loader.load(:memory) end it "should return the current memory size in MB" do @@ -317,7 +317,7 @@ before(:each) do Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/swap -l').returns "" - Facter.collection.loader.load(:memory) + Facter.collection.internal_loader.load(:memory) end it "should return the current memory size in MB" do @@ -358,7 +358,7 @@ Facter::Util::Resolution.stubs(:exec).with("sysctl -n hw.physmem").returns('248512512') - Facter.collection.loader.load(:memory) + Facter.collection.internal_loader.load(:memory) end after :each do @@ -410,7 +410,7 @@ SWAP Facter::Util::Resolution.stubs(:exec).with('swapinfo -k').returns sample_swapinfo - Facter.collection.loader.load(:memory) + Facter.collection.internal_loader.load(:memory) end it "should return the current swap free in MB" do @@ -438,7 +438,7 @@ SWAP Facter::Util::Resolution.stubs(:exec).with('swapinfo -k').returns sample_swapinfo - Facter.collection.loader.load(:memory) + Facter.collection.internal_loader.load(:memory) end it "should return the current swap free in MB" do @@ -467,7 +467,7 @@ SWAP Facter::Util::Resolution.stubs(:exec).with('swapinfo -k').returns sample_swapinfo - Facter.collection.loader.load(:memory) + Facter.collection.internal_loader.load(:memory) end it "should return the current swap free in MB" do diff --git a/spec/unit/operatingsystem_spec.rb b/spec/unit/operatingsystem_spec.rb index f6bdb0c27f..631dda2314 100755 --- a/spec/unit/operatingsystem_spec.rb +++ b/spec/unit/operatingsystem_spec.rb @@ -66,7 +66,7 @@ describe "depending on LSB release information" do before :each do - Facter.collection.loader.load(:lsb) + Facter.collection.internal_loader.load(:lsb) end it "on Ubuntu should use the lsbdistid fact" do diff --git a/spec/unit/processor_spec.rb b/spec/unit/processor_spec.rb index 849bad358c..e9368d2cc0 100755 --- a/spec/unit/processor_spec.rb +++ b/spec/unit/processor_spec.rb @@ -22,7 +22,7 @@ def load(procs) # processor facts belong to a file with a different name, # so load the file explicitly (after stubbing kernel), # but we have to stub execquery first - Facter.collection.loader.load(:processor) + Facter.collection.internal_loader.load(:processor) end describe "2003" do @@ -68,7 +68,7 @@ def load(procs) describe "on Solaris" do before :each do - Facter.collection.loader.load(:processor) + Facter.collection.internal_loader.load(:processor) Facter.fact(:kernel).stubs(:value).returns(:sunos) end @@ -87,7 +87,7 @@ def load(procs) describe "on Unixes" do before :each do - Facter.collection.loader.load(:processor) + Facter.collection.internal_loader.load(:processor) end it "should be 1 in SPARC fixture" do diff --git a/spec/unit/ssh_spec.rb b/spec/unit/ssh_spec.rb index a58d949546..e1556deb3d 100755 --- a/spec/unit/ssh_spec.rb +++ b/spec/unit/ssh_spec.rb @@ -13,10 +13,10 @@ '/etc/opt/ssh', ] - before do + before :each do # We need these facts loaded, but they belong to a file with a # different name, so load the file explicitly. - Facter.collection.loader.load(:ssh) + Facter.collection.internal_loader.load(:ssh) end # fingerprints extracted from ssh-keygen -r '' -f /etc/ssh/ssh_host_dsa_key.pub diff --git a/spec/unit/util/collection_spec.rb b/spec/unit/util/collection_spec.rb index 84fee3dd6a..94a3ed1cce 100755 --- a/spec/unit/util/collection_spec.rb +++ b/spec/unit/util/collection_spec.rb @@ -105,12 +105,12 @@ end it "should use its loader to try to load the fact if no fact can be found" do - collection.loader.expects(:load).with(:testing) + collection.internal_loader.expects(:load).with(:testing) collection.fact("testing") end it "should return nil if it cannot find or load the fact" do - collection.loader.expects(:load).with(:testing) + collection.internal_loader.expects(:load).with(:testing) collection.fact("testing").should be_nil end end From 8f7267ebf11a6678d324915b231b7ab24942899c Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Thu, 12 Jul 2012 10:38:19 -0700 Subject: [PATCH 0910/3753] (#2157) Make JsonParser always available but disabled without json gem Without this patch the tests for the JsonParser class and the class itself are not defined if the JSON gem is not available. This is a problem because the end user will consider this behavior a "silent failure" if they place *.json files in the data directory and Facter silently skips over them because the JSON library is not loadable. This patch fixes the problem by always defining the JsonParser class. The class itself will issue a warning when it tries to return results for a JSON data file and the JSON library is not available. This patch also marks the json parser tests as pending if the json library is unloadable. This patch also fixes this error with command line option parsing: $ facter --json invalid option: --json With this patch we're getting: $ facter --json You do not have JSON support in your version of Ruby. JSON output disabled Paired-with: Hailee Kenney --- lib/facter.rb | 12 ++++++++++- lib/facter/application.rb | 4 +--- lib/facter/util/directory_loader.rb | 3 ++- lib/facter/util/json.rb | 14 ------------- lib/facter/util/parser.rb | 32 +++++++++++++---------------- spec/unit/util/parser_spec.rb | 17 ++++++++------- 6 files changed, 36 insertions(+), 46 deletions(-) delete mode 100644 lib/facter/util/json.rb diff --git a/lib/facter.rb b/lib/facter.rb index 334722de07..a20769b2fc 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -21,7 +21,6 @@ module Util; end require 'facter/util/fact' require 'facter/util/collection' require 'facter/util/monkey_patches' - require 'facter/util/json' include Comparable include Enumerable @@ -95,6 +94,17 @@ def self.timing? @@timing != 0 end + # Facter.json? is meant to provide a lightweight way to check if the JSON + # "feature" is available. + def self.json? + begin + require 'json' + true + rescue LoadError + false + end + end + # Return a fact object by name. If you use this, you still have to call # 'value' on it to retrieve the actual value. def self.[](name) diff --git a/lib/facter/application.rb b/lib/facter/application.rb index 33b73857bf..23e3a14988 100644 --- a/lib/facter/application.rb +++ b/lib/facter/application.rb @@ -91,9 +91,7 @@ def self.parse(argv) options = {} OptionParser.new do |opts| opts.on("-y", "--yaml") { |v| options[:yaml] = v } - if Facter.json? - opts.on("-j", "--json") { |v| options[:json] = v } - end + opts.on("-j", "--json") { |v| options[:json] = v } opts.on( "--trace") { |v| options[:trace] = v } opts.on( "--external-dir DIR") { |v| create_directory_loader(v) } opts.on( "--no-external-dir") { |v| create_nothing_loader } diff --git a/lib/facter/util/directory_loader.rb b/lib/facter/util/directory_loader.rb index 4c176ff22a..9c05e2cf07 100644 --- a/lib/facter/util/directory_loader.rb +++ b/lib/facter/util/directory_loader.rb @@ -12,9 +12,10 @@ # Facts can be in the form of JSON, YAML or Text files # and any executable that returns key=value pairs. -require 'facter/util/parser' +require 'facter' require 'facter/util/config' require 'facter/util/composite_loader' +require 'facter/util/parser' class Facter::Util::DirectoryLoader require 'yaml' diff --git a/lib/facter/util/json.rb b/lib/facter/util/json.rb deleted file mode 100644 index f71f209ddc..0000000000 --- a/lib/facter/util/json.rb +++ /dev/null @@ -1,14 +0,0 @@ -module Facter - - # Idealy this should be an autoloaded feature test like in Puppet, - # but that's more overhead that is needed right now. - def self.json? - begin - require 'json' - true - rescue LoadError - false - end - end -end - diff --git a/lib/facter/util/parser.rb b/lib/facter/util/parser.rb index 4f3ad21f38..653f2979dc 100644 --- a/lib/facter/util/parser.rb +++ b/lib/facter/util/parser.rb @@ -2,7 +2,7 @@ # facts such as scripts, text, json and yaml files. # # Parsers must subclass this class and provide their own #results method. -require 'facter/util/json' +require 'facter' module Facter::Util::Parser @parsers = [] @@ -81,28 +81,24 @@ def results extension_matches?(filename, "txt") end - if Facter.json? - class JsonParser < Base - def self.matches?(filename) - extension_matches?(filename, "json") - end - - def results - attempts = 0 - begin - require 'json' - rescue LoadError => e - raise e if attempts >= 1 - attempts += 1 - end + class JsonParser < Base + def self.matches?(filename) + extension_matches?(filename, "json") + end + def results + if Facter.json? JSON.load(File.read(filename)) + else + Facter.warnonce "Cannot parse JSON data file #{filename} without the json library." + Facter.warnonce "Suggested next step is `gem install json` to install the json library." + nil end end + end - register(JsonParser) do |filename| - extension_matches?(filename, "json") - end + register(JsonParser) do |filename| + extension_matches?(filename, "json") end class ScriptParser < Base diff --git a/spec/unit/util/parser_spec.rb b/spec/unit/util/parser_spec.rb index 91dea38621..6d955c53e1 100644 --- a/spec/unit/util/parser_spec.rb +++ b/spec/unit/util/parser_spec.rb @@ -54,18 +54,17 @@ lambda { Facter::Util::Parser.parser_for("/some/path/that/doesn't/exist.yaml").results }.should_not raise_error end end - - if Facter.json? - describe "json" do - it "should return a hash of whatever is stored on disk" do - file = tmpfilename('parser') + ".json" - data = {"one" => "two", "three" => "four"} + describe "json" do + it "should return a hash of whatever is stored on disk" do + pending("this test requires the json library") unless Facter.json? + file = tmpfilename('parser') + ".json" - File.open(file, "w") { |f| f.print data.to_json } + data = {"one" => "two", "three" => "four"} - Facter::Util::Parser.parser_for(file).results.should == data - end + File.open(file, "w") { |f| f.print data.to_json } + + Facter::Util::Parser.parser_for(file).results.should == data end end From 5ba762ca16873f8d22dc1537258ae66f87b18135 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Thu, 12 Jul 2012 11:25:33 -0700 Subject: [PATCH 0911/3753] (#2157) Make parser matcher work with Enumerable Without this patch the parser matcher for filename extensions was locked into the specific class of Array and String. The implementation is generally applicable to Enumerable and descendants of String though. This patch simply changes the conditional checks to use the Ruby idiom of comparing an object instances to parent classes using a case statement. This is equivalent to using kind_of? or is_a? which are themselves synonyms. diff --git a/lib/facter/util/parser.rb b/lib/facter/util/parser.rb index 653f297..05dec2f 100644 --- a/lib/facter/util/parser.rb +++ b/lib/facter/util/parser.rb @@ -10,10 +10,11 @@ module Facter::Util::Parser # For support mutliple extensions you can pass an array of extensions as # +ext+. def self.extension_matches?(filename, ext) - if ext.class == String then - extension = ext.downcase - elsif ext.class == Array then - extension = ext.collect {|x| x.downcase } + extension = case ext + when String + ext.downcase + when Enumerable + ext.collect {|x| x.downcase } end [extension].flatten.to_a.include?(file_extension(filename).downcase) end --- lib/facter/util/parser.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/facter/util/parser.rb b/lib/facter/util/parser.rb index 653f2979dc..05dec2fe41 100644 --- a/lib/facter/util/parser.rb +++ b/lib/facter/util/parser.rb @@ -10,10 +10,11 @@ module Facter::Util::Parser # For support mutliple extensions you can pass an array of extensions as # +ext+. def self.extension_matches?(filename, ext) - if ext.class == String then - extension = ext.downcase - elsif ext.class == Array then - extension = ext.collect {|x| x.downcase } + extension = case ext + when String + ext.downcase + when Enumerable + ext.collect {|x| x.downcase } end [extension].flatten.to_a.include?(file_extension(filename).downcase) end From c436ef271fa58c88c69c790042c525635972fd9d Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Thu, 12 Jul 2012 11:28:45 -0700 Subject: [PATCH 0912/3753] (#2157) Remove needless methods This patch simply removes two methods that could not have worked if they were ever invoked by the implementation. They could not have worked because the methods they were calling do not exist in the scope they were being called. --- lib/facter/util/parser.rb | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/lib/facter/util/parser.rb b/lib/facter/util/parser.rb index 05dec2fe41..d46dfb9ffe 100644 --- a/lib/facter/util/parser.rb +++ b/lib/facter/util/parser.rb @@ -60,10 +60,6 @@ def results end class TextParser < Base - def self.matches?(filename) - extension_matches?(filename, "txt") - end - def results result = {} File.readlines(filename).each do |line| @@ -83,10 +79,6 @@ def results end class JsonParser < Base - def self.matches?(filename) - extension_matches?(filename, "json") - end - def results if Facter.json? JSON.load(File.read(filename)) @@ -98,6 +90,8 @@ def results end end + + register(JsonParser) do |filename| extension_matches?(filename, "json") end From 84bc0f8cec3be37d6da4dd3577f6540954269d29 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Thu, 12 Jul 2012 14:49:23 -0700 Subject: [PATCH 0913/3753] (#2157) Change parser specs to not touch the filesystem Without this patch all of the parser specification examples would create files in the filesystem and read them back out or execute them. This is a problem because the tests are much slower to run, much harder to maintain, and there's crap left all over the place on my system. This patch fixes the problem by changing the parser implementations to load content on demand. The spec tests themselves have been changed to stub out the File.read method when appropriate. We've also changed the specifications to stub out Facter::Util::Resolution.exec Paired-with: Hailee Kenney --- lib/facter/util/parser.rb | 58 +++++++++++++---------- spec/unit/util/parser_spec.rb | 89 ++++++++++++++++------------------- 2 files changed, 74 insertions(+), 73 deletions(-) diff --git a/lib/facter/util/parser.rb b/lib/facter/util/parser.rb index d46dfb9ffe..d09eec52b0 100644 --- a/lib/facter/util/parser.rb +++ b/lib/facter/util/parser.rb @@ -3,6 +3,7 @@ # # Parsers must subclass this class and provide their own #results method. require 'facter' +require 'yaml' module Facter::Util::Parser @parsers = [] @@ -40,18 +41,34 @@ def self.parser_for(filename) class Base attr_reader :filename - def initialize(filename) + def initialize(filename, content = nil) @filename = filename + @content = content end - end - class YamlParser < Base + def content + @content ||= File.read(filename) + end + + # results on the base class is really meant to be just an exception handler + # wrapper. def results - require 'yaml' + parse_results + rescue Exception => detail + Facter.warn("Failed to handle #{filename} as #{self.class} facts") + Facter.warn("detail: #{detail.class}: #{detail.message}") + Facter.debug(detail.backtrace.join("\n\t")) + nil + end + + def parse_results + raise ArgumentError, "Subclasses must respond to parse_results" + end + end - YAML.load_file(filename) - rescue Exception => e - Facter.warn("Failed to handle #{filename} as yaml facts: #{e.class}: #{e}") + class YamlParser < Base + def parse_results + YAML.load(content) end end @@ -60,17 +77,15 @@ def results end class TextParser < Base - def results + def parse_results + re = /^(.+)=(.+)$/ result = {} - File.readlines(filename).each do |line| - - if line.chomp =~ /^(.+)=(.+)$/ - result[$1] = $2 + content.lines.each do |line| + if match_data = re.match(line.chomp) + result[match_data[1]] = match_data[2] end end result - rescue Exception => e - Facter.warn("Failed to handle #{filename} as text facts: #{e.class}: #{e}") end end @@ -81,7 +96,7 @@ def results class JsonParser < Base def results if Facter.json? - JSON.load(File.read(filename)) + JSON.load(content) else Facter.warnonce "Cannot parse JSON data file #{filename} without the json library." Facter.warnonce "Suggested next step is `gem install json` to install the json library." @@ -90,8 +105,6 @@ def results end end - - register(JsonParser) do |filename| extension_matches?(filename, "json") end @@ -101,16 +114,13 @@ def results output = Facter::Util::Resolution.exec(filename) result = {} - output.split("\n").each do |line| - if line =~ /^(.+)=(.+)$/ - result[$1] = $2 + re = /^(.+)=(.+)$/ + output.lines.collect(&:chomp).each do |line| + if match_data = re.match(line) + result[match_data[1]] = match_data[2] end end - result - rescue Exception => e - Facter.warn("Failed to handle #{filename} as script facts: #{e.class}: #{e}") - Facter.debug(e.backtrace.join("\n\t")) end end diff --git a/spec/unit/util/parser_spec.rb b/spec/unit/util/parser_spec.rb index 6d955c53e1..2401c34528 100644 --- a/spec/unit/util/parser_spec.rb +++ b/spec/unit/util/parser_spec.rb @@ -33,86 +33,77 @@ end end - describe "yaml" do - it "should return a hash of whatever is stored on disk" do - file = tmpfilename('parser') + ".yaml" - - data = {"one" => "two", "three" => "four"} + let(:data) do {"one" => "two", "three" => "four"} end - File.open(file, "w") { |f| f.print YAML.dump(data) } + describe "yaml" do + let(:data_in_yaml) do YAML.dump(data) end + let(:data_file) do "/tmp/foo.yaml" end - Facter::Util::Parser.parser_for(file).results.should == data + it "should return a hash of whatever is stored on disk" do + File.stubs(:read).with(data_file).returns(data_in_yaml) + described_class.parser_for(data_file).results.should == data end it "should handle exceptions and warn" do - file = tmpfilename('parser') + ".yaml" - - data = {"one" => "two", "three" => "four"} - - File.open(file, "w") { |f| f.print "}" } - Facter.expects(:warn) - lambda { Facter::Util::Parser.parser_for("/some/path/that/doesn't/exist.yaml").results }.should_not raise_error + # YAML data with an error + File.stubs(:read).with(data_file).returns(data_in_yaml + "}") + Facter.expects(:warn).at_least_once + lambda { Facter::Util::Parser.parser_for(data_file).results }.should_not raise_error end end describe "json" do + let(:data_in_json) do JSON.dump(data) end + let(:data_file) do "/tmp/foo.yaml" end + it "should return a hash of whatever is stored on disk" do pending("this test requires the json library") unless Facter.json? - file = tmpfilename('parser') + ".json" - - data = {"one" => "two", "three" => "four"} - - File.open(file, "w") { |f| f.print data.to_json } - - Facter::Util::Parser.parser_for(file).results.should == data + File.stubs(:read).with(data_file).returns(data_in_json) + Facter::Util::Parser.parser_for(data_file).results.should == data end end describe "txt" do - it "should return a hash of whatever is stored on disk" do - file = tmpfilename('parser') + ".txt" - - data = "one=two\nthree=four\n" + let(:data_file) do "/tmp/foo.txt" end - File.open(file, "w") { |f| f.print data } - - Facter::Util::Parser.parser_for(file).results.should == {"one" => "two", "three" => "four"} + shared_examples_for "txt parser" do + it "should return a hash of whatever is stored on disk" do + File.stubs(:read).with(data_file).returns(data_in_txt) + Facter::Util::Parser.parser_for(data_file).results.should == data + end end - it "should ignore any non-setting lines" do - file = tmpfilename('parser') + ".txt" - - data = "one=two\nfive\nthree=four\n" - - File.open(file, "w") { |f| f.print data } + context "well formed data" do + let(:data_in_txt) do "one=two\nthree=four\n" end + it_behaves_like "txt parser" + end - Facter::Util::Parser.parser_for(file).results.should == {"one" => "two", "three" => "four"} + context "extra data" do + let(:data_in_txt) do "one=two\nfive\nthree=four\n" end + it_behaves_like "txt parser" end end describe "scripts" do - before do - @script = tmpfilename('parser') - data = "#!/bin/sh -echo one=two -echo three=four -" - - File.open(@script, "w") { |f| f.print data } - File.chmod(0755, @script) + let :cmd do "/tmp/foo.sh" end + let :data_in_txt do "one=two\nthree=four\n" end + + before :each do + Facter::Util::Resolution.stubs(:exec).with(cmd).returns(data_in_txt) + File.stubs(:executable?).with(cmd).returns(true) end it "should return a hash of whatever is returned by the executable" do - Facter::Util::Parser.parser_for(@script).results.should == {"one" => "two", "three" => "four"} + File.stubs(:file?).with(cmd).returns(true) + Facter::Util::Parser.parser_for(cmd).results.should == data end it "should not parse a directory" do - Dir.mktmpdir do |dir| - Facter::Util::Parser.parser_for(dir).results.should == false - end + File.stubs(:file?).with(cmd).returns(false) + Facter::Util::Parser.parser_for(cmd).results.should == false end end - + describe "nothing parser" do it "uses the nothing parser when there is no other parser" do Facter::Util::Parser.parser_for("this.is.not.valid").results.should == false From 5075b5086e5772c15ff4274f954c32bcdf850f82 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Fri, 13 Jul 2012 11:09:28 -0700 Subject: [PATCH 0914/3753] (#2157) Fixup yaml typo in json spec test The test for the external fact loading of json data was incorrectly using a yaml file extension for the filename. This was causing failures because the YAML parser was being used instead of the JSON parser. This patch uses the proper json file extension. Paired-with: Hailee Kenney --- spec/unit/util/parser_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/unit/util/parser_spec.rb b/spec/unit/util/parser_spec.rb index 2401c34528..c12d74b631 100644 --- a/spec/unit/util/parser_spec.rb +++ b/spec/unit/util/parser_spec.rb @@ -54,7 +54,7 @@ describe "json" do let(:data_in_json) do JSON.dump(data) end - let(:data_file) do "/tmp/foo.yaml" end + let(:data_file) do "/tmp/foo.json" end it "should return a hash of whatever is stored on disk" do pending("this test requires the json library") unless Facter.json? From 83b43ff3f48dcb156047899a1dc5fb5f53bc7a9a Mon Sep 17 00:00:00 2001 From: Andrew Parker Date: Fri, 13 Jul 2012 14:42:22 -0700 Subject: [PATCH 0915/3753] Revert "(#2157) external facts command line - install.rb" This reverts commit 45b8cf3500710082acb55c9f2b059988d052691a. The external facts are not held in the libexec directory in order to be compatible with the stdlib facts.d directories. Reviewed-by: Jeff McCune --- install.rb | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/install.rb b/install.rb index cde513e11b..7fd442f081 100755 --- a/install.rb +++ b/install.rb @@ -89,7 +89,6 @@ def glob(list) man = glob(%w{man/man8/*}) libs = glob(%w{lib/**/*.rb lib/**/*.py lib/**/LICENSE}) tests = glob(%w{tests/**/*.rb}) -libexec = glob(%w{libexec/ext/README}) def do_bins(bins, target, strip = 's?bin/') bins.each do |bf| @@ -125,16 +124,6 @@ def do_man(man, strip = 'man/') end end -def do_libexec(libexec, strip = 'libexec/') - libexec.each do |lf| - olf = File.join(InstallOptions.libexec_dir, lf.gsub(/#{strip}/, '')) - op = File.dirname(olf) - FileUtils.makedirs(op, {:mode => 0755, :verbose => true}) - FileUtils.chmod(0755, op) - FileUtils.install(lf, olf, {:mode => 0644, :verbose => true}) - end -end - # Verify that all of the prereqs are installed def check_prereqs PREREQS.each { |pre| @@ -210,9 +199,6 @@ def prepare_installation opts.on('--mandir[=OPTIONAL]', 'Installation directory for man pages', 'overrides RbConfig::CONFIG["mandir"]') do |mandir| InstallOptions.mandir = mandir end - opts.on('--libexecdir[=OPTIONAL]', 'Installation directory for libexec files', 'Defaults to /usr/lib/facter') do |libexecdir| - InstallOptions.libexecdir = libexecdir - end opts.on('--quick', 'Performs a quick installation. Only the', 'installation is done.') do |quick| InstallOptions.rdoc = false InstallOptions.ri = false @@ -277,28 +263,16 @@ def prepare_installation mandir = RbConfig::CONFIG['mandir'] end - if not InstallOptions.libexecdir.nil? - libexecdir = InstallOptions.libexecdir - else - if is_windows? - libexecdir = Facter::Util::Config.windows_data_dir - else - libexecdir = "/usr/lib/facter" - end - end - if (destdir = InstallOptions.destdir) bindir = join(destdir, bindir) sbindir = join(destdir, sbindir) mandir = join(destdir, mandir) sitelibdir = join(destdir, sitelibdir) - libexeclibdir = join(destdir, libexeclibdir) FileUtils.makedirs(bindir) FileUtils.makedirs(sbindir) FileUtils.makedirs(mandir) FileUtils.makedirs(sitelibdir) - FileUtils.makedirs(libexeclibdir) end InstallOptions.site_dir = sitelibdir @@ -306,7 +280,6 @@ def prepare_installation InstallOptions.sbin_dir = sbindir InstallOptions.lib_dir = libdir InstallOptions.man_dir = mandir - InstallOptions.libexec_dir = libexecdir end ## @@ -454,4 +427,3 @@ def install_binfile(from, op_file, target) do_bins(bins, InstallOptions.bin_dir) do_libs(libs) do_man(man) -do_libexec(libexec) From 2fd7f6eb70a24fd831db4fc9f9b3dd7b3622b53e Mon Sep 17 00:00:00 2001 From: Andrew Parker Date: Fri, 13 Jul 2012 14:51:59 -0700 Subject: [PATCH 0916/3753] (Maint) Ensure that the right Facter is loaded during install The install.rb script uses Facter to figure out some things during install. However, it incorrectly adds itself to the load path and instead will pick up an already installed facter instead of the facter that is being installed. Paired-with: Patrick Carlisle --- install.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install.rb b/install.rb index 7fd442f081..2001ac4c30 100755 --- a/install.rb +++ b/install.rb @@ -65,7 +65,7 @@ $haveman = false end -$LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), 'lib')) +$LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), 'lib'))) require 'facter' @operatingsystem = Facter[:operatingsystem].value From 6ab542697edd9cdff83ea2507842303c79adf832 Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Fri, 13 Jul 2012 15:21:45 -0700 Subject: [PATCH 0917/3753] Only use git describe in Rakefile if git is present This commit modifies the get_version method of the Rakefile to only use git describe if git is present, and if not fall back to the old method of reading the facter.rb file. Currently rake -T fails if git is not present with 'command not found: git describe'. Signed-off-by: Moses Mendoza --- Rakefile | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index 0415b7a273..93fabaca8b 100644 --- a/Rakefile +++ b/Rakefile @@ -28,7 +28,12 @@ FILES = FileList[ ] def get_version - `git describe`.strip + %x{which git &> /dev/null} + if $?.success? + `git describe`.strip + else + File.read('lib/facter.rb')[/FACTERVERSION *= *'(.*)'/,1] or fail "Couldn't find FACTERVERSION" + end end # :build_environment and :tar are mostly borrowed from puppet-dashboard Rakefile From f273906ec5206bc74cd65a100399a0d495bb441f Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Fri, 13 Jul 2012 15:41:35 -0700 Subject: [PATCH 0918/3753] (#2157) Fix spec failures on Ruby 1.8.5 The external facts pull request contained a call to String#lines which is not a valid method in Ruby 1.8.5. Without this patch the specs fail correctly. This patch fixes the problem by changing String#lines to String#each_line. The tests pass on CentOS 5 with Ruby 1.8.5-25el5. This patch also fixes a missing method error in the directory loader where we were calling String#start_with? which does not exist in Ruby 1.8.5. This patch replaces the method call with a regular expression. Finally, the exception handler in the directory loader has been tightened up to only catch Errno::ENOENT exceptions. Paired-with: Hailee Kenney --- lib/facter/util/directory_loader.rb | 6 +++--- lib/facter/util/parser.rb | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/facter/util/directory_loader.rb b/lib/facter/util/directory_loader.rb index 9c05e2cf07..5fc2af1749 100644 --- a/lib/facter/util/directory_loader.rb +++ b/lib/facter/util/directory_loader.rb @@ -16,9 +16,9 @@ require 'facter/util/config' require 'facter/util/composite_loader' require 'facter/util/parser' +require 'yaml' class Facter::Util::DirectoryLoader - require 'yaml' class NoSuchDirectoryError < Exception end @@ -73,11 +73,11 @@ def load(collection) def entries Dir.entries(directory).find_all { |f| should_parse?(f) }.sort.map { |f| File.join(directory, f) } - rescue + rescue Errno::ENOENT => detail [] end def should_parse?(file) - not file.start_with?('.') + not file =~ /^\./ end end diff --git a/lib/facter/util/parser.rb b/lib/facter/util/parser.rb index d09eec52b0..924ebe3d34 100644 --- a/lib/facter/util/parser.rb +++ b/lib/facter/util/parser.rb @@ -80,7 +80,7 @@ class TextParser < Base def parse_results re = /^(.+)=(.+)$/ result = {} - content.lines.each do |line| + content.each_line do |line| if match_data = re.match(line.chomp) result[match_data[1]] = match_data[2] end @@ -115,8 +115,8 @@ def results result = {} re = /^(.+)=(.+)$/ - output.lines.collect(&:chomp).each do |line| - if match_data = re.match(line) + output.each_line do |line| + if match_data = re.match(line.chomp) result[match_data[1]] = match_data[2] end end From 7d1fb722237ca53be7b8cb61b4d92e604349063c Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Mon, 16 Jul 2012 09:46:58 -0700 Subject: [PATCH 0919/3753] (Maint) Whitespace fixup --- spec/unit/util/config_spec.rb | 2 -- spec/unit/util/loader_spec.rb | 24 +++++++++--------------- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/spec/unit/util/config_spec.rb b/spec/unit/util/config_spec.rb index 7f6adce13d..925c1e5def 100644 --- a/spec/unit/util/config_spec.rb +++ b/spec/unit/util/config_spec.rb @@ -34,7 +34,6 @@ end describe "external_facts_dirs" do - it "should return the default value for linux" do Facter::Util::Config.stubs(:is_windows?).returns(false) Facter::Util::Config.external_facts_dirs.should == ["/etc/facter/facts.d", "/etc/puppetlabs/facter/facts.d"] @@ -52,5 +51,4 @@ Facter::Util::Config.external_facts_dirs.should == [File.join("C:\\Documents", 'PuppetLabs', 'facter', 'facts.d')] end end - end diff --git a/spec/unit/util/loader_spec.rb b/spec/unit/util/loader_spec.rb index 04e8552a37..8fbc4fcb69 100755 --- a/spec/unit/util/loader_spec.rb +++ b/spec/unit/util/loader_spec.rb @@ -39,21 +39,21 @@ def load_file(file) @settings = mock 'settings' @settings.stubs(:value).returns "/eh" end - + it "should cache the result of a previous check" do Pathname.any_instance.expects(:absolute?).returns(true).once - + # we explicitly want two calls here to check that we get # the second from the cache @loader.should be_valid_search_path "/foo" @loader.should be_valid_search_path "/foo" end - - # Used to have test for " " as a directory since that should + + # Used to have test for " " as a directory since that should # be a relative directory, but on Windows in both 1.8.7 and # 1.9.3 it is an absolute directory (WTF Windows). Considering - # we didn't have a valid use case for a " " directory, the - # test was removed. + # we didn't have a valid use case for a " " directory, the + # test was removed. [ '.', @@ -67,13 +67,10 @@ def load_file(file) ' /', ' \/', ].each do |dir| - it "should be false for relative path #{dir}" do @loader.should_not be_valid_search_path dir end - end - [ '/.', '/..', @@ -86,11 +83,9 @@ def load_file(file) '/ ', '/ /..', ].each do |dir| - it "should be true for absolute path #{dir}" do @loader.should be_valid_search_path dir end - end end @@ -111,15 +106,14 @@ def load_file(file) end end - it "should warn the user when an invalid search path has been excluded" do + it "should warn the user when an invalid search path has been excluded" do dirs = $LOAD_PATH.collect { |d| File.join(d, "facter") } @loader.stubs(:valid_search_path?).returns(false) dirs.each do |dir| Facter.expects(:debugonce).with("Relative directory #{dir} removed from search path.").once - end + end paths = @loader.search_path - end - + end it "should exclude invalid search paths" do dirs = $LOAD_PATH.collect { |d| File.join(d, "facter") } From 250f9308e2708ad90a4d1139e6831e718aa41bd3 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Mon, 16 Jul 2012 09:48:41 -0700 Subject: [PATCH 0920/3753] (#2157) Fixup remove executable facts on Windows Without this patch applied the ScriptParser class is still being used to try and resolve invalid facts on windows. This causes the following spec failure: 2) Facter::Util::DirectoryLoader when loading facts from disk should ignore files with an extension of 'bak' Failure/Error: subject.load(collection) NoMethodError: undefined method `each_line' for nil:NilClass # ./spec/../lib/facter/util/parser.rb:118:in `results' # ./spec/../lib/facter/util/directory_loader.rb:61:in `load' # ./spec/../lib/facter/util/directory_loader.rb:55:in `each' # ./spec/../lib/facter/util/directory_loader.rb:55:in `load' # ./spec/unit/util/directory_loader_spec.rb:60 This failure is a result of `File.executable?` returning true on Windows when the file itself is not actually executable, because it is not a PATHEXT file. This patch also updates the parser specification examples to ensure the expected results are returned on Windows. In particular, script parsers should be disabled on the windows platform. Paired-with: Hailee Kenney --- lib/facter/util/parser.rb | 6 ++++-- spec/unit/util/config_spec.rb | 1 + spec/unit/util/parser_spec.rb | 21 +++++++++++++++++++-- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/lib/facter/util/parser.rb b/lib/facter/util/parser.rb index 924ebe3d34..259d4ca819 100644 --- a/lib/facter/util/parser.rb +++ b/lib/facter/util/parser.rb @@ -125,7 +125,9 @@ def results end register(ScriptParser) do |filename| - File.executable?(filename) && File.file?(filename) + if not Facter::Util::Config.is_windows? + File.executable?(filename) && File.file?(filename) + end end @@ -133,7 +135,7 @@ def results # The return from results indicates to the caller the file was not parsed correctly. class NothingParser def results - false + nil end end end diff --git a/spec/unit/util/config_spec.rb b/spec/unit/util/config_spec.rb index 925c1e5def..5505aad4a1 100644 --- a/spec/unit/util/config_spec.rb +++ b/spec/unit/util/config_spec.rb @@ -36,6 +36,7 @@ describe "external_facts_dirs" do it "should return the default value for linux" do Facter::Util::Config.stubs(:is_windows?).returns(false) + Facter::Util::Config.stubs(:windows_data_dir).returns(nil) Facter::Util::Config.external_facts_dirs.should == ["/etc/facter/facts.d", "/etc/puppetlabs/facter/facts.d"] end diff --git a/spec/unit/util/parser_spec.rb b/spec/unit/util/parser_spec.rb index c12d74b631..51cacc6b31 100644 --- a/spec/unit/util/parser_spec.rb +++ b/spec/unit/util/parser_spec.rb @@ -94,19 +94,36 @@ end it "should return a hash of whatever is returned by the executable" do + pending("this test does not run on windows") if Facter::Util::Config.is_windows? File.stubs(:file?).with(cmd).returns(true) Facter::Util::Parser.parser_for(cmd).results.should == data end it "should not parse a directory" do File.stubs(:file?).with(cmd).returns(false) - Facter::Util::Parser.parser_for(cmd).results.should == false + Facter::Util::Parser.parser_for(cmd).results.should be_nil + end + + context "on Windows" do + let :cmd do "/tmp/foo.bat" end + + before :each do + Facter::Util::Config.stubs(:is_windows?).returns(true) + end + + let :parser do + Facter::Util::Parser.parser_for(cmd) + end + + it "should return no results" do + parser.results.should be_nil + end end end describe "nothing parser" do it "uses the nothing parser when there is no other parser" do - Facter::Util::Parser.parser_for("this.is.not.valid").results.should == false + Facter::Util::Parser.parser_for("this.is.not.valid").results.should be_nil end end end From 7de57ba3279ee2e54a94fb795dbb1a78e2efa59a Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Mon, 16 Jul 2012 11:13:35 -0700 Subject: [PATCH 0921/3753] (#2157) Fix Mocha::StubbingError in loader_spec Without this patch applied the spec tests for the loader_spec.rb file are setting a stub on a nil object. This is a problem because Mocha 0.11.3 will raise an exception catching this common error. This exception is not raised in Mocha 0.10.4. Mocha 0.11.3 has added a feature to raise stubbing errors when trying to stub methods on nil objects. Paired-with: Hailee Kenney --- spec/unit/util/loader_spec.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spec/unit/util/loader_spec.rb b/spec/unit/util/loader_spec.rb index 8fbc4fcb69..3e2d3bf94f 100755 --- a/spec/unit/util/loader_spec.rb +++ b/spec/unit/util/loader_spec.rb @@ -34,7 +34,7 @@ def load_file(file) end describe "#valid_seach_path?" do - before do + before :each do @loader = Facter::Util::Loader.new @settings = mock 'settings' @settings.stubs(:value).returns "/eh" @@ -270,7 +270,6 @@ def load_file(file) Kernel.expects(:load).with(f) end - @ext_loader.stubs(:load) @loader.load_all @loader.loaded_files.should == %w{/one/dir/bar.rb /one/dir/foo.rb} From b69e21f649f767fa6610381aa0a9916ff6884a24 Mon Sep 17 00:00:00 2001 From: Andrew Elwell Date: Thu, 19 Jul 2012 08:11:13 +0200 Subject: [PATCH 0922/3753] Update version nos to match Facter development Minor documentation fixup to clarify version numbering as the original was Puppet centric rather than Factor. --- CONTRIBUTING.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index dd8e89720e..898d409c3e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -79,8 +79,8 @@ The long version based on the current stable series if it was introduced there, or on `master` if it is not yet present in a stable release. - The current stable series is 2.7.x, and the current maintenance - series is 2.6.x. + The current facter stable series is 2.x, and the current maintenance + series is 1.6.x. 1. Make separate commits for logically separate changes. @@ -108,7 +108,7 @@ The long version code are much more likely to be merged in with a minimum of bike-shedding or requested changes. Ideally, the commit message would include information, and be in a form suitable for - inclusion in the release notes for the version of Puppet that + inclusion in the release notes for the version of Facter that includes them. Please also check that you are not introducing any trailing @@ -270,7 +270,7 @@ review. * Merging topic branches When merging code from a topic branch into the integration branch - (Ex: master, 2.7.x, 1.6.x, etc.), there should always be a merge + (Ex: master, 2.x, 1.6.x, etc.), there should always be a merge commit. You can accomplish this by always providing the `--no-ff` flag to `git merge`. From 739879dc872850fa026b64592a62f390d8e74a91 Mon Sep 17 00:00:00 2001 From: Andrew Elwell Date: Thu, 19 Jul 2012 08:11:13 +0200 Subject: [PATCH 0923/3753] Update version nos to match Facter development Minor documentation fixup to clarify version numbering as the original was Puppet centric rather than Factor. --- CONTRIBUTING.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index dd8e89720e..898d409c3e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -79,8 +79,8 @@ The long version based on the current stable series if it was introduced there, or on `master` if it is not yet present in a stable release. - The current stable series is 2.7.x, and the current maintenance - series is 2.6.x. + The current facter stable series is 2.x, and the current maintenance + series is 1.6.x. 1. Make separate commits for logically separate changes. @@ -108,7 +108,7 @@ The long version code are much more likely to be merged in with a minimum of bike-shedding or requested changes. Ideally, the commit message would include information, and be in a form suitable for - inclusion in the release notes for the version of Puppet that + inclusion in the release notes for the version of Facter that includes them. Please also check that you are not introducing any trailing @@ -270,7 +270,7 @@ review. * Merging topic branches When merging code from a topic branch into the integration branch - (Ex: master, 2.7.x, 1.6.x, etc.), there should always be a merge + (Ex: master, 2.x, 1.6.x, etc.), there should always be a merge commit. You can accomplish this by always providing the `--no-ff` flag to `git merge`. From f7f90e47fbb0baee0669c173bcbd7792e77b9731 Mon Sep 17 00:00:00 2001 From: Andrew Elwell Date: Thu, 19 Jul 2012 08:11:13 +0200 Subject: [PATCH 0924/3753] Update version nos to match Facter development Minor documentation fixup to clarify version numbering as the original was Puppet centric rather than Factor. --- CONTRIBUTING.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index dd8e89720e..898d409c3e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -79,8 +79,8 @@ The long version based on the current stable series if it was introduced there, or on `master` if it is not yet present in a stable release. - The current stable series is 2.7.x, and the current maintenance - series is 2.6.x. + The current facter stable series is 2.x, and the current maintenance + series is 1.6.x. 1. Make separate commits for logically separate changes. @@ -108,7 +108,7 @@ The long version code are much more likely to be merged in with a minimum of bike-shedding or requested changes. Ideally, the commit message would include information, and be in a form suitable for - inclusion in the release notes for the version of Puppet that + inclusion in the release notes for the version of Facter that includes them. Please also check that you are not introducing any trailing @@ -270,7 +270,7 @@ review. * Merging topic branches When merging code from a topic branch into the integration branch - (Ex: master, 2.7.x, 1.6.x, etc.), there should always be a merge + (Ex: master, 2.x, 1.6.x, etc.), there should always be a merge commit. You can accomplish this by always providing the `--no-ff` flag to `git merge`. From 8a49948b3719a10e38040af7057d44f148bea7ce Mon Sep 17 00:00:00 2001 From: Daniel Pittman Date: Thu, 19 Jul 2012 17:12:05 -0700 Subject: [PATCH 0925/3753] Delete the "ARP" facts, which are pathologically bad. The "ARP" facts are pretty awesome, really, except for a tiny handful of little things that could do with improvement. For example, they are not really about ARP, they are about detecting if the current node is an EC2 node or not. So, the name is kind of wrong. They depend on the fact that the EC2 happened to communicate with a particular EC2 metadata service first, before doing anything else, because they just return the first entry in the ARP table attached to an interface. They are also not used anywhere in our core - even the other facts that used to use them stopped, or duplicated their functionality. All in all, they can go: they have so much wrong with them that saving them is kind of crazy. Signed-off-by: Daniel Pittman --- lib/facter/arp.rb | 28 ---------------------------- 1 file changed, 28 deletions(-) delete mode 100644 lib/facter/arp.rb diff --git a/lib/facter/arp.rb b/lib/facter/arp.rb deleted file mode 100644 index 2c8bde5f4b..0000000000 --- a/lib/facter/arp.rb +++ /dev/null @@ -1,28 +0,0 @@ -require 'facter/util/ip' - -Facter.add(:arp) do - confine :kernel => :linux - setcode do - output = Facter::Util::Resolution.exec('arp -an') - if not output.nil? - arp = "" - output.each_line do |s| - if s =~ /^\S+\s\S+\s\S+\s(\S+)\s\S+\s\S+\s\S+$/ - arp = $1.downcase - break # stops on the first match - end - end - end - "fe:ff:ff:ff:ff:ff" == arp ? arp : nil - end -end - -Facter::Util::IP.get_interfaces.each do |interface| - Facter.add("arp_" + Facter::Util::IP.alphafy(interface)) do - confine :kernel => :linux - setcode do - arp = Facter::Util::IP.get_arp_value(interface) - "fe:ff:ff:ff:ff:ff" == arp ? arp : nil - end - end -end From bb44774917df863aae5edae97d333bf2d3e5766b Mon Sep 17 00:00:00 2001 From: Daniel Pittman Date: Thu, 19 Jul 2012 17:14:13 -0700 Subject: [PATCH 0926/3753] Delete Linux-only `get_arp_value` helper. This was only ever implemented for Linux, and only to support the ARP facts that were all about detecting if we are EC2 or not. It also kind of sucks, because by "get ARP value" it means "get the first ARP entry tied to an interface, because picking some random value based on the oldest machine you communicated with that hasn't fallen out of the cache yet." If someone actually comes up with a use for this data (eg: some random ARP value based on previous network behaviour of other tools we run) then I suppose they can reintroduce it - but, honestly, I can't imagine why. Signed-off-by: Daniel Pittman --- lib/facter/util/ip.rb | 7 ------- 1 file changed, 7 deletions(-) diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index c45c416564..d78f7c98e6 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -192,11 +192,4 @@ def self.get_network_value(interface) network = ip.mask(subnet.to_s).to_s end end - - def self.get_arp_value(interface) - arp = Facter::Util::Resolution.exec("arp -en -i #{interface} | sed -e 1d") - if arp =~ /^\S+\s+\w+\s+(\S+)\s+\w\s+\S+$/ - return $1 - end - end end From 4b926d0df3bb02a0492908fd13479e0a148ae19e Mon Sep 17 00:00:00 2001 From: Daniel Pittman Date: Thu, 19 Jul 2012 19:11:46 -0700 Subject: [PATCH 0927/3753] Faster MAC address load on Linux via sysfs When sysfs is available we can work out the MAC address of the primary interface much faster than shelling out to ifconfig delivers. This is about an order of magnitude faster on my machine, being 0.12ms vs 1.5ms when using the external process. Signed-off-by: Daniel Pittman --- lib/facter/macaddress.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/facter/macaddress.rb b/lib/facter/macaddress.rb index 6af30aab31..4abf92c080 100644 --- a/lib/facter/macaddress.rb +++ b/lib/facter/macaddress.rb @@ -9,6 +9,19 @@ require 'facter/util/macaddress' +Facter.add(:macaddress) do + confine :kernel => 'Linux' + has_weight 10 # about an order of magnitude faster + setcode do + begin + Dir.glob('/sys/class/net/*').reject {|x| x[-3..-1] == '/lo' }.first + path and File.read(path + '/address') + rescue Exception + nil + end + end +end + Facter.add(:macaddress) do confine :kernel => %w{SunOS Linux GNU/kFreeBSD} setcode do From 58a685b19fc394c657bce867315ce992fcd55452 Mon Sep 17 00:00:00 2001 From: Ashley Penney Date: Sun, 22 Jul 2012 20:09:30 -0400 Subject: [PATCH 0928/3753] Add XenServer to operatingsystem for #5255. --- lib/facter/operatingsystem.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index ff7d34b92c..cf9e2dd2ba 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -65,6 +65,8 @@ "PSBM" elsif txt =~ /Ascendos/i "Ascendos" + elsif txt =~ /^XenServer/i + "XenServer" else "RedHat" end From 51119c0c76c1c280d765856aaf90f03a858dcb56 Mon Sep 17 00:00:00 2001 From: Christopher Webber Date: Fri, 13 Jul 2012 07:31:34 -0700 Subject: [PATCH 0929/3753] Add support for distingushing which Solaris The operatingsystem fact now returns OpenIndiana, SmartOS or OmniOS if you are on those platforms and not Oracle Solaris. This handles the distro problem on Solaris/SunOS the same way facter deals with Linux distros. --- lib/facter/operatingsystem.rb | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index ff7d34b92c..e77df0bbba 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -5,7 +5,7 @@ # Resolution: # If the kernel is a Linux kernel, check for the existence of a selection of # files in /etc/ to find the specific flavour. -# On SunOS based kernels, return Solaris. +# On SunOS based kernels, attempt to determine the flavour, otherwise return Solaris. # On systems other than Linux, use the kernel value. # # Caveats: @@ -17,7 +17,18 @@ if FileTest.exists?("/etc/debian_version") "Nexenta" else - "Solaris" + txt = File.read("/etc/release") + if txt =~ /OmniOS/ + "OmniOS" + elsif txt =~ /OpenIndiana/ + "OpenIndiana" + elsif txt =~ /SmartOS/ + "SmartOS" + elsif txt =~ /Joyent/ + "SmartOS" + else + "Solaris" + end end end end From 27ac413d4c15d9806e9d7d98cef9e14e80acdbaf Mon Sep 17 00:00:00 2001 From: Christopher Webber Date: Fri, 13 Jul 2012 09:52:45 -0700 Subject: [PATCH 0930/3753] Add new distros to Solaris family Add the new distros added as part of the changes to the operatingsystem fact to the Solaris family. --- lib/facter/osfamily.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/osfamily.rb b/lib/facter/osfamily.rb index 0cc3056fad..8e9f093514 100644 --- a/lib/facter/osfamily.rb +++ b/lib/facter/osfamily.rb @@ -22,7 +22,7 @@ "Debian" when "SLES", "SLED", "OpenSuSE", "SuSE" "Suse" - when "Solaris", "Nexenta" + when "Solaris", "Nexenta", "OmniOS", "OpenIndiana", "SmartOS" "Solaris" else Facter.value("kernel") From b7a7f5074bc6bbea4cec9527177d105d6489e9f1 Mon Sep 17 00:00:00 2001 From: Eric Sorenson Date: Mon, 16 Jul 2012 18:09:57 -0700 Subject: [PATCH 0931/3753] Fixes behaviour change if /etc/release does not exist. Our $operatingsystem default before the variant detection was "Solaris" for SunOS variants, but without the 'FileTest.exists?' guard, the case statement would drop an exception and it'd end up set to "SunOS". --- lib/facter/operatingsystem.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index e77df0bbba..75c42cb5fb 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -16,7 +16,7 @@ setcode do if FileTest.exists?("/etc/debian_version") "Nexenta" - else + elsif FileTest.exists?("/etc/release") txt = File.read("/etc/release") if txt =~ /OmniOS/ "OmniOS" @@ -29,6 +29,8 @@ else "Solaris" end + else + "Solaris" end end end From f3d95713eed5f08d93ad16861b8d30c3c08d6cd9 Mon Sep 17 00:00:00 2001 From: Christopher Webber Date: Wed, 18 Jul 2012 19:26:29 -0700 Subject: [PATCH 0932/3753] Update other facts to use osfamily instead of operatingsystem --- lib/facter/macaddress.rb | 2 +- lib/facter/operatingsystemrelease.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/facter/macaddress.rb b/lib/facter/macaddress.rb index 6af30aab31..ef2857de0d 100644 --- a/lib/facter/macaddress.rb +++ b/lib/facter/macaddress.rb @@ -22,7 +22,7 @@ end Facter.add(:macaddress) do - confine :operatingsystem => "Solaris" + confine :osfamily => "Solaris" setcode do ether = [] output = Facter::Util::Resolution.exec("/usr/bin/netstat -np") diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index a0db2baff0..dd2972576a 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -149,7 +149,7 @@ end Facter.add(:operatingsystemrelease) do - confine :operatingsystem => :solaris + confine :osfamily => :solaris setcode do release = File.open('/etc/release','r') {|f| f.readline.chomp} if match = /\s+s(\d+)[sx]?(_u\d+)?.*(?:SPARC|X86)/.match(release) From b292fdcd1d3c4233f32b448122f430fca7e4c545 Mon Sep 17 00:00:00 2001 From: Andrew Elwell Date: Thu, 19 Jul 2012 08:11:13 +0200 Subject: [PATCH 0933/3753] Update version nos to match Facter development Minor documentation fixup to clarify version numbering as the original was Puppet centric rather than Factor. --- CONTRIBUTING.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index dd8e89720e..898d409c3e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -79,8 +79,8 @@ The long version based on the current stable series if it was introduced there, or on `master` if it is not yet present in a stable release. - The current stable series is 2.7.x, and the current maintenance - series is 2.6.x. + The current facter stable series is 2.x, and the current maintenance + series is 1.6.x. 1. Make separate commits for logically separate changes. @@ -108,7 +108,7 @@ The long version code are much more likely to be merged in with a minimum of bike-shedding or requested changes. Ideally, the commit message would include information, and be in a form suitable for - inclusion in the release notes for the version of Puppet that + inclusion in the release notes for the version of Facter that includes them. Please also check that you are not introducing any trailing @@ -270,7 +270,7 @@ review. * Merging topic branches When merging code from a topic branch into the integration branch - (Ex: master, 2.7.x, 1.6.x, etc.), there should always be a merge + (Ex: master, 2.x, 1.6.x, etc.), there should always be a merge commit. You can accomplish this by always providing the `--no-ff` flag to `git merge`. From 36facb3ac1ca30c7b8e7adb4653c06088811170a Mon Sep 17 00:00:00 2001 From: Ashley Penney Date: Sun, 22 Jul 2012 20:44:23 -0400 Subject: [PATCH 0934/3753] Add OpenIndiana to operatingsystem and osfamily, as well as check /etc/release for Solaris rather than just falling back to it. --- spec/unit/operatingsystem_spec.rb | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/spec/unit/operatingsystem_spec.rb b/spec/unit/operatingsystem_spec.rb index 631dda2314..8f83c55bcf 100755 --- a/spec/unit/operatingsystem_spec.rb +++ b/spec/unit/operatingsystem_spec.rb @@ -29,8 +29,17 @@ Facter.fact(:operatingsystem).value.should == "Nexenta" end - it "should be Solaris for SunOS if no other variants match" do - Facter.fact(:operatingsystem).value.should == "Solaris" + { + "OpenIndiana" => "OpenIndiana Development oi_148 X86", + "Solaris" => "Solaris 10 10/08 s10x_u6wos_07b X86", + }.each_pair do |operatingsystem, string| + it "should be #{operatingsystem} based on /etc/release contents #{string}" do + FileTest.expects(:exists?).with("/etc/debian_version").returns false + FileTest.expects(:exists?).with("/etc/release").returns true + File.expects(:read).with("/etc/release").returns string + + Facter.fact(:operatingsystem).value.should == operatingsystem + end end end From fa60a8754d09178368f180562da1f0fc06d39694 Mon Sep 17 00:00:00 2001 From: Ashley Penney Date: Tue, 24 Jul 2012 16:21:09 -0400 Subject: [PATCH 0935/3753] Modify this to handle Solaris a little better, via the use of uname -v. --- lib/facter/operatingsystem.rb | 26 +++++++++++--------------- spec/unit/operatingsystem_spec.rb | 21 ++++++++++++--------- 2 files changed, 23 insertions(+), 24 deletions(-) diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index 75c42cb5fb..85c0de5162 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -14,23 +14,19 @@ Facter.add(:operatingsystem) do confine :kernel => :sunos setcode do - if FileTest.exists?("/etc/debian_version") + # Use uname -v because /etc/release can change in zones under SmartOS. + # It's apparently not trustworthy enough to rely on for this fact. + output = Facter::Util::Resolution.exec('uname -v') + if output =~ /^joyent_/ + "SmartOS" + elsif output =~ /^oi_/ + "OpenIndiana" + elsif output =~ /^omnios-/ + "OmniOS" + elsif FileTest.exists?("/etc/debian_version") "Nexenta" - elsif FileTest.exists?("/etc/release") - txt = File.read("/etc/release") - if txt =~ /OmniOS/ - "OmniOS" - elsif txt =~ /OpenIndiana/ - "OpenIndiana" - elsif txt =~ /SmartOS/ - "SmartOS" - elsif txt =~ /Joyent/ - "SmartOS" - else - "Solaris" - end else - "Solaris" + "Solaris" end end end diff --git a/spec/unit/operatingsystem_spec.rb b/spec/unit/operatingsystem_spec.rb index 8f83c55bcf..311bb1db02 100755 --- a/spec/unit/operatingsystem_spec.rb +++ b/spec/unit/operatingsystem_spec.rb @@ -29,16 +29,19 @@ Facter.fact(:operatingsystem).value.should == "Nexenta" end - { - "OpenIndiana" => "OpenIndiana Development oi_148 X86", - "Solaris" => "Solaris 10 10/08 s10x_u6wos_07b X86", - }.each_pair do |operatingsystem, string| - it "should be #{operatingsystem} based on /etc/release contents #{string}" do - FileTest.expects(:exists?).with("/etc/debian_version").returns false - FileTest.expects(:exists?).with("/etc/release").returns true - File.expects(:read).with("/etc/release").returns string + it "should be Solaris if /etc/debian_version is missing and uname -v failed to match" do + FileTest.expects(:exists?).with("/etc/debian_version").returns false + Facter.fact(:operatingsystem).value.should == "Solaris" + end - Facter.fact(:operatingsystem).value.should == operatingsystem + { + "SmartOS" => "joyent_20120629T002039Z", + "OmniOS" => "omnios-dda4bb3", + "OpenIndiana" => "oi_151a", + }.each_pair do |distribution, string| + it "should be #{distribution} if uname -v is '#{string}'" do + Facter::Util::Resolution.stubs(:exec).with('uname -v').returns(string) + Facter.fact(:operatingsystem).value.should == distribution end end end From 10aa3aae4043a042ce42b480ce52cf992cc3ad6a Mon Sep 17 00:00:00 2001 From: Dominic Cleal Date: Fri, 29 Jun 2012 18:31:44 +0100 Subject: [PATCH 0936/3753] (#15049) Return only one selinuxfs path as string from mounts The block that parses /proc/self/mountinfo to find a selinuxfs filesystem would return results as an array. On Ruby 1.8, interpolating this into a string for File.exists? when one result was returned worked, while on Ruby 1.9 it interpolated as ["/sys/fs/selinux"]/enforce so later failed. This changes the block to return the single result string rather than an array. This also fixes #11531 where multiple selinuxfs filesystems could be mounted, as it returns only the first mountpoint. The /proc file was changed from /proc/self/mountinfo to /proc/self/mounts for compatibility with Linux 2.6.25 and older. --- lib/facter/selinux.rb | 11 ++++---- spec/unit/selinux_spec.rb | 59 +++++++++++++++++++++++++++++++++------ 2 files changed, 56 insertions(+), 14 deletions(-) diff --git a/lib/facter/selinux.rb b/lib/facter/selinux.rb index 36f4b2fdcc..194dd82684 100644 --- a/lib/facter/selinux.rb +++ b/lib/facter/selinux.rb @@ -15,15 +15,16 @@ # This supports the fact that the selinux mount point is not always in the # same location -- the selinux mount point is operating system specific. def selinux_mount_point - if FileTest.exists?('/proc/self/mountinfo') - File.open('/proc/self/mountinfo') do |f| + path = "/selinux" + if FileTest.exists?('/proc/self/mounts') + File.open('/proc/self/mounts') do |f| f.grep(/selinuxfs/) do |line| - line.split[4] + path = line.split[1] + break end end - else - "/selinux" end + path end Facter.add("selinux") do diff --git a/spec/unit/selinux_spec.rb b/spec/unit/selinux_spec.rb index 63313a70f3..c47822b413 100755 --- a/spec/unit/selinux_spec.rb +++ b/spec/unit/selinux_spec.rb @@ -9,17 +9,58 @@ Facter.clear end - it "should return true if SELinux enabled" do - Facter.fact(:kernel).stubs(:value).returns("Linux") + describe "should detect if SELinux is enabled" do + it "and return true with default /selinux" do + Facter.fact(:kernel).stubs(:value).returns("Linux") - FileTest.stubs(:exists?).returns false - File.stubs(:read).with("/proc/self/attr/current").returns("notkernel") + FileTest.stubs(:exists?).returns false + File.stubs(:read).with("/proc/self/attr/current").returns("notkernel") - FileTest.expects(:exists?).with("/selinux/enforce").returns true - FileTest.expects(:exists?).with("/proc/self/attr/current").returns true - File.expects(:read).with("/proc/self/attr/current").returns("kernel") + FileTest.expects(:exists?).with("/selinux/enforce").returns true + FileTest.expects(:exists?).with("/proc/self/attr/current").returns true + File.expects(:read).with("/proc/self/attr/current").returns("kernel") + + Facter.fact(:selinux).value.should == "true" + end + + it "and return true with selinuxfs path from /proc" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + + mounts = mock() + lines = [ "selinuxfs /sys/fs/selinux selinuxfs rw,relatime 0 0" ] + mounts.expects(:grep).multiple_yields(*lines) + + FileTest.expects(:exists?).with("/proc/self/mounts").returns true + File.expects(:open).with("/proc/self/mounts").yields(mounts) + + FileTest.expects(:exists?).with("/sys/fs/selinux/enforce").returns true + + FileTest.expects(:exists?).with("/proc/self/attr/current").returns true + File.expects(:read).with("/proc/self/attr/current").returns("kernel") + + Facter.fact(:selinux).value.should == "true" + end + + it "and return true with multiple selinuxfs mounts from /proc" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + + mounts = mock() + lines = [ + "selinuxfs /sys/fs/selinux selinuxfs rw,relatime 0 0", + "selinuxfs /var/tmp/imgcreate-R2wmE6/install_root/sys/fs/selinux selinuxfs rw,relatime 0 0", + ] + mounts.expects(:grep).multiple_yields(*lines) + + FileTest.expects(:exists?).with("/proc/self/mounts").returns true + File.expects(:open).with("/proc/self/mounts").yields(mounts) + + FileTest.expects(:exists?).with("/sys/fs/selinux/enforce").returns true + + FileTest.expects(:exists?).with("/proc/self/attr/current").returns true + File.expects(:read).with("/proc/self/attr/current").returns("kernel") - Facter.fact(:selinux).value.should == "true" + Facter.fact(:selinux).value.should == "true" + end end it "should return true if SELinux policy enabled" do @@ -36,7 +77,7 @@ it "should return an SELinux policy version" do Facter.fact(:selinux).stubs(:value).returns("true") - FileTest.stubs(:exists?).with("/proc/self/mountinfo").returns false + FileTest.stubs(:exists?).with("/proc/self/mounts").returns false File.stubs(:read).with("/selinux/policyvers").returns("") From d8238fa619eca017436923169ef19229730d34f6 Mon Sep 17 00:00:00 2001 From: Ashley Penney Date: Tue, 24 Jul 2012 18:11:26 -0400 Subject: [PATCH 0937/3753] Further tweaks for XenServer. --- lib/facter/operatingsystemrelease.rb | 2 +- lib/facter/osfamily.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index a0db2baff0..c36a72a01e 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -21,7 +21,7 @@ confine :operatingsystem => %w{CentOS Fedora oel ovs OracleLinux RedHat MeeGo Scientific SLC Ascendos CloudLinux PSBM} setcode do case Facter.value(:operatingsystem) - when "CentOS", "RedHat", "Scientific", "SLC", "Ascendos", "CloudLinux", "PSBM" + when "CentOS", "RedHat", "Scientific", "SLC", "Ascendos", "CloudLinux", "PSBM", "XenServer" releasefile = "/etc/redhat-release" when "Fedora" releasefile = "/etc/fedora-release" diff --git a/lib/facter/osfamily.rb b/lib/facter/osfamily.rb index 0cc3056fad..345ef7b569 100644 --- a/lib/facter/osfamily.rb +++ b/lib/facter/osfamily.rb @@ -16,7 +16,7 @@ setcode do case Facter.value(:operatingsystem) - when "RedHat", "Fedora", "CentOS", "Scientific", "SLC", "Ascendos", "CloudLinux", "PSBM", "OracleLinux", "OVS", "OEL" + when "RedHat", "Fedora", "CentOS", "Scientific", "SLC", "Ascendos", "CloudLinux", "PSBM", "OracleLinux", "OVS", "OEL", "XenServer" "RedHat" when "Ubuntu", "Debian" "Debian" From 42d6dea6ea52fc6ef3d349bf95b1f11e354bd3e7 Mon Sep 17 00:00:00 2001 From: Ashley Penney Date: Tue, 24 Jul 2012 19:53:18 -0400 Subject: [PATCH 0938/3753] In one of the more recent versions of rspec they changed calls from expect{} to be .to and .to_not instead of should/should_not. This fixes the three failure tests I see every time I run the tests. This was driving me crazy. --- spec/unit/util/directory_loader_spec.rb | 4 ++-- spec/unit/util/macosx_spec.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/unit/util/directory_loader_spec.rb b/spec/unit/util/directory_loader_spec.rb index 71295501cb..79ad2b2cd5 100644 --- a/spec/unit/util/directory_loader_spec.rb +++ b/spec/unit/util/directory_loader_spec.rb @@ -22,14 +22,14 @@ it "raises an error when the directory does not exist" do missing_dir = "missing" File.stubs(:directory?).with(missing_dir).returns(false) - expect { Facter::Util::DirectoryLoader.loader_for(missing_dir) }.should raise_error Facter::Util::DirectoryLoader::NoSuchDirectoryError + expect { Facter::Util::DirectoryLoader.loader_for(missing_dir) }.to raise_error Facter::Util::DirectoryLoader::NoSuchDirectoryError end it "should do nothing bad when dir doesn't exist" do fakepath = "/foobar/path" my_loader = Facter::Util::DirectoryLoader.new(fakepath) FileTest.exists?(my_loader.directory).should be_false - expect { my_loader.load(collection) }.should_not raise_error + expect { my_loader.load(collection) }.to_not raise_error end describe "when loading facts from disk" do diff --git a/spec/unit/util/macosx_spec.rb b/spec/unit/util/macosx_spec.rb index d05044ff9f..de6ceb3ffb 100755 --- a/spec/unit/util/macosx_spec.rb +++ b/spec/unit/util/macosx_spec.rb @@ -42,7 +42,7 @@ end it 'should fail when trying to read invalid XML' do - expect { Facter::Util::Macosx.intern_xml('xml<--->') }.should \ + expect { Facter::Util::Macosx.intern_xml('xml<--->') }.to \ raise_error(RuntimeError, /A plist file could not be properly read by Facter::Util::CFPropertyList/) end From 841b99a5c62a44d2d6b75a9ae00bc120ccec42c1 Mon Sep 17 00:00:00 2001 From: Matthaus Litteken Date: Tue, 24 Jul 2012 17:33:53 -0700 Subject: [PATCH 0939/3753] (#15687) Selinux: Test for policyvers before reading it Previously facter would read /#{selinux_mount_point}/policyvers without first verifying it existed, which would spew stderr to the console if it did not exist. This commit makes the default value for the fact "unknown" and only uses a different value if policyvers exists. This also includes an updated test which fails using the previous fact definition. --- lib/facter/selinux.rb | 6 +++++- spec/unit/selinux_spec.rb | 11 +++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/lib/facter/selinux.rb b/lib/facter/selinux.rb index 194dd82684..8a21c0fd8e 100644 --- a/lib/facter/selinux.rb +++ b/lib/facter/selinux.rb @@ -57,7 +57,11 @@ def selinux_mount_point Facter.add("selinux_policyversion") do confine :selinux => :true setcode do - File.read("#{selinux_mount_point}/policyvers") + result = 'unknown' + if FileTest.exists?("#{selinux_mount_point}/policyvers") + result = File.read("#{selinux_mount_point}/policyvers").chomp + end + result end end diff --git a/spec/unit/selinux_spec.rb b/spec/unit/selinux_spec.rb index c47822b413..93599bc715 100755 --- a/spec/unit/selinux_spec.rb +++ b/spec/unit/selinux_spec.rb @@ -79,13 +79,20 @@ Facter.fact(:selinux).stubs(:value).returns("true") FileTest.stubs(:exists?).with("/proc/self/mounts").returns false - File.stubs(:read).with("/selinux/policyvers").returns("") - File.expects(:read).with("/selinux/policyvers").returns("1") + FileTest.expects(:exists?).with("/selinux/policyvers").returns true Facter.fact(:selinux_policyversion).value.should == "1" end + it "it should return 'unknown' SELinux policy version if /selinux/policyvers doesn't exist" do + Facter.fact(:selinux).stubs(:value).returns("true") + FileTest.expects(:exists?).with("/proc/self/mounts").returns false + FileTest.expects(:exists?).with("/selinux/policyvers").returns false + + Facter.fact(:selinux_policyversion).value.should == "unknown" + end + it "should return the SELinux current mode" do Facter.fact(:selinux).stubs(:value).returns("true") From 4842e0af650b2b92309d45766f585c9402206409 Mon Sep 17 00:00:00 2001 From: Ashley Penney Date: Sat, 28 Jul 2012 02:28:31 +0200 Subject: [PATCH 0940/3753] Add XenServer test. --- spec/unit/operatingsystem_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/unit/operatingsystem_spec.rb b/spec/unit/operatingsystem_spec.rb index 631dda2314..cca7c15dfe 100755 --- a/spec/unit/operatingsystem_spec.rb +++ b/spec/unit/operatingsystem_spec.rb @@ -87,6 +87,7 @@ "SLC" => "Scientific Linux CERN SLC release 5.7 (Boron)", "Ascendos" => "Ascendos release 6.0 (Nameless)", "CloudLinux" => "CloudLinux Server release 5.5", + "XenServer" => "XenServer release 5.6.0-31188p (xenenterprise)", }.each_pair do |operatingsystem, string| it "should be #{operatingsystem} based on /etc/redhat-release contents #{string}" do FileTest.expects(:exists?).with("/etc/redhat-release").returns true From 5c3d63581baefaef2f3ebfcd4e9c878c10755c92 Mon Sep 17 00:00:00 2001 From: Hailee Kenney Date: Mon, 30 Jul 2012 13:45:46 -0700 Subject: [PATCH 0941/3753] (#15514) Add compatibility with change to operatingsystem fact Since the operatingsystem fact can now return several different version of Solaris, anything that relies on operatingsystem returning Solaris must now depend on osfamily returning Solaris. Most of these cases have been taken care of, so far this is the only one I have found that was missed, but it's possible there are a few more. --- spec/unit/operatingsystemrelease_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/unit/operatingsystemrelease_spec.rb b/spec/unit/operatingsystemrelease_spec.rb index 318875c494..3d7343821d 100755 --- a/spec/unit/operatingsystemrelease_spec.rb +++ b/spec/unit/operatingsystemrelease_spec.rb @@ -61,7 +61,7 @@ before :each do Facter.fact(:kernel).stubs(:value).returns('SunOS') - Facter.fact(:operatingsystem).stubs(:value).returns('Solaris') + Facter.fact(:osfamily).stubs(:value).returns('Solaris') end { From e9e084fa5a0e263981acc99a3c9c64a894d6ce73 Mon Sep 17 00:00:00 2001 From: Andrew Parker Date: Tue, 31 Jul 2012 10:35:43 -0700 Subject: [PATCH 0942/3753] (Maint) Update CONTRIBUTING.md to match Puppet The previous CONTRIBUTING.md was misleading and didn't match what we were looking for in contributions. In addition it pointed to several tools that are not used anymore. --- CONTRIBUTING.md | 282 +++++++++++++++++++----------------------------- 1 file changed, 110 insertions(+), 172 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 898d409c3e..926701606a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,88 +1,84 @@ -Checklist (and a short version for the impatient) +Checklist/Outline (The short version) ================================================= - * Commits: - - - Make commits of logical units. - - - Check for unnecessary whitespace with "git diff --check" before - committing. - - - Commit using Unix line endings (check the settings around "crlf" in - git-config(1)). - - - Do not check in commented out code or unneeded files. - - - The first line of the commit message should be a short - description (50 characters is the soft limit, excluding ticket - number(s)), and should skip the full stop. - - - If there is an associated Redmine ticket then the first line - should include the ticket number in the form "(#XXXX) Rest of - message". - - - The body should provide a meaningful commit message, which: - - - uses the imperative, present tense: "change", not "changed" or - "changes". - - - includes motivation for the change, and contrasts its - implementation with the previous behavior. - - - Make sure that you have tests for the bug you are fixing, or - feature you are adding. - - - Make sure the test suite passes after your commit (rake spec unit). - - * Submission: - - * Pre-requisites: - - - Make sure you have a [Redmine account](http://projects.puppetlabs.com) - - - Sign the [Contributor License Agreement](https://projects.puppetlabs.com/contributor_licenses/sign) - - * Preferred method: - - - Fork the repository on GitHub. - - - Push your changes to a topic branch in your fork of the - repository. - - - Submit a pull request to the repository in the puppetlabs - organization. - - * Alternate methods: - - - Mail patches to puppet-dev mailing list using `rake mail_patches`, - or `git-format-patch(1)` & `git-send-email(1)`. - - - Attach patches to Redmine ticket. - + * Getting Started: + - Make sure you have a [Redmine account](http://projects.puppetlabs.com) + - Submit a ticket for your issue, assuming one does not already exist. + - Decide what to base your work off of + * `1.6.x`: bug fixes only + * `2.x`: new features that are not breaking changes + * `master`: new features that are breaking changes + + * Making Changes: + - Make sure you have a [GitHub account](https://github.com/signup/free) + - Fork the repository on GitHub + - Make commits of logical units. + - Check for unnecessary whitespace with "git diff --check" before committing. + - Make sure your commit messages are in the proper format + - Make sure you have added the necessary tests for your changes + - Run _all_ the tests to assure nothing else was accidentally broken + + * Submitting Changes: + - Sign the [Contributor License Agreement](https://projects.puppetlabs.com/contributor_licenses/sign) + - Push your changes to a topic branch in your fork of the repository. + - Submit a pull request to the repository in the puppetlabs organization. + - Update your Redmine ticket + The long version ================ - 0. Decide what to base your work on. - - In general, you should always base your work on the oldest - branch that your change is relevant to. - - - A bug fix should be based on the current stable series. If the - bug is not present in the current stable release, then base it on - `master`. - - - A new feature should be based on `master`. - - - Security fixes should be based on the current maintenance series - (that is, the previous stable series). If the security issue - was not present in the maintenance series, then it should be - based on the current stable series if it was introduced there, - or on `master` if it is not yet present in a stable release. - - The current facter stable series is 2.x, and the current maintenance - series is 1.6.x. - - 1. Make separate commits for logically separate changes. + 0. Create a Redmine ticket for the change you'd like to make. + + It's very important that there be a Redmine ticket for the change + you are making. Considering the number of contributions which are + submitted, it is crucial that we know we can find the ticket on Redmine. + + Before making a ticket however, be sure that one does not already exist. + You can do this by searching Redmine or by trying a Google search which + includes `sites:projects.puppetlabs.com` in addition to some of the keywords + related to your issue. + + If you do not find a ticket that that accurately describes the work + you're going to be doing, go ahead and create one. But be sure to + look for related tickets and add them to the 'related tickets' section. + + 1. Decide what to base your work on. + + In general, you should always base your work on the oldest + branch that your change is relevant to, and it will be + eventually merged up. Currently, branches will be merged up as + follows: + 1.6.x => 2.x => master + + Currently, this is how you should decide where to target your changes: + + A bug fix should be based off the the earliest place where it is + relevant. If it first appears in `1.6.x`, then it should be + targeted here and eventually merged up to `2.x` and master. + + New features which are _backwards compatible_ should be targeted + at the next release, which currently is `2.x`. + + New features that are _breaking changes_ should be targeted at + `master`. + + Part of deciding what to what your work should be based off of includes naming + your topic branch to reflect this. Your branch name should have the following + format: + `ticket/target_branch/ticket_number_short_description_of_issuee` + + For example, if you are fixing a bug relating to a hostname problem on aix, + which has Redmine ticket number 12345, then your branch should be named: + `ticket/2.x/12345_fix_hostname_on_aix` + + There is a good chance that if you submit a pull request _from_ master _to_ master, + Puppet Labs developers will suspect that you're not sure about the process. This is + why clear naming of branches and basing your work off the right place will be + extremely helpful in ensuring that your submission is reviewed and merged. Often times + if your change is targeted at the wrong place, we will bounce it back to you and wait + to review it until it has been retargeted. + + 2. Make separate commits for logically separate changes. Please break your commits down into logically consistent units which include new or changed tests relevent to the rest of the @@ -94,7 +90,7 @@ The long version If you're going to refactor a piece of code, please do so as a separate commit from your feature or bug fix changes. - We also really appreciate changes that include tests to make + It's crucial that your changes include tests to make sure the bug isn't re-introduced, and that the feature isn't accidentally broken. @@ -115,7 +111,11 @@ The long version whitespaces or other "whitespace errors". You can do this by running "git diff --check" on your changes before you commit. - 2. Sign the Contributor License Agreement + When writing commit messages, please be sure they meet + [these standards](https://github.com/erlang/otp/wiki/Writing-good-commit-messages), and please include the ticket number in your + short summary. It should look something like this: `(#12345) Fix this issue in Facter` + + 3. Sign the Contributor License Agreement Before we can accept your changes, we do need a signed Puppet Labs Contributor License Agreement (CLA). @@ -131,106 +131,46 @@ The long version If you have any questions about the CLA, please feel free to contact Puppet Labs via email at cla-submissions@puppetlabs.com. - 3. Sending your patches - - We accept multiple ways of submitting your changes for - inclusion. They are listed below in order of preference. - - Please keep in mind that any method that involves sending email - to the mailing list directly requires you to be subscribed to - the mailing list, and that your first post to the list will be - held in a moderation queue. - - * GitHub Pull Requests - - To submit your changes via a GitHub pull request, we _highly_ - recommend that you have them on a topic branch, instead of - directly on "master" or one of the release, or RC branches. - It makes things much easier to keep track of, especially if - you decide to work on another thing before your first change - is merged in. + 4. Sending your patches - GitHub has some pretty good - [general documentation](http://help.github.com/) on using - their site. They also have documentation on - [creating pull requests](http://help.github.com/send-pull-requests/). + To submit your changes via a GitHub pull request, you must + have them on a topic branch, instead of directly on "master" + or one of the release, or RC branches. It makes things much easier + to keep track of, especially if you decide to work on another thing + before your first change is merged in. - In general, after pushing your topic branch up to your - repository on GitHub, you'll switch to the branch in the - GitHub UI and click "Pull Request" towards the top of the page - in order to open a pull request. + GitHub has some pretty good + [general documentation](http://help.github.com/) on using + their site. They also have documentation on + [creating pull requests](http://help.github.com/send-pull-requests/). - You'll want to make sure that you have the appropriate - destination branch in the repository under the puppetlabs - organization. This should be the same branch that you based - your changes off of. + In general, after pushing your topic branch up to your + repository on GitHub, you'll switch to the branch in the + GitHub UI and click "Pull Request" towards the top of the page + in order to open a pull request. - * Other pull requests + You'll want to make sure that you have the appropriate + destination branch in the repository under the puppetlabs + organization. This should be the same branch that you based + your changes off of. - If you already have a publicly accessible version of the - repository hosted elsewhere, and don't wish to or cannot use - GitHub, you can submit your change by requesting that we pull - the changes from your repository by sending an email to the - puppet-dev Google Groups mailing list. + 5. Update the related Redmine ticket. - `git-request-pull(1)` provides a handy way to generate the text - for the email requesting that we pull your changes (and does - some helpful sanity checks in the process). - - * Mailing patches to the mailing list - - If neither of the previous methods works for you, then you can - also mail the patches inline to the puppet-dev Google Group - using either `rake mail_patches`, or by using - `git-format-patch(1)`, and `git-send-email(1)` directly. - - `rake mail_patches` handles setting the appropriate flags to - `git-format-patch(1)` and `git-send-email(1)` for you, but - doesn't allow adding any commentary between the '---', and the - diffstat in the resulting email. It also requires that you - have created your topic branch in the form - `//`. - - If you decide to use `git-format-patch(1)` and - `git-send-email(1)` directly, please be sure to use the - following flags for `git-format-patch(1)`: -C -M -s -n - --subject-prefix='PATCH/puppet' - - * Attaching patches to Redmine - - As a method of last resort you can also directly attach the - output of `git-format-patch(1)`, or `git-diff(1)` to a Redmine - ticket. - - If you are generating the diff outside of Git, please be sure - to generate a unified diff. - - 4. Update the related Redmine ticket. - - If there's a Redmine ticket associated with the change you - submitted, then you should update the ticket to include the - location of your branch, and change the status to "In Topic - Branch Pending Merge", along with any other commentary you may - wish to make. + You should update the Redmine ticket associated + with the change you submitted to include the location of your branch + on the `branch` field of the ticket, and change the status to + "In Topic Branch Pending Review", along with any other commentary + you may wish to make. How to track the status of your change after it's been submitted ================================================================ -Shortly after opening a pull request on GitHub, there should be an -automatic message sent to the puppet-dev Google Groups mailing list -notifying people of this. This notification is used to let the Puppet +Shortly after opening a pull request, there should be an automatic +email sent via GitHub. This notification is used to let the Puppet development community know about your requested change to give them a chance to review, test, and comment on the change(s). -If you submitted your change via manually sending a pull request or -mailing the patches, then we keep track of these using -[patchwork](https://patchwork.puppetlabs.com). When code is merged -into the project it is automatically removed from patchwork, and the -Redmine ticket is manually updated with the commit SHA1. In addition, -the ticket status must be updated by the person who merges the topic -branch to a status of "Merged - Pending Release" - -We do our best to comment on or merge submitted changes within a week. +We do our best to comment on or merge submitted changes within a about week. However, if there hasn't been any commentary on the pull request or mailed patches, and it hasn't been merged in after a week, then feel free to ask for an update by replying on the mailing list to the @@ -246,8 +186,6 @@ Additional Resources * [Bug tracker (Redmine)](http://projects.puppetlabs.com) -* [Patchwork](https://patchwork.puppetlabs.com) - * [Contributor License Agreement](https://projects.puppetlabs.com/contributor_licenses/sign) * [General GitHub documentation](http://help.github.com/) @@ -270,7 +208,7 @@ review. * Merging topic branches When merging code from a topic branch into the integration branch - (Ex: master, 2.x, 1.6.x, etc.), there should always be a merge + (Ex: master, 2.7.x, 1.6.x, etc.), there should always be a merge commit. You can accomplish this by always providing the `--no-ff` flag to `git merge`. From c12d3b6c557df695a7b2b009da099f6a93c7bd31 Mon Sep 17 00:00:00 2001 From: Hunter Haugen Date: Wed, 1 Aug 2012 14:49:09 -0700 Subject: [PATCH 0943/3753] Add Amazon to the RedHat osfamily --- lib/facter/osfamily.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/osfamily.rb b/lib/facter/osfamily.rb index 8e9f093514..52f0da6f90 100644 --- a/lib/facter/osfamily.rb +++ b/lib/facter/osfamily.rb @@ -16,7 +16,7 @@ setcode do case Facter.value(:operatingsystem) - when "RedHat", "Fedora", "CentOS", "Scientific", "SLC", "Ascendos", "CloudLinux", "PSBM", "OracleLinux", "OVS", "OEL" + when "RedHat", "Fedora", "CentOS", "Scientific", "SLC", "Ascendos", "CloudLinux", "PSBM", "OracleLinux", "OVS", "OEL", "Amazon" "RedHat" when "Ubuntu", "Debian" "Debian" From 8faa129c3cbec4ea3635b9ee18c6a27691680434 Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Wed, 1 Aug 2012 17:55:55 -0700 Subject: [PATCH 0944/3753] Update CHANGELOG, lib/facter.rb, conf/redhat/facter.spec for 1.6.11rc1 --- CHANGELOG | 8 ++++++++ conf/redhat/facter.spec | 21 ++++++++++++--------- lib/facter.rb | 2 +- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 06025273fd..0d6f4e02d0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,11 @@ +1.6.11rc1 +=== +e9e084f (Maint) Update CONTRIBUTING.md to match Puppet +841b99a (#15687) Selinux: Test for policyvers before reading it +10aa3aa (#15049) Return only one selinuxfs path as string from mounts +f7f90e4 Update version nos to match Facter development +ab87a2c Modify facter spec to work with Ruby 1.9 + 1.6.10 === 35067dc Bump Facter epoch to 1 diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index c00777b31a..4b8d6f20a0 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -2,17 +2,17 @@ Summary: Ruby module for collecting simple facts about a host operating system Name: facter -Version: 1.6.10 -Release: 2%{?dist} -#Release: 0.1rc1.2%{?dist} +Version: 1.6.11 +#Release: 2%{?dist} +Release: 0.1rc1%{?dist} Epoch: 1 License: Apache 2.0 Group: System Environment/Base URL: http://www.puppetlabs.com/puppet/related-projects/%{name} -#Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc1.tar.gz -Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}.tar.gz -#Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc1.tar.gz.asc -Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}.tar.gz.asc +Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc1.tar.gz +#Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}.tar.gz +Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc1.tar.gz.asc +#Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}.tar.gz.asc BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) @@ -32,8 +32,8 @@ system. Some of the facts are preconfigured, such as the hostname and the operating system. Additional facts can be added through simple Ruby scripts %prep -%setup -q -n %{name}-%{version} -#%setup -q -n %{name}-%{version}rc1 +#%setup -q -n %{name}-%{version} +%setup -q -n %{name}-%{version}rc1 %build @@ -55,6 +55,9 @@ rm -rf %{buildroot} %changelog +* Wed Aug 01 2012 Moses Mendoza - 1.6.11-0.1rc1 +- Update for 1.6.11rc1 + * Sat Jul 07 2012 Michael Stahnke - 1.6.10-2 - Attempt to build fro Ruby 1.9.3 diff --git a/lib/facter.rb b/lib/facter.rb index 91b3d75d89..7bffe04da1 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -25,7 +25,7 @@ module Util; end include Comparable include Enumerable - FACTERVERSION = '1.6.10' + FACTERVERSION = '1.6.11' # = Facter # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. From f75e46ec3732946d14d603663e7966f2380691eb Mon Sep 17 00:00:00 2001 From: Matthaus Litteken Date: Thu, 2 Aug 2012 13:43:01 -0700 Subject: [PATCH 0945/3753] Add build-requires of ruby-rdoc for manpage generation Without a BuildRequires of ruby-rdoc, if we remove the --no-rdoc flag from install.rb, the manpages will still not be generated, which will cause the build to fail, because the %files section lists manpages. Adding a BuildRequires of ruby-rdoc and removing the --no-rdoc flag from install.rb will allow manpage generation for packaging. --- conf/redhat/facter.spec | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index 4b8d6f20a0..0f9a45bacc 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -25,6 +25,7 @@ Requires: pciutils %endif Requires: ruby(abi) >= 1.8 BuildRequires: ruby >= 1.8.5 +BuildRequires: ruby-rdoc %description Ruby module for collecting simple facts about a host Operating @@ -39,7 +40,7 @@ operating system. Additional facts can be added through simple Ruby scripts %install rm -rf %{buildroot} -ruby install.rb --destdir=%{buildroot} --quick --no-rdoc +ruby install.rb --destdir=%{buildroot} --quick %clean rm -rf %{buildroot} @@ -50,7 +51,7 @@ rm -rf %{buildroot} %{_bindir}/facter %{ruby_sitelibdir}/facter.rb %{ruby_sitelibdir}/facter -%{_mandir}/man8/* +%{_mandir}/man8/facter.8.gz %doc CHANGELOG INSTALL LICENSE README.md From d5d232882c12c022eebd3ee18c10927d05e44de0 Mon Sep 17 00:00:00 2001 From: Andrew Abrahamowicz Date: Sat, 4 Aug 2012 18:51:07 -0700 Subject: [PATCH 0946/3753] (#11640) Added support for new OpenStack MAC addresses OpenStack changed the MAC address prefix they use to address an issues with libvirt OpenStack bug: https://bugs.launchpad.net/nova/+bug/921838 --- lib/facter/util/ec2.rb | 7 ++++--- spec/unit/util/ec2_spec.rb | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/lib/facter/util/ec2.rb b/lib/facter/util/ec2.rb index 376c9ca47c..197a5c9dd9 100644 --- a/lib/facter/util/ec2.rb +++ b/lib/facter/util/ec2.rb @@ -26,10 +26,11 @@ def has_euca_mac? !!(Facter.value(:macaddress) =~ %r{^[dD]0:0[dD]:}) end - # Test if this host has a mac address used by OpenStack, which - # normally starts with 02:16:3E + # Test if this host has a mac address used by OpenStack, which + # normally starts with FA:16:3E (older versions of OpenStack + # may generate mac addresses starting with 02:16:3E) def has_openstack_mac? - !!(Facter.value(:macaddress) =~ %r{^02:16:3[eE]}) + !!(Facter.value(:macaddress) =~ %r{^(02|[fF][aA]):16:3[eE]}) end # Test if the host has an arp entry in its cache that matches the EC2 arp, diff --git a/spec/unit/util/ec2_spec.rb b/spec/unit/util/ec2_spec.rb index f1fbe3c073..7662411449 100755 --- a/spec/unit/util/ec2_spec.rb +++ b/spec/unit/util/ec2_spec.rb @@ -83,6 +83,22 @@ Facter::Util::EC2.has_openstack_mac?.should == true end + it "should return true when the mac is a newer openstack mac" do + # https://github.com/openstack/nova/commit/b684d651f540fc512ced58acd5ae2ef4d55a885c#nova/utils.py + Facter.expects(:value).with(:macaddress).\ + at_least_once.returns("fa:16:3e:54:89:fd") + + Facter::Util::EC2.has_openstack_mac?.should == true + end + + it "should return true when the mac is a newer openstack mac and returned in upper case" do + # https://github.com/openstack/nova/commit/b684d651f540fc512ced58acd5ae2ef4d55a885c#nova/utils.py + Facter.expects(:value).with(:macaddress).\ + at_least_once.returns("FA:16:3E:54:89:FD") + + Facter::Util::EC2.has_openstack_mac?.should == true + end + it "should return false when the mac is not a openstack one" do Facter.expects(:value).with(:macaddress).\ at_least_once.returns("0c:1d:a0:bc:aa:02") From 981103acb150d24de7862ebc4bb5e8f811e45ecc Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Wed, 8 Aug 2012 14:37:54 -0700 Subject: [PATCH 0947/3753] Update CHANGELOG, conf/redhat/facter.spec for 1.6.11 --- CHANGELOG | 3 ++- conf/redhat/facter.spec | 19 +++++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 0d6f4e02d0..334eb49cea 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,6 @@ -1.6.11rc1 +1.6.11 === +f75e46e Add build-requires of ruby-rdoc for manpage generation e9e084f (Maint) Update CONTRIBUTING.md to match Puppet 841b99a (#15687) Selinux: Test for policyvers before reading it 10aa3aa (#15049) Return only one selinuxfs path as string from mounts diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index 0f9a45bacc..20083eb0bc 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -3,16 +3,16 @@ Summary: Ruby module for collecting simple facts about a host operating system Name: facter Version: 1.6.11 -#Release: 2%{?dist} -Release: 0.1rc1%{?dist} +Release: 1%{?dist} +#Release: 0.1rc1%{?dist} Epoch: 1 License: Apache 2.0 Group: System Environment/Base URL: http://www.puppetlabs.com/puppet/related-projects/%{name} -Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc1.tar.gz -#Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}.tar.gz -Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc1.tar.gz.asc -#Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}.tar.gz.asc +#Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc1.tar.gz +Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}.tar.gz +#Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc1.tar.gz.asc +Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}.tar.gz.asc BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) @@ -33,8 +33,8 @@ system. Some of the facts are preconfigured, such as the hostname and the operating system. Additional facts can be added through simple Ruby scripts %prep -#%setup -q -n %{name}-%{version} -%setup -q -n %{name}-%{version}rc1 +%setup -q -n %{name}-%{version} +#%setup -q -n %{name}-%{version}rc1 %build @@ -56,6 +56,9 @@ rm -rf %{buildroot} %changelog +* Wed Aug 08 2012 Moses Mendoza - 1.6.11-1 +- Update for 1.6.11 + * Wed Aug 01 2012 Moses Mendoza - 1.6.11-0.1rc1 - Update for 1.6.11rc1 From 1bbbc4d237ce7c5ba05aef6e1852d2c915f2732e Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Wed, 8 Aug 2012 15:24:46 -0700 Subject: [PATCH 0948/3753] Update a facter build-requires for f17 --- conf/redhat/facter.spec | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index 20083eb0bc..7d54bfbd9a 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -25,7 +25,13 @@ Requires: pciutils %endif Requires: ruby(abi) >= 1.8 BuildRequires: ruby >= 1.8.5 + +# In Fedora 17 ruby-rdoc is call rubygem-rdoc +%if 0%{?fedora} >= 17 +BuildRequires: rubygem-rdoc +%else BuildRequires: ruby-rdoc +%endif %description Ruby module for collecting simple facts about a host Operating From 7ca91229da9798cdd175fabd83d731c9da299612 Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Wed, 8 Aug 2012 15:24:46 -0700 Subject: [PATCH 0949/3753] Update a facter build-requires for f17 --- conf/redhat/facter.spec | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index 20083eb0bc..b788ce9bd7 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -25,7 +25,13 @@ Requires: pciutils %endif Requires: ruby(abi) >= 1.8 BuildRequires: ruby >= 1.8.5 + +# In Fedora 17 ruby-rdoc is called rubygem-rdoc +%if 0%{?fedora} >= 17 +BuildRequires: rubygem-rdoc +%else BuildRequires: ruby-rdoc +%endif %description Ruby module for collecting simple facts about a host Operating From 17243bbfe674168bebd9b775145517089af9f049 Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Wed, 8 Aug 2012 17:39:12 -0700 Subject: [PATCH 0950/3753] Update facter redhat spec for fedora 17 This commit adds support for fedora 17/ruby 1.9 which uses vendorlibdir instead of sitelibdir. Signed-off-by: Moses Mendoza --- conf/redhat/facter.spec | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index b788ce9bd7..f9b37a20db 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -1,9 +1,15 @@ -%{!?ruby_sitelibdir: %define ruby_sitelibdir %(ruby -rrbconfig -e 'puts Object.const_get(defined?(RbConfig) ? :RbConfig : :Config)::CONFIG["sitelibdir"]')} +# Fedora 17 ships with ruby 1.9, which uses vendorlibdir instead +# of sitelibdir +%if 0%{?fedora} >= 17 +%global facter_libdir %(ruby -rrbconfig -e 'puts RbConfig::CONFIG["vendorlibdir"]') +%else +%global facter_libdir %(ruby -rrbconfig -e 'puts Object.const_get(defined?(RbConfig) ? :RbConfig : :Config)::CONFIG["sitelibdir"]') +%endif Summary: Ruby module for collecting simple facts about a host operating system Name: facter Version: 1.6.11 -Release: 1%{?dist} +Release: 2%{?dist} #Release: 0.1rc1%{?dist} Epoch: 1 License: Apache 2.0 @@ -46,7 +52,7 @@ operating system. Additional facts can be added through simple Ruby scripts %install rm -rf %{buildroot} -ruby install.rb --destdir=%{buildroot} --quick +ruby install.rb --destdir=%{buildroot} --quick --sitelibdir=%{facter_libdir} %clean rm -rf %{buildroot} @@ -55,13 +61,16 @@ rm -rf %{buildroot} %files %defattr(-,root,root,-) %{_bindir}/facter -%{ruby_sitelibdir}/facter.rb -%{ruby_sitelibdir}/facter +%{facter_libdir}/facter.rb +%{facter_libdir}/facter %{_mandir}/man8/facter.8.gz %doc CHANGELOG INSTALL LICENSE README.md %changelog +* Wed Aug 08 2012 Moses Mendoza - 1.6.11-2 +- Use correct ruby libdir for fedora 17 / ruby 1.9 + * Wed Aug 08 2012 Moses Mendoza - 1.6.11-1 - Update for 1.6.11 From fec2276127d11ecb157e9d505ef8e5922e24d501 Mon Sep 17 00:00:00 2001 From: rahul Date: Wed, 1 Aug 2012 15:04:37 -0700 Subject: [PATCH 0951/3753] (#1424) Added facts zones, zone_ & zonename for solaris. Added facts for zones in solaris and test data file in fixtures. --- lib/facter/zones.rb | 27 +++++++++++++++++++ spec/fixtures/unit/zones/zoneadm-list.out | 3 +++ spec/unit/zonename_spec.rb | 2 +- spec/unit/zones_spec.rb | 33 +++++++++++++++++++++++ 4 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 lib/facter/zones.rb create mode 100644 spec/fixtures/unit/zones/zoneadm-list.out create mode 100644 spec/unit/zones_spec.rb diff --git a/lib/facter/zones.rb b/lib/facter/zones.rb new file mode 100644 index 0000000000..a532a90216 --- /dev/null +++ b/lib/facter/zones.rb @@ -0,0 +1,27 @@ +# Fact: zones# +# +# Purpose: +# Return the list of zones on the system and add one zones_ fact +# for each zone with its state e.g. running, incomplete or installed. +# +# Resolution: +# Uses 'usr/sbin/zoneadm list -cp' to get the list of zones in separate parsable +# lines with delimeter being ':' which is used to split the line string and get +# the zone details. +# +# Caveats: +# We dont support below s10 where zones are not available. + +Facter.add("zones") do + confine :kernel => :sunos + fmt = [:id, :name, :status, :path, :uuid, :brand, :iptype] + l = Facter::Util::Resolution.exec('/usr/sbin/zoneadm list -cp').collect{|l|l.split(':')}.each do |val| + fmt.each_index do |i| + Facter.add "zone_%s_%s" % [val[1], fmt[i]] do + setcode { val[i] } + end + end + end + setcode { l.length } +end + diff --git a/spec/fixtures/unit/zones/zoneadm-list.out b/spec/fixtures/unit/zones/zoneadm-list.out new file mode 100644 index 0000000000..0c93d84fc3 --- /dev/null +++ b/spec/fixtures/unit/zones/zoneadm-list.out @@ -0,0 +1,3 @@ +0:global:running:/::native:shared +0:local:configured:/::native:shared +0:zoneA:stopped:/::native:shared \ No newline at end of file diff --git a/spec/unit/zonename_spec.rb b/spec/unit/zonename_spec.rb index 28915c255c..b768642b81 100644 --- a/spec/unit/zonename_spec.rb +++ b/spec/unit/zonename_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env ruby +#!/usr/bin/env rspec require 'spec_helper' require 'facter' diff --git a/spec/unit/zones_spec.rb b/spec/unit/zones_spec.rb new file mode 100644 index 0000000000..2797ab775e --- /dev/null +++ b/spec/unit/zones_spec.rb @@ -0,0 +1,33 @@ +#!usr/bin/env rspec + +require 'spec_helper' + +describe "on Solaris" do + before do + Facter.fact(:kernel).stubs(:value).returns("SunOS") + end + + describe "number of zones" do + it "should output number of zones" do + zone_list = my_fixture_read("zoneadm-list.out") + Facter::Util::Resolution.stubs(:exec). + with('/usr/sbin/zoneadm list -cp'). + returns(zone_list) + Facter.fact(:zones).value.should == 3 + end + end + + describe "per zone fact and its status" do + it "should have a per zone fact with its status" do + zone_list = my_fixture_read("zoneadm-list.out") + Facter::Util::Resolution.stubs(:exec). + with('/usr/sbin/zoneadm list -cp'). + returns(zone_list) + + Facter.collection.internal_loader.load(:zones) + Facter.value("zone_global_status").should == "running" + Facter.value("zone_local_status").should == "configured" + Facter.value("zone_zoneA_status").should == "stopped" + end + end +end From b1bcf3179b0267f2dd8bc0d429b810155291351b Mon Sep 17 00:00:00 2001 From: Hailee Kenney Date: Fri, 10 Aug 2012 14:15:16 -0700 Subject: [PATCH 0952/3753] (#1424) Fix up syntax in tests --- spec/unit/zones_spec.rb | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/spec/unit/zones_spec.rb b/spec/unit/zones_spec.rb index 2797ab775e..f6fd3ae5cd 100644 --- a/spec/unit/zones_spec.rb +++ b/spec/unit/zones_spec.rb @@ -10,9 +10,8 @@ describe "number of zones" do it "should output number of zones" do zone_list = my_fixture_read("zoneadm-list.out") - Facter::Util::Resolution.stubs(:exec). - with('/usr/sbin/zoneadm list -cp'). - returns(zone_list) + Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/zoneadm list -cp').returns(zone_list) + Facter.fact(:zones).value.should == 3 end end @@ -20,9 +19,7 @@ describe "per zone fact and its status" do it "should have a per zone fact with its status" do zone_list = my_fixture_read("zoneadm-list.out") - Facter::Util::Resolution.stubs(:exec). - with('/usr/sbin/zoneadm list -cp'). - returns(zone_list) + Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/zoneadm list -cp').returns(zone_list) Facter.collection.internal_loader.load(:zones) Facter.value("zone_global_status").should == "running" From 3f0a335e59ea68786bc4f9cbd9ff29fad9a5dd6c Mon Sep 17 00:00:00 2001 From: Hailee Kenney Date: Fri, 10 Aug 2012 15:23:57 -0700 Subject: [PATCH 0953/3753] Revert "(#1424) Fix up syntax in tests" This reverts commit b1bcf3179b0267f2dd8bc0d429b810155291351b. --- spec/unit/zones_spec.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/spec/unit/zones_spec.rb b/spec/unit/zones_spec.rb index f6fd3ae5cd..2797ab775e 100644 --- a/spec/unit/zones_spec.rb +++ b/spec/unit/zones_spec.rb @@ -10,8 +10,9 @@ describe "number of zones" do it "should output number of zones" do zone_list = my_fixture_read("zoneadm-list.out") - Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/zoneadm list -cp').returns(zone_list) - + Facter::Util::Resolution.stubs(:exec). + with('/usr/sbin/zoneadm list -cp'). + returns(zone_list) Facter.fact(:zones).value.should == 3 end end @@ -19,7 +20,9 @@ describe "per zone fact and its status" do it "should have a per zone fact with its status" do zone_list = my_fixture_read("zoneadm-list.out") - Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/zoneadm list -cp').returns(zone_list) + Facter::Util::Resolution.stubs(:exec). + with('/usr/sbin/zoneadm list -cp'). + returns(zone_list) Facter.collection.internal_loader.load(:zones) Facter.value("zone_global_status").should == "running" From cc8f5746571cb2c4948ca1d99ee538213c2da498 Mon Sep 17 00:00:00 2001 From: Hailee Kenney Date: Fri, 10 Aug 2012 15:24:22 -0700 Subject: [PATCH 0954/3753] Revert "(#1424) Added facts zones, zone_ & zonename for solaris." This reverts commit fec2276127d11ecb157e9d505ef8e5922e24d501. --- lib/facter/zones.rb | 27 ------------------- spec/fixtures/unit/zones/zoneadm-list.out | 3 --- spec/unit/zonename_spec.rb | 2 +- spec/unit/zones_spec.rb | 33 ----------------------- 4 files changed, 1 insertion(+), 64 deletions(-) delete mode 100644 lib/facter/zones.rb delete mode 100644 spec/fixtures/unit/zones/zoneadm-list.out delete mode 100644 spec/unit/zones_spec.rb diff --git a/lib/facter/zones.rb b/lib/facter/zones.rb deleted file mode 100644 index a532a90216..0000000000 --- a/lib/facter/zones.rb +++ /dev/null @@ -1,27 +0,0 @@ -# Fact: zones# -# -# Purpose: -# Return the list of zones on the system and add one zones_ fact -# for each zone with its state e.g. running, incomplete or installed. -# -# Resolution: -# Uses 'usr/sbin/zoneadm list -cp' to get the list of zones in separate parsable -# lines with delimeter being ':' which is used to split the line string and get -# the zone details. -# -# Caveats: -# We dont support below s10 where zones are not available. - -Facter.add("zones") do - confine :kernel => :sunos - fmt = [:id, :name, :status, :path, :uuid, :brand, :iptype] - l = Facter::Util::Resolution.exec('/usr/sbin/zoneadm list -cp').collect{|l|l.split(':')}.each do |val| - fmt.each_index do |i| - Facter.add "zone_%s_%s" % [val[1], fmt[i]] do - setcode { val[i] } - end - end - end - setcode { l.length } -end - diff --git a/spec/fixtures/unit/zones/zoneadm-list.out b/spec/fixtures/unit/zones/zoneadm-list.out deleted file mode 100644 index 0c93d84fc3..0000000000 --- a/spec/fixtures/unit/zones/zoneadm-list.out +++ /dev/null @@ -1,3 +0,0 @@ -0:global:running:/::native:shared -0:local:configured:/::native:shared -0:zoneA:stopped:/::native:shared \ No newline at end of file diff --git a/spec/unit/zonename_spec.rb b/spec/unit/zonename_spec.rb index b768642b81..28915c255c 100644 --- a/spec/unit/zonename_spec.rb +++ b/spec/unit/zonename_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#!/usr/bin/env ruby require 'spec_helper' require 'facter' diff --git a/spec/unit/zones_spec.rb b/spec/unit/zones_spec.rb deleted file mode 100644 index 2797ab775e..0000000000 --- a/spec/unit/zones_spec.rb +++ /dev/null @@ -1,33 +0,0 @@ -#!usr/bin/env rspec - -require 'spec_helper' - -describe "on Solaris" do - before do - Facter.fact(:kernel).stubs(:value).returns("SunOS") - end - - describe "number of zones" do - it "should output number of zones" do - zone_list = my_fixture_read("zoneadm-list.out") - Facter::Util::Resolution.stubs(:exec). - with('/usr/sbin/zoneadm list -cp'). - returns(zone_list) - Facter.fact(:zones).value.should == 3 - end - end - - describe "per zone fact and its status" do - it "should have a per zone fact with its status" do - zone_list = my_fixture_read("zoneadm-list.out") - Facter::Util::Resolution.stubs(:exec). - with('/usr/sbin/zoneadm list -cp'). - returns(zone_list) - - Facter.collection.internal_loader.load(:zones) - Facter.value("zone_global_status").should == "running" - Facter.value("zone_local_status").should == "configured" - Facter.value("zone_zoneA_status").should == "stopped" - end - end -end From defbfb8ec033a0319719461c44c548760fdb5d7c Mon Sep 17 00:00:00 2001 From: Matthaus Litteken Date: Mon, 13 Aug 2012 13:26:12 -0700 Subject: [PATCH 0955/3753] (#15291) Add Vendor tag to Facter spec file Previously the spec file had no Vendor tag, which left it undefined. This commit adds a Vendor tag that references the _host_vendor macro, so that it can be easily set to 'Puppet Labs' internally and customized by users easily. The Vendor tag makes it easier for users to tell where the package came from. --- conf/redhat/facter.spec | 1 + 1 file changed, 1 insertion(+) diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index f9b37a20db..448bf1a400 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -12,6 +12,7 @@ Version: 1.6.11 Release: 2%{?dist} #Release: 0.1rc1%{?dist} Epoch: 1 +Vendor: %{?_host_vendor} License: Apache 2.0 Group: System Environment/Base URL: http://www.puppetlabs.com/puppet/related-projects/%{name} From bf6ee4f9417b68642c2764a505cbbd4273cdba9f Mon Sep 17 00:00:00 2001 From: Matthaus Litteken Date: Mon, 13 Aug 2012 13:30:18 -0700 Subject: [PATCH 0956/3753] Retabbed conf/redhat/facter.spec to lineup tag contents. --- conf/redhat/facter.spec | 48 ++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index 448bf1a400..ddfc562387 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -6,38 +6,38 @@ %global facter_libdir %(ruby -rrbconfig -e 'puts Object.const_get(defined?(RbConfig) ? :RbConfig : :Config)::CONFIG["sitelibdir"]') %endif -Summary: Ruby module for collecting simple facts about a host operating system -Name: facter -Version: 1.6.11 -Release: 2%{?dist} -#Release: 0.1rc1%{?dist} -Epoch: 1 +Summary: Ruby module for collecting simple facts about a host operating system +Name: facter +Version: 1.6.11 +Release: 2%{?dist} +#Release: 0.1rc1%{?dist} +Epoch: 1 Vendor: %{?_host_vendor} -License: Apache 2.0 -Group: System Environment/Base -URL: http://www.puppetlabs.com/puppet/related-projects/%{name} -#Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc1.tar.gz -Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}.tar.gz -#Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc1.tar.gz.asc -Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}.tar.gz.asc - -BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) - -Requires: ruby >= 1.8.5 -Requires: which +License: Apache 2.0 +Group: System Environment/Base +URL: http://www.puppetlabs.com/puppet/related-projects/%{name} +#Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc1.tar.gz +Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}.tar.gz +#Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc1.tar.gz.asc +Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}.tar.gz.asc + +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) + +Requires: ruby >= 1.8.5 +Requires: which # dmidecode and pciutils are not available on all arches %ifarch %ix86 x86_64 ia64 -Requires: dmidecode -Requires: pciutils +Requires: dmidecode +Requires: pciutils %endif -Requires: ruby(abi) >= 1.8 -BuildRequires: ruby >= 1.8.5 +Requires: ruby(abi) >= 1.8 +BuildRequires: ruby >= 1.8.5 # In Fedora 17 ruby-rdoc is called rubygem-rdoc %if 0%{?fedora} >= 17 -BuildRequires: rubygem-rdoc +BuildRequires: rubygem-rdoc %else -BuildRequires: ruby-rdoc +BuildRequires: ruby-rdoc %endif %description From e6ba7272644e4aa2822b43df77142e77541d6087 Mon Sep 17 00:00:00 2001 From: rahul Date: Mon, 13 Aug 2012 10:03:12 -0700 Subject: [PATCH 0957/3753] (#1424) Adds zone facts for solaris. Added facts for zones in solaris and test data file in fixtures. The facts added are `id, name, status, path, uuid, brand, iptype` --- lib/facter/zones.rb | 26 ++++++++++++++++++ spec/unit/zonename_spec.rb | 2 +- spec/unit/zones_spec.rb | 55 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 lib/facter/zones.rb create mode 100644 spec/unit/zones_spec.rb diff --git a/lib/facter/zones.rb b/lib/facter/zones.rb new file mode 100644 index 0000000000..47646121a3 --- /dev/null +++ b/lib/facter/zones.rb @@ -0,0 +1,26 @@ +# Fact: zones# +# +# Purpose: +# Return the list of zones on the system and add one zones_ fact +# for each zone with its state e.g. running, incomplete or installed. +# +# Resolution: +# Uses 'usr/sbin/zoneadm list -cp' to get the list of zones in separate parsable +# lines with delimeter being ':' which is used to split the line string and get +# the zone details. +# +# Caveats: +# We dont support below s10 where zones are not available. + +Facter.add("zones") do + confine :kernel => :sunos + fmt = [:id, :name, :status, :path, :uuid, :brand, :iptype] + l = Facter::Util::Resolution.exec('/usr/sbin/zoneadm list -cp').split("\n").collect{|l|l.split(':')}.each do |val| + fmt.each_index do |i| + Facter.add "zone_%s_%s" % [val[1], fmt[i]] do + setcode { val[i] } + end + end + end + setcode { l.length } +end diff --git a/spec/unit/zonename_spec.rb b/spec/unit/zonename_spec.rb index 28915c255c..b768642b81 100644 --- a/spec/unit/zonename_spec.rb +++ b/spec/unit/zonename_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env ruby +#!/usr/bin/env rspec require 'spec_helper' require 'facter' diff --git a/spec/unit/zones_spec.rb b/spec/unit/zones_spec.rb new file mode 100644 index 0000000000..fbbb823838 --- /dev/null +++ b/spec/unit/zones_spec.rb @@ -0,0 +1,55 @@ +#!usr/bin/env rspec + +require 'spec_helper' + +describe "on Solaris" do + before do + Facter.fact(:kernel).stubs(:value).returns("SunOS") + zone_list = <<-EOF +0:global:running:/::native:shared +-:local:configured:/::native:shared +-:zoneA:stopped:/::native:shared + EOF + Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/zoneadm list -cp').returns(zone_list) + Facter.collection.internal_loader.load(:zones) + end + + describe "number of zones" do + it "should output number of zones" do + Facter.fact(:zones).value.should == 3 + end + end + + describe "zone specific values" do + it "Fact#zone__status" do + {'global' => 'running', 'local' => 'configured', 'zoneA' => 'stopped'}.each do |key, val| + Facter.value("zone_%s_status" % key).should == val + end + end + + it "Fact#zone__id" do + {'global' => '0', 'local' => '-', 'zoneA' => '-'}.each do |key, val| + Facter.value("zone_%s_id" % key).should == val + end + end + + it "Fact#zone__path" do + {'global' => '/', 'local' => '/', 'zoneA' => '/'}.each do |key, val| + Facter.value("zone_%s_path" % key).should == val + end + end + + it "Fact#zone__brand" do + {'global' => 'native', 'local' => 'native', 'zoneA' => 'native'}.each do |key, val| + Facter.value("zone_%s_brand" % key).should == val + end + end + + it "Fact#zone__iptype" do + {'global' => 'shared', 'local' => 'shared', 'zoneA' => 'shared'}.each do |key, val| + Facter.value("zone_%s_iptype" % key).should == val + end + end + end +end + From 9006e74fa4c99a2277b34fd8a65ed1acfc18576e Mon Sep 17 00:00:00 2001 From: Matthaus Litteken Date: Tue, 14 Aug 2012 13:25:30 -0700 Subject: [PATCH 0958/3753] (#15852) Allow RC in Puppet, Facter version strings Previously putting rc in the Facter version string would fail, mainly because of a test that verified that the version only contained 3 dot separated integers. This commit updates the Facter version to correctly use 2.0.0-rc4. It also removes the test that verified the Facter version was a 'valid' 3 dotted integer version. --- lib/facter.rb | 2 +- spec/unit/facter_spec.rb | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/facter.rb b/lib/facter.rb index a20769b2fc..ffbf096c89 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -25,7 +25,7 @@ module Util; end include Comparable include Enumerable - FACTERVERSION = '2.0.0' + FACTERVERSION = '2.0.0-rc4' # = Facter # Functions as a hash of 'facts' you might care about about your diff --git a/spec/unit/facter_spec.rb b/spec/unit/facter_spec.rb index 6526b1be72..1312c15f44 100755 --- a/spec/unit/facter_spec.rb +++ b/spec/unit/facter_spec.rb @@ -3,11 +3,6 @@ require 'spec_helper' describe Facter do - - it "should have a version" do - Facter.version.should =~ /^[0-9]+(\.[0-9]+)*$/ - end - it "should have a method for returning its collection" do Facter.should respond_to(:collection) end From 0b49eae183c97351713ff511fcb2cb744a6aa8b4 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Thu, 16 Aug 2012 23:20:01 -0700 Subject: [PATCH 0959/3753] (#15464) Make contributing easy via bundle Gemfile Without this patch the process of figuring out how to quickly set up a development and testing environment for the Puppet _application_ (not a gem library) is unnecessarily complicated. This patch addresses the problem by providing a Bundler compatible Gemfile that specifies all of the Gem dependencies for the Puppet 2.7 application. Puppet contributors can now easily get a working development and testing environment using this sequence of commands: $ git clone --branch 2.7.x git://github.com/puppetlabs/puppet.git $ cd puppet/ $ bundle install # Install all required dependencies $ rspec The .noexec.yaml file excludes the `rake` command so that the Gemfile doesn't raise an exception if the `rubygems-bundler` Gem is installed and automatically running `bundle exec` for us. The Gemfile.lock contains the exact dependency versions. This file is included in the version control system because we're treating Puppet as an application rather than a library. --- .noexec.yaml | 4 ++++ Gemfile | 14 ++++++++++++++ Gemfile.lock | 28 ++++++++++++++++++++++++++++ facter.gemspec | 29 +++++++++++++++++++++++++++++ 4 files changed, 75 insertions(+) create mode 100644 .noexec.yaml create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 facter.gemspec diff --git a/.noexec.yaml b/.noexec.yaml new file mode 100644 index 0000000000..dd4203d521 --- /dev/null +++ b/.noexec.yaml @@ -0,0 +1,4 @@ +--- +exclude: + - rake + - rspec diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000000..7c2472002e --- /dev/null +++ b/Gemfile @@ -0,0 +1,14 @@ +source :rubygems + +gemspec + +group(:development, :test) do + gem "rspec", "~> 2.10.0", :require => false + gem "mocha", "~> 0.10.5", :require => false +end + +if File.exists? "#{__FILE__}.local" + eval(File.read("#{__FILE__}.local"), binding) +end + +# vim:filetype=ruby diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000000..22a3121b60 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,28 @@ +PATH + remote: . + specs: + facter (1.6.11) + +GEM + remote: http://rubygems.org/ + specs: + diff-lcs (1.1.3) + metaclass (0.0.1) + mocha (0.10.5) + metaclass (~> 0.0.1) + rspec (2.10.0) + rspec-core (~> 2.10.0) + rspec-expectations (~> 2.10.0) + rspec-mocks (~> 2.10.0) + rspec-core (2.10.1) + rspec-expectations (2.10.0) + diff-lcs (~> 1.1.3) + rspec-mocks (2.10.1) + +PLATFORMS + ruby + +DEPENDENCIES + facter! + mocha (~> 0.10.5) + rspec (~> 2.10.0) diff --git a/facter.gemspec b/facter.gemspec new file mode 100644 index 0000000000..d2ec27b6f4 --- /dev/null +++ b/facter.gemspec @@ -0,0 +1,29 @@ +# -*- encoding: utf-8 -*- + +Gem::Specification.new do |s| + s.name = "facter" + s.version = "1.6.11" + + s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= + s.authors = ["Puppet Labs"] + s.date = "2012-08-08" + s.description = "You can prove anything with facts!" + s.email = "info@puppetlabs.com" + s.executables = ["facter"] + s.files = ["bin/facter"] + s.homepage = "http://puppetlabs.com/" + s.rdoc_options = ["--title", "Facter - System Inventory Tool", "--main", "README", "--line-numbers"] + s.require_paths = ["lib"] + s.rubyforge_project = "facter" + s.rubygems_version = "1.8.24" + s.summary = "Facter, a system inventory tool" + + if s.respond_to? :specification_version then + s.specification_version = 3 + + if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then + else + end + else + end +end From 56d0aef7c070ebf4f0c994f91b232697f395da63 Mon Sep 17 00:00:00 2001 From: OMCnet Development Team Date: Thu, 2 Aug 2012 09:57:40 +0200 Subject: [PATCH 0960/3753] added freebsd support to processor.rb --- lib/facter/processor.rb | 4 ++-- spec/unit/processor_spec.rb | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb index 0b0f0e36f1..3fd31090dd 100644 --- a/lib/facter/processor.rb +++ b/lib/facter/processor.rb @@ -152,14 +152,14 @@ end Facter.add("Processor") do - confine :kernel => :dragonfly + confine :kernel => [:dragonfly,:freebsd] setcode do Facter::Util::Resolution.exec("sysctl -n hw.model") end end Facter.add("ProcessorCount") do - confine :kernel => :dragonfly + confine :kernel => [:dragonfly,:freebsd] setcode do Facter::Util::Resolution.exec("sysctl -n hw.ncpu") end diff --git a/spec/unit/processor_spec.rb b/spec/unit/processor_spec.rb index e9368d2cc0..3b31db6e9f 100755 --- a/spec/unit/processor_spec.rb +++ b/spec/unit/processor_spec.rb @@ -186,6 +186,21 @@ def load(procs) Facter.fact(:processorcount).value.should == "2" end + it "should be 2 on dual-processor FreeBSD box" do + Facter.fact(:kernel).stubs(:value).returns("FreeBSD") + Facter::Util::Resolution.stubs(:exec).with("sysctl -n hw.ncpu").returns('2') + + Facter.fact(:processorcount).value.should == "2" + end + + it "should print the correct CPU Model on FreeBSD" do + Facter.fact(:kernel).stubs(:value).returns("FreeBSD") + Facter::Util::Resolution.stubs(:exec).with("sysctl -n hw.model").returns('SomeVendor CPU 3GHz') + + Facter.fact(:processor).value.should == "SomeVendor CPU 3GHz" + end + + it "should be 2 on dual-processor DragonFly box" do Facter.fact(:kernel).stubs(:value).returns("DragonFly") Facter::Util::Resolution.stubs(:exec).with("sysctl -n hw.ncpu").returns('2') From c0cbe621d73bd3001b2fa12df828a413c9b8ce85 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Fri, 17 Aug 2012 15:55:52 -0700 Subject: [PATCH 0961/3753] (#15464) Make Facter.version settable via Facter.version= Without this patch applied the version string of Facter is dynamically set from the `git describe` tag in the Rakefile using monkey patching. This is problematic because this causes a constant to be re-defined which issues a warning in Ruby. This patch fixes the problem by implementing the Facter.version= module method. The Rakefile will now set the Facter version by using `require 'puppet/version'` and calling `Facter.version = %x{git describe ...}` Using a module method also has the benefit of making it easier to intercept and mock Facter version specific behavior. From this point forward, the method Facter.version should but used to obtain the version string instead of the constant Facter::FACTERVERSION. --- Rakefile | 5 ++++- lib/facter.rb | 8 ++------ lib/facter/facterversion.rb | 7 +++++-- lib/facter/version.rb | 13 +++++++++++++ tasks/rake/sign.rake | 2 +- 5 files changed, 25 insertions(+), 10 deletions(-) create mode 100644 lib/facter/version.rb diff --git a/Rakefile b/Rakefile index 0415b7a273..5939a28b30 100644 --- a/Rakefile +++ b/Rakefile @@ -1,6 +1,9 @@ # Rakefile for facter -$: << File.expand_path('lib') +# We need access to the Puppet.version method +$LOAD_PATH.unshift(File.expand_path("lib")) +require 'facter/version' + $LOAD_PATH << File.join(File.dirname(__FILE__), 'tasks') require 'rubygems' diff --git a/lib/facter.rb b/lib/facter.rb index 7bffe04da1..8ba6e0c035 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -14,6 +14,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +require 'facter/version' + module Facter # This is just so the other classes have the constant. module Util; end @@ -25,7 +27,6 @@ module Util; end include Comparable include Enumerable - FACTERVERSION = '1.6.11' # = Facter # Functions as a hash of 'facts' you might care about about your # system, such as mac address, IP address, Video card, etc. @@ -58,11 +59,6 @@ def self.collection @collection end - # Return the version of the library. - def self.version - return FACTERVERSION - end - # Add some debugging def self.debug(string) if string.nil? diff --git a/lib/facter/facterversion.rb b/lib/facter/facterversion.rb index 519d660329..e641b9c3b0 100644 --- a/lib/facter/facterversion.rb +++ b/lib/facter/facterversion.rb @@ -2,11 +2,14 @@ # # Purpose: returns the version of the facter module. # -# Resolution: Uses the version constant. +# Resolution: Uses the Facter.version method. # # Caveats: # Facter.add(:facterversion) do - setcode { Facter::FACTERVERSION.to_s } + setcode do + require 'facter/version' + Facter.version.to_s + end end diff --git a/lib/facter/version.rb b/lib/facter/version.rb new file mode 100644 index 0000000000..8cb199c628 --- /dev/null +++ b/lib/facter/version.rb @@ -0,0 +1,13 @@ +module Facter + if not defined? FACTERVERSION then + FACTERVERSION = '1.6.11' + end + + def self.version + @facter_version || FACTERVERSION + end + + def self.version=(version) + @facter_version = version + end +end diff --git a/tasks/rake/sign.rake b/tasks/rake/sign.rake index 70351663b1..fabb0e1165 100644 --- a/tasks/rake/sign.rake +++ b/tasks/rake/sign.rake @@ -1,7 +1,7 @@ desc "Sign the package with the Puppet Labs release key" task :sign_packages do - version = Facter::FACTERVERSION + version = Facter.version # Sign package From 2624508eeec55136a76009133911549d61d18014 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Mon, 20 Aug 2012 16:22:21 -0700 Subject: [PATCH 0962/3753] (Maint) Fix Facter version to be 2.0.0-rc4 During the merge-up from 1.6.x I accidentally set the Facter version to be 1.6.11 in the 2.x branch. This is a problem because Puppet 3.x currently requires Facter 2.x and fails hard if the version is mis-reported. This patch fixes the problem by setting the hard-coded version of Facter.version to be 2.0.0-rc4 which is the last known explicitly set version in the 2.x branch. --- lib/facter/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/version.rb b/lib/facter/version.rb index 8cb199c628..165985a23f 100644 --- a/lib/facter/version.rb +++ b/lib/facter/version.rb @@ -1,6 +1,6 @@ module Facter if not defined? FACTERVERSION then - FACTERVERSION = '1.6.11' + FACTERVERSION = '2.0.0-rc4' end def self.version From 0d141bbe78c74c5634613d70a1bbef2f66be4de8 Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Mon, 20 Aug 2012 21:13:19 -0700 Subject: [PATCH 0963/3753] Fixup redhat spec file for f17 This commit fixes up the redhat facter.spec file, adding in some missing fedora 17 changes that were probably lost in a merge-up. The os-dependent definition of facter_libdir is replaced, and the mandir file-listing is added. The last changelog entry is removed, which is out of chronological order (which prevents rpm building). It also removes an errant conflict marker. Signed-off-by: Moses Mendoza --- conf/redhat/facter.spec | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/conf/redhat/facter.spec b/conf/redhat/facter.spec index 0bf5b89318..6d848aa11d 100644 --- a/conf/redhat/facter.spec +++ b/conf/redhat/facter.spec @@ -1,4 +1,10 @@ -%{!?ruby_sitelibdir: %define ruby_sitelibdir %(ruby -rrbconfig -e 'puts Object.const_get(defined?(RbConfig) ? :RbConfig : :Config)::CONFIG["sitelibdir"]')} +# Fedora 17 ships with ruby 1.9, which uses vendorlibdir instead +# of sitelibdir +%if 0%{?fedora} >= 17 +%global facter_libdir %(ruby -rrbconfig -e 'puts RbConfig::CONFIG["vendorlibdir"]') +%else +%global facter_libdir %(ruby -rrbconfig -e 'puts Object.const_get(defined?(RbConfig) ? :RbConfig : :Config)::CONFIG["sitelibdir"]') +%endif Summary: Ruby module for collecting simple facts about a host operating system Name: facter @@ -54,15 +60,13 @@ rm -rf %{buildroot} %files %defattr(-,root,root,-) %{_bindir}/facter -%{ruby_sitelibdir}/facter.rb -%{ruby_sitelibdir}/facter +%{facter_libdir}/facter.rb +%{facter_libdir}/facter +%{_mandir}/man8/facter.8.gz %doc CHANGELOG INSTALL LICENSE README.md %changelog -* Thu May 24 2012 Moses Mendoza - 2.0.0-0.1rc4 -- Update for 2.0.0rc4 release - * Wed Aug 08 2012 Moses Mendoza - 1.6.11-2 - Use correct ruby libdir for fedora 17 / ruby 1.9 @@ -74,7 +78,6 @@ rm -rf %{buildroot} * Sat Jul 07 2012 Michael Stahnke - 1.6.10-2 - Attempt to build fro Ruby 1.9.3 ->>>>>>> 1.6.x * Tue May 22 2012 Moses Mendoza - 2.0.0-0.1rc3 - Update for 2.0.0rc3 release From db9d15481fbf07563efec502456eb67d0c6ee2ee Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Sun, 29 Jul 2012 10:35:01 -0700 Subject: [PATCH 0964/3753] Move packaging files to ext, rm conf This commit moves packaging artifacts out of the conf directory and into ext, and removes the conf directory. The apple rake task is updated for the new path. conf is a strange directory for packaging artifacts. ext seems saner. --- {conf => ext}/osx/PackageInfo.plist | 0 {conf => ext}/osx/createpackage.sh | 0 {conf => ext}/osx/preflight | 0 {tasks/rake => ext/osx}/templates/prototype.plist.erb | 0 {conf => ext}/redhat/facter.spec | 0 {conf => ext}/solaris/pkginfo | 0 tasks/rake/apple.rake | 2 +- 7 files changed, 1 insertion(+), 1 deletion(-) rename {conf => ext}/osx/PackageInfo.plist (100%) rename {conf => ext}/osx/createpackage.sh (100%) rename {conf => ext}/osx/preflight (100%) rename {tasks/rake => ext/osx}/templates/prototype.plist.erb (100%) rename {conf => ext}/redhat/facter.spec (100%) rename {conf => ext}/solaris/pkginfo (100%) diff --git a/conf/osx/PackageInfo.plist b/ext/osx/PackageInfo.plist similarity index 100% rename from conf/osx/PackageInfo.plist rename to ext/osx/PackageInfo.plist diff --git a/conf/osx/createpackage.sh b/ext/osx/createpackage.sh similarity index 100% rename from conf/osx/createpackage.sh rename to ext/osx/createpackage.sh diff --git a/conf/osx/preflight b/ext/osx/preflight similarity index 100% rename from conf/osx/preflight rename to ext/osx/preflight diff --git a/tasks/rake/templates/prototype.plist.erb b/ext/osx/templates/prototype.plist.erb similarity index 100% rename from tasks/rake/templates/prototype.plist.erb rename to ext/osx/templates/prototype.plist.erb diff --git a/conf/redhat/facter.spec b/ext/redhat/facter.spec similarity index 100% rename from conf/redhat/facter.spec rename to ext/redhat/facter.spec diff --git a/conf/solaris/pkginfo b/ext/solaris/pkginfo similarity index 100% rename from conf/solaris/pkginfo rename to ext/solaris/pkginfo diff --git a/tasks/rake/apple.rake b/tasks/rake/apple.rake index e08e5a7d7c..c516acc302 100644 --- a/tasks/rake/apple.rake +++ b/tasks/rake/apple.rake @@ -130,7 +130,7 @@ def pack_facter_source # Setup a preflight script and replace variables in the files with # the correct paths. - system("#{INSTALL} -o root -g wheel -m 644 #{facter_source}/conf/osx/preflight #{@working_tree['scripts']}") + system("#{INSTALL} -o root -g wheel -m 644 #{facter_source}/ext/osx/preflight #{@working_tree['scripts']}") system("#{SED} -i '' \"s\#{SITELIBDIR}\#/usr/lib/ruby/site_ruby/1.8\#g\" #{@working_tree['scripts']}/preflight") system("#{SED} -i '' \"s\#{BINDIR}\#/usr/bin\#g\" #{@working_tree['scripts']}/preflight") From 6f58b4e159b2c988513ab38e451aab1dc7150505 Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Sun, 29 Jul 2012 10:51:05 -0700 Subject: [PATCH 0965/3753] Move tasks out of 'rake' subdirectory It seems unnecessary for all the tasks to live in a subdirectory, since its the only one and doesn't accomplish anything as far as I can tell. This commit also updates apple.rake task to reference the new paths. --- Rakefile | 2 +- tasks/{rake => }/apple.rake | 4 ++-- tasks/{rake => }/changlog.rake | 0 tasks/{rake => }/ci.rake | 0 tasks/{rake => }/dailybuild.rake | 0 tasks/{rake => }/mail_patches.rake | 0 tasks/{rake => }/metrics.rake | 0 tasks/{rake => }/sign.rake | 0 8 files changed, 3 insertions(+), 3 deletions(-) rename tasks/{rake => }/apple.rake (97%) rename tasks/{rake => }/changlog.rake (100%) rename tasks/{rake => }/ci.rake (100%) rename tasks/{rake => }/dailybuild.rake (100%) rename tasks/{rake => }/mail_patches.rake (100%) rename tasks/{rake => }/metrics.rake (100%) rename tasks/{rake => }/sign.rake (100%) diff --git a/Rakefile b/Rakefile index 5939a28b30..c1995e939c 100644 --- a/Rakefile +++ b/Rakefile @@ -25,7 +25,7 @@ FILES = FileList[ 'install.rb', 'bin/**/*', 'lib/**/*', - 'conf/**/*', + 'ext/**/*', 'etc/**/*', 'spec/**/*' ] diff --git a/tasks/rake/apple.rake b/tasks/apple.rake similarity index 97% rename from tasks/rake/apple.rake rename to tasks/apple.rake index c516acc302..299560b182 100644 --- a/tasks/rake/apple.rake +++ b/tasks/apple.rake @@ -36,7 +36,7 @@ end # description: This method sets up the directory structure that packagemaker # needs to build a package. A prototype.plist file (holding # package-specific options) is built from an ERB template located -# in the tasks/rake/templates directory. +# in the ext/osx/templates directory. def make_directory_tree facter_tmp = '/tmp/facter' @scratch = "#{facter_tmp}/#{@title}" @@ -53,7 +53,7 @@ def make_directory_tree FileUtils.mkdir_p(val) end File.open("#{@scratch}/#{'prototype.plist'}", "w+") do |f| - f.write(ERB.new(File.read('tasks/rake/templates/prototype.plist.erb')).result()) + f.write(ERB.new(File.read('ext/osx/templates/prototype.plist.erb')).result()) end end diff --git a/tasks/rake/changlog.rake b/tasks/changlog.rake similarity index 100% rename from tasks/rake/changlog.rake rename to tasks/changlog.rake diff --git a/tasks/rake/ci.rake b/tasks/ci.rake similarity index 100% rename from tasks/rake/ci.rake rename to tasks/ci.rake diff --git a/tasks/rake/dailybuild.rake b/tasks/dailybuild.rake similarity index 100% rename from tasks/rake/dailybuild.rake rename to tasks/dailybuild.rake diff --git a/tasks/rake/mail_patches.rake b/tasks/mail_patches.rake similarity index 100% rename from tasks/rake/mail_patches.rake rename to tasks/mail_patches.rake diff --git a/tasks/rake/metrics.rake b/tasks/metrics.rake similarity index 100% rename from tasks/rake/metrics.rake rename to tasks/metrics.rake diff --git a/tasks/rake/sign.rake b/tasks/sign.rake similarity index 100% rename from tasks/rake/sign.rake rename to tasks/sign.rake From d2d3baf78f7f77313ad7e5c4996b581c0f5dd039 Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Sun, 29 Jul 2012 10:57:42 -0700 Subject: [PATCH 0966/3753] Replace rake/gempackagetask with rubygems/gempackagetask This is per deprecation of the rake/gempackagetask. Also remove deprecated has_rdoc in gem specification. --- Rakefile | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Rakefile b/Rakefile index c1995e939c..1f79102b3e 100644 --- a/Rakefile +++ b/Rakefile @@ -18,7 +18,7 @@ Dir['tasks/**/*.rake'].each { |t| load t } require 'rake' require 'rake/packagetask' -require 'rake/gempackagetask' +require 'rubygems/package_task' FILES = FileList[ '[A-Z]*', @@ -80,13 +80,12 @@ spec = Gem::Specification.new do |spec| spec.email = 'info@puppetlabs.com' spec.homepage = 'http://puppetlabs.com/' spec.rubyforge_project = 'facter' - spec.has_rdoc = true spec.rdoc_options << '--title' << 'Facter - System Inventory Tool' << '--main' << 'README' << '--line-numbers' end -Rake::GemPackageTask.new(spec) do |pkg| +Gem::PackageTask.new(spec) do |pkg| end task :package => :tar From 84f8e10c7c29db234e000956c1e7cb38da78f104 Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Sun, 29 Jul 2012 11:25:21 -0700 Subject: [PATCH 0967/3753] Add debian build artifacts to facter project This commit takes the debian build artifacts from facter-debian-packaging and places them into facter proper. This is a substantial change that allows us to stop maintaining the second repository altogether. The debian changelog is also moved to an erb file for automation, but this can still be updated manually if desired. gbp.conf is not carried over because debs are build with pdebuild instead of git-build-package. Signed-off-by: Moses Mendoza --- ext/debian/changelog.erb | 71 ++++++++++++++++++++++++++++++++++++ ext/debian/compat | 1 + ext/debian/control | 14 +++++++ ext/debian/copyright | 19 ++++++++++ ext/debian/docs | 2 + ext/debian/lintian-overrides | 0 ext/debian/rules | 9 +++++ 7 files changed, 116 insertions(+) create mode 100644 ext/debian/changelog.erb create mode 100644 ext/debian/compat create mode 100644 ext/debian/control create mode 100644 ext/debian/copyright create mode 100644 ext/debian/docs create mode 100644 ext/debian/lintian-overrides create mode 100755 ext/debian/rules diff --git a/ext/debian/changelog.erb b/ext/debian/changelog.erb new file mode 100644 index 0000000000..753a215219 --- /dev/null +++ b/ext/debian/changelog.erb @@ -0,0 +1,71 @@ +facter (<%= @debversion %>) hardy lucid maverick natty oneiric unstable lenny sid wheezy lucid squeeze precise; urgency=low + + * Update to version <% @debversion %> + + -- Puppet Labs Release <%= Time.now.strftime("%a, %d %b %Y %H:%M:%S %z")%> + +facter (2.0.0-0.1rc4puppetlabs1) hardy lucid maverick natty oneiric unstable lenny sid wheezy lucid squeeze precise; urgency=low + + * Imported upstream 2.0.0rc4. + + -- Moses Mendoza Thu, 24 May 2012 17:09:25 +0000 + +facter (2.0.0-0.1rc3puppetlabs1) hardy lucid maverick natty oneiric unstable lenny sid wheezy lucid squeeze precise; urgency=low + + * Imported upstream 2.0.0rc3. + + -- Moses Mendoza Tue, 22 May 2012 11:48:25 +0000 + +facter (2.0.0-0.1rc2puppetlabs1) hardy lucid maverick natty oneiric unstable lenny sid wheezy lucid squeeze precise; urgency=low + + * Imported upstream 2.0.0rc2. + + -- Moses Mendoza Thu, 17 May 2012 13:15:25 +0000 + +facter (2.0.0-0.1rc1puppetlabs1) hardy lucid maverick natty oneiric unstable lenny sid wheezy lucid squeeze precise; urgency=low + + * Imported upstream 2.0.0rc1. Updated debian/control to include ruby1.9 options. + + -- Matthaus Litteken Tue, 15 May 2012 23:43:25 +0000 + +facter (1.6.8-1puppetlabs1) hardy lucid maverick natty oneiric unstable lenny sid wheezy lucid squeeze precise; urgency=low + + * Imported Upstream version 1.6.8 + + -- Moses Mendoza Mon, 23 Apr 2012 13:11:30 +0000 + +facter (1.6.7-1puppetlabs1) hardy lucid maverick natty oneiric unstable lenny sid wheezy lucid squeeze precise; urgency=low + + * Imported Upstream version 1.6.7 + + -- Moses Mendoza Thu, 29 Mar 2012 22:43:30 +0000 + +facter (1.6.6-1puppetlabs1) hardy lucid maverick natty oneiric unstable lenny sid squeeze wheezy precise; urgency=low + + * (#12436) Adding pciutils as a dependency. + + -- Matthaus Litteken Fri, 10 Feb 2012 01:49:22 +0000 + +facter (1.6.5-1puppetlabs1) hardy lucid maverick natty oneiric unstable lenny sid squeeze wheezy; urgency=low + + * Release facter 1.6.5 + + -- Matthaus Litteken Wed, 25 Jan 2012 11:46:48 -0700 + +facter (1.6.4-1puppetlabs1) hardy lucid maverick natty oneiric unstable lenny sid squeeze wheezy; urgency=low + + * Release facter 1.6.4 + + -- Matthaus Litteken Wed, 7 Dec 2011 13:36:48 -0700 + +facter (1.6.3-1puppetlabs1) hardy lucid maverick natty oneiric unstable lenny sid squeeze wheezy; urgency=low + + * Initial packaging for facter 1.6.3 + + -- Matthaus Litteken Thu, 10 Nov 2011 11:00:48 -0700 + +facter (1.6.2-1puppet1) hardy lucid maverick natty oneiric unstable lenny sid squeeze wheezy; urgency=low + + * Initial packaging for facter 1.6.2 + + -- Matthaus Litteken Fri, 21 Oct 2011 11:00:48 -0700 diff --git a/ext/debian/compat b/ext/debian/compat new file mode 100644 index 0000000000..7ed6ff82de --- /dev/null +++ b/ext/debian/compat @@ -0,0 +1 @@ +5 diff --git a/ext/debian/control b/ext/debian/control new file mode 100644 index 0000000000..37edfdd60e --- /dev/null +++ b/ext/debian/control @@ -0,0 +1,14 @@ +Source: facter +Section: ruby +Priority: optional +Maintainer: Puppet Labs +Build-Depends: cdbs, debhelper (>> 7), ruby1.8 | ruby1.9.1, libopenssl-ruby1.8 | libopenssl-ruby1.9.1 +Standards-Version: 3.9.1 +Homepage: http://www.puppetlabs.com + +Package: facter +Architecture: all +Depends: ${shlibs:Depends}, ${misc:Depends}, ruby1.8 (>= 1.8.5) | ruby1.9.1, libopenssl-ruby1.8 | libopenssl-ruby1.9.1, dmidecode, pciutils +Description: Ruby module for collecting simple facts about a host operating system + Some of the facts are preconfigured, such as the hostname and the operating + system. Additional facts can be added through simple Ruby scripts. diff --git a/ext/debian/copyright b/ext/debian/copyright new file mode 100644 index 0000000000..16cd087fc4 --- /dev/null +++ b/ext/debian/copyright @@ -0,0 +1,19 @@ +This package was debianized by Jacob Helwig on +Sat, 11 Dec 2010 21:11:11 -0800 + +It was downloaded from http://www.puppetlabs.com/ + +Upstream Author: + + Puppet Labs + +Copyright: + + Copyright 2010-2011 Puppet Labs + +License: ASL-2 + http://www.apache.org/licenses/LICENSE-2.0 + + On a Debian system, the license can be found at + /usr/share/common-licenses/Apache-2.0 . + diff --git a/ext/debian/docs b/ext/debian/docs new file mode 100644 index 0000000000..ad253d4753 --- /dev/null +++ b/ext/debian/docs @@ -0,0 +1,2 @@ +CHANGELOG +README.md diff --git a/ext/debian/lintian-overrides b/ext/debian/lintian-overrides new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ext/debian/rules b/ext/debian/rules new file mode 100755 index 0000000000..609b5ef256 --- /dev/null +++ b/ext/debian/rules @@ -0,0 +1,9 @@ +#!/usr/bin/make -f + +include /usr/share/cdbs/1/rules/debhelper.mk +include /usr/share/cdbs/1/rules/buildcore.mk + +LIBDIR=$(shell /usr/bin/ruby1.8 -rrbconfig -e 'puts Config::CONFIG["rubylibdir"]') + +binary-install/facter:: + /usr/bin/ruby1.8 install.rb --sitelibdir=$(LIBDIR) --destdir=$(CURDIR)/debian/$(cdbs_curpkg) --quick --no-rdoc From 8c18e3326e3f394907138fb993f21ace1f97d840 Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Sun, 29 Jul 2012 11:33:35 -0700 Subject: [PATCH 0968/3753] Move facter redhat spec file to erb This commit moves the facter.spec file to and erb file that generates a facter.spec file with appropriate versions etc using rake tasks. It also removes a lingering git merge conflict marker from a previous merge. Signed-off-by: Moses Mendoza Conflicts: ext/redhat/facter.spec.erb --- ext/redhat/{facter.spec => facter.spec.erb} | 55 ++++++++++----------- 1 file changed, 27 insertions(+), 28 deletions(-) rename ext/redhat/{facter.spec => facter.spec.erb} (82%) diff --git a/ext/redhat/facter.spec b/ext/redhat/facter.spec.erb similarity index 82% rename from ext/redhat/facter.spec rename to ext/redhat/facter.spec.erb index ddfc562387..7f32fbe4bd 100644 --- a/ext/redhat/facter.spec +++ b/ext/redhat/facter.spec.erb @@ -1,30 +1,27 @@ -# Fedora 17 ships with ruby 1.9, which uses vendorlibdir instead -# of sitelibdir -%if 0%{?fedora} >= 17 -%global facter_libdir %(ruby -rrbconfig -e 'puts RbConfig::CONFIG["vendorlibdir"]') -%else -%global facter_libdir %(ruby -rrbconfig -e 'puts Object.const_get(defined?(RbConfig) ? :RbConfig : :Config)::CONFIG["sitelibdir"]') -%endif +%{!?ruby_sitelibdir: %define ruby_sitelibdir %(ruby -rrbconfig -e 'puts Object.const_get(defined?(RbConfig) ? :RbConfig : :Config)::CONFIG["sitelibdir"]')} + + +%global _ver 2.0.0 +%global confdir ext/redhat +# VERSION is subbed out during rake srpm process +# %global realversion <%= @version %> +# %global rpmversion <%= @rpmversion %> -Summary: Ruby module for collecting simple facts about a host operating system -Name: facter -Version: 1.6.11 -Release: 2%{?dist} -#Release: 0.1rc1%{?dist} -Epoch: 1 -Vendor: %{?_host_vendor} -License: Apache 2.0 -Group: System Environment/Base -URL: http://www.puppetlabs.com/puppet/related-projects/%{name} -#Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc1.tar.gz -Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}.tar.gz -#Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}rc1.tar.gz.asc -Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{version}.tar.gz.asc - -BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) - -Requires: ruby >= 1.8.5 -Requires: which +Summary: Ruby module for collecting simple facts about a host operating system +Name: facter +Version: %{rpmversion} +Release: <%= @release -%>%{?dist} + +License: Apache 2.0 +Group: System Environment/Base +URL: http://www.puppetlabs.com/puppet/related-projects/%{name} +Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{realversion}.tar.gz +Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{realversion}.tar.gz.asc + +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) + +Requires: ruby >= 1.8.5 +Requires: which # dmidecode and pciutils are not available on all arches %ifarch %ix86 x86_64 ia64 Requires: dmidecode @@ -46,8 +43,7 @@ system. Some of the facts are preconfigured, such as the hostname and the operating system. Additional facts can be added through simple Ruby scripts %prep -%setup -q -n %{name}-%{version} -#%setup -q -n %{name}-%{version}rc1 +%setup -q -n %{name}-%{realversion} %build @@ -69,6 +65,9 @@ rm -rf %{buildroot} %changelog +* <%= Time.now.strftime("%a %b %d %Y") %> Puppet Labs Release - <%= @rpmversion %>-<%= @release %> +- Build for <%= @version %> + * Wed Aug 08 2012 Moses Mendoza - 1.6.11-2 - Use correct ruby libdir for fedora 17 / ruby 1.9 From 900895fcc1db07df27c40d813f2ca67f60da2865 Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Mon, 20 Aug 2012 18:02:19 -0700 Subject: [PATCH 0969/3753] Group requires together This commit just groups the requires in the Rakefile together for readability as well as removes the packaging related tasks that will be handled by the packagin repo. Signed-off-by: Moses Mendoza --- Rakefile | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Rakefile b/Rakefile index 1f79102b3e..7c005592e6 100644 --- a/Rakefile +++ b/Rakefile @@ -9,6 +9,8 @@ $LOAD_PATH << File.join(File.dirname(__FILE__), 'tasks') require 'rubygems' require 'rspec' require 'rspec/core/rake_task' +require 'rake' + begin require 'rcov' rescue LoadError @@ -16,9 +18,6 @@ end Dir['tasks/**/*.rake'].each { |t| load t } -require 'rake' -require 'rake/packagetask' -require 'rubygems/package_task' FILES = FileList[ '[A-Z]*', From fe311c2380360eeb7d3d1fe3a00fa9405e68eb42 Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Mon, 20 Aug 2012 18:18:26 -0700 Subject: [PATCH 0970/3753] Remove obsolete tasks directory This commit removes the tasks directory and its contents, as the packaging-related tasks are to be handled by the packagin repo, and the other task are not, as far as I can tell, used anymore. Signed-off-by: Moses Mendoza --- tasks/apple.rake | 166 ---------------------------------------- tasks/changlog.rake | 15 ---- tasks/ci.rake | 16 ---- tasks/dailybuild.rake | 8 -- tasks/mail_patches.rake | 48 ------------ tasks/metrics.rake | 6 -- tasks/sign.rake | 14 ---- 7 files changed, 273 deletions(-) delete mode 100644 tasks/apple.rake delete mode 100644 tasks/changlog.rake delete mode 100644 tasks/ci.rake delete mode 100644 tasks/dailybuild.rake delete mode 100644 tasks/mail_patches.rake delete mode 100644 tasks/metrics.rake delete mode 100644 tasks/sign.rake diff --git a/tasks/apple.rake b/tasks/apple.rake deleted file mode 100644 index 299560b182..0000000000 --- a/tasks/apple.rake +++ /dev/null @@ -1,166 +0,0 @@ -# Title: Rake task to build Apple packages for Facter. -# Author: Gary Larizza -# Date: 12/5/2011 -# Description: This task will create a DMG-encapsulated package that will -# install Facter on OS X systems. This happens by building -# a directory tree of files that will then be fed to the -# packagemaker binary (can be installed by installing the -# XCode Tools) which will create the .pkg file. -# -require 'fileutils' -require 'erb' -require 'find' -require 'pathname' - -# Path to Binaries (Constants) -TAR = '/usr/bin/tar' -CP = '/bin/cp' -INSTALL = '/usr/bin/install' -DITTO = '/usr/bin/ditto' -PACKAGEMAKER = '/Developer/usr/bin/packagemaker' -SED = '/usr/bin/sed' - -# Setup task to populate all the variables -task :setup do - @version = `git describe`.chomp - @title = "facter-#{@version}" - @reverse_domain = 'com.puppetlabs.facter' - @package_major_version = @version.split('.')[0] - @package_minor_version = @version.split('.')[1] + - @version.split('.')[2].split('-')[0].split('rc')[0] - @pm_restart = 'None' - @build_date = Time.new.strftime("%Y-%m-%dT%H:%M:%SZ") -end - -# method: make_directory_tree -# description: This method sets up the directory structure that packagemaker -# needs to build a package. A prototype.plist file (holding -# package-specific options) is built from an ERB template located -# in the ext/osx/templates directory. -def make_directory_tree - facter_tmp = '/tmp/facter' - @scratch = "#{facter_tmp}/#{@title}" - @working_tree = { - 'scripts' => "#{@scratch}/scripts", - 'resources' => "#{@scratch}/resources", - 'working' => "#{@scratch}/root", - 'payload' => "#{@scratch}/payload", - } - puts "Cleaning Tree: #{facter_tmp}" - FileUtils.rm_rf(facter_tmp) - @working_tree.each do |key,val| - puts "Creating: #{val}" - FileUtils.mkdir_p(val) - end - File.open("#{@scratch}/#{'prototype.plist'}", "w+") do |f| - f.write(ERB.new(File.read('ext/osx/templates/prototype.plist.erb')).result()) - end -end - -# method: build_dmg -# description: This method builds a package from the directory structure in -# /tmp/facter and puts it in the -# /tmp/facter/facter-#{version}/payload directory. A DMG is -# created, using hdiutil, based on the contents of the -# /tmp/facter/facter-#{version}/payload directory. The resultant -# DMG is placed in the pkg/apple directory. -# -def build_dmg - # Local Variables - dmg_format_code = 'UDZO' - zlib_level = '9' - dmg_format_option = "-imagekey zlib-level=#{zlib_level}" - dmg_format = "#{dmg_format_code} #{dmg_format_option}" - dmg_file = "#{@title}.dmg" - package_file = "#{@title}.pkg" - pm_extra_args = '--verbose --no-recommend --no-relocate' - package_target_os = '10.4' - - # Build .pkg file - system("sudo #{PACKAGEMAKER} --root #{@working_tree['working']} \ - --id #{@reverse_domain} \ - --filter DS_Store \ - --target #{package_target_os} \ - --title #{@title} \ - --info #{@scratch}/prototype.plist \ - --scripts #{@working_tree['scripts']} \ - --resources #{@working_tree['resources']} \ - --version #{@version} \ - #{pm_extra_args} --out #{@working_tree['payload']}/#{package_file}") - - # Build .dmg file - system("sudo hdiutil create -volname #{@title} \ - -srcfolder #{@working_tree['payload']} \ - -uid 99 \ - -gid 99 \ - -ov \ - -format #{dmg_format} \ - #{dmg_file}") - - if File.directory?("#{Pathname.pwd}/pkg/apple") - FileUtils.mv("#{Pathname.pwd}/#{dmg_file}", "#{Pathname.pwd}/pkg/apple/#{dmg_file}") - puts "moved: #{dmg_file} has been moved to #{Pathname.pwd}/pkg/apple/#{dmg_file}" - else - FileUtils.mkdir_p("#{Pathname.pwd}/pkg/apple") - FileUtils.mv(dmg_file, "#{Pathname.pwd}/pkg/apple/#{dmg_file}") - puts "moved: #{dmg_file} has been moved to #{Pathname.pwd}/pkg/apple/#{dmg_file}" - end -end - -# method: pack_facter_source -# description: This method copies the facter source into a directory -# structure in /tmp/facter/facter-#{version}/root mirroring the -# structure on the target system for which the package will be -# installed. Anything installed into /tmp/facter/root will be -# installed as the package's payload. -# -def pack_facter_source - work = "#{@working_tree['working']}" - facter_source = Pathname.pwd - - # Make all necessary directories - directories = ["#{work}/usr/bin", - "#{work}/usr/share/doc/facter", - "#{work}/usr/lib/ruby/site_ruby/1.8/facter"] - FileUtils.mkdir_p(directories) - - # Install necessary files - system("#{DITTO} #{facter_source}/bin/ #{work}/usr/bin") - system("#{DITTO} #{facter_source}/lib/ #{work}/usr/lib/ruby/site_ruby/1.8/") - - # Setup a preflight script and replace variables in the files with - # the correct paths. - system("#{INSTALL} -o root -g wheel -m 644 #{facter_source}/ext/osx/preflight #{@working_tree['scripts']}") - system("#{SED} -i '' \"s\#{SITELIBDIR}\#/usr/lib/ruby/site_ruby/1.8\#g\" #{@working_tree['scripts']}/preflight") - system("#{SED} -i '' \"s\#{BINDIR}\#/usr/bin\#g\" #{@working_tree['scripts']}/preflight") - - # Install documentation (matching for files with capital letters) - Dir.foreach("#{facter_source}") do |file| - system("#{INSTALL} -o root -g wheel -m 644 #{facter_source}/#{file} #{work}/usr/share/doc/facter") if file =~ /^[A-Z][A-Z]/ - end - - # Set Permissions - executable_directories = [ "#{work}/usr/bin", ] - FileUtils.chmod_R(0755, executable_directories) - FileUtils.chown_R('root', 'wheel', directories) - FileUtils.chmod_R(0644, "#{work}/usr/lib/ruby/site_ruby/1.8/") - FileUtils.chown_R('root', 'wheel', "#{work}/usr/lib/ruby/site_ruby/1.8/") - Find.find("#{work}/usr/lib/ruby/site_ruby/1.8/") do |dir| - FileUtils.chmod(0755, dir) if File.directory?(dir) - end -end - -namespace :package do - desc "Task for building an Apple Package" - task :apple => [:setup] do - # Test for Root and Packagemaker binary - raise "Please run rake as root to build Apple Packages" unless Process.uid == 0 - raise "Packagemaker must be installed. Please install XCode Tools" unless \ - File.exists?('/Developer/usr/bin/packagemaker') - - make_directory_tree - pack_facter_source - build_dmg - FileUtils.chmod_R(0775, "#{Pathname.pwd}/pkg") - end -end diff --git a/tasks/changlog.rake b/tasks/changlog.rake deleted file mode 100644 index 8622c1c3d6..0000000000 --- a/tasks/changlog.rake +++ /dev/null @@ -1,15 +0,0 @@ -desc "Create a ChangeLog based on git commits." -task :changelog do - begin - gitc = %x{which git-changelog} - rescue - puts "This task needs the git-changelog binary - http://github.com/ReinH/git-changelog" - end - - CHANGELOG_DIR = "#{Dir.pwd}" - mkdir(CHANGELOG_DIR) unless File.directory?(CHANGELOG_DIR) - change_body = `git-changelog --limit=99999` - File.open(File.join(CHANGELOG_DIR, "CHANGELOG"), 'w') do |f| - f << change_body - end -end diff --git a/tasks/ci.rake b/tasks/ci.rake deleted file mode 100644 index a2b395e507..0000000000 --- a/tasks/ci.rake +++ /dev/null @@ -1,16 +0,0 @@ -desc "Prep CI RSpec tests" -task :ci_prep do - require 'rubygems' - begin - gem 'ci_reporter' - require 'ci/reporter/rake/rspec' - require 'ci/reporter/rake/test_unit' - ENV['CI_REPORTS'] = 'results' - rescue LoadError - puts 'Missing ci_reporter gem. You must have the ci_reporter gem installed to run the CI spec tests' - end -end - -desc "Run the CI RSpec tests" -task :ci_spec => [:ci_prep, 'ci:setup:rspec', :spec] do -end diff --git a/tasks/dailybuild.rake b/tasks/dailybuild.rake deleted file mode 100644 index 76ce4e2e27..0000000000 --- a/tasks/dailybuild.rake +++ /dev/null @@ -1,8 +0,0 @@ -desc "Create a Facter daily build" -task :daily => :changelog do - version = "facter" + "-" + Time.now.localtime.strftime("%Y%m%d") - sh "git archive --format=tar --prefix=#{version}/ HEAD^{tree} >#{version}.tar" - sh "pax -waf #{version}.tar -s ':^:#{version}/:' ChangeLog" - sh "rm ChangeLog" - sh "gzip -f -9 #{version}.tar" -end diff --git a/tasks/mail_patches.rake b/tasks/mail_patches.rake deleted file mode 100644 index 7a9f2ea29f..0000000000 --- a/tasks/mail_patches.rake +++ /dev/null @@ -1,48 +0,0 @@ -desc "Send patch information to the puppet-dev list" -task :mail_patches do - if Dir.glob("00*.patch").length > 0 - raise "Patches already exist matching '00*.patch'; clean up first" - end - - unless %x{git status} =~ /On branch (.+)/ - raise "Could not get branch from 'git status'" - end - branch = $1 - - unless branch =~ %r{^([^\/]+)/([^\/]+)/([^\/]+)$} - raise "Branch name does not follow // model; cannot autodetect parent branch" - end - - type, parent, name = $1, $2, $3 - - # Create all of the patches - sh "git format-patch -C -M -s -n --subject-prefix='PATCH/facter' #{parent}..HEAD" - - # Add info to the patches - additional_info = "Local-branch: #{branch}\n" - files = Dir.glob("00*.patch") - files.each do |file| - contents = File.read(file) - contents.sub!(/^---\n/, "---\n#{additional_info}") - File.open(file, 'w') do |file_handle| - file_handle.print contents - end - end - - # And then mail them out. - - # If we've got more than one patch, add --compose - if files.length > 1 - compose = "--compose" - subject = %Q{--subject "#{type} #{name} against #{parent}"} - else - compose = "" - subject = "" - end - - # Now send the mail. - sh "git send-email #{compose} #{subject} --no-signed-off-by-cc --suppress-from --to puppet-dev@googlegroups.com 00*.patch" - - # Finally, clean up the patches - sh "rm 00*.patch" -end diff --git a/tasks/metrics.rake b/tasks/metrics.rake deleted file mode 100644 index 214cc996e9..0000000000 --- a/tasks/metrics.rake +++ /dev/null @@ -1,6 +0,0 @@ -begin - require 'metric_fu' -rescue LoadError - # Metric-fu not installed - # http://metric-fu.rubyforge.org/ -end diff --git a/tasks/sign.rake b/tasks/sign.rake deleted file mode 100644 index fabb0e1165..0000000000 --- a/tasks/sign.rake +++ /dev/null @@ -1,14 +0,0 @@ -desc "Sign the package with the Puppet Labs release key" -task :sign_packages do - - version = Facter.version - - # Sign package - - sh "gpg --homedir $HOME/pl_release_key --detach-sign --output pkg/facter-#{version}.tar.gz.sign --armor pkg/facter-#{version}.tar.gz" - - # Sign gem - - sh "gpg --homedir $HOME/pl_release_key --detach-sign --output pkg/facter-#{version}.gem.sign --armor pkg/facter-#{version}.gem" - -end From 67525304c273603fb50a7aba4f042262e5b932c1 Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Mon, 20 Aug 2012 18:27:08 -0700 Subject: [PATCH 0971/3753] Shift to using packaging repo This commit adds build_defaults.yaml and project_defaults.yaml files and moves packing data into these for use with the packaging repo. It removes packaging-related tasks from the main Rakefile as well, these are to be handled to the packaging repo. Signed-off-by: Moses Mendoza Conflicts: Rakefile --- Rakefile | 93 +++++++++++------------------------------ ext/build_defaults.yaml | 20 +++++++++ ext/project_data.yaml | 15 +++++++ 3 files changed, 60 insertions(+), 68 deletions(-) create mode 100644 ext/build_defaults.yaml create mode 100644 ext/project_data.yaml diff --git a/Rakefile b/Rakefile index 7c005592e6..4e82d84540 100644 --- a/Rakefile +++ b/Rakefile @@ -10,6 +10,7 @@ require 'rubygems' require 'rspec' require 'rspec/core/rake_task' require 'rake' +require 'yaml' begin require 'rcov' @@ -17,88 +18,44 @@ rescue LoadError end Dir['tasks/**/*.rake'].each { |t| load t } +Dir['ext/packaging/tasks/**/*'].sort.each { |t| load t } - -FILES = FileList[ - '[A-Z]*', - 'install.rb', - 'bin/**/*', - 'lib/**/*', - 'ext/**/*', - 'etc/**/*', - 'spec/**/*' -] - -def get_version - `git describe`.strip +begin + @build_defaults ||= YAML.load_file('ext/build_defaults.yaml') + @packaging_url = @build_defaults['packaging_url'] + @packaging_repo = @build_defaults['packaging_repo'] +rescue + STDERR.puts "Unable to read the packaging repo info from ext/build_defaults.yaml" end -# :build_environment and :tar are mostly borrowed from puppet-dashboard Rakefile -task :build_environment do - unless ENV['FORCE'] == '1' - modified = `git status --porcelain | sed -e '/^\?/d'` - if modified.split(/\n/).length != 0 - puts <<-HERE -!! ERROR: Your git working directory is not clean. You must -!! remove or commit your changes before you can create a package: - -#{`git status | grep '^#'`.chomp} - -!! To override this check, set FORCE=1 -- e.g. `rake package:deb FORCE=1` - HERE - raise +namespace :package do + desc "Bootstrap packaging automation, e.g. clone into packaging repo" + task :bootstrap do + if File.exist?("ext/#{@packaging_repo}") + puts "It looks like you already have ext/#{@packaging_repo}. If you don't like it, blow it away with package:implode." + else + cd 'ext' do + %x{git clone #{@packaging_url}} + end end end -end -desc "Create a release .tar.gz" -task :tar => :build_environment do - name = "facter" - rm_rf 'pkg/tar' - temp=`mktemp -d -t tmpXXXXXX`.strip! - version = get_version - base = "#{temp}/#{name}-#{version}/" - mkdir_p base - sh "git checkout-index -af --prefix=#{base}" - mkdir_p "pkg/tar" - sh "tar -C #{temp} -pczf #{temp}/#{name}-#{version}.tar.gz #{name}-#{version}" - mv "#{temp}/#{name}-#{version}.tar.gz", "pkg/tar" - rm_rf temp - puts "Tarball is pkg/tar/#{name}-#{version}.tar.gz" -end - -spec = Gem::Specification.new do |spec| - spec.platform = Gem::Platform::RUBY - spec.name = 'facter' - spec.files = FILES.to_a - spec.executables = %w{facter} - spec.version = get_version.split('-')[0] - spec.summary = 'Facter, a system inventory tool' - spec.description = 'You can prove anything with facts!' - spec.author = 'Puppet Labs' - spec.email = 'info@puppetlabs.com' - spec.homepage = 'http://puppetlabs.com/' - spec.rubyforge_project = 'facter' - spec.rdoc_options << - '--title' << 'Facter - System Inventory Tool' << - '--main' << 'README' << - '--line-numbers' -end -Gem::PackageTask.new(spec) do |pkg| + desc "Remove all cloned packaging automation" + task :implode do + rm_rf "ext/#{@packaging_repo}" + end end -task :package => :tar - task :default do sh %{rake -T} end # Aliases for spec -task :test => [:spec] -task :tests => [:spec] -task :specs => [:spec] +task :tests => [:test] +task :specs => [:test] -RSpec::Core::RakeTask.new do |t| +desc "Run all specs" +RSpec::Core::RakeTask.new(:test) do |t| t.pattern ='spec/{unit,integration}/**/*_spec.rb' t.fail_on_error = true end diff --git a/ext/build_defaults.yaml b/ext/build_defaults.yaml new file mode 100644 index 0000000000..1563aeee29 --- /dev/null +++ b/ext/build_defaults.yaml @@ -0,0 +1,20 @@ +--- +packaging_url: 'git@github.com:puppetlabs/packaging --branch=master' +packaging_repo: 'packaging' +default_cow: 'base-squeeze-i386.cow' +cows: 'base-lucid-amd64.cow base-lucid-i386.cow base-natty-amd64.cow base-natty-i386.cow base-oneiric-amd64.cow base-oneiric-i386.cow base-precise-amd64.cow base-precise-i386.cow base-sid-amd64.cow base-sid-i386.cow base-squeeze-amd64.cow base-squeeze-i386.cow base-testing-amd64.cow base-testing-i386.cow base-wheezy-i386.cow' +pbuild_conf: '/etc/pbuilderrc' +packager: 'puppetlabs' +gpg_name: 'info@puppetlabs.com' +gpg_key: '4BD6EC30' +sign_tar: TRUE +# a space separated list of mock configs +final_mocks: 'pl-5-i386 pl-5-x86_64 pl-6-i386 pl-6-x86_64 fedora-15-i386 fedora-15-x86_64 fedora-16-i386 fedora-16-x86_64 fedora-17-i386 fedora-17-x86_64' +rc_mocks: 'pl-5-i386-dev pl-5-x86_64-dev pl-6-i386-dev pl-6-x86_64-dev fedora-15-i386-dev fedora-15-x86_64-dev fedora-16-i386-dev fedora-16-x86_64-dev fedora-17-i386-dev fedora-17-x86_64-dev' +yum_host: 'burji.puppetlabs.com' +yum_repo_path: '~/repo/' +build_gem: TRUE +build_dmg: TRUE +apt_host: 'burji.puppetlabs.com' +apt_repo_url: 'http://apt.puppetlabs.com/' +apt_repo_path: '/opt/repository/incoming' diff --git a/ext/project_data.yaml b/ext/project_data.yaml new file mode 100644 index 0000000000..65d59bcaa5 --- /dev/null +++ b/ext/project_data.yaml @@ -0,0 +1,15 @@ +--- +project: 'facter' +author: 'Puppet Labs' +email: 'info@puppetlabs.com' +homepage: 'https://github.com/puppetlabs/facter' +summary: 'Facter, a system inventory tool' +description: 'You can prove anything with facts!' +version_file: 'lib/facter/version.rb' +# files and gem_files are space separated lists +files: '[A-Z]* acceptance bin documentation etc ext install.rb lib libexec man spec' +gem_files: '[A-Z]* install.rb bin etc ext lib spec' +gem_require_path: 'lib' +gem_test_files: 'spec/**/*' +gem_executables: 'facter' +gem_default_executables: 'facter' From 6659e61c749dd33969dc6069fd5d78eae94841c4 Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Mon, 20 Aug 2012 20:26:28 -0700 Subject: [PATCH 0972/3753] Fixup redhat spec erb template for f17 This commit fixes up the redhat facter.spec file, adding in some missing fedora 17 changes that were probably lost in a merge-up. The os-dependent definition of facter_libdir is replaced, and the mandir file-listing is added. It also removes an errant conflict marker. Signed-off-by: Moses Mendoza --- ext/redhat/facter.spec.erb | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/ext/redhat/facter.spec.erb b/ext/redhat/facter.spec.erb index 7f32fbe4bd..9f56f1473e 100644 --- a/ext/redhat/facter.spec.erb +++ b/ext/redhat/facter.spec.erb @@ -1,8 +1,11 @@ -%{!?ruby_sitelibdir: %define ruby_sitelibdir %(ruby -rrbconfig -e 'puts Object.const_get(defined?(RbConfig) ? :RbConfig : :Config)::CONFIG["sitelibdir"]')} - +# Fedora 17 ships with ruby 1.9, which uses vendorlibdir instead +# of sitelibdir +%if 0%{?fedora} >= 17 +%global facter_libdir %(ruby -rrbconfig -e 'puts RbConfig::CONFIG["vendorlibdir"]') +%else +%global facter_libdir %(ruby -rrbconfig -e 'puts Object.const_get(defined?(RbConfig) ? :RbConfig : :Config)::CONFIG["sitelibdir"]') +%endif -%global _ver 2.0.0 -%global confdir ext/redhat # VERSION is subbed out during rake srpm process # %global realversion <%= @version %> # %global rpmversion <%= @rpmversion %> From 1e7f5b35236fa3729ff576e3666b4ecb789588cc Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Tue, 21 Aug 2012 11:48:31 -0700 Subject: [PATCH 0973/3753] Update debhelper compat to 7, add format --- ext/debian/compat | 2 +- ext/debian/source/format | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 ext/debian/source/format diff --git a/ext/debian/compat b/ext/debian/compat index 7ed6ff82de..7f8f011eb7 100644 --- a/ext/debian/compat +++ b/ext/debian/compat @@ -1 +1 @@ -5 +7 diff --git a/ext/debian/source/format b/ext/debian/source/format new file mode 100644 index 0000000000..163aaf8d82 --- /dev/null +++ b/ext/debian/source/format @@ -0,0 +1 @@ +3.0 (quilt) From e0454dfee13975aba498f2851abe6e0eb2627406 Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Wed, 22 Aug 2012 14:43:34 -0700 Subject: [PATCH 0974/3753] Remove libexec from file list as its only in 2.x --- ext/project_data.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/project_data.yaml b/ext/project_data.yaml index 65d59bcaa5..1d00f4ea0b 100644 --- a/ext/project_data.yaml +++ b/ext/project_data.yaml @@ -7,7 +7,7 @@ summary: 'Facter, a system inventory tool' description: 'You can prove anything with facts!' version_file: 'lib/facter/version.rb' # files and gem_files are space separated lists -files: '[A-Z]* acceptance bin documentation etc ext install.rb lib libexec man spec' +files: '[A-Z]* acceptance bin documentation etc ext install.rb lib man spec' gem_files: '[A-Z]* install.rb bin etc ext lib spec' gem_require_path: 'lib' gem_test_files: 'spec/**/*' From dd3401efab75e645ae824c38d289ceaf045285da Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Thu, 23 Aug 2012 14:36:17 -0700 Subject: [PATCH 0975/3753] Fixup apple packaging --- ext/osx/file_mapping.yaml | 27 +++++++++++++++++++ ext/osx/preflight | 16 ----------- ext/osx/preflight.erb | 30 +++++++++++++++++++++ ext/osx/{templates => }/prototype.plist.erb | 4 +-- 4 files changed, 59 insertions(+), 18 deletions(-) create mode 100644 ext/osx/file_mapping.yaml delete mode 100755 ext/osx/preflight create mode 100755 ext/osx/preflight.erb rename ext/osx/{templates => }/prototype.plist.erb (96%) diff --git a/ext/osx/file_mapping.yaml b/ext/osx/file_mapping.yaml new file mode 100644 index 0000000000..6febd13787 --- /dev/null +++ b/ext/osx/file_mapping.yaml @@ -0,0 +1,27 @@ +directories: + lib: + path: 'usr/lib/ruby/site_ruby/1.8' + owner: 'root' + group: 'wheel' + perms: '0644' + bin: + path: 'usr/bin' + owner: 'root' + group: 'wheel' + perms: '0755' + facter: + path: 'var/lib/facter' + owner: 'root' + group: 'wheel' + perms: '0644' + etc: + path: 'etc' + owner: 'root' + group: 'wheel' + perms: '0644' +files: + '[A-Z]*': + path: 'usr/share/doc/facter' + owner: 'root' + group: 'wheel' + perms: '0644' diff --git a/ext/osx/preflight b/ext/osx/preflight deleted file mode 100755 index 8066bf47d2..0000000000 --- a/ext/osx/preflight +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash -# -# Make sure that old facter cruft is removed -# This also allows us to downgrade facter as -# it's more likely that installing old versions -# over new will cause issues. -# -# ${3} is the destination volume so that this works correctly -# when being installed to volumes other than the current OS. - -/bin/rm -Rf "${3}{SITELIBDIR}/facter" -/bin/rm -Rf "${3}{SITELIBDIR}/facter.rb" - -# remove old doc files - -/bin/rm -Rf "${3}/usr/share/doc/facter" \ No newline at end of file diff --git a/ext/osx/preflight.erb b/ext/osx/preflight.erb new file mode 100755 index 0000000000..a6c1856c24 --- /dev/null +++ b/ext/osx/preflight.erb @@ -0,0 +1,30 @@ +#!/bin/bash +# +# Make sure that old facter cruft is removed +# This also allows us to downgrade facter as +# it's more likely that installing old versions +# over new will cause issues. +# +# ${3} is the destination volume so that this works correctly +# when being installed to volumes other than the current OS. + +<% begin %> +<% require 'rubygems' %> +<% rescue LoadError %> +<% end %> +<% require 'rake' %> + +# remove libdir +<% Dir.chdir("lib") %> +<% FileList["**/*"].select {|i| File.file?(i)}.each do |file| %> +/bin/rm -Rf "${3}{SITELIBDIR}/<%=file%>" +<% end %> +# remove bin files +<% Dir.chdir("../bin") %> +<% FileList["**/*"].select {|i| File.file?(i)}.each do |file| %> +/bin/rm -Rf "${3}{BINDIR}/<%=file%>" +<% end %> +<% Dir.chdir("..") %> + +# remove old doc files +/bin/rm -Rf "${3}/usr/share/doc/<%=@package_name%>" diff --git a/ext/osx/templates/prototype.plist.erb b/ext/osx/prototype.plist.erb similarity index 96% rename from ext/osx/templates/prototype.plist.erb rename to ext/osx/prototype.plist.erb index e56659a6bc..062a0371e7 100644 --- a/ext/osx/templates/prototype.plist.erb +++ b/ext/osx/prototype.plist.erb @@ -1,6 +1,6 @@ - + CFBundleIdentifier <%= @title %> @@ -35,4 +35,4 @@ IFPkgFlagUpdateInstalledLanguages - + From 601a96753110bd0791fb7053efa21b712d0b28f8 Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Fri, 24 Aug 2012 11:42:47 -0700 Subject: [PATCH 0976/3753] Use git read-only packaging repo for public access Signed-off-by: Moses Mendoza --- ext/build_defaults.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/build_defaults.yaml b/ext/build_defaults.yaml index 1563aeee29..ae65055fbb 100644 --- a/ext/build_defaults.yaml +++ b/ext/build_defaults.yaml @@ -1,5 +1,5 @@ --- -packaging_url: 'git@github.com:puppetlabs/packaging --branch=master' +packaging_url: 'git://github.com/puppetlabs/packaging.git --branch=master' packaging_repo: 'packaging' default_cow: 'base-squeeze-i386.cow' cows: 'base-lucid-amd64.cow base-lucid-i386.cow base-natty-amd64.cow base-natty-i386.cow base-oneiric-amd64.cow base-oneiric-i386.cow base-precise-amd64.cow base-precise-i386.cow base-sid-amd64.cow base-sid-i386.cow base-squeeze-amd64.cow base-squeeze-i386.cow base-testing-amd64.cow base-testing-i386.cow base-wheezy-i386.cow' From 1daadf0c16989c691d92b685d260911fcb1f24d8 Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Mon, 27 Aug 2012 13:48:11 -0700 Subject: [PATCH 0977/3753] Revert "Remove libexec from file list as its only in 2.x" The commit this reverts is a small packaging change that is only applicable to facter 1.6.x and does not apply to 2.x. It was carried up with the last merge-up. This reverts commit e0454dfee13975aba498f2851abe6e0eb2627406. --- ext/project_data.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/project_data.yaml b/ext/project_data.yaml index 1d00f4ea0b..65d59bcaa5 100644 --- a/ext/project_data.yaml +++ b/ext/project_data.yaml @@ -7,7 +7,7 @@ summary: 'Facter, a system inventory tool' description: 'You can prove anything with facts!' version_file: 'lib/facter/version.rb' # files and gem_files are space separated lists -files: '[A-Z]* acceptance bin documentation etc ext install.rb lib man spec' +files: '[A-Z]* acceptance bin documentation etc ext install.rb lib libexec man spec' gem_files: '[A-Z]* install.rb bin etc ext lib spec' gem_require_path: 'lib' gem_test_files: 'spec/**/*' From 903b1d9c87566c9b81d9540c09ebb2d5df07c130 Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Mon, 27 Aug 2012 16:24:21 -0700 Subject: [PATCH 0978/3753] Stop using sed to generate the preflight erb --- ext/osx/preflight.erb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/osx/preflight.erb b/ext/osx/preflight.erb index a6c1856c24..3cb271a8c0 100755 --- a/ext/osx/preflight.erb +++ b/ext/osx/preflight.erb @@ -17,14 +17,14 @@ # remove libdir <% Dir.chdir("lib") %> <% FileList["**/*"].select {|i| File.file?(i)}.each do |file| %> -/bin/rm -Rf "${3}{SITELIBDIR}/<%=file%>" +/bin/rm -Rf "${3}<%= @apple_libdir %>/<%=file%>" <% end %> # remove bin files <% Dir.chdir("../bin") %> <% FileList["**/*"].select {|i| File.file?(i)}.each do |file| %> -/bin/rm -Rf "${3}{BINDIR}/<%=file%>" +/bin/rm -Rf "${3}<%= @apple_bindir %>/<%=file%>" <% end %> <% Dir.chdir("..") %> # remove old doc files -/bin/rm -Rf "${3}/usr/share/doc/<%=@package_name%>" +/bin/rm -Rf "${3}<%= @apple_docdir %>/<%=@package_name%>" From fce4b0169958652b018611563ae1dd5bc281e88d Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Thu, 30 Aug 2012 12:37:01 -0700 Subject: [PATCH 0979/3753] fix redhat spec release template variable The rpm release variable changed in the packaging repo, this commit updates it. --- ext/redhat/facter.spec.erb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/redhat/facter.spec.erb b/ext/redhat/facter.spec.erb index 9f56f1473e..d4462b6d8a 100644 --- a/ext/redhat/facter.spec.erb +++ b/ext/redhat/facter.spec.erb @@ -13,7 +13,7 @@ Summary: Ruby module for collecting simple facts about a host operating system Name: facter Version: %{rpmversion} -Release: <%= @release -%>%{?dist} +Release: <%= @rpmrelease -%>%{?dist} License: Apache 2.0 Group: System Environment/Base @@ -68,7 +68,7 @@ rm -rf %{buildroot} %changelog -* <%= Time.now.strftime("%a %b %d %Y") %> Puppet Labs Release - <%= @rpmversion %>-<%= @release %> +* <%= Time.now.strftime("%a %b %d %Y") %> Puppet Labs Release - <%= @rpmversion %>-<%= @rpmrelease %> - Build for <%= @version %> * Wed Aug 08 2012 Moses Mendoza - 1.6.11-2 From 3ad05f14969954cf5b047b799edc06796e05e811 Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Thu, 30 Aug 2012 14:42:45 -0700 Subject: [PATCH 0980/3753] Remove version test from facter --- spec/unit/facter_spec.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/spec/unit/facter_spec.rb b/spec/unit/facter_spec.rb index a91c896a40..81edbc0410 100755 --- a/spec/unit/facter_spec.rb +++ b/spec/unit/facter_spec.rb @@ -3,11 +3,6 @@ require 'spec_helper' describe Facter do - - it "should have a version" do - Facter.version.should =~ /^[0-9]+(\.[0-9]+)*$/ - end - it "should have a method for returning its collection" do Facter.should respond_to(:collection) end From bc034689a35aac795721daaacef19bcc5a679560 Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Thu, 30 Aug 2012 15:49:33 -0700 Subject: [PATCH 0981/3753] Update CHANGELOG, lib/facter/version.rb for 1.6.12-rc1 --- CHANGELOG | 26 ++++++++++++++++++++++++++ lib/facter/version.rb | 2 +- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 334eb49cea..17984e8743 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,29 @@ +1.6.12rc1 +=== +3ad05f1 Remove version test from facter +fce4b01 fix redhat spec release template variable +903b1d9 Stop using sed to generate the preflight erb +601a967 Use git read-only packaging repo for public access +dd3401e Fixup apple packaging +e0454df Remove libexec from file list as its only in 2.x +1e7f5b3 Update debhelper compat to 7, add format +6659e61 Fixup redhat spec erb template for f17 +6752530 Shift to using packaging repo +fe311c2 Remove obsolete tasks directory +900895f Group requires together +8c18e33 Move facter redhat spec file to erb +84f8e10 Add debian build artifacts to facter project +d2d3baf Replace rake/gempackagetask with rubygems/gempackagetask +6f58b4e Move tasks out of 'rake' subdirectory +db9d154 Move packaging files to ext, rm conf +c0cbe62 (#15464) Make Facter.version settable via Facter.version= +0b49eae (#15464) Make contributing easy via bundle Gemfile +bf6ee4f Retabbed conf/redhat/facter.spec to lineup tag contents. +defbfb8 (#15291) Add Vendor tag to Facter spec file +17243bb Update facter redhat spec for fedora 17 +7ca9122 Update a facter build-requires for f17 +d5d2328 (#11640) Added support for new OpenStack MAC addresses + 1.6.11 === f75e46e Add build-requires of ruby-rdoc for manpage generation diff --git a/lib/facter/version.rb b/lib/facter/version.rb index 8cb199c628..ed24de533f 100644 --- a/lib/facter/version.rb +++ b/lib/facter/version.rb @@ -1,6 +1,6 @@ module Facter if not defined? FACTERVERSION then - FACTERVERSION = '1.6.11' + FACTERVERSION = '1.6.12-rc1' end def self.version From f6ae9567640252aca7a925f76547b35f050023d4 Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Thu, 30 Aug 2012 16:02:17 -0700 Subject: [PATCH 0982/3753] Merge branches '1.6rc' and '1.6.x' into 1.6.x From b95ea54d622cef790bccda8a5354ace679cc3372 Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Thu, 30 Aug 2012 16:32:33 -0700 Subject: [PATCH 0983/3753] fix yum repo path in yaml file --- ext/build_defaults.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/build_defaults.yaml b/ext/build_defaults.yaml index ae65055fbb..da17974b53 100644 --- a/ext/build_defaults.yaml +++ b/ext/build_defaults.yaml @@ -12,7 +12,7 @@ sign_tar: TRUE final_mocks: 'pl-5-i386 pl-5-x86_64 pl-6-i386 pl-6-x86_64 fedora-15-i386 fedora-15-x86_64 fedora-16-i386 fedora-16-x86_64 fedora-17-i386 fedora-17-x86_64' rc_mocks: 'pl-5-i386-dev pl-5-x86_64-dev pl-6-i386-dev pl-6-x86_64-dev fedora-15-i386-dev fedora-15-x86_64-dev fedora-16-i386-dev fedora-16-x86_64-dev fedora-17-i386-dev fedora-17-x86_64-dev' yum_host: 'burji.puppetlabs.com' -yum_repo_path: '~/repo/' +yum_repo_path: '/opt/repository/yum/' build_gem: TRUE build_dmg: TRUE apt_host: 'burji.puppetlabs.com' From c534126f45bab28ccc31880bf19d17744e9d819d Mon Sep 17 00:00:00 2001 From: Andrew Parker Date: Fri, 31 Aug 2012 14:52:58 -0700 Subject: [PATCH 0984/3753] (#10819) Avoid reading from /proc/self/mounts in ruby Reading from /proc/self/mounts in ruby can cause hangs in certain versions of the Linux kernel. The problem appears when a puppet agent is run with --listen, which hold open a socket, and then ruby reads from /proc/self/mounts. When this occurs ruby calls select on the open filehandles which triggers a bug in the kernel that causes the select to hang forever. This commit uses an exec of cat instead of ruby file reading operations, which avoids the ruby interpreter having to call select and trigger the bug. It appears that only /proc/self/mounts has this problem. Other areas of /proc were tested and did not cause the error. --- lib/facter/selinux.rb | 13 ++++++++----- spec/unit/selinux_spec.rb | 15 +++++---------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/lib/facter/selinux.rb b/lib/facter/selinux.rb index 8a21c0fd8e..4d8e801456 100644 --- a/lib/facter/selinux.rb +++ b/lib/facter/selinux.rb @@ -17,11 +17,14 @@ def selinux_mount_point path = "/selinux" if FileTest.exists?('/proc/self/mounts') - File.open('/proc/self/mounts') do |f| - f.grep(/selinuxfs/) do |line| - path = line.split[1] - break - end + # Centos 5 shows an error in which having ruby use File.read to read + # /proc/self/mounts combined with the puppet agent run with --listen causes + # a hang. Reading from other parts of /proc does not seem to cause this problem. + # The work around is to read the file in another process. + # -- andy Fri Aug 31 2012 + selinux_line = Facter::Util::Resolution.exec('cat /proc/self/mounts').lines.find { |line| line =~ /selinuxfs/ } + if selinux_line + path = selinux_line.split[1] end end path diff --git a/spec/unit/selinux_spec.rb b/spec/unit/selinux_spec.rb index 93599bc715..1fa7838925 100755 --- a/spec/unit/selinux_spec.rb +++ b/spec/unit/selinux_spec.rb @@ -26,12 +26,9 @@ it "and return true with selinuxfs path from /proc" do Facter.fact(:kernel).stubs(:value).returns("Linux") - mounts = mock() - lines = [ "selinuxfs /sys/fs/selinux selinuxfs rw,relatime 0 0" ] - mounts.expects(:grep).multiple_yields(*lines) - FileTest.expects(:exists?).with("/proc/self/mounts").returns true - File.expects(:open).with("/proc/self/mounts").yields(mounts) + lines = [ "selinuxfs /sys/fs/selinux selinuxfs rw,relatime 0 0" ] + Facter::Util::Resolution.expects(:exec).with("cat /proc/self/mounts").returns(lines.join("\n")) FileTest.expects(:exists?).with("/sys/fs/selinux/enforce").returns true @@ -44,15 +41,13 @@ it "and return true with multiple selinuxfs mounts from /proc" do Facter.fact(:kernel).stubs(:value).returns("Linux") - mounts = mock() + FileTest.expects(:exists?).with("/proc/self/mounts").returns true + lines = [ "selinuxfs /sys/fs/selinux selinuxfs rw,relatime 0 0", "selinuxfs /var/tmp/imgcreate-R2wmE6/install_root/sys/fs/selinux selinuxfs rw,relatime 0 0", ] - mounts.expects(:grep).multiple_yields(*lines) - - FileTest.expects(:exists?).with("/proc/self/mounts").returns true - File.expects(:open).with("/proc/self/mounts").yields(mounts) + Facter::Util::Resolution.expects(:exec).with("cat /proc/self/mounts").returns(lines.join("\n")) FileTest.expects(:exists?).with("/sys/fs/selinux/enforce").returns true From 398b111c887648121464cb455e5041ee4ac7bf82 Mon Sep 17 00:00:00 2001 From: Andrew Parker Date: Fri, 31 Aug 2012 14:54:27 -0700 Subject: [PATCH 0985/3753] (Maint) Extract common elements of selinux tests The selinux tests were full of duplicate code which made it hard at times to see what each individual test was doing. This commit pulls out several helper methods and creates a few links in the tests between the data that is fetched and later actions that are expected. --- spec/unit/selinux_spec.rb | 134 ++++++++++++++++++-------------------- 1 file changed, 63 insertions(+), 71 deletions(-) diff --git a/spec/unit/selinux_spec.rb b/spec/unit/selinux_spec.rb index 1fa7838925..2efab7d640 100755 --- a/spec/unit/selinux_spec.rb +++ b/spec/unit/selinux_spec.rb @@ -3,34 +3,28 @@ require 'spec_helper' describe "SELinux facts" do - - - after do - Facter.clear - end - describe "should detect if SELinux is enabled" do - it "and return true with default /selinux" do + before :each do Facter.fact(:kernel).stubs(:value).returns("Linux") + end - FileTest.stubs(:exists?).returns false - File.stubs(:read).with("/proc/self/attr/current").returns("notkernel") + it "and return true with default /selinux" do + mounts_does_not_exist - FileTest.expects(:exists?).with("/selinux/enforce").returns true + File.stubs(:read).with("/proc/self/attr/current").returns("notkernel") FileTest.expects(:exists?).with("/proc/self/attr/current").returns true File.expects(:read).with("/proc/self/attr/current").returns("kernel") + FileTest.expects(:exists?).with("/selinux/enforce").returns true + Facter.fact(:selinux).value.should == "true" end it "and return true with selinuxfs path from /proc" do - Facter.fact(:kernel).stubs(:value).returns("Linux") + selinux_root = "/sys/fs/selinux" + mounts_contains("selinuxfs #{selinux_root} selinuxfs rw,relatime 0 0") - FileTest.expects(:exists?).with("/proc/self/mounts").returns true - lines = [ "selinuxfs /sys/fs/selinux selinuxfs rw,relatime 0 0" ] - Facter::Util::Resolution.expects(:exec).with("cat /proc/self/mounts").returns(lines.join("\n")) - - FileTest.expects(:exists?).with("/sys/fs/selinux/enforce").returns true + FileTest.expects(:exists?).with("#{selinux_root}/enforce").returns true FileTest.expects(:exists?).with("/proc/self/attr/current").returns true File.expects(:read).with("/proc/self/attr/current").returns("kernel") @@ -39,17 +33,13 @@ end it "and return true with multiple selinuxfs mounts from /proc" do - Facter.fact(:kernel).stubs(:value).returns("Linux") + selinux_root = "/sys/fs/selinux" + mounts_contains( + "selinuxfs #{selinux_root} selinuxfs rw,relatime 0 0", + "selinuxfs /var/tmp/imgcreate-R2wmE6/install_root/sys/fs/selinux selinuxfs rw,relatime 0 0" + ) - FileTest.expects(:exists?).with("/proc/self/mounts").returns true - - lines = [ - "selinuxfs /sys/fs/selinux selinuxfs rw,relatime 0 0", - "selinuxfs /var/tmp/imgcreate-R2wmE6/install_root/sys/fs/selinux selinuxfs rw,relatime 0 0", - ] - Facter::Util::Resolution.expects(:exec).with("cat /proc/self/mounts").returns(lines.join("\n")) - - FileTest.expects(:exists?).with("/sys/fs/selinux/enforce").returns true + FileTest.expects(:exists?).with("#{selinux_root}/enforce").returns true FileTest.expects(:exists?).with("/proc/self/attr/current").returns true File.expects(:read).with("/proc/self/attr/current").returns("kernel") @@ -58,71 +48,73 @@ end end - it "should return true if SELinux policy enabled" do - Facter.fact(:selinux).stubs(:value).returns("true") - - FileTest.stubs(:exists?).returns false - File.stubs(:read).with("/selinux/enforce").returns("0") - - FileTest.expects(:exists?).with("/selinux/enforce").returns true - File.expects(:read).with("/selinux/enforce").returns("1") - - Facter.fact(:selinux_enforced).value.should == "true" - end + describe "when selinux is present" do + before :each do + Facter.fact(:selinux).stubs(:value).returns("true") + end - it "should return an SELinux policy version" do - Facter.fact(:selinux).stubs(:value).returns("true") - FileTest.stubs(:exists?).with("/proc/self/mounts").returns false + it "should return true if SELinux policy enabled" do + mounts_does_not_exist - File.expects(:read).with("/selinux/policyvers").returns("1") - FileTest.expects(:exists?).with("/selinux/policyvers").returns true + FileTest.expects(:exists?).with("/selinux/enforce").returns true + File.expects(:read).with("/selinux/enforce").returns("1") - Facter.fact(:selinux_policyversion).value.should == "1" - end + Facter.fact(:selinux_enforced).value.should == "true" + end - it "it should return 'unknown' SELinux policy version if /selinux/policyvers doesn't exist" do - Facter.fact(:selinux).stubs(:value).returns("true") - FileTest.expects(:exists?).with("/proc/self/mounts").returns false - FileTest.expects(:exists?).with("/selinux/policyvers").returns false + it "should return an SELinux policy version" do + mounts_does_not_exist - Facter.fact(:selinux_policyversion).value.should == "unknown" - end + FileTest.expects(:exists?).with("/selinux/policyvers").returns true + File.expects(:read).with("/selinux/policyvers").returns("1") - it "should return the SELinux current mode" do - Facter.fact(:selinux).stubs(:value).returns("true") + Facter.fact(:selinux_policyversion).value.should == "1" + end - selinux_sestatus = my_fixture_read("selinux_sestatus") + it "it should return 'unknown' SELinux policy version if /selinux/policyvers doesn't exist" do + mounts_does_not_exist - Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/sestatus').returns(selinux_sestatus) + FileTest.expects(:exists?).with("/selinux/policyvers").returns false - Facter.fact(:selinux_current_mode).value.should == "permissive" - end + Facter.fact(:selinux_policyversion).value.should == "unknown" + end - it "should return the SELinux mode from the configuration file" do - Facter.fact(:selinux).stubs(:value).returns("true") + it "should return the SELinux current mode" do + sestatus_is(my_fixture_read("selinux_sestatus")) - selinux_sestatus = my_fixture_read("selinux_sestatus") + Facter.fact(:selinux_current_mode).value.should == "permissive" + end - Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/sestatus').returns(selinux_sestatus) + it "should return the SELinux mode from the configuration file" do + sestatus_is(my_fixture_read("selinux_sestatus")) - Facter.fact(:selinux_config_mode).value.should == "permissive" - end + Facter.fact(:selinux_config_mode).value.should == "permissive" + end - it "should return the SELinux configuration file policy" do - Facter.fact(:selinux).stubs(:value).returns("true") + it "should return the SELinux configuration file policy" do + sestatus_is(my_fixture_read("selinux_sestatus")) - selinux_sestatus = my_fixture_read("selinux_sestatus") + Facter.fact(:selinux_config_policy).value.should == "targeted" + end - Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/sestatus').returns(selinux_sestatus) + it "should ensure legacy selinux_mode facts returns same value as selinux_config_policy fact" do + Facter.fact(:selinux_config_policy).stubs(:value).returns("targeted") - Facter.fact(:selinux_config_policy).value.should == "targeted" + Facter.fact(:selinux_mode).value.should == "targeted" + end end - it "should ensure legacy selinux_mode facts returns same value as selinux_config_policy fact" do - Facter.fact(:selinux).stubs(:value).returns("true") + def sestatus_is(status) + Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/sestatus').returns(status) + end - Facter.fact(:selinux_config_policy).stubs(:value).returns("targeted") + def mounts_does_not_exist + FileTest.stubs(:exists?).with("/proc/self/mounts").returns false + end - Facter.fact(:selinux_mode).value.should == "targeted" + def mounts_contains(*lines) + FileTest.expects(:exists?).with("/proc/self/mounts").returns true + Facter::Util::Resolution.expects(:exec).with("cat /proc/self/mounts").returns(lines.join("\n")) end + end From 42bec60855f04d735eb30d0103d7782a276c7366 Mon Sep 17 00:00:00 2001 From: Matthaus Litteken Date: Fri, 31 Aug 2012 15:43:26 -0700 Subject: [PATCH 0986/3753] Updating CHANGELOG and version.rb for 1.6.12-rc2 --- CHANGELOG | 6 ++++++ lib/facter/version.rb | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 17984e8743..0c80b3de62 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,9 @@ +1.6.12-rc2 +=== +398b111 (Maint) Extract common elements of selinux tests +c534126 (#10819) Avoid reading from /proc/self/mounts in ruby +b95ea54 fix yum repo path in yaml file + 1.6.12rc1 === 3ad05f1 Remove version test from facter diff --git a/lib/facter/version.rb b/lib/facter/version.rb index ed24de533f..afa48adfeb 100644 --- a/lib/facter/version.rb +++ b/lib/facter/version.rb @@ -1,6 +1,6 @@ module Facter if not defined? FACTERVERSION then - FACTERVERSION = '1.6.12-rc1' + FACTERVERSION = '1.6.12-rc2' end def self.version From f7c086aa8907b8b90954dedf89ec74b0ad6df463 Mon Sep 17 00:00:00 2001 From: Jason Gill Date: Fri, 15 Jun 2012 15:15:56 -0400 Subject: [PATCH 0987/3753] (#10966) Adds BIOS information facts Additionally includes tests for other dmidecode-based facts such as boardmanufacturer --- lib/facter/manufacturer.rb | 5 ++ spec/unit/manufacturer_spec.rb | 115 +++++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 spec/unit/manufacturer_spec.rb diff --git a/lib/facter/manufacturer.rb b/lib/facter/manufacturer.rb index 7fe75e661d..1847e4296c 100644 --- a/lib/facter/manufacturer.rb +++ b/lib/facter/manufacturer.rb @@ -47,6 +47,11 @@ { 'Product(?: Name)?:' => 'boardproductname' }, { 'Serial Number:' => 'boardserialnumber' } ], + '[Bb][Ii][Oo][Ss] [Ii]nformation' => [ + { '[Vv]endor:' => 'bios_vendor' }, + { '[Vv]ersion:' => 'bios_version' }, + { '[Rr]elease [Dd]ate:' => 'bios_release_date' } + ], '[Ss]ystem [Ii]nformation' => [ { 'Manufacturer:' => 'manufacturer' }, { 'Product(?: Name)?:' => 'productname' }, diff --git a/spec/unit/manufacturer_spec.rb b/spec/unit/manufacturer_spec.rb new file mode 100644 index 0000000000..dd412458b9 --- /dev/null +++ b/spec/unit/manufacturer_spec.rb @@ -0,0 +1,115 @@ +#!/usr/bin/env ruby + +require 'spec_helper' +require 'facter' +require 'facter/util/manufacturer' + +describe "Hardware manufacturer facts" do + + describe "on OS'es without DMI support" do + + it "no DMI facts should be reported" do + Facter.fact(:kernel).stubs(:value).returns("Darwin") + Facter.fact(:boardmanufacturer).should == nil + Facter.fact(:boardproductname).should == nil + Facter.fact(:boardserialnumber).should == nil + Facter.fact(:bios_vendor).should == nil + Facter.fact(:bios_version).should == nil + Facter.fact(:bios_release_date).should == nil + Facter.fact(:type).should == nil + end + + end + + describe "on OS'es with DMI support" do + before :each do + Facter.fact(:kernel).stubs(:value).returns("Linux") + dmidecode_output = <<-eos +Handle 0x0000, DMI type 0, 24 bytes +BIOS Information + Vendor: Dell Inc. + Version: 1.2.5 + Release Date: 03/16/2011 + Address: 0xF0000 + Runtime Size: 64 kB + ROM Size: 4096 kB + Characteristics: + ISA is supported + PCI is supported + PNP is supported + BIOS is upgradeable + BIOS shadowing is allowed + Boot from CD is supported + Selectable boot is supported + EDD is supported + 8042 keyboard services are supported (int 9h) + Serial services are supported (int 14h) + CGA/mono video services are supported (int 10h) + ACPI is supported + USB legacy is supported + BIOS boot specification is supported + Function key-initiated network boot is supported + Targeted content distribution is supported + BIOS Revision: 1.2 + +Handle 0x0100, DMI type 1, 27 bytes +System Information + Manufacturer: Dell Inc. + Product Name: PowerEdge R515 + Version: Not Specified + Serial Number: ABCD124 + UUID: 1A2B3456-7890-1A23-4567-B8C91D123456 + Wake-up Type: Power Switch + SKU Number: Not Specified + Family: Not Specified + +Handle 0x0200, DMI type 2, 9 bytes +Base Board Information + Manufacturer: Dell Inc. + Product Name: 03X0MN + Version: A03 + Serial Number: ..AB1234567B1234. + Asset Tag: Not Specified + +Handle 0x0300, DMI type 3, 21 bytes +Chassis Information + Manufacturer: Dell Inc. + Type: Rack Mount Chassis + Lock: Present + Version: Not Specified + Serial Number: ABCD124 + Asset Tag: Not Specified + Boot-up State: Safe + Power Supply State: Safe + Thermal State: Safe + Security Status: Unknown + OEM Information: 0x00000000 + Height: 2 U + Number Of Power Cords: Unspecified + Contained Elements: 0 + +Handle 0x7F00, DMI type 127, 4 bytes +End Of Table + eos + Facter::Manufacturer.stubs(:get_dmi_table).returns(dmidecode_output) + end + + it "should report the correct details from the DMI query" do + Facter.fact(:manufacturer).value.should == "Dell Inc." + Facter.fact(:boardmanufacturer).value.should == "Dell Inc." + Facter.fact(:boardproductname).value.should == "03X0MN" + Facter.fact(:boardserialnumber).value.should == "..AB1234567B1234." + Facter.fact(:bios_vendor).value.should == "Dell Inc." + Facter.fact(:bios_version).value.should == "1.2.5" + Facter.fact(:bios_release_date).value.should == "03/16/2011" + Facter.fact(:manufacturer).value.should == "Dell Inc." + Facter.fact(:productname).value.should == "PowerEdge R515" + Facter.fact(:serialnumber).value.should == "ABCD124" + Facter.fact(:type).value.should == "Rack Mount Chassis" + Facter.fact(:productname).value.should_not == Facter.fact(:boardproductname).value + Facter.fact(:serialnumber).value.should_not == Facter.fact(:boardserialnumber).value + end + + end + +end \ No newline at end of file From 3d45e2b2b4c0e6f93cc22b1718d4b97e8a442da2 Mon Sep 17 00:00:00 2001 From: Ashley Penney Date: Thu, 12 Jul 2012 21:36:05 -0400 Subject: [PATCH 0988/3753] Add the uuid from dmidecode and add appropriate unit testing for it. --- lib/facter/manufacturer.rb | 3 +- .../util/manufacturer/intel_linux_dmidecode | 549 ++++++++++++++++++ spec/unit/util/manufacturer_spec.rb | 10 + 3 files changed, 561 insertions(+), 1 deletion(-) create mode 100644 spec/fixtures/unit/util/manufacturer/intel_linux_dmidecode diff --git a/lib/facter/manufacturer.rb b/lib/facter/manufacturer.rb index 1847e4296c..7447107234 100644 --- a/lib/facter/manufacturer.rb +++ b/lib/facter/manufacturer.rb @@ -55,7 +55,8 @@ '[Ss]ystem [Ii]nformation' => [ { 'Manufacturer:' => 'manufacturer' }, { 'Product(?: Name)?:' => 'productname' }, - { 'Serial Number:' => 'serialnumber' } + { 'Serial Number:' => 'serialnumber' }, + { 'UUID:' => 'uuid' } ], '(Chassis Information|system enclosure or chassis)' => [ { '(?:Chassis )?Type:' => 'type' } diff --git a/spec/fixtures/unit/util/manufacturer/intel_linux_dmidecode b/spec/fixtures/unit/util/manufacturer/intel_linux_dmidecode new file mode 100644 index 0000000000..78415bc5d7 --- /dev/null +++ b/spec/fixtures/unit/util/manufacturer/intel_linux_dmidecode @@ -0,0 +1,549 @@ +# dmidecode 2.11 +SMBIOS 2.7 present. +52 structures occupying 2192 bytes. +Table at 0x000EB270. + +Handle 0x0000, DMI type 0, 24 bytes +BIOS Information + Vendor: Intel Corp. + Version: AGH6110H.86A.0039.2012.0410.1054 + Release Date: 04/10/2012 + Address: 0xF0000 + Runtime Size: 64 kB + ROM Size: 1024 kB + Characteristics: + PCI is supported + BIOS is upgradeable + BIOS shadowing is allowed + Boot from CD is supported + Selectable boot is supported + BIOS ROM is socketed + EDD is supported + 5.25"/1.2 MB floppy services are supported (int 13h) + 3.5"/720 kB floppy services are supported (int 13h) + 3.5"/2.88 MB floppy services are supported (int 13h) + Print screen service is supported (int 5h) + 8042 keyboard services are supported (int 9h) + Serial services are supported (int 14h) + Printer services are supported (int 17h) + ACPI is supported + USB legacy is supported + BIOS boot specification is supported + Targeted content distribution is supported + UEFI is supported + +Handle 0x0001, DMI type 1, 27 bytes +System Information + Manufacturer: + Product Name: + Version: + Serial Number: + UUID: 60A98BB3-95B6-E111-AF74-4C72B9247D28 + Wake-up Type: Power Switch + SKU Number: Not Specified + Family: Not Specified + +Handle 0x0002, DMI type 2, 15 bytes +Base Board Information + Manufacturer: Intel Corporation + Product Name: DH61AG + Version: AAG23736-503 + Serial Number: BTAG2240143N + Asset Tag: To be filled by O.E.M. + Features: + Board is a hosting board + Board is replaceable + Location In Chassis: To be filled by O.E.M. + Chassis Handle: 0x0003 + Type: Motherboard + Contained Object Handles: 0 + +Handle 0x0003, DMI type 3, 22 bytes +Chassis Information + Manufacturer: + Type: Desktop + Lock: Not Present + Version: + Serial Number: + Asset Tag: + Boot-up State: Safe + Power Supply State: Safe + Thermal State: Safe + Security Status: None + OEM Information: 0x00000000 + Height: Unspecified + Number Of Power Cords: 1 + Contained Elements: 0 + SKU Number: To be filled by O.E.M. + +Handle 0x0004, DMI type 4, 42 bytes +Processor Information + Socket Designation: LGA1155 CPU 1 + Type: Central Processor + Family: Core i3 + Manufacturer: Intel(R) Corp. + ID: A7 06 02 00 FF FB EB BF + Signature: Type 0, Family 6, Model 42, Stepping 7 + Flags: + FPU (Floating-point unit on-chip) + VME (Virtual mode extension) + DE (Debugging extension) + PSE (Page size extension) + TSC (Time stamp counter) + MSR (Model specific registers) + PAE (Physical address extension) + MCE (Machine check exception) + CX8 (CMPXCHG8 instruction supported) + APIC (On-chip APIC hardware supported) + SEP (Fast system call) + MTRR (Memory type range registers) + PGE (Page global enable) + MCA (Machine check architecture) + CMOV (Conditional move instruction supported) + PAT (Page attribute table) + PSE-36 (36-bit page size extension) + CLFSH (CLFLUSH instruction supported) + DS (Debug store) + ACPI (ACPI supported) + MMX (MMX technology supported) + FXSR (FXSAVE and FXSTOR instructions supported) + SSE (Streaming SIMD extensions) + SSE2 (Streaming SIMD extensions 2) + SS (Self-snoop) + HTT (Multi-threading) + TM (Thermal monitor supported) + PBE (Pending break enabled) + Version: Intel(R) Core(TM) i3-2130 CPU @ 3.40GHz + Voltage: 1.7 V + External Clock: 25 MHz + Max Speed: 4000 MHz + Current Speed: 3400 MHz + Status: Populated, Enabled + Upgrade: Socket BGA1155 + L1 Cache Handle: 0x0005 + L2 Cache Handle: 0x0006 + L3 Cache Handle: 0x0007 + Serial Number: To Be Filled By O.E.M. + Asset Tag: To Be Filled By O.E.M. + Part Number: To Be Filled By O.E.M. + Core Count: 2 + Core Enabled: 1 + Thread Count: 2 + Characteristics: + 64-bit capable + +Handle 0x0005, DMI type 7, 19 bytes +Cache Information + Socket Designation: L1-Cache + Configuration: Enabled, Not Socketed, Level 1 + Operational Mode: Write Back + Location: Internal + Installed Size: 32 kB + Maximum Size: 32 kB + Supported SRAM Types: + Other + Installed SRAM Type: Other + Speed: Unknown + Error Correction Type: None + System Type: Unified + Associativity: 8-way Set-associative + +Handle 0x0006, DMI type 7, 19 bytes +Cache Information + Socket Designation: L2-Cache + Configuration: Enabled, Not Socketed, Level 2 + Operational Mode: Varies With Memory Address + Location: Internal + Installed Size: 512 kB + Maximum Size: 512 kB + Supported SRAM Types: + Other + Installed SRAM Type: Other + Speed: Unknown + Error Correction Type: None + System Type: Unified + Associativity: 8-way Set-associative + +Handle 0x0007, DMI type 7, 19 bytes +Cache Information + Socket Designation: L3-Cache + Configuration: Enabled, Not Socketed, Level 3 + Operational Mode: Unknown + Location: Internal + Installed Size: 3072 kB + Maximum Size: 3072 kB + Supported SRAM Types: + Other + Installed SRAM Type: Other + Speed: Unknown + Error Correction Type: None + System Type: Unified + Associativity: Other + +Handle 0x0008, DMI type 8, 9 bytes +Port Connector Information + Internal Reference Designator: J1A1 + Internal Connector Type: None + External Reference Designator: PS2Mouse + External Connector Type: PS/2 + Port Type: Mouse Port + +Handle 0x0009, DMI type 8, 9 bytes +Port Connector Information + Internal Reference Designator: J1A1 + Internal Connector Type: None + External Reference Designator: Keyboard + External Connector Type: PS/2 + Port Type: Keyboard Port + +Handle 0x000A, DMI type 8, 9 bytes +Port Connector Information + Internal Reference Designator: J2A1 + Internal Connector Type: None + External Reference Designator: TV Out + External Connector Type: Mini Centronics Type-14 + Port Type: Other + +Handle 0x000B, DMI type 8, 9 bytes +Port Connector Information + Internal Reference Designator: J2A2A + Internal Connector Type: None + External Reference Designator: COM A + External Connector Type: DB-9 male + Port Type: Serial Port 16550A Compatible + +Handle 0x000C, DMI type 8, 9 bytes +Port Connector Information + Internal Reference Designator: J2A2B + Internal Connector Type: None + External Reference Designator: Video + External Connector Type: DB-15 female + Port Type: Video Port + +Handle 0x000D, DMI type 8, 9 bytes +Port Connector Information + Internal Reference Designator: J3A1 + Internal Connector Type: None + External Reference Designator: USB1 + External Connector Type: Access Bus (USB) + Port Type: USB + +Handle 0x000E, DMI type 8, 9 bytes +Port Connector Information + Internal Reference Designator: J3A1 + Internal Connector Type: None + External Reference Designator: USB2 + External Connector Type: Access Bus (USB) + Port Type: USB + +Handle 0x000F, DMI type 8, 9 bytes +Port Connector Information + Internal Reference Designator: J3A1 + Internal Connector Type: None + External Reference Designator: USB3 + External Connector Type: Access Bus (USB) + Port Type: USB + +Handle 0x0010, DMI type 8, 9 bytes +Port Connector Information + Internal Reference Designator: J9A1 - TPM HDR + Internal Connector Type: Other + External Reference Designator: Not Specified + External Connector Type: None + Port Type: Other + +Handle 0x0011, DMI type 8, 9 bytes +Port Connector Information + Internal Reference Designator: J9C1 - PCIE DOCKING CONN + Internal Connector Type: Other + External Reference Designator: Not Specified + External Connector Type: None + Port Type: Other + +Handle 0x0012, DMI type 8, 9 bytes +Port Connector Information + Internal Reference Designator: J2B3 - CPU FAN + Internal Connector Type: Other + External Reference Designator: Not Specified + External Connector Type: None + Port Type: Other + +Handle 0x0013, DMI type 8, 9 bytes +Port Connector Information + Internal Reference Designator: J6C2 - EXT HDMI + Internal Connector Type: Other + External Reference Designator: Not Specified + External Connector Type: None + Port Type: Other + +Handle 0x0014, DMI type 8, 9 bytes +Port Connector Information + Internal Reference Designator: J3C1 - GMCH FAN + Internal Connector Type: Other + External Reference Designator: Not Specified + External Connector Type: None + Port Type: Other + +Handle 0x0015, DMI type 8, 9 bytes +Port Connector Information + Internal Reference Designator: J1D1 - ITP + Internal Connector Type: Other + External Reference Designator: Not Specified + External Connector Type: None + Port Type: Other + +Handle 0x0016, DMI type 8, 9 bytes +Port Connector Information + Internal Reference Designator: J9E2 - MDC INTPSR + Internal Connector Type: Other + External Reference Designator: Not Specified + External Connector Type: None + Port Type: Other + +Handle 0x0017, DMI type 8, 9 bytes +Port Connector Information + Internal Reference Designator: J9E4 - MDC INTPSR + Internal Connector Type: Other + External Reference Designator: Not Specified + External Connector Type: None + Port Type: Other + +Handle 0x0018, DMI type 8, 9 bytes +Port Connector Information + Internal Reference Designator: J9E3 - LPC HOT DOCKING + Internal Connector Type: Other + External Reference Designator: Not Specified + External Connector Type: None + Port Type: Other + +Handle 0x0019, DMI type 8, 9 bytes +Port Connector Information + Internal Reference Designator: J9E1 - SCAN MATRIX + Internal Connector Type: Other + External Reference Designator: Not Specified + External Connector Type: None + Port Type: Other + +Handle 0x001A, DMI type 8, 9 bytes +Port Connector Information + Internal Reference Designator: J9G1 - LPC SIDE BAND + Internal Connector Type: Other + External Reference Designator: Not Specified + External Connector Type: None + Port Type: Other + +Handle 0x001B, DMI type 8, 9 bytes +Port Connector Information + Internal Reference Designator: J8F1 - UNIFIED + Internal Connector Type: Other + External Reference Designator: Not Specified + External Connector Type: None + Port Type: Other + +Handle 0x001C, DMI type 8, 9 bytes +Port Connector Information + Internal Reference Designator: J6F1 - LVDS + Internal Connector Type: Other + External Reference Designator: Not Specified + External Connector Type: None + Port Type: Other + +Handle 0x001D, DMI type 8, 9 bytes +Port Connector Information + Internal Reference Designator: J2F1 - LAI FAN + Internal Connector Type: Other + External Reference Designator: Not Specified + External Connector Type: None + Port Type: Other + +Handle 0x001E, DMI type 8, 9 bytes +Port Connector Information + Internal Reference Designator: J2G1 - GFX VID + Internal Connector Type: Other + External Reference Designator: Not Specified + External Connector Type: None + Port Type: Other + +Handle 0x001F, DMI type 8, 9 bytes +Port Connector Information + Internal Reference Designator: J1G6 - AC JACK + Internal Connector Type: Other + External Reference Designator: Not Specified + External Connector Type: None + Port Type: Other + +Handle 0x0020, DMI type 9, 17 bytes +System Slot Information + Designation: PCIe X4 SLOT1 + Type: x4 PCI Express + Current Usage: Available + Length: Long + ID: 0 + Characteristics: + 3.3 V is provided + Opening is shared + PME signal is supported + Bus Address: 0000:00:01.0 + +Handle 0x0021, DMI type 10, 6 bytes +On Board Device Information + Type: Video + Status: Enabled + Description: Intel(R) HD Graphics Device + +Handle 0x0022, DMI type 10, 6 bytes +On Board Device Information + Type: Ethernet + Status: Enabled + Description: Intel(R) 82579V Gigabit Network Device + +Handle 0x0023, DMI type 10, 6 bytes +On Board Device Information + Type: Sound + Status: Enabled + Description: Intel(R) High Definition Audio Device + +Handle 0x0024, DMI type 11, 5 bytes +OEM Strings + String 1: To Be Filled By O.E.M. + +Handle 0x0025, DMI type 12, 5 bytes +System Configuration Options + Option 1: To Be Filled By O.E.M. + +Handle 0x0026, DMI type 16, 15 bytes +Physical Memory Array + Location: System Board Or Motherboard + Use: System Memory + Error Correction Type: None + Maximum Capacity: 16 GB + Error Information Handle: No Error + Number Of Devices: 2 + +Handle 0x0027, DMI type 18, 23 bytes +32-bit Memory Error Information + Type: OK + Granularity: Unknown + Operation: Unknown + Vendor Syndrome: Unknown + Memory Array Address: Unknown + Device Address: Unknown + Resolution: Unknown + +Handle 0x0028, DMI type 19, 15 bytes +Memory Array Mapped Address + Starting Address: 0x00000000000 + Ending Address: 0x003FFFFFFFF + Range Size: 16 GB + Physical Array Handle: 0x0026 + Partition Width: 1 + +Handle 0x0029, DMI type 17, 28 bytes +Memory Device + Array Handle: 0x0026 + Error Information Handle: No Error + Total Width: 64 bits + Data Width: 64 bits + Size: 8192 MB + Form Factor: DIMM + Set: None + Locator: SODIMM1 + Bank Locator: Channel A DIMM 0 + Type: DDR3 + Type Detail: Synchronous + Speed: 1333 MHz + Manufacturer: Kingston + Serial Number: 363D6E24 + Asset Tag: A1_AssetTagNum0 + Part Number: 9905428-093.A00LF + Rank: 2 + +Handle 0x002A, DMI type 18, 23 bytes +32-bit Memory Error Information + Type: OK + Granularity: Unknown + Operation: Unknown + Vendor Syndrome: Unknown + Memory Array Address: Unknown + Device Address: Unknown + Resolution: Unknown + +Handle 0x002B, DMI type 20, 19 bytes +Memory Device Mapped Address + Starting Address: 0x00000000000 + Ending Address: 0x001FFFFFFFF + Range Size: 8 GB + Physical Device Handle: 0x0029 + Memory Array Mapped Address Handle: 0x0028 + Partition Row Position: 1 + +Handle 0x002C, DMI type 17, 28 bytes +Memory Device + Array Handle: 0x0026 + Error Information Handle: No Error + Total Width: 64 bits + Data Width: 64 bits + Size: 8192 MB + Form Factor: DIMM + Set: None + Locator: SODIMM2 + Bank Locator: Channel B DIMM 0 + Type: DDR3 + Type Detail: Synchronous + Speed: 1333 MHz + Manufacturer: Kingston + Serial Number: 363D2A28 + Asset Tag: A1_AssetTagNum1 + Part Number: 9905428-093.A00LF + Rank: 2 + +Handle 0x002D, DMI type 18, 23 bytes +32-bit Memory Error Information + Type: OK + Granularity: Unknown + Operation: Unknown + Vendor Syndrome: Unknown + Memory Array Address: Unknown + Device Address: Unknown + Resolution: Unknown + +Handle 0x002E, DMI type 20, 19 bytes +Memory Device Mapped Address + Starting Address: 0x00200000000 + Ending Address: 0x003FFFFFFFF + Range Size: 8 GB + Physical Device Handle: 0x002C + Memory Array Mapped Address Handle: 0x0028 + Partition Row Position: 1 + +Handle 0x002F, DMI type 32, 20 bytes +System Boot Information + Status: No errors detected + +Handle 0x0030, DMI type 41, 11 bytes +Onboard Device + Reference Designation: Intel(R) HD Graphics Device + Type: Video + Status: Enabled + Type Instance: 1 + Bus Address: 0000:00:02.0 + +Handle 0x0031, DMI type 41, 11 bytes +Onboard Device + Reference Designation: Intel(R) 82579V Gigabit Network Device + Type: Ethernet + Status: Enabled + Type Instance: 1 + Bus Address: 0000:00:19.0 + +Handle 0x0032, DMI type 41, 11 bytes +Onboard Device + Reference Designation: Intel(R) High Definition Audio Device + Type: Sound + Status: Enabled + Type Instance: 1 + Bus Address: 0000:00:1b.0 + +Handle 0x0041, DMI type 127, 4 bytes +End Of Table + diff --git a/spec/unit/util/manufacturer_spec.rb b/spec/unit/util/manufacturer_spec.rb index c5230c3aec..b2d76dbbae 100755 --- a/spec/unit/util/manufacturer_spec.rb +++ b/spec/unit/util/manufacturer_spec.rb @@ -123,6 +123,16 @@ Facter.value(:ramlocation).should == "System Board Or Motherboard" end + it "should return an appropriate uuid on linux" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + dmidecode = my_fixture_read("intel_linux_dmidecode") + Facter::Manufacturer.expects(:get_dmi_table).returns(dmidecode) + + query = { '[Ss]ystem [Ii]nformation' => [ { 'UUID:' => 'uuid' } ] } + Facter::Manufacturer.dmi_find_system_info(query) + Facter.value(:uuid).should == "60A98BB3-95B6-E111-AF74-4C72B9247D28" + end + def find_product_name(os) output_file = case os when "FreeBSD" then my_fixture("freebsd_dmidecode") From ec9524f0f0f89386e9cfdc1b6793acc9673533ae Mon Sep 17 00:00:00 2001 From: Evan Pierce Date: Wed, 5 Sep 2012 16:08:44 -0700 Subject: [PATCH 0989/3753] (#15585) Make ext fact regex not greedy Prior to this commit an external fact value of the form fact_name = a = b would be returned as b when running Facter, as opposed to the correct fact_value of a = b. This commit now partitions around the first equal sign from fact_name=fact_value. --- lib/facter/util/parser.rb | 2 +- spec/unit/util/parser_spec.rb | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/facter/util/parser.rb b/lib/facter/util/parser.rb index 259d4ca819..8f2db609cc 100644 --- a/lib/facter/util/parser.rb +++ b/lib/facter/util/parser.rb @@ -78,7 +78,7 @@ def parse_results class TextParser < Base def parse_results - re = /^(.+)=(.+)$/ + re = /^(.+?)=(.+)$/ result = {} content.each_line do |line| if match_data = re.match(line.chomp) diff --git a/spec/unit/util/parser_spec.rb b/spec/unit/util/parser_spec.rb index 51cacc6b31..be7b9e7f4d 100644 --- a/spec/unit/util/parser_spec.rb +++ b/spec/unit/util/parser_spec.rb @@ -77,6 +77,12 @@ let(:data_in_txt) do "one=two\nthree=four\n" end it_behaves_like "txt parser" end + + context "extra equal sign" do + let(:data_in_txt) do "one=two\nthree=four=five\n" end + let(:data) do {"one" => "two", "three" => "four=five"} end + it_behaves_like "txt parser" + end context "extra data" do let(:data_in_txt) do "one=two\nfive\nthree=four\n" end From b7135963645a31a99b6d52292bd36c0308d0182c Mon Sep 17 00:00:00 2001 From: rahul Date: Thu, 6 Sep 2012 11:49:54 -0700 Subject: [PATCH 0990/3753] (maint) add packaging artifacts This patch adds pkg/ and ext/packaging to .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..54930fca9c --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +pkg/ +ext/packaging/ From bffa03a6fb3fc48bb81080b9e3ddb65112bc3ee6 Mon Sep 17 00:00:00 2001 From: rahul Date: Thu, 30 Aug 2012 20:06:48 -0700 Subject: [PATCH 0991/3753] (packaging) add ips support This patch adds ips support for facter. --- ext/build_defaults.yaml | 1 + ext/ips/facter.p5m.erb | 10 ++++++++++ ext/ips/rules | 7 +++++++ ext/ips/transforms | 18 ++++++++++++++++++ 4 files changed, 36 insertions(+) create mode 100644 ext/ips/facter.p5m.erb create mode 100755 ext/ips/rules create mode 100644 ext/ips/transforms diff --git a/ext/build_defaults.yaml b/ext/build_defaults.yaml index da17974b53..f263d2332c 100644 --- a/ext/build_defaults.yaml +++ b/ext/build_defaults.yaml @@ -18,3 +18,4 @@ build_dmg: TRUE apt_host: 'burji.puppetlabs.com' apt_repo_url: 'http://apt.puppetlabs.com/' apt_repo_path: '/opt/repository/incoming' +ips_repo: 'file:///opt/repository/ips' diff --git a/ext/ips/facter.p5m.erb b/ext/ips/facter.p5m.erb new file mode 100644 index 0000000000..266f422647 --- /dev/null +++ b/ext/ips/facter.p5m.erb @@ -0,0 +1,10 @@ +set name=pkg.fmri value=pkg://puppetlabs.com/application/<%=@name%>@<%=@ipsversion%> +set name=pkg.summary value="<%=@summary%>" +set name=pkg.human-version value="<%=@version%>" +set name=pkg.description value="<%=@description%>" +set name=info.classification value="org.opensolaris.category.2008:Applications/System Utilities" +set name=org.opensolaris.consolidation value="puppet" +set name=description value="<%=@description%>" +set name=variant.opensolaris.zone value=global value=nonglobal +set name=variant.arch value=sparc value=i386 +license facter.license license="Apache v2.0" diff --git a/ext/ips/rules b/ext/ips/rules new file mode 100755 index 0000000000..c9d6725c4c --- /dev/null +++ b/ext/ips/rules @@ -0,0 +1,7 @@ +#!/usr/bin/make -f + +LIBDIR=$(shell /usr/bin/ruby18 -rrbconfig -e 'puts Config::CONFIG["rubylibdir"]') +DESTDIR=$(CURDIR)/pkg/ips/proto + +binary-install/facter:: + /usr/bin/ruby18 install.rb --destdir=$(DESTDIR) --bindir=/usr/bin --sbindir=/usr/sbin --sitelibdir=$(LIBDIR) --mandir=/usr/share/man diff --git a/ext/ips/transforms b/ext/ips/transforms new file mode 100644 index 0000000000..0ce9fcf9ff --- /dev/null +++ b/ext/ips/transforms @@ -0,0 +1,18 @@ + default facet.doc.man true> + add restart_fmri svc:/application/man-index:default> + +# drop user +drop> +drop> +drop> +drop> +drop> +drop> +drop> +drop> +drop> +drop> + + +# saner dependencies + edit fmri "@[^ \t\n\r\f\v]*" ""> From 5a3e537e216360d571e4b23649bee05ad0e63258 Mon Sep 17 00:00:00 2001 From: Matthaus Litteken Date: Mon, 10 Sep 2012 16:35:46 -0700 Subject: [PATCH 0992/3753] Updating FACTERVERSION and CHANGELOG for 1.6.12 --- CHANGELOG | 5 +---- lib/facter/version.rb | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 0c80b3de62..5f6fa5a957 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,11 +1,8 @@ -1.6.12-rc2 +1.6.12 === 398b111 (Maint) Extract common elements of selinux tests c534126 (#10819) Avoid reading from /proc/self/mounts in ruby b95ea54 fix yum repo path in yaml file - -1.6.12rc1 -=== 3ad05f1 Remove version test from facter fce4b01 fix redhat spec release template variable 903b1d9 Stop using sed to generate the preflight erb diff --git a/lib/facter/version.rb b/lib/facter/version.rb index afa48adfeb..19d052a6ca 100644 --- a/lib/facter/version.rb +++ b/lib/facter/version.rb @@ -1,6 +1,6 @@ module Facter if not defined? FACTERVERSION then - FACTERVERSION = '1.6.12-rc2' + FACTERVERSION = '1.6.12' end def self.version From b5fe6d0517c4475021040767a001f35aa27c47f9 Mon Sep 17 00:00:00 2001 From: Daniel Pittman Date: Tue, 11 Sep 2012 10:34:47 -0700 Subject: [PATCH 0993/3753] Update copyright years in LICENSE This brings the copyright years up to date, reflecting the set of years we actually worked on the software. Signed-off-by: Daniel Pittman --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 571ef3d435..64a21dea7c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ Facter - Host Fact Detection and Reporting -Copyright 2011 Puppet Labs Inc +Copyright 2005-2012 Puppet Labs Inc Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From 1b4226ccec3904383bd1924672f16d94454b31a7 Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Wed, 12 Sep 2012 00:45:29 -0700 Subject: [PATCH 0994/3753] Fixup apple package plist for use with packaging repo With the packaging repo change to the non-flat package format, the package info plist format changes to plist. Signed-off-by: Moses Mendoza --- ext/osx/prototype.plist.erb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/osx/prototype.plist.erb b/ext/osx/prototype.plist.erb index 062a0371e7..e56659a6bc 100644 --- a/ext/osx/prototype.plist.erb +++ b/ext/osx/prototype.plist.erb @@ -1,6 +1,6 @@ - + CFBundleIdentifier <%= @title %> @@ -35,4 +35,4 @@ IFPkgFlagUpdateInstalledLanguages - + From 0b85564c5db642d72b0383a550664a9a7533b97e Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Thu, 13 Sep 2012 09:41:36 -0700 Subject: [PATCH 0995/3753] (#16341) return epoch to 1.6.x branch The epoch was lost during the transition to a templated spec file. This commit replaces it. Signed-off-by: Moses Mendoza --- ext/redhat/facter.spec.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/redhat/facter.spec.erb b/ext/redhat/facter.spec.erb index d4462b6d8a..f96edf7ed5 100644 --- a/ext/redhat/facter.spec.erb +++ b/ext/redhat/facter.spec.erb @@ -14,7 +14,7 @@ Summary: Ruby module for collecting simple facts about a host operating system Name: facter Version: %{rpmversion} Release: <%= @rpmrelease -%>%{?dist} - +Epoch: 1 License: Apache 2.0 Group: System Environment/Base URL: http://www.puppetlabs.com/puppet/related-projects/%{name} From 7b51f4a652cf4a51508a683baa5e68bbdfdacaaa Mon Sep 17 00:00:00 2001 From: rahul Date: Thu, 13 Sep 2012 17:50:46 -0700 Subject: [PATCH 0996/3753] (packaging) ips: add shipping and signing support This patch is complementary to the patch to packaging repository which adds shipping and signing support for ips. It adds variables, ips_repo ips_store ips_host --- ext/build_defaults.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ext/build_defaults.yaml b/ext/build_defaults.yaml index f263d2332c..165d57161a 100644 --- a/ext/build_defaults.yaml +++ b/ext/build_defaults.yaml @@ -18,4 +18,6 @@ build_dmg: TRUE apt_host: 'burji.puppetlabs.com' apt_repo_url: 'http://apt.puppetlabs.com/' apt_repo_path: '/opt/repository/incoming' -ips_repo: 'file:///opt/repository/ips' +ips_repo: '/var/pkgrepo' +ips_store: '/opt/repository' +ips_host: 'solaris-11-ips-repo.acctest.dc1.puppetlabs.net' From dd576645ab1265fdabc8946b65b6dd5ca23c2c8f Mon Sep 17 00:00:00 2001 From: Michael Renz Date: Tue, 18 Sep 2012 20:37:50 -0400 Subject: [PATCH 0997/3753] Fixes Bug # 1415: truncated Infiniband MAC addresses FOR LINUX ONLY. If interface starts with ib, uses one of two methods to get infiniband MAC address and fails with a MAC address of all FF otherwise. --- lib/facter/macaddress.rb | 2 +- lib/facter/util/ip.rb | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/facter/macaddress.rb b/lib/facter/macaddress.rb index 4d85a07e87..1e70711c32 100644 --- a/lib/facter/macaddress.rb +++ b/lib/facter/macaddress.rb @@ -28,7 +28,7 @@ ether = [] output = Facter::Util::Resolution.exec("/sbin/ifconfig -a") output.each_line do |s| - ether.push($1) if s =~ /(?:ether|HWaddr) (\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/ + ether.push($1) if s =~ /(?:ether|HWaddr) ((\w{1,2}:){5,}\w{1,2})/ end Facter::Util::Macaddress.standardize(ether[0]) end diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index d78f7c98e6..80db706cc0 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -89,8 +89,24 @@ def self.get_all_interface_output def self.get_single_interface_output(interface) output = "" case Facter.value(:kernel) - when 'Linux', 'OpenBSD', 'NetBSD', 'FreeBSD', 'Darwin', 'GNU/kFreeBSD', 'DragonFly' + when 'OpenBSD', 'NetBSD', 'FreeBSD', 'Darwin', 'GNU/kFreeBSD', 'DragonFly' output = %x{/sbin/ifconfig #{interface}} + when 'Linux' + ifconfig_output = %x{/sbin/ifconfig #{interface} 2>/dev/null} + if interface =~ /^ib/ then + if File::exist?("/sys/class/net/#{interface}/address") then + real_mac_address = File.read("/sys/class/net/#{interface}/address").chomp + elsif File::exist?("/sbin/ip") then + ip_output = %x{/sbin/ip link show #{interface}} + real_mac_address = ip_output.scan(%r{infiniband\s+((\w{1,2}:){5,}\w{1,2})}) + else + real_mac_address = "FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF" + Facter.debug("ip.rb: nothing under /sys/class/net/#{interface}/address and /sbin/ip not available") + end + output = ifconfig_output.sub(%r{(?:ether|HWaddr)\s+((\w{1,2}:){5,}\w{1,2})}, "HWaddr #{real_mac_address}") + else + output = %x{/sbin/ifconfig #{interface}} + end when 'SunOS' output = %x{/usr/sbin/ifconfig #{interface}} when 'HP-UX' From 3f78ad69a3fd05fe6c76c0ec2f4c9bb4ad3556f6 Mon Sep 17 00:00:00 2001 From: Michael Renz Date: Wed, 19 Sep 2012 09:00:47 -0400 Subject: [PATCH 0998/3753] ifconfig should not run twice for Linux. --- lib/facter/util/ip.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index 80db706cc0..7defd2e6cf 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -105,7 +105,7 @@ def self.get_single_interface_output(interface) end output = ifconfig_output.sub(%r{(?:ether|HWaddr)\s+((\w{1,2}:){5,}\w{1,2})}, "HWaddr #{real_mac_address}") else - output = %x{/sbin/ifconfig #{interface}} + output = ifconfig_output end when 'SunOS' output = %x{/usr/sbin/ifconfig #{interface}} From 246a70f92738d18677a743a21267538f32b4e251 Mon Sep 17 00:00:00 2001 From: Michael Renz Date: Wed, 19 Sep 2012 09:55:51 -0400 Subject: [PATCH 0999/3753] Handles ruby hanging of File.read of /sys/class/ with cat. --- lib/facter/util/ip.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index 7defd2e6cf..d1a1fbc210 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -95,7 +95,7 @@ def self.get_single_interface_output(interface) ifconfig_output = %x{/sbin/ifconfig #{interface} 2>/dev/null} if interface =~ /^ib/ then if File::exist?("/sys/class/net/#{interface}/address") then - real_mac_address = File.read("/sys/class/net/#{interface}/address").chomp + real_mac_address = `cat /sys/class/net/#{interface}/address`.chomp elsif File::exist?("/sbin/ip") then ip_output = %x{/sbin/ip link show #{interface}} real_mac_address = ip_output.scan(%r{infiniband\s+((\w{1,2}:){5,}\w{1,2})}) From 284bc45c5c2bfbe10109d178b4389be070fa1f8d Mon Sep 17 00:00:00 2001 From: Michael Renz Date: Wed, 19 Sep 2012 16:39:14 -0400 Subject: [PATCH 1000/3753] #16005 Puppet agent generates ifconfig warnings on InfiniBand systems fix --- lib/facter/ipaddress.rb | 2 +- lib/facter/ipaddress6.rb | 2 +- lib/facter/macaddress.rb | 14 +++++++++++++- lib/facter/util/ip.rb | 4 +++- lib/facter/util/netmask.rb | 2 +- 5 files changed, 19 insertions(+), 5 deletions(-) diff --git a/lib/facter/ipaddress.rb b/lib/facter/ipaddress.rb index 360becd2ea..1d0f2886d0 100644 --- a/lib/facter/ipaddress.rb +++ b/lib/facter/ipaddress.rb @@ -26,7 +26,7 @@ confine :kernel => :linux setcode do ip = nil - output = %x{/sbin/ifconfig} + output = %x{/sbin/ifconfig 2>/dev/null} output.split(/^\S/).each { |str| if str =~ /inet addr:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ diff --git a/lib/facter/ipaddress6.rb b/lib/facter/ipaddress6.rb index 16b70fb135..7d2885fb6e 100644 --- a/lib/facter/ipaddress6.rb +++ b/lib/facter/ipaddress6.rb @@ -38,7 +38,7 @@ def get_address_after_token(output, token, return_first=false) Facter.add(:ipaddress6) do confine :kernel => :linux setcode do - output = Facter::Util::Resolution.exec('/sbin/ifconfig') + output = Facter::Util::Resolution.exec('/sbin/ifconfig 2>/dev/null') get_address_after_token(output, 'inet6 addr:') end diff --git a/lib/facter/macaddress.rb b/lib/facter/macaddress.rb index 1e70711c32..b7afa432df 100644 --- a/lib/facter/macaddress.rb +++ b/lib/facter/macaddress.rb @@ -23,7 +23,19 @@ end Facter.add(:macaddress) do - confine :kernel => %w{SunOS Linux GNU/kFreeBSD} + confine :kernel => 'Linux' + setcode do + ether = [] + output = Facter::Util::Resolution.exec("/sbin/ifconfig -a 2>/dev/null") + output.each_line do |s| + ether.push($1) if s =~ /(?:ether|HWaddr) ((\w{1,2}:){5,}\w{1,2})/ + end + Facter::Util::Macaddress.standardize(ether[0]) + end +end + +Facter.add(:macaddress) do + confine :kernel => %w{SunOS GNU/kFreeBSD} setcode do ether = [] output = Facter::Util::Resolution.exec("/sbin/ifconfig -a") diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index d1a1fbc210..be0ab96b48 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -73,8 +73,10 @@ def self.get_interfaces def self.get_all_interface_output case Facter.value(:kernel) - when 'Linux', 'OpenBSD', 'NetBSD', 'FreeBSD', 'Darwin', 'GNU/kFreeBSD', 'DragonFly' + when 'OpenBSD', 'NetBSD', 'FreeBSD', 'Darwin', 'GNU/kFreeBSD', 'DragonFly' output = %x{/sbin/ifconfig -a} + when 'Linux' + output = %x{/sbin/ifconfig -a 2>/dev/null} when 'SunOS' output = %x{/usr/sbin/ifconfig -a} when 'HP-UX' diff --git a/lib/facter/util/netmask.rb b/lib/facter/util/netmask.rb index b78b173ea7..36a212e09f 100644 --- a/lib/facter/util/netmask.rb +++ b/lib/facter/util/netmask.rb @@ -7,7 +7,7 @@ def self.get_netmask case Facter.value(:kernel) when 'Linux' ops = { - :ifconfig => '/sbin/ifconfig', + :ifconfig => '/sbin/ifconfig 2>/dev/null', :regex => %r{\s+ inet\saddr: #{Facter.ipaddress} .*? Mask: (#{ipregex})}x, :munge => nil, } From 7205d59be41433067acf99ae50bd2291f9c1df4a Mon Sep 17 00:00:00 2001 From: Michael Renz Date: Wed, 19 Sep 2012 17:05:11 -0400 Subject: [PATCH 1001/3753] #1415 ip_spec.rb test added for linux_ifconfig_ib0 --- spec/fixtures/unit/util/ip/linux_ifconfig_ib0 | 8 ++++++++ spec/unit/util/ip_spec.rb | 9 +++++++++ 2 files changed, 17 insertions(+) create mode 100644 spec/fixtures/unit/util/ip/linux_ifconfig_ib0 diff --git a/spec/fixtures/unit/util/ip/linux_ifconfig_ib0 b/spec/fixtures/unit/util/ip/linux_ifconfig_ib0 new file mode 100644 index 0000000000..17eae58c81 --- /dev/null +++ b/spec/fixtures/unit/util/ip/linux_ifconfig_ib0 @@ -0,0 +1,8 @@ +ib0 Link encap:InfiniBand HWaddr 80:00:00:4a:fe:80:00:00:00:00:00:00:00:02:c9:03:00:43:27:21 + inet addr:10.6.193.12 Bcast:10.6.193.255 Mask:255.255.255.0 + inet6 addr: fe80::202:c903:43:2721/64 Scope:Link + UP BROADCAST RUNNING MULTICAST MTU:65520 Metric:1 + RX packets:8 errors:0 dropped:0 overruns:0 frame:0 + TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:1024 + RX bytes:448 (448.0 b) TX bytes:0 (0.0 b) \ No newline at end of file diff --git a/spec/unit/util/ip_spec.rb b/spec/unit/util/ip_spec.rb index e4bc056c78..56f216c0f7 100755 --- a/spec/unit/util/ip_spec.rb +++ b/spec/unit/util/ip_spec.rb @@ -207,6 +207,15 @@ Facter::Util::IP.get_interface_value("em1", "netmask").should == "255.255.255.0" end + it "should return correct macaddress information for infiniband on Linux" do + ifconfig_interface = my_fixture_read("linux_ifconfig_ib0") + + Facter::Util::IP.expects(:get_single_interface_output).with("ib0").returns(ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("Linux") + + Facter::Util::IP.get_interface_value("ib0", "macaddress").should == "80:00:00:4a:fe:80:00:00:00:00:00:00:00:02:c9:03:00:43:27:21" + end + it "should not get bonding master on interface aliases" do Facter.stubs(:value).with(:kernel).returns("Linux") From 0f15e41774d17e27ebc4d440a13170368214c8ce Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Wed, 19 Sep 2012 15:08:54 -0700 Subject: [PATCH 1002/3753] fail better in package repo rake tasks This commit modifies the top level Rakefile to only load the packaging repo yaml file if it exists, as well as provide some error handling for other unwanted conditions that may arise as a result of trying to set up the packaging repo. Signed-off-by: Moses Mendoza --- Rakefile | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/Rakefile b/Rakefile index 4e82d84540..b84ec9e3e0 100644 --- a/Rakefile +++ b/Rakefile @@ -10,7 +10,6 @@ require 'rubygems' require 'rspec' require 'rspec/core/rake_task' require 'rake' -require 'yaml' begin require 'rcov' @@ -20,29 +19,35 @@ end Dir['tasks/**/*.rake'].each { |t| load t } Dir['ext/packaging/tasks/**/*'].sort.each { |t| load t } -begin - @build_defaults ||= YAML.load_file('ext/build_defaults.yaml') +build_defs_file = 'ext/build_defaults.yaml' +if File.exist?(build_defs_file) + begin + require 'yaml' + @build_defaults ||= YAML.load_file(build_defs_file) + rescue Exception => e + STDERR.puts "Unable to load yaml from #{build_defs_file}:" + STDERR.puts e + end @packaging_url = @build_defaults['packaging_url'] @packaging_repo = @build_defaults['packaging_repo'] -rescue - STDERR.puts "Unable to read the packaging repo info from ext/build_defaults.yaml" -end + raise "Could not find packaging url in #{build_defs_file}" if @packaging_url.nil? + raise "Could not find packaging repo in #{build_defs_file}" if @packaging_repo.nil? -namespace :package do - desc "Bootstrap packaging automation, e.g. clone into packaging repo" - task :bootstrap do - if File.exist?("ext/#{@packaging_repo}") - puts "It looks like you already have ext/#{@packaging_repo}. If you don't like it, blow it away with package:implode." - else - cd 'ext' do - %x{git clone #{@packaging_url}} + namespace :package do + desc "Bootstrap packaging automation, e.g. clone into packaging repo" + task :bootstrap do + if File.exist?("ext/#{@packaging_repo}") + puts "It looks like you already have ext/#{@packaging_repo}. If you don't like it, blow it away with package:implode." + else + cd 'ext' do + %x{git clone #{@packaging_url}} + end end end - end - - desc "Remove all cloned packaging automation" - task :implode do - rm_rf "ext/#{@packaging_repo}" + desc "Remove all cloned packaging automation" + task :implode do + rm_rf "ext/#{@packaging_repo}" + end end end From 4c30c0c1c69d17532d832990fc5b06dd0447b4ec Mon Sep 17 00:00:00 2001 From: Michael Renz Date: Wed, 19 Sep 2012 18:33:42 -0400 Subject: [PATCH 1003/3753] #1415 added two methods and spec tests --- lib/facter/util/ip.rb | 35 +++++++++++-------- .../util/ip/linux_get_single_interface_ib0 | 8 +++++ spec/fixtures/unit/util/ip/linux_ifconfig_ib0 | 2 +- spec/unit/util/ip_spec.rb | 26 ++++++++++++-- 4 files changed, 54 insertions(+), 17 deletions(-) create mode 100644 spec/fixtures/unit/util/ip/linux_get_single_interface_ib0 diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index be0ab96b48..af4c1f3021 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -73,9 +73,7 @@ def self.get_interfaces def self.get_all_interface_output case Facter.value(:kernel) - when 'OpenBSD', 'NetBSD', 'FreeBSD', 'Darwin', 'GNU/kFreeBSD', 'DragonFly' - output = %x{/sbin/ifconfig -a} - when 'Linux' + when 'Linux', 'OpenBSD', 'NetBSD', 'FreeBSD', 'Darwin', 'GNU/kFreeBSD', 'DragonFly' output = %x{/sbin/ifconfig -a 2>/dev/null} when 'SunOS' output = %x{/usr/sbin/ifconfig -a} @@ -88,23 +86,32 @@ def self.get_all_interface_output output end + def self.get_infiniband_macaddress(interface) + if File::exist?("/sys/class/net/#{interface}/address") then + ib_mac_address = `cat /sys/class/net/#{interface}/address`.chomp + elsif File::exist?("/sbin/ip") then + ip_output = %x{/sbin/ip link show #{interface}} + ib_mac_address = ip_output.scan(%r{infiniband\s+((\w{1,2}:){5,}\w{1,2})}) + else + ib_mac_address = "FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF" + Facter.debug("ip.rb: nothing under /sys/class/net/#{interface}/address and /sbin/ip not available") + end + ib_mac_address + end + + def self.ifconfig_interface(interface) + output = %x{/sbin/ifconfig #{interface} 2>/dev/null} + end + def self.get_single_interface_output(interface) output = "" case Facter.value(:kernel) when 'OpenBSD', 'NetBSD', 'FreeBSD', 'Darwin', 'GNU/kFreeBSD', 'DragonFly' - output = %x{/sbin/ifconfig #{interface}} + output = Facter::Util::IP.ifconfig_interface(interface) when 'Linux' - ifconfig_output = %x{/sbin/ifconfig #{interface} 2>/dev/null} + ifconfig_output = Facter::Util::IP.ifconfig_interface(interface) if interface =~ /^ib/ then - if File::exist?("/sys/class/net/#{interface}/address") then - real_mac_address = `cat /sys/class/net/#{interface}/address`.chomp - elsif File::exist?("/sbin/ip") then - ip_output = %x{/sbin/ip link show #{interface}} - real_mac_address = ip_output.scan(%r{infiniband\s+((\w{1,2}:){5,}\w{1,2})}) - else - real_mac_address = "FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF" - Facter.debug("ip.rb: nothing under /sys/class/net/#{interface}/address and /sbin/ip not available") - end + real_mac_address = get_infiniband_macaddress(interface) output = ifconfig_output.sub(%r{(?:ether|HWaddr)\s+((\w{1,2}:){5,}\w{1,2})}, "HWaddr #{real_mac_address}") else output = ifconfig_output diff --git a/spec/fixtures/unit/util/ip/linux_get_single_interface_ib0 b/spec/fixtures/unit/util/ip/linux_get_single_interface_ib0 new file mode 100644 index 0000000000..0827b98620 --- /dev/null +++ b/spec/fixtures/unit/util/ip/linux_get_single_interface_ib0 @@ -0,0 +1,8 @@ +ib0 Link encap:InfiniBand HWaddr 80:00:00:4a:fe:80:00:00:00:00:00:00:00:02:c9:03:00:43:27:21 + inet addr:10.6.193.12 Bcast:10.6.193.255 Mask:255.255.255.0 + inet6 addr: fe80::202:c903:43:2721/64 Scope:Link + UP BROADCAST RUNNING MULTICAST MTU:65520 Metric:1 + RX packets:8 errors:0 dropped:0 overruns:0 frame:0 + TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:1024 + RX bytes:448 (448.0 b) TX bytes:0 (0.0 b) \ No newline at end of file diff --git a/spec/fixtures/unit/util/ip/linux_ifconfig_ib0 b/spec/fixtures/unit/util/ip/linux_ifconfig_ib0 index 17eae58c81..74cf3014e5 100644 --- a/spec/fixtures/unit/util/ip/linux_ifconfig_ib0 +++ b/spec/fixtures/unit/util/ip/linux_ifconfig_ib0 @@ -1,4 +1,4 @@ -ib0 Link encap:InfiniBand HWaddr 80:00:00:4a:fe:80:00:00:00:00:00:00:00:02:c9:03:00:43:27:21 +ib0 Link encap:InfiniBand HWaddr 80:00:00:4A:FE:80:00:00:00:00:00:00:00:00:00:00:00:00:00:00 inet addr:10.6.193.12 Bcast:10.6.193.255 Mask:255.255.255.0 inet6 addr: fe80::202:c903:43:2721/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:65520 Metric:1 diff --git a/spec/unit/util/ip_spec.rb b/spec/unit/util/ip_spec.rb index 56f216c0f7..1159b9c3f0 100755 --- a/spec/unit/util/ip_spec.rb +++ b/spec/unit/util/ip_spec.rb @@ -208,14 +208,36 @@ end it "should return correct macaddress information for infiniband on Linux" do - ifconfig_interface = my_fixture_read("linux_ifconfig_ib0") + correct_ifconfig_interface = my_fixture_read("linux_get_single_interface_ib0") - Facter::Util::IP.expects(:get_single_interface_output).with("ib0").returns(ifconfig_interface) + Facter::Util::IP.expects(:get_single_interface_output).with("ib0").returns(correct_ifconfig_interface) Facter.stubs(:value).with(:kernel).returns("Linux") Facter::Util::IP.get_interface_value("ib0", "macaddress").should == "80:00:00:4a:fe:80:00:00:00:00:00:00:00:02:c9:03:00:43:27:21" end + it "should replace the incorrect macaddress with the correct macaddress in ifconfig for infiniband on Linux" do + ifconfig_interface = my_fixture_read("linux_ifconfig_ib0") + correct_ifconfig_interface = my_fixture_read("linux_get_single_interface_ib0") + + Facter::Util::IP.expects(:get_infiniband_macaddress).with("ib0").returns("80:00:00:4a:fe:80:00:00:00:00:00:00:00:02:c9:03:00:43:27:21") + Facter::Util::IP.expects(:ifconfig_interface).with("ib0").returns(ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("Linux") + + Facter::Util::IP.get_single_interface_output("ib0").should == correct_ifconfig_interface + end + + it "should return fake macaddress information for infiniband on Linux when neither sysfs or /sbin/ip are available" do + ifconfig_interface = my_fixture_read("linux_ifconfig_ib0") + + File.stubs(:exists?).with("/sys/class/net/ib0/address").returns(false) + File.stubs(:exists?).with("/sbin/ip").returns(false) + Facter::Util::IP.expects(:ifconfig_interface).with("ib0").returns(ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("Linux") + + Facter::Util::IP.get_interface_value("ib0", "macaddress").should == "FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF" + end + it "should not get bonding master on interface aliases" do Facter.stubs(:value).with(:kernel).returns("Linux") From b06708e3ab1f518a68f5475af9e0aa6bd33034c3 Mon Sep 17 00:00:00 2001 From: Michael Renz Date: Wed, 19 Sep 2012 18:39:15 -0400 Subject: [PATCH 1004/3753] ifconfig_interface should output its output --- lib/facter/util/ip.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index af4c1f3021..595e0c97a2 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -101,6 +101,7 @@ def self.get_infiniband_macaddress(interface) def self.ifconfig_interface(interface) output = %x{/sbin/ifconfig #{interface} 2>/dev/null} + output end def self.get_single_interface_output(interface) From 8c86551c1e437453ad591e3294b1ceb181d5aa33 Mon Sep 17 00:00:00 2001 From: Michael Renz Date: Wed, 19 Sep 2012 18:44:58 -0400 Subject: [PATCH 1005/3753] #1415 fixed the regex map --- lib/facter/util/ip.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index 595e0c97a2..4551757e89 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -7,7 +7,7 @@ module Facter::Util::IP :linux => { :ipaddress => /inet addr:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, :ipaddress6 => /inet6 addr: ((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/, - :macaddress => /(?:ether|HWaddr)\s+(\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/, + :macaddress => /(?:ether|HWaddr)\s+((\w{1,2}:){5,}\w{1,2})/, :netmask => /Mask:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ }, :bsd => { From 29f5e22b1721c32338226f6b76e88e91244a2369 Mon Sep 17 00:00:00 2001 From: Michael Renz Date: Wed, 19 Sep 2012 19:00:13 -0400 Subject: [PATCH 1006/3753] #1415 ifconfig_interface change. --- lib/facter/util/ip.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index 4551757e89..1c63e0a6c2 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -100,8 +100,7 @@ def self.get_infiniband_macaddress(interface) end def self.ifconfig_interface(interface) - output = %x{/sbin/ifconfig #{interface} 2>/dev/null} - output + %x{/sbin/ifconfig #{interface} 2>/dev/null} end def self.get_single_interface_output(interface) From 0bfca9ec8b5c31a264e3871ce09e90ed1ccb7c1d Mon Sep 17 00:00:00 2001 From: Michael Renz Date: Wed, 19 Sep 2012 19:08:37 -0400 Subject: [PATCH 1007/3753] #1415 updated ipaddress6_spec.rb and macaddress_spec.rb to reflect updated ifconfig STDERR redirect --- spec/unit/ipaddress6_spec.rb | 2 +- spec/unit/macaddress_spec.rb | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/unit/ipaddress6_spec.rb b/spec/unit/ipaddress6_spec.rb index cf4d8bfb22..fc03e62f02 100755 --- a/spec/unit/ipaddress6_spec.rb +++ b/spec/unit/ipaddress6_spec.rb @@ -28,7 +28,7 @@ def netsh_fixture(filename) it "should return ipaddress6 information for Linux" do Facter::Util::Resolution.stubs(:exec).with('uname -s').returns('Linux') - Facter::Util::Resolution.stubs(:exec).with('/sbin/ifconfig'). + Facter::Util::Resolution.stubs(:exec).with('/sbin/ifconfig 2>/dev/null'). returns(ifconfig_fixture('linux_ifconfig_all_with_multiple_interfaces')) Facter.value(:ipaddress6).should == "2610:10:20:209:212:3fff:febe:2201" diff --git a/spec/unit/macaddress_spec.rb b/spec/unit/macaddress_spec.rb index c2b6c9473a..1bdbf5674c 100755 --- a/spec/unit/macaddress_spec.rb +++ b/spec/unit/macaddress_spec.rb @@ -24,14 +24,14 @@ def netsh_fixture(filename) end it "should return the macaddress of the first interface" do - Facter::Util::Resolution.stubs(:exec).with('/sbin/ifconfig -a'). + Facter::Util::Resolution.stubs(:exec).with('/sbin/ifconfig -a 2>/dev/null'). returns(ifconfig_fixture('linux_ifconfig_all_with_multiple_interfaces')) Facter.value(:macaddress).should == "00:12:3f:be:22:01" end it "should return nil when no macaddress can be found" do - Facter::Util::Resolution.stubs(:exec).with('/sbin/ifconfig -a'). + Facter::Util::Resolution.stubs(:exec).with('/sbin/ifconfig -a 2>/dev/null'). returns(ifconfig_fixture('linux_ifconfig_no_mac')) proc { Facter.value(:macaddress) }.should_not raise_error @@ -40,7 +40,7 @@ def netsh_fixture(filename) # some interfaces dont have a real mac addresses (like venet inside a container) it "should return nil when no interface has a real macaddress" do - Facter::Util::Resolution.stubs(:exec).with('/sbin/ifconfig -a'). + Facter::Util::Resolution.stubs(:exec).with('/sbin/ifconfig -a 2>/dev/null'). returns(ifconfig_fixture('linux_ifconfig_venet')) proc { Facter.value(:macaddress) }.should_not raise_error From caed72f27ff6ab134602a0095e17af9f50458c4d Mon Sep 17 00:00:00 2001 From: Todd Zullinger Date: Thu, 21 Jun 2012 15:10:11 -0400 Subject: [PATCH 1008/3753] Improve rubysitedir fact With ruby-1.9.3 -- at least on Fedora 17 -- trawling through the library paths looking for the directory does not work. Using RbConfig seems more reliable and much simpler. --- lib/facter/rubysitedir.rb | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/lib/facter/rubysitedir.rb b/lib/facter/rubysitedir.rb index 73aa2152fc..930df383af 100644 --- a/lib/facter/rubysitedir.rb +++ b/lib/facter/rubysitedir.rb @@ -2,17 +2,11 @@ # # Purpose: Returns Ruby's site library directory. # -# Resolution: Works out the version to major/minor (1.8, 1.9, etc), then joins -# that with all the $: library paths. -# -# Caveats: -# + +require 'rbconfig' Facter.add :rubysitedir do setcode do - version = RUBY_VERSION.to_s.sub(/\.\d+$/, '') - $:.find do |dir| - dir =~ /#{File.join("site_ruby", version)}$/ - end + RbConfig::CONFIG["sitelibdir"] end end From 41fc7e078bd0e79deadaaf76f261906b90fc1cc0 Mon Sep 17 00:00:00 2001 From: Matthaus Litteken Date: Thu, 20 Sep 2012 12:05:43 -0700 Subject: [PATCH 1009/3753] (#5205) Update facter manpage This commit doesn't handle the request in the ticket of dynamically generated manpage, but it does update the man page to have updated flags and help, correct license, copyright holder and copyright year. It also fills in the name of the program for the manpage so that the header doesn't look quite so mysterious. --- man/man8/facter.8 | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/man/man8/facter.8 b/man/man8/facter.8 index 7bfec86dca..83c5812727 100644 --- a/man/man8/facter.8 +++ b/man/man8/facter.8 @@ -1,6 +1,6 @@ -.TH "" "" "" +.TH "FACTER" "8" "September 2012" "Puppet Labs, Inc" "Facter manual" .SH NAME - \- +Facter - Collect and display facts about the system. .\" Man page generated from reStructeredText. . .SH SYNOPSIS @@ -9,8 +9,7 @@ Collect and display facts about the system. .SH USAGE .INDENT 0.0 .INDENT 3.5 -.sp -facter [\-d|\-\-debug] [\-h|\-\-help] [\-p|\-\-puppet] [\-v|\-\-version] [\-y|\-\-yaml] [fact] [fact] [...] +facter [\-d|\-\-debug] [\-h|\-\-help] [\-p|\-\-puppet] [\-v|\-\-version] [\-y|\-\-yaml] [\-j|\-\-json] [fact] [fact] [...] .UNINDENT .UNINDENT .SH DESCRIPTION @@ -22,23 +21,27 @@ information about a system from within the shell or within Ruby. If no facts are specifically asked for, then all facts will be returned. .SH OPTIONS .sp -debug: Enable debugging. +yaml: Emit facts in YAML format. .sp -help: Print this help message +json: Emit facts in JSON format. .INDENT 0.0 .TP .B puppet: Load the Puppet libraries, thus allowing Facter to load -. Puppet\-specific facts. .UNINDENT .sp version: Print the version and exit. .sp -yaml: Emit facts in YAML format. +help: Print this help message. +.sp +debug: Enable debugging. +.sp +trace: Enable backtraces. +.sp +timing: Enable timing. .SH EXAMPLE .INDENT 0.0 .INDENT 3.5 -.sp facter kernel .UNINDENT .UNINDENT @@ -47,8 +50,8 @@ facter kernel Luke Kanies .SH COPYRIGHT .sp -Copyright (c) 2006 Reductive Labs, LLC Licensed under the GNU Public -License +Copyright (c) 2012 Puppet Labs, Inc Licensed under the Apache 2.0 +license .\" Generated by docutils manpage writer. -.\" +.\" . From 8083990b4545e8a80c70fe61c3952e7a127db561 Mon Sep 17 00:00:00 2001 From: Matthaus Owens Date: Thu, 20 Sep 2012 20:38:44 +0000 Subject: [PATCH 1010/3753] Update facter debian packaging for 1.9 support Because facter works on ruby 1.8 and 1.9, putting it in the vendordir instead of the vendorlibdir or rubylibdir is preferred so that either ruby can be used with facter. This updates the dependencies to bring in ruby instead of a specific ruby version and also replaces ruby1.8 calls in rules with ruby calls. --- ext/debian/control | 4 ++-- ext/debian/rules | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/ext/debian/control b/ext/debian/control index 37edfdd60e..a61fa844cc 100644 --- a/ext/debian/control +++ b/ext/debian/control @@ -2,13 +2,13 @@ Source: facter Section: ruby Priority: optional Maintainer: Puppet Labs -Build-Depends: cdbs, debhelper (>> 7), ruby1.8 | ruby1.9.1, libopenssl-ruby1.8 | libopenssl-ruby1.9.1 +Build-Depends: cdbs, debhelper (>> 7), ruby | ruby-interpreter, libopenssl-ruby | libopenssl-ruby1.8 | libopenssl-ruby1.9.1, rdoc Standards-Version: 3.9.1 Homepage: http://www.puppetlabs.com Package: facter Architecture: all -Depends: ${shlibs:Depends}, ${misc:Depends}, ruby1.8 (>= 1.8.5) | ruby1.9.1, libopenssl-ruby1.8 | libopenssl-ruby1.9.1, dmidecode, pciutils +Depends: ${shlibs:Depends}, ${misc:Depends}, ruby | ruby-interpreter, libopenssl-ruby | libopenssl-ruby1.8 | libopenssl-ruby1.9.1, dmidecode, pciutils Description: Ruby module for collecting simple facts about a host operating system Some of the facts are preconfigured, such as the hostname and the operating system. Additional facts can be added through simple Ruby scripts. diff --git a/ext/debian/rules b/ext/debian/rules index 609b5ef256..7000d3e42e 100755 --- a/ext/debian/rules +++ b/ext/debian/rules @@ -3,7 +3,9 @@ include /usr/share/cdbs/1/rules/debhelper.mk include /usr/share/cdbs/1/rules/buildcore.mk -LIBDIR=$(shell /usr/bin/ruby1.8 -rrbconfig -e 'puts Config::CONFIG["rubylibdir"]') +LIBDIR=$(shell /usr/bin/ruby -rrbconfig -e 'puts Config::CONFIG["vendordir"]') +BINDIR=$(shell /usr/bin/ruby -rrbconfig -e 'puts Config::CONFIG["bindir"]') binary-install/facter:: - /usr/bin/ruby1.8 install.rb --sitelibdir=$(LIBDIR) --destdir=$(CURDIR)/debian/$(cdbs_curpkg) --quick --no-rdoc + /usr/bin/ruby install.rb --sitelibdir=$(LIBDIR) --destdir=$(CURDIR)/debian/$(cdbs_curpkg) --quick + cp -p $(CURDIR)/bin/facter $(CURDIR)/debian/$(cdbs_curpkg)/$(BINDIR) From 5f64f45b6c7a5bd8db949ec849d35406b9eadfa6 Mon Sep 17 00:00:00 2001 From: Garrett Honeycutt Date: Thu, 20 Sep 2012 13:53:08 -0700 Subject: [PATCH 1011/3753] removes trailing whitespace --- man/man8/facter.8 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/man8/facter.8 b/man/man8/facter.8 index 83c5812727..9c6ea54010 100644 --- a/man/man8/facter.8 +++ b/man/man8/facter.8 @@ -53,5 +53,5 @@ Luke Kanies Copyright (c) 2012 Puppet Labs, Inc Licensed under the Apache 2.0 license .\" Generated by docutils manpage writer. -.\" +.\" . From 561c156114c2b8632081ee23066973e22dd73b27 Mon Sep 17 00:00:00 2001 From: Garrett Honeycutt Date: Thu, 20 Sep 2012 13:57:09 -0700 Subject: [PATCH 1012/3753] fixes formatting bug in man page --- man/man8/facter.8 | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/man/man8/facter.8 b/man/man8/facter.8 index 9c6ea54010..e4e263ab25 100644 --- a/man/man8/facter.8 +++ b/man/man8/facter.8 @@ -24,11 +24,8 @@ If no facts are specifically asked for, then all facts will be returned. yaml: Emit facts in YAML format. .sp json: Emit facts in JSON format. -.INDENT 0.0 -.TP -.B puppet: Load the Puppet libraries, thus allowing Facter to load -Puppet\-specific facts. -.UNINDENT +.sp +puppet: Load the Puppet libraries, thus allowing Facter to load Puppet specific (custom) facts .sp version: Print the version and exit. .sp From 53b7eccb5a3189b72ebd604a3aa01f819f3f6dec Mon Sep 17 00:00:00 2001 From: Garrett Honeycutt Date: Thu, 20 Sep 2012 13:58:31 -0700 Subject: [PATCH 1013/3753] removes generator comments --- man/man8/facter.8 | 3 --- 1 file changed, 3 deletions(-) diff --git a/man/man8/facter.8 b/man/man8/facter.8 index e4e263ab25..b741ba67e4 100644 --- a/man/man8/facter.8 +++ b/man/man8/facter.8 @@ -1,7 +1,6 @@ .TH "FACTER" "8" "September 2012" "Puppet Labs, Inc" "Facter manual" .SH NAME Facter - Collect and display facts about the system. -.\" Man page generated from reStructeredText. . .SH SYNOPSIS .sp @@ -49,6 +48,4 @@ Luke Kanies .sp Copyright (c) 2012 Puppet Labs, Inc Licensed under the Apache 2.0 license -.\" Generated by docutils manpage writer. -.\" . From ce581f83f7093f48fcee4389fa4bd178374011e7 Mon Sep 17 00:00:00 2001 From: Garrett Honeycutt Date: Thu, 20 Sep 2012 14:02:57 -0700 Subject: [PATCH 1014/3753] fixes spacing on puppet option to be in line --- man/man8/facter.8 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/man8/facter.8 b/man/man8/facter.8 index b741ba67e4..476c8ce55c 100644 --- a/man/man8/facter.8 +++ b/man/man8/facter.8 @@ -24,7 +24,7 @@ yaml: Emit facts in YAML format. .sp json: Emit facts in JSON format. .sp -puppet: Load the Puppet libraries, thus allowing Facter to load Puppet specific (custom) facts +puppet: Load the Puppet libraries, thus allowing Facter to load Puppet specific (custom) facts .sp version: Print the version and exit. .sp From eb871750cb53870844877ebd07e9f14f3726ad82 Mon Sep 17 00:00:00 2001 From: Matthaus Litteken Date: Thu, 20 Sep 2012 15:21:34 -0700 Subject: [PATCH 1015/3753] (maint) Replace deprecated Config with RbConfig in debian/rules --- ext/debian/rules | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/debian/rules b/ext/debian/rules index 7000d3e42e..69ac55d8ea 100755 --- a/ext/debian/rules +++ b/ext/debian/rules @@ -3,8 +3,8 @@ include /usr/share/cdbs/1/rules/debhelper.mk include /usr/share/cdbs/1/rules/buildcore.mk -LIBDIR=$(shell /usr/bin/ruby -rrbconfig -e 'puts Config::CONFIG["vendordir"]') -BINDIR=$(shell /usr/bin/ruby -rrbconfig -e 'puts Config::CONFIG["bindir"]') +LIBDIR=$(shell /usr/bin/ruby -rrbconfig -e 'puts RbConfig::CONFIG["vendordir"]') +BINDIR=$(shell /usr/bin/ruby -rrbconfig -e 'puts RbConfig::CONFIG["bindir"]') binary-install/facter:: /usr/bin/ruby install.rb --sitelibdir=$(LIBDIR) --destdir=$(CURDIR)/debian/$(cdbs_curpkg) --quick From 869747266cdac0279587bb16379dfb167b7bfe82 Mon Sep 17 00:00:00 2001 From: Matthaus Litteken Date: Fri, 21 Sep 2012 14:00:20 -0700 Subject: [PATCH 1016/3753] Remove sbin references from install.rb Facter has never shipped sbin files, so install.rb no longer needs to reference those variables or accept those flags. This commit removes the --sbindir flag as well as all other mention of sbin. --- install.rb | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/install.rb b/install.rb index 909171f21f..d1a5cea01a 100755 --- a/install.rb +++ b/install.rb @@ -82,10 +82,9 @@ def glob(list) end # Set these values to what you want installed. -sbins = glob(%w{sbin/*}) bins = glob(%w{bin/*}) -rdoc = glob(%w{bin/* sbin/* lib/**/*.rb README README-library CHANGELOG TODO Install}).reject { |e| e=~ /\.(bat|cmd)$/ } -ri = glob(%w(bin/*.rb sbin/* lib/**/*.rb)).reject { |e| e=~ /\.(bat|cmd)$/ } +rdoc = glob(%w{bin/* lib/**/*.rb README README-library CHANGELOG TODO Install}).reject { |e| e=~ /\.(bat|cmd)$/ } +ri = glob(%w(bin/*.rb lib/**/*.rb)).reject { |e| e=~ /\.(bat|cmd)$/ } man = glob(%w{man/man8/*}) libs = glob(%w{lib/**/*.rb lib/**/*.py}) tests = glob(%w{tests/**/*.rb}) @@ -190,8 +189,6 @@ def prepare_installation opts.on('--bindir[=OPTIONAL]', 'Installation directory for binaries', 'overrides RbConfig::CONFIG["bindir"]') do |bindir| InstallOptions.bindir = bindir end - opts.on('--sbindir[=OPTIONAL]', 'Installation directory for system binaries', 'overrides RbConfig::CONFIG["sbindir"]') do |sbindir| - InstallOptions.sbindir = sbindir end opts.on('--sitelibdir[=OPTIONAL]', 'Installation directory for libraries', 'overrides RbConfig::CONFIG["sitelibdir"]') do |sitelibdir| InstallOptions.sitelibdir = sitelibdir @@ -221,14 +218,12 @@ def prepare_installation version = [RbConfig::CONFIG["MAJOR"], RbConfig::CONFIG["MINOR"]].join(".") libdir = File.join(RbConfig::CONFIG["libdir"], "ruby", version) - # Mac OS X 10.5 and higher declare bindir and sbindir as + # Mac OS X 10.5 and higher declare bindir # /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin - # /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/sbin # which is not generally where people expect executables to be installed # These settings are appropriate defaults for all OS X versions. if RUBY_PLATFORM =~ /^universal-darwin[\d\.]+$/ RbConfig::CONFIG['bindir'] = "/usr/bin" - RbConfig::CONFIG['sbindir'] = "/usr/sbin" end if not InstallOptions.bindir.nil? @@ -237,12 +232,6 @@ def prepare_installation bindir = RbConfig::CONFIG['bindir'] end - if not InstallOptions.sbindir.nil? - sbindir = InstallOptions.sbindir - else - sbindir = RbConfig::CONFIG['sbindir'] - end - if not InstallOptions.sitelibdir.nil? sitelibdir = InstallOptions.sitelibdir else @@ -267,30 +256,25 @@ def prepare_installation if (destdir = ENV['DESTDIR']) warn "DESTDIR is deprecated. Use --destdir instead." bindir = join(destdir, bindir) - sbindir = join(destdir, sbindir) mandir = join(destdir, mandir) sitelibdir = join(destdir, sitelibdir) FileUtils.makedirs(bindir) - FileUtils.makedirs(sbindir) FileUtils.makedirs(mandir) FileUtils.makedirs(sitelibdir) # This is the new way forward elsif (destdir = InstallOptions.destdir) bindir = join(destdir, bindir) - sbindir = join(destdir, sbindir) mandir = join(destdir, mandir) sitelibdir = join(destdir, sitelibdir) FileUtils.makedirs(bindir) - FileUtils.makedirs(sbindir) FileUtils.makedirs(mandir) FileUtils.makedirs(sitelibdir) end InstallOptions.site_dir = sitelibdir InstallOptions.bin_dir = bindir - InstallOptions.sbin_dir = sbindir InstallOptions.lib_dir = libdir InstallOptions.man_dir = mandir end @@ -436,7 +420,6 @@ def install_binfile(from, op_file, target) #build_rdoc(rdoc) if InstallOptions.rdoc #build_ri(ri) if InstallOptions.ri #build_man(bins) if InstallOptions.man -do_bins(sbins, InstallOptions.sbin_dir) do_bins(bins, InstallOptions.bin_dir) do_libs(libs) do_man(man) From efe976b4c501cea69742dee24d2d84008e8189e0 Mon Sep 17 00:00:00 2001 From: Matthaus Litteken Date: Fri, 21 Sep 2012 14:04:14 -0700 Subject: [PATCH 1017/3753] Remove RbConfig=>Config monkey_patch RbConfig was monkey_patched to reference Config on older rubies (< 1.8.5). As 1.8.5 is the minimum ruby for facter, this monkey patch is no longer required and should be removed. --- install.rb | 6 ------ 1 file changed, 6 deletions(-) diff --git a/install.rb b/install.rb index d1a5cea01a..1029d8a7df 100755 --- a/install.rb +++ b/install.rb @@ -47,12 +47,6 @@ $haverdoc = false end -# Monkey patch RbConfig->Config for Rubies older then 1.8.5. -unless defined? ::RbConfig - require 'rbconfig' - ::RbConfig = ::Config -end - begin if $haverdoc rst2man = %x{which rst2man.py} From 008a8d71050e62b07b6747e9009b5ae49ca12842 Mon Sep 17 00:00:00 2001 From: Matthaus Litteken Date: Fri, 21 Sep 2012 14:05:31 -0700 Subject: [PATCH 1018/3753] Add --ruby flag to install.rb If the ruby used to install facter isn't the ruby desired to execute facter, this flag will allow users and packages to set their own ruby. This is particularly useful when working with ruby and alternatives. --- install.rb | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/install.rb b/install.rb index 1029d8a7df..9862982394 100755 --- a/install.rb +++ b/install.rb @@ -183,6 +183,8 @@ def prepare_installation opts.on('--bindir[=OPTIONAL]', 'Installation directory for binaries', 'overrides RbConfig::CONFIG["bindir"]') do |bindir| InstallOptions.bindir = bindir end + opts.on('--ruby[=OPTIONAL]', 'Ruby interpreter to use with installation', 'overrides ruby used to call install.rb') do |ruby| + InstallOptions.ruby = ruby end opts.on('--sitelibdir[=OPTIONAL]', 'Installation directory for libraries', 'overrides RbConfig::CONFIG["sitelibdir"]') do |sitelibdir| InstallOptions.sitelibdir = sitelibdir @@ -360,17 +362,18 @@ def run_tests(test_list) def install_binfile(from, op_file, target) tmp_file = Tempfile.new('facter-binfile') - ruby = File.join(RbConfig::CONFIG['bindir'], RbConfig::CONFIG['ruby_install_name']) + if not InstallOptions.ruby.nil? + ruby = InstallOptions.ruby + else + ruby = File.join(RbConfig::CONFIG['bindir'], RbConfig::CONFIG['ruby_install_name']) + end File.open(from) do |ip| File.open(tmp_file.path, "w") do |op| - ruby = File.join(RbConfig::CONFIG['bindir'], RbConfig::CONFIG['ruby_install_name']) op.puts "#!#{ruby}" contents = ip.readlines - if contents[0] =~ /^#!/ - contents.shift - end - op.write contents.join() + contents.shift if contents[0] =~ /^#!/ + op.write contents.join end end From 29af0cd8a05bbc532751fad024064a03979ef943 Mon Sep 17 00:00:00 2001 From: Matthaus Litteken Date: Fri, 21 Sep 2012 14:17:30 -0700 Subject: [PATCH 1019/3753] Remove svn specific code from install.rb We no longer need to exclude .svn files from facter, as it is now using git. --- install.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/install.rb b/install.rb index 9862982394..a2e467ba0b 100755 --- a/install.rb +++ b/install.rb @@ -71,7 +71,6 @@ def glob(list) g = list.map { |i| Dir.glob(i) } g.flatten! g.compact! - g.reject! { |e| e =~ /\.svn/ } g end From 262ef6a27e03a8a3d4e5d28abc9bd692c6ae1a74 Mon Sep 17 00:00:00 2001 From: Michael Renz Date: Tue, 18 Sep 2012 20:37:50 -0400 Subject: [PATCH 1020/3753] (#1415) Added case for Linux/IB to get correct IB address or fail predictably. Originally, facter displayed a truncated infiniband MAC address. Now, If the interface starts with ib, use one of two methods to get the infiniband MAC address or fails with a MAC address of all FF otherwise. --- lib/facter/macaddress.rb | 2 +- lib/facter/util/ip.rb | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/facter/macaddress.rb b/lib/facter/macaddress.rb index 6af30aab31..46f3c58caf 100644 --- a/lib/facter/macaddress.rb +++ b/lib/facter/macaddress.rb @@ -15,7 +15,7 @@ ether = [] output = Facter::Util::Resolution.exec("/sbin/ifconfig -a") output.each_line do |s| - ether.push($1) if s =~ /(?:ether|HWaddr) (\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/ + ether.push($1) if s =~ /(?:ether|HWaddr) ((\w{1,2}:){5,}\w{1,2})/ end Facter::Util::Macaddress.standardize(ether[0]) end diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index c45c416564..f5bd1eccd5 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -89,8 +89,24 @@ def self.get_all_interface_output def self.get_single_interface_output(interface) output = "" case Facter.value(:kernel) - when 'Linux', 'OpenBSD', 'NetBSD', 'FreeBSD', 'Darwin', 'GNU/kFreeBSD', 'DragonFly' + when 'OpenBSD', 'NetBSD', 'FreeBSD', 'Darwin', 'GNU/kFreeBSD', 'DragonFly' output = %x{/sbin/ifconfig #{interface}} + when 'Linux' + ifconfig_output = %x{/sbin/ifconfig #{interface} 2>/dev/null} + if interface =~ /^ib/ then + if File::exist?("/sys/class/net/#{interface}/address") then + real_mac_address = File.read("/sys/class/net/#{interface}/address").chomp + elsif File::exist?("/sbin/ip") then + ip_output = %x{/sbin/ip link show #{interface}} + real_mac_address = ip_output.scan(%r{infiniband\s+((\w{1,2}:){5,}\w{1,2})}) + else + real_mac_address = "FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF" + Facter.debug("ip.rb: nothing under /sys/class/net/#{interface}/address and /sbin/ip not available") + end + output = ifconfig_output.sub(%r{(?:ether|HWaddr)\s+((\w{1,2}:){5,}\w{1,2})}, "HWaddr #{real_mac_address}") + else + output = %x{/sbin/ifconfig #{interface}} + end when 'SunOS' output = %x{/usr/sbin/ifconfig #{interface}} when 'HP-UX' From 240dfac1f87e5797a4e3ba64ec20392f8fdee9af Mon Sep 17 00:00:00 2001 From: Michael Renz Date: Wed, 19 Sep 2012 09:00:47 -0400 Subject: [PATCH 1021/3753] (#1415) ifconfig should not run twice for Linux. The prior commit had ifconfig run twice on Linux systems. This commit fixes it so that ifconfig runs only once. --- lib/facter/util/ip.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index f5bd1eccd5..89c3655dfc 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -105,7 +105,7 @@ def self.get_single_interface_output(interface) end output = ifconfig_output.sub(%r{(?:ether|HWaddr)\s+((\w{1,2}:){5,}\w{1,2})}, "HWaddr #{real_mac_address}") else - output = %x{/sbin/ifconfig #{interface}} + output = ifconfig_output end when 'SunOS' output = %x{/usr/sbin/ifconfig #{interface}} From d4d0b8007108288054eaf51a9c9f3fb74b0954c5 Mon Sep 17 00:00:00 2001 From: Michael Renz Date: Wed, 19 Sep 2012 09:55:51 -0400 Subject: [PATCH 1022/3753] (#1415) Handles ruby hanging of File.read of /sys/class/ with cat. The principle reporter of this bug was concerned that File.read would hang certain versions of Ruby. This commit changes File.read to backticked cat to fix that. --- lib/facter/util/ip.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index 89c3655dfc..ba2b89710e 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -95,7 +95,7 @@ def self.get_single_interface_output(interface) ifconfig_output = %x{/sbin/ifconfig #{interface} 2>/dev/null} if interface =~ /^ib/ then if File::exist?("/sys/class/net/#{interface}/address") then - real_mac_address = File.read("/sys/class/net/#{interface}/address").chomp + real_mac_address = `cat /sys/class/net/#{interface}/address`.chomp elsif File::exist?("/sbin/ip") then ip_output = %x{/sbin/ip link show #{interface}} real_mac_address = ip_output.scan(%r{infiniband\s+((\w{1,2}:){5,}\w{1,2})}) From 1c43b911a8e0d99133ad330e57cbd0c562259900 Mon Sep 17 00:00:00 2001 From: Michael Renz Date: Wed, 19 Sep 2012 16:39:14 -0400 Subject: [PATCH 1023/3753] (#16005) Puppet agent generates ifconfig warnings on InfiniBand systems fix Whenever ifconfig was called on systems with Ib interfaces, it would echo direcly to STDERR. This commit suppresses ifconfig STDERR messages. --- lib/facter/ipaddress.rb | 2 +- lib/facter/ipaddress6.rb | 2 +- lib/facter/macaddress.rb | 27 ++++++++++++++++++++++++++- lib/facter/util/ip.rb | 4 +++- lib/facter/util/netmask.rb | 2 +- 5 files changed, 32 insertions(+), 5 deletions(-) diff --git a/lib/facter/ipaddress.rb b/lib/facter/ipaddress.rb index 360becd2ea..1d0f2886d0 100644 --- a/lib/facter/ipaddress.rb +++ b/lib/facter/ipaddress.rb @@ -26,7 +26,7 @@ confine :kernel => :linux setcode do ip = nil - output = %x{/sbin/ifconfig} + output = %x{/sbin/ifconfig 2>/dev/null} output.split(/^\S/).each { |str| if str =~ /inet addr:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ diff --git a/lib/facter/ipaddress6.rb b/lib/facter/ipaddress6.rb index 16b70fb135..7d2885fb6e 100644 --- a/lib/facter/ipaddress6.rb +++ b/lib/facter/ipaddress6.rb @@ -38,7 +38,7 @@ def get_address_after_token(output, token, return_first=false) Facter.add(:ipaddress6) do confine :kernel => :linux setcode do - output = Facter::Util::Resolution.exec('/sbin/ifconfig') + output = Facter::Util::Resolution.exec('/sbin/ifconfig 2>/dev/null') get_address_after_token(output, 'inet6 addr:') end diff --git a/lib/facter/macaddress.rb b/lib/facter/macaddress.rb index 46f3c58caf..377da1badc 100644 --- a/lib/facter/macaddress.rb +++ b/lib/facter/macaddress.rb @@ -10,7 +10,32 @@ require 'facter/util/macaddress' Facter.add(:macaddress) do - confine :kernel => %w{SunOS Linux GNU/kFreeBSD} + confine :kernel => 'Linux' + has_weight 10 # about an order of magnitude faster + setcode do + begin + Dir.glob('/sys/class/net/*').reject {|x| x[-3..-1] == '/lo' }.first + path and File.read(path + '/address') + rescue Exception + nil + end + end +end + +Facter.add(:macaddress) do + confine :kernel => 'Linux' + setcode do + ether = [] + output = Facter::Util::Resolution.exec("/sbin/ifconfig -a 2>/dev/null") + output.each_line do |s| + ether.push($1) if s =~ /(?:ether|HWaddr) ((\w{1,2}:){5,}\w{1,2})/ + end + Facter::Util::Macaddress.standardize(ether[0]) + end +end + +Facter.add(:macaddress) do + confine :kernel => %w{SunOS GNU/kFreeBSD} setcode do ether = [] output = Facter::Util::Resolution.exec("/sbin/ifconfig -a") diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index ba2b89710e..bae8b5763a 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -73,8 +73,10 @@ def self.get_interfaces def self.get_all_interface_output case Facter.value(:kernel) - when 'Linux', 'OpenBSD', 'NetBSD', 'FreeBSD', 'Darwin', 'GNU/kFreeBSD', 'DragonFly' + when 'OpenBSD', 'NetBSD', 'FreeBSD', 'Darwin', 'GNU/kFreeBSD', 'DragonFly' output = %x{/sbin/ifconfig -a} + when 'Linux' + output = %x{/sbin/ifconfig -a 2>/dev/null} when 'SunOS' output = %x{/usr/sbin/ifconfig -a} when 'HP-UX' diff --git a/lib/facter/util/netmask.rb b/lib/facter/util/netmask.rb index b78b173ea7..36a212e09f 100644 --- a/lib/facter/util/netmask.rb +++ b/lib/facter/util/netmask.rb @@ -7,7 +7,7 @@ def self.get_netmask case Facter.value(:kernel) when 'Linux' ops = { - :ifconfig => '/sbin/ifconfig', + :ifconfig => '/sbin/ifconfig 2>/dev/null', :regex => %r{\s+ inet\saddr: #{Facter.ipaddress} .*? Mask: (#{ipregex})}x, :munge => nil, } From 6c8f5f539ac83a668ffc1576d0f291fdff5b1621 Mon Sep 17 00:00:00 2001 From: Michael Renz Date: Wed, 19 Sep 2012 17:05:11 -0400 Subject: [PATCH 1024/3753] (#1415) ip_spec.rb test added for linux_ifconfig_ib0 This adds a basic unit test for ip.rb. Later commits make this unit test more detailed. --- spec/fixtures/unit/util/ip/linux_ifconfig_ib0 | 8 ++++++++ spec/unit/util/ip_spec.rb | 9 +++++++++ 2 files changed, 17 insertions(+) create mode 100644 spec/fixtures/unit/util/ip/linux_ifconfig_ib0 diff --git a/spec/fixtures/unit/util/ip/linux_ifconfig_ib0 b/spec/fixtures/unit/util/ip/linux_ifconfig_ib0 new file mode 100644 index 0000000000..17eae58c81 --- /dev/null +++ b/spec/fixtures/unit/util/ip/linux_ifconfig_ib0 @@ -0,0 +1,8 @@ +ib0 Link encap:InfiniBand HWaddr 80:00:00:4a:fe:80:00:00:00:00:00:00:00:02:c9:03:00:43:27:21 + inet addr:10.6.193.12 Bcast:10.6.193.255 Mask:255.255.255.0 + inet6 addr: fe80::202:c903:43:2721/64 Scope:Link + UP BROADCAST RUNNING MULTICAST MTU:65520 Metric:1 + RX packets:8 errors:0 dropped:0 overruns:0 frame:0 + TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:1024 + RX bytes:448 (448.0 b) TX bytes:0 (0.0 b) \ No newline at end of file diff --git a/spec/unit/util/ip_spec.rb b/spec/unit/util/ip_spec.rb index 0feedf9899..fe272257d6 100755 --- a/spec/unit/util/ip_spec.rb +++ b/spec/unit/util/ip_spec.rb @@ -205,6 +205,15 @@ Facter::Util::IP.get_interface_value("em1", "netmask").should == "255.255.255.0" end + it "should return correct macaddress information for infiniband on Linux" do + ifconfig_interface = my_fixture_read("linux_ifconfig_ib0") + + Facter::Util::IP.expects(:get_single_interface_output).with("ib0").returns(ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("Linux") + + Facter::Util::IP.get_interface_value("ib0", "macaddress").should == "80:00:00:4a:fe:80:00:00:00:00:00:00:00:02:c9:03:00:43:27:21" + end + it "should not get bonding master on interface aliases" do Facter.stubs(:value).with(:kernel).returns("Linux") From 1d82907b22e9071177097f119bbeb7bc466a4b0a Mon Sep 17 00:00:00 2001 From: Michael Renz Date: Wed, 19 Sep 2012 18:33:42 -0400 Subject: [PATCH 1025/3753] (#1415) added two methods and spec tests get_single_interface_output was getting messy, so the logic for what to do when infiniband is present was split out to get_infiniband_macaddress. To help unit tests, ifconfig was placed into ifconfig_interface This also expands on unit tests. --- lib/facter/util/ip.rb | 35 +++++++++++-------- .../util/ip/linux_get_single_interface_ib0 | 8 +++++ spec/fixtures/unit/util/ip/linux_ifconfig_ib0 | 2 +- spec/unit/util/ip_spec.rb | 26 ++++++++++++-- 4 files changed, 54 insertions(+), 17 deletions(-) create mode 100644 spec/fixtures/unit/util/ip/linux_get_single_interface_ib0 diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index bae8b5763a..bb5363e8be 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -73,9 +73,7 @@ def self.get_interfaces def self.get_all_interface_output case Facter.value(:kernel) - when 'OpenBSD', 'NetBSD', 'FreeBSD', 'Darwin', 'GNU/kFreeBSD', 'DragonFly' - output = %x{/sbin/ifconfig -a} - when 'Linux' + when 'Linux', 'OpenBSD', 'NetBSD', 'FreeBSD', 'Darwin', 'GNU/kFreeBSD', 'DragonFly' output = %x{/sbin/ifconfig -a 2>/dev/null} when 'SunOS' output = %x{/usr/sbin/ifconfig -a} @@ -88,23 +86,32 @@ def self.get_all_interface_output output end + def self.get_infiniband_macaddress(interface) + if File::exist?("/sys/class/net/#{interface}/address") then + ib_mac_address = `cat /sys/class/net/#{interface}/address`.chomp + elsif File::exist?("/sbin/ip") then + ip_output = %x{/sbin/ip link show #{interface}} + ib_mac_address = ip_output.scan(%r{infiniband\s+((\w{1,2}:){5,}\w{1,2})}) + else + ib_mac_address = "FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF" + Facter.debug("ip.rb: nothing under /sys/class/net/#{interface}/address and /sbin/ip not available") + end + ib_mac_address + end + + def self.ifconfig_interface(interface) + output = %x{/sbin/ifconfig #{interface} 2>/dev/null} + end + def self.get_single_interface_output(interface) output = "" case Facter.value(:kernel) when 'OpenBSD', 'NetBSD', 'FreeBSD', 'Darwin', 'GNU/kFreeBSD', 'DragonFly' - output = %x{/sbin/ifconfig #{interface}} + output = Facter::Util::IP.ifconfig_interface(interface) when 'Linux' - ifconfig_output = %x{/sbin/ifconfig #{interface} 2>/dev/null} + ifconfig_output = Facter::Util::IP.ifconfig_interface(interface) if interface =~ /^ib/ then - if File::exist?("/sys/class/net/#{interface}/address") then - real_mac_address = `cat /sys/class/net/#{interface}/address`.chomp - elsif File::exist?("/sbin/ip") then - ip_output = %x{/sbin/ip link show #{interface}} - real_mac_address = ip_output.scan(%r{infiniband\s+((\w{1,2}:){5,}\w{1,2})}) - else - real_mac_address = "FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF" - Facter.debug("ip.rb: nothing under /sys/class/net/#{interface}/address and /sbin/ip not available") - end + real_mac_address = get_infiniband_macaddress(interface) output = ifconfig_output.sub(%r{(?:ether|HWaddr)\s+((\w{1,2}:){5,}\w{1,2})}, "HWaddr #{real_mac_address}") else output = ifconfig_output diff --git a/spec/fixtures/unit/util/ip/linux_get_single_interface_ib0 b/spec/fixtures/unit/util/ip/linux_get_single_interface_ib0 new file mode 100644 index 0000000000..0827b98620 --- /dev/null +++ b/spec/fixtures/unit/util/ip/linux_get_single_interface_ib0 @@ -0,0 +1,8 @@ +ib0 Link encap:InfiniBand HWaddr 80:00:00:4a:fe:80:00:00:00:00:00:00:00:02:c9:03:00:43:27:21 + inet addr:10.6.193.12 Bcast:10.6.193.255 Mask:255.255.255.0 + inet6 addr: fe80::202:c903:43:2721/64 Scope:Link + UP BROADCAST RUNNING MULTICAST MTU:65520 Metric:1 + RX packets:8 errors:0 dropped:0 overruns:0 frame:0 + TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:1024 + RX bytes:448 (448.0 b) TX bytes:0 (0.0 b) \ No newline at end of file diff --git a/spec/fixtures/unit/util/ip/linux_ifconfig_ib0 b/spec/fixtures/unit/util/ip/linux_ifconfig_ib0 index 17eae58c81..74cf3014e5 100644 --- a/spec/fixtures/unit/util/ip/linux_ifconfig_ib0 +++ b/spec/fixtures/unit/util/ip/linux_ifconfig_ib0 @@ -1,4 +1,4 @@ -ib0 Link encap:InfiniBand HWaddr 80:00:00:4a:fe:80:00:00:00:00:00:00:00:02:c9:03:00:43:27:21 +ib0 Link encap:InfiniBand HWaddr 80:00:00:4A:FE:80:00:00:00:00:00:00:00:00:00:00:00:00:00:00 inet addr:10.6.193.12 Bcast:10.6.193.255 Mask:255.255.255.0 inet6 addr: fe80::202:c903:43:2721/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:65520 Metric:1 diff --git a/spec/unit/util/ip_spec.rb b/spec/unit/util/ip_spec.rb index fe272257d6..e0034912e3 100755 --- a/spec/unit/util/ip_spec.rb +++ b/spec/unit/util/ip_spec.rb @@ -206,14 +206,36 @@ end it "should return correct macaddress information for infiniband on Linux" do - ifconfig_interface = my_fixture_read("linux_ifconfig_ib0") + correct_ifconfig_interface = my_fixture_read("linux_get_single_interface_ib0") - Facter::Util::IP.expects(:get_single_interface_output).with("ib0").returns(ifconfig_interface) + Facter::Util::IP.expects(:get_single_interface_output).with("ib0").returns(correct_ifconfig_interface) Facter.stubs(:value).with(:kernel).returns("Linux") Facter::Util::IP.get_interface_value("ib0", "macaddress").should == "80:00:00:4a:fe:80:00:00:00:00:00:00:00:02:c9:03:00:43:27:21" end + it "should replace the incorrect macaddress with the correct macaddress in ifconfig for infiniband on Linux" do + ifconfig_interface = my_fixture_read("linux_ifconfig_ib0") + correct_ifconfig_interface = my_fixture_read("linux_get_single_interface_ib0") + + Facter::Util::IP.expects(:get_infiniband_macaddress).with("ib0").returns("80:00:00:4a:fe:80:00:00:00:00:00:00:00:02:c9:03:00:43:27:21") + Facter::Util::IP.expects(:ifconfig_interface).with("ib0").returns(ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("Linux") + + Facter::Util::IP.get_single_interface_output("ib0").should == correct_ifconfig_interface + end + + it "should return fake macaddress information for infiniband on Linux when neither sysfs or /sbin/ip are available" do + ifconfig_interface = my_fixture_read("linux_ifconfig_ib0") + + File.stubs(:exists?).with("/sys/class/net/ib0/address").returns(false) + File.stubs(:exists?).with("/sbin/ip").returns(false) + Facter::Util::IP.expects(:ifconfig_interface).with("ib0").returns(ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("Linux") + + Facter::Util::IP.get_interface_value("ib0", "macaddress").should == "FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF" + end + it "should not get bonding master on interface aliases" do Facter.stubs(:value).with(:kernel).returns("Linux") From f0858deb185b94d98e469bfdf268accb7dd430a9 Mon Sep 17 00:00:00 2001 From: Michael Renz Date: Wed, 19 Sep 2012 18:39:15 -0400 Subject: [PATCH 1026/3753] (#1415) ifconfig_interface should output its output In my first iteration of ifconfig_interface, I declared the ifconfig as var output but did not output var output. I output var output in ifconfig_interface here. --- lib/facter/util/ip.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index bb5363e8be..45f40f3068 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -101,6 +101,7 @@ def self.get_infiniband_macaddress(interface) def self.ifconfig_interface(interface) output = %x{/sbin/ifconfig #{interface} 2>/dev/null} + output end def self.get_single_interface_output(interface) From d413e93a4e7860acc7e9f520e7ede5dae5027bbe Mon Sep 17 00:00:00 2001 From: Michael Renz Date: Wed, 19 Sep 2012 18:44:58 -0400 Subject: [PATCH 1027/3753] (#1415) fixed the regex map REGEX_MAP in ip.rb was limiting MAC addresses to 6 bytes. This fix changes that to AT LEAST 5 bytes. Infiniband is 20 bytes. --- lib/facter/util/ip.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index 45f40f3068..7816e00b09 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -7,7 +7,7 @@ module Facter::Util::IP :linux => { :ipaddress => /inet addr:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, :ipaddress6 => /inet6 addr: ((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/, - :macaddress => /(?:ether|HWaddr)\s+(\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/, + :macaddress => /(?:ether|HWaddr)\s+((\w{1,2}:){5,}\w{1,2})/, :netmask => /Mask:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ }, :bsd => { From a81469e4bdff04e3ab2824a0600a7b2e36950e90 Mon Sep 17 00:00:00 2001 From: Michael Renz Date: Wed, 19 Sep 2012 19:00:13 -0400 Subject: [PATCH 1028/3753] (#1415) ifconfig_interface change. I didn't know that you didn't have to put output from a backtick into a variable to just pass it from a method if you weren't doing more to it. Thank you zaphod42! --- lib/facter/util/ip.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index 7816e00b09..8d9e2e7538 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -100,8 +100,7 @@ def self.get_infiniband_macaddress(interface) end def self.ifconfig_interface(interface) - output = %x{/sbin/ifconfig #{interface} 2>/dev/null} - output + %x{/sbin/ifconfig #{interface} 2>/dev/null} end def self.get_single_interface_output(interface) From 593c1e20b3da74f52315cac5237fd6c7b45f3c04 Mon Sep 17 00:00:00 2001 From: Michael Renz Date: Wed, 19 Sep 2012 19:08:37 -0400 Subject: [PATCH 1029/3753] (#16005) updated ipaddress6_spec.rb and macaddress_spec.rb to reflect updated ifconfig STDERR redirect This fixes 4 cases in related specs so that they know about the change to the way that ifconfig is called. Also, I rebased and reformatted the commit messages. --- spec/unit/ipaddress6_spec.rb | 2 +- spec/unit/macaddress_spec.rb | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/unit/ipaddress6_spec.rb b/spec/unit/ipaddress6_spec.rb index 6b1a79b20c..cd03aa13a5 100755 --- a/spec/unit/ipaddress6_spec.rb +++ b/spec/unit/ipaddress6_spec.rb @@ -25,7 +25,7 @@ def netsh_fixture(filename) it "should return ipaddress6 information for Linux" do Facter::Util::Resolution.stubs(:exec).with('uname -s').returns('Linux') - Facter::Util::Resolution.stubs(:exec).with('/sbin/ifconfig'). + Facter::Util::Resolution.stubs(:exec).with('/sbin/ifconfig 2>/dev/null'). returns(ifconfig_fixture('linux_ifconfig_all_with_multiple_interfaces')) Facter.value(:ipaddress6).should == "2610:10:20:209:212:3fff:febe:2201" diff --git a/spec/unit/macaddress_spec.rb b/spec/unit/macaddress_spec.rb index 244bfc9136..3dc24d9747 100755 --- a/spec/unit/macaddress_spec.rb +++ b/spec/unit/macaddress_spec.rb @@ -22,14 +22,14 @@ def netsh_fixture(filename) end it "should return the macaddress of the first interface" do - Facter::Util::Resolution.stubs(:exec).with('/sbin/ifconfig -a'). + Facter::Util::Resolution.stubs(:exec).with('/sbin/ifconfig -a 2>/dev/null'). returns(ifconfig_fixture('linux_ifconfig_all_with_multiple_interfaces')) Facter.value(:macaddress).should == "00:12:3f:be:22:01" end it "should return nil when no macaddress can be found" do - Facter::Util::Resolution.stubs(:exec).with('/sbin/ifconfig -a'). + Facter::Util::Resolution.stubs(:exec).with('/sbin/ifconfig -a 2>/dev/null'). returns(ifconfig_fixture('linux_ifconfig_no_mac')) proc { Facter.value(:macaddress) }.should_not raise_error @@ -38,7 +38,7 @@ def netsh_fixture(filename) # some interfaces dont have a real mac addresses (like venet inside a container) it "should return nil when no interface has a real macaddress" do - Facter::Util::Resolution.stubs(:exec).with('/sbin/ifconfig -a'). + Facter::Util::Resolution.stubs(:exec).with('/sbin/ifconfig -a 2>/dev/null'). returns(ifconfig_fixture('linux_ifconfig_venet')) proc { Facter.value(:macaddress) }.should_not raise_error From d008d35c73a6b91ac7543c6e532a6084b1d97c83 Mon Sep 17 00:00:00 2001 From: Matthaus Litteken Date: Mon, 24 Sep 2012 13:57:48 -0700 Subject: [PATCH 1030/3753] (maint) Update debian/rules to use new --ruby flag This updates the install.rb call in debian/rules to use the new --ruby flag and sets the ruby binary location to /usr/bin/ruby, as is the standard on debian/ubuntu installations. This is preferred to using env ruby as is in the facter binary because that depends on the users' path, and might not bring in the expected ruby. --- ext/debian/rules | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ext/debian/rules b/ext/debian/rules index 69ac55d8ea..6e2a6d5855 100755 --- a/ext/debian/rules +++ b/ext/debian/rules @@ -7,5 +7,4 @@ LIBDIR=$(shell /usr/bin/ruby -rrbconfig -e 'puts RbConfig::CONFIG["vendordir"]') BINDIR=$(shell /usr/bin/ruby -rrbconfig -e 'puts RbConfig::CONFIG["bindir"]') binary-install/facter:: - /usr/bin/ruby install.rb --sitelibdir=$(LIBDIR) --destdir=$(CURDIR)/debian/$(cdbs_curpkg) --quick - cp -p $(CURDIR)/bin/facter $(CURDIR)/debian/$(cdbs_curpkg)/$(BINDIR) + /usr/bin/ruby install.rb --sitelibdir=$(LIBDIR) --bindir=$(BINDIR) --ruby=/usr/bin/ruby --destdir=$(CURDIR)/debian/$(cdbs_curpkg) --quick From 43d3e68091ed3af623688a71ddee52d71defbe52 Mon Sep 17 00:00:00 2001 From: Matthaus Litteken Date: Wed, 26 Sep 2012 11:43:18 -0700 Subject: [PATCH 1031/3753] Update default cows Previously, the default cows were set to build for both arches. Because facter is a noarch debian package, building against i386 cows is sufficient and reduces overall build time. This also adds cows for debian stable and unstable. --- ext/build_defaults.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/build_defaults.yaml b/ext/build_defaults.yaml index 165d57161a..c6618cf86c 100644 --- a/ext/build_defaults.yaml +++ b/ext/build_defaults.yaml @@ -2,7 +2,7 @@ packaging_url: 'git://github.com/puppetlabs/packaging.git --branch=master' packaging_repo: 'packaging' default_cow: 'base-squeeze-i386.cow' -cows: 'base-lucid-amd64.cow base-lucid-i386.cow base-natty-amd64.cow base-natty-i386.cow base-oneiric-amd64.cow base-oneiric-i386.cow base-precise-amd64.cow base-precise-i386.cow base-sid-amd64.cow base-sid-i386.cow base-squeeze-amd64.cow base-squeeze-i386.cow base-testing-amd64.cow base-testing-i386.cow base-wheezy-i386.cow' +cows: 'base-lucid-i386.cow base-natty-i386.cow base-oneiric-i386.cow base-precise-i386.cow base-sid-i386.cow base-squeeze-i386.cow base-stable-i386.cow base-testing-i386.cow base-unstable-i386.cow base-wheezy-i386.cow' pbuild_conf: '/etc/pbuilderrc' packager: 'puppetlabs' gpg_name: 'info@puppetlabs.com' From 4cb10307269c5712c180bf2b8c5c5eb1dd6bd343 Mon Sep 17 00:00:00 2001 From: Matthaus Litteken Date: Wed, 26 Sep 2012 12:22:34 -0700 Subject: [PATCH 1032/3753] Remove asc file a source for RPM builds We used to require the asc file for RPM builds of facter. This was not consistant with our packaging of any other platform and raised the barrier to entry if you were trying to build your own facter package. This commit removes the requirements of signing the tarball for building an RPM package. Tarball will still be signed prior to official release from Rel-Eng team at Puppet Labs. --- ext/build_defaults.yaml | 2 +- ext/redhat/facter.spec.erb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/build_defaults.yaml b/ext/build_defaults.yaml index c6618cf86c..c0ade3323e 100644 --- a/ext/build_defaults.yaml +++ b/ext/build_defaults.yaml @@ -7,7 +7,7 @@ pbuild_conf: '/etc/pbuilderrc' packager: 'puppetlabs' gpg_name: 'info@puppetlabs.com' gpg_key: '4BD6EC30' -sign_tar: TRUE +sign_tar: FALSE # a space separated list of mock configs final_mocks: 'pl-5-i386 pl-5-x86_64 pl-6-i386 pl-6-x86_64 fedora-15-i386 fedora-15-x86_64 fedora-16-i386 fedora-16-x86_64 fedora-17-i386 fedora-17-x86_64' rc_mocks: 'pl-5-i386-dev pl-5-x86_64-dev pl-6-i386-dev pl-6-x86_64-dev fedora-15-i386-dev fedora-15-x86_64-dev fedora-16-i386-dev fedora-16-x86_64-dev fedora-17-i386-dev fedora-17-x86_64-dev' diff --git a/ext/redhat/facter.spec.erb b/ext/redhat/facter.spec.erb index f96edf7ed5..982a4bc9bd 100644 --- a/ext/redhat/facter.spec.erb +++ b/ext/redhat/facter.spec.erb @@ -18,8 +18,8 @@ Epoch: 1 License: Apache 2.0 Group: System Environment/Base URL: http://www.puppetlabs.com/puppet/related-projects/%{name} +# Note this URL will only be valid at official tags from Puppet Labs Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{realversion}.tar.gz -Source1: http://puppetlabs.com/downloads/%{name}/%{name}-%{realversion}.tar.gz.asc BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) From 903b298f7cad176bf676f2dcbc4cc3e52c105d4a Mon Sep 17 00:00:00 2001 From: Matthaus Litteken Date: Wed, 26 Sep 2012 12:22:57 -0700 Subject: [PATCH 1033/3753] Spec file cleanup This commit removes the Config, RbConfig switch which was needed for ruby 1.8.1 compatibility, as 1.8.5 is the minimum supported ruby. It also updates the license to a valid correctly named license. It removes comments from some %global macros as well. It also adds the epoch to the changelog entry. --- ext/redhat/facter.spec.erb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ext/redhat/facter.spec.erb b/ext/redhat/facter.spec.erb index 982a4bc9bd..5f4e7c9260 100644 --- a/ext/redhat/facter.spec.erb +++ b/ext/redhat/facter.spec.erb @@ -3,19 +3,19 @@ %if 0%{?fedora} >= 17 %global facter_libdir %(ruby -rrbconfig -e 'puts RbConfig::CONFIG["vendorlibdir"]') %else -%global facter_libdir %(ruby -rrbconfig -e 'puts Object.const_get(defined?(RbConfig) ? :RbConfig : :Config)::CONFIG["sitelibdir"]') +%global facter_libdir %(ruby -rrbconfig -e 'puts RbConfig::CONFIG["sitelibdir"]') %endif # VERSION is subbed out during rake srpm process -# %global realversion <%= @version %> -# %global rpmversion <%= @rpmversion %> +%global realversion <%= @version %> +%global rpmversion <%= @rpmversion %> Summary: Ruby module for collecting simple facts about a host operating system Name: facter Version: %{rpmversion} Release: <%= @rpmrelease -%>%{?dist} Epoch: 1 -License: Apache 2.0 +License: ASL 2.0 Group: System Environment/Base URL: http://www.puppetlabs.com/puppet/related-projects/%{name} # Note this URL will only be valid at official tags from Puppet Labs @@ -68,7 +68,7 @@ rm -rf %{buildroot} %changelog -* <%= Time.now.strftime("%a %b %d %Y") %> Puppet Labs Release - <%= @rpmversion %>-<%= @rpmrelease %> +* <%= Time.now.strftime("%a %b %d %Y") %> Puppet Labs Release - 1:<%= @rpmversion %>-<%= @rpmrelease %> - Build for <%= @version %> * Wed Aug 08 2012 Moses Mendoza - 1.6.11-2 From 25cb69fb0bd9da029fcec03daebb037d2bcf9ded Mon Sep 17 00:00:00 2001 From: Matthaus Litteken Date: Wed, 26 Sep 2012 12:25:55 -0700 Subject: [PATCH 1034/3753] Remove INSTALL from Facter The INSTALL file dates back before there were packages besides gems for Facter. As there are now packages for most major platforms, the INSTALL file isn't needed. This removes it from the tree and from packaging. --- INSTALL | 7 ------- ext/redhat/facter.spec.erb | 2 +- install.rb | 2 +- 3 files changed, 2 insertions(+), 9 deletions(-) delete mode 100644 INSTALL diff --git a/INSTALL b/INSTALL deleted file mode 100644 index 7c82ecc7db..0000000000 --- a/INSTALL +++ /dev/null @@ -1,7 +0,0 @@ -Run 'ruby install.rb' or use one of the distributed gem files at -http://puppetlabs.com/downloads/gems . - -install.rb should successfully install; let me know if it doesn't. - -Otherwise, you can just set RUBYLIB to contain its lib directory, or copy -the libs into your main ruby library directory. diff --git a/ext/redhat/facter.spec.erb b/ext/redhat/facter.spec.erb index 5f4e7c9260..7d00d0ae2d 100644 --- a/ext/redhat/facter.spec.erb +++ b/ext/redhat/facter.spec.erb @@ -64,7 +64,7 @@ rm -rf %{buildroot} %{facter_libdir}/facter.rb %{facter_libdir}/facter %{_mandir}/man8/facter.8.gz -%doc CHANGELOG INSTALL LICENSE README.md +%doc CHANGELOG LICENSE README.md %changelog diff --git a/install.rb b/install.rb index a2e467ba0b..dd532c5f68 100755 --- a/install.rb +++ b/install.rb @@ -76,7 +76,7 @@ def glob(list) # Set these values to what you want installed. bins = glob(%w{bin/*}) -rdoc = glob(%w{bin/* lib/**/*.rb README README-library CHANGELOG TODO Install}).reject { |e| e=~ /\.(bat|cmd)$/ } +rdoc = glob(%w{bin/* lib/**/*.rb README README-library CHANGELOG TODO }).reject { |e| e=~ /\.(bat|cmd)$/ } ri = glob(%w(bin/*.rb lib/**/*.rb)).reject { |e| e=~ /\.(bat|cmd)$/ } man = glob(%w{man/man8/*}) libs = glob(%w{lib/**/*.rb lib/**/*.py}) From cc2015dc75bdd242cd240a6f4cf5f4b39ab7a1b4 Mon Sep 17 00:00:00 2001 From: Matthaus Litteken Date: Wed, 26 Sep 2012 12:28:15 -0700 Subject: [PATCH 1035/3753] Remove CHANGELOG from Facter This commit removes the CHANGELOG file in the top level of facter, which only contains commit hashes/ messages from the previous releases, and is of debatable utility. This information can be gleaned easily from a git log, and is not of much use without git. Removing it removes yet another manual process from the releasing of facter. It also removes references of the CHANGELOG from the packaging. --- CHANGELOG | 938 ------------------------------------- ext/debian/docs | 1 - ext/redhat/facter.spec.erb | 2 +- install.rb | 2 +- 4 files changed, 2 insertions(+), 941 deletions(-) delete mode 100644 CHANGELOG diff --git a/CHANGELOG b/CHANGELOG deleted file mode 100644 index 5f6fa5a957..0000000000 --- a/CHANGELOG +++ /dev/null @@ -1,938 +0,0 @@ -1.6.12 -=== -398b111 (Maint) Extract common elements of selinux tests -c534126 (#10819) Avoid reading from /proc/self/mounts in ruby -b95ea54 fix yum repo path in yaml file -3ad05f1 Remove version test from facter -fce4b01 fix redhat spec release template variable -903b1d9 Stop using sed to generate the preflight erb -601a967 Use git read-only packaging repo for public access -dd3401e Fixup apple packaging -e0454df Remove libexec from file list as its only in 2.x -1e7f5b3 Update debhelper compat to 7, add format -6659e61 Fixup redhat spec erb template for f17 -6752530 Shift to using packaging repo -fe311c2 Remove obsolete tasks directory -900895f Group requires together -8c18e33 Move facter redhat spec file to erb -84f8e10 Add debian build artifacts to facter project -d2d3baf Replace rake/gempackagetask with rubygems/gempackagetask -6f58b4e Move tasks out of 'rake' subdirectory -db9d154 Move packaging files to ext, rm conf -c0cbe62 (#15464) Make Facter.version settable via Facter.version= -0b49eae (#15464) Make contributing easy via bundle Gemfile -bf6ee4f Retabbed conf/redhat/facter.spec to lineup tag contents. -defbfb8 (#15291) Add Vendor tag to Facter spec file -17243bb Update facter redhat spec for fedora 17 -7ca9122 Update a facter build-requires for f17 -d5d2328 (#11640) Added support for new OpenStack MAC addresses - -1.6.11 -=== -f75e46e Add build-requires of ruby-rdoc for manpage generation -e9e084f (Maint) Update CONTRIBUTING.md to match Puppet -841b99a (#15687) Selinux: Test for policyvers before reading it -10aa3aa (#15049) Return only one selinuxfs path as string from mounts -f7f90e4 Update version nos to match Facter development -ab87a2c Modify facter spec to work with Ruby 1.9 - -1.6.10 -=== -35067dc Bump Facter epoch to 1 -d6a3e91 Make package task depend on tar in Rakfile -f42896d (#14764) Stub architecture fact when Windows facts run on Linux -f44ca52 (maint) Fix hardware model fact for ruby 1.9 -964d1f0 (#12864) Close registry key -ab025bb Revert "Revert "(#12864) Windows: get primary DNS from registry"" -478386d (#10261) Detect x64 architecture on Windows -6cc881d Use git describe in Rakefile to determine pkg ver -2043244 (#13678) Remove deprecation msg triggerd by the ipaddress6 fact -d118d81 (#13678) Add filename extension on absolute paths on windows -b050eb1 (#14582) Fix noise in LSB facts -85654b0 (#13678) Allow passing shell built-ins to exec method on windows -8f4c016 (#13678) Single quote paths on unix with spaces -2d164e8 (#13678) Join PATHs correctly on windows -e7e7e8f (#13678) Extend spec tests for expand_command -0fea7b0 maint: Add shared context for specs to imitate windows or posix -60d0cd2 (#13678) Fix spec failures on windows -121a2ab (#13678) Fix quoting in expand_command -55b1125 (#13678) Add more unit tests for new methods -9086c0a (#13678) Add RDoc documentation for new methods -165ace4 (#13678) Convert command to absolute paths before executing - -1.6.9 -=== -b398bd8 (#14334) Fix dmidecode based facts on DragonFly BSD -6c46b2c (#14332) Correct stubbing on Ubuntu -753f3a4 Revert "(#12864) Windows: get primary DNS from registry" -ac51593 Wrap dmidecode/pciutils in ifarch block -fbaa8fe (#11511) Correct lsbrelease specfile filename -14eee2b (#12864) Windows: get primary DNS from registry -2842c96 Update rpm spec file -515fd65 (#11511) Split lsb facts into multiple files - -1.6.8 -=== -b86fe4c (#12831) Add rspec tests to have_which method in Resolution -70be957 (#12831) Fix recursion on first kernel fact resolution - -1.6.7 -=== -ab9e26c Updating CHANGELOG and lib/facter.rb for Facter 1.6.7rc1 -bdbf332 (#12720) Add Solaris CPU info to 'processorN' fact. -a7f5924 (#12813) Redirect lspci output to /dev/null -6ec2863 (#12669) Preserve timestamps when installing files - -1.6.6 -=== -e046144 Updated CHANGELOG for 1.6.6rc2 -c273d34 Make ec2 facts work on CentOS again (#12666) -c218d84 (#12362) Use Tempfile to generate temp files -f6bbe14 (#12170) Adds gem spec description Without this patch, the gem spec file is missing a description attribute, which caus -5c5c330 Changes apple rake task to reflect package name facter instead of puppet. -9b5cb26 Updating conf/redhat/facter.spec for 1.6.5 release. -7d3889d (#12079) Fix order-dependent test failure due to odd stubbing. -7f2a0e2 add a simple test for openstack ec2 facts -cb598aa Support EC2 facts on OpenStack - -1.6.5 -=== -71d3d3d (#12077) Add pciutils RPM dependency -1df5b46 (#11566) Add windows support for ec2 facts -d1a33e5 (#11848) Don't hard code ruby install paths in Windows batch files -14cad7e Build a Rake task for building Apple Packages -5a60ca6 (#11559) Switch to RbConfig & Provide alias for RbConfig for pre-1.8.5 -88c9429 (#10271) Identifying 'Amazon' using '/etc/system-release' -2de7b84 (#11661) EC2 rspec tests were using throw not raise to simulate a timeout -b51ccf0 (#11583) Add basic coverage to the ec2 fact -82692ba (#9599) Generalize zone detection -e6cebd3 (#9599) Add nexenta facts -9401b78 (#11583) Switch request method to open-uri monkey patch 'open' -3ccac87 (#9708) Amend requires in specs to use simple requires -b0b5282 (#9708) Confine facts by kernel not operating system and remove confine for hardwareisa -c473e3f (#10309) Remove the with_verbose_disabled method -a99d87c (#10309) Rename tmpfile to tmpfilename to make function clear -d50fc48 (#10309) Move all fixture data in spec/unit/data to spec/fixtures -d6e8523 (#10309) Integrate new PuppetlabsSpec helpers into our existing facter spec code and general spec cleanup -c1604c7 (#10309) Add puppetlabs_spec helper library based on Puppets own puppet_spec helpers -d141e7e (maint) Fix requirement for FileUtils as operatingsystem_spec needs it now -9c224d3 (#11436) Unify memorysize and memorytotal facts -5c6322a (maint) Joined conditional statements for domain -a1dba38 (#11196) Scan all arp entries for an ec2 mac -5cd30eb (#8279) Join ec2 fact output with commas -4633996 (#9789) Extend coverage of operatingsystem specs -6d21f90 Move Linux specific virtual tests to correct block. -cb4e294 (#7753) Added error checking when adding resolves -6201820 (maint) remove redundant arch detection -4f9da1c (#11328) Fix uptime detection on OpenBSD -3f99f16 (#11328) Add virtualisation detection for OpenBSD - -1.6.4 -=== -6406c8f (#11041) Add dmidecode as a requirement for rpm -ed81492 (#10444) Add identification of system boards to Facter -bdbb2da (#10885) Malformed facter.bat when ruby dir contains backreferences -0bad18b (#10490) Handle case where no macaddress can be found - -1.6.3 -=== -b2a66a9 (#7038) Validate prtdiag output in manufacturer -c9db305 (#10228) Ascendos OS support for various facts. -6efadbb (#10233) Adds support for Parallels Server Bare Metal to Facter -ce8f572 (#10079) Remove trailing whitespace -e89758d Updated CHANGELOG for 1.6.2 -7b14b77 (#9928) Correct readlines stubbing to return an array instead of an empty string. -0d6df28 (#9904) Remove windows rspec warning. -f0ccb5e (#9555) Spec tests: Change all cases of tabs and 4 space indentation to 2 space indentation. - -1.6.2 -=== -d7c00f6 (#9852) Fixing watchr on facter -abf636e (#9555) Change all cases of tabs and 4 space indentation to 2 space indentation. -db1b5af (#9830) Add sshecdsakey fact -1b69791 (#9404) Add memory & update processor facts for DragonFly and OpenBSD. -bce2c69 (#9404) De-clumsify CPU count detection and swap detection on OpenBSD. -cd0ae15 (#9404) Efficiency cleanups for DragonFly facts. -d5511f6 (#9404) Add cross-fact support to facter for DragonFly BSD. -0dfc4e9 (#6728) Improve openvz/cloudlinux detection. -2c5ad52 (#6728) Facter improperly detects openvzve on CloudLinux systems -9101e46 (#7951) added OS support for Amazon Linux -b3784f7 add operatingsystema and operatingsystemrelease support for cloudlinux -8605bba (#9787) Change rspec format so we use the default, not document -b579613 (#7726) Silence prtconf error message inside zones -db3c606 (#9786) Add aliases: specs, tests, test in rake that points at 'spec'. -dfda9be (#4980, #6470) Fix architecture in Darwin and Ubuntu -8f938c1 (#6792) Added osfamily fact. -af1ef43 (#6515) Fix for ruby-1.8.5. Switched use of 'line.each' to 'each_line'. -328ff75 (#6515 and #2945) Fix processorcount for arm, sparc & ppc for linux. -51329b8 (#3856) Detect VirtualBox on Darwin as well as Linux and SunOS -83498b5 (#7996) Restrict solaris cpu processor detection -6e29ff7 (#8615) ENV hash is now local to tests -124a09b (#8240) Fixed regex pattern for domain -fd93c5f (#7996) Add solaris processor facts -3f1a163 (#9593) Require rubygems to handle json output for ruby 1.8.7. -c4fe415 (#9295) Added spec tests for Hyper-V detection -ea23417 (#9295) Initial detection of Hyper-V hypervisor -82351ab Stub out OS and HW model to avoid test failures. Only stub vmware -v (don't expect it) since it needn't be invoked if we already identified Xen or something else. -16a8cab (#2747) Fix detection of xen0 vs xenu in Xen 3.2. - -1.6.1 -=== -1f009e0 Updated CHANGELOG for 1.6.1rc4 -3117e82 (#9517) Fix physicalprocessorcount on windows -ef0a9aa Updated CHANGELOG for 1.6.1rc3 -4d93745 (#8491) Prevent repeated loading of fact files -6db71d4 (#9457) Fix logic for domain fact so hostname, then dnsdomainname and finally resolv.conf is used. -18cd964 Updated CHANGELOG for 1.6.1rc2 -7edef60 Change count to length for compatibility -88f343c (#2344) VMware version parsing fix -7457fe5 SELinux spec test fix for ubuntu -8bed5bb Clear messages between test runs -f4ad6bf Macaddress spec test needed operatingsystem fact -6c098cc Rakefile should fail on error -42c5471 Updated CHANGELOG for 1.6.1rc1. -6d47012 (#4869) Implement productname as Darwin hw.model -d28d96c (#4508) Xen HVM domU not detected as virtual -d55983e (#9178) Add Oracle Linux identification -1cb9cb6 (#6610) Fix Autotest proper run -ec04277 (#4228) Ensure MAC address octets have leading zeroes. -241cddc (#6610) Fix rSpec output format -3eb3628 Add Scientific Linux CERN detection to facter. Fixes #9260 -0bf827f (maint) Add kernel stubbing for is_virtual fact spec -f810170 (#7957) is_virtual should be false for openvz host nodes -1414e0b (#9059) is_virtual should be false on vmware_server -7fb0e6a (#8439) Add interface-specific ip facts for Windows -5d5848c (#8439) Add ipaddress6 fact on Windows -7531a2b (#8439) Add ps fact on Windows -ddb67c5 (#8439) Move macaddress resolution on Windows -0721f2f (#7682) Add complete support for Scientific Linux -a347920 (#9183) Add support for Alpine linux detection -824fac0 (#8439) Add physicalprocessorcount and processor facts on Windows -9ef56d6 (#8439) Implement total and free physical memory on Windows -b3e2274 (#8439) Add Facter::Util::WMI module -00bed7a (#8964) Search mountinfo for selinux mount point -cceb74b (maint) Add stubbing and corrections for domain spec -f7daae3 (maint) Remove global var from domain and hostname -e8d00ec (#8964) Search mountinfo for selinux mount point -46cbd68 Fix the SPEC file (COPYING no longer shipped, README -> README.md) -c5d63d4 Fix #2766 - silence unknown sysctl values -2ba8e7b Add document outlining preferred contribution methods -5d9cc84 (#8660) Fix destdir option on Windows -e329450 (#8247) Fixing arp DNS timeout issue. -bdd9e39 Maint: Fix tests to run on Windows -c1b631d Maint: Refactor detection of windows platform -9b7a41d Maint: Deprecate facter resolution interpreter parameter -0356a2a Maint: Fix facter install on Windows -15d0406 use each_line instead of each for strings in ruby 1.9 -08b3f77 (#7854) Add Augeas library version fact -e84c051 Fixed #7307 - Added serial number fact to Solaris -6fb6ee5 (#4869) Implement productname as Darwin hw.model -63c8b11 (#4508) Xen HVM domU not detected as virtual - -1.6.0 -=== -9404a7a (#7670) Add an acceptance test -0c23845 maint: Fix spelling of acceptance directory -926e912 (#7670) Stop preloading all facts in the application -2255abe (#7670) Never fail to find a fact that is present -8002c24 (#7507) Fix 1.9.2 test failure -0635822 Removed inappropriately uncredited Ohai method from ec2 fact -6b1cd16 (#6614) Update ipaddress6 fact to work with Ruby 1.9 -21fe217 (#6612) Changed uptime spec to be endian agnostic -19f96b5 (#6728) Facter improperly detects openvzve on CloudLinux systems -5b10173 (#5135) Fix faulty logic in physicalprocessorcount -53cd946 Ensures that ARP facts are returned only on EC2 hosts -bfa038d Fixed #6974 - Moved to Apache 2.0 license -d56bca8 refactor the mechanism for allowing for resolution ordering to be influenced -9f4c5c6 (#6740) facter doesn't always respect facts in environment variables -7441b32 Partial fix for #6971 - Fix for virtual tests -7f3e89d (#2714) Fixed faulty test -bfc16f6 (#2714) Added timeout to prtdiag resulution -c2ff833 (#5135) Refactored physicalprocessorcount -0c4a98b Re-factor. Do not use pure-Ruby file reading against "/proc/cpuinfo" and possibly any entry under "/sys" from the sysfs file system. -cb52b06 Fix. Using sysfs file system entries to count the number of physical CPUs. Fall-back to "/proc/cpuinfo" included for backward-compatibility with legacy systems. -3efa9d7 (#3856) Add virtualbox detection via lspci (graphics card), dmidecode, and prtdiag for Solaris and corresponding tests. Darwin case is not handled yet. -7c80172 (#6883) Update Facter install.rb to be slightly more informative. -d31e3f9 (#5394) Document each Facter fact. -af4947c (#6862) Add a default subject for the mail_patches rake task -d6967a0 (#6613) Switch solaris macaddress fact to netstat -e056218 (#6817) Fix for Ruby 1.9 by calling .each_line on a string -861c2b2 maint: cleanup whitespace -f6c9927 (#6719) Corrected faulty logic in bugfix -e42e57c (#3856) Add virtualbox detection via lspci (graphics card), dmidecode, and prtdiag for Solaris and corresponding tests. Darwin case is not handled yet. -0b5b546 (#6883) Update Facter install.rb to be slightly more informative. -7c08270 (#5394) Document each Facter fact. -06eb3f5 (#6883) Update Facter install.rb to be slightly more informative. -1063753 (#6862) Add a default subject for the mail_patches rake task -56b5f10 (#6613) Switch solaris macaddress fact to netstat -fd4f31c (#6817) Fix for Ruby 1.9 by calling .each_line on a string -72996ff maint: cleanup whitespace - -1.5.9 -===== -4de8b20 Updated CHANGELOG for 1.5.9rc6 -cc67a01 Removed inappropriately uncredited Ohai method from ec2 fact -69f98da Add facter test for ticket 7039 -f91c120 downcase arp output so that the ec2 arp is matched -a75f0f9 (#7039) Pre-load all facts when requesting a single fact -6b97242 Update CHANGELOG for 1.5.9rc5 -acf0bb2 Ensures that ARP facts are returned only on EC2 hosts -76f544b Updated CHANGELOG for 1.5.9rc4 -09b9f9b (#6795) Update tests to reflect changed exec -3db1cd0 Updated CHANGELOG for 1.5.9rc3 -def3322 (#6795) xendomains: Ignore error output from xm list -f39d487 (#6763) Use Facter::Util::Resolution.exec for arp -3eb9410 arp: Cleanup indendation -50b9b3f Updated CHANGELOG for 1.5.9rc2 -2fb8316 Clean up indentation, and alignment in macaddress_spec.rb -3f0a340 (#6716) fix facter issues on OSX with ipv6 in macaddress.rb. -43f82ef Update CHANGELOG for 1.5.9rc1 -d62e079 Fixed #2346 - A much cleverer EC2 fact -0411d2e Fixed #2346 - Part 1: Added arp fact for Linux -5b6f4fa Discussion on ec2 facts - #2346 -e917e1a Fixed #3087 - Identify VMWare -d0f0f63 (#6327) Memory facts should be available on Mac Darwin -458a22d Incremented release to 1.5.9 -4eb64fe Fixed #6719 Typo -ffd80ac (#5011) Adds swap statistics for OSX -1207765 (#6719) Restricts virtualization types for zones -8d71db3 Fixed #6616 - Stubbing in VMware tests on Linux -aa959df Remove Solaris from the list of confined systems. It won't get the original lsb facts, and it's nonsensical too. -2e48e18 Fixed #6695 - Updated id fact for Darwin et al -d718af4 Fix #6679 - Added Scientific Linux to operatingsystem fact -dea6f78 Further fix to #5485 - SELinux facts -6d6d8da (#2721) Merged patch from Brane GraAnar -868e7ba (#5485) Made selinux_mode fact work -214da73 Fixed #5485 - Updated selinux_mode fact -ba2601f Fix for #6495 - Updated interface detection -93461d9 Fixed #5950 - Solaris ipaddress incorrect after bonding failure -2e06cdc (#6615) fix missing stub calls in loader specs -3c7841e (#5666) windows support for facter/id.rb -dd5d5bf (#4925) - MS Windows doesn't do man pages -52026ee Fixed #5699 - Added processorcount support for S390x -7dd730d Fixed #5699 - Added virtual support for s390x/Zlinux -d6ce08a Fixed #6611 - Fixed broken HPVM test and rationalised test structure -84fa3c4 (#6525) change semicolons to 'then' in case statement for ruby 1.9.2 compatibility -3e6217d Fixes #6521 and other Ruby 1.9 issues -eb5d6fc Fixed #6525 - Test failures on Ruby 1.9.x -cb25119 (#2270) add testing for the new ipaddress6 feature -ea29483 (#2270) add IPv6 support to facter core. -77eb512 (#2270) Remove DWIM code from ipaddress on Darwin. -f5bf0f5 (#6360) Flush Facter top level cache before every test case. -0d7a2e6 Fix #4755: add support for GNU/kFreeBSD platform where missing. -b88a088 (#5510) Facter should load custom fact definitions in filename order. -7a8be16 Refactor #6044 -- use _spec.rb as the pattern for spec tests. -b39f892 Refactor #6044 -- require spec_helper with a consistent path. -a4fe459 Refactor #6044 -- port testing to rspec2 -af9134c (#5086) Try using kstat before falling back to 'who -b' to determine uptime. -cbbfe55 Refactor util/uptime.rb tests to reduce duplication using contexts -f0cc2c0 (#4575) win32 support for manufacturer, productname, & serialnumber -c40fc07 (#1423) Memory facts for Solaris -1985528 (#4754) Change is_virtual logic to not enumerate virtual types -739040f (#4754) Add support for Darwin and Parallels VM to "virtual" fact -9332f8a (#5325) Add tests for SPARC manufacturer and product name -5b561e3 (#5325) Manufacturer and product name on SPARC -9d99079 maint: Fix spec failures caused by having a space in the path to facter's source -89da001 maint: require rubygems so hudson can run the specs -1eef842 Maint: add "Local-branch:" info to mails sent by "rake mail_patches" -f007a9d (#4989) Add xendomains fact -1fa87a9 JSON support. Works in 1.9.1. Warnings in 1.9.2. LoadError on 1.8.7 for some reason -43e203c (#5040) fact virtual should detect hpvm -7cec60a (#5016) is_virtual should be true on solaris zones -f2e66b6 (#5031) Remove redundant puts from RDoc.usage -f4da528 maint: Fix merge error -d62b013 Issue #4889 Fact values should all be strings -07f186d [#4552] Updating --timing to report in milliseconds instead of seconds -1f387a5 [#4552] Apply patch from Dean Wilson -244d2f1 Better fix for Bug 4569: Uptime Fact is incorrect on Windows -11544c1 [#4289] operatingsystemrelease fact for oel, ovs -e6bfdf9 Fix for bug #4569 -8c4d0cd (#4558) Fail with message on --help errors -7210429 [#4558] Refactor facter binary using optparse -b5c85de [#4563] Add a --trace option to the binary -ebcb81b [#4558] Refactor facter binary using optparse -b8b7123 (#4567) Remove unnecessary or non-portable redirects -7ecba71 (#4567) Retain detached HEAD state -1125e1e Make sure FreeBSD spec also works on systems that have /proc/cpuinfo. -889e150 Sync rpm spec file from Fedora/EPEL -725dce0 Rename Reductive Labs to Puppet Labs -ff473ef Updated signing rake task -a85f2b0 [#2865] Fix reporting of virtual facts -f67ec05 [#4567] Add ext/facter-diff to compare output of 2 versions -4050acc Removing stupid .DS_Store files :( -016cf03 [#3703] Fix macaddress fact for Darwin - -1.5.8 -===== -ca2da36 Updated install.rb and created man page -3671c9f [#4583] Refactor uptime to use Resolution.exec -fca8861 [#4594] Reintroduce fix for #1291 from original patch -32c0cb0 [#4594] Revert "fixes #2573, #2085, #1291..." -e7df4c0 Updated CHANGELOG for 1.5.8rc2 -9c9cabd Better fix for Bug 4569: Uptime Fact is incorrect on Windows -01a515f [#4289] operatingsystemrelease fact for oel, ovs -b6c0a6b Fix for bug #4569 -51bcebe Fixed Rakefile package task version detection -81ccb48 Removed references to Reductive Labs in the Rakefile - -1.5.8rc1 -======== -f280703 Incremented version to 1.5.8 -98ef5e8 Updated CHANGELOG for 1.5.8rc1 -4398b36 Updated CHANGELOG rake task -e02be1d [#4156] Updating spec to match Kai's change -bff84c2 [#4156] Applying patch by Kai -b7fe989 [#2330] Update uptime calculation to use /bin/cat -e9a60bc Facter::Manufacturer - sunos test + simplified regex -be411c0 Facter::Manufacturer - test for SunOS and FreeBSD -67f6604 [#4062] Implement operating system facts for MeeGo -a2bcacd [#2330] Uptime should not make redundant system calls -ce7bd9f Refactor rakefile to use spec.ops, separate rcov task -faaa169 Fix #4352 - Support for detecting KVM virtuals on FreeBSD -82286e4 Fix #4352 - Support for detecting virtuals (jails) on FreeBSD -b2c2114 Properly wrapped the windows ipaddress fact in a setcode block. -1bd2ca2 Fixed #3929 - Added user confine to AIX memory facts -8106bc3 Adding HP-UX support to Facter's IP facts -83b3ea6 Fixed #3393 - Updates to Facter for MS Windows -ffcae46 Fixed #3403 - Added fact to query vlans; added spec test -d4b8401 Merged Jos Backus patch to remove requirement for ftools altogether -73dcbb9 Fixed #2355 read hang on /proc/xen/capabilties on RHEL 4.7 -d109def Fix #1365 - load all facts via cli -6c87917 Fixed failing test introduced by previous commit -c5b8d3b Fixes #3740 - split dmi output on regex -25bf5c2 Fix virtual unit test on non-linux by stubbing kernel -9a00eae Fixed #2313 - Somewhat essential hardware facts not available on OpenBSD, patch included -e19024b Fixed #2938 - interfaces that don't match ^\w+[.:]?\d+ are ignored -97879f9 Added support for Slackware in operatingsystem and operatingsystemrelease -802e6c2 Fixed #3542 - Ruby 1.9: broken unittest, String#each no longer exists -2f016f3 Fixed #3541 - Ruby 1.9: broken unittest, unexpected invocation: Process.waitall() -84d3d9f Fixed #3445 - Facter does not handle solaris branded zones properly -b5a8de0 Fix for #3411 install.rb should not put "." first in the tmp_dirs -8ea33eb Fixed #3447 - OVS and OEL not matching in operatingsystemrelease -aeee83c Fixed #3410 - Warnings in rake spec -8bf8cb5 Fixes #3397 - is_virtual fact does not detect Linux-VServer -62b6773 Add kvm support to virtual fact -dca615c fixes #2573, #2085, #1291 - fixes domain and fqdn facts resolution -86447c8 Revert "use popen3 in Resolution.exec" -7750f03 Fix #2341 - stricter handling of dmidecode split -f4269d9 Fix #2746 - add architecture support for GNU/kFreeBSD -50cef83 Fix missing error case -356cf15 Remove whitespace in DMI facts (#3008, #3011) -feecd39 Only ignore IPs starting with 127. -68fc123 Added package signing task -33fb770 use popen3 in Resolution.exec to catch stderr -8109806 introduce a warn mechanism for debugging -b2c1ca5 Add docs to Mac OS X package creation script and clean out old docs in the preflight -5412eab Fixed : 2788 - ftools missing in Ruby 1.9 -5b95a12 Fixes #2704. Problem finding install.rb three levels up -9aef69e Removed all ChangeLog - -1.5.7 -===== -3a39dd8 Updated ChangeLog and task -07dca60 Added additional exclusion to rcov process -8398238 Added rcov support to spec task -7194454 Updated CI Rake task -2472048 Clarify licensing as GPLv2 (or any later version) - -1.5.7rc1 -======== -4bc05e9 Added new format ChangeLog -5bc8db3 Incremented version and updated CHANGELOG -eb3a8a7 Issue #2414 - add unit test -7623e25 Fix errors when alias IP's are defined -bfe8a2a Fix 2455 - improve error handling on fact load -49470cf Fix broken solaris zone tests on EC2 -9515a40 Issue #2548 netblock fact -33be9e0 Add Darwin netmask support on top of Jim's patch -9d846b4 Fix #2306 netmask and ipaddress on SunOS and BSDs -7d4a5f9 Updated Rakefile and moved Rake tasks to tasks/rake directory -5982deb Added default Rake task -0e0483a Fix bug where you'd get an 'undefined method' error if trying to access a fact's value when collection has not being yet initialized. -fe41fb8 Fix #2470 - duplicate entries in interfaces fact -be9e484 Update OS X minor version fact to cope with '10.x' values and provide test coverage switch %x{} call to Facter::Util::Resolution.exec for better testing -f3ad66f Update install.rb to cope with all OS X versions, not just 10.5 -c02d3b6 Issue #2292 Add tests for virtual facts -6c9fec5 Added path fact -51c6e3d Issue #2314 OpenBSD sysctl -95e5fea Fix broken ci build with explicit clearing before tests -efc30e7 Change spec output to enable broken build debugging -6d71410 Fixed CI spec task -82d97e2 Fix operatingsystemrelease on Red Hat based distros -bee55c4 Consolidate operatingsystemrelease for CentOS, Fedora, oel, ovs, and RedHat - -1.5.6 -===== -f4cb619 Updated CHANGELOG and bumped version for 1.5.6 -dcdd5ce Fixes #2307 - Minor fix for zone in virtual.rb -ba44f08 Removed --no-thread and --no-chain-reply-to from rake mail_patches task -806f49f Added facter branding to format patch command -96c015c Added spec.executables to Facter gemspec in Rakefile -d97a63e Sync rpm spec file with latest from Fedora/EPEL - -1.5.5 -===== -dad4569 Added path to Rakefile -365cb8e CHANGELOG updates -68e0b24 Fix #2278 Revert fix for 2120 -b533e78 Tighten operatingsystemrelease regex on CentOS < 5 -48aa135 Fix operatingsystemrelease for CentOS < 5 -253fef1 Added spec files to package list Fixed CI rake tasks -b37d683 Added install.rb to Rakefile package task -7995d05 Bumped release to 1.5.5rc2 - -1.5.5rc2 -======== -1de8891 Bumped release to 1.5.5rc2 -56760d3 Facter #2120 - Solaris support for Facter[virtual] -2fb91ca Tests for #2227 - multiple interfaces on Darwin -00b192a Added SELinux tests -aecac08 Fix #2155 - architecture facts on Gentoo -831d937 Refactor #2154 - Modified patch from Benedikt Bohm to simplify openvz and vserver detection -7f3d237 Cleaned up Rakefile and removed requirement for Reductive Labs build library -a6adf59 Facter ticket 2214 - Fix facts for OVS -e101faf Fixed #2131 - Facter doesn't populate lsbmajdistrelease on OEL (also OEL/OVS and other facts) -73e6656 Facter fix #2231 typo -2518312 Fix #2236 - don't use each_line on arrays -f94abfc Fixed #1327 - Added SELinux facts -8768371 Fixed #2119 - Added support for non-global Solaris 10 zones -23a5b3d Fixed #2215 - Added support for SUSE Linux Enterprise Desktop to operatingsystem and operatingsystemrelease -e93b1e6 Added support for ArchLinux to operatingsystem fact -8e4a689 Fixes #2169 Correctly recognises dom0 and domUs -636a91d Partial fix for #2191 - Facter compatibility for Ruby 1.9 -9df0583 Added COPYING in and CHANGELOG updates -516402c Fixing #1918 - facter --puppet always works -d89ea7a Fixing ifconfig warnings generated on OS X -7fa2576 Fixed #2132 - support for named interface aliases under linux -7a81945 correctly compare values - fixes #2021 -1288b26 Fixed #2080 - IPAddress resolutions should be reordered -a6d6ba5 Use resultion.exec util instead of which checks -89a3aa8 Fix to stdout in resolution.rb -add6d47 Fixed #2081 - Fixed interfaces fact for vlan subinterfaces -8def362 Fixed #2063 - added kernelmajversion fact -5d94f7f Fixed #2055 - SunoS Interface error -9376e5b Fixed #2044 - virtual fact thread fix -c754949 Fix for rake task for reductive-build library -75db918 Fixed lib install permissions -ba2e470 Fixed #2040 - Facter should provide a macosx_productversion_major fact -77fa46b Fix virtual fact if xen but /proc/virtual present -9722e1f Fixed #2003 - Added is_virtual fact -7a30a6a Fixed CHANGELOG -c6c30a4 Fixed #2035 - Missing brace for OSX preflight -b6f0f99 more consistent indentation and alignment. also removal of trailing whitespace -9bc174f Further fix #2032 - close IO -6b904a0 Added EC2 facts -86b01bf Fixed #2032 - file.open hanging on /proc/uptime on some platform - -1.5.4 -===== -91d8cb7 Updated to version 1.5.4 -a99d043 Fixed #1966 - Added physicalprocessorcount fact -94ea807 This commit refs #1555, #1898 and fixes #1761 -04389db Added support for Oracle VM Server to operatingsystem and operatingsystemrelease -552f150 Added support for Oracle Enterprise Linux to operatingsystem and operatingsystemrelease -a932a69 Added README.rst for Facter -e52f962 Added Reductive Labs build library -0726437 Updated README - -1.5.4rc1 -======== -f4bc74d Fixing #1927 - failing facts don't kill Facter -063e4dc Fixed #1850 - Facter updates for Ruby 1.9 -b85ab0a Fixed #1924 - Fixed lo / lo:0 local interface matching -4dcd012 Fixed generic uptime fact -d93ca69 Fixed Ubuntu operatingsystem identification -effb82f Cleaner fix for #1926 -ccafc00 Fixed #1926 - IPAddr to_s issue -d9eef19 Added timezone fact - -1.5.3 -===== -b86a1fb Updated to version 1.5.3 -a73e803 Fixing the usage of the macosx util module; I somehow missed renaming it here -23289bd Fixed uptime refactor issues on non-Linux platforms Signed-off-by: James Turnbull -a194c91 Adding mail_patches rake task -a82f476 Renaming Facter::Macosx to Facter::Util::Macosx -1f1fa9b Fixing #1838 - profiler failures don't throw exceptions -5f202c9 Fixed #1867 - Fixed OpenSuSE detection -0bcdb71 Fixing #1854 - Adding ArchLinux support -fab9d1c Added network fact -da52e30 Fixed #1870 - Format all subnet masks as human-readable -c2de35f Added uptime facts -02c2912 Refactor - rename ipmess to interfaces -db4face Fixed autotest on win32 -c149b49 Fix bug #1870 and add interface fact support for darwin systems -aa56886 Refactoring the IP support, and fixing #1846. -91e25b9 Fixing indentation everywhere -074eda9 Fixing autotest, now that vendor/ is gone -01754f6 Removing the vendor/ gems. -e6d987d Fixing #1761 - Solaris no longer uses /etc/release -a70184a Fixed #1791 - support for virtual fact on Solaris 10 -99833a1 Fixed #1793 - Added more Solaris 10 facts -85b2a55 minor fix to operatingsystemversion to correctly parse /etc/release on OpenSolaris 2008.11. -8247304 Fixed errors on unrecognised option in binary -0fe4611 Added ci namespace and Rake tasks -7ddea77 Fix for #1727 - id fact should not rely on whoami on Solaris Signed-off-by: Martin Englund -f9a346a Sync specfile with latest from Fedora -fd07cd2 Removed EPM task -43d0aea Fixed #1697 - Typo in ipaddress.rb causes timeout under Solaris 10 SPARC -4e707c6 Fixed #1650 - OS X package creation script should be more selective about cleaning out prior versions -8a38aa5 Added Ubuntu to a variety of confines -051c843 Removed ENV path setting from virtual.rb -6393e82 Fixed #1634 - Update virtual fact to differentiate OpenVZ hardware nodes and virtual environments -de39f6c Revamp domain resolution -4d7b44c Fixed #1619 - Applying patch by seanmil, adding support for SLES. -84b83c4 Fixed #1509 - Fixed version recognition for SLES. -20650ac Fixes #1582 - Fix MAC address reporting for Linux bonding slave interfaces -a86577c Fixing the GPL/LGPL incompatibility by choosing the oldest-mentioned license (GPL). -c1d937c Fixed #1575 - CentOS fix for Facter SPEC file -1d00253 Fixed #1547 - finally killed dots in IP address facts -9c9c79a Fixed #1567 - fixed createpackage.sh output -d4cf657 Fixed #1569 - createpackage.rb bug - -1.5.2 -===== -a80779b Updated to version 1.5.2 -0e49580 Updated to version 1.5.2 -6e0a1f3 Fixes #1558 - Adjusted virtual fact to allow non-root users to execute it -4998d3b Fixes #1562 - Removed facter from PREREQS -0fac704 Fixed #1558 - Updated virtual fact for xenu and xen0 -5c50bc3 Fixed #1555 - added operatingsystemrelease for Solaris -e503857 Fixed #1559 - update to dmidecode fact -518393e Fixed . dot escaping -0356b6e Updated to version 1.5.1 - -1.5.1 -===== -bff615c Updated to version 1.5.1 -c2eb5ba Updated to version 1.5.1 -bc35a3b Adding a rake task for creating an archive. -d24504e Added a Process.waitall thread when there's a timeout, to avoid zombies. -bd87aa0 Set the timeout for the host-based and resolv-based resolutions to 2. -e6aa39f Updating changelog for previous two commits -095eb15 Applied patch by josb to fix CentOS version detection. -422dd11 Facter fix #1422, no default timeout -ca93b81 Adding better SuSE detection for both operatingsystem and release. -b7be581 Add unit rspec tests for ticket 1425 -af81fb3 Extract ifconfig output to data directory -2546c53 Add sample test and strawman solution for IP parsing code -b33d8c6 Add module level tests for Facter::IPAddress -590a3d0 Fixes #1492 - added kernelversion fact -d8b708b Fix ticket 1425 on Solaris -b91ee5e Remove duplicated code paths -df8fc8c fix terrible error with overwriting permissions -91ca4ab Fixed #1490 - Added virtual fact -ff45c86 Feature #1487: Package creation scripts for Mac OS X -9b42182 Modified the operatingsystem fact for Debian so it looks in /etc/debian_version instead of /proc/version. -e1023de Feature #1478: Allow specification of --bindir --sbindir --sitelibdir --mandir --destdir in install.rb -845ae94 Feature #1475: CONFIG['bindir'] CONFIG['sbindir'] have undesirable defaults on OS X 10.5 -a12608e Fixes #1467 - macaddress not set on Ubuntu -d999d95 Don't try and run lsb_release on windows -1eb94d3 Bug #1434 Don't execute which on windows -bb235e3 Use rbconfig to detect host cpu -3f180b3 Get DNSDomain from WMI to set domain -dc7363e Set macaddress on windows platform -0df872b Get kernel version via wmi -3ea1905 Use ipconfig to determine ip address -5e09ea1 Use rbconfig to detect windows as no uname binary -ded53b0 Fixed Rakefile to include additional files including tests et al -1cf98d1 Adjusted version to be in line with previous standard -ff0e90b Adding (apparently now required) author info to the gem spec - -1.5 -=== -7042c46 Updated to version 1.5 -e98efd3 Updated to version 1.5 -d49d63c Updating the changelog for 1.5 -88fe243 Fixed formatting -8c91649 Fixed #1400 - OperatingSystemRelease should now work on CentOS -927b3a1 Adding a default case for the manufacturer information. -9b464de Further fixes #1378 - updated dmidecode for NetBSD -a44d6c3 Fixes #1378 - update manufacter.rb facts to support BSD -9581190 Partial fix for #1345 - BSD interfaces with aliases now select the first address by default -2ef2041 Retaining 'timeout' as the settor, but using 'limit' as the getter. -e22b408 Changed 'timeout' option to 'limit' -145cee2 Setting the timeout for the puppetversion fact to 1.5. -40a9c1d Fixing some warnings in various classes. -0303885 Fixes #1376 - Display memory facts for AIX -2ac29ac Added processorcount and type facts to AIX -0b0892d Fixes #1334 - Forced Facter to use LANG=C -def18b5 Fixes #1357 - change ps syntax for OSX and BSD -ce7b74c Rejustifying all of the whitespace in the facts, yay. -2ee5d29 Refactoring how recursive searches are detected. -d322df9 Refactored so each fact resolution can specify a separate timeout, but the default is still 0.5. -9a1882e Retrieve hardwaremodel for AIX from sys0 modelname. -b574c6a Refactered ipmess.rb and util/ip.rb to support separate *BSD logic for *BSD aliased interfaces. -d9bd388 Refactor of netmask fact - fixes ticket #66 -09bc48c Testing gitosis -f9961c7 Fixes for ticket #60 -a12d3d8 Removing old test/unit tests. -400bab9 Adding a timeout to fact retrieval, fixing #56. -d235f26 Reverting the version. -7e84cdb Updated CHANGELOG -a5a72bd Added LSB Major Dist Release fact fixing #41 -fc6d1c9 Added support for AIX fixing ticket #56 -17f916f Updated Red Hat spec file for new version and files -86e0708 Incremented version number to 1.5 -edbfc44 Adding a --puppet option to facter to load Puppet facts. -bb41db0 Switching to a search path registration system. -07a3d47 Moving the puppet-related loading tests to an integration test. -03258eb Retrieval of fact values now autoloads facts. -e02b0b3 Updated version. Moved most facts to seperate files. -9c91a6d Facter no longer loads all facts by default. -aaaf767 Moving the version and ruby facts to a separate file. -f1acbc0 Switching Facter to using the new loader. -bb92493 Fixing the last few occurrences of Facter::Resolution instead of Facter::Util::Resolution. -dcfc171 Fixing the test so it doesn't break other tests. -1ba2bed Moving all of the support classes to util/. -be0a803 Creating a 'loader' class to handle loading facts for the collection. -cc9e221 Adding the 'each' method back into Facter. -48b8744 Updating the executable to not use Facter.each. -5889e43 Fixing warnings and interfaces. -bfc4996 Moving Facter's container behaviour into a separate class. -e3c1fda Splitting the instance code into a Fact class. -121d291 Adding all of the tests for the Facter::Resolution class. -8971979 Reorganizing my new tests so they match the autotest discovery. -b8de4e4 Simplifying Confine a bit -c5492c2 Splitting the different classes in Facter up, and adding some tests. -4f39ec8 Adding autotest hooks -fef9b7d fixing whitespace -567549b Closes #1145 - fixed bad interface names by replacing : with _ -d449472 Updated CHANGELOG -bd3b316 removing .swp file -e11edfb Switching from test/unit to rspec, and fixing a couple of small test failures. -1a5ba71 Fixed Solaris detection of lo0 for ticket #46 -92b43e0 Added require util ip.rb file -0c4ac42 Fixed #46 - refactor ipmess.rb -a633aeb Added new files -c312df8 Further updates to split facts and move support functions -df4636a Split out facts from facter.rb and moved all support code to util -4bb9ed4 Added support for multiple interfaces, macaddress and netmask facts for Linux, *BSD, and Solaris -64f9fe9 Fixed conflict merge -2b06799 Revert "Fixed ticket #50 - added selinux facts" -ecc1f0c Added Ubuntu operatingsystem and operatingsystemrelease fact support -96cf3d6 Added Debian release version support -b3962ef Fixed ticket #50 - added selinux facts -d7d82fc Fixed ticket #48 - CentOS operatingsystemrelease fact now reporting correct value -2af364c Added Mandrake support for operatingsystem fact - closed ticket #47 -85fbf8f Added index to imess.rb fixing Ticket #43. -be7c30b Fixed ticket #44 - -1.3.8 -===== -74621b5 Updated to version 1.3.8 -7f1c840 Updated to version 1.3.8 -4d83f6f Updating version in changelog -00ab1f3 Removing the package hosts, so packages are no longer created at all -57c76dd Updated CHANGELOG -b28ce1b Added require for rdoc/ri/ri_paths to address Puppet #753 and Facter #40 -dce6245 Revert "Adjusted :kernel confine to make it more in line with others" -c5e6f60 Adjusted :kernel confine to make it more in line with others -a4698ce Updated CHANGELOG -8b08d5f Added support to return multiple interfaces and their IP addresses as facts. Existing ipaddress fact which returns IP address of first interface on node is still available. Currently Linux only. Closes #6 -6113375 Added macaddress fact support for FreeBSD and OpenBSD - closes #37 Added hardwareisa support for *BSD platforms - closed #38 Facter now detects the Mandriva distribution - closes #39 Facter now correctly detects ipaddress on NetBSD - closes #42 -8426aaf making the install script executable -f35ee22 Drastically speeding up the lsb data retrieval, and refactoring the dmidecode data so it is a bit cleaner and does not produce extraneous output or errors -20986d9 Set operatingsystemrelease to the major release on RHEL and Fedora -43b5640 Remove tabs; don't fail if dmidecode doesn't return expected information -68449a9 Adding manufacturer code, as requested by digant on the Puppet Trac site. -750a0c6 Add YAML output option to the help text. -8a67e32 Fixed problem with executing system_profiler and sw_vers on non Darwin hosts. -43933dd Fixed problem where facter referenced puppet plist utility library. -46d9bed Added a bunch of information from system_profiler -xml. In particular, sp_serial_number is interesting. Also added values from sw_vers, to get the commonly used Mac OS X version and build identifier. -86e3d8e Setting the ldapname so it is guaranteed to be a string -b582612 Applying patch from Valentin Vidic, fixing open filehandles -09261ac Updated to version 1.3.7 -94ca0c3 Updated to version 1.3.7 - -1.3.7 -===== -3e12345 Adding release tag REL_1_3_7 -a329b65 Using consistent naming internally; I previously had essentially random quoting and case, but it is now all lower-case symbols. It should behave the same externally. -4880c69 Applying patch from #36 by psychedelys -11cff7b Fixing Facter.flush -df57cec Fixing #33 -- we now only return the first mac address -31039dc Applying patch from Adam Jacob that makes FACTERLIB work -392d8f2 Applying patch from #35. -824f91c Fixing bug where an up interface not in active use was being selected as the canonical IP instead of using the IP attached to the interface assigned the default route. -38cd613 Sync with Fedora specfile -4cf0016 updating docs a bit -c1a02be Updated to version 1.3.6 -b333df2 Updated to version 1.3.6 - -1.3.6 -===== -ce5258b Adding release tag REL_1_3_6 -cc672ba disabling solaris package generation for facter -067dc2c updating changelog for 1.3.6 -b013e21 Applying patch from #29. -7b665cc Fixing ssh key facts so they only include the key, not the type. -0674780 Make specfile work for FC < 5 and RHEL < 5 -ea65bdd Reconciling with Fedora specfile -4dc1c37 Do not try and check the command if which is not available; fixes trac #30 -c7a9e19 Updated to version 1.3.5 -bae0b49 Updated to version 1.3.5 -9a73c72 Updated to version 1.3.5 - -1.3.5 -===== -5192d94 Adding release tag REL_1_3_5 -4339b46 Fixing #26 -- using Resolution.exec instead of executing directly, and also calling lsb_release for every fact, instead of just once at startup -82fd890 Updated to version 1.3.4 -95352bc Updated to version 1.3.4 -677c986 Updated to version 1.3.4 - -1.3.4 -===== -e882251 Adding release tag REL_1_3_4 -ca498a2 updating changelog for 1.3.4 -d75744b Adding patch from #21, adding lsb_release facts -fe0f2f2 Adding yaml support, as requested in #24 -4abbce9 applying patch from #18. -7407e0c Fixing facter so it does not fail when an unknown fact is asked for -e2185ce Sorting the facts when they are all output -c96cf6a Adding fqdn fact -fc9331a Fixing #20. I just made sure that the Domain fact cchecks the hostname first, so that if the hostname is an fqdn it will set the domain from that. -07a42e6 Applying patch from #22 -610fb5d Applying patch in #23. -3569253 Applying memfree patch from #17. -b9beaa8 updates -722e6f2 doc updates -e2337bd doc updates -2987d50 updates -044f19c adding docs -6f01dec adding docs -4c04592 Updated to version 1.3.3 -474d65d Updated to version 1.3.3 - -1.3.3 -===== -f3333f3 Adding release tag REL_1_3_3 -682b97a updating changelog for 1.3.3 -747d45a Adding the ability to retrieve facts from the environment. -86fdc87 Updated to version 1.3.2 -c4659bd Updated to version 1.3.2 - -1.3.2 -===== -3869edf Adding release tag REL_1_3_3 -ea96381 simple packaging updaets -c2aa508 Adding thread exclusivity to memory and cpu reading -ace180f Re-adding these files, since Matt has found a solution to the hanging problem. -ba2e189 removing processor.rb in case it has the same problems as the memory file -9f14df9 Deleting this file until the hanging problems are resolved -157f68e fixing license issues -a0a33e6 fixing spec file again -31caa08 Updated to version 1.3.1 -8ad0323 Updated to version 1.3.1 -5e34a1f Updated to version 1.3.1 - -1.3.1 -===== -60be696 Adding release tag REL_1_3_1 -73aeade adding a call to dnsdomainname before domainname -6ac796d Fixing #15. Just adding rescue blocks around the load statements. -81f451b updating for 1.3 -b543152 Updated for use with latest Fedora ruby packages -15f2f44 Updated to version 1.3 -15931ef Updated to version 1.3 -261d909 Updated to version 1.3 - -1.3 -=== -92c48b9 Adding release tag REL_1_3 -539d593 fixing installer so it does not install batch files on darwin -4c1d5e0 trying to fix facterbin rubylib setting -7f2504d fixing test so that it works even if rubylib is not set -75b1835 Adding tagging frameworks back into Facter, and adding the ability to specify tags to the to_hash method so that you only receive facts tagged with specific tags -4296f1f fixing the linux processor stuff so it only gets called on linux -558d05a changing the syntax of the fact confines -9908628 Adding some documentation to the binary -a15c8f5 Adding rubysitedir fact, as requested in #13. Also, switching the output when one fact is asked for, so it only produces the single value, with no => symbol. -ee7d3ca fixing test to ignore differences in memory -5ae066b Switching "tag" to "confine", because it is a more appropriate term. I will also add "tags", but they will be used for creating fact collections. -c7cfd08 Adding patch from #11, with slight modifications. -59cea90 Adding patch from #11, with slight modifications. -f3cc5e3 Adding the ability to specify tags as hashes or arrays, as requested in #112. -01d37d9 Getting rid of the autoload method entirely; facts are now only loaded at startup. -3a0181e fixing linux memory stuff -6932a95 accepting patch in #10, although with more abstraction, and creating a module for the memory functions -165a401 Accepting the patch in #9, with some modifications. -af062c6 adding solaris pkg stuff -8794e46 Updated to version 1.2.1 - -1.2.1 -===== -77344ea Adding release tag REL_1_2_1 -b208f47 fixing small bug that only occurs with gems -999929e Updated to version 1.2.0 - -1.2.0 -===== -afe3c30 Adding release tag REL_1_2_0 -99b61e7 Adding final autoloading work. -97f1a5e updating changelog for 1.2.0 -87bbd50 adding another test for the exe -f745454 Adding ruby, puppet, and facter version facts -6c01e04 Fixing install and tests so that there are no errors, hopefully. -22bd24b Added "architecture" fact, added the ability to autoload facts from separate files, and added the ability to retrieve fact values via a method for each fact. -fe782b9 Accepting the patch from #5 -6c37a20 Removing ruby as a prereq -c78d113 Converting rakefile to the new build system -fadc8c5 Minor changes for hte Fedora Extras review -e3e4a03 fixing rake file to build and copy rpms automatically -46996fa updating changelog for 1.1.4 -aab8687 Updated to version 1.1.4 - -1.1.4 -===== -571683b Adding Release tag REL_1_1_4 -0b7dce7 Fixing installer to put the facter executable in /usr/bin instead of / -3c71757 Updated to version 1.1.3 - -1.1.3 -===== -d494ac2 Adding Release tag REL_1_1_3 -3a230a0 adding 1.1.3 changelog -2e407d4 Identifying centos -cc4a943 updates -4579f8f Updated to version 1.1.2 - -1.1.2 -===== -9279ca8 Adding Release tag REL_1_1_2 -d36885f Adding ldapname capabilities -a4309b4 Automatically update version and release in the specfile for new releases -2d84edd Fix specfile in accordance with Fedora Extras guidelines -2c0999e RPM creation now works -62c050a Working on packaging -2c99812 Updated to version 1.1.1 - -1.1.1 -===== -6fef6af Adding Release tag REL_1_1_1 -35ed5f4 Fixing bug when a fact with no resolutions is asked for -a295c73 Fixing bug when a fact with no resolutions is asked for -5a0bd4a Updated to version 1.1.0 - -1.1.0 -===== -81657d1 Adding Release tag REL_1_1_0 -1ed4216 Redoing how tags work. -d9c86d5 updating everything to essentially disable docs generation -64a86db Adding Release tag - -1.0.2 -===== -8c91fb1 adding release tag -1dc02f9 adding extra "return nil" statements, and hopefully fixing the test for cygwin -b542ec5 Updated to version 1.0.2 -f1c8f10 adding changelog -7df3411 adding fixes Eric Sorenson found with cygwin -c646434 updates -fe90bf1 Updated to version 1.0.1 -3f0186d Modified version -58538d2 removing filehandle-based tests -8cb9662 updating INSTALL with patch from ian -7cec936 moving things to the trunk - diff --git a/ext/debian/docs b/ext/debian/docs index ad253d4753..b43bf86b50 100644 --- a/ext/debian/docs +++ b/ext/debian/docs @@ -1,2 +1 @@ -CHANGELOG README.md diff --git a/ext/redhat/facter.spec.erb b/ext/redhat/facter.spec.erb index 7d00d0ae2d..a3f96e9770 100644 --- a/ext/redhat/facter.spec.erb +++ b/ext/redhat/facter.spec.erb @@ -64,7 +64,7 @@ rm -rf %{buildroot} %{facter_libdir}/facter.rb %{facter_libdir}/facter %{_mandir}/man8/facter.8.gz -%doc CHANGELOG LICENSE README.md +%doc LICENSE README.md %changelog diff --git a/install.rb b/install.rb index dd532c5f68..8baa60fa0c 100755 --- a/install.rb +++ b/install.rb @@ -76,7 +76,7 @@ def glob(list) # Set these values to what you want installed. bins = glob(%w{bin/*}) -rdoc = glob(%w{bin/* lib/**/*.rb README README-library CHANGELOG TODO }).reject { |e| e=~ /\.(bat|cmd)$/ } +rdoc = glob(%w{bin/* lib/**/*.rb README README-library TODO }).reject { |e| e=~ /\.(bat|cmd)$/ } ri = glob(%w(bin/*.rb lib/**/*.rb)).reject { |e| e=~ /\.(bat|cmd)$/ } man = glob(%w{man/man8/*}) libs = glob(%w{lib/**/*.rb lib/**/*.py}) From e81b3ab46cf4524742c948f07e98b5e9bcdffe3a Mon Sep 17 00:00:00 2001 From: Matthaus Litteken Date: Wed, 26 Sep 2012 13:07:11 -0700 Subject: [PATCH 1036/3753] Updating FACTERVERSION for facter 1.6.13-rc1 --- lib/facter/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/version.rb b/lib/facter/version.rb index 19d052a6ca..545e9459e4 100644 --- a/lib/facter/version.rb +++ b/lib/facter/version.rb @@ -1,6 +1,6 @@ module Facter if not defined? FACTERVERSION then - FACTERVERSION = '1.6.12' + FACTERVERSION = '1.6.13-rc1' end def self.version From 4ccf1be5b1acbc935ab2703a6c43f772a8972b09 Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Wed, 26 Sep 2012 15:43:20 -0700 Subject: [PATCH 1037/3753] return vendor field and retab This commit returns the vendor field to the facter spec, which was lost in the transition to an erb template. It also tabs in this section for clarity. Signed-off-by: Moses Mendoza --- ext/redhat/facter.spec.erb | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/ext/redhat/facter.spec.erb b/ext/redhat/facter.spec.erb index a3f96e9770..8e0d0015b7 100644 --- a/ext/redhat/facter.spec.erb +++ b/ext/redhat/facter.spec.erb @@ -10,21 +10,22 @@ %global realversion <%= @version %> %global rpmversion <%= @rpmversion %> -Summary: Ruby module for collecting simple facts about a host operating system -Name: facter -Version: %{rpmversion} -Release: <%= @rpmrelease -%>%{?dist} -Epoch: 1 -License: ASL 2.0 -Group: System Environment/Base -URL: http://www.puppetlabs.com/puppet/related-projects/%{name} -# Note this URL will only be valid at official tags from Puppet Labs -Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{realversion}.tar.gz - -BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) - -Requires: ruby >= 1.8.5 -Requires: which +Summary: Ruby module for collecting simple facts about a host operating system +Name: facter +Version: %{rpmversion} +Release: <%= @rpmrelease -%>%{?dist} +Epoch: 1 +Vendor: %{?_host_vendor} +License: ASL 2.0 +Group: System Environment/Base +URL: http://www.puppetlabs.com/puppet/related-projects/%{name} +# Note this URL will only be valid at official tags from Puppet Labs +Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{realversion}.tar.gz + +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) + +Requires: ruby >= 1.8.5 +Requires: which # dmidecode and pciutils are not available on all arches %ifarch %ix86 x86_64 ia64 Requires: dmidecode From b861248d86212471ca40b600277de259b306bad5 Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Thu, 4 Oct 2012 10:04:27 -0700 Subject: [PATCH 1038/3753] Update lib/facter/version.rb for 1.6.13 --- lib/facter/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/version.rb b/lib/facter/version.rb index 545e9459e4..06a65c7469 100644 --- a/lib/facter/version.rb +++ b/lib/facter/version.rb @@ -1,6 +1,6 @@ module Facter if not defined? FACTERVERSION then - FACTERVERSION = '1.6.13-rc1' + FACTERVERSION = '1.6.13' end def self.version From 0b56c1a46f61dc33006e27ae553c3332b96f884f Mon Sep 17 00:00:00 2001 From: Patrick Carlisle Date: Wed, 3 Oct 2012 11:22:23 -0700 Subject: [PATCH 1039/3753] Only load rubygems in bin/facter We should not load rubygems when used as a library. Instead we require rubygems when invoked from the command line. --- bin/facter | 8 ++++++++ lib/facter/application.rb | 1 - 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/bin/facter b/bin/facter index 6c5d4ce09c..5c1310db19 100755 --- a/bin/facter +++ b/bin/facter @@ -55,6 +55,14 @@ # Copyright (c) 2011 Puppet Labs, Inc # Licensed under the Apache 2.0 license +# Load rubygems +unless defined? Bundler::Setup + begin + require 'rubygems' + rescue LoadError + end +end + require 'facter/application' Facter::Application.run(ARGV) diff --git a/lib/facter/application.rb b/lib/facter/application.rb index 3f0f37522c..9b6da1d639 100644 --- a/lib/facter/application.rb +++ b/lib/facter/application.rb @@ -35,7 +35,6 @@ def self.run(argv) # Print the facts as JSON and exit if options[:json] begin - require 'rubygems' require 'json' puts JSON.dump(facts) exit(0) From 1f948556faabbf332eb7b446c78ab8af72f1c7e4 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Fri, 5 Oct 2012 15:23:01 -0700 Subject: [PATCH 1040/3753] (#16829) Change how bundler is detected The constant `::Bundler::Setup` isn't defined in the latest version, instead using just `::Bundler` to detect whether it's been loaded. See also #16757 --- bin/facter | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/bin/facter b/bin/facter index 5c1310db19..deb4a0ec89 100755 --- a/bin/facter +++ b/bin/facter @@ -55,8 +55,11 @@ # Copyright (c) 2011 Puppet Labs, Inc # Licensed under the Apache 2.0 license -# Load rubygems -unless defined? Bundler::Setup +# Bundler and rubygems maintain a set of directories from which to +# load gems. If Bundler is loaded, let it determine what can be +# loaded. If it's not loaded, then use rubygems. But do this before +# loading any facter code, so that our gem loading system is sane. +if not defined? ::Bundler begin require 'rubygems' rescue LoadError From 278e8136bed550d74d7ebf52a7782facb1b97703 Mon Sep 17 00:00:00 2001 From: Alex Harvey Date: Fri, 14 Sep 2012 19:36:29 +1000 Subject: [PATCH 1041/3753] (#14764) Stop calling enum_cpuinfo and enum_lsdev on unsuitable platforms Without this patch, the platform specific utility methods on Facter::Util::Processor named enum_cpuinfo and enum_lsdev are always called when Facter loads processor.rb. This is a problem because exceptions will be raised during spec tests on different platforms as described at https://projects.puppetlabs.com/issues/14764 Reviewed-by: Jeff McCune --- lib/facter/processor.rb | 57 +++++++++++++++-------------------------- 1 file changed, 21 insertions(+), 36 deletions(-) diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb index 0b0f0e36f1..34cf2b3e16 100644 --- a/lib/facter/processor.rb +++ b/lib/facter/processor.rb @@ -22,6 +22,26 @@ require 'thread' require 'facter/util/processor' +## We have to enumerate these outside a Facter.add block to get the processorN descriptions iteratively +## (but we need them inside the Facter.add block above for tests on processorcount to work) +processor_list = case Facter.value(:kernel) +when "AIX" + Facter::Util::Processor.enum_lsdev +when "SunOS" + Facter::Util::Processor.enum_kstat +else + Facter::Util::Processor.enum_cpuinfo +end + +processor_list.each_with_index do |desc, i| + Facter.add("Processor#{i}") do + confine :kernel => [ :aix, :sunos, :linux, :"gnu/kfreebsd" ] + setcode do + desc + end + end +end + Facter.add("ProcessorCount") do confine :kernel => [ :linux, :"gnu/kfreebsd" ] setcode do @@ -78,41 +98,6 @@ end end -## We have to enumerate these outside a Facter.add block to get the processorN descriptions iteratively -## (but we need them inside the Facter.add block above for tests on processorcount to work) -processor_list = Facter::Util::Processor.enum_cpuinfo -processor_list_aix = Facter::Util::Processor.enum_lsdev -processor_list_sunos = Facter::Util::Processor.enum_kstat - -if processor_list.length != 0 - processor_list.each_with_index do |desc, i| - Facter.add("Processor#{i}") do - confine :kernel => [ :linux, :"gnu/kfreebsd" ] - setcode do - desc - end - end - end -elsif processor_list_aix.length != 0 - processor_list_aix.each_with_index do |desc, i| - Facter.add("Processor#{i}") do - confine :kernel => [ :aix ] - setcode do - desc - end - end - end -elsif processor_list_sunos.length != 0 - processor_list_sunos.each_with_index do |desc, i| - Facter.add("Processor#{i}") do - confine :kernel => [ :sunos ] - setcode do - desc - end - end - end -end - if Facter.value(:kernel) == "windows" processor_list = [] @@ -165,7 +150,7 @@ end end -Facter.add("processorcount") do +Facter.add("ProcessorCount") do confine :kernel => :sunos setcode do kstat = Facter::Util::Resolution.exec("/usr/bin/kstat cpu_info") From 8063ffef94c3c7d68719cdc0af18cc6ae392c3a6 Mon Sep 17 00:00:00 2001 From: Alex Harvey Date: Mon, 24 Sep 2012 18:47:48 +1000 Subject: [PATCH 1042/3753] (#13535) Fix uptime; use `uptime` instead of `who -b` Without this patch applied the uptime fact value is often wrong on some flavors of Unix. In particular, the value may be negative or simply incorrect. This is a problem because the Facter value does not contained the truth of our reality. This makes it difficult to model reality inside of Puppet. This patch fixes the problem by replacing the unreliable who -b method and the Solaris kstat method with a simple call to the "uptime" command. This patch also adds RSpec tests for new method and remove RSpec tests for removed methods. Delete now-unused fixture file who_b_boottime. Reviewed-by: Jeff McCune --- lib/facter/util/uptime.rb | 43 ++++++---- spec/fixtures/unit/util/uptime/who_b_boottime | 1 - spec/unit/util/uptime_spec.rb | 79 ++++++++++++++----- 3 files changed, 87 insertions(+), 36 deletions(-) delete mode 100644 spec/fixtures/unit/util/uptime/who_b_boottime diff --git a/lib/facter/util/uptime.rb b/lib/facter/util/uptime.rb index 6a1ca22c64..2fab9f46a3 100644 --- a/lib/facter/util/uptime.rb +++ b/lib/facter/util/uptime.rb @@ -4,7 +4,7 @@ # module Facter::Util::Uptime def self.get_uptime_seconds_unix - uptime_proc_uptime or uptime_sysctl or uptime_kstat or uptime_who_dash_b + uptime_proc_uptime or uptime_sysctl or uptime_executable end def self.get_uptime_seconds_win @@ -31,15 +31,30 @@ def self.uptime_sysctl end end - def self.uptime_kstat - if output = Facter::Util::Resolution.exec("#{uptime_kstat_cmd} 2>/dev/null") - compute_uptime(Time.at(output.chomp.split(/\s/).last.to_i)) - end - end - - def self.uptime_who_dash_b - if output = Facter::Util::Resolution.exec("#{uptime_who_cmd} 2>/dev/null") - compute_uptime(Time.parse(output)) + def self.uptime_executable + if output = Facter::Util::Resolution.exec("#{uptime_executable_cmd} 2>/dev/null") + up=0 + if output =~ /(\d+) day(?:s|\(s\))?,\s+(\d+):(\d+)/ + # Regexp handles Solaris, AIX, HP-UX, and Tru64. + # 'day(?:s|\(s\))?' says maybe 'day', 'days', + # or 'day(s)', and don't set $2. + up=86400*$1.to_i + 3600*$2.to_i + 60*$3.to_i + elsif output =~ /(\d+) day(?:s|\(s\))?,\s+(\d+) hr(?:s|\(s\))?,/ + up=86400*$1.to_i + 3600*$2.to_i + elsif output =~ /(\d+) day(?:s|\(s\))?,\s+(\d+) min(?:s|\(s\))?,/ + up=86400*$1.to_i + 60*$2.to_i + elsif output =~ /(\d+) day(?:s|\(s\))?,/ + up=86400*$1.to_i + elsif output =~ /up\s+(\d+):(\d+),/ + # must anchor to 'up' to avoid matching time of day + # at beginning of line. + up=3600*$1.to_i + 60*$2.to_i + elsif output =~ /(\d+) hr(?:s|\(s\))?,/ + up=3600*$1.to_i + elsif output =~ /(\d+) min(?:s|\(s\))?,/ + up=60*$1.to_i + end + up end end @@ -55,11 +70,7 @@ def self.uptime_sysctl_cmd 'sysctl -n kern.boottime' end - def self.uptime_kstat_cmd - 'kstat -p unix:::boot_time' - end - - def self.uptime_who_cmd - 'who -b' + def self.uptime_executable_cmd + "uptime" end end diff --git a/spec/fixtures/unit/util/uptime/who_b_boottime b/spec/fixtures/unit/util/uptime/who_b_boottime deleted file mode 100644 index 9b29dcd830..0000000000 --- a/spec/fixtures/unit/util/uptime/who_b_boottime +++ /dev/null @@ -1 +0,0 @@ -reboot ~ Aug 1 14:13 diff --git a/spec/unit/util/uptime_spec.rb b/spec/unit/util/uptime_spec.rb index 79bb56c6c7..f41b5528f9 100755 --- a/spec/unit/util/uptime_spec.rb +++ b/spec/unit/util/uptime_spec.rb @@ -41,31 +41,72 @@ describe "nor is 'sysctl kern.boottime'" do before :each do - Facter::Util::Uptime.stubs(:uptime_sysctl_cmd).returns("cat \"#{@nonexistent_file}\"") + Facter::Util::Uptime.stubs(:uptime_proc_uptime).returns(false) + Facter::Util::Uptime.stubs(:uptime_sysctl).returns(false) + Facter.fact(:kernel).stubs(:value).returns('SunOS') end - it "should use 'kstat -p unix:::boot_time'" do - kstat_output_file = my_fixture('kstat_boot_time') # unix:0:system_misc:boot_time 1236919980 - Facter::Util::Uptime.stubs(:uptime_kstat_cmd).returns("cat \"#{kstat_output_file}\"") - Time.stubs(:now).returns Time.at(1236923580) #one hour later - Facter::Util::Uptime.get_uptime_seconds_unix.should == 60 * 60 - end - - describe "nor is 'kstat -p unix:::boot_time'" do - before :each do - Facter::Util::Uptime.stubs(:uptime_kstat_cmd).returns("cat \"#{@nonexistent_file}\"") - end + describe "should use 'uptime' command" do + # Note about uptime variations. + # Solaris (I have examined 5.6, 5.7, 5.8, 5.9, 5.10 & 5.11) and HP-UX (11.00, 11.11, 11.23, 11.31) have time + # right justified at at 8 characters, and two spaces before 'up'. + # Solaris differs from all other Unices (and Linux) in that the plural/singular case of minutes/hours/days are + # written min(s)/hr(s)/day(s) instead of min/mins/hr/hrs etc., e.g. 1 min(s), 2 min(s) as opposed to + # 1 min, 2 mins, etc. + # AIX (4.3.3, 5.2, 5.3, 6.1) differs from other SysV Unices in that times are padded with a leading 0 in the + # hour column where necessary, and have AM/PM in uppercase, and there are three spaces before 'up'. + # Tru64 (4.0, 5.1) differs from other SysV Unices in that times are in 24 hour format, and there are no + # leading spaces. + # Linux (RHEL, uptime version 2.0.7) differs from the SysV Unices in that only minutes before the first hour + # are written as 1 min, 2 min, 3 min etc. and after that full hours are rendered 1:00 or 21:00. Time of + # day was written 5:37pm and right-justified at 8 characters, with 2 spaces before 'up'. A figure in + # whole days was written as 3 days, 0 min. + # By version 2.0.17 the time of day was rewritten in 24hr time with seconds, right-justified at 9 characters. + # By version 3.2.7 one of the spaces before 'up' had been removed. + test_cases = [ + [' 4:42pm up 1 min(s), 0 users, load average: 0.95, 0.25, 0.09', 1*60], + ['13:16 up 58 mins, 2 users, load average: 0.00, 0.02, 0.05', 58*60], + ['13:18 up 1 hr, 1 user, load average: 0.58, 0.23, 0.14', 1*60*60 ], + [' 10:14pm up 3 hr(s), 0 users, load average: 0.00, 0.00, 0.00', 3*60*60 ], + ['14:18 up 2 hrs, 0 users, load average: 0.33, 0.27, 0.29', 2*60*60 ], + [' 9:01pm up 1:47, 0 users, load average: 0.00, 0.00, 0.00', 1*60*60 + 47*60], + ['13:19 up 1:01, 1 user, load average: 0.10, 0.26, 0.21', 1*60*60 + 1*60], + ['10:49 up 22:31, 0 users, load average: 0.26, 0.34, 0.27', 22*60*60 + 31*60], + ['12:18 up 1 day, 0 users, load average: 0.74, 0.20, 0.10', 1*24*60*60 ], + [' 2:48pm up 1 day(s), 0 users, load average: 0.21, 0.20, 0.17', 1*24*60*60 ], + ['12:18 up 2 days, 0 users, load average: 0.50, 0.27, 0.16', 2*24*60*60 ], + [' 1:56pm up 25 day(s), 2 users, load average: 0.59, 0.56, 0.50', 25*24*60*60 ], + [' 1:29pm up 485 days, 0 users, load average: 0.00, 0.01, 0.01', 485*24*60*60 ], + [' 18:11:24 up 69 days, 0 min, 0 users, load average: 0.00, 0.00, 0.00', 69*24*60*60 ], + ['12:19 up 1 day, 1 min, 0 users, load average: 0.07, 0.16, 0.13', 1*24*60*60 + 1*60], + [' 3:23pm up 25 day(s), 27 min(s), 2 users, load average: 0.49, 0.45, 0.46', 25*24*60*60 + 27*60], + [' 02:42PM up 1 day, 39 mins, 0 users, load average: 1.49, 1.74, 1.80', 1*24*60*60 + 39*60], + [' 18:13:13 up 245 days, 44 min, 1 user, load average: 0.00, 0.00, 0.00', 245*24*60*60 + 44*60], + [' 6:09pm up 350 days, 2 min, 1 user, load average: 0.02, 0.03, 0.00', 350*24*60*60 + 2*60], + [' 1:07pm up 174 day(s), 16 hr(s), 0 users, load average: 0.05, 0.04, 0.03', 174*24*60*60 + 16*60*60 ], + [' 02:34PM up 621 days, 18 hrs, 0 users, load average: 2.67, 2.52, 2.56', 621*24*60*60 + 18*60*60 ], + [' 3:30am up 108 days, 1 hr, 31 users, load average: 0.39, 0.40, 0.41', 108*24*60*60 + 1*60*60 ], + ['13:18 up 1 day, 1 hr, 0 users, load average: 0.78, 0.33, 0.18', 1*24*60*60 + 1*60*60 ], + ['14:18 up 1 day, 2 hrs, 0 users, load average: 1.17, 0.48, 0.41', 1*24*60*60 + 2*60*60 ], + ['15:56 up 152 days, 17 hrs, 0 users, load average: 0.01, 0.06, 0.07', 152*24*60*60 + 17*60*60 ], + [' 5:37pm up 25 days, 21:00, 0 users, load average: 0.01, 0.02, 0.00', 25*24*60*60 + 21*60*60 ], + [' 8:59pm up 94 day(s), 3:17, 46 users, load average: 0.66, 0.67, 0.70', 94*24*60*60 + 3*60*60 + 17*60], + [' 3:01pm up 4496 day(s), 21:19, 32 users, load average: 0.61, 0.62, 0.62', 4496*24*60*60 + 21*60*60 + 19*60], + [' 02:42PM up 41 days, 2:38, 0 users, load average: 0.38, 0.70, 0.55', 41*24*60*60 + 2*60*60 + 38*60], + [' 18:13:29 up 25 days, 21:36, 0 users, load average: 0.00, 0.00, 0.00', 25*24*60*60 + 21*60*60 + 36*60], + [' 13:36:05 up 118 days, 1:15, 1 user, load average: 0.00, 0.00, 0.00', 118*24*60*60 + 1*60*60 + 15*60] + ] - it "should use 'who -b'" do - who_b_output_file = my_fixture('who_b_boottime') # Aug 1 14:13 - Facter::Util::Uptime.stubs(:uptime_who_cmd).returns("cat \"#{who_b_output_file}\"") - Time.stubs(:now).returns Time.parse("Aug 01 15:13") # one hour later - Facter::Util::Uptime.get_uptime_seconds_unix.should == 60 * 60 + test_cases.each do |uptime, expected| + it "should return #{expected} for #{uptime}" do + Facter::Util::Resolution.stubs(:exec).with('uptime 2>/dev/null').returns(uptime) + Facter.fact(:uptime_seconds).value.should == expected + end end - describe "nor is 'who -b'" do + describe "nor is 'uptime' command" do before :each do - Facter::Util::Uptime.stubs(:uptime_who_cmd).returns("cat \"#{@nonexistent_file}\"") + Facter::Util::Uptime.stubs(:uptime_executable_cmd).returns("cat \"#{@nonexistent_file}\"") end it "should return nil" do From 521ae2240f8fef6558cebf272d63f7d00a6adb80 Mon Sep 17 00:00:00 2001 From: Alex Harvey Date: Tue, 16 Oct 2012 13:26:55 -0700 Subject: [PATCH 1043/3753] (#16506) Fix illegal option error from uname on hpux Without this patch applied Facter produces a misleading error message when run on HP-UX: $ facter hardwareisa /usr/bin/uname: illegal option -- p usage: uname [-amnrsvil] [-S nodename] This patch fixes the problem by changing the command to `uname -m` when running on the HP-UX kernel. Original author: Alex Harvey Reviewed-by: Jeff McCune --- lib/facter/hardwareisa.rb | 9 +++++++-- spec/unit/hardwareisa_spec.rb | 7 +++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/facter/hardwareisa.rb b/lib/facter/hardwareisa.rb index ecd5a19de3..1529734e1c 100644 --- a/lib/facter/hardwareisa.rb +++ b/lib/facter/hardwareisa.rb @@ -4,12 +4,17 @@ # Returns hardware processor type. # # Resolution: -# On Solaris, Linux and the BSDs simply uses the output of "uname -p" +# On Solaris, AIX, Linux and the BSDs simply uses the output of "uname -p" +# On HP-UX, "uname -m" gives us the same information. # # Caveats: # Some linuxes return unknown to uname -p with relative ease. # Facter.add(:hardwareisa) do - setcode 'uname -p' + if Facter.value(:kernel) == 'HP-UX' + setcode 'uname -m' + else + setcode 'uname -p' + end end diff --git a/spec/unit/hardwareisa_spec.rb b/spec/unit/hardwareisa_spec.rb index f76b319ed5..a709e8c742 100755 --- a/spec/unit/hardwareisa_spec.rb +++ b/spec/unit/hardwareisa_spec.rb @@ -31,4 +31,11 @@ Facter.fact(:hardwareisa).value.should == "Clyde" end + + it "should match uname -m on HP-UX" do + Facter.fact(:kernel).stubs(:value).returns("HP-UX") + Facter::Util::Resolution.stubs(:exec).with("uname -m").returns("Pac-Man") + + Facter.fact(:hardwareisa).value.should == "Pac-Man" + end end From 047a2d950e70cd0921350d83568b42f636ba26ed Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Tue, 16 Oct 2012 14:36:37 -0700 Subject: [PATCH 1044/3753] (#16526) Fix physicalprocessorcount fact on Solaris Without this patch applied Facter produces the following error output when run on versions of Solaris prior to version 8. $ facter -d physicalprocessorcount /usr/sbin/psrinfo: illegal option -- p usage: psrinfo [-v] [processor_id ...] psrinfo -s processor_id This is a problem because the value is not actually returned to the end user. This patch fixes the problem by avoiding the use of the `-p` flag in versions of Solaris prior to kernel release 5.8 (Solaris 8). In these scenarios, we simply count the number of lines in the output of the `psrinfo` command. --- lib/facter/physicalprocessorcount.rb | 14 +++++++++++- spec/unit/physicalprocessorcount_spec.rb | 27 ++++++++++++++++++++---- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/lib/facter/physicalprocessorcount.rb b/lib/facter/physicalprocessorcount.rb index ee9f20d4fb..dbfa35be31 100644 --- a/lib/facter/physicalprocessorcount.rb +++ b/lib/facter/physicalprocessorcount.rb @@ -67,6 +67,18 @@ confine :kernel => :sunos setcode do - Facter::Util::Resolution.exec("/usr/sbin/psrinfo -p") + # (#16526) The -p flag was not added until Solaris 8. Our intent is to + # split the kernel release fact value on the dot, take the second element, + # and disable the -p flag for values < 8 when. + kernelrelease = Facter.value(:kernelrelease) + (major_version, minor_version) = kernelrelease.split(".").map { |str| str.to_i } + cmd = "/usr/sbin/psrinfo" + result = nil + if (major_version > 5) or (major_version == 5 and minor_version >= 8) then + result = Facter::Util::Resolution.exec("#{cmd} -p") + else + output = Facter::Util::Resolution.exec(cmd) + result = output.split("\n").length.to_s + end end end diff --git a/spec/unit/physicalprocessorcount_spec.rb b/spec/unit/physicalprocessorcount_spec.rb index 8fd471a459..3a8d4984da 100755 --- a/spec/unit/physicalprocessorcount_spec.rb +++ b/spec/unit/physicalprocessorcount_spec.rb @@ -48,10 +48,29 @@ end describe "on solaris" do - it "should use the output of psrinfo" do - Facter.fact(:kernel).stubs(:value).returns(:sunos) - Facter::Util::Resolution.expects(:exec).with("/usr/sbin/psrinfo -p").returns(1) - Facter.fact(:physicalprocessorcount).value.should == 1 + let(:psrinfo) do + "0 on-line since 10/16/2012 14:06:12\n" + + "1 on-line since 10/16/2012 14:06:14\n" + end + + %w{ 5.8 5.9 5.10 5.11 }.each do |release| + it "should use the output of psrinfo -p on #{release}" do + Facter.fact(:kernel).stubs(:value).returns(:sunos) + Facter.stubs(:value).with(:kernelrelease).returns(release) + + Facter::Util::Resolution.expects(:exec).with("/usr/sbin/psrinfo -p").returns("1") + Facter.fact(:physicalprocessorcount).value.should == "1" + end + end + + %w{ 5.5.1 5.6 5.7 }.each do |release| + it "uses psrinfo with no -p for kernelrelease #{release}" do + Facter.fact(:kernel).stubs(:value).returns(:sunos) + Facter.stubs(:value).with(:kernelrelease).returns(release) + + Facter::Util::Resolution.expects(:exec).with("/usr/sbin/psrinfo").returns(psrinfo) + Facter.fact(:physicalprocessorcount).value.should == "2" + end end end end From d136414f27bbb9e9ca04e7d4ac5c13370e373e6f Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Wed, 17 Oct 2012 15:28:56 -0700 Subject: [PATCH 1045/3753] (#16527) Fix processorcount fact on Solaris Without this patch applied the processorcount fact on Solaris releases without the kstat command produces the following error message: Could not retrieve processorcount: private method `scan' called for nil:NilClass This is a problem because there is no clear path forward for the end user. This patch fixes the problem by switching the command executed to gather the processor count from executing `kstat` to executing `psrinfo` for Solaris kernel releases before 5.8. --- lib/facter/processor.rb | 17 +++++++++++++++-- spec/unit/processor_spec.rb | 38 +++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb index 34cf2b3e16..3498ce3771 100644 --- a/lib/facter/processor.rb +++ b/lib/facter/processor.rb @@ -153,7 +153,20 @@ Facter.add("ProcessorCount") do confine :kernel => :sunos setcode do - kstat = Facter::Util::Resolution.exec("/usr/bin/kstat cpu_info") - kstat.scan(/\bcore_id\b\s+\d+/).uniq.length + kernelrelease = Facter.value(:kernelrelease) + (major_version, minor_version) = kernelrelease.split(".").map { |str| str.to_i } + result = nil + + if (major_version > 5) or (major_version == 5 and minor_version >= 8) then + if kstat = Facter::Util::Resolution.exec("/usr/bin/kstat cpu_info") + result = kstat.scan(/\bcore_id\b\s+\d+/).uniq.length + end + else + if output = Facter::Util::Resolution.exec("/usr/sbin/psrinfo") then + result = output.split("\n").length + end + end + + result end end diff --git a/spec/unit/processor_spec.rb b/spec/unit/processor_spec.rb index 6be839867e..c7335da8a0 100755 --- a/spec/unit/processor_spec.rb +++ b/spec/unit/processor_spec.rb @@ -70,6 +70,7 @@ def load(procs) before :each do Facter.collection.loader.load(:processor) Facter.fact(:kernel).stubs(:value).returns(:sunos) + Facter.stubs(:value).with(:kernelrelease).returns("5.10") end it "should detect the correct processor count on x86_64" do @@ -242,5 +243,42 @@ def load(procs) Facter.fact(:processorcount).value.should == "16" end + + describe "on solaris" do + before :all do + @fixture_kstat_sparc = File.read(fixtures('processorcount','solaris-sparc-kstat-cpu-info')) + @fixture_kstat_x86_64 = File.read(fixtures('processorcount','solaris-x86_64-kstat-cpu-info')) + end + + let(:psrinfo) do + "0 on-line since 10/16/2012 14:06:12\n" + + "1 on-line since 10/16/2012 14:06:14\n" + end + + let(:kstat_sparc) { @fixture_kstat_sparc } + let(:kstat_x86_64) { @fixture_kstat_x86_64 } + + %w{ 5.8 5.9 5.10 5.11 }.each do |release| + %w{ sparc x86_64 }.each do |arch| + it "uses kstat on release #{release} (#{arch})" do + Facter.fact(:kernel).stubs(:value).returns(:sunos) + Facter.stubs(:value).with(:kernelrelease).returns(release) + + Facter::Util::Resolution.expects(:exec).with("/usr/bin/kstat cpu_info").returns(self.send("kstat_#{arch}".intern)) + Facter.fact(:processorcount).value.should == 8 + end + end + end + + %w{ 5.5.1 5.6 5.7 }.each do |release| + it "uses psrinfo on release #{release}" do + Facter.fact(:kernel).stubs(:value).returns(:sunos) + Facter.stubs(:value).with(:kernelrelease).returns(release) + + Facter::Util::Resolution.expects(:exec).with("/usr/sbin/psrinfo").returns(psrinfo) + Facter.fact(:physicalprocessorcount).value.should == "2" + end + end + end end end From 1077baa069c8358a478da30afc1e480384bc0a81 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Thu, 18 Oct 2012 12:20:23 -0700 Subject: [PATCH 1046/3753] (maint) Add newlines to all files (whitespace only) Without this patch there are a number of files that do not have newlines. This is a problem because it makes it difficult to use `perl -pl -i -e ...` --- spec/fixtures/ifconfig/centos_5_5 | 2 +- spec/fixtures/ifconfig/centos_5_5_eth0 | 2 +- spec/fixtures/ifconfig/darwin_10_3_0 | 2 +- spec/fixtures/ifconfig/darwin_10_3_0_en0 | 2 +- spec/fixtures/ifconfig/darwin_9_8_0 | 2 +- spec/fixtures/ifconfig/darwin_9_8_0_en0 | 2 +- spec/fixtures/ifconfig/fedora_10 | 2 +- spec/fixtures/ifconfig/fedora_10_eth0 | 2 +- spec/fixtures/ifconfig/fedora_13 | 2 +- spec/fixtures/ifconfig/fedora_13_eth0 | 2 +- spec/fixtures/ifconfig/fedora_8 | 2 +- spec/fixtures/ifconfig/fedora_8_eth0 | 2 +- spec/fixtures/ifconfig/freebsd_6_0 | 2 +- spec/fixtures/ifconfig/open_solaris_10 | 2 +- spec/fixtures/ifconfig/open_solaris_b132 | 2 +- spec/fixtures/ifconfig/ubuntu_7_04 | 2 +- spec/fixtures/ifconfig/ubuntu_7_04_eth0 | 2 +- spec/fixtures/netstat/centos_5_5 | 2 +- spec/fixtures/netstat/darwin_10_3_0 | 2 +- spec/fixtures/netstat/darwin_9_8_0 | 2 +- spec/fixtures/netstat/fedora_10 | 2 +- spec/fixtures/netstat/open_solaris_10 | 2 +- spec/fixtures/netstat/open_solaris_b132 | 2 +- spec/fixtures/netstat/ubuntu_7_04 | 2 +- spec/fixtures/unit/util/ec2/centos-arp-ec2.out | 2 +- spec/fixtures/unit/util/ec2/windows-2008-arp-a.out | 2 +- spec/fixtures/unit/util/ip/6.0-STABLE_FreeBSD_ifconfig | 2 +- spec/fixtures/unit/util/ip/linux_get_single_interface_ib0 | 2 +- .../unit/util/ip/linux_ifconfig_all_with_single_interface | 2 +- spec/fixtures/unit/util/ip/linux_ifconfig_ib0 | 2 +- spec/fixtures/unit/util/ip/solaris_ifconfig_single_interface | 2 +- 31 files changed, 31 insertions(+), 31 deletions(-) diff --git a/spec/fixtures/ifconfig/centos_5_5 b/spec/fixtures/ifconfig/centos_5_5 index 7f57d1d311..673c780fc7 100644 --- a/spec/fixtures/ifconfig/centos_5_5 +++ b/spec/fixtures/ifconfig/centos_5_5 @@ -14,4 +14,4 @@ lo Link encap:Local Loopback RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 - RX bytes:0 (0.0 b) TX bytes:0 (0.0 b) \ No newline at end of file + RX bytes:0 (0.0 b) TX bytes:0 (0.0 b) diff --git a/spec/fixtures/ifconfig/centos_5_5_eth0 b/spec/fixtures/ifconfig/centos_5_5_eth0 index c18d9362c1..d1ea3f1409 100644 --- a/spec/fixtures/ifconfig/centos_5_5_eth0 +++ b/spec/fixtures/ifconfig/centos_5_5_eth0 @@ -5,4 +5,4 @@ eth0 Link encap:Ethernet HWaddr 16:8D:2A:15:17:91 RX packets:1914871 errors:0 dropped:0 overruns:0 frame:0 TX packets:3960 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 - RX bytes:178738891 (170.4 MiB) TX bytes:393862 (384.6 KiB) \ No newline at end of file + RX bytes:178738891 (170.4 MiB) TX bytes:393862 (384.6 KiB) diff --git a/spec/fixtures/ifconfig/darwin_10_3_0 b/spec/fixtures/ifconfig/darwin_10_3_0 index 23707ddd60..2759daa14b 100644 --- a/spec/fixtures/ifconfig/darwin_10_3_0 +++ b/spec/fixtures/ifconfig/darwin_10_3_0 @@ -23,4 +23,4 @@ vmnet8: flags=8863 mtu 1500 inet 172.16.95.1 netmask 0xffffff00 broadcast 172.16.95.255 vmnet1: flags=8863 mtu 1500 ether 00:50:56:c0:00:01 - inet 172.16.201.1 netmask 0xffffff00 broadcast 172.16.201.255 \ No newline at end of file + inet 172.16.201.1 netmask 0xffffff00 broadcast 172.16.201.255 diff --git a/spec/fixtures/ifconfig/darwin_10_3_0_en0 b/spec/fixtures/ifconfig/darwin_10_3_0_en0 index 1e33f712a7..f11391a7c8 100644 --- a/spec/fixtures/ifconfig/darwin_10_3_0_en0 +++ b/spec/fixtures/ifconfig/darwin_10_3_0_en0 @@ -3,4 +3,4 @@ en0: flags=8863 mtu 1500 inet6 fe80::217:f2ff:fe06:e3c2%en0 prefixlen 64 scopeid 0x4 inet 100.100.104.12 netmask 0xffffff00 broadcast 100.100.104.255 media: autoselect (1000baseT ) - status: active \ No newline at end of file + status: active diff --git a/spec/fixtures/ifconfig/darwin_9_8_0 b/spec/fixtures/ifconfig/darwin_9_8_0 index 80f5d94cea..db7e623368 100644 --- a/spec/fixtures/ifconfig/darwin_9_8_0 +++ b/spec/fixtures/ifconfig/darwin_9_8_0 @@ -23,4 +23,4 @@ vmnet8: flags=8863 mtu 1500 ether 00:50:56:c0:00:08 vmnet1: flags=8863 mtu 1500 inet 192.168.61.1 netmask 0xffffff00 broadcast 192.168.61.255 - ether 00:50:56:c0:00:01 \ No newline at end of file + ether 00:50:56:c0:00:01 diff --git a/spec/fixtures/ifconfig/darwin_9_8_0_en0 b/spec/fixtures/ifconfig/darwin_9_8_0_en0 index 658ae777a0..1cbfb61713 100644 --- a/spec/fixtures/ifconfig/darwin_9_8_0_en0 +++ b/spec/fixtures/ifconfig/darwin_9_8_0_en0 @@ -3,4 +3,4 @@ en0: flags=8863 mtu 1500 inet 100.100.107.4 netmask 0xffffff00 broadcast 100.100.107.255 ether 00:17:f2:06:e4:2e media: autoselect (1000baseT ) status: active - supported media: autoselect 10baseT/UTP 10baseT/UTP 10baseT/UTP 10baseT/UTP 100baseTX 100baseTX 100baseTX 100baseTX 1000baseT 1000baseT 1000baseT \ No newline at end of file + supported media: autoselect 10baseT/UTP 10baseT/UTP 10baseT/UTP 10baseT/UTP 100baseTX 100baseTX 100baseTX 100baseTX 1000baseT 1000baseT 1000baseT diff --git a/spec/fixtures/ifconfig/fedora_10 b/spec/fixtures/ifconfig/fedora_10 index 42b06ca64c..c6c15354f4 100644 --- a/spec/fixtures/ifconfig/fedora_10 +++ b/spec/fixtures/ifconfig/fedora_10 @@ -33,4 +33,4 @@ vmnet8 Link encap:Ethernet HWaddr 00:50:56:C0:00:08 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:20 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 - RX bytes:0 (0.0 b) TX bytes:0 (0.0 b) \ No newline at end of file + RX bytes:0 (0.0 b) TX bytes:0 (0.0 b) diff --git a/spec/fixtures/ifconfig/fedora_10_eth0 b/spec/fixtures/ifconfig/fedora_10_eth0 index fa957d9094..2b046fafa1 100644 --- a/spec/fixtures/ifconfig/fedora_10_eth0 +++ b/spec/fixtures/ifconfig/fedora_10_eth0 @@ -6,4 +6,4 @@ eth0 Link encap:Ethernet HWaddr 00:17:F2:06:E4:26 TX packets:45447195 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:17037147877 (15.8 GiB) TX bytes:9198873602 (8.5 GiB) - Memory:92c20000-92c40000 \ No newline at end of file + Memory:92c20000-92c40000 diff --git a/spec/fixtures/ifconfig/fedora_13 b/spec/fixtures/ifconfig/fedora_13 index 23491dfb23..5e4266f4c9 100644 --- a/spec/fixtures/ifconfig/fedora_13 +++ b/spec/fixtures/ifconfig/fedora_13 @@ -15,4 +15,4 @@ lo Link encap:Local Loopback RX packets:482 errors:0 dropped:0 overruns:0 frame:0 TX packets:482 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 - RX bytes:48488 (47.3 KiB) TX bytes:48488 (47.3 KiB) \ No newline at end of file + RX bytes:48488 (47.3 KiB) TX bytes:48488 (47.3 KiB) diff --git a/spec/fixtures/ifconfig/fedora_13_eth0 b/spec/fixtures/ifconfig/fedora_13_eth0 index ca7afd9865..1272d6232d 100644 --- a/spec/fixtures/ifconfig/fedora_13_eth0 +++ b/spec/fixtures/ifconfig/fedora_13_eth0 @@ -6,4 +6,4 @@ eth0 Link encap:Ethernet HWaddr 00:17:F2:0D:9B:A8 TX packets:1539208 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:1729726282 (1.6 GiB) TX bytes:1606599653 (1.4 GiB) - Memory:f2c20000-f2c40000 \ No newline at end of file + Memory:f2c20000-f2c40000 diff --git a/spec/fixtures/ifconfig/fedora_8 b/spec/fixtures/ifconfig/fedora_8 index f54caa4b87..ffd55f8b2d 100644 --- a/spec/fixtures/ifconfig/fedora_8 +++ b/spec/fixtures/ifconfig/fedora_8 @@ -35,4 +35,4 @@ lo Link encap:Local Loopback RX packets:59385736 errors:0 dropped:0 overruns:0 frame:0 TX packets:59385736 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 - RX bytes:3519026710 (3.2 GiB) TX bytes:3519026710 (3.2 GiB) \ No newline at end of file + RX bytes:3519026710 (3.2 GiB) TX bytes:3519026710 (3.2 GiB) diff --git a/spec/fixtures/ifconfig/fedora_8_eth0 b/spec/fixtures/ifconfig/fedora_8_eth0 index a7789dc077..0c75dc8e94 100644 --- a/spec/fixtures/ifconfig/fedora_8_eth0 +++ b/spec/fixtures/ifconfig/fedora_8_eth0 @@ -6,4 +6,4 @@ eth0 Link encap:Ethernet HWaddr 00:18:F3:F6:33:E5 TX packets:1241890434 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:3777811119 (3.5 GiB) TX bytes:2061606027 (1.9 GiB) - Interrupt:21 \ No newline at end of file + Interrupt:21 diff --git a/spec/fixtures/ifconfig/freebsd_6_0 b/spec/fixtures/ifconfig/freebsd_6_0 index 3339a45588..3335708c2f 100644 --- a/spec/fixtures/ifconfig/freebsd_6_0 +++ b/spec/fixtures/ifconfig/freebsd_6_0 @@ -9,4 +9,4 @@ fxp0: flags=8843 mtu 1500 media: Ethernet autoselect (100baseTX ) status: active lo0: flags=8049 mtu 16384 - inet 127.0.0.1 netmask 0xff000000 \ No newline at end of file + inet 127.0.0.1 netmask 0xff000000 diff --git a/spec/fixtures/ifconfig/open_solaris_10 b/spec/fixtures/ifconfig/open_solaris_10 index ed5dcbe632..76536fdc8a 100644 --- a/spec/fixtures/ifconfig/open_solaris_10 +++ b/spec/fixtures/ifconfig/open_solaris_10 @@ -9,4 +9,4 @@ hme0: flags=2000841 mtu 1500 index 2 inet6 fe80::a00:20ff:fed1:6d79/10 ether 8:0:20:d1:6d:79 hme0:1: flags=2080841 mtu 1500 index 2 - inet6 2404:130:0:1000:a00:20ff:fed1:6d79/64 \ No newline at end of file + inet6 2404:130:0:1000:a00:20ff:fed1:6d79/64 diff --git a/spec/fixtures/ifconfig/open_solaris_b132 b/spec/fixtures/ifconfig/open_solaris_b132 index c674a41239..9990a726f5 100644 --- a/spec/fixtures/ifconfig/open_solaris_b132 +++ b/spec/fixtures/ifconfig/open_solaris_b132 @@ -17,4 +17,4 @@ int0: flags=2100841 mtu 9000 index 3 inet6 fe80::ff:0/10 ether 2:8:20:89:75:75 int0:1: flags=2180841 mtu 9000 index 3 - inet6 2404:130:40:18::ff:0/64 \ No newline at end of file + inet6 2404:130:40:18::ff:0/64 diff --git a/spec/fixtures/ifconfig/ubuntu_7_04 b/spec/fixtures/ifconfig/ubuntu_7_04 index 8310576401..d1b9ea3b3c 100644 --- a/spec/fixtures/ifconfig/ubuntu_7_04 +++ b/spec/fixtures/ifconfig/ubuntu_7_04 @@ -35,4 +35,4 @@ wifi0 Link encap:UNSPEC HWaddr 00-17-F2-49-E0-E6-00-00-00-00-00-00-00-00-00 TX packets:193 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:199 RX bytes:754290 (736.6 KiB) TX bytes:8878 (8.6 KiB) - Interrupt:16 \ No newline at end of file + Interrupt:16 diff --git a/spec/fixtures/ifconfig/ubuntu_7_04_eth0 b/spec/fixtures/ifconfig/ubuntu_7_04_eth0 index b9fc759303..91eb31ca1a 100644 --- a/spec/fixtures/ifconfig/ubuntu_7_04_eth0 +++ b/spec/fixtures/ifconfig/ubuntu_7_04_eth0 @@ -6,4 +6,4 @@ eth0 Link encap:Ethernet HWaddr 00:16:CB:A6:D4:3A TX packets:280 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:73859 (72.1 KiB) TX bytes:35243 (34.4 KiB) - Interrupt:17 \ No newline at end of file + Interrupt:17 diff --git a/spec/fixtures/netstat/centos_5_5 b/spec/fixtures/netstat/centos_5_5 index f2c593e18d..2c465816a7 100644 --- a/spec/fixtures/netstat/centos_5_5 +++ b/spec/fixtures/netstat/centos_5_5 @@ -2,4 +2,4 @@ Kernel IP routing table Destination Gateway Genmask Flags MSS Window irtt Iface 100.100.100.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0 169.254.0.0 0.0.0.0 255.255.0.0 U 0 0 0 eth0 -0.0.0.0 100.100.100.1 0.0.0.0 UG 0 0 0 eth0 \ No newline at end of file +0.0.0.0 100.100.100.1 0.0.0.0 UG 0 0 0 eth0 diff --git a/spec/fixtures/netstat/darwin_10_3_0 b/spec/fixtures/netstat/darwin_10_3_0 index 62533e2183..0aa6f4c491 100644 --- a/spec/fixtures/netstat/darwin_10_3_0 +++ b/spec/fixtures/netstat/darwin_10_3_0 @@ -32,4 +32,4 @@ fe80::217:f2ff:fe06:e3c2%en0 0:17:f2:6:e3:c2 UHL ff01::/32 ::1 Um lo0 ff02::/32 ::1 UmC lo0 ff02::/32 link#4 UmC en0 -ff02::fb link#4 UHmLW en0 \ No newline at end of file +ff02::fb link#4 UHmLW en0 diff --git a/spec/fixtures/netstat/darwin_9_8_0 b/spec/fixtures/netstat/darwin_9_8_0 index d552d3d3ea..a8b8a507ba 100644 --- a/spec/fixtures/netstat/darwin_9_8_0 +++ b/spec/fixtures/netstat/darwin_9_8_0 @@ -25,4 +25,4 @@ fe80::%en0/64 link#4 UC fe80::217:f2ff:fe06:e42e%en0 0:17:f2:6:e4:2e UHL lo0 ff01::/32 ::1 U lo0 ff02::/32 fe80::1%lo0 UC lo0 -ff02::/32 link#4 UC en0 \ No newline at end of file +ff02::/32 link#4 UC en0 diff --git a/spec/fixtures/netstat/fedora_10 b/spec/fixtures/netstat/fedora_10 index 991bc3a9db..33d3f4e3db 100644 --- a/spec/fixtures/netstat/fedora_10 +++ b/spec/fixtures/netstat/fedora_10 @@ -4,4 +4,4 @@ Destination Gateway Genmask Flags MSS Window irtt Iface 192.168.183.0 0.0.0.0 255.255.255.0 U 0 0 0 vmnet8 100.100.108.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0 169.254.0.0 0.0.0.0 255.255.0.0 U 0 0 0 eth0 -0.0.0.0 100.100.108.1 0.0.0.0 UG 0 0 0 eth0 \ No newline at end of file +0.0.0.0 100.100.108.1 0.0.0.0 UG 0 0 0 eth0 diff --git a/spec/fixtures/netstat/open_solaris_10 b/spec/fixtures/netstat/open_solaris_10 index 7e50620700..c21170434a 100644 --- a/spec/fixtures/netstat/open_solaris_10 +++ b/spec/fixtures/netstat/open_solaris_10 @@ -13,4 +13,4 @@ Routing Table: IPv6 fe80::/10 fe80::a00:20ff:fed1:6d79 U 1 0 hme0 ff00::/8 fe80::a00:20ff:fed1:6d79 U 1 0 hme0 default fe80::214:22ff:fe0a:1c46 UG 1 0 hme0 -::1 ::1 UH 1 140 lo0 \ No newline at end of file +::1 ::1 UH 1 140 lo0 diff --git a/spec/fixtures/netstat/open_solaris_b132 b/spec/fixtures/netstat/open_solaris_b132 index a75fab6294..9a7ed8906a 100644 --- a/spec/fixtures/netstat/open_solaris_b132 +++ b/spec/fixtures/netstat/open_solaris_b132 @@ -14,4 +14,4 @@ Routing Table: IPv6 2404:130:40:18::/64 2404:130:40:18::ff:0 U 4 1302106 int0 fe80::/10 fe80::ff:0 U 3 16283 int0 fe80::/10 fe80::ff:0 U 5 314822 bge0 -default fe80::20d:edff:fe9d:782e UG 2 420100 bge0 \ No newline at end of file +default fe80::20d:edff:fe9d:782e UG 2 420100 bge0 diff --git a/spec/fixtures/netstat/ubuntu_7_04 b/spec/fixtures/netstat/ubuntu_7_04 index 40b80607df..4f49311530 100644 --- a/spec/fixtures/netstat/ubuntu_7_04 +++ b/spec/fixtures/netstat/ubuntu_7_04 @@ -4,4 +4,4 @@ Destination Gateway Genmask Flags MSS Window irtt Iface 169.254.0.0 0.0.0.0 255.255.0.0 U 0 0 0 ath0 169.254.0.0 0.0.0.0 255.255.0.0 U 0 0 0 eth0 0.0.0.0 100.100.106.1 0.0.0.0 UG 0 0 0 eth0 -0.0.0.0 0.0.0.0 0.0.0.0 U 0 0 0 ath0 \ No newline at end of file +0.0.0.0 0.0.0.0 0.0.0.0 U 0 0 0 ath0 diff --git a/spec/fixtures/unit/util/ec2/centos-arp-ec2.out b/spec/fixtures/unit/util/ec2/centos-arp-ec2.out index 24d2ec03ac..22ed8be7ae 100644 --- a/spec/fixtures/unit/util/ec2/centos-arp-ec2.out +++ b/spec/fixtures/unit/util/ec2/centos-arp-ec2.out @@ -1 +1 @@ -? (10.240.93.1) at FE:FF:FF:FF:FF:FF [ether] on eth0 \ No newline at end of file +? (10.240.93.1) at FE:FF:FF:FF:FF:FF [ether] on eth0 diff --git a/spec/fixtures/unit/util/ec2/windows-2008-arp-a.out b/spec/fixtures/unit/util/ec2/windows-2008-arp-a.out index e1623a0e30..7824cf3f98 100644 --- a/spec/fixtures/unit/util/ec2/windows-2008-arp-a.out +++ b/spec/fixtures/unit/util/ec2/windows-2008-arp-a.out @@ -7,4 +7,4 @@ Interface: 10.32.123.54 --- 0xd 169.254.169.254 fe-ff-ff-ff-ff-ff dynamic 224.0.0.22 01-00-5e-00-00-16 static 224.0.0.252 01-00-5e-00-00-fc static - 255.255.255.255 ff-ff-ff-ff-ff-ff static \ No newline at end of file + 255.255.255.255 ff-ff-ff-ff-ff-ff static diff --git a/spec/fixtures/unit/util/ip/6.0-STABLE_FreeBSD_ifconfig b/spec/fixtures/unit/util/ip/6.0-STABLE_FreeBSD_ifconfig index 3339a45588..3335708c2f 100644 --- a/spec/fixtures/unit/util/ip/6.0-STABLE_FreeBSD_ifconfig +++ b/spec/fixtures/unit/util/ip/6.0-STABLE_FreeBSD_ifconfig @@ -9,4 +9,4 @@ fxp0: flags=8843 mtu 1500 media: Ethernet autoselect (100baseTX ) status: active lo0: flags=8049 mtu 16384 - inet 127.0.0.1 netmask 0xff000000 \ No newline at end of file + inet 127.0.0.1 netmask 0xff000000 diff --git a/spec/fixtures/unit/util/ip/linux_get_single_interface_ib0 b/spec/fixtures/unit/util/ip/linux_get_single_interface_ib0 index 0827b98620..e408574c61 100644 --- a/spec/fixtures/unit/util/ip/linux_get_single_interface_ib0 +++ b/spec/fixtures/unit/util/ip/linux_get_single_interface_ib0 @@ -5,4 +5,4 @@ ib0 Link encap:InfiniBand HWaddr 80:00:00:4a:fe:80:00:00:00:00:00:00:00:0 RX packets:8 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1024 - RX bytes:448 (448.0 b) TX bytes:0 (0.0 b) \ No newline at end of file + RX bytes:448 (448.0 b) TX bytes:0 (0.0 b) diff --git a/spec/fixtures/unit/util/ip/linux_ifconfig_all_with_single_interface b/spec/fixtures/unit/util/ip/linux_ifconfig_all_with_single_interface index e063b77e78..1c88454286 100644 --- a/spec/fixtures/unit/util/ip/linux_ifconfig_all_with_single_interface +++ b/spec/fixtures/unit/util/ip/linux_ifconfig_all_with_single_interface @@ -15,4 +15,4 @@ lo Link encap:Local Loopback RX packets:1630 errors:0 dropped:0 overruns:0 frame:0 TX packets:1630 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 - RX bytes:81500 (79.5 KB) TX bytes:81500 (79.5 KB) \ No newline at end of file + RX bytes:81500 (79.5 KB) TX bytes:81500 (79.5 KB) diff --git a/spec/fixtures/unit/util/ip/linux_ifconfig_ib0 b/spec/fixtures/unit/util/ip/linux_ifconfig_ib0 index 74cf3014e5..27d87f76d3 100644 --- a/spec/fixtures/unit/util/ip/linux_ifconfig_ib0 +++ b/spec/fixtures/unit/util/ip/linux_ifconfig_ib0 @@ -5,4 +5,4 @@ ib0 Link encap:InfiniBand HWaddr 80:00:00:4A:FE:80:00:00:00:00:00:00:00:0 RX packets:8 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1024 - RX bytes:448 (448.0 b) TX bytes:0 (0.0 b) \ No newline at end of file + RX bytes:448 (448.0 b) TX bytes:0 (0.0 b) diff --git a/spec/fixtures/unit/util/ip/solaris_ifconfig_single_interface b/spec/fixtures/unit/util/ip/solaris_ifconfig_single_interface index 7a227aa5f9..a408938183 100644 --- a/spec/fixtures/unit/util/ip/solaris_ifconfig_single_interface +++ b/spec/fixtures/unit/util/ip/solaris_ifconfig_single_interface @@ -1,3 +1,3 @@ e1000g0: flags=201004843 mtu 1500 index 2 inet 172.16.15.138 netmask ffffff00 broadcast 172.16.15.255 - ether 0:c:29:c1:70:2a \ No newline at end of file + ether 0:c:29:c1:70:2a From 6e253d6958cc073269e9e49f2d21a19c0f37eb1b Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Thu, 18 Oct 2012 12:20:23 -0700 Subject: [PATCH 1047/3753] (maint) Add newlines to all files (whitespace only) Without this patch there are a number of files that do not have newlines. This is a problem because it makes it difficult to use `perl -pl -i -e ...` --- spec/fixtures/unit/util/ip/linux_get_single_interface_ib0 | 2 +- spec/fixtures/unit/util/ip/linux_ifconfig_ib0 | 2 +- spec/unit/manufacturer_spec.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/fixtures/unit/util/ip/linux_get_single_interface_ib0 b/spec/fixtures/unit/util/ip/linux_get_single_interface_ib0 index 0827b98620..e408574c61 100644 --- a/spec/fixtures/unit/util/ip/linux_get_single_interface_ib0 +++ b/spec/fixtures/unit/util/ip/linux_get_single_interface_ib0 @@ -5,4 +5,4 @@ ib0 Link encap:InfiniBand HWaddr 80:00:00:4a:fe:80:00:00:00:00:00:00:00:0 RX packets:8 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1024 - RX bytes:448 (448.0 b) TX bytes:0 (0.0 b) \ No newline at end of file + RX bytes:448 (448.0 b) TX bytes:0 (0.0 b) diff --git a/spec/fixtures/unit/util/ip/linux_ifconfig_ib0 b/spec/fixtures/unit/util/ip/linux_ifconfig_ib0 index 74cf3014e5..27d87f76d3 100644 --- a/spec/fixtures/unit/util/ip/linux_ifconfig_ib0 +++ b/spec/fixtures/unit/util/ip/linux_ifconfig_ib0 @@ -5,4 +5,4 @@ ib0 Link encap:InfiniBand HWaddr 80:00:00:4A:FE:80:00:00:00:00:00:00:00:0 RX packets:8 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1024 - RX bytes:448 (448.0 b) TX bytes:0 (0.0 b) \ No newline at end of file + RX bytes:448 (448.0 b) TX bytes:0 (0.0 b) diff --git a/spec/unit/manufacturer_spec.rb b/spec/unit/manufacturer_spec.rb index dd412458b9..cacf470f27 100644 --- a/spec/unit/manufacturer_spec.rb +++ b/spec/unit/manufacturer_spec.rb @@ -112,4 +112,4 @@ end -end \ No newline at end of file +end From 6e097b9d7c082b17a729e18e2a5f9fb5b0c6993e Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Thu, 18 Oct 2012 12:22:56 -0700 Subject: [PATCH 1048/3753] (maint) Remove rspec from shebang lines Without this patch Ruby 1.9 is still complaining loudly about trying to parse the spec files. I'd prefer to remove the shebang lines entirely, but doing so will cause encoding errors in Ruby 1.9. This patch strives for a happy middle ground of convincing Ruby it is actually working with Ruby while not confusing it to think it should exec() to rspec. This patch is the result of the following command run against the source tree: find spec -type f -print0 | \ xargs -0 perl -pl -i -e 's,^\#\!\s?/(.*)rspec,\#! /usr/bin/env ruby,' --- spec/integration/facter_spec.rb | 2 +- spec/unit/architecture_spec.rb | 2 +- spec/unit/domain_spec.rb | 2 +- spec/unit/ec2_spec.rb | 2 +- spec/unit/facter_spec.rb | 2 +- spec/unit/filesystems_spec.rb | 2 +- spec/unit/hostname_spec.rb | 2 +- spec/unit/id_spec.rb | 2 +- spec/unit/interfaces_spec.rb | 2 +- spec/unit/ipaddress6_spec.rb | 2 +- spec/unit/kernel_spec.rb | 2 +- spec/unit/kernelmajversion_spec.rb | 2 +- spec/unit/kernelrelease_spec.rb | 2 +- spec/unit/kernelversion_spec.rb | 2 +- spec/unit/lsbdistcodename_spec.rb | 2 +- spec/unit/lsbdistdescription_spec.rb | 2 +- spec/unit/lsbdistid_spec.rb | 2 +- spec/unit/lsbdistrelease_spec.rb | 2 +- spec/unit/lsbrelease_spec.rb | 2 +- spec/unit/macaddress_spec.rb | 2 +- spec/unit/memory_spec.rb | 2 +- spec/unit/operatingsystem_spec.rb | 2 +- spec/unit/operatingsystemrelease_spec.rb | 2 +- spec/unit/physicalprocessorcount_spec.rb | 2 +- spec/unit/processor_spec.rb | 2 +- spec/unit/selinux_spec.rb | 2 +- spec/unit/ssh_spec.rb | 2 +- spec/unit/uptime_spec.rb | 2 +- spec/unit/util/collection_spec.rb | 2 +- spec/unit/util/config_spec.rb | 2 +- spec/unit/util/confine_spec.rb | 2 +- spec/unit/util/ec2_spec.rb | 2 +- spec/unit/util/fact_spec.rb | 2 +- spec/unit/util/ip_spec.rb | 2 +- spec/unit/util/loader_spec.rb | 2 +- spec/unit/util/macaddress_spec.rb | 2 +- spec/unit/util/macosx_spec.rb | 2 +- spec/unit/util/manufacturer_spec.rb | 2 +- spec/unit/util/processor_spec.rb | 2 +- spec/unit/util/registry_spec.rb | 2 +- spec/unit/util/resolution_spec.rb | 2 +- spec/unit/util/uptime_spec.rb | 2 +- spec/unit/util/virtual_spec.rb | 2 +- spec/unit/util/vlans_spec.rb | 2 +- spec/unit/util/wmi_spec.rb | 2 +- spec/unit/util/xendomains_spec.rb | 2 +- spec/unit/virtual_spec.rb | 2 +- spec/unit/zonename_spec.rb | 2 +- 48 files changed, 48 insertions(+), 48 deletions(-) diff --git a/spec/integration/facter_spec.rb b/spec/integration/facter_spec.rb index 4fd33088ed..c9ee6e1612 100755 --- a/spec/integration/facter_spec.rb +++ b/spec/integration/facter_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/architecture_spec.rb b/spec/unit/architecture_spec.rb index 81784882de..7e69198aef 100755 --- a/spec/unit/architecture_spec.rb +++ b/spec/unit/architecture_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/domain_spec.rb b/spec/unit/domain_spec.rb index c772be6a3d..1135de1760 100755 --- a/spec/unit/domain_spec.rb +++ b/spec/unit/domain_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/ec2_spec.rb b/spec/unit/ec2_spec.rb index 03712d2a60..a3dbe9f0a3 100755 --- a/spec/unit/ec2_spec.rb +++ b/spec/unit/ec2_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' require 'facter/util/ec2' diff --git a/spec/unit/facter_spec.rb b/spec/unit/facter_spec.rb index 1312c15f44..e86bfa4012 100755 --- a/spec/unit/facter_spec.rb +++ b/spec/unit/facter_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/filesystems_spec.rb b/spec/unit/filesystems_spec.rb index ab52adaea7..8ed6da233a 100644 --- a/spec/unit/filesystems_spec.rb +++ b/spec/unit/filesystems_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/hostname_spec.rb b/spec/unit/hostname_spec.rb index 35d8bd7db0..0c31493f66 100755 --- a/spec/unit/hostname_spec.rb +++ b/spec/unit/hostname_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/id_spec.rb b/spec/unit/id_spec.rb index 15a61b63d2..e89aae4d18 100755 --- a/spec/unit/id_spec.rb +++ b/spec/unit/id_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/interfaces_spec.rb b/spec/unit/interfaces_spec.rb index c58b221fd4..3798721ebd 100755 --- a/spec/unit/interfaces_spec.rb +++ b/spec/unit/interfaces_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' require 'facter/util/ip' diff --git a/spec/unit/ipaddress6_spec.rb b/spec/unit/ipaddress6_spec.rb index fc03e62f02..6891902109 100755 --- a/spec/unit/ipaddress6_spec.rb +++ b/spec/unit/ipaddress6_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/kernel_spec.rb b/spec/unit/kernel_spec.rb index 79bc895c85..37d3431810 100644 --- a/spec/unit/kernel_spec.rb +++ b/spec/unit/kernel_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/kernelmajversion_spec.rb b/spec/unit/kernelmajversion_spec.rb index 1661e02aa1..426a87cd10 100644 --- a/spec/unit/kernelmajversion_spec.rb +++ b/spec/unit/kernelmajversion_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/kernelrelease_spec.rb b/spec/unit/kernelrelease_spec.rb index 99710874b1..8566d4aa8b 100644 --- a/spec/unit/kernelrelease_spec.rb +++ b/spec/unit/kernelrelease_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/kernelversion_spec.rb b/spec/unit/kernelversion_spec.rb index ea50069eb2..98cedccd5f 100644 --- a/spec/unit/kernelversion_spec.rb +++ b/spec/unit/kernelversion_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/lsbdistcodename_spec.rb b/spec/unit/lsbdistcodename_spec.rb index efb14b7be8..d0297ca288 100755 --- a/spec/unit/lsbdistcodename_spec.rb +++ b/spec/unit/lsbdistcodename_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/lsbdistdescription_spec.rb b/spec/unit/lsbdistdescription_spec.rb index e90764c48e..a73612ea0d 100755 --- a/spec/unit/lsbdistdescription_spec.rb +++ b/spec/unit/lsbdistdescription_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/lsbdistid_spec.rb b/spec/unit/lsbdistid_spec.rb index ae72b738e0..b204a5f6f3 100755 --- a/spec/unit/lsbdistid_spec.rb +++ b/spec/unit/lsbdistid_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/lsbdistrelease_spec.rb b/spec/unit/lsbdistrelease_spec.rb index 45101e0050..c356e901e7 100755 --- a/spec/unit/lsbdistrelease_spec.rb +++ b/spec/unit/lsbdistrelease_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/lsbrelease_spec.rb b/spec/unit/lsbrelease_spec.rb index 89d91bc630..b0d5c93823 100755 --- a/spec/unit/lsbrelease_spec.rb +++ b/spec/unit/lsbrelease_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/macaddress_spec.rb b/spec/unit/macaddress_spec.rb index 1bdbf5674c..05573e9a4c 100755 --- a/spec/unit/macaddress_spec.rb +++ b/spec/unit/macaddress_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/memory_spec.rb b/spec/unit/memory_spec.rb index 459efb9557..8ef6897b05 100755 --- a/spec/unit/memory_spec.rb +++ b/spec/unit/memory_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/operatingsystem_spec.rb b/spec/unit/operatingsystem_spec.rb index d2550f817c..433bd3820b 100755 --- a/spec/unit/operatingsystem_spec.rb +++ b/spec/unit/operatingsystem_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/operatingsystemrelease_spec.rb b/spec/unit/operatingsystemrelease_spec.rb index 3d7343821d..fc77f66bbc 100755 --- a/spec/unit/operatingsystemrelease_spec.rb +++ b/spec/unit/operatingsystemrelease_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/physicalprocessorcount_spec.rb b/spec/unit/physicalprocessorcount_spec.rb index 78bf3f57c5..a36eed47f9 100755 --- a/spec/unit/physicalprocessorcount_spec.rb +++ b/spec/unit/physicalprocessorcount_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/processor_spec.rb b/spec/unit/processor_spec.rb index 126094d431..1cee3f57e5 100755 --- a/spec/unit/processor_spec.rb +++ b/spec/unit/processor_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/selinux_spec.rb b/spec/unit/selinux_spec.rb index c7bc1f9b80..647fbb65c6 100755 --- a/spec/unit/selinux_spec.rb +++ b/spec/unit/selinux_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/ssh_spec.rb b/spec/unit/ssh_spec.rb index e1556deb3d..7fd029dd07 100755 --- a/spec/unit/ssh_spec.rb +++ b/spec/unit/ssh_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' require 'facter/ssh' diff --git a/spec/unit/uptime_spec.rb b/spec/unit/uptime_spec.rb index fd30efccf1..0a2d086bd6 100755 --- a/spec/unit/uptime_spec.rb +++ b/spec/unit/uptime_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' require 'facter/util/uptime' diff --git a/spec/unit/util/collection_spec.rb b/spec/unit/util/collection_spec.rb index 94a3ed1cce..1c92cf2c36 100755 --- a/spec/unit/util/collection_spec.rb +++ b/spec/unit/util/collection_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' require 'facter/util/collection' diff --git a/spec/unit/util/config_spec.rb b/spec/unit/util/config_spec.rb index 5505aad4a1..23bcd99d8a 100644 --- a/spec/unit/util/config_spec.rb +++ b/spec/unit/util/config_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/util/confine_spec.rb b/spec/unit/util/confine_spec.rb index 7f5a07492b..4b50c5342b 100755 --- a/spec/unit/util/confine_spec.rb +++ b/spec/unit/util/confine_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' require 'facter/util/confine' diff --git a/spec/unit/util/ec2_spec.rb b/spec/unit/util/ec2_spec.rb index bd5581f3e2..b5cbf18fec 100755 --- a/spec/unit/util/ec2_spec.rb +++ b/spec/unit/util/ec2_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' require 'facter/util/ec2' diff --git a/spec/unit/util/fact_spec.rb b/spec/unit/util/fact_spec.rb index 72f95db0b5..ad37321539 100755 --- a/spec/unit/util/fact_spec.rb +++ b/spec/unit/util/fact_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' require 'facter/util/fact' diff --git a/spec/unit/util/ip_spec.rb b/spec/unit/util/ip_spec.rb index 1159b9c3f0..6baaad5bd8 100755 --- a/spec/unit/util/ip_spec.rb +++ b/spec/unit/util/ip_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' require 'facter/util/ip' diff --git a/spec/unit/util/loader_spec.rb b/spec/unit/util/loader_spec.rb index 3e2d3bf94f..2b9891e413 100755 --- a/spec/unit/util/loader_spec.rb +++ b/spec/unit/util/loader_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' require 'facter/util/loader' diff --git a/spec/unit/util/macaddress_spec.rb b/spec/unit/util/macaddress_spec.rb index c0e3456e5e..95e79aad74 100755 --- a/spec/unit/util/macaddress_spec.rb +++ b/spec/unit/util/macaddress_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' require 'facter/util/macaddress' diff --git a/spec/unit/util/macosx_spec.rb b/spec/unit/util/macosx_spec.rb index de6ceb3ffb..60d08f3608 100755 --- a/spec/unit/util/macosx_spec.rb +++ b/spec/unit/util/macosx_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' require 'facter/util/macosx' diff --git a/spec/unit/util/manufacturer_spec.rb b/spec/unit/util/manufacturer_spec.rb index b2d76dbbae..59fbf5ca24 100755 --- a/spec/unit/util/manufacturer_spec.rb +++ b/spec/unit/util/manufacturer_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' require 'facter/util/manufacturer' diff --git a/spec/unit/util/processor_spec.rb b/spec/unit/util/processor_spec.rb index b4482157ab..2a3677f732 100755 --- a/spec/unit/util/processor_spec.rb +++ b/spec/unit/util/processor_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' require 'facter/util/processor' diff --git a/spec/unit/util/registry_spec.rb b/spec/unit/util/registry_spec.rb index d7d77b5a98..bc9658ee6b 100644 --- a/spec/unit/util/registry_spec.rb +++ b/spec/unit/util/registry_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' require 'facter/operatingsystem' require 'facter/util/registry' diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index ffe6b6b850..136120afb2 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' require 'facter/util/resolution' diff --git a/spec/unit/util/uptime_spec.rb b/spec/unit/util/uptime_spec.rb index 5b4b757262..e7a152948c 100755 --- a/spec/unit/util/uptime_spec.rb +++ b/spec/unit/util/uptime_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' require 'facter/util/uptime' diff --git a/spec/unit/util/virtual_spec.rb b/spec/unit/util/virtual_spec.rb index c5393fbb95..615acba49a 100755 --- a/spec/unit/util/virtual_spec.rb +++ b/spec/unit/util/virtual_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' require 'facter/util/virtual' diff --git a/spec/unit/util/vlans_spec.rb b/spec/unit/util/vlans_spec.rb index 8316b05bcd..70de170fc6 100755 --- a/spec/unit/util/vlans_spec.rb +++ b/spec/unit/util/vlans_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' require 'facter/util/vlans' diff --git a/spec/unit/util/wmi_spec.rb b/spec/unit/util/wmi_spec.rb index ed48ef70cf..2db2621037 100755 --- a/spec/unit/util/wmi_spec.rb +++ b/spec/unit/util/wmi_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' require 'facter/util/wmi' diff --git a/spec/unit/util/xendomains_spec.rb b/spec/unit/util/xendomains_spec.rb index 391b8a4b92..8649488898 100755 --- a/spec/unit/util/xendomains_spec.rb +++ b/spec/unit/util/xendomains_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' require 'facter/util/xendomains' diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index dceabc8349..e2bb67927a 100755 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' require 'facter/util/virtual' diff --git a/spec/unit/zonename_spec.rb b/spec/unit/zonename_spec.rb index b768642b81..b0c5b31967 100644 --- a/spec/unit/zonename_spec.rb +++ b/spec/unit/zonename_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby require 'spec_helper' require 'facter' From 5b3d509d7e0dbd0f03924627aeb6bab511e6dc57 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Thu, 18 Oct 2012 12:22:56 -0700 Subject: [PATCH 1049/3753] (maint) Remove rspec from shebang lines Without this patch Ruby 1.9 is still complaining loudly about trying to parse the spec files. I'd prefer to remove the shebang lines entirely, but doing so will cause encoding errors in Ruby 1.9. This patch strives for a happy middle ground of convincing Ruby it is actually working with Ruby while not confusing it to think it should exec() to rspec. This patch is the result of the following command run against the source tree: find spec -type f -print0 | \ xargs -0 perl -pl -i -e 's,^\#\!\s?/(.*)rspec,\#! /usr/bin/env ruby,' --- spec/integration/facter_spec.rb | 2 +- spec/unit/architecture_spec.rb | 2 +- spec/unit/domain_spec.rb | 2 +- spec/unit/ec2_spec.rb | 2 +- spec/unit/facter_spec.rb | 2 +- spec/unit/hostname_spec.rb | 2 +- spec/unit/id_spec.rb | 2 +- spec/unit/interfaces_spec.rb | 2 +- spec/unit/ipaddress6_spec.rb | 2 +- spec/unit/lsbdistcodename_spec.rb | 2 +- spec/unit/lsbdistdescription_spec.rb | 2 +- spec/unit/lsbdistid_spec.rb | 2 +- spec/unit/lsbdistrelease_spec.rb | 2 +- spec/unit/lsbrelease_spec.rb | 2 +- spec/unit/macaddress_spec.rb | 2 +- spec/unit/memory_spec.rb | 2 +- spec/unit/operatingsystem_spec.rb | 2 +- spec/unit/operatingsystemrelease_spec.rb | 2 +- spec/unit/physicalprocessorcount_spec.rb | 2 +- spec/unit/processor_spec.rb | 2 +- spec/unit/selinux_spec.rb | 2 +- spec/unit/uptime_spec.rb | 2 +- spec/unit/util/collection_spec.rb | 2 +- spec/unit/util/config_spec.rb | 2 +- spec/unit/util/confine_spec.rb | 2 +- spec/unit/util/ec2_spec.rb | 2 +- spec/unit/util/fact_spec.rb | 2 +- spec/unit/util/ip_spec.rb | 2 +- spec/unit/util/loader_spec.rb | 2 +- spec/unit/util/macaddress_spec.rb | 2 +- spec/unit/util/macosx_spec.rb | 2 +- spec/unit/util/manufacturer_spec.rb | 2 +- spec/unit/util/processor_spec.rb | 2 +- spec/unit/util/registry_spec.rb | 2 +- spec/unit/util/resolution_spec.rb | 2 +- spec/unit/util/uptime_spec.rb | 2 +- spec/unit/util/virtual_spec.rb | 2 +- spec/unit/util/vlans_spec.rb | 2 +- spec/unit/util/wmi_spec.rb | 2 +- spec/unit/util/xendomains_spec.rb | 2 +- spec/unit/virtual_spec.rb | 2 +- 41 files changed, 41 insertions(+), 41 deletions(-) diff --git a/spec/integration/facter_spec.rb b/spec/integration/facter_spec.rb index 0c52895699..456626a3ba 100755 --- a/spec/integration/facter_spec.rb +++ b/spec/integration/facter_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/architecture_spec.rb b/spec/unit/architecture_spec.rb index ae753f190b..ee91299a8a 100755 --- a/spec/unit/architecture_spec.rb +++ b/spec/unit/architecture_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/domain_spec.rb b/spec/unit/domain_spec.rb index 07195ca180..0a589938e8 100755 --- a/spec/unit/domain_spec.rb +++ b/spec/unit/domain_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/ec2_spec.rb b/spec/unit/ec2_spec.rb index 7c7a3d137b..6211a0323b 100755 --- a/spec/unit/ec2_spec.rb +++ b/spec/unit/ec2_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby require 'spec_helper' require 'facter/util/ec2' diff --git a/spec/unit/facter_spec.rb b/spec/unit/facter_spec.rb index 81edbc0410..e86bfa4012 100755 --- a/spec/unit/facter_spec.rb +++ b/spec/unit/facter_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/hostname_spec.rb b/spec/unit/hostname_spec.rb index f25c96dedb..0c31493f66 100755 --- a/spec/unit/hostname_spec.rb +++ b/spec/unit/hostname_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/id_spec.rb b/spec/unit/id_spec.rb index c708caa684..276567e474 100755 --- a/spec/unit/id_spec.rb +++ b/spec/unit/id_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/interfaces_spec.rb b/spec/unit/interfaces_spec.rb index 54aadc5f88..3798721ebd 100755 --- a/spec/unit/interfaces_spec.rb +++ b/spec/unit/interfaces_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby require 'spec_helper' require 'facter/util/ip' diff --git a/spec/unit/ipaddress6_spec.rb b/spec/unit/ipaddress6_spec.rb index cd03aa13a5..64465eb7f5 100755 --- a/spec/unit/ipaddress6_spec.rb +++ b/spec/unit/ipaddress6_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/lsbdistcodename_spec.rb b/spec/unit/lsbdistcodename_spec.rb index e30aadaca7..d0297ca288 100755 --- a/spec/unit/lsbdistcodename_spec.rb +++ b/spec/unit/lsbdistcodename_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/lsbdistdescription_spec.rb b/spec/unit/lsbdistdescription_spec.rb index 31f7761725..a73612ea0d 100755 --- a/spec/unit/lsbdistdescription_spec.rb +++ b/spec/unit/lsbdistdescription_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/lsbdistid_spec.rb b/spec/unit/lsbdistid_spec.rb index 8ead532052..b204a5f6f3 100755 --- a/spec/unit/lsbdistid_spec.rb +++ b/spec/unit/lsbdistid_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/lsbdistrelease_spec.rb b/spec/unit/lsbdistrelease_spec.rb index bd21eba980..c356e901e7 100755 --- a/spec/unit/lsbdistrelease_spec.rb +++ b/spec/unit/lsbdistrelease_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/lsbrelease_spec.rb b/spec/unit/lsbrelease_spec.rb index f1cd7fc2ee..b0d5c93823 100755 --- a/spec/unit/lsbrelease_spec.rb +++ b/spec/unit/lsbrelease_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/macaddress_spec.rb b/spec/unit/macaddress_spec.rb index 3dc24d9747..1b703e3909 100755 --- a/spec/unit/macaddress_spec.rb +++ b/spec/unit/macaddress_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/memory_spec.rb b/spec/unit/memory_spec.rb index 5e8523932f..8ce2cc2efd 100755 --- a/spec/unit/memory_spec.rb +++ b/spec/unit/memory_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/operatingsystem_spec.rb b/spec/unit/operatingsystem_spec.rb index 59069a1bcd..fd267f385f 100755 --- a/spec/unit/operatingsystem_spec.rb +++ b/spec/unit/operatingsystem_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/operatingsystemrelease_spec.rb b/spec/unit/operatingsystemrelease_spec.rb index 76f7bf145a..8158c66e80 100755 --- a/spec/unit/operatingsystemrelease_spec.rb +++ b/spec/unit/operatingsystemrelease_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/physicalprocessorcount_spec.rb b/spec/unit/physicalprocessorcount_spec.rb index 3a8d4984da..a36eed47f9 100755 --- a/spec/unit/physicalprocessorcount_spec.rb +++ b/spec/unit/physicalprocessorcount_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/processor_spec.rb b/spec/unit/processor_spec.rb index c7335da8a0..877e596506 100755 --- a/spec/unit/processor_spec.rb +++ b/spec/unit/processor_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/selinux_spec.rb b/spec/unit/selinux_spec.rb index 2efab7d640..647fbb65c6 100755 --- a/spec/unit/selinux_spec.rb +++ b/spec/unit/selinux_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/uptime_spec.rb b/spec/unit/uptime_spec.rb index e1962d9250..0a2d086bd6 100755 --- a/spec/unit/uptime_spec.rb +++ b/spec/unit/uptime_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby require 'spec_helper' require 'facter/util/uptime' diff --git a/spec/unit/util/collection_spec.rb b/spec/unit/util/collection_spec.rb index 5dce6bdcf6..a78a674807 100755 --- a/spec/unit/util/collection_spec.rb +++ b/spec/unit/util/collection_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby require 'spec_helper' require 'facter/util/collection' diff --git a/spec/unit/util/config_spec.rb b/spec/unit/util/config_spec.rb index 3eb134b4d2..f01fd3feb0 100644 --- a/spec/unit/util/config_spec.rb +++ b/spec/unit/util/config_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby require 'spec_helper' require 'facter/util/config' diff --git a/spec/unit/util/confine_spec.rb b/spec/unit/util/confine_spec.rb index 806637dc8c..4b50c5342b 100755 --- a/spec/unit/util/confine_spec.rb +++ b/spec/unit/util/confine_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby require 'spec_helper' require 'facter/util/confine' diff --git a/spec/unit/util/ec2_spec.rb b/spec/unit/util/ec2_spec.rb index 7662411449..b5cbf18fec 100755 --- a/spec/unit/util/ec2_spec.rb +++ b/spec/unit/util/ec2_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby require 'spec_helper' require 'facter/util/ec2' diff --git a/spec/unit/util/fact_spec.rb b/spec/unit/util/fact_spec.rb index b3afd84b2a..89e95a25c5 100755 --- a/spec/unit/util/fact_spec.rb +++ b/spec/unit/util/fact_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby require 'spec_helper' require 'facter/util/fact' diff --git a/spec/unit/util/ip_spec.rb b/spec/unit/util/ip_spec.rb index e0034912e3..8bb8c96e55 100755 --- a/spec/unit/util/ip_spec.rb +++ b/spec/unit/util/ip_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby require 'spec_helper' require 'facter/util/ip' diff --git a/spec/unit/util/loader_spec.rb b/spec/unit/util/loader_spec.rb index 3a83f16365..dfd432ddd3 100755 --- a/spec/unit/util/loader_spec.rb +++ b/spec/unit/util/loader_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby require 'spec_helper' require 'facter/util/loader' diff --git a/spec/unit/util/macaddress_spec.rb b/spec/unit/util/macaddress_spec.rb index bf9d4e55f8..95e79aad74 100755 --- a/spec/unit/util/macaddress_spec.rb +++ b/spec/unit/util/macaddress_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby require 'spec_helper' require 'facter/util/macaddress' diff --git a/spec/unit/util/macosx_spec.rb b/spec/unit/util/macosx_spec.rb index dc7bb9bde8..3d81575a61 100755 --- a/spec/unit/util/macosx_spec.rb +++ b/spec/unit/util/macosx_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby require 'spec_helper' require 'facter/util/macosx' diff --git a/spec/unit/util/manufacturer_spec.rb b/spec/unit/util/manufacturer_spec.rb index da9bcbc286..038126e953 100755 --- a/spec/unit/util/manufacturer_spec.rb +++ b/spec/unit/util/manufacturer_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby require 'spec_helper' require 'facter/util/manufacturer' diff --git a/spec/unit/util/processor_spec.rb b/spec/unit/util/processor_spec.rb index 6e0d5b665c..2a3677f732 100755 --- a/spec/unit/util/processor_spec.rb +++ b/spec/unit/util/processor_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby require 'spec_helper' require 'facter/util/processor' diff --git a/spec/unit/util/registry_spec.rb b/spec/unit/util/registry_spec.rb index f13860afd8..bc9658ee6b 100644 --- a/spec/unit/util/registry_spec.rb +++ b/spec/unit/util/registry_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby require 'spec_helper' require 'facter/operatingsystem' require 'facter/util/registry' diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index c3e3794bf0..ca41648d8c 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby require 'spec_helper' require 'facter/util/resolution' diff --git a/spec/unit/util/uptime_spec.rb b/spec/unit/util/uptime_spec.rb index f41b5528f9..e7a152948c 100755 --- a/spec/unit/util/uptime_spec.rb +++ b/spec/unit/util/uptime_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby require 'spec_helper' require 'facter/util/uptime' diff --git a/spec/unit/util/virtual_spec.rb b/spec/unit/util/virtual_spec.rb index ceda83942b..cf22f2b6d5 100755 --- a/spec/unit/util/virtual_spec.rb +++ b/spec/unit/util/virtual_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby require 'spec_helper' require 'facter/util/virtual' diff --git a/spec/unit/util/vlans_spec.rb b/spec/unit/util/vlans_spec.rb index 17ae7eda35..70de170fc6 100755 --- a/spec/unit/util/vlans_spec.rb +++ b/spec/unit/util/vlans_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby require 'spec_helper' require 'facter/util/vlans' diff --git a/spec/unit/util/wmi_spec.rb b/spec/unit/util/wmi_spec.rb index 0f03a18d19..2db2621037 100755 --- a/spec/unit/util/wmi_spec.rb +++ b/spec/unit/util/wmi_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby require 'spec_helper' require 'facter/util/wmi' diff --git a/spec/unit/util/xendomains_spec.rb b/spec/unit/util/xendomains_spec.rb index ff0481fae6..8649488898 100755 --- a/spec/unit/util/xendomains_spec.rb +++ b/spec/unit/util/xendomains_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby require 'spec_helper' require 'facter/util/xendomains' diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index bcd840c768..10ba760812 100755 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby require 'spec_helper' require 'facter/util/virtual' From 462756af2f1ee5c109e301ab0d5a6a329562020f Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Thu, 18 Oct 2012 16:16:54 -0700 Subject: [PATCH 1050/3753] (maint) Add mailmap for git shortlog --- .mailmap | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 .mailmap diff --git a/.mailmap b/.mailmap new file mode 100644 index 0000000000..e751536b04 --- /dev/null +++ b/.mailmap @@ -0,0 +1,99 @@ +Adrien Thebo Adrien Thebo +Adrien Thebo adrienthebo +Allen Ballman ballman +Andrew Shafer Andrew Shafer +Andrew Shafer Andrew Shafer +Andrew Shafer shafer +Anselm Strauss Anselm Strauss +Anselm Strauss Anselm Strauss +Ben Hengst ben hengst +Ben Hughes Ben H +Ben Hughes Ben Hughes +Blake Barnett shadoi +Brice Figureau Brice Figureau +Bryan Kearney Bryan Kearney +Cameron Thomas Cameron Thomas +Carl Caum Carl Caum +Chris Price Chris Price +Chris Price cprice +Christian Hofstaedtler Christian Hofstaedtler +Dan Bode Dan Bode +Dan Bode Dan Bode +Dan Bode Dan Bode +Daniel Pittman Daniel Pittman +David Lutterkort David Lutterkort +David Lutterkort lutter +David Lutterkort lutter +David Schmitt David Schmitt +Deepak Giridharagopal Deepak Giridharagopal +Dominic Maraglia +Donavan Miller donavan +Eric Sorenson Eric Sorenson +Eric Sorenson Eric Sorenson +Eric Sorenson Eric Sorenson +Francois Deppierraz Francois Deppierraz +Garrett Honeycutt Garrett Honeycutt +Gary Larizza Gary Larizza +Greg Sutcliffe Greg Sutcliffe +James Turnbull James Turnbull +James Turnbull James Turnbull +James Turnbull James Turnbull +James Turnbull root +Jeff McCune +Jeff McCune Jeffrey J McCune +Jeff McCune Jeffrey McCune +Jeff McCune mccune +Jeff McCune mccune +Jeff Weiss Jeff Weiss +Jesse Wolfe Jesse Wolfe +Jos Boumans josb +Josh Cooper Josh Cooper +Josh Cooper joshcooper +Joshua Harlan Lifton lifton +Justin Stoller Justin Stoller +Kelsey Hightower Kelsey Hightower +Kelsey Hightower Kelsey Hightower +Ken Barber Ken Barber +Luke Kanies +Luke Kanies Luke Kaines +Luke Kanies Luke Kanies +Luke Kanies Luke Kanies +Luke Kanies luke +Markus Roberts Markus Roberts +Markus Roberts Markus Roberts +Markus Roberts markus +Martin Englund Martin Englund +Matt Palmer mpalmer +Matt Robinson Matt Robinson +Matthaus Owens Matthaus Litteken +Matthaus Owens Matthaus Litteken +Matthaus Owens Matthaus Owens +Michael Stahnke stahnma +Moses Mendoza MosesMendoza +Nan Liu Nan Liu +Nick Fagerlund nfagerlund +Nick Fagerlund nfagerlund +Nigel Kersten Nigel Kersten +Nigel Kersten Nigel Kersten +Ohad Levy Ohad Levy +Patrick Carlisle Patrick +Patrick Carlisle pcarlisle +Paul Lathrop Paul Lathrop +Paul Lathrop Paul Lathrop +Peter Meier duritong +Peter Meier mh +Peter Mørch peter +Pieter van de Bruggen Pieter van de Bruggen +Rahul Gopinath rahul +Rahul Gopinath rahul +Rahul Gopinath rahul +Rahul Gopinath vrthra <9@vrtra.net> +Rein Henrichs Rein Henrichs +Rudy Gevaert rgevaert +Rudy Gevaert rgevaert +Russ Allbery Russ Allbery +Sean E. Millichamp Sean Millichamp +Sean E. Millichamp Sean Millichamp +Steve McIintosh steve mcintosh +Thom May Thom May +Thom May Thom May From cdbd05cee7935824544583aadd4db7e814d1e262 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Thu, 18 Oct 2012 12:04:05 -0700 Subject: [PATCH 1051/3753] (maint) Cleanup of the virtual spec tests. Just some spacing issues and other minor things I'd like to straighten up before trying to fix a bunch of tests. --- spec/unit/virtual_spec.rb | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index 10ba760812..027c52eb6a 100755 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -5,7 +5,7 @@ require 'facter/util/macosx' describe "Virtual fact" do - before do + before(:each) do Facter::Util::Virtual.stubs(:zone?).returns(false) Facter::Util::Virtual.stubs(:openvz?).returns(false) Facter::Util::Virtual.stubs(:vserver?).returns(false) @@ -70,15 +70,14 @@ end describe "on Linux" do - - before do - Facter::Util::Resolution.stubs(:exec).with("vmware -v").returns false - Facter.fact(:operatingsystem).stubs(:value).returns(true) - # Ensure the tests don't fail on Xen - FileTest.stubs(:exists?).with("/proc/sys/xen").returns false - FileTest.stubs(:exists?).with("/sys/bus/xen").returns false - FileTest.stubs(:exists?).with("/proc/xen").returns false - Facter.fact(:architecture).stubs(:value).returns(true) + before(:each) do + Facter::Util::Resolution.stubs(:exec).with("vmware -v").returns false + Facter.fact(:operatingsystem).stubs(:value).returns(true) + # Ensure the tests don't fail on Xen + FileTest.stubs(:exists?).with("/proc/sys/xen").returns false + FileTest.stubs(:exists?).with("/sys/bus/xen").returns false + FileTest.stubs(:exists?).with("/proc/xen").returns false + Facter.fact(:architecture).stubs(:value).returns(true) end it "should be parallels with Parallels vendor id from lspci 2>/dev/null" do From 2ed2575ba89f50c2da92f562bb925e21ca11dc8b Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Thu, 18 Oct 2012 17:04:12 -0700 Subject: [PATCH 1052/3753] (maint) Properly indent virtual_spec.rb --- spec/unit/virtual_spec.rb | 41 +++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index 027c52eb6a..6483dc9cea 100755 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -31,16 +31,16 @@ end it "should be hpvm on HP-UX when in HP-VM" do - Facter.fact(:kernel).stubs(:value).returns("HP-UX") - Facter::Util::Virtual.stubs(:hpvm?).returns(true) - Facter.fact(:virtual).value.should == "hpvm" + Facter.fact(:kernel).stubs(:value).returns("HP-UX") + Facter::Util::Virtual.stubs(:hpvm?).returns(true) + Facter.fact(:virtual).value.should == "hpvm" end it "should be zlinux on s390x" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:architecture).stubs(:value).returns("s390x") - Facter::Util::Virtual.stubs(:zlinux?).returns(true) - Facter.fact(:virtual).value.should == "zlinux" + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:architecture).stubs(:value).returns("s390x") + Facter::Util::Virtual.stubs(:zlinux?).returns(true) + Facter.fact(:virtual).value.should == "zlinux" end describe "on Darwin" do @@ -169,8 +169,8 @@ Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("System Information\nManufacturer: Microsoft Corporation\nProduct Name: Virtual Machine") Facter.fact(:virtual).value.should == "hyperv" end - end + describe "on Solaris" do before(:each) do Facter::Util::Resolution.stubs(:exec).with("vmware -v").returns false @@ -236,29 +236,28 @@ end describe "is_virtual fact" do - it "should be virtual when running on xen" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:virtual).stubs(:value).returns("xenu") - Facter.fact(:is_virtual).value.should == "true" + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:virtual).stubs(:value).returns("xenu") + Facter.fact(:is_virtual).value.should == "true" end it "should be false when running on xen0" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:virtual).stubs(:value).returns("xen0") - Facter.fact(:is_virtual).value.should == "false" + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:virtual).stubs(:value).returns("xen0") + Facter.fact(:is_virtual).value.should == "false" end it "should be true when running on xenhvm" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:virtual).stubs(:value).returns("xenhvm") - Facter.fact(:is_virtual).value.should == "true" + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:virtual).stubs(:value).returns("xenhvm") + Facter.fact(:is_virtual).value.should == "true" end it "should be false when running on physical" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:virtual).stubs(:value).returns("physical") - Facter.fact(:is_virtual).value.should == "false" + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:virtual).stubs(:value).returns("physical") + Facter.fact(:is_virtual).value.should == "false" end it "should be true when running on vmware" do From cf43fc0092f0476d378ea05dd8e21fc170a51bdf Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Thu, 18 Oct 2012 12:04:34 -0700 Subject: [PATCH 1053/3753] (#8210) Add a heavy virtual fact using virt-what Without this patch facter does not return kvm when running inside of an enterprise linux guest VM hosted on an enterprise linux kvm host. This is a problem because facter improperly reports the virtual machine is actually running on physical hardware, which is not true. This causes downstream problems for anyone relying on the virtual fact being accurate. This patch fixes the problem by adding a new resolver for the virtual fact that has a very high weight. The new resolver calls `virt-what`, available on Enterprise Linux machines by installing the `virt-what` package. The last line of output is used to determine the virtual machine hypervisor type. If virt-what is not available or returns no output, then existing lower weight resolvers are used instead. --- lib/facter/util/virtual.rb | 15 +++++++++++++++ lib/facter/virtual.rb | 35 ++++++++++++++++++++++++++++++++++- spec/unit/virtual_spec.rb | 38 ++++++++++++++++++++++++++++++++++++-- 3 files changed, 85 insertions(+), 3 deletions(-) diff --git a/lib/facter/util/virtual.rb b/lib/facter/util/virtual.rb index e6b822127b..11e6f79f2e 100644 --- a/lib/facter/util/virtual.rb +++ b/lib/facter/util/virtual.rb @@ -1,4 +1,19 @@ module Facter::Util::Virtual + ## + # virt_what is a delegating helper method intended to make it easier to stub + # the system call without affecting other calls to + # Facter::Util::Resolution.exec + def self.virt_what(command = "virt-what") + Facter::Util::Resolution.exec command + end + + ## + # lspci is a delegating helper method intended to make it easier to stub the + # system call without affecting other calls to Facter::Util::Resolution.exec + def self.lspci(command = "lspci 2>/dev/null") + Facter::Util::Resolution.exec command + end + def self.openvz? FileTest.directory?("/proc/vz") and not self.openvz_cloudlinux? end diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index ea9e1bde34..ff85234de8 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -89,7 +89,7 @@ end if result == "physical" - output = Facter::Util::Resolution.exec('lspci 2>/dev/null') + output = Facter::Util::Virtual.lspci if not output.nil? output.each_line do |p| # --- look for the vmware video card to determine if it is virtual => vmware. @@ -153,6 +153,39 @@ end end +## +# virtual fact based on virt-what command. +# +# The output is mapped onto existing known values for the virtual fact in an +# effort to preserve consistency. This fact has a high weight becuase the +# virt-what tool is expected to be maintained upstream. +# +# If the virt-what command is not available, this fact will not resolve to a +# value and lower-weight virtual facts will be attempted. +# +# Only the last line of the virt-what command is returned +Facter.add("virtual") do + has_weight 500 + + setcode do + output = Facter::Util::Virtual.virt_what + case output + when 'linux_vserver' + Facter::Util::Virtual.vserver_type + when /xen-hvm/i + 'xenhvm' + when /xen-dom0/i + 'xen0' + when /xen-domU/i + 'xenu' + when /ibm_systemz/i + 'zlinux' + else + output.to_s.split("\n").last + end + end +end + # Fact: is_virtual # # Purpose: returning true or false for if a machine is virtualised or not. diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index 6483dc9cea..8e6863fd6a 100755 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -13,6 +13,7 @@ Facter::Util::Virtual.stubs(:kvm?).returns(false) Facter::Util::Virtual.stubs(:hpvm?).returns(false) Facter::Util::Virtual.stubs(:zlinux?).returns(false) + Facter::Util::Virtual.stubs(:virt_what).returns(nil) end it "should be zone on Solaris when a zone" do @@ -82,13 +83,13 @@ it "should be parallels with Parallels vendor id from lspci 2>/dev/null" do Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns("01:00.0 VGA compatible controller: Unknown device 1ab8:4005") + Facter::Util::Virtual.stubs(:lspci).returns("01:00.0 VGA compatible controller: Unknown device 1ab8:4005") Facter.fact(:virtual).value.should == "parallels" end it "should be parallels with Parallels vendor name from lspci 2>/dev/null" do Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns("01:00.0 VGA compatible controller: Parallels Display Adapter") + Facter::Util::Virtual.stubs(:lspci).returns("01:00.0 VGA compatible controller: Parallels Display Adapter") Facter.fact(:virtual).value.should == "parallels" end @@ -233,6 +234,39 @@ Facter.fact(:virtual).value.should == "xenhvm" end end + + describe "with the virt-what command available (#8210)" do + describe "when the output of virt-what disagrees with lower weight facts" do + virt_what_map = { + 'xen-hvm' => 'xenhvm', + 'xen-dom0' => 'xen0', + 'xen-domU' => 'xenu', + 'ibm_systemz' => 'zlinux', + } + + virt_what_map.each do |input,output| + it "maps #{input} to #{output}" do + Facter::Util::Virtual.expects(:virt_what).returns(input) + Facter.value(:virtual).should == output + end + end + end + + describe "arbitrary outputs of virt-what" do + it "returns the last line output from virt-what" do + Facter::Util::Virtual.expects(:virt_what).returns("one\ntwo\nthree space\n") + Facter.value(:virtual).should == "three space" + end + end + + describe "when virt-what returns linux_vserver" do + it "delegates to Facter::Util::Virtual.vserver_type" do + Facter::Util::Virtual.expects(:virt_what).returns("linux_vserver") + Facter::Util::Virtual.expects(:vserver_type).returns("fake_vserver_type") + Facter.value(:virtual).should == "fake_vserver_type" + end + end + end end describe "is_virtual fact" do From c462b12c4c082f06960a8657fd29099a25af02a3 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Fri, 19 Oct 2012 11:35:26 -0700 Subject: [PATCH 1054/3753] (maint) Remove bad test of manufacturer fact on Solaris Without this patch, the following test is failing when run in a Solaris 10 virtual machine: 1) Facter::Manufacturer should not set manufacturer or productname if prtdiag output is nil Failure/Error: Facter.value(:manufacturer).should be_nil expected: nil got: "VMware, Inc." # ./spec/unit/util/manufacturer_spec.rb:40 This problem is self-evident and is caused by the fact that the test is simply wrong. Other implementations of the manufacturer spec may provide a valid result, which is exactly what is happening in this situation. In the situation where `prtdiag` is nil, the manufacturer of the machine appears to be VMware, which is expected behavior. --- spec/unit/util/manufacturer_spec.rb | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/spec/unit/util/manufacturer_spec.rb b/spec/unit/util/manufacturer_spec.rb index 59fbf5ca24..834b305a03 100755 --- a/spec/unit/util/manufacturer_spec.rb +++ b/spec/unit/util/manufacturer_spec.rb @@ -31,16 +31,6 @@ Facter.value(:productname).should == "SPARC Enterprise T5220" end - it "should not set manufacturer or productname if prtdiag output is nil" do - # Stub kernel so we don't have windows fall through to its own mechanism - Facter.fact(:kernel).stubs(:value).returns("SunOS") - - Facter::Util::Resolution.stubs(:exec).returns(nil) - Facter::Manufacturer.prtdiag_sparc_find_system_info() - Facter.value(:manufacturer).should be_nil - Facter.value(:productname).should be_nil - end - it "should strip white space on dmi output with spaces" do dmidecode_output = my_fixture_read("linux_dmidecode_with_spaces") Facter::Manufacturer.expects(:get_dmi_table).returns(dmidecode_output) From c61894994a5884fc8ac44f9e780d4b062097d124 Mon Sep 17 00:00:00 2001 From: Joshua Hoblitt Date: Tue, 16 Oct 2012 16:39:25 -0700 Subject: [PATCH 1055/3753] (#17029) add osfamily Gentoo With a single member of operatingsystem type Gentoo. Tests for popular Gentoo derivatives like Sabayon are not current in the facter tree so this patch is not adding them to the the initial Gentoo osfamily definition. --- lib/facter/osfamily.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/facter/osfamily.rb b/lib/facter/osfamily.rb index aa3f50a217..228f9a1086 100644 --- a/lib/facter/osfamily.rb +++ b/lib/facter/osfamily.rb @@ -24,6 +24,8 @@ "Suse" when "Solaris", "Nexenta", "OmniOS", "OpenIndiana", "SmartOS" "Solaris" + when "Gentoo" + "Gentoo" else Facter.value("kernel") end From 80ba654290bc21e9370b45311da113f5d5b7303d Mon Sep 17 00:00:00 2001 From: Stefan Schulte Date: Sat, 20 Oct 2012 22:05:33 +0200 Subject: [PATCH 1056/3753] maint: Remove trailing whitespace in osfamiliy fact --- lib/facter/osfamily.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/osfamily.rb b/lib/facter/osfamily.rb index 228f9a1086..60abd2bb51 100644 --- a/lib/facter/osfamily.rb +++ b/lib/facter/osfamily.rb @@ -25,7 +25,7 @@ when "Solaris", "Nexenta", "OmniOS", "OpenIndiana", "SmartOS" "Solaris" when "Gentoo" - "Gentoo" + "Gentoo" else Facter.value("kernel") end From 06d99c0b8e6d5ba181dca7d69c26ca58abe55acd Mon Sep 17 00:00:00 2001 From: Stefan Schulte Date: Sat, 20 Oct 2012 22:06:21 +0200 Subject: [PATCH 1057/3753] maint: Improve test coverage for osfamily fact --- spec/unit/osfamily_spec.rb | 61 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 spec/unit/osfamily_spec.rb diff --git a/spec/unit/osfamily_spec.rb b/spec/unit/osfamily_spec.rb new file mode 100644 index 0000000000..a074ecc515 --- /dev/null +++ b/spec/unit/osfamily_spec.rb @@ -0,0 +1,61 @@ +#! /usr/bin/env ruby + +require 'spec_helper' + +describe "OS Family fact" do + + { + 'SmartOS' => 'Solaris', + 'OpenIndiana' => 'Solaris', + 'OmniOS' => 'Solaris', + 'Nexenta' => 'Solaris', + 'Solaris' => 'Solaris', + 'Ubuntu' => 'Debian', + 'Debian' => 'Debian', + 'Gentoo' => 'Gentoo', + 'Fedora' => 'RedHat', + 'Amazon' => 'RedHat', + 'OracleLinux' => 'RedHat', + 'OVS' => 'RedHat', + 'OEL' => 'RedHat', + 'CentOS' => 'RedHat', + 'SLC' => 'RedHat', + 'Scientific' => 'RedHat', + 'CloudLinux' => 'RedHat', + 'PSBM' => 'RedHat', + 'Ascendos' => 'RedHat', + 'XenServer' => 'RedHat', + 'RedHat' => 'RedHat', + 'SLES' => 'Suse', + 'SLED' => 'Suse', + 'OpenSuSE' => 'Suse', + 'SuSE' => 'Suse' + }.each do |os,family| + it "should return #{family} on operatingsystem #{os}" do + Facter.fact(:operatingsystem).stubs(:value).returns os + Facter.fact(:osfamily).value.should == family + end + end + + [ + 'Mandriva', + 'Mandrake', + 'MeeGo', + 'Archlinux', + 'VMWareESX', + 'Bluewhite64', + 'Slamd64', + 'Slackware', + 'Alpine', + 'Mageia', + 'ESXi', + 'windows', + 'HP-UX' + ].each do |os| + it "should return the kernel fact on operatingsystem #{os}" do + Facter.fact(:operatingsystem).stubs(:value).returns os + Facter.fact(:kernel).stubs(:value).returns 'random_kernel_fact' + Facter.fact(:osfamily).value.should == 'random_kernel_fact' + end + end +end From 62478ff874f436c44f63be3c2b9b2c69e251b309 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Mon, 22 Oct 2012 16:05:19 -0700 Subject: [PATCH 1058/3753] (#11609) Fix processorX facts for AIX systems Without this patch Facter running on AIX systems will output misleading information in the indexed processorX facts. This misleading information looks like: $ facter processor0 processor0 => 0PowerPC_POWER5 processor1 => 6PowerPC_POWER5 processor2 => 2PowerPC_POWER5 processor3 => 4PowerPC_POWER5 There are a number of problems here. First, the mapping of values to their index is not stable, resulting in non-deterministic behavior. Second, the AIX specific identifiers are leaking into the fact values. I learned my lesson when I implemented the Mac OS X System Profiler sp_* facts. The lesson learned is that we should not implement facts specific to a single platform, but instead re-use the existing platform-agnostic and consistent facts we have. To this end, this patch solves the problem by mapping the platform specific processor identifiers to our incrementing index identifiers. The patch makes sure to sort the list so the results are idempotent. The new output is: $ facter processor0 processor0 => PowerPC_POWER5 processor1 => PowerPC_POWER5 processor2 => PowerPC_POWER5 processor3 => PowerPC_POWER5 --- lib/facter/processor.rb | 6 +- lib/facter/util/processor.rb | 97 ++++++++++++++++++++++++-------- spec/unit/processor_spec.rb | 44 ++++++++++++--- spec/unit/util/processor_spec.rb | 8 --- 4 files changed, 113 insertions(+), 42 deletions(-) diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb index 3498ce3771..3b6fb67b55 100644 --- a/lib/facter/processor.rb +++ b/lib/facter/processor.rb @@ -24,9 +24,9 @@ ## We have to enumerate these outside a Facter.add block to get the processorN descriptions iteratively ## (but we need them inside the Facter.add block above for tests on processorcount to work) -processor_list = case Facter.value(:kernel) +processor_list = case Facter::Util::Processor.kernel_fact_value when "AIX" - Facter::Util::Processor.enum_lsdev + Facter::Util::Processor.aix_processor_list when "SunOS" Facter::Util::Processor.enum_kstat else @@ -71,7 +71,7 @@ Facter.add("ProcessorCount") do confine :kernel => :aix setcode do - processor_list = Facter::Util::Processor.enum_lsdev + processor_list = Facter::Util::Processor.aix_processor_list processor_list.length.to_s end diff --git a/lib/facter/util/processor.rb b/lib/facter/util/processor.rb index 3abdb7b731..5aa1b185db 100644 --- a/lib/facter/util/processor.rb +++ b/lib/facter/util/processor.rb @@ -1,4 +1,75 @@ -module Facter::Util::Processor +module Facter +module Util +module Processor + ## + # aix_processor_list is intended to generate a list of values for the + # processorX facts. The behavior is as follows from + # [#11609](http://projects.puppetlabs.com/issues/11609) + # + # 1. Take a list of all the processor identifiers for the platform, + # represented as system-native identifiers in strings. + # 2. Sort the list + # 3. Assign an incrementing from 0 integer index to each identifier. + # 4. Store the value of the system identifier in the processorX fact where X + # is the incrementing index. + # + # Returns an Array, sorted, containing the values for the facts. + def self.aix_processor_list + return_value = [] + aix_proc_id_list = [] + + if output = lsdev then + output.split("\n").each do |line| + if match = line.match(/proc\d+/) + aix_proc_id_list << match[0] + end + end + end + + # Generalized alphanumeric sort to put "proc12" behind "proc4" + padding = 8 + aix_proc_id_list = aix_proc_id_list.sort do |a,b| + a,b = [a,b].map do |s| + s.gsub(/\d+/) { |m| "0"*(padding - m.size) + m } + end + a<=>b + end + + aix_proc_id_list.each do |proc_id| + if output = lsattr("lsattr -El #{proc_id} -a type") + if match = output.match(/type\s+([^\s]+)\s+Processor/i) + return_value << match[1] + end + end + end + + return_value + end + + ## + # lsdev is intended to directly delegate to Facter::Util::Resolution.exec in an + # effort to make the processorX facts easier to test by stubbing only the + # behaviors we need to stub to get the output of the system command. + def self.lsdev(command="lsdev -Cc processor") + Facter::Util::Resolution.exec(command) + end + + ## + # lsattr is intended to directly delegate to Facter::Util::Resolution.exec in + # an effort to make the processorX facts easier to test. See also the + # {lsdev} method. + def self.lsattr(command="lsattr -El proc0 -a type") + Facter::Util::Resolution.exec(command) + end + + ## + # kernel_fact_value is intended to directly delegate to Facter.value(:kernel) + # to make it easier to stub the kernel fact without affecting the entire + # system. + def self.kernel_fact_value + Facter.value(:kernel) + end + def self.enum_cpuinfo processor_num = -1 processor_list = [] @@ -64,28 +135,6 @@ def self.enum_cpuinfo processor_list end - def self.enum_lsdev - processor_num = -1 - processor_list = {} - Thread::exclusive do - procs = Facter::Util::Resolution.exec('lsdev -Cc processor') - if procs - procs.each_line do |proc| - if proc =~ /^proc(\d+)/ - processor_num = $1.to_i - # Not retrieving the frequency since AIX 4.3.3 doesn't support the - # attribute and some people still use the OS. - proctype = Facter::Util::Resolution.exec('lsattr -El proc0 -a type') - if proctype =~ /^type\s+(\S+)\s+/ - processor_list[processor_num] = $1 - end - end - end - end - end - processor_list - end - def self.enum_kstat processor_num = -1 processor_list = [] @@ -105,3 +154,5 @@ def self.enum_kstat processor_list end end +end +end diff --git a/spec/unit/processor_spec.rb b/spec/unit/processor_spec.rb index 877e596506..8e884b7aae 100755 --- a/spec/unit/processor_spec.rb +++ b/spec/unit/processor_spec.rb @@ -1,5 +1,6 @@ #! /usr/bin/env ruby +require 'facter/util/processor' require 'spec_helper' def cpuinfo_fixture(filename) @@ -194,14 +195,6 @@ def load(procs) Facter.fact(:processorcount).value.should == "2" end - it "should be 6 on six-processor AIX box" do - Facter.fact(:kernel).stubs(:value).returns("AIX") - Facter::Util::Resolution.stubs(:exec).with("lsdev -Cc processor").returns("proc0 Available 00-00 Processor\nproc2 Available 00-02 Processor\nproc4 Available 00-04 Processor\nproc6 Available 00-06 Processor\nproc8 Available 00-08 Processor\nproc10 Available 00-10 Processor") - Facter::Util::Resolution.stubs(:exec).with("lsattr -El proc0 -a type").returns("type PowerPC_POWER3 Processor type False") - - Facter.fact(:processorcount).value.should == "6" - end - it "should be 2 via sysfs when cpu0 and cpu1 are present" do Facter.fact(:kernel).stubs(:value).returns("Linux") File.stubs(:exists?).with('/sys/devices/system/cpu').returns(true) @@ -282,3 +275,38 @@ def load(procs) end end end + +describe "processorX facts" do + describe "on AIX" do + def self.lsdev_examples + examples = [] + examples << "proc0 Available 00-00 Processor\n" + + "proc4 Available 00-04 Processor\n" + + "proc8 Defined 00-08 Processor\n" + + "proc12 Defined 00-12 Processor\n" + examples + end + + let(:lsattr) do + "type PowerPC_POWER5 Processor type False\n" + end + + lsdev_examples.each_with_index do |lsdev_example, i| + context "lsdev example ##{i}" do + before :each do + Facter.fact(:kernel).stubs(:value).returns("AIX") + Facter::Util::Processor.stubs(:lsdev).returns(lsdev_example) + Facter::Util::Processor.stubs(:lsattr).returns(lsattr) + Facter.collection.loader.load(:processor) + end + + lsdev_example.split("\n").each_with_index do |line, idx| + aix_idx = idx * 4 + it "maps proc#{aix_idx} to processor#{idx} (#11609)" do + Facter.value("processor#{idx}").should == "PowerPC_POWER5" + end + end + end + end + end +end diff --git a/spec/unit/util/processor_spec.rb b/spec/unit/util/processor_spec.rb index 2a3677f732..fe8a924994 100755 --- a/spec/unit/util/processor_spec.rb +++ b/spec/unit/util/processor_spec.rb @@ -50,14 +50,6 @@ def cpuinfo_fixture(filename) Facter::Util::Processor.enum_cpuinfo[3].should == "Quad-Core AMD Opteron(tm) Processor 2374 HE" end - it "should get the processor type on AIX box" do - Facter.fact(:kernel).stubs(:value).returns("AIX") - Facter::Util::Resolution.stubs(:exec).with("lsdev -Cc processor").returns("proc0 Available 00-00 Processor\nproc2 Available 00-02 Processor\nproc4 Available 00-04 Processor\nproc6 Available 00-06 Processor\nproc8 Available 00-08 Processor\nproc10 Available 00-10 Processor") - Facter::Util::Resolution.stubs(:exec).with("lsattr -El proc0 -a type").returns("type PowerPC_POWER3 Processor type False") - - Facter::Util::Processor.enum_lsdev[0].should == "PowerPC_POWER3" - end - it "should get the processor description on Solaris (x86)" do Facter.fact(:kernel).stubs(:value).returns("SunOS") Facter.fact(:architecture).stubs(:value).returns("i86pc") From 75bd00250d40c15b221512f7237ec9d357a6072f Mon Sep 17 00:00:00 2001 From: Matthaus Litteken Date: Thu, 13 Sep 2012 17:35:48 -0700 Subject: [PATCH 1059/3753] (#16397) Update selinux_mount_point for 1.8.5 compatibility The lines method does not exist for the String class in ruby 1.8.5, which causes the selinux_mount_point function to print Could not retrieve selinux: undefined method lines' for #String:0x2abc1ffefe88to STDERR. This commit monkey patches facter with `lines` so that it can safely use the `lines` method on 1.8.5. It monkey_patches both String and IO. It also includes spec tests for the String and IO classes to verify they respond to lines in the expected manner. --- lib/facter/util/monkey_patches.rb | 32 ++++++++++++++++++++ spec/unit/util/monkey_patches_spec.rb | 42 +++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 spec/unit/util/monkey_patches_spec.rb diff --git a/lib/facter/util/monkey_patches.rb b/lib/facter/util/monkey_patches.rb index 8d5d93f195..0946137e92 100644 --- a/lib/facter/util/monkey_patches.rb +++ b/lib/facter/util/monkey_patches.rb @@ -2,6 +2,38 @@ # version 1.8.5. This allows us to use RbConfig in place of the older Config in # our code and still be compatible with at least Ruby 1.8.1. require 'rbconfig' +require 'enumerator' + unless defined? ::RbConfig ::RbConfig = ::Config end + +module Facter + module Util + module MonkeyPatches + module Lines + def lines(separator = $/) + if block_given? + self.each_line(separator) {|line| yield line } + return self + else + return enum_for(:each_line, separator) + end + end + end + end + end +end + +public +class String + unless method_defined? :lines + include Facter::Util::MonkeyPatches::Lines + end +end + +class IO + unless method_defined? :lines + include Facter::Util::MonkeyPatches::Lines + end +end diff --git a/spec/unit/util/monkey_patches_spec.rb b/spec/unit/util/monkey_patches_spec.rb new file mode 100644 index 0000000000..13d3fa6a4e --- /dev/null +++ b/spec/unit/util/monkey_patches_spec.rb @@ -0,0 +1,42 @@ +require 'spec_helper' +require 'tempfile' + +describe 'Monkey Patches' do + let(:subject) { "a b c d e f\ng h i j" } + + context 'String' do + it "should respond to lines" do + subject.lines.to_a.should == ["a b c d e f\n", "g h i j"] + end + it "should accept a block" do + our_lines = [] + subject.lines do |line| our_lines << line end + our_lines.should == ["a b c d e f\n", "g h i j"] + end + end + + context 'IO' do + it "should respond to lines" do + our_lines = nil + Tempfile.open("lines") do | file | + file.write(subject) + file.flush + file.rewind + our_lines = file.lines.to_a + end + our_lines.should == ["a b c d e f\n", "g h i j"] + end + it "should accept a block" do + our_lines = [] + file = Tempfile.new("lines") + file.write(subject) + file.flush + file.rewind + file.lines.each do |line| our_lines << line end + file.unlink + our_lines.should == ["a b c d e f\n", "g h i j"] + end + end + +end + From b81528d4b89ac5a2042b01e4d3010c0170fa91d8 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Thu, 25 Oct 2012 12:56:06 -0700 Subject: [PATCH 1060/3753] Maint: fix mismatched stub Previously, the infiniband spec test would fail on systems where the file `/sys/class/net/ib0/address` did not exist, but `/sbin/ip` did, as it would attempt to execute `/sbin/ip link show ib0`. This change was introduced in commit 1d82907b. The real issue was that the code was using `File::exist?`, but the spec test was stubbing `File.exists?`. And this wasn't noticed in CI, because jenkins was executing `rake spec`, which doesn't do anything and just returns 0. This commit changes the code to use `File.exists?` as that is more standard usage, and updates the spec test to add expectations on the correct methods. --- lib/facter/util/ip.rb | 4 ++-- spec/unit/util/ip_spec.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index 8d9e2e7538..3a52bbbef1 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -87,9 +87,9 @@ def self.get_all_interface_output end def self.get_infiniband_macaddress(interface) - if File::exist?("/sys/class/net/#{interface}/address") then + if File.exists?("/sys/class/net/#{interface}/address") then ib_mac_address = `cat /sys/class/net/#{interface}/address`.chomp - elsif File::exist?("/sbin/ip") then + elsif File.exists?("/sbin/ip") then ip_output = %x{/sbin/ip link show #{interface}} ib_mac_address = ip_output.scan(%r{infiniband\s+((\w{1,2}:){5,}\w{1,2})}) else diff --git a/spec/unit/util/ip_spec.rb b/spec/unit/util/ip_spec.rb index 8bb8c96e55..d67dfb8b11 100755 --- a/spec/unit/util/ip_spec.rb +++ b/spec/unit/util/ip_spec.rb @@ -228,8 +228,8 @@ it "should return fake macaddress information for infiniband on Linux when neither sysfs or /sbin/ip are available" do ifconfig_interface = my_fixture_read("linux_ifconfig_ib0") - File.stubs(:exists?).with("/sys/class/net/ib0/address").returns(false) - File.stubs(:exists?).with("/sbin/ip").returns(false) + File.expects(:exists?).with("/sys/class/net/ib0/address").returns(false) + File.expects(:exists?).with("/sbin/ip").returns(false) Facter::Util::IP.expects(:ifconfig_interface).with("ib0").returns(ifconfig_interface) Facter.stubs(:value).with(:kernel).returns("Linux") From 213672fa7bcaa423b6c9c83a27362b85d1d7c328 Mon Sep 17 00:00:00 2001 From: Andrew Parker Date: Thu, 25 Oct 2012 13:44:16 -0700 Subject: [PATCH 1061/3753] (Maint) Remove aliases for spec from Rakefile Previously `rake test`, `rake specs`, `rake tests` were all equivalent. However, there was also `rake spec` which existed, but was not listed by `rake -T`, but also did not fail. The `rake spec` task is the most common one for running spec tests. As things were setup, the `rake spec` test would run, exit with 0, but not actually execute any tests. This commit removes the various aliases and leavs us with just `rake spec`. --- Rakefile | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Rakefile b/Rakefile index b84ec9e3e0..db0941dae7 100644 --- a/Rakefile +++ b/Rakefile @@ -55,12 +55,8 @@ task :default do sh %{rake -T} end -# Aliases for spec -task :tests => [:test] -task :specs => [:test] - desc "Run all specs" -RSpec::Core::RakeTask.new(:test) do |t| +RSpec::Core::RakeTask.new do |t| t.pattern ='spec/{unit,integration}/**/*_spec.rb' t.fail_on_error = true end From c2de0b9e53cdfb6874620a800450ae4a9f49ef12 Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Thu, 25 Oct 2012 14:10:35 -0700 Subject: [PATCH 1062/3753] Update lib/facter/version.rb for 1.6.14-rc1 --- lib/facter/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/version.rb b/lib/facter/version.rb index 06a65c7469..79be825151 100644 --- a/lib/facter/version.rb +++ b/lib/facter/version.rb @@ -1,6 +1,6 @@ module Facter if not defined? FACTERVERSION then - FACTERVERSION = '1.6.13' + FACTERVERSION = '1.6.14-rc1' end def self.version From b973caf508ba73edc88a5bcf229835a07ab7b530 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Mon, 29 Oct 2012 12:01:47 -0700 Subject: [PATCH 1063/3753] (maint) Add .yardopts file This patch makes it easy to generate documentation, including private methods. The assumption is that code will be marked up in markdown style yard comments. YARD tags are documented at: [YARD Tags Overview](http://rubydoc.info/docs/yard/file/docs/Tags.md) The YARD documentation is currently intended for Puppet developers and community contributors. While useful for end users, YARD documentation is not intended for new users or end users who are not developing on Puppet. --- .gitignore | 3 +++ .yardopts | 6 ++++++ 2 files changed, 9 insertions(+) create mode 100644 .yardopts diff --git a/.gitignore b/.gitignore index 54930fca9c..fb7f80a195 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ pkg/ ext/packaging/ +# YARD generated documentation +.yardoc +doc diff --git a/.yardopts b/.yardopts new file mode 100644 index 0000000000..0036282bd9 --- /dev/null +++ b/.yardopts @@ -0,0 +1,6 @@ +--protected +--private +--verbose +--markup markdown +--readme README_DEVELOPER.md +lib/**/*.rb From 0d6636261336a36d4fdaaf950218ccaa95d61487 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Mon, 29 Oct 2012 16:59:36 -0700 Subject: [PATCH 1064/3753] (#16511) Do not call arp -an on Solaris nodes Without this patch applied the EC2 facts cause Facter to execute the `arp -an` system command on Solaris systems. This is an invalid command on Solaris 8, but not 9 or 10. This is a problem because the following output is displayed to the user every run: arp: -an: unknown host The other problem is that facts which depend on this behavior working are themselves broken and do not resolve on Solaris. This patch _papers over the problem_, but does not fix the core issues present in the EC2 facts. The core problem is that way too much EC2 specific work is being done in the main scope, causing this behavior and code paths to be exercised on systems like Solaris 8 that will never run in EC2. The example `arp -a` command example is copied from http://docstore.mik.ua/orelly/networking_2ndEd/tcp/ch13_04.htm --- lib/facter/util/ec2.rb | 29 +++++++++++-------- .../unit/util/ec2/solaris8_arp_a_not_ec2.out | 7 +++++ spec/unit/util/ec2_spec.rb | 15 ++++++++++ 3 files changed, 39 insertions(+), 12 deletions(-) create mode 100644 spec/fixtures/unit/util/ec2/solaris8_arp_a_not_ec2.out diff --git a/lib/facter/util/ec2.rb b/lib/facter/util/ec2.rb index 197a5c9dd9..618ac88d73 100644 --- a/lib/facter/util/ec2.rb +++ b/lib/facter/util/ec2.rb @@ -36,19 +36,24 @@ def has_openstack_mac? # Test if the host has an arp entry in its cache that matches the EC2 arp, # which is normally +fe:ff:ff:ff:ff:ff+. def has_ec2_arp? - mac_address = "fe:ff:ff:ff:ff:ff" - if Facter.value(:kernel) == 'windows' - arp_command = "arp -a" - mac_address.gsub!(":","-") - else - arp_command = "arp -an" - end + kernel = Facter.value(:kernel) + + mac_address_re = case kernel + when /Windows/i + /fe-ff-ff-ff-ff-ff/i + else + /fe:ff:ff:ff:ff:ff/i + end + + arp_command = case kernel + when /Windows/i, /SunOS/i + "arp -a" + else + "arp -an" + end - arp_table = Facter::Util::Resolution.exec(arp_command) - if not arp_table.nil? - arp_table.each_line do |line| - return true if line.downcase.include?(mac_address) - end + if arp_table = Facter::Util::Resolution.exec(arp_command) + return true if arp_table.match(mac_address_re) end return false end diff --git a/spec/fixtures/unit/util/ec2/solaris8_arp_a_not_ec2.out b/spec/fixtures/unit/util/ec2/solaris8_arp_a_not_ec2.out new file mode 100644 index 0000000000..b1ec8f7e81 --- /dev/null +++ b/spec/fixtures/unit/util/ec2/solaris8_arp_a_not_ec2.out @@ -0,0 +1,7 @@ +Net to Media Table: IPv4 +Device IP Address Mask Flags Phys Addr +------ -------------------- --------------- ----- --------------- +dnet0 pecan 255.255.255.255 08:00:20:05:21:33 +dnet0 horseshoe 255.255.255.255 00:00:0c:e0:80:b1 +dnet0 crab 255.255.255.255 SP 08:00:20:22:fd:51 +dnet0 BASE-ADDRESS.MCAST.NET 240.0.0.0 SM 01:00:5e:00:00:00 diff --git a/spec/unit/util/ec2_spec.rb b/spec/unit/util/ec2_spec.rb index b5cbf18fec..8851c984cf 100755 --- a/spec/unit/util/ec2_spec.rb +++ b/spec/unit/util/ec2_spec.rb @@ -57,6 +57,21 @@ Facter::Util::EC2.has_ec2_arp?.should == false end end + + describe "on solaris" do + before :each do + Facter.stubs(:value).with(:kernel).returns("SunOS") + end + + it "should fail if arp table does not contain fe:ff:ff:ff:ff:ff" do + ec2arp = my_fixture_read("solaris8_arp_a_not_ec2.out") + + Facter::Util::Resolution.expects(:exec).with("arp -a"). + at_least_once.returns(ec2arp) + + Facter::Util::EC2.has_ec2_arp?.should == false + end + end end describe "is_euca_mac? method" do From 0fcb67b985666ffa734ca22cc145843ce462f9ae Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Tue, 30 Oct 2012 16:05:59 -0700 Subject: [PATCH 1065/3753] (maint) Avoid "Use RbConfig instead of obsolete and deprecated Config" message Without this patch the spec tests for Facter issue the following warning: Use RbConfig instead of obsolete and deprecated Config. This patch fixes the problem by branching on the presence or absence of the RbConfig constant. --- lib/facter/util/config.rb | 4 ++-- spec/unit/util/config_spec.rb | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/facter/util/config.rb b/lib/facter/util/config.rb index 793788d881..0db77bad8d 100644 --- a/lib/facter/util/config.rb +++ b/lib/facter/util/config.rb @@ -13,12 +13,12 @@ def self.ext_fact_loader=(loader) end def self.is_mac? - Config::CONFIG['host_os'] =~ /darwin/i + (defined?(RbConfig) ? RbConfig : Config)::CONFIG['host_os'] =~ /darwin/i end # Returns true if OS is windows def self.is_windows? - RbConfig::CONFIG['host_os'] =~ /mswin|win32|dos|mingw|cygwin/i + (defined?(RbConfig) ? RbConfig : Config)::CONFIG['host_os'] =~ /mswin|win32|dos|mingw|cygwin/i end def self.windows_data_dir diff --git a/spec/unit/util/config_spec.rb b/spec/unit/util/config_spec.rb index 23bcd99d8a..f5ad3f264f 100644 --- a/spec/unit/util/config_spec.rb +++ b/spec/unit/util/config_spec.rb @@ -9,7 +9,7 @@ it "should detect windows if Ruby Config::CONFIG['host_os'] returns a windows OS" do host_os = ["mswin","win32","dos","mingw","cygwin"] host_os.each do |h| - Config::CONFIG.stubs(:[]).with('host_os').returns(h) + (defined?(RbConfig) ? RbConfig : Config)::CONFIG.stubs(:[]).with('host_os').returns(h) Facter::Util::Config.is_windows?.should be_true end end @@ -17,7 +17,7 @@ it "should not detect windows if Ruby Config::CONFIG['host_os'] returns a non-windows OS" do host_os = ["darwin","linux"] host_os.each do |h| - Config::CONFIG.stubs(:[]).with('host_os').returns(h) + (defined?(RbConfig) ? RbConfig : Config)::CONFIG.stubs(:[]).with('host_os').returns(h) Facter::Util::Config.is_windows?.should be_false end end @@ -27,7 +27,7 @@ it "should detect mac if Ruby Config::CONFIG['host_os'] returns darwin" do host_os = ["darwin"] host_os.each do |h| - Config::CONFIG.stubs(:[]).with('host_os').returns(h) + (defined?(RbConfig) ? RbConfig : Config)::CONFIG.stubs(:[]).with('host_os').returns(h) Facter::Util::Config.is_mac?.should be_true end end From 450ab74fead4f99b15b3183c3540b93072a7864a Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Thu, 1 Nov 2012 09:59:08 -0700 Subject: [PATCH 1066/3753] Update lib/facter/version.rb for 1.6.14 --- lib/facter/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/version.rb b/lib/facter/version.rb index 79be825151..57bb02f218 100644 --- a/lib/facter/version.rb +++ b/lib/facter/version.rb @@ -1,6 +1,6 @@ module Facter if not defined? FACTERVERSION then - FACTERVERSION = '1.6.14-rc1' + FACTERVERSION = '1.6.14' end def self.version From df5b95c5001b046bf9617b6252958c07123dd253 Mon Sep 17 00:00:00 2001 From: Andrew Parker Date: Fri, 2 Nov 2012 14:00:58 -0700 Subject: [PATCH 1067/3753] Revert "Merge pull request #205 from jeffweiss/ticket/master/12147_remove_iphostnumber_fact" This reverts commit b12cad57790d10034ba1ac1291dd3ca6cb3b8dda, reversing changes made to 15385641bd2ed764c347834b7f1248c75630b1fa. This commit is being reverted on the 1.7.x branch in order to get rid of backwards incompatible changes that had been made in anticipation of a Facter 2 release. --- lib/facter/iphostnumber.rb | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 lib/facter/iphostnumber.rb diff --git a/lib/facter/iphostnumber.rb b/lib/facter/iphostnumber.rb new file mode 100644 index 0000000000..2d22017c28 --- /dev/null +++ b/lib/facter/iphostnumber.rb @@ -0,0 +1,29 @@ +# Fact: iphostnumber +# +# Purpose: On selected versions of Darwin, returns the host's IP address. +# +# Resolution: +# Uses either the scutil program to get the localhost name, or parses output +# of ifconfig for a MAC address. +# +# Caveats: +# + +Facter.add(:iphostnumber) do + confine :kernel => :darwin, :kernelrelease => "R6" + setcode do + %x{/usr/sbin/scutil --get LocalHostName} + end +end +Facter.add(:iphostnumber) do + confine :kernel => :darwin, :kernelrelease => "R6" + setcode do + ether = nil + output = %x{/sbin/ifconfig} + + output =~ /HWaddr (\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ + ether = $1 + + ether + end +end From 15f36bfe3b6d466531decdbb37f1699ed3aa28b3 Mon Sep 17 00:00:00 2001 From: Andrew Parker Date: Fri, 2 Nov 2012 15:14:50 -0700 Subject: [PATCH 1068/3753] Revert "Merge pull request #204 from jeffweiss/ticket/master/11466_remove_duplicate_memorytotal_fact" This reverts commit 15385641bd2ed764c347834b7f1248c75630b1fa, reversing changes made to c6aa778e6a62dd5a4f9d3bd929a41b460ae3d1e9. Conflicts: lib/facter/memory.rb spec/unit/memory_spec.rb This is being put back in for the 1.7 series of facter. --- lib/facter/memory.rb | 11 +++++++++++ spec/unit/memory_spec.rb | 7 +++++++ 2 files changed, 18 insertions(+) diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb index 39935ab274..583c937311 100644 --- a/lib/facter/memory.rb +++ b/lib/facter/memory.rb @@ -160,3 +160,14 @@ "%.2f" % [(swapfree.to_f / 1024.0) / 1024.0] end end + +# http://projects.puppetlabs.com/issues/11436 +# +# Unifying naming for the amount of physical memory in a given host. +# This fact is DEPRECATED and will be removed in Facter 2.0 per +# http://projects.puppetlabs.com/issues/11466 +Facter.add("MemoryTotal") do + setcode do + Facter.value("memorysize") + end +end diff --git a/spec/unit/memory_spec.rb b/spec/unit/memory_spec.rb index 459efb9557..6ccaae62e6 100755 --- a/spec/unit/memory_spec.rb +++ b/spec/unit/memory_spec.rb @@ -510,6 +510,13 @@ Facter::Util::WMI.stubs(:execquery).returns([computer]) Facter.fact(:memorysize_mb).value.should == '3999.55' + Facter.fact(:MemoryTotal).value.should == '3.91 GB' end end + + it "should use the memorysize fact for the memorytotal fact" do + Facter.fact("memorysize").expects(:value).once.returns "yay" + Facter::Util::Resolution.expects(:exec).never + Facter.fact("memorytotal").value.should == "yay" + end end From 290842f4b51b33c6d341fb4715fd54e77f0f7d87 Mon Sep 17 00:00:00 2001 From: Andrew Parker Date: Fri, 2 Nov 2012 15:21:41 -0700 Subject: [PATCH 1069/3753] Revert "Merge pull request #208 from hkenney/ticket/master/14469_strip_whitespace_from_frozen_strings" This reverts commit d5a3ff7f5561b1dafbcd234b4b5c6f9a58ba6f1b, reversing changes made to 634f2f6a57b461926afcb2e07dcfe4ed659f5b0c. Conflicts: spec/unit/util/resolution_spec.rb This is being reverted from the 1.7.x branch in order to have a release of non-breaking changes. --- lib/facter/util/resolution.rb | 2 +- spec/unit/util/resolution_spec.rb | 11 ++--------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 115815e718..dd21628983 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -297,7 +297,7 @@ def value Facter.show_time "#{self.name}: #{"%.2f" % ms}ms" unless @preserve_whitespace - result = result.strip if result && result.respond_to?(:strip) + result.strip! if result && result.respond_to?(:strip!) end return nil if result == "" diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index ffe6b6b850..29ac64e4bd 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -172,15 +172,8 @@ def handy_method() describe "when dealing with whitespace" do it "should by default strip whitespace" do @resolve.setcode {' value '} - @resolve.value.should == 'value' - end - - it "should strip whitespace from frozen strings" do - result = ' val ue ' - result.freeze - @resolve.setcode{result} - @resolve.value.should == 'val ue' - end + @resolve.value.should == 'value' + end describe "when given a string" do From e8ed8131bd1cae059904d0a5a4e721aa79304965 Mon Sep 17 00:00:00 2001 From: Andrew Parker Date: Fri, 2 Nov 2012 15:23:32 -0700 Subject: [PATCH 1070/3753] Revert "Merge pull request #202 from hkenney/ticket/master/3226_strip_whitespace_from_fact" This reverts commit bc109e7ff5bbc5f8107c555345db64e17317eebb, reversing changes made to 94d0d13ddfd2773573a007136bd8c52d4b8bafc0. Conflicts: spec/unit/util/resolution_spec.rb This is being reverted from the 1.7.x branch in order to have a release of non-breaking changes. --- lib/facter/util/resolution.rb | 8 --- spec/unit/util/resolution_spec.rb | 99 ------------------------------- 2 files changed, 107 deletions(-) diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index dd21628983..18ae76f948 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -222,10 +222,6 @@ def limit @timeout end - def preserve_whitespace - @preserve_whitespace = true - end - # Set our code for returning a value. def setcode(string = nil, interp = nil, &block) Facter.warnonce "The interpreter parameter to 'setcode' is deprecated and will be removed in a future version." if interp @@ -296,10 +292,6 @@ def value ms = (finishtime - starttime) * 1000 Facter.show_time "#{self.name}: #{"%.2f" % ms}ms" - unless @preserve_whitespace - result.strip! if result && result.respond_to?(:strip!) - end - return nil if result == "" return result end diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index 29ac64e4bd..75959e0e1e 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -169,105 +169,6 @@ def handy_method() @resolve.value.should == "foo" end - describe "when dealing with whitespace" do - it "should by default strip whitespace" do - @resolve.setcode {' value '} - @resolve.value.should == 'value' - end - - describe "when given a string" do - - [true, false - ].each do |windows| - describe "#{ (windows) ? '' : 'not' } on Windows" do - before do - given_a_configuration_of(:is_windows => windows) - end - - describe "stripping whitespace" do - [{:name => 'leading', :result => ' value', :expect => 'value'}, - {:name => 'trailing', :result => 'value ', :expect => 'value'}, - {:name => 'internal', :result => 'val ue', :expect => 'val ue'}, - {:name => 'leading and trailing', :result => ' value ', :expect => 'value'}, - {:name => 'leading and internal', :result => ' val ue', :expect => 'val ue'}, - {:name => 'trailing and internal', :result => 'val ue ', :expect => 'val ue'} - ].each do |scenario| - - it "should remove outer whitespace when whitespace is #{scenario[:name]}" do - @resolve.setcode "/bin/foo" - Facter::Util::Resolution.expects(:exec).once.with("/bin/foo").returns scenario[:result] - @resolve.value.should == scenario[:expect] - end - - end - end - - describe "not stripping whitespace" do - before do - @resolve.preserve_whitespace - end - - [{:name => 'leading', :result => ' value', :expect => ' value'}, - {:name => 'trailing', :result => 'value ', :expect => 'value '}, - {:name => 'internal', :result => 'val ue', :expect => 'val ue'}, - {:name => 'leading and trailing', :result => ' value ', :expect => ' value '}, - {:name => 'leading and internal', :result => ' val ue', :expect => ' val ue'}, - {:name => 'trailing and internal', :result => 'val ue ', :expect => 'val ue '} - ].each do |scenario| - - it "should not remove #{scenario[:name]} whitespace" do - @resolve.setcode "/bin/foo" - Facter::Util::Resolution.expects(:exec).once.with("/bin/foo").returns scenario[:result] - @resolve.value.should == scenario[:expect] - end - - end - end - end - end - end - - describe "when given a block" do - describe "stripping whitespace" do - [{:name => 'leading', :result => ' value', :expect => 'value'}, - {:name => 'trailing', :result => 'value ', :expect => 'value'}, - {:name => 'internal', :result => 'val ue', :expect => 'val ue'}, - {:name => 'leading and trailing', :result => ' value ', :expect => 'value'}, - {:name => 'leading and internal', :result => ' val ue', :expect => 'val ue'}, - {:name => 'trailing and internal', :result => 'val ue ', :expect => 'val ue'} - ].each do |scenario| - - it "should remove outer whitespace when whitespace is #{scenario[:name]}" do - @resolve.setcode {scenario[:result]} - @resolve.value.should == scenario[:expect] - end - - end - end - - describe "not stripping whitespace" do - before do - @resolve.preserve_whitespace - end - - [{:name => 'leading', :result => ' value', :expect => ' value'}, - {:name => 'trailing', :result => 'value ', :expect => 'value '}, - {:name => 'internal', :result => 'val ue', :expect => 'val ue'}, - {:name => 'leading and trailing', :result => ' value ', :expect => ' value '}, - {:name => 'leading and internal', :result => ' val ue', :expect => ' val ue'}, - {:name => 'trailing and internal', :result => 'val ue ', :expect => 'val ue '} - ].each do |scenario| - - it "should not remove #{scenario[:name]} whitespace" do - @resolve.setcode {scenario[:result]} - @resolve.value.should == scenario[:expect] - end - - end - end - end - end - describe "and setcode has not been called" do it "should return nil" do Facter::Util::Resolution.expects(:exec).with(nil, nil).never From 0e721457c5dfcd05d688b8bd4d5d45c70fb0691a Mon Sep 17 00:00:00 2001 From: Alex Harvey Date: Wed, 31 Oct 2012 16:49:42 +1100 Subject: [PATCH 1071/3753] (#11612) Add support for processorX facts on HP-UX Without this patch applied, the processorX facts are not available on HP-UX platforms. This is a problem because the lack of these facts make it difficult to write Puppet manifests that work on HP-UX and other platforms like Linux that support the processorX facts. These methods involve using machinfo when it is available, using sched.models and getconf when machinfo is not available. A suite of RSpec tests are included as well as fixture files for four selected examples of machinfo outputs, a sample sched.models file and unistd.h. --- lib/facter/processor.rb | 12 +- lib/facter/util/processor.rb | 139 ++ spec/fixtures/hpux/machinfo/hppa-rp4440 | 26 + spec/fixtures/hpux/machinfo/ia64-rx2620 | 49 + spec/fixtures/hpux/machinfo/ia64-rx6600 | 26 + spec/fixtures/hpux/machinfo/ia64-rx8640 | 53 + spec/fixtures/hpux/sched.models | 174 +++ spec/fixtures/hpux/unistd.h | 1534 +++++++++++++++++++++++ spec/unit/processor_spec.rb | 96 ++ 9 files changed, 2108 insertions(+), 1 deletion(-) create mode 100644 spec/fixtures/hpux/machinfo/hppa-rp4440 create mode 100644 spec/fixtures/hpux/machinfo/ia64-rx2620 create mode 100644 spec/fixtures/hpux/machinfo/ia64-rx6600 create mode 100644 spec/fixtures/hpux/machinfo/ia64-rx8640 create mode 100644 spec/fixtures/hpux/sched.models create mode 100644 spec/fixtures/hpux/unistd.h diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb index 3b6fb67b55..a50bdf64d3 100644 --- a/lib/facter/processor.rb +++ b/lib/facter/processor.rb @@ -27,6 +27,8 @@ processor_list = case Facter::Util::Processor.kernel_fact_value when "AIX" Facter::Util::Processor.aix_processor_list +when "HP-UX" + Facter::Util::Processor.hpux_processor_list when "SunOS" Facter::Util::Processor.enum_kstat else @@ -35,7 +37,7 @@ processor_list.each_with_index do |desc, i| Facter.add("Processor#{i}") do - confine :kernel => [ :aix, :sunos, :linux, :"gnu/kfreebsd" ] + confine :kernel => [ :aix, :"hp-ux", :sunos, :linux, :"gnu/kfreebsd" ] setcode do desc end @@ -77,6 +79,14 @@ end end +Facter.add("ProcessorCount") do + confine :kernel => :"hp-ux" + setcode do + processor_list = Facter::Util::Processor.hpux_processor_list + processor_list.length.to_s + end +end + Facter.add("Processor") do confine :kernel => :openbsd setcode do diff --git a/lib/facter/util/processor.rb b/lib/facter/util/processor.rb index 5aa1b185db..14c22dc1ab 100644 --- a/lib/facter/util/processor.rb +++ b/lib/facter/util/processor.rb @@ -70,6 +70,145 @@ def self.kernel_fact_value Facter.value(:kernel) end + ## + # hpux_processor_list is intended to generate a list of values for the + # processorX facts. + def self.hpux_processor_list + return_value = [] + hpux_proc_id_list = [] + cpu = "" + + ## + # first, try parsing machinfo output. + if output = machinfo then + output.split("\n").each do |line| + if line.match(/processor model:\s+\d+\s+(.*)/) then + cpu = $1.to_s + elsif line.match(/\d+\s+((?:PA-RISC|Intel).*processors.*)/) then + cpu = $1.to_s + cpu.sub!(/processors/, "processor") + end + end + end + + ## + # if that fails, try looking using model command and cross referencing against + # sched.models, which could be in three places. This file only catered for + # PA-RISC. Unfortunately, the file is not up to date sometimes. + if cpu.empty? then + m = model + m.sub!(/\s+$/, "") + m.sub!(/.*\//, "") + m.sub!(/.*\s+/, "") + + if sched_models_lines = read_sched_models + sched_models_lines.each do |l| + if l.match(m) and l.match(/^\S+\s+\d+\.\d+\s+(\S+)/) then + cpu = "PA-RISC " + $1.to_s.sub!(/^PA/, "") + " processor" + break # we assume first match is the only match. + end + end + end + end + + ## + # if that also fails, report the CPU version based on unistd.h and chip type based on getconf. + if cpu.empty? then + cpu_version = getconf_cpu_version + cpu_chip_type = getconf_cpu_chip_type + cpu_string = "" + + if lines = read_unistd_h("/usr/include/sys/unistd.h") then + lines.each do |l| + if l.match(/define.*0x#{cpu_version.to_i.to_s(16)}.*\/\*\s+(.*)\s+\*\//) then + cpu_string = $1.to_s + break + end + end + end + + if cpu_string.empty? then + cpu_string = "CPU v" + cpu_version + end + + cpu = cpu_string + " CHIP TYPE #" + cpu_chip_type + end + + ## + # now count (logical) CPUs using ioscan. We set processorX for X in 0..processorcount + # to cpu as worked out above. HP-UX does not support more than one installed CPU + # model. + if output = ioscan then + output.split("\n").each do |line| + if line.match(/processor/) then + hpux_proc_id_list << cpu + end + end + end + + hpux_proc_id_list + end + + ## + # read_sched_models is intended to be stubbed instead of File.readlines + # @return [Array] of strings containing the lines of the file or nil if the + # sched.models file could not be located. + def self.read_sched_models + path = if File.exists?("/usr/lib/sched.models") + "/usr/lib/sched.models" + elsif File.exists?("/usr/sam/lib/mo/sched.models") + "/usr/sam/lib/mo/sched.models" + elsif File.exists?("/opt/langtools/lib/sched.models") + "/opt/langtools/lib/sched.models" + end + + if path + File.readlines(path) + end + end + private_class_method :read_sched_models + + ## + # read_unistd_h is intended to be stubbed instead of File.readlines + # @return [Array] of Strings or nil if the file does not exist. + def self.read_unistd_h(path) + if File.exists?(path) then + File.readlines(path) + end + end + private_class_method :read_unistd_h + + ## + # machinfo delegates directly to Facter::Util::Resolution.exec, as with lsdev + # above. + def self.machinfo(command="/usr/contrib/bin/machinfo") + Facter::Util::Resolution.exec(command) + end + + ## + # model delegates directly to Facter::Util::Resolution.exec. + def self.model(command="model") + Facter::Util::Resolution.exec(command) + end + + ## + # ioscan delegates directly to Facter::Util::Resolution.exec. + def self.ioscan(command="ioscan -fknCprocessor") + Facter::Util::Resolution.exec(command) + end + + ## + # getconf_cpu_version delegates directly to Facter::Util::Resolution.exec. + def self.getconf_cpu_version(command="getconf CPU_VERSION") + Facter::Util::Resolution.exec(command) + end + + ## + # getconf_cpu_chip_type delegates directly to Facter::Util::Resolution.exec. + def self.getconf_cpu_chip_type(command="getconf CPU_CHIP_TYPE") + Facter::Util::Resolution.exec(command) + end + def self.enum_cpuinfo processor_num = -1 processor_list = [] diff --git a/spec/fixtures/hpux/machinfo/hppa-rp4440 b/spec/fixtures/hpux/machinfo/hppa-rp4440 new file mode 100644 index 0000000000..d388b5b452 --- /dev/null +++ b/spec/fixtures/hpux/machinfo/hppa-rp4440 @@ -0,0 +1,26 @@ +CPU info: + 4 PA-RISC 8800 processors (1000 MHz, 64 MB) + CPU version 5 + 8 logical processors (2 per socket) + +Memory: 24574 MB (24 GB) + +Firmware info: + Firmware revision: 45.44 + IPMI is supported on this system. + BMC firmware revision: 3.52 + +Platform info: + Model: "9000/800/rp4440 " + Machine ID number: XXXXXXXXXXXXXXXXXXX + Machine serial number: SGHXXXXXXX + +OS info: + Nodename: myhost + Release: HP-UX B.11.31 + Version: U (unlimited-user license) + Machine: 9000/800 + ID Number: 97704309 + vmunix _release_version: +_release_version: +@(#) $Revision: vmunix: B.11.31_LR FLAVOR=perf diff --git a/spec/fixtures/hpux/machinfo/ia64-rx2620 b/spec/fixtures/hpux/machinfo/ia64-rx2620 new file mode 100644 index 0000000000..8fca810346 --- /dev/null +++ b/spec/fixtures/hpux/machinfo/ia64-rx2620 @@ -0,0 +1,49 @@ +CPU info: + Number of CPUs = 2 + Number of enabled CPUs = 2 + Clock speed = 1600 MHz + Bus speed = 400 MT/s + CPUID registers + vendor information = "GenuineIntel" + processor serial number = 0x0000000000000000 + processor version info = 0x000000001f020104 + architecture revision: 0 + processor family: 31 Intel(R) Itanium 2 Family Processors + processor model: 2 Intel(R) Itanium 2 processor + Bus features + implemented = 0xbdf0000060000000 + selected = 0x0000000040000000 + Bus Lock Signal masked + processor revision: 1 Stepping A1 + largest CPUID reg: 4 + processor capabilities = 0x0000000000000001 + implements long branch: 1 + +Cache info: + L1 Instruction: size = 16 KB, associativity = 4 + L1 Data: size = 16 KB, associativity = 4 + L2 Unified: size = 256 KB, associativity = 8 + L3 Unified: size = 6144 KB, associativity = 12 + +Memory = 20468 MB (19.988281 GB) + +Firmware info: + Firmware revision = 03.17 + FP SWA driver revision: 1.18 + IPMI is supported on this system. + BMC version: 3.48 + +Platform info: + model string = "ia64 hp server rx2620" + machine id number = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + machine serial number = XXXXXXXXXX + +OS info: + sysname = HP-UX + nodename = myhost + release = B.11.23 + version = U (unlimited-user license) + machine = ia64 + idnumber = XXXXXXXXXX + vmunix _release_version: +@(#) $Revision: vmunix: B11.23_LR FLAVOR=perf Fri Aug 29 22:35:38 PDT 2003 $ diff --git a/spec/fixtures/hpux/machinfo/ia64-rx6600 b/spec/fixtures/hpux/machinfo/ia64-rx6600 new file mode 100644 index 0000000000..368a8a7918 --- /dev/null +++ b/spec/fixtures/hpux/machinfo/ia64-rx6600 @@ -0,0 +1,26 @@ +CPU info: + 4 Intel(R) Itanium 2 9100 series processors (1.59 GHz, 18 MB) + 532 MT/s bus, CPU version A1 + 8 logical processors (2 per socket) + +Memory: 24542 MB (23.97 GB) + +Firmware info: + Firmware revision: 04.11 + FP SWA driver revision: 1.18 + IPMI is supported on this system. + BMC firmware revision: 5.24 + +Platform info: + Model: "ia64 hp server rx6600" + Machine ID number: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + Machine serial number: XXXXXXXXXX + +OS info: + Nodename: bt1353b6 + Release: HP-UX B.11.31 + Version: U (unlimited-user license) + Machine: ia64 + ID Number: XXXXXXXXXX + vmunix _release_version: +@(#) $Revision: vmunix: B.11.31_LR FLAVOR=perf diff --git a/spec/fixtures/hpux/machinfo/ia64-rx8640 b/spec/fixtures/hpux/machinfo/ia64-rx8640 new file mode 100644 index 0000000000..05c5ce58c0 --- /dev/null +++ b/spec/fixtures/hpux/machinfo/ia64-rx8640 @@ -0,0 +1,53 @@ +CPU info: + Number of CPUs = 16 + Number of enabled CPUs = 2 + Number of enabled sockets = 2 + Cores per socket = 2 + Clock speed = 1598 MHz + Bus speed = 533 MT/s + CPUID registers + vendor information = "GenuineIntel" + processor serial number = 0x0000000000000000 + processor version info = 0x0000000020010104 + architecture revision: 0 + processor family: 32 Intel(R) Itanium 2 9100 series + processor model: 1 Intel(R) Itanium 2 9100 series + Bus features + implemented = 0xbdf0000020000000 + selected = 0x0020000000000000 + Exclusive Bus Cache Line Replacement Enabled + processor revision: 1 Stepping A1 + largest CPUID reg: 4 + processor capabilities = 0x0000000000000005 + implements long branch: 1 + implements 16-byte atomic operations: 1 + +Cache info (per core): + L1 Instruction: size = 16 KB, associativity = 4 + L1 Data: size = 16 KB, associativity = 4 + L2 Instruction: size = 1024 KB, associativity = 8 + L2 Data: size = 256 KB, associativity = 8 + L3 Unified: size = 9216 KB, associativity = 9 + +Memory = 4046 MB (3.951172 GB) + +Firmware info: + Firmware revision = 9.048 + FP SWA driver revision: 1.18 + IPMI is supported on this system. + BMC version: 4.01 + +Platform info: + model string = "ia64 hp server rx8640" + machine id number = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + machine serial number = XXXXXXXXXX + +OS info: + sysname = HP-UX + nodename = bakprdinfh001 + release = B.11.23 + version = U (unlimited-user license) + machine = ia64 + idnumber = XXXXXXXXXX + vmunix _release_version: +@(#) $Revision: vmunix: B11.23_LR FLAVOR=perf Fri Aug 29 22:35:38 PDT 2003 $ diff --git a/spec/fixtures/hpux/sched.models b/spec/fixtures/hpux/sched.models new file mode 100644 index 0000000000..5ce6813ae2 --- /dev/null +++ b/spec/fixtures/hpux/sched.models @@ -0,0 +1,174 @@ +/* @(#) $Header: sched.models,v 73.22 2001/06/06 18:47:11 tkopren Exp $ */ +A180 1.1e PA7300LC +A180c 1.1e PA7300LC +A400-36 2.0 PA8500 +A400-44 2.0 PA8500 +A400-5X 2.0 PA8600 +A400-7X 2.0 PA8700 +A400-8X 2.0 PA8700 +A500-36 2.0 PA8500 +A500-44 2.0 PA8500 +A500-55 2.0 PA8600 +A500-5X 2.0 PA8600 +A500-7X 2.0 PA8700 +A500-8X 2.0 PA8700 +B1000 2.0 PA8500 +B115 1.1e PA7300 +B120 1.1e PA7300 +B132L 1.1e PA7300 +B160L 1.1e PA7300 +B2000 2.0 PA8500 +B2600 2.0 PA8700 +C100 1.1d PA7200 +C110 1.1d PA7200 +C115 1.1e PA7300 +C120 1.1e PA7300 +C130 2.0 PA8000 +C140 2.0 PA8000 +C160 2.0 PA8000 +C160L 1.1e PA7300 +C180 2.0 PA8000 +C180-XP 2.0 PA8000 +C200+ 2.0 PA8200 +C240+ 2.0 PA8200 +C3000 2.0 PA8500 +C360 2.0 PA8500 +C3600 2.0 PA8600 +C3700 2.0 PA8700 +C3750 2.0 PA8700 +D200 1.1c PA7100LC +D210 1.1c PA7100LC +D220 1.1e PA7300 +D230 1.1e PA7300 +D250 1.1d PA7200 +D260 1.1d PA7200 +D270 2.0 PA8000 +D280 2.0 PA8000 +D310 1.1c PA7100LC +D320 1.1e PA7300 +D330 1.1e PA7300 +D350 1.1d PA7200 +D360 1.1d PA7200 +D370 2.0 PA8000 +D380 2.0 PA8000 +D390 2.0 PA8000 +D410 1.1d PA7200 +D650 2.0 PA8000 +DX0 1.1c PA7100LC +DX5 1.1c PA7100LC +DXO 1.1c PA7100LC +E25 1.1c PA7100LC +E35 1.1c PA7100LC +E45 1.1c PA7100LC +E55 1.1c PA7100LC +G50 1.1b PA7100 +G60 1.1b PA7100 +G70 1.1b PA7100 +H50 1.1b PA7100 +H60 1.1b PA7100 +H70 1.1b PA7100 +I50 1.1b PA7100 +I60 1.1b PA7100 +I70 1.1b PA7100 +J200 1.1d PA7200 +J210 1.1d PA7200 +J210XC 1.1d PA7200 +J220 2.0 PA8000 +J2240 2.0 PA8200 +J280 2.0 PA8000 +J400 2.0 PA8000 +J410 2.0 PA8000 +J5000 2.0 PA8500 +J5600 2.0 PA8600 +J6000 2.0 PA8600 +J6700 2.0 PA8700 +J6750 2.0 PA8700 +J7000 2.0 PA8500 +J7600 2.0 PA8600 +K100 1.1d PA7200 +K200 1.1d PA7200 +K210 1.1d PA7200 +K220 1.1d PA7200 +K230 1.1d PA7200 +K250 2.0 PA8000 +K260 2.0 PA8000 +K260-EG 2.0 PA8000 +K270 2.0 PA8200 +K360 2.0 PA8000 +K370 2.0 PA8200 +K380 2.0 PA8200 +K400 1.1d PA7200 +K410 1.1d PA7200 +K420 1.1d PA7200 +K450 2.0 PA8000 +K460 2.0 PA8000 +K460-EG 2.0 PA8000 +K460-XP 2.0 PA8000 +K470 2.0 PA8200 +K570 2.0 PA8200 +K580 2.0 PA8200 +L1000-36 2.0 PA8500 +L1000-44 2.0 PA8500 +L1000-5X 2.0 PA8600 +L1000-8X 2.0 PA8700 +L1500-6x 2.0 PA8700 +L1500-7x 2.0 PA8700 +L1500-8x 2.0 PA8700 +L1500-9x 2.0 PA8700 +L2000-36 2.0 PA8500 +L2000-44 2.0 PA8500 +L2000-5X 2.0 PA8600 +L2000-8X 2.0 PA8700 +L3000-55 2.0 PA8600 +L3000-5x 2.0 PA8600 +L3000-6x 2.0 PA8700 +L3000-7x 2.0 PA8700 +L3000-8x 2.0 PA8700 +L3000-9x 2.0 PA8700 +N4000-36 2.0 PA8500 +N4000-44 2.0 PA8500 +N4000-55 2.0 PA8600 +N4000-5X 2.0 PA8600 +N4000-6X 2.0 PA8700 +N4000-7X 2.0 PA8700 +N4000-8X 2.0 PA8700 +N4000-8Y 2.0 PA8700 +N4000-8Z 2.0 PA8700 +N4000-9X 2.0 PA8700 +R380 2.0 PA8000 +R390 2.0 PA8000 +S700i 1.1e PA7300 +S715 1.1e PA7300 +S744 1.1e PA7300 +S760 1.1e PA7300 +T500 1.1b PA7100 +T520 1.1b PA7100 +T540 2.0 PA8000 +V2200 2.0 PA8200 +V2250 2.0 PA8200 +V2500 2.0 PA8500 +V2600 2.0 PA8600 +V2650 2.0 PA8700 +V2700 2.0 PA8700 +g4000 Itanium(TM) +i2000 Itanium(TM) +u16000 Itanium(TM) +715 1.1c PA7100LC +712 1.1c PA7100LC +722 1.1c PA7100LC +725 1.1c PA7100LC +728 1.1d PA7200 +735 1.1b PA7100 +742 1.1b PA7100 +743 1.1c PA7100LC +744 1.1e PA7300 +745 1.1b PA7100 +747 1.1b PA7100 +755 1.1b PA7100 +770 1.1d PA7200 +777 1.1d PA7200 +778 1.1e PA7300 +779 1.1e PA7300 +780 2.0 PA8000 +781 2.0 PA8000 +782 2.0 PA8200 diff --git a/spec/fixtures/hpux/unistd.h b/spec/fixtures/hpux/unistd.h new file mode 100644 index 0000000000..739fb800a7 --- /dev/null +++ b/spec/fixtures/hpux/unistd.h @@ -0,0 +1,1534 @@ +/* @(#) unistd.h $Date: 2008/08/13 14:45:21 $Revision: r11.31/2 PATCH_11.31 (B11.31.0903LR) */ + +/* + * (C) Copyright 1996-2008 Hewlett-Packard Development Company, L.P. + * + * BEGIN_DESC + * + * File: + * @(#) common/include/sys/unistd.h $Revision: $ + * + * END_DESC + */ + +#ifndef _SYS_UNISTD_INCLUDED +#define _SYS_UNISTD_INCLUDED + +#ifndef _SYS_STDSYMS_INCLUDED +# include +#endif /* _SYS_STDSYMS_INCLUDED */ + +# include + +/* Types */ + +#ifdef _INCLUDE_POSIX_SOURCE +# ifndef NULL +# include +# endif /* NULL */ +#endif /* _INCLUDE_POSIX_SOURCE */ + +#if !defined(_INCLUDE_XOPEN_SOURCE) && !defined(_INCLUDE_XOPEN_SOURCE_EXTENDED) +/* The return value on failure of the sbrk(2) system call */ +#define SBRK_FAILED (void *)-1L +#endif + +/* HP-UX supports 64-bit files on 32-bit systems */ +#define _LFS64_STDIO 1 +#define _LFS64_ASYNCHRONOUS_IO 1 +#define _LFS_ASYNCHRONOUS_IO 1 +#define _LFS_LARGEFILE 1 +#define _LFS64_LARGEFILE 1 + +#if defined(_INCLUDE_XOPEN_SOURCE_EXTENDED) || defined(_INCLUDE_POSIX_SOURCE) +# include +#endif /* _INCLUDE_XOPEN_SOURCE_EXTENDED || _INCLUDE_POSIX_SOURCE */ + +#ifdef _INCLUDE_HPUX_SOURCE +# ifdef _KERNEL + /* Structure for "utime" function moved to the unsupported section */ +# else /* ! _KERNEL */ +# include +# endif /* ! _KERNEL */ +#endif /* _INCLUDE_HPUX_SOURCE */ + +/* Function prototypes */ + +#ifndef _KERNEL +#ifdef __cplusplus + extern "C" { +#endif /* __cplusplus */ + +#ifdef _INCLUDE_POSIX_SOURCE +#if defined(__ia64) && !defined(_LIBC) + /* pragmas needed to support -B protected */ +#pragma extern _exit, access, chdir, chown, close, ctermid +#pragma extern dup, dup2, execl, execle, execlp, execv, execve, execvp +#pragma extern fpathconf, getcwd, getgroups, getlogin +#ifdef _INCLUDE_XOPEN_SOURCE_PRE_600 +#pragma extern cuserid +#endif /* _INCLUDE_XOPEN_SOURCE_PRE_600 */ +# ifdef _REENTRANT +# pragma extern getlogin_r +# endif /* _REENTRANT */ +#pragma extern isatty, link +# if !defined(__cplusplus) || !defined(_APP32_64BIT_OFF_T) +# pragma extern lseek +# endif /* !__cplusplus || !_APP32_64BIT_OFF_T */ +#pragma builtin read +#pragma extern pathconf, pause, pipe, read, rmdir, setgid, setpgid +#pragma extern setsid, setuid, sleep, sysconf, tcgetpgrp, tcsetpgrp +#pragma extern ttyname +# ifdef _REENTRANT +# pragma extern ttyname_r +# endif /* _REENTRANT */ +#pragma builtin write +#pragma extern unlink, write, alarm, fork, getuid, geteuid, getgid +#pragma extern getegid, getpid, getpgrp, getppid +#endif /* __ia64 && ! _LIBC */ + + extern void _exit __((int)); + extern int access __((const char *, int)); + extern int chdir __((const char *)); + extern int chown __((const char *, uid_t, gid_t)); + extern int close __((int)); + extern char *ctermid __((char *)); +#ifdef _INCLUDE_XOPEN_SOURCE_PRE_600 + extern char *cuserid __((char *)); +#endif /* _INCLUDE_XOPEN_SOURCE_PRE_600 */ + extern int dup __((int)); + extern int dup2 __((int, int)); + extern int execl __((const char *, const char *, ...)); + extern int execle __((const char *, const char *, ...)); + extern int execlp __((const char *, const char *, ...)); + extern int execv __((const char *, char *const [])); + extern int execve __((const char *, char *const [], char *const [])); + extern int execvp __((const char *, char *const [])); + extern long fpathconf __((int, int)); + extern char *getcwd __((char *, __size_t)); + extern int getgroups __((int, gid_t [])); + extern char *getlogin __((void)); +# ifdef _REENTRANT +# ifndef _PTHREADS_DRAFT4 + extern int getlogin_r __((char *, __size_t)); +# else /* _PTHREADS_DRAFT4 */ + extern int getlogin_r __((char *, int)); +# endif /* _PTHREADS_DRAFT4 */ +# endif + extern int isatty __((int)); + extern int link __((const char *, const char *)); +# if !defined(__cplusplus) || !defined(_APP32_64BIT_OFF_T) + _LF_EXTERN off_t lseek __((int, off_t, int)); +# endif /* !__cplusplus || !_APP32_64BIT_OFF_T */ + extern long pathconf __((const char *, int)); + extern int pause __((void)); + extern int pipe __((int *)); + extern ssize_t read __((int, void *, __size_t)); + extern int rmdir __((const char *)); + extern int setgid __((gid_t)); + extern int setpgid __((pid_t, pid_t)); + extern pid_t setsid __((void)); + extern int setuid __((uid_t)); + extern unsigned int sleep __((unsigned int)); + extern long sysconf __((int)); + extern pid_t tcgetpgrp __((int)); + extern int tcsetpgrp __((int, pid_t)); + extern char *ttyname __((int)); +# ifdef _REENTRANT +# ifndef _PTHREADS_DRAFT4 + extern int ttyname_r __((int, char *, __size_t)); +# else /* _PTHREADS_DRAFT4 */ + extern int ttyname_r __((int, char *, int)); +# endif /* _PTHREADS_DRAFT4 */ +# endif + extern int unlink __((const char *)); + extern ssize_t write __((int, const void *, __size_t)); + +# ifdef _CLASSIC_POSIX_TYPES + unsigned long alarm(); + extern int fork(); + extern unsigned short getuid(); + extern unsigned short geteuid(); + extern unsigned short getgid(); + extern unsigned short getegid(); + extern int getpid(); + extern int getpgrp(); + extern int getppid(); +# else + extern unsigned int alarm __((unsigned int)); + extern pid_t fork __((void)); + extern gid_t getegid __((void)); + extern uid_t geteuid __((void)); + extern gid_t getgid __((void)); + extern pid_t getpgrp __((void)); + extern pid_t getpid __((void)); + extern pid_t getppid __((void)); + extern uid_t getuid __((void)); +# endif /* _CLASSIC_POSIX_TYPES */ +#endif /* _INCLUDE_POSIX_SOURCE */ + + +#ifdef _INCLUDE_POSIX2_SOURCE +#if defined(__ia64) && !defined(_LIBC) + /* pragmas needed to support -B protected */ +# pragma extern optarg, opterr, optind, optopt, getopt, confstr +#endif /* __ia64 && ! _LIBC */ + + extern char *optarg; + extern int opterr; + extern int optind; + extern int optopt; + /* fnmatch() has moved to */ + extern int getopt __((int, char * const [], const char *));/* was */ + extern __size_t confstr __((int, char *, __size_t)); +#endif /* _INCLUDE_POSIX2_SOURCE */ + +#ifdef _INCLUDE_POSIX1C_SOURCE +# if defined(__ia64) && !defined(_LIBC) + /* pragmas needed to support -B protected */ +# pragma extern pthread_atfork +# endif /* __ia64 && ! _LIBC */ +# ifdef _PROTOTYPES + extern int pthread_atfork(void (*)(void), void (*)(void), + void (*)(void)); +# else /* not _PROTOTYPES */ + extern int pthread_atfork(); +# endif /* _PROTOTYPES */ +#endif /* _INCLUDE_POSIX1C_SOURCE */ + + +#ifdef _INCLUDE_XOPEN_SOURCE +# if defined(__ia64) && !defined(_LIBC) +# /* pragmas needed to support -B protected */ +# pragma extern crypt, encrypt, fsync, nice +# ifdef _INCLUDE_XOPEN_SOURCE_PRE_600 +# pragma extern chroot, getpass +# endif /* _INCLUDE_XOPEN_SOURCE_PRE_600 */ +# if defined(_XPG3) || defined(_INCLUDE_HPUX_SOURCE) || defined(_SVID3) +# pragma extern rename +# endif /* _XPG3 || _INCLUDE_HPUX_SOURCE || _SVID3 */ +# if !defined(_INCLUDE_AES_SOURCE) || defined(_INCLUDE_XOPEN_SOURCE_EXTENDED) +# ifdef _BIND_LIBCALLS +# pragma builtin_milli swab +# endif /* _BIND_LIBCALLS */ +# pragma extern swab +# endif /* not _INCLUDE_AES_SOURCE || _INCLUDE_XOPEN_SOURCE_EXTENDED */ +# endif /* __ia64 && ! _LIBC */ + +# ifdef _INCLUDE_XOPEN_SOURCE_PRE_600 + extern int chroot __((const char *)); + extern char *getpass __((const char *)); +# endif /* _INCLUDE_XOPEN_SOURCE_PRE_600 */ + extern char *crypt __((const char *, const char *)); + extern void encrypt __((char [64], int)); + extern int fsync __((int)); + extern int nice __((int)); +# if defined(_XPG3) || defined(_INCLUDE_HPUX_SOURCE) || defined(_SVID3) +# ifdef _NAMESPACE_STD +namespace std { + extern int rename __((const char *, const char *)); /* now in */ +} +using std::rename; +# else /* !_NAMESPACE_STD */ + extern int rename __((const char *, const char *)); /* now in */ +# endif /* _NAMESPACE_STD */ +# endif /* _XPG3 || _INCLUDE_HPUX_SOURCE || _SVID3 */ +# if !defined(_INCLUDE_AES_SOURCE) || defined(_INCLUDE_XOPEN_SOURCE_EXTENDED) + extern void swab __((const void * __restrict, void * __restrict, ssize_t)); +# endif /* not _INCLUDE_AES_SOURCE || _INCLUDE_XOPEN_SOURCE_EXTENDED */ +#endif /* _INCLUDE_XOPEN_SOURCE */ + + +#ifdef _INCLUDE_AES_SOURCE +# if !defined(_XPG4_EXTENDED) || defined(_INCLUDE_HPUX_SOURCE) + /* Exclude from CASPEC but keep in HPUX */ +# if defined(__ia64) && !defined(_LIBC) + /* pragmas needed to support -B protected */ +# pragma extern environ +# endif /* __ia64 && ! _LIBC */ + extern char **environ; +# endif /* !_XPG4_EXTENDED || _INCLUDE_HPUX_SOURCE */ + +# if defined(__ia64) && !defined(_LIBC) + /* pragmas needed to support -B protected */ +# pragma extern readlink, fchown, symlink +# endif /* __ia64 && ! _LIBC */ + +# ifdef _INCLUDE_XOPEN_SOURCE_EXTENDED +# ifdef _INCLUDE_XOPEN_SOURCE_PRE_600 + extern int readlink __((const char *, char *, __size_t)); /*XPG4_EXT, HPUX*/ +# else /* _INCLUDE_XOPEN_SOURCE_600 */ + extern ssize_t readlink __((const char * __restrict, char * __restrict, __size_t)); /*Unix 2003, HPUX*/ +# endif /* _INCLUDE_XOPEN_SOURCE_PRE_600 */ +# else /* ! _INCLUDE_XOPEN_SOURCE_EXTENDED */ + extern int readlink __((const char *, char *, int)); /* AES */ +# endif /* _INCLUDE_XOPEN_SOURCE_EXTENDED */ + extern int fchown __((int, uid_t, gid_t)); +# if !defined(__cplusplus) || !defined(_APP32_64BIT_OFF_T) +# if defined(__ia64) && !defined(_LIBC) + /* pragmas needed to support -B protected */ +# pragma extern ftruncate, truncate +# endif /* __ia64 && ! _LIBC */ + _LF_EXTERN int ftruncate __((int, off_t)); + _LF_EXTERN int truncate __((const char *, off_t)); +# endif /* !__cplusplus || !_APP32_64BIT_OFF_T */ +# if !defined(_XPG4_EXTENDED) || defined(_INCLUDE_HPUX_SOURCE) + /* Exclude from CASPEC but keep in HPUX */ +# if defined(__ia64) && !defined(_LIBC) + /* pragmas needed to support -B protected */ +# pragma extern setgroups +# endif /* __ia64 && ! _LIBC */ + extern int setgroups __((int, gid_t [])); +# endif /* !_XPG4_EXTENDED || _INCLUDE_HPUX_SOURCE */ + extern int symlink __((const char *, const char *)); +#endif /* _INCLUDE_AES_SOURCE */ + +#ifdef _INCLUDE_XOPEN_SOURCE_EXTENDED +# ifdef _XPG4_EXTENDED +# if defined(__ia64) && !defined(_LIBC) + /* pragmas needed to support -B protected */ +# pragma extern setpgrp +# endif /* __ia64 && ! _LIBC */ + extern pid_t setpgrp __((void)); +# else /* !_XPG4_EXTENDED */ +# ifndef _SVID3 +# if defined(__ia64) && !defined(_LIBC) + /* pragmas needed to support -B protected */ +# pragma extern setpgrp +# endif /* __ia64 && ! _LIBC */ + +# ifdef _CLASSIC_ID_TYPES + extern int setpgrp(); +# else /* ! _CLASSIC_ID_TYPES */ + extern pid_t setpgrp __((void)); +# endif /* _CLASSIC_ID_TYPES */ +# endif /* ! _SVID3 */ +# endif /* _XPG4_EXTENDED */ + +#if defined(__ia64) && !defined(_LIBC) + /* pragmas needed to support -B protected */ +# pragma extern vfork +#endif /* __ia64 && ! _LIBC */ +# ifdef _CLASSIC_ID_TYPES + extern int vfork(); +# else /* not _CLASSIC_ID_TYPES */ + extern pid_t vfork __((void)); +# endif /* not _CLASSIC_ID_TYPES */ + +# if defined(_XPG4_EXTENDED) && !defined(_INCLUDE_HPUX_SOURCE) + /* For CASPEC, look in stdlib.h for the _XPG4_EXTENDED definition */ + /* But for _INCLUDE_HPUX_SOURCE, maintain definitions here */ +# else /* !_XPG4_EXTENDED || _INCLUDE_HPUX_SOURCE*/ +# ifndef _MKTEMP_DEFINED +# define _MKTEMP_DEFINED +# if defined(__ia64) && !defined(_LIBC) + /* pragmas needed to support -B protected */ +# pragma extern mkstemp, mktemp, ttyslot +# endif /* __ia64 && ! _LIBC */ + extern int mkstemp __((char *)); + extern char *mktemp __((char *)); + extern int ttyslot __((void)); +# endif /*_MKTEMP_DEFINED */ +# endif /* _XPG4_EXTENDED && !_INCLUDE_HPUX_SOURCE */ + +#if defined(__ia64) && !defined(_LIBC) + /* pragmas needed to support -B protected */ +# pragma extern fchdir, gethostid, gethostname +# pragma extern getpgid, getsid, getwd +# if !defined(__cplusplus) || !defined(_APP32_64BIT_OFF_T) +# pragma extern lockf +# endif /* !__cplusplus || !_APP32_64BIT_OFF_T */ +# pragma extern lchown, setregid, setreuid, sync +# pragma extern ualarm, usleep +# ifdef _INCLUDE_XOPEN_SOURCE_PRE_600 +# pragma extern brk, sbrk, getdtablesize, getpagesize +# endif /* _INCLUD_XOPEN_SOURCE_PRE_600 */ +#endif /* __ia64 && ! _LIBC */ + +#ifdef _INCLUDE_XOPEN_SOURCE_PRE_600 + extern int brk __((void *)); + extern int getdtablesize __((void)); + extern int getpagesize __((void)); +#endif /* _INCLUDE_XOPEN_SOURCE_PRE_600 */ + extern int fchdir __((int)); +# ifdef _XPG4_EXTENDED + extern long gethostid __((void)); +# else /* !_XPG4_EXTENDED */ + extern int gethostid __((void)); +# endif /* _XPG4_EXTENDED */ + extern int gethostname __((char *, __size_t)); + extern pid_t getpgid __((pid_t)); + extern pid_t getsid __((pid_t)); + extern char *getwd __((char *)); +# if !defined(__cplusplus) || !defined(_APP32_64BIT_OFF_T) + _LF_EXTERN int lockf __((int, int, off_t)); +# endif /* !__cplusplus || !_APP32_64BIT_OFF_T */ + extern int lchown __((const char *, uid_t, gid_t)); +# ifdef _INCLUDE_XOPEN_SOURCE_PRE_600 +# ifdef _CLASSIC_XOPEN_TYPES + extern char *sbrk __((int)); +# else /* not _CLASSIC_XOPEN_TYPES */ +# ifdef _INCLUDE_XOPEN_SOURCE_PRE_500 + extern void *sbrk __((int)); +# else /* _INCLUDE_XOPEN_SOURCE_500 */ + extern void *sbrk __((intptr_t)); +# endif /* _INCLUDE_XOPEN_SOURCE_PRE_500 */ +# endif /* not _CLASSIC_XOPEN_TYPES */ +# endif /* _INCLUDE_XOPEN_SOURCE_PRE_600 */ + extern int setregid __((gid_t, gid_t)); + extern int setreuid __((uid_t, uid_t)); + extern void sync __((void)); +# ifdef _XPG4_EXTENDED + extern useconds_t ualarm __((useconds_t, useconds_t)); + extern int usleep __((useconds_t)); +# else /* !_XPG4_EXTENDED */ + extern unsigned int ualarm __((unsigned int, unsigned int)); + extern int usleep __((unsigned int)); +# endif /* _XPG4_EXTENDED */ +#endif /* _INCLUDE_XOPEN_SOURCE_EXTENDED */ + +#if defined(_INCLUDE_XOPEN_SOURCE_500) +# if defined(__ia64) && !defined(_LIBC) + /* pragmas needed to support -B protected */ +# pragma extern fdatasync +# if !defined(_APP32_64BIT_OFF_T) +# pragma extern pread, pwrite +# endif /* !_APP32_64BIT_OFF_T */ +# endif /* __ia64 && ! _LIBC */ +# if !defined(__cplusplus) || !defined(_APP32_64BIT_OFF_T) + _LF_EXTERN ssize_t pread __((int, void *, size_t, off_t)); + _LF_EXTERN ssize_t pwrite __((int, const void *, size_t, off_t)); +# endif /* !__cplusplus || !_APP32_64BIT_OFF_T */ + extern int fdatasync __((int)); +# if defined(_LARGEFILE64_SOURCE) +# ifdef __LP64__ +# define pread64 pread +# define pwrite64 pwrite +# else /* __LP64__ */ +# if defined(__ia64) && !defined(_LIBC) + /* pragmas needed to support -B protected */ +# pragma extern pread64, pwrite64 +# endif /* __ia64 && ! _LIBC */ + extern ssize_t pread64 __((int, void *, size_t, off64_t)); + extern ssize_t pwrite64 __((int, const void *, size_t, off64_t)); +# endif /* __LP64__ */ +# endif /* _LARGEFILE64_SOURCE */ +#endif /* _INCLUDE_XOPEN_SOURCE_500 */ + + +#ifdef _INCLUDE_XOPEN_SOURCE_600 +# if defined(__ia64) && !defined(_LIBC) + /* pragmas needed to support -B protected */ +# pragma extern setegid, seteuid +# endif /* __ia64 && ! _LIBC */ + extern int setegid __((gid_t)); + extern int seteuid __((uid_t)); +#endif /* _INCLUDE_XOPEN_SOURCE_600 */ + + +#ifdef _INCLUDE_HPUX_SOURCE +# if defined(__ia64) && !defined(_LIBC) + /* pragmas needed to support -B protected */ +# pragma extern endusershell, fsctl, getcdf, gethcwd, getpgrp2 +# pragma extern getusershell, getresgid, getresuid, hidecdf, initgroups +# ifndef _XPG4_EXTENDED +# pragma extern ioctl +# endif /* !_XPG4_EXTENDED */ +# pragma extern logname, lsync +# if !defined(__cplusplus) || !defined(_APP32_64BIT_OFF_T) +# pragma extern prealloc +# endif /* !__cplusplus || !_APP32_64BIT_OFF_T */ +# pragma extern sethostname, setpgrp2, setresgid, setresuid +# pragma extern setusershell, sgetl, sputl, swapon, swapoff, ttyname +# ifndef __STDC_32_MODE__ +# pragma extern __sysconfx +# endif /* __STDC_32_MODE__ */ +# ifdef _REENTRANT +# ifndef _PTHREADS_DRAFT4 +# pragma extern ttyname_r +# else +# pragma extern ttyname_r, endusershell_r, getusershell_r +# pragma extern setusershell_r +# endif /* _PTHREADS_DRAFT4 */ +# endif /* _REENTRANT */ +# pragma extern set_userthreadid +# endif /* __ia64 && ! _LIBC */ + + extern void endusershell __((void)); + extern int fsctl __((int, int, void *, __size_t)); + extern char *getcdf __((const char *, char *, __size_t)); + extern char *gethcwd __((char *, __size_t)); + extern int getpgrp2 __((pid_t)); + extern char *getusershell __((void)); + extern int getresgid __((gid_t *, gid_t *, gid_t *)); + extern int getresuid __((uid_t *, uid_t *, uid_t *)); + extern char *hidecdf __((const char *, char *, __size_t)); + extern int initgroups __((const char *, gid_t)); +# ifndef _XPG4_EXTENDED + extern int ioctl __((int, int, ...)); +# endif /* !_XPG4_EXTENDED */ + extern char *logname __((void)); + extern void lsync __((void)); +# if !defined(__cplusplus) || !defined(_APP32_64BIT_OFF_T) + _LF_EXTERN int prealloc __((int, off_t)); +# endif /* !__cplusplus || !_APP32_64BIT_OFF_T */ + extern int sethostname __((const char *, __size_t)); + extern int setpgrp2 __((pid_t, pid_t)); + extern int setresgid __((gid_t, gid_t, gid_t)); + extern int setresuid __((uid_t, uid_t, uid_t)); + extern void setusershell __((void)); + extern long sgetl __((const char *)); + extern void sputl __((long, char *)); + extern int swapon __((const char *, ...)); + extern int swapoff __((const char *, int)); + extern char *ttyname __((int)); +#ifndef __STDC_32_MODE__ + extern int64_t __sysconfx __((int, int)); +#endif /* __STDC_32_MODE__ */ +# ifdef _REENTRANT +# ifndef _PTHREADS_DRAFT4 + extern int ttyname_r __((int, char *, __size_t)); +# else /* _PTHREADS_DRAFT4 */ + extern int ttyname_r __((int, char *, int)); + extern void endusershell_r __((char **)); + extern char *getusershell_r __((char **)); + extern void setusershell_r __((char **)); +# endif /* _PTHREADS_DRAFT4 */ +# endif + extern int set_userthreadid __((int)); + +#if defined(__ia64) && !defined(_LIBC) + /* pragmas needed to support -B protected */ +# ifdef _SVID3 +# pragma extern gettxt +# else +# pragma extern setpgrp3 +# endif /* _SVID3 */ +#endif /* __ia64 && ! _LIBC */ + +# ifdef _CLASSIC_ID_TYPES +# ifdef _SVID3 + extern char *gettxt(); +# endif /* _SVID3 */ +# ifndef _SVID3 + extern int setpgrp3(); +# endif /* _SVID3 */ +# else /* not _CLASSIC_ID_TYPES */ +# ifdef _SVID3 + extern char *gettxt __((const char *, const char *)); +# endif /* _SVID3 */ +# ifndef _SVID3 + extern pid_t setpgrp3 __((void)); +# endif /* _SVID3 */ +# endif /* not _CLASSIC_ID_TYPES */ +#endif /* _INCLUDE_HPUX_SOURCE */ + +# if defined(_LARGEFILE64_SOURCE) +# ifdef __LP64__ +# define prealloc64 prealloc +# define lockf64 lockf +# define truncate64 truncate +# define ftruncate64 ftruncate +# define lseek64 lseek +# else /* __LP64__ */ +# if defined(__ia64) && !defined(_LIBC) + /* pragmas needed to support -B protected */ +# pragma extern prealloc64, lockf64, truncate64, ftruncate64 +# pragma extern lseek64 +# endif /* __ia64 && ! _LIBC */ + extern int prealloc64 __((int, off64_t)); + extern int lockf64 __((int, int, off64_t)); + extern int truncate64 __((const char *, off64_t)); + extern int ftruncate64 __((int, off64_t)); + extern off64_t lseek64 __((int, off64_t, int)); +# endif /* __LP64 */ +# endif /* _LARGEFILE64_SOURCE */ + +# ifdef _APP32_64BIT_OFF_T +# if defined(__ia64) && !defined(_LIBC) + /* pragmas needed to support -B protected */ +# pragma extern __prealloc64, __lockf64, __truncate64, __ftruncate64 +# pragma extern __lseek64, __pread64, __pwrite64 +# endif /* __ia64 && ! _LIBC */ +extern int __prealloc64 __((int, off_t)); +extern int __lockf64 __((int, int, off_t)); +extern int __truncate64 __((const char *, off_t)); +extern int __ftruncate64 __((int, off_t)); +extern off64_t __lseek64 __((int, off_t, int)); +extern ssize_t __pread64 __((int, void *, size_t, off64_t)); +extern ssize_t __pwrite64 __((int, const void *, size_t, off64_t)); +# ifndef __cplusplus +static int truncate(a,b) __const char *a; off_t b; { return __truncate64(a,b); } +static int prealloc(a,b) int a; off_t b; { return __prealloc64(a,b); } +static int lockf(a,b,c) int a, b; off_t c; { return __lockf64(a,b,c); } +static int ftruncate(a,b) int a; off_t b; { return __ftruncate64(a,b); } +static off_t lseek(a,b,c) int a, c; off_t b; { return __lseek64(a,b,c); } +static ssize_t pread(a,b,c,d) int a; void *b; size_t c; off64_t d; + { return __pread64(a,b,c,d); } +static ssize_t pwrite(a,b,c,d) int a; const void *b; size_t c; off64_t d; + { return __pwrite64(a,b,c,d); } +# endif /* __cplusplus */ +# endif /* _APP32_64BIT_OFF_T */ + +#ifdef __cplusplus + } +#endif /* __cplusplus */ + +#if defined(__cplusplus) && defined(_APP32_64BIT_OFF_T) +inline int prealloc __((int, off_t)); +inline off_t lseek __((int, off_t, int)); +inline int ftruncate __((int, off_t)); +inline int truncate __((const char *, off_t)); +inline int lockf __((int, int, off_t)); +inline ssize_t pread __((int, void *, size_t, off64_t)); +inline ssize_t pwrite __((int, const void *, size_t, off64_t)); + +inline int truncate(const char *a, off_t b) { return __truncate64(a,b); } +inline int prealloc(int a, off_t b) { return __prealloc64(a,b); } +inline int lockf(int a, int b, off_t c) { return __lockf64(a,b,c); } +inline int ftruncate(int a, off_t b) { return __ftruncate64(a,b); } +inline off_t lseek(int a, off_t b, int c) { return __lseek64(a,b,c); } +inline ssize_t pread(int a, void *b, size_t c, off64_t d) + { return __pread64(a,b,c,d); } +inline ssize_t pwrite(int a, const void *b, size_t c, off64_t d) + { return __pwrite64(a,b,c,d); } +# endif /* __cplusplus && _APP32_64BIT_OFF_T */ + +#endif /* not _KERNEL */ + + +/* Symbolic constants */ + +#if defined(_INCLUDE_POSIX_SOURCE) || defined(_INCLUDE_POSIX2_SOURCE) + +/* Symbolic constants for the access() function */ +/* These must match the values found in */ +# ifndef R_OK +# define R_OK 4 /* Test for read permission */ +# define W_OK 2 /* Test for write permission */ +# define X_OK 1 /* Test for execute (search) permission */ +# define F_OK 0 /* Test for existence of file */ +# endif /* R_OK */ + +/* Symbolic constants for the lseek() function */ +# ifndef SEEK_SET +# define SEEK_SET 0 /* Set file pointer to "offset" */ +# define SEEK_CUR 1 /* Set file pointer to current plus "offset" */ +# define SEEK_END 2 /* Set file pointer to EOF plus "offset" */ +# endif /* SEEK_SET */ + +/* Versions of POSIX.1 we support */ +# define _POSIX1_VERSION_88 198808L /* We support POSIX.1-1988 */ +# define _POSIX1_VERSION_90 199009L /* We support POSIX.1-1990 */ +# define _POSIX1_VERSION_93 199309L /* We support POSIX.1b-1993 */ +# define _POSIX1_VERSION_95 199506L /* We support POSIX.1-1995 */ +# define _POSIX1_VERSION_01 200112L /* We support POSIX.1-2001 */ + +# ifdef _POSIX1_1988 +# define _POSIX_VERSION _POSIX1_VERSION_88 +# else /* not _POSIX1_1988 */ +# if !defined(_POSIX_C_SOURCE) || (_POSIX_C_SOURCE < 199309L) +# define _POSIX_VERSION _POSIX1_VERSION_90 +# else /* _POSIX_C_SOURCE && _POSIX_C_SOURCE >= 199309L */ +# if _POSIX_C_SOURCE < 199506L +# define _POSIX_VERSION _POSIX1_VERSION_93 +# else /* _POSIX_C_SOURCE >= 199506L */ +# if _POSIX_C_SOURCE < 200112L +# define _POSIX_VERSION _POSIX1_VERSION_95 +# else /* _POSIX_C_SOURCE >= 200112L */ +# define _POSIX_VERSION _POSIX1_VERSION_01 +# endif /* _POSIX_C_SOURCE < 200112L */ +# endif /* _POSIX_C_SOURCE < 199506L */ +# endif /* _POSIX_C_SOURCE && _POSIX_C_SOURCE >= 199309L */ +# endif /* not _POSIX1_1988 */ + +# define STDIN_FILENO 0 +# define STDOUT_FILENO 1 +# define STDERR_FILENO 2 + +/* Compile-time symbolic constants */ +# define _POSIX_SAVED_IDS 1 /* If defined, each process has a + * saved set-user-ID and a saved + * set_group-ID + */ +# define _POSIX_JOB_CONTROL 2 /* If defined, it indicates that + the implementation supports job + control */ +# define _POSIX_VDISABLE 0xff /* Character which disables local + TTY control character functions */ +/* All the following macros are defined as 1 for Unix95 environment and 200112 for others */ +#if defined(_INCLUDE_XOPEN_SOURCE_600) +# define _POSIX_PRIORITY_SCHEDULING 200112L /* If defined, POSIX.1b Priority + Scheduler extensions are + supported */ +# define _POSIX_TIMERS 200112L /* If defined, POSIX.1b Clocks & Timers + extensions are supported */ +# define _POSIX_SEMAPHORES 200112L /* If defined, POSIX.1b Semaphores + are supported */ +# define _POSIX_SYNCHRONIZED_IO 200112L /* If defined, POSIX.1b Synchronized + IO option is supported */ +# define _POSIX_FSYNC 200112L /* If defined, POSIX.1b File + Synchronization option is + supported. Must be defined if + _POSIX_SYNCHRONIZED_IO is */ +# define _POSIX_ASYNCHRONOUS_IO 200112L /* If defined, POSIX.1b Asynchronous + IO option is supported */ +# define _POSIX_MEMLOCK 200112L /* If defined, POSIX.1b mlockall and + munlockall are supported */ +# define _POSIX_MEMLOCK_RANGE 200112L /* If defined, POSIX.1b mlock and + munlock are supported */ +# define _POSIX_SHARED_MEMORY_OBJECTS 200112L /* If defined, POSIX.1b shm_open and + shm_unlink are supported */ +# define _POSIX_REALTIME_SIGNALS 200112L /* If defined, POSIX.1b Realtime + Signals extension option + is supported */ +# define _POSIX_MESSAGE_PASSING 200112L /* if defined, POSIX.1b Message Passing + extensions are supported */ + +# define _POSIX_THREAD_ATTR_STACKADDR 200112L /* if defined, thread stack address + attribute option is supported */ +# define _POSIX_THREAD_ATTR_STACKSIZE 200112L /* if defined, thread stack size + attribute option is supported */ +# define _POSIX_THREAD_PROCESS_SHARED 200112L /* if defined, process-shared + synchronization is supported */ +# define _POSIX_THREAD_SAFE_FUNCTIONS 200112L /* if defined, thread-safe + functions are supported */ +# define _POSIX_THREADS 200112L /* Base pthread functions are supported */ + +# define _POSIX_BARRIERS -1 +# define _POSIX_CLOCK_SELECTION -1 +# define _POSIX_IPV6 200112L +# define _POSIX_MONOTONIC_CLOCK -1 +# define _POSIX_RAW_SOCKETS -1 +# define _POSIX_READER_WRITER_LOCKS 200112L +# define _POSIX_SPAWN -1 +# define _POSIX_SPIN_LOCKS -1 +# define _POSIX_TIMEOUTS -1 + +# define _POSIX_ADVISORY_INFO -1 +# define _POSIX_CPUTIME -1 +# define _POSIX_THREAD_CPUTIME -1 +# define _POSIX_MAPPED_FILES 200112L +# define _POSIX_MEMORY_PROTECTION 200112L +# define _POSIX_TYPED_MEMORY_OBJECTS -1 + +# define _POSIX_PRIORITIZED_IO -1 +# define _POSIX_SPORADIC_SERVER -1 +# define _POSIX_THREAD_PRIO_PROTECT -1 +# define _POSIX_THREAD_PRIO_INHERIT -1 +# define _POSIX_THREAD_SPORADIC_SERVER -1 + +/* Constants for tracing option */ +# define _POSIX_TRACE -1 +# define _POSIX_TRACE_EVENT_FILTER -1 +# define _POSIX_TRACE_INHERIT -1 +# define _POSIX_TRACE_LOG -1 + +/* Constants for the Batch Environment Services and Utilities option */ +# define _POSIX2_PBS -1 +# define _POSIX2_PBS_ACCOUNTING -1 +# define _POSIX2_PBS_CHECKPOINT -1 +# define _POSIX2_PBS_LOCATE -1 +# define _POSIX2_PBS_MESSAGE -1 +# define _POSIX2_PBS_TRACK -1 + +# define _POSIX_REGEXP 1 /* Supports POSIX Regular Expressions */ +# define _POSIX_SHELL 1 /* Supports POSIX shell */ + +#else + +# define _POSIX_PRIORITY_SCHEDULING 1 /* If defined, POSIX.1b Priority + Scheduler extensions are + supported */ +# define _POSIX_TIMERS 1 /* If defined, POSIX.1b Clocks & Timers + extensions are supported */ +# define _POSIX_SEMAPHORES 1 /* If defined, POSIX.1b Semaphores + are supported */ +# define _POSIX_SYNCHRONIZED_IO 1 /* If defined, POSIX.1b Synchronized + IO option is supported */ +# define _POSIX_FSYNC 1 /* If defined, POSIX.1b File + Synchronization option is + supported. Must be defined if + _POSIX_SYNCHRONIZED_IO is */ +# define _POSIX_ASYNCHRONOUS_IO 1 /* If defined, POSIX.1b Asynchronous + IO option is supported */ +# define _POSIX_MEMLOCK 1 /* If defined, POSIX.1b mlockall and + munlockall are supported */ +# define _POSIX_MEMLOCK_RANGE 1 /* If defined, POSIX.1b mlock and + munlock are supported */ +# define _POSIX_SHARED_MEMORY_OBJECTS 1 /* If defined, POSIX.1b shm_open and + shm_unlink are supported */ +# define _POSIX_REALTIME_SIGNALS 1 /* If defined, POSIX.1b Realtime + Signals extension option + is supported */ +# define _POSIX_MESSAGE_PASSING 1 /* if defined, POSIX.1b Message Passing + extensions are supported */ + +/* Added for POSIX.1c (threads extensions) */ +# define _POSIX_THREAD_ATTR_STACKADDR 1 /* if defined, thread stack address + attribute option is supported */ +# define _POSIX_THREAD_ATTR_STACKSIZE 1 /* if defined, thread stack size + attribute option is supported */ +# define _POSIX_THREAD_PROCESS_SHARED 1 /* if defined, process-shared + synchronization is supported */ +# define _POSIX_THREAD_SAFE_FUNCTIONS 1 /* if defined, thread-safe + functions are supported */ +# define _POSIX_THREADS 1 /* Base pthread functions are supported */ + +#endif + +# ifdef _INCLUDE_XOPEN_SOURCE_PRE_600 +# define _POSIX_THREAD_PRIORITY_SCHEDULING 1 /* thread execution + scheduling is supported */ +# else +# define _POSIX_THREAD_PRIORITY_SCHEDULING -1 +# endif /*_INCLUDE_XOPEN_SOURCE_PRE_600 */ + +/* _POSIX_CHOWN_RESTRICTED, _POSIX_NO_TRUNC and _POSIX_SYNC_IO are not + * defined here since they are pathname-dependent. Use the pathconf() or + * fpathconf() functions to query for these values. + */ + + +/* Symbolic constants for sysconf() variables defined by POSIX.1-1988: 0-7 */ + +# define _SC_ARG_MAX 0 /* ARG_MAX: Max length of argument to exec() + including environment data */ +# define _SC_CHILD_MAX 1 /* CHILD_MAX: Max # of processes per userid */ +# define _SC_CLK_TCK 2 /* Number of clock ticks per second */ +# define _SC_NGROUPS_MAX 3 /* NGROUPS_MAX: Max # of simultaneous + supplementary group IDs per process */ +# define _SC_OPEN_MAX 4 /* OPEN_MAX: Max # of files that one process + can have open at any one time */ +# define _SC_JOB_CONTROL 5 /* _POSIX_JOB_CONTROL: 1 iff supported */ +# define _SC_SAVED_IDS 6 /* _POSIX_SAVED_IDS: 1 iff supported */ +# define _SC_1_VERSION_88 7 /* _POSIX_VERSION: Date of POSIX.1-1988 */ + +/* Symbolic constants for sysconf() variables added by POSIX.1-1990: 100-199 */ + +# define _SC_STREAM_MAX 100 /* STREAM_MAX: Max # of open stdio FILEs */ +# define _SC_TZNAME_MAX 101 /* TZNAME_MAX: Max length of timezone name */ +# define _SC_1_VERSION_90 102 /* _POSIX_VERSION: Date of POSIX.1-1990 */ +# define _SC_1_VERSION_93 103 /* _POSIX_VERSION: Date of POSIX.1b-1993 */ +# define _SC_1_VERSION_95 104 +# define _SC_1_VERSION_01 105 + +/* Pick appropriate value for _SC_VERSION symbolic constant */ + +# if (_POSIX_VERSION == _POSIX1_VERSION_88) +# define _SC_VERSION _SC_1_VERSION_88 +# else +# if (_POSIX_VERSION == _POSIX1_VERSION_90) +# define _SC_VERSION _SC_1_VERSION_90 +# else +# if (_POSIX_VERSION == _POSIX1_VERSION_93) +# define _SC_VERSION _SC_1_VERSION_93 +# else +# if (_POSIX_VERSION == _POSIX1_VERSION_95) +# define _SC_VERSION _SC_1_VERSION_95 +# else +# define _SC_VERSION _SC_1_VERSION_01 +# endif +# endif +# endif +# endif + +/* Symbolic constants for sysconf() variables added by POSIX.2: 200-299 */ + +# define _SC_BC_BASE_MAX 200 /* largest ibase & obase for bc */ +# define _SC_BC_DIM_MAX 201 /* max array elements for bc */ +# define _SC_BC_SCALE_MAX 202 /* max scale value for bc */ +# define _SC_EXPR_NEST_MAX 204 /* max nesting of (...) for expr */ +# define _SC_LINE_MAX 205 /* max length in bytes of input line */ +# define _SC_RE_DUP_MAX 207 /* max regular expressions permitted */ +# define _SC_2_VERSION 211 /* Current version of POSIX.2 */ +# define _SC_2_C_BIND 212 /* C Language Bindings Option */ +# define _SC_2_C_DEV 213 /* C Development Utilities Option */ +# define _SC_2_FORT_DEV 214 /* FORTRAN Dev. Utilities Option */ +# define _SC_2_SW_DEV 215 /* Software Dev. Utilities Option */ +# define _SC_2_C_VERSION 216 /* version of POSIX.2 CLB supported */ +# define _SC_2_CHAR_TERM 217 /* termianls exist where vi works */ +# define _SC_2_FORT_RUN 218 /* FORTRAN Runtime Utilities Option */ +# define _SC_2_LOCALEDEF 219 /* localedef(1M) can create locales */ +# define _SC_2_UPE 220 /* User Portability Utilities Option */ +# define _SC_BC_STRING_MAX 221 /* max scale value for bc */ +# define _SC_COLL_WEIGHTS_MAX 222 /* max collation weights in locale */ + + /* The following are obsolete and will be removed in a future release */ +# define _SC_COLL_ELEM_MAX 203 /* max bytes in collation element */ +# define _SC_PASTE_FILES_MAX 206 /* max file operands for paste */ +# define _SC_SED_PATTERN_MAX 208 /* max bytes of pattern space for sed */ +# define _SC_SENDTO_MAX 209 /* max bytes of message for sendto */ +# define _SC_SORT_LINE_MAX 210 /* max bytes of input line for sort */ + +/* Symbolic constants for sysconf() variables added by POSIX.4: 400-499 */ +# define _SC_TIMER_MAX 400 /* max number of timers per process */ +# define _SC_FSYNC 401 /* yes: POSIX.1b File Synchronization */ +# define _SC_SYNCHRONIZED_IO 402 /* yes: POSIX.1b Synchronized IO */ +# define _SC_PRIORITY_SCHEDULING 403 /* Priority scheduling supported */ +# define _SC_TIMERS 404 /* POSIX.1b Clocks and Timers supported*/ +# define _SC_DELAYTIMER_MAX 405 /* max timer overrun count */ +/* these following POSIX.4 constants represent unsupported functionality */ +# define _SC_ASYNCHRONOUS_IO 406 /* POSIX.1b asynchronous I/O supported */ +# define _SC_MAPPED_FILES 407 /* POSIX.1b mapped files supported */ +# define _SC_MEMLOCK 408 /* POSIX.1b memory locking supported */ +# define _SC_MEMLOCK_RANGE 409 /* POSIX.1b memory range locking */ +# define _SC_MEMORY_PROTECTION 410 /* POSIX.1b memory protection supported*/ +# define _SC_MESSAGE_PASSING 411 /* POSIX.1b message queues supported */ +# define _SC_PRIORITIZED_IO 412 /* POSIX.1b prioritized I/O supported */ +# define _SC_REALTIME_SIGNALS 413 /* POSIX.1b realtime signals supported */ +# define _SC_SEMAPHORES 414 /* POSIX.1b semaphores supported */ +# define _SC_SHARED_MEMORY_OBJECTS 415 /* POSIX.1b shared memory supported */ + +# define _SC_AIO_LISTIO_MAX 416 /* max I/O ops in a list I/O call */ +# define _SC_AIO_MAX 417 /* max outstanding async I/O ops */ +# define _SC_AIO_PRIO_DELTA_MAX 418 /* max aio/scheduling prio delta */ +# define _SC_MQ_OPEN_MAX 419 /* max open msg queues per process */ +# define _SC_MQ_PRIO_MAX 420 /* max different message priorities */ +# define _SC_RTSIG_MAX 421 /* # of realtime signals */ +# define _SC_SEM_NSEMS_MAX 422 /* max open semaphores per process */ +# define _SC_SEM_VALUE_MAX 423 /* max semaphore value */ +# define _SC_SIGQUEUE_MAX 424 /* max queued signals pending/sender */ + +/* Symbolic constants for sysconf() variables added by POSIX.1c (threads) */ +# define _SC_THREAD_DESTRUCTOR_ITERATIONS 430 /* PTHREAD_DESTRUCTOR_ITERATIONS: + max trys to destroy thread- + specific data on thrd exit */ +# define _SC_THREAD_KEYS_MAX 431 /* PTHREAD_KEYS_MAX: max num data + keys per proc */ +# define _SC_THREAD_STACK_MIN 432 /* PTHREAD_STACK_MIN: min size of + thread stack */ +# define _SC_THREAD_THREADS_MAX 433 /* PTHREAD_THREADS_MAX: max threads + per proc */ + +# define _SC_THREADS 434 /* _POSIX_THREADS: + 1 iff POSIX threads supported */ +# define _SC_THREAD_ATTR_STACKADDR 435 /* _POSIX_THREAD_ATTR_STACKADDR: + 1 iff stack address attribute + supported */ +# define _SC_THREAD_ATTR_STACKSIZE 436 /* _POSIX_THREAD_ATTR_STACKSIZE: + 1 iff stack size attribute + supported */ +# define _SC_THREAD_PRIORITY_SCHEDULING 437 /*_POSIX_THREAD_PRIORITY_SCHEDULING + 1 iff thread execution + scheduling supported */ +# define _SC_THREAD_PRIO_INHERIT 438 /* _POSIX_THREAD_PRIO_INHERIT: + 1 iff priority inheritance is + supported */ +# define _SC_THREAD_PRIO_PROTECT 439 /* _POSIX_THREAD_PRIO_PROTECT: + 1 iff priority protection is + supported */ +# define _SC_THREAD_PROCESS_SHARED 440 /* _POSIX_THREAD_PROCESS_SHARED: + 1 iff process-shared + synchronization is supported */ +# define _SC_THREAD_SAFE_FUNCTIONS 441 /* _POSIX_THREAD_SAFE_FUNCTIONS: + 1 iff thread-safe functions are + supported */ +# define _SC_GETGR_R_SIZE_MAX 442 /* Maximum size of getgrgid_r() and + and getgrnam_r() data buffers */ +# define _SC_GETPW_R_SIZE_MAX 443 /* Maximum size of getpwuid_r() and + and getpwnam_r() data buffers */ +# define _SC_LOGIN_NAME_MAX 444 /* Value of LOGIN_NAME_MAX */ +# define _SC_TTY_NAME_MAX 445 /* Value of TTY_NAME_MAX */ +# define _SC_CACHE_LINE_SIZE 446 /* Size of Cache line in bytes*/ +# define _SC_I_CACHE_SIZE 447 /* Size of I-Cache in bytes */ +# define _SC_D_CACHE_SIZE 448 /* Size of D-Cache in bytes */ +# define _SC_I_CACHE_LINE_SIZE 449 /* Size of I-Cache line in bytes */ +# define _SC_D_CACHE_LINE_SIZE 450 /* Size of D-Cache line in bytes */ +# define _SC_I_CACHE_WT 451 /* 0 means write-back I-Cache, + 1 means write-through I-Cache */ +# define _SC_D_CACHE_WT 452 /* 0 for write-back D-Cache, + 1 for write-through D-Cache */ +# define _SC_I_CACHE_CST 453 /* 0 means I-Cache is not issuing coherent + operations, 1 means I-Cache is issuing + coherent operations */ +# define _SC_D_CACHE_CST 454 /* 0 means D-Cache is not issuing cohere + operations, 1 means D-Cache is issuing + coherent operations */ +# define _SC_I_CACHE_F_SEL 455 /* tells software how to flush a range + of address from the I_cache and has + following meaning: + 0 - Both FIC and FDC must be used + 1 - Only need FDC + 2 - Only need FIC + 3 - Either FIC or FDC may be used */ +# define _SC_D_CACHE_F_SEL 456 /* tells software how to flush a range + of address from the D_cache and has + following meaning: + 0 - Both FIC and FDC must be used + 1 - Only need FDC + 2 - Only need FIC + 3 - Either FIC or FDC may be used*/ +# define _SC_I_CACHE_LOOP 457 /* intended for set-associative caches. + It is used to force the FDCE + instruction to be executed multiple + times with the same address. Note + that when it is 1, software can + optimize out the inner loop of the C + routine. */ +# define _SC_D_CACHE_LOOP 458 /* same as _SC_I_CACHE_LOOP */ + +/* Symbolic constants for sysconf() variables added by POSIX.1,2003: 500-599 */ +# define _SC_2_PBS 500 +# define _SC_2_PBS_ACCOUNTING 501 +# define _SC_2_PBS_CHECKPOINT 502 +# define _SC_2_PBS_LOCATE 503 +# define _SC_2_PBS_MESSAGE 504 +# define _SC_2_PBS_TRACK 505 +# define _SC_REGEXP 506 +# define _SC_SHELL 507 +# define _SC_HOST_NAME_MAX 508 +# define _SC_SYMLOOP_MAX 509 +# define _SC_ADVISORY_INFO 510 +# define _SC_BARRIERS 511 +# define _SC_CLOCK_SELECTION 512 +# define _SC_CPUTIME 513 +# define _SC_IPV6 515 +# define _SC_MONOTONIC_CLOCK 516 +# define _SC_RAW_SOCKETS 518 +# define _SC_READER_WRITER_LOCKS 519 +# define _SC_SPAWN 520 +# define _SC_SPIN_LOCKS 521 +# define _SC_SPORADIC_SERVER 522 +# define _SC_THREAD_CPUTIME 523 +# define _SC_THREAD_SPORADIC_SERVER 524 +# define _SC_TIMEOUTS 525 +# define _SC_TRACE 526 +# define _SC_TRACE_EVENT_FILTER 527 +# define _SC_TRACE_INHERIT 528 +# define _SC_TRACE_LOG 529 +# define _SC_TYPED_MEMORY_OBJECTS 530 +# define _SC_XOPEN_REALTIME 531 +# define _SC_XOPEN_REALTIME_THREADS 532 + +/* Symbolic constants for sysconf() variables defined by X/Open: 2000-2999 */ + +# define _SC_CLOCKS_PER_SEC 2000 /* CLOCKS_PER_SEC: Units/sec of clock() */ +# define _SC_XPG3_VERSION 8 /* 3 */ +# define _SC_XPG4_VERSION 2001 /* 4 */ +# define _SC_PASS_MAX 9 /* Max # of bytes in password */ +# define _SC_XOPEN_CRYPT 2002 /* Encryption feature group supported */ +# define _SC_XOPEN_ENH_I18N 2003 /* Enhanced I18N feature group " */ +# define _SC_XOPEN_SHM 2004 /* Shared memory feature group " */ +# ifdef _XPG3 +# define _SC_XOPEN_VERSION _SC_XPG3_VERSION /* Issue of XPG supported */ +# else /* not _XPG3 */ +# define _SC_XOPEN_VERSION _SC_XPG4_VERSION /* Issue of XPG supported */ +# endif /* not _XPG3 */ + +/* Symbolic constants for sysconf() variables defined by XPG5 */ + +# define _SC_XBS5_ILP32_OFF32 2005 /* 32-bit int, long, pointer and off_t */ +# define _SC_XBS5_ILP32_OFFBIG 2006 /* 32-bit int, long, pointer, and 64-bit off_t */ +# define _SC_XBS5_LP64_OFF64 2007 /* 32-bit int, 64-bit long, pointer, off_t */ +# define _SC_XBS5_LPBIG_OFFBIG 2008 /* at least 32-bit int, at least 64-bit long, pointer, off_t */ + +/* Symbolic constants for sysconf() variables defined for UNIX 2003 */ +# define _SC_XOPEN_STREAMS 2009 +# define _SC_XOPEN_LEGACY 2010 + +# define _SC_V6_ILP32_OFF32 _SC_XBS5_ILP32_OFF32 +# define _SC_V6_ILP32_OFFBIG _SC_XBS5_ILP32_OFFBIG +# define _SC_V6_LP64_OFF64 _SC_XBS5_LP64_OFF64 +# define _SC_V6_LPBIG_OFFBIG _SC_XBS5_LPBIG_OFFBIG + +# define _SC_SS_REPL_MAX 2011 +# define _SC_TRACE_EVENT_NAME_MAX 2012 +# define _SC_TRACE_NAME_MAX 2013 +# define _SC_TRACE_SYS_MAX 2014 +# define _SC_TRACE_USER_EVENT_MAX 2015 + +/* Symbolic constants for sysconf() variables defined by OSF: 3000-3999 */ + +# define _SC_AES_OS_VERSION 3000 /* AES_OS_VERSION: Version of OSF/AES OS */ +# define _SC_PAGE_SIZE 3001 /* PAGE_SIZE: Software page size */ +# define _SC_ATEXIT_MAX 3002 /* ATEXIT_MAX: Max # of atexit() funcs */ + +/* Symbolic constants for sysconf() variables defined by SVID/3 */ +# define _SC_PAGESIZE _SC_PAGE_SIZE + +/* Symbolic constants for sysconf() variables defined by HP-UX: 10000-19999 */ + +# define _SC_SECURITY_CLASS 10000 /* SECURITY_CLASS: DoD security level */ +# define _SC_CPU_VERSION 10001 /* CPU type this program is running on */ +# define _SC_IO_TYPE 10002 /* I/O system type this system supports */ +# define _SC_MSEM_LOCKID 10003 /* msemaphore lock unique identifier */ +# define _SC_MCAS_OFFSET 10004 /* Offset on gateway page of mcas_util() */ +# define _SC_CPU_KEYBITS1 10005 /* hardware key bit information */ +# define _SC_PROC_RSRC_MGR 10006 /* Process Resource Manager is configured */ +# define _SC_SOFTPOWER 10007 /* Soft Power Switch Hardware exists */ +# define _SC_EXEC_INTERPRETER_LENGTH 10008 /* for '#!' scripts, inclusive */ +# define _SC_SLVM_MAXNODES 10009 /* Max num of nodes supported by SLVM */ +# define _SC_SIGRTMIN 10010 /* First POSIX.4 Realtime Signal */ +# define _SC_SIGRTMAX 10011 /* Last POSIX.4 Realtime Signal */ +# define _SC_LIBC_VERSION 10012 /* Libc version */ +# define _SC_KERNEL_BITS 10013 /* running kernel is 32 or 64bit */ +# define _SC_KERNEL_IS_BIGENDIAN 10014 /* indicates kernel "big-endian" */ +# define _SC_HW_32_64_CAPABLE 10015 /* indicates whether h/w is capable + of running 32bit and/or 64bit OS */ +# define _SC_INT_MIN 10016 /* minimum value an object of type int can hold */ +# define _SC_INT_MAX 10017 /* maximum value an object of type int can hold */ +# define _SC_LONG_MIN 10018 /* minimum value an object of type long can hold */ +# define _SC_LONG_MAX 10019 /* maximum value an object of type long can hold */ +# define _SC_SSIZE_MAX 10020 /* maximum value an object of type ssize_t can hold */ +# define _SC_WORD_BIT 10021 /* number of bits in a word */ +# define _SC_LONG_BIT 10022 /* number of bits in a long */ +# define _SC_CPU_CHIP_TYPE 10023 /* encoded CPU chip type from PDC */ +# define _SC_CCNUMA_PM 10024 /* CCNUMA Programming Model Exts Active */ +# define _SC_CCNUMA_SUPPORT 10025 /* CCNUMA supported platform */ +# define _SC_IPMI_INTERFACE 10026 /* ipmi interface type */ +# define _SC_SPROFIL_MAX 10027 /* max number of profiled regions in + sprofil system call */ +# define _SC_NUM_CPUS 10028 /* number of cpus in use */ +# define _SC_MEM_MBYTES 10029 /* Mbytes of memory */ + +/* reserve 10030 for private use */ + +# define _SC_PSET_RTE_SUPPORT 10031 /* RTE PSets Supported */ +# define _SC_RTE_SUPPORT 10032 /* RTE Supported */ + +# define _SC_HG_SUPPORT 10034 /* HG (Project Mercury) supported */ +# define _SC_INIT_PROCESS_ID 10035 /* PID of the INIT process */ +# define _SC_SWAPPER_PROCESS_ID 10036 /* PID of the SWAPPER process */ +# define _SC_VHAND_PROCESS_ID 10037 /* PID of the VHAND process */ +# define _SC_HOST_NAME_MAX_2 10038 /* duplicate of _SC_HOST_NAME_MAX, + for compatibility with 11.23 0409 */ +# define _SC_SCALABLE_INIT 10039 /* Scalable init code is present */ + +# define _SC_HT_CAPABLE 10040 /* The hardware is capable of + hyperthread */ +# define _SC_HT_ENABLED 10041 /* The hardware is hyperthread enabled */ + +/* Macro to check if numeric username is enabled */ +# define _SC_EXTENDED_LOGIN_NAME 10042 +# define _SC_MINCORE 10043 /* mincore() system call support */ + + +# define _SC_CELL_OLA_SUPPORT 11001 /* OS supports Online Cell Addition */ +# define _SC_CELL_OLD_SUPPORT 11002 /* OS supports Online Cell Deletion */ +# define _SC_CPU_OLA_SUPPORT 11003 /* OS supports Online CPU Addition */ +# define _SC_CPU_OLD_SUPPORT 11004 /* OS supports Online CPU Deletion */ +# define _SC_MEM_OLA_SUPPORT 11005 /* OS supports Online Memory Addition */ +# define _SC_MEM_OLD_SUPPORT 11006 /* OS supports Online Memory Deletion */ +# define _SC_LORA_MODE 11007 /* NUMA mode for the partition */ + +# define _SC_P2P 19500 /* p2p bcopy feature */ + +/* value(s) returned by sysconf(_SC_P2P) */ + +# define _SC_P2P_ENABLED 0x1 +# define _SC_P2P_DATA_MOVER 0x2 + +# define _SC_GANG_SCHED 19501 /* gang scheduler feature */ + +# define _SC_PSET_SUPPORT 19502 /* Processor Set functionality support */ + +/* 20000-20999 reserved for private use */ + +/* Symbolic constants for pathconf() defined by POSIX.1: 0-99 */ +# define _PC_LINK_MAX 0 /* LINK_MAX: Max # of links to a single + file */ +# define _PC_MAX_CANON 1 /* MAX_CANON: Max # of bytes in a terminal + canonical input line */ +# define _PC_MAX_INPUT 2 /* MAX_INPUT: Max # of bytes allowed in + a terminal input queue */ +# define _PC_NAME_MAX 3 /* NAME_MAX: Max # of bytes in a filename */ + +# define _PC_PATH_MAX 4 /* PATH_MAX: Max # of bytes in a pathname */ + +# define _PC_PIPE_BUF 5 /* PIPE_BUF: Max # of bytes for which pipe + writes are atomic */ +# define _PC_CHOWN_RESTRICTED 6 /* _POSIX_CHOWN_RESTRICTED: 1 iff only a + privileged process can use chown() */ +# define _PC_NO_TRUNC 7 /* _POSIX_NO_TRUNC: 1 iff an error is + detected when exceeding NAME_MAX */ +# define _PC_VDISABLE 8 /* _POSIX_VDISABLE: character setting which + disables TTY local editing characters */ + +/* Symbolic constants for pathconf() defined by POSIX.1b */ + +# define _PC_SYNC_IO 100 /* SYNC_IO: 1 iff Synchronized IO may be + performed for the associated file */ +# define _PC_ASYNC_IO 101 /* Async I/O may be performed on this fd */ +# define _PC_PRIO_IO 102 /* I/O Prioritization is done on this fd */ + +# define _PC_FILESIZEBITS 103 /* bits needed to represent file offset */ + +/* Symbolic constants for pathconf() defined by POSIX.1d or Unix2003 */ + +# define _PC_2_SYMLINKS 600 +# define _PC_ALLOC_SIZE_MIN 601 +# define _PC_REC_INCR_XFER_SIZE 602 +# define _PC_REC_MAX_XFER_SIZE 603 +# define _PC_REC_MIN_XFER_SIZE 604 +# define _PC_REC_XFER_ALIGN 605 +# define _PC_SYMLINK_MAX 606 + +#endif /* _INCLUDE_POSIX_SOURCE || _INCLUDE_POSIX2_SOURCE */ + +/* Issue(s) of X/Open Portability Guide we support */ + +#ifdef _INCLUDE_XOPEN_SOURCE + +# ifdef _XPG3 +# define _XOPEN_VERSION 3 +# else /* not _XPG3 */ +# define _XOPEN_VERSION 4 +# endif /* not _XPG3 */ + +# ifdef _XPG2 +# define _XOPEN_XPG2 1 +# else /* not _XPG2 */ +# ifdef _XPG3 +# define _XOPEN_XPG3 1 +# else /* not _XPG3 */ +# define _XOPEN_XPG4 1 +# endif /* not _XPG3 */ +# endif /* not _XPG2 */ + +# define _XOPEN_XCU_VERSION 3 /* X/Open Commands & Utilities */ + + /* XPG4 Feature Groups */ + +# define _XOPEN_CRYPT 1 /* Encryption and Decryption */ +# define _XOPEN_ENH_I18N 1 /* Enhanced Internationalization */ + /* _XOPEN_SHM is not defined because the Shared Memory routines can be + configured in and out of the system. See uxgen(1M) or config(1M). */ +# ifdef _INCLUDE_XOPEN_SOURCE_500 +# define _XOPEN_SHM 1 +# endif /* _INCLUDE_XOPEN_SOURCE_500 */ + +#endif /* _INCLUDE_XOPEN_SOURCE */ + + +/* Revision of AES OS we support */ + +#ifdef _INCLUDE_AES_SOURCE +# define _AES_OS_VERSION 1 +#endif /* _INCLUDE_AES_SOURCE */ + + +#ifdef _INCLUDE_POSIX2_SOURCE + +# define _POSIX2_VERSION_92 199209L /* We support POSIX.2-1992 */ +# define _POSIX2_VERSION_01 200112L /* We support POSIX.2-2001 */ + +/* Conformance and options for POSIX.2 */ +# ifdef _INCLUDE_XOPEN_SOURCE_PRE_600 +# define _POSIX2_C_VERSION _POSIX2_VERSION_92 +# ifdef _HPUX_SOURCE + /* IEEE POSIX.2-2001 base standard */ +# define _POSIX2_VERSION _POSIX2_VERSION_01 + /* IEEE POSIX.2-2001 C language binding */ +# define _SUPPORTED_POSIX2_OPTION _POSIX2_VERSION_01 +# else /* !_HPUX_SOURCE */ + /* IEEE POSIX.2-1992 base standard */ +# define _POSIX2_VERSION _POSIX2_VERSION_92 + /* IEEE POSIX.2-1992 C language binding */ +# define _SUPPORTED_POSIX2_OPTION 1L +# endif /* !_HPUX_SOURCE */ +# else /* !_INCLUDE_XOPEN_SOURCE_PRE_600 */ + /* IEEE POSIX.2-1992 base standard */ +# define _POSIX2_VERSION _POSIX2_VERSION_01 +# define _SUPPORTED_POSIX2_OPTION _POSIX2_VERSION_01 +# endif /* !_INCLUDE_XOPEN_SOURCE_PRE_600 */ + +/* c89 finds POSIX.2 funcs by default */ +# define _POSIX2_C_BIND _SUPPORTED_POSIX2_OPTION + +/* c89, lex, yacc, etc. are provided */ +# define _POSIX2_C_DEV _SUPPORTED_POSIX2_OPTION + +/* make, ar, etc. are provided */ +# define _POSIX2_SW_DEV _SUPPORTED_POSIX2_OPTION + +/* terminals exist where vi works */ +# define _POSIX2_CHAR_TERM _SUPPORTED_POSIX2_OPTION + +/* User Portability Utilities supported */ +# define _POSIX2_UPE _SUPPORTED_POSIX2_OPTION + +/* localedef(1M) can create locales */ +# define _POSIX2_LOCALEDEF _SUPPORTED_POSIX2_OPTION + +/* fort77 is not provided */ +# define _POSIX2_FORT_DEV -1L + +/* asa is not provided */ +# define _POSIX2_FORT_RUN -1L + + +/* Symbolic constants representing C-language compilation environments defined + * by XPG5 + */ +# define _XBS5_ILP32_OFF32 32 /* 32-bit int, long, pointer, off_t */ +# define _XBS5_ILP32_OFFBIG 32 /* 32-bit int, long, pointer, 64-bit off_t */ +# define _XBS5_LP64_OFF64 64 /* 32-bit int, 64-bit long, pointer, off_t */ +# define _XBS5_LPBIG_OFFBIG 64 /* 32-bit int, 64-bit long, pointer, off_t */ + +/* Symbolic constants for confstr() defined by POSIX.2: 200-299 */ + +# define _CS_PATH 200 /* Search path that finds all POSIX.2 utils */ + +/* Symbolic constants for confstr() defined by XPG5: 300-399 */ + +/* Initial compiler options, final compiler options, set of libraries and lint + * options for 32-bit int, long, pointer, off_t. + */ +# define _CS_XBS5_ILP32_OFF32_CFLAGS 300 +# define _CS_XBS5_ILP32_OFF32_LDFLAGS 301 +# define _CS_XBS5_ILP32_OFF32_LIBS 302 +# define _CS_XBS5_ILP32_OFF32_LINTFLAGS 303 + +/* Initial compiler options, final compiler options, set of libraries and lint + * options for 32-bit int, long, pointer, 64-bit off_t. + */ +# define _CS_XBS5_ILP32_OFFBIG_CFLAGS 304 +# define _CS_XBS5_ILP32_OFFBIG_LDFLAGS 305 +# define _CS_XBS5_ILP32_OFFBIG_LIBS 306 +# define _CS_XBS5_ILP32_OFFBIG_LINTFLAGS 307 + +/* Initial compiler options, final compiler options, set of libraries and lint + * options for 32-bit int, 64-bit long, pointer, off_t. + */ +# define _CS_XBS5_LP64_OFF64_CFLAGS 308 +# define _CS_XBS5_LP64_OFF64_LDFLAGS 309 +# define _CS_XBS5_LP64_OFF64_LIBS 310 +# define _CS_XBS5_LP64_OFF64_LINTFLAGS 311 + +/* Initial compiler options, final compiler options, set of libraries and lint + * options for an int type using at least 32-bits, and long, pointer, and off_t + * types using at least 64-bits. + */ +# define _CS_XBS5_LPBIG_OFFBIG_CFLAGS 312 +# define _CS_XBS5_LPBIG_OFFBIG_LDFLAGS 313 +# define _CS_XBS5_LPBIG_OFFBIG_LIBS 314 +# define _CS_XBS5_LPBIG_OFFBIG_LINTFLAGS 315 + +/* Symbolic constants for confstr() defined by Unix2003 : 700 - 799 */ + +/* Initial C99 compiler options, final C99 compiler options, set of libraries + * options for 32-bit int, long, pointer, off_t. + */ +# define _CS_POSIX_V6_ILP32_OFF32_CFLAGS 700 +# define _CS_POSIX_V6_ILP32_OFF32_LDFLAGS 701 +# define _CS_POSIX_V6_ILP32_OFF32_LIBS 702 + +/* Initial C99 compiler options, final C99 compiler options, set of libraries + * options for 32-bit int, long, pointer, 64-bit off_t. + */ +# define _CS_POSIX_V6_ILP32_OFFBIG_CFLAGS 704 +# define _CS_POSIX_V6_ILP32_OFFBIG_LDFLAGS 705 +# define _CS_POSIX_V6_ILP32_OFFBIG_LIBS 706 + +/* Initial C99 compiler options, final C99 compiler options, set of libraries + * options for 32-bit int, 64-bit long, pointer, off_t. + */ +# define _CS_POSIX_V6_LP64_OFF64_CFLAGS 708 +# define _CS_POSIX_V6_LP64_OFF64_LDFLAGS 709 +# define _CS_POSIX_V6_LP64_OFF64_LIBS 710 + +/* Initial compiler options, final compiler options, set of libraries and lint + * options for an int type using at least 32-bits, and long, pointer, and off_t + * types using at least 64-bits. + */ +# define _CS_POSIX_V6_LPBIG_OFFBIG_CFLAGS 712 +# define _CS_POSIX_V6_LPBIG_OFFBIG_LDFLAGS 713 +# define _CS_POSIX_V6_LPBIG_OFFBIG_LIBS 714 + +# define _CS_POSIX_V6_WIDTH_RESTRICTED_ENVS 716 + +/* Symbolic constants for confstr() defined by HP-UX: 10000-19999 */ + +# define _CS_MACHINE_MODEL 10000 /* system model name */ +# define _CS_HW_CPU_SUPP_BITS 10001 /* OS configurations supported */ +# define _CS_KERNEL_BITS 10002 /* kernel running is "32" or "64" bits */ +# define _CS_MACHINE_IDENT 10003 /* Machine ID */ +# define _CS_PARTITION_IDENT 10004 /* Partition ID */ +# define _CS_MACHINE_SERIAL 10005 /* Machine serial number */ + +/* Symbolic constants for use with fnmatch() have been moved to */ + +#endif /* _INCLUDE_POSIX2_SOURCE */ + +# ifndef _XOPEN_UNIX +# define _XOPEN_UNIX -1 +# endif +# define _XOPEN_CURSES 1 + +#ifdef _INCLUDE_XOPEN_SOURCE_EXTENDED + +/* Symbolic constants for the "lockf" function: */ + +# define F_ULOCK 0 /* Unlock a previously locked region */ +# define F_LOCK 1 /* Lock a region for exclusive use */ +# define F_TLOCK 2 /* Test and lock a region for exclusive use */ +# define F_TEST 3 /* Test a region for a previous lock */ + +/* Symbolic constants for sysconf() variables defined by XPG4_EXTENDED */ +/* Continue with ones for XOPEN (2000-2999) */ + +# define _SC_IOV_MAX 2100 /* Max # of iovec structures for readv/writev */ +# define _SC_XOPEN_UNIX 2101 /* Whether or not XOPEN_UNIX is supported */ +#endif /* _INCLUDE_XOPEN_SOURCE_EXTENDED */ + +/* + * Define _XOPEN_VERSION symbolic constant for unix98 + * compliance. + */ +#ifdef _INCLUDE_XOPEN_SOURCE_500 +# undef _XOPEN_VERSION +# define _XOPEN_VERSION 500 +#endif /* _INCLUDE_XOPEN_SOURCE_500 */ +/* + * Define _XOPEN_VERSION symbolic constant for unix03 + * compliance. + */ +#if defined(_INCLUDE_XOPEN_SOURCE_600) +# undef _XOPEN_VERSION +# define _XOPEN_VERSION 600 +# define _XOPEN_STREAMS 1 +# define _XOPEN_REALTIME -1 +# define _XOPEN_LEGACY -1 +# define _POSIX_V6_ILP32_OFF32 32 +# define _POSIX_V6_ILP32_OFFBIG 32 +# define _POSIX_V6_LP64_OFF64 64 +# define _POSIX_V6_LPBIG_OFFBIG 64 +#endif /* _INCLUDE_XOPEN_SOURCE_600 */ + +#ifdef _INCLUDE_HPUX_SOURCE + +/* Symbolic constants for the passwd file and group file */ + +# define GF_PATH "/etc/group" /* Path name of the "group" file */ +# define PF_PATH "/etc/passwd" /* Path name of the "passwd" file */ +# define IN_PATH "/usr/include" /* Path name for <...> files */ + + +/* Path on which all POSIX.2 utilities can be found */ + +# define CS_PATH \ + "/usr/bin:/usr/ccs/bin:/opt/ansic/bin:/opt/langtools/bin:/opt/fortran/bin" + +# define CS_XBS5_ILP32_OFF32_CFLAGS "" +# define CS_XBS5_ILP32_OFF32_LDFLAGS "" +# define CS_XBS5_ILP32_OFF32_LIBS "" +# define CS_XBS5_ILP32_OFF32_LINTFLAGS "" +# define CS_XBS5_ILP32_OFFBIG_CFLAGS "-D_FILE_OFFSET_BITS=64" +# define CS_XBS5_ILP32_OFFBIG_LDFLAGS "" +# define CS_XBS5_ILP32_OFFBIG_LIBS "" +# define CS_XBS5_ILP32_OFFBIG_LINTFLAGS "-D_FILE_OFFSET_BITS=64" + +#if defined(__ia64) +# define CS_XBS5_LP64_OFF64_CFLAGS "+DD64" +# define CS_XBS5_LPBIG_OFFBIG_CFLAGS "+DD64" +#else +# define CS_XBS5_LP64_OFF64_CFLAGS "+DA2.0W" +# define CS_XBS5_LPBIG_OFFBIG_CFLAGS "+DA2.0W" +#endif + +# define CS_XBS5_LP64_OFF64_LDFLAGS "" +# define CS_XBS5_LP64_OFF64_LIBS "" +# define CS_XBS5_LP64_OFF64_LINTFLAGS "" +# define CS_XBS5_LPBIG_OFFBIG_LDFLAGS "" +# define CS_XBS5_LPBIG_OFFBIG_LIBS "" +# define CS_XBS5_LPBIG_OFFBIG_LINTFLAGS "" + +# define CS_POSIX_V6_ILP32_OFF32_CFLAGS "" +# define CS_POSIX_V6_ILP32_OFF32_LDFLAGS "" +# define CS_POSIX_V6_ILP32_OFF32_LIBS "" +# define CS_POSIX_V6_ILP32_OFFBIG_CFLAGS "-D_FILE_OFFSET_BITS=64" +# define CS_POSIX_V6_ILP32_OFFBIG_LDFLAGS "" +# define CS_POSIX_V6_ILP32_OFFBIG_LIBS "" +# define CS_POSIX_V6_LP64_OFF64_CFLAGS "+DD64" +# define CS_POSIX_V6_LP64_OFF64_LDFLAGS "" +# define CS_POSIX_V6_LP64_OFF64_LIBS "" +# define CS_POSIX_V6_LPBIG_OFFBIG_CFLAGS "+DD64" +# define CS_POSIX_V6_LPBIG_OFFBIG_LDFLAGS "" +# define CS_POSIX_V6_LPBIG_OFFBIG_LIBS "" +# define CS_POSIX_V6_WIDTH_RESTRICTED_ENVS "_POSIX_V6_ILP32_OFF32\n_POSIX_V6_ILP32_OFFBIG\n_POSIX_V6_LP64_OFF64\n_POSIX_V6_LPBIG_OFFBIG" + +/* Symbolic constants for values of sysconf(_SC_SECURITY_LEVEL) */ + +# define SEC_CLASS_NONE 0 /* default secure system */ +# define SEC_CLASS_C2 1 /* C2 level security */ +# define SEC_CLASS_B1 2 /* B1 level security */ + +/* Symbolic constants for values of sysconf(_SC_IO_TYPE) */ + +# define IO_TYPE_WSIO 01 +# define IO_TYPE_SIO 02 +# define IO_TYPE_CDIO 03 + +/* Symbolic constants for values of sysconf(_SC_CPU_KEYBITS1) */ + +#define HARITH 0x00000010 /* Halfword parallel add, subtract, average */ +#define HSHIFTADD 0x00000020 /* Halfword parallel shift-and-add */ + +/* Symbolic constants for values of sysconf(_SC_CPU_VERSION) */ +/* These are the same as the magic numbers defined in */ +/* Symbolic constants for values of sysconf(_SC_CPU_VERSION) + do not have to be monotonic. + Values from 0x0210 through 0x02ff have been reserved for + revisions of PA-RISC */ + +# define CPU_HP_MC68020 0x20C /* Motorola MC68020 */ +# define CPU_HP_MC68030 0x20D /* Motorola MC68030 */ +# define CPU_HP_MC68040 0x20E /* Motorola MC68040 */ +# define CPU_PA_RISC1_0 0x20B /* HP PA-RISC1.0 */ +# define CPU_PA_RISC1_1 0x210 /* HP PA-RISC1.1 */ +# define CPU_PA_RISC1_2 0x211 /* HP PA-RISC1.2 */ +# define CPU_PA_RISC2_0 0x214 /* HP PA-RISC2.0 */ +# define CPU_PA_RISC_MAX 0x2FF /* Maximum value for HP PA-RISC systems */ +# define CPU_IA64_ARCHREV_0 0x300 /* IA-64 archrev 0 */ + +/* Macro for detecting whether a given CPU version is an HP PA-RISC machine */ + +# define CPU_IS_PA_RISC(__x) \ + ((__x) == CPU_PA_RISC1_0 || \ + ((__x) >= CPU_PA_RISC1_1 && (__x) <= CPU_PA_RISC_MAX)) + +/* Macro for detecting whether a given CPU version is an HP MC680x0 machine */ + +# define CPU_IS_HP_MC68K(__x) \ + ((__x) == CPU_HP_MC68020 || \ + (__x) == CPU_HP_MC68030 || \ + (__x) == CPU_HP_MC68040) + + +/* Macros to interpret return value from sysconf(_SC_HW_32_64_CAPABLE) */ + +# define _SYSTEM_SUPPORTS_LP64OS(__x) ((__x) & 0x1) +# define _SYSTEM_SUPPORTS_ILP32OS(__x) ((__x) & 0x2) + +#ifndef _KERNEL +/* + * serialize system call + */ + /* Function prototype */ +#if defined(__ia64) && !defined(_LIBC) + /* pragmas needed to support -B protected */ +#pragma extern serialize +#endif /* __ia64 && ! _LIBC */ +extern int serialize __((int, pid_t)); + +#endif /* not _KERNEL */ + +#endif /* _INCLUDE_HPUX_SOURCE */ + +#ifdef _UNSUPPORTED + + /* + * NOTE: The following header file contains information specific + * to the internals of the HP-UX implementation. The contents of + * this header file are subject to change without notice. Such + * changes may affect source code, object code, or binary + * compatibility between releases of HP-UX. Code which uses + * the symbols contained within this header file is inherently + * non-portable (even between HP-UX implementations). + */ +# include <.unsupp/sys/_unistd.h> +#endif /* _UNSUPPORTED */ + +#endif /* _SYS_UNISTD_INCLUDED */ + diff --git a/spec/unit/processor_spec.rb b/spec/unit/processor_spec.rb index 8e884b7aae..14a22c6e6d 100755 --- a/spec/unit/processor_spec.rb +++ b/spec/unit/processor_spec.rb @@ -309,4 +309,100 @@ def self.lsdev_examples end end end + + describe "on HP-UX" do + def self.machinfo_examples + examples = [] + examples << [File.read(fixtures('hpux','machinfo','ia64-rx2620')), "Intel(R) Itanium 2 processor"] + examples << [File.read(fixtures('hpux','machinfo','ia64-rx6600')), "Intel(R) Itanium 2 9100 series processor (1.59 GHz, 18 MB)"] + examples << [File.read(fixtures('hpux','machinfo','ia64-rx8640')), "Intel(R) Itanium 2 9100 series"] + examples << [File.read(fixtures('hpux','machinfo','hppa-rp4440')), "PA-RISC 8800 processor (1000 MHz, 64 MB)"] + examples + end + + let(:ioscan) do + "Class I H/W Path Driver S/W State H/W Type Description\n" + + "===================================================================\n" + + "processor 0 0/120 processor CLAIMED PROCESSOR Processor\n" + + "processor 1 0/123 processor CLAIMED PROCESSOR Processor\n" + end + + describe "when machinfo is available" do + machinfo_examples.each_with_index do |example, i| + machinfo_example, expected_cpu = example + context "machinfo example ##{i}" do + before :each do + Facter.fact(:kernel).stubs(:value).returns("HP-UX") + Facter::Util::Processor.stubs(:ioscan).returns(ioscan) + Facter::Util::Processor.stubs(:machinfo).returns(machinfo_example) + Facter.collection.loader.load(:processor) + end + + %w{ 0 1 }.each do |j| + it "should find #{expected_cpu}" do + Facter.value("processor#{j}").should == expected_cpu + end + end + end + end + end + + def self.model_and_getconf_examples + examples = [] + examples << ["9000/800/L3000-5x", "sched.models_present", "unistd.h_present", "532", "616", "PA-RISC 8600 processor"] + examples << ["9000/800/L3000-5x", "", "unistd.h_present", "532", "616", "HP PA-RISC2.0 CHIP TYPE #616"] + examples << ["9000/800/L3000-5x", "", "", "532", "616", "CPU v532 CHIP TYPE #616"] + examples << ["ia64 hp server rx2660", "sched.models_present", "unistd.h_present", "768", "536936708", "IA-64 archrev 0 CHIP TYPE #536936708"] + examples << ["ia64 hp server rx2660", "", "unistd.h_present", "768", "536936708", "IA-64 archrev 0 CHIP TYPE #536936708"] + examples << ["ia64 hp server rx2660", "", "", "768", "536936708", "CPU v768 CHIP TYPE #536936708"] + examples + end + + sched_models = File.readlines(fixtures('hpux','sched.models')) + unistd_h = File.readlines(fixtures('hpux','unistd.h')) + + describe "when machinfo is not available" do + model_and_getconf_examples.each_with_index do |example, i| + model_example, sm, unistd, getconf_cpu_ver, getconf_chip_type, expected_cpu = example + context "and model and getconf example ##{i}" do + before :each do + Facter.fact(:kernel).stubs(:value).returns("HP-UX") + Facter::Util::Processor.stubs(:ioscan).returns(ioscan) + Facter::Util::Processor.stubs(:getconf_cpu_version).returns(getconf_cpu_ver) + Facter::Util::Processor.stubs(:getconf_cpu_chip_type).returns(getconf_chip_type) + Facter::Util::Processor.stubs(:machinfo).returns(nil) + Facter::Util::Processor.stubs(:model).returns(model_example) + end + + if unistd == "unistd.h_present" then + before :each do + Facter::Util::Processor.stubs(:read_unistd_h).returns(unistd_h) + end + else + before :each do + Facter::Util::Processor.stubs(:read_unistd_h).returns(nil) + end + end + + if sm == "sched.models_present" then + before :each do + Facter::Util::Processor.stubs(:read_sched_models).returns(sched_models) + Facter.collection.loader.load(:processor) + end + else + before :each do + Facter::Util::Processor.stubs(:read_sched_models).returns(nil) + Facter.collection.loader.load(:processor) + end + end + + %w{ 0 1 }.each do |j| + it "should find #{expected_cpu}" do + Facter.value("processor#{j}").should == expected_cpu + end + end + end + end + end + end end From 1d36e02a4ce6a81e798a3b8c24e1b8b1b1b016b8 Mon Sep 17 00:00:00 2001 From: Andrew Parker Date: Wed, 7 Nov 2012 15:05:42 -0800 Subject: [PATCH 1072/3753] (Maint) Properly mock SunOS in test for manufacturer Previously this test did not properly mock the kernl and hardwareisa facts, which caused it to actually run the code on solaris. Having the code actually run on solaris made it return an unexpected value and fail the test. This changes the mocking and should cause the test to start passing on solaris. --- spec/unit/util/manufacturer_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/unit/util/manufacturer_spec.rb b/spec/unit/util/manufacturer_spec.rb index 59fbf5ca24..0e4a8dc202 100755 --- a/spec/unit/util/manufacturer_spec.rb +++ b/spec/unit/util/manufacturer_spec.rb @@ -34,8 +34,9 @@ it "should not set manufacturer or productname if prtdiag output is nil" do # Stub kernel so we don't have windows fall through to its own mechanism Facter.fact(:kernel).stubs(:value).returns("SunOS") + Facter.fact(:hardwareisa).stubs(:value).returns("sparc") - Facter::Util::Resolution.stubs(:exec).returns(nil) + Facter::Util::Resolution.stubs(:exec).with(regexp_matches(/prtdiag/)).returns(nil) Facter::Manufacturer.prtdiag_sparc_find_system_info() Facter.value(:manufacturer).should be_nil Facter.value(:productname).should be_nil From e3da3c7b7f9b215929c6d7ec654e3577ff1697bb Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Fri, 9 Nov 2012 14:32:33 -0800 Subject: [PATCH 1073/3753] (#17493) Safer handling of EC2 'open' calls Previously, when facter (and therefore) facter was run on an openstack node, the metadata method did not protect against the 'open' method from raising exceptions. Since we don't know ahead of time what the name of the metadata ec2 fact(s) are, we have to add a rescue block outside of the setcode block. If an exception occurs, we warn, similar to how facter's built-in resolution mechanism warns about exceptions. Previously, the ec2_userdata fact performed the 'open' call outside of the setcode block, and only rescued one type of exception. This commit moves the 'open' call into the setcode block, which automatically rescues and warns if needed. As a result of this change, the ec2_userdata fact is not evaluated until actually requested, which simplifies the tests for the ec2 metadata fact(s). Based on code from Eric Sorenson --- lib/facter/ec2.rb | 10 ++++++---- spec/unit/ec2_spec.rb | 40 ++++++++++++++++++++++------------------ 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/lib/facter/ec2.rb b/lib/facter/ec2.rb index 98c957f7dc..e314d24762 100644 --- a/lib/facter/ec2.rb +++ b/lib/facter/ec2.rb @@ -14,13 +14,15 @@ def metadata(id = "") metadata(key) end end +rescue => details + Facter.warn "Could not retrieve ec2 metadata: #{details.message}" end def userdata() - begin - value = open("http://169.254.169.254/2008-02-01/user-data/").read.split - Facter.add(:ec2_userdata) { setcode { value } } - rescue OpenURI::HTTPError + Facter.add(:ec2_userdata) do + setcode do + open("http://169.254.169.254/2008-02-01/user-data/").read.split + end end end diff --git a/spec/unit/ec2_spec.rb b/spec/unit/ec2_spec.rb index 6211a0323b..431b666592 100755 --- a/spec/unit/ec2_spec.rb +++ b/spec/unit/ec2_spec.rb @@ -28,11 +28,6 @@ with("#{api_prefix}/2008-02-01/meta-data/foo"). at_least_once.returns(StringIO.new("bar")) - # No user-data - Object.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/user-data/"). - at_least_once.returns(StringIO.new("")) - Facter.collection.loader.load(:ec2) Facter.fact(:ec2_foo).value.should == "bar" end @@ -46,11 +41,6 @@ with("#{api_prefix}/2008-02-01/meta-data/foo"). at_least_once.returns(StringIO.new("bar\nbaz")) - # No user-data - Object.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/user-data/"). - at_least_once.returns(StringIO.new("")) - Facter.collection.loader.load(:ec2) Facter.fact(:ec2_foo).value.should == "bar,baz" end @@ -68,11 +58,6 @@ with("#{api_prefix}/2008-02-01/meta-data/foo/bar"). at_least_once.returns(StringIO.new("baz")) - # No user-data - Object.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/user-data/"). - at_least_once.returns(StringIO.new("")) - Facter.collection.loader.load(:ec2) Facter.fact(:ec2_foo_bar).value.should == "baz" end @@ -83,7 +68,7 @@ with("#{api_prefix}/2008-02-01/meta-data/"). at_least_once.returns(StringIO.new("")) - Object.any_instance.expects(:open). + Facter::Util::Resolution.any_instance.expects(:open). with("#{api_prefix}/2008-02-01/user-data/"). at_least_once.returns(StringIO.new("test")) @@ -109,7 +94,7 @@ with("#{api_prefix}/2008-02-01/meta-data/").\ at_least_once.returns(StringIO.new("")) - Object.any_instance.expects(:open).\ + Facter::Util::Resolution.any_instance.expects(:open).\ with("#{api_prefix}/2008-02-01/user-data/").\ at_least_once.returns(StringIO.new("test")) @@ -137,7 +122,7 @@ with("#{api_prefix}/2008-02-01/meta-data/").\ at_least_once.returns(StringIO.new("")) - Object.any_instance.expects(:open).\ + Facter::Util::Resolution.any_instance.expects(:open).\ with("#{api_prefix}/2008-02-01/user-data/").\ at_least_once.returns(StringIO.new("test")) @@ -146,6 +131,25 @@ Facter.fact(:ec2_userdata).value.should == ["test"] end + + it "should return nil if open fails" do + Facter.expects(:warn).with('Could not retrieve ec2 metadata: host unreachable').twice + Facter::Util::Resolution.any_instance.stubs(:warn) # do not pollute test output + + Object.any_instance.expects(:open). + with("#{api_prefix}/2008-02-01/meta-data/"). + at_least_once.raises(RuntimeError, 'host unreachable') + + Facter::Util::Resolution.any_instance.expects(:open). + with("#{api_prefix}/2008-02-01/user-data/"). + at_least_once.raises(RuntimeError, 'host unreachable') + + # Force a fact load + Facter.collection.loader.load(:ec2) + + Facter.fact(:ec2_userdata).value.should be_nil + end + end describe "when api connect test fails" do From b336259e77ff3dfa543fff867f09e73d014d1eeb Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Fri, 9 Nov 2012 14:33:11 -0800 Subject: [PATCH 1074/3753] (#17493) Merge rescue blocks There was no difference in behavior between the two rescue blocks, so this commit merges them together. Also some of the tests were expecting an exception to not occur, in which case it's just better to execute the code directly. --- lib/facter/util/ec2.rb | 6 ++---- spec/unit/util/ec2_spec.rb | 30 +++++++++--------------------- 2 files changed, 11 insertions(+), 25 deletions(-) diff --git a/lib/facter/util/ec2.rb b/lib/facter/util/ec2.rb index 618ac88d73..04668bde24 100644 --- a/lib/facter/util/ec2.rb +++ b/lib/facter/util/ec2.rb @@ -14,10 +14,8 @@ def can_connect?(wait_sec=2) url = "http://169.254.169.254/" Timeout::timeout(wait_sec) {open(url)} return true - rescue Timeout::Error - return false - rescue - return false + rescue + return false end # Test if this host has a mac address used by Eucalyptus clouds, which diff --git a/spec/unit/util/ec2_spec.rb b/spec/unit/util/ec2_spec.rb index 8851c984cf..a7ab2c0e1e 100755 --- a/spec/unit/util/ec2_spec.rb +++ b/spec/unit/util/ec2_spec.rb @@ -125,41 +125,29 @@ describe "can_connect? method" do it "returns true if api responds" do # Return something upon connecting to the root - Module.any_instance.expects(:open).with("#{api_prefix}:80/").\ + Module.any_instance.expects(:open).with("#{api_prefix}:80/"). at_least_once.returns("2008-02-01\nlatest") - Facter::Util::EC2.can_connect?.should == true + Facter::Util::EC2.can_connect?.should be_true end describe "when connection times out" do - before :each do + it "should return false" do # Emulate a timeout when connecting by throwing an exception - Module.any_instance.expects(:open).with("#{api_prefix}:80/").\ - at_least_once.raises(Timeout::Error) - end + Module.any_instance.expects(:open).with("#{api_prefix}:80/"). + at_least_once.raises(RuntimeError) - it "should not raise exception" do - expect { Facter::Util::EC2.can_connect? }.to_not raise_error - end - - it "should return false" do - Facter::Util::EC2.can_connect?.should == false + Facter::Util::EC2.can_connect?.should be_false end end describe "when connection is refused" do - before :each do + it "should return false" do # Emulate a connection refused - Module.any_instance.expects(:open).with("#{api_prefix}:80/").\ + Module.any_instance.expects(:open).with("#{api_prefix}:80/"). at_least_once.raises(Errno::ECONNREFUSED) - end - it "should not raise exception" do - expect { Facter::Util::EC2.can_connect? }.to_not raise_error - end - - it "should return false" do - Facter::Util::EC2.can_connect?.should == false + Facter::Util::EC2.can_connect?.should be_false end end end From bcbaffdfd417a73995221b2e14337e00497b0c37 Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Sun, 11 Nov 2012 15:19:23 +0000 Subject: [PATCH 1075/3753] Update ips ruby libdir to use sitelibdir instead of rubylibdir Signed-off-by: Moses Mendoza --- ext/ips/rules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/ips/rules b/ext/ips/rules index c9d6725c4c..5f8a91fcf9 100755 --- a/ext/ips/rules +++ b/ext/ips/rules @@ -1,6 +1,6 @@ #!/usr/bin/make -f -LIBDIR=$(shell /usr/bin/ruby18 -rrbconfig -e 'puts Config::CONFIG["rubylibdir"]') +LIBDIR=$(shell /usr/bin/ruby18 -rrbconfig -e 'puts Config::CONFIG["sitelibdir"]') DESTDIR=$(CURDIR)/pkg/ips/proto binary-install/facter:: From 2a9c78afcd83395f8acacf9f902ec70d36648cef Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Sun, 11 Nov 2012 15:20:29 +0000 Subject: [PATCH 1076/3753] Turn on IPS building for facter --- ext/build_defaults.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/ext/build_defaults.yaml b/ext/build_defaults.yaml index c0ade3323e..1df4b64dda 100644 --- a/ext/build_defaults.yaml +++ b/ext/build_defaults.yaml @@ -15,6 +15,7 @@ yum_host: 'burji.puppetlabs.com' yum_repo_path: '/opt/repository/yum/' build_gem: TRUE build_dmg: TRUE +build_ips: TRUE apt_host: 'burji.puppetlabs.com' apt_repo_url: 'http://apt.puppetlabs.com/' apt_repo_path: '/opt/repository/incoming' From c31cca5b79a5158941e1dcbfcb10b904581e9425 Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Sun, 11 Nov 2012 15:42:15 +0000 Subject: [PATCH 1077/3753] install.rb doesn't support sbindir, so remove it from ips rules Signed-off-by: Moses Mendoza --- ext/ips/rules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/ips/rules b/ext/ips/rules index 5f8a91fcf9..b76d8fe324 100755 --- a/ext/ips/rules +++ b/ext/ips/rules @@ -4,4 +4,4 @@ LIBDIR=$(shell /usr/bin/ruby18 -rrbconfig -e 'puts Config::CONFIG["sitelibdir"]' DESTDIR=$(CURDIR)/pkg/ips/proto binary-install/facter:: - /usr/bin/ruby18 install.rb --destdir=$(DESTDIR) --bindir=/usr/bin --sbindir=/usr/sbin --sitelibdir=$(LIBDIR) --mandir=/usr/share/man + /usr/bin/ruby18 install.rb --destdir=$(DESTDIR) --bindir=/usr/bin --sitelibdir=$(LIBDIR) --mandir=/usr/share/man From 6b9ce2e0a2abce3f5ec038c73070c9cb9efd55ac Mon Sep 17 00:00:00 2001 From: Francis Gulotta Date: Thu, 25 Oct 2012 08:32:34 -0400 Subject: [PATCH 1078/3753] (#17177) Add MTU information to interfaces add regex strings for linux bsd, and sunos to pull mtu information add tests to confirm that information is valid add the mtu label to interfaces.rb --- lib/facter/interfaces.rb | 2 +- lib/facter/util/ip.rb | 9 ++++++--- spec/unit/util/ip_spec.rb | 27 +++++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/lib/facter/interfaces.rb b/lib/facter/interfaces.rb index d02a4f7223..4ace619bbf 100644 --- a/lib/facter/interfaces.rb +++ b/lib/facter/interfaces.rb @@ -31,7 +31,7 @@ # Make a fact for each detail of each interface. Yay. # There's no point in confining these facts, since we wouldn't be able to create # them if we weren't running on a supported platform. - %w{ipaddress ipaddress6 macaddress netmask}.each do |label| + %w{ipaddress ipaddress6 macaddress netmask mtu}.each do |label| Facter.add(label + "_" + Facter::Util::IP.alphafy(interface)) do setcode do Facter::Util::IP.get_interface_value(interface, label) diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index 3a52bbbef1..d68037ee11 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -8,20 +8,23 @@ module Facter::Util::IP :ipaddress => /inet addr:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, :ipaddress6 => /inet6 addr: ((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/, :macaddress => /(?:ether|HWaddr)\s+((\w{1,2}:){5,}\w{1,2})/, - :netmask => /Mask:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + :netmask => /Mask:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, + :mtu => /MTU:(\d+)/ }, :bsd => { :aliases => [:openbsd, :netbsd, :freebsd, :darwin, :"gnu/kfreebsd", :dragonfly], :ipaddress => /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, :ipaddress6 => /inet6 ((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/, :macaddress => /(?:ether|lladdr)\s+(\w?\w:\w?\w:\w?\w:\w?\w:\w?\w:\w?\w)/, - :netmask => /netmask\s+0x(\w{8})/ + :netmask => /netmask\s+0x(\w{8})/, + :mtu => /mtu\s+(\d+)/ }, :sunos => { :ipaddress => /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, :ipaddress6 => /inet6 ((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/, :macaddress => /(?:ether|lladdr)\s+(\w?\w:\w?\w:\w?\w:\w?\w:\w?\w:\w?\w)/, - :netmask => /netmask\s+(\w{8})/ + :netmask => /netmask\s+(\w{8})/, + :mtu => /mtu\s+(\d+)/ }, :"hp-ux" => { :ipaddress => /\s+inet (\S+)\s.*/, diff --git a/spec/unit/util/ip_spec.rb b/spec/unit/util/ip_spec.rb index d67dfb8b11..54377ff78e 100755 --- a/spec/unit/util/ip_spec.rb +++ b/spec/unit/util/ip_spec.rb @@ -261,6 +261,33 @@ Facter::Util::IP.get_arp_value("eth0").should == "00:00:0c:9f:f0:04" end + it "should return mtu information on Linux" do + linux_ifconfig = my_fixture_read("linux_ifconfig_all_with_single_interface") + Facter::Util::IP.stubs(:get_all_interface_output).returns(linux_ifconfig) + Facter.stubs(:value).with(:kernel).returns("Linux") + + Facter::Util::IP.get_interface_value("eth0", "mtu").should == "1500" + Facter::Util::IP.get_interface_value("lo", "mtu").should == "16436" + end + + it "should return mtu information on Darwin" do + darwin_ifconfig_interface = my_fixture_read("darwin_ifconfig_single_interface") + + Facter::Util::IP.expects(:get_single_interface_output).with("en1").returns(darwin_ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("Darwin") + + Facter::Util::IP.get_interface_value("en1", "mtu").should == "1500" + end + + it "should return mtu information for Solaris" do + solaris_ifconfig_interface = my_fixture_read("solaris_ifconfig_single_interface") + + Facter::Util::IP.expects(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("SunOS") + + Facter::Util::IP.get_interface_value("e1000g0", "mtu").should == "1500" + end + describe "on Windows" do before :each do Facter.stubs(:value).with(:kernel).returns("windows") From 6ef8ccef8e2f6dc7ab826ad4f7d96f761da52a1b Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Tue, 13 Nov 2012 11:09:33 -0500 Subject: [PATCH 1079/3753] Fixup whitespace in (#17177) Add MTU information to interfaces --- spec/unit/util/ip_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/unit/util/ip_spec.rb b/spec/unit/util/ip_spec.rb index 54377ff78e..a25a73fdef 100755 --- a/spec/unit/util/ip_spec.rb +++ b/spec/unit/util/ip_spec.rb @@ -269,7 +269,7 @@ Facter::Util::IP.get_interface_value("eth0", "mtu").should == "1500" Facter::Util::IP.get_interface_value("lo", "mtu").should == "16436" end - + it "should return mtu information on Darwin" do darwin_ifconfig_interface = my_fixture_read("darwin_ifconfig_single_interface") @@ -278,7 +278,7 @@ Facter::Util::IP.get_interface_value("en1", "mtu").should == "1500" end - + it "should return mtu information for Solaris" do solaris_ifconfig_interface = my_fixture_read("solaris_ifconfig_single_interface") @@ -287,7 +287,7 @@ Facter::Util::IP.get_interface_value("e1000g0", "mtu").should == "1500" end - + describe "on Windows" do before :each do Facter.stubs(:value).with(:kernel).returns("windows") From d46b42a9ed716eec80c72e7c5ab19538dbd7fdc3 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Tue, 13 Nov 2012 11:29:55 -0500 Subject: [PATCH 1080/3753] (#17177) Fix spec failure when running on Mac OS X Without this patch applied, the following example fails when run on Mac OS X. This patch fixes the failure by stubbing out the system call to `ifconfig #{interface}` using fixture data. $ rspec -fp spec/unit/util/ip_spec.rb Failures: 1) Facter::Util::IP should return mtu information on Linux Failure/Error: Facter::Util::IP.get_interface_value("eth0", "mtu").should == "1500" expected: "1500" got: nil (using ==) # ./spec/unit/util/ip_spec.rb:269:in `block (2 levels) in ' --- .../unit/util/ip/linux_get_single_interface_eth0 | 10 ++++++++++ .../unit/util/ip/linux_get_single_interface_lo | 9 +++++++++ spec/unit/util/ip_spec.rb | 4 ++++ 3 files changed, 23 insertions(+) create mode 100644 spec/fixtures/unit/util/ip/linux_get_single_interface_eth0 create mode 100644 spec/fixtures/unit/util/ip/linux_get_single_interface_lo diff --git a/spec/fixtures/unit/util/ip/linux_get_single_interface_eth0 b/spec/fixtures/unit/util/ip/linux_get_single_interface_eth0 new file mode 100644 index 0000000000..b944f70bb6 --- /dev/null +++ b/spec/fixtures/unit/util/ip/linux_get_single_interface_eth0 @@ -0,0 +1,10 @@ +eth0 Link encap:Ethernet HWaddr 00:0c:29:52:15:e9 + inet addr:172.16.15.133 Bcast:172.16.15.255 Mask:255.255.255.0 + inet6 addr: fe80::20c:29ff:fe52:15e9/64 Scope:Link + UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 + RX packets:173 errors:173 dropped:0 overruns:0 frame:0 + TX packets:208 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:1000 + RX bytes:40970 (40.0 KB) TX bytes:24760 (24.1 KB) + Interrupt:16 Base address:0x2024 + diff --git a/spec/fixtures/unit/util/ip/linux_get_single_interface_lo b/spec/fixtures/unit/util/ip/linux_get_single_interface_lo new file mode 100644 index 0000000000..e8dbbfe1f1 --- /dev/null +++ b/spec/fixtures/unit/util/ip/linux_get_single_interface_lo @@ -0,0 +1,9 @@ +lo Link encap:Local Loopback + inet addr:127.0.0.1 Mask:255.0.0.0 + inet6 addr: ::1/128 Scope:Host + UP LOOPBACK RUNNING MTU:16436 Metric:1 + RX packets:1630 errors:0 dropped:0 overruns:0 frame:0 + TX packets:1630 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:0 + RX bytes:81500 (79.5 KB) TX bytes:81500 (79.5 KB) + diff --git a/spec/unit/util/ip_spec.rb b/spec/unit/util/ip_spec.rb index a25a73fdef..db9f34c57e 100755 --- a/spec/unit/util/ip_spec.rb +++ b/spec/unit/util/ip_spec.rb @@ -264,6 +264,10 @@ it "should return mtu information on Linux" do linux_ifconfig = my_fixture_read("linux_ifconfig_all_with_single_interface") Facter::Util::IP.stubs(:get_all_interface_output).returns(linux_ifconfig) + Facter::Util::IP.stubs(:get_single_interface_output).with("eth0"). + returns(my_fixture_read("linux_get_single_interface_eth0")) + Facter::Util::IP.stubs(:get_single_interface_output).with("lo"). + returns(my_fixture_read("linux_get_single_interface_lo")) Facter.stubs(:value).with(:kernel).returns("Linux") Facter::Util::IP.get_interface_value("eth0", "mtu").should == "1500" From b1f196a0d155f91403c3b969a3226607e342b0f6 Mon Sep 17 00:00:00 2001 From: Niels Abspoel Date: Sat, 10 Nov 2012 18:58:56 +0100 Subject: [PATCH 1081/3753] (#17521) Add osfamily fact ArchLinux Archlinux doesn't have a osfamily fact. I believe Archlinux needs one because of the following: * Manjaro Linux based on Archlinux http://blog.manjaro.org/ * Archbang based on Archlinux http://archbang.org/ * More archlinux based distro's: https://wiki.archlinux.org/index.php/Arch_Based_Distributions_(Active) --- lib/facter/osfamily.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/facter/osfamily.rb b/lib/facter/osfamily.rb index 60abd2bb51..6162ef8929 100644 --- a/lib/facter/osfamily.rb +++ b/lib/facter/osfamily.rb @@ -26,6 +26,8 @@ "Solaris" when "Gentoo" "Gentoo" + when "Archlinux" + "Archlinux" else Facter.value("kernel") end From 1f808cf5adfc0345e0df2dc479f4a645e1376330 Mon Sep 17 00:00:00 2001 From: Stefan Schulte Date: Wed, 14 Nov 2012 20:11:55 +0100 Subject: [PATCH 1082/3753] (#17521) Modify spec to respect Archlinux osfamily On ArchLinux we now set the osfamily to ArchLinux to be able to support furture Arch forks more easily. Because the default osfamily is to return the kernel fact we also have to modify the spec tests. --- spec/unit/osfamily_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/unit/osfamily_spec.rb b/spec/unit/osfamily_spec.rb index a074ecc515..ecab32679b 100644 --- a/spec/unit/osfamily_spec.rb +++ b/spec/unit/osfamily_spec.rb @@ -5,6 +5,7 @@ describe "OS Family fact" do { + 'Archlinux' => 'Archlinux', 'SmartOS' => 'Solaris', 'OpenIndiana' => 'Solaris', 'OmniOS' => 'Solaris', @@ -41,7 +42,6 @@ 'Mandriva', 'Mandrake', 'MeeGo', - 'Archlinux', 'VMWareESX', 'Bluewhite64', 'Slamd64', From 4b2baab5d49ebc18720c7caa033e85a1963e624d Mon Sep 17 00:00:00 2001 From: Matthaus Owens Date: Thu, 15 Nov 2012 11:40:51 -0800 Subject: [PATCH 1083/3753] (maint) Remove hard dependency on rspec from Rakefile Rspec is currently required to run any rake tasks in Facter, or even to list the available rake tasks. For users wishing to just build packages, this is a high bar. This commit moves the rspec and rubygem requires into a begin/rescue block so that loaderrors can be caught and ignored. It also moves the tasks that are defined using Rspec::Core::RakeTask into a conditional on whether Rspec::Core::RakeTask is available to use. This allows the rake tasks to be listed and run without rspec installed. --- Rakefile | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/Rakefile b/Rakefile index db0941dae7..aad745b5f2 100644 --- a/Rakefile +++ b/Rakefile @@ -6,16 +6,16 @@ require 'facter/version' $LOAD_PATH << File.join(File.dirname(__FILE__), 'tasks') -require 'rubygems' -require 'rspec' -require 'rspec/core/rake_task' -require 'rake' - begin + require 'rubygems' + require 'rspec' + require 'rspec/core/rake_task' require 'rcov' rescue LoadError end +require 'rake' + Dir['tasks/**/*.rake'].each { |t| load t } Dir['ext/packaging/tasks/**/*'].sort.each { |t| load t } @@ -55,17 +55,19 @@ task :default do sh %{rake -T} end -desc "Run all specs" -RSpec::Core::RakeTask.new do |t| - t.pattern ='spec/{unit,integration}/**/*_spec.rb' - t.fail_on_error = true -end +if defined?(RSpec::Core::RakeTask) + desc "Run all specs" + RSpec::Core::RakeTask.new do |t| + t.pattern ='spec/{unit,integration}/**/*_spec.rb' + t.fail_on_error = true + end -RSpec::Core::RakeTask.new('spec:rcov') do |t| - t.pattern ='spec/{unit,integration}/**/*_spec.rb' - t.fail_on_error = true - if defined?(Rcov) - t.rcov = true - t.rcov_opts = ['--exclude', 'spec/*,test/*,results/*,/usr/lib/*,/usr/local/lib/*,gems/*'] + RSpec::Core::RakeTask.new('spec:rcov') do |t| + t.pattern ='spec/{unit,integration}/**/*_spec.rb' + t.fail_on_error = true + if defined?(Rcov) + t.rcov = true + t.rcov_opts = ['--exclude', 'spec/*,test/*,results/*,/usr/lib/*,/usr/local/lib/*,gems/*'] + end end end From ff60b690adbf421425fe6a8142bb0424fa62d8d2 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Thu, 1 Nov 2012 16:11:08 -0700 Subject: [PATCH 1084/3753] Improve Facter.version API to help automate releases Without this patch applied, it is difficult to fully automate releases because the `FACTERVERSION` constant in code must be updated, creating a new version of the code. This circular dependency makes it very difficult to use `git describe` as the basis for a version string. This patch addresses the problem by providing a way to break the circular dependency. With this patch applied, Facter will use the contents of `lib/facter/VERSION`, if it exists, as the version string. This VERSION file is not intended to be version controlled therefore changing it does not create a new version in history as does changing the code itself. This patch defines the public API to maintain the software version for package maintainers. --- lib/facter/version.rb | 72 ++++++++++++++++++++++++++++++++++++++- spec/unit/version_spec.rb | 42 +++++++++++++++++++++++ 2 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 spec/unit/version_spec.rb diff --git a/lib/facter/version.rb b/lib/facter/version.rb index 57bb02f218..204eec14fb 100644 --- a/lib/facter/version.rb +++ b/lib/facter/version.rb @@ -3,11 +3,81 @@ module Facter FACTERVERSION = '1.6.14' end + ## + # version is a public API method intended to always provide a fast and + # lightweight way to determine the version of Facter. + # + # The intent is that software external to Facter be able to determine the + # Facter version with no side-effects. The expected use is: + # + # require 'facter/version' + # version = Facter.version + # + # This function has the following ordering precedence. This precedence list + # is designed to facilitate automated packaging tasks by simply writing to + # the VERSION file in the same directory as this source file. + # + # 1. If a version has been explicitly assigned using the Facter.version= + # method, return that version. + # 2. If there is a VERSION file, read the contents, trim any + # trailing whitespace, and return that version string. + # 3. Return the value of the Facter::FACTERVERSION constant hard-coded into + # the source code. + # + # If there is no VERSION file, the method must return the version string of + # the nearest parent version that is an officially released version. That is + # to say, if a branch named 3.1.x contains 25 patches on top of the most + # recent official release of 3.1.1, then the version method must return the + # string "3.1.1" if no "VERSION" file is present. + # + # By design the version identifier is _not_ intended to vary during the life + # a process. There is no guarantee provided that writing to the VERSION file + # while a Puppet process is running will cause the version string to be + # updated. On the contrary, the contents of the VERSION are cached to reduce + # filesystem accesses. + # + # The VERSION file is intended to be used by package maintainers who may be + # applying patches or otherwise changing the software version in a manner + # that warrants a different software version identifier. The VERSION file is + # intended to be managed and owned by the release process and packaging + # related tasks, and as such should not reside in version control. The + # FACTERVERSION constant is intended to be version controlled in history. + # + # Ideally, this behavior will allow package maintainers to precisely specify + # the version of the software they're packaging as in the following example: + # + # $ git describe > lib/facter/VERSION + # $ ruby -r facter -e 'puts Facter.version' + # 1.6.14-6-g66f2c99 + # + # @api public + # + # @return [String] containing the facter version, e.g. "1.6.14" def self.version - @facter_version || FACTERVERSION + version_file = File.join(File.dirname(__FILE__), 'VERSION') + return @facter_version if @facter_version + if version = read_version_file(version_file) + @facter_version = version + end + @facter_version ||= FACTERVERSION end def self.version=(version) @facter_version = version end + + ## + # read_version_file reads the content of the "VERSION" file that lives in the + # same directory as this source code file. + # + # @api private + # + # @return [String] for example: "1.6.14-6-gea42046" or nil if the VERSION + # file does not exist. + def self.read_version_file(path) + if File.exists?(path) + File.read(path).chomp + end + end + private_class_method :read_version_file end diff --git a/spec/unit/version_spec.rb b/spec/unit/version_spec.rb new file mode 100644 index 0000000000..ab5b6ad21d --- /dev/null +++ b/spec/unit/version_spec.rb @@ -0,0 +1,42 @@ +require "spec_helper" +require "facter/version" +require 'pathname' + +describe "Facter.version Public API" do + before :each do + Facter.instance_eval do + if @facter_version + @facter_version = nil + end + end + end + + context "without a VERSION file" do + before :each do + Facter.stubs(:read_version_file).returns(nil) + end + + it "is Facter::FACTERVERSION" do + Facter.version.should == Facter::FACTERVERSION + end + it "respects the version= setter" do + Facter.version = '1.2.3' + Facter.version.should == '1.2.3' + end + end + + context "with a VERSION file" do + it "is the content of the file" do + Facter.expects(:read_version_file).with() do |path| + pathname = Pathname.new(path) + pathname.basename.to_s == "VERSION" + end.returns('1.6.14-6-gea42046') + + Facter.version.should == '1.6.14-6-gea42046' + end + it "respects the version= setter" do + Facter.version = '1.2.3' + Facter.version.should == '1.2.3' + end + end +end From 2674e0d6f988375e5e348e60ffba2b9011cff5ef Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Tue, 20 Nov 2012 12:04:14 -0500 Subject: [PATCH 1085/3753] Add Facter::Util::FileRead.read helper method Without this patch, there isn't a robust and easy way to simply read the contents of a file from within a fact's setcode block. This is a problem because it encourages the use of Facter::Util::Resolution.exec('cat ...') which is a bit heavy for this purpose. This patch addresses the problem by adding a new utility method and module intended to simply read and return the contents of a file. If the file is inaccessible or does not exist then the method logs a debug message and returns nil. --- lib/facter/util/file_read.rb | 32 ++++++++++++++++++++++++++++++++ spec/unit/util/file_read_spec.rb | 29 +++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 lib/facter/util/file_read.rb create mode 100644 spec/unit/util/file_read_spec.rb diff --git a/lib/facter/util/file_read.rb b/lib/facter/util/file_read.rb new file mode 100644 index 0000000000..258cd665bf --- /dev/null +++ b/lib/facter/util/file_read.rb @@ -0,0 +1,32 @@ +module Facter +module Util +## +# {Facter::Util::FileRead} is a utility module intended to provide easily +# mockable methods that delegate to simple file read methods. The intent is to +# avoid the need to execute the `cat` system command or `File.read` directly in +# Ruby, as mocking these behaviors can have wide-ranging effects. +# +# All Facter facts are encouraged to use this method instead of File.read or +# Facter::Util::Resolution.exec('cat ...') +# +# @api public +module FileRead + ## + # read returns the raw content of a file as a string. If the file does not + # exist, or the process does not have permission to read the file then nil is + # returned. + # + # @api public + # + # @return [String] the raw contents of the file at {path} or {nil} if the + # file cannot be read because it does not exist or the process does not have + # permission to read the file. + def self.read(path) + File.read(path) + rescue Errno::ENOENT, Errno::EACCES => detail + Facter.debug "Could not read #{path}: #{detail.message}" + nil + end +end +end +end diff --git a/spec/unit/util/file_read_spec.rb b/spec/unit/util/file_read_spec.rb new file mode 100644 index 0000000000..3ccf814b6c --- /dev/null +++ b/spec/unit/util/file_read_spec.rb @@ -0,0 +1,29 @@ +#! /usr/bin/env ruby + +require 'facter/util/file_read' +require 'spec_helper' + +describe Facter::Util::FileRead do + let(:issue) { "Ubuntu 10.04.4 LTS \\n \\l\n\n" } + + it "reads a file" do + File.expects(:read).with("/etc/issue").returns(issue) + Facter::Util::FileRead.read("/etc/issue").should == issue + end + + it "returns nil if the file cannot be accessed" do + File.stubs(:read).with("/etc/issue").raises(Errno::EACCES.new("/etc/issue")) + Facter::Util::FileRead.read("/etc/issue").should be_nil + end + + it "returns nil if the file does not exist" do + File.stubs(:read).with("/etc/issue").raises(Errno::ENOENT.new("/etc/issue")) + Facter::Util::FileRead.read("/etc/issue").should be_nil + end + + it "logs a message when handing exceptions" do + File.stubs(:read).with("/etc/issue").raises(Errno::EACCES.new("/etc/issue")) + Facter.expects(:debug).with("Could not read /etc/issue: Permission denied - /etc/issue") + Facter::Util::FileRead.read("/etc/issue") + end +end From b97337c8faf05c8b4dbd5f1d2f4bc48b15e0cfab Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Tue, 20 Nov 2012 12:07:46 -0500 Subject: [PATCH 1086/3753] Add expectations for operatingsystemrelease on Ubuntu Without this patch, there are no examples of the expected behavior of the operatingsystemrelease fact for Ubuntu. This is a problem because it is unclear if the fact's value should contain the patch version in addition to the major and minor version. This patch fixes the problem by adding an RSpec example that clearly specifies the operatingsystemrelease fact should only contain the major and minor version and not the patch version. If this behavior changes in the future this example will fail to pass. See https://github.com/puppetlabs/facter/pull/278 for the decision. --- lib/facter/operatingsystemrelease.rb | 11 ++++++++--- spec/unit/operatingsystemrelease_spec.rb | 13 +++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index 0708a612fa..29a0e05faa 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -17,6 +17,8 @@ # Caveats: # +require 'facter/util/file_read' + Facter.add(:operatingsystemrelease) do confine :operatingsystem => %w{CentOS Fedora oel ovs OracleLinux RedHat MeeGo Scientific SLC Ascendos CloudLinux PSBM} setcode do @@ -55,9 +57,12 @@ Facter.add(:operatingsystemrelease) do confine :operatingsystem => %w{Ubuntu} setcode do - release = Facter::Util::Resolution.exec('cat /etc/issue') - if release =~ /Ubuntu (\d+.\d+)/ - $1 + if release = Facter::Util::FileRead.read('/etc/issue') + if match = release.match(/Ubuntu ((\d+.\d+)(\.(\d+))?)/) + # Return only the major and minor version numbers. This behavior must + # be preserved for compatibility reasons. + match[2] + end end end end diff --git a/spec/unit/operatingsystemrelease_spec.rb b/spec/unit/operatingsystemrelease_spec.rb index fc77f66bbc..b2c37d9f63 100755 --- a/spec/unit/operatingsystemrelease_spec.rb +++ b/spec/unit/operatingsystemrelease_spec.rb @@ -1,5 +1,6 @@ #! /usr/bin/env ruby +require 'facter/util/file_read' require 'spec_helper' describe "Operating System Release fact" do @@ -134,6 +135,18 @@ File.stubs(:open).with('/etc/release','r').returns 'some future release string' Facter.fact(:operatingsystemrelease).value.should == Facter.fact(:kernelrelease).value end + end + context "Ubuntu" do + let(:issue) { "Ubuntu 10.04.4 LTS \\n \\l\n\n" } + before :each do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:operatingsystem).stubs(:value).returns("Ubuntu") + end + + it "Returns only the major and minor version (not patch version)" do + Facter::Util::FileRead.stubs(:read).with("/etc/issue").returns(issue) + Facter.fact(:operatingsystemrelease).value.should == "10.04" + end end end From 7aaee5e168ba78a5e8a6dc57f5cc61e21221f497 Mon Sep 17 00:00:00 2001 From: Matthaus Owens Date: Wed, 21 Nov 2012 13:28:54 -0800 Subject: [PATCH 1087/3753] Updating FACTERVERSION for 1.6.15-rc1 --- lib/facter/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/version.rb b/lib/facter/version.rb index 57bb02f218..5692649b2a 100644 --- a/lib/facter/version.rb +++ b/lib/facter/version.rb @@ -1,6 +1,6 @@ module Facter if not defined? FACTERVERSION then - FACTERVERSION = '1.6.14' + FACTERVERSION = '1.6.15-rc1' end def self.version From e35acbb0798fb4e6863ca2e9151ed43c14dfac9b Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 27 Nov 2012 12:49:16 -0800 Subject: [PATCH 1088/3753] (#17840) Use enum_cpuinfo for x86 arch Windows and Gentoo consider 32 bit x86 architectures to be called 'x86', but this value was not handled when checking for processorcount information. This patch adds x86 alongside the other synonyms for this architecture. --- lib/facter/util/processor.rb | 2 +- .../fixtures/unit/util/processor/x86-pentium2 | 41 +++++++++++++++++++ spec/unit/util/processor_spec.rb | 16 ++++++++ 3 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 spec/fixtures/unit/util/processor/x86-pentium2 diff --git a/lib/facter/util/processor.rb b/lib/facter/util/processor.rb index 14c22dc1ab..ebb2fd4d39 100644 --- a/lib/facter/util/processor.rb +++ b/lib/facter/util/processor.rb @@ -217,7 +217,7 @@ def self.enum_cpuinfo if File.exists?(cpuinfo) model = Facter.value(:architecture) case model - when "x86_64", "amd64", "i386", /parisc/, "hppa", "ia64" + when "x86_64", "amd64", "i386", "x86", /parisc/, "hppa", "ia64" Thread::exclusive do File.readlines(cpuinfo).each do |l| if l =~ /processor\s+:\s+(\d+)/ diff --git a/spec/fixtures/unit/util/processor/x86-pentium2 b/spec/fixtures/unit/util/processor/x86-pentium2 new file mode 100644 index 0000000000..e1fcdeabee --- /dev/null +++ b/spec/fixtures/unit/util/processor/x86-pentium2 @@ -0,0 +1,41 @@ +processor : 0 +vendor_id : GenuineIntel +cpu family : 6 +model : 5 +model name : Pentium II (Deschutes) +stepping : 1 +cpu MHz : 333.379 +cache size : 512 KB +fdiv_bug : no +hlt_bug : no +f00f_bug : no +coma_bug : no +fpu : yes +fpu_exception : yes +cpuid level : 2 +wp : yes +flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 mmx fxsr +bogomips : 667.53 +clflush size : 32 +power management: + +processor : 1 +vendor_id : GenuineIntel +cpu family : 6 +model : 5 +model name : Pentium II (Deschutes) +stepping : 1 +cpu MHz : 333.379 +cache size : 512 KB +fdiv_bug : no +hlt_bug : no +f00f_bug : no +coma_bug : no +fpu : yes +fpu_exception : yes +cpuid level : 2 +wp : yes +flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 mmx fxsr +bogomips : 666.44 +clflush size : 32 +power management: diff --git a/spec/unit/util/processor_spec.rb b/spec/unit/util/processor_spec.rb index fe8a924994..1dfbbab9ed 100755 --- a/spec/unit/util/processor_spec.rb +++ b/spec/unit/util/processor_spec.rb @@ -50,6 +50,22 @@ def cpuinfo_fixture(filename) Facter::Util::Processor.enum_cpuinfo[3].should == "Quad-Core AMD Opteron(tm) Processor 2374 HE" end + describe "with architecture x86" do + before do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:architecture).stubs(:value).returns("x86") + File.stubs(:exists?).with("/proc/cpuinfo").returns(true) + File.stubs(:readlines).with("/proc/cpuinfo").returns(my_fixture_read("x86-pentium2").lines) + end + + subject { Facter::Util::Processor.enum_cpuinfo } + + it "should have the correct processor titles" do + subject[0].should == "Pentium II (Deschutes)" + subject[1].should == "Pentium II (Deschutes)" + end + end + it "should get the processor description on Solaris (x86)" do Facter.fact(:kernel).stubs(:value).returns("SunOS") Facter.fact(:architecture).stubs(:value).returns("i86pc") From e5ca9165778e15c6637af3b221ed33b401070fb4 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 27 Nov 2012 14:51:08 -0800 Subject: [PATCH 1089/3753] (maint) Remove processor_spec duplicate stubbing The Facter::Util::Processor spec had a number of repeated stubs that were merged with expectation blocks. This commit moves the expectations into before blocks and uses example groups to scope the stubbing. --- spec/unit/util/processor_spec.rb | 128 ++++++++++++++++--------------- 1 file changed, 65 insertions(+), 63 deletions(-) diff --git a/spec/unit/util/processor_spec.rb b/spec/unit/util/processor_spec.rb index 1dfbbab9ed..7a53f1d189 100755 --- a/spec/unit/util/processor_spec.rb +++ b/spec/unit/util/processor_spec.rb @@ -8,84 +8,86 @@ def cpuinfo_fixture(filename) end describe Facter::Util::Processor do - it "should get the processor description from the amd64solo fixture" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:architecture).stubs(:value).returns("amd64") - File.stubs(:exists?).with("/proc/cpuinfo").returns(true) - File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("amd64solo")) + describe "on linux" do + before :each do + Facter.fact(:kernel).stubs(:value).returns("Linux") + File.stubs(:exists?).with("/proc/cpuinfo").returns(true) + end - Facter::Util::Processor.enum_cpuinfo[0].should == "Intel(R) Core(TM)2 Duo CPU P8700 @ 2.53GHz" - end + describe "with architecture amd64" do + before :each do + Facter.fact(:architecture).stubs(:value).returns("amd64") + end - it "should get the processor descriptions from the amd64dual fixture" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:architecture).stubs(:value).returns("amd64") - File.stubs(:exists?).with("/proc/cpuinfo").returns(true) - File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("amd64dual")) + it "should get the processor description from the amd64solo fixture" do + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("amd64solo")) + Facter::Util::Processor.enum_cpuinfo[0].should == "Intel(R) Core(TM)2 Duo CPU P8700 @ 2.53GHz" + end - Facter::Util::Processor.enum_cpuinfo[0].should == "Intel(R) Core(TM)2 Duo CPU P8700 @ 2.53GHz" - Facter::Util::Processor.enum_cpuinfo[1].should == "Intel(R) Core(TM)2 Duo CPU P8700 @ 2.53GHz" - end + it "should get the processor descriptions from the amd64dual fixture" do + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("amd64dual")) - it "should get the processor descriptions from the amd64tri fixture" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:architecture).stubs(:value).returns("amd64") - File.stubs(:exists?).with("/proc/cpuinfo").returns(true) - File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("amd64tri")) + Facter::Util::Processor.enum_cpuinfo[0].should == "Intel(R) Core(TM)2 Duo CPU P8700 @ 2.53GHz" + Facter::Util::Processor.enum_cpuinfo[1].should == "Intel(R) Core(TM)2 Duo CPU P8700 @ 2.53GHz" + end - Facter::Util::Processor.enum_cpuinfo[0].should == "Intel(R) Core(TM)2 Duo CPU P8700 @ 2.53GHz" - Facter::Util::Processor.enum_cpuinfo[1].should == "Intel(R) Core(TM)2 Duo CPU P8700 @ 2.53GHz" - Facter::Util::Processor.enum_cpuinfo[2].should == "Intel(R) Core(TM)2 Duo CPU P8700 @ 2.53GHz" - end + it "should get the processor descriptions from the amd64tri fixture" do + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("amd64tri")) - it "should get the processor descriptions from the amd64quad fixture" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:architecture).stubs(:value).returns("amd64") - File.stubs(:exists?).with("/proc/cpuinfo").returns(true) - File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("amd64quad")) + Facter::Util::Processor.enum_cpuinfo[0].should == "Intel(R) Core(TM)2 Duo CPU P8700 @ 2.53GHz" + Facter::Util::Processor.enum_cpuinfo[1].should == "Intel(R) Core(TM)2 Duo CPU P8700 @ 2.53GHz" + Facter::Util::Processor.enum_cpuinfo[2].should == "Intel(R) Core(TM)2 Duo CPU P8700 @ 2.53GHz" + end - Facter::Util::Processor.enum_cpuinfo[0].should == "Quad-Core AMD Opteron(tm) Processor 2374 HE" - Facter::Util::Processor.enum_cpuinfo[1].should == "Quad-Core AMD Opteron(tm) Processor 2374 HE" - Facter::Util::Processor.enum_cpuinfo[2].should == "Quad-Core AMD Opteron(tm) Processor 2374 HE" - Facter::Util::Processor.enum_cpuinfo[3].should == "Quad-Core AMD Opteron(tm) Processor 2374 HE" - end + it "should get the processor descriptions from the amd64quad fixture" do + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("amd64quad")) - describe "with architecture x86" do - before do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:architecture).stubs(:value).returns("x86") - File.stubs(:exists?).with("/proc/cpuinfo").returns(true) - File.stubs(:readlines).with("/proc/cpuinfo").returns(my_fixture_read("x86-pentium2").lines) + Facter::Util::Processor.enum_cpuinfo[0].should == "Quad-Core AMD Opteron(tm) Processor 2374 HE" + Facter::Util::Processor.enum_cpuinfo[1].should == "Quad-Core AMD Opteron(tm) Processor 2374 HE" + Facter::Util::Processor.enum_cpuinfo[2].should == "Quad-Core AMD Opteron(tm) Processor 2374 HE" + Facter::Util::Processor.enum_cpuinfo[3].should == "Quad-Core AMD Opteron(tm) Processor 2374 HE" + end end - subject { Facter::Util::Processor.enum_cpuinfo } + describe "with architecture x86" do + before :each do + Facter.fact(:architecture).stubs(:value).returns("x86") + File.stubs(:readlines).with("/proc/cpuinfo").returns(my_fixture_read("x86-pentium2").lines) + end + + subject { Facter::Util::Processor.enum_cpuinfo } - it "should have the correct processor titles" do - subject[0].should == "Pentium II (Deschutes)" - subject[1].should == "Pentium II (Deschutes)" + it "should have the correct processor titles" do + subject[0].should == "Pentium II (Deschutes)" + subject[1].should == "Pentium II (Deschutes)" + end end end - it "should get the processor description on Solaris (x86)" do - Facter.fact(:kernel).stubs(:value).returns("SunOS") - Facter.fact(:architecture).stubs(:value).returns("i86pc") - Facter::Util::Resolution.stubs(:exec).with("/usr/bin/kstat cpu_info").returns(my_fixture_read("solaris-i86pc")) + describe "on Solaris" do + before :each do + Facter.fact(:kernel).stubs(:value).returns("SunOS") + end - Facter::Util::Processor.enum_kstat[0].should == "Intel(r) Core(tm) i5 CPU M 450 @ 2.40GHz" - end + it "should get the processor description on Solaris (x86)" do + Facter.fact(:architecture).stubs(:value).returns("i86pc") + Facter::Util::Resolution.stubs(:exec).with("/usr/bin/kstat cpu_info").returns(my_fixture_read("solaris-i86pc")) - it "should get the processor description on Solaris (SPARC64)" do - Facter.fact(:kernel).stubs(:value).returns("SunOS") - Facter.fact(:architecture).stubs(:value).returns("sun4u") - Facter::Util::Resolution.stubs(:exec).with("/usr/bin/kstat cpu_info").returns(my_fixture_read("solaris-sun4u")) - - Facter::Util::Processor.enum_kstat[0].should == "SPARC64-VII" - Facter::Util::Processor.enum_kstat[1].should == "SPARC64-VII" - Facter::Util::Processor.enum_kstat[2].should == "SPARC64-VII" - Facter::Util::Processor.enum_kstat[3].should == "SPARC64-VII" - Facter::Util::Processor.enum_kstat[4].should == "SPARC64-VII" - Facter::Util::Processor.enum_kstat[5].should == "SPARC64-VII" - Facter::Util::Processor.enum_kstat[6].should == "SPARC64-VII" - Facter::Util::Processor.enum_kstat[7].should == "SPARC64-VII" + Facter::Util::Processor.enum_kstat[0].should == "Intel(r) Core(tm) i5 CPU M 450 @ 2.40GHz" + end + + it "should get the processor description on Solaris (SPARC64)" do + Facter.fact(:architecture).stubs(:value).returns("sun4u") + Facter::Util::Resolution.stubs(:exec).with("/usr/bin/kstat cpu_info").returns(my_fixture_read("solaris-sun4u")) + + Facter::Util::Processor.enum_kstat[0].should == "SPARC64-VII" + Facter::Util::Processor.enum_kstat[1].should == "SPARC64-VII" + Facter::Util::Processor.enum_kstat[2].should == "SPARC64-VII" + Facter::Util::Processor.enum_kstat[3].should == "SPARC64-VII" + Facter::Util::Processor.enum_kstat[4].should == "SPARC64-VII" + Facter::Util::Processor.enum_kstat[5].should == "SPARC64-VII" + Facter::Util::Processor.enum_kstat[6].should == "SPARC64-VII" + Facter::Util::Processor.enum_kstat[7].should == "SPARC64-VII" + end end end From 6e7ab4431a88660c2b5395a389b0202425dff194 Mon Sep 17 00:00:00 2001 From: Matthaus Owens Date: Wed, 28 Nov 2012 11:35:51 -0800 Subject: [PATCH 1090/3753] Updating FACTERVERSION to 1.6.15 --- lib/facter/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/version.rb b/lib/facter/version.rb index 5692649b2a..2bc126af63 100644 --- a/lib/facter/version.rb +++ b/lib/facter/version.rb @@ -1,6 +1,6 @@ module Facter if not defined? FACTERVERSION then - FACTERVERSION = '1.6.15-rc1' + FACTERVERSION = '1.6.15' end def self.version From b62b3f6d83fdf216f551f0547754f5b10ac70bc1 Mon Sep 17 00:00:00 2001 From: Alex Harvey Date: Mon, 26 Nov 2012 01:00:55 +1100 Subject: [PATCH 1091/3753] (#17487) Add support for HPUX NIC bonding in IP facts Without this patch applied facter prints messages like $ facter Could not retrieve netmask_lan3_: undefined method `+' for nil:NilClass Could not retrieve macaddress_lan3_: undefined method `+' for nil:NilClass Could not retrieve network_lan3_: undefined method `+' for nil:NilClass Could not retrieve ipaddress6_lan3_: undefined method `+' for nil:NilClass Could not retrieve ipaddress_lan3_: undefined method `+' for nil:NilClass Could not retrieve mtu_lan3_: undefined method `+' for nil:NilClass ... interfaces => lan4_1,lan3_,lan1,lo0,lan4 The patch is a rewrite of Hongbo Hu's patch submitted in #11612 with sed code implemented in Ruby. The patch replaces the existing HP-UX unit tests with an exhaustive selection of netstat -in, ifconfig, and lanscan examples on various HP platforms with and without NIC bonding. The patch also includes tests for MTU facts commented out as MTU facts have not been implemented yet. These can be uncommented after (#17808) is fixed. --- lib/facter/util/ip.rb | 28 ++- .../unit/util/ip/hpux_1111_ifconfig_lan0 | 2 + .../unit/util/ip/hpux_1111_ifconfig_lan1 | 2 + .../unit/util/ip/hpux_1111_ifconfig_lo0 | 2 + spec/fixtures/unit/util/ip/hpux_1111_lanscan | 5 + .../unit/util/ip/hpux_1111_netstat_in | 4 + .../util/ip/hpux_1131_asterisk_ifconfig_lan0 | 2 + .../util/ip/hpux_1131_asterisk_ifconfig_lan1 | 2 + .../util/ip/hpux_1131_asterisk_ifconfig_lo0 | 2 + .../unit/util/ip/hpux_1131_asterisk_lanscan | 5 + .../util/ip/hpux_1131_asterisk_netstat_in | 4 + .../unit/util/ip/hpux_1131_ifconfig_lan0 | 2 + .../unit/util/ip/hpux_1131_ifconfig_lan1 | 2 + .../unit/util/ip/hpux_1131_ifconfig_lo0 | 2 + spec/fixtures/unit/util/ip/hpux_1131_lanscan | 4 + .../unit/util/ip/hpux_1131_netstat_in | 4 + .../ip/hpux_1131_nic_bonding_ifconfig_lan1 | 2 + .../ip/hpux_1131_nic_bonding_ifconfig_lan4 | 2 + .../ip/hpux_1131_nic_bonding_ifconfig_lan4_1 | 2 + .../ip/hpux_1131_nic_bonding_ifconfig_lo0 | 2 + .../util/ip/hpux_1131_nic_bonding_lanscan | 9 + .../util/ip/hpux_1131_nic_bonding_netstat_in | 6 + .../util/ip/hpux_ifconfig_single_interface | 3 - .../unit/util/ip/hpux_netstat_all_interfaces | 3 - spec/unit/util/ip_spec.rb | 180 +++++++++++++----- 25 files changed, 221 insertions(+), 60 deletions(-) create mode 100644 spec/fixtures/unit/util/ip/hpux_1111_ifconfig_lan0 create mode 100644 spec/fixtures/unit/util/ip/hpux_1111_ifconfig_lan1 create mode 100644 spec/fixtures/unit/util/ip/hpux_1111_ifconfig_lo0 create mode 100644 spec/fixtures/unit/util/ip/hpux_1111_lanscan create mode 100644 spec/fixtures/unit/util/ip/hpux_1111_netstat_in create mode 100644 spec/fixtures/unit/util/ip/hpux_1131_asterisk_ifconfig_lan0 create mode 100644 spec/fixtures/unit/util/ip/hpux_1131_asterisk_ifconfig_lan1 create mode 100644 spec/fixtures/unit/util/ip/hpux_1131_asterisk_ifconfig_lo0 create mode 100644 spec/fixtures/unit/util/ip/hpux_1131_asterisk_lanscan create mode 100644 spec/fixtures/unit/util/ip/hpux_1131_asterisk_netstat_in create mode 100644 spec/fixtures/unit/util/ip/hpux_1131_ifconfig_lan0 create mode 100644 spec/fixtures/unit/util/ip/hpux_1131_ifconfig_lan1 create mode 100644 spec/fixtures/unit/util/ip/hpux_1131_ifconfig_lo0 create mode 100644 spec/fixtures/unit/util/ip/hpux_1131_lanscan create mode 100644 spec/fixtures/unit/util/ip/hpux_1131_netstat_in create mode 100644 spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lan1 create mode 100644 spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lan4 create mode 100644 spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lan4_1 create mode 100644 spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lo0 create mode 100644 spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_lanscan create mode 100644 spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_netstat_in delete mode 100644 spec/fixtures/unit/util/ip/hpux_ifconfig_single_interface delete mode 100644 spec/fixtures/unit/util/ip/hpux_netstat_all_interfaces diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index d68037ee11..192836d07f 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -81,7 +81,14 @@ def self.get_all_interface_output when 'SunOS' output = %x{/usr/sbin/ifconfig -a} when 'HP-UX' - output = %x{/bin/netstat -in | sed -e 1d} + # (#17487)[https://projects.puppetlabs.com/issues/17487] + # Handle NIC bonding where asterisks and virtual NICs are printed. + if output = hpux_netstat_in + output.gsub!(/\*/, "") # delete asterisks. + output.gsub!(/^[^\n]*none[^\n]*\n/, "") # delete lines with 'none' instead of IPs. + output.sub!(/^[^\n]*\n/, "") # delete the header line. + output + end when 'windows' output = %x|#{ENV['SYSTEMROOT']}/system32/netsh.exe interface ip show interface| output += %x|#{ENV['SYSTEMROOT']}/system32/netsh.exe interface ipv6 show interface| @@ -89,6 +96,13 @@ def self.get_all_interface_output output end + ## + # hpux_netstat_in is a delegate method that allows us to stub netstat -in + # without stubbing exec. + def self.hpux_netstat_in + Facter::Util::Resolution.exec("/bin/netstat -in") + end + def self.get_infiniband_macaddress(interface) if File.exists?("/sys/class/net/#{interface}/address") then ib_mac_address = `cat /sys/class/net/#{interface}/address`.chomp @@ -123,14 +137,22 @@ def self.get_single_interface_output(interface) output = %x{/usr/sbin/ifconfig #{interface}} when 'HP-UX' mac = "" - ifc = %x{/usr/sbin/ifconfig #{interface}} - %x{/usr/sbin/lanscan}.scan(/(\dx\S+).*UP\s+(\w+\d+)/).each {|i| mac = i[0] if i.include?(interface) } + ifc = hpux_ifconfig_interface(interface) + hpux_lanscan.scan(/(\dx\S+).*UP\s+(\w+\d+)/).each {|i| mac = i[0] if i.include?(interface) } mac = mac.sub(/0x(\S+)/,'\1').scan(/../).join(":") output = ifc + "\n" + mac end output end + def self.hpux_ifconfig_interface(interface) + Facter::Util::Resolution.exec("/usr/sbin/ifconfig #{interface}") + end + + def self.hpux_lanscan + Facter::Util::Resolution.exec("/usr/sbin/lanscan") + end + def self.get_output_for_interface_and_label(interface, label) return get_single_interface_output(interface) unless Facter.value(:kernel) == 'windows' diff --git a/spec/fixtures/unit/util/ip/hpux_1111_ifconfig_lan0 b/spec/fixtures/unit/util/ip/hpux_1111_ifconfig_lan0 new file mode 100644 index 0000000000..485814e1d7 --- /dev/null +++ b/spec/fixtures/unit/util/ip/hpux_1111_ifconfig_lan0 @@ -0,0 +1,2 @@ +lan0: flags=843 + inet 192.168.3.10 netmask ffffff00 broadcast 192.168.3.255 diff --git a/spec/fixtures/unit/util/ip/hpux_1111_ifconfig_lan1 b/spec/fixtures/unit/util/ip/hpux_1111_ifconfig_lan1 new file mode 100644 index 0000000000..c3d92776e0 --- /dev/null +++ b/spec/fixtures/unit/util/ip/hpux_1111_ifconfig_lan1 @@ -0,0 +1,2 @@ +lan1: flags=843 + inet 10.1.1.6 netmask ffffff00 broadcast 10.1.1.255 diff --git a/spec/fixtures/unit/util/ip/hpux_1111_ifconfig_lo0 b/spec/fixtures/unit/util/ip/hpux_1111_ifconfig_lo0 new file mode 100644 index 0000000000..5f09a3e2cc --- /dev/null +++ b/spec/fixtures/unit/util/ip/hpux_1111_ifconfig_lo0 @@ -0,0 +1,2 @@ +lo0: flags=849 + inet 127.0.0.1 netmask ff000000 diff --git a/spec/fixtures/unit/util/ip/hpux_1111_lanscan b/spec/fixtures/unit/util/ip/hpux_1111_lanscan new file mode 100644 index 0000000000..75ccd6cd7e --- /dev/null +++ b/spec/fixtures/unit/util/ip/hpux_1111_lanscan @@ -0,0 +1,5 @@ +Hardware Station Crd Hdw Net-Interface NM MAC HP-DLPI DLPI +Path Address In# State NamePPA ID Type Support Mjr# +0/0/0/0 0x00307F0C79DC 0 UP lan0 snap0 1 ETHER Yes 119 +0/10/0/0/6/0 0x0010797B5CDE 1 UP lan1 snap1 2 ETHER Yes 119 +0/10/0/0/7/0 0x0010797B5CDF 2 UP lan2 snap2 3 ETHER Yes 119 diff --git a/spec/fixtures/unit/util/ip/hpux_1111_netstat_in b/spec/fixtures/unit/util/ip/hpux_1111_netstat_in new file mode 100644 index 0000000000..1cfe0dee80 --- /dev/null +++ b/spec/fixtures/unit/util/ip/hpux_1111_netstat_in @@ -0,0 +1,4 @@ +Name Mtu Network Address Ipkts Opkts +lan1 1500 10.1.1.0 10.1.1.6 435347580 1287271 +lan0 1500 192.168.3.0 192.168.3.10 28101904 3569941319 +lo0 4136 127.0.0.0 127.0.0.1 5071536 5071539 diff --git a/spec/fixtures/unit/util/ip/hpux_1131_asterisk_ifconfig_lan0 b/spec/fixtures/unit/util/ip/hpux_1131_asterisk_ifconfig_lan0 new file mode 100644 index 0000000000..00088cfe2f --- /dev/null +++ b/spec/fixtures/unit/util/ip/hpux_1131_asterisk_ifconfig_lan0 @@ -0,0 +1,2 @@ +lan0: flags=843 + inet 192.168.3.9 netmask ffffff00 broadcast 192.168.3.255 diff --git a/spec/fixtures/unit/util/ip/hpux_1131_asterisk_ifconfig_lan1 b/spec/fixtures/unit/util/ip/hpux_1131_asterisk_ifconfig_lan1 new file mode 100644 index 0000000000..df739cb523 --- /dev/null +++ b/spec/fixtures/unit/util/ip/hpux_1131_asterisk_ifconfig_lan1 @@ -0,0 +1,2 @@ +lan1: flags=842 + inet 10.10.0.5 netmask ffffff00 broadcast 10.10.0.255 diff --git a/spec/fixtures/unit/util/ip/hpux_1131_asterisk_ifconfig_lo0 b/spec/fixtures/unit/util/ip/hpux_1131_asterisk_ifconfig_lo0 new file mode 100644 index 0000000000..5f09a3e2cc --- /dev/null +++ b/spec/fixtures/unit/util/ip/hpux_1131_asterisk_ifconfig_lo0 @@ -0,0 +1,2 @@ +lo0: flags=849 + inet 127.0.0.1 netmask ff000000 diff --git a/spec/fixtures/unit/util/ip/hpux_1131_asterisk_lanscan b/spec/fixtures/unit/util/ip/hpux_1131_asterisk_lanscan new file mode 100644 index 0000000000..a2dd634a14 --- /dev/null +++ b/spec/fixtures/unit/util/ip/hpux_1131_asterisk_lanscan @@ -0,0 +1,5 @@ +Hardware Station Crd Hdw Net-Interface NM MAC HP-DLPI DLPI +Path Address In# State NamePPA ID Type Support Mjr# +0/0/0/0 0x00305D0626B2 0 UP lan0 snap0 1 ETHER Yes 119 +0/4/2/0/6/0 0x0010797BBE46 1 UP lan1 snap1 2 ETHER Yes 119 +0/4/2/0/7/0 0x0010797BBE47 2 DOWN lan2 snap2 3 ETHER Yes 119 diff --git a/spec/fixtures/unit/util/ip/hpux_1131_asterisk_netstat_in b/spec/fixtures/unit/util/ip/hpux_1131_asterisk_netstat_in new file mode 100644 index 0000000000..c1c7da6fea --- /dev/null +++ b/spec/fixtures/unit/util/ip/hpux_1131_asterisk_netstat_in @@ -0,0 +1,4 @@ +Name Mtu Network Address Ipkts Ierrs Opkts Oerrs Coll +lan1* 1500 10.10.0.0 10.10.0.5 786 0 240 0 0 +lan0 1500 192.168.3.0 192.168.3.9 1823744990 0 23598735 0 0 +lo0 4136 127.0.0.0 127.0.0.1 7048047 0 7048047 0 0 diff --git a/spec/fixtures/unit/util/ip/hpux_1131_ifconfig_lan0 b/spec/fixtures/unit/util/ip/hpux_1131_ifconfig_lan0 new file mode 100644 index 0000000000..f91acc4af7 --- /dev/null +++ b/spec/fixtures/unit/util/ip/hpux_1131_ifconfig_lan0 @@ -0,0 +1,2 @@ +lan0: flags=1843 + inet 192.168.30.152 netmask ffffff00 broadcast 192.168.30.255 diff --git a/spec/fixtures/unit/util/ip/hpux_1131_ifconfig_lan1 b/spec/fixtures/unit/util/ip/hpux_1131_ifconfig_lan1 new file mode 100644 index 0000000000..07a6f5b3d8 --- /dev/null +++ b/spec/fixtures/unit/util/ip/hpux_1131_ifconfig_lan1 @@ -0,0 +1,2 @@ +lan1: flags=1843 + inet 10.1.54.36 netmask ffffff00 broadcast 10.1.54.255 diff --git a/spec/fixtures/unit/util/ip/hpux_1131_ifconfig_lo0 b/spec/fixtures/unit/util/ip/hpux_1131_ifconfig_lo0 new file mode 100644 index 0000000000..5f09a3e2cc --- /dev/null +++ b/spec/fixtures/unit/util/ip/hpux_1131_ifconfig_lo0 @@ -0,0 +1,2 @@ +lo0: flags=849 + inet 127.0.0.1 netmask ff000000 diff --git a/spec/fixtures/unit/util/ip/hpux_1131_lanscan b/spec/fixtures/unit/util/ip/hpux_1131_lanscan new file mode 100644 index 0000000000..4c78ee1fe8 --- /dev/null +++ b/spec/fixtures/unit/util/ip/hpux_1131_lanscan @@ -0,0 +1,4 @@ +Hardware Station Crd Hdw Net-Interface NM MAC HP-DLPI DLPI +Path Address In# State NamePPA ID Type Support Mjr# +0/1/2/0 0x0012317D6209 0 UP lan0 snap0 1 ETHER Yes 119 +0/6/1/0 0x0017FD2D2A57 1 UP lan1 snap1 2 ETHER Yes 119 diff --git a/spec/fixtures/unit/util/ip/hpux_1131_netstat_in b/spec/fixtures/unit/util/ip/hpux_1131_netstat_in new file mode 100644 index 0000000000..6656d41b6d --- /dev/null +++ b/spec/fixtures/unit/util/ip/hpux_1131_netstat_in @@ -0,0 +1,4 @@ +Name Mtu Network Address Ipkts Ierrs Opkts Oerrs Coll +lan1 1500 10.1.54.0 10.1.54.36 117489535 0 1681709 0 79 +lan0 1500 192.168.30.0 192.168.30.152 964843646 0 1668475345 0 0 +lo0 4136 127.0.0.0 127.0.0.1 4658855 0 4658855 0 0 diff --git a/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lan1 b/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lan1 new file mode 100644 index 0000000000..188cf2a710 --- /dev/null +++ b/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lan1 @@ -0,0 +1,2 @@ +lan1: flags=1843 + inet 192.168.30.32 netmask ffffff00 broadcast 192.168.30.255 diff --git a/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lan4 b/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lan4 new file mode 100644 index 0000000000..868f2e474f --- /dev/null +++ b/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lan4 @@ -0,0 +1,2 @@ +lan4: flags=1843 + inet 192.168.32.75 netmask ffffff00 broadcast 192.168.32.255 diff --git a/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lan4_1 b/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lan4_1 new file mode 100644 index 0000000000..7a19bd4519 --- /dev/null +++ b/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lan4_1 @@ -0,0 +1,2 @@ +lan4:1: flags=1843 + inet 192.168.1.197 netmask ffffff00 broadcast 192.168.1.255 diff --git a/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lo0 b/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lo0 new file mode 100644 index 0000000000..5f09a3e2cc --- /dev/null +++ b/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lo0 @@ -0,0 +1,2 @@ +lo0: flags=849 + inet 127.0.0.1 netmask ff000000 diff --git a/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_lanscan b/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_lanscan new file mode 100644 index 0000000000..0365d63632 --- /dev/null +++ b/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_lanscan @@ -0,0 +1,9 @@ +Hardware Station Crd Hdw Net-Interface NM MAC HP-DLPI DLPI +Path Address In# State NamePPA ID Type Support Mjr# +0/1/2/0 0x001319BD1C2D 0 UP lan0 snap0 1 ETHER Yes 119 +0/2/1/0 0x0012819E48DE 1 UP lan1 snap1 2 ETHER Yes 119 +0/2/1/1 0x0012819E48DF 2 UP lan2 snap2 3 ETHER Yes 119 +0/4/2/0/6/0 0x001165EB7385 5 UP lan5 snap5 4 ETHER Yes 119 +0/5/2/0/6/0 0x001165EB73E6 3 UP lan3 snap3 5 ETHER Yes 119 +0/6/1/0 0x0012819E4A7E 4 UP lan4 snap4 6 ETHER Yes 119 +0/6/1/1 0x0012819E4A7F 6 UP lan6 snap6 7 ETHER Yes 119 diff --git a/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_netstat_in b/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_netstat_in new file mode 100644 index 0000000000..ca5f915eb5 --- /dev/null +++ b/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_netstat_in @@ -0,0 +1,6 @@ +Name Mtu Network Address Ipkts Ierrs Opkts Oerrs Coll +lan4:1 1500 192.168.1.0 192.168.1.197 121 0 6 0 0 +lan3* 1500 none none 0 0 0 0 0 +lan1 1500 192.168.30.0 192.168.30.32 211188606 0 132070934 0 0 +lo0 4136 127.0.0.0 127.0.0.1 513508160 0 513509185 0 0 +lan4 1500 192.168.32.0 192.168.32.75 2640827721 0 2257447701 0 0 diff --git a/spec/fixtures/unit/util/ip/hpux_ifconfig_single_interface b/spec/fixtures/unit/util/ip/hpux_ifconfig_single_interface deleted file mode 100644 index ff54eb8c75..0000000000 --- a/spec/fixtures/unit/util/ip/hpux_ifconfig_single_interface +++ /dev/null @@ -1,3 +0,0 @@ -lan0: flags=1843 - inet 168.24.80.71 netmask ffffff00 broadcast 168.24.80.255 -00:13:21:BD:9C:B7 diff --git a/spec/fixtures/unit/util/ip/hpux_netstat_all_interfaces b/spec/fixtures/unit/util/ip/hpux_netstat_all_interfaces deleted file mode 100644 index 0e8f9dc0bf..0000000000 --- a/spec/fixtures/unit/util/ip/hpux_netstat_all_interfaces +++ /dev/null @@ -1,3 +0,0 @@ -lan1 1500 192.168.100.0 192.168.100.182 12964 0 900 0 0 -lan0 1500 192.168.100.0 192.168.100.181 12964 0 715 0 0 -lo0 4136 127.0.0.0 127.0.0.1 98 0 98 0 0 diff --git a/spec/unit/util/ip_spec.rb b/spec/unit/util/ip_spec.rb index db9f34c57e..79ebe6a8b8 100755 --- a/spec/unit/util/ip_spec.rb +++ b/spec/unit/util/ip_spec.rb @@ -42,12 +42,6 @@ Facter::Util::IP.get_interfaces().should == ["lo0", "e1000g0"] end - it "should return a list three interfaces on HP-UX with three interfaces multiply reporting" do - hpux_netstat = my_fixture_read("hpux_netstat_all_interfaces") - Facter::Util::IP.stubs(:get_all_interface_output).returns(hpux_netstat) - Facter::Util::IP.get_interfaces().should == ["lan1", "lan0", "lo0"] - end - it "should return a list of six interfaces on a GNU/kFreeBSD with six interfaces" do kfreebsd_ifconfig = my_fixture_read("debian_kfreebsd_ifconfig") Facter::Util::IP.stubs(:get_all_interface_output).returns(kfreebsd_ifconfig) @@ -97,24 +91,6 @@ Facter::Util::IP.get_network_value("e1000g0").should == "172.16.15.0" end - it "should return ipaddress information for HP-UX" do - hpux_ifconfig_interface = my_fixture_read("hpux_ifconfig_single_interface") - - Facter::Util::IP.expects(:get_single_interface_output).with("lan0").returns(hpux_ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("HP-UX") - - Facter::Util::IP.get_interface_value("lan0", "ipaddress").should == "168.24.80.71" - end - - it "should return macaddress information for HP-UX" do - hpux_ifconfig_interface = my_fixture_read("hpux_ifconfig_single_interface") - - Facter::Util::IP.expects(:get_single_interface_output).with("lan0").returns(hpux_ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("HP-UX") - - Facter::Util::IP.get_interface_value("lan0", "macaddress").should == "00:13:21:BD:9C:B7" - end - it "should return macaddress with leading zeros stripped off for GNU/kFreeBSD" do kfreebsd_ifconfig = my_fixture_read("debian_kfreebsd_ifconfig") @@ -124,24 +100,6 @@ Facter::Util::IP.get_interface_value("em0", "macaddress").should == "0:11:a:59:67:90" end - it "should return netmask information for HP-UX" do - hpux_ifconfig_interface = my_fixture_read("hpux_ifconfig_single_interface") - - Facter::Util::IP.expects(:get_single_interface_output).with("lan0").returns(hpux_ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("HP-UX") - - Facter::Util::IP.get_interface_value("lan0", "netmask").should == "255.255.255.0" - end - - it "should return calculated network information for HP-UX" do - hpux_ifconfig_interface = my_fixture_read("hpux_ifconfig_single_interface") - - Facter::Util::IP.stubs(:get_single_interface_output).with("lan0").returns(hpux_ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("HP-UX") - - Facter::Util::IP.get_network_value("lan0").should == "168.24.80.0" - end - it "should return interface information for FreeBSD supported via an alias" do ifconfig_interface = my_fixture_read("6.0-STABLE_FreeBSD_ifconfig") @@ -178,15 +136,6 @@ Facter::Util::IP.get_interface_value("e1000g0", "netmask").should == "255.255.255.0" end - it "should return a human readable netmask on HP-UX" do - hpux_ifconfig_interface = my_fixture_read("hpux_ifconfig_single_interface") - - Facter::Util::IP.expects(:get_single_interface_output).with("lan0").returns(hpux_ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("HP-UX") - - Facter::Util::IP.get_interface_value("lan0", "netmask").should == "255.255.255.0" - end - it "should return a human readable netmask on Darwin" do darwin_ifconfig_interface = my_fixture_read("darwin_ifconfig_single_interface") @@ -292,6 +241,135 @@ Facter::Util::IP.get_interface_value("e1000g0", "mtu").should == "1500" end + # (#17487) - tests for HP-UX. + # some fake data for testing robustness of regexps. + def self.fake_netstat_in_examples + examples = [] + examples << ["Header row\na line with none in it\na line without\nanother line without\n", + "a line without\nanother line without\n"] + examples << ["Header row\na line without\na line with none in it\nanother line with none\nanother line without\n", + "a line without\nanother line without\n"] + examples << ["Header row\na line with * asterisks *\na line with none in it\nanother line without\n", + "a line with asterisks \nanother line without\n"] + examples << ["a line with none none none in it\na line with none in it\na line without\nanother line without\n", + "another line without\n"] + examples + end + + fake_netstat_in_examples.each_with_index do |example, i| + input, expected_output = example + it "should pass regexp test on fake netstat input example #{i}" do + Facter.stubs(:value).with(:kernel).returns("HP-UX") + Facter::Util::IP.stubs(:hpux_netstat_in).returns(input) + Facter::Util::IP.get_all_interface_output().should == expected_output + end + end + + # and some real data for exhaustive tests. + def self.hpux_examples + examples = [] + examples << ["HP-UX 11.11", + ["lan1", "lan0", "lo0" ], + ["1500", "1500", "4136" ], + ["10.1.1.6", "192.168.3.10", "127.0.0.1"], + ["255.255.255.0", "255.255.255.0", "255.0.0.0"], + ["00:10:79:7B:5C:DE", "00:30:7F:0C:79:DC", nil ], + [my_fixture_read("hpux_1111_ifconfig_lan1"), + my_fixture_read("hpux_1111_ifconfig_lan0"), + my_fixture_read("hpux_1111_ifconfig_lo0")], + my_fixture_read("hpux_1111_netstat_in"), + my_fixture_read("hpux_1111_lanscan")] + + examples << ["HP-UX 11.31", + ["lan1", "lan0", "lo0" ], + ["1500", "1500", "4136" ], + ["10.1.54.36", "192.168.30.152", "127.0.0.1"], + ["255.255.255.0", "255.255.255.0", "255.0.0.0"], + ["00:17:FD:2D:2A:57", "00:12:31:7D:62:09", nil ], + [my_fixture_read("hpux_1131_ifconfig_lan1"), + my_fixture_read("hpux_1131_ifconfig_lan0"), + my_fixture_read("hpux_1131_ifconfig_lo0")], + my_fixture_read("hpux_1131_netstat_in"), + my_fixture_read("hpux_1131_lanscan")] + + examples << ["HP-UX 11.31 with an asterisk after a NIC that has an address", + ["lan1", "lan0", "lo0" ], + ["1500", "1500", "4136" ], + ["10.10.0.5", "192.168.3.9", "127.0.0.1"], + ["255.255.255.0", "255.255.255.0", "255.0.0.0"], + ["00:10:79:7B:BE:46", "00:30:5D:06:26:B2", nil ], + [my_fixture_read("hpux_1131_asterisk_ifconfig_lan1"), + my_fixture_read("hpux_1131_asterisk_ifconfig_lan0"), + my_fixture_read("hpux_1131_asterisk_ifconfig_lo0")], + my_fixture_read("hpux_1131_asterisk_netstat_in"), + my_fixture_read("hpux_1131_asterisk_lanscan")] + + examples << ["HP-UX 11.31 with NIC bonding and one virtual NIC", + ["lan4:1", "lan1", "lo0", "lan4" ], + ["1500", "1500", "4136", "1500" ], + ["192.168.1.197", "192.168.30.32", "127.0.0.1", "192.168.32.75" ], + ["255.255.255.0", "255.255.255.0", "255.0.0.0", "255.255.255.0" ], + [nil, "00:12:81:9E:48:DE", nil, "00:12:81:9E:4A:7E"], + [my_fixture_read("hpux_1131_nic_bonding_ifconfig_lan4_1"), + my_fixture_read("hpux_1131_nic_bonding_ifconfig_lan1"), + my_fixture_read("hpux_1131_nic_bonding_ifconfig_lo0"), + my_fixture_read("hpux_1131_nic_bonding_ifconfig_lan4")], + my_fixture_read("hpux_1131_nic_bonding_netstat_in"), + my_fixture_read("hpux_1131_nic_bonding_lanscan")] + examples + end + + hpux_examples.each do |example| + description, array_of_expected_ifs, array_of_expected_mtus, + array_of_expected_ips, array_of_expected_netmasks, + array_of_expected_macs, array_of_ifconfig_fixtures, + netstat_in_fixture, lanscan_fixture = example + + it "should return a list three interfaces on #{description}" do + Facter.stubs(:value).with(:kernel).returns("HP-UX") + Facter::Util::IP.stubs(:hpux_netstat_in).returns(netstat_in_fixture) + Facter::Util::IP.get_interfaces().should == array_of_expected_ifs + end + + array_of_expected_ifs.each_with_index do |nic, i| + ifconfig_fixture = array_of_ifconfig_fixtures[i] + expected_mtu = array_of_expected_mtus[i] + expected_ip = array_of_expected_ips[i] + expected_netmask = array_of_expected_netmasks[i] + expected_mac = array_of_expected_macs[i] + + # (#17808) These tests fail because MTU facts haven't been implemented for HP-UX. + #it "should return MTU #{expected_mtu} on #{nic} for #{description} example" do + # Facter.stubs(:value).with(:kernel).returns("HP-UX") + # Facter::Util::IP.stubs(:hpux_netstat_in).returns(netstat_in_fixture) + # Facter::Util::IP.stubs(:hpux_lanscan).returns(lanscan_fixture) + # Facter::Util::IP.stubs(:hpux_ifconfig_interface).with(nic).returns(ifconfig_fixture) + # Facter::Util::IP.get_interface_value(nic, "mtu").should == expected_mtu + #end + + it "should return IP #{expected_ip} on #{nic} for #{description} example" do + Facter.stubs(:value).with(:kernel).returns("HP-UX") + Facter::Util::IP.stubs(:hpux_lanscan).returns(lanscan_fixture) + Facter::Util::IP.stubs(:hpux_ifconfig_interface).with(nic).returns(ifconfig_fixture) + Facter::Util::IP.get_interface_value(nic, "ipaddress").should == expected_ip + end + + it "should return netmask #{expected_netmask} on #{nic} for #{description} example" do + Facter.stubs(:value).with(:kernel).returns("HP-UX") + Facter::Util::IP.stubs(:hpux_lanscan).returns(lanscan_fixture) + Facter::Util::IP.stubs(:hpux_ifconfig_interface).with(nic).returns(ifconfig_fixture) + Facter::Util::IP.get_interface_value(nic, "netmask").should == expected_netmask + end + + it "should return MAC address #{expected_mac} on #{nic} for #{description} example" do + Facter.stubs(:value).with(:kernel).returns("HP-UX") + Facter::Util::IP.stubs(:hpux_lanscan).returns(lanscan_fixture) + Facter::Util::IP.stubs(:hpux_ifconfig_interface).with(nic).returns(ifconfig_fixture) + Facter::Util::IP.get_interface_value(nic, "macaddress").should == expected_mac + end + end + end + describe "on Windows" do before :each do Facter.stubs(:value).with(:kernel).returns("windows") From 0b672ed06f0ac96209935fbefab9bc5a57507277 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Thu, 29 Nov 2012 09:43:42 -0800 Subject: [PATCH 1092/3753] (#17855) Rescue Timeout::Error Previously, if the Facter::EC2.can_connect? method timed-out, an instance of Timeout::Error would be raised. Under ruby 1.8.7, this class is not a StandardError, and so wasn't rescued. And since the can_connect? method is called when the ec2 code is loaded (as opposed to a setcode block), the exception was never caught causing facter to exit. In ruby 1.9.3, Timeout::Error is a StandardError, so the default `rescue` block catches it. This issue was caused by commit b336259e when trying to fix #17493. This commit restores the explicit rescue of Timeout::Error so that it behaves consistently on all supported ruby versions. --- lib/facter/ec2.rb | 3 +-- lib/facter/util/ec2.rb | 2 ++ spec/unit/ec2_spec.rb | 6 ++++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/facter/ec2.rb b/lib/facter/ec2.rb index e314d24762..418e60b171 100644 --- a/lib/facter/ec2.rb +++ b/lib/facter/ec2.rb @@ -26,9 +26,8 @@ def userdata() end end -if (Facter::Util::EC2.has_euca_mac? || Facter::Util::EC2.has_openstack_mac? || +if (Facter::Util::EC2.has_euca_mac? || Facter::Util::EC2.has_openstack_mac? || Facter::Util::EC2.has_ec2_arp?) && Facter::Util::EC2.can_connect? - metadata userdata else diff --git a/lib/facter/util/ec2.rb b/lib/facter/util/ec2.rb index 04668bde24..8f024d4a24 100644 --- a/lib/facter/util/ec2.rb +++ b/lib/facter/util/ec2.rb @@ -14,6 +14,8 @@ def can_connect?(wait_sec=2) url = "http://169.254.169.254/" Timeout::timeout(wait_sec) {open(url)} return true + rescue Timeout::Error + return false rescue return false end diff --git a/spec/unit/ec2_spec.rb b/spec/unit/ec2_spec.rb index 431b666592..8b3bc333b6 100755 --- a/spec/unit/ec2_spec.rb +++ b/spec/unit/ec2_spec.rb @@ -170,5 +170,11 @@ Facter.fact(:ec2_userdata).should == nil end + + it "should rescue the exception" do + Facter::Util::EC2.expects(:open).with("#{api_prefix}:80/").raises(Timeout::Error) + + Facter::Util::EC2.should_not be_can_connect + end end end From edcfa9b4314dde30d018f3b8d63dbcbb749286f8 Mon Sep 17 00:00:00 2001 From: Matthaus Owens Date: Wed, 28 Nov 2012 22:49:06 -0800 Subject: [PATCH 1093/3753] (#17854) Allow install.rb to execute from outside of source root Previously if install.rb was invoked when outside of the source root, all of the globs would fail to find bins, libs, etc, or worse if run from / it will find the binaries in /bin and copy them to bindir. This commit addresses that by running the install after cding into the directory containing the install.rb file. --- install.rb | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/install.rb b/install.rb index 4788af9413..a31175b1ef 100755 --- a/install.rb +++ b/install.rb @@ -74,14 +74,6 @@ def glob(list) g end -# Set these values to what you want installed. -bins = glob(%w{bin/*}) -rdoc = glob(%w{bin/* lib/**/*.rb README README-library TODO }).reject { |e| e=~ /\.(bat|cmd)$/ } -ri = glob(%w(bin/*.rb lib/**/*.rb)).reject { |e| e=~ /\.(bat|cmd)$/ } -man = glob(%w{man/man8/*}) -libs = glob(%w{lib/**/*.rb lib/**/*.py lib/**/LICENSE}) -tests = glob(%w{tests/**/*.rb}) - def do_bins(bins, target, strip = 's?bin/') bins.each do |bf| obf = bf.gsub(/#{strip}/, '') @@ -409,13 +401,24 @@ def install_binfile(from, op_file, target) tmp_file.unlink end -check_prereqs -prepare_installation - -run_tests(tests) if InstallOptions.tests -#build_rdoc(rdoc) if InstallOptions.rdoc -#build_ri(ri) if InstallOptions.ri -#build_man(bins) if InstallOptions.man -do_bins(bins, InstallOptions.bin_dir) -do_libs(libs) -do_man(man) +# Change directory into the facter root so we don't get the wrong files for install. +FileUtils.cd File.dirname(__FILE__) do + # Set these values to what you want installed. + bins = glob(%w{bin/*}) + rdoc = glob(%w{bin/* lib/**/*.rb README README-library TODO }).reject { |e| e=~ /\.(bat|cmd)$/ } + ri = glob(%w(bin/*.rb lib/**/*.rb)).reject { |e| e=~ /\.(bat|cmd)$/ } + man = glob(%w{man/man8/*}) + libs = glob(%w{lib/**/*.rb lib/**/*.py lib/**/LICENSE}) + tests = glob(%w{tests/**/*.rb}) + + check_prereqs + prepare_installation + + run_tests(tests) if InstallOptions.tests + #build_rdoc(rdoc) if InstallOptions.rdoc + #build_ri(ri) if InstallOptions.ri + #build_man(bins) if InstallOptions.man + do_bins(bins, InstallOptions.bin_dir) + do_libs(libs) + do_man(man) +end From 682676dce31b9cadc5c1ffb35004594135cbb3a5 Mon Sep 17 00:00:00 2001 From: Matthaus Owens Date: Thu, 29 Nov 2012 13:47:50 -0800 Subject: [PATCH 1094/3753] Remove unused code, globs from install.rb The run_tests method and tests globs don't apply to facter, which has no test directory, so this commit removes that method and its call. The rdoc globs refrenced files which no longer exist, so they have been cleaned up and existing files are left. --- install.rb | 36 +++++------------------------------- 1 file changed, 5 insertions(+), 31 deletions(-) diff --git a/install.rb b/install.rb index a31175b1ef..c7269fa69d 100755 --- a/install.rb +++ b/install.rb @@ -14,20 +14,17 @@ # # bin/ # executable files -- "commands" # lib/ # the source of the library -# tests/ # unit tests # # The default behaviour: -# 1) Run all unit test files (ending in .rb) found in all directories under -# tests/. -# 2) Build Rdoc documentation from all files in bin/ (excluding .bat and .cmd), +# 1) Build Rdoc documentation from all files in bin/ (excluding .bat and .cmd), # all .rb files in lib/, ./README, ./ChangeLog, and ./Install. -# 3) Build ri documentation from all files in bin/ (excluding .bat and .cmd), +# 2) Build ri documentation from all files in bin/ (excluding .bat and .cmd), # and all .rb files in lib/. This is disabled by default on Win32. -# 4) Install commands from bin/ into the Ruby bin directory. On Windows, if a +# 3) Install commands from bin/ into the Ruby bin directory. On Windows, if a # if a corresponding batch file (.bat or .cmd) exists in the bin directory, # it will be copied over as well. Otherwise, a batch file (always .bat) will # be created to run the specified command. -# 5) Install all library files ending in .rb from lib/ into Ruby's +# 4) Install all library files ending in .rb from lib/ into Ruby's # site_lib/version directory. # #++ @@ -324,27 +321,6 @@ def build_man(bins) end end -def run_tests(test_list) - begin - require 'test/unit/ui/console/testrunner' - $:.unshift "lib" - test_list.each do |test| - next if File.directory?(test) - require test - end - - tests = [] - ObjectSpace.each_object { |o| tests << o if o.kind_of?(Class) } - tests.delete_if { |o| !o.ancestors.include?(Test::Unit::TestCase) } - tests.delete_if { |o| o == Test::Unit::TestCase } - - tests.each { |test| Test::Unit::UI::Console::TestRunner.run(test) } - $:.shift - rescue LoadError - puts "Missing testrunner library; skipping tests" - end -end - ## # Install file(s) from ./bin to RbConfig::CONFIG['bindir']. Patch it on the way # to insert a #! line; on a Unix install, the command is named as expected @@ -405,16 +381,14 @@ def install_binfile(from, op_file, target) FileUtils.cd File.dirname(__FILE__) do # Set these values to what you want installed. bins = glob(%w{bin/*}) - rdoc = glob(%w{bin/* lib/**/*.rb README README-library TODO }).reject { |e| e=~ /\.(bat|cmd)$/ } + rdoc = glob(%w{bin/* lib/**/*.rb README* }).reject { |e| e=~ /\.(bat|cmd)$/ } ri = glob(%w(bin/*.rb lib/**/*.rb)).reject { |e| e=~ /\.(bat|cmd)$/ } man = glob(%w{man/man8/*}) libs = glob(%w{lib/**/*.rb lib/**/*.py lib/**/LICENSE}) - tests = glob(%w{tests/**/*.rb}) check_prereqs prepare_installation - run_tests(tests) if InstallOptions.tests #build_rdoc(rdoc) if InstallOptions.rdoc #build_ri(ri) if InstallOptions.ri #build_man(bins) if InstallOptions.man From 7eb4d387f476d79910ac9b3b6a298a29f063bb25 Mon Sep 17 00:00:00 2001 From: Matthaus Owens Date: Thu, 29 Nov 2012 14:05:50 -0800 Subject: [PATCH 1095/3753] Add deprecation warning for --tests flag in install.rb The tests flag does nothing in facter and has never been functional. This commit removes some of the points where tests is set and also adds a deprecation warning indicating that the tests flags will be removed in the future. It also changes the default from on to off for InstallOptions.tests. --- install.rb | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/install.rb b/install.rb index c7269fa69d..ba4ffc0398 100755 --- a/install.rb +++ b/install.rb @@ -148,8 +148,6 @@ def prepare_installation InstallOptions.man = false end - InstallOptions.tests = true - ARGV.options do |opts| opts.banner = "Usage: #{File.basename($0)} [options]" opts.separator "" @@ -162,8 +160,9 @@ def prepare_installation opts.on('--[no-]man', 'Presents the creation of man pages.', 'Default on.') do |onman| InstallOptions.man = onman end - opts.on('--[no-]tests', 'Prevents the execution of unit tests.', 'Default on.') do |ontest| + opts.on('--[no-]tests', 'Prevents the execution of unit tests.', 'Default off.') do |ontest| InstallOptions.tests = ontest + warn "The tests flag has never worked in Facter, is deprecated as of Nov 29, 2012, and will be removed in a future release of Facter." end opts.on('--destdir[=OPTIONAL]', 'Installation prefix for all targets', 'Default essentially /') do |destdir| InstallOptions.destdir = destdir @@ -183,12 +182,10 @@ def prepare_installation opts.on('--quick', 'Performs a quick installation. Only the', 'installation is done.') do |quick| InstallOptions.rdoc = false InstallOptions.ri = false - InstallOptions.tests = false end opts.on('--full', 'Performs a full installation. All', 'optional installation steps are run.') do |full| InstallOptions.rdoc = true InstallOptions.ri = true - InstallOptions.tests = true end opts.separator("") opts.on_tail('--help', "Shows this help text.") do From 1c8b9cc5839f72df6eebf3d39d863dfce73d079c Mon Sep 17 00:00:00 2001 From: Andrew Parker Date: Fri, 30 Nov 2012 11:44:34 -0800 Subject: [PATCH 1096/3753] (Maint) Remove loader test dependence on ENV Previously the test for checking that FACTER_* environment variables get turned into facts was sensitive to the actual environment variables with which the test ran. This caused incorrect test failures if there was such an environment variable during the test run. This changes the loader to be able to be given the environment hash that it should look at and changes the tests to stop relying on the environment for testing. --- lib/facter/util/loader.rb | 9 +-- spec/unit/util/loader_spec.rb | 114 +++++++++++++++++----------------- 2 files changed, 63 insertions(+), 60 deletions(-) diff --git a/lib/facter/util/loader.rb b/lib/facter/util/loader.rb index 87008ffa54..163d378035 100644 --- a/lib/facter/util/loader.rb +++ b/lib/facter/util/loader.rb @@ -5,9 +5,10 @@ # Load facts on demand. class Facter::Util::Loader - def initialize + def initialize(environment_vars = ENV) @loaded = [] @valid_path = {} + @environment_vars = environment_vars end # Load all resolutions for a single fact. @@ -55,8 +56,8 @@ def load_all def search_path result = [] result += $LOAD_PATH.collect { |d| File.join(d, "facter") } - if ENV.include?("FACTERLIB") - result += ENV["FACTERLIB"].split(File::PATH_SEPARATOR) + if @environment_vars.include?("FACTERLIB") + result += @environment_vars["FACTERLIB"].split(File::PATH_SEPARATOR) end # This allows others to register additional paths we should search. @@ -105,7 +106,7 @@ def load_file(file) # all will be loaded. def load_env(fact = nil) # Load from the environment, if possible - ENV.each do |name, value| + @environment_vars.each do |name, value| # Skip anything that doesn't match our regex. next unless name =~ /^facter_?(\w+)$/i env_name = $1 diff --git a/spec/unit/util/loader_spec.rb b/spec/unit/util/loader_spec.rb index 2b9891e413..0701d10054 100755 --- a/spec/unit/util/loader_spec.rb +++ b/spec/unit/util/loader_spec.rb @@ -21,6 +21,14 @@ def load_file(file) Facter::Util::Loader.any_instance.unstub(:load_all) end + def loader_from(places) + env = places[:env] || {} + search_path = places[:search_path] || [] + loader = Facter::Util::Loader.new(env) + loader.stubs(:search_path).returns search_path + loader + end + it "should have a method for loading individual facts by name" do Facter::Util::Loader.new.should respond_to(:load) end @@ -133,32 +141,27 @@ def load_file(file) describe "and the FACTERLIB environment variable is set" do it "should include all paths in FACTERLIB" do - Facter::Util::Resolution.with_env "FACTERLIB" => "/one/path#{File::PATH_SEPARATOR}/two/path" do - paths = @loader.search_path - %w{/one/path /two/path}.each do |dir| - paths.should be_include(dir) - end + loader = Facter::Util::Loader.new("FACTERLIB" => "/one/path#{File::PATH_SEPARATOR}/two/path") + + paths = loader.search_path + %w{/one/path /two/path}.each do |dir| + paths.should be_include(dir) end end end end describe "when loading facts" do - before do - @loader = Facter::Util::Loader.new - @loader.stubs(:search_path).returns [] - end - it "should load values from the matching environment variable if one is present" do + loader = loader_from(:env => { "facter_testing" => "yayness" }) + Facter.expects(:add).with("testing") - Facter::Util::Resolution.with_env "facter_testing" => "yayness" do - @loader.load(:testing) - end + loader.load(:testing) end it "should load any files in the search path with names matching the fact name" do - @loader.expects(:search_path).returns %w{/one/dir /two/dir} + loader = loader_from(:search_path => %w{/one/dir /two/dir}) FileTest.stubs(:exist?).returns false FileTest.expects(:exist?).with("/one/dir/testing.rb").returns true FileTest.expects(:exist?).with("/two/dir/testing.rb").returns true @@ -166,16 +169,16 @@ def load_file(file) Kernel.expects(:load).with("/one/dir/testing.rb") Kernel.expects(:load).with("/two/dir/testing.rb") - @loader.load(:testing) + loader.load(:testing) end it 'should load any ruby files in directories matching the fact name in the search path in sorted order regardless of the order returned by Dir.entries' do - @loader = TestLoader.new + loader = TestLoader.new - @loader.stubs(:search_path).returns %w{/one/dir} + loader.stubs(:search_path).returns %w{/one/dir} FileTest.stubs(:exist?).returns false FileTest.stubs(:directory?).with("/one/dir/testing").returns true - @loader.stubs(:search_path).returns %w{/one/dir} + loader.stubs(:search_path).returns %w{/one/dir} Dir.stubs(:entries).with("/one/dir/testing").returns %w{foo.rb bar.rb} %w{/one/dir/testing/foo.rb /one/dir/testing/bar.rb}.each do |f| @@ -183,12 +186,12 @@ def load_file(file) Kernel.stubs(:load).with(f) end - @loader.load(:testing) - @loader.loaded_files.should == %w{/one/dir/testing/bar.rb /one/dir/testing/foo.rb} + loader.load(:testing) + loader.loaded_files.should == %w{/one/dir/testing/bar.rb /one/dir/testing/foo.rb} end it "should load any ruby files in directories matching the fact name in the search path" do - @loader.expects(:search_path).returns %w{/one/dir} + loader = loader_from(:search_path => %w{/one/dir}) FileTest.stubs(:exist?).returns false FileTest.expects(:directory?).with("/one/dir/testing").returns true @@ -196,11 +199,11 @@ def load_file(file) Kernel.expects(:load).with("/one/dir/testing/two.rb") - @loader.load(:testing) + loader.load(:testing) end it "should not load files that don't end in '.rb'" do - @loader.expects(:search_path).returns %w{/one/dir} + loader = loader_from(:search_path => %w{/one/dir}) FileTest.stubs(:exist?).returns false FileTest.expects(:directory?).with("/one/dir/testing").returns true @@ -208,41 +211,38 @@ def load_file(file) Kernel.expects(:load).never - @loader.load(:testing) + loader.load(:testing) end end describe "when loading all facts" do before :each do - @loader = Facter::Util::Loader.new - @loader.stubs(:search_path).returns [] - FileTest.stubs(:directory?).returns true end it "should skip directories that do not exist" do - @loader.expects(:search_path).returns %w{/one/dir} + loader = loader_from(:search_path => %w{/one/dir}) FileTest.expects(:directory?).with("/one/dir").returns false Dir.expects(:entries).with("/one/dir").never - @loader.load_all + loader.load_all end it "should load all files in all search paths" do - @loader.expects(:search_path).returns %w{/one/dir /two/dir} + loader = loader_from(:search_path => %w{/one/dir /two/dir}) Dir.expects(:entries).with("/one/dir").returns %w{a.rb b.rb} Dir.expects(:entries).with("/two/dir").returns %w{c.rb d.rb} %w{/one/dir/a.rb /one/dir/b.rb /two/dir/c.rb /two/dir/d.rb}.each { |f| Kernel.expects(:load).with(f) } - @loader.load_all + loader.load_all end it "should load all files in all subdirectories in all search paths" do - @loader.expects(:search_path).returns %w{/one/dir /two/dir} + loader = loader_from(:search_path => %w{/one/dir /two/dir}) Dir.expects(:entries).with("/one/dir").returns %w{a} Dir.expects(:entries).with("/two/dir").returns %w{b} @@ -254,13 +254,13 @@ def load_file(file) %w{/one/dir/a/c.rb /two/dir/b/d.rb}.each { |f| Kernel.expects(:load).with(f) } - @loader.load_all + loader.load_all end it 'should load all files in sorted order for any given directory regardless of the order returned by Dir.entries' do - @loader = TestLoader.new + loader = TestLoader.new - @loader.stubs(:search_path).returns %w{/one/dir} + loader.stubs(:search_path).returns %w{/one/dir} Dir.stubs(:entries).with("/one/dir").returns %w{foo.rb bar.rb} %w{/one/dir}.each { |f| File.stubs(:directory?).with(f).returns true } @@ -270,13 +270,13 @@ def load_file(file) Kernel.expects(:load).with(f) end - @loader.load_all + loader.load_all - @loader.loaded_files.should == %w{/one/dir/bar.rb /one/dir/foo.rb} + loader.loaded_files.should == %w{/one/dir/bar.rb /one/dir/foo.rb} end it "should not load files in the util subdirectory" do - @loader.expects(:search_path).returns %w{/one/dir} + loader = loader_from(:search_path => %w{/one/dir}) Dir.expects(:entries).with("/one/dir").returns %w{util} @@ -284,11 +284,11 @@ def load_file(file) Dir.expects(:entries).with("/one/dir/util").never - @loader.load_all + loader.load_all end it "should not load files in a lib subdirectory" do - @loader.expects(:search_path).returns %w{/one/dir} + loader = loader_from(:search_path => %w{/one/dir}) Dir.expects(:entries).with("/one/dir").returns %w{lib} @@ -296,51 +296,53 @@ def load_file(file) Dir.expects(:entries).with("/one/dir/lib").never - @loader.load_all + loader.load_all end it "should not load files in '.' or '..'" do - @loader.expects(:search_path).returns %w{/one/dir} + loader = loader_from(:search_path => %w{/one/dir}) Dir.expects(:entries).with("/one/dir").returns %w{. ..} File.expects(:entries).with("/one/dir/.").never File.expects(:entries).with("/one/dir/..").never - @loader.load_all + loader.load_all end it "should not raise an exception when a file is unloadable" do - @loader.expects(:search_path).returns %w{/one/dir} + loader = loader_from(:search_path => %w{/one/dir}) Dir.expects(:entries).with("/one/dir").returns %w{a.rb} Kernel.expects(:load).with("/one/dir/a.rb").raises(LoadError) - @loader.expects(:warn) + loader.expects(:warn) - lambda { @loader.load_all }.should_not raise_error + lambda { loader.load_all }.should_not raise_error end it "should load all facts from the environment" do + loader = loader_from(:env => { "facter_one" => "yayness", "facter_two" => "boo" }) + Facter.expects(:add).with('one') Facter.expects(:add).with('two') - Facter::Util::Resolution.with_env "facter_one" => "yayness", "facter_two" => "boo" do - @loader.load_all - end + loader.load_all end it "should only load all facts one time" do - @loader.expects(:load_env).once - @loader.load_all - @loader.load_all + loader = loader_from(:env => {}) + loader.expects(:load_env).once + + loader.load_all + loader.load_all end end it "should load facts on the facter search path only once" do facterlibdir = File.expand_path(File.dirname(__FILE__) + '../../../fixtures/unit/util/loader') - Facter::Util::Resolution.with_env 'FACTERLIB' => facterlibdir do - Facter::Util::Loader.new.load_all - Facter.value(:nosuchfact).should be_nil - end + + Facter::Util::Loader.new('FACTERLIB' => facterlibdir).load_all + + Facter.value(:nosuchfact).should be_nil end end From 86234655ee1db9fac4b81b7c05c35df12b9f0e2a Mon Sep 17 00:00:00 2001 From: Andrew Parker Date: Fri, 30 Nov 2012 12:04:27 -0800 Subject: [PATCH 1097/3753] (Maint) Remove unneeded mocking There were mocks for something called 'settings', which were never used. This removes them. --- spec/unit/util/loader_spec.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/spec/unit/util/loader_spec.rb b/spec/unit/util/loader_spec.rb index 0701d10054..ec5fba6eae 100755 --- a/spec/unit/util/loader_spec.rb +++ b/spec/unit/util/loader_spec.rb @@ -44,8 +44,6 @@ def loader_from(places) describe "#valid_seach_path?" do before :each do @loader = Facter::Util::Loader.new - @settings = mock 'settings' - @settings.stubs(:value).returns "/eh" end it "should cache the result of a previous check" do @@ -100,8 +98,6 @@ def loader_from(places) describe "when determining the search path" do before do @loader = Facter::Util::Loader.new - @settings = mock 'settings' - @settings.stubs(:value).returns "/eh" end it "should include the facter subdirectory of all paths in ruby LOAD_PATH" do From a9dafe9bbf8a272e6615474ba5c782adce19ba17 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Thu, 29 Nov 2012 09:43:42 -0800 Subject: [PATCH 1098/3753] (#17855) Rescue Timeout::Error Previously, if the Facter::EC2.can_connect? method timed-out, an instance of Timeout::Error would be raised. Under ruby 1.8.7, this class is not a StandardError, and so wasn't rescued. And since the can_connect? method is called when the ec2 code is loaded (as opposed to a setcode block), the exception was never caught causing facter to exit. In ruby 1.9.3, Timeout::Error is a StandardError, so the default `rescue` block catches it. This issue was caused by commit b336259e when trying to fix #17493. This commit restores the explicit rescue of Timeout::Error so that it behaves consistently on all supported ruby versions. --- lib/facter/ec2.rb | 3 +-- lib/facter/util/ec2.rb | 2 ++ spec/unit/ec2_spec.rb | 6 ++++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/facter/ec2.rb b/lib/facter/ec2.rb index e314d24762..418e60b171 100644 --- a/lib/facter/ec2.rb +++ b/lib/facter/ec2.rb @@ -26,9 +26,8 @@ def userdata() end end -if (Facter::Util::EC2.has_euca_mac? || Facter::Util::EC2.has_openstack_mac? || +if (Facter::Util::EC2.has_euca_mac? || Facter::Util::EC2.has_openstack_mac? || Facter::Util::EC2.has_ec2_arp?) && Facter::Util::EC2.can_connect? - metadata userdata else diff --git a/lib/facter/util/ec2.rb b/lib/facter/util/ec2.rb index 04668bde24..8f024d4a24 100644 --- a/lib/facter/util/ec2.rb +++ b/lib/facter/util/ec2.rb @@ -14,6 +14,8 @@ def can_connect?(wait_sec=2) url = "http://169.254.169.254/" Timeout::timeout(wait_sec) {open(url)} return true + rescue Timeout::Error + return false rescue return false end diff --git a/spec/unit/ec2_spec.rb b/spec/unit/ec2_spec.rb index 431b666592..8b3bc333b6 100755 --- a/spec/unit/ec2_spec.rb +++ b/spec/unit/ec2_spec.rb @@ -170,5 +170,11 @@ Facter.fact(:ec2_userdata).should == nil end + + it "should rescue the exception" do + Facter::Util::EC2.expects(:open).with("#{api_prefix}:80/").raises(Timeout::Error) + + Facter::Util::EC2.should_not be_can_connect + end end end From 09ad0bb7596ae8d2b5b1bc394e7bd1d8d10b88a4 Mon Sep 17 00:00:00 2001 From: Matthaus Owens Date: Fri, 30 Nov 2012 15:59:22 -0800 Subject: [PATCH 1099/3753] Remove fedora 15 mocks, as f15 is EOL. --- ext/build_defaults.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/build_defaults.yaml b/ext/build_defaults.yaml index 1df4b64dda..b6797086ba 100644 --- a/ext/build_defaults.yaml +++ b/ext/build_defaults.yaml @@ -9,8 +9,8 @@ gpg_name: 'info@puppetlabs.com' gpg_key: '4BD6EC30' sign_tar: FALSE # a space separated list of mock configs -final_mocks: 'pl-5-i386 pl-5-x86_64 pl-6-i386 pl-6-x86_64 fedora-15-i386 fedora-15-x86_64 fedora-16-i386 fedora-16-x86_64 fedora-17-i386 fedora-17-x86_64' -rc_mocks: 'pl-5-i386-dev pl-5-x86_64-dev pl-6-i386-dev pl-6-x86_64-dev fedora-15-i386-dev fedora-15-x86_64-dev fedora-16-i386-dev fedora-16-x86_64-dev fedora-17-i386-dev fedora-17-x86_64-dev' +final_mocks: 'pl-5-i386 pl-5-x86_64 pl-6-i386 pl-6-x86_64 fedora-16-i386 fedora-16-x86_64 fedora-17-i386 fedora-17-x86_64' +rc_mocks: 'pl-5-i386-dev pl-5-x86_64-dev pl-6-i386-dev pl-6-x86_64-dev fedora-16-i386-dev fedora-16-x86_64-dev fedora-17-i386-dev fedora-17-x86_64-dev' yum_host: 'burji.puppetlabs.com' yum_repo_path: '/opt/repository/yum/' build_gem: TRUE From 008a8d36ed32be6f1de7723e6c859ac9ecb15b6c Mon Sep 17 00:00:00 2001 From: Matthaus Owens Date: Fri, 30 Nov 2012 15:59:22 -0800 Subject: [PATCH 1100/3753] Remove fedora 15 mocks, as f15 is EOL. --- ext/build_defaults.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/build_defaults.yaml b/ext/build_defaults.yaml index 1df4b64dda..b6797086ba 100644 --- a/ext/build_defaults.yaml +++ b/ext/build_defaults.yaml @@ -9,8 +9,8 @@ gpg_name: 'info@puppetlabs.com' gpg_key: '4BD6EC30' sign_tar: FALSE # a space separated list of mock configs -final_mocks: 'pl-5-i386 pl-5-x86_64 pl-6-i386 pl-6-x86_64 fedora-15-i386 fedora-15-x86_64 fedora-16-i386 fedora-16-x86_64 fedora-17-i386 fedora-17-x86_64' -rc_mocks: 'pl-5-i386-dev pl-5-x86_64-dev pl-6-i386-dev pl-6-x86_64-dev fedora-15-i386-dev fedora-15-x86_64-dev fedora-16-i386-dev fedora-16-x86_64-dev fedora-17-i386-dev fedora-17-x86_64-dev' +final_mocks: 'pl-5-i386 pl-5-x86_64 pl-6-i386 pl-6-x86_64 fedora-16-i386 fedora-16-x86_64 fedora-17-i386 fedora-17-x86_64' +rc_mocks: 'pl-5-i386-dev pl-5-x86_64-dev pl-6-i386-dev pl-6-x86_64-dev fedora-16-i386-dev fedora-16-x86_64-dev fedora-17-i386-dev fedora-17-x86_64-dev' yum_host: 'burji.puppetlabs.com' yum_repo_path: '/opt/repository/yum/' build_gem: TRUE From cec7b6c8f2674a3b010db04fdca599ecee572abc Mon Sep 17 00:00:00 2001 From: Matthaus Owens Date: Fri, 30 Nov 2012 15:07:22 -0800 Subject: [PATCH 1101/3753] Update FACTERVERSION to 1.6.16 --- lib/facter/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/version.rb b/lib/facter/version.rb index 2bc126af63..bd074615eb 100644 --- a/lib/facter/version.rb +++ b/lib/facter/version.rb @@ -1,6 +1,6 @@ module Facter if not defined? FACTERVERSION then - FACTERVERSION = '1.6.15' + FACTERVERSION = '1.6.16' end def self.version From e12965d90278d58775a8ed071b42dcdec0a0eb13 Mon Sep 17 00:00:00 2001 From: Marc Fournier Date: Thu, 22 Nov 2012 20:39:20 +0100 Subject: [PATCH 1102/3753] (#17794) remove :kernel confinement of {zfs,zpool}_version As ZFS is now available on more or less every Solaris and BSD variant, as well as on Linux (via FUSE), it doesn't make sense to confine these facts to an evergrowing list of kernels. This patch: - removes every reference to :kernel, as well as the SunOS/FreeBSD subsections from the tests - runs the "setcode" block only if the zfs/zpool commands are present on the system, and adds a test for this case - adds sample output of zfs/zpool on Linux (FUSE) to fixtures + a test using them - removes the "should not run on Linux" test. The zfs/zpool providers in puppet have been recently updated in a similar fashion (55f953bc). --- lib/facter/zfs_version.rb | 10 +-- lib/facter/zpool_version.rb | 10 +-- .../unit/zfs_version/linux-fuse_0.6.9 | 14 ++++ .../unit/zpool_version/linux-fuse_0.6.9 | 35 ++++++++++ spec/unit/zfs_version_spec.rb | 67 ++++++++----------- spec/unit/zpool_version_spec.rb | 67 ++++++++----------- 6 files changed, 117 insertions(+), 86 deletions(-) create mode 100644 spec/fixtures/unit/zfs_version/linux-fuse_0.6.9 create mode 100644 spec/fixtures/unit/zpool_version/linux-fuse_0.6.9 diff --git a/lib/facter/zfs_version.rb b/lib/facter/zfs_version.rb index f4f5793e32..6f5d97e563 100644 --- a/lib/facter/zfs_version.rb +++ b/lib/facter/zfs_version.rb @@ -1,10 +1,10 @@ require 'facter' Facter.add('zfs_version') do - confine :kernel => %w(SunOS FreeBSD GNU/kFreeBSD) - - setcode do - zfs_v = Facter::Util::Resolution.exec('zfs upgrade -v') - zfs_version = zfs_v.scan(/^\s+(\d+)\s+/m).flatten.last unless zfs_v.nil? + if Facter::Util::Resolution.which('zfs') + setcode do + zfs_v = Facter::Util::Resolution.exec('zfs upgrade -v') + zfs_version = zfs_v.scan(/^\s+(\d+)\s+/m).flatten.last unless zfs_v.nil? + end end end diff --git a/lib/facter/zpool_version.rb b/lib/facter/zpool_version.rb index 10832af7ff..4e3429610f 100644 --- a/lib/facter/zpool_version.rb +++ b/lib/facter/zpool_version.rb @@ -1,10 +1,10 @@ require 'facter' Facter.add('zpool_version') do - confine :kernel => %w(SunOS FreeBSD GNU/kFreeBSD) - - setcode do - zpool_v = Facter::Util::Resolution.exec('zpool upgrade -v') - zpool_version = zpool_v.match(/ZFS pool version (\d+)./).captures.first unless zpool_v.nil? + if Facter::Util::Resolution.which('zpool') + setcode do + zpool_v = Facter::Util::Resolution.exec('zpool upgrade -v') + zpool_version = zpool_v.match(/ZFS pool version (\d+)./).captures.first unless zpool_v.nil? + end end end diff --git a/spec/fixtures/unit/zfs_version/linux-fuse_0.6.9 b/spec/fixtures/unit/zfs_version/linux-fuse_0.6.9 new file mode 100644 index 0000000000..2aff499786 --- /dev/null +++ b/spec/fixtures/unit/zfs_version/linux-fuse_0.6.9 @@ -0,0 +1,14 @@ +The following filesystem versions are supported: + +VER DESCRIPTION +--- -------------------------------------------------------- + 1 Initial ZFS filesystem version + 2 Enhanced directory entries + 3 Case insensitive and File system unique identifier (FUID) + 4 userquota, groupquota properties + +For more information on a particular version, including supported releases, see: + +http://www.opensolaris.org/os/community/zfs/version/zpl/N + +Where 'N' is the version number. diff --git a/spec/fixtures/unit/zpool_version/linux-fuse_0.6.9 b/spec/fixtures/unit/zpool_version/linux-fuse_0.6.9 new file mode 100644 index 0000000000..4ccab198b6 --- /dev/null +++ b/spec/fixtures/unit/zpool_version/linux-fuse_0.6.9 @@ -0,0 +1,35 @@ +This system is currently running ZFS pool version 23. + +The following versions are supported: + +VER DESCRIPTION +--- -------------------------------------------------------- + 1 Initial ZFS version + 2 Ditto blocks (replicated metadata) + 3 Hot spares and double parity RAID-Z + 4 zpool history + 5 Compression using the gzip algorithm + 6 bootfs pool property + 7 Separate intent log devices + 8 Delegated administration + 9 refquota and refreservation properties + 10 Cache devices + 11 Improved scrub performance + 12 Snapshot properties + 13 snapused property + 14 passthrough-x aclinherit + 15 user/group space accounting + 16 stmf property support + 17 Triple-parity RAID-Z + 18 Snapshot user holds + 19 Log device removal + 20 Compression using zle (zero-length encoding) + 21 Deduplication + 22 Received properties + 23 Slim ZIL + +For more information on a particular version, including supported releases, see: + +http://www.opensolaris.org/os/community/zfs/version/N + +Where 'N' is the version number. diff --git a/spec/unit/zfs_version_spec.rb b/spec/unit/zfs_version_spec.rb index 257be3fa49..d16856b9ad 100644 --- a/spec/unit/zfs_version_spec.rb +++ b/spec/unit/zfs_version_spec.rb @@ -14,52 +14,43 @@ # Solaris 10 8/11 (u10) 29 5 # Solaris 11 11/11 (ga) 33 5 - describe "for Solaris" do - before :each do - Facter.fact(:kernel).stubs(:value).returns("SunOS") - end - - it "should return correct version on Solaris 10" do - Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(my_fixture_read('solaris_10')) - Facter.fact(:zfs_version).value.should == "3" - end + before :each do + Facter::Util::Resolution.stubs(:which).with("zfs").returns("/usr/bin/zfs") + end - it "should return correct version on Solaris 11" do - Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(my_fixture_read('solaris_11')) - Facter.fact(:zfs_version).value.should == "5" - end + it "should return correct version on Solaris 10" do + Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(my_fixture_read('solaris_10')) + Facter.fact(:zfs_version).value.should == "3" + end - it "should return nil if zfs command is not available" do - Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(nil) - Facter.fact(:zfs_version).value.should == nil - end + it "should return correct version on Solaris 11" do + Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(my_fixture_read('solaris_11')) + Facter.fact(:zfs_version).value.should == "5" end - ['FreeBSD', 'GNU/kFreeBSD'].each do |kernel| - describe "for #{kernel}" do - before :each do - Facter.fact(:kernel).stubs(:value).returns("#{kernel}") - end + it "should return correct version on FreeBSD 8.2" do + Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(my_fixture_read('freebsd_8.2')) + Facter.fact(:zfs_version).value.should == "4" + end - it "should return correct version on #{kernel} 8.2" do - Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(my_fixture_read('freebsd_8.2')) - Facter.fact(:zfs_version).value.should == "4" - end + it "should return correct version on FreeBSD 9.0" do + Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(my_fixture_read('freebsd_9.0')) + Facter.fact(:zfs_version).value.should == "5" + end - it "should return correct version on #{kernel} 9.0" do - Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(my_fixture_read('freebsd_9.0')) - Facter.fact(:zfs_version).value.should == "5" - end + it "should return correct version on Linux with ZFS-fuse" do + Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(my_fixture_read('linux-fuse_0.6.9')) + Facter.fact(:zfs_version).value.should == "4" + end - it "should return nil if zfs command is not available" do - Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(nil) - Facter.fact(:zfs_version).value.should == nil - end - end - end + it "should return nil if zfs command is not available" do + Facter::Util::Resolution.stubs(:which).with("zfs").returns(nil) + Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(my_fixture_read('linux-fuse_0.6.9')) + Facter.fact(:zfs_version).value.should == nil + end - it "should not run on Linux" do - Facter.fact(:kernel).stubs(:value).returns("Linux") + it "should return nil if zfs fails to run" do + Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(nil) Facter.fact(:zfs_version).value.should == nil end end diff --git a/spec/unit/zpool_version_spec.rb b/spec/unit/zpool_version_spec.rb index f381361618..09737bdb47 100644 --- a/spec/unit/zpool_version_spec.rb +++ b/spec/unit/zpool_version_spec.rb @@ -14,52 +14,43 @@ # Solaris 10 8/11 (u10) 29 5 # Solaris 11 11/11 (ga) 33 5 - describe "for Solaris" do - before :each do - Facter.fact(:kernel).stubs(:value).returns("SunOS") - end + before :each do + Facter::Util::Resolution.stubs(:which).with("zpool").returns("/usr/bin/zpool") + end - it "should return correct version on Solaris 10" do - Facter::Util::Resolution.stubs(:exec).with("zpool upgrade -v").returns(my_fixture_read('solaris_10')) - Facter.fact(:zpool_version).value.should == "22" - end + it "should return correct version on Solaris 10" do + Facter::Util::Resolution.stubs(:exec).with("zpool upgrade -v").returns(my_fixture_read('solaris_10')) + Facter.fact(:zpool_version).value.should == "22" + end - it "should return correct version on Solaris 11" do - Facter::Util::Resolution.stubs(:exec).with("zpool upgrade -v").returns(my_fixture_read('solaris_11')) - Facter.fact(:zpool_version).value.should == "33" - end + it "should return correct version on Solaris 11" do + Facter::Util::Resolution.stubs(:exec).with("zpool upgrade -v").returns(my_fixture_read('solaris_11')) + Facter.fact(:zpool_version).value.should == "33" + end - it "should return nil if zpool is not available" do - Facter::Util::Resolution.stubs(:exec).with("zpool upgrade -v").returns(nil) - Facter.fact(:zpool_version).value.should == nil - end + it "should return correct version on FreeBSD 8.2" do + Facter::Util::Resolution.stubs(:exec).with("zpool upgrade -v").returns(my_fixture_read('freebsd_8.2')) + Facter.fact(:zpool_version).value.should == "15" end - ['FreeBSD', 'GNU/kFreeBSD'].each do |kernel| - describe "on #{kernel}" do - before :each do - Facter.fact(:kernel).stubs(:value).returns("#{kernel}") - end + it "should return correct version on FreeBSD 9.0" do + Facter::Util::Resolution.stubs(:exec).with("zpool upgrade -v").returns(my_fixture_read('freebsd_9.0')) + Facter.fact(:zpool_version).value.should == "28" + end - it "should return correct version on #{kernel} 8.2" do - Facter::Util::Resolution.stubs(:exec).with("zpool upgrade -v").returns(my_fixture_read('freebsd_8.2')) - Facter.fact(:zpool_version).value.should == "15" - end + it "should return correct version on Linux with ZFS-fuse" do + Facter::Util::Resolution.stubs(:exec).with("zpool upgrade -v").returns(my_fixture_read('linux-fuse_0.6.9')) + Facter.fact(:zpool_version).value.should == "23" + end - it "should return correct version on #{kernel} 9.0" do - Facter::Util::Resolution.stubs(:exec).with("zpool upgrade -v").returns(my_fixture_read('freebsd_9.0')) - Facter.fact(:zpool_version).value.should == "28" - end + it "should return nil if zpool is not available" do + Facter::Util::Resolution.stubs(:which).with("zpool").returns(nil) + Facter::Util::Resolution.stubs(:exec).with("zpool upgrade -v").returns(my_fixture_read('linux-fuse_0.6.9')) + Facter.fact(:zpool_version).value.should == nil + end - it "should return nil if zpool is not available" do - Facter::Util::Resolution.stubs(:exec).with("zpool upgrade -v").returns(nil) - Facter.fact(:zpool_version).value.should == nil - end - end - end - it "should not run on Linux" do - Facter.fact(:kernel).stubs(:value).returns("Linux") + it "should return nil if zpool fails to run" do + Facter::Util::Resolution.stubs(:exec).with("zpool upgrade -v").returns(nil) Facter.fact(:zpool_version).value.should == nil end end - From 5a25b33b880b69b021dd0a67e13df5a20ceec344 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Sun, 2 Dec 2012 10:52:10 -0800 Subject: [PATCH 1103/3753] (#17794) Specify how zfs facts behave when tools are configured Without this patch applied we don't have an example of how the zfs related facts behave when the client tools are configured after the facts have been defined and loaded but before they've been resolved most recently. The expectation expressed by this patch is that the facts will resolve when the client tools are available and will not resolve if the client tools are not available. Installing the client tools should be sufficient to cause the facts to resolve. This patch also changes the implementation to make the expected behavior of refreshing the fact values using the code specified in the setcode block. Squash into --- lib/facter/zfs_version.rb | 4 ++-- lib/facter/zpool_version.rb | 4 ++-- spec/unit/zfs_version_spec.rb | 20 ++++++++++++++++++++ spec/unit/zpool_version_spec.rb | 20 ++++++++++++++++++++ 4 files changed, 44 insertions(+), 4 deletions(-) diff --git a/lib/facter/zfs_version.rb b/lib/facter/zfs_version.rb index 6f5d97e563..8eeee11db5 100644 --- a/lib/facter/zfs_version.rb +++ b/lib/facter/zfs_version.rb @@ -1,8 +1,8 @@ require 'facter' Facter.add('zfs_version') do - if Facter::Util::Resolution.which('zfs') - setcode do + setcode do + if Facter::Util::Resolution.which('zfs') zfs_v = Facter::Util::Resolution.exec('zfs upgrade -v') zfs_version = zfs_v.scan(/^\s+(\d+)\s+/m).flatten.last unless zfs_v.nil? end diff --git a/lib/facter/zpool_version.rb b/lib/facter/zpool_version.rb index 4e3429610f..d182b2dc77 100644 --- a/lib/facter/zpool_version.rb +++ b/lib/facter/zpool_version.rb @@ -1,8 +1,8 @@ require 'facter' Facter.add('zpool_version') do - if Facter::Util::Resolution.which('zpool') - setcode do + setcode do + if Facter::Util::Resolution.which('zpool') zpool_v = Facter::Util::Resolution.exec('zpool upgrade -v') zpool_version = zpool_v.match(/ZFS pool version (\d+)./).captures.first unless zpool_v.nil? end diff --git a/spec/unit/zfs_version_spec.rb b/spec/unit/zfs_version_spec.rb index d16856b9ad..905dbc9ee5 100644 --- a/spec/unit/zfs_version_spec.rb +++ b/spec/unit/zfs_version_spec.rb @@ -53,4 +53,24 @@ Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(nil) Facter.fact(:zfs_version).value.should == nil end + + it "handles the zfs command becoming available at a later point in time" do + # Simulate Puppet configuring the zfs tools from a persistent daemon by + # simulating three sequential responses to which('zfs') + # (NOTE, each resolution causes which to execute twice. + Facter::Util::Resolution.stubs(:which). + with("zfs"). + returns(nil,nil,nil,nil,"/usr/bin/zfs") + Facter::Util::Resolution.stubs(:exec). + with("zfs upgrade -v"). + returns(my_fixture_read('linux-fuse_0.6.9')) + + fact = Facter.fact(:zfs_version) + + # zfs is not present the first two times the fact is resolved. + fact.value.should_not == "4" + fact.value.should_not == "4" + # zfs was configured between the second and third resolutions. + fact.value.should == "4" + end end diff --git a/spec/unit/zpool_version_spec.rb b/spec/unit/zpool_version_spec.rb index 09737bdb47..0cba70fdb6 100644 --- a/spec/unit/zpool_version_spec.rb +++ b/spec/unit/zpool_version_spec.rb @@ -53,4 +53,24 @@ Facter::Util::Resolution.stubs(:exec).with("zpool upgrade -v").returns(nil) Facter.fact(:zpool_version).value.should == nil end + + it "handles the zpool command becoming available" do + # Simulate Puppet configuring the zfs tools from a persistent daemon by + # simulating three sequential responses to which('zpool') + # (NOTE, each resolution causes which to execute twice. + Facter::Util::Resolution.stubs(:which). + with("zpool"). + returns(nil,nil,nil,nil,"/usr/bin/zpool") + Facter::Util::Resolution.stubs(:exec). + with("zpool upgrade -v"). + returns(my_fixture_read('linux-fuse_0.6.9')) + + fact = Facter.fact(:zpool_version) + + # zfs is not present the first two times the fact is resolved. + fact.value.should_not == "23" + fact.value.should_not == "23" + # zfs was configured between the second and third resolutions. + fact.value.should == "23" + end end From 7bba9b2777d4f53ede5cbcd15b18ad9a524de3a2 Mon Sep 17 00:00:00 2001 From: Jos Backus Date: Wed, 17 Oct 2012 13:32:45 -0700 Subject: [PATCH 1104/3753] (#16626) Fix handling of bonded Linux interfaces Array#to_s doesn't produce the right format for hwaddrre to match, so use #join instead. --- lib/facter/util/ip.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index 192836d07f..b768c42338 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -208,9 +208,9 @@ def self.get_interface_value(interface, label) # We have to dig a bit to get the original/real MAC address of the interface. bonddev = get_bonding_master(interface) if label == 'macaddress' and bonddev - bondinfo = IO.readlines("/proc/net/bonding/#{bonddev}") + bondinfo = IO.readlines("/proc/net/bonding/#{bonddev}").join hwaddrre = /^Slave Interface: #{interface}\n[^\n].+?\nPermanent HW addr: (([0-9a-fA-F]{2}:?)*)$/m - value = hwaddrre.match(bondinfo.to_s)[1].upcase + value = hwaddrre.match(bondinfo)[1].upcase else output_int = get_output_for_interface_and_label(interface, label) From c0b123b2d39b19a9e4a9a93992f283220630ffd9 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Wed, 5 Dec 2012 15:35:03 -0800 Subject: [PATCH 1105/3753] (#16626) Exercise IP.get_interface_value("bond0","macaddress") Without this patch, the behavior of the utility method Facter::Util::IP.get_interface_value("bond0", "macaddress") has no examples in the form of automated tests. This patch adds test coverage for this specific code path. --- lib/facter/util/ip.rb | 21 ++++++++++++++--- .../ip/linux_2_6_35_proc_net_bonding_bond0 | 19 +++++++++++++++ spec/unit/util/ip_spec.rb | 23 +++++++++++++++++++ 3 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 spec/fixtures/unit/util/ip/linux_2_6_35_proc_net_bonding_bond0 diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index b768c42338..7a40fcd1be 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -208,9 +208,11 @@ def self.get_interface_value(interface, label) # We have to dig a bit to get the original/real MAC address of the interface. bonddev = get_bonding_master(interface) if label == 'macaddress' and bonddev - bondinfo = IO.readlines("/proc/net/bonding/#{bonddev}").join - hwaddrre = /^Slave Interface: #{interface}\n[^\n].+?\nPermanent HW addr: (([0-9a-fA-F]{2}:?)*)$/m - value = hwaddrre.match(bondinfo)[1].upcase + bondinfo = read_proc_net_bonding("/proc/net/bonding/#{bonddev}") + re = /^Slave Interface: #{interface}\b.*?\bPermanent HW addr: (([0-9A-F]{2}:?)*)$/im + if match = re.match(bondinfo) + value = match[1].upcase + end else output_int = get_output_for_interface_and_label(interface, label) @@ -230,6 +232,19 @@ def self.get_interface_value(interface, label) end end + ## + # read_proc_net_bonding is a seam method for mocking purposes. + # + # @param path [String] representing the path to read, e.g. "/proc/net/bonding/bond0" + # + # @api private + # + # @return [String] modeling the raw file read + def self.read_proc_net_bonding(path) + File.read(path) if File.exists?(path) + end + private_class_method :read_proc_net_bonding + def self.get_network_value(interface) require 'ipaddr' diff --git a/spec/fixtures/unit/util/ip/linux_2_6_35_proc_net_bonding_bond0 b/spec/fixtures/unit/util/ip/linux_2_6_35_proc_net_bonding_bond0 new file mode 100644 index 0000000000..c817794044 --- /dev/null +++ b/spec/fixtures/unit/util/ip/linux_2_6_35_proc_net_bonding_bond0 @@ -0,0 +1,19 @@ +Ethernet Channel Bonding Driver: v3.5.0 (November 4, 2008) + +Bonding Mode: fault-tolerance (active-backup) +Primary Slave: None +Currently Active Slave: eth0 +MII Status: up +MII Polling Interval (ms): 100 +Up Delay (ms): 200 +Down Delay (ms): 200 + +Slave Interface: eth1 +MII Status: up +Link Failure Count: 0 +Permanent HW addr: 00:11:22:33:44:56 + +Slave Interface: eth0 +MII Status: up +Link Failure Count: 0 +Permanent HW addr: 00:11:22:33:44:55 diff --git a/spec/unit/util/ip_spec.rb b/spec/unit/util/ip_spec.rb index 79ebe6a8b8..243e27279f 100755 --- a/spec/unit/util/ip_spec.rb +++ b/spec/unit/util/ip_spec.rb @@ -408,4 +408,27 @@ def self.hpux_examples Facter::Util::IP.get_interface_value("Teredo Tunneling Pseudo-Interface", "ipaddress6").should == "2001:0:4137:9e76:2087:77a:53ef:7527" end end + + context "with bonded ethernet interfaces on Linux" do + before :each do + Facter.fact(:kernel).stubs(:value).returns("Linux") + end + + describe "Facter::Util::Ip.get_interface_value" do + before :each do + Facter::Util::IP.stubs(:read_proc_net_bonding). + with("/proc/net/bonding/bond0"). + returns(my_fixture_read("linux_2_6_35_proc_net_bonding_bond0")) + + Facter::Util::IP.stubs(:get_bonding_master).returns("bond0") + end + + it 'provides the real device macaddress for eth0' do + Facter::Util::IP.get_interface_value("eth0", "macaddress").should == "00:11:22:33:44:55" + end + it 'provides the real device macaddress for eth1' do + Facter::Util::IP.get_interface_value("eth1", "macaddress").should == "00:11:22:33:44:56" + end + end + end end From 10a1380d9ef8c2d85a2eddb44067d761f007eb34 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Wed, 5 Dec 2012 15:38:00 -0800 Subject: [PATCH 1106/3753] (Maint) Add YARD doc for get_interface_value Without this commit there is no clear documentation for the Facter::Util::IP.get_interface_value class method. This is a problem because the method behaves unexpectedly in some situations and is not intended for public API use. This patch fixes the problem by clearly documenting the method and marking it as a private API. --- lib/facter/util/ip.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index 7a40fcd1be..3f544ab83f 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -190,6 +190,19 @@ def self.get_bonding_master(interface) device end + ## + # get_interface_value obtains the value of a specific attribute of a specific + # interface. + # + # @param interface [String] the interface identifier, e.g. "eth0" or "bond0" + # + # @param label [String] the attribute of the interface to obtain a value for, + # e.g. "netmask" or "ipaddress" + # + # @api private + # + # @return [String] representing the requested value. An empty array is + # returned if the kernel is not supported by the REGEX_MAP constant. def self.get_interface_value(interface, label) tmp1 = [] From 5cfd683e75384821f03b3e4547178ec3c1cc4797 Mon Sep 17 00:00:00 2001 From: Matthaus Owens Date: Wed, 5 Dec 2012 16:39:08 -0800 Subject: [PATCH 1107/3753] Add quantal to default cows list. --- ext/build_defaults.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/build_defaults.yaml b/ext/build_defaults.yaml index b6797086ba..57ff137834 100644 --- a/ext/build_defaults.yaml +++ b/ext/build_defaults.yaml @@ -2,7 +2,7 @@ packaging_url: 'git://github.com/puppetlabs/packaging.git --branch=master' packaging_repo: 'packaging' default_cow: 'base-squeeze-i386.cow' -cows: 'base-lucid-i386.cow base-natty-i386.cow base-oneiric-i386.cow base-precise-i386.cow base-sid-i386.cow base-squeeze-i386.cow base-stable-i386.cow base-testing-i386.cow base-unstable-i386.cow base-wheezy-i386.cow' +cows: 'base-lucid-i386.cow base-natty-i386.cow base-oneiric-i386.cow base-precise-i386.cow base-quantal-i386.cow base-sid-i386.cow base-squeeze-i386.cow base-stable-i386.cow base-testing-i386.cow base-unstable-i386.cow base-wheezy-i386.cow' pbuild_conf: '/etc/pbuilderrc' packager: 'puppetlabs' gpg_name: 'info@puppetlabs.com' From a0b1acbfcd591c9cfb64260726f37eda0aba844a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Dal=C3=A9n?= Date: Mon, 3 Dec 2012 22:08:19 +0100 Subject: [PATCH 1108/3753] (#17917) Allow case equality confinement matching Without this patch applied the confine functionality is limited to testing equality. This is a problem because some facts have different, but similar, values over time. For example, the use case motivating this feature is: I have some facts I want to run on all generations of dell machines, but the manufacturer fact has changed between them so I want to be able to use something like /.*Dell.*/ This patch implements case equality which provides a superset of the regular expression matching that addresses the problem. This patch also cleans up the spec tests around confines a bit. --- lib/facter/util/confine.rb | 7 +--- spec/unit/util/confine_spec.rb | 74 ++++++++++++++-------------------- 2 files changed, 32 insertions(+), 49 deletions(-) diff --git a/lib/facter/util/confine.rb b/lib/facter/util/confine.rb index 1f9e11ec27..3bb504bb74 100644 --- a/lib/facter/util/confine.rb +++ b/lib/facter/util/confine.rb @@ -31,11 +31,6 @@ def true? return false if value.nil? - @values.each do |v| - v = convert(v) - next unless v.class == value.class - return true if value == v - end - return false + return @values.any? { |v| convert(v) === value } end end diff --git a/spec/unit/util/confine_spec.rb b/spec/unit/util/confine_spec.rb index 4b50c5342b..5bdd8daf8e 100755 --- a/spec/unit/util/confine_spec.rb +++ b/spec/unit/util/confine_spec.rb @@ -32,8 +32,12 @@ end describe "when evaluating" do + def confined(fact_value, *confines) + @fact.stubs(:value).returns fact_value + Facter::Util::Confine.new("yay", *confines).true? + end + before do - @confine = Facter::Util::Confine.new("yay", "one", "two", "Four", :xy, true, 1, [3,4]) @fact = mock 'fact' Facter.stubs(:[]).returns @fact end @@ -41,7 +45,7 @@ it "should return false if the fact does not exist" do Facter.expects(:[]).with("yay").returns nil - @confine.true?.should be_false + Facter::Util::Confine.new("yay", "test").true?.should be_false end it "should use the returned fact to get the value" do @@ -49,91 +53,75 @@ @fact.expects(:value).returns nil - @confine.true? + Facter::Util::Confine.new("yay", "test").true? end it "should return false if the fact has no value" do - @fact.stubs(:value).returns nil - - @confine.true?.should be_false + confined(nil, "test").should be_false end it "should return true if any of the provided values matches the fact's value" do - @fact.stubs(:value).returns "two" - - @confine.true?.should be_true + confined("two", "two").should be_true end it "should return true if any of the provided symbol values matches the fact's value" do - @fact.stubs(:value).returns :xy - - @confine.true?.should be_true + confined(:xy, :xy).should be_true end it "should return true if any of the provided integer values matches the fact's value" do - @fact.stubs(:value).returns 1 - - @confine.true?.should be_true + confined(1, 1).should be_true end it "should return true if any of the provided boolan values matches the fact's value" do - @fact.stubs(:value).returns true - - @confine.true?.should be_true + confined(true, true).should be_true end it "should return true if any of the provided array values matches the fact's value" do - @fact.stubs(:value).returns [3,4] - - @confine.true?.should be_true + confined([3,4], [3,4]).should be_true end it "should return true if any of the provided symbol values matches the fact's string value" do - @fact.stubs(:value).returns :one - - @confine.true?.should be_true + confined(:one, "one").should be_true end it "should return true if any of the provided string values matches case-insensitive the fact's value" do - @fact.stubs(:value).returns "four" - - @confine.true?.should be_true + confined("four", "Four").should be_true end it "should return true if any of the provided symbol values matches case-insensitive the fact's string value" do - @fact.stubs(:value).returns :four - - @confine.true?.should be_true + confined(:four, "Four").should be_true end it "should return true if any of the provided symbol values matches the fact's string value" do - @fact.stubs(:value).returns :Xy + confined("xy", :xy).should be_true + end - @confine.true?.should be_true + it "should return true if any of the provided regexp values matches the fact's string value" do + confined("abc", /abc/).should be_true end - it "should return false if none of the provided values matches the fact's value" do - @fact.stubs(:value).returns "three" + it "should return true if any of the provided ranges matches the fact's value" do + confined(6, (5..7)).should be_true + end - @confine.true?.should be_false + it "should return false if none of the provided values matches the fact's value" do + confined("three", "two", "four").should be_false end it "should return false if none of the provided integer values matches the fact's value" do - @fact.stubs(:value).returns 2 - - @confine.true?.should be_false + confined(2, 1, [3,4], (5..7)).should be_false end it "should return false if none of the provided boolan values matches the fact's value" do - @fact.stubs(:value).returns false - - @confine.true?.should be_false + confined(false, true).should be_false end it "should return false if none of the provided array values matches the fact's value" do - @fact.stubs(:value).returns [1,2] + confined([1,2], [3,4]).should be_false + end - @confine.true?.should be_false + it "should return false if none of the provided ranges matches the fact's value" do + confined(8, (5..7)).should be_false end end end From 73bc95adc8f3e348e5566f89a0b7c90a98dd50f2 Mon Sep 17 00:00:00 2001 From: Patrick Carlisle Date: Mon, 3 Dec 2012 17:44:45 -0800 Subject: [PATCH 1109/3753] Document Facter API with YARD This adds YARD documentation with `@api public` tags to the public API for facter. This is a first pass, so it's probably missing some things that should be considered public. --- .yardopts | 5 + README.md | 3 +- lib/facter.rb | 230 ++++++++++++++++++++++++------ lib/facter/processor.rb | 5 +- lib/facter/util/collection.rb | 5 +- lib/facter/util/fact.rb | 48 +++++-- lib/facter/util/file_read.rb | 11 +- lib/facter/util/nothing_loader.rb | 9 +- lib/facter/util/resolution.rb | 179 +++++++++++++++++++---- lib/facter/util/values.rb | 2 +- lib/facter/version.rb | 79 +++++----- spec/unit/facter_spec.rb | 9 +- 12 files changed, 438 insertions(+), 147 deletions(-) diff --git a/.yardopts b/.yardopts index 0036282bd9..a552a9bc42 100644 --- a/.yardopts +++ b/.yardopts @@ -3,4 +3,9 @@ --verbose --markup markdown --readme README_DEVELOPER.md +--tag status +--transitive-tag status +--tag comment +--hide-tag comment +--no-transitive-tag api lib/**/*.rb diff --git a/README.md b/README.md index e5a54534b6..0185c582c1 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,7 @@ Facter This package is largely meant to be a library for collecting facts about your system. These facts are mostly strings (i.e., not numbers), and are things -like the output of `uname`, public ssh and cfengine keys, the number of -processors, etc. +like the output of `uname`, public ssh keys, the number of processors, etc. See `bin/facter` for an example of the interface. diff --git a/lib/facter.rb b/lib/facter.rb index 760dfac52d..eff1665b57 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -16,8 +16,20 @@ require 'facter/version' +# Functions as a hash of 'facts' about your system system, such as MAC +# address, IP address, architecture, etc. +# +# @example Retrieve a fact +# puts Facter['operatingsystem'].value +# +# @example Retrieve all facts +# Facter.to_hash +# => { "kernel"=>"Linux", "uptime_days"=>0, "ipaddress"=>"10.0.0.1" } +# +# @api public module Facter - # This is just so the other classes have the constant. + # Most core functionality of facter is implemented in `Facter::Util`. + # @api public module Util; end require 'facter/util/fact' @@ -27,28 +39,25 @@ module Util; end include Comparable include Enumerable - # = Facter - # Functions as a hash of 'facts' you might care about about your - # system, such as mac address, IP address, Video card, etc. - # returns them dynamically - - # == Synopsis - # - # Generally, treat Facter as a hash: - # == Example - # require 'facter' - # puts Facter['operatingsystem'] - # - + # @api private GREEN = "" + # @api private RESET = "" + # @api private @@debug = 0 + # @api private @@timing = 0 + # @api private @@messages = {} + # @api private @@debug_messages = {} # module methods + # Accessor for the collection object which holds all the facts + # @return [Facter::Util::Collection] the collection of facts + # + # @api private def self.collection unless defined?(@collection) and @collection @collection = Facter::Util::Collection.new( @@ -58,7 +67,10 @@ def self.collection @collection end - # Add some debugging + # Prints a debug message if debugging is turned on + # + # @param string [String] the debug message + # @return [void] def self.debug(string) if string.nil? return @@ -68,7 +80,13 @@ def self.debug(string) end end - # Debug once. + # Prints a debug message only once. + # + # @note Uniqueness is based on the string, not the specific location + # of the method call. + # + # @param msg [String] the debug message + # @return [void] def self.debugonce(msg) if msg and not msg.empty? and @@debug_messages[msg].nil? @@debug_messages[msg] = true @@ -76,21 +94,31 @@ def self.debugonce(msg) end end + # Returns whether debugging output is turned on def self.debugging? @@debug != 0 end - # show the timing information + # Prints a timing + # + # @param string [String] the time to print + # @return [void] + # + # @api private def self.show_time(string) puts "#{GREEN}#{string}#{RESET}" if string and Facter.timing? end + # Returns whether timing output is turned on + # + # @api private def self.timing? @@timing != 0 end - # Facter.json? is meant to provide a lightweight way to check if the JSON - # "feature" is available. + # Returns whether the JSON "feature" is available. + # + # @api private def self.json? begin require 'json' @@ -100,34 +128,94 @@ def self.json? end end - # Return a fact object by name. If you use this, you still have to call - # 'value' on it to retrieve the actual value. + # Returns a fact object by name. If you use this, you still have to + # call {Facter::Util::Fact#value `value`} on it to retrieve the actual + # value. + # + # @param name [String] the name of the fact + # + # @return [Facter::Util::Fact, nil] The fact object, or nil if no fact + # is found. + # + # @api public def self.[](name) collection.fact(name) end - class << self - [:fact, :flush, :list, :value].each do |method| - define_method(method) do |*args| - collection.send(method, *args) - end - end + # (see []) + def self.fact(name) + collection.fact(name) + end - [:list, :to_hash].each do |method| - define_method(method) do |*args| - collection.load_all - collection.send(method, *args) - end - end + # Flushes cached values for all facts. This does not cause code to be + # reloaded; it only clears the cached results. + # + # @return [void] + # + # @api public + def self.flush + collection.flush + end + + # Lists all fact names + # + # @return [Array] array of fact names + # + # @api public + def self.list + collection.list end + # Gets the value for a fact. Returns `nil` if no such fact exists. + # + # @param name [String] the fact name + # @return [Object, nil] the value of the fact, or nil if no fact is + # found + # + # @api public + def self.value(name) + collection.value(name) + end - # Add a resolution mechanism for a named fact. This does not distinguish - # between adding a new fact and adding a new way to resolve a fact. + # Gets a hash mapping fact names to their values + # + # @return [Hash{String => Object}] the hash of fact names and values + # + # @api public + def self.to_hash + collection.load_all + collection.to_hash + end + + # Adds a {Facter::Util::Resolution resolution} mechanism for a named + # fact. This does not distinguish between adding a new fact and adding + # a new way to resolve a fact. + # + # @overload add(name, options = {}, { || ... }) + # @param name [String] the fact name + # @param options [Hash] optional parameters for the fact - attributes + # of {Facter::Util::Fact} and {Facter::Util::Resolution} can be + # supplied here + # @option options [Integer] :timeout set the + # {Facter::Util::Resolution#timeout timeout} for this resolution + # @param block [Proc] a block defining a fact resolution + # + # @return [Facter::Util::Fact] the fact object, which includes any previously + # defined resolutions + # + # @api public def self.add(name, options = {}, &block) collection.add(name, options, &block) end + # Iterates over fact names and values + # + # @yieldparam [String] name the fact name + # @yieldparam [String] value the current value of the fact + # + # @return [void] + # + # @api public def self.each # Make sure all facts are loaded. collection.load_all @@ -140,6 +228,8 @@ def self.each class << self # Allow users to call fact names directly on the Facter class, # either retrieving the value or comparing it to an existing value. + # + # @api private def method_missing(name, *args) question = false if name.to_s =~ /\?$/ @@ -168,19 +258,30 @@ def method_missing(name, *args) end end - # Clear all facts. Mostly used for testing. + # Clears all cached values and removes all facts from memory. + # + # @return [void] + # + # @api public def self.clear Facter.flush Facter.reset end - # Clear all messages. Used only in testing. Can't add to self.clear - # because we don't want to warn multiple times for items that are warnonce'd + # Clears the seen state of warning messages. See {warnonce}. + # + # @return [void] + # + # @api private def self.clear_messages @@messages.clear end - # Set debugging on or off. + # Sets debugging on or off. + # + # @return [void] + # + # @api private def self.debugging(bit) if bit case bit @@ -206,7 +307,11 @@ def self.debugging(bit) end end - # Set timing on or off. + # Sets whether timing messages are displayed. + # + # @return [void] + # + # @api private def self.timing(bit) if bit case bit @@ -223,6 +328,12 @@ def self.timing(bit) end end + # Prints a warning message. The message is only printed if debugging + # is enabled. + # + # @param msg [String] the warning message to be printed + # + # @return [void] def self.warn(msg) if Facter.debugging? and msg and not msg.empty? msg = [msg] unless msg.respond_to? :each @@ -230,7 +341,16 @@ def self.warn(msg) end end - # Warn once. + # Prints a warning message only once per process. Each unique string + # is printed once. + # + # @note Unlike {warn} the message will be printed even if debugging is + # not turned on. This behavior is likely to change and should not be + # relied on. + # + # @param msg [String] the warning message to be printed + # + # @return [void] def self.warnonce(msg) if msg and not msg.empty? and @@messages[msg].nil? @@messages[msg] = true @@ -238,24 +358,44 @@ def self.warnonce(msg) end end - # Remove them all. + # Removes all facts from memory. Use this when the fact code has + # changed on disk and needs to be reloaded. + # + # @return [void] + # + # @api public def self.reset @collection = nil end - # Load all of the default facts, and then everything from disk. + # Loads all facts. + # + # @return [void] + # + # @api public def self.loadfacts collection.load_all end @search_path = [] - # Register a directory to search through. + # Registers directories to be searched for facts. Relative paths will + # be interpreted in the current working directory. + # + # @param dirs [String] directories to search + # + # @return [void] + # + # @api public def self.search(*dirs) @search_path += dirs end - # Return our registered search directories. + # Returns the registered search directories. + # + # @return [Array] An array of the directories searched + # + # @api public def self.search_path @search_path.dup end diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb index 1d409eb687..804cfa3fcc 100644 --- a/lib/facter/processor.rb +++ b/lib/facter/processor.rb @@ -22,8 +22,9 @@ require 'thread' require 'facter/util/processor' -## We have to enumerate these outside a Facter.add block to get the processorN descriptions iteratively -## (but we need them inside the Facter.add block above for tests on processorcount to work) +# We have to enumerate these outside a Facter.add block to get the processorN +# descriptions iteratively (but we need them inside the Facter.add block above +# for tests on processorcount to work) processor_list = case Facter::Util::Processor.kernel_fact_value when "AIX" Facter::Util::Processor.aix_processor_list diff --git a/lib/facter/util/collection.rb b/lib/facter/util/collection.rb index cb9359ba45..cbf008be43 100644 --- a/lib/facter/util/collection.rb +++ b/lib/facter/util/collection.rb @@ -4,6 +4,8 @@ # Manage which facts exist and how we access them. Largely just a wrapper # around a hash of facts. +# +# @api private class Facter::Util::Collection def initialize(internal_loader, external_loader) @@ -12,8 +14,7 @@ def initialize(internal_loader, external_loader) @external_loader = external_loader end - # Return a fact object by name. If you use this, you still have to call - # 'value' on it to retrieve the actual value. + # Return a fact object by name. def [](name) value(name) end diff --git a/lib/facter/util/fact.rb b/lib/facter/util/fact.rb index 9279b98360..55e1781c6b 100644 --- a/lib/facter/util/fact.rb +++ b/lib/facter/util/fact.rb @@ -1,12 +1,28 @@ require 'facter' require 'facter/util/resolution' +# This class represents a fact. Each fact has a name and multiple +# {Facter::Util::Resolution resolutions}. +# +# Create facts using {Facter.add} +# +# @api public class Facter::Util::Fact - TIMEOUT = 5 - - attr_accessor :name, :ldapname - - # Create a new fact, with no resolution mechanisms. + # The name of the fact + # @return [String] + attr_accessor :name + + # @return [String] + # @deprecated + attr_accessor :ldapname + + # Creates a new fact, with no resolution mechanisms. See {Facter.add} + # for the public API for creating facts. + # @param name [String] the fact name + # @param options [Hash] optional parameters + # @option options [String] :ldapname set the ldapname property on the fact + # + # @api private def initialize(name, options = {}) @name = name.to_s.downcase.intern @@ -28,8 +44,13 @@ def initialize(name, options = {}) @value = nil end - # Add a new resolution mechanism. This requires a block, which will then - # be evaluated in the context of the new mechanism. + # Adds a new {Facter::Util::Resolution resolution}. This requires a + # block, which will then be evaluated in the context of the new + # resolution. + # + # @return [void] + # + # @api private def add(value = nil, &block) begin resolve = Facter::Util::Resolution.new(@name) @@ -44,13 +65,20 @@ def add(value = nil, &block) end end - # Flush any cached values. + # Flushs any cached values. + # + # @return [void] + # + # @api private def flush @value = nil end - # Return the value for a given fact. Searches through all of the mechanisms - # and returns either the first value or nil. + # Returns the value for this fact. This searches all resolutions by + # suitability and weight (see {Facter::Util::Resolution}). If no + # suitable resolution is found, it returns nil. + # + # @api public def value return @value if @value diff --git a/lib/facter/util/file_read.rb b/lib/facter/util/file_read.rb index 258cd665bf..36320e9441 100644 --- a/lib/facter/util/file_read.rb +++ b/lib/facter/util/file_read.rb @@ -1,6 +1,6 @@ module Facter module Util -## + # {Facter::Util::FileRead} is a utility module intended to provide easily # mockable methods that delegate to simple file read methods. The intent is to # avoid the need to execute the `cat` system command or `File.read` directly in @@ -11,16 +11,17 @@ module Util # # @api public module FileRead - ## # read returns the raw content of a file as a string. If the file does not # exist, or the process does not have permission to read the file then nil is # returned. # # @api public # - # @return [String] the raw contents of the file at {path} or {nil} if the - # file cannot be read because it does not exist or the process does not have - # permission to read the file. + # @param path [String] the path to be read + # + # @return [String, nil] the raw contents of the file or `nil` if the + # file cannot be read because it does not exist or the process does not have + # permission to read the file. def self.read(path) File.read(path) rescue Errno::ENOENT, Errno::EACCES => detail diff --git a/lib/facter/util/nothing_loader.rb b/lib/facter/util/nothing_loader.rb index 6e337c3ab5..e700aa1cd1 100644 --- a/lib/facter/util/nothing_loader.rb +++ b/lib/facter/util/nothing_loader.rb @@ -1,13 +1,10 @@ -# An external fact loader that doesn't load anything - -# This makes it possible to disable loading -# of external facts - module Facter module Util + # An external fact loader that doesn't load anything + # This makes it possible to disable loading + # of external facts class NothingLoader - def load(collection) end end diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 18ae76f948..a1a18c12f9 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -1,22 +1,39 @@ -# An actual fact resolution mechanism. These are largely just chunks of -# code, with optional confinements restricting the mechanisms to only working on -# specific systems. Note that the confinements are always ANDed, so any -# confinements specified must all be true for the resolution to be -# suitable. require 'facter/util/confine' require 'facter/util/config' require 'timeout' +# This represents a fact resolution. A resolution is a concrete +# implementation of a fact. A single fact can have many resolutions and +# the correct resolution will be chosen at runtime. Each time +# {Facter.add} is called, a new resolution is created and added to the +# set of resolutions for the fact named in the call. Each resolution +# has a {#has_weight weight}, which defines its priority over other +# resolutions, and a set of {#confine _confinements_}, which defines the +# conditions under which it will be chosen. All confinements must be +# satisfied for a fact to be considered _suitable_. +# +# @api public class Facter::Util::Resolution - attr_accessor :interpreter, :code, :name, :timeout + # The timeout, in seconds, for evaluating this resolution. The default + # is 0 which is equivalent to no timeout. This can be set using the + # options hash argument to {Facter.add}. + # @return [Integer] + # @api public + attr_accessor :timeout + + # @api private + attr_accessor :interpreter, :code, :name attr_writer :value, :weight INTERPRETER = Facter::Util::Config.is_windows? ? "cmd.exe" : "/bin/sh" # Returns the locations to be searched when looking for a binary. This # is currently determined by the +PATH+ environment variable plus - # /sbin and /usr/sbin when run on unix + # `/sbin` and `/usr/sbin` when run on unix + # + # @return [Array] the paths to be searched for binaries + # @api private def self.search_paths if Facter::Util::Config.is_windows? ENV['PATH'].split(File::PATH_SEPARATOR) @@ -28,12 +45,18 @@ def self.search_paths end end - # Determine the full path to a binary. If the supplied filename does not + # Determines the full path to a binary. If the supplied filename does not # already describe an absolute path then different locations (determined - # by self.search_paths) will be searched for a match. + # by {search_paths}) will be searched for a match. # # Returns nil if no matching executable can be found otherwise returns # the expanded pathname. + # + # @param bin [String] the executable to locate + # @return [String,nil] the full path to the executable or nil if not + # found + # + # @api public def self.which(bin) if absolute_path?(bin) return bin if File.executable?(bin) @@ -70,6 +93,9 @@ def self.which(bin) # Determine in a platform-specific way whether a path is absolute. This # defaults to the local platform if none is specified. + # + # @param path [String] the path to check + # @param platform [:posix,:windows,nil] the platform logic to use def self.absolute_path?(path, platform=nil) # Escape once for the string literal, and once for the regex. slash = '[\\\\/]' @@ -83,13 +109,14 @@ def self.absolute_path?(path, platform=nil) !! (path =~ regexes[platform]) end - # Expand the executable of a commandline to an absolute path. The executable - # is the first word of the commandline. If the executable contains spaces, - # it has be but in double quotes to be properly recognized. + # Given a command line, this returns the command line with the + # executable written as an absolute path. If the executable contains + # spaces, it has be but in double quotes to be properly recognized. + # + # @param command [String] the command line # - # Returns the commandline with the expanded binary or nil if the binary - # can't be found. If the path to the binary contains quotes, the whole binary - # is put in quotes. + # @return [String, nil] the command line with the executable's path + # expanded, or nil if the executable cannot be found. def self.expand_command(command) if match = /^"(.+?)"(?:\s+(.*))?/.match(command) exe, arguments = match.captures @@ -109,13 +136,18 @@ def self.expand_command(command) end end + # Overrides environment variables within a block of code. The + # specified values will be set for the duration of the block, after + # which the original values (if any) will be restored. + # + # @overload with_env(values, { || ... }) + # + # @param values [HashString>] A hash of the environment + # variables to override # - # Call this method with a block of code for which you would like to temporarily modify - # one or more environment variables; the specified values will be set for the duration - # of your block, after which the original values (if any) will be restored. + # @return [void] # - # [values] a Hash containing the key/value pairs of any environment variables that you - # would like to temporarily override + # @api public def self.with_env(values) old = {} values.each do |var, value| @@ -143,18 +175,32 @@ def self.with_env(values) rv end - # Execute a program and return the output of that program. + # Executes a program and return the output of that program. # # Returns nil if the program can't be found, or if there is a problem # executing the code. # + # @param code [String] the program to run + # @return [String, nil] the output of the program or nil + # + # @api public + # + # @note Since Facter 1.5.8 this strips trailing newlines from the + # returned value. If a fact will be used by versions of Facter older + # than 1.5.8 then you should call chomp the returned string. + # + # @overload exec(code) + # @overload exec(code, interpreter = nil) + # @param [String] interpreter unused, only exists for backwards + # compatibility + # @deprecated def self.exec(code, interpreter = nil) Facter.warnonce "The interpreter parameter to 'exec' is deprecated and will be removed in a future version." if interpreter ## Set LANG to force i18n to C for the duration of this exec; this ensures that any code that parses the ## output of the command can expect it to be in a consistent / predictable format / locale with_env "LANG" => "C" do - + if expanded_code = expand_command(code) # if we can find the binary, we'll run the command with the expanded path to the binary code = expanded_code @@ -164,7 +210,7 @@ def self.exec(code, interpreter = nil) return nil unless Facter::Util::Config.is_windows? return nil if absolute_path?(code) end - + out = nil begin @@ -177,7 +223,7 @@ def self.exec(code, interpreter = nil) $stderr.puts detail return nil end - + if out == "" return nil else @@ -186,18 +232,56 @@ def self.exec(code, interpreter = nil) end end - # Add a new confine to the resolution mechanism. + # Sets the conditions for this resolution to be used. This takes a + # hash of fact names and values. Every fact must match the values + # given for that fact, otherwise this resolution will not be + # considered suitable. The values given for a fact can be an array, in + # which case the value of the fact must be in the array for it to + # match. + # + # @param confines [Hash{String => Object}] a hash of facts and the + # values they should have in order for this resolution to be + # used + # + # @example Confining to Linux + # Facter.add(:powerstates) do + # # This resolution only makes sense on linux systems + # confine :kernel => "Linux" + # setcode do + # Facter::Util::Resolution.exec('cat /sys/power/states') + # end + # end + # + # @return [void] + # + # @api public def confine(confines) confines.each do |fact, values| @confines.push Facter::Util::Confine.new(fact, *values) end end + # Sets the weight of this resolution. If multiple suitable resolutions + # are found, the one with the highest weight will be used. If weight + # is not given, the number of confines set on a resolution will be + # used as its weight (so that the most specific resolution is used). + # + # @param weight [Integer] the weight of this resolution + # + # @return [void] + # + # @api public def has_weight(weight) @weight = weight end # Create a new resolution mechanism. + # + # @param name [String] The name of the resolution. This is mostly + # unused and resolutions are treated as anonymous. + # @return [void] + # + # @api private def initialize(name) @name = name @confines = [] @@ -206,7 +290,13 @@ def initialize(name) @weight = nil end - # Return the importance of this resolution. + # Returns the importance of this resolution. If the weight was not + # given, the number of confines is used instead (so that a more + # specific resolution wins over a less specific one). + # + # @return [Integer] the weight of this resolution + # + # @api private def weight if @weight @weight @@ -215,14 +305,32 @@ def weight end end - # We need this as a getter for 'timeout', because some versions - # of ruby seem to already have a 'timeout' method and we can't - # seem to override the instance methods, somehow. + # (see #timeout) + # This is another name for {#timeout}. + # @comment We need this as a getter for 'timeout', because some versions + # of ruby seem to already have a 'timeout' method and we can't + # seem to override the instance methods, somehow. def limit @timeout end - # Set our code for returning a value. + # Sets the code block or external program that will be evaluated to + # get the value of the fact. + # + # @return [void] + # + # @overload setcode(string) + # Sets an external program to call to get the value of the resolution + # @param [String] string the external program to run to get the + # value + # + # @overload setcode(&block) + # Sets the resolution's value by evaluating a block at runtime + # @param [Proc] block The block to determine the resolution's value. + # This block is run when the fact is evaluated. Errors raised from + # inside the block are rescued and printed to stderr. + # + # @api public def setcode(string = nil, interp = nil, &block) Facter.warnonce "The interpreter parameter to 'setcode' is deprecated and will be removed in a future version." if interp if string @@ -236,17 +344,21 @@ def setcode(string = nil, interp = nil, &block) end end + # @deprecated def interpreter Facter.warnonce "The 'Facter::Util::Resolution.interpreter' method is deprecated and will be removed in a future version." @interpreter end + # @deprecated def interpreter=(interp) Facter.warnonce "The 'Facter::Util::Resolution.interpreter=' method is deprecated and will be removed in a future version." @interpreter = interp end # Is this resolution mechanism suitable on the system in question? + # + # @api private def suitable? unless defined? @suitable @suitable = ! @confines.detect { |confine| ! confine.true? } @@ -255,11 +367,16 @@ def suitable? return @suitable end + # (see value) + # @deprecated def to_s return self.value() end - # How we get a value for our resolution mechanism. + # Evaluates the code block or external program to get the value of the + # fact. + # + # @api private def value return @value if @value result = nil diff --git a/lib/facter/util/values.rb b/lib/facter/util/values.rb index a8a5bfab64..d69198767a 100644 --- a/lib/facter/util/values.rb +++ b/lib/facter/util/values.rb @@ -1,6 +1,6 @@ -# A util module for facter containing helper methods module Facter module Util + # A util module for facter containing helper methods module Values module_function diff --git a/lib/facter/version.rb b/lib/facter/version.rb index ec77598c0a..15efc0c0c2 100644 --- a/lib/facter/version.rb +++ b/lib/facter/version.rb @@ -3,52 +3,51 @@ module Facter FACTERVERSION = '2.0.0-rc4' end - ## - # version is a public API method intended to always provide a fast and - # lightweight way to determine the version of Facter. + # Returns the running version of Facter. # - # The intent is that software external to Facter be able to determine the - # Facter version with no side-effects. The expected use is: + # @comment The intent is that software external to Facter be able to + # determine the Facter version with no side-effects. The expected + # use is: # # require 'facter/version' # version = Facter.version # - # This function has the following ordering precedence. This precedence list - # is designed to facilitate automated packaging tasks by simply writing to - # the VERSION file in the same directory as this source file. + # This function has the following ordering precedence. This precedence list + # is designed to facilitate automated packaging tasks by simply writing to + # the VERSION file in the same directory as this source file. # - # 1. If a version has been explicitly assigned using the Facter.version= - # method, return that version. - # 2. If there is a VERSION file, read the contents, trim any - # trailing whitespace, and return that version string. - # 3. Return the value of the Facter::FACTERVERSION constant hard-coded into - # the source code. + # 1. If a version has been explicitly assigned using the Facter.version= + # method, return that version. + # 2. If there is a VERSION file, read the contents, trim any + # trailing whitespace, and return that version string. + # 3. Return the value of the Facter::FACTERVERSION constant hard-coded into + # the source code. # - # If there is no VERSION file, the method must return the version string of - # the nearest parent version that is an officially released version. That is - # to say, if a branch named 3.1.x contains 25 patches on top of the most - # recent official release of 3.1.1, then the version method must return the - # string "3.1.1" if no "VERSION" file is present. + # If there is no VERSION file, the method must return the version string of + # the nearest parent version that is an officially released version. That is + # to say, if a branch named 3.1.x contains 25 patches on top of the most + # recent official release of 3.1.1, then the version method must return the + # string "3.1.1" if no "VERSION" file is present. # - # By design the version identifier is _not_ intended to vary during the life - # a process. There is no guarantee provided that writing to the VERSION file - # while a Puppet process is running will cause the version string to be - # updated. On the contrary, the contents of the VERSION are cached to reduce - # filesystem accesses. + # By design the version identifier is _not_ intended to vary during the life of + # a process. There is no guarantee provided that writing to the VERSION file + # while a Puppet process is running will cause the version string to be + # updated. On the contrary, the contents of the VERSION are cached to reduce + # filesystem accesses. # - # The VERSION file is intended to be used by package maintainers who may be - # applying patches or otherwise changing the software version in a manner - # that warrants a different software version identifier. The VERSION file is - # intended to be managed and owned by the release process and packaging - # related tasks, and as such should not reside in version control. The - # FACTERVERSION constant is intended to be version controlled in history. + # The VERSION file is intended to be used by package maintainers who may be + # applying patches or otherwise changing the software version in a manner + # that warrants a different software version identifier. The VERSION file is + # intended to be managed and owned by the release process and packaging + # related tasks, and as such should not reside in version control. The + # FACTERVERSION constant is intended to be version controlled in history. # - # Ideally, this behavior will allow package maintainers to precisely specify - # the version of the software they're packaging as in the following example: + # Ideally, this behavior will allow package maintainers to precisely specify + # the version of the software they're packaging as in the following example: # - # $ git describe > lib/facter/VERSION - # $ ruby -r facter -e 'puts Facter.version' - # 1.6.14-6-g66f2c99 + # $ git describe > lib/facter/VERSION + # $ ruby -r facter -e 'puts Facter.version' + # 1.6.14-6-g66f2c99 # # @api public # @@ -62,17 +61,21 @@ def self.version @facter_version ||= FACTERVERSION end + # Sets the Facter version + # + # @return [void] + # + # @api private def self.version=(version) @facter_version = version end - ## - # read_version_file reads the content of the "VERSION" file that lives in the + # This reads the content of the "VERSION" file that lives in the # same directory as this source code file. # # @api private # - # @return [String] for example: "1.6.14-6-gea42046" or nil if the VERSION + # @return [String] the version -- for example: "1.6.14-6-gea42046" or nil if the VERSION # file does not exist. def self.read_version_file(path) if File.exists?(path) diff --git a/spec/unit/facter_spec.rb b/spec/unit/facter_spec.rb index e86bfa4012..446c6112b3 100755 --- a/spec/unit/facter_spec.rb +++ b/spec/unit/facter_spec.rb @@ -17,8 +17,8 @@ end it "should delegate the :fact method to the collection" do - Facter.collection.expects(:fact) - Facter.fact + Facter.collection.expects(:fact).with("afact") + Facter.fact("afact") end it "should delegate the :list method to the collection" do @@ -28,7 +28,6 @@ it "should load all facts when listing" do Facter.collection.expects(:load_all) - Facter.collection.stubs(:list) Facter.list end @@ -44,8 +43,8 @@ end it "should delegate the :value method to the collection" do - Facter.collection.expects(:value) - Facter.value + Facter.collection.expects(:value).with("myvaluefact") + Facter.value("myvaluefact") end it "should delegate the :each method to the collection" do From 8c33118efe7d41633df437bd3d51d0c6cb03043b Mon Sep 17 00:00:00 2001 From: Patrick Carlisle Date: Mon, 10 Dec 2012 14:33:07 -0800 Subject: [PATCH 1110/3753] Use the API to test resolution weight --- spec/unit/util/fact_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/unit/util/fact_spec.rb b/spec/unit/util/fact_spec.rb index ad37321539..bc8abefec4 100755 --- a/spec/unit/util/fact_spec.rb +++ b/spec/unit/util/fact_spec.rb @@ -48,9 +48,9 @@ end it "should re-sort the resolutions by weight, so the most restricted resolutions are first" do - @fact.add { self.value = "1"; self.weight = 1 } - @fact.add { self.value = "2"; self.weight = 2 } - @fact.add { self.value = "0"; self.weight = 0 } + @fact.add { has_weight 1; setcode { "1" } } + @fact.add { has_weight 2; setcode { "2" } } + @fact.add { has_weight 0; setcode { "0" } } @fact.value.should == "2" end end From 656c5ae65283ccb5714ae15dc38484f7c2871f69 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Tue, 11 Dec 2012 21:16:09 +0000 Subject: [PATCH 1111/3753] (#17925) Fix ec2_userdata: 404 Not Found Error Without this patch applied Facter returns the following error message to the user when no user-data is present in the instance metadata server when Facter is running on an instance in Amazon AWS EC2. $ facter ec2_userdata Could not retrieve ec2_userdata: 404 Not Found This patch addresses the problem by handling the exception produced from the HTTP 404 Not Found error. In this circumstance of no user-data being present, the fact value is not set. With this patch applied the behavior is as follows: $ userdata=$(facter ec2_userdata); echo "userdata: [${userdata}]" userdata: [] And the error is not silently dropped with debugging turned on: $ facter --debug ec2_userdata No user-data present at http://169.254.169.254/latest/user-data/: server responded with 404 Not Found value for ec2_userdata is still nil --- lib/facter/ec2.rb | 4 +++- lib/facter/util/ec2.rb | 40 ++++++++++++++++++++++++++++++++++++++ spec/unit/ec2_spec.rb | 24 +++++++++++------------ spec/unit/util/ec2_spec.rb | 26 +++++++++++++++++++++++++ 4 files changed, 81 insertions(+), 13 deletions(-) diff --git a/lib/facter/ec2.rb b/lib/facter/ec2.rb index 418e60b171..09e0109528 100644 --- a/lib/facter/ec2.rb +++ b/lib/facter/ec2.rb @@ -21,7 +21,9 @@ def metadata(id = "") def userdata() Facter.add(:ec2_userdata) do setcode do - open("http://169.254.169.254/2008-02-01/user-data/").read.split + if userdata = Facter::Util::EC2.userdata + userdata.split + end end end end diff --git a/lib/facter/util/ec2.rb b/lib/facter/util/ec2.rb index 8f024d4a24..c5c393ac3f 100644 --- a/lib/facter/util/ec2.rb +++ b/lib/facter/util/ec2.rb @@ -58,4 +58,44 @@ def has_ec2_arp? return false end end + + ## + # userdata returns a single string containing the body of the response of the + # GET request for the URI http://169.254.169.254/latest/user-data/ If the + # metadata server responds with a 404 Not Found error code then this method + # retuns `nil`. + # + # @param version [String] containing the API version for the request. + # Defaults to "latest" and other examples are documented at + # http://aws.amazon.com/archives/Amazon%20EC2 + # + # @api public + # + # @return [String] containing the response body or `nil` + def self.userdata(version="latest") + uri = "http://169.254.169.254/#{version}/user-data/" + begin + read_uri(uri) + rescue OpenURI::HTTPError => detail + case detail.message + when /404 Not Found/i + Facter.debug "No user-data present at #{uri}: server responded with #{detail.message}" + return nil + else + raise detail + end + end + end + + ## + # read_uri provides a seam method to easily test the HTTP client + # functionality of a HTTP based metadata server. + # + # @api private + # + # @return [String] containing the body of the response + def self.read_uri(uri) + open(uri).read + end + private_class_method :read_uri end diff --git a/spec/unit/ec2_spec.rb b/spec/unit/ec2_spec.rb index 8b3bc333b6..185e6a8d6e 100755 --- a/spec/unit/ec2_spec.rb +++ b/spec/unit/ec2_spec.rb @@ -68,9 +68,9 @@ with("#{api_prefix}/2008-02-01/meta-data/"). at_least_once.returns(StringIO.new("")) - Facter::Util::Resolution.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/user-data/"). - at_least_once.returns(StringIO.new("test")) + Facter::Util::EC2.stubs(:read_uri). + with("#{api_prefix}/latest/user-data/"). + returns("test") Facter.collection.loader.load(:ec2) Facter.fact(:ec2_userdata).value.should == ["test"] @@ -94,9 +94,9 @@ with("#{api_prefix}/2008-02-01/meta-data/").\ at_least_once.returns(StringIO.new("")) - Facter::Util::Resolution.any_instance.expects(:open).\ - with("#{api_prefix}/2008-02-01/user-data/").\ - at_least_once.returns(StringIO.new("test")) + Facter::Util::EC2.stubs(:read_uri). + with("#{api_prefix}/latest/user-data/"). + returns("test") # Force a fact load Facter.collection.loader.load(:ec2) @@ -122,9 +122,9 @@ with("#{api_prefix}/2008-02-01/meta-data/").\ at_least_once.returns(StringIO.new("")) - Facter::Util::Resolution.any_instance.expects(:open).\ - with("#{api_prefix}/2008-02-01/user-data/").\ - at_least_once.returns(StringIO.new("test")) + Facter::Util::EC2.stubs(:read_uri). + with("#{api_prefix}/latest/user-data/"). + returns("test") # Force a fact load Facter.collection.loader.load(:ec2) @@ -140,9 +140,9 @@ with("#{api_prefix}/2008-02-01/meta-data/"). at_least_once.raises(RuntimeError, 'host unreachable') - Facter::Util::Resolution.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/user-data/"). - at_least_once.raises(RuntimeError, 'host unreachable') + Facter::Util::EC2.stubs(:read_uri). + with("#{api_prefix}/latest/user-data/"). + raises(RuntimeError, 'host unreachable') # Force a fact load Facter.collection.loader.load(:ec2) diff --git a/spec/unit/util/ec2_spec.rb b/spec/unit/util/ec2_spec.rb index a7ab2c0e1e..c894e597a3 100755 --- a/spec/unit/util/ec2_spec.rb +++ b/spec/unit/util/ec2_spec.rb @@ -151,4 +151,30 @@ end end end + + describe "Facter::Util::EC2.userdata" do + let :not_found_error do + OpenURI::HTTPError.new("404 Not Found", StringIO.new) + end + + let :example_userdata do + "owner=jeff@puppetlabs.com\ngroup=platform_team" + end + + it 'returns nil when no userdata is present' do + Facter::Util::EC2.stubs(:read_uri).raises(not_found_error) + Facter::Util::EC2.userdata.should be_nil + end + + it "returns the string containing the body" do + Facter::Util::EC2.stubs(:read_uri).returns(example_userdata) + Facter::Util::EC2.userdata.should == example_userdata + end + + it "uses the specified API version" do + expected_uri = "http://169.254.169.254/2008-02-01/user-data/" + Facter::Util::EC2.expects(:read_uri).with(expected_uri).returns(example_userdata) + Facter::Util::EC2.userdata('2008-02-01').should == example_userdata + end + end end From a36918ab40965dd03bb492eec055920e5cd3ad13 Mon Sep 17 00:00:00 2001 From: Pierre-Gilles Mialon Date: Wed, 14 Nov 2012 09:53:52 +0100 Subject: [PATCH 1112/3753] (#17612) Add Google Compute Engine support to virtual fact Without this patch the virtual fact does not return a value that specifically identifies Google Compute Engine. This is a problem because configuration decisions may depend on if the resources being configured exist within GCE or not. This patch addresses the problem by changing the behavior of the `virtual` fact to return the value `gce` when running in Google Compute Engine's cloud. This patch uses the Google recommended and documented way to detect if we are running on a Google Compute Engine: https://developers.google.com/compute/docs/instances#dmi --- lib/facter/virtual.rb | 3 +++ spec/unit/virtual_spec.rb | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index eed3ab15aa..c4e1b0edbf 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -111,6 +111,9 @@ # --- look for Hyper-V video card # --- 00:08.0 VGA compatible controller: Microsoft Corporation Hyper-V virtual VGA result = "hyperv" if p =~ /Microsoft Corporation Hyper-V/ + # --- look for gmetrics for GCE + # --- 00:05.0 Class 8007: Google, Inc. Device 6442 + result = "gce" if p =~ /Class 8007: Google, Inc/ end else output = Facter::Util::Resolution.exec('dmidecode') diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index d8714592f9..c06d2a79e3 100755 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -105,6 +105,12 @@ Facter.fact(:virtual).value.should == "virtualbox" end + it "should be gce with gce vendor name from lspci 2>/dev/null" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns("00:05.0 Class 8007: Google, Inc. Device 6442") + Facter.fact(:virtual).value.should == "gce" + end + it "should be vmware with VMWare vendor name from dmidecode" do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) From a43d6f870390f5f463643383c56fa21c52e5e6b2 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Wed, 12 Dec 2012 14:42:05 -0800 Subject: [PATCH 1113/3753] (#17612) Follow Google docs to populate virtual => gce Without this patch applied Facter uses lspci to determine if the system is running in the Google Compute Engine virtual environment. This is a problem because Google recommends parsing the output of /sys/firmware/dmi/entries/1-0/raw as per [Detecting if You Are Running in Google Compute Engine](https://developers.google.com/compute/docs/instances#dmi). `lspci` may be unreliable over time. This patch addresses the problem by adding a higher weight fact that scans `/sys/firmware/dmi/entries/1-0/raw` as recommended by Google. The fact is confined to Linux and disables itself if `/sys/firmware/dmi/entries/1-0/raw` does not exist in the filesystem. --- lib/facter/util/virtual.rb | 19 ++++++++- lib/facter/virtual.rb | 15 +++++++ .../unit/virtual/sysfs_dmi_entries_raw.txt | Bin 0 -> 42 bytes spec/unit/virtual_spec.rb | 37 +++++++++++++++--- 4 files changed, 63 insertions(+), 8 deletions(-) create mode 100644 spec/fixtures/unit/virtual/sysfs_dmi_entries_raw.txt diff --git a/lib/facter/util/virtual.rb b/lib/facter/util/virtual.rb index 40a33e50e6..bc2c9a6734 100644 --- a/lib/facter/util/virtual.rb +++ b/lib/facter/util/virtual.rb @@ -99,7 +99,22 @@ def self.hpvm? Facter::Util::Resolution.exec("/usr/bin/getconf MACHINE_MODEL").chomp =~ /Virtual Machine/ end - def self.zlinux? + def self.zlinux? "zlinux" - end + end + + ## + # read_sysfs Reads the raw data as per the documentation at [Detecting if You + # Are Running in Google Compute + # Engine](https://developers.google.com/compute/docs/instances#dmi) This + # method is intended to provide an easy seam to mock. + # + # @api public + # + # @return [String] or nil if the path does not exist + def self.read_sysfs_dmi_entries(path="/sys/firmware/dmi/entries/1-0/raw") + if File.exists?(path) + File.read(path) + end + end end diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index c4e1b0edbf..f365c9a87e 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -193,6 +193,21 @@ end end +## +# virtual fact specific to Google Compute Engine's Linux sysfs entry. +Facter.add("virtual") do + has_weight 600 + confine :kernel => "Linux" + + setcode do + if dmi_data = Facter::Util::Virtual.read_sysfs_dmi_entries + case dmi_data + when /Google/ + "gce" + end + end + end +end # Fact: is_virtual # # Purpose: returning true or false for if a machine is virtualised or not. diff --git a/spec/fixtures/unit/virtual/sysfs_dmi_entries_raw.txt b/spec/fixtures/unit/virtual/sysfs_dmi_entries_raw.txt new file mode 100644 index 0000000000000000000000000000000000000000..81c025a0f904998758cc47b8e404ec1b4791e40c GIT binary patch literal 42 bcmZRSW?*DwV!!}w3=Ho1`RO^SFp2>HFRujQ literal 0 HcmV?d00001 diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index c06d2a79e3..e2355bf43e 100755 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -105,12 +105,6 @@ Facter.fact(:virtual).value.should == "virtualbox" end - it "should be gce with gce vendor name from lspci 2>/dev/null" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns("00:05.0 Class 8007: Google, Inc. Device 6442") - Facter.fact(:virtual).value.should == "gce" - end - it "should be vmware with VMWare vendor name from dmidecode" do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) @@ -176,6 +170,37 @@ Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("System Information\nManufacturer: Microsoft Corporation\nProduct Name: Virtual Machine") Facter.fact(:virtual).value.should == "hyperv" end + + context "In Google Compute Engine" do + before :each do + Facter.fact(:kernel).stubs(:value).returns("Linux") + end + + context "Without /sys/firmware/dmi/entries/1-0/raw" do + before :each do + Facter::Util::Virtual.stubs(:read_sysfs_dmi_entries).returns(nil) + end + + it "should be gce with gce vendor name from lspci 2>/dev/null" do + Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns("00:05.0 Class 8007: Google, Inc. Device 6442") + Facter.fact(:virtual).value.should == "gce" + end + end + + context "With /sys/firmware/dmi/entries/1-0/raw" do + let :sysfs_dmi_raw do + my_fixture_read('sysfs_dmi_entries_raw.txt') + end + + before :each do + Facter::Util::Virtual.stubs(:read_sysfs_dmi_entries).returns(sysfs_dmi_raw) + end + + it "(#17612) is 'gce'" do + Facter.fact(:virtual).value.should == "gce" + end + end + end end describe "on Solaris" do From 41909b7dd59a209a93ff1f4415b67612c5a3e8c0 Mon Sep 17 00:00:00 2001 From: Jared Curtis Date: Sat, 1 Dec 2012 07:34:08 -0800 Subject: [PATCH 1114/3753] (#15001) ifconfig regex will optionally match 'addr:' Newer versions of ifconfig, as found in Fedora 17, have changed the output format of ifconfig. This change removed the 'addr:' string which prevents the regex from matching. This patch makes matching 'addr:' optional. --- lib/facter/ipaddress.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/ipaddress.rb b/lib/facter/ipaddress.rb index 1d0f2886d0..346b535011 100644 --- a/lib/facter/ipaddress.rb +++ b/lib/facter/ipaddress.rb @@ -29,7 +29,7 @@ output = %x{/sbin/ifconfig 2>/dev/null} output.split(/^\S/).each { |str| - if str =~ /inet addr:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + if str =~ /inet (?:addr:)?([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ tmp = $1 unless tmp =~ /^127\./ ip = tmp From ae348934b35b2ceb346e9b37c58d56d178108933 Mon Sep 17 00:00:00 2001 From: Jared Curtis Date: Wed, 5 Dec 2012 16:02:42 -0800 Subject: [PATCH 1115/3753] Test data for net-tools 1.60 --- spec/fixtures/ifconfig/linux_ifconfig_no_addr | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 spec/fixtures/ifconfig/linux_ifconfig_no_addr diff --git a/spec/fixtures/ifconfig/linux_ifconfig_no_addr b/spec/fixtures/ifconfig/linux_ifconfig_no_addr new file mode 100644 index 0000000000..dcfdaf1232 --- /dev/null +++ b/spec/fixtures/ifconfig/linux_ifconfig_no_addr @@ -0,0 +1,19 @@ +em1: flags=4163 mtu 1500 + inet 131.252.209.153 netmask 255.255.255.0 broadcast 192.168.76.255 + inet6 2610:10:20:209:212:3fff:febe:2201 prefixlen 128 scopeid 0x0 + inet6 fe80::221:ccff:fe4b:297d prefixlen 64 scopeid 0x20 + ether 00:21:cc:4b:29:7d txqueuelen 1000 (Ethernet) + RX packets 27222144 bytes 31247414760 (29.1 GiB) + RX errors 0 dropped 0 overruns 0 frame 0 + TX packets 10259038 bytes 7784519352 (7.2 GiB) + TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 + device interrupt 20 memory 0xd2600000-d2620000 + +lo: flags=73 mtu 16436 + inet 127.0.0.1 netmask 255.0.0.0 + inet6 ::1 prefixlen 128 scopeid 0x10 + loop txqueuelen 0 (Local Loopback) + RX packets 257371 bytes 37104110 (35.3 MiB) + RX errors 0 dropped 0 overruns 0 frame 0 + TX packets 257371 bytes 37104110 (35.3 MiB) + TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 From 0e514b843642176a9322bd3b8d0ad9b4420e516e Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Mon, 17 Dec 2012 17:22:49 -0800 Subject: [PATCH 1116/3753] (#15001) Add spec examples for ipaddress fact Without this patch we don't have an example behaviors for the ipaddress fact. This patch addresses the problem by adding two examples for the facts parsing the output of ifconfig. These examples slightly refactor the ipaddress fact confined to Linux in order to take advantage of the stubbed method returning the output of the ifconfig command. Fixture data for ifconfig output is included for Ubuntu 12.04 and Fedora 17. The notable difference with Fedora 17 is that Net Tools 1.60 removes the "addr" substring from the interface description. --- lib/facter/ipaddress.rb | 19 ++++------- lib/facter/util/ip.rb | 8 +++++ .../ipaddress/ifconfig_net_tools_1.60.txt | 19 +++++++++++ .../unit/ipaddress/ifconfig_ubuntu_1204.txt | 16 ++++++++++ spec/unit/ipaddress_spec.rb | 32 +++++++++++++++++++ 5 files changed, 82 insertions(+), 12 deletions(-) create mode 100644 spec/fixtures/unit/ipaddress/ifconfig_net_tools_1.60.txt create mode 100644 spec/fixtures/unit/ipaddress/ifconfig_ubuntu_1204.txt create mode 100755 spec/unit/ipaddress_spec.rb diff --git a/lib/facter/ipaddress.rb b/lib/facter/ipaddress.rb index 346b535011..a7d7c4d6be 100644 --- a/lib/facter/ipaddress.rb +++ b/lib/facter/ipaddress.rb @@ -22,23 +22,18 @@ # checking this is a useful IP address. # +require 'facter/util/ip' + Facter.add(:ipaddress) do confine :kernel => :linux setcode do ip = nil - output = %x{/sbin/ifconfig 2>/dev/null} - - output.split(/^\S/).each { |str| - if str =~ /inet (?:addr:)?([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ - tmp = $1 - unless tmp =~ /^127\./ - ip = tmp - break - end + if output = Facter::Util::IP.get_ifconfig + regexp = /inet (?:addr:)?([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + if match = regexp.match(output) + match[1] unless /^127/.match(match[1]) end - } - - ip + end end end diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index 3f544ab83f..dfb4e8053d 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -96,6 +96,14 @@ def self.get_all_interface_output output end + ## + # get_ifconfig simply delegates to the ifconfig command. + # + # @return [String] the output of `/sbin/ifconfig 2>/dev/null` or nil + def self.get_ifconfig + Facter::Util::Resolution.exec("/sbin/ifconfig 2>/dev/null") + end + ## # hpux_netstat_in is a delegate method that allows us to stub netstat -in # without stubbing exec. diff --git a/spec/fixtures/unit/ipaddress/ifconfig_net_tools_1.60.txt b/spec/fixtures/unit/ipaddress/ifconfig_net_tools_1.60.txt new file mode 100644 index 0000000000..dcfdaf1232 --- /dev/null +++ b/spec/fixtures/unit/ipaddress/ifconfig_net_tools_1.60.txt @@ -0,0 +1,19 @@ +em1: flags=4163 mtu 1500 + inet 131.252.209.153 netmask 255.255.255.0 broadcast 192.168.76.255 + inet6 2610:10:20:209:212:3fff:febe:2201 prefixlen 128 scopeid 0x0 + inet6 fe80::221:ccff:fe4b:297d prefixlen 64 scopeid 0x20 + ether 00:21:cc:4b:29:7d txqueuelen 1000 (Ethernet) + RX packets 27222144 bytes 31247414760 (29.1 GiB) + RX errors 0 dropped 0 overruns 0 frame 0 + TX packets 10259038 bytes 7784519352 (7.2 GiB) + TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 + device interrupt 20 memory 0xd2600000-d2620000 + +lo: flags=73 mtu 16436 + inet 127.0.0.1 netmask 255.0.0.0 + inet6 ::1 prefixlen 128 scopeid 0x10 + loop txqueuelen 0 (Local Loopback) + RX packets 257371 bytes 37104110 (35.3 MiB) + RX errors 0 dropped 0 overruns 0 frame 0 + TX packets 257371 bytes 37104110 (35.3 MiB) + TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 diff --git a/spec/fixtures/unit/ipaddress/ifconfig_ubuntu_1204.txt b/spec/fixtures/unit/ipaddress/ifconfig_ubuntu_1204.txt new file mode 100644 index 0000000000..f2800cc7b8 --- /dev/null +++ b/spec/fixtures/unit/ipaddress/ifconfig_ubuntu_1204.txt @@ -0,0 +1,16 @@ +eth0 Link encap:Ethernet HWaddr 42:01:0a:57:50:6e + inet addr:10.87.80.110 Bcast:10.87.80.110 Mask:255.255.255.255 + UP BROADCAST RUNNING MULTICAST MTU:1460 Metric:1 + RX packets:1609444 errors:0 dropped:0 overruns:0 frame:0 + TX packets:1479569 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:1000 + RX bytes:673812693 (673.8 MB) TX bytes:221186872 (221.1 MB) + +lo Link encap:Local Loopback + inet addr:127.0.0.1 Mask:255.0.0.0 + UP LOOPBACK RUNNING MTU:16436 Metric:1 + RX packets:5435415 errors:0 dropped:0 overruns:0 frame:0 + TX packets:5435415 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:0 + RX bytes:734540224 (734.5 MB) TX bytes:734540224 (734.5 MB) + diff --git a/spec/unit/ipaddress_spec.rb b/spec/unit/ipaddress_spec.rb new file mode 100755 index 0000000000..f746e1012e --- /dev/null +++ b/spec/unit/ipaddress_spec.rb @@ -0,0 +1,32 @@ +#! /usr/bin/env ruby + +require 'spec_helper' +require 'facter/util/ip' + +shared_examples_for "ifconfig output" do |platform, address, fixture| + it "correctly on #{platform}" do + Facter::Util::IP.stubs(:get_ifconfig).returns(my_fixture_read(fixture)) + subject.value.should == address + end +end + +RSpec.configure do |config| + config.alias_it_should_behave_like_to :example_behavior_for, "parses" +end + +describe "The ipaddress fact" do + subject do + Facter.collection.loader.load(:ipaddress) + Facter.fact(:ipaddress) + end + context "on Linux" do + before :each do + Facter.fact(:kernel).stubs(:value).returns("Linux") + end + + example_behavior_for "ifconfig output", + "Ubuntu 12.04", "10.87.80.110", "ifconfig_ubuntu_1204.txt" + example_behavior_for "ifconfig output", + "Fedora 17", "131.252.209.153", "ifconfig_net_tools_1.60.txt" + end +end From 41e94681a8f9c59e63c4435dc2239ab890b57854 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Mon, 17 Dec 2012 17:27:46 -0800 Subject: [PATCH 1117/3753] (#15001) Use the internal loader to load ipaddress Without this patch the fix to the ipaddress fact fails our spec tests because the method Facter.collection.internal_loader.load should be used to load a fact. This patch uses the correct method call and resolves the spec failure. --- spec/unit/ipaddress_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/unit/ipaddress_spec.rb b/spec/unit/ipaddress_spec.rb index f746e1012e..3e874f7ddd 100755 --- a/spec/unit/ipaddress_spec.rb +++ b/spec/unit/ipaddress_spec.rb @@ -16,7 +16,7 @@ describe "The ipaddress fact" do subject do - Facter.collection.loader.load(:ipaddress) + Facter.collection.internal_loader.load(:ipaddress) Facter.fact(:ipaddress) end context "on Linux" do From 362853398bebfc6f0d868bd63f9e2157ae132b96 Mon Sep 17 00:00:00 2001 From: Chris Barker Date: Fri, 7 Dec 2012 12:14:57 -0800 Subject: [PATCH 1118/3753] (#15708) fixes facter file mappings for pkgbuild changes ext and var to be private/etc and private/var previous behavior would break systems running 10.5 if this was installed as a flat package (pkgbuild's format) --- ext/osx/file_mapping.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/osx/file_mapping.yaml b/ext/osx/file_mapping.yaml index 6febd13787..8ee36ad8d1 100644 --- a/ext/osx/file_mapping.yaml +++ b/ext/osx/file_mapping.yaml @@ -10,12 +10,12 @@ directories: group: 'wheel' perms: '0755' facter: - path: 'var/lib/facter' + path: 'private/var/lib/facter' owner: 'root' group: 'wheel' perms: '0644' etc: - path: 'etc' + path: 'private/etc' owner: 'root' group: 'wheel' perms: '0644' From d770aec2dfd6312df247fde54315fe3bf3b4775d Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Wed, 19 Dec 2012 14:48:53 -0800 Subject: [PATCH 1119/3753] Update lib/facter/version.rb for 1.6.17-rc1 --- lib/facter/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/version.rb b/lib/facter/version.rb index bd074615eb..8bad44ee24 100644 --- a/lib/facter/version.rb +++ b/lib/facter/version.rb @@ -1,6 +1,6 @@ module Facter if not defined? FACTERVERSION then - FACTERVERSION = '1.6.16' + FACTERVERSION = '1.6.17-rc1' end def self.version From bf19fb399372b55aa7044747a1df29e9648f3d2e Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Sat, 30 Jun 2012 00:51:09 +0200 Subject: [PATCH 1120/3753] (#15306) Read /proc/self/status in binary mode On at least a Joyent SmartMachine (illumos based Solaris) the file /proc/self/status contains binary data. When read with File.read in Ruby 1.9, this is parsed as UTF-8 which fails with an exception. This patch instead opens the file in binary mode to avoid the failure. --- lib/facter/util/virtual.rb | 2 +- spec/unit/util/virtual_spec.rb | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/facter/util/virtual.rb b/lib/facter/util/virtual.rb index 11e6f79f2e..41472b89b1 100644 --- a/lib/facter/util/virtual.rb +++ b/lib/facter/util/virtual.rb @@ -46,7 +46,7 @@ def self.zone? def self.vserver? return false unless FileTest.exists?("/proc/self/status") - txt = File.read("/proc/self/status") + txt = File.open("/proc/self/status", "rb").read return true if txt =~ /^(s_context|VxID):[[:blank:]]*[0-9]/ return false end diff --git a/spec/unit/util/virtual_spec.rb b/spec/unit/util/virtual_spec.rb index cf22f2b6d5..da74d7094c 100755 --- a/spec/unit/util/virtual_spec.rb +++ b/spec/unit/util/virtual_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' require 'facter/util/virtual' +require 'stringio' describe Facter::Util::Virtual do @@ -64,19 +65,19 @@ it "should detect vserver when vxid present in process status" do FileTest.stubs(:exists?).with("/proc/self/status").returns(true) - File.stubs(:read).with("/proc/self/status").returns("VxID: 42\n") + File.stubs(:open).with("/proc/self/status", "rb").returns(StringIO.new("VxID: 42\n")) Facter::Util::Virtual.should be_vserver end it "should detect vserver when s_context present in process status" do FileTest.stubs(:exists?).with("/proc/self/status").returns(true) - File.stubs(:read).with("/proc/self/status").returns("s_context: 42\n") + File.stubs(:open).with("/proc/self/status", "rb").returns(StringIO.new("s_context: 42\n")) Facter::Util::Virtual.should be_vserver end it "should not detect vserver when vserver flags not present in process status" do FileTest.stubs(:exists?).with("/proc/self/status").returns(true) - File.stubs(:read).with("/proc/self/status").returns("wibble: 42\n") + File.stubs(:open).with("/proc/self/status", "rb").returns(StringIO.new("wibble: 42\n")) Facter::Util::Virtual.should_not be_vserver end @@ -94,7 +95,7 @@ it "should detect vserver as #{expected.inspect}" do status = File.read(status_file) FileTest.stubs(:exists?).with("/proc/self/status").returns(true) - File.stubs(:read).with("/proc/self/status").returns(status) + File.stubs(:open).with("/proc/self/status", "rb").returns(StringIO.new(status)) Facter::Util::Virtual.vserver?.should == expected end end From 70c6c92c2bdde4705cf79e242af415c343e565fc Mon Sep 17 00:00:00 2001 From: Matthaus Owens Date: Thu, 27 Dec 2012 10:01:29 -0800 Subject: [PATCH 1121/3753] Update FACTERVERSION to 1.6.17 --- lib/facter/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/version.rb b/lib/facter/version.rb index 8bad44ee24..8da96e8253 100644 --- a/lib/facter/version.rb +++ b/lib/facter/version.rb @@ -1,6 +1,6 @@ module Facter if not defined? FACTERVERSION then - FACTERVERSION = '1.6.17-rc1' + FACTERVERSION = '1.6.17' end def self.version From 8dd7ec6706995d339ac6ce8a98071cc67bd814cc Mon Sep 17 00:00:00 2001 From: Garrett Honeycutt Date: Thu, 3 Jan 2013 11:01:18 -0500 Subject: [PATCH 1122/3753] (#18336) osfamily for mandrake This commit will add another osfamily entry for Mandrake that includes the Mandriva derivative. It will also allow for change in the urpmi package provider as noted in ticket #18335. --- lib/facter/osfamily.rb | 2 ++ spec/unit/osfamily_spec.rb | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/facter/osfamily.rb b/lib/facter/osfamily.rb index 6162ef8929..d3c6a5461a 100644 --- a/lib/facter/osfamily.rb +++ b/lib/facter/osfamily.rb @@ -28,6 +28,8 @@ "Gentoo" when "Archlinux" "Archlinux" + when "Mandrake", "Mandriva" + "Mandrake" else Facter.value("kernel") end diff --git a/spec/unit/osfamily_spec.rb b/spec/unit/osfamily_spec.rb index ecab32679b..4113b3c326 100644 --- a/spec/unit/osfamily_spec.rb +++ b/spec/unit/osfamily_spec.rb @@ -30,7 +30,9 @@ 'SLES' => 'Suse', 'SLED' => 'Suse', 'OpenSuSE' => 'Suse', - 'SuSE' => 'Suse' + 'SuSE' => 'Suse', + 'Mandriva' => 'Mandrake', + 'Mandrake' => 'Mandrake' }.each do |os,family| it "should return #{family} on operatingsystem #{os}" do Facter.fact(:operatingsystem).stubs(:value).returns os @@ -39,8 +41,6 @@ end [ - 'Mandriva', - 'Mandrake', 'MeeGo', 'VMWareESX', 'Bluewhite64', From 9dd8afbca5231a4824d8e57def7ebfa54546f2c9 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Sat, 5 Jan 2013 21:57:01 -0800 Subject: [PATCH 1123/3753] (maint) Add Travis CI Support Without this patch facter has Travis CI configuration files, but they don't seem to completely specify the dependency versions and the build matrix. This patch addresses the problem by putting the dependency information in the conventional Gemfile location. This patch should coincide with enabling Travis CI support for pull requests. A build status image is also included in the project README. Finally, this patch also removes the Gemfile.lock since this shouldn't be included in the repository as Facter is more of application than it is a library. This reverses the decision to include it, which was my mistake, in 0b49eae and (#15464). --- .gitignore | 3 +++ .travis.yml | 12 ++++++++++++ Gemfile | 16 +++++++++++----- Gemfile.lock | 28 ---------------------------- README.md | 2 ++ facter.gemspec | 16 +++++++++++++++- 6 files changed, 43 insertions(+), 34 deletions(-) create mode 100644 .travis.yml delete mode 100644 Gemfile.lock diff --git a/.gitignore b/.gitignore index fb7f80a195..e6a4d2aa7a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ ext/packaging/ # YARD generated documentation .yardoc doc +.bundle +vendor/ +Gemfile.lock diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..4e72a7c121 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,12 @@ +language: ruby +bundler_args: --without development +script: "bundle exec rake spec SPEC_OPTS='--format documentation'" +notifications: + email: false +rvm: + - 1.9.3 + - 1.8.7 + - ruby-head +matrix: + allow_failures: + - rvm: ruby-head diff --git a/Gemfile b/Gemfile index 7c2472002e..0abec7398e 100644 --- a/Gemfile +++ b/Gemfile @@ -1,14 +1,20 @@ source :rubygems -gemspec +group :development do + gem 'watchr' +end -group(:development, :test) do - gem "rspec", "~> 2.10.0", :require => false - gem "mocha", "~> 0.10.5", :require => false +group :development, :test do + gem 'rake' + gem 'facter', ">= 1.0.0", :path => File.expand_path("..", __FILE__), :require => false + gem 'rspec', "~> 2.11.0", :require => false + gem 'mocha', "~> 0.10.5", :require => false + gem 'json', "~> 1.7", :require => false + gem 'puppetlabs_spec_helper', :require => false end if File.exists? "#{__FILE__}.local" eval(File.read("#{__FILE__}.local"), binding) end -# vim:filetype=ruby +# vim:ft=ruby diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index 22a3121b60..0000000000 --- a/Gemfile.lock +++ /dev/null @@ -1,28 +0,0 @@ -PATH - remote: . - specs: - facter (1.6.11) - -GEM - remote: http://rubygems.org/ - specs: - diff-lcs (1.1.3) - metaclass (0.0.1) - mocha (0.10.5) - metaclass (~> 0.0.1) - rspec (2.10.0) - rspec-core (~> 2.10.0) - rspec-expectations (~> 2.10.0) - rspec-mocks (~> 2.10.0) - rspec-core (2.10.1) - rspec-expectations (2.10.0) - diff-lcs (~> 1.1.3) - rspec-mocks (2.10.1) - -PLATFORMS - ruby - -DEPENDENCIES - facter! - mocha (~> 0.10.5) - rspec (~> 2.10.0) diff --git a/README.md b/README.md index 0185c582c1..c7a747d8c4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ Facter ====== +[![Build Status](https://travis-ci.org/puppetlabs/facter.png?branch=master)](https://travis-ci.org/puppetlabs/facter) + This package is largely meant to be a library for collecting facts about your system. These facts are mostly strings (i.e., not numbers), and are things like the output of `uname`, public ssh keys, the number of processors, etc. diff --git a/facter.gemspec b/facter.gemspec index d2ec27b6f4..ed68793419 100644 --- a/facter.gemspec +++ b/facter.gemspec @@ -1,8 +1,22 @@ +# # -*- encoding: utf-8 -*- +begin + require 'facter/version' +rescue LoadError => detail + $LOAD_PATH.unshift(File.expand_path("../lib", __FILE__)) + require 'facter/version' +end + Gem::Specification.new do |s| s.name = "facter" - s.version = "1.6.11" + + version = Facter.version + if mdata = version.match(/(\d+\.\d+\.\d+)/) + s.version = mdata[1] + else + s.version = version + end s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Puppet Labs"] From 7879c33f69f8cb07e6c0c1ba3f57317184de0ee4 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Sat, 5 Jan 2013 23:22:02 -0800 Subject: [PATCH 1124/3753] Fix Travis CI failures on virtualbox Without this patch Travis CI has false positive spec test failures. This is becuase the virtualbox utility methods are not properly stubbed, causing other tests to execute in an unexpected fake environment. This patch fixes the problem by stubbing out the `Facter::Util::Virtual.virtualbox?` method. --- spec/unit/virtual_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index e2355bf43e..a0b6e669f7 100755 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -14,6 +14,7 @@ Facter::Util::Virtual.stubs(:hpvm?).returns(false) Facter::Util::Virtual.stubs(:zlinux?).returns(false) Facter::Util::Virtual.stubs(:virt_what).returns(nil) + Facter::Util::Virtual.stubs(:virtualbox?).returns(false) end it "should be zone on Solaris when a zone" do From 7522fde125a67ed77299d189bf2ad4e97a34760a Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Sun, 6 Jan 2013 11:01:37 -0800 Subject: [PATCH 1125/3753] Whitespace only reflow of README This branch does not have a travis configuration file so I'm checking the behavior of Travis when the branch is pushed to github. I suspect every single active branch needs a .travis.yml configuration file. --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e5a54534b6..bbaf029038 100644 --- a/README.md +++ b/README.md @@ -11,12 +11,14 @@ See `bin/facter` for an example of the interface. Running Facter -------------- -Run the `facter` binary on the command for a full list of facts supported on your host. +Run the `facter` binary on the command for a full list of facts supported on +your host. Adding your own facts --------------------- -See the [Adding Facts](http://docs.puppetlabs.com/guides/custom_facts.html) page for details of how to add your own custom facts to Facter. +See the [Adding Facts](http://docs.puppetlabs.com/guides/custom_facts.html) +page for details of how to add your own custom facts to Facter. Further Information ------------------- From b8f6abf5e2ca6fd5aa69b245d5c448e03d9e7779 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Sun, 6 Jan 2013 13:45:16 -0800 Subject: [PATCH 1126/3753] (maint) Add Travis CI support to active branches Without this patch all active branches do not have proper Travis configuration files. This is a problem because Travis will exercise any branch pushed to the puppetlabs organization repository on Github. The default behavior is to email notifications which is undesirable. To address this problem Travis configuration settings are added to an active branch. Active branches are those that have recent activity when compared against the following command: for k in `git branch -r |perl -pe s/^..//` do echo -e `git show --pretty=format:"%Cgreen%ci %Cblue%cr%Creset" $k|head -n 1`\\t$k; done | sort -r | grep origin Which should produce output like: 2013-01-06 11:01:40 -0800 3 hours ago origin/1.6.x 2013-01-05 23:33:48 -0800 14 hours ago origin/master 2012-12-27 10:03:43 -0800 10 days ago origin/2.x 2012-12-27 10:02:58 -0800 10 days ago origin/1.7.x --- .gitignore | 3 +++ .travis.yml | 12 ++++++++++++ Gemfile | 16 +++++++++++----- Gemfile.lock | 28 ---------------------------- 4 files changed, 26 insertions(+), 33 deletions(-) create mode 100644 .travis.yml delete mode 100644 Gemfile.lock diff --git a/.gitignore b/.gitignore index fb7f80a195..6b4c36bb9d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ ext/packaging/ # YARD generated documentation .yardoc doc +.bundle/ +vendor/ +Gemfile.lock diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..3eec50c542 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,12 @@ +language: ruby +bundler_args: --without development +script: "bundle exec rake spec SPEC_OPTS='--color --format documentation'" +notifications: + email: false +rvm: + - 1.9.3 + - 1.8.7 + - ruby-head +matrix: + allow_failures: + - rvm: ruby-head diff --git a/Gemfile b/Gemfile index 7c2472002e..0abec7398e 100644 --- a/Gemfile +++ b/Gemfile @@ -1,14 +1,20 @@ source :rubygems -gemspec +group :development do + gem 'watchr' +end -group(:development, :test) do - gem "rspec", "~> 2.10.0", :require => false - gem "mocha", "~> 0.10.5", :require => false +group :development, :test do + gem 'rake' + gem 'facter', ">= 1.0.0", :path => File.expand_path("..", __FILE__), :require => false + gem 'rspec', "~> 2.11.0", :require => false + gem 'mocha', "~> 0.10.5", :require => false + gem 'json', "~> 1.7", :require => false + gem 'puppetlabs_spec_helper', :require => false end if File.exists? "#{__FILE__}.local" eval(File.read("#{__FILE__}.local"), binding) end -# vim:filetype=ruby +# vim:ft=ruby diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index 22a3121b60..0000000000 --- a/Gemfile.lock +++ /dev/null @@ -1,28 +0,0 @@ -PATH - remote: . - specs: - facter (1.6.11) - -GEM - remote: http://rubygems.org/ - specs: - diff-lcs (1.1.3) - metaclass (0.0.1) - mocha (0.10.5) - metaclass (~> 0.0.1) - rspec (2.10.0) - rspec-core (~> 2.10.0) - rspec-expectations (~> 2.10.0) - rspec-mocks (~> 2.10.0) - rspec-core (2.10.1) - rspec-expectations (2.10.0) - diff-lcs (~> 1.1.3) - rspec-mocks (2.10.1) - -PLATFORMS - ruby - -DEPENDENCIES - facter! - mocha (~> 0.10.5) - rspec (~> 2.10.0) From 51de32a66ab0be587217bdb44bcf39fa903e550d Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Sat, 5 Jan 2013 23:22:02 -0800 Subject: [PATCH 1127/3753] Fix Travis CI failures on virtualbox Without this patch Travis CI has false positive spec test failures. This is becuase the virtualbox utility methods are not properly stubbed, causing other tests to execute in an unexpected fake environment. This patch fixes the problem by stubbing out the `Facter::Util::Virtual.virtualbox?` method. --- spec/unit/virtual_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index 8e6863fd6a..d0d4d15619 100755 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -14,6 +14,7 @@ Facter::Util::Virtual.stubs(:hpvm?).returns(false) Facter::Util::Virtual.stubs(:zlinux?).returns(false) Facter::Util::Virtual.stubs(:virt_what).returns(nil) + Facter::Util::Virtual.stubs(:virtualbox?).returns(false) end it "should be zone on Solaris when a zone" do From f0956497ddf19ac49df5e9a86e1c93580cb5e939 Mon Sep 17 00:00:00 2001 From: Zach Leslie Date: Tue, 15 Jan 2013 15:41:57 -0800 Subject: [PATCH 1128/3753] (maint) DRY up the ProcessorCount fact for BSD systems Confine OpenBSD, FreeBSD, and DragonFlyBSD ProcessorCount fact into a single definition. --- lib/facter/processor.rb | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb index 804cfa3fcc..080b8c1f27 100644 --- a/lib/facter/processor.rb +++ b/lib/facter/processor.rb @@ -95,13 +95,6 @@ end end -Facter.add("ProcessorCount") do - confine :kernel => :openbsd - setcode do - Facter::Util::Resolution.exec("sysctl -n hw.ncpu") - end -end - Facter.add("ProcessorCount") do confine :kernel => :Darwin setcode do @@ -155,7 +148,7 @@ end Facter.add("ProcessorCount") do - confine :kernel => [:dragonfly,:freebsd] + confine :kernel => [:dragonfly,:freebsd,:openbsd] setcode do Facter::Util::Resolution.exec("sysctl -n hw.ncpu") end From 2a6faff7a3336526e04a36a5657c1985849cd281 Mon Sep 17 00:00:00 2001 From: Toby Hsieh Date: Tue, 1 Jan 2013 21:35:33 -0800 Subject: [PATCH 1129/3753] (#18308) Use argv in Facter::Application.parse --- lib/facter/application.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/facter/application.rb b/lib/facter/application.rb index 9b6da1d639..e76e58a9e4 100644 --- a/lib/facter/application.rb +++ b/lib/facter/application.rb @@ -67,6 +67,11 @@ def self.run(argv) private + # Parses the given argument array destructively to return an options hash + # (and possibly perform side effects such as changing settings). + # + # @param [Array] argv command line arguments + # @return [Hash] options hash def self.parse(argv) options = {} OptionParser.new do |opts| @@ -95,7 +100,7 @@ def self.parse(argv) exit(1) end end - end.parse! + end.parse!(argv) options rescue OptionParser::InvalidOption => e From cf3e02b965ad5cc5ff2e4c0a807d8a9036a59b65 Mon Sep 17 00:00:00 2001 From: Toby Hsieh Date: Tue, 1 Jan 2013 21:46:13 -0800 Subject: [PATCH 1130/3753] Add tests for Facter::Application.parse --- spec/unit/application_spec.rb | 56 +++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 spec/unit/application_spec.rb diff --git a/spec/unit/application_spec.rb b/spec/unit/application_spec.rb new file mode 100644 index 0000000000..8cb9584f73 --- /dev/null +++ b/spec/unit/application_spec.rb @@ -0,0 +1,56 @@ +require 'spec_helper' +require 'facter/application' + +describe Facter::Application do + describe '.parse' do + it 'returns an empty hash when given no options' do + Facter::Application.parse([]).should == {} + Facter::Application.parse(['architecture', 'kernel']).should == {} + end + + [:yaml, :json, :trace].each do |option_key| + it "sets options[:#{option_key}] when given --#{option_key}" do + options = Facter::Application.parse(["--#{option_key}"]) + options[option_key].should be_true + end + end + + [['-y', :yaml], ['-j', :json]].each do |option, key| + it "sets options[:#{key}] when given #{option}" do + options = Facter::Application.parse([option]) + options[key].should be_true + end + end + + ['-d', '--debug'].each do |option| + it "enables debugging when given #{option}" do + Facter.debugging(false) + Facter::Application.parse([option]) + Facter.should be_debugging + Facter.debugging(false) + end + end + + ['-t', '--timing'].each do |option| + it "enables timing when given #{option}" do + Facter.timing(false) + Facter::Application.parse([option]) + Facter.should be_timing + Facter.timing(false) + end + end + + ['-p', '--puppet'].each do |option| + it "calls load_puppet when given #{option}" do + Facter::Application.expects(:load_puppet) + Facter::Application.parse([option]) + end + end + + it 'mutates argv so that non-option arguments are left' do + argv = ['-y', '--trace', 'uptime', 'virtual'] + Facter::Application.parse(argv) + argv.should == ['uptime', 'virtual'] + end + end +end From 67454ff952f89b3024489dd8a8e3027fdaac4606 Mon Sep 17 00:00:00 2001 From: scepticulous Date: Mon, 28 Jan 2013 21:09:20 +0100 Subject: [PATCH 1131/3753] (#13396) Unify ifconfig usage and lookup ifconfig path to fix support for recent net-tools Without this patch applied linux systems with recent net-tools loose support for ipaddress,macaddress and other network related facts. This is due to a path change from /sbin/ifconfig to /bin/ifconfig. This patch fixes this issue by unifying the lookup and execution of ifconfig and testing where the ifconfig binary is installed. --- lib/facter/ipaddress.rb | 8 ++++---- lib/facter/ipaddress6.rb | 6 +++--- lib/facter/iphostnumber.rb | 2 +- lib/facter/macaddress.rb | 9 +++++---- lib/facter/util/ip.rb | 26 +++++++++++++++--------- lib/facter/util/netmask.rb | 8 ++++---- spec/unit/ipaddress6_spec.rb | 10 ++++++--- spec/unit/ipaddress_spec.rb | 2 +- spec/unit/macaddress_spec.rb | 12 +++++++---- spec/unit/util/ip_spec.rb | 39 ++++++++++++++++++++++++++++++++++++ 10 files changed, 89 insertions(+), 33 deletions(-) diff --git a/lib/facter/ipaddress.rb b/lib/facter/ipaddress.rb index a7d7c4d6be..ad6257c01f 100644 --- a/lib/facter/ipaddress.rb +++ b/lib/facter/ipaddress.rb @@ -28,7 +28,7 @@ confine :kernel => :linux setcode do ip = nil - if output = Facter::Util::IP.get_ifconfig + if output = Facter::Util::IP.exec_ifconfig(["2>/dev/null"]) regexp = /inet (?:addr:)?([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ if match = regexp.match(output) match[1] unless /^127/.match(match[1]) @@ -41,7 +41,7 @@ confine :kernel => %w{FreeBSD OpenBSD Darwin DragonFly} setcode do ip = nil - output = %x{/sbin/ifconfig} + output = Facter::Util::IP.exec_ifconfig output.split(/^\S/).each { |str| if str =~ /inet ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ @@ -61,7 +61,7 @@ confine :kernel => %w{NetBSD SunOS} setcode do ip = nil - output = %x{/sbin/ifconfig -a} + output = Facter::Util::IP.exec_ifconfig(["-a"]) output.split(/^\S/).each { |str| if str =~ /inet ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ @@ -81,7 +81,7 @@ confine :kernel => %w{AIX} setcode do ip = nil - output = %x{/usr/sbin/ifconfig -a} + output = Facter::Util::IP.exec_ifconfig(["-a"]) output.split(/^\S/).each { |str| if str =~ /inet ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ diff --git a/lib/facter/ipaddress6.rb b/lib/facter/ipaddress6.rb index 7d2885fb6e..1d0bd7a93d 100644 --- a/lib/facter/ipaddress6.rb +++ b/lib/facter/ipaddress6.rb @@ -38,7 +38,7 @@ def get_address_after_token(output, token, return_first=false) Facter.add(:ipaddress6) do confine :kernel => :linux setcode do - output = Facter::Util::Resolution.exec('/sbin/ifconfig 2>/dev/null') + output = Facter::Util::IP.exec_ifconfig(["2>/dev/null"]) get_address_after_token(output, 'inet6 addr:') end @@ -47,7 +47,7 @@ def get_address_after_token(output, token, return_first=false) Facter.add(:ipaddress6) do confine :kernel => %w{SunOS} setcode do - output = Facter::Util::Resolution.exec('/usr/sbin/ifconfig -a') + output = Facter::Util::IP.exec_ifconfig(["-a"]) get_address_after_token(output, 'inet6') end @@ -56,7 +56,7 @@ def get_address_after_token(output, token, return_first=false) Facter.add(:ipaddress6) do confine :kernel => %w{Darwin FreeBSD OpenBSD} setcode do - output = Facter::Util::Resolution.exec('/sbin/ifconfig -a') + output = Facter::Util::IP.exec_ifconfig(["-a"]) get_address_after_token(output, 'inet6', true) end diff --git a/lib/facter/iphostnumber.rb b/lib/facter/iphostnumber.rb index 2d22017c28..c0bd1ee8d0 100644 --- a/lib/facter/iphostnumber.rb +++ b/lib/facter/iphostnumber.rb @@ -19,7 +19,7 @@ confine :kernel => :darwin, :kernelrelease => "R6" setcode do ether = nil - output = %x{/sbin/ifconfig} + output = Facter::Util::IP.exec_ifconfig output =~ /HWaddr (\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ ether = $1 diff --git a/lib/facter/macaddress.rb b/lib/facter/macaddress.rb index b7afa432df..b8f31a92c2 100644 --- a/lib/facter/macaddress.rb +++ b/lib/facter/macaddress.rb @@ -26,7 +26,8 @@ confine :kernel => 'Linux' setcode do ether = [] - output = Facter::Util::Resolution.exec("/sbin/ifconfig -a 2>/dev/null") + output = Facter::Util::IP.exec_ifconfig(["-a","2>/dev/null"]) + output.each_line do |s| ether.push($1) if s =~ /(?:ether|HWaddr) ((\w{1,2}:){5,}\w{1,2})/ end @@ -38,7 +39,7 @@ confine :kernel => %w{SunOS GNU/kFreeBSD} setcode do ether = [] - output = Facter::Util::Resolution.exec("/sbin/ifconfig -a") + output = Facter::Util::IP.exec_ifconfig(["-a"]) output.each_line do |s| ether.push($1) if s =~ /(?:ether|HWaddr) ((\w{1,2}:){5,}\w{1,2})/ end @@ -62,7 +63,7 @@ confine :operatingsystem => %w{FreeBSD OpenBSD DragonFly} setcode do ether = [] - output = Facter::Util::Resolution.exec("/sbin/ifconfig") + output = Facter::Util::IP.exec_ifconfig output.each_line do |s| if s =~ /(?:ether|lladdr)\s+(\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ ether.push($1) @@ -82,7 +83,7 @@ setcode do ether = [] ip = nil - output = %x{/usr/sbin/ifconfig -a} + output = Facter::Util::IP.exec_ifconfig(["-a"]) output.each_line do |str| if str =~ /([a-z]+\d+): flags=/ devname = $1 diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index 89cd6192d7..a62808aaf4 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -77,9 +77,9 @@ def self.get_interfaces def self.get_all_interface_output case Facter.value(:kernel) when 'Linux', 'OpenBSD', 'NetBSD', 'FreeBSD', 'Darwin', 'GNU/kFreeBSD', 'DragonFly' - output = %x{/sbin/ifconfig -a 2>/dev/null} + output = Facter::Util::IP.exec_ifconfig(["-a","2>/dev/null"]) when 'SunOS' - output = %x{/usr/sbin/ifconfig -a} + output = Facter::Util::IP.exec_ifconfig(["-a"]) when 'HP-UX' # (#17487)[https://projects.puppetlabs.com/issues/17487] # Handle NIC bonding where asterisks and virtual NICs are printed. @@ -96,14 +96,22 @@ def self.get_all_interface_output output end + ## - # get_ifconfig simply delegates to the ifconfig command. + # exec_ifconfig uses the ifconfig command # - # @return [String] the output of `/sbin/ifconfig 2>/dev/null` or nil + # @return [String] the output of `ifconfig #{arguments} 2>/dev/null` or nil + def self.exec_ifconfig(additional_arguments=[]) + Facter::Util::Resolution.exec("#{self.get_ifconfig} #{additional_arguments.join(' ')}") + end + ## + # get_ifconfig looks up the ifconfig binary + # + # @return [String] path to the ifconfig binary def self.get_ifconfig - Facter::Util::Resolution.exec("/sbin/ifconfig 2>/dev/null") + common_paths=["/bin/ifconfig","/sbin/ifconfig","/usr/sbin/ifconfig"] + common_paths.select{|path| File.executable?(path)}.first end - ## # hpux_netstat_in is a delegate method that allows us to stub netstat -in # without stubbing exec. @@ -125,7 +133,7 @@ def self.get_infiniband_macaddress(interface) end def self.ifconfig_interface(interface) - %x{/sbin/ifconfig #{interface} 2>/dev/null} + output = Facter::Util::IP.exec_ifconfig([interface,"2>/dev/null"]) end def self.get_single_interface_output(interface) @@ -142,7 +150,7 @@ def self.get_single_interface_output(interface) output = ifconfig_output end when 'SunOS' - output = %x{/usr/sbin/ifconfig #{interface}} + output = Facter::Util::IP.exec_ifconfig([interface]) when 'HP-UX' mac = "" ifc = hpux_ifconfig_interface(interface) @@ -154,7 +162,7 @@ def self.get_single_interface_output(interface) end def self.hpux_ifconfig_interface(interface) - Facter::Util::Resolution.exec("/usr/sbin/ifconfig #{interface}") + Facter::Util::IP.exec_ifconfig([interface]) end def self.hpux_lanscan diff --git a/lib/facter/util/netmask.rb b/lib/facter/util/netmask.rb index 36a212e09f..a7d212949b 100644 --- a/lib/facter/util/netmask.rb +++ b/lib/facter/util/netmask.rb @@ -7,25 +7,25 @@ def self.get_netmask case Facter.value(:kernel) when 'Linux' ops = { - :ifconfig => '/sbin/ifconfig 2>/dev/null', + :ifconfig_opts => ['2>/dev/null'], :regex => %r{\s+ inet\saddr: #{Facter.ipaddress} .*? Mask: (#{ipregex})}x, :munge => nil, } when 'SunOS' ops = { - :ifconfig => '/usr/sbin/ifconfig -a', + :ifconfig_opts => ['-a'], :regex => %r{\s+ inet \s #{Facter.ipaddress} \s netmask \s (\w{8})}x, :munge => Proc.new { |mask| mask.scan(/../).collect do |byte| byte.to_i(16) end.join('.') } } when 'FreeBSD','NetBSD','OpenBSD', 'Darwin', 'GNU/kFreeBSD', 'DragonFly' ops = { - :ifconfig => '/sbin/ifconfig -a', + :ifconfig_opts => ['-a'], :regex => %r{\s+ inet \s #{Facter.ipaddress} \s netmask \s 0x(\w{8})}x, :munge => Proc.new { |mask| mask.scan(/../).collect do |byte| byte.to_i(16) end.join('.') } } end - %x{#{ops[:ifconfig]}}.split(/\n/).collect do |line| + Facter::Util::IP.get_ifconfig(ops[:ifconfig_opts]).split(/\n/).collect do |line| matches = line.match(ops[:regex]) if !matches.nil? if ops[:munge].nil? diff --git a/spec/unit/ipaddress6_spec.rb b/spec/unit/ipaddress6_spec.rb index 6891902109..1d01259afb 100755 --- a/spec/unit/ipaddress6_spec.rb +++ b/spec/unit/ipaddress6_spec.rb @@ -1,6 +1,7 @@ #! /usr/bin/env ruby require 'spec_helper' +require 'facter/util/ip' def ifconfig_fixture(filename) File.read(fixtures('ifconfig', filename)) @@ -20,7 +21,8 @@ def netsh_fixture(filename) it "should return ipaddress6 information for Darwin" do Facter::Util::Resolution.stubs(:exec).with('uname -s').returns('Darwin') - Facter::Util::Resolution.stubs(:exec).with('/sbin/ifconfig -a'). + Facter::Util::IP.stubs(:get_ifconfig).returns("/sbin/ifconfig") + Facter::Util::IP.stubs(:exec_ifconfig).with(["-a"]). returns(ifconfig_fixture('darwin_ifconfig_all_with_multiple_interfaces')) Facter.value(:ipaddress6).should == "2610:10:20:209:223:32ff:fed5:ee34" @@ -28,7 +30,8 @@ def netsh_fixture(filename) it "should return ipaddress6 information for Linux" do Facter::Util::Resolution.stubs(:exec).with('uname -s').returns('Linux') - Facter::Util::Resolution.stubs(:exec).with('/sbin/ifconfig 2>/dev/null'). + Facter::Util::IP.stubs(:get_ifconfig).returns("/sbin/ifconfig") + Facter::Util::IP.stubs(:exec_ifconfig).with(["2>/dev/null"]). returns(ifconfig_fixture('linux_ifconfig_all_with_multiple_interfaces')) Facter.value(:ipaddress6).should == "2610:10:20:209:212:3fff:febe:2201" @@ -36,7 +39,8 @@ def netsh_fixture(filename) it "should return ipaddress6 information for Solaris" do Facter::Util::Resolution.stubs(:exec).with('uname -s').returns('SunOS') - Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/ifconfig -a'). + Facter::Util::IP.stubs(:get_ifconfig).returns("/usr/sbin/ifconfig") + Facter::Util::IP.stubs(:exec_ifconfig).with(["-a"]). returns(ifconfig_fixture('sunos_ifconfig_all_with_multiple_interfaces')) Facter.value(:ipaddress6).should == "2610:10:20:209:203:baff:fe27:a7c" diff --git a/spec/unit/ipaddress_spec.rb b/spec/unit/ipaddress_spec.rb index 3e874f7ddd..714ec047e0 100755 --- a/spec/unit/ipaddress_spec.rb +++ b/spec/unit/ipaddress_spec.rb @@ -5,7 +5,7 @@ shared_examples_for "ifconfig output" do |platform, address, fixture| it "correctly on #{platform}" do - Facter::Util::IP.stubs(:get_ifconfig).returns(my_fixture_read(fixture)) + Facter::Util::IP.stubs(:exec_ifconfig).returns(my_fixture_read(fixture)) subject.value.should == address end end diff --git a/spec/unit/macaddress_spec.rb b/spec/unit/macaddress_spec.rb index 05573e9a4c..638ef2ad7c 100755 --- a/spec/unit/macaddress_spec.rb +++ b/spec/unit/macaddress_spec.rb @@ -1,6 +1,8 @@ #! /usr/bin/env ruby require 'spec_helper' +require 'facter/util/ip' + def ifconfig_fixture(filename) File.read(fixtures('ifconfig', filename)) @@ -21,17 +23,18 @@ def netsh_fixture(filename) before :each do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter.fact(:operatingsystem).stubs(:value).returns("Linux") + Facter::Util::IP.stubs(:get_ifconfig).returns("/sbin/ifconfig") end it "should return the macaddress of the first interface" do - Facter::Util::Resolution.stubs(:exec).with('/sbin/ifconfig -a 2>/dev/null'). + Facter::Util::IP.stubs(:exec_ifconfig).with(["-a","2>/dev/null"]). returns(ifconfig_fixture('linux_ifconfig_all_with_multiple_interfaces')) Facter.value(:macaddress).should == "00:12:3f:be:22:01" end it "should return nil when no macaddress can be found" do - Facter::Util::Resolution.stubs(:exec).with('/sbin/ifconfig -a 2>/dev/null'). + Facter::Util::IP.stubs(:exec_ifconfig).with(["-a","2>/dev/null"]). returns(ifconfig_fixture('linux_ifconfig_no_mac')) proc { Facter.value(:macaddress) }.should_not raise_error @@ -40,7 +43,7 @@ def netsh_fixture(filename) # some interfaces dont have a real mac addresses (like venet inside a container) it "should return nil when no interface has a real macaddress" do - Facter::Util::Resolution.stubs(:exec).with('/sbin/ifconfig -a 2>/dev/null'). + Facter::Util::IP.stubs(:exec_ifconfig).with(["-a","2>/dev/null"]). returns(ifconfig_fixture('linux_ifconfig_venet')) proc { Facter.value(:macaddress) }.should_not raise_error @@ -51,7 +54,8 @@ def netsh_fixture(filename) describe "when run on BSD" do it "should return macaddress information" do Facter.fact(:kernel).stubs(:value).returns("FreeBSD") - Facter::Util::Resolution.stubs(:exec).with('/sbin/ifconfig'). + Facter::Util::IP.stubs(:get_ifconfig).returns("/sbin/ifconfig") + Facter::Util::IP.stubs(:exec_ifconfig). returns(ifconfig_fixture('bsd_ifconfig_all_with_multiple_interfaces')) Facter.value(:macaddress).should == "00:0b:db:93:09:67" diff --git a/spec/unit/util/ip_spec.rb b/spec/unit/util/ip_spec.rb index 53ef019dc6..3d45c82c8f 100755 --- a/spec/unit/util/ip_spec.rb +++ b/spec/unit/util/ip_spec.rb @@ -411,6 +411,45 @@ def self.hpux_examples end end + describe "exec_ifconfig" do + it "uses get_ifconfig" do + Facter::Util::IP.stubs(:get_ifconfig).returns("/sbin/ifconfig").once + + Facter::Util::IP.exec_ifconfig + end + it "support additional arguments" do + Facter::Util::IP.stubs(:get_ifconfig).returns("/sbin/ifconfig") + + Facter::Util::Resolution.stubs(:exec).with("/sbin/ifconfig -a") + + Facter::Util::IP.exec_ifconfig(["-a"]) + end + it "joins multiple arguments correctly" do + Facter::Util::IP.stubs(:get_ifconfig).returns("/sbin/ifconfig") + + Facter::Util::Resolution.stubs(:exec).with("/sbin/ifconfig -a -e -i -j") + + Facter::Util::IP.exec_ifconfig(["-a","-e","-i","-j"]) + end + end + describe "get_ifconfig" do + it "assigns /sbin/ifconfig if it is executable" do + File.stubs(:executable?).returns(false) + File.stubs(:executable?).with("/sbin/ifconfig").returns(true) + Facter::Util::IP.get_ifconfig.should eq("/sbin/ifconfig") + end + it "assigns /usr/sbin/ifconfig if it is executable" do + File.stubs(:executable?).returns(false) + File.stubs(:executable?).with("/usr/sbin/ifconfig").returns(true) + Facter::Util::IP.get_ifconfig.should eq("/usr/sbin/ifconfig") + end + it "assigns /bin/ifconfig if it is executable" do + File.stubs(:executable?).returns(false) + File.stubs(:executable?).with("/bin/ifconfig").returns(true) + Facter::Util::IP.get_ifconfig.should eq("/bin/ifconfig") + end + end + context "with bonded ethernet interfaces on Linux" do before :each do Facter.fact(:kernel).stubs(:value).returns("Linux") From ed26fa72635549a61bd3780e1a7439f428e8b4a8 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Thu, 24 Jan 2013 21:43:19 -0800 Subject: [PATCH 1132/3753] Remove .rspec from source The only thing this file did was enable color. This is a personal preference, as is --fail-fast while using autotest. It's a problem that personal preferences can't be expressed without making the source dirty. This patch fixes the problem my removing and ignoring the rspec file. This patch also fixes a file not found exception in the watchr script when the .rspec file does not exist. --- .gitignore | 1 + .rspec | 1 - spec/watchr.rb | 4 +++- 3 files changed, 4 insertions(+), 2 deletions(-) delete mode 100644 .rspec diff --git a/.gitignore b/.gitignore index 6b4c36bb9d..9eef99a0e2 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ doc .bundle/ vendor/ Gemfile.lock +.rspec diff --git a/.rspec b/.rspec deleted file mode 100644 index 53607ea52b..0000000000 --- a/.rspec +++ /dev/null @@ -1 +0,0 @@ ---colour diff --git a/spec/watchr.rb b/spec/watchr.rb index 31e39bd909..5be5b040b0 100755 --- a/spec/watchr.rb +++ b/spec/watchr.rb @@ -63,7 +63,9 @@ def run_spec(command) def run_spec_files(files) files = Array(files) return if files.empty? - opts = File.readlines('.rspec').collect { |l| l.chomp }.join(" ") + opts = if File.exist? '.rspec' + File.readlines('.rspec').collect { |l| l.chomp }.join(" ") + end begin run_spec("rspec --tty #{opts} #{files.join(' ')}") rescue => detail From b03ee5c5339289c973cf04d44da099e18c8797f2 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Mon, 28 Jan 2013 22:58:30 -0800 Subject: [PATCH 1133/3753] (maint) Add pry development gem dependency This is just a development debugging tool. --- Gemfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Gemfile b/Gemfile index 0abec7398e..f0782ae365 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,7 @@ source :rubygems group :development do + gem 'pry' gem 'watchr' end From 3439e567078d1a82664af55b049ec490fa46504e Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Tue, 30 Oct 2012 18:40:45 -0700 Subject: [PATCH 1134/3753] Add rake collect:ec2_metadata task Facter does not provide the means to easily collect example fixture data from a running EC2 instance. This is a problem because it is difficult to develop robust facts using the EC2 metadata server without example fixtures to write specification tests against. This also makes TDD/BDD difficult when running on a Mac laptop outside of the EC2 environment. This patch is enables TDD/BDD of EC2 facts by providing example responses from the metadata server API as YAML fixture data. --- Rakefile | 58 ++++++++++++++++++- .../ec2/ec2_meta_data_ami_id_request.yaml | 12 ++++ .../ec2/ec2_meta_data_ami_id_response.yaml | 25 ++++++++ ...c2_meta_data_ami_launch_index_request.yaml | 12 ++++ ...2_meta_data_ami_launch_index_response.yaml | 25 ++++++++ ...2_meta_data_ami_manifest_path_request.yaml | 12 ++++ ..._meta_data_ami_manifest_path_response.yaml | 25 ++++++++ ...data_block_device_mapping_ami_request.yaml | 12 ++++ ...ata_block_device_mapping_ami_response.yaml | 25 ++++++++ ...eta_data_block_device_mapping_request.yaml | 12 ++++ ...ta_data_block_device_mapping_response.yaml | 27 +++++++++ ...ata_block_device_mapping_root_request.yaml | 12 ++++ ...ta_block_device_mapping_root_response.yaml | 25 ++++++++ .../ec2/ec2_meta_data_hostname_request.yaml | 12 ++++ .../ec2/ec2_meta_data_hostname_response.yaml | 25 ++++++++ ...ec2_meta_data_instance_action_request.yaml | 12 ++++ ...c2_meta_data_instance_action_response.yaml | 25 ++++++++ .../ec2_meta_data_instance_id_request.yaml | 12 ++++ .../ec2_meta_data_instance_id_response.yaml | 25 ++++++++ .../ec2_meta_data_instance_type_request.yaml | 12 ++++ .../ec2_meta_data_instance_type_response.yaml | 25 ++++++++ .../ec2/ec2_meta_data_kernel_id_request.yaml | 12 ++++ .../ec2/ec2_meta_data_kernel_id_response.yaml | 25 ++++++++ .../ec2_meta_data_local_hostname_request.yaml | 12 ++++ ...ec2_meta_data_local_hostname_response.yaml | 25 ++++++++ .../ec2/ec2_meta_data_local_ipv4_request.yaml | 12 ++++ .../ec2_meta_data_local_ipv4_response.yaml | 25 ++++++++ .../util/ec2/ec2_meta_data_mac_request.yaml | 12 ++++ .../util/ec2/ec2_meta_data_mac_response.yaml | 25 ++++++++ .../ec2/ec2_meta_data_metrics_request.yaml | 12 ++++ .../ec2/ec2_meta_data_metrics_response.yaml | 25 ++++++++ ...ec2_meta_data_metrics_vhostmd_request.yaml | 12 ++++ ...c2_meta_data_metrics_vhostmd_response.yaml | 25 ++++++++ ..._31_3b_12_60_e7_device_number_request.yaml | 12 ++++ ...31_3b_12_60_e7_device_number_response.yaml | 25 ++++++++ ...31_3b_12_60_e7_local_hostname_request.yaml | 12 ++++ ...1_3b_12_60_e7_local_hostname_response.yaml | 25 ++++++++ ...12_31_3b_12_60_e7_local_ipv4s_request.yaml | 12 ++++ ...2_31_3b_12_60_e7_local_ipv4s_response.yaml | 25 ++++++++ ...es_macs_12_31_3b_12_60_e7_mac_request.yaml | 12 ++++ ...s_macs_12_31_3b_12_60_e7_mac_response.yaml | 25 ++++++++ ...cs_12_31_3b_12_60_e7_owner_id_request.yaml | 12 ++++ ...s_12_31_3b_12_60_e7_owner_id_response.yaml | 25 ++++++++ ...1_3b_12_60_e7_public_hostname_request.yaml | 12 ++++ ..._3b_12_60_e7_public_hostname_response.yaml | 25 ++++++++ ...2_31_3b_12_60_e7_public_ipv4s_request.yaml | 12 ++++ ..._31_3b_12_60_e7_public_ipv4s_response.yaml | 25 ++++++++ ...rfaces_macs_12_31_3b_12_60_e7_request.yaml | 12 ++++ ...faces_macs_12_31_3b_12_60_e7_response.yaml | 32 ++++++++++ ..._data_network_interfaces_macs_request.yaml | 12 ++++ ...data_network_interfaces_macs_response.yaml | 25 ++++++++ ..._meta_data_network_interfaces_request.yaml | 12 ++++ ...meta_data_network_interfaces_response.yaml | 25 ++++++++ .../ec2/ec2_meta_data_network_request.yaml | 12 ++++ .../ec2/ec2_meta_data_network_response.yaml | 25 ++++++++ ...a_placement_availability_zone_request.yaml | 12 ++++ ..._placement_availability_zone_response.yaml | 25 ++++++++ .../ec2/ec2_meta_data_placement_request.yaml | 12 ++++ .../ec2/ec2_meta_data_placement_response.yaml | 25 ++++++++ .../ec2/ec2_meta_data_profile_request.yaml | 12 ++++ .../ec2/ec2_meta_data_profile_response.yaml | 25 ++++++++ ...ec2_meta_data_public_hostname_request.yaml | 12 ++++ ...c2_meta_data_public_hostname_response.yaml | 25 ++++++++ .../ec2_meta_data_public_ipv4_request.yaml | 12 ++++ .../ec2_meta_data_public_ipv4_response.yaml | 25 ++++++++ ..._meta_data_public_keys_0_jeff_request.yaml | 12 ++++ ...meta_data_public_keys_0_jeff_response.yaml | 31 ++++++++++ .../ec2_meta_data_public_keys_request.yaml | 12 ++++ .../ec2_meta_data_public_keys_response.yaml | 25 ++++++++ .../unit/util/ec2/ec2_meta_data_request.yaml | 12 ++++ .../ec2_meta_data_reservation_id_request.yaml | 12 ++++ ...ec2_meta_data_reservation_id_response.yaml | 25 ++++++++ .../unit/util/ec2/ec2_meta_data_response.yaml | 46 +++++++++++++++ ...ec2_meta_data_security_groups_request.yaml | 12 ++++ ...c2_meta_data_security_groups_response.yaml | 25 ++++++++ 75 files changed, 1462 insertions(+), 1 deletion(-) create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_ami_id_request.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_ami_id_response.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_ami_launch_index_request.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_ami_launch_index_response.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_ami_manifest_path_request.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_ami_manifest_path_response.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_block_device_mapping_ami_request.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_block_device_mapping_ami_response.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_block_device_mapping_request.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_block_device_mapping_response.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_block_device_mapping_root_request.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_block_device_mapping_root_response.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_hostname_request.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_hostname_response.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_instance_action_request.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_instance_action_response.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_instance_id_request.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_instance_id_response.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_instance_type_request.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_instance_type_response.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_kernel_id_request.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_kernel_id_response.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_local_hostname_request.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_local_hostname_response.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_local_ipv4_request.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_local_ipv4_response.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_mac_request.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_mac_response.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_metrics_request.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_metrics_response.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_metrics_vhostmd_request.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_metrics_vhostmd_response.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_device_number_request.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_device_number_response.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_local_hostname_request.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_local_hostname_response.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_local_ipv4s_request.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_local_ipv4s_response.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_mac_request.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_mac_response.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_owner_id_request.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_owner_id_response.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_public_hostname_request.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_public_hostname_response.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_public_ipv4s_request.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_public_ipv4s_response.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_request.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_response.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_request.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_response.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_request.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_response.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_network_request.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_network_response.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_placement_availability_zone_request.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_placement_availability_zone_response.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_placement_request.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_placement_response.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_profile_request.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_profile_response.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_public_hostname_request.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_public_hostname_response.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_public_ipv4_request.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_public_ipv4_response.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_public_keys_0_jeff_request.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_public_keys_0_jeff_response.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_public_keys_request.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_public_keys_response.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_request.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_reservation_id_request.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_reservation_id_response.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_response.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_security_groups_request.yaml create mode 100644 spec/fixtures/unit/util/ec2/ec2_meta_data_security_groups_response.yaml diff --git a/Rakefile b/Rakefile index aad745b5f2..3dd023252a 100644 --- a/Rakefile +++ b/Rakefile @@ -3,6 +3,7 @@ # We need access to the Puppet.version method $LOAD_PATH.unshift(File.expand_path("lib")) require 'facter/version' +require 'yaml' $LOAD_PATH << File.join(File.dirname(__FILE__), 'tasks') @@ -22,7 +23,6 @@ Dir['ext/packaging/tasks/**/*'].sort.each { |t| load t } build_defs_file = 'ext/build_defaults.yaml' if File.exist?(build_defs_file) begin - require 'yaml' @build_defaults ||= YAML.load_file(build_defs_file) rescue Exception => e STDERR.puts "Unable to load yaml from #{build_defs_file}:" @@ -71,3 +71,59 @@ if defined?(RSpec::Core::RakeTask) end end end + +namespace :collect do + desc "Scrape EC2 Metadata into fixtures" + task :ec2_metadata do + collect_metadata + end + + ## + # collect_metadata walks the Amazon AWS EC2 Metadata API and records each + # request and response instance as a serialized YAML string. This method is + # intended to be used by Rake tasks Puppet users invoke to collect data for + # development and troubleshooting purposes. + def collect_metadata(key='/', date=Time.now.strftime("%F"), dir="spec/fixtures/unit/util/ec2") + require 'timeout' + require 'net/http' + require 'uri' + + # Local variables + file_prefix = "ec2_meta_data#{key.gsub(/[^a-zA-Z0-9]+/, '_')}".gsub(/_+$/, '') + response = nil + + Dir.chdir(dir) do + uri = URI("http://169.254.169.254/latest/meta-data#{key}") + Timeout::timeout(4) do + Net::HTTP.start(uri.host, uri.port) do |http| + request = Net::HTTP::Get.new(uri.request_uri) + response = http.request(request) + + write_fixture(request, "#{file_prefix}_request.yaml") + write_fixture(response, "#{file_prefix}_response.yaml") + end + end + end + + ## + # if the current key is a directory, decend into all of the files. If the + # current key is not, we've already written it out and we're done. + if key.end_with? "/" + response.read_body.lines.each do |line| + collect_metadata("#{key}#{line.chomp}", date, dir) + end + end + end + + ## + # write_fixture dumps an internal Ruby object to a file intended to be used + # as a fixture for spec testing. + # + # @return [String] Serialized string model representation of obj + def write_fixture(obj, filename, quiet=false) + File.open(filename, "w+") do |fd| + fd.write(YAML.dump(request)) + end + puts "Wrote: #{dir}/#{request_file}" unless quiet + end +end diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_ami_id_request.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_ami_id_request.yaml new file mode 100644 index 0000000000..6476b6fca4 --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_ami_id_request.yaml @@ -0,0 +1,12 @@ +--- !ruby/object:Net::HTTP::Get +body: +body_stream: +header: + accept: + - "*/*" + host: + - 169.254.169.254 +method: GET +path: /latest/meta-data/ami-id +request_has_body: false +response_has_body: true diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_ami_id_response.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_ami_id_response.yaml new file mode 100644 index 0000000000..071f80fb90 --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_ami_id_response.yaml @@ -0,0 +1,25 @@ +--- !ruby/object:Net::HTTPOK +body: ami-1624987f +body_exist: true +code: "200" +header: + connection: + - close + server: + - EC2ws + date: + - Wed, 31 Oct 2012 02:34:30 GMT + etag: + - "\"3765980309\"" + content-length: + - "12" + last-modified: + - Tue, 30 Oct 2012 23:48:24 GMT + accept-ranges: + - bytes + content-type: + - text/plain +http_version: "1.0" +message: OK +read: true +socket: diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_ami_launch_index_request.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_ami_launch_index_request.yaml new file mode 100644 index 0000000000..56f906ea79 --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_ami_launch_index_request.yaml @@ -0,0 +1,12 @@ +--- !ruby/object:Net::HTTP::Get +body: +body_stream: +header: + accept: + - "*/*" + host: + - 169.254.169.254 +method: GET +path: /latest/meta-data/ami-launch-index +request_has_body: false +response_has_body: true diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_ami_launch_index_response.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_ami_launch_index_response.yaml new file mode 100644 index 0000000000..0e65ee0b4e --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_ami_launch_index_response.yaml @@ -0,0 +1,25 @@ +--- !ruby/object:Net::HTTPOK +body: "0" +body_exist: true +code: "200" +header: + connection: + - close + server: + - EC2ws + date: + - Wed, 31 Oct 2012 02:34:30 GMT + etag: + - "\"22963574\"" + content-length: + - "1" + last-modified: + - Tue, 30 Oct 2012 23:48:24 GMT + accept-ranges: + - bytes + content-type: + - text/plain +http_version: "1.0" +message: OK +read: true +socket: diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_ami_manifest_path_request.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_ami_manifest_path_request.yaml new file mode 100644 index 0000000000..f0d3557fd6 --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_ami_manifest_path_request.yaml @@ -0,0 +1,12 @@ +--- !ruby/object:Net::HTTP::Get +body: +body_stream: +header: + accept: + - "*/*" + host: + - 169.254.169.254 +method: GET +path: /latest/meta-data/ami-manifest-path +request_has_body: false +response_has_body: true diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_ami_manifest_path_response.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_ami_manifest_path_response.yaml new file mode 100644 index 0000000000..b54afa14d5 --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_ami_manifest_path_response.yaml @@ -0,0 +1,25 @@ +--- !ruby/object:Net::HTTPOK +body: (unknown) +body_exist: true +code: "200" +header: + connection: + - close + server: + - EC2ws + date: + - Wed, 31 Oct 2012 02:34:30 GMT + etag: + - "\"90072436\"" + content-length: + - "9" + last-modified: + - Tue, 30 Oct 2012 23:48:24 GMT + accept-ranges: + - bytes + content-type: + - text/plain +http_version: "1.0" +message: OK +read: true +socket: diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_block_device_mapping_ami_request.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_block_device_mapping_ami_request.yaml new file mode 100644 index 0000000000..93588e61a5 --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_block_device_mapping_ami_request.yaml @@ -0,0 +1,12 @@ +--- !ruby/object:Net::HTTP::Get +body: +body_stream: +header: + accept: + - "*/*" + host: + - 169.254.169.254 +method: GET +path: /latest/meta-data/block-device-mapping/ami +request_has_body: false +response_has_body: true diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_block_device_mapping_ami_response.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_block_device_mapping_ami_response.yaml new file mode 100644 index 0000000000..1b7d502f65 --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_block_device_mapping_ami_response.yaml @@ -0,0 +1,25 @@ +--- !ruby/object:Net::HTTPOK +body: /dev/sda1 +body_exist: true +code: "200" +header: + connection: + - close + server: + - EC2ws + date: + - Wed, 31 Oct 2012 02:34:30 GMT + etag: + - "\"90072816\"" + content-length: + - "9" + last-modified: + - Tue, 30 Oct 2012 23:48:24 GMT + accept-ranges: + - bytes + content-type: + - text/plain +http_version: "1.0" +message: OK +read: true +socket: diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_block_device_mapping_request.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_block_device_mapping_request.yaml new file mode 100644 index 0000000000..543bcfe2e2 --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_block_device_mapping_request.yaml @@ -0,0 +1,12 @@ +--- !ruby/object:Net::HTTP::Get +body: +body_stream: +header: + accept: + - "*/*" + host: + - 169.254.169.254 +method: GET +path: /latest/meta-data/block-device-mapping/ +request_has_body: false +response_has_body: true diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_block_device_mapping_response.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_block_device_mapping_response.yaml new file mode 100644 index 0000000000..0be12f68b3 --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_block_device_mapping_response.yaml @@ -0,0 +1,27 @@ +--- !ruby/object:Net::HTTPOK +body: |- + ami + root +body_exist: true +code: "200" +header: + connection: + - close + server: + - EC2ws + date: + - Wed, 31 Oct 2012 02:34:30 GMT + etag: + - "\"98461428\"" + content-length: + - "8" + last-modified: + - Tue, 30 Oct 2012 23:48:24 GMT + accept-ranges: + - bytes + content-type: + - text/plain +http_version: "1.0" +message: OK +read: true +socket: diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_block_device_mapping_root_request.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_block_device_mapping_root_request.yaml new file mode 100644 index 0000000000..190c1497a1 --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_block_device_mapping_root_request.yaml @@ -0,0 +1,12 @@ +--- !ruby/object:Net::HTTP::Get +body: +body_stream: +header: + accept: + - "*/*" + host: + - 169.254.169.254 +method: GET +path: /latest/meta-data/block-device-mapping/root +request_has_body: false +response_has_body: true diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_block_device_mapping_root_response.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_block_device_mapping_root_response.yaml new file mode 100644 index 0000000000..48829e082e --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_block_device_mapping_root_response.yaml @@ -0,0 +1,25 @@ +--- !ruby/object:Net::HTTPOK +body: /dev/sda1 +body_exist: true +code: "200" +header: + connection: + - close + server: + - EC2ws + date: + - Wed, 31 Oct 2012 02:34:30 GMT + etag: + - "\"90072822\"" + content-length: + - "9" + last-modified: + - Tue, 30 Oct 2012 23:48:24 GMT + accept-ranges: + - bytes + content-type: + - text/plain +http_version: "1.0" +message: OK +read: true +socket: diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_hostname_request.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_hostname_request.yaml new file mode 100644 index 0000000000..15fc14a3b2 --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_hostname_request.yaml @@ -0,0 +1,12 @@ +--- !ruby/object:Net::HTTP::Get +body: +body_stream: +header: + accept: + - "*/*" + host: + - 169.254.169.254 +method: GET +path: /latest/meta-data/hostname +request_has_body: false +response_has_body: true diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_hostname_response.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_hostname_response.yaml new file mode 100644 index 0000000000..bb831ac19a --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_hostname_response.yaml @@ -0,0 +1,25 @@ +--- !ruby/object:Net::HTTPOK +body: ip-10-202-99-21.ec2.internal +body_exist: true +code: "200" +header: + connection: + - close + server: + - EC2ws + date: + - Wed, 31 Oct 2012 02:34:30 GMT + etag: + - "\"3581431253\"" + content-length: + - "28" + last-modified: + - Tue, 30 Oct 2012 23:48:24 GMT + accept-ranges: + - bytes + content-type: + - text/plain +http_version: "1.0" +message: OK +read: true +socket: diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_instance_action_request.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_instance_action_request.yaml new file mode 100644 index 0000000000..92f6a3003c --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_instance_action_request.yaml @@ -0,0 +1,12 @@ +--- !ruby/object:Net::HTTP::Get +body: +body_stream: +header: + accept: + - "*/*" + host: + - 169.254.169.254 +method: GET +path: /latest/meta-data/instance-action +request_has_body: false +response_has_body: true diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_instance_action_response.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_instance_action_response.yaml new file mode 100644 index 0000000000..dfbbbeffa7 --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_instance_action_response.yaml @@ -0,0 +1,25 @@ +--- !ruby/object:Net::HTTPOK +body: none +body_exist: true +code: "200" +header: + connection: + - close + server: + - EC2ws + date: + - Wed, 31 Oct 2012 02:34:30 GMT + etag: + - "\"64906556\"" + content-length: + - "4" + last-modified: + - Tue, 30 Oct 2012 23:48:24 GMT + accept-ranges: + - bytes + content-type: + - text/plain +http_version: "1.0" +message: OK +read: true +socket: diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_instance_id_request.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_instance_id_request.yaml new file mode 100644 index 0000000000..7bbdc8577e --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_instance_id_request.yaml @@ -0,0 +1,12 @@ +--- !ruby/object:Net::HTTP::Get +body: +body_stream: +header: + accept: + - "*/*" + host: + - 169.254.169.254 +method: GET +path: /latest/meta-data/instance-id +request_has_body: false +response_has_body: true diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_instance_id_response.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_instance_id_response.yaml new file mode 100644 index 0000000000..f52bdb111d --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_instance_id_response.yaml @@ -0,0 +1,25 @@ +--- !ruby/object:Net::HTTPOK +body: i-c9bdd6b5 +body_exist: true +code: "200" +header: + connection: + - close + server: + - EC2ws + date: + - Wed, 31 Oct 2012 02:34:30 GMT + etag: + - "\"3782757781\"" + content-length: + - "10" + last-modified: + - Tue, 30 Oct 2012 23:48:24 GMT + accept-ranges: + - bytes + content-type: + - text/plain +http_version: "1.0" +message: OK +read: true +socket: diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_instance_type_request.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_instance_type_request.yaml new file mode 100644 index 0000000000..a7af141827 --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_instance_type_request.yaml @@ -0,0 +1,12 @@ +--- !ruby/object:Net::HTTP::Get +body: +body_stream: +header: + accept: + - "*/*" + host: + - 169.254.169.254 +method: GET +path: /latest/meta-data/instance-type +request_has_body: false +response_has_body: true diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_instance_type_response.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_instance_type_response.yaml new file mode 100644 index 0000000000..a85308c64e --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_instance_type_response.yaml @@ -0,0 +1,25 @@ +--- !ruby/object:Net::HTTPOK +body: t1.micro +body_exist: true +code: "200" +header: + connection: + - close + server: + - EC2ws + date: + - Wed, 31 Oct 2012 02:34:30 GMT + etag: + - "\"98458866\"" + content-length: + - "8" + last-modified: + - Tue, 30 Oct 2012 23:48:24 GMT + accept-ranges: + - bytes + content-type: + - text/plain +http_version: "1.0" +message: OK +read: true +socket: diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_kernel_id_request.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_kernel_id_request.yaml new file mode 100644 index 0000000000..28cea89e14 --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_kernel_id_request.yaml @@ -0,0 +1,12 @@ +--- !ruby/object:Net::HTTP::Get +body: +body_stream: +header: + accept: + - "*/*" + host: + - 169.254.169.254 +method: GET +path: /latest/meta-data/kernel-id +request_has_body: false +response_has_body: true diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_kernel_id_response.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_kernel_id_response.yaml new file mode 100644 index 0000000000..2127557e31 --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_kernel_id_response.yaml @@ -0,0 +1,25 @@ +--- !ruby/object:Net::HTTPOK +body: aki-88aa75e1 +body_exist: true +code: "200" +header: + connection: + - close + server: + - EC2ws + date: + - Wed, 31 Oct 2012 02:34:30 GMT + etag: + - "\"3765978773\"" + content-length: + - "12" + last-modified: + - Tue, 30 Oct 2012 23:48:24 GMT + accept-ranges: + - bytes + content-type: + - text/plain +http_version: "1.0" +message: OK +read: true +socket: diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_local_hostname_request.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_local_hostname_request.yaml new file mode 100644 index 0000000000..1d1496fbea --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_local_hostname_request.yaml @@ -0,0 +1,12 @@ +--- !ruby/object:Net::HTTP::Get +body: +body_stream: +header: + accept: + - "*/*" + host: + - 169.254.169.254 +method: GET +path: /latest/meta-data/local-hostname +request_has_body: false +response_has_body: true diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_local_hostname_response.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_local_hostname_response.yaml new file mode 100644 index 0000000000..25ccefa4a7 --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_local_hostname_response.yaml @@ -0,0 +1,25 @@ +--- !ruby/object:Net::HTTPOK +body: ip-10-202-99-21.ec2.internal +body_exist: true +code: "200" +header: + connection: + - close + server: + - EC2ws + date: + - Wed, 31 Oct 2012 02:34:30 GMT + etag: + - "\"3581429205\"" + content-length: + - "28" + last-modified: + - Tue, 30 Oct 2012 23:48:24 GMT + accept-ranges: + - bytes + content-type: + - text/plain +http_version: "1.0" +message: OK +read: true +socket: diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_local_ipv4_request.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_local_ipv4_request.yaml new file mode 100644 index 0000000000..41aa000508 --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_local_ipv4_request.yaml @@ -0,0 +1,12 @@ +--- !ruby/object:Net::HTTP::Get +body: +body_stream: +header: + accept: + - "*/*" + host: + - 169.254.169.254 +method: GET +path: /latest/meta-data/local-ipv4 +request_has_body: false +response_has_body: true diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_local_ipv4_response.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_local_ipv4_response.yaml new file mode 100644 index 0000000000..9d0cd7acab --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_local_ipv4_response.yaml @@ -0,0 +1,25 @@ +--- !ruby/object:Net::HTTPOK +body: 10.202.99.21 +body_exist: true +code: "200" +header: + connection: + - close + server: + - EC2ws + date: + - Wed, 31 Oct 2012 02:34:30 GMT + etag: + - "\"3765980501\"" + content-length: + - "12" + last-modified: + - Tue, 30 Oct 2012 23:48:24 GMT + accept-ranges: + - bytes + content-type: + - text/plain +http_version: "1.0" +message: OK +read: true +socket: diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_mac_request.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_mac_request.yaml new file mode 100644 index 0000000000..4d86978873 --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_mac_request.yaml @@ -0,0 +1,12 @@ +--- !ruby/object:Net::HTTP::Get +body: +body_stream: +header: + accept: + - "*/*" + host: + - 169.254.169.254 +method: GET +path: /latest/meta-data/mac +request_has_body: false +response_has_body: true diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_mac_response.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_mac_response.yaml new file mode 100644 index 0000000000..379f172e33 --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_mac_response.yaml @@ -0,0 +1,25 @@ +--- !ruby/object:Net::HTTPOK +body: 12:31:3B:12:60:E7 +body_exist: true +code: "200" +header: + connection: + - close + server: + - EC2ws + date: + - Wed, 31 Oct 2012 02:34:30 GMT + etag: + - "\"3807921877\"" + content-length: + - "17" + last-modified: + - Tue, 30 Oct 2012 23:48:24 GMT + accept-ranges: + - bytes + content-type: + - text/plain +http_version: "1.0" +message: OK +read: true +socket: diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_metrics_request.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_metrics_request.yaml new file mode 100644 index 0000000000..f56b00ec7e --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_metrics_request.yaml @@ -0,0 +1,12 @@ +--- !ruby/object:Net::HTTP::Get +body: +body_stream: +header: + accept: + - "*/*" + host: + - 169.254.169.254 +method: GET +path: /latest/meta-data/metrics/ +request_has_body: false +response_has_body: true diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_metrics_response.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_metrics_response.yaml new file mode 100644 index 0000000000..ec5faf5d57 --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_metrics_response.yaml @@ -0,0 +1,25 @@ +--- !ruby/object:Net::HTTPOK +body: vhostmd +body_exist: true +code: "200" +header: + connection: + - close + server: + - EC2ws + date: + - Wed, 31 Oct 2012 02:34:30 GMT + etag: + - "\"39738612\"" + content-length: + - "7" + last-modified: + - Tue, 30 Oct 2012 23:48:24 GMT + accept-ranges: + - bytes + content-type: + - text/plain +http_version: "1.0" +message: OK +read: true +socket: diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_metrics_vhostmd_request.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_metrics_vhostmd_request.yaml new file mode 100644 index 0000000000..9b8fdc9941 --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_metrics_vhostmd_request.yaml @@ -0,0 +1,12 @@ +--- !ruby/object:Net::HTTP::Get +body: +body_stream: +header: + accept: + - "*/*" + host: + - 169.254.169.254 +method: GET +path: /latest/meta-data/metrics/vhostmd +request_has_body: false +response_has_body: true diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_metrics_vhostmd_response.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_metrics_vhostmd_response.yaml new file mode 100644 index 0000000000..db307906cd --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_metrics_vhostmd_response.yaml @@ -0,0 +1,25 @@ +--- !ruby/object:Net::HTTPOK +body: +body_exist: true +code: "200" +header: + connection: + - close + server: + - EC2ws + date: + - Wed, 31 Oct 2012 02:34:30 GMT + etag: + - "\"3316539729\"" + content-length: + - "38" + last-modified: + - Wed, 31 Oct 2012 02:34:00 GMT + accept-ranges: + - bytes + content-type: + - text/plain +http_version: "1.0" +message: OK +read: true +socket: diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_device_number_request.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_device_number_request.yaml new file mode 100644 index 0000000000..16a86e4652 --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_device_number_request.yaml @@ -0,0 +1,12 @@ +--- !ruby/object:Net::HTTP::Get +body: +body_stream: +header: + accept: + - "*/*" + host: + - 169.254.169.254 +method: GET +path: /latest/meta-data/network/interfaces/macs/12:31:3b:12:60:e7/device-number +request_has_body: false +response_has_body: true diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_device_number_response.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_device_number_response.yaml new file mode 100644 index 0000000000..edf41898f6 --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_device_number_response.yaml @@ -0,0 +1,25 @@ +--- !ruby/object:Net::HTTPOK +body: "0" +body_exist: true +code: "200" +header: + connection: + - close + server: + - EC2ws + date: + - Wed, 31 Oct 2012 02:34:30 GMT + etag: + - "\"22963894\"" + content-length: + - "1" + last-modified: + - Tue, 30 Oct 2012 23:48:24 GMT + accept-ranges: + - bytes + content-type: + - text/plain +http_version: "1.0" +message: OK +read: true +socket: diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_local_hostname_request.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_local_hostname_request.yaml new file mode 100644 index 0000000000..b033a89fb0 --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_local_hostname_request.yaml @@ -0,0 +1,12 @@ +--- !ruby/object:Net::HTTP::Get +body: +body_stream: +header: + accept: + - "*/*" + host: + - 169.254.169.254 +method: GET +path: /latest/meta-data/network/interfaces/macs/12:31:3b:12:60:e7/local-hostname +request_has_body: false +response_has_body: true diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_local_hostname_response.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_local_hostname_response.yaml new file mode 100644 index 0000000000..84d8236eba --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_local_hostname_response.yaml @@ -0,0 +1,25 @@ +--- !ruby/object:Net::HTTPOK +body: ip-10-202-99-21.ec2.internal +body_exist: true +code: "200" +header: + connection: + - close + server: + - EC2ws + date: + - Wed, 31 Oct 2012 02:34:30 GMT + etag: + - "\"3581424789\"" + content-length: + - "28" + last-modified: + - Tue, 30 Oct 2012 23:48:24 GMT + accept-ranges: + - bytes + content-type: + - text/plain +http_version: "1.0" +message: OK +read: true +socket: diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_local_ipv4s_request.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_local_ipv4s_request.yaml new file mode 100644 index 0000000000..9d95f8e796 --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_local_ipv4s_request.yaml @@ -0,0 +1,12 @@ +--- !ruby/object:Net::HTTP::Get +body: +body_stream: +header: + accept: + - "*/*" + host: + - 169.254.169.254 +method: GET +path: /latest/meta-data/network/interfaces/macs/12:31:3b:12:60:e7/local-ipv4s +request_has_body: false +response_has_body: true diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_local_ipv4s_response.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_local_ipv4s_response.yaml new file mode 100644 index 0000000000..29ed578efb --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_local_ipv4s_response.yaml @@ -0,0 +1,25 @@ +--- !ruby/object:Net::HTTPOK +body: 10.202.99.21 +body_exist: true +code: "200" +header: + connection: + - close + server: + - EC2ws + date: + - Wed, 31 Oct 2012 02:34:30 GMT + etag: + - "\"3765974357\"" + content-length: + - "12" + last-modified: + - Tue, 30 Oct 2012 23:48:24 GMT + accept-ranges: + - bytes + content-type: + - text/plain +http_version: "1.0" +message: OK +read: true +socket: diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_mac_request.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_mac_request.yaml new file mode 100644 index 0000000000..f34b89bf54 --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_mac_request.yaml @@ -0,0 +1,12 @@ +--- !ruby/object:Net::HTTP::Get +body: +body_stream: +header: + accept: + - "*/*" + host: + - 169.254.169.254 +method: GET +path: /latest/meta-data/network/interfaces/macs/12:31:3b:12:60:e7/mac +request_has_body: false +response_has_body: true diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_mac_response.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_mac_response.yaml new file mode 100644 index 0000000000..4124601a75 --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_mac_response.yaml @@ -0,0 +1,25 @@ +--- !ruby/object:Net::HTTPOK +body: 12:31:3b:12:60:e7 +body_exist: true +code: "200" +header: + connection: + - close + server: + - EC2ws + date: + - Wed, 31 Oct 2012 02:34:30 GMT + etag: + - "\"3807917077\"" + content-length: + - "17" + last-modified: + - Tue, 30 Oct 2012 23:48:24 GMT + accept-ranges: + - bytes + content-type: + - text/plain +http_version: "1.0" +message: OK +read: true +socket: diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_owner_id_request.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_owner_id_request.yaml new file mode 100644 index 0000000000..bfd06097a1 --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_owner_id_request.yaml @@ -0,0 +1,12 @@ +--- !ruby/object:Net::HTTP::Get +body: +body_stream: +header: + accept: + - "*/*" + host: + - 169.254.169.254 +method: GET +path: /latest/meta-data/network/interfaces/macs/12:31:3b:12:60:e7/owner-id +request_has_body: false +response_has_body: true diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_owner_id_response.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_owner_id_response.yaml new file mode 100644 index 0000000000..64d4dbd006 --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_owner_id_response.yaml @@ -0,0 +1,25 @@ +--- !ruby/object:Net::HTTPOK +body: "659177031278" +body_exist: true +code: "200" +header: + connection: + - close + server: + - EC2ws + date: + - Wed, 31 Oct 2012 02:34:30 GMT + etag: + - "\"3765974485\"" + content-length: + - "12" + last-modified: + - Tue, 30 Oct 2012 23:48:24 GMT + accept-ranges: + - bytes + content-type: + - text/plain +http_version: "1.0" +message: OK +read: true +socket: diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_public_hostname_request.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_public_hostname_request.yaml new file mode 100644 index 0000000000..f78a82b2af --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_public_hostname_request.yaml @@ -0,0 +1,12 @@ +--- !ruby/object:Net::HTTP::Get +body: +body_stream: +header: + accept: + - "*/*" + host: + - 169.254.169.254 +method: GET +path: /latest/meta-data/network/interfaces/macs/12:31:3b:12:60:e7/public-hostname +request_has_body: false +response_has_body: true diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_public_hostname_response.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_public_hostname_response.yaml new file mode 100644 index 0000000000..f5c3ca355b --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_public_hostname_response.yaml @@ -0,0 +1,25 @@ +--- !ruby/object:Net::HTTPOK +body: ec2-23-22-77-215.compute-1.amazonaws.com +body_exist: true +code: "200" +header: + connection: + - close + server: + - EC2ws + date: + - Wed, 31 Oct 2012 02:34:30 GMT + etag: + - "\"2977445269\"" + content-length: + - "40" + last-modified: + - Tue, 30 Oct 2012 23:48:24 GMT + accept-ranges: + - bytes + content-type: + - text/plain +http_version: "1.0" +message: OK +read: true +socket: diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_public_ipv4s_request.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_public_ipv4s_request.yaml new file mode 100644 index 0000000000..bec706e7c2 --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_public_ipv4s_request.yaml @@ -0,0 +1,12 @@ +--- !ruby/object:Net::HTTP::Get +body: +body_stream: +header: + accept: + - "*/*" + host: + - 169.254.169.254 +method: GET +path: /latest/meta-data/network/interfaces/macs/12:31:3b:12:60:e7/public-ipv4s +request_has_body: false +response_has_body: true diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_public_ipv4s_response.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_public_ipv4s_response.yaml new file mode 100644 index 0000000000..23910e651f --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_public_ipv4s_response.yaml @@ -0,0 +1,25 @@ +--- !ruby/object:Net::HTTPOK +body: 23.22.77.215 +body_exist: true +code: "200" +header: + connection: + - close + server: + - EC2ws + date: + - Wed, 31 Oct 2012 02:34:30 GMT + etag: + - "\"3765974293\"" + content-length: + - "12" + last-modified: + - Tue, 30 Oct 2012 23:48:24 GMT + accept-ranges: + - bytes + content-type: + - text/plain +http_version: "1.0" +message: OK +read: true +socket: diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_request.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_request.yaml new file mode 100644 index 0000000000..6c417e0d9c --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_request.yaml @@ -0,0 +1,12 @@ +--- !ruby/object:Net::HTTP::Get +body: +body_stream: +header: + accept: + - "*/*" + host: + - 169.254.169.254 +method: GET +path: /latest/meta-data/network/interfaces/macs/12:31:3b:12:60:e7/ +request_has_body: false +response_has_body: true diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_response.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_response.yaml new file mode 100644 index 0000000000..7023de2979 --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_12_31_3b_12_60_e7_response.yaml @@ -0,0 +1,32 @@ +--- !ruby/object:Net::HTTPOK +body: |- + device-number + local-hostname + local-ipv4s + mac + owner-id + public-hostname + public-ipv4s +body_exist: true +code: "200" +header: + connection: + - close + server: + - EC2ws + date: + - Wed, 31 Oct 2012 02:34:30 GMT + etag: + - "\"1886926549\"" + content-length: + - "82" + last-modified: + - Tue, 30 Oct 2012 23:48:24 GMT + accept-ranges: + - bytes + content-type: + - text/plain +http_version: "1.0" +message: OK +read: true +socket: diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_request.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_request.yaml new file mode 100644 index 0000000000..be860e425a --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_request.yaml @@ -0,0 +1,12 @@ +--- !ruby/object:Net::HTTP::Get +body: +body_stream: +header: + accept: + - "*/*" + host: + - 169.254.169.254 +method: GET +path: /latest/meta-data/network/interfaces/macs/ +request_has_body: false +response_has_body: true diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_response.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_response.yaml new file mode 100644 index 0000000000..73ac2c7335 --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_macs_response.yaml @@ -0,0 +1,25 @@ +--- !ruby/object:Net::HTTPOK +body: 12:31:3b:12:60:e7/ +body_exist: true +code: "200" +header: + connection: + - close + server: + - EC2ws + date: + - Wed, 31 Oct 2012 02:34:30 GMT + etag: + - "\"3849860757\"" + content-length: + - "18" + last-modified: + - Tue, 30 Oct 2012 23:48:24 GMT + accept-ranges: + - bytes + content-type: + - text/plain +http_version: "1.0" +message: OK +read: true +socket: diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_request.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_request.yaml new file mode 100644 index 0000000000..34a067a24e --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_request.yaml @@ -0,0 +1,12 @@ +--- !ruby/object:Net::HTTP::Get +body: +body_stream: +header: + accept: + - "*/*" + host: + - 169.254.169.254 +method: GET +path: /latest/meta-data/network/interfaces/ +request_has_body: false +response_has_body: true diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_response.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_response.yaml new file mode 100644 index 0000000000..5723ea0ce4 --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_network_interfaces_response.yaml @@ -0,0 +1,25 @@ +--- !ruby/object:Net::HTTPOK +body: macs/ +body_exist: true +code: "200" +header: + connection: + - close + server: + - EC2ws + date: + - Wed, 31 Oct 2012 02:34:30 GMT + etag: + - "\"56515838\"" + content-length: + - "5" + last-modified: + - Tue, 30 Oct 2012 23:48:24 GMT + accept-ranges: + - bytes + content-type: + - text/plain +http_version: "1.0" +message: OK +read: true +socket: diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_network_request.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_network_request.yaml new file mode 100644 index 0000000000..63d624ebdb --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_network_request.yaml @@ -0,0 +1,12 @@ +--- !ruby/object:Net::HTTP::Get +body: +body_stream: +header: + accept: + - "*/*" + host: + - 169.254.169.254 +method: GET +path: /latest/meta-data/network/ +request_has_body: false +response_has_body: true diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_network_response.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_network_response.yaml new file mode 100644 index 0000000000..84268a863c --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_network_response.yaml @@ -0,0 +1,25 @@ +--- !ruby/object:Net::HTTPOK +body: interfaces/ +body_exist: true +code: "200" +header: + connection: + - close + server: + - EC2ws + date: + - Wed, 31 Oct 2012 02:34:30 GMT + etag: + - "\"3791617493\"" + content-length: + - "11" + last-modified: + - Tue, 30 Oct 2012 23:48:24 GMT + accept-ranges: + - bytes + content-type: + - text/plain +http_version: "1.0" +message: OK +read: true +socket: diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_placement_availability_zone_request.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_placement_availability_zone_request.yaml new file mode 100644 index 0000000000..cda47167c1 --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_placement_availability_zone_request.yaml @@ -0,0 +1,12 @@ +--- !ruby/object:Net::HTTP::Get +body: +body_stream: +header: + accept: + - "*/*" + host: + - 169.254.169.254 +method: GET +path: /latest/meta-data/placement/availability-zone +request_has_body: false +response_has_body: true diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_placement_availability_zone_response.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_placement_availability_zone_response.yaml new file mode 100644 index 0000000000..91794f5704 --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_placement_availability_zone_response.yaml @@ -0,0 +1,25 @@ +--- !ruby/object:Net::HTTPOK +body: us-east-1d +body_exist: true +code: "200" +header: + connection: + - close + server: + - EC2ws + date: + - Wed, 31 Oct 2012 02:34:30 GMT + etag: + - "\"3782755669\"" + content-length: + - "10" + last-modified: + - Tue, 30 Oct 2012 23:48:24 GMT + accept-ranges: + - bytes + content-type: + - text/plain +http_version: "1.0" +message: OK +read: true +socket: diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_placement_request.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_placement_request.yaml new file mode 100644 index 0000000000..af23ce07ea --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_placement_request.yaml @@ -0,0 +1,12 @@ +--- !ruby/object:Net::HTTP::Get +body: +body_stream: +header: + accept: + - "*/*" + host: + - 169.254.169.254 +method: GET +path: /latest/meta-data/placement/ +request_has_body: false +response_has_body: true diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_placement_response.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_placement_response.yaml new file mode 100644 index 0000000000..b412253d8b --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_placement_response.yaml @@ -0,0 +1,25 @@ +--- !ruby/object:Net::HTTPOK +body: availability-zone +body_exist: true +code: "200" +header: + connection: + - close + server: + - EC2ws + date: + - Wed, 31 Oct 2012 02:34:30 GMT + etag: + - "\"3808394645\"" + content-length: + - "17" + last-modified: + - Tue, 30 Oct 2012 23:48:24 GMT + accept-ranges: + - bytes + content-type: + - text/plain +http_version: "1.0" +message: OK +read: true +socket: diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_profile_request.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_profile_request.yaml new file mode 100644 index 0000000000..4fc5018419 --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_profile_request.yaml @@ -0,0 +1,12 @@ +--- !ruby/object:Net::HTTP::Get +body: +body_stream: +header: + accept: + - "*/*" + host: + - 169.254.169.254 +method: GET +path: /latest/meta-data/profile +request_has_body: false +response_has_body: true diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_profile_response.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_profile_response.yaml new file mode 100644 index 0000000000..b9a21825aa --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_profile_response.yaml @@ -0,0 +1,25 @@ +--- !ruby/object:Net::HTTPOK +body: default-paravirtual +body_exist: true +code: "200" +header: + connection: + - close + server: + - EC2ws + date: + - Wed, 31 Oct 2012 02:34:30 GMT + etag: + - "\"3858251221\"" + content-length: + - "19" + last-modified: + - Tue, 30 Oct 2012 23:48:24 GMT + accept-ranges: + - bytes + content-type: + - text/plain +http_version: "1.0" +message: OK +read: true +socket: diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_public_hostname_request.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_public_hostname_request.yaml new file mode 100644 index 0000000000..5ece8b358d --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_public_hostname_request.yaml @@ -0,0 +1,12 @@ +--- !ruby/object:Net::HTTP::Get +body: +body_stream: +header: + accept: + - "*/*" + host: + - 169.254.169.254 +method: GET +path: /latest/meta-data/public-hostname +request_has_body: false +response_has_body: true diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_public_hostname_response.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_public_hostname_response.yaml new file mode 100644 index 0000000000..a55fb0ba21 --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_public_hostname_response.yaml @@ -0,0 +1,25 @@ +--- !ruby/object:Net::HTTPOK +body: ec2-23-22-77-215.compute-1.amazonaws.com +body_exist: true +code: "200" +header: + connection: + - close + server: + - EC2ws + date: + - Wed, 31 Oct 2012 02:34:30 GMT + etag: + - "\"2977447317\"" + content-length: + - "40" + last-modified: + - Tue, 30 Oct 2012 23:48:24 GMT + accept-ranges: + - bytes + content-type: + - text/plain +http_version: "1.0" +message: OK +read: true +socket: diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_public_ipv4_request.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_public_ipv4_request.yaml new file mode 100644 index 0000000000..da9d93c8c6 --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_public_ipv4_request.yaml @@ -0,0 +1,12 @@ +--- !ruby/object:Net::HTTP::Get +body: +body_stream: +header: + accept: + - "*/*" + host: + - 169.254.169.254 +method: GET +path: /latest/meta-data/public-ipv4 +request_has_body: false +response_has_body: true diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_public_ipv4_response.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_public_ipv4_response.yaml new file mode 100644 index 0000000000..bc5a7676df --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_public_ipv4_response.yaml @@ -0,0 +1,25 @@ +--- !ruby/object:Net::HTTPOK +body: 23.22.77.215 +body_exist: true +code: "200" +header: + connection: + - close + server: + - EC2ws + date: + - Wed, 31 Oct 2012 02:34:30 GMT + etag: + - "\"3765976405\"" + content-length: + - "12" + last-modified: + - Tue, 30 Oct 2012 23:48:24 GMT + accept-ranges: + - bytes + content-type: + - text/plain +http_version: "1.0" +message: OK +read: true +socket: diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_public_keys_0_jeff_request.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_public_keys_0_jeff_request.yaml new file mode 100644 index 0000000000..1ef742bd77 --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_public_keys_0_jeff_request.yaml @@ -0,0 +1,12 @@ +--- !ruby/object:Net::HTTP::Get +body: +body_stream: +header: + accept: + - "*/*" + host: + - 169.254.169.254 +method: GET +path: /latest/meta-data/public-keys/0=jeff +request_has_body: false +response_has_body: true diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_public_keys_0_jeff_response.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_public_keys_0_jeff_response.yaml new file mode 100644 index 0000000000..4cc4531bbf --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_public_keys_0_jeff_response.yaml @@ -0,0 +1,31 @@ +--- !ruby/object:Net::HTTPNotFound +body: | + + + + + 404 - Not Found + + +

    404 - Not Found

    + + + +body_exist: true +code: "404" +header: + connection: + - close + server: + - EC2ws + date: + - Wed, 31 Oct 2012 02:34:30 GMT + content-length: + - "345" + content-type: + - text/html +http_version: "1.0" +message: Not Found +read: true +socket: diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_public_keys_request.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_public_keys_request.yaml new file mode 100644 index 0000000000..87127fc131 --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_public_keys_request.yaml @@ -0,0 +1,12 @@ +--- !ruby/object:Net::HTTP::Get +body: +body_stream: +header: + accept: + - "*/*" + host: + - 169.254.169.254 +method: GET +path: /latest/meta-data/public-keys/ +request_has_body: false +response_has_body: true diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_public_keys_response.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_public_keys_response.yaml new file mode 100644 index 0000000000..5033c30eea --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_public_keys_response.yaml @@ -0,0 +1,25 @@ +--- !ruby/object:Net::HTTPOK +body: 0=jeff +body_exist: true +code: "200" +header: + connection: + - close + server: + - EC2ws + date: + - Wed, 31 Oct 2012 02:34:30 GMT + etag: + - "\"48129328\"" + content-length: + - "6" + last-modified: + - Tue, 30 Oct 2012 23:48:24 GMT + accept-ranges: + - bytes + content-type: + - text/plain +http_version: "1.0" +message: OK +read: true +socket: diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_request.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_request.yaml new file mode 100644 index 0000000000..e70df2a098 --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_request.yaml @@ -0,0 +1,12 @@ +--- !ruby/object:Net::HTTP::Get +body: +body_stream: +header: + accept: + - "*/*" + host: + - 169.254.169.254 +method: GET +path: /latest/meta-data/ +request_has_body: false +response_has_body: true diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_reservation_id_request.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_reservation_id_request.yaml new file mode 100644 index 0000000000..3d805774cc --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_reservation_id_request.yaml @@ -0,0 +1,12 @@ +--- !ruby/object:Net::HTTP::Get +body: +body_stream: +header: + accept: + - "*/*" + host: + - 169.254.169.254 +method: GET +path: /latest/meta-data/reservation-id +request_has_body: false +response_has_body: true diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_reservation_id_response.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_reservation_id_response.yaml new file mode 100644 index 0000000000..7292fab25c --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_reservation_id_response.yaml @@ -0,0 +1,25 @@ +--- !ruby/object:Net::HTTPOK +body: r-016e5467 +body_exist: true +code: "200" +header: + connection: + - close + server: + - EC2ws + date: + - Wed, 31 Oct 2012 02:34:30 GMT + etag: + - "\"3782755413\"" + content-length: + - "10" + last-modified: + - Tue, 30 Oct 2012 23:48:24 GMT + accept-ranges: + - bytes + content-type: + - text/plain +http_version: "1.0" +message: OK +read: true +socket: diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_response.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_response.yaml new file mode 100644 index 0000000000..9bf264fb83 --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_response.yaml @@ -0,0 +1,46 @@ +--- !ruby/object:Net::HTTPOK +body: |- + ami-id + ami-launch-index + ami-manifest-path + block-device-mapping/ + hostname + instance-action + instance-id + instance-type + kernel-id + local-hostname + local-ipv4 + mac + metrics/ + network/ + placement/ + profile + public-hostname + public-ipv4 + public-keys/ + reservation-id + security-groups +body_exist: true +code: "200" +header: + connection: + - close + server: + - EC2ws + date: + - Wed, 31 Oct 2012 02:34:30 GMT + etag: + - "\"2228961327\"" + content-length: + - "263" + last-modified: + - Tue, 30 Oct 2012 23:48:24 GMT + accept-ranges: + - bytes + content-type: + - text/plain +http_version: "1.0" +message: OK +read: true +socket: diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_security_groups_request.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_security_groups_request.yaml new file mode 100644 index 0000000000..b8c8f32d11 --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_security_groups_request.yaml @@ -0,0 +1,12 @@ +--- !ruby/object:Net::HTTP::Get +body: +body_stream: +header: + accept: + - "*/*" + host: + - 169.254.169.254 +method: GET +path: /latest/meta-data/security-groups +request_has_body: false +response_has_body: true diff --git a/spec/fixtures/unit/util/ec2/ec2_meta_data_security_groups_response.yaml b/spec/fixtures/unit/util/ec2/ec2_meta_data_security_groups_response.yaml new file mode 100644 index 0000000000..c902341fd7 --- /dev/null +++ b/spec/fixtures/unit/util/ec2/ec2_meta_data_security_groups_response.yaml @@ -0,0 +1,25 @@ +--- !ruby/object:Net::HTTPOK +body: quicklaunch-1 +body_exist: true +code: "200" +header: + connection: + - close + server: + - EC2ws + date: + - Wed, 31 Oct 2012 02:34:30 GMT + etag: + - "\"3774366741\"" + content-length: + - "13" + last-modified: + - Tue, 30 Oct 2012 23:48:24 GMT + accept-ranges: + - bytes + content-type: + - text/plain +http_version: "1.0" +message: OK +read: true +socket: From bd2605838f5f1386c02e588121e9488072d95cba Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Mon, 28 Jan 2013 17:17:46 -0800 Subject: [PATCH 1135/3753] (#7559) Add Facter::Util::EC2.with_metadata_server method Without this patch Facter has no clear way to define the EC2 related facts in a robust and reliable manner. This is a problem because Facter needs to run as robustly on nodes that are inside of EC2 as it does on nodes that are outside of EC2. The root cause of the problem is that we have no way to introspect the system to determine if we're on EC2 or not without making a network call to the metadata server. [1] This means that either non-EC2 nodes will suffer a timeout trying to connect to a non-existent metadata server or EC2 nodes will suffer from facts not being defined. This patch addresses the problem by adding a new utility method with the following behavior. A block is accepted and will be executed only under the following conditions. The block should execute whatever code is necessary to define the EC2 facts themselves. 1: Do not execute if the virtual fact is not "xenu" 2: Do not execute the metadata server does not respond in 100ms after three consecutive attempts. [1] https://forums.aws.amazon.com/thread.jspa?threadID=62617 --- lib/facter/util/ec2.rb | 73 ++++++++++++++++++++++++++++++++++++++ spec/unit/util/ec2_spec.rb | 44 +++++++++++++++++++++++ 2 files changed, 117 insertions(+) diff --git a/lib/facter/util/ec2.rb b/lib/facter/util/ec2.rb index c5c393ac3f..769903a120 100644 --- a/lib/facter/util/ec2.rb +++ b/lib/facter/util/ec2.rb @@ -4,6 +4,79 @@ # Provide a set of utility static methods that help with resolving the EC2 # fact. module Facter::Util::EC2 + ## + # with_metadata_server takes a block of code and executes the block only if Facter is + # running on node that can access a metadata server at + # http://169.254.168.254/. This is useful to decide if it's reasonably + # likely that talking to the EC2 metadata server will be successful or not. + # + # @option options [Integer] :timeout (100) the maxiumum number of + # milliseconds Facter will block trying to talk to the metadata server. + # Defaults to 200. + # + # @option options [String] :fact ('virtual') the fact to check. The block will only be + # executed if the fact named here matches the value named in the :value + # option. + # + # @option options [String] :value ('xenu') the value to check. The block will be + # executed if Facter.value(options[:fact]) matches this value. + # + # @option options [String] :api_version ('latest') the Amazon AWS API + # version. The version string is usually a date, e.g. '2008-02-01'. + # + # @option options [Fixnum] :retry_limit (3) the maximum number of times that + # this method will try to contact the metadata server. The maximum run time + # is the timeout times this limit, so please keep the value small. + # + # @return [Object] the return value of the passed block, or {false} if the + # block was not executed because the conditions were not met or a timeout + # occurs. + def self.with_metadata_server(options = {}, &block) + opts = options.dup + opts[:timeout] ||= 100 + opts[:fact] ||= 'virtual' + opts[:value] ||= 'xenu' + opts[:api_version] ||= 'latest' + opts[:retry_limit] ||= 3 + # Conversion to fractional seconds for Timeout + timeout = opts[:timeout] / 1000.0 + raise ArgumentError, "A value is required for :fact" if opts[:fact].nil? + raise ArgumentError, "A value is required for :value" if opts[:value].nil? + return false if Facter.value(opts[:fact]) != opts[:value] + + metadata_base_url = "http://169.254.169.254/" + + attempts = 0 + begin + able_to_connect = false + attempts = attempts + 1 + # Read the list of supported API versions + Timeout.timeout(timeout) do + read_uri(metadata_base_url) + end + rescue Timeout::Error => detail + retry if attempts < opts[:retry_limit] + Facter.warn "Timeout exceeded trying to communicate with #{metadata_base_url}, " + + "metadata server facts will be undefined. #{detail.message}" + rescue Errno::EHOSTUNREACH, Errno::ENETUNREACH, Errno::ECONNREFUSED => detail + retry if attempts < opts[:retry_limit] + Facter.warn "No metadata server available at #{metadata_base_url}, " + + "metadata server facts will be undefined. #{detail.message}" + rescue OpenURI::HTTPError => detail + retry if attempts < opts[:retry_limit] + Facter.warn "Metadata server at #{metadata_base_url} responded with an error. " + + "metadata server facts will be undefined. #{detail.message}" + else + able_to_connect = true + end + + if able_to_connect + return block.call + else + return false + end + end + class << self # Test if we can connect to the EC2 api. Return true if able to connect. # On failure this function fails silently and returns false. diff --git a/spec/unit/util/ec2_spec.rb b/spec/unit/util/ec2_spec.rb index c894e597a3..d3dcf716b2 100755 --- a/spec/unit/util/ec2_spec.rb +++ b/spec/unit/util/ec2_spec.rb @@ -177,4 +177,48 @@ Facter::Util::EC2.userdata('2008-02-01').should == example_userdata end end + + describe "Facter::Util::EC2.with_metadata_server", :focus => true do + before :each do + Facter::Util::EC2.stubs(:read_uri).returns("latest") + end + + subject do + Facter::Util::EC2.with_metadata_server do + "HELLO FROM THE CODE BLOCK" + end + end + + it 'returns false when not running on xenu' do + Facter.stubs(:value).with('virtual').returns('vmware') + subject.should be_false + end + + context 'default options and running on a xenu virtual machine' do + before :each do + Facter.stubs(:value).with('virtual').returns('xenu') + end + it 'returns the value of the block when the metadata server responds' do + subject.should == "HELLO FROM THE CODE BLOCK" + end + it 'returns false when the metadata server is unreachable' do + described_class.stubs(:read_uri).raises(Errno::ENETUNREACH) + subject.should be_false + end + it 'does not execute the block if the connection raises an exception' do + described_class.stubs(:read_uri).raises(Timeout::Error) + myvar = "The block didn't get called" + described_class.with_metadata_server do + myvar = "The block was called and should not have been." + end.should be_false + myvar.should == "The block didn't get called" + end + it 'succeeds on the third retry' do + retry_metadata = sequence('metadata') + Timeout.expects(:timeout).twice.in_sequence(retry_metadata).raises(Timeout::Error) + Timeout.expects(:timeout).once.in_sequence(retry_metadata).returns(true) + subject.should == "HELLO FROM THE CODE BLOCK" + end + end + end end From ce18220fcb93e13ff459d2b4abcf18a96c658b87 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Mon, 28 Jan 2013 17:41:05 -0800 Subject: [PATCH 1136/3753] (#7559) Try to define EC2 facts if virtual => xenu Without this patch the EC2 facts use a number of methods to determine if they're running on openstack, Amazon EC2, eucalyptus, and others. These methods have proved to be lacking robustness. This is a problem because Facter fails to define facts even when a Metadata server is available. This patch addresses the problem by changing the behavior of deciding when to define the dynamic EC2 facts. The Facter::Util::EC2.with_metadata_server method is used with a 50ms timeout over three consecutive retries. With this patch applied the EC2 metadata and userdata facts will be defined whenever the node is has a virtual fact value of "xenu" and there is a metadata data server available at http://169.254.169.254 This patch also removes the `userdata` and `metadata` methods that were previous defined as instance methods on the base Object class. --- lib/facter/ec2.rb | 36 +------ lib/facter/util/ec2.rb | 140 +++++++++++++++------------ spec/unit/ec2_spec.rb | 193 +++++++++---------------------------- spec/unit/util/ec2_spec.rb | 144 --------------------------- 4 files changed, 127 insertions(+), 386 deletions(-) diff --git a/lib/facter/ec2.rb b/lib/facter/ec2.rb index 09e0109528..fefe88a93d 100644 --- a/lib/facter/ec2.rb +++ b/lib/facter/ec2.rb @@ -1,37 +1,3 @@ require 'facter/util/ec2' -require 'open-uri' -def metadata(id = "") - open("http://169.254.169.254/2008-02-01/meta-data/#{id||=''}").read. - split("\n").each do |o| - key = "#{id}#{o.gsub(/\=.*$/, '/')}" - if key[-1..-1] != '/' - value = open("http://169.254.169.254/2008-02-01/meta-data/#{key}").read. - split("\n") - symbol = "ec2_#{key.gsub(/\-|\//, '_')}".to_sym - Facter.add(symbol) { setcode { value.join(',') } } - else - metadata(key) - end - end -rescue => details - Facter.warn "Could not retrieve ec2 metadata: #{details.message}" -end - -def userdata() - Facter.add(:ec2_userdata) do - setcode do - if userdata = Facter::Util::EC2.userdata - userdata.split - end - end - end -end - -if (Facter::Util::EC2.has_euca_mac? || Facter::Util::EC2.has_openstack_mac? || - Facter::Util::EC2.has_ec2_arp?) && Facter::Util::EC2.can_connect? - metadata - userdata -else - Facter.debug "Not an EC2 host" -end +Facter::Util::EC2.add_ec2_facts diff --git a/lib/facter/util/ec2.rb b/lib/facter/util/ec2.rb index 769903a120..30035227d4 100644 --- a/lib/facter/util/ec2.rb +++ b/lib/facter/util/ec2.rb @@ -4,9 +4,60 @@ # Provide a set of utility static methods that help with resolving the EC2 # fact. module Facter::Util::EC2 + CONNECTION_ERRORS = [ + OpenURI::HTTPError, + Errno::EHOSTDOWN, + Errno::EHOSTUNREACH, + Errno::ENETUNREACH, + Errno::ECONNABORTED, + Errno::ECONNREFUSED, + Errno::ECONNRESET, + Errno::ETIMEDOUT, + ] ## - # with_metadata_server takes a block of code and executes the block only if Facter is - # running on node that can access a metadata server at + # metadata is a recursive function that walks over the metadata server + # located at http://169.254.169.254 and defines a fact for each value found. + # This method introduces a high amount of latency to Facter, so care must be + # taken to call it only when reasonably certain the host is running in an + # environment where the metadata server is available. + def self.define_metadata_facts(id = "") + begin + if body = read_uri("http://169.254.169.254/latest/meta-data/#{id}") + body.split("\n").each do |o| + key = "#{id}#{o.gsub(/\=.*$/, '/')}" + if key[-1..-1] != '/' + value = read_uri("http://169.254.169.254/latest/meta-data/#{key}").split("\n") + symbol = "ec2_#{key.gsub(/\-|\//, '_')}".to_sym + Facter.add(symbol) { setcode { value.join(',') } } + else + define_metadata_facts(key) + end + end + end + rescue *CONNECTION_ERRORS => detail + Facter.warn "Could not retrieve ec2 metadata: #{detail.message}" + end + end + + ## + # define_userdata_fact creates a single fact named 'ec2_userdata' which has a + # value of the contents of the EC2 userdata field. This method introduces a + # high amount of latency to Facter, so care must be taken to call it only + # when reasonably certain the host is running in an environment where the + # metadata server is available. + def self.define_userdata_fact + Facter.add(:ec2_userdata) do + setcode do + if userdata = Facter::Util::EC2.userdata + userdata.split + end + end + end + end + + ## + # with_metadata_server takes a block of code and executes the block only if + # Facter is running on node that can access a metadata server at # http://169.254.168.254/. This is useful to decide if it's reasonably # likely that talking to the EC2 metadata server will be successful or not. # @@ -14,12 +65,12 @@ module Facter::Util::EC2 # milliseconds Facter will block trying to talk to the metadata server. # Defaults to 200. # - # @option options [String] :fact ('virtual') the fact to check. The block will only be - # executed if the fact named here matches the value named in the :value - # option. + # @option options [String] :fact ('virtual') the fact to check. The block + # will only be executed if the fact named here matches the value named in the + # :value option. # - # @option options [String] :value ('xenu') the value to check. The block will be - # executed if Facter.value(options[:fact]) matches this value. + # @option options [String] :value ('xenu') the value to check. The block + # will be executed if Facter.value(options[:fact]) matches this value. # # @option options [String] :api_version ('latest') the Amazon AWS API # version. The version string is usually a date, e.g. '2008-02-01'. @@ -77,61 +128,6 @@ def self.with_metadata_server(options = {}, &block) end end - class << self - # Test if we can connect to the EC2 api. Return true if able to connect. - # On failure this function fails silently and returns false. - # - # The +wait_sec+ parameter provides you with an adjustable timeout. - # - def can_connect?(wait_sec=2) - url = "http://169.254.169.254/" - Timeout::timeout(wait_sec) {open(url)} - return true - rescue Timeout::Error - return false - rescue - return false - end - - # Test if this host has a mac address used by Eucalyptus clouds, which - # normally is +d0:0d+. - def has_euca_mac? - !!(Facter.value(:macaddress) =~ %r{^[dD]0:0[dD]:}) - end - - # Test if this host has a mac address used by OpenStack, which - # normally starts with FA:16:3E (older versions of OpenStack - # may generate mac addresses starting with 02:16:3E) - def has_openstack_mac? - !!(Facter.value(:macaddress) =~ %r{^(02|[fF][aA]):16:3[eE]}) - end - - # Test if the host has an arp entry in its cache that matches the EC2 arp, - # which is normally +fe:ff:ff:ff:ff:ff+. - def has_ec2_arp? - kernel = Facter.value(:kernel) - - mac_address_re = case kernel - when /Windows/i - /fe-ff-ff-ff-ff-ff/i - else - /fe:ff:ff:ff:ff:ff/i - end - - arp_command = case kernel - when /Windows/i, /SunOS/i - "arp -a" - else - "arp -an" - end - - if arp_table = Facter::Util::Resolution.exec(arp_command) - return true if arp_table.match(mac_address_re) - end - return false - end - end - ## # userdata returns a single string containing the body of the response of the # GET request for the URI http://169.254.169.254/latest/user-data/ If the @@ -171,4 +167,24 @@ def self.read_uri(uri) open(uri).read end private_class_method :read_uri + + ## + # add_ec2_facts defines EC2 related facts when running on an EC2 compatible + # node. This method will only ever do work once for the life of a process in + # order to limit the amount of network I/O. + # + # @option options [Boolean] :force (false) whether or not to force + # re-definition of the facts. + def self.add_ec2_facts(options = {}) + opts = options.dup + opts[:force] ||= false + unless opts[:force] + return nil if @add_ec2_facts_has_run + end + @add_ec2_facts_has_run = true + with_metadata_server :timeout => 50 do + define_userdata_fact + define_metadata_facts + end + end end diff --git a/spec/unit/ec2_spec.rb b/spec/unit/ec2_spec.rb index 93d0939e53..4a672017a4 100755 --- a/spec/unit/ec2_spec.rb +++ b/spec/unit/ec2_spec.rb @@ -17,171 +17,74 @@ # Assume we can connect Facter::Util::EC2.stubs(:can_connect?).returns(true) - end - - it "should create flat meta-data facts" do - Object.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/meta-data/"). - at_least_once.returns(StringIO.new("foo")) - - Object.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/meta-data/foo"). - at_least_once.returns(StringIO.new("bar")) - - Facter.collection.internal_loader.load(:ec2) - Facter.fact(:ec2_foo).value.should == "bar" + # The stubs above this line may not be necessary any + # longer. + Facter::Util::EC2.stubs(:read_uri). + with('http://169.254.169.254/').returns('OK') + Facter.stubs(:value). + with('virtual').returns('xenu') end - it "should create flat meta-data facts with comma seperation" do - Object.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/meta-data/"). - at_least_once.returns(StringIO.new("foo")) - - Object.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/meta-data/foo"). - at_least_once.returns(StringIO.new("bar\nbaz")) - - Facter.collection.internal_loader.load(:ec2) - - Facter.fact(:ec2_foo).value.should == "bar,baz" + let :util do + Facter::Util::EC2 end - it "should create structured meta-data facts" do - Object.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/meta-data/"). - at_least_once.returns(StringIO.new("foo/")) - - Object.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/meta-data/foo/"). - at_least_once.returns(StringIO.new("bar")) + it "defines facts dynamically from meta-data/" do + util.stubs(:read_uri). + with("#{api_prefix}/latest/meta-data/"). + returns("some_key_name") + util.stubs(:read_uri). + with("#{api_prefix}/latest/meta-data/some_key_name"). + at_least_once.returns("some_key_value") - Object.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/meta-data/foo/bar"). - at_least_once.returns(StringIO.new("baz")) + Facter::Util::EC2.add_ec2_facts(:force => true) - Facter.collection.internal_loader.load(:ec2) - - Facter.fact(:ec2_foo_bar).value.should == "baz" + Facter.fact(:ec2_some_key_name). + value.should == "some_key_value" end - it "should create ec2_user_data fact" do - # No meta-data - Object.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/meta-data/"). - at_least_once.returns(StringIO.new("")) + it "defines fact values with comma separation" do + util.stubs(:read_uri). + with("#{api_prefix}/latest/meta-data/"). + returns("some_key_name") + util.stubs(:read_uri). + with("#{api_prefix}/latest/meta-data/some_key_name"). + at_least_once.returns("bar\nbaz") - Facter::Util::EC2.stubs(:read_uri). - with("#{api_prefix}/latest/user-data/"). - returns("test") + Facter::Util::EC2.add_ec2_facts(:force => true) - Facter.collection.internal_loader.load(:ec2) - Facter.fact(:ec2_userdata).value.should == ["test"] + Facter.fact(:ec2_some_key_name). + value.should == "bar,baz" end - end - describe "when running on eucalyptus" do - before :each do - # Return false for ec2, true for eucalyptus - Facter::Util::EC2.stubs(:has_euca_mac?).returns(true) - Facter::Util::EC2.stubs(:has_openstack_mac?).returns(false) - Facter::Util::EC2.stubs(:has_ec2_arp?).returns(false) - - # Assume we can connect - Facter::Util::EC2.stubs(:can_connect?).returns(true) - end - - it "should create ec2_user_data fact" do - # No meta-data - Object.any_instance.expects(:open).\ - with("#{api_prefix}/2008-02-01/meta-data/").\ - at_least_once.returns(StringIO.new("")) - - Facter::Util::EC2.stubs(:read_uri). - with("#{api_prefix}/latest/user-data/"). - returns("test") - - # Force a fact load - Facter.collection.internal_loader.load(:ec2) - - Facter.fact(:ec2_userdata).value.should == ["test"] - end - end - - describe "when running on openstack" do - before :each do - # Return false for ec2, true for eucalyptus - Facter::Util::EC2.stubs(:has_openstack_mac?).returns(true) - Facter::Util::EC2.stubs(:has_euca_mac?).returns(false) - Facter::Util::EC2.stubs(:has_ec2_arp?).returns(false) + it "should create structured meta-data facts" do + util.stubs(:read_uri). + with("#{api_prefix}/latest/meta-data/"). + returns("foo/") + util.stubs(:read_uri). + with("#{api_prefix}/latest/meta-data/foo/"). + at_least_once.returns("bar") + util.stubs(:read_uri). + with("#{api_prefix}/latest/meta-data/foo/bar"). + at_least_once.returns("baz") + + Facter::Util::EC2.add_ec2_facts(:force => true) - # Assume we can connect - Facter::Util::EC2.stubs(:can_connect?).returns(true) + Facter.fact(:ec2_foo_bar).value.should == "baz" end - it "should create ec2_user_data fact" do - # No meta-data - Object.any_instance.expects(:open).\ - with("#{api_prefix}/2008-02-01/meta-data/").\ - at_least_once.returns(StringIO.new("")) - - Facter::Util::EC2.stubs(:read_uri). + it "should create ec2_userdata fact" do + util.stubs(:read_uri). + with("#{api_prefix}/latest/meta-data/"). + returns("") + util.stubs(:read_uri). with("#{api_prefix}/latest/user-data/"). - returns("test") + at_least_once.returns("test") - # Force a fact load - Facter.collection.internal_loader.load(:ec2) + Facter::Util::EC2.add_ec2_facts(:force => true) Facter.fact(:ec2_userdata).value.should == ["test"] end - - it "should return nil if open fails" do - Facter.expects(:warn).with('Could not retrieve ec2 metadata: host unreachable').twice - Facter::Util::Resolution.any_instance.stubs(:warn) # do not pollute test output - - Object.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/meta-data/"). - at_least_once.raises(RuntimeError, 'host unreachable') - - Facter::Util::EC2.stubs(:read_uri). - with("#{api_prefix}/latest/user-data/"). - raises(RuntimeError, 'host unreachable') - - # Force a fact load - Facter.collection.internal_loader.load(:ec2) - - Facter.fact(:ec2_userdata).value.should be_nil - end - - end - - describe "when api connect test fails" do - before :each do - Facter.stubs(:warnonce) - end - - it "should not populate ec2_userdata" do - # Emulate ec2 for now as it matters little to this test - Facter::Util::EC2.stubs(:has_euca_mac?).returns(true) - Facter::Util::EC2.stubs(:has_ec2_arp?).never - Facter::Util::EC2.expects(:can_connect?).at_least_once.returns(false) - - # The API should never be called at this point - Object.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/meta-data/").never - Object.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/user-data/").never - - # Force a fact load - Facter.collection.internal_loader.load(:ec2) - - Facter.fact(:ec2_userdata).should == nil - end - - it "should rescue the exception" do - Facter::Util::EC2.expects(:open).with("#{api_prefix}:80/").raises(Timeout::Error) - - Facter::Util::EC2.should_not be_can_connect - end end end diff --git a/spec/unit/util/ec2_spec.rb b/spec/unit/util/ec2_spec.rb index d3dcf716b2..180c956f25 100755 --- a/spec/unit/util/ec2_spec.rb +++ b/spec/unit/util/ec2_spec.rb @@ -8,150 +8,6 @@ # environments. let(:api_prefix) { "http://169.254.169.254/" } - describe "is_ec2_arp? method" do - describe "on linux" do - before :each do - # Return fake kernel - Facter.stubs(:value).with(:kernel).returns("linux") - end - - it "should succeed if arp table contains fe:ff:ff:ff:ff:ff" do - ec2arp = my_fixture_read("linux-arp-ec2.out") - Facter::Util::Resolution.expects(:exec).with("arp -an").\ - at_least_once.returns(ec2arp) - Facter::Util::EC2.has_ec2_arp?.should == true - end - - it "should succeed if arp table contains FE:FF:FF:FF:FF:FF" do - ec2arp = my_fixture_read("centos-arp-ec2.out") - Facter::Util::Resolution.expects(:exec).with("arp -an").\ - at_least_once.returns(ec2arp) - Facter::Util::EC2.has_ec2_arp?.should == true - end - - it "should fail if arp table does not contain fe:ff:ff:ff:ff:ff" do - ec2arp = my_fixture_read("linux-arp-not-ec2.out") - Facter::Util::Resolution.expects(:exec).with("arp -an"). - at_least_once.returns(ec2arp) - Facter::Util::EC2.has_ec2_arp?.should == false - end - end - - describe "on windows" do - before :each do - # Return fake kernel - Facter.stubs(:value).with(:kernel).returns("windows") - end - - it "should succeed if arp table contains fe-ff-ff-ff-ff-ff" do - ec2arp = my_fixture_read("windows-2008-arp-a.out") - Facter::Util::Resolution.expects(:exec).with("arp -a").\ - at_least_once.returns(ec2arp) - Facter::Util::EC2.has_ec2_arp?.should == true - end - - it "should fail if arp table does not contain fe-ff-ff-ff-ff-ff" do - ec2arp = my_fixture_read("windows-2008-arp-a-not-ec2.out") - Facter::Util::Resolution.expects(:exec).with("arp -a"). - at_least_once.returns(ec2arp) - Facter::Util::EC2.has_ec2_arp?.should == false - end - end - - describe "on solaris" do - before :each do - Facter.stubs(:value).with(:kernel).returns("SunOS") - end - - it "should fail if arp table does not contain fe:ff:ff:ff:ff:ff" do - ec2arp = my_fixture_read("solaris8_arp_a_not_ec2.out") - - Facter::Util::Resolution.expects(:exec).with("arp -a"). - at_least_once.returns(ec2arp) - - Facter::Util::EC2.has_ec2_arp?.should == false - end - end - end - - describe "is_euca_mac? method" do - it "should return true when the mac is a eucalyptus one" do - Facter.expects(:value).with(:macaddress).\ - at_least_once.returns("d0:0d:1a:b0:a1:00") - - Facter::Util::EC2.has_euca_mac?.should == true - end - - it "should return false when the mac is not a eucalyptus one" do - Facter.expects(:value).with(:macaddress).\ - at_least_once.returns("0c:1d:a0:bc:aa:02") - - Facter::Util::EC2.has_euca_mac?.should == false - end - end - - describe "is_openstack_mac? method" do - it "should return true when the mac is an openstack one" do - Facter.expects(:value).with(:macaddress).\ - at_least_once.returns("02:16:3e:54:89:fd") - - Facter::Util::EC2.has_openstack_mac?.should == true - end - - it "should return true when the mac is a newer openstack mac" do - # https://github.com/openstack/nova/commit/b684d651f540fc512ced58acd5ae2ef4d55a885c#nova/utils.py - Facter.expects(:value).with(:macaddress).\ - at_least_once.returns("fa:16:3e:54:89:fd") - - Facter::Util::EC2.has_openstack_mac?.should == true - end - - it "should return true when the mac is a newer openstack mac and returned in upper case" do - # https://github.com/openstack/nova/commit/b684d651f540fc512ced58acd5ae2ef4d55a885c#nova/utils.py - Facter.expects(:value).with(:macaddress).\ - at_least_once.returns("FA:16:3E:54:89:FD") - - Facter::Util::EC2.has_openstack_mac?.should == true - end - - it "should return false when the mac is not a openstack one" do - Facter.expects(:value).with(:macaddress).\ - at_least_once.returns("0c:1d:a0:bc:aa:02") - - Facter::Util::EC2.has_openstack_mac?.should == false - end - end - - describe "can_connect? method" do - it "returns true if api responds" do - # Return something upon connecting to the root - Module.any_instance.expects(:open).with("#{api_prefix}:80/"). - at_least_once.returns("2008-02-01\nlatest") - - Facter::Util::EC2.can_connect?.should be_true - end - - describe "when connection times out" do - it "should return false" do - # Emulate a timeout when connecting by throwing an exception - Module.any_instance.expects(:open).with("#{api_prefix}:80/"). - at_least_once.raises(RuntimeError) - - Facter::Util::EC2.can_connect?.should be_false - end - end - - describe "when connection is refused" do - it "should return false" do - # Emulate a connection refused - Module.any_instance.expects(:open).with("#{api_prefix}:80/"). - at_least_once.raises(Errno::ECONNREFUSED) - - Facter::Util::EC2.can_connect?.should be_false - end - end - end - describe "Facter::Util::EC2.userdata" do let :not_found_error do OpenURI::HTTPError.new("404 Not Found", StringIO.new) From 5c38ef951a8e05166c70b53658db0043ca5c2191 Mon Sep 17 00:00:00 2001 From: Daniel Pittman Date: Thu, 19 Jul 2012 21:42:35 -0700 Subject: [PATCH 1137/3753] Avoid external command for reading filesystem type list. This eliminates the use of cat in favour of plain and ordinary read; despite the brief RedHat reintroduction of the kernel bug when using select on files in /proc, this isn't worth working around in some-but-not-all places. It also eliminates most of the lines while still having the same effect, making the code much cleaner. Signed-off-by: Daniel Pittman --- lib/facter/filesystems.rb | 32 +++++++++----------------------- spec/unit/filesystems_spec.rb | 3 +-- 2 files changed, 10 insertions(+), 25 deletions(-) mode change 100644 => 100755 spec/unit/filesystems_spec.rb diff --git a/lib/facter/filesystems.rb b/lib/facter/filesystems.rb index a6f028cf5e..69aa1f6fe0 100644 --- a/lib/facter/filesystems.rb +++ b/lib/facter/filesystems.rb @@ -6,31 +6,17 @@ # Facter.add('filesystems') do confine :kernel => :linux - setcode do - # fuseblk can't be created and arguably isn't usable here. If you feel this - # doesn't match your use-case please raise a bug. - exclude = %w(fuseblk) - - # Make regular expression form our patterns ... - exclude = Regexp.union(*exclude.collect { |i| Regexp.new(i) }) - - # We utilise rely on "cat" for reading values from entries under "/proc". - # This is due to some problems with IO#read in Ruby and reading content of - # the "proc" file system that was reported more than once in the past ... - file_systems = [] - Facter::Util::Resolution.exec('cat /proc/filesystems 2> /dev/null').each_line do |line| - # Remove bloat ... - line.strip! - # Line of interest should not start with "nodev" ... - next if line.empty? or line.match(/^nodev/) + exclude = /^nodev|fuseblk/ - # We have something, so let us apply our device type filter ... - next if line.match(exclude) - - file_systems << line - end - file_systems.sort.join(',') + setcode do + # fuseblk can't be created and arguably isn't usable here. If you feel + # this doesn't match your use-case please raise a bug. + File.read('/proc/filesystems'). + split(/\n/). + reject {|l| l =~ exclude }. + map {|l| l.strip! }. + join(',') end end diff --git a/spec/unit/filesystems_spec.rb b/spec/unit/filesystems_spec.rb old mode 100644 new mode 100755 index 8ed6da233a..db1329fcc7 --- a/spec/unit/filesystems_spec.rb +++ b/spec/unit/filesystems_spec.rb @@ -14,8 +14,7 @@ before :each do Facter.fact(:kernel).stubs(:value).returns('Linux') fixture_data = my_fixture_read('linux') - Facter::Util::Resolution.expects(:exec) \ - .with('cat /proc/filesystems 2> /dev/null').returns(fixture_data) + File.expects(:read).with('/proc/filesystems').returns(fixture_data) Facter.collection.internal_loader.load(:filesystems) end From 70eb20dc89c351a94abb49006df9d9623c5d8f8b Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Wed, 30 Jan 2013 14:51:10 -0800 Subject: [PATCH 1138/3753] Faster, sysfs based interface listing for Linux. This reimplements "obtain a list of all interfaces" to use sysfs, where available, since it is significantly faster and cheaper than doing the same through forking and executing an external command. Signed-off-by: Daniel Pittman Conflicts: spec/unit/util/ip_spec.rb --- lib/facter/interfaces.rb | 20 +++++++++++--------- lib/facter/util/ip.rb | 7 +++++++ spec/unit/util/ip_spec.rb | 11 +++++++++-- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/lib/facter/interfaces.rb b/lib/facter/interfaces.rb index 4ace619bbf..9b132e1c24 100644 --- a/lib/facter/interfaces.rb +++ b/lib/facter/interfaces.rb @@ -1,12 +1,3 @@ -# Fact: interfaces -# -# Purpose: -# -# Resolution: -# -# Caveats: -# - # interfaces.rb # Try to get additional Facts about the machine's network interfaces # @@ -19,6 +10,17 @@ # Note that most of this only works on a fixed list of platforms; notably, Darwin # is missing. +Facter.add(:interfaces) do + confine :kernel => 'Linux' + has_weight 20 + setcode do + list = Dir.glob('/sys/class/net/*').map do |name| + Facter::Util::IP.alphafy(name.split('/').last) + end + list.empty? ? nil : list.join(',') + end +end + Facter.add(:interfaces) do confine :kernel => Facter::Util::IP.supported_platforms setcode do diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index a62808aaf4..73e20be55d 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -61,6 +61,13 @@ def self.supported_platforms end def self.get_interfaces + # Use sysfs on most Linux systems, which is the fastest option. + if File.exist?('/sys/class/net') + return Dir.glob('/sys/class/net/*').map do |name| + Facter::Util::IP.alphafy(name.split('/').last) + end + end + return [] unless output = Facter::Util::IP.get_all_interface_output() # windows interface names contain spaces and are quoted and can appear multiple diff --git a/spec/unit/util/ip_spec.rb b/spec/unit/util/ip_spec.rb index 3d45c82c8f..0f236fa160 100755 --- a/spec/unit/util/ip_spec.rb +++ b/spec/unit/util/ip_spec.rb @@ -22,35 +22,41 @@ end it "should return an empty list of interfaces on an unknown kernel" do + File.stubs(:exist?).with('/sys/class/net').returns(false) Facter.stubs(:value).returns("UnknownKernel") Facter::Util::IP.get_interfaces().should == [] end - it "should return a list with a single interface and the loopback interface on Linux with a single interface" do + it "should return a list with a single interface and the loopback interface on Linux with a single interface without sysfs" do + File.stubs(:exist?).with('/sys/class/net').returns(false) linux_ifconfig = my_fixture_read("linux_ifconfig_all_with_single_interface") Facter::Util::IP.stubs(:get_all_interface_output).returns(linux_ifconfig) - Facter::Util::IP.get_interfaces().should == ["eth0", "lo"] + Facter::Util::IP.get_interfaces().should =~ ["eth0", "lo"] end it "should return a list two interfaces on Darwin with two interfaces" do + File.stubs(:exist?).with('/sys/class/net').returns(false) darwin_ifconfig = my_fixture_read("darwin_ifconfig_all_with_multiple_interfaces") Facter::Util::IP.stubs(:get_all_interface_output).returns(darwin_ifconfig) Facter::Util::IP.get_interfaces().should == ["lo0", "en0"] end it "should return a list two interfaces on Solaris with two interfaces multiply reporting" do + File.stubs(:exist?).with('/sys/class/net').returns(false) solaris_ifconfig = my_fixture_read("solaris_ifconfig_all_with_multiple_interfaces") Facter::Util::IP.stubs(:get_all_interface_output).returns(solaris_ifconfig) Facter::Util::IP.get_interfaces().should == ["lo0", "e1000g0"] end it "should return a list of six interfaces on a GNU/kFreeBSD with six interfaces" do + File.stubs(:exist?).with('/sys/class/net').returns(false) kfreebsd_ifconfig = my_fixture_read("debian_kfreebsd_ifconfig") Facter::Util::IP.stubs(:get_all_interface_output).returns(kfreebsd_ifconfig) Facter::Util::IP.get_interfaces().should == ["em0", "em1", "bge0", "bge1", "lo0", "vlan0"] end it "should return a list of only connected interfaces on Windows" do + File.stubs(:exist?).with('/sys/class/net').returns(false) Facter.fact(:kernel).stubs(:value).returns("windows") windows_netsh = my_fixture_read("windows_netsh_all_interfaces") Facter::Util::IP.stubs(:get_all_interface_output).returns(windows_netsh) @@ -121,6 +127,7 @@ end it "should return all interfaces correctly on OS X" do + File.stubs(:exist?).with('/sys/class/net').returns(false) ifconfig_interface = my_fixture_read("Mac_OS_X_10.5.5_ifconfig") Facter::Util::IP.expects(:get_all_interface_output).returns(ifconfig_interface) From f2d04f8d8df0797968ef814d727032051858a1f3 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Wed, 30 Jan 2013 14:59:10 -0800 Subject: [PATCH 1139/3753] Use simpler DSL for declaring external commands. When a fact is simply the result of running an external command, a block isn't needed - just the command string. This changes a bunch of facts to use that shorter, simpler form as an example to those who follow about how they should be declared. Signed-off-by: Daniel Pittman Conflicts: lib/facter/physicalprocessorcount.rb lib/facter/processor.rb --- lib/facter/hostname.rb | 4 +--- lib/facter/lsbdistcodename.rb | 4 +--- lib/facter/lsbdistid.rb | 4 +--- lib/facter/lsbdistrelease.rb | 4 +--- lib/facter/lsbrelease.rb | 4 +--- lib/facter/physicalprocessorcount.rb | 14 ++++++-------- lib/facter/processor.rb | 16 ++++------------ lib/facter/util/manufacturer.rb | 8 ++------ 8 files changed, 17 insertions(+), 41 deletions(-) diff --git a/lib/facter/hostname.rb b/lib/facter/hostname.rb index 2371ec8796..1f80fb3091 100644 --- a/lib/facter/hostname.rb +++ b/lib/facter/hostname.rb @@ -27,7 +27,5 @@ Facter.add(:hostname) do confine :kernel => :darwin, :kernelrelease => "R7" - setcode do - Facter::Util::Resolution.exec('/usr/sbin/scutil --get LocalHostName') - end + setcode '/usr/sbin/scutil --get LocalHostName' end diff --git a/lib/facter/lsbdistcodename.rb b/lib/facter/lsbdistcodename.rb index 0c0f5d9131..01ec03ecc9 100644 --- a/lib/facter/lsbdistcodename.rb +++ b/lib/facter/lsbdistcodename.rb @@ -12,7 +12,5 @@ Facter.add(:lsbdistcodename) do confine :kernel => [ :linux, :"gnu/kfreebsd" ] - setcode do - Facter::Util::Resolution.exec('lsb_release -c -s 2>/dev/null') - end + setcode 'lsb_release -c -s 2>/dev/null' end diff --git a/lib/facter/lsbdistid.rb b/lib/facter/lsbdistid.rb index 065ef9b2b3..79290fb6fc 100644 --- a/lib/facter/lsbdistid.rb +++ b/lib/facter/lsbdistid.rb @@ -12,7 +12,5 @@ Facter.add(:lsbdistid) do confine :kernel => [ :linux, :"gnu/kfreebsd" ] - setcode do - Facter::Util::Resolution.exec('lsb_release -i -s 2>/dev/null') - end + setcode 'lsb_release -i -s 2>/dev/null' end diff --git a/lib/facter/lsbdistrelease.rb b/lib/facter/lsbdistrelease.rb index ae5a9b22ed..95e17eb61a 100644 --- a/lib/facter/lsbdistrelease.rb +++ b/lib/facter/lsbdistrelease.rb @@ -12,7 +12,5 @@ Facter.add(:lsbdistrelease) do confine :kernel => [ :linux, :"gnu/kfreebsd" ] - setcode do - Facter::Util::Resolution.exec('lsb_release -r -s 2>/dev/null') - end + setcode 'lsb_release -r -s 2>/dev/null' end diff --git a/lib/facter/lsbrelease.rb b/lib/facter/lsbrelease.rb index a1728bb1c4..9fc4adf161 100644 --- a/lib/facter/lsbrelease.rb +++ b/lib/facter/lsbrelease.rb @@ -12,7 +12,5 @@ Facter.add(:lsbrelease) do confine :kernel => [ :linux, :"gnu/kfreebsd" ] - setcode do - Facter::Util::Resolution.exec('lsb_release -v -s 2>/dev/null') - end + setcode 'lsb_release -v -s 2>/dev/null' end diff --git a/lib/facter/physicalprocessorcount.rb b/lib/facter/physicalprocessorcount.rb index dbfa35be31..274ecacbb9 100644 --- a/lib/facter/physicalprocessorcount.rb +++ b/lib/facter/physicalprocessorcount.rb @@ -66,19 +66,17 @@ Facter.add('physicalprocessorcount') do confine :kernel => :sunos + # (#16526) The -p flag was not added until Solaris 8. Our intent is to + # split the kernel release fact value on the dot, take the second element, + # and disable the -p flag for values < 8 when. setcode do - # (#16526) The -p flag was not added until Solaris 8. Our intent is to - # split the kernel release fact value on the dot, take the second element, - # and disable the -p flag for values < 8 when. kernelrelease = Facter.value(:kernelrelease) (major_version, minor_version) = kernelrelease.split(".").map { |str| str.to_i } - cmd = "/usr/sbin/psrinfo" - result = nil if (major_version > 5) or (major_version == 5 and minor_version >= 8) then - result = Facter::Util::Resolution.exec("#{cmd} -p") + Facter::Util::Resolution.exec("/usr/sbin/psrinfo -p") else - output = Facter::Util::Resolution.exec(cmd) - result = output.split("\n").length.to_s + output = Facter::Util::Resolution.exec("/usr/sbin/psrinfo") + output.split("\n").length.to_s end end end diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb index 080b8c1f27..fced211cd7 100644 --- a/lib/facter/processor.rb +++ b/lib/facter/processor.rb @@ -90,16 +90,12 @@ Facter.add("Processor") do confine :kernel => :openbsd - setcode do - Facter::Util::Resolution.exec("uname -p") - end + setcode "uname -p" end Facter.add("ProcessorCount") do confine :kernel => :Darwin - setcode do - Facter::Util::Resolution.exec("sysctl -n hw.ncpu") - end + setcode "sysctl -n hw.ncpu" end if Facter.value(:kernel) == "windows" @@ -142,16 +138,12 @@ Facter.add("Processor") do confine :kernel => [:dragonfly,:freebsd] - setcode do - Facter::Util::Resolution.exec("sysctl -n hw.model") - end + setcode "sysctl -n hw.model" end Facter.add("ProcessorCount") do confine :kernel => [:dragonfly,:freebsd,:openbsd] - setcode do - Facter::Util::Resolution.exec("sysctl -n hw.ncpu") - end + setcode "sysctl -n hw.ncpu" end Facter.add("ProcessorCount") do diff --git a/lib/facter/util/manufacturer.rb b/lib/facter/util/manufacturer.rb index ecaa97febd..5da7a917fe 100644 --- a/lib/facter/util/manufacturer.rb +++ b/lib/facter/util/manufacturer.rb @@ -54,9 +54,7 @@ def self.sysctl_find_system_info(name) name.each do |sysctlkey,facterkey| Facter.add(facterkey) do confine :kernel => [:openbsd, :darwin] - setcode do - Facter::Util::Resolution.exec("sysctl -n #{sysctlkey} 2>/dev/null") - end + setcode "sysctl -n #{sysctlkey} 2>/dev/null" end end end @@ -80,9 +78,7 @@ def self.prtdiag_sparc_find_system_info() end Facter.add('serialnumber') do - setcode do - Facter::Util::Resolution.exec("/usr/sbin/sneep") - end + setcode "/usr/sbin/sneep" end end From df71d88b3b58779e68fd0c053e38addec5572971 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Wed, 30 Jan 2013 15:00:06 -0800 Subject: [PATCH 1140/3753] Avoid using cat to read files. ...because, seriously, UUOC is no laughing matter. Don't condone cat abuse. Signed-off-by: Daniel Pittman Conflicts: lib/facter/operatingsystemrelease.rb --- lib/facter/operatingsystemrelease.rb | 12 ++++----- lib/facter/physicalprocessorcount.rb | 7 +++--- lib/facter/util/uptime.rb | 8 ++++-- lib/facter/util/virtual.rb | 6 ++--- lib/facter/virtual.rb | 3 +-- spec/unit/physicalprocessorcount_spec.rb | 10 ++++---- spec/unit/util/uptime_spec.rb | 3 +-- spec/unit/util/virtual_spec.rb | 31 ++++++++++++++++++++++-- 8 files changed, 54 insertions(+), 26 deletions(-) diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index 29a0e05faa..ac93c63427 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -50,7 +50,7 @@ Facter.add(:operatingsystemrelease) do confine :operatingsystem => %w{Debian} setcode do - release = Facter::Util::Resolution.exec('cat /etc/debian_version') + release = File.read('/etc/debian_version') end end @@ -70,7 +70,7 @@ Facter.add(:operatingsystemrelease) do confine :operatingsystem => %w{SLES SLED OpenSuSE} setcode do - releasefile = Facter::Util::Resolution.exec('cat /etc/SuSE-release') + releasefile = File.read('/etc/SuSE-release') if releasefile =~ /^VERSION\s*=\s*(\d+)/ releasemajor = $1 if releasefile =~ /^PATCHLEVEL\s*=\s*(\d+)/ @@ -90,7 +90,7 @@ Facter.add(:operatingsystemrelease) do confine :operatingsystem => %w{Slackware} setcode do - release = Facter::Util::Resolution.exec('cat /etc/slackware-version') + release = File.read('/etc/slackware-version') if release =~ /Slackware ([0-9.]+)/ $1 end @@ -100,7 +100,7 @@ Facter.add(:operatingsystemrelease) do confine :operatingsystem => %w{Mageia} setcode do - release = Facter::Util::Resolution.exec('cat /etc/mageia-release') + release = File.read('/etc/mageia-release') if release =~ /Mageia release ([0-9.]+)/ $1 end @@ -110,7 +110,7 @@ Facter.add(:operatingsystemrelease) do confine :operatingsystem => %w{Bluewhite64} setcode do - releasefile = Facter::Util::Resolution.exec('cat /etc/bluewhite64-version') + releasefile = File.read('/etc/bluewhite64-version') if releasefile =~ /^\s*\w+\s+(\d+)\.(\d+)/ $1 + "." + $2 else @@ -132,7 +132,7 @@ Facter.add(:operatingsystemrelease) do confine :operatingsystem => %w{Slamd64} setcode do - releasefile = Facter::Util::Resolution.exec('cat /etc/slamd64-version') + releasefile = File.read('/etc/slamd64-version') if releasefile =~ /^\s*\w+\s+(\d+)\.(\d+)/ $1 + "." + $2 else diff --git a/lib/facter/physicalprocessorcount.rb b/lib/facter/physicalprocessorcount.rb index 274ecacbb9..a9bba11569 100644 --- a/lib/facter/physicalprocessorcount.rb +++ b/lib/facter/physicalprocessorcount.rb @@ -39,7 +39,7 @@ lookup_pattern = "#{sysfs_cpu_directory}" + "/cpu*/topology/physical_package_id" - Dir.glob(lookup_pattern).collect { |f| Facter::Util::Resolution.exec("cat #{f}")}.uniq.size + Dir.glob(lookup_pattern).collect {|f| File.read(f) }.uniq.size else # @@ -48,9 +48,8 @@ # We assume that /proc/cpuinfo has what we need and is so then we need # to make sure that we only count unique entries ... # - str = Facter::Util::Resolution.exec("grep 'physical.\\+:' /proc/cpuinfo") - - if str then str.scan(/\d+/).uniq.size; end + n = File.read('/proc/cpuinfo').split(/\n/).grep(/^physical id/).count + n > 0 ? n : nil end end end diff --git a/lib/facter/util/uptime.rb b/lib/facter/util/uptime.rb index 2fab9f46a3..cdfaeef205 100644 --- a/lib/facter/util/uptime.rb +++ b/lib/facter/util/uptime.rb @@ -20,8 +20,12 @@ def self.get_uptime_seconds_win private def self.uptime_proc_uptime - if output = Facter::Util::Resolution.exec("/bin/cat #{uptime_file} 2>/dev/null") - output.chomp.split(" ").first.to_i + begin + if output = File.read(uptime_file) + output.chomp.split(" ").first.to_i + end + rescue Errno::ENOENT + nil end end diff --git a/lib/facter/util/virtual.rb b/lib/facter/util/virtual.rb index 798432ba74..8f02b628fb 100644 --- a/lib/facter/util/virtual.rb +++ b/lib/facter/util/virtual.rb @@ -23,11 +23,11 @@ def self.openvz_type return false unless self.openvz? return false unless FileTest.exists?( '/proc/self/status' ) - envid = Facter::Util::Resolution.exec( 'grep "envID" /proc/self/status' ) + envid = File.read('/proc/self/status') if envid =~ /^envID:\s+0$/i - return 'openvzhn' + return 'openvzhn' elsif envid =~ /^envID:\s+(\d+)$/i - return 'openvzve' + return 'openvzve' end end diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index f365c9a87e..3e3d4e1f09 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -48,9 +48,8 @@ Facter.add("virtual") do confine :kernel => %w{Linux FreeBSD OpenBSD SunOS HP-UX GNU/kFreeBSD} - result = "physical" - setcode do + result = "physical" if Facter.value(:kernel) == "SunOS" and Facter::Util::Virtual.zone? result = "zone" diff --git a/spec/unit/physicalprocessorcount_spec.rb b/spec/unit/physicalprocessorcount_spec.rb index a36eed47f9..26afa6e039 100755 --- a/spec/unit/physicalprocessorcount_spec.rb +++ b/spec/unit/physicalprocessorcount_spec.rb @@ -12,7 +12,7 @@ it "should return one physical CPU" do Dir.stubs(:glob).with("/sys/devices/system/cpu/cpu*/topology/physical_package_id").returns(["/sys/devices/system/cpu/cpu0/topology/physical_package_id"]) - Facter::Util::Resolution.stubs(:exec).with("cat /sys/devices/system/cpu/cpu0/topology/physical_package_id").returns("0") + File.stubs(:read).with("/sys/devices/system/cpu/cpu0/topology/physical_package_id").returns("0") Facter.fact(:physicalprocessorcount).value.should == 1 end @@ -25,10 +25,10 @@ /sys/devices/system/cpu/cpu3/topology/physical_package_id }) - Facter::Util::Resolution.stubs(:exec).with("cat /sys/devices/system/cpu/cpu0/topology/physical_package_id").returns("0") - Facter::Util::Resolution.stubs(:exec).with("cat /sys/devices/system/cpu/cpu1/topology/physical_package_id").returns("1") - Facter::Util::Resolution.stubs(:exec).with("cat /sys/devices/system/cpu/cpu2/topology/physical_package_id").returns("2") - Facter::Util::Resolution.stubs(:exec).with("cat /sys/devices/system/cpu/cpu3/topology/physical_package_id").returns("3") + File.stubs(:read).with("/sys/devices/system/cpu/cpu0/topology/physical_package_id").returns("0") + File.stubs(:read).with("/sys/devices/system/cpu/cpu1/topology/physical_package_id").returns("1") + File.stubs(:read).with("/sys/devices/system/cpu/cpu2/topology/physical_package_id").returns("2") + File.stubs(:read).with("/sys/devices/system/cpu/cpu3/topology/physical_package_id").returns("3") Facter.fact(:physicalprocessorcount).value.should == 4 end diff --git a/spec/unit/util/uptime_spec.rb b/spec/unit/util/uptime_spec.rb index e7a152948c..c1b84f737a 100755 --- a/spec/unit/util/uptime_spec.rb +++ b/spec/unit/util/uptime_spec.rb @@ -8,8 +8,7 @@ describe ".get_uptime_seconds_unix", :unless => Facter.value(:operatingsystem) == 'windows' do describe "when /proc/uptime is available" do before do - uptime_file = my_fixture("ubuntu_proc_uptime") - Facter::Util::Uptime.stubs(:uptime_file).returns("\"#{uptime_file}\"") + Facter::Util::Uptime.stubs(:uptime_file).returns(my_fixture("ubuntu_proc_uptime")) end it "should return the uptime in seconds as an integer" do diff --git a/spec/unit/util/virtual_spec.rb b/spec/unit/util/virtual_spec.rb index ab3f72f6d9..9e4d78d823 100755 --- a/spec/unit/util/virtual_spec.rb +++ b/spec/unit/util/virtual_spec.rb @@ -30,20 +30,47 @@ it "should identify openvzhn when /proc/self/status has envID of 0" do Facter::Util::Virtual.stubs(:openvz?).returns(true) FileTest.stubs(:exists?).with("/proc/self/status").returns(true) - Facter::Util::Resolution.stubs(:exec).with('grep "envID" /proc/self/status').returns("envID: 0") + File.stubs(:read).with('/proc/self/status').returns < Date: Fri, 20 Jul 2012 08:22:22 -0700 Subject: [PATCH 1141/3753] Trivial whitespace cleanup. Literally, one misplaced space gone. Signed-off-by: Daniel Pittman --- lib/facter/kernelrelease.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/kernelrelease.rb b/lib/facter/kernelrelease.rb index 182cfe8420..8bbc01ea0b 100644 --- a/lib/facter/kernelrelease.rb +++ b/lib/facter/kernelrelease.rb @@ -23,7 +23,7 @@ Facter.add(:kernelrelease) do confine :kernel => "hp-ux" setcode do - version = Facter::Util::Resolution.exec('uname -r') + version = Facter::Util::Resolution.exec('uname -r') version[2..-1] end end From 18e1ceff0ace5d5ecb89ada7d92da6ddc07c5ad4 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Wed, 30 Jan 2013 15:13:52 -0800 Subject: [PATCH 1142/3753] Fixup failures after addressing merge conflicts --- spec/unit/physicalprocessorcount_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/unit/physicalprocessorcount_spec.rb b/spec/unit/physicalprocessorcount_spec.rb index 26afa6e039..fb2ec74c8e 100755 --- a/spec/unit/physicalprocessorcount_spec.rb +++ b/spec/unit/physicalprocessorcount_spec.rb @@ -37,6 +37,7 @@ describe "on windows" do it "should return 4 physical CPUs" do Facter.fact(:kernel).stubs(:value).returns("windows") + Facter.fact(:kernelrelease).stubs(:value).returns("6.1.7601") require 'facter/util/wmi' ole = stub 'WIN32OLE' From bde7cb648807f5c86f65c51fe3b4fe532d15bc64 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Thu, 31 Jan 2013 11:55:01 -0800 Subject: [PATCH 1143/3753] Maint: Ensure we don't read from /sys/class/net Previously, the ip_spec test relied on the /sys/class/net file not existing, so that it would flow into the HP-UX logic. But when the test is executed on linux, the file exists, so the test short-circuits and never evaluate the HP-UX part. Since the facter tests are so fragile, I `expect` more than `stub` to ensure the code takes the route I'm trying to test. --- spec/unit/util/ip_spec.rb | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/spec/unit/util/ip_spec.rb b/spec/unit/util/ip_spec.rb index 0f236fa160..3d95ee5cd7 100755 --- a/spec/unit/util/ip_spec.rb +++ b/spec/unit/util/ip_spec.rb @@ -335,9 +335,10 @@ def self.hpux_examples netstat_in_fixture, lanscan_fixture = example it "should return a list three interfaces on #{description}" do - Facter.stubs(:value).with(:kernel).returns("HP-UX") - Facter::Util::IP.stubs(:hpux_netstat_in).returns(netstat_in_fixture) - Facter::Util::IP.get_interfaces().should == array_of_expected_ifs + Facter.expects(:value).with(:kernel).at_least_once.returns("HP-UX") + File.expects(:exist?).with('/sys/class/net').returns(false) + Facter::Util::IP.expects(:hpux_netstat_in).returns(netstat_in_fixture) + Facter::Util::IP.get_interfaces.should == array_of_expected_ifs end array_of_expected_ifs.each_with_index do |nic, i| @@ -349,31 +350,31 @@ def self.hpux_examples # (#17808) These tests fail because MTU facts haven't been implemented for HP-UX. #it "should return MTU #{expected_mtu} on #{nic} for #{description} example" do - # Facter.stubs(:value).with(:kernel).returns("HP-UX") - # Facter::Util::IP.stubs(:hpux_netstat_in).returns(netstat_in_fixture) - # Facter::Util::IP.stubs(:hpux_lanscan).returns(lanscan_fixture) - # Facter::Util::IP.stubs(:hpux_ifconfig_interface).with(nic).returns(ifconfig_fixture) + # Facter.expects(:value).with(:kernel).at_least_once.returns("HP-UX") + # Facter::Util::IP.expects(:hpux_netstat_in).returns(netstat_in_fixture) + # Facter::Util::IP.expects(:hpux_lanscan).returns(lanscan_fixture) + # Facter::Util::IP.expects(:hpux_ifconfig_interface).with(nic).returns(ifconfig_fixture) # Facter::Util::IP.get_interface_value(nic, "mtu").should == expected_mtu #end it "should return IP #{expected_ip} on #{nic} for #{description} example" do - Facter.stubs(:value).with(:kernel).returns("HP-UX") - Facter::Util::IP.stubs(:hpux_lanscan).returns(lanscan_fixture) - Facter::Util::IP.stubs(:hpux_ifconfig_interface).with(nic).returns(ifconfig_fixture) + Facter.expects(:value).with(:kernel).at_least_once.returns("HP-UX") + Facter::Util::IP.expects(:hpux_lanscan).returns(lanscan_fixture) + Facter::Util::IP.expects(:hpux_ifconfig_interface).with(nic).returns(ifconfig_fixture) Facter::Util::IP.get_interface_value(nic, "ipaddress").should == expected_ip end it "should return netmask #{expected_netmask} on #{nic} for #{description} example" do - Facter.stubs(:value).with(:kernel).returns("HP-UX") - Facter::Util::IP.stubs(:hpux_lanscan).returns(lanscan_fixture) - Facter::Util::IP.stubs(:hpux_ifconfig_interface).with(nic).returns(ifconfig_fixture) + Facter.expects(:value).with(:kernel).at_least_once.returns("HP-UX") + Facter::Util::IP.expects(:hpux_lanscan).returns(lanscan_fixture) + Facter::Util::IP.expects(:hpux_ifconfig_interface).with(nic).returns(ifconfig_fixture) Facter::Util::IP.get_interface_value(nic, "netmask").should == expected_netmask end it "should return MAC address #{expected_mac} on #{nic} for #{description} example" do - Facter.stubs(:value).with(:kernel).returns("HP-UX") - Facter::Util::IP.stubs(:hpux_lanscan).returns(lanscan_fixture) - Facter::Util::IP.stubs(:hpux_ifconfig_interface).with(nic).returns(ifconfig_fixture) + Facter.expects(:value).with(:kernel).at_least_once.returns("HP-UX") + Facter::Util::IP.expects(:hpux_lanscan).returns(lanscan_fixture) + Facter::Util::IP.expects(:hpux_ifconfig_interface).with(nic).returns(ifconfig_fixture) Facter::Util::IP.get_interface_value(nic, "macaddress").should == expected_mac end end From 0424afc9993237e7d01677c7db08aae6b4339e4d Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Thu, 31 Jan 2013 16:09:47 -0800 Subject: [PATCH 1144/3753] (maint) Fix physicalprocessorcount with multiple cores Without this patch applied the physicalprocessorcount fact reports the wrong number of processors. Instead of reporting the number of physical processing units, it reports the number of cores. This patch addresses the problem by changing the behavior to count only the number of unique processor identities as suggested by Stefan Schulte. We didn't catch this issue while rebasing GH-266 in GH-388 because this behavior had no specified expectations. This patch adds an example of the expected behavior when there are multiple cores per processor. --- lib/facter/physicalprocessorcount.rb | 2 +- .../unit/physicalprocessorcount/amd64dual | 57 +++++++++++++++++++ spec/unit/physicalprocessorcount_spec.rb | 46 ++++++++++----- 3 files changed, 90 insertions(+), 15 deletions(-) create mode 100644 spec/fixtures/unit/physicalprocessorcount/amd64dual diff --git a/lib/facter/physicalprocessorcount.rb b/lib/facter/physicalprocessorcount.rb index a9bba11569..a8ff68605c 100644 --- a/lib/facter/physicalprocessorcount.rb +++ b/lib/facter/physicalprocessorcount.rb @@ -48,7 +48,7 @@ # We assume that /proc/cpuinfo has what we need and is so then we need # to make sure that we only count unique entries ... # - n = File.read('/proc/cpuinfo').split(/\n/).grep(/^physical id/).count + n = File.read('/proc/cpuinfo').split(/\n/).grep(/^physical id/).uniq.count n > 0 ? n : nil end end diff --git a/spec/fixtures/unit/physicalprocessorcount/amd64dual b/spec/fixtures/unit/physicalprocessorcount/amd64dual new file mode 100644 index 0000000000..101e5b5c76 --- /dev/null +++ b/spec/fixtures/unit/physicalprocessorcount/amd64dual @@ -0,0 +1,57 @@ +processor : 0 +vendor_id : GenuineIntel +cpu family : 6 +model : 23 +model name : Intel(R) Core(TM)2 Duo CPU P8700 @ 2.53GHz +stepping : 10 +cpu MHz : 689.302 +cache size : 6144 KB +physical id : 0 +siblings : 2 +core id : 0 +cpu cores : 2 +apicid : 0 +initial apicid : 0 +fdiv_bug : no +hlt_bug : no +f00f_bug : no +coma_bug : no +fpu : yes +fpu_exception : yes +cpuid level : 5 +wp : yes +flags : fpu vme de pse tsc msr mce cx8 apic mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht constant_tsc pni ssse3 +bogomips : 1378.60 +clflush size : 64 +cache_alignment : 64 +address sizes : 36 bits physical, 48 bits virtual +power management: + +processor : 1 +vendor_id : GenuineIntel +cpu family : 6 +model : 23 +model name : Intel(R) Core(TM)2 Duo CPU P8700 @ 2.53GHz +stepping : 10 +cpu MHz : 689.302 +cache size : 6144 KB +physical id : 0 +siblings : 2 +core id : 1 +cpu cores : 2 +apicid : 1 +initial apicid : 1 +fdiv_bug : no +hlt_bug : no +f00f_bug : no +coma_bug : no +fpu : yes +fpu_exception : yes +cpuid level : 5 +wp : yes +flags : fpu vme de pse tsc msr mce cx8 apic mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht constant_tsc pni ssse3 +bogomips : 1687.45 +clflush size : 64 +cache_alignment : 64 +address sizes : 36 bits physical, 48 bits virtual +power management: diff --git a/spec/unit/physicalprocessorcount_spec.rb b/spec/unit/physicalprocessorcount_spec.rb index fb2ec74c8e..e9fa952484 100755 --- a/spec/unit/physicalprocessorcount_spec.rb +++ b/spec/unit/physicalprocessorcount_spec.rb @@ -7,30 +7,48 @@ describe "on linux" do before :each do Facter.fact(:kernel).stubs(:value).returns("Linux") - File.stubs(:exists?).with('/sys/devices/system/cpu').returns(true) end + context "with /sys/devices/system/cpu and not /proc/cpuinfo" do + before :each do + File.stubs(:exists?).with('/sys/devices/system/cpu').returns(true) + end - it "should return one physical CPU" do - Dir.stubs(:glob).with("/sys/devices/system/cpu/cpu*/topology/physical_package_id").returns(["/sys/devices/system/cpu/cpu0/topology/physical_package_id"]) - File.stubs(:read).with("/sys/devices/system/cpu/cpu0/topology/physical_package_id").returns("0") + it "should return one physical CPU" do + Dir.stubs(:glob).with("/sys/devices/system/cpu/cpu*/topology/physical_package_id").returns(["/sys/devices/system/cpu/cpu0/topology/physical_package_id"]) + File.stubs(:read).with("/sys/devices/system/cpu/cpu0/topology/physical_package_id").returns("0") - Facter.fact(:physicalprocessorcount).value.should == 1 - end + Facter.fact(:physicalprocessorcount).value.should == 1 + end - it "should return four physical CPUs" do - Dir.stubs(:glob).with("/sys/devices/system/cpu/cpu*/topology/physical_package_id").returns(%w{ + it "should return four physical CPUs" do + Dir.stubs(:glob).with("/sys/devices/system/cpu/cpu*/topology/physical_package_id").returns(%w{ /sys/devices/system/cpu/cpu0/topology/physical_package_id /sys/devices/system/cpu/cpu1/topology/physical_package_id /sys/devices/system/cpu/cpu2/topology/physical_package_id /sys/devices/system/cpu/cpu3/topology/physical_package_id - }) + }) - File.stubs(:read).with("/sys/devices/system/cpu/cpu0/topology/physical_package_id").returns("0") - File.stubs(:read).with("/sys/devices/system/cpu/cpu1/topology/physical_package_id").returns("1") - File.stubs(:read).with("/sys/devices/system/cpu/cpu2/topology/physical_package_id").returns("2") - File.stubs(:read).with("/sys/devices/system/cpu/cpu3/topology/physical_package_id").returns("3") + File.stubs(:read).with("/sys/devices/system/cpu/cpu0/topology/physical_package_id").returns("0") + File.stubs(:read).with("/sys/devices/system/cpu/cpu1/topology/physical_package_id").returns("1") + File.stubs(:read).with("/sys/devices/system/cpu/cpu2/topology/physical_package_id").returns("2") + File.stubs(:read).with("/sys/devices/system/cpu/cpu3/topology/physical_package_id").returns("3") - Facter.fact(:physicalprocessorcount).value.should == 4 + Facter.fact(:physicalprocessorcount).value.should == 4 + end + end + + context "with /proc/cpuinfo and not /sys/devices/system/cpu" do + before :all do + @cpuinfo = my_fixture_read('amd64dual') + end + before :each do + File.stubs(:exists?).with('/sys/devices/system/cpu').returns(false) + end + + it "should return 1 physical CPU when there are multiple cores" do + File.stubs(:read).with("/proc/cpuinfo").returns(@cpuinfo) + Facter.fact(:physicalprocessorcount).value.should == 1 + end end end From 9c45c94a1826ad79297d059a1e7926460b503bbf Mon Sep 17 00:00:00 2001 From: Stefan Schulte Date: Fri, 1 Feb 2013 23:14:53 +0100 Subject: [PATCH 1145/3753] Add specs for physicalprocessorcount Add test to check if multicore processors are handled correctly when we only want to count physical processors --- .../unit/physicalprocessorcount/two_multicore | 28 ++++++++++ .../physicalprocessorcount/two_singlecore | 28 ++++++++++ spec/unit/physicalprocessorcount_spec.rb | 54 +++++++++++++++++-- 3 files changed, 107 insertions(+), 3 deletions(-) create mode 100644 spec/fixtures/unit/physicalprocessorcount/two_multicore create mode 100644 spec/fixtures/unit/physicalprocessorcount/two_singlecore diff --git a/spec/fixtures/unit/physicalprocessorcount/two_multicore b/spec/fixtures/unit/physicalprocessorcount/two_multicore new file mode 100644 index 0000000000..86d3e4567e --- /dev/null +++ b/spec/fixtures/unit/physicalprocessorcount/two_multicore @@ -0,0 +1,28 @@ +processor : 0 +model name : Intel(R) Xeon(R) CPU 5160 @ 3.00GHz +cache size : 4096 KB +physical id : 0 +siblings : 2 +core id : 0 +cpu cores : 2 +processor : 1 +model name : Intel(R) Xeon(R) CPU 5160 @ 3.00GHz +cache size : 4096 KB +physical id : 0 +siblings : 2 +core id : 1 +cpu cores : 2 +processor : 2 +model name : Intel(R) Xeon(R) CPU 5160 @ 3.00GHz +cache size : 4096 KB +physical id : 3 +siblings : 2 +core id : 0 +cpu cores : 2 +processor : 3 +model name : Intel(R) Xeon(R) CPU 5160 @ 3.00GHz +cache size : 4096 KB +physical id : 3 +siblings : 2 +core id : 1 +cpu cores : 2 diff --git a/spec/fixtures/unit/physicalprocessorcount/two_singlecore b/spec/fixtures/unit/physicalprocessorcount/two_singlecore new file mode 100644 index 0000000000..319a0fd66b --- /dev/null +++ b/spec/fixtures/unit/physicalprocessorcount/two_singlecore @@ -0,0 +1,28 @@ +processor : 0 +model name : Intel(R) Xeon(TM) CPU 3.60GHz +cache size : 1024 KB +physical id : 0 +siblings : 2 +core id : 0 +cpu cores : 1 +processor : 1 +model name : Intel(R) Xeon(TM) CPU 3.60GHz +cache size : 1024 KB +physical id : 3 +siblings : 2 +core id : 0 +cpu cores : 1 +processor : 2 +model name : Intel(R) Xeon(TM) CPU 3.60GHz +cache size : 1024 KB +physical id : 0 +siblings : 2 +core id : 0 +cpu cores : 1 +processor : 3 +model name : Intel(R) Xeon(TM) CPU 3.60GHz +cache size : 1024 KB +physical id : 3 +siblings : 2 +core id : 0 +cpu cores : 1 diff --git a/spec/unit/physicalprocessorcount_spec.rb b/spec/unit/physicalprocessorcount_spec.rb index e9fa952484..9e264fcf68 100755 --- a/spec/unit/physicalprocessorcount_spec.rb +++ b/spec/unit/physicalprocessorcount_spec.rb @@ -20,6 +20,21 @@ Facter.fact(:physicalprocessorcount).value.should == 1 end + it "should still return one physical CPU if on a multicore processor" do + Dir.expects(:glob).with("/sys/devices/system/cpu/cpu*/topology/physical_package_id").returns([ + "/sys/devices/system/cpu/cpu0/topology/physical_package_id", + "/sys/devices/system/cpu/cpu1/topology/physical_package_id", + "/sys/devices/system/cpu/cpu2/topology/physical_package_id", + "/sys/devices/system/cpu/cpu3/topology/physical_package_id" + ]) + File.expects(:read).with("/sys/devices/system/cpu/cpu0/topology/physical_package_id").returns("0") + File.expects(:read).with("/sys/devices/system/cpu/cpu1/topology/physical_package_id").returns("0") + File.expects(:read).with("/sys/devices/system/cpu/cpu2/topology/physical_package_id").returns("0") + File.expects(:read).with("/sys/devices/system/cpu/cpu3/topology/physical_package_id").returns("0") + + Facter.fact(:physicalprocessorcount).value.should == 1 + end + it "should return four physical CPUs" do Dir.stubs(:glob).with("/sys/devices/system/cpu/cpu*/topology/physical_package_id").returns(%w{ /sys/devices/system/cpu/cpu0/topology/physical_package_id @@ -35,20 +50,53 @@ Facter.fact(:physicalprocessorcount).value.should == 4 end + + it "should return four physical CPUs if on 4 multicore processors" do + Dir.expects(:glob).with("/sys/devices/system/cpu/cpu*/topology/physical_package_id").returns(%w{ + /sys/devices/system/cpu/cpu0/topology/physical_package_id + /sys/devices/system/cpu/cpu1/topology/physical_package_id + /sys/devices/system/cpu/cpu2/topology/physical_package_id + /sys/devices/system/cpu/cpu3/topology/physical_package_id + /sys/devices/system/cpu/cpu4/topology/physical_package_id + /sys/devices/system/cpu/cpu5/topology/physical_package_id + /sys/devices/system/cpu/cpu6/topology/physical_package_id + /sys/devices/system/cpu/cpu7/topology/physical_package_id + }) + File.expects(:read).with("/sys/devices/system/cpu/cpu0/topology/physical_package_id").returns("0") + File.expects(:read).with("/sys/devices/system/cpu/cpu1/topology/physical_package_id").returns("0") + File.expects(:read).with("/sys/devices/system/cpu/cpu2/topology/physical_package_id").returns("1") + File.expects(:read).with("/sys/devices/system/cpu/cpu3/topology/physical_package_id").returns("1") + File.expects(:read).with("/sys/devices/system/cpu/cpu4/topology/physical_package_id").returns("2") + File.expects(:read).with("/sys/devices/system/cpu/cpu5/topology/physical_package_id").returns("2") + File.expects(:read).with("/sys/devices/system/cpu/cpu6/topology/physical_package_id").returns("3") + File.expects(:read).with("/sys/devices/system/cpu/cpu7/topology/physical_package_id").returns("3") + + Facter.fact(:physicalprocessorcount).value.should == 4 + end end context "with /proc/cpuinfo and not /sys/devices/system/cpu" do - before :all do - @cpuinfo = my_fixture_read('amd64dual') - end before :each do File.stubs(:exists?).with('/sys/devices/system/cpu').returns(false) end it "should return 1 physical CPU when there are multiple cores" do + @cpuinfo = my_fixture_read('amd64dual') File.stubs(:read).with("/proc/cpuinfo").returns(@cpuinfo) Facter.fact(:physicalprocessorcount).value.should == 1 end + + it "should return 2 physical CPUs when there are 2 singlecore CPUs" do + @cpuinfo = my_fixture_read('two_singlecore') + File.stubs(:read).with("/proc/cpuinfo").returns(@cpuinfo) + Facter.fact(:physicalprocessorcount).value.should == 2 + end + + it "should return 2 physical CPUs when there are 2 multicore CPUs" do + @cpuinfo = my_fixture_read('two_multicore') + File.stubs(:read).with("/proc/cpuinfo").returns(@cpuinfo) + Facter.fact(:physicalprocessorcount).value.should == 2 + end end end From 891a1486c0cf765685c0a8064bd37a8b24ea0360 Mon Sep 17 00:00:00 2001 From: scepticulous Date: Mon, 28 Jan 2013 21:09:20 +0100 Subject: [PATCH 1146/3753] (#13396) Unify ifconfig usage and lookup ifconfig path to fix support for recent net-tools Without this patch applied linux systems with recent net-tools loose support for ipaddress,macaddress and other network related facts. This is due to a path change from /sbin/ifconfig to /bin/ifconfig. This patch fixes this issue by unifying the lookup and execution of ifconfig and testing where the ifconfig binary is installed. --- lib/facter/ipaddress.rb | 8 ++++---- lib/facter/ipaddress6.rb | 6 +++--- lib/facter/iphostnumber.rb | 2 +- lib/facter/macaddress.rb | 9 +++++---- lib/facter/util/ip.rb | 26 +++++++++++++++--------- lib/facter/util/netmask.rb | 8 ++++---- spec/unit/ipaddress6_spec.rb | 10 ++++++--- spec/unit/ipaddress_spec.rb | 2 +- spec/unit/macaddress_spec.rb | 12 +++++++---- spec/unit/util/ip_spec.rb | 39 ++++++++++++++++++++++++++++++++++++ 10 files changed, 89 insertions(+), 33 deletions(-) diff --git a/lib/facter/ipaddress.rb b/lib/facter/ipaddress.rb index a7d7c4d6be..ad6257c01f 100644 --- a/lib/facter/ipaddress.rb +++ b/lib/facter/ipaddress.rb @@ -28,7 +28,7 @@ confine :kernel => :linux setcode do ip = nil - if output = Facter::Util::IP.get_ifconfig + if output = Facter::Util::IP.exec_ifconfig(["2>/dev/null"]) regexp = /inet (?:addr:)?([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ if match = regexp.match(output) match[1] unless /^127/.match(match[1]) @@ -41,7 +41,7 @@ confine :kernel => %w{FreeBSD OpenBSD Darwin DragonFly} setcode do ip = nil - output = %x{/sbin/ifconfig} + output = Facter::Util::IP.exec_ifconfig output.split(/^\S/).each { |str| if str =~ /inet ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ @@ -61,7 +61,7 @@ confine :kernel => %w{NetBSD SunOS} setcode do ip = nil - output = %x{/sbin/ifconfig -a} + output = Facter::Util::IP.exec_ifconfig(["-a"]) output.split(/^\S/).each { |str| if str =~ /inet ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ @@ -81,7 +81,7 @@ confine :kernel => %w{AIX} setcode do ip = nil - output = %x{/usr/sbin/ifconfig -a} + output = Facter::Util::IP.exec_ifconfig(["-a"]) output.split(/^\S/).each { |str| if str =~ /inet ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ diff --git a/lib/facter/ipaddress6.rb b/lib/facter/ipaddress6.rb index 7d2885fb6e..1d0bd7a93d 100644 --- a/lib/facter/ipaddress6.rb +++ b/lib/facter/ipaddress6.rb @@ -38,7 +38,7 @@ def get_address_after_token(output, token, return_first=false) Facter.add(:ipaddress6) do confine :kernel => :linux setcode do - output = Facter::Util::Resolution.exec('/sbin/ifconfig 2>/dev/null') + output = Facter::Util::IP.exec_ifconfig(["2>/dev/null"]) get_address_after_token(output, 'inet6 addr:') end @@ -47,7 +47,7 @@ def get_address_after_token(output, token, return_first=false) Facter.add(:ipaddress6) do confine :kernel => %w{SunOS} setcode do - output = Facter::Util::Resolution.exec('/usr/sbin/ifconfig -a') + output = Facter::Util::IP.exec_ifconfig(["-a"]) get_address_after_token(output, 'inet6') end @@ -56,7 +56,7 @@ def get_address_after_token(output, token, return_first=false) Facter.add(:ipaddress6) do confine :kernel => %w{Darwin FreeBSD OpenBSD} setcode do - output = Facter::Util::Resolution.exec('/sbin/ifconfig -a') + output = Facter::Util::IP.exec_ifconfig(["-a"]) get_address_after_token(output, 'inet6', true) end diff --git a/lib/facter/iphostnumber.rb b/lib/facter/iphostnumber.rb index 2d22017c28..c0bd1ee8d0 100644 --- a/lib/facter/iphostnumber.rb +++ b/lib/facter/iphostnumber.rb @@ -19,7 +19,7 @@ confine :kernel => :darwin, :kernelrelease => "R6" setcode do ether = nil - output = %x{/sbin/ifconfig} + output = Facter::Util::IP.exec_ifconfig output =~ /HWaddr (\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ ether = $1 diff --git a/lib/facter/macaddress.rb b/lib/facter/macaddress.rb index 377da1badc..842ee98e29 100644 --- a/lib/facter/macaddress.rb +++ b/lib/facter/macaddress.rb @@ -26,7 +26,8 @@ confine :kernel => 'Linux' setcode do ether = [] - output = Facter::Util::Resolution.exec("/sbin/ifconfig -a 2>/dev/null") + output = Facter::Util::IP.exec_ifconfig(["-a","2>/dev/null"]) + output.each_line do |s| ether.push($1) if s =~ /(?:ether|HWaddr) ((\w{1,2}:){5,}\w{1,2})/ end @@ -38,7 +39,7 @@ confine :kernel => %w{SunOS GNU/kFreeBSD} setcode do ether = [] - output = Facter::Util::Resolution.exec("/sbin/ifconfig -a") + output = Facter::Util::IP.exec_ifconfig(["-a"]) output.each_line do |s| ether.push($1) if s =~ /(?:ether|HWaddr) ((\w{1,2}:){5,}\w{1,2})/ end @@ -62,7 +63,7 @@ confine :operatingsystem => %w{FreeBSD OpenBSD DragonFly} setcode do ether = [] - output = Facter::Util::Resolution.exec("/sbin/ifconfig") + output = Facter::Util::IP.exec_ifconfig output.each_line do |s| if s =~ /(?:ether|lladdr)\s+(\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)/ ether.push($1) @@ -82,7 +83,7 @@ setcode do ether = [] ip = nil - output = %x{/usr/sbin/ifconfig -a} + output = Facter::Util::IP.exec_ifconfig(["-a"]) output.each_line do |str| if str =~ /([a-z]+\d+): flags=/ devname = $1 diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index dfb4e8053d..11683ef16b 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -77,9 +77,9 @@ def self.get_interfaces def self.get_all_interface_output case Facter.value(:kernel) when 'Linux', 'OpenBSD', 'NetBSD', 'FreeBSD', 'Darwin', 'GNU/kFreeBSD', 'DragonFly' - output = %x{/sbin/ifconfig -a 2>/dev/null} + output = Facter::Util::IP.exec_ifconfig(["-a","2>/dev/null"]) when 'SunOS' - output = %x{/usr/sbin/ifconfig -a} + output = Facter::Util::IP.exec_ifconfig(["-a"]) when 'HP-UX' # (#17487)[https://projects.puppetlabs.com/issues/17487] # Handle NIC bonding where asterisks and virtual NICs are printed. @@ -96,14 +96,22 @@ def self.get_all_interface_output output end + ## - # get_ifconfig simply delegates to the ifconfig command. + # exec_ifconfig uses the ifconfig command # - # @return [String] the output of `/sbin/ifconfig 2>/dev/null` or nil + # @return [String] the output of `ifconfig #{arguments} 2>/dev/null` or nil + def self.exec_ifconfig(additional_arguments=[]) + Facter::Util::Resolution.exec("#{self.get_ifconfig} #{additional_arguments.join(' ')}") + end + ## + # get_ifconfig looks up the ifconfig binary + # + # @return [String] path to the ifconfig binary def self.get_ifconfig - Facter::Util::Resolution.exec("/sbin/ifconfig 2>/dev/null") + common_paths=["/bin/ifconfig","/sbin/ifconfig","/usr/sbin/ifconfig"] + common_paths.select{|path| File.executable?(path)}.first end - ## # hpux_netstat_in is a delegate method that allows us to stub netstat -in # without stubbing exec. @@ -125,7 +133,7 @@ def self.get_infiniband_macaddress(interface) end def self.ifconfig_interface(interface) - %x{/sbin/ifconfig #{interface} 2>/dev/null} + output = Facter::Util::IP.exec_ifconfig([interface,"2>/dev/null"]) end def self.get_single_interface_output(interface) @@ -142,7 +150,7 @@ def self.get_single_interface_output(interface) output = ifconfig_output end when 'SunOS' - output = %x{/usr/sbin/ifconfig #{interface}} + output = Facter::Util::IP.exec_ifconfig([interface]) when 'HP-UX' mac = "" ifc = hpux_ifconfig_interface(interface) @@ -154,7 +162,7 @@ def self.get_single_interface_output(interface) end def self.hpux_ifconfig_interface(interface) - Facter::Util::Resolution.exec("/usr/sbin/ifconfig #{interface}") + Facter::Util::IP.exec_ifconfig([interface]) end def self.hpux_lanscan diff --git a/lib/facter/util/netmask.rb b/lib/facter/util/netmask.rb index 36a212e09f..a7d212949b 100644 --- a/lib/facter/util/netmask.rb +++ b/lib/facter/util/netmask.rb @@ -7,25 +7,25 @@ def self.get_netmask case Facter.value(:kernel) when 'Linux' ops = { - :ifconfig => '/sbin/ifconfig 2>/dev/null', + :ifconfig_opts => ['2>/dev/null'], :regex => %r{\s+ inet\saddr: #{Facter.ipaddress} .*? Mask: (#{ipregex})}x, :munge => nil, } when 'SunOS' ops = { - :ifconfig => '/usr/sbin/ifconfig -a', + :ifconfig_opts => ['-a'], :regex => %r{\s+ inet \s #{Facter.ipaddress} \s netmask \s (\w{8})}x, :munge => Proc.new { |mask| mask.scan(/../).collect do |byte| byte.to_i(16) end.join('.') } } when 'FreeBSD','NetBSD','OpenBSD', 'Darwin', 'GNU/kFreeBSD', 'DragonFly' ops = { - :ifconfig => '/sbin/ifconfig -a', + :ifconfig_opts => ['-a'], :regex => %r{\s+ inet \s #{Facter.ipaddress} \s netmask \s 0x(\w{8})}x, :munge => Proc.new { |mask| mask.scan(/../).collect do |byte| byte.to_i(16) end.join('.') } } end - %x{#{ops[:ifconfig]}}.split(/\n/).collect do |line| + Facter::Util::IP.get_ifconfig(ops[:ifconfig_opts]).split(/\n/).collect do |line| matches = line.match(ops[:regex]) if !matches.nil? if ops[:munge].nil? diff --git a/spec/unit/ipaddress6_spec.rb b/spec/unit/ipaddress6_spec.rb index 64465eb7f5..0ec8d840e4 100755 --- a/spec/unit/ipaddress6_spec.rb +++ b/spec/unit/ipaddress6_spec.rb @@ -1,6 +1,7 @@ #! /usr/bin/env ruby require 'spec_helper' +require 'facter/util/ip' def ifconfig_fixture(filename) File.read(fixtures('ifconfig', filename)) @@ -17,7 +18,8 @@ def netsh_fixture(filename) it "should return ipaddress6 information for Darwin" do Facter::Util::Resolution.stubs(:exec).with('uname -s').returns('Darwin') - Facter::Util::Resolution.stubs(:exec).with('/sbin/ifconfig -a'). + Facter::Util::IP.stubs(:get_ifconfig).returns("/sbin/ifconfig") + Facter::Util::IP.stubs(:exec_ifconfig).with(["-a"]). returns(ifconfig_fixture('darwin_ifconfig_all_with_multiple_interfaces')) Facter.value(:ipaddress6).should == "2610:10:20:209:223:32ff:fed5:ee34" @@ -25,7 +27,8 @@ def netsh_fixture(filename) it "should return ipaddress6 information for Linux" do Facter::Util::Resolution.stubs(:exec).with('uname -s').returns('Linux') - Facter::Util::Resolution.stubs(:exec).with('/sbin/ifconfig 2>/dev/null'). + Facter::Util::IP.stubs(:get_ifconfig).returns("/sbin/ifconfig") + Facter::Util::IP.stubs(:exec_ifconfig).with(["2>/dev/null"]). returns(ifconfig_fixture('linux_ifconfig_all_with_multiple_interfaces')) Facter.value(:ipaddress6).should == "2610:10:20:209:212:3fff:febe:2201" @@ -33,7 +36,8 @@ def netsh_fixture(filename) it "should return ipaddress6 information for Solaris" do Facter::Util::Resolution.stubs(:exec).with('uname -s').returns('SunOS') - Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/ifconfig -a'). + Facter::Util::IP.stubs(:get_ifconfig).returns("/usr/sbin/ifconfig") + Facter::Util::IP.stubs(:exec_ifconfig).with(["-a"]). returns(ifconfig_fixture('sunos_ifconfig_all_with_multiple_interfaces')) Facter.value(:ipaddress6).should == "2610:10:20:209:203:baff:fe27:a7c" diff --git a/spec/unit/ipaddress_spec.rb b/spec/unit/ipaddress_spec.rb index f746e1012e..fccae8a509 100755 --- a/spec/unit/ipaddress_spec.rb +++ b/spec/unit/ipaddress_spec.rb @@ -5,7 +5,7 @@ shared_examples_for "ifconfig output" do |platform, address, fixture| it "correctly on #{platform}" do - Facter::Util::IP.stubs(:get_ifconfig).returns(my_fixture_read(fixture)) + Facter::Util::IP.stubs(:exec_ifconfig).returns(my_fixture_read(fixture)) subject.value.should == address end end diff --git a/spec/unit/macaddress_spec.rb b/spec/unit/macaddress_spec.rb index 1b703e3909..22784efe1e 100755 --- a/spec/unit/macaddress_spec.rb +++ b/spec/unit/macaddress_spec.rb @@ -1,6 +1,8 @@ #! /usr/bin/env ruby require 'spec_helper' +require 'facter/util/ip' + def ifconfig_fixture(filename) File.read(fixtures('ifconfig', filename)) @@ -19,17 +21,18 @@ def netsh_fixture(filename) before :each do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter.fact(:operatingsystem).stubs(:value).returns("Linux") + Facter::Util::IP.stubs(:get_ifconfig).returns("/sbin/ifconfig") end it "should return the macaddress of the first interface" do - Facter::Util::Resolution.stubs(:exec).with('/sbin/ifconfig -a 2>/dev/null'). + Facter::Util::IP.stubs(:exec_ifconfig).with(["-a","2>/dev/null"]). returns(ifconfig_fixture('linux_ifconfig_all_with_multiple_interfaces')) Facter.value(:macaddress).should == "00:12:3f:be:22:01" end it "should return nil when no macaddress can be found" do - Facter::Util::Resolution.stubs(:exec).with('/sbin/ifconfig -a 2>/dev/null'). + Facter::Util::IP.stubs(:exec_ifconfig).with(["-a","2>/dev/null"]). returns(ifconfig_fixture('linux_ifconfig_no_mac')) proc { Facter.value(:macaddress) }.should_not raise_error @@ -38,7 +41,7 @@ def netsh_fixture(filename) # some interfaces dont have a real mac addresses (like venet inside a container) it "should return nil when no interface has a real macaddress" do - Facter::Util::Resolution.stubs(:exec).with('/sbin/ifconfig -a 2>/dev/null'). + Facter::Util::IP.stubs(:exec_ifconfig).with(["-a","2>/dev/null"]). returns(ifconfig_fixture('linux_ifconfig_venet')) proc { Facter.value(:macaddress) }.should_not raise_error @@ -49,7 +52,8 @@ def netsh_fixture(filename) describe "when run on BSD" do it "should return macaddress information" do Facter.fact(:kernel).stubs(:value).returns("FreeBSD") - Facter::Util::Resolution.stubs(:exec).with('/sbin/ifconfig'). + Facter::Util::IP.stubs(:get_ifconfig).returns("/sbin/ifconfig") + Facter::Util::IP.stubs(:exec_ifconfig). returns(ifconfig_fixture('bsd_ifconfig_all_with_multiple_interfaces')) Facter.value(:macaddress).should == "00:0b:db:93:09:67" diff --git a/spec/unit/util/ip_spec.rb b/spec/unit/util/ip_spec.rb index 243e27279f..c149465135 100755 --- a/spec/unit/util/ip_spec.rb +++ b/spec/unit/util/ip_spec.rb @@ -409,6 +409,45 @@ def self.hpux_examples end end + describe "exec_ifconfig" do + it "uses get_ifconfig" do + Facter::Util::IP.stubs(:get_ifconfig).returns("/sbin/ifconfig").once + + Facter::Util::IP.exec_ifconfig + end + it "support additional arguments" do + Facter::Util::IP.stubs(:get_ifconfig).returns("/sbin/ifconfig") + + Facter::Util::Resolution.stubs(:exec).with("/sbin/ifconfig -a") + + Facter::Util::IP.exec_ifconfig(["-a"]) + end + it "joins multiple arguments correctly" do + Facter::Util::IP.stubs(:get_ifconfig).returns("/sbin/ifconfig") + + Facter::Util::Resolution.stubs(:exec).with("/sbin/ifconfig -a -e -i -j") + + Facter::Util::IP.exec_ifconfig(["-a","-e","-i","-j"]) + end + end + describe "get_ifconfig" do + it "assigns /sbin/ifconfig if it is executable" do + File.stubs(:executable?).returns(false) + File.stubs(:executable?).with("/sbin/ifconfig").returns(true) + Facter::Util::IP.get_ifconfig.should eq("/sbin/ifconfig") + end + it "assigns /usr/sbin/ifconfig if it is executable" do + File.stubs(:executable?).returns(false) + File.stubs(:executable?).with("/usr/sbin/ifconfig").returns(true) + Facter::Util::IP.get_ifconfig.should eq("/usr/sbin/ifconfig") + end + it "assigns /bin/ifconfig if it is executable" do + File.stubs(:executable?).returns(false) + File.stubs(:executable?).with("/bin/ifconfig").returns(true) + Facter::Util::IP.get_ifconfig.should eq("/bin/ifconfig") + end + end + context "with bonded ethernet interfaces on Linux" do before :each do Facter.fact(:kernel).stubs(:value).returns("Linux") From 2f13829b3379614d1e7a14d741a1254dc9771445 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Mon, 4 Feb 2013 15:32:06 -0800 Subject: [PATCH 1147/3753] (maint) Fix spec failure in Ruby 1.8.5 due to Array#count Without this patch the examples fail with Ruby 1.8.5 because Array#count is called and this method is not defined in this version of Ruby. This patch fixes the problem by calling Array#length instead. --- lib/facter/physicalprocessorcount.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/physicalprocessorcount.rb b/lib/facter/physicalprocessorcount.rb index a8ff68605c..94f5f70bf4 100644 --- a/lib/facter/physicalprocessorcount.rb +++ b/lib/facter/physicalprocessorcount.rb @@ -48,7 +48,7 @@ # We assume that /proc/cpuinfo has what we need and is so then we need # to make sure that we only count unique entries ... # - n = File.read('/proc/cpuinfo').split(/\n/).grep(/^physical id/).uniq.count + n = File.read('/proc/cpuinfo').split(/\n/).grep(/^physical id/).uniq.length n > 0 ? n : nil end end From a79451a6978846e2addd9c848cfa80c6339a996d Mon Sep 17 00:00:00 2001 From: Patrick Carlisle Date: Mon, 4 Feb 2013 15:51:07 -0800 Subject: [PATCH 1148/3753] Add missing require for macaddress This fixes the error "Could not retrieve macaddress: uninitialized constant Facter::Util::IP" --- lib/facter/macaddress.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/facter/macaddress.rb b/lib/facter/macaddress.rb index 842ee98e29..689988f9ef 100644 --- a/lib/facter/macaddress.rb +++ b/lib/facter/macaddress.rb @@ -8,6 +8,7 @@ # require 'facter/util/macaddress' +require 'facter/util/ip' Facter.add(:macaddress) do confine :kernel => 'Linux' From 7d257510078bd201fbf2456b486557fcb80a7f7b Mon Sep 17 00:00:00 2001 From: Patrick Carlisle Date: Mon, 4 Feb 2013 15:53:10 -0800 Subject: [PATCH 1149/3753] Call correct function on IP in NetMask This fixes the error "Could not retrieve netmask: wrong number of arguments (1 for 0)" --- lib/facter/util/netmask.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/util/netmask.rb b/lib/facter/util/netmask.rb index a7d212949b..4a6d03ca93 100644 --- a/lib/facter/util/netmask.rb +++ b/lib/facter/util/netmask.rb @@ -25,7 +25,7 @@ def self.get_netmask } end - Facter::Util::IP.get_ifconfig(ops[:ifconfig_opts]).split(/\n/).collect do |line| + Facter::Util::IP.exec_ifconfig(ops[:ifconfig_opts]).split(/\n/).collect do |line| matches = line.match(ops[:regex]) if !matches.nil? if ops[:munge].nil? From 505bb9b2bacbaf79a0760dd980550d17461fadf9 Mon Sep 17 00:00:00 2001 From: David Kowis Date: Sun, 3 Feb 2013 19:51:17 -0600 Subject: [PATCH 1150/3753] (#18756) Fix getting interface specific data with newer net_tools A simple regexp, and then some spec tests to verify that it works with 1.60 version, as well as 2.0 version of ifconfig output. --- lib/facter/util/ip.rb | 4 +-- .../unit/interfaces/eth0_net_tools_1.60.txt | 10 +++++++ .../unit/interfaces/eth0_net_tools_2.0.txt | 9 ++++++ spec/unit/interfaces_spec.rb | 30 +++++++++++++++++++ 4 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 spec/fixtures/unit/interfaces/eth0_net_tools_1.60.txt create mode 100644 spec/fixtures/unit/interfaces/eth0_net_tools_2.0.txt diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index 73e20be55d..fb376e050e 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -5,8 +5,8 @@ module Facter::Util::IP # a given platform or set of platforms. REGEX_MAP = { :linux => { - :ipaddress => /inet addr:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, - :ipaddress6 => /inet6 addr: ((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/, + :ipaddress => /inet (?:addr:)?([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, + :ipaddress6 => /inet6 (?:addr:)? ?((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/, :macaddress => /(?:ether|HWaddr)\s+((\w{1,2}:){5,}\w{1,2})/, :netmask => /Mask:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, :mtu => /MTU:(\d+)/ diff --git a/spec/fixtures/unit/interfaces/eth0_net_tools_1.60.txt b/spec/fixtures/unit/interfaces/eth0_net_tools_1.60.txt new file mode 100644 index 0000000000..c0e84ea229 --- /dev/null +++ b/spec/fixtures/unit/interfaces/eth0_net_tools_1.60.txt @@ -0,0 +1,10 @@ +eth0: flags=4163 mtu 1500 + inet 131.252.209.153 netmask 255.255.255.0 broadcast 192.168.76.255 + inet6 2610:10:20:209:212:3fff:febe:2201 prefixlen 128 scopeid 0x0 + inet6 fe80::221:ccff:fe4b:297d prefixlen 64 scopeid 0x20 + ether 00:21:cc:4b:29:7d txqueuelen 1000 (Ethernet) + RX packets 27222144 bytes 31247414760 (29.1 GiB) + RX errors 0 dropped 0 overruns 0 frame 0 + TX packets 10259038 bytes 7784519352 (7.2 GiB) + TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 + device interrupt 20 memory 0xd2600000-d2620000 diff --git a/spec/fixtures/unit/interfaces/eth0_net_tools_2.0.txt b/spec/fixtures/unit/interfaces/eth0_net_tools_2.0.txt new file mode 100644 index 0000000000..70019d350f --- /dev/null +++ b/spec/fixtures/unit/interfaces/eth0_net_tools_2.0.txt @@ -0,0 +1,9 @@ +eth0: flags=4163 mtu 1500 + inet 10.10.220.1 netmask 255.255.255.0 broadcast 10.10.220.255 + inet6 fe80::21f:bcff:fe0d:5fb1 prefixlen 64 scopeid 0x20 + ether 00:1f:bc:0d:5f:b1 txqueuelen 1000 (Ethernet) + RX packets 3790976 bytes 4764279418 (4.4 GiB) + RX errors 0 dropped 0 overruns 0 frame 0 + TX packets 1573379 bytes 304674658 (290.5 MiB) + TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 + diff --git a/spec/unit/interfaces_spec.rb b/spec/unit/interfaces_spec.rb index 3798721ebd..5a8bf1959b 100755 --- a/spec/unit/interfaces_spec.rb +++ b/spec/unit/interfaces_spec.rb @@ -3,6 +3,13 @@ require 'spec_helper' require 'facter/util/ip' +shared_examples_for "iface specific ifconfig output" do |platform, address, fixture| + it "correctly on #{platform} for eth0" do + Facter::Util::IP.stubs(:ifconfig_interface).returns(my_fixture_read(fixture)) + subject.value.should == address + end +end + describe "Per Interface IP facts" do it "should replace the ':' in an interface list with '_'" do # So we look supported @@ -19,3 +26,26 @@ Facter.fact(:interfaces).value.should == %{Local_Area_Connection,Loopback__Pseudo_Interface____1_} end end + + +RSpec.configure do |config| + config.alias_it_should_behave_like_to :example_behavior_for, "parses" +end + + +describe "the ipaddress_$iface fact" do + subject do + Facter.collection.loader.load(:interfaces) + Facter.fact(:ipaddress_eth0) + end + + context "on Linux" do + before :each do + Facter::Util::IP.stubs(:get_interfaces).returns(["eth0"]) + Facter.fact(:kernel).stubs(:value).returns("Linux") + end + example_behavior_for "iface specific ifconfig output", "Fedora 18", "10.10.220.1", "eth0_net_tools_2.0.txt" + + example_behavior_for "iface specific ifconfig output", "net_tools 1.60", "131.252.209.153", "eth0_net_tools_1.60.txt" + end +end From ee0c4642eeaa5d0284cbb33a571c0b4196003c51 Mon Sep 17 00:00:00 2001 From: David Kowis Date: Sun, 3 Feb 2013 21:35:02 -0600 Subject: [PATCH 1151/3753] Update the fixtures to include a "real" ipv6 address Also, update the 1.60 fixture to use the older format, so that this test actually verifies both outputs of ifconfig $iface --- .../unit/interfaces/eth0_net_tools_1.60.txt | 19 +++++++++---------- .../unit/interfaces/eth0_net_tools_2.0.txt | 2 +- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/spec/fixtures/unit/interfaces/eth0_net_tools_1.60.txt b/spec/fixtures/unit/interfaces/eth0_net_tools_1.60.txt index c0e84ea229..5fc7b55e39 100644 --- a/spec/fixtures/unit/interfaces/eth0_net_tools_1.60.txt +++ b/spec/fixtures/unit/interfaces/eth0_net_tools_1.60.txt @@ -1,10 +1,9 @@ -eth0: flags=4163 mtu 1500 - inet 131.252.209.153 netmask 255.255.255.0 broadcast 192.168.76.255 - inet6 2610:10:20:209:212:3fff:febe:2201 prefixlen 128 scopeid 0x0 - inet6 fe80::221:ccff:fe4b:297d prefixlen 64 scopeid 0x20 - ether 00:21:cc:4b:29:7d txqueuelen 1000 (Ethernet) - RX packets 27222144 bytes 31247414760 (29.1 GiB) - RX errors 0 dropped 0 overruns 0 frame 0 - TX packets 10259038 bytes 7784519352 (7.2 GiB) - TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 - device interrupt 20 memory 0xd2600000-d2620000 +eth0 Link encap:Ethernet HWaddr 00:16:3E:7D:EC:7E + inet addr:10.10.220.210 Bcast:10.10.220.255 Mask:255.255.255.0 + inet6 addr: dead::216:3eff:fe7d:ec7e/64 Scope:Link + UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 + RX packets:40798458 errors:0 dropped:77022 overruns:0 frame:0 + TX packets:33143050 errors:0 dropped:23 overruns:0 carrier:0 + collisions:0 txqueuelen:1000 + RX bytes:2331651396 (2223.6 Mb) TX bytes:46661615683 (44499.9 Mb) + Interrupt:40 diff --git a/spec/fixtures/unit/interfaces/eth0_net_tools_2.0.txt b/spec/fixtures/unit/interfaces/eth0_net_tools_2.0.txt index 70019d350f..27ab2b8355 100644 --- a/spec/fixtures/unit/interfaces/eth0_net_tools_2.0.txt +++ b/spec/fixtures/unit/interfaces/eth0_net_tools_2.0.txt @@ -1,6 +1,6 @@ eth0: flags=4163 mtu 1500 inet 10.10.220.1 netmask 255.255.255.0 broadcast 10.10.220.255 - inet6 fe80::21f:bcff:fe0d:5fb1 prefixlen 64 scopeid 0x20 + inet6 dead::21f:bcff:fe0d:5fb1 prefixlen 64 scopeid 0x20 ether 00:1f:bc:0d:5f:b1 txqueuelen 1000 (Ethernet) RX packets 3790976 bytes 4764279418 (4.4 GiB) RX errors 0 dropped 0 overruns 0 frame 0 From adff45623b8aaa32f0147059a451e7d5dee2de44 Mon Sep 17 00:00:00 2001 From: David Kowis Date: Sun, 3 Feb 2013 21:35:48 -0600 Subject: [PATCH 1152/3753] Add ipaddress6_$iface fact checks to verify the "real" ipv6 addresses It automatically ignores ones link-local, so I faked it :) --- spec/unit/interfaces_spec.rb | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/spec/unit/interfaces_spec.rb b/spec/unit/interfaces_spec.rb index 5a8bf1959b..c495a499eb 100755 --- a/spec/unit/interfaces_spec.rb +++ b/spec/unit/interfaces_spec.rb @@ -35,7 +35,7 @@ describe "the ipaddress_$iface fact" do subject do - Facter.collection.loader.load(:interfaces) + Facter.collection.internal_loader.load(:interfaces) Facter.fact(:ipaddress_eth0) end @@ -46,6 +46,23 @@ end example_behavior_for "iface specific ifconfig output", "Fedora 18", "10.10.220.1", "eth0_net_tools_2.0.txt" - example_behavior_for "iface specific ifconfig output", "net_tools 1.60", "131.252.209.153", "eth0_net_tools_1.60.txt" + example_behavior_for "iface specific ifconfig output", "net_tools 1.60", "10.10.220.210", "eth0_net_tools_1.60.txt" + end +end + +describe "the ipaddress6_$iface fact" do + subject do + Facter.collection.internal_loader.load(:interfaces) + Facter.fact(:ipaddress6_eth0) + end + + context "on Linux" do + before :each do + Facter::Util::IP.stubs(:get_interfaces).returns(["eth0"]) + Facter.fact(:kernel).stubs(:value).returns("Linux") + end + example_behavior_for "iface specific ifconfig output", "Fedora 18", "dead::21f:bcff:fe0d:5fb1", "eth0_net_tools_2.0.txt" + + example_behavior_for "iface specific ifconfig output", "net_tools 1.60", "dead::216:3eff:fe7d:ec7e", "eth0_net_tools_1.60.txt" end end From 63d5296beb3f71471c0fa22c28b2316f1059930e Mon Sep 17 00:00:00 2001 From: Patrick Carlisle Date: Mon, 4 Feb 2013 16:03:35 -0800 Subject: [PATCH 1153/3753] Only run Facter::Memory.meminfo_number on supported platforms Having this code outside of the setcode block means it gets evaluated and generates a spurious warning on unsupported platforms. --- lib/facter/memory.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb index 583c937311..a8b05906ef 100644 --- a/lib/facter/memory.rb +++ b/lib/facter/memory.rb @@ -75,8 +75,8 @@ }.each do |fact, name| Facter.add(fact) do confine :kernel => [ :linux, :"gnu/kfreebsd" ] - meminfo = Facter::Memory.meminfo_number(name) setcode do + meminfo = Facter::Memory.meminfo_number(name) "%.2f" % [meminfo] end end From 3e19ab5faf0b5fcbbbffcd87f505a4faa06ebc4d Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Fri, 8 Feb 2013 10:27:07 -0800 Subject: [PATCH 1154/3753] Pull Request Automation Demo This commit is simply a demo of how a pull request flows through the system. I have a body here to show stuff off. Here's a picture of a grumpy cat. ![Grumpy Cat](http://t.qkme.me/3r15cw.jpg) --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index d7efef9be9..f0f4bbc6f9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ Facter ====== +Let's see how this pull request flows through to Trello. + [![Build Status](https://travis-ci.org/puppetlabs/facter.png?branch=master)](https://travis-ci.org/puppetlabs/facter) This package is largely meant to be a library for collecting facts about your From a6d763f2442c72dc860d69fc09fa2ba93dd5b904 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Fri, 8 Feb 2013 13:21:46 -0800 Subject: [PATCH 1155/3753] Revert "Merge pull request #394 from jeffmccune/webapp_demo" This reverts commit 92c2ccbebe1878b3e017e96ef8ef0c91840f8fba, reversing changes made to 6aea604d5fd3840e195723e14b98e9b830d88ca5. I accidentally clicked the merge button when I meant to click the close button. Whoops. --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index f0f4bbc6f9..d7efef9be9 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ Facter ====== -Let's see how this pull request flows through to Trello. - [![Build Status](https://travis-ci.org/puppetlabs/facter.png?branch=master)](https://travis-ci.org/puppetlabs/facter) This package is largely meant to be a library for collecting facts about your From 459801ef178502207bd8720e4d1b9a822a74ab41 Mon Sep 17 00:00:00 2001 From: Joshua Hoblitt Date: Tue, 16 Oct 2012 16:39:25 -0700 Subject: [PATCH 1156/3753] (#17029) add osfamily Gentoo With a single member of operatingsystem type Gentoo. Tests for popular Gentoo derivatives like Sabayon are not current in the facter tree so this patch is not adding them to the the initial Gentoo osfamily definition. --- lib/facter/osfamily.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/facter/osfamily.rb b/lib/facter/osfamily.rb index aa3f50a217..228f9a1086 100644 --- a/lib/facter/osfamily.rb +++ b/lib/facter/osfamily.rb @@ -24,6 +24,8 @@ "Suse" when "Solaris", "Nexenta", "OmniOS", "OpenIndiana", "SmartOS" "Solaris" + when "Gentoo" + "Gentoo" else Facter.value("kernel") end From e3b8ee4f5a8e860f5df280052f68eec0e94849d7 Mon Sep 17 00:00:00 2001 From: Niels Abspoel Date: Sat, 10 Nov 2012 18:58:56 +0100 Subject: [PATCH 1157/3753] (#17521) Add osfamily fact ArchLinux Archlinux doesn't have a osfamily fact. I believe Archlinux needs one because of the following: * Manjaro Linux based on Archlinux http://blog.manjaro.org/ * Archbang based on Archlinux http://archbang.org/ * More archlinux based distro's: https://wiki.archlinux.org/index.php/Arch_Based_Distributions_(Active) Conflicts: lib/facter/osfamily.rb --- lib/facter/osfamily.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/facter/osfamily.rb b/lib/facter/osfamily.rb index 228f9a1086..6162ef8929 100644 --- a/lib/facter/osfamily.rb +++ b/lib/facter/osfamily.rb @@ -25,7 +25,9 @@ when "Solaris", "Nexenta", "OmniOS", "OpenIndiana", "SmartOS" "Solaris" when "Gentoo" - "Gentoo" + "Gentoo" + when "Archlinux" + "Archlinux" else Facter.value("kernel") end From fd928a8d79c9347a8f1cec4ac6763a789d424077 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Wed, 13 Feb 2013 14:53:00 -0800 Subject: [PATCH 1158/3753] (doc) Update COMMITTERS and CONTRIBUTING Without this patch the documentation describing our workflow and process is out of date for Facter. This is a problem because community contributors and people new to Puppet Labs have a more difficult time learning how to submit contributions and merge contributions into the codebase. This patch addresses the problem by copying the COMMITTERS and CONTRIBUTING documents from Puppet and customizing them specifically to Facter. [ci skip] --- COMMITTERS.md | 185 ++++++++++++++++++++++++++++++ CONTRIBUTING.md | 294 ++++++++++-------------------------------------- 2 files changed, 246 insertions(+), 233 deletions(-) create mode 100644 COMMITTERS.md diff --git a/COMMITTERS.md b/COMMITTERS.md new file mode 100644 index 0000000000..6f8184643d --- /dev/null +++ b/COMMITTERS.md @@ -0,0 +1,185 @@ +Committing changes to Facter +==== + +We would like to make it easier for community members to contribute to facter +using pull requests, even if it makes the task of reviewing and committing +these changes a little harder. Pull requests are only ever based on a single +branch, however, we maintain more than one active branch. As a result +contributors should target their changes at the master branch. This makes the +process of contributing a little easier for the contributor since they don't +need to concern themselves with the question, "What branch do I base my changes +on?" This is already called out in the [CONTRIBUTING.md](http://goo.gl/XRH2J). + +Therefore, it is the responsibility of the committer to re-base the change set +on the appropriate branch which should receive the contribution. + +The rest of this document addresses the concerns of the committer. This +document will help guide the committer decide which branch to base, or re-base +a contribution on top of. This document also describes our branch management +strategy, which is closely related to the decision of what branch to commit +changes into. + +Terminology +==== + +Many of these terms have more than one meaning. For the purposes of this +document, the following terms refer to specific things. + +**contributor** - A person who makes a change to facter and submits a change +set in the form of a pull request. + +**change set** - A set of discrete patches which combined together form a +contribution. A change set takes the form of Git commits and is submitted to +facter in the form of a pull request. + +**committer** - A person responsible for reviewing a pull request and then +making the decision what base branch to merge the change set into. + +**base branch** - A branch in Git that contains an active history of changes +and will eventually be released using semantic version guidelines. The branch +named master will always exist as a base branch. All other base branches will +be associated with a specific released version of facter, e.g. 1.6.x and 1.7.x. + +Committer Guide +==== + +This section provides a guide to follow while committing change sets to facter +base branches. + +How to decide what release(s) should be patched +--- + +This section provides a guide to help a committer decide the specific base +branch that a change set should be merged into. + +The latest minor release of a major release is the only base branch that should +be patched. Older minor releases in a major release do not get patched. Before +the switch to [semantic versions](http://semver.org/) committers did not have +to think about the difference between minor and major releases. Committing to +the latest minor release of a major release is a policy intended to limit the +number of active base branches that must be managed. + +Security patches are handled as a special case. Security patches may be +applied to earlier minor releases of a major release. + +How to commit a change set to multiple base branches +--- + +A change set may apply to multiple releases. In this situation the change set +needs to be committed to multiple base branches. This section provides a guide +for how to merge patches across releases, e.g. 2.7 is patched, how should the +changes be applied to 3.0? + +First, merge the change set into the lowest numbered base branch, e.g. 2.7. +Next, merge the changed base branch up through all later base branches by using +the `--no-ff --log` git merge options. We commonly refer to this as our "merge +up process" because we merge in once, then merge up multiple times. + +When a new minor release branch is created (e.g. 3.1.x) then the previous one +is deleted (e.g. 3.0.x). Any security or urgent fixes that might have to be +applied to the older code line is done by creating an ad-hoc branch from the +tag of the last patch release of the old minor line. + +Code review checklist +--- + +This section aims to provide a checklist of things to look for when reviewing a +pull request and determining if the change set should be merged into a base +branch: + + * All tests pass + * Are there any platform gotchas? (Does a change make an assumption about + platform specific behavior that is incompatible with other platforms? e.g. + Windows paths vs. POSIX paths.) + * Is the change backwards compatible? (It should be) + * Are there YARD docs for API changes? + * Does the change set also require documentation changes? If so is the + documentation being kept up to date? + * Does the change set include clean code? (software code that is formatted + correctly and in an organized manner so that another coder can easily read + or modify it.) HINT: `git diff master --check` + * Does the change set conform to the contributing guide? + + +Commit citizen guidelines: +--- + +This section aims to provide guidelines for being a good commit citizen by +paying attention to our automated build tools. + + * Don’t push on a broken build. (A broken build is defined as a failing job + in the [Facter](https://jenkins.puppetlabs.com/view/Facter/) + page.) + * Watch the build until your changes have gone through green + * Update the ticket status and target version. The target version field in + our issue tracker should be updated to be the next release of facter. For + example, if the most recent release of facter is 1.6.17 and you merge a + backwards compatible change set into master, then the target version should + be 1.7.0 in the issue tracker.) + * Ensure the pull request is closed (Hint: amend your merge commit to contain + the string `closes #123` where 123 is the pull request number. + +Example Procedure +==== + +This section helps a committer rebase a contribution onto an earlier base +branch, then merge into the base branch and up through all active base +branches. + +Suppose a contributor submits a pull request based on master. The change set +fixes a bug reported against facter 1.7.1 which is the most recently released +version of facter. + +In this example the committer should rebase the change set onto the 1.7.x +branch since this is a bug rather than new functionality. + +First, the committer pulls down the branch using the `hub` gem. This tool +automates the process of adding the remote repository and creating a local +branch to track the remote branch. + + $ hub checkout https://github.com/puppetlabs/facter/pull/1234 + Branch jeffmccune-fix_foo_error set up to track remote branch fix_foo_error from jeffmccune. + Switched to a new branch 'jeffmccune-fix_foo_error' + +At this point the topic branch is a descendant of master, but we want it to +descend from 1.7.x. The committer creates a new branch then re-bases the +change set: + + $ git branch bug/1.7.x/fix_foo_error + $ git rebase --onto 1.7.x master bug/1.7.x/fix_foo_error + First, rewinding head to replay your work on top of it... + Applying: (#23456) Fix FooError that always bites users in 1.7.1 + +The `git rebase` command may be interpreted as, "First, check out the branch +named `bug/1.7.x/fix_foo_error`, then take the changes that were previously +based on `master` and re-base them onto `1.7.x`. + +Now that we have a topic branch containing the change set based on the correct +release branch, the committer merges in: + + $ git checkout 1.7.x + Switched to branch '1.7.x' + $ git merge --no-ff --log bug/1.7.x/fix_foo_error + Merge made by the 'recursive' strategy. + foo | 0 + 1 file changed, 0 insertions(+), 0 deletions(-) + create mode 100644 foo + +Once merged into the first base branch, the committer merges up: + + $ git checkout master + Switched to branch 'master' + $ git merge --no-ff --log 1.7.x + Merge made by the 'recursive' strategy. + foo | 0 + 1 file changed, 0 insertions(+), 0 deletions(-) + create mode 100644 foo + +Once the change set has been merged "in and up." the committer pushes. (Note, +the checklist should be complete at this point.) Note that both the 1.7.x and +master branches are being pushed at the same time. + + $ git push puppetlabs master:master 1.7.x:1.7.x + +That's it! The committer then updates the pull request, updates the issue in +our issue tracker, and keeps an eye on the build status. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 926701606a..44d68a5593 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,237 +1,65 @@ -Checklist/Outline (The short version) -================================================= - - * Getting Started: - - Make sure you have a [Redmine account](http://projects.puppetlabs.com) - - Submit a ticket for your issue, assuming one does not already exist. - - Decide what to base your work off of - * `1.6.x`: bug fixes only - * `2.x`: new features that are not breaking changes - * `master`: new features that are breaking changes - - * Making Changes: - - Make sure you have a [GitHub account](https://github.com/signup/free) - - Fork the repository on GitHub - - Make commits of logical units. - - Check for unnecessary whitespace with "git diff --check" before committing. - - Make sure your commit messages are in the proper format - - Make sure you have added the necessary tests for your changes - - Run _all_ the tests to assure nothing else was accidentally broken - - * Submitting Changes: - - Sign the [Contributor License Agreement](https://projects.puppetlabs.com/contributor_licenses/sign) - - Push your changes to a topic branch in your fork of the repository. - - Submit a pull request to the repository in the puppetlabs organization. - - Update your Redmine ticket - -The long version -================ - - 0. Create a Redmine ticket for the change you'd like to make. - - It's very important that there be a Redmine ticket for the change - you are making. Considering the number of contributions which are - submitted, it is crucial that we know we can find the ticket on Redmine. - - Before making a ticket however, be sure that one does not already exist. - You can do this by searching Redmine or by trying a Google search which - includes `sites:projects.puppetlabs.com` in addition to some of the keywords - related to your issue. - - If you do not find a ticket that that accurately describes the work - you're going to be doing, go ahead and create one. But be sure to - look for related tickets and add them to the 'related tickets' section. - - 1. Decide what to base your work on. - - In general, you should always base your work on the oldest - branch that your change is relevant to, and it will be - eventually merged up. Currently, branches will be merged up as - follows: - 1.6.x => 2.x => master - - Currently, this is how you should decide where to target your changes: - - A bug fix should be based off the the earliest place where it is - relevant. If it first appears in `1.6.x`, then it should be - targeted here and eventually merged up to `2.x` and master. - - New features which are _backwards compatible_ should be targeted - at the next release, which currently is `2.x`. - - New features that are _breaking changes_ should be targeted at - `master`. - - Part of deciding what to what your work should be based off of includes naming - your topic branch to reflect this. Your branch name should have the following - format: - `ticket/target_branch/ticket_number_short_description_of_issuee` - - For example, if you are fixing a bug relating to a hostname problem on aix, - which has Redmine ticket number 12345, then your branch should be named: - `ticket/2.x/12345_fix_hostname_on_aix` - - There is a good chance that if you submit a pull request _from_ master _to_ master, - Puppet Labs developers will suspect that you're not sure about the process. This is - why clear naming of branches and basing your work off the right place will be - extremely helpful in ensuring that your submission is reviewed and merged. Often times - if your change is targeted at the wrong place, we will bounce it back to you and wait - to review it until it has been retargeted. - - 2. Make separate commits for logically separate changes. - - Please break your commits down into logically consistent units - which include new or changed tests relevent to the rest of the - change. The goal of doing this is to make the diff easier to - read for whoever is reviewing your code. In general, the easier - your diff is to read, the more likely someone will be happy to - review it and get it into the code base. - - If you're going to refactor a piece of code, please do so as a - separate commit from your feature or bug fix changes. - - It's crucial that your changes include tests to make - sure the bug isn't re-introduced, and that the feature isn't - accidentally broken. - - Describe the technical detail of the change(s). If your - description starts to get too long, that's a good sign that you - probably need to split up your commit into more finely grained - pieces. - - Commits which plainly describe the the things which help - reviewers check the patch and future developers understand the - code are much more likely to be merged in with a minimum of - bike-shedding or requested changes. Ideally, the commit message - would include information, and be in a form suitable for - inclusion in the release notes for the version of Facter that - includes them. - - Please also check that you are not introducing any trailing - whitespaces or other "whitespace errors". You can do this by - running "git diff --check" on your changes before you commit. - - When writing commit messages, please be sure they meet - [these standards](https://github.com/erlang/otp/wiki/Writing-good-commit-messages), and please include the ticket number in your - short summary. It should look something like this: `(#12345) Fix this issue in Facter` - - 3. Sign the Contributor License Agreement - - Before we can accept your changes, we do need a signed Puppet - Labs Contributor License Agreement (CLA). - - You can access the CLA via the - [Contributor License Agreement link](https://projects.puppetlabs.com/contributor_licenses/sign) - in the top menu bar of our Redmine instance. Once you've signed - the CLA, a badge will show up next to your name on the - [Puppet Project Overview Page](http://projects.puppetlabs.com/projects/puppet?jump=welcome), - and your name will be listed under "Contributor License Signers" - section. - - If you have any questions about the CLA, please feel free to - contact Puppet Labs via email at cla-submissions@puppetlabs.com. - - 4. Sending your patches - - To submit your changes via a GitHub pull request, you must - have them on a topic branch, instead of directly on "master" - or one of the release, or RC branches. It makes things much easier - to keep track of, especially if you decide to work on another thing - before your first change is merged in. - - GitHub has some pretty good - [general documentation](http://help.github.com/) on using - their site. They also have documentation on - [creating pull requests](http://help.github.com/send-pull-requests/). - - In general, after pushing your topic branch up to your - repository on GitHub, you'll switch to the branch in the - GitHub UI and click "Pull Request" towards the top of the page - in order to open a pull request. - - You'll want to make sure that you have the appropriate - destination branch in the repository under the puppetlabs - organization. This should be the same branch that you based - your changes off of. - - 5. Update the related Redmine ticket. - - You should update the Redmine ticket associated - with the change you submitted to include the location of your branch - on the `branch` field of the ticket, and change the status to - "In Topic Branch Pending Review", along with any other commentary - you may wish to make. - -How to track the status of your change after it's been submitted -================================================================ - -Shortly after opening a pull request, there should be an automatic -email sent via GitHub. This notification is used to let the Puppet -development community know about your requested change to give them a -chance to review, test, and comment on the change(s). - -We do our best to comment on or merge submitted changes within a about week. -However, if there hasn't been any commentary on the pull request or -mailed patches, and it hasn't been merged in after a week, then feel -free to ask for an update by replying on the mailing list to the -automatic notification or mailed patches. It probably wasn't -intentional, and probably just slipped through the cracks. - -Additional Resources -==================== - -* [Getting additional help](http://projects.puppetlabs.com/projects/puppet/wiki/Getting_Help) - -* [Writing tests](http://projects.puppetlabs.com/projects/puppet/wiki/Development_Writing_Tests) - +# How to contribute + +Third-party patches are essential for keeping facter great. We simply can't +access the huge number of platforms and myriad configurations for running +facter. We want to keep it as easy as possible to contribute changes that +get things working in your environment. There are a few guidelines that we +need contributors to follow so that we can have a chance of keeping on +top of things. + +## Getting Started + +* Make sure you have a [Redmine account](http://projects.puppetlabs.com) +* Make sure you have a [GitHub account](https://github.com/signup/free) +* Submit a ticket for your issue, assuming one does not already exist. + * Clearly describe the issue including steps to reproduce when it is a bug. + * Make sure you fill in the earliest version that you know has the issue. +* Fork the repository on GitHub + +## Making Changes + +* Create a topic branch from where you want to base your work. + * This is usually the master branch. + * Only target release branches if you are certain your fix must be on that + branch. + * To quickly create a topic branch based on master; `git branch + fix/master/my_contribution master` then checkout the new branch with `git + checkout fix/master/my_contribution`. Please avoid working directly on the + `master` branch. +* Make commits of logical units. +* Check for unnecessary whitespace with `git diff --check` before committing. +* Make sure your commit messages are in the proper format. + +```` + (#99999) Make the example in CONTRIBUTING imperative and concrete + + Without this patch applied the example commit message in the CONTRIBUTING + document is not a concrete example. This is a problem because the + contributor is left to imagine what the commit message should look like + based on a description rather than an example. This patch fixes the + problem by making the example concrete and imperative. + + The first line is a real life imperative statement with a ticket number + from our issue tracker. The body describes the behavior without the patch, + why this is a problem, and how the patch fixes the problem when applied. +```` + +* Make sure you have added the necessary tests for your changes. +* Run _all_ the tests to assure nothing else was accidentally broken. + +## Submitting Changes + +* Sign the [Contributor License Agreement](https://projects.puppetlabs.com/contributor_licenses/sign). +* Push your changes to a topic branch in your fork of the repository. +* Submit a pull request to the repository in the puppetlabs organization. +* Update your Redmine ticket to mark that you have submitted code and are ready for it to be reviewed. + * Include a link to the pull request in the ticket + +# Additional Resources + +* [More information on contributing](http://links.puppetlabs.com/contribute-to-puppet) * [Bug tracker (Redmine)](http://projects.puppetlabs.com) - * [Contributor License Agreement](https://projects.puppetlabs.com/contributor_licenses/sign) - * [General GitHub documentation](http://help.github.com/) - * [GitHub pull request documentation](http://help.github.com/send-pull-requests/) - -If you have commit access to the repository -=========================================== - -Even if you have commit access to the repository, you'll still need to -go through the process above, and have someone else review and merge -in your changes. The rule is that all changes must be reviewed by a -developer on the project (that didn't write the code) to ensure that -all changes go through a code review process. - -Having someone other than the author of the topic branch recorded as -performing the merge is the record that they performed the code -review. - - * Merging topic branches - - When merging code from a topic branch into the integration branch - (Ex: master, 2.7.x, 1.6.x, etc.), there should always be a merge - commit. You can accomplish this by always providing the `--no-ff` - flag to `git merge`. - - git merge --no-ff --log tickets/master/1234-fix-something-broken - - The reason for always forcing this merge commit is that it - provides a consistent way to look up what changes & commits were - in a topic branch, whether that topic branch had one, or 500 - commits. For example, if the merge commit had an abbreviated - SHA-1 of `coffeebad`, then you could use the following `git log` - invocation to show you which commits it brought in: - - git log coffeebad^1..coffeebad^2 - - The following would show you which changes were made on the topic - branch: - - git diff coffeebad^1...coffeebad^2 - - Because we _always_ merge the topic branch into the integration - branch the first parent (`^1`) of a merge commit will be the most - recent commit on the integration branch from just before we merged - in the topic, and the second parent (`^2`) will always be the most - recent commit that was made in the topic branch. This also serves - as the record of who performed the code review, as mentioned - above. +* #puppet-dev IRC channel on freenode.org From 11a414390bd94f2d73e8b4461382d0a3b37316f8 Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Fri, 15 Feb 2013 11:34:49 -0800 Subject: [PATCH 1159/3753] [packaging] Remove natty from the deb pkg list, its EOL Ubuntu Natty is EOL, and we are no longer going to build or ship for this distribution. This commit removes it from the default list of distributions we build for in facter. Signed-off-by: Moses Mendoza --- ext/build_defaults.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/build_defaults.yaml b/ext/build_defaults.yaml index 57ff137834..5925405a27 100644 --- a/ext/build_defaults.yaml +++ b/ext/build_defaults.yaml @@ -2,7 +2,7 @@ packaging_url: 'git://github.com/puppetlabs/packaging.git --branch=master' packaging_repo: 'packaging' default_cow: 'base-squeeze-i386.cow' -cows: 'base-lucid-i386.cow base-natty-i386.cow base-oneiric-i386.cow base-precise-i386.cow base-quantal-i386.cow base-sid-i386.cow base-squeeze-i386.cow base-stable-i386.cow base-testing-i386.cow base-unstable-i386.cow base-wheezy-i386.cow' +cows: 'base-lucid-i386.cow base-oneiric-i386.cow base-precise-i386.cow base-quantal-i386.cow base-sid-i386.cow base-squeeze-i386.cow base-stable-i386.cow base-testing-i386.cow base-unstable-i386.cow base-wheezy-i386.cow' pbuild_conf: '/etc/pbuilderrc' packager: 'puppetlabs' gpg_name: 'info@puppetlabs.com' From 17bfadbeb6df0a42aadf95485ff31ec842fc9e83 Mon Sep 17 00:00:00 2001 From: Jared Curtis Date: Thu, 14 Feb 2013 13:09:41 -0800 Subject: [PATCH 1160/3753] (#17083) Add rhev & ovirt support to the virtual fact Without this patch the virtual fact does not return specific values for the RHEV hypervisor or oVirt nodes. This path addresses the problem by implementing specific checks for these virtualization platforms. --- lib/facter/util/virtual.rb | 12 ++++++++++- lib/facter/virtual.rb | 10 +++++++++ spec/unit/util/virtual_spec.rb | 38 ++++++++++++++++++++++++++++++++++ spec/unit/virtual_spec.rb | 28 +++++++++++++++++++++++++ 4 files changed, 87 insertions(+), 1 deletion(-) diff --git a/lib/facter/util/virtual.rb b/lib/facter/util/virtual.rb index 798432ba74..f034531909 100644 --- a/lib/facter/util/virtual.rb +++ b/lib/facter/util/virtual.rb @@ -84,7 +84,17 @@ def self.kvm_type # TODO Tell the difference between kvm and qemu # Can't work out a way to do this at the moment that doesn't # require a special binary - "kvm" + if self.kvm? + "kvm" + end + end + + def self.rhev? + File.read("/sys/devices/virtual/dmi/id/product_name") =~ /RHEV Hypervisor/ rescue false + end + + def self.ovirt? + File.read("/sys/devices/virtual/dmi/id/product_name") =~ /oVirt Node/ rescue false end def self.jail? diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index f365c9a87e..bbf8d1bf50 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -92,6 +92,14 @@ result = "jail" if Facter::Util::Virtual.jail? end + if Facter::Util::Virtual.rhev? + result = "rhev" + end + + if Facter::Util::Virtual.ovirt? + result = "ovirt" + end + if result == "physical" output = Facter::Util::Virtual.lspci if not output.nil? @@ -124,6 +132,8 @@ result = "virtualbox" if pd =~ /VirtualBox/ result = "xenhvm" if pd =~ /HVM domU/ result = "hyperv" if pd =~ /Product Name: Virtual Machine/ + result = "rhev" if pd =~ /Product Name: RHEV Hypervisor/ + result = "ovirt" if pd =~ /Product Name: oVirt Node/ end elsif Facter.value(:kernel) == 'SunOS' res = Facter::Util::Resolution.new('prtdiag') diff --git a/spec/unit/util/virtual_spec.rb b/spec/unit/util/virtual_spec.rb index ab3f72f6d9..dcb62fb62f 100755 --- a/spec/unit/util/virtual_spec.rb +++ b/spec/unit/util/virtual_spec.rb @@ -81,6 +81,44 @@ Facter::Util::Virtual.should_not be_vserver end + it "should identify kvm" do + Facter::Util::Virtual.stubs(:kvm?).returns(true) + Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("something") + Facter::Util::Virtual.kvm_type().should == "kvm" + end + + it "should be able to detect RHEV via sysfs on Linux" do + # Fake files are always hard to stub. :/ + File.stubs(:read).with("/sys/devices/virtual/dmi/id/product_name"). + returns("RHEV Hypervisor") + + Facter::Util::Virtual.should be_rhev + end + + it "should be able to detect RHEV via sysfs on Linux improperly" do + # Fake files are always hard to stub. :/ + File.stubs(:read).with("/sys/devices/virtual/dmi/id/product_name"). + returns("something else") + + Facter::Util::Virtual.should_not be_rhev + end + + it "should be able to detect ovirt via sysfs on Linux" do + # Fake files are always hard to stub. :/ + File.stubs(:read).with("/sys/devices/virtual/dmi/id/product_name"). + returns("oVirt Node") + + Facter::Util::Virtual.should be_ovirt + end + + it "should be able to detect ovirt via sysfs on Linux improperly" do + # Fake files are always hard to stub. :/ + File.stubs(:read).with("/sys/devices/virtual/dmi/id/product_name"). + returns("something else") + + Facter::Util::Virtual.should_not be_ovirt + end + fixture_path = fixtures('virtual', 'proc_self_status') test_cases = [ diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index a0b6e669f7..d7c953519a 100755 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -14,6 +14,8 @@ Facter::Util::Virtual.stubs(:hpvm?).returns(false) Facter::Util::Virtual.stubs(:zlinux?).returns(false) Facter::Util::Virtual.stubs(:virt_what).returns(nil) + Facter::Util::Virtual.stubs(:rhev?).returns(false) + Facter::Util::Virtual.stubs(:ovirt?).returns(false) Facter::Util::Virtual.stubs(:virtualbox?).returns(false) end @@ -159,6 +161,20 @@ Facter.fact(:virtual).value.should == "virtualbox" end + it "should be rhev with RHEV Hypervisor product name from dmidecode" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) + Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("Product Name: RHEV Hypervisor") + Facter.fact(:virtual).value.should == "rhev" + end + + it "should be ovirt with oVirt Node product name from dmidecode" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) + Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("Product Name: oVirt Node") + Facter.fact(:virtual).value.should == "ovirt" + end + it "should be hyperv with Microsoft vendor name from lspci 2>/dev/null" do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns("00:08.0 VGA compatible controller: Microsoft Corporation Hyper-V virtual VGA") @@ -398,4 +414,16 @@ Facter.fact(:virtual).stubs(:value).returns("hyperv") Facter.fact(:is_virtual).value.should == "true" end + + it "should be true when running on rhev" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:virtual).stubs(:value).returns("rhev") + Facter.fact(:is_virtual).value.should == "true" + end + + it "should be true when running on ovirt" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:virtual).stubs(:value).returns("ovirt") + Facter.fact(:is_virtual).value.should == "true" + end end From de8bbb60bbaae30c16a3f12e7aea18fbd2000905 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Tue, 19 Feb 2013 10:30:14 -0800 Subject: [PATCH 1161/3753] Fix newline in operatingsystemrelease fact Without this patch applied there is a trailing newline in the value of the `operatingsystemrelease` Facter fact on Debian systems. The root case of this problem is the change made in df71d88 which replease useless use of cat with File.read methods. This patch addresses the problem by stripping off all trailing whitespace from the affected operatingsystemrelease resolvers. --- lib/facter/operatingsystemrelease.rb | 8 ++++++-- spec/unit/operatingsystemrelease_spec.rb | 6 ++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index 29a0e05faa..4b0ef98a13 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -50,7 +50,9 @@ Facter.add(:operatingsystemrelease) do confine :operatingsystem => %w{Debian} setcode do - release = Facter::Util::Resolution.exec('cat /etc/debian_version') + release = Facter::Util::FileRead.read('/etc/debian_version') + release.sub!(/\s*$/, '') + release end end @@ -144,7 +146,9 @@ Facter.add(:operatingsystemrelease) do confine :operatingsystem => :Alpine setcode do - File.read('/etc/alpine-release') + release = Facter::Util::FileRead.read('/etc/alpine-release') + release.sub!(/\s*$/, '') + release end end diff --git a/spec/unit/operatingsystemrelease_spec.rb b/spec/unit/operatingsystemrelease_spec.rb index b2c37d9f63..3269d10d3b 100755 --- a/spec/unit/operatingsystemrelease_spec.rb +++ b/spec/unit/operatingsystemrelease_spec.rb @@ -39,6 +39,12 @@ end end + it "does not include trailing whitespace on Debian" do + Facter.fact(:operatingsystem).stubs(:value).returns("Debian") + Facter::Util::FileRead.stubs(:read).returns("6.0.6\n") + Facter.fact(:operatingsystemrelease).value.should == "6.0.6" + end + it "for VMWareESX it should run the vmware -v command" do Facter.fact(:kernel).stubs(:value).returns("VMkernel") Facter.fact(:kernelrelease).stubs(:value).returns("4.1.0") From edbad6fac04d2425394143c7ca0fcacd99719aea Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Tue, 19 Feb 2013 10:47:31 -0800 Subject: [PATCH 1162/3753] (refactor) Send operatingsystemrelease through Facter::Util::FileRead.read Without this patch there are number of different strategies employed inside of the file defining the operatingsystemrelease resolutions. This is a problem because File.read can be tricky to stub in isolation, and the inconsistency in the file provides a bad example for contributors. Finally, we lose some automatic debugging and exception handling when the file cannot be read due to existence or permission problems. This patch addresses the problem by changing all of the instances of File.open and File.read to `Facter::Util::FileRead.read`, which is a supported and public API. In addition, this patch also removes all occurrences of the magical global back reference variables. The use of these are discouraged because they are not thread safe, and they're slightly more difficult to read. The convention of if match = /pattern/.match('pattern string') ... replaces the magic global variables. --- lib/facter/operatingsystemrelease.rb | 96 +++++++++++++----------- spec/unit/operatingsystemrelease_spec.rb | 4 +- 2 files changed, 55 insertions(+), 45 deletions(-) diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index 4b0ef98a13..2daecd626b 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -36,12 +36,13 @@ when "OVS", "ovs" releasefile = "/etc/ovs-release" end - File::open(releasefile, "r") do |f| - line = f.readline.chomp - if line =~ /\(Rawhide\)$/ + + if release = Facter::Util::FileRead.read(releasefile) + line = release.split("\n").first.chomp + if match = /\(Rawhide\)$/.match(line) "Rawhide" - elsif line =~ /release (\d[\d.]*)/ - $1 + elsif match = /release (\d[\d.]*)/.match(line) + match[1] end end end @@ -50,9 +51,10 @@ Facter.add(:operatingsystemrelease) do confine :operatingsystem => %w{Debian} setcode do - release = Facter::Util::FileRead.read('/etc/debian_version') - release.sub!(/\s*$/, '') - release + if release = Facter::Util::FileRead.read('/etc/debian_version') + release.sub!(/\s*$/, '') + release + end end end @@ -72,19 +74,20 @@ Facter.add(:operatingsystemrelease) do confine :operatingsystem => %w{SLES SLED OpenSuSE} setcode do - releasefile = Facter::Util::Resolution.exec('cat /etc/SuSE-release') - if releasefile =~ /^VERSION\s*=\s*(\d+)/ - releasemajor = $1 - if releasefile =~ /^PATCHLEVEL\s*=\s*(\d+)/ - releaseminor = $1 - elsif releasefile =~ /^VERSION\s=.*.(\d+)/ - releaseminor = $1 + if release = Facter::Util::FileRead.read('/etc/SuSE-release') + if match = /^VERSION\s*=\s*(\d+)/.match(release) + releasemajor = match[1] + if match = /^PATCHLEVEL\s*=\s*(\d+)/.match(release) + releaseminor = match[1] + elsif match = /^VERSION\s=.*.(\d+)/.match(release) + releaseminor = match[1] + else + releaseminor = "0" + end + releasemajor + "." + releaseminor else - releaseminor = "0" + "unknown" end - releasemajor + "." + releaseminor - else - "unknown" end end end @@ -92,9 +95,10 @@ Facter.add(:operatingsystemrelease) do confine :operatingsystem => %w{Slackware} setcode do - release = Facter::Util::Resolution.exec('cat /etc/slackware-version') - if release =~ /Slackware ([0-9.]+)/ - $1 + if release = Facter::Util::FileRead.read('/etc/slackware-version') + if match = /Slackware ([0-9.]+)/.match(release) + match[1] + end end end end @@ -102,9 +106,10 @@ Facter.add(:operatingsystemrelease) do confine :operatingsystem => %w{Mageia} setcode do - release = Facter::Util::Resolution.exec('cat /etc/mageia-release') - if release =~ /Mageia release ([0-9.]+)/ - $1 + if release = Facter::Util::FileRead.read('/etc/mageia-release') + if match = /Mageia release ([0-9.]+)/.match(release) + match[1] + end end end end @@ -112,11 +117,12 @@ Facter.add(:operatingsystemrelease) do confine :operatingsystem => %w{Bluewhite64} setcode do - releasefile = Facter::Util::Resolution.exec('cat /etc/bluewhite64-version') - if releasefile =~ /^\s*\w+\s+(\d+)\.(\d+)/ - $1 + "." + $2 - else - "unknown" + if release = Facter::Util::FileRead.read('/etc/bluewhite64-version') + if match = /^\s*\w+\s+(\d+)\.(\d+)/.match(release) + match[1] + "." + match[2] + else + "unknown" + end end end end @@ -125,8 +131,8 @@ confine :operatingsystem => %w{VMwareESX} setcode do release = Facter::Util::Resolution.exec('vmware -v') - if release =~ /VMware ESX .*?(\d.*)/ - $1 + if match = /VMware ESX .*?(\d.*)/.match(release) + match[1] end end end @@ -134,11 +140,12 @@ Facter.add(:operatingsystemrelease) do confine :operatingsystem => %w{Slamd64} setcode do - releasefile = Facter::Util::Resolution.exec('cat /etc/slamd64-version') - if releasefile =~ /^\s*\w+\s+(\d+)\.(\d+)/ - $1 + "." + $2 - else - "unknown" + if release = Facter::Util::FileRead.read('/etc/slamd64-version') + if match = /^\s*\w+\s+(\d+)\.(\d+)/.match(release) + match[1] + "." + match[2] + else + "unknown" + end end end end @@ -146,9 +153,10 @@ Facter.add(:operatingsystemrelease) do confine :operatingsystem => :Alpine setcode do - release = Facter::Util::FileRead.read('/etc/alpine-release') - release.sub!(/\s*$/, '') - release + if release = Facter::Util::FileRead.read('/etc/alpine-release') + release.sub!(/\s*$/, '') + release + end end end @@ -160,9 +168,11 @@ Facter.add(:operatingsystemrelease) do confine :osfamily => :solaris setcode do - release = File.open('/etc/release','r') {|f| f.readline.chomp} - if match = /\s+s(\d+)[sx]?(_u\d+)?.*(?:SPARC|X86)/.match(release) - match.captures.join('') + if release = Facter::Util::FileRead.read('/etc/release') + line = release.split("\n").first.chomp + if match = /\s+s(\d+)[sx]?(_u\d+)?.*(?:SPARC|X86)/.match(line) + match.captures.join('') + end end end end diff --git a/spec/unit/operatingsystemrelease_spec.rb b/spec/unit/operatingsystemrelease_spec.rb index 3269d10d3b..88f59bc7f8 100755 --- a/spec/unit/operatingsystemrelease_spec.rb +++ b/spec/unit/operatingsystemrelease_spec.rb @@ -32,7 +32,7 @@ it "should read the #{file.inspect} file" do Facter.fact(:operatingsystem).stubs(:value).returns(system) - File.expects(:open).with(file, "r").at_least(1) + Facter::Util::FileRead.expects(:read).with(file).at_least(1) Facter.fact(:operatingsystemrelease).value end @@ -120,7 +120,7 @@ 'Oracle Solaris 10 8/11 s10x_u10wos_17b X86' => '10_u10', }.each do |fakeinput,expected_output| it "should be able to parse a release of #{fakeinput}" do - File.stubs(:open).with('/etc/release','r').returns fakeinput + Facter::Util::FileRead.stubs(:read).with('/etc/release').returns fakeinput Facter.fact(:operatingsystemrelease).value.should == expected_output end end From fbf28932fa3fd6a3e504ba0f32bcacf5f543dc3d Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Tue, 19 Feb 2013 11:34:16 -0800 Subject: [PATCH 1163/3753] (maint) Update gem source in Gemfile Without this patch installed the following warning message is displayed on a Debian 6 system when running `bundle install --path vendor/bundle`. The source :rubygems is deprecated because HTTP requests are insecure. Please change your source to 'https://rubygems.org/' if possible This patch addresses the problem by changing the gem source to https://rubygems.org which avoids the warning. --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 0abec7398e..c0dbcc6ff2 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,4 @@ -source :rubygems +source "https://rubygems.org/" group :development do gem 'watchr' From aeb1aa278e8726e19ad28723e7e2b623267039cd Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Mon, 25 Feb 2013 13:20:57 -0800 Subject: [PATCH 1164/3753] (maint) Add Ruby 2.0.0 to Travis build matrix Without this patch we're not testing against Ruby 2.0.0 which has recently been released. This is a problem because we'd like a way to be notified if a change set breaks compatibility with future supported versions of Ruby. This patch should not be taken as an indication that we fully support Ruby 2.0, just as an indication that we plan to in the future. --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 3eec50c542..52dfb9e672 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,9 @@ notifications: rvm: - 1.9.3 - 1.8.7 + - 2.0.0 - ruby-head matrix: allow_failures: - rvm: ruby-head + - rvm: 2.0.0 From 18ba6f34c716335c10d8d72f2125eec7337bf887 Mon Sep 17 00:00:00 2001 From: Matthaus Owens Date: Mon, 25 Feb 2013 14:37:46 -0800 Subject: [PATCH 1165/3753] (maint) Remove rc_mocks from build_defaults Packaging internals have been updated to no longer require rc_mocks, so this commit removes the rc_mocks from the build_defaults. --- ext/build_defaults.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/ext/build_defaults.yaml b/ext/build_defaults.yaml index 5925405a27..ffaa878cff 100644 --- a/ext/build_defaults.yaml +++ b/ext/build_defaults.yaml @@ -10,7 +10,6 @@ gpg_key: '4BD6EC30' sign_tar: FALSE # a space separated list of mock configs final_mocks: 'pl-5-i386 pl-5-x86_64 pl-6-i386 pl-6-x86_64 fedora-16-i386 fedora-16-x86_64 fedora-17-i386 fedora-17-x86_64' -rc_mocks: 'pl-5-i386-dev pl-5-x86_64-dev pl-6-i386-dev pl-6-x86_64-dev fedora-16-i386-dev fedora-16-x86_64-dev fedora-17-i386-dev fedora-17-x86_64-dev' yum_host: 'burji.puppetlabs.com' yum_repo_path: '/opt/repository/yum/' build_gem: TRUE From 871342f08944a5c54588d0ce2511273914ad192a Mon Sep 17 00:00:00 2001 From: Matthaus Owens Date: Mon, 25 Feb 2013 14:39:59 -0800 Subject: [PATCH 1166/3753] (maint) Add f18 mocks to build_defaults Fedora 18 has been released to the world, so we should build packages for it. This commit adds the fedora 18 mocks to the build_defaults so we can easily supply fedora 18 packages. --- ext/build_defaults.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/build_defaults.yaml b/ext/build_defaults.yaml index ffaa878cff..bc2d6e640b 100644 --- a/ext/build_defaults.yaml +++ b/ext/build_defaults.yaml @@ -9,7 +9,7 @@ gpg_name: 'info@puppetlabs.com' gpg_key: '4BD6EC30' sign_tar: FALSE # a space separated list of mock configs -final_mocks: 'pl-5-i386 pl-5-x86_64 pl-6-i386 pl-6-x86_64 fedora-16-i386 fedora-16-x86_64 fedora-17-i386 fedora-17-x86_64' +final_mocks: 'pl-5-i386 pl-5-x86_64 pl-6-i386 pl-6-x86_64 fedora-16-i386 fedora-16-x86_64 fedora-17-i386 fedora-17-x86_64 fedora-18-i386 fedora-18-x86_64' yum_host: 'burji.puppetlabs.com' yum_repo_path: '/opt/repository/yum/' build_gem: TRUE From 25e194e189ba19de9f3a5971efc19133d1af40d3 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Tue, 26 Feb 2013 14:33:03 -0800 Subject: [PATCH 1167/3753] (maint) Fix Solaris 10 spec failures Without this patch applied we're seeing failures in the Solaris 10 spec tests. The root cause of this problem is a stub method that affects too much of the system as a whole rather than the unit being tested. This patch addresses the problem by narrowing the scope of the method stub to limit the overall effect on the system. --- spec/unit/operatingsystemrelease_spec.rb | 35 +++++++++++++++--------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/spec/unit/operatingsystemrelease_spec.rb b/spec/unit/operatingsystemrelease_spec.rb index 88f59bc7f8..9375596a20 100755 --- a/spec/unit/operatingsystemrelease_spec.rb +++ b/spec/unit/operatingsystemrelease_spec.rb @@ -125,21 +125,30 @@ end end - it "should fallback to the kernelrelease fact if /etc/release is empty" do - File.stubs(:open).with('/etc/release','r').raises EOFError - Facter::Util::Resolution.any_instance.stubs(:warn) # do not pollute test output - Facter.fact(:operatingsystemrelease).value.should == Facter.fact(:kernelrelease).value - end + context "malformed /etc/release files" do + before :each do + Facter::Util::Resolution.any_instance.stubs(:warn) + end + it "should fallback to the kernelrelease fact if /etc/release is empty" do + Facter::Util::FileRead.stubs(:read).with('/etc/release'). + raises EOFError + Facter.fact(:operatingsystemrelease).value. + should == Facter.fact(:kernelrelease).value + end - it "should fallback to the kernelrelease fact if /etc/release is not present" do - File.stubs(:open).with('/etc/release','r').raises Errno::ENOENT - Facter::Util::Resolution.any_instance.stubs(:warn) # do not pollute test output - Facter.fact(:operatingsystemrelease).value.should == Facter.fact(:kernelrelease).value - end + it "should fallback to the kernelrelease fact if /etc/release is not present" do + Facter::Util::FileRead.stubs(:read).with('/etc/release'). + raises Errno::ENOENT + Facter.fact(:operatingsystemrelease).value. + should == Facter.fact(:kernelrelease).value + end - it "should fallback to the kernelrelease fact if /etc/release cannot be parsed" do - File.stubs(:open).with('/etc/release','r').returns 'some future release string' - Facter.fact(:operatingsystemrelease).value.should == Facter.fact(:kernelrelease).value + it "should fallback to the kernelrelease fact if /etc/release cannot be parsed" do + Facter::Util::FileRead.stubs(:read).with('/etc/release'). + returns 'some future release string' + Facter.fact(:operatingsystemrelease).value. + should == Facter.fact(:kernelrelease).value + end end end From 071018115482457dc0750cf0f0bf0d23180d25f4 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Tue, 26 Feb 2013 15:06:46 -0800 Subject: [PATCH 1168/3753] (maint) Fix failing manufacturer spec on Solaris 10 Without this patch the specs are failing against solaris 10. The root cause of the problem is that the manufacturer spec is getting a value from a different resolver than the one being tested. The example expected the result to be nil rather than being resolved by the alternative resolver. This patch fixes the issue by changing the expectation such that the manufacturer spec is not the value set by the resolver being tested. --- spec/unit/util/manufacturer_spec.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/spec/unit/util/manufacturer_spec.rb b/spec/unit/util/manufacturer_spec.rb index 038126e953..172f8c5765 100755 --- a/spec/unit/util/manufacturer_spec.rb +++ b/spec/unit/util/manufacturer_spec.rb @@ -31,14 +31,13 @@ Facter.value(:productname).should == "SPARC Enterprise T5220" end - it "should not set manufacturer or productname if prtdiag output is nil" do + it "should not set manufacturer if prtdiag output is nil" do # Stub kernel so we don't have windows fall through to its own mechanism Facter.fact(:kernel).stubs(:value).returns("SunOS") Facter::Util::Resolution.stubs(:exec).returns(nil) Facter::Manufacturer.prtdiag_sparc_find_system_info() - Facter.value(:manufacturer).should be_nil - Facter.value(:productname).should be_nil + Facter.value(:manufacturer).should_not == "Sun Microsystems" end it "should strip white space on dmi output with spaces" do From b07bcd58b417ea868cae90d3051fb8ca28788b5b Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Thu, 28 Feb 2013 18:36:44 -0800 Subject: [PATCH 1169/3753] [packaging] Update mocks for rpmbuilder mock format The Puppet Labs mocks created by rpmbuilder have assumed a new format, pl-el-* vs el-*, in order to avoid overwriting the configurations supplied by the mock package. This commit updates the mocks in facter to reflect the new standard, so that we can continue to build packages with builders created with the rpmbuilder module. Signed-off-by: Moses Mendoza --- ext/build_defaults.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/build_defaults.yaml b/ext/build_defaults.yaml index bc2d6e640b..6657a7e526 100644 --- a/ext/build_defaults.yaml +++ b/ext/build_defaults.yaml @@ -9,7 +9,7 @@ gpg_name: 'info@puppetlabs.com' gpg_key: '4BD6EC30' sign_tar: FALSE # a space separated list of mock configs -final_mocks: 'pl-5-i386 pl-5-x86_64 pl-6-i386 pl-6-x86_64 fedora-16-i386 fedora-16-x86_64 fedora-17-i386 fedora-17-x86_64 fedora-18-i386 fedora-18-x86_64' +final_mocks: 'pl-el-5-i386 pl-el-5-x86_64 pl-el-6-i386 pl-el-6-x86_64 pl-fedora-16-i386 pl-fedora-16-x86_64 pl-fedora-17-i386 pl-fedora-17-x86_64 pl-fedora-18-i386 pl-fedora-18-x86_64' yum_host: 'burji.puppetlabs.com' yum_repo_path: '/opt/repository/yum/' build_gem: TRUE From 11e9d5257256fc791347f6ea8d4c07a240a65482 Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Tue, 5 Mar 2013 13:25:25 -0800 Subject: [PATCH 1170/3753] Update lib/facter/version.rb for 1.6.18-rc1 Signed-off-by: Moses Mendoza --- lib/facter/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/version.rb b/lib/facter/version.rb index 8da96e8253..6096e7db0e 100644 --- a/lib/facter/version.rb +++ b/lib/facter/version.rb @@ -1,6 +1,6 @@ module Facter if not defined? FACTERVERSION then - FACTERVERSION = '1.6.17' + FACTERVERSION = '1.6.18-rc1' end def self.version From 63d2d617eb5c4c37aa326613981fbada633fe78f Mon Sep 17 00:00:00 2001 From: Russell Harrison Date: Fri, 1 Mar 2013 17:45:04 -0500 Subject: [PATCH 1171/3753] (#15001) Return correct value for ipaddress6 for Fedora 17+ --- lib/facter/ipaddress6.rb | 3 +-- lib/facter/util/ip.rb | 4 ++-- .../ifconfig/ifconfig_net_tools_1.60.txt | 19 +++++++++++++++++++ .../ifconfig/ifconfig_ubuntu_1204.txt | 16 ++++++++++++++++ spec/unit/ipaddress6_spec.rb | 9 +++++++++ 5 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 spec/fixtures/ifconfig/ifconfig_net_tools_1.60.txt create mode 100644 spec/fixtures/ifconfig/ifconfig_ubuntu_1204.txt diff --git a/lib/facter/ipaddress6.rb b/lib/facter/ipaddress6.rb index 1d0bd7a93d..cf804ab76d 100644 --- a/lib/facter/ipaddress6.rb +++ b/lib/facter/ipaddress6.rb @@ -39,8 +39,7 @@ def get_address_after_token(output, token, return_first=false) confine :kernel => :linux setcode do output = Facter::Util::IP.exec_ifconfig(["2>/dev/null"]) - - get_address_after_token(output, 'inet6 addr:') + get_address_after_token(output, 'inet6(?: addr:)?') end end diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index 11683ef16b..96fa153c11 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -5,8 +5,8 @@ module Facter::Util::IP # a given platform or set of platforms. REGEX_MAP = { :linux => { - :ipaddress => /inet addr:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, - :ipaddress6 => /inet6 addr: ((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/, + :ipaddress => /inet (?:addr:)?([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, + :ipaddress6 => /inet6 (?:addr: )?((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/, :macaddress => /(?:ether|HWaddr)\s+((\w{1,2}:){5,}\w{1,2})/, :netmask => /Mask:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, :mtu => /MTU:(\d+)/ diff --git a/spec/fixtures/ifconfig/ifconfig_net_tools_1.60.txt b/spec/fixtures/ifconfig/ifconfig_net_tools_1.60.txt new file mode 100644 index 0000000000..dcfdaf1232 --- /dev/null +++ b/spec/fixtures/ifconfig/ifconfig_net_tools_1.60.txt @@ -0,0 +1,19 @@ +em1: flags=4163 mtu 1500 + inet 131.252.209.153 netmask 255.255.255.0 broadcast 192.168.76.255 + inet6 2610:10:20:209:212:3fff:febe:2201 prefixlen 128 scopeid 0x0 + inet6 fe80::221:ccff:fe4b:297d prefixlen 64 scopeid 0x20 + ether 00:21:cc:4b:29:7d txqueuelen 1000 (Ethernet) + RX packets 27222144 bytes 31247414760 (29.1 GiB) + RX errors 0 dropped 0 overruns 0 frame 0 + TX packets 10259038 bytes 7784519352 (7.2 GiB) + TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 + device interrupt 20 memory 0xd2600000-d2620000 + +lo: flags=73 mtu 16436 + inet 127.0.0.1 netmask 255.0.0.0 + inet6 ::1 prefixlen 128 scopeid 0x10 + loop txqueuelen 0 (Local Loopback) + RX packets 257371 bytes 37104110 (35.3 MiB) + RX errors 0 dropped 0 overruns 0 frame 0 + TX packets 257371 bytes 37104110 (35.3 MiB) + TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 diff --git a/spec/fixtures/ifconfig/ifconfig_ubuntu_1204.txt b/spec/fixtures/ifconfig/ifconfig_ubuntu_1204.txt new file mode 100644 index 0000000000..f2800cc7b8 --- /dev/null +++ b/spec/fixtures/ifconfig/ifconfig_ubuntu_1204.txt @@ -0,0 +1,16 @@ +eth0 Link encap:Ethernet HWaddr 42:01:0a:57:50:6e + inet addr:10.87.80.110 Bcast:10.87.80.110 Mask:255.255.255.255 + UP BROADCAST RUNNING MULTICAST MTU:1460 Metric:1 + RX packets:1609444 errors:0 dropped:0 overruns:0 frame:0 + TX packets:1479569 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:1000 + RX bytes:673812693 (673.8 MB) TX bytes:221186872 (221.1 MB) + +lo Link encap:Local Loopback + inet addr:127.0.0.1 Mask:255.0.0.0 + UP LOOPBACK RUNNING MTU:16436 Metric:1 + RX packets:5435415 errors:0 dropped:0 overruns:0 frame:0 + TX packets:5435415 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:0 + RX bytes:734540224 (734.5 MB) TX bytes:734540224 (734.5 MB) + diff --git a/spec/unit/ipaddress6_spec.rb b/spec/unit/ipaddress6_spec.rb index 0ec8d840e4..de72f66fc0 100755 --- a/spec/unit/ipaddress6_spec.rb +++ b/spec/unit/ipaddress6_spec.rb @@ -34,6 +34,15 @@ def netsh_fixture(filename) Facter.value(:ipaddress6).should == "2610:10:20:209:212:3fff:febe:2201" end + it "should return ipaddress6 information for Linux with recent net-tools" do + Facter::Util::Resolution.stubs(:exec).with('uname -s').returns('Linux') + Facter::Util::IP.stubs(:get_ifconfig).returns("/sbin/ifconfig") + Facter::Util::IP.stubs(:exec_ifconfig).with(["2>/dev/null"]). + returns(ifconfig_fixture('ifconfig_net_tools_1.60.txt')) + + Facter.value(:ipaddress6).should == "2610:10:20:209:212:3fff:febe:2201" + end + it "should return ipaddress6 information for Solaris" do Facter::Util::Resolution.stubs(:exec).with('uname -s').returns('SunOS') Facter::Util::IP.stubs(:get_ifconfig).returns("/usr/sbin/ifconfig") From da5d14094f396b57991a1027ee3ed13c0fdc8fbe Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Thu, 7 Mar 2013 13:49:57 -0800 Subject: [PATCH 1172/3753] (maint) Update Gemfile to work well on windows Without this patch applied, the following issues are encountered trying to get Facter working in a development environment on Windows. This is with Ruby 1.9.3 and Bundler 1.2.4. First, the runtime provided by rubyinstaller.org is unable to verify the SSL certificate of rubygems.org. For Windows, we work around the problem by allowing the user to specify an alternate gem source using the GEM_SOURCE environment variable. For example, `$ENV:GEM_SOURCE="http://rubygems.org/"`. Next, `bundle exec facter --version` does not work because Facter 1.7 depends on the win32-dir gem and this dependency is not specified in the Gemfile. This patch addresses the problem by specifying the dependency on win32-dir. --- Gemfile | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index c0dbcc6ff2..52ea59bd43 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,8 @@ -source "https://rubygems.org/" +if gem_source = ENV['GEM_SOURCE'] + source gem_source +else + source "https://rubygems.org/" +end group :development do gem 'watchr' @@ -13,6 +17,13 @@ group :development, :test do gem 'puppetlabs_spec_helper', :require => false end +platform :mswin, :mingw do + gem "win32-api", "~> 1.4.8" + gem "win32-dir", "~> 0.3.7" + gem "windows-api", "~> 0.4.1" + gem "windows-pr", "~> 1.2.1" +end + if File.exists? "#{__FILE__}.local" eval(File.read("#{__FILE__}.local"), binding) end From ba9e1a24d87fcd6968035caa4d811cf19a0577c7 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Thu, 7 Mar 2013 14:03:48 -0800 Subject: [PATCH 1173/3753] (maint) Clean up require => false in Gemfile This is just a maintenance patch to clean up all of the `:require => false` entries in the bundler Gemfile. These statements prevent Bundler from loading the gem libraries when running `bundle exec` or `Bundler.setup`. We don't run `Bundler.setup` in the code, so the libraries will only be pre-loaded when run through `bundle exec` which is by design. This does not appear to cause any problems with the spec examples. --- Gemfile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Gemfile b/Gemfile index 52ea59bd43..d6b928c8bf 100644 --- a/Gemfile +++ b/Gemfile @@ -10,11 +10,11 @@ end group :development, :test do gem 'rake' - gem 'facter', ">= 1.0.0", :path => File.expand_path("..", __FILE__), :require => false - gem 'rspec', "~> 2.11.0", :require => false - gem 'mocha', "~> 0.10.5", :require => false - gem 'json', "~> 1.7", :require => false - gem 'puppetlabs_spec_helper', :require => false + gem 'facter', ">= 1.0.0", :path => File.expand_path("..", __FILE__) + gem 'rspec', "~> 2.11.0" + gem 'mocha', "~> 0.10.5" + gem 'json', "~> 1.7" + gem 'puppetlabs_spec_helper' end platform :mswin, :mingw do From 9c38f19b6ab4258de86d5960d0f62e5d95df797b Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Thu, 7 Mar 2013 14:45:48 -0800 Subject: [PATCH 1174/3753] (#16948) Fix hardwaremodel fact on windows 32 bit Without this patch applied the hardwaremodel fact will return 'x64' when a 32 bit operating system is running inside of a 64 bit CPU. This is a problem because it is inconsistent with the way Facter operates on other platforms where the behavior is to inspect the operating system rather than the CPU itself. This patch fixes the problem by checking the AddressWidth attribute which will report 32 when a 32 bit OS is running on a 64 bit CPU. The attribute will report 64 when a 64 bit OS is running. --- lib/facter/hardwaremodel.rb | 5 +++-- spec/unit/hardwaremodel_spec.rb | 9 ++++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/facter/hardwaremodel.rb b/lib/facter/hardwaremodel.rb index 752ff865fc..adfda481b8 100644 --- a/lib/facter/hardwaremodel.rb +++ b/lib/facter/hardwaremodel.rb @@ -36,12 +36,13 @@ # windows 8 release. --jeffweiss 23 May 2012 require 'facter/util/wmi' model = "" - Facter::Util::WMI.execquery("select Architecture, Level from Win32_Processor").each do |cpu| + Facter::Util::WMI.execquery("select Architecture, Level, AddressWidth from Win32_Processor").each do |cpu| model = case cpu.Architecture when 11 then 'neutral' # PROCESSOR_ARCHITECTURE_NEUTRAL when 10 then 'i686' # PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 - when 9 then 'x64' # PROCESSOR_ARCHITECTURE_AMD64 + when 9 then # PROCESSOR_ARCHITECTURE_AMD64 + cpu.AddressWidth == 32 ? "i#{cpu.Level}86" : 'x64' # 32 bit OS on 64 bit CPU when 8 then 'msil' # PROCESSOR_ARCHITECTURE_MSIL when 7 then 'alpha64' # PROCESSOR_ARCHITECTURE_ALPHA64 when 6 then 'ia64' # PROCESSOR_ARCHITECTURE_IA64 diff --git a/spec/unit/hardwaremodel_spec.rb b/spec/unit/hardwaremodel_spec.rb index fdbb5762af..a168c11ef5 100644 --- a/spec/unit/hardwaremodel_spec.rb +++ b/spec/unit/hardwaremodel_spec.rb @@ -25,10 +25,17 @@ end it "should detect x64" do - cpu = mock('cpu', :Architecture => 9) + cpu = mock('cpu', :Architecture => 9, :AddressWidth => 64) Facter::Util::WMI.expects(:execquery).returns([cpu]) Facter.fact(:hardwaremodel).value.should == "x64" end + + it "(#16948) reports i686 when a 32 bit OS is running on a 64 bit CPU" do + cpu = mock('cpu', :Architecture => 9, :AddressWidth => 32, :Level => 6) + Facter::Util::WMI.expects(:execquery).returns([cpu]) + + Facter.fact(:hardwaremodel).value.should == "i686" + end end end From f188d096dca70eb021381780ce302f58aa6df7c9 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Fri, 8 Mar 2013 12:52:12 -0800 Subject: [PATCH 1175/3753] (maint) centralize system fact stubbing in virtual specs The specs for the virtual fact on various platforms were duplicating method stubs for the same fact. This commit removes the stub duplication and puts the stubs into `before` blocks. --- spec/unit/virtual_spec.rb | 33 +++++++++------------------------ 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index d0d4d15619..3b6a7e53ae 100755 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -46,26 +46,26 @@ end describe "on Darwin" do - it "should be parallels with Parallels vendor id" do + before do Facter.fact(:kernel).stubs(:value).returns("Darwin") + end + + it "should be parallels with Parallels vendor id" do Facter::Util::Macosx.stubs(:profiler_data).returns({ "spdisplays_vendor-id" => "0x1ab8" }) Facter.fact(:virtual).value.should == "parallels" end it "should be parallels with Parallels vendor name" do - Facter.fact(:kernel).stubs(:value).returns("Darwin") Facter::Util::Macosx.stubs(:profiler_data).returns({ "spdisplays_vendor" => "Parallels" }) Facter.fact(:virtual).value.should == "parallels" end it "should be vmware with VMWare vendor id" do - Facter.fact(:kernel).stubs(:value).returns("Darwin") Facter::Util::Macosx.stubs(:profiler_data).returns({ "spdisplays_vendor-id" => "0x15ad" }) Facter.fact(:virtual).value.should == "vmware" end it "should be vmware with VMWare vendor name" do - Facter.fact(:kernel).stubs(:value).returns("Darwin") Facter::Util::Macosx.stubs(:profiler_data).returns({ "spdisplays_vendor" => "VMWare" }) Facter.fact(:virtual).value.should == "vmware" end @@ -73,9 +73,11 @@ describe "on Linux" do before(:each) do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:operatingsystem).stubs(:value).returns("Linux") + Facter::Util::Resolution.stubs(:exec).with("vmware -v").returns false - Facter.fact(:operatingsystem).stubs(:value).returns(true) - # Ensure the tests don't fail on Xen + FileTest.stubs(:exists?).with("/proc/sys/xen").returns false FileTest.stubs(:exists?).with("/sys/bus/xen").returns false FileTest.stubs(:exists?).with("/proc/xen").returns false @@ -83,39 +85,32 @@ end it "should be parallels with Parallels vendor id from lspci 2>/dev/null" do - Facter.fact(:kernel).stubs(:value).returns("Linux") Facter::Util::Virtual.stubs(:lspci).returns("01:00.0 VGA compatible controller: Unknown device 1ab8:4005") Facter.fact(:virtual).value.should == "parallels" end it "should be parallels with Parallels vendor name from lspci 2>/dev/null" do - Facter.fact(:kernel).stubs(:value).returns("Linux") Facter::Util::Virtual.stubs(:lspci).returns("01:00.0 VGA compatible controller: Parallels Display Adapter") Facter.fact(:virtual).value.should == "parallels" end it "should be vmware with VMware vendor name from lspci 2>/dev/null" do - Facter.fact(:kernel).stubs(:value).returns("Linux") Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns("00:0f.0 VGA compatible controller: VMware Inc [VMware SVGA II] PCI Display Adapter") Facter.fact(:virtual).value.should == "vmware" end it "should be virtualbox with VirtualBox vendor name from lspci 2>/dev/null" do - Facter.fact(:kernel).stubs(:value).returns("Linux") Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns("00:02.0 VGA compatible controller: InnoTek Systemberatung GmbH VirtualBox Graphics Adapter") Facter.fact(:virtual).value.should == "virtualbox" end it "should be vmware with VMWare vendor name from dmidecode" do - Facter.fact(:kernel).stubs(:value).returns("Linux") Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("On Board Device 1 Information\nType: Video\nStatus: Disabled\nDescription: VMware SVGA II") Facter.fact(:virtual).value.should == "vmware" end it "should be xen0 with xen dom0 files in /proc" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:operatingsystem).stubs(:value).returns("Linux") Facter.fact(:hardwaremodel).stubs(:value).returns("i386") Facter::Util::Virtual.expects(:xen?).returns(true) FileTest.expects(:exists?).with("/proc/xen/xsd_kva").returns(true) @@ -123,8 +118,6 @@ end it "should be xenu with xen domU files in /proc" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:operatingsystem).stubs(:value).returns("Linux") Facter.fact(:hardwaremodel).stubs(:value).returns("i386") Facter::Util::Virtual.expects(:xen?).returns(true) FileTest.expects(:exists?).with("/proc/xen/xsd_kva").returns(false) @@ -133,40 +126,34 @@ end it "should be xenhvm with Xen HVM vendor name from lspci 2>/dev/null" do - Facter.fact(:kernel).stubs(:value).returns("Linux") Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns("00:03.0 Unassigned class [ff80]: XenSource, Inc. Xen Platform Device (rev 01)") Facter.fact(:virtual).value.should == "xenhvm" end it "should be xenhvm with Xen HVM vendor name from dmidecode" do - Facter.fact(:kernel).stubs(:value).returns("Linux") Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("System Information\nManufacturer: Xen\nProduct Name: HVM domU") Facter.fact(:virtual).value.should == "xenhvm" end it "should be parallels with Parallels vendor name from dmidecode" do - Facter.fact(:kernel).stubs(:value).returns("Linux") Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("On Board Device Information\nType: Video\nStatus: Disabled\nDescription: Parallels Video Adapter") Facter.fact(:virtual).value.should == "parallels" end it "should be virtualbox with VirtualBox vendor name from dmidecode" do - Facter.fact(:kernel).stubs(:value).returns("Linux") Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("BIOS Information\nVendor: innotek GmbH\nVersion: VirtualBox\n\nSystem Information\nManufacturer: innotek GmbH\nProduct Name: VirtualBox\nFamily: Virtual Machine") Facter.fact(:virtual).value.should == "virtualbox" end it "should be hyperv with Microsoft vendor name from lspci 2>/dev/null" do - Facter.fact(:kernel).stubs(:value).returns("Linux") Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns("00:08.0 VGA compatible controller: Microsoft Corporation Hyper-V virtual VGA") Facter.fact(:virtual).value.should == "hyperv" end it "should be hyperv with Microsoft vendor name from dmidecode" do - Facter.fact(:kernel).stubs(:value).returns("Linux") Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("System Information\nManufacturer: Microsoft Corporation\nProduct Name: Virtual Machine") Facter.fact(:virtual).value.should == "hyperv" @@ -176,10 +163,10 @@ describe "on Solaris" do before(:each) do Facter::Util::Resolution.stubs(:exec).with("vmware -v").returns false + Facter.fact(:kernel).stubs(:value).returns("SunOS") end it "should be vmware with VMWare vendor name from prtdiag" do - Facter.fact(:kernel).stubs(:value).returns("SunOS") Facter.fact(:hardwaremodel).stubs(:value).returns(nil) Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns(nil) @@ -188,7 +175,6 @@ end it "should be parallels with Parallels vendor name from prtdiag" do - Facter.fact(:kernel).stubs(:value).returns("SunOS") Facter.fact(:hardwaremodel).stubs(:value).returns(nil) Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns(nil) @@ -197,7 +183,6 @@ end it "should be virtualbox with VirtualBox vendor name from prtdiag" do - Facter.fact(:kernel).stubs(:value).returns("SunOS") Facter.fact(:hardwaremodel).stubs(:value).returns(nil) Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns(nil) From b343205868ddb0489273d45c6d1cdafe82bedb8a Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Mon, 11 Mar 2013 11:04:18 -0700 Subject: [PATCH 1176/3753] (maint) Update link to the contributor license agreement New link: http://links.puppetlabs.com/cla as per Eric. [ci skip] --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 44d68a5593..7d06a977e7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -49,7 +49,7 @@ top of things. ## Submitting Changes -* Sign the [Contributor License Agreement](https://projects.puppetlabs.com/contributor_licenses/sign). +* Sign the [Contributor License Agreement](http://links.puppetlabs.com/cla). * Push your changes to a topic branch in your fork of the repository. * Submit a pull request to the repository in the puppetlabs organization. * Update your Redmine ticket to mark that you have submitted code and are ready for it to be reviewed. @@ -59,7 +59,7 @@ top of things. * [More information on contributing](http://links.puppetlabs.com/contribute-to-puppet) * [Bug tracker (Redmine)](http://projects.puppetlabs.com) -* [Contributor License Agreement](https://projects.puppetlabs.com/contributor_licenses/sign) +* [Contributor License Agreement](http://links.puppetlabs.com/cla) * [General GitHub documentation](http://help.github.com/) * [GitHub pull request documentation](http://help.github.com/send-pull-requests/) * #puppet-dev IRC channel on freenode.org From b00e77389812c91317932a2af992b232caaadc22 Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Wed, 13 Mar 2013 11:12:58 -0700 Subject: [PATCH 1177/3753] Update lib/facter/version.rb for 1.6.18 --- lib/facter/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/version.rb b/lib/facter/version.rb index 6096e7db0e..d922c3be64 100644 --- a/lib/facter/version.rb +++ b/lib/facter/version.rb @@ -1,6 +1,6 @@ module Facter if not defined? FACTERVERSION then - FACTERVERSION = '1.6.18-rc1' + FACTERVERSION = '1.6.18' end def self.version From db8a80c42351fd07e3cc0c71cef7d0e585edf15c Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Mon, 11 Mar 2013 18:15:38 -0700 Subject: [PATCH 1178/3753] (maint) Add pry to Gemfile as a development dependency Without this patch it's difficult to do interactive debugging and introspection of a live, running facter process. This is a problem because it makes developing Facter more difficult than it needs to be. This patch addresses the problem by listing pry as a development dependency of Facter. With this patch applied, the following command may be used to drop into a debugger using the `require 'pry'; binding.pry` statement while running the spec tests. bundle exec rspec spec/ This patch also adds the yard and redcarpet development dependencies which synchronize Facter 1.7 with Puppet master regarding YARD documentation. --- Gemfile | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index d6b928c8bf..ac84800131 100644 --- a/Gemfile +++ b/Gemfile @@ -4,8 +4,12 @@ else source "https://rubygems.org/" end -group :development do - gem 'watchr' +# C Ruby (MRI) or Rubinius, but NOT Windows +platforms :ruby do + gem 'watchr', :group => :development + gem 'pry', :group => :development + gem 'yard', :group => :development + gem 'redcarpet', :group => :development end group :development, :test do From 00544caf8d585114d6ad4241e9b7cdf25c534dd5 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Mon, 11 Mar 2013 18:16:58 -0700 Subject: [PATCH 1179/3753] Add on_flush fact resolution DSL method Without this patch it is difficult to model an expensive system call inside of an object instance, generate N dynamic facts from the data contained in the model, then keep all of the resulting fact values in sync with one another without making N system calls. This patch addresses the problem by implementing a callback mechanism when fact values are flushed. Fact authors may define a so-called `on_flush` block that will be executed when the fact value is flushed by Facter. This block is then useful to invalidate the model data referenced by all of the N dynamic facts. The first fact to refresh it's value will then incur the expense of the system call, but the next N-1 facts do not need to make the expensive system call as their data has already been refreshed. That is to say, flushing the value for all N facts will only incur 1 system call instead of N system calls using the traditional setcode block. --- lib/facter/util/fact.rb | 6 +++++- lib/facter/util/resolution.rb | 31 +++++++++++++++++++++++++++++++ spec/unit/util/fact_spec.rb | 29 +++++++++++++++++++++++++++-- spec/unit/util/resolution_spec.rb | 21 +++++++++++++++++++++ 4 files changed, 84 insertions(+), 3 deletions(-) diff --git a/lib/facter/util/fact.rb b/lib/facter/util/fact.rb index 9279b98360..3ada605e2f 100644 --- a/lib/facter/util/fact.rb +++ b/lib/facter/util/fact.rb @@ -44,8 +44,12 @@ def add(value = nil, &block) end end - # Flush any cached values. + ## + # Flush any cached values. If the resolver has a callback block defined + # using the on_flush DSL method, then invoke that block by sending a message + # to Resolution#flush. def flush + @resolves.each { |r| r.flush } @value = nil end diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 18ae76f948..49d355da4d 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -236,6 +236,37 @@ def setcode(string = nil, interp = nil, &block) end end + ## + # on_flush accepts a block and executes the block when the resolution's value + # is flushed. This makes it possible to model a single, expensive system + # call inside of a Ruby object and then define multiple dynamic facts which + # resolve by sending messages to the model instance. If one of the dynamic + # facts is flushed then it can, in turn, flush the data stored in the model + # instance to keep all of the dynamic facts in sync without making multiple, + # expensive, system calls. + # + # Please see the Solaris zones fact for an example of how this feature may be + # used. + # + # @see Facter::Util::Fact#flush + # @see Facter::Util::Resolution#flush + # + # @api public + def on_flush(&block) + @on_flush_block = block + end + + ## + # flush executes the block, if any, stored by the {on_flush} method + # + # @see Facter::Util::Fact#flush + # @see Facter::Util::Resolution#on_flush + # + # @api private + def flush + @on_flush_block.call if @on_flush_block + end + def interpreter Facter.warnonce "The 'Facter::Util::Resolution.interpreter' method is deprecated and will be removed in a future version." @interpreter diff --git a/spec/unit/util/fact_spec.rb b/spec/unit/util/fact_spec.rb index ad37321539..49a6d9fdcc 100755 --- a/spec/unit/util/fact_spec.rb +++ b/spec/unit/util/fact_spec.rb @@ -109,7 +109,32 @@ end end - it "should have a method for flushing the cached fact" do - Facter::Util::Fact.new(:foo).should respond_to(:flush) + describe '#flush' do + subject do + Facter::Util::Fact.new(:foo) + end + context 'basic facts using setcode' do + it "flushes the cached value when invoked" do + system = mock('some_system_call') + system.expects(:data).twice.returns(100,200) + + subject.add { setcode { system.data } } + 5.times { subject.value.should == 100 } + subject.flush + subject.value.should == 200 + end + end + context 'facts using setcode and on_flush' do + it 'invokes the block passed to on_flush' do + model = { :data => "Hello World" } + subject.add do + on_flush { model[:data] = "FLUSHED!" } + setcode { model[:data] } + end + subject.value.should == "Hello World" + subject.flush + subject.value.should == "FLUSHED!" + end + end end end diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index 271cde9865..a989dd8613 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -155,6 +155,27 @@ def handy_method() end end + describe 'callbacks when flushing facts' do + class FlushFakeError < StandardError; end + + subject do + Facter::Util::Resolution.new("jeff") + end + + context '#on_flush' do + it 'accepts a block with on_flush' do + subject.on_flush() { raise NotImplementedError } + end + end + + context '#flush' do + it 'calls the block passed to on_flush' do + subject.on_flush() { raise FlushFakeError } + expect { subject.flush }.to raise_error FlushFakeError + end + end + end + it "should be able to return a value" do Facter::Util::Resolution.new("yay").should respond_to(:value) end From e4eb583d4ac48e7dcae5335bda278595340591ca Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Wed, 13 Mar 2013 15:08:06 -0700 Subject: [PATCH 1180/3753] (#17890) Refactor the facter solaris zone facts Without this patch the solaris zones fact executes `/usr/sbin/zoneadm list -cp` on all systems. This behavior is not confined to SunOS systems. This is a problem because this creates noise for the end user. The root cause of the problem is that the block passed to the Resolution instance executes before the confinement statement takes effect. In the scope of this block is a call to Resolution.exec('/usr/sbin/zoneadm list -cp'). This patch addresses the problem by moving the behavior for defining the set of dynamic facts into the Facter::Util::SolarisZones.add_facts class method. This method is called only when running on the SunOS kernel. The add_facts method has the behavior of instantiating an instance of SolarisZones to model the output of the zoneadm command. We model this output because the system command is extremely expensive from a performance and efficiency point of view. All of the dynamic facts that are defined as a result of parsing the zoneadm output retain a reference to this single model instance. In the situation where one, many, or all of the fact values are flushed then the model instance is also flushed. The first fact to resolve again will refresh the model and all subsequent resolutions will re-use the data contained in the model. It is not uncommon to have 10 or more zones defined on a system and there are 7 dynamic facts generated for each zone. Without this shared model instance it is unclear how to avoid executing 10*7 system calls when all of the dynamic cache values are flushed. --- lib/facter/util/solaris_zones.rb | 153 +++++++++++++++++++++++++++ lib/facter/zones.rb | 15 +-- spec/unit/util/solaris_zones_spec.rb | 127 ++++++++++++++++++++++ spec/unit/zones_spec.rb | 2 +- 4 files changed, 284 insertions(+), 13 deletions(-) create mode 100644 lib/facter/util/solaris_zones.rb create mode 100644 spec/unit/util/solaris_zones_spec.rb diff --git a/lib/facter/util/solaris_zones.rb b/lib/facter/util/solaris_zones.rb new file mode 100644 index 0000000000..76c4a2dd29 --- /dev/null +++ b/lib/facter/util/solaris_zones.rb @@ -0,0 +1,153 @@ +require 'facter/util/resolution' + +module Facter +module Util + ## + # Provide a set of utility methods to interact with Solaris zones. This class + # is expected to be instantiated once per set of resolutions in order to + # cache the output of the zoneadm command, which can be quite expensive. + # + # @api private + class SolarisZones + attr_reader :zone_hash + attr_reader :zoneadm_cmd + attr_reader :zoneadm_output + attr_reader :zoneadm_keys + + ## + # add_facts defines all of the facts for solaris zones, for example `zones`, + # `zone_global_id`, `zone_global_status`, etc... This method defines the + # static fact named `zones`. The value of this fact is the numver of zones + # reported by the zoneadm system command. The `zones` fact also defines + # all of the dynamic facts describing the following seven attribute values + # for each zone. + # + # Zones may be added to the system while Facter is loaded. In order to + # define new dynamic facts that reflect this new information, the `virtual` + # will define new facts as a side effect of refreshing it's own value. + # + # @api private + def self.add_facts + model = new + model.refresh + model.add_dynamic_facts + Facter.add("zones") do + setcode do + model.refresh if model.flushed? + model.add_dynamic_facts + model.count + end + on_flush do + model.flush! + end + end + end + + ## + # @param [Hash] opts the options to create the instance with + # @option opts [String] :zoneadm_cmd ('/usr/sbin/zoneadm list -cp') the + # system command to inspect zones + # @option opts [String] :zoneadm_output (nil) the cached output of the + # zoneadm_cmd + def initialize(opts = {}) + @zoneadm_keys = [:id, :name, :status, :path, :uuid, :brand, :iptype] + @zoneadm_cmd = opts[:zoneadm_cmd] || '/usr/sbin/zoneadm list -cp' + if opts[:zoneadm_output] + @zoneadm_output = opts[:zoneadm_output] + end + end + + ## + # add_dynamic_facts defines all of the dynamic facts derived from parsing + # the output of the zoneadm command. The zone facts are dynamic, so this + # method has the behavior of figuring out what dynamic zone facts need to + # be defined and how they should be resolved. + # + # @param model [SolarisZones] the model used to store data from the system + # + # @api private + def add_dynamic_facts + model = self + zone_hash.each_pair do |zone, attr_hsh| + attr_hsh.keys.each do |attr| + Facter.add("zone_#{zone}_#{attr}") do + setcode do + model.refresh if model.flushed? + # Don't resolve if the zone has since been deleted + if zone_hsh = model.zone_hash[zone] + zone_hsh[attr] # the value + end + end + on_flush do + model.flush! + end + end + end + end + end + + ## + # refresh executes the zoneadm_cmd and stores the output data. + # + # @api private + # + # @return [Hash] the parsed output of the zoneadm command + def refresh + @zoneadm_output = Facter::Util::Resolution.exec(zoneadm_cmd) + parse! + end + + ## + # parse! parses the string stored in {@zoneadm_output} and stores the + # resulting Hash data structure in {@zone_hash} + # + # @api private + def parse! + rows = @zoneadm_output.split("\n").collect { |line| line.split(':') } + + @zone_hash = rows.inject({}) do |memo, fields| + zone = fields[1].intern + # Transform the row into a hash with keys named by the column names + memo[zone] = Hash[*@zoneadm_keys.zip(fields).flatten] + memo + end + end + private :parse! + + ## + # count returns the number of running zones, including the global zone. + # This method is intended to be used from the setcode block of the `zones` + # fact. + # + # @api private + # + # @return [Fixnum, nil] the number of running zones or nil if the number + # could not be determined. + def count + if @zone_hash + @zone_hash.size + end + end + + ## + # flush! purges the saved data from the zoneadm_cmd output + # + # @api private + def flush! + @zoneadm_output = nil + @zone_hash = nil + end + + ## + # flushed? returns true if the instance has no parsed data accessible via + # the {zone_hash} method. + # + # @api private + # + # @return [Boolean] true if there is no parsed data, false otherwise + def flushed? + !@zone_hash + end + end +end +end diff --git a/lib/facter/zones.rb b/lib/facter/zones.rb index 47646121a3..2dbcc7ccaf 100644 --- a/lib/facter/zones.rb +++ b/lib/facter/zones.rb @@ -11,16 +11,7 @@ # # Caveats: # We dont support below s10 where zones are not available. - -Facter.add("zones") do - confine :kernel => :sunos - fmt = [:id, :name, :status, :path, :uuid, :brand, :iptype] - l = Facter::Util::Resolution.exec('/usr/sbin/zoneadm list -cp').split("\n").collect{|l|l.split(':')}.each do |val| - fmt.each_index do |i| - Facter.add "zone_%s_%s" % [val[1], fmt[i]] do - setcode { val[i] } - end - end - end - setcode { l.length } +require 'facter/util/solaris_zones' +if Facter.value(:kernel) == 'SunOS' + Facter::Util::SolarisZones.add_facts end diff --git a/spec/unit/util/solaris_zones_spec.rb b/spec/unit/util/solaris_zones_spec.rb new file mode 100644 index 0000000000..c5c48ef1e6 --- /dev/null +++ b/spec/unit/util/solaris_zones_spec.rb @@ -0,0 +1,127 @@ +require 'spec_helper' +require 'facter/util/solaris_zones' + +describe Facter::Util::SolarisZones do + let :zone_list do + zone_list = <<-EOF +0:global:running:/::native:shared +-:local:configured:/::native:shared +-:zoneA:stopped:/::native:shared + EOF + end + + let :zone_list2 do + zone_list = <<-EOF +0:global:running:/::native:shared +-:local:configured:/::native:shared +-:zoneB:stopped:/::native:shared +-:zoneC:stopped:/::native:shared + EOF + end + + subject do + described_class.new(:zoneadm_output => zone_list) + end + + describe '.add_facts' do + before :each do + zones = described_class.new(:zoneadm_output => zone_list) + zones.send(:parse!) + zones.stubs(:refresh) + described_class.stubs(:new).returns(zones) + end + + it 'defines the zones fact' do + described_class.add_facts + Facter.fact(:zones).value.should == 3 + end + + it 'defines a fact for each attribute of a zone' do + described_class.add_facts + [:id, :name, :status, :path, :uuid, :brand, :iptype].each do |attr| + Facter.fact("zone_local_#{attr}".intern). + should be_a_kind_of Facter::Util::Fact + end + end + end + + describe '#refresh' do + it 'executes the zoneadm_cmd' do + Facter::Util::Resolution.expects(:exec).with(subject.zoneadm_cmd).returns(zone_list) + subject.refresh + end + end + + describe 'multiple facts sharing a single model' do + context 'when zones is resolved for the first time' do + it 'counts the number of zones' do + given_initial_zone_facts + Facter.fact(:zones).value.should == 3 + end + it 'defines facts for zoneA' do + given_initial_zone_facts + Facter.fact(:zone_zoneA_id).value.should == '-' + end + it 'does not define facts for zoneB' do + given_initial_zone_facts + Facter.fact(:zone_zoneB_id).should be_nil + end + it 'uses a single read of the system information for all of the dynamically generated zone facts' do + given_initial_zone_facts # <= single read happens here + + Facter::Util::Resolution.expects(:exec).never + Facter.fact(:zone_zoneA_id).value + Facter.fact(:zone_local_id).value + end + end + context 'when all facts have been flushed after zones was resolved once' do + it 'updates the number of zones' do + given_initial_zone_facts + when_facts_have_been_resolved_then_flushed + + Facter.fact(:zones).value.should == 4 + end + it 'stops resolving a value for a zone that no longer exists' do + given_initial_zone_facts + when_facts_have_been_resolved_then_flushed + + Facter.fact(:zone_zoneA_id).value.should be_nil + Facter.fact(:zone_zoneA_status).value.should be_nil + Facter.fact(:zone_zoneA_path).value.should be_nil + end + it 'defines facts for new zones' do + given_initial_zone_facts + when_facts_have_been_resolved_then_flushed + + Facter.fact(:zone_zoneB_id).should be_nil + Facter.fact(:zones).value + Facter.fact(:zone_zoneB_id).value.should be_a_kind_of String + end + it 'uses a single read of the system information for all of the dynamically generated zone facts' do + given_initial_zone_facts + when_facts_have_been_resolved_then_flushed + + Facter::Util::Resolution.expects(:exec).once.returns(zone_list2) + Facter.fact(:zones).value + Facter.fact(:zone_zoneA_id).value + Facter.fact(:zone_local_id).value + end + + end + end + + def given_initial_zone_facts + Facter::Util::Resolution.stubs(:exec). + with(subject.zoneadm_cmd). + returns(zone_list) + described_class.add_facts + end + + def when_facts_have_been_resolved_then_flushed + Facter.fact(:zones).value + Facter.fact(:zone_zoneA_id).value + Facter.fact(:zone_local_id).value + Facter::Util::Resolution.stubs(:exec).returns(zone_list2) + Facter.flush + end +end diff --git a/spec/unit/zones_spec.rb b/spec/unit/zones_spec.rb index fbbb823838..62fdf6ef29 100644 --- a/spec/unit/zones_spec.rb +++ b/spec/unit/zones_spec.rb @@ -1,4 +1,4 @@ -#!usr/bin/env rspec +#! /usr/bin/env ruby require 'spec_helper' From 5fc648597ddedeae3f8da45f472d2692682725ad Mon Sep 17 00:00:00 2001 From: Kyle Anderson Date: Mon, 18 Mar 2013 09:33:26 -0700 Subject: [PATCH 1181/3753] (#19676) Add openwrt OS detection support --- lib/facter/operatingsystem.rb | 2 ++ lib/facter/operatingsystemrelease.rb | 11 +++++++++++ spec/unit/operatingsystem_spec.rb | 1 + spec/unit/operatingsystemrelease_spec.rb | 1 + 4 files changed, 15 insertions(+) diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index 7cb10f5ff9..f8e666e6ab 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -38,6 +38,8 @@ "Ubuntu" elsif FileTest.exists?("/etc/debian_version") "Debian" + elsif FileTest.exists?("/etc/openwrt_release") + "OpenWrt" elsif FileTest.exists?("/etc/gentoo-release") "Gentoo" elsif FileTest.exists?("/etc/fedora-release") diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index 2daecd626b..319896d7ed 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -92,6 +92,17 @@ end end +Facter.add(:operatingsystemrelease) do + confine :operatingsystem => %w{OpenWrt} + setcode do + if release = Facter::Util::FileRead.read('/etc/openwrt_version') + if match = /^(\d+\.\d+.*)/.match(release) + match[1] + end + end + end +end + Facter.add(:operatingsystemrelease) do confine :operatingsystem => %w{Slackware} setcode do diff --git a/spec/unit/operatingsystem_spec.rb b/spec/unit/operatingsystem_spec.rb index 433bd3820b..794b9cadc5 100755 --- a/spec/unit/operatingsystem_spec.rb +++ b/spec/unit/operatingsystem_spec.rb @@ -63,6 +63,7 @@ "MeeGo" => "/etc/meego-release", "Archlinux" => "/etc/arch-release", "OracleLinux" => "/etc/oracle-release", + "OpenWrt" => "/etc/openwrt_release", "Alpine" => "/etc/alpine-release", "VMWareESX" => "/etc/vmware-release", "Bluewhite64" => "/etc/bluewhite64-version", diff --git a/spec/unit/operatingsystemrelease_spec.rb b/spec/unit/operatingsystemrelease_spec.rb index 9375596a20..cda20fe93d 100755 --- a/spec/unit/operatingsystemrelease_spec.rb +++ b/spec/unit/operatingsystemrelease_spec.rb @@ -14,6 +14,7 @@ end test_cases = { + "OpenWrt" => "/etc/openwrt_version", "CentOS" => "/etc/redhat-release", "RedHat" => "/etc/redhat-release", "Scientific" => "/etc/redhat-release", From 707dfc2800488a2ef31a7e5c27dd37731c37438c Mon Sep 17 00:00:00 2001 From: Luis Fernandez Alvarez Date: Thu, 21 Mar 2013 14:31:32 +0100 Subject: [PATCH 1182/3753] (#19761) Added virtual and is_virtual fact on Windows Without this patch, the virtual and is_virtual fact is not set on Windows computers. This patch fixes the problem, giving an initial coverage for some hypervisors. The solution proposed is based on the manufacturer and model properties from WMI object Win32_ComputerSystem. This first version is able to detect the following systems: HyperV, VirtualBox, Vmware and KVM. --- lib/facter/virtual.rb | 23 ++++++++++++++++++++++- spec/unit/virtual_spec.rb | 31 +++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index bbf8d1bf50..20e2702ddb 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -170,6 +170,27 @@ end end +Facter.add("virtual") do + confine :kernel => "windows" + setcode do + require 'facter/util/wmi' + result = nil + Facter::Util::WMI.execquery("SELECT manufacturer, model FROM Win32_ComputerSystem").each do |computersystem| + result = + case computersystem.model + when /VirtualBox/ then "virtualbox" + when /Virtual Machine/ + computersystem.manufacturer =~ /Microsoft/ ? "hyperv" : nil + when /VMware/ then "vmware" + when /KVM/ then "kvm" + else "physical" + end + break + end + result + end +end + ## # virtual fact based on virt-what command. # @@ -230,7 +251,7 @@ # Facter.add("is_virtual") do - confine :kernel => %w{Linux FreeBSD OpenBSD SunOS HP-UX Darwin GNU/kFreeBSD} + confine :kernel => %w{Linux FreeBSD OpenBSD SunOS HP-UX Darwin GNU/kFreeBSD windows} setcode do physical_types = %w{physical xen0 vmware_server vmware_workstation openvzhn} diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index 0062ca6269..855da9d4a1 100755 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -268,6 +268,37 @@ end end + describe "on Windows" do + require 'facter/util/wmi' + before do + Facter.fact(:kernel).stubs(:value).returns("windows") + end + + it "should be kvm with KVM model name from Win32_ComputerSystem" do + computersystem = mock('computersystem', :model => 'KVM') + Facter::Util::WMI.expects(:execquery).returns([computersystem]) + Facter.fact(:virtual).value.should == "kvm" + end + + it "should be hyperv with Virtual Machine model name and Microsoft Corporation manufacturer from Win32_ComputerSystem" do + computersystem = mock('computersystem', :manufacturer => 'Microsoft Corporation', :model => 'Virtual Machine') + Facter::Util::WMI.expects(:execquery).returns([computersystem]) + Facter.fact(:virtual).value.should == "hyperv" + end + + it "should be virtualbox with VirtualBox model name from Win32_ComputerSystem" do + computersystem = mock('computersystem', :model => 'VirtualBox') + Facter::Util::WMI.expects(:execquery).returns([computersystem]) + Facter.fact(:virtual).value.should == "virtualbox" + end + + it "should be vmware with VMware like model name from Win32_ComputerSystem" do + computersystem = mock('computersystem', :model => 'VMware Virtual Platform') + Facter::Util::WMI.expects(:execquery).returns([computersystem]) + Facter.fact(:virtual).value.should == "vmware" + end + end + describe "with the virt-what command available (#8210)" do describe "when the output of virt-what disagrees with lower weight facts" do virt_what_map = { From 9e1223c462cced89dd6529bdc3580f0e8ec5944b Mon Sep 17 00:00:00 2001 From: Kyle Anderson Date: Sat, 23 Mar 2013 13:44:41 -0700 Subject: [PATCH 1183/3753] Added OpenWrt (19676) ps detection and unit test for ps fact --- lib/facter/ps.rb | 5 +++++ spec/unit/ps_spec.rb | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100755 spec/unit/ps_spec.rb diff --git a/lib/facter/ps.rb b/lib/facter/ps.rb index 5d8cb0ae3a..5fb2984543 100644 --- a/lib/facter/ps.rb +++ b/lib/facter/ps.rb @@ -14,6 +14,11 @@ setcode do 'ps -ef' end end +Facter.add(:ps) do + confine :operatingsystem => :OpenWrt + setcode do 'ps www' end +end + Facter.add(:ps) do confine :operatingsystem => %w{FreeBSD NetBSD OpenBSD Darwin DragonFly} setcode do 'ps auxwww' end diff --git a/spec/unit/ps_spec.rb b/spec/unit/ps_spec.rb new file mode 100755 index 0000000000..2e25a4b80c --- /dev/null +++ b/spec/unit/ps_spec.rb @@ -0,0 +1,42 @@ +#! /usr/bin/env ruby + +require 'spec_helper' + +describe "ps facts" do + + it "should return busybox style ps www on OpenWrt" do + Facter.fact(:operatingsystem).stubs(:value).returns 'OpenWrt' + Facter.fact(:ps).value.should == 'ps www' + end + + [ + 'FreeBSD', + 'NetBSD', + 'OpenBSD', + 'Darwin', + 'DragonFly' + ].each do |os| + it "should return unix style ps on operatingsystem #{os}" do + Facter.fact(:operatingsystem).stubs(:value).returns os + Facter.fact(:ps).value.should == 'ps auxwww' + end + end + + # Other Linux Distros should return a ps -ef + [ + 'RedHat', + 'Debian', + ].each do |os| + it "should return gnu/linux style ps -ef on operatingsystem #{os}" do + Facter.fact(:operatingsystem).stubs(:value).returns os + Facter.fact(:ps).value.should == 'ps -ef' + end + end + + it "should return tasklist.exe on Windows" do + Facter.fact(:operatingsystem).stubs(:value).returns 'windows' + Facter.fact(:ps).value.should == 'tasklist.exe' + end + +end + From 43cff12b74aa18187f1501d812a9d61bfcf1aa9f Mon Sep 17 00:00:00 2001 From: Wolf Noble Date: Fri, 22 Mar 2013 17:07:36 -0500 Subject: [PATCH 1184/3753] (19873) Add OS major release fact On RHEL clones and Debian (Amazon, CentOS, CloudLinux, Debian, Fedora, OEL, OracleLinux, OVS, RedHat, Scientific, SLC) it would be useful to pull out the major release version ie 5.8=>5, 6.3=>6, 6.0.3=>6 This is available as the lsbmajdistrelease but there are too many dependencies on LSB for minimal installs --- lib/facter/operatingsystemmajrelease.rb | 33 +++++++++++++++++++++ spec/unit/operatingsystemmajrelease_spec.rb | 16 ++++++++++ 2 files changed, 49 insertions(+) create mode 100644 lib/facter/operatingsystemmajrelease.rb create mode 100644 spec/unit/operatingsystemmajrelease_spec.rb diff --git a/lib/facter/operatingsystemmajrelease.rb b/lib/facter/operatingsystemmajrelease.rb new file mode 100644 index 0000000000..f530125314 --- /dev/null +++ b/lib/facter/operatingsystemmajrelease.rb @@ -0,0 +1,33 @@ +# Fact: operatingsystemmajrelease +# +# Purpose: Returns the major release of the operating system. +# +# Resolution: splits down the operatingsystemrelease fact at decimal point for +# osfamily RedHat derivatives and Debian. +# +# This should be the same as lsbmajdistrelease, but on minimal systems there +# are too many dependencies to use LSB +# +# List of operatingsystems at time of writing: +#"Alpine" "Amazon" "Archlinux" "Ascendos" "Bluewhite64" "CentOS" "CloudLinux" +#"Debian" "Fedora" "Gentoo" "Mandrake" "Mandriva" "MeeGo" "OEL" "OpenSuSE" +#"OracleLinux" "OVS" "PSBM" "RedHat" "Scientific" "Slackware" "Slamd64" "SLC" +#"SLED" "SLES" "SuSE" "Ubuntu" "VMWareESX" +Facter.add(:operatingsystemmajrelease) do + confine :operatingsystem => [ + :Amazon, + :CentOS, + :CloudLinux, + :Debian, + :Fedora, + :OEL, + :OracleLinux, + :OVS, + :RedHat, + :Scientific, + :SLC + ] + setcode do + Facter.value('operatingsystemrelease').split('.').first + end +end diff --git a/spec/unit/operatingsystemmajrelease_spec.rb b/spec/unit/operatingsystemmajrelease_spec.rb new file mode 100644 index 0000000000..9c1a467437 --- /dev/null +++ b/spec/unit/operatingsystemmajrelease_spec.rb @@ -0,0 +1,16 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper' +require 'facter' + +describe "OS Major Release fact" do + ['Amazon','CentOS','CloudLinux','Debian','Fedora','OEL','OracleLinux','OVS','RedHat','Scientific','SLC'].each do |operatingsystem| + context "on #{operatingsystem} operatingsystems" do + it "should be derived from operatingsystemrelease" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:operatingsystem).stubs(:value).returns(operatingsystem) + Facter.fact(:operatingsystemrelease).stubs(:value).returns("6.3") + Facter.fact(:operatingsystemmajrelease).value.should == "6" + end + end + end +end From 3bcbcb20790d6efd6425c3523954e953e1c409b8 Mon Sep 17 00:00:00 2001 From: Mike Carlson Date: Mon, 2 Jan 2012 12:42:26 -0800 Subject: [PATCH 1185/3753] Added support for FreeBSD block devices --- lib/facter/blockdevices.rb | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/lib/facter/blockdevices.rb b/lib/facter/blockdevices.rb index e0ddc48fb4..bc19496428 100644 --- a/lib/facter/blockdevices.rb +++ b/lib/facter/blockdevices.rb @@ -101,5 +101,27 @@ setcode { blockdevices.sort.join(',') } end end +end + +if Facter.value(:kernel) == 'FreeBSD' + blockdevices = Facter::Util::Resolution.exec('/sbin/sysctl -n kern.disks').split(' ') + + Facter.add(:blockdevices) do + setcode { blockdevices.sort.join(',') } + end + + blockdevices.each do |device| + Facter.add("blockdevice_#{device}_size".to_sym) do + setcode { Facter::Util::Resolution.exec("/usr/sbin/diskinfo #{device} | /usr/bin/awk '{print $3}'") } + end + Facter.add("blockdevice_#{device}_model".to_sym) do + if device =~ /ad/ + setcode { Facter::Util::Resolution.exec("/sbin/atacontrol list | /usr/bin/awk -F '<|>' '/#{device}/ { print $2 }'") } + else + setcode { Facter::Util::Resolution.exec("/sbin/camcontrol inquiry #{device} -D | /usr/bin/awk -F '<|>' '{ print $2}'" ) } + end + end + end end + From d98a8970235e737d86953aec45635924dfcdc625 Mon Sep 17 00:00:00 2001 From: Mike Carlson Date: Wed, 8 Feb 2012 11:55:40 -0800 Subject: [PATCH 1186/3753] update blockdevice_device_model for FreeBSD - include support for mfi disks. since it is a abstracted raid disk, the output will simply be "MFI Local Disk", as that seemed the simplest way to handle this problem. --- lib/facter/blockdevices.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/facter/blockdevices.rb b/lib/facter/blockdevices.rb index bc19496428..e4397db6d9 100644 --- a/lib/facter/blockdevices.rb +++ b/lib/facter/blockdevices.rb @@ -118,6 +118,8 @@ Facter.add("blockdevice_#{device}_model".to_sym) do if device =~ /ad/ setcode { Facter::Util::Resolution.exec("/sbin/atacontrol list | /usr/bin/awk -F '<|>' '/#{device}/ { print $2 }'") } + elsif device =~ /mfi/ + setcode { "MFI Local Disk" } else setcode { Facter::Util::Resolution.exec("/sbin/camcontrol inquiry #{device} -D | /usr/bin/awk -F '<|>' '{ print $2}'" ) } end From 0a79d7f9870a97a738c437d2c0f1ecc81f24cb2a Mon Sep 17 00:00:00 2001 From: Michael Moll Date: Sun, 3 Jun 2012 21:59:31 +0200 Subject: [PATCH 1187/3753] Improve FreeBSD blockdevice facts - Hardwire the size of CD drives to zero. diskinfo runs into errors when querying empty drives. - Use a different camcontrol command when dealing with ATA drives and ATA-CAM is used. - split FreeBSD blockdevice facts now we get model and vendor, just like on Linux --- lib/facter/blockdevices.rb | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/lib/facter/blockdevices.rb b/lib/facter/blockdevices.rb index e4397db6d9..1e0abb4e81 100644 --- a/lib/facter/blockdevices.rb +++ b/lib/facter/blockdevices.rb @@ -112,18 +112,32 @@ blockdevices.each do |device| Facter.add("blockdevice_#{device}_size".to_sym) do - setcode { Facter::Util::Resolution.exec("/usr/sbin/diskinfo #{device} | /usr/bin/awk '{print $3}'") } + if device =~ /acd|cd/ + setcode { "0" } + else + setcode { Facter::Util::Resolution.exec("/usr/sbin/diskinfo #{device} | /usr/bin/awk '{ print $3 }'") } + end + end + + devicestring = "" + if device =~ /ada/ + devicestring = Facter::Util::Resolution.exec("/sbin/camcontrol identify #{device} | /usr/bin/awk -F 'device\ model\ *' '/device model/ { print $2 }'") + elsif device =~ /ad/ + devicestring = Facter::Util::Resolution.exec("/sbin/atacontrol list | /usr/bin/awk -F '<|>' '/#{device}/ { print $2 }'") + elsif device =~ /mfi/ + devicestring = "MFI Local Disk" + else + devicestring = Facter::Util::Resolution.exec("/sbin/camcontrol inquiry #{device} -D | /usr/bin/awk -F '<|>' '{ print $2 }'") + end + + devicevendor, devicemodel = devicestring.split(' ', 2) + + Facter.add("blockdevice_#{device}_vendor".to_sym) do + setcode { devicevendor } end Facter.add("blockdevice_#{device}_model".to_sym) do - if device =~ /ad/ - setcode { Facter::Util::Resolution.exec("/sbin/atacontrol list | /usr/bin/awk -F '<|>' '/#{device}/ { print $2 }'") } - elsif device =~ /mfi/ - setcode { "MFI Local Disk" } - else - setcode { Facter::Util::Resolution.exec("/sbin/camcontrol inquiry #{device} -D | /usr/bin/awk -F '<|>' '{ print $2}'" ) } - end + setcode { devicemodel } end end end - From c5a65e342721383ffdd5068b1cd3f952e0166b0e Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Wed, 14 Nov 2012 08:58:05 -0800 Subject: [PATCH 1188/3753] Add freeBSD facts --- spec/unit/blockdevices_spec.rb | 76 +++++++++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 2 deletions(-) diff --git a/spec/unit/blockdevices_spec.rb b/spec/unit/blockdevices_spec.rb index 54620b8e07..167b546e0d 100644 --- a/spec/unit/blockdevices_spec.rb +++ b/spec/unit/blockdevices_spec.rb @@ -6,15 +6,87 @@ describe "Block device facts" do - describe "on non-Linux OS" do + describe "on unsupported platforms" do - it "should not exist when kernel isn't Linux" do + it "should not exist when kernel isn't Linux or FreeBSD" do Facter.fact(:kernel).stubs(:value).returns("SunOS") Facter.fact(:blockdevices).should == nil end end + describe "on FreeBSD" do + + before :each do + Facter.fact(:kernel).stubs(:value).returns("FreeBSD") + + Facter::Util::Resolution.stubs(:exec).with("/sbin/sysctl -n kern.disks").returns("cd0 da0 ada0 ad10 mfi0") + + Facter::Util::Resolution.stubs(:exec).with("/sbin/camcontrol inquiry cd0 -D | /usr/bin/awk -F '<|>' '{ print $2 }'").returns("TEAC DV-28E-N 1.6A") + Facter::Util::Resolution.stubs(:exec).with("/sbin/camcontrol inquiry da0 -D | /usr/bin/awk -F '<|>' '{ print $2 }'").returns("HP 73.4G MAU3073NC HPC2") + Facter::Util::Resolution.stubs(:exec).with("/sbin/camcontrol identify ada0 | /usr/bin/awk -F 'device\ model\ *' '/device model/ { print $2 }'").returns("Corsair Force GT") + Facter::Util::Resolution.stubs(:exec).with("/sbin/atacontrol list | /usr/bin/awk -F '<|>' '/ad10/ { print $2 }'").returns("WDC WD1003FBYX-01Y7B0/01.01V01") + + Facter::Util::Resolution.stubs(:exec).with("/usr/sbin/diskinfo da0 | /usr/bin/awk '{ print $3 }'").returns("73407865856") + Facter::Util::Resolution.stubs(:exec).with("/usr/sbin/diskinfo ada0 | /usr/bin/awk '{ print $3 }'").returns("120034123776") + Facter::Util::Resolution.stubs(:exec).with("/usr/sbin/diskinfo ad10 | /usr/bin/awk '{ print $3 }'").returns("1000204886016") + Facter::Util::Resolution.stubs(:exec).with("/usr/sbin/diskinfo mfi0 | /usr/bin/awk '{ print $3 }'").returns("1000000000000") + end + + describe 'blockdevices fact' do + it 'should return a sorted list of block devices' do + Facter.fact(:blockdevices).value.should == 'ad10,ada0,cd0,da0,mfi0' + end + end + + describe 'individual block device facts' do + + before :each do + # We need to manually load this fact file since the fact name varies + # from the contained file. + Facter.collection.internal_loader.load(:blockdevices) + end + + blockdev_tests = { + 'cd0' => { + :size => "0", + :model => "DV-28E-N 1.6A", + :vendor => "TEAC", + }, + 'da0' => { + :model => "73.4G MAU3073NC HPC2", + :vendor => "HP", + :size => "73407865856", + }, + 'ada0' => { + :model => "Force GT", + :vendor => "Corsair", + :size => "120034123776", + }, + 'ad10' => { + :model => "WD1003FBYX-01Y7B0/01.01V01", + :vendor => "WDC", + :size => "1000204886016", + }, + 'mfi0' => { + :model => "Local Disk", + :vendor => "MFI", + :size => "1000000000000", + } + } + + blockdev_tests.each_pair do |dev, tests| + tests.each_pair do |name, expected| + factname = "blockdevice_#{dev}_#{name}" + + it "#{factname} should be #{expected}" do + Facter.fact(factname).value.should == expected + end + end + end + end + end + describe "on Linux" do describe "with /sys/block/" do From 3db1ddcf421ebba7f296a60515c31560b850d59c Mon Sep 17 00:00:00 2001 From: razic Date: Thu, 14 Mar 2013 22:09:45 +0700 Subject: [PATCH 1189/3753] (#17710) Refactor the IP module This pull request aims to refactor the Facter::Util::IP module with kernel specific classes adhering to a common interface. This pull request eliminates case statements and crazy kernel conditionals within methods. Additionally, this eliminates the giant REGEX_MAP constant at the top of the Facter::Util::IP module and adds some documentation to methods that were previously left undocumented. The related ticket is here: http://projects.puppetlabs.com/issues/17710 --- lib/facter/interfaces.rb | 53 +- lib/facter/network.rb | 13 +- lib/facter/util/ip.rb | 338 +++-------- lib/facter/util/ip/base.rb | 198 +++++++ lib/facter/util/ip/darwin.rb | 6 + lib/facter/util/ip/dragonfly.rb | 6 + lib/facter/util/ip/free_bsd.rb | 6 + lib/facter/util/ip/gnu_k_free_bsd.rb | 9 + lib/facter/util/ip/hpux.rb | 76 +++ lib/facter/util/ip/linux.rb | 169 ++++++ lib/facter/util/ip/net_bsd.rb | 6 + lib/facter/util/ip/open_bsd.rb | 6 + lib/facter/util/ip/sun_os.rb | 24 + lib/facter/util/ip/windows.rb | 73 +++ .../ifconfig_all_with_multiple_interfaces} | 0 .../ip/darwin/ifconfig_with_single_interface | 6 + ...-STABLE_ifconfig_with_multiple_interfaces} | 0 .../6.0-STABLE_ifconfig_with_single_interface | 10 + .../ifconfig_all_with_multiple_interfaces} | 0 .../ifconfig_with_single_interface | 8 + .../1111_ifconfig_lan0} | 0 .../1111_ifconfig_lan1} | 0 .../1111_ifconfig_lo0} | 0 .../{hpux_1111_lanscan => hpux/1111_lanscan} | 0 .../1111_netstat_in} | 0 .../1131_asterisk_ifconfig_lan0} | 0 .../1131_asterisk_ifconfig_lan1} | 0 .../1131_asterisk_ifconfig_lo0} | 0 .../1131_asterisk_lanscan} | 0 .../1131_asterisk_netstat_in} | 0 .../1131_ifconfig_lan0} | 0 .../1131_ifconfig_lan1} | 0 .../1131_ifconfig_lo0} | 0 .../{hpux_1131_lanscan => hpux/1131_lanscan} | 0 .../1131_netstat_in} | 0 .../1131_nic_bonding_ifconfig_lan1} | 0 .../1131_nic_bonding_ifconfig_lan4} | 0 .../1131_nic_bonding_ifconfig_lan4_1} | 0 .../1131_nic_bonding_ifconfig_lo0} | 0 .../1131_nic_bonding_lanscan} | 0 .../1131_nic_bonding_netstat_in} | 0 .../2_6_35_proc_net_bonding_bond0} | 0 .../ifconfig_all_with_single_interface} | 0 .../ifconfig_single_interface_eth0} | 0 .../ifconfig_single_interface_lo} | 0 .../ifconfig_with_single_interface_ib0} | 0 .../ifconfig_all_with_multiple_interfaces} | 0 .../ifconfig_single_interface} | 0 .../netsh_all_interfaces} | 0 .../netsh_with_single_interface} | 0 .../netsh_with_single_interface6} | 0 spec/unit/blockdevices_spec.rb | 2 - spec/unit/hardwareisa_spec.rb | 2 - spec/unit/hardwaremodel_spec.rb | 2 - spec/unit/interfaces_spec.rb | 19 +- spec/unit/ldom_spec.rb | 2 - spec/unit/lsbmajdistrelease_spec.rb | 2 - spec/unit/manufacturer_spec.rb | 2 - spec/unit/uniqueid_spec.rb | 2 - spec/unit/util/directory_loader_spec.rb | 3 - spec/unit/util/ip/base_spec.rb | 147 +++++ spec/unit/util/ip/darwin_spec.rb | 87 +++ spec/unit/util/ip/dragonfly_spec.rb | 34 ++ spec/unit/util/ip/free_bsd_spec.rb | 56 ++ spec/unit/util/ip/gnu_k_free_bsd_spec.rb | 85 +++ spec/unit/util/ip/hpux_spec.rb | 536 ++++++++++++++++++ spec/unit/util/ip/linux_spec.rb | 158 ++++++ spec/unit/util/ip/net_bsd_spec.rb | 34 ++ spec/unit/util/ip/open_bsd_spec.rb | 34 ++ spec/unit/util/ip/sun_os_spec.rb | 91 +++ spec/unit/util/ip/windows_spec.rb | 94 +++ spec/unit/util/ip_spec.rb | 454 +-------------- spec/unit/util/parser_spec.rb | 5 +- spec/unit/zfs_version_spec.rb | 2 - spec/unit/zpool_version_spec.rb | 2 - 75 files changed, 2088 insertions(+), 774 deletions(-) create mode 100644 lib/facter/util/ip/base.rb create mode 100644 lib/facter/util/ip/darwin.rb create mode 100644 lib/facter/util/ip/dragonfly.rb create mode 100644 lib/facter/util/ip/free_bsd.rb create mode 100644 lib/facter/util/ip/gnu_k_free_bsd.rb create mode 100644 lib/facter/util/ip/hpux.rb create mode 100644 lib/facter/util/ip/linux.rb create mode 100644 lib/facter/util/ip/net_bsd.rb create mode 100644 lib/facter/util/ip/open_bsd.rb create mode 100644 lib/facter/util/ip/sun_os.rb create mode 100644 lib/facter/util/ip/windows.rb rename spec/fixtures/unit/util/ip/{darwin_ifconfig_all_with_multiple_interfaces => darwin/ifconfig_all_with_multiple_interfaces} (100%) create mode 100644 spec/fixtures/unit/util/ip/darwin/ifconfig_with_single_interface rename spec/fixtures/unit/util/ip/{6.0-STABLE_FreeBSD_ifconfig => free_bsd/6.0-STABLE_ifconfig_with_multiple_interfaces} (100%) create mode 100644 spec/fixtures/unit/util/ip/free_bsd/6.0-STABLE_ifconfig_with_single_interface rename spec/fixtures/unit/util/ip/{debian_kfreebsd_ifconfig => gnu_k_free_bsd/ifconfig_all_with_multiple_interfaces} (100%) create mode 100644 spec/fixtures/unit/util/ip/gnu_k_free_bsd/ifconfig_with_single_interface rename spec/fixtures/unit/util/ip/{hpux_1111_ifconfig_lan0 => hpux/1111_ifconfig_lan0} (100%) rename spec/fixtures/unit/util/ip/{hpux_1111_ifconfig_lan1 => hpux/1111_ifconfig_lan1} (100%) rename spec/fixtures/unit/util/ip/{hpux_1111_ifconfig_lo0 => hpux/1111_ifconfig_lo0} (100%) rename spec/fixtures/unit/util/ip/{hpux_1111_lanscan => hpux/1111_lanscan} (100%) rename spec/fixtures/unit/util/ip/{hpux_1111_netstat_in => hpux/1111_netstat_in} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_asterisk_ifconfig_lan0 => hpux/1131_asterisk_ifconfig_lan0} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_asterisk_ifconfig_lan1 => hpux/1131_asterisk_ifconfig_lan1} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_asterisk_ifconfig_lo0 => hpux/1131_asterisk_ifconfig_lo0} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_asterisk_lanscan => hpux/1131_asterisk_lanscan} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_asterisk_netstat_in => hpux/1131_asterisk_netstat_in} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_ifconfig_lan0 => hpux/1131_ifconfig_lan0} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_ifconfig_lan1 => hpux/1131_ifconfig_lan1} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_ifconfig_lo0 => hpux/1131_ifconfig_lo0} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_lanscan => hpux/1131_lanscan} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_netstat_in => hpux/1131_netstat_in} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_nic_bonding_ifconfig_lan1 => hpux/1131_nic_bonding_ifconfig_lan1} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_nic_bonding_ifconfig_lan4 => hpux/1131_nic_bonding_ifconfig_lan4} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_nic_bonding_ifconfig_lan4_1 => hpux/1131_nic_bonding_ifconfig_lan4_1} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_nic_bonding_ifconfig_lo0 => hpux/1131_nic_bonding_ifconfig_lo0} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_nic_bonding_lanscan => hpux/1131_nic_bonding_lanscan} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_nic_bonding_netstat_in => hpux/1131_nic_bonding_netstat_in} (100%) rename spec/fixtures/unit/util/ip/{linux_2_6_35_proc_net_bonding_bond0 => linux/2_6_35_proc_net_bonding_bond0} (100%) rename spec/fixtures/unit/util/ip/{linux_ifconfig_all_with_single_interface => linux/ifconfig_all_with_single_interface} (100%) rename spec/fixtures/unit/util/ip/{linux_get_single_interface_eth0 => linux/ifconfig_single_interface_eth0} (100%) rename spec/fixtures/unit/util/ip/{linux_get_single_interface_lo => linux/ifconfig_single_interface_lo} (100%) rename spec/fixtures/unit/util/ip/{linux_get_single_interface_ib0 => linux/ifconfig_with_single_interface_ib0} (100%) rename spec/fixtures/unit/util/ip/{solaris_ifconfig_all_with_multiple_interfaces => sun_os/ifconfig_all_with_multiple_interfaces} (100%) rename spec/fixtures/unit/util/ip/{solaris_ifconfig_single_interface => sun_os/ifconfig_single_interface} (100%) rename spec/fixtures/unit/util/ip/{windows_netsh_all_interfaces => windows/netsh_all_interfaces} (100%) rename spec/fixtures/unit/util/ip/{windows_netsh_single_interface => windows/netsh_with_single_interface} (100%) rename spec/fixtures/unit/util/ip/{windows_netsh_single_interface6 => windows/netsh_with_single_interface6} (100%) create mode 100644 spec/unit/util/ip/base_spec.rb create mode 100644 spec/unit/util/ip/darwin_spec.rb create mode 100644 spec/unit/util/ip/dragonfly_spec.rb create mode 100644 spec/unit/util/ip/free_bsd_spec.rb create mode 100644 spec/unit/util/ip/gnu_k_free_bsd_spec.rb create mode 100644 spec/unit/util/ip/hpux_spec.rb create mode 100644 spec/unit/util/ip/linux_spec.rb create mode 100644 spec/unit/util/ip/net_bsd_spec.rb create mode 100644 spec/unit/util/ip/open_bsd_spec.rb create mode 100644 spec/unit/util/ip/sun_os_spec.rb create mode 100644 spec/unit/util/ip/windows_spec.rb diff --git a/lib/facter/interfaces.rb b/lib/facter/interfaces.rb index 9b132e1c24..bf7a0be504 100644 --- a/lib/facter/interfaces.rb +++ b/lib/facter/interfaces.rb @@ -1,42 +1,35 @@ -# interfaces.rb -# Try to get additional Facts about the machine's network interfaces +# encoding: UTF-8 + +# Fact: interfaces # -# Original concept Copyright (C) 2007 psychedelys -# Update and *BSD support (C) 2007 James Turnbull +# Purpose: +# Try to get additional Facts about the machine's network interfaces # +# Caveats: +# Most of this only works on a fixed list of platforms; notably, Darwin is +# missing. require 'facter/util/ip' -# Note that most of this only works on a fixed list of platforms; notably, Darwin -# is missing. +interfaces = Facter::Util::IP.interfaces -Facter.add(:interfaces) do - confine :kernel => 'Linux' - has_weight 20 - setcode do - list = Dir.glob('/sys/class/net/*').map do |name| - Facter::Util::IP.alphafy(name.split('/').last) - end - list.empty? ? nil : list.join(',') - end -end +if interfaces.any? + Facter.add(:interfaces) do + setcode do + alphafied_interfaces = interfaces.map do |interface| + Facter::Util::IP.alphafy(interface) + end -Facter.add(:interfaces) do - confine :kernel => Facter::Util::IP.supported_platforms - setcode do - Facter::Util::IP.get_interfaces.collect { |iface| Facter::Util::IP.alphafy(iface) }.join(",") + alphafied_interfaces.join(",") + end end -end - -Facter::Util::IP.get_interfaces.each do |interface| - # Make a fact for each detail of each interface. Yay. - # There's no point in confining these facts, since we wouldn't be able to create - # them if we weren't running on a supported platform. - %w{ipaddress ipaddress6 macaddress netmask mtu}.each do |label| - Facter.add(label + "_" + Facter::Util::IP.alphafy(interface)) do - setcode do - Facter::Util::IP.get_interface_value(interface, label) + interfaces.each do |interface| + %w[ipaddress ipaddress6 macaddress netmask mtu].each do |label| + Facter.add("#{label}_#{Facter::Util::IP.alphafy(interface)}") do + setcode do + Facter::Util::IP.value_for_interface_and_label(interface, label) + end end end end diff --git a/lib/facter/network.rb b/lib/facter/network.rb index 390895a05b..506b02afad 100644 --- a/lib/facter/network.rb +++ b/lib/facter/network.rb @@ -1,20 +1,17 @@ # Fact: network # # Purpose: -# Get IP, network and netmask information for available network -# interfacs. +# Get IP, network and netmask information for available network interfaces. # # Resolution: -# Uses 'facter/util/ip' to enumerate interfaces and return their information. -# -# Caveats: -# +# Uses 'facter/util/ip' to enumerate interfaces and return their information. + require 'facter/util/ip' -Facter::Util::IP.get_interfaces.each do |interface| +Facter::Util::IP.interfaces.each do |interface| Facter.add("network_" + Facter::Util::IP.alphafy(interface)) do setcode do - Facter::Util::IP.get_network_value(interface) + Facter::Util::IP.network(interface) end end end diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index 6bed1dd4d0..059ab8a0a7 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -1,296 +1,126 @@ +# encoding: UTF-8 + +require 'facter/util/ip/base' +require 'facter/util/ip/darwin' +require 'facter/util/ip/sun_os' +require 'facter/util/ip/linux' +require 'facter/util/ip/net_bsd' +require 'facter/util/ip/open_bsd' +require 'facter/util/ip/free_bsd' +require 'facter/util/ip/dragonfly' +require 'facter/util/ip/windows' +require 'facter/util/ip/hpux' +require 'facter/util/ip/gnu_k_free_bsd' + # A base module for collecting IP-related # information from all kinds of platforms. module Facter::Util::IP - # A map of all the different regexes that work for - # a given platform or set of platforms. - REGEX_MAP = { - :linux => { - :ipaddress => /inet (?:addr:)?([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, - :ipaddress6 => /inet6 (?:addr: )?((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/, - :macaddress => /(?:ether|HWaddr)\s+((\w{1,2}:){5,}\w{1,2})/, - :netmask => /Mask:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, - :mtu => /MTU:(\d+)/ - }, - :bsd => { - :aliases => [:openbsd, :netbsd, :freebsd, :darwin, :"gnu/kfreebsd", :dragonfly], - :ipaddress => /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, - :ipaddress6 => /inet6 ((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/, - :macaddress => /(?:ether|lladdr)\s+(\w?\w:\w?\w:\w?\w:\w?\w:\w?\w:\w?\w)/, - :netmask => /netmask\s+0x(\w{8})/, - :mtu => /mtu\s+(\d+)/ - }, - :sunos => { - :ipaddress => /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, - :ipaddress6 => /inet6 ((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/, - :macaddress => /(?:ether|lladdr)\s+(\w?\w:\w?\w:\w?\w:\w?\w:\w?\w:\w?\w)/, - :netmask => /netmask\s+(\w{8})/, - :mtu => /mtu\s+(\d+)/ - }, - :"hp-ux" => { - :ipaddress => /\s+inet (\S+)\s.*/, - :macaddress => /(\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/, - :netmask => /.*\s+netmask (\S+)\s.*/ - }, - :windows => { - :ipaddress => /\s+IP Address:\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, - :ipaddress6 => /Address ((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/, - :netmask => /\s+Subnet Prefix:\s+\S+\s+\(mask ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\)/ - } - } - # Convert an interface name into purely alphanumeric characters. + # + # @param [String] interface e.g. 'eth0' + # + # @return [String] + # + # @api public def self.alphafy(interface) - interface.gsub(/[^a-z0-9_]/i, '_') - end - - def self.convert_from_hex?(kernel) - kernels_to_convert = [:sunos, :openbsd, :netbsd, :freebsd, :darwin, :"hp-ux", :"gnu/kfreebsd", :dragonfly] - kernels_to_convert.include?(kernel) + interface.to_s.gsub(/[^a-z0-9_]/i, '_') end + # Returns an array of supported platforms in string format. These array values + # are synonymous with the values returned from Facter.value(:kernel). + # + # @return [Array] contains strings corresponding to a kernel + # + # @api public def self.supported_platforms - REGEX_MAP.inject([]) do |result, tmp| - key, map = tmp - if map[:aliases] - result += map[:aliases] - else - result << key - end - result - end - end - - def self.get_interfaces - # Use sysfs on most Linux systems, which is the fastest option. - if File.exist?('/sys/class/net') - return Dir.glob('/sys/class/net/*').map do |name| - Facter::Util::IP.alphafy(name.split('/').last) - end - end - - return [] unless output = Facter::Util::IP.get_all_interface_output() - - # windows interface names contain spaces and are quoted and can appear multiple - # times as ipv4 and ipv6 - return output.scan(/\s* connected\s*(\S.*)/).flatten.uniq if Facter.value(:kernel) == 'windows' - - # Our regex appears to be stupid, in that it leaves colons sitting - # at the end of interfaces. So, we have to trim those trailing - # characters. I tried making the regex better but supporting all - # platforms with a single regex is probably a bit too much. - output.scan(/^\S+/).collect { |i| i.sub(/:$/, '') }.uniq + kernel_classes.map(&:to_s) end - def self.get_all_interface_output - case Facter.value(:kernel) - when 'Linux', 'OpenBSD', 'NetBSD', 'FreeBSD', 'Darwin', 'GNU/kFreeBSD', 'DragonFly' - output = Facter::Util::IP.exec_ifconfig(["-a","2>/dev/null"]) - when 'SunOS' - output = Facter::Util::IP.exec_ifconfig(["-a"]) - when 'HP-UX' - # (#17487)[https://projects.puppetlabs.com/issues/17487] - # Handle NIC bonding where asterisks and virtual NICs are printed. - if output = hpux_netstat_in - output.gsub!(/\*/, "") # delete asterisks. - output.gsub!(/^[^\n]*none[^\n]*\n/, "") # delete lines with 'none' instead of IPs. - output.sub!(/^[^\n]*\n/, "") # delete the header line. - output - end - when 'windows' - output = %x|#{ENV['SYSTEMROOT']}/system32/netsh.exe interface ip show interface| - output += %x|#{ENV['SYSTEMROOT']}/system32/netsh.exe interface ipv6 show interface| - end - output + # A delegate method to the kernel's subclass ultimately obtaining the + # interfaces. + # + # @return [Array] + # + # @api public + def self.interfaces + kernel_class.interfaces end - - ## - # exec_ifconfig uses the ifconfig command + # Uses the ifconfig command # - # @return [String] the output of `ifconfig #{arguments} 2>/dev/null` or nil + # @param [Array] additional arguments + # + # @return [String] the output of the command + # + # @api public def self.exec_ifconfig(additional_arguments=[]) Facter::Util::Resolution.exec("#{self.get_ifconfig} #{additional_arguments.join(' ')}") end - ## - # get_ifconfig looks up the ifconfig binary + + # Looks up the ifconfig binary. # # @return [String] path to the ifconfig binary + # + # @api public def self.get_ifconfig common_paths=["/bin/ifconfig","/sbin/ifconfig","/usr/sbin/ifconfig"] common_paths.select{|path| File.executable?(path)}.first end - ## - # hpux_netstat_in is a delegate method that allows us to stub netstat -in - # without stubbing exec. - def self.hpux_netstat_in - Facter::Util::Resolution.exec("/bin/netstat -in") - end - - def self.get_infiniband_macaddress(interface) - if File.exists?("/sys/class/net/#{interface}/address") then - ib_mac_address = `cat /sys/class/net/#{interface}/address`.chomp - elsif File.exists?("/sbin/ip") then - ip_output = %x{/sbin/ip link show #{interface}} - ib_mac_address = ip_output.scan(%r{infiniband\s+((\w{1,2}:){5,}\w{1,2})}) - else - ib_mac_address = "FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF" - Facter.debug("ip.rb: nothing under /sys/class/net/#{interface}/address and /sbin/ip not available") - end - ib_mac_address - end - def self.ifconfig_interface(interface) - output = Facter::Util::IP.exec_ifconfig([interface,"2>/dev/null"]) - end - - def self.get_single_interface_output(interface) - output = "" - case Facter.value(:kernel) - when 'OpenBSD', 'NetBSD', 'FreeBSD', 'Darwin', 'GNU/kFreeBSD', 'DragonFly' - output = Facter::Util::IP.ifconfig_interface(interface) - when 'Linux' - ifconfig_output = Facter::Util::IP.ifconfig_interface(interface) - if interface =~ /^ib/ then - real_mac_address = get_infiniband_macaddress(interface) - output = ifconfig_output.sub(%r{(?:ether|HWaddr)\s+((\w{1,2}:){5,}\w{1,2})}, "HWaddr #{real_mac_address}") - else - output = ifconfig_output - end - when 'SunOS' - output = Facter::Util::IP.exec_ifconfig([interface]) - when 'HP-UX' - mac = "" - ifc = hpux_ifconfig_interface(interface) - hpux_lanscan.scan(/(\dx\S+).*UP\s+(\w+\d+)/).each {|i| mac = i[0] if i.include?(interface) } - mac = mac.sub(/0x(\S+)/,'\1').scan(/../).join(":") - output = ifc + "\n" + mac + # A delegate method to `value_for_interface_and_label` which is implemented in + # Facter::Util::IP::Base and it's subclasses. + # + # @param interface [String] label [String] e.g ['eth0', 'MTU'] + # + # @return [String] or [NilClass] + # + # @api public + def self.value_for_interface_and_label(interface, label) + if kernel_supported? + kernel_class.value_for_interface_and_label(interface, label) end - output - end - - def self.hpux_ifconfig_interface(interface) - Facter::Util::IP.exec_ifconfig([interface]) - end - - def self.hpux_lanscan - Facter::Util::Resolution.exec("/usr/sbin/lanscan") end - def self.get_output_for_interface_and_label(interface, label) - return get_single_interface_output(interface) unless Facter.value(:kernel) == 'windows' - - if label == 'ipaddress6' - output = %x|#{ENV['SYSTEMROOT']}/system32/netsh.exe interface ipv6 show address \"#{interface}\"| - else - output = %x|#{ENV['SYSTEMROOT']}/system32/netsh.exe interface ip show address \"#{interface}\"| + # A delegate method to obtain the network of an interface + # + # @param interface [String] e.g 'eth0' + # + # @return [String] or [NilClass] + # + # @api public + def self.network(interface) + if kernel_supported? + kernel_class.network(interface, label) end - output end - def self.get_bonding_master(interface) - if Facter.value(:kernel) != 'Linux' - return nil - end - # We need ip instead of ifconfig because it will show us - # the bonding master device. - if not FileTest.executable?("/sbin/ip") - return nil - end - # A bonding interface can never be an alias interface. Alias - # interfaces do have a colon in their name and the ip link show - # command throws an error message when we pass it an alias - # interface. - if interface =~ /:/ - return nil - end - regex = /SLAVE[,>].* (bond[0-9]+)/ - ethbond = regex.match(%x{/sbin/ip link show #{interface}}) - if ethbond - device = ethbond[1] - else - device = nil - end - device - end + private - ## - # get_interface_value obtains the value of a specific attribute of a specific - # interface. + # A delegate method for obtaining Facter::Util::IP::Base's subclasses. # - # @param interface [String] the interface identifier, e.g. "eth0" or "bond0" - # - # @param label [String] the attribute of the interface to obtain a value for, - # e.g. "netmask" or "ipaddress" + # @return [Array] # # @api private - # - # @return [String] representing the requested value. An empty array is - # returned if the kernel is not supported by the REGEX_MAP constant. - def self.get_interface_value(interface, label) - tmp1 = [] - - kernel = Facter.value(:kernel).downcase.to_sym - - # If it's not directly in the map or aliased in the map, then we don't know how to deal with it. - unless map = REGEX_MAP[kernel] || REGEX_MAP.values.find { |tmp| tmp[:aliases] and tmp[:aliases].include?(kernel) } - return [] - end - - # Pull the correct regex out of the map. - regex = map[label.to_sym] - - # Linux changes the MAC address reported via ifconfig when an ethernet interface - # becomes a slave of a bonding device to the master MAC address. - # We have to dig a bit to get the original/real MAC address of the interface. - bonddev = get_bonding_master(interface) - if label == 'macaddress' and bonddev - bondinfo = read_proc_net_bonding("/proc/net/bonding/#{bonddev}") - re = /^Slave Interface: #{interface}\b.*?\bPermanent HW addr: (([0-9A-F]{2}:?)*)$/im - if match = re.match(bondinfo) - value = match[1].upcase - end - else - output_int = get_output_for_interface_and_label(interface, label) - - output_int.each_line do |s| - if s =~ regex - value = $1 - if label == 'netmask' && convert_from_hex?(kernel) - value = value.scan(/../).collect do |byte| byte.to_i(16) end.join('.') - end - tmp1.push(value) - end - end - - if tmp1 - value = tmp1.shift - end - end + def self.kernel_classes + Facter::Util::IP::Base.subclasses end - ## - # read_proc_net_bonding is a seam method for mocking purposes. + # Obtains the cooresponding Facter::Util::IP::Base subclass for the current + # kernel. # - # @param path [String] representing the path to read, e.g. "/proc/net/bonding/bond0" + # @return Subclass of [Facter::Util::IP::Base] # # @api private - # - # @return [String] modeling the raw file read - def self.read_proc_net_bonding(path) - File.read(path) if File.exists?(path) + def self.kernel_class + kernel_classes.find { |klass| klass.to_s == Facter.value(:kernel) } end - private_class_method :read_proc_net_bonding - - def self.get_network_value(interface) - require 'ipaddr' - ipaddress = get_interface_value(interface, "ipaddress") - netmask = get_interface_value(interface, "netmask") - - if ipaddress && netmask - ip = IPAddr.new(ipaddress, Socket::AF_INET) - subnet = IPAddr.new(netmask, Socket::AF_INET) - network = ip.mask(subnet.to_s).to_s - end + # Boolean to determine whether the current kernel is supported. + # + # @return [Boolean] true or false + # + # @api private + def self.kernel_supported? + supported_platforms.include?(Facter.value(:kernel)) end end diff --git a/lib/facter/util/ip/base.rb b/lib/facter/util/ip/base.rb new file mode 100644 index 0000000000..efdb10a237 --- /dev/null +++ b/lib/facter/util/ip/base.rb @@ -0,0 +1,198 @@ +# encoding: UTF-8 + +require 'ipaddr' + +module Facter + module Util + module IP + end + end +end + +class Facter::Util::IP::Base + # A regex to match an IPv4 address from `ifconfig` output. This regex will + # work for most platforms. You can override this in your subclass if you need + # a different regex. + # + # @return [Regexp] + # + # @api public + IPADDRESS_REGEX = /inet.*?([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + + + # A regex to match an IPv6 address from `ifconfig` output. This regex will + # work for most platforms. You can override this in your subclass if you need + # a different regex. + # + # @return [Regexp] + # + # @api public + IPADDRESS6_REGEX = / + inet6.*?((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4}) + /x + + # A regex to match a MAC address from `ifconfig` output. This regex will work + # for most platforms. You can override this in your subclass if you need a + # different regex. + # + # @return [Regexp] + # + # @api public + MACADDRESS_REGEX = /(?:ether|lladdr)\s+(\w?\w:\w?\w:\w?\w:\w?\w:\w?\w:\w?\w)/ + + # A regex to match the netmask from `ifconfig` output. This regex will work + # for most platforms. You can override this in your subclass if you need a + # different regex. + # + # @return [Regexp] + # + # @api public + NETMASK_REGEX = /netmask\s+0x(\w{8})/ + + # A regex to match the MTU from `ifconfig` output. This regex will work for + # most platforms. You can override this in your subclass if you need a + # different regex. + # + # @return [Regexp] + # + # @api public + MTU_REGEX = /mtu\s+(\d+)/ + + # Returns the name of the Class without nesting. Mostly used for finding + # the right class corresponding to the value of Facter.value(:kernel) + # + # @return [String] The string without nesting. + # + # @api public + def self.to_s + super.split('::').last + end + + # Used in conjunction with the `.inherited` hook, this method will store + # an Array of the Class' subclasses. + # + # @return [Array] The subclasses. + # + # @api public + def self.subclasses + @subclasses ||= [] + end + + # Most kernels will need to have their netmask converted from hex. If + # your kernel doesn't display the netmask in hex, you'll need to + # override this method in your subclass to return false. + # + # @return [Boolean] true + # + # @api public + def self.convert_netmask_from_hex? + true + end + + # Network bonding is creation of a single bonded interface by combining 2 or + # more Ethernet interfaces. I think this is mostly used in Linux so this base + # method will return nil, however you should override this in your subclass if + # need be. See the Facter::Util::IP::Linux.bonding_master method. + # + # @return [NilClass] + # + # @api public + def self.bonding_master(interface) + end + + # Returns an array of interfaces from `ifconfig`. e.g. ['eth0', 'eth1'] This + # will work on most platforms, but override in your subclass if need be. + # + # @return [Array] + # + # @api public + def self.interfaces + exec("#{ifconfig_path} -a 2> /dev/null").to_s.scan(/^\w+/).uniq + end + + # Get the value of an interface and label. For example, you may want to find + # the MTU for eth0. + # + # @param interface [String] label [String] and optional command [String] + # + # @return [String] or [NilClass] + # + # @api public + def self.value_for_interface_and_label(interface, label, cmd = nil) + if regex = regex_for(label) + cmd ||= "#{ifconfig_path} #{interface} 2> /dev/null" + + if match = regex.match(exec(cmd).to_s) + if label == 'netmask' && convert_netmask_from_hex? + match[1].scan(/../).map { |byte| byte.to_i(16) }.join('.') + else + match[1] + end + end + end + end + + # Returns the IP network for the given interface. + # + # @return [String] or [NilClass] + # + # @api public + def self.network(interface) + ipaddress = value_for_interface_and_label(interface, "ipaddress") + netmask = value_for_interface_and_label(interface, "netmask") + + if ipaddress && netmask + ip = IPAddr.new(ipaddress, Socket::AF_INET) + subnet = IPAddr.new(netmask, Socket::AF_INET) + + ip.mask(subnet.to_s).to_s + end + end + + private + + # This is a Ruby hook method that will help to maintain a list of + # subclasses. See the `.subclasses` method for more information. + # + # @return [Array] The subclasses. + # + # @api private + def self.inherited subclass + subclasses << subclass + end + + # This loops over `ifconfig` paths to find the first that is executable. + # + # @return [String] + # + # @api private + def self.ifconfig_path + %w[/bin/ifconfig /sbin/ifconfig /usr/sbin/ifconfig].find do |path| + File.executable?(path) + end + end + + # Delegation method to Facter::Util::Resolution.exec. + # + # @param command [String] the command to execute + # + # @return [String] or [Nil] + # + # @api private + def self.exec string + Facter::Util::Resolution.exec string + end + + # Grabs the corresponding regex constant. e.g. NETMASK_REGEX + # + # @param label [String] e.g. 'netmask' + # + # @return [Regexp] or [NilClass] + # + # @api private + def self.regex_for label + constant = "#{label.to_s.upcase}_REGEX" + + const_get(constant) if constants.find { |c| /^#{constant}$/.match(c) } + end +end diff --git a/lib/facter/util/ip/darwin.rb b/lib/facter/util/ip/darwin.rb new file mode 100644 index 0000000000..83c95fc791 --- /dev/null +++ b/lib/facter/util/ip/darwin.rb @@ -0,0 +1,6 @@ +# encoding: UTF-8 + +require 'facter/util/ip/base' + +class Facter::Util::IP::Darwin < Facter::Util::IP::Base +end diff --git a/lib/facter/util/ip/dragonfly.rb b/lib/facter/util/ip/dragonfly.rb new file mode 100644 index 0000000000..0ccf87c359 --- /dev/null +++ b/lib/facter/util/ip/dragonfly.rb @@ -0,0 +1,6 @@ +# encoding: UTF-8 + +require 'facter/util/ip/base' + +class Facter::Util::IP::Dragonfly < Facter::Util::IP::Base +end diff --git a/lib/facter/util/ip/free_bsd.rb b/lib/facter/util/ip/free_bsd.rb new file mode 100644 index 0000000000..6db3942c39 --- /dev/null +++ b/lib/facter/util/ip/free_bsd.rb @@ -0,0 +1,6 @@ +# encoding: UTF-8 + +require 'facter/util/ip/base' + +class Facter::Util::IP::FreeBSD < Facter::Util::IP::Base +end diff --git a/lib/facter/util/ip/gnu_k_free_bsd.rb b/lib/facter/util/ip/gnu_k_free_bsd.rb new file mode 100644 index 0000000000..5988a1562c --- /dev/null +++ b/lib/facter/util/ip/gnu_k_free_bsd.rb @@ -0,0 +1,9 @@ +# encoding: UTF-8 + +require 'facter/util/ip/base' + +class Facter::Util::IP::GNUkFreeBSD < Facter::Util::IP::Base + def self.to_s + 'GNU/kFreeBSD' + end +end diff --git a/lib/facter/util/ip/hpux.rb b/lib/facter/util/ip/hpux.rb new file mode 100644 index 0000000000..d1c127c1b5 --- /dev/null +++ b/lib/facter/util/ip/hpux.rb @@ -0,0 +1,76 @@ +# encoding: UTF-8 + +require 'facter/util/ip/base' + +class Facter::Util::IP::HPUX < Facter::Util::IP::Base + # A regex to match an IPv4 address from `ifconfig` output. + # + # @return [Regexp] + # + # @api public + IPADDRESS_REGEX = /\s+inet (\S+)\s.*/ + + # A regex to match a MAC address from `ifconfig` output. + # + # @return [Regexp] + # + # @api public + MACADDRESS_REGEX = /(\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/ + + # A regex to match the netmask from `ifconfig` output. + # + # @return [Regexp] + # + # @api public + NETMASK_REGEX = /.*\s+netmask (\S+)\s.*/ + + # The path to the `lanscan` executable. + # + # @return [Regexp] + # + # @api public + LANSCAN = '/usr/sbin/lanscan' + + def self.to_s + 'HP-UX' + end + + # Gets an array of interfaces from `netstat`. The cryptic text replacements in + # the method handles NIC bonding where asterisks and virtual NICs are printed. + # See (#17487)[https://projects.puppetlabs.com/issues/17487] for more info. + # + # @return [Array] + # + # @api public + def self.interfaces + exec("/bin/netstat -in"). + to_s. + gsub(/\*/, ""). + gsub(/^[^\n]*none[^\n]*\n/, ""). + sub(/^[^\n]*\n/, ""). + scan(/^\w+/) + end + + def self.value_for_interface_and_label(interface, label) + value = super(interface, label) + + if !value && label == 'macaddress' + if macaddress = lanscan.to_s[/\dx(\S+).*UP\s+#{interface}/, 1] + macaddress.scan(/../).join(':') + end + else + value + end + end + + private + + # Execute lanscan. + # + # @return [String] or [NilClass] + # + # @api private + def self.lanscan + exec(LANSCAN) if File.exist?(LANSCAN) + end +end diff --git a/lib/facter/util/ip/linux.rb b/lib/facter/util/ip/linux.rb new file mode 100644 index 0000000000..61dbdf70a7 --- /dev/null +++ b/lib/facter/util/ip/linux.rb @@ -0,0 +1,169 @@ +# encoding: UTF-8 + +require 'facter/util/ip/base' + +class Facter::Util::IP::Linux < Facter::Util::IP::Base + # A regex to match an IPv4 address from `ifconfig` output. + # + # @return [Regexp] + # + # @api public + IPADDRESS_REGEX = /inet\s(?:addr:)?([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + + # A regex to match an IPv6 address from `ifconfig` output. + # + # @return [Regexp] + # + # @api public + IPADDRESS6_REGEX = /inet6\s(?:addr: )?((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/ + + + # A regex to match a MAC address from `ifconfig` output. + # + # @return [Regexp] + # + # @api public + MACADDRESS_REGEX = /(?:ether|HWaddr)\s+((\w{1,2}:){5,}\w{1,2})/ + + # A regex to match the netmask from `ifconfig` output. + # + # @return [Regexp] + # + # @api public + NETMASK_REGEX = /Mask:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + + # A regex to match the MTU from `ifconfig` output. + # + # @return [Regexp] + # + # @api public + MTU_REGEX = /MTU:(\d+)/ + + # Linux doesn't display netmask in hex. + # + # @return [Boolean] false by default + # + # @api public + def self.convert_netmask_from_hex? + false + end + + # Network bonding is creation of a single bonded interface by combining 2 or + # more Ethernet interfaces. This method returns the bonding master. + # + # @param interface [String] the interface, e.g. 'eth0' + # + # @return [String] or [NilClass] + # + # @api public + def self.bonding_master(interface) + # We need `ip` instead of `ifconfig` because it shows us the bonding master. + return unless FileTest.executable?("/sbin/ip") + + # A bonding interface can never be an alias interface. Alias interfaces do + # have a colon in their name and the ip link show command throws an error + # message when we pass it an alias interface. + return if interface.match(/:/) + + regex = /SLAVE[,>].* (bond[0-9]+)/ + ethbond = regex.match(%x{/sbin/ip link show #{interface}}) + + ethbond[1] if ethbond + end + + # Returns an array of interfaces. e.g. ['eth0', 'eth1'] We will check sysfs + # first, since that is the fastest option, but fallback to `ifconfig` if + # neccessary. + # + # @return [Array] + # + # @api public + def self.interfaces + if File.exist?('/sys/class/net') + Dir.glob('/sys/class/net/*').map do |name| + name.split('/').last + end + else + super + end + end + + # Get the value of an interface and label. For example, you may want to find + # the MTU for eth0. If an infiniband interface is passed, it will try to + # obtain the real value. + # + # @param interface [String] label [String] and optional command [String] + # + # @return [String] or [NilClass] + # + # @api public + def self.value_for_interface_and_label(interface, label) + if label == 'macaddress' + bonddev = bonding_master(interface) + + if infiniband?(interface) + infiniband_macaddress(interface) || super + elsif bonddev + bonddev_macaddress(bonddev, interface) || super + else + super + end + else + super + end + end + + private + + # Boolean method to test whether an interface is the infiniband. + # + # @param interface [String] e.g. 'ib0' + # + # @return [Boolean] true or false + # + # @api private + def self.infiniband?(interface) + !!/^ib/.match(interface) + end + + # Attempts to obtain the real macaddress for an infiniband interface. + # + # @param interface [String] e.g. 'ib0' + # + # @return [String] or [NilClass] + # + # @api private + def self.infiniband_macaddress(interface) + sysfs = "/sys/class/net/#{interface}/address" + + if File.exists?(sysfs) + exec("cat #{sysfs}") + elsif File.exists?("/sbin/ip") + exec("/sbin/ip link show #{interface}"). + to_s. + scan(%r{infiniband\s+((\w{1,2}:){5,}\w{1,2})})[0] + end + end + + # Attempts to obtain the real macaddress for a bonded interface. + # + # @param bonddev [String] interface [String] e.g. 'bond0', 'eth0' + # + # @return [String] or [NilClass] + # + # @api private + def self.bonddev_macaddress(bonddev, interface) + path = "/proc/net/bonding/#{bonddev}" + + if File.exists?(path) + bondinfo = File.read(path) + regex = / + ^Slave\sInterface:\s + #{interface}\b.*?\bPermanent\sHW\saddr:\s(([0-9A-F]{2}:?)*)$ + /imx + match = regex.match(bondinfo) + + match[1].upcase if match + end + end +end diff --git a/lib/facter/util/ip/net_bsd.rb b/lib/facter/util/ip/net_bsd.rb new file mode 100644 index 0000000000..3b97c9c158 --- /dev/null +++ b/lib/facter/util/ip/net_bsd.rb @@ -0,0 +1,6 @@ +# encoding: UTF-8 + +require 'facter/util/ip/base' + +class Facter::Util::IP::NetBSD < Facter::Util::IP::Base +end diff --git a/lib/facter/util/ip/open_bsd.rb b/lib/facter/util/ip/open_bsd.rb new file mode 100644 index 0000000000..3204a70194 --- /dev/null +++ b/lib/facter/util/ip/open_bsd.rb @@ -0,0 +1,6 @@ +# encoding: UTF-8 + +require 'facter/util/ip/base' + +class Facter::Util::IP::OpenBSD < Facter::Util::IP::Base +end diff --git a/lib/facter/util/ip/sun_os.rb b/lib/facter/util/ip/sun_os.rb new file mode 100644 index 0000000000..78efd9c7c5 --- /dev/null +++ b/lib/facter/util/ip/sun_os.rb @@ -0,0 +1,24 @@ +# encoding: UTF-8 + +require 'facter/util/ip/base' + +class Facter::Util::IP::SunOS < Facter::Util::IP::Base + # A regex to match the netmask from `ifconfig` output. + # + # @return [Regexp] + # + # @api public + NETMASK_REGEX = /netmask\s(\w{8})/ + + # Get the value of an interface and label. For example, you may want to find + # the MTU for eth0. + # + # @param interface [String] label [String] e.g ['eth0', 'MTU'] + # + # @return [String] or [NilClass] + # + # @api public + def self.value_for_interface_and_label(interface, label) + super(interface, label, "#{ifconfig_path} #{interface}") + end +end diff --git a/lib/facter/util/ip/windows.rb b/lib/facter/util/ip/windows.rb new file mode 100644 index 0000000000..45b65861bd --- /dev/null +++ b/lib/facter/util/ip/windows.rb @@ -0,0 +1,73 @@ +# encoding: UTF-8 + +require 'facter/util/ip/base' + +class Facter::Util::IP::Windows < Facter::Util::IP::Base + # A regex to match an IPv4 address from `ifconfig` output. + # + # @return [Regexp] + # + # @api public + IPADDRESS_REGEX = /\s+IP\sAddress:\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + + # A regex to match an IPv6 address from `ifconfig` output. + # + # @return [Regexp] + # + # @api public + IPADDRESS6_REGEX = /Address\s((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/ + + # A regex to match the netmask from `ifconfig` output. + # + # @return [Regexp] + # + # @api public + NETMASK_REGEX = /\s+Subnet\sPrefix:\s+\S+\s+\(mask\s([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\)/ + + # The path to netsh.exe. + # + # @return [String] + # + # @api public + NETSH = "#{ENV['SYSTEMROOT']}/system32/netsh.exe" + + def self.to_s + 'windows' + end + + # Windows doesn't display netmask in hex. + # + # @return [Boolean] false by default + # + # @api public + def self.convert_netmask_from_hex? + false + end + + # Uses netsh.exe to obtain a list of interfaces. + # + # @return [Array] + # + # @api public + def self.interfaces + cmd = "#{NETSH} interface %s show interface" + output = exec("#{cmd % 'ip'} && #{cmd % 'ipv6'}").to_s + + output.scan(/\s* connected\s*(\S.*)/).flatten.uniq + end + + # Get the value of an interface and label. For example, you may want to find + # the MTU for eth0. Uses netsh.exe. + # + # @param interface [String] label [String] + # + # @return [String] or [NilClass] + # + # @api public + def self.value_for_interface_and_label(interface, label) + opt = label == 'ipaddress6' ? 'ipv6' : 'ip' + cmd = "#{NETSH} interface #{opt} show address \"#{interface}\"" + + super(interface, label, cmd) + end +end diff --git a/spec/fixtures/unit/util/ip/darwin_ifconfig_all_with_multiple_interfaces b/spec/fixtures/unit/util/ip/darwin/ifconfig_all_with_multiple_interfaces similarity index 100% rename from spec/fixtures/unit/util/ip/darwin_ifconfig_all_with_multiple_interfaces rename to spec/fixtures/unit/util/ip/darwin/ifconfig_all_with_multiple_interfaces diff --git a/spec/fixtures/unit/util/ip/darwin/ifconfig_with_single_interface b/spec/fixtures/unit/util/ip/darwin/ifconfig_with_single_interface new file mode 100644 index 0000000000..6a4e10326c --- /dev/null +++ b/spec/fixtures/unit/util/ip/darwin/ifconfig_with_single_interface @@ -0,0 +1,6 @@ +en0: flags=8863 mtu 1500 + inet6 fe80::223:6cff:fe99:602b%en1 prefixlen 64 scopeid 0x5 + inet 192.168.0.10 netmask 0xffffff00 broadcast 192.168.0.255 + ether 00:23:6c:99:60:2b + media: autoselect status: active + supported media: autoselect diff --git a/spec/fixtures/unit/util/ip/6.0-STABLE_FreeBSD_ifconfig b/spec/fixtures/unit/util/ip/free_bsd/6.0-STABLE_ifconfig_with_multiple_interfaces similarity index 100% rename from spec/fixtures/unit/util/ip/6.0-STABLE_FreeBSD_ifconfig rename to spec/fixtures/unit/util/ip/free_bsd/6.0-STABLE_ifconfig_with_multiple_interfaces diff --git a/spec/fixtures/unit/util/ip/free_bsd/6.0-STABLE_ifconfig_with_single_interface b/spec/fixtures/unit/util/ip/free_bsd/6.0-STABLE_ifconfig_with_single_interface new file mode 100644 index 0000000000..b1324be986 --- /dev/null +++ b/spec/fixtures/unit/util/ip/free_bsd/6.0-STABLE_ifconfig_with_single_interface @@ -0,0 +1,10 @@ +fxp0: flags=8843 mtu 1500 + options=b + inet x.x.58.26 netmask 0xfffffff8 broadcast x.x.58.31 + inet x.x.58.27 netmask 0xffffffff broadcast x.x.58.27 + inet x.x.58.28 netmask 0xffffffff broadcast x.x.58.28 + inet x.x.58.29 netmask 0xffffffff broadcast x.x.58.29 + inet x.x.58.30 netmask 0xffffffff broadcast x.x.58.30 + ether 00:0e:0c:68:67:7c + media: Ethernet autoselect (100baseTX ) + status: active diff --git a/spec/fixtures/unit/util/ip/debian_kfreebsd_ifconfig b/spec/fixtures/unit/util/ip/gnu_k_free_bsd/ifconfig_all_with_multiple_interfaces similarity index 100% rename from spec/fixtures/unit/util/ip/debian_kfreebsd_ifconfig rename to spec/fixtures/unit/util/ip/gnu_k_free_bsd/ifconfig_all_with_multiple_interfaces diff --git a/spec/fixtures/unit/util/ip/gnu_k_free_bsd/ifconfig_with_single_interface b/spec/fixtures/unit/util/ip/gnu_k_free_bsd/ifconfig_with_single_interface new file mode 100644 index 0000000000..b9ce459282 --- /dev/null +++ b/spec/fixtures/unit/util/ip/gnu_k_free_bsd/ifconfig_with_single_interface @@ -0,0 +1,8 @@ +em1: flags=8843 metric 0 mtu 1500 + options=209b + ether 0:11:a:59:67:91 + inet6 fe80::211:aff:fe59:6791%em1 prefixlen 64 scopeid 0x2 + inet 192.168.10.10 netmask 0xffffff00 broadcast 192.168.10.255 + nd6 options=3 + media: Ethernet autoselect (100baseTX ) + status: active diff --git a/spec/fixtures/unit/util/ip/hpux_1111_ifconfig_lan0 b/spec/fixtures/unit/util/ip/hpux/1111_ifconfig_lan0 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1111_ifconfig_lan0 rename to spec/fixtures/unit/util/ip/hpux/1111_ifconfig_lan0 diff --git a/spec/fixtures/unit/util/ip/hpux_1111_ifconfig_lan1 b/spec/fixtures/unit/util/ip/hpux/1111_ifconfig_lan1 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1111_ifconfig_lan1 rename to spec/fixtures/unit/util/ip/hpux/1111_ifconfig_lan1 diff --git a/spec/fixtures/unit/util/ip/hpux_1111_ifconfig_lo0 b/spec/fixtures/unit/util/ip/hpux/1111_ifconfig_lo0 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1111_ifconfig_lo0 rename to spec/fixtures/unit/util/ip/hpux/1111_ifconfig_lo0 diff --git a/spec/fixtures/unit/util/ip/hpux_1111_lanscan b/spec/fixtures/unit/util/ip/hpux/1111_lanscan similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1111_lanscan rename to spec/fixtures/unit/util/ip/hpux/1111_lanscan diff --git a/spec/fixtures/unit/util/ip/hpux_1111_netstat_in b/spec/fixtures/unit/util/ip/hpux/1111_netstat_in similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1111_netstat_in rename to spec/fixtures/unit/util/ip/hpux/1111_netstat_in diff --git a/spec/fixtures/unit/util/ip/hpux_1131_asterisk_ifconfig_lan0 b/spec/fixtures/unit/util/ip/hpux/1131_asterisk_ifconfig_lan0 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_asterisk_ifconfig_lan0 rename to spec/fixtures/unit/util/ip/hpux/1131_asterisk_ifconfig_lan0 diff --git a/spec/fixtures/unit/util/ip/hpux_1131_asterisk_ifconfig_lan1 b/spec/fixtures/unit/util/ip/hpux/1131_asterisk_ifconfig_lan1 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_asterisk_ifconfig_lan1 rename to spec/fixtures/unit/util/ip/hpux/1131_asterisk_ifconfig_lan1 diff --git a/spec/fixtures/unit/util/ip/hpux_1131_asterisk_ifconfig_lo0 b/spec/fixtures/unit/util/ip/hpux/1131_asterisk_ifconfig_lo0 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_asterisk_ifconfig_lo0 rename to spec/fixtures/unit/util/ip/hpux/1131_asterisk_ifconfig_lo0 diff --git a/spec/fixtures/unit/util/ip/hpux_1131_asterisk_lanscan b/spec/fixtures/unit/util/ip/hpux/1131_asterisk_lanscan similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_asterisk_lanscan rename to spec/fixtures/unit/util/ip/hpux/1131_asterisk_lanscan diff --git a/spec/fixtures/unit/util/ip/hpux_1131_asterisk_netstat_in b/spec/fixtures/unit/util/ip/hpux/1131_asterisk_netstat_in similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_asterisk_netstat_in rename to spec/fixtures/unit/util/ip/hpux/1131_asterisk_netstat_in diff --git a/spec/fixtures/unit/util/ip/hpux_1131_ifconfig_lan0 b/spec/fixtures/unit/util/ip/hpux/1131_ifconfig_lan0 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_ifconfig_lan0 rename to spec/fixtures/unit/util/ip/hpux/1131_ifconfig_lan0 diff --git a/spec/fixtures/unit/util/ip/hpux_1131_ifconfig_lan1 b/spec/fixtures/unit/util/ip/hpux/1131_ifconfig_lan1 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_ifconfig_lan1 rename to spec/fixtures/unit/util/ip/hpux/1131_ifconfig_lan1 diff --git a/spec/fixtures/unit/util/ip/hpux_1131_ifconfig_lo0 b/spec/fixtures/unit/util/ip/hpux/1131_ifconfig_lo0 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_ifconfig_lo0 rename to spec/fixtures/unit/util/ip/hpux/1131_ifconfig_lo0 diff --git a/spec/fixtures/unit/util/ip/hpux_1131_lanscan b/spec/fixtures/unit/util/ip/hpux/1131_lanscan similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_lanscan rename to spec/fixtures/unit/util/ip/hpux/1131_lanscan diff --git a/spec/fixtures/unit/util/ip/hpux_1131_netstat_in b/spec/fixtures/unit/util/ip/hpux/1131_netstat_in similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_netstat_in rename to spec/fixtures/unit/util/ip/hpux/1131_netstat_in diff --git a/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lan1 b/spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_ifconfig_lan1 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lan1 rename to spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_ifconfig_lan1 diff --git a/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lan4 b/spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_ifconfig_lan4 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lan4 rename to spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_ifconfig_lan4 diff --git a/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lan4_1 b/spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_ifconfig_lan4_1 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lan4_1 rename to spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_ifconfig_lan4_1 diff --git a/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lo0 b/spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_ifconfig_lo0 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lo0 rename to spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_ifconfig_lo0 diff --git a/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_lanscan b/spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_lanscan similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_lanscan rename to spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_lanscan diff --git a/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_netstat_in b/spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_netstat_in similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_netstat_in rename to spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_netstat_in diff --git a/spec/fixtures/unit/util/ip/linux_2_6_35_proc_net_bonding_bond0 b/spec/fixtures/unit/util/ip/linux/2_6_35_proc_net_bonding_bond0 similarity index 100% rename from spec/fixtures/unit/util/ip/linux_2_6_35_proc_net_bonding_bond0 rename to spec/fixtures/unit/util/ip/linux/2_6_35_proc_net_bonding_bond0 diff --git a/spec/fixtures/unit/util/ip/linux_ifconfig_all_with_single_interface b/spec/fixtures/unit/util/ip/linux/ifconfig_all_with_single_interface similarity index 100% rename from spec/fixtures/unit/util/ip/linux_ifconfig_all_with_single_interface rename to spec/fixtures/unit/util/ip/linux/ifconfig_all_with_single_interface diff --git a/spec/fixtures/unit/util/ip/linux_get_single_interface_eth0 b/spec/fixtures/unit/util/ip/linux/ifconfig_single_interface_eth0 similarity index 100% rename from spec/fixtures/unit/util/ip/linux_get_single_interface_eth0 rename to spec/fixtures/unit/util/ip/linux/ifconfig_single_interface_eth0 diff --git a/spec/fixtures/unit/util/ip/linux_get_single_interface_lo b/spec/fixtures/unit/util/ip/linux/ifconfig_single_interface_lo similarity index 100% rename from spec/fixtures/unit/util/ip/linux_get_single_interface_lo rename to spec/fixtures/unit/util/ip/linux/ifconfig_single_interface_lo diff --git a/spec/fixtures/unit/util/ip/linux_get_single_interface_ib0 b/spec/fixtures/unit/util/ip/linux/ifconfig_with_single_interface_ib0 similarity index 100% rename from spec/fixtures/unit/util/ip/linux_get_single_interface_ib0 rename to spec/fixtures/unit/util/ip/linux/ifconfig_with_single_interface_ib0 diff --git a/spec/fixtures/unit/util/ip/solaris_ifconfig_all_with_multiple_interfaces b/spec/fixtures/unit/util/ip/sun_os/ifconfig_all_with_multiple_interfaces similarity index 100% rename from spec/fixtures/unit/util/ip/solaris_ifconfig_all_with_multiple_interfaces rename to spec/fixtures/unit/util/ip/sun_os/ifconfig_all_with_multiple_interfaces diff --git a/spec/fixtures/unit/util/ip/solaris_ifconfig_single_interface b/spec/fixtures/unit/util/ip/sun_os/ifconfig_single_interface similarity index 100% rename from spec/fixtures/unit/util/ip/solaris_ifconfig_single_interface rename to spec/fixtures/unit/util/ip/sun_os/ifconfig_single_interface diff --git a/spec/fixtures/unit/util/ip/windows_netsh_all_interfaces b/spec/fixtures/unit/util/ip/windows/netsh_all_interfaces similarity index 100% rename from spec/fixtures/unit/util/ip/windows_netsh_all_interfaces rename to spec/fixtures/unit/util/ip/windows/netsh_all_interfaces diff --git a/spec/fixtures/unit/util/ip/windows_netsh_single_interface b/spec/fixtures/unit/util/ip/windows/netsh_with_single_interface similarity index 100% rename from spec/fixtures/unit/util/ip/windows_netsh_single_interface rename to spec/fixtures/unit/util/ip/windows/netsh_with_single_interface diff --git a/spec/fixtures/unit/util/ip/windows_netsh_single_interface6 b/spec/fixtures/unit/util/ip/windows/netsh_with_single_interface6 similarity index 100% rename from spec/fixtures/unit/util/ip/windows_netsh_single_interface6 rename to spec/fixtures/unit/util/ip/windows/netsh_with_single_interface6 diff --git a/spec/unit/blockdevices_spec.rb b/spec/unit/blockdevices_spec.rb index 54620b8e07..92c4e972c9 100644 --- a/spec/unit/blockdevices_spec.rb +++ b/spec/unit/blockdevices_spec.rb @@ -1,5 +1,3 @@ -#!/usr/bin/env ruby - require 'spec_helper' require 'facter' require 'facter/util/nothing_loader' diff --git a/spec/unit/hardwareisa_spec.rb b/spec/unit/hardwareisa_spec.rb index a709e8c742..d613026251 100755 --- a/spec/unit/hardwareisa_spec.rb +++ b/spec/unit/hardwareisa_spec.rb @@ -1,5 +1,3 @@ -#!/usr/bin/env ruby - require 'spec_helper' require 'facter' diff --git a/spec/unit/hardwaremodel_spec.rb b/spec/unit/hardwaremodel_spec.rb index a168c11ef5..e9ef3d40de 100644 --- a/spec/unit/hardwaremodel_spec.rb +++ b/spec/unit/hardwaremodel_spec.rb @@ -1,5 +1,3 @@ -#!/usr/bin/env ruby - require 'spec_helper' require 'facter' diff --git a/spec/unit/interfaces_spec.rb b/spec/unit/interfaces_spec.rb index c495a499eb..4733b49db5 100755 --- a/spec/unit/interfaces_spec.rb +++ b/spec/unit/interfaces_spec.rb @@ -4,8 +4,12 @@ require 'facter/util/ip' shared_examples_for "iface specific ifconfig output" do |platform, address, fixture| + before :each do + Facter::Util::IP::Base.stubs(:exec).returns(my_fixture_read(fixture)) + Facter::Util::IP.kernel_class.stubs(:exec).returns(my_fixture_read(fixture)) + end + it "correctly on #{platform} for eth0" do - Facter::Util::IP.stubs(:ifconfig_interface).returns(my_fixture_read(fixture)) subject.value.should == address end end @@ -14,15 +18,14 @@ it "should replace the ':' in an interface list with '_'" do # So we look supported Facter.fact(:kernel).stubs(:value).returns("SunOS") - - Facter::Util::IP.stubs(:get_interfaces).returns %w{eth0:1 eth1:2} + Facter::Util::IP.stubs(:interfaces).returns %w{eth0:1 eth1:2} Facter.fact(:interfaces).value.should == %{eth0_1,eth1_2} end it "should replace non-alphanumerics in an interface list with '_'" do Facter.fact(:kernel).stubs(:value).returns("windows") - Facter::Util::IP.stubs(:get_interfaces).returns ["Local Area Connection", "Loopback \"Pseudo-Interface\" (#1)"] + Facter::Util::IP.stubs(:interfaces).returns ["Local Area Connection", "Loopback \"Pseudo-Interface\" (#1)"] Facter.fact(:interfaces).value.should == %{Local_Area_Connection,Loopback__Pseudo_Interface____1_} end end @@ -41,12 +44,12 @@ context "on Linux" do before :each do - Facter::Util::IP.stubs(:get_interfaces).returns(["eth0"]) + Facter::Util::IP.stubs(:interfaces).returns(["eth0"]) Facter.fact(:kernel).stubs(:value).returns("Linux") end - example_behavior_for "iface specific ifconfig output", "Fedora 18", "10.10.220.1", "eth0_net_tools_2.0.txt" - example_behavior_for "iface specific ifconfig output", "net_tools 1.60", "10.10.220.210", "eth0_net_tools_1.60.txt" + example_behavior_for "iface specific ifconfig output", "Fedora 18", "10.10.220.1", 'eth0_net_tools_2.0.txt' + example_behavior_for "iface specific ifconfig output", "net_tools 1.60", "10.10.220.210", 'eth0_net_tools_1.60.txt' end end @@ -58,7 +61,7 @@ context "on Linux" do before :each do - Facter::Util::IP.stubs(:get_interfaces).returns(["eth0"]) + Facter::Util::IP.stubs(:interfaces).returns(["eth0"]) Facter.fact(:kernel).stubs(:value).returns("Linux") end example_behavior_for "iface specific ifconfig output", "Fedora 18", "dead::21f:bcff:fe0d:5fb1", "eth0_net_tools_2.0.txt" diff --git a/spec/unit/ldom_spec.rb b/spec/unit/ldom_spec.rb index f97d4a8765..27ec932278 100644 --- a/spec/unit/ldom_spec.rb +++ b/spec/unit/ldom_spec.rb @@ -1,5 +1,3 @@ -#!/usr/bin/env ruby - require 'spec_helper' def ldom_fixtures(filename) diff --git a/spec/unit/lsbmajdistrelease_spec.rb b/spec/unit/lsbmajdistrelease_spec.rb index 4397c02f8f..f6bb1dee8f 100755 --- a/spec/unit/lsbmajdistrelease_spec.rb +++ b/spec/unit/lsbmajdistrelease_spec.rb @@ -1,5 +1,3 @@ -#!/usr/bin/env ruby - require 'spec_helper' require 'facter' diff --git a/spec/unit/manufacturer_spec.rb b/spec/unit/manufacturer_spec.rb index cacf470f27..f18bb9b0c3 100644 --- a/spec/unit/manufacturer_spec.rb +++ b/spec/unit/manufacturer_spec.rb @@ -1,5 +1,3 @@ -#!/usr/bin/env ruby - require 'spec_helper' require 'facter' require 'facter/util/manufacturer' diff --git a/spec/unit/uniqueid_spec.rb b/spec/unit/uniqueid_spec.rb index e9870d39fd..3ac9bbf49d 100755 --- a/spec/unit/uniqueid_spec.rb +++ b/spec/unit/uniqueid_spec.rb @@ -1,5 +1,3 @@ -#!/usr/bin/env ruby - require 'spec_helper' require 'facter' diff --git a/spec/unit/util/directory_loader_spec.rb b/spec/unit/util/directory_loader_spec.rb index 79ad2b2cd5..de8f3ca5aa 100644 --- a/spec/unit/util/directory_loader_spec.rb +++ b/spec/unit/util/directory_loader_spec.rb @@ -1,7 +1,4 @@ -#!/usr/bin/env ruby - require 'spec_helper' - require 'facter/util/directory_loader' describe Facter::Util::DirectoryLoader do diff --git a/spec/unit/util/ip/base_spec.rb b/spec/unit/util/ip/base_spec.rb new file mode 100644 index 0000000000..9484c22fa9 --- /dev/null +++ b/spec/unit/util/ip/base_spec.rb @@ -0,0 +1,147 @@ +# encoding: UTF-8 + +require 'spec_helper' +require 'facter/util/ip/base' + +describe Facter::Util::IP::Base do + subject { described_class } + + describe ".subclasses" do + let(:subclasses) { described_class.subclasses } + + it { expect(subclasses).to be_an Array } + + it "should be memoized" do + expect(subclasses).to be subclasses + end + + it "should list subclasses" do + subclass = Class.new described_class + + expect(subclasses).to include subclass + end + end + + describe ".to_s" do + let(:to_s) { described_class.to_s } + + it "should return the name of the class without nesting" do + expect(to_s).to eq 'Base' + end + end + + describe ".convert_netmask_from_hex?" do + let :convert_netmask_from_hex? do + described_class.convert_netmask_from_hex? + end + + it { expect(convert_netmask_from_hex?).to be true } + end + + describe ".bonding_master" do + let(:bonding_master) { described_class.bonding_master('eth0') } + + it { expect(bonding_master).to be_nil } + end + + describe ".interfaces" do + let(:interfaces) { described_class.interfaces } + + it "uses `ifconfig` to list the interfaces" do + described_class.expects(:ifconfig_path).returns('/bin/ifconfig') + described_class.expects(:exec).with("/bin/ifconfig -a 2> /dev/null") + expect(interfaces).to be_an Array + end + end + + describe ".value_for_interface_and_label(interface, label)" do + let :value_for_interface_and_label do + described_class.value_for_interface_and_label interface, label + end + + let(:interface) { 'eth0' } + + describe "there is no regex for the label" do + let(:label) { 'foobar' } + + before :each do + described_class.expects(:regex_for).with(label).returns(nil) + end + + it { expect(value_for_interface_and_label).to be_nil } + end + + describe "there is a regex for the label" do + let(:regex) { // } + let(:ifconfig_path) { '/usr/bin/ifconfig' } + let(:exec_cmd) { "#{ifconfig_path} #{interface} 2> /dev/null" } + let(:ifconfig_output) { "" } + + before :each do + described_class.expects(:ifconfig_path).returns(ifconfig_path) + described_class.expects(:regex_for).with(label).returns(regex) + described_class.expects(:exec).with(exec_cmd).returns(ifconfig_output) + regex.expects(:match).with(ifconfig_output).returns(match_data) + end + + describe "there is a match with the exec output and the regex" do + describe "the label is not 'netmask'" do + let(:match_data) { ['inet 127.0.0.1', '127.0.0.1'] } + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to eq '127.0.0.1' } + end + + describe "the label is 'netmask'" do + let(:label) { 'netmask' } + + describe "the netmask needs to be converted from hex" do + let(:match_data) { ['netmask ffffff00', 'ffffff00'] } + + before :each do + described_class.expects(:convert_netmask_from_hex?).returns(true) + end + + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + end + + describe "the netmask does not need to be converted from hex" do + let(:match_data) { ['netmask 255.255.255.0', '255.255.255.0'] } + + before :each do + described_class.expects(:convert_netmask_from_hex?).returns(false) + end + + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + end + end + end + + describe "there is not a match with the exec output and the regex" do + let(:match_data) { nil } + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to be_nil } + end + end + + describe ".network(interface)" do + let(:network) { described_class.network(interface) } + let(:interface) { 'e1000g0' } + + before :each do + described_class. + expects(:value_for_interface_and_label). + with(interface, 'ipaddress'). + returns('172.16.15.138') + + described_class. + expects(:value_for_interface_and_label). + with(interface, 'netmask'). + returns('255.255.255.0') + end + + it { expect(network).to eq "172.16.15.0" } + end + end +end diff --git a/spec/unit/util/ip/darwin_spec.rb b/spec/unit/util/ip/darwin_spec.rb new file mode 100644 index 0000000000..b9407f29af --- /dev/null +++ b/spec/unit/util/ip/darwin_spec.rb @@ -0,0 +1,87 @@ +# encoding: UTF-8 + +require 'spec_helper' +require 'facter/util/ip/darwin' + +describe Facter::Util::IP::Darwin do + describe ".to_s" do + let :to_s do + described_class.to_s + end + + it "should be 'Darwin'" do + expect(to_s).to eq 'Darwin' + end + end + + describe ".convert_netmask_from_hex?" do + let :convert_netmask_from_hex? do + described_class.convert_netmask_from_hex? + end + + it "should be true" do + expect(convert_netmask_from_hex?).to be true + end + end + + describe ".bonding_master" do + let :bonding_master do + described_class.bonding_master('eth0') + end + + it { expect(bonding_master).to be_nil } + end + + describe ".interfaces" do + let :interfaces do + described_class.interfaces + end + + let :ifconfig_output do + my_fixture_read("ifconfig_all_with_multiple_interfaces") + end + + before :each do + described_class.expects(:ifconfig_path).returns('/usr/bin/ifconfig') + described_class.expects(:exec).with(anything).returns(ifconfig_output) + end + + it "should return an array with two interfaces" do + expect(interfaces).to eq ["lo0", "en0"] + end + end + + describe ".value_for_interface_and_label(interface, label)" do + let :value_for_interface_and_label do + described_class.value_for_interface_and_label interface, label + end + + let(:interface) { 'en0' } + let(:ifconfig_output) { my_fixture_read "ifconfig_with_single_interface" } + let(:ifconfig_path) { "/usr/bin/ifconfig" } + let(:exec_cmd) { "#{ifconfig_path} #{interface} 2> /dev/null" } + + before :each do + described_class.expects(:ifconfig_path).returns(ifconfig_path) + described_class.expects(:exec).with(exec_cmd).returns(ifconfig_output) + end + + describe "macaddress" do + let(:label) { 'macaddress' } + + it { expect(value_for_interface_and_label).to eq '00:23:6c:99:60:2b' } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + end + + describe "mtu" do + let(:label) { 'mtu' } + + it { expect(value_for_interface_and_label).to eq '1500' } + end + end +end diff --git a/spec/unit/util/ip/dragonfly_spec.rb b/spec/unit/util/ip/dragonfly_spec.rb new file mode 100644 index 0000000000..f2c670a209 --- /dev/null +++ b/spec/unit/util/ip/dragonfly_spec.rb @@ -0,0 +1,34 @@ +# encoding: UTF-8 + +require 'spec_helper' +require 'facter/util/ip/dragonfly' + +describe Facter::Util::IP::Dragonfly do + describe ".to_s" do + let :to_s do + described_class.to_s + end + + it "should be 'Dragonfly'" do + expect(to_s).to eq 'Dragonfly' + end + end + + describe ".convert_netmask_from_hex?" do + let :convert_netmask_from_hex? do + described_class.convert_netmask_from_hex? + end + + it "should be true" do + expect(convert_netmask_from_hex?).to be true + end + end + + describe ".bonding_master" do + let(:bonding_master) do + described_class.bonding_master('eth0') + end + + it { expect(bonding_master).to be_nil } + end +end diff --git a/spec/unit/util/ip/free_bsd_spec.rb b/spec/unit/util/ip/free_bsd_spec.rb new file mode 100644 index 0000000000..d69989e492 --- /dev/null +++ b/spec/unit/util/ip/free_bsd_spec.rb @@ -0,0 +1,56 @@ +# encoding: UTF-8 + +require 'spec_helper' +require 'facter/util/ip/free_bsd' + +describe Facter::Util::IP::FreeBSD do + describe ".to_s" do + let :to_s do + described_class.to_s + end + + it "should be 'FreeBSD'" do + expect(to_s).to eq 'FreeBSD' + end + end + + describe ".convert_netmask_from_hex?" do + let :convert_netmask_from_hex? do + described_class.convert_netmask_from_hex? + end + + it "should be true" do + expect(convert_netmask_from_hex?).to be true + end + end + + describe ".bonding_master" do + let :bonding_master do + described_class.bonding_master('eth0') + end + + it { expect(bonding_master).to be_nil } + end + + describe ".value_for_interface_and_label(interface, label)" do + let :value_for_interface_and_label do + described_class.value_for_interface_and_label interface, label + end + + let(:interface) { 'fxp0' } + let(:ifconfig_output) { my_fixture_read "6.0-STABLE_ifconfig_with_single_interface" } + let(:ifconfig_path) { "/usr/bin/ifconfig" } + let(:exec_cmd) { "#{ifconfig_path} #{interface} 2> /dev/null" } + + before :each do + described_class.expects(:ifconfig_path).returns(ifconfig_path) + described_class.expects(:exec).with(exec_cmd).returns(ifconfig_output) + end + + describe "macaddress" do + let(:label) { 'macaddress' } + + it { expect(value_for_interface_and_label).to eq '00:0e:0c:68:67:7c' } + end + end +end diff --git a/spec/unit/util/ip/gnu_k_free_bsd_spec.rb b/spec/unit/util/ip/gnu_k_free_bsd_spec.rb new file mode 100644 index 0000000000..591571d6ec --- /dev/null +++ b/spec/unit/util/ip/gnu_k_free_bsd_spec.rb @@ -0,0 +1,85 @@ +# encoding: UTF-8 + +require 'spec_helper' +require 'facter/util/ip/gnu_k_free_bsd' + +describe Facter::Util::IP::GNUkFreeBSD do + before :each do + Facter.fact(:kernel).stubs(:value).returns('GNU/kFreeBSD') + end + + describe ".to_s" do + let :to_s do + described_class.to_s + end + + it "should be 'GNU/kFreeBSD'" do + expect(to_s).to eq 'GNU/kFreeBSD' + end + end + + describe ".convert_netmask_from_hex?" do + let :convert_netmask_from_hex? do + described_class.convert_netmask_from_hex? + end + + it "should be true" do + expect(convert_netmask_from_hex?).to be true + end + end + + describe ".bonding_master" do + let :bonding_master do + described_class.bonding_master('eth0') + end + + it { expect(bonding_master).to be_nil } + end + + describe ".interfaces" do + let :interfaces do + described_class.interfaces + end + + let :ifconfig_output do + my_fixture_read("ifconfig_all_with_multiple_interfaces") + end + + before :each do + described_class.expects(:ifconfig_path).returns('/usr/bin/ifconfig') + described_class.expects(:exec).with(anything).returns(ifconfig_output) + end + + it "should return an array with six interfaces" do + expect(interfaces).to eq %w[em0 em1 bge0 bge1 lo0 vlan0] + end + end + + describe ".value_for_interface_and_label(interface, label)" do + let :value_for_interface_and_label do + described_class.value_for_interface_and_label interface, label + end + + let(:interface) { 'em1' } + let(:ifconfig_output) { my_fixture_read "ifconfig_with_single_interface" } + let(:ifconfig_path) { "/usr/bin/ifconfig" } + let(:exec_cmd) { "#{ifconfig_path} #{interface} 2> /dev/null" } + + before :each do + described_class.expects(:ifconfig_path).returns(ifconfig_path) + described_class.expects(:exec).with(exec_cmd).returns(ifconfig_output) + end + + describe "macaddress" do + let(:label) { 'macaddress' } + + it { expect(value_for_interface_and_label).to eq '0:11:a:59:67:91' } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + end + end +end diff --git a/spec/unit/util/ip/hpux_spec.rb b/spec/unit/util/ip/hpux_spec.rb new file mode 100644 index 0000000000..ec3f21bfe1 --- /dev/null +++ b/spec/unit/util/ip/hpux_spec.rb @@ -0,0 +1,536 @@ +# encoding: UTF-8 + +require 'spec_helper' +require 'facter/util/ip/hpux' + +describe Facter::Util::IP::HPUX do + before :each do + Facter.fact(:kernel).stubs(:value).returns("HP-UX") + end + + describe ".to_s" do + let :to_s do + described_class.to_s + end + + it "should be 'HP-UX'" do + expect(to_s).to eq 'HP-UX' + end + end + + describe ".convert_netmask_from_hex?" do + let :convert_netmask_from_hex? do + described_class.convert_netmask_from_hex? + end + + it "should be true" do + expect(convert_netmask_from_hex?).to be true + end + end + + describe ".bonding_master" do + let :bonding_master do + described_class.bonding_master('eth0') + end + + it { expect(bonding_master).to be_nil } + end + + describe ".interfaces" do + let :interfaces do + described_class.interfaces + end + + before :each do + described_class.stubs(:exec).with(anything).returns(netstat_output) + end + + describe "version 11.11" do + let :netstat_output do + my_fixture_read("1111_netstat_in") + end + + it "should return an array of interfaces" do + expect(interfaces).to eq %w[lan1 lan0 lo0] + end + end + + describe "version 11.31" do + let :netstat_output do + my_fixture_read("1131_netstat_in") + end + + it "should return an array of interfaces" do + expect(interfaces).to eq %w[lan1 lan0 lo0] + end + end + end + + describe ".value_for_interface_and_label(interface, label)" do + let :value_for_interface_and_label do + described_class.value_for_interface_and_label interface, label + end + + describe "version 11.11" do + let(:ifconfig_output) { my_fixture_read("1111_ifconfig_#{interface}") } + let(:lanscan_output) { my_fixture_read("1111_lanscan") } + + before :each do + described_class.stubs(:exec).returns(ifconfig_output) + end + + describe "lan1" do + let(:interface) { 'lan1' } + + describe "ipaddress" do + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to eq '10.1.1.6' } + end + + describe "mtu" do + let(:label) { 'mtu' } + + it { pending "(#17808) MTU has not been implemented for HP-UX" } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + end + + describe "macaddress" do + let(:label) { 'macaddress' } + + before :each do + described_class.expects(:lanscan).returns(lanscan_output) + end + + it { expect(value_for_interface_and_label).to eq "00:10:79:7B:5C:DE" } + end + end + + describe "lan0" do + let(:interface) { 'lan0' } + + describe "ipaddress" do + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to eq "192.168.3.10" } + end + + describe "mtu" do + let(:label) { 'mtu' } + + it { pending "(#17808) MTU has not been implemented for HP-UX" } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + end + + describe "macaddress" do + let(:label) { 'macaddress' } + + before :each do + described_class.expects(:lanscan).returns(lanscan_output) + end + + it { expect(value_for_interface_and_label).to eq "00:30:7F:0C:79:DC" } + end + end + + describe "lo0" do + let(:interface) { 'lo0' } + + describe "ipaddress" do + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to eq "127.0.0.1" } + end + + describe "mtu" do + let(:label) { 'mtu' } + + it { pending "(#17808) MTU has not been implemented for HP-UX" } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.0.0.0' } + end + + describe "macaddress" do + let(:label) { 'macaddress' } + + it { expect(value_for_interface_and_label).to be_nil } + end + end + end + + describe "version 11.31" do + describe "when interfaces are normal" do + let(:ifconfig_output) { my_fixture_read("1131_ifconfig_#{interface}") } + let(:lanscan_output) { my_fixture_read("1131_lanscan") } + + before :each do + described_class.stubs(:exec).returns(ifconfig_output) + end + + describe "lan1" do + let(:interface) { 'lan1' } + + describe "ipaddress" do + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to eq '10.1.54.36' } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + end + + describe "macaddress" do + let(:label) { 'macaddress' } + let(:macaddress) { '00:17:FD:2D:2A:57' } + + before :each do + described_class.expects(:lanscan).returns(lanscan_output) + end + + it { expect(value_for_interface_and_label).to eq macaddress } + end + + describe "mtu" do + let(:label) { 'mtu' } + + it { pending "(#17808) MTU has not been implemented for HP-UX" } + end + end + + describe "lan0" do + let(:interface) { 'lan0' } + + describe "ipaddress" do + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to eq '192.168.30.152' } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + end + + describe "macaddress" do + let(:label) { 'macaddress' } + let(:macaddress) { '00:12:31:7D:62:09' } + + before :each do + described_class.expects(:lanscan).returns(lanscan_output) + end + + it { expect(value_for_interface_and_label).to eq macaddress } + end + + describe "mtu" do + let(:label) { 'mtu' } + + it { pending "(#17808) MTU has not been implemented for HP-UX" } + end + end + + describe "lo0" do + let(:interface) { 'lo0' } + + describe "ipaddress" do + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to eq '127.0.0.1' } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.0.0.0' } + end + + describe "macaddress" do + let(:label) { 'macaddress' } + + before :each do + described_class.expects(:lanscan).returns(lanscan_output) + end + + it { expect(value_for_interface_and_label).to be_nil } + end + + describe "mtu" do + let(:label) { 'mtu' } + + it { pending "(#17808) MTU has not been implemented for HP-UX" } + end + end + end + + describe "when an interface has an asterisk appended" do + let(:lanscan_output) { my_fixture_read("1131_asterisk_lanscan") } + + let :ifconfig_output do + my_fixture_read "1131_asterisk_ifconfig_#{interface}" + end + + before :each do + described_class.stubs(:exec).returns(ifconfig_output) + end + + describe "lan1" do + let(:interface) { 'lan1' } + + describe "ipaddress" do + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to eq '10.10.0.5' } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + end + + describe "macaddress" do + let(:label) { 'macaddress' } + let(:macaddress) { '00:10:79:7B:BE:46' } + + before :each do + described_class.expects(:lanscan).returns(lanscan_output) + end + + it { expect(value_for_interface_and_label).to eq macaddress } + end + + describe "mtu" do + let(:label) { 'mtu' } + + it { pending "(#17808) MTU has not been implemented for HP-UX" } + end + end + + describe "lan0" do + let(:interface) { 'lan0' } + + describe "ipaddress" do + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to eq '192.168.3.9' } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + end + + describe "macaddress" do + let(:label) { 'macaddress' } + let(:macaddress) { '00:30:5D:06:26:B2' } + + before :each do + described_class.expects(:lanscan).returns(lanscan_output) + end + + it { expect(value_for_interface_and_label).to eq macaddress } + end + + describe "mtu" do + let(:label) { 'mtu' } + + it { pending "(#17808) MTU has not been implemented for HP-UX" } + end + end + + describe "lo0" do + let(:interface) { 'lo0' } + + describe "ipaddress" do + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to eq '127.0.0.1' } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.0.0.0' } + end + + describe "macaddress" do + let(:label) { 'macaddress' } + + before :each do + described_class.expects(:lanscan).returns(lanscan_output) + end + + it { expect(value_for_interface_and_label).to be_nil } + end + + describe "mtu" do + let(:label) { 'mtu' } + + it { pending "(#17808) MTU has not been implemented for HP-UX" } + end + end + end + + describe "when an interface is bonded and has one virtual interface" do + let(:lanscan_output) { my_fixture_read "1131_nic_bonding_lanscan" } + + let :ifconfig_output do + my_fixture_read "1131_nic_bonding_ifconfig_#{interface.sub(':', '_')}" + end + + before :each do + described_class.stubs(:exec).returns(ifconfig_output) + end + + describe "lan1" do + let(:interface) { 'lan1' } + + describe "ipaddress" do + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to eq '192.168.30.32' } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + end + + describe "macaddress" do + let(:label) { 'macaddress' } + let(:macaddress) { '00:12:81:9E:48:DE' } + + before :each do + described_class.expects(:lanscan).returns(lanscan_output) + end + + it { expect(value_for_interface_and_label).to eq macaddress } + end + + describe "mtu" do + let(:label) { 'mtu' } + + it { pending "(#17808) MTU has not been implemented for HP-UX" } + end + end + + describe "lo0" do + let(:interface) { 'lo0' } + + describe "ipaddress" do + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to eq '127.0.0.1' } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.0.0.0' } + end + + describe "macaddress" do + let(:label) { 'macaddress' } + + before :each do + described_class.expects(:lanscan).returns(lanscan_output) + end + + it { expect(value_for_interface_and_label).to be_nil } + end + + describe "mtu" do + let(:label) { 'mtu' } + + it { pending "(#17808) MTU has not been implemented for HP-UX" } + end + end + + describe "lan4" do + let(:interface) { 'lan4' } + + describe "ipaddress" do + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to eq '192.168.32.75' } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + end + + describe "macaddress" do + let(:label) { 'macaddress' } + let(:macaddress) { '00:12:81:9E:4A:7E' } + + before :each do + described_class.expects(:lanscan).returns(lanscan_output) + end + + it { expect(value_for_interface_and_label).to eq macaddress } + end + + describe "mtu" do + let(:label) { 'mtu' } + + it { pending "(#17808) MTU has not been implemented for HP-UX" } + end + end + + describe "lan4:1" do + let(:interface) { 'lan4:1' } + + describe "ipaddress" do + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to eq '192.168.1.197' } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + end + + describe "macaddress" do + let(:label) { 'macaddress' } + + before :each do + described_class.expects(:lanscan).returns(lanscan_output) + end + + it { expect(value_for_interface_and_label).to be_nil } + end + + describe "mtu" do + let(:label) { 'mtu' } + + it { pending "(#17808) MTU has not been implemented for HP-UX" } + end + end + end + end + end +end diff --git a/spec/unit/util/ip/linux_spec.rb b/spec/unit/util/ip/linux_spec.rb new file mode 100644 index 0000000000..85383115e7 --- /dev/null +++ b/spec/unit/util/ip/linux_spec.rb @@ -0,0 +1,158 @@ +# encoding: UTF-8 + +require 'spec_helper' +require 'facter/util/ip/linux' + +describe Facter::Util::IP::Linux do + before :each do + Facter.fact(:kernel).stubs(:value).returns("Linux") + end + + describe ".to_s" do + let :to_s do + described_class.to_s + end + + it "should be 'Linux'" do + expect(to_s).to eq 'Linux' + end + end + + describe ".convert_netmask_from_hex?" do + let :convert_netmask_from_hex? do + described_class.convert_netmask_from_hex? + end + + it "should be true" do + expect(convert_netmask_from_hex?).to be false + end + end + + describe ".bonding_master(interface)" do + let :bonding_master do + described_class.bonding_master(interface) + end + + describe "on interface aliases" do + let :interface do + "eth0:1" + end + + it { expect(bonding_master).to be_nil } + end + end + + describe ".interfaces" do + let :interfaces do + described_class.interfaces + end + + describe "without sysfs" do + let :ifconfig_output do + my_fixture_read("ifconfig_all_with_single_interface") + end + + before :each do + File.expects(:exist?).with('/sys/class/net').returns(false) + described_class.expects(:ifconfig_path).returns('/usr/bin/ifconfig') + described_class.expects(:exec).with(anything).returns(ifconfig_output) + end + + it "should return an array with a single interface and the loopback" do + expect(interfaces).to eq ["eth0", "lo"] + end + end + + describe "with sysfs" do + let :sysfs do + %w[/sys/class/net/eth0 /sys/class/net/lo] + end + + before :each do + File.expects(:exist?).with('/sys/class/net').returns(true) + Dir.expects(:glob).with('/sys/class/net/*').returns(sysfs) + end + + it "should return an array with a single interface and the loopback" do + expect(interfaces).to eq ["eth0", "lo"] + end + end + end + + describe "value_for_interface_and_label(interface, label)" do + let :value_for_interface_and_label do + described_class.value_for_interface_and_label interface, label + end + + describe "infiniband interface" do + let(:interface) { 'ib0' } + + let :ifconfig_output do + my_fixture_read "ifconfig_with_single_interface_ib0" + end + + describe "macaddress" do + let(:label) { 'macaddress' } + + before :each do + described_class.expects(:infiniband_macaddress).returns('bar') + end + + it { expect(value_for_interface_and_label).to eq 'bar' } + end + end + + describe "normal interface" do + let(:ifconfig_path) { '/usr/bin/ifconfig' } + let(:exec_cmd) { "#{ifconfig_path} #{interface} 2> /dev/null" } + + let(:ifconfig_output) do + my_fixture_read "ifconfig_single_interface_#{interface}" + end + + before :each do + described_class.expects(:ifconfig_path).returns(ifconfig_path) + described_class.expects(:exec).with(exec_cmd).returns(ifconfig_output) + end + + describe "eth0" do + let(:interface) { 'eth0' } + + describe "mtu" do + let(:label) { 'mtu' } + + it { expect(value_for_interface_and_label).to eq '1500' } + end + end + + describe "lo" do + let(:interface) { 'lo' } + + describe "mtu" do + let(:label) { 'mtu' } + + it { expect(value_for_interface_and_label).to eq '16436' } + end + end + end + + describe "bonded interface" do + let(:interface) { 'eth0' } + let(:bond) { 'bond0' } + let(:proc_net_path) { '/proc/net/bonding/bond0' } + + before :each do + proc_net = my_fixture_read "2_6_35_proc_net_bonding_#{bond}" + described_class.expects(:bonding_master).with(interface).returns(bond) + File.expects(:exists?).with(proc_net_path).returns(true) + File.expects(:read).with(proc_net_path).returns(proc_net) + end + + describe "macaddress" do + let(:label) { 'macaddress' } + + it { expect(value_for_interface_and_label).to eq "00:11:22:33:44:55" } + end + end + end +end diff --git a/spec/unit/util/ip/net_bsd_spec.rb b/spec/unit/util/ip/net_bsd_spec.rb new file mode 100644 index 0000000000..489e82b2b4 --- /dev/null +++ b/spec/unit/util/ip/net_bsd_spec.rb @@ -0,0 +1,34 @@ +# encoding: UTF-8 + +require 'spec_helper' +require 'facter/util/ip/net_bsd' + +describe Facter::Util::IP::NetBSD do + describe ".to_s" do + let :to_s do + described_class.to_s + end + + it "should be 'NetBSD'" do + expect(to_s).to eq 'NetBSD' + end + end + + describe ".convert_netmask_from_hex?" do + let :convert_netmask_from_hex? do + described_class.convert_netmask_from_hex? + end + + it "should be true" do + expect(convert_netmask_from_hex?).to be true + end + end + + describe ".bonding_master" do + let :bonding_master do + described_class.bonding_master('eth0') + end + + it { expect(bonding_master).to be_nil } + end +end diff --git a/spec/unit/util/ip/open_bsd_spec.rb b/spec/unit/util/ip/open_bsd_spec.rb new file mode 100644 index 0000000000..0a9508dc60 --- /dev/null +++ b/spec/unit/util/ip/open_bsd_spec.rb @@ -0,0 +1,34 @@ +# encoding: UTF-8 + +require 'spec_helper' +require 'facter/util/ip/open_bsd' + +describe Facter::Util::IP::OpenBSD do + describe ".to_s" do + let :to_s do + described_class.to_s + end + + it "should be 'OpenBSD'" do + expect(to_s).to eq 'OpenBSD' + end + end + + describe ".convert_netmask_from_hex?" do + let :convert_netmask_from_hex? do + described_class.convert_netmask_from_hex? + end + + it "should be true" do + expect(convert_netmask_from_hex?).to be true + end + end + + describe ".bonding_master" do + let :bonding_master do + described_class.bonding_master('eth0') + end + + it { expect(bonding_master).to be_nil } + end +end diff --git a/spec/unit/util/ip/sun_os_spec.rb b/spec/unit/util/ip/sun_os_spec.rb new file mode 100644 index 0000000000..23e366612b --- /dev/null +++ b/spec/unit/util/ip/sun_os_spec.rb @@ -0,0 +1,91 @@ +# encoding: UTF-8 + +require 'spec_helper' +require 'facter/util/ip/sun_os' + +describe Facter::Util::IP::SunOS do + before :each do + Facter.fact(:kernel).stubs(:value).with('SunOS') + end + + describe ".to_s" do + let :to_s do + described_class.to_s + end + + it "should be 'SunOS'" do + expect(to_s).to eq 'SunOS' + end + end + + describe ".convert_netmask_from_hex?" do + let :convert_netmask_from_hex? do + described_class.convert_netmask_from_hex? + end + + it "should be true" do + expect(convert_netmask_from_hex?).to be true + end + end + + describe ".bonding_master" do + let :bonding_master do + described_class.bonding_master('eth0') + end + + it { expect(bonding_master).to be_nil } + end + + describe ".interfaces" do + let :interfaces do + described_class.interfaces + end + + let :ifconfig_output do + my_fixture_read("ifconfig_all_with_multiple_interfaces") + end + + before :each do + described_class.expects(:ifconfig_path).returns('/usr/bin/ifconfig') + described_class.expects(:exec).with(anything).returns(ifconfig_output) + end + + it "should return an array with two interfaces" do + expect(interfaces).to eq ["lo0", "e1000g0"] + end + end + + describe ".value_for_interface_and_label(interface, label)" do + let :value_for_interface_and_label do + described_class.value_for_interface_and_label interface, label + end + + let(:interface) { 'e1000g0' } + let(:ifconfig_output) { my_fixture_read "ifconfig_single_interface" } + let(:ifconfig_path) { "/usr/bin/ifconfig" } + let(:exec_cmd) { "#{ifconfig_path} #{interface}" } + + before :each do + described_class.expects(:ifconfig_path).returns(ifconfig_path) + described_class.expects(:exec).with(exec_cmd).returns(ifconfig_output) + end + + describe "ipaddress" do + let(:label) { "ipaddress" } + + it { expect(value_for_interface_and_label).to eq "172.16.15.138" } + end + + describe "netmask" do + let(:label) { "netmask" } + + it { expect(value_for_interface_and_label).to eq "255.255.255.0" } + end + + describe "mtu" do + let(:label) { "mtu" } + + it { expect(value_for_interface_and_label).to eq '1500' } + end + end +end diff --git a/spec/unit/util/ip/windows_spec.rb b/spec/unit/util/ip/windows_spec.rb new file mode 100644 index 0000000000..09fc3fb68c --- /dev/null +++ b/spec/unit/util/ip/windows_spec.rb @@ -0,0 +1,94 @@ +# encoding: UTF-8 + +require 'spec_helper' +require 'facter/util/ip/windows' + +describe Facter::Util::IP::Windows do + before :each do + Facter.fact(:kernel).stubs(:value).returns('windows') + end + + describe ".to_s" do + let(:to_s) { described_class.to_s } + + it { expect(to_s).to eq 'windows' } + end + + describe ".convert_netmask_from_hex?" do + let :convert_netmask_from_hex? do + described_class.convert_netmask_from_hex? + end + + it { expect(convert_netmask_from_hex?).to be false } + end + + describe ".bonding_master" do + let(:bonding_master) { described_class.bonding_master('eth0') } + + it { expect(bonding_master).to be_nil } + end + + describe ".interfaces" do + let(:interfaces) { described_class.interfaces } + + let(:netsh_output) { my_fixture_read "netsh_all_interfaces" } + + let :expected_interfaces do + [ + "Loopback Pseudo-Interface 1", + "Local Area Connection", + "Teredo Tunneling Pseudo-Interface" + ] + end + + before :each do + described_class.expects(:exec).with(anything).returns(netsh_output) + end + + it "should return an array of only connected interfaces" do + expect(interfaces).to eq expected_interfaces + end + end + + describe "value_for_interface_and_label(interface, label)" do + let(:interface) { 'Local Area Connection' } + let(:netsh_output) { my_fixture_read("netsh_with_single_interface") } + + let :value_for_interface_and_label do + described_class.value_for_interface_and_label interface, label + end + + let(:exec_cmd) do + "#{described_class::NETSH} interface ip show address \"#{interface}\"" + end + + before :each do + described_class.expects(:exec).with(exec_cmd).returns(netsh_output) + end + + describe "ipaddress" do + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to eq '172.16.138.216' } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + end + + describe "ipaddress6" do + let(:interface) { 'Teredo Tunneling Pseudo-Interface' } + let(:label) { 'ipaddress6' } + let(:expected_ip) { '2001:0:4137:9e76:2087:77a:53ef:7527' } + let(:netsh_output) { my_fixture_read("netsh_with_single_interface6") } + + let(:exec_cmd) do + "#{described_class::NETSH} interface ipv6 show address \"#{interface}\"" + end + + it { expect(value_for_interface_and_label).to eq expected_ip } + end + end +end diff --git a/spec/unit/util/ip_spec.rb b/spec/unit/util/ip_spec.rb index 3d95ee5cd7..faee2be628 100755 --- a/spec/unit/util/ip_spec.rb +++ b/spec/unit/util/ip_spec.rb @@ -6,416 +6,12 @@ describe Facter::Util::IP do include FacterSpec::ConfigHelper - before :each do - given_a_configuration_of(:is_windows => false) - end - - [:freebsd, :linux, :netbsd, :openbsd, :sunos, :darwin, :"hp-ux", :"gnu/kfreebsd", :windows].each do |platform| + %w{ + FreeBSD Linux NetBSD OpenBSD SunOS Darwin HP-UX GNU/kFreeBSD windows + Dragonfly + }.each do |platform| it "should be supported on #{platform}" do - given_a_configuration_of(:is_windows => platform == :windows) - Facter::Util::IP.supported_platforms.should be_include(platform) - end - end - - it "should return a list of interfaces" do - Facter::Util::IP.should respond_to(:get_interfaces) - end - - it "should return an empty list of interfaces on an unknown kernel" do - File.stubs(:exist?).with('/sys/class/net').returns(false) - Facter.stubs(:value).returns("UnknownKernel") - Facter::Util::IP.get_interfaces().should == [] - end - - it "should return a list with a single interface and the loopback interface on Linux with a single interface without sysfs" do - File.stubs(:exist?).with('/sys/class/net').returns(false) - linux_ifconfig = my_fixture_read("linux_ifconfig_all_with_single_interface") - Facter::Util::IP.stubs(:get_all_interface_output).returns(linux_ifconfig) - Facter::Util::IP.get_interfaces().should =~ ["eth0", "lo"] - end - - it "should return a list two interfaces on Darwin with two interfaces" do - File.stubs(:exist?).with('/sys/class/net').returns(false) - darwin_ifconfig = my_fixture_read("darwin_ifconfig_all_with_multiple_interfaces") - Facter::Util::IP.stubs(:get_all_interface_output).returns(darwin_ifconfig) - Facter::Util::IP.get_interfaces().should == ["lo0", "en0"] - end - - it "should return a list two interfaces on Solaris with two interfaces multiply reporting" do - File.stubs(:exist?).with('/sys/class/net').returns(false) - solaris_ifconfig = my_fixture_read("solaris_ifconfig_all_with_multiple_interfaces") - Facter::Util::IP.stubs(:get_all_interface_output).returns(solaris_ifconfig) - Facter::Util::IP.get_interfaces().should == ["lo0", "e1000g0"] - end - - it "should return a list of six interfaces on a GNU/kFreeBSD with six interfaces" do - File.stubs(:exist?).with('/sys/class/net').returns(false) - kfreebsd_ifconfig = my_fixture_read("debian_kfreebsd_ifconfig") - Facter::Util::IP.stubs(:get_all_interface_output).returns(kfreebsd_ifconfig) - Facter::Util::IP.get_interfaces().should == ["em0", "em1", "bge0", "bge1", "lo0", "vlan0"] - end - - it "should return a list of only connected interfaces on Windows" do - File.stubs(:exist?).with('/sys/class/net').returns(false) - Facter.fact(:kernel).stubs(:value).returns("windows") - windows_netsh = my_fixture_read("windows_netsh_all_interfaces") - Facter::Util::IP.stubs(:get_all_interface_output).returns(windows_netsh) - Facter::Util::IP.get_interfaces().should == ["Loopback Pseudo-Interface 1", "Local Area Connection", "Teredo Tunneling Pseudo-Interface"] - end - - it "should return a value for a specific interface" do - Facter::Util::IP.should respond_to(:get_interface_value) - end - - it "should not return interface information for unsupported platforms" do - Facter.stubs(:value).with(:kernel).returns("bleah") - Facter::Util::IP.get_interface_value("e1000g0", "netmask").should == [] - end - - it "should return ipaddress information for Solaris" do - solaris_ifconfig_interface = my_fixture_read("solaris_ifconfig_single_interface") - - Facter::Util::IP.expects(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("SunOS") - - Facter::Util::IP.get_interface_value("e1000g0", "ipaddress").should == "172.16.15.138" - end - - it "should return netmask information for Solaris" do - solaris_ifconfig_interface = my_fixture_read("solaris_ifconfig_single_interface") - - Facter::Util::IP.expects(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("SunOS") - - Facter::Util::IP.get_interface_value("e1000g0", "netmask").should == "255.255.255.0" - end - - it "should return calculated network information for Solaris" do - solaris_ifconfig_interface = my_fixture_read("solaris_ifconfig_single_interface") - - Facter::Util::IP.stubs(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("SunOS") - - Facter::Util::IP.get_network_value("e1000g0").should == "172.16.15.0" - end - - it "should return macaddress with leading zeros stripped off for GNU/kFreeBSD" do - kfreebsd_ifconfig = my_fixture_read("debian_kfreebsd_ifconfig") - - Facter::Util::IP.expects(:get_single_interface_output).with("em0").returns(kfreebsd_ifconfig) - Facter.stubs(:value).with(:kernel).returns("GNU/kFreeBSD") - - Facter::Util::IP.get_interface_value("em0", "macaddress").should == "0:11:a:59:67:90" - end - - it "should return interface information for FreeBSD supported via an alias" do - ifconfig_interface = my_fixture_read("6.0-STABLE_FreeBSD_ifconfig") - - Facter::Util::IP.expects(:get_single_interface_output).with("fxp0").returns(ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("FreeBSD") - - Facter::Util::IP.get_interface_value("fxp0", "macaddress").should == "00:0e:0c:68:67:7c" - end - - it "should return macaddress information for OS X" do - ifconfig_interface = my_fixture_read("Mac_OS_X_10.5.5_ifconfig") - - Facter::Util::IP.expects(:get_single_interface_output).with("en1").returns(ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("Darwin") - - Facter::Util::IP.get_interface_value("en1", "macaddress").should == "00:1b:63:ae:02:66" - end - - it "should return all interfaces correctly on OS X" do - File.stubs(:exist?).with('/sys/class/net').returns(false) - ifconfig_interface = my_fixture_read("Mac_OS_X_10.5.5_ifconfig") - - Facter::Util::IP.expects(:get_all_interface_output).returns(ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("Darwin") - - Facter::Util::IP.get_interfaces().should == ["lo0", "gif0", "stf0", "en0", "fw0", "en1", "vmnet8", "vmnet1"] - end - - it "should return a human readable netmask on Solaris" do - solaris_ifconfig_interface = my_fixture_read("solaris_ifconfig_single_interface") - - Facter::Util::IP.expects(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("SunOS") - - Facter::Util::IP.get_interface_value("e1000g0", "netmask").should == "255.255.255.0" - end - - it "should return a human readable netmask on Darwin" do - darwin_ifconfig_interface = my_fixture_read("darwin_ifconfig_single_interface") - - Facter::Util::IP.expects(:get_single_interface_output).with("en1").returns(darwin_ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("Darwin") - - Facter::Util::IP.get_interface_value("en1", "netmask").should == "255.255.255.0" - end - - it "should return a human readable netmask on GNU/kFreeBSD" do - kfreebsd_ifconfig = my_fixture_read("debian_kfreebsd_ifconfig") - - Facter::Util::IP.expects(:get_single_interface_output).with("em1").returns(kfreebsd_ifconfig) - Facter.stubs(:value).with(:kernel).returns("GNU/kFreeBSD") - - Facter::Util::IP.get_interface_value("em1", "netmask").should == "255.255.255.0" - end - - it "should return correct macaddress information for infiniband on Linux" do - correct_ifconfig_interface = my_fixture_read("linux_get_single_interface_ib0") - - Facter::Util::IP.expects(:get_single_interface_output).with("ib0").returns(correct_ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("Linux") - - Facter::Util::IP.get_interface_value("ib0", "macaddress").should == "80:00:00:4a:fe:80:00:00:00:00:00:00:00:02:c9:03:00:43:27:21" - end - - it "should replace the incorrect macaddress with the correct macaddress in ifconfig for infiniband on Linux" do - ifconfig_interface = my_fixture_read("linux_ifconfig_ib0") - correct_ifconfig_interface = my_fixture_read("linux_get_single_interface_ib0") - - Facter::Util::IP.expects(:get_infiniband_macaddress).with("ib0").returns("80:00:00:4a:fe:80:00:00:00:00:00:00:00:02:c9:03:00:43:27:21") - Facter::Util::IP.expects(:ifconfig_interface).with("ib0").returns(ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("Linux") - - Facter::Util::IP.get_single_interface_output("ib0").should == correct_ifconfig_interface - end - - it "should return fake macaddress information for infiniband on Linux when neither sysfs or /sbin/ip are available" do - ifconfig_interface = my_fixture_read("linux_ifconfig_ib0") - - File.expects(:exists?).with("/sys/class/net/ib0/address").returns(false) - File.expects(:exists?).with("/sbin/ip").returns(false) - Facter::Util::IP.expects(:ifconfig_interface).with("ib0").returns(ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("Linux") - - Facter::Util::IP.get_interface_value("ib0", "macaddress").should == "FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF" - end - - it "should not get bonding master on interface aliases" do - Facter.stubs(:value).with(:kernel).returns("Linux") - - Facter::Util::IP.get_bonding_master("eth0:1").should be_nil - end - - [:freebsd, :netbsd, :openbsd, :sunos, :darwin, :"hp-ux"].each do |platform| - it "should require conversion from hex on #{platform}" do - Facter::Util::IP.convert_from_hex?(platform).should == true - end - end - - [:windows].each do |platform| - it "should not require conversion from hex on #{platform}" do - Facter::Util::IP.convert_from_hex?(platform).should be_false - end - end - - it "should return an arp address on Linux" do - Facter.stubs(:value).with(:kernel).returns("Linux") - - Facter::Util::IP.expects(:get_arp_value).with("eth0").returns("00:00:0c:9f:f0:04") - Facter::Util::IP.get_arp_value("eth0").should == "00:00:0c:9f:f0:04" - end - - it "should return mtu information on Linux" do - linux_ifconfig = my_fixture_read("linux_ifconfig_all_with_single_interface") - Facter::Util::IP.stubs(:get_all_interface_output).returns(linux_ifconfig) - Facter::Util::IP.stubs(:get_single_interface_output).with("eth0"). - returns(my_fixture_read("linux_get_single_interface_eth0")) - Facter::Util::IP.stubs(:get_single_interface_output).with("lo"). - returns(my_fixture_read("linux_get_single_interface_lo")) - Facter.stubs(:value).with(:kernel).returns("Linux") - - Facter::Util::IP.get_interface_value("eth0", "mtu").should == "1500" - Facter::Util::IP.get_interface_value("lo", "mtu").should == "16436" - end - - it "should return mtu information on Darwin" do - darwin_ifconfig_interface = my_fixture_read("darwin_ifconfig_single_interface") - - Facter::Util::IP.expects(:get_single_interface_output).with("en1").returns(darwin_ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("Darwin") - - Facter::Util::IP.get_interface_value("en1", "mtu").should == "1500" - end - - it "should return mtu information for Solaris" do - solaris_ifconfig_interface = my_fixture_read("solaris_ifconfig_single_interface") - - Facter::Util::IP.expects(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("SunOS") - - Facter::Util::IP.get_interface_value("e1000g0", "mtu").should == "1500" - end - - # (#17487) - tests for HP-UX. - # some fake data for testing robustness of regexps. - def self.fake_netstat_in_examples - examples = [] - examples << ["Header row\na line with none in it\na line without\nanother line without\n", - "a line without\nanother line without\n"] - examples << ["Header row\na line without\na line with none in it\nanother line with none\nanother line without\n", - "a line without\nanother line without\n"] - examples << ["Header row\na line with * asterisks *\na line with none in it\nanother line without\n", - "a line with asterisks \nanother line without\n"] - examples << ["a line with none none none in it\na line with none in it\na line without\nanother line without\n", - "another line without\n"] - examples - end - - fake_netstat_in_examples.each_with_index do |example, i| - input, expected_output = example - it "should pass regexp test on fake netstat input example #{i}" do - Facter.stubs(:value).with(:kernel).returns("HP-UX") - Facter::Util::IP.stubs(:hpux_netstat_in).returns(input) - Facter::Util::IP.get_all_interface_output().should == expected_output - end - end - - # and some real data for exhaustive tests. - def self.hpux_examples - examples = [] - examples << ["HP-UX 11.11", - ["lan1", "lan0", "lo0" ], - ["1500", "1500", "4136" ], - ["10.1.1.6", "192.168.3.10", "127.0.0.1"], - ["255.255.255.0", "255.255.255.0", "255.0.0.0"], - ["00:10:79:7B:5C:DE", "00:30:7F:0C:79:DC", nil ], - [my_fixture_read("hpux_1111_ifconfig_lan1"), - my_fixture_read("hpux_1111_ifconfig_lan0"), - my_fixture_read("hpux_1111_ifconfig_lo0")], - my_fixture_read("hpux_1111_netstat_in"), - my_fixture_read("hpux_1111_lanscan")] - - examples << ["HP-UX 11.31", - ["lan1", "lan0", "lo0" ], - ["1500", "1500", "4136" ], - ["10.1.54.36", "192.168.30.152", "127.0.0.1"], - ["255.255.255.0", "255.255.255.0", "255.0.0.0"], - ["00:17:FD:2D:2A:57", "00:12:31:7D:62:09", nil ], - [my_fixture_read("hpux_1131_ifconfig_lan1"), - my_fixture_read("hpux_1131_ifconfig_lan0"), - my_fixture_read("hpux_1131_ifconfig_lo0")], - my_fixture_read("hpux_1131_netstat_in"), - my_fixture_read("hpux_1131_lanscan")] - - examples << ["HP-UX 11.31 with an asterisk after a NIC that has an address", - ["lan1", "lan0", "lo0" ], - ["1500", "1500", "4136" ], - ["10.10.0.5", "192.168.3.9", "127.0.0.1"], - ["255.255.255.0", "255.255.255.0", "255.0.0.0"], - ["00:10:79:7B:BE:46", "00:30:5D:06:26:B2", nil ], - [my_fixture_read("hpux_1131_asterisk_ifconfig_lan1"), - my_fixture_read("hpux_1131_asterisk_ifconfig_lan0"), - my_fixture_read("hpux_1131_asterisk_ifconfig_lo0")], - my_fixture_read("hpux_1131_asterisk_netstat_in"), - my_fixture_read("hpux_1131_asterisk_lanscan")] - - examples << ["HP-UX 11.31 with NIC bonding and one virtual NIC", - ["lan4:1", "lan1", "lo0", "lan4" ], - ["1500", "1500", "4136", "1500" ], - ["192.168.1.197", "192.168.30.32", "127.0.0.1", "192.168.32.75" ], - ["255.255.255.0", "255.255.255.0", "255.0.0.0", "255.255.255.0" ], - [nil, "00:12:81:9E:48:DE", nil, "00:12:81:9E:4A:7E"], - [my_fixture_read("hpux_1131_nic_bonding_ifconfig_lan4_1"), - my_fixture_read("hpux_1131_nic_bonding_ifconfig_lan1"), - my_fixture_read("hpux_1131_nic_bonding_ifconfig_lo0"), - my_fixture_read("hpux_1131_nic_bonding_ifconfig_lan4")], - my_fixture_read("hpux_1131_nic_bonding_netstat_in"), - my_fixture_read("hpux_1131_nic_bonding_lanscan")] - examples - end - - hpux_examples.each do |example| - description, array_of_expected_ifs, array_of_expected_mtus, - array_of_expected_ips, array_of_expected_netmasks, - array_of_expected_macs, array_of_ifconfig_fixtures, - netstat_in_fixture, lanscan_fixture = example - - it "should return a list three interfaces on #{description}" do - Facter.expects(:value).with(:kernel).at_least_once.returns("HP-UX") - File.expects(:exist?).with('/sys/class/net').returns(false) - Facter::Util::IP.expects(:hpux_netstat_in).returns(netstat_in_fixture) - Facter::Util::IP.get_interfaces.should == array_of_expected_ifs - end - - array_of_expected_ifs.each_with_index do |nic, i| - ifconfig_fixture = array_of_ifconfig_fixtures[i] - expected_mtu = array_of_expected_mtus[i] - expected_ip = array_of_expected_ips[i] - expected_netmask = array_of_expected_netmasks[i] - expected_mac = array_of_expected_macs[i] - - # (#17808) These tests fail because MTU facts haven't been implemented for HP-UX. - #it "should return MTU #{expected_mtu} on #{nic} for #{description} example" do - # Facter.expects(:value).with(:kernel).at_least_once.returns("HP-UX") - # Facter::Util::IP.expects(:hpux_netstat_in).returns(netstat_in_fixture) - # Facter::Util::IP.expects(:hpux_lanscan).returns(lanscan_fixture) - # Facter::Util::IP.expects(:hpux_ifconfig_interface).with(nic).returns(ifconfig_fixture) - # Facter::Util::IP.get_interface_value(nic, "mtu").should == expected_mtu - #end - - it "should return IP #{expected_ip} on #{nic} for #{description} example" do - Facter.expects(:value).with(:kernel).at_least_once.returns("HP-UX") - Facter::Util::IP.expects(:hpux_lanscan).returns(lanscan_fixture) - Facter::Util::IP.expects(:hpux_ifconfig_interface).with(nic).returns(ifconfig_fixture) - Facter::Util::IP.get_interface_value(nic, "ipaddress").should == expected_ip - end - - it "should return netmask #{expected_netmask} on #{nic} for #{description} example" do - Facter.expects(:value).with(:kernel).at_least_once.returns("HP-UX") - Facter::Util::IP.expects(:hpux_lanscan).returns(lanscan_fixture) - Facter::Util::IP.expects(:hpux_ifconfig_interface).with(nic).returns(ifconfig_fixture) - Facter::Util::IP.get_interface_value(nic, "netmask").should == expected_netmask - end - - it "should return MAC address #{expected_mac} on #{nic} for #{description} example" do - Facter.expects(:value).with(:kernel).at_least_once.returns("HP-UX") - Facter::Util::IP.expects(:hpux_lanscan).returns(lanscan_fixture) - Facter::Util::IP.expects(:hpux_ifconfig_interface).with(nic).returns(ifconfig_fixture) - Facter::Util::IP.get_interface_value(nic, "macaddress").should == expected_mac - end - end - end - - describe "on Windows" do - before :each do - Facter.stubs(:value).with(:kernel).returns("windows") - end - - it "should return ipaddress information" do - windows_netsh = my_fixture_read("windows_netsh_single_interface") - - Facter::Util::IP.expects(:get_output_for_interface_and_label).with("Local Area Connection", "ipaddress").returns(windows_netsh) - - Facter::Util::IP.get_interface_value("Local Area Connection", "ipaddress").should == "172.16.138.216" - end - - it "should return a human readable netmask" do - windows_netsh = my_fixture_read("windows_netsh_single_interface") - - Facter::Util::IP.expects(:get_output_for_interface_and_label).with("Local Area Connection", "netmask").returns(windows_netsh) - - Facter::Util::IP.get_interface_value("Local Area Connection", "netmask").should == "255.255.255.0" - end - - it "should return network information" do - windows_netsh = my_fixture_read("windows_netsh_single_interface") - - Facter::Util::IP.stubs(:get_output_for_interface_and_label).with("Local Area Connection", "ipaddress").returns(windows_netsh) - Facter::Util::IP.stubs(:get_output_for_interface_and_label).with("Local Area Connection", "netmask").returns(windows_netsh) - - Facter::Util::IP.get_network_value("Local Area Connection").should == "172.16.138.0" - end - - it "should return ipaddress6 information" do - windows_netsh = my_fixture_read("windows_netsh_single_interface6") - - Facter::Util::IP.expects(:get_output_for_interface_and_label).with("Teredo Tunneling Pseudo-Interface", "ipaddress6").returns(windows_netsh) - - Facter::Util::IP.get_interface_value("Teredo Tunneling Pseudo-Interface", "ipaddress6").should == "2001:0:4137:9e76:2087:77a:53ef:7527" + Facter::Util::IP.supported_platforms.should include platform end end @@ -440,44 +36,4 @@ def self.hpux_examples Facter::Util::IP.exec_ifconfig(["-a","-e","-i","-j"]) end end - describe "get_ifconfig" do - it "assigns /sbin/ifconfig if it is executable" do - File.stubs(:executable?).returns(false) - File.stubs(:executable?).with("/sbin/ifconfig").returns(true) - Facter::Util::IP.get_ifconfig.should eq("/sbin/ifconfig") - end - it "assigns /usr/sbin/ifconfig if it is executable" do - File.stubs(:executable?).returns(false) - File.stubs(:executable?).with("/usr/sbin/ifconfig").returns(true) - Facter::Util::IP.get_ifconfig.should eq("/usr/sbin/ifconfig") - end - it "assigns /bin/ifconfig if it is executable" do - File.stubs(:executable?).returns(false) - File.stubs(:executable?).with("/bin/ifconfig").returns(true) - Facter::Util::IP.get_ifconfig.should eq("/bin/ifconfig") - end - end - - context "with bonded ethernet interfaces on Linux" do - before :each do - Facter.fact(:kernel).stubs(:value).returns("Linux") - end - - describe "Facter::Util::Ip.get_interface_value" do - before :each do - Facter::Util::IP.stubs(:read_proc_net_bonding). - with("/proc/net/bonding/bond0"). - returns(my_fixture_read("linux_2_6_35_proc_net_bonding_bond0")) - - Facter::Util::IP.stubs(:get_bonding_master).returns("bond0") - end - - it 'provides the real device macaddress for eth0' do - Facter::Util::IP.get_interface_value("eth0", "macaddress").should == "00:11:22:33:44:55" - end - it 'provides the real device macaddress for eth1' do - Facter::Util::IP.get_interface_value("eth1", "macaddress").should == "00:11:22:33:44:56" - end - end - end end diff --git a/spec/unit/util/parser_spec.rb b/spec/unit/util/parser_spec.rb index be7b9e7f4d..8a0ae6ee48 100644 --- a/spec/unit/util/parser_spec.rb +++ b/spec/unit/util/parser_spec.rb @@ -1,10 +1,7 @@ -#!/usr/bin/env ruby - require 'spec_helper' - require 'facter/util/parser' require 'tempfile' -require 'tmpdir.rb' +require 'tmpdir' describe Facter::Util::Parser do include PuppetlabsSpec::Files diff --git a/spec/unit/zfs_version_spec.rb b/spec/unit/zfs_version_spec.rb index 905dbc9ee5..9b88392a31 100644 --- a/spec/unit/zfs_version_spec.rb +++ b/spec/unit/zfs_version_spec.rb @@ -1,5 +1,3 @@ -#!/usr/bin/env ruby - require 'spec_helper' describe "zfs_version fact" do diff --git a/spec/unit/zpool_version_spec.rb b/spec/unit/zpool_version_spec.rb index 0cba70fdb6..5952b8f7b0 100644 --- a/spec/unit/zpool_version_spec.rb +++ b/spec/unit/zpool_version_spec.rb @@ -1,5 +1,3 @@ -#!/usr/bin/env ruby - require 'spec_helper' describe "zpool_version fact" do From 28141c536b81d43ce566f1c030d477e058a27578 Mon Sep 17 00:00:00 2001 From: Matthaus Litteken Date: Wed, 26 Sep 2012 12:28:15 -0700 Subject: [PATCH 1190/3753] Remove CHANGELOG from Facter This commit removes the CHANGELOG file in the top level of facter, which only contains commit hashes/ messages from the previous releases, and is of debatable utility. This information can be gleaned easily from a git log, and is not of much use without git. Removing it removes yet another manual process from the releasing of facter. It also removes references of the CHANGELOG from the packaging. This commit was merged into 1.6.x but the CHANGELOG returned to the 1.7.x branch. This is a cherry-pick of that commit into 1.7.x. Conflicts: CHANGELOG install.rb --- CHANGELOG | 1001 ----------------------------------------------------- 1 file changed, 1001 deletions(-) delete mode 100644 CHANGELOG diff --git a/CHANGELOG b/CHANGELOG deleted file mode 100644 index 323a80366d..0000000000 --- a/CHANGELOG +++ /dev/null @@ -1,1001 +0,0 @@ -1.6.12 -=== -398b111 (Maint) Extract common elements of selinux tests -c534126 (#10819) Avoid reading from /proc/self/mounts in ruby -b95ea54 fix yum repo path in yaml file -3ad05f1 Remove version test from facter -fce4b01 fix redhat spec release template variable -903b1d9 Stop using sed to generate the preflight erb -601a967 Use git read-only packaging repo for public access -dd3401e Fixup apple packaging -e0454df Remove libexec from file list as its only in 2.x -1e7f5b3 Update debhelper compat to 7, add format -6659e61 Fixup redhat spec erb template for f17 -6752530 Shift to using packaging repo -fe311c2 Remove obsolete tasks directory -900895f Group requires together -8c18e33 Move facter redhat spec file to erb -84f8e10 Add debian build artifacts to facter project -d2d3baf Replace rake/gempackagetask with rubygems/gempackagetask -6f58b4e Move tasks out of 'rake' subdirectory -db9d154 Move packaging files to ext, rm conf -c0cbe62 (#15464) Make Facter.version settable via Facter.version= -0b49eae (#15464) Make contributing easy via bundle Gemfile -bf6ee4f Retabbed conf/redhat/facter.spec to lineup tag contents. -defbfb8 (#15291) Add Vendor tag to Facter spec file -17243bb Update facter redhat spec for fedora 17 -7ca9122 Update a facter build-requires for f17 -d5d2328 (#11640) Added support for new OpenStack MAC addresses - -1.6.11 -=== -f75e46e Add build-requires of ruby-rdoc for manpage generation -e9e084f (Maint) Update CONTRIBUTING.md to match Puppet -841b99a (#15687) Selinux: Test for policyvers before reading it -10aa3aa (#15049) Return only one selinuxfs path as string from mounts -f7f90e4 Update version nos to match Facter development -ab87a2c Modify facter spec to work with Ruby 1.9 - -1.6.10 -=== -d6a3e91 Make package task depend on tar in Rakfile -f42896d (#14764) Stub architecture fact when Windows facts run on Linux -f44ca52 (maint) Fix hardware model fact for ruby 1.9 -964d1f0 (#12864) Close registry key -ab025bb Revert "Revert "(#12864) Windows: get primary DNS from registry"" -478386d (#10261) Detect x64 architecture on Windows -2043244 (#13678) Remove deprecation msg triggerd by the ipaddress6 fact -d118d81 (#13678) Add filename extension on absolute paths on windows -85654b0 (#13678) Allow passing shell built-ins to exec method on windows -8f4c016 (#13678) Single quote paths on unix with spaces -2d164e8 (#13678) Join PATHs correctly on windows -e7e7e8f (#13678) Extend spec tests for expand_command -0fea7b0 maint: Add shared context for specs to imitate windows or posix -60d0cd2 (#13678) Fix spec failures on windows -121a2ab (#13678) Fix quoting in expand_command -55b1125 (#13678) Add more unit tests for new methods -9086c0a (#13678) Add RDoc documentation for new methods -165ace4 (#13678) Convert command to absolute paths before executing - -2.0.0rc3 -=== -6cc881d Use git describe in Rakefile to determine pkg ver -653e9e0 (#14466) Fix style issues -5a8547d (14466) Warn when no facts found -b050eb1 (#14582) Fix noise in LSB facts - -2.0.0rc2 -=== -0fa58fc (#14521) Ensure install propagates licenses -4eaa7c4 (#14521) Ensure install only strips leading lib dir - -2.0.0rc1 -=== -1dd50f4 Use absolute path for $LOAD_PATH in installer -02ff878 Bump Facter version to 2.0.0 for the release. -ffe7129 (#3909) Strip trailing dots from domain fact -e9326d7 (#14467) Warn when removing relative paths -fa38c60 (#14469) Strip whitespace from frozen strings -c13a517 (maint) Fix broken Windows tests -cb429df Partial revert of CFPropertyList migration. -9f01fe7 (#12147) Remove Darwin 6-specific iphostnumber fact -9810595 (#11466) Remove deprecated memorytotal fact -4a2d358 (#6955) Remove relative dirs from fact search path -6e7d967 (#3226) Wire up fact preserve_whitespace DSL -3a84a0e (#3226) Strip whitespace from fact -3168927 (#7484) Domain Fact should handle TLD -6b9e2e5 (#7484) Domain fact should handle only TLD -5aa2a6f (#12864) Windows: get primary DNS from registry -635ccc4 Namespace CFPropertyList under Puppet::Util -af682cc [#11299] Replace facter/util/plist with cfpropertylist -2e7a108 (#12790) Raise an exception if recursion is detected -9ff4453 Removed exclusive threading -4581b17 (maint) removing trailing whitespace -c80de1c (#11969) Add zfs zpool version facts. -5273842 (#6682) Add Solaris ldom facts and update virtual detection. -f77584f (#12311) use 'ensure' to restore env vars in Resolution.with_env -0010a65 (#10232) Tests for VirtualBox detection via sysfs -40efab4 fix tests for virtual detection -976b7af fix xen0/xenu detection: https://projects.puppetlabs.com/issues/10625 -2a86219 (#11660) Adds feature SSHFP (#11659) Adds ssh spec -4595c48 (#12012) removed extraneous nils -9c2afdb (#12012) Fix bug with overriding LANG environment variable -2fc1b0c (#9574) Add filesystem fact & tests -c93922c (#12012) Fix ENV['LANG'] spec tests on Windows -d8be0a6 (#12012) Fix ruby 1.8.5 incompatibility in new spec test -66cbfac (#12012) Add spec unit tests for env/LANG changes -810c465 (#12012) Remove global override of LANG environment variable -26918b3 (#12012) Move "with_env" utility method from test code into lib code -f47c592 (#1424) Add Solaris zonename fact. -2bdacfb Revert "(#11660) Adds feature SSHFP" -50f6da6 (#11660) Adds feature SSHFP -6c8683a (#11660) Adds feature SSHFP (#11659) Adds ssh spec -64fa8ea (maint) Fix requires for newer rspec revisions so we don't break build -2eb4ede (#9708) Confine facts by kernel not operating system and remove confine for hardwareisa -30e068a (#11082) Add negative tests for operatingsystemrelease -7713d95 (#11082) Add fact operatingsystemrelease for Solaris -844b67b (#10964) Adding the ability to directly set values -a9140d5 (#6617) Deprecate DESTDIR environment variable for installer -dd704c5 (#10238) Adds support for retrieving information about block devices in Linux, with tests; updated to include sizes and be more flexible for future additions -e0cb1ae Revert "Merge branch 'ticket/10251' of git://github.com/jgrocho/facter" -ce67c98 (#10251) Creating RC tarballs should be handled by rake. -64770d0 Try to get VirtualBox info without dmidecode -9df78a1 (#9929) Add support for Mageia to operatingsystem.rb and operatingsystemrelease.rb -51cb8e2 Cleaned up Arch Linux detection routine - -1.6.9 -=== -b398bd8 (#14334) Fix dmidecode based facts on DragonFly BSD -6c46b2c (#14332) Correct stubbing on Ubuntu -753f3a4 Revert "(#12864) Windows: get primary DNS from registry" -ac51593 Wrap dmidecode/pciutils in ifarch block -fbaa8fe (#11511) Correct lsbrelease specfile filename -14eee2b (#12864) Windows: get primary DNS from registry -2842c96 Update rpm spec file -515fd65 (#11511) Split lsb facts into multiple files - -1.6.8 -=== -b86fe4c (#12831) Add rspec tests to have_which method in Resolution -70be957 (#12831) Fix recursion on first kernel fact resolution - -1.6.7 -=== -ab9e26c Updating CHANGELOG and lib/facter.rb for Facter 1.6.7rc1 -bdbf332 (#12720) Add Solaris CPU info to 'processorN' fact. -a7f5924 (#12813) Redirect lspci output to /dev/null -6ec2863 (#12669) Preserve timestamps when installing files - -1.6.6 -=== -e046144 Updated CHANGELOG for 1.6.6rc2 -c273d34 Make ec2 facts work on CentOS again (#12666) -c218d84 (#12362) Use Tempfile to generate temp files -f6bbe14 (#12170) Adds gem spec description Without this patch, the gem spec file is missing a description attribute, which caus -5c5c330 Changes apple rake task to reflect package name facter instead of puppet. -9b5cb26 Updating conf/redhat/facter.spec for 1.6.5 release. -7d3889d (#12079) Fix order-dependent test failure due to odd stubbing. -7f2a0e2 add a simple test for openstack ec2 facts -cb598aa Support EC2 facts on OpenStack - -1.6.5 -=== -71d3d3d (#12077) Add pciutils RPM dependency -1df5b46 (#11566) Add windows support for ec2 facts -d1a33e5 (#11848) Don't hard code ruby install paths in Windows batch files -14cad7e Build a Rake task for building Apple Packages -5a60ca6 (#11559) Switch to RbConfig & Provide alias for RbConfig for pre-1.8.5 -88c9429 (#10271) Identifying 'Amazon' using '/etc/system-release' -2de7b84 (#11661) EC2 rspec tests were using throw not raise to simulate a timeout -b51ccf0 (#11583) Add basic coverage to the ec2 fact -82692ba (#9599) Generalize zone detection -e6cebd3 (#9599) Add nexenta facts -9401b78 (#11583) Switch request method to open-uri monkey patch 'open' -3ccac87 (#9708) Amend requires in specs to use simple requires -b0b5282 (#9708) Confine facts by kernel not operating system and remove confine for hardwareisa -c473e3f (#10309) Remove the with_verbose_disabled method -a99d87c (#10309) Rename tmpfile to tmpfilename to make function clear -d50fc48 (#10309) Move all fixture data in spec/unit/data to spec/fixtures -d6e8523 (#10309) Integrate new PuppetlabsSpec helpers into our existing facter spec code and general spec cleanup -c1604c7 (#10309) Add puppetlabs_spec helper library based on Puppets own puppet_spec helpers -d141e7e (maint) Fix requirement for FileUtils as operatingsystem_spec needs it now -9c224d3 (#11436) Unify memorysize and memorytotal facts -5c6322a (maint) Joined conditional statements for domain -a1dba38 (#11196) Scan all arp entries for an ec2 mac -5cd30eb (#8279) Join ec2 fact output with commas -4633996 (#9789) Extend coverage of operatingsystem specs -6d21f90 Move Linux specific virtual tests to correct block. -cb4e294 (#7753) Added error checking when adding resolves -6201820 (maint) remove redundant arch detection -4f9da1c (#11328) Fix uptime detection on OpenBSD -3f99f16 (#11328) Add virtualisation detection for OpenBSD - -1.6.4 -=== -6406c8f (#11041) Add dmidecode as a requirement for rpm -ed81492 (#10444) Add identification of system boards to Facter -bdbb2da (#10885) Malformed facter.bat when ruby dir contains backreferences -0bad18b (#10490) Handle case where no macaddress can be found - -1.6.3 -=== -b2a66a9 (#7038) Validate prtdiag output in manufacturer -c9db305 (#10228) Ascendos OS support for various facts. -6efadbb (#10233) Adds support for Parallels Server Bare Metal to Facter -ce8f572 (#10079) Remove trailing whitespace -e89758d Updated CHANGELOG for 1.6.2 -7b14b77 (#9928) Correct readlines stubbing to return an array instead of an empty string. -0d6df28 (#9904) Remove windows rspec warning. -f0ccb5e (#9555) Spec tests: Change all cases of tabs and 4 space indentation to 2 space indentation. - -1.6.2 -=== -d7c00f6 (#9852) Fixing watchr on facter -abf636e (#9555) Change all cases of tabs and 4 space indentation to 2 space indentation. -db1b5af (#9830) Add sshecdsakey fact -1b69791 (#9404) Add memory & update processor facts for DragonFly and OpenBSD. -bce2c69 (#9404) De-clumsify CPU count detection and swap detection on OpenBSD. -cd0ae15 (#9404) Efficiency cleanups for DragonFly facts. -d5511f6 (#9404) Add cross-fact support to facter for DragonFly BSD. -0dfc4e9 (#6728) Improve openvz/cloudlinux detection. -2c5ad52 (#6728) Facter improperly detects openvzve on CloudLinux systems -9101e46 (#7951) added OS support for Amazon Linux -b3784f7 add operatingsystema and operatingsystemrelease support for cloudlinux -8605bba (#9787) Change rspec format so we use the default, not document -b579613 (#7726) Silence prtconf error message inside zones -db3c606 (#9786) Add aliases: specs, tests, test in rake that points at 'spec'. -dfda9be (#4980, #6470) Fix architecture in Darwin and Ubuntu -8f938c1 (#6792) Added osfamily fact. -af1ef43 (#6515) Fix for ruby-1.8.5. Switched use of 'line.each' to 'each_line'. -328ff75 (#6515 and #2945) Fix processorcount for arm, sparc & ppc for linux. -51329b8 (#3856) Detect VirtualBox on Darwin as well as Linux and SunOS -83498b5 (#7996) Restrict solaris cpu processor detection -6e29ff7 (#8615) ENV hash is now local to tests -124a09b (#8240) Fixed regex pattern for domain -fd93c5f (#7996) Add solaris processor facts -3f1a163 (#9593) Require rubygems to handle json output for ruby 1.8.7. -c4fe415 (#9295) Added spec tests for Hyper-V detection -ea23417 (#9295) Initial detection of Hyper-V hypervisor -82351ab Stub out OS and HW model to avoid test failures. Only stub vmware -v (don't expect it) since it needn't be invoked if we already identified Xen or something else. -16a8cab (#2747) Fix detection of xen0 vs xenu in Xen 3.2. - -1.6.1 -=== -1f009e0 Updated CHANGELOG for 1.6.1rc4 -3117e82 (#9517) Fix physicalprocessorcount on windows -ef0a9aa Updated CHANGELOG for 1.6.1rc3 -4d93745 (#8491) Prevent repeated loading of fact files -6db71d4 (#9457) Fix logic for domain fact so hostname, then dnsdomainname and finally resolv.conf is used. -18cd964 Updated CHANGELOG for 1.6.1rc2 -7edef60 Change count to length for compatibility -88f343c (#2344) VMware version parsing fix -7457fe5 SELinux spec test fix for ubuntu -8bed5bb Clear messages between test runs -f4ad6bf Macaddress spec test needed operatingsystem fact -6c098cc Rakefile should fail on error -42c5471 Updated CHANGELOG for 1.6.1rc1. -6d47012 (#4869) Implement productname as Darwin hw.model -d28d96c (#4508) Xen HVM domU not detected as virtual -d55983e (#9178) Add Oracle Linux identification -1cb9cb6 (#6610) Fix Autotest proper run -ec04277 (#4228) Ensure MAC address octets have leading zeroes. -241cddc (#6610) Fix rSpec output format -3eb3628 Add Scientific Linux CERN detection to facter. Fixes #9260 -0bf827f (maint) Add kernel stubbing for is_virtual fact spec -f810170 (#7957) is_virtual should be false for openvz host nodes -1414e0b (#9059) is_virtual should be false on vmware_server -7fb0e6a (#8439) Add interface-specific ip facts for Windows -5d5848c (#8439) Add ipaddress6 fact on Windows -7531a2b (#8439) Add ps fact on Windows -ddb67c5 (#8439) Move macaddress resolution on Windows -0721f2f (#7682) Add complete support for Scientific Linux -a347920 (#9183) Add support for Alpine linux detection -824fac0 (#8439) Add physicalprocessorcount and processor facts on Windows -9ef56d6 (#8439) Implement total and free physical memory on Windows -b3e2274 (#8439) Add Facter::Util::WMI module -00bed7a (#8964) Search mountinfo for selinux mount point -cceb74b (maint) Add stubbing and corrections for domain spec -f7daae3 (maint) Remove global var from domain and hostname -e8d00ec (#8964) Search mountinfo for selinux mount point -46cbd68 Fix the SPEC file (COPYING no longer shipped, README -> README.md) -c5d63d4 Fix #2766 - silence unknown sysctl values -2ba8e7b Add document outlining preferred contribution methods -5d9cc84 (#8660) Fix destdir option on Windows -e329450 (#8247) Fixing arp DNS timeout issue. -bdd9e39 Maint: Fix tests to run on Windows -c1b631d Maint: Refactor detection of windows platform -9b7a41d Maint: Deprecate facter resolution interpreter parameter -0356a2a Maint: Fix facter install on Windows -15d0406 use each_line instead of each for strings in ruby 1.9 -08b3f77 (#7854) Add Augeas library version fact -e84c051 Fixed #7307 - Added serial number fact to Solaris -6fb6ee5 (#4869) Implement productname as Darwin hw.model -63c8b11 (#4508) Xen HVM domU not detected as virtual - -1.6.0 -=== -9404a7a (#7670) Add an acceptance test -0c23845 maint: Fix spelling of acceptance directory -926e912 (#7670) Stop preloading all facts in the application -2255abe (#7670) Never fail to find a fact that is present -8002c24 (#7507) Fix 1.9.2 test failure -0635822 Removed inappropriately uncredited Ohai method from ec2 fact -6b1cd16 (#6614) Update ipaddress6 fact to work with Ruby 1.9 -21fe217 (#6612) Changed uptime spec to be endian agnostic -19f96b5 (#6728) Facter improperly detects openvzve on CloudLinux systems -5b10173 (#5135) Fix faulty logic in physicalprocessorcount -53cd946 Ensures that ARP facts are returned only on EC2 hosts -bfa038d Fixed #6974 - Moved to Apache 2.0 license -d56bca8 refactor the mechanism for allowing for resolution ordering to be influenced -9f4c5c6 (#6740) facter doesn't always respect facts in environment variables -7441b32 Partial fix for #6971 - Fix for virtual tests -7f3e89d (#2714) Fixed faulty test -bfc16f6 (#2714) Added timeout to prtdiag resulution -c2ff833 (#5135) Refactored physicalprocessorcount -0c4a98b Re-factor. Do not use pure-Ruby file reading against "/proc/cpuinfo" and possibly any entry under "/sys" from the sysfs file system. -cb52b06 Fix. Using sysfs file system entries to count the number of physical CPUs. Fall-back to "/proc/cpuinfo" included for backward-compatibility with legacy systems. -3efa9d7 (#3856) Add virtualbox detection via lspci (graphics card), dmidecode, and prtdiag for Solaris and corresponding tests. Darwin case is not handled yet. -7c80172 (#6883) Update Facter install.rb to be slightly more informative. -d31e3f9 (#5394) Document each Facter fact. -af4947c (#6862) Add a default subject for the mail_patches rake task -d6967a0 (#6613) Switch solaris macaddress fact to netstat -e056218 (#6817) Fix for Ruby 1.9 by calling .each_line on a string -861c2b2 maint: cleanup whitespace -f6c9927 (#6719) Corrected faulty logic in bugfix -e42e57c (#3856) Add virtualbox detection via lspci (graphics card), dmidecode, and prtdiag for Solaris and corresponding tests. Darwin case is not handled yet. -0b5b546 (#6883) Update Facter install.rb to be slightly more informative. -7c08270 (#5394) Document each Facter fact. -06eb3f5 (#6883) Update Facter install.rb to be slightly more informative. -1063753 (#6862) Add a default subject for the mail_patches rake task -56b5f10 (#6613) Switch solaris macaddress fact to netstat -fd4f31c (#6817) Fix for Ruby 1.9 by calling .each_line on a string -72996ff maint: cleanup whitespace - -1.5.9 -===== -4de8b20 Updated CHANGELOG for 1.5.9rc6 -cc67a01 Removed inappropriately uncredited Ohai method from ec2 fact -69f98da Add facter test for ticket 7039 -f91c120 downcase arp output so that the ec2 arp is matched -a75f0f9 (#7039) Pre-load all facts when requesting a single fact -6b97242 Update CHANGELOG for 1.5.9rc5 -acf0bb2 Ensures that ARP facts are returned only on EC2 hosts -76f544b Updated CHANGELOG for 1.5.9rc4 -09b9f9b (#6795) Update tests to reflect changed exec -3db1cd0 Updated CHANGELOG for 1.5.9rc3 -def3322 (#6795) xendomains: Ignore error output from xm list -f39d487 (#6763) Use Facter::Util::Resolution.exec for arp -3eb9410 arp: Cleanup indendation -50b9b3f Updated CHANGELOG for 1.5.9rc2 -2fb8316 Clean up indentation, and alignment in macaddress_spec.rb -3f0a340 (#6716) fix facter issues on OSX with ipv6 in macaddress.rb. -43f82ef Update CHANGELOG for 1.5.9rc1 -d62e079 Fixed #2346 - A much cleverer EC2 fact -0411d2e Fixed #2346 - Part 1: Added arp fact for Linux -5b6f4fa Discussion on ec2 facts - #2346 -e917e1a Fixed #3087 - Identify VMWare -d0f0f63 (#6327) Memory facts should be available on Mac Darwin -458a22d Incremented release to 1.5.9 -4eb64fe Fixed #6719 Typo -ffd80ac (#5011) Adds swap statistics for OSX -1207765 (#6719) Restricts virtualization types for zones -8d71db3 Fixed #6616 - Stubbing in VMware tests on Linux -aa959df Remove Solaris from the list of confined systems. It won't get the original lsb facts, and it's nonsensical too. -2e48e18 Fixed #6695 - Updated id fact for Darwin et al -d718af4 Fix #6679 - Added Scientific Linux to operatingsystem fact -dea6f78 Further fix to #5485 - SELinux facts -6d6d8da (#2721) Merged patch from Brane GraAnar -868e7ba (#5485) Made selinux_mode fact work -214da73 Fixed #5485 - Updated selinux_mode fact -ba2601f Fix for #6495 - Updated interface detection -93461d9 Fixed #5950 - Solaris ipaddress incorrect after bonding failure -2e06cdc (#6615) fix missing stub calls in loader specs -3c7841e (#5666) windows support for facter/id.rb -dd5d5bf (#4925) - MS Windows doesn't do man pages -52026ee Fixed #5699 - Added processorcount support for S390x -7dd730d Fixed #5699 - Added virtual support for s390x/Zlinux -d6ce08a Fixed #6611 - Fixed broken HPVM test and rationalised test structure -84fa3c4 (#6525) change semicolons to 'then' in case statement for ruby 1.9.2 compatibility -3e6217d Fixes #6521 and other Ruby 1.9 issues -eb5d6fc Fixed #6525 - Test failures on Ruby 1.9.x -cb25119 (#2270) add testing for the new ipaddress6 feature -ea29483 (#2270) add IPv6 support to facter core. -77eb512 (#2270) Remove DWIM code from ipaddress on Darwin. -f5bf0f5 (#6360) Flush Facter top level cache before every test case. -0d7a2e6 Fix #4755: add support for GNU/kFreeBSD platform where missing. -b88a088 (#5510) Facter should load custom fact definitions in filename order. -7a8be16 Refactor #6044 -- use _spec.rb as the pattern for spec tests. -b39f892 Refactor #6044 -- require spec_helper with a consistent path. -a4fe459 Refactor #6044 -- port testing to rspec2 -af9134c (#5086) Try using kstat before falling back to 'who -b' to determine uptime. -cbbfe55 Refactor util/uptime.rb tests to reduce duplication using contexts -f0cc2c0 (#4575) win32 support for manufacturer, productname, & serialnumber -c40fc07 (#1423) Memory facts for Solaris -1985528 (#4754) Change is_virtual logic to not enumerate virtual types -739040f (#4754) Add support for Darwin and Parallels VM to "virtual" fact -9332f8a (#5325) Add tests for SPARC manufacturer and product name -5b561e3 (#5325) Manufacturer and product name on SPARC -9d99079 maint: Fix spec failures caused by having a space in the path to facter's source -89da001 maint: require rubygems so hudson can run the specs -1eef842 Maint: add "Local-branch:" info to mails sent by "rake mail_patches" -f007a9d (#4989) Add xendomains fact -1fa87a9 JSON support. Works in 1.9.1. Warnings in 1.9.2. LoadError on 1.8.7 for some reason -43e203c (#5040) fact virtual should detect hpvm -7cec60a (#5016) is_virtual should be true on solaris zones -f2e66b6 (#5031) Remove redundant puts from RDoc.usage -f4da528 maint: Fix merge error -d62b013 Issue #4889 Fact values should all be strings -07f186d [#4552] Updating --timing to report in milliseconds instead of seconds -1f387a5 [#4552] Apply patch from Dean Wilson -244d2f1 Better fix for Bug 4569: Uptime Fact is incorrect on Windows -11544c1 [#4289] operatingsystemrelease fact for oel, ovs -e6bfdf9 Fix for bug #4569 -8c4d0cd (#4558) Fail with message on --help errors -7210429 [#4558] Refactor facter binary using optparse -b5c85de [#4563] Add a --trace option to the binary -ebcb81b [#4558] Refactor facter binary using optparse -b8b7123 (#4567) Remove unnecessary or non-portable redirects -7ecba71 (#4567) Retain detached HEAD state -1125e1e Make sure FreeBSD spec also works on systems that have /proc/cpuinfo. -889e150 Sync rpm spec file from Fedora/EPEL -725dce0 Rename Reductive Labs to Puppet Labs -ff473ef Updated signing rake task -a85f2b0 [#2865] Fix reporting of virtual facts -f67ec05 [#4567] Add ext/facter-diff to compare output of 2 versions -4050acc Removing stupid .DS_Store files :( -016cf03 [#3703] Fix macaddress fact for Darwin - -1.5.8 -===== -ca2da36 Updated install.rb and created man page -3671c9f [#4583] Refactor uptime to use Resolution.exec -fca8861 [#4594] Reintroduce fix for #1291 from original patch -32c0cb0 [#4594] Revert "fixes #2573, #2085, #1291..." -e7df4c0 Updated CHANGELOG for 1.5.8rc2 -9c9cabd Better fix for Bug 4569: Uptime Fact is incorrect on Windows -01a515f [#4289] operatingsystemrelease fact for oel, ovs -b6c0a6b Fix for bug #4569 -51bcebe Fixed Rakefile package task version detection -81ccb48 Removed references to Reductive Labs in the Rakefile - -1.5.8rc1 -======== -f280703 Incremented version to 1.5.8 -98ef5e8 Updated CHANGELOG for 1.5.8rc1 -4398b36 Updated CHANGELOG rake task -e02be1d [#4156] Updating spec to match Kai's change -bff84c2 [#4156] Applying patch by Kai -b7fe989 [#2330] Update uptime calculation to use /bin/cat -e9a60bc Facter::Manufacturer - sunos test + simplified regex -be411c0 Facter::Manufacturer - test for SunOS and FreeBSD -67f6604 [#4062] Implement operating system facts for MeeGo -a2bcacd [#2330] Uptime should not make redundant system calls -ce7bd9f Refactor rakefile to use spec.ops, separate rcov task -faaa169 Fix #4352 - Support for detecting KVM virtuals on FreeBSD -82286e4 Fix #4352 - Support for detecting virtuals (jails) on FreeBSD -b2c2114 Properly wrapped the windows ipaddress fact in a setcode block. -1bd2ca2 Fixed #3929 - Added user confine to AIX memory facts -8106bc3 Adding HP-UX support to Facter's IP facts -83b3ea6 Fixed #3393 - Updates to Facter for MS Windows -ffcae46 Fixed #3403 - Added fact to query vlans; added spec test -d4b8401 Merged Jos Backus patch to remove requirement for ftools altogether -73dcbb9 Fixed #2355 read hang on /proc/xen/capabilties on RHEL 4.7 -d109def Fix #1365 - load all facts via cli -6c87917 Fixed failing test introduced by previous commit -c5b8d3b Fixes #3740 - split dmi output on regex -25bf5c2 Fix virtual unit test on non-linux by stubbing kernel -9a00eae Fixed #2313 - Somewhat essential hardware facts not available on OpenBSD, patch included -e19024b Fixed #2938 - interfaces that don't match ^\w+[.:]?\d+ are ignored -97879f9 Added support for Slackware in operatingsystem and operatingsystemrelease -802e6c2 Fixed #3542 - Ruby 1.9: broken unittest, String#each no longer exists -2f016f3 Fixed #3541 - Ruby 1.9: broken unittest, unexpected invocation: Process.waitall() -84d3d9f Fixed #3445 - Facter does not handle solaris branded zones properly -b5a8de0 Fix for #3411 install.rb should not put "." first in the tmp_dirs -8ea33eb Fixed #3447 - OVS and OEL not matching in operatingsystemrelease -aeee83c Fixed #3410 - Warnings in rake spec -8bf8cb5 Fixes #3397 - is_virtual fact does not detect Linux-VServer -62b6773 Add kvm support to virtual fact -dca615c fixes #2573, #2085, #1291 - fixes domain and fqdn facts resolution -86447c8 Revert "use popen3 in Resolution.exec" -7750f03 Fix #2341 - stricter handling of dmidecode split -f4269d9 Fix #2746 - add architecture support for GNU/kFreeBSD -50cef83 Fix missing error case -356cf15 Remove whitespace in DMI facts (#3008, #3011) -feecd39 Only ignore IPs starting with 127. -68fc123 Added package signing task -33fb770 use popen3 in Resolution.exec to catch stderr -8109806 introduce a warn mechanism for debugging -b2c1ca5 Add docs to Mac OS X package creation script and clean out old docs in the preflight -5412eab Fixed : 2788 - ftools missing in Ruby 1.9 -5b95a12 Fixes #2704. Problem finding install.rb three levels up -9aef69e Removed all ChangeLog - -1.5.7 -===== -3a39dd8 Updated ChangeLog and task -07dca60 Added additional exclusion to rcov process -8398238 Added rcov support to spec task -7194454 Updated CI Rake task -2472048 Clarify licensing as GPLv2 (or any later version) - -1.5.7rc1 -======== -4bc05e9 Added new format ChangeLog -5bc8db3 Incremented version and updated CHANGELOG -eb3a8a7 Issue #2414 - add unit test -7623e25 Fix errors when alias IP's are defined -bfe8a2a Fix 2455 - improve error handling on fact load -49470cf Fix broken solaris zone tests on EC2 -9515a40 Issue #2548 netblock fact -33be9e0 Add Darwin netmask support on top of Jim's patch -9d846b4 Fix #2306 netmask and ipaddress on SunOS and BSDs -7d4a5f9 Updated Rakefile and moved Rake tasks to tasks/rake directory -5982deb Added default Rake task -0e0483a Fix bug where you'd get an 'undefined method' error if trying to access a fact's value when collection has not being yet initialized. -fe41fb8 Fix #2470 - duplicate entries in interfaces fact -be9e484 Update OS X minor version fact to cope with '10.x' values and provide test coverage switch %x{} call to Facter::Util::Resolution.exec for better testing -f3ad66f Update install.rb to cope with all OS X versions, not just 10.5 -c02d3b6 Issue #2292 Add tests for virtual facts -6c9fec5 Added path fact -51c6e3d Issue #2314 OpenBSD sysctl -95e5fea Fix broken ci build with explicit clearing before tests -efc30e7 Change spec output to enable broken build debugging -6d71410 Fixed CI spec task -82d97e2 Fix operatingsystemrelease on Red Hat based distros -bee55c4 Consolidate operatingsystemrelease for CentOS, Fedora, oel, ovs, and RedHat - -1.5.6 -===== -f4cb619 Updated CHANGELOG and bumped version for 1.5.6 -dcdd5ce Fixes #2307 - Minor fix for zone in virtual.rb -ba44f08 Removed --no-thread and --no-chain-reply-to from rake mail_patches task -806f49f Added facter branding to format patch command -96c015c Added spec.executables to Facter gemspec in Rakefile -d97a63e Sync rpm spec file with latest from Fedora/EPEL - -1.5.5 -===== -dad4569 Added path to Rakefile -365cb8e CHANGELOG updates -68e0b24 Fix #2278 Revert fix for 2120 -b533e78 Tighten operatingsystemrelease regex on CentOS < 5 -48aa135 Fix operatingsystemrelease for CentOS < 5 -253fef1 Added spec files to package list Fixed CI rake tasks -b37d683 Added install.rb to Rakefile package task -7995d05 Bumped release to 1.5.5rc2 - -1.5.5rc2 -======== -1de8891 Bumped release to 1.5.5rc2 -56760d3 Facter #2120 - Solaris support for Facter[virtual] -2fb91ca Tests for #2227 - multiple interfaces on Darwin -00b192a Added SELinux tests -aecac08 Fix #2155 - architecture facts on Gentoo -831d937 Refactor #2154 - Modified patch from Benedikt Bohm to simplify openvz and vserver detection -7f3d237 Cleaned up Rakefile and removed requirement for Reductive Labs build library -a6adf59 Facter ticket 2214 - Fix facts for OVS -e101faf Fixed #2131 - Facter doesn't populate lsbmajdistrelease on OEL (also OEL/OVS and other facts) -73e6656 Facter fix #2231 typo -2518312 Fix #2236 - don't use each_line on arrays -f94abfc Fixed #1327 - Added SELinux facts -8768371 Fixed #2119 - Added support for non-global Solaris 10 zones -23a5b3d Fixed #2215 - Added support for SUSE Linux Enterprise Desktop to operatingsystem and operatingsystemrelease -e93b1e6 Added support for ArchLinux to operatingsystem fact -8e4a689 Fixes #2169 Correctly recognises dom0 and domUs -636a91d Partial fix for #2191 - Facter compatibility for Ruby 1.9 -9df0583 Added COPYING in and CHANGELOG updates -516402c Fixing #1918 - facter --puppet always works -d89ea7a Fixing ifconfig warnings generated on OS X -7fa2576 Fixed #2132 - support for named interface aliases under linux -7a81945 correctly compare values - fixes #2021 -1288b26 Fixed #2080 - IPAddress resolutions should be reordered -a6d6ba5 Use resultion.exec util instead of which checks -89a3aa8 Fix to stdout in resolution.rb -add6d47 Fixed #2081 - Fixed interfaces fact for vlan subinterfaces -8def362 Fixed #2063 - added kernelmajversion fact -5d94f7f Fixed #2055 - SunoS Interface error -9376e5b Fixed #2044 - virtual fact thread fix -c754949 Fix for rake task for reductive-build library -75db918 Fixed lib install permissions -ba2e470 Fixed #2040 - Facter should provide a macosx_productversion_major fact -77fa46b Fix virtual fact if xen but /proc/virtual present -9722e1f Fixed #2003 - Added is_virtual fact -7a30a6a Fixed CHANGELOG -c6c30a4 Fixed #2035 - Missing brace for OSX preflight -b6f0f99 more consistent indentation and alignment. also removal of trailing whitespace -9bc174f Further fix #2032 - close IO -6b904a0 Added EC2 facts -86b01bf Fixed #2032 - file.open hanging on /proc/uptime on some platform - -1.5.4 -===== -91d8cb7 Updated to version 1.5.4 -a99d043 Fixed #1966 - Added physicalprocessorcount fact -94ea807 This commit refs #1555, #1898 and fixes #1761 -04389db Added support for Oracle VM Server to operatingsystem and operatingsystemrelease -552f150 Added support for Oracle Enterprise Linux to operatingsystem and operatingsystemrelease -a932a69 Added README.rst for Facter -e52f962 Added Reductive Labs build library -0726437 Updated README - -1.5.4rc1 -======== -f4bc74d Fixing #1927 - failing facts don't kill Facter -063e4dc Fixed #1850 - Facter updates for Ruby 1.9 -b85ab0a Fixed #1924 - Fixed lo / lo:0 local interface matching -4dcd012 Fixed generic uptime fact -d93ca69 Fixed Ubuntu operatingsystem identification -effb82f Cleaner fix for #1926 -ccafc00 Fixed #1926 - IPAddr to_s issue -d9eef19 Added timezone fact - -1.5.3 -===== -b86a1fb Updated to version 1.5.3 -a73e803 Fixing the usage of the macosx util module; I somehow missed renaming it here -23289bd Fixed uptime refactor issues on non-Linux platforms Signed-off-by: James Turnbull -a194c91 Adding mail_patches rake task -a82f476 Renaming Facter::Macosx to Facter::Util::Macosx -1f1fa9b Fixing #1838 - profiler failures don't throw exceptions -5f202c9 Fixed #1867 - Fixed OpenSuSE detection -0bcdb71 Fixing #1854 - Adding ArchLinux support -fab9d1c Added network fact -da52e30 Fixed #1870 - Format all subnet masks as human-readable -c2de35f Added uptime facts -02c2912 Refactor - rename ipmess to interfaces -db4face Fixed autotest on win32 -c149b49 Fix bug #1870 and add interface fact support for darwin systems -aa56886 Refactoring the IP support, and fixing #1846. -91e25b9 Fixing indentation everywhere -074eda9 Fixing autotest, now that vendor/ is gone -01754f6 Removing the vendor/ gems. -e6d987d Fixing #1761 - Solaris no longer uses /etc/release -a70184a Fixed #1791 - support for virtual fact on Solaris 10 -99833a1 Fixed #1793 - Added more Solaris 10 facts -85b2a55 minor fix to operatingsystemversion to correctly parse /etc/release on OpenSolaris 2008.11. -8247304 Fixed errors on unrecognised option in binary -0fe4611 Added ci namespace and Rake tasks -7ddea77 Fix for #1727 - id fact should not rely on whoami on Solaris Signed-off-by: Martin Englund -f9a346a Sync specfile with latest from Fedora -fd07cd2 Removed EPM task -43d0aea Fixed #1697 - Typo in ipaddress.rb causes timeout under Solaris 10 SPARC -4e707c6 Fixed #1650 - OS X package creation script should be more selective about cleaning out prior versions -8a38aa5 Added Ubuntu to a variety of confines -051c843 Removed ENV path setting from virtual.rb -6393e82 Fixed #1634 - Update virtual fact to differentiate OpenVZ hardware nodes and virtual environments -de39f6c Revamp domain resolution -4d7b44c Fixed #1619 - Applying patch by seanmil, adding support for SLES. -84b83c4 Fixed #1509 - Fixed version recognition for SLES. -20650ac Fixes #1582 - Fix MAC address reporting for Linux bonding slave interfaces -a86577c Fixing the GPL/LGPL incompatibility by choosing the oldest-mentioned license (GPL). -c1d937c Fixed #1575 - CentOS fix for Facter SPEC file -1d00253 Fixed #1547 - finally killed dots in IP address facts -9c9c79a Fixed #1567 - fixed createpackage.sh output -d4cf657 Fixed #1569 - createpackage.rb bug - -1.5.2 -===== -a80779b Updated to version 1.5.2 -0e49580 Updated to version 1.5.2 -6e0a1f3 Fixes #1558 - Adjusted virtual fact to allow non-root users to execute it -4998d3b Fixes #1562 - Removed facter from PREREQS -0fac704 Fixed #1558 - Updated virtual fact for xenu and xen0 -5c50bc3 Fixed #1555 - added operatingsystemrelease for Solaris -e503857 Fixed #1559 - update to dmidecode fact -518393e Fixed . dot escaping -0356b6e Updated to version 1.5.1 - -1.5.1 -===== -bff615c Updated to version 1.5.1 -c2eb5ba Updated to version 1.5.1 -bc35a3b Adding a rake task for creating an archive. -d24504e Added a Process.waitall thread when there's a timeout, to avoid zombies. -bd87aa0 Set the timeout for the host-based and resolv-based resolutions to 2. -e6aa39f Updating changelog for previous two commits -095eb15 Applied patch by josb to fix CentOS version detection. -422dd11 Facter fix #1422, no default timeout -ca93b81 Adding better SuSE detection for both operatingsystem and release. -b7be581 Add unit rspec tests for ticket 1425 -af81fb3 Extract ifconfig output to data directory -2546c53 Add sample test and strawman solution for IP parsing code -b33d8c6 Add module level tests for Facter::IPAddress -590a3d0 Fixes #1492 - added kernelversion fact -d8b708b Fix ticket 1425 on Solaris -b91ee5e Remove duplicated code paths -df8fc8c fix terrible error with overwriting permissions -91ca4ab Fixed #1490 - Added virtual fact -ff45c86 Feature #1487: Package creation scripts for Mac OS X -9b42182 Modified the operatingsystem fact for Debian so it looks in /etc/debian_version instead of /proc/version. -e1023de Feature #1478: Allow specification of --bindir --sbindir --sitelibdir --mandir --destdir in install.rb -845ae94 Feature #1475: CONFIG['bindir'] CONFIG['sbindir'] have undesirable defaults on OS X 10.5 -a12608e Fixes #1467 - macaddress not set on Ubuntu -d999d95 Don't try and run lsb_release on windows -1eb94d3 Bug #1434 Don't execute which on windows -bb235e3 Use rbconfig to detect host cpu -3f180b3 Get DNSDomain from WMI to set domain -dc7363e Set macaddress on windows platform -0df872b Get kernel version via wmi -3ea1905 Use ipconfig to determine ip address -5e09ea1 Use rbconfig to detect windows as no uname binary -ded53b0 Fixed Rakefile to include additional files including tests et al -1cf98d1 Adjusted version to be in line with previous standard -ff0e90b Adding (apparently now required) author info to the gem spec - -1.5 -=== -7042c46 Updated to version 1.5 -e98efd3 Updated to version 1.5 -d49d63c Updating the changelog for 1.5 -88fe243 Fixed formatting -8c91649 Fixed #1400 - OperatingSystemRelease should now work on CentOS -927b3a1 Adding a default case for the manufacturer information. -9b464de Further fixes #1378 - updated dmidecode for NetBSD -a44d6c3 Fixes #1378 - update manufacter.rb facts to support BSD -9581190 Partial fix for #1345 - BSD interfaces with aliases now select the first address by default -2ef2041 Retaining 'timeout' as the settor, but using 'limit' as the getter. -e22b408 Changed 'timeout' option to 'limit' -145cee2 Setting the timeout for the puppetversion fact to 1.5. -40a9c1d Fixing some warnings in various classes. -0303885 Fixes #1376 - Display memory facts for AIX -2ac29ac Added processorcount and type facts to AIX -0b0892d Fixes #1334 - Forced Facter to use LANG=C -def18b5 Fixes #1357 - change ps syntax for OSX and BSD -ce7b74c Rejustifying all of the whitespace in the facts, yay. -2ee5d29 Refactoring how recursive searches are detected. -d322df9 Refactored so each fact resolution can specify a separate timeout, but the default is still 0.5. -9a1882e Retrieve hardwaremodel for AIX from sys0 modelname. -b574c6a Refactered ipmess.rb and util/ip.rb to support separate *BSD logic for *BSD aliased interfaces. -d9bd388 Refactor of netmask fact - fixes ticket #66 -09bc48c Testing gitosis -f9961c7 Fixes for ticket #60 -a12d3d8 Removing old test/unit tests. -400bab9 Adding a timeout to fact retrieval, fixing #56. -d235f26 Reverting the version. -7e84cdb Updated CHANGELOG -a5a72bd Added LSB Major Dist Release fact fixing #41 -fc6d1c9 Added support for AIX fixing ticket #56 -17f916f Updated Red Hat spec file for new version and files -86e0708 Incremented version number to 1.5 -edbfc44 Adding a --puppet option to facter to load Puppet facts. -bb41db0 Switching to a search path registration system. -07a3d47 Moving the puppet-related loading tests to an integration test. -03258eb Retrieval of fact values now autoloads facts. -e02b0b3 Updated version. Moved most facts to seperate files. -9c91a6d Facter no longer loads all facts by default. -aaaf767 Moving the version and ruby facts to a separate file. -f1acbc0 Switching Facter to using the new loader. -bb92493 Fixing the last few occurrences of Facter::Resolution instead of Facter::Util::Resolution. -dcfc171 Fixing the test so it doesn't break other tests. -1ba2bed Moving all of the support classes to util/. -be0a803 Creating a 'loader' class to handle loading facts for the collection. -cc9e221 Adding the 'each' method back into Facter. -48b8744 Updating the executable to not use Facter.each. -5889e43 Fixing warnings and interfaces. -bfc4996 Moving Facter's container behaviour into a separate class. -e3c1fda Splitting the instance code into a Fact class. -121d291 Adding all of the tests for the Facter::Resolution class. -8971979 Reorganizing my new tests so they match the autotest discovery. -b8de4e4 Simplifying Confine a bit -c5492c2 Splitting the different classes in Facter up, and adding some tests. -4f39ec8 Adding autotest hooks -fef9b7d fixing whitespace -567549b Closes #1145 - fixed bad interface names by replacing : with _ -d449472 Updated CHANGELOG -bd3b316 removing .swp file -e11edfb Switching from test/unit to rspec, and fixing a couple of small test failures. -1a5ba71 Fixed Solaris detection of lo0 for ticket #46 -92b43e0 Added require util ip.rb file -0c4ac42 Fixed #46 - refactor ipmess.rb -a633aeb Added new files -c312df8 Further updates to split facts and move support functions -df4636a Split out facts from facter.rb and moved all support code to util -4bb9ed4 Added support for multiple interfaces, macaddress and netmask facts for Linux, *BSD, and Solaris -64f9fe9 Fixed conflict merge -2b06799 Revert "Fixed ticket #50 - added selinux facts" -ecc1f0c Added Ubuntu operatingsystem and operatingsystemrelease fact support -96cf3d6 Added Debian release version support -b3962ef Fixed ticket #50 - added selinux facts -d7d82fc Fixed ticket #48 - CentOS operatingsystemrelease fact now reporting correct value -2af364c Added Mandrake support for operatingsystem fact - closed ticket #47 -85fbf8f Added index to imess.rb fixing Ticket #43. -be7c30b Fixed ticket #44 - -1.3.8 -===== -74621b5 Updated to version 1.3.8 -7f1c840 Updated to version 1.3.8 -4d83f6f Updating version in changelog -00ab1f3 Removing the package hosts, so packages are no longer created at all -57c76dd Updated CHANGELOG -b28ce1b Added require for rdoc/ri/ri_paths to address Puppet #753 and Facter #40 -dce6245 Revert "Adjusted :kernel confine to make it more in line with others" -c5e6f60 Adjusted :kernel confine to make it more in line with others -a4698ce Updated CHANGELOG -8b08d5f Added support to return multiple interfaces and their IP addresses as facts. Existing ipaddress fact which returns IP address of first interface on node is still available. Currently Linux only. Closes #6 -6113375 Added macaddress fact support for FreeBSD and OpenBSD - closes #37 Added hardwareisa support for *BSD platforms - closed #38 Facter now detects the Mandriva distribution - closes #39 Facter now correctly detects ipaddress on NetBSD - closes #42 -8426aaf making the install script executable -f35ee22 Drastically speeding up the lsb data retrieval, and refactoring the dmidecode data so it is a bit cleaner and does not produce extraneous output or errors -20986d9 Set operatingsystemrelease to the major release on RHEL and Fedora -43b5640 Remove tabs; don't fail if dmidecode doesn't return expected information -68449a9 Adding manufacturer code, as requested by digant on the Puppet Trac site. -750a0c6 Add YAML output option to the help text. -8a67e32 Fixed problem with executing system_profiler and sw_vers on non Darwin hosts. -43933dd Fixed problem where facter referenced puppet plist utility library. -46d9bed Added a bunch of information from system_profiler -xml. In particular, sp_serial_number is interesting. Also added values from sw_vers, to get the commonly used Mac OS X version and build identifier. -86e3d8e Setting the ldapname so it is guaranteed to be a string -b582612 Applying patch from Valentin Vidic, fixing open filehandles -09261ac Updated to version 1.3.7 -94ca0c3 Updated to version 1.3.7 - -1.3.7 -===== -3e12345 Adding release tag REL_1_3_7 -a329b65 Using consistent naming internally; I previously had essentially random quoting and case, but it is now all lower-case symbols. It should behave the same externally. -4880c69 Applying patch from #36 by psychedelys -11cff7b Fixing Facter.flush -df57cec Fixing #33 -- we now only return the first mac address -31039dc Applying patch from Adam Jacob that makes FACTERLIB work -392d8f2 Applying patch from #35. -824f91c Fixing bug where an up interface not in active use was being selected as the canonical IP instead of using the IP attached to the interface assigned the default route. -38cd613 Sync with Fedora specfile -4cf0016 updating docs a bit -c1a02be Updated to version 1.3.6 -b333df2 Updated to version 1.3.6 - -1.3.6 -===== -ce5258b Adding release tag REL_1_3_6 -cc672ba disabling solaris package generation for facter -067dc2c updating changelog for 1.3.6 -b013e21 Applying patch from #29. -7b665cc Fixing ssh key facts so they only include the key, not the type. -0674780 Make specfile work for FC < 5 and RHEL < 5 -ea65bdd Reconciling with Fedora specfile -4dc1c37 Do not try and check the command if which is not available; fixes trac #30 -c7a9e19 Updated to version 1.3.5 -bae0b49 Updated to version 1.3.5 -9a73c72 Updated to version 1.3.5 - -1.3.5 -===== -5192d94 Adding release tag REL_1_3_5 -4339b46 Fixing #26 -- using Resolution.exec instead of executing directly, and also calling lsb_release for every fact, instead of just once at startup -82fd890 Updated to version 1.3.4 -95352bc Updated to version 1.3.4 -677c986 Updated to version 1.3.4 - -1.3.4 -===== -e882251 Adding release tag REL_1_3_4 -ca498a2 updating changelog for 1.3.4 -d75744b Adding patch from #21, adding lsb_release facts -fe0f2f2 Adding yaml support, as requested in #24 -4abbce9 applying patch from #18. -7407e0c Fixing facter so it does not fail when an unknown fact is asked for -e2185ce Sorting the facts when they are all output -c96cf6a Adding fqdn fact -fc9331a Fixing #20. I just made sure that the Domain fact cchecks the hostname first, so that if the hostname is an fqdn it will set the domain from that. -07a42e6 Applying patch from #22 -610fb5d Applying patch in #23. -3569253 Applying memfree patch from #17. -b9beaa8 updates -722e6f2 doc updates -e2337bd doc updates -2987d50 updates -044f19c adding docs -6f01dec adding docs -4c04592 Updated to version 1.3.3 -474d65d Updated to version 1.3.3 - -1.3.3 -===== -f3333f3 Adding release tag REL_1_3_3 -682b97a updating changelog for 1.3.3 -747d45a Adding the ability to retrieve facts from the environment. -86fdc87 Updated to version 1.3.2 -c4659bd Updated to version 1.3.2 - -1.3.2 -===== -3869edf Adding release tag REL_1_3_3 -ea96381 simple packaging updaets -c2aa508 Adding thread exclusivity to memory and cpu reading -ace180f Re-adding these files, since Matt has found a solution to the hanging problem. -ba2e189 removing processor.rb in case it has the same problems as the memory file -9f14df9 Deleting this file until the hanging problems are resolved -157f68e fixing license issues -a0a33e6 fixing spec file again -31caa08 Updated to version 1.3.1 -8ad0323 Updated to version 1.3.1 -5e34a1f Updated to version 1.3.1 - -1.3.1 -===== -60be696 Adding release tag REL_1_3_1 -73aeade adding a call to dnsdomainname before domainname -6ac796d Fixing #15. Just adding rescue blocks around the load statements. -81f451b updating for 1.3 -b543152 Updated for use with latest Fedora ruby packages -15f2f44 Updated to version 1.3 -15931ef Updated to version 1.3 -261d909 Updated to version 1.3 - -1.3 -=== -92c48b9 Adding release tag REL_1_3 -539d593 fixing installer so it does not install batch files on darwin -4c1d5e0 trying to fix facterbin rubylib setting -7f2504d fixing test so that it works even if rubylib is not set -75b1835 Adding tagging frameworks back into Facter, and adding the ability to specify tags to the to_hash method so that you only receive facts tagged with specific tags -4296f1f fixing the linux processor stuff so it only gets called on linux -558d05a changing the syntax of the fact confines -9908628 Adding some documentation to the binary -a15c8f5 Adding rubysitedir fact, as requested in #13. Also, switching the output when one fact is asked for, so it only produces the single value, with no => symbol. -ee7d3ca fixing test to ignore differences in memory -5ae066b Switching "tag" to "confine", because it is a more appropriate term. I will also add "tags", but they will be used for creating fact collections. -c7cfd08 Adding patch from #11, with slight modifications. -59cea90 Adding patch from #11, with slight modifications. -f3cc5e3 Adding the ability to specify tags as hashes or arrays, as requested in #112. -01d37d9 Getting rid of the autoload method entirely; facts are now only loaded at startup. -3a0181e fixing linux memory stuff -6932a95 accepting patch in #10, although with more abstraction, and creating a module for the memory functions -165a401 Accepting the patch in #9, with some modifications. -af062c6 adding solaris pkg stuff -8794e46 Updated to version 1.2.1 - -1.2.1 -===== -77344ea Adding release tag REL_1_2_1 -b208f47 fixing small bug that only occurs with gems -999929e Updated to version 1.2.0 - -1.2.0 -===== -afe3c30 Adding release tag REL_1_2_0 -99b61e7 Adding final autoloading work. -97f1a5e updating changelog for 1.2.0 -87bbd50 adding another test for the exe -f745454 Adding ruby, puppet, and facter version facts -6c01e04 Fixing install and tests so that there are no errors, hopefully. -22bd24b Added "architecture" fact, added the ability to autoload facts from separate files, and added the ability to retrieve fact values via a method for each fact. -fe782b9 Accepting the patch from #5 -6c37a20 Removing ruby as a prereq -c78d113 Converting rakefile to the new build system -fadc8c5 Minor changes for hte Fedora Extras review -e3e4a03 fixing rake file to build and copy rpms automatically -46996fa updating changelog for 1.1.4 -aab8687 Updated to version 1.1.4 - -1.1.4 -===== -571683b Adding Release tag REL_1_1_4 -0b7dce7 Fixing installer to put the facter executable in /usr/bin instead of / -3c71757 Updated to version 1.1.3 - -1.1.3 -===== -d494ac2 Adding Release tag REL_1_1_3 -3a230a0 adding 1.1.3 changelog -2e407d4 Identifying centos -cc4a943 updates -4579f8f Updated to version 1.1.2 - -1.1.2 -===== -9279ca8 Adding Release tag REL_1_1_2 -d36885f Adding ldapname capabilities -a4309b4 Automatically update version and release in the specfile for new releases -2d84edd Fix specfile in accordance with Fedora Extras guidelines -2c0999e RPM creation now works -62c050a Working on packaging -2c99812 Updated to version 1.1.1 - -1.1.1 -===== -6fef6af Adding Release tag REL_1_1_1 -35ed5f4 Fixing bug when a fact with no resolutions is asked for -a295c73 Fixing bug when a fact with no resolutions is asked for -5a0bd4a Updated to version 1.1.0 - -1.1.0 -===== -81657d1 Adding Release tag REL_1_1_0 -1ed4216 Redoing how tags work. -d9c86d5 updating everything to essentially disable docs generation -64a86db Adding Release tag - -1.0.2 -===== -8c91fb1 adding release tag -1dc02f9 adding extra "return nil" statements, and hopefully fixing the test for cygwin -b542ec5 Updated to version 1.0.2 -f1c8f10 adding changelog -7df3411 adding fixes Eric Sorenson found with cygwin -c646434 updates -fe90bf1 Updated to version 1.0.1 -3f0186d Modified version -58538d2 removing filehandle-based tests -8cb9662 updating INSTALL with patch from ian -7cec936 moving things to the trunk - From bdf668d0888dd0dbd726ec7ca62c858218141bb0 Mon Sep 17 00:00:00 2001 From: Matthaus Owens Date: Sun, 17 Mar 2013 11:12:53 -0700 Subject: [PATCH 1191/3753] (packaging) Make dmidecode arch specific on debian The dmidecode package on debian is only available on ia64, amd64 and i386, and not for other arches such as arm. This pull request makes dmidecode only required for those arches where it is available. This has the side effect of making facter on debian arch specific. --- ext/debian/control | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/debian/control b/ext/debian/control index a61fa844cc..a7cd6b2683 100644 --- a/ext/debian/control +++ b/ext/debian/control @@ -7,8 +7,8 @@ Standards-Version: 3.9.1 Homepage: http://www.puppetlabs.com Package: facter -Architecture: all -Depends: ${shlibs:Depends}, ${misc:Depends}, ruby | ruby-interpreter, libopenssl-ruby | libopenssl-ruby1.8 | libopenssl-ruby1.9.1, dmidecode, pciutils +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends}, ruby | ruby-interpreter, libopenssl-ruby | libopenssl-ruby1.8 | libopenssl-ruby1.9.1, dmidecode [i386 amd64 ia64], pciutils Description: Ruby module for collecting simple facts about a host operating system Some of the facts are preconfigured, such as the hostname and the operating system. Additional facts can be added through simple Ruby scripts. From a80fa947add687c2a4d4e1f9d3533faf90ec8d3b Mon Sep 17 00:00:00 2001 From: Matthaus Owens Date: Mon, 1 Apr 2013 14:35:11 -0700 Subject: [PATCH 1192/3753] (packaging) Add amd64 cows to the cows in build_defaults Now that dmidecode is an arch specific dependency of facter, it is no longer possible to build a noarch/all package using one cow per dist. This commit adds an amd64 cow to each of the dists so that both i386 and amd64 packages are built for facter. --- ext/build_defaults.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/build_defaults.yaml b/ext/build_defaults.yaml index 6657a7e526..1ea92d3dfc 100644 --- a/ext/build_defaults.yaml +++ b/ext/build_defaults.yaml @@ -2,7 +2,7 @@ packaging_url: 'git://github.com/puppetlabs/packaging.git --branch=master' packaging_repo: 'packaging' default_cow: 'base-squeeze-i386.cow' -cows: 'base-lucid-i386.cow base-oneiric-i386.cow base-precise-i386.cow base-quantal-i386.cow base-sid-i386.cow base-squeeze-i386.cow base-stable-i386.cow base-testing-i386.cow base-unstable-i386.cow base-wheezy-i386.cow' +cows: 'base-lucid-i386.cow base-lucid-amd64.cow base-oneiric-i386.cow base-oneiric-amd64.cow base-precise-i386.cow base-precise-amd64.cow base-quantal-i386.cow base-quantal-amd64.cow base-sid-i386.cow base-sid-amd64.cow base-squeeze-i386.cow base-squeeze-amd64.cow base-stable-i386.cow base-stable-amd64.cow base-testing-i386.cow base-testing-amd64.cow base-unstable-i386.cow base-unstable-amd64.cow base-wheezy-i386.cow base-wheezy-amd64.cow' pbuild_conf: '/etc/pbuilderrc' packager: 'puppetlabs' gpg_name: 'info@puppetlabs.com' From 08c9e7c96f2920c0beb48f687729a545fc3d673f Mon Sep 17 00:00:00 2001 From: Matthaus Owens Date: Mon, 1 Apr 2013 14:51:08 -0700 Subject: [PATCH 1193/3753] (packaging) Add dependency on virt-what for rpm and deb packages In commit cf43fc0092f0476d378ea05dd8e21fc170a51bdf (#8210), virt-what was added as a utility for better virtualization detection. Without virt-what installed, that better detection is not available, so this commit adds virt-what as a dependency for debian and rpm packages. virt-what is a lightweight package (~25k), that is available on all currently supported flavors of debian and rpm based systems. --- ext/debian/changelog.erb | 26 ++++---------------------- ext/debian/control | 2 +- ext/redhat/facter.spec.erb | 4 ++++ 3 files changed, 9 insertions(+), 23 deletions(-) diff --git a/ext/debian/changelog.erb b/ext/debian/changelog.erb index 753a215219..1ef5c6c034 100644 --- a/ext/debian/changelog.erb +++ b/ext/debian/changelog.erb @@ -1,32 +1,14 @@ -facter (<%= @debversion %>) hardy lucid maverick natty oneiric unstable lenny sid wheezy lucid squeeze precise; urgency=low +facter (<%= @debversion %>) hardy lucid oneiric unstable sid wheezy lucid squeeze precise quantal; urgency=low * Update to version <% @debversion %> -- Puppet Labs Release <%= Time.now.strftime("%a, %d %b %Y %H:%M:%S %z")%> -facter (2.0.0-0.1rc4puppetlabs1) hardy lucid maverick natty oneiric unstable lenny sid wheezy lucid squeeze precise; urgency=low +facter (1.7.0-0.1rc1puppetlabs1) hardy lucid oneiric unstable sid wheezy lucid squeeze precise; urgency=low - * Imported upstream 2.0.0rc4. + * Add dependency on virt-what to facter for better virutalization detection - -- Moses Mendoza Thu, 24 May 2012 17:09:25 +0000 - -facter (2.0.0-0.1rc3puppetlabs1) hardy lucid maverick natty oneiric unstable lenny sid wheezy lucid squeeze precise; urgency=low - - * Imported upstream 2.0.0rc3. - - -- Moses Mendoza Tue, 22 May 2012 11:48:25 +0000 - -facter (2.0.0-0.1rc2puppetlabs1) hardy lucid maverick natty oneiric unstable lenny sid wheezy lucid squeeze precise; urgency=low - - * Imported upstream 2.0.0rc2. - - -- Moses Mendoza Thu, 17 May 2012 13:15:25 +0000 - -facter (2.0.0-0.1rc1puppetlabs1) hardy lucid maverick natty oneiric unstable lenny sid wheezy lucid squeeze precise; urgency=low - - * Imported upstream 2.0.0rc1. Updated debian/control to include ruby1.9 options. - - -- Matthaus Litteken Tue, 15 May 2012 23:43:25 +0000 + -- Matthaus Owens Mon, 01 Apr 2013 13:11:30 +0000 facter (1.6.8-1puppetlabs1) hardy lucid maverick natty oneiric unstable lenny sid wheezy lucid squeeze precise; urgency=low diff --git a/ext/debian/control b/ext/debian/control index a7cd6b2683..c5f8a9d11f 100644 --- a/ext/debian/control +++ b/ext/debian/control @@ -8,7 +8,7 @@ Homepage: http://www.puppetlabs.com Package: facter Architecture: any -Depends: ${shlibs:Depends}, ${misc:Depends}, ruby | ruby-interpreter, libopenssl-ruby | libopenssl-ruby1.8 | libopenssl-ruby1.9.1, dmidecode [i386 amd64 ia64], pciutils +Depends: ${shlibs:Depends}, ${misc:Depends}, ruby | ruby-interpreter, libopenssl-ruby | libopenssl-ruby1.8 | libopenssl-ruby1.9.1, dmidecode [i386 amd64 ia64], virt-what, pciutils Description: Ruby module for collecting simple facts about a host operating system Some of the facts are preconfigured, such as the hostname and the operating system. Additional facts can be added through simple Ruby scripts. diff --git a/ext/redhat/facter.spec.erb b/ext/redhat/facter.spec.erb index 9d40fad15d..1a04b1d937 100644 --- a/ext/redhat/facter.spec.erb +++ b/ext/redhat/facter.spec.erb @@ -31,6 +31,7 @@ Requires: which Requires: dmidecode Requires: pciutils %endif +Requires: virt-what Requires: ruby(abi) >= 1.8 BuildRequires: ruby >= 1.8.5 @@ -72,6 +73,9 @@ rm -rf %{buildroot} * <%= Time.now.strftime("%a %b %d %Y") %> Puppet Labs Release - 1:<%= @rpmversion %>-<%= @rpmrelease %> - Build for <%= @version %> +* Mon Apr 01 2013 Matthaus Owens - 1:1.7.0-0.1rc1 +- Add dependency on virt-what to facter for better virutalization detection + * Wed Aug 08 2012 Moses Mendoza - 1.6.11-2 - Use correct ruby libdir for fedora 17 / ruby 1.9 From 5a0291c72b580c6a458dcf677bce5e55dd7031ab Mon Sep 17 00:00:00 2001 From: Matthaus Owens Date: Mon, 1 Apr 2013 15:09:28 -0700 Subject: [PATCH 1194/3753] (packaging) Update FACTERVERSION to 1.7.0-rc1 --- lib/facter/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/version.rb b/lib/facter/version.rb index 72180dbf67..e45162440e 100644 --- a/lib/facter/version.rb +++ b/lib/facter/version.rb @@ -1,6 +1,6 @@ module Facter if not defined? FACTERVERSION then - FACTERVERSION = '1.6.18' + FACTERVERSION = '1.7.0-rc1' end ## From 906efe7fd48abd42e25856c00e6a871a152cac1a Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Mon, 1 Apr 2013 16:09:39 -0700 Subject: [PATCH 1195/3753] Revert #406 This reverts commit 674689501fcefdfe8cfbe35e19391ebcbf6d357d, reversing changes made to 435664829e3a8ec98aed2c38bc3f625db38b84e3. This change set is being reverted because we're seeing the following issue when running Facter on Linux and Darwin: $ facter Could not retrieve network_gif0: undefined local variable or method `label' for Facter::Util::IP:Module Could not retrieve network_en1: undefined local variable or method `label' for Facter::Util::IP:Module Could not retrieve network_vboxnet0: undefined local variable or method `label' for Facter::Util::IP:Module Could not retrieve network_stf0: undefined local variable or method `label' for Facter::Util::IP:Module Could not retrieve network_fw0: undefined local variable or method `label' for Facter::Util::IP:Module Could not retrieve network_vmnet1: undefined local variable or method `label' for Facter::Util::IP:Module Could not retrieve network_lo0: undefined local variable or method `label' for Facter::Util::IP:Module Could not retrieve network_en0: undefined local variable or method `label' for Facter::Util::IP:Module Could not retrieve network_p2p0: undefined local variable or method `label' for Facter::Util::IP:Module Could not retrieve network_vmnet8: undefined local variable or method `label' for Facter::Util::IP:Module --- lib/facter/interfaces.rb | 53 +- lib/facter/network.rb | 13 +- lib/facter/util/ip.rb | 338 ++++++++--- lib/facter/util/ip/base.rb | 198 ------- lib/facter/util/ip/darwin.rb | 6 - lib/facter/util/ip/dragonfly.rb | 6 - lib/facter/util/ip/free_bsd.rb | 6 - lib/facter/util/ip/gnu_k_free_bsd.rb | 9 - lib/facter/util/ip/hpux.rb | 76 --- lib/facter/util/ip/linux.rb | 169 ------ lib/facter/util/ip/net_bsd.rb | 6 - lib/facter/util/ip/open_bsd.rb | 6 - lib/facter/util/ip/sun_os.rb | 24 - lib/facter/util/ip/windows.rb | 73 --- ...interfaces => 6.0-STABLE_FreeBSD_ifconfig} | 0 .../ip/darwin/ifconfig_with_single_interface | 6 - ...win_ifconfig_all_with_multiple_interfaces} | 0 ...le_interfaces => debian_kfreebsd_ifconfig} | 0 .../6.0-STABLE_ifconfig_with_single_interface | 10 - .../ifconfig_with_single_interface | 8 - ..._ifconfig_lan0 => hpux_1111_ifconfig_lan0} | 0 ..._ifconfig_lan1 => hpux_1111_ifconfig_lan1} | 0 ...11_ifconfig_lo0 => hpux_1111_ifconfig_lo0} | 0 .../{hpux/1111_lanscan => hpux_1111_lanscan} | 0 .../1111_netstat_in => hpux_1111_netstat_in} | 0 ..._lan0 => hpux_1131_asterisk_ifconfig_lan0} | 0 ..._lan1 => hpux_1131_asterisk_ifconfig_lan1} | 0 ...ig_lo0 => hpux_1131_asterisk_ifconfig_lo0} | 0 ...isk_lanscan => hpux_1131_asterisk_lanscan} | 0 ...tstat_in => hpux_1131_asterisk_netstat_in} | 0 ..._ifconfig_lan0 => hpux_1131_ifconfig_lan0} | 0 ..._ifconfig_lan1 => hpux_1131_ifconfig_lan1} | 0 ...31_ifconfig_lo0 => hpux_1131_ifconfig_lo0} | 0 .../{hpux/1131_lanscan => hpux_1131_lanscan} | 0 .../1131_netstat_in => hpux_1131_netstat_in} | 0 ...n1 => hpux_1131_nic_bonding_ifconfig_lan1} | 0 ...n4 => hpux_1131_nic_bonding_ifconfig_lan4} | 0 ... => hpux_1131_nic_bonding_ifconfig_lan4_1} | 0 ...lo0 => hpux_1131_nic_bonding_ifconfig_lo0} | 0 ..._lanscan => hpux_1131_nic_bonding_lanscan} | 0 ...at_in => hpux_1131_nic_bonding_netstat_in} | 0 ...d0 => linux_2_6_35_proc_net_bonding_bond0} | 0 ...e_eth0 => linux_get_single_interface_eth0} | 0 ...ace_ib0 => linux_get_single_interface_ib0} | 0 ...rface_lo => linux_get_single_interface_lo} | 0 ... linux_ifconfig_all_with_single_interface} | 0 ...ris_ifconfig_all_with_multiple_interfaces} | 0 ...face => solaris_ifconfig_single_interface} | 0 ...nterfaces => windows_netsh_all_interfaces} | 0 ...terface => windows_netsh_single_interface} | 0 ...rface6 => windows_netsh_single_interface6} | 0 spec/unit/blockdevices_spec.rb | 2 + spec/unit/hardwareisa_spec.rb | 2 + spec/unit/hardwaremodel_spec.rb | 2 + spec/unit/interfaces_spec.rb | 19 +- spec/unit/ldom_spec.rb | 2 + spec/unit/lsbmajdistrelease_spec.rb | 2 + spec/unit/manufacturer_spec.rb | 2 + spec/unit/uniqueid_spec.rb | 2 + spec/unit/util/directory_loader_spec.rb | 3 + spec/unit/util/ip/base_spec.rb | 147 ----- spec/unit/util/ip/darwin_spec.rb | 87 --- spec/unit/util/ip/dragonfly_spec.rb | 34 -- spec/unit/util/ip/free_bsd_spec.rb | 56 -- spec/unit/util/ip/gnu_k_free_bsd_spec.rb | 85 --- spec/unit/util/ip/hpux_spec.rb | 536 ------------------ spec/unit/util/ip/linux_spec.rb | 158 ------ spec/unit/util/ip/net_bsd_spec.rb | 34 -- spec/unit/util/ip/open_bsd_spec.rb | 34 -- spec/unit/util/ip/sun_os_spec.rb | 91 --- spec/unit/util/ip/windows_spec.rb | 94 --- spec/unit/util/ip_spec.rb | 454 ++++++++++++++- spec/unit/util/parser_spec.rb | 5 +- spec/unit/zfs_version_spec.rb | 2 + spec/unit/zpool_version_spec.rb | 2 + 75 files changed, 774 insertions(+), 2088 deletions(-) delete mode 100644 lib/facter/util/ip/base.rb delete mode 100644 lib/facter/util/ip/darwin.rb delete mode 100644 lib/facter/util/ip/dragonfly.rb delete mode 100644 lib/facter/util/ip/free_bsd.rb delete mode 100644 lib/facter/util/ip/gnu_k_free_bsd.rb delete mode 100644 lib/facter/util/ip/hpux.rb delete mode 100644 lib/facter/util/ip/linux.rb delete mode 100644 lib/facter/util/ip/net_bsd.rb delete mode 100644 lib/facter/util/ip/open_bsd.rb delete mode 100644 lib/facter/util/ip/sun_os.rb delete mode 100644 lib/facter/util/ip/windows.rb rename spec/fixtures/unit/util/ip/{free_bsd/6.0-STABLE_ifconfig_with_multiple_interfaces => 6.0-STABLE_FreeBSD_ifconfig} (100%) delete mode 100644 spec/fixtures/unit/util/ip/darwin/ifconfig_with_single_interface rename spec/fixtures/unit/util/ip/{darwin/ifconfig_all_with_multiple_interfaces => darwin_ifconfig_all_with_multiple_interfaces} (100%) rename spec/fixtures/unit/util/ip/{gnu_k_free_bsd/ifconfig_all_with_multiple_interfaces => debian_kfreebsd_ifconfig} (100%) delete mode 100644 spec/fixtures/unit/util/ip/free_bsd/6.0-STABLE_ifconfig_with_single_interface delete mode 100644 spec/fixtures/unit/util/ip/gnu_k_free_bsd/ifconfig_with_single_interface rename spec/fixtures/unit/util/ip/{hpux/1111_ifconfig_lan0 => hpux_1111_ifconfig_lan0} (100%) rename spec/fixtures/unit/util/ip/{hpux/1111_ifconfig_lan1 => hpux_1111_ifconfig_lan1} (100%) rename spec/fixtures/unit/util/ip/{hpux/1111_ifconfig_lo0 => hpux_1111_ifconfig_lo0} (100%) rename spec/fixtures/unit/util/ip/{hpux/1111_lanscan => hpux_1111_lanscan} (100%) rename spec/fixtures/unit/util/ip/{hpux/1111_netstat_in => hpux_1111_netstat_in} (100%) rename spec/fixtures/unit/util/ip/{hpux/1131_asterisk_ifconfig_lan0 => hpux_1131_asterisk_ifconfig_lan0} (100%) rename spec/fixtures/unit/util/ip/{hpux/1131_asterisk_ifconfig_lan1 => hpux_1131_asterisk_ifconfig_lan1} (100%) rename spec/fixtures/unit/util/ip/{hpux/1131_asterisk_ifconfig_lo0 => hpux_1131_asterisk_ifconfig_lo0} (100%) rename spec/fixtures/unit/util/ip/{hpux/1131_asterisk_lanscan => hpux_1131_asterisk_lanscan} (100%) rename spec/fixtures/unit/util/ip/{hpux/1131_asterisk_netstat_in => hpux_1131_asterisk_netstat_in} (100%) rename spec/fixtures/unit/util/ip/{hpux/1131_ifconfig_lan0 => hpux_1131_ifconfig_lan0} (100%) rename spec/fixtures/unit/util/ip/{hpux/1131_ifconfig_lan1 => hpux_1131_ifconfig_lan1} (100%) rename spec/fixtures/unit/util/ip/{hpux/1131_ifconfig_lo0 => hpux_1131_ifconfig_lo0} (100%) rename spec/fixtures/unit/util/ip/{hpux/1131_lanscan => hpux_1131_lanscan} (100%) rename spec/fixtures/unit/util/ip/{hpux/1131_netstat_in => hpux_1131_netstat_in} (100%) rename spec/fixtures/unit/util/ip/{hpux/1131_nic_bonding_ifconfig_lan1 => hpux_1131_nic_bonding_ifconfig_lan1} (100%) rename spec/fixtures/unit/util/ip/{hpux/1131_nic_bonding_ifconfig_lan4 => hpux_1131_nic_bonding_ifconfig_lan4} (100%) rename spec/fixtures/unit/util/ip/{hpux/1131_nic_bonding_ifconfig_lan4_1 => hpux_1131_nic_bonding_ifconfig_lan4_1} (100%) rename spec/fixtures/unit/util/ip/{hpux/1131_nic_bonding_ifconfig_lo0 => hpux_1131_nic_bonding_ifconfig_lo0} (100%) rename spec/fixtures/unit/util/ip/{hpux/1131_nic_bonding_lanscan => hpux_1131_nic_bonding_lanscan} (100%) rename spec/fixtures/unit/util/ip/{hpux/1131_nic_bonding_netstat_in => hpux_1131_nic_bonding_netstat_in} (100%) rename spec/fixtures/unit/util/ip/{linux/2_6_35_proc_net_bonding_bond0 => linux_2_6_35_proc_net_bonding_bond0} (100%) rename spec/fixtures/unit/util/ip/{linux/ifconfig_single_interface_eth0 => linux_get_single_interface_eth0} (100%) rename spec/fixtures/unit/util/ip/{linux/ifconfig_with_single_interface_ib0 => linux_get_single_interface_ib0} (100%) rename spec/fixtures/unit/util/ip/{linux/ifconfig_single_interface_lo => linux_get_single_interface_lo} (100%) rename spec/fixtures/unit/util/ip/{linux/ifconfig_all_with_single_interface => linux_ifconfig_all_with_single_interface} (100%) rename spec/fixtures/unit/util/ip/{sun_os/ifconfig_all_with_multiple_interfaces => solaris_ifconfig_all_with_multiple_interfaces} (100%) rename spec/fixtures/unit/util/ip/{sun_os/ifconfig_single_interface => solaris_ifconfig_single_interface} (100%) rename spec/fixtures/unit/util/ip/{windows/netsh_all_interfaces => windows_netsh_all_interfaces} (100%) rename spec/fixtures/unit/util/ip/{windows/netsh_with_single_interface => windows_netsh_single_interface} (100%) rename spec/fixtures/unit/util/ip/{windows/netsh_with_single_interface6 => windows_netsh_single_interface6} (100%) delete mode 100644 spec/unit/util/ip/base_spec.rb delete mode 100644 spec/unit/util/ip/darwin_spec.rb delete mode 100644 spec/unit/util/ip/dragonfly_spec.rb delete mode 100644 spec/unit/util/ip/free_bsd_spec.rb delete mode 100644 spec/unit/util/ip/gnu_k_free_bsd_spec.rb delete mode 100644 spec/unit/util/ip/hpux_spec.rb delete mode 100644 spec/unit/util/ip/linux_spec.rb delete mode 100644 spec/unit/util/ip/net_bsd_spec.rb delete mode 100644 spec/unit/util/ip/open_bsd_spec.rb delete mode 100644 spec/unit/util/ip/sun_os_spec.rb delete mode 100644 spec/unit/util/ip/windows_spec.rb diff --git a/lib/facter/interfaces.rb b/lib/facter/interfaces.rb index bf7a0be504..9b132e1c24 100644 --- a/lib/facter/interfaces.rb +++ b/lib/facter/interfaces.rb @@ -1,35 +1,42 @@ -# encoding: UTF-8 - -# Fact: interfaces -# -# Purpose: +# interfaces.rb # Try to get additional Facts about the machine's network interfaces # -# Caveats: -# Most of this only works on a fixed list of platforms; notably, Darwin is -# missing. +# Original concept Copyright (C) 2007 psychedelys +# Update and *BSD support (C) 2007 James Turnbull +# require 'facter/util/ip' -interfaces = Facter::Util::IP.interfaces - -if interfaces.any? - Facter.add(:interfaces) do - setcode do - alphafied_interfaces = interfaces.map do |interface| - Facter::Util::IP.alphafy(interface) - end +# Note that most of this only works on a fixed list of platforms; notably, Darwin +# is missing. - alphafied_interfaces.join(",") +Facter.add(:interfaces) do + confine :kernel => 'Linux' + has_weight 20 + setcode do + list = Dir.glob('/sys/class/net/*').map do |name| + Facter::Util::IP.alphafy(name.split('/').last) end + list.empty? ? nil : list.join(',') end +end + +Facter.add(:interfaces) do + confine :kernel => Facter::Util::IP.supported_platforms + setcode do + Facter::Util::IP.get_interfaces.collect { |iface| Facter::Util::IP.alphafy(iface) }.join(",") + end +end + +Facter::Util::IP.get_interfaces.each do |interface| - interfaces.each do |interface| - %w[ipaddress ipaddress6 macaddress netmask mtu].each do |label| - Facter.add("#{label}_#{Facter::Util::IP.alphafy(interface)}") do - setcode do - Facter::Util::IP.value_for_interface_and_label(interface, label) - end + # Make a fact for each detail of each interface. Yay. + # There's no point in confining these facts, since we wouldn't be able to create + # them if we weren't running on a supported platform. + %w{ipaddress ipaddress6 macaddress netmask mtu}.each do |label| + Facter.add(label + "_" + Facter::Util::IP.alphafy(interface)) do + setcode do + Facter::Util::IP.get_interface_value(interface, label) end end end diff --git a/lib/facter/network.rb b/lib/facter/network.rb index 506b02afad..390895a05b 100644 --- a/lib/facter/network.rb +++ b/lib/facter/network.rb @@ -1,17 +1,20 @@ # Fact: network # # Purpose: -# Get IP, network and netmask information for available network interfaces. +# Get IP, network and netmask information for available network +# interfacs. # # Resolution: -# Uses 'facter/util/ip' to enumerate interfaces and return their information. - +# Uses 'facter/util/ip' to enumerate interfaces and return their information. +# +# Caveats: +# require 'facter/util/ip' -Facter::Util::IP.interfaces.each do |interface| +Facter::Util::IP.get_interfaces.each do |interface| Facter.add("network_" + Facter::Util::IP.alphafy(interface)) do setcode do - Facter::Util::IP.network(interface) + Facter::Util::IP.get_network_value(interface) end end end diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index 059ab8a0a7..6bed1dd4d0 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -1,126 +1,296 @@ -# encoding: UTF-8 - -require 'facter/util/ip/base' -require 'facter/util/ip/darwin' -require 'facter/util/ip/sun_os' -require 'facter/util/ip/linux' -require 'facter/util/ip/net_bsd' -require 'facter/util/ip/open_bsd' -require 'facter/util/ip/free_bsd' -require 'facter/util/ip/dragonfly' -require 'facter/util/ip/windows' -require 'facter/util/ip/hpux' -require 'facter/util/ip/gnu_k_free_bsd' - # A base module for collecting IP-related # information from all kinds of platforms. module Facter::Util::IP + # A map of all the different regexes that work for + # a given platform or set of platforms. + REGEX_MAP = { + :linux => { + :ipaddress => /inet (?:addr:)?([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, + :ipaddress6 => /inet6 (?:addr: )?((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/, + :macaddress => /(?:ether|HWaddr)\s+((\w{1,2}:){5,}\w{1,2})/, + :netmask => /Mask:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, + :mtu => /MTU:(\d+)/ + }, + :bsd => { + :aliases => [:openbsd, :netbsd, :freebsd, :darwin, :"gnu/kfreebsd", :dragonfly], + :ipaddress => /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, + :ipaddress6 => /inet6 ((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/, + :macaddress => /(?:ether|lladdr)\s+(\w?\w:\w?\w:\w?\w:\w?\w:\w?\w:\w?\w)/, + :netmask => /netmask\s+0x(\w{8})/, + :mtu => /mtu\s+(\d+)/ + }, + :sunos => { + :ipaddress => /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, + :ipaddress6 => /inet6 ((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/, + :macaddress => /(?:ether|lladdr)\s+(\w?\w:\w?\w:\w?\w:\w?\w:\w?\w:\w?\w)/, + :netmask => /netmask\s+(\w{8})/, + :mtu => /mtu\s+(\d+)/ + }, + :"hp-ux" => { + :ipaddress => /\s+inet (\S+)\s.*/, + :macaddress => /(\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/, + :netmask => /.*\s+netmask (\S+)\s.*/ + }, + :windows => { + :ipaddress => /\s+IP Address:\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, + :ipaddress6 => /Address ((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/, + :netmask => /\s+Subnet Prefix:\s+\S+\s+\(mask ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\)/ + } + } + # Convert an interface name into purely alphanumeric characters. - # - # @param [String] interface e.g. 'eth0' - # - # @return [String] - # - # @api public def self.alphafy(interface) - interface.to_s.gsub(/[^a-z0-9_]/i, '_') + interface.gsub(/[^a-z0-9_]/i, '_') + end + + def self.convert_from_hex?(kernel) + kernels_to_convert = [:sunos, :openbsd, :netbsd, :freebsd, :darwin, :"hp-ux", :"gnu/kfreebsd", :dragonfly] + kernels_to_convert.include?(kernel) end - # Returns an array of supported platforms in string format. These array values - # are synonymous with the values returned from Facter.value(:kernel). - # - # @return [Array] contains strings corresponding to a kernel - # - # @api public def self.supported_platforms - kernel_classes.map(&:to_s) + REGEX_MAP.inject([]) do |result, tmp| + key, map = tmp + if map[:aliases] + result += map[:aliases] + else + result << key + end + result + end end - # A delegate method to the kernel's subclass ultimately obtaining the - # interfaces. - # - # @return [Array] - # - # @api public - def self.interfaces - kernel_class.interfaces + def self.get_interfaces + # Use sysfs on most Linux systems, which is the fastest option. + if File.exist?('/sys/class/net') + return Dir.glob('/sys/class/net/*').map do |name| + Facter::Util::IP.alphafy(name.split('/').last) + end + end + + return [] unless output = Facter::Util::IP.get_all_interface_output() + + # windows interface names contain spaces and are quoted and can appear multiple + # times as ipv4 and ipv6 + return output.scan(/\s* connected\s*(\S.*)/).flatten.uniq if Facter.value(:kernel) == 'windows' + + # Our regex appears to be stupid, in that it leaves colons sitting + # at the end of interfaces. So, we have to trim those trailing + # characters. I tried making the regex better but supporting all + # platforms with a single regex is probably a bit too much. + output.scan(/^\S+/).collect { |i| i.sub(/:$/, '') }.uniq end - # Uses the ifconfig command - # - # @param [Array] additional arguments - # - # @return [String] the output of the command + def self.get_all_interface_output + case Facter.value(:kernel) + when 'Linux', 'OpenBSD', 'NetBSD', 'FreeBSD', 'Darwin', 'GNU/kFreeBSD', 'DragonFly' + output = Facter::Util::IP.exec_ifconfig(["-a","2>/dev/null"]) + when 'SunOS' + output = Facter::Util::IP.exec_ifconfig(["-a"]) + when 'HP-UX' + # (#17487)[https://projects.puppetlabs.com/issues/17487] + # Handle NIC bonding where asterisks and virtual NICs are printed. + if output = hpux_netstat_in + output.gsub!(/\*/, "") # delete asterisks. + output.gsub!(/^[^\n]*none[^\n]*\n/, "") # delete lines with 'none' instead of IPs. + output.sub!(/^[^\n]*\n/, "") # delete the header line. + output + end + when 'windows' + output = %x|#{ENV['SYSTEMROOT']}/system32/netsh.exe interface ip show interface| + output += %x|#{ENV['SYSTEMROOT']}/system32/netsh.exe interface ipv6 show interface| + end + output + end + + + ## + # exec_ifconfig uses the ifconfig command # - # @api public + # @return [String] the output of `ifconfig #{arguments} 2>/dev/null` or nil def self.exec_ifconfig(additional_arguments=[]) Facter::Util::Resolution.exec("#{self.get_ifconfig} #{additional_arguments.join(' ')}") end - - # Looks up the ifconfig binary. + ## + # get_ifconfig looks up the ifconfig binary # # @return [String] path to the ifconfig binary - # - # @api public def self.get_ifconfig common_paths=["/bin/ifconfig","/sbin/ifconfig","/usr/sbin/ifconfig"] common_paths.select{|path| File.executable?(path)}.first end + ## + # hpux_netstat_in is a delegate method that allows us to stub netstat -in + # without stubbing exec. + def self.hpux_netstat_in + Facter::Util::Resolution.exec("/bin/netstat -in") + end - # A delegate method to `value_for_interface_and_label` which is implemented in - # Facter::Util::IP::Base and it's subclasses. - # - # @param interface [String] label [String] e.g ['eth0', 'MTU'] - # - # @return [String] or [NilClass] - # - # @api public - def self.value_for_interface_and_label(interface, label) - if kernel_supported? - kernel_class.value_for_interface_and_label(interface, label) + def self.get_infiniband_macaddress(interface) + if File.exists?("/sys/class/net/#{interface}/address") then + ib_mac_address = `cat /sys/class/net/#{interface}/address`.chomp + elsif File.exists?("/sbin/ip") then + ip_output = %x{/sbin/ip link show #{interface}} + ib_mac_address = ip_output.scan(%r{infiniband\s+((\w{1,2}:){5,}\w{1,2})}) + else + ib_mac_address = "FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF" + Facter.debug("ip.rb: nothing under /sys/class/net/#{interface}/address and /sbin/ip not available") end + ib_mac_address end - # A delegate method to obtain the network of an interface - # - # @param interface [String] e.g 'eth0' - # - # @return [String] or [NilClass] - # - # @api public - def self.network(interface) - if kernel_supported? - kernel_class.network(interface, label) + def self.ifconfig_interface(interface) + output = Facter::Util::IP.exec_ifconfig([interface,"2>/dev/null"]) + end + + def self.get_single_interface_output(interface) + output = "" + case Facter.value(:kernel) + when 'OpenBSD', 'NetBSD', 'FreeBSD', 'Darwin', 'GNU/kFreeBSD', 'DragonFly' + output = Facter::Util::IP.ifconfig_interface(interface) + when 'Linux' + ifconfig_output = Facter::Util::IP.ifconfig_interface(interface) + if interface =~ /^ib/ then + real_mac_address = get_infiniband_macaddress(interface) + output = ifconfig_output.sub(%r{(?:ether|HWaddr)\s+((\w{1,2}:){5,}\w{1,2})}, "HWaddr #{real_mac_address}") + else + output = ifconfig_output + end + when 'SunOS' + output = Facter::Util::IP.exec_ifconfig([interface]) + when 'HP-UX' + mac = "" + ifc = hpux_ifconfig_interface(interface) + hpux_lanscan.scan(/(\dx\S+).*UP\s+(\w+\d+)/).each {|i| mac = i[0] if i.include?(interface) } + mac = mac.sub(/0x(\S+)/,'\1').scan(/../).join(":") + output = ifc + "\n" + mac end + output end - private + def self.hpux_ifconfig_interface(interface) + Facter::Util::IP.exec_ifconfig([interface]) + end - # A delegate method for obtaining Facter::Util::IP::Base's subclasses. - # - # @return [Array] - # - # @api private - def self.kernel_classes - Facter::Util::IP::Base.subclasses + def self.hpux_lanscan + Facter::Util::Resolution.exec("/usr/sbin/lanscan") + end + + def self.get_output_for_interface_and_label(interface, label) + return get_single_interface_output(interface) unless Facter.value(:kernel) == 'windows' + + if label == 'ipaddress6' + output = %x|#{ENV['SYSTEMROOT']}/system32/netsh.exe interface ipv6 show address \"#{interface}\"| + else + output = %x|#{ENV['SYSTEMROOT']}/system32/netsh.exe interface ip show address \"#{interface}\"| + end + output + end + + def self.get_bonding_master(interface) + if Facter.value(:kernel) != 'Linux' + return nil + end + # We need ip instead of ifconfig because it will show us + # the bonding master device. + if not FileTest.executable?("/sbin/ip") + return nil + end + # A bonding interface can never be an alias interface. Alias + # interfaces do have a colon in their name and the ip link show + # command throws an error message when we pass it an alias + # interface. + if interface =~ /:/ + return nil + end + regex = /SLAVE[,>].* (bond[0-9]+)/ + ethbond = regex.match(%x{/sbin/ip link show #{interface}}) + if ethbond + device = ethbond[1] + else + device = nil + end + device end - # Obtains the cooresponding Facter::Util::IP::Base subclass for the current - # kernel. + ## + # get_interface_value obtains the value of a specific attribute of a specific + # interface. + # + # @param interface [String] the interface identifier, e.g. "eth0" or "bond0" # - # @return Subclass of [Facter::Util::IP::Base] + # @param label [String] the attribute of the interface to obtain a value for, + # e.g. "netmask" or "ipaddress" # # @api private - def self.kernel_class - kernel_classes.find { |klass| klass.to_s == Facter.value(:kernel) } + # + # @return [String] representing the requested value. An empty array is + # returned if the kernel is not supported by the REGEX_MAP constant. + def self.get_interface_value(interface, label) + tmp1 = [] + + kernel = Facter.value(:kernel).downcase.to_sym + + # If it's not directly in the map or aliased in the map, then we don't know how to deal with it. + unless map = REGEX_MAP[kernel] || REGEX_MAP.values.find { |tmp| tmp[:aliases] and tmp[:aliases].include?(kernel) } + return [] + end + + # Pull the correct regex out of the map. + regex = map[label.to_sym] + + # Linux changes the MAC address reported via ifconfig when an ethernet interface + # becomes a slave of a bonding device to the master MAC address. + # We have to dig a bit to get the original/real MAC address of the interface. + bonddev = get_bonding_master(interface) + if label == 'macaddress' and bonddev + bondinfo = read_proc_net_bonding("/proc/net/bonding/#{bonddev}") + re = /^Slave Interface: #{interface}\b.*?\bPermanent HW addr: (([0-9A-F]{2}:?)*)$/im + if match = re.match(bondinfo) + value = match[1].upcase + end + else + output_int = get_output_for_interface_and_label(interface, label) + + output_int.each_line do |s| + if s =~ regex + value = $1 + if label == 'netmask' && convert_from_hex?(kernel) + value = value.scan(/../).collect do |byte| byte.to_i(16) end.join('.') + end + tmp1.push(value) + end + end + + if tmp1 + value = tmp1.shift + end + end end - # Boolean to determine whether the current kernel is supported. + ## + # read_proc_net_bonding is a seam method for mocking purposes. # - # @return [Boolean] true or false + # @param path [String] representing the path to read, e.g. "/proc/net/bonding/bond0" # # @api private - def self.kernel_supported? - supported_platforms.include?(Facter.value(:kernel)) + # + # @return [String] modeling the raw file read + def self.read_proc_net_bonding(path) + File.read(path) if File.exists?(path) + end + private_class_method :read_proc_net_bonding + + def self.get_network_value(interface) + require 'ipaddr' + + ipaddress = get_interface_value(interface, "ipaddress") + netmask = get_interface_value(interface, "netmask") + + if ipaddress && netmask + ip = IPAddr.new(ipaddress, Socket::AF_INET) + subnet = IPAddr.new(netmask, Socket::AF_INET) + network = ip.mask(subnet.to_s).to_s + end end end diff --git a/lib/facter/util/ip/base.rb b/lib/facter/util/ip/base.rb deleted file mode 100644 index efdb10a237..0000000000 --- a/lib/facter/util/ip/base.rb +++ /dev/null @@ -1,198 +0,0 @@ -# encoding: UTF-8 - -require 'ipaddr' - -module Facter - module Util - module IP - end - end -end - -class Facter::Util::IP::Base - # A regex to match an IPv4 address from `ifconfig` output. This regex will - # work for most platforms. You can override this in your subclass if you need - # a different regex. - # - # @return [Regexp] - # - # @api public - IPADDRESS_REGEX = /inet.*?([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ - - - # A regex to match an IPv6 address from `ifconfig` output. This regex will - # work for most platforms. You can override this in your subclass if you need - # a different regex. - # - # @return [Regexp] - # - # @api public - IPADDRESS6_REGEX = / - inet6.*?((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4}) - /x - - # A regex to match a MAC address from `ifconfig` output. This regex will work - # for most platforms. You can override this in your subclass if you need a - # different regex. - # - # @return [Regexp] - # - # @api public - MACADDRESS_REGEX = /(?:ether|lladdr)\s+(\w?\w:\w?\w:\w?\w:\w?\w:\w?\w:\w?\w)/ - - # A regex to match the netmask from `ifconfig` output. This regex will work - # for most platforms. You can override this in your subclass if you need a - # different regex. - # - # @return [Regexp] - # - # @api public - NETMASK_REGEX = /netmask\s+0x(\w{8})/ - - # A regex to match the MTU from `ifconfig` output. This regex will work for - # most platforms. You can override this in your subclass if you need a - # different regex. - # - # @return [Regexp] - # - # @api public - MTU_REGEX = /mtu\s+(\d+)/ - - # Returns the name of the Class without nesting. Mostly used for finding - # the right class corresponding to the value of Facter.value(:kernel) - # - # @return [String] The string without nesting. - # - # @api public - def self.to_s - super.split('::').last - end - - # Used in conjunction with the `.inherited` hook, this method will store - # an Array of the Class' subclasses. - # - # @return [Array] The subclasses. - # - # @api public - def self.subclasses - @subclasses ||= [] - end - - # Most kernels will need to have their netmask converted from hex. If - # your kernel doesn't display the netmask in hex, you'll need to - # override this method in your subclass to return false. - # - # @return [Boolean] true - # - # @api public - def self.convert_netmask_from_hex? - true - end - - # Network bonding is creation of a single bonded interface by combining 2 or - # more Ethernet interfaces. I think this is mostly used in Linux so this base - # method will return nil, however you should override this in your subclass if - # need be. See the Facter::Util::IP::Linux.bonding_master method. - # - # @return [NilClass] - # - # @api public - def self.bonding_master(interface) - end - - # Returns an array of interfaces from `ifconfig`. e.g. ['eth0', 'eth1'] This - # will work on most platforms, but override in your subclass if need be. - # - # @return [Array] - # - # @api public - def self.interfaces - exec("#{ifconfig_path} -a 2> /dev/null").to_s.scan(/^\w+/).uniq - end - - # Get the value of an interface and label. For example, you may want to find - # the MTU for eth0. - # - # @param interface [String] label [String] and optional command [String] - # - # @return [String] or [NilClass] - # - # @api public - def self.value_for_interface_and_label(interface, label, cmd = nil) - if regex = regex_for(label) - cmd ||= "#{ifconfig_path} #{interface} 2> /dev/null" - - if match = regex.match(exec(cmd).to_s) - if label == 'netmask' && convert_netmask_from_hex? - match[1].scan(/../).map { |byte| byte.to_i(16) }.join('.') - else - match[1] - end - end - end - end - - # Returns the IP network for the given interface. - # - # @return [String] or [NilClass] - # - # @api public - def self.network(interface) - ipaddress = value_for_interface_and_label(interface, "ipaddress") - netmask = value_for_interface_and_label(interface, "netmask") - - if ipaddress && netmask - ip = IPAddr.new(ipaddress, Socket::AF_INET) - subnet = IPAddr.new(netmask, Socket::AF_INET) - - ip.mask(subnet.to_s).to_s - end - end - - private - - # This is a Ruby hook method that will help to maintain a list of - # subclasses. See the `.subclasses` method for more information. - # - # @return [Array] The subclasses. - # - # @api private - def self.inherited subclass - subclasses << subclass - end - - # This loops over `ifconfig` paths to find the first that is executable. - # - # @return [String] - # - # @api private - def self.ifconfig_path - %w[/bin/ifconfig /sbin/ifconfig /usr/sbin/ifconfig].find do |path| - File.executable?(path) - end - end - - # Delegation method to Facter::Util::Resolution.exec. - # - # @param command [String] the command to execute - # - # @return [String] or [Nil] - # - # @api private - def self.exec string - Facter::Util::Resolution.exec string - end - - # Grabs the corresponding regex constant. e.g. NETMASK_REGEX - # - # @param label [String] e.g. 'netmask' - # - # @return [Regexp] or [NilClass] - # - # @api private - def self.regex_for label - constant = "#{label.to_s.upcase}_REGEX" - - const_get(constant) if constants.find { |c| /^#{constant}$/.match(c) } - end -end diff --git a/lib/facter/util/ip/darwin.rb b/lib/facter/util/ip/darwin.rb deleted file mode 100644 index 83c95fc791..0000000000 --- a/lib/facter/util/ip/darwin.rb +++ /dev/null @@ -1,6 +0,0 @@ -# encoding: UTF-8 - -require 'facter/util/ip/base' - -class Facter::Util::IP::Darwin < Facter::Util::IP::Base -end diff --git a/lib/facter/util/ip/dragonfly.rb b/lib/facter/util/ip/dragonfly.rb deleted file mode 100644 index 0ccf87c359..0000000000 --- a/lib/facter/util/ip/dragonfly.rb +++ /dev/null @@ -1,6 +0,0 @@ -# encoding: UTF-8 - -require 'facter/util/ip/base' - -class Facter::Util::IP::Dragonfly < Facter::Util::IP::Base -end diff --git a/lib/facter/util/ip/free_bsd.rb b/lib/facter/util/ip/free_bsd.rb deleted file mode 100644 index 6db3942c39..0000000000 --- a/lib/facter/util/ip/free_bsd.rb +++ /dev/null @@ -1,6 +0,0 @@ -# encoding: UTF-8 - -require 'facter/util/ip/base' - -class Facter::Util::IP::FreeBSD < Facter::Util::IP::Base -end diff --git a/lib/facter/util/ip/gnu_k_free_bsd.rb b/lib/facter/util/ip/gnu_k_free_bsd.rb deleted file mode 100644 index 5988a1562c..0000000000 --- a/lib/facter/util/ip/gnu_k_free_bsd.rb +++ /dev/null @@ -1,9 +0,0 @@ -# encoding: UTF-8 - -require 'facter/util/ip/base' - -class Facter::Util::IP::GNUkFreeBSD < Facter::Util::IP::Base - def self.to_s - 'GNU/kFreeBSD' - end -end diff --git a/lib/facter/util/ip/hpux.rb b/lib/facter/util/ip/hpux.rb deleted file mode 100644 index d1c127c1b5..0000000000 --- a/lib/facter/util/ip/hpux.rb +++ /dev/null @@ -1,76 +0,0 @@ -# encoding: UTF-8 - -require 'facter/util/ip/base' - -class Facter::Util::IP::HPUX < Facter::Util::IP::Base - # A regex to match an IPv4 address from `ifconfig` output. - # - # @return [Regexp] - # - # @api public - IPADDRESS_REGEX = /\s+inet (\S+)\s.*/ - - # A regex to match a MAC address from `ifconfig` output. - # - # @return [Regexp] - # - # @api public - MACADDRESS_REGEX = /(\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/ - - # A regex to match the netmask from `ifconfig` output. - # - # @return [Regexp] - # - # @api public - NETMASK_REGEX = /.*\s+netmask (\S+)\s.*/ - - # The path to the `lanscan` executable. - # - # @return [Regexp] - # - # @api public - LANSCAN = '/usr/sbin/lanscan' - - def self.to_s - 'HP-UX' - end - - # Gets an array of interfaces from `netstat`. The cryptic text replacements in - # the method handles NIC bonding where asterisks and virtual NICs are printed. - # See (#17487)[https://projects.puppetlabs.com/issues/17487] for more info. - # - # @return [Array] - # - # @api public - def self.interfaces - exec("/bin/netstat -in"). - to_s. - gsub(/\*/, ""). - gsub(/^[^\n]*none[^\n]*\n/, ""). - sub(/^[^\n]*\n/, ""). - scan(/^\w+/) - end - - def self.value_for_interface_and_label(interface, label) - value = super(interface, label) - - if !value && label == 'macaddress' - if macaddress = lanscan.to_s[/\dx(\S+).*UP\s+#{interface}/, 1] - macaddress.scan(/../).join(':') - end - else - value - end - end - - private - - # Execute lanscan. - # - # @return [String] or [NilClass] - # - # @api private - def self.lanscan - exec(LANSCAN) if File.exist?(LANSCAN) - end -end diff --git a/lib/facter/util/ip/linux.rb b/lib/facter/util/ip/linux.rb deleted file mode 100644 index 61dbdf70a7..0000000000 --- a/lib/facter/util/ip/linux.rb +++ /dev/null @@ -1,169 +0,0 @@ -# encoding: UTF-8 - -require 'facter/util/ip/base' - -class Facter::Util::IP::Linux < Facter::Util::IP::Base - # A regex to match an IPv4 address from `ifconfig` output. - # - # @return [Regexp] - # - # @api public - IPADDRESS_REGEX = /inet\s(?:addr:)?([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ - - # A regex to match an IPv6 address from `ifconfig` output. - # - # @return [Regexp] - # - # @api public - IPADDRESS6_REGEX = /inet6\s(?:addr: )?((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/ - - - # A regex to match a MAC address from `ifconfig` output. - # - # @return [Regexp] - # - # @api public - MACADDRESS_REGEX = /(?:ether|HWaddr)\s+((\w{1,2}:){5,}\w{1,2})/ - - # A regex to match the netmask from `ifconfig` output. - # - # @return [Regexp] - # - # @api public - NETMASK_REGEX = /Mask:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ - - # A regex to match the MTU from `ifconfig` output. - # - # @return [Regexp] - # - # @api public - MTU_REGEX = /MTU:(\d+)/ - - # Linux doesn't display netmask in hex. - # - # @return [Boolean] false by default - # - # @api public - def self.convert_netmask_from_hex? - false - end - - # Network bonding is creation of a single bonded interface by combining 2 or - # more Ethernet interfaces. This method returns the bonding master. - # - # @param interface [String] the interface, e.g. 'eth0' - # - # @return [String] or [NilClass] - # - # @api public - def self.bonding_master(interface) - # We need `ip` instead of `ifconfig` because it shows us the bonding master. - return unless FileTest.executable?("/sbin/ip") - - # A bonding interface can never be an alias interface. Alias interfaces do - # have a colon in their name and the ip link show command throws an error - # message when we pass it an alias interface. - return if interface.match(/:/) - - regex = /SLAVE[,>].* (bond[0-9]+)/ - ethbond = regex.match(%x{/sbin/ip link show #{interface}}) - - ethbond[1] if ethbond - end - - # Returns an array of interfaces. e.g. ['eth0', 'eth1'] We will check sysfs - # first, since that is the fastest option, but fallback to `ifconfig` if - # neccessary. - # - # @return [Array] - # - # @api public - def self.interfaces - if File.exist?('/sys/class/net') - Dir.glob('/sys/class/net/*').map do |name| - name.split('/').last - end - else - super - end - end - - # Get the value of an interface and label. For example, you may want to find - # the MTU for eth0. If an infiniband interface is passed, it will try to - # obtain the real value. - # - # @param interface [String] label [String] and optional command [String] - # - # @return [String] or [NilClass] - # - # @api public - def self.value_for_interface_and_label(interface, label) - if label == 'macaddress' - bonddev = bonding_master(interface) - - if infiniband?(interface) - infiniband_macaddress(interface) || super - elsif bonddev - bonddev_macaddress(bonddev, interface) || super - else - super - end - else - super - end - end - - private - - # Boolean method to test whether an interface is the infiniband. - # - # @param interface [String] e.g. 'ib0' - # - # @return [Boolean] true or false - # - # @api private - def self.infiniband?(interface) - !!/^ib/.match(interface) - end - - # Attempts to obtain the real macaddress for an infiniband interface. - # - # @param interface [String] e.g. 'ib0' - # - # @return [String] or [NilClass] - # - # @api private - def self.infiniband_macaddress(interface) - sysfs = "/sys/class/net/#{interface}/address" - - if File.exists?(sysfs) - exec("cat #{sysfs}") - elsif File.exists?("/sbin/ip") - exec("/sbin/ip link show #{interface}"). - to_s. - scan(%r{infiniband\s+((\w{1,2}:){5,}\w{1,2})})[0] - end - end - - # Attempts to obtain the real macaddress for a bonded interface. - # - # @param bonddev [String] interface [String] e.g. 'bond0', 'eth0' - # - # @return [String] or [NilClass] - # - # @api private - def self.bonddev_macaddress(bonddev, interface) - path = "/proc/net/bonding/#{bonddev}" - - if File.exists?(path) - bondinfo = File.read(path) - regex = / - ^Slave\sInterface:\s - #{interface}\b.*?\bPermanent\sHW\saddr:\s(([0-9A-F]{2}:?)*)$ - /imx - match = regex.match(bondinfo) - - match[1].upcase if match - end - end -end diff --git a/lib/facter/util/ip/net_bsd.rb b/lib/facter/util/ip/net_bsd.rb deleted file mode 100644 index 3b97c9c158..0000000000 --- a/lib/facter/util/ip/net_bsd.rb +++ /dev/null @@ -1,6 +0,0 @@ -# encoding: UTF-8 - -require 'facter/util/ip/base' - -class Facter::Util::IP::NetBSD < Facter::Util::IP::Base -end diff --git a/lib/facter/util/ip/open_bsd.rb b/lib/facter/util/ip/open_bsd.rb deleted file mode 100644 index 3204a70194..0000000000 --- a/lib/facter/util/ip/open_bsd.rb +++ /dev/null @@ -1,6 +0,0 @@ -# encoding: UTF-8 - -require 'facter/util/ip/base' - -class Facter::Util::IP::OpenBSD < Facter::Util::IP::Base -end diff --git a/lib/facter/util/ip/sun_os.rb b/lib/facter/util/ip/sun_os.rb deleted file mode 100644 index 78efd9c7c5..0000000000 --- a/lib/facter/util/ip/sun_os.rb +++ /dev/null @@ -1,24 +0,0 @@ -# encoding: UTF-8 - -require 'facter/util/ip/base' - -class Facter::Util::IP::SunOS < Facter::Util::IP::Base - # A regex to match the netmask from `ifconfig` output. - # - # @return [Regexp] - # - # @api public - NETMASK_REGEX = /netmask\s(\w{8})/ - - # Get the value of an interface and label. For example, you may want to find - # the MTU for eth0. - # - # @param interface [String] label [String] e.g ['eth0', 'MTU'] - # - # @return [String] or [NilClass] - # - # @api public - def self.value_for_interface_and_label(interface, label) - super(interface, label, "#{ifconfig_path} #{interface}") - end -end diff --git a/lib/facter/util/ip/windows.rb b/lib/facter/util/ip/windows.rb deleted file mode 100644 index 45b65861bd..0000000000 --- a/lib/facter/util/ip/windows.rb +++ /dev/null @@ -1,73 +0,0 @@ -# encoding: UTF-8 - -require 'facter/util/ip/base' - -class Facter::Util::IP::Windows < Facter::Util::IP::Base - # A regex to match an IPv4 address from `ifconfig` output. - # - # @return [Regexp] - # - # @api public - IPADDRESS_REGEX = /\s+IP\sAddress:\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ - - # A regex to match an IPv6 address from `ifconfig` output. - # - # @return [Regexp] - # - # @api public - IPADDRESS6_REGEX = /Address\s((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/ - - # A regex to match the netmask from `ifconfig` output. - # - # @return [Regexp] - # - # @api public - NETMASK_REGEX = /\s+Subnet\sPrefix:\s+\S+\s+\(mask\s([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\)/ - - # The path to netsh.exe. - # - # @return [String] - # - # @api public - NETSH = "#{ENV['SYSTEMROOT']}/system32/netsh.exe" - - def self.to_s - 'windows' - end - - # Windows doesn't display netmask in hex. - # - # @return [Boolean] false by default - # - # @api public - def self.convert_netmask_from_hex? - false - end - - # Uses netsh.exe to obtain a list of interfaces. - # - # @return [Array] - # - # @api public - def self.interfaces - cmd = "#{NETSH} interface %s show interface" - output = exec("#{cmd % 'ip'} && #{cmd % 'ipv6'}").to_s - - output.scan(/\s* connected\s*(\S.*)/).flatten.uniq - end - - # Get the value of an interface and label. For example, you may want to find - # the MTU for eth0. Uses netsh.exe. - # - # @param interface [String] label [String] - # - # @return [String] or [NilClass] - # - # @api public - def self.value_for_interface_and_label(interface, label) - opt = label == 'ipaddress6' ? 'ipv6' : 'ip' - cmd = "#{NETSH} interface #{opt} show address \"#{interface}\"" - - super(interface, label, cmd) - end -end diff --git a/spec/fixtures/unit/util/ip/free_bsd/6.0-STABLE_ifconfig_with_multiple_interfaces b/spec/fixtures/unit/util/ip/6.0-STABLE_FreeBSD_ifconfig similarity index 100% rename from spec/fixtures/unit/util/ip/free_bsd/6.0-STABLE_ifconfig_with_multiple_interfaces rename to spec/fixtures/unit/util/ip/6.0-STABLE_FreeBSD_ifconfig diff --git a/spec/fixtures/unit/util/ip/darwin/ifconfig_with_single_interface b/spec/fixtures/unit/util/ip/darwin/ifconfig_with_single_interface deleted file mode 100644 index 6a4e10326c..0000000000 --- a/spec/fixtures/unit/util/ip/darwin/ifconfig_with_single_interface +++ /dev/null @@ -1,6 +0,0 @@ -en0: flags=8863 mtu 1500 - inet6 fe80::223:6cff:fe99:602b%en1 prefixlen 64 scopeid 0x5 - inet 192.168.0.10 netmask 0xffffff00 broadcast 192.168.0.255 - ether 00:23:6c:99:60:2b - media: autoselect status: active - supported media: autoselect diff --git a/spec/fixtures/unit/util/ip/darwin/ifconfig_all_with_multiple_interfaces b/spec/fixtures/unit/util/ip/darwin_ifconfig_all_with_multiple_interfaces similarity index 100% rename from spec/fixtures/unit/util/ip/darwin/ifconfig_all_with_multiple_interfaces rename to spec/fixtures/unit/util/ip/darwin_ifconfig_all_with_multiple_interfaces diff --git a/spec/fixtures/unit/util/ip/gnu_k_free_bsd/ifconfig_all_with_multiple_interfaces b/spec/fixtures/unit/util/ip/debian_kfreebsd_ifconfig similarity index 100% rename from spec/fixtures/unit/util/ip/gnu_k_free_bsd/ifconfig_all_with_multiple_interfaces rename to spec/fixtures/unit/util/ip/debian_kfreebsd_ifconfig diff --git a/spec/fixtures/unit/util/ip/free_bsd/6.0-STABLE_ifconfig_with_single_interface b/spec/fixtures/unit/util/ip/free_bsd/6.0-STABLE_ifconfig_with_single_interface deleted file mode 100644 index b1324be986..0000000000 --- a/spec/fixtures/unit/util/ip/free_bsd/6.0-STABLE_ifconfig_with_single_interface +++ /dev/null @@ -1,10 +0,0 @@ -fxp0: flags=8843 mtu 1500 - options=b - inet x.x.58.26 netmask 0xfffffff8 broadcast x.x.58.31 - inet x.x.58.27 netmask 0xffffffff broadcast x.x.58.27 - inet x.x.58.28 netmask 0xffffffff broadcast x.x.58.28 - inet x.x.58.29 netmask 0xffffffff broadcast x.x.58.29 - inet x.x.58.30 netmask 0xffffffff broadcast x.x.58.30 - ether 00:0e:0c:68:67:7c - media: Ethernet autoselect (100baseTX ) - status: active diff --git a/spec/fixtures/unit/util/ip/gnu_k_free_bsd/ifconfig_with_single_interface b/spec/fixtures/unit/util/ip/gnu_k_free_bsd/ifconfig_with_single_interface deleted file mode 100644 index b9ce459282..0000000000 --- a/spec/fixtures/unit/util/ip/gnu_k_free_bsd/ifconfig_with_single_interface +++ /dev/null @@ -1,8 +0,0 @@ -em1: flags=8843 metric 0 mtu 1500 - options=209b - ether 0:11:a:59:67:91 - inet6 fe80::211:aff:fe59:6791%em1 prefixlen 64 scopeid 0x2 - inet 192.168.10.10 netmask 0xffffff00 broadcast 192.168.10.255 - nd6 options=3 - media: Ethernet autoselect (100baseTX ) - status: active diff --git a/spec/fixtures/unit/util/ip/hpux/1111_ifconfig_lan0 b/spec/fixtures/unit/util/ip/hpux_1111_ifconfig_lan0 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux/1111_ifconfig_lan0 rename to spec/fixtures/unit/util/ip/hpux_1111_ifconfig_lan0 diff --git a/spec/fixtures/unit/util/ip/hpux/1111_ifconfig_lan1 b/spec/fixtures/unit/util/ip/hpux_1111_ifconfig_lan1 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux/1111_ifconfig_lan1 rename to spec/fixtures/unit/util/ip/hpux_1111_ifconfig_lan1 diff --git a/spec/fixtures/unit/util/ip/hpux/1111_ifconfig_lo0 b/spec/fixtures/unit/util/ip/hpux_1111_ifconfig_lo0 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux/1111_ifconfig_lo0 rename to spec/fixtures/unit/util/ip/hpux_1111_ifconfig_lo0 diff --git a/spec/fixtures/unit/util/ip/hpux/1111_lanscan b/spec/fixtures/unit/util/ip/hpux_1111_lanscan similarity index 100% rename from spec/fixtures/unit/util/ip/hpux/1111_lanscan rename to spec/fixtures/unit/util/ip/hpux_1111_lanscan diff --git a/spec/fixtures/unit/util/ip/hpux/1111_netstat_in b/spec/fixtures/unit/util/ip/hpux_1111_netstat_in similarity index 100% rename from spec/fixtures/unit/util/ip/hpux/1111_netstat_in rename to spec/fixtures/unit/util/ip/hpux_1111_netstat_in diff --git a/spec/fixtures/unit/util/ip/hpux/1131_asterisk_ifconfig_lan0 b/spec/fixtures/unit/util/ip/hpux_1131_asterisk_ifconfig_lan0 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux/1131_asterisk_ifconfig_lan0 rename to spec/fixtures/unit/util/ip/hpux_1131_asterisk_ifconfig_lan0 diff --git a/spec/fixtures/unit/util/ip/hpux/1131_asterisk_ifconfig_lan1 b/spec/fixtures/unit/util/ip/hpux_1131_asterisk_ifconfig_lan1 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux/1131_asterisk_ifconfig_lan1 rename to spec/fixtures/unit/util/ip/hpux_1131_asterisk_ifconfig_lan1 diff --git a/spec/fixtures/unit/util/ip/hpux/1131_asterisk_ifconfig_lo0 b/spec/fixtures/unit/util/ip/hpux_1131_asterisk_ifconfig_lo0 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux/1131_asterisk_ifconfig_lo0 rename to spec/fixtures/unit/util/ip/hpux_1131_asterisk_ifconfig_lo0 diff --git a/spec/fixtures/unit/util/ip/hpux/1131_asterisk_lanscan b/spec/fixtures/unit/util/ip/hpux_1131_asterisk_lanscan similarity index 100% rename from spec/fixtures/unit/util/ip/hpux/1131_asterisk_lanscan rename to spec/fixtures/unit/util/ip/hpux_1131_asterisk_lanscan diff --git a/spec/fixtures/unit/util/ip/hpux/1131_asterisk_netstat_in b/spec/fixtures/unit/util/ip/hpux_1131_asterisk_netstat_in similarity index 100% rename from spec/fixtures/unit/util/ip/hpux/1131_asterisk_netstat_in rename to spec/fixtures/unit/util/ip/hpux_1131_asterisk_netstat_in diff --git a/spec/fixtures/unit/util/ip/hpux/1131_ifconfig_lan0 b/spec/fixtures/unit/util/ip/hpux_1131_ifconfig_lan0 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux/1131_ifconfig_lan0 rename to spec/fixtures/unit/util/ip/hpux_1131_ifconfig_lan0 diff --git a/spec/fixtures/unit/util/ip/hpux/1131_ifconfig_lan1 b/spec/fixtures/unit/util/ip/hpux_1131_ifconfig_lan1 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux/1131_ifconfig_lan1 rename to spec/fixtures/unit/util/ip/hpux_1131_ifconfig_lan1 diff --git a/spec/fixtures/unit/util/ip/hpux/1131_ifconfig_lo0 b/spec/fixtures/unit/util/ip/hpux_1131_ifconfig_lo0 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux/1131_ifconfig_lo0 rename to spec/fixtures/unit/util/ip/hpux_1131_ifconfig_lo0 diff --git a/spec/fixtures/unit/util/ip/hpux/1131_lanscan b/spec/fixtures/unit/util/ip/hpux_1131_lanscan similarity index 100% rename from spec/fixtures/unit/util/ip/hpux/1131_lanscan rename to spec/fixtures/unit/util/ip/hpux_1131_lanscan diff --git a/spec/fixtures/unit/util/ip/hpux/1131_netstat_in b/spec/fixtures/unit/util/ip/hpux_1131_netstat_in similarity index 100% rename from spec/fixtures/unit/util/ip/hpux/1131_netstat_in rename to spec/fixtures/unit/util/ip/hpux_1131_netstat_in diff --git a/spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_ifconfig_lan1 b/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lan1 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_ifconfig_lan1 rename to spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lan1 diff --git a/spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_ifconfig_lan4 b/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lan4 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_ifconfig_lan4 rename to spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lan4 diff --git a/spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_ifconfig_lan4_1 b/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lan4_1 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_ifconfig_lan4_1 rename to spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lan4_1 diff --git a/spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_ifconfig_lo0 b/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lo0 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_ifconfig_lo0 rename to spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lo0 diff --git a/spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_lanscan b/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_lanscan similarity index 100% rename from spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_lanscan rename to spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_lanscan diff --git a/spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_netstat_in b/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_netstat_in similarity index 100% rename from spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_netstat_in rename to spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_netstat_in diff --git a/spec/fixtures/unit/util/ip/linux/2_6_35_proc_net_bonding_bond0 b/spec/fixtures/unit/util/ip/linux_2_6_35_proc_net_bonding_bond0 similarity index 100% rename from spec/fixtures/unit/util/ip/linux/2_6_35_proc_net_bonding_bond0 rename to spec/fixtures/unit/util/ip/linux_2_6_35_proc_net_bonding_bond0 diff --git a/spec/fixtures/unit/util/ip/linux/ifconfig_single_interface_eth0 b/spec/fixtures/unit/util/ip/linux_get_single_interface_eth0 similarity index 100% rename from spec/fixtures/unit/util/ip/linux/ifconfig_single_interface_eth0 rename to spec/fixtures/unit/util/ip/linux_get_single_interface_eth0 diff --git a/spec/fixtures/unit/util/ip/linux/ifconfig_with_single_interface_ib0 b/spec/fixtures/unit/util/ip/linux_get_single_interface_ib0 similarity index 100% rename from spec/fixtures/unit/util/ip/linux/ifconfig_with_single_interface_ib0 rename to spec/fixtures/unit/util/ip/linux_get_single_interface_ib0 diff --git a/spec/fixtures/unit/util/ip/linux/ifconfig_single_interface_lo b/spec/fixtures/unit/util/ip/linux_get_single_interface_lo similarity index 100% rename from spec/fixtures/unit/util/ip/linux/ifconfig_single_interface_lo rename to spec/fixtures/unit/util/ip/linux_get_single_interface_lo diff --git a/spec/fixtures/unit/util/ip/linux/ifconfig_all_with_single_interface b/spec/fixtures/unit/util/ip/linux_ifconfig_all_with_single_interface similarity index 100% rename from spec/fixtures/unit/util/ip/linux/ifconfig_all_with_single_interface rename to spec/fixtures/unit/util/ip/linux_ifconfig_all_with_single_interface diff --git a/spec/fixtures/unit/util/ip/sun_os/ifconfig_all_with_multiple_interfaces b/spec/fixtures/unit/util/ip/solaris_ifconfig_all_with_multiple_interfaces similarity index 100% rename from spec/fixtures/unit/util/ip/sun_os/ifconfig_all_with_multiple_interfaces rename to spec/fixtures/unit/util/ip/solaris_ifconfig_all_with_multiple_interfaces diff --git a/spec/fixtures/unit/util/ip/sun_os/ifconfig_single_interface b/spec/fixtures/unit/util/ip/solaris_ifconfig_single_interface similarity index 100% rename from spec/fixtures/unit/util/ip/sun_os/ifconfig_single_interface rename to spec/fixtures/unit/util/ip/solaris_ifconfig_single_interface diff --git a/spec/fixtures/unit/util/ip/windows/netsh_all_interfaces b/spec/fixtures/unit/util/ip/windows_netsh_all_interfaces similarity index 100% rename from spec/fixtures/unit/util/ip/windows/netsh_all_interfaces rename to spec/fixtures/unit/util/ip/windows_netsh_all_interfaces diff --git a/spec/fixtures/unit/util/ip/windows/netsh_with_single_interface b/spec/fixtures/unit/util/ip/windows_netsh_single_interface similarity index 100% rename from spec/fixtures/unit/util/ip/windows/netsh_with_single_interface rename to spec/fixtures/unit/util/ip/windows_netsh_single_interface diff --git a/spec/fixtures/unit/util/ip/windows/netsh_with_single_interface6 b/spec/fixtures/unit/util/ip/windows_netsh_single_interface6 similarity index 100% rename from spec/fixtures/unit/util/ip/windows/netsh_with_single_interface6 rename to spec/fixtures/unit/util/ip/windows_netsh_single_interface6 diff --git a/spec/unit/blockdevices_spec.rb b/spec/unit/blockdevices_spec.rb index 92c4e972c9..54620b8e07 100644 --- a/spec/unit/blockdevices_spec.rb +++ b/spec/unit/blockdevices_spec.rb @@ -1,3 +1,5 @@ +#!/usr/bin/env ruby + require 'spec_helper' require 'facter' require 'facter/util/nothing_loader' diff --git a/spec/unit/hardwareisa_spec.rb b/spec/unit/hardwareisa_spec.rb index d613026251..a709e8c742 100755 --- a/spec/unit/hardwareisa_spec.rb +++ b/spec/unit/hardwareisa_spec.rb @@ -1,3 +1,5 @@ +#!/usr/bin/env ruby + require 'spec_helper' require 'facter' diff --git a/spec/unit/hardwaremodel_spec.rb b/spec/unit/hardwaremodel_spec.rb index e9ef3d40de..a168c11ef5 100644 --- a/spec/unit/hardwaremodel_spec.rb +++ b/spec/unit/hardwaremodel_spec.rb @@ -1,3 +1,5 @@ +#!/usr/bin/env ruby + require 'spec_helper' require 'facter' diff --git a/spec/unit/interfaces_spec.rb b/spec/unit/interfaces_spec.rb index 4733b49db5..c495a499eb 100755 --- a/spec/unit/interfaces_spec.rb +++ b/spec/unit/interfaces_spec.rb @@ -4,12 +4,8 @@ require 'facter/util/ip' shared_examples_for "iface specific ifconfig output" do |platform, address, fixture| - before :each do - Facter::Util::IP::Base.stubs(:exec).returns(my_fixture_read(fixture)) - Facter::Util::IP.kernel_class.stubs(:exec).returns(my_fixture_read(fixture)) - end - it "correctly on #{platform} for eth0" do + Facter::Util::IP.stubs(:ifconfig_interface).returns(my_fixture_read(fixture)) subject.value.should == address end end @@ -18,14 +14,15 @@ it "should replace the ':' in an interface list with '_'" do # So we look supported Facter.fact(:kernel).stubs(:value).returns("SunOS") - Facter::Util::IP.stubs(:interfaces).returns %w{eth0:1 eth1:2} + + Facter::Util::IP.stubs(:get_interfaces).returns %w{eth0:1 eth1:2} Facter.fact(:interfaces).value.should == %{eth0_1,eth1_2} end it "should replace non-alphanumerics in an interface list with '_'" do Facter.fact(:kernel).stubs(:value).returns("windows") - Facter::Util::IP.stubs(:interfaces).returns ["Local Area Connection", "Loopback \"Pseudo-Interface\" (#1)"] + Facter::Util::IP.stubs(:get_interfaces).returns ["Local Area Connection", "Loopback \"Pseudo-Interface\" (#1)"] Facter.fact(:interfaces).value.should == %{Local_Area_Connection,Loopback__Pseudo_Interface____1_} end end @@ -44,12 +41,12 @@ context "on Linux" do before :each do - Facter::Util::IP.stubs(:interfaces).returns(["eth0"]) + Facter::Util::IP.stubs(:get_interfaces).returns(["eth0"]) Facter.fact(:kernel).stubs(:value).returns("Linux") end + example_behavior_for "iface specific ifconfig output", "Fedora 18", "10.10.220.1", "eth0_net_tools_2.0.txt" - example_behavior_for "iface specific ifconfig output", "Fedora 18", "10.10.220.1", 'eth0_net_tools_2.0.txt' - example_behavior_for "iface specific ifconfig output", "net_tools 1.60", "10.10.220.210", 'eth0_net_tools_1.60.txt' + example_behavior_for "iface specific ifconfig output", "net_tools 1.60", "10.10.220.210", "eth0_net_tools_1.60.txt" end end @@ -61,7 +58,7 @@ context "on Linux" do before :each do - Facter::Util::IP.stubs(:interfaces).returns(["eth0"]) + Facter::Util::IP.stubs(:get_interfaces).returns(["eth0"]) Facter.fact(:kernel).stubs(:value).returns("Linux") end example_behavior_for "iface specific ifconfig output", "Fedora 18", "dead::21f:bcff:fe0d:5fb1", "eth0_net_tools_2.0.txt" diff --git a/spec/unit/ldom_spec.rb b/spec/unit/ldom_spec.rb index 27ec932278..f97d4a8765 100644 --- a/spec/unit/ldom_spec.rb +++ b/spec/unit/ldom_spec.rb @@ -1,3 +1,5 @@ +#!/usr/bin/env ruby + require 'spec_helper' def ldom_fixtures(filename) diff --git a/spec/unit/lsbmajdistrelease_spec.rb b/spec/unit/lsbmajdistrelease_spec.rb index f6bb1dee8f..4397c02f8f 100755 --- a/spec/unit/lsbmajdistrelease_spec.rb +++ b/spec/unit/lsbmajdistrelease_spec.rb @@ -1,3 +1,5 @@ +#!/usr/bin/env ruby + require 'spec_helper' require 'facter' diff --git a/spec/unit/manufacturer_spec.rb b/spec/unit/manufacturer_spec.rb index f18bb9b0c3..cacf470f27 100644 --- a/spec/unit/manufacturer_spec.rb +++ b/spec/unit/manufacturer_spec.rb @@ -1,3 +1,5 @@ +#!/usr/bin/env ruby + require 'spec_helper' require 'facter' require 'facter/util/manufacturer' diff --git a/spec/unit/uniqueid_spec.rb b/spec/unit/uniqueid_spec.rb index 3ac9bbf49d..e9870d39fd 100755 --- a/spec/unit/uniqueid_spec.rb +++ b/spec/unit/uniqueid_spec.rb @@ -1,3 +1,5 @@ +#!/usr/bin/env ruby + require 'spec_helper' require 'facter' diff --git a/spec/unit/util/directory_loader_spec.rb b/spec/unit/util/directory_loader_spec.rb index de8f3ca5aa..79ad2b2cd5 100644 --- a/spec/unit/util/directory_loader_spec.rb +++ b/spec/unit/util/directory_loader_spec.rb @@ -1,4 +1,7 @@ +#!/usr/bin/env ruby + require 'spec_helper' + require 'facter/util/directory_loader' describe Facter::Util::DirectoryLoader do diff --git a/spec/unit/util/ip/base_spec.rb b/spec/unit/util/ip/base_spec.rb deleted file mode 100644 index 9484c22fa9..0000000000 --- a/spec/unit/util/ip/base_spec.rb +++ /dev/null @@ -1,147 +0,0 @@ -# encoding: UTF-8 - -require 'spec_helper' -require 'facter/util/ip/base' - -describe Facter::Util::IP::Base do - subject { described_class } - - describe ".subclasses" do - let(:subclasses) { described_class.subclasses } - - it { expect(subclasses).to be_an Array } - - it "should be memoized" do - expect(subclasses).to be subclasses - end - - it "should list subclasses" do - subclass = Class.new described_class - - expect(subclasses).to include subclass - end - end - - describe ".to_s" do - let(:to_s) { described_class.to_s } - - it "should return the name of the class without nesting" do - expect(to_s).to eq 'Base' - end - end - - describe ".convert_netmask_from_hex?" do - let :convert_netmask_from_hex? do - described_class.convert_netmask_from_hex? - end - - it { expect(convert_netmask_from_hex?).to be true } - end - - describe ".bonding_master" do - let(:bonding_master) { described_class.bonding_master('eth0') } - - it { expect(bonding_master).to be_nil } - end - - describe ".interfaces" do - let(:interfaces) { described_class.interfaces } - - it "uses `ifconfig` to list the interfaces" do - described_class.expects(:ifconfig_path).returns('/bin/ifconfig') - described_class.expects(:exec).with("/bin/ifconfig -a 2> /dev/null") - expect(interfaces).to be_an Array - end - end - - describe ".value_for_interface_and_label(interface, label)" do - let :value_for_interface_and_label do - described_class.value_for_interface_and_label interface, label - end - - let(:interface) { 'eth0' } - - describe "there is no regex for the label" do - let(:label) { 'foobar' } - - before :each do - described_class.expects(:regex_for).with(label).returns(nil) - end - - it { expect(value_for_interface_and_label).to be_nil } - end - - describe "there is a regex for the label" do - let(:regex) { // } - let(:ifconfig_path) { '/usr/bin/ifconfig' } - let(:exec_cmd) { "#{ifconfig_path} #{interface} 2> /dev/null" } - let(:ifconfig_output) { "" } - - before :each do - described_class.expects(:ifconfig_path).returns(ifconfig_path) - described_class.expects(:regex_for).with(label).returns(regex) - described_class.expects(:exec).with(exec_cmd).returns(ifconfig_output) - regex.expects(:match).with(ifconfig_output).returns(match_data) - end - - describe "there is a match with the exec output and the regex" do - describe "the label is not 'netmask'" do - let(:match_data) { ['inet 127.0.0.1', '127.0.0.1'] } - let(:label) { 'ipaddress' } - - it { expect(value_for_interface_and_label).to eq '127.0.0.1' } - end - - describe "the label is 'netmask'" do - let(:label) { 'netmask' } - - describe "the netmask needs to be converted from hex" do - let(:match_data) { ['netmask ffffff00', 'ffffff00'] } - - before :each do - described_class.expects(:convert_netmask_from_hex?).returns(true) - end - - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } - end - - describe "the netmask does not need to be converted from hex" do - let(:match_data) { ['netmask 255.255.255.0', '255.255.255.0'] } - - before :each do - described_class.expects(:convert_netmask_from_hex?).returns(false) - end - - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } - end - end - end - - describe "there is not a match with the exec output and the regex" do - let(:match_data) { nil } - let(:label) { 'ipaddress' } - - it { expect(value_for_interface_and_label).to be_nil } - end - end - - describe ".network(interface)" do - let(:network) { described_class.network(interface) } - let(:interface) { 'e1000g0' } - - before :each do - described_class. - expects(:value_for_interface_and_label). - with(interface, 'ipaddress'). - returns('172.16.15.138') - - described_class. - expects(:value_for_interface_and_label). - with(interface, 'netmask'). - returns('255.255.255.0') - end - - it { expect(network).to eq "172.16.15.0" } - end - end -end diff --git a/spec/unit/util/ip/darwin_spec.rb b/spec/unit/util/ip/darwin_spec.rb deleted file mode 100644 index b9407f29af..0000000000 --- a/spec/unit/util/ip/darwin_spec.rb +++ /dev/null @@ -1,87 +0,0 @@ -# encoding: UTF-8 - -require 'spec_helper' -require 'facter/util/ip/darwin' - -describe Facter::Util::IP::Darwin do - describe ".to_s" do - let :to_s do - described_class.to_s - end - - it "should be 'Darwin'" do - expect(to_s).to eq 'Darwin' - end - end - - describe ".convert_netmask_from_hex?" do - let :convert_netmask_from_hex? do - described_class.convert_netmask_from_hex? - end - - it "should be true" do - expect(convert_netmask_from_hex?).to be true - end - end - - describe ".bonding_master" do - let :bonding_master do - described_class.bonding_master('eth0') - end - - it { expect(bonding_master).to be_nil } - end - - describe ".interfaces" do - let :interfaces do - described_class.interfaces - end - - let :ifconfig_output do - my_fixture_read("ifconfig_all_with_multiple_interfaces") - end - - before :each do - described_class.expects(:ifconfig_path).returns('/usr/bin/ifconfig') - described_class.expects(:exec).with(anything).returns(ifconfig_output) - end - - it "should return an array with two interfaces" do - expect(interfaces).to eq ["lo0", "en0"] - end - end - - describe ".value_for_interface_and_label(interface, label)" do - let :value_for_interface_and_label do - described_class.value_for_interface_and_label interface, label - end - - let(:interface) { 'en0' } - let(:ifconfig_output) { my_fixture_read "ifconfig_with_single_interface" } - let(:ifconfig_path) { "/usr/bin/ifconfig" } - let(:exec_cmd) { "#{ifconfig_path} #{interface} 2> /dev/null" } - - before :each do - described_class.expects(:ifconfig_path).returns(ifconfig_path) - described_class.expects(:exec).with(exec_cmd).returns(ifconfig_output) - end - - describe "macaddress" do - let(:label) { 'macaddress' } - - it { expect(value_for_interface_and_label).to eq '00:23:6c:99:60:2b' } - end - - describe "netmask" do - let(:label) { 'netmask' } - - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } - end - - describe "mtu" do - let(:label) { 'mtu' } - - it { expect(value_for_interface_and_label).to eq '1500' } - end - end -end diff --git a/spec/unit/util/ip/dragonfly_spec.rb b/spec/unit/util/ip/dragonfly_spec.rb deleted file mode 100644 index f2c670a209..0000000000 --- a/spec/unit/util/ip/dragonfly_spec.rb +++ /dev/null @@ -1,34 +0,0 @@ -# encoding: UTF-8 - -require 'spec_helper' -require 'facter/util/ip/dragonfly' - -describe Facter::Util::IP::Dragonfly do - describe ".to_s" do - let :to_s do - described_class.to_s - end - - it "should be 'Dragonfly'" do - expect(to_s).to eq 'Dragonfly' - end - end - - describe ".convert_netmask_from_hex?" do - let :convert_netmask_from_hex? do - described_class.convert_netmask_from_hex? - end - - it "should be true" do - expect(convert_netmask_from_hex?).to be true - end - end - - describe ".bonding_master" do - let(:bonding_master) do - described_class.bonding_master('eth0') - end - - it { expect(bonding_master).to be_nil } - end -end diff --git a/spec/unit/util/ip/free_bsd_spec.rb b/spec/unit/util/ip/free_bsd_spec.rb deleted file mode 100644 index d69989e492..0000000000 --- a/spec/unit/util/ip/free_bsd_spec.rb +++ /dev/null @@ -1,56 +0,0 @@ -# encoding: UTF-8 - -require 'spec_helper' -require 'facter/util/ip/free_bsd' - -describe Facter::Util::IP::FreeBSD do - describe ".to_s" do - let :to_s do - described_class.to_s - end - - it "should be 'FreeBSD'" do - expect(to_s).to eq 'FreeBSD' - end - end - - describe ".convert_netmask_from_hex?" do - let :convert_netmask_from_hex? do - described_class.convert_netmask_from_hex? - end - - it "should be true" do - expect(convert_netmask_from_hex?).to be true - end - end - - describe ".bonding_master" do - let :bonding_master do - described_class.bonding_master('eth0') - end - - it { expect(bonding_master).to be_nil } - end - - describe ".value_for_interface_and_label(interface, label)" do - let :value_for_interface_and_label do - described_class.value_for_interface_and_label interface, label - end - - let(:interface) { 'fxp0' } - let(:ifconfig_output) { my_fixture_read "6.0-STABLE_ifconfig_with_single_interface" } - let(:ifconfig_path) { "/usr/bin/ifconfig" } - let(:exec_cmd) { "#{ifconfig_path} #{interface} 2> /dev/null" } - - before :each do - described_class.expects(:ifconfig_path).returns(ifconfig_path) - described_class.expects(:exec).with(exec_cmd).returns(ifconfig_output) - end - - describe "macaddress" do - let(:label) { 'macaddress' } - - it { expect(value_for_interface_and_label).to eq '00:0e:0c:68:67:7c' } - end - end -end diff --git a/spec/unit/util/ip/gnu_k_free_bsd_spec.rb b/spec/unit/util/ip/gnu_k_free_bsd_spec.rb deleted file mode 100644 index 591571d6ec..0000000000 --- a/spec/unit/util/ip/gnu_k_free_bsd_spec.rb +++ /dev/null @@ -1,85 +0,0 @@ -# encoding: UTF-8 - -require 'spec_helper' -require 'facter/util/ip/gnu_k_free_bsd' - -describe Facter::Util::IP::GNUkFreeBSD do - before :each do - Facter.fact(:kernel).stubs(:value).returns('GNU/kFreeBSD') - end - - describe ".to_s" do - let :to_s do - described_class.to_s - end - - it "should be 'GNU/kFreeBSD'" do - expect(to_s).to eq 'GNU/kFreeBSD' - end - end - - describe ".convert_netmask_from_hex?" do - let :convert_netmask_from_hex? do - described_class.convert_netmask_from_hex? - end - - it "should be true" do - expect(convert_netmask_from_hex?).to be true - end - end - - describe ".bonding_master" do - let :bonding_master do - described_class.bonding_master('eth0') - end - - it { expect(bonding_master).to be_nil } - end - - describe ".interfaces" do - let :interfaces do - described_class.interfaces - end - - let :ifconfig_output do - my_fixture_read("ifconfig_all_with_multiple_interfaces") - end - - before :each do - described_class.expects(:ifconfig_path).returns('/usr/bin/ifconfig') - described_class.expects(:exec).with(anything).returns(ifconfig_output) - end - - it "should return an array with six interfaces" do - expect(interfaces).to eq %w[em0 em1 bge0 bge1 lo0 vlan0] - end - end - - describe ".value_for_interface_and_label(interface, label)" do - let :value_for_interface_and_label do - described_class.value_for_interface_and_label interface, label - end - - let(:interface) { 'em1' } - let(:ifconfig_output) { my_fixture_read "ifconfig_with_single_interface" } - let(:ifconfig_path) { "/usr/bin/ifconfig" } - let(:exec_cmd) { "#{ifconfig_path} #{interface} 2> /dev/null" } - - before :each do - described_class.expects(:ifconfig_path).returns(ifconfig_path) - described_class.expects(:exec).with(exec_cmd).returns(ifconfig_output) - end - - describe "macaddress" do - let(:label) { 'macaddress' } - - it { expect(value_for_interface_and_label).to eq '0:11:a:59:67:91' } - end - - describe "netmask" do - let(:label) { 'netmask' } - - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } - end - end -end diff --git a/spec/unit/util/ip/hpux_spec.rb b/spec/unit/util/ip/hpux_spec.rb deleted file mode 100644 index ec3f21bfe1..0000000000 --- a/spec/unit/util/ip/hpux_spec.rb +++ /dev/null @@ -1,536 +0,0 @@ -# encoding: UTF-8 - -require 'spec_helper' -require 'facter/util/ip/hpux' - -describe Facter::Util::IP::HPUX do - before :each do - Facter.fact(:kernel).stubs(:value).returns("HP-UX") - end - - describe ".to_s" do - let :to_s do - described_class.to_s - end - - it "should be 'HP-UX'" do - expect(to_s).to eq 'HP-UX' - end - end - - describe ".convert_netmask_from_hex?" do - let :convert_netmask_from_hex? do - described_class.convert_netmask_from_hex? - end - - it "should be true" do - expect(convert_netmask_from_hex?).to be true - end - end - - describe ".bonding_master" do - let :bonding_master do - described_class.bonding_master('eth0') - end - - it { expect(bonding_master).to be_nil } - end - - describe ".interfaces" do - let :interfaces do - described_class.interfaces - end - - before :each do - described_class.stubs(:exec).with(anything).returns(netstat_output) - end - - describe "version 11.11" do - let :netstat_output do - my_fixture_read("1111_netstat_in") - end - - it "should return an array of interfaces" do - expect(interfaces).to eq %w[lan1 lan0 lo0] - end - end - - describe "version 11.31" do - let :netstat_output do - my_fixture_read("1131_netstat_in") - end - - it "should return an array of interfaces" do - expect(interfaces).to eq %w[lan1 lan0 lo0] - end - end - end - - describe ".value_for_interface_and_label(interface, label)" do - let :value_for_interface_and_label do - described_class.value_for_interface_and_label interface, label - end - - describe "version 11.11" do - let(:ifconfig_output) { my_fixture_read("1111_ifconfig_#{interface}") } - let(:lanscan_output) { my_fixture_read("1111_lanscan") } - - before :each do - described_class.stubs(:exec).returns(ifconfig_output) - end - - describe "lan1" do - let(:interface) { 'lan1' } - - describe "ipaddress" do - let(:label) { 'ipaddress' } - - it { expect(value_for_interface_and_label).to eq '10.1.1.6' } - end - - describe "mtu" do - let(:label) { 'mtu' } - - it { pending "(#17808) MTU has not been implemented for HP-UX" } - end - - describe "netmask" do - let(:label) { 'netmask' } - - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } - end - - describe "macaddress" do - let(:label) { 'macaddress' } - - before :each do - described_class.expects(:lanscan).returns(lanscan_output) - end - - it { expect(value_for_interface_and_label).to eq "00:10:79:7B:5C:DE" } - end - end - - describe "lan0" do - let(:interface) { 'lan0' } - - describe "ipaddress" do - let(:label) { 'ipaddress' } - - it { expect(value_for_interface_and_label).to eq "192.168.3.10" } - end - - describe "mtu" do - let(:label) { 'mtu' } - - it { pending "(#17808) MTU has not been implemented for HP-UX" } - end - - describe "netmask" do - let(:label) { 'netmask' } - - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } - end - - describe "macaddress" do - let(:label) { 'macaddress' } - - before :each do - described_class.expects(:lanscan).returns(lanscan_output) - end - - it { expect(value_for_interface_and_label).to eq "00:30:7F:0C:79:DC" } - end - end - - describe "lo0" do - let(:interface) { 'lo0' } - - describe "ipaddress" do - let(:label) { 'ipaddress' } - - it { expect(value_for_interface_and_label).to eq "127.0.0.1" } - end - - describe "mtu" do - let(:label) { 'mtu' } - - it { pending "(#17808) MTU has not been implemented for HP-UX" } - end - - describe "netmask" do - let(:label) { 'netmask' } - - it { expect(value_for_interface_and_label).to eq '255.0.0.0' } - end - - describe "macaddress" do - let(:label) { 'macaddress' } - - it { expect(value_for_interface_and_label).to be_nil } - end - end - end - - describe "version 11.31" do - describe "when interfaces are normal" do - let(:ifconfig_output) { my_fixture_read("1131_ifconfig_#{interface}") } - let(:lanscan_output) { my_fixture_read("1131_lanscan") } - - before :each do - described_class.stubs(:exec).returns(ifconfig_output) - end - - describe "lan1" do - let(:interface) { 'lan1' } - - describe "ipaddress" do - let(:label) { 'ipaddress' } - - it { expect(value_for_interface_and_label).to eq '10.1.54.36' } - end - - describe "netmask" do - let(:label) { 'netmask' } - - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } - end - - describe "macaddress" do - let(:label) { 'macaddress' } - let(:macaddress) { '00:17:FD:2D:2A:57' } - - before :each do - described_class.expects(:lanscan).returns(lanscan_output) - end - - it { expect(value_for_interface_and_label).to eq macaddress } - end - - describe "mtu" do - let(:label) { 'mtu' } - - it { pending "(#17808) MTU has not been implemented for HP-UX" } - end - end - - describe "lan0" do - let(:interface) { 'lan0' } - - describe "ipaddress" do - let(:label) { 'ipaddress' } - - it { expect(value_for_interface_and_label).to eq '192.168.30.152' } - end - - describe "netmask" do - let(:label) { 'netmask' } - - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } - end - - describe "macaddress" do - let(:label) { 'macaddress' } - let(:macaddress) { '00:12:31:7D:62:09' } - - before :each do - described_class.expects(:lanscan).returns(lanscan_output) - end - - it { expect(value_for_interface_and_label).to eq macaddress } - end - - describe "mtu" do - let(:label) { 'mtu' } - - it { pending "(#17808) MTU has not been implemented for HP-UX" } - end - end - - describe "lo0" do - let(:interface) { 'lo0' } - - describe "ipaddress" do - let(:label) { 'ipaddress' } - - it { expect(value_for_interface_and_label).to eq '127.0.0.1' } - end - - describe "netmask" do - let(:label) { 'netmask' } - - it { expect(value_for_interface_and_label).to eq '255.0.0.0' } - end - - describe "macaddress" do - let(:label) { 'macaddress' } - - before :each do - described_class.expects(:lanscan).returns(lanscan_output) - end - - it { expect(value_for_interface_and_label).to be_nil } - end - - describe "mtu" do - let(:label) { 'mtu' } - - it { pending "(#17808) MTU has not been implemented for HP-UX" } - end - end - end - - describe "when an interface has an asterisk appended" do - let(:lanscan_output) { my_fixture_read("1131_asterisk_lanscan") } - - let :ifconfig_output do - my_fixture_read "1131_asterisk_ifconfig_#{interface}" - end - - before :each do - described_class.stubs(:exec).returns(ifconfig_output) - end - - describe "lan1" do - let(:interface) { 'lan1' } - - describe "ipaddress" do - let(:label) { 'ipaddress' } - - it { expect(value_for_interface_and_label).to eq '10.10.0.5' } - end - - describe "netmask" do - let(:label) { 'netmask' } - - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } - end - - describe "macaddress" do - let(:label) { 'macaddress' } - let(:macaddress) { '00:10:79:7B:BE:46' } - - before :each do - described_class.expects(:lanscan).returns(lanscan_output) - end - - it { expect(value_for_interface_and_label).to eq macaddress } - end - - describe "mtu" do - let(:label) { 'mtu' } - - it { pending "(#17808) MTU has not been implemented for HP-UX" } - end - end - - describe "lan0" do - let(:interface) { 'lan0' } - - describe "ipaddress" do - let(:label) { 'ipaddress' } - - it { expect(value_for_interface_and_label).to eq '192.168.3.9' } - end - - describe "netmask" do - let(:label) { 'netmask' } - - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } - end - - describe "macaddress" do - let(:label) { 'macaddress' } - let(:macaddress) { '00:30:5D:06:26:B2' } - - before :each do - described_class.expects(:lanscan).returns(lanscan_output) - end - - it { expect(value_for_interface_and_label).to eq macaddress } - end - - describe "mtu" do - let(:label) { 'mtu' } - - it { pending "(#17808) MTU has not been implemented for HP-UX" } - end - end - - describe "lo0" do - let(:interface) { 'lo0' } - - describe "ipaddress" do - let(:label) { 'ipaddress' } - - it { expect(value_for_interface_and_label).to eq '127.0.0.1' } - end - - describe "netmask" do - let(:label) { 'netmask' } - - it { expect(value_for_interface_and_label).to eq '255.0.0.0' } - end - - describe "macaddress" do - let(:label) { 'macaddress' } - - before :each do - described_class.expects(:lanscan).returns(lanscan_output) - end - - it { expect(value_for_interface_and_label).to be_nil } - end - - describe "mtu" do - let(:label) { 'mtu' } - - it { pending "(#17808) MTU has not been implemented for HP-UX" } - end - end - end - - describe "when an interface is bonded and has one virtual interface" do - let(:lanscan_output) { my_fixture_read "1131_nic_bonding_lanscan" } - - let :ifconfig_output do - my_fixture_read "1131_nic_bonding_ifconfig_#{interface.sub(':', '_')}" - end - - before :each do - described_class.stubs(:exec).returns(ifconfig_output) - end - - describe "lan1" do - let(:interface) { 'lan1' } - - describe "ipaddress" do - let(:label) { 'ipaddress' } - - it { expect(value_for_interface_and_label).to eq '192.168.30.32' } - end - - describe "netmask" do - let(:label) { 'netmask' } - - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } - end - - describe "macaddress" do - let(:label) { 'macaddress' } - let(:macaddress) { '00:12:81:9E:48:DE' } - - before :each do - described_class.expects(:lanscan).returns(lanscan_output) - end - - it { expect(value_for_interface_and_label).to eq macaddress } - end - - describe "mtu" do - let(:label) { 'mtu' } - - it { pending "(#17808) MTU has not been implemented for HP-UX" } - end - end - - describe "lo0" do - let(:interface) { 'lo0' } - - describe "ipaddress" do - let(:label) { 'ipaddress' } - - it { expect(value_for_interface_and_label).to eq '127.0.0.1' } - end - - describe "netmask" do - let(:label) { 'netmask' } - - it { expect(value_for_interface_and_label).to eq '255.0.0.0' } - end - - describe "macaddress" do - let(:label) { 'macaddress' } - - before :each do - described_class.expects(:lanscan).returns(lanscan_output) - end - - it { expect(value_for_interface_and_label).to be_nil } - end - - describe "mtu" do - let(:label) { 'mtu' } - - it { pending "(#17808) MTU has not been implemented for HP-UX" } - end - end - - describe "lan4" do - let(:interface) { 'lan4' } - - describe "ipaddress" do - let(:label) { 'ipaddress' } - - it { expect(value_for_interface_and_label).to eq '192.168.32.75' } - end - - describe "netmask" do - let(:label) { 'netmask' } - - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } - end - - describe "macaddress" do - let(:label) { 'macaddress' } - let(:macaddress) { '00:12:81:9E:4A:7E' } - - before :each do - described_class.expects(:lanscan).returns(lanscan_output) - end - - it { expect(value_for_interface_and_label).to eq macaddress } - end - - describe "mtu" do - let(:label) { 'mtu' } - - it { pending "(#17808) MTU has not been implemented for HP-UX" } - end - end - - describe "lan4:1" do - let(:interface) { 'lan4:1' } - - describe "ipaddress" do - let(:label) { 'ipaddress' } - - it { expect(value_for_interface_and_label).to eq '192.168.1.197' } - end - - describe "netmask" do - let(:label) { 'netmask' } - - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } - end - - describe "macaddress" do - let(:label) { 'macaddress' } - - before :each do - described_class.expects(:lanscan).returns(lanscan_output) - end - - it { expect(value_for_interface_and_label).to be_nil } - end - - describe "mtu" do - let(:label) { 'mtu' } - - it { pending "(#17808) MTU has not been implemented for HP-UX" } - end - end - end - end - end -end diff --git a/spec/unit/util/ip/linux_spec.rb b/spec/unit/util/ip/linux_spec.rb deleted file mode 100644 index 85383115e7..0000000000 --- a/spec/unit/util/ip/linux_spec.rb +++ /dev/null @@ -1,158 +0,0 @@ -# encoding: UTF-8 - -require 'spec_helper' -require 'facter/util/ip/linux' - -describe Facter::Util::IP::Linux do - before :each do - Facter.fact(:kernel).stubs(:value).returns("Linux") - end - - describe ".to_s" do - let :to_s do - described_class.to_s - end - - it "should be 'Linux'" do - expect(to_s).to eq 'Linux' - end - end - - describe ".convert_netmask_from_hex?" do - let :convert_netmask_from_hex? do - described_class.convert_netmask_from_hex? - end - - it "should be true" do - expect(convert_netmask_from_hex?).to be false - end - end - - describe ".bonding_master(interface)" do - let :bonding_master do - described_class.bonding_master(interface) - end - - describe "on interface aliases" do - let :interface do - "eth0:1" - end - - it { expect(bonding_master).to be_nil } - end - end - - describe ".interfaces" do - let :interfaces do - described_class.interfaces - end - - describe "without sysfs" do - let :ifconfig_output do - my_fixture_read("ifconfig_all_with_single_interface") - end - - before :each do - File.expects(:exist?).with('/sys/class/net').returns(false) - described_class.expects(:ifconfig_path).returns('/usr/bin/ifconfig') - described_class.expects(:exec).with(anything).returns(ifconfig_output) - end - - it "should return an array with a single interface and the loopback" do - expect(interfaces).to eq ["eth0", "lo"] - end - end - - describe "with sysfs" do - let :sysfs do - %w[/sys/class/net/eth0 /sys/class/net/lo] - end - - before :each do - File.expects(:exist?).with('/sys/class/net').returns(true) - Dir.expects(:glob).with('/sys/class/net/*').returns(sysfs) - end - - it "should return an array with a single interface and the loopback" do - expect(interfaces).to eq ["eth0", "lo"] - end - end - end - - describe "value_for_interface_and_label(interface, label)" do - let :value_for_interface_and_label do - described_class.value_for_interface_and_label interface, label - end - - describe "infiniband interface" do - let(:interface) { 'ib0' } - - let :ifconfig_output do - my_fixture_read "ifconfig_with_single_interface_ib0" - end - - describe "macaddress" do - let(:label) { 'macaddress' } - - before :each do - described_class.expects(:infiniband_macaddress).returns('bar') - end - - it { expect(value_for_interface_and_label).to eq 'bar' } - end - end - - describe "normal interface" do - let(:ifconfig_path) { '/usr/bin/ifconfig' } - let(:exec_cmd) { "#{ifconfig_path} #{interface} 2> /dev/null" } - - let(:ifconfig_output) do - my_fixture_read "ifconfig_single_interface_#{interface}" - end - - before :each do - described_class.expects(:ifconfig_path).returns(ifconfig_path) - described_class.expects(:exec).with(exec_cmd).returns(ifconfig_output) - end - - describe "eth0" do - let(:interface) { 'eth0' } - - describe "mtu" do - let(:label) { 'mtu' } - - it { expect(value_for_interface_and_label).to eq '1500' } - end - end - - describe "lo" do - let(:interface) { 'lo' } - - describe "mtu" do - let(:label) { 'mtu' } - - it { expect(value_for_interface_and_label).to eq '16436' } - end - end - end - - describe "bonded interface" do - let(:interface) { 'eth0' } - let(:bond) { 'bond0' } - let(:proc_net_path) { '/proc/net/bonding/bond0' } - - before :each do - proc_net = my_fixture_read "2_6_35_proc_net_bonding_#{bond}" - described_class.expects(:bonding_master).with(interface).returns(bond) - File.expects(:exists?).with(proc_net_path).returns(true) - File.expects(:read).with(proc_net_path).returns(proc_net) - end - - describe "macaddress" do - let(:label) { 'macaddress' } - - it { expect(value_for_interface_and_label).to eq "00:11:22:33:44:55" } - end - end - end -end diff --git a/spec/unit/util/ip/net_bsd_spec.rb b/spec/unit/util/ip/net_bsd_spec.rb deleted file mode 100644 index 489e82b2b4..0000000000 --- a/spec/unit/util/ip/net_bsd_spec.rb +++ /dev/null @@ -1,34 +0,0 @@ -# encoding: UTF-8 - -require 'spec_helper' -require 'facter/util/ip/net_bsd' - -describe Facter::Util::IP::NetBSD do - describe ".to_s" do - let :to_s do - described_class.to_s - end - - it "should be 'NetBSD'" do - expect(to_s).to eq 'NetBSD' - end - end - - describe ".convert_netmask_from_hex?" do - let :convert_netmask_from_hex? do - described_class.convert_netmask_from_hex? - end - - it "should be true" do - expect(convert_netmask_from_hex?).to be true - end - end - - describe ".bonding_master" do - let :bonding_master do - described_class.bonding_master('eth0') - end - - it { expect(bonding_master).to be_nil } - end -end diff --git a/spec/unit/util/ip/open_bsd_spec.rb b/spec/unit/util/ip/open_bsd_spec.rb deleted file mode 100644 index 0a9508dc60..0000000000 --- a/spec/unit/util/ip/open_bsd_spec.rb +++ /dev/null @@ -1,34 +0,0 @@ -# encoding: UTF-8 - -require 'spec_helper' -require 'facter/util/ip/open_bsd' - -describe Facter::Util::IP::OpenBSD do - describe ".to_s" do - let :to_s do - described_class.to_s - end - - it "should be 'OpenBSD'" do - expect(to_s).to eq 'OpenBSD' - end - end - - describe ".convert_netmask_from_hex?" do - let :convert_netmask_from_hex? do - described_class.convert_netmask_from_hex? - end - - it "should be true" do - expect(convert_netmask_from_hex?).to be true - end - end - - describe ".bonding_master" do - let :bonding_master do - described_class.bonding_master('eth0') - end - - it { expect(bonding_master).to be_nil } - end -end diff --git a/spec/unit/util/ip/sun_os_spec.rb b/spec/unit/util/ip/sun_os_spec.rb deleted file mode 100644 index 23e366612b..0000000000 --- a/spec/unit/util/ip/sun_os_spec.rb +++ /dev/null @@ -1,91 +0,0 @@ -# encoding: UTF-8 - -require 'spec_helper' -require 'facter/util/ip/sun_os' - -describe Facter::Util::IP::SunOS do - before :each do - Facter.fact(:kernel).stubs(:value).with('SunOS') - end - - describe ".to_s" do - let :to_s do - described_class.to_s - end - - it "should be 'SunOS'" do - expect(to_s).to eq 'SunOS' - end - end - - describe ".convert_netmask_from_hex?" do - let :convert_netmask_from_hex? do - described_class.convert_netmask_from_hex? - end - - it "should be true" do - expect(convert_netmask_from_hex?).to be true - end - end - - describe ".bonding_master" do - let :bonding_master do - described_class.bonding_master('eth0') - end - - it { expect(bonding_master).to be_nil } - end - - describe ".interfaces" do - let :interfaces do - described_class.interfaces - end - - let :ifconfig_output do - my_fixture_read("ifconfig_all_with_multiple_interfaces") - end - - before :each do - described_class.expects(:ifconfig_path).returns('/usr/bin/ifconfig') - described_class.expects(:exec).with(anything).returns(ifconfig_output) - end - - it "should return an array with two interfaces" do - expect(interfaces).to eq ["lo0", "e1000g0"] - end - end - - describe ".value_for_interface_and_label(interface, label)" do - let :value_for_interface_and_label do - described_class.value_for_interface_and_label interface, label - end - - let(:interface) { 'e1000g0' } - let(:ifconfig_output) { my_fixture_read "ifconfig_single_interface" } - let(:ifconfig_path) { "/usr/bin/ifconfig" } - let(:exec_cmd) { "#{ifconfig_path} #{interface}" } - - before :each do - described_class.expects(:ifconfig_path).returns(ifconfig_path) - described_class.expects(:exec).with(exec_cmd).returns(ifconfig_output) - end - - describe "ipaddress" do - let(:label) { "ipaddress" } - - it { expect(value_for_interface_and_label).to eq "172.16.15.138" } - end - - describe "netmask" do - let(:label) { "netmask" } - - it { expect(value_for_interface_and_label).to eq "255.255.255.0" } - end - - describe "mtu" do - let(:label) { "mtu" } - - it { expect(value_for_interface_and_label).to eq '1500' } - end - end -end diff --git a/spec/unit/util/ip/windows_spec.rb b/spec/unit/util/ip/windows_spec.rb deleted file mode 100644 index 09fc3fb68c..0000000000 --- a/spec/unit/util/ip/windows_spec.rb +++ /dev/null @@ -1,94 +0,0 @@ -# encoding: UTF-8 - -require 'spec_helper' -require 'facter/util/ip/windows' - -describe Facter::Util::IP::Windows do - before :each do - Facter.fact(:kernel).stubs(:value).returns('windows') - end - - describe ".to_s" do - let(:to_s) { described_class.to_s } - - it { expect(to_s).to eq 'windows' } - end - - describe ".convert_netmask_from_hex?" do - let :convert_netmask_from_hex? do - described_class.convert_netmask_from_hex? - end - - it { expect(convert_netmask_from_hex?).to be false } - end - - describe ".bonding_master" do - let(:bonding_master) { described_class.bonding_master('eth0') } - - it { expect(bonding_master).to be_nil } - end - - describe ".interfaces" do - let(:interfaces) { described_class.interfaces } - - let(:netsh_output) { my_fixture_read "netsh_all_interfaces" } - - let :expected_interfaces do - [ - "Loopback Pseudo-Interface 1", - "Local Area Connection", - "Teredo Tunneling Pseudo-Interface" - ] - end - - before :each do - described_class.expects(:exec).with(anything).returns(netsh_output) - end - - it "should return an array of only connected interfaces" do - expect(interfaces).to eq expected_interfaces - end - end - - describe "value_for_interface_and_label(interface, label)" do - let(:interface) { 'Local Area Connection' } - let(:netsh_output) { my_fixture_read("netsh_with_single_interface") } - - let :value_for_interface_and_label do - described_class.value_for_interface_and_label interface, label - end - - let(:exec_cmd) do - "#{described_class::NETSH} interface ip show address \"#{interface}\"" - end - - before :each do - described_class.expects(:exec).with(exec_cmd).returns(netsh_output) - end - - describe "ipaddress" do - let(:label) { 'ipaddress' } - - it { expect(value_for_interface_and_label).to eq '172.16.138.216' } - end - - describe "netmask" do - let(:label) { 'netmask' } - - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } - end - - describe "ipaddress6" do - let(:interface) { 'Teredo Tunneling Pseudo-Interface' } - let(:label) { 'ipaddress6' } - let(:expected_ip) { '2001:0:4137:9e76:2087:77a:53ef:7527' } - let(:netsh_output) { my_fixture_read("netsh_with_single_interface6") } - - let(:exec_cmd) do - "#{described_class::NETSH} interface ipv6 show address \"#{interface}\"" - end - - it { expect(value_for_interface_and_label).to eq expected_ip } - end - end -end diff --git a/spec/unit/util/ip_spec.rb b/spec/unit/util/ip_spec.rb index faee2be628..3d95ee5cd7 100755 --- a/spec/unit/util/ip_spec.rb +++ b/spec/unit/util/ip_spec.rb @@ -6,12 +6,416 @@ describe Facter::Util::IP do include FacterSpec::ConfigHelper - %w{ - FreeBSD Linux NetBSD OpenBSD SunOS Darwin HP-UX GNU/kFreeBSD windows - Dragonfly - }.each do |platform| + before :each do + given_a_configuration_of(:is_windows => false) + end + + [:freebsd, :linux, :netbsd, :openbsd, :sunos, :darwin, :"hp-ux", :"gnu/kfreebsd", :windows].each do |platform| it "should be supported on #{platform}" do - Facter::Util::IP.supported_platforms.should include platform + given_a_configuration_of(:is_windows => platform == :windows) + Facter::Util::IP.supported_platforms.should be_include(platform) + end + end + + it "should return a list of interfaces" do + Facter::Util::IP.should respond_to(:get_interfaces) + end + + it "should return an empty list of interfaces on an unknown kernel" do + File.stubs(:exist?).with('/sys/class/net').returns(false) + Facter.stubs(:value).returns("UnknownKernel") + Facter::Util::IP.get_interfaces().should == [] + end + + it "should return a list with a single interface and the loopback interface on Linux with a single interface without sysfs" do + File.stubs(:exist?).with('/sys/class/net').returns(false) + linux_ifconfig = my_fixture_read("linux_ifconfig_all_with_single_interface") + Facter::Util::IP.stubs(:get_all_interface_output).returns(linux_ifconfig) + Facter::Util::IP.get_interfaces().should =~ ["eth0", "lo"] + end + + it "should return a list two interfaces on Darwin with two interfaces" do + File.stubs(:exist?).with('/sys/class/net').returns(false) + darwin_ifconfig = my_fixture_read("darwin_ifconfig_all_with_multiple_interfaces") + Facter::Util::IP.stubs(:get_all_interface_output).returns(darwin_ifconfig) + Facter::Util::IP.get_interfaces().should == ["lo0", "en0"] + end + + it "should return a list two interfaces on Solaris with two interfaces multiply reporting" do + File.stubs(:exist?).with('/sys/class/net').returns(false) + solaris_ifconfig = my_fixture_read("solaris_ifconfig_all_with_multiple_interfaces") + Facter::Util::IP.stubs(:get_all_interface_output).returns(solaris_ifconfig) + Facter::Util::IP.get_interfaces().should == ["lo0", "e1000g0"] + end + + it "should return a list of six interfaces on a GNU/kFreeBSD with six interfaces" do + File.stubs(:exist?).with('/sys/class/net').returns(false) + kfreebsd_ifconfig = my_fixture_read("debian_kfreebsd_ifconfig") + Facter::Util::IP.stubs(:get_all_interface_output).returns(kfreebsd_ifconfig) + Facter::Util::IP.get_interfaces().should == ["em0", "em1", "bge0", "bge1", "lo0", "vlan0"] + end + + it "should return a list of only connected interfaces on Windows" do + File.stubs(:exist?).with('/sys/class/net').returns(false) + Facter.fact(:kernel).stubs(:value).returns("windows") + windows_netsh = my_fixture_read("windows_netsh_all_interfaces") + Facter::Util::IP.stubs(:get_all_interface_output).returns(windows_netsh) + Facter::Util::IP.get_interfaces().should == ["Loopback Pseudo-Interface 1", "Local Area Connection", "Teredo Tunneling Pseudo-Interface"] + end + + it "should return a value for a specific interface" do + Facter::Util::IP.should respond_to(:get_interface_value) + end + + it "should not return interface information for unsupported platforms" do + Facter.stubs(:value).with(:kernel).returns("bleah") + Facter::Util::IP.get_interface_value("e1000g0", "netmask").should == [] + end + + it "should return ipaddress information for Solaris" do + solaris_ifconfig_interface = my_fixture_read("solaris_ifconfig_single_interface") + + Facter::Util::IP.expects(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("SunOS") + + Facter::Util::IP.get_interface_value("e1000g0", "ipaddress").should == "172.16.15.138" + end + + it "should return netmask information for Solaris" do + solaris_ifconfig_interface = my_fixture_read("solaris_ifconfig_single_interface") + + Facter::Util::IP.expects(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("SunOS") + + Facter::Util::IP.get_interface_value("e1000g0", "netmask").should == "255.255.255.0" + end + + it "should return calculated network information for Solaris" do + solaris_ifconfig_interface = my_fixture_read("solaris_ifconfig_single_interface") + + Facter::Util::IP.stubs(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("SunOS") + + Facter::Util::IP.get_network_value("e1000g0").should == "172.16.15.0" + end + + it "should return macaddress with leading zeros stripped off for GNU/kFreeBSD" do + kfreebsd_ifconfig = my_fixture_read("debian_kfreebsd_ifconfig") + + Facter::Util::IP.expects(:get_single_interface_output).with("em0").returns(kfreebsd_ifconfig) + Facter.stubs(:value).with(:kernel).returns("GNU/kFreeBSD") + + Facter::Util::IP.get_interface_value("em0", "macaddress").should == "0:11:a:59:67:90" + end + + it "should return interface information for FreeBSD supported via an alias" do + ifconfig_interface = my_fixture_read("6.0-STABLE_FreeBSD_ifconfig") + + Facter::Util::IP.expects(:get_single_interface_output).with("fxp0").returns(ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("FreeBSD") + + Facter::Util::IP.get_interface_value("fxp0", "macaddress").should == "00:0e:0c:68:67:7c" + end + + it "should return macaddress information for OS X" do + ifconfig_interface = my_fixture_read("Mac_OS_X_10.5.5_ifconfig") + + Facter::Util::IP.expects(:get_single_interface_output).with("en1").returns(ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("Darwin") + + Facter::Util::IP.get_interface_value("en1", "macaddress").should == "00:1b:63:ae:02:66" + end + + it "should return all interfaces correctly on OS X" do + File.stubs(:exist?).with('/sys/class/net').returns(false) + ifconfig_interface = my_fixture_read("Mac_OS_X_10.5.5_ifconfig") + + Facter::Util::IP.expects(:get_all_interface_output).returns(ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("Darwin") + + Facter::Util::IP.get_interfaces().should == ["lo0", "gif0", "stf0", "en0", "fw0", "en1", "vmnet8", "vmnet1"] + end + + it "should return a human readable netmask on Solaris" do + solaris_ifconfig_interface = my_fixture_read("solaris_ifconfig_single_interface") + + Facter::Util::IP.expects(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("SunOS") + + Facter::Util::IP.get_interface_value("e1000g0", "netmask").should == "255.255.255.0" + end + + it "should return a human readable netmask on Darwin" do + darwin_ifconfig_interface = my_fixture_read("darwin_ifconfig_single_interface") + + Facter::Util::IP.expects(:get_single_interface_output).with("en1").returns(darwin_ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("Darwin") + + Facter::Util::IP.get_interface_value("en1", "netmask").should == "255.255.255.0" + end + + it "should return a human readable netmask on GNU/kFreeBSD" do + kfreebsd_ifconfig = my_fixture_read("debian_kfreebsd_ifconfig") + + Facter::Util::IP.expects(:get_single_interface_output).with("em1").returns(kfreebsd_ifconfig) + Facter.stubs(:value).with(:kernel).returns("GNU/kFreeBSD") + + Facter::Util::IP.get_interface_value("em1", "netmask").should == "255.255.255.0" + end + + it "should return correct macaddress information for infiniband on Linux" do + correct_ifconfig_interface = my_fixture_read("linux_get_single_interface_ib0") + + Facter::Util::IP.expects(:get_single_interface_output).with("ib0").returns(correct_ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("Linux") + + Facter::Util::IP.get_interface_value("ib0", "macaddress").should == "80:00:00:4a:fe:80:00:00:00:00:00:00:00:02:c9:03:00:43:27:21" + end + + it "should replace the incorrect macaddress with the correct macaddress in ifconfig for infiniband on Linux" do + ifconfig_interface = my_fixture_read("linux_ifconfig_ib0") + correct_ifconfig_interface = my_fixture_read("linux_get_single_interface_ib0") + + Facter::Util::IP.expects(:get_infiniband_macaddress).with("ib0").returns("80:00:00:4a:fe:80:00:00:00:00:00:00:00:02:c9:03:00:43:27:21") + Facter::Util::IP.expects(:ifconfig_interface).with("ib0").returns(ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("Linux") + + Facter::Util::IP.get_single_interface_output("ib0").should == correct_ifconfig_interface + end + + it "should return fake macaddress information for infiniband on Linux when neither sysfs or /sbin/ip are available" do + ifconfig_interface = my_fixture_read("linux_ifconfig_ib0") + + File.expects(:exists?).with("/sys/class/net/ib0/address").returns(false) + File.expects(:exists?).with("/sbin/ip").returns(false) + Facter::Util::IP.expects(:ifconfig_interface).with("ib0").returns(ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("Linux") + + Facter::Util::IP.get_interface_value("ib0", "macaddress").should == "FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF" + end + + it "should not get bonding master on interface aliases" do + Facter.stubs(:value).with(:kernel).returns("Linux") + + Facter::Util::IP.get_bonding_master("eth0:1").should be_nil + end + + [:freebsd, :netbsd, :openbsd, :sunos, :darwin, :"hp-ux"].each do |platform| + it "should require conversion from hex on #{platform}" do + Facter::Util::IP.convert_from_hex?(platform).should == true + end + end + + [:windows].each do |platform| + it "should not require conversion from hex on #{platform}" do + Facter::Util::IP.convert_from_hex?(platform).should be_false + end + end + + it "should return an arp address on Linux" do + Facter.stubs(:value).with(:kernel).returns("Linux") + + Facter::Util::IP.expects(:get_arp_value).with("eth0").returns("00:00:0c:9f:f0:04") + Facter::Util::IP.get_arp_value("eth0").should == "00:00:0c:9f:f0:04" + end + + it "should return mtu information on Linux" do + linux_ifconfig = my_fixture_read("linux_ifconfig_all_with_single_interface") + Facter::Util::IP.stubs(:get_all_interface_output).returns(linux_ifconfig) + Facter::Util::IP.stubs(:get_single_interface_output).with("eth0"). + returns(my_fixture_read("linux_get_single_interface_eth0")) + Facter::Util::IP.stubs(:get_single_interface_output).with("lo"). + returns(my_fixture_read("linux_get_single_interface_lo")) + Facter.stubs(:value).with(:kernel).returns("Linux") + + Facter::Util::IP.get_interface_value("eth0", "mtu").should == "1500" + Facter::Util::IP.get_interface_value("lo", "mtu").should == "16436" + end + + it "should return mtu information on Darwin" do + darwin_ifconfig_interface = my_fixture_read("darwin_ifconfig_single_interface") + + Facter::Util::IP.expects(:get_single_interface_output).with("en1").returns(darwin_ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("Darwin") + + Facter::Util::IP.get_interface_value("en1", "mtu").should == "1500" + end + + it "should return mtu information for Solaris" do + solaris_ifconfig_interface = my_fixture_read("solaris_ifconfig_single_interface") + + Facter::Util::IP.expects(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("SunOS") + + Facter::Util::IP.get_interface_value("e1000g0", "mtu").should == "1500" + end + + # (#17487) - tests for HP-UX. + # some fake data for testing robustness of regexps. + def self.fake_netstat_in_examples + examples = [] + examples << ["Header row\na line with none in it\na line without\nanother line without\n", + "a line without\nanother line without\n"] + examples << ["Header row\na line without\na line with none in it\nanother line with none\nanother line without\n", + "a line without\nanother line without\n"] + examples << ["Header row\na line with * asterisks *\na line with none in it\nanother line without\n", + "a line with asterisks \nanother line without\n"] + examples << ["a line with none none none in it\na line with none in it\na line without\nanother line without\n", + "another line without\n"] + examples + end + + fake_netstat_in_examples.each_with_index do |example, i| + input, expected_output = example + it "should pass regexp test on fake netstat input example #{i}" do + Facter.stubs(:value).with(:kernel).returns("HP-UX") + Facter::Util::IP.stubs(:hpux_netstat_in).returns(input) + Facter::Util::IP.get_all_interface_output().should == expected_output + end + end + + # and some real data for exhaustive tests. + def self.hpux_examples + examples = [] + examples << ["HP-UX 11.11", + ["lan1", "lan0", "lo0" ], + ["1500", "1500", "4136" ], + ["10.1.1.6", "192.168.3.10", "127.0.0.1"], + ["255.255.255.0", "255.255.255.0", "255.0.0.0"], + ["00:10:79:7B:5C:DE", "00:30:7F:0C:79:DC", nil ], + [my_fixture_read("hpux_1111_ifconfig_lan1"), + my_fixture_read("hpux_1111_ifconfig_lan0"), + my_fixture_read("hpux_1111_ifconfig_lo0")], + my_fixture_read("hpux_1111_netstat_in"), + my_fixture_read("hpux_1111_lanscan")] + + examples << ["HP-UX 11.31", + ["lan1", "lan0", "lo0" ], + ["1500", "1500", "4136" ], + ["10.1.54.36", "192.168.30.152", "127.0.0.1"], + ["255.255.255.0", "255.255.255.0", "255.0.0.0"], + ["00:17:FD:2D:2A:57", "00:12:31:7D:62:09", nil ], + [my_fixture_read("hpux_1131_ifconfig_lan1"), + my_fixture_read("hpux_1131_ifconfig_lan0"), + my_fixture_read("hpux_1131_ifconfig_lo0")], + my_fixture_read("hpux_1131_netstat_in"), + my_fixture_read("hpux_1131_lanscan")] + + examples << ["HP-UX 11.31 with an asterisk after a NIC that has an address", + ["lan1", "lan0", "lo0" ], + ["1500", "1500", "4136" ], + ["10.10.0.5", "192.168.3.9", "127.0.0.1"], + ["255.255.255.0", "255.255.255.0", "255.0.0.0"], + ["00:10:79:7B:BE:46", "00:30:5D:06:26:B2", nil ], + [my_fixture_read("hpux_1131_asterisk_ifconfig_lan1"), + my_fixture_read("hpux_1131_asterisk_ifconfig_lan0"), + my_fixture_read("hpux_1131_asterisk_ifconfig_lo0")], + my_fixture_read("hpux_1131_asterisk_netstat_in"), + my_fixture_read("hpux_1131_asterisk_lanscan")] + + examples << ["HP-UX 11.31 with NIC bonding and one virtual NIC", + ["lan4:1", "lan1", "lo0", "lan4" ], + ["1500", "1500", "4136", "1500" ], + ["192.168.1.197", "192.168.30.32", "127.0.0.1", "192.168.32.75" ], + ["255.255.255.0", "255.255.255.0", "255.0.0.0", "255.255.255.0" ], + [nil, "00:12:81:9E:48:DE", nil, "00:12:81:9E:4A:7E"], + [my_fixture_read("hpux_1131_nic_bonding_ifconfig_lan4_1"), + my_fixture_read("hpux_1131_nic_bonding_ifconfig_lan1"), + my_fixture_read("hpux_1131_nic_bonding_ifconfig_lo0"), + my_fixture_read("hpux_1131_nic_bonding_ifconfig_lan4")], + my_fixture_read("hpux_1131_nic_bonding_netstat_in"), + my_fixture_read("hpux_1131_nic_bonding_lanscan")] + examples + end + + hpux_examples.each do |example| + description, array_of_expected_ifs, array_of_expected_mtus, + array_of_expected_ips, array_of_expected_netmasks, + array_of_expected_macs, array_of_ifconfig_fixtures, + netstat_in_fixture, lanscan_fixture = example + + it "should return a list three interfaces on #{description}" do + Facter.expects(:value).with(:kernel).at_least_once.returns("HP-UX") + File.expects(:exist?).with('/sys/class/net').returns(false) + Facter::Util::IP.expects(:hpux_netstat_in).returns(netstat_in_fixture) + Facter::Util::IP.get_interfaces.should == array_of_expected_ifs + end + + array_of_expected_ifs.each_with_index do |nic, i| + ifconfig_fixture = array_of_ifconfig_fixtures[i] + expected_mtu = array_of_expected_mtus[i] + expected_ip = array_of_expected_ips[i] + expected_netmask = array_of_expected_netmasks[i] + expected_mac = array_of_expected_macs[i] + + # (#17808) These tests fail because MTU facts haven't been implemented for HP-UX. + #it "should return MTU #{expected_mtu} on #{nic} for #{description} example" do + # Facter.expects(:value).with(:kernel).at_least_once.returns("HP-UX") + # Facter::Util::IP.expects(:hpux_netstat_in).returns(netstat_in_fixture) + # Facter::Util::IP.expects(:hpux_lanscan).returns(lanscan_fixture) + # Facter::Util::IP.expects(:hpux_ifconfig_interface).with(nic).returns(ifconfig_fixture) + # Facter::Util::IP.get_interface_value(nic, "mtu").should == expected_mtu + #end + + it "should return IP #{expected_ip} on #{nic} for #{description} example" do + Facter.expects(:value).with(:kernel).at_least_once.returns("HP-UX") + Facter::Util::IP.expects(:hpux_lanscan).returns(lanscan_fixture) + Facter::Util::IP.expects(:hpux_ifconfig_interface).with(nic).returns(ifconfig_fixture) + Facter::Util::IP.get_interface_value(nic, "ipaddress").should == expected_ip + end + + it "should return netmask #{expected_netmask} on #{nic} for #{description} example" do + Facter.expects(:value).with(:kernel).at_least_once.returns("HP-UX") + Facter::Util::IP.expects(:hpux_lanscan).returns(lanscan_fixture) + Facter::Util::IP.expects(:hpux_ifconfig_interface).with(nic).returns(ifconfig_fixture) + Facter::Util::IP.get_interface_value(nic, "netmask").should == expected_netmask + end + + it "should return MAC address #{expected_mac} on #{nic} for #{description} example" do + Facter.expects(:value).with(:kernel).at_least_once.returns("HP-UX") + Facter::Util::IP.expects(:hpux_lanscan).returns(lanscan_fixture) + Facter::Util::IP.expects(:hpux_ifconfig_interface).with(nic).returns(ifconfig_fixture) + Facter::Util::IP.get_interface_value(nic, "macaddress").should == expected_mac + end + end + end + + describe "on Windows" do + before :each do + Facter.stubs(:value).with(:kernel).returns("windows") + end + + it "should return ipaddress information" do + windows_netsh = my_fixture_read("windows_netsh_single_interface") + + Facter::Util::IP.expects(:get_output_for_interface_and_label).with("Local Area Connection", "ipaddress").returns(windows_netsh) + + Facter::Util::IP.get_interface_value("Local Area Connection", "ipaddress").should == "172.16.138.216" + end + + it "should return a human readable netmask" do + windows_netsh = my_fixture_read("windows_netsh_single_interface") + + Facter::Util::IP.expects(:get_output_for_interface_and_label).with("Local Area Connection", "netmask").returns(windows_netsh) + + Facter::Util::IP.get_interface_value("Local Area Connection", "netmask").should == "255.255.255.0" + end + + it "should return network information" do + windows_netsh = my_fixture_read("windows_netsh_single_interface") + + Facter::Util::IP.stubs(:get_output_for_interface_and_label).with("Local Area Connection", "ipaddress").returns(windows_netsh) + Facter::Util::IP.stubs(:get_output_for_interface_and_label).with("Local Area Connection", "netmask").returns(windows_netsh) + + Facter::Util::IP.get_network_value("Local Area Connection").should == "172.16.138.0" + end + + it "should return ipaddress6 information" do + windows_netsh = my_fixture_read("windows_netsh_single_interface6") + + Facter::Util::IP.expects(:get_output_for_interface_and_label).with("Teredo Tunneling Pseudo-Interface", "ipaddress6").returns(windows_netsh) + + Facter::Util::IP.get_interface_value("Teredo Tunneling Pseudo-Interface", "ipaddress6").should == "2001:0:4137:9e76:2087:77a:53ef:7527" end end @@ -36,4 +440,44 @@ Facter::Util::IP.exec_ifconfig(["-a","-e","-i","-j"]) end end + describe "get_ifconfig" do + it "assigns /sbin/ifconfig if it is executable" do + File.stubs(:executable?).returns(false) + File.stubs(:executable?).with("/sbin/ifconfig").returns(true) + Facter::Util::IP.get_ifconfig.should eq("/sbin/ifconfig") + end + it "assigns /usr/sbin/ifconfig if it is executable" do + File.stubs(:executable?).returns(false) + File.stubs(:executable?).with("/usr/sbin/ifconfig").returns(true) + Facter::Util::IP.get_ifconfig.should eq("/usr/sbin/ifconfig") + end + it "assigns /bin/ifconfig if it is executable" do + File.stubs(:executable?).returns(false) + File.stubs(:executable?).with("/bin/ifconfig").returns(true) + Facter::Util::IP.get_ifconfig.should eq("/bin/ifconfig") + end + end + + context "with bonded ethernet interfaces on Linux" do + before :each do + Facter.fact(:kernel).stubs(:value).returns("Linux") + end + + describe "Facter::Util::Ip.get_interface_value" do + before :each do + Facter::Util::IP.stubs(:read_proc_net_bonding). + with("/proc/net/bonding/bond0"). + returns(my_fixture_read("linux_2_6_35_proc_net_bonding_bond0")) + + Facter::Util::IP.stubs(:get_bonding_master).returns("bond0") + end + + it 'provides the real device macaddress for eth0' do + Facter::Util::IP.get_interface_value("eth0", "macaddress").should == "00:11:22:33:44:55" + end + it 'provides the real device macaddress for eth1' do + Facter::Util::IP.get_interface_value("eth1", "macaddress").should == "00:11:22:33:44:56" + end + end + end end diff --git a/spec/unit/util/parser_spec.rb b/spec/unit/util/parser_spec.rb index 8a0ae6ee48..be7b9e7f4d 100644 --- a/spec/unit/util/parser_spec.rb +++ b/spec/unit/util/parser_spec.rb @@ -1,7 +1,10 @@ +#!/usr/bin/env ruby + require 'spec_helper' + require 'facter/util/parser' require 'tempfile' -require 'tmpdir' +require 'tmpdir.rb' describe Facter::Util::Parser do include PuppetlabsSpec::Files diff --git a/spec/unit/zfs_version_spec.rb b/spec/unit/zfs_version_spec.rb index 9b88392a31..905dbc9ee5 100644 --- a/spec/unit/zfs_version_spec.rb +++ b/spec/unit/zfs_version_spec.rb @@ -1,3 +1,5 @@ +#!/usr/bin/env ruby + require 'spec_helper' describe "zfs_version fact" do diff --git a/spec/unit/zpool_version_spec.rb b/spec/unit/zpool_version_spec.rb index 5952b8f7b0..0cba70fdb6 100644 --- a/spec/unit/zpool_version_spec.rb +++ b/spec/unit/zpool_version_spec.rb @@ -1,3 +1,5 @@ +#!/usr/bin/env ruby + require 'spec_helper' describe "zpool_version fact" do From 80f9074ab532663ad7b9a93b00d99a6b27fa5043 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Tue, 2 Apr 2013 20:51:00 -0700 Subject: [PATCH 1196/3753] (maint) Fix unhandled exception when zoneadm is missing Without this patch the puppet master spec examples are currently failing when run on Solaris 10. The root cause of this failure is an assumption that the `zoneadm` command is always available on Solaris 10 systems. Because of this assumption, the method that parses out the zone information tries to call split on an instance variable that is assumed to be a string, but is actually `nil` when the `zoneadm` command is missing. This patch addresses the problem by checking if the instance variable is truthy and only then calling the split method on it. Otherwise a debug message records the fact that the zoneadm command is not available which causes the zone facts to be unset. --- lib/facter/util/solaris_zones.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/facter/util/solaris_zones.rb b/lib/facter/util/solaris_zones.rb index 76c4a2dd29..28bfbe39b4 100644 --- a/lib/facter/util/solaris_zones.rb +++ b/lib/facter/util/solaris_zones.rb @@ -103,7 +103,12 @@ def refresh # # @api private def parse! - rows = @zoneadm_output.split("\n").collect { |line| line.split(':') } + if @zoneadm_output + rows = @zoneadm_output.split("\n").collect { |line| line.split(':') } + else + Facter.debug "Cannot parse zone facts, #{zoneadm_cmd} returned no output" + rows = [] + end @zone_hash = rows.inject({}) do |memo, fields| zone = fields[1].intern From 2f02c827d1ed58842eb005bff4fe633082f15d1e Mon Sep 17 00:00:00 2001 From: Todd Zullinger Date: Fri, 29 Mar 2013 19:31:59 -0400 Subject: [PATCH 1197/3753] (#19989) Filter virt-what warnings from virtual fact MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Presently, the virt-what command puts errors on stdout, prefixed with virt-what:. This ends up in the virtual fact, which is undesirable. When a fixed virt-what is released and available for supported systems, the 'output.gsub /^virt-what: .*$/' can be removed from Facter::Util::Virtual::virt_what(). A virt-what fix was committed upstream¹. Thanks to Adrien Thebo for help on making this testable. ¹ http://git.annexia.org/?p=virt-what.git;a=commitdiff;h=2f47e06 --- lib/facter/util/virtual.rb | 10 +++++++++- spec/unit/util/virtual_spec.rb | 6 ++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/facter/util/virtual.rb b/lib/facter/util/virtual.rb index f034531909..3a11571841 100644 --- a/lib/facter/util/virtual.rb +++ b/lib/facter/util/virtual.rb @@ -3,8 +3,16 @@ module Facter::Util::Virtual # virt_what is a delegating helper method intended to make it easier to stub # the system call without affecting other calls to # Facter::Util::Resolution.exec + # + # Per https://bugzilla.redhat.com/show_bug.cgi?id=719611 when run as a + # non-root user the virt-what command may emit an error message on stdout, + # and later versions of virt-what may emit this message on stderr. This + # method ensures stderr is redirected and that error messages are stripped + # from stdout. def self.virt_what(command = "virt-what") - Facter::Util::Resolution.exec command + redirected_cmd = "#{command} 2>/dev/null" + output = Facter::Util::Resolution.exec redirected_cmd + output.gsub(/^virt-what: .*$/, '') end ## diff --git a/spec/unit/util/virtual_spec.rb b/spec/unit/util/virtual_spec.rb index dcb62fb62f..9265701fb8 100755 --- a/spec/unit/util/virtual_spec.rb +++ b/spec/unit/util/virtual_spec.rb @@ -235,4 +235,10 @@ Facter::Util::Virtual.should_not be_virtualbox end + + it "should strip out warnings on stdout from virt-what" do + virt_what_warning = "virt-what: this script must be run as root" + Facter::Util::Resolution.expects(:exec).with('virt-what 2>/dev/null').returns virt_what_warning + Facter::Util::Virtual.virt_what.should_not match /^virt-what: / + end end From 4b44b797785ad48d64116e9e13f063dfe89910d2 Mon Sep 17 00:00:00 2001 From: Lex Rivera Date: Tue, 2 Apr 2013 14:36:05 +0400 Subject: [PATCH 1198/3753] [virtual] improve detection of proxmox-based KVM virtual machines --- lib/facter/util/virtual.rb | 1 + lib/facter/virtual.rb | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/lib/facter/util/virtual.rb b/lib/facter/util/virtual.rb index 3a11571841..64044a6e13 100644 --- a/lib/facter/util/virtual.rb +++ b/lib/facter/util/virtual.rb @@ -82,6 +82,7 @@ def self.kvm? Facter::Util::Resolution.exec("/sbin/sysctl -n hw.model") end (txt =~ /QEMU Virtual CPU/) ? true : false + (txt =~ /Common KVM processor/) ? true : false end def self.virtualbox? diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 20e2702ddb..f581f9fca9 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -122,6 +122,9 @@ # --- look for gmetrics for GCE # --- 00:05.0 Class 8007: Google, Inc. Device 6442 result = "gce" if p =~ /Class 8007: Google, Inc/ + # --- look for Red Hat VirtIO drivers. Commonly used in KVM environment + # --- 00:03.0 Unclassified device [00ff]: Red Hat, Inc Virtio memory balloon + result = "kvm" if p =~ /Red Hat, Inc Virtio/ end else output = Facter::Util::Resolution.exec('dmidecode') @@ -134,6 +137,7 @@ result = "hyperv" if pd =~ /Product Name: Virtual Machine/ result = "rhev" if pd =~ /Product Name: RHEV Hypervisor/ result = "ovirt" if pd =~ /Product Name: oVirt Node/ + result = "kvm" if pd =~ /Product Name: Bochs/ end elsif Facter.value(:kernel) == 'SunOS' res = Facter::Util::Resolution.new('prtdiag') From 5c653e98e97ba8e83a46b8e0c1fd72dfe672964b Mon Sep 17 00:00:00 2001 From: Lex Rivera Date: Tue, 2 Apr 2013 15:22:06 +0400 Subject: [PATCH 1199/3753] [virtual] rewrite cpu-based detection of KVM-based VMs --- lib/facter/util/virtual.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/facter/util/virtual.rb b/lib/facter/util/virtual.rb index 64044a6e13..5d75dfbe8f 100644 --- a/lib/facter/util/virtual.rb +++ b/lib/facter/util/virtual.rb @@ -81,8 +81,10 @@ def self.kvm? elsif ["FreeBSD", "OpenBSD"].include? Facter.value(:kernel) Facter::Util::Resolution.exec("/sbin/sysctl -n hw.model") end - (txt =~ /QEMU Virtual CPU/) ? true : false - (txt =~ /Common KVM processor/) ? true : false + if txt =~ /QEMU Virtual CPU/ then true + elsif txt =~ /Common KVM processor/ then true + else false + end end def self.virtualbox? From edb7b66f883f53435893139999e1771f7b942b83 Mon Sep 17 00:00:00 2001 From: Lex Rivera Date: Tue, 2 Apr 2013 15:22:39 +0400 Subject: [PATCH 1200/3753] [virtual] add tests for dmidecode / bochs and lspci / virtio --- spec/unit/virtual_spec.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index 855da9d4a1..4563981068 100755 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -106,6 +106,11 @@ Facter.fact(:virtual).value.should == "virtualbox" end + it "should be kvm with Red Hat, Inc vendor name and Virtio driver family from lspci 2>/dev/null" do + Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns("00:03.0 Unclassified device [00ff]: Red Hat, Inc Virtio memory balloon") + Facter.fact(:virtual).value.should == "kvm" + end + it "should be vmware with VMWare vendor name from dmidecode" do Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("On Board Device 1 Information\nType: Video\nStatus: Disabled\nDescription: VMware SVGA II") @@ -175,6 +180,11 @@ Facter.fact(:virtual).value.should == "hyperv" end + it "should be kvm with Bochs product name from dmidecode" do + Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("Product Name: Bochs") + Facter.fact(:virtual).value.should == "kvm" + end + context "In Google Compute Engine" do before :each do Facter.fact(:kernel).stubs(:value).returns("Linux") From 3eab7bfdbba3d9d23a0c130ebb07b929290052c3 Mon Sep 17 00:00:00 2001 From: Lex Rivera Date: Tue, 2 Apr 2013 15:33:48 +0400 Subject: [PATCH 1201/3753] [virtual] use manufacturer instead of product name in Bochs/KVM detection --- lib/facter/virtual.rb | 2 +- spec/unit/virtual_spec.rb | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index f581f9fca9..3a384a56f7 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -137,7 +137,7 @@ result = "hyperv" if pd =~ /Product Name: Virtual Machine/ result = "rhev" if pd =~ /Product Name: RHEV Hypervisor/ result = "ovirt" if pd =~ /Product Name: oVirt Node/ - result = "kvm" if pd =~ /Product Name: Bochs/ + result = "kvm" if pd =~ /Manufacturer: Bochs/ end elsif Facter.value(:kernel) == 'SunOS' res = Facter::Util::Resolution.new('prtdiag') diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index 4563981068..409dec5c82 100755 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -180,10 +180,11 @@ Facter.fact(:virtual).value.should == "hyperv" end - it "should be kvm with Bochs product name from dmidecode" do - Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("Product Name: Bochs") - Facter.fact(:virtual).value.should == "kvm" - end + it "should be kvm with Bochs vendor name from dmidecode" do + Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) + Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("Manufacturer: Bochs") + Facter.fact(:virtual).value.should == "kvm" + end context "In Google Compute Engine" do before :each do From 08188324fe44efc5e93defb2ee49955d825f3a02 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 2 Apr 2013 17:12:12 -0700 Subject: [PATCH 1202/3753] Revert "Merge branch 'pull-419' into 1.7.x" Facter 1.7 is in RC; this is being reverted to avoid delaying 1.7.0. This reverts commit 229fca73ac31e33534c8be3e0322345a3b07cc95, reversing changes made to b7784a3af80c70a833dfd3de1fa32dc1eb62384e. --- lib/facter/util/virtual.rb | 5 +---- lib/facter/virtual.rb | 4 ---- spec/unit/virtual_spec.rb | 11 ----------- 3 files changed, 1 insertion(+), 19 deletions(-) diff --git a/lib/facter/util/virtual.rb b/lib/facter/util/virtual.rb index 5d75dfbe8f..3a11571841 100644 --- a/lib/facter/util/virtual.rb +++ b/lib/facter/util/virtual.rb @@ -81,10 +81,7 @@ def self.kvm? elsif ["FreeBSD", "OpenBSD"].include? Facter.value(:kernel) Facter::Util::Resolution.exec("/sbin/sysctl -n hw.model") end - if txt =~ /QEMU Virtual CPU/ then true - elsif txt =~ /Common KVM processor/ then true - else false - end + (txt =~ /QEMU Virtual CPU/) ? true : false end def self.virtualbox? diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 3a384a56f7..20e2702ddb 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -122,9 +122,6 @@ # --- look for gmetrics for GCE # --- 00:05.0 Class 8007: Google, Inc. Device 6442 result = "gce" if p =~ /Class 8007: Google, Inc/ - # --- look for Red Hat VirtIO drivers. Commonly used in KVM environment - # --- 00:03.0 Unclassified device [00ff]: Red Hat, Inc Virtio memory balloon - result = "kvm" if p =~ /Red Hat, Inc Virtio/ end else output = Facter::Util::Resolution.exec('dmidecode') @@ -137,7 +134,6 @@ result = "hyperv" if pd =~ /Product Name: Virtual Machine/ result = "rhev" if pd =~ /Product Name: RHEV Hypervisor/ result = "ovirt" if pd =~ /Product Name: oVirt Node/ - result = "kvm" if pd =~ /Manufacturer: Bochs/ end elsif Facter.value(:kernel) == 'SunOS' res = Facter::Util::Resolution.new('prtdiag') diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index 409dec5c82..855da9d4a1 100755 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -106,11 +106,6 @@ Facter.fact(:virtual).value.should == "virtualbox" end - it "should be kvm with Red Hat, Inc vendor name and Virtio driver family from lspci 2>/dev/null" do - Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns("00:03.0 Unclassified device [00ff]: Red Hat, Inc Virtio memory balloon") - Facter.fact(:virtual).value.should == "kvm" - end - it "should be vmware with VMWare vendor name from dmidecode" do Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("On Board Device 1 Information\nType: Video\nStatus: Disabled\nDescription: VMware SVGA II") @@ -180,12 +175,6 @@ Facter.fact(:virtual).value.should == "hyperv" end - it "should be kvm with Bochs vendor name from dmidecode" do - Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) - Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("Manufacturer: Bochs") - Facter.fact(:virtual).value.should == "kvm" - end - context "In Google Compute Engine" do before :each do Facter.fact(:kernel).stubs(:value).returns("Linux") From 9e1443af0369dcb6f4756689adce2a1ce4ffcd30 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 2 Apr 2013 17:14:48 -0700 Subject: [PATCH 1203/3753] Revert "Revert "Merge branch 'pull-419' into 1.7.x"" This revert serves to move GH-419 into master. This reverts commit 08188324fe44efc5e93defb2ee49955d825f3a02. --- lib/facter/util/virtual.rb | 5 ++++- lib/facter/virtual.rb | 4 ++++ spec/unit/virtual_spec.rb | 11 +++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/facter/util/virtual.rb b/lib/facter/util/virtual.rb index 942435ffa0..55cf981ad0 100644 --- a/lib/facter/util/virtual.rb +++ b/lib/facter/util/virtual.rb @@ -81,7 +81,10 @@ def self.kvm? elsif ["FreeBSD", "OpenBSD"].include? Facter.value(:kernel) Facter::Util::Resolution.exec("/sbin/sysctl -n hw.model") end - (txt =~ /QEMU Virtual CPU/) ? true : false + if txt =~ /QEMU Virtual CPU/ then true + elsif txt =~ /Common KVM processor/ then true + else false + end end def self.virtualbox? diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 30131ddd81..5ec548c686 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -121,6 +121,9 @@ # --- look for gmetrics for GCE # --- 00:05.0 Class 8007: Google, Inc. Device 6442 result = "gce" if p =~ /Class 8007: Google, Inc/ + # --- look for Red Hat VirtIO drivers. Commonly used in KVM environment + # --- 00:03.0 Unclassified device [00ff]: Red Hat, Inc Virtio memory balloon + result = "kvm" if p =~ /Red Hat, Inc Virtio/ end else output = Facter::Util::Resolution.exec('dmidecode') @@ -133,6 +136,7 @@ result = "hyperv" if pd =~ /Product Name: Virtual Machine/ result = "rhev" if pd =~ /Product Name: RHEV Hypervisor/ result = "ovirt" if pd =~ /Product Name: oVirt Node/ + result = "kvm" if pd =~ /Manufacturer: Bochs/ end elsif Facter.value(:kernel) == 'SunOS' res = Facter::Util::Resolution.new('prtdiag') diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index 855da9d4a1..409dec5c82 100755 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -106,6 +106,11 @@ Facter.fact(:virtual).value.should == "virtualbox" end + it "should be kvm with Red Hat, Inc vendor name and Virtio driver family from lspci 2>/dev/null" do + Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns("00:03.0 Unclassified device [00ff]: Red Hat, Inc Virtio memory balloon") + Facter.fact(:virtual).value.should == "kvm" + end + it "should be vmware with VMWare vendor name from dmidecode" do Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("On Board Device 1 Information\nType: Video\nStatus: Disabled\nDescription: VMware SVGA II") @@ -175,6 +180,12 @@ Facter.fact(:virtual).value.should == "hyperv" end + it "should be kvm with Bochs vendor name from dmidecode" do + Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) + Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("Manufacturer: Bochs") + Facter.fact(:virtual).value.should == "kvm" + end + context "In Google Compute Engine" do before :each do Facter.fact(:kernel).stubs(:value).returns("Linux") From 4a59d36147c83dc8e803f1079bed67d41efd9da3 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Wed, 3 Apr 2013 10:59:52 -0700 Subject: [PATCH 1204/3753] (Maint) Don't parse nil Facter::Util::Resolution output Previously, running facter on a system without virt-what would produce: Could not retrieve virtual: undefined method `gsub' for nil:NilClass This commit checks that we have a non-nil output before trying to parse it. --- lib/facter/util/virtual.rb | 2 +- lib/facter/virtual.rb | 29 +++++++++++++++-------------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/lib/facter/util/virtual.rb b/lib/facter/util/virtual.rb index 3a11571841..75fb55a728 100644 --- a/lib/facter/util/virtual.rb +++ b/lib/facter/util/virtual.rb @@ -12,7 +12,7 @@ module Facter::Util::Virtual def self.virt_what(command = "virt-what") redirected_cmd = "#{command} 2>/dev/null" output = Facter::Util::Resolution.exec redirected_cmd - output.gsub(/^virt-what: .*$/, '') + output.gsub(/^virt-what: .*$/, '') if output end ## diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 20e2702ddb..0a68e14596 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -206,20 +206,21 @@ has_weight 500 setcode do - output = Facter::Util::Virtual.virt_what - case output - when 'linux_vserver' - Facter::Util::Virtual.vserver_type - when /xen-hvm/i - 'xenhvm' - when /xen-dom0/i - 'xen0' - when /xen-domU/i - 'xenu' - when /ibm_systemz/i - 'zlinux' - else - output.to_s.split("\n").last + if output = Facter::Util::Virtual.virt_what + case output + when 'linux_vserver' + Facter::Util::Virtual.vserver_type + when /xen-hvm/i + 'xenhvm' + when /xen-dom0/i + 'xen0' + when /xen-domU/i + 'xenu' + when /ibm_systemz/i + 'zlinux' + else + output.to_s.split("\n").last + end end end end From a9c82fd42eb56bf6d368923959e28712b04fc329 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Wed, 3 Apr 2013 11:35:54 -0700 Subject: [PATCH 1205/3753] (Maint) Suppress spec test output The macosx_spec ensures that an error is raised when parsing invalid XML, but this causes the following output when running the tests Fatal error: error parsing attribute name at :1. Fatal error: attributes construct error at :1. Fatal error: Couldn't find end of Start Tag bad line 1 at :1. Fatal error: Extra content at the end of the document at :1. This commit suppresses the output written to STDERR. See LibXML::XML::Error::VERBOSE_HANDLER. --- spec/unit/util/macosx_spec.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/spec/unit/util/macosx_spec.rb b/spec/unit/util/macosx_spec.rb index 60d08f3608..f633263c9d 100755 --- a/spec/unit/util/macosx_spec.rb +++ b/spec/unit/util/macosx_spec.rb @@ -42,8 +42,10 @@ end it 'should fail when trying to read invalid XML' do - expect { Facter::Util::Macosx.intern_xml('xml<--->') }.to \ - raise_error(RuntimeError, /A plist file could not be properly read by Facter::Util::CFPropertyList/) + STDERR.stubs(:<<) + expect { + Facter::Util::Macosx.intern_xml('xml<--->') + }.to raise_error(RuntimeError, /A plist file could not be properly read by Facter::Util::CFPropertyList/) end describe "when collecting profiler data" do From e65537753ea8e5261c7397f7471e08784f961795 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Dal=C3=A9n?= Date: Thu, 21 Mar 2013 16:09:57 +0100 Subject: [PATCH 1206/3753] (#19845) Allow a block to be supplied as a confine Allows a confine to take only a block parameter like: confine { File.exist? '/bin/foo' } Or take a fact name and a block: confine :ipaddress do |addr| require 'ipaddr' IPAddr.new('192.168.0.0/16').include? addr end This is similar to what Puppet allows in provider confines. --- lib/facter/util/confine.rb | 38 +++++++++++++++++++++++++++---- lib/facter/util/resolution.rb | 18 ++++++++++++--- spec/unit/util/confine_spec.rb | 21 +++++++++++++++++ spec/unit/util/resolution_spec.rb | 21 +++++++++++++++++ 4 files changed, 91 insertions(+), 7 deletions(-) diff --git a/lib/facter/util/confine.rb b/lib/facter/util/confine.rb index 3bb504bb74..eac539ce35 100644 --- a/lib/facter/util/confine.rb +++ b/lib/facter/util/confine.rb @@ -10,19 +10,40 @@ class Facter::Util::Confine # Add the restriction. Requires the fact name, an operator, and the value # we're comparing to. - def initialize(fact, *values) - raise ArgumentError, "The fact name must be provided" unless fact - raise ArgumentError, "One or more values must be provided" if values.empty? + # + # @param fact [Symbol] Name of the fact + # @param values [Array] One or more values to match against. + # They can be any type that provides a === method. + # @param block [Proc] Alternatively a block can be supplied as a check. The fact + # value will be passed as the argument to the block. If the block returns + # true then the fact will be enabled, otherwise it will be disabled. + def initialize(fact = nil, *values, &block) + raise ArgumentError, "The fact name must be provided" unless fact or block_given? + if values.empty? and not block_given? + raise ArgumentError, "One or more values or a block must be provided" + end @fact = fact @values = values + @block = block end def to_s + return @block.to_s if @block return "'%s' '%s'" % [@fact, @values.join(",")] end # Evaluate the fact, returning true or false. + # if we have a block paramter then we only evaluate that instead def true? + if @block and not @fact then + begin + return !! @block.call + rescue StandardError => error + Facter.debug "Confine raised #{error.class} #{error}" + return false + end + end + unless fact = Facter[@fact] Facter.debug "No fact for %s" % @fact return false @@ -31,6 +52,15 @@ def true? return false if value.nil? - return @values.any? { |v| convert(v) === value } + if @block then + begin + return !! @block.call(value) + rescue StandardError => error + Facter.debug "Confine raised #{error.class} #{error}" + return false + end + end + + return @values.any? do |v| convert(v) === value end end end diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index f7d667e928..22479bacd5 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -255,9 +255,21 @@ def self.exec(code, interpreter = nil) # @return [void] # # @api public - def confine(confines) - confines.each do |fact, values| - @confines.push Facter::Util::Confine.new(fact, *values) + def confine(confines = nil, &block) + case confines + when Hash + confines.each do |fact, values| + @confines.push Facter::Util::Confine.new(fact, *values) + end + else + if block + if confines + @confines.push Facter::Util::Confine.new(confines, &block) + else + @confines.push Facter::Util::Confine.new(&block) + end + else + end end end diff --git a/spec/unit/util/confine_spec.rb b/spec/unit/util/confine_spec.rb index 5bdd8daf8e..f6b11b5d7c 100755 --- a/spec/unit/util/confine_spec.rb +++ b/spec/unit/util/confine_spec.rb @@ -123,5 +123,26 @@ def confined(fact_value, *confines) it "should return false if none of the provided ranges matches the fact's value" do confined(8, (5..7)).should be_false end + + it "should accept and evaluate a block argument against the fact" do + @fact.expects(:value).returns 'foo' + confine = Facter::Util::Confine.new :yay do |f| f === 'foo' end + confine.true?.should be_true + end + + it "should return false if the block raises a StandardError when checking a fact" do + @fact.stubs(:value).returns 'foo' + confine = Facter::Util::Confine.new :yay do |f| raise StandardError end + confine.true?.should be_false + end + + it "should accept and evaluate only a block argument" do + Facter::Util::Confine.new { true }.true?.should be_true + Facter::Util::Confine.new { false }.true?.should be_false + end + + it "should return false if the block raises a StandardError" do + Facter::Util::Confine.new { raise StandardError }.true?.should be_false + end end end diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index a989dd8613..c844ebb93d 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -344,6 +344,27 @@ class FlushFakeError < StandardError; end @resolve.confine "one" => "foo", "two" => "fee" end + it "should accept a single fact with a block parameter" do + lambda { @resolve.confine :one do true end }.should_not raise_error + end + + it "should create a Util::Confine instance for the provided fact with block parameter" do + block = lambda { true } + Facter::Util::Confine.expects(:new).with("one") + + @resolve.confine("one", &block) + end + + it "should accept a single block parameter" do + lambda { @resolve.confine() do true end }.should_not raise_error + end + + it "should create a Util::Confine instance for the provided block parameter" do + block = lambda { true } + Facter::Util::Confine.expects(:new) + + @resolve.confine(&block) + end end describe "when determining suitability" do From 49e3f85132946c61498a5dd364025f6d5147f26e Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Wed, 3 Apr 2013 13:40:37 -0700 Subject: [PATCH 1207/3753] (doc) Update the Resolution#confine API docs Without this patch, the API documentation for the Facter::Util::Resolution#confine is a bit muddled and confusing. This confusion is caused by the different, overloaded, forms of arguments that #confine accepts. This patch addresses the problem by using the @overload YARD tag to separate out each form, document the arguments for each form, and provide a clear example for each form. --- lib/facter/util/resolution.rb | 64 ++++++++++++++++++++++++----------- 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 22479bacd5..5e8744b241 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -232,29 +232,55 @@ def self.exec(code, interpreter = nil) end end - # Sets the conditions for this resolution to be used. This takes a - # hash of fact names and values. Every fact must match the values - # given for that fact, otherwise this resolution will not be - # considered suitable. The values given for a fact can be an array, in - # which case the value of the fact must be in the array for it to - # match. - # - # @param confines [Hash{String => Object}] a hash of facts and the - # values they should have in order for this resolution to be - # used - # - # @example Confining to Linux - # Facter.add(:powerstates) do - # # This resolution only makes sense on linux systems - # confine :kernel => "Linux" - # setcode do - # Facter::Util::Resolution.exec('cat /sys/power/states') - # end - # end + ## + # Sets the conditions for this resolution to be used. This method accepts + # multiple forms of arguments to determine suitability. # # @return [void] # # @api public + # + # @overload confine(confines) + # Confine a fact to a specific fact value or values. This form takes a + # hash of fact names and values. Every fact must match the values given for + # that fact, otherwise this resolution will not be considered suitable. The + # values given for a fact can be an array, in which case the value of the + # fact must be in the array for it to match. + # @param [Hash{String,Symbol=>String,Array}] confines set of facts identified by the hash keys whose + # fact value must match the argument value. + # @example Confining to Linux + # Facter.add(:powerstates) do + # # This resolution only makes sense on linux systems + # confine :kernel => "Linux" + # setcode do + # File.read('/sys/power/states') + # end + # end + # + # @overload confine(confines, &block) + # Confine a fact to a block with the value of a specified fact yielded to + # the block. + # @param [String,Symbol] confines the fact name whose value should be + # yielded to the block + # @param [Proc] block determines the suitability of the fact. If the block + # evaluates to `false` or `nil` then the confined fact will not be + # evaluated. + # @yield [value] the value of the fact identified by {confines} + # @example Confine the fact to a host with an ipaddress in a specific + # subnet + # confine :ipaddress do |addr| + # require 'ipaddr' + # IPAddr.new('192.168.0.0/16').include? addr + # end + # + # @overload confine(&block) + # Confine a fact to a block. The fact will be evaluated only if the block + # evaluates to something other than `false` or `nil`. + # @param [Proc] block determines the suitability of the fact. If the block + # evaluates to `false` or `nil` then the confined fact will not be + # evaluated. + # @example Confine the fact to systems with a specific file. + # confine { File.exist? '/bin/foo' } def confine(confines = nil, &block) case confines when Hash From fee751e75e743f6d3fe004c997fdce6cc7ce09f1 Mon Sep 17 00:00:00 2001 From: Patrick Carlisle Date: Wed, 3 Apr 2013 14:01:22 -0700 Subject: [PATCH 1208/3753] (#20053) Add missing require in ipaddress6 fact This fixes the error Could not retrieve ipaddress6: uninitialized constant Facter::Util::IP --- lib/facter/ipaddress6.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/facter/ipaddress6.rb b/lib/facter/ipaddress6.rb index cf804ab76d..b05ab9a0e3 100644 --- a/lib/facter/ipaddress6.rb +++ b/lib/facter/ipaddress6.rb @@ -21,6 +21,8 @@ # Used the ipaddress fact that is already part of # Facter as a template. +require 'facter/util/ip' + def get_address_after_token(output, token, return_first=false) ip = nil From 9b2947313a5d66eaee11a30d8f030e457e7d1e68 Mon Sep 17 00:00:00 2001 From: Patrick Carlisle Date: Wed, 3 Apr 2013 14:20:27 -0700 Subject: [PATCH 1209/3753] (Maint) Fix environment leakage in loader spec This removes the stub on Facter when loading facts from the environment. This would cause the test to fail if the environment had any extra variables named FACTER_*. --- spec/unit/util/loader_spec.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/spec/unit/util/loader_spec.rb b/spec/unit/util/loader_spec.rb index 2b9891e413..63dacb90fd 100755 --- a/spec/unit/util/loader_spec.rb +++ b/spec/unit/util/loader_spec.rb @@ -321,12 +321,11 @@ def load_file(file) end it "should load all facts from the environment" do - Facter.expects(:add).with('one') - Facter.expects(:add).with('two') - Facter::Util::Resolution.with_env "facter_one" => "yayness", "facter_two" => "boo" do @loader.load_all end + Facter.value(:one).should == 'yayness' + Facter.value(:two).should == 'boo' end it "should only load all facts one time" do From 0c1fb7486f4312cba06d61003bc221d5e0ef4a90 Mon Sep 17 00:00:00 2001 From: Matthaus Owens Date: Fri, 5 Apr 2013 10:21:55 -0700 Subject: [PATCH 1210/3753] (#20072) Redirect stderr to the correct null device on windows Previously the virt_what method redirected stderr to /dev/null, which fails on windows. This commit adds a case for windows where the stderr is redirected to NUL, the windows null device. It also adds tests to ensure this behavior. --- lib/facter/util/virtual.rb | 6 +++++- spec/unit/util/virtual_spec.rb | 13 +++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/facter/util/virtual.rb b/lib/facter/util/virtual.rb index 75fb55a728..d3ce67336c 100644 --- a/lib/facter/util/virtual.rb +++ b/lib/facter/util/virtual.rb @@ -10,7 +10,11 @@ module Facter::Util::Virtual # method ensures stderr is redirected and that error messages are stripped # from stdout. def self.virt_what(command = "virt-what") - redirected_cmd = "#{command} 2>/dev/null" + if Facter.value(:kernel) == 'windows' + redirected_cmd = "#{command} 2>NUL" + else + redirected_cmd = "#{command} 2>/dev/null" + end output = Facter::Util::Resolution.exec redirected_cmd output.gsub(/^virt-what: .*$/, '') if output end diff --git a/spec/unit/util/virtual_spec.rb b/spec/unit/util/virtual_spec.rb index 9265701fb8..178328df79 100755 --- a/spec/unit/util/virtual_spec.rb +++ b/spec/unit/util/virtual_spec.rb @@ -238,7 +238,20 @@ it "should strip out warnings on stdout from virt-what" do virt_what_warning = "virt-what: this script must be run as root" + Facter.fact(:kernel).expects(:value).returns("linux") Facter::Util::Resolution.expects(:exec).with('virt-what 2>/dev/null').returns virt_what_warning Facter::Util::Virtual.virt_what.should_not match /^virt-what: / end + + ["windows","linux","unix"].each do |kernel| + describe "virt_what should redirect to the correct null device" do + let(:null_device) { kernel == "windows" ? "2>NUL" : "2>/dev/null" } + + it "on #{kernel}" do + Facter.fact(:kernel).expects(:value).returns(kernel) + Facter::Util::Resolution.expects(:exec).with("virt-what #{null_device}") + Facter::Util::Virtual.virt_what + end + end + end end From 9cefa228db33b7c9b1358a15d3cf21e2db54987c Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Fri, 5 Apr 2013 11:23:42 -0700 Subject: [PATCH 1211/3753] (#22072) Execute fully resolved virt-what Previously, we were calling virt-what, which on windows, causes it to think it might be a shell built-in, which it isn't. And that results in the warning: Using Facter::Util::Resolution.exec with a shell built-in is deprecated Most built-ins can be replaced with native ruby commands. If you really have to run a built-in, pass "cmd /c your_builtin" as a command. This commit changes the virt-what helper to first resolve virt-what into a fully qualified path, which uses ENV['PATH']. On Unix, it also looks in /sbin and /usr/sbin. --- lib/facter/util/virtual.rb | 3 +++ spec/unit/util/virtual_spec.rb | 43 ++++++++++++++++++++++++---------- 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/lib/facter/util/virtual.rb b/lib/facter/util/virtual.rb index d3ce67336c..262379ab77 100644 --- a/lib/facter/util/virtual.rb +++ b/lib/facter/util/virtual.rb @@ -10,6 +10,9 @@ module Facter::Util::Virtual # method ensures stderr is redirected and that error messages are stripped # from stdout. def self.virt_what(command = "virt-what") + command = Facter::Util::Resolution.which(command) + return unless command + if Facter.value(:kernel) == 'windows' redirected_cmd = "#{command} 2>NUL" else diff --git a/spec/unit/util/virtual_spec.rb b/spec/unit/util/virtual_spec.rb index 178328df79..821ba62ccc 100755 --- a/spec/unit/util/virtual_spec.rb +++ b/spec/unit/util/virtual_spec.rb @@ -236,22 +236,39 @@ Facter::Util::Virtual.should_not be_virtualbox end - it "should strip out warnings on stdout from virt-what" do - virt_what_warning = "virt-what: this script must be run as root" - Facter.fact(:kernel).expects(:value).returns("linux") - Facter::Util::Resolution.expects(:exec).with('virt-what 2>/dev/null').returns virt_what_warning - Facter::Util::Virtual.virt_what.should_not match /^virt-what: / + shared_examples_for "virt-what" do |kernel, path, null_device| + Facter.fact(:kernel).expects(:value).returns(kernel) + Facter::Util::Resolution.expects(:which).with("virt-what").returns(path) + Facter::Util::Resolution.expects(:exec).with("#{path} 2>#{null_device}") + Facter::Util::Virtual.virt_what end - ["windows","linux","unix"].each do |kernel| - describe "virt_what should redirect to the correct null device" do - let(:null_device) { kernel == "windows" ? "2>NUL" : "2>/dev/null" } + context "on linux" do + before :each do + Facter.fact(:kernel).expects(:value).returns("linux") + end - it "on #{kernel}" do - Facter.fact(:kernel).expects(:value).returns(kernel) - Facter::Util::Resolution.expects(:exec).with("virt-what #{null_device}") - Facter::Util::Virtual.virt_what - end + it_should_behave_like "virt-what", "linux", "/usr/bin/virt-what", "/dev/null" + + it "should strip out warnings on stdout from virt-what" do + virt_what_warning = "virt-what: this script must be run as root" + Facter::Util::Resolution.expects(:which).with('virt-what').returns "/usr/bin/virt-what" + Facter::Util::Resolution.expects(:exec).with('/usr/bin/virt-what 2>/dev/null').returns virt_what_warning + Facter::Util::Virtual.virt_what.should_not match /^virt-what: / + end + end + + context "on unix" do + before :each do + Facter.fact(:kernel).expects(:value).returns("unix") + end + it_should_behave_like "virt-what", "unix", "/usr/bin/virt-what", "/dev/null" + end + + context "on windows" do + before :each do + Facter.fact(:kernel).expects(:value).returns("windows") end + it_should_behave_like "virt-what", "windows", 'c:\windows\system32\virt-what', "NUL" end end From 6ebe9d1d244749b5875be21f7e4705065aeef612 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Fri, 5 Apr 2013 11:28:00 -0700 Subject: [PATCH 1212/3753] Maint: Colorize spec output on Windows Previously, running bundle exec rspec spec on Windows output ANSI escape sequences. This commit requires the win32console gem to colorize the output. --- Gemfile | 1 + spec/spec_helper.rb | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/Gemfile b/Gemfile index ac84800131..d2d5db16ef 100644 --- a/Gemfile +++ b/Gemfile @@ -26,6 +26,7 @@ platform :mswin, :mingw do gem "win32-dir", "~> 0.3.7" gem "windows-api", "~> 0.4.1" gem "windows-pr", "~> 1.2.1" + gem "win32console", "~> 1.3.2" end if File.exists? "#{__FILE__}.local" diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 10cbf04f52..97fa072fba 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -20,6 +20,13 @@ RSpec.configure do |config| config.mock_with :mocha + if Facter::Util::Config.is_windows? + require 'win32console' + config.output_stream = $stdout + config.error_stream = $stderr + config.formatters.each { |f| f.instance_variable_set(:@output, $stdout) } + end + config.before :each do # Ensure that we don't accidentally cache facts and environment # between test cases. From b5f50cfcb38de12fb98126c26e71190b4387aba7 Mon Sep 17 00:00:00 2001 From: Matthaus Owens Date: Fri, 5 Apr 2013 10:25:01 -0700 Subject: [PATCH 1213/3753] (#14522) Don't read /proc/self/status on Solaris /proc/self/status isn't a standard file on Solaris and can't be read as such. Attempting to read it when using ruby 1.9.3 produces errors such as 'invalid byte sequence in US-ASCII'. According to http://www.ruby-forum.com/topic/163804, /proc/self/status is a sparse file with a map at /proc/self/map to indicate which parts of the file to read. As a stopgap measure, this commit simply returns false on Solaris, rather than trying to read the file. --- lib/facter/util/virtual.rb | 2 ++ spec/unit/util/virtual_spec.rb | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/lib/facter/util/virtual.rb b/lib/facter/util/virtual.rb index 262379ab77..3d1c94edcd 100644 --- a/lib/facter/util/virtual.rb +++ b/lib/facter/util/virtual.rb @@ -60,6 +60,8 @@ def self.zone? end def self.vserver? + # /proc/self/status is not easily readable on Solaris. See http://projects.puppetlabs.com/issues/14522 + return false if Facter.value(:osfamily) == "Solaris" return false unless FileTest.exists?("/proc/self/status") txt = File.open("/proc/self/status", "rb").read return true if txt =~ /^(s_context|VxID):[[:blank:]]*[0-9]/ diff --git a/spec/unit/util/virtual_spec.rb b/spec/unit/util/virtual_spec.rb index 821ba62ccc..4dfab334a6 100755 --- a/spec/unit/util/virtual_spec.rb +++ b/spec/unit/util/virtual_spec.rb @@ -58,6 +58,13 @@ Facter::Util::Virtual.should_not be_zone end + it "should not read /proc/self/status on Solaris" do + Facter.expects(:value).with(:osfamily).returns("Solaris") + FileTest.expects(:exists?).with("/proc/self/status").never + File.expects(:open).with("/proc/self/status").never + Facter::Util::Virtual.vserver?.should == false + end + it "should not detect vserver if no self status" do FileTest.stubs(:exists?).with("/proc/self/status").returns(false) Facter::Util::Virtual.should_not be_vserver From 5e7b695f534d9f3cb1b3d819c3accbfd38ad5d71 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Fri, 5 Apr 2013 12:58:04 -0700 Subject: [PATCH 1214/3753] (#14522) Force encoding of data read from /proc/self/status Without this patch the previous fix for this issue worked well, but was actually testing and fixing a different problem than described in the issue. Reading the /proc/self/status file on Solaris works well, however the file contents are very different from the Linux platform. On linux the file contains US-ASCII encoded text which is fully compatible with our default UTF-8 encoding. On Solaris, however, the file is raw data representing the memory space of the process. This raw data has no encoding, and as such, causes the invalid byte sequence when compared with a regular expression. This patch addresses the problem by forcing the encoding of the string into UTF-8, removing any invalid characters. The comparison is not a problem on MRI versions which do not have `String#encode!`. --- lib/facter/util/virtual.rb | 5 +++-- .../util/virtual/solaris10_proc_self_status1 | Bin 0 -> 1136 bytes spec/unit/util/virtual_spec.rb | 18 +++++++++++++----- 3 files changed, 16 insertions(+), 7 deletions(-) create mode 100644 spec/fixtures/unit/util/virtual/solaris10_proc_self_status1 diff --git a/lib/facter/util/virtual.rb b/lib/facter/util/virtual.rb index 3d1c94edcd..af192dadc6 100644 --- a/lib/facter/util/virtual.rb +++ b/lib/facter/util/virtual.rb @@ -60,10 +60,11 @@ def self.zone? end def self.vserver? - # /proc/self/status is not easily readable on Solaris. See http://projects.puppetlabs.com/issues/14522 - return false if Facter.value(:osfamily) == "Solaris" return false unless FileTest.exists?("/proc/self/status") txt = File.open("/proc/self/status", "rb").read + if txt.respond_to?(:encode!) + txt.encode!('UTF-8', 'UTF-8', :invalid => :replace) + end return true if txt =~ /^(s_context|VxID):[[:blank:]]*[0-9]/ return false end diff --git a/spec/fixtures/unit/util/virtual/solaris10_proc_self_status1 b/spec/fixtures/unit/util/virtual/solaris10_proc_self_status1 new file mode 100644 index 0000000000000000000000000000000000000000..f3003511135e0f2080cbccd339fc6fc997d2f44c GIT binary patch literal 1136 zcmY#jVBlnAU|`5nVqi!CVi*k+!V1o0uyL>`0Hq!G zC?IC0lA9Hv;YlUq28}UHWV$3I7*iuGy&NJUy(DNo`v)@NFesvcz!{1c$+L1W0OgK+ z0H#AQ`0>y6=*NF+fP7F~@&Pd$5QD_5fpinl10Xq&etTgc0R;d5LuimZ2!P~h0UG3K JVd~H{69B3cBY6M- literal 0 HcmV?d00001 diff --git a/spec/unit/util/virtual_spec.rb b/spec/unit/util/virtual_spec.rb index 4dfab334a6..5db24e7370 100755 --- a/spec/unit/util/virtual_spec.rb +++ b/spec/unit/util/virtual_spec.rb @@ -58,11 +58,12 @@ Facter::Util::Virtual.should_not be_zone end - it "should not read /proc/self/status on Solaris" do - Facter.expects(:value).with(:osfamily).returns("Solaris") - FileTest.expects(:exists?).with("/proc/self/status").never - File.expects(:open).with("/proc/self/status").never - Facter::Util::Virtual.vserver?.should == false + it "(#14522) handles the unencoded binary data in /proc/self/status on Solaris" do + Facter.fact(:osfamily).stubs(:value).returns("Solaris") + File.stubs(:open).with('/proc/self/status', 'rb').returns(solaris_proc_self_status) + FileTest.stubs(:exists?).with('/proc/self/status').returns(true) + + Facter::Util::Virtual.vserver?.should eq(false) end it "should not detect vserver if no self status" do @@ -243,6 +244,13 @@ Facter::Util::Virtual.should_not be_virtualbox end + let :solaris_proc_self_status do + sample_data = my_fixture_read('solaris10_proc_self_status1') + mockfile = mock('File') + mockfile.stubs(:read).returns(sample_data) + mockfile + end + shared_examples_for "virt-what" do |kernel, path, null_device| Facter.fact(:kernel).expects(:value).returns(kernel) Facter::Util::Resolution.expects(:which).with("virt-what").returns(path) From 0598806656458addf6553c438bb67c71a9dd81fc Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Fri, 5 Apr 2013 13:37:43 -0700 Subject: [PATCH 1215/3753] Maint: Fix order dependent test failure Previously, executing the following would fail due to order dependent test failures: rspec spec/unit/util/virtual_spec.rb spec/unit/util/macaddress_spec.rb rspec spec/unit/util/virtual_spec.rb spec/unit/util/uptime_spec.rb This occurs because rspec filters were being used on describe blocks, but those values are not cleared up between examples. This commit instead filters the tests based on Facter::Util::Config.is_windows? It also removes duplicate expectations on kernel value --- spec/unit/util/macaddress_spec.rb | 2 +- spec/unit/util/uptime_spec.rb | 4 ++-- spec/unit/util/virtual_spec.rb | 13 ++----------- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/spec/unit/util/macaddress_spec.rb b/spec/unit/util/macaddress_spec.rb index 95e79aad74..c16d7a5695 100755 --- a/spec/unit/util/macaddress_spec.rb +++ b/spec/unit/util/macaddress_spec.rb @@ -18,7 +18,7 @@ end end -describe "Darwin", :unless => Facter.value(:operatingsystem) == 'windows' do +describe "Darwin", :unless => Facter::Util::Config.is_windows? do test_cases = [ # version, iface, real macaddress, fallback macaddress ["9.8.0", 'en0', "00:17:f2:06:e4:2e", "00:17:f2:06:e4:2e"], diff --git a/spec/unit/util/uptime_spec.rb b/spec/unit/util/uptime_spec.rb index e7a152948c..4bbbb752d5 100755 --- a/spec/unit/util/uptime_spec.rb +++ b/spec/unit/util/uptime_spec.rb @@ -5,7 +5,7 @@ describe Facter::Util::Uptime do - describe ".get_uptime_seconds_unix", :unless => Facter.value(:operatingsystem) == 'windows' do + describe ".get_uptime_seconds_unix", :unless => Facter::Util::Config.is_windows? do describe "when /proc/uptime is available" do before do uptime_file = my_fixture("ubuntu_proc_uptime") @@ -118,7 +118,7 @@ end end - describe ".get_uptime_seconds_win", :if => Facter.value(:operatingsystem) == 'windows' do + describe ".get_uptime_seconds_win", :if => Facter::Util::Config.is_windows? do it "should return a postive value" do Facter::Util::Uptime.get_uptime_seconds_win.should > 0 end diff --git a/spec/unit/util/virtual_spec.rb b/spec/unit/util/virtual_spec.rb index 5db24e7370..c0ee4b6074 100755 --- a/spec/unit/util/virtual_spec.rb +++ b/spec/unit/util/virtual_spec.rb @@ -252,21 +252,18 @@ end shared_examples_for "virt-what" do |kernel, path, null_device| - Facter.fact(:kernel).expects(:value).returns(kernel) + Facter.fact(:kernel).stubs(:value).returns(kernel) Facter::Util::Resolution.expects(:which).with("virt-what").returns(path) Facter::Util::Resolution.expects(:exec).with("#{path} 2>#{null_device}") Facter::Util::Virtual.virt_what end context "on linux" do - before :each do - Facter.fact(:kernel).expects(:value).returns("linux") - end - it_should_behave_like "virt-what", "linux", "/usr/bin/virt-what", "/dev/null" it "should strip out warnings on stdout from virt-what" do virt_what_warning = "virt-what: this script must be run as root" + Facter.fact(:kernel).stubs(:value).returns('linux') Facter::Util::Resolution.expects(:which).with('virt-what').returns "/usr/bin/virt-what" Facter::Util::Resolution.expects(:exec).with('/usr/bin/virt-what 2>/dev/null').returns virt_what_warning Facter::Util::Virtual.virt_what.should_not match /^virt-what: / @@ -274,16 +271,10 @@ end context "on unix" do - before :each do - Facter.fact(:kernel).expects(:value).returns("unix") - end it_should_behave_like "virt-what", "unix", "/usr/bin/virt-what", "/dev/null" end context "on windows" do - before :each do - Facter.fact(:kernel).expects(:value).returns("windows") - end it_should_behave_like "virt-what", "windows", 'c:\windows\system32\virt-what', "NUL" end end From aa4ce2d39f6b692dd3b598c64c4b90ccc9cf8903 Mon Sep 17 00:00:00 2001 From: Matthaus Owens Date: Sun, 7 Apr 2013 20:21:36 -0700 Subject: [PATCH 1216/3753] (packaging) Update FACTERVERSION to 1.7.0-rc2 --- lib/facter/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/version.rb b/lib/facter/version.rb index e45162440e..9aa5a490cb 100644 --- a/lib/facter/version.rb +++ b/lib/facter/version.rb @@ -1,6 +1,6 @@ module Facter if not defined? FACTERVERSION then - FACTERVERSION = '1.7.0-rc1' + FACTERVERSION = '1.7.0-rc2' end ## From 30a8d20109e43cc6802aa4bda0171790460abe64 Mon Sep 17 00:00:00 2001 From: Michael Moll Date: Mon, 1 Apr 2013 20:46:11 +0200 Subject: [PATCH 1217/3753] refactor blockdevices code --- lib/facter/blockdevices.rb | 90 +++---------------- lib/facter/util/blockdevices.rb | 41 +++++++++ lib/facter/util/blockdevices/freebsd.rb | 74 +++++++++++++++ lib/facter/util/blockdevices/linux.rb | 52 +++++++++++ .../blockdevices/freebsd_atacontrol_cap_ad10 | 22 +++++ .../freebsd_camcontrol_identify_ada0 | 37 ++++++++ .../blockdevices/freebsd_diskinfo_ad10 | 10 +++ .../blockdevices/freebsd_diskinfo_ada0 | 10 +++ .../blockdevices/freebsd_diskinfo_da0 | 10 +++ .../blockdevices/freebsd_diskinfo_mfi0 | 7 ++ spec/unit/blockdevices_spec.rb | 46 ++++++---- 11 files changed, 302 insertions(+), 97 deletions(-) create mode 100644 lib/facter/util/blockdevices.rb create mode 100644 lib/facter/util/blockdevices/freebsd.rb create mode 100644 lib/facter/util/blockdevices/linux.rb create mode 100644 spec/fixtures/blockdevices/freebsd_atacontrol_cap_ad10 create mode 100644 spec/fixtures/blockdevices/freebsd_camcontrol_identify_ada0 create mode 100644 spec/fixtures/blockdevices/freebsd_diskinfo_ad10 create mode 100644 spec/fixtures/blockdevices/freebsd_diskinfo_ada0 create mode 100644 spec/fixtures/blockdevices/freebsd_diskinfo_da0 create mode 100644 spec/fixtures/blockdevices/freebsd_diskinfo_mfi0 diff --git a/lib/facter/blockdevices.rb b/lib/facter/blockdevices.rb index 1e0abb4e81..98aab69ffa 100644 --- a/lib/facter/blockdevices.rb +++ b/lib/facter/blockdevices.rb @@ -50,94 +50,24 @@ # Author: Jason Gill require 'facter' +require 'facter/util/blockdevices' -# Only Linux 2.6+ kernels support sysfs which is required to easily get device details -if Facter.value(:kernel) == 'Linux' - - sysfs_block_directory = '/sys/block/' - - blockdevices = [] - - # This should prevent any non-2.6 kernels or odd machines without sysfs support from being investigated further - if File.exist?(sysfs_block_directory) - - # Iterate over each file in the /sys/block/ directory and skip ones that do not have a device subdirectory - Dir.entries(sysfs_block_directory).each do |device| - sysfs_device_directory = sysfs_block_directory + device + "/device" - next unless File.exist?(sysfs_device_directory) - - # Add the device to the blockdevices list, which is returned as it's own fact later on - blockdevices << device - - sizefile = sysfs_block_directory + device + "/size" - vendorfile = sysfs_device_directory + "/vendor" - modelfile = sysfs_device_directory + "/model" - - if File.exist?(sizefile) - Facter.add("blockdevice_#{device}_size".to_sym) do - setcode { IO.read(sizefile).strip.to_i * 512 } - end - end - - if File.exist?(vendorfile) - Facter.add("blockdevice_#{device}_vendor".to_sym) do - setcode { IO.read(vendorfile).strip } - end - end - - if File.exist?(modelfile) - Facter.add("blockdevice_#{device}_model".to_sym) do - setcode { IO.read(modelfile).strip } - end - end - - end - - end - - # Return a comma-seperated list of block devices found - unless blockdevices.empty? - Facter.add(:blockdevices) do - setcode { blockdevices.sort.join(',') } - end - end -end - -if Facter.value(:kernel) == 'FreeBSD' - blockdevices = Facter::Util::Resolution.exec('/sbin/sysctl -n kern.disks').split(' ') - +if Facter::Util::Blockdevices.available? Facter.add(:blockdevices) do - setcode { blockdevices.sort.join(',') } + setcode { Facter::Util::Blockdevices.devices.join(',') } end - blockdevices.each do |device| - Facter.add("blockdevice_#{device}_size".to_sym) do - if device =~ /acd|cd/ - setcode { "0" } - else - setcode { Facter::Util::Resolution.exec("/usr/sbin/diskinfo #{device} | /usr/bin/awk '{ print $3 }'") } - end + Facter::Util::Blockdevices.devices.each do |device| + Facter.add("blockdevice_#{device}_size") do + setcode { Facter::Util::Blockdevices.device_size(device) } end - devicestring = "" - if device =~ /ada/ - devicestring = Facter::Util::Resolution.exec("/sbin/camcontrol identify #{device} | /usr/bin/awk -F 'device\ model\ *' '/device model/ { print $2 }'") - elsif device =~ /ad/ - devicestring = Facter::Util::Resolution.exec("/sbin/atacontrol list | /usr/bin/awk -F '<|>' '/#{device}/ { print $2 }'") - elsif device =~ /mfi/ - devicestring = "MFI Local Disk" - else - devicestring = Facter::Util::Resolution.exec("/sbin/camcontrol inquiry #{device} -D | /usr/bin/awk -F '<|>' '{ print $2 }'") - end - - devicevendor, devicemodel = devicestring.split(' ', 2) - - Facter.add("blockdevice_#{device}_vendor".to_sym) do - setcode { devicevendor } + Facter.add("blockdevice_#{device}_vendor") do + setcode { Facter::Util::Blockdevices.device_vendor(device) } end - Facter.add("blockdevice_#{device}_model".to_sym) do - setcode { devicemodel } + Facter.add("blockdevice_#{device}_model") do + setcode { Facter::Util::Blockdevices.device_model(device)} end end end diff --git a/lib/facter/util/blockdevices.rb b/lib/facter/util/blockdevices.rb new file mode 100644 index 0000000000..4bcef493b9 --- /dev/null +++ b/lib/facter/util/blockdevices.rb @@ -0,0 +1,41 @@ +require 'facter/util/blockdevices/linux' +require 'facter/util/blockdevices/freebsd' + +module Facter::Util::Blockdevices + + IMPLEMENTATIONS = { + 'FreeBSD' => FreeBSD, + 'Linux' => Linux + } + + module NoImplementation + def self.devices + [] + end + end + + def self.implementation + IMPLEMENTATIONS[Facter.value(:kernel)] || NoImplementation + end + + def self.devices + implementation.devices + end + + def self.device_model(device_name) + implementation.device_model(device_name) + end + + def self.device_vendor(device_name) + implementation.device_vendor(device_name) + end + + def self.device_size(device_name) + implementation.device_size(device_name) + end + + def self.available? + !self.devices.empty? + end + +end diff --git a/lib/facter/util/blockdevices/freebsd.rb b/lib/facter/util/blockdevices/freebsd.rb new file mode 100644 index 0000000000..0a3d6b98d0 --- /dev/null +++ b/lib/facter/util/blockdevices/freebsd.rb @@ -0,0 +1,74 @@ +module Facter::Util::Blockdevices + + module FreeBSD + def self.device_vendor(device_name) + vendor_and_model(device_name).first + end + + def self.device_model(device_name) + vendor_and_model(device_name).last + end + + def self.device_size(device_name) + if device_name =~ /acd|cd/ + "0" + else + cmd_out = Facter::Util::Resolution.exec("/usr/sbin/diskinfo -v #{device_name}") + parse_diskinfo_size cmd_out + end + end + + def self.devices + Facter::Util::Resolution.exec('/sbin/sysctl -n kern.disks').split(' ').sort + end + + private + + def self.vendor_and_model(device_name) + devicestring = "" + if device_name =~ /ada/ + cmd_out = Facter::Util::Resolution.exec("/sbin/camcontrol identify #{device_name}") + devicestring = parse_camcontrol cmd_out + elsif device_name =~ /ad/ + cmd_out = Facter::Util::Resolution.exec("/sbin/atacontrol cap #{device_name}") + devicestring = parse_atacontrol cmd_out + elsif device_name =~ /mfi/ + devicestring = "MFI Local Disk" + else + cmd_out = Facter::Util::Resolution.exec("/sbin/camcontrol inquiry #{device_name} -D") + devicestring = parse_camcontrol cmd_out + end + + devicestring.split(' ', 2) + end + + def self.parse_camcontrol(output_str) + if output_str =~ /\s<(.+?)>\s/m + $1 + else + raise "parse_camcontrol failed. output string:\n#{output_str}" + end + end + + def self.parse_diskinfo_size(output_str) + entries = output_str.split(/\n/).map{|l| l.split(/\s+\#\s+/)} + match = entries.find{|(_, label)| label =~ /mediasize in bytes/ } + if match + match.first.strip + else + raise "parsing diskinfo output failed. output:\n#{output_str}" + end + end + + def self.parse_atacontrol(output_str) + entries = output_str.split(/\n/).map{|l| l.split(/\s{2,}/) } + match = entries.find{|(label, _)| label =~ /device model/ } + if match + "ATA #{match.last.strip}" + else + raise "parsing atacontrol output failed. output:\n#{output_str}" + end + end + + end +end diff --git a/lib/facter/util/blockdevices/linux.rb b/lib/facter/util/blockdevices/linux.rb new file mode 100644 index 0000000000..0c57a3a632 --- /dev/null +++ b/lib/facter/util/blockdevices/linux.rb @@ -0,0 +1,52 @@ +module Facter::Util::Blockdevices + + module Linux + # Only Linux 2.6+ kernels support sysfs which is required to easily get device details + SYSFS_BLOCK_DIRECTORY = '/sys/block/' + + def self.read_if_exists(f) + if File.exist?(f) + IO.read(f).strip + else + nil + end + end + + def self.device_vendor(device_name) + read_if_exists device_dir(device_name) + "/vendor" + end + + def self.device_model(device_name) + read_if_exists device_dir(device_name) + "/model" + end + + def self.device_size(device_name) + content = read_if_exists SYSFS_BLOCK_DIRECTORY + device_name + "/size" + if content + content.to_i * 512 + else + nil + end + end + + def self.devices + # This should prevent any non-2.6 kernels or odd machines + # without sysfs support from being investigated further + if File.exist?(SYSFS_BLOCK_DIRECTORY) + # Iterate over each file in the /sys/block/ directory and keep + # those that have a device subdirectory + Dir.entries(SYSFS_BLOCK_DIRECTORY).find_all do |f| + File.exist?(device_dir(f)) + end + else + [] + end + end + + def self.device_dir(device) + SYSFS_BLOCK_DIRECTORY + device + "/device" + end + + end + +end diff --git a/spec/fixtures/blockdevices/freebsd_atacontrol_cap_ad10 b/spec/fixtures/blockdevices/freebsd_atacontrol_cap_ad10 new file mode 100644 index 0000000000..142ecf1bf8 --- /dev/null +++ b/spec/fixtures/blockdevices/freebsd_atacontrol_cap_ad10 @@ -0,0 +1,22 @@ +Protocol ATA/ATAPI revision 4 +device model WDC WD1003FBYX-01Y7B 0/01.01V01 +serial number 00000000000000000001 +firmware revision 00000001 +cylinders 8322 +heads 16 +sectors/track 63 +lba supported 8388608 sectors +lba48 not supported +dma supported +overlap not supported + +Feature Support Enable Value Vendor +write cache no no +read ahead no no +Tagged Command Queuing (TCQ) no no 0/0x00 +SMART no no +microcode download no no +security no no +power management yes yes +advanced power management yes no 16384/0x4000 +automatic acoustic management no no 0/0x00 0/0x00 diff --git a/spec/fixtures/blockdevices/freebsd_camcontrol_identify_ada0 b/spec/fixtures/blockdevices/freebsd_camcontrol_identify_ada0 new file mode 100644 index 0000000000..1e09a5b65c --- /dev/null +++ b/spec/fixtures/blockdevices/freebsd_camcontrol_identify_ada0 @@ -0,0 +1,37 @@ +pass0: ATA-8 SATA 3.x device +pass0: 600.000MB/s transfers (SATA 3.x, UDMA6, PIO 8192bytes) + +protocol ATA/ATAPI-8 SATA 3.x +device model Corsair Force GT +firmware revision 1.3.3 +serial number 123 +WWN 0000000000000000 +cylinders 16383 +heads 16 +sectors/track 63 +sector size logical 512, physical 512, offset 0 +LBA supported 234441648 sectors +LBA48 supported 234441648 sectors +PIO supported PIO4 +DMA supported WDMA2 UDMA6 +media RPM non-rotating + +Feature Support Enabled Value Vendor +read ahead yes yes +write cache yes yes +flush cache yes yes +overlap no +Tagged Command Queuing (TCQ) no no +Native Command Queuing (NCQ) yes 32 tags +SMART yes yes +microcode download yes yes +security yes no +power management yes yes +advanced power management yes yes 254/0xFE +automatic acoustic management no no +media status notification no no +power-up in Standby yes no +write-read-verify yes no 0/0x0 +unload yes yes +free-fall no no +data set management (TRIM) yes diff --git a/spec/fixtures/blockdevices/freebsd_diskinfo_ad10 b/spec/fixtures/blockdevices/freebsd_diskinfo_ad10 new file mode 100644 index 0000000000..988aa3a3fa --- /dev/null +++ b/spec/fixtures/blockdevices/freebsd_diskinfo_ad10 @@ -0,0 +1,10 @@ +ad10 + 512 # sectorsize + 1000204886016 # mediasize in bytes (100G) + 123 # mediasize in sectors + 0 # stripesize + 0 # stripeoffset + 321 # Cylinders according to firmware. + 16 # Heads according to firmware. + 63 # Sectors according to firmware. + my ident # Disk ident. diff --git a/spec/fixtures/blockdevices/freebsd_diskinfo_ada0 b/spec/fixtures/blockdevices/freebsd_diskinfo_ada0 new file mode 100644 index 0000000000..2c5dc86db2 --- /dev/null +++ b/spec/fixtures/blockdevices/freebsd_diskinfo_ada0 @@ -0,0 +1,10 @@ +ada0 + 512 # sectorsize + 120034123776 # mediasize in bytes (111G) + 234441648 # mediasize in sectors + 0 # stripesize + 0 # stripeoffset + 232581 # Cylinders according to firmware. + 16 # Heads according to firmware. + 63 # Sectors according to firmware. + 12118201000006890700 # Disk ident. diff --git a/spec/fixtures/blockdevices/freebsd_diskinfo_da0 b/spec/fixtures/blockdevices/freebsd_diskinfo_da0 new file mode 100644 index 0000000000..e234f33532 --- /dev/null +++ b/spec/fixtures/blockdevices/freebsd_diskinfo_da0 @@ -0,0 +1,10 @@ +da0 + 512 # sectorsize + 73407865856 # mediasize in bytes (68G) + 143374738 # mediasize in sectors + 0 # stripesize + 0 # stripeoffset + 8924 # Cylinders according to firmware. + 255 # Heads according to firmware. + 63 # Sectors according to firmware. + XY007463 0509 # Disk ident. diff --git a/spec/fixtures/blockdevices/freebsd_diskinfo_mfi0 b/spec/fixtures/blockdevices/freebsd_diskinfo_mfi0 new file mode 100644 index 0000000000..d4aa851dc8 --- /dev/null +++ b/spec/fixtures/blockdevices/freebsd_diskinfo_mfi0 @@ -0,0 +1,7 @@ +mfi0 + 512 # sectorsize + 1000000000000 # mediasize in bytes (1.0T) + 123 # mediasize in sectors + 321 # Cylinders according to firmware. + 255 # Heads according to firmware. + 63 # Sectors according to firmware. diff --git a/spec/unit/blockdevices_spec.rb b/spec/unit/blockdevices_spec.rb index 167b546e0d..9c67f5ea13 100644 --- a/spec/unit/blockdevices_spec.rb +++ b/spec/unit/blockdevices_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' require 'facter' +require 'facter/util/blockdevices' require 'facter/util/nothing_loader' describe "Block device facts" do @@ -22,15 +23,26 @@ Facter::Util::Resolution.stubs(:exec).with("/sbin/sysctl -n kern.disks").returns("cd0 da0 ada0 ad10 mfi0") - Facter::Util::Resolution.stubs(:exec).with("/sbin/camcontrol inquiry cd0 -D | /usr/bin/awk -F '<|>' '{ print $2 }'").returns("TEAC DV-28E-N 1.6A") - Facter::Util::Resolution.stubs(:exec).with("/sbin/camcontrol inquiry da0 -D | /usr/bin/awk -F '<|>' '{ print $2 }'").returns("HP 73.4G MAU3073NC HPC2") - Facter::Util::Resolution.stubs(:exec).with("/sbin/camcontrol identify ada0 | /usr/bin/awk -F 'device\ model\ *' '/device model/ { print $2 }'").returns("Corsair Force GT") - Facter::Util::Resolution.stubs(:exec).with("/sbin/atacontrol list | /usr/bin/awk -F '<|>' '/ad10/ { print $2 }'").returns("WDC WD1003FBYX-01Y7B0/01.01V01") + Facter::Util::Resolution.stubs(:exec).with("/sbin/camcontrol inquiry cd0 -D").returns("pass0: Removable CD-ROM SCSI-0 device") + Facter::Util::Resolution.stubs(:exec).with("/sbin/camcontrol inquiry da0 -D").returns("pass1: Fixed Direct Access SCSI-3 device") - Facter::Util::Resolution.stubs(:exec).with("/usr/sbin/diskinfo da0 | /usr/bin/awk '{ print $3 }'").returns("73407865856") - Facter::Util::Resolution.stubs(:exec).with("/usr/sbin/diskinfo ada0 | /usr/bin/awk '{ print $3 }'").returns("120034123776") - Facter::Util::Resolution.stubs(:exec).with("/usr/sbin/diskinfo ad10 | /usr/bin/awk '{ print $3 }'").returns("1000204886016") - Facter::Util::Resolution.stubs(:exec).with("/usr/sbin/diskinfo mfi0 | /usr/bin/awk '{ print $3 }'").returns("1000000000000") + sample_atacontrol_cap = File.read(fixtures('blockdevices','freebsd_atacontrol_cap_ad10')) + Facter::Util::Resolution.stubs(:exec).with("/sbin/atacontrol cap ad10").returns(sample_atacontrol_cap) + + sample_camcontrol_identify = File.read(fixtures('blockdevices','freebsd_camcontrol_identify_ada0')) + Facter::Util::Resolution.stubs(:exec).with("/sbin/camcontrol identify ada0").returns(sample_camcontrol_identify) + + sample_diskinfo_da0 = File.read(fixtures('blockdevices','freebsd_diskinfo_da0')) + Facter::Util::Resolution.stubs(:exec).with("/usr/sbin/diskinfo -v da0").returns(sample_diskinfo_da0) + + sample_diskinfo_ada0 = File.read(fixtures('blockdevices','freebsd_diskinfo_ada0')) + Facter::Util::Resolution.stubs(:exec).with("/usr/sbin/diskinfo -v ada0").returns(sample_diskinfo_ada0) + + sample_diskinfo_ad10 = File.read(fixtures('blockdevices','freebsd_diskinfo_ad10')) + Facter::Util::Resolution.stubs(:exec).with("/usr/sbin/diskinfo -v ad10").returns(sample_diskinfo_ad10) + + sample_diskinfo_mfi0 = File.read(fixtures('blockdevices','freebsd_diskinfo_mfi0')) + Facter::Util::Resolution.stubs(:exec).with("/usr/sbin/diskinfo -v mfi0").returns(sample_diskinfo_mfi0) end describe 'blockdevices fact' do @@ -59,13 +71,13 @@ :size => "73407865856", }, 'ada0' => { - :model => "Force GT", + :model => "Force GT 1.3.3", :vendor => "Corsair", :size => "120034123776", }, 'ad10' => { - :model => "WD1003FBYX-01Y7B0/01.01V01", - :vendor => "WDC", + :model => "WDC WD1003FBYX-01Y7B 0/01.01V01", + :vendor => "ATA", :size => "1000204886016", }, 'mfi0' => { @@ -126,16 +138,16 @@ # handle facts that should not exist %w{ . .. hda }.each do |device| - Facter.fact("blockdevice_#{device}_size".to_sym).should == nil - Facter.fact("blockdevice_#{device}_vendor".to_sym).should == nil - Facter.fact("blockdevice_#{device}_model".to_sym).should == nil + Facter.value("blockdevice_#{device}_size".to_sym).should == nil + Facter.value("blockdevice_#{device}_vendor".to_sym).should == nil + Facter.value("blockdevice_#{device}_model".to_sym).should == nil end # handle facts that should exist %w{ sda sdb }.each do |device| - Facter.fact("blockdevice_#{device}_size".to_sym).should_not == nil - Facter.fact("blockdevice_#{device}_vendor".to_sym).should_not == nil - Facter.fact("blockdevice_#{device}_model".to_sym).should_not == nil + Facter.value("blockdevice_#{device}_size".to_sym).should_not == nil + Facter.value("blockdevice_#{device}_vendor".to_sym).should_not == nil + Facter.value("blockdevice_#{device}_model".to_sym).should_not == nil end Facter.fact(:blockdevice_sda_model).value.should == "WDC WD5000AAKS-0" From ed28b9382d8479330611cb5c52aa90662c0f9899 Mon Sep 17 00:00:00 2001 From: Jared Curtis Date: Fri, 5 Apr 2013 15:49:03 -0700 Subject: [PATCH 1218/3753] Ticket #18175 Wrong operatingsystem reported for Xen Cloud Platform Facter will now return XCP for the fact operatingsystem when /etc/redhat-release contains the XCP version string. --- lib/facter/operatingsystem.rb | 2 ++ spec/unit/operatingsystem_spec.rb | 1 + 2 files changed, 3 insertions(+) diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index f8e666e6ab..c36a2bb4a8 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -78,6 +78,8 @@ "Ascendos" elsif txt =~ /^XenServer/i "XenServer" + elsif txt =~ /XCP/ + "XCP" else "RedHat" end diff --git a/spec/unit/operatingsystem_spec.rb b/spec/unit/operatingsystem_spec.rb index 794b9cadc5..b5fa77af9e 100755 --- a/spec/unit/operatingsystem_spec.rb +++ b/spec/unit/operatingsystem_spec.rb @@ -101,6 +101,7 @@ "Ascendos" => "Ascendos release 6.0 (Nameless)", "CloudLinux" => "CloudLinux Server release 5.5", "XenServer" => "XenServer release 5.6.0-31188p (xenenterprise)", + "XCP" => "XCP release 1.6.10-61809c", }.each_pair do |operatingsystem, string| it "should be #{operatingsystem} based on /etc/redhat-release contents #{string}" do FileTest.expects(:exists?).with("/etc/redhat-release").returns true From 7f3c59263c448e01aa22d659750bd48c213ad2b2 Mon Sep 17 00:00:00 2001 From: Garrett Honeycutt Date: Fri, 12 Apr 2013 19:37:14 +0200 Subject: [PATCH 1219/3753] Add the lsbminordistrelease fact Without this patch there is not an easy way to get the minor versions from the LSB Distribution Release version without resorting to using split or other such functions. This patch addresses the problem by adding a new fact, `lsbminordistrelease` which directly contains the minor version parsed out of the full lsbdistrelease version. This fact will be defined whenever the lsbdistrelease fact is defined. --- lib/facter/lsbminordistrelease.rb | 25 +++++++++++++++++ spec/unit/lsbminordistrelease_spec.rb | 40 +++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 lib/facter/lsbminordistrelease.rb create mode 100755 spec/unit/lsbminordistrelease_spec.rb diff --git a/lib/facter/lsbminordistrelease.rb b/lib/facter/lsbminordistrelease.rb new file mode 100644 index 0000000000..bebee5fc57 --- /dev/null +++ b/lib/facter/lsbminordistrelease.rb @@ -0,0 +1,25 @@ +# Fact: lsbminordistrelease +# +# Purpose: Returns the minor version of the operation system version as gleaned +# from the lsbdistrelease fact. +# +# Resolution: +# Parses the lsbdistrelease fact for x.y and returns y. If y is not present, +# the fact is not present. +# +# For both values '1.2.3' and '1.2' of lsbdistrelease, lsbminordistrelease +# would return '2'. For the value '1', no fact would be set for +# lsbminordistrelease. +# +require 'facter' + +Facter.add('lsbminordistrelease') do + confine(:lsbdistrelease) {|ver| !!ver } + + regexp = /\d+\.(\d+)/ + + setcode do + mdata = regexp.match(Facter.value(:lsbdistrelease)) + mdata[1] if mdata + end +end diff --git a/spec/unit/lsbminordistrelease_spec.rb b/spec/unit/lsbminordistrelease_spec.rb new file mode 100755 index 0000000000..38b5a402bc --- /dev/null +++ b/spec/unit/lsbminordistrelease_spec.rb @@ -0,0 +1,40 @@ +#!/usr/bin/env ruby +# +# Copyright (C) 2013 Garrett Honeycutt +# +require 'spec_helper' +require 'facter' + +describe 'lsbminordistrelease fact' do + def stub_version(ver) + Facter.fact(:lsbdistrelease).stubs(:value).returns(ver) + Facter.collection.internal_loader.load(:lsbminordistrelease) + end + context 'lsbdistrelease is defined' do + it 'is 4 when lsbdistrelease is 6.4' do + stub_version('6.4') + Facter.fact(:lsbminordistrelease).value.should == '4' + end + + it 'is 4 when lsbdistrelease is 6.4.1' do + stub_version('6.4.1') + Facter.fact(:lsbminordistrelease).value.should == '4' + end + + it 'is 14 when lsbdistrelease is 6.14.1' do + stub_version('6.14.1') + Facter.fact(:lsbminordistrelease).value.should == '14' + end + end + + context 'lsbdistrelease is not defined' do + it 'is not defined when lsbminordistrelease is false' do + stub_version(false) + Facter.fact(:lsbminordistrelease).value.should be_nil + end + it 'is not defined when lsbminordistrelease is nil' do + stub_version(nil) + Facter.fact(:lsbminordistrelease).value.should be_nil + end + end +end From a89c47bf264fad621fe9afee448ffe92d7dffeca Mon Sep 17 00:00:00 2001 From: Garrett Honeycutt Date: Fri, 12 Apr 2013 21:16:35 +0200 Subject: [PATCH 1220/3753] (maint) Clean up the lsbmajdistrelease fact This change changes the style and confinement of the lsbmajdistrelease fact to match that of the lsbminordistrelease fact. --- lib/facter/lsbmajdistrelease.rb | 7 +------ spec/unit/lsbmajdistrelease_spec.rb | 26 ++++++++++++++++++-------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/lib/facter/lsbmajdistrelease.rb b/lib/facter/lsbmajdistrelease.rb index a40441a038..bc185d2355 100644 --- a/lib/facter/lsbmajdistrelease.rb +++ b/lib/facter/lsbmajdistrelease.rb @@ -7,14 +7,9 @@ # Parses the lsbdistrelease fact for numbers followed by a period and # returns those, or just the lsbdistrelease fact if none were found. # -# Caveats: -# - -# lsbmajdistrelease.rb -# require 'facter' -Facter.add("lsbmajdistrelease") do +Facter.add('lsbmajdistrelease') do confine :kernel => %w{Linux GNU/kFreeBSD} setcode do if /(\d*)\./i =~ Facter.value(:lsbdistrelease) diff --git a/spec/unit/lsbmajdistrelease_spec.rb b/spec/unit/lsbmajdistrelease_spec.rb index 4397c02f8f..e15275585d 100755 --- a/spec/unit/lsbmajdistrelease_spec.rb +++ b/spec/unit/lsbmajdistrelease_spec.rb @@ -1,13 +1,23 @@ #!/usr/bin/env ruby - +# +# Refactor - Copyright (C) 2013 Garrett Honeycutt Date: Mon, 15 Apr 2013 10:28:32 -0400 Subject: [PATCH 1221/3753] Fixup --- lib/facter/lsbmajdistrelease.rb | 16 +++++++-------- lib/facter/lsbminordistrelease.rb | 2 -- spec/unit/lsbmajdistrelease_spec.rb | 30 ++++++++++++++--------------- 3 files changed, 22 insertions(+), 26 deletions(-) diff --git a/lib/facter/lsbmajdistrelease.rb b/lib/facter/lsbmajdistrelease.rb index bc185d2355..1bd030bd45 100644 --- a/lib/facter/lsbmajdistrelease.rb +++ b/lib/facter/lsbmajdistrelease.rb @@ -7,16 +7,14 @@ # Parses the lsbdistrelease fact for numbers followed by a period and # returns those, or just the lsbdistrelease fact if none were found. # -require 'facter' - Facter.add('lsbmajdistrelease') do - confine :kernel => %w{Linux GNU/kFreeBSD} + confine(:lsbdistrelease) {|ver| !!ver } + + regexp = /(\d+)\./ + setcode do - if /(\d*)\./i =~ Facter.value(:lsbdistrelease) - result=$1 - else - result=Facter.value(:lsbdistrelease) - end - result + lsbdistrelease = Facter.value(:lsbdistrelease) + mdata = regexp.match(lsbdistrelease) + mdata ? mdata[1] : lsbdistrelease end end diff --git a/lib/facter/lsbminordistrelease.rb b/lib/facter/lsbminordistrelease.rb index bebee5fc57..0a0e990d26 100644 --- a/lib/facter/lsbminordistrelease.rb +++ b/lib/facter/lsbminordistrelease.rb @@ -11,8 +11,6 @@ # would return '2'. For the value '1', no fact would be set for # lsbminordistrelease. # -require 'facter' - Facter.add('lsbminordistrelease') do confine(:lsbdistrelease) {|ver| !!ver } diff --git a/spec/unit/lsbmajdistrelease_spec.rb b/spec/unit/lsbmajdistrelease_spec.rb index e15275585d..d1135d224d 100755 --- a/spec/unit/lsbmajdistrelease_spec.rb +++ b/spec/unit/lsbmajdistrelease_spec.rb @@ -1,23 +1,23 @@ -#!/usr/bin/env ruby -# -# Refactor - Copyright (C) 2013 Garrett Honeycutt Date: Mon, 15 Apr 2013 10:27:05 -0700 Subject: [PATCH 1222/3753] (packaging) Update FACTERVERSION to 1.7.0 --- lib/facter/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/version.rb b/lib/facter/version.rb index 9aa5a490cb..d63795fe6d 100644 --- a/lib/facter/version.rb +++ b/lib/facter/version.rb @@ -1,6 +1,6 @@ module Facter if not defined? FACTERVERSION then - FACTERVERSION = '1.7.0-rc2' + FACTERVERSION = '1.7.0' end ## From 1ad5ba7005094502f5a171a4c32f7d595018fc27 Mon Sep 17 00:00:00 2001 From: Matthaus Owens Date: Mon, 15 Apr 2013 15:35:20 -0700 Subject: [PATCH 1223/3753] (packaging) Add raring to the default cows list Raring will be released near the end of April, so this commit adds it to the list of cows to build against. --- ext/build_defaults.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/build_defaults.yaml b/ext/build_defaults.yaml index 1ea92d3dfc..c9d68b284f 100644 --- a/ext/build_defaults.yaml +++ b/ext/build_defaults.yaml @@ -2,7 +2,7 @@ packaging_url: 'git://github.com/puppetlabs/packaging.git --branch=master' packaging_repo: 'packaging' default_cow: 'base-squeeze-i386.cow' -cows: 'base-lucid-i386.cow base-lucid-amd64.cow base-oneiric-i386.cow base-oneiric-amd64.cow base-precise-i386.cow base-precise-amd64.cow base-quantal-i386.cow base-quantal-amd64.cow base-sid-i386.cow base-sid-amd64.cow base-squeeze-i386.cow base-squeeze-amd64.cow base-stable-i386.cow base-stable-amd64.cow base-testing-i386.cow base-testing-amd64.cow base-unstable-i386.cow base-unstable-amd64.cow base-wheezy-i386.cow base-wheezy-amd64.cow' +cows: 'base-lucid-i386.cow base-lucid-amd64.cow base-oneiric-i386.cow base-oneiric-amd64.cow base-precise-i386.cow base-precise-amd64.cow base-quantal-i386.cow base-quantal-amd64.cow base-raring-i386.cow base-raring-amd64.cow base-sid-i386.cow base-sid-amd64.cow base-squeeze-i386.cow base-squeeze-amd64.cow base-stable-i386.cow base-stable-amd64.cow base-testing-i386.cow base-testing-amd64.cow base-unstable-i386.cow base-unstable-amd64.cow base-wheezy-i386.cow base-wheezy-amd64.cow' pbuild_conf: '/etc/pbuilderrc' packager: 'puppetlabs' gpg_name: 'info@puppetlabs.com' From e8590a69442455f8c85852438aa35f095deea3bf Mon Sep 17 00:00:00 2001 From: Matthaus Owens Date: Mon, 15 Apr 2013 15:35:32 -0700 Subject: [PATCH 1224/3753] (packaging) Remove f16 from mocks to build as it is EOL Fedora 16 went EOL 02-12-2013, so this commit removes it from the mocks to be built against. --- ext/build_defaults.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/build_defaults.yaml b/ext/build_defaults.yaml index c9d68b284f..6d550ad084 100644 --- a/ext/build_defaults.yaml +++ b/ext/build_defaults.yaml @@ -9,7 +9,7 @@ gpg_name: 'info@puppetlabs.com' gpg_key: '4BD6EC30' sign_tar: FALSE # a space separated list of mock configs -final_mocks: 'pl-el-5-i386 pl-el-5-x86_64 pl-el-6-i386 pl-el-6-x86_64 pl-fedora-16-i386 pl-fedora-16-x86_64 pl-fedora-17-i386 pl-fedora-17-x86_64 pl-fedora-18-i386 pl-fedora-18-x86_64' +final_mocks: 'pl-el-5-i386 pl-el-5-x86_64 pl-el-6-i386 pl-el-6-x86_64 pl-fedora-17-i386 pl-fedora-17-x86_64 pl-fedora-18-i386 pl-fedora-18-x86_64' yum_host: 'burji.puppetlabs.com' yum_repo_path: '/opt/repository/yum/' build_gem: TRUE From 819c8dbca15c2ea0a4056aaf246c35b1b7f3517f Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Tue, 16 Apr 2013 11:27:30 -0400 Subject: [PATCH 1225/3753] (#14522) Force /proc/self/status encoding to valid UTF-8 (try 2) Without this patch the previous attempt to fix issue 14522 is insufficient because the String#encode method is a no-op when the source and destination encodings are the same encoding, even if there are invalid byte sequences. This is a problem because we're still getting unhandled `invalid byte sequence in UTF-8` errors running the specs. This patch addresses the problem by changing encoding from UTF-8 to UTF-16, replacing all invalid byte sequences with the default unicode string of "uFFFD" We then convert back to UTF-8 to guarantee only valid byte sequences remain. --- lib/facter/util/virtual.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/facter/util/virtual.rb b/lib/facter/util/virtual.rb index af192dadc6..a2457f5194 100644 --- a/lib/facter/util/virtual.rb +++ b/lib/facter/util/virtual.rb @@ -63,7 +63,8 @@ def self.vserver? return false unless FileTest.exists?("/proc/self/status") txt = File.open("/proc/self/status", "rb").read if txt.respond_to?(:encode!) - txt.encode!('UTF-8', 'UTF-8', :invalid => :replace) + txt.encode!('UTF-16', 'UTF-8', :invalid => :replace) + txt.encode!('UTF-8', 'UTF-16') end return true if txt =~ /^(s_context|VxID):[[:blank:]]*[0-9]/ return false From d54c86abfba35f48cad78bb99b1d199afe8c2095 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Tue, 16 Apr 2013 11:27:30 -0400 Subject: [PATCH 1226/3753] (#14522) Force /proc/self/status encoding to valid UTF-8 (try 2) Without this patch the previous attempt to fix issue 14522 is insufficient because the String#encode method is a no-op when the source and destination encodings are the same encoding, even if there are invalid byte sequences. This is a problem because we're still getting unhandled `invalid byte sequence in UTF-8` errors running the specs. This patch addresses the problem by changing encoding from UTF-8 to UTF-16, replacing all invalid byte sequences with the default unicode string of "uFFFD" We then convert back to UTF-8 to guarantee only valid byte sequences remain. --- lib/facter/util/virtual.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/facter/util/virtual.rb b/lib/facter/util/virtual.rb index 8223b41141..65cf58a645 100644 --- a/lib/facter/util/virtual.rb +++ b/lib/facter/util/virtual.rb @@ -63,7 +63,8 @@ def self.vserver? return false unless FileTest.exists?("/proc/self/status") txt = File.open("/proc/self/status", "rb").read if txt.respond_to?(:encode!) - txt.encode!('UTF-8', 'UTF-8', :invalid => :replace) + txt.encode!('UTF-16', 'UTF-8', :invalid => :replace) + txt.encode!('UTF-8', 'UTF-16') end return true if txt =~ /^(s_context|VxID):[[:blank:]]*[0-9]/ return false From db5281e191a62ff779bd24042b31232bd5f43a5c Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Mon, 15 Apr 2013 18:30:29 -0400 Subject: [PATCH 1227/3753] (#20236) Add example that reproduces dmidecode issue Without this patch we don't have an example that reproduces the issue described in 20236. This is a problem because we don't automatically know when we've fixed the issue. This patch addresses the problem by adding a single example that reproduces the issue described in 20236; dmidecode is not consulted when the lspci command returns data but does not result in a determination of the virtual platform. --- spec/unit/virtual_spec.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index 855da9d4a1..c78870ba53 100755 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -205,6 +205,12 @@ end end end + + it "(#20236) is vmware when dmidecode contains vmware and lspci returns insufficient information", :focus => true do + Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns("garbage\ninformation\n") + Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("On Board Device 1 Information\nType: Video\nStatus: Disabled\nDescription: VMware SVGA II") + expect(Facter.fact(:virtual).value).to eq("vmware") + end end describe "on Solaris" do From ef8db187e4e1a6ec906dc73459a1feff2be40c9c Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Mon, 15 Apr 2013 22:13:47 -0400 Subject: [PATCH 1228/3753] (#20236) Refactor the virtual fact and fix dmidecode Without this patch there are a ridiculous number of problems with the virtual fact. This patch reduces the number of problems to something more along the lines of an absurd number of problems. The major problem this patch addresses is that a single local variable, `result`, is used to store the determined-thus-far value of the virtual fact. Sometimes this value is checked to see if it's still at the initial value of 'physical'. Only sometimes, however, most of the time subsequent platform checks blindly overwrite the value even if we've already made a determination. This patch addresses the problem by using the `next` keyword to return a value from the block passed to `setcode` as soon as a value is determined. Only if all attempts have failed do we resort to returning 'physical' This patch completely eliminates the use of the `result` accumulator that is constantly being overwritten. As part of this change in behavior, the bug described in #20236 is resolved because we no longer fail to check `dmidecode` if the output of `lspci` does not result in a determination. Surprisingly, the spec tests pass with almost no modification, the one exception being an unexpected invocation on a mocked object resulting from a confinement check added by this patch. This leads me to believe this is a true refactor, or as close to one as we're going to get. The patch does change the behavior substantially. If multiple results could be determined without this patch applied then Facter would return the value of the last determination. With this patch applied Facter will return the first determined result. It's unclear which behavior is expected of the system, the spec tests do not express an expected behavior in this scenario. The comments at the top of virtual.rb lead us to believe the first result should be returned, implying this patch gets us a little closer to correct behavior. I suspect there will be issues filed regardless of which way we go. There certainly have been no end to the number of issues filed against the virtual fact thus far. --- lib/facter/virtual.rb | 201 ++++++++++++++++++-------------------- spec/unit/virtual_spec.rb | 1 + 2 files changed, 95 insertions(+), 107 deletions(-) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 0a68e14596..72ef9fb6e6 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -44,129 +44,116 @@ end end - Facter.add("virtual") do - confine :kernel => %w{Linux FreeBSD OpenBSD SunOS HP-UX GNU/kFreeBSD} - - result = "physical" + confine :kernel => ["FreeBSD", "GNU/kFreeBSD"] setcode do + "jail" if Facter::Util::Virtual.jail? + end +end - if Facter.value(:kernel) == "SunOS" and Facter::Util::Virtual.zone? - result = "zone" - end - - if Facter.value(:kernel)=="HP-UX" - result = "hpvm" if Facter::Util::Virtual.hpvm? - end - - if Facter.value(:architecture)=="s390x" - result = "zlinux" if Facter::Util::Virtual.zlinux? - end - - if Facter::Util::Virtual.openvz? - result = Facter::Util::Virtual.openvz_type() - end +Facter.add("virtual") do + confine :kernel => 'SunOS' - if Facter::Util::Virtual.vserver? - result = Facter::Util::Virtual.vserver_type() + setcode do + next "zone" if Facter::Util::Virtual.zone? + + resolver = Facter::Util::Resolution.new('prtdiag') + resolver.timeout = 6 + resolver.setcode('prtdiag') + output = resolver.value + if output + lines = output.split("\n") + next "parallels" if lines.any? {|l| l =~ /Parallels/ } + next "vmware" if lines.any? {|l| l =~ /VM[wW]are/ } + next "virtualbox" if lines.any? {|l| l =~ /VirtualBox/ } + next "xenhvm" if lines.any? {|l| l =~ /HVM domU/ } end + end +end - if Facter::Util::Virtual.xen? - if FileTest.exists?("/dev/xen/evtchn") - result = "xen0" - elsif FileTest.exists?("/proc/xen") - result = "xenu" - end - end +Facter.add("virtual") do + confine :kernel => 'HP-UX' - if Facter::Util::Virtual.virtualbox? - result = "virtualbox" - end + setcode do + "hpvm" if Facter::Util::Virtual.hpvm? + end +end - if Facter::Util::Virtual.kvm? - result = Facter::Util::Virtual.kvm_type() - end +Facter.add("virtual") do + confine :architecture => 's390x' - if ["FreeBSD", "GNU/kFreeBSD"].include? Facter.value(:kernel) - result = "jail" if Facter::Util::Virtual.jail? - end + setcode do + "zlinux" if Facter::Util::Virtual.zlinux? + end +end - if Facter::Util::Virtual.rhev? - result = "rhev" - end +Facter.add("virtual") do + confine :kernel => 'OpenBSD' - if Facter::Util::Virtual.ovirt? - result = "ovirt" + setcode do + output = Facter::Util::Resolution.exec('sysctl -n hw.product 2>/dev/null') + if output + lines = output.split("\n") + next "parallels" if lines.any? {|l| l =~ /Parallels/ } + next "vmware" if lines.any? {|l| l =~ /VMware/ } + next "virtualbox" if lines.any? {|l| l =~ /VirtualBox/ } + next "xenhvm" if lines.any? {|l| l =~ /HVM domU/ } end + end +end - if result == "physical" - output = Facter::Util::Virtual.lspci - if not output.nil? - output.each_line do |p| - # --- look for the vmware video card to determine if it is virtual => vmware. - # --- 00:0f.0 VGA compatible controller: VMware Inc [VMware SVGA II] PCI Display Adapter - result = "vmware" if p =~ /VM[wW]are/ - # --- look for virtualbox video card to determine if it is virtual => virtualbox. - # --- 00:02.0 VGA compatible controller: InnoTek Systemberatung GmbH VirtualBox Graphics Adapter - result = "virtualbox" if p =~ /VirtualBox/ - # --- look for pci vendor id used by Parallels video card - # --- 01:00.0 VGA compatible controller: Unknown device 1ab8:4005 - result = "parallels" if p =~ /1ab8:|[Pp]arallels/ - # --- look for pci vendor id used by Xen HVM device - # --- 00:03.0 Unassigned class [ff80]: XenSource, Inc. Xen Platform Device (rev 01) - result = "xenhvm" if p =~ /XenSource/ - # --- look for Hyper-V video card - # --- 00:08.0 VGA compatible controller: Microsoft Corporation Hyper-V virtual VGA - result = "hyperv" if p =~ /Microsoft Corporation Hyper-V/ - # --- look for gmetrics for GCE - # --- 00:05.0 Class 8007: Google, Inc. Device 6442 - result = "gce" if p =~ /Class 8007: Google, Inc/ - end - else - output = Facter::Util::Resolution.exec('dmidecode') - if not output.nil? - output.each_line do |pd| - result = "parallels" if pd =~ /Parallels/ - result = "vmware" if pd =~ /VMware/ - result = "virtualbox" if pd =~ /VirtualBox/ - result = "xenhvm" if pd =~ /HVM domU/ - result = "hyperv" if pd =~ /Product Name: Virtual Machine/ - result = "rhev" if pd =~ /Product Name: RHEV Hypervisor/ - result = "ovirt" if pd =~ /Product Name: oVirt Node/ - end - elsif Facter.value(:kernel) == 'SunOS' - res = Facter::Util::Resolution.new('prtdiag') - res.timeout = 6 - res.setcode('prtdiag') - output = res.value - if not output.nil? - output.each_line do |pd| - result = "parallels" if pd =~ /Parallels/ - result = "vmware" if pd =~ /VMware/ - result = "virtualbox" if pd =~ /VirtualBox/ - result = "xenhvm" if pd =~ /HVM domU/ - end - end - elsif Facter.value(:kernel) == 'OpenBSD' - output = Facter::Util::Resolution.exec('sysctl -n hw.product 2>/dev/null') - if not output.nil? - output.each_line do |pd| - result = "parallels" if pd =~ /Parallels/ - result = "vmware" if pd =~ /VMware/ - result = "virtualbox" if pd =~ /VirtualBox/ - result = "xenhvm" if pd =~ /HVM domU/ - end - end - end - end +Facter.add("virtual") do + confine :kernel => %w{Linux FreeBSD OpenBSD SunOS HP-UX GNU/kFreeBSD} - if output = Facter::Util::Resolution.exec("vmware -v") - result = output.sub(/(\S+)\s+(\S+).*/) { | text | "#{$1}_#{$2}"}.downcase - end - end + setcode do + next Facter::Util::Virtual.openvz_type if Facter::Util::Virtual.openvz? + next Facter::Util::Virtual.vserver_type if Facter::Util::Virtual.vserver? - result + if Facter::Util::Virtual.xen? + next "xen0" if FileTest.exists?("/dev/xen/evtchn") + next "xenu" if FileTest.exists?("/proc/xen") + end + + next "virtualbox" if Facter::Util::Virtual.virtualbox? + next Facter::Util::Virtual.kvm_type if Facter::Util::Virtual.kvm? + next "rhev" if Facter::Util::Virtual.rhev? + next "ovirt" if Facter::Util::Virtual.ovirt? + + # Parse lspci + output = Facter::Util::Virtual.lspci + if output + lines = output.split("\n") + next "vmware" if lines.any? {|l| l =~ /VM[wW]are/ } + next "virtualbox" if lines.any? {|l| l =~ /VirtualBox/ } + next "parallels" if lines.any? {|l| l =~ /1ab8:|[Pp]arallels/ } + next "xenhvm" if lines.any? {|l| l =~ /XenSource/ } + next "hyperv" if lines.any? {|l| l =~ /Microsoft Corporation Hyper-V/ } + next "gce" if lines.any? {|l| l =~ /Class 8007: Google, Inc/ } + end + + # Parse dmidecode + output = Facter::Util::Resolution.exec('dmidecode') + if output + lines = output.split("\n") + next "parallels" if lines.any? {|l| l =~ /Parallels/ } + next "vmware" if lines.any? {|l| l =~ /VMware/ } + next "virtualbox" if lines.any? {|l| l =~ /VirtualBox/ } + next "xenhvm" if lines.any? {|l| l =~ /HVM domU/ } + next "hyperv" if lines.any? {|l| l =~ /Product Name: Virtual Machine/ } + next "rhev" if lines.any? {|l| l =~ /Product Name: RHEV Hypervisor/ } + next "ovirt" if lines.any? {|l| l =~ /Product Name: oVirt Node/ } + end + + # Sample output of vmware -v `VMware Server 1.0.5 build-80187` + output = Facter::Util::Resolution.exec("vmware -v") + if output + mdata = output.match /(\S+)\s+(\S+)/ + next "#{mdata[1]}_#{mdata[2]}".downcase if mdata + end + + # Default to 'physical' + next 'physical' end end diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index c78870ba53..4aadf0ef7f 100755 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -278,6 +278,7 @@ require 'facter/util/wmi' before do Facter.fact(:kernel).stubs(:value).returns("windows") + Facter.fact(:architecture).stubs(:value).returns("x64") end it "should be kvm with KVM model name from Win32_ComputerSystem" do From d4fb54850554d67bc3e16245331a684a79d1041c Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 16 Apr 2013 12:49:55 -0700 Subject: [PATCH 1229/3753] (maint) Use RSpec 2.9 expectations We have RSpec 2.11 listed in the project Gemfile but CI is still using 2.9. This commit switches the expectation syntax to the older version for compatibility. --- spec/unit/virtual_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index 4aadf0ef7f..6215e30e53 100755 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -206,10 +206,10 @@ end end - it "(#20236) is vmware when dmidecode contains vmware and lspci returns insufficient information", :focus => true do + it "(#20236) is vmware when dmidecode contains vmware and lspci returns insufficient information" do Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns("garbage\ninformation\n") Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("On Board Device 1 Information\nType: Video\nStatus: Disabled\nDescription: VMware SVGA II") - expect(Facter.fact(:virtual).value).to eq("vmware") + Facter.fact(:virtual).value.should eq("vmware") end end From c3a47a5a9f6f46c31103764765ba015dfee5cf35 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Wed, 17 Apr 2013 22:57:15 -0400 Subject: [PATCH 1230/3753] (#19764) Fix ipaddress issue scanning beyond the first address Without this patch the refactoring of the ipaddress fact in 0e514b8 changed the determination behavior unintentionally. This is a problem because the behavior change caused the setcode block to scan only one address in the output of ifconfig. If the first address is a loopback (127) address, then no additional addresses will be checked. This patch addresses the problem by restoring the behavior of splitting the output of `ifconfig` into lines, then scanning each line. If an address is matched that begins with 127, then scanning continues. Otherwise the matched address is determined to be the ipaddress. The fixture has been extracted from the description of the issue published in the comment at http://git.io/MbXKEg --- lib/facter/ipaddress.rb | 10 +++++++--- .../ifconfig_multiple_127_addresses.txt | 20 +++++++++++++++++++ spec/unit/ipaddress_spec.rb | 4 ++++ 3 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 spec/fixtures/unit/ipaddress/ifconfig_multiple_127_addresses.txt diff --git a/lib/facter/ipaddress.rb b/lib/facter/ipaddress.rb index ad6257c01f..b4ce2e8ea5 100644 --- a/lib/facter/ipaddress.rb +++ b/lib/facter/ipaddress.rb @@ -28,10 +28,14 @@ confine :kernel => :linux setcode do ip = nil - if output = Facter::Util::IP.exec_ifconfig(["2>/dev/null"]) + output = Facter::Util::IP.exec_ifconfig(["2>/dev/null"]) + if output regexp = /inet (?:addr:)?([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ - if match = regexp.match(output) - match[1] unless /^127/.match(match[1]) + output.split("\n").each do |line| + match = regexp.match(line) + if match + break match[1] unless /^127/.match(match[1]) + end end end end diff --git a/spec/fixtures/unit/ipaddress/ifconfig_multiple_127_addresses.txt b/spec/fixtures/unit/ipaddress/ifconfig_multiple_127_addresses.txt new file mode 100644 index 0000000000..9d2a0e9042 --- /dev/null +++ b/spec/fixtures/unit/ipaddress/ifconfig_multiple_127_addresses.txt @@ -0,0 +1,20 @@ +lo Link encap:Local Loopback + inet addr:127.0.0.1 Mask:255.0.0.0 + inet6 addr: ::1/128 Scope:Host + UP LOOPBACK RUNNING MTU:16436 Metric:1 + RX packets:9031461 errors:0 dropped:0 overruns:0 frame:0 + TX packets:9031461 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:0 + RX bytes:3205490447 (2.9 GiB) TX bytes:3205490447 (2.9 GiB) + +venet0 Link encap:UNSPEC HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 + inet addr:127.0.0.1 P-t-P:127.0.0.1 Bcast:0.0.0.0 Mask:255.255.255.255 + UP BROADCAST POINTOPOINT RUNNING NOARP MTU:1500 Metric:1 + RX packets:38161277 errors:0 dropped:0 overruns:0 frame:0 + TX packets:24601924 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:0 + RX bytes:3847964603 (3.5 GiB) TX bytes:5770630041 (5.3 GiB) + +venet0:1 Link encap:UNSPEC HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 + inet addr:10.0.222.20 P-t-P:10.0.222.20 Bcast:10.0.222.20 Mask:255.255.255.255 + UP BROADCAST POINTOPOINT RUNNING NOARP MTU:1500 Metric:1 diff --git a/spec/unit/ipaddress_spec.rb b/spec/unit/ipaddress_spec.rb index 714ec047e0..43325568e9 100755 --- a/spec/unit/ipaddress_spec.rb +++ b/spec/unit/ipaddress_spec.rb @@ -28,5 +28,9 @@ "Ubuntu 12.04", "10.87.80.110", "ifconfig_ubuntu_1204.txt" example_behavior_for "ifconfig output", "Fedora 17", "131.252.209.153", "ifconfig_net_tools_1.60.txt" + example_behavior_for "ifconfig output", + "Linux with multiple loopback addresses", + "10.0.222.20", + "ifconfig_multiple_127_addresses.txt" end end From 35af4ce7f5cd77a79e19d88f5e09d5a465574516 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Thu, 18 Apr 2013 22:59:31 -0700 Subject: [PATCH 1231/3753] (#20301) Handle different error in ruby 1.9 The NumberOfLogicalProcessor property of the Win32_Processor WMI class does not exist in the base win2003 install. Previously, we were trying to access the property, and then catching the resulting error if the property did not exist. In ruby 1.8, it would raise a RuntimeError, but in ruby 1.9, it raises a StandardError, causing the fact to blow up on ruby 1.9. This commit checks to see if the process object responds to that method, and only then does it try to call it. --- lib/facter/processor.rb | 4 ++-- spec/unit/processor_spec.rb | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb index 1d409eb687..1d11d07210 100644 --- a/lib/facter/processor.rb +++ b/lib/facter/processor.rb @@ -117,9 +117,9 @@ # get each physical processor Facter::Util::WMI.execquery("select * from Win32_Processor").each do |proc| # not supported before 2008 - begin + if proc.respond_to?(:NumberOfLogicalProcessors) processor_num = proc.NumberOfLogicalProcessors - rescue RuntimeError => e + else processor_num = 1 end diff --git a/spec/unit/processor_spec.rb b/spec/unit/processor_spec.rb index 31b336d820..1ed6935cbe 100755 --- a/spec/unit/processor_spec.rb +++ b/spec/unit/processor_spec.rb @@ -29,13 +29,14 @@ def load(procs) describe "2003" do before :each do proc = stubs 'proc' - proc.stubs(:NumberOfLogicalProcessors).raises(RuntimeError) proc.stubs(:Name).returns("Intel(R) Celeron(R) processor") load(Array.new(2, proc)) end it "should count 2 processors" do + proc.expects(:NumberOfLogicalProcessors).never + Facter.fact(:processorcount).value.should == "2" end From b8582975737522c9b89f74f6077894cd3f4f58ca Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Mon, 22 Apr 2013 17:04:20 -0700 Subject: [PATCH 1232/3753] (maint) Move facter.gemspec to .gemspec So that people don't get confused. --- facter.gemspec => .gemspec | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename facter.gemspec => .gemspec (100%) diff --git a/facter.gemspec b/.gemspec similarity index 100% rename from facter.gemspec rename to .gemspec From 28408fb0e42dede8382b76e324ed0675a0020dc4 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Mon, 22 Apr 2013 17:08:12 -0700 Subject: [PATCH 1233/3753] (maint) Make facter gemspec report correct version Without this patch the Gem system thinks that the version of Facter running from a development branch is `1.6.11` even though many versions have been released since then. This is a problem because a project that requires Facter >= 1.7.0 should be able to satisfy this dependency using the `stable` development branch, however it cannot because the version information is out of date. This patch addresses the problem by using Facter's public version API to determine the version of Facter contained in the git branch. This patch also attempts to make it clear to the reader that the gemspec is intended for use with the VCS branches under active development and _not_ as the way to produce a gem release package. --- .gemspec | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/.gemspec b/.gemspec index d2ec27b6f4..c6f1b27d32 100644 --- a/.gemspec +++ b/.gemspec @@ -1,8 +1,28 @@ # -*- encoding: utf-8 -*- +# +# PLEASE NOTE +# This gemspec is not intended to be used for building the Facter gem. This +# gemspec is intended for use with bundler when Facter is a dependency of +# another project. For example, the stdlib project is able to integrate with +# the master branch of Facter by using a Gemfile path of +# git://github.com/puppetlabs/facter.git +# +# Please see the [packaging +# repository](https://github.com/puppetlabs/packaging) for information on how +# to build the Puppet gem package. + +begin + require 'facter/version' +rescue LoadError + $LOAD_PATH.unshift(File.expand_path("../lib", __FILE__)) + require 'facter/version' +end Gem::Specification.new do |s| s.name = "facter" - s.version = "1.6.11" + version = Facter.version + mdata = version.match(/(\d+\.\d+\.\d+)/) + s.version = mdata ? mdata[1] : version s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Puppet Labs"] From e5b8cd5457ef39720d60fd04907d3ce2e78b1ba0 Mon Sep 17 00:00:00 2001 From: Stefan Schulte Date: Sat, 20 Apr 2013 15:24:59 +0200 Subject: [PATCH 1234/3753] (#20321) Be more descriptive in deprecation message Facter::Util::Resoultion.exec can be used to run arbitrary commands. On windows it is currently allowed to pass a shell builtin but we raise a deprecation warning. But the deprecation warning did not include the actual commandline it is complaining about so if you have custom facts that make use of this feature you may ask yourself why you see the message. Add the actual command to the deprecation message so it easier to resolve the issue. --- lib/facter/util/resolution.rb | 2 +- spec/unit/util/resolution_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 49d355da4d..9205abe929 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -169,7 +169,7 @@ def self.exec(code, interpreter = nil) begin out = %x{#{code}}.chomp - Facter.warnonce 'Using Facter::Util::Resolution.exec with a shell built-in is deprecated. Most built-ins can be replaced with native ruby commands. If you really have to run a built-in, pass "cmd /c your_builtin" as a command' unless expanded_code + Facter.warnonce "Using Facter::Util::Resolution.exec with a shell built-in is deprecated. Most built-ins can be replaced with native ruby commands. If you really have to run a built-in, pass \"cmd /c your_builtin\" as a command (command responsible for this message was \"#{code}\")" unless expanded_code rescue Errno::ENOENT => detail # command not found on Windows return nil diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index a989dd8613..53d10839f6 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -685,7 +685,7 @@ class FlushFakeError < StandardError; end it "should try to run the command and return output of a shell-builtin" do Facter::Util::Resolution.expects(:expand_command).with(%q{echo foo}).returns nil Facter::Util::Resolution.expects(:`).with(%q{echo foo}).returns 'foo' - Facter.expects(:warnonce).with('Using Facter::Util::Resolution.exec with a shell built-in is deprecated. Most built-ins can be replaced with native ruby commands. If you really have to run a built-in, pass "cmd /c your_builtin" as a command') + Facter.expects(:warnonce).with 'Using Facter::Util::Resolution.exec with a shell built-in is deprecated. Most built-ins can be replaced with native ruby commands. If you really have to run a built-in, pass "cmd /c your_builtin" as a command (command responsible for this message was "echo foo")' Facter::Util::Resolution.exec(%q{echo foo}).should == 'foo' end it "should try to run the command and return nil if not shell-builtin" do From 5480484f64fd9b3912bc229e167c191d22c47416 Mon Sep 17 00:00:00 2001 From: razic Date: Thu, 14 Mar 2013 22:09:45 +0700 Subject: [PATCH 1235/3753] (#17710) Refactor the IP module This pull request aims to refactor the Facter::Util::IP module with kernel specific classes adhering to a common interface. This pull request eliminates case statements and crazy kernel conditionals within methods. Additionally, this eliminates the giant REGEX_MAP constant at the top of the Facter::Util::IP module and adds some documentation to methods that were previously left undocumented. This also removes the untested network.rb fact and moves in into interfaces.rb since they are dynamically generated by the name of the interfaces. They now have tests. This supports the new on_flush method. The related ticket is here: http://projects.puppetlabs.com/issues/17710 --- lib/facter/interfaces.rb | 45 +- lib/facter/network.rb | 20 - lib/facter/util/ip.rb | 425 ++++++------- lib/facter/util/ip/base.rb | 196 ++++++ lib/facter/util/ip/darwin.rb | 6 + lib/facter/util/ip/dragonfly.rb | 6 + lib/facter/util/ip/free_bsd.rb | 6 + lib/facter/util/ip/gnu_k_free_bsd.rb | 9 + lib/facter/util/ip/hpux.rb | 76 +++ lib/facter/util/ip/linux.rb | 169 +++++ lib/facter/util/ip/net_bsd.rb | 6 + lib/facter/util/ip/open_bsd.rb | 6 + lib/facter/util/ip/sun_os.rb | 24 + lib/facter/util/ip/windows.rb | 73 +++ .../ifconfig_all_with_multiple_interfaces} | 0 .../ip/darwin/ifconfig_with_single_interface | 6 + ...-STABLE_ifconfig_with_multiple_interfaces} | 0 .../6.0-STABLE_ifconfig_with_single_interface | 10 + .../ifconfig_all_with_multiple_interfaces} | 0 .../ifconfig_with_single_interface | 8 + .../1111_ifconfig_lan0} | 0 .../1111_ifconfig_lan1} | 0 .../1111_ifconfig_lo0} | 0 .../{hpux_1111_lanscan => hpux/1111_lanscan} | 0 .../1111_netstat_in} | 0 .../1131_asterisk_ifconfig_lan0} | 0 .../1131_asterisk_ifconfig_lan1} | 0 .../1131_asterisk_ifconfig_lo0} | 0 .../1131_asterisk_lanscan} | 0 .../1131_asterisk_netstat_in} | 0 .../1131_ifconfig_lan0} | 0 .../1131_ifconfig_lan1} | 0 .../1131_ifconfig_lo0} | 0 .../{hpux_1131_lanscan => hpux/1131_lanscan} | 0 .../1131_netstat_in} | 0 .../1131_nic_bonding_ifconfig_lan1} | 0 .../1131_nic_bonding_ifconfig_lan4} | 0 .../1131_nic_bonding_ifconfig_lan4_1} | 0 .../1131_nic_bonding_ifconfig_lo0} | 0 .../1131_nic_bonding_lanscan} | 0 .../1131_nic_bonding_netstat_in} | 0 .../2_6_35_proc_net_bonding_bond0} | 0 .../ifconfig_all_with_single_interface} | 0 .../ifconfig_single_interface_eth0} | 0 .../ifconfig_single_interface_lo} | 0 .../ifconfig_with_single_interface_ib0} | 0 .../ifconfig_all_with_multiple_interfaces} | 0 .../ifconfig_single_interface} | 0 .../netsh_all_interfaces} | 0 .../netsh_with_single_interface} | 0 .../netsh_with_single_interface6} | 0 spec/unit/blockdevices_spec.rb | 2 - spec/unit/hardwareisa_spec.rb | 2 - spec/unit/hardwaremodel_spec.rb | 2 - spec/unit/interfaces_spec.rb | 68 --- spec/unit/ldom_spec.rb | 2 - spec/unit/manufacturer_spec.rb | 2 - spec/unit/uniqueid_spec.rb | 2 - spec/unit/util/directory_loader_spec.rb | 3 - spec/unit/util/ip/base_spec.rb | 147 +++++ spec/unit/util/ip/darwin_spec.rb | 87 +++ spec/unit/util/ip/dragonfly_spec.rb | 34 ++ spec/unit/util/ip/free_bsd_spec.rb | 56 ++ spec/unit/util/ip/gnu_k_free_bsd_spec.rb | 85 +++ spec/unit/util/ip/hpux_spec.rb | 536 ++++++++++++++++ spec/unit/util/ip/linux_spec.rb | 158 +++++ spec/unit/util/ip/net_bsd_spec.rb | 34 ++ spec/unit/util/ip/open_bsd_spec.rb | 34 ++ spec/unit/util/ip/sun_os_spec.rb | 91 +++ spec/unit/util/ip/windows_spec.rb | 94 +++ spec/unit/util/ip_spec.rb | 578 +++++------------- spec/unit/util/parser_spec.rb | 5 +- spec/unit/zfs_version_spec.rb | 2 - spec/unit/zpool_version_spec.rb | 2 - 74 files changed, 2295 insertions(+), 822 deletions(-) delete mode 100644 lib/facter/network.rb create mode 100644 lib/facter/util/ip/base.rb create mode 100644 lib/facter/util/ip/darwin.rb create mode 100644 lib/facter/util/ip/dragonfly.rb create mode 100644 lib/facter/util/ip/free_bsd.rb create mode 100644 lib/facter/util/ip/gnu_k_free_bsd.rb create mode 100644 lib/facter/util/ip/hpux.rb create mode 100644 lib/facter/util/ip/linux.rb create mode 100644 lib/facter/util/ip/net_bsd.rb create mode 100644 lib/facter/util/ip/open_bsd.rb create mode 100644 lib/facter/util/ip/sun_os.rb create mode 100644 lib/facter/util/ip/windows.rb rename spec/fixtures/unit/util/ip/{darwin_ifconfig_all_with_multiple_interfaces => darwin/ifconfig_all_with_multiple_interfaces} (100%) create mode 100644 spec/fixtures/unit/util/ip/darwin/ifconfig_with_single_interface rename spec/fixtures/unit/util/ip/{6.0-STABLE_FreeBSD_ifconfig => free_bsd/6.0-STABLE_ifconfig_with_multiple_interfaces} (100%) create mode 100644 spec/fixtures/unit/util/ip/free_bsd/6.0-STABLE_ifconfig_with_single_interface rename spec/fixtures/unit/util/ip/{debian_kfreebsd_ifconfig => gnu_k_free_bsd/ifconfig_all_with_multiple_interfaces} (100%) create mode 100644 spec/fixtures/unit/util/ip/gnu_k_free_bsd/ifconfig_with_single_interface rename spec/fixtures/unit/util/ip/{hpux_1111_ifconfig_lan0 => hpux/1111_ifconfig_lan0} (100%) rename spec/fixtures/unit/util/ip/{hpux_1111_ifconfig_lan1 => hpux/1111_ifconfig_lan1} (100%) rename spec/fixtures/unit/util/ip/{hpux_1111_ifconfig_lo0 => hpux/1111_ifconfig_lo0} (100%) rename spec/fixtures/unit/util/ip/{hpux_1111_lanscan => hpux/1111_lanscan} (100%) rename spec/fixtures/unit/util/ip/{hpux_1111_netstat_in => hpux/1111_netstat_in} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_asterisk_ifconfig_lan0 => hpux/1131_asterisk_ifconfig_lan0} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_asterisk_ifconfig_lan1 => hpux/1131_asterisk_ifconfig_lan1} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_asterisk_ifconfig_lo0 => hpux/1131_asterisk_ifconfig_lo0} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_asterisk_lanscan => hpux/1131_asterisk_lanscan} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_asterisk_netstat_in => hpux/1131_asterisk_netstat_in} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_ifconfig_lan0 => hpux/1131_ifconfig_lan0} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_ifconfig_lan1 => hpux/1131_ifconfig_lan1} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_ifconfig_lo0 => hpux/1131_ifconfig_lo0} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_lanscan => hpux/1131_lanscan} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_netstat_in => hpux/1131_netstat_in} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_nic_bonding_ifconfig_lan1 => hpux/1131_nic_bonding_ifconfig_lan1} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_nic_bonding_ifconfig_lan4 => hpux/1131_nic_bonding_ifconfig_lan4} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_nic_bonding_ifconfig_lan4_1 => hpux/1131_nic_bonding_ifconfig_lan4_1} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_nic_bonding_ifconfig_lo0 => hpux/1131_nic_bonding_ifconfig_lo0} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_nic_bonding_lanscan => hpux/1131_nic_bonding_lanscan} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_nic_bonding_netstat_in => hpux/1131_nic_bonding_netstat_in} (100%) rename spec/fixtures/unit/util/ip/{linux_2_6_35_proc_net_bonding_bond0 => linux/2_6_35_proc_net_bonding_bond0} (100%) rename spec/fixtures/unit/util/ip/{linux_ifconfig_all_with_single_interface => linux/ifconfig_all_with_single_interface} (100%) rename spec/fixtures/unit/util/ip/{linux_get_single_interface_eth0 => linux/ifconfig_single_interface_eth0} (100%) rename spec/fixtures/unit/util/ip/{linux_get_single_interface_lo => linux/ifconfig_single_interface_lo} (100%) rename spec/fixtures/unit/util/ip/{linux_get_single_interface_ib0 => linux/ifconfig_with_single_interface_ib0} (100%) rename spec/fixtures/unit/util/ip/{solaris_ifconfig_all_with_multiple_interfaces => sun_os/ifconfig_all_with_multiple_interfaces} (100%) rename spec/fixtures/unit/util/ip/{solaris_ifconfig_single_interface => sun_os/ifconfig_single_interface} (100%) rename spec/fixtures/unit/util/ip/{windows_netsh_all_interfaces => windows/netsh_all_interfaces} (100%) rename spec/fixtures/unit/util/ip/{windows_netsh_single_interface => windows/netsh_with_single_interface} (100%) rename spec/fixtures/unit/util/ip/{windows_netsh_single_interface6 => windows/netsh_with_single_interface6} (100%) delete mode 100755 spec/unit/interfaces_spec.rb create mode 100644 spec/unit/util/ip/base_spec.rb create mode 100644 spec/unit/util/ip/darwin_spec.rb create mode 100644 spec/unit/util/ip/dragonfly_spec.rb create mode 100644 spec/unit/util/ip/free_bsd_spec.rb create mode 100644 spec/unit/util/ip/gnu_k_free_bsd_spec.rb create mode 100644 spec/unit/util/ip/hpux_spec.rb create mode 100644 spec/unit/util/ip/linux_spec.rb create mode 100644 spec/unit/util/ip/net_bsd_spec.rb create mode 100644 spec/unit/util/ip/open_bsd_spec.rb create mode 100644 spec/unit/util/ip/sun_os_spec.rb create mode 100644 spec/unit/util/ip/windows_spec.rb diff --git a/lib/facter/interfaces.rb b/lib/facter/interfaces.rb index 9b132e1c24..09f97a28bf 100644 --- a/lib/facter/interfaces.rb +++ b/lib/facter/interfaces.rb @@ -1,43 +1,10 @@ -# interfaces.rb -# Try to get additional Facts about the machine's network interfaces -# -# Original concept Copyright (C) 2007 psychedelys -# Update and *BSD support (C) 2007 James Turnbull +# encoding: UTF-8 + +# Fact: interfaces # +# Purpose: +# Try to get facts about the machine's network interfaces require 'facter/util/ip' -# Note that most of this only works on a fixed list of platforms; notably, Darwin -# is missing. - -Facter.add(:interfaces) do - confine :kernel => 'Linux' - has_weight 20 - setcode do - list = Dir.glob('/sys/class/net/*').map do |name| - Facter::Util::IP.alphafy(name.split('/').last) - end - list.empty? ? nil : list.join(',') - end -end - -Facter.add(:interfaces) do - confine :kernel => Facter::Util::IP.supported_platforms - setcode do - Facter::Util::IP.get_interfaces.collect { |iface| Facter::Util::IP.alphafy(iface) }.join(",") - end -end - -Facter::Util::IP.get_interfaces.each do |interface| - - # Make a fact for each detail of each interface. Yay. - # There's no point in confining these facts, since we wouldn't be able to create - # them if we weren't running on a supported platform. - %w{ipaddress ipaddress6 macaddress netmask mtu}.each do |label| - Facter.add(label + "_" + Facter::Util::IP.alphafy(interface)) do - setcode do - Facter::Util::IP.get_interface_value(interface, label) - end - end - end -end +Facter::Util::IP.add_interface_facts diff --git a/lib/facter/network.rb b/lib/facter/network.rb deleted file mode 100644 index 390895a05b..0000000000 --- a/lib/facter/network.rb +++ /dev/null @@ -1,20 +0,0 @@ -# Fact: network -# -# Purpose: -# Get IP, network and netmask information for available network -# interfacs. -# -# Resolution: -# Uses 'facter/util/ip' to enumerate interfaces and return their information. -# -# Caveats: -# -require 'facter/util/ip' - -Facter::Util::IP.get_interfaces.each do |interface| - Facter.add("network_" + Facter::Util::IP.alphafy(interface)) do - setcode do - Facter::Util::IP.get_network_value(interface) - end - end -end diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index 6bed1dd4d0..fe235c017c 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -1,296 +1,249 @@ -# A base module for collecting IP-related -# information from all kinds of platforms. -module Facter::Util::IP - # A map of all the different regexes that work for - # a given platform or set of platforms. - REGEX_MAP = { - :linux => { - :ipaddress => /inet (?:addr:)?([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, - :ipaddress6 => /inet6 (?:addr: )?((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/, - :macaddress => /(?:ether|HWaddr)\s+((\w{1,2}:){5,}\w{1,2})/, - :netmask => /Mask:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, - :mtu => /MTU:(\d+)/ - }, - :bsd => { - :aliases => [:openbsd, :netbsd, :freebsd, :darwin, :"gnu/kfreebsd", :dragonfly], - :ipaddress => /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, - :ipaddress6 => /inet6 ((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/, - :macaddress => /(?:ether|lladdr)\s+(\w?\w:\w?\w:\w?\w:\w?\w:\w?\w:\w?\w)/, - :netmask => /netmask\s+0x(\w{8})/, - :mtu => /mtu\s+(\d+)/ - }, - :sunos => { - :ipaddress => /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, - :ipaddress6 => /inet6 ((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/, - :macaddress => /(?:ether|lladdr)\s+(\w?\w:\w?\w:\w?\w:\w?\w:\w?\w:\w?\w)/, - :netmask => /netmask\s+(\w{8})/, - :mtu => /mtu\s+(\d+)/ - }, - :"hp-ux" => { - :ipaddress => /\s+inet (\S+)\s.*/, - :macaddress => /(\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/, - :netmask => /.*\s+netmask (\S+)\s.*/ - }, - :windows => { - :ipaddress => /\s+IP Address:\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, - :ipaddress6 => /Address ((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/, - :netmask => /\s+Subnet Prefix:\s+\S+\s+\(mask ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\)/ - } - } +# encoding: UTF-8 + +require 'facter/util/ip/base' +require 'facter/util/ip/darwin' +require 'facter/util/ip/sun_os' +require 'facter/util/ip/linux' +require 'facter/util/ip/net_bsd' +require 'facter/util/ip/open_bsd' +require 'facter/util/ip/free_bsd' +require 'facter/util/ip/dragonfly' +require 'facter/util/ip/windows' +require 'facter/util/ip/hpux' +require 'facter/util/ip/gnu_k_free_bsd' + +# A base module for collecting IP related info from all kinds of platforms. +class Facter::Util::IP + INTERFACE_KEYS = %w[ipaddress ipaddress6 macaddress netmask mtu] + + attr_accessor :interfaces_hash + + # Uses the interfaces stored in {@interfaces} to obtain and parse the + # attributes corresponding to {INTERFACE_KEYS} and stores the resulting hash + # in {@interfaces_hash}. + # + # @api private + def parse! + @interfaces_hash = @interfaces.inject({}) do |hashA, interface| + hashA[interface] = INTERFACE_KEYS.inject({}) do |hashB, key| + hashB[key] = value_for_interface_and_label interface, key - # Convert an interface name into purely alphanumeric characters. - def self.alphafy(interface) - interface.gsub(/[^a-z0-9_]/i, '_') - end + hashB + end - def self.convert_from_hex?(kernel) - kernels_to_convert = [:sunos, :openbsd, :netbsd, :freebsd, :darwin, :"hp-ux", :"gnu/kfreebsd", :dragonfly] - kernels_to_convert.include?(kernel) - end + hashA[interface][:network] = network interface - def self.supported_platforms - REGEX_MAP.inject([]) do |result, tmp| - key, map = tmp - if map[:aliases] - result += map[:aliases] - else - result << key - end - result + hashA end end - def self.get_interfaces - # Use sysfs on most Linux systems, which is the fastest option. - if File.exist?('/sys/class/net') - return Dir.glob('/sys/class/net/*').map do |name| - Facter::Util::IP.alphafy(name.split('/').last) + # Adds interface facts like 'eth0'. Also defines dynamic facts describing + # attributes of each interface, like 'ipaddress_eth0' and 'network_eth0'. + # + # @api private + def self.add_interface_facts + model = new + + model.refresh + model.add_dynamic_interface_facts + + Facter.add :interfaces do + confine :kernel => model.supported_platforms + + setcode do + model.refresh if model.flushed? + model.add_dynamic_interface_facts + model.stringified_interfaces end - end - return [] unless output = Facter::Util::IP.get_all_interface_output() + on_flush { model.flush! } + end + end - # windows interface names contain spaces and are quoted and can appear multiple - # times as ipv4 and ipv6 - return output.scan(/\s* connected\s*(\S.*)/).flatten.uniq if Facter.value(:kernel) == 'windows' - # Our regex appears to be stupid, in that it leaves colons sitting - # at the end of interfaces. So, we have to trim those trailing - # characters. I tried making the regex better but supporting all - # platforms with a single regex is probably a bit too much. - output.scan(/^\S+/).collect { |i| i.sub(/:$/, '') }.uniq + # Convert an interface name into purely alphanumeric characters. + # + # @param [String] interface e.g. 'eth0' + # + # @return [String] + # + # @api private + def self.alphafy(interface) + interface.to_s.gsub(/[^a-z0-9_]/i, '_') end - def self.get_all_interface_output - case Facter.value(:kernel) - when 'Linux', 'OpenBSD', 'NetBSD', 'FreeBSD', 'Darwin', 'GNU/kFreeBSD', 'DragonFly' - output = Facter::Util::IP.exec_ifconfig(["-a","2>/dev/null"]) - when 'SunOS' - output = Facter::Util::IP.exec_ifconfig(["-a"]) - when 'HP-UX' - # (#17487)[https://projects.puppetlabs.com/issues/17487] - # Handle NIC bonding where asterisks and virtual NICs are printed. - if output = hpux_netstat_in - output.gsub!(/\*/, "") # delete asterisks. - output.gsub!(/^[^\n]*none[^\n]*\n/, "") # delete lines with 'none' instead of IPs. - output.sub!(/^[^\n]*\n/, "") # delete the header line. - output - end - when 'windows' - output = %x|#{ENV['SYSTEMROOT']}/system32/netsh.exe interface ip show interface| - output += %x|#{ENV['SYSTEMROOT']}/system32/netsh.exe interface ipv6 show interface| - end - output + # Returns an array of supported platforms in string format. These array values + # are synonymous with the values returned from Facter.value(:kernel). + # + # @return [Array] contains strings corresponding to a kernel + # + # @api private + def supported_platforms + kernel_classes.map(&:to_s) end + # A delegate method to the kernel's subclass ultimately obtaining the + # interfaces. + # + # @return [Array] + # + # @api private + def interfaces + kernel_class.interfaces + end - ## - # exec_ifconfig uses the ifconfig command + # Uses the ifconfig command + # + # @param [Array] additional arguments + # + # @return [String] the output of the command # - # @return [String] the output of `ifconfig #{arguments} 2>/dev/null` or nil + # @api private def self.exec_ifconfig(additional_arguments=[]) Facter::Util::Resolution.exec("#{self.get_ifconfig} #{additional_arguments.join(' ')}") end - ## - # get_ifconfig looks up the ifconfig binary + + # Looks up the ifconfig binary. # # @return [String] path to the ifconfig binary + # + # @api private def self.get_ifconfig common_paths=["/bin/ifconfig","/sbin/ifconfig","/usr/sbin/ifconfig"] common_paths.select{|path| File.executable?(path)}.first end - ## - # hpux_netstat_in is a delegate method that allows us to stub netstat -in - # without stubbing exec. - def self.hpux_netstat_in - Facter::Util::Resolution.exec("/bin/netstat -in") - end - def self.get_infiniband_macaddress(interface) - if File.exists?("/sys/class/net/#{interface}/address") then - ib_mac_address = `cat /sys/class/net/#{interface}/address`.chomp - elsif File.exists?("/sbin/ip") then - ip_output = %x{/sbin/ip link show #{interface}} - ib_mac_address = ip_output.scan(%r{infiniband\s+((\w{1,2}:){5,}\w{1,2})}) - else - ib_mac_address = "FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF" - Facter.debug("ip.rb: nothing under /sys/class/net/#{interface}/address and /sbin/ip not available") - end - ib_mac_address + # A delegate method to `value_for_interface_and_label` which is implemented in + # Facter::Util::IP::Base and it's subclasses. + # + # @param interface [String] label [String] e.g ['eth0', 'MTU'] + # + # @return [String] or [NilClass] + # + # @api private + def value_for_interface_and_label(interface, label) + kernel_class.value_for_interface_and_label interface, label end - def self.ifconfig_interface(interface) - output = Facter::Util::IP.exec_ifconfig([interface,"2>/dev/null"]) + # A delegate method to obtain the network of an interface + # + # @param interface [String] e.g 'eth0' + # + # @return [String] or [NilClass] + # + # @api private + def network(interface) + kernel_class.network(interface) end - def self.get_single_interface_output(interface) - output = "" - case Facter.value(:kernel) - when 'OpenBSD', 'NetBSD', 'FreeBSD', 'Darwin', 'GNU/kFreeBSD', 'DragonFly' - output = Facter::Util::IP.ifconfig_interface(interface) - when 'Linux' - ifconfig_output = Facter::Util::IP.ifconfig_interface(interface) - if interface =~ /^ib/ then - real_mac_address = get_infiniband_macaddress(interface) - output = ifconfig_output.sub(%r{(?:ether|HWaddr)\s+((\w{1,2}:){5,}\w{1,2})}, "HWaddr #{real_mac_address}") - else - output = ifconfig_output - end - when 'SunOS' - output = Facter::Util::IP.exec_ifconfig([interface]) - when 'HP-UX' - mac = "" - ifc = hpux_ifconfig_interface(interface) - hpux_lanscan.scan(/(\dx\S+).*UP\s+(\w+\d+)/).each {|i| mac = i[0] if i.include?(interface) } - mac = mac.sub(/0x(\S+)/,'\1').scan(/../).join(":") - output = ifc + "\n" + mac - end - output + # A delegate method for obtaining Facter::Util::IP::Base's subclasses. + # + # @return [Array] + # + # @api private + def kernel_classes + Facter::Util::IP::Base.subclasses end - def self.hpux_ifconfig_interface(interface) - Facter::Util::IP.exec_ifconfig([interface]) + # Obtains the cooresponding Facter::Util::IP::Base subclass for the current + # kernel. + # + # @return Subclass of [Facter::Util::IP::Base] + # + # @api private + def kernel_class + kernel_classes.find { |klass| klass.to_s == Facter.value(:kernel) } end - def self.hpux_lanscan - Facter::Util::Resolution.exec("/usr/sbin/lanscan") + # Boolean to determine whether the current kernel is supported. + # + # @return [Boolean] true or false + # + # @api private + def kernel_supported? + supported_platforms.include?(Facter.value(:kernel)) end - def self.get_output_for_interface_and_label(interface, label) - return get_single_interface_output(interface) unless Facter.value(:kernel) == 'windows' - - if label == 'ipaddress6' - output = %x|#{ENV['SYSTEMROOT']}/system32/netsh.exe interface ipv6 show address \"#{interface}\"| - else - output = %x|#{ENV['SYSTEMROOT']}/system32/netsh.exe interface ip show address \"#{interface}\"| + # Stringifies interfaces so that it can be used as a fact value. + # + # @return [String] the interfaces as a string + # + # @api private + def stringified_interfaces + alphafied_interfaces = @interfaces.map do |interface| + Facter::Util::IP.alphafy(interface) end - output - end - def self.get_bonding_master(interface) - if Facter.value(:kernel) != 'Linux' - return nil - end - # We need ip instead of ifconfig because it will show us - # the bonding master device. - if not FileTest.executable?("/sbin/ip") - return nil - end - # A bonding interface can never be an alias interface. Alias - # interfaces do have a colon in their name and the ip link show - # command throws an error message when we pass it an alias - # interface. - if interface =~ /:/ - return nil - end - regex = /SLAVE[,>].* (bond[0-9]+)/ - ethbond = regex.match(%x{/sbin/ip link show #{interface}}) - if ethbond - device = ethbond[1] - else - device = nil - end - device + alphafied_interfaces.join ',' end - ## - # get_interface_value obtains the value of a specific attribute of a specific - # interface. - # - # @param interface [String] the interface identifier, e.g. "eth0" or "bond0" - # - # @param label [String] the attribute of the interface to obtain a value for, - # e.g. "netmask" or "ipaddress" + # Defines all of the dynamic interface facts derived from parsing the output + # of the network interface ouput. The interface facts are dynamic, so this + # method has the behavior of figuring out what facts need to be added and how + # they should be resolved. # # @api private - # - # @return [String] representing the requested value. An empty array is - # returned if the kernel is not supported by the REGEX_MAP constant. - def self.get_interface_value(interface, label) - tmp1 = [] + def add_dynamic_interface_facts + model = self - kernel = Facter.value(:kernel).downcase.to_sym + @interfaces.each do |interface| + INTERFACE_KEYS.each do |key| + Facter.add "#{key}_#{model.class.alphafy(interface)}" do + confine :kernel => model.supported_platforms - # If it's not directly in the map or aliased in the map, then we don't know how to deal with it. - unless map = REGEX_MAP[kernel] || REGEX_MAP.values.find { |tmp| tmp[:aliases] and tmp[:aliases].include?(kernel) } - return [] - end + setcode do + model.refresh if model.flushed? - # Pull the correct regex out of the map. - regex = map[label.to_sym] - - # Linux changes the MAC address reported via ifconfig when an ethernet interface - # becomes a slave of a bonding device to the master MAC address. - # We have to dig a bit to get the original/real MAC address of the interface. - bonddev = get_bonding_master(interface) - if label == 'macaddress' and bonddev - bondinfo = read_proc_net_bonding("/proc/net/bonding/#{bonddev}") - re = /^Slave Interface: #{interface}\b.*?\bPermanent HW addr: (([0-9A-F]{2}:?)*)$/im - if match = re.match(bondinfo) - value = match[1].upcase - end - else - output_int = get_output_for_interface_and_label(interface, label) - - output_int.each_line do |s| - if s =~ regex - value = $1 - if label == 'netmask' && convert_from_hex?(kernel) - value = value.scan(/../).collect do |byte| byte.to_i(16) end.join('.') + # Don't resolve if the interface has since been deleted + if keys_hash = model.interfaces_hash[interface] + keys_hash[key] end - tmp1.push(value) + end + + on_flush { model.flush! } end end - if tmp1 - value = tmp1.shift + Facter.add "network_#{interface}" do + confine :kernel => model.supported_platforms + + setcode do + model.refresh if model.flushed? + + # Don't resolve if the interface has since been deleted + if keys_hash = model.interfaces_hash[interface] + keys_hash[:network] + end + end + + on_flush { model.flush! } end end end - ## - # read_proc_net_bonding is a seam method for mocking purposes. - # - # @param path [String] representing the path to read, e.g. "/proc/net/bonding/bond0" + # Executes the platform specific system command to obtain the interfaces and + # stores them in {@interfaces}. # # @api private - # - # @return [String] modeling the raw file read - def self.read_proc_net_bonding(path) - File.read(path) if File.exists?(path) - end - private_class_method :read_proc_net_bonding + def refresh + @interfaces = interfaces - def self.get_network_value(interface) - require 'ipaddr' + parse! + end - ipaddress = get_interface_value(interface, "ipaddress") - netmask = get_interface_value(interface, "netmask") + # Checks to see if the intstance has been flushed. + # + # @return [Boolean] true if there is no parsed data + # + # @api private + def flushed? + !interfaces_hash + end - if ipaddress && netmask - ip = IPAddr.new(ipaddress, Socket::AF_INET) - subnet = IPAddr.new(netmask, Socket::AF_INET) - network = ip.mask(subnet.to_s).to_s - end + # Purges the saved data so that the fact can be resolved properly upon flush. + # + # @api private + def flush! + @interfaces_hash = nil end end diff --git a/lib/facter/util/ip/base.rb b/lib/facter/util/ip/base.rb new file mode 100644 index 0000000000..eaa3faa061 --- /dev/null +++ b/lib/facter/util/ip/base.rb @@ -0,0 +1,196 @@ +# encoding: UTF-8 + +require 'ipaddr' + +module Facter + module Util + class IP + end + end +end + +class Facter::Util::IP::Base + # A regex to match an IPv4 address from `ifconfig` output. This regex will + # work for most platforms. You can override this in your subclass if you need + # a different regex. + # + # @return [Regexp] + # + # @api private + IPADDRESS_REGEX = /inet.*?([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + + + # A regex to match an IPv6 address from `ifconfig` output. This regex will + # work for most platforms. You can override this in your subclass if you need + # a different regex. + # + # @return [Regexp] + # + # @api private + IPADDRESS6_REGEX = / + inet6.*?((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4}) + /x + + # A regex to match a MAC address from `ifconfig` output. This regex will work + # for most platforms. You can override this in your subclass if you need a + # different regex. + # + # @return [Regexp] + # + # @api private + MACADDRESS_REGEX = /(?:ether|lladdr)\s+(\w?\w:\w?\w:\w?\w:\w?\w:\w?\w:\w?\w)/ + + # A regex to match the netmask from `ifconfig` output. This regex will work + # for most platforms. You can override this in your subclass if you need a + # different regex. + # + # @return [Regexp] + # + # @api private + NETMASK_REGEX = /netmask\s+0x(\w{8})/ + + # A regex to match the MTU from `ifconfig` output. This regex will work for + # most platforms. You can override this in your subclass if you need a + # different regex. + # + # @return [Regexp] + # + # @api private + MTU_REGEX = /mtu\s+(\d+)/ + + # Returns the name of the Class without nesting. Mostly used for finding + # the right class corresponding to the value of Facter.value(:kernel) + # + # @return [String] The string without nesting. + # + # @api private + def self.to_s + super.split('::').last + end + + # Used in conjunction with the `.inherited` hook, this method will store + # an Array of the Class' subclasses. + # + # @return [Array] The subclasses. + # + # @api private + def self.subclasses + @subclasses ||= [] + end + + # Most kernels will need to have their netmask converted from hex. If + # your kernel doesn't display the netmask in hex, you'll need to + # override this method in your subclass to return false. + # + # @return [Boolean] true + # + # @api private + def self.convert_netmask_from_hex? + true + end + + # Network bonding is creation of a single bonded interface by combining 2 or + # more Ethernet interfaces. I think this is mostly used in Linux so this base + # method will return nil, however you should override this in your subclass if + # need be. See the Facter::Util::IP::Linux.bonding_master method. + # + # @return [NilClass] + # + # @api private + def self.bonding_master(interface) + end + + # Returns an array of interfaces from `ifconfig`. e.g. ['eth0', 'eth1'] This + # will work on most platforms, but override in your subclass if need be. + # + # @return [Array] + # + # @api private + def self.interfaces + exec("#{ifconfig_path} -a 2> /dev/null").to_s.scan(/^\w+/).uniq + end + + # Get the value of an interface and label. For example, you may want to find + # the MTU for eth0. + # + # @param interface [String] label [String] and optional command [String] + # + # @return [String] or [NilClass] + # + # @api private + def self.value_for_interface_and_label(interface, label, cmd = nil) + if regex = regex_for(label) + cmd ||= "#{ifconfig_path} #{interface} 2> /dev/null" + + if match = regex.match(exec(cmd).to_s) + if label == 'netmask' && convert_netmask_from_hex? + match[1].scan(/../).map { |byte| byte.to_i(16) }.join('.') + else + match[1] + end + end + end + end + + # Returns the IP network for the given interface. + # + # @return [String] or [NilClass] + # + # @api private + def self.network(interface) + ipaddress = value_for_interface_and_label(interface, "ipaddress") + netmask = value_for_interface_and_label(interface, "netmask") + + if ipaddress && netmask + ip = IPAddr.new(ipaddress, Socket::AF_INET) + subnet = IPAddr.new(netmask, Socket::AF_INET) + + ip.mask(subnet.to_s).to_s + end + end + + # This is a Ruby hook method that will help to maintain a list of + # subclasses. See the `.subclasses` method for more information. + # + # @return [Array] The subclasses. + # + # @api private + def self.inherited subclass + subclasses << subclass + end + + # This loops over `ifconfig` paths to find the first that is executable. + # + # @return [String] + # + # @api private + def self.ifconfig_path + %w[/bin/ifconfig /sbin/ifconfig /usr/sbin/ifconfig].find do |path| + File.executable?(path) + end + end + + # Delegation method to Facter::Util::Resolution.exec. + # + # @param command [String] the command to execute + # + # @return [String] or [Nil] + # + # @api private + def self.exec string + Facter::Util::Resolution.exec string + end + + # Grabs the corresponding regex constant. e.g. NETMASK_REGEX + # + # @param label [String] e.g. 'netmask' + # + # @return [Regexp] or [NilClass] + # + # @api private + def self.regex_for label + constant = "#{label.to_s.upcase}_REGEX" + + const_get(constant) if constants.find { |c| /^#{constant}$/.match(c) } + end +end diff --git a/lib/facter/util/ip/darwin.rb b/lib/facter/util/ip/darwin.rb new file mode 100644 index 0000000000..83c95fc791 --- /dev/null +++ b/lib/facter/util/ip/darwin.rb @@ -0,0 +1,6 @@ +# encoding: UTF-8 + +require 'facter/util/ip/base' + +class Facter::Util::IP::Darwin < Facter::Util::IP::Base +end diff --git a/lib/facter/util/ip/dragonfly.rb b/lib/facter/util/ip/dragonfly.rb new file mode 100644 index 0000000000..0ccf87c359 --- /dev/null +++ b/lib/facter/util/ip/dragonfly.rb @@ -0,0 +1,6 @@ +# encoding: UTF-8 + +require 'facter/util/ip/base' + +class Facter::Util::IP::Dragonfly < Facter::Util::IP::Base +end diff --git a/lib/facter/util/ip/free_bsd.rb b/lib/facter/util/ip/free_bsd.rb new file mode 100644 index 0000000000..6db3942c39 --- /dev/null +++ b/lib/facter/util/ip/free_bsd.rb @@ -0,0 +1,6 @@ +# encoding: UTF-8 + +require 'facter/util/ip/base' + +class Facter::Util::IP::FreeBSD < Facter::Util::IP::Base +end diff --git a/lib/facter/util/ip/gnu_k_free_bsd.rb b/lib/facter/util/ip/gnu_k_free_bsd.rb new file mode 100644 index 0000000000..5988a1562c --- /dev/null +++ b/lib/facter/util/ip/gnu_k_free_bsd.rb @@ -0,0 +1,9 @@ +# encoding: UTF-8 + +require 'facter/util/ip/base' + +class Facter::Util::IP::GNUkFreeBSD < Facter::Util::IP::Base + def self.to_s + 'GNU/kFreeBSD' + end +end diff --git a/lib/facter/util/ip/hpux.rb b/lib/facter/util/ip/hpux.rb new file mode 100644 index 0000000000..3485b41d36 --- /dev/null +++ b/lib/facter/util/ip/hpux.rb @@ -0,0 +1,76 @@ +# encoding: UTF-8 + +require 'facter/util/ip/base' + +class Facter::Util::IP::HPUX < Facter::Util::IP::Base + # A regex to match an IPv4 address from `ifconfig` output. + # + # @return [Regexp] + # + # @api private + IPADDRESS_REGEX = /\s+inet (\S+)\s.*/ + + # A regex to match a MAC address from `ifconfig` output. + # + # @return [Regexp] + # + # @api private + MACADDRESS_REGEX = /(\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/ + + # A regex to match the netmask from `ifconfig` output. + # + # @return [Regexp] + # + # @api private + NETMASK_REGEX = /.*\s+netmask (\S+)\s.*/ + + # The path to the `lanscan` executable. + # + # @return [Regexp] + # + # @api private + LANSCAN = '/usr/sbin/lanscan' + + def self.to_s + 'HP-UX' + end + + # Gets an array of interfaces from `netstat`. The cryptic text replacements in + # the method handles NIC bonding where asterisks and virtual NICs are printed. + # See (#17487)[https://projects.puppetlabs.com/issues/17487] for more info. + # + # @return [Array] + # + # @api private + def self.interfaces + exec("/bin/netstat -in"). + to_s. + gsub(/\*/, ""). + gsub(/^[^\n]*none[^\n]*\n/, ""). + sub(/^[^\n]*\n/, ""). + scan(/^\w+/) + end + + def self.value_for_interface_and_label(interface, label) + value = super(interface, label) + + if !value && label == 'macaddress' + if macaddress = lanscan.to_s[/\dx(\S+).*UP\s+#{interface}/, 1] + macaddress.scan(/../).join(':') + end + else + value + end + end + + private + + # Execute lanscan. + # + # @return [String] or [NilClass] + # + # @api private + def self.lanscan + exec(LANSCAN) if File.exist?(LANSCAN) + end +end diff --git a/lib/facter/util/ip/linux.rb b/lib/facter/util/ip/linux.rb new file mode 100644 index 0000000000..45fee9f310 --- /dev/null +++ b/lib/facter/util/ip/linux.rb @@ -0,0 +1,169 @@ +# encoding: UTF-8 + +require 'facter/util/ip/base' + +class Facter::Util::IP::Linux < Facter::Util::IP::Base + # A regex to match an IPv4 address from `ifconfig` output. + # + # @return [Regexp] + # + # @api private + IPADDRESS_REGEX = /inet\s(?:addr:)?([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + + # A regex to match an IPv6 address from `ifconfig` output. + # + # @return [Regexp] + # + # @api private + IPADDRESS6_REGEX = /inet6\s(?:addr: )?((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/ + + + # A regex to match a MAC address from `ifconfig` output. + # + # @return [Regexp] + # + # @api private + MACADDRESS_REGEX = /(?:ether|HWaddr)\s+((\w{1,2}:){5,}\w{1,2})/ + + # A regex to match the netmask from `ifconfig` output. + # + # @return [Regexp] + # + # @api private + NETMASK_REGEX = /Mask:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + + # A regex to match the MTU from `ifconfig` output. + # + # @return [Regexp] + # + # @api private + MTU_REGEX = /MTU:(\d+)/ + + # Linux doesn't display netmask in hex. + # + # @return [Boolean] false by default + # + # @api private + def self.convert_netmask_from_hex? + false + end + + # Network bonding is creation of a single bonded interface by combining 2 or + # more Ethernet interfaces. This method returns the bonding master. + # + # @param interface [String] the interface, e.g. 'eth0' + # + # @return [String] or [NilClass] + # + # @api private + def self.bonding_master(interface) + # We need `ip` instead of `ifconfig` because it shows us the bonding master. + return unless FileTest.executable?("/sbin/ip") + + # A bonding interface can never be an alias interface. Alias interfaces do + # have a colon in their name and the ip link show command throws an error + # message when we pass it an alias interface. + return if interface.match(/:/) + + regex = /SLAVE[,>].* (bond[0-9]+)/ + ethbond = regex.match(%x{/sbin/ip link show #{interface}}) + + ethbond[1] if ethbond + end + + # Returns an array of interfaces. e.g. ['eth0', 'eth1'] We will check sysfs + # first, since that is the fastest option, but fallback to `ifconfig` if + # neccessary. + # + # @return [Array] + # + # @api private + def self.interfaces + if File.exist?('/sys/class/net') + Dir.glob('/sys/class/net/*').map do |name| + name.split('/').last + end + else + super + end + end + + # Get the value of an interface and label. For example, you may want to find + # the MTU for eth0. If an infiniband interface is passed, it will try to + # obtain the real value. + # + # @param interface [String] label [String] and optional command [String] + # + # @return [String] or [NilClass] + # + # @api private + def self.value_for_interface_and_label(interface, label) + if label == 'macaddress' + bonddev = bonding_master(interface) + + if infiniband?(interface) + infiniband_macaddress(interface) || super + elsif bonddev + bonddev_macaddress(bonddev, interface) || super + else + super + end + else + super + end + end + + private + + # Boolean method to test whether an interface is the infiniband. + # + # @param interface [String] e.g. 'ib0' + # + # @return [Boolean] true or false + # + # @api private + def self.infiniband?(interface) + !!/^ib/.match(interface) + end + + # Attempts to obtain the real macaddress for an infiniband interface. + # + # @param interface [String] e.g. 'ib0' + # + # @return [String] or [NilClass] + # + # @api private + def self.infiniband_macaddress(interface) + sysfs = "/sys/class/net/#{interface}/address" + + if File.exists?(sysfs) + exec("cat #{sysfs}") + elsif File.exists?("/sbin/ip") + exec("/sbin/ip link show #{interface}"). + to_s. + scan(%r{infiniband\s+((\w{1,2}:){5,}\w{1,2})})[0] + end + end + + # Attempts to obtain the real macaddress for a bonded interface. + # + # @param bonddev [String] interface [String] e.g. 'bond0', 'eth0' + # + # @return [String] or [NilClass] + # + # @api private + def self.bonddev_macaddress(bonddev, interface) + path = "/proc/net/bonding/#{bonddev}" + + if File.exists?(path) + bondinfo = File.read(path) + regex = / + ^Slave\sInterface:\s + #{interface}\b.*?\bPermanent\sHW\saddr:\s(([0-9A-F]{2}:?)*)$ + /imx + match = regex.match(bondinfo) + + match[1].upcase if match + end + end +end diff --git a/lib/facter/util/ip/net_bsd.rb b/lib/facter/util/ip/net_bsd.rb new file mode 100644 index 0000000000..3b97c9c158 --- /dev/null +++ b/lib/facter/util/ip/net_bsd.rb @@ -0,0 +1,6 @@ +# encoding: UTF-8 + +require 'facter/util/ip/base' + +class Facter::Util::IP::NetBSD < Facter::Util::IP::Base +end diff --git a/lib/facter/util/ip/open_bsd.rb b/lib/facter/util/ip/open_bsd.rb new file mode 100644 index 0000000000..3204a70194 --- /dev/null +++ b/lib/facter/util/ip/open_bsd.rb @@ -0,0 +1,6 @@ +# encoding: UTF-8 + +require 'facter/util/ip/base' + +class Facter::Util::IP::OpenBSD < Facter::Util::IP::Base +end diff --git a/lib/facter/util/ip/sun_os.rb b/lib/facter/util/ip/sun_os.rb new file mode 100644 index 0000000000..5533247ab6 --- /dev/null +++ b/lib/facter/util/ip/sun_os.rb @@ -0,0 +1,24 @@ +# encoding: UTF-8 + +require 'facter/util/ip/base' + +class Facter::Util::IP::SunOS < Facter::Util::IP::Base + # A regex to match the netmask from `ifconfig` output. + # + # @return [Regexp] + # + # @api private + NETMASK_REGEX = /netmask\s(\w{8})/ + + # Get the value of an interface and label. For example, you may want to find + # the MTU for eth0. + # + # @param interface [String] label [String] e.g ['eth0', 'MTU'] + # + # @return [String] or [NilClass] + # + # @api private + def self.value_for_interface_and_label(interface, label) + super(interface, label, "#{ifconfig_path} #{interface}") + end +end diff --git a/lib/facter/util/ip/windows.rb b/lib/facter/util/ip/windows.rb new file mode 100644 index 0000000000..52e6d623b7 --- /dev/null +++ b/lib/facter/util/ip/windows.rb @@ -0,0 +1,73 @@ +# encoding: UTF-8 + +require 'facter/util/ip/base' + +class Facter::Util::IP::Windows < Facter::Util::IP::Base + # A regex to match an IPv4 address from `ifconfig` output. + # + # @return [Regexp] + # + # @api private + IPADDRESS_REGEX = /\s+IP\sAddress:\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + + # A regex to match an IPv6 address from `ifconfig` output. + # + # @return [Regexp] + # + # @api private + IPADDRESS6_REGEX = /Address\s((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/ + + # A regex to match the netmask from `ifconfig` output. + # + # @return [Regexp] + # + # @api private + NETMASK_REGEX = /\s+Subnet\sPrefix:\s+\S+\s+\(mask\s([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\)/ + + # The path to netsh.exe. + # + # @return [String] + # + # @api private + NETSH = "#{ENV['SYSTEMROOT']}/system32/netsh.exe" + + def self.to_s + 'windows' + end + + # Windows doesn't display netmask in hex. + # + # @return [Boolean] false by default + # + # @api private + def self.convert_netmask_from_hex? + false + end + + # Uses netsh.exe to obtain a list of interfaces. + # + # @return [Array] + # + # @api private + def self.interfaces + cmd = "#{NETSH} interface %s show interface" + output = exec("#{cmd % 'ip'} && #{cmd % 'ipv6'}").to_s + + output.scan(/\s* connected\s*(\S.*)/).flatten.uniq + end + + # Get the value of an interface and label. For example, you may want to find + # the MTU for eth0. Uses netsh.exe. + # + # @param interface [String] label [String] + # + # @return [String] or [NilClass] + # + # @api private + def self.value_for_interface_and_label(interface, label) + opt = label == 'ipaddress6' ? 'ipv6' : 'ip' + cmd = "#{NETSH} interface #{opt} show address \"#{interface}\"" + + super(interface, label, cmd) + end +end diff --git a/spec/fixtures/unit/util/ip/darwin_ifconfig_all_with_multiple_interfaces b/spec/fixtures/unit/util/ip/darwin/ifconfig_all_with_multiple_interfaces similarity index 100% rename from spec/fixtures/unit/util/ip/darwin_ifconfig_all_with_multiple_interfaces rename to spec/fixtures/unit/util/ip/darwin/ifconfig_all_with_multiple_interfaces diff --git a/spec/fixtures/unit/util/ip/darwin/ifconfig_with_single_interface b/spec/fixtures/unit/util/ip/darwin/ifconfig_with_single_interface new file mode 100644 index 0000000000..6a4e10326c --- /dev/null +++ b/spec/fixtures/unit/util/ip/darwin/ifconfig_with_single_interface @@ -0,0 +1,6 @@ +en0: flags=8863 mtu 1500 + inet6 fe80::223:6cff:fe99:602b%en1 prefixlen 64 scopeid 0x5 + inet 192.168.0.10 netmask 0xffffff00 broadcast 192.168.0.255 + ether 00:23:6c:99:60:2b + media: autoselect status: active + supported media: autoselect diff --git a/spec/fixtures/unit/util/ip/6.0-STABLE_FreeBSD_ifconfig b/spec/fixtures/unit/util/ip/free_bsd/6.0-STABLE_ifconfig_with_multiple_interfaces similarity index 100% rename from spec/fixtures/unit/util/ip/6.0-STABLE_FreeBSD_ifconfig rename to spec/fixtures/unit/util/ip/free_bsd/6.0-STABLE_ifconfig_with_multiple_interfaces diff --git a/spec/fixtures/unit/util/ip/free_bsd/6.0-STABLE_ifconfig_with_single_interface b/spec/fixtures/unit/util/ip/free_bsd/6.0-STABLE_ifconfig_with_single_interface new file mode 100644 index 0000000000..b1324be986 --- /dev/null +++ b/spec/fixtures/unit/util/ip/free_bsd/6.0-STABLE_ifconfig_with_single_interface @@ -0,0 +1,10 @@ +fxp0: flags=8843 mtu 1500 + options=b + inet x.x.58.26 netmask 0xfffffff8 broadcast x.x.58.31 + inet x.x.58.27 netmask 0xffffffff broadcast x.x.58.27 + inet x.x.58.28 netmask 0xffffffff broadcast x.x.58.28 + inet x.x.58.29 netmask 0xffffffff broadcast x.x.58.29 + inet x.x.58.30 netmask 0xffffffff broadcast x.x.58.30 + ether 00:0e:0c:68:67:7c + media: Ethernet autoselect (100baseTX ) + status: active diff --git a/spec/fixtures/unit/util/ip/debian_kfreebsd_ifconfig b/spec/fixtures/unit/util/ip/gnu_k_free_bsd/ifconfig_all_with_multiple_interfaces similarity index 100% rename from spec/fixtures/unit/util/ip/debian_kfreebsd_ifconfig rename to spec/fixtures/unit/util/ip/gnu_k_free_bsd/ifconfig_all_with_multiple_interfaces diff --git a/spec/fixtures/unit/util/ip/gnu_k_free_bsd/ifconfig_with_single_interface b/spec/fixtures/unit/util/ip/gnu_k_free_bsd/ifconfig_with_single_interface new file mode 100644 index 0000000000..b9ce459282 --- /dev/null +++ b/spec/fixtures/unit/util/ip/gnu_k_free_bsd/ifconfig_with_single_interface @@ -0,0 +1,8 @@ +em1: flags=8843 metric 0 mtu 1500 + options=209b + ether 0:11:a:59:67:91 + inet6 fe80::211:aff:fe59:6791%em1 prefixlen 64 scopeid 0x2 + inet 192.168.10.10 netmask 0xffffff00 broadcast 192.168.10.255 + nd6 options=3 + media: Ethernet autoselect (100baseTX ) + status: active diff --git a/spec/fixtures/unit/util/ip/hpux_1111_ifconfig_lan0 b/spec/fixtures/unit/util/ip/hpux/1111_ifconfig_lan0 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1111_ifconfig_lan0 rename to spec/fixtures/unit/util/ip/hpux/1111_ifconfig_lan0 diff --git a/spec/fixtures/unit/util/ip/hpux_1111_ifconfig_lan1 b/spec/fixtures/unit/util/ip/hpux/1111_ifconfig_lan1 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1111_ifconfig_lan1 rename to spec/fixtures/unit/util/ip/hpux/1111_ifconfig_lan1 diff --git a/spec/fixtures/unit/util/ip/hpux_1111_ifconfig_lo0 b/spec/fixtures/unit/util/ip/hpux/1111_ifconfig_lo0 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1111_ifconfig_lo0 rename to spec/fixtures/unit/util/ip/hpux/1111_ifconfig_lo0 diff --git a/spec/fixtures/unit/util/ip/hpux_1111_lanscan b/spec/fixtures/unit/util/ip/hpux/1111_lanscan similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1111_lanscan rename to spec/fixtures/unit/util/ip/hpux/1111_lanscan diff --git a/spec/fixtures/unit/util/ip/hpux_1111_netstat_in b/spec/fixtures/unit/util/ip/hpux/1111_netstat_in similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1111_netstat_in rename to spec/fixtures/unit/util/ip/hpux/1111_netstat_in diff --git a/spec/fixtures/unit/util/ip/hpux_1131_asterisk_ifconfig_lan0 b/spec/fixtures/unit/util/ip/hpux/1131_asterisk_ifconfig_lan0 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_asterisk_ifconfig_lan0 rename to spec/fixtures/unit/util/ip/hpux/1131_asterisk_ifconfig_lan0 diff --git a/spec/fixtures/unit/util/ip/hpux_1131_asterisk_ifconfig_lan1 b/spec/fixtures/unit/util/ip/hpux/1131_asterisk_ifconfig_lan1 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_asterisk_ifconfig_lan1 rename to spec/fixtures/unit/util/ip/hpux/1131_asterisk_ifconfig_lan1 diff --git a/spec/fixtures/unit/util/ip/hpux_1131_asterisk_ifconfig_lo0 b/spec/fixtures/unit/util/ip/hpux/1131_asterisk_ifconfig_lo0 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_asterisk_ifconfig_lo0 rename to spec/fixtures/unit/util/ip/hpux/1131_asterisk_ifconfig_lo0 diff --git a/spec/fixtures/unit/util/ip/hpux_1131_asterisk_lanscan b/spec/fixtures/unit/util/ip/hpux/1131_asterisk_lanscan similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_asterisk_lanscan rename to spec/fixtures/unit/util/ip/hpux/1131_asterisk_lanscan diff --git a/spec/fixtures/unit/util/ip/hpux_1131_asterisk_netstat_in b/spec/fixtures/unit/util/ip/hpux/1131_asterisk_netstat_in similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_asterisk_netstat_in rename to spec/fixtures/unit/util/ip/hpux/1131_asterisk_netstat_in diff --git a/spec/fixtures/unit/util/ip/hpux_1131_ifconfig_lan0 b/spec/fixtures/unit/util/ip/hpux/1131_ifconfig_lan0 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_ifconfig_lan0 rename to spec/fixtures/unit/util/ip/hpux/1131_ifconfig_lan0 diff --git a/spec/fixtures/unit/util/ip/hpux_1131_ifconfig_lan1 b/spec/fixtures/unit/util/ip/hpux/1131_ifconfig_lan1 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_ifconfig_lan1 rename to spec/fixtures/unit/util/ip/hpux/1131_ifconfig_lan1 diff --git a/spec/fixtures/unit/util/ip/hpux_1131_ifconfig_lo0 b/spec/fixtures/unit/util/ip/hpux/1131_ifconfig_lo0 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_ifconfig_lo0 rename to spec/fixtures/unit/util/ip/hpux/1131_ifconfig_lo0 diff --git a/spec/fixtures/unit/util/ip/hpux_1131_lanscan b/spec/fixtures/unit/util/ip/hpux/1131_lanscan similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_lanscan rename to spec/fixtures/unit/util/ip/hpux/1131_lanscan diff --git a/spec/fixtures/unit/util/ip/hpux_1131_netstat_in b/spec/fixtures/unit/util/ip/hpux/1131_netstat_in similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_netstat_in rename to spec/fixtures/unit/util/ip/hpux/1131_netstat_in diff --git a/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lan1 b/spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_ifconfig_lan1 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lan1 rename to spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_ifconfig_lan1 diff --git a/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lan4 b/spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_ifconfig_lan4 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lan4 rename to spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_ifconfig_lan4 diff --git a/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lan4_1 b/spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_ifconfig_lan4_1 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lan4_1 rename to spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_ifconfig_lan4_1 diff --git a/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lo0 b/spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_ifconfig_lo0 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lo0 rename to spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_ifconfig_lo0 diff --git a/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_lanscan b/spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_lanscan similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_lanscan rename to spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_lanscan diff --git a/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_netstat_in b/spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_netstat_in similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_netstat_in rename to spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_netstat_in diff --git a/spec/fixtures/unit/util/ip/linux_2_6_35_proc_net_bonding_bond0 b/spec/fixtures/unit/util/ip/linux/2_6_35_proc_net_bonding_bond0 similarity index 100% rename from spec/fixtures/unit/util/ip/linux_2_6_35_proc_net_bonding_bond0 rename to spec/fixtures/unit/util/ip/linux/2_6_35_proc_net_bonding_bond0 diff --git a/spec/fixtures/unit/util/ip/linux_ifconfig_all_with_single_interface b/spec/fixtures/unit/util/ip/linux/ifconfig_all_with_single_interface similarity index 100% rename from spec/fixtures/unit/util/ip/linux_ifconfig_all_with_single_interface rename to spec/fixtures/unit/util/ip/linux/ifconfig_all_with_single_interface diff --git a/spec/fixtures/unit/util/ip/linux_get_single_interface_eth0 b/spec/fixtures/unit/util/ip/linux/ifconfig_single_interface_eth0 similarity index 100% rename from spec/fixtures/unit/util/ip/linux_get_single_interface_eth0 rename to spec/fixtures/unit/util/ip/linux/ifconfig_single_interface_eth0 diff --git a/spec/fixtures/unit/util/ip/linux_get_single_interface_lo b/spec/fixtures/unit/util/ip/linux/ifconfig_single_interface_lo similarity index 100% rename from spec/fixtures/unit/util/ip/linux_get_single_interface_lo rename to spec/fixtures/unit/util/ip/linux/ifconfig_single_interface_lo diff --git a/spec/fixtures/unit/util/ip/linux_get_single_interface_ib0 b/spec/fixtures/unit/util/ip/linux/ifconfig_with_single_interface_ib0 similarity index 100% rename from spec/fixtures/unit/util/ip/linux_get_single_interface_ib0 rename to spec/fixtures/unit/util/ip/linux/ifconfig_with_single_interface_ib0 diff --git a/spec/fixtures/unit/util/ip/solaris_ifconfig_all_with_multiple_interfaces b/spec/fixtures/unit/util/ip/sun_os/ifconfig_all_with_multiple_interfaces similarity index 100% rename from spec/fixtures/unit/util/ip/solaris_ifconfig_all_with_multiple_interfaces rename to spec/fixtures/unit/util/ip/sun_os/ifconfig_all_with_multiple_interfaces diff --git a/spec/fixtures/unit/util/ip/solaris_ifconfig_single_interface b/spec/fixtures/unit/util/ip/sun_os/ifconfig_single_interface similarity index 100% rename from spec/fixtures/unit/util/ip/solaris_ifconfig_single_interface rename to spec/fixtures/unit/util/ip/sun_os/ifconfig_single_interface diff --git a/spec/fixtures/unit/util/ip/windows_netsh_all_interfaces b/spec/fixtures/unit/util/ip/windows/netsh_all_interfaces similarity index 100% rename from spec/fixtures/unit/util/ip/windows_netsh_all_interfaces rename to spec/fixtures/unit/util/ip/windows/netsh_all_interfaces diff --git a/spec/fixtures/unit/util/ip/windows_netsh_single_interface b/spec/fixtures/unit/util/ip/windows/netsh_with_single_interface similarity index 100% rename from spec/fixtures/unit/util/ip/windows_netsh_single_interface rename to spec/fixtures/unit/util/ip/windows/netsh_with_single_interface diff --git a/spec/fixtures/unit/util/ip/windows_netsh_single_interface6 b/spec/fixtures/unit/util/ip/windows/netsh_with_single_interface6 similarity index 100% rename from spec/fixtures/unit/util/ip/windows_netsh_single_interface6 rename to spec/fixtures/unit/util/ip/windows/netsh_with_single_interface6 diff --git a/spec/unit/blockdevices_spec.rb b/spec/unit/blockdevices_spec.rb index 9c67f5ea13..0061b92e45 100644 --- a/spec/unit/blockdevices_spec.rb +++ b/spec/unit/blockdevices_spec.rb @@ -1,5 +1,3 @@ -#!/usr/bin/env ruby - require 'spec_helper' require 'facter' require 'facter/util/blockdevices' diff --git a/spec/unit/hardwareisa_spec.rb b/spec/unit/hardwareisa_spec.rb index a709e8c742..d613026251 100755 --- a/spec/unit/hardwareisa_spec.rb +++ b/spec/unit/hardwareisa_spec.rb @@ -1,5 +1,3 @@ -#!/usr/bin/env ruby - require 'spec_helper' require 'facter' diff --git a/spec/unit/hardwaremodel_spec.rb b/spec/unit/hardwaremodel_spec.rb index a168c11ef5..e9ef3d40de 100644 --- a/spec/unit/hardwaremodel_spec.rb +++ b/spec/unit/hardwaremodel_spec.rb @@ -1,5 +1,3 @@ -#!/usr/bin/env ruby - require 'spec_helper' require 'facter' diff --git a/spec/unit/interfaces_spec.rb b/spec/unit/interfaces_spec.rb deleted file mode 100755 index c495a499eb..0000000000 --- a/spec/unit/interfaces_spec.rb +++ /dev/null @@ -1,68 +0,0 @@ -#! /usr/bin/env ruby - -require 'spec_helper' -require 'facter/util/ip' - -shared_examples_for "iface specific ifconfig output" do |platform, address, fixture| - it "correctly on #{platform} for eth0" do - Facter::Util::IP.stubs(:ifconfig_interface).returns(my_fixture_read(fixture)) - subject.value.should == address - end -end - -describe "Per Interface IP facts" do - it "should replace the ':' in an interface list with '_'" do - # So we look supported - Facter.fact(:kernel).stubs(:value).returns("SunOS") - - Facter::Util::IP.stubs(:get_interfaces).returns %w{eth0:1 eth1:2} - Facter.fact(:interfaces).value.should == %{eth0_1,eth1_2} - end - - it "should replace non-alphanumerics in an interface list with '_'" do - Facter.fact(:kernel).stubs(:value).returns("windows") - - Facter::Util::IP.stubs(:get_interfaces).returns ["Local Area Connection", "Loopback \"Pseudo-Interface\" (#1)"] - Facter.fact(:interfaces).value.should == %{Local_Area_Connection,Loopback__Pseudo_Interface____1_} - end -end - - -RSpec.configure do |config| - config.alias_it_should_behave_like_to :example_behavior_for, "parses" -end - - -describe "the ipaddress_$iface fact" do - subject do - Facter.collection.internal_loader.load(:interfaces) - Facter.fact(:ipaddress_eth0) - end - - context "on Linux" do - before :each do - Facter::Util::IP.stubs(:get_interfaces).returns(["eth0"]) - Facter.fact(:kernel).stubs(:value).returns("Linux") - end - example_behavior_for "iface specific ifconfig output", "Fedora 18", "10.10.220.1", "eth0_net_tools_2.0.txt" - - example_behavior_for "iface specific ifconfig output", "net_tools 1.60", "10.10.220.210", "eth0_net_tools_1.60.txt" - end -end - -describe "the ipaddress6_$iface fact" do - subject do - Facter.collection.internal_loader.load(:interfaces) - Facter.fact(:ipaddress6_eth0) - end - - context "on Linux" do - before :each do - Facter::Util::IP.stubs(:get_interfaces).returns(["eth0"]) - Facter.fact(:kernel).stubs(:value).returns("Linux") - end - example_behavior_for "iface specific ifconfig output", "Fedora 18", "dead::21f:bcff:fe0d:5fb1", "eth0_net_tools_2.0.txt" - - example_behavior_for "iface specific ifconfig output", "net_tools 1.60", "dead::216:3eff:fe7d:ec7e", "eth0_net_tools_1.60.txt" - end -end diff --git a/spec/unit/ldom_spec.rb b/spec/unit/ldom_spec.rb index f97d4a8765..27ec932278 100644 --- a/spec/unit/ldom_spec.rb +++ b/spec/unit/ldom_spec.rb @@ -1,5 +1,3 @@ -#!/usr/bin/env ruby - require 'spec_helper' def ldom_fixtures(filename) diff --git a/spec/unit/manufacturer_spec.rb b/spec/unit/manufacturer_spec.rb index cacf470f27..f18bb9b0c3 100644 --- a/spec/unit/manufacturer_spec.rb +++ b/spec/unit/manufacturer_spec.rb @@ -1,5 +1,3 @@ -#!/usr/bin/env ruby - require 'spec_helper' require 'facter' require 'facter/util/manufacturer' diff --git a/spec/unit/uniqueid_spec.rb b/spec/unit/uniqueid_spec.rb index e9870d39fd..3ac9bbf49d 100755 --- a/spec/unit/uniqueid_spec.rb +++ b/spec/unit/uniqueid_spec.rb @@ -1,5 +1,3 @@ -#!/usr/bin/env ruby - require 'spec_helper' require 'facter' diff --git a/spec/unit/util/directory_loader_spec.rb b/spec/unit/util/directory_loader_spec.rb index 79ad2b2cd5..de8f3ca5aa 100644 --- a/spec/unit/util/directory_loader_spec.rb +++ b/spec/unit/util/directory_loader_spec.rb @@ -1,7 +1,4 @@ -#!/usr/bin/env ruby - require 'spec_helper' - require 'facter/util/directory_loader' describe Facter::Util::DirectoryLoader do diff --git a/spec/unit/util/ip/base_spec.rb b/spec/unit/util/ip/base_spec.rb new file mode 100644 index 0000000000..9484c22fa9 --- /dev/null +++ b/spec/unit/util/ip/base_spec.rb @@ -0,0 +1,147 @@ +# encoding: UTF-8 + +require 'spec_helper' +require 'facter/util/ip/base' + +describe Facter::Util::IP::Base do + subject { described_class } + + describe ".subclasses" do + let(:subclasses) { described_class.subclasses } + + it { expect(subclasses).to be_an Array } + + it "should be memoized" do + expect(subclasses).to be subclasses + end + + it "should list subclasses" do + subclass = Class.new described_class + + expect(subclasses).to include subclass + end + end + + describe ".to_s" do + let(:to_s) { described_class.to_s } + + it "should return the name of the class without nesting" do + expect(to_s).to eq 'Base' + end + end + + describe ".convert_netmask_from_hex?" do + let :convert_netmask_from_hex? do + described_class.convert_netmask_from_hex? + end + + it { expect(convert_netmask_from_hex?).to be true } + end + + describe ".bonding_master" do + let(:bonding_master) { described_class.bonding_master('eth0') } + + it { expect(bonding_master).to be_nil } + end + + describe ".interfaces" do + let(:interfaces) { described_class.interfaces } + + it "uses `ifconfig` to list the interfaces" do + described_class.expects(:ifconfig_path).returns('/bin/ifconfig') + described_class.expects(:exec).with("/bin/ifconfig -a 2> /dev/null") + expect(interfaces).to be_an Array + end + end + + describe ".value_for_interface_and_label(interface, label)" do + let :value_for_interface_and_label do + described_class.value_for_interface_and_label interface, label + end + + let(:interface) { 'eth0' } + + describe "there is no regex for the label" do + let(:label) { 'foobar' } + + before :each do + described_class.expects(:regex_for).with(label).returns(nil) + end + + it { expect(value_for_interface_and_label).to be_nil } + end + + describe "there is a regex for the label" do + let(:regex) { // } + let(:ifconfig_path) { '/usr/bin/ifconfig' } + let(:exec_cmd) { "#{ifconfig_path} #{interface} 2> /dev/null" } + let(:ifconfig_output) { "" } + + before :each do + described_class.expects(:ifconfig_path).returns(ifconfig_path) + described_class.expects(:regex_for).with(label).returns(regex) + described_class.expects(:exec).with(exec_cmd).returns(ifconfig_output) + regex.expects(:match).with(ifconfig_output).returns(match_data) + end + + describe "there is a match with the exec output and the regex" do + describe "the label is not 'netmask'" do + let(:match_data) { ['inet 127.0.0.1', '127.0.0.1'] } + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to eq '127.0.0.1' } + end + + describe "the label is 'netmask'" do + let(:label) { 'netmask' } + + describe "the netmask needs to be converted from hex" do + let(:match_data) { ['netmask ffffff00', 'ffffff00'] } + + before :each do + described_class.expects(:convert_netmask_from_hex?).returns(true) + end + + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + end + + describe "the netmask does not need to be converted from hex" do + let(:match_data) { ['netmask 255.255.255.0', '255.255.255.0'] } + + before :each do + described_class.expects(:convert_netmask_from_hex?).returns(false) + end + + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + end + end + end + + describe "there is not a match with the exec output and the regex" do + let(:match_data) { nil } + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to be_nil } + end + end + + describe ".network(interface)" do + let(:network) { described_class.network(interface) } + let(:interface) { 'e1000g0' } + + before :each do + described_class. + expects(:value_for_interface_and_label). + with(interface, 'ipaddress'). + returns('172.16.15.138') + + described_class. + expects(:value_for_interface_and_label). + with(interface, 'netmask'). + returns('255.255.255.0') + end + + it { expect(network).to eq "172.16.15.0" } + end + end +end diff --git a/spec/unit/util/ip/darwin_spec.rb b/spec/unit/util/ip/darwin_spec.rb new file mode 100644 index 0000000000..b9407f29af --- /dev/null +++ b/spec/unit/util/ip/darwin_spec.rb @@ -0,0 +1,87 @@ +# encoding: UTF-8 + +require 'spec_helper' +require 'facter/util/ip/darwin' + +describe Facter::Util::IP::Darwin do + describe ".to_s" do + let :to_s do + described_class.to_s + end + + it "should be 'Darwin'" do + expect(to_s).to eq 'Darwin' + end + end + + describe ".convert_netmask_from_hex?" do + let :convert_netmask_from_hex? do + described_class.convert_netmask_from_hex? + end + + it "should be true" do + expect(convert_netmask_from_hex?).to be true + end + end + + describe ".bonding_master" do + let :bonding_master do + described_class.bonding_master('eth0') + end + + it { expect(bonding_master).to be_nil } + end + + describe ".interfaces" do + let :interfaces do + described_class.interfaces + end + + let :ifconfig_output do + my_fixture_read("ifconfig_all_with_multiple_interfaces") + end + + before :each do + described_class.expects(:ifconfig_path).returns('/usr/bin/ifconfig') + described_class.expects(:exec).with(anything).returns(ifconfig_output) + end + + it "should return an array with two interfaces" do + expect(interfaces).to eq ["lo0", "en0"] + end + end + + describe ".value_for_interface_and_label(interface, label)" do + let :value_for_interface_and_label do + described_class.value_for_interface_and_label interface, label + end + + let(:interface) { 'en0' } + let(:ifconfig_output) { my_fixture_read "ifconfig_with_single_interface" } + let(:ifconfig_path) { "/usr/bin/ifconfig" } + let(:exec_cmd) { "#{ifconfig_path} #{interface} 2> /dev/null" } + + before :each do + described_class.expects(:ifconfig_path).returns(ifconfig_path) + described_class.expects(:exec).with(exec_cmd).returns(ifconfig_output) + end + + describe "macaddress" do + let(:label) { 'macaddress' } + + it { expect(value_for_interface_and_label).to eq '00:23:6c:99:60:2b' } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + end + + describe "mtu" do + let(:label) { 'mtu' } + + it { expect(value_for_interface_and_label).to eq '1500' } + end + end +end diff --git a/spec/unit/util/ip/dragonfly_spec.rb b/spec/unit/util/ip/dragonfly_spec.rb new file mode 100644 index 0000000000..f2c670a209 --- /dev/null +++ b/spec/unit/util/ip/dragonfly_spec.rb @@ -0,0 +1,34 @@ +# encoding: UTF-8 + +require 'spec_helper' +require 'facter/util/ip/dragonfly' + +describe Facter::Util::IP::Dragonfly do + describe ".to_s" do + let :to_s do + described_class.to_s + end + + it "should be 'Dragonfly'" do + expect(to_s).to eq 'Dragonfly' + end + end + + describe ".convert_netmask_from_hex?" do + let :convert_netmask_from_hex? do + described_class.convert_netmask_from_hex? + end + + it "should be true" do + expect(convert_netmask_from_hex?).to be true + end + end + + describe ".bonding_master" do + let(:bonding_master) do + described_class.bonding_master('eth0') + end + + it { expect(bonding_master).to be_nil } + end +end diff --git a/spec/unit/util/ip/free_bsd_spec.rb b/spec/unit/util/ip/free_bsd_spec.rb new file mode 100644 index 0000000000..d69989e492 --- /dev/null +++ b/spec/unit/util/ip/free_bsd_spec.rb @@ -0,0 +1,56 @@ +# encoding: UTF-8 + +require 'spec_helper' +require 'facter/util/ip/free_bsd' + +describe Facter::Util::IP::FreeBSD do + describe ".to_s" do + let :to_s do + described_class.to_s + end + + it "should be 'FreeBSD'" do + expect(to_s).to eq 'FreeBSD' + end + end + + describe ".convert_netmask_from_hex?" do + let :convert_netmask_from_hex? do + described_class.convert_netmask_from_hex? + end + + it "should be true" do + expect(convert_netmask_from_hex?).to be true + end + end + + describe ".bonding_master" do + let :bonding_master do + described_class.bonding_master('eth0') + end + + it { expect(bonding_master).to be_nil } + end + + describe ".value_for_interface_and_label(interface, label)" do + let :value_for_interface_and_label do + described_class.value_for_interface_and_label interface, label + end + + let(:interface) { 'fxp0' } + let(:ifconfig_output) { my_fixture_read "6.0-STABLE_ifconfig_with_single_interface" } + let(:ifconfig_path) { "/usr/bin/ifconfig" } + let(:exec_cmd) { "#{ifconfig_path} #{interface} 2> /dev/null" } + + before :each do + described_class.expects(:ifconfig_path).returns(ifconfig_path) + described_class.expects(:exec).with(exec_cmd).returns(ifconfig_output) + end + + describe "macaddress" do + let(:label) { 'macaddress' } + + it { expect(value_for_interface_and_label).to eq '00:0e:0c:68:67:7c' } + end + end +end diff --git a/spec/unit/util/ip/gnu_k_free_bsd_spec.rb b/spec/unit/util/ip/gnu_k_free_bsd_spec.rb new file mode 100644 index 0000000000..591571d6ec --- /dev/null +++ b/spec/unit/util/ip/gnu_k_free_bsd_spec.rb @@ -0,0 +1,85 @@ +# encoding: UTF-8 + +require 'spec_helper' +require 'facter/util/ip/gnu_k_free_bsd' + +describe Facter::Util::IP::GNUkFreeBSD do + before :each do + Facter.fact(:kernel).stubs(:value).returns('GNU/kFreeBSD') + end + + describe ".to_s" do + let :to_s do + described_class.to_s + end + + it "should be 'GNU/kFreeBSD'" do + expect(to_s).to eq 'GNU/kFreeBSD' + end + end + + describe ".convert_netmask_from_hex?" do + let :convert_netmask_from_hex? do + described_class.convert_netmask_from_hex? + end + + it "should be true" do + expect(convert_netmask_from_hex?).to be true + end + end + + describe ".bonding_master" do + let :bonding_master do + described_class.bonding_master('eth0') + end + + it { expect(bonding_master).to be_nil } + end + + describe ".interfaces" do + let :interfaces do + described_class.interfaces + end + + let :ifconfig_output do + my_fixture_read("ifconfig_all_with_multiple_interfaces") + end + + before :each do + described_class.expects(:ifconfig_path).returns('/usr/bin/ifconfig') + described_class.expects(:exec).with(anything).returns(ifconfig_output) + end + + it "should return an array with six interfaces" do + expect(interfaces).to eq %w[em0 em1 bge0 bge1 lo0 vlan0] + end + end + + describe ".value_for_interface_and_label(interface, label)" do + let :value_for_interface_and_label do + described_class.value_for_interface_and_label interface, label + end + + let(:interface) { 'em1' } + let(:ifconfig_output) { my_fixture_read "ifconfig_with_single_interface" } + let(:ifconfig_path) { "/usr/bin/ifconfig" } + let(:exec_cmd) { "#{ifconfig_path} #{interface} 2> /dev/null" } + + before :each do + described_class.expects(:ifconfig_path).returns(ifconfig_path) + described_class.expects(:exec).with(exec_cmd).returns(ifconfig_output) + end + + describe "macaddress" do + let(:label) { 'macaddress' } + + it { expect(value_for_interface_and_label).to eq '0:11:a:59:67:91' } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + end + end +end diff --git a/spec/unit/util/ip/hpux_spec.rb b/spec/unit/util/ip/hpux_spec.rb new file mode 100644 index 0000000000..ec3f21bfe1 --- /dev/null +++ b/spec/unit/util/ip/hpux_spec.rb @@ -0,0 +1,536 @@ +# encoding: UTF-8 + +require 'spec_helper' +require 'facter/util/ip/hpux' + +describe Facter::Util::IP::HPUX do + before :each do + Facter.fact(:kernel).stubs(:value).returns("HP-UX") + end + + describe ".to_s" do + let :to_s do + described_class.to_s + end + + it "should be 'HP-UX'" do + expect(to_s).to eq 'HP-UX' + end + end + + describe ".convert_netmask_from_hex?" do + let :convert_netmask_from_hex? do + described_class.convert_netmask_from_hex? + end + + it "should be true" do + expect(convert_netmask_from_hex?).to be true + end + end + + describe ".bonding_master" do + let :bonding_master do + described_class.bonding_master('eth0') + end + + it { expect(bonding_master).to be_nil } + end + + describe ".interfaces" do + let :interfaces do + described_class.interfaces + end + + before :each do + described_class.stubs(:exec).with(anything).returns(netstat_output) + end + + describe "version 11.11" do + let :netstat_output do + my_fixture_read("1111_netstat_in") + end + + it "should return an array of interfaces" do + expect(interfaces).to eq %w[lan1 lan0 lo0] + end + end + + describe "version 11.31" do + let :netstat_output do + my_fixture_read("1131_netstat_in") + end + + it "should return an array of interfaces" do + expect(interfaces).to eq %w[lan1 lan0 lo0] + end + end + end + + describe ".value_for_interface_and_label(interface, label)" do + let :value_for_interface_and_label do + described_class.value_for_interface_and_label interface, label + end + + describe "version 11.11" do + let(:ifconfig_output) { my_fixture_read("1111_ifconfig_#{interface}") } + let(:lanscan_output) { my_fixture_read("1111_lanscan") } + + before :each do + described_class.stubs(:exec).returns(ifconfig_output) + end + + describe "lan1" do + let(:interface) { 'lan1' } + + describe "ipaddress" do + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to eq '10.1.1.6' } + end + + describe "mtu" do + let(:label) { 'mtu' } + + it { pending "(#17808) MTU has not been implemented for HP-UX" } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + end + + describe "macaddress" do + let(:label) { 'macaddress' } + + before :each do + described_class.expects(:lanscan).returns(lanscan_output) + end + + it { expect(value_for_interface_and_label).to eq "00:10:79:7B:5C:DE" } + end + end + + describe "lan0" do + let(:interface) { 'lan0' } + + describe "ipaddress" do + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to eq "192.168.3.10" } + end + + describe "mtu" do + let(:label) { 'mtu' } + + it { pending "(#17808) MTU has not been implemented for HP-UX" } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + end + + describe "macaddress" do + let(:label) { 'macaddress' } + + before :each do + described_class.expects(:lanscan).returns(lanscan_output) + end + + it { expect(value_for_interface_and_label).to eq "00:30:7F:0C:79:DC" } + end + end + + describe "lo0" do + let(:interface) { 'lo0' } + + describe "ipaddress" do + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to eq "127.0.0.1" } + end + + describe "mtu" do + let(:label) { 'mtu' } + + it { pending "(#17808) MTU has not been implemented for HP-UX" } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.0.0.0' } + end + + describe "macaddress" do + let(:label) { 'macaddress' } + + it { expect(value_for_interface_and_label).to be_nil } + end + end + end + + describe "version 11.31" do + describe "when interfaces are normal" do + let(:ifconfig_output) { my_fixture_read("1131_ifconfig_#{interface}") } + let(:lanscan_output) { my_fixture_read("1131_lanscan") } + + before :each do + described_class.stubs(:exec).returns(ifconfig_output) + end + + describe "lan1" do + let(:interface) { 'lan1' } + + describe "ipaddress" do + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to eq '10.1.54.36' } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + end + + describe "macaddress" do + let(:label) { 'macaddress' } + let(:macaddress) { '00:17:FD:2D:2A:57' } + + before :each do + described_class.expects(:lanscan).returns(lanscan_output) + end + + it { expect(value_for_interface_and_label).to eq macaddress } + end + + describe "mtu" do + let(:label) { 'mtu' } + + it { pending "(#17808) MTU has not been implemented for HP-UX" } + end + end + + describe "lan0" do + let(:interface) { 'lan0' } + + describe "ipaddress" do + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to eq '192.168.30.152' } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + end + + describe "macaddress" do + let(:label) { 'macaddress' } + let(:macaddress) { '00:12:31:7D:62:09' } + + before :each do + described_class.expects(:lanscan).returns(lanscan_output) + end + + it { expect(value_for_interface_and_label).to eq macaddress } + end + + describe "mtu" do + let(:label) { 'mtu' } + + it { pending "(#17808) MTU has not been implemented for HP-UX" } + end + end + + describe "lo0" do + let(:interface) { 'lo0' } + + describe "ipaddress" do + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to eq '127.0.0.1' } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.0.0.0' } + end + + describe "macaddress" do + let(:label) { 'macaddress' } + + before :each do + described_class.expects(:lanscan).returns(lanscan_output) + end + + it { expect(value_for_interface_and_label).to be_nil } + end + + describe "mtu" do + let(:label) { 'mtu' } + + it { pending "(#17808) MTU has not been implemented for HP-UX" } + end + end + end + + describe "when an interface has an asterisk appended" do + let(:lanscan_output) { my_fixture_read("1131_asterisk_lanscan") } + + let :ifconfig_output do + my_fixture_read "1131_asterisk_ifconfig_#{interface}" + end + + before :each do + described_class.stubs(:exec).returns(ifconfig_output) + end + + describe "lan1" do + let(:interface) { 'lan1' } + + describe "ipaddress" do + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to eq '10.10.0.5' } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + end + + describe "macaddress" do + let(:label) { 'macaddress' } + let(:macaddress) { '00:10:79:7B:BE:46' } + + before :each do + described_class.expects(:lanscan).returns(lanscan_output) + end + + it { expect(value_for_interface_and_label).to eq macaddress } + end + + describe "mtu" do + let(:label) { 'mtu' } + + it { pending "(#17808) MTU has not been implemented for HP-UX" } + end + end + + describe "lan0" do + let(:interface) { 'lan0' } + + describe "ipaddress" do + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to eq '192.168.3.9' } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + end + + describe "macaddress" do + let(:label) { 'macaddress' } + let(:macaddress) { '00:30:5D:06:26:B2' } + + before :each do + described_class.expects(:lanscan).returns(lanscan_output) + end + + it { expect(value_for_interface_and_label).to eq macaddress } + end + + describe "mtu" do + let(:label) { 'mtu' } + + it { pending "(#17808) MTU has not been implemented for HP-UX" } + end + end + + describe "lo0" do + let(:interface) { 'lo0' } + + describe "ipaddress" do + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to eq '127.0.0.1' } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.0.0.0' } + end + + describe "macaddress" do + let(:label) { 'macaddress' } + + before :each do + described_class.expects(:lanscan).returns(lanscan_output) + end + + it { expect(value_for_interface_and_label).to be_nil } + end + + describe "mtu" do + let(:label) { 'mtu' } + + it { pending "(#17808) MTU has not been implemented for HP-UX" } + end + end + end + + describe "when an interface is bonded and has one virtual interface" do + let(:lanscan_output) { my_fixture_read "1131_nic_bonding_lanscan" } + + let :ifconfig_output do + my_fixture_read "1131_nic_bonding_ifconfig_#{interface.sub(':', '_')}" + end + + before :each do + described_class.stubs(:exec).returns(ifconfig_output) + end + + describe "lan1" do + let(:interface) { 'lan1' } + + describe "ipaddress" do + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to eq '192.168.30.32' } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + end + + describe "macaddress" do + let(:label) { 'macaddress' } + let(:macaddress) { '00:12:81:9E:48:DE' } + + before :each do + described_class.expects(:lanscan).returns(lanscan_output) + end + + it { expect(value_for_interface_and_label).to eq macaddress } + end + + describe "mtu" do + let(:label) { 'mtu' } + + it { pending "(#17808) MTU has not been implemented for HP-UX" } + end + end + + describe "lo0" do + let(:interface) { 'lo0' } + + describe "ipaddress" do + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to eq '127.0.0.1' } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.0.0.0' } + end + + describe "macaddress" do + let(:label) { 'macaddress' } + + before :each do + described_class.expects(:lanscan).returns(lanscan_output) + end + + it { expect(value_for_interface_and_label).to be_nil } + end + + describe "mtu" do + let(:label) { 'mtu' } + + it { pending "(#17808) MTU has not been implemented for HP-UX" } + end + end + + describe "lan4" do + let(:interface) { 'lan4' } + + describe "ipaddress" do + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to eq '192.168.32.75' } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + end + + describe "macaddress" do + let(:label) { 'macaddress' } + let(:macaddress) { '00:12:81:9E:4A:7E' } + + before :each do + described_class.expects(:lanscan).returns(lanscan_output) + end + + it { expect(value_for_interface_and_label).to eq macaddress } + end + + describe "mtu" do + let(:label) { 'mtu' } + + it { pending "(#17808) MTU has not been implemented for HP-UX" } + end + end + + describe "lan4:1" do + let(:interface) { 'lan4:1' } + + describe "ipaddress" do + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to eq '192.168.1.197' } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + end + + describe "macaddress" do + let(:label) { 'macaddress' } + + before :each do + described_class.expects(:lanscan).returns(lanscan_output) + end + + it { expect(value_for_interface_and_label).to be_nil } + end + + describe "mtu" do + let(:label) { 'mtu' } + + it { pending "(#17808) MTU has not been implemented for HP-UX" } + end + end + end + end + end +end diff --git a/spec/unit/util/ip/linux_spec.rb b/spec/unit/util/ip/linux_spec.rb new file mode 100644 index 0000000000..85383115e7 --- /dev/null +++ b/spec/unit/util/ip/linux_spec.rb @@ -0,0 +1,158 @@ +# encoding: UTF-8 + +require 'spec_helper' +require 'facter/util/ip/linux' + +describe Facter::Util::IP::Linux do + before :each do + Facter.fact(:kernel).stubs(:value).returns("Linux") + end + + describe ".to_s" do + let :to_s do + described_class.to_s + end + + it "should be 'Linux'" do + expect(to_s).to eq 'Linux' + end + end + + describe ".convert_netmask_from_hex?" do + let :convert_netmask_from_hex? do + described_class.convert_netmask_from_hex? + end + + it "should be true" do + expect(convert_netmask_from_hex?).to be false + end + end + + describe ".bonding_master(interface)" do + let :bonding_master do + described_class.bonding_master(interface) + end + + describe "on interface aliases" do + let :interface do + "eth0:1" + end + + it { expect(bonding_master).to be_nil } + end + end + + describe ".interfaces" do + let :interfaces do + described_class.interfaces + end + + describe "without sysfs" do + let :ifconfig_output do + my_fixture_read("ifconfig_all_with_single_interface") + end + + before :each do + File.expects(:exist?).with('/sys/class/net').returns(false) + described_class.expects(:ifconfig_path).returns('/usr/bin/ifconfig') + described_class.expects(:exec).with(anything).returns(ifconfig_output) + end + + it "should return an array with a single interface and the loopback" do + expect(interfaces).to eq ["eth0", "lo"] + end + end + + describe "with sysfs" do + let :sysfs do + %w[/sys/class/net/eth0 /sys/class/net/lo] + end + + before :each do + File.expects(:exist?).with('/sys/class/net').returns(true) + Dir.expects(:glob).with('/sys/class/net/*').returns(sysfs) + end + + it "should return an array with a single interface and the loopback" do + expect(interfaces).to eq ["eth0", "lo"] + end + end + end + + describe "value_for_interface_and_label(interface, label)" do + let :value_for_interface_and_label do + described_class.value_for_interface_and_label interface, label + end + + describe "infiniband interface" do + let(:interface) { 'ib0' } + + let :ifconfig_output do + my_fixture_read "ifconfig_with_single_interface_ib0" + end + + describe "macaddress" do + let(:label) { 'macaddress' } + + before :each do + described_class.expects(:infiniband_macaddress).returns('bar') + end + + it { expect(value_for_interface_and_label).to eq 'bar' } + end + end + + describe "normal interface" do + let(:ifconfig_path) { '/usr/bin/ifconfig' } + let(:exec_cmd) { "#{ifconfig_path} #{interface} 2> /dev/null" } + + let(:ifconfig_output) do + my_fixture_read "ifconfig_single_interface_#{interface}" + end + + before :each do + described_class.expects(:ifconfig_path).returns(ifconfig_path) + described_class.expects(:exec).with(exec_cmd).returns(ifconfig_output) + end + + describe "eth0" do + let(:interface) { 'eth0' } + + describe "mtu" do + let(:label) { 'mtu' } + + it { expect(value_for_interface_and_label).to eq '1500' } + end + end + + describe "lo" do + let(:interface) { 'lo' } + + describe "mtu" do + let(:label) { 'mtu' } + + it { expect(value_for_interface_and_label).to eq '16436' } + end + end + end + + describe "bonded interface" do + let(:interface) { 'eth0' } + let(:bond) { 'bond0' } + let(:proc_net_path) { '/proc/net/bonding/bond0' } + + before :each do + proc_net = my_fixture_read "2_6_35_proc_net_bonding_#{bond}" + described_class.expects(:bonding_master).with(interface).returns(bond) + File.expects(:exists?).with(proc_net_path).returns(true) + File.expects(:read).with(proc_net_path).returns(proc_net) + end + + describe "macaddress" do + let(:label) { 'macaddress' } + + it { expect(value_for_interface_and_label).to eq "00:11:22:33:44:55" } + end + end + end +end diff --git a/spec/unit/util/ip/net_bsd_spec.rb b/spec/unit/util/ip/net_bsd_spec.rb new file mode 100644 index 0000000000..489e82b2b4 --- /dev/null +++ b/spec/unit/util/ip/net_bsd_spec.rb @@ -0,0 +1,34 @@ +# encoding: UTF-8 + +require 'spec_helper' +require 'facter/util/ip/net_bsd' + +describe Facter::Util::IP::NetBSD do + describe ".to_s" do + let :to_s do + described_class.to_s + end + + it "should be 'NetBSD'" do + expect(to_s).to eq 'NetBSD' + end + end + + describe ".convert_netmask_from_hex?" do + let :convert_netmask_from_hex? do + described_class.convert_netmask_from_hex? + end + + it "should be true" do + expect(convert_netmask_from_hex?).to be true + end + end + + describe ".bonding_master" do + let :bonding_master do + described_class.bonding_master('eth0') + end + + it { expect(bonding_master).to be_nil } + end +end diff --git a/spec/unit/util/ip/open_bsd_spec.rb b/spec/unit/util/ip/open_bsd_spec.rb new file mode 100644 index 0000000000..0a9508dc60 --- /dev/null +++ b/spec/unit/util/ip/open_bsd_spec.rb @@ -0,0 +1,34 @@ +# encoding: UTF-8 + +require 'spec_helper' +require 'facter/util/ip/open_bsd' + +describe Facter::Util::IP::OpenBSD do + describe ".to_s" do + let :to_s do + described_class.to_s + end + + it "should be 'OpenBSD'" do + expect(to_s).to eq 'OpenBSD' + end + end + + describe ".convert_netmask_from_hex?" do + let :convert_netmask_from_hex? do + described_class.convert_netmask_from_hex? + end + + it "should be true" do + expect(convert_netmask_from_hex?).to be true + end + end + + describe ".bonding_master" do + let :bonding_master do + described_class.bonding_master('eth0') + end + + it { expect(bonding_master).to be_nil } + end +end diff --git a/spec/unit/util/ip/sun_os_spec.rb b/spec/unit/util/ip/sun_os_spec.rb new file mode 100644 index 0000000000..23e366612b --- /dev/null +++ b/spec/unit/util/ip/sun_os_spec.rb @@ -0,0 +1,91 @@ +# encoding: UTF-8 + +require 'spec_helper' +require 'facter/util/ip/sun_os' + +describe Facter::Util::IP::SunOS do + before :each do + Facter.fact(:kernel).stubs(:value).with('SunOS') + end + + describe ".to_s" do + let :to_s do + described_class.to_s + end + + it "should be 'SunOS'" do + expect(to_s).to eq 'SunOS' + end + end + + describe ".convert_netmask_from_hex?" do + let :convert_netmask_from_hex? do + described_class.convert_netmask_from_hex? + end + + it "should be true" do + expect(convert_netmask_from_hex?).to be true + end + end + + describe ".bonding_master" do + let :bonding_master do + described_class.bonding_master('eth0') + end + + it { expect(bonding_master).to be_nil } + end + + describe ".interfaces" do + let :interfaces do + described_class.interfaces + end + + let :ifconfig_output do + my_fixture_read("ifconfig_all_with_multiple_interfaces") + end + + before :each do + described_class.expects(:ifconfig_path).returns('/usr/bin/ifconfig') + described_class.expects(:exec).with(anything).returns(ifconfig_output) + end + + it "should return an array with two interfaces" do + expect(interfaces).to eq ["lo0", "e1000g0"] + end + end + + describe ".value_for_interface_and_label(interface, label)" do + let :value_for_interface_and_label do + described_class.value_for_interface_and_label interface, label + end + + let(:interface) { 'e1000g0' } + let(:ifconfig_output) { my_fixture_read "ifconfig_single_interface" } + let(:ifconfig_path) { "/usr/bin/ifconfig" } + let(:exec_cmd) { "#{ifconfig_path} #{interface}" } + + before :each do + described_class.expects(:ifconfig_path).returns(ifconfig_path) + described_class.expects(:exec).with(exec_cmd).returns(ifconfig_output) + end + + describe "ipaddress" do + let(:label) { "ipaddress" } + + it { expect(value_for_interface_and_label).to eq "172.16.15.138" } + end + + describe "netmask" do + let(:label) { "netmask" } + + it { expect(value_for_interface_and_label).to eq "255.255.255.0" } + end + + describe "mtu" do + let(:label) { "mtu" } + + it { expect(value_for_interface_and_label).to eq '1500' } + end + end +end diff --git a/spec/unit/util/ip/windows_spec.rb b/spec/unit/util/ip/windows_spec.rb new file mode 100644 index 0000000000..09fc3fb68c --- /dev/null +++ b/spec/unit/util/ip/windows_spec.rb @@ -0,0 +1,94 @@ +# encoding: UTF-8 + +require 'spec_helper' +require 'facter/util/ip/windows' + +describe Facter::Util::IP::Windows do + before :each do + Facter.fact(:kernel).stubs(:value).returns('windows') + end + + describe ".to_s" do + let(:to_s) { described_class.to_s } + + it { expect(to_s).to eq 'windows' } + end + + describe ".convert_netmask_from_hex?" do + let :convert_netmask_from_hex? do + described_class.convert_netmask_from_hex? + end + + it { expect(convert_netmask_from_hex?).to be false } + end + + describe ".bonding_master" do + let(:bonding_master) { described_class.bonding_master('eth0') } + + it { expect(bonding_master).to be_nil } + end + + describe ".interfaces" do + let(:interfaces) { described_class.interfaces } + + let(:netsh_output) { my_fixture_read "netsh_all_interfaces" } + + let :expected_interfaces do + [ + "Loopback Pseudo-Interface 1", + "Local Area Connection", + "Teredo Tunneling Pseudo-Interface" + ] + end + + before :each do + described_class.expects(:exec).with(anything).returns(netsh_output) + end + + it "should return an array of only connected interfaces" do + expect(interfaces).to eq expected_interfaces + end + end + + describe "value_for_interface_and_label(interface, label)" do + let(:interface) { 'Local Area Connection' } + let(:netsh_output) { my_fixture_read("netsh_with_single_interface") } + + let :value_for_interface_and_label do + described_class.value_for_interface_and_label interface, label + end + + let(:exec_cmd) do + "#{described_class::NETSH} interface ip show address \"#{interface}\"" + end + + before :each do + described_class.expects(:exec).with(exec_cmd).returns(netsh_output) + end + + describe "ipaddress" do + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to eq '172.16.138.216' } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + end + + describe "ipaddress6" do + let(:interface) { 'Teredo Tunneling Pseudo-Interface' } + let(:label) { 'ipaddress6' } + let(:expected_ip) { '2001:0:4137:9e76:2087:77a:53ef:7527' } + let(:netsh_output) { my_fixture_read("netsh_with_single_interface6") } + + let(:exec_cmd) do + "#{described_class::NETSH} interface ipv6 show address \"#{interface}\"" + end + + it { expect(value_for_interface_and_label).to eq expected_ip } + end + end +end diff --git a/spec/unit/util/ip_spec.rb b/spec/unit/util/ip_spec.rb index 3d95ee5cd7..1707d065c3 100755 --- a/spec/unit/util/ip_spec.rb +++ b/spec/unit/util/ip_spec.rb @@ -4,418 +4,56 @@ require 'facter/util/ip' describe Facter::Util::IP do - include FacterSpec::ConfigHelper - - before :each do - given_a_configuration_of(:is_windows => false) - end - - [:freebsd, :linux, :netbsd, :openbsd, :sunos, :darwin, :"hp-ux", :"gnu/kfreebsd", :windows].each do |platform| + let :interfaces_hash do + { + :eth0 => { + :ipaddress => 1, + :ipaddress6 => 2, + :macaddress => 3, + :netmask => 4, + :mtu => 5, + :network => 6 + }, + + :lo0 => { + :ipaddress => 7, + :ipaddress6 => 8, + :macaddress => 9, + :netmask => 10, + :mtu => 11, + :network => 12 + } + } + end + + let :interfaces_hash2 do + { + :en1 => { + :ipaddress => 13, + :ipaddress6 => 14, + :macaddress => 15, + :netmask => 16, + :mtu => 17, + :network => 18 + }, + + :lo => { + :ipaddress => 19, + :ipaddress6 => 20, + :macaddress => 21, + :netmask => 22, + :mtu => 23, + :network => 24 + } + } + end + + %w{ + FreeBSD Linux NetBSD OpenBSD SunOS Darwin HP-UX GNU/kFreeBSD windows + Dragonfly + }.each do |platform| it "should be supported on #{platform}" do - given_a_configuration_of(:is_windows => platform == :windows) - Facter::Util::IP.supported_platforms.should be_include(platform) - end - end - - it "should return a list of interfaces" do - Facter::Util::IP.should respond_to(:get_interfaces) - end - - it "should return an empty list of interfaces on an unknown kernel" do - File.stubs(:exist?).with('/sys/class/net').returns(false) - Facter.stubs(:value).returns("UnknownKernel") - Facter::Util::IP.get_interfaces().should == [] - end - - it "should return a list with a single interface and the loopback interface on Linux with a single interface without sysfs" do - File.stubs(:exist?).with('/sys/class/net').returns(false) - linux_ifconfig = my_fixture_read("linux_ifconfig_all_with_single_interface") - Facter::Util::IP.stubs(:get_all_interface_output).returns(linux_ifconfig) - Facter::Util::IP.get_interfaces().should =~ ["eth0", "lo"] - end - - it "should return a list two interfaces on Darwin with two interfaces" do - File.stubs(:exist?).with('/sys/class/net').returns(false) - darwin_ifconfig = my_fixture_read("darwin_ifconfig_all_with_multiple_interfaces") - Facter::Util::IP.stubs(:get_all_interface_output).returns(darwin_ifconfig) - Facter::Util::IP.get_interfaces().should == ["lo0", "en0"] - end - - it "should return a list two interfaces on Solaris with two interfaces multiply reporting" do - File.stubs(:exist?).with('/sys/class/net').returns(false) - solaris_ifconfig = my_fixture_read("solaris_ifconfig_all_with_multiple_interfaces") - Facter::Util::IP.stubs(:get_all_interface_output).returns(solaris_ifconfig) - Facter::Util::IP.get_interfaces().should == ["lo0", "e1000g0"] - end - - it "should return a list of six interfaces on a GNU/kFreeBSD with six interfaces" do - File.stubs(:exist?).with('/sys/class/net').returns(false) - kfreebsd_ifconfig = my_fixture_read("debian_kfreebsd_ifconfig") - Facter::Util::IP.stubs(:get_all_interface_output).returns(kfreebsd_ifconfig) - Facter::Util::IP.get_interfaces().should == ["em0", "em1", "bge0", "bge1", "lo0", "vlan0"] - end - - it "should return a list of only connected interfaces on Windows" do - File.stubs(:exist?).with('/sys/class/net').returns(false) - Facter.fact(:kernel).stubs(:value).returns("windows") - windows_netsh = my_fixture_read("windows_netsh_all_interfaces") - Facter::Util::IP.stubs(:get_all_interface_output).returns(windows_netsh) - Facter::Util::IP.get_interfaces().should == ["Loopback Pseudo-Interface 1", "Local Area Connection", "Teredo Tunneling Pseudo-Interface"] - end - - it "should return a value for a specific interface" do - Facter::Util::IP.should respond_to(:get_interface_value) - end - - it "should not return interface information for unsupported platforms" do - Facter.stubs(:value).with(:kernel).returns("bleah") - Facter::Util::IP.get_interface_value("e1000g0", "netmask").should == [] - end - - it "should return ipaddress information for Solaris" do - solaris_ifconfig_interface = my_fixture_read("solaris_ifconfig_single_interface") - - Facter::Util::IP.expects(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("SunOS") - - Facter::Util::IP.get_interface_value("e1000g0", "ipaddress").should == "172.16.15.138" - end - - it "should return netmask information for Solaris" do - solaris_ifconfig_interface = my_fixture_read("solaris_ifconfig_single_interface") - - Facter::Util::IP.expects(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("SunOS") - - Facter::Util::IP.get_interface_value("e1000g0", "netmask").should == "255.255.255.0" - end - - it "should return calculated network information for Solaris" do - solaris_ifconfig_interface = my_fixture_read("solaris_ifconfig_single_interface") - - Facter::Util::IP.stubs(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("SunOS") - - Facter::Util::IP.get_network_value("e1000g0").should == "172.16.15.0" - end - - it "should return macaddress with leading zeros stripped off for GNU/kFreeBSD" do - kfreebsd_ifconfig = my_fixture_read("debian_kfreebsd_ifconfig") - - Facter::Util::IP.expects(:get_single_interface_output).with("em0").returns(kfreebsd_ifconfig) - Facter.stubs(:value).with(:kernel).returns("GNU/kFreeBSD") - - Facter::Util::IP.get_interface_value("em0", "macaddress").should == "0:11:a:59:67:90" - end - - it "should return interface information for FreeBSD supported via an alias" do - ifconfig_interface = my_fixture_read("6.0-STABLE_FreeBSD_ifconfig") - - Facter::Util::IP.expects(:get_single_interface_output).with("fxp0").returns(ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("FreeBSD") - - Facter::Util::IP.get_interface_value("fxp0", "macaddress").should == "00:0e:0c:68:67:7c" - end - - it "should return macaddress information for OS X" do - ifconfig_interface = my_fixture_read("Mac_OS_X_10.5.5_ifconfig") - - Facter::Util::IP.expects(:get_single_interface_output).with("en1").returns(ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("Darwin") - - Facter::Util::IP.get_interface_value("en1", "macaddress").should == "00:1b:63:ae:02:66" - end - - it "should return all interfaces correctly on OS X" do - File.stubs(:exist?).with('/sys/class/net').returns(false) - ifconfig_interface = my_fixture_read("Mac_OS_X_10.5.5_ifconfig") - - Facter::Util::IP.expects(:get_all_interface_output).returns(ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("Darwin") - - Facter::Util::IP.get_interfaces().should == ["lo0", "gif0", "stf0", "en0", "fw0", "en1", "vmnet8", "vmnet1"] - end - - it "should return a human readable netmask on Solaris" do - solaris_ifconfig_interface = my_fixture_read("solaris_ifconfig_single_interface") - - Facter::Util::IP.expects(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("SunOS") - - Facter::Util::IP.get_interface_value("e1000g0", "netmask").should == "255.255.255.0" - end - - it "should return a human readable netmask on Darwin" do - darwin_ifconfig_interface = my_fixture_read("darwin_ifconfig_single_interface") - - Facter::Util::IP.expects(:get_single_interface_output).with("en1").returns(darwin_ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("Darwin") - - Facter::Util::IP.get_interface_value("en1", "netmask").should == "255.255.255.0" - end - - it "should return a human readable netmask on GNU/kFreeBSD" do - kfreebsd_ifconfig = my_fixture_read("debian_kfreebsd_ifconfig") - - Facter::Util::IP.expects(:get_single_interface_output).with("em1").returns(kfreebsd_ifconfig) - Facter.stubs(:value).with(:kernel).returns("GNU/kFreeBSD") - - Facter::Util::IP.get_interface_value("em1", "netmask").should == "255.255.255.0" - end - - it "should return correct macaddress information for infiniband on Linux" do - correct_ifconfig_interface = my_fixture_read("linux_get_single_interface_ib0") - - Facter::Util::IP.expects(:get_single_interface_output).with("ib0").returns(correct_ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("Linux") - - Facter::Util::IP.get_interface_value("ib0", "macaddress").should == "80:00:00:4a:fe:80:00:00:00:00:00:00:00:02:c9:03:00:43:27:21" - end - - it "should replace the incorrect macaddress with the correct macaddress in ifconfig for infiniband on Linux" do - ifconfig_interface = my_fixture_read("linux_ifconfig_ib0") - correct_ifconfig_interface = my_fixture_read("linux_get_single_interface_ib0") - - Facter::Util::IP.expects(:get_infiniband_macaddress).with("ib0").returns("80:00:00:4a:fe:80:00:00:00:00:00:00:00:02:c9:03:00:43:27:21") - Facter::Util::IP.expects(:ifconfig_interface).with("ib0").returns(ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("Linux") - - Facter::Util::IP.get_single_interface_output("ib0").should == correct_ifconfig_interface - end - - it "should return fake macaddress information for infiniband on Linux when neither sysfs or /sbin/ip are available" do - ifconfig_interface = my_fixture_read("linux_ifconfig_ib0") - - File.expects(:exists?).with("/sys/class/net/ib0/address").returns(false) - File.expects(:exists?).with("/sbin/ip").returns(false) - Facter::Util::IP.expects(:ifconfig_interface).with("ib0").returns(ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("Linux") - - Facter::Util::IP.get_interface_value("ib0", "macaddress").should == "FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF" - end - - it "should not get bonding master on interface aliases" do - Facter.stubs(:value).with(:kernel).returns("Linux") - - Facter::Util::IP.get_bonding_master("eth0:1").should be_nil - end - - [:freebsd, :netbsd, :openbsd, :sunos, :darwin, :"hp-ux"].each do |platform| - it "should require conversion from hex on #{platform}" do - Facter::Util::IP.convert_from_hex?(platform).should == true - end - end - - [:windows].each do |platform| - it "should not require conversion from hex on #{platform}" do - Facter::Util::IP.convert_from_hex?(platform).should be_false - end - end - - it "should return an arp address on Linux" do - Facter.stubs(:value).with(:kernel).returns("Linux") - - Facter::Util::IP.expects(:get_arp_value).with("eth0").returns("00:00:0c:9f:f0:04") - Facter::Util::IP.get_arp_value("eth0").should == "00:00:0c:9f:f0:04" - end - - it "should return mtu information on Linux" do - linux_ifconfig = my_fixture_read("linux_ifconfig_all_with_single_interface") - Facter::Util::IP.stubs(:get_all_interface_output).returns(linux_ifconfig) - Facter::Util::IP.stubs(:get_single_interface_output).with("eth0"). - returns(my_fixture_read("linux_get_single_interface_eth0")) - Facter::Util::IP.stubs(:get_single_interface_output).with("lo"). - returns(my_fixture_read("linux_get_single_interface_lo")) - Facter.stubs(:value).with(:kernel).returns("Linux") - - Facter::Util::IP.get_interface_value("eth0", "mtu").should == "1500" - Facter::Util::IP.get_interface_value("lo", "mtu").should == "16436" - end - - it "should return mtu information on Darwin" do - darwin_ifconfig_interface = my_fixture_read("darwin_ifconfig_single_interface") - - Facter::Util::IP.expects(:get_single_interface_output).with("en1").returns(darwin_ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("Darwin") - - Facter::Util::IP.get_interface_value("en1", "mtu").should == "1500" - end - - it "should return mtu information for Solaris" do - solaris_ifconfig_interface = my_fixture_read("solaris_ifconfig_single_interface") - - Facter::Util::IP.expects(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("SunOS") - - Facter::Util::IP.get_interface_value("e1000g0", "mtu").should == "1500" - end - - # (#17487) - tests for HP-UX. - # some fake data for testing robustness of regexps. - def self.fake_netstat_in_examples - examples = [] - examples << ["Header row\na line with none in it\na line without\nanother line without\n", - "a line without\nanother line without\n"] - examples << ["Header row\na line without\na line with none in it\nanother line with none\nanother line without\n", - "a line without\nanother line without\n"] - examples << ["Header row\na line with * asterisks *\na line with none in it\nanother line without\n", - "a line with asterisks \nanother line without\n"] - examples << ["a line with none none none in it\na line with none in it\na line without\nanother line without\n", - "another line without\n"] - examples - end - - fake_netstat_in_examples.each_with_index do |example, i| - input, expected_output = example - it "should pass regexp test on fake netstat input example #{i}" do - Facter.stubs(:value).with(:kernel).returns("HP-UX") - Facter::Util::IP.stubs(:hpux_netstat_in).returns(input) - Facter::Util::IP.get_all_interface_output().should == expected_output - end - end - - # and some real data for exhaustive tests. - def self.hpux_examples - examples = [] - examples << ["HP-UX 11.11", - ["lan1", "lan0", "lo0" ], - ["1500", "1500", "4136" ], - ["10.1.1.6", "192.168.3.10", "127.0.0.1"], - ["255.255.255.0", "255.255.255.0", "255.0.0.0"], - ["00:10:79:7B:5C:DE", "00:30:7F:0C:79:DC", nil ], - [my_fixture_read("hpux_1111_ifconfig_lan1"), - my_fixture_read("hpux_1111_ifconfig_lan0"), - my_fixture_read("hpux_1111_ifconfig_lo0")], - my_fixture_read("hpux_1111_netstat_in"), - my_fixture_read("hpux_1111_lanscan")] - - examples << ["HP-UX 11.31", - ["lan1", "lan0", "lo0" ], - ["1500", "1500", "4136" ], - ["10.1.54.36", "192.168.30.152", "127.0.0.1"], - ["255.255.255.0", "255.255.255.0", "255.0.0.0"], - ["00:17:FD:2D:2A:57", "00:12:31:7D:62:09", nil ], - [my_fixture_read("hpux_1131_ifconfig_lan1"), - my_fixture_read("hpux_1131_ifconfig_lan0"), - my_fixture_read("hpux_1131_ifconfig_lo0")], - my_fixture_read("hpux_1131_netstat_in"), - my_fixture_read("hpux_1131_lanscan")] - - examples << ["HP-UX 11.31 with an asterisk after a NIC that has an address", - ["lan1", "lan0", "lo0" ], - ["1500", "1500", "4136" ], - ["10.10.0.5", "192.168.3.9", "127.0.0.1"], - ["255.255.255.0", "255.255.255.0", "255.0.0.0"], - ["00:10:79:7B:BE:46", "00:30:5D:06:26:B2", nil ], - [my_fixture_read("hpux_1131_asterisk_ifconfig_lan1"), - my_fixture_read("hpux_1131_asterisk_ifconfig_lan0"), - my_fixture_read("hpux_1131_asterisk_ifconfig_lo0")], - my_fixture_read("hpux_1131_asterisk_netstat_in"), - my_fixture_read("hpux_1131_asterisk_lanscan")] - - examples << ["HP-UX 11.31 with NIC bonding and one virtual NIC", - ["lan4:1", "lan1", "lo0", "lan4" ], - ["1500", "1500", "4136", "1500" ], - ["192.168.1.197", "192.168.30.32", "127.0.0.1", "192.168.32.75" ], - ["255.255.255.0", "255.255.255.0", "255.0.0.0", "255.255.255.0" ], - [nil, "00:12:81:9E:48:DE", nil, "00:12:81:9E:4A:7E"], - [my_fixture_read("hpux_1131_nic_bonding_ifconfig_lan4_1"), - my_fixture_read("hpux_1131_nic_bonding_ifconfig_lan1"), - my_fixture_read("hpux_1131_nic_bonding_ifconfig_lo0"), - my_fixture_read("hpux_1131_nic_bonding_ifconfig_lan4")], - my_fixture_read("hpux_1131_nic_bonding_netstat_in"), - my_fixture_read("hpux_1131_nic_bonding_lanscan")] - examples - end - - hpux_examples.each do |example| - description, array_of_expected_ifs, array_of_expected_mtus, - array_of_expected_ips, array_of_expected_netmasks, - array_of_expected_macs, array_of_ifconfig_fixtures, - netstat_in_fixture, lanscan_fixture = example - - it "should return a list three interfaces on #{description}" do - Facter.expects(:value).with(:kernel).at_least_once.returns("HP-UX") - File.expects(:exist?).with('/sys/class/net').returns(false) - Facter::Util::IP.expects(:hpux_netstat_in).returns(netstat_in_fixture) - Facter::Util::IP.get_interfaces.should == array_of_expected_ifs - end - - array_of_expected_ifs.each_with_index do |nic, i| - ifconfig_fixture = array_of_ifconfig_fixtures[i] - expected_mtu = array_of_expected_mtus[i] - expected_ip = array_of_expected_ips[i] - expected_netmask = array_of_expected_netmasks[i] - expected_mac = array_of_expected_macs[i] - - # (#17808) These tests fail because MTU facts haven't been implemented for HP-UX. - #it "should return MTU #{expected_mtu} on #{nic} for #{description} example" do - # Facter.expects(:value).with(:kernel).at_least_once.returns("HP-UX") - # Facter::Util::IP.expects(:hpux_netstat_in).returns(netstat_in_fixture) - # Facter::Util::IP.expects(:hpux_lanscan).returns(lanscan_fixture) - # Facter::Util::IP.expects(:hpux_ifconfig_interface).with(nic).returns(ifconfig_fixture) - # Facter::Util::IP.get_interface_value(nic, "mtu").should == expected_mtu - #end - - it "should return IP #{expected_ip} on #{nic} for #{description} example" do - Facter.expects(:value).with(:kernel).at_least_once.returns("HP-UX") - Facter::Util::IP.expects(:hpux_lanscan).returns(lanscan_fixture) - Facter::Util::IP.expects(:hpux_ifconfig_interface).with(nic).returns(ifconfig_fixture) - Facter::Util::IP.get_interface_value(nic, "ipaddress").should == expected_ip - end - - it "should return netmask #{expected_netmask} on #{nic} for #{description} example" do - Facter.expects(:value).with(:kernel).at_least_once.returns("HP-UX") - Facter::Util::IP.expects(:hpux_lanscan).returns(lanscan_fixture) - Facter::Util::IP.expects(:hpux_ifconfig_interface).with(nic).returns(ifconfig_fixture) - Facter::Util::IP.get_interface_value(nic, "netmask").should == expected_netmask - end - - it "should return MAC address #{expected_mac} on #{nic} for #{description} example" do - Facter.expects(:value).with(:kernel).at_least_once.returns("HP-UX") - Facter::Util::IP.expects(:hpux_lanscan).returns(lanscan_fixture) - Facter::Util::IP.expects(:hpux_ifconfig_interface).with(nic).returns(ifconfig_fixture) - Facter::Util::IP.get_interface_value(nic, "macaddress").should == expected_mac - end - end - end - - describe "on Windows" do - before :each do - Facter.stubs(:value).with(:kernel).returns("windows") - end - - it "should return ipaddress information" do - windows_netsh = my_fixture_read("windows_netsh_single_interface") - - Facter::Util::IP.expects(:get_output_for_interface_and_label).with("Local Area Connection", "ipaddress").returns(windows_netsh) - - Facter::Util::IP.get_interface_value("Local Area Connection", "ipaddress").should == "172.16.138.216" - end - - it "should return a human readable netmask" do - windows_netsh = my_fixture_read("windows_netsh_single_interface") - - Facter::Util::IP.expects(:get_output_for_interface_and_label).with("Local Area Connection", "netmask").returns(windows_netsh) - - Facter::Util::IP.get_interface_value("Local Area Connection", "netmask").should == "255.255.255.0" - end - - it "should return network information" do - windows_netsh = my_fixture_read("windows_netsh_single_interface") - - Facter::Util::IP.stubs(:get_output_for_interface_and_label).with("Local Area Connection", "ipaddress").returns(windows_netsh) - Facter::Util::IP.stubs(:get_output_for_interface_and_label).with("Local Area Connection", "netmask").returns(windows_netsh) - - Facter::Util::IP.get_network_value("Local Area Connection").should == "172.16.138.0" - end - - it "should return ipaddress6 information" do - windows_netsh = my_fixture_read("windows_netsh_single_interface6") - - Facter::Util::IP.expects(:get_output_for_interface_and_label).with("Teredo Tunneling Pseudo-Interface", "ipaddress6").returns(windows_netsh) - - Facter::Util::IP.get_interface_value("Teredo Tunneling Pseudo-Interface", "ipaddress6").should == "2001:0:4137:9e76:2087:77a:53ef:7527" + Facter::Util::IP.new.supported_platforms.should include platform end end @@ -440,44 +78,112 @@ def self.hpux_examples Facter::Util::IP.exec_ifconfig(["-a","-e","-i","-j"]) end end - describe "get_ifconfig" do - it "assigns /sbin/ifconfig if it is executable" do - File.stubs(:executable?).returns(false) - File.stubs(:executable?).with("/sbin/ifconfig").returns(true) - Facter::Util::IP.get_ifconfig.should eq("/sbin/ifconfig") + + describe ".add_interface_facts" do + before :each do + given_initial_interfaces_facts + described_class.add_interface_facts end - it "assigns /usr/sbin/ifconfig if it is executable" do - File.stubs(:executable?).returns(false) - File.stubs(:executable?).with("/usr/sbin/ifconfig").returns(true) - Facter::Util::IP.get_ifconfig.should eq("/usr/sbin/ifconfig") + + it "defines the 'interfaces' fact" do + expect(Facter.fact(:interfaces)).to be_a_kind_of Facter::Util::Fact end - it "assigns /bin/ifconfig if it is executable" do - File.stubs(:executable?).returns(false) - File.stubs(:executable?).with("/bin/ifconfig").returns(true) - Facter::Util::IP.get_ifconfig.should eq("/bin/ifconfig") + + it "defines a fact for each attribute of an interface" do + interfaces_hash.keys.each do |interface| + described_class::INTERFACE_KEYS.each do |attr| + expect(Facter.fact("#{attr}_#{interface}")). + to be_a_kind_of Facter::Util::Fact + end + end + end + + it "defines a fact for an interface's network" do + interfaces_hash.keys.each do |interface| + expect(Facter.fact("network_#{interface}")). + to be_a_kind_of Facter::Util::Fact + end end end - context "with bonded ethernet interfaces on Linux" do - before :each do - Facter.fact(:kernel).stubs(:value).returns("Linux") + describe "multiple facts sharing a single model" do + describe "when interfaces are resolved for the first time" do + before :each do + given_initial_interfaces_facts + Facter.value(:interfaces) + end + + it 'lists the interfaces for the interfaces fact' do + expect(Facter.value(:interfaces)).to eq interfaces_hash.keys.join(',') + end + + it 'defines dynamic facts for the interfaces' do + interfaces_hash.keys.each do |interface| + described_class::INTERFACE_KEYS.each do |attr| + expect(Facter.value("#{attr}_#{interface}")). + to eq interfaces_hash[interface][attr] + end + end + end + + it 'defines a dynamic fact for the interfaces networks' do + interfaces_hash.keys.each do |interface| + expect(Facter.value("network_#{interface}")). + to eq interfaces_hash[interface][:network] + end + end end - describe "Facter::Util::Ip.get_interface_value" do + describe "when interface facts have been flushed after being resolved" do before :each do - Facter::Util::IP.stubs(:read_proc_net_bonding). - with("/proc/net/bonding/bond0"). - returns(my_fixture_read("linux_2_6_35_proc_net_bonding_bond0")) + given_initial_interfaces_facts + when_interfaces_facts_have_been_resolved_then_flushed + end + + it "updates the interfaces fact" do + expect(Facter.value(:interfaces)).to eq interfaces_hash2.keys.join(',') + end - Facter::Util::IP.stubs(:get_bonding_master).returns("bond0") + it "defines new dynamic facts for the new interfaces attributes" do + interfaces_hash2.keys.each do |interface| + described_class::INTERFACE_KEYS.each do |attr| + expect(Facter.value("#{attr}_#{interface}")). + to eq interfaces_hash2[interface][attr] + end + end end - it 'provides the real device macaddress for eth0' do - Facter::Util::IP.get_interface_value("eth0", "macaddress").should == "00:11:22:33:44:55" + it "defines a new dynamic fact for the new interfaces network" do + interfaces_hash2.keys.each do |interface| + expect(Facter.value("network_#{interface}")). + to eq interfaces_hash2[interface][:network] + end end - it 'provides the real device macaddress for eth1' do - Facter::Util::IP.get_interface_value("eth1", "macaddress").should == "00:11:22:33:44:56" + end + end + + def given_initial_interfaces_facts + model = described_class.new + model.stubs(:interfaces).returns(interfaces_hash.keys) + model.stubs(:parse!) + model.interfaces_hash = interfaces_hash + described_class.stubs(:new).returns(model) + end + + def when_interfaces_facts_have_been_resolved_then_flushed + interfaces_hash.keys.each do |interface| + described_class::INTERFACE_KEYS.each do |attr| + Facter.value("#{attr}_#{interface}") end end + + model = described_class.new + model.stubs(:interfaces).returns(interfaces_hash2.keys) + model.stubs(:parse!) + model.interfaces_hash = interfaces_hash2 + described_class.stubs(:new).returns(model) + + Facter.flush + Facter.value(:interfaces) end end diff --git a/spec/unit/util/parser_spec.rb b/spec/unit/util/parser_spec.rb index be7b9e7f4d..8a0ae6ee48 100644 --- a/spec/unit/util/parser_spec.rb +++ b/spec/unit/util/parser_spec.rb @@ -1,10 +1,7 @@ -#!/usr/bin/env ruby - require 'spec_helper' - require 'facter/util/parser' require 'tempfile' -require 'tmpdir.rb' +require 'tmpdir' describe Facter::Util::Parser do include PuppetlabsSpec::Files diff --git a/spec/unit/zfs_version_spec.rb b/spec/unit/zfs_version_spec.rb index 905dbc9ee5..9b88392a31 100644 --- a/spec/unit/zfs_version_spec.rb +++ b/spec/unit/zfs_version_spec.rb @@ -1,5 +1,3 @@ -#!/usr/bin/env ruby - require 'spec_helper' describe "zfs_version fact" do diff --git a/spec/unit/zpool_version_spec.rb b/spec/unit/zpool_version_spec.rb index 0cba70fdb6..5952b8f7b0 100644 --- a/spec/unit/zpool_version_spec.rb +++ b/spec/unit/zpool_version_spec.rb @@ -1,5 +1,3 @@ -#!/usr/bin/env ruby - require 'spec_helper' describe "zpool_version fact" do From a8f37a701d7b69be870da7790904babddfb774c4 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Mon, 15 Apr 2013 17:25:34 -0400 Subject: [PATCH 1236/3753] (maint) Avoid executing ifconfig from the specs Without this patch the spec examples will execute /sbin/ifconfig. This is a problem because the test will fail on platform which do not have ifconfig, like Windows. This patch addresses the problem by expecting Facter::Util::Resolution.exec is called, preventing the system command from being executed. The patch also re-words the example to describe the exact behavior being tested. --- lib/facter/util/ip.rb | 2 +- spec/unit/util/ip_spec.rb | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index fe235c017c..9d38d3ea48 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -100,7 +100,7 @@ def interfaces # # @api private def self.exec_ifconfig(additional_arguments=[]) - Facter::Util::Resolution.exec("#{self.get_ifconfig} #{additional_arguments.join(' ')}") + Facter::Util::Resolution.exec([self.get_ifconfig.to_s, *additional_arguments].join(' ')) end # Looks up the ifconfig binary. diff --git a/spec/unit/util/ip_spec.rb b/spec/unit/util/ip_spec.rb index 1707d065c3..7bf392747a 100755 --- a/spec/unit/util/ip_spec.rb +++ b/spec/unit/util/ip_spec.rb @@ -58,9 +58,9 @@ end describe "exec_ifconfig" do - it "uses get_ifconfig" do - Facter::Util::IP.stubs(:get_ifconfig).returns("/sbin/ifconfig").once - + it "executes /sbin/ifconfig when get_ifconfig() returns /sbin/ifconfig" do + Facter::Util::IP.expects(:get_ifconfig).returns("/sbin/ifconfig") + Facter::Util::Resolution.expects(:exec).with("/sbin/ifconfig") Facter::Util::IP.exec_ifconfig end it "support additional arguments" do From 79653bca3bf618adea5f1b50d112e537e0736aab Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Mon, 15 Apr 2013 17:31:40 -0400 Subject: [PATCH 1237/3753] (maint) Fix Facter::Util::IP.exec_ifconfig examples Without this patch the Facter::Util::IP.exec_ifconfig examples only set stub methods, they don't actually set expectations that ifconfig is executed as the examples describe. This patch addresses the problem by updating the examples to reflect the intent of the behavior expressed in the description of the example. --- spec/unit/util/ip_spec.rb | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/spec/unit/util/ip_spec.rb b/spec/unit/util/ip_spec.rb index 7bf392747a..00cc854e41 100755 --- a/spec/unit/util/ip_spec.rb +++ b/spec/unit/util/ip_spec.rb @@ -63,18 +63,17 @@ Facter::Util::Resolution.expects(:exec).with("/sbin/ifconfig") Facter::Util::IP.exec_ifconfig end - it "support additional arguments" do - Facter::Util::IP.stubs(:get_ifconfig).returns("/sbin/ifconfig") - Facter::Util::Resolution.stubs(:exec).with("/sbin/ifconfig -a") + it "executes `ifconfig -a` when given ['-a']" do + Facter::Util::IP.stubs(:get_ifconfig).returns("/sbin/ifconfig") + Facter::Util::Resolution.expects(:exec).with("/sbin/ifconfig -a") Facter::Util::IP.exec_ifconfig(["-a"]) end - it "joins multiple arguments correctly" do + it "executes `ifconfig -a -e -i -j` when given ['-a', '-e', '-i', '-j]" do Facter::Util::IP.stubs(:get_ifconfig).returns("/sbin/ifconfig") - Facter::Util::Resolution.stubs(:exec).with("/sbin/ifconfig -a -e -i -j") - + Facter::Util::Resolution.expects(:exec).with("/sbin/ifconfig -a -e -i -j") Facter::Util::IP.exec_ifconfig(["-a","-e","-i","-j"]) end end From cc67a355889e8796eadfe750cbb662c158cc46b4 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Tue, 23 Apr 2013 15:23:10 -0700 Subject: [PATCH 1238/3753] (maint) Silence no facts loaded warning in specs Without this patch the mocked fact value in some of the tests are producing a warning that no facts are loaded. This is a problem because the warning is a red herring side effect of the mocking itself. This patch addresses the problem by stubbing the Facter.warnonce method for these specific examples that have no facts loaded at the time of setting the expectation. --- spec/unit/memory_spec.rb | 2 ++ spec/unit/util/ip_spec.rb | 2 ++ 2 files changed, 4 insertions(+) diff --git a/spec/unit/memory_spec.rb b/spec/unit/memory_spec.rb index 1771a53e20..975e422e90 100755 --- a/spec/unit/memory_spec.rb +++ b/spec/unit/memory_spec.rb @@ -515,6 +515,8 @@ end it "should use the memorysize fact for the memorytotal fact" do + Facter.stubs(:warnonce) + Facter.fact("memorysize").expects(:value).once.returns "yay" Facter::Util::Resolution.expects(:exec).never Facter.fact("memorytotal").value.should == "yay" diff --git a/spec/unit/util/ip_spec.rb b/spec/unit/util/ip_spec.rb index 00cc854e41..c44c0613c1 100755 --- a/spec/unit/util/ip_spec.rb +++ b/spec/unit/util/ip_spec.rb @@ -135,6 +135,8 @@ describe "when interface facts have been flushed after being resolved" do before :each do + Facter.stubs(:warnonce) + given_initial_interfaces_facts when_interfaces_facts_have_been_resolved_then_flushed end From 7ac859d9d7717dbceed678274bfa650e7327025c Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Tue, 23 Apr 2013 15:26:17 -0700 Subject: [PATCH 1239/3753] (maint) Remove focus tag from ec2 specs This was a stray artifact from my debugging that shouldn't have been committed, but was by accident. --- spec/unit/util/ec2_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/unit/util/ec2_spec.rb b/spec/unit/util/ec2_spec.rb index 180c956f25..2472c8712f 100755 --- a/spec/unit/util/ec2_spec.rb +++ b/spec/unit/util/ec2_spec.rb @@ -34,7 +34,7 @@ end end - describe "Facter::Util::EC2.with_metadata_server", :focus => true do + describe "Facter::Util::EC2.with_metadata_server" do before :each do Facter::Util::EC2.stubs(:read_uri).returns("latest") end From f83db167b7147f0fe05419b4c1ee83f7b03786dc Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Tue, 23 Apr 2013 16:18:35 -0700 Subject: [PATCH 1240/3753] (maint) Replace rspec >= 2.11 expect(foo).to with foo.should Without this patch the specs are failing in our CI system because we have rspec 2.9 installed and the change set in c028d61 introduced a lot of examples that use the `expect().to` syntax added in rspec-2.11. This patch addresses the problem by replacing all instances of `expect(foo).to` with `foo.should`. --- spec/unit/util/ip/base_spec.rb | 26 +++---- spec/unit/util/ip/darwin_spec.rb | 14 ++-- spec/unit/util/ip/dragonfly_spec.rb | 6 +- spec/unit/util/ip/free_bsd_spec.rb | 8 +-- spec/unit/util/ip/gnu_k_free_bsd_spec.rb | 12 ++-- spec/unit/util/ip/hpux_spec.rb | 88 ++++++++++++------------ spec/unit/util/ip/linux_spec.rb | 18 ++--- spec/unit/util/ip/net_bsd_spec.rb | 6 +- spec/unit/util/ip/open_bsd_spec.rb | 6 +- spec/unit/util/ip/sun_os_spec.rb | 14 ++-- spec/unit/util/ip/windows_spec.rb | 14 ++-- spec/unit/util/ip_spec.rb | 24 +++---- 12 files changed, 115 insertions(+), 121 deletions(-) diff --git a/spec/unit/util/ip/base_spec.rb b/spec/unit/util/ip/base_spec.rb index 9484c22fa9..b5b1223893 100644 --- a/spec/unit/util/ip/base_spec.rb +++ b/spec/unit/util/ip/base_spec.rb @@ -9,16 +9,16 @@ describe ".subclasses" do let(:subclasses) { described_class.subclasses } - it { expect(subclasses).to be_an Array } + it { subclasses.should be_an Array } it "should be memoized" do - expect(subclasses).to be subclasses + described_class.subclasses().should be described_class.subclasses() end it "should list subclasses" do subclass = Class.new described_class - expect(subclasses).to include subclass + subclasses.should include subclass end end @@ -26,7 +26,7 @@ let(:to_s) { described_class.to_s } it "should return the name of the class without nesting" do - expect(to_s).to eq 'Base' + to_s.should eq 'Base' end end @@ -35,13 +35,13 @@ described_class.convert_netmask_from_hex? end - it { expect(convert_netmask_from_hex?).to be true } + it { convert_netmask_from_hex?.should eq true } end describe ".bonding_master" do let(:bonding_master) { described_class.bonding_master('eth0') } - it { expect(bonding_master).to be_nil } + it { bonding_master.should be_nil } end describe ".interfaces" do @@ -50,7 +50,7 @@ it "uses `ifconfig` to list the interfaces" do described_class.expects(:ifconfig_path).returns('/bin/ifconfig') described_class.expects(:exec).with("/bin/ifconfig -a 2> /dev/null") - expect(interfaces).to be_an Array + interfaces.should be_an Array end end @@ -68,7 +68,7 @@ described_class.expects(:regex_for).with(label).returns(nil) end - it { expect(value_for_interface_and_label).to be_nil } + it { value_for_interface_and_label.should be_nil } end describe "there is a regex for the label" do @@ -89,7 +89,7 @@ let(:match_data) { ['inet 127.0.0.1', '127.0.0.1'] } let(:label) { 'ipaddress' } - it { expect(value_for_interface_and_label).to eq '127.0.0.1' } + it { value_for_interface_and_label.should eq '127.0.0.1' } end describe "the label is 'netmask'" do @@ -102,7 +102,7 @@ described_class.expects(:convert_netmask_from_hex?).returns(true) end - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + it { value_for_interface_and_label.should eq '255.255.255.0' } end describe "the netmask does not need to be converted from hex" do @@ -112,7 +112,7 @@ described_class.expects(:convert_netmask_from_hex?).returns(false) end - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + it { value_for_interface_and_label.should eq '255.255.255.0' } end end end @@ -121,7 +121,7 @@ let(:match_data) { nil } let(:label) { 'ipaddress' } - it { expect(value_for_interface_and_label).to be_nil } + it { value_for_interface_and_label.should be_nil } end end @@ -141,7 +141,7 @@ returns('255.255.255.0') end - it { expect(network).to eq "172.16.15.0" } + it { network.should eq "172.16.15.0" } end end end diff --git a/spec/unit/util/ip/darwin_spec.rb b/spec/unit/util/ip/darwin_spec.rb index b9407f29af..6123ab8054 100644 --- a/spec/unit/util/ip/darwin_spec.rb +++ b/spec/unit/util/ip/darwin_spec.rb @@ -10,7 +10,7 @@ end it "should be 'Darwin'" do - expect(to_s).to eq 'Darwin' + to_s.should eq 'Darwin' end end @@ -20,7 +20,7 @@ end it "should be true" do - expect(convert_netmask_from_hex?).to be true + convert_netmask_from_hex?.should be true end end @@ -29,7 +29,7 @@ described_class.bonding_master('eth0') end - it { expect(bonding_master).to be_nil } + it { bonding_master.should be_nil } end describe ".interfaces" do @@ -47,7 +47,7 @@ end it "should return an array with two interfaces" do - expect(interfaces).to eq ["lo0", "en0"] + interfaces.should eq ["lo0", "en0"] end end @@ -69,19 +69,19 @@ describe "macaddress" do let(:label) { 'macaddress' } - it { expect(value_for_interface_and_label).to eq '00:23:6c:99:60:2b' } + it { value_for_interface_and_label.should eq '00:23:6c:99:60:2b' } end describe "netmask" do let(:label) { 'netmask' } - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + it { value_for_interface_and_label.should eq '255.255.255.0' } end describe "mtu" do let(:label) { 'mtu' } - it { expect(value_for_interface_and_label).to eq '1500' } + it { value_for_interface_and_label.should eq '1500' } end end end diff --git a/spec/unit/util/ip/dragonfly_spec.rb b/spec/unit/util/ip/dragonfly_spec.rb index f2c670a209..796601e803 100644 --- a/spec/unit/util/ip/dragonfly_spec.rb +++ b/spec/unit/util/ip/dragonfly_spec.rb @@ -10,7 +10,7 @@ end it "should be 'Dragonfly'" do - expect(to_s).to eq 'Dragonfly' + to_s.should eq 'Dragonfly' end end @@ -20,7 +20,7 @@ end it "should be true" do - expect(convert_netmask_from_hex?).to be true + convert_netmask_from_hex?.should be true end end @@ -29,6 +29,6 @@ described_class.bonding_master('eth0') end - it { expect(bonding_master).to be_nil } + it { bonding_master.should be_nil } end end diff --git a/spec/unit/util/ip/free_bsd_spec.rb b/spec/unit/util/ip/free_bsd_spec.rb index d69989e492..5a0bc3ad2e 100644 --- a/spec/unit/util/ip/free_bsd_spec.rb +++ b/spec/unit/util/ip/free_bsd_spec.rb @@ -10,7 +10,7 @@ end it "should be 'FreeBSD'" do - expect(to_s).to eq 'FreeBSD' + to_s.should eq 'FreeBSD' end end @@ -20,7 +20,7 @@ end it "should be true" do - expect(convert_netmask_from_hex?).to be true + convert_netmask_from_hex?.should be true end end @@ -29,7 +29,7 @@ described_class.bonding_master('eth0') end - it { expect(bonding_master).to be_nil } + it { bonding_master.should be_nil } end describe ".value_for_interface_and_label(interface, label)" do @@ -50,7 +50,7 @@ describe "macaddress" do let(:label) { 'macaddress' } - it { expect(value_for_interface_and_label).to eq '00:0e:0c:68:67:7c' } + it { value_for_interface_and_label.should eq '00:0e:0c:68:67:7c' } end end end diff --git a/spec/unit/util/ip/gnu_k_free_bsd_spec.rb b/spec/unit/util/ip/gnu_k_free_bsd_spec.rb index 591571d6ec..14d8908507 100644 --- a/spec/unit/util/ip/gnu_k_free_bsd_spec.rb +++ b/spec/unit/util/ip/gnu_k_free_bsd_spec.rb @@ -14,7 +14,7 @@ end it "should be 'GNU/kFreeBSD'" do - expect(to_s).to eq 'GNU/kFreeBSD' + to_s.should eq 'GNU/kFreeBSD' end end @@ -24,7 +24,7 @@ end it "should be true" do - expect(convert_netmask_from_hex?).to be true + convert_netmask_from_hex?.should be true end end @@ -33,7 +33,7 @@ described_class.bonding_master('eth0') end - it { expect(bonding_master).to be_nil } + it { bonding_master.should be_nil } end describe ".interfaces" do @@ -51,7 +51,7 @@ end it "should return an array with six interfaces" do - expect(interfaces).to eq %w[em0 em1 bge0 bge1 lo0 vlan0] + interfaces.should eq %w[em0 em1 bge0 bge1 lo0 vlan0] end end @@ -73,13 +73,13 @@ describe "macaddress" do let(:label) { 'macaddress' } - it { expect(value_for_interface_and_label).to eq '0:11:a:59:67:91' } + it { value_for_interface_and_label.should eq '0:11:a:59:67:91' } end describe "netmask" do let(:label) { 'netmask' } - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + it { value_for_interface_and_label.should eq '255.255.255.0' } end end end diff --git a/spec/unit/util/ip/hpux_spec.rb b/spec/unit/util/ip/hpux_spec.rb index ec3f21bfe1..3874f2a6e3 100644 --- a/spec/unit/util/ip/hpux_spec.rb +++ b/spec/unit/util/ip/hpux_spec.rb @@ -14,7 +14,7 @@ end it "should be 'HP-UX'" do - expect(to_s).to eq 'HP-UX' + to_s.should eq 'HP-UX' end end @@ -24,7 +24,7 @@ end it "should be true" do - expect(convert_netmask_from_hex?).to be true + convert_netmask_from_hex?.should be true end end @@ -33,7 +33,7 @@ described_class.bonding_master('eth0') end - it { expect(bonding_master).to be_nil } + it { bonding_master.should be_nil } end describe ".interfaces" do @@ -51,7 +51,7 @@ end it "should return an array of interfaces" do - expect(interfaces).to eq %w[lan1 lan0 lo0] + interfaces.should eq %w[lan1 lan0 lo0] end end @@ -61,7 +61,7 @@ end it "should return an array of interfaces" do - expect(interfaces).to eq %w[lan1 lan0 lo0] + interfaces.should eq %w[lan1 lan0 lo0] end end end @@ -85,7 +85,7 @@ describe "ipaddress" do let(:label) { 'ipaddress' } - it { expect(value_for_interface_and_label).to eq '10.1.1.6' } + it { value_for_interface_and_label.should eq '10.1.1.6' } end describe "mtu" do @@ -97,7 +97,7 @@ describe "netmask" do let(:label) { 'netmask' } - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + it { value_for_interface_and_label.should eq '255.255.255.0' } end describe "macaddress" do @@ -107,7 +107,7 @@ described_class.expects(:lanscan).returns(lanscan_output) end - it { expect(value_for_interface_and_label).to eq "00:10:79:7B:5C:DE" } + it { value_for_interface_and_label.should eq "00:10:79:7B:5C:DE" } end end @@ -117,7 +117,7 @@ describe "ipaddress" do let(:label) { 'ipaddress' } - it { expect(value_for_interface_and_label).to eq "192.168.3.10" } + it { value_for_interface_and_label.should eq "192.168.3.10" } end describe "mtu" do @@ -129,7 +129,7 @@ describe "netmask" do let(:label) { 'netmask' } - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + it { value_for_interface_and_label.should eq '255.255.255.0' } end describe "macaddress" do @@ -139,7 +139,7 @@ described_class.expects(:lanscan).returns(lanscan_output) end - it { expect(value_for_interface_and_label).to eq "00:30:7F:0C:79:DC" } + it { value_for_interface_and_label.should eq "00:30:7F:0C:79:DC" } end end @@ -149,7 +149,7 @@ describe "ipaddress" do let(:label) { 'ipaddress' } - it { expect(value_for_interface_and_label).to eq "127.0.0.1" } + it { value_for_interface_and_label.should eq "127.0.0.1" } end describe "mtu" do @@ -161,13 +161,13 @@ describe "netmask" do let(:label) { 'netmask' } - it { expect(value_for_interface_and_label).to eq '255.0.0.0' } + it { value_for_interface_and_label.should eq '255.0.0.0' } end describe "macaddress" do let(:label) { 'macaddress' } - it { expect(value_for_interface_and_label).to be_nil } + it { value_for_interface_and_label.should be_nil } end end end @@ -187,13 +187,13 @@ describe "ipaddress" do let(:label) { 'ipaddress' } - it { expect(value_for_interface_and_label).to eq '10.1.54.36' } + it { value_for_interface_and_label.should eq '10.1.54.36' } end describe "netmask" do let(:label) { 'netmask' } - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + it { value_for_interface_and_label.should eq '255.255.255.0' } end describe "macaddress" do @@ -204,7 +204,7 @@ described_class.expects(:lanscan).returns(lanscan_output) end - it { expect(value_for_interface_and_label).to eq macaddress } + it { value_for_interface_and_label.should eq macaddress } end describe "mtu" do @@ -220,13 +220,13 @@ describe "ipaddress" do let(:label) { 'ipaddress' } - it { expect(value_for_interface_and_label).to eq '192.168.30.152' } + it { value_for_interface_and_label.should eq '192.168.30.152' } end describe "netmask" do let(:label) { 'netmask' } - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + it { value_for_interface_and_label.should eq '255.255.255.0' } end describe "macaddress" do @@ -237,7 +237,7 @@ described_class.expects(:lanscan).returns(lanscan_output) end - it { expect(value_for_interface_and_label).to eq macaddress } + it { value_for_interface_and_label.should eq macaddress } end describe "mtu" do @@ -253,13 +253,13 @@ describe "ipaddress" do let(:label) { 'ipaddress' } - it { expect(value_for_interface_and_label).to eq '127.0.0.1' } + it { value_for_interface_and_label.should eq '127.0.0.1' } end describe "netmask" do let(:label) { 'netmask' } - it { expect(value_for_interface_and_label).to eq '255.0.0.0' } + it { value_for_interface_and_label.should eq '255.0.0.0' } end describe "macaddress" do @@ -269,7 +269,7 @@ described_class.expects(:lanscan).returns(lanscan_output) end - it { expect(value_for_interface_and_label).to be_nil } + it { value_for_interface_and_label.should be_nil } end describe "mtu" do @@ -297,13 +297,13 @@ describe "ipaddress" do let(:label) { 'ipaddress' } - it { expect(value_for_interface_and_label).to eq '10.10.0.5' } + it { value_for_interface_and_label.should eq '10.10.0.5' } end describe "netmask" do let(:label) { 'netmask' } - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + it { value_for_interface_and_label.should eq '255.255.255.0' } end describe "macaddress" do @@ -314,7 +314,7 @@ described_class.expects(:lanscan).returns(lanscan_output) end - it { expect(value_for_interface_and_label).to eq macaddress } + it { value_for_interface_and_label.should eq macaddress } end describe "mtu" do @@ -330,13 +330,13 @@ describe "ipaddress" do let(:label) { 'ipaddress' } - it { expect(value_for_interface_and_label).to eq '192.168.3.9' } + it { value_for_interface_and_label.should eq '192.168.3.9' } end describe "netmask" do let(:label) { 'netmask' } - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + it { value_for_interface_and_label.should eq '255.255.255.0' } end describe "macaddress" do @@ -347,7 +347,7 @@ described_class.expects(:lanscan).returns(lanscan_output) end - it { expect(value_for_interface_and_label).to eq macaddress } + it { value_for_interface_and_label.should eq macaddress } end describe "mtu" do @@ -363,13 +363,13 @@ describe "ipaddress" do let(:label) { 'ipaddress' } - it { expect(value_for_interface_and_label).to eq '127.0.0.1' } + it { value_for_interface_and_label.should eq '127.0.0.1' } end describe "netmask" do let(:label) { 'netmask' } - it { expect(value_for_interface_and_label).to eq '255.0.0.0' } + it { value_for_interface_and_label.should eq '255.0.0.0' } end describe "macaddress" do @@ -379,7 +379,7 @@ described_class.expects(:lanscan).returns(lanscan_output) end - it { expect(value_for_interface_and_label).to be_nil } + it { value_for_interface_and_label.should be_nil } end describe "mtu" do @@ -407,13 +407,13 @@ describe "ipaddress" do let(:label) { 'ipaddress' } - it { expect(value_for_interface_and_label).to eq '192.168.30.32' } + it { value_for_interface_and_label.should eq '192.168.30.32' } end describe "netmask" do let(:label) { 'netmask' } - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + it { value_for_interface_and_label.should eq '255.255.255.0' } end describe "macaddress" do @@ -424,7 +424,7 @@ described_class.expects(:lanscan).returns(lanscan_output) end - it { expect(value_for_interface_and_label).to eq macaddress } + it { value_for_interface_and_label.should eq macaddress } end describe "mtu" do @@ -440,13 +440,13 @@ describe "ipaddress" do let(:label) { 'ipaddress' } - it { expect(value_for_interface_and_label).to eq '127.0.0.1' } + it { value_for_interface_and_label.should eq '127.0.0.1' } end describe "netmask" do let(:label) { 'netmask' } - it { expect(value_for_interface_and_label).to eq '255.0.0.0' } + it { value_for_interface_and_label.should eq '255.0.0.0' } end describe "macaddress" do @@ -456,7 +456,7 @@ described_class.expects(:lanscan).returns(lanscan_output) end - it { expect(value_for_interface_and_label).to be_nil } + it { value_for_interface_and_label.should be_nil } end describe "mtu" do @@ -472,13 +472,13 @@ describe "ipaddress" do let(:label) { 'ipaddress' } - it { expect(value_for_interface_and_label).to eq '192.168.32.75' } + it { value_for_interface_and_label.should eq '192.168.32.75' } end describe "netmask" do let(:label) { 'netmask' } - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + it { value_for_interface_and_label.should eq '255.255.255.0' } end describe "macaddress" do @@ -489,7 +489,7 @@ described_class.expects(:lanscan).returns(lanscan_output) end - it { expect(value_for_interface_and_label).to eq macaddress } + it { value_for_interface_and_label.should eq macaddress } end describe "mtu" do @@ -505,13 +505,13 @@ describe "ipaddress" do let(:label) { 'ipaddress' } - it { expect(value_for_interface_and_label).to eq '192.168.1.197' } + it { value_for_interface_and_label.should eq '192.168.1.197' } end describe "netmask" do let(:label) { 'netmask' } - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + it { value_for_interface_and_label.should eq '255.255.255.0' } end describe "macaddress" do @@ -521,7 +521,7 @@ described_class.expects(:lanscan).returns(lanscan_output) end - it { expect(value_for_interface_and_label).to be_nil } + it { value_for_interface_and_label.should be_nil } end describe "mtu" do diff --git a/spec/unit/util/ip/linux_spec.rb b/spec/unit/util/ip/linux_spec.rb index 85383115e7..ccb1083a00 100644 --- a/spec/unit/util/ip/linux_spec.rb +++ b/spec/unit/util/ip/linux_spec.rb @@ -14,7 +14,7 @@ end it "should be 'Linux'" do - expect(to_s).to eq 'Linux' + to_s.should eq 'Linux' end end @@ -24,7 +24,7 @@ end it "should be true" do - expect(convert_netmask_from_hex?).to be false + convert_netmask_from_hex?.should be false end end @@ -38,7 +38,7 @@ "eth0:1" end - it { expect(bonding_master).to be_nil } + it { bonding_master.should be_nil } end end @@ -59,7 +59,7 @@ end it "should return an array with a single interface and the loopback" do - expect(interfaces).to eq ["eth0", "lo"] + interfaces.should eq ["eth0", "lo"] end end @@ -74,7 +74,7 @@ end it "should return an array with a single interface and the loopback" do - expect(interfaces).to eq ["eth0", "lo"] + interfaces.should eq ["eth0", "lo"] end end end @@ -98,7 +98,7 @@ described_class.expects(:infiniband_macaddress).returns('bar') end - it { expect(value_for_interface_and_label).to eq 'bar' } + it { value_for_interface_and_label.should eq 'bar' } end end @@ -121,7 +121,7 @@ describe "mtu" do let(:label) { 'mtu' } - it { expect(value_for_interface_and_label).to eq '1500' } + it { value_for_interface_and_label.should eq '1500' } end end @@ -131,7 +131,7 @@ describe "mtu" do let(:label) { 'mtu' } - it { expect(value_for_interface_and_label).to eq '16436' } + it { value_for_interface_and_label.should eq '16436' } end end end @@ -151,7 +151,7 @@ describe "macaddress" do let(:label) { 'macaddress' } - it { expect(value_for_interface_and_label).to eq "00:11:22:33:44:55" } + it { value_for_interface_and_label.should eq "00:11:22:33:44:55" } end end end diff --git a/spec/unit/util/ip/net_bsd_spec.rb b/spec/unit/util/ip/net_bsd_spec.rb index 489e82b2b4..e2a736ef5e 100644 --- a/spec/unit/util/ip/net_bsd_spec.rb +++ b/spec/unit/util/ip/net_bsd_spec.rb @@ -10,7 +10,7 @@ end it "should be 'NetBSD'" do - expect(to_s).to eq 'NetBSD' + to_s.should eq 'NetBSD' end end @@ -20,7 +20,7 @@ end it "should be true" do - expect(convert_netmask_from_hex?).to be true + convert_netmask_from_hex?.should be true end end @@ -29,6 +29,6 @@ described_class.bonding_master('eth0') end - it { expect(bonding_master).to be_nil } + it { bonding_master.should be_nil } end end diff --git a/spec/unit/util/ip/open_bsd_spec.rb b/spec/unit/util/ip/open_bsd_spec.rb index 0a9508dc60..66f9f31fd2 100644 --- a/spec/unit/util/ip/open_bsd_spec.rb +++ b/spec/unit/util/ip/open_bsd_spec.rb @@ -10,7 +10,7 @@ end it "should be 'OpenBSD'" do - expect(to_s).to eq 'OpenBSD' + to_s.should eq 'OpenBSD' end end @@ -20,7 +20,7 @@ end it "should be true" do - expect(convert_netmask_from_hex?).to be true + convert_netmask_from_hex?.should be true end end @@ -29,6 +29,6 @@ described_class.bonding_master('eth0') end - it { expect(bonding_master).to be_nil } + it { bonding_master.should be_nil } end end diff --git a/spec/unit/util/ip/sun_os_spec.rb b/spec/unit/util/ip/sun_os_spec.rb index 23e366612b..61c5edc366 100644 --- a/spec/unit/util/ip/sun_os_spec.rb +++ b/spec/unit/util/ip/sun_os_spec.rb @@ -14,7 +14,7 @@ end it "should be 'SunOS'" do - expect(to_s).to eq 'SunOS' + to_s.should eq 'SunOS' end end @@ -24,7 +24,7 @@ end it "should be true" do - expect(convert_netmask_from_hex?).to be true + convert_netmask_from_hex?.should be true end end @@ -33,7 +33,7 @@ described_class.bonding_master('eth0') end - it { expect(bonding_master).to be_nil } + it { bonding_master.should be_nil } end describe ".interfaces" do @@ -51,7 +51,7 @@ end it "should return an array with two interfaces" do - expect(interfaces).to eq ["lo0", "e1000g0"] + interfaces.should eq ["lo0", "e1000g0"] end end @@ -73,19 +73,19 @@ describe "ipaddress" do let(:label) { "ipaddress" } - it { expect(value_for_interface_and_label).to eq "172.16.15.138" } + it { value_for_interface_and_label.should eq "172.16.15.138" } end describe "netmask" do let(:label) { "netmask" } - it { expect(value_for_interface_and_label).to eq "255.255.255.0" } + it { value_for_interface_and_label.should eq "255.255.255.0" } end describe "mtu" do let(:label) { "mtu" } - it { expect(value_for_interface_and_label).to eq '1500' } + it { value_for_interface_and_label.should eq '1500' } end end end diff --git a/spec/unit/util/ip/windows_spec.rb b/spec/unit/util/ip/windows_spec.rb index 09fc3fb68c..487533ee6e 100644 --- a/spec/unit/util/ip/windows_spec.rb +++ b/spec/unit/util/ip/windows_spec.rb @@ -11,7 +11,7 @@ describe ".to_s" do let(:to_s) { described_class.to_s } - it { expect(to_s).to eq 'windows' } + it { to_s.should eq 'windows' } end describe ".convert_netmask_from_hex?" do @@ -19,13 +19,13 @@ described_class.convert_netmask_from_hex? end - it { expect(convert_netmask_from_hex?).to be false } + it { convert_netmask_from_hex?.should be false } end describe ".bonding_master" do let(:bonding_master) { described_class.bonding_master('eth0') } - it { expect(bonding_master).to be_nil } + it { bonding_master.should be_nil } end describe ".interfaces" do @@ -46,7 +46,7 @@ end it "should return an array of only connected interfaces" do - expect(interfaces).to eq expected_interfaces + interfaces.should eq expected_interfaces end end @@ -69,13 +69,13 @@ describe "ipaddress" do let(:label) { 'ipaddress' } - it { expect(value_for_interface_and_label).to eq '172.16.138.216' } + it { value_for_interface_and_label.should eq '172.16.138.216' } end describe "netmask" do let(:label) { 'netmask' } - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + it { value_for_interface_and_label.should eq '255.255.255.0' } end describe "ipaddress6" do @@ -88,7 +88,7 @@ "#{described_class::NETSH} interface ipv6 show address \"#{interface}\"" end - it { expect(value_for_interface_and_label).to eq expected_ip } + it { value_for_interface_and_label.should eq expected_ip } end end end diff --git a/spec/unit/util/ip_spec.rb b/spec/unit/util/ip_spec.rb index c44c0613c1..db82c982dc 100755 --- a/spec/unit/util/ip_spec.rb +++ b/spec/unit/util/ip_spec.rb @@ -85,22 +85,20 @@ end it "defines the 'interfaces' fact" do - expect(Facter.fact(:interfaces)).to be_a_kind_of Facter::Util::Fact + Facter.fact(:interfaces).should be_a_kind_of Facter::Util::Fact end it "defines a fact for each attribute of an interface" do interfaces_hash.keys.each do |interface| described_class::INTERFACE_KEYS.each do |attr| - expect(Facter.fact("#{attr}_#{interface}")). - to be_a_kind_of Facter::Util::Fact + Facter.fact("#{attr}_#{interface}").should be_a_kind_of Facter::Util::Fact end end end it "defines a fact for an interface's network" do interfaces_hash.keys.each do |interface| - expect(Facter.fact("network_#{interface}")). - to be_a_kind_of Facter::Util::Fact + Facter.fact("network_#{interface}").should be_a_kind_of Facter::Util::Fact end end end @@ -113,22 +111,20 @@ end it 'lists the interfaces for the interfaces fact' do - expect(Facter.value(:interfaces)).to eq interfaces_hash.keys.join(',') + Facter.value(:interfaces).should eq interfaces_hash.keys.join(',') end it 'defines dynamic facts for the interfaces' do interfaces_hash.keys.each do |interface| described_class::INTERFACE_KEYS.each do |attr| - expect(Facter.value("#{attr}_#{interface}")). - to eq interfaces_hash[interface][attr] + Facter.value("#{attr}_#{interface}").should eq interfaces_hash[interface][attr] end end end it 'defines a dynamic fact for the interfaces networks' do interfaces_hash.keys.each do |interface| - expect(Facter.value("network_#{interface}")). - to eq interfaces_hash[interface][:network] + Facter.value("network_#{interface}").should eq interfaces_hash[interface][:network] end end end @@ -142,22 +138,20 @@ end it "updates the interfaces fact" do - expect(Facter.value(:interfaces)).to eq interfaces_hash2.keys.join(',') + Facter.value(:interfaces).should eq interfaces_hash2.keys.join(',') end it "defines new dynamic facts for the new interfaces attributes" do interfaces_hash2.keys.each do |interface| described_class::INTERFACE_KEYS.each do |attr| - expect(Facter.value("#{attr}_#{interface}")). - to eq interfaces_hash2[interface][attr] + Facter.value("#{attr}_#{interface}").should eq interfaces_hash2[interface][attr] end end end it "defines a new dynamic fact for the new interfaces network" do interfaces_hash2.keys.each do |interface| - expect(Facter.value("network_#{interface}")). - to eq interfaces_hash2[interface][:network] + Facter.value("network_#{interface}").should eq interfaces_hash2[interface][:network] end end end From b4097871e84d7a7b3441130289c594aee859af74 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Wed, 24 Apr 2013 11:59:00 -0700 Subject: [PATCH 1241/3753] Revert "(maint) Replace rspec >= 2.11 expect(foo).to with foo.should" This reverts commit f83db167b7147f0fe05419b4c1ee83f7b03786dc. --- spec/unit/util/ip/base_spec.rb | 26 +++---- spec/unit/util/ip/darwin_spec.rb | 14 ++-- spec/unit/util/ip/dragonfly_spec.rb | 6 +- spec/unit/util/ip/free_bsd_spec.rb | 8 +-- spec/unit/util/ip/gnu_k_free_bsd_spec.rb | 12 ++-- spec/unit/util/ip/hpux_spec.rb | 88 ++++++++++++------------ spec/unit/util/ip/linux_spec.rb | 18 ++--- spec/unit/util/ip/net_bsd_spec.rb | 6 +- spec/unit/util/ip/open_bsd_spec.rb | 6 +- spec/unit/util/ip/sun_os_spec.rb | 14 ++-- spec/unit/util/ip/windows_spec.rb | 14 ++-- spec/unit/util/ip_spec.rb | 24 ++++--- 12 files changed, 121 insertions(+), 115 deletions(-) diff --git a/spec/unit/util/ip/base_spec.rb b/spec/unit/util/ip/base_spec.rb index b5b1223893..9484c22fa9 100644 --- a/spec/unit/util/ip/base_spec.rb +++ b/spec/unit/util/ip/base_spec.rb @@ -9,16 +9,16 @@ describe ".subclasses" do let(:subclasses) { described_class.subclasses } - it { subclasses.should be_an Array } + it { expect(subclasses).to be_an Array } it "should be memoized" do - described_class.subclasses().should be described_class.subclasses() + expect(subclasses).to be subclasses end it "should list subclasses" do subclass = Class.new described_class - subclasses.should include subclass + expect(subclasses).to include subclass end end @@ -26,7 +26,7 @@ let(:to_s) { described_class.to_s } it "should return the name of the class without nesting" do - to_s.should eq 'Base' + expect(to_s).to eq 'Base' end end @@ -35,13 +35,13 @@ described_class.convert_netmask_from_hex? end - it { convert_netmask_from_hex?.should eq true } + it { expect(convert_netmask_from_hex?).to be true } end describe ".bonding_master" do let(:bonding_master) { described_class.bonding_master('eth0') } - it { bonding_master.should be_nil } + it { expect(bonding_master).to be_nil } end describe ".interfaces" do @@ -50,7 +50,7 @@ it "uses `ifconfig` to list the interfaces" do described_class.expects(:ifconfig_path).returns('/bin/ifconfig') described_class.expects(:exec).with("/bin/ifconfig -a 2> /dev/null") - interfaces.should be_an Array + expect(interfaces).to be_an Array end end @@ -68,7 +68,7 @@ described_class.expects(:regex_for).with(label).returns(nil) end - it { value_for_interface_and_label.should be_nil } + it { expect(value_for_interface_and_label).to be_nil } end describe "there is a regex for the label" do @@ -89,7 +89,7 @@ let(:match_data) { ['inet 127.0.0.1', '127.0.0.1'] } let(:label) { 'ipaddress' } - it { value_for_interface_and_label.should eq '127.0.0.1' } + it { expect(value_for_interface_and_label).to eq '127.0.0.1' } end describe "the label is 'netmask'" do @@ -102,7 +102,7 @@ described_class.expects(:convert_netmask_from_hex?).returns(true) end - it { value_for_interface_and_label.should eq '255.255.255.0' } + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } end describe "the netmask does not need to be converted from hex" do @@ -112,7 +112,7 @@ described_class.expects(:convert_netmask_from_hex?).returns(false) end - it { value_for_interface_and_label.should eq '255.255.255.0' } + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } end end end @@ -121,7 +121,7 @@ let(:match_data) { nil } let(:label) { 'ipaddress' } - it { value_for_interface_and_label.should be_nil } + it { expect(value_for_interface_and_label).to be_nil } end end @@ -141,7 +141,7 @@ returns('255.255.255.0') end - it { network.should eq "172.16.15.0" } + it { expect(network).to eq "172.16.15.0" } end end end diff --git a/spec/unit/util/ip/darwin_spec.rb b/spec/unit/util/ip/darwin_spec.rb index 6123ab8054..b9407f29af 100644 --- a/spec/unit/util/ip/darwin_spec.rb +++ b/spec/unit/util/ip/darwin_spec.rb @@ -10,7 +10,7 @@ end it "should be 'Darwin'" do - to_s.should eq 'Darwin' + expect(to_s).to eq 'Darwin' end end @@ -20,7 +20,7 @@ end it "should be true" do - convert_netmask_from_hex?.should be true + expect(convert_netmask_from_hex?).to be true end end @@ -29,7 +29,7 @@ described_class.bonding_master('eth0') end - it { bonding_master.should be_nil } + it { expect(bonding_master).to be_nil } end describe ".interfaces" do @@ -47,7 +47,7 @@ end it "should return an array with two interfaces" do - interfaces.should eq ["lo0", "en0"] + expect(interfaces).to eq ["lo0", "en0"] end end @@ -69,19 +69,19 @@ describe "macaddress" do let(:label) { 'macaddress' } - it { value_for_interface_and_label.should eq '00:23:6c:99:60:2b' } + it { expect(value_for_interface_and_label).to eq '00:23:6c:99:60:2b' } end describe "netmask" do let(:label) { 'netmask' } - it { value_for_interface_and_label.should eq '255.255.255.0' } + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } end describe "mtu" do let(:label) { 'mtu' } - it { value_for_interface_and_label.should eq '1500' } + it { expect(value_for_interface_and_label).to eq '1500' } end end end diff --git a/spec/unit/util/ip/dragonfly_spec.rb b/spec/unit/util/ip/dragonfly_spec.rb index 796601e803..f2c670a209 100644 --- a/spec/unit/util/ip/dragonfly_spec.rb +++ b/spec/unit/util/ip/dragonfly_spec.rb @@ -10,7 +10,7 @@ end it "should be 'Dragonfly'" do - to_s.should eq 'Dragonfly' + expect(to_s).to eq 'Dragonfly' end end @@ -20,7 +20,7 @@ end it "should be true" do - convert_netmask_from_hex?.should be true + expect(convert_netmask_from_hex?).to be true end end @@ -29,6 +29,6 @@ described_class.bonding_master('eth0') end - it { bonding_master.should be_nil } + it { expect(bonding_master).to be_nil } end end diff --git a/spec/unit/util/ip/free_bsd_spec.rb b/spec/unit/util/ip/free_bsd_spec.rb index 5a0bc3ad2e..d69989e492 100644 --- a/spec/unit/util/ip/free_bsd_spec.rb +++ b/spec/unit/util/ip/free_bsd_spec.rb @@ -10,7 +10,7 @@ end it "should be 'FreeBSD'" do - to_s.should eq 'FreeBSD' + expect(to_s).to eq 'FreeBSD' end end @@ -20,7 +20,7 @@ end it "should be true" do - convert_netmask_from_hex?.should be true + expect(convert_netmask_from_hex?).to be true end end @@ -29,7 +29,7 @@ described_class.bonding_master('eth0') end - it { bonding_master.should be_nil } + it { expect(bonding_master).to be_nil } end describe ".value_for_interface_and_label(interface, label)" do @@ -50,7 +50,7 @@ describe "macaddress" do let(:label) { 'macaddress' } - it { value_for_interface_and_label.should eq '00:0e:0c:68:67:7c' } + it { expect(value_for_interface_and_label).to eq '00:0e:0c:68:67:7c' } end end end diff --git a/spec/unit/util/ip/gnu_k_free_bsd_spec.rb b/spec/unit/util/ip/gnu_k_free_bsd_spec.rb index 14d8908507..591571d6ec 100644 --- a/spec/unit/util/ip/gnu_k_free_bsd_spec.rb +++ b/spec/unit/util/ip/gnu_k_free_bsd_spec.rb @@ -14,7 +14,7 @@ end it "should be 'GNU/kFreeBSD'" do - to_s.should eq 'GNU/kFreeBSD' + expect(to_s).to eq 'GNU/kFreeBSD' end end @@ -24,7 +24,7 @@ end it "should be true" do - convert_netmask_from_hex?.should be true + expect(convert_netmask_from_hex?).to be true end end @@ -33,7 +33,7 @@ described_class.bonding_master('eth0') end - it { bonding_master.should be_nil } + it { expect(bonding_master).to be_nil } end describe ".interfaces" do @@ -51,7 +51,7 @@ end it "should return an array with six interfaces" do - interfaces.should eq %w[em0 em1 bge0 bge1 lo0 vlan0] + expect(interfaces).to eq %w[em0 em1 bge0 bge1 lo0 vlan0] end end @@ -73,13 +73,13 @@ describe "macaddress" do let(:label) { 'macaddress' } - it { value_for_interface_and_label.should eq '0:11:a:59:67:91' } + it { expect(value_for_interface_and_label).to eq '0:11:a:59:67:91' } end describe "netmask" do let(:label) { 'netmask' } - it { value_for_interface_and_label.should eq '255.255.255.0' } + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } end end end diff --git a/spec/unit/util/ip/hpux_spec.rb b/spec/unit/util/ip/hpux_spec.rb index 3874f2a6e3..ec3f21bfe1 100644 --- a/spec/unit/util/ip/hpux_spec.rb +++ b/spec/unit/util/ip/hpux_spec.rb @@ -14,7 +14,7 @@ end it "should be 'HP-UX'" do - to_s.should eq 'HP-UX' + expect(to_s).to eq 'HP-UX' end end @@ -24,7 +24,7 @@ end it "should be true" do - convert_netmask_from_hex?.should be true + expect(convert_netmask_from_hex?).to be true end end @@ -33,7 +33,7 @@ described_class.bonding_master('eth0') end - it { bonding_master.should be_nil } + it { expect(bonding_master).to be_nil } end describe ".interfaces" do @@ -51,7 +51,7 @@ end it "should return an array of interfaces" do - interfaces.should eq %w[lan1 lan0 lo0] + expect(interfaces).to eq %w[lan1 lan0 lo0] end end @@ -61,7 +61,7 @@ end it "should return an array of interfaces" do - interfaces.should eq %w[lan1 lan0 lo0] + expect(interfaces).to eq %w[lan1 lan0 lo0] end end end @@ -85,7 +85,7 @@ describe "ipaddress" do let(:label) { 'ipaddress' } - it { value_for_interface_and_label.should eq '10.1.1.6' } + it { expect(value_for_interface_and_label).to eq '10.1.1.6' } end describe "mtu" do @@ -97,7 +97,7 @@ describe "netmask" do let(:label) { 'netmask' } - it { value_for_interface_and_label.should eq '255.255.255.0' } + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } end describe "macaddress" do @@ -107,7 +107,7 @@ described_class.expects(:lanscan).returns(lanscan_output) end - it { value_for_interface_and_label.should eq "00:10:79:7B:5C:DE" } + it { expect(value_for_interface_and_label).to eq "00:10:79:7B:5C:DE" } end end @@ -117,7 +117,7 @@ describe "ipaddress" do let(:label) { 'ipaddress' } - it { value_for_interface_and_label.should eq "192.168.3.10" } + it { expect(value_for_interface_and_label).to eq "192.168.3.10" } end describe "mtu" do @@ -129,7 +129,7 @@ describe "netmask" do let(:label) { 'netmask' } - it { value_for_interface_and_label.should eq '255.255.255.0' } + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } end describe "macaddress" do @@ -139,7 +139,7 @@ described_class.expects(:lanscan).returns(lanscan_output) end - it { value_for_interface_and_label.should eq "00:30:7F:0C:79:DC" } + it { expect(value_for_interface_and_label).to eq "00:30:7F:0C:79:DC" } end end @@ -149,7 +149,7 @@ describe "ipaddress" do let(:label) { 'ipaddress' } - it { value_for_interface_and_label.should eq "127.0.0.1" } + it { expect(value_for_interface_and_label).to eq "127.0.0.1" } end describe "mtu" do @@ -161,13 +161,13 @@ describe "netmask" do let(:label) { 'netmask' } - it { value_for_interface_and_label.should eq '255.0.0.0' } + it { expect(value_for_interface_and_label).to eq '255.0.0.0' } end describe "macaddress" do let(:label) { 'macaddress' } - it { value_for_interface_and_label.should be_nil } + it { expect(value_for_interface_and_label).to be_nil } end end end @@ -187,13 +187,13 @@ describe "ipaddress" do let(:label) { 'ipaddress' } - it { value_for_interface_and_label.should eq '10.1.54.36' } + it { expect(value_for_interface_and_label).to eq '10.1.54.36' } end describe "netmask" do let(:label) { 'netmask' } - it { value_for_interface_and_label.should eq '255.255.255.0' } + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } end describe "macaddress" do @@ -204,7 +204,7 @@ described_class.expects(:lanscan).returns(lanscan_output) end - it { value_for_interface_and_label.should eq macaddress } + it { expect(value_for_interface_and_label).to eq macaddress } end describe "mtu" do @@ -220,13 +220,13 @@ describe "ipaddress" do let(:label) { 'ipaddress' } - it { value_for_interface_and_label.should eq '192.168.30.152' } + it { expect(value_for_interface_and_label).to eq '192.168.30.152' } end describe "netmask" do let(:label) { 'netmask' } - it { value_for_interface_and_label.should eq '255.255.255.0' } + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } end describe "macaddress" do @@ -237,7 +237,7 @@ described_class.expects(:lanscan).returns(lanscan_output) end - it { value_for_interface_and_label.should eq macaddress } + it { expect(value_for_interface_and_label).to eq macaddress } end describe "mtu" do @@ -253,13 +253,13 @@ describe "ipaddress" do let(:label) { 'ipaddress' } - it { value_for_interface_and_label.should eq '127.0.0.1' } + it { expect(value_for_interface_and_label).to eq '127.0.0.1' } end describe "netmask" do let(:label) { 'netmask' } - it { value_for_interface_and_label.should eq '255.0.0.0' } + it { expect(value_for_interface_and_label).to eq '255.0.0.0' } end describe "macaddress" do @@ -269,7 +269,7 @@ described_class.expects(:lanscan).returns(lanscan_output) end - it { value_for_interface_and_label.should be_nil } + it { expect(value_for_interface_and_label).to be_nil } end describe "mtu" do @@ -297,13 +297,13 @@ describe "ipaddress" do let(:label) { 'ipaddress' } - it { value_for_interface_and_label.should eq '10.10.0.5' } + it { expect(value_for_interface_and_label).to eq '10.10.0.5' } end describe "netmask" do let(:label) { 'netmask' } - it { value_for_interface_and_label.should eq '255.255.255.0' } + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } end describe "macaddress" do @@ -314,7 +314,7 @@ described_class.expects(:lanscan).returns(lanscan_output) end - it { value_for_interface_and_label.should eq macaddress } + it { expect(value_for_interface_and_label).to eq macaddress } end describe "mtu" do @@ -330,13 +330,13 @@ describe "ipaddress" do let(:label) { 'ipaddress' } - it { value_for_interface_and_label.should eq '192.168.3.9' } + it { expect(value_for_interface_and_label).to eq '192.168.3.9' } end describe "netmask" do let(:label) { 'netmask' } - it { value_for_interface_and_label.should eq '255.255.255.0' } + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } end describe "macaddress" do @@ -347,7 +347,7 @@ described_class.expects(:lanscan).returns(lanscan_output) end - it { value_for_interface_and_label.should eq macaddress } + it { expect(value_for_interface_and_label).to eq macaddress } end describe "mtu" do @@ -363,13 +363,13 @@ describe "ipaddress" do let(:label) { 'ipaddress' } - it { value_for_interface_and_label.should eq '127.0.0.1' } + it { expect(value_for_interface_and_label).to eq '127.0.0.1' } end describe "netmask" do let(:label) { 'netmask' } - it { value_for_interface_and_label.should eq '255.0.0.0' } + it { expect(value_for_interface_and_label).to eq '255.0.0.0' } end describe "macaddress" do @@ -379,7 +379,7 @@ described_class.expects(:lanscan).returns(lanscan_output) end - it { value_for_interface_and_label.should be_nil } + it { expect(value_for_interface_and_label).to be_nil } end describe "mtu" do @@ -407,13 +407,13 @@ describe "ipaddress" do let(:label) { 'ipaddress' } - it { value_for_interface_and_label.should eq '192.168.30.32' } + it { expect(value_for_interface_and_label).to eq '192.168.30.32' } end describe "netmask" do let(:label) { 'netmask' } - it { value_for_interface_and_label.should eq '255.255.255.0' } + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } end describe "macaddress" do @@ -424,7 +424,7 @@ described_class.expects(:lanscan).returns(lanscan_output) end - it { value_for_interface_and_label.should eq macaddress } + it { expect(value_for_interface_and_label).to eq macaddress } end describe "mtu" do @@ -440,13 +440,13 @@ describe "ipaddress" do let(:label) { 'ipaddress' } - it { value_for_interface_and_label.should eq '127.0.0.1' } + it { expect(value_for_interface_and_label).to eq '127.0.0.1' } end describe "netmask" do let(:label) { 'netmask' } - it { value_for_interface_and_label.should eq '255.0.0.0' } + it { expect(value_for_interface_and_label).to eq '255.0.0.0' } end describe "macaddress" do @@ -456,7 +456,7 @@ described_class.expects(:lanscan).returns(lanscan_output) end - it { value_for_interface_and_label.should be_nil } + it { expect(value_for_interface_and_label).to be_nil } end describe "mtu" do @@ -472,13 +472,13 @@ describe "ipaddress" do let(:label) { 'ipaddress' } - it { value_for_interface_and_label.should eq '192.168.32.75' } + it { expect(value_for_interface_and_label).to eq '192.168.32.75' } end describe "netmask" do let(:label) { 'netmask' } - it { value_for_interface_and_label.should eq '255.255.255.0' } + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } end describe "macaddress" do @@ -489,7 +489,7 @@ described_class.expects(:lanscan).returns(lanscan_output) end - it { value_for_interface_and_label.should eq macaddress } + it { expect(value_for_interface_and_label).to eq macaddress } end describe "mtu" do @@ -505,13 +505,13 @@ describe "ipaddress" do let(:label) { 'ipaddress' } - it { value_for_interface_and_label.should eq '192.168.1.197' } + it { expect(value_for_interface_and_label).to eq '192.168.1.197' } end describe "netmask" do let(:label) { 'netmask' } - it { value_for_interface_and_label.should eq '255.255.255.0' } + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } end describe "macaddress" do @@ -521,7 +521,7 @@ described_class.expects(:lanscan).returns(lanscan_output) end - it { value_for_interface_and_label.should be_nil } + it { expect(value_for_interface_and_label).to be_nil } end describe "mtu" do diff --git a/spec/unit/util/ip/linux_spec.rb b/spec/unit/util/ip/linux_spec.rb index ccb1083a00..85383115e7 100644 --- a/spec/unit/util/ip/linux_spec.rb +++ b/spec/unit/util/ip/linux_spec.rb @@ -14,7 +14,7 @@ end it "should be 'Linux'" do - to_s.should eq 'Linux' + expect(to_s).to eq 'Linux' end end @@ -24,7 +24,7 @@ end it "should be true" do - convert_netmask_from_hex?.should be false + expect(convert_netmask_from_hex?).to be false end end @@ -38,7 +38,7 @@ "eth0:1" end - it { bonding_master.should be_nil } + it { expect(bonding_master).to be_nil } end end @@ -59,7 +59,7 @@ end it "should return an array with a single interface and the loopback" do - interfaces.should eq ["eth0", "lo"] + expect(interfaces).to eq ["eth0", "lo"] end end @@ -74,7 +74,7 @@ end it "should return an array with a single interface and the loopback" do - interfaces.should eq ["eth0", "lo"] + expect(interfaces).to eq ["eth0", "lo"] end end end @@ -98,7 +98,7 @@ described_class.expects(:infiniband_macaddress).returns('bar') end - it { value_for_interface_and_label.should eq 'bar' } + it { expect(value_for_interface_and_label).to eq 'bar' } end end @@ -121,7 +121,7 @@ describe "mtu" do let(:label) { 'mtu' } - it { value_for_interface_and_label.should eq '1500' } + it { expect(value_for_interface_and_label).to eq '1500' } end end @@ -131,7 +131,7 @@ describe "mtu" do let(:label) { 'mtu' } - it { value_for_interface_and_label.should eq '16436' } + it { expect(value_for_interface_and_label).to eq '16436' } end end end @@ -151,7 +151,7 @@ describe "macaddress" do let(:label) { 'macaddress' } - it { value_for_interface_and_label.should eq "00:11:22:33:44:55" } + it { expect(value_for_interface_and_label).to eq "00:11:22:33:44:55" } end end end diff --git a/spec/unit/util/ip/net_bsd_spec.rb b/spec/unit/util/ip/net_bsd_spec.rb index e2a736ef5e..489e82b2b4 100644 --- a/spec/unit/util/ip/net_bsd_spec.rb +++ b/spec/unit/util/ip/net_bsd_spec.rb @@ -10,7 +10,7 @@ end it "should be 'NetBSD'" do - to_s.should eq 'NetBSD' + expect(to_s).to eq 'NetBSD' end end @@ -20,7 +20,7 @@ end it "should be true" do - convert_netmask_from_hex?.should be true + expect(convert_netmask_from_hex?).to be true end end @@ -29,6 +29,6 @@ described_class.bonding_master('eth0') end - it { bonding_master.should be_nil } + it { expect(bonding_master).to be_nil } end end diff --git a/spec/unit/util/ip/open_bsd_spec.rb b/spec/unit/util/ip/open_bsd_spec.rb index 66f9f31fd2..0a9508dc60 100644 --- a/spec/unit/util/ip/open_bsd_spec.rb +++ b/spec/unit/util/ip/open_bsd_spec.rb @@ -10,7 +10,7 @@ end it "should be 'OpenBSD'" do - to_s.should eq 'OpenBSD' + expect(to_s).to eq 'OpenBSD' end end @@ -20,7 +20,7 @@ end it "should be true" do - convert_netmask_from_hex?.should be true + expect(convert_netmask_from_hex?).to be true end end @@ -29,6 +29,6 @@ described_class.bonding_master('eth0') end - it { bonding_master.should be_nil } + it { expect(bonding_master).to be_nil } end end diff --git a/spec/unit/util/ip/sun_os_spec.rb b/spec/unit/util/ip/sun_os_spec.rb index 61c5edc366..23e366612b 100644 --- a/spec/unit/util/ip/sun_os_spec.rb +++ b/spec/unit/util/ip/sun_os_spec.rb @@ -14,7 +14,7 @@ end it "should be 'SunOS'" do - to_s.should eq 'SunOS' + expect(to_s).to eq 'SunOS' end end @@ -24,7 +24,7 @@ end it "should be true" do - convert_netmask_from_hex?.should be true + expect(convert_netmask_from_hex?).to be true end end @@ -33,7 +33,7 @@ described_class.bonding_master('eth0') end - it { bonding_master.should be_nil } + it { expect(bonding_master).to be_nil } end describe ".interfaces" do @@ -51,7 +51,7 @@ end it "should return an array with two interfaces" do - interfaces.should eq ["lo0", "e1000g0"] + expect(interfaces).to eq ["lo0", "e1000g0"] end end @@ -73,19 +73,19 @@ describe "ipaddress" do let(:label) { "ipaddress" } - it { value_for_interface_and_label.should eq "172.16.15.138" } + it { expect(value_for_interface_and_label).to eq "172.16.15.138" } end describe "netmask" do let(:label) { "netmask" } - it { value_for_interface_and_label.should eq "255.255.255.0" } + it { expect(value_for_interface_and_label).to eq "255.255.255.0" } end describe "mtu" do let(:label) { "mtu" } - it { value_for_interface_and_label.should eq '1500' } + it { expect(value_for_interface_and_label).to eq '1500' } end end end diff --git a/spec/unit/util/ip/windows_spec.rb b/spec/unit/util/ip/windows_spec.rb index 487533ee6e..09fc3fb68c 100644 --- a/spec/unit/util/ip/windows_spec.rb +++ b/spec/unit/util/ip/windows_spec.rb @@ -11,7 +11,7 @@ describe ".to_s" do let(:to_s) { described_class.to_s } - it { to_s.should eq 'windows' } + it { expect(to_s).to eq 'windows' } end describe ".convert_netmask_from_hex?" do @@ -19,13 +19,13 @@ described_class.convert_netmask_from_hex? end - it { convert_netmask_from_hex?.should be false } + it { expect(convert_netmask_from_hex?).to be false } end describe ".bonding_master" do let(:bonding_master) { described_class.bonding_master('eth0') } - it { bonding_master.should be_nil } + it { expect(bonding_master).to be_nil } end describe ".interfaces" do @@ -46,7 +46,7 @@ end it "should return an array of only connected interfaces" do - interfaces.should eq expected_interfaces + expect(interfaces).to eq expected_interfaces end end @@ -69,13 +69,13 @@ describe "ipaddress" do let(:label) { 'ipaddress' } - it { value_for_interface_and_label.should eq '172.16.138.216' } + it { expect(value_for_interface_and_label).to eq '172.16.138.216' } end describe "netmask" do let(:label) { 'netmask' } - it { value_for_interface_and_label.should eq '255.255.255.0' } + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } end describe "ipaddress6" do @@ -88,7 +88,7 @@ "#{described_class::NETSH} interface ipv6 show address \"#{interface}\"" end - it { value_for_interface_and_label.should eq expected_ip } + it { expect(value_for_interface_and_label).to eq expected_ip } end end end diff --git a/spec/unit/util/ip_spec.rb b/spec/unit/util/ip_spec.rb index db82c982dc..c44c0613c1 100755 --- a/spec/unit/util/ip_spec.rb +++ b/spec/unit/util/ip_spec.rb @@ -85,20 +85,22 @@ end it "defines the 'interfaces' fact" do - Facter.fact(:interfaces).should be_a_kind_of Facter::Util::Fact + expect(Facter.fact(:interfaces)).to be_a_kind_of Facter::Util::Fact end it "defines a fact for each attribute of an interface" do interfaces_hash.keys.each do |interface| described_class::INTERFACE_KEYS.each do |attr| - Facter.fact("#{attr}_#{interface}").should be_a_kind_of Facter::Util::Fact + expect(Facter.fact("#{attr}_#{interface}")). + to be_a_kind_of Facter::Util::Fact end end end it "defines a fact for an interface's network" do interfaces_hash.keys.each do |interface| - Facter.fact("network_#{interface}").should be_a_kind_of Facter::Util::Fact + expect(Facter.fact("network_#{interface}")). + to be_a_kind_of Facter::Util::Fact end end end @@ -111,20 +113,22 @@ end it 'lists the interfaces for the interfaces fact' do - Facter.value(:interfaces).should eq interfaces_hash.keys.join(',') + expect(Facter.value(:interfaces)).to eq interfaces_hash.keys.join(',') end it 'defines dynamic facts for the interfaces' do interfaces_hash.keys.each do |interface| described_class::INTERFACE_KEYS.each do |attr| - Facter.value("#{attr}_#{interface}").should eq interfaces_hash[interface][attr] + expect(Facter.value("#{attr}_#{interface}")). + to eq interfaces_hash[interface][attr] end end end it 'defines a dynamic fact for the interfaces networks' do interfaces_hash.keys.each do |interface| - Facter.value("network_#{interface}").should eq interfaces_hash[interface][:network] + expect(Facter.value("network_#{interface}")). + to eq interfaces_hash[interface][:network] end end end @@ -138,20 +142,22 @@ end it "updates the interfaces fact" do - Facter.value(:interfaces).should eq interfaces_hash2.keys.join(',') + expect(Facter.value(:interfaces)).to eq interfaces_hash2.keys.join(',') end it "defines new dynamic facts for the new interfaces attributes" do interfaces_hash2.keys.each do |interface| described_class::INTERFACE_KEYS.each do |attr| - Facter.value("#{attr}_#{interface}").should eq interfaces_hash2[interface][attr] + expect(Facter.value("#{attr}_#{interface}")). + to eq interfaces_hash2[interface][attr] end end end it "defines a new dynamic fact for the new interfaces network" do interfaces_hash2.keys.each do |interface| - Facter.value("network_#{interface}").should eq interfaces_hash2[interface][:network] + expect(Facter.value("network_#{interface}")). + to eq interfaces_hash2[interface][:network] end end end From 4030518ca0ce163b42ea12cd253e9b169f9c78a6 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Wed, 24 Apr 2013 11:59:14 -0700 Subject: [PATCH 1242/3753] Revert "Merge branch 'razic-fix/2.x/17710_ipmodule_refactor'" This reverts commit c028d611ef294f2a206e90b9268b0afa61ff0654, reversing changes made to e189f8a7739cad36bf8130741056ebb3f4a4f766. This change set is being reverted because we're getting the following error when running the acceptance test suite on Microsoft Windows. Could not retrieve local facts: undefined method `get_interfaces' for Facter::Util::IP:Class --- lib/facter/interfaces.rb | 45 +- lib/facter/network.rb | 20 + lib/facter/util/ip.rb | 427 +++++++------ lib/facter/util/ip/base.rb | 196 ------ lib/facter/util/ip/darwin.rb | 6 - lib/facter/util/ip/dragonfly.rb | 6 - lib/facter/util/ip/free_bsd.rb | 6 - lib/facter/util/ip/gnu_k_free_bsd.rb | 9 - lib/facter/util/ip/hpux.rb | 76 --- lib/facter/util/ip/linux.rb | 169 ----- lib/facter/util/ip/net_bsd.rb | 6 - lib/facter/util/ip/open_bsd.rb | 6 - lib/facter/util/ip/sun_os.rb | 24 - lib/facter/util/ip/windows.rb | 73 --- ...interfaces => 6.0-STABLE_FreeBSD_ifconfig} | 0 .../ip/darwin/ifconfig_with_single_interface | 6 - ...win_ifconfig_all_with_multiple_interfaces} | 0 ...le_interfaces => debian_kfreebsd_ifconfig} | 0 .../6.0-STABLE_ifconfig_with_single_interface | 10 - .../ifconfig_with_single_interface | 8 - ..._ifconfig_lan0 => hpux_1111_ifconfig_lan0} | 0 ..._ifconfig_lan1 => hpux_1111_ifconfig_lan1} | 0 ...11_ifconfig_lo0 => hpux_1111_ifconfig_lo0} | 0 .../{hpux/1111_lanscan => hpux_1111_lanscan} | 0 .../1111_netstat_in => hpux_1111_netstat_in} | 0 ..._lan0 => hpux_1131_asterisk_ifconfig_lan0} | 0 ..._lan1 => hpux_1131_asterisk_ifconfig_lan1} | 0 ...ig_lo0 => hpux_1131_asterisk_ifconfig_lo0} | 0 ...isk_lanscan => hpux_1131_asterisk_lanscan} | 0 ...tstat_in => hpux_1131_asterisk_netstat_in} | 0 ..._ifconfig_lan0 => hpux_1131_ifconfig_lan0} | 0 ..._ifconfig_lan1 => hpux_1131_ifconfig_lan1} | 0 ...31_ifconfig_lo0 => hpux_1131_ifconfig_lo0} | 0 .../{hpux/1131_lanscan => hpux_1131_lanscan} | 0 .../1131_netstat_in => hpux_1131_netstat_in} | 0 ...n1 => hpux_1131_nic_bonding_ifconfig_lan1} | 0 ...n4 => hpux_1131_nic_bonding_ifconfig_lan4} | 0 ... => hpux_1131_nic_bonding_ifconfig_lan4_1} | 0 ...lo0 => hpux_1131_nic_bonding_ifconfig_lo0} | 0 ..._lanscan => hpux_1131_nic_bonding_lanscan} | 0 ...at_in => hpux_1131_nic_bonding_netstat_in} | 0 ...d0 => linux_2_6_35_proc_net_bonding_bond0} | 0 ...e_eth0 => linux_get_single_interface_eth0} | 0 ...ace_ib0 => linux_get_single_interface_ib0} | 0 ...rface_lo => linux_get_single_interface_lo} | 0 ... linux_ifconfig_all_with_single_interface} | 0 ...ris_ifconfig_all_with_multiple_interfaces} | 0 ...face => solaris_ifconfig_single_interface} | 0 ...nterfaces => windows_netsh_all_interfaces} | 0 ...terface => windows_netsh_single_interface} | 0 ...rface6 => windows_netsh_single_interface6} | 0 spec/unit/blockdevices_spec.rb | 2 + spec/unit/hardwareisa_spec.rb | 2 + spec/unit/hardwaremodel_spec.rb | 2 + spec/unit/interfaces_spec.rb | 68 ++ spec/unit/ldom_spec.rb | 2 + spec/unit/manufacturer_spec.rb | 2 + spec/unit/memory_spec.rb | 2 - spec/unit/uniqueid_spec.rb | 2 + spec/unit/util/directory_loader_spec.rb | 3 + spec/unit/util/ec2_spec.rb | 2 +- spec/unit/util/ip/base_spec.rb | 147 ----- spec/unit/util/ip/darwin_spec.rb | 87 --- spec/unit/util/ip/dragonfly_spec.rb | 34 - spec/unit/util/ip/free_bsd_spec.rb | 56 -- spec/unit/util/ip/gnu_k_free_bsd_spec.rb | 85 --- spec/unit/util/ip/hpux_spec.rb | 536 ---------------- spec/unit/util/ip/linux_spec.rb | 158 ----- spec/unit/util/ip/net_bsd_spec.rb | 34 - spec/unit/util/ip/open_bsd_spec.rb | 34 - spec/unit/util/ip/sun_os_spec.rb | 91 --- spec/unit/util/ip/windows_spec.rb | 94 --- spec/unit/util/ip_spec.rb | 581 +++++++++++++----- spec/unit/util/parser_spec.rb | 5 +- spec/unit/zfs_version_spec.rb | 2 + spec/unit/zpool_version_spec.rb | 2 + 76 files changed, 825 insertions(+), 2301 deletions(-) create mode 100644 lib/facter/network.rb delete mode 100644 lib/facter/util/ip/base.rb delete mode 100644 lib/facter/util/ip/darwin.rb delete mode 100644 lib/facter/util/ip/dragonfly.rb delete mode 100644 lib/facter/util/ip/free_bsd.rb delete mode 100644 lib/facter/util/ip/gnu_k_free_bsd.rb delete mode 100644 lib/facter/util/ip/hpux.rb delete mode 100644 lib/facter/util/ip/linux.rb delete mode 100644 lib/facter/util/ip/net_bsd.rb delete mode 100644 lib/facter/util/ip/open_bsd.rb delete mode 100644 lib/facter/util/ip/sun_os.rb delete mode 100644 lib/facter/util/ip/windows.rb rename spec/fixtures/unit/util/ip/{free_bsd/6.0-STABLE_ifconfig_with_multiple_interfaces => 6.0-STABLE_FreeBSD_ifconfig} (100%) delete mode 100644 spec/fixtures/unit/util/ip/darwin/ifconfig_with_single_interface rename spec/fixtures/unit/util/ip/{darwin/ifconfig_all_with_multiple_interfaces => darwin_ifconfig_all_with_multiple_interfaces} (100%) rename spec/fixtures/unit/util/ip/{gnu_k_free_bsd/ifconfig_all_with_multiple_interfaces => debian_kfreebsd_ifconfig} (100%) delete mode 100644 spec/fixtures/unit/util/ip/free_bsd/6.0-STABLE_ifconfig_with_single_interface delete mode 100644 spec/fixtures/unit/util/ip/gnu_k_free_bsd/ifconfig_with_single_interface rename spec/fixtures/unit/util/ip/{hpux/1111_ifconfig_lan0 => hpux_1111_ifconfig_lan0} (100%) rename spec/fixtures/unit/util/ip/{hpux/1111_ifconfig_lan1 => hpux_1111_ifconfig_lan1} (100%) rename spec/fixtures/unit/util/ip/{hpux/1111_ifconfig_lo0 => hpux_1111_ifconfig_lo0} (100%) rename spec/fixtures/unit/util/ip/{hpux/1111_lanscan => hpux_1111_lanscan} (100%) rename spec/fixtures/unit/util/ip/{hpux/1111_netstat_in => hpux_1111_netstat_in} (100%) rename spec/fixtures/unit/util/ip/{hpux/1131_asterisk_ifconfig_lan0 => hpux_1131_asterisk_ifconfig_lan0} (100%) rename spec/fixtures/unit/util/ip/{hpux/1131_asterisk_ifconfig_lan1 => hpux_1131_asterisk_ifconfig_lan1} (100%) rename spec/fixtures/unit/util/ip/{hpux/1131_asterisk_ifconfig_lo0 => hpux_1131_asterisk_ifconfig_lo0} (100%) rename spec/fixtures/unit/util/ip/{hpux/1131_asterisk_lanscan => hpux_1131_asterisk_lanscan} (100%) rename spec/fixtures/unit/util/ip/{hpux/1131_asterisk_netstat_in => hpux_1131_asterisk_netstat_in} (100%) rename spec/fixtures/unit/util/ip/{hpux/1131_ifconfig_lan0 => hpux_1131_ifconfig_lan0} (100%) rename spec/fixtures/unit/util/ip/{hpux/1131_ifconfig_lan1 => hpux_1131_ifconfig_lan1} (100%) rename spec/fixtures/unit/util/ip/{hpux/1131_ifconfig_lo0 => hpux_1131_ifconfig_lo0} (100%) rename spec/fixtures/unit/util/ip/{hpux/1131_lanscan => hpux_1131_lanscan} (100%) rename spec/fixtures/unit/util/ip/{hpux/1131_netstat_in => hpux_1131_netstat_in} (100%) rename spec/fixtures/unit/util/ip/{hpux/1131_nic_bonding_ifconfig_lan1 => hpux_1131_nic_bonding_ifconfig_lan1} (100%) rename spec/fixtures/unit/util/ip/{hpux/1131_nic_bonding_ifconfig_lan4 => hpux_1131_nic_bonding_ifconfig_lan4} (100%) rename spec/fixtures/unit/util/ip/{hpux/1131_nic_bonding_ifconfig_lan4_1 => hpux_1131_nic_bonding_ifconfig_lan4_1} (100%) rename spec/fixtures/unit/util/ip/{hpux/1131_nic_bonding_ifconfig_lo0 => hpux_1131_nic_bonding_ifconfig_lo0} (100%) rename spec/fixtures/unit/util/ip/{hpux/1131_nic_bonding_lanscan => hpux_1131_nic_bonding_lanscan} (100%) rename spec/fixtures/unit/util/ip/{hpux/1131_nic_bonding_netstat_in => hpux_1131_nic_bonding_netstat_in} (100%) rename spec/fixtures/unit/util/ip/{linux/2_6_35_proc_net_bonding_bond0 => linux_2_6_35_proc_net_bonding_bond0} (100%) rename spec/fixtures/unit/util/ip/{linux/ifconfig_single_interface_eth0 => linux_get_single_interface_eth0} (100%) rename spec/fixtures/unit/util/ip/{linux/ifconfig_with_single_interface_ib0 => linux_get_single_interface_ib0} (100%) rename spec/fixtures/unit/util/ip/{linux/ifconfig_single_interface_lo => linux_get_single_interface_lo} (100%) rename spec/fixtures/unit/util/ip/{linux/ifconfig_all_with_single_interface => linux_ifconfig_all_with_single_interface} (100%) rename spec/fixtures/unit/util/ip/{sun_os/ifconfig_all_with_multiple_interfaces => solaris_ifconfig_all_with_multiple_interfaces} (100%) rename spec/fixtures/unit/util/ip/{sun_os/ifconfig_single_interface => solaris_ifconfig_single_interface} (100%) rename spec/fixtures/unit/util/ip/{windows/netsh_all_interfaces => windows_netsh_all_interfaces} (100%) rename spec/fixtures/unit/util/ip/{windows/netsh_with_single_interface => windows_netsh_single_interface} (100%) rename spec/fixtures/unit/util/ip/{windows/netsh_with_single_interface6 => windows_netsh_single_interface6} (100%) create mode 100755 spec/unit/interfaces_spec.rb delete mode 100644 spec/unit/util/ip/base_spec.rb delete mode 100644 spec/unit/util/ip/darwin_spec.rb delete mode 100644 spec/unit/util/ip/dragonfly_spec.rb delete mode 100644 spec/unit/util/ip/free_bsd_spec.rb delete mode 100644 spec/unit/util/ip/gnu_k_free_bsd_spec.rb delete mode 100644 spec/unit/util/ip/hpux_spec.rb delete mode 100644 spec/unit/util/ip/linux_spec.rb delete mode 100644 spec/unit/util/ip/net_bsd_spec.rb delete mode 100644 spec/unit/util/ip/open_bsd_spec.rb delete mode 100644 spec/unit/util/ip/sun_os_spec.rb delete mode 100644 spec/unit/util/ip/windows_spec.rb diff --git a/lib/facter/interfaces.rb b/lib/facter/interfaces.rb index 09f97a28bf..9b132e1c24 100644 --- a/lib/facter/interfaces.rb +++ b/lib/facter/interfaces.rb @@ -1,10 +1,43 @@ -# encoding: UTF-8 - -# Fact: interfaces +# interfaces.rb +# Try to get additional Facts about the machine's network interfaces +# +# Original concept Copyright (C) 2007 psychedelys +# Update and *BSD support (C) 2007 James Turnbull # -# Purpose: -# Try to get facts about the machine's network interfaces require 'facter/util/ip' -Facter::Util::IP.add_interface_facts +# Note that most of this only works on a fixed list of platforms; notably, Darwin +# is missing. + +Facter.add(:interfaces) do + confine :kernel => 'Linux' + has_weight 20 + setcode do + list = Dir.glob('/sys/class/net/*').map do |name| + Facter::Util::IP.alphafy(name.split('/').last) + end + list.empty? ? nil : list.join(',') + end +end + +Facter.add(:interfaces) do + confine :kernel => Facter::Util::IP.supported_platforms + setcode do + Facter::Util::IP.get_interfaces.collect { |iface| Facter::Util::IP.alphafy(iface) }.join(",") + end +end + +Facter::Util::IP.get_interfaces.each do |interface| + + # Make a fact for each detail of each interface. Yay. + # There's no point in confining these facts, since we wouldn't be able to create + # them if we weren't running on a supported platform. + %w{ipaddress ipaddress6 macaddress netmask mtu}.each do |label| + Facter.add(label + "_" + Facter::Util::IP.alphafy(interface)) do + setcode do + Facter::Util::IP.get_interface_value(interface, label) + end + end + end +end diff --git a/lib/facter/network.rb b/lib/facter/network.rb new file mode 100644 index 0000000000..390895a05b --- /dev/null +++ b/lib/facter/network.rb @@ -0,0 +1,20 @@ +# Fact: network +# +# Purpose: +# Get IP, network and netmask information for available network +# interfacs. +# +# Resolution: +# Uses 'facter/util/ip' to enumerate interfaces and return their information. +# +# Caveats: +# +require 'facter/util/ip' + +Facter::Util::IP.get_interfaces.each do |interface| + Facter.add("network_" + Facter::Util::IP.alphafy(interface)) do + setcode do + Facter::Util::IP.get_network_value(interface) + end + end +end diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index 9d38d3ea48..6bed1dd4d0 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -1,249 +1,296 @@ -# encoding: UTF-8 - -require 'facter/util/ip/base' -require 'facter/util/ip/darwin' -require 'facter/util/ip/sun_os' -require 'facter/util/ip/linux' -require 'facter/util/ip/net_bsd' -require 'facter/util/ip/open_bsd' -require 'facter/util/ip/free_bsd' -require 'facter/util/ip/dragonfly' -require 'facter/util/ip/windows' -require 'facter/util/ip/hpux' -require 'facter/util/ip/gnu_k_free_bsd' - -# A base module for collecting IP related info from all kinds of platforms. -class Facter::Util::IP - INTERFACE_KEYS = %w[ipaddress ipaddress6 macaddress netmask mtu] - - attr_accessor :interfaces_hash - - # Uses the interfaces stored in {@interfaces} to obtain and parse the - # attributes corresponding to {INTERFACE_KEYS} and stores the resulting hash - # in {@interfaces_hash}. - # - # @api private - def parse! - @interfaces_hash = @interfaces.inject({}) do |hashA, interface| - hashA[interface] = INTERFACE_KEYS.inject({}) do |hashB, key| - hashB[key] = value_for_interface_and_label interface, key +# A base module for collecting IP-related +# information from all kinds of platforms. +module Facter::Util::IP + # A map of all the different regexes that work for + # a given platform or set of platforms. + REGEX_MAP = { + :linux => { + :ipaddress => /inet (?:addr:)?([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, + :ipaddress6 => /inet6 (?:addr: )?((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/, + :macaddress => /(?:ether|HWaddr)\s+((\w{1,2}:){5,}\w{1,2})/, + :netmask => /Mask:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, + :mtu => /MTU:(\d+)/ + }, + :bsd => { + :aliases => [:openbsd, :netbsd, :freebsd, :darwin, :"gnu/kfreebsd", :dragonfly], + :ipaddress => /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, + :ipaddress6 => /inet6 ((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/, + :macaddress => /(?:ether|lladdr)\s+(\w?\w:\w?\w:\w?\w:\w?\w:\w?\w:\w?\w)/, + :netmask => /netmask\s+0x(\w{8})/, + :mtu => /mtu\s+(\d+)/ + }, + :sunos => { + :ipaddress => /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, + :ipaddress6 => /inet6 ((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/, + :macaddress => /(?:ether|lladdr)\s+(\w?\w:\w?\w:\w?\w:\w?\w:\w?\w:\w?\w)/, + :netmask => /netmask\s+(\w{8})/, + :mtu => /mtu\s+(\d+)/ + }, + :"hp-ux" => { + :ipaddress => /\s+inet (\S+)\s.*/, + :macaddress => /(\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/, + :netmask => /.*\s+netmask (\S+)\s.*/ + }, + :windows => { + :ipaddress => /\s+IP Address:\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, + :ipaddress6 => /Address ((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/, + :netmask => /\s+Subnet Prefix:\s+\S+\s+\(mask ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\)/ + } + } - hashB - end + # Convert an interface name into purely alphanumeric characters. + def self.alphafy(interface) + interface.gsub(/[^a-z0-9_]/i, '_') + end - hashA[interface][:network] = network interface + def self.convert_from_hex?(kernel) + kernels_to_convert = [:sunos, :openbsd, :netbsd, :freebsd, :darwin, :"hp-ux", :"gnu/kfreebsd", :dragonfly] + kernels_to_convert.include?(kernel) + end - hashA + def self.supported_platforms + REGEX_MAP.inject([]) do |result, tmp| + key, map = tmp + if map[:aliases] + result += map[:aliases] + else + result << key + end + result end end - # Adds interface facts like 'eth0'. Also defines dynamic facts describing - # attributes of each interface, like 'ipaddress_eth0' and 'network_eth0'. - # - # @api private - def self.add_interface_facts - model = new - - model.refresh - model.add_dynamic_interface_facts - - Facter.add :interfaces do - confine :kernel => model.supported_platforms - - setcode do - model.refresh if model.flushed? - model.add_dynamic_interface_facts - model.stringified_interfaces + def self.get_interfaces + # Use sysfs on most Linux systems, which is the fastest option. + if File.exist?('/sys/class/net') + return Dir.glob('/sys/class/net/*').map do |name| + Facter::Util::IP.alphafy(name.split('/').last) end - - on_flush { model.flush! } end - end + return [] unless output = Facter::Util::IP.get_all_interface_output() - # Convert an interface name into purely alphanumeric characters. - # - # @param [String] interface e.g. 'eth0' - # - # @return [String] - # - # @api private - def self.alphafy(interface) - interface.to_s.gsub(/[^a-z0-9_]/i, '_') - end + # windows interface names contain spaces and are quoted and can appear multiple + # times as ipv4 and ipv6 + return output.scan(/\s* connected\s*(\S.*)/).flatten.uniq if Facter.value(:kernel) == 'windows' - # Returns an array of supported platforms in string format. These array values - # are synonymous with the values returned from Facter.value(:kernel). - # - # @return [Array] contains strings corresponding to a kernel - # - # @api private - def supported_platforms - kernel_classes.map(&:to_s) + # Our regex appears to be stupid, in that it leaves colons sitting + # at the end of interfaces. So, we have to trim those trailing + # characters. I tried making the regex better but supporting all + # platforms with a single regex is probably a bit too much. + output.scan(/^\S+/).collect { |i| i.sub(/:$/, '') }.uniq end - # A delegate method to the kernel's subclass ultimately obtaining the - # interfaces. - # - # @return [Array] - # - # @api private - def interfaces - kernel_class.interfaces + def self.get_all_interface_output + case Facter.value(:kernel) + when 'Linux', 'OpenBSD', 'NetBSD', 'FreeBSD', 'Darwin', 'GNU/kFreeBSD', 'DragonFly' + output = Facter::Util::IP.exec_ifconfig(["-a","2>/dev/null"]) + when 'SunOS' + output = Facter::Util::IP.exec_ifconfig(["-a"]) + when 'HP-UX' + # (#17487)[https://projects.puppetlabs.com/issues/17487] + # Handle NIC bonding where asterisks and virtual NICs are printed. + if output = hpux_netstat_in + output.gsub!(/\*/, "") # delete asterisks. + output.gsub!(/^[^\n]*none[^\n]*\n/, "") # delete lines with 'none' instead of IPs. + output.sub!(/^[^\n]*\n/, "") # delete the header line. + output + end + when 'windows' + output = %x|#{ENV['SYSTEMROOT']}/system32/netsh.exe interface ip show interface| + output += %x|#{ENV['SYSTEMROOT']}/system32/netsh.exe interface ipv6 show interface| + end + output end - # Uses the ifconfig command - # - # @param [Array] additional arguments - # - # @return [String] the output of the command + + ## + # exec_ifconfig uses the ifconfig command # - # @api private + # @return [String] the output of `ifconfig #{arguments} 2>/dev/null` or nil def self.exec_ifconfig(additional_arguments=[]) - Facter::Util::Resolution.exec([self.get_ifconfig.to_s, *additional_arguments].join(' ')) + Facter::Util::Resolution.exec("#{self.get_ifconfig} #{additional_arguments.join(' ')}") end - - # Looks up the ifconfig binary. + ## + # get_ifconfig looks up the ifconfig binary # # @return [String] path to the ifconfig binary - # - # @api private def self.get_ifconfig common_paths=["/bin/ifconfig","/sbin/ifconfig","/usr/sbin/ifconfig"] common_paths.select{|path| File.executable?(path)}.first end + ## + # hpux_netstat_in is a delegate method that allows us to stub netstat -in + # without stubbing exec. + def self.hpux_netstat_in + Facter::Util::Resolution.exec("/bin/netstat -in") + end - # A delegate method to `value_for_interface_and_label` which is implemented in - # Facter::Util::IP::Base and it's subclasses. - # - # @param interface [String] label [String] e.g ['eth0', 'MTU'] - # - # @return [String] or [NilClass] - # - # @api private - def value_for_interface_and_label(interface, label) - kernel_class.value_for_interface_and_label interface, label + def self.get_infiniband_macaddress(interface) + if File.exists?("/sys/class/net/#{interface}/address") then + ib_mac_address = `cat /sys/class/net/#{interface}/address`.chomp + elsif File.exists?("/sbin/ip") then + ip_output = %x{/sbin/ip link show #{interface}} + ib_mac_address = ip_output.scan(%r{infiniband\s+((\w{1,2}:){5,}\w{1,2})}) + else + ib_mac_address = "FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF" + Facter.debug("ip.rb: nothing under /sys/class/net/#{interface}/address and /sbin/ip not available") + end + ib_mac_address end - # A delegate method to obtain the network of an interface - # - # @param interface [String] e.g 'eth0' - # - # @return [String] or [NilClass] - # - # @api private - def network(interface) - kernel_class.network(interface) + def self.ifconfig_interface(interface) + output = Facter::Util::IP.exec_ifconfig([interface,"2>/dev/null"]) end - # A delegate method for obtaining Facter::Util::IP::Base's subclasses. - # - # @return [Array] - # - # @api private - def kernel_classes - Facter::Util::IP::Base.subclasses + def self.get_single_interface_output(interface) + output = "" + case Facter.value(:kernel) + when 'OpenBSD', 'NetBSD', 'FreeBSD', 'Darwin', 'GNU/kFreeBSD', 'DragonFly' + output = Facter::Util::IP.ifconfig_interface(interface) + when 'Linux' + ifconfig_output = Facter::Util::IP.ifconfig_interface(interface) + if interface =~ /^ib/ then + real_mac_address = get_infiniband_macaddress(interface) + output = ifconfig_output.sub(%r{(?:ether|HWaddr)\s+((\w{1,2}:){5,}\w{1,2})}, "HWaddr #{real_mac_address}") + else + output = ifconfig_output + end + when 'SunOS' + output = Facter::Util::IP.exec_ifconfig([interface]) + when 'HP-UX' + mac = "" + ifc = hpux_ifconfig_interface(interface) + hpux_lanscan.scan(/(\dx\S+).*UP\s+(\w+\d+)/).each {|i| mac = i[0] if i.include?(interface) } + mac = mac.sub(/0x(\S+)/,'\1').scan(/../).join(":") + output = ifc + "\n" + mac + end + output end - # Obtains the cooresponding Facter::Util::IP::Base subclass for the current - # kernel. - # - # @return Subclass of [Facter::Util::IP::Base] - # - # @api private - def kernel_class - kernel_classes.find { |klass| klass.to_s == Facter.value(:kernel) } + def self.hpux_ifconfig_interface(interface) + Facter::Util::IP.exec_ifconfig([interface]) end - # Boolean to determine whether the current kernel is supported. - # - # @return [Boolean] true or false - # - # @api private - def kernel_supported? - supported_platforms.include?(Facter.value(:kernel)) + def self.hpux_lanscan + Facter::Util::Resolution.exec("/usr/sbin/lanscan") end - # Stringifies interfaces so that it can be used as a fact value. - # - # @return [String] the interfaces as a string - # - # @api private - def stringified_interfaces - alphafied_interfaces = @interfaces.map do |interface| - Facter::Util::IP.alphafy(interface) + def self.get_output_for_interface_and_label(interface, label) + return get_single_interface_output(interface) unless Facter.value(:kernel) == 'windows' + + if label == 'ipaddress6' + output = %x|#{ENV['SYSTEMROOT']}/system32/netsh.exe interface ipv6 show address \"#{interface}\"| + else + output = %x|#{ENV['SYSTEMROOT']}/system32/netsh.exe interface ip show address \"#{interface}\"| end + output + end - alphafied_interfaces.join ',' + def self.get_bonding_master(interface) + if Facter.value(:kernel) != 'Linux' + return nil + end + # We need ip instead of ifconfig because it will show us + # the bonding master device. + if not FileTest.executable?("/sbin/ip") + return nil + end + # A bonding interface can never be an alias interface. Alias + # interfaces do have a colon in their name and the ip link show + # command throws an error message when we pass it an alias + # interface. + if interface =~ /:/ + return nil + end + regex = /SLAVE[,>].* (bond[0-9]+)/ + ethbond = regex.match(%x{/sbin/ip link show #{interface}}) + if ethbond + device = ethbond[1] + else + device = nil + end + device end - # Defines all of the dynamic interface facts derived from parsing the output - # of the network interface ouput. The interface facts are dynamic, so this - # method has the behavior of figuring out what facts need to be added and how - # they should be resolved. + ## + # get_interface_value obtains the value of a specific attribute of a specific + # interface. + # + # @param interface [String] the interface identifier, e.g. "eth0" or "bond0" + # + # @param label [String] the attribute of the interface to obtain a value for, + # e.g. "netmask" or "ipaddress" # # @api private - def add_dynamic_interface_facts - model = self + # + # @return [String] representing the requested value. An empty array is + # returned if the kernel is not supported by the REGEX_MAP constant. + def self.get_interface_value(interface, label) + tmp1 = [] - @interfaces.each do |interface| - INTERFACE_KEYS.each do |key| - Facter.add "#{key}_#{model.class.alphafy(interface)}" do - confine :kernel => model.supported_platforms + kernel = Facter.value(:kernel).downcase.to_sym - setcode do - model.refresh if model.flushed? + # If it's not directly in the map or aliased in the map, then we don't know how to deal with it. + unless map = REGEX_MAP[kernel] || REGEX_MAP.values.find { |tmp| tmp[:aliases] and tmp[:aliases].include?(kernel) } + return [] + end - # Don't resolve if the interface has since been deleted - if keys_hash = model.interfaces_hash[interface] - keys_hash[key] + # Pull the correct regex out of the map. + regex = map[label.to_sym] + + # Linux changes the MAC address reported via ifconfig when an ethernet interface + # becomes a slave of a bonding device to the master MAC address. + # We have to dig a bit to get the original/real MAC address of the interface. + bonddev = get_bonding_master(interface) + if label == 'macaddress' and bonddev + bondinfo = read_proc_net_bonding("/proc/net/bonding/#{bonddev}") + re = /^Slave Interface: #{interface}\b.*?\bPermanent HW addr: (([0-9A-F]{2}:?)*)$/im + if match = re.match(bondinfo) + value = match[1].upcase + end + else + output_int = get_output_for_interface_and_label(interface, label) + + output_int.each_line do |s| + if s =~ regex + value = $1 + if label == 'netmask' && convert_from_hex?(kernel) + value = value.scan(/../).collect do |byte| byte.to_i(16) end.join('.') end - end - - on_flush { model.flush! } + tmp1.push(value) end end - Facter.add "network_#{interface}" do - confine :kernel => model.supported_platforms - - setcode do - model.refresh if model.flushed? - - # Don't resolve if the interface has since been deleted - if keys_hash = model.interfaces_hash[interface] - keys_hash[:network] - end - end - - on_flush { model.flush! } + if tmp1 + value = tmp1.shift end end end - # Executes the platform specific system command to obtain the interfaces and - # stores them in {@interfaces}. + ## + # read_proc_net_bonding is a seam method for mocking purposes. # - # @api private - def refresh - @interfaces = interfaces - - parse! - end - - # Checks to see if the intstance has been flushed. - # - # @return [Boolean] true if there is no parsed data + # @param path [String] representing the path to read, e.g. "/proc/net/bonding/bond0" # # @api private - def flushed? - !interfaces_hash + # + # @return [String] modeling the raw file read + def self.read_proc_net_bonding(path) + File.read(path) if File.exists?(path) end + private_class_method :read_proc_net_bonding - # Purges the saved data so that the fact can be resolved properly upon flush. - # - # @api private - def flush! - @interfaces_hash = nil + def self.get_network_value(interface) + require 'ipaddr' + + ipaddress = get_interface_value(interface, "ipaddress") + netmask = get_interface_value(interface, "netmask") + + if ipaddress && netmask + ip = IPAddr.new(ipaddress, Socket::AF_INET) + subnet = IPAddr.new(netmask, Socket::AF_INET) + network = ip.mask(subnet.to_s).to_s + end end end diff --git a/lib/facter/util/ip/base.rb b/lib/facter/util/ip/base.rb deleted file mode 100644 index eaa3faa061..0000000000 --- a/lib/facter/util/ip/base.rb +++ /dev/null @@ -1,196 +0,0 @@ -# encoding: UTF-8 - -require 'ipaddr' - -module Facter - module Util - class IP - end - end -end - -class Facter::Util::IP::Base - # A regex to match an IPv4 address from `ifconfig` output. This regex will - # work for most platforms. You can override this in your subclass if you need - # a different regex. - # - # @return [Regexp] - # - # @api private - IPADDRESS_REGEX = /inet.*?([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ - - - # A regex to match an IPv6 address from `ifconfig` output. This regex will - # work for most platforms. You can override this in your subclass if you need - # a different regex. - # - # @return [Regexp] - # - # @api private - IPADDRESS6_REGEX = / - inet6.*?((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4}) - /x - - # A regex to match a MAC address from `ifconfig` output. This regex will work - # for most platforms. You can override this in your subclass if you need a - # different regex. - # - # @return [Regexp] - # - # @api private - MACADDRESS_REGEX = /(?:ether|lladdr)\s+(\w?\w:\w?\w:\w?\w:\w?\w:\w?\w:\w?\w)/ - - # A regex to match the netmask from `ifconfig` output. This regex will work - # for most platforms. You can override this in your subclass if you need a - # different regex. - # - # @return [Regexp] - # - # @api private - NETMASK_REGEX = /netmask\s+0x(\w{8})/ - - # A regex to match the MTU from `ifconfig` output. This regex will work for - # most platforms. You can override this in your subclass if you need a - # different regex. - # - # @return [Regexp] - # - # @api private - MTU_REGEX = /mtu\s+(\d+)/ - - # Returns the name of the Class without nesting. Mostly used for finding - # the right class corresponding to the value of Facter.value(:kernel) - # - # @return [String] The string without nesting. - # - # @api private - def self.to_s - super.split('::').last - end - - # Used in conjunction with the `.inherited` hook, this method will store - # an Array of the Class' subclasses. - # - # @return [Array] The subclasses. - # - # @api private - def self.subclasses - @subclasses ||= [] - end - - # Most kernels will need to have their netmask converted from hex. If - # your kernel doesn't display the netmask in hex, you'll need to - # override this method in your subclass to return false. - # - # @return [Boolean] true - # - # @api private - def self.convert_netmask_from_hex? - true - end - - # Network bonding is creation of a single bonded interface by combining 2 or - # more Ethernet interfaces. I think this is mostly used in Linux so this base - # method will return nil, however you should override this in your subclass if - # need be. See the Facter::Util::IP::Linux.bonding_master method. - # - # @return [NilClass] - # - # @api private - def self.bonding_master(interface) - end - - # Returns an array of interfaces from `ifconfig`. e.g. ['eth0', 'eth1'] This - # will work on most platforms, but override in your subclass if need be. - # - # @return [Array] - # - # @api private - def self.interfaces - exec("#{ifconfig_path} -a 2> /dev/null").to_s.scan(/^\w+/).uniq - end - - # Get the value of an interface and label. For example, you may want to find - # the MTU for eth0. - # - # @param interface [String] label [String] and optional command [String] - # - # @return [String] or [NilClass] - # - # @api private - def self.value_for_interface_and_label(interface, label, cmd = nil) - if regex = regex_for(label) - cmd ||= "#{ifconfig_path} #{interface} 2> /dev/null" - - if match = regex.match(exec(cmd).to_s) - if label == 'netmask' && convert_netmask_from_hex? - match[1].scan(/../).map { |byte| byte.to_i(16) }.join('.') - else - match[1] - end - end - end - end - - # Returns the IP network for the given interface. - # - # @return [String] or [NilClass] - # - # @api private - def self.network(interface) - ipaddress = value_for_interface_and_label(interface, "ipaddress") - netmask = value_for_interface_and_label(interface, "netmask") - - if ipaddress && netmask - ip = IPAddr.new(ipaddress, Socket::AF_INET) - subnet = IPAddr.new(netmask, Socket::AF_INET) - - ip.mask(subnet.to_s).to_s - end - end - - # This is a Ruby hook method that will help to maintain a list of - # subclasses. See the `.subclasses` method for more information. - # - # @return [Array] The subclasses. - # - # @api private - def self.inherited subclass - subclasses << subclass - end - - # This loops over `ifconfig` paths to find the first that is executable. - # - # @return [String] - # - # @api private - def self.ifconfig_path - %w[/bin/ifconfig /sbin/ifconfig /usr/sbin/ifconfig].find do |path| - File.executable?(path) - end - end - - # Delegation method to Facter::Util::Resolution.exec. - # - # @param command [String] the command to execute - # - # @return [String] or [Nil] - # - # @api private - def self.exec string - Facter::Util::Resolution.exec string - end - - # Grabs the corresponding regex constant. e.g. NETMASK_REGEX - # - # @param label [String] e.g. 'netmask' - # - # @return [Regexp] or [NilClass] - # - # @api private - def self.regex_for label - constant = "#{label.to_s.upcase}_REGEX" - - const_get(constant) if constants.find { |c| /^#{constant}$/.match(c) } - end -end diff --git a/lib/facter/util/ip/darwin.rb b/lib/facter/util/ip/darwin.rb deleted file mode 100644 index 83c95fc791..0000000000 --- a/lib/facter/util/ip/darwin.rb +++ /dev/null @@ -1,6 +0,0 @@ -# encoding: UTF-8 - -require 'facter/util/ip/base' - -class Facter::Util::IP::Darwin < Facter::Util::IP::Base -end diff --git a/lib/facter/util/ip/dragonfly.rb b/lib/facter/util/ip/dragonfly.rb deleted file mode 100644 index 0ccf87c359..0000000000 --- a/lib/facter/util/ip/dragonfly.rb +++ /dev/null @@ -1,6 +0,0 @@ -# encoding: UTF-8 - -require 'facter/util/ip/base' - -class Facter::Util::IP::Dragonfly < Facter::Util::IP::Base -end diff --git a/lib/facter/util/ip/free_bsd.rb b/lib/facter/util/ip/free_bsd.rb deleted file mode 100644 index 6db3942c39..0000000000 --- a/lib/facter/util/ip/free_bsd.rb +++ /dev/null @@ -1,6 +0,0 @@ -# encoding: UTF-8 - -require 'facter/util/ip/base' - -class Facter::Util::IP::FreeBSD < Facter::Util::IP::Base -end diff --git a/lib/facter/util/ip/gnu_k_free_bsd.rb b/lib/facter/util/ip/gnu_k_free_bsd.rb deleted file mode 100644 index 5988a1562c..0000000000 --- a/lib/facter/util/ip/gnu_k_free_bsd.rb +++ /dev/null @@ -1,9 +0,0 @@ -# encoding: UTF-8 - -require 'facter/util/ip/base' - -class Facter::Util::IP::GNUkFreeBSD < Facter::Util::IP::Base - def self.to_s - 'GNU/kFreeBSD' - end -end diff --git a/lib/facter/util/ip/hpux.rb b/lib/facter/util/ip/hpux.rb deleted file mode 100644 index 3485b41d36..0000000000 --- a/lib/facter/util/ip/hpux.rb +++ /dev/null @@ -1,76 +0,0 @@ -# encoding: UTF-8 - -require 'facter/util/ip/base' - -class Facter::Util::IP::HPUX < Facter::Util::IP::Base - # A regex to match an IPv4 address from `ifconfig` output. - # - # @return [Regexp] - # - # @api private - IPADDRESS_REGEX = /\s+inet (\S+)\s.*/ - - # A regex to match a MAC address from `ifconfig` output. - # - # @return [Regexp] - # - # @api private - MACADDRESS_REGEX = /(\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/ - - # A regex to match the netmask from `ifconfig` output. - # - # @return [Regexp] - # - # @api private - NETMASK_REGEX = /.*\s+netmask (\S+)\s.*/ - - # The path to the `lanscan` executable. - # - # @return [Regexp] - # - # @api private - LANSCAN = '/usr/sbin/lanscan' - - def self.to_s - 'HP-UX' - end - - # Gets an array of interfaces from `netstat`. The cryptic text replacements in - # the method handles NIC bonding where asterisks and virtual NICs are printed. - # See (#17487)[https://projects.puppetlabs.com/issues/17487] for more info. - # - # @return [Array] - # - # @api private - def self.interfaces - exec("/bin/netstat -in"). - to_s. - gsub(/\*/, ""). - gsub(/^[^\n]*none[^\n]*\n/, ""). - sub(/^[^\n]*\n/, ""). - scan(/^\w+/) - end - - def self.value_for_interface_and_label(interface, label) - value = super(interface, label) - - if !value && label == 'macaddress' - if macaddress = lanscan.to_s[/\dx(\S+).*UP\s+#{interface}/, 1] - macaddress.scan(/../).join(':') - end - else - value - end - end - - private - - # Execute lanscan. - # - # @return [String] or [NilClass] - # - # @api private - def self.lanscan - exec(LANSCAN) if File.exist?(LANSCAN) - end -end diff --git a/lib/facter/util/ip/linux.rb b/lib/facter/util/ip/linux.rb deleted file mode 100644 index 45fee9f310..0000000000 --- a/lib/facter/util/ip/linux.rb +++ /dev/null @@ -1,169 +0,0 @@ -# encoding: UTF-8 - -require 'facter/util/ip/base' - -class Facter::Util::IP::Linux < Facter::Util::IP::Base - # A regex to match an IPv4 address from `ifconfig` output. - # - # @return [Regexp] - # - # @api private - IPADDRESS_REGEX = /inet\s(?:addr:)?([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ - - # A regex to match an IPv6 address from `ifconfig` output. - # - # @return [Regexp] - # - # @api private - IPADDRESS6_REGEX = /inet6\s(?:addr: )?((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/ - - - # A regex to match a MAC address from `ifconfig` output. - # - # @return [Regexp] - # - # @api private - MACADDRESS_REGEX = /(?:ether|HWaddr)\s+((\w{1,2}:){5,}\w{1,2})/ - - # A regex to match the netmask from `ifconfig` output. - # - # @return [Regexp] - # - # @api private - NETMASK_REGEX = /Mask:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ - - # A regex to match the MTU from `ifconfig` output. - # - # @return [Regexp] - # - # @api private - MTU_REGEX = /MTU:(\d+)/ - - # Linux doesn't display netmask in hex. - # - # @return [Boolean] false by default - # - # @api private - def self.convert_netmask_from_hex? - false - end - - # Network bonding is creation of a single bonded interface by combining 2 or - # more Ethernet interfaces. This method returns the bonding master. - # - # @param interface [String] the interface, e.g. 'eth0' - # - # @return [String] or [NilClass] - # - # @api private - def self.bonding_master(interface) - # We need `ip` instead of `ifconfig` because it shows us the bonding master. - return unless FileTest.executable?("/sbin/ip") - - # A bonding interface can never be an alias interface. Alias interfaces do - # have a colon in their name and the ip link show command throws an error - # message when we pass it an alias interface. - return if interface.match(/:/) - - regex = /SLAVE[,>].* (bond[0-9]+)/ - ethbond = regex.match(%x{/sbin/ip link show #{interface}}) - - ethbond[1] if ethbond - end - - # Returns an array of interfaces. e.g. ['eth0', 'eth1'] We will check sysfs - # first, since that is the fastest option, but fallback to `ifconfig` if - # neccessary. - # - # @return [Array] - # - # @api private - def self.interfaces - if File.exist?('/sys/class/net') - Dir.glob('/sys/class/net/*').map do |name| - name.split('/').last - end - else - super - end - end - - # Get the value of an interface and label. For example, you may want to find - # the MTU for eth0. If an infiniband interface is passed, it will try to - # obtain the real value. - # - # @param interface [String] label [String] and optional command [String] - # - # @return [String] or [NilClass] - # - # @api private - def self.value_for_interface_and_label(interface, label) - if label == 'macaddress' - bonddev = bonding_master(interface) - - if infiniband?(interface) - infiniband_macaddress(interface) || super - elsif bonddev - bonddev_macaddress(bonddev, interface) || super - else - super - end - else - super - end - end - - private - - # Boolean method to test whether an interface is the infiniband. - # - # @param interface [String] e.g. 'ib0' - # - # @return [Boolean] true or false - # - # @api private - def self.infiniband?(interface) - !!/^ib/.match(interface) - end - - # Attempts to obtain the real macaddress for an infiniband interface. - # - # @param interface [String] e.g. 'ib0' - # - # @return [String] or [NilClass] - # - # @api private - def self.infiniband_macaddress(interface) - sysfs = "/sys/class/net/#{interface}/address" - - if File.exists?(sysfs) - exec("cat #{sysfs}") - elsif File.exists?("/sbin/ip") - exec("/sbin/ip link show #{interface}"). - to_s. - scan(%r{infiniband\s+((\w{1,2}:){5,}\w{1,2})})[0] - end - end - - # Attempts to obtain the real macaddress for a bonded interface. - # - # @param bonddev [String] interface [String] e.g. 'bond0', 'eth0' - # - # @return [String] or [NilClass] - # - # @api private - def self.bonddev_macaddress(bonddev, interface) - path = "/proc/net/bonding/#{bonddev}" - - if File.exists?(path) - bondinfo = File.read(path) - regex = / - ^Slave\sInterface:\s - #{interface}\b.*?\bPermanent\sHW\saddr:\s(([0-9A-F]{2}:?)*)$ - /imx - match = regex.match(bondinfo) - - match[1].upcase if match - end - end -end diff --git a/lib/facter/util/ip/net_bsd.rb b/lib/facter/util/ip/net_bsd.rb deleted file mode 100644 index 3b97c9c158..0000000000 --- a/lib/facter/util/ip/net_bsd.rb +++ /dev/null @@ -1,6 +0,0 @@ -# encoding: UTF-8 - -require 'facter/util/ip/base' - -class Facter::Util::IP::NetBSD < Facter::Util::IP::Base -end diff --git a/lib/facter/util/ip/open_bsd.rb b/lib/facter/util/ip/open_bsd.rb deleted file mode 100644 index 3204a70194..0000000000 --- a/lib/facter/util/ip/open_bsd.rb +++ /dev/null @@ -1,6 +0,0 @@ -# encoding: UTF-8 - -require 'facter/util/ip/base' - -class Facter::Util::IP::OpenBSD < Facter::Util::IP::Base -end diff --git a/lib/facter/util/ip/sun_os.rb b/lib/facter/util/ip/sun_os.rb deleted file mode 100644 index 5533247ab6..0000000000 --- a/lib/facter/util/ip/sun_os.rb +++ /dev/null @@ -1,24 +0,0 @@ -# encoding: UTF-8 - -require 'facter/util/ip/base' - -class Facter::Util::IP::SunOS < Facter::Util::IP::Base - # A regex to match the netmask from `ifconfig` output. - # - # @return [Regexp] - # - # @api private - NETMASK_REGEX = /netmask\s(\w{8})/ - - # Get the value of an interface and label. For example, you may want to find - # the MTU for eth0. - # - # @param interface [String] label [String] e.g ['eth0', 'MTU'] - # - # @return [String] or [NilClass] - # - # @api private - def self.value_for_interface_and_label(interface, label) - super(interface, label, "#{ifconfig_path} #{interface}") - end -end diff --git a/lib/facter/util/ip/windows.rb b/lib/facter/util/ip/windows.rb deleted file mode 100644 index 52e6d623b7..0000000000 --- a/lib/facter/util/ip/windows.rb +++ /dev/null @@ -1,73 +0,0 @@ -# encoding: UTF-8 - -require 'facter/util/ip/base' - -class Facter::Util::IP::Windows < Facter::Util::IP::Base - # A regex to match an IPv4 address from `ifconfig` output. - # - # @return [Regexp] - # - # @api private - IPADDRESS_REGEX = /\s+IP\sAddress:\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ - - # A regex to match an IPv6 address from `ifconfig` output. - # - # @return [Regexp] - # - # @api private - IPADDRESS6_REGEX = /Address\s((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/ - - # A regex to match the netmask from `ifconfig` output. - # - # @return [Regexp] - # - # @api private - NETMASK_REGEX = /\s+Subnet\sPrefix:\s+\S+\s+\(mask\s([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\)/ - - # The path to netsh.exe. - # - # @return [String] - # - # @api private - NETSH = "#{ENV['SYSTEMROOT']}/system32/netsh.exe" - - def self.to_s - 'windows' - end - - # Windows doesn't display netmask in hex. - # - # @return [Boolean] false by default - # - # @api private - def self.convert_netmask_from_hex? - false - end - - # Uses netsh.exe to obtain a list of interfaces. - # - # @return [Array] - # - # @api private - def self.interfaces - cmd = "#{NETSH} interface %s show interface" - output = exec("#{cmd % 'ip'} && #{cmd % 'ipv6'}").to_s - - output.scan(/\s* connected\s*(\S.*)/).flatten.uniq - end - - # Get the value of an interface and label. For example, you may want to find - # the MTU for eth0. Uses netsh.exe. - # - # @param interface [String] label [String] - # - # @return [String] or [NilClass] - # - # @api private - def self.value_for_interface_and_label(interface, label) - opt = label == 'ipaddress6' ? 'ipv6' : 'ip' - cmd = "#{NETSH} interface #{opt} show address \"#{interface}\"" - - super(interface, label, cmd) - end -end diff --git a/spec/fixtures/unit/util/ip/free_bsd/6.0-STABLE_ifconfig_with_multiple_interfaces b/spec/fixtures/unit/util/ip/6.0-STABLE_FreeBSD_ifconfig similarity index 100% rename from spec/fixtures/unit/util/ip/free_bsd/6.0-STABLE_ifconfig_with_multiple_interfaces rename to spec/fixtures/unit/util/ip/6.0-STABLE_FreeBSD_ifconfig diff --git a/spec/fixtures/unit/util/ip/darwin/ifconfig_with_single_interface b/spec/fixtures/unit/util/ip/darwin/ifconfig_with_single_interface deleted file mode 100644 index 6a4e10326c..0000000000 --- a/spec/fixtures/unit/util/ip/darwin/ifconfig_with_single_interface +++ /dev/null @@ -1,6 +0,0 @@ -en0: flags=8863 mtu 1500 - inet6 fe80::223:6cff:fe99:602b%en1 prefixlen 64 scopeid 0x5 - inet 192.168.0.10 netmask 0xffffff00 broadcast 192.168.0.255 - ether 00:23:6c:99:60:2b - media: autoselect status: active - supported media: autoselect diff --git a/spec/fixtures/unit/util/ip/darwin/ifconfig_all_with_multiple_interfaces b/spec/fixtures/unit/util/ip/darwin_ifconfig_all_with_multiple_interfaces similarity index 100% rename from spec/fixtures/unit/util/ip/darwin/ifconfig_all_with_multiple_interfaces rename to spec/fixtures/unit/util/ip/darwin_ifconfig_all_with_multiple_interfaces diff --git a/spec/fixtures/unit/util/ip/gnu_k_free_bsd/ifconfig_all_with_multiple_interfaces b/spec/fixtures/unit/util/ip/debian_kfreebsd_ifconfig similarity index 100% rename from spec/fixtures/unit/util/ip/gnu_k_free_bsd/ifconfig_all_with_multiple_interfaces rename to spec/fixtures/unit/util/ip/debian_kfreebsd_ifconfig diff --git a/spec/fixtures/unit/util/ip/free_bsd/6.0-STABLE_ifconfig_with_single_interface b/spec/fixtures/unit/util/ip/free_bsd/6.0-STABLE_ifconfig_with_single_interface deleted file mode 100644 index b1324be986..0000000000 --- a/spec/fixtures/unit/util/ip/free_bsd/6.0-STABLE_ifconfig_with_single_interface +++ /dev/null @@ -1,10 +0,0 @@ -fxp0: flags=8843 mtu 1500 - options=b - inet x.x.58.26 netmask 0xfffffff8 broadcast x.x.58.31 - inet x.x.58.27 netmask 0xffffffff broadcast x.x.58.27 - inet x.x.58.28 netmask 0xffffffff broadcast x.x.58.28 - inet x.x.58.29 netmask 0xffffffff broadcast x.x.58.29 - inet x.x.58.30 netmask 0xffffffff broadcast x.x.58.30 - ether 00:0e:0c:68:67:7c - media: Ethernet autoselect (100baseTX ) - status: active diff --git a/spec/fixtures/unit/util/ip/gnu_k_free_bsd/ifconfig_with_single_interface b/spec/fixtures/unit/util/ip/gnu_k_free_bsd/ifconfig_with_single_interface deleted file mode 100644 index b9ce459282..0000000000 --- a/spec/fixtures/unit/util/ip/gnu_k_free_bsd/ifconfig_with_single_interface +++ /dev/null @@ -1,8 +0,0 @@ -em1: flags=8843 metric 0 mtu 1500 - options=209b - ether 0:11:a:59:67:91 - inet6 fe80::211:aff:fe59:6791%em1 prefixlen 64 scopeid 0x2 - inet 192.168.10.10 netmask 0xffffff00 broadcast 192.168.10.255 - nd6 options=3 - media: Ethernet autoselect (100baseTX ) - status: active diff --git a/spec/fixtures/unit/util/ip/hpux/1111_ifconfig_lan0 b/spec/fixtures/unit/util/ip/hpux_1111_ifconfig_lan0 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux/1111_ifconfig_lan0 rename to spec/fixtures/unit/util/ip/hpux_1111_ifconfig_lan0 diff --git a/spec/fixtures/unit/util/ip/hpux/1111_ifconfig_lan1 b/spec/fixtures/unit/util/ip/hpux_1111_ifconfig_lan1 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux/1111_ifconfig_lan1 rename to spec/fixtures/unit/util/ip/hpux_1111_ifconfig_lan1 diff --git a/spec/fixtures/unit/util/ip/hpux/1111_ifconfig_lo0 b/spec/fixtures/unit/util/ip/hpux_1111_ifconfig_lo0 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux/1111_ifconfig_lo0 rename to spec/fixtures/unit/util/ip/hpux_1111_ifconfig_lo0 diff --git a/spec/fixtures/unit/util/ip/hpux/1111_lanscan b/spec/fixtures/unit/util/ip/hpux_1111_lanscan similarity index 100% rename from spec/fixtures/unit/util/ip/hpux/1111_lanscan rename to spec/fixtures/unit/util/ip/hpux_1111_lanscan diff --git a/spec/fixtures/unit/util/ip/hpux/1111_netstat_in b/spec/fixtures/unit/util/ip/hpux_1111_netstat_in similarity index 100% rename from spec/fixtures/unit/util/ip/hpux/1111_netstat_in rename to spec/fixtures/unit/util/ip/hpux_1111_netstat_in diff --git a/spec/fixtures/unit/util/ip/hpux/1131_asterisk_ifconfig_lan0 b/spec/fixtures/unit/util/ip/hpux_1131_asterisk_ifconfig_lan0 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux/1131_asterisk_ifconfig_lan0 rename to spec/fixtures/unit/util/ip/hpux_1131_asterisk_ifconfig_lan0 diff --git a/spec/fixtures/unit/util/ip/hpux/1131_asterisk_ifconfig_lan1 b/spec/fixtures/unit/util/ip/hpux_1131_asterisk_ifconfig_lan1 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux/1131_asterisk_ifconfig_lan1 rename to spec/fixtures/unit/util/ip/hpux_1131_asterisk_ifconfig_lan1 diff --git a/spec/fixtures/unit/util/ip/hpux/1131_asterisk_ifconfig_lo0 b/spec/fixtures/unit/util/ip/hpux_1131_asterisk_ifconfig_lo0 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux/1131_asterisk_ifconfig_lo0 rename to spec/fixtures/unit/util/ip/hpux_1131_asterisk_ifconfig_lo0 diff --git a/spec/fixtures/unit/util/ip/hpux/1131_asterisk_lanscan b/spec/fixtures/unit/util/ip/hpux_1131_asterisk_lanscan similarity index 100% rename from spec/fixtures/unit/util/ip/hpux/1131_asterisk_lanscan rename to spec/fixtures/unit/util/ip/hpux_1131_asterisk_lanscan diff --git a/spec/fixtures/unit/util/ip/hpux/1131_asterisk_netstat_in b/spec/fixtures/unit/util/ip/hpux_1131_asterisk_netstat_in similarity index 100% rename from spec/fixtures/unit/util/ip/hpux/1131_asterisk_netstat_in rename to spec/fixtures/unit/util/ip/hpux_1131_asterisk_netstat_in diff --git a/spec/fixtures/unit/util/ip/hpux/1131_ifconfig_lan0 b/spec/fixtures/unit/util/ip/hpux_1131_ifconfig_lan0 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux/1131_ifconfig_lan0 rename to spec/fixtures/unit/util/ip/hpux_1131_ifconfig_lan0 diff --git a/spec/fixtures/unit/util/ip/hpux/1131_ifconfig_lan1 b/spec/fixtures/unit/util/ip/hpux_1131_ifconfig_lan1 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux/1131_ifconfig_lan1 rename to spec/fixtures/unit/util/ip/hpux_1131_ifconfig_lan1 diff --git a/spec/fixtures/unit/util/ip/hpux/1131_ifconfig_lo0 b/spec/fixtures/unit/util/ip/hpux_1131_ifconfig_lo0 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux/1131_ifconfig_lo0 rename to spec/fixtures/unit/util/ip/hpux_1131_ifconfig_lo0 diff --git a/spec/fixtures/unit/util/ip/hpux/1131_lanscan b/spec/fixtures/unit/util/ip/hpux_1131_lanscan similarity index 100% rename from spec/fixtures/unit/util/ip/hpux/1131_lanscan rename to spec/fixtures/unit/util/ip/hpux_1131_lanscan diff --git a/spec/fixtures/unit/util/ip/hpux/1131_netstat_in b/spec/fixtures/unit/util/ip/hpux_1131_netstat_in similarity index 100% rename from spec/fixtures/unit/util/ip/hpux/1131_netstat_in rename to spec/fixtures/unit/util/ip/hpux_1131_netstat_in diff --git a/spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_ifconfig_lan1 b/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lan1 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_ifconfig_lan1 rename to spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lan1 diff --git a/spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_ifconfig_lan4 b/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lan4 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_ifconfig_lan4 rename to spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lan4 diff --git a/spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_ifconfig_lan4_1 b/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lan4_1 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_ifconfig_lan4_1 rename to spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lan4_1 diff --git a/spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_ifconfig_lo0 b/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lo0 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_ifconfig_lo0 rename to spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lo0 diff --git a/spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_lanscan b/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_lanscan similarity index 100% rename from spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_lanscan rename to spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_lanscan diff --git a/spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_netstat_in b/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_netstat_in similarity index 100% rename from spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_netstat_in rename to spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_netstat_in diff --git a/spec/fixtures/unit/util/ip/linux/2_6_35_proc_net_bonding_bond0 b/spec/fixtures/unit/util/ip/linux_2_6_35_proc_net_bonding_bond0 similarity index 100% rename from spec/fixtures/unit/util/ip/linux/2_6_35_proc_net_bonding_bond0 rename to spec/fixtures/unit/util/ip/linux_2_6_35_proc_net_bonding_bond0 diff --git a/spec/fixtures/unit/util/ip/linux/ifconfig_single_interface_eth0 b/spec/fixtures/unit/util/ip/linux_get_single_interface_eth0 similarity index 100% rename from spec/fixtures/unit/util/ip/linux/ifconfig_single_interface_eth0 rename to spec/fixtures/unit/util/ip/linux_get_single_interface_eth0 diff --git a/spec/fixtures/unit/util/ip/linux/ifconfig_with_single_interface_ib0 b/spec/fixtures/unit/util/ip/linux_get_single_interface_ib0 similarity index 100% rename from spec/fixtures/unit/util/ip/linux/ifconfig_with_single_interface_ib0 rename to spec/fixtures/unit/util/ip/linux_get_single_interface_ib0 diff --git a/spec/fixtures/unit/util/ip/linux/ifconfig_single_interface_lo b/spec/fixtures/unit/util/ip/linux_get_single_interface_lo similarity index 100% rename from spec/fixtures/unit/util/ip/linux/ifconfig_single_interface_lo rename to spec/fixtures/unit/util/ip/linux_get_single_interface_lo diff --git a/spec/fixtures/unit/util/ip/linux/ifconfig_all_with_single_interface b/spec/fixtures/unit/util/ip/linux_ifconfig_all_with_single_interface similarity index 100% rename from spec/fixtures/unit/util/ip/linux/ifconfig_all_with_single_interface rename to spec/fixtures/unit/util/ip/linux_ifconfig_all_with_single_interface diff --git a/spec/fixtures/unit/util/ip/sun_os/ifconfig_all_with_multiple_interfaces b/spec/fixtures/unit/util/ip/solaris_ifconfig_all_with_multiple_interfaces similarity index 100% rename from spec/fixtures/unit/util/ip/sun_os/ifconfig_all_with_multiple_interfaces rename to spec/fixtures/unit/util/ip/solaris_ifconfig_all_with_multiple_interfaces diff --git a/spec/fixtures/unit/util/ip/sun_os/ifconfig_single_interface b/spec/fixtures/unit/util/ip/solaris_ifconfig_single_interface similarity index 100% rename from spec/fixtures/unit/util/ip/sun_os/ifconfig_single_interface rename to spec/fixtures/unit/util/ip/solaris_ifconfig_single_interface diff --git a/spec/fixtures/unit/util/ip/windows/netsh_all_interfaces b/spec/fixtures/unit/util/ip/windows_netsh_all_interfaces similarity index 100% rename from spec/fixtures/unit/util/ip/windows/netsh_all_interfaces rename to spec/fixtures/unit/util/ip/windows_netsh_all_interfaces diff --git a/spec/fixtures/unit/util/ip/windows/netsh_with_single_interface b/spec/fixtures/unit/util/ip/windows_netsh_single_interface similarity index 100% rename from spec/fixtures/unit/util/ip/windows/netsh_with_single_interface rename to spec/fixtures/unit/util/ip/windows_netsh_single_interface diff --git a/spec/fixtures/unit/util/ip/windows/netsh_with_single_interface6 b/spec/fixtures/unit/util/ip/windows_netsh_single_interface6 similarity index 100% rename from spec/fixtures/unit/util/ip/windows/netsh_with_single_interface6 rename to spec/fixtures/unit/util/ip/windows_netsh_single_interface6 diff --git a/spec/unit/blockdevices_spec.rb b/spec/unit/blockdevices_spec.rb index 0061b92e45..9c67f5ea13 100644 --- a/spec/unit/blockdevices_spec.rb +++ b/spec/unit/blockdevices_spec.rb @@ -1,3 +1,5 @@ +#!/usr/bin/env ruby + require 'spec_helper' require 'facter' require 'facter/util/blockdevices' diff --git a/spec/unit/hardwareisa_spec.rb b/spec/unit/hardwareisa_spec.rb index d613026251..a709e8c742 100755 --- a/spec/unit/hardwareisa_spec.rb +++ b/spec/unit/hardwareisa_spec.rb @@ -1,3 +1,5 @@ +#!/usr/bin/env ruby + require 'spec_helper' require 'facter' diff --git a/spec/unit/hardwaremodel_spec.rb b/spec/unit/hardwaremodel_spec.rb index e9ef3d40de..a168c11ef5 100644 --- a/spec/unit/hardwaremodel_spec.rb +++ b/spec/unit/hardwaremodel_spec.rb @@ -1,3 +1,5 @@ +#!/usr/bin/env ruby + require 'spec_helper' require 'facter' diff --git a/spec/unit/interfaces_spec.rb b/spec/unit/interfaces_spec.rb new file mode 100755 index 0000000000..c495a499eb --- /dev/null +++ b/spec/unit/interfaces_spec.rb @@ -0,0 +1,68 @@ +#! /usr/bin/env ruby + +require 'spec_helper' +require 'facter/util/ip' + +shared_examples_for "iface specific ifconfig output" do |platform, address, fixture| + it "correctly on #{platform} for eth0" do + Facter::Util::IP.stubs(:ifconfig_interface).returns(my_fixture_read(fixture)) + subject.value.should == address + end +end + +describe "Per Interface IP facts" do + it "should replace the ':' in an interface list with '_'" do + # So we look supported + Facter.fact(:kernel).stubs(:value).returns("SunOS") + + Facter::Util::IP.stubs(:get_interfaces).returns %w{eth0:1 eth1:2} + Facter.fact(:interfaces).value.should == %{eth0_1,eth1_2} + end + + it "should replace non-alphanumerics in an interface list with '_'" do + Facter.fact(:kernel).stubs(:value).returns("windows") + + Facter::Util::IP.stubs(:get_interfaces).returns ["Local Area Connection", "Loopback \"Pseudo-Interface\" (#1)"] + Facter.fact(:interfaces).value.should == %{Local_Area_Connection,Loopback__Pseudo_Interface____1_} + end +end + + +RSpec.configure do |config| + config.alias_it_should_behave_like_to :example_behavior_for, "parses" +end + + +describe "the ipaddress_$iface fact" do + subject do + Facter.collection.internal_loader.load(:interfaces) + Facter.fact(:ipaddress_eth0) + end + + context "on Linux" do + before :each do + Facter::Util::IP.stubs(:get_interfaces).returns(["eth0"]) + Facter.fact(:kernel).stubs(:value).returns("Linux") + end + example_behavior_for "iface specific ifconfig output", "Fedora 18", "10.10.220.1", "eth0_net_tools_2.0.txt" + + example_behavior_for "iface specific ifconfig output", "net_tools 1.60", "10.10.220.210", "eth0_net_tools_1.60.txt" + end +end + +describe "the ipaddress6_$iface fact" do + subject do + Facter.collection.internal_loader.load(:interfaces) + Facter.fact(:ipaddress6_eth0) + end + + context "on Linux" do + before :each do + Facter::Util::IP.stubs(:get_interfaces).returns(["eth0"]) + Facter.fact(:kernel).stubs(:value).returns("Linux") + end + example_behavior_for "iface specific ifconfig output", "Fedora 18", "dead::21f:bcff:fe0d:5fb1", "eth0_net_tools_2.0.txt" + + example_behavior_for "iface specific ifconfig output", "net_tools 1.60", "dead::216:3eff:fe7d:ec7e", "eth0_net_tools_1.60.txt" + end +end diff --git a/spec/unit/ldom_spec.rb b/spec/unit/ldom_spec.rb index 27ec932278..f97d4a8765 100644 --- a/spec/unit/ldom_spec.rb +++ b/spec/unit/ldom_spec.rb @@ -1,3 +1,5 @@ +#!/usr/bin/env ruby + require 'spec_helper' def ldom_fixtures(filename) diff --git a/spec/unit/manufacturer_spec.rb b/spec/unit/manufacturer_spec.rb index f18bb9b0c3..cacf470f27 100644 --- a/spec/unit/manufacturer_spec.rb +++ b/spec/unit/manufacturer_spec.rb @@ -1,3 +1,5 @@ +#!/usr/bin/env ruby + require 'spec_helper' require 'facter' require 'facter/util/manufacturer' diff --git a/spec/unit/memory_spec.rb b/spec/unit/memory_spec.rb index 975e422e90..1771a53e20 100755 --- a/spec/unit/memory_spec.rb +++ b/spec/unit/memory_spec.rb @@ -515,8 +515,6 @@ end it "should use the memorysize fact for the memorytotal fact" do - Facter.stubs(:warnonce) - Facter.fact("memorysize").expects(:value).once.returns "yay" Facter::Util::Resolution.expects(:exec).never Facter.fact("memorytotal").value.should == "yay" diff --git a/spec/unit/uniqueid_spec.rb b/spec/unit/uniqueid_spec.rb index 3ac9bbf49d..e9870d39fd 100755 --- a/spec/unit/uniqueid_spec.rb +++ b/spec/unit/uniqueid_spec.rb @@ -1,3 +1,5 @@ +#!/usr/bin/env ruby + require 'spec_helper' require 'facter' diff --git a/spec/unit/util/directory_loader_spec.rb b/spec/unit/util/directory_loader_spec.rb index de8f3ca5aa..79ad2b2cd5 100644 --- a/spec/unit/util/directory_loader_spec.rb +++ b/spec/unit/util/directory_loader_spec.rb @@ -1,4 +1,7 @@ +#!/usr/bin/env ruby + require 'spec_helper' + require 'facter/util/directory_loader' describe Facter::Util::DirectoryLoader do diff --git a/spec/unit/util/ec2_spec.rb b/spec/unit/util/ec2_spec.rb index 2472c8712f..180c956f25 100755 --- a/spec/unit/util/ec2_spec.rb +++ b/spec/unit/util/ec2_spec.rb @@ -34,7 +34,7 @@ end end - describe "Facter::Util::EC2.with_metadata_server" do + describe "Facter::Util::EC2.with_metadata_server", :focus => true do before :each do Facter::Util::EC2.stubs(:read_uri).returns("latest") end diff --git a/spec/unit/util/ip/base_spec.rb b/spec/unit/util/ip/base_spec.rb deleted file mode 100644 index 9484c22fa9..0000000000 --- a/spec/unit/util/ip/base_spec.rb +++ /dev/null @@ -1,147 +0,0 @@ -# encoding: UTF-8 - -require 'spec_helper' -require 'facter/util/ip/base' - -describe Facter::Util::IP::Base do - subject { described_class } - - describe ".subclasses" do - let(:subclasses) { described_class.subclasses } - - it { expect(subclasses).to be_an Array } - - it "should be memoized" do - expect(subclasses).to be subclasses - end - - it "should list subclasses" do - subclass = Class.new described_class - - expect(subclasses).to include subclass - end - end - - describe ".to_s" do - let(:to_s) { described_class.to_s } - - it "should return the name of the class without nesting" do - expect(to_s).to eq 'Base' - end - end - - describe ".convert_netmask_from_hex?" do - let :convert_netmask_from_hex? do - described_class.convert_netmask_from_hex? - end - - it { expect(convert_netmask_from_hex?).to be true } - end - - describe ".bonding_master" do - let(:bonding_master) { described_class.bonding_master('eth0') } - - it { expect(bonding_master).to be_nil } - end - - describe ".interfaces" do - let(:interfaces) { described_class.interfaces } - - it "uses `ifconfig` to list the interfaces" do - described_class.expects(:ifconfig_path).returns('/bin/ifconfig') - described_class.expects(:exec).with("/bin/ifconfig -a 2> /dev/null") - expect(interfaces).to be_an Array - end - end - - describe ".value_for_interface_and_label(interface, label)" do - let :value_for_interface_and_label do - described_class.value_for_interface_and_label interface, label - end - - let(:interface) { 'eth0' } - - describe "there is no regex for the label" do - let(:label) { 'foobar' } - - before :each do - described_class.expects(:regex_for).with(label).returns(nil) - end - - it { expect(value_for_interface_and_label).to be_nil } - end - - describe "there is a regex for the label" do - let(:regex) { // } - let(:ifconfig_path) { '/usr/bin/ifconfig' } - let(:exec_cmd) { "#{ifconfig_path} #{interface} 2> /dev/null" } - let(:ifconfig_output) { "" } - - before :each do - described_class.expects(:ifconfig_path).returns(ifconfig_path) - described_class.expects(:regex_for).with(label).returns(regex) - described_class.expects(:exec).with(exec_cmd).returns(ifconfig_output) - regex.expects(:match).with(ifconfig_output).returns(match_data) - end - - describe "there is a match with the exec output and the regex" do - describe "the label is not 'netmask'" do - let(:match_data) { ['inet 127.0.0.1', '127.0.0.1'] } - let(:label) { 'ipaddress' } - - it { expect(value_for_interface_and_label).to eq '127.0.0.1' } - end - - describe "the label is 'netmask'" do - let(:label) { 'netmask' } - - describe "the netmask needs to be converted from hex" do - let(:match_data) { ['netmask ffffff00', 'ffffff00'] } - - before :each do - described_class.expects(:convert_netmask_from_hex?).returns(true) - end - - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } - end - - describe "the netmask does not need to be converted from hex" do - let(:match_data) { ['netmask 255.255.255.0', '255.255.255.0'] } - - before :each do - described_class.expects(:convert_netmask_from_hex?).returns(false) - end - - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } - end - end - end - - describe "there is not a match with the exec output and the regex" do - let(:match_data) { nil } - let(:label) { 'ipaddress' } - - it { expect(value_for_interface_and_label).to be_nil } - end - end - - describe ".network(interface)" do - let(:network) { described_class.network(interface) } - let(:interface) { 'e1000g0' } - - before :each do - described_class. - expects(:value_for_interface_and_label). - with(interface, 'ipaddress'). - returns('172.16.15.138') - - described_class. - expects(:value_for_interface_and_label). - with(interface, 'netmask'). - returns('255.255.255.0') - end - - it { expect(network).to eq "172.16.15.0" } - end - end -end diff --git a/spec/unit/util/ip/darwin_spec.rb b/spec/unit/util/ip/darwin_spec.rb deleted file mode 100644 index b9407f29af..0000000000 --- a/spec/unit/util/ip/darwin_spec.rb +++ /dev/null @@ -1,87 +0,0 @@ -# encoding: UTF-8 - -require 'spec_helper' -require 'facter/util/ip/darwin' - -describe Facter::Util::IP::Darwin do - describe ".to_s" do - let :to_s do - described_class.to_s - end - - it "should be 'Darwin'" do - expect(to_s).to eq 'Darwin' - end - end - - describe ".convert_netmask_from_hex?" do - let :convert_netmask_from_hex? do - described_class.convert_netmask_from_hex? - end - - it "should be true" do - expect(convert_netmask_from_hex?).to be true - end - end - - describe ".bonding_master" do - let :bonding_master do - described_class.bonding_master('eth0') - end - - it { expect(bonding_master).to be_nil } - end - - describe ".interfaces" do - let :interfaces do - described_class.interfaces - end - - let :ifconfig_output do - my_fixture_read("ifconfig_all_with_multiple_interfaces") - end - - before :each do - described_class.expects(:ifconfig_path).returns('/usr/bin/ifconfig') - described_class.expects(:exec).with(anything).returns(ifconfig_output) - end - - it "should return an array with two interfaces" do - expect(interfaces).to eq ["lo0", "en0"] - end - end - - describe ".value_for_interface_and_label(interface, label)" do - let :value_for_interface_and_label do - described_class.value_for_interface_and_label interface, label - end - - let(:interface) { 'en0' } - let(:ifconfig_output) { my_fixture_read "ifconfig_with_single_interface" } - let(:ifconfig_path) { "/usr/bin/ifconfig" } - let(:exec_cmd) { "#{ifconfig_path} #{interface} 2> /dev/null" } - - before :each do - described_class.expects(:ifconfig_path).returns(ifconfig_path) - described_class.expects(:exec).with(exec_cmd).returns(ifconfig_output) - end - - describe "macaddress" do - let(:label) { 'macaddress' } - - it { expect(value_for_interface_and_label).to eq '00:23:6c:99:60:2b' } - end - - describe "netmask" do - let(:label) { 'netmask' } - - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } - end - - describe "mtu" do - let(:label) { 'mtu' } - - it { expect(value_for_interface_and_label).to eq '1500' } - end - end -end diff --git a/spec/unit/util/ip/dragonfly_spec.rb b/spec/unit/util/ip/dragonfly_spec.rb deleted file mode 100644 index f2c670a209..0000000000 --- a/spec/unit/util/ip/dragonfly_spec.rb +++ /dev/null @@ -1,34 +0,0 @@ -# encoding: UTF-8 - -require 'spec_helper' -require 'facter/util/ip/dragonfly' - -describe Facter::Util::IP::Dragonfly do - describe ".to_s" do - let :to_s do - described_class.to_s - end - - it "should be 'Dragonfly'" do - expect(to_s).to eq 'Dragonfly' - end - end - - describe ".convert_netmask_from_hex?" do - let :convert_netmask_from_hex? do - described_class.convert_netmask_from_hex? - end - - it "should be true" do - expect(convert_netmask_from_hex?).to be true - end - end - - describe ".bonding_master" do - let(:bonding_master) do - described_class.bonding_master('eth0') - end - - it { expect(bonding_master).to be_nil } - end -end diff --git a/spec/unit/util/ip/free_bsd_spec.rb b/spec/unit/util/ip/free_bsd_spec.rb deleted file mode 100644 index d69989e492..0000000000 --- a/spec/unit/util/ip/free_bsd_spec.rb +++ /dev/null @@ -1,56 +0,0 @@ -# encoding: UTF-8 - -require 'spec_helper' -require 'facter/util/ip/free_bsd' - -describe Facter::Util::IP::FreeBSD do - describe ".to_s" do - let :to_s do - described_class.to_s - end - - it "should be 'FreeBSD'" do - expect(to_s).to eq 'FreeBSD' - end - end - - describe ".convert_netmask_from_hex?" do - let :convert_netmask_from_hex? do - described_class.convert_netmask_from_hex? - end - - it "should be true" do - expect(convert_netmask_from_hex?).to be true - end - end - - describe ".bonding_master" do - let :bonding_master do - described_class.bonding_master('eth0') - end - - it { expect(bonding_master).to be_nil } - end - - describe ".value_for_interface_and_label(interface, label)" do - let :value_for_interface_and_label do - described_class.value_for_interface_and_label interface, label - end - - let(:interface) { 'fxp0' } - let(:ifconfig_output) { my_fixture_read "6.0-STABLE_ifconfig_with_single_interface" } - let(:ifconfig_path) { "/usr/bin/ifconfig" } - let(:exec_cmd) { "#{ifconfig_path} #{interface} 2> /dev/null" } - - before :each do - described_class.expects(:ifconfig_path).returns(ifconfig_path) - described_class.expects(:exec).with(exec_cmd).returns(ifconfig_output) - end - - describe "macaddress" do - let(:label) { 'macaddress' } - - it { expect(value_for_interface_and_label).to eq '00:0e:0c:68:67:7c' } - end - end -end diff --git a/spec/unit/util/ip/gnu_k_free_bsd_spec.rb b/spec/unit/util/ip/gnu_k_free_bsd_spec.rb deleted file mode 100644 index 591571d6ec..0000000000 --- a/spec/unit/util/ip/gnu_k_free_bsd_spec.rb +++ /dev/null @@ -1,85 +0,0 @@ -# encoding: UTF-8 - -require 'spec_helper' -require 'facter/util/ip/gnu_k_free_bsd' - -describe Facter::Util::IP::GNUkFreeBSD do - before :each do - Facter.fact(:kernel).stubs(:value).returns('GNU/kFreeBSD') - end - - describe ".to_s" do - let :to_s do - described_class.to_s - end - - it "should be 'GNU/kFreeBSD'" do - expect(to_s).to eq 'GNU/kFreeBSD' - end - end - - describe ".convert_netmask_from_hex?" do - let :convert_netmask_from_hex? do - described_class.convert_netmask_from_hex? - end - - it "should be true" do - expect(convert_netmask_from_hex?).to be true - end - end - - describe ".bonding_master" do - let :bonding_master do - described_class.bonding_master('eth0') - end - - it { expect(bonding_master).to be_nil } - end - - describe ".interfaces" do - let :interfaces do - described_class.interfaces - end - - let :ifconfig_output do - my_fixture_read("ifconfig_all_with_multiple_interfaces") - end - - before :each do - described_class.expects(:ifconfig_path).returns('/usr/bin/ifconfig') - described_class.expects(:exec).with(anything).returns(ifconfig_output) - end - - it "should return an array with six interfaces" do - expect(interfaces).to eq %w[em0 em1 bge0 bge1 lo0 vlan0] - end - end - - describe ".value_for_interface_and_label(interface, label)" do - let :value_for_interface_and_label do - described_class.value_for_interface_and_label interface, label - end - - let(:interface) { 'em1' } - let(:ifconfig_output) { my_fixture_read "ifconfig_with_single_interface" } - let(:ifconfig_path) { "/usr/bin/ifconfig" } - let(:exec_cmd) { "#{ifconfig_path} #{interface} 2> /dev/null" } - - before :each do - described_class.expects(:ifconfig_path).returns(ifconfig_path) - described_class.expects(:exec).with(exec_cmd).returns(ifconfig_output) - end - - describe "macaddress" do - let(:label) { 'macaddress' } - - it { expect(value_for_interface_and_label).to eq '0:11:a:59:67:91' } - end - - describe "netmask" do - let(:label) { 'netmask' } - - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } - end - end -end diff --git a/spec/unit/util/ip/hpux_spec.rb b/spec/unit/util/ip/hpux_spec.rb deleted file mode 100644 index ec3f21bfe1..0000000000 --- a/spec/unit/util/ip/hpux_spec.rb +++ /dev/null @@ -1,536 +0,0 @@ -# encoding: UTF-8 - -require 'spec_helper' -require 'facter/util/ip/hpux' - -describe Facter::Util::IP::HPUX do - before :each do - Facter.fact(:kernel).stubs(:value).returns("HP-UX") - end - - describe ".to_s" do - let :to_s do - described_class.to_s - end - - it "should be 'HP-UX'" do - expect(to_s).to eq 'HP-UX' - end - end - - describe ".convert_netmask_from_hex?" do - let :convert_netmask_from_hex? do - described_class.convert_netmask_from_hex? - end - - it "should be true" do - expect(convert_netmask_from_hex?).to be true - end - end - - describe ".bonding_master" do - let :bonding_master do - described_class.bonding_master('eth0') - end - - it { expect(bonding_master).to be_nil } - end - - describe ".interfaces" do - let :interfaces do - described_class.interfaces - end - - before :each do - described_class.stubs(:exec).with(anything).returns(netstat_output) - end - - describe "version 11.11" do - let :netstat_output do - my_fixture_read("1111_netstat_in") - end - - it "should return an array of interfaces" do - expect(interfaces).to eq %w[lan1 lan0 lo0] - end - end - - describe "version 11.31" do - let :netstat_output do - my_fixture_read("1131_netstat_in") - end - - it "should return an array of interfaces" do - expect(interfaces).to eq %w[lan1 lan0 lo0] - end - end - end - - describe ".value_for_interface_and_label(interface, label)" do - let :value_for_interface_and_label do - described_class.value_for_interface_and_label interface, label - end - - describe "version 11.11" do - let(:ifconfig_output) { my_fixture_read("1111_ifconfig_#{interface}") } - let(:lanscan_output) { my_fixture_read("1111_lanscan") } - - before :each do - described_class.stubs(:exec).returns(ifconfig_output) - end - - describe "lan1" do - let(:interface) { 'lan1' } - - describe "ipaddress" do - let(:label) { 'ipaddress' } - - it { expect(value_for_interface_and_label).to eq '10.1.1.6' } - end - - describe "mtu" do - let(:label) { 'mtu' } - - it { pending "(#17808) MTU has not been implemented for HP-UX" } - end - - describe "netmask" do - let(:label) { 'netmask' } - - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } - end - - describe "macaddress" do - let(:label) { 'macaddress' } - - before :each do - described_class.expects(:lanscan).returns(lanscan_output) - end - - it { expect(value_for_interface_and_label).to eq "00:10:79:7B:5C:DE" } - end - end - - describe "lan0" do - let(:interface) { 'lan0' } - - describe "ipaddress" do - let(:label) { 'ipaddress' } - - it { expect(value_for_interface_and_label).to eq "192.168.3.10" } - end - - describe "mtu" do - let(:label) { 'mtu' } - - it { pending "(#17808) MTU has not been implemented for HP-UX" } - end - - describe "netmask" do - let(:label) { 'netmask' } - - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } - end - - describe "macaddress" do - let(:label) { 'macaddress' } - - before :each do - described_class.expects(:lanscan).returns(lanscan_output) - end - - it { expect(value_for_interface_and_label).to eq "00:30:7F:0C:79:DC" } - end - end - - describe "lo0" do - let(:interface) { 'lo0' } - - describe "ipaddress" do - let(:label) { 'ipaddress' } - - it { expect(value_for_interface_and_label).to eq "127.0.0.1" } - end - - describe "mtu" do - let(:label) { 'mtu' } - - it { pending "(#17808) MTU has not been implemented for HP-UX" } - end - - describe "netmask" do - let(:label) { 'netmask' } - - it { expect(value_for_interface_and_label).to eq '255.0.0.0' } - end - - describe "macaddress" do - let(:label) { 'macaddress' } - - it { expect(value_for_interface_and_label).to be_nil } - end - end - end - - describe "version 11.31" do - describe "when interfaces are normal" do - let(:ifconfig_output) { my_fixture_read("1131_ifconfig_#{interface}") } - let(:lanscan_output) { my_fixture_read("1131_lanscan") } - - before :each do - described_class.stubs(:exec).returns(ifconfig_output) - end - - describe "lan1" do - let(:interface) { 'lan1' } - - describe "ipaddress" do - let(:label) { 'ipaddress' } - - it { expect(value_for_interface_and_label).to eq '10.1.54.36' } - end - - describe "netmask" do - let(:label) { 'netmask' } - - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } - end - - describe "macaddress" do - let(:label) { 'macaddress' } - let(:macaddress) { '00:17:FD:2D:2A:57' } - - before :each do - described_class.expects(:lanscan).returns(lanscan_output) - end - - it { expect(value_for_interface_and_label).to eq macaddress } - end - - describe "mtu" do - let(:label) { 'mtu' } - - it { pending "(#17808) MTU has not been implemented for HP-UX" } - end - end - - describe "lan0" do - let(:interface) { 'lan0' } - - describe "ipaddress" do - let(:label) { 'ipaddress' } - - it { expect(value_for_interface_and_label).to eq '192.168.30.152' } - end - - describe "netmask" do - let(:label) { 'netmask' } - - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } - end - - describe "macaddress" do - let(:label) { 'macaddress' } - let(:macaddress) { '00:12:31:7D:62:09' } - - before :each do - described_class.expects(:lanscan).returns(lanscan_output) - end - - it { expect(value_for_interface_and_label).to eq macaddress } - end - - describe "mtu" do - let(:label) { 'mtu' } - - it { pending "(#17808) MTU has not been implemented for HP-UX" } - end - end - - describe "lo0" do - let(:interface) { 'lo0' } - - describe "ipaddress" do - let(:label) { 'ipaddress' } - - it { expect(value_for_interface_and_label).to eq '127.0.0.1' } - end - - describe "netmask" do - let(:label) { 'netmask' } - - it { expect(value_for_interface_and_label).to eq '255.0.0.0' } - end - - describe "macaddress" do - let(:label) { 'macaddress' } - - before :each do - described_class.expects(:lanscan).returns(lanscan_output) - end - - it { expect(value_for_interface_and_label).to be_nil } - end - - describe "mtu" do - let(:label) { 'mtu' } - - it { pending "(#17808) MTU has not been implemented for HP-UX" } - end - end - end - - describe "when an interface has an asterisk appended" do - let(:lanscan_output) { my_fixture_read("1131_asterisk_lanscan") } - - let :ifconfig_output do - my_fixture_read "1131_asterisk_ifconfig_#{interface}" - end - - before :each do - described_class.stubs(:exec).returns(ifconfig_output) - end - - describe "lan1" do - let(:interface) { 'lan1' } - - describe "ipaddress" do - let(:label) { 'ipaddress' } - - it { expect(value_for_interface_and_label).to eq '10.10.0.5' } - end - - describe "netmask" do - let(:label) { 'netmask' } - - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } - end - - describe "macaddress" do - let(:label) { 'macaddress' } - let(:macaddress) { '00:10:79:7B:BE:46' } - - before :each do - described_class.expects(:lanscan).returns(lanscan_output) - end - - it { expect(value_for_interface_and_label).to eq macaddress } - end - - describe "mtu" do - let(:label) { 'mtu' } - - it { pending "(#17808) MTU has not been implemented for HP-UX" } - end - end - - describe "lan0" do - let(:interface) { 'lan0' } - - describe "ipaddress" do - let(:label) { 'ipaddress' } - - it { expect(value_for_interface_and_label).to eq '192.168.3.9' } - end - - describe "netmask" do - let(:label) { 'netmask' } - - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } - end - - describe "macaddress" do - let(:label) { 'macaddress' } - let(:macaddress) { '00:30:5D:06:26:B2' } - - before :each do - described_class.expects(:lanscan).returns(lanscan_output) - end - - it { expect(value_for_interface_and_label).to eq macaddress } - end - - describe "mtu" do - let(:label) { 'mtu' } - - it { pending "(#17808) MTU has not been implemented for HP-UX" } - end - end - - describe "lo0" do - let(:interface) { 'lo0' } - - describe "ipaddress" do - let(:label) { 'ipaddress' } - - it { expect(value_for_interface_and_label).to eq '127.0.0.1' } - end - - describe "netmask" do - let(:label) { 'netmask' } - - it { expect(value_for_interface_and_label).to eq '255.0.0.0' } - end - - describe "macaddress" do - let(:label) { 'macaddress' } - - before :each do - described_class.expects(:lanscan).returns(lanscan_output) - end - - it { expect(value_for_interface_and_label).to be_nil } - end - - describe "mtu" do - let(:label) { 'mtu' } - - it { pending "(#17808) MTU has not been implemented for HP-UX" } - end - end - end - - describe "when an interface is bonded and has one virtual interface" do - let(:lanscan_output) { my_fixture_read "1131_nic_bonding_lanscan" } - - let :ifconfig_output do - my_fixture_read "1131_nic_bonding_ifconfig_#{interface.sub(':', '_')}" - end - - before :each do - described_class.stubs(:exec).returns(ifconfig_output) - end - - describe "lan1" do - let(:interface) { 'lan1' } - - describe "ipaddress" do - let(:label) { 'ipaddress' } - - it { expect(value_for_interface_and_label).to eq '192.168.30.32' } - end - - describe "netmask" do - let(:label) { 'netmask' } - - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } - end - - describe "macaddress" do - let(:label) { 'macaddress' } - let(:macaddress) { '00:12:81:9E:48:DE' } - - before :each do - described_class.expects(:lanscan).returns(lanscan_output) - end - - it { expect(value_for_interface_and_label).to eq macaddress } - end - - describe "mtu" do - let(:label) { 'mtu' } - - it { pending "(#17808) MTU has not been implemented for HP-UX" } - end - end - - describe "lo0" do - let(:interface) { 'lo0' } - - describe "ipaddress" do - let(:label) { 'ipaddress' } - - it { expect(value_for_interface_and_label).to eq '127.0.0.1' } - end - - describe "netmask" do - let(:label) { 'netmask' } - - it { expect(value_for_interface_and_label).to eq '255.0.0.0' } - end - - describe "macaddress" do - let(:label) { 'macaddress' } - - before :each do - described_class.expects(:lanscan).returns(lanscan_output) - end - - it { expect(value_for_interface_and_label).to be_nil } - end - - describe "mtu" do - let(:label) { 'mtu' } - - it { pending "(#17808) MTU has not been implemented for HP-UX" } - end - end - - describe "lan4" do - let(:interface) { 'lan4' } - - describe "ipaddress" do - let(:label) { 'ipaddress' } - - it { expect(value_for_interface_and_label).to eq '192.168.32.75' } - end - - describe "netmask" do - let(:label) { 'netmask' } - - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } - end - - describe "macaddress" do - let(:label) { 'macaddress' } - let(:macaddress) { '00:12:81:9E:4A:7E' } - - before :each do - described_class.expects(:lanscan).returns(lanscan_output) - end - - it { expect(value_for_interface_and_label).to eq macaddress } - end - - describe "mtu" do - let(:label) { 'mtu' } - - it { pending "(#17808) MTU has not been implemented for HP-UX" } - end - end - - describe "lan4:1" do - let(:interface) { 'lan4:1' } - - describe "ipaddress" do - let(:label) { 'ipaddress' } - - it { expect(value_for_interface_and_label).to eq '192.168.1.197' } - end - - describe "netmask" do - let(:label) { 'netmask' } - - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } - end - - describe "macaddress" do - let(:label) { 'macaddress' } - - before :each do - described_class.expects(:lanscan).returns(lanscan_output) - end - - it { expect(value_for_interface_and_label).to be_nil } - end - - describe "mtu" do - let(:label) { 'mtu' } - - it { pending "(#17808) MTU has not been implemented for HP-UX" } - end - end - end - end - end -end diff --git a/spec/unit/util/ip/linux_spec.rb b/spec/unit/util/ip/linux_spec.rb deleted file mode 100644 index 85383115e7..0000000000 --- a/spec/unit/util/ip/linux_spec.rb +++ /dev/null @@ -1,158 +0,0 @@ -# encoding: UTF-8 - -require 'spec_helper' -require 'facter/util/ip/linux' - -describe Facter::Util::IP::Linux do - before :each do - Facter.fact(:kernel).stubs(:value).returns("Linux") - end - - describe ".to_s" do - let :to_s do - described_class.to_s - end - - it "should be 'Linux'" do - expect(to_s).to eq 'Linux' - end - end - - describe ".convert_netmask_from_hex?" do - let :convert_netmask_from_hex? do - described_class.convert_netmask_from_hex? - end - - it "should be true" do - expect(convert_netmask_from_hex?).to be false - end - end - - describe ".bonding_master(interface)" do - let :bonding_master do - described_class.bonding_master(interface) - end - - describe "on interface aliases" do - let :interface do - "eth0:1" - end - - it { expect(bonding_master).to be_nil } - end - end - - describe ".interfaces" do - let :interfaces do - described_class.interfaces - end - - describe "without sysfs" do - let :ifconfig_output do - my_fixture_read("ifconfig_all_with_single_interface") - end - - before :each do - File.expects(:exist?).with('/sys/class/net').returns(false) - described_class.expects(:ifconfig_path).returns('/usr/bin/ifconfig') - described_class.expects(:exec).with(anything).returns(ifconfig_output) - end - - it "should return an array with a single interface and the loopback" do - expect(interfaces).to eq ["eth0", "lo"] - end - end - - describe "with sysfs" do - let :sysfs do - %w[/sys/class/net/eth0 /sys/class/net/lo] - end - - before :each do - File.expects(:exist?).with('/sys/class/net').returns(true) - Dir.expects(:glob).with('/sys/class/net/*').returns(sysfs) - end - - it "should return an array with a single interface and the loopback" do - expect(interfaces).to eq ["eth0", "lo"] - end - end - end - - describe "value_for_interface_and_label(interface, label)" do - let :value_for_interface_and_label do - described_class.value_for_interface_and_label interface, label - end - - describe "infiniband interface" do - let(:interface) { 'ib0' } - - let :ifconfig_output do - my_fixture_read "ifconfig_with_single_interface_ib0" - end - - describe "macaddress" do - let(:label) { 'macaddress' } - - before :each do - described_class.expects(:infiniband_macaddress).returns('bar') - end - - it { expect(value_for_interface_and_label).to eq 'bar' } - end - end - - describe "normal interface" do - let(:ifconfig_path) { '/usr/bin/ifconfig' } - let(:exec_cmd) { "#{ifconfig_path} #{interface} 2> /dev/null" } - - let(:ifconfig_output) do - my_fixture_read "ifconfig_single_interface_#{interface}" - end - - before :each do - described_class.expects(:ifconfig_path).returns(ifconfig_path) - described_class.expects(:exec).with(exec_cmd).returns(ifconfig_output) - end - - describe "eth0" do - let(:interface) { 'eth0' } - - describe "mtu" do - let(:label) { 'mtu' } - - it { expect(value_for_interface_and_label).to eq '1500' } - end - end - - describe "lo" do - let(:interface) { 'lo' } - - describe "mtu" do - let(:label) { 'mtu' } - - it { expect(value_for_interface_and_label).to eq '16436' } - end - end - end - - describe "bonded interface" do - let(:interface) { 'eth0' } - let(:bond) { 'bond0' } - let(:proc_net_path) { '/proc/net/bonding/bond0' } - - before :each do - proc_net = my_fixture_read "2_6_35_proc_net_bonding_#{bond}" - described_class.expects(:bonding_master).with(interface).returns(bond) - File.expects(:exists?).with(proc_net_path).returns(true) - File.expects(:read).with(proc_net_path).returns(proc_net) - end - - describe "macaddress" do - let(:label) { 'macaddress' } - - it { expect(value_for_interface_and_label).to eq "00:11:22:33:44:55" } - end - end - end -end diff --git a/spec/unit/util/ip/net_bsd_spec.rb b/spec/unit/util/ip/net_bsd_spec.rb deleted file mode 100644 index 489e82b2b4..0000000000 --- a/spec/unit/util/ip/net_bsd_spec.rb +++ /dev/null @@ -1,34 +0,0 @@ -# encoding: UTF-8 - -require 'spec_helper' -require 'facter/util/ip/net_bsd' - -describe Facter::Util::IP::NetBSD do - describe ".to_s" do - let :to_s do - described_class.to_s - end - - it "should be 'NetBSD'" do - expect(to_s).to eq 'NetBSD' - end - end - - describe ".convert_netmask_from_hex?" do - let :convert_netmask_from_hex? do - described_class.convert_netmask_from_hex? - end - - it "should be true" do - expect(convert_netmask_from_hex?).to be true - end - end - - describe ".bonding_master" do - let :bonding_master do - described_class.bonding_master('eth0') - end - - it { expect(bonding_master).to be_nil } - end -end diff --git a/spec/unit/util/ip/open_bsd_spec.rb b/spec/unit/util/ip/open_bsd_spec.rb deleted file mode 100644 index 0a9508dc60..0000000000 --- a/spec/unit/util/ip/open_bsd_spec.rb +++ /dev/null @@ -1,34 +0,0 @@ -# encoding: UTF-8 - -require 'spec_helper' -require 'facter/util/ip/open_bsd' - -describe Facter::Util::IP::OpenBSD do - describe ".to_s" do - let :to_s do - described_class.to_s - end - - it "should be 'OpenBSD'" do - expect(to_s).to eq 'OpenBSD' - end - end - - describe ".convert_netmask_from_hex?" do - let :convert_netmask_from_hex? do - described_class.convert_netmask_from_hex? - end - - it "should be true" do - expect(convert_netmask_from_hex?).to be true - end - end - - describe ".bonding_master" do - let :bonding_master do - described_class.bonding_master('eth0') - end - - it { expect(bonding_master).to be_nil } - end -end diff --git a/spec/unit/util/ip/sun_os_spec.rb b/spec/unit/util/ip/sun_os_spec.rb deleted file mode 100644 index 23e366612b..0000000000 --- a/spec/unit/util/ip/sun_os_spec.rb +++ /dev/null @@ -1,91 +0,0 @@ -# encoding: UTF-8 - -require 'spec_helper' -require 'facter/util/ip/sun_os' - -describe Facter::Util::IP::SunOS do - before :each do - Facter.fact(:kernel).stubs(:value).with('SunOS') - end - - describe ".to_s" do - let :to_s do - described_class.to_s - end - - it "should be 'SunOS'" do - expect(to_s).to eq 'SunOS' - end - end - - describe ".convert_netmask_from_hex?" do - let :convert_netmask_from_hex? do - described_class.convert_netmask_from_hex? - end - - it "should be true" do - expect(convert_netmask_from_hex?).to be true - end - end - - describe ".bonding_master" do - let :bonding_master do - described_class.bonding_master('eth0') - end - - it { expect(bonding_master).to be_nil } - end - - describe ".interfaces" do - let :interfaces do - described_class.interfaces - end - - let :ifconfig_output do - my_fixture_read("ifconfig_all_with_multiple_interfaces") - end - - before :each do - described_class.expects(:ifconfig_path).returns('/usr/bin/ifconfig') - described_class.expects(:exec).with(anything).returns(ifconfig_output) - end - - it "should return an array with two interfaces" do - expect(interfaces).to eq ["lo0", "e1000g0"] - end - end - - describe ".value_for_interface_and_label(interface, label)" do - let :value_for_interface_and_label do - described_class.value_for_interface_and_label interface, label - end - - let(:interface) { 'e1000g0' } - let(:ifconfig_output) { my_fixture_read "ifconfig_single_interface" } - let(:ifconfig_path) { "/usr/bin/ifconfig" } - let(:exec_cmd) { "#{ifconfig_path} #{interface}" } - - before :each do - described_class.expects(:ifconfig_path).returns(ifconfig_path) - described_class.expects(:exec).with(exec_cmd).returns(ifconfig_output) - end - - describe "ipaddress" do - let(:label) { "ipaddress" } - - it { expect(value_for_interface_and_label).to eq "172.16.15.138" } - end - - describe "netmask" do - let(:label) { "netmask" } - - it { expect(value_for_interface_and_label).to eq "255.255.255.0" } - end - - describe "mtu" do - let(:label) { "mtu" } - - it { expect(value_for_interface_and_label).to eq '1500' } - end - end -end diff --git a/spec/unit/util/ip/windows_spec.rb b/spec/unit/util/ip/windows_spec.rb deleted file mode 100644 index 09fc3fb68c..0000000000 --- a/spec/unit/util/ip/windows_spec.rb +++ /dev/null @@ -1,94 +0,0 @@ -# encoding: UTF-8 - -require 'spec_helper' -require 'facter/util/ip/windows' - -describe Facter::Util::IP::Windows do - before :each do - Facter.fact(:kernel).stubs(:value).returns('windows') - end - - describe ".to_s" do - let(:to_s) { described_class.to_s } - - it { expect(to_s).to eq 'windows' } - end - - describe ".convert_netmask_from_hex?" do - let :convert_netmask_from_hex? do - described_class.convert_netmask_from_hex? - end - - it { expect(convert_netmask_from_hex?).to be false } - end - - describe ".bonding_master" do - let(:bonding_master) { described_class.bonding_master('eth0') } - - it { expect(bonding_master).to be_nil } - end - - describe ".interfaces" do - let(:interfaces) { described_class.interfaces } - - let(:netsh_output) { my_fixture_read "netsh_all_interfaces" } - - let :expected_interfaces do - [ - "Loopback Pseudo-Interface 1", - "Local Area Connection", - "Teredo Tunneling Pseudo-Interface" - ] - end - - before :each do - described_class.expects(:exec).with(anything).returns(netsh_output) - end - - it "should return an array of only connected interfaces" do - expect(interfaces).to eq expected_interfaces - end - end - - describe "value_for_interface_and_label(interface, label)" do - let(:interface) { 'Local Area Connection' } - let(:netsh_output) { my_fixture_read("netsh_with_single_interface") } - - let :value_for_interface_and_label do - described_class.value_for_interface_and_label interface, label - end - - let(:exec_cmd) do - "#{described_class::NETSH} interface ip show address \"#{interface}\"" - end - - before :each do - described_class.expects(:exec).with(exec_cmd).returns(netsh_output) - end - - describe "ipaddress" do - let(:label) { 'ipaddress' } - - it { expect(value_for_interface_and_label).to eq '172.16.138.216' } - end - - describe "netmask" do - let(:label) { 'netmask' } - - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } - end - - describe "ipaddress6" do - let(:interface) { 'Teredo Tunneling Pseudo-Interface' } - let(:label) { 'ipaddress6' } - let(:expected_ip) { '2001:0:4137:9e76:2087:77a:53ef:7527' } - let(:netsh_output) { my_fixture_read("netsh_with_single_interface6") } - - let(:exec_cmd) do - "#{described_class::NETSH} interface ipv6 show address \"#{interface}\"" - end - - it { expect(value_for_interface_and_label).to eq expected_ip } - end - end -end diff --git a/spec/unit/util/ip_spec.rb b/spec/unit/util/ip_spec.rb index c44c0613c1..3d95ee5cd7 100755 --- a/spec/unit/util/ip_spec.rb +++ b/spec/unit/util/ip_spec.rb @@ -4,187 +4,480 @@ require 'facter/util/ip' describe Facter::Util::IP do - let :interfaces_hash do - { - :eth0 => { - :ipaddress => 1, - :ipaddress6 => 2, - :macaddress => 3, - :netmask => 4, - :mtu => 5, - :network => 6 - }, - - :lo0 => { - :ipaddress => 7, - :ipaddress6 => 8, - :macaddress => 9, - :netmask => 10, - :mtu => 11, - :network => 12 - } - } - end - - let :interfaces_hash2 do - { - :en1 => { - :ipaddress => 13, - :ipaddress6 => 14, - :macaddress => 15, - :netmask => 16, - :mtu => 17, - :network => 18 - }, - - :lo => { - :ipaddress => 19, - :ipaddress6 => 20, - :macaddress => 21, - :netmask => 22, - :mtu => 23, - :network => 24 - } - } - end - - %w{ - FreeBSD Linux NetBSD OpenBSD SunOS Darwin HP-UX GNU/kFreeBSD windows - Dragonfly - }.each do |platform| + include FacterSpec::ConfigHelper + + before :each do + given_a_configuration_of(:is_windows => false) + end + + [:freebsd, :linux, :netbsd, :openbsd, :sunos, :darwin, :"hp-ux", :"gnu/kfreebsd", :windows].each do |platform| it "should be supported on #{platform}" do - Facter::Util::IP.new.supported_platforms.should include platform + given_a_configuration_of(:is_windows => platform == :windows) + Facter::Util::IP.supported_platforms.should be_include(platform) end end - describe "exec_ifconfig" do - it "executes /sbin/ifconfig when get_ifconfig() returns /sbin/ifconfig" do - Facter::Util::IP.expects(:get_ifconfig).returns("/sbin/ifconfig") - Facter::Util::Resolution.expects(:exec).with("/sbin/ifconfig") - Facter::Util::IP.exec_ifconfig - end + it "should return a list of interfaces" do + Facter::Util::IP.should respond_to(:get_interfaces) + end - it "executes `ifconfig -a` when given ['-a']" do - Facter::Util::IP.stubs(:get_ifconfig).returns("/sbin/ifconfig") + it "should return an empty list of interfaces on an unknown kernel" do + File.stubs(:exist?).with('/sys/class/net').returns(false) + Facter.stubs(:value).returns("UnknownKernel") + Facter::Util::IP.get_interfaces().should == [] + end - Facter::Util::Resolution.expects(:exec).with("/sbin/ifconfig -a") - Facter::Util::IP.exec_ifconfig(["-a"]) + it "should return a list with a single interface and the loopback interface on Linux with a single interface without sysfs" do + File.stubs(:exist?).with('/sys/class/net').returns(false) + linux_ifconfig = my_fixture_read("linux_ifconfig_all_with_single_interface") + Facter::Util::IP.stubs(:get_all_interface_output).returns(linux_ifconfig) + Facter::Util::IP.get_interfaces().should =~ ["eth0", "lo"] + end + + it "should return a list two interfaces on Darwin with two interfaces" do + File.stubs(:exist?).with('/sys/class/net').returns(false) + darwin_ifconfig = my_fixture_read("darwin_ifconfig_all_with_multiple_interfaces") + Facter::Util::IP.stubs(:get_all_interface_output).returns(darwin_ifconfig) + Facter::Util::IP.get_interfaces().should == ["lo0", "en0"] + end + + it "should return a list two interfaces on Solaris with two interfaces multiply reporting" do + File.stubs(:exist?).with('/sys/class/net').returns(false) + solaris_ifconfig = my_fixture_read("solaris_ifconfig_all_with_multiple_interfaces") + Facter::Util::IP.stubs(:get_all_interface_output).returns(solaris_ifconfig) + Facter::Util::IP.get_interfaces().should == ["lo0", "e1000g0"] + end + + it "should return a list of six interfaces on a GNU/kFreeBSD with six interfaces" do + File.stubs(:exist?).with('/sys/class/net').returns(false) + kfreebsd_ifconfig = my_fixture_read("debian_kfreebsd_ifconfig") + Facter::Util::IP.stubs(:get_all_interface_output).returns(kfreebsd_ifconfig) + Facter::Util::IP.get_interfaces().should == ["em0", "em1", "bge0", "bge1", "lo0", "vlan0"] + end + + it "should return a list of only connected interfaces on Windows" do + File.stubs(:exist?).with('/sys/class/net').returns(false) + Facter.fact(:kernel).stubs(:value).returns("windows") + windows_netsh = my_fixture_read("windows_netsh_all_interfaces") + Facter::Util::IP.stubs(:get_all_interface_output).returns(windows_netsh) + Facter::Util::IP.get_interfaces().should == ["Loopback Pseudo-Interface 1", "Local Area Connection", "Teredo Tunneling Pseudo-Interface"] + end + + it "should return a value for a specific interface" do + Facter::Util::IP.should respond_to(:get_interface_value) + end + + it "should not return interface information for unsupported platforms" do + Facter.stubs(:value).with(:kernel).returns("bleah") + Facter::Util::IP.get_interface_value("e1000g0", "netmask").should == [] + end + + it "should return ipaddress information for Solaris" do + solaris_ifconfig_interface = my_fixture_read("solaris_ifconfig_single_interface") + + Facter::Util::IP.expects(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("SunOS") + + Facter::Util::IP.get_interface_value("e1000g0", "ipaddress").should == "172.16.15.138" + end + + it "should return netmask information for Solaris" do + solaris_ifconfig_interface = my_fixture_read("solaris_ifconfig_single_interface") + + Facter::Util::IP.expects(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("SunOS") + + Facter::Util::IP.get_interface_value("e1000g0", "netmask").should == "255.255.255.0" + end + + it "should return calculated network information for Solaris" do + solaris_ifconfig_interface = my_fixture_read("solaris_ifconfig_single_interface") + + Facter::Util::IP.stubs(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("SunOS") + + Facter::Util::IP.get_network_value("e1000g0").should == "172.16.15.0" + end + + it "should return macaddress with leading zeros stripped off for GNU/kFreeBSD" do + kfreebsd_ifconfig = my_fixture_read("debian_kfreebsd_ifconfig") + + Facter::Util::IP.expects(:get_single_interface_output).with("em0").returns(kfreebsd_ifconfig) + Facter.stubs(:value).with(:kernel).returns("GNU/kFreeBSD") + + Facter::Util::IP.get_interface_value("em0", "macaddress").should == "0:11:a:59:67:90" + end + + it "should return interface information for FreeBSD supported via an alias" do + ifconfig_interface = my_fixture_read("6.0-STABLE_FreeBSD_ifconfig") + + Facter::Util::IP.expects(:get_single_interface_output).with("fxp0").returns(ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("FreeBSD") + + Facter::Util::IP.get_interface_value("fxp0", "macaddress").should == "00:0e:0c:68:67:7c" + end + + it "should return macaddress information for OS X" do + ifconfig_interface = my_fixture_read("Mac_OS_X_10.5.5_ifconfig") + + Facter::Util::IP.expects(:get_single_interface_output).with("en1").returns(ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("Darwin") + + Facter::Util::IP.get_interface_value("en1", "macaddress").should == "00:1b:63:ae:02:66" + end + + it "should return all interfaces correctly on OS X" do + File.stubs(:exist?).with('/sys/class/net').returns(false) + ifconfig_interface = my_fixture_read("Mac_OS_X_10.5.5_ifconfig") + + Facter::Util::IP.expects(:get_all_interface_output).returns(ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("Darwin") + + Facter::Util::IP.get_interfaces().should == ["lo0", "gif0", "stf0", "en0", "fw0", "en1", "vmnet8", "vmnet1"] + end + + it "should return a human readable netmask on Solaris" do + solaris_ifconfig_interface = my_fixture_read("solaris_ifconfig_single_interface") + + Facter::Util::IP.expects(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("SunOS") + + Facter::Util::IP.get_interface_value("e1000g0", "netmask").should == "255.255.255.0" + end + + it "should return a human readable netmask on Darwin" do + darwin_ifconfig_interface = my_fixture_read("darwin_ifconfig_single_interface") + + Facter::Util::IP.expects(:get_single_interface_output).with("en1").returns(darwin_ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("Darwin") + + Facter::Util::IP.get_interface_value("en1", "netmask").should == "255.255.255.0" + end + + it "should return a human readable netmask on GNU/kFreeBSD" do + kfreebsd_ifconfig = my_fixture_read("debian_kfreebsd_ifconfig") + + Facter::Util::IP.expects(:get_single_interface_output).with("em1").returns(kfreebsd_ifconfig) + Facter.stubs(:value).with(:kernel).returns("GNU/kFreeBSD") + + Facter::Util::IP.get_interface_value("em1", "netmask").should == "255.255.255.0" + end + + it "should return correct macaddress information for infiniband on Linux" do + correct_ifconfig_interface = my_fixture_read("linux_get_single_interface_ib0") + + Facter::Util::IP.expects(:get_single_interface_output).with("ib0").returns(correct_ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("Linux") + + Facter::Util::IP.get_interface_value("ib0", "macaddress").should == "80:00:00:4a:fe:80:00:00:00:00:00:00:00:02:c9:03:00:43:27:21" + end + + it "should replace the incorrect macaddress with the correct macaddress in ifconfig for infiniband on Linux" do + ifconfig_interface = my_fixture_read("linux_ifconfig_ib0") + correct_ifconfig_interface = my_fixture_read("linux_get_single_interface_ib0") + + Facter::Util::IP.expects(:get_infiniband_macaddress).with("ib0").returns("80:00:00:4a:fe:80:00:00:00:00:00:00:00:02:c9:03:00:43:27:21") + Facter::Util::IP.expects(:ifconfig_interface).with("ib0").returns(ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("Linux") + + Facter::Util::IP.get_single_interface_output("ib0").should == correct_ifconfig_interface + end + + it "should return fake macaddress information for infiniband on Linux when neither sysfs or /sbin/ip are available" do + ifconfig_interface = my_fixture_read("linux_ifconfig_ib0") + + File.expects(:exists?).with("/sys/class/net/ib0/address").returns(false) + File.expects(:exists?).with("/sbin/ip").returns(false) + Facter::Util::IP.expects(:ifconfig_interface).with("ib0").returns(ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("Linux") + + Facter::Util::IP.get_interface_value("ib0", "macaddress").should == "FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF" + end + + it "should not get bonding master on interface aliases" do + Facter.stubs(:value).with(:kernel).returns("Linux") + + Facter::Util::IP.get_bonding_master("eth0:1").should be_nil + end + + [:freebsd, :netbsd, :openbsd, :sunos, :darwin, :"hp-ux"].each do |platform| + it "should require conversion from hex on #{platform}" do + Facter::Util::IP.convert_from_hex?(platform).should == true end - it "executes `ifconfig -a -e -i -j` when given ['-a', '-e', '-i', '-j]" do - Facter::Util::IP.stubs(:get_ifconfig).returns("/sbin/ifconfig") + end - Facter::Util::Resolution.expects(:exec).with("/sbin/ifconfig -a -e -i -j") - Facter::Util::IP.exec_ifconfig(["-a","-e","-i","-j"]) + [:windows].each do |platform| + it "should not require conversion from hex on #{platform}" do + Facter::Util::IP.convert_from_hex?(platform).should be_false end end - describe ".add_interface_facts" do - before :each do - given_initial_interfaces_facts - described_class.add_interface_facts + it "should return an arp address on Linux" do + Facter.stubs(:value).with(:kernel).returns("Linux") + + Facter::Util::IP.expects(:get_arp_value).with("eth0").returns("00:00:0c:9f:f0:04") + Facter::Util::IP.get_arp_value("eth0").should == "00:00:0c:9f:f0:04" + end + + it "should return mtu information on Linux" do + linux_ifconfig = my_fixture_read("linux_ifconfig_all_with_single_interface") + Facter::Util::IP.stubs(:get_all_interface_output).returns(linux_ifconfig) + Facter::Util::IP.stubs(:get_single_interface_output).with("eth0"). + returns(my_fixture_read("linux_get_single_interface_eth0")) + Facter::Util::IP.stubs(:get_single_interface_output).with("lo"). + returns(my_fixture_read("linux_get_single_interface_lo")) + Facter.stubs(:value).with(:kernel).returns("Linux") + + Facter::Util::IP.get_interface_value("eth0", "mtu").should == "1500" + Facter::Util::IP.get_interface_value("lo", "mtu").should == "16436" + end + + it "should return mtu information on Darwin" do + darwin_ifconfig_interface = my_fixture_read("darwin_ifconfig_single_interface") + + Facter::Util::IP.expects(:get_single_interface_output).with("en1").returns(darwin_ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("Darwin") + + Facter::Util::IP.get_interface_value("en1", "mtu").should == "1500" + end + + it "should return mtu information for Solaris" do + solaris_ifconfig_interface = my_fixture_read("solaris_ifconfig_single_interface") + + Facter::Util::IP.expects(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) + Facter.stubs(:value).with(:kernel).returns("SunOS") + + Facter::Util::IP.get_interface_value("e1000g0", "mtu").should == "1500" + end + + # (#17487) - tests for HP-UX. + # some fake data for testing robustness of regexps. + def self.fake_netstat_in_examples + examples = [] + examples << ["Header row\na line with none in it\na line without\nanother line without\n", + "a line without\nanother line without\n"] + examples << ["Header row\na line without\na line with none in it\nanother line with none\nanother line without\n", + "a line without\nanother line without\n"] + examples << ["Header row\na line with * asterisks *\na line with none in it\nanother line without\n", + "a line with asterisks \nanother line without\n"] + examples << ["a line with none none none in it\na line with none in it\na line without\nanother line without\n", + "another line without\n"] + examples + end + + fake_netstat_in_examples.each_with_index do |example, i| + input, expected_output = example + it "should pass regexp test on fake netstat input example #{i}" do + Facter.stubs(:value).with(:kernel).returns("HP-UX") + Facter::Util::IP.stubs(:hpux_netstat_in).returns(input) + Facter::Util::IP.get_all_interface_output().should == expected_output end + end + + # and some real data for exhaustive tests. + def self.hpux_examples + examples = [] + examples << ["HP-UX 11.11", + ["lan1", "lan0", "lo0" ], + ["1500", "1500", "4136" ], + ["10.1.1.6", "192.168.3.10", "127.0.0.1"], + ["255.255.255.0", "255.255.255.0", "255.0.0.0"], + ["00:10:79:7B:5C:DE", "00:30:7F:0C:79:DC", nil ], + [my_fixture_read("hpux_1111_ifconfig_lan1"), + my_fixture_read("hpux_1111_ifconfig_lan0"), + my_fixture_read("hpux_1111_ifconfig_lo0")], + my_fixture_read("hpux_1111_netstat_in"), + my_fixture_read("hpux_1111_lanscan")] + + examples << ["HP-UX 11.31", + ["lan1", "lan0", "lo0" ], + ["1500", "1500", "4136" ], + ["10.1.54.36", "192.168.30.152", "127.0.0.1"], + ["255.255.255.0", "255.255.255.0", "255.0.0.0"], + ["00:17:FD:2D:2A:57", "00:12:31:7D:62:09", nil ], + [my_fixture_read("hpux_1131_ifconfig_lan1"), + my_fixture_read("hpux_1131_ifconfig_lan0"), + my_fixture_read("hpux_1131_ifconfig_lo0")], + my_fixture_read("hpux_1131_netstat_in"), + my_fixture_read("hpux_1131_lanscan")] + + examples << ["HP-UX 11.31 with an asterisk after a NIC that has an address", + ["lan1", "lan0", "lo0" ], + ["1500", "1500", "4136" ], + ["10.10.0.5", "192.168.3.9", "127.0.0.1"], + ["255.255.255.0", "255.255.255.0", "255.0.0.0"], + ["00:10:79:7B:BE:46", "00:30:5D:06:26:B2", nil ], + [my_fixture_read("hpux_1131_asterisk_ifconfig_lan1"), + my_fixture_read("hpux_1131_asterisk_ifconfig_lan0"), + my_fixture_read("hpux_1131_asterisk_ifconfig_lo0")], + my_fixture_read("hpux_1131_asterisk_netstat_in"), + my_fixture_read("hpux_1131_asterisk_lanscan")] - it "defines the 'interfaces' fact" do - expect(Facter.fact(:interfaces)).to be_a_kind_of Facter::Util::Fact + examples << ["HP-UX 11.31 with NIC bonding and one virtual NIC", + ["lan4:1", "lan1", "lo0", "lan4" ], + ["1500", "1500", "4136", "1500" ], + ["192.168.1.197", "192.168.30.32", "127.0.0.1", "192.168.32.75" ], + ["255.255.255.0", "255.255.255.0", "255.0.0.0", "255.255.255.0" ], + [nil, "00:12:81:9E:48:DE", nil, "00:12:81:9E:4A:7E"], + [my_fixture_read("hpux_1131_nic_bonding_ifconfig_lan4_1"), + my_fixture_read("hpux_1131_nic_bonding_ifconfig_lan1"), + my_fixture_read("hpux_1131_nic_bonding_ifconfig_lo0"), + my_fixture_read("hpux_1131_nic_bonding_ifconfig_lan4")], + my_fixture_read("hpux_1131_nic_bonding_netstat_in"), + my_fixture_read("hpux_1131_nic_bonding_lanscan")] + examples + end + + hpux_examples.each do |example| + description, array_of_expected_ifs, array_of_expected_mtus, + array_of_expected_ips, array_of_expected_netmasks, + array_of_expected_macs, array_of_ifconfig_fixtures, + netstat_in_fixture, lanscan_fixture = example + + it "should return a list three interfaces on #{description}" do + Facter.expects(:value).with(:kernel).at_least_once.returns("HP-UX") + File.expects(:exist?).with('/sys/class/net').returns(false) + Facter::Util::IP.expects(:hpux_netstat_in).returns(netstat_in_fixture) + Facter::Util::IP.get_interfaces.should == array_of_expected_ifs end - it "defines a fact for each attribute of an interface" do - interfaces_hash.keys.each do |interface| - described_class::INTERFACE_KEYS.each do |attr| - expect(Facter.fact("#{attr}_#{interface}")). - to be_a_kind_of Facter::Util::Fact - end + array_of_expected_ifs.each_with_index do |nic, i| + ifconfig_fixture = array_of_ifconfig_fixtures[i] + expected_mtu = array_of_expected_mtus[i] + expected_ip = array_of_expected_ips[i] + expected_netmask = array_of_expected_netmasks[i] + expected_mac = array_of_expected_macs[i] + + # (#17808) These tests fail because MTU facts haven't been implemented for HP-UX. + #it "should return MTU #{expected_mtu} on #{nic} for #{description} example" do + # Facter.expects(:value).with(:kernel).at_least_once.returns("HP-UX") + # Facter::Util::IP.expects(:hpux_netstat_in).returns(netstat_in_fixture) + # Facter::Util::IP.expects(:hpux_lanscan).returns(lanscan_fixture) + # Facter::Util::IP.expects(:hpux_ifconfig_interface).with(nic).returns(ifconfig_fixture) + # Facter::Util::IP.get_interface_value(nic, "mtu").should == expected_mtu + #end + + it "should return IP #{expected_ip} on #{nic} for #{description} example" do + Facter.expects(:value).with(:kernel).at_least_once.returns("HP-UX") + Facter::Util::IP.expects(:hpux_lanscan).returns(lanscan_fixture) + Facter::Util::IP.expects(:hpux_ifconfig_interface).with(nic).returns(ifconfig_fixture) + Facter::Util::IP.get_interface_value(nic, "ipaddress").should == expected_ip + end + + it "should return netmask #{expected_netmask} on #{nic} for #{description} example" do + Facter.expects(:value).with(:kernel).at_least_once.returns("HP-UX") + Facter::Util::IP.expects(:hpux_lanscan).returns(lanscan_fixture) + Facter::Util::IP.expects(:hpux_ifconfig_interface).with(nic).returns(ifconfig_fixture) + Facter::Util::IP.get_interface_value(nic, "netmask").should == expected_netmask end - end - it "defines a fact for an interface's network" do - interfaces_hash.keys.each do |interface| - expect(Facter.fact("network_#{interface}")). - to be_a_kind_of Facter::Util::Fact + it "should return MAC address #{expected_mac} on #{nic} for #{description} example" do + Facter.expects(:value).with(:kernel).at_least_once.returns("HP-UX") + Facter::Util::IP.expects(:hpux_lanscan).returns(lanscan_fixture) + Facter::Util::IP.expects(:hpux_ifconfig_interface).with(nic).returns(ifconfig_fixture) + Facter::Util::IP.get_interface_value(nic, "macaddress").should == expected_mac end end end - describe "multiple facts sharing a single model" do - describe "when interfaces are resolved for the first time" do - before :each do - given_initial_interfaces_facts - Facter.value(:interfaces) - end + describe "on Windows" do + before :each do + Facter.stubs(:value).with(:kernel).returns("windows") + end - it 'lists the interfaces for the interfaces fact' do - expect(Facter.value(:interfaces)).to eq interfaces_hash.keys.join(',') - end + it "should return ipaddress information" do + windows_netsh = my_fixture_read("windows_netsh_single_interface") - it 'defines dynamic facts for the interfaces' do - interfaces_hash.keys.each do |interface| - described_class::INTERFACE_KEYS.each do |attr| - expect(Facter.value("#{attr}_#{interface}")). - to eq interfaces_hash[interface][attr] - end - end - end + Facter::Util::IP.expects(:get_output_for_interface_and_label).with("Local Area Connection", "ipaddress").returns(windows_netsh) - it 'defines a dynamic fact for the interfaces networks' do - interfaces_hash.keys.each do |interface| - expect(Facter.value("network_#{interface}")). - to eq interfaces_hash[interface][:network] - end - end + Facter::Util::IP.get_interface_value("Local Area Connection", "ipaddress").should == "172.16.138.216" end - describe "when interface facts have been flushed after being resolved" do - before :each do - Facter.stubs(:warnonce) + it "should return a human readable netmask" do + windows_netsh = my_fixture_read("windows_netsh_single_interface") - given_initial_interfaces_facts - when_interfaces_facts_have_been_resolved_then_flushed - end + Facter::Util::IP.expects(:get_output_for_interface_and_label).with("Local Area Connection", "netmask").returns(windows_netsh) - it "updates the interfaces fact" do - expect(Facter.value(:interfaces)).to eq interfaces_hash2.keys.join(',') - end + Facter::Util::IP.get_interface_value("Local Area Connection", "netmask").should == "255.255.255.0" + end - it "defines new dynamic facts for the new interfaces attributes" do - interfaces_hash2.keys.each do |interface| - described_class::INTERFACE_KEYS.each do |attr| - expect(Facter.value("#{attr}_#{interface}")). - to eq interfaces_hash2[interface][attr] - end - end - end + it "should return network information" do + windows_netsh = my_fixture_read("windows_netsh_single_interface") - it "defines a new dynamic fact for the new interfaces network" do - interfaces_hash2.keys.each do |interface| - expect(Facter.value("network_#{interface}")). - to eq interfaces_hash2[interface][:network] - end - end + Facter::Util::IP.stubs(:get_output_for_interface_and_label).with("Local Area Connection", "ipaddress").returns(windows_netsh) + Facter::Util::IP.stubs(:get_output_for_interface_and_label).with("Local Area Connection", "netmask").returns(windows_netsh) + + Facter::Util::IP.get_network_value("Local Area Connection").should == "172.16.138.0" + end + + it "should return ipaddress6 information" do + windows_netsh = my_fixture_read("windows_netsh_single_interface6") + + Facter::Util::IP.expects(:get_output_for_interface_and_label).with("Teredo Tunneling Pseudo-Interface", "ipaddress6").returns(windows_netsh) + + Facter::Util::IP.get_interface_value("Teredo Tunneling Pseudo-Interface", "ipaddress6").should == "2001:0:4137:9e76:2087:77a:53ef:7527" end end - def given_initial_interfaces_facts - model = described_class.new - model.stubs(:interfaces).returns(interfaces_hash.keys) - model.stubs(:parse!) - model.interfaces_hash = interfaces_hash - described_class.stubs(:new).returns(model) + describe "exec_ifconfig" do + it "uses get_ifconfig" do + Facter::Util::IP.stubs(:get_ifconfig).returns("/sbin/ifconfig").once + + Facter::Util::IP.exec_ifconfig + end + it "support additional arguments" do + Facter::Util::IP.stubs(:get_ifconfig).returns("/sbin/ifconfig") + + Facter::Util::Resolution.stubs(:exec).with("/sbin/ifconfig -a") + + Facter::Util::IP.exec_ifconfig(["-a"]) + end + it "joins multiple arguments correctly" do + Facter::Util::IP.stubs(:get_ifconfig).returns("/sbin/ifconfig") + + Facter::Util::Resolution.stubs(:exec).with("/sbin/ifconfig -a -e -i -j") + + Facter::Util::IP.exec_ifconfig(["-a","-e","-i","-j"]) + end + end + describe "get_ifconfig" do + it "assigns /sbin/ifconfig if it is executable" do + File.stubs(:executable?).returns(false) + File.stubs(:executable?).with("/sbin/ifconfig").returns(true) + Facter::Util::IP.get_ifconfig.should eq("/sbin/ifconfig") + end + it "assigns /usr/sbin/ifconfig if it is executable" do + File.stubs(:executable?).returns(false) + File.stubs(:executable?).with("/usr/sbin/ifconfig").returns(true) + Facter::Util::IP.get_ifconfig.should eq("/usr/sbin/ifconfig") + end + it "assigns /bin/ifconfig if it is executable" do + File.stubs(:executable?).returns(false) + File.stubs(:executable?).with("/bin/ifconfig").returns(true) + Facter::Util::IP.get_ifconfig.should eq("/bin/ifconfig") + end end - def when_interfaces_facts_have_been_resolved_then_flushed - interfaces_hash.keys.each do |interface| - described_class::INTERFACE_KEYS.each do |attr| - Facter.value("#{attr}_#{interface}") - end + context "with bonded ethernet interfaces on Linux" do + before :each do + Facter.fact(:kernel).stubs(:value).returns("Linux") end - model = described_class.new - model.stubs(:interfaces).returns(interfaces_hash2.keys) - model.stubs(:parse!) - model.interfaces_hash = interfaces_hash2 - described_class.stubs(:new).returns(model) + describe "Facter::Util::Ip.get_interface_value" do + before :each do + Facter::Util::IP.stubs(:read_proc_net_bonding). + with("/proc/net/bonding/bond0"). + returns(my_fixture_read("linux_2_6_35_proc_net_bonding_bond0")) - Facter.flush - Facter.value(:interfaces) + Facter::Util::IP.stubs(:get_bonding_master).returns("bond0") + end + + it 'provides the real device macaddress for eth0' do + Facter::Util::IP.get_interface_value("eth0", "macaddress").should == "00:11:22:33:44:55" + end + it 'provides the real device macaddress for eth1' do + Facter::Util::IP.get_interface_value("eth1", "macaddress").should == "00:11:22:33:44:56" + end + end end end diff --git a/spec/unit/util/parser_spec.rb b/spec/unit/util/parser_spec.rb index 8a0ae6ee48..be7b9e7f4d 100644 --- a/spec/unit/util/parser_spec.rb +++ b/spec/unit/util/parser_spec.rb @@ -1,7 +1,10 @@ +#!/usr/bin/env ruby + require 'spec_helper' + require 'facter/util/parser' require 'tempfile' -require 'tmpdir' +require 'tmpdir.rb' describe Facter::Util::Parser do include PuppetlabsSpec::Files diff --git a/spec/unit/zfs_version_spec.rb b/spec/unit/zfs_version_spec.rb index 9b88392a31..905dbc9ee5 100644 --- a/spec/unit/zfs_version_spec.rb +++ b/spec/unit/zfs_version_spec.rb @@ -1,3 +1,5 @@ +#!/usr/bin/env ruby + require 'spec_helper' describe "zfs_version fact" do diff --git a/spec/unit/zpool_version_spec.rb b/spec/unit/zpool_version_spec.rb index 5952b8f7b0..0cba70fdb6 100644 --- a/spec/unit/zpool_version_spec.rb +++ b/spec/unit/zpool_version_spec.rb @@ -1,3 +1,5 @@ +#!/usr/bin/env ruby + require 'spec_helper' describe "zpool_version fact" do From 0da8bd75c482466f1212f2cbd3d24d35532416dd Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Thu, 18 Apr 2013 22:59:31 -0700 Subject: [PATCH 1243/3753] (#20301) Handle different error in ruby 1.9 The NumberOfLogicalProcessor property of the Win32_Processor WMI class does not exist in the base win2003 install. Previously, we were trying to access the property, and then catching the resulting error if the property did not exist. In ruby 1.8, it would raise a RuntimeError, but in ruby 1.9, it raises a StandardError, causing the fact to blow up on ruby 1.9. This commit checks to see if the process object responds to that method, and only then does it try to call it. --- lib/facter/processor.rb | 4 ++-- spec/unit/processor_spec.rb | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb index 1d409eb687..1d11d07210 100644 --- a/lib/facter/processor.rb +++ b/lib/facter/processor.rb @@ -117,9 +117,9 @@ # get each physical processor Facter::Util::WMI.execquery("select * from Win32_Processor").each do |proc| # not supported before 2008 - begin + if proc.respond_to?(:NumberOfLogicalProcessors) processor_num = proc.NumberOfLogicalProcessors - rescue RuntimeError => e + else processor_num = 1 end diff --git a/spec/unit/processor_spec.rb b/spec/unit/processor_spec.rb index 31b336d820..1ed6935cbe 100755 --- a/spec/unit/processor_spec.rb +++ b/spec/unit/processor_spec.rb @@ -29,13 +29,14 @@ def load(procs) describe "2003" do before :each do proc = stubs 'proc' - proc.stubs(:NumberOfLogicalProcessors).raises(RuntimeError) proc.stubs(:Name).returns("Intel(R) Celeron(R) processor") load(Array.new(2, proc)) end it "should count 2 processors" do + proc.expects(:NumberOfLogicalProcessors).never + Facter.fact(:processorcount).value.should == "2" end From a6bc494d41d316629c0e55db1c577792d8f05c20 Mon Sep 17 00:00:00 2001 From: Jeremy Katz Date: Mon, 29 Apr 2013 22:16:51 -0400 Subject: [PATCH 1244/3753] Add initial functionality for exposing Rackspace Cloud data Rackspace Cloud doesn't expose the metadata service provided by OpenStack. You can get some basic information about region and what the instance id is from xenstore, though --- lib/facter/rackspace.rb | 36 +++++++++++++++++++++++++++++++++ spec/unit/rackspace_spec.rb | 40 +++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 lib/facter/rackspace.rb create mode 100644 spec/unit/rackspace_spec.rb diff --git a/lib/facter/rackspace.rb b/lib/facter/rackspace.rb new file mode 100644 index 0000000000..a419cbc068 --- /dev/null +++ b/lib/facter/rackspace.rb @@ -0,0 +1,36 @@ +# Purpose: Determine information about Rackspace cloud instances +# +# Resolution: +# If this is a Rackspace Cloud instance, populates rsc_ facts +# +# Caveats: +# Depends on Xenstore +# + +Facter.add(:is_rsc) do + setcode do + result = Facter::Util::Resolution.exec("/usr/bin/xenstore-read vm-data/provider_data/provider") + if result == "Rackspace" + "true" + else + "false" + end + end +end + +Facter.add(:rsc_region) do + confine :is_rsc => "true" + setcode do + Facter::Util::Resolution.exec("/usr/bin/xenstore-read vm-data/provider_data/region") + end +end + +Facter.add(:rsc_instance_id) do + confine :is_rsc => "true" + setcode do + result = Facter::Util::Resolution.exec("/usr/bin/xenstore-read name") + if result and (match = result.match(/instance-(.*)/)) + match[1] + end + end +end diff --git a/spec/unit/rackspace_spec.rb b/spec/unit/rackspace_spec.rb new file mode 100644 index 0000000000..dfbc54ed83 --- /dev/null +++ b/spec/unit/rackspace_spec.rb @@ -0,0 +1,40 @@ +#!/usr/bin/env ruby + +require 'spec_helper' +require 'facter' + +describe "rackspace facts" do + describe "on Rackspace Cloud" do + before :each do + Facter.collection.internal_loader.load(:rackspace) + end + + it "should set is_rsc to true" do + Facter::Util::Resolution.stubs(:exec).with("/usr/bin/xenstore-read vm-data/provider_data/provider").returns("Rackspace") + Facter.fact(:is_rsc).value.should == "true" + end + + it "should set the region to dfw" do + Facter.fact(:is_rsc).stubs(:value).returns("true") + Facter::Util::Resolution.stubs(:exec).with("/usr/bin/xenstore-read vm-data/provider_data/region").returns("dfw") + Facter.fact(:rsc_region).value.should == "dfw" + end + + it "should get the instance id" do + Facter.fact(:is_rsc).stubs(:value).returns("true") + Facter::Util::Resolution.stubs(:exec).with("/usr/bin/xenstore-read name").returns("instance-75a96685-85d6-44c6-aed8-41ef0fb2cfcc") + Facter.fact(:rsc_instance_id).value.should == "75a96685-85d6-44c6-aed8-41ef0fb2cfcc" + end + end + + describe "not on Rackspace Cloud" do + before do + Facter.collection.internal_loader.load(:rackspace) + end + + it "should set is_rsc to false" do + Facter::Util::Resolution.stubs(:exec).with("/usr/bin/xenstore-read vm-data/provider_data/provider").returns("other") + Facter.fact(:is_rsc).value.should == "false" + end + end +end \ No newline at end of file From cc5b2e4fa26371fe392a7ba858a779e76afba223 Mon Sep 17 00:00:00 2001 From: Jeremy Katz Date: Thu, 2 May 2013 13:24:16 -0400 Subject: [PATCH 1245/3753] Set is_rsc to nil (or don't set it) if we're not on Rackspace Cloud --- lib/facter/rackspace.rb | 2 -- spec/unit/rackspace_spec.rb | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/facter/rackspace.rb b/lib/facter/rackspace.rb index a419cbc068..0a27f7b0ae 100644 --- a/lib/facter/rackspace.rb +++ b/lib/facter/rackspace.rb @@ -12,8 +12,6 @@ result = Facter::Util::Resolution.exec("/usr/bin/xenstore-read vm-data/provider_data/provider") if result == "Rackspace" "true" - else - "false" end end end diff --git a/spec/unit/rackspace_spec.rb b/spec/unit/rackspace_spec.rb index dfbc54ed83..14d0b59d05 100644 --- a/spec/unit/rackspace_spec.rb +++ b/spec/unit/rackspace_spec.rb @@ -32,9 +32,9 @@ Facter.collection.internal_loader.load(:rackspace) end - it "should set is_rsc to false" do + it "shouldn't set is_rsc" do Facter::Util::Resolution.stubs(:exec).with("/usr/bin/xenstore-read vm-data/provider_data/provider").returns("other") - Facter.fact(:is_rsc).value.should == "false" + Facter.fact(:is_rsc).value.should == nil end end -end \ No newline at end of file +end From b5cb1ef23b5ec5512cc68fefee222882808fbb25 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Thu, 18 Apr 2013 22:59:31 -0700 Subject: [PATCH 1246/3753] (#20301) Handle different error in ruby 1.9 The NumberOfLogicalProcessor property of the Win32_Processor WMI class does not exist in the base win2003 install. Previously, we were trying to access the property, and then catching the resulting error if the property did not exist. In ruby 1.8, it would raise a RuntimeError, but in ruby 1.9, it raises a StandardError, causing the fact to blow up on ruby 1.9. This commit checks to see if the process object responds to that method, and only then does it try to call it. --- lib/facter/processor.rb | 4 ++-- spec/unit/processor_spec.rb | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb index 1d409eb687..1d11d07210 100644 --- a/lib/facter/processor.rb +++ b/lib/facter/processor.rb @@ -117,9 +117,9 @@ # get each physical processor Facter::Util::WMI.execquery("select * from Win32_Processor").each do |proc| # not supported before 2008 - begin + if proc.respond_to?(:NumberOfLogicalProcessors) processor_num = proc.NumberOfLogicalProcessors - rescue RuntimeError => e + else processor_num = 1 end diff --git a/spec/unit/processor_spec.rb b/spec/unit/processor_spec.rb index 31b336d820..1ed6935cbe 100755 --- a/spec/unit/processor_spec.rb +++ b/spec/unit/processor_spec.rb @@ -29,13 +29,14 @@ def load(procs) describe "2003" do before :each do proc = stubs 'proc' - proc.stubs(:NumberOfLogicalProcessors).raises(RuntimeError) proc.stubs(:Name).returns("Intel(R) Celeron(R) processor") load(Array.new(2, proc)) end it "should count 2 processors" do + proc.expects(:NumberOfLogicalProcessors).never + Facter.fact(:processorcount).value.should == "2" end From bf39f9aa181ddbf14df842e7508e1e115e87abe6 Mon Sep 17 00:00:00 2001 From: Matthaus Owens Date: Fri, 3 May 2013 11:59:09 -0700 Subject: [PATCH 1247/3753] (packaging) Update FACTERVERSION to 1.7.1-rc1 --- lib/facter/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/version.rb b/lib/facter/version.rb index d63795fe6d..21ceb1e0f6 100644 --- a/lib/facter/version.rb +++ b/lib/facter/version.rb @@ -1,6 +1,6 @@ module Facter if not defined? FACTERVERSION then - FACTERVERSION = '1.7.0' + FACTERVERSION = '1.7.1-rc1' end ## From db87076cba2d0cb6b4b6338339bb2e659cb1971c Mon Sep 17 00:00:00 2001 From: Konstantin Orekhov Date: Fri, 3 May 2013 18:39:18 -0700 Subject: [PATCH 1248/3753] Fix for http://projects.puppetlabs.com/issues/18215 A simple fix for an issue #18215 that still exists in facter 1.7. The logic changed to run kstat only if SunOS version is below 5.8, otherwise use psrinfo. This way processor count correctly reported. --- lib/facter/processor.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb index d01b874013..575563069f 100644 --- a/lib/facter/processor.rb +++ b/lib/facter/processor.rb @@ -153,7 +153,7 @@ (major_version, minor_version) = kernelrelease.split(".").map { |str| str.to_i } result = nil - if (major_version > 5) or (major_version == 5 and minor_version >= 8) then + if (major_version < 5) or (major_version == 5 and minor_version <= 8) then if kstat = Facter::Util::Resolution.exec("/usr/bin/kstat cpu_info") result = kstat.scan(/\bcore_id\b\s+\d+/).uniq.length end From 75c7ea6c3231a267472475c720ca81100327ca11 Mon Sep 17 00:00:00 2001 From: Konstantin Orekhov Date: Mon, 6 May 2013 15:56:06 -0700 Subject: [PATCH 1249/3753] Refined logic for SunOS processorCount Refined logic to use kstat only on SunOS < 5.8, psrinfo on everything from 5.8 and above. --- lib/facter/processor.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb index 575563069f..c4ab386c73 100644 --- a/lib/facter/processor.rb +++ b/lib/facter/processor.rb @@ -153,7 +153,7 @@ (major_version, minor_version) = kernelrelease.split(".").map { |str| str.to_i } result = nil - if (major_version < 5) or (major_version == 5 and minor_version <= 8) then + if (major_version < 5) or (major_version == 5 and minor_version < 8) then if kstat = Facter::Util::Resolution.exec("/usr/bin/kstat cpu_info") result = kstat.scan(/\bcore_id\b\s+\d+/).uniq.length end From 04067a2903a5cf0873599632e48cdacfac232ad4 Mon Sep 17 00:00:00 2001 From: Konstantin Orekhov Date: Mon, 6 May 2013 18:28:47 -0700 Subject: [PATCH 1250/3753] (#18215) Updated processor_spec.rb for Solaris changes Updated processor_spec.rb for Solaris changes to support fixes for issue #18215. --- spec/unit/processor_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/unit/processor_spec.rb b/spec/unit/processor_spec.rb index 1ed6935cbe..3578183887 100755 --- a/spec/unit/processor_spec.rb +++ b/spec/unit/processor_spec.rb @@ -267,7 +267,7 @@ def load(procs) let(:kstat_sparc) { @fixture_kstat_sparc } let(:kstat_x86_64) { @fixture_kstat_x86_64 } - %w{ 5.8 5.9 5.10 5.11 }.each do |release| + %w{ 5.5.1 5.6 5.7 }.each do |release| %w{ sparc x86_64 }.each do |arch| it "uses kstat on release #{release} (#{arch})" do Facter.fact(:kernel).stubs(:value).returns(:sunos) @@ -279,7 +279,7 @@ def load(procs) end end - %w{ 5.5.1 5.6 5.7 }.each do |release| + %w{ 5.8 5.9 5.10 5.11 }.each do |release| it "uses psrinfo on release #{release}" do Facter.fact(:kernel).stubs(:value).returns(:sunos) Facter.stubs(:value).with(:kernelrelease).returns(release) From 3631910116966aaa3663bf1dfbf37b9006f686a9 Mon Sep 17 00:00:00 2001 From: Marc Fournier Date: Wed, 8 May 2013 14:06:54 +0200 Subject: [PATCH 1251/3753] (#20617) add support for zfsonlinux + fixtures "zpool upgrade -v" has a "FEAT DESCRIPTION" section that other ZFS implementations don't have. The regex in the zpool_version fact choked on this difference. It was replaced by same one used in the zfs_version fact. Thanks to Trey Dockendorf for his helpful bugreport! --- lib/facter/zpool_version.rb | 2 +- .../unit/zfs_version/zfsonlinux_0.6.1 | 13 +++++ .../unit/zpool_version/zfsonlinux_0.6.1 | 48 +++++++++++++++++++ spec/unit/zfs_version_spec.rb | 5 ++ spec/unit/zpool_version_spec.rb | 5 ++ 5 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 spec/fixtures/unit/zfs_version/zfsonlinux_0.6.1 create mode 100644 spec/fixtures/unit/zpool_version/zfsonlinux_0.6.1 diff --git a/lib/facter/zpool_version.rb b/lib/facter/zpool_version.rb index d182b2dc77..b0470fc230 100644 --- a/lib/facter/zpool_version.rb +++ b/lib/facter/zpool_version.rb @@ -4,7 +4,7 @@ setcode do if Facter::Util::Resolution.which('zpool') zpool_v = Facter::Util::Resolution.exec('zpool upgrade -v') - zpool_version = zpool_v.match(/ZFS pool version (\d+)./).captures.first unless zpool_v.nil? + zpool_version = zpool_v.scan(/^\s+(\d+)\s+/m).flatten.last unless zpool_v.nil? end end end diff --git a/spec/fixtures/unit/zfs_version/zfsonlinux_0.6.1 b/spec/fixtures/unit/zfs_version/zfsonlinux_0.6.1 new file mode 100644 index 0000000000..8be11f6b15 --- /dev/null +++ b/spec/fixtures/unit/zfs_version/zfsonlinux_0.6.1 @@ -0,0 +1,13 @@ +The following filesystem versions are supported: + +VER DESCRIPTION +--- -------------------------------------------------------- + 1 Initial ZFS filesystem version + 2 Enhanced directory entries + 3 Case insensitive and filesystem user identifier (FUID) + 4 userquota, groupquota properties + 5 System attributes + +For more information on a particular version, including supported releases, +see the ZFS Administration Guide. + diff --git a/spec/fixtures/unit/zpool_version/zfsonlinux_0.6.1 b/spec/fixtures/unit/zpool_version/zfsonlinux_0.6.1 new file mode 100644 index 0000000000..69cf8e8ac9 --- /dev/null +++ b/spec/fixtures/unit/zpool_version/zfsonlinux_0.6.1 @@ -0,0 +1,48 @@ +This system supports ZFS pool feature flags. + +The following features are supported: + +FEAT DESCRIPTION +------------------------------------------------------------- +async_destroy (read-only compatible) + Destroy filesystems asynchronously. +empty_bpobj (read-only compatible) + Snapshots use less space. +lz4_compress + LZ4 compression algorithm support. + +The following legacy versions are also supported: + +VER DESCRIPTION +--- -------------------------------------------------------- + 1 Initial ZFS version + 2 Ditto blocks (replicated metadata) + 3 Hot spares and double parity RAID-Z + 4 zpool history + 5 Compression using the gzip algorithm + 6 bootfs pool property + 7 Separate intent log devices + 8 Delegated administration + 9 refquota and refreservation properties + 10 Cache devices + 11 Improved scrub performance + 12 Snapshot properties + 13 snapused property + 14 passthrough-x aclinherit + 15 user/group space accounting + 16 stmf property support + 17 Triple-parity RAID-Z + 18 Snapshot user holds + 19 Log device removal + 20 Compression using zle (zero-length encoding) + 21 Deduplication + 22 Received properties + 23 Slim ZIL + 24 System attributes + 25 Improved scrub stats + 26 Improved snapshot deletion performance + 27 Improved snapshot creation performance + 28 Multiple vdev replacements + +For more information on a particular version, including supported releases, +see the ZFS Administration Guide. diff --git a/spec/unit/zfs_version_spec.rb b/spec/unit/zfs_version_spec.rb index 905dbc9ee5..a7e7d2a63a 100644 --- a/spec/unit/zfs_version_spec.rb +++ b/spec/unit/zfs_version_spec.rb @@ -43,6 +43,11 @@ Facter.fact(:zfs_version).value.should == "4" end + it "should return correct version on Linux with zfsonlinux" do + Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(my_fixture_read('zfsonlinux_0.6.1')) + Facter.fact(:zfs_version).value.should == "5" + end + it "should return nil if zfs command is not available" do Facter::Util::Resolution.stubs(:which).with("zfs").returns(nil) Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(my_fixture_read('linux-fuse_0.6.9')) diff --git a/spec/unit/zpool_version_spec.rb b/spec/unit/zpool_version_spec.rb index 0cba70fdb6..cf854bd595 100644 --- a/spec/unit/zpool_version_spec.rb +++ b/spec/unit/zpool_version_spec.rb @@ -43,6 +43,11 @@ Facter.fact(:zpool_version).value.should == "23" end + it "should return correct version on Linux with zfsonlinux" do + Facter::Util::Resolution.stubs(:exec).with("zpool upgrade -v").returns(my_fixture_read('zfsonlinux_0.6.1')) + Facter.fact(:zpool_version).value.should == "28" + end + it "should return nil if zpool is not available" do Facter::Util::Resolution.stubs(:which).with("zpool").returns(nil) Facter::Util::Resolution.stubs(:exec).with("zpool upgrade -v").returns(my_fixture_read('linux-fuse_0.6.9')) From 684b40a65ab9d3db3ad645b839eddd57e16728bd Mon Sep 17 00:00:00 2001 From: Konstantin Orekhov Date: Wed, 8 May 2013 14:03:19 -0700 Subject: [PATCH 1252/3753] processorcount spec wrongly used physicalprocessorcount fact processorcount spec wrongly used physicalprocessorcount fact --- spec/unit/processor_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/unit/processor_spec.rb b/spec/unit/processor_spec.rb index 3578183887..9ab4531def 100755 --- a/spec/unit/processor_spec.rb +++ b/spec/unit/processor_spec.rb @@ -285,7 +285,7 @@ def load(procs) Facter.stubs(:value).with(:kernelrelease).returns(release) Facter::Util::Resolution.expects(:exec).with("/usr/sbin/psrinfo").returns(psrinfo) - Facter.fact(:physicalprocessorcount).value.should == "2" + Facter.fact(:processorcount).value.should == "2" end end end From 95fcd1d9d6b91076019275cac63901e25d8cba46 Mon Sep 17 00:00:00 2001 From: Konstantin Orekhov Date: Wed, 8 May 2013 16:21:24 -0700 Subject: [PATCH 1253/3753] adding psrinfo from Solaris with 24 CPUs and using it in processor spec adding psrinfo from Solaris with 24 CPUs and using it in processor spec. --- spec/fixtures/processorcount/solaris-psrinfo | 24 ++++++++++++++++++++ spec/unit/processor_spec.rb | 22 ++++++++---------- 2 files changed, 33 insertions(+), 13 deletions(-) create mode 100644 spec/fixtures/processorcount/solaris-psrinfo diff --git a/spec/fixtures/processorcount/solaris-psrinfo b/spec/fixtures/processorcount/solaris-psrinfo new file mode 100644 index 0000000000..a03ce95364 --- /dev/null +++ b/spec/fixtures/processorcount/solaris-psrinfo @@ -0,0 +1,24 @@ +0 on-line since 10/01/2012 21:05:55 +1 on-line since 10/01/2012 21:06:00 +2 on-line since 10/01/2012 21:06:00 +3 on-line since 10/01/2012 21:06:00 +4 on-line since 10/01/2012 21:06:00 +5 on-line since 10/01/2012 21:06:00 +6 on-line since 10/01/2012 21:06:00 +7 on-line since 10/01/2012 21:06:00 +8 on-line since 10/01/2012 21:06:00 +9 on-line since 10/01/2012 21:06:00 +10 on-line since 10/01/2012 21:06:00 +11 on-line since 10/01/2012 21:06:00 +12 on-line since 10/01/2012 21:06:01 +13 on-line since 10/01/2012 21:06:01 +14 on-line since 10/01/2012 21:06:01 +15 on-line since 10/01/2012 21:06:01 +16 on-line since 10/01/2012 21:06:01 +17 on-line since 10/01/2012 21:06:01 +18 on-line since 10/01/2012 21:06:01 +19 on-line since 10/01/2012 21:06:01 +20 on-line since 10/01/2012 21:06:01 +21 on-line since 10/01/2012 21:06:01 +22 on-line since 10/01/2012 21:06:01 +23 on-line since 10/01/2012 21:06:01 diff --git a/spec/unit/processor_spec.rb b/spec/unit/processor_spec.rb index 9ab4531def..61229769da 100755 --- a/spec/unit/processor_spec.rb +++ b/spec/unit/processor_spec.rb @@ -76,15 +76,15 @@ def load(procs) end it "should detect the correct processor count on x86_64" do - fixture_data = File.read(fixtures('processorcount','solaris-x86_64-kstat-cpu-info')) - Facter::Util::Resolution.expects(:exec).with("/usr/bin/kstat cpu_info").returns(fixture_data) - Facter.fact(:processorcount).value.should == 8 + fixture_data = File.read(fixtures('processorcount','solaris-psrinfo')) + Facter::Util::Resolution.expects(:exec).with("/usr/sbin/psrinfo").returns(fixture_data) + Facter.fact(:processorcount).value.should == 24 end it "should detect the correct processor count on sparc" do - fixture_data = File.read(fixtures('processorcount','solaris-sparc-kstat-cpu-info')) - Facter::Util::Resolution.expects(:exec).with("/usr/bin/kstat cpu_info").returns(fixture_data) - Facter.fact(:processorcount).value.should == 8 + fixture_data = File.read(fixtures('processorcount','solaris-psrinfo')) + Facter::Util::Resolution.expects(:exec).with("/usr/sbin/psrinfo").returns(fixture_data) + Facter.fact(:processorcount).value.should == 24 end end @@ -259,11 +259,6 @@ def load(procs) @fixture_kstat_x86_64 = File.read(fixtures('processorcount','solaris-x86_64-kstat-cpu-info')) end - let(:psrinfo) do - "0 on-line since 10/16/2012 14:06:12\n" + - "1 on-line since 10/16/2012 14:06:14\n" - end - let(:kstat_sparc) { @fixture_kstat_sparc } let(:kstat_x86_64) { @fixture_kstat_x86_64 } @@ -284,8 +279,9 @@ def load(procs) Facter.fact(:kernel).stubs(:value).returns(:sunos) Facter.stubs(:value).with(:kernelrelease).returns(release) - Facter::Util::Resolution.expects(:exec).with("/usr/sbin/psrinfo").returns(psrinfo) - Facter.fact(:processorcount).value.should == "2" + fixture_data = File.read(fixtures('processorcount','solaris-psrinfo')) + Facter::Util::Resolution.expects(:exec).with("/usr/sbin/psrinfo").returns(fixture_data) + Facter.fact(:processorcount).value.should == "24" end end end From e43d25f1a3afc2e338c82e470bf65a9ddfc70ba6 Mon Sep 17 00:00:00 2001 From: Konstantin Orekhov Date: Wed, 8 May 2013 19:53:49 -0700 Subject: [PATCH 1254/3753] removing double quotes in a spec --- spec/unit/processor_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/unit/processor_spec.rb b/spec/unit/processor_spec.rb index 61229769da..70e15654a9 100755 --- a/spec/unit/processor_spec.rb +++ b/spec/unit/processor_spec.rb @@ -281,7 +281,7 @@ def load(procs) fixture_data = File.read(fixtures('processorcount','solaris-psrinfo')) Facter::Util::Resolution.expects(:exec).with("/usr/sbin/psrinfo").returns(fixture_data) - Facter.fact(:processorcount).value.should == "24" + Facter.fact(:processorcount).value.should == 24 end end end From 4c035e8a243ebd5cf911d8acb226067943ed670b Mon Sep 17 00:00:00 2001 From: Konstantin Orekhov Date: Fri, 10 May 2013 16:36:51 -0700 Subject: [PATCH 1255/3753] removed duplicate tests for Solaris --- spec/unit/processor_spec.rb | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/spec/unit/processor_spec.rb b/spec/unit/processor_spec.rb index 70e15654a9..0e377fad15 100755 --- a/spec/unit/processor_spec.rb +++ b/spec/unit/processor_spec.rb @@ -68,26 +68,6 @@ def load(procs) end end - describe "on Solaris" do - before :each do - Facter.collection.internal_loader.load(:processor) - Facter.fact(:kernel).stubs(:value).returns(:sunos) - Facter.stubs(:value).with(:kernelrelease).returns("5.10") - end - - it "should detect the correct processor count on x86_64" do - fixture_data = File.read(fixtures('processorcount','solaris-psrinfo')) - Facter::Util::Resolution.expects(:exec).with("/usr/sbin/psrinfo").returns(fixture_data) - Facter.fact(:processorcount).value.should == 24 - end - - it "should detect the correct processor count on sparc" do - fixture_data = File.read(fixtures('processorcount','solaris-psrinfo')) - Facter::Util::Resolution.expects(:exec).with("/usr/sbin/psrinfo").returns(fixture_data) - Facter.fact(:processorcount).value.should == 24 - end - end - describe "on Unixes" do before :each do Facter.collection.internal_loader.load(:processor) From 785fd8b85210b1e718eb62ae23eba893382e34bf Mon Sep 17 00:00:00 2001 From: Matthaus Owens Date: Mon, 13 May 2013 10:10:04 -0700 Subject: [PATCH 1256/3753] (packaging) Update FACTERVERSION to 1.7.1 --- lib/facter/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/version.rb b/lib/facter/version.rb index 21ceb1e0f6..89349f2aae 100644 --- a/lib/facter/version.rb +++ b/lib/facter/version.rb @@ -1,6 +1,6 @@ module Facter if not defined? FACTERVERSION then - FACTERVERSION = '1.7.1-rc1' + FACTERVERSION = '1.7.1' end ## From 3f2e4784e1d64d014db32852ecb34069f11ab16b Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 16 Apr 2013 12:49:55 -0700 Subject: [PATCH 1257/3753] (maint) Use RSpec 2.9 expectations We have RSpec 2.11 listed in the project Gemfile but CI is still using 2.9. This commit switches the expectation syntax to the older version for compatibility. --- spec/unit/virtual_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index 4aadf0ef7f..6215e30e53 100755 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -206,10 +206,10 @@ end end - it "(#20236) is vmware when dmidecode contains vmware and lspci returns insufficient information", :focus => true do + it "(#20236) is vmware when dmidecode contains vmware and lspci returns insufficient information" do Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns("garbage\ninformation\n") Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("On Board Device 1 Information\nType: Video\nStatus: Disabled\nDescription: VMware SVGA II") - expect(Facter.fact(:virtual).value).to eq("vmware") + Facter.fact(:virtual).value.should eq("vmware") end end From b0b871112fe10d2eb1f59289c09aca54bdd7736a Mon Sep 17 00:00:00 2001 From: Alex Harvey Date: Sat, 17 Nov 2012 14:41:41 +1100 Subject: [PATCH 1258/3753] (#17894) Add fix to handle processorX facts on HP Superdomes. Without this patch applied, the processorX facts fail on the Superdome2-16s model servers. The patch includes machinfo fixtures for the Superdome2-16s as well as the Superdome SD32B as provided by Luke M. --- lib/facter/util/processor.rb | 2 + .../hpux/machinfo/superdome-server-SD32B | 53 +++++++++++++++++++ spec/fixtures/hpux/machinfo/superdome2-16s | 31 +++++++++++ spec/unit/processor_spec.rb | 2 + 4 files changed, 88 insertions(+) create mode 100644 spec/fixtures/hpux/machinfo/superdome-server-SD32B create mode 100644 spec/fixtures/hpux/machinfo/superdome2-16s diff --git a/lib/facter/util/processor.rb b/lib/facter/util/processor.rb index c619d38a74..335dcf6fa5 100644 --- a/lib/facter/util/processor.rb +++ b/lib/facter/util/processor.rb @@ -87,6 +87,8 @@ def self.hpux_processor_list elsif line.match(/\d+\s+((?:PA-RISC|Intel).*processors.*)/) then cpu = $1.to_s cpu.sub!(/processors/, "processor") + elsif line.match(/\s+(Intel.*Processor.*)/) then + cpu = $1.to_s end end end diff --git a/spec/fixtures/hpux/machinfo/superdome-server-SD32B b/spec/fixtures/hpux/machinfo/superdome-server-SD32B new file mode 100644 index 0000000000..14aefbeaad --- /dev/null +++ b/spec/fixtures/hpux/machinfo/superdome-server-SD32B @@ -0,0 +1,53 @@ +CPU info: + Number of CPUs = 8 + Number of enabled CPUs = 2 + Number of enabled sockets = 2 + Cores per socket = 2 + Clock speed = 1598 MHz + Bus speed = 533 MT/s + CPUID registers + vendor information = "GenuineIntel" + processor serial number = 0x0000000000000000 + processor version info = 0x0000000020000704 + architecture revision: 0 + processor family: 32 Intel(R) Itanium 2 9000 series + processor model: 1 Intel(R) Itanium 2 9000 series + Bus features + implemented = 0xbdf0000020000000 + selected = 0x0020000000000000 + Exclusive Bus Cache Line Replacement Enabled + processor revision: 7 Stepping C2 + largest CPUID reg: 4 + processor capabilities = 0x0000000000000005 + implements long branch: 1 + implements 16-byte atomic operations: 1 + +Cache info (per core): + L1 Instruction: size = 16 KB, associativity = 4 + L1 Data: size = 16 KB, associativity = 4 + L2 Instruction: size = 1024 KB, associativity = 8 + L2 Data: size = 256 KB, associativity = 8 + L3 Unified: size = 12288 KB, associativity = 12 + +Memory = 32700 MB (31.933594 GB) + +Firmware info: + Firmware revision = 9.66 + FP SWA driver revision: 1.18 + IPMI is supported on this system. + ERROR: Unable to obtain manageability firmware revision info. + +Platform info: + model string = "ia64 hp superdome server SD32B" + machine id number = STRING_WITH_DASHES + machine serial number = STRING + +OS info: + sysname = HP-UX + nodename = HOSTNAME + release = B.11.23 + version = U (unlimited-user license) + machine = ia64 + idnumber = NUMBER + vmunix _release_version: +@(#) $Revision: vmunix: B11.23_LR FLAVOR=perf Fri Aug 29 22:35:38 PDT 2003 $ diff --git a/spec/fixtures/hpux/machinfo/superdome2-16s b/spec/fixtures/hpux/machinfo/superdome2-16s new file mode 100644 index 0000000000..5652d19139 --- /dev/null +++ b/spec/fixtures/hpux/machinfo/superdome2-16s @@ -0,0 +1,31 @@ +CPU info: + Intel(R) Itanium(R) Processor 9340 (1.6 GHz, 15 MB) + 4 cores, 8 logical processors per socket + 4.79 GT/s QPI, CPU version E0 + Active processor count: + 2 sockets + 6 cores (3 per socket) + 12 logical processors (6 per socket) + LCPU attribute is enabled + +Memory: 14332 MB (14 GB) + +Firmware info: + Firmware revision: 004.044.000 + FP SWA driver revision: 1.18 + IPMI is supported on this system. + BMC firmware revision: 2.53 + +Platform info: + Model: "ia64 hp Superdome2 16s" + Machine ID number: STRING_WITH_DASHES + Machine serial number: STRING + +OS info: + Nodename: HOSTNAME + Release: HP-UX B.11.31 + Version: U (unlimited-user license) + Machine: ia64 + ID Number: NUMBER + vmunix _release_version: +@(#) $Revision: vmunix: B.11.31_LR FLAVOR=perf diff --git a/spec/unit/processor_spec.rb b/spec/unit/processor_spec.rb index 0e377fad15..aa47127218 100755 --- a/spec/unit/processor_spec.rb +++ b/spec/unit/processor_spec.rb @@ -309,6 +309,8 @@ def self.machinfo_examples examples << [File.read(fixtures('hpux','machinfo','ia64-rx6600')), "Intel(R) Itanium 2 9100 series processor (1.59 GHz, 18 MB)"] examples << [File.read(fixtures('hpux','machinfo','ia64-rx8640')), "Intel(R) Itanium 2 9100 series"] examples << [File.read(fixtures('hpux','machinfo','hppa-rp4440')), "PA-RISC 8800 processor (1000 MHz, 64 MB)"] + examples << [File.read(fixtures('hpux','machinfo','superdome-server-SD32B')), "Intel(R) Itanium 2 9000 series"] + examples << [File.read(fixtures('hpux','machinfo','superdome2-16s')), "Intel(R) Itanium(R) Processor 9340 (1.6 GHz, 15 MB)"] examples end From 38995f3704ab6a771e6b5b74867966edc3d4ca46 Mon Sep 17 00:00:00 2001 From: Hailee Kenney Date: Thu, 16 May 2013 14:08:01 -0700 Subject: [PATCH 1259/3753] (#17894) Fix whitespace error Prior to this commit there was a whitespace error in superdome-server-SD32B which is now fixed. --- spec/fixtures/hpux/machinfo/superdome-server-SD32B | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/fixtures/hpux/machinfo/superdome-server-SD32B b/spec/fixtures/hpux/machinfo/superdome-server-SD32B index 14aefbeaad..11a44e66c4 100644 --- a/spec/fixtures/hpux/machinfo/superdome-server-SD32B +++ b/spec/fixtures/hpux/machinfo/superdome-server-SD32B @@ -49,5 +49,5 @@ OS info: version = U (unlimited-user license) machine = ia64 idnumber = NUMBER - vmunix _release_version: + vmunix _release_version: @(#) $Revision: vmunix: B11.23_LR FLAVOR=perf Fri Aug 29 22:35:38 PDT 2003 $ From 6c6bd0deec70047e0b32ef1b870c75a988b032ae Mon Sep 17 00:00:00 2001 From: razic Date: Thu, 14 Mar 2013 22:09:45 +0700 Subject: [PATCH 1260/3753] (#17710) Refactor the IP module This pull request aims to refactor the Facter::Util::IP module with kernel specific classes adhering to a common interface. This pull request eliminates case statements and crazy kernel conditionals within methods. Additionally, this eliminates the giant REGEX_MAP constant at the top of the Facter::Util::IP module and adds some documentation to methods that were previously left undocumented. This also removes the untested network.rb fact and moves in into interfaces.rb since they are dynamically generated by the name of the interfaces. They now have tests. This supports the new on_flush method. The related ticket is here: http://projects.puppetlabs.com/issues/17710 --- lib/facter/interfaces.rb | 45 +- lib/facter/network.rb | 20 - lib/facter/util/ip.rb | 427 ++++++------- lib/facter/util/ip/base.rb | 196 ++++++ lib/facter/util/ip/darwin.rb | 6 + lib/facter/util/ip/dragonfly.rb | 6 + lib/facter/util/ip/free_bsd.rb | 6 + lib/facter/util/ip/gnu_k_free_bsd.rb | 9 + lib/facter/util/ip/hpux.rb | 76 +++ lib/facter/util/ip/linux.rb | 169 +++++ lib/facter/util/ip/net_bsd.rb | 6 + lib/facter/util/ip/open_bsd.rb | 6 + lib/facter/util/ip/sun_os.rb | 24 + lib/facter/util/ip/windows.rb | 73 +++ .../ifconfig_all_with_multiple_interfaces} | 0 .../ip/darwin/ifconfig_with_single_interface | 6 + ...-STABLE_ifconfig_with_multiple_interfaces} | 0 .../6.0-STABLE_ifconfig_with_single_interface | 10 + .../ifconfig_all_with_multiple_interfaces} | 0 .../ifconfig_with_single_interface | 8 + .../1111_ifconfig_lan0} | 0 .../1111_ifconfig_lan1} | 0 .../1111_ifconfig_lo0} | 0 .../{hpux_1111_lanscan => hpux/1111_lanscan} | 0 .../1111_netstat_in} | 0 .../1131_asterisk_ifconfig_lan0} | 0 .../1131_asterisk_ifconfig_lan1} | 0 .../1131_asterisk_ifconfig_lo0} | 0 .../1131_asterisk_lanscan} | 0 .../1131_asterisk_netstat_in} | 0 .../1131_ifconfig_lan0} | 0 .../1131_ifconfig_lan1} | 0 .../1131_ifconfig_lo0} | 0 .../{hpux_1131_lanscan => hpux/1131_lanscan} | 0 .../1131_netstat_in} | 0 .../1131_nic_bonding_ifconfig_lan1} | 0 .../1131_nic_bonding_ifconfig_lan4} | 0 .../1131_nic_bonding_ifconfig_lan4_1} | 0 .../1131_nic_bonding_ifconfig_lo0} | 0 .../1131_nic_bonding_lanscan} | 0 .../1131_nic_bonding_netstat_in} | 0 .../2_6_35_proc_net_bonding_bond0} | 0 .../ifconfig_all_with_single_interface} | 0 .../ifconfig_single_interface_eth0} | 0 .../ifconfig_single_interface_lo} | 0 .../ifconfig_with_single_interface_ib0} | 0 .../ifconfig_all_with_multiple_interfaces} | 0 .../ifconfig_single_interface} | 0 .../netsh_all_interfaces} | 0 .../netsh_with_single_interface} | 0 .../netsh_with_single_interface6} | 0 spec/unit/blockdevices_spec.rb | 2 - spec/unit/hardwareisa_spec.rb | 2 - spec/unit/hardwaremodel_spec.rb | 2 - spec/unit/interfaces_spec.rb | 68 --- spec/unit/ldom_spec.rb | 2 - spec/unit/manufacturer_spec.rb | 2 - spec/unit/uniqueid_spec.rb | 2 - spec/unit/util/directory_loader_spec.rb | 3 - spec/unit/util/ip/base_spec.rb | 147 +++++ spec/unit/util/ip/darwin_spec.rb | 87 +++ spec/unit/util/ip/dragonfly_spec.rb | 34 ++ spec/unit/util/ip/free_bsd_spec.rb | 56 ++ spec/unit/util/ip/gnu_k_free_bsd_spec.rb | 85 +++ spec/unit/util/ip/hpux_spec.rb | 536 ++++++++++++++++ spec/unit/util/ip/linux_spec.rb | 158 +++++ spec/unit/util/ip/net_bsd_spec.rb | 34 ++ spec/unit/util/ip/open_bsd_spec.rb | 34 ++ spec/unit/util/ip/sun_os_spec.rb | 91 +++ spec/unit/util/ip/windows_spec.rb | 94 +++ spec/unit/util/ip_spec.rb | 578 +++++------------- spec/unit/util/parser_spec.rb | 5 +- spec/unit/zfs_version_spec.rb | 2 - spec/unit/zpool_version_spec.rb | 2 - 74 files changed, 2297 insertions(+), 822 deletions(-) delete mode 100644 lib/facter/network.rb create mode 100644 lib/facter/util/ip/base.rb create mode 100644 lib/facter/util/ip/darwin.rb create mode 100644 lib/facter/util/ip/dragonfly.rb create mode 100644 lib/facter/util/ip/free_bsd.rb create mode 100644 lib/facter/util/ip/gnu_k_free_bsd.rb create mode 100644 lib/facter/util/ip/hpux.rb create mode 100644 lib/facter/util/ip/linux.rb create mode 100644 lib/facter/util/ip/net_bsd.rb create mode 100644 lib/facter/util/ip/open_bsd.rb create mode 100644 lib/facter/util/ip/sun_os.rb create mode 100644 lib/facter/util/ip/windows.rb rename spec/fixtures/unit/util/ip/{darwin_ifconfig_all_with_multiple_interfaces => darwin/ifconfig_all_with_multiple_interfaces} (100%) create mode 100644 spec/fixtures/unit/util/ip/darwin/ifconfig_with_single_interface rename spec/fixtures/unit/util/ip/{6.0-STABLE_FreeBSD_ifconfig => free_bsd/6.0-STABLE_ifconfig_with_multiple_interfaces} (100%) create mode 100644 spec/fixtures/unit/util/ip/free_bsd/6.0-STABLE_ifconfig_with_single_interface rename spec/fixtures/unit/util/ip/{debian_kfreebsd_ifconfig => gnu_k_free_bsd/ifconfig_all_with_multiple_interfaces} (100%) create mode 100644 spec/fixtures/unit/util/ip/gnu_k_free_bsd/ifconfig_with_single_interface rename spec/fixtures/unit/util/ip/{hpux_1111_ifconfig_lan0 => hpux/1111_ifconfig_lan0} (100%) rename spec/fixtures/unit/util/ip/{hpux_1111_ifconfig_lan1 => hpux/1111_ifconfig_lan1} (100%) rename spec/fixtures/unit/util/ip/{hpux_1111_ifconfig_lo0 => hpux/1111_ifconfig_lo0} (100%) rename spec/fixtures/unit/util/ip/{hpux_1111_lanscan => hpux/1111_lanscan} (100%) rename spec/fixtures/unit/util/ip/{hpux_1111_netstat_in => hpux/1111_netstat_in} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_asterisk_ifconfig_lan0 => hpux/1131_asterisk_ifconfig_lan0} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_asterisk_ifconfig_lan1 => hpux/1131_asterisk_ifconfig_lan1} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_asterisk_ifconfig_lo0 => hpux/1131_asterisk_ifconfig_lo0} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_asterisk_lanscan => hpux/1131_asterisk_lanscan} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_asterisk_netstat_in => hpux/1131_asterisk_netstat_in} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_ifconfig_lan0 => hpux/1131_ifconfig_lan0} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_ifconfig_lan1 => hpux/1131_ifconfig_lan1} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_ifconfig_lo0 => hpux/1131_ifconfig_lo0} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_lanscan => hpux/1131_lanscan} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_netstat_in => hpux/1131_netstat_in} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_nic_bonding_ifconfig_lan1 => hpux/1131_nic_bonding_ifconfig_lan1} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_nic_bonding_ifconfig_lan4 => hpux/1131_nic_bonding_ifconfig_lan4} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_nic_bonding_ifconfig_lan4_1 => hpux/1131_nic_bonding_ifconfig_lan4_1} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_nic_bonding_ifconfig_lo0 => hpux/1131_nic_bonding_ifconfig_lo0} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_nic_bonding_lanscan => hpux/1131_nic_bonding_lanscan} (100%) rename spec/fixtures/unit/util/ip/{hpux_1131_nic_bonding_netstat_in => hpux/1131_nic_bonding_netstat_in} (100%) rename spec/fixtures/unit/util/ip/{linux_2_6_35_proc_net_bonding_bond0 => linux/2_6_35_proc_net_bonding_bond0} (100%) rename spec/fixtures/unit/util/ip/{linux_ifconfig_all_with_single_interface => linux/ifconfig_all_with_single_interface} (100%) rename spec/fixtures/unit/util/ip/{linux_get_single_interface_eth0 => linux/ifconfig_single_interface_eth0} (100%) rename spec/fixtures/unit/util/ip/{linux_get_single_interface_lo => linux/ifconfig_single_interface_lo} (100%) rename spec/fixtures/unit/util/ip/{linux_get_single_interface_ib0 => linux/ifconfig_with_single_interface_ib0} (100%) rename spec/fixtures/unit/util/ip/{solaris_ifconfig_all_with_multiple_interfaces => sun_os/ifconfig_all_with_multiple_interfaces} (100%) rename spec/fixtures/unit/util/ip/{solaris_ifconfig_single_interface => sun_os/ifconfig_single_interface} (100%) rename spec/fixtures/unit/util/ip/{windows_netsh_all_interfaces => windows/netsh_all_interfaces} (100%) rename spec/fixtures/unit/util/ip/{windows_netsh_single_interface => windows/netsh_with_single_interface} (100%) rename spec/fixtures/unit/util/ip/{windows_netsh_single_interface6 => windows/netsh_with_single_interface6} (100%) delete mode 100755 spec/unit/interfaces_spec.rb create mode 100644 spec/unit/util/ip/base_spec.rb create mode 100644 spec/unit/util/ip/darwin_spec.rb create mode 100644 spec/unit/util/ip/dragonfly_spec.rb create mode 100644 spec/unit/util/ip/free_bsd_spec.rb create mode 100644 spec/unit/util/ip/gnu_k_free_bsd_spec.rb create mode 100644 spec/unit/util/ip/hpux_spec.rb create mode 100644 spec/unit/util/ip/linux_spec.rb create mode 100644 spec/unit/util/ip/net_bsd_spec.rb create mode 100644 spec/unit/util/ip/open_bsd_spec.rb create mode 100644 spec/unit/util/ip/sun_os_spec.rb create mode 100644 spec/unit/util/ip/windows_spec.rb diff --git a/lib/facter/interfaces.rb b/lib/facter/interfaces.rb index 9b132e1c24..09f97a28bf 100644 --- a/lib/facter/interfaces.rb +++ b/lib/facter/interfaces.rb @@ -1,43 +1,10 @@ -# interfaces.rb -# Try to get additional Facts about the machine's network interfaces -# -# Original concept Copyright (C) 2007 psychedelys -# Update and *BSD support (C) 2007 James Turnbull +# encoding: UTF-8 + +# Fact: interfaces # +# Purpose: +# Try to get facts about the machine's network interfaces require 'facter/util/ip' -# Note that most of this only works on a fixed list of platforms; notably, Darwin -# is missing. - -Facter.add(:interfaces) do - confine :kernel => 'Linux' - has_weight 20 - setcode do - list = Dir.glob('/sys/class/net/*').map do |name| - Facter::Util::IP.alphafy(name.split('/').last) - end - list.empty? ? nil : list.join(',') - end -end - -Facter.add(:interfaces) do - confine :kernel => Facter::Util::IP.supported_platforms - setcode do - Facter::Util::IP.get_interfaces.collect { |iface| Facter::Util::IP.alphafy(iface) }.join(",") - end -end - -Facter::Util::IP.get_interfaces.each do |interface| - - # Make a fact for each detail of each interface. Yay. - # There's no point in confining these facts, since we wouldn't be able to create - # them if we weren't running on a supported platform. - %w{ipaddress ipaddress6 macaddress netmask mtu}.each do |label| - Facter.add(label + "_" + Facter::Util::IP.alphafy(interface)) do - setcode do - Facter::Util::IP.get_interface_value(interface, label) - end - end - end -end +Facter::Util::IP.add_interface_facts diff --git a/lib/facter/network.rb b/lib/facter/network.rb deleted file mode 100644 index 390895a05b..0000000000 --- a/lib/facter/network.rb +++ /dev/null @@ -1,20 +0,0 @@ -# Fact: network -# -# Purpose: -# Get IP, network and netmask information for available network -# interfacs. -# -# Resolution: -# Uses 'facter/util/ip' to enumerate interfaces and return their information. -# -# Caveats: -# -require 'facter/util/ip' - -Facter::Util::IP.get_interfaces.each do |interface| - Facter.add("network_" + Facter::Util::IP.alphafy(interface)) do - setcode do - Facter::Util::IP.get_network_value(interface) - end - end -end diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index 6bed1dd4d0..6d180f8cf8 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -1,296 +1,251 @@ -# A base module for collecting IP-related -# information from all kinds of platforms. -module Facter::Util::IP - # A map of all the different regexes that work for - # a given platform or set of platforms. - REGEX_MAP = { - :linux => { - :ipaddress => /inet (?:addr:)?([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, - :ipaddress6 => /inet6 (?:addr: )?((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/, - :macaddress => /(?:ether|HWaddr)\s+((\w{1,2}:){5,}\w{1,2})/, - :netmask => /Mask:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, - :mtu => /MTU:(\d+)/ - }, - :bsd => { - :aliases => [:openbsd, :netbsd, :freebsd, :darwin, :"gnu/kfreebsd", :dragonfly], - :ipaddress => /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, - :ipaddress6 => /inet6 ((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/, - :macaddress => /(?:ether|lladdr)\s+(\w?\w:\w?\w:\w?\w:\w?\w:\w?\w:\w?\w)/, - :netmask => /netmask\s+0x(\w{8})/, - :mtu => /mtu\s+(\d+)/ - }, - :sunos => { - :ipaddress => /inet\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, - :ipaddress6 => /inet6 ((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/, - :macaddress => /(?:ether|lladdr)\s+(\w?\w:\w?\w:\w?\w:\w?\w:\w?\w:\w?\w)/, - :netmask => /netmask\s+(\w{8})/, - :mtu => /mtu\s+(\d+)/ - }, - :"hp-ux" => { - :ipaddress => /\s+inet (\S+)\s.*/, - :macaddress => /(\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/, - :netmask => /.*\s+netmask (\S+)\s.*/ - }, - :windows => { - :ipaddress => /\s+IP Address:\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, - :ipaddress6 => /Address ((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/, - :netmask => /\s+Subnet Prefix:\s+\S+\s+\(mask ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\)/ - } - } +# encoding: UTF-8 + +require 'facter/util/ip/base' +require 'facter/util/ip/darwin' +require 'facter/util/ip/sun_os' +require 'facter/util/ip/linux' +require 'facter/util/ip/net_bsd' +require 'facter/util/ip/open_bsd' +require 'facter/util/ip/free_bsd' +require 'facter/util/ip/dragonfly' +require 'facter/util/ip/windows' +require 'facter/util/ip/hpux' +require 'facter/util/ip/gnu_k_free_bsd' + +# A base module for collecting IP related info from all kinds of platforms. +class Facter::Util::IP + INTERFACE_KEYS = %w[ipaddress ipaddress6 macaddress netmask mtu] + + attr_accessor :interfaces_hash + + # Uses the interfaces stored in {@interfaces} to obtain and parse the + # attributes corresponding to {INTERFACE_KEYS} and stores the resulting hash + # in {@interfaces_hash}. + # + # @api private + def parse! + @interfaces_hash = @interfaces.inject({}) do |hashA, interface| + hashA[interface] = INTERFACE_KEYS.inject({}) do |hashB, key| + hashB[key] = value_for_interface_and_label interface, key - # Convert an interface name into purely alphanumeric characters. - def self.alphafy(interface) - interface.gsub(/[^a-z0-9_]/i, '_') - end + hashB + end - def self.convert_from_hex?(kernel) - kernels_to_convert = [:sunos, :openbsd, :netbsd, :freebsd, :darwin, :"hp-ux", :"gnu/kfreebsd", :dragonfly] - kernels_to_convert.include?(kernel) - end + hashA[interface][:network] = network interface - def self.supported_platforms - REGEX_MAP.inject([]) do |result, tmp| - key, map = tmp - if map[:aliases] - result += map[:aliases] - else - result << key - end - result + hashA end end - def self.get_interfaces - # Use sysfs on most Linux systems, which is the fastest option. - if File.exist?('/sys/class/net') - return Dir.glob('/sys/class/net/*').map do |name| - Facter::Util::IP.alphafy(name.split('/').last) - end - end + # Adds interface facts like 'eth0'. Also defines dynamic facts describing + # attributes of each interface, like 'ipaddress_eth0' and 'network_eth0'. + # + # @api private + def self.add_interface_facts + model = new - return [] unless output = Facter::Util::IP.get_all_interface_output() + return unless model.kernel_supported? - # windows interface names contain spaces and are quoted and can appear multiple - # times as ipv4 and ipv6 - return output.scan(/\s* connected\s*(\S.*)/).flatten.uniq if Facter.value(:kernel) == 'windows' + model.refresh + model.add_dynamic_interface_facts - # Our regex appears to be stupid, in that it leaves colons sitting - # at the end of interfaces. So, we have to trim those trailing - # characters. I tried making the regex better but supporting all - # platforms with a single regex is probably a bit too much. - output.scan(/^\S+/).collect { |i| i.sub(/:$/, '') }.uniq - end + Facter.add :interfaces do + confine :kernel => model.supported_platforms - def self.get_all_interface_output - case Facter.value(:kernel) - when 'Linux', 'OpenBSD', 'NetBSD', 'FreeBSD', 'Darwin', 'GNU/kFreeBSD', 'DragonFly' - output = Facter::Util::IP.exec_ifconfig(["-a","2>/dev/null"]) - when 'SunOS' - output = Facter::Util::IP.exec_ifconfig(["-a"]) - when 'HP-UX' - # (#17487)[https://projects.puppetlabs.com/issues/17487] - # Handle NIC bonding where asterisks and virtual NICs are printed. - if output = hpux_netstat_in - output.gsub!(/\*/, "") # delete asterisks. - output.gsub!(/^[^\n]*none[^\n]*\n/, "") # delete lines with 'none' instead of IPs. - output.sub!(/^[^\n]*\n/, "") # delete the header line. - output + setcode do + model.refresh if model.flushed? + model.add_dynamic_interface_facts + model.stringified_interfaces end - when 'windows' - output = %x|#{ENV['SYSTEMROOT']}/system32/netsh.exe interface ip show interface| - output += %x|#{ENV['SYSTEMROOT']}/system32/netsh.exe interface ipv6 show interface| + + on_flush { model.flush! } end - output end - ## - # exec_ifconfig uses the ifconfig command + # Convert an interface name into purely alphanumeric characters. + # + # @param [String] interface e.g. 'eth0' + # + # @return [String] + # + # @api private + def self.alphafy(interface) + interface.to_s.gsub(/[^a-z0-9_]/i, '_') + end + + # Returns an array of supported platforms in string format. These array values + # are synonymous with the values returned from Facter.value(:kernel). + # + # @return [Array] contains strings corresponding to a kernel # - # @return [String] the output of `ifconfig #{arguments} 2>/dev/null` or nil + # @api private + def supported_platforms + kernel_classes.map(&:to_s) + end + + # A delegate method to the kernel's subclass ultimately obtaining the + # interfaces. + # + # @return [Array] + # + # @api private + def interfaces + kernel_class.interfaces + end + + # Uses the ifconfig command + # + # @param [Array] additional arguments + # + # @return [String] the output of the command + # + # @api private def self.exec_ifconfig(additional_arguments=[]) Facter::Util::Resolution.exec("#{self.get_ifconfig} #{additional_arguments.join(' ')}") end - ## - # get_ifconfig looks up the ifconfig binary + + # Looks up the ifconfig binary. # # @return [String] path to the ifconfig binary + # + # @api private def self.get_ifconfig common_paths=["/bin/ifconfig","/sbin/ifconfig","/usr/sbin/ifconfig"] common_paths.select{|path| File.executable?(path)}.first end - ## - # hpux_netstat_in is a delegate method that allows us to stub netstat -in - # without stubbing exec. - def self.hpux_netstat_in - Facter::Util::Resolution.exec("/bin/netstat -in") - end - def self.get_infiniband_macaddress(interface) - if File.exists?("/sys/class/net/#{interface}/address") then - ib_mac_address = `cat /sys/class/net/#{interface}/address`.chomp - elsif File.exists?("/sbin/ip") then - ip_output = %x{/sbin/ip link show #{interface}} - ib_mac_address = ip_output.scan(%r{infiniband\s+((\w{1,2}:){5,}\w{1,2})}) - else - ib_mac_address = "FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF" - Facter.debug("ip.rb: nothing under /sys/class/net/#{interface}/address and /sbin/ip not available") - end - ib_mac_address + # A delegate method to `value_for_interface_and_label` which is implemented in + # Facter::Util::IP::Base and it's subclasses. + # + # @param interface [String] label [String] e.g ['eth0', 'MTU'] + # + # @return [String] or [NilClass] + # + # @api private + def value_for_interface_and_label(interface, label) + kernel_class.value_for_interface_and_label interface, label end - def self.ifconfig_interface(interface) - output = Facter::Util::IP.exec_ifconfig([interface,"2>/dev/null"]) + # A delegate method to obtain the network of an interface + # + # @param interface [String] e.g 'eth0' + # + # @return [String] or [NilClass] + # + # @api private + def network(interface) + kernel_class.network(interface) end - def self.get_single_interface_output(interface) - output = "" - case Facter.value(:kernel) - when 'OpenBSD', 'NetBSD', 'FreeBSD', 'Darwin', 'GNU/kFreeBSD', 'DragonFly' - output = Facter::Util::IP.ifconfig_interface(interface) - when 'Linux' - ifconfig_output = Facter::Util::IP.ifconfig_interface(interface) - if interface =~ /^ib/ then - real_mac_address = get_infiniband_macaddress(interface) - output = ifconfig_output.sub(%r{(?:ether|HWaddr)\s+((\w{1,2}:){5,}\w{1,2})}, "HWaddr #{real_mac_address}") - else - output = ifconfig_output - end - when 'SunOS' - output = Facter::Util::IP.exec_ifconfig([interface]) - when 'HP-UX' - mac = "" - ifc = hpux_ifconfig_interface(interface) - hpux_lanscan.scan(/(\dx\S+).*UP\s+(\w+\d+)/).each {|i| mac = i[0] if i.include?(interface) } - mac = mac.sub(/0x(\S+)/,'\1').scan(/../).join(":") - output = ifc + "\n" + mac - end - output + # A delegate method for obtaining Facter::Util::IP::Base's subclasses. + # + # @return [Array] + # + # @api private + def kernel_classes + Facter::Util::IP::Base.subclasses end - def self.hpux_ifconfig_interface(interface) - Facter::Util::IP.exec_ifconfig([interface]) + # Obtains the cooresponding Facter::Util::IP::Base subclass for the current + # kernel. + # + # @return Subclass of [Facter::Util::IP::Base] + # + # @api private + def kernel_class + kernel_classes.find { |klass| klass.to_s == Facter.value(:kernel) } end - def self.hpux_lanscan - Facter::Util::Resolution.exec("/usr/sbin/lanscan") + # Boolean to determine whether the current kernel is supported. + # + # @return [Boolean] true or false + # + # @api private + def kernel_supported? + supported_platforms.include?(Facter.value(:kernel)) end - def self.get_output_for_interface_and_label(interface, label) - return get_single_interface_output(interface) unless Facter.value(:kernel) == 'windows' - - if label == 'ipaddress6' - output = %x|#{ENV['SYSTEMROOT']}/system32/netsh.exe interface ipv6 show address \"#{interface}\"| - else - output = %x|#{ENV['SYSTEMROOT']}/system32/netsh.exe interface ip show address \"#{interface}\"| + # Stringifies interfaces so that it can be used as a fact value. + # + # @return [String] the interfaces as a string + # + # @api private + def stringified_interfaces + alphafied_interfaces = @interfaces.map do |interface| + Facter::Util::IP.alphafy(interface) end - output - end - def self.get_bonding_master(interface) - if Facter.value(:kernel) != 'Linux' - return nil - end - # We need ip instead of ifconfig because it will show us - # the bonding master device. - if not FileTest.executable?("/sbin/ip") - return nil - end - # A bonding interface can never be an alias interface. Alias - # interfaces do have a colon in their name and the ip link show - # command throws an error message when we pass it an alias - # interface. - if interface =~ /:/ - return nil - end - regex = /SLAVE[,>].* (bond[0-9]+)/ - ethbond = regex.match(%x{/sbin/ip link show #{interface}}) - if ethbond - device = ethbond[1] - else - device = nil - end - device + alphafied_interfaces.join ',' end - ## - # get_interface_value obtains the value of a specific attribute of a specific - # interface. - # - # @param interface [String] the interface identifier, e.g. "eth0" or "bond0" - # - # @param label [String] the attribute of the interface to obtain a value for, - # e.g. "netmask" or "ipaddress" + # Defines all of the dynamic interface facts derived from parsing the output + # of the network interface ouput. The interface facts are dynamic, so this + # method has the behavior of figuring out what facts need to be added and how + # they should be resolved. # # @api private - # - # @return [String] representing the requested value. An empty array is - # returned if the kernel is not supported by the REGEX_MAP constant. - def self.get_interface_value(interface, label) - tmp1 = [] + def add_dynamic_interface_facts + model = self - kernel = Facter.value(:kernel).downcase.to_sym + @interfaces.each do |interface| + INTERFACE_KEYS.each do |key| + Facter.add "#{key}_#{model.class.alphafy(interface)}" do + confine :kernel => model.supported_platforms - # If it's not directly in the map or aliased in the map, then we don't know how to deal with it. - unless map = REGEX_MAP[kernel] || REGEX_MAP.values.find { |tmp| tmp[:aliases] and tmp[:aliases].include?(kernel) } - return [] - end + setcode do + model.refresh if model.flushed? - # Pull the correct regex out of the map. - regex = map[label.to_sym] - - # Linux changes the MAC address reported via ifconfig when an ethernet interface - # becomes a slave of a bonding device to the master MAC address. - # We have to dig a bit to get the original/real MAC address of the interface. - bonddev = get_bonding_master(interface) - if label == 'macaddress' and bonddev - bondinfo = read_proc_net_bonding("/proc/net/bonding/#{bonddev}") - re = /^Slave Interface: #{interface}\b.*?\bPermanent HW addr: (([0-9A-F]{2}:?)*)$/im - if match = re.match(bondinfo) - value = match[1].upcase - end - else - output_int = get_output_for_interface_and_label(interface, label) - - output_int.each_line do |s| - if s =~ regex - value = $1 - if label == 'netmask' && convert_from_hex?(kernel) - value = value.scan(/../).collect do |byte| byte.to_i(16) end.join('.') + # Don't resolve if the interface has since been deleted + if keys_hash = model.interfaces_hash[interface] + keys_hash[key] end - tmp1.push(value) + end + + on_flush { model.flush! } end end - if tmp1 - value = tmp1.shift + Facter.add "network_#{interface}" do + confine :kernel => model.supported_platforms + + setcode do + model.refresh if model.flushed? + + # Don't resolve if the interface has since been deleted + if keys_hash = model.interfaces_hash[interface] + keys_hash[:network] + end + end + + on_flush { model.flush! } end end end - ## - # read_proc_net_bonding is a seam method for mocking purposes. - # - # @param path [String] representing the path to read, e.g. "/proc/net/bonding/bond0" + # Executes the platform specific system command to obtain the interfaces and + # stores them in {@interfaces}. # # @api private - # - # @return [String] modeling the raw file read - def self.read_proc_net_bonding(path) - File.read(path) if File.exists?(path) - end - private_class_method :read_proc_net_bonding + def refresh + @interfaces = interfaces - def self.get_network_value(interface) - require 'ipaddr' + parse! + end - ipaddress = get_interface_value(interface, "ipaddress") - netmask = get_interface_value(interface, "netmask") + # Checks to see if the intstance has been flushed. + # + # @return [Boolean] true if there is no parsed data + # + # @api private + def flushed? + !interfaces_hash + end - if ipaddress && netmask - ip = IPAddr.new(ipaddress, Socket::AF_INET) - subnet = IPAddr.new(netmask, Socket::AF_INET) - network = ip.mask(subnet.to_s).to_s - end + # Purges the saved data so that the fact can be resolved properly upon flush. + # + # @api private + def flush! + @interfaces_hash = nil end end diff --git a/lib/facter/util/ip/base.rb b/lib/facter/util/ip/base.rb new file mode 100644 index 0000000000..eaa3faa061 --- /dev/null +++ b/lib/facter/util/ip/base.rb @@ -0,0 +1,196 @@ +# encoding: UTF-8 + +require 'ipaddr' + +module Facter + module Util + class IP + end + end +end + +class Facter::Util::IP::Base + # A regex to match an IPv4 address from `ifconfig` output. This regex will + # work for most platforms. You can override this in your subclass if you need + # a different regex. + # + # @return [Regexp] + # + # @api private + IPADDRESS_REGEX = /inet.*?([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + + + # A regex to match an IPv6 address from `ifconfig` output. This regex will + # work for most platforms. You can override this in your subclass if you need + # a different regex. + # + # @return [Regexp] + # + # @api private + IPADDRESS6_REGEX = / + inet6.*?((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4}) + /x + + # A regex to match a MAC address from `ifconfig` output. This regex will work + # for most platforms. You can override this in your subclass if you need a + # different regex. + # + # @return [Regexp] + # + # @api private + MACADDRESS_REGEX = /(?:ether|lladdr)\s+(\w?\w:\w?\w:\w?\w:\w?\w:\w?\w:\w?\w)/ + + # A regex to match the netmask from `ifconfig` output. This regex will work + # for most platforms. You can override this in your subclass if you need a + # different regex. + # + # @return [Regexp] + # + # @api private + NETMASK_REGEX = /netmask\s+0x(\w{8})/ + + # A regex to match the MTU from `ifconfig` output. This regex will work for + # most platforms. You can override this in your subclass if you need a + # different regex. + # + # @return [Regexp] + # + # @api private + MTU_REGEX = /mtu\s+(\d+)/ + + # Returns the name of the Class without nesting. Mostly used for finding + # the right class corresponding to the value of Facter.value(:kernel) + # + # @return [String] The string without nesting. + # + # @api private + def self.to_s + super.split('::').last + end + + # Used in conjunction with the `.inherited` hook, this method will store + # an Array of the Class' subclasses. + # + # @return [Array] The subclasses. + # + # @api private + def self.subclasses + @subclasses ||= [] + end + + # Most kernels will need to have their netmask converted from hex. If + # your kernel doesn't display the netmask in hex, you'll need to + # override this method in your subclass to return false. + # + # @return [Boolean] true + # + # @api private + def self.convert_netmask_from_hex? + true + end + + # Network bonding is creation of a single bonded interface by combining 2 or + # more Ethernet interfaces. I think this is mostly used in Linux so this base + # method will return nil, however you should override this in your subclass if + # need be. See the Facter::Util::IP::Linux.bonding_master method. + # + # @return [NilClass] + # + # @api private + def self.bonding_master(interface) + end + + # Returns an array of interfaces from `ifconfig`. e.g. ['eth0', 'eth1'] This + # will work on most platforms, but override in your subclass if need be. + # + # @return [Array] + # + # @api private + def self.interfaces + exec("#{ifconfig_path} -a 2> /dev/null").to_s.scan(/^\w+/).uniq + end + + # Get the value of an interface and label. For example, you may want to find + # the MTU for eth0. + # + # @param interface [String] label [String] and optional command [String] + # + # @return [String] or [NilClass] + # + # @api private + def self.value_for_interface_and_label(interface, label, cmd = nil) + if regex = regex_for(label) + cmd ||= "#{ifconfig_path} #{interface} 2> /dev/null" + + if match = regex.match(exec(cmd).to_s) + if label == 'netmask' && convert_netmask_from_hex? + match[1].scan(/../).map { |byte| byte.to_i(16) }.join('.') + else + match[1] + end + end + end + end + + # Returns the IP network for the given interface. + # + # @return [String] or [NilClass] + # + # @api private + def self.network(interface) + ipaddress = value_for_interface_and_label(interface, "ipaddress") + netmask = value_for_interface_and_label(interface, "netmask") + + if ipaddress && netmask + ip = IPAddr.new(ipaddress, Socket::AF_INET) + subnet = IPAddr.new(netmask, Socket::AF_INET) + + ip.mask(subnet.to_s).to_s + end + end + + # This is a Ruby hook method that will help to maintain a list of + # subclasses. See the `.subclasses` method for more information. + # + # @return [Array] The subclasses. + # + # @api private + def self.inherited subclass + subclasses << subclass + end + + # This loops over `ifconfig` paths to find the first that is executable. + # + # @return [String] + # + # @api private + def self.ifconfig_path + %w[/bin/ifconfig /sbin/ifconfig /usr/sbin/ifconfig].find do |path| + File.executable?(path) + end + end + + # Delegation method to Facter::Util::Resolution.exec. + # + # @param command [String] the command to execute + # + # @return [String] or [Nil] + # + # @api private + def self.exec string + Facter::Util::Resolution.exec string + end + + # Grabs the corresponding regex constant. e.g. NETMASK_REGEX + # + # @param label [String] e.g. 'netmask' + # + # @return [Regexp] or [NilClass] + # + # @api private + def self.regex_for label + constant = "#{label.to_s.upcase}_REGEX" + + const_get(constant) if constants.find { |c| /^#{constant}$/.match(c) } + end +end diff --git a/lib/facter/util/ip/darwin.rb b/lib/facter/util/ip/darwin.rb new file mode 100644 index 0000000000..83c95fc791 --- /dev/null +++ b/lib/facter/util/ip/darwin.rb @@ -0,0 +1,6 @@ +# encoding: UTF-8 + +require 'facter/util/ip/base' + +class Facter::Util::IP::Darwin < Facter::Util::IP::Base +end diff --git a/lib/facter/util/ip/dragonfly.rb b/lib/facter/util/ip/dragonfly.rb new file mode 100644 index 0000000000..0ccf87c359 --- /dev/null +++ b/lib/facter/util/ip/dragonfly.rb @@ -0,0 +1,6 @@ +# encoding: UTF-8 + +require 'facter/util/ip/base' + +class Facter::Util::IP::Dragonfly < Facter::Util::IP::Base +end diff --git a/lib/facter/util/ip/free_bsd.rb b/lib/facter/util/ip/free_bsd.rb new file mode 100644 index 0000000000..6db3942c39 --- /dev/null +++ b/lib/facter/util/ip/free_bsd.rb @@ -0,0 +1,6 @@ +# encoding: UTF-8 + +require 'facter/util/ip/base' + +class Facter::Util::IP::FreeBSD < Facter::Util::IP::Base +end diff --git a/lib/facter/util/ip/gnu_k_free_bsd.rb b/lib/facter/util/ip/gnu_k_free_bsd.rb new file mode 100644 index 0000000000..5988a1562c --- /dev/null +++ b/lib/facter/util/ip/gnu_k_free_bsd.rb @@ -0,0 +1,9 @@ +# encoding: UTF-8 + +require 'facter/util/ip/base' + +class Facter::Util::IP::GNUkFreeBSD < Facter::Util::IP::Base + def self.to_s + 'GNU/kFreeBSD' + end +end diff --git a/lib/facter/util/ip/hpux.rb b/lib/facter/util/ip/hpux.rb new file mode 100644 index 0000000000..3485b41d36 --- /dev/null +++ b/lib/facter/util/ip/hpux.rb @@ -0,0 +1,76 @@ +# encoding: UTF-8 + +require 'facter/util/ip/base' + +class Facter::Util::IP::HPUX < Facter::Util::IP::Base + # A regex to match an IPv4 address from `ifconfig` output. + # + # @return [Regexp] + # + # @api private + IPADDRESS_REGEX = /\s+inet (\S+)\s.*/ + + # A regex to match a MAC address from `ifconfig` output. + # + # @return [Regexp] + # + # @api private + MACADDRESS_REGEX = /(\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/ + + # A regex to match the netmask from `ifconfig` output. + # + # @return [Regexp] + # + # @api private + NETMASK_REGEX = /.*\s+netmask (\S+)\s.*/ + + # The path to the `lanscan` executable. + # + # @return [Regexp] + # + # @api private + LANSCAN = '/usr/sbin/lanscan' + + def self.to_s + 'HP-UX' + end + + # Gets an array of interfaces from `netstat`. The cryptic text replacements in + # the method handles NIC bonding where asterisks and virtual NICs are printed. + # See (#17487)[https://projects.puppetlabs.com/issues/17487] for more info. + # + # @return [Array] + # + # @api private + def self.interfaces + exec("/bin/netstat -in"). + to_s. + gsub(/\*/, ""). + gsub(/^[^\n]*none[^\n]*\n/, ""). + sub(/^[^\n]*\n/, ""). + scan(/^\w+/) + end + + def self.value_for_interface_and_label(interface, label) + value = super(interface, label) + + if !value && label == 'macaddress' + if macaddress = lanscan.to_s[/\dx(\S+).*UP\s+#{interface}/, 1] + macaddress.scan(/../).join(':') + end + else + value + end + end + + private + + # Execute lanscan. + # + # @return [String] or [NilClass] + # + # @api private + def self.lanscan + exec(LANSCAN) if File.exist?(LANSCAN) + end +end diff --git a/lib/facter/util/ip/linux.rb b/lib/facter/util/ip/linux.rb new file mode 100644 index 0000000000..45fee9f310 --- /dev/null +++ b/lib/facter/util/ip/linux.rb @@ -0,0 +1,169 @@ +# encoding: UTF-8 + +require 'facter/util/ip/base' + +class Facter::Util::IP::Linux < Facter::Util::IP::Base + # A regex to match an IPv4 address from `ifconfig` output. + # + # @return [Regexp] + # + # @api private + IPADDRESS_REGEX = /inet\s(?:addr:)?([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + + # A regex to match an IPv6 address from `ifconfig` output. + # + # @return [Regexp] + # + # @api private + IPADDRESS6_REGEX = /inet6\s(?:addr: )?((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/ + + + # A regex to match a MAC address from `ifconfig` output. + # + # @return [Regexp] + # + # @api private + MACADDRESS_REGEX = /(?:ether|HWaddr)\s+((\w{1,2}:){5,}\w{1,2})/ + + # A regex to match the netmask from `ifconfig` output. + # + # @return [Regexp] + # + # @api private + NETMASK_REGEX = /Mask:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + + # A regex to match the MTU from `ifconfig` output. + # + # @return [Regexp] + # + # @api private + MTU_REGEX = /MTU:(\d+)/ + + # Linux doesn't display netmask in hex. + # + # @return [Boolean] false by default + # + # @api private + def self.convert_netmask_from_hex? + false + end + + # Network bonding is creation of a single bonded interface by combining 2 or + # more Ethernet interfaces. This method returns the bonding master. + # + # @param interface [String] the interface, e.g. 'eth0' + # + # @return [String] or [NilClass] + # + # @api private + def self.bonding_master(interface) + # We need `ip` instead of `ifconfig` because it shows us the bonding master. + return unless FileTest.executable?("/sbin/ip") + + # A bonding interface can never be an alias interface. Alias interfaces do + # have a colon in their name and the ip link show command throws an error + # message when we pass it an alias interface. + return if interface.match(/:/) + + regex = /SLAVE[,>].* (bond[0-9]+)/ + ethbond = regex.match(%x{/sbin/ip link show #{interface}}) + + ethbond[1] if ethbond + end + + # Returns an array of interfaces. e.g. ['eth0', 'eth1'] We will check sysfs + # first, since that is the fastest option, but fallback to `ifconfig` if + # neccessary. + # + # @return [Array] + # + # @api private + def self.interfaces + if File.exist?('/sys/class/net') + Dir.glob('/sys/class/net/*').map do |name| + name.split('/').last + end + else + super + end + end + + # Get the value of an interface and label. For example, you may want to find + # the MTU for eth0. If an infiniband interface is passed, it will try to + # obtain the real value. + # + # @param interface [String] label [String] and optional command [String] + # + # @return [String] or [NilClass] + # + # @api private + def self.value_for_interface_and_label(interface, label) + if label == 'macaddress' + bonddev = bonding_master(interface) + + if infiniband?(interface) + infiniband_macaddress(interface) || super + elsif bonddev + bonddev_macaddress(bonddev, interface) || super + else + super + end + else + super + end + end + + private + + # Boolean method to test whether an interface is the infiniband. + # + # @param interface [String] e.g. 'ib0' + # + # @return [Boolean] true or false + # + # @api private + def self.infiniband?(interface) + !!/^ib/.match(interface) + end + + # Attempts to obtain the real macaddress for an infiniband interface. + # + # @param interface [String] e.g. 'ib0' + # + # @return [String] or [NilClass] + # + # @api private + def self.infiniband_macaddress(interface) + sysfs = "/sys/class/net/#{interface}/address" + + if File.exists?(sysfs) + exec("cat #{sysfs}") + elsif File.exists?("/sbin/ip") + exec("/sbin/ip link show #{interface}"). + to_s. + scan(%r{infiniband\s+((\w{1,2}:){5,}\w{1,2})})[0] + end + end + + # Attempts to obtain the real macaddress for a bonded interface. + # + # @param bonddev [String] interface [String] e.g. 'bond0', 'eth0' + # + # @return [String] or [NilClass] + # + # @api private + def self.bonddev_macaddress(bonddev, interface) + path = "/proc/net/bonding/#{bonddev}" + + if File.exists?(path) + bondinfo = File.read(path) + regex = / + ^Slave\sInterface:\s + #{interface}\b.*?\bPermanent\sHW\saddr:\s(([0-9A-F]{2}:?)*)$ + /imx + match = regex.match(bondinfo) + + match[1].upcase if match + end + end +end diff --git a/lib/facter/util/ip/net_bsd.rb b/lib/facter/util/ip/net_bsd.rb new file mode 100644 index 0000000000..3b97c9c158 --- /dev/null +++ b/lib/facter/util/ip/net_bsd.rb @@ -0,0 +1,6 @@ +# encoding: UTF-8 + +require 'facter/util/ip/base' + +class Facter::Util::IP::NetBSD < Facter::Util::IP::Base +end diff --git a/lib/facter/util/ip/open_bsd.rb b/lib/facter/util/ip/open_bsd.rb new file mode 100644 index 0000000000..3204a70194 --- /dev/null +++ b/lib/facter/util/ip/open_bsd.rb @@ -0,0 +1,6 @@ +# encoding: UTF-8 + +require 'facter/util/ip/base' + +class Facter::Util::IP::OpenBSD < Facter::Util::IP::Base +end diff --git a/lib/facter/util/ip/sun_os.rb b/lib/facter/util/ip/sun_os.rb new file mode 100644 index 0000000000..5533247ab6 --- /dev/null +++ b/lib/facter/util/ip/sun_os.rb @@ -0,0 +1,24 @@ +# encoding: UTF-8 + +require 'facter/util/ip/base' + +class Facter::Util::IP::SunOS < Facter::Util::IP::Base + # A regex to match the netmask from `ifconfig` output. + # + # @return [Regexp] + # + # @api private + NETMASK_REGEX = /netmask\s(\w{8})/ + + # Get the value of an interface and label. For example, you may want to find + # the MTU for eth0. + # + # @param interface [String] label [String] e.g ['eth0', 'MTU'] + # + # @return [String] or [NilClass] + # + # @api private + def self.value_for_interface_and_label(interface, label) + super(interface, label, "#{ifconfig_path} #{interface}") + end +end diff --git a/lib/facter/util/ip/windows.rb b/lib/facter/util/ip/windows.rb new file mode 100644 index 0000000000..52e6d623b7 --- /dev/null +++ b/lib/facter/util/ip/windows.rb @@ -0,0 +1,73 @@ +# encoding: UTF-8 + +require 'facter/util/ip/base' + +class Facter::Util::IP::Windows < Facter::Util::IP::Base + # A regex to match an IPv4 address from `ifconfig` output. + # + # @return [Regexp] + # + # @api private + IPADDRESS_REGEX = /\s+IP\sAddress:\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ + + # A regex to match an IPv6 address from `ifconfig` output. + # + # @return [Regexp] + # + # @api private + IPADDRESS6_REGEX = /Address\s((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/ + + # A regex to match the netmask from `ifconfig` output. + # + # @return [Regexp] + # + # @api private + NETMASK_REGEX = /\s+Subnet\sPrefix:\s+\S+\s+\(mask\s([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\)/ + + # The path to netsh.exe. + # + # @return [String] + # + # @api private + NETSH = "#{ENV['SYSTEMROOT']}/system32/netsh.exe" + + def self.to_s + 'windows' + end + + # Windows doesn't display netmask in hex. + # + # @return [Boolean] false by default + # + # @api private + def self.convert_netmask_from_hex? + false + end + + # Uses netsh.exe to obtain a list of interfaces. + # + # @return [Array] + # + # @api private + def self.interfaces + cmd = "#{NETSH} interface %s show interface" + output = exec("#{cmd % 'ip'} && #{cmd % 'ipv6'}").to_s + + output.scan(/\s* connected\s*(\S.*)/).flatten.uniq + end + + # Get the value of an interface and label. For example, you may want to find + # the MTU for eth0. Uses netsh.exe. + # + # @param interface [String] label [String] + # + # @return [String] or [NilClass] + # + # @api private + def self.value_for_interface_and_label(interface, label) + opt = label == 'ipaddress6' ? 'ipv6' : 'ip' + cmd = "#{NETSH} interface #{opt} show address \"#{interface}\"" + + super(interface, label, cmd) + end +end diff --git a/spec/fixtures/unit/util/ip/darwin_ifconfig_all_with_multiple_interfaces b/spec/fixtures/unit/util/ip/darwin/ifconfig_all_with_multiple_interfaces similarity index 100% rename from spec/fixtures/unit/util/ip/darwin_ifconfig_all_with_multiple_interfaces rename to spec/fixtures/unit/util/ip/darwin/ifconfig_all_with_multiple_interfaces diff --git a/spec/fixtures/unit/util/ip/darwin/ifconfig_with_single_interface b/spec/fixtures/unit/util/ip/darwin/ifconfig_with_single_interface new file mode 100644 index 0000000000..6a4e10326c --- /dev/null +++ b/spec/fixtures/unit/util/ip/darwin/ifconfig_with_single_interface @@ -0,0 +1,6 @@ +en0: flags=8863 mtu 1500 + inet6 fe80::223:6cff:fe99:602b%en1 prefixlen 64 scopeid 0x5 + inet 192.168.0.10 netmask 0xffffff00 broadcast 192.168.0.255 + ether 00:23:6c:99:60:2b + media: autoselect status: active + supported media: autoselect diff --git a/spec/fixtures/unit/util/ip/6.0-STABLE_FreeBSD_ifconfig b/spec/fixtures/unit/util/ip/free_bsd/6.0-STABLE_ifconfig_with_multiple_interfaces similarity index 100% rename from spec/fixtures/unit/util/ip/6.0-STABLE_FreeBSD_ifconfig rename to spec/fixtures/unit/util/ip/free_bsd/6.0-STABLE_ifconfig_with_multiple_interfaces diff --git a/spec/fixtures/unit/util/ip/free_bsd/6.0-STABLE_ifconfig_with_single_interface b/spec/fixtures/unit/util/ip/free_bsd/6.0-STABLE_ifconfig_with_single_interface new file mode 100644 index 0000000000..b1324be986 --- /dev/null +++ b/spec/fixtures/unit/util/ip/free_bsd/6.0-STABLE_ifconfig_with_single_interface @@ -0,0 +1,10 @@ +fxp0: flags=8843 mtu 1500 + options=b + inet x.x.58.26 netmask 0xfffffff8 broadcast x.x.58.31 + inet x.x.58.27 netmask 0xffffffff broadcast x.x.58.27 + inet x.x.58.28 netmask 0xffffffff broadcast x.x.58.28 + inet x.x.58.29 netmask 0xffffffff broadcast x.x.58.29 + inet x.x.58.30 netmask 0xffffffff broadcast x.x.58.30 + ether 00:0e:0c:68:67:7c + media: Ethernet autoselect (100baseTX ) + status: active diff --git a/spec/fixtures/unit/util/ip/debian_kfreebsd_ifconfig b/spec/fixtures/unit/util/ip/gnu_k_free_bsd/ifconfig_all_with_multiple_interfaces similarity index 100% rename from spec/fixtures/unit/util/ip/debian_kfreebsd_ifconfig rename to spec/fixtures/unit/util/ip/gnu_k_free_bsd/ifconfig_all_with_multiple_interfaces diff --git a/spec/fixtures/unit/util/ip/gnu_k_free_bsd/ifconfig_with_single_interface b/spec/fixtures/unit/util/ip/gnu_k_free_bsd/ifconfig_with_single_interface new file mode 100644 index 0000000000..b9ce459282 --- /dev/null +++ b/spec/fixtures/unit/util/ip/gnu_k_free_bsd/ifconfig_with_single_interface @@ -0,0 +1,8 @@ +em1: flags=8843 metric 0 mtu 1500 + options=209b + ether 0:11:a:59:67:91 + inet6 fe80::211:aff:fe59:6791%em1 prefixlen 64 scopeid 0x2 + inet 192.168.10.10 netmask 0xffffff00 broadcast 192.168.10.255 + nd6 options=3 + media: Ethernet autoselect (100baseTX ) + status: active diff --git a/spec/fixtures/unit/util/ip/hpux_1111_ifconfig_lan0 b/spec/fixtures/unit/util/ip/hpux/1111_ifconfig_lan0 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1111_ifconfig_lan0 rename to spec/fixtures/unit/util/ip/hpux/1111_ifconfig_lan0 diff --git a/spec/fixtures/unit/util/ip/hpux_1111_ifconfig_lan1 b/spec/fixtures/unit/util/ip/hpux/1111_ifconfig_lan1 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1111_ifconfig_lan1 rename to spec/fixtures/unit/util/ip/hpux/1111_ifconfig_lan1 diff --git a/spec/fixtures/unit/util/ip/hpux_1111_ifconfig_lo0 b/spec/fixtures/unit/util/ip/hpux/1111_ifconfig_lo0 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1111_ifconfig_lo0 rename to spec/fixtures/unit/util/ip/hpux/1111_ifconfig_lo0 diff --git a/spec/fixtures/unit/util/ip/hpux_1111_lanscan b/spec/fixtures/unit/util/ip/hpux/1111_lanscan similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1111_lanscan rename to spec/fixtures/unit/util/ip/hpux/1111_lanscan diff --git a/spec/fixtures/unit/util/ip/hpux_1111_netstat_in b/spec/fixtures/unit/util/ip/hpux/1111_netstat_in similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1111_netstat_in rename to spec/fixtures/unit/util/ip/hpux/1111_netstat_in diff --git a/spec/fixtures/unit/util/ip/hpux_1131_asterisk_ifconfig_lan0 b/spec/fixtures/unit/util/ip/hpux/1131_asterisk_ifconfig_lan0 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_asterisk_ifconfig_lan0 rename to spec/fixtures/unit/util/ip/hpux/1131_asterisk_ifconfig_lan0 diff --git a/spec/fixtures/unit/util/ip/hpux_1131_asterisk_ifconfig_lan1 b/spec/fixtures/unit/util/ip/hpux/1131_asterisk_ifconfig_lan1 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_asterisk_ifconfig_lan1 rename to spec/fixtures/unit/util/ip/hpux/1131_asterisk_ifconfig_lan1 diff --git a/spec/fixtures/unit/util/ip/hpux_1131_asterisk_ifconfig_lo0 b/spec/fixtures/unit/util/ip/hpux/1131_asterisk_ifconfig_lo0 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_asterisk_ifconfig_lo0 rename to spec/fixtures/unit/util/ip/hpux/1131_asterisk_ifconfig_lo0 diff --git a/spec/fixtures/unit/util/ip/hpux_1131_asterisk_lanscan b/spec/fixtures/unit/util/ip/hpux/1131_asterisk_lanscan similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_asterisk_lanscan rename to spec/fixtures/unit/util/ip/hpux/1131_asterisk_lanscan diff --git a/spec/fixtures/unit/util/ip/hpux_1131_asterisk_netstat_in b/spec/fixtures/unit/util/ip/hpux/1131_asterisk_netstat_in similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_asterisk_netstat_in rename to spec/fixtures/unit/util/ip/hpux/1131_asterisk_netstat_in diff --git a/spec/fixtures/unit/util/ip/hpux_1131_ifconfig_lan0 b/spec/fixtures/unit/util/ip/hpux/1131_ifconfig_lan0 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_ifconfig_lan0 rename to spec/fixtures/unit/util/ip/hpux/1131_ifconfig_lan0 diff --git a/spec/fixtures/unit/util/ip/hpux_1131_ifconfig_lan1 b/spec/fixtures/unit/util/ip/hpux/1131_ifconfig_lan1 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_ifconfig_lan1 rename to spec/fixtures/unit/util/ip/hpux/1131_ifconfig_lan1 diff --git a/spec/fixtures/unit/util/ip/hpux_1131_ifconfig_lo0 b/spec/fixtures/unit/util/ip/hpux/1131_ifconfig_lo0 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_ifconfig_lo0 rename to spec/fixtures/unit/util/ip/hpux/1131_ifconfig_lo0 diff --git a/spec/fixtures/unit/util/ip/hpux_1131_lanscan b/spec/fixtures/unit/util/ip/hpux/1131_lanscan similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_lanscan rename to spec/fixtures/unit/util/ip/hpux/1131_lanscan diff --git a/spec/fixtures/unit/util/ip/hpux_1131_netstat_in b/spec/fixtures/unit/util/ip/hpux/1131_netstat_in similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_netstat_in rename to spec/fixtures/unit/util/ip/hpux/1131_netstat_in diff --git a/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lan1 b/spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_ifconfig_lan1 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lan1 rename to spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_ifconfig_lan1 diff --git a/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lan4 b/spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_ifconfig_lan4 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lan4 rename to spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_ifconfig_lan4 diff --git a/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lan4_1 b/spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_ifconfig_lan4_1 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lan4_1 rename to spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_ifconfig_lan4_1 diff --git a/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lo0 b/spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_ifconfig_lo0 similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_ifconfig_lo0 rename to spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_ifconfig_lo0 diff --git a/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_lanscan b/spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_lanscan similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_lanscan rename to spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_lanscan diff --git a/spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_netstat_in b/spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_netstat_in similarity index 100% rename from spec/fixtures/unit/util/ip/hpux_1131_nic_bonding_netstat_in rename to spec/fixtures/unit/util/ip/hpux/1131_nic_bonding_netstat_in diff --git a/spec/fixtures/unit/util/ip/linux_2_6_35_proc_net_bonding_bond0 b/spec/fixtures/unit/util/ip/linux/2_6_35_proc_net_bonding_bond0 similarity index 100% rename from spec/fixtures/unit/util/ip/linux_2_6_35_proc_net_bonding_bond0 rename to spec/fixtures/unit/util/ip/linux/2_6_35_proc_net_bonding_bond0 diff --git a/spec/fixtures/unit/util/ip/linux_ifconfig_all_with_single_interface b/spec/fixtures/unit/util/ip/linux/ifconfig_all_with_single_interface similarity index 100% rename from spec/fixtures/unit/util/ip/linux_ifconfig_all_with_single_interface rename to spec/fixtures/unit/util/ip/linux/ifconfig_all_with_single_interface diff --git a/spec/fixtures/unit/util/ip/linux_get_single_interface_eth0 b/spec/fixtures/unit/util/ip/linux/ifconfig_single_interface_eth0 similarity index 100% rename from spec/fixtures/unit/util/ip/linux_get_single_interface_eth0 rename to spec/fixtures/unit/util/ip/linux/ifconfig_single_interface_eth0 diff --git a/spec/fixtures/unit/util/ip/linux_get_single_interface_lo b/spec/fixtures/unit/util/ip/linux/ifconfig_single_interface_lo similarity index 100% rename from spec/fixtures/unit/util/ip/linux_get_single_interface_lo rename to spec/fixtures/unit/util/ip/linux/ifconfig_single_interface_lo diff --git a/spec/fixtures/unit/util/ip/linux_get_single_interface_ib0 b/spec/fixtures/unit/util/ip/linux/ifconfig_with_single_interface_ib0 similarity index 100% rename from spec/fixtures/unit/util/ip/linux_get_single_interface_ib0 rename to spec/fixtures/unit/util/ip/linux/ifconfig_with_single_interface_ib0 diff --git a/spec/fixtures/unit/util/ip/solaris_ifconfig_all_with_multiple_interfaces b/spec/fixtures/unit/util/ip/sun_os/ifconfig_all_with_multiple_interfaces similarity index 100% rename from spec/fixtures/unit/util/ip/solaris_ifconfig_all_with_multiple_interfaces rename to spec/fixtures/unit/util/ip/sun_os/ifconfig_all_with_multiple_interfaces diff --git a/spec/fixtures/unit/util/ip/solaris_ifconfig_single_interface b/spec/fixtures/unit/util/ip/sun_os/ifconfig_single_interface similarity index 100% rename from spec/fixtures/unit/util/ip/solaris_ifconfig_single_interface rename to spec/fixtures/unit/util/ip/sun_os/ifconfig_single_interface diff --git a/spec/fixtures/unit/util/ip/windows_netsh_all_interfaces b/spec/fixtures/unit/util/ip/windows/netsh_all_interfaces similarity index 100% rename from spec/fixtures/unit/util/ip/windows_netsh_all_interfaces rename to spec/fixtures/unit/util/ip/windows/netsh_all_interfaces diff --git a/spec/fixtures/unit/util/ip/windows_netsh_single_interface b/spec/fixtures/unit/util/ip/windows/netsh_with_single_interface similarity index 100% rename from spec/fixtures/unit/util/ip/windows_netsh_single_interface rename to spec/fixtures/unit/util/ip/windows/netsh_with_single_interface diff --git a/spec/fixtures/unit/util/ip/windows_netsh_single_interface6 b/spec/fixtures/unit/util/ip/windows/netsh_with_single_interface6 similarity index 100% rename from spec/fixtures/unit/util/ip/windows_netsh_single_interface6 rename to spec/fixtures/unit/util/ip/windows/netsh_with_single_interface6 diff --git a/spec/unit/blockdevices_spec.rb b/spec/unit/blockdevices_spec.rb index 9c67f5ea13..0061b92e45 100644 --- a/spec/unit/blockdevices_spec.rb +++ b/spec/unit/blockdevices_spec.rb @@ -1,5 +1,3 @@ -#!/usr/bin/env ruby - require 'spec_helper' require 'facter' require 'facter/util/blockdevices' diff --git a/spec/unit/hardwareisa_spec.rb b/spec/unit/hardwareisa_spec.rb index a709e8c742..d613026251 100755 --- a/spec/unit/hardwareisa_spec.rb +++ b/spec/unit/hardwareisa_spec.rb @@ -1,5 +1,3 @@ -#!/usr/bin/env ruby - require 'spec_helper' require 'facter' diff --git a/spec/unit/hardwaremodel_spec.rb b/spec/unit/hardwaremodel_spec.rb index a168c11ef5..e9ef3d40de 100644 --- a/spec/unit/hardwaremodel_spec.rb +++ b/spec/unit/hardwaremodel_spec.rb @@ -1,5 +1,3 @@ -#!/usr/bin/env ruby - require 'spec_helper' require 'facter' diff --git a/spec/unit/interfaces_spec.rb b/spec/unit/interfaces_spec.rb deleted file mode 100755 index c495a499eb..0000000000 --- a/spec/unit/interfaces_spec.rb +++ /dev/null @@ -1,68 +0,0 @@ -#! /usr/bin/env ruby - -require 'spec_helper' -require 'facter/util/ip' - -shared_examples_for "iface specific ifconfig output" do |platform, address, fixture| - it "correctly on #{platform} for eth0" do - Facter::Util::IP.stubs(:ifconfig_interface).returns(my_fixture_read(fixture)) - subject.value.should == address - end -end - -describe "Per Interface IP facts" do - it "should replace the ':' in an interface list with '_'" do - # So we look supported - Facter.fact(:kernel).stubs(:value).returns("SunOS") - - Facter::Util::IP.stubs(:get_interfaces).returns %w{eth0:1 eth1:2} - Facter.fact(:interfaces).value.should == %{eth0_1,eth1_2} - end - - it "should replace non-alphanumerics in an interface list with '_'" do - Facter.fact(:kernel).stubs(:value).returns("windows") - - Facter::Util::IP.stubs(:get_interfaces).returns ["Local Area Connection", "Loopback \"Pseudo-Interface\" (#1)"] - Facter.fact(:interfaces).value.should == %{Local_Area_Connection,Loopback__Pseudo_Interface____1_} - end -end - - -RSpec.configure do |config| - config.alias_it_should_behave_like_to :example_behavior_for, "parses" -end - - -describe "the ipaddress_$iface fact" do - subject do - Facter.collection.internal_loader.load(:interfaces) - Facter.fact(:ipaddress_eth0) - end - - context "on Linux" do - before :each do - Facter::Util::IP.stubs(:get_interfaces).returns(["eth0"]) - Facter.fact(:kernel).stubs(:value).returns("Linux") - end - example_behavior_for "iface specific ifconfig output", "Fedora 18", "10.10.220.1", "eth0_net_tools_2.0.txt" - - example_behavior_for "iface specific ifconfig output", "net_tools 1.60", "10.10.220.210", "eth0_net_tools_1.60.txt" - end -end - -describe "the ipaddress6_$iface fact" do - subject do - Facter.collection.internal_loader.load(:interfaces) - Facter.fact(:ipaddress6_eth0) - end - - context "on Linux" do - before :each do - Facter::Util::IP.stubs(:get_interfaces).returns(["eth0"]) - Facter.fact(:kernel).stubs(:value).returns("Linux") - end - example_behavior_for "iface specific ifconfig output", "Fedora 18", "dead::21f:bcff:fe0d:5fb1", "eth0_net_tools_2.0.txt" - - example_behavior_for "iface specific ifconfig output", "net_tools 1.60", "dead::216:3eff:fe7d:ec7e", "eth0_net_tools_1.60.txt" - end -end diff --git a/spec/unit/ldom_spec.rb b/spec/unit/ldom_spec.rb index f97d4a8765..27ec932278 100644 --- a/spec/unit/ldom_spec.rb +++ b/spec/unit/ldom_spec.rb @@ -1,5 +1,3 @@ -#!/usr/bin/env ruby - require 'spec_helper' def ldom_fixtures(filename) diff --git a/spec/unit/manufacturer_spec.rb b/spec/unit/manufacturer_spec.rb index cacf470f27..f18bb9b0c3 100644 --- a/spec/unit/manufacturer_spec.rb +++ b/spec/unit/manufacturer_spec.rb @@ -1,5 +1,3 @@ -#!/usr/bin/env ruby - require 'spec_helper' require 'facter' require 'facter/util/manufacturer' diff --git a/spec/unit/uniqueid_spec.rb b/spec/unit/uniqueid_spec.rb index e9870d39fd..3ac9bbf49d 100755 --- a/spec/unit/uniqueid_spec.rb +++ b/spec/unit/uniqueid_spec.rb @@ -1,5 +1,3 @@ -#!/usr/bin/env ruby - require 'spec_helper' require 'facter' diff --git a/spec/unit/util/directory_loader_spec.rb b/spec/unit/util/directory_loader_spec.rb index 79ad2b2cd5..de8f3ca5aa 100644 --- a/spec/unit/util/directory_loader_spec.rb +++ b/spec/unit/util/directory_loader_spec.rb @@ -1,7 +1,4 @@ -#!/usr/bin/env ruby - require 'spec_helper' - require 'facter/util/directory_loader' describe Facter::Util::DirectoryLoader do diff --git a/spec/unit/util/ip/base_spec.rb b/spec/unit/util/ip/base_spec.rb new file mode 100644 index 0000000000..9484c22fa9 --- /dev/null +++ b/spec/unit/util/ip/base_spec.rb @@ -0,0 +1,147 @@ +# encoding: UTF-8 + +require 'spec_helper' +require 'facter/util/ip/base' + +describe Facter::Util::IP::Base do + subject { described_class } + + describe ".subclasses" do + let(:subclasses) { described_class.subclasses } + + it { expect(subclasses).to be_an Array } + + it "should be memoized" do + expect(subclasses).to be subclasses + end + + it "should list subclasses" do + subclass = Class.new described_class + + expect(subclasses).to include subclass + end + end + + describe ".to_s" do + let(:to_s) { described_class.to_s } + + it "should return the name of the class without nesting" do + expect(to_s).to eq 'Base' + end + end + + describe ".convert_netmask_from_hex?" do + let :convert_netmask_from_hex? do + described_class.convert_netmask_from_hex? + end + + it { expect(convert_netmask_from_hex?).to be true } + end + + describe ".bonding_master" do + let(:bonding_master) { described_class.bonding_master('eth0') } + + it { expect(bonding_master).to be_nil } + end + + describe ".interfaces" do + let(:interfaces) { described_class.interfaces } + + it "uses `ifconfig` to list the interfaces" do + described_class.expects(:ifconfig_path).returns('/bin/ifconfig') + described_class.expects(:exec).with("/bin/ifconfig -a 2> /dev/null") + expect(interfaces).to be_an Array + end + end + + describe ".value_for_interface_and_label(interface, label)" do + let :value_for_interface_and_label do + described_class.value_for_interface_and_label interface, label + end + + let(:interface) { 'eth0' } + + describe "there is no regex for the label" do + let(:label) { 'foobar' } + + before :each do + described_class.expects(:regex_for).with(label).returns(nil) + end + + it { expect(value_for_interface_and_label).to be_nil } + end + + describe "there is a regex for the label" do + let(:regex) { // } + let(:ifconfig_path) { '/usr/bin/ifconfig' } + let(:exec_cmd) { "#{ifconfig_path} #{interface} 2> /dev/null" } + let(:ifconfig_output) { "" } + + before :each do + described_class.expects(:ifconfig_path).returns(ifconfig_path) + described_class.expects(:regex_for).with(label).returns(regex) + described_class.expects(:exec).with(exec_cmd).returns(ifconfig_output) + regex.expects(:match).with(ifconfig_output).returns(match_data) + end + + describe "there is a match with the exec output and the regex" do + describe "the label is not 'netmask'" do + let(:match_data) { ['inet 127.0.0.1', '127.0.0.1'] } + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to eq '127.0.0.1' } + end + + describe "the label is 'netmask'" do + let(:label) { 'netmask' } + + describe "the netmask needs to be converted from hex" do + let(:match_data) { ['netmask ffffff00', 'ffffff00'] } + + before :each do + described_class.expects(:convert_netmask_from_hex?).returns(true) + end + + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + end + + describe "the netmask does not need to be converted from hex" do + let(:match_data) { ['netmask 255.255.255.0', '255.255.255.0'] } + + before :each do + described_class.expects(:convert_netmask_from_hex?).returns(false) + end + + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + end + end + end + + describe "there is not a match with the exec output and the regex" do + let(:match_data) { nil } + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to be_nil } + end + end + + describe ".network(interface)" do + let(:network) { described_class.network(interface) } + let(:interface) { 'e1000g0' } + + before :each do + described_class. + expects(:value_for_interface_and_label). + with(interface, 'ipaddress'). + returns('172.16.15.138') + + described_class. + expects(:value_for_interface_and_label). + with(interface, 'netmask'). + returns('255.255.255.0') + end + + it { expect(network).to eq "172.16.15.0" } + end + end +end diff --git a/spec/unit/util/ip/darwin_spec.rb b/spec/unit/util/ip/darwin_spec.rb new file mode 100644 index 0000000000..b9407f29af --- /dev/null +++ b/spec/unit/util/ip/darwin_spec.rb @@ -0,0 +1,87 @@ +# encoding: UTF-8 + +require 'spec_helper' +require 'facter/util/ip/darwin' + +describe Facter::Util::IP::Darwin do + describe ".to_s" do + let :to_s do + described_class.to_s + end + + it "should be 'Darwin'" do + expect(to_s).to eq 'Darwin' + end + end + + describe ".convert_netmask_from_hex?" do + let :convert_netmask_from_hex? do + described_class.convert_netmask_from_hex? + end + + it "should be true" do + expect(convert_netmask_from_hex?).to be true + end + end + + describe ".bonding_master" do + let :bonding_master do + described_class.bonding_master('eth0') + end + + it { expect(bonding_master).to be_nil } + end + + describe ".interfaces" do + let :interfaces do + described_class.interfaces + end + + let :ifconfig_output do + my_fixture_read("ifconfig_all_with_multiple_interfaces") + end + + before :each do + described_class.expects(:ifconfig_path).returns('/usr/bin/ifconfig') + described_class.expects(:exec).with(anything).returns(ifconfig_output) + end + + it "should return an array with two interfaces" do + expect(interfaces).to eq ["lo0", "en0"] + end + end + + describe ".value_for_interface_and_label(interface, label)" do + let :value_for_interface_and_label do + described_class.value_for_interface_and_label interface, label + end + + let(:interface) { 'en0' } + let(:ifconfig_output) { my_fixture_read "ifconfig_with_single_interface" } + let(:ifconfig_path) { "/usr/bin/ifconfig" } + let(:exec_cmd) { "#{ifconfig_path} #{interface} 2> /dev/null" } + + before :each do + described_class.expects(:ifconfig_path).returns(ifconfig_path) + described_class.expects(:exec).with(exec_cmd).returns(ifconfig_output) + end + + describe "macaddress" do + let(:label) { 'macaddress' } + + it { expect(value_for_interface_and_label).to eq '00:23:6c:99:60:2b' } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + end + + describe "mtu" do + let(:label) { 'mtu' } + + it { expect(value_for_interface_and_label).to eq '1500' } + end + end +end diff --git a/spec/unit/util/ip/dragonfly_spec.rb b/spec/unit/util/ip/dragonfly_spec.rb new file mode 100644 index 0000000000..f2c670a209 --- /dev/null +++ b/spec/unit/util/ip/dragonfly_spec.rb @@ -0,0 +1,34 @@ +# encoding: UTF-8 + +require 'spec_helper' +require 'facter/util/ip/dragonfly' + +describe Facter::Util::IP::Dragonfly do + describe ".to_s" do + let :to_s do + described_class.to_s + end + + it "should be 'Dragonfly'" do + expect(to_s).to eq 'Dragonfly' + end + end + + describe ".convert_netmask_from_hex?" do + let :convert_netmask_from_hex? do + described_class.convert_netmask_from_hex? + end + + it "should be true" do + expect(convert_netmask_from_hex?).to be true + end + end + + describe ".bonding_master" do + let(:bonding_master) do + described_class.bonding_master('eth0') + end + + it { expect(bonding_master).to be_nil } + end +end diff --git a/spec/unit/util/ip/free_bsd_spec.rb b/spec/unit/util/ip/free_bsd_spec.rb new file mode 100644 index 0000000000..d69989e492 --- /dev/null +++ b/spec/unit/util/ip/free_bsd_spec.rb @@ -0,0 +1,56 @@ +# encoding: UTF-8 + +require 'spec_helper' +require 'facter/util/ip/free_bsd' + +describe Facter::Util::IP::FreeBSD do + describe ".to_s" do + let :to_s do + described_class.to_s + end + + it "should be 'FreeBSD'" do + expect(to_s).to eq 'FreeBSD' + end + end + + describe ".convert_netmask_from_hex?" do + let :convert_netmask_from_hex? do + described_class.convert_netmask_from_hex? + end + + it "should be true" do + expect(convert_netmask_from_hex?).to be true + end + end + + describe ".bonding_master" do + let :bonding_master do + described_class.bonding_master('eth0') + end + + it { expect(bonding_master).to be_nil } + end + + describe ".value_for_interface_and_label(interface, label)" do + let :value_for_interface_and_label do + described_class.value_for_interface_and_label interface, label + end + + let(:interface) { 'fxp0' } + let(:ifconfig_output) { my_fixture_read "6.0-STABLE_ifconfig_with_single_interface" } + let(:ifconfig_path) { "/usr/bin/ifconfig" } + let(:exec_cmd) { "#{ifconfig_path} #{interface} 2> /dev/null" } + + before :each do + described_class.expects(:ifconfig_path).returns(ifconfig_path) + described_class.expects(:exec).with(exec_cmd).returns(ifconfig_output) + end + + describe "macaddress" do + let(:label) { 'macaddress' } + + it { expect(value_for_interface_and_label).to eq '00:0e:0c:68:67:7c' } + end + end +end diff --git a/spec/unit/util/ip/gnu_k_free_bsd_spec.rb b/spec/unit/util/ip/gnu_k_free_bsd_spec.rb new file mode 100644 index 0000000000..591571d6ec --- /dev/null +++ b/spec/unit/util/ip/gnu_k_free_bsd_spec.rb @@ -0,0 +1,85 @@ +# encoding: UTF-8 + +require 'spec_helper' +require 'facter/util/ip/gnu_k_free_bsd' + +describe Facter::Util::IP::GNUkFreeBSD do + before :each do + Facter.fact(:kernel).stubs(:value).returns('GNU/kFreeBSD') + end + + describe ".to_s" do + let :to_s do + described_class.to_s + end + + it "should be 'GNU/kFreeBSD'" do + expect(to_s).to eq 'GNU/kFreeBSD' + end + end + + describe ".convert_netmask_from_hex?" do + let :convert_netmask_from_hex? do + described_class.convert_netmask_from_hex? + end + + it "should be true" do + expect(convert_netmask_from_hex?).to be true + end + end + + describe ".bonding_master" do + let :bonding_master do + described_class.bonding_master('eth0') + end + + it { expect(bonding_master).to be_nil } + end + + describe ".interfaces" do + let :interfaces do + described_class.interfaces + end + + let :ifconfig_output do + my_fixture_read("ifconfig_all_with_multiple_interfaces") + end + + before :each do + described_class.expects(:ifconfig_path).returns('/usr/bin/ifconfig') + described_class.expects(:exec).with(anything).returns(ifconfig_output) + end + + it "should return an array with six interfaces" do + expect(interfaces).to eq %w[em0 em1 bge0 bge1 lo0 vlan0] + end + end + + describe ".value_for_interface_and_label(interface, label)" do + let :value_for_interface_and_label do + described_class.value_for_interface_and_label interface, label + end + + let(:interface) { 'em1' } + let(:ifconfig_output) { my_fixture_read "ifconfig_with_single_interface" } + let(:ifconfig_path) { "/usr/bin/ifconfig" } + let(:exec_cmd) { "#{ifconfig_path} #{interface} 2> /dev/null" } + + before :each do + described_class.expects(:ifconfig_path).returns(ifconfig_path) + described_class.expects(:exec).with(exec_cmd).returns(ifconfig_output) + end + + describe "macaddress" do + let(:label) { 'macaddress' } + + it { expect(value_for_interface_and_label).to eq '0:11:a:59:67:91' } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + end + end +end diff --git a/spec/unit/util/ip/hpux_spec.rb b/spec/unit/util/ip/hpux_spec.rb new file mode 100644 index 0000000000..ec3f21bfe1 --- /dev/null +++ b/spec/unit/util/ip/hpux_spec.rb @@ -0,0 +1,536 @@ +# encoding: UTF-8 + +require 'spec_helper' +require 'facter/util/ip/hpux' + +describe Facter::Util::IP::HPUX do + before :each do + Facter.fact(:kernel).stubs(:value).returns("HP-UX") + end + + describe ".to_s" do + let :to_s do + described_class.to_s + end + + it "should be 'HP-UX'" do + expect(to_s).to eq 'HP-UX' + end + end + + describe ".convert_netmask_from_hex?" do + let :convert_netmask_from_hex? do + described_class.convert_netmask_from_hex? + end + + it "should be true" do + expect(convert_netmask_from_hex?).to be true + end + end + + describe ".bonding_master" do + let :bonding_master do + described_class.bonding_master('eth0') + end + + it { expect(bonding_master).to be_nil } + end + + describe ".interfaces" do + let :interfaces do + described_class.interfaces + end + + before :each do + described_class.stubs(:exec).with(anything).returns(netstat_output) + end + + describe "version 11.11" do + let :netstat_output do + my_fixture_read("1111_netstat_in") + end + + it "should return an array of interfaces" do + expect(interfaces).to eq %w[lan1 lan0 lo0] + end + end + + describe "version 11.31" do + let :netstat_output do + my_fixture_read("1131_netstat_in") + end + + it "should return an array of interfaces" do + expect(interfaces).to eq %w[lan1 lan0 lo0] + end + end + end + + describe ".value_for_interface_and_label(interface, label)" do + let :value_for_interface_and_label do + described_class.value_for_interface_and_label interface, label + end + + describe "version 11.11" do + let(:ifconfig_output) { my_fixture_read("1111_ifconfig_#{interface}") } + let(:lanscan_output) { my_fixture_read("1111_lanscan") } + + before :each do + described_class.stubs(:exec).returns(ifconfig_output) + end + + describe "lan1" do + let(:interface) { 'lan1' } + + describe "ipaddress" do + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to eq '10.1.1.6' } + end + + describe "mtu" do + let(:label) { 'mtu' } + + it { pending "(#17808) MTU has not been implemented for HP-UX" } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + end + + describe "macaddress" do + let(:label) { 'macaddress' } + + before :each do + described_class.expects(:lanscan).returns(lanscan_output) + end + + it { expect(value_for_interface_and_label).to eq "00:10:79:7B:5C:DE" } + end + end + + describe "lan0" do + let(:interface) { 'lan0' } + + describe "ipaddress" do + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to eq "192.168.3.10" } + end + + describe "mtu" do + let(:label) { 'mtu' } + + it { pending "(#17808) MTU has not been implemented for HP-UX" } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + end + + describe "macaddress" do + let(:label) { 'macaddress' } + + before :each do + described_class.expects(:lanscan).returns(lanscan_output) + end + + it { expect(value_for_interface_and_label).to eq "00:30:7F:0C:79:DC" } + end + end + + describe "lo0" do + let(:interface) { 'lo0' } + + describe "ipaddress" do + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to eq "127.0.0.1" } + end + + describe "mtu" do + let(:label) { 'mtu' } + + it { pending "(#17808) MTU has not been implemented for HP-UX" } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.0.0.0' } + end + + describe "macaddress" do + let(:label) { 'macaddress' } + + it { expect(value_for_interface_and_label).to be_nil } + end + end + end + + describe "version 11.31" do + describe "when interfaces are normal" do + let(:ifconfig_output) { my_fixture_read("1131_ifconfig_#{interface}") } + let(:lanscan_output) { my_fixture_read("1131_lanscan") } + + before :each do + described_class.stubs(:exec).returns(ifconfig_output) + end + + describe "lan1" do + let(:interface) { 'lan1' } + + describe "ipaddress" do + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to eq '10.1.54.36' } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + end + + describe "macaddress" do + let(:label) { 'macaddress' } + let(:macaddress) { '00:17:FD:2D:2A:57' } + + before :each do + described_class.expects(:lanscan).returns(lanscan_output) + end + + it { expect(value_for_interface_and_label).to eq macaddress } + end + + describe "mtu" do + let(:label) { 'mtu' } + + it { pending "(#17808) MTU has not been implemented for HP-UX" } + end + end + + describe "lan0" do + let(:interface) { 'lan0' } + + describe "ipaddress" do + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to eq '192.168.30.152' } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + end + + describe "macaddress" do + let(:label) { 'macaddress' } + let(:macaddress) { '00:12:31:7D:62:09' } + + before :each do + described_class.expects(:lanscan).returns(lanscan_output) + end + + it { expect(value_for_interface_and_label).to eq macaddress } + end + + describe "mtu" do + let(:label) { 'mtu' } + + it { pending "(#17808) MTU has not been implemented for HP-UX" } + end + end + + describe "lo0" do + let(:interface) { 'lo0' } + + describe "ipaddress" do + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to eq '127.0.0.1' } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.0.0.0' } + end + + describe "macaddress" do + let(:label) { 'macaddress' } + + before :each do + described_class.expects(:lanscan).returns(lanscan_output) + end + + it { expect(value_for_interface_and_label).to be_nil } + end + + describe "mtu" do + let(:label) { 'mtu' } + + it { pending "(#17808) MTU has not been implemented for HP-UX" } + end + end + end + + describe "when an interface has an asterisk appended" do + let(:lanscan_output) { my_fixture_read("1131_asterisk_lanscan") } + + let :ifconfig_output do + my_fixture_read "1131_asterisk_ifconfig_#{interface}" + end + + before :each do + described_class.stubs(:exec).returns(ifconfig_output) + end + + describe "lan1" do + let(:interface) { 'lan1' } + + describe "ipaddress" do + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to eq '10.10.0.5' } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + end + + describe "macaddress" do + let(:label) { 'macaddress' } + let(:macaddress) { '00:10:79:7B:BE:46' } + + before :each do + described_class.expects(:lanscan).returns(lanscan_output) + end + + it { expect(value_for_interface_and_label).to eq macaddress } + end + + describe "mtu" do + let(:label) { 'mtu' } + + it { pending "(#17808) MTU has not been implemented for HP-UX" } + end + end + + describe "lan0" do + let(:interface) { 'lan0' } + + describe "ipaddress" do + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to eq '192.168.3.9' } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + end + + describe "macaddress" do + let(:label) { 'macaddress' } + let(:macaddress) { '00:30:5D:06:26:B2' } + + before :each do + described_class.expects(:lanscan).returns(lanscan_output) + end + + it { expect(value_for_interface_and_label).to eq macaddress } + end + + describe "mtu" do + let(:label) { 'mtu' } + + it { pending "(#17808) MTU has not been implemented for HP-UX" } + end + end + + describe "lo0" do + let(:interface) { 'lo0' } + + describe "ipaddress" do + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to eq '127.0.0.1' } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.0.0.0' } + end + + describe "macaddress" do + let(:label) { 'macaddress' } + + before :each do + described_class.expects(:lanscan).returns(lanscan_output) + end + + it { expect(value_for_interface_and_label).to be_nil } + end + + describe "mtu" do + let(:label) { 'mtu' } + + it { pending "(#17808) MTU has not been implemented for HP-UX" } + end + end + end + + describe "when an interface is bonded and has one virtual interface" do + let(:lanscan_output) { my_fixture_read "1131_nic_bonding_lanscan" } + + let :ifconfig_output do + my_fixture_read "1131_nic_bonding_ifconfig_#{interface.sub(':', '_')}" + end + + before :each do + described_class.stubs(:exec).returns(ifconfig_output) + end + + describe "lan1" do + let(:interface) { 'lan1' } + + describe "ipaddress" do + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to eq '192.168.30.32' } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + end + + describe "macaddress" do + let(:label) { 'macaddress' } + let(:macaddress) { '00:12:81:9E:48:DE' } + + before :each do + described_class.expects(:lanscan).returns(lanscan_output) + end + + it { expect(value_for_interface_and_label).to eq macaddress } + end + + describe "mtu" do + let(:label) { 'mtu' } + + it { pending "(#17808) MTU has not been implemented for HP-UX" } + end + end + + describe "lo0" do + let(:interface) { 'lo0' } + + describe "ipaddress" do + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to eq '127.0.0.1' } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.0.0.0' } + end + + describe "macaddress" do + let(:label) { 'macaddress' } + + before :each do + described_class.expects(:lanscan).returns(lanscan_output) + end + + it { expect(value_for_interface_and_label).to be_nil } + end + + describe "mtu" do + let(:label) { 'mtu' } + + it { pending "(#17808) MTU has not been implemented for HP-UX" } + end + end + + describe "lan4" do + let(:interface) { 'lan4' } + + describe "ipaddress" do + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to eq '192.168.32.75' } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + end + + describe "macaddress" do + let(:label) { 'macaddress' } + let(:macaddress) { '00:12:81:9E:4A:7E' } + + before :each do + described_class.expects(:lanscan).returns(lanscan_output) + end + + it { expect(value_for_interface_and_label).to eq macaddress } + end + + describe "mtu" do + let(:label) { 'mtu' } + + it { pending "(#17808) MTU has not been implemented for HP-UX" } + end + end + + describe "lan4:1" do + let(:interface) { 'lan4:1' } + + describe "ipaddress" do + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to eq '192.168.1.197' } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + end + + describe "macaddress" do + let(:label) { 'macaddress' } + + before :each do + described_class.expects(:lanscan).returns(lanscan_output) + end + + it { expect(value_for_interface_and_label).to be_nil } + end + + describe "mtu" do + let(:label) { 'mtu' } + + it { pending "(#17808) MTU has not been implemented for HP-UX" } + end + end + end + end + end +end diff --git a/spec/unit/util/ip/linux_spec.rb b/spec/unit/util/ip/linux_spec.rb new file mode 100644 index 0000000000..85383115e7 --- /dev/null +++ b/spec/unit/util/ip/linux_spec.rb @@ -0,0 +1,158 @@ +# encoding: UTF-8 + +require 'spec_helper' +require 'facter/util/ip/linux' + +describe Facter::Util::IP::Linux do + before :each do + Facter.fact(:kernel).stubs(:value).returns("Linux") + end + + describe ".to_s" do + let :to_s do + described_class.to_s + end + + it "should be 'Linux'" do + expect(to_s).to eq 'Linux' + end + end + + describe ".convert_netmask_from_hex?" do + let :convert_netmask_from_hex? do + described_class.convert_netmask_from_hex? + end + + it "should be true" do + expect(convert_netmask_from_hex?).to be false + end + end + + describe ".bonding_master(interface)" do + let :bonding_master do + described_class.bonding_master(interface) + end + + describe "on interface aliases" do + let :interface do + "eth0:1" + end + + it { expect(bonding_master).to be_nil } + end + end + + describe ".interfaces" do + let :interfaces do + described_class.interfaces + end + + describe "without sysfs" do + let :ifconfig_output do + my_fixture_read("ifconfig_all_with_single_interface") + end + + before :each do + File.expects(:exist?).with('/sys/class/net').returns(false) + described_class.expects(:ifconfig_path).returns('/usr/bin/ifconfig') + described_class.expects(:exec).with(anything).returns(ifconfig_output) + end + + it "should return an array with a single interface and the loopback" do + expect(interfaces).to eq ["eth0", "lo"] + end + end + + describe "with sysfs" do + let :sysfs do + %w[/sys/class/net/eth0 /sys/class/net/lo] + end + + before :each do + File.expects(:exist?).with('/sys/class/net').returns(true) + Dir.expects(:glob).with('/sys/class/net/*').returns(sysfs) + end + + it "should return an array with a single interface and the loopback" do + expect(interfaces).to eq ["eth0", "lo"] + end + end + end + + describe "value_for_interface_and_label(interface, label)" do + let :value_for_interface_and_label do + described_class.value_for_interface_and_label interface, label + end + + describe "infiniband interface" do + let(:interface) { 'ib0' } + + let :ifconfig_output do + my_fixture_read "ifconfig_with_single_interface_ib0" + end + + describe "macaddress" do + let(:label) { 'macaddress' } + + before :each do + described_class.expects(:infiniband_macaddress).returns('bar') + end + + it { expect(value_for_interface_and_label).to eq 'bar' } + end + end + + describe "normal interface" do + let(:ifconfig_path) { '/usr/bin/ifconfig' } + let(:exec_cmd) { "#{ifconfig_path} #{interface} 2> /dev/null" } + + let(:ifconfig_output) do + my_fixture_read "ifconfig_single_interface_#{interface}" + end + + before :each do + described_class.expects(:ifconfig_path).returns(ifconfig_path) + described_class.expects(:exec).with(exec_cmd).returns(ifconfig_output) + end + + describe "eth0" do + let(:interface) { 'eth0' } + + describe "mtu" do + let(:label) { 'mtu' } + + it { expect(value_for_interface_and_label).to eq '1500' } + end + end + + describe "lo" do + let(:interface) { 'lo' } + + describe "mtu" do + let(:label) { 'mtu' } + + it { expect(value_for_interface_and_label).to eq '16436' } + end + end + end + + describe "bonded interface" do + let(:interface) { 'eth0' } + let(:bond) { 'bond0' } + let(:proc_net_path) { '/proc/net/bonding/bond0' } + + before :each do + proc_net = my_fixture_read "2_6_35_proc_net_bonding_#{bond}" + described_class.expects(:bonding_master).with(interface).returns(bond) + File.expects(:exists?).with(proc_net_path).returns(true) + File.expects(:read).with(proc_net_path).returns(proc_net) + end + + describe "macaddress" do + let(:label) { 'macaddress' } + + it { expect(value_for_interface_and_label).to eq "00:11:22:33:44:55" } + end + end + end +end diff --git a/spec/unit/util/ip/net_bsd_spec.rb b/spec/unit/util/ip/net_bsd_spec.rb new file mode 100644 index 0000000000..489e82b2b4 --- /dev/null +++ b/spec/unit/util/ip/net_bsd_spec.rb @@ -0,0 +1,34 @@ +# encoding: UTF-8 + +require 'spec_helper' +require 'facter/util/ip/net_bsd' + +describe Facter::Util::IP::NetBSD do + describe ".to_s" do + let :to_s do + described_class.to_s + end + + it "should be 'NetBSD'" do + expect(to_s).to eq 'NetBSD' + end + end + + describe ".convert_netmask_from_hex?" do + let :convert_netmask_from_hex? do + described_class.convert_netmask_from_hex? + end + + it "should be true" do + expect(convert_netmask_from_hex?).to be true + end + end + + describe ".bonding_master" do + let :bonding_master do + described_class.bonding_master('eth0') + end + + it { expect(bonding_master).to be_nil } + end +end diff --git a/spec/unit/util/ip/open_bsd_spec.rb b/spec/unit/util/ip/open_bsd_spec.rb new file mode 100644 index 0000000000..0a9508dc60 --- /dev/null +++ b/spec/unit/util/ip/open_bsd_spec.rb @@ -0,0 +1,34 @@ +# encoding: UTF-8 + +require 'spec_helper' +require 'facter/util/ip/open_bsd' + +describe Facter::Util::IP::OpenBSD do + describe ".to_s" do + let :to_s do + described_class.to_s + end + + it "should be 'OpenBSD'" do + expect(to_s).to eq 'OpenBSD' + end + end + + describe ".convert_netmask_from_hex?" do + let :convert_netmask_from_hex? do + described_class.convert_netmask_from_hex? + end + + it "should be true" do + expect(convert_netmask_from_hex?).to be true + end + end + + describe ".bonding_master" do + let :bonding_master do + described_class.bonding_master('eth0') + end + + it { expect(bonding_master).to be_nil } + end +end diff --git a/spec/unit/util/ip/sun_os_spec.rb b/spec/unit/util/ip/sun_os_spec.rb new file mode 100644 index 0000000000..23e366612b --- /dev/null +++ b/spec/unit/util/ip/sun_os_spec.rb @@ -0,0 +1,91 @@ +# encoding: UTF-8 + +require 'spec_helper' +require 'facter/util/ip/sun_os' + +describe Facter::Util::IP::SunOS do + before :each do + Facter.fact(:kernel).stubs(:value).with('SunOS') + end + + describe ".to_s" do + let :to_s do + described_class.to_s + end + + it "should be 'SunOS'" do + expect(to_s).to eq 'SunOS' + end + end + + describe ".convert_netmask_from_hex?" do + let :convert_netmask_from_hex? do + described_class.convert_netmask_from_hex? + end + + it "should be true" do + expect(convert_netmask_from_hex?).to be true + end + end + + describe ".bonding_master" do + let :bonding_master do + described_class.bonding_master('eth0') + end + + it { expect(bonding_master).to be_nil } + end + + describe ".interfaces" do + let :interfaces do + described_class.interfaces + end + + let :ifconfig_output do + my_fixture_read("ifconfig_all_with_multiple_interfaces") + end + + before :each do + described_class.expects(:ifconfig_path).returns('/usr/bin/ifconfig') + described_class.expects(:exec).with(anything).returns(ifconfig_output) + end + + it "should return an array with two interfaces" do + expect(interfaces).to eq ["lo0", "e1000g0"] + end + end + + describe ".value_for_interface_and_label(interface, label)" do + let :value_for_interface_and_label do + described_class.value_for_interface_and_label interface, label + end + + let(:interface) { 'e1000g0' } + let(:ifconfig_output) { my_fixture_read "ifconfig_single_interface" } + let(:ifconfig_path) { "/usr/bin/ifconfig" } + let(:exec_cmd) { "#{ifconfig_path} #{interface}" } + + before :each do + described_class.expects(:ifconfig_path).returns(ifconfig_path) + described_class.expects(:exec).with(exec_cmd).returns(ifconfig_output) + end + + describe "ipaddress" do + let(:label) { "ipaddress" } + + it { expect(value_for_interface_and_label).to eq "172.16.15.138" } + end + + describe "netmask" do + let(:label) { "netmask" } + + it { expect(value_for_interface_and_label).to eq "255.255.255.0" } + end + + describe "mtu" do + let(:label) { "mtu" } + + it { expect(value_for_interface_and_label).to eq '1500' } + end + end +end diff --git a/spec/unit/util/ip/windows_spec.rb b/spec/unit/util/ip/windows_spec.rb new file mode 100644 index 0000000000..09fc3fb68c --- /dev/null +++ b/spec/unit/util/ip/windows_spec.rb @@ -0,0 +1,94 @@ +# encoding: UTF-8 + +require 'spec_helper' +require 'facter/util/ip/windows' + +describe Facter::Util::IP::Windows do + before :each do + Facter.fact(:kernel).stubs(:value).returns('windows') + end + + describe ".to_s" do + let(:to_s) { described_class.to_s } + + it { expect(to_s).to eq 'windows' } + end + + describe ".convert_netmask_from_hex?" do + let :convert_netmask_from_hex? do + described_class.convert_netmask_from_hex? + end + + it { expect(convert_netmask_from_hex?).to be false } + end + + describe ".bonding_master" do + let(:bonding_master) { described_class.bonding_master('eth0') } + + it { expect(bonding_master).to be_nil } + end + + describe ".interfaces" do + let(:interfaces) { described_class.interfaces } + + let(:netsh_output) { my_fixture_read "netsh_all_interfaces" } + + let :expected_interfaces do + [ + "Loopback Pseudo-Interface 1", + "Local Area Connection", + "Teredo Tunneling Pseudo-Interface" + ] + end + + before :each do + described_class.expects(:exec).with(anything).returns(netsh_output) + end + + it "should return an array of only connected interfaces" do + expect(interfaces).to eq expected_interfaces + end + end + + describe "value_for_interface_and_label(interface, label)" do + let(:interface) { 'Local Area Connection' } + let(:netsh_output) { my_fixture_read("netsh_with_single_interface") } + + let :value_for_interface_and_label do + described_class.value_for_interface_and_label interface, label + end + + let(:exec_cmd) do + "#{described_class::NETSH} interface ip show address \"#{interface}\"" + end + + before :each do + described_class.expects(:exec).with(exec_cmd).returns(netsh_output) + end + + describe "ipaddress" do + let(:label) { 'ipaddress' } + + it { expect(value_for_interface_and_label).to eq '172.16.138.216' } + end + + describe "netmask" do + let(:label) { 'netmask' } + + it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + end + + describe "ipaddress6" do + let(:interface) { 'Teredo Tunneling Pseudo-Interface' } + let(:label) { 'ipaddress6' } + let(:expected_ip) { '2001:0:4137:9e76:2087:77a:53ef:7527' } + let(:netsh_output) { my_fixture_read("netsh_with_single_interface6") } + + let(:exec_cmd) do + "#{described_class::NETSH} interface ipv6 show address \"#{interface}\"" + end + + it { expect(value_for_interface_and_label).to eq expected_ip } + end + end +end diff --git a/spec/unit/util/ip_spec.rb b/spec/unit/util/ip_spec.rb index 3d95ee5cd7..1707d065c3 100755 --- a/spec/unit/util/ip_spec.rb +++ b/spec/unit/util/ip_spec.rb @@ -4,418 +4,56 @@ require 'facter/util/ip' describe Facter::Util::IP do - include FacterSpec::ConfigHelper - - before :each do - given_a_configuration_of(:is_windows => false) - end - - [:freebsd, :linux, :netbsd, :openbsd, :sunos, :darwin, :"hp-ux", :"gnu/kfreebsd", :windows].each do |platform| + let :interfaces_hash do + { + :eth0 => { + :ipaddress => 1, + :ipaddress6 => 2, + :macaddress => 3, + :netmask => 4, + :mtu => 5, + :network => 6 + }, + + :lo0 => { + :ipaddress => 7, + :ipaddress6 => 8, + :macaddress => 9, + :netmask => 10, + :mtu => 11, + :network => 12 + } + } + end + + let :interfaces_hash2 do + { + :en1 => { + :ipaddress => 13, + :ipaddress6 => 14, + :macaddress => 15, + :netmask => 16, + :mtu => 17, + :network => 18 + }, + + :lo => { + :ipaddress => 19, + :ipaddress6 => 20, + :macaddress => 21, + :netmask => 22, + :mtu => 23, + :network => 24 + } + } + end + + %w{ + FreeBSD Linux NetBSD OpenBSD SunOS Darwin HP-UX GNU/kFreeBSD windows + Dragonfly + }.each do |platform| it "should be supported on #{platform}" do - given_a_configuration_of(:is_windows => platform == :windows) - Facter::Util::IP.supported_platforms.should be_include(platform) - end - end - - it "should return a list of interfaces" do - Facter::Util::IP.should respond_to(:get_interfaces) - end - - it "should return an empty list of interfaces on an unknown kernel" do - File.stubs(:exist?).with('/sys/class/net').returns(false) - Facter.stubs(:value).returns("UnknownKernel") - Facter::Util::IP.get_interfaces().should == [] - end - - it "should return a list with a single interface and the loopback interface on Linux with a single interface without sysfs" do - File.stubs(:exist?).with('/sys/class/net').returns(false) - linux_ifconfig = my_fixture_read("linux_ifconfig_all_with_single_interface") - Facter::Util::IP.stubs(:get_all_interface_output).returns(linux_ifconfig) - Facter::Util::IP.get_interfaces().should =~ ["eth0", "lo"] - end - - it "should return a list two interfaces on Darwin with two interfaces" do - File.stubs(:exist?).with('/sys/class/net').returns(false) - darwin_ifconfig = my_fixture_read("darwin_ifconfig_all_with_multiple_interfaces") - Facter::Util::IP.stubs(:get_all_interface_output).returns(darwin_ifconfig) - Facter::Util::IP.get_interfaces().should == ["lo0", "en0"] - end - - it "should return a list two interfaces on Solaris with two interfaces multiply reporting" do - File.stubs(:exist?).with('/sys/class/net').returns(false) - solaris_ifconfig = my_fixture_read("solaris_ifconfig_all_with_multiple_interfaces") - Facter::Util::IP.stubs(:get_all_interface_output).returns(solaris_ifconfig) - Facter::Util::IP.get_interfaces().should == ["lo0", "e1000g0"] - end - - it "should return a list of six interfaces on a GNU/kFreeBSD with six interfaces" do - File.stubs(:exist?).with('/sys/class/net').returns(false) - kfreebsd_ifconfig = my_fixture_read("debian_kfreebsd_ifconfig") - Facter::Util::IP.stubs(:get_all_interface_output).returns(kfreebsd_ifconfig) - Facter::Util::IP.get_interfaces().should == ["em0", "em1", "bge0", "bge1", "lo0", "vlan0"] - end - - it "should return a list of only connected interfaces on Windows" do - File.stubs(:exist?).with('/sys/class/net').returns(false) - Facter.fact(:kernel).stubs(:value).returns("windows") - windows_netsh = my_fixture_read("windows_netsh_all_interfaces") - Facter::Util::IP.stubs(:get_all_interface_output).returns(windows_netsh) - Facter::Util::IP.get_interfaces().should == ["Loopback Pseudo-Interface 1", "Local Area Connection", "Teredo Tunneling Pseudo-Interface"] - end - - it "should return a value for a specific interface" do - Facter::Util::IP.should respond_to(:get_interface_value) - end - - it "should not return interface information for unsupported platforms" do - Facter.stubs(:value).with(:kernel).returns("bleah") - Facter::Util::IP.get_interface_value("e1000g0", "netmask").should == [] - end - - it "should return ipaddress information for Solaris" do - solaris_ifconfig_interface = my_fixture_read("solaris_ifconfig_single_interface") - - Facter::Util::IP.expects(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("SunOS") - - Facter::Util::IP.get_interface_value("e1000g0", "ipaddress").should == "172.16.15.138" - end - - it "should return netmask information for Solaris" do - solaris_ifconfig_interface = my_fixture_read("solaris_ifconfig_single_interface") - - Facter::Util::IP.expects(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("SunOS") - - Facter::Util::IP.get_interface_value("e1000g0", "netmask").should == "255.255.255.0" - end - - it "should return calculated network information for Solaris" do - solaris_ifconfig_interface = my_fixture_read("solaris_ifconfig_single_interface") - - Facter::Util::IP.stubs(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("SunOS") - - Facter::Util::IP.get_network_value("e1000g0").should == "172.16.15.0" - end - - it "should return macaddress with leading zeros stripped off for GNU/kFreeBSD" do - kfreebsd_ifconfig = my_fixture_read("debian_kfreebsd_ifconfig") - - Facter::Util::IP.expects(:get_single_interface_output).with("em0").returns(kfreebsd_ifconfig) - Facter.stubs(:value).with(:kernel).returns("GNU/kFreeBSD") - - Facter::Util::IP.get_interface_value("em0", "macaddress").should == "0:11:a:59:67:90" - end - - it "should return interface information for FreeBSD supported via an alias" do - ifconfig_interface = my_fixture_read("6.0-STABLE_FreeBSD_ifconfig") - - Facter::Util::IP.expects(:get_single_interface_output).with("fxp0").returns(ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("FreeBSD") - - Facter::Util::IP.get_interface_value("fxp0", "macaddress").should == "00:0e:0c:68:67:7c" - end - - it "should return macaddress information for OS X" do - ifconfig_interface = my_fixture_read("Mac_OS_X_10.5.5_ifconfig") - - Facter::Util::IP.expects(:get_single_interface_output).with("en1").returns(ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("Darwin") - - Facter::Util::IP.get_interface_value("en1", "macaddress").should == "00:1b:63:ae:02:66" - end - - it "should return all interfaces correctly on OS X" do - File.stubs(:exist?).with('/sys/class/net').returns(false) - ifconfig_interface = my_fixture_read("Mac_OS_X_10.5.5_ifconfig") - - Facter::Util::IP.expects(:get_all_interface_output).returns(ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("Darwin") - - Facter::Util::IP.get_interfaces().should == ["lo0", "gif0", "stf0", "en0", "fw0", "en1", "vmnet8", "vmnet1"] - end - - it "should return a human readable netmask on Solaris" do - solaris_ifconfig_interface = my_fixture_read("solaris_ifconfig_single_interface") - - Facter::Util::IP.expects(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("SunOS") - - Facter::Util::IP.get_interface_value("e1000g0", "netmask").should == "255.255.255.0" - end - - it "should return a human readable netmask on Darwin" do - darwin_ifconfig_interface = my_fixture_read("darwin_ifconfig_single_interface") - - Facter::Util::IP.expects(:get_single_interface_output).with("en1").returns(darwin_ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("Darwin") - - Facter::Util::IP.get_interface_value("en1", "netmask").should == "255.255.255.0" - end - - it "should return a human readable netmask on GNU/kFreeBSD" do - kfreebsd_ifconfig = my_fixture_read("debian_kfreebsd_ifconfig") - - Facter::Util::IP.expects(:get_single_interface_output).with("em1").returns(kfreebsd_ifconfig) - Facter.stubs(:value).with(:kernel).returns("GNU/kFreeBSD") - - Facter::Util::IP.get_interface_value("em1", "netmask").should == "255.255.255.0" - end - - it "should return correct macaddress information for infiniband on Linux" do - correct_ifconfig_interface = my_fixture_read("linux_get_single_interface_ib0") - - Facter::Util::IP.expects(:get_single_interface_output).with("ib0").returns(correct_ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("Linux") - - Facter::Util::IP.get_interface_value("ib0", "macaddress").should == "80:00:00:4a:fe:80:00:00:00:00:00:00:00:02:c9:03:00:43:27:21" - end - - it "should replace the incorrect macaddress with the correct macaddress in ifconfig for infiniband on Linux" do - ifconfig_interface = my_fixture_read("linux_ifconfig_ib0") - correct_ifconfig_interface = my_fixture_read("linux_get_single_interface_ib0") - - Facter::Util::IP.expects(:get_infiniband_macaddress).with("ib0").returns("80:00:00:4a:fe:80:00:00:00:00:00:00:00:02:c9:03:00:43:27:21") - Facter::Util::IP.expects(:ifconfig_interface).with("ib0").returns(ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("Linux") - - Facter::Util::IP.get_single_interface_output("ib0").should == correct_ifconfig_interface - end - - it "should return fake macaddress information for infiniband on Linux when neither sysfs or /sbin/ip are available" do - ifconfig_interface = my_fixture_read("linux_ifconfig_ib0") - - File.expects(:exists?).with("/sys/class/net/ib0/address").returns(false) - File.expects(:exists?).with("/sbin/ip").returns(false) - Facter::Util::IP.expects(:ifconfig_interface).with("ib0").returns(ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("Linux") - - Facter::Util::IP.get_interface_value("ib0", "macaddress").should == "FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF" - end - - it "should not get bonding master on interface aliases" do - Facter.stubs(:value).with(:kernel).returns("Linux") - - Facter::Util::IP.get_bonding_master("eth0:1").should be_nil - end - - [:freebsd, :netbsd, :openbsd, :sunos, :darwin, :"hp-ux"].each do |platform| - it "should require conversion from hex on #{platform}" do - Facter::Util::IP.convert_from_hex?(platform).should == true - end - end - - [:windows].each do |platform| - it "should not require conversion from hex on #{platform}" do - Facter::Util::IP.convert_from_hex?(platform).should be_false - end - end - - it "should return an arp address on Linux" do - Facter.stubs(:value).with(:kernel).returns("Linux") - - Facter::Util::IP.expects(:get_arp_value).with("eth0").returns("00:00:0c:9f:f0:04") - Facter::Util::IP.get_arp_value("eth0").should == "00:00:0c:9f:f0:04" - end - - it "should return mtu information on Linux" do - linux_ifconfig = my_fixture_read("linux_ifconfig_all_with_single_interface") - Facter::Util::IP.stubs(:get_all_interface_output).returns(linux_ifconfig) - Facter::Util::IP.stubs(:get_single_interface_output).with("eth0"). - returns(my_fixture_read("linux_get_single_interface_eth0")) - Facter::Util::IP.stubs(:get_single_interface_output).with("lo"). - returns(my_fixture_read("linux_get_single_interface_lo")) - Facter.stubs(:value).with(:kernel).returns("Linux") - - Facter::Util::IP.get_interface_value("eth0", "mtu").should == "1500" - Facter::Util::IP.get_interface_value("lo", "mtu").should == "16436" - end - - it "should return mtu information on Darwin" do - darwin_ifconfig_interface = my_fixture_read("darwin_ifconfig_single_interface") - - Facter::Util::IP.expects(:get_single_interface_output).with("en1").returns(darwin_ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("Darwin") - - Facter::Util::IP.get_interface_value("en1", "mtu").should == "1500" - end - - it "should return mtu information for Solaris" do - solaris_ifconfig_interface = my_fixture_read("solaris_ifconfig_single_interface") - - Facter::Util::IP.expects(:get_single_interface_output).with("e1000g0").returns(solaris_ifconfig_interface) - Facter.stubs(:value).with(:kernel).returns("SunOS") - - Facter::Util::IP.get_interface_value("e1000g0", "mtu").should == "1500" - end - - # (#17487) - tests for HP-UX. - # some fake data for testing robustness of regexps. - def self.fake_netstat_in_examples - examples = [] - examples << ["Header row\na line with none in it\na line without\nanother line without\n", - "a line without\nanother line without\n"] - examples << ["Header row\na line without\na line with none in it\nanother line with none\nanother line without\n", - "a line without\nanother line without\n"] - examples << ["Header row\na line with * asterisks *\na line with none in it\nanother line without\n", - "a line with asterisks \nanother line without\n"] - examples << ["a line with none none none in it\na line with none in it\na line without\nanother line without\n", - "another line without\n"] - examples - end - - fake_netstat_in_examples.each_with_index do |example, i| - input, expected_output = example - it "should pass regexp test on fake netstat input example #{i}" do - Facter.stubs(:value).with(:kernel).returns("HP-UX") - Facter::Util::IP.stubs(:hpux_netstat_in).returns(input) - Facter::Util::IP.get_all_interface_output().should == expected_output - end - end - - # and some real data for exhaustive tests. - def self.hpux_examples - examples = [] - examples << ["HP-UX 11.11", - ["lan1", "lan0", "lo0" ], - ["1500", "1500", "4136" ], - ["10.1.1.6", "192.168.3.10", "127.0.0.1"], - ["255.255.255.0", "255.255.255.0", "255.0.0.0"], - ["00:10:79:7B:5C:DE", "00:30:7F:0C:79:DC", nil ], - [my_fixture_read("hpux_1111_ifconfig_lan1"), - my_fixture_read("hpux_1111_ifconfig_lan0"), - my_fixture_read("hpux_1111_ifconfig_lo0")], - my_fixture_read("hpux_1111_netstat_in"), - my_fixture_read("hpux_1111_lanscan")] - - examples << ["HP-UX 11.31", - ["lan1", "lan0", "lo0" ], - ["1500", "1500", "4136" ], - ["10.1.54.36", "192.168.30.152", "127.0.0.1"], - ["255.255.255.0", "255.255.255.0", "255.0.0.0"], - ["00:17:FD:2D:2A:57", "00:12:31:7D:62:09", nil ], - [my_fixture_read("hpux_1131_ifconfig_lan1"), - my_fixture_read("hpux_1131_ifconfig_lan0"), - my_fixture_read("hpux_1131_ifconfig_lo0")], - my_fixture_read("hpux_1131_netstat_in"), - my_fixture_read("hpux_1131_lanscan")] - - examples << ["HP-UX 11.31 with an asterisk after a NIC that has an address", - ["lan1", "lan0", "lo0" ], - ["1500", "1500", "4136" ], - ["10.10.0.5", "192.168.3.9", "127.0.0.1"], - ["255.255.255.0", "255.255.255.0", "255.0.0.0"], - ["00:10:79:7B:BE:46", "00:30:5D:06:26:B2", nil ], - [my_fixture_read("hpux_1131_asterisk_ifconfig_lan1"), - my_fixture_read("hpux_1131_asterisk_ifconfig_lan0"), - my_fixture_read("hpux_1131_asterisk_ifconfig_lo0")], - my_fixture_read("hpux_1131_asterisk_netstat_in"), - my_fixture_read("hpux_1131_asterisk_lanscan")] - - examples << ["HP-UX 11.31 with NIC bonding and one virtual NIC", - ["lan4:1", "lan1", "lo0", "lan4" ], - ["1500", "1500", "4136", "1500" ], - ["192.168.1.197", "192.168.30.32", "127.0.0.1", "192.168.32.75" ], - ["255.255.255.0", "255.255.255.0", "255.0.0.0", "255.255.255.0" ], - [nil, "00:12:81:9E:48:DE", nil, "00:12:81:9E:4A:7E"], - [my_fixture_read("hpux_1131_nic_bonding_ifconfig_lan4_1"), - my_fixture_read("hpux_1131_nic_bonding_ifconfig_lan1"), - my_fixture_read("hpux_1131_nic_bonding_ifconfig_lo0"), - my_fixture_read("hpux_1131_nic_bonding_ifconfig_lan4")], - my_fixture_read("hpux_1131_nic_bonding_netstat_in"), - my_fixture_read("hpux_1131_nic_bonding_lanscan")] - examples - end - - hpux_examples.each do |example| - description, array_of_expected_ifs, array_of_expected_mtus, - array_of_expected_ips, array_of_expected_netmasks, - array_of_expected_macs, array_of_ifconfig_fixtures, - netstat_in_fixture, lanscan_fixture = example - - it "should return a list three interfaces on #{description}" do - Facter.expects(:value).with(:kernel).at_least_once.returns("HP-UX") - File.expects(:exist?).with('/sys/class/net').returns(false) - Facter::Util::IP.expects(:hpux_netstat_in).returns(netstat_in_fixture) - Facter::Util::IP.get_interfaces.should == array_of_expected_ifs - end - - array_of_expected_ifs.each_with_index do |nic, i| - ifconfig_fixture = array_of_ifconfig_fixtures[i] - expected_mtu = array_of_expected_mtus[i] - expected_ip = array_of_expected_ips[i] - expected_netmask = array_of_expected_netmasks[i] - expected_mac = array_of_expected_macs[i] - - # (#17808) These tests fail because MTU facts haven't been implemented for HP-UX. - #it "should return MTU #{expected_mtu} on #{nic} for #{description} example" do - # Facter.expects(:value).with(:kernel).at_least_once.returns("HP-UX") - # Facter::Util::IP.expects(:hpux_netstat_in).returns(netstat_in_fixture) - # Facter::Util::IP.expects(:hpux_lanscan).returns(lanscan_fixture) - # Facter::Util::IP.expects(:hpux_ifconfig_interface).with(nic).returns(ifconfig_fixture) - # Facter::Util::IP.get_interface_value(nic, "mtu").should == expected_mtu - #end - - it "should return IP #{expected_ip} on #{nic} for #{description} example" do - Facter.expects(:value).with(:kernel).at_least_once.returns("HP-UX") - Facter::Util::IP.expects(:hpux_lanscan).returns(lanscan_fixture) - Facter::Util::IP.expects(:hpux_ifconfig_interface).with(nic).returns(ifconfig_fixture) - Facter::Util::IP.get_interface_value(nic, "ipaddress").should == expected_ip - end - - it "should return netmask #{expected_netmask} on #{nic} for #{description} example" do - Facter.expects(:value).with(:kernel).at_least_once.returns("HP-UX") - Facter::Util::IP.expects(:hpux_lanscan).returns(lanscan_fixture) - Facter::Util::IP.expects(:hpux_ifconfig_interface).with(nic).returns(ifconfig_fixture) - Facter::Util::IP.get_interface_value(nic, "netmask").should == expected_netmask - end - - it "should return MAC address #{expected_mac} on #{nic} for #{description} example" do - Facter.expects(:value).with(:kernel).at_least_once.returns("HP-UX") - Facter::Util::IP.expects(:hpux_lanscan).returns(lanscan_fixture) - Facter::Util::IP.expects(:hpux_ifconfig_interface).with(nic).returns(ifconfig_fixture) - Facter::Util::IP.get_interface_value(nic, "macaddress").should == expected_mac - end - end - end - - describe "on Windows" do - before :each do - Facter.stubs(:value).with(:kernel).returns("windows") - end - - it "should return ipaddress information" do - windows_netsh = my_fixture_read("windows_netsh_single_interface") - - Facter::Util::IP.expects(:get_output_for_interface_and_label).with("Local Area Connection", "ipaddress").returns(windows_netsh) - - Facter::Util::IP.get_interface_value("Local Area Connection", "ipaddress").should == "172.16.138.216" - end - - it "should return a human readable netmask" do - windows_netsh = my_fixture_read("windows_netsh_single_interface") - - Facter::Util::IP.expects(:get_output_for_interface_and_label).with("Local Area Connection", "netmask").returns(windows_netsh) - - Facter::Util::IP.get_interface_value("Local Area Connection", "netmask").should == "255.255.255.0" - end - - it "should return network information" do - windows_netsh = my_fixture_read("windows_netsh_single_interface") - - Facter::Util::IP.stubs(:get_output_for_interface_and_label).with("Local Area Connection", "ipaddress").returns(windows_netsh) - Facter::Util::IP.stubs(:get_output_for_interface_and_label).with("Local Area Connection", "netmask").returns(windows_netsh) - - Facter::Util::IP.get_network_value("Local Area Connection").should == "172.16.138.0" - end - - it "should return ipaddress6 information" do - windows_netsh = my_fixture_read("windows_netsh_single_interface6") - - Facter::Util::IP.expects(:get_output_for_interface_and_label).with("Teredo Tunneling Pseudo-Interface", "ipaddress6").returns(windows_netsh) - - Facter::Util::IP.get_interface_value("Teredo Tunneling Pseudo-Interface", "ipaddress6").should == "2001:0:4137:9e76:2087:77a:53ef:7527" + Facter::Util::IP.new.supported_platforms.should include platform end end @@ -440,44 +78,112 @@ def self.hpux_examples Facter::Util::IP.exec_ifconfig(["-a","-e","-i","-j"]) end end - describe "get_ifconfig" do - it "assigns /sbin/ifconfig if it is executable" do - File.stubs(:executable?).returns(false) - File.stubs(:executable?).with("/sbin/ifconfig").returns(true) - Facter::Util::IP.get_ifconfig.should eq("/sbin/ifconfig") + + describe ".add_interface_facts" do + before :each do + given_initial_interfaces_facts + described_class.add_interface_facts end - it "assigns /usr/sbin/ifconfig if it is executable" do - File.stubs(:executable?).returns(false) - File.stubs(:executable?).with("/usr/sbin/ifconfig").returns(true) - Facter::Util::IP.get_ifconfig.should eq("/usr/sbin/ifconfig") + + it "defines the 'interfaces' fact" do + expect(Facter.fact(:interfaces)).to be_a_kind_of Facter::Util::Fact end - it "assigns /bin/ifconfig if it is executable" do - File.stubs(:executable?).returns(false) - File.stubs(:executable?).with("/bin/ifconfig").returns(true) - Facter::Util::IP.get_ifconfig.should eq("/bin/ifconfig") + + it "defines a fact for each attribute of an interface" do + interfaces_hash.keys.each do |interface| + described_class::INTERFACE_KEYS.each do |attr| + expect(Facter.fact("#{attr}_#{interface}")). + to be_a_kind_of Facter::Util::Fact + end + end + end + + it "defines a fact for an interface's network" do + interfaces_hash.keys.each do |interface| + expect(Facter.fact("network_#{interface}")). + to be_a_kind_of Facter::Util::Fact + end end end - context "with bonded ethernet interfaces on Linux" do - before :each do - Facter.fact(:kernel).stubs(:value).returns("Linux") + describe "multiple facts sharing a single model" do + describe "when interfaces are resolved for the first time" do + before :each do + given_initial_interfaces_facts + Facter.value(:interfaces) + end + + it 'lists the interfaces for the interfaces fact' do + expect(Facter.value(:interfaces)).to eq interfaces_hash.keys.join(',') + end + + it 'defines dynamic facts for the interfaces' do + interfaces_hash.keys.each do |interface| + described_class::INTERFACE_KEYS.each do |attr| + expect(Facter.value("#{attr}_#{interface}")). + to eq interfaces_hash[interface][attr] + end + end + end + + it 'defines a dynamic fact for the interfaces networks' do + interfaces_hash.keys.each do |interface| + expect(Facter.value("network_#{interface}")). + to eq interfaces_hash[interface][:network] + end + end end - describe "Facter::Util::Ip.get_interface_value" do + describe "when interface facts have been flushed after being resolved" do before :each do - Facter::Util::IP.stubs(:read_proc_net_bonding). - with("/proc/net/bonding/bond0"). - returns(my_fixture_read("linux_2_6_35_proc_net_bonding_bond0")) + given_initial_interfaces_facts + when_interfaces_facts_have_been_resolved_then_flushed + end + + it "updates the interfaces fact" do + expect(Facter.value(:interfaces)).to eq interfaces_hash2.keys.join(',') + end - Facter::Util::IP.stubs(:get_bonding_master).returns("bond0") + it "defines new dynamic facts for the new interfaces attributes" do + interfaces_hash2.keys.each do |interface| + described_class::INTERFACE_KEYS.each do |attr| + expect(Facter.value("#{attr}_#{interface}")). + to eq interfaces_hash2[interface][attr] + end + end end - it 'provides the real device macaddress for eth0' do - Facter::Util::IP.get_interface_value("eth0", "macaddress").should == "00:11:22:33:44:55" + it "defines a new dynamic fact for the new interfaces network" do + interfaces_hash2.keys.each do |interface| + expect(Facter.value("network_#{interface}")). + to eq interfaces_hash2[interface][:network] + end end - it 'provides the real device macaddress for eth1' do - Facter::Util::IP.get_interface_value("eth1", "macaddress").should == "00:11:22:33:44:56" + end + end + + def given_initial_interfaces_facts + model = described_class.new + model.stubs(:interfaces).returns(interfaces_hash.keys) + model.stubs(:parse!) + model.interfaces_hash = interfaces_hash + described_class.stubs(:new).returns(model) + end + + def when_interfaces_facts_have_been_resolved_then_flushed + interfaces_hash.keys.each do |interface| + described_class::INTERFACE_KEYS.each do |attr| + Facter.value("#{attr}_#{interface}") end end + + model = described_class.new + model.stubs(:interfaces).returns(interfaces_hash2.keys) + model.stubs(:parse!) + model.interfaces_hash = interfaces_hash2 + described_class.stubs(:new).returns(model) + + Facter.flush + Facter.value(:interfaces) end end diff --git a/spec/unit/util/parser_spec.rb b/spec/unit/util/parser_spec.rb index be7b9e7f4d..8a0ae6ee48 100644 --- a/spec/unit/util/parser_spec.rb +++ b/spec/unit/util/parser_spec.rb @@ -1,10 +1,7 @@ -#!/usr/bin/env ruby - require 'spec_helper' - require 'facter/util/parser' require 'tempfile' -require 'tmpdir.rb' +require 'tmpdir' describe Facter::Util::Parser do include PuppetlabsSpec::Files diff --git a/spec/unit/zfs_version_spec.rb b/spec/unit/zfs_version_spec.rb index 905dbc9ee5..9b88392a31 100644 --- a/spec/unit/zfs_version_spec.rb +++ b/spec/unit/zfs_version_spec.rb @@ -1,5 +1,3 @@ -#!/usr/bin/env ruby - require 'spec_helper' describe "zfs_version fact" do diff --git a/spec/unit/zpool_version_spec.rb b/spec/unit/zpool_version_spec.rb index 0cba70fdb6..5952b8f7b0 100644 --- a/spec/unit/zpool_version_spec.rb +++ b/spec/unit/zpool_version_spec.rb @@ -1,5 +1,3 @@ -#!/usr/bin/env ruby - require 'spec_helper' describe "zpool_version fact" do From a8fd80220d9454d54937727fa641be4bd90ee6a0 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Fri, 17 May 2013 12:16:11 -0700 Subject: [PATCH 1261/3753] Revert "Revert "(maint) Replace rspec >= 2.11 expect(foo).to with foo.should"" This reverts commit b4097871e84d7a7b3441130289c594aee859af74. --- spec/unit/util/ip/base_spec.rb | 26 +++---- spec/unit/util/ip/darwin_spec.rb | 14 ++-- spec/unit/util/ip/dragonfly_spec.rb | 6 +- spec/unit/util/ip/free_bsd_spec.rb | 8 +-- spec/unit/util/ip/gnu_k_free_bsd_spec.rb | 12 ++-- spec/unit/util/ip/hpux_spec.rb | 88 ++++++++++++------------ spec/unit/util/ip/linux_spec.rb | 18 ++--- spec/unit/util/ip/net_bsd_spec.rb | 6 +- spec/unit/util/ip/open_bsd_spec.rb | 6 +- spec/unit/util/ip/sun_os_spec.rb | 14 ++-- spec/unit/util/ip/windows_spec.rb | 14 ++-- spec/unit/util/ip_spec.rb | 24 +++---- 12 files changed, 115 insertions(+), 121 deletions(-) diff --git a/spec/unit/util/ip/base_spec.rb b/spec/unit/util/ip/base_spec.rb index 9484c22fa9..b5b1223893 100644 --- a/spec/unit/util/ip/base_spec.rb +++ b/spec/unit/util/ip/base_spec.rb @@ -9,16 +9,16 @@ describe ".subclasses" do let(:subclasses) { described_class.subclasses } - it { expect(subclasses).to be_an Array } + it { subclasses.should be_an Array } it "should be memoized" do - expect(subclasses).to be subclasses + described_class.subclasses().should be described_class.subclasses() end it "should list subclasses" do subclass = Class.new described_class - expect(subclasses).to include subclass + subclasses.should include subclass end end @@ -26,7 +26,7 @@ let(:to_s) { described_class.to_s } it "should return the name of the class without nesting" do - expect(to_s).to eq 'Base' + to_s.should eq 'Base' end end @@ -35,13 +35,13 @@ described_class.convert_netmask_from_hex? end - it { expect(convert_netmask_from_hex?).to be true } + it { convert_netmask_from_hex?.should eq true } end describe ".bonding_master" do let(:bonding_master) { described_class.bonding_master('eth0') } - it { expect(bonding_master).to be_nil } + it { bonding_master.should be_nil } end describe ".interfaces" do @@ -50,7 +50,7 @@ it "uses `ifconfig` to list the interfaces" do described_class.expects(:ifconfig_path).returns('/bin/ifconfig') described_class.expects(:exec).with("/bin/ifconfig -a 2> /dev/null") - expect(interfaces).to be_an Array + interfaces.should be_an Array end end @@ -68,7 +68,7 @@ described_class.expects(:regex_for).with(label).returns(nil) end - it { expect(value_for_interface_and_label).to be_nil } + it { value_for_interface_and_label.should be_nil } end describe "there is a regex for the label" do @@ -89,7 +89,7 @@ let(:match_data) { ['inet 127.0.0.1', '127.0.0.1'] } let(:label) { 'ipaddress' } - it { expect(value_for_interface_and_label).to eq '127.0.0.1' } + it { value_for_interface_and_label.should eq '127.0.0.1' } end describe "the label is 'netmask'" do @@ -102,7 +102,7 @@ described_class.expects(:convert_netmask_from_hex?).returns(true) end - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + it { value_for_interface_and_label.should eq '255.255.255.0' } end describe "the netmask does not need to be converted from hex" do @@ -112,7 +112,7 @@ described_class.expects(:convert_netmask_from_hex?).returns(false) end - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + it { value_for_interface_and_label.should eq '255.255.255.0' } end end end @@ -121,7 +121,7 @@ let(:match_data) { nil } let(:label) { 'ipaddress' } - it { expect(value_for_interface_and_label).to be_nil } + it { value_for_interface_and_label.should be_nil } end end @@ -141,7 +141,7 @@ returns('255.255.255.0') end - it { expect(network).to eq "172.16.15.0" } + it { network.should eq "172.16.15.0" } end end end diff --git a/spec/unit/util/ip/darwin_spec.rb b/spec/unit/util/ip/darwin_spec.rb index b9407f29af..6123ab8054 100644 --- a/spec/unit/util/ip/darwin_spec.rb +++ b/spec/unit/util/ip/darwin_spec.rb @@ -10,7 +10,7 @@ end it "should be 'Darwin'" do - expect(to_s).to eq 'Darwin' + to_s.should eq 'Darwin' end end @@ -20,7 +20,7 @@ end it "should be true" do - expect(convert_netmask_from_hex?).to be true + convert_netmask_from_hex?.should be true end end @@ -29,7 +29,7 @@ described_class.bonding_master('eth0') end - it { expect(bonding_master).to be_nil } + it { bonding_master.should be_nil } end describe ".interfaces" do @@ -47,7 +47,7 @@ end it "should return an array with two interfaces" do - expect(interfaces).to eq ["lo0", "en0"] + interfaces.should eq ["lo0", "en0"] end end @@ -69,19 +69,19 @@ describe "macaddress" do let(:label) { 'macaddress' } - it { expect(value_for_interface_and_label).to eq '00:23:6c:99:60:2b' } + it { value_for_interface_and_label.should eq '00:23:6c:99:60:2b' } end describe "netmask" do let(:label) { 'netmask' } - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + it { value_for_interface_and_label.should eq '255.255.255.0' } end describe "mtu" do let(:label) { 'mtu' } - it { expect(value_for_interface_and_label).to eq '1500' } + it { value_for_interface_and_label.should eq '1500' } end end end diff --git a/spec/unit/util/ip/dragonfly_spec.rb b/spec/unit/util/ip/dragonfly_spec.rb index f2c670a209..796601e803 100644 --- a/spec/unit/util/ip/dragonfly_spec.rb +++ b/spec/unit/util/ip/dragonfly_spec.rb @@ -10,7 +10,7 @@ end it "should be 'Dragonfly'" do - expect(to_s).to eq 'Dragonfly' + to_s.should eq 'Dragonfly' end end @@ -20,7 +20,7 @@ end it "should be true" do - expect(convert_netmask_from_hex?).to be true + convert_netmask_from_hex?.should be true end end @@ -29,6 +29,6 @@ described_class.bonding_master('eth0') end - it { expect(bonding_master).to be_nil } + it { bonding_master.should be_nil } end end diff --git a/spec/unit/util/ip/free_bsd_spec.rb b/spec/unit/util/ip/free_bsd_spec.rb index d69989e492..5a0bc3ad2e 100644 --- a/spec/unit/util/ip/free_bsd_spec.rb +++ b/spec/unit/util/ip/free_bsd_spec.rb @@ -10,7 +10,7 @@ end it "should be 'FreeBSD'" do - expect(to_s).to eq 'FreeBSD' + to_s.should eq 'FreeBSD' end end @@ -20,7 +20,7 @@ end it "should be true" do - expect(convert_netmask_from_hex?).to be true + convert_netmask_from_hex?.should be true end end @@ -29,7 +29,7 @@ described_class.bonding_master('eth0') end - it { expect(bonding_master).to be_nil } + it { bonding_master.should be_nil } end describe ".value_for_interface_and_label(interface, label)" do @@ -50,7 +50,7 @@ describe "macaddress" do let(:label) { 'macaddress' } - it { expect(value_for_interface_and_label).to eq '00:0e:0c:68:67:7c' } + it { value_for_interface_and_label.should eq '00:0e:0c:68:67:7c' } end end end diff --git a/spec/unit/util/ip/gnu_k_free_bsd_spec.rb b/spec/unit/util/ip/gnu_k_free_bsd_spec.rb index 591571d6ec..14d8908507 100644 --- a/spec/unit/util/ip/gnu_k_free_bsd_spec.rb +++ b/spec/unit/util/ip/gnu_k_free_bsd_spec.rb @@ -14,7 +14,7 @@ end it "should be 'GNU/kFreeBSD'" do - expect(to_s).to eq 'GNU/kFreeBSD' + to_s.should eq 'GNU/kFreeBSD' end end @@ -24,7 +24,7 @@ end it "should be true" do - expect(convert_netmask_from_hex?).to be true + convert_netmask_from_hex?.should be true end end @@ -33,7 +33,7 @@ described_class.bonding_master('eth0') end - it { expect(bonding_master).to be_nil } + it { bonding_master.should be_nil } end describe ".interfaces" do @@ -51,7 +51,7 @@ end it "should return an array with six interfaces" do - expect(interfaces).to eq %w[em0 em1 bge0 bge1 lo0 vlan0] + interfaces.should eq %w[em0 em1 bge0 bge1 lo0 vlan0] end end @@ -73,13 +73,13 @@ describe "macaddress" do let(:label) { 'macaddress' } - it { expect(value_for_interface_and_label).to eq '0:11:a:59:67:91' } + it { value_for_interface_and_label.should eq '0:11:a:59:67:91' } end describe "netmask" do let(:label) { 'netmask' } - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + it { value_for_interface_and_label.should eq '255.255.255.0' } end end end diff --git a/spec/unit/util/ip/hpux_spec.rb b/spec/unit/util/ip/hpux_spec.rb index ec3f21bfe1..3874f2a6e3 100644 --- a/spec/unit/util/ip/hpux_spec.rb +++ b/spec/unit/util/ip/hpux_spec.rb @@ -14,7 +14,7 @@ end it "should be 'HP-UX'" do - expect(to_s).to eq 'HP-UX' + to_s.should eq 'HP-UX' end end @@ -24,7 +24,7 @@ end it "should be true" do - expect(convert_netmask_from_hex?).to be true + convert_netmask_from_hex?.should be true end end @@ -33,7 +33,7 @@ described_class.bonding_master('eth0') end - it { expect(bonding_master).to be_nil } + it { bonding_master.should be_nil } end describe ".interfaces" do @@ -51,7 +51,7 @@ end it "should return an array of interfaces" do - expect(interfaces).to eq %w[lan1 lan0 lo0] + interfaces.should eq %w[lan1 lan0 lo0] end end @@ -61,7 +61,7 @@ end it "should return an array of interfaces" do - expect(interfaces).to eq %w[lan1 lan0 lo0] + interfaces.should eq %w[lan1 lan0 lo0] end end end @@ -85,7 +85,7 @@ describe "ipaddress" do let(:label) { 'ipaddress' } - it { expect(value_for_interface_and_label).to eq '10.1.1.6' } + it { value_for_interface_and_label.should eq '10.1.1.6' } end describe "mtu" do @@ -97,7 +97,7 @@ describe "netmask" do let(:label) { 'netmask' } - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + it { value_for_interface_and_label.should eq '255.255.255.0' } end describe "macaddress" do @@ -107,7 +107,7 @@ described_class.expects(:lanscan).returns(lanscan_output) end - it { expect(value_for_interface_and_label).to eq "00:10:79:7B:5C:DE" } + it { value_for_interface_and_label.should eq "00:10:79:7B:5C:DE" } end end @@ -117,7 +117,7 @@ describe "ipaddress" do let(:label) { 'ipaddress' } - it { expect(value_for_interface_and_label).to eq "192.168.3.10" } + it { value_for_interface_and_label.should eq "192.168.3.10" } end describe "mtu" do @@ -129,7 +129,7 @@ describe "netmask" do let(:label) { 'netmask' } - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + it { value_for_interface_and_label.should eq '255.255.255.0' } end describe "macaddress" do @@ -139,7 +139,7 @@ described_class.expects(:lanscan).returns(lanscan_output) end - it { expect(value_for_interface_and_label).to eq "00:30:7F:0C:79:DC" } + it { value_for_interface_and_label.should eq "00:30:7F:0C:79:DC" } end end @@ -149,7 +149,7 @@ describe "ipaddress" do let(:label) { 'ipaddress' } - it { expect(value_for_interface_and_label).to eq "127.0.0.1" } + it { value_for_interface_and_label.should eq "127.0.0.1" } end describe "mtu" do @@ -161,13 +161,13 @@ describe "netmask" do let(:label) { 'netmask' } - it { expect(value_for_interface_and_label).to eq '255.0.0.0' } + it { value_for_interface_and_label.should eq '255.0.0.0' } end describe "macaddress" do let(:label) { 'macaddress' } - it { expect(value_for_interface_and_label).to be_nil } + it { value_for_interface_and_label.should be_nil } end end end @@ -187,13 +187,13 @@ describe "ipaddress" do let(:label) { 'ipaddress' } - it { expect(value_for_interface_and_label).to eq '10.1.54.36' } + it { value_for_interface_and_label.should eq '10.1.54.36' } end describe "netmask" do let(:label) { 'netmask' } - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + it { value_for_interface_and_label.should eq '255.255.255.0' } end describe "macaddress" do @@ -204,7 +204,7 @@ described_class.expects(:lanscan).returns(lanscan_output) end - it { expect(value_for_interface_and_label).to eq macaddress } + it { value_for_interface_and_label.should eq macaddress } end describe "mtu" do @@ -220,13 +220,13 @@ describe "ipaddress" do let(:label) { 'ipaddress' } - it { expect(value_for_interface_and_label).to eq '192.168.30.152' } + it { value_for_interface_and_label.should eq '192.168.30.152' } end describe "netmask" do let(:label) { 'netmask' } - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + it { value_for_interface_and_label.should eq '255.255.255.0' } end describe "macaddress" do @@ -237,7 +237,7 @@ described_class.expects(:lanscan).returns(lanscan_output) end - it { expect(value_for_interface_and_label).to eq macaddress } + it { value_for_interface_and_label.should eq macaddress } end describe "mtu" do @@ -253,13 +253,13 @@ describe "ipaddress" do let(:label) { 'ipaddress' } - it { expect(value_for_interface_and_label).to eq '127.0.0.1' } + it { value_for_interface_and_label.should eq '127.0.0.1' } end describe "netmask" do let(:label) { 'netmask' } - it { expect(value_for_interface_and_label).to eq '255.0.0.0' } + it { value_for_interface_and_label.should eq '255.0.0.0' } end describe "macaddress" do @@ -269,7 +269,7 @@ described_class.expects(:lanscan).returns(lanscan_output) end - it { expect(value_for_interface_and_label).to be_nil } + it { value_for_interface_and_label.should be_nil } end describe "mtu" do @@ -297,13 +297,13 @@ describe "ipaddress" do let(:label) { 'ipaddress' } - it { expect(value_for_interface_and_label).to eq '10.10.0.5' } + it { value_for_interface_and_label.should eq '10.10.0.5' } end describe "netmask" do let(:label) { 'netmask' } - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + it { value_for_interface_and_label.should eq '255.255.255.0' } end describe "macaddress" do @@ -314,7 +314,7 @@ described_class.expects(:lanscan).returns(lanscan_output) end - it { expect(value_for_interface_and_label).to eq macaddress } + it { value_for_interface_and_label.should eq macaddress } end describe "mtu" do @@ -330,13 +330,13 @@ describe "ipaddress" do let(:label) { 'ipaddress' } - it { expect(value_for_interface_and_label).to eq '192.168.3.9' } + it { value_for_interface_and_label.should eq '192.168.3.9' } end describe "netmask" do let(:label) { 'netmask' } - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + it { value_for_interface_and_label.should eq '255.255.255.0' } end describe "macaddress" do @@ -347,7 +347,7 @@ described_class.expects(:lanscan).returns(lanscan_output) end - it { expect(value_for_interface_and_label).to eq macaddress } + it { value_for_interface_and_label.should eq macaddress } end describe "mtu" do @@ -363,13 +363,13 @@ describe "ipaddress" do let(:label) { 'ipaddress' } - it { expect(value_for_interface_and_label).to eq '127.0.0.1' } + it { value_for_interface_and_label.should eq '127.0.0.1' } end describe "netmask" do let(:label) { 'netmask' } - it { expect(value_for_interface_and_label).to eq '255.0.0.0' } + it { value_for_interface_and_label.should eq '255.0.0.0' } end describe "macaddress" do @@ -379,7 +379,7 @@ described_class.expects(:lanscan).returns(lanscan_output) end - it { expect(value_for_interface_and_label).to be_nil } + it { value_for_interface_and_label.should be_nil } end describe "mtu" do @@ -407,13 +407,13 @@ describe "ipaddress" do let(:label) { 'ipaddress' } - it { expect(value_for_interface_and_label).to eq '192.168.30.32' } + it { value_for_interface_and_label.should eq '192.168.30.32' } end describe "netmask" do let(:label) { 'netmask' } - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + it { value_for_interface_and_label.should eq '255.255.255.0' } end describe "macaddress" do @@ -424,7 +424,7 @@ described_class.expects(:lanscan).returns(lanscan_output) end - it { expect(value_for_interface_and_label).to eq macaddress } + it { value_for_interface_and_label.should eq macaddress } end describe "mtu" do @@ -440,13 +440,13 @@ describe "ipaddress" do let(:label) { 'ipaddress' } - it { expect(value_for_interface_and_label).to eq '127.0.0.1' } + it { value_for_interface_and_label.should eq '127.0.0.1' } end describe "netmask" do let(:label) { 'netmask' } - it { expect(value_for_interface_and_label).to eq '255.0.0.0' } + it { value_for_interface_and_label.should eq '255.0.0.0' } end describe "macaddress" do @@ -456,7 +456,7 @@ described_class.expects(:lanscan).returns(lanscan_output) end - it { expect(value_for_interface_and_label).to be_nil } + it { value_for_interface_and_label.should be_nil } end describe "mtu" do @@ -472,13 +472,13 @@ describe "ipaddress" do let(:label) { 'ipaddress' } - it { expect(value_for_interface_and_label).to eq '192.168.32.75' } + it { value_for_interface_and_label.should eq '192.168.32.75' } end describe "netmask" do let(:label) { 'netmask' } - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + it { value_for_interface_and_label.should eq '255.255.255.0' } end describe "macaddress" do @@ -489,7 +489,7 @@ described_class.expects(:lanscan).returns(lanscan_output) end - it { expect(value_for_interface_and_label).to eq macaddress } + it { value_for_interface_and_label.should eq macaddress } end describe "mtu" do @@ -505,13 +505,13 @@ describe "ipaddress" do let(:label) { 'ipaddress' } - it { expect(value_for_interface_and_label).to eq '192.168.1.197' } + it { value_for_interface_and_label.should eq '192.168.1.197' } end describe "netmask" do let(:label) { 'netmask' } - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + it { value_for_interface_and_label.should eq '255.255.255.0' } end describe "macaddress" do @@ -521,7 +521,7 @@ described_class.expects(:lanscan).returns(lanscan_output) end - it { expect(value_for_interface_and_label).to be_nil } + it { value_for_interface_and_label.should be_nil } end describe "mtu" do diff --git a/spec/unit/util/ip/linux_spec.rb b/spec/unit/util/ip/linux_spec.rb index 85383115e7..ccb1083a00 100644 --- a/spec/unit/util/ip/linux_spec.rb +++ b/spec/unit/util/ip/linux_spec.rb @@ -14,7 +14,7 @@ end it "should be 'Linux'" do - expect(to_s).to eq 'Linux' + to_s.should eq 'Linux' end end @@ -24,7 +24,7 @@ end it "should be true" do - expect(convert_netmask_from_hex?).to be false + convert_netmask_from_hex?.should be false end end @@ -38,7 +38,7 @@ "eth0:1" end - it { expect(bonding_master).to be_nil } + it { bonding_master.should be_nil } end end @@ -59,7 +59,7 @@ end it "should return an array with a single interface and the loopback" do - expect(interfaces).to eq ["eth0", "lo"] + interfaces.should eq ["eth0", "lo"] end end @@ -74,7 +74,7 @@ end it "should return an array with a single interface and the loopback" do - expect(interfaces).to eq ["eth0", "lo"] + interfaces.should eq ["eth0", "lo"] end end end @@ -98,7 +98,7 @@ described_class.expects(:infiniband_macaddress).returns('bar') end - it { expect(value_for_interface_and_label).to eq 'bar' } + it { value_for_interface_and_label.should eq 'bar' } end end @@ -121,7 +121,7 @@ describe "mtu" do let(:label) { 'mtu' } - it { expect(value_for_interface_and_label).to eq '1500' } + it { value_for_interface_and_label.should eq '1500' } end end @@ -131,7 +131,7 @@ describe "mtu" do let(:label) { 'mtu' } - it { expect(value_for_interface_and_label).to eq '16436' } + it { value_for_interface_and_label.should eq '16436' } end end end @@ -151,7 +151,7 @@ describe "macaddress" do let(:label) { 'macaddress' } - it { expect(value_for_interface_and_label).to eq "00:11:22:33:44:55" } + it { value_for_interface_and_label.should eq "00:11:22:33:44:55" } end end end diff --git a/spec/unit/util/ip/net_bsd_spec.rb b/spec/unit/util/ip/net_bsd_spec.rb index 489e82b2b4..e2a736ef5e 100644 --- a/spec/unit/util/ip/net_bsd_spec.rb +++ b/spec/unit/util/ip/net_bsd_spec.rb @@ -10,7 +10,7 @@ end it "should be 'NetBSD'" do - expect(to_s).to eq 'NetBSD' + to_s.should eq 'NetBSD' end end @@ -20,7 +20,7 @@ end it "should be true" do - expect(convert_netmask_from_hex?).to be true + convert_netmask_from_hex?.should be true end end @@ -29,6 +29,6 @@ described_class.bonding_master('eth0') end - it { expect(bonding_master).to be_nil } + it { bonding_master.should be_nil } end end diff --git a/spec/unit/util/ip/open_bsd_spec.rb b/spec/unit/util/ip/open_bsd_spec.rb index 0a9508dc60..66f9f31fd2 100644 --- a/spec/unit/util/ip/open_bsd_spec.rb +++ b/spec/unit/util/ip/open_bsd_spec.rb @@ -10,7 +10,7 @@ end it "should be 'OpenBSD'" do - expect(to_s).to eq 'OpenBSD' + to_s.should eq 'OpenBSD' end end @@ -20,7 +20,7 @@ end it "should be true" do - expect(convert_netmask_from_hex?).to be true + convert_netmask_from_hex?.should be true end end @@ -29,6 +29,6 @@ described_class.bonding_master('eth0') end - it { expect(bonding_master).to be_nil } + it { bonding_master.should be_nil } end end diff --git a/spec/unit/util/ip/sun_os_spec.rb b/spec/unit/util/ip/sun_os_spec.rb index 23e366612b..61c5edc366 100644 --- a/spec/unit/util/ip/sun_os_spec.rb +++ b/spec/unit/util/ip/sun_os_spec.rb @@ -14,7 +14,7 @@ end it "should be 'SunOS'" do - expect(to_s).to eq 'SunOS' + to_s.should eq 'SunOS' end end @@ -24,7 +24,7 @@ end it "should be true" do - expect(convert_netmask_from_hex?).to be true + convert_netmask_from_hex?.should be true end end @@ -33,7 +33,7 @@ described_class.bonding_master('eth0') end - it { expect(bonding_master).to be_nil } + it { bonding_master.should be_nil } end describe ".interfaces" do @@ -51,7 +51,7 @@ end it "should return an array with two interfaces" do - expect(interfaces).to eq ["lo0", "e1000g0"] + interfaces.should eq ["lo0", "e1000g0"] end end @@ -73,19 +73,19 @@ describe "ipaddress" do let(:label) { "ipaddress" } - it { expect(value_for_interface_and_label).to eq "172.16.15.138" } + it { value_for_interface_and_label.should eq "172.16.15.138" } end describe "netmask" do let(:label) { "netmask" } - it { expect(value_for_interface_and_label).to eq "255.255.255.0" } + it { value_for_interface_and_label.should eq "255.255.255.0" } end describe "mtu" do let(:label) { "mtu" } - it { expect(value_for_interface_and_label).to eq '1500' } + it { value_for_interface_and_label.should eq '1500' } end end end diff --git a/spec/unit/util/ip/windows_spec.rb b/spec/unit/util/ip/windows_spec.rb index 09fc3fb68c..487533ee6e 100644 --- a/spec/unit/util/ip/windows_spec.rb +++ b/spec/unit/util/ip/windows_spec.rb @@ -11,7 +11,7 @@ describe ".to_s" do let(:to_s) { described_class.to_s } - it { expect(to_s).to eq 'windows' } + it { to_s.should eq 'windows' } end describe ".convert_netmask_from_hex?" do @@ -19,13 +19,13 @@ described_class.convert_netmask_from_hex? end - it { expect(convert_netmask_from_hex?).to be false } + it { convert_netmask_from_hex?.should be false } end describe ".bonding_master" do let(:bonding_master) { described_class.bonding_master('eth0') } - it { expect(bonding_master).to be_nil } + it { bonding_master.should be_nil } end describe ".interfaces" do @@ -46,7 +46,7 @@ end it "should return an array of only connected interfaces" do - expect(interfaces).to eq expected_interfaces + interfaces.should eq expected_interfaces end end @@ -69,13 +69,13 @@ describe "ipaddress" do let(:label) { 'ipaddress' } - it { expect(value_for_interface_and_label).to eq '172.16.138.216' } + it { value_for_interface_and_label.should eq '172.16.138.216' } end describe "netmask" do let(:label) { 'netmask' } - it { expect(value_for_interface_and_label).to eq '255.255.255.0' } + it { value_for_interface_and_label.should eq '255.255.255.0' } end describe "ipaddress6" do @@ -88,7 +88,7 @@ "#{described_class::NETSH} interface ipv6 show address \"#{interface}\"" end - it { expect(value_for_interface_and_label).to eq expected_ip } + it { value_for_interface_and_label.should eq expected_ip } end end end diff --git a/spec/unit/util/ip_spec.rb b/spec/unit/util/ip_spec.rb index 1707d065c3..910a4baf21 100755 --- a/spec/unit/util/ip_spec.rb +++ b/spec/unit/util/ip_spec.rb @@ -86,22 +86,20 @@ end it "defines the 'interfaces' fact" do - expect(Facter.fact(:interfaces)).to be_a_kind_of Facter::Util::Fact + Facter.fact(:interfaces).should be_a_kind_of Facter::Util::Fact end it "defines a fact for each attribute of an interface" do interfaces_hash.keys.each do |interface| described_class::INTERFACE_KEYS.each do |attr| - expect(Facter.fact("#{attr}_#{interface}")). - to be_a_kind_of Facter::Util::Fact + Facter.fact("#{attr}_#{interface}").should be_a_kind_of Facter::Util::Fact end end end it "defines a fact for an interface's network" do interfaces_hash.keys.each do |interface| - expect(Facter.fact("network_#{interface}")). - to be_a_kind_of Facter::Util::Fact + Facter.fact("network_#{interface}").should be_a_kind_of Facter::Util::Fact end end end @@ -114,22 +112,20 @@ end it 'lists the interfaces for the interfaces fact' do - expect(Facter.value(:interfaces)).to eq interfaces_hash.keys.join(',') + Facter.value(:interfaces).should eq interfaces_hash.keys.join(',') end it 'defines dynamic facts for the interfaces' do interfaces_hash.keys.each do |interface| described_class::INTERFACE_KEYS.each do |attr| - expect(Facter.value("#{attr}_#{interface}")). - to eq interfaces_hash[interface][attr] + Facter.value("#{attr}_#{interface}").should eq interfaces_hash[interface][attr] end end end it 'defines a dynamic fact for the interfaces networks' do interfaces_hash.keys.each do |interface| - expect(Facter.value("network_#{interface}")). - to eq interfaces_hash[interface][:network] + Facter.value("network_#{interface}").should eq interfaces_hash[interface][:network] end end end @@ -141,22 +137,20 @@ end it "updates the interfaces fact" do - expect(Facter.value(:interfaces)).to eq interfaces_hash2.keys.join(',') + Facter.value(:interfaces).should eq interfaces_hash2.keys.join(',') end it "defines new dynamic facts for the new interfaces attributes" do interfaces_hash2.keys.each do |interface| described_class::INTERFACE_KEYS.each do |attr| - expect(Facter.value("#{attr}_#{interface}")). - to eq interfaces_hash2[interface][attr] + Facter.value("#{attr}_#{interface}").should eq interfaces_hash2[interface][attr] end end end it "defines a new dynamic fact for the new interfaces network" do interfaces_hash2.keys.each do |interface| - expect(Facter.value("network_#{interface}")). - to eq interfaces_hash2[interface][:network] + Facter.value("network_#{interface}").should eq interfaces_hash2[interface][:network] end end end From abf58ec59d95eb70142f57410f0e577d4895d057 Mon Sep 17 00:00:00 2001 From: Jae Park Date: Tue, 21 May 2013 13:44:36 -0700 Subject: [PATCH 1262/3753] Remove redundant attr_accessor :interpreter To prevent warnings. Both reader and writer are defined at line 417. --- lib/facter/util/resolution.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 6538bd04e2..7465a91238 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -23,7 +23,7 @@ class Facter::Util::Resolution attr_accessor :timeout # @api private - attr_accessor :interpreter, :code, :name + attr_accessor :code, :name attr_writer :value, :weight INTERPRETER = Facter::Util::Config.is_windows? ? "cmd.exe" : "/bin/sh" From 3f52ed1768e90411e953d1277d9451a39d30fe55 Mon Sep 17 00:00:00 2001 From: Lance Dillon Date: Tue, 30 Apr 2013 09:39:05 -0400 Subject: [PATCH 1263/3753] Break out code to parse sysfs into separate function (#19249) Split ProcessorCount to separate TotalProcessorCount and ActiveProcessorCount facts --- lib/facter/processor.rb | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb index c4ab386c73..37f4f36d64 100644 --- a/lib/facter/processor.rb +++ b/lib/facter/processor.rb @@ -45,6 +45,34 @@ end end +Facter.add("ActiveProcessorCount") do + confine :kernel => [ :linux, :"gnu/kfreebsd" ] + setcode do + processor_list = Facter::Util::Processor.enum_cpuinfo + + aprocs = processor_list.compact + if aprocs.length != 0 + aprocs.length.to_s + end + end +end + +def sysfs_proc_count() + sysfs_cpu_directory = '/sys/devices/system/cpu' + if File.exists?(sysfs_cpu_directory) + lookup_pattern = "#{sysfs_cpu_directory}" + "/cpu[0-9]*" + cpuCount = Dir.glob(lookup_pattern).length + cpuCount.to_s + end +end + +Facter.add("TotalProcessorCount") do + confine :kernel => [ :linux, :"gnu/kfreebsd" ] + setcode do + sysfs_proc_count + end +end + Facter.add("ProcessorCount") do confine :kernel => [ :linux, :"gnu/kfreebsd" ] setcode do @@ -62,12 +90,7 @@ setcode do ## The method above is preferable since it provides the description of the CPU as well ## but if that returned 0, then we enumerate sysfs - sysfs_cpu_directory = '/sys/devices/system/cpu' - if File.exists?(sysfs_cpu_directory) - lookup_pattern = "#{sysfs_cpu_directory}" + "/cpu[0-9]*" - cpuCount = Dir.glob(lookup_pattern).length - cpuCount.to_s - end + sysfs_proc_count end end From 47fba1c8429501f8d1a30744acf56134c2dfc91c Mon Sep 17 00:00:00 2001 From: Lance Dillon Date: Tue, 21 May 2013 14:28:10 -0400 Subject: [PATCH 1264/3753] move sysfs_proc_count to util/processor.rb change processorcount to be same as totalprocessorcount, probably will remove totalprocessorcount --- lib/facter/processor.rb | 17 ++++------------- lib/facter/util/processor.rb | 9 +++++++++ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb index 37f4f36d64..7b677b414b 100644 --- a/lib/facter/processor.rb +++ b/lib/facter/processor.rb @@ -51,34 +51,25 @@ processor_list = Facter::Util::Processor.enum_cpuinfo aprocs = processor_list.compact + ## If this returned nothing, then don't resolve the fact if aprocs.length != 0 aprocs.length.to_s end end end -def sysfs_proc_count() - sysfs_cpu_directory = '/sys/devices/system/cpu' - if File.exists?(sysfs_cpu_directory) - lookup_pattern = "#{sysfs_cpu_directory}" + "/cpu[0-9]*" - cpuCount = Dir.glob(lookup_pattern).length - cpuCount.to_s - end -end - Facter.add("TotalProcessorCount") do confine :kernel => [ :linux, :"gnu/kfreebsd" ] setcode do - sysfs_proc_count + Facter::Util::Processor.sysfs_proc_count end end Facter.add("ProcessorCount") do confine :kernel => [ :linux, :"gnu/kfreebsd" ] setcode do - processor_list = Facter::Util::Processor.enum_cpuinfo + processor_list = Facter::Util::Processor.sysfs_proc_count - ## If this returned nothing, then don't resolve the fact if processor_list.length != 0 processor_list.length.to_s end @@ -90,7 +81,7 @@ def sysfs_proc_count() setcode do ## The method above is preferable since it provides the description of the CPU as well ## but if that returned 0, then we enumerate sysfs - sysfs_proc_count + Facter::Util::Processor.sysfs_proc_count end end diff --git a/lib/facter/util/processor.rb b/lib/facter/util/processor.rb index 335dcf6fa5..73b7d32916 100644 --- a/lib/facter/util/processor.rb +++ b/lib/facter/util/processor.rb @@ -211,6 +211,15 @@ def self.getconf_cpu_chip_type(command="getconf CPU_CHIP_TYPE") Facter::Util::Resolution.exec(command) end + def self.sysfs_proc_count + sysfs_cpu_directory = '/sys/devices/system/cpu' + if File.exists?(sysfs_cpu_directory) + lookup_pattern = "#{sysfs_cpu_directory}" + "/cpu[0-9]*" + cpuCount = Dir.glob(lookup_pattern).length + cpuCount.to_s + end + end + def self.enum_cpuinfo processor_num = -1 processor_list = [] From 39bd7e2e34ff6a7ee15e36521ef84280be28606f Mon Sep 17 00:00:00 2001 From: Lance Dillon Date: Tue, 21 May 2013 17:58:57 -0400 Subject: [PATCH 1265/3753] Refactored to eliminate code duplication --- lib/facter/processor.rb | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb index 7b677b414b..0d4a981aa4 100644 --- a/lib/facter/processor.rb +++ b/lib/facter/processor.rb @@ -65,23 +65,22 @@ end end -Facter.add("ProcessorCount") do +Facter.add(:processorcount) do confine :kernel => [ :linux, :"gnu/kfreebsd" ] + has_weight 10 setcode do - processor_list = Facter::Util::Processor.sysfs_proc_count - - if processor_list.length != 0 - processor_list.length.to_s + output = Facter.value(:activeprocessorcount) + if output != 0 + output end end end -Facter.add("ProcessorCount") do +Facter.add(:processorcount) do confine :kernel => [ :linux, :"gnu/kfreebsd" ] + has_weight 1 setcode do - ## The method above is preferable since it provides the description of the CPU as well - ## but if that returned 0, then we enumerate sysfs - Facter::Util::Processor.sysfs_proc_count + Facter.value(:totalprocessorcount) end end From 13db9b5e374e68035fb9e05daff329990e21b899 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Wed, 22 May 2013 10:52:52 -0700 Subject: [PATCH 1266/3753] (maint) refactor processor specs, test sysfs fallback --- spec/unit/processor_spec.rb | 148 ++++++++++++++++++++---------------- 1 file changed, 83 insertions(+), 65 deletions(-) diff --git a/spec/unit/processor_spec.rb b/spec/unit/processor_spec.rb index aa47127218..debcec2987 100755 --- a/spec/unit/processor_spec.rb +++ b/spec/unit/processor_spec.rb @@ -68,91 +68,109 @@ def load(procs) end end - describe "on Unixes" do - before :each do - Facter.collection.internal_loader.load(:processor) - end + describe "on Linux" do - it "should be 1 in SPARC fixture" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:operatingsystem).stubs(:value).returns("Linux") - Facter.fact(:architecture).stubs(:value).returns("sparc") - File.stubs(:exists?).with("/proc/cpuinfo").returns(true) - File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("sparc")) + shared_context 'Linux processor stubs' do + before :each do + Facter.collection.internal_loader.load(:processor) - Facter.fact(:processorcount).value.should == "1" + Facter.fact(:kernel).stubs(:value).returns 'Linux' + Facter.fact(:operatingsystem).stubs(:value).returns 'Linux' + File.stubs(:exists?).with("/proc/cpuinfo").returns(true) + end end - it "should be 2 in ppc64 fixture on Linux" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:architecture).stubs(:value).returns("ppc64") - File.stubs(:exists?).with("/proc/cpuinfo").returns(true) - File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("ppc64")) + shared_examples_for 'a /proc/cpuinfo based processor fact' do |processor_fact| + include_context 'Linux processor stubs' - Facter.fact(:processorcount).value.should == "2" - end + it "should be 1 in SPARC fixture" do + Facter.fact(:architecture).stubs(:value).returns("sparc") + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("sparc")) - it "should be 2 in panda-armel fixture on Linux" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:architecture).stubs(:value).returns("arm") - File.stubs(:exists?).with("/proc/cpuinfo").returns(true) - File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("panda-armel")) + Facter.fact(processor_fact).value.should == "1" + end - Facter.fact(:processorcount).value.should == "2" - end + it "should be 2 in ppc64 fixture on Linux" do + Facter.fact(:architecture).stubs(:value).returns("ppc64") + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("ppc64")) - it "should be 1 in bbg3-armel fixture on Linux" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:architecture).stubs(:value).returns("arm") - File.stubs(:exists?).with("/proc/cpuinfo").returns(true) - File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("bbg3-armel")) + Facter.fact(processor_fact).value.should == "2" + end - Facter.fact(:processorcount).value.should == "1" - end + it "should be 2 in panda-armel fixture on Linux" do + Facter.fact(:architecture).stubs(:value).returns("arm") + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("panda-armel")) - it "should be 1 in beaglexm-armel fixture on Linux" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:architecture).stubs(:value).returns("arm") - File.stubs(:exists?).with("/proc/cpuinfo").returns(true) - File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("beaglexm-armel")) + Facter.fact(processor_fact).value.should == "2" + end - Facter.fact(:processorcount).value.should == "1" - end + it "should be 1 in bbg3-armel fixture on Linux" do + Facter.fact(:architecture).stubs(:value).returns("arm") + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("bbg3-armel")) - it "should be 1 in amd64solo fixture on Linux" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:architecture).stubs(:value).returns("amd64") - File.stubs(:exists?).with("/proc/cpuinfo").returns(true) - File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("amd64solo")) + Facter.fact(processor_fact).value.should == "1" + end - Facter.fact(:processorcount).value.should == "1" - end + it "should be 1 in beaglexm-armel fixture on Linux" do + Facter.fact(:architecture).stubs(:value).returns("arm") + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("beaglexm-armel")) - it "should be 2 in amd64dual fixture on Linux" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:architecture).stubs(:value).returns("amd64") - File.stubs(:exists?).with("/proc/cpuinfo").returns(true) - File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("amd64dual")) + Facter.fact(processor_fact).value.should == "1" + end - Facter.fact(:processorcount).value.should == "2" - end + it "should be 1 in amd64solo fixture on Linux" do + Facter.fact(:architecture).stubs(:value).returns("amd64") + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("amd64solo")) - it "should be 3 in amd64tri fixture on Linux" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:architecture).stubs(:value).returns("amd64") - File.stubs(:exists?).with("/proc/cpuinfo").returns(true) - File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("amd64tri")) + Facter.fact(processor_fact).value.should == "1" + end + + it "should be 2 in amd64dual fixture on Linux" do + Facter.fact(:architecture).stubs(:value).returns("amd64") + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("amd64dual")) + + Facter.fact(processor_fact).value.should == "2" + end + + it "should be 3 in amd64tri fixture on Linux" do + Facter.fact(:architecture).stubs(:value).returns("amd64") + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("amd64tri")) + + Facter.fact(processor_fact).value.should == "3" + end - Facter.fact(:processorcount).value.should == "3" + it "should be 4 in amd64quad fixture on Linux" do + Facter.fact(:architecture).stubs(:value).returns("amd64") + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("amd64quad")) + + Facter.fact(processor_fact).value.should == "4" + end end - it "should be 4 in amd64quad fixture on Linux" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:architecture).stubs(:value).returns("amd64") - File.stubs(:exists?).with("/proc/cpuinfo").returns(true) - File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("amd64quad")) + it_behaves_like 'a /proc/cpuinfo based processor fact', :activeprocessorcount + it_behaves_like 'a /proc/cpuinfo based processor fact', :processorcount + + describe 'when /proc/cpuinfo returns 0 processors (#2945)' do + include_context 'Linux processor stubs' + before do + File.stubs(:readlines).with("/proc/cpuinfo").returns([]) + end + + it 'enumerates sysfs for the processorcount' do + File.stubs(:exists?).with('/sys/devices/system/cpu').returns(true) + Dir.stubs(:glob).with("/sys/devices/system/cpu/cpu[0-9]*").returns(%w{ + /sys/devices/system/cpu/cpu0 + /sys/devices/system/cpu/cpu1 + }) - Facter.fact(:processorcount).value.should == "4" + Facter.fact(:processorcount).value.should == '2' + end + end + end + + describe "on Unixes" do + before :each do + Facter.collection.internal_loader.load(:processor) end it "should be 2 on dual-processor Darwin box" do From a956476ebf00ea48e9e7a010e170a72657522847 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Wed, 22 May 2013 10:58:31 -0700 Subject: [PATCH 1267/3753] (maint) downcase processorcount fact names --- lib/facter/processor.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb index 0d4a981aa4..093cd857d1 100644 --- a/lib/facter/processor.rb +++ b/lib/facter/processor.rb @@ -45,7 +45,7 @@ end end -Facter.add("ActiveProcessorCount") do +Facter.add(:activeprocessorcount) do confine :kernel => [ :linux, :"gnu/kfreebsd" ] setcode do processor_list = Facter::Util::Processor.enum_cpuinfo @@ -58,7 +58,7 @@ end end -Facter.add("TotalProcessorCount") do +Facter.add(:totalprocessorcount) do confine :kernel => [ :linux, :"gnu/kfreebsd" ] setcode do Facter::Util::Processor.sysfs_proc_count From 65c70e1a6797f4899ff0646bbd239dd820aec8ba Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Wed, 22 May 2013 11:19:40 -0700 Subject: [PATCH 1268/3753] (maint) Refactor specs for totalprocessorcount --- spec/unit/processor_spec.rb | 72 +++++++++++++++---------------------- 1 file changed, 28 insertions(+), 44 deletions(-) diff --git a/spec/unit/processor_spec.rb b/spec/unit/processor_spec.rb index debcec2987..9eaf773758 100755 --- a/spec/unit/processor_spec.rb +++ b/spec/unit/processor_spec.rb @@ -166,6 +166,34 @@ def load(procs) Facter.fact(:processorcount).value.should == '2' end end + + describe 'totalprocessorcount' do + def sysfs_cpu_stubs(count) + (0...count).map { |index| "/sys/devices/system/cpu/cpu#{index}" } + end + + include_context 'Linux processor stubs' + + before do + File.stubs(:exists?).with('/sys/devices/system/cpu').returns(true) + end + + it "should be 2 via sysfs when cpu0 and cpu1 are present" do + Dir.stubs(:glob).with("/sys/devices/system/cpu/cpu[0-9]*").returns( + sysfs_cpu_stubs(2) + ) + + Facter.fact(:totalprocessorcount).value.should == "2" + end + + it "should be 16 via sysfs when cpu0 through cpu15 are present" do + Dir.stubs(:glob).with("/sys/devices/system/cpu/cpu[0-9]*").returns( + sysfs_cpu_stubs(16) + ) + + Facter.fact(:totalprocessorcount).value.should == "16" + end + end end describe "on Unixes" do @@ -201,56 +229,12 @@ def load(procs) Facter.fact(:processor).value.should == "SomeVendor CPU 3GHz" end - it "should be 2 on dual-processor DragonFly box" do Facter.fact(:kernel).stubs(:value).returns("DragonFly") Facter::Util::Resolution.stubs(:exec).with("sysctl -n hw.ncpu").returns('2') Facter.fact(:processorcount).value.should == "2" end - - it "should be 2 via sysfs when cpu0 and cpu1 are present" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - File.stubs(:exists?).with('/sys/devices/system/cpu').returns(true) - ## sysfs method is only used if cpuinfo method returned no processors - File.stubs(:exists?).with("/proc/cpuinfo").returns(true) - File.stubs(:readlines).with("/proc/cpuinfo").returns([]) - Dir.stubs(:glob).with("/sys/devices/system/cpu/cpu[0-9]*").returns(%w{ - /sys/devices/system/cpu/cpu0 - /sys/devices/system/cpu/cpu1 - }) - - Facter.fact(:processorcount).value.should == "2" - end - - it "should be 16 via sysfs when cpu0 through cpu15 are present" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - File.stubs(:exists?).with('/sys/devices/system/cpu').returns(true) - ## sysfs method is only used if cpuinfo method returned no processors - File.stubs(:exists?).with("/proc/cpuinfo").returns(true) - File.stubs(:readlines).with("/proc/cpuinfo").returns([]) - Dir.stubs(:glob).with("/sys/devices/system/cpu/cpu[0-9]*").returns(%w{ - /sys/devices/system/cpu/cpu0 - /sys/devices/system/cpu/cpu1 - /sys/devices/system/cpu/cpu2 - /sys/devices/system/cpu/cpu3 - /sys/devices/system/cpu/cpu4 - /sys/devices/system/cpu/cpu5 - /sys/devices/system/cpu/cpu6 - /sys/devices/system/cpu/cpu7 - /sys/devices/system/cpu/cpu8 - /sys/devices/system/cpu/cpu9 - /sys/devices/system/cpu/cpu10 - /sys/devices/system/cpu/cpu11 - /sys/devices/system/cpu/cpu12 - /sys/devices/system/cpu/cpu13 - /sys/devices/system/cpu/cpu14 - /sys/devices/system/cpu/cpu15 - }) - - Facter.fact(:processorcount).value.should == "16" - end - describe "on solaris" do before :all do @fixture_kstat_sparc = File.read(fixtures('processorcount','solaris-sparc-kstat-cpu-info')) From 38fe6ea2a08317a447c17eede3d320617707d6cd Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Wed, 22 May 2013 11:21:10 -0700 Subject: [PATCH 1269/3753] (maint) refactor shared processorcount/totalprocessorcount behavior --- spec/unit/processor_spec.rb | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/spec/unit/processor_spec.rb b/spec/unit/processor_spec.rb index 9eaf773758..0c1a70d3e0 100755 --- a/spec/unit/processor_spec.rb +++ b/spec/unit/processor_spec.rb @@ -150,6 +150,10 @@ def load(procs) it_behaves_like 'a /proc/cpuinfo based processor fact', :activeprocessorcount it_behaves_like 'a /proc/cpuinfo based processor fact', :processorcount + def sysfs_cpu_stubs(count) + (0...count).map { |index| "/sys/devices/system/cpu/cpu#{index}" } + end + describe 'when /proc/cpuinfo returns 0 processors (#2945)' do include_context 'Linux processor stubs' before do @@ -158,20 +162,15 @@ def load(procs) it 'enumerates sysfs for the processorcount' do File.stubs(:exists?).with('/sys/devices/system/cpu').returns(true) - Dir.stubs(:glob).with("/sys/devices/system/cpu/cpu[0-9]*").returns(%w{ - /sys/devices/system/cpu/cpu0 - /sys/devices/system/cpu/cpu1 - }) + Dir.stubs(:glob).with("/sys/devices/system/cpu/cpu[0-9]*").returns( + sysfs_cpu_stubs(2) + ) Facter.fact(:processorcount).value.should == '2' end end describe 'totalprocessorcount' do - def sysfs_cpu_stubs(count) - (0...count).map { |index| "/sys/devices/system/cpu/cpu#{index}" } - end - include_context 'Linux processor stubs' before do From d249246bf7ce1d9f0c8488e80713f37f7c66e25e Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Wed, 17 Apr 2013 23:53:47 -0400 Subject: [PATCH 1270/3753] (#20229) Fix per-interface netmask_* facts with net-tools 1.60 Without this patch the interface specific facts for the netmask are not correctly parsed on systems with net-tools 1.60 or later, such as Archlinux. This patch addresses the problem by adjusting the regular expression to take into account the new format of the ifconfig command. This patch is specific to the netmask_* facts and does not affect the behavior of the general `netmask` fact. --- lib/facter/util/ip.rb | 2 +- .../interfaces/ifconfig_net_tools_1.60.txt | 19 ++++++++++++ .../ifconfig_net_tools_1.60.txt.em1 | 10 +++++++ .../interfaces/ifconfig_net_tools_1.60.txt.lo | 8 +++++ spec/unit/interfaces_spec.rb | 29 +++++++++++++++++++ 5 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 spec/fixtures/unit/interfaces/ifconfig_net_tools_1.60.txt create mode 100644 spec/fixtures/unit/interfaces/ifconfig_net_tools_1.60.txt.em1 create mode 100644 spec/fixtures/unit/interfaces/ifconfig_net_tools_1.60.txt.lo diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index 63e74bb116..cb035e3b1f 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -8,7 +8,7 @@ module Facter::Util::IP :ipaddress => /inet (?:addr:)?([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, :ipaddress6 => /inet6 (?:addr: )?((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/, :macaddress => /(?:ether|HWaddr)\s+((\w{1,2}:){5,}\w{1,2})/, - :netmask => /Mask:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, + :netmask => /(?:Mask:|netmask )([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, :mtu => /MTU:(\d+)/ }, :bsd => { diff --git a/spec/fixtures/unit/interfaces/ifconfig_net_tools_1.60.txt b/spec/fixtures/unit/interfaces/ifconfig_net_tools_1.60.txt new file mode 100644 index 0000000000..dcfdaf1232 --- /dev/null +++ b/spec/fixtures/unit/interfaces/ifconfig_net_tools_1.60.txt @@ -0,0 +1,19 @@ +em1: flags=4163 mtu 1500 + inet 131.252.209.153 netmask 255.255.255.0 broadcast 192.168.76.255 + inet6 2610:10:20:209:212:3fff:febe:2201 prefixlen 128 scopeid 0x0 + inet6 fe80::221:ccff:fe4b:297d prefixlen 64 scopeid 0x20 + ether 00:21:cc:4b:29:7d txqueuelen 1000 (Ethernet) + RX packets 27222144 bytes 31247414760 (29.1 GiB) + RX errors 0 dropped 0 overruns 0 frame 0 + TX packets 10259038 bytes 7784519352 (7.2 GiB) + TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 + device interrupt 20 memory 0xd2600000-d2620000 + +lo: flags=73 mtu 16436 + inet 127.0.0.1 netmask 255.0.0.0 + inet6 ::1 prefixlen 128 scopeid 0x10 + loop txqueuelen 0 (Local Loopback) + RX packets 257371 bytes 37104110 (35.3 MiB) + RX errors 0 dropped 0 overruns 0 frame 0 + TX packets 257371 bytes 37104110 (35.3 MiB) + TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 diff --git a/spec/fixtures/unit/interfaces/ifconfig_net_tools_1.60.txt.em1 b/spec/fixtures/unit/interfaces/ifconfig_net_tools_1.60.txt.em1 new file mode 100644 index 0000000000..e06be7ff0e --- /dev/null +++ b/spec/fixtures/unit/interfaces/ifconfig_net_tools_1.60.txt.em1 @@ -0,0 +1,10 @@ +em1: flags=4163 mtu 1500 + inet 131.252.209.153 netmask 255.255.255.0 broadcast 192.168.76.255 + inet6 2610:10:20:209:212:3fff:febe:2201 prefixlen 128 scopeid 0x0 + inet6 fe80::221:ccff:fe4b:297d prefixlen 64 scopeid 0x20 + ether 00:21:cc:4b:29:7d txqueuelen 1000 (Ethernet) + RX packets 27222144 bytes 31247414760 (29.1 GiB) + RX errors 0 dropped 0 overruns 0 frame 0 + TX packets 10259038 bytes 7784519352 (7.2 GiB) + TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 + device interrupt 20 memory 0xd2600000-d2620000 diff --git a/spec/fixtures/unit/interfaces/ifconfig_net_tools_1.60.txt.lo b/spec/fixtures/unit/interfaces/ifconfig_net_tools_1.60.txt.lo new file mode 100644 index 0000000000..6101930f1c --- /dev/null +++ b/spec/fixtures/unit/interfaces/ifconfig_net_tools_1.60.txt.lo @@ -0,0 +1,8 @@ +lo: flags=73 mtu 16436 + inet 127.0.0.1 netmask 255.0.0.0 + inet6 ::1 prefixlen 128 scopeid 0x10 + loop txqueuelen 0 (Local Loopback) + RX packets 257371 bytes 37104110 (35.3 MiB) + RX errors 0 dropped 0 overruns 0 frame 0 + TX packets 257371 bytes 37104110 (35.3 MiB) + TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 diff --git a/spec/unit/interfaces_spec.rb b/spec/unit/interfaces_spec.rb index 3798721ebd..159d04032c 100755 --- a/spec/unit/interfaces_spec.rb +++ b/spec/unit/interfaces_spec.rb @@ -3,6 +3,22 @@ require 'spec_helper' require 'facter/util/ip' +shared_examples_for "netmask from ifconfig output" do |platform, interface, address, fixture| + it "correctly on #{platform} interface #{interface}" do + Facter::Util::IP.stubs(:exec_ifconfig).returns(my_fixture_read(fixture)) + Facter::Util::IP.stubs(:get_output_for_interface_and_label). + returns(my_fixture_read("#{fixture}.#{interface}")) + Facter.collection.internal_loader.load(:interfaces) + + fact = Facter.fact("netmask_#{interface}".intern) + fact.value.should eq(address) + end +end + +RSpec.configure do |config| + config.alias_it_should_behave_like_to :example_behavior_for, "parses" +end + describe "Per Interface IP facts" do it "should replace the ':' in an interface list with '_'" do # So we look supported @@ -19,3 +35,16 @@ Facter.fact(:interfaces).value.should == %{Local_Area_Connection,Loopback__Pseudo_Interface____1_} end end + +describe "Netmask handling on Linux" do + before :each do + Facter.fact(:kernel).stubs(:value).returns("Linux") + end + + example_behavior_for "netmask from ifconfig output", + "Archlinux (net-tools 1.60)", "em1", + "255.255.255.0", "ifconfig_net_tools_1.60.txt" + example_behavior_for "netmask from ifconfig output", + "Archlinux (net-tools 1.60)", "lo", + "255.0.0.0", "ifconfig_net_tools_1.60.txt" +end From 928b7e8b3638a05ab17bf66eafdf740cc65f6fc1 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Thu, 18 Apr 2013 16:11:08 -0400 Subject: [PATCH 1271/3753] (#20229) Fix netmask fact with Linux net-tools 1.60 Without this patch the netmask fact does not return a value on Archlinux because of the new format of the net-tools package. This patch addresses the problem by updating the regular expressions for the netmask fact. Both the old format and the new format are covered by the regular expression now. --- lib/facter/util/netmask.rb | 2 +- .../unit/netmask/ifconfig_net_tools_1.60.txt | 19 +++++++++++ .../unit/netmask/ifconfig_ubuntu_1204.txt | 16 ++++++++++ spec/unit/interfaces_spec.rb | 6 ++-- spec/unit/netmask_spec.rb | 32 +++++++++++++++++++ 5 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 spec/fixtures/unit/netmask/ifconfig_net_tools_1.60.txt create mode 100644 spec/fixtures/unit/netmask/ifconfig_ubuntu_1204.txt create mode 100644 spec/unit/netmask_spec.rb diff --git a/lib/facter/util/netmask.rb b/lib/facter/util/netmask.rb index 4a6d03ca93..7f1d2794f6 100644 --- a/lib/facter/util/netmask.rb +++ b/lib/facter/util/netmask.rb @@ -8,7 +8,7 @@ def self.get_netmask when 'Linux' ops = { :ifconfig_opts => ['2>/dev/null'], - :regex => %r{\s+ inet\saddr: #{Facter.ipaddress} .*? Mask: (#{ipregex})}x, + :regex => %r{#{Facter.ipaddress}.*?(?:Mask:|netmask)\s*(#{ipregex})}x, :munge => nil, } when 'SunOS' diff --git a/spec/fixtures/unit/netmask/ifconfig_net_tools_1.60.txt b/spec/fixtures/unit/netmask/ifconfig_net_tools_1.60.txt new file mode 100644 index 0000000000..dcfdaf1232 --- /dev/null +++ b/spec/fixtures/unit/netmask/ifconfig_net_tools_1.60.txt @@ -0,0 +1,19 @@ +em1: flags=4163 mtu 1500 + inet 131.252.209.153 netmask 255.255.255.0 broadcast 192.168.76.255 + inet6 2610:10:20:209:212:3fff:febe:2201 prefixlen 128 scopeid 0x0 + inet6 fe80::221:ccff:fe4b:297d prefixlen 64 scopeid 0x20 + ether 00:21:cc:4b:29:7d txqueuelen 1000 (Ethernet) + RX packets 27222144 bytes 31247414760 (29.1 GiB) + RX errors 0 dropped 0 overruns 0 frame 0 + TX packets 10259038 bytes 7784519352 (7.2 GiB) + TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 + device interrupt 20 memory 0xd2600000-d2620000 + +lo: flags=73 mtu 16436 + inet 127.0.0.1 netmask 255.0.0.0 + inet6 ::1 prefixlen 128 scopeid 0x10 + loop txqueuelen 0 (Local Loopback) + RX packets 257371 bytes 37104110 (35.3 MiB) + RX errors 0 dropped 0 overruns 0 frame 0 + TX packets 257371 bytes 37104110 (35.3 MiB) + TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 diff --git a/spec/fixtures/unit/netmask/ifconfig_ubuntu_1204.txt b/spec/fixtures/unit/netmask/ifconfig_ubuntu_1204.txt new file mode 100644 index 0000000000..f2800cc7b8 --- /dev/null +++ b/spec/fixtures/unit/netmask/ifconfig_ubuntu_1204.txt @@ -0,0 +1,16 @@ +eth0 Link encap:Ethernet HWaddr 42:01:0a:57:50:6e + inet addr:10.87.80.110 Bcast:10.87.80.110 Mask:255.255.255.255 + UP BROADCAST RUNNING MULTICAST MTU:1460 Metric:1 + RX packets:1609444 errors:0 dropped:0 overruns:0 frame:0 + TX packets:1479569 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:1000 + RX bytes:673812693 (673.8 MB) TX bytes:221186872 (221.1 MB) + +lo Link encap:Local Loopback + inet addr:127.0.0.1 Mask:255.0.0.0 + UP LOOPBACK RUNNING MTU:16436 Metric:1 + RX packets:5435415 errors:0 dropped:0 overruns:0 frame:0 + TX packets:5435415 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:0 + RX bytes:734540224 (734.5 MB) TX bytes:734540224 (734.5 MB) + diff --git a/spec/unit/interfaces_spec.rb b/spec/unit/interfaces_spec.rb index 159d04032c..6def1c61f5 100755 --- a/spec/unit/interfaces_spec.rb +++ b/spec/unit/interfaces_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' require 'facter/util/ip' -shared_examples_for "netmask from ifconfig output" do |platform, interface, address, fixture| +shared_examples_for "netmask_* from ifconfig output" do |platform, interface, address, fixture| it "correctly on #{platform} interface #{interface}" do Facter::Util::IP.stubs(:exec_ifconfig).returns(my_fixture_read(fixture)) Facter::Util::IP.stubs(:get_output_for_interface_and_label). @@ -41,10 +41,10 @@ Facter.fact(:kernel).stubs(:value).returns("Linux") end - example_behavior_for "netmask from ifconfig output", + example_behavior_for "netmask_* from ifconfig output", "Archlinux (net-tools 1.60)", "em1", "255.255.255.0", "ifconfig_net_tools_1.60.txt" - example_behavior_for "netmask from ifconfig output", + example_behavior_for "netmask_* from ifconfig output", "Archlinux (net-tools 1.60)", "lo", "255.0.0.0", "ifconfig_net_tools_1.60.txt" end diff --git a/spec/unit/netmask_spec.rb b/spec/unit/netmask_spec.rb new file mode 100644 index 0000000000..dcbcfddaed --- /dev/null +++ b/spec/unit/netmask_spec.rb @@ -0,0 +1,32 @@ +#! /usr/bin/env ruby + +require 'spec_helper' +require 'facter/util/ip' + +shared_examples_for "netmask from ifconfig output" do |platform, address, fixture| + it "correctly on #{platform}" do + Facter::Util::IP.stubs(:exec_ifconfig).returns(my_fixture_read(fixture)) + Facter.collection.internal_loader.load(:netmask) + + Facter.fact(:netmask).value.should eq(address) + end +end + +RSpec.configure do |config| + config.alias_it_should_behave_like_to :example_behavior_for, "parses" +end + +describe "netmask fact" do + before :each do + Facter.fact(:kernel).stubs(:value).returns("Linux") + end + + context "on Linux" do + example_behavior_for "netmask from ifconfig output", + "Archlinux (net-tools 1.60)", "255.255.255.0", + "ifconfig_net_tools_1.60.txt" + example_behavior_for "netmask from ifconfig output", + "Ubuntu 12.04", "255.255.255.255", + "ifconfig_ubuntu_1204.txt" + end +end From 423a3bca3c4b9beace9e1ba7490191d8eaa0625b Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Fri, 24 May 2013 11:25:18 -0700 Subject: [PATCH 1272/3753] (maint) De-duplicate parses RSpec alias Adrien pointed out the duplication of RSpec.configure do |config| config.alias_it_should_behave_like_to :example_behavior_for, "parses" end in both of the interfaces_spec file and the netmask_spec file. This patch removes the duplicate statements and places them in a shared file required by both example specs. --- spec/shared_formats/parses.rb | 3 +++ spec/unit/interfaces_spec.rb | 5 +---- spec/unit/netmask_spec.rb | 5 +---- 3 files changed, 5 insertions(+), 8 deletions(-) create mode 100644 spec/shared_formats/parses.rb diff --git a/spec/shared_formats/parses.rb b/spec/shared_formats/parses.rb new file mode 100644 index 0000000000..f3be03af85 --- /dev/null +++ b/spec/shared_formats/parses.rb @@ -0,0 +1,3 @@ +RSpec.configure do |config| + config.alias_it_should_behave_like_to :example_behavior_for, "parses" +end diff --git a/spec/unit/interfaces_spec.rb b/spec/unit/interfaces_spec.rb index 6def1c61f5..41a7d660f0 100755 --- a/spec/unit/interfaces_spec.rb +++ b/spec/unit/interfaces_spec.rb @@ -1,6 +1,7 @@ #! /usr/bin/env ruby require 'spec_helper' +require 'shared_formats/parses' require 'facter/util/ip' shared_examples_for "netmask_* from ifconfig output" do |platform, interface, address, fixture| @@ -15,10 +16,6 @@ end end -RSpec.configure do |config| - config.alias_it_should_behave_like_to :example_behavior_for, "parses" -end - describe "Per Interface IP facts" do it "should replace the ':' in an interface list with '_'" do # So we look supported diff --git a/spec/unit/netmask_spec.rb b/spec/unit/netmask_spec.rb index dcbcfddaed..ce77eefffe 100644 --- a/spec/unit/netmask_spec.rb +++ b/spec/unit/netmask_spec.rb @@ -1,6 +1,7 @@ #! /usr/bin/env ruby require 'spec_helper' +require 'shared_formats/parses' require 'facter/util/ip' shared_examples_for "netmask from ifconfig output" do |platform, address, fixture| @@ -12,10 +13,6 @@ end end -RSpec.configure do |config| - config.alias_it_should_behave_like_to :example_behavior_for, "parses" -end - describe "netmask fact" do before :each do Facter.fact(:kernel).stubs(:value).returns("Linux") From f2a19232ff8b4bbb3334be6be7499c2203110417 Mon Sep 17 00:00:00 2001 From: Andrew Parker Date: Tue, 28 May 2013 15:37:00 -0700 Subject: [PATCH 1273/3753] (Maint) Remove whitespace errors --- lib/facter/domain.rb | 9 +++---- spec/unit/domain_spec.rb | 58 ++++++++++++++++++++-------------------- 2 files changed, 33 insertions(+), 34 deletions(-) diff --git a/lib/facter/domain.rb b/lib/facter/domain.rb index 079417018e..2bfe68b2b7 100644 --- a/lib/facter/domain.rb +++ b/lib/facter/domain.rb @@ -23,16 +23,15 @@ # Get the domain from various sources; the order of these # steps is important - # In some OS 'hostname -f' will change the hostname to '-f' - # We know that Solaris and HP-UX exhibit this behavior - # On good OS, 'hostname -f' will return the FQDN which is preferable - # Due to dangerous behavior of 'hostname -f' on old OS, we will explicitly opt-in + # In some OS 'hostname -f' will change the hostname to '-f' + # We know that Solaris and HP-UX exhibit this behavior + # On good OS, 'hostname -f' will return the FQDN which is preferable + # Due to dangerous behavior of 'hostname -f' on old OS, we will explicitly opt-in # 'hostname -f' --hkenney May 9, 2012 hostname_command = 'hostname' can_do_hostname_f = Regexp.union /Linux/i, /FreeBSD/i, /Darwin/i hostname_command = 'hostname -f' if Facter.value(:kernel) =~ can_do_hostname_f - if name = Facter::Util::Resolution.exec(hostname_command) \ and name =~ /.*?\.(.+$)/ diff --git a/spec/unit/domain_spec.rb b/spec/unit/domain_spec.rb index 1135de1760..27fb7063f5 100755 --- a/spec/unit/domain_spec.rb +++ b/spec/unit/domain_spec.rb @@ -16,30 +16,30 @@ Facter.fact(:kernel).stubs(:value).returns(nested_hash[:kernel]) FileTest.stubs(:exists?).with("/etc/resolv.conf").returns(true) end - - let :hostname_command do + + let :hostname_command do nested_hash[:hostname_command] - end - + end + it "should use the hostname binary" do Facter::Util::Resolution.expects(:exec).with(hostname_command).returns "test.example.com" Facter.fact(:domain).value.should == "example.com" end - + it "should fall back to the dnsdomainname binary" do Facter::Util::Resolution.expects(:exec).with(hostname_command).returns("myhost") Facter::Util::Resolution.expects(:exec).with("dnsdomainname").returns("example.com") Facter.fact(:domain).value.should == "example.com" end - - + + it "should fall back to /etc/resolv.conf" do Facter::Util::Resolution.expects(:exec).with(hostname_command).at_least_once.returns("myhost") Facter::Util::Resolution.expects(:exec).with("dnsdomainname").at_least_once.returns("") File.expects(:open).with('/etc/resolv.conf').at_least_once Facter.fact(:domain).value end - + it "should attempt to resolve facts in a specific order" do seq = sequence('domain') Facter::Util::Resolution.stubs(:exec).with(hostname_command).in_sequence(seq).at_least_once @@ -47,16 +47,16 @@ File.expects(:open).with('/etc/resolv.conf').in_sequence(seq).at_least_once Facter.fact(:domain).value end - - describe "Top level domain" do + + describe "Top level domain" do it "should find the domain name" do Facter::Util::Resolution.expects(:exec).with(hostname_command).returns "ns01.tld" Facter::Util::Resolution.expects(:exec).with("dnsdomainname").never File.expects(:exists?).with('/etc/resolv.conf').never - Facter.fact(:domain).value.should == "tld" - end - end - + Facter.fact(:domain).value.should == "tld" + end + end + describe "when using /etc/resolv.conf" do before do Facter::Util::Resolution.stubs(:exec).with(hostname_command) @@ -64,7 +64,7 @@ @mock_file = mock() File.stubs(:open).with("/etc/resolv.conf").yields(@mock_file) end - + it "should use the domain field over the search field" do lines = [ "nameserver 4.2.2.1", @@ -83,7 +83,7 @@ @mock_file.expects(:each).multiple_yields(*lines) Facter.fact(:domain).value.should == 'example.org' end - + it "should use the first domain in the search field" do lines = [ "search example.org example.net", @@ -91,7 +91,7 @@ @mock_file.expects(:each).multiple_yields(*lines) Facter.fact(:domain).value.should == 'example.org' end - + # Test permutations of domain and search, where 'domain' can be a value of # the search keyword and the domain keyword # and also where 'search' can be a value of the search keyword and the @@ -129,7 +129,7 @@ end end end - end + end describe "on Windows" do before(:each) do @@ -173,35 +173,35 @@ end end - describe "with trailing dots" do - describe "on Windows" do + describe "with trailing dots" do + describe "on Windows" do before do Facter.fact(:kernel).stubs(:value).returns("windows") require 'facter/util/registry' require 'facter/util/wmi' end - + [{:registry => 'testdomain.', :wmi => '', :expect => 'testdomain'}, {:registry => '', :wmi => 'testdomain.', :expect => 'testdomain'}, ].each do |scenario| - describe "scenarios" do + describe "scenarios" do before(:each) do Facter::Util::Registry.stubs(:hklm_read).returns(scenario[:registry]) nic = stubs 'nic' nic.stubs(:DNSDomain).returns(scenario[:wmi]) Facter::Util::WMI.stubs(:execquery).with("select DNSDomain from Win32_NetworkAdapterConfiguration where IPEnabled = True").returns([nic]) end - + it "should return #{scenario[:expect]}" do Facter.fact(:domain).value.should == scenario[:expect] end - + it "should remove trailing dots" do Facter.fact(:domain).value.should_not =~ /\.$/ end - end - end + end + end end describe "on everything else" do @@ -213,12 +213,12 @@ [{:hostname => 'host.testdomain.', :dnsdomainname => '', :resolve_domain => '', :resolve_search => '', :expect => 'testdomain'}, {:hostname => '', :dnsdomainname => 'testdomain.', :resolve_domain => '', :resolve_search => '', :expect => 'testdomain'}, {:hostname => '', :dnsdomainname => '', :resolve_domain => 'testdomain.', :resolve_search => '', :expect => 'testdomain'}, - {:hostname => '', :dnsdomainname => '', :resolve_domain => '', :resolve_search => 'testdomain.', :expect => 'testdomain'}, + {:hostname => '', :dnsdomainname => '', :resolve_domain => '', :resolve_search => 'testdomain.', :expect => 'testdomain'}, {:hostname => '', :dnsdomainname => '', :resolve_domain => '', :resolve_search => '', :expect => nil} ].each do |scenario| describe "scenarios" do - before(:each) do + before(:each) do Facter::Util::Resolution.stubs(:exec).with("hostname -f").returns(scenario[:hostname]) Facter::Util::Resolution.stubs(:exec).with("dnsdomainname").returns(scenario[:dnsdomainname]) @mock_file = mock() @@ -238,7 +238,7 @@ Facter.fact(:domain).value.should == scenario[:expect] end end - end + end end end end From 120403340aca38831bbdae4c64d6f8fc151fca52 Mon Sep 17 00:00:00 2001 From: Andrew Parker Date: Tue, 28 May 2013 15:52:14 -0700 Subject: [PATCH 1274/3753] (#20938) Ignore stderr from domain commands Previously, when the hostname of a system is in a messed up stated, the hostname and dnsdomainname commands would output warning messages to stderr. The facter command execution system does not capture this, and so the stderr output ends up making it all the way out to the caller and likely to the user, as is the case for puppet when using facter. This commit takes the simple approach of redirecting the error output to /dev/null, which should be sufficient for the platforms that use these commands (non-windows). --- lib/facter/domain.rb | 12 +++++++++--- spec/unit/domain_spec.rb | 24 ++++++++++++------------ 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/lib/facter/domain.rb b/lib/facter/domain.rb index 2bfe68b2b7..5d176e6716 100644 --- a/lib/facter/domain.rb +++ b/lib/facter/domain.rb @@ -28,15 +28,21 @@ # On good OS, 'hostname -f' will return the FQDN which is preferable # Due to dangerous behavior of 'hostname -f' on old OS, we will explicitly opt-in # 'hostname -f' --hkenney May 9, 2012 - hostname_command = 'hostname' + basic_hostname = 'hostname 2> /dev/null' + full_hostname = 'hostname -f 2> /dev/null' can_do_hostname_f = Regexp.union /Linux/i, /FreeBSD/i, /Darwin/i - hostname_command = 'hostname -f' if Facter.value(:kernel) =~ can_do_hostname_f + + hostname_command = if Facter.value(:kernel) =~ can_do_hostname_f + full_hostname + else + basic_hostname + end if name = Facter::Util::Resolution.exec(hostname_command) \ and name =~ /.*?\.(.+$)/ return_value = $1 - elsif domain = Facter::Util::Resolution.exec('dnsdomainname') \ + elsif domain = Facter::Util::Resolution.exec('dnsdomainname 2> /dev/null') \ and domain =~ /.+/ return_value = domain diff --git a/spec/unit/domain_spec.rb b/spec/unit/domain_spec.rb index 27fb7063f5..14016d8009 100755 --- a/spec/unit/domain_spec.rb +++ b/spec/unit/domain_spec.rb @@ -4,11 +4,11 @@ describe "Domain name facts" do - { :linux => {:kernel => "Linux", :hostname_command => "hostname -f"}, - :solaris => {:kernel => "SunOS", :hostname_command => "hostname"}, - :darwin => {:kernel => "Darwin", :hostname_command => "hostname -f"}, - :freebsd => {:kernel => "FreeBSD", :hostname_command => "hostname -f"}, - :hpux => {:kernel => "HP-UX", :hostname_command => "hostname"}, + { :linux => {:kernel => "Linux", :hostname_command => "hostname -f 2> /dev/null"}, + :solaris => {:kernel => "SunOS", :hostname_command => "hostname 2> /dev/null"}, + :darwin => {:kernel => "Darwin", :hostname_command => "hostname -f 2> /dev/null"}, + :freebsd => {:kernel => "FreeBSD", :hostname_command => "hostname -f 2> /dev/null"}, + :hpux => {:kernel => "HP-UX", :hostname_command => "hostname 2> /dev/null"}, }.each do |key, nested_hash| describe "on #{key}" do @@ -28,14 +28,14 @@ it "should fall back to the dnsdomainname binary" do Facter::Util::Resolution.expects(:exec).with(hostname_command).returns("myhost") - Facter::Util::Resolution.expects(:exec).with("dnsdomainname").returns("example.com") + Facter::Util::Resolution.expects(:exec).with("dnsdomainname 2> /dev/null").returns("example.com") Facter.fact(:domain).value.should == "example.com" end it "should fall back to /etc/resolv.conf" do Facter::Util::Resolution.expects(:exec).with(hostname_command).at_least_once.returns("myhost") - Facter::Util::Resolution.expects(:exec).with("dnsdomainname").at_least_once.returns("") + Facter::Util::Resolution.expects(:exec).with("dnsdomainname 2> /dev/null").at_least_once.returns("") File.expects(:open).with('/etc/resolv.conf').at_least_once Facter.fact(:domain).value end @@ -43,7 +43,7 @@ it "should attempt to resolve facts in a specific order" do seq = sequence('domain') Facter::Util::Resolution.stubs(:exec).with(hostname_command).in_sequence(seq).at_least_once - Facter::Util::Resolution.stubs(:exec).with("dnsdomainname").in_sequence(seq).at_least_once + Facter::Util::Resolution.stubs(:exec).with("dnsdomainname 2> /dev/null").in_sequence(seq).at_least_once File.expects(:open).with('/etc/resolv.conf').in_sequence(seq).at_least_once Facter.fact(:domain).value end @@ -51,7 +51,7 @@ describe "Top level domain" do it "should find the domain name" do Facter::Util::Resolution.expects(:exec).with(hostname_command).returns "ns01.tld" - Facter::Util::Resolution.expects(:exec).with("dnsdomainname").never + Facter::Util::Resolution.expects(:exec).with("dnsdomainname 2> /dev/null").never File.expects(:exists?).with('/etc/resolv.conf').never Facter.fact(:domain).value.should == "tld" end @@ -60,7 +60,7 @@ describe "when using /etc/resolv.conf" do before do Facter::Util::Resolution.stubs(:exec).with(hostname_command) - Facter::Util::Resolution.stubs(:exec).with("dnsdomainname") + Facter::Util::Resolution.stubs(:exec).with("dnsdomainname 2> /dev/null") @mock_file = mock() File.stubs(:open).with("/etc/resolv.conf").yields(@mock_file) end @@ -219,8 +219,8 @@ describe "scenarios" do before(:each) do - Facter::Util::Resolution.stubs(:exec).with("hostname -f").returns(scenario[:hostname]) - Facter::Util::Resolution.stubs(:exec).with("dnsdomainname").returns(scenario[:dnsdomainname]) + Facter::Util::Resolution.stubs(:exec).with("hostname -f 2> /dev/null").returns(scenario[:hostname]) + Facter::Util::Resolution.stubs(:exec).with("dnsdomainname 2> /dev/null").returns(scenario[:dnsdomainname]) @mock_file = mock() File.stubs(:open).with("/etc/resolv.conf").yields(@mock_file) lines = [ From e31abb8ad6db4a722c184457f967675ffda44024 Mon Sep 17 00:00:00 2001 From: briancain Date: Tue, 28 May 2013 16:05:21 -0700 Subject: [PATCH 1275/3753] (maint) Remove duplication from selinux facts Prior to this commit, the selinux fact had code duplication. This commit abstracts this duplication to a hash of fact name and label. --- lib/facter/selinux.rb | 39 ++++++++++++--------------------------- 1 file changed, 12 insertions(+), 27 deletions(-) diff --git a/lib/facter/selinux.rb b/lib/facter/selinux.rb index 4d8e801456..2ae1762f5b 100644 --- a/lib/facter/selinux.rb +++ b/lib/facter/selinux.rb @@ -68,33 +68,18 @@ def selinux_mount_point end end -Facter.add("selinux_current_mode") do - confine :selinux => :true - setcode do - result = 'unknown' - mode = Facter::Util::Resolution.exec(sestatus_cmd) - mode.each_line { |l| result = $1 if l =~ /^Current mode\:\s+(\w+)$/i } - result.chomp - end -end - -Facter.add("selinux_config_mode") do - confine :selinux => :true - setcode do - result = 'unknown' - mode = Facter::Util::Resolution.exec(sestatus_cmd) - mode.each_line { |l| result = $1 if l =~ /^Mode from config file\:\s+(\w+)$/i } - result.chomp - end -end - -Facter.add("selinux_config_policy") do - confine :selinux => :true - setcode do - result = 'unknown' - mode = Facter::Util::Resolution.exec(sestatus_cmd) - mode.each_line { |l| result = $1 if l =~ /^Policy from config file\:\s+(\w+)$/i } - result.chomp +{ "selinux_current_mode" => "Current mode", + "selinux_config_mode" => "Mode from config file", + "selinux_config_policy" => "Policy from config file" +}.each_pair do |fact, label| + Facter.add(fact) do + confine :selinux => :true + setcode do + result = 'unknown' + mode = Facter::Util::Resolution.exec(sestatus_cmd) + mode.each_line { |l| result = $1 if l =~ /^#{label}\:\s+(\w+)$/i } + result.chomp + end end end From 9c0cc1e7a02efcc0322f72e5418bde57993dcdf6 Mon Sep 17 00:00:00 2001 From: Andrew Parker Date: Tue, 28 May 2013 16:22:29 -0700 Subject: [PATCH 1276/3753] (Maint) Remove duplication in domain tests The tests were full of duplication around what commands would be executed and how. This works toward removing the duplication and even combines two tests together to provide a less implementation specific assertion about the precedence of the resolv.conf data. --- spec/unit/domain_spec.rb | 189 +++++++++++++++++++++++---------------- 1 file changed, 112 insertions(+), 77 deletions(-) diff --git a/spec/unit/domain_spec.rb b/spec/unit/domain_spec.rb index 14016d8009..96176f9734 100755 --- a/spec/unit/domain_spec.rb +++ b/spec/unit/domain_spec.rb @@ -1,94 +1,98 @@ #! /usr/bin/env ruby require 'spec_helper' +require 'stringio' describe "Domain name facts" do - { :linux => {:kernel => "Linux", :hostname_command => "hostname -f 2> /dev/null"}, - :solaris => {:kernel => "SunOS", :hostname_command => "hostname 2> /dev/null"}, - :darwin => {:kernel => "Darwin", :hostname_command => "hostname -f 2> /dev/null"}, - :freebsd => {:kernel => "FreeBSD", :hostname_command => "hostname -f 2> /dev/null"}, - :hpux => {:kernel => "HP-UX", :hostname_command => "hostname 2> /dev/null"}, - }.each do |key, nested_hash| + def resolv_conf_contains(*lines) + file_handle = StringIO.new(lines.join("\n")) + FileTest.stubs(:exists?).with("/etc/resolv.conf").returns(true) + File.stubs(:open).with("/etc/resolv.conf").yields(file_handle) + end - describe "on #{key}" do - before do - Facter.fact(:kernel).stubs(:value).returns(nested_hash[:kernel]) - FileTest.stubs(:exists?).with("/etc/resolv.conf").returns(true) + [ + { :kernel => "Linux", :hostname_command => "hostname -f 2> /dev/null" }, + { :kernel => "SunOS", :hostname_command => "hostname 2> /dev/null" }, + { :kernel => "Darwin", :hostname_command => "hostname -f 2> /dev/null" }, + { :kernel => "FreeBSD", :hostname_command => "hostname -f 2> /dev/null" }, + { :kernel => "HP-UX", :hostname_command => "hostname 2> /dev/null" }, + ].each do |scenario| + + describe "on #{scenario[:kernel]}" do + let(:hostname_command) { scenario[:hostname_command] } + let(:dnsdomain_command) { "dnsdomainname 2> /dev/null" } + + def the_hostname_is(value) + Facter::Util::Resolution.stubs(:exec).with(hostname_command).returns(value) end - let :hostname_command do - nested_hash[:hostname_command] + def the_dnsdomainname_is(value) + Facter::Util::Resolution.stubs(:exec).with(dnsdomain_command).returns(value) + end + + before do + Facter.fact(:kernel).stubs(:value).returns(scenario[:kernel]) end it "should use the hostname binary" do - Facter::Util::Resolution.expects(:exec).with(hostname_command).returns "test.example.com" + the_hostname_is("test.example.com") + Facter.fact(:domain).value.should == "example.com" end it "should fall back to the dnsdomainname binary" do - Facter::Util::Resolution.expects(:exec).with(hostname_command).returns("myhost") - Facter::Util::Resolution.expects(:exec).with("dnsdomainname 2> /dev/null").returns("example.com") + the_hostname_is("myhost") + the_dnsdomainname_is("example.com") + Facter.fact(:domain).value.should == "example.com" end - it "should fall back to /etc/resolv.conf" do - Facter::Util::Resolution.expects(:exec).with(hostname_command).at_least_once.returns("myhost") - Facter::Util::Resolution.expects(:exec).with("dnsdomainname 2> /dev/null").at_least_once.returns("") - File.expects(:open).with('/etc/resolv.conf').at_least_once - Facter.fact(:domain).value - end + the_hostname_is("myhost") + the_dnsdomainname_is("") - it "should attempt to resolve facts in a specific order" do - seq = sequence('domain') - Facter::Util::Resolution.stubs(:exec).with(hostname_command).in_sequence(seq).at_least_once - Facter::Util::Resolution.stubs(:exec).with("dnsdomainname 2> /dev/null").in_sequence(seq).at_least_once - File.expects(:open).with('/etc/resolv.conf').in_sequence(seq).at_least_once - Facter.fact(:domain).value + resolv_conf_contains("domain testing.com") + + Facter.fact(:domain).value.should == "testing.com" end describe "Top level domain" do it "should find the domain name" do - Facter::Util::Resolution.expects(:exec).with(hostname_command).returns "ns01.tld" - Facter::Util::Resolution.expects(:exec).with("dnsdomainname 2> /dev/null").never - File.expects(:exists?).with('/etc/resolv.conf').never + the_hostname_is("ns01.tld") + Facter.fact(:domain).value.should == "tld" end end describe "when using /etc/resolv.conf" do before do - Facter::Util::Resolution.stubs(:exec).with(hostname_command) - Facter::Util::Resolution.stubs(:exec).with("dnsdomainname 2> /dev/null") - @mock_file = mock() - File.stubs(:open).with("/etc/resolv.conf").yields(@mock_file) + the_hostname_is("") + the_dnsdomainname_is("") end it "should use the domain field over the search field" do - lines = [ - "nameserver 4.2.2.1", - "search example.org", - "domain example.com", - ] - @mock_file.expects(:each).multiple_yields(*lines) + resolv_conf_contains( + "nameserver 4.2.2.1", + "search example.org", + "domain example.com" + ) + Facter.fact(:domain).value.should == 'example.com' end it "should fall back to the search field" do - lines = [ - "nameserver 4.2.2.1", - "search example.org", - ] - @mock_file.expects(:each).multiple_yields(*lines) + resolv_conf_contains( + "nameserver 4.2.2.1", + "search example.org" + ) + Facter.fact(:domain).value.should == 'example.org' end it "should use the first domain in the search field" do - lines = [ - "search example.org example.net", - ] - @mock_file.expects(:each).multiple_yields(*lines) + resolv_conf_contains("search example.org example.net") + Facter.fact(:domain).value.should == 'example.org' end @@ -107,23 +111,21 @@ # Why someone would have their machines named 'www.domain' or 'www.search', I # don't know, but we'll at least handle it properly [ - ["domain domain", "domain"], - ["domain search", "search"], - ["search domain", "domain"], - ["search search", "search"], - ["search domain notdomain", "domain"], - [["#search notdomain","search search"], "search"], - [["# search notdomain","search search"], "search"], - [["#domain notdomain","domain domain"], "domain"], - [["# domain notdomain","domain domain"], "domain"], + [["domain domain"], "domain"], + [["domain search"], "search"], + [["search domain"], "domain"], + [["search search"], "search"], + [["search domain notdomain"], "domain"], + [["#search notdomain", "search search"], "search"], + [["# search notdomain", "search search"], "search"], + [["#domain notdomain", "domain domain"], "domain"], + [["# domain notdomain", "domain domain"], "domain"], ].each do |tuple| - field = tuple[0] + conf = tuple[0] expect = tuple[1] - it "should return #{expect} from \"#{field}\"" do - lines = [ - field - ].flatten - @mock_file.expects(:each).multiple_yields(*lines) + it "should return #{expect} from \"#{conf}\"" do + resolv_conf_contains(*conf) + Facter.fact(:domain).value.should == expect end end @@ -210,24 +212,57 @@ FileTest.stubs(:exists?).with("/etc/resolv.conf").returns(true) end - [{:hostname => 'host.testdomain.', :dnsdomainname => '', :resolve_domain => '', :resolve_search => '', :expect => 'testdomain'}, - {:hostname => '', :dnsdomainname => 'testdomain.', :resolve_domain => '', :resolve_search => '', :expect => 'testdomain'}, - {:hostname => '', :dnsdomainname => '', :resolve_domain => 'testdomain.', :resolve_search => '', :expect => 'testdomain'}, - {:hostname => '', :dnsdomainname => '', :resolve_domain => '', :resolve_search => 'testdomain.', :expect => 'testdomain'}, - {:hostname => '', :dnsdomainname => '', :resolve_domain => '', :resolve_search => '', :expect => nil} + [ + { + :scenario => 'when there is only a hostname', + :hostname => 'host.testdomain.', + :dnsdomainname => '', + :resolve_domain => '', + :resolve_search => '', + :expect => 'testdomain' + }, + { + :scenario => 'when there is only a domain name', + :hostname => '', + :dnsdomainname => 'testdomain.', + :resolve_domain => '', + :resolve_search => '', + :expect => 'testdomain' + }, + { + :scenario => 'when there is only a resolve domain', + :hostname => '', + :dnsdomainname => '', + :resolve_domain => 'testdomain.', + :resolve_search => '', + :expect => 'testdomain' + }, + { + :scenario => 'when there is only a resolve search', + :hostname => '', + :dnsdomainname => '', + :resolve_domain => '', + :resolve_search => 'testdomain.', + :expect => 'testdomain' + }, + { + :scenario => 'when there is no information available', + :hostname => '', + :dnsdomainname => '', + :resolve_domain => '', + :resolve_search => '', + :expect => nil + } ].each do |scenario| - describe "scenarios" do + describe scenario[:scenario] do before(:each) do Facter::Util::Resolution.stubs(:exec).with("hostname -f 2> /dev/null").returns(scenario[:hostname]) Facter::Util::Resolution.stubs(:exec).with("dnsdomainname 2> /dev/null").returns(scenario[:dnsdomainname]) - @mock_file = mock() - File.stubs(:open).with("/etc/resolv.conf").yields(@mock_file) - lines = [ - "search #{scenario[:resolve_search]}", - "domain #{scenario[:resolve_domain]}", - ] - @mock_file.stubs(:each).multiple_yields(*lines) + resolv_conf_contains( + "search #{scenario[:resolve_search]}", + "domain #{scenario[:resolve_domain]}" + ) end it "should remove trailing dots" do From e6184d700d459d6a9f780d89520afcda6a2e8f93 Mon Sep 17 00:00:00 2001 From: Andrew Roetker Date: Tue, 28 May 2013 16:35:15 -0700 Subject: [PATCH 1277/3753] (maint) Remove duplication from virtual fact Prior to this commit the SunOS and OpenBSD resolutions for the virtual fact had duplicate logic. This commit abstracts the logic into the `Facter::Util::Virtual.parse_virtualization` helper. --- lib/facter/util/virtual.rb | 10 ++++++++++ lib/facter/virtual.rb | 16 ++-------------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/lib/facter/util/virtual.rb b/lib/facter/util/virtual.rb index 65cf58a645..0beaf9be8e 100644 --- a/lib/facter/util/virtual.rb +++ b/lib/facter/util/virtual.rb @@ -135,6 +135,16 @@ def self.zlinux? "zlinux" end + def self.parse_virtualization(output) + if output + lines = output.split("\n") + return "parallels" if lines.any? {|l| l =~ /Parallels/ } + return "vmware" if lines.any? {|l| l =~ /VM[wW]are/ } + return "virtualbox" if lines.any? {|l| l =~ /VirtualBox/ } + return "xenhvm" if lines.any? {|l| l =~ /HVM domU/ } + end + end + ## # read_sysfs Reads the raw data as per the documentation at [Detecting if You # Are Running in Google Compute diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index add5885e3c..7d2487f49d 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -62,13 +62,7 @@ resolver.timeout = 6 resolver.setcode('prtdiag') output = resolver.value - if output - lines = output.split("\n") - next "parallels" if lines.any? {|l| l =~ /Parallels/ } - next "vmware" if lines.any? {|l| l =~ /VM[wW]are/ } - next "virtualbox" if lines.any? {|l| l =~ /VirtualBox/ } - next "xenhvm" if lines.any? {|l| l =~ /HVM domU/ } - end + Facter::Util::Virtual.parse_virtualization(output) end end @@ -93,13 +87,7 @@ setcode do output = Facter::Util::Resolution.exec('sysctl -n hw.product 2>/dev/null') - if output - lines = output.split("\n") - next "parallels" if lines.any? {|l| l =~ /Parallels/ } - next "vmware" if lines.any? {|l| l =~ /VMware/ } - next "virtualbox" if lines.any? {|l| l =~ /VirtualBox/ } - next "xenhvm" if lines.any? {|l| l =~ /HVM domU/ } - end + Facter::Util::Virtual.parse_virtualization(output) end end From b739a96626a5bbc48ffa99a5b5f3fb9ce5f8bf83 Mon Sep 17 00:00:00 2001 From: Aran Cox Date: Fri, 31 May 2013 11:11:22 -0500 Subject: [PATCH 1278/3753] (#20994) fix incorrectly set memorysize on AIX using svmon (was set to 0) tested on AIX 5.3, 6.1, and 7.1 --- lib/facter/util/memory.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/facter/util/memory.rb b/lib/facter/util/memory.rb index 7c63dd0992..aef8ffc145 100644 --- a/lib/facter/util/memory.rb +++ b/lib/facter/util/memory.rb @@ -109,6 +109,10 @@ def self.mem_size_info(kernel = Facter.value(:kernel)) Facter::Util::Resolution.exec("sysctl -n hw.memsize") when /Dragonfly/i Facter::Util::Resolution.exec("sysctl -n hw.physmem") + when /AIX/i + if Facter::Util::Resolution.exec("/usr/bin/svmon -O unit=KB") =~ /^memory\s+(\d+)\s+/ + $1 + end end end @@ -116,6 +120,8 @@ def self.scale_mem_size_value(value, kernel) case kernel when /OpenBSD/i, /FreeBSD/i, /Darwin/i, /Dragonfly/i value.to_f / 1024.0 / 1024.0 + when /AIX/i + value.to_f / 1024.0 else value.to_f end From 183b2ba4035cd0e532a7bfcb90047c816a4dfd4d Mon Sep 17 00:00:00 2001 From: Aran Cox Date: Fri, 31 May 2013 11:12:48 -0500 Subject: [PATCH 1279/3753] (#20994) fix incorrect memoryfree fact on AIX (was set to 0) tested on AIX 5.3, 6.1, and 7.1 --- lib/facter/util/memory.rb | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/facter/util/memory.rb b/lib/facter/util/memory.rb index aef8ffc145..94115a9bd5 100644 --- a/lib/facter/util/memory.rb +++ b/lib/facter/util/memory.rb @@ -67,6 +67,14 @@ def self.vmstat_darwin_find_free_memory() freemem = ( memfree + memspecfree ) * pagesize end + # on AIX use svmon to get the free memory: + # it's the third value on the line starting with memory + # svmon can be run by non root users + def self.svmon_aix_find_free_memory() + Facter::Util::Resolution.exec("/usr/bin/svmon -O unit=KB") =~ /^memory\s+\d+\s+\d+\s+(\d+)\s+/ + $1 + end + def self.mem_free(kernel = Facter.value(:kernel)) output = mem_free_info(kernel) scale_mem_free_value output, kernel @@ -80,12 +88,14 @@ def self.mem_free_info(kernel = Facter.value(:kernel)) vmstat_find_free_memory(["-H"]) when /Darwin/i vmstat_darwin_find_free_memory() + when /AIX/i + svmon_aix_find_free_memory() end end def self.scale_mem_free_value (value, kernel) case kernel - when /OpenBSD/i, /FreeBSD/i, /SunOS/i, /Dragonfly/i + when /OpenBSD/i, /FreeBSD/i, /SunOS/i, /Dragonfly/i, /AIX/i value.to_f / 1024.0 when /Darwin/i value.to_f / 1024.0 / 1024.0 From 248a526fb2a7ad7598a6b61d613560ddbdd3cf69 Mon Sep 17 00:00:00 2001 From: Aran Cox Date: Sun, 2 Jun 2013 10:59:29 -0500 Subject: [PATCH 1280/3753] (#20994) add rspec tests for memoryfree and memorysize on AIX using svmon --- spec/unit/memory_spec.rb | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/spec/unit/memory_spec.rb b/spec/unit/memory_spec.rb index 1771a53e20..20f490cb20 100755 --- a/spec/unit/memory_spec.rb +++ b/spec/unit/memory_spec.rb @@ -155,6 +155,20 @@ Facter::Util::Resolution.stubs(:exec).with('swap -l').returns(swapusage) + svmon = < Date: Tue, 4 Jun 2013 13:30:46 -0700 Subject: [PATCH 1281/3753] (maint) remove operatingsystemrelease duplication Prior to this commit, the `operatingsystemrelease` fact had a significant amount of duplicate code in determining the release version of the operating system. This commit condenses that code to a more managable format. --- lib/facter/operatingsystemrelease.rb | 96 ++++++++-------------------- 1 file changed, 27 insertions(+), 69 deletions(-) diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index 319896d7ed..3f377919ae 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -48,12 +48,17 @@ end end -Facter.add(:operatingsystemrelease) do - confine :operatingsystem => %w{Debian} - setcode do - if release = Facter::Util::FileRead.read('/etc/debian_version') - release.sub!(/\s*$/, '') - release +{ + :Debian => '/etc/debian_version', + :Alpine => '/etc/alpine-release', +}.each do |platform, file_name| + Facter.add(:operatingsystemrelease) do + confine :operatingsystem => platform + setcode do + if release = Facter::Util::FileRead.read(file_name) + release.sub!(/\s*$/, '') + release + end end end end @@ -92,47 +97,22 @@ end end -Facter.add(:operatingsystemrelease) do - confine :operatingsystem => %w{OpenWrt} - setcode do - if release = Facter::Util::FileRead.read('/etc/openwrt_version') - if match = /^(\d+\.\d+.*)/.match(release) - match[1] - end - end - end -end - -Facter.add(:operatingsystemrelease) do - confine :operatingsystem => %w{Slackware} - setcode do - if release = Facter::Util::FileRead.read('/etc/slackware-version') - if match = /Slackware ([0-9.]+)/.match(release) - match[1] - end - end - end -end - -Facter.add(:operatingsystemrelease) do - confine :operatingsystem => %w{Mageia} - setcode do - if release = Facter::Util::FileRead.read('/etc/mageia-release') - if match = /Mageia release ([0-9.]+)/.match(release) - match[1] - end - end - end -end - -Facter.add(:operatingsystemrelease) do - confine :operatingsystem => %w{Bluewhite64} - setcode do - if release = Facter::Util::FileRead.read('/etc/bluewhite64-version') - if match = /^\s*\w+\s+(\d+)\.(\d+)/.match(release) - match[1] + "." + match[2] - else - "unknown" +{ + :OpenWrt => {:file => '/etc/openwrt_version', :regexp => /^(\d+\.\d+.*)/}, + :Slackware => {:file => '/etc/slackware-version', :regexp => /Slackware ([0-9.]+)/}, + :Mageia => {:file => '/etc/mageia-release', :regexp => /Mageia release ([0-9.]+)/}, + :Bluewhite64 => {:file => '/etc/bluewhite64-version', :regexp => /^\s*\w+\s+(\d+\.\d+)/}, + :Slamd64 => {:file => '/etc/slamd64-version', :regexp => /^\s*\w+\s+(\d+\.\d+)/}, +}.each do |platform, other_stuff| + Facter.add(:operatingsystemrelease) do + confine :operatingsystem => platform + setcode do + if release = Facter::Util::FileRead.read(other_stuff[:file]) + if match = other_stuff[:regexp].match(release) + match[1] + else + "unknown" + end end end end @@ -148,28 +128,6 @@ end end -Facter.add(:operatingsystemrelease) do - confine :operatingsystem => %w{Slamd64} - setcode do - if release = Facter::Util::FileRead.read('/etc/slamd64-version') - if match = /^\s*\w+\s+(\d+)\.(\d+)/.match(release) - match[1] + "." + match[2] - else - "unknown" - end - end - end -end - -Facter.add(:operatingsystemrelease) do - confine :operatingsystem => :Alpine - setcode do - if release = Facter::Util::FileRead.read('/etc/alpine-release') - release.sub!(/\s*$/, '') - release - end - end -end Facter.add(:operatingsystemrelease) do confine :operatingsystem => %W{Amazon} From 5086186d5e10dfa1c49aec9bee1dc2eaa5983016 Mon Sep 17 00:00:00 2001 From: Melissa Date: Tue, 4 Jun 2013 14:30:20 -0700 Subject: [PATCH 1282/3753] (maint) remove duplicate code from resolution test Prior to this commit, the tests for Facter::Util::Resolution were the same for windows and non-windows systems. This commit combines those two tests. --- spec/unit/util/resolution_spec.rb | 49 ++++++++++++------------------- 1 file changed, 18 insertions(+), 31 deletions(-) diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index ed044444b2..222a98e620 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -198,43 +198,30 @@ class FlushFakeError < StandardError; end end describe "and the code is a string" do - describe "on windows" do - before do - given_a_configuration_of(:is_windows => true) - end - - it "should return the result of executing the code" do - @resolve.setcode "/bin/foo" - Facter::Util::Resolution.expects(:exec).once.with("/bin/foo").returns "yup" - - @resolve.value.should == "yup" - end - - it "should return nil if the value is an empty string" do - @resolve.setcode "/bin/foo" - Facter::Util::Resolution.expects(:exec).once.returns "" - @resolve.value.should be_nil - end - end + [ true, + false + ].each do |bool| - describe "on non-windows systems" do - before do - given_a_configuration_of(:is_windows => false) - end + describe "on #{(bool)?'':'non-'}windows system" do + before do + given_a_configuration_of(:is_windows => bool) + end - it "should return the result of executing the code" do - @resolve.setcode "/bin/foo" - Facter::Util::Resolution.expects(:exec).once.with("/bin/foo").returns "yup" + it "should return the result of executing the code" do + @resolve.setcode "/bin/foo" + Facter::Util::Resolution.expects(:exec).once.with("/bin/foo").returns "yup" - @resolve.value.should == "yup" - end + @resolve.value.should == "yup" + end - it "should return nil if the value is an empty string" do - @resolve.setcode "/bin/foo" - Facter::Util::Resolution.expects(:exec).once.returns "" - @resolve.value.should be_nil + it "should return nil if the value is an empty string" do + @resolve.setcode "/bin/foo" + Facter::Util::Resolution.expects(:exec).once.returns "" + @resolve.value.should be_nil + end end end + end describe "and the code is a block" do From 31a26a19c48c63971eeddd5a7e25de3f7bca9223 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Wed, 5 Jun 2013 11:15:40 -0700 Subject: [PATCH 1283/3753] (maint) Extract heredoc fixtures from memory spec --- spec/fixtures/unit/memory/aix-svmon | 9 ++ spec/fixtures/unit/memory/aix-swap_l | 2 + .../unit/memory/darwin-swapinfo-multiple | 3 + .../unit/memory/darwin-swapinfo-single | 2 + spec/fixtures/unit/memory/darwin-vm_stat | 13 ++ spec/fixtures/unit/memory/dragonfly-vmstat | 3 + spec/fixtures/unit/memory/freebsd-vmstat | 3 + spec/fixtures/unit/memory/linux-proc_meminfo | 10 ++ spec/fixtures/unit/memory/openbsd-vmstat | 3 + spec/fixtures/unit/memory/solaris-prtconf | 4 + .../unit/memory/solaris-swap_l-multiple | 3 + .../unit/memory/solaris-swap_l-single | 2 + spec/fixtures/unit/memory/solaris-vmstat | 3 + spec/unit/memory_spec.rb | 132 +++--------------- 14 files changed, 81 insertions(+), 111 deletions(-) create mode 100644 spec/fixtures/unit/memory/aix-svmon create mode 100644 spec/fixtures/unit/memory/aix-swap_l create mode 100644 spec/fixtures/unit/memory/darwin-swapinfo-multiple create mode 100644 spec/fixtures/unit/memory/darwin-swapinfo-single create mode 100644 spec/fixtures/unit/memory/darwin-vm_stat create mode 100644 spec/fixtures/unit/memory/dragonfly-vmstat create mode 100644 spec/fixtures/unit/memory/freebsd-vmstat create mode 100644 spec/fixtures/unit/memory/linux-proc_meminfo create mode 100644 spec/fixtures/unit/memory/openbsd-vmstat create mode 100644 spec/fixtures/unit/memory/solaris-prtconf create mode 100644 spec/fixtures/unit/memory/solaris-swap_l-multiple create mode 100644 spec/fixtures/unit/memory/solaris-swap_l-single create mode 100644 spec/fixtures/unit/memory/solaris-vmstat diff --git a/spec/fixtures/unit/memory/aix-svmon b/spec/fixtures/unit/memory/aix-svmon new file mode 100644 index 0000000000..bb96729300 --- /dev/null +++ b/spec/fixtures/unit/memory/aix-svmon @@ -0,0 +1,9 @@ +Unit: KB +-------------------------------------------------------------------------------------- + size inuse free pin virtual available mmode +memory 32768000 9948408 22819592 2432080 4448928 27231828 Ded +pg space 34078720 15000 + + work pers clnt other +pin 1478228 0 0 953852 +in use 4448928 0 5499480 diff --git a/spec/fixtures/unit/memory/aix-swap_l b/spec/fixtures/unit/memory/aix-swap_l new file mode 100644 index 0000000000..1966dbf9ba --- /dev/null +++ b/spec/fixtures/unit/memory/aix-swap_l @@ -0,0 +1,2 @@ +device maj,min total free +/dev/hd6 10, 2 512MB 508MB diff --git a/spec/fixtures/unit/memory/darwin-swapinfo-multiple b/spec/fixtures/unit/memory/darwin-swapinfo-multiple new file mode 100644 index 0000000000..f5e9305a25 --- /dev/null +++ b/spec/fixtures/unit/memory/darwin-swapinfo-multiple @@ -0,0 +1,3 @@ +Device 1K-blocks Used Avail Capacity +/dev/da0p3 2048540 0 1048540 0% +/dev/da0p4 3048540 0 1048540 0% diff --git a/spec/fixtures/unit/memory/darwin-swapinfo-single b/spec/fixtures/unit/memory/darwin-swapinfo-single new file mode 100644 index 0000000000..8b41560644 --- /dev/null +++ b/spec/fixtures/unit/memory/darwin-swapinfo-single @@ -0,0 +1,2 @@ +Device 1K-blocks Used Avail Capacity +/dev/da0p3 2048540 0 1048540 0% diff --git a/spec/fixtures/unit/memory/darwin-vm_stat b/spec/fixtures/unit/memory/darwin-vm_stat new file mode 100644 index 0000000000..300f405152 --- /dev/null +++ b/spec/fixtures/unit/memory/darwin-vm_stat @@ -0,0 +1,13 @@ +Mach Virtual Memory Statistics: (page size of 4096 bytes) +Pages free: 28430. +Pages active: 1152576. +Pages inactive: 489054. +Pages speculative: 7076. +Pages wired down: 418217. +"Translation faults": 1340091228. +Pages copy-on-write: 16851357. +Pages zero filled: 665168768. +Pages reactivated: 3082708. +Pageins: 13862917. +Pageouts: 1384383. +Object cache: 14 hits of 2619925 lookups (0% hit rate) diff --git a/spec/fixtures/unit/memory/dragonfly-vmstat b/spec/fixtures/unit/memory/dragonfly-vmstat new file mode 100644 index 0000000000..bae884cc05 --- /dev/null +++ b/spec/fixtures/unit/memory/dragonfly-vmstat @@ -0,0 +1,3 @@ + procs memory page disks faults cpu + r b w avm fre flt re pi po fr sr da0 sg1 in sy cs us sy id + 0 0 0 33152 13940 1902120 2198 53119 11642 6544597 5460994 0 0 6148243 7087927 3484264 0 1 9 diff --git a/spec/fixtures/unit/memory/freebsd-vmstat b/spec/fixtures/unit/memory/freebsd-vmstat new file mode 100644 index 0000000000..3525abf511 --- /dev/null +++ b/spec/fixtures/unit/memory/freebsd-vmstat @@ -0,0 +1,3 @@ + procs memory page disks faults cpu + r b w avm fre flt re pi po fr sr da0 cd0 in sy cs us sy id + 1 0 0 207600 656640 10 0 0 0 13 0 0 0 51 164 257 0 1 99 diff --git a/spec/fixtures/unit/memory/linux-proc_meminfo b/spec/fixtures/unit/memory/linux-proc_meminfo new file mode 100644 index 0000000000..bf68aa0a1a --- /dev/null +++ b/spec/fixtures/unit/memory/linux-proc_meminfo @@ -0,0 +1,10 @@ +MemTotal: 255908 kB +MemFree: 69936 kB +Buffers: 15812 kB +Cached: 115124 kB +SwapCached: 0 kB +Active: 92700 kB +Inactive: 63792 kB +SwapTotal: 524280 kB +SwapFree: 524280 kB +Dirty: 4 kB diff --git a/spec/fixtures/unit/memory/openbsd-vmstat b/spec/fixtures/unit/memory/openbsd-vmstat new file mode 100644 index 0000000000..df57d8e9d3 --- /dev/null +++ b/spec/fixtures/unit/memory/openbsd-vmstat @@ -0,0 +1,3 @@ + procs memory page disks traps cpu + r b w avm fre flt re pi po fr sr cd0 sd0 int sys cs us sy id + 0 0 0 11048 181028 39 0 0 0 0 0 0 1 3 90 17 0 0 100 diff --git a/spec/fixtures/unit/memory/solaris-prtconf b/spec/fixtures/unit/memory/solaris-prtconf new file mode 100644 index 0000000000..5da9238d91 --- /dev/null +++ b/spec/fixtures/unit/memory/solaris-prtconf @@ -0,0 +1,4 @@ +System Configuration: Sun Microsystems sun4u +Memory size: 2048 Megabytes +System Peripherals (Software Nodes): + diff --git a/spec/fixtures/unit/memory/solaris-swap_l-multiple b/spec/fixtures/unit/memory/solaris-swap_l-multiple new file mode 100644 index 0000000000..843eb736da --- /dev/null +++ b/spec/fixtures/unit/memory/solaris-swap_l-multiple @@ -0,0 +1,3 @@ +swapfile dev swaplo blocks free +/dev/swap 4294967295,4294967295 16 2097136 2097136 +/dev/swap2 4294967295,4294967295 16 2097136 2097136 diff --git a/spec/fixtures/unit/memory/solaris-swap_l-single b/spec/fixtures/unit/memory/solaris-swap_l-single new file mode 100644 index 0000000000..1f4e804165 --- /dev/null +++ b/spec/fixtures/unit/memory/solaris-swap_l-single @@ -0,0 +1,2 @@ +swapfile dev swaplo blocks free +/dev/swap 4294967295,4294967295 16 2097136 2097136 diff --git a/spec/fixtures/unit/memory/solaris-vmstat b/spec/fixtures/unit/memory/solaris-vmstat new file mode 100644 index 0000000000..d3c9d11d35 --- /dev/null +++ b/spec/fixtures/unit/memory/solaris-vmstat @@ -0,0 +1,3 @@ + kthr memory page disk faults cpu + r b w swap free re mf pi po fr de sr s0 s3 -- -- in sy cs us sy id + 0 0 0 1154552 476224 8 19 0 0 0 0 0 0 0 0 0 460 294 236 1 2 97 diff --git a/spec/unit/memory_spec.rb b/spec/unit/memory_spec.rb index 20f490cb20..97f427b352 100755 --- a/spec/unit/memory_spec.rb +++ b/spec/unit/memory_spec.rb @@ -48,22 +48,7 @@ before(:each) do Facter.fact(:kernel).stubs(:value).returns("Darwin") Facter::Util::Resolution.stubs(:exec).with('sysctl -n hw.memsize').returns('8589934592') - sample_vm_stat = </dev/null').returns sample_prtconf - - vmstat_lines = </dev/null').returns(my_fixture_read('solaris-prtconf')) + + Facter::Util::Resolution.stubs(:exec).with('vmstat').returns(my_fixture_read('solaris-vmstat')) end after(:each) do @@ -282,11 +225,7 @@ describe "when single swap exists" do before(:each) do - sample_swap_line = < Date: Wed, 5 Jun 2013 17:29:45 -0700 Subject: [PATCH 1284/3753] (#20915) Read dmi entries as binary data The DMI entry information that is read to determine the virtual platform is actually binary data. When it is interpreted as an encoded string, then there will be encoding errors. This changes it to be read in "rb" mode, which has nearly no effect on ruby 1.8.7, but causes it to be read as Encoding::ASCII_8BIT on ruby 1.9.3 (which is how ruby deals with binary data). --- lib/facter/util/file_read.rb | 4 ++++ lib/facter/util/virtual.rb | 4 +++- .../unit/util/virtual/invalid_unicode_dmi_entries | Bin 0 -> 74 bytes spec/unit/util/virtual_spec.rb | 9 +++++++++ 4 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 spec/fixtures/unit/util/virtual/invalid_unicode_dmi_entries diff --git a/lib/facter/util/file_read.rb b/lib/facter/util/file_read.rb index 258cd665bf..82404a3e1c 100644 --- a/lib/facter/util/file_read.rb +++ b/lib/facter/util/file_read.rb @@ -27,6 +27,10 @@ def self.read(path) Facter.debug "Could not read #{path}: #{detail.message}" nil end + + def self.read_binary(path) + File.open(path, "rb") { |contents| contents.read } + end end end end diff --git a/lib/facter/util/virtual.rb b/lib/facter/util/virtual.rb index a2457f5194..d22478137d 100644 --- a/lib/facter/util/virtual.rb +++ b/lib/facter/util/virtual.rb @@ -1,3 +1,5 @@ +require 'facter/util/file_read' + module Facter::Util::Virtual ## # virt_what is a delegating helper method intended to make it easier to stub @@ -143,7 +145,7 @@ def self.zlinux? # @return [String] or nil if the path does not exist def self.read_sysfs_dmi_entries(path="/sys/firmware/dmi/entries/1-0/raw") if File.exists?(path) - File.read(path) + Facter::Util::FileRead.read_binary(path) end end end diff --git a/spec/fixtures/unit/util/virtual/invalid_unicode_dmi_entries b/spec/fixtures/unit/util/virtual/invalid_unicode_dmi_entries new file mode 100644 index 0000000000000000000000000000000000000000..675dd143484af0d8f17ff375007deef178a09cc1 GIT binary patch literal 74 zcmZRSW@KPwVrID|drZFLv(L9ZyoGZX{F~mw#=x4HmzQ6XnyuiTo8-X|mRVF%nwaC1 YU%_CgXT)Ft6HxF?OwP#6OJ!gH0G`1ZUH||9 literal 0 HcmV?d00001 diff --git a/spec/unit/util/virtual_spec.rb b/spec/unit/util/virtual_spec.rb index c0ee4b6074..2331142db3 100755 --- a/spec/unit/util/virtual_spec.rb +++ b/spec/unit/util/virtual_spec.rb @@ -147,6 +147,15 @@ end end + it "reads dmi entries as ascii data" do + entries_file = my_fixture('invalid_unicode_dmi_entries') + expected_contents = 'Virtual' + + entries = Facter::Util::Virtual.read_sysfs_dmi_entries(entries_file) + + entries.should =~ /#{expected_contents}/ + end + it "should identify vserver_host when /proc/virtual exists" do Facter::Util::Virtual.expects(:vserver?).returns(true) FileTest.stubs(:exists?).with("/proc/virtual").returns(true) From f81e95b9ca17d20a59aafe3bdb04a34f328d2c13 Mon Sep 17 00:00:00 2001 From: Melissa Date: Thu, 6 Jun 2013 15:45:11 -0700 Subject: [PATCH 1285/3753] (maint) fix formatting and add more testing info Prior to this commit, there were some formatting issues taking away from readability of `lib/facter/operatingsystemrelease.rb`, and there was limiting testing of this file. This commit fixes those formatting issues, and adds more test coverage. --- lib/facter/operatingsystemrelease.rb | 33 +++++++++++++------ spec/fixtures/unit/operatingsystem/ascendos | 1 + spec/fixtures/unit/operatingsystem/centos | 1 + .../unit/operatingsystem/cloudlinux5.5 | 1 + spec/fixtures/unit/operatingsystem/debian | 1 + spec/fixtures/unit/operatingsystem/redhat | 1 + spec/fixtures/unit/operatingsystem/scientific | 1 + spec/fixtures/unit/operatingsystem/slc5.7 | 1 + spec/fixtures/unit/operatingsystem/ubuntu | 1 + .../unit/operatingsystem/xcp1.6.10-61809c | 1 + .../operatingsystem/xenserver5.6.0-31188p | 1 + .../unit/operatingsystemrelease/ascendos | 1 + .../unit/operatingsystemrelease/centos | 1 + .../unit/operatingsystemrelease/cloudlinux | 1 + .../unit/operatingsystemrelease/redhat | 1 + .../unit/operatingsystemrelease/scientific | 1 + spec/fixtures/unit/operatingsystemrelease/slc | 1 + spec/fixtures/unit/operatingsystemrelease/xcp | 1 + .../unit/operatingsystemrelease/xenserver | 1 + spec/unit/operatingsystemrelease_spec.rb | 19 ++++++++--- 20 files changed, 56 insertions(+), 14 deletions(-) create mode 100644 spec/fixtures/unit/operatingsystem/ascendos create mode 100644 spec/fixtures/unit/operatingsystem/centos create mode 100644 spec/fixtures/unit/operatingsystem/cloudlinux5.5 create mode 100644 spec/fixtures/unit/operatingsystem/debian create mode 100644 spec/fixtures/unit/operatingsystem/redhat create mode 100644 spec/fixtures/unit/operatingsystem/scientific create mode 100644 spec/fixtures/unit/operatingsystem/slc5.7 create mode 100644 spec/fixtures/unit/operatingsystem/ubuntu create mode 100644 spec/fixtures/unit/operatingsystem/xcp1.6.10-61809c create mode 100644 spec/fixtures/unit/operatingsystem/xenserver5.6.0-31188p create mode 100644 spec/fixtures/unit/operatingsystemrelease/ascendos create mode 100644 spec/fixtures/unit/operatingsystemrelease/centos create mode 100644 spec/fixtures/unit/operatingsystemrelease/cloudlinux create mode 100644 spec/fixtures/unit/operatingsystemrelease/redhat create mode 100644 spec/fixtures/unit/operatingsystemrelease/scientific create mode 100644 spec/fixtures/unit/operatingsystemrelease/slc create mode 100644 spec/fixtures/unit/operatingsystemrelease/xcp create mode 100644 spec/fixtures/unit/operatingsystemrelease/xenserver diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index 3f377919ae..20b9351a3a 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -98,20 +98,33 @@ end { - :OpenWrt => {:file => '/etc/openwrt_version', :regexp => /^(\d+\.\d+.*)/}, - :Slackware => {:file => '/etc/slackware-version', :regexp => /Slackware ([0-9.]+)/}, - :Mageia => {:file => '/etc/mageia-release', :regexp => /Mageia release ([0-9.]+)/}, - :Bluewhite64 => {:file => '/etc/bluewhite64-version', :regexp => /^\s*\w+\s+(\d+\.\d+)/}, - :Slamd64 => {:file => '/etc/slamd64-version', :regexp => /^\s*\w+\s+(\d+\.\d+)/}, -}.each do |platform, other_stuff| + :OpenWrt => { + :file => '/etc/openwrt_version', + :regexp => /^(\d+\.\d+.*)/ + }, + :Slackware => { + :file => '/etc/slackware-version', + :regexp => /Slackware ([0-9.]+)/ + }, + :Mageia => { + :file => '/etc/mageia-release', + :regexp => /Mageia release ([0-9.]+)/ + }, + :Bluewhite64 => { + :file => '/etc/bluewhite64-version', + :regexp => /^\s*\w+\s+(\d+\.\d+)/ + }, + :Slamd64 => { + :file => '/etc/slamd64-version', + :regexp => /^\s*\w+\s+(\d+\.\d+)/ + }, +}.each do |platform, platform_data| Facter.add(:operatingsystemrelease) do confine :operatingsystem => platform setcode do - if release = Facter::Util::FileRead.read(other_stuff[:file]) - if match = other_stuff[:regexp].match(release) + if release = Facter::Util::FileRead.read(platform_data[:file]) + if match = platform_data[:regexp].match(release) match[1] - else - "unknown" end end end diff --git a/spec/fixtures/unit/operatingsystem/ascendos b/spec/fixtures/unit/operatingsystem/ascendos new file mode 100644 index 0000000000..fa8ba7250d --- /dev/null +++ b/spec/fixtures/unit/operatingsystem/ascendos @@ -0,0 +1 @@ +Ascendos release 6.0 (Nameless) diff --git a/spec/fixtures/unit/operatingsystem/centos b/spec/fixtures/unit/operatingsystem/centos new file mode 100644 index 0000000000..e5ea412ef5 --- /dev/null +++ b/spec/fixtures/unit/operatingsystem/centos @@ -0,0 +1 @@ +CentOS release 5.6 (Final) diff --git a/spec/fixtures/unit/operatingsystem/cloudlinux5.5 b/spec/fixtures/unit/operatingsystem/cloudlinux5.5 new file mode 100644 index 0000000000..54bdcbd865 --- /dev/null +++ b/spec/fixtures/unit/operatingsystem/cloudlinux5.5 @@ -0,0 +1 @@ +CloudLinux Server release 5.5 diff --git a/spec/fixtures/unit/operatingsystem/debian b/spec/fixtures/unit/operatingsystem/debian new file mode 100644 index 0000000000..a33797735e --- /dev/null +++ b/spec/fixtures/unit/operatingsystem/debian @@ -0,0 +1 @@ +6.0.6\n diff --git a/spec/fixtures/unit/operatingsystem/redhat b/spec/fixtures/unit/operatingsystem/redhat new file mode 100644 index 0000000000..b9523bbb97 --- /dev/null +++ b/spec/fixtures/unit/operatingsystem/redhat @@ -0,0 +1 @@ +Red Hat Enterprise Linux Server release 6.0 (Santiago) diff --git a/spec/fixtures/unit/operatingsystem/scientific b/spec/fixtures/unit/operatingsystem/scientific new file mode 100644 index 0000000000..b096a9477f --- /dev/null +++ b/spec/fixtures/unit/operatingsystem/scientific @@ -0,0 +1 @@ +Scientific Linux release 6.0 (Carbon) diff --git a/spec/fixtures/unit/operatingsystem/slc5.7 b/spec/fixtures/unit/operatingsystem/slc5.7 new file mode 100644 index 0000000000..696f451b2e --- /dev/null +++ b/spec/fixtures/unit/operatingsystem/slc5.7 @@ -0,0 +1 @@ +Scientific Linux CERN SLC release 5.7 (Boron) diff --git a/spec/fixtures/unit/operatingsystem/ubuntu b/spec/fixtures/unit/operatingsystem/ubuntu new file mode 100644 index 0000000000..6c14eb2ebb --- /dev/null +++ b/spec/fixtures/unit/operatingsystem/ubuntu @@ -0,0 +1 @@ +Ubuntu 12.04.2. LTS \n \1 diff --git a/spec/fixtures/unit/operatingsystem/xcp1.6.10-61809c b/spec/fixtures/unit/operatingsystem/xcp1.6.10-61809c new file mode 100644 index 0000000000..f01583300a --- /dev/null +++ b/spec/fixtures/unit/operatingsystem/xcp1.6.10-61809c @@ -0,0 +1 @@ +XCP release 1.6.10-61809c diff --git a/spec/fixtures/unit/operatingsystem/xenserver5.6.0-31188p b/spec/fixtures/unit/operatingsystem/xenserver5.6.0-31188p new file mode 100644 index 0000000000..76f416a749 --- /dev/null +++ b/spec/fixtures/unit/operatingsystem/xenserver5.6.0-31188p @@ -0,0 +1 @@ +XenServer release 5.6.0-31188p (xenenterprise) diff --git a/spec/fixtures/unit/operatingsystemrelease/ascendos b/spec/fixtures/unit/operatingsystemrelease/ascendos new file mode 100644 index 0000000000..fa8ba7250d --- /dev/null +++ b/spec/fixtures/unit/operatingsystemrelease/ascendos @@ -0,0 +1 @@ +Ascendos release 6.0 (Nameless) diff --git a/spec/fixtures/unit/operatingsystemrelease/centos b/spec/fixtures/unit/operatingsystemrelease/centos new file mode 100644 index 0000000000..e5ea412ef5 --- /dev/null +++ b/spec/fixtures/unit/operatingsystemrelease/centos @@ -0,0 +1 @@ +CentOS release 5.6 (Final) diff --git a/spec/fixtures/unit/operatingsystemrelease/cloudlinux b/spec/fixtures/unit/operatingsystemrelease/cloudlinux new file mode 100644 index 0000000000..54bdcbd865 --- /dev/null +++ b/spec/fixtures/unit/operatingsystemrelease/cloudlinux @@ -0,0 +1 @@ +CloudLinux Server release 5.5 diff --git a/spec/fixtures/unit/operatingsystemrelease/redhat b/spec/fixtures/unit/operatingsystemrelease/redhat new file mode 100644 index 0000000000..b9523bbb97 --- /dev/null +++ b/spec/fixtures/unit/operatingsystemrelease/redhat @@ -0,0 +1 @@ +Red Hat Enterprise Linux Server release 6.0 (Santiago) diff --git a/spec/fixtures/unit/operatingsystemrelease/scientific b/spec/fixtures/unit/operatingsystemrelease/scientific new file mode 100644 index 0000000000..b096a9477f --- /dev/null +++ b/spec/fixtures/unit/operatingsystemrelease/scientific @@ -0,0 +1 @@ +Scientific Linux release 6.0 (Carbon) diff --git a/spec/fixtures/unit/operatingsystemrelease/slc b/spec/fixtures/unit/operatingsystemrelease/slc new file mode 100644 index 0000000000..696f451b2e --- /dev/null +++ b/spec/fixtures/unit/operatingsystemrelease/slc @@ -0,0 +1 @@ +Scientific Linux CERN SLC release 5.7 (Boron) diff --git a/spec/fixtures/unit/operatingsystemrelease/xcp b/spec/fixtures/unit/operatingsystemrelease/xcp new file mode 100644 index 0000000000..f01583300a --- /dev/null +++ b/spec/fixtures/unit/operatingsystemrelease/xcp @@ -0,0 +1 @@ +XCP release 1.6.10-61809c diff --git a/spec/fixtures/unit/operatingsystemrelease/xenserver b/spec/fixtures/unit/operatingsystemrelease/xenserver new file mode 100644 index 0000000000..76f416a749 --- /dev/null +++ b/spec/fixtures/unit/operatingsystemrelease/xenserver @@ -0,0 +1 @@ +XenServer release 5.6.0-31188p (xenenterprise) diff --git a/spec/unit/operatingsystemrelease_spec.rb b/spec/unit/operatingsystemrelease_spec.rb index cda20fe93d..6197cd19f9 100755 --- a/spec/unit/operatingsystemrelease_spec.rb +++ b/spec/unit/operatingsystemrelease_spec.rb @@ -13,6 +13,7 @@ Facter.clear end + #added test cases, it looks like Debian, Alpine, and Ubuntu are tested later in this code test_cases = { "OpenWrt" => "/etc/openwrt_version", "CentOS" => "/etc/redhat-release", @@ -26,15 +27,25 @@ "ovs" => "/etc/ovs-release", "OracleLinux" => "/etc/oracle-release", "Ascendos" => "/etc/redhat-release", + "CloudLinux" => "/etc/redhat-release", + "SLC" => "/etc/redhat-release", } + #these are the OSs that we have information on what the content fo the files they should be reading looks like + file_content_test_cases = [ + "CentOS", "RedHat", "Scientific", " Ascendos", "CloudLinux", "SLC"] + test_cases.each do |system, file| describe "with operatingsystem reported as #{system.inspect}" do it "should read the #{file.inspect} file" do Facter.fact(:operatingsystem).stubs(:value).returns(system) - - Facter::Util::FileRead.expects(:read).with(file).at_least(1) - + + if file_content_test_cases.include?(system) + Facter::Util::FileRead.expects(:read).with(file).at_least(1).returns(my_fixture_read(system.downcase)) + else + Facter::Util::FileRead.expects(:read).with(file).at_least(1) + end + Facter.fact(:operatingsystemrelease).value end end @@ -59,7 +70,7 @@ it "for Alpine it should use the contents of /etc/alpine-release" do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter.fact(:operatingsystem).stubs(:value).returns("Alpine") - + File.expects(:read).with("/etc/alpine-release").returns("foo") Facter.fact(:operatingsystemrelease).value.should == "foo" From 96ec7672daface665645fc7430a2d017432ba017 Mon Sep 17 00:00:00 2001 From: Melissa Date: Fri, 7 Jun 2013 11:55:51 -0700 Subject: [PATCH 1286/3753] (maint) remove unused code from resolution_spec Prior to this commit, the resolution_spec.rb file tests setcode for both a windows and non-windows system. The tests come out exactly the same for both systems. An investigation into the code suggests that this platform distinction is never actually used. This commit removes the part of the test that specifies what to do on a windows vs. non-windows system. --- spec/unit/util/resolution_spec.rb | 30 +++++++++--------------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index 222a98e620..f013ffb61b 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -198,30 +198,18 @@ class FlushFakeError < StandardError; end end describe "and the code is a string" do - [ true, - false - ].each do |bool| + it "should return the result of executing the code" do + @resolve.setcode "/bin/foo" + Facter::Util::Resolution.expects(:exec).once.with("/bin/foo").returns "yup" - describe "on #{(bool)?'':'non-'}windows system" do - before do - given_a_configuration_of(:is_windows => bool) - end - - it "should return the result of executing the code" do - @resolve.setcode "/bin/foo" - Facter::Util::Resolution.expects(:exec).once.with("/bin/foo").returns "yup" - - @resolve.value.should == "yup" - end - - it "should return nil if the value is an empty string" do - @resolve.setcode "/bin/foo" - Facter::Util::Resolution.expects(:exec).once.returns "" - @resolve.value.should be_nil - end - end + @resolve.value.should == "yup" end + it "should return nil if the value is an empty string" do + @resolve.setcode "/bin/foo" + Facter::Util::Resolution.expects(:exec).once.returns "" + @resolve.value.should be_nil + end end describe "and the code is a block" do From c8bf75709fbb090914ade0ca33431fd2eb586819 Mon Sep 17 00:00:00 2001 From: Melissa Date: Fri, 7 Jun 2013 13:33:44 -0700 Subject: [PATCH 1287/3753] (maint) remove whitespace and alter formatting Prior to this commit there were some whitespace errors in the code, relevant information for the test_cases was stored in two different data sets, and there was unnecessary comments. This commit fixes the whitespace issues, reformats the datastructure of the test_cases hash, and gets rid of the comments. --- spec/unit/operatingsystemrelease_spec.rb | 95 ++++++++++++++++-------- 1 file changed, 66 insertions(+), 29 deletions(-) diff --git a/spec/unit/operatingsystemrelease_spec.rb b/spec/unit/operatingsystemrelease_spec.rb index 6197cd19f9..ce2e75f60d 100755 --- a/spec/unit/operatingsystemrelease_spec.rb +++ b/spec/unit/operatingsystemrelease_spec.rb @@ -13,39 +13,76 @@ Facter.clear end - #added test cases, it looks like Debian, Alpine, and Ubuntu are tested later in this code test_cases = { - "OpenWrt" => "/etc/openwrt_version", - "CentOS" => "/etc/redhat-release", - "RedHat" => "/etc/redhat-release", - "Scientific" => "/etc/redhat-release", - "Fedora" => "/etc/fedora-release", - "MeeGo" => "/etc/meego-release", - "OEL" => "/etc/enterprise-release", - "oel" => "/etc/enterprise-release", - "OVS" => "/etc/ovs-release", - "ovs" => "/etc/ovs-release", - "OracleLinux" => "/etc/oracle-release", - "Ascendos" => "/etc/redhat-release", - "CloudLinux" => "/etc/redhat-release", - "SLC" => "/etc/redhat-release", + 'OpenWrt' => { + :path => '/etc/openwrt_version', + :has_fixture => false + }, + "CentOS" => { + :path => "/etc/redhat-release", + :has_fixture => true + }, + "RedHat" => { + :path => "/etc/redhat-release", + :has_fixture => true + }, + "Scientific" => { + :path => "/etc/redhat-release", + :has_fixture => true + }, + "Fedora" => { + :path => "/etc/fedora-release", + :has_fixture => false + }, + "MeeGo" => { + :path => "/etc/meego-release", + :has_fixture => false + }, + "OEL" => { + :path => "/etc/enterprise-release", + :has_fixture => false + }, + "oel" => { + :path => "/etc/enterprise-release", + :has_fixture => false + }, + "OVS" => { + :path => "/etc/ovs-release", + :has_fixture => false + }, + "ovs" => { + :path => "/etc/ovs-release", + :has_fixture => false + }, + "OracleLinux" => { + :path => "/etc/oracle-release", + :has_fixture => false + }, + "Ascendos" => { + :path => "/etc/redhat-release", + :has_fixture => true + }, + "CloudLinux" => { + :path => "/etc/redhat-release", + :has_fixture => true + }, + "SLC" => { + :path => "/etc/redhat-release", + :has_fixture => true + } } - #these are the OSs that we have information on what the content fo the files they should be reading looks like - file_content_test_cases = [ - "CentOS", "RedHat", "Scientific", " Ascendos", "CloudLinux", "SLC"] - - test_cases.each do |system, file| + test_cases.each do |system, file_data| describe "with operatingsystem reported as #{system.inspect}" do - it "should read the #{file.inspect} file" do + it "should read the #{file_data[:path].inspect} file" do Facter.fact(:operatingsystem).stubs(:value).returns(system) - - if file_content_test_cases.include?(system) - Facter::Util::FileRead.expects(:read).with(file).at_least(1).returns(my_fixture_read(system.downcase)) - else - Facter::Util::FileRead.expects(:read).with(file).at_least(1) - end - + + if file_data[:has_fixture] == true + Facter::Util::FileRead.expects(:read).with(file_data[:path]).returns(my_fixture_read(system.downcase)) + else + Facter::Util::FileRead.expects(:read).with(file_data[:path]).at_least(1) + end + Facter.fact(:operatingsystemrelease).value end end @@ -70,7 +107,7 @@ it "for Alpine it should use the contents of /etc/alpine-release" do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter.fact(:operatingsystem).stubs(:value).returns("Alpine") - + File.expects(:read).with("/etc/alpine-release").returns("foo") Facter.fact(:operatingsystemrelease).value.should == "foo" From 810b33b1fb758f488e3a3ee24a69ff5cf55412d9 Mon Sep 17 00:00:00 2001 From: Antoine Jacoutot Date: Sat, 11 May 2013 10:30:14 +0200 Subject: [PATCH 1288/3753] (#19293) Enhance the release fact for OpenBSD Currently facter only outputs the main release version number without the actual substring information. e.g. 5.3 versus 5.3-beta --- lib/facter/kernelrelease.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/facter/kernelrelease.rb b/lib/facter/kernelrelease.rb index 8bbc01ea0b..7901cc9b62 100644 --- a/lib/facter/kernelrelease.rb +++ b/lib/facter/kernelrelease.rb @@ -20,6 +20,13 @@ setcode 'oslevel -s' end +Facter.add("kernelrelease") do + confine :kernel => :openbsd + setcode do + Facter::Util::Resolution.exec("/sbin/sysctl -n kern.version").split(' ')[1] + end +end + Facter.add(:kernelrelease) do confine :kernel => "hp-ux" setcode do From 2f6b28a510ff2ca9fe7007999d9a61896958bc6d Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Wed, 12 Jun 2013 11:10:52 -0700 Subject: [PATCH 1289/3753] (maint) Whitespace fix for kernelrelease_spec.rb --- spec/unit/kernelrelease_spec.rb | 50 ++++++++++++++++----------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/spec/unit/kernelrelease_spec.rb b/spec/unit/kernelrelease_spec.rb index 8566d4aa8b..079e9cab06 100644 --- a/spec/unit/kernelrelease_spec.rb +++ b/spec/unit/kernelrelease_spec.rb @@ -2,52 +2,52 @@ require 'spec_helper' -describe "Kernel release fact" do +describe "Kernel release fact" do - describe "on Windows" do - before do + describe "on Windows" do + before do Facter.fact(:kernel).stubs(:value).returns("windows") require 'facter/util/wmi' version = stubs 'version' version.stubs(:Version).returns("test_kernel") Facter::Util::WMI.stubs(:execquery).with("SELECT Version from Win32_OperatingSystem").returns([version]) end - - it "should return the kernel release" do + + it "should return the kernel release" do Facter.fact(:kernelrelease).value.should == "test_kernel" - end - end + end + end - describe "on AIX" do - before do + describe "on AIX" do + before do Facter.fact(:kernel).stubs(:value).returns("aix") Facter::Util::Resolution.stubs(:exec).with('oslevel -s').returns("test_kernel") - end - - it "should return the kernel release" do + end + + it "should return the kernel release" do Facter.fact(:kernelrelease).value.should == "test_kernel" - end - end + end + end describe "on HP-UX" do before do - Facter.fact(:kernel).stubs(:value).returns("hp-ux") + Facter.fact(:kernel).stubs(:value).returns("hp-ux") Facter::Util::Resolution.stubs(:exec).with('uname -r').returns("B.11.31") - end - + end + it "should remove preceding letters" do Facter.fact(:kernelrelease).value.should == "11.31" - end - end + end + end - describe "on everything else" do + describe "on everything else" do before do Facter.fact(:kernel).stubs(:value).returns("linux") Facter::Util::Resolution.stubs(:exec).with('uname -r').returns("test_kernel") - end - + end + it "should return the kernel release" do Facter.fact(:kernelrelease).value.should == "test_kernel" - end - end -end + end + end +end From beb2ea47326e4a918cf86a781002ae3fd4be8dab Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Wed, 12 Jun 2013 11:24:08 -0700 Subject: [PATCH 1290/3753] (#19293) Unit tests for openbsd kernelrelease fact --- spec/fixtures/unit/kernelrelease/openbsd-5.3 | 2 ++ .../unit/kernelrelease/openbsd-5.3-current | 3 +++ spec/unit/kernelrelease_spec.rb | 16 ++++++++++++++++ 3 files changed, 21 insertions(+) create mode 100644 spec/fixtures/unit/kernelrelease/openbsd-5.3 create mode 100644 spec/fixtures/unit/kernelrelease/openbsd-5.3-current diff --git a/spec/fixtures/unit/kernelrelease/openbsd-5.3 b/spec/fixtures/unit/kernelrelease/openbsd-5.3 new file mode 100644 index 0000000000..181030d09f --- /dev/null +++ b/spec/fixtures/unit/kernelrelease/openbsd-5.3 @@ -0,0 +1,2 @@ +OpenBSD 5.3 (GENERIC.MP) #2: Fri May 17 15:54:55 CEST 2013 +root@binpatch-53-i386.mtier.org:/home/jasper/binpatchng/work-binpatch53-i386/src/sys/arch/i386/compile/GENERIC.MP diff --git a/spec/fixtures/unit/kernelrelease/openbsd-5.3-current b/spec/fixtures/unit/kernelrelease/openbsd-5.3-current new file mode 100644 index 0000000000..c1b4913a5b --- /dev/null +++ b/spec/fixtures/unit/kernelrelease/openbsd-5.3-current @@ -0,0 +1,3 @@ +OpenBSD 5.3-current (GENERIC.MP) #130: Wed Jun 5 15:15:03 MDT 2013 +deraadt@amd64.openbsd.org:/usr/src/sys/arch/amd64/compile/GENERIC.MP + diff --git a/spec/unit/kernelrelease_spec.rb b/spec/unit/kernelrelease_spec.rb index 079e9cab06..e803e182c2 100644 --- a/spec/unit/kernelrelease_spec.rb +++ b/spec/unit/kernelrelease_spec.rb @@ -50,4 +50,20 @@ Facter.fact(:kernelrelease).value.should == "test_kernel" end end + + describe 'on OpenBSD' do + before do + Facter.fact(:kernel).stubs(:value).returns :openbsd + end + + it 'parses 5.3-current sysctl output' do + Facter::Util::Resolution.expects(:exec).with("/sbin/sysctl -n kern.version").returns(my_fixture_read('openbsd-5.3-current')) + Facter.value(:kernelrelease).should == '5.3-current' + end + + it 'parses 5.3 sysctl output' do + Facter::Util::Resolution.expects(:exec).with("/sbin/sysctl -n kern.version").returns(my_fixture_read('openbsd-5.3')) + Facter.value(:kernelrelease).should == '5.3' + end + end end From 4b6ce1af90875bd4a678fe610998f8cafe91622a Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 13 Jun 2013 17:01:04 -0700 Subject: [PATCH 1291/3753] Test operatingsystemrelease output where possible On platforms where we have fixture data available for the operatingsystemrelease fact, we should test the parsing of the actual files. On other platforms where we lack such fixtures, just ensure that the correct file is read. Paired-with: Melissa --- spec/unit/operatingsystemrelease_spec.rb | 89 ++++++++++-------------- 1 file changed, 35 insertions(+), 54 deletions(-) diff --git a/spec/unit/operatingsystemrelease_spec.rb b/spec/unit/operatingsystemrelease_spec.rb index ce2e75f60d..7d87d80830 100755 --- a/spec/unit/operatingsystemrelease_spec.rb +++ b/spec/unit/operatingsystemrelease_spec.rb @@ -13,77 +13,58 @@ Facter.clear end - test_cases = { - 'OpenWrt' => { - :path => '/etc/openwrt_version', - :has_fixture => false - }, + # We don't currently have fixtures for these releases. + no_fixtures = { + "Fedora" => { :path => "/etc/fedora-release" }, + "MeeGo" => { :path => "/etc/meego-release" }, + "OEL" => { :path => "/etc/enterprise-release" }, + "oel" => { :path => "/etc/enterprise-release" }, + "OVS" => { :path => "/etc/ovs-release" }, + "ovs" => { :path => "/etc/ovs-release" }, + "OracleLinux" => { :path => "/etc/oracle-release" }, + 'OpenWrt' => { :path => '/etc/openwrt_version' }, + "Scientific" => { :path => "/etc/redhat-release" } + } + + no_fixtures.each do |system, file_data| + describe "with operatingsystem #{system.inspect}" do + it "reads the #{file_data[:path].inspect} file" do + Facter.fact(:operatingsystem).stubs(:value).returns(system) + Facter::Util::FileRead.expects(:read).with(file_data[:path]).at_least_once + Facter.fact(:operatingsystemrelease).value + end + end + end + + with_fixtures = { "CentOS" => { :path => "/etc/redhat-release", - :has_fixture => true + :expected_value => '5.6' }, "RedHat" => { :path => "/etc/redhat-release", - :has_fixture => true - }, - "Scientific" => { - :path => "/etc/redhat-release", - :has_fixture => true - }, - "Fedora" => { - :path => "/etc/fedora-release", - :has_fixture => false - }, - "MeeGo" => { - :path => "/etc/meego-release", - :has_fixture => false - }, - "OEL" => { - :path => "/etc/enterprise-release", - :has_fixture => false - }, - "oel" => { - :path => "/etc/enterprise-release", - :has_fixture => false - }, - "OVS" => { - :path => "/etc/ovs-release", - :has_fixture => false - }, - "ovs" => { - :path => "/etc/ovs-release", - :has_fixture => false - }, - "OracleLinux" => { - :path => "/etc/oracle-release", - :has_fixture => false + :expected_value => '6.0' }, "Ascendos" => { :path => "/etc/redhat-release", - :has_fixture => true + :expected_value => '6.0' }, "CloudLinux" => { :path => "/etc/redhat-release", - :has_fixture => true + :expected_value => '5.5' }, "SLC" => { :path => "/etc/redhat-release", - :has_fixture => true - } + :expected_value => '5.7' + }, } - test_cases.each do |system, file_data| - describe "with operatingsystem reported as #{system.inspect}" do - it "should read the #{file_data[:path].inspect} file" do + with_fixtures.each do |system, file_data| + describe "with operatingsystem #{system.inspect}" do + it "reads the #{file_data[:path].inspect} file" do Facter.fact(:operatingsystem).stubs(:value).returns(system) - - if file_data[:has_fixture] == true - Facter::Util::FileRead.expects(:read).with(file_data[:path]).returns(my_fixture_read(system.downcase)) - else - Facter::Util::FileRead.expects(:read).with(file_data[:path]).at_least(1) - end - - Facter.fact(:operatingsystemrelease).value + Facter::Util::FileRead.expects(:read).with(file_data[:path]).returns(my_fixture_read(system.downcase)) + Facter.fact(:operatingsystemrelease).value.should == file_data[:expected_value] end end end From 13d16b72f2702d841ed7b2026058bf630f956ed1 Mon Sep 17 00:00:00 2001 From: Matt Peterson Date: Fri, 14 Jun 2013 16:25:54 -0700 Subject: [PATCH 1292/3753] (#21260) Add Cumulus Linux OS detection support --- lib/facter/operatingsystem.rb | 5 ++++- lib/facter/operatingsystemmajrelease.rb | 3 ++- lib/facter/operatingsystemrelease.rb | 8 ++++++++ lib/facter/osfamily.rb | 2 +- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index c36a2bb4a8..1c443e3c4c 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -34,7 +34,10 @@ Facter.add(:operatingsystem) do confine :kernel => :linux setcode do - if Facter.value(:lsbdistid) == "Ubuntu" + if FileTest.exists?("/etc/os-release") + File.read("/etc/os-release") =~ /^NAME=.*["'](.+?)["']$/ + $1 + elsif Facter.value(:lsbdistid) == "Ubuntu" "Ubuntu" elsif FileTest.exists?("/etc/debian_version") "Debian" diff --git a/lib/facter/operatingsystemmajrelease.rb b/lib/facter/operatingsystemmajrelease.rb index f530125314..6e83578e88 100644 --- a/lib/facter/operatingsystemmajrelease.rb +++ b/lib/facter/operatingsystemmajrelease.rb @@ -25,7 +25,8 @@ :OVS, :RedHat, :Scientific, - :SLC + :SLC, + :"Cumulus Linux" ] setcode do Facter.value('operatingsystemrelease').split('.').first diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index 20b9351a3a..b6ac16b918 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -76,6 +76,14 @@ end end +Facter.add(:operatingsystemrelease) do + confine :operatingsystem => %w{Cumulus\ Linux} + setcode do + File.read("/etc/os-release") =~ /^VERSION_ID=.*["'](.+?)["']$/ + $1 + end +end + Facter.add(:operatingsystemrelease) do confine :operatingsystem => %w{SLES SLED OpenSuSE} setcode do diff --git a/lib/facter/osfamily.rb b/lib/facter/osfamily.rb index d3c6a5461a..64d1aeaad9 100644 --- a/lib/facter/osfamily.rb +++ b/lib/facter/osfamily.rb @@ -18,7 +18,7 @@ case Facter.value(:operatingsystem) when "RedHat", "Fedora", "CentOS", "Scientific", "SLC", "Ascendos", "CloudLinux", "PSBM", "OracleLinux", "OVS", "OEL", "Amazon", "XenServer" "RedHat" - when "Ubuntu", "Debian" + when "Ubuntu", "Debian", "Cumulus Linux" "Debian" when "SLES", "SLED", "OpenSuSE", "SuSE" "Suse" From 5548df76ecb9563de6301c0852d161ffb0cfbaf9 Mon Sep 17 00:00:00 2001 From: Matt Peterson Date: Fri, 14 Jun 2013 18:11:03 -0700 Subject: [PATCH 1293/3753] collapsed in a parsable symbol --- lib/facter/operatingsystem.rb | 2 +- lib/facter/operatingsystemmajrelease.rb | 2 +- lib/facter/operatingsystemrelease.rb | 2 +- lib/facter/osfamily.rb | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index 1c443e3c4c..bd4c44ce47 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -36,7 +36,7 @@ setcode do if FileTest.exists?("/etc/os-release") File.read("/etc/os-release") =~ /^NAME=.*["'](.+?)["']$/ - $1 + $1.gsub(/[^a-zA-Z]/, '') elsif Facter.value(:lsbdistid) == "Ubuntu" "Ubuntu" elsif FileTest.exists?("/etc/debian_version") diff --git a/lib/facter/operatingsystemmajrelease.rb b/lib/facter/operatingsystemmajrelease.rb index 6e83578e88..a05f0f0b47 100644 --- a/lib/facter/operatingsystemmajrelease.rb +++ b/lib/facter/operatingsystemmajrelease.rb @@ -26,7 +26,7 @@ :RedHat, :Scientific, :SLC, - :"Cumulus Linux" + :CumulusLinux ] setcode do Facter.value('operatingsystemrelease').split('.').first diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index b6ac16b918..5b46c6cd0e 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -77,7 +77,7 @@ end Facter.add(:operatingsystemrelease) do - confine :operatingsystem => %w{Cumulus\ Linux} + confine :operatingsystem => %w{CumulusLinux} setcode do File.read("/etc/os-release") =~ /^VERSION_ID=.*["'](.+?)["']$/ $1 diff --git a/lib/facter/osfamily.rb b/lib/facter/osfamily.rb index 64d1aeaad9..149f7f5d02 100644 --- a/lib/facter/osfamily.rb +++ b/lib/facter/osfamily.rb @@ -18,7 +18,7 @@ case Facter.value(:operatingsystem) when "RedHat", "Fedora", "CentOS", "Scientific", "SLC", "Ascendos", "CloudLinux", "PSBM", "OracleLinux", "OVS", "OEL", "Amazon", "XenServer" "RedHat" - when "Ubuntu", "Debian", "Cumulus Linux" + when "Ubuntu", "Debian", "CumulusLinux" "Debian" when "SLES", "SLED", "OpenSuSE", "SuSE" "Suse" From a55f4c42da303f11861f2cfb32f759e6c69a74c6 Mon Sep 17 00:00:00 2001 From: Matt Peterson Date: Fri, 14 Jun 2013 18:16:53 -0700 Subject: [PATCH 1294/3753] Per pull request #453, don't match an array when a single OS string is matched --- lib/facter/operatingsystemrelease.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index 5b46c6cd0e..b445385de3 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -64,7 +64,7 @@ end Facter.add(:operatingsystemrelease) do - confine :operatingsystem => %w{Ubuntu} + confine :operatingsystem => 'Ubuntu' setcode do if release = Facter::Util::FileRead.read('/etc/issue') if match = release.match(/Ubuntu ((\d+.\d+)(\.(\d+))?)/) @@ -77,7 +77,7 @@ end Facter.add(:operatingsystemrelease) do - confine :operatingsystem => %w{CumulusLinux} + confine :operatingsystem => 'CumulusLinux' setcode do File.read("/etc/os-release") =~ /^VERSION_ID=.*["'](.+?)["']$/ $1 @@ -140,7 +140,7 @@ end Facter.add(:operatingsystemrelease) do - confine :operatingsystem => %w{VMwareESX} + confine :operatingsystem => 'VMwareESX' setcode do release = Facter::Util::Resolution.exec('vmware -v') if match = /VMware ESX .*?(\d.*)/.match(release) From 6628b079b6aa7551e59dbd7c2451640cf21f37f6 Mon Sep 17 00:00:00 2001 From: Matt Peterson Date: Mon, 17 Jun 2013 08:49:31 -0700 Subject: [PATCH 1295/3753] liberal regex --- lib/facter/operatingsystem.rb | 2 +- lib/facter/operatingsystemrelease.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index bd4c44ce47..6221e120a3 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -35,7 +35,7 @@ confine :kernel => :linux setcode do if FileTest.exists?("/etc/os-release") - File.read("/etc/os-release") =~ /^NAME=.*["'](.+?)["']$/ + File.read("/etc/os-release") =~ /^NAME=["']?(.+?)["']?$/ $1.gsub(/[^a-zA-Z]/, '') elsif Facter.value(:lsbdistid) == "Ubuntu" "Ubuntu" diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index b445385de3..cf8bea10e5 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -79,7 +79,7 @@ Facter.add(:operatingsystemrelease) do confine :operatingsystem => 'CumulusLinux' setcode do - File.read("/etc/os-release") =~ /^VERSION_ID=.*["'](.+?)["']$/ + File.read("/etc/os-release") =~ /^VERSION_ID=["']?(.+?)["']?$/ $1 end end From 20293592682d32b414d3f409d0c9c0e2fe1f1610 Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Mon, 24 Jun 2013 14:27:12 -0700 Subject: [PATCH 1296/3753] (packaging) Use the packaging loader for tasks The packaging repo now uses an explicit loader which handles loading the various rake tasks in packaging. This commit updates the facter Rakefile to use the loader instead of a blind glob of the ext/packaging/tasks directory. We move this load into the rescue LoadError block because the packaging repo won't always be there, and require 'rake' ahead of time. --- Rakefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Rakefile b/Rakefile index aad745b5f2..792af41f95 100644 --- a/Rakefile +++ b/Rakefile @@ -6,7 +6,10 @@ require 'facter/version' $LOAD_PATH << File.join(File.dirname(__FILE__), 'tasks') +require 'rake' + begin + load File.join(File.dirname(__FILE__), 'ext', 'packaging', 'packaging.rake') require 'rubygems' require 'rspec' require 'rspec/core/rake_task' @@ -14,10 +17,7 @@ begin rescue LoadError end -require 'rake' - Dir['tasks/**/*.rake'].each { |t| load t } -Dir['ext/packaging/tasks/**/*'].sort.each { |t| load t } build_defs_file = 'ext/build_defaults.yaml' if File.exist?(build_defs_file) From 9ad6dea9f9772dd6424f2424a2ff3c3e3f350d8e Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Mon, 24 Jun 2013 14:58:43 -0700 Subject: [PATCH 1297/3753] (packaging) Remove Ubuntu Oneiric from build targets Oneiric is EOL, and we are no longer building for it. This commit removes the Oneiric target from the build list for facter. Signed-off-by: Moses Mendoza --- ext/build_defaults.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/build_defaults.yaml b/ext/build_defaults.yaml index 6d550ad084..74c88424ea 100644 --- a/ext/build_defaults.yaml +++ b/ext/build_defaults.yaml @@ -2,7 +2,7 @@ packaging_url: 'git://github.com/puppetlabs/packaging.git --branch=master' packaging_repo: 'packaging' default_cow: 'base-squeeze-i386.cow' -cows: 'base-lucid-i386.cow base-lucid-amd64.cow base-oneiric-i386.cow base-oneiric-amd64.cow base-precise-i386.cow base-precise-amd64.cow base-quantal-i386.cow base-quantal-amd64.cow base-raring-i386.cow base-raring-amd64.cow base-sid-i386.cow base-sid-amd64.cow base-squeeze-i386.cow base-squeeze-amd64.cow base-stable-i386.cow base-stable-amd64.cow base-testing-i386.cow base-testing-amd64.cow base-unstable-i386.cow base-unstable-amd64.cow base-wheezy-i386.cow base-wheezy-amd64.cow' +cows: 'base-lucid-i386.cow base-lucid-amd64.cow base-precise-i386.cow base-precise-amd64.cow base-quantal-i386.cow base-quantal-amd64.cow base-raring-i386.cow base-raring-amd64.cow base-sid-i386.cow base-sid-amd64.cow base-squeeze-i386.cow base-squeeze-amd64.cow base-stable-i386.cow base-stable-amd64.cow base-testing-i386.cow base-testing-amd64.cow base-unstable-i386.cow base-unstable-amd64.cow base-wheezy-i386.cow base-wheezy-amd64.cow' pbuild_conf: '/etc/pbuilderrc' packager: 'puppetlabs' gpg_name: 'info@puppetlabs.com' From 4b3a085cba637ca9832a08f7860e8de285b28eea Mon Sep 17 00:00:00 2001 From: Matthaus Owens Date: Wed, 26 Jun 2013 16:12:23 -0700 Subject: [PATCH 1298/3753] (packaging) Update FACTERVERSION to 1.7.2-rc1 --- lib/facter/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/version.rb b/lib/facter/version.rb index 89349f2aae..933350e469 100644 --- a/lib/facter/version.rb +++ b/lib/facter/version.rb @@ -1,6 +1,6 @@ module Facter if not defined? FACTERVERSION then - FACTERVERSION = '1.7.1' + FACTERVERSION = '1.7.2-rc1' end ## From c2078780731897e9e17cb31f76fdcda56333c70f Mon Sep 17 00:00:00 2001 From: Sergey Sudakovich Date: Wed, 26 Jun 2013 20:54:45 -0700 Subject: [PATCH 1299/3753] Spec for Cumulus Linux --- spec/unit/operatingsystem_spec.rb | 8 ++++++++ spec/unit/operatingsystemmajrelease_spec.rb | 2 +- spec/unit/operatingsystemrelease_spec.rb | 7 +++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/spec/unit/operatingsystem_spec.rb b/spec/unit/operatingsystem_spec.rb index b5fa77af9e..8dc9d86636 100755 --- a/spec/unit/operatingsystem_spec.rb +++ b/spec/unit/operatingsystem_spec.rb @@ -139,4 +139,12 @@ Facter.fact(:operatingsystem).value.should == "SLC" end end + describe "on Cumulus Linux" do + it "should identify as 'Cumulus Linux'" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + FileTest.expects(:exists?).with("/etc/os-release").returns true + File.expects(:read).with("/etc/os-release").returns 'NAME="Cumulus Linux"' + Facter.fact(:operatingsystem).value.should == "CumulusLinux" + end + end end diff --git a/spec/unit/operatingsystemmajrelease_spec.rb b/spec/unit/operatingsystemmajrelease_spec.rb index 9c1a467437..99948b0f2b 100644 --- a/spec/unit/operatingsystemmajrelease_spec.rb +++ b/spec/unit/operatingsystemmajrelease_spec.rb @@ -3,7 +3,7 @@ require 'facter' describe "OS Major Release fact" do - ['Amazon','CentOS','CloudLinux','Debian','Fedora','OEL','OracleLinux','OVS','RedHat','Scientific','SLC'].each do |operatingsystem| + ['Amazon','CentOS','CloudLinux','Debian','Fedora','OEL','OracleLinux','OVS','RedHat','Scientific','SLC','CumulusLinux'].each do |operatingsystem| context "on #{operatingsystem} operatingsystems" do it "should be derived from operatingsystemrelease" do Facter.fact(:kernel).stubs(:value).returns("Linux") diff --git a/spec/unit/operatingsystemrelease_spec.rb b/spec/unit/operatingsystemrelease_spec.rb index 7d87d80830..b4f23687cc 100755 --- a/spec/unit/operatingsystemrelease_spec.rb +++ b/spec/unit/operatingsystemrelease_spec.rb @@ -194,4 +194,11 @@ Facter.fact(:operatingsystemrelease).value.should == "10.04" end end + + it "for Cumulus Linux" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:operatingsystem).stubs(:value).returns("CumulusLinux") + File.expects(:read).with("/etc/os-release").returns("VERSION_ID=1.5.0") + Facter.fact(:operatingsystemrelease).value.should == "1.5.0" + end end From e54fb62e4f686ff12e345a3eda46633b33655bcc Mon Sep 17 00:00:00 2001 From: Matt Peterson Date: Thu, 27 Jun 2013 17:38:35 -0700 Subject: [PATCH 1300/3753] Per Adrien feedback, an explicit check and return value --- lib/facter/operatingsystemrelease.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index cf8bea10e5..80b5794049 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -77,10 +77,13 @@ end Facter.add(:operatingsystemrelease) do - confine :operatingsystem => 'CumulusLinux' +confine :operatingsystem => 'CumulusLinux' setcode do - File.read("/etc/os-release") =~ /^VERSION_ID=["']?(.+?)["']?$/ - $1 + if release = Facter::Util::FileRead.read('/etc/os-release') + if match = release.match(/VERSION_ID=["']?(.+?)["']?$/) + match[1] + end + end end end From 27200a72e0382f32505f80e53378a370c1fb1906 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Fri, 28 Jun 2013 09:55:28 -0700 Subject: [PATCH 1301/3753] (maint) remove stale documentation directory The documentation directory has long been superceded by the puppet-docs repo (https://github.com/puppetlabs/puppet-docs) and other sources. This commit removes the directory to reduce clutter. --- documentation/custom.page | 22 ---------------------- documentation/index.page | 19 ------------------- 2 files changed, 41 deletions(-) delete mode 100644 documentation/custom.page delete mode 100644 documentation/index.page diff --git a/documentation/custom.page b/documentation/custom.page deleted file mode 100644 index 2416606e9e..0000000000 --- a/documentation/custom.page +++ /dev/null @@ -1,22 +0,0 @@ ---- -inMenu: true -directoryName: Custom Facts ---- - -Facter does everything it can to make adding custom facts easy. It will -autoload any files it finds in a ``facter/`` directory in its search -path, so you don't need to modify the package files. Also, Facter will -search through your environment for any variables whose names start with -'FACTER_' (case insensitive) and automatically add those facts. - -As a simple example, here is how I publish my home directory to Puppet: - - Facter.add("home") do - setcode do - ENV['HOME'] - end - end - -I have ~/lib/ruby in my $RUBYLIB environment variable, so I just created -~/lib/ruby/facter and dropped the above code into a ``home.rb`` file -within that directory. diff --git a/documentation/index.page b/documentation/index.page deleted file mode 100644 index c64938aa86..0000000000 --- a/documentation/index.page +++ /dev/null @@ -1,19 +0,0 @@ ---- -inMenu: false -directoryName: Facter ---- - -A cross-platform Ruby library for retrieving facts from operating systems. -Supports multiple resolution mechanisms, any of which can be restricted to -working only on certain operating systems or environments. Facter is especially -useful for retrieving things like operating system names, IP addresses, MAC -addresses, and SSH keys. - -It is easy to extend Facter to include your own [custom facts](custom.html) or -to include additional mechanisms for retrieving facts. - -* [Downloads](/downloads/facter/) -* [Bug Tracking](/cgi-bin/facter.cgi) -* [API Documentation](/downloads/facter/apidocs/) - -*$Id$* From 330166b954f9d1939af22adf5f24e4a59c706f65 Mon Sep 17 00:00:00 2001 From: Matt Peterson Date: Fri, 28 Jun 2013 10:50:50 -0700 Subject: [PATCH 1302/3753] preserve agressive regex to match other setcodes, also explicit variable --- lib/facter/operatingsystem.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index 6221e120a3..da2a34c6c5 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -35,8 +35,8 @@ confine :kernel => :linux setcode do if FileTest.exists?("/etc/os-release") - File.read("/etc/os-release") =~ /^NAME=["']?(.+?)["']?$/ - $1.gsub(/[^a-zA-Z]/, '') + match = File.read('/etc/os-release').match /^NAME=["']?(.+?)["']?$/ + match[1].gsub(/[^a-zA-Z]/, '') elsif Facter.value(:lsbdistid) == "Ubuntu" "Ubuntu" elsif FileTest.exists?("/etc/debian_version") From 766c84a84a4a7008858c45eaf221402a81e1bfa2 Mon Sep 17 00:00:00 2001 From: Jasper Lievisse Adriaanse Date: Thu, 27 Jun 2013 22:59:20 +0200 Subject: [PATCH 1303/3753] Add physicalprocessorcount fact for OpenBSD --- lib/facter/physicalprocessorcount.rb | 7 +++++++ spec/unit/physicalprocessorcount_spec.rb | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/lib/facter/physicalprocessorcount.rb b/lib/facter/physicalprocessorcount.rb index 94f5f70bf4..36af4c4ac3 100644 --- a/lib/facter/physicalprocessorcount.rb +++ b/lib/facter/physicalprocessorcount.rb @@ -79,3 +79,10 @@ end end end + +Facter.add('physicalprocessorcount') do + confine :kernel => :openbsd + setcode do + Facter::Util::Resolution.exec("sysctl -n hw.ncpufound") + end +end diff --git a/spec/unit/physicalprocessorcount_spec.rb b/spec/unit/physicalprocessorcount_spec.rb index 9e264fcf68..f05fc0ab4b 100755 --- a/spec/unit/physicalprocessorcount_spec.rb +++ b/spec/unit/physicalprocessorcount_spec.rb @@ -140,4 +140,12 @@ end end end + + describe "on openbsd" do + it "should return 4 physical CPUs" do + Facter.fact(:kernel).stubs(:value).returns("OpenBSD") + Facter::Util::Resolution.expects(:exec).with("sysctl -n hw.ncpufound").returns("4") + Facter.fact(:physicalprocessorcount).value.should == "4" + end + end end From d091bf882d731232ebcbf8d1dc324593cc837a98 Mon Sep 17 00:00:00 2001 From: Matthaus Owens Date: Fri, 28 Jun 2013 13:32:56 -0700 Subject: [PATCH 1304/3753] (maint) Remove unused facter.conf Facter.conf is not used anywhere in facter and has not been modified in the entire git history of facter. This commit removes the file and directory to reduce clutter. --- etc/facter.conf | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 etc/facter.conf diff --git a/etc/facter.conf b/etc/facter.conf deleted file mode 100644 index 9140d501e8..0000000000 --- a/etc/facter.conf +++ /dev/null @@ -1,5 +0,0 @@ -Hostname -OperatingSystem -OperatingSystemRelease -SSHDSAKey -CfKey From 9e0c3bfc385290c5f5bc5020a3445ad277a03e5f Mon Sep 17 00:00:00 2001 From: Justin Carr Date: Fri, 28 Jun 2013 12:03:34 -0700 Subject: [PATCH 1305/3753] (maint) Refactor hpux_spec.rb to reduce test redundancy Prior to this commit tests for IP facts had significant duplication making it difficult to maintain in the event fixtures change or version support is added or dropped. This commit consolidates the common features of the tests, making it easier to identify where maintenance should occur as fixtures change. --- spec/unit/util/ip/hpux_spec.rb | 121 ++++++++++----------------------- 1 file changed, 35 insertions(+), 86 deletions(-) diff --git a/spec/unit/util/ip/hpux_spec.rb b/spec/unit/util/ip/hpux_spec.rb index 3874f2a6e3..7abd41b522 100644 --- a/spec/unit/util/ip/hpux_spec.rb +++ b/spec/unit/util/ip/hpux_spec.rb @@ -181,101 +181,50 @@ described_class.stubs(:exec).returns(ifconfig_output) end - describe "lan1" do - let(:interface) { 'lan1' } - - describe "ipaddress" do - let(:label) { 'ipaddress' } - - it { value_for_interface_and_label.should eq '10.1.54.36' } - end - - describe "netmask" do - let(:label) { 'netmask' } - - it { value_for_interface_and_label.should eq '255.255.255.0' } - end - - describe "macaddress" do - let(:label) { 'macaddress' } - let(:macaddress) { '00:17:FD:2D:2A:57' } - - before :each do - described_class.expects(:lanscan).returns(lanscan_output) + { "lan1" => + { :ipaddress => '10.1.54.36', + :netmask => '255.255.255.0', + :macaddress => '00:17:FD:2D:2A:57' + }, + "lan0" => + { :ipaddress => '192.168.30.152', + :netmask => '255.255.255.0', + :macaddress => '00:12:31:7D:62:09'}, + "lo0" => + { :ipaddress => '127.0.0.1', + :netmask => '255.0.0.0', + :macaddress => nil} + }.each do |key, value| + describe key do + let (:interface) { key } + + describe "ipaddress" do + let(:label) { 'ipaddress' } + + it { value_for_interface_and_label.should eq value[:ipaddress] } end - it { value_for_interface_and_label.should eq macaddress } - end - - describe "mtu" do - let(:label) { 'mtu' } - - it { pending "(#17808) MTU has not been implemented for HP-UX" } - end - end - - describe "lan0" do - let(:interface) { 'lan0' } - - describe "ipaddress" do - let(:label) { 'ipaddress' } - - it { value_for_interface_and_label.should eq '192.168.30.152' } - end - - describe "netmask" do - let(:label) { 'netmask' } - - it { value_for_interface_and_label.should eq '255.255.255.0' } - end + describe "netmask" do + let(:label) { 'netmask' } - describe "macaddress" do - let(:label) { 'macaddress' } - let(:macaddress) { '00:12:31:7D:62:09' } - - before :each do - described_class.expects(:lanscan).returns(lanscan_output) + it { value_for_interface_and_label.should eq value[:netmask] } end - it { value_for_interface_and_label.should eq macaddress } - end + describe "macaddress" do + let(:label) { 'macaddress' } + + before :each do + described_class.expects(:lanscan).returns(lanscan_output) + end - describe "mtu" do - let(:label) { 'mtu' } - - it { pending "(#17808) MTU has not been implemented for HP-UX" } - end - end - - describe "lo0" do - let(:interface) { 'lo0' } - - describe "ipaddress" do - let(:label) { 'ipaddress' } - - it { value_for_interface_and_label.should eq '127.0.0.1' } - end - - describe "netmask" do - let(:label) { 'netmask' } - - it { value_for_interface_and_label.should eq '255.0.0.0' } - end - - describe "macaddress" do - let(:label) { 'macaddress' } - - before :each do - described_class.expects(:lanscan).returns(lanscan_output) + it { value_for_interface_and_label.should eq value[:macaddress] } end - it { value_for_interface_and_label.should be_nil } - end - - describe "mtu" do - let(:label) { 'mtu' } + describe "mtu" do + let(:label) { 'mtu' } - it { pending "(#17808) MTU has not been implemented for HP-UX" } + it { pending "(#17808) MTU has not been implemented for HP-UX" } + end end end end From 9be738fbfd7accfa1946e24518b41fde29ce01dc Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Mon, 1 Jul 2013 15:47:45 -0700 Subject: [PATCH 1306/3753] (#21533) Verify facter doesn't write to stderr This commit doesn't fix #21533, but it adds an acceptance test to detect if executing `facter` writes to stdout. --- acceptance/tests/no_errors_on_stderr.rb | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 acceptance/tests/no_errors_on_stderr.rb diff --git a/acceptance/tests/no_errors_on_stderr.rb b/acceptance/tests/no_errors_on_stderr.rb new file mode 100644 index 0000000000..d9ae59936e --- /dev/null +++ b/acceptance/tests/no_errors_on_stderr.rb @@ -0,0 +1,6 @@ +test_name "Running facter should not output anything to stderr" + +on(hosts, facter) do |result| + fail_test "Hostname fact is missing" unless stdout =~ /hostname\s*=>\s*\S*/ + fail_test "Facter should not have written to stderr: #{stderr}" unless stderr == "" +end From 397c9fd7c9bbe64f8d5c3eb3f3bc5aa95969ad3d Mon Sep 17 00:00:00 2001 From: Jasper Lievisse Adriaanse Date: Tue, 2 Jul 2013 17:10:32 +0200 Subject: [PATCH 1307/3753] Add tests for OpenBSD ifconfig with bridge(4) rules. http://projects.puppetlabs.com/issues/16630 --- spec/fixtures/ifconfig/openbsd_bridge_rules | 11 +++++++++++ spec/unit/macaddress_spec.rb | 12 ++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 spec/fixtures/ifconfig/openbsd_bridge_rules diff --git a/spec/fixtures/ifconfig/openbsd_bridge_rules b/spec/fixtures/ifconfig/openbsd_bridge_rules new file mode 100644 index 0000000000..357de0194f --- /dev/null +++ b/spec/fixtures/ifconfig/openbsd_bridge_rules @@ -0,0 +1,11 @@ +bridge0: flags=41 + groups: bridge + priority 32768 hellotime 2 fwddelay 15 maxage 20 holdcnt 6 proto rstp + vlan1 flags=3 + port 5 ifpriority 0 ifcost 0 + em0 flags=7 + port 1 ifpriority 0 ifcost 0 +block out on em0 src 00:24:21:ef:1b:de +pflog0: flags=41 mtu 33152 + priority: 0 + groups: pflog diff --git a/spec/unit/macaddress_spec.rb b/spec/unit/macaddress_spec.rb index 638ef2ad7c..9ded257ac7 100755 --- a/spec/unit/macaddress_spec.rb +++ b/spec/unit/macaddress_spec.rb @@ -62,4 +62,16 @@ def netsh_fixture(filename) end end + describe "when run on OpenBSD with bridge(4) rules" do + it "should return macaddress information" do + Facter.fact(:kernel).stubs(:value).returns("OpenBSD") + Facter::Util::IP.stubs(:get_ifconfig).returns("/sbin/ifconfig") + Facter::Util::IP.stubs(:exec_ifconfig). + returns(ifconfig_fixture('openbsd_bridge_rules')) + + proc { Facter.value(:macaddress) }.should_not raise_error + Facter.value(:macaddress).should be_nil + end + end + end From 166e6b202cd131608c38d69c786dcdd1302542eb Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Wed, 3 Jul 2013 13:46:25 -0500 Subject: [PATCH 1308/3753] Adding a windows fact for i686 to architecture specs --- spec/unit/architecture_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/unit/architecture_spec.rb b/spec/unit/architecture_spec.rb index 7e69198aef..1c45e04a53 100755 --- a/spec/unit/architecture_spec.rb +++ b/spec/unit/architecture_spec.rb @@ -22,6 +22,7 @@ ["Gentoo","i686"] => "x86", ["Gentoo","pentium"] => "x86", ["windows","i386"] => "x86", + ["windows","i686"] => "x86", ["windows","x64"] => "x64", } generic_archs = Hash.new From 7c1b49e6b0163ef2a0319a70b65fac8220d2b846 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Wed, 3 Jul 2013 13:56:49 -0500 Subject: [PATCH 1309/3753] (#20989) x86 windows hardware model show i686 not i1586 Currently hardware model uses the cpu level when using 32 bit windows on x64 capable hardware. The actual architecture is i686 even though it might be showing as i1586 if the cpu level is 15. Based on my research on this, all levels above 6 should show the i686 hardware model. i1586 or i1186 would be a misnomer. This fix adds a check to determine if the cpu level is over 6 and then responds by setting the architecture_level (new) to 6, otherwise it uses the cpu level coming back from the windows processor query. Note this only affects windows hardware. --- lib/facter/hardwaremodel.rb | 9 +++++++-- spec/unit/hardwaremodel_spec.rb | 17 ++++++++++++++++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/lib/facter/hardwaremodel.rb b/lib/facter/hardwaremodel.rb index adfda481b8..48a1b934e7 100644 --- a/lib/facter/hardwaremodel.rb +++ b/lib/facter/hardwaremodel.rb @@ -32,17 +32,22 @@ # http://source.winehq.org/source/include/winnt.h#L568 # http://msdn.microsoft.com/en-us/library/windows/desktop/aa394373(v=vs.85).aspx # http://msdn.microsoft.com/en-us/library/windows/desktop/windows.system.processorarchitecture.aspx + # http://linux.derkeiler.com/Mailing-Lists/Kernel/2008-05/msg12924.html (anything over 6 is still i686) # Also, arm and neutral are included because they are valid for the upcoming # windows 8 release. --jeffweiss 23 May 2012 require 'facter/util/wmi' model = "" + architecture_level = nil + Facter::Util::WMI.execquery("select Architecture, Level, AddressWidth from Win32_Processor").each do |cpu| + architecture_level = (cpu.Level > 5) ? 6 : cpu.Level; + model = case cpu.Architecture when 11 then 'neutral' # PROCESSOR_ARCHITECTURE_NEUTRAL when 10 then 'i686' # PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 when 9 then # PROCESSOR_ARCHITECTURE_AMD64 - cpu.AddressWidth == 32 ? "i#{cpu.Level}86" : 'x64' # 32 bit OS on 64 bit CPU + cpu.AddressWidth == 32 ? "i#{architecture_level}86" : 'x64' # 32 bit OS on 64 bit CPU when 8 then 'msil' # PROCESSOR_ARCHITECTURE_MSIL when 7 then 'alpha64' # PROCESSOR_ARCHITECTURE_ALPHA64 when 6 then 'ia64' # PROCESSOR_ARCHITECTURE_IA64 @@ -51,7 +56,7 @@ when 3 then 'powerpc' # PROCESSOR_ARCHITECTURE_PPC when 2 then 'alpha' # PROCESSOR_ARCHITECTURE_ALPHA when 1 then 'mips' # PROCESSOR_ARCHITECTURE_MIPS - when 0 then "i#{cpu.Level}86" # PROCESSOR_ARCHITECTURE_INTEL + when 0 then "i#{architecture_level}86" # PROCESSOR_ARCHITECTURE_INTEL else 'unknown' # PROCESSOR_ARCHITECTURE_UNKNOWN end break diff --git a/spec/unit/hardwaremodel_spec.rb b/spec/unit/hardwaremodel_spec.rb index e9ef3d40de..4d2c7d15aa 100644 --- a/spec/unit/hardwaremodel_spec.rb +++ b/spec/unit/hardwaremodel_spec.rb @@ -15,6 +15,14 @@ Facter.fact(:kernel).stubs(:value).returns("windows") end + it "should detect i486" do + cpu = mock('cpu', :Architecture => 0) + cpu.expects(:Level).returns(4).twice + Facter::Util::WMI.expects(:execquery).returns([cpu]) + + Facter.fact(:hardwaremodel).value.should == "i486" + end + it "should detect i686" do cpu = mock('cpu', :Architecture => 0, :Level => 6) Facter::Util::WMI.expects(:execquery).returns([cpu]) @@ -23,7 +31,7 @@ end it "should detect x64" do - cpu = mock('cpu', :Architecture => 9, :AddressWidth => 64) + cpu = mock('cpu', :Architecture => 9, :AddressWidth => 64, :Level => 6) Facter::Util::WMI.expects(:execquery).returns([cpu]) Facter.fact(:hardwaremodel).value.should == "x64" @@ -35,5 +43,12 @@ Facter.fact(:hardwaremodel).value.should == "i686" end + + it "(#20989) should report i686 when a 32 bit OS is running on a 64 bit CPU and when level is greater than 6 (and not something like i1586)" do + cpu = mock('cpu', :Architecture => 9, :AddressWidth => 32, :Level => 15) + Facter::Util::WMI.expects(:execquery).returns([cpu]) + + Facter.fact(:hardwaremodel).value.should == "i686" + end end end From 1938f391f9b4d17630d951f99dd8be91709e5bb1 Mon Sep 17 00:00:00 2001 From: Carl-Christian Salvesen Date: Wed, 5 Jun 2013 13:32:04 +0200 Subject: [PATCH 1310/3753] Add LinuxMint to operating system facts --- lib/facter/operatingsystem.rb | 2 ++ lib/facter/operatingsystemrelease.rb | 11 +++++++++++ lib/facter/osfamily.rb | 2 +- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index c36a2bb4a8..e3a9a40e46 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -36,6 +36,8 @@ setcode do if Facter.value(:lsbdistid) == "Ubuntu" "Ubuntu" + elsif Facter.value(:lsbdistid) == "LinuxMint" + "LinuxMint" elsif FileTest.exists?("/etc/debian_version") "Debian" elsif FileTest.exists?("/etc/openwrt_release") diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index 20b9351a3a..0523c5bd07 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -76,6 +76,17 @@ end end +Facter.add(:operatingsystemrelease) do + confine :operatingsystem => 'LinuxMint' + setcode do + if release = Facter::Util::FileRead.read('/etc/linuxmint/info') + if match = release.match(/RELEASE\=(\d+)/) + match[1] + end + end + end +end + Facter.add(:operatingsystemrelease) do confine :operatingsystem => %w{SLES SLED OpenSuSE} setcode do diff --git a/lib/facter/osfamily.rb b/lib/facter/osfamily.rb index d3c6a5461a..d11e52aa21 100644 --- a/lib/facter/osfamily.rb +++ b/lib/facter/osfamily.rb @@ -18,7 +18,7 @@ case Facter.value(:operatingsystem) when "RedHat", "Fedora", "CentOS", "Scientific", "SLC", "Ascendos", "CloudLinux", "PSBM", "OracleLinux", "OVS", "OEL", "Amazon", "XenServer" "RedHat" - when "Ubuntu", "Debian" + when "LinuxMint", "Ubuntu", "Debian" "Debian" when "SLES", "SLED", "OpenSuSE", "SuSE" "Suse" From ec6aed48db0e00315a2a44f0790810a6b7e39229 Mon Sep 17 00:00:00 2001 From: Spencer Krum Date: Fri, 28 Jun 2013 14:47:52 -0700 Subject: [PATCH 1311/3753] adding tests for LinuxMint facts --- spec/unit/operatingsystem_spec.rb | 7 +++++++ spec/unit/operatingsystemrelease_spec.rb | 1 + spec/unit/osfamily_spec.rb | 1 + 3 files changed, 9 insertions(+) diff --git a/spec/unit/operatingsystem_spec.rb b/spec/unit/operatingsystem_spec.rb index b5fa77af9e..8eadee4b01 100755 --- a/spec/unit/operatingsystem_spec.rb +++ b/spec/unit/operatingsystem_spec.rb @@ -89,6 +89,13 @@ Facter.fact(:operatingsystem).value.should == "Ubuntu" end + it "on LinuxMint should use the lsbdistid fact" do + FileUtils.stubs(:exists?).with("/etc/debian_version").returns true + + Facter.stubs(:value).with(:lsbdistid).returns("LinuxMint") + Facter.fact(:operatingsystem).value.should == "LinuxMint" + end + end diff --git a/spec/unit/operatingsystemrelease_spec.rb b/spec/unit/operatingsystemrelease_spec.rb index 7d87d80830..2a766c8160 100755 --- a/spec/unit/operatingsystemrelease_spec.rb +++ b/spec/unit/operatingsystemrelease_spec.rb @@ -16,6 +16,7 @@ # We don't currently have fixtures for these releases. no_fixtures = { "Fedora" => { :path => "/etc/fedora-release" }, + "LinuxMint" => { :path => "/etc/linuxmint/info" }, "MeeGo" => { :path => "/etc/meego-release" }, "OEL" => { :path => "/etc/enterprise-release" }, "oel" => { :path => "/etc/enterprise-release" }, diff --git a/spec/unit/osfamily_spec.rb b/spec/unit/osfamily_spec.rb index 4113b3c326..f6d6502928 100644 --- a/spec/unit/osfamily_spec.rb +++ b/spec/unit/osfamily_spec.rb @@ -13,6 +13,7 @@ 'Solaris' => 'Solaris', 'Ubuntu' => 'Debian', 'Debian' => 'Debian', + 'LinuxMint' => 'Debian', 'Gentoo' => 'Gentoo', 'Fedora' => 'RedHat', 'Amazon' => 'RedHat', From 428c403430c2a6a4eac88420b323934439610a63 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Wed, 3 Jul 2013 16:12:21 -0700 Subject: [PATCH 1312/3753] (maint) Use `expect` blocks over procs in specs --- spec/unit/macaddress_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/unit/macaddress_spec.rb b/spec/unit/macaddress_spec.rb index 9ded257ac7..004168130c 100755 --- a/spec/unit/macaddress_spec.rb +++ b/spec/unit/macaddress_spec.rb @@ -37,7 +37,7 @@ def netsh_fixture(filename) Facter::Util::IP.stubs(:exec_ifconfig).with(["-a","2>/dev/null"]). returns(ifconfig_fixture('linux_ifconfig_no_mac')) - proc { Facter.value(:macaddress) }.should_not raise_error + expect { Facter.value(:macaddress) }.to_not raise_error Facter.value(:macaddress).should be_nil end @@ -46,7 +46,7 @@ def netsh_fixture(filename) Facter::Util::IP.stubs(:exec_ifconfig).with(["-a","2>/dev/null"]). returns(ifconfig_fixture('linux_ifconfig_venet')) - proc { Facter.value(:macaddress) }.should_not raise_error + expect { Facter.value(:macaddress) }.to_not raise_error Facter.value(:macaddress).should be_nil end end @@ -69,7 +69,7 @@ def netsh_fixture(filename) Facter::Util::IP.stubs(:exec_ifconfig). returns(ifconfig_fixture('openbsd_bridge_rules')) - proc { Facter.value(:macaddress) }.should_not raise_error + expect { Facter.value(:macaddress) }.to_not raise_error Facter.value(:macaddress).should be_nil end end From 89998f51e87805be45588d451b0bd6f884ce9fbf Mon Sep 17 00:00:00 2001 From: Andrew Beresford Date: Fri, 14 Jun 2013 11:08:10 +0200 Subject: [PATCH 1313/3753] (#21250) Fix zfs_version on old versions of Solaris 10 --- lib/facter/zfs_version.rb | 8 +++- spec/fixtures/unit/zfs_version/zfs_new | 61 ++++++++++++++++++++++++++ spec/fixtures/unit/zfs_version/zfs_old | 43 ++++++++++++++++++ spec/unit/zfs_version_spec.rb | 16 ++++++- 4 files changed, 125 insertions(+), 3 deletions(-) create mode 100644 spec/fixtures/unit/zfs_version/zfs_new create mode 100644 spec/fixtures/unit/zfs_version/zfs_old diff --git a/lib/facter/zfs_version.rb b/lib/facter/zfs_version.rb index 8eeee11db5..72af6c7b56 100644 --- a/lib/facter/zfs_version.rb +++ b/lib/facter/zfs_version.rb @@ -3,8 +3,12 @@ Facter.add('zfs_version') do setcode do if Facter::Util::Resolution.which('zfs') - zfs_v = Facter::Util::Resolution.exec('zfs upgrade -v') - zfs_version = zfs_v.scan(/^\s+(\d+)\s+/m).flatten.last unless zfs_v.nil? + zfs_help = Facter::Util::Resolution.exec('zfs -?') + zfs_has_upgrade = zfs_help.match(/^\s+upgrade/) unless zfs_help.nil? + if zfs_has_upgrade + zfs_v = Facter::Util::Resolution.exec('zfs upgrade -v') + zfs_version = zfs_v.scan(/^\s+(\d+)\s+/m).flatten.last unless zfs_v.nil? + end end end end diff --git a/spec/fixtures/unit/zfs_version/zfs_new b/spec/fixtures/unit/zfs_version/zfs_new new file mode 100644 index 0000000000..6b36e2475a --- /dev/null +++ b/spec/fixtures/unit/zfs_version/zfs_new @@ -0,0 +1,61 @@ +usage: zfs command args ... +where 'command' is one of the following: + + create [-p] [-o property=value] ... + create [-ps] [-b blocksize] [-o property=value] ... -V + destroy [-rRf] + destroy [-rRd] + + snapshot [-r] [-o property=value] ... + rollback [-rRf] + clone [-p] [-o property=value] ... + promote + rename + rename -p + rename -r + list [-rH][-d max] [-o property[,...]] [-t type[,...]] [-s property] ... + [-S property] ... [filesystem|volume|snapshot] ... + + set ... + get [-rHp] [-d max] [-o "all" | field[,...]] [-s source[,...]] + <"all" | property[,...]> [filesystem|volume|snapshot] ... + inherit [-rS] ... + upgrade [-v] + upgrade [-r] [-V version] <-a | filesystem ...> + userspace [-hniHp] [-o field[,...]] [-sS field] ... [-t type[,...]] + + groupspace [-hniHpU] [-o field[,...]] [-sS field] ... [-t type[,...]] + + + mount + mount [-vO] [-o opts] <-a | filesystem> + unmount [-f] <-a | filesystem|mountpoint> + share <-a | filesystem> + unshare <-a | filesystem|mountpoint> + + send [-RDp] [-[iI] snapshot] + receive [-vnFu] + receive [-vnFu] [-d | -e] + + allow + allow [-ldug] <"everyone"|user|group>[,...] [,...] + + allow [-ld] -e [,...] + allow -c [,...] + allow -s @setname [,...] + + unallow [-rldug] <"everyone"|user|group>[,...] + [[,...]] + unallow [-rld] -e [[,...]] + unallow [-r] -c [[,...]] + unallow [-r] -s @setname [[,...]] + + hold [-r] ... + holds [-r] ... + release [-r] ... + +Each dataset is of the form: pool/[dataset/]*dataset[@name] + +For the property list, run: zfs set|get + +For the delegated permission list, run: zfs allow|unallow diff --git a/spec/fixtures/unit/zfs_version/zfs_old b/spec/fixtures/unit/zfs_version/zfs_old new file mode 100644 index 0000000000..734edc93df --- /dev/null +++ b/spec/fixtures/unit/zfs_version/zfs_old @@ -0,0 +1,43 @@ +usage: zfs command args ... +where 'command' is one of the following: + + create [[-o property=value] ... ] + create [-s] [-b blocksize] [[-o property=value] ...] + -V + destroy [-rRf] + + snapshot [-r] + rollback [-rRf] + clone + promote + rename + + list [-rH] [-o property[,property]...] [-t type[,type]...] + [-s property [-s property]...] [-S property [-S property]...] + [filesystem|volume|snapshot] ... + + set ... + get [-rHp] [-o field[,field]...] [-s source[,source]...] + [filesystem|volume|snapshot] ... + inherit [-r] ... + + mount + mount [-o opts] [-O] -a + mount [-o opts] [-O] + + unmount [-f] -a + unmount [-f] + + share -a + share + + unshare [-f] -a + unshare [-f] + + send [-i ] + receive [-vnF] + receive [-vnF] -d + +Each dataset is of the form: pool/[dataset/]*dataset[@name] + +For the property list, run: zfs set|get diff --git a/spec/unit/zfs_version_spec.rb b/spec/unit/zfs_version_spec.rb index 349491c3df..30b8237c6f 100644 --- a/spec/unit/zfs_version_spec.rb +++ b/spec/unit/zfs_version_spec.rb @@ -16,32 +16,43 @@ Facter::Util::Resolution.stubs(:which).with("zfs").returns("/usr/bin/zfs") end + it "should return nil on old versions of Solaris 10" do + Facter::Util::Resolution.stubs(:exec).with("zfs -?").returns(my_fixture_read('zfs_old')) + Facter.fact(:zfs_version).value.should == nil + end + it "should return correct version on Solaris 10" do + Facter::Util::Resolution.stubs(:exec).with("zfs -?").returns(my_fixture_read('zfs_new')) Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(my_fixture_read('solaris_10')) Facter.fact(:zfs_version).value.should == "3" end it "should return correct version on Solaris 11" do + Facter::Util::Resolution.stubs(:exec).with("zfs -?").returns(my_fixture_read('zfs_new')) Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(my_fixture_read('solaris_11')) Facter.fact(:zfs_version).value.should == "5" end it "should return correct version on FreeBSD 8.2" do + Facter::Util::Resolution.stubs(:exec).with("zfs -?").returns(my_fixture_read('zfs_new')) Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(my_fixture_read('freebsd_8.2')) Facter.fact(:zfs_version).value.should == "4" end it "should return correct version on FreeBSD 9.0" do + Facter::Util::Resolution.stubs(:exec).with("zfs -?").returns(my_fixture_read('zfs_new')) Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(my_fixture_read('freebsd_9.0')) Facter.fact(:zfs_version).value.should == "5" end it "should return correct version on Linux with ZFS-fuse" do + Facter::Util::Resolution.stubs(:exec).with("zfs -?").returns(my_fixture_read('zfs_new')) Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(my_fixture_read('linux-fuse_0.6.9')) Facter.fact(:zfs_version).value.should == "4" end it "should return correct version on Linux with zfsonlinux" do + Facter::Util::Resolution.stubs(:exec).with("zfs -?").returns(my_fixture_read('zfs_new')) Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(my_fixture_read('zfsonlinux_0.6.1')) Facter.fact(:zfs_version).value.should == "5" end @@ -53,7 +64,7 @@ end it "should return nil if zfs fails to run" do - Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(nil) + Facter::Util::Resolution.stubs(:exec).with("zfs -?").returns(nil) Facter.fact(:zfs_version).value.should == nil end @@ -64,6 +75,9 @@ Facter::Util::Resolution.stubs(:which). with("zfs"). returns(nil,nil,nil,nil,"/usr/bin/zfs") + Facter::Util::Resolution.stubs(:exec). + with("zfs -?"). + returns(my_fixture_read('zfs_new')) Facter::Util::Resolution.stubs(:exec). with("zfs upgrade -v"). returns(my_fixture_read('linux-fuse_0.6.9')) From efa2f0a03c2a5bbb578cba546cb7893a07042917 Mon Sep 17 00:00:00 2001 From: Michael Stahnke Date: Fri, 5 Jul 2013 11:57:26 -0700 Subject: [PATCH 1314/3753] Don't build debuginfo rpm package Prior to this commit, mock would spit out a debuginfo package on facter. This is pointless as facter contains zero compiled code. It would be a noarch package, save for some arch-specific dependencies. So, since I can't fix that bug in RPM, I can make it so I don't have to remove the debuginfo package every single time. Signed-off-by: Michael Stahnke --- ext/redhat/facter.spec.erb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ext/redhat/facter.spec.erb b/ext/redhat/facter.spec.erb index 1a04b1d937..3c573ed110 100644 --- a/ext/redhat/facter.spec.erb +++ b/ext/redhat/facter.spec.erb @@ -6,6 +6,10 @@ %global facter_libdir %(ruby -rrbconfig -e 'puts RbConfig::CONFIG["sitelibdir"]') %endif + +# Building debuginfo is pointless, as this has no symbols. +%global debug_package %{nil} + # VERSION is subbed out during rake srpm process %global realversion <%= @version %> %global rpmversion <%= @rpmversion %> @@ -73,6 +77,9 @@ rm -rf %{buildroot} * <%= Time.now.strftime("%a %b %d %Y") %> Puppet Labs Release - 1:<%= @rpmversion %>-<%= @rpmrelease %> - Build for <%= @version %> +* Fri Jul 05 2013 Michael Stahnke - 1:1.7.2-0.1rc1 +- Do not build debuginfo any more + * Mon Apr 01 2013 Matthaus Owens - 1:1.7.0-0.1rc1 - Add dependency on virt-what to facter for better virutalization detection From 663c3d3b55cc1317d29845975ba117a346702788 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Wed, 3 Jul 2013 13:46:25 -0500 Subject: [PATCH 1315/3753] Adding a windows fact for i686 to architecture specs --- spec/unit/architecture_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/unit/architecture_spec.rb b/spec/unit/architecture_spec.rb index 7e69198aef..1c45e04a53 100755 --- a/spec/unit/architecture_spec.rb +++ b/spec/unit/architecture_spec.rb @@ -22,6 +22,7 @@ ["Gentoo","i686"] => "x86", ["Gentoo","pentium"] => "x86", ["windows","i386"] => "x86", + ["windows","i686"] => "x86", ["windows","x64"] => "x64", } generic_archs = Hash.new From 0cb1ff1e49522ed6d4a7d6ca564e4a46cd78b1a8 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Wed, 3 Jul 2013 13:56:49 -0500 Subject: [PATCH 1316/3753] (#20989) x86 windows hardware model show i686 not i1586 Currently hardware model uses the cpu level when using 32 bit windows on x64 capable hardware. The actual architecture is i686 even though it might be showing as i1586 if the cpu level is 15. Based on my research on this, all levels above 6 should show the i686 hardware model. i1586 or i1186 would be a misnomer. This fix adds a check to determine if the cpu level is over 6 and then responds by setting the architecture_level (new) to 6, otherwise it uses the cpu level coming back from the windows processor query. Note this only affects windows hardware. --- lib/facter/hardwaremodel.rb | 9 +++++++-- spec/unit/hardwaremodel_spec.rb | 17 ++++++++++++++++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/lib/facter/hardwaremodel.rb b/lib/facter/hardwaremodel.rb index adfda481b8..48a1b934e7 100644 --- a/lib/facter/hardwaremodel.rb +++ b/lib/facter/hardwaremodel.rb @@ -32,17 +32,22 @@ # http://source.winehq.org/source/include/winnt.h#L568 # http://msdn.microsoft.com/en-us/library/windows/desktop/aa394373(v=vs.85).aspx # http://msdn.microsoft.com/en-us/library/windows/desktop/windows.system.processorarchitecture.aspx + # http://linux.derkeiler.com/Mailing-Lists/Kernel/2008-05/msg12924.html (anything over 6 is still i686) # Also, arm and neutral are included because they are valid for the upcoming # windows 8 release. --jeffweiss 23 May 2012 require 'facter/util/wmi' model = "" + architecture_level = nil + Facter::Util::WMI.execquery("select Architecture, Level, AddressWidth from Win32_Processor").each do |cpu| + architecture_level = (cpu.Level > 5) ? 6 : cpu.Level; + model = case cpu.Architecture when 11 then 'neutral' # PROCESSOR_ARCHITECTURE_NEUTRAL when 10 then 'i686' # PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 when 9 then # PROCESSOR_ARCHITECTURE_AMD64 - cpu.AddressWidth == 32 ? "i#{cpu.Level}86" : 'x64' # 32 bit OS on 64 bit CPU + cpu.AddressWidth == 32 ? "i#{architecture_level}86" : 'x64' # 32 bit OS on 64 bit CPU when 8 then 'msil' # PROCESSOR_ARCHITECTURE_MSIL when 7 then 'alpha64' # PROCESSOR_ARCHITECTURE_ALPHA64 when 6 then 'ia64' # PROCESSOR_ARCHITECTURE_IA64 @@ -51,7 +56,7 @@ when 3 then 'powerpc' # PROCESSOR_ARCHITECTURE_PPC when 2 then 'alpha' # PROCESSOR_ARCHITECTURE_ALPHA when 1 then 'mips' # PROCESSOR_ARCHITECTURE_MIPS - when 0 then "i#{cpu.Level}86" # PROCESSOR_ARCHITECTURE_INTEL + when 0 then "i#{architecture_level}86" # PROCESSOR_ARCHITECTURE_INTEL else 'unknown' # PROCESSOR_ARCHITECTURE_UNKNOWN end break diff --git a/spec/unit/hardwaremodel_spec.rb b/spec/unit/hardwaremodel_spec.rb index a168c11ef5..29c893db5b 100644 --- a/spec/unit/hardwaremodel_spec.rb +++ b/spec/unit/hardwaremodel_spec.rb @@ -17,6 +17,14 @@ Facter.fact(:kernel).stubs(:value).returns("windows") end + it "should detect i486" do + cpu = mock('cpu', :Architecture => 0) + cpu.expects(:Level).returns(4).twice + Facter::Util::WMI.expects(:execquery).returns([cpu]) + + Facter.fact(:hardwaremodel).value.should == "i486" + end + it "should detect i686" do cpu = mock('cpu', :Architecture => 0, :Level => 6) Facter::Util::WMI.expects(:execquery).returns([cpu]) @@ -25,7 +33,7 @@ end it "should detect x64" do - cpu = mock('cpu', :Architecture => 9, :AddressWidth => 64) + cpu = mock('cpu', :Architecture => 9, :AddressWidth => 64, :Level => 6) Facter::Util::WMI.expects(:execquery).returns([cpu]) Facter.fact(:hardwaremodel).value.should == "x64" @@ -37,5 +45,12 @@ Facter.fact(:hardwaremodel).value.should == "i686" end + + it "(#20989) should report i686 when a 32 bit OS is running on a 64 bit CPU and when level is greater than 6 (and not something like i1586)" do + cpu = mock('cpu', :Architecture => 9, :AddressWidth => 32, :Level => 15) + Facter::Util::WMI.expects(:execquery).returns([cpu]) + + Facter.fact(:hardwaremodel).value.should == "i686" + end end end From 2027595b79c97e218c1d9266a7311dba07a55fa2 Mon Sep 17 00:00:00 2001 From: Matthaus Owens Date: Mon, 8 Jul 2013 17:17:37 -0700 Subject: [PATCH 1317/3753] (packaging) Update FACTERVERSION to 1.7.2 --- lib/facter/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/version.rb b/lib/facter/version.rb index 933350e469..c793e3f8b6 100644 --- a/lib/facter/version.rb +++ b/lib/facter/version.rb @@ -1,6 +1,6 @@ module Facter if not defined? FACTERVERSION then - FACTERVERSION = '1.7.2-rc1' + FACTERVERSION = '1.7.2' end ## From 603127523d1ff740f59ca966796813697dc19917 Mon Sep 17 00:00:00 2001 From: Josh Partlow Date: Mon, 8 Jul 2013 16:13:37 -0700 Subject: [PATCH 1318/3753] (maint) Fix mocha expectations crashing facter_spec Running `bundle exec rspec spec/unit/util/virtual_spec.rb spec/unit/facter_spec.rb` prior to this patch would crash rspec outright while the test harness was still loading. This was because virtual_spec.rb had expectations set in a shared_examples block but outside of a before or it block. These then caused an expectation failure when explicit calls to Facter[:foo] were used as the description in two describe calls in facter_spec. This patch clears up both those misuses of rspec, and also adds the log_spec_order method from Puppet for future bisecting of spec order issues in Facter...which hopefully we won't have to do. :) --- spec/spec_helper.rb | 21 +++++++++++++++++++++ spec/unit/facter_spec.rb | 4 ++-- spec/unit/util/virtual_spec.rb | 13 +++++++++---- 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 97fa072fba..b0d8566a49 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -17,6 +17,27 @@ require file.relative_path_from(Pathname.new(dir)) end +module LogSpecOrder + # Log the spec order to a file, but only if the LOG_SPEC_ORDER environment + # variable is set. This could be enabled on Jenkins runs, as it can + # be used with Nick L.'s bisect script to help identify and debug + # order-dependent spec failures. + # + # jpartlow 2013-07-05: this was in puppet and I pulled it into facter because + # I was seeing similar ordering issues in the specs...and needed to bisect them :/ + def self.log_spec_order + if ENV['LOG_SPEC_ORDER'] + File.open("./spec_order.txt", "w") do |logfile| + RSpec.configuration.files_to_run.each { |f| logfile.puts f } + end + end + end +end + +# Executing here rather than after :suite, so that we can get the order output +# even when the issue breaks rspec when specs are first loaded. +LogSpecOrder.log_spec_order + RSpec.configure do |config| config.mock_with :mocha diff --git a/spec/unit/facter_spec.rb b/spec/unit/facter_spec.rb index e86bfa4012..3f9d086c1a 100755 --- a/spec/unit/facter_spec.rb +++ b/spec/unit/facter_spec.rb @@ -102,13 +102,13 @@ end end - describe Facter[:hostname] do + describe "Facter[:hostname]" do it "should have its ldapname set to 'cn'" do Facter[:hostname].ldapname.should == "cn" end end - describe Facter[:ipaddress] do + describe "Facter[:ipaddress]" do it "should have its ldapname set to 'iphostnumber'" do Facter[:ipaddress].ldapname.should == "iphostnumber" end diff --git a/spec/unit/util/virtual_spec.rb b/spec/unit/util/virtual_spec.rb index 2331142db3..f4dca6f212 100755 --- a/spec/unit/util/virtual_spec.rb +++ b/spec/unit/util/virtual_spec.rb @@ -261,10 +261,15 @@ end shared_examples_for "virt-what" do |kernel, path, null_device| - Facter.fact(:kernel).stubs(:value).returns(kernel) - Facter::Util::Resolution.expects(:which).with("virt-what").returns(path) - Facter::Util::Resolution.expects(:exec).with("#{path} 2>#{null_device}") - Facter::Util::Virtual.virt_what + before(:each) do + Facter.fact(:kernel).stubs(:value).returns(kernel) + Facter::Util::Resolution.expects(:which).with("virt-what").returns(path) + Facter::Util::Resolution.expects(:exec).with("#{path} 2>#{null_device}") + end + + it "on #{kernel} virt-what is at #{path} and stderr is sent to #{null_device}" do + Facter::Util::Virtual.virt_what + end end context "on linux" do From ad902bdaf5efa47e729038b3fda93c9fc9335860 Mon Sep 17 00:00:00 2001 From: Josh Partlow Date: Mon, 8 Jul 2013 16:39:13 -0700 Subject: [PATCH 1319/3753] (maint) Rakefile loads all the libraries it can. The call to load ext/packaging/packaging.rake will fail unless the packaging system has been bootstrapped. Previously this failure would prevent attempts to load other libraries (notably rspec), and a `be rake spec` would return silently without executing any specs. Requires each lib separately so that failure to load one does not prevent others. --- Rakefile | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Rakefile b/Rakefile index 792af41f95..acc0dfa09e 100644 --- a/Rakefile +++ b/Rakefile @@ -10,13 +10,19 @@ require 'rake' begin load File.join(File.dirname(__FILE__), 'ext', 'packaging', 'packaging.rake') - require 'rubygems' - require 'rspec' - require 'rspec/core/rake_task' - require 'rcov' rescue LoadError end +['rubygems', +'rspec', +'rspec/core/rake_task', +'rcov',].each do |lib| + begin + require lib + rescue LoadError + end +end + Dir['tasks/**/*.rake'].each { |t| load t } build_defs_file = 'ext/build_defaults.yaml' From 65577a4c81803415ba0df6a404ddca66f7c04b05 Mon Sep 17 00:00:00 2001 From: Josh Partlow Date: Mon, 8 Jul 2013 16:43:41 -0700 Subject: [PATCH 1320/3753] (maint) Update README with a little bit of spec info. --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index bbaf029038..5bed36e772 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,14 @@ Adding your own facts See the [Adding Facts](http://docs.puppetlabs.com/guides/custom_facts.html) page for details of how to add your own custom facts to Facter. +Running Specs +------------- + +* bundle install --path .bundle/gems +* bundle exec rake spec + +Note: external facts in the system facts.d directory can cause spec failures. + Further Information ------------------- From d49f646fe621d72d9066e3402be5e6ba7da5d5eb Mon Sep 17 00:00:00 2001 From: Elias Probst Date: Sat, 29 Jun 2013 01:19:19 +0200 Subject: [PATCH 1321/3753] (#21533) Convert network data to string before access --- lib/facter/ipaddress6.rb | 2 +- lib/facter/macaddress.rb | 2 +- lib/facter/util/netmask.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/facter/ipaddress6.rb b/lib/facter/ipaddress6.rb index b05ab9a0e3..4e44d13e65 100644 --- a/lib/facter/ipaddress6.rb +++ b/lib/facter/ipaddress6.rb @@ -26,7 +26,7 @@ def get_address_after_token(output, token, return_first=false) ip = nil - output.scan(/#{token} ((?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/).each do |match| + String(output).scan(/#{token} ((?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/).each do |match| match = match.first unless match =~ /fe80.*/ or match == "::1" ip = match diff --git a/lib/facter/macaddress.rb b/lib/facter/macaddress.rb index 6c0ef78026..3bcc92751a 100644 --- a/lib/facter/macaddress.rb +++ b/lib/facter/macaddress.rb @@ -29,7 +29,7 @@ ether = [] output = Facter::Util::IP.exec_ifconfig(["-a","2>/dev/null"]) - output.each_line do |s| + String(output).each_line do |s| ether.push($1) if s =~ /(?:ether|HWaddr) ((\w{1,2}:){5,}\w{1,2})/ end Facter::Util::Macaddress.standardize(ether[0]) diff --git a/lib/facter/util/netmask.rb b/lib/facter/util/netmask.rb index 7f1d2794f6..41503c87fb 100644 --- a/lib/facter/util/netmask.rb +++ b/lib/facter/util/netmask.rb @@ -25,7 +25,7 @@ def self.get_netmask } end - Facter::Util::IP.exec_ifconfig(ops[:ifconfig_opts]).split(/\n/).collect do |line| + String(Facter::Util::IP.exec_ifconfig(ops[:ifconfig_opts])).split(/\n/).collect do |line| matches = line.match(ops[:regex]) if !matches.nil? if ops[:munge].nil? From 9f5bec1a8160ccf00758e6f16362573d5b8dc07b Mon Sep 17 00:00:00 2001 From: Matthaus Owens Date: Thu, 11 Jul 2013 11:28:15 -0700 Subject: [PATCH 1322/3753] (maint) Clean up install.rb dependencies The openssl, xmlrpc/client, and xmlrpc/server libraries are not used anywhere in facter, so checking for them in facter serves no purpose. This commit removes them from the list of PREREQS for facter. --- install.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install.rb b/install.rb index ba4ffc0398..0ce2f9e323 100755 --- a/install.rb +++ b/install.rb @@ -60,7 +60,7 @@ require 'facter' @operatingsystem = Facter[:operatingsystem].value -PREREQS = %w{openssl xmlrpc/client xmlrpc/server cgi} +PREREQS = %w{cgi} InstallOptions = OpenStruct.new From 9d04cf54c8c75ab7c1bbc2a7c21c80dc734676b7 Mon Sep 17 00:00:00 2001 From: Matthaus Owens Date: Thu, 11 Jul 2013 11:32:32 -0700 Subject: [PATCH 1323/3753] (maint) Remove openssl dependencies from debian/control file As openssl is not required or used by facter, this commit removes the dependency from the facter debian package. --- ext/debian/changelog.erb | 6 ++++++ ext/debian/control | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/ext/debian/changelog.erb b/ext/debian/changelog.erb index 1ef5c6c034..8e902d2f7d 100644 --- a/ext/debian/changelog.erb +++ b/ext/debian/changelog.erb @@ -4,6 +4,12 @@ facter (<%= @debversion %>) hardy lucid oneiric unstable sid wheezy lucid squeez -- Puppet Labs Release <%= Time.now.strftime("%a, %d %b %Y %H:%M:%S %z")%> +facter (1.7.2-1puppetlabs2) lucid unstable sid wheezy lucid squeeze precise; urgency=low + + * Remove dependenices on libssl-ruby from facter as they are not used + + -- Matthaus Owens Thu, 11 Jul 2013 13:11:30 +0000 + facter (1.7.0-0.1rc1puppetlabs1) hardy lucid oneiric unstable sid wheezy lucid squeeze precise; urgency=low * Add dependency on virt-what to facter for better virutalization detection diff --git a/ext/debian/control b/ext/debian/control index c5f8a9d11f..468349f890 100644 --- a/ext/debian/control +++ b/ext/debian/control @@ -2,13 +2,13 @@ Source: facter Section: ruby Priority: optional Maintainer: Puppet Labs -Build-Depends: cdbs, debhelper (>> 7), ruby | ruby-interpreter, libopenssl-ruby | libopenssl-ruby1.8 | libopenssl-ruby1.9.1, rdoc +Build-Depends: cdbs, debhelper (>> 7), ruby | ruby-interpreter Standards-Version: 3.9.1 Homepage: http://www.puppetlabs.com Package: facter Architecture: any -Depends: ${shlibs:Depends}, ${misc:Depends}, ruby | ruby-interpreter, libopenssl-ruby | libopenssl-ruby1.8 | libopenssl-ruby1.9.1, dmidecode [i386 amd64 ia64], virt-what, pciutils +Depends: ${shlibs:Depends}, ${misc:Depends}, ruby | ruby-interpreter, dmidecode [i386 amd64 ia64], virt-what, pciutils Description: Ruby module for collecting simple facts about a host operating system Some of the facts are preconfigured, such as the hostname and the operating system. Additional facts can be added through simple Ruby scripts. From 3979271f32b3e33ac8a897d7a9b639eb206b4420 Mon Sep 17 00:00:00 2001 From: Matthaus Owens Date: Thu, 11 Jul 2013 11:33:59 -0700 Subject: [PATCH 1324/3753] (packaging) Update changelog template with currently supported debian/ubuntu dists. --- ext/debian/changelog.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/debian/changelog.erb b/ext/debian/changelog.erb index 8e902d2f7d..02497fd3bc 100644 --- a/ext/debian/changelog.erb +++ b/ext/debian/changelog.erb @@ -1,4 +1,4 @@ -facter (<%= @debversion %>) hardy lucid oneiric unstable sid wheezy lucid squeeze precise quantal; urgency=low +facter (<%= @debversion %>) lucid unstable sid wheezy lucid squeeze precise quantal raring; urgency=low * Update to version <% @debversion %> From a0e90de13f8ce6d6b8aee27a5b6f97a48eb8abad Mon Sep 17 00:00:00 2001 From: Scott Schneider Date: Fri, 12 Jul 2013 10:03:39 -0700 Subject: [PATCH 1325/3753] Adding vCloud node configs --- acceptance/config/centos-5.cfg | 21 +++++++++++++++++++++ acceptance/config/fedora-18.cfg | 21 +++++++++++++++++++++ acceptance/config/redhat-6.cfg | 21 +++++++++++++++++++++ acceptance/config/ubuntu-1004.cfg | 21 +++++++++++++++++++++ 4 files changed, 84 insertions(+) create mode 100644 acceptance/config/centos-5.cfg create mode 100644 acceptance/config/fedora-18.cfg create mode 100644 acceptance/config/redhat-6.cfg create mode 100644 acceptance/config/ubuntu-1004.cfg diff --git a/acceptance/config/centos-5.cfg b/acceptance/config/centos-5.cfg new file mode 100644 index 0000000000..0ec1d7c299 --- /dev/null +++ b/acceptance/config/centos-5.cfg @@ -0,0 +1,21 @@ +HOSTS: + centos-55-64-1: + roles: + - master + - database + - agent + platform: el-5-x86_64 + template: centos-5-x86_64 + hypervisor: vcloud + centos-55-386-1: + roles: + - agent + platform: el-5-i386 + template: centos-5-i386 + hypervisor: vcloud +CONFIG: + nfs_server: none + consoleport: 443 + datastore: instance0 + folder: Delivery/Quality Assurance/FOSS/Dynamic + resourcepool: delivery/Quality Assurance/FOSS/Dynamic diff --git a/acceptance/config/fedora-18.cfg b/acceptance/config/fedora-18.cfg new file mode 100644 index 0000000000..bfa0ee8ae9 --- /dev/null +++ b/acceptance/config/fedora-18.cfg @@ -0,0 +1,21 @@ +HOSTS: + fedora-18-64-1: + roles: + - master + - database + - agent + platform: fc-18-x86_64 + template: fedora-18-x86_64 + hypervisor: vcloud + fedora-18-64-2: + roles: + - agent + platform: fc-18-x86_64 + template: fedora-18-x86_64 + hypervisor: vcloud +CONFIG: + nfs_server: none + consoleport: 443 + datastore: instance0 + folder: Delivery/Quality Assurance/FOSS/Dynamic + resourcepool: delivery/Quality Assurance/FOSS/Dynamic diff --git a/acceptance/config/redhat-6.cfg b/acceptance/config/redhat-6.cfg new file mode 100644 index 0000000000..0f8479fac5 --- /dev/null +++ b/acceptance/config/redhat-6.cfg @@ -0,0 +1,21 @@ +HOSTS: + rhel-6-latest-64-1: + roles: + - master + - database + - agent + platform: el-6-x86_64 + template: redhat-6-x86_64 + hypervisor: vcloud + rhel-6-latest-32-1: + roles: + - agent + platform: el-6-i386 + template: redhat-6-i386 + hypervisor: vcloud +CONFIG: + nfs_server: none + consoleport: 443 + datastore: instance0 + folder: Delivery/Quality Assurance/FOSS/Dynamic + resourcepool: delivery/Quality Assurance/FOSS/Dynamic diff --git a/acceptance/config/ubuntu-1004.cfg b/acceptance/config/ubuntu-1004.cfg new file mode 100644 index 0000000000..f3b7e91988 --- /dev/null +++ b/acceptance/config/ubuntu-1004.cfg @@ -0,0 +1,21 @@ +HOSTS: + ubuntu-1004-64-1: + roles: + - master + - database + - agent + platform: ubuntu-10.04-amd64 + template: ubuntu-1004-x86_64 + hypervisor: vcloud + ubuntu-1004-32-1: + roles: + - agent + platform: ubuntu-10.04-i386 + template: ubuntu-1004-i386 + hypervisor: vcloud +CONFIG: + nfs_server: none + consoleport: 443 + datastore: instance0 + folder: Delivery/Quality Assurance/FOSS/Dynamic + resourcepool: delivery/Quality Assurance/FOSS/Dynamic From d40949f7a4f1ca3c677710df51f30d5b77ad41a8 Mon Sep 17 00:00:00 2001 From: Scott Schneider Date: Fri, 12 Jul 2013 10:03:45 -0700 Subject: [PATCH 1326/3753] Setup script to install Git on vCloud hosts --- acceptance/setup/00_install_git.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 acceptance/setup/00_install_git.rb diff --git a/acceptance/setup/00_install_git.rb b/acceptance/setup/00_install_git.rb new file mode 100644 index 0000000000..21a3f14b5e --- /dev/null +++ b/acceptance/setup/00_install_git.rb @@ -0,0 +1,13 @@ +test_name "Install Git" + +hosts.each do |host| + case host['platform'] + when /el-|fc-/ + step 'Installing Git' + on host, 'yum -y install git' + when /debian-|ubuntu-/ + step 'Installing Git' + on host, 'apt-get -y install git-core' + else + end +end From ea34ce38c3b215e61bf7695079dc3c1bbd2d867c Mon Sep 17 00:00:00 2001 From: Franz Pletz Date: Sat, 13 Jul 2013 17:13:28 +0200 Subject: [PATCH 1327/3753] (#20216) Print timings to stderr instead of stdout Facter prints both timings and facts to stdout if timing is enabled. To facilitate separating both outputs the timings should be printed to stderr instead. --- lib/facter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter.rb b/lib/facter.rb index eff1665b57..244664e462 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -106,7 +106,7 @@ def self.debugging? # # @api private def self.show_time(string) - puts "#{GREEN}#{string}#{RESET}" if string and Facter.timing? + $stderr.puts "#{GREEN}#{string}#{RESET}" if string and Facter.timing? end # Returns whether timing output is turned on From ce47f78166c43843a8d70a4b790fb45264941537 Mon Sep 17 00:00:00 2001 From: Arnoud de Jonge Date: Sat, 13 Jul 2013 17:26:13 +0200 Subject: [PATCH 1328/3753] Facter::Util::Resolution.exec can return nil, which caused facter to crash. Fixed. --- lib/facter/util/parser.rb | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/facter/util/parser.rb b/lib/facter/util/parser.rb index 8f2db609cc..7f93d68152 100644 --- a/lib/facter/util/parser.rb +++ b/lib/facter/util/parser.rb @@ -111,13 +111,14 @@ def results class ScriptParser < Base def results - output = Facter::Util::Resolution.exec(filename) - result = {} - re = /^(.+)=(.+)$/ - output.each_line do |line| - if match_data = re.match(line.chomp) - result[match_data[1]] = match_data[2] + + if output = Facter::Util::Resolution.exec(filename) + re = /^(.+)=(.+)$/ + output.each_line do |line| + if match_data = re.match(line.chomp) + result[match_data[1]] = match_data[2] + end end end result From d82f695a0f2b625a7719e4a07064a925dc11ed07 Mon Sep 17 00:00:00 2001 From: Alexandre De Dommelin Date: Sat, 13 Jul 2013 21:16:56 +0200 Subject: [PATCH 1329/3753] Fix Bug #18141 : prtdiag timeout too low for large Solaris systems --- lib/facter/virtual.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 7d2487f49d..1c18bd3616 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -59,7 +59,7 @@ next "zone" if Facter::Util::Virtual.zone? resolver = Facter::Util::Resolution.new('prtdiag') - resolver.timeout = 6 + resolver.timeout = 12 resolver.setcode('prtdiag') output = resolver.value Facter::Util::Virtual.parse_virtualization(output) From 53eeb2ae2a9ac132df81eb259373738c11bce29d Mon Sep 17 00:00:00 2001 From: Matthaus Owens Date: Mon, 15 Jul 2013 09:47:34 -0700 Subject: [PATCH 1330/3753] (packaging) Remove createpackage.sh from facter This shell script has been replaced and superceded by the packaging rake tasks. Running `rake package:bootstrap` followed by `rake package:apple` will build a dmg locally in a similar manner to this script. --- ext/osx/createpackage.sh | 179 --------------------------------------- 1 file changed, 179 deletions(-) delete mode 100755 ext/osx/createpackage.sh diff --git a/ext/osx/createpackage.sh b/ext/osx/createpackage.sh deleted file mode 100755 index 7b473ad762..0000000000 --- a/ext/osx/createpackage.sh +++ /dev/null @@ -1,179 +0,0 @@ -#!/bin/bash -# -# Script to build an "old style" not flat pkg out of the facter repository. -# -# Author: Nigel Kersten (nigelk@google.com) -# -# Last Updated: 2008-07-31 -# -# Copyright 2008 Google Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License - - -INSTALLRB="install.rb" -BINDIR="/usr/bin" -SITELIBDIR="/usr/lib/ruby/site_ruby/1.8" -PACKAGEMAKER="/Developer/usr/bin/packagemaker" -PROTO_PLIST="PackageInfo.plist" -PREFLIGHT="preflight" - - -function find_installer() { - # we walk up three directories to make this executable from the root, - # root/conf or root/conf/osx - if [ -f "./${INSTALLRB}" ]; then - installer="$(pwd)/${INSTALLRB}" - elif [ -f "../${INSTALLRB}" ]; then - installer="$(pwd)/../${INSTALLRB}" - elif [ -f "../../${INSTALLRB}" ]; then - installer="$(pwd)/../../${INSTALLRB}" - else - installer="" - fi -} - -function find_facter_root() { - facter_root=$(dirname "${installer}") -} - -function install_facter() { - echo "Installing Facter to ${pkgroot}" - cd "$facter_root" - ./"${INSTALLRB}" --destdir="${pkgroot}" --bindir="${BINDIR}" --sitelibdir="${SITELIBDIR}" - chown -R root:admin "${pkgroot}" -} - -function install_docs() { - echo "Installing docs to ${pkgroot}" - docdir="${pkgroot}/usr/share/doc/facter" - mkdir -p "${docdir}" - for docfile in ChangeLog COPYING LICENSE README README.rst TODO; do - install -m 0644 "${facter_root}/${docfile}" "${docdir}" - done - chown -R root:wheel "${docdir}" - chmod 0755 "${docdir}" -} - -function get_facter_version() { - facter_version=$(RUBYLIB="${pkgroot}/${SITELIBDIR}:${RUBYLIB}" ruby -e "require 'facter'; puts Facter.version") -} - -function prepare_package() { - # As we can't specify to follow symlinks from the command line, we have - # to go through the hassle of creating an Info.plist file for packagemaker - # to look at for package creation and substitue the version strings out. - # Major/Minor versions can only be integers, so we have "1" and "50" for - # facter version 1.5 - # Note too that for 10.5 compatibility this Info.plist *must* be set to - # follow symlinks. - VER1=$(echo ${facter_version} | awk -F "." '{print $1}') - VER2=$(echo ${facter_version} | awk -F "." '{print $2}') - VER3=$(echo ${facter_version} | awk -F "." '{print $3}') - major_version="${VER1}" - minor_version="${VER2}${VER3}" - cp "${facter_root}/conf/osx/${PROTO_PLIST}" "${pkgtemp}" - sed -i '' "s/{SHORTVERSION}/${facter_version}/g" "${pkgtemp}/${PROTO_PLIST}" - sed -i '' "s/{MAJORVERSION}/${major_version}/g" "${pkgtemp}/${PROTO_PLIST}" - sed -i '' "s/{MINORVERSION}/${minor_version}/g" "${pkgtemp}/${PROTO_PLIST}" - - # We need to create a preflight script to remove traces of previous - # facter installs due to limitations in Apple's pkg format. - mkdir "${pkgtemp}/scripts" - cp "${facter_root}/conf/osx/${PREFLIGHT}" "${pkgtemp}/scripts" - - # substitute in the sitelibdir specified above on the assumption that this - # is where any previous facter install exists that should be cleaned out. - sed -i '' "s|{SITELIBDIR}|${SITELIBDIR}|g" "${pkgtemp}/scripts/${PREFLIGHT}" - chmod 0755 "${pkgtemp}/scripts/${PREFLIGHT}" -} - -function create_package() { - rm -fr "$(pwd)/facter-${facter_version}.pkg" - echo "Building package" - echo "Note that packagemaker is reknowned for spurious errors. Don't panic." - "${PACKAGEMAKER}" --root "${pkgroot}" \ - --info "${pkgtemp}/${PROTO_PLIST}" \ - --scripts ${pkgtemp}/scripts \ - --out "$(pwd)/facter-${facter_version}.pkg" - if [ $? -ne 0 ]; then - echo "There was a problem building the package." - cleanup_and_exit 1 - exit 1 - else - echo "The package has been built at:" - echo "$(pwd)/facter-${facter_version}.pkg" - fi -} - -function cleanup_and_exit() { - if [ -d "${pkgroot}" ]; then - rm -fr "${pkgroot}" - fi - if [ -d "${pkgtemp}" ]; then - rm -fr "${pkgtemp}" - fi - exit $1 -} - -# Program entry point -function main() { - - if [ $(whoami) != "root" ]; then - echo "This script needs to be run as root via su or sudo." - cleanup_and_exit 1 - fi - - find_installer - - if [ ! "${installer}" ]; then - echo "Unable to find ${INSTALLRB}" - cleanup_and_exit 1 - fi - - find_facter_root - - if [ ! "${facter_root}" ]; then - echo "Unable to find facter repository root." - cleanup_and_exit 1 - fi - - pkgroot=$(mktemp -d -t facterpkg) - - if [ ! "${pkgroot}" ]; then - echo "Unable to create temporary package root." - cleanup_and_exit 1 - fi - - pkgtemp=$(mktemp -d -t factertmp) - - if [ ! "${pkgtemp}" ]; then - echo "Unable to create temporary package root." - cleanup_and_exit 1 - fi - - install_facter - get_facter_version - - if [ ! "${facter_version}" ]; then - echo "Unable to retrieve facter version" - cleanup_and_exit 1 - fi - - prepare_package - create_package - - cleanup_and_exit 0 -} - -main "$@" From 11ee819d2126f5a19d31e293c89c805c8e3cc850 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Mon, 15 Jul 2013 12:26:41 -0500 Subject: [PATCH 1331/3753] (Maint) Ignore Gemfile.local This ignores the Gemfile.local so that specific gems like fuubar can be applied. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 6b4c36bb9d..3c099d796a 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ doc .bundle/ vendor/ Gemfile.lock +Gemfile.local \ No newline at end of file From a1842d0c8ca82d7c87e6e5d86e6d806784d103f1 Mon Sep 17 00:00:00 2001 From: Matthaus Owens Date: Tue, 16 Jul 2013 09:45:41 -0700 Subject: [PATCH 1332/3753] (packaging) Remove PackageInfo.plist, it is no longer used by packaging. --- ext/osx/PackageInfo.plist | 36 ------------------------------------ 1 file changed, 36 deletions(-) delete mode 100644 ext/osx/PackageInfo.plist diff --git a/ext/osx/PackageInfo.plist b/ext/osx/PackageInfo.plist deleted file mode 100644 index 6668e2efaf..0000000000 --- a/ext/osx/PackageInfo.plist +++ /dev/null @@ -1,36 +0,0 @@ - - - - - CFBundleIdentifier - com.reductivelabs.facter - CFBundleShortVersionString - {SHORTVERSION} - IFMajorVersion - {MAJORVERSION} - IFMinorVersion - {MINORVERSION} - IFPkgFlagAllowBackRev - - IFPkgFlagAuthorizationAction - RootAuthorization - IFPkgFlagDefaultLocation - / - IFPkgFlagFollowLinks - - IFPkgFlagInstallFat - - IFPkgFlagIsRequired - - IFPkgFlagOverwritePermissions - - IFPkgFlagRelocatable - - IFPkgFlagRestartAction - None - IFPkgFlagRootVolumeOnly - - IFPkgFlagUpdateInstalledLanguages - - - From 18522b4d923134a2b75a4c2ae98a8450f9b5f984 Mon Sep 17 00:00:00 2001 From: Melissa Date: Fri, 12 Jul 2013 15:57:20 -0700 Subject: [PATCH 1333/3753] (Bug #21762) Update facter to support F19 This commit modifies the bild_defaults file to include fedora 19 in the final mocks list. It also removes the ruby(abi) requirement, as F19 no longer provides it. --- ext/build_defaults.yaml | 2 +- ext/redhat/facter.spec.erb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/build_defaults.yaml b/ext/build_defaults.yaml index 74c88424ea..fb68296dd7 100644 --- a/ext/build_defaults.yaml +++ b/ext/build_defaults.yaml @@ -9,7 +9,7 @@ gpg_name: 'info@puppetlabs.com' gpg_key: '4BD6EC30' sign_tar: FALSE # a space separated list of mock configs -final_mocks: 'pl-el-5-i386 pl-el-5-x86_64 pl-el-6-i386 pl-el-6-x86_64 pl-fedora-17-i386 pl-fedora-17-x86_64 pl-fedora-18-i386 pl-fedora-18-x86_64' +final_mocks: 'pl-el-5-i386 pl-el-5-x86_64 pl-el-6-i386 pl-el-6-x86_64 pl-fedora-17-i386 pl-fedora-17-x86_64 pl-fedora-18-i386 pl-fedora-18-x86_64 pl-fedora-19-i386 pl-fedora-19-x86_64' yum_host: 'burji.puppetlabs.com' yum_repo_path: '/opt/repository/yum/' build_gem: TRUE diff --git a/ext/redhat/facter.spec.erb b/ext/redhat/facter.spec.erb index 1a04b1d937..9e3321a6eb 100644 --- a/ext/redhat/facter.spec.erb +++ b/ext/redhat/facter.spec.erb @@ -32,7 +32,7 @@ Requires: dmidecode Requires: pciutils %endif Requires: virt-what -Requires: ruby(abi) >= 1.8 +Requires: ruby >= 1.8.5 BuildRequires: ruby >= 1.8.5 # In Fedora 17 ruby-rdoc is called rubygem-rdoc From 95e9fdcf7d2d618bbf53c365570be6b78f728dd2 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Wed, 17 Jul 2013 10:31:38 -0700 Subject: [PATCH 1334/3753] (refactor) Use braces for one line blocks --- lib/facter/operatingsystem.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index e3a9a40e46..197ed450b8 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -114,12 +114,10 @@ Facter.add(:operatingsystem) do confine :kernel => "VMkernel" - setcode do - "ESXi" - end + setcode { "ESXi" } end Facter.add(:operatingsystem) do # Default to just returning the kernel as the operating system - setcode do Facter[:kernel].value end + setcode { Facter[:kernel].value } end From ec2bac884e629c9889db60a074cc3f16d789213b Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Wed, 17 Jul 2013 10:29:05 -0700 Subject: [PATCH 1335/3753] (refactor) Deduplicate operatingsystem fact resolution The operatingsystem fact on Linux had all possible fact resolutions expressed in a single location. This makes testing and reasoning about the fact somewhat frail, as it implies ordering that might or might not be there. This commit extracts common behavior of detecting redhat and suse variants based on file contents, and behavior around detecting the operating system based on the presence of a file. --- lib/facter/operatingsystem.rb | 132 ++++++++++++++--------------- lib/facter/util/operatingsystem.rb | 54 ++++++++++++ spec/unit/operatingsystem_spec.rb | 4 +- 3 files changed, 118 insertions(+), 72 deletions(-) create mode 100644 lib/facter/util/operatingsystem.rb diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index 197ed450b8..092a6cacc6 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -11,6 +11,8 @@ # Caveats: # +require 'facter/util/operatingsystem' + Facter.add(:operatingsystem) do confine :kernel => :sunos setcode do @@ -31,87 +33,77 @@ end end +# Resolution for Debian variants. Facter.add(:operatingsystem) do confine :kernel => :linux + has_weight 10 setcode do - if Facter.value(:lsbdistid) == "Ubuntu" - "Ubuntu" - elsif Facter.value(:lsbdistid) == "LinuxMint" - "LinuxMint" - elsif FileTest.exists?("/etc/debian_version") - "Debian" - elsif FileTest.exists?("/etc/openwrt_release") - "OpenWrt" - elsif FileTest.exists?("/etc/gentoo-release") - "Gentoo" - elsif FileTest.exists?("/etc/fedora-release") - "Fedora" - elsif FileTest.exists?("/etc/mandriva-release") - "Mandriva" - elsif FileTest.exists?("/etc/mandrake-release") - "Mandrake" - elsif FileTest.exists?("/etc/meego-release") - "MeeGo" - elsif FileTest.exists?("/etc/arch-release") - "Archlinux" - elsif FileTest.exists?("/etc/oracle-release") - "OracleLinux" - elsif FileTest.exists?("/etc/enterprise-release") - if FileTest.exists?("/etc/ovs-release") - "OVS" + if FileTest.exists? '/etc/debian_version' + if Facter.value(:lsbdistid) == "Ubuntu" + "Ubuntu" + elsif Facter.value(:lsbdistid) == "LinuxMint" + "LinuxMint" else - "OEL" + "Debian" end - elsif FileTest.exists?("/etc/vmware-release") - "VMWareESX" - elsif FileTest.exists?("/etc/redhat-release") - txt = File.read("/etc/redhat-release") - if txt =~ /centos/i - "CentOS" - elsif txt =~ /CERN/ - "SLC" - elsif txt =~ /scientific/i - "Scientific" - elsif txt =~ /^cloudlinux/i - "CloudLinux" - elsif txt =~ /^Parallels Server Bare Metal/i - "PSBM" - elsif txt =~ /Ascendos/i - "Ascendos" - elsif txt =~ /^XenServer/i - "XenServer" - elsif txt =~ /XCP/ - "XCP" - else - "RedHat" - end - elsif FileTest.exists?("/etc/SuSE-release") - txt = File.read("/etc/SuSE-release") - if txt =~ /^SUSE LINUX Enterprise Server/i - "SLES" - elsif txt =~ /^SUSE LINUX Enterprise Desktop/i - "SLED" - elsif txt =~ /^openSUSE/i - "OpenSuSE" + end + end +end + +Facter.add(:operatingsystem) do + confine :kernel => :linux + has_weight 10 + setcode do + fpath = '/etc/redhat-release' + if FileTest.exists? fpath + candidates = Facter::Util::Operatingsystem::REDHAT_VARIANTS + variant = Facter::Util::Operatingsystem.release_by_file(fpath, candidates) + variant || 'RedHat' + end + end +end + +Facter.add(:operatingsystem) do + confine :kernel => :linux + has_weight 10 + setcode do + if FileTest.exists? '/etc/enterprise-release' + if FileTest.exists? '/etc/ovs-release' + 'OVS' else - "SuSE" + 'OEL' end - elsif FileTest.exists?("/etc/bluewhite64-version") - "Bluewhite64" - elsif FileTest.exists?("/etc/slamd64-version") - "Slamd64" - elsif FileTest.exists?("/etc/slackware-version") - "Slackware" - elsif FileTest.exists?("/etc/alpine-release") - "Alpine" - elsif FileTest.exists?("/etc/mageia-release") - "Mageia" - elsif FileTest.exists?("/etc/system-release") - "Amazon" end end end +Facter.add(:operatingsystem) do + confine :kernel => :linux + has_weight 10 + setcode do + fpath = "/etc/SuSE-release" + if FileTest.exists? fpath + candidates = Facter::Util::Operatingsystem::SUSE_VARIANTS + variant = Facter::Util::Operatingsystem.release_by_file(fpath, candidates) + variant || 'SuSE' + end + end +end + +Facter.add(:operatingsystem) do + confine :kernel => :linux + has_weight 5 + setcode do + files = Facter::Util::Operatingsystem::OPERATINGSYSTEM_FILES + + key = files.keys.find do |release_file| + FileTest.exists? release_file + end + + files[key] + end +end + Facter.add(:operatingsystem) do confine :kernel => "VMkernel" setcode { "ESXi" } diff --git a/lib/facter/util/operatingsystem.rb b/lib/facter/util/operatingsystem.rb new file mode 100644 index 0000000000..8913ed3936 --- /dev/null +++ b/lib/facter/util/operatingsystem.rb @@ -0,0 +1,54 @@ + +module Facter +module Util +module Operatingsystem + + OPERATINGSYSTEM_FILES = { + "/etc/openwrt_release" => "OpenWrt", + "/etc/gentoo-release" => "Gentoo", + "/etc/fedora-release" => "Fedora", + "/etc/mandriva-release" => "Mandriva", + "/etc/mandrake-release" => "Mandrake", + "/etc/meego-release" => "MeeGo", + "/etc/arch-release" => "Archlinux", + "/etc/oracle-release" => "OracleLinux", + "/etc/vmware-release" => "VMWareESX", + "/etc/bluewhite64-version" => "Bluewhite64", + "/etc/slamd64-version" => "Slamd64", + "/etc/slackware-version" => "Slackware", + "/etc/alpine-release" => "Alpine", + "/etc/mageia-release" => "Mageia", + "/etc/system-release" => "Amazon", + } + + REDHAT_VARIANTS = { + /centos/i => "CentOS", + /CERN/ => "SLC", + /scientific/i => "Scientific", + /^cloudlinux/i => "CloudLinux", + /Ascendos/i => "Ascendos", + /^XenServer/i => "XenServer", + /XCP/ => "XCP", + /^Parallels Server Bare Metal/i => "PSBM", + } + + SUSE_VARIANTS = { + /^SUSE LINUX Enterprise Server/i => "SLES", + /^SUSE LINUX Enterprise Desktop/i => "SLED", + /^openSUSE/i => "OpenSuSE", + } + + def release_by_file(path, candidates) + str = File.read(path) + + key = candidates.keys.find do |variant_regex| + str.match(variant_regex) + end + + candidates[key] + end + + module_function :release_by_file +end +end +end diff --git a/spec/unit/operatingsystem_spec.rb b/spec/unit/operatingsystem_spec.rb index 8eadee4b01..d5a8e9a009 100755 --- a/spec/unit/operatingsystem_spec.rb +++ b/spec/unit/operatingsystem_spec.rb @@ -83,14 +83,14 @@ end it "on Ubuntu should use the lsbdistid fact" do - FileUtils.stubs(:exists?).with("/etc/debian_version").returns true + FileTest.stubs(:exists?).with("/etc/debian_version").returns true Facter.stubs(:value).with(:lsbdistid).returns("Ubuntu") Facter.fact(:operatingsystem).value.should == "Ubuntu" end it "on LinuxMint should use the lsbdistid fact" do - FileUtils.stubs(:exists?).with("/etc/debian_version").returns true + FileTest.stubs(:exists?).with("/etc/debian_version").returns true Facter.stubs(:value).with(:lsbdistid).returns("LinuxMint") Facter.fact(:operatingsystem).value.should == "LinuxMint" From 121143121c1f9e6f849a301733d8261cfe15e037 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 18 Jul 2013 10:37:33 -0700 Subject: [PATCH 1336/3753] (maint) disambiguate SLC and Scientific linux regexes The regexes to detect Scientific Linux and Scientific Linux CERN were ambiguous and order dependent. Since the regexes were stored in a hash and were thus unordered, these looser regexes could cause mismatches. This commit uses stricter regexes for both SL and SLC to disambiguate this. --- lib/facter/util/operatingsystem.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/facter/util/operatingsystem.rb b/lib/facter/util/operatingsystem.rb index 8913ed3936..698688c220 100644 --- a/lib/facter/util/operatingsystem.rb +++ b/lib/facter/util/operatingsystem.rb @@ -22,14 +22,14 @@ module Operatingsystem } REDHAT_VARIANTS = { - /centos/i => "CentOS", - /CERN/ => "SLC", - /scientific/i => "Scientific", - /^cloudlinux/i => "CloudLinux", - /Ascendos/i => "Ascendos", - /^XenServer/i => "XenServer", - /XCP/ => "XCP", - /^Parallels Server Bare Metal/i => "PSBM", + /centos/i => "CentOS", + /scientific linux CERN/i => "SLC", + /scientific linux release/i => "Scientific", + /^cloudlinux/i => "CloudLinux", + /Ascendos/i => "Ascendos", + /^XenServer/i => "XenServer", + /XCP/ => "XCP", + /^Parallels Server Bare Metal/i => "PSBM", } SUSE_VARIANTS = { From 8b6630397c344df93044aa0bccfa42776620179b Mon Sep 17 00:00:00 2001 From: Leonardo Mello Date: Sat, 20 Jul 2013 17:37:01 -0300 Subject: [PATCH 1337/3753] fix use C as default locale. Currently facter sets only LANG environment variable, this does not work on systems that have LC_ALL configured, because LC_ALL have higher priority than LANG variable. So the correct procedure is to configure LC_ALL=C instead of LANG=C. --- lib/facter/util/resolution.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 7465a91238..63acff2c59 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -199,7 +199,7 @@ def self.exec(code, interpreter = nil) ## Set LANG to force i18n to C for the duration of this exec; this ensures that any code that parses the ## output of the command can expect it to be in a consistent / predictable format / locale - with_env "LANG" => "C" do + with_env "LC_ALL" => "C" do if expanded_code = expand_command(code) # if we can find the binary, we'll run the command with the expanded path to the binary From 49ee7626c63154112058db2d3c3462a4f32f7248 Mon Sep 17 00:00:00 2001 From: Leonardo Mello Date: Sat, 20 Jul 2013 17:45:41 -0300 Subject: [PATCH 1338/3753] change resolution_spec to test LC_ALL locale environment var. --- lib/facter/util/resolution.rb | 2 +- spec/unit/util/resolution_spec.rb | 28 ++++++++++++++-------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 63acff2c59..933591d864 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -197,7 +197,7 @@ def self.with_env(values) def self.exec(code, interpreter = nil) Facter.warnonce "The interpreter parameter to 'exec' is deprecated and will be removed in a future version." if interpreter - ## Set LANG to force i18n to C for the duration of this exec; this ensures that any code that parses the + ## Set LC_ALL to force i18n to C for the duration of this exec; this ensures that any code that parses the ## output of the command can expect it to be in a consistent / predictable format / locale with_env "LC_ALL" => "C" do diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index f013ffb61b..016bc5b394 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -54,7 +54,7 @@ describe "when overriding environment variables" do it "should execute the caller's block with the specified env vars" do - test_env = { "LANG" => "C", "FOO" => "BAR" } + test_env = { "LC_ALL" => "C", "FOO" => "BAR" } Facter::Util::Resolution.with_env test_env do test_env.keys.each do |key| ENV[key].should == test_env[key] @@ -594,8 +594,8 @@ class FlushFakeError < StandardError; end Facter::Util::Resolution.exec(echo_command).should == "foo" end - it "should override the LANG environment variable" do - Facter::Util::Resolution.exec(echo_env_var_command % 'LANG').should == "C" + it "should override the LC_ALL environment variable" do + Facter::Util::Resolution.exec(echo_env_var_command % 'LC_ALL').should == "C" end it "should respect other overridden environment variables" do @@ -604,27 +604,27 @@ class FlushFakeError < StandardError; end end end - it "should restore overridden LANG environment variable after execution" do + it "should restore overridden LC_ALL environment variable after execution" do # we're going to call with_env in a nested fashion, to make sure that the environment gets restored properly # at each level - Facter::Util::Resolution.with_env( {"LANG" => "foo"} ) do - # Resolution.exec always overrides 'LANG' for its own execution scope - Facter::Util::Resolution.exec(echo_env_var_command % 'LANG').should == "C" + Facter::Util::Resolution.with_env( {"LC_ALL" => "foo"} ) do + # Resolution.exec always overrides 'LC_ALL' for its own execution scope + Facter::Util::Resolution.exec(echo_env_var_command % 'LC_ALL').should == "C" # But after 'exec' completes, we should see our value restored - ENV['LANG'].should == "foo" + ENV['LC_ALL'].should == "foo" # Now we'll do a nested call to with_env - Facter::Util::Resolution.with_env( {"LANG" => "bar"} ) do + Facter::Util::Resolution.with_env( {"LC_ALL" => "bar"} ) do # During 'exec' it should still be 'C' - Facter::Util::Resolution.exec(echo_env_var_command % 'LANG').should == "C" + Facter::Util::Resolution.exec(echo_env_var_command % 'LC_ALL').should == "C" # After exec it should be restored to our current value for this level of the nesting... - ENV['LANG'].should == "bar" + ENV['LC_ALL'].should == "bar" end # Now we've dropped out of one level of nesting, - ENV['LANG'].should == "foo" + ENV['LC_ALL'].should == "foo" # Call exec one more time just for kicks - Facter::Util::Resolution.exec(echo_env_var_command % 'LANG').should == "C" + Facter::Util::Resolution.exec(echo_env_var_command % 'LC_ALL').should == "C" # One last check at our current nesting level. - ENV['LANG'].should == "foo" + ENV['LC_ALL'].should == "foo" end end From 17f5e2aee11ba68daa5c63556c041a452df79569 Mon Sep 17 00:00:00 2001 From: Matthaus Owens Date: Mon, 22 Jul 2013 21:52:16 -0700 Subject: [PATCH 1339/3753] (packaging) Remove etc directory references from packaging In commit d091bf882d731232ebcbf8d1dc324593cc837a98, facter.conf and the etc directory were removed from facter. This commit removes the references to the etc directory from the osx file mapping file and ext/project_data.yaml, which is used to build the gem and tarball. Without this commit building the gem or tarball will fail trying to copy a nonexistent directory. --- ext/osx/file_mapping.yaml | 5 ----- ext/project_data.yaml | 4 ++-- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/ext/osx/file_mapping.yaml b/ext/osx/file_mapping.yaml index 8ee36ad8d1..e21a0d9d8a 100644 --- a/ext/osx/file_mapping.yaml +++ b/ext/osx/file_mapping.yaml @@ -14,11 +14,6 @@ directories: owner: 'root' group: 'wheel' perms: '0644' - etc: - path: 'private/etc' - owner: 'root' - group: 'wheel' - perms: '0644' files: '[A-Z]*': path: 'usr/share/doc/facter' diff --git a/ext/project_data.yaml b/ext/project_data.yaml index 65d59bcaa5..73e1cf2bde 100644 --- a/ext/project_data.yaml +++ b/ext/project_data.yaml @@ -7,8 +7,8 @@ summary: 'Facter, a system inventory tool' description: 'You can prove anything with facts!' version_file: 'lib/facter/version.rb' # files and gem_files are space separated lists -files: '[A-Z]* acceptance bin documentation etc ext install.rb lib libexec man spec' -gem_files: '[A-Z]* install.rb bin etc ext lib spec' +files: '[A-Z]* acceptance bin documentation ext install.rb lib libexec man spec' +gem_files: '[A-Z]* install.rb bin ext lib spec' gem_require_path: 'lib' gem_test_files: 'spec/**/*' gem_executables: 'facter' From 32fd13a5f645c2fe5cfe8e6146a8fbcb95230043 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Demonchy?= Date: Thu, 11 Jul 2013 18:25:21 +0200 Subject: [PATCH 1340/3753] ip address fact that return full ifconfig output On system with non english locale, the ip address fact return full ifconfig output. --- lib/facter/ipaddress.rb | 6 ++++-- .../ipaddress/ifconfig_non_english_locale.txt | 18 ++++++++++++++++++ spec/unit/ipaddress_spec.rb | 2 ++ 3 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 spec/fixtures/unit/ipaddress/ifconfig_non_english_locale.txt diff --git a/lib/facter/ipaddress.rb b/lib/facter/ipaddress.rb index b4ce2e8ea5..b9475fddee 100644 --- a/lib/facter/ipaddress.rb +++ b/lib/facter/ipaddress.rb @@ -33,11 +33,13 @@ regexp = /inet (?:addr:)?([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/ output.split("\n").each do |line| match = regexp.match(line) - if match - break match[1] unless /^127/.match(match[1]) + if match and not /^127\./.match(match[1]) + ip = match[1] + break end end end + ip end end diff --git a/spec/fixtures/unit/ipaddress/ifconfig_non_english_locale.txt b/spec/fixtures/unit/ipaddress/ifconfig_non_english_locale.txt new file mode 100644 index 0000000000..b61bc95e74 --- /dev/null +++ b/spec/fixtures/unit/ipaddress/ifconfig_non_english_locale.txt @@ -0,0 +1,18 @@ +lo Link encap:Boucle locale + inet adr:127.0.0.1 Masque:255.0.0.0 + adr inet6: ::1/128 Scope:Hote + UP LOOPBACK RUNNING MTU:65536 Metric:1 + RX packets:1001 errors:0 dropped:0 overruns:0 frame:0 + TX packets:1001 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 lg file transmission:0 + RX bytes:52562 (51.3 KiB) TX bytes:52562 (51.3 KiB) + +wlan0 Link encap:Ethernet HWaddr ac:81:12:2d:86:25 + inet adr:192.168.1.83 Bcast:192.168.1.255 Masque:255.255.255.0 + adr inet6: fe80::ae81:12ff:fe2d:8625/64 Scope:Lien + UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 + RX packets:493220 errors:0 dropped:0 overruns:0 frame:0 + TX packets:390375 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 lg file transmission:1000 + RX bytes:569234568 (542.8 MiB) TX bytes:69778980 (66.5 MiB) + diff --git a/spec/unit/ipaddress_spec.rb b/spec/unit/ipaddress_spec.rb index 43325568e9..969437e9a7 100755 --- a/spec/unit/ipaddress_spec.rb +++ b/spec/unit/ipaddress_spec.rb @@ -32,5 +32,7 @@ "Linux with multiple loopback addresses", "10.0.222.20", "ifconfig_multiple_127_addresses.txt" + example_behavior_for "ifconfig output", + "Linux with non english locale", nil, "ifconfig_non_english_locale.txt" end end From d85a4c1cd1298cd597227c24f2fb420b1140a4b6 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Wed, 24 Jul 2013 11:12:27 -0700 Subject: [PATCH 1341/3753] (refactor) Simplify ipaddress fact specs --- spec/unit/ipaddress_spec.rb | 55 +++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/spec/unit/ipaddress_spec.rb b/spec/unit/ipaddress_spec.rb index 969437e9a7..764e43ab78 100755 --- a/spec/unit/ipaddress_spec.rb +++ b/spec/unit/ipaddress_spec.rb @@ -3,36 +3,37 @@ require 'spec_helper' require 'facter/util/ip' -shared_examples_for "ifconfig output" do |platform, address, fixture| - it "correctly on #{platform}" do - Facter::Util::IP.stubs(:exec_ifconfig).returns(my_fixture_read(fixture)) - subject.value.should == address +describe "ipaddress fact" do + before do + Facter.collection.internal_loader.load(:ipaddress) end -end -RSpec.configure do |config| - config.alias_it_should_behave_like_to :example_behavior_for, "parses" -end + context 'using `ifconfig`' do + context "on Linux" do + before :each do + Facter.fact(:kernel).stubs(:value).returns("Linux") + end -describe "The ipaddress fact" do - subject do - Facter.collection.internal_loader.load(:ipaddress) - Facter.fact(:ipaddress) - end - context "on Linux" do - before :each do - Facter.fact(:kernel).stubs(:value).returns("Linux") - end + def expect_ifconfig_parse(address, fixture) + Facter::Util::IP.stubs(:exec_ifconfig).returns(my_fixture_read(fixture)) + Facter.fact(:ipaddress).value.should == address + end + + it "parses correctly on Ubuntu 12.04" do + expect_ifconfig_parse "10.87.80.110", "ifconfig_ubuntu_1204.txt" + end + + it "parses correctly on Fedora 17" do + expect_ifconfig_parse "131.252.209.153", "ifconfig_net_tools_1.60.txt" + end - example_behavior_for "ifconfig output", - "Ubuntu 12.04", "10.87.80.110", "ifconfig_ubuntu_1204.txt" - example_behavior_for "ifconfig output", - "Fedora 17", "131.252.209.153", "ifconfig_net_tools_1.60.txt" - example_behavior_for "ifconfig output", - "Linux with multiple loopback addresses", - "10.0.222.20", - "ifconfig_multiple_127_addresses.txt" - example_behavior_for "ifconfig output", - "Linux with non english locale", nil, "ifconfig_non_english_locale.txt" + it "parses a real address over multiple loopback addresses" do + expect_ifconfig_parse "10.0.222.20", "ifconfig_multiple_127_addresses.txt" + end + + it "parses nothing with a non-english locale" do + expect_ifconfig_parse nil, "ifconfig_non_english_locale.txt" + end + end end end From 56ce53a639e2f5fac102624de099ce9c13ab6de0 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Wed, 24 Jul 2013 11:12:47 -0700 Subject: [PATCH 1342/3753] (maint) Stub extraneous facts when testing ifconfig If the higher weight ifconfig fact fails to resolve, Facter will try to determine the IP address by resolving the hostname. When testing to ensure that the ifconfig based resolution returns nil, the resolution fact will be called and return an unexpected value. This commit stubs the hostname fact when testing the ifconfig ipaddress resolution to ensure the tests behave consistently. --- spec/unit/ipaddress_spec.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/unit/ipaddress_spec.rb b/spec/unit/ipaddress_spec.rb index 764e43ab78..85f9aa6702 100755 --- a/spec/unit/ipaddress_spec.rb +++ b/spec/unit/ipaddress_spec.rb @@ -9,6 +9,10 @@ end context 'using `ifconfig`' do + before :each do + Facter.fact(:hostname).stubs(:value) + end + context "on Linux" do before :each do Facter.fact(:kernel).stubs(:value).returns("Linux") From d6a81b1596d66b4ab4e017ae4b797efe7435c944 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Wed, 24 Jul 2013 12:04:05 -0700 Subject: [PATCH 1343/3753] (maint) Handle Fedora as a proper Redhat variant The refactor of the operatingsystem fact in ec2bac8 split up fact resolution but removed the strict ordering of the resolution method. Fedora has /etc/redhat-release present which caused the redhat fact to take precedence, but the release string was not matched. The behavior of the redhat fact defaults to `redhat` if no exact version was matched. Since the fact was resolved, a lower resolution fact could not be used This commit adds fedora as a recognized release string so the redhat resolution behaves as expected. --- lib/facter/util/operatingsystem.rb | 2 +- spec/unit/operatingsystem_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/facter/util/operatingsystem.rb b/lib/facter/util/operatingsystem.rb index 698688c220..866eda3235 100644 --- a/lib/facter/util/operatingsystem.rb +++ b/lib/facter/util/operatingsystem.rb @@ -6,7 +6,6 @@ module Operatingsystem OPERATINGSYSTEM_FILES = { "/etc/openwrt_release" => "OpenWrt", "/etc/gentoo-release" => "Gentoo", - "/etc/fedora-release" => "Fedora", "/etc/mandriva-release" => "Mandriva", "/etc/mandrake-release" => "Mandrake", "/etc/meego-release" => "MeeGo", @@ -30,6 +29,7 @@ module Operatingsystem /^XenServer/i => "XenServer", /XCP/ => "XCP", /^Parallels Server Bare Metal/i => "PSBM", + /^Fedora release/ => "Fedora", } SUSE_VARIANTS = { diff --git a/spec/unit/operatingsystem_spec.rb b/spec/unit/operatingsystem_spec.rb index d5a8e9a009..8f766aee5d 100755 --- a/spec/unit/operatingsystem_spec.rb +++ b/spec/unit/operatingsystem_spec.rb @@ -57,7 +57,6 @@ { "Debian" => "/etc/debian_version", "Gentoo" => "/etc/gentoo-release", - "Fedora" => "/etc/fedora-release", "Mandriva" => "/etc/mandriva-release", "Mandrake" => "/etc/mandrake-release", "MeeGo" => "/etc/meego-release", @@ -109,6 +108,7 @@ "CloudLinux" => "CloudLinux Server release 5.5", "XenServer" => "XenServer release 5.6.0-31188p (xenenterprise)", "XCP" => "XCP release 1.6.10-61809c", + "Fedora" => "Fedora release 18 (Spherical Cow)", }.each_pair do |operatingsystem, string| it "should be #{operatingsystem} based on /etc/redhat-release contents #{string}" do FileTest.expects(:exists?).with("/etc/redhat-release").returns true From d58f8c512935f9c05fe8645d2d22bc05ddfb1c7c Mon Sep 17 00:00:00 2001 From: Jasper Lievisse Adriaanse Date: Wed, 24 Jul 2013 21:08:14 +0200 Subject: [PATCH 1344/3753] Consolidate several queries for hw.physmem on BSD. --- lib/facter/util/memory.rb | 6 +----- spec/unit/memory_spec.rb | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/facter/util/memory.rb b/lib/facter/util/memory.rb index 94115a9bd5..0b4e511960 100644 --- a/lib/facter/util/memory.rb +++ b/lib/facter/util/memory.rb @@ -111,14 +111,10 @@ def self.mem_size(kernel = Facter.value(:kernel)) def self.mem_size_info(kernel = Facter.value(:kernel)) case kernel - when /OpenBSD/i - Facter::Util::Resolution.exec("sysctl hw.physmem | cut -d'=' -f2") - when /FreeBSD/i + when /Dragonfly/i, /FreeBSD/i, /OpenBSD/i Facter::Util::Resolution.exec("sysctl -n hw.physmem") when /Darwin/i Facter::Util::Resolution.exec("sysctl -n hw.memsize") - when /Dragonfly/i - Facter::Util::Resolution.exec("sysctl -n hw.physmem") when /AIX/i if Facter::Util::Resolution.exec("/usr/bin/svmon -O unit=KB") =~ /^memory\s+(\d+)\s+/ $1 diff --git a/spec/unit/memory_spec.rb b/spec/unit/memory_spec.rb index 97f427b352..837fabf5cb 100755 --- a/spec/unit/memory_spec.rb +++ b/spec/unit/memory_spec.rb @@ -183,7 +183,7 @@ Facter::Util::Resolution.stubs(:exec).with('vmstat').returns(my_fixture_read('openbsd-vmstat')) - Facter::Util::Resolution.stubs(:exec).with("sysctl hw.physmem | cut -d'=' -f2").returns('267321344') + Facter::Util::Resolution.stubs(:exec).with('sysctl -n hw.physmem').returns('267321344') Facter.collection.internal_loader.load(:memory) end From 42e2814800615c16ad3c0cf124f7aba1e99df9f1 Mon Sep 17 00:00:00 2001 From: Jasper Lievisse Adriaanse Date: Thu, 27 Jun 2013 21:56:42 +0200 Subject: [PATCH 1345/3753] Add fact for encrypted swap on OpenBSD --- lib/facter/memory.rb | 24 +++++++++++++++--------- spec/unit/memory_spec.rb | 6 ++++++ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb index a8b05906ef..5b0fe56b1f 100644 --- a/lib/facter/memory.rb +++ b/lib/facter/memory.rb @@ -82,15 +82,21 @@ end end -if Facter.value(:kernel) == "Darwin" - Facter.add("SwapEncrypted") do - confine :kernel => :Darwin - setcode do - swap = Facter::Util::Resolution.exec('sysctl vm.swapusage') - encrypted = false - if swap =~ /\(encrypted\)/ then encrypted = true; end - encrypted - end +Facter.add("SwapEncrypted") do + confine :kernel => :openbsd + setcode do + sysctl_encrypted = Facter::Util::Resolution.exec("sysctl -n vm.swapencrypt.enable").to_i + !(sysctl_encrypted.zero?) + end +end + +Facter.add("SwapEncrypted") do + confine :kernel => :Darwin + setcode do + swap = Facter::Util::Resolution.exec('sysctl vm.swapusage') + encrypted = false + if swap =~ /\(encrypted\)/ then encrypted = true; end + encrypted end end diff --git a/spec/unit/memory_spec.rb b/spec/unit/memory_spec.rb index 97f427b352..57ce4656d0 100755 --- a/spec/unit/memory_spec.rb +++ b/spec/unit/memory_spec.rb @@ -185,6 +185,8 @@ Facter::Util::Resolution.stubs(:exec).with("sysctl hw.physmem | cut -d'=' -f2").returns('267321344') + Facter::Util::Resolution.stubs(:exec).with('sysctl -n vm.swapencrypt.enable').returns('1') + Facter.collection.internal_loader.load(:memory) end @@ -207,6 +209,10 @@ it "should return the current memory size in MB" do Facter.fact(:memorysize_mb).value.should == "254.94" end + + it "should return whether swap is encrypted" do + Facter.fact(:swapencrypted).value.should == true + end end describe "on Solaris" do From 17ee3f65f92d172e5537a7d084a9359f51ae8855 Mon Sep 17 00:00:00 2001 From: Leonardo Mello Date: Fri, 26 Jul 2013 14:17:26 -0300 Subject: [PATCH 1346/3753] should configure LANG and LC_ALL. refer to the pull request: https://github.com/puppetlabs/facter/pull/497 --- lib/facter/util/resolution.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 933591d864..55c92a5696 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -199,7 +199,8 @@ def self.exec(code, interpreter = nil) ## Set LC_ALL to force i18n to C for the duration of this exec; this ensures that any code that parses the ## output of the command can expect it to be in a consistent / predictable format / locale - with_env "LC_ALL" => "C" do + locale_vars = { "LC_ALL" => "C", "LANG" => "C" } + with_env locale_vars do if expanded_code = expand_command(code) # if we can find the binary, we'll run the command with the expanded path to the binary From 76c48aa2069948984a45c939004c664fa3eaabbb Mon Sep 17 00:00:00 2001 From: Stephen Brown II Date: Fri, 26 Jul 2013 12:19:26 -0400 Subject: [PATCH 1347/3753] (#21954) Add support for Mageia OS to osfamily.rb The addition of Mageia OS in https://github.com/puppetlabs/facter/commit/e2f040a5bc4ed7539ecc54a5c3d26f93a9ed6a18 (Ticket #9929) was incomplete. Mageia is derived from Mandrake and Mandriva, and so is in the same family. This patch adds support to return osfamily='Mandrake' on a Mageia OS system, rather than the generic 'Linux' kernel value, as well as adjusting the tests expecting the kernel fact for the Mageia operating system. --- lib/facter/osfamily.rb | 2 +- spec/unit/osfamily_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/facter/osfamily.rb b/lib/facter/osfamily.rb index d11e52aa21..b9c27c1ae0 100644 --- a/lib/facter/osfamily.rb +++ b/lib/facter/osfamily.rb @@ -28,7 +28,7 @@ "Gentoo" when "Archlinux" "Archlinux" - when "Mandrake", "Mandriva" + when "Mandrake", "Mandriva", "Mageia" "Mandrake" else Facter.value("kernel") diff --git a/spec/unit/osfamily_spec.rb b/spec/unit/osfamily_spec.rb index f6d6502928..64180c3569 100644 --- a/spec/unit/osfamily_spec.rb +++ b/spec/unit/osfamily_spec.rb @@ -32,6 +32,7 @@ 'SLED' => 'Suse', 'OpenSuSE' => 'Suse', 'SuSE' => 'Suse', + 'Mageia' => 'Mandrake', 'Mandriva' => 'Mandrake', 'Mandrake' => 'Mandrake' }.each do |os,family| @@ -48,7 +49,6 @@ 'Slamd64', 'Slackware', 'Alpine', - 'Mageia', 'ESXi', 'windows', 'HP-UX' From 995232eeb54b0aede5df5dea6ad4a35aee5c8208 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Mon, 29 Jul 2013 14:26:46 -0700 Subject: [PATCH 1348/3753] Maint: Don't load facts twice during specs Previously, the spec_helper was prepending facter's lib directory to the $LOAD_PATH, which had the side-effect of causing the zfs and zpool commands to be executed 5 times, not 3 (as specified in the comments). Originally, this was noticed because we were having to add `.at_least_once` to expectations even though the method should have only been called once. This commit changes the spec_helper to no longer prepend facter's top-level lib directory to the LOAD_PATH. In doing so, it causes the virtual fact resolutions to be evaluated in a different order than the virtual tests expected. In particular, the tests we're trying to verify the more-specific resolution, but were actually running with the more generic one. This commit adds a higher weight to the more specific virtual facts, since that's the expected behavior, and it ensures the tests reliably test the resolution they are trying to set their expectations on. Paired-with: Patrick Carlisle --- lib/facter/virtual.rb | 10 +++++----- spec/spec_helper.rb | 10 +++------- spec/unit/ec2_spec.rb | 2 +- spec/unit/zfs_version_spec.rb | 9 ++++----- spec/unit/zpool_version_spec.rb | 9 ++++----- 5 files changed, 17 insertions(+), 23 deletions(-) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 72ef9fb6e6..c65a4bd8ab 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -46,7 +46,7 @@ Facter.add("virtual") do confine :kernel => ["FreeBSD", "GNU/kFreeBSD"] - + has_weight 10 setcode do "jail" if Facter::Util::Virtual.jail? end @@ -54,7 +54,7 @@ Facter.add("virtual") do confine :kernel => 'SunOS' - + has_weight 10 setcode do next "zone" if Facter::Util::Virtual.zone? @@ -74,7 +74,7 @@ Facter.add("virtual") do confine :kernel => 'HP-UX' - + has_weight 10 setcode do "hpvm" if Facter::Util::Virtual.hpvm? end @@ -82,7 +82,7 @@ Facter.add("virtual") do confine :architecture => 's390x' - + has_weight 10 setcode do "zlinux" if Facter::Util::Virtual.zlinux? end @@ -90,7 +90,7 @@ Facter.add("virtual") do confine :kernel => 'OpenBSD' - + has_weight 10 setcode do output = Facter::Util::Resolution.exec('sysctl -n hw.product 2>/dev/null') if output diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index b0d8566a49..ead2438034 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,10 +1,3 @@ -# Add the projects lib directory to our load path so we can require libraries -# within it easily. -dir = File.expand_path(File.dirname(__FILE__)) - -SPECDIR = dir -$LOAD_PATH.unshift("#{dir}/../lib") - require 'rubygems' require 'mocha' require 'rspec' @@ -13,6 +6,9 @@ require 'puppetlabs_spec_helper' require 'pathname' +# load shared_context within this project's spec directory +dir = File.expand_path(File.dirname(__FILE__)) + Pathname.glob("#{dir}/shared_contexts/*.rb") do |file| require file.relative_path_from(Pathname.new(dir)) end diff --git a/spec/unit/ec2_spec.rb b/spec/unit/ec2_spec.rb index 93d0939e53..3046789c62 100755 --- a/spec/unit/ec2_spec.rb +++ b/spec/unit/ec2_spec.rb @@ -136,7 +136,7 @@ end it "should return nil if open fails" do - Facter.expects(:warn).with('Could not retrieve ec2 metadata: host unreachable').twice + Facter.expects(:warn).with('Could not retrieve ec2 metadata: host unreachable') Facter::Util::Resolution.any_instance.stubs(:warn) # do not pollute test output Object.any_instance.expects(:open). diff --git a/spec/unit/zfs_version_spec.rb b/spec/unit/zfs_version_spec.rb index 905dbc9ee5..2bfc01e384 100644 --- a/spec/unit/zfs_version_spec.rb +++ b/spec/unit/zfs_version_spec.rb @@ -56,11 +56,10 @@ it "handles the zfs command becoming available at a later point in time" do # Simulate Puppet configuring the zfs tools from a persistent daemon by - # simulating three sequential responses to which('zfs') - # (NOTE, each resolution causes which to execute twice. + # simulating three sequential responses to which('zfs'). Facter::Util::Resolution.stubs(:which). with("zfs"). - returns(nil,nil,nil,nil,"/usr/bin/zfs") + returns(nil,nil,"/usr/bin/zfs") Facter::Util::Resolution.stubs(:exec). with("zfs upgrade -v"). returns(my_fixture_read('linux-fuse_0.6.9')) @@ -68,8 +67,8 @@ fact = Facter.fact(:zfs_version) # zfs is not present the first two times the fact is resolved. - fact.value.should_not == "4" - fact.value.should_not == "4" + fact.value.should be_nil + fact.value.should be_nil # zfs was configured between the second and third resolutions. fact.value.should == "4" end diff --git a/spec/unit/zpool_version_spec.rb b/spec/unit/zpool_version_spec.rb index 0cba70fdb6..fc6a952a79 100644 --- a/spec/unit/zpool_version_spec.rb +++ b/spec/unit/zpool_version_spec.rb @@ -56,11 +56,10 @@ it "handles the zpool command becoming available" do # Simulate Puppet configuring the zfs tools from a persistent daemon by - # simulating three sequential responses to which('zpool') - # (NOTE, each resolution causes which to execute twice. + # simulating three sequential responses to which('zpool'). Facter::Util::Resolution.stubs(:which). with("zpool"). - returns(nil,nil,nil,nil,"/usr/bin/zpool") + returns(nil,nil,"/usr/bin/zpool") Facter::Util::Resolution.stubs(:exec). with("zpool upgrade -v"). returns(my_fixture_read('linux-fuse_0.6.9')) @@ -68,8 +67,8 @@ fact = Facter.fact(:zpool_version) # zfs is not present the first two times the fact is resolved. - fact.value.should_not == "23" - fact.value.should_not == "23" + fact.value.should be_nil + fact.value.should be_nil # zfs was configured between the second and third resolutions. fact.value.should == "23" end From 0a8c231b4269a838eecf58f7ec0b0103be2d456b Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Wed, 24 Jul 2013 16:59:22 -0700 Subject: [PATCH 1349/3753] (#16668) Use WMI to collect IPv4 address Previously, we were calling `IPSocket.getaddress(Socket.gethostname)` to resolve the `ipaddress` fact on Windows, but using WMI-based methods to resolve other IP related facts, e.g. interfaces. This led to inconsistent information -- ipaddress from one interface, netmask from another. This commit introduces a `get_preferred_network_adapters` method that returns an ordered list of WIN32OLE objects that wrap Win32_NetworkAdapterConfiguration objects. The list is ordered by the IPConnectionMetric. If two interfaces have the same metric, e.g. host with multiple NICs, then the order is based on the adapter bindings[1]. It is possible for a network adapter configuration to contain IPv4 and/or IPv6 addresses, but only the former will be considered valid for the ipaddress fact. [1] http://support.microsoft.com/kb/894564 --- lib/facter/ipaddress.rb | 15 ++++- lib/facter/util/ip/windows.rb | 107 ++++++++++++++++++++++++++++++++++ spec/unit/ipaddress_spec.rb | 90 ++++++++++++++++++++++++++++ 3 files changed, 210 insertions(+), 2 deletions(-) create mode 100644 lib/facter/util/ip/windows.rb diff --git a/lib/facter/ipaddress.rb b/lib/facter/ipaddress.rb index b9475fddee..97ed46427b 100644 --- a/lib/facter/ipaddress.rb +++ b/lib/facter/ipaddress.rb @@ -105,9 +105,20 @@ Facter.add(:ipaddress) do confine :kernel => %w{windows} + setcode do - require 'socket' - IPSocket.getaddress(Socket.gethostname) + require 'facter/util/ip/windows' + ipaddr = nil + + adapters = Facter::Util::IP::Windows.get_preferred_network_adapters + adapters.find do |nic| + nic.IPAddress.any? do |addr| + ipaddr = addr if Facter::Util::IP::Windows.valid_ipv4_address?(addr) + ipaddr + end + end + + ipaddr end end diff --git a/lib/facter/util/ip/windows.rb b/lib/facter/util/ip/windows.rb new file mode 100644 index 0000000000..11ce6ec52e --- /dev/null +++ b/lib/facter/util/ip/windows.rb @@ -0,0 +1,107 @@ +# encoding: UTF-8 + +require 'facter/util/wmi' + +class Facter::Util::IP::Windows + # The path to netsh.exe. + # + # @return [String] + # + # @api private + NETSH = "#{ENV['SYSTEMROOT']}/system32/netsh.exe" + + # The WMI query used to return ip information + # + # @return [String] + # + # @api private + WMI_IP_INFO_QUERY = 'SELECT Description, ServiceName, IPAddress, IPConnectionMetric, InterfaceIndex, Index, IPSubnet, MACAddress, MTU, SettingID FROM Win32_NetworkAdapterConfiguration WHERE IPConnectionMetric IS NOT NULL AND IPEnabled = TRUE' + + def self.to_s + 'windows' + end + + # Windows doesn't display netmask in hex. + # + # @return [Boolean] false by default + # + # @api private + def self.convert_netmask_from_hex? + false + end + + # Uses netsh.exe to obtain a list of interfaces. + # + # @return [Array] + # + # @api private + def self.interfaces + cmd = "#{NETSH} interface %s show interface" + output = exec("#{cmd % 'ip'} && #{cmd % 'ipv6'}").to_s + + output.scan(/\s* connected\s*(\S.*)/).flatten.uniq + end + + # Executes a wmi query that returns ip information and returns for each item in the results + # + # @return [Win32OLE] objects + # + # @api private + def self.exec_wmi_ip_query(&block) + Facter::Util::WMI.execquery(WMI_IP_INFO_QUERY).each do |nic| + yield nic + end + end + + # Gets a list of active adapters and sorts by the lowest connection metric (aka best weight) and MACAddress to ensure order + # + # @return [Win32OLE] + # + # @api private + def self.get_preferred_network_adapters + network_adapters = [] + + self.exec_wmi_ip_query do |nic| + network_adapters << nic + end + + require 'facter/util/registry' + bindings = {} + + Facter::Util::Registry.hklm_read('SYSTEM\CurrentControlSet\Services\Tcpip\Linkage','Bind').each_with_index do |entry, index| + match_data = entry.match(/\\Device\\(\{.*\})/) + unless match_data.nil? + bindings[match_data[1]] = index + end + end + + network_adapters.sort do |nic_left,nic_right| + cmp = nic_left.IPConnectionMetric <=> nic_right.IPConnectionMetric + if cmp == 0 + bindings[nic_left.SettingID] <=> bindings[nic_right.SettingID] + else + cmp + end + end + end + + # Determines if the value passed in is a valid ipv4 address. + # + # @param ip_address [String] + # + # @return [String] or [NilClass] + # + # @api private + def self.valid_ipv4_address?(ip_address) + String(ip_address).scan(/(?:[0-9]{1,3}\.){3}[0-9]{1,3}/).each do |match| + # excluding 169.254.x.x in Windows - this is the DHCP APIPA + # meaning that if the node cannot get an ip address from the dhcp server, + # it auto-assigns a private ip address + unless match == "127.0.0.1" or match =~ /^169.254.*/ + return match + end + end + + nil + end +end diff --git a/spec/unit/ipaddress_spec.rb b/spec/unit/ipaddress_spec.rb index 85f9aa6702..62cbbc2723 100755 --- a/spec/unit/ipaddress_spec.rb +++ b/spec/unit/ipaddress_spec.rb @@ -40,4 +40,94 @@ def expect_ifconfig_parse(address, fixture) end end end + + context "on Windows" do + require 'facter/util/wmi' + require 'facter/util/registry' + require 'facter/util/ip/windows' + + let(:settingId0) { '{4AE6B55C-6DD6-427D-A5BB-13535D4BE926}' } + let(:settingId1) { '{38762816-7957-42AC-8DAA-3B08D0C857C7}' } + let(:nic_bindings) { ["\\Device\\#{settingId0}", "\\Device\\#{settingId1}" ] } + + before :each do + Facter.fact(:kernel).stubs(:value).returns(:windows) + Facter.fact(:kernelrelease).stubs(:value).returns('6.1.7601') + Facter::Util::Registry.stubs(:hklm_read).returns(nic_bindings) + end + + it "should do what when VPN is turned on?" + + context "when you have no active network adapter" do + it "should return nil if there are no active (or any) network adapters" do + Facter::Util::WMI.expects(:execquery).with(Facter::Util::IP::Windows::WMI_IP_INFO_QUERY).returns([]) + Facter::Util::Resolution.stubs(:exec) + + Facter.value(:ipaddress).should == nil + end + end + + context "when you have one network adapter" do + it "should return the ip address properly" do + network1 = mock('network1') + network1.expects(:IPAddress).returns(["12.123.12.12", "2011:0:4137:9e76:2087:77a:53ef:7527"]) + Facter::Util::WMI.expects(:execquery).returns([network1]) + + Facter.value(:ipaddress).should == "12.123.12.12" + end + end + + context "when you have more than one network adapter" do + it "should return the ip of the adapter with the lowest IP connection metric (best connection)" do + network1 = mock('network1') + network1.expects(:IPConnectionMetric).returns(10) + network2 = mock('network2') + network2.expects(:IPConnectionMetric).returns(5) + network2.expects(:IPAddress).returns(["12.123.12.13", "2013:0:4137:9e76:2087:77a:53ef:7527"]) + Facter::Util::WMI.expects(:execquery).returns([network1, network2]) + + Facter.value(:ipaddress).should == "12.123.12.13" + end + + it "should return the ip of the adapter with the lowest IP connection metric (best connection) that has ipv4 enabled" do + network1 = mock('network1') + network1.expects(:IPConnectionMetric).returns(10) + network1.expects(:IPAddress).returns(["12.123.12.12", "2011:0:4137:9e76:2087:77a:53ef:7527"]) + network2 = mock('network2') + network2.expects(:IPConnectionMetric).returns(5) + network2.expects(:IPAddress).returns(["2013:0:4137:9e76:2087:77a:53ef:7527"]) + Facter::Util::WMI.expects(:execquery).returns([network1, network2]) + + Facter.value(:ipaddress).should == "12.123.12.12" + end + + context "when the IP connection metric is the same" do + it "should return the ip of the adapter with the lowest binding order" do + network1 = mock('network1') + network1.expects(:SettingID).returns(settingId0) + network1.expects(:IPConnectionMetric).returns(5) + network1.expects(:IPAddress).returns(["12.123.12.12", "2011:0:4137:9e76:2087:77a:53ef:7527"]) + network2 = mock('network2') + network2.expects(:SettingID).returns(settingId1) + network2.expects(:IPConnectionMetric).returns(5) + Facter::Util::WMI.expects(:execquery).returns([network1, network2]) + + Facter.value(:ipaddress).should == "12.123.12.12" + end + + it "should return the ip of the adapter with the lowest binding order even if the adapter is not first" do + network1 = mock('network1') + network1.expects(:IPConnectionMetric).returns(5) + network1.expects(:SettingID).returns(settingId1) + network2 = mock('network2') + network2.expects(:IPConnectionMetric).returns(5) + network2.expects(:IPAddress).returns(["12.123.12.13", "2013:0:4137:9e76:2087:77a:53ef:7527"]) + network2.expects(:SettingID).returns(settingId0) + Facter::Util::WMI.expects(:execquery).returns([network1, network2]) + + Facter.value(:ipaddress).should == "12.123.12.13" + end + end + end + end end From 8ab8b985953b0f9654e5bd86c3a02648d9828e24 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Wed, 24 Jul 2013 17:01:27 -0700 Subject: [PATCH 1350/3753] (#16668) Use WMI to collect netmask fact Previously, Windows hosts didn't report the netmask fact. --- lib/facter/netmask.rb | 19 +++++++++ spec/unit/netmask_spec.rb | 84 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 98 insertions(+), 5 deletions(-) diff --git a/lib/facter/netmask.rb b/lib/facter/netmask.rb index 710ebe47ac..9f749b3e19 100644 --- a/lib/facter/netmask.rb +++ b/lib/facter/netmask.rb @@ -21,3 +21,22 @@ Facter::NetMask.get_netmask end end + +Facter.add(:netmask) do + confine :kernel => :windows + setcode do + require 'facter/util/ip/windows' + + mask = nil + + adapters = Facter::Util::IP::Windows.get_preferred_network_adapters + adapters.find do |nic| + nic.IPSubnet.any? do |subnet| + mask = subnet if Facter::Util::IP::Windows.valid_ipv4_address?(subnet) + mask + end + end + + mask + end +end diff --git a/spec/unit/netmask_spec.rb b/spec/unit/netmask_spec.rb index ce77eefffe..25896b3425 100644 --- a/spec/unit/netmask_spec.rb +++ b/spec/unit/netmask_spec.rb @@ -13,12 +13,12 @@ end end -describe "netmask fact" do - before :each do - Facter.fact(:kernel).stubs(:value).returns("Linux") - end - +describe "The netmask fact" do context "on Linux" do + before :each do + Facter.fact(:kernel).stubs(:value).returns("Linux") + end + example_behavior_for "netmask from ifconfig output", "Archlinux (net-tools 1.60)", "255.255.255.0", "ifconfig_net_tools_1.60.txt" @@ -26,4 +26,78 @@ "Ubuntu 12.04", "255.255.255.255", "ifconfig_ubuntu_1204.txt" end + + context "on Windows" do + require 'facter/util/wmi' + require 'facter/util/registry' + + let(:settingId0) { '{4AE6B55C-6DD6-427D-A5BB-13535D4BE926}' } + let(:settingId1) { '{38762816-7957-42AC-8DAA-3B08D0C857C7}' } + let(:nic_bindings) { ["\\Device\\#{settingId0}", "\\Device\\#{settingId1}" ] } + + before :each do + Facter.fact(:kernel).stubs(:value).returns(:windows) + Facter::Util::Registry.stubs(:hklm_read).returns(nic_bindings) + end + + describe "when you have no active network adapter" do + it "should return nil if there are no active (or any) network adapters" do + Facter::Util::WMI.expects(:execquery).returns([]) + + Facter.value(:netmask).should == nil + end + end + + describe "when you have one network adapter" do + it "should return properly" do + network1 = mock('network1') + network1.expects(:IPSubnet).returns(["255.255.255.0", "48","2"]) + Facter::Util::WMI.expects(:execquery).returns([network1]) + + Facter.value(:netmask).should == "255.255.255.0" + end + end + + describe "when you have more than one network adapter" do + it "should return the netmask of the adapter with the lowest IP connection metric (best connection)" do + network1 = mock('network1') + network1.expects(:IPConnectionMetric).returns(10) + network2 = mock('network2') + network2.expects(:IPConnectionMetric).returns(5) + network2.expects(:IPSubnet).returns(["255.255.0.0", "48","2"]) + Facter::Util::WMI.expects(:execquery).returns([network1, network2]) + + Facter.value(:netmask).should == "255.255.0.0" + end + + context "when the IP connection metric is the same" do + it "should return the netmask of the adapter with the lowest binding order" do + network1 = mock('network1') + network1.expects(:SettingID).returns(settingId0) + network1.expects(:IPConnectionMetric).returns(5) + network1.expects(:IPSubnet).returns(["255.255.255.0", "48","64"]) + network2 = mock('network2') + network2.expects(:SettingID).returns(settingId1) + network2.expects(:IPConnectionMetric).returns(5) + + Facter::Util::WMI.expects(:execquery).returns([network1, network2]) + + Facter.value(:netmask).should =="255.255.255.0" + end + + it "should return the netmask of the adapter with the lowest binding even if the adapter is not first" do + network1 = mock('network1') + network1.expects(:SettingID).returns(settingId1) + network1.expects(:IPConnectionMetric).returns(5) + network2 = mock('network2') + network2.expects(:SettingID).returns(settingId0) + network2.expects(:IPConnectionMetric).returns(5) + network2.expects(:IPSubnet).returns(["255.255.0.0", "48","2"]) + Facter::Util::WMI.expects(:execquery).returns([network1, network2]) + + Facter.value(:netmask).should =="255.255.0.0" + end + end + end + end end From 0ddf0012a69d1b624aa1d9e0a2fa3a8ab2b67211 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Mon, 29 Jul 2013 15:57:13 -0700 Subject: [PATCH 1351/3753] (#16668) Use WMI to collect macaddress fact Previously, facter would report on the first macaddress associated with any interface that had IP enabled. This could easily be an interface with a high cost metric, or an IPv6 only interface. This commit refactors the macaddress fact to use the same ordered list of network adapters as is done for ipaddress, to ensure we return a consistent set of facts. --- lib/facter/util/macaddress.rb | 12 ++--- spec/unit/util/macaddress_spec.rb | 76 +++++++++++++++++++++++++++---- 2 files changed, 69 insertions(+), 19 deletions(-) diff --git a/lib/facter/util/macaddress.rb b/lib/facter/util/macaddress.rb index 82470ec41c..0309971565 100644 --- a/lib/facter/util/macaddress.rb +++ b/lib/facter/util/macaddress.rb @@ -33,16 +33,10 @@ def self.ifconfig_command module Windows def macaddress - require 'facter/util/wmi' + require 'facter/util/ip/windows' - query = "select MACAddress from Win32_NetworkAdapterConfiguration where IPEnabled = True" - - ether = nil - Facter::Util::WMI.execquery(query).each do |nic| - ether = nic.MacAddress - break - end - ether + adapter = Facter::Util::IP::Windows.get_preferred_network_adapters.first + adapter ? adapter.MACAddress : nil end module_function :macaddress end diff --git a/spec/unit/util/macaddress_spec.rb b/spec/unit/util/macaddress_spec.rb index c16d7a5695..863ed11796 100755 --- a/spec/unit/util/macaddress_spec.rb +++ b/spec/unit/util/macaddress_spec.rb @@ -81,19 +81,75 @@ end end -describe "Windows" do - it "should return the first macaddress" do - Facter.fact(:kernel).stubs(:value).returns("windows") +describe "The macaddress fact" do + context "on Windows" do + require 'facter/util/wmi' + require 'facter/util/registry' - nic = stubs 'nic' - nic.stubs(:MacAddress).returns("00:0C:29:0C:9E:9F") + let(:settingId0) { '{4AE6B55C-6DD6-427D-A5BB-13535D4BE926}' } + let(:settingId1) { '{38762816-7957-42AC-8DAA-3B08D0C857C7}' } + let(:nic_bindings) { ["\\Device\\#{settingId0}", "\\Device\\#{settingId1}" ] } - nic2 = stubs 'nic' - nic2.stubs(:MacAddress).returns("00:0C:29:0C:9E:AF") + before :each do + Facter.fact(:kernel).stubs(:value).returns(:windows) + Facter::Util::Registry.stubs(:hklm_read).returns(nic_bindings) + end - require 'facter/util/wmi' - Facter::Util::WMI.stubs(:execquery).with("select MACAddress from Win32_NetworkAdapterConfiguration where IPEnabled = True").returns([nic, nic2]) + describe "when you have no active network adapter" do + it "should return nil if there are no active (or any) network adapters" do + Facter::Util::WMI.expects(:execquery).returns([]) + + Facter.value(:macaddress).should == nil + end + end + + describe "when you have one network adapter" do + it "should return properly" do + network1 = mock('network1') + network1.expects(:MACAddress).returns("00:0C:29:0C:9E:9F") + Facter::Util::WMI.expects(:execquery).returns([network1]) + + Facter.value(:macaddress).should == "00:0C:29:0C:9E:9F" + end + end - Facter.fact(:macaddress).value.should == "00:0C:29:0C:9E:9F" + describe "when you have more than one network adapter" do + it "should return the macaddress of the adapter with the lowest IP connection metric (best connection)" do + network1 = mock('network1') + network1.expects(:IPConnectionMetric).returns(10) + network2 = mock('network2') + network2.expects(:MACAddress).returns("00:0C:29:0C:9E:AF") + network2.expects(:IPConnectionMetric).returns(5) + Facter::Util::WMI.expects(:execquery).returns([network1, network2]) + + Facter.value(:macaddress).should == "00:0C:29:0C:9E:AF" + end + + context "when the IP connection metric is the same" do + it "should return the macaddress of the adapter with the lowest binding order" do + network1 = mock('network1', :MACAddress => "23:24:df:12:12:00") + network1.expects(:SettingID).returns(settingId0) + network1.expects(:IPConnectionMetric).returns(5) + network2 = mock('network2') + network2.expects(:SettingID).returns(settingId1) + network2.expects(:IPConnectionMetric).returns(5) + Facter::Util::WMI.expects(:execquery).returns([network1, network2]) + + Facter.value(:macaddress).should == "23:24:df:12:12:00" + end + + it "should return the macaddress of the adapter with the lowest MACAddress when multiple adapters have the same IP connection metric when the lowest MACAddress is not first" do + network1 = stub('network1', :MACAddress => "23:24:df:12:12:00") + network1.expects(:SettingID).returns(settingId1) + network1.expects(:IPConnectionMetric).returns(5) + network2 = stub('network2', :MACAddress => "23:24:df:12:12:11") + network2.expects(:SettingID).returns(settingId0) + network2.expects(:IPConnectionMetric).returns(5) + Facter::Util::WMI.expects(:execquery).returns([network1, network2]) + + Facter.value(:macaddress).should == "23:24:df:12:12:11" + end + end + end end end From 66d8c80575e167946e72f8b0004ce79e0d1baf6b Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Wed, 24 Jul 2013 17:12:03 -0700 Subject: [PATCH 1352/3753] (#16668) Use WMI to collect IPv6 address Previously, we were using netsh.exe to collect the ipaddress6 fact, but it relied on en_US output that wasn't internationalizable. This commit uses WMI, like the ipaddress fact, and handles issues like multiple NICs, both IPv4 and IPv6 IP addresses on a single NIC, per-interface metrics, and the binding order of interfaces. --- lib/facter/ipaddress6.rb | 15 +++- lib/facter/util/ip/windows.rb | 18 +++++ spec/unit/ipaddress6_spec.rb | 145 ++++++++++++++++++++++++++++++++-- 3 files changed, 167 insertions(+), 11 deletions(-) diff --git a/lib/facter/ipaddress6.rb b/lib/facter/ipaddress6.rb index 4e44d13e65..559088b9b9 100644 --- a/lib/facter/ipaddress6.rb +++ b/lib/facter/ipaddress6.rb @@ -26,7 +26,7 @@ def get_address_after_token(output, token, return_first=false) ip = nil - String(output).scan(/#{token} ((?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/).each do |match| + String(output).scan(/#{token}\s?((?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/).each do |match| match = match.first unless match =~ /fe80.*/ or match == "::1" ip = match @@ -66,8 +66,17 @@ def get_address_after_token(output, token, return_first=false) Facter.add(:ipaddress6) do confine :kernel => :windows setcode do - output = Facter::Util::Resolution.exec("#{ENV['SYSTEMROOT']}/system32/netsh.exe interface ipv6 show address level=verbose") + require 'facter/util/ip/windows' + ipaddr = nil - get_address_after_token(output, 'Address', true) + adapters = Facter::Util::IP::Windows.get_preferred_network_adapters + adapters.find do |nic| + nic.IPAddress.any? do |addr| + ipaddr = addr if Facter::Util::IP::Windows.valid_ipv6_address?(addr) + ipaddr + end + end + + ipaddr end end diff --git a/lib/facter/util/ip/windows.rb b/lib/facter/util/ip/windows.rb index 11ce6ec52e..3030d30e23 100644 --- a/lib/facter/util/ip/windows.rb +++ b/lib/facter/util/ip/windows.rb @@ -104,4 +104,22 @@ def self.valid_ipv4_address?(ip_address) nil end + + # Determines if the value passed in is a valid ipv6 address. + # + # @param ip_address [String] + # + # @return [String] or [NilClass] + # + # @api private + def self.valid_ipv6_address?(ip_address) + String(ip_address).scan(/(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4}/).each do |match| + unless match =~ /fe80.*/ or match == "::1" + return match + end + end + + nil + end + end diff --git a/spec/unit/ipaddress6_spec.rb b/spec/unit/ipaddress6_spec.rb index 54e553d13c..e46ef86f77 100755 --- a/spec/unit/ipaddress6_spec.rb +++ b/spec/unit/ipaddress6_spec.rb @@ -12,7 +12,7 @@ def netsh_fixture(filename) end -describe "IPv6 address fact" do +describe "The IPv6 address fact" do include FacterSpec::ConfigHelper before do @@ -55,14 +55,143 @@ def netsh_fixture(filename) Facter.value(:ipaddress6).should == "2610:10:20:209:203:baff:fe27:a7c" end - it "should return ipaddress6 information for Windows" do - ENV.stubs(:[]).with('SYSTEMROOT').returns('d:/windows') - given_a_configuration_of(:is_windows => true) + context "on Windows" do + require 'facter/util/wmi' + require 'facter/util/registry' + require 'facter/util/ip/windows' - fixture = netsh_fixture('windows_netsh_addresses_with_multiple_interfaces') - Facter::Util::Resolution.stubs(:exec).with('d:/windows/system32/netsh.exe interface ipv6 show address level=verbose'). - returns(fixture) + let(:settingId0) { '{4AE6B55C-6DD6-427D-A5BB-13535D4BE926}' } + let(:settingId1) { '{38762816-7957-42AC-8DAA-3B08D0C857C7}' } + let(:nic_bindings) { ["\\Device\\#{settingId0}", "\\Device\\#{settingId1}" ] } - Facter.value(:ipaddress6).should == "2001:0:4137:9e76:2087:77a:53ef:7527" + before :each do + Facter.fact(:kernel).stubs(:value).returns(:windows) + Facter::Util::Registry.stubs(:hklm_read).returns(nic_bindings) + given_a_configuration_of(:is_windows => true) + end + + it "should do what when VPN is turned on?" + + context "when you have no active network adapter" do + it "should return nil if there are no active (or any) network adapters" do + Facter::Util::WMI.expects(:execquery).with(Facter::Util::IP::Windows::WMI_IP_INFO_QUERY).returns([]) + + Facter.value(:ipaddress6).should == nil + end + end + + context "when you have one network adapter" do + it "should return empty if ipv6 is not on" do + network1 = mock('network1', :IPAddress => ["12.123.12.12"]) + Facter::Util::WMI.expects(:execquery).returns([network1]) + + Facter.value(:ipaddress6).should == nil + end + + it "should return the ipv6 address properly" do + network1 = mock('network1', :IPAddress => ["12.123.12.12", "2011:0:4137:9e76:2087:77a:53ef:7527"]) + Facter::Util::WMI.expects(:execquery).returns([network1]) + + Facter.value(:ipaddress6).should == "2011:0:4137:9e76:2087:77a:53ef:7527" + end + + it "should return the first ipv6 address if there is more than one (multi-homing)" do + network1 = mock('network1', :IPAddress => ["12.123.12.12", "2013:0:4137:9e76:2087:77a:53ef:7527", "2011:0:4137:9e76:2087:77a:53ef:7527"]) + Facter::Util::WMI.expects(:execquery).returns([network1]) + + Facter.value(:ipaddress6).should == "2013:0:4137:9e76:2087:77a:53ef:7527" + end + + it "should return return nil if the ipv6 address is link local" do + network1 = mock('network1', :IPAddress => ["12.123.12.12", "fe80::2db2:5b42:4e30:b508"]) + Facter::Util::WMI.expects(:execquery).returns([network1]) + + Facter.value(:ipaddress6).should == nil + end + end + + context "when you have more than one network adapter" do + it "should return empty if ipv6 is not on" do + network1 = mock('network1') + network1.expects(:SettingID).returns(settingId0) + network1.expects(:IPConnectionMetric).returns(10) + network1.expects(:IPAddress).returns(["12.123.12.12"]) + network2 = mock('network2') + network2.expects(:SettingID).returns(settingId1) + network2.expects(:IPConnectionMetric).returns(10) + network2.expects(:IPAddress).returns(["12.123.12.13"]) + Facter::Util::WMI.expects(:execquery).returns([network1, network2]) + + Facter.value(:ipaddress6).should == nil + end + + it "should return the ipv6 of the adapter with the lowest IP connection metric (best connection)" do + network1 = mock('network1') + network1.expects(:IPConnectionMetric).returns(10) + network2 = mock('network2') + network2.expects(:IPConnectionMetric).returns(5) + network2.expects(:IPAddress).returns(["12.123.12.13", "2013:0:4137:9e76:2087:77a:53ef:7527"]) + Facter::Util::WMI.expects(:execquery).returns([network1, network2]) + + Facter.value(:ipaddress6).should == "2013:0:4137:9e76:2087:77a:53ef:7527" + end + + it "should return the ipv6 of the adapter with the lowest IP connection metric (best connection) that has ipv6 enabled" do + network1 = mock('network1') + network1.expects(:IPConnectionMetric).returns(10) + network1.expects(:IPAddress).returns(["12.123.12.12", "2011:0:4137:9e76:2087:77a:53ef:7527"]) + network2 = mock('network2') + network2.expects(:IPConnectionMetric).returns(5) + network2.expects(:IPAddress).returns(["12.123.12.13"]) + + Facter::Util::WMI.expects(:execquery).returns([network2, network1]) + + Facter.value(:ipaddress6).should == "2011:0:4137:9e76:2087:77a:53ef:7527" + end + + context "when the IP connection metric is the same" do + it "should return the ipv6 of the adapter with the lowest binding order" do + network1 = mock('network1') + network1.expects(:SettingID).returns(settingId0) + network1.expects(:IPConnectionMetric).returns(5) + network1.expects(:IPAddress).returns(["12.123.12.12", "2011:0:4137:9e76:2087:77a:53ef:7527"]) + network2 = mock('network2') + network2.expects(:SettingID).returns(settingId1) + network2.expects(:IPConnectionMetric).returns(5) + #network2.expects(:IPAddress).returns(["12.123.12.13", "2013:0:4137:9e76:2087:77a:53ef:7527"]) + Facter::Util::WMI.expects(:execquery).returns([network1, network2]) + + Facter.value(:ipaddress6).should == "2011:0:4137:9e76:2087:77a:53ef:7527" + end + + it "should return the ipv6 of the adapter with the lowest binding order even if the adapter is not first" do +<<<<<<< HEAD + network1 = mock('network1') + network1.expects(:SettingID).returns(settingId1) + network1.expects(:IPConnectionMetric).returns(5) + network2 = mock('network2') + network2.expects(:SettingID).returns(settingId0) + network2.expects(:IPConnectionMetric).returns(5) + network2.expects(:IPAddress).returns(["12.123.12.13", "2013:0:4137:9e76:2087:77a:53ef:7527"]) + Facter::Util::WMI.expects(:execquery).returns([network1, network2]) + + Facter.value(:ipaddress6).should == "2013:0:4137:9e76:2087:77a:53ef:7527" +||||||| parent of 6541f09... maint: remove whitespace from netmask.rb, windows_network.rb, and ipaddress6_spec.rb + nics = given_two_valid_windows_nics_with_ipv4_and_ipv6 + Facter::Util::Registry.stubs(:hklm_read).returns(["\\Device\\#{settingId1}", "\\Device\\#{settingId0}" ]) + + Facter::Util::WMI.expects(:execquery).returns(nics.values) + + Facter.value(:ipaddress6).should == ipv6Address1 +======= + nics = given_two_valid_windows_nics_with_ipv4_and_ipv6 + Facter::Util::Registry.stubs(:hklm_read).returns(["\\Device\\#{settingId1}", "\\Device\\#{settingId0}" ]) + Facter::Util::WMI.expects(:execquery).returns(nics.values) + + Facter.value(:ipaddress6).should == ipv6Address1 +>>>>>>> 6541f09... maint: remove whitespace from netmask.rb, windows_network.rb, and ipaddress6_spec.rb + end + end + end end end From 672efc59bb02f1799a7782233d7e6055064f595a Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Wed, 24 Jul 2013 17:17:18 -0700 Subject: [PATCH 1353/3753] (#21518) Use WMI to collect per-interface facts Previously, we used netsh.exe to collect the interfaces fact, and the derived per-interface facts on Windows. However, netsh.exe is not available on plain 2003 servers, and the facts relied on netsh.exe outputting en_US text. This commit refactors the method to use WMI instead, which does not have these issues. --- lib/facter/util/ip/windows.rb | 57 ++++++++++++++++++++++++++++--- spec/unit/util/ip/windows_spec.rb | 48 ++++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 4 deletions(-) create mode 100644 spec/unit/util/ip/windows_spec.rb diff --git a/lib/facter/util/ip/windows.rb b/lib/facter/util/ip/windows.rb index 3030d30e23..1a5fc83511 100644 --- a/lib/facter/util/ip/windows.rb +++ b/lib/facter/util/ip/windows.rb @@ -17,6 +17,13 @@ class Facter::Util::IP::Windows # @api private WMI_IP_INFO_QUERY = 'SELECT Description, ServiceName, IPAddress, IPConnectionMetric, InterfaceIndex, Index, IPSubnet, MACAddress, MTU, SettingID FROM Win32_NetworkAdapterConfiguration WHERE IPConnectionMetric IS NOT NULL AND IPEnabled = TRUE' + WINDOWS_LABEL_WMI_MAP = { + :ipaddress => 'IPAddress', + :ipaddress6 => 'IPAddress', + :macaddress => 'MACAddress', + :netmask => 'IPSubnet' + } + def self.to_s 'windows' end @@ -30,16 +37,58 @@ def self.convert_netmask_from_hex? false end - # Uses netsh.exe to obtain a list of interfaces. + # Retrieves a list of unique interfaces names. # # @return [Array] # # @api private def self.interfaces - cmd = "#{NETSH} interface %s show interface" - output = exec("#{cmd % 'ip'} && #{cmd % 'ipv6'}").to_s + network_interfaces = [] + self.exec_wmi_ip_query do |nic_config| + Facter::Util::WMI.execquery("SELECT * FROM Win32_NetworkAdapter WHERE Index = #{nic_config.Index}").each do |nic| + network_interfaces << nic.NetConnectionId + end + end + + network_interfaces.uniq + end + + # Get the value of an interface and label. For example, you may want to find + # the MTU for eth0. + # + # @param interface [String] label [String] + # + # @return [String] or [NilClass] + # + # @api private + def self.value_for_interface_and_label(interface, label) + wmi_value = WINDOWS_LABEL_WMI_MAP[label.downcase.to_sym] + label_value = nil + Facter::Util::WMI.execquery("SELECT Index FROM Win32_NetworkAdapter WHERE NetConnectionID = '#{interface}'").each do |nic| + Facter::Util::WMI.execquery("SELECT #{wmi_value} FROM Win32_NetworkAdapterConfiguration WHERE Index = #{nic.Index}").each do |nic_config| + case label.downcase.to_sym + when :ipaddress + nic_config.IPAddress.any? do |addr| + label_value = addr if valid_ipv4_address?(addr) + label_value + end + when :ipaddress6 + nic_config.IPAddress.any? do |addr| + label_value = addr if Facter::Util::IP::Windows.valid_ipv6_address?(addr) + label_value + end + when :netmask + nic_config.IPSubnet.any? do |addr| + label_value = addr if Facter::Util::IP::Windows.valid_ipv4_address?(addr) + label_value + end + when :macaddress + label_value = nic_config.MACAddress + end + end + end - output.scan(/\s* connected\s*(\S.*)/).flatten.uniq + label_value end # Executes a wmi query that returns ip information and returns for each item in the results diff --git a/spec/unit/util/ip/windows_spec.rb b/spec/unit/util/ip/windows_spec.rb new file mode 100644 index 0000000000..da7cbc53c8 --- /dev/null +++ b/spec/unit/util/ip/windows_spec.rb @@ -0,0 +1,48 @@ +# encoding: UTF-8 + +require 'spec_helper' +require 'facter/util/ip/windows' + +describe Facter::Util::IP::Windows do + before :each do + Facter.fact(:kernel).stubs(:value).returns('windows') + end + + describe ".to_s" do + let(:to_s) { described_class.to_s } + + it { to_s.should eq 'windows' } + end + + describe ".convert_netmask_from_hex?" do + let :convert_netmask_from_hex? do + described_class.convert_netmask_from_hex? + end + + it { convert_netmask_from_hex?.should be false } + end + + describe ".bonding_master" do + let(:bonding_master) { described_class.bonding_master('eth0') } + + pending("porting to Windows") do + it { bonding_master.should be_nil } + end + end + + describe ".interfaces" do + let(:name) { 'Local Area Connection' } + let(:index) { 7 } + let(:nic_config) { mock('nic_config', :Index => index) } + let(:nic) { mock('nic', :NetConnectionId => name ) } + + it "should return an array of only connected interfaces" do + Facter::Util::WMI.expects(:execquery).with(Facter::Util::IP::Windows::WMI_IP_INFO_QUERY). + returns([nic_config]) + Facter::Util::WMI.expects(:execquery).with("SELECT * FROM Win32_NetworkAdapter WHERE Index = #{index}"). + returns([nic]) + + described_class.interfaces.should == [name] + end + end +end From c3199c7385b9dedbc62e6884ccf67d27f352e59e Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Mon, 29 Jul 2013 14:51:19 -0700 Subject: [PATCH 1354/3753] (#16665) Remove references to netsh We no longer use netsh to collect IP related facts on Windows. This commit removes references to it. --- lib/facter/util/ip/windows.rb | 7 ---- ...s_netsh_addresses_with_multiple_interfaces | 35 ------------------- spec/unit/ipaddress6_spec.rb | 5 --- spec/unit/macaddress_spec.rb | 4 --- 4 files changed, 51 deletions(-) delete mode 100644 spec/fixtures/netsh/windows_netsh_addresses_with_multiple_interfaces diff --git a/lib/facter/util/ip/windows.rb b/lib/facter/util/ip/windows.rb index 1a5fc83511..966b727960 100644 --- a/lib/facter/util/ip/windows.rb +++ b/lib/facter/util/ip/windows.rb @@ -3,13 +3,6 @@ require 'facter/util/wmi' class Facter::Util::IP::Windows - # The path to netsh.exe. - # - # @return [String] - # - # @api private - NETSH = "#{ENV['SYSTEMROOT']}/system32/netsh.exe" - # The WMI query used to return ip information # # @return [String] diff --git a/spec/fixtures/netsh/windows_netsh_addresses_with_multiple_interfaces b/spec/fixtures/netsh/windows_netsh_addresses_with_multiple_interfaces deleted file mode 100644 index 6235f8ed62..0000000000 --- a/spec/fixtures/netsh/windows_netsh_addresses_with_multiple_interfaces +++ /dev/null @@ -1,35 +0,0 @@ -Address ::1 Parameters ---------------------------------------------------------- -Interface Luid : Loopback Pseudo-Interface 1 -Scope Id : 0.0 -Valid Lifetime : infinite -Preferred Lifetime : infinite -DAD State : Preferred -Address Type : Other - -Address fe80::7128:aa90:8f2a:8375%9 Parameters ---------------------------------------------------------- -Interface Luid : Local Area Connection -Scope Id : 0.9 -Valid Lifetime : infinite -Preferred Lifetime : infinite -DAD State : Preferred -Address Type : Other - -Address fe80::5efe:172.16.138.216%11 Parameters ---------------------------------------------------------- -Interface Luid : isatap.localdomain -Scope Id : 0.11 -Valid Lifetime : infinite -Preferred Lifetime : infinite -DAD State : Deprecated -Address Type : Other - -Address 2001:0:4137:9e76:2087:77a:53ef:7527 Parameters ---------------------------------------------------------- -Interface Luid : Teredo Tunneling Pseudo-Interface -Scope Id : 0.0 -Valid Lifetime : infinite -Preferred Lifetime : infinite -DAD State : Preferred -Address Type : Public diff --git a/spec/unit/ipaddress6_spec.rb b/spec/unit/ipaddress6_spec.rb index e46ef86f77..2735f3d3ab 100755 --- a/spec/unit/ipaddress6_spec.rb +++ b/spec/unit/ipaddress6_spec.rb @@ -7,11 +7,6 @@ def ifconfig_fixture(filename) File.read(fixtures('ifconfig', filename)) end -def netsh_fixture(filename) - File.read(fixtures('netsh', filename)) -end - - describe "The IPv6 address fact" do include FacterSpec::ConfigHelper diff --git a/spec/unit/macaddress_spec.rb b/spec/unit/macaddress_spec.rb index 638ef2ad7c..4550a939fb 100755 --- a/spec/unit/macaddress_spec.rb +++ b/spec/unit/macaddress_spec.rb @@ -8,10 +8,6 @@ def ifconfig_fixture(filename) File.read(fixtures('ifconfig', filename)) end -def netsh_fixture(filename) - File.read(fixtures('netsh', filename)) -end - describe "macaddress fact" do include FacterSpec::ConfigHelper From 2ac6cc1beab82a56f0fe30934ad48444dc9c19dc Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Mon, 29 Jul 2013 17:01:23 -0700 Subject: [PATCH 1355/3753] Maint: Simplify how interfaces are enumerated Previously, we were enumerating each WIN32OLE object, only to have the caller append the yielded object to an array and then call Enumerable methods on the array. This commit simplifies the method to return an Array of WIN32OLE objects, so the caller can call Enumerable methods directly on it. --- lib/facter/util/ip/windows.rb | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/lib/facter/util/ip/windows.rb b/lib/facter/util/ip/windows.rb index 966b727960..aec988e0c0 100644 --- a/lib/facter/util/ip/windows.rb +++ b/lib/facter/util/ip/windows.rb @@ -36,14 +36,15 @@ def self.convert_netmask_from_hex? # # @api private def self.interfaces - network_interfaces = [] - self.exec_wmi_ip_query do |nic_config| - Facter::Util::WMI.execquery("SELECT * FROM Win32_NetworkAdapter WHERE Index = #{nic_config.Index}").each do |nic| - network_interfaces << nic.NetConnectionId + interface_names = [] + + network_adapter_configurations.map do |nic| + Facter::Util::WMI.execquery("SELECT * FROM Win32_NetworkAdapter WHERE Index = #{nic.Index}").each do |nic| + interface_names << nic.NetConnectionId end end - network_interfaces.uniq + interface_names.uniq end # Get the value of an interface and label. For example, you may want to find @@ -89,10 +90,13 @@ def self.value_for_interface_and_label(interface, label) # @return [Win32OLE] objects # # @api private - def self.exec_wmi_ip_query(&block) + def self.network_adapter_configurations + nics = [] + # Win32OLE doesn't implement Enumerable Facter::Util::WMI.execquery(WMI_IP_INFO_QUERY).each do |nic| - yield nic + nics << nic end + nics end # Gets a list of active adapters and sorts by the lowest connection metric (aka best weight) and MACAddress to ensure order @@ -101,12 +105,6 @@ def self.exec_wmi_ip_query(&block) # # @api private def self.get_preferred_network_adapters - network_adapters = [] - - self.exec_wmi_ip_query do |nic| - network_adapters << nic - end - require 'facter/util/registry' bindings = {} @@ -117,7 +115,7 @@ def self.get_preferred_network_adapters end end - network_adapters.sort do |nic_left,nic_right| + network_adapter_configurations.sort do |nic_left,nic_right| cmp = nic_left.IPConnectionMetric <=> nic_right.IPConnectionMetric if cmp == 0 bindings[nic_left.SettingID] <=> bindings[nic_right.SettingID] From 9f18629dd74a9fd6323e454a73a93837f384df5b Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Mon, 29 Jul 2013 17:02:01 -0700 Subject: [PATCH 1356/3753] (#16668) Support IPv6 adapter binding order Previously, if multiple interfaces had the same IP connection metric, then we would always use the IPv4 specific adapter binding order, which could be incorrect when reporting IPv6 IP related facts. This commit adds a method for returning the ordered list of interfaces supporting IPv6, and updates the various facts to use the appropriate IPv4 or IPv6 method. --- lib/facter/ipaddress.rb | 3 +- lib/facter/ipaddress6.rb | 2 +- lib/facter/netmask.rb | 2 +- lib/facter/util/ip/windows.rb | 73 +++++++++++++++++++++++++++-------- lib/facter/util/macaddress.rb | 2 +- 5 files changed, 61 insertions(+), 21 deletions(-) diff --git a/lib/facter/ipaddress.rb b/lib/facter/ipaddress.rb index 97ed46427b..f4b46b1242 100644 --- a/lib/facter/ipaddress.rb +++ b/lib/facter/ipaddress.rb @@ -105,12 +105,11 @@ Facter.add(:ipaddress) do confine :kernel => %w{windows} - setcode do require 'facter/util/ip/windows' ipaddr = nil - adapters = Facter::Util::IP::Windows.get_preferred_network_adapters + adapters = Facter::Util::IP::Windows.get_preferred_ipv4_adapters adapters.find do |nic| nic.IPAddress.any? do |addr| ipaddr = addr if Facter::Util::IP::Windows.valid_ipv4_address?(addr) diff --git a/lib/facter/ipaddress6.rb b/lib/facter/ipaddress6.rb index 559088b9b9..c60cf3f3fd 100644 --- a/lib/facter/ipaddress6.rb +++ b/lib/facter/ipaddress6.rb @@ -69,7 +69,7 @@ def get_address_after_token(output, token, return_first=false) require 'facter/util/ip/windows' ipaddr = nil - adapters = Facter::Util::IP::Windows.get_preferred_network_adapters + adapters = Facter::Util::IP::Windows.get_preferred_ipv6_adapters adapters.find do |nic| nic.IPAddress.any? do |addr| ipaddr = addr if Facter::Util::IP::Windows.valid_ipv6_address?(addr) diff --git a/lib/facter/netmask.rb b/lib/facter/netmask.rb index 9f749b3e19..4730e5fe0d 100644 --- a/lib/facter/netmask.rb +++ b/lib/facter/netmask.rb @@ -29,7 +29,7 @@ mask = nil - adapters = Facter::Util::IP::Windows.get_preferred_network_adapters + adapters = Facter::Util::IP::Windows.get_preferred_ipv4_adapters adapters.find do |nic| nic.IPSubnet.any? do |subnet| mask = subnet if Facter::Util::IP::Windows.valid_ipv4_address?(subnet) diff --git a/lib/facter/util/ip/windows.rb b/lib/facter/util/ip/windows.rb index aec988e0c0..7c5aba3a84 100644 --- a/lib/facter/util/ip/windows.rb +++ b/lib/facter/util/ip/windows.rb @@ -51,7 +51,6 @@ def self.interfaces # the MTU for eth0. # # @param interface [String] label [String] - # # @return [String] or [NilClass] # # @api private @@ -99,36 +98,79 @@ def self.network_adapter_configurations nics end - # Gets a list of active adapters and sorts by the lowest connection metric (aka best weight) and MACAddress to ensure order + # Gets a list of active IPv4 network adapter configurations sorted by the + # lowest IP connection metric. If two configurations have the same metric, + # then the IPv4 specific binding order as specified in the registry will + # be used. # - # @return [Win32OLE] + # return [Array] # # @api private - def self.get_preferred_network_adapters - require 'facter/util/registry' - bindings = {} - - Facter::Util::Registry.hklm_read('SYSTEM\CurrentControlSet\Services\Tcpip\Linkage','Bind').each_with_index do |entry, index| - match_data = entry.match(/\\Device\\(\{.*\})/) - unless match_data.nil? - bindings[match_data[1]] = index - end - end + def self.get_preferred_ipv4_adapters + get_preferred_network_adapters(Bindings4.new) + end + # Gets a list of active IPv6 network adapter configurations sorted by the + # lowest IP connection metric. If two configurations have the same metric, + # then the IPv6 specific binding order as specified in the registry will + # be used. + # + # return [Array] + # + # @api private + def self.get_preferred_ipv6_adapters + get_preferred_network_adapters(Bindings6.new) + end + + # Gets a list of active network adapter configurations sorted by the lowest + # IP connection metric. If two configurations have the same metric, then + # the adapter binding order as specified in the registry will be used. + # Note the order may different for IPv4 vs IPv6 addresses. + # + # @see http://support.microsoft.com/kb/894564 + # @return [Array] + # + # @api private + def self.get_preferred_network_adapters(bindings) network_adapter_configurations.sort do |nic_left,nic_right| cmp = nic_left.IPConnectionMetric <=> nic_right.IPConnectionMetric if cmp == 0 - bindings[nic_left.SettingID] <=> bindings[nic_right.SettingID] + bindings.bindings[nic_left.SettingID] <=> bindings.bindings[nic_right.SettingID] else cmp end end end + class Bindings4 + def initialize + @key = 'SYSTEM\CurrentControlSet\Services\Tcpip\Linkage' + end + + def bindings + require 'facter/util/registry' + bindings = {} + + Facter::Util::Registry.hklm_read(@key, 'Bind').each_with_index do |entry, index| + match_data = entry.match(/\\Device\\(\{.*\})/) + unless match_data.nil? + bindings[match_data[1]] = index + end + end + + bindings + end + end + + class Bindings6 < Bindings4 + def initialize + @key = 'SYSTEM\CurrentControlSet\Services\Tcpip6\Linkage' + end + end + # Determines if the value passed in is a valid ipv4 address. # # @param ip_address [String] - # # @return [String] or [NilClass] # # @api private @@ -148,7 +190,6 @@ def self.valid_ipv4_address?(ip_address) # Determines if the value passed in is a valid ipv6 address. # # @param ip_address [String] - # # @return [String] or [NilClass] # # @api private diff --git a/lib/facter/util/macaddress.rb b/lib/facter/util/macaddress.rb index 0309971565..28486726e2 100644 --- a/lib/facter/util/macaddress.rb +++ b/lib/facter/util/macaddress.rb @@ -35,7 +35,7 @@ module Windows def macaddress require 'facter/util/ip/windows' - adapter = Facter::Util::IP::Windows.get_preferred_network_adapters.first + adapter = Facter::Util::IP::Windows.get_preferred_ipv4_adapters.first adapter ? adapter.MACAddress : nil end module_function :macaddress From abf0d0853874700cb119221d3568e8574e009e6d Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Wed, 24 Jul 2013 23:36:01 -0700 Subject: [PATCH 1357/3753] Maint: Update yardocs Updated yardocs where appropriate. Also changed the predicate methods to return a boolean instead of String or nil. --- lib/facter/util/ip/windows.rb | 38 +++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/lib/facter/util/ip/windows.rb b/lib/facter/util/ip/windows.rb index 7c5aba3a84..0881ecb649 100644 --- a/lib/facter/util/ip/windows.rb +++ b/lib/facter/util/ip/windows.rb @@ -10,6 +10,9 @@ class Facter::Util::IP::Windows # @api private WMI_IP_INFO_QUERY = 'SELECT Description, ServiceName, IPAddress, IPConnectionMetric, InterfaceIndex, Index, IPSubnet, MACAddress, MTU, SettingID FROM Win32_NetworkAdapterConfiguration WHERE IPConnectionMetric IS NOT NULL AND IPEnabled = TRUE' + # Mapping fact names to WMI properties of the Win32_NetworkAdapterConfiguration + # + # @api private WINDOWS_LABEL_WMI_MAP = { :ipaddress => 'IPAddress', :ipaddress6 => 'IPAddress', @@ -32,7 +35,7 @@ def self.convert_netmask_from_hex? # Retrieves a list of unique interfaces names. # - # @return [Array] + # @return [Array] # # @api private def self.interfaces @@ -50,8 +53,9 @@ def self.interfaces # Get the value of an interface and label. For example, you may want to find # the MTU for eth0. # - # @param interface [String] label [String] - # @return [String] or [NilClass] + # @param [String] interface the name of the interface returned by the {#interfaces} method. + # @param [String] label the type of value to return, e.g. ipaddress + # @return [String] the value, or nil if not defined # # @api private def self.value_for_interface_and_label(interface, label) @@ -84,14 +88,14 @@ def self.value_for_interface_and_label(interface, label) label_value end - # Executes a wmi query that returns ip information and returns for each item in the results + # Returns an array of partial Win32_NetworkAdapterConfiguration objects. # - # @return [Win32OLE] objects + # @return [Array] objects # # @api private def self.network_adapter_configurations nics = [] - # Win32OLE doesn't implement Enumerable + # WIN32OLE doesn't implement Enumerable Facter::Util::WMI.execquery(WMI_IP_INFO_QUERY).each do |nic| nics << nic end @@ -103,7 +107,7 @@ def self.network_adapter_configurations # then the IPv4 specific binding order as specified in the registry will # be used. # - # return [Array] + # @return [Array] # # @api private def self.get_preferred_ipv4_adapters @@ -115,7 +119,7 @@ def self.get_preferred_ipv4_adapters # then the IPv6 specific binding order as specified in the registry will # be used. # - # return [Array] + # @return [Array] # # @api private def self.get_preferred_ipv6_adapters @@ -128,7 +132,7 @@ def self.get_preferred_ipv6_adapters # Note the order may different for IPv4 vs IPv6 addresses. # # @see http://support.microsoft.com/kb/894564 - # @return [Array] + # @return [Array] # # @api private def self.get_preferred_network_adapters(bindings) @@ -170,8 +174,8 @@ def initialize # Determines if the value passed in is a valid ipv4 address. # - # @param ip_address [String] - # @return [String] or [NilClass] + # @param [String] ip_address the IPv4 address to validate + # @return [Boolean] # # @api private def self.valid_ipv4_address?(ip_address) @@ -180,27 +184,27 @@ def self.valid_ipv4_address?(ip_address) # meaning that if the node cannot get an ip address from the dhcp server, # it auto-assigns a private ip address unless match == "127.0.0.1" or match =~ /^169.254.*/ - return match + return !!match end end - nil + false end # Determines if the value passed in is a valid ipv6 address. # - # @param ip_address [String] - # @return [String] or [NilClass] + # @param [String] ip_address the IPv6 address to validate + # @return [Boolean] # # @api private def self.valid_ipv6_address?(ip_address) String(ip_address).scan(/(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4}/).each do |match| unless match =~ /fe80.*/ or match == "::1" - return match + return !!match end end - nil + false end end From 8fc6bd57c836de3c6c11d338019fabf6b4122c40 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Sat, 27 Jul 2013 06:31:58 -0500 Subject: [PATCH 1358/3753] (#16668) added windows network spec helper --- spec/lib/facter_spec/windows_network.rb | 64 +++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 spec/lib/facter_spec/windows_network.rb diff --git a/spec/lib/facter_spec/windows_network.rb b/spec/lib/facter_spec/windows_network.rb new file mode 100644 index 0000000000..21d9f1a0cf --- /dev/null +++ b/spec/lib/facter_spec/windows_network.rb @@ -0,0 +1,64 @@ +require 'spec_helper' + +module FacterSpec::WindowsNetwork + + def settingId0 + '{4AE6B55C-6DD6-427D-A5BB-13535D4BE926}' + end + + def settingId1 + '{38762816-7957-42AC-8DAA-3B08D0C857C7}' + end + + def nic_bindings + ["\\Device\\#{settingId0}", "\\Device\\#{settingId1}" ] + end + + def macAddress0 + '23:24:df:12:12:00' + end + + def macAddress1 + '00:0C:29:0C:9E:9F' + end + + def ipAddress0 + '12.123.12.12' + end + + def ipAddress1 + '12.123.12.13' + end + + def subnet0 + '255.255.255.0' + end + + def subnet1 + '255.255.0.0' + end + + def ipv6Address0 + '2011:0:4137:9e76:2087:77a:53ef:7527' + end + + def ipv6Address1 + '2013:0:4137:9e76:2087:77a:53ef:7527' + end + + def ipv6LinkLocal + 'fe80::2db2:5b42:4e30:b508' + end + + def given_a_valid_windows_nic_with_ipv4_and_ipv6 + stub('network0', :IPAddress => [ipAddress0, ipv6Address0], :SettingID => settingId0, :IPConnectionMetric => 10,:MACAddress => macAddress0,:IPSubnet => [subnet0, '48','2']) + end + + def given_two_valid_windows_nics_with_ipv4_and_ipv6 + { + :nic0 => given_a_valid_windows_nic_with_ipv4_and_ipv6, + :nic1 => stub('network1', :IPAddress => [ipAddress1, ipv6Address1], :SettingID => settingId1, :IPConnectionMetric => 10,:MACAddress => macAddress1,:IPSubnet => [subnet1, '48','2']) + } + end + +end From fb3bd3b943ce32648c66cb94b1049d67d2d5bd0b Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Mon, 29 Jul 2013 14:31:11 -0700 Subject: [PATCH 1359/3753] maint: adding lib to spec path in spec helper Add the 'spec/lib' directory to the load path so we can load spec specific code. --- spec/spec_helper.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index ead2438034..be481046a0 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -8,6 +8,7 @@ # load shared_context within this project's spec directory dir = File.expand_path(File.dirname(__FILE__)) +$LOAD_PATH.unshift File.join(dir, 'lib') Pathname.glob("#{dir}/shared_contexts/*.rb") do |file| require file.relative_path_from(Pathname.new(dir)) From 47bfb4a06a33263344d203232da1daabe2b10e95 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Sat, 27 Jul 2013 06:34:53 -0500 Subject: [PATCH 1360/3753] (#16668) Only consider NICs that have a binding entry This ensures that only nics that are in the bindings for ipv4/ipv6 are checked against the registry bindings. --- lib/facter/util/ip/windows.rb | 4 +- spec/unit/ipaddress6_spec.rb | 102 ++++++++++-------------------- spec/unit/ipaddress_spec.rb | 69 ++++++++------------ spec/unit/netmask_spec.rb | 53 ++++++---------- spec/unit/util/macaddress_spec.rb | 52 ++++++--------- 5 files changed, 97 insertions(+), 183 deletions(-) mode change 100644 => 100755 spec/unit/netmask_spec.rb diff --git a/lib/facter/util/ip/windows.rb b/lib/facter/util/ip/windows.rb index 0881ecb649..42b4db7ce9 100644 --- a/lib/facter/util/ip/windows.rb +++ b/lib/facter/util/ip/windows.rb @@ -136,7 +136,9 @@ def self.get_preferred_ipv6_adapters # # @api private def self.get_preferred_network_adapters(bindings) - network_adapter_configurations.sort do |nic_left,nic_right| + network_adapter_configurations.select do |nic| + bindings.bindings.include?(nic.SettingID) + end.sort do |nic_left,nic_right| cmp = nic_left.IPConnectionMetric <=> nic_right.IPConnectionMetric if cmp == 0 bindings.bindings[nic_left.SettingID] <=> bindings.bindings[nic_right.SettingID] diff --git a/spec/unit/ipaddress6_spec.rb b/spec/unit/ipaddress6_spec.rb index 2735f3d3ab..9173f90954 100755 --- a/spec/unit/ipaddress6_spec.rb +++ b/spec/unit/ipaddress6_spec.rb @@ -54,10 +54,9 @@ def ifconfig_fixture(filename) require 'facter/util/wmi' require 'facter/util/registry' require 'facter/util/ip/windows' + require 'facter_spec/windows_network' - let(:settingId0) { '{4AE6B55C-6DD6-427D-A5BB-13535D4BE926}' } - let(:settingId1) { '{38762816-7957-42AC-8DAA-3B08D0C857C7}' } - let(:nic_bindings) { ["\\Device\\#{settingId0}", "\\Device\\#{settingId1}" ] } + include FacterSpec::WindowsNetwork before :each do Facter.fact(:kernel).stubs(:value).returns(:windows) @@ -77,29 +76,31 @@ def ifconfig_fixture(filename) context "when you have one network adapter" do it "should return empty if ipv6 is not on" do - network1 = mock('network1', :IPAddress => ["12.123.12.12"]) - Facter::Util::WMI.expects(:execquery).returns([network1]) + nic = given_a_valid_windows_nic_with_ipv4_and_ipv6 + nic.expects(:IPAddress).returns([ipAddress1]) + Facter::Util::WMI.expects(:execquery).returns([nic]) Facter.value(:ipaddress6).should == nil end it "should return the ipv6 address properly" do - network1 = mock('network1', :IPAddress => ["12.123.12.12", "2011:0:4137:9e76:2087:77a:53ef:7527"]) - Facter::Util::WMI.expects(:execquery).returns([network1]) + Facter::Util::WMI.expects(:execquery).returns([given_a_valid_windows_nic_with_ipv4_and_ipv6]) - Facter.value(:ipaddress6).should == "2011:0:4137:9e76:2087:77a:53ef:7527" + Facter.value(:ipaddress6).should == ipv6Address0 end it "should return the first ipv6 address if there is more than one (multi-homing)" do - network1 = mock('network1', :IPAddress => ["12.123.12.12", "2013:0:4137:9e76:2087:77a:53ef:7527", "2011:0:4137:9e76:2087:77a:53ef:7527"]) - Facter::Util::WMI.expects(:execquery).returns([network1]) + nic = given_a_valid_windows_nic_with_ipv4_and_ipv6 + nic.expects(:IPAddress).returns([ipAddress0, ipv6Address0,ipv6Address1]) + Facter::Util::WMI.expects(:execquery).returns([nic]) - Facter.value(:ipaddress6).should == "2013:0:4137:9e76:2087:77a:53ef:7527" + Facter.value(:ipaddress6).should == ipv6Address0 end it "should return return nil if the ipv6 address is link local" do - network1 = mock('network1', :IPAddress => ["12.123.12.12", "fe80::2db2:5b42:4e30:b508"]) - Facter::Util::WMI.expects(:execquery).returns([network1]) + nic = given_a_valid_windows_nic_with_ipv4_and_ipv6 + nic.expects(:IPAddress).returns([ipAddress0, ipv6LinkLocal]) + Facter::Util::WMI.expects(:execquery).returns([nic]) Facter.value(:ipaddress6).should == nil end @@ -107,84 +108,45 @@ def ifconfig_fixture(filename) context "when you have more than one network adapter" do it "should return empty if ipv6 is not on" do - network1 = mock('network1') - network1.expects(:SettingID).returns(settingId0) - network1.expects(:IPConnectionMetric).returns(10) - network1.expects(:IPAddress).returns(["12.123.12.12"]) - network2 = mock('network2') - network2.expects(:SettingID).returns(settingId1) - network2.expects(:IPConnectionMetric).returns(10) - network2.expects(:IPAddress).returns(["12.123.12.13"]) - Facter::Util::WMI.expects(:execquery).returns([network1, network2]) + nics = given_two_valid_windows_nics_with_ipv4_and_ipv6 + nics[:nic0].expects(:IPAddress).returns([ipAddress0]) + nics[:nic1].expects(:IPAddress).returns([ipAddress1]) + Facter::Util::WMI.expects(:execquery).returns(nics.values) Facter.value(:ipaddress6).should == nil end it "should return the ipv6 of the adapter with the lowest IP connection metric (best connection)" do - network1 = mock('network1') - network1.expects(:IPConnectionMetric).returns(10) - network2 = mock('network2') - network2.expects(:IPConnectionMetric).returns(5) - network2.expects(:IPAddress).returns(["12.123.12.13", "2013:0:4137:9e76:2087:77a:53ef:7527"]) - Facter::Util::WMI.expects(:execquery).returns([network1, network2]) - - Facter.value(:ipaddress6).should == "2013:0:4137:9e76:2087:77a:53ef:7527" + nics = given_two_valid_windows_nics_with_ipv4_and_ipv6 + nics[:nic1].expects(:IPConnectionMetric).returns(5) + Facter::Util::WMI.expects(:execquery).returns(nics.values) + + Facter.value(:ipaddress6).should == ipv6Address1 end it "should return the ipv6 of the adapter with the lowest IP connection metric (best connection) that has ipv6 enabled" do - network1 = mock('network1') - network1.expects(:IPConnectionMetric).returns(10) - network1.expects(:IPAddress).returns(["12.123.12.12", "2011:0:4137:9e76:2087:77a:53ef:7527"]) - network2 = mock('network2') - network2.expects(:IPConnectionMetric).returns(5) - network2.expects(:IPAddress).returns(["12.123.12.13"]) - - Facter::Util::WMI.expects(:execquery).returns([network2, network1]) + nics = given_two_valid_windows_nics_with_ipv4_and_ipv6 + nics[:nic1].expects(:IPConnectionMetric).returns(5) + nics[:nic1].expects(:IPAddress).returns([ipAddress1]) + Facter::Util::WMI.expects(:execquery).returns(nics.values) - Facter.value(:ipaddress6).should == "2011:0:4137:9e76:2087:77a:53ef:7527" + Facter.value(:ipaddress6).should == ipv6Address0 end context "when the IP connection metric is the same" do it "should return the ipv6 of the adapter with the lowest binding order" do - network1 = mock('network1') - network1.expects(:SettingID).returns(settingId0) - network1.expects(:IPConnectionMetric).returns(5) - network1.expects(:IPAddress).returns(["12.123.12.12", "2011:0:4137:9e76:2087:77a:53ef:7527"]) - network2 = mock('network2') - network2.expects(:SettingID).returns(settingId1) - network2.expects(:IPConnectionMetric).returns(5) - #network2.expects(:IPAddress).returns(["12.123.12.13", "2013:0:4137:9e76:2087:77a:53ef:7527"]) - Facter::Util::WMI.expects(:execquery).returns([network1, network2]) - - Facter.value(:ipaddress6).should == "2011:0:4137:9e76:2087:77a:53ef:7527" - end - - it "should return the ipv6 of the adapter with the lowest binding order even if the adapter is not first" do -<<<<<<< HEAD - network1 = mock('network1') - network1.expects(:SettingID).returns(settingId1) - network1.expects(:IPConnectionMetric).returns(5) - network2 = mock('network2') - network2.expects(:SettingID).returns(settingId0) - network2.expects(:IPConnectionMetric).returns(5) - network2.expects(:IPAddress).returns(["12.123.12.13", "2013:0:4137:9e76:2087:77a:53ef:7527"]) - Facter::Util::WMI.expects(:execquery).returns([network1, network2]) - - Facter.value(:ipaddress6).should == "2013:0:4137:9e76:2087:77a:53ef:7527" -||||||| parent of 6541f09... maint: remove whitespace from netmask.rb, windows_network.rb, and ipaddress6_spec.rb nics = given_two_valid_windows_nics_with_ipv4_and_ipv6 - Facter::Util::Registry.stubs(:hklm_read).returns(["\\Device\\#{settingId1}", "\\Device\\#{settingId0}" ]) - Facter::Util::WMI.expects(:execquery).returns(nics.values) - Facter.value(:ipaddress6).should == ipv6Address1 -======= + Facter.value(:ipaddress6).should == ipv6Address0 + end + + it "should return the ipv6 of the adapter with the lowest binding order even if the adapter is not first" do nics = given_two_valid_windows_nics_with_ipv4_and_ipv6 Facter::Util::Registry.stubs(:hklm_read).returns(["\\Device\\#{settingId1}", "\\Device\\#{settingId0}" ]) Facter::Util::WMI.expects(:execquery).returns(nics.values) Facter.value(:ipaddress6).should == ipv6Address1 ->>>>>>> 6541f09... maint: remove whitespace from netmask.rb, windows_network.rb, and ipaddress6_spec.rb end end end diff --git a/spec/unit/ipaddress_spec.rb b/spec/unit/ipaddress_spec.rb index 62cbbc2723..674b74370f 100755 --- a/spec/unit/ipaddress_spec.rb +++ b/spec/unit/ipaddress_spec.rb @@ -45,10 +45,9 @@ def expect_ifconfig_parse(address, fixture) require 'facter/util/wmi' require 'facter/util/registry' require 'facter/util/ip/windows' + require 'facter_spec/windows_network' - let(:settingId0) { '{4AE6B55C-6DD6-427D-A5BB-13535D4BE926}' } - let(:settingId1) { '{38762816-7957-42AC-8DAA-3B08D0C857C7}' } - let(:nic_bindings) { ["\\Device\\#{settingId0}", "\\Device\\#{settingId1}" ] } + include FacterSpec::WindowsNetwork before :each do Facter.fact(:kernel).stubs(:value).returns(:windows) @@ -69,63 +68,45 @@ def expect_ifconfig_parse(address, fixture) context "when you have one network adapter" do it "should return the ip address properly" do - network1 = mock('network1') - network1.expects(:IPAddress).returns(["12.123.12.12", "2011:0:4137:9e76:2087:77a:53ef:7527"]) - Facter::Util::WMI.expects(:execquery).returns([network1]) + nic = given_a_valid_windows_nic_with_ipv4_and_ipv6 + Facter::Util::WMI.expects(:execquery).returns([nic]) - Facter.value(:ipaddress).should == "12.123.12.12" + Facter.value(:ipaddress).should == ipAddress0 end end context "when you have more than one network adapter" do it "should return the ip of the adapter with the lowest IP connection metric (best connection)" do - network1 = mock('network1') - network1.expects(:IPConnectionMetric).returns(10) - network2 = mock('network2') - network2.expects(:IPConnectionMetric).returns(5) - network2.expects(:IPAddress).returns(["12.123.12.13", "2013:0:4137:9e76:2087:77a:53ef:7527"]) - Facter::Util::WMI.expects(:execquery).returns([network1, network2]) - - Facter.value(:ipaddress).should == "12.123.12.13" + nics = given_two_valid_windows_nics_with_ipv4_and_ipv6 + nics[:nic1].expects(:IPConnectionMetric).returns(5) + Facter::Util::WMI.expects(:execquery).returns(nics.values) + + Facter.value(:ipaddress).should == ipAddress1 end it "should return the ip of the adapter with the lowest IP connection metric (best connection) that has ipv4 enabled" do - network1 = mock('network1') - network1.expects(:IPConnectionMetric).returns(10) - network1.expects(:IPAddress).returns(["12.123.12.12", "2011:0:4137:9e76:2087:77a:53ef:7527"]) - network2 = mock('network2') - network2.expects(:IPConnectionMetric).returns(5) - network2.expects(:IPAddress).returns(["2013:0:4137:9e76:2087:77a:53ef:7527"]) - Facter::Util::WMI.expects(:execquery).returns([network1, network2]) - - Facter.value(:ipaddress).should == "12.123.12.12" + nics = given_two_valid_windows_nics_with_ipv4_and_ipv6 + nics[:nic1].expects(:IPConnectionMetric).returns(5) + nics[:nic1].expects(:IPAddress).returns([ipv6Address1]) + Facter::Util::WMI.expects(:execquery).returns(nics.values) + + Facter.value(:ipaddress).should == ipAddress0 end context "when the IP connection metric is the same" do it "should return the ip of the adapter with the lowest binding order" do - network1 = mock('network1') - network1.expects(:SettingID).returns(settingId0) - network1.expects(:IPConnectionMetric).returns(5) - network1.expects(:IPAddress).returns(["12.123.12.12", "2011:0:4137:9e76:2087:77a:53ef:7527"]) - network2 = mock('network2') - network2.expects(:SettingID).returns(settingId1) - network2.expects(:IPConnectionMetric).returns(5) - Facter::Util::WMI.expects(:execquery).returns([network1, network2]) - - Facter.value(:ipaddress).should == "12.123.12.12" + nics = given_two_valid_windows_nics_with_ipv4_and_ipv6 + Facter::Util::WMI.expects(:execquery).returns(nics.values) + + Facter.value(:ipaddress).should == ipAddress0 end it "should return the ip of the adapter with the lowest binding order even if the adapter is not first" do - network1 = mock('network1') - network1.expects(:IPConnectionMetric).returns(5) - network1.expects(:SettingID).returns(settingId1) - network2 = mock('network2') - network2.expects(:IPConnectionMetric).returns(5) - network2.expects(:IPAddress).returns(["12.123.12.13", "2013:0:4137:9e76:2087:77a:53ef:7527"]) - network2.expects(:SettingID).returns(settingId0) - Facter::Util::WMI.expects(:execquery).returns([network1, network2]) - - Facter.value(:ipaddress).should == "12.123.12.13" + nics = given_two_valid_windows_nics_with_ipv4_and_ipv6 + Facter::Util::WMI.expects(:execquery).returns(nics.values) + Facter::Util::Registry.stubs(:hklm_read).returns(["\\Device\\#{settingId1}", "\\Device\\#{settingId0}" ]) + + Facter.value(:ipaddress).should == ipAddress1 end end end diff --git a/spec/unit/netmask_spec.rb b/spec/unit/netmask_spec.rb old mode 100644 new mode 100755 index 25896b3425..8e81cc87fe --- a/spec/unit/netmask_spec.rb +++ b/spec/unit/netmask_spec.rb @@ -30,10 +30,9 @@ context "on Windows" do require 'facter/util/wmi' require 'facter/util/registry' + require 'facter_spec/windows_network' - let(:settingId0) { '{4AE6B55C-6DD6-427D-A5BB-13535D4BE926}' } - let(:settingId1) { '{38762816-7957-42AC-8DAA-3B08D0C857C7}' } - let(:nic_bindings) { ["\\Device\\#{settingId0}", "\\Device\\#{settingId1}" ] } + include FacterSpec::WindowsNetwork before :each do Facter.fact(:kernel).stubs(:value).returns(:windows) @@ -50,52 +49,36 @@ describe "when you have one network adapter" do it "should return properly" do - network1 = mock('network1') - network1.expects(:IPSubnet).returns(["255.255.255.0", "48","2"]) - Facter::Util::WMI.expects(:execquery).returns([network1]) + nic = given_a_valid_windows_nic_with_ipv4_and_ipv6 + Facter::Util::WMI.expects(:execquery).returns([nic]) - Facter.value(:netmask).should == "255.255.255.0" + Facter.value(:netmask).should == subnet0 end end describe "when you have more than one network adapter" do it "should return the netmask of the adapter with the lowest IP connection metric (best connection)" do - network1 = mock('network1') - network1.expects(:IPConnectionMetric).returns(10) - network2 = mock('network2') - network2.expects(:IPConnectionMetric).returns(5) - network2.expects(:IPSubnet).returns(["255.255.0.0", "48","2"]) - Facter::Util::WMI.expects(:execquery).returns([network1, network2]) - - Facter.value(:netmask).should == "255.255.0.0" + nics = given_two_valid_windows_nics_with_ipv4_and_ipv6 + nics[:nic1].expects(:IPConnectionMetric).returns(5) + Facter::Util::WMI.expects(:execquery).returns(nics.values) + + Facter.value(:netmask).should == subnet1 end context "when the IP connection metric is the same" do it "should return the netmask of the adapter with the lowest binding order" do - network1 = mock('network1') - network1.expects(:SettingID).returns(settingId0) - network1.expects(:IPConnectionMetric).returns(5) - network1.expects(:IPSubnet).returns(["255.255.255.0", "48","64"]) - network2 = mock('network2') - network2.expects(:SettingID).returns(settingId1) - network2.expects(:IPConnectionMetric).returns(5) - - Facter::Util::WMI.expects(:execquery).returns([network1, network2]) + nics = given_two_valid_windows_nics_with_ipv4_and_ipv6 + Facter::Util::WMI.expects(:execquery).returns(nics.values) - Facter.value(:netmask).should =="255.255.255.0" + Facter.value(:netmask).should == subnet0 end it "should return the netmask of the adapter with the lowest binding even if the adapter is not first" do - network1 = mock('network1') - network1.expects(:SettingID).returns(settingId1) - network1.expects(:IPConnectionMetric).returns(5) - network2 = mock('network2') - network2.expects(:SettingID).returns(settingId0) - network2.expects(:IPConnectionMetric).returns(5) - network2.expects(:IPSubnet).returns(["255.255.0.0", "48","2"]) - Facter::Util::WMI.expects(:execquery).returns([network1, network2]) - - Facter.value(:netmask).should =="255.255.0.0" + nics = given_two_valid_windows_nics_with_ipv4_and_ipv6 + Facter::Util::WMI.expects(:execquery).returns(nics.values) + Facter::Util::Registry.stubs(:hklm_read).returns(["\\Device\\#{settingId1}", "\\Device\\#{settingId0}" ]) + + Facter.value(:netmask).should == subnet1 end end end diff --git a/spec/unit/util/macaddress_spec.rb b/spec/unit/util/macaddress_spec.rb index 863ed11796..17e55006de 100755 --- a/spec/unit/util/macaddress_spec.rb +++ b/spec/unit/util/macaddress_spec.rb @@ -85,10 +85,9 @@ context "on Windows" do require 'facter/util/wmi' require 'facter/util/registry' + require 'facter_spec/windows_network' - let(:settingId0) { '{4AE6B55C-6DD6-427D-A5BB-13535D4BE926}' } - let(:settingId1) { '{38762816-7957-42AC-8DAA-3B08D0C857C7}' } - let(:nic_bindings) { ["\\Device\\#{settingId0}", "\\Device\\#{settingId1}" ] } + include FacterSpec::WindowsNetwork before :each do Facter.fact(:kernel).stubs(:value).returns(:windows) @@ -105,49 +104,36 @@ describe "when you have one network adapter" do it "should return properly" do - network1 = mock('network1') - network1.expects(:MACAddress).returns("00:0C:29:0C:9E:9F") - Facter::Util::WMI.expects(:execquery).returns([network1]) + nic = given_a_valid_windows_nic_with_ipv4_and_ipv6 + Facter::Util::WMI.expects(:execquery).returns([nic]) - Facter.value(:macaddress).should == "00:0C:29:0C:9E:9F" + Facter.value(:macaddress).should == macAddress0 end end describe "when you have more than one network adapter" do it "should return the macaddress of the adapter with the lowest IP connection metric (best connection)" do - network1 = mock('network1') - network1.expects(:IPConnectionMetric).returns(10) - network2 = mock('network2') - network2.expects(:MACAddress).returns("00:0C:29:0C:9E:AF") - network2.expects(:IPConnectionMetric).returns(5) - Facter::Util::WMI.expects(:execquery).returns([network1, network2]) - - Facter.value(:macaddress).should == "00:0C:29:0C:9E:AF" + nics = given_two_valid_windows_nics_with_ipv4_and_ipv6 + nics[:nic1].expects(:IPConnectionMetric).returns(5) + Facter::Util::WMI.expects(:execquery).returns(nics.values) + + Facter.value(:macaddress).should == macAddress1 end context "when the IP connection metric is the same" do it "should return the macaddress of the adapter with the lowest binding order" do - network1 = mock('network1', :MACAddress => "23:24:df:12:12:00") - network1.expects(:SettingID).returns(settingId0) - network1.expects(:IPConnectionMetric).returns(5) - network2 = mock('network2') - network2.expects(:SettingID).returns(settingId1) - network2.expects(:IPConnectionMetric).returns(5) - Facter::Util::WMI.expects(:execquery).returns([network1, network2]) - - Facter.value(:macaddress).should == "23:24:df:12:12:00" + nics = given_two_valid_windows_nics_with_ipv4_and_ipv6 + Facter::Util::WMI.expects(:execquery).returns(nics.values) + + Facter.value(:macaddress).should == macAddress0 end it "should return the macaddress of the adapter with the lowest MACAddress when multiple adapters have the same IP connection metric when the lowest MACAddress is not first" do - network1 = stub('network1', :MACAddress => "23:24:df:12:12:00") - network1.expects(:SettingID).returns(settingId1) - network1.expects(:IPConnectionMetric).returns(5) - network2 = stub('network2', :MACAddress => "23:24:df:12:12:11") - network2.expects(:SettingID).returns(settingId0) - network2.expects(:IPConnectionMetric).returns(5) - Facter::Util::WMI.expects(:execquery).returns([network1, network2]) - - Facter.value(:macaddress).should == "23:24:df:12:12:11" + nics = given_two_valid_windows_nics_with_ipv4_and_ipv6 + Facter::Util::WMI.expects(:execquery).returns(nics.values) + Facter::Util::Registry.stubs(:hklm_read).returns(["\\Device\\#{settingId1}", "\\Device\\#{settingId0}" ]) + + Facter.value(:macaddress).should == macAddress1 end end end From aaebc4e395a09b20d64c6633269e7090c801806a Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Mon, 29 Jul 2013 14:21:09 -0500 Subject: [PATCH 1361/3753] (#16668) Ipv6 remove warning If someone does not have ipv6 installed or their machine doesn't have the ipv6 bindings in the registry, we should suppress any messages that result in not finding that key. --- lib/facter/util/ip/windows.rb | 3 +++ spec/unit/ipaddress6_spec.rb | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/lib/facter/util/ip/windows.rb b/lib/facter/util/ip/windows.rb index 42b4db7ce9..805a2bbcf8 100644 --- a/lib/facter/util/ip/windows.rb +++ b/lib/facter/util/ip/windows.rb @@ -157,6 +157,7 @@ def bindings require 'facter/util/registry' bindings = {} + Facter::Util::Registry.hklm_read(@key, 'Bind').each_with_index do |entry, index| match_data = entry.match(/\\Device\\(\{.*\})/) unless match_data.nil? @@ -165,6 +166,8 @@ def bindings end bindings + rescue + {} end end diff --git a/spec/unit/ipaddress6_spec.rb b/spec/unit/ipaddress6_spec.rb index 9173f90954..1c9153d288 100755 --- a/spec/unit/ipaddress6_spec.rb +++ b/spec/unit/ipaddress6_spec.rb @@ -74,6 +74,13 @@ def ifconfig_fixture(filename) end end + it "should return nil if the system doesn't have ipv6 installed", :if => Facter::Util::Config.is_windows? do + Facter::Util::Resolution.any_instance.expects(:warn).never + Facter::Util::Registry.stubs(:hklm_read).raises(Win32::Registry::Error, 2) + + Facter.value(:ipaddress6).should == nil + end + context "when you have one network adapter" do it "should return empty if ipv6 is not on" do nic = given_a_valid_windows_nic_with_ipv4_and_ipv6 From 3ade301829ee8121b86d5608fea91149f5a38f60 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Mon, 29 Jul 2013 15:52:46 -0700 Subject: [PATCH 1362/3753] (#16668) Backport WMI changes to per-interface facts Previously, facter was using WMI for the global interface facts, e.g. ipaddress, but still using netsh for the per-interface facts. This commit updates the per-interface facts to use the WMI methods. This commit is broken out in order to make merging up to master easier, since the code has been drastically modified there. --- lib/facter/util/ip.rb | 32 ++++++++++++++------------------ lib/facter/util/ip/windows.rb | 2 +- spec/unit/util/ip_spec.rb | 30 +++++++++--------------------- 3 files changed, 24 insertions(+), 40 deletions(-) diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index cb035e3b1f..c15a6a1a75 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -31,11 +31,7 @@ module Facter::Util::IP :macaddress => /(\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2}:\w{1,2})/, :netmask => /.*\s+netmask (\S+)\s.*/ }, - :windows => { - :ipaddress => /\s+IP Address:\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, - :ipaddress6 => /Address ((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/, - :netmask => /\s+Subnet Prefix:\s+\S+\s+\(mask ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\)/ - } + :windows => {} } # Convert an interface name into purely alphanumeric characters. @@ -61,11 +57,12 @@ def self.supported_platforms end def self.get_interfaces - return [] unless output = Facter::Util::IP.get_all_interface_output() + if Facter.value(:kernel) == 'windows' + require 'facter/util/ip/windows' + return Facter::Util::IP::Windows.interfaces + end - # windows interface names contain spaces and are quoted and can appear multiple - # times as ipv4 and ipv6 - return output.scan(/\s* connected\s*(\S.*)/).flatten.uniq if Facter.value(:kernel) == 'windows' + return [] unless output = Facter::Util::IP.get_all_interface_output() # Our regex appears to be stupid, in that it leaves colons sitting # at the end of interfaces. So, we have to trim those trailing @@ -89,9 +86,6 @@ def self.get_all_interface_output output.sub!(/^[^\n]*\n/, "") # delete the header line. output end - when 'windows' - output = %x|#{ENV['SYSTEMROOT']}/system32/netsh.exe interface ip show interface| - output += %x|#{ENV['SYSTEMROOT']}/system32/netsh.exe interface ipv6 show interface| end output end @@ -172,12 +166,9 @@ def self.hpux_lanscan def self.get_output_for_interface_and_label(interface, label) return get_single_interface_output(interface) unless Facter.value(:kernel) == 'windows' - if label == 'ipaddress6' - output = %x|#{ENV['SYSTEMROOT']}/system32/netsh.exe interface ipv6 show address \"#{interface}\"| - else - output = %x|#{ENV['SYSTEMROOT']}/system32/netsh.exe interface ip show address \"#{interface}\"| - end - output + require 'facter/util/ip/windows' + output = Facter::Util::IP::Windows.value_for_interface_and_label(interface, label) + output ? output : "" end def self.get_bonding_master(interface) @@ -220,6 +211,11 @@ def self.get_bonding_master(interface) # @return [String] representing the requested value. An empty array is # returned if the kernel is not supported by the REGEX_MAP constant. def self.get_interface_value(interface, label) + if Facter.value(:kernel) == 'windows' + require 'facter/util/ip/windows' + return Facter::Util::IP::Windows.value_for_interface_and_label(interface, label) + end + tmp1 = [] kernel = Facter.value(:kernel).downcase.to_sym diff --git a/lib/facter/util/ip/windows.rb b/lib/facter/util/ip/windows.rb index 805a2bbcf8..30c4c05797 100644 --- a/lib/facter/util/ip/windows.rb +++ b/lib/facter/util/ip/windows.rb @@ -1,6 +1,7 @@ # encoding: UTF-8 require 'facter/util/wmi' +require 'facter/util/ip' class Facter::Util::IP::Windows # The WMI query used to return ip information @@ -157,7 +158,6 @@ def bindings require 'facter/util/registry' bindings = {} - Facter::Util::Registry.hklm_read(@key, 'Bind').each_with_index do |entry, index| match_data = entry.match(/\\Device\\(\{.*\})/) unless match_data.nil? diff --git a/spec/unit/util/ip_spec.rb b/spec/unit/util/ip_spec.rb index 3d45c82c8f..67772b46b3 100755 --- a/spec/unit/util/ip_spec.rb +++ b/spec/unit/util/ip_spec.rb @@ -52,8 +52,8 @@ it "should return a list of only connected interfaces on Windows" do Facter.fact(:kernel).stubs(:value).returns("windows") - windows_netsh = my_fixture_read("windows_netsh_all_interfaces") - Facter::Util::IP.stubs(:get_all_interface_output).returns(windows_netsh) + + Facter::Util::IP::Windows.expects(:interfaces).returns(["Loopback Pseudo-Interface 1", "Local Area Connection", "Teredo Tunneling Pseudo-Interface"]) Facter::Util::IP.get_interfaces().should == ["Loopback Pseudo-Interface 1", "Local Area Connection", "Teredo Tunneling Pseudo-Interface"] end @@ -373,41 +373,29 @@ def self.hpux_examples end describe "on Windows" do + require 'facter/util/ip/windows' + before :each do Facter.stubs(:value).with(:kernel).returns("windows") end it "should return ipaddress information" do - windows_netsh = my_fixture_read("windows_netsh_single_interface") - - Facter::Util::IP.expects(:get_output_for_interface_and_label).with("Local Area Connection", "ipaddress").returns(windows_netsh) + Facter::Util::IP::Windows.expects(:value_for_interface_and_label).with("Local Area Connection", "ipaddress").returns('172.16.138.216') Facter::Util::IP.get_interface_value("Local Area Connection", "ipaddress").should == "172.16.138.216" end - it "should return a human readable netmask" do - windows_netsh = my_fixture_read("windows_netsh_single_interface") - - Facter::Util::IP.expects(:get_output_for_interface_and_label).with("Local Area Connection", "netmask").returns(windows_netsh) - - Facter::Util::IP.get_interface_value("Local Area Connection", "netmask").should == "255.255.255.0" - end - it "should return network information" do - windows_netsh = my_fixture_read("windows_netsh_single_interface") - - Facter::Util::IP.stubs(:get_output_for_interface_and_label).with("Local Area Connection", "ipaddress").returns(windows_netsh) - Facter::Util::IP.stubs(:get_output_for_interface_and_label).with("Local Area Connection", "netmask").returns(windows_netsh) + Facter::Util::IP::Windows.expects(:value_for_interface_and_label).with("Local Area Connection", "ipaddress").returns('172.16.138.216') + Facter::Util::IP::Windows.expects(:value_for_interface_and_label).with("Local Area Connection", "netmask").returns('255.255.255.0') Facter::Util::IP.get_network_value("Local Area Connection").should == "172.16.138.0" end it "should return ipaddress6 information" do - windows_netsh = my_fixture_read("windows_netsh_single_interface6") - - Facter::Util::IP.expects(:get_output_for_interface_and_label).with("Teredo Tunneling Pseudo-Interface", "ipaddress6").returns(windows_netsh) + Facter::Util::IP::Windows.expects(:value_for_interface_and_label).with("Local Area Connection", "ipaddress6").returns("2001:0:4137:9e76:2087:77a:53ef:7527") - Facter::Util::IP.get_interface_value("Teredo Tunneling Pseudo-Interface", "ipaddress6").should == "2001:0:4137:9e76:2087:77a:53ef:7527" + Facter::Util::IP.get_interface_value("Local Area Connection", "ipaddress6").should == "2001:0:4137:9e76:2087:77a:53ef:7527" end end From c128221fabe36a5d3b1dd2cf23daea105bd20b7e Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Tue, 30 Jul 2013 15:36:20 -0700 Subject: [PATCH 1363/3753] Maint: Don't output "No facts loaded from" during tests Previously, running `bundle exec rspec spec/unit/memory_spec.rb` would output the warning "No facts loaded from ...". The reason was because the memorytotal fact is defined in a file whose name doesn't match, and the code that forces facter to load the memory facts was within an inner describe block. See #7670 and commit 2255abee7b for more information about facter's quirky loading behavior. This commit just moves the `load(:memory)` before each to the outermost describe block. This ensures that all of the memory-derived facts are "preloaded" so facter doesn't print a warning that it can't find them. --- spec/unit/memory_spec.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/spec/unit/memory_spec.rb b/spec/unit/memory_spec.rb index 1771a53e20..e79f8d3745 100755 --- a/spec/unit/memory_spec.rb +++ b/spec/unit/memory_spec.rb @@ -3,15 +3,15 @@ require 'spec_helper' describe "Memory facts" do - after do + before(:each) do + Facter.collection.internal_loader.load(:memory) + end + + after(:each) do Facter.clear end describe "when returning scaled sizes" do - before(:each) do - Facter.collection.internal_loader.load(:memory) - end - [ "memorysize", "memoryfree", "swapsize", @@ -515,8 +515,8 @@ end it "should use the memorysize fact for the memorytotal fact" do - Facter.fact("memorysize").expects(:value).once.returns "yay" + Facter.fact("memorysize").expects(:value).once.returns "16.00 GB" Facter::Util::Resolution.expects(:exec).never - Facter.fact("memorytotal").value.should == "yay" + Facter.fact(:memorytotal).value.should == "16.00 GB" end end From 667c10c9047410e4fd10f1540ff377e536664c96 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Wed, 31 Jul 2013 16:45:29 -0700 Subject: [PATCH 1364/3753] Maint: Don't load dynamic facts prior to them being added Previously, running `rspec spec/unit/util/ip_spec.rb` would generate `No facts loaded from` messages, because the `when_interfaces_facts_...` method would try to load the interface-specific facts (eth0_mtu) before the parent `interfaces` fact had created them. This commit changes the method to evaluate the parent `interfaces` fact instead, which results in the dynamic facts being evaluated. It also refactors the test to not stub the private `parse!` method of the class we are trying to test. Instead we stub the IP subclass delegate instead. --- spec/unit/util/ip_spec.rb | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/spec/unit/util/ip_spec.rb b/spec/unit/util/ip_spec.rb index 910a4baf21..bb7ec73052 100755 --- a/spec/unit/util/ip_spec.rb +++ b/spec/unit/util/ip_spec.rb @@ -157,27 +157,27 @@ end def given_initial_interfaces_facts - model = described_class.new - model.stubs(:interfaces).returns(interfaces_hash.keys) - model.stubs(:parse!) - model.interfaces_hash = interfaces_hash - described_class.stubs(:new).returns(model) + stub_ip_facts(interfaces_hash) end def when_interfaces_facts_have_been_resolved_then_flushed - interfaces_hash.keys.each do |interface| - described_class::INTERFACE_KEYS.each do |attr| - Facter.value("#{attr}_#{interface}") - end - end + Facter.value(:interfaces) - model = described_class.new - model.stubs(:interfaces).returns(interfaces_hash2.keys) - model.stubs(:parse!) - model.interfaces_hash = interfaces_hash2 - described_class.stubs(:new).returns(model) + stub_ip_facts(interfaces_hash2) Facter.flush Facter.value(:interfaces) end + + def stub_ip_facts(intf_hash) + delegate = stub('ipsubclass') + delegate.stubs(:interfaces).returns(intf_hash.keys) + intf_hash.each_pair do |interface, values| + described_class::INTERFACE_KEYS.each do |label| + delegate.stubs(:value_for_interface_and_label).with(interface, label).returns(values[label]) + end + delegate.stubs(:network).with(interface).returns(values[:network]) + end + described_class.any_instance.stubs(:kernel_class).returns(delegate) + end end From 4e12130e7c8eb21306c3078527346546811160a1 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Wed, 31 Jul 2013 17:31:56 -0700 Subject: [PATCH 1365/3753] Maint: Clear memory facts now that they are loaded earlier The previous commit moved the preloading of memory related facts to the outermost describe block. When the inner describe block tried to preload the swapencrypted fact, it would fail on non-Darwin systems, because the stubs where never evaluated. This commit clears the facts in the inner describe block as is done for every other platform. --- spec/unit/memory_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/unit/memory_spec.rb b/spec/unit/memory_spec.rb index e79f8d3745..2de005e3f3 100755 --- a/spec/unit/memory_spec.rb +++ b/spec/unit/memory_spec.rb @@ -46,6 +46,7 @@ describe "on Darwin" do before(:each) do + Facter.clear Facter.fact(:kernel).stubs(:value).returns("Darwin") Facter::Util::Resolution.stubs(:exec).with('sysctl -n hw.memsize').returns('8589934592') sample_vm_stat = < Date: Tue, 30 Jul 2013 10:46:48 -0500 Subject: [PATCH 1366/3753] (#21738) Revert "(#2157) Remove support for executable external facts on Windows" This reverts commit 008cc6cdf79df1589c060e2a088caffc9cf6da77. Conflicts: lib/facter/util/directory_loader.rb lib/facter/util/parser.rb --- lib/facter/util/directory_loader.rb | 2 +- lib/facter/util/parser.rb | 37 +++++++++++++++++++++++++++-- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/lib/facter/util/directory_loader.rb b/lib/facter/util/directory_loader.rb index 5fc2af1749..d0db23d415 100644 --- a/lib/facter/util/directory_loader.rb +++ b/lib/facter/util/directory_loader.rb @@ -1,7 +1,7 @@ # A Facter plugin that loads external facts. # # Default Unix Directories: -# /etc/facter/facts.d, /etc/puppetlbas/facter/facts.d +# /etc/facter/facts.d", "/etc/puppetlabs/facter/facts.d" # # Default Windows Direcotires: # C:\ProgramData\Puppetlabs\facter\facts.d (2008) diff --git a/lib/facter/util/parser.rb b/lib/facter/util/parser.rb index 8f2db609cc..d03bc4fe66 100644 --- a/lib/facter/util/parser.rb +++ b/lib/facter/util/parser.rb @@ -125,12 +125,45 @@ def results end register(ScriptParser) do |filename| - if not Facter::Util::Config.is_windows? - File.executable?(filename) && File.file?(filename) + if Facter::Util::Config.is_windows? + extension_matches?(filename, %w{bat com exe}) + else + File.executable?(filename) end end + # Executes and parses the key value output of Powershell scripts + # + # Before you can run unsigned ps1 scripts it requires a change to execution + # policy: + # + # Set-ExecutionPolicy RemoteSigned -Scope LocalMachine + # Set-ExecutionPolicy RemoteSigned -Scope CurrentUser + # + class PowershellParser < Base + # Returns a hash of facts from powershell output + def results + shell_command = "powershell -File #{filename}" + output = Facter::Util::Resolution.exec(shell_command) + + result = {} + output.split("\n").each do |line| + if line =~ /^(.+)=(.+)$/ + result[$1] = $2 + end + end + result + rescue Exception => e + Facter.warn("Failed to handle #{filename} as powershell facts: #{e.class}: #{e}") + Facter.debug(e.backtrace.join("\n\t")) + end + end + + register(PowershellParser) do |filename| + Facter::Util::Config.is_windows? && extension_matches?(filename, "ps1") + end + # A parser that is used when there is no other parser that can handle the file # The return from results indicates to the caller the file was not parsed correctly. class NothingParser From 6423c0d7d56b80d0c0b88636470cf896d01b7f2b Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Tue, 30 Jul 2013 12:39:01 -0500 Subject: [PATCH 1367/3753] (#21738) Add .cmd / Update PowerShell parser This updates the PowerShell parser to add a switch for the Bypass execution policy which means that the execution policy will not need to be set prior to running facter. This also adds some switches (NoProfile, NoLogo, NonInteractive) that speed up the execution of external powershell facts. This also adds the extension .cmd as a valid file extension as part of the ScriptParser. This functions in the same way as .bat. --- lib/facter/util/parser.rb | 17 +++++------------ spec/unit/util/parser_spec.rb | 35 ++++++++++++++++++++++++++++++++--- 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/lib/facter/util/parser.rb b/lib/facter/util/parser.rb index d03bc4fe66..36f135824b 100644 --- a/lib/facter/util/parser.rb +++ b/lib/facter/util/parser.rb @@ -126,24 +126,17 @@ def results register(ScriptParser) do |filename| if Facter::Util::Config.is_windows? - extension_matches?(filename, %w{bat com exe}) + extension_matches?(filename, %w{bat cmd com exe}) && File.file?(filename) else - File.executable?(filename) + File.executable?(filename) && File.file?(filename) end end # Executes and parses the key value output of Powershell scripts - # - # Before you can run unsigned ps1 scripts it requires a change to execution - # policy: - # - # Set-ExecutionPolicy RemoteSigned -Scope LocalMachine - # Set-ExecutionPolicy RemoteSigned -Scope CurrentUser - # class PowershellParser < Base # Returns a hash of facts from powershell output def results - shell_command = "powershell -File #{filename}" + shell_command = "powershell -NoProfile -NonInteractive -NoLogo -ExecutionPolicy Bypass -File \"#{filename}\"" output = Facter::Util::Resolution.exec(shell_command) result = {} @@ -161,9 +154,9 @@ def results end register(PowershellParser) do |filename| - Facter::Util::Config.is_windows? && extension_matches?(filename, "ps1") + Facter::Util::Config.is_windows? && extension_matches?(filename, "ps1") && File.file?(filename) end - + # A parser that is used when there is no other parser that can handle the file # The return from results indicates to the caller the file was not parsed correctly. class NothingParser diff --git a/spec/unit/util/parser_spec.rb b/spec/unit/util/parser_spec.rb index be7b9e7f4d..00bba5d53a 100644 --- a/spec/unit/util/parser_spec.rb +++ b/spec/unit/util/parser_spec.rb @@ -1,7 +1,6 @@ #!/usr/bin/env ruby require 'spec_helper' - require 'facter/util/parser' require 'tempfile' require 'tmpdir.rb' @@ -121,12 +120,42 @@ Facter::Util::Parser.parser_for(cmd) end - it "should return no results" do - parser.results.should be_nil + it "should not parse a directory" do + File.stubs(:file?).with(cmd).returns(false) + Facter::Util::Parser.parser_for(cmd).results.should be_nil + end + + it "should return the data properly" do + File.stubs(:file?).with(cmd).returns(true) + parser.results.should == data end end end + describe "powershell parser" do + let :ps1 do "/tmp/foo.ps1" end + let :data_in_ps1 do "one=two\nthree=four\n" end + + before :each do + Facter::Util::Config.stubs(:is_windows?).returns(true) + Facter::Util::Resolution.stubs(:exec).returns(data_in_ps1) + end + + let :parser do + Facter::Util::Parser.parser_for(ps1) + end + + it "should not parse a directory" do + File.stubs(:file?).with(ps1).returns(false) + Facter::Util::Parser.parser_for(ps1).results.should be_nil + end + + it "should return data properly" do + File.stubs(:file?).with(ps1).returns(true) + parser.results.should == data + end + end + describe "nothing parser" do it "uses the nothing parser when there is no other parser" do Facter::Util::Parser.parser_for("this.is.not.valid").results.should be_nil From fc8b1446c31ba595421bb8b039504835bf48172e Mon Sep 17 00:00:00 2001 From: Jasper Lievisse Adriaanse Date: Sun, 28 Jul 2013 14:51:40 +0200 Subject: [PATCH 1368/3753] Add Facter::Util::POSIX for a consistent way of dealing with sysctl(7). --- lib/facter/kernelrelease.rb | 3 ++- lib/facter/memory.rb | 16 ++++++++-------- lib/facter/physicalprocessorcount.rb | 4 +++- lib/facter/processor.rb | 13 ++++++++++--- lib/facter/util/blockdevices/freebsd.rb | 4 +++- lib/facter/util/manufacturer.rb | 5 +++-- lib/facter/util/memory.rb | 8 +++++--- lib/facter/util/posix.rb | 16 ++++++++++++++++ lib/facter/util/virtual.rb | 3 ++- lib/facter/virtual.rb | 2 +- spec/unit/blockdevices_spec.rb | 2 +- spec/unit/kernelrelease_spec.rb | 4 ++-- spec/unit/memory_spec.rb | 20 ++++++++++---------- spec/unit/physicalprocessorcount_spec.rb | 3 ++- spec/unit/processor_spec.rb | 12 +++++++----- spec/unit/util/virtual_spec.rb | 4 ++-- spec/unit/virtual_spec.rb | 8 ++++---- 17 files changed, 81 insertions(+), 46 deletions(-) create mode 100644 lib/facter/util/posix.rb diff --git a/lib/facter/kernelrelease.rb b/lib/facter/kernelrelease.rb index 7901cc9b62..60d8a67e8a 100644 --- a/lib/facter/kernelrelease.rb +++ b/lib/facter/kernelrelease.rb @@ -10,6 +10,7 @@ # # Caveats: # +require 'facter/util/posix' Facter.add(:kernelrelease) do setcode 'uname -r' @@ -23,7 +24,7 @@ Facter.add("kernelrelease") do confine :kernel => :openbsd setcode do - Facter::Util::Resolution.exec("/sbin/sysctl -n kern.version").split(' ')[1] + Facter::Util::POSIX.sysctl("kern.version").split(' ')[1] end end diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb index 5b0fe56b1f..6091ffad0e 100644 --- a/lib/facter/memory.rb +++ b/lib/facter/memory.rb @@ -85,7 +85,7 @@ Facter.add("SwapEncrypted") do confine :kernel => :openbsd setcode do - sysctl_encrypted = Facter::Util::Resolution.exec("sysctl -n vm.swapencrypt.enable").to_i + sysctl_encrypted = Facter::Util::POSIX.sysctl("vm.swapencrypt.enable").to_i !(sysctl_encrypted.zero?) end end @@ -93,7 +93,7 @@ Facter.add("SwapEncrypted") do confine :kernel => :Darwin setcode do - swap = Facter::Util::Resolution.exec('sysctl vm.swapusage') + swap = Facter::Util::POSIX.sysctl('vm.swapusage') encrypted = false if swap =~ /\(encrypted\)/ then encrypted = true; end encrypted @@ -149,8 +149,8 @@ Facter.add("swapsize_mb") do confine :kernel => :dragonfly setcode do - page_size = Facter::Util::Resolution.exec("/sbin/sysctl -n hw.pagesize").to_f - swaptotal = Facter::Util::Resolution.exec("/sbin/sysctl -n vm.swap_size").to_f * page_size + page_size = Facter::Util::POSIX.sysctl("hw.pagesize").to_f + swaptotal = Facter::Util::POSIX.sysctl("vm.swap_size").to_f * page_size "%.2f" % [(swaptotal.to_f / 1024.0) / 1024.0] end end @@ -158,10 +158,10 @@ Facter.add("swapfree_mb") do confine :kernel => :dragonfly setcode do - page_size = Facter::Util::Resolution.exec("/sbin/sysctl -n hw.pagesize").to_f - swaptotal = Facter::Util::Resolution.exec("/sbin/sysctl -n vm.swap_size").to_f * page_size - swap_anon_use = Facter::Util::Resolution.exec("/sbin/sysctl -n vm.swap_anon_use").to_f * page_size - swap_cache_use = Facter::Util::Resolution.exec("/sbin/sysctl -n vm.swap_cache_use").to_f * page_size + page_size = Facter::Util::POSIX.sysctl("hw.pagesize").to_f + swaptotal = Facter::Util::POSIX.sysctl("vm.swap_size").to_f * page_size + swap_anon_use = Facter::Util::POSIX.sysctl("vm.swap_anon_use").to_f * page_size + swap_cache_use = Facter::Util::POSIX.sysctl("vm.swap_cache_use").to_f * page_size swapfree = swaptotal - swap_anon_use - swap_cache_use "%.2f" % [(swapfree.to_f / 1024.0) / 1024.0] end diff --git a/lib/facter/physicalprocessorcount.rb b/lib/facter/physicalprocessorcount.rb index 36af4c4ac3..c8917806bf 100644 --- a/lib/facter/physicalprocessorcount.rb +++ b/lib/facter/physicalprocessorcount.rb @@ -9,6 +9,8 @@ # # Caveats: # +require 'facter/util/posix' + Facter.add('physicalprocessorcount') do confine :kernel => :linux @@ -83,6 +85,6 @@ Facter.add('physicalprocessorcount') do confine :kernel => :openbsd setcode do - Facter::Util::Resolution.exec("sysctl -n hw.ncpufound") + Facter::Util::POSIX.sysctl("hw.ncpufound") end end diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb index 093cd857d1..d4a3b13831 100644 --- a/lib/facter/processor.rb +++ b/lib/facter/processor.rb @@ -21,6 +21,7 @@ require 'thread' require 'facter/util/processor' +require 'facter/util/posix' # We have to enumerate these outside a Facter.add block to get the processorN # descriptions iteratively (but we need them inside the Facter.add block above @@ -108,7 +109,9 @@ Facter.add("ProcessorCount") do confine :kernel => :Darwin - setcode "sysctl -n hw.ncpu" + setcode do + Facter::Util::POSIX.sysctl("hw.ncpu") + end end if Facter.value(:kernel) == "windows" @@ -151,12 +154,16 @@ Facter.add("Processor") do confine :kernel => [:dragonfly,:freebsd] - setcode "sysctl -n hw.model" + setcode do + Facter::Util::POSIX.sysctl("hw.model") + end end Facter.add("ProcessorCount") do confine :kernel => [:dragonfly,:freebsd,:openbsd] - setcode "sysctl -n hw.ncpu" + setcode do + Facter::Util::POSIX.sysctl("hw.ncpu") + end end Facter.add("ProcessorCount") do diff --git a/lib/facter/util/blockdevices/freebsd.rb b/lib/facter/util/blockdevices/freebsd.rb index 0a3d6b98d0..c3b5d96b03 100644 --- a/lib/facter/util/blockdevices/freebsd.rb +++ b/lib/facter/util/blockdevices/freebsd.rb @@ -1,3 +1,5 @@ +require 'facter/util/posix' + module Facter::Util::Blockdevices module FreeBSD @@ -19,7 +21,7 @@ def self.device_size(device_name) end def self.devices - Facter::Util::Resolution.exec('/sbin/sysctl -n kern.disks').split(' ').sort + Facter::Util::POSIX.sysctl('kern.disks').split(' ').sort end private diff --git a/lib/facter/util/manufacturer.rb b/lib/facter/util/manufacturer.rb index 5da7a917fe..f319023596 100644 --- a/lib/facter/util/manufacturer.rb +++ b/lib/facter/util/manufacturer.rb @@ -1,8 +1,9 @@ # mamufacturer.rb # Support methods for manufacturer specific facts -module Facter::Manufacturer +require 'facter/util/posix' +module Facter::Manufacturer def self.get_dmi_table() case Facter.value(:kernel) when 'Linux', 'GNU/kFreeBSD' @@ -54,7 +55,7 @@ def self.sysctl_find_system_info(name) name.each do |sysctlkey,facterkey| Facter.add(facterkey) do confine :kernel => [:openbsd, :darwin] - setcode "sysctl -n #{sysctlkey} 2>/dev/null" + setcode Facter::Util::POSIX.sysctl(sysctlkey) end end end diff --git a/lib/facter/util/memory.rb b/lib/facter/util/memory.rb index 0b4e511960..a084942bb7 100644 --- a/lib/facter/util/memory.rb +++ b/lib/facter/util/memory.rb @@ -2,6 +2,8 @@ ## Support module for memory related facts ## +require 'facter/util/posix' + module Facter::Memory def self.meminfo_number(tag) memsize = "" @@ -112,9 +114,9 @@ def self.mem_size(kernel = Facter.value(:kernel)) def self.mem_size_info(kernel = Facter.value(:kernel)) case kernel when /Dragonfly/i, /FreeBSD/i, /OpenBSD/i - Facter::Util::Resolution.exec("sysctl -n hw.physmem") + Facter::Util::POSIX.sysctl("hw.physmem") when /Darwin/i - Facter::Util::Resolution.exec("sysctl -n hw.memsize") + Facter::Util::POSIX.sysctl("hw.memsize") when /AIX/i if Facter::Util::Resolution.exec("/usr/bin/svmon -O unit=KB") =~ /^memory\s+(\d+)\s+/ $1 @@ -152,7 +154,7 @@ def self.swap_info(kernel = Facter.value(:kernel)) when /FreeBSD/i Facter::Util::Resolution.exec('swapinfo -k') when /Darwin/i - Facter::Util::Resolution.exec('sysctl vm.swapusage') + Facter::Util::POSIX.sysctl('vm.swapusage') when /SunOS/i Facter::Util::Resolution.exec('/usr/sbin/swap -l') end diff --git a/lib/facter/util/posix.rb b/lib/facter/util/posix.rb new file mode 100644 index 0000000000..dfcca557b8 --- /dev/null +++ b/lib/facter/util/posix.rb @@ -0,0 +1,16 @@ +module Facter +module Util +module POSIX + # Provides a consistent way of invoking sysctl(8) across POSIX platforms + # + # @param mib [String] the sysctl(8) MIB name + # + # @api private + def sysctl(mib) + Facter::Util::Resolution.exec("/sbin/sysctl -n #{mib} 2>/dev/null") + end + + module_function :sysctl +end +end +end diff --git a/lib/facter/util/virtual.rb b/lib/facter/util/virtual.rb index 5da853cb46..973ac4410c 100644 --- a/lib/facter/util/virtual.rb +++ b/lib/facter/util/virtual.rb @@ -1,3 +1,4 @@ +require 'facter/util/posix' require 'facter/util/file_read' module Facter::Util::Virtual @@ -92,7 +93,7 @@ def self.kvm? txt = if FileTest.exists?("/proc/cpuinfo") File.read("/proc/cpuinfo") elsif ["FreeBSD", "OpenBSD"].include? Facter.value(:kernel) - Facter::Util::Resolution.exec("/sbin/sysctl -n hw.model") + Facter::Util::POSIX.sysctl("hw.model") end if txt =~ /QEMU Virtual CPU/ then true elsif txt =~ /Common KVM processor/ then true diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 1c18bd3616..88ae6e46a3 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -86,7 +86,7 @@ confine :kernel => 'OpenBSD' setcode do - output = Facter::Util::Resolution.exec('sysctl -n hw.product 2>/dev/null') + output = Facter::Util::POSIX.sysctl("hw.product") Facter::Util::Virtual.parse_virtualization(output) end end diff --git a/spec/unit/blockdevices_spec.rb b/spec/unit/blockdevices_spec.rb index 0061b92e45..081447475f 100644 --- a/spec/unit/blockdevices_spec.rb +++ b/spec/unit/blockdevices_spec.rb @@ -19,7 +19,7 @@ before :each do Facter.fact(:kernel).stubs(:value).returns("FreeBSD") - Facter::Util::Resolution.stubs(:exec).with("/sbin/sysctl -n kern.disks").returns("cd0 da0 ada0 ad10 mfi0") + Facter::Util::POSIX.stubs(:sysctl).with("kern.disks").returns("cd0 da0 ada0 ad10 mfi0") Facter::Util::Resolution.stubs(:exec).with("/sbin/camcontrol inquiry cd0 -D").returns("pass0: Removable CD-ROM SCSI-0 device") Facter::Util::Resolution.stubs(:exec).with("/sbin/camcontrol inquiry da0 -D").returns("pass1: Fixed Direct Access SCSI-3 device") diff --git a/spec/unit/kernelrelease_spec.rb b/spec/unit/kernelrelease_spec.rb index e803e182c2..f7ceb6887b 100644 --- a/spec/unit/kernelrelease_spec.rb +++ b/spec/unit/kernelrelease_spec.rb @@ -57,12 +57,12 @@ end it 'parses 5.3-current sysctl output' do - Facter::Util::Resolution.expects(:exec).with("/sbin/sysctl -n kern.version").returns(my_fixture_read('openbsd-5.3-current')) + Facter::Util::POSIX.stubs(:sysctl).with("kern.version").returns(my_fixture_read('openbsd-5.3-current')) Facter.value(:kernelrelease).should == '5.3-current' end it 'parses 5.3 sysctl output' do - Facter::Util::Resolution.expects(:exec).with("/sbin/sysctl -n kern.version").returns(my_fixture_read('openbsd-5.3')) + Facter::Util::POSIX.stubs(:sysctl).with("kern.version").returns(my_fixture_read('openbsd-5.3')) Facter.value(:kernelrelease).should == '5.3' end end diff --git a/spec/unit/memory_spec.rb b/spec/unit/memory_spec.rb index 418316a3c1..647d8fca8c 100755 --- a/spec/unit/memory_spec.rb +++ b/spec/unit/memory_spec.rb @@ -47,9 +47,9 @@ describe "on Darwin" do before(:each) do Facter.fact(:kernel).stubs(:value).returns("Darwin") - Facter::Util::Resolution.stubs(:exec).with('sysctl -n hw.memsize').returns('8589934592') + Facter::Util::POSIX.stubs(:sysctl).with('hw.memsize').returns('8589934592') Facter::Util::Resolution.stubs(:exec).with('vm_stat').returns(my_fixture_read('darwin-vm_stat')) - Facter::Util::Resolution.stubs(:exec).with('sysctl vm.swapusage').returns("vm.swapusage: total = 64.00M used = 1.00M free = 63.00M (encrypted)") + Facter::Util::POSIX.stubs(:sysctl).with('vm.swapusage').returns("vm.swapusage: total = 64.00M used = 1.00M free = 63.00M (encrypted)") Facter.collection.internal_loader.load(:memory) end @@ -183,9 +183,9 @@ Facter::Util::Resolution.stubs(:exec).with('vmstat').returns(my_fixture_read('openbsd-vmstat')) - Facter::Util::Resolution.stubs(:exec).with('sysctl -n hw.physmem').returns('267321344') + Facter::Util::POSIX.stubs(:sysctl).with('hw.physmem').returns('267321344') - Facter::Util::Resolution.stubs(:exec).with('sysctl -n vm.swapencrypt.enable').returns('1') + Facter::Util::POSIX.stubs(:sysctl).with('vm.swapencrypt.enable').returns('1') Facter.collection.internal_loader.load(:memory) end @@ -307,14 +307,14 @@ Facter.fact(:kernel).stubs(:value).returns("dragonfly") swapusage = "total: 148342k bytes allocated = 0k used, 148342k available" - Facter::Util::Resolution.stubs(:exec).with('/sbin/sysctl -n hw.pagesize').returns("4096") - Facter::Util::Resolution.stubs(:exec).with('/sbin/sysctl -n vm.swap_size').returns("128461") - Facter::Util::Resolution.stubs(:exec).with('/sbin/sysctl -n vm.swap_anon_use').returns("2635") - Facter::Util::Resolution.stubs(:exec).with('/sbin/sysctl -n vm.swap_cache_use').returns("0") + Facter::Util::POSIX.stubs(:sysctl).with('hw.pagesize').returns("4096") + Facter::Util::POSIX.stubs(:sysctl).with('vm.swap_size').returns("128461") + Facter::Util::POSIX.stubs(:sysctl).with('vm.swap_anon_use').returns("2635") + Facter::Util::POSIX.stubs(:sysctl).with('vm.swap_cache_use').returns("0") Facter::Util::Resolution.stubs(:exec).with('vmstat').returns my_fixture_read('dragonfly-vmstat') - Facter::Util::Resolution.stubs(:exec).with("sysctl -n hw.physmem").returns('248512512') + Facter::Util::POSIX.stubs(:sysctl).with("hw.physmem").returns('248512512') Facter.collection.internal_loader.load(:memory) end @@ -346,7 +346,7 @@ Facter.fact(:kernel).stubs(:value).returns("FreeBSD") Facter::Util::Resolution.stubs(:exec).with('vmstat -H').returns my_fixture_read('freebsd-vmstat') - Facter::Util::Resolution.stubs(:exec).with('sysctl -n hw.physmem').returns '1056276480' + Facter::Util::POSIX.stubs(:sysctl).with('hw.physmem').returns '1056276480' end after(:each) do diff --git a/spec/unit/physicalprocessorcount_spec.rb b/spec/unit/physicalprocessorcount_spec.rb index f05fc0ab4b..edd9127f92 100755 --- a/spec/unit/physicalprocessorcount_spec.rb +++ b/spec/unit/physicalprocessorcount_spec.rb @@ -1,6 +1,7 @@ #! /usr/bin/env ruby require 'spec_helper' +require 'facter/util/posix' describe "Physical processor count facts" do @@ -144,7 +145,7 @@ describe "on openbsd" do it "should return 4 physical CPUs" do Facter.fact(:kernel).stubs(:value).returns("OpenBSD") - Facter::Util::Resolution.expects(:exec).with("sysctl -n hw.ncpufound").returns("4") + Facter::Util::POSIX.expects(:sysctl).with("hw.ncpufound").returns("4") Facter.fact(:physicalprocessorcount).value.should == "4" end end diff --git a/spec/unit/processor_spec.rb b/spec/unit/processor_spec.rb index 0c1a70d3e0..ecf81121cc 100755 --- a/spec/unit/processor_spec.rb +++ b/spec/unit/processor_spec.rb @@ -1,5 +1,6 @@ #! /usr/bin/env ruby +require 'facter/util/posix' require 'facter/util/processor' require 'spec_helper' @@ -202,38 +203,39 @@ def sysfs_cpu_stubs(count) it "should be 2 on dual-processor Darwin box" do Facter.fact(:kernel).stubs(:value).returns("Darwin") - Facter::Util::Resolution.stubs(:exec).with("sysctl -n hw.ncpu").returns('2') + Facter::Util::POSIX.stubs(:sysctl).with("hw.ncpu").returns('2') Facter.fact(:processorcount).value.should == "2" end it "should be 2 on dual-processor OpenBSD box" do Facter.fact(:kernel).stubs(:value).returns("OpenBSD") - Facter::Util::Resolution.stubs(:exec).with("sysctl -n hw.ncpu").returns('2') + Facter::Util::POSIX.stubs(:sysctl).with("hw.ncpu").returns('2') Facter.fact(:processorcount).value.should == "2" end it "should be 2 on dual-processor FreeBSD box" do Facter.fact(:kernel).stubs(:value).returns("FreeBSD") - Facter::Util::Resolution.stubs(:exec).with("sysctl -n hw.ncpu").returns('2') + Facter::Util::POSIX.stubs(:sysctl).with("hw.ncpu").returns('2') Facter.fact(:processorcount).value.should == "2" end it "should print the correct CPU Model on FreeBSD" do Facter.fact(:kernel).stubs(:value).returns("FreeBSD") - Facter::Util::Resolution.stubs(:exec).with("sysctl -n hw.model").returns('SomeVendor CPU 3GHz') + Facter::Util::POSIX.stubs(:sysctl).with("hw.model").returns('SomeVendor CPU 3GHz') Facter.fact(:processor).value.should == "SomeVendor CPU 3GHz" end it "should be 2 on dual-processor DragonFly box" do Facter.fact(:kernel).stubs(:value).returns("DragonFly") - Facter::Util::Resolution.stubs(:exec).with("sysctl -n hw.ncpu").returns('2') + Facter::Util::POSIX.stubs(:sysctl).with("hw.ncpu").returns('2') Facter.fact(:processorcount).value.should == "2" end + describe "on solaris" do before :all do @fixture_kstat_sparc = File.read(fixtures('processorcount','solaris-sparc-kstat-cpu-info')) diff --git a/spec/unit/util/virtual_spec.rb b/spec/unit/util/virtual_spec.rb index 660dc5455a..b8903e04a8 100755 --- a/spec/unit/util/virtual_spec.rb +++ b/spec/unit/util/virtual_spec.rb @@ -229,14 +229,14 @@ it "should detect kvm on FreeBSD" do FileTest.stubs(:exists?).with("/proc/cpuinfo").returns(false) Facter.fact(:kernel).stubs(:value).returns("FreeBSD") - Facter::Util::Resolution.stubs(:exec).with("/sbin/sysctl -n hw.model").returns("QEMU Virtual CPU version 0.12.4") + Facter::Util::POSIX.stubs(:sysctl).with("hw.model").returns("QEMU Virtual CPU version 0.12.4") Facter::Util::Virtual.should be_kvm end it "should detect kvm on OpenBSD" do FileTest.stubs(:exists?).with("/proc/cpuinfo").returns(false) Facter.fact(:kernel).stubs(:value).returns("OpenBSD") - Facter::Util::Resolution.stubs(:exec).with("/sbin/sysctl -n hw.model").returns('QEMU Virtual CPU version (cpu64-rhel6) ("AuthenticAMD" 686-class, 512KB L2 cache)') + Facter::Util::POSIX.stubs(:sysctl).with("hw.model").returns('QEMU Virtual CPU version (cpu64-rhel6) ("AuthenticAMD" 686-class, 512KB L2 cache)') Facter::Util::Virtual.should be_kvm end diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index 4e701b08c3..94556c63aa 100755 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -265,22 +265,22 @@ end it "should be parallels with Parallels product name from sysctl" do - Facter::Util::Resolution.stubs(:exec).with('sysctl -n hw.product 2>/dev/null').returns("Parallels Virtual Platform") + Facter::Util::POSIX.stubs(:sysctl).with('hw.product').returns("Parallels Virtual Platform") Facter.fact(:virtual).value.should == "parallels" end it "should be vmware with VMware product name from sysctl" do - Facter::Util::Resolution.stubs(:exec).with('sysctl -n hw.product 2>/dev/null').returns("VMware Virtual Platform") + Facter::Util::POSIX.stubs(:sysctl).with('hw.product').returns("VMware Virtual Platform") Facter.fact(:virtual).value.should == "vmware" end it "should be virtualbox with VirtualBox product name from sysctl" do - Facter::Util::Resolution.stubs(:exec).with('sysctl -n hw.product 2>/dev/null').returns("VirtualBox") + Facter::Util::POSIX.stubs(:sysctl).with('hw.product').returns("VirtualBox") Facter.fact(:virtual).value.should == "virtualbox" end it "should be xenhvm with Xen HVM product name from sysctl" do - Facter::Util::Resolution.stubs(:exec).with('sysctl -n hw.product 2>/dev/null').returns("HVM domU") + Facter::Util::POSIX.stubs(:sysctl).with('hw.product').returns("HVM domU") Facter.fact(:virtual).value.should == "xenhvm" end end From 2b2f36edbbdd3712956cead24e35e2995df35bf5 Mon Sep 17 00:00:00 2001 From: Mark Zeren Date: Sun, 4 Aug 2013 18:48:02 -0700 Subject: [PATCH 1369/3753] (#22060) Revert "Faster MAC address load on Linux via sysfs" This reverts commit 4b926d0df3bb02a0492908fd13479e0a148ae19e. Author: Daniel Pittman daniel@puppetlabs.com Date: Thu Jul 19 19:11:46 2012 While running `puppet agent --no-daemonize --debug`, and IFF somewhere in the code I have $DEBUG = true, then if I Ctrl-C to kill the agent I see the following exception: Exception `NilClass' at .../facter/lib/facter/macaddress.rb:19 - undefined local variable or method `path' for :Facter::Util::Resolution This is followed by an "infinite" recursion / stackoverflow which impeeds debugging other issues. The enhancement introduced in 4b926d0 never actually took effect because this exception was normally ignored causing the new (faster) MAC detection code to always return nil. Spec tests were never written for this enhancement nor for its interaction with ifconfig based MAC detection. Thus the safest path forward is to just revert the offending commit. --- lib/facter/macaddress.rb | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/lib/facter/macaddress.rb b/lib/facter/macaddress.rb index 3bcc92751a..3d0dffa8df 100644 --- a/lib/facter/macaddress.rb +++ b/lib/facter/macaddress.rb @@ -10,19 +10,6 @@ require 'facter/util/macaddress' require 'facter/util/ip' -Facter.add(:macaddress) do - confine :kernel => 'Linux' - has_weight 10 # about an order of magnitude faster - setcode do - begin - Dir.glob('/sys/class/net/*').reject {|x| x[-3..-1] == '/lo' }.first - path and File.read(path + '/address') - rescue Exception - nil - end - end -end - Facter.add(:macaddress) do confine :kernel => 'Linux' setcode do From 26a7691eb7399d791e8a12b4efa1f55f14826ec9 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Mon, 5 Aug 2013 16:48:40 -0500 Subject: [PATCH 1370/3753] maint: Refactor Key Value Output Parsers This extracts parsing the output of a command to a common method shared between powershellparser, textparser and scriptparser and cleans up the methods to take advantage of the same parsing technique. --- lib/facter/util/parser.rb | 47 +++++++++++++-------------------------- 1 file changed, 16 insertions(+), 31 deletions(-) diff --git a/lib/facter/util/parser.rb b/lib/facter/util/parser.rb index 36f135824b..4596c8fbdb 100644 --- a/lib/facter/util/parser.rb +++ b/lib/facter/util/parser.rb @@ -66,6 +66,19 @@ def parse_results end end + module KeyValuePairOutputFormat + def self.parse(output) + result = {} + re = /^(.+?)=(.+)$/ + output.each_line do |line| + if match_data = re.match(line.chomp) + result[match_data[1]] = match_data[2] + end + end + result + end + end + class YamlParser < Base def parse_results YAML.load(content) @@ -78,14 +91,7 @@ def parse_results class TextParser < Base def parse_results - re = /^(.+?)=(.+)$/ - result = {} - content.each_line do |line| - if match_data = re.match(line.chomp) - result[match_data[1]] = match_data[2] - end - end - result + KeyValuePairOutputFormat.parse content end end @@ -111,16 +117,7 @@ def results class ScriptParser < Base def results - output = Facter::Util::Resolution.exec(filename) - - result = {} - re = /^(.+)=(.+)$/ - output.each_line do |line| - if match_data = re.match(line.chomp) - result[match_data[1]] = match_data[2] - end - end - result + KeyValuePairOutputFormat.parse Facter::Util::Resolution.exec(filename) end end @@ -137,19 +134,7 @@ class PowershellParser < Base # Returns a hash of facts from powershell output def results shell_command = "powershell -NoProfile -NonInteractive -NoLogo -ExecutionPolicy Bypass -File \"#{filename}\"" - output = Facter::Util::Resolution.exec(shell_command) - - result = {} - output.split("\n").each do |line| - if line =~ /^(.+)=(.+)$/ - result[$1] = $2 - end - end - - result - rescue Exception => e - Facter.warn("Failed to handle #{filename} as powershell facts: #{e.class}: #{e}") - Facter.debug(e.backtrace.join("\n\t")) + KeyValuePairOutputFormat.parse Facter::Util::Resolution.exec(shell_command) end end From bdd793a6a0503a7298f9439b5112ca2282e82cab Mon Sep 17 00:00:00 2001 From: Clay Caviness Date: Thu, 8 Aug 2013 14:21:18 -0400 Subject: [PATCH 1371/3753] 21760/22005 update preflight to delete old lib dir In order to install in the top-level site_ruby directory, we need to clean out any installs in the old site_ruby/1.8 location. We're doing this by setting a variable in the package:apple task. If it's set, we remove all files there as well as files in the new location. While I'm here: * update erb to strip leading and trailing spaces * validate critical variables so a script is never generated with dangerous paths * only target top-level files/dirs in lib, since we're doing rm -RF anyway --- ext/osx/preflight.erb | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/ext/osx/preflight.erb b/ext/osx/preflight.erb index 3cb271a8c0..6e185f83f5 100755 --- a/ext/osx/preflight.erb +++ b/ext/osx/preflight.erb @@ -8,23 +8,31 @@ # ${3} is the destination volume so that this works correctly # when being installed to volumes other than the current OS. -<% begin %> -<% require 'rubygems' %> -<% rescue LoadError %> -<% end %> -<% require 'rake' %> +<%- begin -%> + <%- require 'rubygems' -%> +<%- rescue LoadError -%> +<%- end -%> +<%- require 'rake' -%> + +<%- ["@apple_libdir", "@apple_sbindir", "@apple_bindir", "@apple_docdir", "@package_name"].each do |i| -%> + <%- val = eval(i) -%> + <%- raise "Critical variable #{i} is unset!" if val.nil? or val.empty? -%> +<%- end -%> + +# remove ruby library files +<%- Dir.chdir("lib") -%> +<%- [@apple_old_libdir, @apple_libdir].select {|i| i}.each do |libdir| -%> + <%- FileList["*"].each do |file| -%> +/bin/rm -Rf "${3}<%= libdir %>/<%= file %>" + <%- end -%> +<%- end -%> -# remove libdir -<% Dir.chdir("lib") %> -<% FileList["**/*"].select {|i| File.file?(i)}.each do |file| %> -/bin/rm -Rf "${3}<%= @apple_libdir %>/<%=file%>" -<% end %> # remove bin files -<% Dir.chdir("../bin") %> -<% FileList["**/*"].select {|i| File.file?(i)}.each do |file| %> -/bin/rm -Rf "${3}<%= @apple_bindir %>/<%=file%>" -<% end %> -<% Dir.chdir("..") %> +<%- Dir.chdir("../bin") -%> +<%- FileList["*"].each do |file| -%> +/bin/rm -Rf "${3}<%= @apple_bindir %>/<%= file %>" +<%- end -%> +<%- Dir.chdir("..") -%> # remove old doc files -/bin/rm -Rf "${3}<%= @apple_docdir %>/<%=@package_name%>" +/bin/rm -Rf "${3}<%= @apple_docdir %>/<%= @package_name %>" From a3b02c6fd274e7145e2ae99e235cb45ec2ab084d Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 8 Aug 2013 14:18:39 -0700 Subject: [PATCH 1372/3753] (maint) explicitly require operating system fact dependencies Commit 330166b954f9d1939af22adf5f24e4a59c706f65 introduced a dependency on facter/util/file_read in the operatingsystem fact resolutions, but did not explicitly require the class. This introduced a race condition where loading facts in certain orders could cause an unitialized constant error. This commit adds the needed require to ensure this works correctly. --- lib/facter/operatingsystem.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index f684073619..3a689a5902 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -12,6 +12,7 @@ # require 'facter/util/operatingsystem' +require 'facter/util/file_read' Facter.add(:operatingsystem) do confine :kernel => :sunos From df565ed6e8da64e5da450dbc3718809cc526bc9e Mon Sep 17 00:00:00 2001 From: kylo Date: Thu, 8 Aug 2013 14:49:51 -0700 Subject: [PATCH 1373/3753] Initial commit --- README.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000000..a825f9ca8a --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +cfacter +======= + +Tinkering with a C/C++ facter From a6336e950c7e3f5ba35591c590fd9e4760187ad8 Mon Sep 17 00:00:00 2001 From: Steve Frank Date: Mon, 29 Jul 2013 12:23:22 -0400 Subject: [PATCH 1374/3753] Handle when facter is embedded in a jar --- lib/facter/util/loader.rb | 3 ++- spec/unit/util/loader_spec.rb | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/facter/util/loader.rb b/lib/facter/util/loader.rb index 163d378035..f32e43e602 100644 --- a/lib/facter/util/loader.rb +++ b/lib/facter/util/loader.rb @@ -73,7 +73,8 @@ def search_path def valid_search_path?(path) return @valid_path[path] unless @valid_path[path].nil? - return @valid_path[path] = Pathname.new(path).absolute? + #if absolute or if embedded in a jar + return @valid_path[path] = (Pathname.new(path).absolute? or path.start_with?('file:/')) end private :valid_search_path? diff --git a/spec/unit/util/loader_spec.rb b/spec/unit/util/loader_spec.rb index ec5fba6eae..0e80e49e65 100755 --- a/spec/unit/util/loader_spec.rb +++ b/spec/unit/util/loader_spec.rb @@ -93,6 +93,10 @@ def loader_from(places) @loader.should be_valid_search_path dir end end + + it "is true for paths with a file:/ uri scheme" do + @loader.should be_valid_search_path 'file:/in/jar' + end end describe "when determining the search path" do From ff44c5931426d8c0724b7e06c48c6f7875423d29 Mon Sep 17 00:00:00 2001 From: Clay Caviness Date: Tue, 13 Aug 2013 15:41:48 -0400 Subject: [PATCH 1375/3753] Use Dir.glob(), don't require rubygems or rake, use Dir.chdir as a block --- ext/osx/preflight.erb | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/ext/osx/preflight.erb b/ext/osx/preflight.erb index 6e185f83f5..ab7d9afce1 100755 --- a/ext/osx/preflight.erb +++ b/ext/osx/preflight.erb @@ -8,31 +8,26 @@ # ${3} is the destination volume so that this works correctly # when being installed to volumes other than the current OS. -<%- begin -%> - <%- require 'rubygems' -%> -<%- rescue LoadError -%> -<%- end -%> -<%- require 'rake' -%> - <%- ["@apple_libdir", "@apple_sbindir", "@apple_bindir", "@apple_docdir", "@package_name"].each do |i| -%> <%- val = eval(i) -%> <%- raise "Critical variable #{i} is unset!" if val.nil? or val.empty? -%> <%- end -%> # remove ruby library files -<%- Dir.chdir("lib") -%> -<%- [@apple_old_libdir, @apple_libdir].select {|i| i}.each do |libdir| -%> - <%- FileList["*"].each do |file| -%> +<%- Dir.chdir("lib") do -%> + <%- [@apple_old_libdir, @apple_libdir].compact.each do |libdir| -%> + <%- Dir.glob("*").each do |file| -%> /bin/rm -Rf "${3}<%= libdir %>/<%= file %>" + <%- end -%> <%- end -%> <%- end -%> # remove bin files -<%- Dir.chdir("../bin") -%> -<%- FileList["*"].each do |file| -%> +<%- Dir.chdir("bin") do -%> + <%- Dir.glob("*").each do |file| -%> /bin/rm -Rf "${3}<%= @apple_bindir %>/<%= file %>" + <%- end -%> <%- end -%> -<%- Dir.chdir("..") -%> # remove old doc files /bin/rm -Rf "${3}<%= @apple_docdir %>/<%= @package_name %>" From 77de566d7e1e932fc8514f561186d6bb81112449 Mon Sep 17 00:00:00 2001 From: Melissa Date: Tue, 13 Aug 2013 11:48:49 -0700 Subject: [PATCH 1376/3753] (Bug #22163) remove hardcoded hostname dependencies Prior to this commit, burji.puppetlabs.com was hardcoded in different points of puppet. Since we're in the process of migrating off burji and onto burji2, we realized this probably is not a good thing to have in place. This commit removes that hardcoded dependency on burji. --- ext/build_defaults.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ext/build_defaults.yaml b/ext/build_defaults.yaml index fb68296dd7..d84ff91ff8 100644 --- a/ext/build_defaults.yaml +++ b/ext/build_defaults.yaml @@ -10,14 +10,15 @@ gpg_key: '4BD6EC30' sign_tar: FALSE # a space separated list of mock configs final_mocks: 'pl-el-5-i386 pl-el-5-x86_64 pl-el-6-i386 pl-el-6-x86_64 pl-fedora-17-i386 pl-fedora-17-x86_64 pl-fedora-18-i386 pl-fedora-18-x86_64 pl-fedora-19-i386 pl-fedora-19-x86_64' -yum_host: 'burji.puppetlabs.com' +yum_host: 'yum.puppetlabs.com' yum_repo_path: '/opt/repository/yum/' build_gem: TRUE build_dmg: TRUE build_ips: TRUE -apt_host: 'burji.puppetlabs.com' +apt_host: 'apt.puppetlabs.com' apt_repo_url: 'http://apt.puppetlabs.com/' apt_repo_path: '/opt/repository/incoming' ips_repo: '/var/pkgrepo' ips_store: '/opt/repository' ips_host: 'solaris-11-ips-repo.acctest.dc1.puppetlabs.net' +tar_host: 'downloads.puppetlabs.com' From b9abdd1635ce6b18a42088ab1102ba171a205544 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Mon, 19 Aug 2013 14:13:39 -0500 Subject: [PATCH 1377/3753] (#12116) Windows domain/fqdn error when no domain This fixes an annoying issue that causes facter to throw errors when domain is not properly configured or doesn't exist. With this fix, it will properly return an empty domain and empty fqdn without errorring. When moving up the chain to the less specific domain fact, added a windows_hostname command and a check so that it also does not fail with path not found errors. Paired-with: Josh Cooper --- lib/facter/domain.rb | 8 +++++++- spec/unit/domain_spec.rb | 16 ++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/lib/facter/domain.rb b/lib/facter/domain.rb index 5d176e6716..6741b6e454 100644 --- a/lib/facter/domain.rb +++ b/lib/facter/domain.rb @@ -29,11 +29,14 @@ # Due to dangerous behavior of 'hostname -f' on old OS, we will explicitly opt-in # 'hostname -f' --hkenney May 9, 2012 basic_hostname = 'hostname 2> /dev/null' + windows_hostname = 'hostname > NUL' full_hostname = 'hostname -f 2> /dev/null' can_do_hostname_f = Regexp.union /Linux/i, /FreeBSD/i, /Darwin/i hostname_command = if Facter.value(:kernel) =~ can_do_hostname_f full_hostname + elsif Facter.value(:kernel) == "windows" + windows_hostname else basic_hostname end @@ -42,7 +45,7 @@ and name =~ /.*?\.(.+$)/ return_value = $1 - elsif domain = Facter::Util::Resolution.exec('dnsdomainname 2> /dev/null') \ + elsif Facter.value(:kernel) != "windows" and domain = Facter::Util::Resolution.exec('dnsdomainname 2> /dev/null') \ and domain =~ /.+/ return_value = domain @@ -80,6 +83,9 @@ break } end + + domain ||= '' + domain.gsub(/\.$/, '') end end diff --git a/spec/unit/domain_spec.rb b/spec/unit/domain_spec.rb index 96176f9734..93e259d525 100755 --- a/spec/unit/domain_spec.rb +++ b/spec/unit/domain_spec.rb @@ -172,6 +172,22 @@ def the_dnsdomainname_is(value) Facter.fact(:domain).value.should == 'foo.com' end + + context "without any network adapters with a specified DNSDomain" do + let(:hostname_command) { 'hostname > NUL' } + + it "should return nil" do + nic = stubs 'nic' + nic.stubs(:DNSDomain).returns(nil) + Facter::Util::Resolution.stubs(:exec).with(hostname_command).returns('sometest') + FileTest.stubs(:exists?).with("/etc/resolv.conf").returns(false) + + require 'facter/util/wmi' + Facter::Util::WMI.stubs(:execquery).with("select DNSDomain from Win32_NetworkAdapterConfiguration where IPEnabled = True").returns([nic]) + + Facter.fact(:domain).value.should be_nil + end + end end end From 5a730af67e30812ff1cbb1cf4bd39611173ae3c8 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Mon, 19 Aug 2013 15:01:04 -0700 Subject: [PATCH 1378/3753] (#12116) Don't break on the first interface Previously, if there were multiple interfaces with IPEnabled on Windows, we would always use the DNSDomain value from the first interface, even if it was nil or empty. This commit changes the domain fact to look for the first non-nil and non-empty DNSDomain value. --- lib/facter/domain.rb | 6 ++++-- spec/unit/domain_spec.rb | 37 ++++++++++++++++++++++++++----------- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/lib/facter/domain.rb b/lib/facter/domain.rb index 6741b6e454..9709d92977 100644 --- a/lib/facter/domain.rb +++ b/lib/facter/domain.rb @@ -79,8 +79,10 @@ if domain == "" require 'facter/util/wmi' Facter::Util::WMI.execquery("select DNSDomain from Win32_NetworkAdapterConfiguration where IPEnabled = True").each { |nic| - domain = nic.DNSDomain - break + if nic.DNSDomain && nic.DNSDomain.length > 0 + domain = nic.DNSDomain + break + end } end diff --git a/spec/unit/domain_spec.rb b/spec/unit/domain_spec.rb index 93e259d525..a2c13851ba 100755 --- a/spec/unit/domain_spec.rb +++ b/spec/unit/domain_spec.rb @@ -160,31 +160,46 @@ def the_dnsdomainname_is(value) Facter::Util::Registry.stubs(:hklm_read).returns('') end - it "should use the DNSDomain for the first nic where ip is enabled" do - nic = stubs 'nic' - nic.stubs(:DNSDomain).returns("foo.com") + def expects_dnsdomains(domains) + nics = [] - nic2 = stubs 'nic' - nic2.stubs(:DNSDomain).returns("bar.com") + domains.each do |domain| + nic = stubs 'nic' + nic.stubs(:DNSDomain).returns(domain) + nics << nic + end require 'facter/util/wmi' - Facter::Util::WMI.stubs(:execquery).with("select DNSDomain from Win32_NetworkAdapterConfiguration where IPEnabled = True").returns([nic, nic2]) + Facter::Util::WMI.stubs(:execquery).with("select DNSDomain from Win32_NetworkAdapterConfiguration where IPEnabled = True").returns(nics) + end + + it "uses the first DNSDomain" do + expects_dnsdomains(['foo.com', 'bar.com']) Facter.fact(:domain).value.should == 'foo.com' end + it "uses the first non-nil DNSDomain" do + expects_dnsdomains([nil, 'bar.com']) + + Facter.fact(:domain).value.should == 'bar.com' + end + + it "uses the first non-empty DNSDomain" do + expects_dnsdomains(['', 'bar.com']) + + Facter.fact(:domain).value.should == 'bar.com' + end + context "without any network adapters with a specified DNSDomain" do let(:hostname_command) { 'hostname > NUL' } it "should return nil" do - nic = stubs 'nic' - nic.stubs(:DNSDomain).returns(nil) + expects_dnsdomains([nil]) + Facter::Util::Resolution.stubs(:exec).with(hostname_command).returns('sometest') FileTest.stubs(:exists?).with("/etc/resolv.conf").returns(false) - require 'facter/util/wmi' - Facter::Util::WMI.stubs(:execquery).with("select DNSDomain from Win32_NetworkAdapterConfiguration where IPEnabled = True").returns([nic]) - Facter.fact(:domain).value.should be_nil end end From cc5edf00fda91418a2ad205635312c59d5cd9a1f Mon Sep 17 00:00:00 2001 From: Melissa Date: Mon, 19 Aug 2013 13:29:55 -0700 Subject: [PATCH 1379/3753] (Bug #22238) Remove EOL f17 We're removing support for Fedora 17, because it's gone End of Life. --- ext/build_defaults.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/build_defaults.yaml b/ext/build_defaults.yaml index d84ff91ff8..7693ba6e9c 100644 --- a/ext/build_defaults.yaml +++ b/ext/build_defaults.yaml @@ -9,7 +9,7 @@ gpg_name: 'info@puppetlabs.com' gpg_key: '4BD6EC30' sign_tar: FALSE # a space separated list of mock configs -final_mocks: 'pl-el-5-i386 pl-el-5-x86_64 pl-el-6-i386 pl-el-6-x86_64 pl-fedora-17-i386 pl-fedora-17-x86_64 pl-fedora-18-i386 pl-fedora-18-x86_64 pl-fedora-19-i386 pl-fedora-19-x86_64' +final_mocks: 'pl-el-5-i386 pl-el-5-x86_64 pl-el-6-i386 pl-el-6-x86_64 pl-fedora-18-i386 pl-fedora-18-x86_64 pl-fedora-19-i386 pl-fedora-19-x86_64' yum_host: 'yum.puppetlabs.com' yum_repo_path: '/opt/repository/yum/' build_gem: TRUE From db8411cc3c7826c6cce90ff771b9483d1426fbc0 Mon Sep 17 00:00:00 2001 From: Clay Caviness Date: Tue, 20 Aug 2013 09:19:42 +0100 Subject: [PATCH 1380/3753] use instance_variable_get instead of eval --- ext/osx/preflight.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) mode change 100755 => 100644 ext/osx/preflight.erb diff --git a/ext/osx/preflight.erb b/ext/osx/preflight.erb old mode 100755 new mode 100644 index ab7d9afce1..994cbddc90 --- a/ext/osx/preflight.erb +++ b/ext/osx/preflight.erb @@ -9,7 +9,7 @@ # when being installed to volumes other than the current OS. <%- ["@apple_libdir", "@apple_sbindir", "@apple_bindir", "@apple_docdir", "@package_name"].each do |i| -%> - <%- val = eval(i) -%> + <%- val = instance_variable_get(i) -%> <%- raise "Critical variable #{i} is unset!" if val.nil? or val.empty? -%> <%- end -%> From 132c0ce72ee280137e8bf9936684699004c53c6c Mon Sep 17 00:00:00 2001 From: Andrew Parker Date: Tue, 20 Aug 2013 13:56:34 -0700 Subject: [PATCH 1381/3753] (#15586) Use OptionParser for help Ruby 1.9.3 dropped Rdoc.usage. The output of `facter --help` depended on this command. The suggested change from the Rdoc maintainer is to use the banner of the OptionParser instead. This changes it to use the banner. --- bin/facter | 58 ------------------------ lib/facter/application.rb | 93 +++++++++++++++++++++++++++++---------- 2 files changed, 69 insertions(+), 82 deletions(-) diff --git a/bin/facter b/bin/facter index 686f880fe5..0616157063 100755 --- a/bin/facter +++ b/bin/facter @@ -1,62 +1,4 @@ #!/usr/bin/env ruby -# -# = Synopsis -# -# Collect and display facts about the system. -# -# = Usage -# -# facter [-d|--debug] [-h|--help] [-p|--puppet] [-v|--version] [-y|--yaml] [-j|--json] [--external-dir DIR] [--no-external-dir] [fact] [fact] [...] -# -# = Description -# -# Collect and display facts about the current system. The library behind -# Facter is easy to expand, making Facter an easy way to collect information -# about a system from within the shell or within Ruby. -# -# If no facts are specifically asked for, then all facts will be returned. -# -# = Options -# -# yaml:: -# Emit facts in YAML format. -# -# json:: -# Emit facts in JSON format. -# -# puppet:: -# Load the Puppet libraries, thus allowing Facter to load Puppet-specific facts. -# -# version:: -# Print the version and exit. -# -# help:: -# Print this help message. -# -# debug:: -# Enable debugging. -# -# trace:: -# Enable backtraces. -# -# timing:: -# Enable timing. -# -# facts.d:: -# The directory to use for external facts. -# -# = Example -# -# facter kernel -# -# = Author -# -# Luke Kanies -# -# = Copyright -# -# Copyright (c) 2011 Puppet Labs, Inc -# Licensed under the Apache 2.0 license # Bundler and rubygems maintain a set of directories from which to # load gems. If Bundler is loaded, let it determine what can be diff --git a/lib/facter/application.rb b/lib/facter/application.rb index 514021ce51..d94be0f9a7 100644 --- a/lib/facter/application.rb +++ b/lib/facter/application.rb @@ -93,35 +93,80 @@ def self.run(argv) # @return [Hash] options hash def self.parse(argv) options = {} - OptionParser.new do |opts| - opts.on("-y", "--yaml") { |v| options[:yaml] = v } - opts.on("-j", "--json") { |v| options[:json] = v } - opts.on( "--trace") { |v| options[:trace] = v } - opts.on( "--external-dir DIR") { |v| create_directory_loader(v) } - opts.on( "--no-external-dir") { |v| create_nothing_loader } - opts.on("-d", "--debug") { |v| Facter.debugging(1) } - opts.on("-t", "--timing") { |v| Facter.timing(1) } - opts.on("-p", "--puppet") { |v| load_puppet } - - opts.on_tail("-v", "--version") do + parser = OptionParser.new do |opts| + opts.banner = <<-BANNER +Synopsis +======== + +Collect and display facts about the system. + +Usage +===== + + facter [-d|--debug] [-h|--help] [-p|--puppet] [-v|--version] [-y|--yaml] [-j|--json] [--external-dir DIR] [--no-external-dir] [fact] [fact] [...] + +Description +=========== + +Collect and display facts about the current system. The library behind +Facter is easy to expand, making Facter an easy way to collect information +about a system from within the shell or within Ruby. + +If no facts are specifically asked for, then all facts will be returned. + +EXAMPLE +======= + facter kernel + +AUTHOR +====== + Luke Kanies + +COPYRIGHT +========= + Copyright (c) 2011-2012 Puppet Labs, Inc Licensed under the Apache 2.0 license + +USAGE +===== + BANNER + opts.on("-y", + "--yaml", + "Emit facts in YAML format.") { |v| options[:yaml] = v } + opts.on("-j", + "--json", + "Emit facts in JSON format.") { |v| options[:json] = v } + opts.on("--trace", + "Enable backtraces.") { |v| options[:trace] = v } + opts.on("--external-dir DIR", + "The directory to use for external facts.") { |v| create_directory_loader(v) } + opts.on("--no-external-dir", + "Turn off external facts.") { |v| create_nothing_loader } + opts.on("-d", + "--debug", + "Enable debugging.") { |v| Facter.debugging(1) } + opts.on("-t", + "--timing", + "Enable timing.") { |v| Facter.timing(1) } + opts.on("-p", + "--puppet", + "Load the Puppet libraries, thus allowing Facter to load Puppet-specific facts.") { |v| load_puppet } + + opts.on_tail("-v", + "--version", + "Print the version and exit.") do puts Facter.version exit(0) end - opts.on_tail("-h", "--help") do - begin - require 'rdoc/ri/ri_paths' - require 'rdoc/usage' - RDoc.usage # print usage and exit - rescue LoadError - $stderr.puts "No help available unless your RDoc has RDoc.usage" - exit(1) - rescue => e - $stderr.puts "fatal: #{e}" - exit(1) - end + opts.on_tail("-h", + "--help", + "Print this help message.") do + puts parser + exit(0) end - end.parse!(argv) + end + + parser.parse!(argv) options rescue OptionParser::InvalidOption => e From d7c863eb490c4fea1012084804347fb6f6ea8e34 Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Sat, 17 Aug 2013 17:34:41 +0000 Subject: [PATCH 1382/3753] (#22233) Add facts from Google Compute Engine metadata service Without this patch facter does not report fact values for the data stored in the Google Compute Engine metadata service. This is a problem because the metadata service is a primary vehicle for end users to pass in information about compute instances. This information is often necessary for automated configuration of the compute instances. This patch addresses the problem by dynamically generating a list of facts based on the data made available by the http://metadata service in the GCE environment. The behavior of these facts is to obtain a JSON structured hash from the metadata service if the `virtual` fact has the value of `gce`. --- .gitignore | 1 + Rakefile | 83 ++++++++++ lib/facter/gce.rb | 3 + lib/facter/util/gce.rb | 153 ++++++++++++++++++ ..._metadata_instance_attributes_request.yaml | 15 ++ ...metadata_instance_attributes_response.yaml | 23 +++ ...metadata_instance_description_request.yaml | 15 ++ ...etadata_instance_description_response.yaml | 23 +++ ..._instance_disks_0_device_name_request.yaml | 15 ++ ...instance_disks_0_device_name_response.yaml | 23 +++ ...tadata_instance_disks_0_index_request.yaml | 15 ++ ...adata_instance_disks_0_index_response.yaml | 23 +++ ...etadata_instance_disks_0_mode_request.yaml | 15 ++ ...tadata_instance_disks_0_mode_response.yaml | 23 +++ ...gce_metadata_instance_disks_0_request.yaml | 15 ++ ...ce_metadata_instance_disks_0_response.yaml | 31 ++++ ...etadata_instance_disks_0_type_request.yaml | 15 ++ ...tadata_instance_disks_0_type_response.yaml | 23 +++ .../gce_metadata_instance_disks_request.yaml | 15 ++ .../gce_metadata_instance_disks_response.yaml | 25 +++ ...ce_metadata_instance_hostname_request.yaml | 15 ++ ...e_metadata_instance_hostname_response.yaml | 23 +++ .../gce/gce_metadata_instance_id_request.yaml | 15 ++ .../gce_metadata_instance_id_response.yaml | 23 +++ .../gce_metadata_instance_image_request.yaml | 15 ++ .../gce_metadata_instance_image_response.yaml | 23 +++ ...etadata_instance_machine_type_request.yaml | 15 ++ ...tadata_instance_machine_type_response.yaml | 23 +++ ..._access_configs_0_external_ip_request.yaml | 15 ++ ...access_configs_0_external_ip_response.yaml | 23 +++ ...interfaces_0_access_configs_0_request.yaml | 15 ++ ...nterfaces_0_access_configs_0_response.yaml | 27 ++++ ...faces_0_access_configs_0_type_request.yaml | 15 ++ ...aces_0_access_configs_0_type_response.yaml | 23 +++ ...k_interfaces_0_access_configs_request.yaml | 15 ++ ..._interfaces_0_access_configs_response.yaml | 25 +++ ...rk_interfaces_0_forwarded_ips_request.yaml | 15 ++ ...k_interfaces_0_forwarded_ips_response.yaml | 23 +++ ...tance_network_interfaces_0_ip_request.yaml | 15 ++ ...ance_network_interfaces_0_ip_response.yaml | 23 +++ ..._network_interfaces_0_network_request.yaml | 15 ++ ...network_interfaces_0_network_response.yaml | 23 +++ ...instance_network_interfaces_0_request.yaml | 15 ++ ...nstance_network_interfaces_0_response.yaml | 31 ++++ ...a_instance_network_interfaces_request.yaml | 15 ++ ..._instance_network_interfaces_response.yaml | 25 +++ .../gce/gce_metadata_instance_request.yaml | 15 ++ .../gce/gce_metadata_instance_response.yaml | 45 ++++++ ...t_gserviceaccount_com_aliases_request.yaml | 15 ++ ..._gserviceaccount_com_aliases_response.yaml | 25 +++ ...ect_gserviceaccount_com_email_request.yaml | 15 ++ ...ct_gserviceaccount_com_email_response.yaml | 23 +++ ...1_project_gserviceaccount_com_request.yaml | 15 ++ ..._project_gserviceaccount_com_response.yaml | 31 ++++ ...ct_gserviceaccount_com_scopes_request.yaml | 15 ++ ...t_gserviceaccount_com_scopes_response.yaml | 29 ++++ ...ect_gserviceaccount_com_token_request.yaml | 15 ++ ...ct_gserviceaccount_com_token_response.yaml | 21 +++ ...vice_accounts_default_aliases_request.yaml | 15 ++ ...ice_accounts_default_aliases_response.yaml | 25 +++ ...ervice_accounts_default_email_request.yaml | 15 ++ ...rvice_accounts_default_email_response.yaml | 23 +++ ...ance_service_accounts_default_request.yaml | 15 ++ ...nce_service_accounts_default_response.yaml | 31 ++++ ...rvice_accounts_default_scopes_request.yaml | 15 ++ ...vice_accounts_default_scopes_response.yaml | 29 ++++ ...ervice_accounts_default_token_request.yaml | 15 ++ ...rvice_accounts_default_token_response.yaml | 21 +++ ...ata_instance_service_accounts_request.yaml | 15 ++ ...ta_instance_service_accounts_response.yaml | 27 ++++ .../gce_metadata_instance_tags_request.yaml | 15 ++ .../gce_metadata_instance_tags_response.yaml | 23 +++ .../gce_metadata_instance_zone_request.yaml | 15 ++ .../gce_metadata_instance_zone_response.yaml | 23 +++ ...e_metadata_project_attributes_request.yaml | 15 ++ ..._metadata_project_attributes_response.yaml | 25 +++ ...ta_project_attributes_sshKeys_request.yaml | 15 ++ ...a_project_attributes_sshKeys_response.yaml | 25 +++ ...ta_project_numeric_project_id_request.yaml | 15 ++ ...a_project_numeric_project_id_response.yaml | 23 +++ ...e_metadata_project_project_id_request.yaml | 15 ++ ..._metadata_project_project_id_response.yaml | 23 +++ .../gce/gce_metadata_project_request.yaml | 15 ++ .../gce/gce_metadata_project_response.yaml | 29 ++++ .../unit/util/gce/gce_metadata_request.yaml | 15 ++ .../unit/util/gce/gce_metadata_response.yaml | 27 ++++ spec/unit/gce_spec.rb | 38 +++++ spec/unit/util/gce_spec.rb | 48 ++++++ 88 files changed, 1978 insertions(+) create mode 100644 lib/facter/gce.rb create mode 100644 lib/facter/util/gce.rb create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_attributes_request.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_attributes_response.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_description_request.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_description_response.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_device_name_request.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_device_name_response.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_index_request.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_index_response.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_mode_request.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_mode_response.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_request.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_response.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_type_request.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_type_response.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_disks_request.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_disks_response.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_hostname_request.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_hostname_response.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_id_request.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_id_response.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_image_request.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_image_response.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_machine_type_request.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_machine_type_response.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_external_ip_request.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_external_ip_response.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_request.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_response.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_type_request.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_type_response.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_request.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_response.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_forwarded_ips_request.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_forwarded_ips_response.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_ip_request.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_ip_response.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_network_request.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_network_response.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_request.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_response.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_request.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_response.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_request.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_response.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_aliases_request.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_aliases_response.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_email_request.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_email_response.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_request.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_response.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_scopes_request.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_scopes_response.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_token_request.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_token_response.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_aliases_request.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_aliases_response.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_email_request.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_email_response.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_request.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_response.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_scopes_request.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_scopes_response.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_token_request.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_token_response.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_request.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_response.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_tags_request.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_tags_response.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_zone_request.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_zone_response.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_project_attributes_request.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_project_attributes_response.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_project_attributes_sshKeys_request.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_project_attributes_sshKeys_response.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_project_numeric_project_id_request.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_project_numeric_project_id_response.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_project_project_id_request.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_project_project_id_response.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_project_request.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_project_response.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_request.yaml create mode 100644 spec/fixtures/unit/util/gce/gce_metadata_response.yaml create mode 100755 spec/unit/gce_spec.rb create mode 100755 spec/unit/util/gce_spec.rb diff --git a/.gitignore b/.gitignore index 799e2b82f2..2644ef6d37 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ pkg/ ext/packaging/ +*.swp # YARD generated documentation .yardoc doc diff --git a/Rakefile b/Rakefile index af9318d8f2..cd2e0739b3 100644 --- a/Rakefile +++ b/Rakefile @@ -133,3 +133,86 @@ namespace :collect do puts "Wrote: #{dir}/#{request_file}" unless quiet end end + +namespace :collect do + # gem install bundle + # bundle install --path .bundle/gems + # bundle exec rake "collect:gce_metadata" + # metadata will be scrubbed with the following changes: + # - project_number becomes '111111111111' + # - project_id becomes 'development_project' + # - sshKeys are trimmed + # - auth token is masked out + desc "Scrape GCE Metadata into fixtures, scrubbing sensitive data" + task :gce_metadata do + collect_metadata + end + + ## + # collect_metadata walks the Google's GCE Metadata API and records each + # request and response instance as a serialized YAML string. This method is + # intended to be used by Rake tasks Puppet users invoke to collect data for + # development and troubleshooting purposes. + def collect_metadata(key='/', date=Time.now.strftime("%F"), dir="spec/fixtures/unit/util/gce") + require 'timeout' + require 'net/http' + require 'uri' + + # Local variables + file_prefix = "gce_metadata#{key.gsub(/[^a-zA-Z0-9]+/, '_')}".gsub(/_+$/, '').gsub(/\d{12}/,'111111111111') + response = nil + + Dir.chdir(dir) do + uri = URI("http://metadata/computeMetadata/v1beta1#{key}") + Timeout::timeout(4) do + Net::HTTP.start(uri.host, uri.port) do |http| + request = Net::HTTP::Get.new(uri.request_uri) + response = scrub_gce(key.split("/")[-1], http.request(request)) + + write_fixture(request, "#{file_prefix}_request.yaml") + write_fixture(response, "#{file_prefix}_response.yaml") + end + end + end + + ## + # if the current key is a directory, decend into all of the files. If the + # current key is not, we've already written it out and we're done. + if key.end_with? "/" + response.read_body.lines.each do |line| + collect_metadata("#{key}#{line.chomp}", date, dir) + end + end + end + + ## + # write_fixture dumps an internal Ruby object to a file intended to be used + # as a fixture for spec testing. + # + def write_fixture(obj, filename, quiet=false) + File.open(filename, "w+") do |fd| + s = YAML.dump(obj) + fd.write(s.gsub(/\d{12}/,'111111111111')) + end + puts "Wrote: #{filename}" unless quiet + end + + ## + # scrub_gce scrubs sensitive data from HTTP response before writing fixtures + # to disk + # + # @return [Net::HTTPResponse] Sanitized GCE response in YAML format + def scrub_gce(key, resp) + case key + when "sshKeys" + resp.body = "googler:ssh-rsa AAA...ej googler@facter-dev\n" + resp["content-length"] = "44" + when "project-id" + resp.body = "development_project" + resp["content-length"] = "19" + when "token" + resp.body = '{"access_token":"ya29.AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","expires_in":1234,"token_type":"Bearer"}' + end + resp + end +end diff --git a/lib/facter/gce.rb b/lib/facter/gce.rb new file mode 100644 index 0000000000..c77054abda --- /dev/null +++ b/lib/facter/gce.rb @@ -0,0 +1,3 @@ +require 'facter/util/gce' + +Facter::Util::GCE.add_gce_facts diff --git a/lib/facter/util/gce.rb b/lib/facter/util/gce.rb new file mode 100644 index 0000000000..39e970e74e --- /dev/null +++ b/lib/facter/util/gce.rb @@ -0,0 +1,153 @@ +# Copyright 2013 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Google Compute Engine: Storing and Retrieving Metadata +# https://developers.google.com/compute/docs/metadata + +# TL;DR +# From the GCE instance, collect all facts about the GCE instance and project +# with 'curl "http://metadata/computeMetadata/v1beta1/?recursive=true&alt=json' +# + +require 'timeout' +require 'open-uri' +require 'json' + +# Provide a set of utility static methods that help with resolving the GCE +# fact. +module Facter::Util::GCE + CONNECTION_ERRORS = [ + OpenURI::HTTPError, + Errno::EHOSTDOWN, + Errno::EHOSTUNREACH, + Errno::ENETUNREACH, + Errno::ECONNABORTED, + Errno::ECONNREFUSED, + Errno::ECONNRESET, + Errno::ETIMEDOUT, + Timeout::Error, + ] + + METADATA_URL="http://metadata/computeMetadata/v1beta1/?recursive=true&alt=json" + + ## + # recursive function to flatten the json response into single + # key/value pairs. hash keys become keys, lists are appended + # to keys with numerical index + def self.metadata_facts(var, val) + if val.is_a?(Hash) + val.each_pair do |k, v| + if ["image", "machineType", "zone", "network"].include? k + v = v.split('/')[-1] + end + metadata_facts("#{var}_#{k}", v) + end + elsif val.is_a?(Array) + val.each_with_index do |v, i| + metadata_facts("#{var}_#{i}", v) + end + else + # convert the sshKeys single entry into multiple entries, one + # per key + if var.end_with? "sshKeys" + val.split("\n").each_with_index do |v, i| + Facter.add("#{var}_#{i}".to_sym) { setcode { v } } + end + else + Facter.add(var.to_sym) { setcode { val } } + end + end + end + + ## + # with_metadata_server takes a block of code and executes the block only if + # Facter is running on node that can access a metadata server at + # http://metadata/. This is useful to decide if it's reasonably + # likely that talking to the GCE metadata server will be successful or not. + # + # @option options [Integer] :timeout (100) the maxiumum number of + # milliseconds Facter will block trying to talk to the metadata server. + # Defaults to 200. + # + # @option options [String] :fact ('virtual') the fact to check. The block + # will only be executed if the fact named here matches the value named in the + # :value option. + # + # @option options [String] :value ('gce') the value to check. The block + # will be executed if Facter.value(options[:fact]) matches this value. + # + # @option options [Fixnum] :retry_limit (3) the maximum number of times that + # this method will try to contact the metadata server. The maximum run time + # is the timeout times this limit, so please keep the value small. + # + # @return [Boolean] the return {true} if successfula, {false} otherwise + def self.with_metadata_server(options = {}) + opts = options.dup + opts[:timeout] ||= 100 + opts[:fact] ||= 'virtual' + opts[:value] ||= 'gce' + opts[:retry_limit] ||= 3 + # Conversion to fractional seconds for Timeout + timeout = opts[:timeout] / 1000.0 + raise ArgumentError, "A value is required for :fact" if opts[:fact].nil? + raise ArgumentError, "A value is required for :value" if opts[:value].nil? + return false if Facter.value(opts[:fact]) != opts[:value] + + attempts = 0 + begin + attempts = attempts + 1 + # Read the list of supported API versions + Timeout.timeout(timeout) do + if body = read_uri("#{METADATA_URL}") + metadata_facts("gce", JSON.parse(body)) + end + end + rescue *CONNECTION_ERRORS => detail + retry if attempts < opts[:retry_limit] + Facter.warn "Unable to fetch metadata from #{METADATA_URL}, " + + "metadata server facts will be undefined. #{detail.message}" + return false + end + return true + end + + ## + # read_uri provides a seam method to easily test the HTTP client + # functionality of a HTTP based metadata server. + # + # @api private + # + # @return [String] containing the body of the response + def self.read_uri(uri) + open(uri).read + end + private_class_method :read_uri + + ## + # add_gce_facts defines GCE related facts when running on an GCE compatible + # node. This method will only ever do work once for the life of a process in + # order to limit the amount of network I/O. + # + # @option options [Boolean] :force (false) whether or not to force + # re-definition of the facts. + def self.add_gce_facts(options = {}) + opts = options.dup + opts[:force] ||= false + unless opts[:force] + return nil if @add_gce_facts_has_run + end + @add_gce_facts_has_run = true + with_metadata_server :timeout => 50 + end +end diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_attributes_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_attributes_request.yaml new file mode 100644 index 0000000000..d5bb46d0d9 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_attributes_request.yaml @@ -0,0 +1,15 @@ +--- !ruby/object:Net::HTTP::Get +method: GET +request_has_body: false +response_has_body: true +path: /computeMetadata/v1beta1/instance/attributes/ +header: + accept: + - ! '*/*' + user-agent: + - Ruby + host: + - metadata +body: +body_stream: +body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_attributes_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_attributes_response.yaml new file mode 100644 index 0000000000..bc0df5bbc3 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_attributes_response.yaml @@ -0,0 +1,23 @@ +--- !ruby/object:Net::HTTPOK +http_version: '1.1' +code: '200' +message: OK +header: + content-type: + - application/text + etag: + - 3c19e4d76229ba8c + date: + - Sat, 17 Aug 2013 17:27:19 GMT + server: + - Metadata Server for VM + content-length: + - '0' + x-xss-protection: + - 1; mode=block + x-frame-options: + - SAMEORIGIN +body: '' +read: true +socket: +body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_description_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_description_request.yaml new file mode 100644 index 0000000000..7304b03683 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_description_request.yaml @@ -0,0 +1,15 @@ +--- !ruby/object:Net::HTTP::Get +method: GET +request_has_body: false +response_has_body: true +path: /computeMetadata/v1beta1/instance/description +header: + accept: + - ! '*/*' + user-agent: + - Ruby + host: + - metadata +body: +body_stream: +body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_description_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_description_response.yaml new file mode 100644 index 0000000000..bc0df5bbc3 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_description_response.yaml @@ -0,0 +1,23 @@ +--- !ruby/object:Net::HTTPOK +http_version: '1.1' +code: '200' +message: OK +header: + content-type: + - application/text + etag: + - 3c19e4d76229ba8c + date: + - Sat, 17 Aug 2013 17:27:19 GMT + server: + - Metadata Server for VM + content-length: + - '0' + x-xss-protection: + - 1; mode=block + x-frame-options: + - SAMEORIGIN +body: '' +read: true +socket: +body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_device_name_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_device_name_request.yaml new file mode 100644 index 0000000000..e1d1e295c6 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_device_name_request.yaml @@ -0,0 +1,15 @@ +--- !ruby/object:Net::HTTP::Get +method: GET +request_has_body: false +response_has_body: true +path: /computeMetadata/v1beta1/instance/disks/0/device-name +header: + accept: + - ! '*/*' + user-agent: + - Ruby + host: + - metadata +body: +body_stream: +body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_device_name_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_device_name_response.yaml new file mode 100644 index 0000000000..0ffe0480d4 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_device_name_response.yaml @@ -0,0 +1,23 @@ +--- !ruby/object:Net::HTTPOK +http_version: '1.1' +code: '200' +message: OK +header: + content-type: + - application/text + etag: + - 293a50d9c06a8a32 + date: + - Sat, 17 Aug 2013 17:27:19 GMT + server: + - Metadata Server for VM + content-length: + - '15' + x-xss-protection: + - 1; mode=block + x-frame-options: + - SAMEORIGIN +body: boot-facter-dev +read: true +socket: +body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_index_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_index_request.yaml new file mode 100644 index 0000000000..38b21f041b --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_index_request.yaml @@ -0,0 +1,15 @@ +--- !ruby/object:Net::HTTP::Get +method: GET +request_has_body: false +response_has_body: true +path: /computeMetadata/v1beta1/instance/disks/0/index +header: + accept: + - ! '*/*' + user-agent: + - Ruby + host: + - metadata +body: +body_stream: +body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_index_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_index_response.yaml new file mode 100644 index 0000000000..dae5d35001 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_index_response.yaml @@ -0,0 +1,23 @@ +--- !ruby/object:Net::HTTPOK +http_version: '1.1' +code: '200' +message: OK +header: + content-type: + - application/text + etag: + - 99a9c7abf407dd27 + date: + - Sat, 17 Aug 2013 17:27:19 GMT + server: + - Metadata Server for VM + content-length: + - '1' + x-xss-protection: + - 1; mode=block + x-frame-options: + - SAMEORIGIN +body: '0' +read: true +socket: +body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_mode_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_mode_request.yaml new file mode 100644 index 0000000000..52c3ee3f11 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_mode_request.yaml @@ -0,0 +1,15 @@ +--- !ruby/object:Net::HTTP::Get +method: GET +request_has_body: false +response_has_body: true +path: /computeMetadata/v1beta1/instance/disks/0/mode +header: + accept: + - ! '*/*' + user-agent: + - Ruby + host: + - metadata +body: +body_stream: +body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_mode_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_mode_response.yaml new file mode 100644 index 0000000000..5f575f4e3e --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_mode_response.yaml @@ -0,0 +1,23 @@ +--- !ruby/object:Net::HTTPOK +http_version: '1.1' +code: '200' +message: OK +header: + content-type: + - application/text + etag: + - 50d532090d9f52d2 + date: + - Sat, 17 Aug 2013 17:27:19 GMT + server: + - Metadata Server for VM + content-length: + - '10' + x-xss-protection: + - 1; mode=block + x-frame-options: + - SAMEORIGIN +body: READ_WRITE +read: true +socket: +body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_request.yaml new file mode 100644 index 0000000000..d79a2c1737 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_request.yaml @@ -0,0 +1,15 @@ +--- !ruby/object:Net::HTTP::Get +method: GET +request_has_body: false +response_has_body: true +path: /computeMetadata/v1beta1/instance/disks/0/ +header: + accept: + - ! '*/*' + user-agent: + - Ruby + host: + - metadata +body: +body_stream: +body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_response.yaml new file mode 100644 index 0000000000..ece51354d6 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_response.yaml @@ -0,0 +1,31 @@ +--- !ruby/object:Net::HTTPOK +http_version: '1.1' +code: '200' +message: OK +header: + content-type: + - application/text + etag: + - b1eee485c34883d2 + date: + - Sat, 17 Aug 2013 17:27:19 GMT + server: + - Metadata Server for VM + content-length: + - '28' + x-xss-protection: + - 1; mode=block + x-frame-options: + - SAMEORIGIN +body: ! 'device-name + + index + + mode + + type + +' +read: true +socket: +body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_type_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_type_request.yaml new file mode 100644 index 0000000000..1a65e5e3cd --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_type_request.yaml @@ -0,0 +1,15 @@ +--- !ruby/object:Net::HTTP::Get +method: GET +request_has_body: false +response_has_body: true +path: /computeMetadata/v1beta1/instance/disks/0/type +header: + accept: + - ! '*/*' + user-agent: + - Ruby + host: + - metadata +body: +body_stream: +body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_type_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_type_response.yaml new file mode 100644 index 0000000000..459097acb7 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_type_response.yaml @@ -0,0 +1,23 @@ +--- !ruby/object:Net::HTTPOK +http_version: '1.1' +code: '200' +message: OK +header: + content-type: + - application/text + etag: + - c69eb581f2475c79 + date: + - Sat, 17 Aug 2013 17:27:19 GMT + server: + - Metadata Server for VM + content-length: + - '10' + x-xss-protection: + - 1; mode=block + x-frame-options: + - SAMEORIGIN +body: PERSISTENT +read: true +socket: +body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_request.yaml new file mode 100644 index 0000000000..5d7696da62 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_request.yaml @@ -0,0 +1,15 @@ +--- !ruby/object:Net::HTTP::Get +method: GET +request_has_body: false +response_has_body: true +path: /computeMetadata/v1beta1/instance/disks/ +header: + accept: + - ! '*/*' + user-agent: + - Ruby + host: + - metadata +body: +body_stream: +body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_response.yaml new file mode 100644 index 0000000000..5f66ec7004 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_response.yaml @@ -0,0 +1,25 @@ +--- !ruby/object:Net::HTTPOK +http_version: '1.1' +code: '200' +message: OK +header: + content-type: + - application/text + etag: + - ab12d55d87368cc3 + date: + - Sat, 17 Aug 2013 17:27:19 GMT + server: + - Metadata Server for VM + content-length: + - '3' + x-xss-protection: + - 1; mode=block + x-frame-options: + - SAMEORIGIN +body: ! '0/ + +' +read: true +socket: +body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_hostname_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_hostname_request.yaml new file mode 100644 index 0000000000..15b90a6e41 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_hostname_request.yaml @@ -0,0 +1,15 @@ +--- !ruby/object:Net::HTTP::Get +method: GET +request_has_body: false +response_has_body: true +path: /computeMetadata/v1beta1/instance/hostname +header: + accept: + - ! '*/*' + user-agent: + - Ruby + host: + - metadata +body: +body_stream: +body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_hostname_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_hostname_response.yaml new file mode 100644 index 0000000000..e004f8b338 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_hostname_response.yaml @@ -0,0 +1,23 @@ +--- !ruby/object:Net::HTTPOK +http_version: '1.1' +code: '200' +message: OK +header: + content-type: + - application/text + etag: + - fd0483c1cc3a83fe + date: + - Sat, 17 Aug 2013 17:27:19 GMT + server: + - Metadata Server for VM + content-length: + - '41' + x-xss-protection: + - 1; mode=block + x-frame-options: + - SAMEORIGIN +body: facter-dev.c.erjohnso.google.com.internal +read: true +socket: +body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_id_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_id_request.yaml new file mode 100644 index 0000000000..81567561fc --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_id_request.yaml @@ -0,0 +1,15 @@ +--- !ruby/object:Net::HTTP::Get +method: GET +request_has_body: false +response_has_body: true +path: /computeMetadata/v1beta1/instance/id +header: + accept: + - ! '*/*' + user-agent: + - Ruby + host: + - metadata +body: +body_stream: +body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_id_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_id_response.yaml new file mode 100644 index 0000000000..b8f5b5bdcf --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_id_response.yaml @@ -0,0 +1,23 @@ +--- !ruby/object:Net::HTTPOK +http_version: '1.1' +code: '200' +message: OK +header: + content-type: + - application/text + etag: + - d809d4ca2c1d9dfa + date: + - Sat, 17 Aug 2013 17:27:19 GMT + server: + - Metadata Server for VM + content-length: + - '20' + x-xss-protection: + - 1; mode=block + x-frame-options: + - SAMEORIGIN +body: '11111111111167524945' +read: true +socket: +body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_image_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_image_request.yaml new file mode 100644 index 0000000000..3cce9c19db --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_image_request.yaml @@ -0,0 +1,15 @@ +--- !ruby/object:Net::HTTP::Get +method: GET +request_has_body: false +response_has_body: true +path: /computeMetadata/v1beta1/instance/image +header: + accept: + - ! '*/*' + user-agent: + - Ruby + host: + - metadata +body: +body_stream: +body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_image_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_image_response.yaml new file mode 100644 index 0000000000..bc0df5bbc3 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_image_response.yaml @@ -0,0 +1,23 @@ +--- !ruby/object:Net::HTTPOK +http_version: '1.1' +code: '200' +message: OK +header: + content-type: + - application/text + etag: + - 3c19e4d76229ba8c + date: + - Sat, 17 Aug 2013 17:27:19 GMT + server: + - Metadata Server for VM + content-length: + - '0' + x-xss-protection: + - 1; mode=block + x-frame-options: + - SAMEORIGIN +body: '' +read: true +socket: +body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_machine_type_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_machine_type_request.yaml new file mode 100644 index 0000000000..7d3b1a4dee --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_machine_type_request.yaml @@ -0,0 +1,15 @@ +--- !ruby/object:Net::HTTP::Get +method: GET +request_has_body: false +response_has_body: true +path: /computeMetadata/v1beta1/instance/machine-type +header: + accept: + - ! '*/*' + user-agent: + - Ruby + host: + - metadata +body: +body_stream: +body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_machine_type_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_machine_type_response.yaml new file mode 100644 index 0000000000..b4fa9f8466 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_machine_type_response.yaml @@ -0,0 +1,23 @@ +--- !ruby/object:Net::HTTPOK +http_version: '1.1' +code: '200' +message: OK +header: + content-type: + - application/text + etag: + - 3b5d634c2f6e1895 + date: + - Sat, 17 Aug 2013 17:27:19 GMT + server: + - Metadata Server for VM + content-length: + - '48' + x-xss-protection: + - 1; mode=block + x-frame-options: + - SAMEORIGIN +body: projects/111111111111/machineTypes/n1-standard-1 +read: true +socket: +body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_external_ip_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_external_ip_request.yaml new file mode 100644 index 0000000000..a34eea66ea --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_external_ip_request.yaml @@ -0,0 +1,15 @@ +--- !ruby/object:Net::HTTP::Get +method: GET +request_has_body: false +response_has_body: true +path: /computeMetadata/v1beta1/instance/network-interfaces/0/access-configs/0/external-ip +header: + accept: + - ! '*/*' + user-agent: + - Ruby + host: + - metadata +body: +body_stream: +body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_external_ip_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_external_ip_response.yaml new file mode 100644 index 0000000000..659ec68981 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_external_ip_response.yaml @@ -0,0 +1,23 @@ +--- !ruby/object:Net::HTTPOK +http_version: '1.1' +code: '200' +message: OK +header: + content-type: + - application/text + etag: + - f21b7bdff7c43b94 + date: + - Sat, 17 Aug 2013 17:27:19 GMT + server: + - Metadata Server for VM + content-length: + - '13' + x-xss-protection: + - 1; mode=block + x-frame-options: + - SAMEORIGIN +body: 108.59.80.149 +read: true +socket: +body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_request.yaml new file mode 100644 index 0000000000..26e6db75d7 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_request.yaml @@ -0,0 +1,15 @@ +--- !ruby/object:Net::HTTP::Get +method: GET +request_has_body: false +response_has_body: true +path: /computeMetadata/v1beta1/instance/network-interfaces/0/access-configs/0/ +header: + accept: + - ! '*/*' + user-agent: + - Ruby + host: + - metadata +body: +body_stream: +body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_response.yaml new file mode 100644 index 0000000000..cc108bb57a --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_response.yaml @@ -0,0 +1,27 @@ +--- !ruby/object:Net::HTTPOK +http_version: '1.1' +code: '200' +message: OK +header: + content-type: + - application/text + etag: + - 94d53a9d51a160b2 + date: + - Sat, 17 Aug 2013 17:27:19 GMT + server: + - Metadata Server for VM + content-length: + - '17' + x-xss-protection: + - 1; mode=block + x-frame-options: + - SAMEORIGIN +body: ! 'external-ip + + type + +' +read: true +socket: +body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_type_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_type_request.yaml new file mode 100644 index 0000000000..ffcf51b22e --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_type_request.yaml @@ -0,0 +1,15 @@ +--- !ruby/object:Net::HTTP::Get +method: GET +request_has_body: false +response_has_body: true +path: /computeMetadata/v1beta1/instance/network-interfaces/0/access-configs/0/type +header: + accept: + - ! '*/*' + user-agent: + - Ruby + host: + - metadata +body: +body_stream: +body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_type_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_type_response.yaml new file mode 100644 index 0000000000..39acba1538 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_type_response.yaml @@ -0,0 +1,23 @@ +--- !ruby/object:Net::HTTPOK +http_version: '1.1' +code: '200' +message: OK +header: + content-type: + - application/text + etag: + - 3f039588d5138075 + date: + - Sat, 17 Aug 2013 17:27:19 GMT + server: + - Metadata Server for VM + content-length: + - '14' + x-xss-protection: + - 1; mode=block + x-frame-options: + - SAMEORIGIN +body: ONE_TO_ONE_NAT +read: true +socket: +body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_request.yaml new file mode 100644 index 0000000000..2e6a7c9d9e --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_request.yaml @@ -0,0 +1,15 @@ +--- !ruby/object:Net::HTTP::Get +method: GET +request_has_body: false +response_has_body: true +path: /computeMetadata/v1beta1/instance/network-interfaces/0/access-configs/ +header: + accept: + - ! '*/*' + user-agent: + - Ruby + host: + - metadata +body: +body_stream: +body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_response.yaml new file mode 100644 index 0000000000..5f66ec7004 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_response.yaml @@ -0,0 +1,25 @@ +--- !ruby/object:Net::HTTPOK +http_version: '1.1' +code: '200' +message: OK +header: + content-type: + - application/text + etag: + - ab12d55d87368cc3 + date: + - Sat, 17 Aug 2013 17:27:19 GMT + server: + - Metadata Server for VM + content-length: + - '3' + x-xss-protection: + - 1; mode=block + x-frame-options: + - SAMEORIGIN +body: ! '0/ + +' +read: true +socket: +body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_forwarded_ips_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_forwarded_ips_request.yaml new file mode 100644 index 0000000000..ccaae9c45f --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_forwarded_ips_request.yaml @@ -0,0 +1,15 @@ +--- !ruby/object:Net::HTTP::Get +method: GET +request_has_body: false +response_has_body: true +path: /computeMetadata/v1beta1/instance/network-interfaces/0/forwarded-ips/ +header: + accept: + - ! '*/*' + user-agent: + - Ruby + host: + - metadata +body: +body_stream: +body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_forwarded_ips_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_forwarded_ips_response.yaml new file mode 100644 index 0000000000..bc0df5bbc3 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_forwarded_ips_response.yaml @@ -0,0 +1,23 @@ +--- !ruby/object:Net::HTTPOK +http_version: '1.1' +code: '200' +message: OK +header: + content-type: + - application/text + etag: + - 3c19e4d76229ba8c + date: + - Sat, 17 Aug 2013 17:27:19 GMT + server: + - Metadata Server for VM + content-length: + - '0' + x-xss-protection: + - 1; mode=block + x-frame-options: + - SAMEORIGIN +body: '' +read: true +socket: +body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_ip_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_ip_request.yaml new file mode 100644 index 0000000000..f2705c05aa --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_ip_request.yaml @@ -0,0 +1,15 @@ +--- !ruby/object:Net::HTTP::Get +method: GET +request_has_body: false +response_has_body: true +path: /computeMetadata/v1beta1/instance/network-interfaces/0/ip +header: + accept: + - ! '*/*' + user-agent: + - Ruby + host: + - metadata +body: +body_stream: +body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_ip_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_ip_response.yaml new file mode 100644 index 0000000000..7f27e2f37e --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_ip_response.yaml @@ -0,0 +1,23 @@ +--- !ruby/object:Net::HTTPOK +http_version: '1.1' +code: '200' +message: OK +header: + content-type: + - application/text + etag: + - 7b9925a822e85501 + date: + - Sat, 17 Aug 2013 17:27:19 GMT + server: + - Metadata Server for VM + content-length: + - '13' + x-xss-protection: + - 1; mode=block + x-frame-options: + - SAMEORIGIN +body: 10.240.75.158 +read: true +socket: +body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_network_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_network_request.yaml new file mode 100644 index 0000000000..c200fddf15 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_network_request.yaml @@ -0,0 +1,15 @@ +--- !ruby/object:Net::HTTP::Get +method: GET +request_has_body: false +response_has_body: true +path: /computeMetadata/v1beta1/instance/network-interfaces/0/network +header: + accept: + - ! '*/*' + user-agent: + - Ruby + host: + - metadata +body: +body_stream: +body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_network_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_network_response.yaml new file mode 100644 index 0000000000..7f561fc613 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_network_response.yaml @@ -0,0 +1,23 @@ +--- !ruby/object:Net::HTTPOK +http_version: '1.1' +code: '200' +message: OK +header: + content-type: + - application/text + etag: + - b978938d33323a32 + date: + - Sat, 17 Aug 2013 17:27:19 GMT + server: + - Metadata Server for VM + content-length: + - '38' + x-xss-protection: + - 1; mode=block + x-frame-options: + - SAMEORIGIN +body: projects/111111111111/networks/default +read: true +socket: +body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_request.yaml new file mode 100644 index 0000000000..307b854d94 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_request.yaml @@ -0,0 +1,15 @@ +--- !ruby/object:Net::HTTP::Get +method: GET +request_has_body: false +response_has_body: true +path: /computeMetadata/v1beta1/instance/network-interfaces/0/ +header: + accept: + - ! '*/*' + user-agent: + - Ruby + host: + - metadata +body: +body_stream: +body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_response.yaml new file mode 100644 index 0000000000..47b7de2cf2 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_response.yaml @@ -0,0 +1,31 @@ +--- !ruby/object:Net::HTTPOK +http_version: '1.1' +code: '200' +message: OK +header: + content-type: + - application/text + etag: + - 8aeb9aad2dc89639 + date: + - Sat, 17 Aug 2013 17:27:19 GMT + server: + - Metadata Server for VM + content-length: + - '42' + x-xss-protection: + - 1; mode=block + x-frame-options: + - SAMEORIGIN +body: ! 'access-configs/ + + forwarded-ips/ + + ip + + network + +' +read: true +socket: +body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_request.yaml new file mode 100644 index 0000000000..ef689604b8 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_request.yaml @@ -0,0 +1,15 @@ +--- !ruby/object:Net::HTTP::Get +method: GET +request_has_body: false +response_has_body: true +path: /computeMetadata/v1beta1/instance/network-interfaces/ +header: + accept: + - ! '*/*' + user-agent: + - Ruby + host: + - metadata +body: +body_stream: +body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_response.yaml new file mode 100644 index 0000000000..5f66ec7004 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_response.yaml @@ -0,0 +1,25 @@ +--- !ruby/object:Net::HTTPOK +http_version: '1.1' +code: '200' +message: OK +header: + content-type: + - application/text + etag: + - ab12d55d87368cc3 + date: + - Sat, 17 Aug 2013 17:27:19 GMT + server: + - Metadata Server for VM + content-length: + - '3' + x-xss-protection: + - 1; mode=block + x-frame-options: + - SAMEORIGIN +body: ! '0/ + +' +read: true +socket: +body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_request.yaml new file mode 100644 index 0000000000..bf6ad02d34 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_request.yaml @@ -0,0 +1,15 @@ +--- !ruby/object:Net::HTTP::Get +method: GET +request_has_body: false +response_has_body: true +path: /computeMetadata/v1beta1/instance/ +header: + accept: + - ! '*/*' + user-agent: + - Ruby + host: + - metadata +body: +body_stream: +body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_response.yaml new file mode 100644 index 0000000000..027ca82587 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_response.yaml @@ -0,0 +1,45 @@ +--- !ruby/object:Net::HTTPOK +http_version: '1.1' +code: '200' +message: OK +header: + content-type: + - application/text + etag: + - d2c43af72b3b62b0 + date: + - Sat, 17 Aug 2013 17:27:19 GMT + server: + - Metadata Server for VM + content-length: + - '110' + x-xss-protection: + - 1; mode=block + x-frame-options: + - SAMEORIGIN +body: ! 'attributes/ + + description + + disks/ + + hostname + + id + + image + + machine-type + + network-interfaces/ + + service-accounts/ + + tags + + zone + +' +read: true +socket: +body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_aliases_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_aliases_request.yaml new file mode 100644 index 0000000000..ddedde0faf --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_aliases_request.yaml @@ -0,0 +1,15 @@ +--- !ruby/object:Net::HTTP::Get +method: GET +request_has_body: false +response_has_body: true +path: /computeMetadata/v1beta1/instance/service-accounts/111111111111@project.gserviceaccount.com/aliases +header: + accept: + - ! '*/*' + user-agent: + - Ruby + host: + - metadata +body: +body_stream: +body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_aliases_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_aliases_response.yaml new file mode 100644 index 0000000000..bf9f0a0494 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_aliases_response.yaml @@ -0,0 +1,25 @@ +--- !ruby/object:Net::HTTPOK +http_version: '1.1' +code: '200' +message: OK +header: + content-type: + - application/text + etag: + - b4247236bff543a4 + date: + - Sat, 17 Aug 2013 17:27:19 GMT + server: + - Metadata Server for VM + content-length: + - '8' + x-xss-protection: + - 1; mode=block + x-frame-options: + - SAMEORIGIN +body: ! 'default + +' +read: true +socket: +body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_email_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_email_request.yaml new file mode 100644 index 0000000000..a3e2e5da07 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_email_request.yaml @@ -0,0 +1,15 @@ +--- !ruby/object:Net::HTTP::Get +method: GET +request_has_body: false +response_has_body: true +path: /computeMetadata/v1beta1/instance/service-accounts/111111111111@project.gserviceaccount.com/email +header: + accept: + - ! '*/*' + user-agent: + - Ruby + host: + - metadata +body: +body_stream: +body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_email_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_email_response.yaml new file mode 100644 index 0000000000..09c5dd7421 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_email_response.yaml @@ -0,0 +1,23 @@ +--- !ruby/object:Net::HTTPOK +http_version: '1.1' +code: '200' +message: OK +header: + content-type: + - application/text + etag: + - 37ec425b66a500d + date: + - Sat, 17 Aug 2013 17:27:19 GMT + server: + - Metadata Server for VM + content-length: + - '40' + x-xss-protection: + - 1; mode=block + x-frame-options: + - SAMEORIGIN +body: 111111111111@project.gserviceaccount.com +read: true +socket: +body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_request.yaml new file mode 100644 index 0000000000..3d06dcec70 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_request.yaml @@ -0,0 +1,15 @@ +--- !ruby/object:Net::HTTP::Get +method: GET +request_has_body: false +response_has_body: true +path: /computeMetadata/v1beta1/instance/service-accounts/111111111111@project.gserviceaccount.com/ +header: + accept: + - ! '*/*' + user-agent: + - Ruby + host: + - metadata +body: +body_stream: +body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_response.yaml new file mode 100644 index 0000000000..fd3f671a9d --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_response.yaml @@ -0,0 +1,31 @@ +--- !ruby/object:Net::HTTPOK +http_version: '1.1' +code: '200' +message: OK +header: + content-type: + - application/text + etag: + - 2357256c2bcc7636 + date: + - Sat, 17 Aug 2013 17:27:19 GMT + server: + - Metadata Server for VM + content-length: + - '27' + x-xss-protection: + - 1; mode=block + x-frame-options: + - SAMEORIGIN +body: ! 'aliases + + email + + scopes + + token + +' +read: true +socket: +body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_scopes_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_scopes_request.yaml new file mode 100644 index 0000000000..8a886be1fe --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_scopes_request.yaml @@ -0,0 +1,15 @@ +--- !ruby/object:Net::HTTP::Get +method: GET +request_has_body: false +response_has_body: true +path: /computeMetadata/v1beta1/instance/service-accounts/111111111111@project.gserviceaccount.com/scopes +header: + accept: + - ! '*/*' + user-agent: + - Ruby + host: + - metadata +body: +body_stream: +body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_scopes_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_scopes_response.yaml new file mode 100644 index 0000000000..1dfe1c8e25 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_scopes_response.yaml @@ -0,0 +1,29 @@ +--- !ruby/object:Net::HTTPOK +http_version: '1.1' +code: '200' +message: OK +header: + content-type: + - application/text + etag: + - 7a6c63608b17425d + date: + - Sat, 17 Aug 2013 17:27:19 GMT + server: + - Metadata Server for VM + content-length: + - '143' + x-xss-protection: + - 1; mode=block + x-frame-options: + - SAMEORIGIN +body: ! 'https://www.googleapis.com/auth/userinfo.email++%20%20https://www.googleapis.com/auth/compute++%20%20https://www.googleapis.com/auth/devstorage.full_control++' +read: true +socket: +body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_token_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_token_request.yaml new file mode 100644 index 0000000000..b29f9ebb61 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_token_request.yaml @@ -0,0 +1,15 @@ +--- !ruby/object:Net::HTTP::Get +method: GET +request_has_body: false +response_has_body: true +path: /computeMetadata/v1beta1/instance/service-accounts/111111111111@project.gserviceaccount.com/token +header: + accept: + - ! '*/*' + user-agent: + - Ruby + host: + - metadata +body: +body_stream: +body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_token_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_token_response.yaml new file mode 100644 index 0000000000..fcb4836b7e --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_token_response.yaml @@ -0,0 +1,21 @@ +--- !ruby/object:Net::HTTPOK +http_version: '1.1' +code: '200' +message: OK +header: + content-type: + - application/json + date: + - Sat, 17 Aug 2013 17:27:19 GMT + server: + - Metadata Server for VM + content-length: + - '114' + x-xss-protection: + - 1; mode=block + x-frame-options: + - SAMEORIGIN +body: ! '{"access_token":"ya29.AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","expires_in":1234,"token_type":"Bearer"}' +read: true +socket: +body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_aliases_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_aliases_request.yaml new file mode 100644 index 0000000000..78ef2bddad --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_aliases_request.yaml @@ -0,0 +1,15 @@ +--- !ruby/object:Net::HTTP::Get +method: GET +request_has_body: false +response_has_body: true +path: /computeMetadata/v1beta1/instance/service-accounts/default/aliases +header: + accept: + - ! '*/*' + user-agent: + - Ruby + host: + - metadata +body: +body_stream: +body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_aliases_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_aliases_response.yaml new file mode 100644 index 0000000000..bf9f0a0494 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_aliases_response.yaml @@ -0,0 +1,25 @@ +--- !ruby/object:Net::HTTPOK +http_version: '1.1' +code: '200' +message: OK +header: + content-type: + - application/text + etag: + - b4247236bff543a4 + date: + - Sat, 17 Aug 2013 17:27:19 GMT + server: + - Metadata Server for VM + content-length: + - '8' + x-xss-protection: + - 1; mode=block + x-frame-options: + - SAMEORIGIN +body: ! 'default + +' +read: true +socket: +body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_email_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_email_request.yaml new file mode 100644 index 0000000000..db6789e875 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_email_request.yaml @@ -0,0 +1,15 @@ +--- !ruby/object:Net::HTTP::Get +method: GET +request_has_body: false +response_has_body: true +path: /computeMetadata/v1beta1/instance/service-accounts/default/email +header: + accept: + - ! '*/*' + user-agent: + - Ruby + host: + - metadata +body: +body_stream: +body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_email_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_email_response.yaml new file mode 100644 index 0000000000..09c5dd7421 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_email_response.yaml @@ -0,0 +1,23 @@ +--- !ruby/object:Net::HTTPOK +http_version: '1.1' +code: '200' +message: OK +header: + content-type: + - application/text + etag: + - 37ec425b66a500d + date: + - Sat, 17 Aug 2013 17:27:19 GMT + server: + - Metadata Server for VM + content-length: + - '40' + x-xss-protection: + - 1; mode=block + x-frame-options: + - SAMEORIGIN +body: 111111111111@project.gserviceaccount.com +read: true +socket: +body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_request.yaml new file mode 100644 index 0000000000..64be4d4133 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_request.yaml @@ -0,0 +1,15 @@ +--- !ruby/object:Net::HTTP::Get +method: GET +request_has_body: false +response_has_body: true +path: /computeMetadata/v1beta1/instance/service-accounts/default/ +header: + accept: + - ! '*/*' + user-agent: + - Ruby + host: + - metadata +body: +body_stream: +body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_response.yaml new file mode 100644 index 0000000000..fd3f671a9d --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_response.yaml @@ -0,0 +1,31 @@ +--- !ruby/object:Net::HTTPOK +http_version: '1.1' +code: '200' +message: OK +header: + content-type: + - application/text + etag: + - 2357256c2bcc7636 + date: + - Sat, 17 Aug 2013 17:27:19 GMT + server: + - Metadata Server for VM + content-length: + - '27' + x-xss-protection: + - 1; mode=block + x-frame-options: + - SAMEORIGIN +body: ! 'aliases + + email + + scopes + + token + +' +read: true +socket: +body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_scopes_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_scopes_request.yaml new file mode 100644 index 0000000000..36ec584a6b --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_scopes_request.yaml @@ -0,0 +1,15 @@ +--- !ruby/object:Net::HTTP::Get +method: GET +request_has_body: false +response_has_body: true +path: /computeMetadata/v1beta1/instance/service-accounts/default/scopes +header: + accept: + - ! '*/*' + user-agent: + - Ruby + host: + - metadata +body: +body_stream: +body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_scopes_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_scopes_response.yaml new file mode 100644 index 0000000000..1dfe1c8e25 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_scopes_response.yaml @@ -0,0 +1,29 @@ +--- !ruby/object:Net::HTTPOK +http_version: '1.1' +code: '200' +message: OK +header: + content-type: + - application/text + etag: + - 7a6c63608b17425d + date: + - Sat, 17 Aug 2013 17:27:19 GMT + server: + - Metadata Server for VM + content-length: + - '143' + x-xss-protection: + - 1; mode=block + x-frame-options: + - SAMEORIGIN +body: ! 'https://www.googleapis.com/auth/userinfo.email++%20%20https://www.googleapis.com/auth/compute++%20%20https://www.googleapis.com/auth/devstorage.full_control++' +read: true +socket: +body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_token_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_token_request.yaml new file mode 100644 index 0000000000..bd4a1c1a92 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_token_request.yaml @@ -0,0 +1,15 @@ +--- !ruby/object:Net::HTTP::Get +method: GET +request_has_body: false +response_has_body: true +path: /computeMetadata/v1beta1/instance/service-accounts/default/token +header: + accept: + - ! '*/*' + user-agent: + - Ruby + host: + - metadata +body: +body_stream: +body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_token_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_token_response.yaml new file mode 100644 index 0000000000..fcb4836b7e --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_token_response.yaml @@ -0,0 +1,21 @@ +--- !ruby/object:Net::HTTPOK +http_version: '1.1' +code: '200' +message: OK +header: + content-type: + - application/json + date: + - Sat, 17 Aug 2013 17:27:19 GMT + server: + - Metadata Server for VM + content-length: + - '114' + x-xss-protection: + - 1; mode=block + x-frame-options: + - SAMEORIGIN +body: ! '{"access_token":"ya29.AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","expires_in":1234,"token_type":"Bearer"}' +read: true +socket: +body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_request.yaml new file mode 100644 index 0000000000..84af1c28d4 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_request.yaml @@ -0,0 +1,15 @@ +--- !ruby/object:Net::HTTP::Get +method: GET +request_has_body: false +response_has_body: true +path: /computeMetadata/v1beta1/instance/service-accounts/ +header: + accept: + - ! '*/*' + user-agent: + - Ruby + host: + - metadata +body: +body_stream: +body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_response.yaml new file mode 100644 index 0000000000..8c45b73ea3 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_response.yaml @@ -0,0 +1,27 @@ +--- !ruby/object:Net::HTTPOK +http_version: '1.1' +code: '200' +message: OK +header: + content-type: + - application/text + etag: + - 5f077465e28c38fc + date: + - Sat, 17 Aug 2013 17:27:19 GMT + server: + - Metadata Server for VM + content-length: + - '51' + x-xss-protection: + - 1; mode=block + x-frame-options: + - SAMEORIGIN +body: ! '111111111111@project.gserviceaccount.com/ + + default/ + +' +read: true +socket: +body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_tags_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_tags_request.yaml new file mode 100644 index 0000000000..d5c428fc0f --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_tags_request.yaml @@ -0,0 +1,15 @@ +--- !ruby/object:Net::HTTP::Get +method: GET +request_has_body: false +response_has_body: true +path: /computeMetadata/v1beta1/instance/tags +header: + accept: + - ! '*/*' + user-agent: + - Ruby + host: + - metadata +body: +body_stream: +body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_tags_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_tags_response.yaml new file mode 100644 index 0000000000..38931c104e --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_tags_response.yaml @@ -0,0 +1,23 @@ +--- !ruby/object:Net::HTTPOK +http_version: '1.1' +code: '200' +message: OK +header: + content-type: + - application/json + etag: + - d8f74779f132891f + date: + - Sat, 17 Aug 2013 17:27:19 GMT + server: + - Metadata Server for VM + content-length: + - '22' + x-xss-protection: + - 1; mode=block + x-frame-options: + - SAMEORIGIN +body: ! '["taga","tagb","tagc"]' +read: true +socket: +body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_zone_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_zone_request.yaml new file mode 100644 index 0000000000..4670dd125d --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_zone_request.yaml @@ -0,0 +1,15 @@ +--- !ruby/object:Net::HTTP::Get +method: GET +request_has_body: false +response_has_body: true +path: /computeMetadata/v1beta1/instance/zone +header: + accept: + - ! '*/*' + user-agent: + - Ruby + host: + - metadata +body: +body_stream: +body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_zone_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_zone_response.yaml new file mode 100644 index 0000000000..53c2ab6283 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_instance_zone_response.yaml @@ -0,0 +1,23 @@ +--- !ruby/object:Net::HTTPOK +http_version: '1.1' +code: '200' +message: OK +header: + content-type: + - application/text + etag: + - a4c6308ff0045544 + date: + - Sat, 17 Aug 2013 17:27:19 GMT + server: + - Metadata Server for VM + content-length: + - '41' + x-xss-protection: + - 1; mode=block + x-frame-options: + - SAMEORIGIN +body: projects/111111111111/zones/us-central1-b +read: true +socket: +body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_project_attributes_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_project_attributes_request.yaml new file mode 100644 index 0000000000..8ae6d17a76 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_project_attributes_request.yaml @@ -0,0 +1,15 @@ +--- !ruby/object:Net::HTTP::Get +method: GET +request_has_body: false +response_has_body: true +path: /computeMetadata/v1beta1/project/attributes/ +header: + accept: + - ! '*/*' + user-agent: + - Ruby + host: + - metadata +body: +body_stream: +body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_project_attributes_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_project_attributes_response.yaml new file mode 100644 index 0000000000..4586a7b60e --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_project_attributes_response.yaml @@ -0,0 +1,25 @@ +--- !ruby/object:Net::HTTPOK +http_version: '1.1' +code: '200' +message: OK +header: + content-type: + - application/text + etag: + - 4b14c7cf9ef3ff05 + date: + - Sat, 17 Aug 2013 17:27:19 GMT + server: + - Metadata Server for VM + content-length: + - '8' + x-xss-protection: + - 1; mode=block + x-frame-options: + - SAMEORIGIN +body: ! 'sshKeys + +' +read: true +socket: +body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_project_attributes_sshKeys_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_project_attributes_sshKeys_request.yaml new file mode 100644 index 0000000000..434128c228 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_project_attributes_sshKeys_request.yaml @@ -0,0 +1,15 @@ +--- !ruby/object:Net::HTTP::Get +method: GET +request_has_body: false +response_has_body: true +path: /computeMetadata/v1beta1/project/attributes/sshKeys +header: + accept: + - ! '*/*' + user-agent: + - Ruby + host: + - metadata +body: +body_stream: +body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_project_attributes_sshKeys_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_project_attributes_sshKeys_response.yaml new file mode 100644 index 0000000000..276783ca1b --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_project_attributes_sshKeys_response.yaml @@ -0,0 +1,25 @@ +--- !ruby/object:Net::HTTPOK +http_version: '1.1' +code: '200' +message: OK +header: + content-type: + - application/text + etag: + - c96b7e65135ea515 + date: + - Sat, 17 Aug 2013 17:27:19 GMT + server: + - Metadata Server for VM + content-length: + - '44' + x-xss-protection: + - 1; mode=block + x-frame-options: + - SAMEORIGIN +body: ! 'googler:ssh-rsa AAA...ej googler@facter-dev + +' +read: true +socket: +body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_project_numeric_project_id_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_project_numeric_project_id_request.yaml new file mode 100644 index 0000000000..4e8976e8b5 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_project_numeric_project_id_request.yaml @@ -0,0 +1,15 @@ +--- !ruby/object:Net::HTTP::Get +method: GET +request_has_body: false +response_has_body: true +path: /computeMetadata/v1beta1/project/numeric-project-id +header: + accept: + - ! '*/*' + user-agent: + - Ruby + host: + - metadata +body: +body_stream: +body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_project_numeric_project_id_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_project_numeric_project_id_response.yaml new file mode 100644 index 0000000000..d9e1a26f91 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_project_numeric_project_id_response.yaml @@ -0,0 +1,23 @@ +--- !ruby/object:Net::HTTPOK +http_version: '1.1' +code: '200' +message: OK +header: + content-type: + - application/text + etag: + - b3e3edd326c1b6c8 + date: + - Sat, 17 Aug 2013 17:27:19 GMT + server: + - Metadata Server for VM + content-length: + - '12' + x-xss-protection: + - 1; mode=block + x-frame-options: + - SAMEORIGIN +body: '111111111111' +read: true +socket: +body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_project_project_id_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_project_project_id_request.yaml new file mode 100644 index 0000000000..76a60ca56f --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_project_project_id_request.yaml @@ -0,0 +1,15 @@ +--- !ruby/object:Net::HTTP::Get +method: GET +request_has_body: false +response_has_body: true +path: /computeMetadata/v1beta1/project/project-id +header: + accept: + - ! '*/*' + user-agent: + - Ruby + host: + - metadata +body: +body_stream: +body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_project_project_id_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_project_project_id_response.yaml new file mode 100644 index 0000000000..0ac920c85a --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_project_project_id_response.yaml @@ -0,0 +1,23 @@ +--- !ruby/object:Net::HTTPOK +http_version: '1.1' +code: '200' +message: OK +header: + content-type: + - application/text + etag: + - c304e9e2bacfa408 + date: + - Sat, 17 Aug 2013 17:27:19 GMT + server: + - Metadata Server for VM + content-length: + - '19' + x-xss-protection: + - 1; mode=block + x-frame-options: + - SAMEORIGIN +body: development_project +read: true +socket: +body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_project_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_project_request.yaml new file mode 100644 index 0000000000..0e0d52c482 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_project_request.yaml @@ -0,0 +1,15 @@ +--- !ruby/object:Net::HTTP::Get +method: GET +request_has_body: false +response_has_body: true +path: /computeMetadata/v1beta1/project/ +header: + accept: + - ! '*/*' + user-agent: + - Ruby + host: + - metadata +body: +body_stream: +body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_project_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_project_response.yaml new file mode 100644 index 0000000000..66aa2203b9 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_project_response.yaml @@ -0,0 +1,29 @@ +--- !ruby/object:Net::HTTPOK +http_version: '1.1' +code: '200' +message: OK +header: + content-type: + - application/text + etag: + - 7b505a6ed13cdb4c + date: + - Sat, 17 Aug 2013 17:27:19 GMT + server: + - Metadata Server for VM + content-length: + - '42' + x-xss-protection: + - 1; mode=block + x-frame-options: + - SAMEORIGIN +body: ! 'attributes/ + + numeric-project-id + + project-id + +' +read: true +socket: +body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_request.yaml new file mode 100644 index 0000000000..74ed49e2ff --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_request.yaml @@ -0,0 +1,15 @@ +--- !ruby/object:Net::HTTP::Get +method: GET +request_has_body: false +response_has_body: true +path: /computeMetadata/v1beta1/ +header: + accept: + - ! '*/*' + user-agent: + - Ruby + host: + - metadata +body: +body_stream: +body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_response.yaml new file mode 100644 index 0000000000..8a9c476da4 --- /dev/null +++ b/spec/fixtures/unit/util/gce/gce_metadata_response.yaml @@ -0,0 +1,27 @@ +--- !ruby/object:Net::HTTPOK +http_version: '1.1' +code: '200' +message: OK +header: + content-type: + - application/text + etag: + - 5fa0a7665355d01e + date: + - Sat, 17 Aug 2013 17:27:19 GMT + server: + - Metadata Server for VM + content-length: + - '19' + x-xss-protection: + - 1; mode=block + x-frame-options: + - SAMEORIGIN +body: ! 'instance/ + + project/ + +' +read: true +socket: +body_exist: true diff --git a/spec/unit/gce_spec.rb b/spec/unit/gce_spec.rb new file mode 100755 index 0000000000..61d9c1659b --- /dev/null +++ b/spec/unit/gce_spec.rb @@ -0,0 +1,38 @@ +#! /usr/bin/env ruby + +require 'spec_helper' +require 'facter/util/gce' + +describe "gce facts" do + # This is the standard prefix for making an API call in GCE (or fake) + # environments. + let(:api_prefix) { "http://metadata/computeMetadata" } + let(:api_version) { "v1beta1" } + + describe "when running on gce" do + before :each do + # Assume we can connect + Facter::Util::GCE.stubs(:can_connect?).returns(true) + Facter::Util::GCE.stubs(:read_uri). + with('http://metadata/').returns('OK') + Facter.stubs(:value). + with('virtual').returns('gce') + end + + let :util do + Facter::Util::GCE + end + + it "defines facts dynamically from metadata/" do + util.stubs(:read_uri). + with("#{api_prefix}/#{api_version}/?recursive=true&alt=json"). + returns('{"some_key_name":"some_key_value"}') + + Facter::Util::GCE.add_gce_facts(:force => true) + + Facter.fact(:gce_some_key_name). + value.should == "some_key_value" + end + + end +end diff --git a/spec/unit/util/gce_spec.rb b/spec/unit/util/gce_spec.rb new file mode 100755 index 0000000000..7135658afa --- /dev/null +++ b/spec/unit/util/gce_spec.rb @@ -0,0 +1,48 @@ +#! /usr/bin/env ruby + +require 'spec_helper' +require 'facter/util/gce' + +describe Facter::Util::GCE do + # This is the standard prefix for making an API call in GCE (or fake) + # environments. + let(:api_prefix) { "http://metadata/computeMetadata" } + let(:api_version) { "v1beta1" } + + describe "Facter::Util::GCE.with_metadata_server", :focus => true do + before :each do + Facter::Util::GCE.stubs(:read_uri).returns(:api_version) + end + + subject do + Facter::Util::GCE.with_metadata_server do + true + end + end + + it 'returns false when not running on gce' do + Facter.stubs(:value).with('virtual').returns('vmware') + subject.should be_false + end + + context 'default options and running on a gce virtual machine' do + before :each do + Facter.stubs(:value).with('virtual').returns('gce') + end + it 'returns false when the metadata server is unreachable' do + described_class.stubs(:read_uri).raises(Errno::ENETUNREACH) + subject.should be_false + end + it 'does not execute the block if the connection raises an exception' do + described_class.stubs(:read_uri).raises(Timeout::Error) + subject.should be_false + end + it 'succeeds on the third retry' do + retry_metadata = sequence('metadata') + Timeout.expects(:timeout).twice.in_sequence(retry_metadata).raises(Timeout::Error) + Timeout.expects(:timeout).once.in_sequence(retry_metadata).returns(true) + subject.should be_true + end + end + end +end From 113ddc2960d5a685956d3e654d4ecfe9cff65706 Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Sat, 17 Aug 2013 17:40:45 +0000 Subject: [PATCH 1383/3753] (#22233) Add license information for GCE facts Without this patch there is no clear licensing information included in the GCE facts. This patch adds the license headers. --- lib/facter/gce.rb | 14 ++++++++++++++ spec/unit/gce_spec.rb | 13 +++++++++++++ spec/unit/util/gce_spec.rb | 13 +++++++++++++ 3 files changed, 40 insertions(+) diff --git a/lib/facter/gce.rb b/lib/facter/gce.rb index c77054abda..9ab8477c2b 100644 --- a/lib/facter/gce.rb +++ b/lib/facter/gce.rb @@ -1,3 +1,17 @@ +# Copyright 2013 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + require 'facter/util/gce' Facter::Util::GCE.add_gce_facts diff --git a/spec/unit/gce_spec.rb b/spec/unit/gce_spec.rb index 61d9c1659b..ac959b2263 100755 --- a/spec/unit/gce_spec.rb +++ b/spec/unit/gce_spec.rb @@ -1,4 +1,17 @@ #! /usr/bin/env ruby +# Copyright 2013 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. require 'spec_helper' require 'facter/util/gce' diff --git a/spec/unit/util/gce_spec.rb b/spec/unit/util/gce_spec.rb index 7135658afa..4833f7b893 100755 --- a/spec/unit/util/gce_spec.rb +++ b/spec/unit/util/gce_spec.rb @@ -1,4 +1,17 @@ #! /usr/bin/env ruby +# Copyright 2013 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. require 'spec_helper' require 'facter/util/gce' From 110bfc28215cbc1ac3d56e6059de35c88c69d73c Mon Sep 17 00:00:00 2001 From: Josh Partlow Date: Wed, 21 Aug 2013 13:08:10 -0700 Subject: [PATCH 1384/3753] (#22233) Guarded json require for gce fact The gce fact was requiring json, creating a dependency in Facter where non had existed (except on development/test instances). This was producing unwanted errors in Ruby 1.8.7 where the json gem was not present, and no gce facts were available anyway. This commit ensures that we only attempt to load json if we are in the gce environment (as determined by a metadata url resolving) and actually have gce data to parse. --- lib/facter/util/gce.rb | 10 +++++++++- spec/unit/util/gce_spec.rb | 14 +++++++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/lib/facter/util/gce.rb b/lib/facter/util/gce.rb index 39e970e74e..67d96319c4 100644 --- a/lib/facter/util/gce.rb +++ b/lib/facter/util/gce.rb @@ -22,7 +22,6 @@ require 'timeout' require 'open-uri' -require 'json' # Provide a set of utility static methods that help with resolving the GCE # fact. @@ -110,6 +109,7 @@ def self.with_metadata_server(options = {}) # Read the list of supported API versions Timeout.timeout(timeout) do if body = read_uri("#{METADATA_URL}") + require_json metadata_facts("gce", JSON.parse(body)) end end @@ -150,4 +150,12 @@ def self.add_gce_facts(options = {}) @add_gce_facts_has_run = true with_metadata_server :timeout => 50 end + + private + + # @api private + def self.require_json + raise(LoadError, "no json gem") if !Facter.json? + end + end diff --git a/spec/unit/util/gce_spec.rb b/spec/unit/util/gce_spec.rb index 4833f7b893..779f40b486 100755 --- a/spec/unit/util/gce_spec.rb +++ b/spec/unit/util/gce_spec.rb @@ -28,9 +28,7 @@ end subject do - Facter::Util::GCE.with_metadata_server do - true - end + Facter::Util::GCE.with_metadata_server end it 'returns false when not running on gce' do @@ -39,23 +37,33 @@ end context 'default options and running on a gce virtual machine' do + before :each do Facter.stubs(:value).with('virtual').returns('gce') end + it 'returns false when the metadata server is unreachable' do described_class.stubs(:read_uri).raises(Errno::ENETUNREACH) subject.should be_false end + it 'does not execute the block if the connection raises an exception' do described_class.stubs(:read_uri).raises(Timeout::Error) subject.should be_false end + it 'succeeds on the third retry' do retry_metadata = sequence('metadata') Timeout.expects(:timeout).twice.in_sequence(retry_metadata).raises(Timeout::Error) Timeout.expects(:timeout).once.in_sequence(retry_metadata).returns(true) subject.should be_true end + + it 'raises an error if json gem is not present' do + Facter.stubs(:json?).returns(false) + described_class.stubs(:read_uri).returns('{"some":"json"}') + expect { subject }.to raise_error(LoadError, /json/) + end end end end From d376a6a5770b52d06f1f9be0e0be585cb23b5ad8 Mon Sep 17 00:00:00 2001 From: Josh Partlow Date: Wed, 21 Aug 2013 17:13:32 -0700 Subject: [PATCH 1385/3753] (maint) Pins redcarpet to < 3.0.0 for Ruby 1.8.7 The redcarpet gem 3.0.0 version does not work in Ruby 1.8.7. https://github.com/vmg/redcarpet/issues/280 --- Gemfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index d2d5db16ef..8949ba18b3 100644 --- a/Gemfile +++ b/Gemfile @@ -9,7 +9,8 @@ platforms :ruby do gem 'watchr', :group => :development gem 'pry', :group => :development gem 'yard', :group => :development - gem 'redcarpet', :group => :development + redcarpet_version = RUBY_VERSION =~ /^1\.8/ ? "< 3.0.0" : nil + gem 'redcarpet', redcarpet_version, :group => :development end group :development, :test do From f06d7d71fe615c58d7b5f67cc86ca02d4935b99c Mon Sep 17 00:00:00 2001 From: Josh Partlow Date: Wed, 21 Aug 2013 17:15:35 -0700 Subject: [PATCH 1386/3753] (#22233) Catch json load error for gce when no json gem present When the specs were run in an environment without the json gem, the gce_spec.rb would fail, because it was now throwing a LoadError. Splits the spec into two, guarded by Facter.json? for the expected cases with and without json present. --- spec/unit/gce_spec.rb | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/spec/unit/gce_spec.rb b/spec/unit/gce_spec.rb index ac959b2263..b147164841 100755 --- a/spec/unit/gce_spec.rb +++ b/spec/unit/gce_spec.rb @@ -36,16 +36,20 @@ Facter::Util::GCE end - it "defines facts dynamically from metadata/" do + it "defines facts dynamically from metadata/", :if => Facter.json? do util.stubs(:read_uri). with("#{api_prefix}/#{api_version}/?recursive=true&alt=json"). returns('{"some_key_name":"some_key_value"}') - Facter::Util::GCE.add_gce_facts(:force => true) - - Facter.fact(:gce_some_key_name). - value.should == "some_key_value" + Facter::Util::GCE.add_gce_facts(:force => true) + + Facter.fact(:gce_some_key_name). + value.should == "some_key_value" end + it "raises a LoadError if json gem is not present.", :unless => Facter.json? do + util.stubs(:read_uri).returns('{"some":"json"}') + expect { Facter::Util::GCE.add_gce_facts(:force => true) }.to raise_error(LoadError, /no json gem/) + end end end From bfac93844ef1a1481301503a6b3a73f638020c45 Mon Sep 17 00:00:00 2001 From: Garrett Wollman Date: Fri, 9 Mar 2012 17:51:14 -0500 Subject: [PATCH 1387/3753] (13051) Make the uniqueid for FreeBSD return the value of sysctl -n kern.hostid New patch, moves hostuuid to a separate source file and adds unit tests for both uniqueid and hostuuid on FreeBSD. This patch was submitted by Garrett Wollman in the description of #13051. --- lib/facter/hostuuid.rb | 6 ++++++ lib/facter/uniqueid.rb | 7 +++++++ spec/unit/hostuuid_spec.rb | 13 +++++++++++++ spec/unit/uniqueid_spec.rb | 7 +++++++ 4 files changed, 33 insertions(+) create mode 100644 lib/facter/hostuuid.rb create mode 100755 spec/unit/hostuuid_spec.rb diff --git a/lib/facter/hostuuid.rb b/lib/facter/hostuuid.rb new file mode 100644 index 0000000000..407b64f77c --- /dev/null +++ b/lib/facter/hostuuid.rb @@ -0,0 +1,6 @@ +Facter.add(:hostuuid) do + confine :kernel => :freebsd + setcode do + Facter::Util::Resolution.exec('sysctl -n kern.hostuuid') + end +end diff --git a/lib/facter/uniqueid.rb b/lib/facter/uniqueid.rb index df2ed87346..7a882ccb85 100644 --- a/lib/facter/uniqueid.rb +++ b/lib/facter/uniqueid.rb @@ -2,3 +2,10 @@ setcode 'hostid' confine :kernel => %w{SunOS Linux AIX GNU/kFreeBSD} end + +Facter.add(:uniqueid) do + confine :kernel => :freebsd + setcode do + Facter::Util::Resolution.exec('sysctl -n kern.hostid') + end +end diff --git a/spec/unit/hostuuid_spec.rb b/spec/unit/hostuuid_spec.rb new file mode 100755 index 0000000000..96f829d929 --- /dev/null +++ b/spec/unit/hostuuid_spec.rb @@ -0,0 +1,13 @@ +#!/usr/bin/env ruby + +require 'spec_helper' +require 'facter' + +describe "host UUID fact" do + it "should match kern.hostuuid on FreeBSD" do + Facter.fact(:kernel).stubs(:value).returns("FreeBSD") + Facter::Util::Resolution.stubs(:exec).with("sysctl -n kern.hostuuid").returns("a0391b10-6c8c-11e1-b960-001b21b8d7b0") + + Facter.fact(:hostuuid).value.should == "a0391b10-6c8c-11e1-b960-001b21b8d7b0" + end +end diff --git a/spec/unit/uniqueid_spec.rb b/spec/unit/uniqueid_spec.rb index 3ac9bbf49d..811d54e3f0 100755 --- a/spec/unit/uniqueid_spec.rb +++ b/spec/unit/uniqueid_spec.rb @@ -22,4 +22,11 @@ Facter.fact(:uniqueid).value.should == "Moe" end + + it "should match kern.hostid on FreeBSD" do + Facter.fact(:kernel).stubs(:value).returns("FreeBSD") + Facter::Util::Resolution.stubs(:exec).with("sysctl -n kern.hostid").returns("Shemp") + + Facter.fact(:uniqueid).value.should == "Shemp" + end end From 2f9229e5d58be06d09f915a1312876c149de0b41 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 27 Aug 2013 11:38:09 -0700 Subject: [PATCH 1388/3753] (maint) Document hostuuid and uniqueid facts --- lib/facter/hostuuid.rb | 9 +++++++++ lib/facter/uniqueid.rb | 11 +++++++++++ 2 files changed, 20 insertions(+) diff --git a/lib/facter/hostuuid.rb b/lib/facter/hostuuid.rb index 407b64f77c..6e635fda81 100644 --- a/lib/facter/hostuuid.rb +++ b/lib/facter/hostuuid.rb @@ -1,3 +1,12 @@ +# Fact: hostuuid +# +# Purpose: Return the hardware UUID value +# +# Resolution: +# +# On FreeBSD, use the kernel's UUID value, which is the result of the system's +# burned in UUID. + Facter.add(:hostuuid) do confine :kernel => :freebsd setcode do diff --git a/lib/facter/uniqueid.rb b/lib/facter/uniqueid.rb index 7a882ccb85..1639da6317 100644 --- a/lib/facter/uniqueid.rb +++ b/lib/facter/uniqueid.rb @@ -1,3 +1,14 @@ +# Fact: uniqueid +# +# Purpose: Return a unique numeric identifier for the given system. +# +# Resolution: +# +# On platforms that have a `hostid` command, use the output of that command. +# +# On FreeBSD, use the kernel's hostid value, which is the first four bytes of +# the host UUID. + Facter.add(:uniqueid) do setcode 'hostid' confine :kernel => %w{SunOS Linux AIX GNU/kFreeBSD} From fede91d20bcad1fc2dec804f62d93c3a5888c114 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 27 Aug 2013 11:39:23 -0700 Subject: [PATCH 1389/3753] (maint) Use sysctl helper method for FreeBSD sysctl calls Commit fc8b1446c31 added a helper method for cleanly invoking sysctl; this commit updates the behavior added in bfac93844e to take advantage of the new method. --- lib/facter/hostuuid.rb | 2 +- lib/facter/uniqueid.rb | 2 +- spec/unit/hostuuid_spec.rb | 2 +- spec/unit/uniqueid_spec.rb | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/facter/hostuuid.rb b/lib/facter/hostuuid.rb index 6e635fda81..c98e74e4b7 100644 --- a/lib/facter/hostuuid.rb +++ b/lib/facter/hostuuid.rb @@ -10,6 +10,6 @@ Facter.add(:hostuuid) do confine :kernel => :freebsd setcode do - Facter::Util::Resolution.exec('sysctl -n kern.hostuuid') + Facter::Util::POSIX.sysctl('kern.hostuuid') end end diff --git a/lib/facter/uniqueid.rb b/lib/facter/uniqueid.rb index 1639da6317..757cdb02e5 100644 --- a/lib/facter/uniqueid.rb +++ b/lib/facter/uniqueid.rb @@ -17,6 +17,6 @@ Facter.add(:uniqueid) do confine :kernel => :freebsd setcode do - Facter::Util::Resolution.exec('sysctl -n kern.hostid') + Facter::Util::POSIX.sysctl('kern.hostid') end end diff --git a/spec/unit/hostuuid_spec.rb b/spec/unit/hostuuid_spec.rb index 96f829d929..8983f5c443 100755 --- a/spec/unit/hostuuid_spec.rb +++ b/spec/unit/hostuuid_spec.rb @@ -6,7 +6,7 @@ describe "host UUID fact" do it "should match kern.hostuuid on FreeBSD" do Facter.fact(:kernel).stubs(:value).returns("FreeBSD") - Facter::Util::Resolution.stubs(:exec).with("sysctl -n kern.hostuuid").returns("a0391b10-6c8c-11e1-b960-001b21b8d7b0") + Facter::Util::POSIX.stubs(:sysctl).with("kern.hostuuid").returns("a0391b10-6c8c-11e1-b960-001b21b8d7b0") Facter.fact(:hostuuid).value.should == "a0391b10-6c8c-11e1-b960-001b21b8d7b0" end diff --git a/spec/unit/uniqueid_spec.rb b/spec/unit/uniqueid_spec.rb index 811d54e3f0..6da4c228a5 100755 --- a/spec/unit/uniqueid_spec.rb +++ b/spec/unit/uniqueid_spec.rb @@ -25,7 +25,7 @@ it "should match kern.hostid on FreeBSD" do Facter.fact(:kernel).stubs(:value).returns("FreeBSD") - Facter::Util::Resolution.stubs(:exec).with("sysctl -n kern.hostid").returns("Shemp") + Facter::Util::POSIX.stubs(:sysctl).with("kern.hostid").returns("Shemp") Facter.fact(:uniqueid).value.should == "Shemp" end From abb477df361f24267cf3dc3ca91e16662809f3e7 Mon Sep 17 00:00:00 2001 From: Ashley Penney Date: Mon, 12 Aug 2013 12:49:48 -0400 Subject: [PATCH 1390/3753] (#22143) Add 'gid' fact to resolve users primary group. A very simple fact to fetch the primary group. I've only tested this on Linux/OSX/FreeBSD but id -ng seems to be global. I even got a user to test it on Hurd and it worked. --- lib/facter/gid.rb | 15 +++++++++++++++ spec/unit/gid_spec.rb | 22 ++++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 lib/facter/gid.rb create mode 100755 spec/unit/gid_spec.rb diff --git a/lib/facter/gid.rb b/lib/facter/gid.rb new file mode 100644 index 0000000000..f312a83ba1 --- /dev/null +++ b/lib/facter/gid.rb @@ -0,0 +1,15 @@ +# Fact: gid +# +# Purpose: Return the gid of the user running Puppet +# +# Resolution: +# +# Caveats: +# Not supported in windows yet. + +Facter.add(:gid) do + confine do + Facter::Util::Resolution.which('id') + end + setcode 'id -ng' +end diff --git a/spec/unit/gid_spec.rb b/spec/unit/gid_spec.rb new file mode 100755 index 0000000000..2f23f16242 --- /dev/null +++ b/spec/unit/gid_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe "gid fact" do + + describe "on systems with id" do + it "should return the current group" do + Facter::Util::Resolution.expects(:which).with('id').returns(true) + Facter::Util::Resolution.expects(:exec).once.with('id -ng').returns 'bar' + + Facter.fact(:gid).value.should == 'bar' + end + end + + describe "on systems without id" do + it "is not supported" do + Facter::Util::Resolution.expects(:which).with('id').returns(false) + + Facter.fact(:gid).value.should == nil + end + end + +end From 9e79d7c8ca1724ad636914d2f8cbec7d2e518271 Mon Sep 17 00:00:00 2001 From: Melissa Date: Tue, 27 Aug 2013 15:24:52 -0700 Subject: [PATCH 1391/3753] (packaging) Update FACTERVERSION to 1.7.3-rc1 --- lib/facter/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/version.rb b/lib/facter/version.rb index c793e3f8b6..01db4eccd0 100644 --- a/lib/facter/version.rb +++ b/lib/facter/version.rb @@ -1,6 +1,6 @@ module Facter if not defined? FACTERVERSION then - FACTERVERSION = '1.7.2' + FACTERVERSION = '1.7.3-rc1' end ## From c3c6f6df11ac6e23c1806c74461a964890b6ff66 Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Tue, 27 Aug 2013 18:30:31 -0700 Subject: [PATCH 1392/3753] (21868) Move facter libary target to /Library/Ruby/Site We currently drop the facter library files in /usr/lib/ruby/site_ruby/1.8 OSX, which is a symlink to /Library/Ruby/Site/1.8. As of OSX 10.9 Maverick this will no longer work because it will be ruby 2.0.0. There are hard way and easy ways to solve this problem. A hard way would be to somehow introspect the target host at install time, and then dynamically copy the payload into the correct ruby version specific path. Another hard way woul be to create different OSX packages for different versions of OSX. Or, we could pick the easy way, which is to just install into /Library/Ruby/Site (the equivalent of /usr/lib/ruby/site_ruby). Since facter will work on all rubies shipped with supported versions of OSX, this will "just work". Signed-off-by: Moses Mendoza --- ext/osx/file_mapping.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/osx/file_mapping.yaml b/ext/osx/file_mapping.yaml index e21a0d9d8a..8accc83f19 100644 --- a/ext/osx/file_mapping.yaml +++ b/ext/osx/file_mapping.yaml @@ -1,6 +1,6 @@ directories: lib: - path: 'usr/lib/ruby/site_ruby/1.8' + path: 'Library/Ruby/Site' owner: 'root' group: 'wheel' perms: '0644' From 44122d813e3a5da522567fb53556389feddf9be9 Mon Sep 17 00:00:00 2001 From: Josh Partlow Date: Wed, 28 Aug 2013 10:21:37 -0700 Subject: [PATCH 1393/3753] (#22233) Warn but do not fail if find gce facts without json lib Previous patch to GCE would fail outright if there were GCE facts to parse, but we had no JSON library. It is better if Facter is allowed to gather what facts it can rather than failing outright, so this change just warns that we cannot load GCE facts due to missing JSON gem, similar to how we warn if there is a problem parsing the GCE facts. Also fixes up specs a little for consistency and to remove unused references. --- lib/facter/util/gce.rb | 6 ++++-- spec/unit/gce_spec.rb | 28 ++++++++++--------------- spec/unit/util/gce_spec.rb | 42 +++++++++++++++++++------------------- 3 files changed, 36 insertions(+), 40 deletions(-) diff --git a/lib/facter/util/gce.rb b/lib/facter/util/gce.rb index 67d96319c4..3bc8425ec1 100644 --- a/lib/facter/util/gce.rb +++ b/lib/facter/util/gce.rb @@ -109,7 +109,7 @@ def self.with_metadata_server(options = {}) # Read the list of supported API versions Timeout.timeout(timeout) do if body = read_uri("#{METADATA_URL}") - require_json + return false if !require_json metadata_facts("gce", JSON.parse(body)) end end @@ -155,7 +155,9 @@ def self.add_gce_facts(options = {}) # @api private def self.require_json - raise(LoadError, "no json gem") if !Facter.json? + return true if Facter.json? + Facter.warn("Cannot load GCE facts: no JSON gem available") + return false end end diff --git a/spec/unit/gce_spec.rb b/spec/unit/gce_spec.rb index b147164841..2009e9e62b 100755 --- a/spec/unit/gce_spec.rb +++ b/spec/unit/gce_spec.rb @@ -23,33 +23,27 @@ let(:api_version) { "v1beta1" } describe "when running on gce" do + let(:gce) { Facter::Util::GCE } + let(:url) { "#{api_prefix}/#{api_version}/?recursive=true&alt=json" } before :each do # Assume we can connect - Facter::Util::GCE.stubs(:can_connect?).returns(true) - Facter::Util::GCE.stubs(:read_uri). - with('http://metadata/').returns('OK') + gce.stubs(:can_connect?).returns(true) Facter.stubs(:value). with('virtual').returns('gce') end - let :util do - Facter::Util::GCE - end - it "defines facts dynamically from metadata/", :if => Facter.json? do - util.stubs(:read_uri). - with("#{api_prefix}/#{api_version}/?recursive=true&alt=json"). - returns('{"some_key_name":"some_key_value"}') + gce.expects(:read_uri).with(url).returns('{"some_key_name":"some_key_value"}') - Facter::Util::GCE.add_gce_facts(:force => true) - - Facter.fact(:gce_some_key_name). - value.should == "some_key_value" + expect(gce.add_gce_facts(:force => true)).to be_true + expect(Facter.fact(:gce_some_key_name).value).to eq("some_key_value") end - it "raises a LoadError if json gem is not present.", :unless => Facter.json? do - util.stubs(:read_uri).returns('{"some":"json"}') - expect { Facter::Util::GCE.add_gce_facts(:force => true) }.to raise_error(LoadError, /no json gem/) + it "returns false if json gem is not present.", :unless => Facter.json? do + gce.expects(:read_uri).with(url).returns('{"some":"json"}') + + expect(gce.add_gce_facts(:force => true)).to be_false + expect(Facter.to_hash.keys).to_not include('gce_some') end end end diff --git a/spec/unit/util/gce_spec.rb b/spec/unit/util/gce_spec.rb index 779f40b486..acc937face 100755 --- a/spec/unit/util/gce_spec.rb +++ b/spec/unit/util/gce_spec.rb @@ -17,23 +17,12 @@ require 'facter/util/gce' describe Facter::Util::GCE do - # This is the standard prefix for making an API call in GCE (or fake) - # environments. - let(:api_prefix) { "http://metadata/computeMetadata" } - let(:api_version) { "v1beta1" } - - describe "Facter::Util::GCE.with_metadata_server", :focus => true do - before :each do - Facter::Util::GCE.stubs(:read_uri).returns(:api_version) - end - - subject do - Facter::Util::GCE.with_metadata_server - end + describe "with_metadata_server", :focus => true do + let(:gce) { Facter::Util::GCE } it 'returns false when not running on gce' do Facter.stubs(:value).with('virtual').returns('vmware') - subject.should be_false + expect(gce.with_metadata_server).to be_false end context 'default options and running on a gce virtual machine' do @@ -44,25 +33,36 @@ it 'returns false when the metadata server is unreachable' do described_class.stubs(:read_uri).raises(Errno::ENETUNREACH) - subject.should be_false + expect(gce.with_metadata_server).to be_false end it 'does not execute the block if the connection raises an exception' do described_class.stubs(:read_uri).raises(Timeout::Error) - subject.should be_false + expect(gce.with_metadata_server).to be_false end it 'succeeds on the third retry' do retry_metadata = sequence('metadata') Timeout.expects(:timeout).twice.in_sequence(retry_metadata).raises(Timeout::Error) Timeout.expects(:timeout).once.in_sequence(retry_metadata).returns(true) - subject.should be_true + expect(gce.with_metadata_server).to be_true end - it 'raises an error if json gem is not present' do - Facter.stubs(:json?).returns(false) - described_class.stubs(:read_uri).returns('{"some":"json"}') - expect { subject }.to raise_error(LoadError, /json/) + context 'with gce' do + before do + described_class.stubs(:read_uri).returns('{"some":"json"}') + end + + it 'does not raise an error but returns false if json gem is not present' do + Facter.stubs(:json?).returns(false) + expect(gce.with_metadata_server).to be_false + end + + it 'warns if json gem is not present' do + Facter.stubs(:json?).returns(false) + Facter.expects(:warn).with("Cannot load GCE facts: no JSON gem available") + expect(gce.with_metadata_server).to be_false + end end end end From 0d1f705ab0ab7d4f06233323bb8c8e814d835e2f Mon Sep 17 00:00:00 2001 From: Andrew Parker Date: Wed, 28 Aug 2013 17:33:34 -0700 Subject: [PATCH 1394/3753] (#22334) Use Facter's logging system for warn Several points in facter would use ruby's built in warn method, which always prints to stderr. This made unstoppable messages show up on the stderr such as: [vagrant@localhost ~]$ facter virtual Could not retrieve virtual: Permission denied - /sys/firmware/dmi/entries/1-0/raw virtualbox By using facter's normal logging system, the user has control of these showing up and they won't end up in stderr (which might be going nowhere in a daemonized process). --- lib/facter/util/loader.rb | 2 +- lib/facter/util/resolution.rb | 6 +++--- spec/unit/ec2_spec.rb | 2 +- spec/unit/util/loader_spec.rb | 2 +- spec/unit/util/resolution_spec.rb | 6 +++--- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/facter/util/loader.rb b/lib/facter/util/loader.rb index 87008ffa54..f0e5fee6a5 100644 --- a/lib/facter/util/loader.rb +++ b/lib/facter/util/loader.rb @@ -97,7 +97,7 @@ def load_file(file) # Don't store the path if the file can't be loaded # in case it's loadable later on. @loaded.delete(file) - warn "Error loading fact #{file} #{detail}" + Facter.warn "Error loading fact #{file} #{detail}" end end diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 9205abe929..a19b81aa9a 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -174,7 +174,7 @@ def self.exec(code, interpreter = nil) # command not found on Windows return nil rescue => detail - $stderr.puts detail + Facter.warn(detail) return nil end @@ -307,7 +307,7 @@ def value end end rescue Timeout::Error => detail - warn "Timed out seeking value for %s" % self.name + Facter.warn "Timed out seeking value for %s" % self.name # This call avoids zombies -- basically, create a thread that will # dezombify all of the child processes that we're ignoring because @@ -315,7 +315,7 @@ def value Thread.new { Process.waitall } return nil rescue => details - warn "Could not retrieve %s: %s" % [self.name, details] + Facter.warn "Could not retrieve %s: %s" % [self.name, details] return nil end diff --git a/spec/unit/ec2_spec.rb b/spec/unit/ec2_spec.rb index 3046789c62..f26a61316f 100755 --- a/spec/unit/ec2_spec.rb +++ b/spec/unit/ec2_spec.rb @@ -136,8 +136,8 @@ end it "should return nil if open fails" do + Facter.stubs(:warn) # do not pollute test output Facter.expects(:warn).with('Could not retrieve ec2 metadata: host unreachable') - Facter::Util::Resolution.any_instance.stubs(:warn) # do not pollute test output Object.any_instance.expects(:open). with("#{api_prefix}/2008-02-01/meta-data/"). diff --git a/spec/unit/util/loader_spec.rb b/spec/unit/util/loader_spec.rb index 63dacb90fd..d0b870667d 100755 --- a/spec/unit/util/loader_spec.rb +++ b/spec/unit/util/loader_spec.rb @@ -315,7 +315,7 @@ def load_file(file) Dir.expects(:entries).with("/one/dir").returns %w{a.rb} Kernel.expects(:load).with("/one/dir/a.rb").raises(LoadError) - @loader.expects(:warn) + Facter.expects(:warn) lambda { @loader.load_all }.should_not raise_error end diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index 53d10839f6..e0300bf915 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -240,7 +240,7 @@ class FlushFakeError < StandardError; end describe "and the code is a block" do it "should warn but not fail if the code fails" do @resolve.setcode { raise "feh" } - @resolve.expects(:warn) + Facter.expects(:warn) @resolve.value.should be_nil end @@ -269,7 +269,7 @@ class FlushFakeError < StandardError; end end it "should timeout after the provided timeout" do - @resolve.expects(:warn) + Facter.expects(:warn) @resolve.timeout = 0.1 @resolve.setcode { sleep 2; raise "This is a test" } Thread.expects(:new).yields @@ -278,7 +278,7 @@ class FlushFakeError < StandardError; end end it "should waitall to avoid zombies if the timeout is exceeded" do - @resolve.stubs(:warn) + Facter.stubs(:warn) @resolve.timeout = 0.1 @resolve.setcode { sleep 2; raise "This is a test" } From 6cbb35eabf91aff56b42b2baf42c405ce9103584 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Thu, 5 Sep 2013 10:38:32 -0700 Subject: [PATCH 1395/3753] (maint) Cleanup whitespace errors Clean up some trailing whitespace. --- lib/facter/util/gce.rb | 6 +++--- spec/unit/gce_spec.rb | 6 +++--- spec/unit/util/gce_spec.rb | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/facter/util/gce.rb b/lib/facter/util/gce.rb index 3bc8425ec1..c3aa225987 100644 --- a/lib/facter/util/gce.rb +++ b/lib/facter/util/gce.rb @@ -1,11 +1,11 @@ # Copyright 2013 Google Inc. All Rights Reserved. -# +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. diff --git a/spec/unit/gce_spec.rb b/spec/unit/gce_spec.rb index 2009e9e62b..8dd39fa569 100755 --- a/spec/unit/gce_spec.rb +++ b/spec/unit/gce_spec.rb @@ -1,12 +1,12 @@ #! /usr/bin/env ruby # Copyright 2013 Google Inc. All Rights Reserved. -# +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. diff --git a/spec/unit/util/gce_spec.rb b/spec/unit/util/gce_spec.rb index acc937face..d039e9dc6c 100755 --- a/spec/unit/util/gce_spec.rb +++ b/spec/unit/util/gce_spec.rb @@ -1,12 +1,12 @@ #! /usr/bin/env ruby # Copyright 2013 Google Inc. All Rights Reserved. -# +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. From 4619eddf4717fbc889b0a34454a588d1eb60a6a8 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Thu, 5 Sep 2013 10:45:52 -0700 Subject: [PATCH 1396/3753] (#22233) Remove filters for the tests Without this patch an example is excluded entirely if the `json` library is not available. If it is available then a related example is removed instead. This is a problem because it isn't clear why the examples are being removed when the tests are run. This patch addresses the problem by marking the first test as pending if the json library is not available. The second test always runs with this patch applied because the patch mocks the behavior of JSON being absent even if it is present during the test. --- spec/unit/gce_spec.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/spec/unit/gce_spec.rb b/spec/unit/gce_spec.rb index 8dd39fa569..a64e57b3e2 100755 --- a/spec/unit/gce_spec.rb +++ b/spec/unit/gce_spec.rb @@ -32,14 +32,17 @@ with('virtual').returns('gce') end - it "defines facts dynamically from metadata/", :if => Facter.json? do + it "defines facts dynamically from metadata/" do + pending "Example cannot run without the 'json' library" unless Facter.json? + gce.expects(:read_uri).with(url).returns('{"some_key_name":"some_key_value"}') expect(gce.add_gce_facts(:force => true)).to be_true expect(Facter.fact(:gce_some_key_name).value).to eq("some_key_value") end - it "returns false if json gem is not present.", :unless => Facter.json? do + it "returns false if json gem is not present." do + gce.stubs(:require_json).returns(false) gce.expects(:read_uri).with(url).returns('{"some":"json"}') expect(gce.add_gce_facts(:force => true)).to be_false From fbc4f75f801f0bc5fc892bdc26354cb7645ad5d8 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Thu, 5 Sep 2013 11:13:16 -0700 Subject: [PATCH 1397/3753] (#22233) Replace expect().to with .should Without this patch we're seeing test failures on the Solaris 10 spec system. These failures are caused by the system not respecting the dependencies specified in the Gemfile. This patch addresses the problem by changing the tests to use rspec 2.9 compatible syntax since this is a faster route to a green build than changing the spec system itself. This patch will be reverted once the Solaris 10 spec system has been updated to respect the Gemfile. --- spec/unit/gce_spec.rb | 6 +++--- spec/unit/util/gce_spec.rb | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/spec/unit/gce_spec.rb b/spec/unit/gce_spec.rb index a64e57b3e2..b570fd7081 100755 --- a/spec/unit/gce_spec.rb +++ b/spec/unit/gce_spec.rb @@ -38,15 +38,15 @@ gce.expects(:read_uri).with(url).returns('{"some_key_name":"some_key_value"}') expect(gce.add_gce_facts(:force => true)).to be_true - expect(Facter.fact(:gce_some_key_name).value).to eq("some_key_value") + Facter.fact(:gce_some_key_name).value.should eq("some_key_value") end it "returns false if json gem is not present." do gce.stubs(:require_json).returns(false) gce.expects(:read_uri).with(url).returns('{"some":"json"}') - expect(gce.add_gce_facts(:force => true)).to be_false - expect(Facter.to_hash.keys).to_not include('gce_some') + gce.add_gce_facts(:force => true).should be_false + Facter.to_hash.keys.should_not include('gce_some') end end end diff --git a/spec/unit/util/gce_spec.rb b/spec/unit/util/gce_spec.rb index d039e9dc6c..fd09fe47a5 100755 --- a/spec/unit/util/gce_spec.rb +++ b/spec/unit/util/gce_spec.rb @@ -22,7 +22,7 @@ it 'returns false when not running on gce' do Facter.stubs(:value).with('virtual').returns('vmware') - expect(gce.with_metadata_server).to be_false + gce.with_metadata_server.should be_false end context 'default options and running on a gce virtual machine' do @@ -33,19 +33,19 @@ it 'returns false when the metadata server is unreachable' do described_class.stubs(:read_uri).raises(Errno::ENETUNREACH) - expect(gce.with_metadata_server).to be_false + gce.with_metadata_server.should be_false end it 'does not execute the block if the connection raises an exception' do described_class.stubs(:read_uri).raises(Timeout::Error) - expect(gce.with_metadata_server).to be_false + gce.with_metadata_server.should be_false end it 'succeeds on the third retry' do retry_metadata = sequence('metadata') Timeout.expects(:timeout).twice.in_sequence(retry_metadata).raises(Timeout::Error) Timeout.expects(:timeout).once.in_sequence(retry_metadata).returns(true) - expect(gce.with_metadata_server).to be_true + gce.with_metadata_server.should be_true end context 'with gce' do @@ -55,13 +55,13 @@ it 'does not raise an error but returns false if json gem is not present' do Facter.stubs(:json?).returns(false) - expect(gce.with_metadata_server).to be_false + gce.with_metadata_server.should be_false end it 'warns if json gem is not present' do Facter.stubs(:json?).returns(false) Facter.expects(:warn).with("Cannot load GCE facts: no JSON gem available") - expect(gce.with_metadata_server).to be_false + gce.with_metadata_server.should be_false end end end From 178e2c616b87892eb54a031d2f20699e9797a132 Mon Sep 17 00:00:00 2001 From: Andrew Parker Date: Fri, 30 Aug 2013 09:46:45 -0700 Subject: [PATCH 1398/3753] (#22349) Read from user's home when non-root The external facts directory always assumed that the user was root, or at least had read access to the global facts.d directory. This created problems for users with restricted permissions when running as non-root. Instead of trying to handle the permissions problem, which would have made facter run, this changes facter to follow the same non-root style as puppet, which is to look in the home directory for a .facter/facts.d external facts directory. Simply handling the error would have had two undesirable consequences: 1) it would have hidden a problem from the user and 2) it would have made external facts cumbersome to use as non-root as the user would always have to specify --external-dir, which is not currently possible when facter is used by puppet. --- Gemfile | 1 + lib/facter/util/config.rb | 16 +++++++++----- lib/facter/util/unix_root.rb | 5 +++++ lib/facter/util/windows_root.rb | 37 +++++++++++++++++++++++++++++++++ spec/unit/util/config_spec.rb | 9 ++++++++ 5 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 lib/facter/util/unix_root.rb create mode 100644 lib/facter/util/windows_root.rb diff --git a/Gemfile b/Gemfile index d2d5db16ef..51e3614220 100644 --- a/Gemfile +++ b/Gemfile @@ -22,6 +22,7 @@ group :development, :test do end platform :mswin, :mingw do + gem "sys-admin", "~> 1.5.6" gem "win32-api", "~> 1.4.8" gem "win32-dir", "~> 0.3.7" gem "windows-api", "~> 0.4.1" diff --git a/lib/facter/util/config.rb b/lib/facter/util/config.rb index 4c95dd65f0..26c2c8514e 100644 --- a/lib/facter/util/config.rb +++ b/lib/facter/util/config.rb @@ -30,16 +30,22 @@ def self.windows_data_dir end def self.external_facts_dirs - windows_dir = windows_data_dir - if windows_dir.nil? then - ["/etc/facter/facts.d", "/etc/puppetlabs/facter/facts.d"] + if Facter::Util::Root.root? + windows_dir = windows_data_dir + if windows_dir.nil? then + ["/etc/facter/facts.d", "/etc/puppetlabs/facter/facts.d"] + else + [File.join(windows_dir, 'PuppetLabs', 'facter', 'facts.d')] + end else - [File.join(windows_dir, 'PuppetLabs', 'facter', 'facts.d')] + [File.expand_path(File.join("~", ".facter", "facts.d"))] end end end if Facter::Util::Config.is_windows? - require 'rubygems' require 'win32/dir' + require 'facter/util/windows_root' +else + require 'facter/util/unix_root' end diff --git a/lib/facter/util/unix_root.rb b/lib/facter/util/unix_root.rb new file mode 100644 index 0000000000..c7be2ffc70 --- /dev/null +++ b/lib/facter/util/unix_root.rb @@ -0,0 +1,5 @@ +module Facter::Util::Root + def self.root? + Process.uid == 0 + end +end diff --git a/lib/facter/util/windows_root.rb b/lib/facter/util/windows_root.rb new file mode 100644 index 0000000000..ad7500eef7 --- /dev/null +++ b/lib/facter/util/windows_root.rb @@ -0,0 +1,37 @@ +require 'windows/system_info' +require 'windows/security' +require 'sys/admin' + +module Facter::Util::Root + extend ::Windows::SystemInfo + extend ::Windows::Security + + def self.root? + # if Vista or later, check for unrestricted process token + return Win32::Security.elevated_security? unless windows_version < 6.0 + + # otherwise 2003 or less + check_token_membership + end + + def self.check_token_membership + sid = 0.chr * 80 + size = [80].pack('L') + member = 0.chr * 4 + + unless CreateWellKnownSid(WinBuiltinAdministratorsSid, nil, sid, size) + raise "Failed to create administrators SID" + end + + unless IsValidSid(sid) + raise "Invalid SID" + end + + unless CheckTokenMembership(nil, sid, member) + raise "Failed to check membership" + end + + # Is administrators SID enabled in calling thread's access token? + member.unpack('L')[0] == 1 + end +end diff --git a/spec/unit/util/config_spec.rb b/spec/unit/util/config_spec.rb index b0caabd039..25afcf0b65 100644 --- a/spec/unit/util/config_spec.rb +++ b/spec/unit/util/config_spec.rb @@ -34,6 +34,10 @@ end describe "external_facts_dirs" do + before :each do + Facter::Util::Root.stubs(:root?).returns(true) + end + it "should return the default value for linux" do Facter::Util::Config.stubs(:is_windows?).returns(false) Facter::Util::Config.stubs(:windows_data_dir).returns(nil) @@ -51,5 +55,10 @@ Facter::Util::Config.stubs(:windows_data_dir).returns("C:\\Documents") Facter::Util::Config.external_facts_dirs.should == [File.join("C:\\Documents", 'PuppetLabs', 'facter', 'facts.d')] end + + it "returns the users home directory when not root" do + Facter::Util::Root.stubs(:root?).returns(false) + Facter::Util::Config.external_facts_dirs.should == [File.expand_path(File.join("~", ".facter", "facts.d"))] + end end end From 65c7b4e0772c6e38f54730f04a55f24e43b7c78e Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Fri, 6 Sep 2013 10:38:22 -0700 Subject: [PATCH 1399/3753] Maint: Only try to install json gem for ruby 1.8 The json gem contains native code, so a compiler must be present in order to install the gem (bundle install). On Windows, we intentionally do not install the devkit, mainly for security reasons. However, the json gem is only required when running ruby 1.8, so we confine the Gemfile accordingly. This resolves the issue on Windows, because we only support ruby 1.9 on that platform. --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 51e3614220..5f7178e448 100644 --- a/Gemfile +++ b/Gemfile @@ -17,7 +17,7 @@ group :development, :test do gem 'facter', ">= 1.0.0", :path => File.expand_path("..", __FILE__) gem 'rspec', "~> 2.11.0" gem 'mocha', "~> 0.10.5" - gem 'json', "~> 1.7" + gem 'json', "~> 1.7", :platforms => :ruby_18 gem 'puppetlabs_spec_helper' end From a598423c3f5ef946e0d1eb8ae64b4b41e413e3b1 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Fri, 6 Sep 2013 11:09:11 -0700 Subject: [PATCH 1400/3753] (#22349) Qualify WinBuiltinAdministratorsSid constant Previously, we were getting a NameError when trying to resolve the WinBuiltinAdministratorsSid constant, though it seems to be load order dependent. --- lib/facter/util/windows_root.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/util/windows_root.rb b/lib/facter/util/windows_root.rb index ad7500eef7..d2bd019f6b 100644 --- a/lib/facter/util/windows_root.rb +++ b/lib/facter/util/windows_root.rb @@ -19,7 +19,7 @@ def self.check_token_membership size = [80].pack('L') member = 0.chr * 4 - unless CreateWellKnownSid(WinBuiltinAdministratorsSid, nil, sid, size) + unless CreateWellKnownSid(Windows::Security::WinBuiltinAdministratorsSid, nil, sid, size) raise "Failed to create administrators SID" end From e7a26cf3ba494c6b942f87a6651e5c6d9b20f2d4 Mon Sep 17 00:00:00 2001 From: Matthaus Owens Date: Fri, 6 Sep 2013 14:29:37 -0700 Subject: [PATCH 1401/3753] (packaging) Update FACTERVERSION to 1.7.3 --- lib/facter/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/version.rb b/lib/facter/version.rb index 01db4eccd0..10a0d346bf 100644 --- a/lib/facter/version.rb +++ b/lib/facter/version.rb @@ -1,6 +1,6 @@ module Facter if not defined? FACTERVERSION then - FACTERVERSION = '1.7.3-rc1' + FACTERVERSION = '1.7.3' end ## From e599671fcae1d761dc7fe261330d80cf63695889 Mon Sep 17 00:00:00 2001 From: "Lorenzo J. Cubero" Date: Thu, 18 Jul 2013 17:41:56 +0200 Subject: [PATCH 1402/3753] (#21604) Detect xen virtual fact on windows --- lib/facter/virtual.rb | 7 ++++++- spec/unit/virtual_spec.rb | 6 ++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 1c18bd3616..2ce544886b 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -160,7 +160,12 @@ computersystem.manufacturer =~ /Microsoft/ ? "hyperv" : nil when /VMware/ then "vmware" when /KVM/ then "kvm" - else "physical" + else + if computersystem.manufacturer =~ /Xen/ + "xen" + else + "physical" + end end break end diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index 4e701b08c3..723bd2878d 100755 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -315,6 +315,12 @@ Facter::Util::WMI.expects(:execquery).returns([computersystem]) Facter.fact(:virtual).value.should == "vmware" end + + it "resolves as Xen with a manufacturer name like xen" do + computersystem = mock('computersystem', :model => nil, :manufacturer => 'Xen') + Facter::Util::WMI.expects(:execquery).returns([computersystem]) + Facter.fact(:virtual).value.should == "xen" + end end describe "with the virt-what command available (#8210)" do From eabff5e2361437d7c76631dac6b7517a2ccfa72c Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 9 Sep 2013 09:44:28 -0700 Subject: [PATCH 1403/3753] (maint) Refactor windows virtual fact for readability --- lib/facter/virtual.rb | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 2ce544886b..6d1c0fcc4d 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -153,22 +153,25 @@ require 'facter/util/wmi' result = nil Facter::Util::WMI.execquery("SELECT manufacturer, model FROM Win32_ComputerSystem").each do |computersystem| - result = - case computersystem.model - when /VirtualBox/ then "virtualbox" - when /Virtual Machine/ - computersystem.manufacturer =~ /Microsoft/ ? "hyperv" : nil - when /VMware/ then "vmware" - when /KVM/ then "kvm" - else - if computersystem.manufacturer =~ /Xen/ - "xen" - else - "physical" - end - end + case computersystem.model + when /VirtualBox/ + result = "virtualbox" + when /Virtual Machine/ + result = "hyperv" if computersystem.manufacturer =~ /Microsoft/ + when /VMware/ + result = "vmware" + when /KVM/ + result = "kvm" + end + + if result.nil? and computersystem.manufacturer =~ /Xen/ + result = "xen" + end + break end + result ||= "physical" + result end end From b1c8c5657cd3bbfa63f38ab5a530a381968ed2b3 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Mon, 9 Sep 2013 09:57:22 -0700 Subject: [PATCH 1404/3753] Maint: Don't try to install json gem on Windows Previously, `bundle install` would fail on windows without the ruby devkit installed. As this is not something we ship with, confine the gem to only be installed on ruby platforms, i.e. not windows. Also running `bundle install --without development test` followed by `bundle exec facter` on all platforms would fail due to `facter` being in the development/test group. --- Gemfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index c0dbcc6ff2..986731dbad 100644 --- a/Gemfile +++ b/Gemfile @@ -6,13 +6,14 @@ end group :development, :test do gem 'rake' - gem 'facter', ">= 1.0.0", :path => File.expand_path("..", __FILE__), :require => false gem 'rspec', "~> 2.11.0", :require => false gem 'mocha', "~> 0.10.5", :require => false - gem 'json', "~> 1.7", :require => false + gem 'json', "~> 1.7", :platforms => :ruby, :require => false gem 'puppetlabs_spec_helper', :require => false end +gem 'facter', ">= 1.0.0", :path => File.expand_path("..", __FILE__), :require => false + if File.exists? "#{__FILE__}.local" eval(File.read("#{__FILE__}.local"), binding) end From 43540e33185af5a3f0d6464be359fb139850816b Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Thu, 18 Apr 2013 22:59:31 -0700 Subject: [PATCH 1405/3753] (#20301) Handle different error in ruby 1.9 The NumberOfLogicalProcessor property of the Win32_Processor WMI class does not exist in the base win2003 install. Previously, we were trying to access the property, and then catching the resulting error if the property did not exist. In ruby 1.8, it would raise a RuntimeError, but in ruby 1.9, it raises a StandardError, causing the fact to blow up on ruby 1.9. This commit checks to see if the process object responds to that method, and only then does it try to call it. --- lib/facter/processor.rb | 4 ++-- spec/unit/processor_spec.rb | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb index a50bdf64d3..63979123ea 100644 --- a/lib/facter/processor.rb +++ b/lib/facter/processor.rb @@ -117,9 +117,9 @@ # get each physical processor Facter::Util::WMI.execquery("select * from Win32_Processor").each do |proc| # not supported before 2008 - begin + if proc.respond_to?(:NumberOfLogicalProcessors) processor_num = proc.NumberOfLogicalProcessors - rescue RuntimeError => e + else processor_num = 1 end diff --git a/spec/unit/processor_spec.rb b/spec/unit/processor_spec.rb index 14a22c6e6d..8caf97c50e 100755 --- a/spec/unit/processor_spec.rb +++ b/spec/unit/processor_spec.rb @@ -29,13 +29,14 @@ def load(procs) describe "2003" do before :each do proc = stubs 'proc' - proc.stubs(:NumberOfLogicalProcessors).raises(RuntimeError) proc.stubs(:Name).returns("Intel(R) Celeron(R) processor") load(Array.new(2, proc)) end it "should count 2 processors" do + proc.expects(:NumberOfLogicalProcessors).never + Facter.fact(:processorcount).value.should == "2" end From 95495339aa354b4cff1ad02c8d8aec11b9c4adca Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Wed, 11 Sep 2013 12:03:15 -0700 Subject: [PATCH 1406/3753] (maint) Remove :focus => true from specs --- spec/unit/util/ec2_spec.rb | 2 +- spec/unit/util/gce_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/unit/util/ec2_spec.rb b/spec/unit/util/ec2_spec.rb index 180c956f25..2472c8712f 100755 --- a/spec/unit/util/ec2_spec.rb +++ b/spec/unit/util/ec2_spec.rb @@ -34,7 +34,7 @@ end end - describe "Facter::Util::EC2.with_metadata_server", :focus => true do + describe "Facter::Util::EC2.with_metadata_server" do before :each do Facter::Util::EC2.stubs(:read_uri).returns("latest") end diff --git a/spec/unit/util/gce_spec.rb b/spec/unit/util/gce_spec.rb index fd09fe47a5..ef7f585f7e 100755 --- a/spec/unit/util/gce_spec.rb +++ b/spec/unit/util/gce_spec.rb @@ -17,7 +17,7 @@ require 'facter/util/gce' describe Facter::Util::GCE do - describe "with_metadata_server", :focus => true do + describe "with_metadata_server" do let(:gce) { Facter::Util::GCE } it 'returns false when not running on gce' do From 7c317c2d1d3bdf0669271958b0461fd0f2c90564 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Thu, 5 Sep 2013 13:37:08 -0700 Subject: [PATCH 1407/3753] (#22233) Conform to code standards Without this patch the documentation for Facter::Util::GCE.with_metadata_server does not match the implementation. The implementation appears to have been copied from Facter::Util::EC2.with_metadata_server which does take a block as an argument. The GCE implementation does not take a block but the documentation mentions it does. This is a problem because we have two methods of the same name that have different designs, which is confusing. This patch addresses the problem by modifying the GCE implementation of with_metadata_server to accept a block. The reason the block was originally removed was likely because all of the metadata necessary to define the facts may be gathered with a single call. Unlike EC2, there's no need to first check if the service is responsive then call back out to the service to get the response. In GCE if the service is responsive then the initial response is all that's required. This patch augments the original design by requiring the block passed to with_metadata_server to accept a single string argument. The method will pass the body of the initial response to the block, which will usually be JSON, allowing the block to define the fact values based on the initial response rather than requiring a subsequent request. --- lib/facter/util/gce.rb | 38 ++++++++++++++++++++++++++------------ spec/unit/gce_spec.rb | 1 - spec/unit/util/gce_spec.rb | 31 ++++++++++--------------------- 3 files changed, 36 insertions(+), 34 deletions(-) diff --git a/lib/facter/util/gce.rb b/lib/facter/util/gce.rb index c3aa225987..c638b7ed2e 100644 --- a/lib/facter/util/gce.rb +++ b/lib/facter/util/gce.rb @@ -72,12 +72,13 @@ def self.metadata_facts(var, val) ## # with_metadata_server takes a block of code and executes the block only if # Facter is running on node that can access a metadata server at - # http://metadata/. This is useful to decide if it's reasonably - # likely that talking to the GCE metadata server will be successful or not. + # http://metadata/. The request URI is configurable and the response body + # will be passed to the first argument of the block. This is useful to + # decide if it's reasonably likely that talking to the GCE metadata server + # will be successful or not. # # @option options [Integer] :timeout (100) the maxiumum number of # milliseconds Facter will block trying to talk to the metadata server. - # Defaults to 200. # # @option options [String] :fact ('virtual') the fact to check. The block # will only be executed if the fact named here matches the value named in the @@ -90,13 +91,20 @@ def self.metadata_facts(var, val) # this method will try to contact the metadata server. The maximum run time # is the timeout times this limit, so please keep the value small. # + # @option options [String] :url ({METADATA_URL}) the url that will be used to + # attempt the initial connection to the metadata server. If the URL responds + # then the body of the response will be passed as the first argument to the + # block provided. + # # @return [Boolean] the return {true} if successfula, {false} otherwise - def self.with_metadata_server(options = {}) + # + def self.with_metadata_server(options = {}, &block) opts = options.dup opts[:timeout] ||= 100 opts[:fact] ||= 'virtual' opts[:value] ||= 'gce' opts[:retry_limit] ||= 3 + opts[:url] ||= METADATA_URL # Conversion to fractional seconds for Timeout timeout = opts[:timeout] / 1000.0 raise ArgumentError, "A value is required for :fact" if opts[:fact].nil? @@ -105,23 +113,25 @@ def self.with_metadata_server(options = {}) attempts = 0 begin + body = nil attempts = attempts + 1 # Read the list of supported API versions Timeout.timeout(timeout) do - if body = read_uri("#{METADATA_URL}") - return false if !require_json - metadata_facts("gce", JSON.parse(body)) - end + body = read_uri(opts[:url]) end rescue *CONNECTION_ERRORS => detail retry if attempts < opts[:retry_limit] - Facter.warn "Unable to fetch metadata from #{METADATA_URL}, " + + Facter.warn "Unable to fetch metadata from #{opts[:url]}, " + "metadata server facts will be undefined. #{detail.message}" return false end - return true - end + if body + return block.call(body) + else + return false + end + end ## # read_uri provides a seam method to easily test the HTTP client # functionality of a HTTP based metadata server. @@ -148,7 +158,11 @@ def self.add_gce_facts(options = {}) return nil if @add_gce_facts_has_run end @add_gce_facts_has_run = true - with_metadata_server :timeout => 50 + if require_json + with_metadata_server(:timeout => 50) do |body| + metadata_facts("gce", JSON.parse(body)) + end + end end private diff --git a/spec/unit/gce_spec.rb b/spec/unit/gce_spec.rb index b570fd7081..31c3d45ef9 100755 --- a/spec/unit/gce_spec.rb +++ b/spec/unit/gce_spec.rb @@ -43,7 +43,6 @@ it "returns false if json gem is not present." do gce.stubs(:require_json).returns(false) - gce.expects(:read_uri).with(url).returns('{"some":"json"}') gce.add_gce_facts(:force => true).should be_false Facter.to_hash.keys.should_not include('gce_some') diff --git a/spec/unit/util/gce_spec.rb b/spec/unit/util/gce_spec.rb index ef7f585f7e..e71fd28e28 100755 --- a/spec/unit/util/gce_spec.rb +++ b/spec/unit/util/gce_spec.rb @@ -22,47 +22,36 @@ it 'returns false when not running on gce' do Facter.stubs(:value).with('virtual').returns('vmware') - gce.with_metadata_server.should be_false + gce.with_metadata_server {|body| body}.should be_false end context 'default options and running on a gce virtual machine' do - before :each do Facter.stubs(:value).with('virtual').returns('gce') end it 'returns false when the metadata server is unreachable' do described_class.stubs(:read_uri).raises(Errno::ENETUNREACH) - gce.with_metadata_server.should be_false + gce.with_metadata_server {|body| body }.should be_false end it 'does not execute the block if the connection raises an exception' do described_class.stubs(:read_uri).raises(Timeout::Error) - gce.with_metadata_server.should be_false + gce.with_metadata_server {|body| body }.should be_false end it 'succeeds on the third retry' do retry_metadata = sequence('metadata') - Timeout.expects(:timeout).twice.in_sequence(retry_metadata).raises(Timeout::Error) - Timeout.expects(:timeout).once.in_sequence(retry_metadata).returns(true) - gce.with_metadata_server.should be_true - end + described_class.stubs(:read_uri).twice.in_sequence(retry_metadata).raises(Errno::EHOSTUNREACH) + described_class.stubs(:read_uri).once.in_sequence(retry_metadata).returns('RESPONSE BODY') - context 'with gce' do - before do - described_class.stubs(:read_uri).returns('{"some":"json"}') - end + gce.with_metadata_server {|body| body }.should eq('RESPONSE BODY') + end - it 'does not raise an error but returns false if json gem is not present' do - Facter.stubs(:json?).returns(false) - gce.with_metadata_server.should be_false - end + it 'passes the response body to the block' do + described_class.stubs(:read_uri).returns('RESPONSE BODY') - it 'warns if json gem is not present' do - Facter.stubs(:json?).returns(false) - Facter.expects(:warn).with("Cannot load GCE facts: no JSON gem available") - gce.with_metadata_server.should be_false - end + gce.with_metadata_server {|body| body }.should eq('RESPONSE BODY') end end end From cf7ed1c027740ba7a30f5f911a89d9d91e231de5 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Wed, 11 Sep 2013 12:50:09 -0700 Subject: [PATCH 1408/3753] (#22233) Clean up the rake collect:gce_metadata task Without this patch the GCE metadata collection task which produces fixture data duplicates some method names in the :collect rake namespace. This patch addresses the problem by separating out the method names to avoid collision and separating out the EC2 and GCE tasks into their own files. --- Rakefile | 139 -------------------------------- tasks/collect_ec2_fixtures.rake | 55 +++++++++++++ tasks/collect_gce_fixtures.rake | 78 ++++++++++++++++++ 3 files changed, 133 insertions(+), 139 deletions(-) create mode 100644 tasks/collect_ec2_fixtures.rake create mode 100644 tasks/collect_gce_fixtures.rake diff --git a/Rakefile b/Rakefile index cd2e0739b3..4d3e363d3f 100644 --- a/Rakefile +++ b/Rakefile @@ -77,142 +77,3 @@ if defined?(RSpec::Core::RakeTask) end end end - -namespace :collect do - desc "Scrape EC2 Metadata into fixtures" - task :ec2_metadata do - collect_metadata - end - - ## - # collect_metadata walks the Amazon AWS EC2 Metadata API and records each - # request and response instance as a serialized YAML string. This method is - # intended to be used by Rake tasks Puppet users invoke to collect data for - # development and troubleshooting purposes. - def collect_metadata(key='/', date=Time.now.strftime("%F"), dir="spec/fixtures/unit/util/ec2") - require 'timeout' - require 'net/http' - require 'uri' - - # Local variables - file_prefix = "ec2_meta_data#{key.gsub(/[^a-zA-Z0-9]+/, '_')}".gsub(/_+$/, '') - response = nil - - Dir.chdir(dir) do - uri = URI("http://169.254.169.254/latest/meta-data#{key}") - Timeout::timeout(4) do - Net::HTTP.start(uri.host, uri.port) do |http| - request = Net::HTTP::Get.new(uri.request_uri) - response = http.request(request) - - write_fixture(request, "#{file_prefix}_request.yaml") - write_fixture(response, "#{file_prefix}_response.yaml") - end - end - end - - ## - # if the current key is a directory, decend into all of the files. If the - # current key is not, we've already written it out and we're done. - if key.end_with? "/" - response.read_body.lines.each do |line| - collect_metadata("#{key}#{line.chomp}", date, dir) - end - end - end - - ## - # write_fixture dumps an internal Ruby object to a file intended to be used - # as a fixture for spec testing. - # - # @return [String] Serialized string model representation of obj - def write_fixture(obj, filename, quiet=false) - File.open(filename, "w+") do |fd| - fd.write(YAML.dump(request)) - end - puts "Wrote: #{dir}/#{request_file}" unless quiet - end -end - -namespace :collect do - # gem install bundle - # bundle install --path .bundle/gems - # bundle exec rake "collect:gce_metadata" - # metadata will be scrubbed with the following changes: - # - project_number becomes '111111111111' - # - project_id becomes 'development_project' - # - sshKeys are trimmed - # - auth token is masked out - desc "Scrape GCE Metadata into fixtures, scrubbing sensitive data" - task :gce_metadata do - collect_metadata - end - - ## - # collect_metadata walks the Google's GCE Metadata API and records each - # request and response instance as a serialized YAML string. This method is - # intended to be used by Rake tasks Puppet users invoke to collect data for - # development and troubleshooting purposes. - def collect_metadata(key='/', date=Time.now.strftime("%F"), dir="spec/fixtures/unit/util/gce") - require 'timeout' - require 'net/http' - require 'uri' - - # Local variables - file_prefix = "gce_metadata#{key.gsub(/[^a-zA-Z0-9]+/, '_')}".gsub(/_+$/, '').gsub(/\d{12}/,'111111111111') - response = nil - - Dir.chdir(dir) do - uri = URI("http://metadata/computeMetadata/v1beta1#{key}") - Timeout::timeout(4) do - Net::HTTP.start(uri.host, uri.port) do |http| - request = Net::HTTP::Get.new(uri.request_uri) - response = scrub_gce(key.split("/")[-1], http.request(request)) - - write_fixture(request, "#{file_prefix}_request.yaml") - write_fixture(response, "#{file_prefix}_response.yaml") - end - end - end - - ## - # if the current key is a directory, decend into all of the files. If the - # current key is not, we've already written it out and we're done. - if key.end_with? "/" - response.read_body.lines.each do |line| - collect_metadata("#{key}#{line.chomp}", date, dir) - end - end - end - - ## - # write_fixture dumps an internal Ruby object to a file intended to be used - # as a fixture for spec testing. - # - def write_fixture(obj, filename, quiet=false) - File.open(filename, "w+") do |fd| - s = YAML.dump(obj) - fd.write(s.gsub(/\d{12}/,'111111111111')) - end - puts "Wrote: #{filename}" unless quiet - end - - ## - # scrub_gce scrubs sensitive data from HTTP response before writing fixtures - # to disk - # - # @return [Net::HTTPResponse] Sanitized GCE response in YAML format - def scrub_gce(key, resp) - case key - when "sshKeys" - resp.body = "googler:ssh-rsa AAA...ej googler@facter-dev\n" - resp["content-length"] = "44" - when "project-id" - resp.body = "development_project" - resp["content-length"] = "19" - when "token" - resp.body = '{"access_token":"ya29.AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","expires_in":1234,"token_type":"Bearer"}' - end - resp - end -end diff --git a/tasks/collect_ec2_fixtures.rake b/tasks/collect_ec2_fixtures.rake new file mode 100644 index 0000000000..61a5696b7f --- /dev/null +++ b/tasks/collect_ec2_fixtures.rake @@ -0,0 +1,55 @@ +namespace :collect do + desc "Scrape EC2 Metadata into fixtures" + task :ec2_metadata do + collect_metadata + end + + ## + # collect_metadata walks the Amazon AWS EC2 Metadata API and records each + # request and response instance as a serialized YAML string. This method is + # intended to be used by Rake tasks Puppet users invoke to collect data for + # development and troubleshooting purposes. + def collect_metadata(key='/', date=Time.now.strftime("%F"), dir="spec/fixtures/unit/util/ec2") + require 'timeout' + require 'net/http' + require 'uri' + + # Local variables + file_prefix = "ec2_meta_data#{key.gsub(/[^a-zA-Z0-9]+/, '_')}".gsub(/_+$/, '') + response = nil + + Dir.chdir(dir) do + uri = URI("http://169.254.169.254/latest/meta-data#{key}") + Timeout::timeout(4) do + Net::HTTP.start(uri.host, uri.port) do |http| + request = Net::HTTP::Get.new(uri.request_uri) + response = http.request(request) + + write_fixture(request, "#{file_prefix}_request.yaml") + write_fixture(response, "#{file_prefix}_response.yaml") + end + end + end + + ## + # if the current key is a directory, decend into all of the files. If the + # current key is not, we've already written it out and we're done. + if key.end_with? "/" + response.read_body.lines.each do |line| + collect_metadata("#{key}#{line.chomp}", date, dir) + end + end + end + + ## + # write_fixture dumps an internal Ruby object to a file intended to be used + # as a fixture for spec testing. + # + # @return [String] Serialized string model representation of obj + def write_fixture(obj, filename, quiet=false) + File.open(filename, "w+") do |fd| + fd.write(YAML.dump(request)) + end + puts "Wrote: #{dir}/#{request_file}" unless quiet + end +end diff --git a/tasks/collect_gce_fixtures.rake b/tasks/collect_gce_fixtures.rake new file mode 100644 index 0000000000..e4a055fe2d --- /dev/null +++ b/tasks/collect_gce_fixtures.rake @@ -0,0 +1,78 @@ +namespace :collect do + desc "Scrape GCE Metadata into fixtures, scrubbing sensitive data" + task :gce_metadata do + collect_gce_metadata + end + + ## + # collect_gce_metadata walks the Google's GCE Metadata API and records each + # request and response instance as a serialized YAML string. This method is + # intended to be used by Rake tasks Puppet users invoke to collect data for + # development and troubleshooting purposes. + def collect_gce_metadata(key='/', date=Time.now.strftime("%F"), dir="spec/fixtures/unit/util/gce") + require 'timeout' + require 'net/http' + require 'uri' + + # Local variables + file_prefix = "gce_metadata#{key.gsub(/[^a-zA-Z0-9]+/, '_')}".gsub(/_+$/, '').gsub(/\d{12}/,'111111111111') + response = nil + + Dir.chdir(dir) do + uri = URI("http://metadata/computeMetadata/v1beta1#{key}") + Timeout::timeout(4) do + Net::HTTP.start(uri.host, uri.port) do |http| + request = Net::HTTP::Get.new(uri.request_uri) + response = scrub_gce(key.split("/")[-1], http.request(request)) + + write_gce_fixture(request, "#{file_prefix}_request.yaml") + write_gce_fixture(response, "#{file_prefix}_response.yaml") + end + end + end + + ## + # if the current key is a directory, decend into all of the files. If the + # current key is not, we've already written it out and we're done. + if key.end_with? "/" + response.read_body.lines.each do |line| + collect_metadata("#{key}#{line.chomp}", date, dir) + end + end + end + + ## + # write_gce_fixture dumps an internal Ruby object to a file intended to be + # used as a fixture for spec testing. + # + def write_gce_fixture(obj, filename, quiet=false) + File.open(filename, "w+") do |fd| + s = YAML.dump(obj) + fd.write(s.gsub(/\d{12}/,'111111111111')) + end + puts "Wrote: #{filename}" unless quiet + end + + ## + # scrub_gce scrubs sensitive data from HTTP response before writing fixtures + # to disk metadata will be scrubbed with the following changes: + # - project_number becomes '111111111111' + # - project_id becomes 'development_project' + # - sshKeys are trimmed + # - auth token is masked out + # + # @return [Net::HTTPResponse] Sanitized GCE response in YAML format + def scrub_gce(key, resp) + case key + when "sshKeys" + resp.body = "googler:ssh-rsa AAA...ej googler@facter-dev\n" + resp["content-length"] = "44" + when "project-id" + resp.body = "development_project" + resp["content-length"] = "19" + when "token" + resp.body = '{"access_token":"ya29.AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","expires_in":1234,"token_type":"Bearer"}' + end + resp + end +end From fde0153f56a61fd3bb33cbe6ca5df3da6a1dbdda Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Wed, 11 Sep 2013 15:03:16 -0700 Subject: [PATCH 1409/3753] (#22233) Remove unused GCE fixtures These fixtures aren't actually being used by the tests and as such should not be included in the codebase. --- ..._metadata_instance_attributes_request.yaml | 15 ------- ...metadata_instance_attributes_response.yaml | 23 ---------- ...metadata_instance_description_request.yaml | 15 ------- ...etadata_instance_description_response.yaml | 23 ---------- ..._instance_disks_0_device_name_request.yaml | 15 ------- ...instance_disks_0_device_name_response.yaml | 23 ---------- ...tadata_instance_disks_0_index_request.yaml | 15 ------- ...adata_instance_disks_0_index_response.yaml | 23 ---------- ...etadata_instance_disks_0_mode_request.yaml | 15 ------- ...tadata_instance_disks_0_mode_response.yaml | 23 ---------- ...gce_metadata_instance_disks_0_request.yaml | 15 ------- ...ce_metadata_instance_disks_0_response.yaml | 31 ------------- ...etadata_instance_disks_0_type_request.yaml | 15 ------- ...tadata_instance_disks_0_type_response.yaml | 23 ---------- .../gce_metadata_instance_disks_request.yaml | 15 ------- .../gce_metadata_instance_disks_response.yaml | 25 ----------- ...ce_metadata_instance_hostname_request.yaml | 15 ------- ...e_metadata_instance_hostname_response.yaml | 23 ---------- .../gce/gce_metadata_instance_id_request.yaml | 15 ------- .../gce_metadata_instance_id_response.yaml | 23 ---------- .../gce_metadata_instance_image_request.yaml | 15 ------- .../gce_metadata_instance_image_response.yaml | 23 ---------- ...etadata_instance_machine_type_request.yaml | 15 ------- ...tadata_instance_machine_type_response.yaml | 23 ---------- ..._access_configs_0_external_ip_request.yaml | 15 ------- ...access_configs_0_external_ip_response.yaml | 23 ---------- ...interfaces_0_access_configs_0_request.yaml | 15 ------- ...nterfaces_0_access_configs_0_response.yaml | 27 ----------- ...faces_0_access_configs_0_type_request.yaml | 15 ------- ...aces_0_access_configs_0_type_response.yaml | 23 ---------- ...k_interfaces_0_access_configs_request.yaml | 15 ------- ..._interfaces_0_access_configs_response.yaml | 25 ----------- ...rk_interfaces_0_forwarded_ips_request.yaml | 15 ------- ...k_interfaces_0_forwarded_ips_response.yaml | 23 ---------- ...tance_network_interfaces_0_ip_request.yaml | 15 ------- ...ance_network_interfaces_0_ip_response.yaml | 23 ---------- ..._network_interfaces_0_network_request.yaml | 15 ------- ...network_interfaces_0_network_response.yaml | 23 ---------- ...instance_network_interfaces_0_request.yaml | 15 ------- ...nstance_network_interfaces_0_response.yaml | 31 ------------- ...a_instance_network_interfaces_request.yaml | 15 ------- ..._instance_network_interfaces_response.yaml | 25 ----------- .../gce/gce_metadata_instance_request.yaml | 15 ------- .../gce/gce_metadata_instance_response.yaml | 45 ------------------- ...t_gserviceaccount_com_aliases_request.yaml | 15 ------- ..._gserviceaccount_com_aliases_response.yaml | 25 ----------- ...ect_gserviceaccount_com_email_request.yaml | 15 ------- ...ct_gserviceaccount_com_email_response.yaml | 23 ---------- ...1_project_gserviceaccount_com_request.yaml | 15 ------- ..._project_gserviceaccount_com_response.yaml | 31 ------------- ...ct_gserviceaccount_com_scopes_request.yaml | 15 ------- ...t_gserviceaccount_com_scopes_response.yaml | 29 ------------ ...ect_gserviceaccount_com_token_request.yaml | 15 ------- ...ct_gserviceaccount_com_token_response.yaml | 21 --------- ...vice_accounts_default_aliases_request.yaml | 15 ------- ...ice_accounts_default_aliases_response.yaml | 25 ----------- ...ervice_accounts_default_email_request.yaml | 15 ------- ...rvice_accounts_default_email_response.yaml | 23 ---------- ...ance_service_accounts_default_request.yaml | 15 ------- ...nce_service_accounts_default_response.yaml | 31 ------------- ...rvice_accounts_default_scopes_request.yaml | 15 ------- ...vice_accounts_default_scopes_response.yaml | 29 ------------ ...ervice_accounts_default_token_request.yaml | 15 ------- ...rvice_accounts_default_token_response.yaml | 21 --------- ...ata_instance_service_accounts_request.yaml | 15 ------- ...ta_instance_service_accounts_response.yaml | 27 ----------- .../gce_metadata_instance_tags_request.yaml | 15 ------- .../gce_metadata_instance_tags_response.yaml | 23 ---------- .../gce_metadata_instance_zone_request.yaml | 15 ------- .../gce_metadata_instance_zone_response.yaml | 23 ---------- ...e_metadata_project_attributes_request.yaml | 15 ------- ..._metadata_project_attributes_response.yaml | 25 ----------- ...ta_project_attributes_sshKeys_request.yaml | 15 ------- ...a_project_attributes_sshKeys_response.yaml | 25 ----------- ...ta_project_numeric_project_id_request.yaml | 15 ------- ...a_project_numeric_project_id_response.yaml | 23 ---------- ...e_metadata_project_project_id_request.yaml | 15 ------- ..._metadata_project_project_id_response.yaml | 23 ---------- .../gce/gce_metadata_project_request.yaml | 15 ------- .../gce/gce_metadata_project_response.yaml | 29 ------------ .../unit/util/gce/gce_metadata_request.yaml | 15 ------- .../unit/util/gce/gce_metadata_response.yaml | 27 ----------- 82 files changed, 1652 deletions(-) delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_attributes_request.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_attributes_response.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_description_request.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_description_response.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_device_name_request.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_device_name_response.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_index_request.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_index_response.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_mode_request.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_mode_response.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_request.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_response.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_type_request.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_type_response.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_disks_request.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_disks_response.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_hostname_request.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_hostname_response.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_id_request.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_id_response.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_image_request.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_image_response.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_machine_type_request.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_machine_type_response.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_external_ip_request.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_external_ip_response.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_request.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_response.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_type_request.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_type_response.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_request.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_response.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_forwarded_ips_request.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_forwarded_ips_response.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_ip_request.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_ip_response.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_network_request.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_network_response.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_request.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_response.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_request.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_response.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_request.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_response.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_aliases_request.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_aliases_response.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_email_request.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_email_response.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_request.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_response.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_scopes_request.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_scopes_response.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_token_request.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_token_response.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_aliases_request.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_aliases_response.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_email_request.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_email_response.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_request.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_response.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_scopes_request.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_scopes_response.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_token_request.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_token_response.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_request.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_response.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_tags_request.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_tags_response.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_zone_request.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_instance_zone_response.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_project_attributes_request.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_project_attributes_response.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_project_attributes_sshKeys_request.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_project_attributes_sshKeys_response.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_project_numeric_project_id_request.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_project_numeric_project_id_response.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_project_project_id_request.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_project_project_id_response.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_project_request.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_project_response.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_request.yaml delete mode 100644 spec/fixtures/unit/util/gce/gce_metadata_response.yaml diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_attributes_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_attributes_request.yaml deleted file mode 100644 index d5bb46d0d9..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_attributes_request.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- !ruby/object:Net::HTTP::Get -method: GET -request_has_body: false -response_has_body: true -path: /computeMetadata/v1beta1/instance/attributes/ -header: - accept: - - ! '*/*' - user-agent: - - Ruby - host: - - metadata -body: -body_stream: -body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_attributes_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_attributes_response.yaml deleted file mode 100644 index bc0df5bbc3..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_attributes_response.yaml +++ /dev/null @@ -1,23 +0,0 @@ ---- !ruby/object:Net::HTTPOK -http_version: '1.1' -code: '200' -message: OK -header: - content-type: - - application/text - etag: - - 3c19e4d76229ba8c - date: - - Sat, 17 Aug 2013 17:27:19 GMT - server: - - Metadata Server for VM - content-length: - - '0' - x-xss-protection: - - 1; mode=block - x-frame-options: - - SAMEORIGIN -body: '' -read: true -socket: -body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_description_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_description_request.yaml deleted file mode 100644 index 7304b03683..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_description_request.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- !ruby/object:Net::HTTP::Get -method: GET -request_has_body: false -response_has_body: true -path: /computeMetadata/v1beta1/instance/description -header: - accept: - - ! '*/*' - user-agent: - - Ruby - host: - - metadata -body: -body_stream: -body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_description_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_description_response.yaml deleted file mode 100644 index bc0df5bbc3..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_description_response.yaml +++ /dev/null @@ -1,23 +0,0 @@ ---- !ruby/object:Net::HTTPOK -http_version: '1.1' -code: '200' -message: OK -header: - content-type: - - application/text - etag: - - 3c19e4d76229ba8c - date: - - Sat, 17 Aug 2013 17:27:19 GMT - server: - - Metadata Server for VM - content-length: - - '0' - x-xss-protection: - - 1; mode=block - x-frame-options: - - SAMEORIGIN -body: '' -read: true -socket: -body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_device_name_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_device_name_request.yaml deleted file mode 100644 index e1d1e295c6..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_device_name_request.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- !ruby/object:Net::HTTP::Get -method: GET -request_has_body: false -response_has_body: true -path: /computeMetadata/v1beta1/instance/disks/0/device-name -header: - accept: - - ! '*/*' - user-agent: - - Ruby - host: - - metadata -body: -body_stream: -body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_device_name_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_device_name_response.yaml deleted file mode 100644 index 0ffe0480d4..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_device_name_response.yaml +++ /dev/null @@ -1,23 +0,0 @@ ---- !ruby/object:Net::HTTPOK -http_version: '1.1' -code: '200' -message: OK -header: - content-type: - - application/text - etag: - - 293a50d9c06a8a32 - date: - - Sat, 17 Aug 2013 17:27:19 GMT - server: - - Metadata Server for VM - content-length: - - '15' - x-xss-protection: - - 1; mode=block - x-frame-options: - - SAMEORIGIN -body: boot-facter-dev -read: true -socket: -body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_index_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_index_request.yaml deleted file mode 100644 index 38b21f041b..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_index_request.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- !ruby/object:Net::HTTP::Get -method: GET -request_has_body: false -response_has_body: true -path: /computeMetadata/v1beta1/instance/disks/0/index -header: - accept: - - ! '*/*' - user-agent: - - Ruby - host: - - metadata -body: -body_stream: -body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_index_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_index_response.yaml deleted file mode 100644 index dae5d35001..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_index_response.yaml +++ /dev/null @@ -1,23 +0,0 @@ ---- !ruby/object:Net::HTTPOK -http_version: '1.1' -code: '200' -message: OK -header: - content-type: - - application/text - etag: - - 99a9c7abf407dd27 - date: - - Sat, 17 Aug 2013 17:27:19 GMT - server: - - Metadata Server for VM - content-length: - - '1' - x-xss-protection: - - 1; mode=block - x-frame-options: - - SAMEORIGIN -body: '0' -read: true -socket: -body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_mode_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_mode_request.yaml deleted file mode 100644 index 52c3ee3f11..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_mode_request.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- !ruby/object:Net::HTTP::Get -method: GET -request_has_body: false -response_has_body: true -path: /computeMetadata/v1beta1/instance/disks/0/mode -header: - accept: - - ! '*/*' - user-agent: - - Ruby - host: - - metadata -body: -body_stream: -body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_mode_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_mode_response.yaml deleted file mode 100644 index 5f575f4e3e..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_mode_response.yaml +++ /dev/null @@ -1,23 +0,0 @@ ---- !ruby/object:Net::HTTPOK -http_version: '1.1' -code: '200' -message: OK -header: - content-type: - - application/text - etag: - - 50d532090d9f52d2 - date: - - Sat, 17 Aug 2013 17:27:19 GMT - server: - - Metadata Server for VM - content-length: - - '10' - x-xss-protection: - - 1; mode=block - x-frame-options: - - SAMEORIGIN -body: READ_WRITE -read: true -socket: -body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_request.yaml deleted file mode 100644 index d79a2c1737..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_request.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- !ruby/object:Net::HTTP::Get -method: GET -request_has_body: false -response_has_body: true -path: /computeMetadata/v1beta1/instance/disks/0/ -header: - accept: - - ! '*/*' - user-agent: - - Ruby - host: - - metadata -body: -body_stream: -body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_response.yaml deleted file mode 100644 index ece51354d6..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_response.yaml +++ /dev/null @@ -1,31 +0,0 @@ ---- !ruby/object:Net::HTTPOK -http_version: '1.1' -code: '200' -message: OK -header: - content-type: - - application/text - etag: - - b1eee485c34883d2 - date: - - Sat, 17 Aug 2013 17:27:19 GMT - server: - - Metadata Server for VM - content-length: - - '28' - x-xss-protection: - - 1; mode=block - x-frame-options: - - SAMEORIGIN -body: ! 'device-name - - index - - mode - - type - -' -read: true -socket: -body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_type_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_type_request.yaml deleted file mode 100644 index 1a65e5e3cd..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_type_request.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- !ruby/object:Net::HTTP::Get -method: GET -request_has_body: false -response_has_body: true -path: /computeMetadata/v1beta1/instance/disks/0/type -header: - accept: - - ! '*/*' - user-agent: - - Ruby - host: - - metadata -body: -body_stream: -body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_type_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_type_response.yaml deleted file mode 100644 index 459097acb7..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_0_type_response.yaml +++ /dev/null @@ -1,23 +0,0 @@ ---- !ruby/object:Net::HTTPOK -http_version: '1.1' -code: '200' -message: OK -header: - content-type: - - application/text - etag: - - c69eb581f2475c79 - date: - - Sat, 17 Aug 2013 17:27:19 GMT - server: - - Metadata Server for VM - content-length: - - '10' - x-xss-protection: - - 1; mode=block - x-frame-options: - - SAMEORIGIN -body: PERSISTENT -read: true -socket: -body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_request.yaml deleted file mode 100644 index 5d7696da62..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_request.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- !ruby/object:Net::HTTP::Get -method: GET -request_has_body: false -response_has_body: true -path: /computeMetadata/v1beta1/instance/disks/ -header: - accept: - - ! '*/*' - user-agent: - - Ruby - host: - - metadata -body: -body_stream: -body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_response.yaml deleted file mode 100644 index 5f66ec7004..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_disks_response.yaml +++ /dev/null @@ -1,25 +0,0 @@ ---- !ruby/object:Net::HTTPOK -http_version: '1.1' -code: '200' -message: OK -header: - content-type: - - application/text - etag: - - ab12d55d87368cc3 - date: - - Sat, 17 Aug 2013 17:27:19 GMT - server: - - Metadata Server for VM - content-length: - - '3' - x-xss-protection: - - 1; mode=block - x-frame-options: - - SAMEORIGIN -body: ! '0/ - -' -read: true -socket: -body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_hostname_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_hostname_request.yaml deleted file mode 100644 index 15b90a6e41..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_hostname_request.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- !ruby/object:Net::HTTP::Get -method: GET -request_has_body: false -response_has_body: true -path: /computeMetadata/v1beta1/instance/hostname -header: - accept: - - ! '*/*' - user-agent: - - Ruby - host: - - metadata -body: -body_stream: -body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_hostname_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_hostname_response.yaml deleted file mode 100644 index e004f8b338..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_hostname_response.yaml +++ /dev/null @@ -1,23 +0,0 @@ ---- !ruby/object:Net::HTTPOK -http_version: '1.1' -code: '200' -message: OK -header: - content-type: - - application/text - etag: - - fd0483c1cc3a83fe - date: - - Sat, 17 Aug 2013 17:27:19 GMT - server: - - Metadata Server for VM - content-length: - - '41' - x-xss-protection: - - 1; mode=block - x-frame-options: - - SAMEORIGIN -body: facter-dev.c.erjohnso.google.com.internal -read: true -socket: -body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_id_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_id_request.yaml deleted file mode 100644 index 81567561fc..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_id_request.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- !ruby/object:Net::HTTP::Get -method: GET -request_has_body: false -response_has_body: true -path: /computeMetadata/v1beta1/instance/id -header: - accept: - - ! '*/*' - user-agent: - - Ruby - host: - - metadata -body: -body_stream: -body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_id_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_id_response.yaml deleted file mode 100644 index b8f5b5bdcf..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_id_response.yaml +++ /dev/null @@ -1,23 +0,0 @@ ---- !ruby/object:Net::HTTPOK -http_version: '1.1' -code: '200' -message: OK -header: - content-type: - - application/text - etag: - - d809d4ca2c1d9dfa - date: - - Sat, 17 Aug 2013 17:27:19 GMT - server: - - Metadata Server for VM - content-length: - - '20' - x-xss-protection: - - 1; mode=block - x-frame-options: - - SAMEORIGIN -body: '11111111111167524945' -read: true -socket: -body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_image_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_image_request.yaml deleted file mode 100644 index 3cce9c19db..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_image_request.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- !ruby/object:Net::HTTP::Get -method: GET -request_has_body: false -response_has_body: true -path: /computeMetadata/v1beta1/instance/image -header: - accept: - - ! '*/*' - user-agent: - - Ruby - host: - - metadata -body: -body_stream: -body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_image_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_image_response.yaml deleted file mode 100644 index bc0df5bbc3..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_image_response.yaml +++ /dev/null @@ -1,23 +0,0 @@ ---- !ruby/object:Net::HTTPOK -http_version: '1.1' -code: '200' -message: OK -header: - content-type: - - application/text - etag: - - 3c19e4d76229ba8c - date: - - Sat, 17 Aug 2013 17:27:19 GMT - server: - - Metadata Server for VM - content-length: - - '0' - x-xss-protection: - - 1; mode=block - x-frame-options: - - SAMEORIGIN -body: '' -read: true -socket: -body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_machine_type_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_machine_type_request.yaml deleted file mode 100644 index 7d3b1a4dee..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_machine_type_request.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- !ruby/object:Net::HTTP::Get -method: GET -request_has_body: false -response_has_body: true -path: /computeMetadata/v1beta1/instance/machine-type -header: - accept: - - ! '*/*' - user-agent: - - Ruby - host: - - metadata -body: -body_stream: -body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_machine_type_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_machine_type_response.yaml deleted file mode 100644 index b4fa9f8466..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_machine_type_response.yaml +++ /dev/null @@ -1,23 +0,0 @@ ---- !ruby/object:Net::HTTPOK -http_version: '1.1' -code: '200' -message: OK -header: - content-type: - - application/text - etag: - - 3b5d634c2f6e1895 - date: - - Sat, 17 Aug 2013 17:27:19 GMT - server: - - Metadata Server for VM - content-length: - - '48' - x-xss-protection: - - 1; mode=block - x-frame-options: - - SAMEORIGIN -body: projects/111111111111/machineTypes/n1-standard-1 -read: true -socket: -body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_external_ip_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_external_ip_request.yaml deleted file mode 100644 index a34eea66ea..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_external_ip_request.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- !ruby/object:Net::HTTP::Get -method: GET -request_has_body: false -response_has_body: true -path: /computeMetadata/v1beta1/instance/network-interfaces/0/access-configs/0/external-ip -header: - accept: - - ! '*/*' - user-agent: - - Ruby - host: - - metadata -body: -body_stream: -body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_external_ip_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_external_ip_response.yaml deleted file mode 100644 index 659ec68981..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_external_ip_response.yaml +++ /dev/null @@ -1,23 +0,0 @@ ---- !ruby/object:Net::HTTPOK -http_version: '1.1' -code: '200' -message: OK -header: - content-type: - - application/text - etag: - - f21b7bdff7c43b94 - date: - - Sat, 17 Aug 2013 17:27:19 GMT - server: - - Metadata Server for VM - content-length: - - '13' - x-xss-protection: - - 1; mode=block - x-frame-options: - - SAMEORIGIN -body: 108.59.80.149 -read: true -socket: -body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_request.yaml deleted file mode 100644 index 26e6db75d7..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_request.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- !ruby/object:Net::HTTP::Get -method: GET -request_has_body: false -response_has_body: true -path: /computeMetadata/v1beta1/instance/network-interfaces/0/access-configs/0/ -header: - accept: - - ! '*/*' - user-agent: - - Ruby - host: - - metadata -body: -body_stream: -body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_response.yaml deleted file mode 100644 index cc108bb57a..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_response.yaml +++ /dev/null @@ -1,27 +0,0 @@ ---- !ruby/object:Net::HTTPOK -http_version: '1.1' -code: '200' -message: OK -header: - content-type: - - application/text - etag: - - 94d53a9d51a160b2 - date: - - Sat, 17 Aug 2013 17:27:19 GMT - server: - - Metadata Server for VM - content-length: - - '17' - x-xss-protection: - - 1; mode=block - x-frame-options: - - SAMEORIGIN -body: ! 'external-ip - - type - -' -read: true -socket: -body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_type_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_type_request.yaml deleted file mode 100644 index ffcf51b22e..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_type_request.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- !ruby/object:Net::HTTP::Get -method: GET -request_has_body: false -response_has_body: true -path: /computeMetadata/v1beta1/instance/network-interfaces/0/access-configs/0/type -header: - accept: - - ! '*/*' - user-agent: - - Ruby - host: - - metadata -body: -body_stream: -body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_type_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_type_response.yaml deleted file mode 100644 index 39acba1538..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_0_type_response.yaml +++ /dev/null @@ -1,23 +0,0 @@ ---- !ruby/object:Net::HTTPOK -http_version: '1.1' -code: '200' -message: OK -header: - content-type: - - application/text - etag: - - 3f039588d5138075 - date: - - Sat, 17 Aug 2013 17:27:19 GMT - server: - - Metadata Server for VM - content-length: - - '14' - x-xss-protection: - - 1; mode=block - x-frame-options: - - SAMEORIGIN -body: ONE_TO_ONE_NAT -read: true -socket: -body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_request.yaml deleted file mode 100644 index 2e6a7c9d9e..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_request.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- !ruby/object:Net::HTTP::Get -method: GET -request_has_body: false -response_has_body: true -path: /computeMetadata/v1beta1/instance/network-interfaces/0/access-configs/ -header: - accept: - - ! '*/*' - user-agent: - - Ruby - host: - - metadata -body: -body_stream: -body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_response.yaml deleted file mode 100644 index 5f66ec7004..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_access_configs_response.yaml +++ /dev/null @@ -1,25 +0,0 @@ ---- !ruby/object:Net::HTTPOK -http_version: '1.1' -code: '200' -message: OK -header: - content-type: - - application/text - etag: - - ab12d55d87368cc3 - date: - - Sat, 17 Aug 2013 17:27:19 GMT - server: - - Metadata Server for VM - content-length: - - '3' - x-xss-protection: - - 1; mode=block - x-frame-options: - - SAMEORIGIN -body: ! '0/ - -' -read: true -socket: -body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_forwarded_ips_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_forwarded_ips_request.yaml deleted file mode 100644 index ccaae9c45f..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_forwarded_ips_request.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- !ruby/object:Net::HTTP::Get -method: GET -request_has_body: false -response_has_body: true -path: /computeMetadata/v1beta1/instance/network-interfaces/0/forwarded-ips/ -header: - accept: - - ! '*/*' - user-agent: - - Ruby - host: - - metadata -body: -body_stream: -body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_forwarded_ips_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_forwarded_ips_response.yaml deleted file mode 100644 index bc0df5bbc3..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_forwarded_ips_response.yaml +++ /dev/null @@ -1,23 +0,0 @@ ---- !ruby/object:Net::HTTPOK -http_version: '1.1' -code: '200' -message: OK -header: - content-type: - - application/text - etag: - - 3c19e4d76229ba8c - date: - - Sat, 17 Aug 2013 17:27:19 GMT - server: - - Metadata Server for VM - content-length: - - '0' - x-xss-protection: - - 1; mode=block - x-frame-options: - - SAMEORIGIN -body: '' -read: true -socket: -body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_ip_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_ip_request.yaml deleted file mode 100644 index f2705c05aa..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_ip_request.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- !ruby/object:Net::HTTP::Get -method: GET -request_has_body: false -response_has_body: true -path: /computeMetadata/v1beta1/instance/network-interfaces/0/ip -header: - accept: - - ! '*/*' - user-agent: - - Ruby - host: - - metadata -body: -body_stream: -body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_ip_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_ip_response.yaml deleted file mode 100644 index 7f27e2f37e..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_ip_response.yaml +++ /dev/null @@ -1,23 +0,0 @@ ---- !ruby/object:Net::HTTPOK -http_version: '1.1' -code: '200' -message: OK -header: - content-type: - - application/text - etag: - - 7b9925a822e85501 - date: - - Sat, 17 Aug 2013 17:27:19 GMT - server: - - Metadata Server for VM - content-length: - - '13' - x-xss-protection: - - 1; mode=block - x-frame-options: - - SAMEORIGIN -body: 10.240.75.158 -read: true -socket: -body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_network_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_network_request.yaml deleted file mode 100644 index c200fddf15..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_network_request.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- !ruby/object:Net::HTTP::Get -method: GET -request_has_body: false -response_has_body: true -path: /computeMetadata/v1beta1/instance/network-interfaces/0/network -header: - accept: - - ! '*/*' - user-agent: - - Ruby - host: - - metadata -body: -body_stream: -body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_network_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_network_response.yaml deleted file mode 100644 index 7f561fc613..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_network_response.yaml +++ /dev/null @@ -1,23 +0,0 @@ ---- !ruby/object:Net::HTTPOK -http_version: '1.1' -code: '200' -message: OK -header: - content-type: - - application/text - etag: - - b978938d33323a32 - date: - - Sat, 17 Aug 2013 17:27:19 GMT - server: - - Metadata Server for VM - content-length: - - '38' - x-xss-protection: - - 1; mode=block - x-frame-options: - - SAMEORIGIN -body: projects/111111111111/networks/default -read: true -socket: -body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_request.yaml deleted file mode 100644 index 307b854d94..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_request.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- !ruby/object:Net::HTTP::Get -method: GET -request_has_body: false -response_has_body: true -path: /computeMetadata/v1beta1/instance/network-interfaces/0/ -header: - accept: - - ! '*/*' - user-agent: - - Ruby - host: - - metadata -body: -body_stream: -body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_response.yaml deleted file mode 100644 index 47b7de2cf2..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_0_response.yaml +++ /dev/null @@ -1,31 +0,0 @@ ---- !ruby/object:Net::HTTPOK -http_version: '1.1' -code: '200' -message: OK -header: - content-type: - - application/text - etag: - - 8aeb9aad2dc89639 - date: - - Sat, 17 Aug 2013 17:27:19 GMT - server: - - Metadata Server for VM - content-length: - - '42' - x-xss-protection: - - 1; mode=block - x-frame-options: - - SAMEORIGIN -body: ! 'access-configs/ - - forwarded-ips/ - - ip - - network - -' -read: true -socket: -body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_request.yaml deleted file mode 100644 index ef689604b8..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_request.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- !ruby/object:Net::HTTP::Get -method: GET -request_has_body: false -response_has_body: true -path: /computeMetadata/v1beta1/instance/network-interfaces/ -header: - accept: - - ! '*/*' - user-agent: - - Ruby - host: - - metadata -body: -body_stream: -body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_response.yaml deleted file mode 100644 index 5f66ec7004..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_network_interfaces_response.yaml +++ /dev/null @@ -1,25 +0,0 @@ ---- !ruby/object:Net::HTTPOK -http_version: '1.1' -code: '200' -message: OK -header: - content-type: - - application/text - etag: - - ab12d55d87368cc3 - date: - - Sat, 17 Aug 2013 17:27:19 GMT - server: - - Metadata Server for VM - content-length: - - '3' - x-xss-protection: - - 1; mode=block - x-frame-options: - - SAMEORIGIN -body: ! '0/ - -' -read: true -socket: -body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_request.yaml deleted file mode 100644 index bf6ad02d34..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_request.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- !ruby/object:Net::HTTP::Get -method: GET -request_has_body: false -response_has_body: true -path: /computeMetadata/v1beta1/instance/ -header: - accept: - - ! '*/*' - user-agent: - - Ruby - host: - - metadata -body: -body_stream: -body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_response.yaml deleted file mode 100644 index 027ca82587..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_response.yaml +++ /dev/null @@ -1,45 +0,0 @@ ---- !ruby/object:Net::HTTPOK -http_version: '1.1' -code: '200' -message: OK -header: - content-type: - - application/text - etag: - - d2c43af72b3b62b0 - date: - - Sat, 17 Aug 2013 17:27:19 GMT - server: - - Metadata Server for VM - content-length: - - '110' - x-xss-protection: - - 1; mode=block - x-frame-options: - - SAMEORIGIN -body: ! 'attributes/ - - description - - disks/ - - hostname - - id - - image - - machine-type - - network-interfaces/ - - service-accounts/ - - tags - - zone - -' -read: true -socket: -body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_aliases_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_aliases_request.yaml deleted file mode 100644 index ddedde0faf..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_aliases_request.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- !ruby/object:Net::HTTP::Get -method: GET -request_has_body: false -response_has_body: true -path: /computeMetadata/v1beta1/instance/service-accounts/111111111111@project.gserviceaccount.com/aliases -header: - accept: - - ! '*/*' - user-agent: - - Ruby - host: - - metadata -body: -body_stream: -body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_aliases_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_aliases_response.yaml deleted file mode 100644 index bf9f0a0494..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_aliases_response.yaml +++ /dev/null @@ -1,25 +0,0 @@ ---- !ruby/object:Net::HTTPOK -http_version: '1.1' -code: '200' -message: OK -header: - content-type: - - application/text - etag: - - b4247236bff543a4 - date: - - Sat, 17 Aug 2013 17:27:19 GMT - server: - - Metadata Server for VM - content-length: - - '8' - x-xss-protection: - - 1; mode=block - x-frame-options: - - SAMEORIGIN -body: ! 'default - -' -read: true -socket: -body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_email_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_email_request.yaml deleted file mode 100644 index a3e2e5da07..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_email_request.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- !ruby/object:Net::HTTP::Get -method: GET -request_has_body: false -response_has_body: true -path: /computeMetadata/v1beta1/instance/service-accounts/111111111111@project.gserviceaccount.com/email -header: - accept: - - ! '*/*' - user-agent: - - Ruby - host: - - metadata -body: -body_stream: -body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_email_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_email_response.yaml deleted file mode 100644 index 09c5dd7421..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_email_response.yaml +++ /dev/null @@ -1,23 +0,0 @@ ---- !ruby/object:Net::HTTPOK -http_version: '1.1' -code: '200' -message: OK -header: - content-type: - - application/text - etag: - - 37ec425b66a500d - date: - - Sat, 17 Aug 2013 17:27:19 GMT - server: - - Metadata Server for VM - content-length: - - '40' - x-xss-protection: - - 1; mode=block - x-frame-options: - - SAMEORIGIN -body: 111111111111@project.gserviceaccount.com -read: true -socket: -body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_request.yaml deleted file mode 100644 index 3d06dcec70..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_request.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- !ruby/object:Net::HTTP::Get -method: GET -request_has_body: false -response_has_body: true -path: /computeMetadata/v1beta1/instance/service-accounts/111111111111@project.gserviceaccount.com/ -header: - accept: - - ! '*/*' - user-agent: - - Ruby - host: - - metadata -body: -body_stream: -body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_response.yaml deleted file mode 100644 index fd3f671a9d..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_response.yaml +++ /dev/null @@ -1,31 +0,0 @@ ---- !ruby/object:Net::HTTPOK -http_version: '1.1' -code: '200' -message: OK -header: - content-type: - - application/text - etag: - - 2357256c2bcc7636 - date: - - Sat, 17 Aug 2013 17:27:19 GMT - server: - - Metadata Server for VM - content-length: - - '27' - x-xss-protection: - - 1; mode=block - x-frame-options: - - SAMEORIGIN -body: ! 'aliases - - email - - scopes - - token - -' -read: true -socket: -body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_scopes_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_scopes_request.yaml deleted file mode 100644 index 8a886be1fe..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_scopes_request.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- !ruby/object:Net::HTTP::Get -method: GET -request_has_body: false -response_has_body: true -path: /computeMetadata/v1beta1/instance/service-accounts/111111111111@project.gserviceaccount.com/scopes -header: - accept: - - ! '*/*' - user-agent: - - Ruby - host: - - metadata -body: -body_stream: -body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_scopes_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_scopes_response.yaml deleted file mode 100644 index 1dfe1c8e25..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_scopes_response.yaml +++ /dev/null @@ -1,29 +0,0 @@ ---- !ruby/object:Net::HTTPOK -http_version: '1.1' -code: '200' -message: OK -header: - content-type: - - application/text - etag: - - 7a6c63608b17425d - date: - - Sat, 17 Aug 2013 17:27:19 GMT - server: - - Metadata Server for VM - content-length: - - '143' - x-xss-protection: - - 1; mode=block - x-frame-options: - - SAMEORIGIN -body: ! 'https://www.googleapis.com/auth/userinfo.email--%20%20https://www.googleapis.com/auth/compute--%20%20https://www.googleapis.com/auth/devstorage.full_control--' -read: true -socket: -body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_token_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_token_request.yaml deleted file mode 100644 index b29f9ebb61..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_token_request.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- !ruby/object:Net::HTTP::Get -method: GET -request_has_body: false -response_has_body: true -path: /computeMetadata/v1beta1/instance/service-accounts/111111111111@project.gserviceaccount.com/token -header: - accept: - - ! '*/*' - user-agent: - - Ruby - host: - - metadata -body: -body_stream: -body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_token_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_token_response.yaml deleted file mode 100644 index fcb4836b7e..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_111111111111_project_gserviceaccount_com_token_response.yaml +++ /dev/null @@ -1,21 +0,0 @@ ---- !ruby/object:Net::HTTPOK -http_version: '1.1' -code: '200' -message: OK -header: - content-type: - - application/json - date: - - Sat, 17 Aug 2013 17:27:19 GMT - server: - - Metadata Server for VM - content-length: - - '114' - x-xss-protection: - - 1; mode=block - x-frame-options: - - SAMEORIGIN -body: ! '{"access_token":"ya29.AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","expires_in":1234,"token_type":"Bearer"}' -read: true -socket: -body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_aliases_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_aliases_request.yaml deleted file mode 100644 index 78ef2bddad..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_aliases_request.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- !ruby/object:Net::HTTP::Get -method: GET -request_has_body: false -response_has_body: true -path: /computeMetadata/v1beta1/instance/service-accounts/default/aliases -header: - accept: - - ! '*/*' - user-agent: - - Ruby - host: - - metadata -body: -body_stream: -body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_aliases_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_aliases_response.yaml deleted file mode 100644 index bf9f0a0494..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_aliases_response.yaml +++ /dev/null @@ -1,25 +0,0 @@ ---- !ruby/object:Net::HTTPOK -http_version: '1.1' -code: '200' -message: OK -header: - content-type: - - application/text - etag: - - b4247236bff543a4 - date: - - Sat, 17 Aug 2013 17:27:19 GMT - server: - - Metadata Server for VM - content-length: - - '8' - x-xss-protection: - - 1; mode=block - x-frame-options: - - SAMEORIGIN -body: ! 'default - -' -read: true -socket: -body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_email_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_email_request.yaml deleted file mode 100644 index db6789e875..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_email_request.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- !ruby/object:Net::HTTP::Get -method: GET -request_has_body: false -response_has_body: true -path: /computeMetadata/v1beta1/instance/service-accounts/default/email -header: - accept: - - ! '*/*' - user-agent: - - Ruby - host: - - metadata -body: -body_stream: -body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_email_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_email_response.yaml deleted file mode 100644 index 09c5dd7421..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_email_response.yaml +++ /dev/null @@ -1,23 +0,0 @@ ---- !ruby/object:Net::HTTPOK -http_version: '1.1' -code: '200' -message: OK -header: - content-type: - - application/text - etag: - - 37ec425b66a500d - date: - - Sat, 17 Aug 2013 17:27:19 GMT - server: - - Metadata Server for VM - content-length: - - '40' - x-xss-protection: - - 1; mode=block - x-frame-options: - - SAMEORIGIN -body: 111111111111@project.gserviceaccount.com -read: true -socket: -body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_request.yaml deleted file mode 100644 index 64be4d4133..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_request.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- !ruby/object:Net::HTTP::Get -method: GET -request_has_body: false -response_has_body: true -path: /computeMetadata/v1beta1/instance/service-accounts/default/ -header: - accept: - - ! '*/*' - user-agent: - - Ruby - host: - - metadata -body: -body_stream: -body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_response.yaml deleted file mode 100644 index fd3f671a9d..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_response.yaml +++ /dev/null @@ -1,31 +0,0 @@ ---- !ruby/object:Net::HTTPOK -http_version: '1.1' -code: '200' -message: OK -header: - content-type: - - application/text - etag: - - 2357256c2bcc7636 - date: - - Sat, 17 Aug 2013 17:27:19 GMT - server: - - Metadata Server for VM - content-length: - - '27' - x-xss-protection: - - 1; mode=block - x-frame-options: - - SAMEORIGIN -body: ! 'aliases - - email - - scopes - - token - -' -read: true -socket: -body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_scopes_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_scopes_request.yaml deleted file mode 100644 index 36ec584a6b..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_scopes_request.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- !ruby/object:Net::HTTP::Get -method: GET -request_has_body: false -response_has_body: true -path: /computeMetadata/v1beta1/instance/service-accounts/default/scopes -header: - accept: - - ! '*/*' - user-agent: - - Ruby - host: - - metadata -body: -body_stream: -body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_scopes_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_scopes_response.yaml deleted file mode 100644 index 1dfe1c8e25..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_scopes_response.yaml +++ /dev/null @@ -1,29 +0,0 @@ ---- !ruby/object:Net::HTTPOK -http_version: '1.1' -code: '200' -message: OK -header: - content-type: - - application/text - etag: - - 7a6c63608b17425d - date: - - Sat, 17 Aug 2013 17:27:19 GMT - server: - - Metadata Server for VM - content-length: - - '143' - x-xss-protection: - - 1; mode=block - x-frame-options: - - SAMEORIGIN -body: ! 'https://www.googleapis.com/auth/userinfo.email--%20%20https://www.googleapis.com/auth/compute--%20%20https://www.googleapis.com/auth/devstorage.full_control--' -read: true -socket: -body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_token_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_token_request.yaml deleted file mode 100644 index bd4a1c1a92..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_token_request.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- !ruby/object:Net::HTTP::Get -method: GET -request_has_body: false -response_has_body: true -path: /computeMetadata/v1beta1/instance/service-accounts/default/token -header: - accept: - - ! '*/*' - user-agent: - - Ruby - host: - - metadata -body: -body_stream: -body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_token_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_token_response.yaml deleted file mode 100644 index fcb4836b7e..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_default_token_response.yaml +++ /dev/null @@ -1,21 +0,0 @@ ---- !ruby/object:Net::HTTPOK -http_version: '1.1' -code: '200' -message: OK -header: - content-type: - - application/json - date: - - Sat, 17 Aug 2013 17:27:19 GMT - server: - - Metadata Server for VM - content-length: - - '114' - x-xss-protection: - - 1; mode=block - x-frame-options: - - SAMEORIGIN -body: ! '{"access_token":"ya29.AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","expires_in":1234,"token_type":"Bearer"}' -read: true -socket: -body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_request.yaml deleted file mode 100644 index 84af1c28d4..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_request.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- !ruby/object:Net::HTTP::Get -method: GET -request_has_body: false -response_has_body: true -path: /computeMetadata/v1beta1/instance/service-accounts/ -header: - accept: - - ! '*/*' - user-agent: - - Ruby - host: - - metadata -body: -body_stream: -body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_response.yaml deleted file mode 100644 index 8c45b73ea3..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_service_accounts_response.yaml +++ /dev/null @@ -1,27 +0,0 @@ ---- !ruby/object:Net::HTTPOK -http_version: '1.1' -code: '200' -message: OK -header: - content-type: - - application/text - etag: - - 5f077465e28c38fc - date: - - Sat, 17 Aug 2013 17:27:19 GMT - server: - - Metadata Server for VM - content-length: - - '51' - x-xss-protection: - - 1; mode=block - x-frame-options: - - SAMEORIGIN -body: ! '111111111111@project.gserviceaccount.com/ - - default/ - -' -read: true -socket: -body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_tags_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_tags_request.yaml deleted file mode 100644 index d5c428fc0f..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_tags_request.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- !ruby/object:Net::HTTP::Get -method: GET -request_has_body: false -response_has_body: true -path: /computeMetadata/v1beta1/instance/tags -header: - accept: - - ! '*/*' - user-agent: - - Ruby - host: - - metadata -body: -body_stream: -body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_tags_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_tags_response.yaml deleted file mode 100644 index 38931c104e..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_tags_response.yaml +++ /dev/null @@ -1,23 +0,0 @@ ---- !ruby/object:Net::HTTPOK -http_version: '1.1' -code: '200' -message: OK -header: - content-type: - - application/json - etag: - - d8f74779f132891f - date: - - Sat, 17 Aug 2013 17:27:19 GMT - server: - - Metadata Server for VM - content-length: - - '22' - x-xss-protection: - - 1; mode=block - x-frame-options: - - SAMEORIGIN -body: ! '["taga","tagb","tagc"]' -read: true -socket: -body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_zone_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_zone_request.yaml deleted file mode 100644 index 4670dd125d..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_zone_request.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- !ruby/object:Net::HTTP::Get -method: GET -request_has_body: false -response_has_body: true -path: /computeMetadata/v1beta1/instance/zone -header: - accept: - - ! '*/*' - user-agent: - - Ruby - host: - - metadata -body: -body_stream: -body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_instance_zone_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_instance_zone_response.yaml deleted file mode 100644 index 53c2ab6283..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_instance_zone_response.yaml +++ /dev/null @@ -1,23 +0,0 @@ ---- !ruby/object:Net::HTTPOK -http_version: '1.1' -code: '200' -message: OK -header: - content-type: - - application/text - etag: - - a4c6308ff0045544 - date: - - Sat, 17 Aug 2013 17:27:19 GMT - server: - - Metadata Server for VM - content-length: - - '41' - x-xss-protection: - - 1; mode=block - x-frame-options: - - SAMEORIGIN -body: projects/111111111111/zones/us-central1-b -read: true -socket: -body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_project_attributes_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_project_attributes_request.yaml deleted file mode 100644 index 8ae6d17a76..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_project_attributes_request.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- !ruby/object:Net::HTTP::Get -method: GET -request_has_body: false -response_has_body: true -path: /computeMetadata/v1beta1/project/attributes/ -header: - accept: - - ! '*/*' - user-agent: - - Ruby - host: - - metadata -body: -body_stream: -body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_project_attributes_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_project_attributes_response.yaml deleted file mode 100644 index 4586a7b60e..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_project_attributes_response.yaml +++ /dev/null @@ -1,25 +0,0 @@ ---- !ruby/object:Net::HTTPOK -http_version: '1.1' -code: '200' -message: OK -header: - content-type: - - application/text - etag: - - 4b14c7cf9ef3ff05 - date: - - Sat, 17 Aug 2013 17:27:19 GMT - server: - - Metadata Server for VM - content-length: - - '8' - x-xss-protection: - - 1; mode=block - x-frame-options: - - SAMEORIGIN -body: ! 'sshKeys - -' -read: true -socket: -body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_project_attributes_sshKeys_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_project_attributes_sshKeys_request.yaml deleted file mode 100644 index 434128c228..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_project_attributes_sshKeys_request.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- !ruby/object:Net::HTTP::Get -method: GET -request_has_body: false -response_has_body: true -path: /computeMetadata/v1beta1/project/attributes/sshKeys -header: - accept: - - ! '*/*' - user-agent: - - Ruby - host: - - metadata -body: -body_stream: -body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_project_attributes_sshKeys_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_project_attributes_sshKeys_response.yaml deleted file mode 100644 index 276783ca1b..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_project_attributes_sshKeys_response.yaml +++ /dev/null @@ -1,25 +0,0 @@ ---- !ruby/object:Net::HTTPOK -http_version: '1.1' -code: '200' -message: OK -header: - content-type: - - application/text - etag: - - c96b7e65135ea515 - date: - - Sat, 17 Aug 2013 17:27:19 GMT - server: - - Metadata Server for VM - content-length: - - '44' - x-xss-protection: - - 1; mode=block - x-frame-options: - - SAMEORIGIN -body: ! 'googler:ssh-rsa AAA...ej googler@facter-dev - -' -read: true -socket: -body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_project_numeric_project_id_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_project_numeric_project_id_request.yaml deleted file mode 100644 index 4e8976e8b5..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_project_numeric_project_id_request.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- !ruby/object:Net::HTTP::Get -method: GET -request_has_body: false -response_has_body: true -path: /computeMetadata/v1beta1/project/numeric-project-id -header: - accept: - - ! '*/*' - user-agent: - - Ruby - host: - - metadata -body: -body_stream: -body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_project_numeric_project_id_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_project_numeric_project_id_response.yaml deleted file mode 100644 index d9e1a26f91..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_project_numeric_project_id_response.yaml +++ /dev/null @@ -1,23 +0,0 @@ ---- !ruby/object:Net::HTTPOK -http_version: '1.1' -code: '200' -message: OK -header: - content-type: - - application/text - etag: - - b3e3edd326c1b6c8 - date: - - Sat, 17 Aug 2013 17:27:19 GMT - server: - - Metadata Server for VM - content-length: - - '12' - x-xss-protection: - - 1; mode=block - x-frame-options: - - SAMEORIGIN -body: '111111111111' -read: true -socket: -body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_project_project_id_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_project_project_id_request.yaml deleted file mode 100644 index 76a60ca56f..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_project_project_id_request.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- !ruby/object:Net::HTTP::Get -method: GET -request_has_body: false -response_has_body: true -path: /computeMetadata/v1beta1/project/project-id -header: - accept: - - ! '*/*' - user-agent: - - Ruby - host: - - metadata -body: -body_stream: -body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_project_project_id_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_project_project_id_response.yaml deleted file mode 100644 index 0ac920c85a..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_project_project_id_response.yaml +++ /dev/null @@ -1,23 +0,0 @@ ---- !ruby/object:Net::HTTPOK -http_version: '1.1' -code: '200' -message: OK -header: - content-type: - - application/text - etag: - - c304e9e2bacfa408 - date: - - Sat, 17 Aug 2013 17:27:19 GMT - server: - - Metadata Server for VM - content-length: - - '19' - x-xss-protection: - - 1; mode=block - x-frame-options: - - SAMEORIGIN -body: development_project -read: true -socket: -body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_project_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_project_request.yaml deleted file mode 100644 index 0e0d52c482..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_project_request.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- !ruby/object:Net::HTTP::Get -method: GET -request_has_body: false -response_has_body: true -path: /computeMetadata/v1beta1/project/ -header: - accept: - - ! '*/*' - user-agent: - - Ruby - host: - - metadata -body: -body_stream: -body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_project_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_project_response.yaml deleted file mode 100644 index 66aa2203b9..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_project_response.yaml +++ /dev/null @@ -1,29 +0,0 @@ ---- !ruby/object:Net::HTTPOK -http_version: '1.1' -code: '200' -message: OK -header: - content-type: - - application/text - etag: - - 7b505a6ed13cdb4c - date: - - Sat, 17 Aug 2013 17:27:19 GMT - server: - - Metadata Server for VM - content-length: - - '42' - x-xss-protection: - - 1; mode=block - x-frame-options: - - SAMEORIGIN -body: ! 'attributes/ - - numeric-project-id - - project-id - -' -read: true -socket: -body_exist: true diff --git a/spec/fixtures/unit/util/gce/gce_metadata_request.yaml b/spec/fixtures/unit/util/gce/gce_metadata_request.yaml deleted file mode 100644 index 74ed49e2ff..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_request.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- !ruby/object:Net::HTTP::Get -method: GET -request_has_body: false -response_has_body: true -path: /computeMetadata/v1beta1/ -header: - accept: - - ! '*/*' - user-agent: - - Ruby - host: - - metadata -body: -body_stream: -body_data: diff --git a/spec/fixtures/unit/util/gce/gce_metadata_response.yaml b/spec/fixtures/unit/util/gce/gce_metadata_response.yaml deleted file mode 100644 index 8a9c476da4..0000000000 --- a/spec/fixtures/unit/util/gce/gce_metadata_response.yaml +++ /dev/null @@ -1,27 +0,0 @@ ---- !ruby/object:Net::HTTPOK -http_version: '1.1' -code: '200' -message: OK -header: - content-type: - - application/text - etag: - - 5fa0a7665355d01e - date: - - Sat, 17 Aug 2013 17:27:19 GMT - server: - - Metadata Server for VM - content-length: - - '19' - x-xss-protection: - - 1; mode=block - x-frame-options: - - SAMEORIGIN -body: ! 'instance/ - - project/ - -' -read: true -socket: -body_exist: true From 28fe30d40fb6f11ad441693c5100bd9bbd3058c5 Mon Sep 17 00:00:00 2001 From: rwelgan Date: Mon, 19 Aug 2013 17:42:46 -0700 Subject: [PATCH 1410/3753] (#16081) Facter reports bogus arch on AIX Without this patch applied facter reports the hardware model of the system rather than the processor architecture. For example, root@l488pp139_pub[/software/pe-aix/init] > /opt/puppet/bin/facter architecture => IBM,9179-MHB With the patch, the architecture for this system is: l488pp056_pub > facter architecture PowerPC_POWER7 while the hardwaremodel remains: l488pp056_pub > facter hardwaremodel IBM,9179-MHB --- lib/facter/architecture.rb | 15 ++++++++++++++- lib/facter/util/architecture.rb | 19 +++++++++++++++++++ spec/unit/architecture_spec.rb | 8 ++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 lib/facter/util/architecture.rb diff --git a/lib/facter/architecture.rb b/lib/facter/architecture.rb index 6ff63e5fc8..689d74d046 100644 --- a/lib/facter/architecture.rb +++ b/lib/facter/architecture.rb @@ -4,18 +4,31 @@ # Return the CPU hardware architecture. # # Resolution: -# On OpenBSD, Linux and Debian's kfreebsd, use the hardwaremodel fact. +# On non-AIX IBM, OpenBSD, Linux and Debian's kfreebsd, use the hardwaremodel fact. +# On AIX get the arch value from lsattr -El proc0 -a type # Gentoo and Debian call "x86_86" "amd64". # Gentoo also calls "i386" "x86". # # Caveats: # +require 'facter/util/architecture' + Facter.add(:architecture) do setcode do model = Facter.value(:hardwaremodel) case model # most linuxen use "x86_64" + when /IBM*/ + case Facter.value(:operatingsystem) + when "AIX" + arch = Facter::Util::Architecture.lsattr + if (match = arch.match /type\s(\S+)\s/) + match[1] + end + else + model + end when "x86_64" case Facter.value(:operatingsystem) when "Debian", "Gentoo", "GNU/kFreeBSD", "Ubuntu" diff --git a/lib/facter/util/architecture.rb b/lib/facter/util/architecture.rb new file mode 100644 index 0000000000..c1686353a9 --- /dev/null +++ b/lib/facter/util/architecture.rb @@ -0,0 +1,19 @@ +# A module to help test architecture facts on non-AIX test hardware + +module Facter::Util::Architecture + ## + # lsattr is intended to directly delegate to Facter::Util::Resolution.exec in + # an effort to make the processorX facts easier to test. See also the + # {lsdev} method. + def self.lsattr(command="lsattr -El proc0 -a type") + Facter::Util::Resolution.exec(command) + end + + ## + # kernel_fact_value is intended to directly delegate to Facter.value(:kernel) + # to make it easier to stub the kernel fact without affecting the entire + # system. + def self.kernel_fact_value + Facter.value(:kernel) + end +end diff --git a/spec/unit/architecture_spec.rb b/spec/unit/architecture_spec.rb index 1c45e04a53..2f32def67d 100755 --- a/spec/unit/architecture_spec.rb +++ b/spec/unit/architecture_spec.rb @@ -1,6 +1,7 @@ #! /usr/bin/env ruby require 'spec_helper' +require 'facter/util/architecture' describe "Architecture fact" do @@ -52,4 +53,11 @@ end end + it "(#16081) should be PowerPC_POWER7 if os is AIX" do + Facter.fact(:kernel).stubs(:value).returns("AIX") + Facter::Util::Architecture.stubs(:lsattr).returns("type PowerPC_POWER7 Processor type False") + Facter.fact(:hardwaremodel).stubs(:value).returns("IBM,8233-E8B") + Facter.fact(:architecture).value.should == "PowerPC_POWER7" + end + end From b5a4c68f1533e6d0f89251ffbebe39cfb30edf53 Mon Sep 17 00:00:00 2001 From: Luis Fernandez Alvarez Date: Wed, 24 Jul 2013 11:32:23 +0200 Subject: [PATCH 1411/3753] (#7621) - Added operatingsystemrelease support to Windows Without this patch, the fact 'operatingsystemrelease' was set to kernel version fact for all the windows flavors. This patchs brings the option of having the common release name for the windows machines, something that can help in writing specific puppet manifests and other tasks according to the specific windows version. The patch is based on a WMI call to Win32_OperatingSystem. This aproach has some drawbacks but I think the code is simple and easy to mantain and update. Anyway, extracting the windows version is not an easy task for Microsoft as well: http://msdn.microsoft.com/en-us/library/windows/desktop/ms724429%28v=vs.85%29.aspx --- lib/facter/operatingsystemrelease.rb | 28 +++++++++++++ spec/unit/operatingsystemrelease_spec.rb | 51 ++++++++++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index fd8f242d9e..b9fd136368 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -181,6 +181,34 @@ end end +Facter.add(:operatingsystemrelease) do + confine :operatingsystem => :windows + setcode do + require 'facter/util/wmi' + result = nil + Facter::Util::WMI.execquery("SELECT version, producttype FROM Win32_OperatingSystem").each do |os| + result = + case os.version + when /^6\.2/ + os.producttype == 1 ? "8" : "2012" + when /^6\.1/ + os.producttype == 1 ? "7" : "2008 R2" + when /^6\.0/ + os.producttype == 1 ? "Vista" : "2008" + when /^5\.2/ + if os.producttype == 1 + "XP" + else + os.othertypedescription == "R2" ? "2003 R2" : "2003" + end + else + Facter[:kernelrelease].value + end + break + end + result + end +end Facter.add(:operatingsystemrelease) do setcode do Facter[:kernelrelease].value end diff --git a/spec/unit/operatingsystemrelease_spec.rb b/spec/unit/operatingsystemrelease_spec.rb index df493dd5cb..c32f7984c0 100755 --- a/spec/unit/operatingsystemrelease_spec.rb +++ b/spec/unit/operatingsystemrelease_spec.rb @@ -183,6 +183,57 @@ end end + describe "with operatingsystem reported as Windows" do + require 'facter/util/wmi' + before do + Facter.fact(:kernel).stubs(:value).returns("windows") + end + + { + ['5.2.3790', 1] => "XP", + ['6.0.6002', 1] => "Vista", + ['6.0.6002', 2] => "2008", + ['6.0.6002', 3] => "2008", + ['6.1.7601', 1] => "7", + ['6.1.7601', 2] => "2008 R2", + ['6.1.7601', 3] => "2008 R2", + ['6.2.9200', 1] => "8", + ['6.2.9200', 2] => "2012", + ['6.2.9200', 3] => "2012", + }.each do |os_values, expected_output| + it "should be #{expected_output} with Version #{os_values[0]} and ProductType #{os_values[1]}" do + os = mock('os', :version => os_values[0], :producttype => os_values[1]) + Facter::Util::WMI.expects(:execquery).returns([os]) + Facter.fact(:operatingsystemrelease).value.should == expected_output + end + end + + { + ['5.2.3790', 2, ""] => "2003", + ['5.2.3790', 2, "R2"] => "2003 R2", + ['5.2.3790', 3, ""] => "2003", + ['5.2.3790', 3, "R2"] => "2003 R2", + }.each do |os_values, expected_output| + it "should be #{expected_output} with Version #{os_values[0]} and ProductType #{os_values[1]} and OtherTypeDescription #{os_values[2]}" do + os = mock('os', :version => os_values[0], :producttype => os_values[1], :othertypedescription => os_values[2]) + Facter::Util::WMI.expects(:execquery).returns([os]) + Facter.fact(:operatingsystemrelease).value.should == expected_output + end + end + + context "Unknown Windows version" do + before :each do + Facter.fact(:kernelrelease).stubs(:value).returns("X.Y.ZZZZ") + end + + it "should be kernel version value with unknown values " do + os = mock('os', :version => "X.Y.ZZZZ") + Facter::Util::WMI.expects(:execquery).returns([os]) + Facter.fact(:operatingsystemrelease).value.should == "X.Y.ZZZZ" + end + end + end + context "Ubuntu" do let(:issue) { "Ubuntu 10.04.4 LTS \\n \\l\n\n" } before :each do From 08727ddc4a93c36df4ca097a4e9b99dfac9d56fa Mon Sep 17 00:00:00 2001 From: Brendan Murtagh Date: Wed, 11 Sep 2013 18:08:19 -0400 Subject: [PATCH 1412/3753] (#12504) Read correct file for operatingsystemrelase on Ubuntu --- lib/facter/operatingsystemrelease.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index 319896d7ed..7653cbeef1 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -61,8 +61,8 @@ Facter.add(:operatingsystemrelease) do confine :operatingsystem => %w{Ubuntu} setcode do - if release = Facter::Util::FileRead.read('/etc/issue') - if match = release.match(/Ubuntu ((\d+.\d+)(\.(\d+))?)/) + if release = Facter::Util::FileRead.read('/etc/lsb-release') + if match = release.match(/DISTRIB_RELEASE=((\d+.\d+)(\.(\d+))?)/) # Return only the major and minor version numbers. This behavior must # be preserved for compatibility reasons. match[2] From 5dac5e441f744b8a79490464a2a7603af5cf6868 Mon Sep 17 00:00:00 2001 From: Garrett Honeycutt Date: Wed, 11 Sep 2013 20:17:14 -0400 Subject: [PATCH 1413/3753] (#12504) Read non-configurable file for operatingsystemrelease on Ubuntu Without this patch, the operatingsystemrelease for Ubuntu is gathered by parsing /etc/issue. This is a problem as /etc/issue is meant to have configurable content to optionally be displayed when logging in to a system. This patch uses /etc/lsb-release which is meant to have static data describing the release version. --- lib/facter/operatingsystemrelease.rb | 2 +- spec/unit/operatingsystemrelease_spec.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index 7653cbeef1..f0d3512a8a 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -5,7 +5,7 @@ # Resolution: # On RedHat derivatives, returns their '/etc/-release' file. # On Debian, returns '/etc/debian_version'. -# On Ubuntu, parses '/etc/issue' for the release version. +# On Ubuntu, parses '/etc/lsb-release' for the release version. # On Suse, derivatives, parses '/etc/SuSE-release' for a selection of version # information. # On Slackware, parses '/etc/slackware-version'. diff --git a/spec/unit/operatingsystemrelease_spec.rb b/spec/unit/operatingsystemrelease_spec.rb index cda20fe93d..7aab1e0fd4 100755 --- a/spec/unit/operatingsystemrelease_spec.rb +++ b/spec/unit/operatingsystemrelease_spec.rb @@ -154,14 +154,14 @@ end context "Ubuntu" do - let(:issue) { "Ubuntu 10.04.4 LTS \\n \\l\n\n" } + let(:lsbrelease) { 'DISTRIB_ID=Ubuntu\nDISTRIB_RELEASE=10.04\nDISTRIB_CODENAME=lucid\nDISTRIB_DESCRIPTION="Ubuntu 10.04.4 LTS"'} before :each do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter.fact(:operatingsystem).stubs(:value).returns("Ubuntu") end it "Returns only the major and minor version (not patch version)" do - Facter::Util::FileRead.stubs(:read).with("/etc/issue").returns(issue) + Facter::Util::FileRead.stubs(:read).with("/etc/lsb-release").returns(lsbrelease) Facter.fact(:operatingsystemrelease).value.should == "10.04" end end From 6ef6c59eff304a43f66a1b580c07ef6c4a21ebc3 Mon Sep 17 00:00:00 2001 From: john Date: Sat, 21 Sep 2013 21:11:32 -0500 Subject: [PATCH 1414/3753] (#22636) Allow list of external fact directories to be appended to Currently, the only way to override the external facts directories is to write your own Facter::Util::FactLoader and replace the existing loader with this. This commit allows you to simply set or append to the already existing ext_facts_dirs variable which makes it much more extensible for use by other programs like puppet. --- lib/facter/util/config.rb | 28 +++++++++++++++++++--------- spec/unit/util/config_spec.rb | 20 ++++++++++++++++++++ 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/lib/facter/util/config.rb b/lib/facter/util/config.rb index c7d4aa7874..ab7df5516b 100644 --- a/lib/facter/util/config.rb +++ b/lib/facter/util/config.rb @@ -29,23 +29,33 @@ def self.windows_data_dir end end + def self.external_facts_dirs=(dir) + @external_facts_dirs = dir + end + def self.external_facts_dirs + @external_facts_dirs + end + + def self.setup_default_ext_facts_dirs if Facter::Util::Root.root? windows_dir = windows_data_dir if windows_dir.nil? then - ["/etc/facter/facts.d", "/etc/puppetlabs/facter/facts.d"] + @external_facts_dirs = ["/etc/facter/facts.d", "/etc/puppetlabs/facter/facts.d"] else - [File.join(windows_dir, 'PuppetLabs', 'facter', 'facts.d')] + @external_facts_dirs = [File.join(windows_dir, 'PuppetLabs', 'facter', 'facts.d')] end else - [File.expand_path(File.join("~", ".facter", "facts.d"))] + @external_facts_dirs = [File.expand_path(File.join("~", ".facter", "facts.d"))] end end -end -if Facter::Util::Config.is_windows? - require 'win32/dir' - require 'facter/util/windows_root' -else - require 'facter/util/unix_root' + if Facter::Util::Config.is_windows? + require 'win32/dir' + require 'facter/util/windows_root' + else + require 'facter/util/unix_root' + end + + setup_default_ext_facts_dirs end diff --git a/spec/unit/util/config_spec.rb b/spec/unit/util/config_spec.rb index e5c4ff21f2..9dba5ccd47 100755 --- a/spec/unit/util/config_spec.rb +++ b/spec/unit/util/config_spec.rb @@ -41,24 +41,44 @@ it "should return the default value for linux" do Facter::Util::Config.stubs(:is_windows?).returns(false) Facter::Util::Config.stubs(:windows_data_dir).returns(nil) + Facter::Util::Config.setup_default_ext_facts_dirs Facter::Util::Config.external_facts_dirs.should == ["/etc/facter/facts.d", "/etc/puppetlabs/facter/facts.d"] end it "should return the default value for windows 2008" do Facter::Util::Config.stubs(:is_windows?).returns(true) Facter::Util::Config.stubs(:windows_data_dir).returns("C:\\ProgramData") + Facter::Util::Config.setup_default_ext_facts_dirs Facter::Util::Config.external_facts_dirs.should == [File.join("C:\\ProgramData", 'PuppetLabs', 'facter', 'facts.d')] end it "should return the default value for windows 2003R2" do Facter::Util::Config.stubs(:is_windows?).returns(true) Facter::Util::Config.stubs(:windows_data_dir).returns("C:\\Documents") + Facter::Util::Config.setup_default_ext_facts_dirs Facter::Util::Config.external_facts_dirs.should == [File.join("C:\\Documents", 'PuppetLabs', 'facter', 'facts.d')] end it "returns the users home directory when not root" do Facter::Util::Root.stubs(:root?).returns(false) + Facter::Util::Config.setup_default_ext_facts_dirs Facter::Util::Config.external_facts_dirs.should == [File.expand_path(File.join("~", ".facter", "facts.d"))] end + + it "includes additional values when user appends to the list" do + Facter::Util::Config.setup_default_ext_facts_dirs + original_values = Facter::Util::Config.external_facts_dirs.dup + new_value = '/usr/share/newdir' + Facter::Util::Config.external_facts_dirs << new_value + Facter::Util::Config.external_facts_dirs.should == original_values + [new_value] + end + + it "should only output new values when explicitly set" do + Facter::Util::Config.setup_default_ext_facts_dirs + new_value = ['/usr/share/newdir'] + Facter::Util::Config.external_facts_dirs = new_value + Facter::Util::Config.external_facts_dirs.should == new_value + end + end end From 874a5a96ac5fa778c50f1e93424850022b1756cf Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Mon, 23 Sep 2013 10:24:19 -0500 Subject: [PATCH 1415/3753] (#22619) Error when NetConnectionId is missing When someone is using something like failover clustering, an adapter configuration is created and set to IPEnabled but the underlying network adapter is set to NetEnabled=False. Further, the NetConnectionId is empty and causes facter to error. This adds a check to remove items where the adapter itself is NetEnabled=False as well. This also adds a check to not add empty or nil items to the list of interfaces. --- lib/facter/util/ip/windows.rb | 4 ++-- spec/unit/util/ip/windows_spec.rb | 36 +++++++++++++++++++++++++++++-- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/lib/facter/util/ip/windows.rb b/lib/facter/util/ip/windows.rb index 30c4c05797..0bbdfa73ab 100644 --- a/lib/facter/util/ip/windows.rb +++ b/lib/facter/util/ip/windows.rb @@ -43,8 +43,8 @@ def self.interfaces interface_names = [] network_adapter_configurations.map do |nic| - Facter::Util::WMI.execquery("SELECT * FROM Win32_NetworkAdapter WHERE Index = #{nic.Index}").each do |nic| - interface_names << nic.NetConnectionId + Facter::Util::WMI.execquery("SELECT * FROM Win32_NetworkAdapter WHERE Index = #{nic.Index} AND NetEnabled = TRUE").each do |nic| + interface_names << nic.NetConnectionId unless nic.NetConnectionId.nil? or nic.NetConnectionId.empty? end end diff --git a/spec/unit/util/ip/windows_spec.rb b/spec/unit/util/ip/windows_spec.rb index da7cbc53c8..208ad60b1d 100644 --- a/spec/unit/util/ip/windows_spec.rb +++ b/spec/unit/util/ip/windows_spec.rb @@ -34,15 +34,47 @@ let(:name) { 'Local Area Connection' } let(:index) { 7 } let(:nic_config) { mock('nic_config', :Index => index) } - let(:nic) { mock('nic', :NetConnectionId => name ) } + let(:nic) { stub('nic', :NetConnectionId => name ) } + let(:nic_empty_NetConnectionId) { stub('nic', :NetConnectionId => '' ) } + let(:nic_nil_NetConnectionId) { stub('nic', :NetConnectionId => nil ) } + let(:wmi_query) {"SELECT * FROM Win32_NetworkAdapter WHERE Index = #{index} AND NetEnabled = TRUE"} it "should return an array of only connected interfaces" do Facter::Util::WMI.expects(:execquery).with(Facter::Util::IP::Windows::WMI_IP_INFO_QUERY). returns([nic_config]) - Facter::Util::WMI.expects(:execquery).with("SELECT * FROM Win32_NetworkAdapter WHERE Index = #{index}"). + Facter::Util::WMI.expects(:execquery).with(wmi_query). returns([nic]) described_class.interfaces.should == [name] end + + it "should not return an interface with an empty NetConnectionId" do + Facter::Util::WMI.expects(:execquery).with(Facter::Util::IP::Windows::WMI_IP_INFO_QUERY). + returns([nic_config]) + Facter::Util::WMI.expects(:execquery).with(wmi_query). + returns([nic_empty_NetConnectionId]) + + described_class.interfaces.should == [] + end + + it "should not return an interface with a nil NetConnectionId" do + Facter::Util::WMI.expects(:execquery).with(Facter::Util::IP::Windows::WMI_IP_INFO_QUERY). + returns([nic_config]) + Facter::Util::WMI.expects(:execquery).with(wmi_query). + returns([nic_nil_NetConnectionId]) + + described_class.interfaces.should == [] + end + + context "when the adapter configuration is enabled but the underlying adapter is not enabled" do + it "should not return an interface" do + Facter::Util::WMI.expects(:execquery).with(Facter::Util::IP::Windows::WMI_IP_INFO_QUERY). + returns([nic_config]) + Facter::Util::WMI.expects(:execquery).with(wmi_query). + returns([]) + + described_class.interfaces.should == [] + end + end end end From 87f345cc46cd39186b1cb2b36f4c93e7a8674529 Mon Sep 17 00:00:00 2001 From: "Lorenzo J. Cubero" Date: Thu, 18 Jul 2013 17:41:56 +0200 Subject: [PATCH 1416/3753] (#21604) Detect xen virtual fact on windows --- lib/facter/virtual.rb | 7 ++++++- spec/unit/virtual_spec.rb | 6 ++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index c65a4bd8ab..74eec5d72b 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -170,7 +170,12 @@ computersystem.manufacturer =~ /Microsoft/ ? "hyperv" : nil when /VMware/ then "vmware" when /KVM/ then "kvm" - else "physical" + else + if computersystem.manufacturer =~ /Xen/ + "xen" + else + "physical" + end end break end diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index 6215e30e53..88e34240ea 100755 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -304,6 +304,12 @@ Facter::Util::WMI.expects(:execquery).returns([computersystem]) Facter.fact(:virtual).value.should == "vmware" end + + it "resolves as Xen with a manufacturer name like xen" do + computersystem = mock('computersystem', :model => nil, :manufacturer => 'Xen') + Facter::Util::WMI.expects(:execquery).returns([computersystem]) + Facter.fact(:virtual).value.should == "xen" + end end describe "with the virt-what command available (#8210)" do From 1bc12b9eec460c9835ccf3fcb26942702b4e7b49 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 9 Sep 2013 09:44:28 -0700 Subject: [PATCH 1417/3753] (maint) Refactor windows virtual fact for readability --- lib/facter/virtual.rb | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 74eec5d72b..98f8f25cf0 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -163,22 +163,25 @@ require 'facter/util/wmi' result = nil Facter::Util::WMI.execquery("SELECT manufacturer, model FROM Win32_ComputerSystem").each do |computersystem| - result = - case computersystem.model - when /VirtualBox/ then "virtualbox" - when /Virtual Machine/ - computersystem.manufacturer =~ /Microsoft/ ? "hyperv" : nil - when /VMware/ then "vmware" - when /KVM/ then "kvm" - else - if computersystem.manufacturer =~ /Xen/ - "xen" - else - "physical" - end - end + case computersystem.model + when /VirtualBox/ + result = "virtualbox" + when /Virtual Machine/ + result = "hyperv" if computersystem.manufacturer =~ /Microsoft/ + when /VMware/ + result = "vmware" + when /KVM/ + result = "kvm" + end + + if result.nil? and computersystem.manufacturer =~ /Xen/ + result = "xen" + end + break end + result ||= "physical" + result end end From 311699bd59063a7ec29df4d9ad6b2be2d186ff1f Mon Sep 17 00:00:00 2001 From: Joshua Hoblitt Date: Mon, 23 Sep 2013 14:17:47 -0700 Subject: [PATCH 1418/3753] (22651) add fixture access methods for example /proc/cpuinfo files A common set of access methods for fixture example `/proc/cpuinfo` files named `FacterSpec::Cpuinfo#cpuinfo_fixtures` `FacterSpec::Cpuinfo#cpuinfo_fixture_read` and `FacterSpec::Cpuinfo#cpuinfo_fixture_readlines` --- spec/fixtures/cpuinfo/amd64twentyfour | 600 ++++++++++++++++++++++++++ spec/lib/facter_spec/cpuinfo.rb | 15 + spec/unit/processor_spec.rb | 34 +- spec/unit/util/processor_spec.rb | 15 +- 4 files changed, 643 insertions(+), 21 deletions(-) create mode 100644 spec/fixtures/cpuinfo/amd64twentyfour create mode 100644 spec/lib/facter_spec/cpuinfo.rb diff --git a/spec/fixtures/cpuinfo/amd64twentyfour b/spec/fixtures/cpuinfo/amd64twentyfour new file mode 100644 index 0000000000..cdf12c5c6d --- /dev/null +++ b/spec/fixtures/cpuinfo/amd64twentyfour @@ -0,0 +1,600 @@ +processor : 0 +vendor_id : GenuineIntel +cpu family : 6 +model : 44 +model name : Intel(R) Xeon(R) CPU X5675 @ 3.07GHz +stepping : 2 +cpu MHz : 3068.000 +cache size : 12288 KB +physical id : 0 +siblings : 12 +core id : 0 +cpu cores : 6 +apicid : 0 +initial apicid : 0 +fpu : yes +fpu_exception : yes +cpuid level : 11 +wp : yes +flags : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm dca sse4_1 sse4_2 popcnt aes lahf_lm ida arat epb dts tpr_shadow vnmi flexpriority ept vpid +bogomips : 6133.05 +clflush size : 64 +cache_alignment : 64 +address sizes : 40 bits physical, 48 bits virtual +power management: + +processor : 1 +vendor_id : GenuineIntel +cpu family : 6 +model : 44 +model name : Intel(R) Xeon(R) CPU X5675 @ 3.07GHz +stepping : 2 +cpu MHz : 3068.000 +cache size : 12288 KB +physical id : 0 +siblings : 12 +core id : 1 +cpu cores : 6 +apicid : 2 +initial apicid : 2 +fpu : yes +fpu_exception : yes +cpuid level : 11 +wp : yes +flags : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm dca sse4_1 sse4_2 popcnt aes lahf_lm ida arat epb dts tpr_shadow vnmi flexpriority ept vpid +bogomips : 6133.05 +clflush size : 64 +cache_alignment : 64 +address sizes : 40 bits physical, 48 bits virtual +power management: + +processor : 2 +vendor_id : GenuineIntel +cpu family : 6 +model : 44 +model name : Intel(R) Xeon(R) CPU X5675 @ 3.07GHz +stepping : 2 +cpu MHz : 3068.000 +cache size : 12288 KB +physical id : 0 +siblings : 12 +core id : 2 +cpu cores : 6 +apicid : 4 +initial apicid : 4 +fpu : yes +fpu_exception : yes +cpuid level : 11 +wp : yes +flags : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm dca sse4_1 sse4_2 popcnt aes lahf_lm ida arat epb dts tpr_shadow vnmi flexpriority ept vpid +bogomips : 6133.05 +clflush size : 64 +cache_alignment : 64 +address sizes : 40 bits physical, 48 bits virtual +power management: + +processor : 3 +vendor_id : GenuineIntel +cpu family : 6 +model : 44 +model name : Intel(R) Xeon(R) CPU X5675 @ 3.07GHz +stepping : 2 +cpu MHz : 3068.000 +cache size : 12288 KB +physical id : 0 +siblings : 12 +core id : 8 +cpu cores : 6 +apicid : 16 +initial apicid : 16 +fpu : yes +fpu_exception : yes +cpuid level : 11 +wp : yes +flags : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm dca sse4_1 sse4_2 popcnt aes lahf_lm ida arat epb dts tpr_shadow vnmi flexpriority ept vpid +bogomips : 6133.05 +clflush size : 64 +cache_alignment : 64 +address sizes : 40 bits physical, 48 bits virtual +power management: + +processor : 4 +vendor_id : GenuineIntel +cpu family : 6 +model : 44 +model name : Intel(R) Xeon(R) CPU X5675 @ 3.07GHz +stepping : 2 +cpu MHz : 3068.000 +cache size : 12288 KB +physical id : 0 +siblings : 12 +core id : 9 +cpu cores : 6 +apicid : 18 +initial apicid : 18 +fpu : yes +fpu_exception : yes +cpuid level : 11 +wp : yes +flags : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm dca sse4_1 sse4_2 popcnt aes lahf_lm ida arat epb dts tpr_shadow vnmi flexpriority ept vpid +bogomips : 6133.05 +clflush size : 64 +cache_alignment : 64 +address sizes : 40 bits physical, 48 bits virtual +power management: + +processor : 5 +vendor_id : GenuineIntel +cpu family : 6 +model : 44 +model name : Intel(R) Xeon(R) CPU X5675 @ 3.07GHz +stepping : 2 +cpu MHz : 3068.000 +cache size : 12288 KB +physical id : 0 +siblings : 12 +core id : 10 +cpu cores : 6 +apicid : 20 +initial apicid : 20 +fpu : yes +fpu_exception : yes +cpuid level : 11 +wp : yes +flags : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm dca sse4_1 sse4_2 popcnt aes lahf_lm ida arat epb dts tpr_shadow vnmi flexpriority ept vpid +bogomips : 6133.05 +clflush size : 64 +cache_alignment : 64 +address sizes : 40 bits physical, 48 bits virtual +power management: + +processor : 6 +vendor_id : GenuineIntel +cpu family : 6 +model : 44 +model name : Intel(R) Xeon(R) CPU X5675 @ 3.07GHz +stepping : 2 +cpu MHz : 3068.000 +cache size : 12288 KB +physical id : 1 +siblings : 12 +core id : 0 +cpu cores : 6 +apicid : 32 +initial apicid : 32 +fpu : yes +fpu_exception : yes +cpuid level : 11 +wp : yes +flags : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm dca sse4_1 sse4_2 popcnt aes lahf_lm ida arat epb dts tpr_shadow vnmi flexpriority ept vpid +bogomips : 6133.17 +clflush size : 64 +cache_alignment : 64 +address sizes : 40 bits physical, 48 bits virtual +power management: + +processor : 7 +vendor_id : GenuineIntel +cpu family : 6 +model : 44 +model name : Intel(R) Xeon(R) CPU X5675 @ 3.07GHz +stepping : 2 +cpu MHz : 3068.000 +cache size : 12288 KB +physical id : 1 +siblings : 12 +core id : 1 +cpu cores : 6 +apicid : 34 +initial apicid : 34 +fpu : yes +fpu_exception : yes +cpuid level : 11 +wp : yes +flags : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm dca sse4_1 sse4_2 popcnt aes lahf_lm ida arat epb dts tpr_shadow vnmi flexpriority ept vpid +bogomips : 6133.17 +clflush size : 64 +cache_alignment : 64 +address sizes : 40 bits physical, 48 bits virtual +power management: + +processor : 8 +vendor_id : GenuineIntel +cpu family : 6 +model : 44 +model name : Intel(R) Xeon(R) CPU X5675 @ 3.07GHz +stepping : 2 +cpu MHz : 3068.000 +cache size : 12288 KB +physical id : 1 +siblings : 12 +core id : 2 +cpu cores : 6 +apicid : 36 +initial apicid : 36 +fpu : yes +fpu_exception : yes +cpuid level : 11 +wp : yes +flags : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm dca sse4_1 sse4_2 popcnt aes lahf_lm ida arat epb dts tpr_shadow vnmi flexpriority ept vpid +bogomips : 6133.17 +clflush size : 64 +cache_alignment : 64 +address sizes : 40 bits physical, 48 bits virtual +power management: + +processor : 9 +vendor_id : GenuineIntel +cpu family : 6 +model : 44 +model name : Intel(R) Xeon(R) CPU X5675 @ 3.07GHz +stepping : 2 +cpu MHz : 3068.000 +cache size : 12288 KB +physical id : 1 +siblings : 12 +core id : 8 +cpu cores : 6 +apicid : 48 +initial apicid : 48 +fpu : yes +fpu_exception : yes +cpuid level : 11 +wp : yes +flags : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm dca sse4_1 sse4_2 popcnt aes lahf_lm ida arat epb dts tpr_shadow vnmi flexpriority ept vpid +bogomips : 6133.17 +clflush size : 64 +cache_alignment : 64 +address sizes : 40 bits physical, 48 bits virtual +power management: + +processor : 10 +vendor_id : GenuineIntel +cpu family : 6 +model : 44 +model name : Intel(R) Xeon(R) CPU X5675 @ 3.07GHz +stepping : 2 +cpu MHz : 3068.000 +cache size : 12288 KB +physical id : 1 +siblings : 12 +core id : 9 +cpu cores : 6 +apicid : 50 +initial apicid : 50 +fpu : yes +fpu_exception : yes +cpuid level : 11 +wp : yes +flags : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm dca sse4_1 sse4_2 popcnt aes lahf_lm ida arat epb dts tpr_shadow vnmi flexpriority ept vpid +bogomips : 6133.17 +clflush size : 64 +cache_alignment : 64 +address sizes : 40 bits physical, 48 bits virtual +power management: + +processor : 11 +vendor_id : GenuineIntel +cpu family : 6 +model : 44 +model name : Intel(R) Xeon(R) CPU X5675 @ 3.07GHz +stepping : 2 +cpu MHz : 3068.000 +cache size : 12288 KB +physical id : 1 +siblings : 12 +core id : 10 +cpu cores : 6 +apicid : 52 +initial apicid : 52 +fpu : yes +fpu_exception : yes +cpuid level : 11 +wp : yes +flags : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm dca sse4_1 sse4_2 popcnt aes lahf_lm ida arat epb dts tpr_shadow vnmi flexpriority ept vpid +bogomips : 6133.17 +clflush size : 64 +cache_alignment : 64 +address sizes : 40 bits physical, 48 bits virtual +power management: + +processor : 12 +vendor_id : GenuineIntel +cpu family : 6 +model : 44 +model name : Intel(R) Xeon(R) CPU X5675 @ 3.07GHz +stepping : 2 +cpu MHz : 3068.000 +cache size : 12288 KB +physical id : 0 +siblings : 12 +core id : 0 +cpu cores : 6 +apicid : 1 +initial apicid : 1 +fpu : yes +fpu_exception : yes +cpuid level : 11 +wp : yes +flags : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm dca sse4_1 sse4_2 popcnt aes lahf_lm ida arat epb dts tpr_shadow vnmi flexpriority ept vpid +bogomips : 6133.05 +clflush size : 64 +cache_alignment : 64 +address sizes : 40 bits physical, 48 bits virtual +power management: + +processor : 13 +vendor_id : GenuineIntel +cpu family : 6 +model : 44 +model name : Intel(R) Xeon(R) CPU X5675 @ 3.07GHz +stepping : 2 +cpu MHz : 3068.000 +cache size : 12288 KB +physical id : 0 +siblings : 12 +core id : 1 +cpu cores : 6 +apicid : 3 +initial apicid : 3 +fpu : yes +fpu_exception : yes +cpuid level : 11 +wp : yes +flags : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm dca sse4_1 sse4_2 popcnt aes lahf_lm ida arat epb dts tpr_shadow vnmi flexpriority ept vpid +bogomips : 6133.05 +clflush size : 64 +cache_alignment : 64 +address sizes : 40 bits physical, 48 bits virtual +power management: + +processor : 14 +vendor_id : GenuineIntel +cpu family : 6 +model : 44 +model name : Intel(R) Xeon(R) CPU X5675 @ 3.07GHz +stepping : 2 +cpu MHz : 3068.000 +cache size : 12288 KB +physical id : 0 +siblings : 12 +core id : 2 +cpu cores : 6 +apicid : 5 +initial apicid : 5 +fpu : yes +fpu_exception : yes +cpuid level : 11 +wp : yes +flags : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm dca sse4_1 sse4_2 popcnt aes lahf_lm ida arat epb dts tpr_shadow vnmi flexpriority ept vpid +bogomips : 6133.05 +clflush size : 64 +cache_alignment : 64 +address sizes : 40 bits physical, 48 bits virtual +power management: + +processor : 15 +vendor_id : GenuineIntel +cpu family : 6 +model : 44 +model name : Intel(R) Xeon(R) CPU X5675 @ 3.07GHz +stepping : 2 +cpu MHz : 3068.000 +cache size : 12288 KB +physical id : 0 +siblings : 12 +core id : 8 +cpu cores : 6 +apicid : 17 +initial apicid : 17 +fpu : yes +fpu_exception : yes +cpuid level : 11 +wp : yes +flags : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm dca sse4_1 sse4_2 popcnt aes lahf_lm ida arat epb dts tpr_shadow vnmi flexpriority ept vpid +bogomips : 6133.05 +clflush size : 64 +cache_alignment : 64 +address sizes : 40 bits physical, 48 bits virtual +power management: + +processor : 16 +vendor_id : GenuineIntel +cpu family : 6 +model : 44 +model name : Intel(R) Xeon(R) CPU X5675 @ 3.07GHz +stepping : 2 +cpu MHz : 3068.000 +cache size : 12288 KB +physical id : 0 +siblings : 12 +core id : 9 +cpu cores : 6 +apicid : 19 +initial apicid : 19 +fpu : yes +fpu_exception : yes +cpuid level : 11 +wp : yes +flags : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm dca sse4_1 sse4_2 popcnt aes lahf_lm ida arat epb dts tpr_shadow vnmi flexpriority ept vpid +bogomips : 6133.05 +clflush size : 64 +cache_alignment : 64 +address sizes : 40 bits physical, 48 bits virtual +power management: + +processor : 17 +vendor_id : GenuineIntel +cpu family : 6 +model : 44 +model name : Intel(R) Xeon(R) CPU X5675 @ 3.07GHz +stepping : 2 +cpu MHz : 3068.000 +cache size : 12288 KB +physical id : 0 +siblings : 12 +core id : 10 +cpu cores : 6 +apicid : 21 +initial apicid : 21 +fpu : yes +fpu_exception : yes +cpuid level : 11 +wp : yes +flags : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm dca sse4_1 sse4_2 popcnt aes lahf_lm ida arat epb dts tpr_shadow vnmi flexpriority ept vpid +bogomips : 6133.05 +clflush size : 64 +cache_alignment : 64 +address sizes : 40 bits physical, 48 bits virtual +power management: + +processor : 18 +vendor_id : GenuineIntel +cpu family : 6 +model : 44 +model name : Intel(R) Xeon(R) CPU X5675 @ 3.07GHz +stepping : 2 +cpu MHz : 3068.000 +cache size : 12288 KB +physical id : 1 +siblings : 12 +core id : 0 +cpu cores : 6 +apicid : 33 +initial apicid : 33 +fpu : yes +fpu_exception : yes +cpuid level : 11 +wp : yes +flags : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm dca sse4_1 sse4_2 popcnt aes lahf_lm ida arat epb dts tpr_shadow vnmi flexpriority ept vpid +bogomips : 6133.17 +clflush size : 64 +cache_alignment : 64 +address sizes : 40 bits physical, 48 bits virtual +power management: + +processor : 19 +vendor_id : GenuineIntel +cpu family : 6 +model : 44 +model name : Intel(R) Xeon(R) CPU X5675 @ 3.07GHz +stepping : 2 +cpu MHz : 3068.000 +cache size : 12288 KB +physical id : 1 +siblings : 12 +core id : 1 +cpu cores : 6 +apicid : 35 +initial apicid : 35 +fpu : yes +fpu_exception : yes +cpuid level : 11 +wp : yes +flags : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm dca sse4_1 sse4_2 popcnt aes lahf_lm ida arat epb dts tpr_shadow vnmi flexpriority ept vpid +bogomips : 6133.17 +clflush size : 64 +cache_alignment : 64 +address sizes : 40 bits physical, 48 bits virtual +power management: + +processor : 20 +vendor_id : GenuineIntel +cpu family : 6 +model : 44 +model name : Intel(R) Xeon(R) CPU X5675 @ 3.07GHz +stepping : 2 +cpu MHz : 3068.000 +cache size : 12288 KB +physical id : 1 +siblings : 12 +core id : 2 +cpu cores : 6 +apicid : 37 +initial apicid : 37 +fpu : yes +fpu_exception : yes +cpuid level : 11 +wp : yes +flags : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm dca sse4_1 sse4_2 popcnt aes lahf_lm ida arat epb dts tpr_shadow vnmi flexpriority ept vpid +bogomips : 6133.17 +clflush size : 64 +cache_alignment : 64 +address sizes : 40 bits physical, 48 bits virtual +power management: + +processor : 21 +vendor_id : GenuineIntel +cpu family : 6 +model : 44 +model name : Intel(R) Xeon(R) CPU X5675 @ 3.07GHz +stepping : 2 +cpu MHz : 3068.000 +cache size : 12288 KB +physical id : 1 +siblings : 12 +core id : 8 +cpu cores : 6 +apicid : 49 +initial apicid : 49 +fpu : yes +fpu_exception : yes +cpuid level : 11 +wp : yes +flags : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm dca sse4_1 sse4_2 popcnt aes lahf_lm ida arat epb dts tpr_shadow vnmi flexpriority ept vpid +bogomips : 6133.17 +clflush size : 64 +cache_alignment : 64 +address sizes : 40 bits physical, 48 bits virtual +power management: + +processor : 22 +vendor_id : GenuineIntel +cpu family : 6 +model : 44 +model name : Intel(R) Xeon(R) CPU X5675 @ 3.07GHz +stepping : 2 +cpu MHz : 3068.000 +cache size : 12288 KB +physical id : 1 +siblings : 12 +core id : 9 +cpu cores : 6 +apicid : 51 +initial apicid : 51 +fpu : yes +fpu_exception : yes +cpuid level : 11 +wp : yes +flags : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm dca sse4_1 sse4_2 popcnt aes lahf_lm ida arat epb dts tpr_shadow vnmi flexpriority ept vpid +bogomips : 6133.17 +clflush size : 64 +cache_alignment : 64 +address sizes : 40 bits physical, 48 bits virtual +power management: + +processor : 23 +vendor_id : GenuineIntel +cpu family : 6 +model : 44 +model name : Intel(R) Xeon(R) CPU X5675 @ 3.07GHz +stepping : 2 +cpu MHz : 3068.000 +cache size : 12288 KB +physical id : 1 +siblings : 12 +core id : 10 +cpu cores : 6 +apicid : 53 +initial apicid : 53 +fpu : yes +fpu_exception : yes +cpuid level : 11 +wp : yes +flags : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm dca sse4_1 sse4_2 popcnt aes lahf_lm ida arat epb dts tpr_shadow vnmi flexpriority ept vpid +bogomips : 6133.17 +clflush size : 64 +cache_alignment : 64 +address sizes : 40 bits physical, 48 bits virtual +power management: + diff --git a/spec/lib/facter_spec/cpuinfo.rb b/spec/lib/facter_spec/cpuinfo.rb new file mode 100644 index 0000000000..84a91e6559 --- /dev/null +++ b/spec/lib/facter_spec/cpuinfo.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +module FacterSpec::Cpuinfo + def cpuinfo_fixtures(filename) + fixtures('cpuinfo', filename) + end + + def cpuinfo_fixture_read(filename) + File.read(cpuinfo_fixtures(filename)) + end + + def cpuinfo_fixture_readlines(filename) + cpuinfo_fixture_read(filename).split(/\n/) + end +end diff --git a/spec/unit/processor_spec.rb b/spec/unit/processor_spec.rb index 1ed6935cbe..003d68c0c9 100755 --- a/spec/unit/processor_spec.rb +++ b/spec/unit/processor_spec.rb @@ -1,12 +1,9 @@ #! /usr/bin/env ruby require 'facter/util/processor' +require 'facter_spec/cpuinfo' require 'spec_helper' -def cpuinfo_fixture(filename) - File.open(fixtures('cpuinfo', filename)).readlines -end - describe "Processor facts" do describe "on Windows" do before :each do @@ -89,6 +86,8 @@ def load(procs) end describe "on Unixes" do + include FacterSpec::Cpuinfo + before :each do Facter.collection.internal_loader.load(:processor) end @@ -98,7 +97,7 @@ def load(procs) Facter.fact(:operatingsystem).stubs(:value).returns("Linux") Facter.fact(:architecture).stubs(:value).returns("sparc") File.stubs(:exists?).with("/proc/cpuinfo").returns(true) - File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("sparc")) + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture_readlines("sparc")) Facter.fact(:processorcount).value.should == "1" end @@ -107,7 +106,7 @@ def load(procs) Facter.fact(:kernel).stubs(:value).returns("Linux") Facter.fact(:architecture).stubs(:value).returns("ppc64") File.stubs(:exists?).with("/proc/cpuinfo").returns(true) - File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("ppc64")) + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture_readlines("ppc64")) Facter.fact(:processorcount).value.should == "2" end @@ -116,7 +115,7 @@ def load(procs) Facter.fact(:kernel).stubs(:value).returns("Linux") Facter.fact(:architecture).stubs(:value).returns("arm") File.stubs(:exists?).with("/proc/cpuinfo").returns(true) - File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("panda-armel")) + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture_readlines("panda-armel")) Facter.fact(:processorcount).value.should == "2" end @@ -125,7 +124,7 @@ def load(procs) Facter.fact(:kernel).stubs(:value).returns("Linux") Facter.fact(:architecture).stubs(:value).returns("arm") File.stubs(:exists?).with("/proc/cpuinfo").returns(true) - File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("bbg3-armel")) + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture_readlines("bbg3-armel")) Facter.fact(:processorcount).value.should == "1" end @@ -134,7 +133,7 @@ def load(procs) Facter.fact(:kernel).stubs(:value).returns("Linux") Facter.fact(:architecture).stubs(:value).returns("arm") File.stubs(:exists?).with("/proc/cpuinfo").returns(true) - File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("beaglexm-armel")) + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture_readlines("beaglexm-armel")) Facter.fact(:processorcount).value.should == "1" end @@ -143,7 +142,7 @@ def load(procs) Facter.fact(:kernel).stubs(:value).returns("Linux") Facter.fact(:architecture).stubs(:value).returns("amd64") File.stubs(:exists?).with("/proc/cpuinfo").returns(true) - File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("amd64solo")) + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture_readlines("amd64solo")) Facter.fact(:processorcount).value.should == "1" end @@ -152,7 +151,7 @@ def load(procs) Facter.fact(:kernel).stubs(:value).returns("Linux") Facter.fact(:architecture).stubs(:value).returns("amd64") File.stubs(:exists?).with("/proc/cpuinfo").returns(true) - File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("amd64dual")) + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture_readlines("amd64dual")) Facter.fact(:processorcount).value.should == "2" end @@ -161,7 +160,7 @@ def load(procs) Facter.fact(:kernel).stubs(:value).returns("Linux") Facter.fact(:architecture).stubs(:value).returns("amd64") File.stubs(:exists?).with("/proc/cpuinfo").returns(true) - File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("amd64tri")) + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture_readlines("amd64tri")) Facter.fact(:processorcount).value.should == "3" end @@ -170,11 +169,20 @@ def load(procs) Facter.fact(:kernel).stubs(:value).returns("Linux") Facter.fact(:architecture).stubs(:value).returns("amd64") File.stubs(:exists?).with("/proc/cpuinfo").returns(true) - File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("amd64quad")) + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture_readlines("amd64quad")) Facter.fact(:processorcount).value.should == "4" end + it "should be 4 in amd64quad fixture on Linux" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:architecture).stubs(:value).returns("amd64") + File.stubs(:exists?).with("/proc/cpuinfo").returns(true) + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture_readlines("amd64twentyfour")) + + Facter.fact(:processorcount).value.should == "24" + end + it "should be 2 on dual-processor Darwin box" do Facter.fact(:kernel).stubs(:value).returns("Darwin") Facter::Util::Resolution.stubs(:exec).with("sysctl -n hw.ncpu").returns('2') diff --git a/spec/unit/util/processor_spec.rb b/spec/unit/util/processor_spec.rb index 7a53f1d189..5637a7b182 100755 --- a/spec/unit/util/processor_spec.rb +++ b/spec/unit/util/processor_spec.rb @@ -2,13 +2,12 @@ require 'spec_helper' require 'facter/util/processor' - -def cpuinfo_fixture(filename) - File.open(fixtures('cpuinfo', filename)).readlines -end +require 'facter_spec/cpuinfo' describe Facter::Util::Processor do describe "on linux" do + include FacterSpec::Cpuinfo + before :each do Facter.fact(:kernel).stubs(:value).returns("Linux") File.stubs(:exists?).with("/proc/cpuinfo").returns(true) @@ -20,19 +19,19 @@ def cpuinfo_fixture(filename) end it "should get the processor description from the amd64solo fixture" do - File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("amd64solo")) + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture_readlines("amd64solo")) Facter::Util::Processor.enum_cpuinfo[0].should == "Intel(R) Core(TM)2 Duo CPU P8700 @ 2.53GHz" end it "should get the processor descriptions from the amd64dual fixture" do - File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("amd64dual")) + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture_readlines("amd64dual")) Facter::Util::Processor.enum_cpuinfo[0].should == "Intel(R) Core(TM)2 Duo CPU P8700 @ 2.53GHz" Facter::Util::Processor.enum_cpuinfo[1].should == "Intel(R) Core(TM)2 Duo CPU P8700 @ 2.53GHz" end it "should get the processor descriptions from the amd64tri fixture" do - File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("amd64tri")) + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture_readlines("amd64tri")) Facter::Util::Processor.enum_cpuinfo[0].should == "Intel(R) Core(TM)2 Duo CPU P8700 @ 2.53GHz" Facter::Util::Processor.enum_cpuinfo[1].should == "Intel(R) Core(TM)2 Duo CPU P8700 @ 2.53GHz" @@ -40,7 +39,7 @@ def cpuinfo_fixture(filename) end it "should get the processor descriptions from the amd64quad fixture" do - File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture("amd64quad")) + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture_readlines("amd64quad")) Facter::Util::Processor.enum_cpuinfo[0].should == "Quad-Core AMD Opteron(tm) Processor 2374 HE" Facter::Util::Processor.enum_cpuinfo[1].should == "Quad-Core AMD Opteron(tm) Processor 2374 HE" From fd7203ee0d6e68592555fe83b5def7b4f878f890 Mon Sep 17 00:00:00 2001 From: Konstantin Orekhov Date: Mon, 6 May 2013 18:28:47 -0700 Subject: [PATCH 1419/3753] (#18215) Updated processor_spec.rb for Solaris changes Updated processor_spec.rb for Solaris changes to support fixes for issue #18215. --- spec/unit/processor_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/unit/processor_spec.rb b/spec/unit/processor_spec.rb index 003d68c0c9..8fb3352c5f 100755 --- a/spec/unit/processor_spec.rb +++ b/spec/unit/processor_spec.rb @@ -275,7 +275,7 @@ def load(procs) let(:kstat_sparc) { @fixture_kstat_sparc } let(:kstat_x86_64) { @fixture_kstat_x86_64 } - %w{ 5.8 5.9 5.10 5.11 }.each do |release| + %w{ 5.5.1 5.6 5.7 }.each do |release| %w{ sparc x86_64 }.each do |arch| it "uses kstat on release #{release} (#{arch})" do Facter.fact(:kernel).stubs(:value).returns(:sunos) @@ -287,7 +287,7 @@ def load(procs) end end - %w{ 5.5.1 5.6 5.7 }.each do |release| + %w{ 5.8 5.9 5.10 5.11 }.each do |release| it "uses psrinfo on release #{release}" do Facter.fact(:kernel).stubs(:value).returns(:sunos) Facter.stubs(:value).with(:kernelrelease).returns(release) From 502fb5ea6e7ece79dececfa9308fb82ab5aeddcf Mon Sep 17 00:00:00 2001 From: Konstantin Orekhov Date: Wed, 8 May 2013 14:03:19 -0700 Subject: [PATCH 1420/3753] processorcount spec wrongly used physicalprocessorcount fact processorcount spec wrongly used physicalprocessorcount fact --- spec/unit/processor_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/unit/processor_spec.rb b/spec/unit/processor_spec.rb index 8fb3352c5f..395476ac08 100755 --- a/spec/unit/processor_spec.rb +++ b/spec/unit/processor_spec.rb @@ -293,7 +293,7 @@ def load(procs) Facter.stubs(:value).with(:kernelrelease).returns(release) Facter::Util::Resolution.expects(:exec).with("/usr/sbin/psrinfo").returns(psrinfo) - Facter.fact(:physicalprocessorcount).value.should == "2" + Facter.fact(:processorcount).value.should == "2" end end end From 0fba62bd216e8ac5321c65d6ff0d570bd5e62084 Mon Sep 17 00:00:00 2001 From: Konstantin Orekhov Date: Wed, 8 May 2013 16:21:24 -0700 Subject: [PATCH 1421/3753] adding psrinfo from Solaris with 24 CPUs and using it in processor spec adding psrinfo from Solaris with 24 CPUs and using it in processor spec. --- spec/fixtures/processorcount/solaris-psrinfo | 24 ++++++++++++++++++++ spec/unit/processor_spec.rb | 22 ++++++++---------- 2 files changed, 33 insertions(+), 13 deletions(-) create mode 100644 spec/fixtures/processorcount/solaris-psrinfo diff --git a/spec/fixtures/processorcount/solaris-psrinfo b/spec/fixtures/processorcount/solaris-psrinfo new file mode 100644 index 0000000000..a03ce95364 --- /dev/null +++ b/spec/fixtures/processorcount/solaris-psrinfo @@ -0,0 +1,24 @@ +0 on-line since 10/01/2012 21:05:55 +1 on-line since 10/01/2012 21:06:00 +2 on-line since 10/01/2012 21:06:00 +3 on-line since 10/01/2012 21:06:00 +4 on-line since 10/01/2012 21:06:00 +5 on-line since 10/01/2012 21:06:00 +6 on-line since 10/01/2012 21:06:00 +7 on-line since 10/01/2012 21:06:00 +8 on-line since 10/01/2012 21:06:00 +9 on-line since 10/01/2012 21:06:00 +10 on-line since 10/01/2012 21:06:00 +11 on-line since 10/01/2012 21:06:00 +12 on-line since 10/01/2012 21:06:01 +13 on-line since 10/01/2012 21:06:01 +14 on-line since 10/01/2012 21:06:01 +15 on-line since 10/01/2012 21:06:01 +16 on-line since 10/01/2012 21:06:01 +17 on-line since 10/01/2012 21:06:01 +18 on-line since 10/01/2012 21:06:01 +19 on-line since 10/01/2012 21:06:01 +20 on-line since 10/01/2012 21:06:01 +21 on-line since 10/01/2012 21:06:01 +22 on-line since 10/01/2012 21:06:01 +23 on-line since 10/01/2012 21:06:01 diff --git a/spec/unit/processor_spec.rb b/spec/unit/processor_spec.rb index 395476ac08..f5d10acf08 100755 --- a/spec/unit/processor_spec.rb +++ b/spec/unit/processor_spec.rb @@ -73,15 +73,15 @@ def load(procs) end it "should detect the correct processor count on x86_64" do - fixture_data = File.read(fixtures('processorcount','solaris-x86_64-kstat-cpu-info')) - Facter::Util::Resolution.expects(:exec).with("/usr/bin/kstat cpu_info").returns(fixture_data) - Facter.fact(:processorcount).value.should == 8 + fixture_data = File.read(fixtures('processorcount','solaris-psrinfo')) + Facter::Util::Resolution.expects(:exec).with("/usr/sbin/psrinfo").returns(fixture_data) + Facter.fact(:processorcount).value.should == 24 end it "should detect the correct processor count on sparc" do - fixture_data = File.read(fixtures('processorcount','solaris-sparc-kstat-cpu-info')) - Facter::Util::Resolution.expects(:exec).with("/usr/bin/kstat cpu_info").returns(fixture_data) - Facter.fact(:processorcount).value.should == 8 + fixture_data = File.read(fixtures('processorcount','solaris-psrinfo')) + Facter::Util::Resolution.expects(:exec).with("/usr/sbin/psrinfo").returns(fixture_data) + Facter.fact(:processorcount).value.should == 24 end end @@ -267,11 +267,6 @@ def load(procs) @fixture_kstat_x86_64 = File.read(fixtures('processorcount','solaris-x86_64-kstat-cpu-info')) end - let(:psrinfo) do - "0 on-line since 10/16/2012 14:06:12\n" + - "1 on-line since 10/16/2012 14:06:14\n" - end - let(:kstat_sparc) { @fixture_kstat_sparc } let(:kstat_x86_64) { @fixture_kstat_x86_64 } @@ -292,8 +287,9 @@ def load(procs) Facter.fact(:kernel).stubs(:value).returns(:sunos) Facter.stubs(:value).with(:kernelrelease).returns(release) - Facter::Util::Resolution.expects(:exec).with("/usr/sbin/psrinfo").returns(psrinfo) - Facter.fact(:processorcount).value.should == "2" + fixture_data = File.read(fixtures('processorcount','solaris-psrinfo')) + Facter::Util::Resolution.expects(:exec).with("/usr/sbin/psrinfo").returns(fixture_data) + Facter.fact(:processorcount).value.should == "24" end end end From 59c65cf13934d2ef37058350b8b2422b53e63543 Mon Sep 17 00:00:00 2001 From: Konstantin Orekhov Date: Wed, 8 May 2013 19:53:49 -0700 Subject: [PATCH 1422/3753] removing double quotes in a spec --- spec/unit/processor_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/unit/processor_spec.rb b/spec/unit/processor_spec.rb index f5d10acf08..6d434ea05c 100755 --- a/spec/unit/processor_spec.rb +++ b/spec/unit/processor_spec.rb @@ -289,7 +289,7 @@ def load(procs) fixture_data = File.read(fixtures('processorcount','solaris-psrinfo')) Facter::Util::Resolution.expects(:exec).with("/usr/sbin/psrinfo").returns(fixture_data) - Facter.fact(:processorcount).value.should == "24" + Facter.fact(:processorcount).value.should == 24 end end end From 54ba32e1cb59741b34badc7c80b688f67b3af50c Mon Sep 17 00:00:00 2001 From: Konstantin Orekhov Date: Fri, 10 May 2013 16:36:51 -0700 Subject: [PATCH 1423/3753] removed duplicate tests for Solaris --- spec/unit/processor_spec.rb | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/spec/unit/processor_spec.rb b/spec/unit/processor_spec.rb index 6d434ea05c..80fb70c879 100755 --- a/spec/unit/processor_spec.rb +++ b/spec/unit/processor_spec.rb @@ -65,26 +65,6 @@ def load(procs) end end - describe "on Solaris" do - before :each do - Facter.collection.internal_loader.load(:processor) - Facter.fact(:kernel).stubs(:value).returns(:sunos) - Facter.stubs(:value).with(:kernelrelease).returns("5.10") - end - - it "should detect the correct processor count on x86_64" do - fixture_data = File.read(fixtures('processorcount','solaris-psrinfo')) - Facter::Util::Resolution.expects(:exec).with("/usr/sbin/psrinfo").returns(fixture_data) - Facter.fact(:processorcount).value.should == 24 - end - - it "should detect the correct processor count on sparc" do - fixture_data = File.read(fixtures('processorcount','solaris-psrinfo')) - Facter::Util::Resolution.expects(:exec).with("/usr/sbin/psrinfo").returns(fixture_data) - Facter.fact(:processorcount).value.should == 24 - end - end - describe "on Unixes" do include FacterSpec::Cpuinfo From 26a089b957d043c6c7fbeed6522a150feb3e81ac Mon Sep 17 00:00:00 2001 From: Alex Harvey Date: Sat, 17 Nov 2012 14:41:41 +1100 Subject: [PATCH 1424/3753] (#17894) Add fix to handle processorX facts on HP Superdomes. Without this patch applied, the processorX facts fail on the Superdome2-16s model servers. The patch includes machinfo fixtures for the Superdome2-16s as well as the Superdome SD32B as provided by Luke M. --- lib/facter/util/processor.rb | 2 + .../hpux/machinfo/superdome-server-SD32B | 53 +++++++++++++++++++ spec/fixtures/hpux/machinfo/superdome2-16s | 31 +++++++++++ spec/unit/processor_spec.rb | 2 + 4 files changed, 88 insertions(+) create mode 100644 spec/fixtures/hpux/machinfo/superdome-server-SD32B create mode 100644 spec/fixtures/hpux/machinfo/superdome2-16s diff --git a/lib/facter/util/processor.rb b/lib/facter/util/processor.rb index c619d38a74..335dcf6fa5 100644 --- a/lib/facter/util/processor.rb +++ b/lib/facter/util/processor.rb @@ -87,6 +87,8 @@ def self.hpux_processor_list elsif line.match(/\d+\s+((?:PA-RISC|Intel).*processors.*)/) then cpu = $1.to_s cpu.sub!(/processors/, "processor") + elsif line.match(/\s+(Intel.*Processor.*)/) then + cpu = $1.to_s end end end diff --git a/spec/fixtures/hpux/machinfo/superdome-server-SD32B b/spec/fixtures/hpux/machinfo/superdome-server-SD32B new file mode 100644 index 0000000000..14aefbeaad --- /dev/null +++ b/spec/fixtures/hpux/machinfo/superdome-server-SD32B @@ -0,0 +1,53 @@ +CPU info: + Number of CPUs = 8 + Number of enabled CPUs = 2 + Number of enabled sockets = 2 + Cores per socket = 2 + Clock speed = 1598 MHz + Bus speed = 533 MT/s + CPUID registers + vendor information = "GenuineIntel" + processor serial number = 0x0000000000000000 + processor version info = 0x0000000020000704 + architecture revision: 0 + processor family: 32 Intel(R) Itanium 2 9000 series + processor model: 1 Intel(R) Itanium 2 9000 series + Bus features + implemented = 0xbdf0000020000000 + selected = 0x0020000000000000 + Exclusive Bus Cache Line Replacement Enabled + processor revision: 7 Stepping C2 + largest CPUID reg: 4 + processor capabilities = 0x0000000000000005 + implements long branch: 1 + implements 16-byte atomic operations: 1 + +Cache info (per core): + L1 Instruction: size = 16 KB, associativity = 4 + L1 Data: size = 16 KB, associativity = 4 + L2 Instruction: size = 1024 KB, associativity = 8 + L2 Data: size = 256 KB, associativity = 8 + L3 Unified: size = 12288 KB, associativity = 12 + +Memory = 32700 MB (31.933594 GB) + +Firmware info: + Firmware revision = 9.66 + FP SWA driver revision: 1.18 + IPMI is supported on this system. + ERROR: Unable to obtain manageability firmware revision info. + +Platform info: + model string = "ia64 hp superdome server SD32B" + machine id number = STRING_WITH_DASHES + machine serial number = STRING + +OS info: + sysname = HP-UX + nodename = HOSTNAME + release = B.11.23 + version = U (unlimited-user license) + machine = ia64 + idnumber = NUMBER + vmunix _release_version: +@(#) $Revision: vmunix: B11.23_LR FLAVOR=perf Fri Aug 29 22:35:38 PDT 2003 $ diff --git a/spec/fixtures/hpux/machinfo/superdome2-16s b/spec/fixtures/hpux/machinfo/superdome2-16s new file mode 100644 index 0000000000..5652d19139 --- /dev/null +++ b/spec/fixtures/hpux/machinfo/superdome2-16s @@ -0,0 +1,31 @@ +CPU info: + Intel(R) Itanium(R) Processor 9340 (1.6 GHz, 15 MB) + 4 cores, 8 logical processors per socket + 4.79 GT/s QPI, CPU version E0 + Active processor count: + 2 sockets + 6 cores (3 per socket) + 12 logical processors (6 per socket) + LCPU attribute is enabled + +Memory: 14332 MB (14 GB) + +Firmware info: + Firmware revision: 004.044.000 + FP SWA driver revision: 1.18 + IPMI is supported on this system. + BMC firmware revision: 2.53 + +Platform info: + Model: "ia64 hp Superdome2 16s" + Machine ID number: STRING_WITH_DASHES + Machine serial number: STRING + +OS info: + Nodename: HOSTNAME + Release: HP-UX B.11.31 + Version: U (unlimited-user license) + Machine: ia64 + ID Number: NUMBER + vmunix _release_version: +@(#) $Revision: vmunix: B.11.31_LR FLAVOR=perf diff --git a/spec/unit/processor_spec.rb b/spec/unit/processor_spec.rb index 80fb70c879..2585ffb9be 100755 --- a/spec/unit/processor_spec.rb +++ b/spec/unit/processor_spec.rb @@ -317,6 +317,8 @@ def self.machinfo_examples examples << [File.read(fixtures('hpux','machinfo','ia64-rx6600')), "Intel(R) Itanium 2 9100 series processor (1.59 GHz, 18 MB)"] examples << [File.read(fixtures('hpux','machinfo','ia64-rx8640')), "Intel(R) Itanium 2 9100 series"] examples << [File.read(fixtures('hpux','machinfo','hppa-rp4440')), "PA-RISC 8800 processor (1000 MHz, 64 MB)"] + examples << [File.read(fixtures('hpux','machinfo','superdome-server-SD32B')), "Intel(R) Itanium 2 9000 series"] + examples << [File.read(fixtures('hpux','machinfo','superdome2-16s')), "Intel(R) Itanium(R) Processor 9340 (1.6 GHz, 15 MB)"] examples end From 3849295841085e37298a53acc6565e1c07cc1ddc Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Wed, 22 May 2013 10:52:52 -0700 Subject: [PATCH 1425/3753] (maint) refactor processor specs, test sysfs fallback Conflicts: spec/unit/processor_spec.rb --- spec/unit/processor_spec.rb | 204 +++++++++++++++++++----------------- 1 file changed, 109 insertions(+), 95 deletions(-) diff --git a/spec/unit/processor_spec.rb b/spec/unit/processor_spec.rb index 2585ffb9be..200afbf42f 100755 --- a/spec/unit/processor_spec.rb +++ b/spec/unit/processor_spec.rb @@ -1,8 +1,8 @@ #! /usr/bin/env ruby require 'facter/util/processor' -require 'facter_spec/cpuinfo' require 'spec_helper' +require 'facter_spec/cpuinfo' describe "Processor facts" do describe "on Windows" do @@ -65,102 +65,110 @@ def load(procs) end end - describe "on Unixes" do + describe "on Linux" do include FacterSpec::Cpuinfo - before :each do - Facter.collection.internal_loader.load(:processor) + shared_context 'Linux processor stubs' do + before :each do + Facter.collection.internal_loader.load(:processor) + + Facter.fact(:kernel).stubs(:value).returns 'Linux' + Facter.fact(:operatingsystem).stubs(:value).returns 'Linux' + File.stubs(:exists?).with("/proc/cpuinfo").returns(true) + end end - it "should be 1 in SPARC fixture" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:operatingsystem).stubs(:value).returns("Linux") - Facter.fact(:architecture).stubs(:value).returns("sparc") - File.stubs(:exists?).with("/proc/cpuinfo").returns(true) - File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture_readlines("sparc")) + shared_examples_for 'a /proc/cpuinfo based processor fact' do |processor_fact| + include_context 'Linux processor stubs' - Facter.fact(:processorcount).value.should == "1" - end + it "should be 1 in SPARC fixture" do + Facter.fact(:architecture).stubs(:value).returns("sparc") + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture_readlines("sparc")) - it "should be 2 in ppc64 fixture on Linux" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:architecture).stubs(:value).returns("ppc64") - File.stubs(:exists?).with("/proc/cpuinfo").returns(true) - File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture_readlines("ppc64")) + Facter.fact(processor_fact).value.should == "1" + end - Facter.fact(:processorcount).value.should == "2" - end + it "should be 2 in ppc64 fixture on Linux" do + Facter.fact(:architecture).stubs(:value).returns("ppc64") + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture_readlines("ppc64")) - it "should be 2 in panda-armel fixture on Linux" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:architecture).stubs(:value).returns("arm") - File.stubs(:exists?).with("/proc/cpuinfo").returns(true) - File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture_readlines("panda-armel")) + Facter.fact(processor_fact).value.should == "2" + end - Facter.fact(:processorcount).value.should == "2" - end + it "should be 2 in panda-armel fixture on Linux" do + Facter.fact(:architecture).stubs(:value).returns("arm") + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture_readlines("panda-armel")) - it "should be 1 in bbg3-armel fixture on Linux" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:architecture).stubs(:value).returns("arm") - File.stubs(:exists?).with("/proc/cpuinfo").returns(true) - File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture_readlines("bbg3-armel")) + Facter.fact(processor_fact).value.should == "2" + end - Facter.fact(:processorcount).value.should == "1" - end + it "should be 1 in bbg3-armel fixture on Linux" do + Facter.fact(:architecture).stubs(:value).returns("arm") + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture_readlines("bbg3-armel")) - it "should be 1 in beaglexm-armel fixture on Linux" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:architecture).stubs(:value).returns("arm") - File.stubs(:exists?).with("/proc/cpuinfo").returns(true) - File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture_readlines("beaglexm-armel")) + Facter.fact(processor_fact).value.should == "1" + end - Facter.fact(:processorcount).value.should == "1" - end + it "should be 1 in beaglexm-armel fixture on Linux" do + Facter.fact(:architecture).stubs(:value).returns("arm") + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture_readlines("beaglexm-armel")) - it "should be 1 in amd64solo fixture on Linux" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:architecture).stubs(:value).returns("amd64") - File.stubs(:exists?).with("/proc/cpuinfo").returns(true) - File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture_readlines("amd64solo")) + Facter.fact(processor_fact).value.should == "1" + end - Facter.fact(:processorcount).value.should == "1" - end + it "should be 1 in amd64solo fixture on Linux" do + Facter.fact(:architecture).stubs(:value).returns("amd64") + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture_readlines("amd64solo")) - it "should be 2 in amd64dual fixture on Linux" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:architecture).stubs(:value).returns("amd64") - File.stubs(:exists?).with("/proc/cpuinfo").returns(true) - File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture_readlines("amd64dual")) + Facter.fact(processor_fact).value.should == "1" + end - Facter.fact(:processorcount).value.should == "2" - end + it "should be 2 in amd64dual fixture on Linux" do + Facter.fact(:architecture).stubs(:value).returns("amd64") + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture_readlines("amd64dual")) - it "should be 3 in amd64tri fixture on Linux" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:architecture).stubs(:value).returns("amd64") - File.stubs(:exists?).with("/proc/cpuinfo").returns(true) - File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture_readlines("amd64tri")) + Facter.fact(processor_fact).value.should == "2" + end + + it "should be 3 in amd64tri fixture on Linux" do + Facter.fact(:architecture).stubs(:value).returns("amd64") + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture_readlines("amd64tri")) + + Facter.fact(processor_fact).value.should == "3" + end - Facter.fact(:processorcount).value.should == "3" + it "should be 4 in amd64quad fixture on Linux" do + Facter.fact(:architecture).stubs(:value).returns("amd64") + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture_readlines("amd64quad")) + + Facter.fact(processor_fact).value.should == "4" + end end - it "should be 4 in amd64quad fixture on Linux" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:architecture).stubs(:value).returns("amd64") - File.stubs(:exists?).with("/proc/cpuinfo").returns(true) - File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture_readlines("amd64quad")) + it_behaves_like 'a /proc/cpuinfo based processor fact', :processorcount - Facter.fact(:processorcount).value.should == "4" + describe 'when /proc/cpuinfo returns 0 processors (#2945)' do + include_context 'Linux processor stubs' + before do + File.stubs(:readlines).with("/proc/cpuinfo").returns([]) + end + + it 'enumerates sysfs for the processorcount' do + File.stubs(:exists?).with('/sys/devices/system/cpu').returns(true) + Dir.stubs(:glob).with("/sys/devices/system/cpu/cpu[0-9]*").returns(%w{ + /sys/devices/system/cpu/cpu0 + /sys/devices/system/cpu/cpu1 + }) + + Facter.fact(:processorcount).value.should == '2' + end end + end - it "should be 4 in amd64quad fixture on Linux" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter.fact(:architecture).stubs(:value).returns("amd64") - File.stubs(:exists?).with("/proc/cpuinfo").returns(true) - File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture_readlines("amd64twentyfour")) - Facter.fact(:processorcount).value.should == "24" + describe "on Unixes" do + before :each do + Facter.collection.internal_loader.load(:processor) end it "should be 2 on dual-processor Darwin box" do @@ -240,37 +248,43 @@ def load(procs) Facter.fact(:processorcount).value.should == "16" end + end - describe "on solaris" do - before :all do - @fixture_kstat_sparc = File.read(fixtures('processorcount','solaris-sparc-kstat-cpu-info')) - @fixture_kstat_x86_64 = File.read(fixtures('processorcount','solaris-x86_64-kstat-cpu-info')) - end + describe "on solaris" do + before :each do + Facter::Util::Processor.stubs(:kernel_fact_value).returns :sunos + Facter.fact(:kernel).stubs(:value).returns(:sunos) + Facter.collection.internal_loader.load(:processor) + end - let(:kstat_sparc) { @fixture_kstat_sparc } - let(:kstat_x86_64) { @fixture_kstat_x86_64 } + before :all do + @fixture_kstat_sparc = File.read(fixtures('processorcount','solaris-sparc-kstat-cpu-info')) + @fixture_kstat_x86_64 = File.read(fixtures('processorcount','solaris-x86_64-kstat-cpu-info')) + end - %w{ 5.5.1 5.6 5.7 }.each do |release| - %w{ sparc x86_64 }.each do |arch| - it "uses kstat on release #{release} (#{arch})" do - Facter.fact(:kernel).stubs(:value).returns(:sunos) - Facter.stubs(:value).with(:kernelrelease).returns(release) + let(:kstat_sparc) { @fixture_kstat_sparc } + let(:kstat_x86_64) { @fixture_kstat_x86_64 } - Facter::Util::Resolution.expects(:exec).with("/usr/bin/kstat cpu_info").returns(self.send("kstat_#{arch}".intern)) - Facter.fact(:processorcount).value.should == 8 - end + %w{ 5.8 5.9 5.10 5.11 }.each do |release| + %w{ sparc x86_64 }.each do |arch| + it "uses kstat on release #{release} (#{arch})" do + Facter.stubs(:value).with(:kernelrelease).returns(release) + + Facter::Util::Resolution.expects(:exec).with("/usr/sbin/psrinfo").never + Facter::Util::Resolution.expects(:exec).with("/usr/bin/kstat cpu_info").returns(self.send("kstat_#{arch}".intern)) + Facter.fact(:processorcount).value.should == 8 end end + end - %w{ 5.8 5.9 5.10 5.11 }.each do |release| - it "uses psrinfo on release #{release}" do - Facter.fact(:kernel).stubs(:value).returns(:sunos) - Facter.stubs(:value).with(:kernelrelease).returns(release) + %w{ 5.5.1 5.6 5.7 }.each do |release| + it "uses psrinfo on release #{release}" do + Facter.stubs(:value).with(:kernelrelease).returns(release) - fixture_data = File.read(fixtures('processorcount','solaris-psrinfo')) - Facter::Util::Resolution.expects(:exec).with("/usr/sbin/psrinfo").returns(fixture_data) - Facter.fact(:processorcount).value.should == 24 - end + fixture_data = File.read(fixtures('processorcount','solaris-psrinfo')) + Facter::Util::Resolution.expects(:exec).with("/usr/bin/kstat cpu_info").never + Facter::Util::Resolution.expects(:exec).with("/usr/sbin/psrinfo").returns(fixture_data) + Facter.fact(:processorcount).value.should == 24 end end end From dec6f7d876bdd33c0eb317d2942cf9f371561045 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Wed, 22 May 2013 11:19:40 -0700 Subject: [PATCH 1426/3753] (maint) Simplify processor_spec sysfs stubbing Conflicts: spec/unit/processor_spec.rb --- spec/unit/processor_spec.rb | 69 ++++++++++--------------------------- 1 file changed, 19 insertions(+), 50 deletions(-) diff --git a/spec/unit/processor_spec.rb b/spec/unit/processor_spec.rb index 200afbf42f..6dad114061 100755 --- a/spec/unit/processor_spec.rb +++ b/spec/unit/processor_spec.rb @@ -147,20 +147,32 @@ def load(procs) it_behaves_like 'a /proc/cpuinfo based processor fact', :processorcount + def sysfs_cpu_stubs(count) + (0...count).map { |index| "/sys/devices/system/cpu/cpu#{index}" } + end + describe 'when /proc/cpuinfo returns 0 processors (#2945)' do include_context 'Linux processor stubs' + before do File.stubs(:readlines).with("/proc/cpuinfo").returns([]) + File.stubs(:exists?).with("/sys/devices/system/cpu").returns(true) + end + + it "should be 2 via sysfs when cpu0 and cpu1 are present" do + Dir.stubs(:glob).with("/sys/devices/system/cpu/cpu[0-9]*").returns( + sysfs_cpu_stubs(2) + ) + + Facter.fact(:processorcount).value.should == "2" end - it 'enumerates sysfs for the processorcount' do - File.stubs(:exists?).with('/sys/devices/system/cpu').returns(true) - Dir.stubs(:glob).with("/sys/devices/system/cpu/cpu[0-9]*").returns(%w{ - /sys/devices/system/cpu/cpu0 - /sys/devices/system/cpu/cpu1 - }) + it "should be 16 via sysfs when cpu0 through cpu15 are present" do + Dir.stubs(:glob).with("/sys/devices/system/cpu/cpu[0-9]*").returns( + sysfs_cpu_stubs(16) + ) - Facter.fact(:processorcount).value.should == '2' + Facter.fact(:processorcount).value.should == "16" end end end @@ -199,55 +211,12 @@ def load(procs) Facter.fact(:processor).value.should == "SomeVendor CPU 3GHz" end - it "should be 2 on dual-processor DragonFly box" do Facter.fact(:kernel).stubs(:value).returns("DragonFly") Facter::Util::Resolution.stubs(:exec).with("sysctl -n hw.ncpu").returns('2') Facter.fact(:processorcount).value.should == "2" end - - it "should be 2 via sysfs when cpu0 and cpu1 are present" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - File.stubs(:exists?).with('/sys/devices/system/cpu').returns(true) - ## sysfs method is only used if cpuinfo method returned no processors - File.stubs(:exists?).with("/proc/cpuinfo").returns(true) - File.stubs(:readlines).with("/proc/cpuinfo").returns([]) - Dir.stubs(:glob).with("/sys/devices/system/cpu/cpu[0-9]*").returns(%w{ - /sys/devices/system/cpu/cpu0 - /sys/devices/system/cpu/cpu1 - }) - - Facter.fact(:processorcount).value.should == "2" - end - - it "should be 16 via sysfs when cpu0 through cpu15 are present" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - File.stubs(:exists?).with('/sys/devices/system/cpu').returns(true) - ## sysfs method is only used if cpuinfo method returned no processors - File.stubs(:exists?).with("/proc/cpuinfo").returns(true) - File.stubs(:readlines).with("/proc/cpuinfo").returns([]) - Dir.stubs(:glob).with("/sys/devices/system/cpu/cpu[0-9]*").returns(%w{ - /sys/devices/system/cpu/cpu0 - /sys/devices/system/cpu/cpu1 - /sys/devices/system/cpu/cpu2 - /sys/devices/system/cpu/cpu3 - /sys/devices/system/cpu/cpu4 - /sys/devices/system/cpu/cpu5 - /sys/devices/system/cpu/cpu6 - /sys/devices/system/cpu/cpu7 - /sys/devices/system/cpu/cpu8 - /sys/devices/system/cpu/cpu9 - /sys/devices/system/cpu/cpu10 - /sys/devices/system/cpu/cpu11 - /sys/devices/system/cpu/cpu12 - /sys/devices/system/cpu/cpu13 - /sys/devices/system/cpu/cpu14 - /sys/devices/system/cpu/cpu15 - }) - - Facter.fact(:processorcount).value.should == "16" - end end describe "on solaris" do From 668df52a3570370a590dcb2c2f0cb487156b8a71 Mon Sep 17 00:00:00 2001 From: Rob Braden Date: Wed, 25 Sep 2013 11:27:32 -0700 Subject: [PATCH 1427/3753] Pass --man to install.rb to get man pages in Debian lucid and older --- ext/debian/rules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/debian/rules b/ext/debian/rules index 6e2a6d5855..7dd2b79e82 100755 --- a/ext/debian/rules +++ b/ext/debian/rules @@ -7,4 +7,4 @@ LIBDIR=$(shell /usr/bin/ruby -rrbconfig -e 'puts RbConfig::CONFIG["vendordir"]') BINDIR=$(shell /usr/bin/ruby -rrbconfig -e 'puts RbConfig::CONFIG["bindir"]') binary-install/facter:: - /usr/bin/ruby install.rb --sitelibdir=$(LIBDIR) --bindir=$(BINDIR) --ruby=/usr/bin/ruby --destdir=$(CURDIR)/debian/$(cdbs_curpkg) --quick + /usr/bin/ruby install.rb --sitelibdir=$(LIBDIR) --bindir=$(BINDIR) --ruby=/usr/bin/ruby --destdir=$(CURDIR)/debian/$(cdbs_curpkg) --quick --man From a1dd388c4ab5d685d98632adfcd64eba6bc1132a Mon Sep 17 00:00:00 2001 From: fare5 Date: Fri, 23 Aug 2013 12:22:46 +0200 Subject: [PATCH 1428/3753] Support xen 4.0 command invocation As of Xen 4.0, querying for xen domains is done with `/usr/bin/xl` while earlier versions used `/usr/bin/xm`. This commit checks over the preferred command (xl) and then falls back to old command (xm) to detect the correct command to run. --- lib/facter/util/xendomains.rb | 15 ++++++-- spec/unit/util/xendomains_spec.rb | 63 ++++++++++++++++++++++++++----- 2 files changed, 66 insertions(+), 12 deletions(-) diff --git a/lib/facter/util/xendomains.rb b/lib/facter/util/xendomains.rb index 6b0d403ba8..a80a04600e 100644 --- a/lib/facter/util/xendomains.rb +++ b/lib/facter/util/xendomains.rb @@ -1,10 +1,19 @@ # A module to gather running Xen Domains # module Facter::Util::Xendomains + XEN_COMMANDS = ['/usr/sbin/xl', '/usr/sbin/xm'] + + def self.xen_command + XEN_COMMANDS.find { |cmd| Facter::Util::Resolution.which(cmd) } + end + def self.get_domains - if xm_list = Facter::Util::Resolution.exec('/usr/sbin/xm list 2>/dev/null') - domains = xm_list.split("\n").reject { |line| line =~ /^(Name|Domain-0)/ } - domains.map { |line| line.split(/\s/)[0] }.join(',') + command = self.xen_command + if command + if domains_list = Facter::Util::Resolution.exec("#{command} list 2>/dev/null") + domains = domains_list.split("\n").reject { |line| line =~ /^(Name|Domain-0)/ } + domains.map { |line| line.split(/\s/)[0] }.join(',') + end end end end diff --git a/spec/unit/util/xendomains_spec.rb b/spec/unit/util/xendomains_spec.rb index 8649488898..819cbb7821 100755 --- a/spec/unit/util/xendomains_spec.rb +++ b/spec/unit/util/xendomains_spec.rb @@ -4,18 +4,63 @@ require 'facter/util/xendomains' describe Facter::Util::Xendomains do - describe ".get_domains" do - it "should return a list of running Xen Domains on Xen0" do - xen0_domains = my_fixture_read("xendomains") - Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/xm list 2>/dev/null').returns(xen0_domains) - Facter::Util::Xendomains.get_domains.should == %{web01,mailserver} + + let(:xen0_domains) { my_fixture_read("xendomains") } + + describe "when the xl command is present" do + before do + Facter::Util::Resolution.stubs(:which).with('/usr/sbin/xl').returns('/usr/sbin/xl') end - describe "when xm list isn't executable" do - it "should be nil" do - Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/xm list 2>/dev/null').returns(nil) - Facter::Util::Xendomains.get_domains.should == nil + describe "and the xm command is not present" do + + before do + Facter::Util::Resolution.stubs(:which).with('/usr/sbin/xm').returns(nil) + Facter::Util::Resolution.expects(:exec).with('/usr/sbin/xm list 2>/dev/null').never end + + it "lists the domains running on Xen0 with the 'xl' command" do + Facter::Util::Resolution.expects(:exec).with('/usr/sbin/xl list 2>/dev/null').returns(xen0_domains) + Facter::Util::Xendomains.get_domains.should == %{web01,mailserver} + end + end + + describe "and the xm command is also present" do + before do + Facter::Util::Resolution.stubs(:which).with('/usr/sbin/xm').returns('/usr/bin/xm') + Facter::Util::Resolution.expects(:exec).with('/usr/sbin/xm list 2>/dev/null').never + end + + it "prefers xl over xm" do + Facter::Util::Resolution.expects(:exec).with('/usr/sbin/xl list 2>/dev/null').returns(xen0_domains) + Facter::Util::Xendomains.get_domains.should == %{web01,mailserver} + end + end + end + + describe "when xl is not present" do + before do + Facter::Util::Resolution.stubs(:which).with('/usr/sbin/xl').returns(nil) + Facter::Util::Resolution.expects(:exec).with('/usr/sbin/xl list 2>/dev/null').never + end + + describe "and the xm command is present" do + before do + Facter::Util::Resolution.stubs(:which).with('/usr/sbin/xm').returns('/usr/sbin/xm') + end + + it "lists the domains running on Xen0 with the 'xm' command" do + Facter::Util::Resolution.expects(:exec).with('/usr/sbin/xm list 2>/dev/null').returns(xen0_domains) + Facter::Util::Xendomains.get_domains.should == %{web01,mailserver} + end + end + end + + describe "neither xl or xm are present" do + it "returns nil" do + Facter::Util::Resolution.stubs(:which).with('/usr/sbin/xl').returns(nil) + Facter::Util::Resolution.stubs(:which).with('/usr/sbin/xm').returns(nil) + Facter::Util::Xendomains.get_domains.should == nil end end end From 45a1dc93da27a87e6f5b37a2c7dbaeaf5b40fa9a Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Thu, 8 Aug 2013 14:56:09 -0700 Subject: [PATCH 1429/3753] Initial tinkering. --- .gitignore | 4 + Makefile | 15 ++ cfacter.cc | 25 +++ cfacterlib.cc | 511 ++++++++++++++++++++++++++++++++++++++++++++++++++ cfacterlib.h | 15 ++ 5 files changed, 570 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 cfacter.cc create mode 100644 cfacterlib.cc create mode 100644 cfacterlib.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..b736df2351 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.so +*.o +*~ +cfacter diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000..de7449e004 --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ +cfacter: cfacter.cc cfacterlib.cc cfacterlib.h + g++ -g -o cfacter cfacter.cc cfacterlib.cc + +cfacterlib.o: cfacterlib.cc cfacterlib.h + g++ -g -fPIC -c -o $@ cfacterlib.cc + +cfacterlib.so: cfacterlib.o + g++ -g -o $@ $^ -fPIC -shared + +missing: + -$(shell facter | cut -f1 -d' ' | sort > /tmp/facter.txt) + -$(shell ./cfacter | cut -f1 -d' ' | sort > /tmp/cfacter.txt) + -@$(shell diff /tmp/facter.txt /tmp/cfacter.txt > /tmp/facterdiff.txt | true) + cat /tmp/facterdiff.txt + -@rm /tmp/facter.txt /tmp/cfacter.txt /tmp/facterdiff.txt diff --git a/cfacter.cc b/cfacter.cc new file mode 100644 index 0000000000..bf50a5a6af --- /dev/null +++ b/cfacter.cc @@ -0,0 +1,25 @@ +#include "cfacterlib.h" + +#include +#include + +using namespace std; + +int main(int argc, char **argv) +{ + // facter version itself -- report? if so, report facter 'equivalent'? + cout << "facterversion => 1.7.3" << endl; + + dump_network_facts(); + dump_kernel_facts(); + dump_blockdevice_facts(); + dump_lsb_facts(); + dump_uptime_facts(); + dump_virtual_facts(); + dump_hardwired_facts(); + dump_misc_facts(); + dump_ruby_lib_versions(); + dump_mem_facts(); + dump_selinux_facts(); + exit(0); +} diff --git a/cfacterlib.cc b/cfacterlib.cc new file mode 100644 index 0000000000..551af82656 --- /dev/null +++ b/cfacterlib.cc @@ -0,0 +1,511 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cfacterlib.h" + +using namespace std; + +// trim from start +static inline std::string <rim(std::string &s) { + s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun(std::isspace)))); + return s; +} + +// trim from end +static inline std::string &rtrim(std::string &s) { + s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun(std::isspace))).base(), s.end()); + return s; +} + +// trim from both ends +static inline std::string &trim(std::string &s) { + return ltrim(rtrim(s)); +} + +static inline void tokenize(std::string &s, vector &tokens) { + istringstream iss(s); + copy(istream_iterator(iss), + istream_iterator(), + back_inserter >(tokens)); +} + +static inline void split(const string &s, char delim, vector &elems) { + stringstream ss(s); + string item; + while (getline(ss, item, delim)) { + elems.push_back(item); + } +} + + +// handy for some /proc and /sys files +string read_oneline_file(const string file_path) +{ + std::ifstream oneline_file(file_path.c_str(), std::ifstream::in); + std::string line; + std::getline(oneline_file, line); + return line; +} + +void dump_network_facts() +{ + struct ifreq *ifr; + struct ifconf ifc; + int s, i; + int numif; + + // find number of interfaces. + memset(&ifc, 0, sizeof(ifc)); + ifc.ifc_ifcu.ifcu_req = NULL; + ifc.ifc_len = 0; + + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + perror("socket"); + exit(1); + } + + if (ioctl(s, SIOCGIFCONF, &ifc) < 0) { + perror("ioctl"); + exit(1); + } + + if ((ifr = static_cast(malloc(ifc.ifc_len))) == NULL) { + perror("malloc"); + exit(1); + } + ifc.ifc_ifcu.ifcu_req = ifr; + + if (ioctl(s, SIOCGIFCONF, &ifc) < 0) { + perror("ioctl SIOCGIFCONF"); + exit(1); + } + + string interfaces = ""; + + bool primaryInterfacePrinted = false; + numif = ifc.ifc_len / sizeof(struct ifreq); + for (i = 0; i < numif; i++) { + struct ifreq *r = &ifr[i]; + struct in_addr ip_addr = ((struct sockaddr_in *)&r->ifr_addr)->sin_addr; + + // no idea what the real algorithm is to identify the unmarked + // interface, i.e. the one that facter reports as just 'ipaddress' + // here just take the first one that's not 'lo' + bool primaryInterface = false; + if (!primaryInterfacePrinted && strcmp(r->ifr_name, "lo")) { + // this is the chosen interface + primaryInterface = true; + primaryInterfacePrinted = true; + } + + // build up 'interfaces' fact as we go, appending comma after all but last + interfaces += r->ifr_name; + if (i < numif - 1) + interfaces += ","; + + const char *ipaddress = inet_ntoa(ip_addr); + cout << "ipaddress_" << r->ifr_name << " => " << ipaddress << endl; + if (primaryInterface) + cout << "ipaddress => " << ipaddress << endl; + + // mtu + if (ioctl(s, SIOCGIFMTU, r) < 0) { + perror("ioctl SIOCGIFMTU"); + exit(1); + } + + cout << "mtu_" << r->ifr_name << " => " << r->ifr_mtu << endl; + if (primaryInterface) ; // no unmarked version of this network fact + + // netmask and network are both derived from the same ioctl + if (ioctl(s, SIOCGIFNETMASK, r) < 0) { + perror("ioctl SIOCGIFNETMASK"); + exit(1); + } + + // netmask + struct in_addr netmask_addr = ((struct sockaddr_in *)&r->ifr_netmask)->sin_addr; + const char *netmask = inet_ntoa(netmask_addr); + cout << "netmask_" << r->ifr_name << " => " << netmask << endl; + if (primaryInterface) + cout << "netmask => " << netmask << endl; + + // mess of casting to get the network address + struct in_addr network_addr; + network_addr.s_addr = + (in_addr_t) uint32_t(netmask_addr.s_addr) & uint32_t(ip_addr.s_addr); + string network = inet_ntoa(network_addr); + cout << "network_" << r->ifr_name << " => " << network << endl; + if (primaryInterface) + cout << "network => " << network << endl; + + // and the mac address (but not for loopback) + if (strcmp(r->ifr_name, "lo")) { + if (ioctl(s, SIOCGIFHWADDR, r) < 0) { + perror("ioctl SIOCGIFHWADDR"); + exit(1); + } + + // extract mac into a string, okay a char array + uint8_t *mac_bytes = (uint8_t *)((sockaddr *)&r->ifr_hwaddr)->sa_data; + char mac_address[18]; + sprintf(mac_address, "%02x:%02x:%02x:%02x:%02x:%02x", + mac_bytes[0], mac_bytes[1], mac_bytes[2], + mac_bytes[3], mac_bytes[4], mac_bytes[5]); + + // and dump it out + cout << "macaddress_" << r->ifr_name << " => " << mac_address << endl; + if (primaryInterface) + cout << "macaddress => " << mac_address << endl; + } + } + + cout << "interfaces => " << interfaces << endl; + + close(s); + free(ifr); +} + +void dump_kernel_facts() +{ + // this is linux-only, so there you have it + cout << "kernel => Linux" << endl; + string kernelrelease = read_oneline_file("/proc/sys/kernel/osrelease"); + cout << "kernelrelease => " << kernelrelease << endl; + string kernelversion = kernelrelease.substr(0, kernelrelease.find("-")); + cout << "kernelversion => " << kernelversion << endl; + string kernelmajversion = kernelversion.substr(0, kernelversion.rfind(".")); + cout << "kernelmajversion => " << kernelmajversion << endl; +} + +void dump_lsb_facts() +{ + std::ifstream lsb_release_file("/etc/lsb-release", std::ifstream::in); + std::string line; + while (std::getline(lsb_release_file, line)) { + unsigned sep = line.find("="); + string key = line.substr(0, sep); + string value = line.substr(sep + 1, string::npos); + + if (key == "DISTRIB_ID") { + cout << "lsbdistid => " << value << endl; + cout << "operatingsystem => " << value << endl; + cout << "osfamily => Debian" << endl; + } + else if (key == "DISTRIB_RELEASE") { + cout << "lsbdistrelease => " << value << endl; + cout << "operatingsystemrelease => " << value << endl; + cout << "lsbmajdistrelease => " << value.substr(0, value.find(".")) << endl; + } + else if (key == "DISTRIB_CODENAME") + cout << "lsbdistcodename => " << value << endl; + else if (key == "DISTRIB_DESCRIPTION") + cout << "lsbdistdescription => " << value << endl; + } +} + +void dump_uptime_facts() +{ + string uptime = read_oneline_file("/proc/uptime"); + unsigned int uptime_seconds; + sscanf(uptime.c_str(), "%ud", &uptime_seconds); + unsigned int uptime_hours = uptime_seconds / 3600; + unsigned int uptime_days = uptime_hours / 24; + cout << "uptime_seconds => " << uptime_seconds << endl; + cout << "uptime_hours => " << uptime_hours << endl; + cout << "uptime_days => " << uptime_days << endl; + cout << "uptime => " << uptime_days << " days" << endl; +} + +string popen_stdout(string cmd) +{ + FILE *cmd_fd = popen(cmd.c_str(), "r"); + string cmd_output = ""; + char buf[1024]; + size_t bytesRead; + while (bytesRead = fread(buf, 1, sizeof(buf) - 1, cmd_fd)) { + buf[bytesRead] = 0; + cmd_output += buf; + } + return cmd_output; +} + +void dump_virtual_facts() +{ + // poked at the real facter's virtual support, some combo of file existence + // plus lspci plus dmidecode + + // from a time perspective simulate expense with lspci and dmidecode invocations + string lspci_output = popen_stdout("lspci"); + string dmidecode_output = popen_stdout("dmidecode"); + std::stringstream ss(dmidecode_output); + string dmidecode_line; + while (std::getline(ss, dmidecode_line)) { + size_t sep = dmidecode_line.find(":"); + if (sep != string::npos) { + string key = dmidecode_line.substr(0, sep); + string value = dmidecode_line.substr(sep + 1, string::npos); + if (key.find("UUID") != string::npos) + cout << "uuid => " << trim(value) << endl; + } + } + + // instead of parsing all of lspci, how about looking for vendors in lspci -n? + // or walking the /sys/bus/pci/devices files. or don't sweat it, the total + // lspci time is ~40 ms. + + cout << "is_virtual => false" << endl; + cout << "virtual => physical" << endl; + +} + +// placeholders for some hardwired facts, cuz not sure what to do with them +void dump_hardwired_facts() +{ + cout << "ps => ps -ef" << endl; // what is this? + cout << "uniqueid => 007f0101" << endl; // ?? +} + + +// versions of things we don't have if we're not running ruby +// omit or 'undef' or ...? for now, omit but collect them here +void dump_ruby_lib_versions() +{ +/* + cout << "puppetversion => undef" << endl; + cout << "augeasversion => undef" << endl; + cout << "rubysitedir => undef" << endl; + cout << "rubyversion => undef" << endl; +*/ +} + +// block devices +void dump_blockdevice_facts() +{ + string blockdevices = ""; + DIR *sys_block_dir = opendir("/sys/block"); + struct dirent *bd; + + while (bd = readdir(sys_block_dir)) { + bool real_block_device = false; + string device_dir_path = "/sys/block/"; + device_dir_path += bd->d_name; + + DIR *device_dir = opendir(device_dir_path.c_str()); + struct dirent *subdir; + while (subdir = readdir(device_dir)) { + if (strcmp(subdir->d_name, "device") == 0) { + // we have a winner + real_block_device = true; + break; + } + } + + if (!real_block_device) continue; + + // add it to the blockdevices list, careful with the comma + if (!blockdevices.empty()) + blockdevices += ","; + blockdevices += bd->d_name; + + string model_file = "/sys/block/" + string(bd->d_name) + "/device/model"; + cout << "blockdevice_" << bd->d_name << "_model => " << + read_oneline_file(model_file) << endl; + + string vendor_file = "/sys/block/" + string(bd->d_name) + "/device/vendor"; + cout << "blockdevice_" << bd->d_name << "_vendor => " << + read_oneline_file(vendor_file) << endl; + + string size_file = "/sys/block/" + string(bd->d_name) + "/size"; + string size_line = read_oneline_file(size_file); + int64_t size; + // SCNd64 didn't work here?? + sscanf(size_line.c_str(), "%lld", (long long int *)&size); + cout << "blockdevice_" << bd->d_name << "_size => " << size * 512 << endl; + } + + cout << "blockdevices => " << blockdevices << endl; +} + +void dump_misc_facts() +{ + cout << "path => " << getenv("PATH") << endl; + cout << "uid => " << popen_stdout("whoami") << endl; + + // timezone + //char tzstring[16]; + ////time_t here_and_now; + ////strftime(tzstring, sizeof(tzstring - 1), "%Z", localtime(here_and_now)); +} + +// dump just one fact, optionally in two formats +static void dump_mem_fact(std::string fact_name, int fact_value, bool dump_mb_variant = true) +{ + float fact_value_scaled = fact_value / 1024.0; + + if (dump_mb_variant) { + cout << fact_name << "_mb => " << fixed << setprecision(2) << fact_value_scaled << endl; + } + + int scale_index; + for (scale_index = 0; + fact_value_scaled > 1024.0; + fact_value_scaled /= 1024.0, ++scale_index) ; + + std::string scale[4] = {"MB", "GB", "TB", "PB"}; // oh yeah, petabytes ... + + cout << fact_name << " => " << fixed << setprecision(2) << fact_value_scaled << scale[scale_index] << endl; +} + +void dump_mem_facts() +{ + std::ifstream oneline_file("/proc/meminfo", std::ifstream::in); + std::string line; + + // The MemFree fact is the sum of MemFree + Buffer + Cached from /proc/meninfo, + // so sum that one as we go, and dump it out at the end. + // The other three memory facts are straight from /proc/meminfo, so dump those + // as we go. + // All four facts are dumped in two formats: + // _mb => %.2f + // => %.2f %s (where the suffix string is one of MB/GB/TB) + // And then there is a ninth fact, 'memorytotal', which is the same as 'memorysize'. + // + + // NB: this all assumes that all values are in KB. + + unsigned int memoryfree = 0; + + while (std::getline(oneline_file, line)) { + vector tokens; + tokenize(line, tokens); + if (tokens.size() < 3) continue; // should never happen + + if (tokens[0] == "MemTotal:") { + int mem_total = atoi(tokens[1].c_str()); + dump_mem_fact("memorysize", mem_total); + dump_mem_fact("memorytotal", mem_total, false); + } + else if (tokens[0] == "MemFree:" || tokens[0] == "Cached:" || tokens[0] == "Buffers:") { + memoryfree += atoi(tokens[1].c_str()); + } + else if (tokens[0] == "SwapTotal:") + dump_mem_fact("swapsize", atoi(tokens[1].c_str())); + else if (tokens[0] == "SwapFree:") + dump_mem_fact("swapfree", atoi(tokens[1].c_str())); + } + + dump_mem_fact("memoryfree", memoryfree); +} + +static bool file_exist (string filename) +{ + struct stat buffer; + return stat (filename.c_str(), &buffer) == 0; +} + +static string get_selinux_path() +{ + static string selinux_path = ""; + static bool inited = false; + + if (inited) + return selinux_path; + + std::ifstream mounts("/proc/self/mounts", std::ifstream::in); + std::string line; + + while (std::getline(mounts, line)) { + vector tokens; + tokenize(line, tokens); + if (tokens.size() < 2) continue; + if (tokens[0] != "selinuxfs") continue; + + selinux_path = tokens[1]; + break; + } + + inited = true; + + return selinux_path; +} + +static bool selinux() +{ + string selinux_path = get_selinux_path(); + if (selinux_path.empty()) + return false; + + string selinux_enforce_path = selinux_path + "/enforce"; + string security_attr_path = "/proc/self/attr/current"; + if (file_exist(selinux_enforce_path) && file_exist(security_attr_path) && read_oneline_file(security_attr_path) != "kernel") + return true; + + return false; +} + +void dump_selinux_facts() +{ + if (!selinux()) { + cout << "selinux => false" << endl; + return; + } + + cout << "selinux => true" << endl; + + string selinux_path = get_selinux_path(); + + string selinux_enforce_path = selinux_path + "/enforce"; + cout << "selinux_enforced => " << ((read_oneline_file(selinux_enforce_path) == "1") ? "true" : "false") << endl; + + string selinux_policyvers_path = selinux_path + "/policyvers"; + cout << "selinux_policyversion => " << read_oneline_file(selinux_policyvers_path) << endl; + + string selinux_cmd = "/usr/sbin/sestatus"; + FILE* pipe = popen(selinux_cmd.c_str(), "r"); + if (!pipe) return; + + char buffer[512]; // seems like a lot, but there's no constant available + while (!feof(pipe)) { + if (fgets(buffer, 128, pipe) != NULL) { + vector elems; + split(buffer, ':', elems); + if (elems.size() < 2) continue; // shouldn't happen + if (elems[0] == "Current mode") { + cout << "selinux_current_mode => " << trim(elems[1]) << endl; + } + else if (elems[0] == "Mode from config file") { + cout << "selinux_config_mode => " << trim(elems[1]) << endl; + } + else if (elems[0] == "Policy from config file") { + cout << "selinux_config_policy => " << trim(elems[1]) << endl; + cout << "selinux_mode => " << trim(elems[1]) << endl; + } + } + } + + pclose(pipe); +} + + diff --git a/cfacterlib.h b/cfacterlib.h new file mode 100644 index 0000000000..2ac3e7f606 --- /dev/null +++ b/cfacterlib.h @@ -0,0 +1,15 @@ +extern "C" { + +void dump_network_facts(); +void dump_kernel_facts(); +void dump_blockdevice_facts(); +void dump_lsb_facts(); +void dump_uptime_facts(); +void dump_virtual_facts(); +void dump_hardwired_facts(); +void dump_misc_facts(); +void dump_ruby_lib_versions(); +void dump_mem_facts(); +void dump_selinux_facts(); + +} From b394c6a25734ed78aeb43c07eec89e657451f5bd Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Mon, 30 Sep 2013 12:57:23 -0400 Subject: [PATCH 1430/3753] Parse /etc/redhat-release but just for fedora --- cfacter.cc | 2 +- cfacterlib.cc | 40 +++++++++++++++++++++++++++++++++------- cfacterlib.h | 2 +- 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/cfacter.cc b/cfacter.cc index bf50a5a6af..73d0f94bee 100644 --- a/cfacter.cc +++ b/cfacter.cc @@ -13,7 +13,7 @@ int main(int argc, char **argv) dump_network_facts(); dump_kernel_facts(); dump_blockdevice_facts(); - dump_lsb_facts(); + dump_operatingsystem_facts(); dump_uptime_facts(); dump_virtual_facts(); dump_hardwired_facts(); diff --git a/cfacterlib.cc b/cfacterlib.cc index 551af82656..f1787c5cbc 100644 --- a/cfacterlib.cc +++ b/cfacterlib.cc @@ -58,6 +58,11 @@ static inline void split(const string &s, char delim, vector &elems) { } } +static bool file_exist (string filename) +{ + struct stat buffer; + return stat (filename.c_str(), &buffer) == 0; +} // handy for some /proc and /sys files string read_oneline_file(const string file_path) @@ -199,7 +204,7 @@ void dump_kernel_facts() cout << "kernelmajversion => " << kernelmajversion << endl; } -void dump_lsb_facts() +static void dump_lsb_facts() { std::ifstream lsb_release_file("/etc/lsb-release", std::ifstream::in); std::string line; @@ -225,6 +230,33 @@ void dump_lsb_facts() } } +// gonna need to pick a regex library to do os facts rights given all the variants +// for now, just fedora ;> + +static void dump_redhat_facts() +{ + if (file_exist("/etc/redhat-release")) { + string redhat_release = read_oneline_file("/etc/redhat-release"); + vector tokens; + tokenize(redhat_release, tokens); + if (tokens.size() >= 2 && tokens[0] == "Fedora" && tokens[1] == "release") { + cout << "operatingsystem => Fedora" << endl; + if (tokens.size() >= 3) { + cout << "operatingsystemrelease => " << tokens[2] << endl; + cout << "operatingsystemmajrelease => " << tokens[2] << endl; + } + } + else + cout << "operatingsystem => RedHat" << endl; + } +} + +void dump_operatingsystem_facts() +{ + dump_lsb_facts(); + dump_redhat_facts(); +} + void dump_uptime_facts() { string uptime = read_oneline_file("/proc/uptime"); @@ -419,12 +451,6 @@ void dump_mem_facts() dump_mem_fact("memoryfree", memoryfree); } -static bool file_exist (string filename) -{ - struct stat buffer; - return stat (filename.c_str(), &buffer) == 0; -} - static string get_selinux_path() { static string selinux_path = ""; diff --git a/cfacterlib.h b/cfacterlib.h index 2ac3e7f606..455e3396fe 100644 --- a/cfacterlib.h +++ b/cfacterlib.h @@ -3,7 +3,7 @@ extern "C" { void dump_network_facts(); void dump_kernel_facts(); void dump_blockdevice_facts(); -void dump_lsb_facts(); +void dump_operatingsystem_facts(); void dump_uptime_facts(); void dump_virtual_facts(); void dump_hardwired_facts(); From c4ab5018823e8e560964f05549486e2a1177eacd Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Mon, 30 Sep 2013 16:17:19 -0400 Subject: [PATCH 1431/3753] Fix the id fact --- cfacterlib.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cfacterlib.cc b/cfacterlib.cc index f1787c5cbc..fec5eaa938 100644 --- a/cfacterlib.cc +++ b/cfacterlib.cc @@ -383,7 +383,8 @@ void dump_blockdevice_facts() void dump_misc_facts() { cout << "path => " << getenv("PATH") << endl; - cout << "uid => " << popen_stdout("whoami") << endl; + string whoami = popen_stdout("whoami"); + cout << "id => " << trim(whoami) << endl; // timezone //char tzstring[16]; From f457f598e3785834c81ee5730a0eb7e85566b33e Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 1 Oct 2013 15:33:40 -0700 Subject: [PATCH 1432/3753] (#22322) Suppress swap stderr on AIX and Solaris When `swap -l` is run on Solaris and no swap devices are present, the command emits "No swap devices configured". This commit squelches the stderr output to reduce the noice produced when running Facter. This supersedes GH-530. --- lib/facter/util/memory.rb | 4 ++-- spec/unit/memory_spec.rb | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/facter/util/memory.rb b/lib/facter/util/memory.rb index 7c63dd0992..9a2401ea52 100644 --- a/lib/facter/util/memory.rb +++ b/lib/facter/util/memory.rb @@ -134,7 +134,7 @@ def self.swap_free(kernel = Facter.value(:kernel)) def self.swap_info(kernel = Facter.value(:kernel)) case kernel when /AIX/i - (Facter.value(:id) == "root") ? Facter::Util::Resolution.exec('swap -l') : nil + (Facter.value(:id) == "root") ? Facter::Util::Resolution.exec('swap -l 2>/dev/null') : nil when /OpenBSD/i Facter::Util::Resolution.exec('swapctl -s') when /FreeBSD/i @@ -142,7 +142,7 @@ def self.swap_info(kernel = Facter.value(:kernel)) when /Darwin/i Facter::Util::Resolution.exec('sysctl vm.swapusage') when /SunOS/i - Facter::Util::Resolution.exec('/usr/sbin/swap -l') + Facter::Util::Resolution.exec('/usr/sbin/swap -l 2>/dev/null') end end diff --git a/spec/unit/memory_spec.rb b/spec/unit/memory_spec.rb index 2de005e3f3..d249d5b01c 100755 --- a/spec/unit/memory_spec.rb +++ b/spec/unit/memory_spec.rb @@ -154,7 +154,7 @@ /dev/hd6 10, 2 512MB 508MB SWAP - Facter::Util::Resolution.stubs(:exec).with('swap -l').returns(swapusage) + Facter::Util::Resolution.stubs(:exec).with('swap -l 2>/dev/null').returns(swapusage) Facter.collection.internal_loader.load(:memory) end @@ -264,7 +264,7 @@ swapfile dev swaplo blocks free /dev/swap 4294967295,4294967295 16 2097136 2097136 SWAP - Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/swap -l').returns sample_swap_line + Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/swap -l 2>/dev/null').returns sample_swap_line Facter.collection.internal_loader.load(:memory) end @@ -293,7 +293,7 @@ /dev/swap 4294967295,4294967295 16 2097136 2097136 /dev/swap2 4294967295,4294967295 16 2097136 2097136 SWAP - Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/swap -l').returns sample_swap_line + Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/swap -l 2>/dev/null').returns sample_swap_line Facter.collection.internal_loader.load(:memory) end @@ -316,7 +316,7 @@ describe "when no swap exists" do before(:each) do - Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/swap -l').returns "" + Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/swap -l 2>/dev/null').returns "" Facter.collection.internal_loader.load(:memory) end From f6c286e788c251e18dc01d0bbab639df9070447c Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Thu, 3 Oct 2013 13:37:26 -0700 Subject: [PATCH 1433/3753] (maint) Break up a long line and trim empty lines. --- lib/facter/ssh.rb | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/lib/facter/ssh.rb b/lib/facter/ssh.rb index 8d3c4bbd69..7a446a0c88 100644 --- a/lib/facter/ssh.rb +++ b/lib/facter/ssh.rb @@ -11,21 +11,23 @@ ## Facts related to SSH ## -{"SSHDSAKey" => { :file => "ssh_host_dsa_key.pub", :sshfprrtype => 2 } , "SSHRSAKey" => { :file => "ssh_host_rsa_key.pub", :sshfprrtype => 1 }, "SSHECDSAKey" => { :file => "ssh_host_ecdsa_key.pub", :sshfprrtype => 3 } }.each do |name,key| - +{"SSHDSAKey" => { :file => "ssh_host_dsa_key.pub", :sshfprrtype => 2 }, + "SSHRSAKey" => { :file => "ssh_host_rsa_key.pub", :sshfprrtype => 1 }, + "SSHECDSAKey" => { :file => "ssh_host_ecdsa_key.pub", :sshfprrtype => 3 } }.each do |name,key| + Facter.add(name) do setcode do value = nil - + [ "/etc/ssh", "/usr/local/etc/ssh", "/etc", "/usr/local/etc", "/etc/opt/ssh", ].each do |dir| - + filepath = File.join(dir,key[:file]) - + if FileTest.file?(filepath) begin value = File.read(filepath).chomp.split(/\s+/)[1] @@ -35,16 +37,16 @@ end end end - + value end end - + Facter.add('SSHFP_' + name[3..-4]) do setcode do ssh = Facter.fact(name).value value = nil - + if ssh && key[:sshfprrtype] begin require 'digest/sha1' @@ -60,10 +62,10 @@ value = nil end end - + value end - + end - + end From cf18cdb7740230b200e65009e839b52d81282270 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Sat, 28 Sep 2013 14:09:48 -0700 Subject: [PATCH 1434/3753] (maint) Cleanup an unused variable and an unintended array assignment --- lib/facter/util/memory.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/facter/util/memory.rb b/lib/facter/util/memory.rb index 5fb362f3ef..879f582009 100644 --- a/lib/facter/util/memory.rb +++ b/lib/facter/util/memory.rb @@ -6,8 +6,7 @@ module Facter::Memory def self.meminfo_number(tag) - memsize = "" - size = [0] + size = 0 File.readlines("/proc/meminfo").each do |l| size = $1.to_f if l =~ /^#{tag}:\s+(\d+)\s+\S+/ if tag == "MemFree" && @@ -168,7 +167,7 @@ def self.parse_swap (output, kernel = Facter.value(:kernel), size_or_free = :siz output.each_line do |line| value += parse_swap_line(line, kernel, is_size) end - end + end value_in_mb = scale_swap_value(value, kernel) end @@ -177,7 +176,7 @@ def self.parse_swap (output, kernel = Facter.value(:kernel), size_or_free = :siz # regex corresponds to the swap size value and the second corresponds to the swap # free value, but this may not always be the case. In Ruby 1.9.3 it is possible # to give these names, but sadly 1.8.7 does not support this. - + def self.parse_swap_line(line, kernel, is_size) case kernel when /AIX/i From 40603c979e9516dc46f703e56747b6e26e7c3f89 Mon Sep 17 00:00:00 2001 From: Vlastimil Holer Date: Thu, 3 Oct 2013 15:42:41 +0200 Subject: [PATCH 1435/3753] Catch exceptions when detecting SELinux status --- lib/facter/selinux.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/facter/selinux.rb b/lib/facter/selinux.rb index 2ae1762f5b..7ee8695d1f 100644 --- a/lib/facter/selinux.rb +++ b/lib/facter/selinux.rb @@ -36,8 +36,11 @@ def selinux_mount_point result = "false" if FileTest.exists?("#{selinux_mount_point}/enforce") if FileTest.exists?("/proc/self/attr/current") - if (File.read("/proc/self/attr/current") != "kernel\0") - result = "true" + begin + if (File.read("/proc/self/attr/current") != "kernel\0") + result = "true" + end + rescue end end end From 51264661774eef26e84e529da293f3996b414657 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Thu, 3 Oct 2013 20:27:34 -0700 Subject: [PATCH 1436/3753] (#18429) Remove trailing whitespace --- lib/facter/selinux.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/selinux.rb b/lib/facter/selinux.rb index 7ee8695d1f..5fc79e1f6e 100644 --- a/lib/facter/selinux.rb +++ b/lib/facter/selinux.rb @@ -36,7 +36,7 @@ def selinux_mount_point result = "false" if FileTest.exists?("#{selinux_mount_point}/enforce") if FileTest.exists?("/proc/self/attr/current") - begin + begin if (File.read("/proc/self/attr/current") != "kernel\0") result = "true" end From cab9601aa262f2026ff7240cd3597b87f1ea58c7 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Thu, 3 Oct 2013 20:27:51 -0700 Subject: [PATCH 1437/3753] (#18429) Add a spec test describing the error scenario --- spec/unit/selinux_spec.rb | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/spec/unit/selinux_spec.rb b/spec/unit/selinux_spec.rb index 647fbb65c6..f6e4386263 100755 --- a/spec/unit/selinux_spec.rb +++ b/spec/unit/selinux_spec.rb @@ -104,7 +104,23 @@ end end - def sestatus_is(status) + describe "should detect if SELinux is disabled" do + before :each do + Facter.fact(:kernel).stubs(:value).returns("Linux") + end + + it "and return false if /proc/self/attr/current is unavailable" do + mounts_does_not_exist + + File.stubs(:read).with("/proc/self/attr/current").raises(Errno::EINVAL) + FileTest.expects(:exists?).with("/proc/self/attr/current").returns true + FileTest.expects(:exists?).with("/selinux/enforce").returns true + + Facter.fact(:selinux).value.should == "false" + end + end + + def sestatus_is(status) Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/sestatus').returns(status) end From cede22986dbcdd5588971c764a4257a1d121cc15 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Fri, 4 Oct 2013 12:46:59 -0400 Subject: [PATCH 1438/3753] Mimic facter defaults for selinux facts --- cfacter.cc | 1 + cfacterlib.cc | 68 +++++++++++++++++++++++++++++++++++++++++++++------ cfacterlib.h | 1 + 3 files changed, 63 insertions(+), 7 deletions(-) diff --git a/cfacter.cc b/cfacter.cc index 73d0f94bee..c33997e35a 100644 --- a/cfacter.cc +++ b/cfacter.cc @@ -21,5 +21,6 @@ int main(int argc, char **argv) dump_ruby_lib_versions(); dump_mem_facts(); dump_selinux_facts(); + dump_ssh_facts(); exit(0); } diff --git a/cfacterlib.cc b/cfacterlib.cc index fec5eaa938..1ccb3510e4 100644 --- a/cfacterlib.cc +++ b/cfacterlib.cc @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -236,6 +237,7 @@ static void dump_lsb_facts() static void dump_redhat_facts() { if (file_exist("/etc/redhat-release")) { + cout << "osfamily => RedHat" << endl; string redhat_release = read_oneline_file("/etc/redhat-release"); vector tokens; tokenize(redhat_release, tokens); @@ -499,15 +501,26 @@ void dump_selinux_facts() return; } - cout << "selinux => true" << endl; + map selinux_map; + selinux_map["selinux"] = "true"; + + // defaults from facter + selinux_map["selinux_enforced"] = "false"; + selinux_map["selinux_policyversion"] = "unknown"; + selinux_map["selinux_current_mode"] = "unknown"; + selinux_map["selinux_config_mode"] = "unknown"; + selinux_map["selinux_config_policy"] = "unknown"; + selinux_map["selinux_mode"] = "unknown"; string selinux_path = get_selinux_path(); string selinux_enforce_path = selinux_path + "/enforce"; - cout << "selinux_enforced => " << ((read_oneline_file(selinux_enforce_path) == "1") ? "true" : "false") << endl; + if (file_exist(selinux_enforce_path)) + selinux_map["selinux_enforced"] = ((read_oneline_file(selinux_enforce_path) == "1") ? "true" : "false"); string selinux_policyvers_path = selinux_path + "/policyvers"; - cout << "selinux_policyversion => " << read_oneline_file(selinux_policyvers_path) << endl; + if (file_exist(selinux_policyvers_path)) + selinux_map["selinux_policyversion"] = read_oneline_file(selinux_policyvers_path); string selinux_cmd = "/usr/sbin/sestatus"; FILE* pipe = popen(selinux_cmd.c_str(), "r"); @@ -520,19 +533,60 @@ void dump_selinux_facts() split(buffer, ':', elems); if (elems.size() < 2) continue; // shouldn't happen if (elems[0] == "Current mode") { - cout << "selinux_current_mode => " << trim(elems[1]) << endl; + selinux_map["selinux_current_mode"] = trim(elems[1]); } else if (elems[0] == "Mode from config file") { - cout << "selinux_config_mode => " << trim(elems[1]) << endl; + selinux_map["selinux_config_mode"] = trim(elems[1]); } else if (elems[0] == "Policy from config file") { - cout << "selinux_config_policy => " << trim(elems[1]) << endl; - cout << "selinux_mode => " << trim(elems[1]) << endl; + selinux_map["selinux_config_policy"] = trim(elems[1]); + selinux_map["selinux_mode"] = trim(elems[1]); } } } pclose(pipe); + + typedef map::iterator iter; + for (iter i = selinux_map.begin(); i != selinux_map.end(); ++i) { + cout << i->first << " => " << i->second << endl; + } } +static void dump_ssh_fact(string fact_name, string path_name) +{ + string ssh_directories[] = { + "/etc/ssh", + "/usr/local/etc/ssh", + "/etc", + "/usr/local/etc", + "/etc/opt/ssh", + }; + + for (int i = 0; i < sizeof(ssh_directories) / sizeof(string); ++i) { + string full_path = ssh_directories[i] + "/" + path_name; + if (file_exist(full_path)) { + string key = read_oneline_file(full_path); + vector tokens; + tokenize(trim(key), tokens); + if (tokens.size() < 2) continue; // should never happen + cout << fact_name << " => " << tokens[1] << endl; + break; + } + } +} +// no support for the sshfp facts, which require base64/sha1sum code +void dump_ssh_facts() +{ + // not til C++11 do we have static initialization of stl maps + map fact_map; + fact_map["sshdsakey"] = "ssh_host_dsa_key.pub"; + fact_map["sshrsakey"] = "ssh_host_rsa_key.pub"; + fact_map["sshecdsakey"] = "ssh_host_ecdsa_key.pub"; + + typedef map::iterator iter; + for (iter i = fact_map.begin(); i != fact_map.end(); ++i) { + dump_ssh_fact(i->first, i->second); + } +} diff --git a/cfacterlib.h b/cfacterlib.h index 455e3396fe..c53683c7e8 100644 --- a/cfacterlib.h +++ b/cfacterlib.h @@ -11,5 +11,6 @@ void dump_misc_facts(); void dump_ruby_lib_versions(); void dump_mem_facts(); void dump_selinux_facts(); +void dump_ssh_facts(); } From f4dc6c71e8b4e9d0e8d345a9bcc861d5515f011d Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Fri, 4 Oct 2013 12:48:27 -0400 Subject: [PATCH 1439/3753] Tweak 'missing' target for multi-line facts --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index de7449e004..718f1dcd8d 100644 --- a/Makefile +++ b/Makefile @@ -8,8 +8,8 @@ cfacterlib.so: cfacterlib.o g++ -g -o $@ $^ -fPIC -shared missing: - -$(shell facter | cut -f1 -d' ' | sort > /tmp/facter.txt) - -$(shell ./cfacter | cut -f1 -d' ' | sort > /tmp/cfacter.txt) + -$(shell facter | grep "=>" | cut -f1 -d' ' | sort > /tmp/facter.txt) + -$(shell ./cfacter | grep "=>" | cut -f1 -d' ' | sort > /tmp/cfacter.txt) -@$(shell diff /tmp/facter.txt /tmp/cfacter.txt > /tmp/facterdiff.txt | true) cat /tmp/facterdiff.txt -@rm /tmp/facter.txt /tmp/cfacter.txt /tmp/facterdiff.txt From d02574f9ee27eaf8603b31d06895d98317e01746 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Fri, 4 Oct 2013 14:40:23 -0400 Subject: [PATCH 1440/3753] Add physicalprocessorcount --- cfacter.cc | 1 + cfacterlib.cc | 36 ++++++++++++++++++++++++++++++++++++ cfacterlib.h | 1 + 3 files changed, 38 insertions(+) diff --git a/cfacter.cc b/cfacter.cc index c33997e35a..8d80fdacf5 100644 --- a/cfacter.cc +++ b/cfacter.cc @@ -22,5 +22,6 @@ int main(int argc, char **argv) dump_mem_facts(); dump_selinux_facts(); dump_ssh_facts(); + dump_processor_facts(); exit(0); } diff --git a/cfacterlib.cc b/cfacterlib.cc index 1ccb3510e4..1ff5c7d3a5 100644 --- a/cfacterlib.cc +++ b/cfacterlib.cc @@ -17,9 +17,11 @@ #include #include #include +#include #include #include #include +#include #include #include @@ -590,3 +592,37 @@ void dump_ssh_facts() dump_ssh_fact(i->first, i->second); } } + +static void dump_physicalprocessorcount_fact() +{ + // So, facter has logic to use /sys and fallback to /proc + // but I don't know why the /sys support was added; research needed. + // Since sys is the default, just reproduce that logic for now. + + string sysfs_cpu_directory = "/sys/devices/system/cpu"; + vector package_ids; + if (file_exist(sysfs_cpu_directory)) { + unsigned int i = 0; + while (true) { + char buf[10]; + snprintf(buf, sizeof(buf) - 1, "%ud", i); + string cpu_phys_file = sysfs_cpu_directory + "/cpu" + buf + "/topology/physical_package_id"; + if (!file_exist(cpu_phys_file)) + break; + + package_ids.push_back(read_oneline_file(cpu_phys_file)); + } + + sort(package_ids.begin(), package_ids.end()); + unique(package_ids.begin(), package_ids.end()); + cout << "physicalprocessorcount => " << package_ids.size() << endl; + } + else { + // here's where the fall back to /proc/cpuinfo would go + } +} + +void dump_processor_facts() +{ + dump_physicalprocessorcount_fact(); +} diff --git a/cfacterlib.h b/cfacterlib.h index c53683c7e8..f960ea2dfd 100644 --- a/cfacterlib.h +++ b/cfacterlib.h @@ -12,5 +12,6 @@ void dump_ruby_lib_versions(); void dump_mem_facts(); void dump_selinux_facts(); void dump_ssh_facts(); +void dump_processor_facts(); } From 8ccf2b9318a4f40dd82ef7c0701599577a4e4cef Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Sat, 5 Oct 2013 03:47:18 -0400 Subject: [PATCH 1441/3753] Add additional fact support, and a TODO file ;> --- Makefile | 2 +- TODO | 8 ++ cfacter.cc | 5 + cfacterlib.cc | 286 ++++++++++++++++++++++++++++++++++++++++++++++---- cfacterlib.h | 5 +- 5 files changed, 282 insertions(+), 24 deletions(-) create mode 100644 TODO diff --git a/Makefile b/Makefile index 718f1dcd8d..29f3ee617b 100644 --- a/Makefile +++ b/Makefile @@ -11,5 +11,5 @@ missing: -$(shell facter | grep "=>" | cut -f1 -d' ' | sort > /tmp/facter.txt) -$(shell ./cfacter | grep "=>" | cut -f1 -d' ' | sort > /tmp/cfacter.txt) -@$(shell diff /tmp/facter.txt /tmp/cfacter.txt > /tmp/facterdiff.txt | true) - cat /tmp/facterdiff.txt + -@cat /tmp/facterdiff.txt -@rm /tmp/facter.txt /tmp/cfacter.txt /tmp/facterdiff.txt diff --git a/TODO b/TODO new file mode 100644 index 0000000000..f50b3e3ae6 --- /dev/null +++ b/TODO @@ -0,0 +1,8 @@ +* Add regex support (e.g. for os and processor file parsing) +* Allow 'facter ' (currently returns all facts) -- still *retrieve* all facts but only return the one? or introspect what's available? +* Split out into app, core lib, libs per functional group (network, storage, selinux, processors, etc) +* Use dynamic loading of libs (dlopen, etc) +* autoconf build +* is 'weight' needed or is there a better way? +* virtual +* fact dependencies \ No newline at end of file diff --git a/cfacter.cc b/cfacter.cc index 8d80fdacf5..9a14d7fa27 100644 --- a/cfacter.cc +++ b/cfacter.cc @@ -23,5 +23,10 @@ int main(int argc, char **argv) dump_selinux_facts(); dump_ssh_facts(); dump_processor_facts(); + dump_architecture_facts(); + dump_dmidecode_facts(); + dump_filesystems_facts(); + dump_hostname_facts(); + exit(0); } diff --git a/cfacterlib.cc b/cfacterlib.cc index 1ff5c7d3a5..57020752a6 100644 --- a/cfacterlib.cc +++ b/cfacterlib.cc @@ -29,6 +29,41 @@ using namespace std; +// For case-insensitive strings, define ci_string +// Thank you, Herb Sutter: http://www.gotw.ca/gotw/029.htm +// +struct ci_char_traits : public char_traits +// just inherit all the other functions +// that we don't need to override +{ + static bool eq( char c1, char c2 ) + { return toupper(c1) == toupper(c2); } + + static bool ne( char c1, char c2 ) + { return toupper(c1) != toupper(c2); } + + static bool lt( char c1, char c2 ) + { return toupper(c1) < toupper(c2); } + + static int compare( const char* s1, + const char* s2, + size_t n ) { + return strncasecmp( s1, s2, n ); + // if available on your compiler, + // otherwise you can roll your own + } + + static const char* + find( const char* s, int n, char a ) { + while( n-- > 0 && toupper(*s) != toupper(a) ) { + ++s; + } + return s; + } +}; + +typedef basic_string ci_string; + // trim from start static inline std::string <rim(std::string &s) { s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun(std::isspace)))); @@ -284,6 +319,7 @@ string popen_stdout(string cmd) buf[bytesRead] = 0; cmd_output += buf; } + pclose(cmd_fd); return cmd_output; } @@ -292,25 +328,12 @@ void dump_virtual_facts() // poked at the real facter's virtual support, some combo of file existence // plus lspci plus dmidecode - // from a time perspective simulate expense with lspci and dmidecode invocations - string lspci_output = popen_stdout("lspci"); - string dmidecode_output = popen_stdout("dmidecode"); - std::stringstream ss(dmidecode_output); - string dmidecode_line; - while (std::getline(ss, dmidecode_line)) { - size_t sep = dmidecode_line.find(":"); - if (sep != string::npos) { - string key = dmidecode_line.substr(0, sep); - string value = dmidecode_line.substr(sep + 1, string::npos); - if (key.find("UUID") != string::npos) - cout << "uuid => " << trim(value) << endl; - } - } - // instead of parsing all of lspci, how about looking for vendors in lspci -n? // or walking the /sys/bus/pci/devices files. or don't sweat it, the total // lspci time is ~40 ms. + // virtual could be discovered in lots of places so requires some special handling + cout << "is_virtual => false" << endl; cout << "virtual => physical" << endl; @@ -390,10 +413,12 @@ void dump_misc_facts() string whoami = popen_stdout("whoami"); cout << "id => " << trim(whoami) << endl; - // timezone - //char tzstring[16]; - ////time_t here_and_now; - ////strftime(tzstring, sizeof(tzstring - 1), "%Z", localtime(here_and_now)); + //timezone + char tzstring[16]; + time_t t = time(NULL); + struct tm *loc = localtime(&t); + strftime(tzstring, sizeof(tzstring - 1), "%Z", loc); + cout << "timezone => " << tzstring << endl; } // dump just one fact, optionally in two formats @@ -573,6 +598,12 @@ static void dump_ssh_fact(string fact_name, string path_name) tokenize(trim(key), tokens); if (tokens.size() < 2) continue; // should never happen cout << fact_name << " => " << tokens[1] << endl; + + // skpping the finger print facts, which require base64 decode and sha libs + // on the cmd line it would be something like the result of these two: + // "cat " + full_path + " | cut -d' ' -f 2 | base64 -d - | sha256sum - | cut -d' ' -f 1" + // "cat " + full_path + " | cut -d' ' -f 2 | base64 -d - | sha1sum - | cut -d' ' -f 1" + break; } } @@ -602,11 +633,11 @@ static void dump_physicalprocessorcount_fact() string sysfs_cpu_directory = "/sys/devices/system/cpu"; vector package_ids; if (file_exist(sysfs_cpu_directory)) { - unsigned int i = 0; - while (true) { + for (int i = 0; ; i++) { char buf[10]; - snprintf(buf, sizeof(buf) - 1, "%ud", i); + snprintf(buf, sizeof(buf) - 1, "%u", i); string cpu_phys_file = sysfs_cpu_directory + "/cpu" + buf + "/topology/physical_package_id"; + cout << cpu_phys_file << endl; if (!file_exist(cpu_phys_file)) break; @@ -622,7 +653,218 @@ static void dump_physicalprocessorcount_fact() } } +void dump_processorcount_fact() +{ + std::ifstream cpuinfo_file("/proc/cpuinfo", std::ifstream::in); + std::string line; + int processor_count = 0; + string current_processor_number; + while (std::getline(cpuinfo_file, line)) { + unsigned sep = line.find(":"); + string tmp = line.substr(0, sep); + string key = trim(tmp); + + if (key == "processor") { + ++processor_count; + string tmp = line.substr(sep + 1, string::npos); + current_processor_number = trim(tmp); + } + else if (key == "model name") { + string tmp = line.substr(sep + 1, string::npos); + cout << "processor" << current_processor_number << " => " << trim(tmp) << endl; + } + } + // this was added after 1.7.3, omit for now, needs investigation + if (false) cout << "activeprocessorcount => " << processor_count << endl; + cout << "processorcount => " << processor_count << endl; +} + void dump_processor_facts() { dump_physicalprocessorcount_fact(); + dump_processorcount_fact(); +} + +void dump_architecture_facts() +{ + struct utsname uts; + if (uname(&uts) == 0) { + // This is cheating at some level because these are all the same on x86_64 linux. + // Otoh, some of these may be compiled-in for a C version. And then if facter + // relies on 'uname -p' here and that commonizes, this should perhaps just shell out + // and not reproduce that logic. Regardless, need to survey cross-platform here and + // take it from there. + cout << "hardwaremodel => " << uts.machine << endl; + cout << "hardwareisa => " << uts.machine << endl; + cout << "architecture => " << uts.machine << endl; + } +} + +void dump_dmidecode_facts() +{ + // from a time perspective simulate expense with lspci and dmidecode invocations + string dmidecode_output = popen_stdout("/usr/sbin/dmidecode"); + std::stringstream ss(dmidecode_output); + string line; + + enum { + bios_information, + base_board_information, + system_information, + chassis_information, + unknown + } dmi_section = unknown; + + while (std::getline(ss, line)) { + if (line.empty()) continue; + + // enable case-insensitive compares + ci_string ci_line = line.c_str(); + + // identify the dmi section, they all begin at the beginning of a line + // and there are only a handful of interest to us + if (ci_line == "BIOS Information") { + dmi_section = bios_information; + continue; + } + else if (ci_line == "Base Board Information") { + dmi_section = base_board_information; + continue; + } + else if (ci_line == "System Information") { + dmi_section = system_information; + continue; + } + else if (ci_line == "Chassis Information" || ci_line == "system enclosure or chassis") { + dmi_section = chassis_information; + continue; + } + else if (ci_line[0] >= 'A' && ci_line[0] <= 'Z') { + dmi_section = unknown; + continue; + } + + // if we're in the middle of an unknown section, skip + if (dmi_section == unknown) continue; + + size_t sep = line.find(":"); + if (sep != string::npos) { + string tmp = line.substr(0, sep); + string key = trim(tmp); + if (dmi_section == bios_information) { + ci_string ci_key = key.c_str(); + if (ci_key == "vendor") { + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); + cout << "bios_vendor => " << value << endl; + } + if (ci_key == "version") { + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); + cout << "bios_version => " << value << endl; + } + if (ci_key == "release date") { + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); + cout << "bios_release_date => " << value << endl; + } + } + else if (dmi_section == base_board_information) { + ci_string ci_key = key.c_str(); + if (ci_key == "manufacturer") { + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); + cout << "boardmanufacturer => " << value << endl; + } + if (ci_key == "product name" || ci_key == "product") { + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); + cout << "boardproductname => " << value << endl; + } + if (ci_key == "serial number") { + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); + cout << "boardserialnumber => " << value << endl; + } + } + else if (dmi_section == system_information) { + ci_string ci_key = key.c_str(); + if (ci_key == "manufacturer") { + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); + cout << "manufacturer => " << value << endl; + } + if (ci_key == "product name" || ci_key == "product") { + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); + cout << "productname => " << value << endl; + } + if (ci_key == "serial number") { + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); + cout << "serialnumber => " << value << endl; + } + if (ci_key == "uuid") { + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); + cout << "uuid => " << value << endl; + } + } + else if (dmi_section == chassis_information) { + ci_string ci_key = key.c_str(); + if (ci_key == "chassis type" || ci_key == "type") { + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); + cout << "type => " << value << endl; + } + } + } + } +} + +void dump_filesystems_facts() +{ + std::ifstream cpuinfo_file("/proc/filesystems", std::ifstream::in); + std::string line; + string filesystems = ""; + while (std::getline(cpuinfo_file, line)) { + if (line.find("nodev") != string::npos || line.find("fuseblk") != string::npos) + continue; + + if (!filesystems.empty()) + filesystems += ","; + + filesystems += trim(line); + } + cout << "filesystems => " << filesystems << endl; +} + +void dump_hostname_facts() +{ + // there's some history here, perhaps just port the facter conditional straight across? + // so this is short-term + string hostname_output = popen_stdout("hostname"); + unsigned sep = hostname_output.find("."); + string hostname = hostname_output.substr(0, sep); + + ifstream resolv_conf_file("/etc/resolv.conf", std::ifstream::in); + string line; + string domain; + string search; + while (std::getline(resolv_conf_file, line)) { + vector elems; + tokenize(line, elems); + if (elems.size() >= 2) { + if (elems[0] == "domain") + domain = trim(elems[1]); + else if (elems[0] == "search") + search = trim(elems[1]); + } + } + if (domain.empty() && !search.empty()) + domain = search; + + cout << "hostname => " << hostname << endl; + cout << "domain => " << domain << endl; + cout << "fqdn => " << hostname << "." << domain << endl; } diff --git a/cfacterlib.h b/cfacterlib.h index f960ea2dfd..6fcbcadd18 100644 --- a/cfacterlib.h +++ b/cfacterlib.h @@ -13,5 +13,8 @@ void dump_mem_facts(); void dump_selinux_facts(); void dump_ssh_facts(); void dump_processor_facts(); - +void dump_architecture_facts(); +void dump_dmidecode_facts(); +void dump_filesystems_facts(); +void dump_hostname_facts(); } From 505f50e878f42cb533deae7b5c688529d82d1db4 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Tue, 8 Oct 2013 14:34:05 -0400 Subject: [PATCH 1442/3753] Indent consistently --- cfacter.cc | 40 +-- cfacterlib.cc | 839 +++++++++++++++++++++++++------------------------- cfacterlib.h | 35 ++- 3 files changed, 458 insertions(+), 456 deletions(-) diff --git a/cfacter.cc b/cfacter.cc index 9a14d7fa27..55cc96937e 100644 --- a/cfacter.cc +++ b/cfacter.cc @@ -7,26 +7,26 @@ using namespace std; int main(int argc, char **argv) { - // facter version itself -- report? if so, report facter 'equivalent'? - cout << "facterversion => 1.7.3" << endl; + // facter version itself -- report? if so, report facter 'equivalent'? + cout << "facterversion => 1.7.3" << endl; - dump_network_facts(); - dump_kernel_facts(); - dump_blockdevice_facts(); - dump_operatingsystem_facts(); - dump_uptime_facts(); - dump_virtual_facts(); - dump_hardwired_facts(); - dump_misc_facts(); - dump_ruby_lib_versions(); - dump_mem_facts(); - dump_selinux_facts(); - dump_ssh_facts(); - dump_processor_facts(); - dump_architecture_facts(); - dump_dmidecode_facts(); - dump_filesystems_facts(); - dump_hostname_facts(); + dump_network_facts(); + dump_kernel_facts(); + dump_blockdevice_facts(); + dump_operatingsystem_facts(); + dump_uptime_facts(); + dump_virtual_facts(); + dump_hardwired_facts(); + dump_misc_facts(); + dump_ruby_lib_versions(); + dump_mem_facts(); + dump_selinux_facts(); + dump_ssh_facts(); + dump_processor_facts(); + dump_architecture_facts(); + dump_dmidecode_facts(); + dump_filesystems_facts(); + dump_hostname_facts(); - exit(0); + exit(0); } diff --git a/cfacterlib.cc b/cfacterlib.cc index 57020752a6..a58ac59fe6 100644 --- a/cfacterlib.cc +++ b/cfacterlib.cc @@ -1,3 +1,4 @@ + #include #include #include @@ -53,39 +54,39 @@ struct ci_char_traits : public char_traits // otherwise you can roll your own } - static const char* - find( const char* s, int n, char a ) { - while( n-- > 0 && toupper(*s) != toupper(a) ) { - ++s; - } - return s; + static const char* + find( const char* s, int n, char a ) { + while( n-- > 0 && toupper(*s) != toupper(a) ) { + ++s; } + return s; + } }; typedef basic_string ci_string; // trim from start static inline std::string <rim(std::string &s) { - s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun(std::isspace)))); - return s; + s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun(std::isspace)))); + return s; } // trim from end static inline std::string &rtrim(std::string &s) { - s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun(std::isspace))).base(), s.end()); - return s; + s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun(std::isspace))).base(), s.end()); + return s; } // trim from both ends static inline std::string &trim(std::string &s) { - return ltrim(rtrim(s)); + return ltrim(rtrim(s)); } static inline void tokenize(std::string &s, vector &tokens) { - istringstream iss(s); - copy(istream_iterator(iss), - istream_iterator(), - back_inserter >(tokens)); + istringstream iss(s); + copy(istream_iterator(iss), + istream_iterator(), + back_inserter >(tokens)); } static inline void split(const string &s, char delim, vector &elems) { @@ -105,167 +106,167 @@ static bool file_exist (string filename) // handy for some /proc and /sys files string read_oneline_file(const string file_path) { - std::ifstream oneline_file(file_path.c_str(), std::ifstream::in); - std::string line; - std::getline(oneline_file, line); - return line; + std::ifstream oneline_file(file_path.c_str(), std::ifstream::in); + std::string line; + std::getline(oneline_file, line); + return line; } void dump_network_facts() { - struct ifreq *ifr; - struct ifconf ifc; - int s, i; - int numif; - - // find number of interfaces. - memset(&ifc, 0, sizeof(ifc)); - ifc.ifc_ifcu.ifcu_req = NULL; - ifc.ifc_len = 0; - - if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { - perror("socket"); - exit(1); - } - - if (ioctl(s, SIOCGIFCONF, &ifc) < 0) { - perror("ioctl"); - exit(1); - } - - if ((ifr = static_cast(malloc(ifc.ifc_len))) == NULL) { - perror("malloc"); - exit(1); - } - ifc.ifc_ifcu.ifcu_req = ifr; - - if (ioctl(s, SIOCGIFCONF, &ifc) < 0) { - perror("ioctl SIOCGIFCONF"); - exit(1); - } - - string interfaces = ""; - - bool primaryInterfacePrinted = false; - numif = ifc.ifc_len / sizeof(struct ifreq); - for (i = 0; i < numif; i++) { - struct ifreq *r = &ifr[i]; - struct in_addr ip_addr = ((struct sockaddr_in *)&r->ifr_addr)->sin_addr; - - // no idea what the real algorithm is to identify the unmarked - // interface, i.e. the one that facter reports as just 'ipaddress' - // here just take the first one that's not 'lo' - bool primaryInterface = false; - if (!primaryInterfacePrinted && strcmp(r->ifr_name, "lo")) { - // this is the chosen interface - primaryInterface = true; - primaryInterfacePrinted = true; - } - - // build up 'interfaces' fact as we go, appending comma after all but last - interfaces += r->ifr_name; - if (i < numif - 1) - interfaces += ","; - - const char *ipaddress = inet_ntoa(ip_addr); - cout << "ipaddress_" << r->ifr_name << " => " << ipaddress << endl; - if (primaryInterface) - cout << "ipaddress => " << ipaddress << endl; - - // mtu - if (ioctl(s, SIOCGIFMTU, r) < 0) { - perror("ioctl SIOCGIFMTU"); - exit(1); - } - - cout << "mtu_" << r->ifr_name << " => " << r->ifr_mtu << endl; - if (primaryInterface) ; // no unmarked version of this network fact + struct ifreq *ifr; + struct ifconf ifc; + int s, i; + int numif; + + // find number of interfaces. + memset(&ifc, 0, sizeof(ifc)); + ifc.ifc_ifcu.ifcu_req = NULL; + ifc.ifc_len = 0; + + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + perror("socket"); + exit(1); + } + + if (ioctl(s, SIOCGIFCONF, &ifc) < 0) { + perror("ioctl"); + exit(1); + } + + if ((ifr = static_cast(malloc(ifc.ifc_len))) == NULL) { + perror("malloc"); + exit(1); + } + ifc.ifc_ifcu.ifcu_req = ifr; + + if (ioctl(s, SIOCGIFCONF, &ifc) < 0) { + perror("ioctl SIOCGIFCONF"); + exit(1); + } + + string interfaces = ""; + + bool primaryInterfacePrinted = false; + numif = ifc.ifc_len / sizeof(struct ifreq); + for (i = 0; i < numif; i++) { + struct ifreq *r = &ifr[i]; + struct in_addr ip_addr = ((struct sockaddr_in *)&r->ifr_addr)->sin_addr; + + // no idea what the real algorithm is to identify the unmarked + // interface, i.e. the one that facter reports as just 'ipaddress' + // here just take the first one that's not 'lo' + bool primaryInterface = false; + if (!primaryInterfacePrinted && strcmp(r->ifr_name, "lo")) { + // this is the chosen interface + primaryInterface = true; + primaryInterfacePrinted = true; + } + + // build up 'interfaces' fact as we go, appending comma after all but last + interfaces += r->ifr_name; + if (i < numif - 1) + interfaces += ","; + + const char *ipaddress = inet_ntoa(ip_addr); + cout << "ipaddress_" << r->ifr_name << " => " << ipaddress << endl; + if (primaryInterface) + cout << "ipaddress => " << ipaddress << endl; + + // mtu + if (ioctl(s, SIOCGIFMTU, r) < 0) { + perror("ioctl SIOCGIFMTU"); + exit(1); + } + + cout << "mtu_" << r->ifr_name << " => " << r->ifr_mtu << endl; + if (primaryInterface) ; // no unmarked version of this network fact - // netmask and network are both derived from the same ioctl - if (ioctl(s, SIOCGIFNETMASK, r) < 0) { - perror("ioctl SIOCGIFNETMASK"); - exit(1); - } - - // netmask - struct in_addr netmask_addr = ((struct sockaddr_in *)&r->ifr_netmask)->sin_addr; - const char *netmask = inet_ntoa(netmask_addr); - cout << "netmask_" << r->ifr_name << " => " << netmask << endl; - if (primaryInterface) - cout << "netmask => " << netmask << endl; - - // mess of casting to get the network address - struct in_addr network_addr; - network_addr.s_addr = - (in_addr_t) uint32_t(netmask_addr.s_addr) & uint32_t(ip_addr.s_addr); - string network = inet_ntoa(network_addr); - cout << "network_" << r->ifr_name << " => " << network << endl; - if (primaryInterface) - cout << "network => " << network << endl; - - // and the mac address (but not for loopback) - if (strcmp(r->ifr_name, "lo")) { - if (ioctl(s, SIOCGIFHWADDR, r) < 0) { - perror("ioctl SIOCGIFHWADDR"); - exit(1); - } - - // extract mac into a string, okay a char array - uint8_t *mac_bytes = (uint8_t *)((sockaddr *)&r->ifr_hwaddr)->sa_data; - char mac_address[18]; - sprintf(mac_address, "%02x:%02x:%02x:%02x:%02x:%02x", - mac_bytes[0], mac_bytes[1], mac_bytes[2], - mac_bytes[3], mac_bytes[4], mac_bytes[5]); - - // and dump it out - cout << "macaddress_" << r->ifr_name << " => " << mac_address << endl; - if (primaryInterface) - cout << "macaddress => " << mac_address << endl; - } - } - - cout << "interfaces => " << interfaces << endl; - - close(s); - free(ifr); + // netmask and network are both derived from the same ioctl + if (ioctl(s, SIOCGIFNETMASK, r) < 0) { + perror("ioctl SIOCGIFNETMASK"); + exit(1); + } + + // netmask + struct in_addr netmask_addr = ((struct sockaddr_in *)&r->ifr_netmask)->sin_addr; + const char *netmask = inet_ntoa(netmask_addr); + cout << "netmask_" << r->ifr_name << " => " << netmask << endl; + if (primaryInterface) + cout << "netmask => " << netmask << endl; + + // mess of casting to get the network address + struct in_addr network_addr; + network_addr.s_addr = + (in_addr_t) uint32_t(netmask_addr.s_addr) & uint32_t(ip_addr.s_addr); + string network = inet_ntoa(network_addr); + cout << "network_" << r->ifr_name << " => " << network << endl; + if (primaryInterface) + cout << "network => " << network << endl; + + // and the mac address (but not for loopback) + if (strcmp(r->ifr_name, "lo")) { + if (ioctl(s, SIOCGIFHWADDR, r) < 0) { + perror("ioctl SIOCGIFHWADDR"); + exit(1); + } + + // extract mac into a string, okay a char array + uint8_t *mac_bytes = (uint8_t *)((sockaddr *)&r->ifr_hwaddr)->sa_data; + char mac_address[18]; + sprintf(mac_address, "%02x:%02x:%02x:%02x:%02x:%02x", + mac_bytes[0], mac_bytes[1], mac_bytes[2], + mac_bytes[3], mac_bytes[4], mac_bytes[5]); + + // and dump it out + cout << "macaddress_" << r->ifr_name << " => " << mac_address << endl; + if (primaryInterface) + cout << "macaddress => " << mac_address << endl; + } + } + + cout << "interfaces => " << interfaces << endl; + + close(s); + free(ifr); } void dump_kernel_facts() { - // this is linux-only, so there you have it - cout << "kernel => Linux" << endl; - string kernelrelease = read_oneline_file("/proc/sys/kernel/osrelease"); - cout << "kernelrelease => " << kernelrelease << endl; - string kernelversion = kernelrelease.substr(0, kernelrelease.find("-")); - cout << "kernelversion => " << kernelversion << endl; - string kernelmajversion = kernelversion.substr(0, kernelversion.rfind(".")); - cout << "kernelmajversion => " << kernelmajversion << endl; + // this is linux-only, so there you have it + cout << "kernel => Linux" << endl; + string kernelrelease = read_oneline_file("/proc/sys/kernel/osrelease"); + cout << "kernelrelease => " << kernelrelease << endl; + string kernelversion = kernelrelease.substr(0, kernelrelease.find("-")); + cout << "kernelversion => " << kernelversion << endl; + string kernelmajversion = kernelversion.substr(0, kernelversion.rfind(".")); + cout << "kernelmajversion => " << kernelmajversion << endl; } static void dump_lsb_facts() { - std::ifstream lsb_release_file("/etc/lsb-release", std::ifstream::in); - std::string line; - while (std::getline(lsb_release_file, line)) { - unsigned sep = line.find("="); - string key = line.substr(0, sep); - string value = line.substr(sep + 1, string::npos); + std::ifstream lsb_release_file("/etc/lsb-release", std::ifstream::in); + std::string line; + while (std::getline(lsb_release_file, line)) { + unsigned sep = line.find("="); + string key = line.substr(0, sep); + string value = line.substr(sep + 1, string::npos); - if (key == "DISTRIB_ID") { - cout << "lsbdistid => " << value << endl; - cout << "operatingsystem => " << value << endl; - cout << "osfamily => Debian" << endl; - } - else if (key == "DISTRIB_RELEASE") { - cout << "lsbdistrelease => " << value << endl; - cout << "operatingsystemrelease => " << value << endl; - cout << "lsbmajdistrelease => " << value.substr(0, value.find(".")) << endl; - } - else if (key == "DISTRIB_CODENAME") - cout << "lsbdistcodename => " << value << endl; - else if (key == "DISTRIB_DESCRIPTION") - cout << "lsbdistdescription => " << value << endl; + if (key == "DISTRIB_ID") { + cout << "lsbdistid => " << value << endl; + cout << "operatingsystem => " << value << endl; + cout << "osfamily => Debian" << endl; + } + else if (key == "DISTRIB_RELEASE") { + cout << "lsbdistrelease => " << value << endl; + cout << "operatingsystemrelease => " << value << endl; + cout << "lsbmajdistrelease => " << value.substr(0, value.find(".")) << endl; } + else if (key == "DISTRIB_CODENAME") + cout << "lsbdistcodename => " << value << endl; + else if (key == "DISTRIB_DESCRIPTION") + cout << "lsbdistdescription => " << value << endl; + } } // gonna need to pick a regex library to do os facts rights given all the variants @@ -298,52 +299,52 @@ void dump_operatingsystem_facts() void dump_uptime_facts() { - string uptime = read_oneline_file("/proc/uptime"); - unsigned int uptime_seconds; - sscanf(uptime.c_str(), "%ud", &uptime_seconds); - unsigned int uptime_hours = uptime_seconds / 3600; - unsigned int uptime_days = uptime_hours / 24; - cout << "uptime_seconds => " << uptime_seconds << endl; - cout << "uptime_hours => " << uptime_hours << endl; - cout << "uptime_days => " << uptime_days << endl; - cout << "uptime => " << uptime_days << " days" << endl; + string uptime = read_oneline_file("/proc/uptime"); + unsigned int uptime_seconds; + sscanf(uptime.c_str(), "%ud", &uptime_seconds); + unsigned int uptime_hours = uptime_seconds / 3600; + unsigned int uptime_days = uptime_hours / 24; + cout << "uptime_seconds => " << uptime_seconds << endl; + cout << "uptime_hours => " << uptime_hours << endl; + cout << "uptime_days => " << uptime_days << endl; + cout << "uptime => " << uptime_days << " days" << endl; } string popen_stdout(string cmd) { - FILE *cmd_fd = popen(cmd.c_str(), "r"); - string cmd_output = ""; - char buf[1024]; - size_t bytesRead; - while (bytesRead = fread(buf, 1, sizeof(buf) - 1, cmd_fd)) { - buf[bytesRead] = 0; - cmd_output += buf; - } - pclose(cmd_fd); - return cmd_output; + FILE *cmd_fd = popen(cmd.c_str(), "r"); + string cmd_output = ""; + char buf[1024]; + size_t bytesRead; + while (bytesRead = fread(buf, 1, sizeof(buf) - 1, cmd_fd)) { + buf[bytesRead] = 0; + cmd_output += buf; + } + pclose(cmd_fd); + return cmd_output; } void dump_virtual_facts() { - // poked at the real facter's virtual support, some combo of file existence - // plus lspci plus dmidecode + // poked at the real facter's virtual support, some combo of file existence + // plus lspci plus dmidecode - // instead of parsing all of lspci, how about looking for vendors in lspci -n? - // or walking the /sys/bus/pci/devices files. or don't sweat it, the total - // lspci time is ~40 ms. + // instead of parsing all of lspci, how about looking for vendors in lspci -n? + // or walking the /sys/bus/pci/devices files. or don't sweat it, the total + // lspci time is ~40 ms. - // virtual could be discovered in lots of places so requires some special handling + // virtual could be discovered in lots of places so requires some special handling - cout << "is_virtual => false" << endl; - cout << "virtual => physical" << endl; + cout << "is_virtual => false" << endl; + cout << "virtual => physical" << endl; } // placeholders for some hardwired facts, cuz not sure what to do with them void dump_hardwired_facts() { - cout << "ps => ps -ef" << endl; // what is this? - cout << "uniqueid => 007f0101" << endl; // ?? + cout << "ps => ps -ef" << endl; // what is this? + cout << "uniqueid => 007f0101" << endl; // ?? } @@ -351,74 +352,74 @@ void dump_hardwired_facts() // omit or 'undef' or ...? for now, omit but collect them here void dump_ruby_lib_versions() { -/* + /* cout << "puppetversion => undef" << endl; cout << "augeasversion => undef" << endl; cout << "rubysitedir => undef" << endl; cout << "rubyversion => undef" << endl; -*/ + */ } // block devices void dump_blockdevice_facts() { - string blockdevices = ""; - DIR *sys_block_dir = opendir("/sys/block"); - struct dirent *bd; - - while (bd = readdir(sys_block_dir)) { - bool real_block_device = false; - string device_dir_path = "/sys/block/"; - device_dir_path += bd->d_name; - - DIR *device_dir = opendir(device_dir_path.c_str()); - struct dirent *subdir; - while (subdir = readdir(device_dir)) { - if (strcmp(subdir->d_name, "device") == 0) { - // we have a winner - real_block_device = true; - break; - } - } - - if (!real_block_device) continue; - - // add it to the blockdevices list, careful with the comma - if (!blockdevices.empty()) - blockdevices += ","; - blockdevices += bd->d_name; - - string model_file = "/sys/block/" + string(bd->d_name) + "/device/model"; - cout << "blockdevice_" << bd->d_name << "_model => " << - read_oneline_file(model_file) << endl; - - string vendor_file = "/sys/block/" + string(bd->d_name) + "/device/vendor"; - cout << "blockdevice_" << bd->d_name << "_vendor => " << - read_oneline_file(vendor_file) << endl; - - string size_file = "/sys/block/" + string(bd->d_name) + "/size"; - string size_line = read_oneline_file(size_file); - int64_t size; - // SCNd64 didn't work here?? - sscanf(size_line.c_str(), "%lld", (long long int *)&size); - cout << "blockdevice_" << bd->d_name << "_size => " << size * 512 << endl; + string blockdevices = ""; + DIR *sys_block_dir = opendir("/sys/block"); + struct dirent *bd; + + while (bd = readdir(sys_block_dir)) { + bool real_block_device = false; + string device_dir_path = "/sys/block/"; + device_dir_path += bd->d_name; + + DIR *device_dir = opendir(device_dir_path.c_str()); + struct dirent *subdir; + while (subdir = readdir(device_dir)) { + if (strcmp(subdir->d_name, "device") == 0) { + // we have a winner + real_block_device = true; + break; + } } + + if (!real_block_device) continue; + + // add it to the blockdevices list, careful with the comma + if (!blockdevices.empty()) + blockdevices += ","; + blockdevices += bd->d_name; + + string model_file = "/sys/block/" + string(bd->d_name) + "/device/model"; + cout << "blockdevice_" << bd->d_name << "_model => " << + read_oneline_file(model_file) << endl; + + string vendor_file = "/sys/block/" + string(bd->d_name) + "/device/vendor"; + cout << "blockdevice_" << bd->d_name << "_vendor => " << + read_oneline_file(vendor_file) << endl; + + string size_file = "/sys/block/" + string(bd->d_name) + "/size"; + string size_line = read_oneline_file(size_file); + int64_t size; + // SCNd64 didn't work here?? + sscanf(size_line.c_str(), "%lld", (long long int *)&size); + cout << "blockdevice_" << bd->d_name << "_size => " << size * 512 << endl; + } - cout << "blockdevices => " << blockdevices << endl; + cout << "blockdevices => " << blockdevices << endl; } void dump_misc_facts() { - cout << "path => " << getenv("PATH") << endl; - string whoami = popen_stdout("whoami"); - cout << "id => " << trim(whoami) << endl; + cout << "path => " << getenv("PATH") << endl; + string whoami = popen_stdout("whoami"); + cout << "id => " << trim(whoami) << endl; - //timezone - char tzstring[16]; - time_t t = time(NULL); - struct tm *loc = localtime(&t); - strftime(tzstring, sizeof(tzstring - 1), "%Z", loc); - cout << "timezone => " << tzstring << endl; + //timezone + char tzstring[16]; + time_t t = time(NULL); + struct tm *loc = localtime(&t); + strftime(tzstring, sizeof(tzstring - 1), "%Z", loc); + cout << "timezone => " << tzstring << endl; } // dump just one fact, optionally in two formats @@ -442,43 +443,43 @@ static void dump_mem_fact(std::string fact_name, int fact_value, bool dump_mb_va void dump_mem_facts() { - std::ifstream oneline_file("/proc/meminfo", std::ifstream::in); - std::string line; - - // The MemFree fact is the sum of MemFree + Buffer + Cached from /proc/meninfo, - // so sum that one as we go, and dump it out at the end. - // The other three memory facts are straight from /proc/meminfo, so dump those - // as we go. - // All four facts are dumped in two formats: - // _mb => %.2f - // => %.2f %s (where the suffix string is one of MB/GB/TB) - // And then there is a ninth fact, 'memorytotal', which is the same as 'memorysize'. - // - - // NB: this all assumes that all values are in KB. - - unsigned int memoryfree = 0; - - while (std::getline(oneline_file, line)) { - vector tokens; - tokenize(line, tokens); - if (tokens.size() < 3) continue; // should never happen - - if (tokens[0] == "MemTotal:") { - int mem_total = atoi(tokens[1].c_str()); - dump_mem_fact("memorysize", mem_total); - dump_mem_fact("memorytotal", mem_total, false); - } - else if (tokens[0] == "MemFree:" || tokens[0] == "Cached:" || tokens[0] == "Buffers:") { - memoryfree += atoi(tokens[1].c_str()); - } - else if (tokens[0] == "SwapTotal:") - dump_mem_fact("swapsize", atoi(tokens[1].c_str())); - else if (tokens[0] == "SwapFree:") - dump_mem_fact("swapfree", atoi(tokens[1].c_str())); + std::ifstream oneline_file("/proc/meminfo", std::ifstream::in); + std::string line; + + // The MemFree fact is the sum of MemFree + Buffer + Cached from /proc/meninfo, + // so sum that one as we go, and dump it out at the end. + // The other three memory facts are straight from /proc/meminfo, so dump those + // as we go. + // All four facts are dumped in two formats: + // _mb => %.2f + // => %.2f %s (where the suffix string is one of MB/GB/TB) + // And then there is a ninth fact, 'memorytotal', which is the same as 'memorysize'. + // + + // NB: this all assumes that all values are in KB. + + unsigned int memoryfree = 0; + + while (std::getline(oneline_file, line)) { + vector tokens; + tokenize(line, tokens); + if (tokens.size() < 3) continue; // should never happen + + if (tokens[0] == "MemTotal:") { + int mem_total = atoi(tokens[1].c_str()); + dump_mem_fact("memorysize", mem_total); + dump_mem_fact("memorytotal", mem_total, false); + } + else if (tokens[0] == "MemFree:" || tokens[0] == "Cached:" || tokens[0] == "Buffers:") { + memoryfree += atoi(tokens[1].c_str()); } + else if (tokens[0] == "SwapTotal:") + dump_mem_fact("swapsize", atoi(tokens[1].c_str())); + else if (tokens[0] == "SwapFree:") + dump_mem_fact("swapfree", atoi(tokens[1].c_str())); + } - dump_mem_fact("memoryfree", memoryfree); + dump_mem_fact("memoryfree", memoryfree); } static string get_selinux_path() @@ -655,28 +656,28 @@ static void dump_physicalprocessorcount_fact() void dump_processorcount_fact() { - std::ifstream cpuinfo_file("/proc/cpuinfo", std::ifstream::in); - std::string line; - int processor_count = 0; - string current_processor_number; - while (std::getline(cpuinfo_file, line)) { - unsigned sep = line.find(":"); - string tmp = line.substr(0, sep); - string key = trim(tmp); - - if (key == "processor") { - ++processor_count; - string tmp = line.substr(sep + 1, string::npos); - current_processor_number = trim(tmp); - } - else if (key == "model name") { - string tmp = line.substr(sep + 1, string::npos); - cout << "processor" << current_processor_number << " => " << trim(tmp) << endl; - } + std::ifstream cpuinfo_file("/proc/cpuinfo", std::ifstream::in); + std::string line; + int processor_count = 0; + string current_processor_number; + while (std::getline(cpuinfo_file, line)) { + unsigned sep = line.find(":"); + string tmp = line.substr(0, sep); + string key = trim(tmp); + + if (key == "processor") { + ++processor_count; + string tmp = line.substr(sep + 1, string::npos); + current_processor_number = trim(tmp); + } + else if (key == "model name") { + string tmp = line.substr(sep + 1, string::npos); + cout << "processor" << current_processor_number << " => " << trim(tmp) << endl; } - // this was added after 1.7.3, omit for now, needs investigation - if (false) cout << "activeprocessorcount => " << processor_count << endl; - cout << "processorcount => " << processor_count << endl; + } + // this was added after 1.7.3, omit for now, needs investigation + if (false) cout << "activeprocessorcount => " << processor_count << endl; + cout << "processorcount => " << processor_count << endl; } void dump_processor_facts() @@ -702,141 +703,141 @@ void dump_architecture_facts() void dump_dmidecode_facts() { - // from a time perspective simulate expense with lspci and dmidecode invocations - string dmidecode_output = popen_stdout("/usr/sbin/dmidecode"); - std::stringstream ss(dmidecode_output); - string line; - - enum { - bios_information, - base_board_information, - system_information, - chassis_information, - unknown - } dmi_section = unknown; - - while (std::getline(ss, line)) { - if (line.empty()) continue; - - // enable case-insensitive compares - ci_string ci_line = line.c_str(); - - // identify the dmi section, they all begin at the beginning of a line - // and there are only a handful of interest to us - if (ci_line == "BIOS Information") { - dmi_section = bios_information; - continue; - } - else if (ci_line == "Base Board Information") { - dmi_section = base_board_information; - continue; - } - else if (ci_line == "System Information") { - dmi_section = system_information; - continue; - } - else if (ci_line == "Chassis Information" || ci_line == "system enclosure or chassis") { - dmi_section = chassis_information; - continue; + // from a time perspective simulate expense with lspci and dmidecode invocations + string dmidecode_output = popen_stdout("/usr/sbin/dmidecode"); + std::stringstream ss(dmidecode_output); + string line; + + enum { + bios_information, + base_board_information, + system_information, + chassis_information, + unknown + } dmi_section = unknown; + + while (std::getline(ss, line)) { + if (line.empty()) continue; + + // enable case-insensitive compares + ci_string ci_line = line.c_str(); + + // identify the dmi section, they all begin at the beginning of a line + // and there are only a handful of interest to us + if (ci_line == "BIOS Information") { + dmi_section = bios_information; + continue; + } + else if (ci_line == "Base Board Information") { + dmi_section = base_board_information; + continue; + } + else if (ci_line == "System Information") { + dmi_section = system_information; + continue; + } + else if (ci_line == "Chassis Information" || ci_line == "system enclosure or chassis") { + dmi_section = chassis_information; + continue; + } + else if (ci_line[0] >= 'A' && ci_line[0] <= 'Z') { + dmi_section = unknown; + continue; + } + + // if we're in the middle of an unknown section, skip + if (dmi_section == unknown) continue; + + size_t sep = line.find(":"); + if (sep != string::npos) { + string tmp = line.substr(0, sep); + string key = trim(tmp); + if (dmi_section == bios_information) { + ci_string ci_key = key.c_str(); + if (ci_key == "vendor") { + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); + cout << "bios_vendor => " << value << endl; + } + if (ci_key == "version") { + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); + cout << "bios_version => " << value << endl; + } + if (ci_key == "release date") { + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); + cout << "bios_release_date => " << value << endl; + } } - else if (ci_line[0] >= 'A' && ci_line[0] <= 'Z') { - dmi_section = unknown; - continue; + else if (dmi_section == base_board_information) { + ci_string ci_key = key.c_str(); + if (ci_key == "manufacturer") { + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); + cout << "boardmanufacturer => " << value << endl; + } + if (ci_key == "product name" || ci_key == "product") { + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); + cout << "boardproductname => " << value << endl; + } + if (ci_key == "serial number") { + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); + cout << "boardserialnumber => " << value << endl; + } } - - // if we're in the middle of an unknown section, skip - if (dmi_section == unknown) continue; - - size_t sep = line.find(":"); - if (sep != string::npos) { - string tmp = line.substr(0, sep); - string key = trim(tmp); - if (dmi_section == bios_information) { - ci_string ci_key = key.c_str(); - if (ci_key == "vendor") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); - cout << "bios_vendor => " << value << endl; - } - if (ci_key == "version") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); - cout << "bios_version => " << value << endl; - } - if (ci_key == "release date") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); - cout << "bios_release_date => " << value << endl; - } + else if (dmi_section == system_information) { + ci_string ci_key = key.c_str(); + if (ci_key == "manufacturer") { + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); + cout << "manufacturer => " << value << endl; } - else if (dmi_section == base_board_information) { - ci_string ci_key = key.c_str(); - if (ci_key == "manufacturer") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); - cout << "boardmanufacturer => " << value << endl; - } - if (ci_key == "product name" || ci_key == "product") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); - cout << "boardproductname => " << value << endl; - } - if (ci_key == "serial number") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); - cout << "boardserialnumber => " << value << endl; - } + if (ci_key == "product name" || ci_key == "product") { + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); + cout << "productname => " << value << endl; } - else if (dmi_section == system_information) { - ci_string ci_key = key.c_str(); - if (ci_key == "manufacturer") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); - cout << "manufacturer => " << value << endl; - } - if (ci_key == "product name" || ci_key == "product") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); - cout << "productname => " << value << endl; - } - if (ci_key == "serial number") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); - cout << "serialnumber => " << value << endl; - } - if (ci_key == "uuid") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); - cout << "uuid => " << value << endl; - } + if (ci_key == "serial number") { + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); + cout << "serialnumber => " << value << endl; } - else if (dmi_section == chassis_information) { - ci_string ci_key = key.c_str(); - if (ci_key == "chassis type" || ci_key == "type") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); - cout << "type => " << value << endl; - } + if (ci_key == "uuid") { + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); + cout << "uuid => " << value << endl; + } + } + else if (dmi_section == chassis_information) { + ci_string ci_key = key.c_str(); + if (ci_key == "chassis type" || ci_key == "type") { + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); + cout << "type => " << value << endl; } } } + } } void dump_filesystems_facts() { - std::ifstream cpuinfo_file("/proc/filesystems", std::ifstream::in); - std::string line; - string filesystems = ""; - while (std::getline(cpuinfo_file, line)) { - if (line.find("nodev") != string::npos || line.find("fuseblk") != string::npos) - continue; + std::ifstream cpuinfo_file("/proc/filesystems", std::ifstream::in); + std::string line; + string filesystems = ""; + while (std::getline(cpuinfo_file, line)) { + if (line.find("nodev") != string::npos || line.find("fuseblk") != string::npos) + continue; - if (!filesystems.empty()) - filesystems += ","; + if (!filesystems.empty()) + filesystems += ","; - filesystems += trim(line); - } - cout << "filesystems => " << filesystems << endl; + filesystems += trim(line); + } + cout << "filesystems => " << filesystems << endl; } void dump_hostname_facts() diff --git a/cfacterlib.h b/cfacterlib.h index 6fcbcadd18..b780ebb55f 100644 --- a/cfacterlib.h +++ b/cfacterlib.h @@ -1,20 +1,21 @@ extern "C" { -void dump_network_facts(); -void dump_kernel_facts(); -void dump_blockdevice_facts(); -void dump_operatingsystem_facts(); -void dump_uptime_facts(); -void dump_virtual_facts(); -void dump_hardwired_facts(); -void dump_misc_facts(); -void dump_ruby_lib_versions(); -void dump_mem_facts(); -void dump_selinux_facts(); -void dump_ssh_facts(); -void dump_processor_facts(); -void dump_architecture_facts(); -void dump_dmidecode_facts(); -void dump_filesystems_facts(); -void dump_hostname_facts(); + void dump_network_facts(); + void dump_kernel_facts(); + void dump_blockdevice_facts(); + void dump_operatingsystem_facts(); + void dump_uptime_facts(); + void dump_virtual_facts(); + void dump_hardwired_facts(); + void dump_misc_facts(); + void dump_ruby_lib_versions(); + void dump_mem_facts(); + void dump_selinux_facts(); + void dump_ssh_facts(); + void dump_processor_facts(); + void dump_architecture_facts(); + void dump_dmidecode_facts(); + void dump_filesystems_facts(); + void dump_hostname_facts(); + } From fbb9007a2b570a88a6e74e6a7d31f97b3bda5a2c Mon Sep 17 00:00:00 2001 From: Theo Chatzimichos Date: Sun, 6 Oct 2013 13:49:59 +0200 Subject: [PATCH 1443/3753] (#22789) Add vserver_host in physical_types virtual => vserver_host was incorrectly shown as is_virtual => true Adding it to physical_types variable so as it is properly shown as is_virtual => false --- lib/facter/virtual.rb | 2 +- spec/unit/virtual_spec.rb | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 98f8f25cf0..1f888f7cfe 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -250,7 +250,7 @@ confine :kernel => %w{Linux FreeBSD OpenBSD SunOS HP-UX Darwin GNU/kFreeBSD windows} setcode do - physical_types = %w{physical xen0 vmware_server vmware_workstation openvzhn} + physical_types = %w{physical xen0 vmware_server vmware_workstation openvzhn vserver_host} if physical_types.include? Facter.value(:virtual) "false" diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index 88e34240ea..c53c009280 100755 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -389,6 +389,12 @@ Facter.fact(:is_virtual).value.should == "true" end + it "should be true when running on vserver" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:virtual).stubs(:value).returns("vserver") + Facter.fact(:is_virtual).value.should == "true" + end + it "should be true when running on kvm" do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter.fact(:virtual).stubs(:value).returns("kvm") @@ -438,6 +444,12 @@ Facter.fact(:is_virtual).value.should == "false" end + it "should be false on vserver host nodes" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:virtual).stubs(:value).returns("vserver_host") + Facter.fact(:is_virtual).value.should == "false" + end + it "should be true when running on hyperv" do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter.fact(:virtual).stubs(:value).returns("hyperv") From 5c6ecde5e3a69609d3ff306f8ee1833a07d882f5 Mon Sep 17 00:00:00 2001 From: Josh Partlow Date: Thu, 10 Oct 2013 10:59:44 -0700 Subject: [PATCH 1444/3753] (maint) Add acceptance setup steps from puppet-acceptance This is so that we can run the acceptance tests with Beaker, which expects each project to manage all of its own setup process. --- acceptance/setup/01_TestSetup.rb | 40 ++++++++++++ acceptance/setup/02_PuppetWorkArounds.rb | 21 +++++++ acceptance/setup/03_PuppetMasterSanity.rb | 24 +++++++ acceptance/setup/04_ValidateSignCert.rb | 28 +++++++++ acceptance/setup/06_InstallModules.rb | 76 +++++++++++++++++++++++ 5 files changed, 189 insertions(+) create mode 100755 acceptance/setup/01_TestSetup.rb create mode 100644 acceptance/setup/02_PuppetWorkArounds.rb create mode 100755 acceptance/setup/03_PuppetMasterSanity.rb create mode 100755 acceptance/setup/04_ValidateSignCert.rb create mode 100644 acceptance/setup/06_InstallModules.rb diff --git a/acceptance/setup/01_TestSetup.rb b/acceptance/setup/01_TestSetup.rb new file mode 100755 index 0000000000..ee71151f2f --- /dev/null +++ b/acceptance/setup/01_TestSetup.rb @@ -0,0 +1,40 @@ +test_name "Install packages and repositories on target machines..." do + extend Beaker::DSL::InstallUtils + + SourcePath = Beaker::DSL::InstallUtils::SourcePath + GitURI = Beaker::DSL::InstallUtils::GitURI + GitHubSig = Beaker::DSL::InstallUtils::GitHubSig + + tmp_repositories = [] + options[:install].each do |uri| + raise(ArgumentError, "#{uri} is not recognized.") unless(uri =~ GitURI) + tmp_repositories << extract_repo_info_from(uri) + end + + repositories = order_packages(tmp_repositories) + + versions = {} + hosts.each_with_index do |host, index| + on host, "echo #{GitHubSig} >> $HOME/.ssh/known_hosts" + + repositories.each do |repository| + step "Install #{repository[:name]}" + install_from_git host, SourcePath, repository + + if index == 1 + versions[repository[:name]] = find_git_repo_versions(host, + SourcePath, + repository) + end + end + end + + step "Agents: create basic puppet.conf" do + agents.each do |agent| + puppetconf = File.join(agent['puppetpath'], 'puppet.conf') + + on agent, "echo '[agent]' > #{puppetconf} && " + + "echo server=#{master} >> #{puppetconf}" + end + end +end diff --git a/acceptance/setup/02_PuppetWorkArounds.rb b/acceptance/setup/02_PuppetWorkArounds.rb new file mode 100644 index 0000000000..d7b1f71a16 --- /dev/null +++ b/acceptance/setup/02_PuppetWorkArounds.rb @@ -0,0 +1,21 @@ +test_name 'work arounds for bugs' do + hosts.each do |host| + next if host['platform'].include? 'windows' + + step "REVISIT: see #9862, this step should not be required for agents" do + on host, "getent group puppet || groupadd puppet" + + if host['platform'].include? 'solaris' + useradd_opts = '-d /puppet -m -s /bin/sh -g puppet puppet' + else + useradd_opts = 'puppet -g puppet -G puppet' + end + + on host, "getent passwd puppet || useradd #{useradd_opts}" + end + + step "REVISIT: Work around bug #5794 not creating reports as required" do + on host, "mkdir -p /tmp/reports && chown puppet:puppet /tmp/reports" + end + end +end diff --git a/acceptance/setup/03_PuppetMasterSanity.rb b/acceptance/setup/03_PuppetMasterSanity.rb new file mode 100755 index 0000000000..6d1df4cae9 --- /dev/null +++ b/acceptance/setup/03_PuppetMasterSanity.rb @@ -0,0 +1,24 @@ +test_name "Puppet Master sanity checks: PID file and SSL dir creation" + +pidfile = '/var/lib/puppet/run/master.pid' + +hostname = on(master, 'facter hostname').stdout.strip +fqdn = on(master, 'facter fqdn').stdout.strip + +master_conf = { + :main => { + :dns_alt_names => "puppet,#{hostname},#{fqdn}", + :verbose => true, + :noop => true, + }, +} + +with_puppet_running_on(master, master_conf) do + # SSL dir created? + step "SSL dir created?" + on master, "[ -d #{master['puppetpath']}/ssl ]" + + # PID file exists? + step "PID file created?" + on master, "[ -f #{pidfile} ]" +end diff --git a/acceptance/setup/04_ValidateSignCert.rb b/acceptance/setup/04_ValidateSignCert.rb new file mode 100755 index 0000000000..7c9e309b5b --- /dev/null +++ b/acceptance/setup/04_ValidateSignCert.rb @@ -0,0 +1,28 @@ +test_name "Validate Sign Cert" + +hostname = on(master, 'facter hostname').stdout.strip +fqdn = on(master, 'facter fqdn').stdout.strip + +master_conf = { + :master => { + :dns_alt_names => "puppet,#{hostname},#{fqdn}", + }, +} + +step "Master: Start Puppet Master" +with_puppet_running_on(master, master_conf) do + hosts.each do |host| + next if host['roles'].include? 'master' + + step "Agents: Run agent --test first time to gen CSR" + on host, puppet_agent("--test"), :acceptable_exit_codes => [1] + end + + # Sign all waiting certs + step "Master: sign all certs" + on master, puppet_cert("--sign --all"), :acceptable_exit_codes => [0,24] + + step "Agents: Run agent --test second time to obtain signed cert" + on agents, puppet_agent("--test"), :acceptable_exit_codes => [0,2] +end + diff --git a/acceptance/setup/06_InstallModules.rb b/acceptance/setup/06_InstallModules.rb new file mode 100644 index 0000000000..00243010b2 --- /dev/null +++ b/acceptance/setup/06_InstallModules.rb @@ -0,0 +1,76 @@ +require 'pathname' + +# Given an array of modules specified by the --modules command line option, +# Parse all of them into an array of usable hash structures. +class PuppetModules + attr_reader :modules + + def initialize(modules=[]) + @modules = modules + end + + def list + return [] unless modules + modules.collect do |uri| + git_url, git_ref = uri.split '#' + folder = Pathname.new(git_url).basename('.git') + name = folder.to_s.split('-', 2)[1] || folder.to_s + { + :name => name, + :url => git_url, + :folder => folder.to_s, + :ref => git_ref, + :protocol => git_url.split(':')[0].intern, + } + end + end +end + +def install_git_module(mod, hosts) + # The idea here is that each test can symlink the modules they want from a + # temporary directory to this location. This will preserve the global + # state of the system while allowing individual test cases to quickly run + # with a module "installed" in the module path. + moddir = "/opt/puppet-git-repos" + target = "#{moddir}/#{mod[:name]}" + + step "Clone #{mod[:url]} if needed" + on hosts, "test -d #{moddir} || mkdir -p #{moddir}" + on hosts, "test -d #{target} || git clone #{mod[:url]} #{target}" + step "Update #{mod[:name]} and check out revision #{mod[:ref]}" + + commands = ["cd #{target}", + "remote rm origin", + "remote add origin #{mod[:url]}", + "fetch origin", + "checkout -f #{mod[:ref]}", + "reset --hard refs/remotes/origin/#{mod[:ref]}", + "clean -fdx", + ] + + on hosts, commands.join(" && git ") +end + +def install_scp_module(mod, hosts) + moddir = "/opt/puppet-git-repos" + target = "#{moddir}/#{mod[:name]}" + + step "Purge #{target} if needed" + on hosts, "test -d #{target} && rm -rf #{target} || true" + + step "Copy #{mod[:name]} to hosts" + scp_to hosts, mod[:url].split(':', 2)[1], target +end + +modules = PuppetModules.new(options[:modules]).list + +step "Masters: Install Puppet Modules" +masters = hosts.select { |host| host['roles'].include? 'master' } + +modules.each do |mod| + if mod[:protocol] == :scp + install_scp_module(mod, masters) + else + install_git_module(mod, masters) + end +end From 22f8e6cbfb91b0d7cbdef65b5872ff96bbb4d11b Mon Sep 17 00:00:00 2001 From: Joshua Hoblitt Date: Mon, 23 Sep 2013 14:17:47 -0700 Subject: [PATCH 1445/3753] (#22649) fixture sources for example /proc/cpuinfo files should be consolidated Merge the `/proc/cpuinfo` examples under `spec/fixtures/cpuinfo/` and `spec/fixtures/unit/physialprocessorcount/` into `spec/fixtures/cpuinfo/` and add a common set of access methods named `#cpuinfo_fixture_read` and `#cpuinfo_fixture_readlines` --- .../two_multicore | 0 .../two_singlecore | 0 .../unit/physicalprocessorcount/amd64dual | 57 ------------------- spec/unit/physicalprocessorcount_spec.rb | 15 ++++- spec/unit/processor_spec.rb | 11 +++- spec/unit/util/processor_spec.rb | 2 +- 6 files changed, 23 insertions(+), 62 deletions(-) rename spec/fixtures/{unit/physicalprocessorcount => cpuinfo}/two_multicore (100%) rename spec/fixtures/{unit/physicalprocessorcount => cpuinfo}/two_singlecore (100%) delete mode 100644 spec/fixtures/unit/physicalprocessorcount/amd64dual diff --git a/spec/fixtures/unit/physicalprocessorcount/two_multicore b/spec/fixtures/cpuinfo/two_multicore similarity index 100% rename from spec/fixtures/unit/physicalprocessorcount/two_multicore rename to spec/fixtures/cpuinfo/two_multicore diff --git a/spec/fixtures/unit/physicalprocessorcount/two_singlecore b/spec/fixtures/cpuinfo/two_singlecore similarity index 100% rename from spec/fixtures/unit/physicalprocessorcount/two_singlecore rename to spec/fixtures/cpuinfo/two_singlecore diff --git a/spec/fixtures/unit/physicalprocessorcount/amd64dual b/spec/fixtures/unit/physicalprocessorcount/amd64dual deleted file mode 100644 index 101e5b5c76..0000000000 --- a/spec/fixtures/unit/physicalprocessorcount/amd64dual +++ /dev/null @@ -1,57 +0,0 @@ -processor : 0 -vendor_id : GenuineIntel -cpu family : 6 -model : 23 -model name : Intel(R) Core(TM)2 Duo CPU P8700 @ 2.53GHz -stepping : 10 -cpu MHz : 689.302 -cache size : 6144 KB -physical id : 0 -siblings : 2 -core id : 0 -cpu cores : 2 -apicid : 0 -initial apicid : 0 -fdiv_bug : no -hlt_bug : no -f00f_bug : no -coma_bug : no -fpu : yes -fpu_exception : yes -cpuid level : 5 -wp : yes -flags : fpu vme de pse tsc msr mce cx8 apic mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht constant_tsc pni ssse3 -bogomips : 1378.60 -clflush size : 64 -cache_alignment : 64 -address sizes : 36 bits physical, 48 bits virtual -power management: - -processor : 1 -vendor_id : GenuineIntel -cpu family : 6 -model : 23 -model name : Intel(R) Core(TM)2 Duo CPU P8700 @ 2.53GHz -stepping : 10 -cpu MHz : 689.302 -cache size : 6144 KB -physical id : 0 -siblings : 2 -core id : 1 -cpu cores : 2 -apicid : 1 -initial apicid : 1 -fdiv_bug : no -hlt_bug : no -f00f_bug : no -coma_bug : no -fpu : yes -fpu_exception : yes -cpuid level : 5 -wp : yes -flags : fpu vme de pse tsc msr mce cx8 apic mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht constant_tsc pni ssse3 -bogomips : 1687.45 -clflush size : 64 -cache_alignment : 64 -address sizes : 36 bits physical, 48 bits virtual -power management: diff --git a/spec/unit/physicalprocessorcount_spec.rb b/spec/unit/physicalprocessorcount_spec.rb index edd9127f92..bc6c46e887 100755 --- a/spec/unit/physicalprocessorcount_spec.rb +++ b/spec/unit/physicalprocessorcount_spec.rb @@ -1,11 +1,14 @@ #! /usr/bin/env ruby require 'spec_helper' +require 'facter_spec/cpuinfo' require 'facter/util/posix' describe "Physical processor count facts" do describe "on linux" do + include FacterSpec::Cpuinfo + before :each do Facter.fact(:kernel).stubs(:value).returns("Linux") end @@ -82,19 +85,25 @@ end it "should return 1 physical CPU when there are multiple cores" do - @cpuinfo = my_fixture_read('amd64dual') + @cpuinfo = cpuinfo_fixture_read('amd64dual') File.stubs(:read).with("/proc/cpuinfo").returns(@cpuinfo) Facter.fact(:physicalprocessorcount).value.should == 1 end it "should return 2 physical CPUs when there are 2 singlecore CPUs" do - @cpuinfo = my_fixture_read('two_singlecore') + @cpuinfo = cpuinfo_fixture_read('two_singlecore') + File.stubs(:read).with("/proc/cpuinfo").returns(@cpuinfo) + Facter.fact(:physicalprocessorcount).value.should == 2 + end + + it "should return 2 physical CPUs when there are 2 multicore CPUs" do + @cpuinfo = cpuinfo_fixture_read('two_multicore') File.stubs(:read).with("/proc/cpuinfo").returns(@cpuinfo) Facter.fact(:physicalprocessorcount).value.should == 2 end it "should return 2 physical CPUs when there are 2 multicore CPUs" do - @cpuinfo = my_fixture_read('two_multicore') + @cpuinfo = cpuinfo_fixture_read('amd64twentyfour') File.stubs(:read).with("/proc/cpuinfo").returns(@cpuinfo) Facter.fact(:physicalprocessorcount).value.should == 2 end diff --git a/spec/unit/processor_spec.rb b/spec/unit/processor_spec.rb index 0927064fd8..2db1344e41 100755 --- a/spec/unit/processor_spec.rb +++ b/spec/unit/processor_spec.rb @@ -1,9 +1,9 @@ #! /usr/bin/env ruby require 'spec_helper' +require 'facter_spec/cpuinfo' require 'facter/util/posix' require 'facter/util/processor' -require 'facter_spec/cpuinfo' describe "Processor facts" do describe "on Windows" do @@ -141,6 +141,15 @@ def load(procs) it "should be 4 in amd64quad fixture on Linux" do Facter.fact(:architecture).stubs(:value).returns("amd64") File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture_readlines("amd64quad")) + + Facter.fact(processor_fact).value.should == "4" + end + + it "should be 24 in amd64twentyfour fixture on Linux" do + Facter.fact(:architecture).stubs(:value).returns("amd64") + File.stubs(:readlines).with("/proc/cpuinfo").returns(cpuinfo_fixture_readlines("amd64twentyfour")) + + Facter.fact(processor_fact).value.should == "24" end end diff --git a/spec/unit/util/processor_spec.rb b/spec/unit/util/processor_spec.rb index 5637a7b182..171125856e 100755 --- a/spec/unit/util/processor_spec.rb +++ b/spec/unit/util/processor_spec.rb @@ -1,8 +1,8 @@ #! /usr/bin/env ruby require 'spec_helper' -require 'facter/util/processor' require 'facter_spec/cpuinfo' +require 'facter/util/processor' describe Facter::Util::Processor do describe "on linux" do From fbeaca9ff9b81edcf5c217c5bd72d82f24954d94 Mon Sep 17 00:00:00 2001 From: Joshua Hoblitt Date: Sun, 13 Oct 2013 15:02:42 -0700 Subject: [PATCH 1446/3753] (#22857) officially support ruby 2.0 To corespond with puppet >= 3.2 Ruby 2.0 support: http://docs.puppetlabs.com/puppet/3/reference/release_notes.html#ruby-20-support --- .travis.yml | 1 - README.md | 7 +++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 52dfb9e672..25dc7b5012 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,4 +11,3 @@ rvm: matrix: allow_failures: - rvm: ruby-head - - rvm: 2.0.0 diff --git a/README.md b/README.md index 5bed36e772..07761ad4cc 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,13 @@ processors, etc. See `bin/facter` for an example of the interface. +Installation +------------ + +Generally, you need the following things installed: + +* A supported Ruby version. Ruby 1.8.7, 1.9.3, and 2.0.0 are fully supported. + Running Facter -------------- From 17de341e860e3a46a618c57d3363df6dfbb5ef67 Mon Sep 17 00:00:00 2001 From: ImRaptor Date: Tue, 15 Oct 2013 09:38:22 -0600 Subject: [PATCH 1447/3753] Include Bochs as Windows virtual machine type. Update virtual.rb to include Windows VM detection when virtual machine is detected as model 'Bochs' --- lib/facter/virtual.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 1f888f7cfe..4416261a6d 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -172,6 +172,8 @@ result = "vmware" when /KVM/ result = "kvm" + when /Bochs/ + result = "bochs" end if result.nil? and computersystem.manufacturer =~ /Xen/ From 2c048fb38f11f208079f313cbc84e1ad5f3d25e6 Mon Sep 17 00:00:00 2001 From: Rob Braden Date: Wed, 16 Oct 2013 17:23:51 -0700 Subject: [PATCH 1448/3753] (packaging) (#22726) Add saucy as a package build target --- ext/build_defaults.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/build_defaults.yaml b/ext/build_defaults.yaml index 7693ba6e9c..93769ac4a7 100644 --- a/ext/build_defaults.yaml +++ b/ext/build_defaults.yaml @@ -2,7 +2,7 @@ packaging_url: 'git://github.com/puppetlabs/packaging.git --branch=master' packaging_repo: 'packaging' default_cow: 'base-squeeze-i386.cow' -cows: 'base-lucid-i386.cow base-lucid-amd64.cow base-precise-i386.cow base-precise-amd64.cow base-quantal-i386.cow base-quantal-amd64.cow base-raring-i386.cow base-raring-amd64.cow base-sid-i386.cow base-sid-amd64.cow base-squeeze-i386.cow base-squeeze-amd64.cow base-stable-i386.cow base-stable-amd64.cow base-testing-i386.cow base-testing-amd64.cow base-unstable-i386.cow base-unstable-amd64.cow base-wheezy-i386.cow base-wheezy-amd64.cow' +cows: 'base-lucid-i386.cow base-lucid-amd64.cow base-precise-i386.cow base-precise-amd64.cow base-quantal-i386.cow base-quantal-amd64.cow base-raring-i386.cow base-raring-amd64.cow base-saucy-i386.cow base-saucy-amd64.cow base-sid-i386.cow base-sid-amd64.cow base-squeeze-i386.cow base-squeeze-amd64.cow base-stable-i386.cow base-stable-amd64.cow base-testing-i386.cow base-testing-amd64.cow base-unstable-i386.cow base-unstable-amd64.cow base-wheezy-i386.cow base-wheezy-amd64.cow' pbuild_conf: '/etc/pbuilderrc' packager: 'puppetlabs' gpg_name: 'info@puppetlabs.com' From 20a2de6e5a158f185e26b6ed6797de781ee874d6 Mon Sep 17 00:00:00 2001 From: John Julien Date: Tue, 15 Oct 2013 22:23:15 -0500 Subject: [PATCH 1449/3753] (#9546) Do not execute com, cmd, exe, or bat files if not on windows Facter currently only executes com, exe, and bat files if on a windows system. It should also exclude these on unix flavored systems since the interpreter for these "cmd.exe" will not be present. This also supports the use of pluginsync for external facts where in a hybrid environment external facts for windows may end up getting syncd to a unix system. --- lib/facter/util/parser.rb | 2 +- spec/unit/util/parser_spec.rb | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/lib/facter/util/parser.rb b/lib/facter/util/parser.rb index 4596c8fbdb..1b46908016 100644 --- a/lib/facter/util/parser.rb +++ b/lib/facter/util/parser.rb @@ -125,7 +125,7 @@ def results if Facter::Util::Config.is_windows? extension_matches?(filename, %w{bat cmd com exe}) && File.file?(filename) else - File.executable?(filename) && File.file?(filename) + File.executable?(filename) && File.file?(filename) && ! extension_matches?(filename, %w{bat cmd com exe}) end end diff --git a/spec/unit/util/parser_spec.rb b/spec/unit/util/parser_spec.rb index 00bba5d53a..11e55c137b 100644 --- a/spec/unit/util/parser_spec.rb +++ b/spec/unit/util/parser_spec.rb @@ -130,6 +130,28 @@ parser.results.should == data end end + + context "exe, bat, cmd, and com files" do + let :cmds do ["/tmp/foo.bat", "/tmp/foo.cmd", "/tmp/foo.exe", "/tmp/foo.com"] end + + before :each do + cmds.each {|cmd| + File.stubs(:executable?).with(cmd).returns(true) + File.stubs(:file?).with(cmd).returns(true) + } + end + + it "should return nothing parser if not on windows" do + Facter::Util::Config.stubs(:is_windows?).returns(false) + cmds.each {|cmd| Facter::Util::Parser.parser_for(cmd).should be_an_instance_of(Facter::Util::Parser::NothingParser) } + end + + it "should return script parser if on windows" do + Facter::Util::Config.stubs(:is_windows?).returns(true) + cmds.each {|cmd| Facter::Util::Parser.parser_for(cmd).should be_an_instance_of(Facter::Util::Parser::ScriptParser) } + end + + end end describe "powershell parser" do From 6caf1ce48fb77cb102abde47d13f16844b603d51 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Tue, 29 Oct 2013 14:44:21 -0700 Subject: [PATCH 1450/3753] (#22857) Note that ruby 2.0.0 should be at least p195. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e652a4e5bf..e5c998b397 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Installation Generally, you need the following things installed: -* A supported Ruby version. Ruby 1.8.7, 1.9.3, and 2.0.0 are fully supported. +* A supported Ruby version. Ruby 1.8.7, 1.9.3, and 2.0.0 (at least p195) are fully supported. Running Facter -------------- From 5d3aab2332a2a85f88f49ebcefba7f025bc73815 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Wed, 30 Oct 2013 13:28:44 -0700 Subject: [PATCH 1451/3753] (Maint) Pin windows gems to match what we ship The win32-dir gem has a dependency on ffi, which was recently updated to 1.9.3 but does not provide a precompiled version. As a result, the gem will attempt to compile native extensions, which will fail on our spec boxes (since the dev kit is purposefully not installed). This commit pins ffi to 1.9.0, updates win32-dir and windows-pr to the version that we ship with, and pins each gem to an exact version matching: https://github.com/puppetlabs/puppet-win32-ruby/tree/1.9.3/ruby/lib/ruby/gems --- Gemfile | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Gemfile b/Gemfile index 28fc38b19b..e4c7c8f1ae 100644 --- a/Gemfile +++ b/Gemfile @@ -21,12 +21,13 @@ group :development, :test do end platform :mswin, :mingw do - gem "sys-admin", "~> 1.5.6" - gem "win32-api", "~> 1.4.8" - gem "win32-dir", "~> 0.3.7" - gem "windows-api", "~> 0.4.1" - gem "windows-pr", "~> 1.2.1" - gem "win32console", "~> 1.3.2" + gem "ffi", "1.9.0", :require => false + gem "sys-admin", "1.5.6", :require => false + gem "win32-api", "1.4.8", :require => false + gem "win32-dir", "0.4.3", :require => false + gem "windows-api", "0.4.2", :require => false + gem "windows-pr", "1.2.2", :require => false + gem "win32console", "1.3.2", :require => false end gem 'facter', ">= 1.0.0", :path => File.expand_path("..", __FILE__) From 5acfe3a26496d4192ed16f21c46bbdcc5391fc07 Mon Sep 17 00:00:00 2001 From: Aran Cox Date: Fri, 31 May 2013 11:11:22 -0500 Subject: [PATCH 1452/3753] (#20994) fix incorrectly set memorysize on AIX using svmon (was set to 0) tested on AIX 5.3, 6.1, and 7.1 --- lib/facter/util/memory.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/facter/util/memory.rb b/lib/facter/util/memory.rb index 9a2401ea52..94b47a81b3 100644 --- a/lib/facter/util/memory.rb +++ b/lib/facter/util/memory.rb @@ -109,6 +109,10 @@ def self.mem_size_info(kernel = Facter.value(:kernel)) Facter::Util::Resolution.exec("sysctl -n hw.memsize") when /Dragonfly/i Facter::Util::Resolution.exec("sysctl -n hw.physmem") + when /AIX/i + if Facter::Util::Resolution.exec("/usr/bin/svmon -O unit=KB") =~ /^memory\s+(\d+)\s+/ + $1 + end end end @@ -116,6 +120,8 @@ def self.scale_mem_size_value(value, kernel) case kernel when /OpenBSD/i, /FreeBSD/i, /Darwin/i, /Dragonfly/i value.to_f / 1024.0 / 1024.0 + when /AIX/i + value.to_f / 1024.0 else value.to_f end From 1e7e36935618b059183a58f9338e0d60936aa0f9 Mon Sep 17 00:00:00 2001 From: Aran Cox Date: Fri, 31 May 2013 11:12:48 -0500 Subject: [PATCH 1453/3753] (#20994) fix incorrect memoryfree fact on AIX (was set to 0) tested on AIX 5.3, 6.1, and 7.1 --- lib/facter/util/memory.rb | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/facter/util/memory.rb b/lib/facter/util/memory.rb index 94b47a81b3..58dca4233f 100644 --- a/lib/facter/util/memory.rb +++ b/lib/facter/util/memory.rb @@ -67,6 +67,14 @@ def self.vmstat_darwin_find_free_memory() freemem = ( memfree + memspecfree ) * pagesize end + # on AIX use svmon to get the free memory: + # it's the third value on the line starting with memory + # svmon can be run by non root users + def self.svmon_aix_find_free_memory() + Facter::Util::Resolution.exec("/usr/bin/svmon -O unit=KB") =~ /^memory\s+\d+\s+\d+\s+(\d+)\s+/ + $1 + end + def self.mem_free(kernel = Facter.value(:kernel)) output = mem_free_info(kernel) scale_mem_free_value output, kernel @@ -80,12 +88,14 @@ def self.mem_free_info(kernel = Facter.value(:kernel)) vmstat_find_free_memory(["-H"]) when /Darwin/i vmstat_darwin_find_free_memory() + when /AIX/i + svmon_aix_find_free_memory() end end def self.scale_mem_free_value (value, kernel) case kernel - when /OpenBSD/i, /FreeBSD/i, /SunOS/i, /Dragonfly/i + when /OpenBSD/i, /FreeBSD/i, /SunOS/i, /Dragonfly/i, /AIX/i value.to_f / 1024.0 when /Darwin/i value.to_f / 1024.0 / 1024.0 From 9faf2c5875c25865b03a96f0016b04ff170a8fa9 Mon Sep 17 00:00:00 2001 From: Aran Cox Date: Sun, 2 Jun 2013 10:59:29 -0500 Subject: [PATCH 1454/3753] (#20994) add rspec tests for memoryfree and memorysize on AIX using svmon --- spec/unit/memory_spec.rb | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/spec/unit/memory_spec.rb b/spec/unit/memory_spec.rb index d249d5b01c..311a742d78 100755 --- a/spec/unit/memory_spec.rb +++ b/spec/unit/memory_spec.rb @@ -156,6 +156,20 @@ Facter::Util::Resolution.stubs(:exec).with('swap -l 2>/dev/null').returns(swapusage) + svmon = < Date: Tue, 12 Nov 2013 11:22:29 -0800 Subject: [PATCH 1455/3753] (#23135) Support deploying to Solaris and Windows vcloud machines Previously, the acceptance suite could only target linux boxen. This commit adds beaker configs for solaris and windows, and also makes the setup steps more cross-platform-friendly. --- acceptance/config/solaris-11.cfg | 22 ++++++++ acceptance/config/windows-2003.cfg | 22 ++++++++ .../lib/puppet/acceptance/install_utils.rb | 54 +++++++++++++++++++ acceptance/setup/00_EnvSetup.rb | 38 +++++++++++++ acceptance/setup/00_install_git.rb | 13 ----- 5 files changed, 136 insertions(+), 13 deletions(-) create mode 100644 acceptance/config/solaris-11.cfg create mode 100644 acceptance/config/windows-2003.cfg create mode 100644 acceptance/lib/puppet/acceptance/install_utils.rb create mode 100644 acceptance/setup/00_EnvSetup.rb delete mode 100644 acceptance/setup/00_install_git.rb diff --git a/acceptance/config/solaris-11.cfg b/acceptance/config/solaris-11.cfg new file mode 100644 index 0000000000..c9989fc759 --- /dev/null +++ b/acceptance/config/solaris-11.cfg @@ -0,0 +1,22 @@ +HOSTS: + centos-6-x86_64: + roles: + - master + - database + - agent + - dashboard + platform: el-6-x86_64 + hypervisor: vcloud + template: centos-6-x86_64 + solaris-11-x86_64: + roles: + - agent + platform: solaris-11-x86_64 + hypervisor: vcloud + template: solaris-11-x86_64 +CONFIG: + nfs_server: NONE + consoleport: 443 + datastore: instance0 + folder: Delivery/Quality Assurance/FOSS/Dynamic + resourcepool: delivery/Quality Assurance/FOSS/Dynamic diff --git a/acceptance/config/windows-2003.cfg b/acceptance/config/windows-2003.cfg new file mode 100644 index 0000000000..e8fd2c9ece --- /dev/null +++ b/acceptance/config/windows-2003.cfg @@ -0,0 +1,22 @@ +HOSTS: + centos-6-x86_64: + roles: + - master + - database + - agent + - dashboard + platform: el-6-x86_64 + hypervisor: vcloud + template: centos-6-x86_64 + win2003-32: + roles: + - agent + platform: windows-2003-32 + hypervisor: vcloud + template: win-2003-i386 +CONFIG: + nfs_server: NONE + consoleport: 443 + datastore: instance0 + folder: Delivery/Quality Assurance/FOSS/Dynamic + resourcepool: delivery/Quality Assurance/FOSS/Dynamic diff --git a/acceptance/lib/puppet/acceptance/install_utils.rb b/acceptance/lib/puppet/acceptance/install_utils.rb new file mode 100644 index 0000000000..cca5b7741b --- /dev/null +++ b/acceptance/lib/puppet/acceptance/install_utils.rb @@ -0,0 +1,54 @@ +require 'open-uri' + +module Puppet + module Acceptance + module InstallUtils + PLATFORM_PATTERNS = { + :redhat => /fedora|el|centos/, + :debian => /debian|ubuntu/, + :solaris => /solaris/, + :windows => /windows/, + }.freeze + + # Installs packages on the hosts. + # + # @param hosts [Array] Array of hosts to install packages to. + # @param package_hash [Hash{Symbol=>Array>}] + # Keys should be a symbol for a platform in PLATFORM_PATTERNS. Values + # should be an array of package names to install, or of two element + # arrays where a[0] is the command we expect to find on the platform + # and a[1] is the package name (when they are different). + # @param options [Hash{Symbol=>Boolean}] + # @option options [Boolean] :check_if_exists First check to see if + # command is present before installing package. (Default false) + # @return true + def install_packages_on(hosts, package_hash, options = {}) + check_if_exists = options[:check_if_exists] + hosts = [hosts] unless hosts.kind_of?(Array) + hosts.each do |host| + package_hash.each do |platform_key,package_list| + if pattern = PLATFORM_PATTERNS[platform_key] + if pattern.match(host['platform']) + package_list.each do |cmd_pkg| + if cmd_pkg.kind_of?(Array) + command, package = cmd_pkg + else + command = package = cmd_pkg + end + if !check_if_exists || !host.check_for_package(command) + host.logger.notify("Installing #{package}") + additional_switches = '--allow-unauthenticated' if platform_key == :debian + host.install_package(package, additional_switches) + end + end + end + else + raise("Unknown platform '#{platform_key}' in package_hash") + end + end + end + return true + end + end + end +end diff --git a/acceptance/setup/00_EnvSetup.rb b/acceptance/setup/00_EnvSetup.rb new file mode 100644 index 0000000000..5794013b71 --- /dev/null +++ b/acceptance/setup/00_EnvSetup.rb @@ -0,0 +1,38 @@ +test_name "Setup environment" + +step "Ensure Git and Ruby" + +require 'puppet/acceptance/install_utils' +extend Puppet::Acceptance::InstallUtils + +PACKAGES = { + :redhat => [ + 'git', + 'ruby', + ], + :debian => [ + ['git', 'git-core'], + 'ruby', + ], + :solaris => [ + ['git', 'developer/versioning/git'], + ['ruby', 'runtime/ruby-18'], + ], + :windows => [ + 'git', + ], +} + +install_packages_on(hosts, PACKAGES, :check_if_exists => true) + +hosts.each do |host| + if host['platform'] =~ /windows/ + on host, 'echo $PATH' + on host, 'git clone https://github.com/puppetlabs/puppet-win32-ruby' + on host, 'cp -r puppet-win32-ruby/ruby/* /' + on host, 'cd /lib; icacls ruby /grant "Everyone:(OI)(CI)(RX)"' + on host, 'cd /lib; icacls ruby /reset /T' + on host, 'ruby --version' + on host, 'cmd /c gem list' + end +end diff --git a/acceptance/setup/00_install_git.rb b/acceptance/setup/00_install_git.rb deleted file mode 100644 index 21a3f14b5e..0000000000 --- a/acceptance/setup/00_install_git.rb +++ /dev/null @@ -1,13 +0,0 @@ -test_name "Install Git" - -hosts.each do |host| - case host['platform'] - when /el-|fc-/ - step 'Installing Git' - on host, 'yum -y install git' - when /debian-|ubuntu-/ - step 'Installing Git' - on host, 'apt-get -y install git-core' - else - end -end From eeb40656536576d128e755d8df6c71e97f0f531f Mon Sep 17 00:00:00 2001 From: Branan Purvine-Riley Date: Thu, 14 Nov 2013 09:24:01 -0800 Subject: [PATCH 1456/3753] (maint) Recognize `fc` as a type of redhat platform in acceptance setup The beaker config file for fedora 18 in this project has a platform starting with `fc` instead of `fedora`. --- acceptance/lib/puppet/acceptance/install_utils.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acceptance/lib/puppet/acceptance/install_utils.rb b/acceptance/lib/puppet/acceptance/install_utils.rb index cca5b7741b..2d1c6c70a7 100644 --- a/acceptance/lib/puppet/acceptance/install_utils.rb +++ b/acceptance/lib/puppet/acceptance/install_utils.rb @@ -4,7 +4,7 @@ module Puppet module Acceptance module InstallUtils PLATFORM_PATTERNS = { - :redhat => /fedora|el|centos/, + :redhat => /fedora|fc|el|centos/, :debian => /debian|ubuntu/, :solaris => /solaris/, :windows => /windows/, From 3a7a2896274b1e608eed944d8bd6ffd9c5a7988e Mon Sep 17 00:00:00 2001 From: Branan Purvine-Riley Date: Thu, 14 Nov 2013 10:22:11 -0800 Subject: [PATCH 1457/3753] (maint) convert fc to fedora in acceptance configs Beaker cares about the platform for package installation, and does not recognize `fc` as a valid platform. The expected string is `fedora` --- acceptance/config/fedora-18.cfg | 4 ++-- acceptance/lib/puppet/acceptance/install_utils.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/acceptance/config/fedora-18.cfg b/acceptance/config/fedora-18.cfg index bfa0ee8ae9..15f467878c 100644 --- a/acceptance/config/fedora-18.cfg +++ b/acceptance/config/fedora-18.cfg @@ -4,13 +4,13 @@ HOSTS: - master - database - agent - platform: fc-18-x86_64 + platform: fedora-18-x86_64 template: fedora-18-x86_64 hypervisor: vcloud fedora-18-64-2: roles: - agent - platform: fc-18-x86_64 + platform: fedora-18-x86_64 template: fedora-18-x86_64 hypervisor: vcloud CONFIG: diff --git a/acceptance/lib/puppet/acceptance/install_utils.rb b/acceptance/lib/puppet/acceptance/install_utils.rb index 2d1c6c70a7..cca5b7741b 100644 --- a/acceptance/lib/puppet/acceptance/install_utils.rb +++ b/acceptance/lib/puppet/acceptance/install_utils.rb @@ -4,7 +4,7 @@ module Puppet module Acceptance module InstallUtils PLATFORM_PATTERNS = { - :redhat => /fedora|fc|el|centos/, + :redhat => /fedora|el|centos/, :debian => /debian|ubuntu/, :solaris => /solaris/, :windows => /windows/, From a55eacbedc57995011687edbb6aed89311de7885 Mon Sep 17 00:00:00 2001 From: Justin Stoller Date: Mon, 25 Nov 2013 13:49:51 -0800 Subject: [PATCH 1458/3753] use the pooling api --- acceptance/config/centos-5.cfg | 1 + acceptance/config/fedora-18.cfg | 1 + acceptance/config/redhat-6.cfg | 1 + acceptance/config/solaris-11.cfg | 1 + acceptance/config/ubuntu-1004.cfg | 1 + acceptance/config/windows-2003.cfg | 1 + 6 files changed, 6 insertions(+) diff --git a/acceptance/config/centos-5.cfg b/acceptance/config/centos-5.cfg index 0ec1d7c299..696b1a4f3b 100644 --- a/acceptance/config/centos-5.cfg +++ b/acceptance/config/centos-5.cfg @@ -19,3 +19,4 @@ CONFIG: datastore: instance0 folder: Delivery/Quality Assurance/FOSS/Dynamic resourcepool: delivery/Quality Assurance/FOSS/Dynamic + pooling_api: http://vcloud.delivery.puppetlabs.net/ diff --git a/acceptance/config/fedora-18.cfg b/acceptance/config/fedora-18.cfg index 15f467878c..ee6d8eb86d 100644 --- a/acceptance/config/fedora-18.cfg +++ b/acceptance/config/fedora-18.cfg @@ -19,3 +19,4 @@ CONFIG: datastore: instance0 folder: Delivery/Quality Assurance/FOSS/Dynamic resourcepool: delivery/Quality Assurance/FOSS/Dynamic + pooling_api: http://vcloud.delivery.puppetlabs.net/ diff --git a/acceptance/config/redhat-6.cfg b/acceptance/config/redhat-6.cfg index 0f8479fac5..79a4aefcc8 100644 --- a/acceptance/config/redhat-6.cfg +++ b/acceptance/config/redhat-6.cfg @@ -19,3 +19,4 @@ CONFIG: datastore: instance0 folder: Delivery/Quality Assurance/FOSS/Dynamic resourcepool: delivery/Quality Assurance/FOSS/Dynamic + pooling_api: http://vcloud.delivery.puppetlabs.net/ diff --git a/acceptance/config/solaris-11.cfg b/acceptance/config/solaris-11.cfg index c9989fc759..7604399ff5 100644 --- a/acceptance/config/solaris-11.cfg +++ b/acceptance/config/solaris-11.cfg @@ -20,3 +20,4 @@ CONFIG: datastore: instance0 folder: Delivery/Quality Assurance/FOSS/Dynamic resourcepool: delivery/Quality Assurance/FOSS/Dynamic + pooling_api: http://vcloud.delivery.puppetlabs.net/ diff --git a/acceptance/config/ubuntu-1004.cfg b/acceptance/config/ubuntu-1004.cfg index f3b7e91988..acf780d981 100644 --- a/acceptance/config/ubuntu-1004.cfg +++ b/acceptance/config/ubuntu-1004.cfg @@ -19,3 +19,4 @@ CONFIG: datastore: instance0 folder: Delivery/Quality Assurance/FOSS/Dynamic resourcepool: delivery/Quality Assurance/FOSS/Dynamic + pooling_api: http://vcloud.delivery.puppetlabs.net/ diff --git a/acceptance/config/windows-2003.cfg b/acceptance/config/windows-2003.cfg index e8fd2c9ece..f38c99f322 100644 --- a/acceptance/config/windows-2003.cfg +++ b/acceptance/config/windows-2003.cfg @@ -20,3 +20,4 @@ CONFIG: datastore: instance0 folder: Delivery/Quality Assurance/FOSS/Dynamic resourcepool: delivery/Quality Assurance/FOSS/Dynamic + pooling_api: http://vcloud.delivery.puppetlabs.net/ From 3e85716aafda375281ca96fc30a01ea87509d508 Mon Sep 17 00:00:00 2001 From: Justin Stoller Date: Wed, 27 Nov 2013 10:10:45 -0800 Subject: [PATCH 1459/3753] Use the same kind of gem source switching as Puppet --- Gemfile | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Gemfile b/Gemfile index 3b9989d010..61d2cd169b 100644 --- a/Gemfile +++ b/Gemfile @@ -1,8 +1,4 @@ -if gem_source = ENV['GEM_SOURCE'] - source gem_source -else - source "https://rubygems.org/" -end +source ENV['GEM_SOURCE'] || "https://rubygems.org/" # C Ruby (MRI) or Rubinius, but NOT Windows platforms :ruby do From 4664fbe08db58f6af5300d80ee00c5c595d0336e Mon Sep 17 00:00:00 2001 From: Rob Braden Date: Wed, 25 Sep 2013 11:27:32 -0700 Subject: [PATCH 1460/3753] (packaging) Pass --man to install.rb to get man pages in Debian lucid and older --- ext/debian/rules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/debian/rules b/ext/debian/rules index 6e2a6d5855..7dd2b79e82 100755 --- a/ext/debian/rules +++ b/ext/debian/rules @@ -7,4 +7,4 @@ LIBDIR=$(shell /usr/bin/ruby -rrbconfig -e 'puts RbConfig::CONFIG["vendordir"]') BINDIR=$(shell /usr/bin/ruby -rrbconfig -e 'puts RbConfig::CONFIG["bindir"]') binary-install/facter:: - /usr/bin/ruby install.rb --sitelibdir=$(LIBDIR) --bindir=$(BINDIR) --ruby=/usr/bin/ruby --destdir=$(CURDIR)/debian/$(cdbs_curpkg) --quick + /usr/bin/ruby install.rb --sitelibdir=$(LIBDIR) --bindir=$(BINDIR) --ruby=/usr/bin/ruby --destdir=$(CURDIR)/debian/$(cdbs_curpkg) --quick --man From d81601f269768b890e8a48705a4d67db18b3177d Mon Sep 17 00:00:00 2001 From: Iristyle Date: Mon, 2 Dec 2013 15:58:36 -0500 Subject: [PATCH 1461/3753] (#22622) External output defaults to empty string - Previously, if an external fact generated empty output, parse would be called with a nil, which cannot have each_line called on it, and would cause Facter to crash. This sets a reasonable default for external command output to the empty string --- lib/facter/util/parser.rb | 2 ++ spec/unit/util/parser_spec.rb | 15 ++++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/facter/util/parser.rb b/lib/facter/util/parser.rb index 1b46908016..7f9c514ed8 100644 --- a/lib/facter/util/parser.rb +++ b/lib/facter/util/parser.rb @@ -68,6 +68,8 @@ def parse_results module KeyValuePairOutputFormat def self.parse(output) + return {} if output.nil? + result = {} re = /^(.+?)=(.+)$/ output.each_line do |line| diff --git a/spec/unit/util/parser_spec.rb b/spec/unit/util/parser_spec.rb index 11e55c137b..ec53f60749 100644 --- a/spec/unit/util/parser_spec.rb +++ b/spec/unit/util/parser_spec.rb @@ -76,7 +76,7 @@ let(:data_in_txt) do "one=two\nthree=four\n" end it_behaves_like "txt parser" end - + context "extra equal sign" do let(:data_in_txt) do "one=two\nthree=four=five\n" end let(:data) do {"one" => "two", "three" => "four=five"} end @@ -109,6 +109,19 @@ Facter::Util::Parser.parser_for(cmd).results.should be_nil end + context "with nil external script output" do + # ensure NothingParser is not used on Windows by tricking parser registration + if Facter::Util::Config.is_windows? + let :cmd do "/tmp/foo.bat" end + end + let :data_in_txt do nil end + + it "should return an empty hash" do + File.stubs(:file?).with(cmd).returns(true) + Facter::Util::Parser.parser_for(cmd).results.should == {} + end + end + context "on Windows" do let :cmd do "/tmp/foo.bat" end From e6d76f8b2926a4830a79647daf18a7b0168c9add Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Thu, 5 Dec 2013 14:30:53 -0800 Subject: [PATCH 1462/3753] (Maint) Execute test on all agents using beaker's DSL method Previously, the test was only executing on the first agent host, and it attempted to run facter directly, which fails on windows when executing via ssh, because facter is a .bat file, not an executable. This commit updates the test to run on all agent hosts, and it uses beaker's built-in DSL method for executing facter, which ultimately executes: cmd /c facter --puppet test_fact1 It also uses beaker's method for passing environment variables to the command being executed. --- ...ket_7039_facter_multiple_facts_one_file.rb | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/acceptance/tests/ticket_7039_facter_multiple_facts_one_file.rb b/acceptance/tests/ticket_7039_facter_multiple_facts_one_file.rb index 76da0651a0..9a5a3e7f24 100644 --- a/acceptance/tests/ticket_7039_facter_multiple_facts_one_file.rb +++ b/acceptance/tests/ticket_7039_facter_multiple_facts_one_file.rb @@ -14,18 +14,21 @@ end } -agent1=agents.first -step "Agent: Create fact file with multiple facts" -create_remote_file(agent1, '/tmp/test_facts.rb', fact_file ) +agents.each do |agent| + step "Agent: Create fact file with multiple facts" + dir = agent.tmpdir('facter7039') + create_remote_file(agent, "#{dir}/test_facts.rb", fact_file) + env = { 'FACTERLIB' => dir } -step "Agent: Verify test_fact1 from /tmp/test_facts.rb" -on(agent1, "export FACTERLIB=/tmp && facter --puppet test_fact1") do - fail_test "Fact 1 not returned by facter --puppet test_fact1" unless - stdout.include? 'test fact 1' -end + step "Agent: Verify test_fact1 from #{dir}/test_facts.rb" + on(agent, facter('--puppet', 'test_fact1', :environment => env)) do + fail_test "Fact 1 not returned by facter --puppet test_fact1" unless + stdout.include? 'test fact 1' + end -step "Agent: Verify test_fact2 from /tmp/test_facts.rb" -on(agent1, "export FACTERLIB=/tmp && facter --puppet test_fact2") do - fail_test "Fact 1 not returned by facter --puppet test_fact2" unless - stdout.include? 'test fact 2' + step "Agent: Verify test_fact2 from #{dir}/test_facts.rb" + on(agent, facter('--puppet', 'test_fact2', :environment => env)) do + fail_test "Fact 1 not returned by facter --puppet test_fact2" unless + stdout.include? 'test fact 2' + end end From 71f3aa1f3ffe6a67039692e675aa9a05cd66e58e Mon Sep 17 00:00:00 2001 From: Scott Schneider Date: Fri, 12 Jul 2013 10:03:39 -0700 Subject: [PATCH 1463/3753] Adding vCloud node configs --- acceptance/config/centos-5.cfg | 21 +++++++++++++++++++++ acceptance/config/fedora-18.cfg | 21 +++++++++++++++++++++ acceptance/config/redhat-6.cfg | 21 +++++++++++++++++++++ acceptance/config/ubuntu-1004.cfg | 21 +++++++++++++++++++++ 4 files changed, 84 insertions(+) create mode 100644 acceptance/config/centos-5.cfg create mode 100644 acceptance/config/fedora-18.cfg create mode 100644 acceptance/config/redhat-6.cfg create mode 100644 acceptance/config/ubuntu-1004.cfg diff --git a/acceptance/config/centos-5.cfg b/acceptance/config/centos-5.cfg new file mode 100644 index 0000000000..0ec1d7c299 --- /dev/null +++ b/acceptance/config/centos-5.cfg @@ -0,0 +1,21 @@ +HOSTS: + centos-55-64-1: + roles: + - master + - database + - agent + platform: el-5-x86_64 + template: centos-5-x86_64 + hypervisor: vcloud + centos-55-386-1: + roles: + - agent + platform: el-5-i386 + template: centos-5-i386 + hypervisor: vcloud +CONFIG: + nfs_server: none + consoleport: 443 + datastore: instance0 + folder: Delivery/Quality Assurance/FOSS/Dynamic + resourcepool: delivery/Quality Assurance/FOSS/Dynamic diff --git a/acceptance/config/fedora-18.cfg b/acceptance/config/fedora-18.cfg new file mode 100644 index 0000000000..bfa0ee8ae9 --- /dev/null +++ b/acceptance/config/fedora-18.cfg @@ -0,0 +1,21 @@ +HOSTS: + fedora-18-64-1: + roles: + - master + - database + - agent + platform: fc-18-x86_64 + template: fedora-18-x86_64 + hypervisor: vcloud + fedora-18-64-2: + roles: + - agent + platform: fc-18-x86_64 + template: fedora-18-x86_64 + hypervisor: vcloud +CONFIG: + nfs_server: none + consoleport: 443 + datastore: instance0 + folder: Delivery/Quality Assurance/FOSS/Dynamic + resourcepool: delivery/Quality Assurance/FOSS/Dynamic diff --git a/acceptance/config/redhat-6.cfg b/acceptance/config/redhat-6.cfg new file mode 100644 index 0000000000..0f8479fac5 --- /dev/null +++ b/acceptance/config/redhat-6.cfg @@ -0,0 +1,21 @@ +HOSTS: + rhel-6-latest-64-1: + roles: + - master + - database + - agent + platform: el-6-x86_64 + template: redhat-6-x86_64 + hypervisor: vcloud + rhel-6-latest-32-1: + roles: + - agent + platform: el-6-i386 + template: redhat-6-i386 + hypervisor: vcloud +CONFIG: + nfs_server: none + consoleport: 443 + datastore: instance0 + folder: Delivery/Quality Assurance/FOSS/Dynamic + resourcepool: delivery/Quality Assurance/FOSS/Dynamic diff --git a/acceptance/config/ubuntu-1004.cfg b/acceptance/config/ubuntu-1004.cfg new file mode 100644 index 0000000000..f3b7e91988 --- /dev/null +++ b/acceptance/config/ubuntu-1004.cfg @@ -0,0 +1,21 @@ +HOSTS: + ubuntu-1004-64-1: + roles: + - master + - database + - agent + platform: ubuntu-10.04-amd64 + template: ubuntu-1004-x86_64 + hypervisor: vcloud + ubuntu-1004-32-1: + roles: + - agent + platform: ubuntu-10.04-i386 + template: ubuntu-1004-i386 + hypervisor: vcloud +CONFIG: + nfs_server: none + consoleport: 443 + datastore: instance0 + folder: Delivery/Quality Assurance/FOSS/Dynamic + resourcepool: delivery/Quality Assurance/FOSS/Dynamic From f43bc1c831ab4fc2f48326ddf328ce1495a9a898 Mon Sep 17 00:00:00 2001 From: Scott Schneider Date: Fri, 12 Jul 2013 10:03:45 -0700 Subject: [PATCH 1464/3753] Setup script to install Git on vCloud hosts --- acceptance/setup/00_install_git.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 acceptance/setup/00_install_git.rb diff --git a/acceptance/setup/00_install_git.rb b/acceptance/setup/00_install_git.rb new file mode 100644 index 0000000000..21a3f14b5e --- /dev/null +++ b/acceptance/setup/00_install_git.rb @@ -0,0 +1,13 @@ +test_name "Install Git" + +hosts.each do |host| + case host['platform'] + when /el-|fc-/ + step 'Installing Git' + on host, 'yum -y install git' + when /debian-|ubuntu-/ + step 'Installing Git' + on host, 'apt-get -y install git-core' + else + end +end From 50ae44ab8607e67bac1c16a50f07f194ff7ed1ca Mon Sep 17 00:00:00 2001 From: Josh Partlow Date: Thu, 10 Oct 2013 10:59:44 -0700 Subject: [PATCH 1465/3753] (maint) Add acceptance setup steps from puppet-acceptance This is so that we can run the acceptance tests with Beaker, which expects each project to manage all of its own setup process. --- acceptance/setup/01_TestSetup.rb | 40 ++++++++++++ acceptance/setup/02_PuppetWorkArounds.rb | 21 +++++++ acceptance/setup/03_PuppetMasterSanity.rb | 24 +++++++ acceptance/setup/04_ValidateSignCert.rb | 28 +++++++++ acceptance/setup/06_InstallModules.rb | 76 +++++++++++++++++++++++ 5 files changed, 189 insertions(+) create mode 100755 acceptance/setup/01_TestSetup.rb create mode 100644 acceptance/setup/02_PuppetWorkArounds.rb create mode 100755 acceptance/setup/03_PuppetMasterSanity.rb create mode 100755 acceptance/setup/04_ValidateSignCert.rb create mode 100644 acceptance/setup/06_InstallModules.rb diff --git a/acceptance/setup/01_TestSetup.rb b/acceptance/setup/01_TestSetup.rb new file mode 100755 index 0000000000..ee71151f2f --- /dev/null +++ b/acceptance/setup/01_TestSetup.rb @@ -0,0 +1,40 @@ +test_name "Install packages and repositories on target machines..." do + extend Beaker::DSL::InstallUtils + + SourcePath = Beaker::DSL::InstallUtils::SourcePath + GitURI = Beaker::DSL::InstallUtils::GitURI + GitHubSig = Beaker::DSL::InstallUtils::GitHubSig + + tmp_repositories = [] + options[:install].each do |uri| + raise(ArgumentError, "#{uri} is not recognized.") unless(uri =~ GitURI) + tmp_repositories << extract_repo_info_from(uri) + end + + repositories = order_packages(tmp_repositories) + + versions = {} + hosts.each_with_index do |host, index| + on host, "echo #{GitHubSig} >> $HOME/.ssh/known_hosts" + + repositories.each do |repository| + step "Install #{repository[:name]}" + install_from_git host, SourcePath, repository + + if index == 1 + versions[repository[:name]] = find_git_repo_versions(host, + SourcePath, + repository) + end + end + end + + step "Agents: create basic puppet.conf" do + agents.each do |agent| + puppetconf = File.join(agent['puppetpath'], 'puppet.conf') + + on agent, "echo '[agent]' > #{puppetconf} && " + + "echo server=#{master} >> #{puppetconf}" + end + end +end diff --git a/acceptance/setup/02_PuppetWorkArounds.rb b/acceptance/setup/02_PuppetWorkArounds.rb new file mode 100644 index 0000000000..d7b1f71a16 --- /dev/null +++ b/acceptance/setup/02_PuppetWorkArounds.rb @@ -0,0 +1,21 @@ +test_name 'work arounds for bugs' do + hosts.each do |host| + next if host['platform'].include? 'windows' + + step "REVISIT: see #9862, this step should not be required for agents" do + on host, "getent group puppet || groupadd puppet" + + if host['platform'].include? 'solaris' + useradd_opts = '-d /puppet -m -s /bin/sh -g puppet puppet' + else + useradd_opts = 'puppet -g puppet -G puppet' + end + + on host, "getent passwd puppet || useradd #{useradd_opts}" + end + + step "REVISIT: Work around bug #5794 not creating reports as required" do + on host, "mkdir -p /tmp/reports && chown puppet:puppet /tmp/reports" + end + end +end diff --git a/acceptance/setup/03_PuppetMasterSanity.rb b/acceptance/setup/03_PuppetMasterSanity.rb new file mode 100755 index 0000000000..6d1df4cae9 --- /dev/null +++ b/acceptance/setup/03_PuppetMasterSanity.rb @@ -0,0 +1,24 @@ +test_name "Puppet Master sanity checks: PID file and SSL dir creation" + +pidfile = '/var/lib/puppet/run/master.pid' + +hostname = on(master, 'facter hostname').stdout.strip +fqdn = on(master, 'facter fqdn').stdout.strip + +master_conf = { + :main => { + :dns_alt_names => "puppet,#{hostname},#{fqdn}", + :verbose => true, + :noop => true, + }, +} + +with_puppet_running_on(master, master_conf) do + # SSL dir created? + step "SSL dir created?" + on master, "[ -d #{master['puppetpath']}/ssl ]" + + # PID file exists? + step "PID file created?" + on master, "[ -f #{pidfile} ]" +end diff --git a/acceptance/setup/04_ValidateSignCert.rb b/acceptance/setup/04_ValidateSignCert.rb new file mode 100755 index 0000000000..7c9e309b5b --- /dev/null +++ b/acceptance/setup/04_ValidateSignCert.rb @@ -0,0 +1,28 @@ +test_name "Validate Sign Cert" + +hostname = on(master, 'facter hostname').stdout.strip +fqdn = on(master, 'facter fqdn').stdout.strip + +master_conf = { + :master => { + :dns_alt_names => "puppet,#{hostname},#{fqdn}", + }, +} + +step "Master: Start Puppet Master" +with_puppet_running_on(master, master_conf) do + hosts.each do |host| + next if host['roles'].include? 'master' + + step "Agents: Run agent --test first time to gen CSR" + on host, puppet_agent("--test"), :acceptable_exit_codes => [1] + end + + # Sign all waiting certs + step "Master: sign all certs" + on master, puppet_cert("--sign --all"), :acceptable_exit_codes => [0,24] + + step "Agents: Run agent --test second time to obtain signed cert" + on agents, puppet_agent("--test"), :acceptable_exit_codes => [0,2] +end + diff --git a/acceptance/setup/06_InstallModules.rb b/acceptance/setup/06_InstallModules.rb new file mode 100644 index 0000000000..00243010b2 --- /dev/null +++ b/acceptance/setup/06_InstallModules.rb @@ -0,0 +1,76 @@ +require 'pathname' + +# Given an array of modules specified by the --modules command line option, +# Parse all of them into an array of usable hash structures. +class PuppetModules + attr_reader :modules + + def initialize(modules=[]) + @modules = modules + end + + def list + return [] unless modules + modules.collect do |uri| + git_url, git_ref = uri.split '#' + folder = Pathname.new(git_url).basename('.git') + name = folder.to_s.split('-', 2)[1] || folder.to_s + { + :name => name, + :url => git_url, + :folder => folder.to_s, + :ref => git_ref, + :protocol => git_url.split(':')[0].intern, + } + end + end +end + +def install_git_module(mod, hosts) + # The idea here is that each test can symlink the modules they want from a + # temporary directory to this location. This will preserve the global + # state of the system while allowing individual test cases to quickly run + # with a module "installed" in the module path. + moddir = "/opt/puppet-git-repos" + target = "#{moddir}/#{mod[:name]}" + + step "Clone #{mod[:url]} if needed" + on hosts, "test -d #{moddir} || mkdir -p #{moddir}" + on hosts, "test -d #{target} || git clone #{mod[:url]} #{target}" + step "Update #{mod[:name]} and check out revision #{mod[:ref]}" + + commands = ["cd #{target}", + "remote rm origin", + "remote add origin #{mod[:url]}", + "fetch origin", + "checkout -f #{mod[:ref]}", + "reset --hard refs/remotes/origin/#{mod[:ref]}", + "clean -fdx", + ] + + on hosts, commands.join(" && git ") +end + +def install_scp_module(mod, hosts) + moddir = "/opt/puppet-git-repos" + target = "#{moddir}/#{mod[:name]}" + + step "Purge #{target} if needed" + on hosts, "test -d #{target} && rm -rf #{target} || true" + + step "Copy #{mod[:name]} to hosts" + scp_to hosts, mod[:url].split(':', 2)[1], target +end + +modules = PuppetModules.new(options[:modules]).list + +step "Masters: Install Puppet Modules" +masters = hosts.select { |host| host['roles'].include? 'master' } + +modules.each do |mod| + if mod[:protocol] == :scp + install_scp_module(mod, masters) + else + install_git_module(mod, masters) + end +end From 681de633d2250fd967816102b05e423fee30d1f3 Mon Sep 17 00:00:00 2001 From: Branan Purvine-Riley Date: Tue, 12 Nov 2013 11:22:29 -0800 Subject: [PATCH 1466/3753] (#23135) Support deploying to Solaris and Windows vcloud machines Previously, the acceptance suite could only target linux boxen. This commit adds beaker configs for solaris and windows, and also makes the setup steps more cross-platform-friendly. --- acceptance/config/solaris-11.cfg | 22 ++++++++ acceptance/config/windows-2003.cfg | 22 ++++++++ .../lib/puppet/acceptance/install_utils.rb | 54 +++++++++++++++++++ acceptance/setup/00_EnvSetup.rb | 38 +++++++++++++ acceptance/setup/00_install_git.rb | 13 ----- 5 files changed, 136 insertions(+), 13 deletions(-) create mode 100644 acceptance/config/solaris-11.cfg create mode 100644 acceptance/config/windows-2003.cfg create mode 100644 acceptance/lib/puppet/acceptance/install_utils.rb create mode 100644 acceptance/setup/00_EnvSetup.rb delete mode 100644 acceptance/setup/00_install_git.rb diff --git a/acceptance/config/solaris-11.cfg b/acceptance/config/solaris-11.cfg new file mode 100644 index 0000000000..c9989fc759 --- /dev/null +++ b/acceptance/config/solaris-11.cfg @@ -0,0 +1,22 @@ +HOSTS: + centos-6-x86_64: + roles: + - master + - database + - agent + - dashboard + platform: el-6-x86_64 + hypervisor: vcloud + template: centos-6-x86_64 + solaris-11-x86_64: + roles: + - agent + platform: solaris-11-x86_64 + hypervisor: vcloud + template: solaris-11-x86_64 +CONFIG: + nfs_server: NONE + consoleport: 443 + datastore: instance0 + folder: Delivery/Quality Assurance/FOSS/Dynamic + resourcepool: delivery/Quality Assurance/FOSS/Dynamic diff --git a/acceptance/config/windows-2003.cfg b/acceptance/config/windows-2003.cfg new file mode 100644 index 0000000000..e8fd2c9ece --- /dev/null +++ b/acceptance/config/windows-2003.cfg @@ -0,0 +1,22 @@ +HOSTS: + centos-6-x86_64: + roles: + - master + - database + - agent + - dashboard + platform: el-6-x86_64 + hypervisor: vcloud + template: centos-6-x86_64 + win2003-32: + roles: + - agent + platform: windows-2003-32 + hypervisor: vcloud + template: win-2003-i386 +CONFIG: + nfs_server: NONE + consoleport: 443 + datastore: instance0 + folder: Delivery/Quality Assurance/FOSS/Dynamic + resourcepool: delivery/Quality Assurance/FOSS/Dynamic diff --git a/acceptance/lib/puppet/acceptance/install_utils.rb b/acceptance/lib/puppet/acceptance/install_utils.rb new file mode 100644 index 0000000000..cca5b7741b --- /dev/null +++ b/acceptance/lib/puppet/acceptance/install_utils.rb @@ -0,0 +1,54 @@ +require 'open-uri' + +module Puppet + module Acceptance + module InstallUtils + PLATFORM_PATTERNS = { + :redhat => /fedora|el|centos/, + :debian => /debian|ubuntu/, + :solaris => /solaris/, + :windows => /windows/, + }.freeze + + # Installs packages on the hosts. + # + # @param hosts [Array] Array of hosts to install packages to. + # @param package_hash [Hash{Symbol=>Array>}] + # Keys should be a symbol for a platform in PLATFORM_PATTERNS. Values + # should be an array of package names to install, or of two element + # arrays where a[0] is the command we expect to find on the platform + # and a[1] is the package name (when they are different). + # @param options [Hash{Symbol=>Boolean}] + # @option options [Boolean] :check_if_exists First check to see if + # command is present before installing package. (Default false) + # @return true + def install_packages_on(hosts, package_hash, options = {}) + check_if_exists = options[:check_if_exists] + hosts = [hosts] unless hosts.kind_of?(Array) + hosts.each do |host| + package_hash.each do |platform_key,package_list| + if pattern = PLATFORM_PATTERNS[platform_key] + if pattern.match(host['platform']) + package_list.each do |cmd_pkg| + if cmd_pkg.kind_of?(Array) + command, package = cmd_pkg + else + command = package = cmd_pkg + end + if !check_if_exists || !host.check_for_package(command) + host.logger.notify("Installing #{package}") + additional_switches = '--allow-unauthenticated' if platform_key == :debian + host.install_package(package, additional_switches) + end + end + end + else + raise("Unknown platform '#{platform_key}' in package_hash") + end + end + end + return true + end + end + end +end diff --git a/acceptance/setup/00_EnvSetup.rb b/acceptance/setup/00_EnvSetup.rb new file mode 100644 index 0000000000..5794013b71 --- /dev/null +++ b/acceptance/setup/00_EnvSetup.rb @@ -0,0 +1,38 @@ +test_name "Setup environment" + +step "Ensure Git and Ruby" + +require 'puppet/acceptance/install_utils' +extend Puppet::Acceptance::InstallUtils + +PACKAGES = { + :redhat => [ + 'git', + 'ruby', + ], + :debian => [ + ['git', 'git-core'], + 'ruby', + ], + :solaris => [ + ['git', 'developer/versioning/git'], + ['ruby', 'runtime/ruby-18'], + ], + :windows => [ + 'git', + ], +} + +install_packages_on(hosts, PACKAGES, :check_if_exists => true) + +hosts.each do |host| + if host['platform'] =~ /windows/ + on host, 'echo $PATH' + on host, 'git clone https://github.com/puppetlabs/puppet-win32-ruby' + on host, 'cp -r puppet-win32-ruby/ruby/* /' + on host, 'cd /lib; icacls ruby /grant "Everyone:(OI)(CI)(RX)"' + on host, 'cd /lib; icacls ruby /reset /T' + on host, 'ruby --version' + on host, 'cmd /c gem list' + end +end diff --git a/acceptance/setup/00_install_git.rb b/acceptance/setup/00_install_git.rb deleted file mode 100644 index 21a3f14b5e..0000000000 --- a/acceptance/setup/00_install_git.rb +++ /dev/null @@ -1,13 +0,0 @@ -test_name "Install Git" - -hosts.each do |host| - case host['platform'] - when /el-|fc-/ - step 'Installing Git' - on host, 'yum -y install git' - when /debian-|ubuntu-/ - step 'Installing Git' - on host, 'apt-get -y install git-core' - else - end -end From eadd3682b1cf5fa06c44951656f0334f23d4b99f Mon Sep 17 00:00:00 2001 From: Branan Purvine-Riley Date: Thu, 14 Nov 2013 09:24:01 -0800 Subject: [PATCH 1467/3753] (maint) Recognize `fc` as a type of redhat platform in acceptance setup The beaker config file for fedora 18 in this project has a platform starting with `fc` instead of `fedora`. --- acceptance/lib/puppet/acceptance/install_utils.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acceptance/lib/puppet/acceptance/install_utils.rb b/acceptance/lib/puppet/acceptance/install_utils.rb index cca5b7741b..2d1c6c70a7 100644 --- a/acceptance/lib/puppet/acceptance/install_utils.rb +++ b/acceptance/lib/puppet/acceptance/install_utils.rb @@ -4,7 +4,7 @@ module Puppet module Acceptance module InstallUtils PLATFORM_PATTERNS = { - :redhat => /fedora|el|centos/, + :redhat => /fedora|fc|el|centos/, :debian => /debian|ubuntu/, :solaris => /solaris/, :windows => /windows/, From 45642409ebe5f15c82208b4c5e065e943c5b021e Mon Sep 17 00:00:00 2001 From: Branan Purvine-Riley Date: Thu, 14 Nov 2013 10:22:11 -0800 Subject: [PATCH 1468/3753] (maint) convert fc to fedora in acceptance configs Beaker cares about the platform for package installation, and does not recognize `fc` as a valid platform. The expected string is `fedora` --- acceptance/config/fedora-18.cfg | 4 ++-- acceptance/lib/puppet/acceptance/install_utils.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/acceptance/config/fedora-18.cfg b/acceptance/config/fedora-18.cfg index bfa0ee8ae9..15f467878c 100644 --- a/acceptance/config/fedora-18.cfg +++ b/acceptance/config/fedora-18.cfg @@ -4,13 +4,13 @@ HOSTS: - master - database - agent - platform: fc-18-x86_64 + platform: fedora-18-x86_64 template: fedora-18-x86_64 hypervisor: vcloud fedora-18-64-2: roles: - agent - platform: fc-18-x86_64 + platform: fedora-18-x86_64 template: fedora-18-x86_64 hypervisor: vcloud CONFIG: diff --git a/acceptance/lib/puppet/acceptance/install_utils.rb b/acceptance/lib/puppet/acceptance/install_utils.rb index 2d1c6c70a7..cca5b7741b 100644 --- a/acceptance/lib/puppet/acceptance/install_utils.rb +++ b/acceptance/lib/puppet/acceptance/install_utils.rb @@ -4,7 +4,7 @@ module Puppet module Acceptance module InstallUtils PLATFORM_PATTERNS = { - :redhat => /fedora|fc|el|centos/, + :redhat => /fedora|el|centos/, :debian => /debian|ubuntu/, :solaris => /solaris/, :windows => /windows/, From b49647e90352a47b7b8d28f675fadf6c87db07e5 Mon Sep 17 00:00:00 2001 From: Justin Stoller Date: Mon, 25 Nov 2013 13:49:51 -0800 Subject: [PATCH 1469/3753] use the pooling api --- acceptance/config/centos-5.cfg | 1 + acceptance/config/fedora-18.cfg | 1 + acceptance/config/redhat-6.cfg | 1 + acceptance/config/solaris-11.cfg | 1 + acceptance/config/ubuntu-1004.cfg | 1 + acceptance/config/windows-2003.cfg | 1 + 6 files changed, 6 insertions(+) diff --git a/acceptance/config/centos-5.cfg b/acceptance/config/centos-5.cfg index 0ec1d7c299..696b1a4f3b 100644 --- a/acceptance/config/centos-5.cfg +++ b/acceptance/config/centos-5.cfg @@ -19,3 +19,4 @@ CONFIG: datastore: instance0 folder: Delivery/Quality Assurance/FOSS/Dynamic resourcepool: delivery/Quality Assurance/FOSS/Dynamic + pooling_api: http://vcloud.delivery.puppetlabs.net/ diff --git a/acceptance/config/fedora-18.cfg b/acceptance/config/fedora-18.cfg index 15f467878c..ee6d8eb86d 100644 --- a/acceptance/config/fedora-18.cfg +++ b/acceptance/config/fedora-18.cfg @@ -19,3 +19,4 @@ CONFIG: datastore: instance0 folder: Delivery/Quality Assurance/FOSS/Dynamic resourcepool: delivery/Quality Assurance/FOSS/Dynamic + pooling_api: http://vcloud.delivery.puppetlabs.net/ diff --git a/acceptance/config/redhat-6.cfg b/acceptance/config/redhat-6.cfg index 0f8479fac5..79a4aefcc8 100644 --- a/acceptance/config/redhat-6.cfg +++ b/acceptance/config/redhat-6.cfg @@ -19,3 +19,4 @@ CONFIG: datastore: instance0 folder: Delivery/Quality Assurance/FOSS/Dynamic resourcepool: delivery/Quality Assurance/FOSS/Dynamic + pooling_api: http://vcloud.delivery.puppetlabs.net/ diff --git a/acceptance/config/solaris-11.cfg b/acceptance/config/solaris-11.cfg index c9989fc759..7604399ff5 100644 --- a/acceptance/config/solaris-11.cfg +++ b/acceptance/config/solaris-11.cfg @@ -20,3 +20,4 @@ CONFIG: datastore: instance0 folder: Delivery/Quality Assurance/FOSS/Dynamic resourcepool: delivery/Quality Assurance/FOSS/Dynamic + pooling_api: http://vcloud.delivery.puppetlabs.net/ diff --git a/acceptance/config/ubuntu-1004.cfg b/acceptance/config/ubuntu-1004.cfg index f3b7e91988..acf780d981 100644 --- a/acceptance/config/ubuntu-1004.cfg +++ b/acceptance/config/ubuntu-1004.cfg @@ -19,3 +19,4 @@ CONFIG: datastore: instance0 folder: Delivery/Quality Assurance/FOSS/Dynamic resourcepool: delivery/Quality Assurance/FOSS/Dynamic + pooling_api: http://vcloud.delivery.puppetlabs.net/ diff --git a/acceptance/config/windows-2003.cfg b/acceptance/config/windows-2003.cfg index e8fd2c9ece..f38c99f322 100644 --- a/acceptance/config/windows-2003.cfg +++ b/acceptance/config/windows-2003.cfg @@ -20,3 +20,4 @@ CONFIG: datastore: instance0 folder: Delivery/Quality Assurance/FOSS/Dynamic resourcepool: delivery/Quality Assurance/FOSS/Dynamic + pooling_api: http://vcloud.delivery.puppetlabs.net/ From 0758ca23a8d77d5c79dfe42aeca69e1dddd1df0a Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Fri, 15 Nov 2013 10:35:27 -0800 Subject: [PATCH 1470/3753] (Maint) Allow acceptance tests to be run on preserved windows hosts Previously, running acceptance tests on a preserved windows host would fail, because the puppet-win32-ruby directory already existed from the previous run, and git clone will report a fatal error. This commit uses beaker's method for cloning a repo. The method handles things like only cloning if necessary, resetting remotes, and cleaning existing workspaces. Cherry-pick of 326b7ac106fe4630ff59ec1da716207c0d67a065 from puppet --- acceptance/setup/00_EnvSetup.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/acceptance/setup/00_EnvSetup.rb b/acceptance/setup/00_EnvSetup.rb index 5794013b71..889d6cc4b8 100644 --- a/acceptance/setup/00_EnvSetup.rb +++ b/acceptance/setup/00_EnvSetup.rb @@ -4,6 +4,8 @@ require 'puppet/acceptance/install_utils' extend Puppet::Acceptance::InstallUtils +require 'beaker/dsl/install_utils' +extend Beaker::DSL::InstallUtils PACKAGES = { :redhat => [ @@ -27,9 +29,9 @@ hosts.each do |host| if host['platform'] =~ /windows/ - on host, 'echo $PATH' - on host, 'git clone https://github.com/puppetlabs/puppet-win32-ruby' - on host, 'cp -r puppet-win32-ruby/ruby/* /' + step "#{host} Install ruby from git" + install_from_git(host, "/opt/puppet-git-repos", :name => 'puppet-win32-ruby', :path => 'git://github.com/puppetlabs/puppet-win32-ruby') + on host, 'cd /opt/puppet-git-repos/puppet-win32-ruby; cp -r ruby/* /' on host, 'cd /lib; icacls ruby /grant "Everyone:(OI)(CI)(RX)"' on host, 'cd /lib; icacls ruby /reset /T' on host, 'ruby --version' From f845df5dce9d6ff7db6c22d7b7082e807e77fde1 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Thu, 5 Dec 2013 20:56:50 -0800 Subject: [PATCH 1471/3753] (Maint) Remove puppet specific setup steps These steps are not needed when executing facter acceptance. --- acceptance/setup/02_PuppetWorkArounds.rb | 21 ------- acceptance/setup/03_PuppetMasterSanity.rb | 24 ------- acceptance/setup/04_ValidateSignCert.rb | 28 --------- acceptance/setup/06_InstallModules.rb | 76 ----------------------- 4 files changed, 149 deletions(-) delete mode 100644 acceptance/setup/02_PuppetWorkArounds.rb delete mode 100755 acceptance/setup/03_PuppetMasterSanity.rb delete mode 100755 acceptance/setup/04_ValidateSignCert.rb delete mode 100644 acceptance/setup/06_InstallModules.rb diff --git a/acceptance/setup/02_PuppetWorkArounds.rb b/acceptance/setup/02_PuppetWorkArounds.rb deleted file mode 100644 index d7b1f71a16..0000000000 --- a/acceptance/setup/02_PuppetWorkArounds.rb +++ /dev/null @@ -1,21 +0,0 @@ -test_name 'work arounds for bugs' do - hosts.each do |host| - next if host['platform'].include? 'windows' - - step "REVISIT: see #9862, this step should not be required for agents" do - on host, "getent group puppet || groupadd puppet" - - if host['platform'].include? 'solaris' - useradd_opts = '-d /puppet -m -s /bin/sh -g puppet puppet' - else - useradd_opts = 'puppet -g puppet -G puppet' - end - - on host, "getent passwd puppet || useradd #{useradd_opts}" - end - - step "REVISIT: Work around bug #5794 not creating reports as required" do - on host, "mkdir -p /tmp/reports && chown puppet:puppet /tmp/reports" - end - end -end diff --git a/acceptance/setup/03_PuppetMasterSanity.rb b/acceptance/setup/03_PuppetMasterSanity.rb deleted file mode 100755 index 6d1df4cae9..0000000000 --- a/acceptance/setup/03_PuppetMasterSanity.rb +++ /dev/null @@ -1,24 +0,0 @@ -test_name "Puppet Master sanity checks: PID file and SSL dir creation" - -pidfile = '/var/lib/puppet/run/master.pid' - -hostname = on(master, 'facter hostname').stdout.strip -fqdn = on(master, 'facter fqdn').stdout.strip - -master_conf = { - :main => { - :dns_alt_names => "puppet,#{hostname},#{fqdn}", - :verbose => true, - :noop => true, - }, -} - -with_puppet_running_on(master, master_conf) do - # SSL dir created? - step "SSL dir created?" - on master, "[ -d #{master['puppetpath']}/ssl ]" - - # PID file exists? - step "PID file created?" - on master, "[ -f #{pidfile} ]" -end diff --git a/acceptance/setup/04_ValidateSignCert.rb b/acceptance/setup/04_ValidateSignCert.rb deleted file mode 100755 index 7c9e309b5b..0000000000 --- a/acceptance/setup/04_ValidateSignCert.rb +++ /dev/null @@ -1,28 +0,0 @@ -test_name "Validate Sign Cert" - -hostname = on(master, 'facter hostname').stdout.strip -fqdn = on(master, 'facter fqdn').stdout.strip - -master_conf = { - :master => { - :dns_alt_names => "puppet,#{hostname},#{fqdn}", - }, -} - -step "Master: Start Puppet Master" -with_puppet_running_on(master, master_conf) do - hosts.each do |host| - next if host['roles'].include? 'master' - - step "Agents: Run agent --test first time to gen CSR" - on host, puppet_agent("--test"), :acceptable_exit_codes => [1] - end - - # Sign all waiting certs - step "Master: sign all certs" - on master, puppet_cert("--sign --all"), :acceptable_exit_codes => [0,24] - - step "Agents: Run agent --test second time to obtain signed cert" - on agents, puppet_agent("--test"), :acceptable_exit_codes => [0,2] -end - diff --git a/acceptance/setup/06_InstallModules.rb b/acceptance/setup/06_InstallModules.rb deleted file mode 100644 index 00243010b2..0000000000 --- a/acceptance/setup/06_InstallModules.rb +++ /dev/null @@ -1,76 +0,0 @@ -require 'pathname' - -# Given an array of modules specified by the --modules command line option, -# Parse all of them into an array of usable hash structures. -class PuppetModules - attr_reader :modules - - def initialize(modules=[]) - @modules = modules - end - - def list - return [] unless modules - modules.collect do |uri| - git_url, git_ref = uri.split '#' - folder = Pathname.new(git_url).basename('.git') - name = folder.to_s.split('-', 2)[1] || folder.to_s - { - :name => name, - :url => git_url, - :folder => folder.to_s, - :ref => git_ref, - :protocol => git_url.split(':')[0].intern, - } - end - end -end - -def install_git_module(mod, hosts) - # The idea here is that each test can symlink the modules they want from a - # temporary directory to this location. This will preserve the global - # state of the system while allowing individual test cases to quickly run - # with a module "installed" in the module path. - moddir = "/opt/puppet-git-repos" - target = "#{moddir}/#{mod[:name]}" - - step "Clone #{mod[:url]} if needed" - on hosts, "test -d #{moddir} || mkdir -p #{moddir}" - on hosts, "test -d #{target} || git clone #{mod[:url]} #{target}" - step "Update #{mod[:name]} and check out revision #{mod[:ref]}" - - commands = ["cd #{target}", - "remote rm origin", - "remote add origin #{mod[:url]}", - "fetch origin", - "checkout -f #{mod[:ref]}", - "reset --hard refs/remotes/origin/#{mod[:ref]}", - "clean -fdx", - ] - - on hosts, commands.join(" && git ") -end - -def install_scp_module(mod, hosts) - moddir = "/opt/puppet-git-repos" - target = "#{moddir}/#{mod[:name]}" - - step "Purge #{target} if needed" - on hosts, "test -d #{target} && rm -rf #{target} || true" - - step "Copy #{mod[:name]} to hosts" - scp_to hosts, mod[:url].split(':', 2)[1], target -end - -modules = PuppetModules.new(options[:modules]).list - -step "Masters: Install Puppet Modules" -masters = hosts.select { |host| host['roles'].include? 'master' } - -modules.each do |mod| - if mod[:protocol] == :scp - install_scp_module(mod, masters) - else - install_git_module(mod, masters) - end -end From 132c25ebf5115b60996c67ada4b96d5db450b58e Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Thu, 5 Dec 2013 22:12:24 -0800 Subject: [PATCH 1472/3753] (Maint) Change canonize to canonicalize To canonize is to officially declare a dead person to be a saint, which isn't what we want. --- lib/facter/util/collection.rb | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/facter/util/collection.rb b/lib/facter/util/collection.rb index cb9359ba45..7c02ce0049 100644 --- a/lib/facter/util/collection.rb +++ b/lib/facter/util/collection.rb @@ -21,7 +21,7 @@ def [](name) # Add a resolution mechanism for a named fact. This does not distinguish # between adding a new fact and adding a new way to resolve a fact. def add(name, options = {}, &block) - name = canonize(name) + name = canonicalize(name) unless fact = @facts[name] fact = Facter::Util::Fact.new(name) @@ -78,7 +78,7 @@ def each # Return a fact by name. def fact(name) - name = canonize(name) + name = canonicalize(name) # Try to load the fact if necessary load(name) unless @facts[name] @@ -143,9 +143,7 @@ def value(name) private - # Provide a consistent means of getting the exact same fact name - # every time. - def canonize(name) + def canonicalize(name) name.to_s.downcase.to_sym end end From f8410650b490ae209b73890de63559a470724672 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Thu, 5 Dec 2013 22:17:13 -0800 Subject: [PATCH 1473/3753] (Maint) Override the `parse_results` method Previously, the Base parser subclasses were not consistent in which parse method they overrode. The `parse` method wraps the `parse_results` method with exception handling. Subclasses that could potentially raise exceptions should override `parse_results` instead. Note the NothingParser is an exception, since it just returns nil. --- lib/facter/util/parser.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/facter/util/parser.rb b/lib/facter/util/parser.rb index 7f9c514ed8..cf3305a88e 100644 --- a/lib/facter/util/parser.rb +++ b/lib/facter/util/parser.rb @@ -102,7 +102,7 @@ def parse_results end class JsonParser < Base - def results + def parse_results if Facter.json? JSON.load(content) else @@ -118,7 +118,7 @@ def results end class ScriptParser < Base - def results + def parse_results KeyValuePairOutputFormat.parse Facter::Util::Resolution.exec(filename) end end @@ -134,7 +134,7 @@ def results # Executes and parses the key value output of Powershell scripts class PowershellParser < Base # Returns a hash of facts from powershell output - def results + def parse_results shell_command = "powershell -NoProfile -NonInteractive -NoLogo -ExecutionPolicy Bypass -File \"#{filename}\"" KeyValuePairOutputFormat.parse Facter::Util::Resolution.exec(shell_command) end From f2da8e4cf80431ec9dd789fac4bcf6f0b209756c Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Thu, 5 Dec 2013 23:06:33 -0800 Subject: [PATCH 1474/3753] (#23368) Simplify how parser is stubbed out Previously, the methods for stubbing out a parser were repeated across tests. This makes it more DRY. --- spec/unit/util/parser_spec.rb | 115 +++++++++++++--------------------- 1 file changed, 43 insertions(+), 72 deletions(-) mode change 100644 => 100755 spec/unit/util/parser_spec.rb diff --git a/spec/unit/util/parser_spec.rb b/spec/unit/util/parser_spec.rb old mode 100644 new mode 100755 index ec53f60749..ccf40449f0 --- a/spec/unit/util/parser_spec.rb +++ b/spec/unit/util/parser_spec.rb @@ -35,8 +35,8 @@ let(:data) do {"one" => "two", "three" => "four"} end describe "yaml" do - let(:data_in_yaml) do YAML.dump(data) end - let(:data_file) do "/tmp/foo.yaml" end + let(:data_in_yaml) { YAML.dump(data) } + let(:data_file) { "/tmp/foo.yaml" } it "should return a hash of whatever is stored on disk" do File.stubs(:read).with(data_file).returns(data_in_yaml) @@ -52,8 +52,8 @@ end describe "json" do - let(:data_in_json) do JSON.dump(data) end - let(:data_file) do "/tmp/foo.json" end + let(:data_in_json) { JSON.dump(data) } + let(:data_file) { "/tmp/foo.json" } it "should return a hash of whatever is stored on disk" do pending("this test requires the json library") unless Facter.json? @@ -63,7 +63,7 @@ end describe "txt" do - let(:data_file) do "/tmp/foo.txt" end + let(:data_file) { "/tmp/foo.txt" } shared_examples_for "txt parser" do it "should return a hash of whatever is stored on disk" do @@ -73,75 +73,51 @@ end context "well formed data" do - let(:data_in_txt) do "one=two\nthree=four\n" end + let(:data_in_txt) { "one=two\nthree=four\n" } it_behaves_like "txt parser" end context "extra equal sign" do - let(:data_in_txt) do "one=two\nthree=four=five\n" end + let(:data_in_txt) { "one=two\nthree=four=five\n" } let(:data) do {"one" => "two", "three" => "four=five"} end it_behaves_like "txt parser" end context "extra data" do - let(:data_in_txt) do "one=two\nfive\nthree=four\n" end + let(:data_in_txt) { "one=two\nfive\nthree=four\n" } it_behaves_like "txt parser" end end describe "scripts" do - let :cmd do "/tmp/foo.sh" end - let :data_in_txt do "one=two\nthree=four\n" end + let(:ext) { Facter::Util::Config.is_windows? ? '.bat' : '.sh' } + let(:cmd) { "/tmp/foo#{ext}" } + let(:data_in_txt) { "one=two\nthree=four\n" } - before :each do - Facter::Util::Resolution.stubs(:exec).with(cmd).returns(data_in_txt) - File.stubs(:executable?).with(cmd).returns(true) - end - - it "should return a hash of whatever is returned by the executable" do - pending("this test does not run on windows") if Facter::Util::Config.is_windows? - File.stubs(:file?).with(cmd).returns(true) - Facter::Util::Parser.parser_for(cmd).results.should == data - end + def expects_script_to_return(path, content, result) + Facter::Util::Resolution.stubs(:exec).with(path).returns(content) + File.stubs(:executable?).with(path).returns(true) + File.stubs(:file?).with(path).returns(true) - it "should not parse a directory" do - File.stubs(:file?).with(cmd).returns(false) - Facter::Util::Parser.parser_for(cmd).results.should be_nil + Facter::Util::Parser.parser_for(path).results.should == result end - context "with nil external script output" do - # ensure NothingParser is not used on Windows by tricking parser registration - if Facter::Util::Config.is_windows? - let :cmd do "/tmp/foo.bat" end - end - let :data_in_txt do nil end + def expects_parser_to_return_nil_for_directory(path) + File.stubs(:file?).with(path).returns(false) - it "should return an empty hash" do - File.stubs(:file?).with(cmd).returns(true) - Facter::Util::Parser.parser_for(cmd).results.should == {} - end + Facter::Util::Parser.parser_for(path).results.should be_nil end - context "on Windows" do - let :cmd do "/tmp/foo.bat" end - - before :each do - Facter::Util::Config.stubs(:is_windows?).returns(true) - end - - let :parser do - Facter::Util::Parser.parser_for(cmd) - end + it "returns a hash of whatever is returned by the executable" do + expects_script_to_return(cmd, data_in_txt, data) + end - it "should not parse a directory" do - File.stubs(:file?).with(cmd).returns(false) - Facter::Util::Parser.parser_for(cmd).results.should be_nil - end + it "should not parse a directory" do + expects_parser_to_return_nil_for_directory(cmd) + end - it "should return the data properly" do - File.stubs(:file?).with(cmd).returns(true) - parser.results.should == data - end + it "returns an empty hash when the script returns nil" do + expects_script_to_return(cmd, nil, {}) end context "exe, bat, cmd, and com files" do @@ -159,35 +135,30 @@ cmds.each {|cmd| Facter::Util::Parser.parser_for(cmd).should be_an_instance_of(Facter::Util::Parser::NothingParser) } end - it "should return script parser if on windows" do + it "should return script parser if on windows" do Facter::Util::Config.stubs(:is_windows?).returns(true) cmds.each {|cmd| Facter::Util::Parser.parser_for(cmd).should be_an_instance_of(Facter::Util::Parser::ScriptParser) } end + end - end - end - - describe "powershell parser" do - let :ps1 do "/tmp/foo.ps1" end - let :data_in_ps1 do "one=two\nthree=four\n" end + describe "powershell parser" do + let(:ps1) { "/tmp/foo.ps1" } - before :each do - Facter::Util::Config.stubs(:is_windows?).returns(true) - Facter::Util::Resolution.stubs(:exec).returns(data_in_ps1) - end + def expects_to_parse_powershell(cmd, content, result) + Facter::Util::Config.stubs(:is_windows?).returns(true) + Facter::Util::Resolution.stubs(:exec).returns(content) + File.stubs(:file?).with(ps1).returns(true) - let :parser do - Facter::Util::Parser.parser_for(ps1) - end + Facter::Util::Parser.parser_for(cmd).results.should == result + end - it "should not parse a directory" do - File.stubs(:file?).with(ps1).returns(false) - Facter::Util::Parser.parser_for(ps1).results.should be_nil - end + it "should not parse a directory" do + expects_parser_to_return_nil_for_directory(ps1) + end - it "should return data properly" do - File.stubs(:file?).with(ps1).returns(true) - parser.results.should == data + it "should parse output from powershell" do + expects_to_parse_powershell(ps1, data_in_txt, data) + end end end From 5b745a728c8a6e89a39e1677051898a080e4c1fe Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Thu, 5 Dec 2013 23:18:42 -0800 Subject: [PATCH 1475/3753] (#23368) Quote scripts that contain spaces The Facter::Util::Resolution.exec method expects that commands with spaces are quoted. On 2003, external facts are typically under C:\Documents and Settings\All Users\Application Data. As a result, facter would always fail to execute external executable facts on 2003. This is not an issue on 2008 and up, because external facts are typically located under C:\ProgramData. This is not an issue for powershell scripts, because we pass the quoted name of the script to powershell. This is not an issue for text based external facts, because `File.read` do not require paths with spaces to be quoted (since its not a command and possibly arguments, or just a command with spaces). This commit adds a `quote` method to the ScriptParser so that we quote the script if it has spaces. --- lib/facter/util/parser.rb | 8 +++++++- spec/unit/util/parser_spec.rb | 8 ++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/facter/util/parser.rb b/lib/facter/util/parser.rb index cf3305a88e..1f87134937 100644 --- a/lib/facter/util/parser.rb +++ b/lib/facter/util/parser.rb @@ -119,7 +119,13 @@ def parse_results class ScriptParser < Base def parse_results - KeyValuePairOutputFormat.parse Facter::Util::Resolution.exec(filename) + KeyValuePairOutputFormat.parse Facter::Util::Resolution.exec(quote(filename)) + end + + private + + def quote(filename) + filename.index(' ') ? "\"#{filename}\"" : filename end end diff --git a/spec/unit/util/parser_spec.rb b/spec/unit/util/parser_spec.rb index ccf40449f0..54bc1e452c 100755 --- a/spec/unit/util/parser_spec.rb +++ b/spec/unit/util/parser_spec.rb @@ -120,6 +120,14 @@ def expects_parser_to_return_nil_for_directory(path) expects_script_to_return(cmd, nil, {}) end + it "quotes scripts with spaces" do + path = "/h a s s p a c e s#{ext}" + + Facter::Util::Resolution.expects(:exec).with("\"#{path}\"").returns(data_in_txt) + + expects_script_to_return(path, data_in_txt, data) + end + context "exe, bat, cmd, and com files" do let :cmds do ["/tmp/foo.bat", "/tmp/foo.cmd", "/tmp/foo.exe", "/tmp/foo.com"] end From ab999818f52519c579843c596db62af017b16b07 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Thu, 5 Dec 2013 22:10:11 -0800 Subject: [PATCH 1476/3753] (#22944) Don't execute external facts 19 times Previously, facter would load every external fact many, many times. For executable facts, e.g. powershell, this means executing the fact. For text based facts, it means reading the file, and parsing its output. On Darwin, it was common for facts to be executed 6 times; on Windows, 19 times. The problem occurs whenever facter requires a file, e.g. `ec2.rb`, which references another fact, e.g. `:macaddress` outside the body of any resolution, and facter has not yet required `macaddress.rb`. For example, the following will cause all external facts to be loaded: if Facter.value(:macaddress) Facter.add(:ec2) do ... end end Note that this is triggered because macaddress comes after ec2. Similarly, `blockdevices.rb` references the `:kernel` fact outside of any resolution, so that's another round of external fact evaluation. This commit keeps adds a boolean flag to ensure we only execute external facts once. --- acceptance/tests/runs_external_facts_once.rb | 59 ++++++++++++++++++++ lib/facter/util/collection.rb | 11 +++- 2 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 acceptance/tests/runs_external_facts_once.rb diff --git a/acceptance/tests/runs_external_facts_once.rb b/acceptance/tests/runs_external_facts_once.rb new file mode 100644 index 0000000000..afd690720c --- /dev/null +++ b/acceptance/tests/runs_external_facts_once.rb @@ -0,0 +1,59 @@ +test_name "#22944: Facter executes external executable facts many times" + +unix_content = <&2 +echo "test=value" +EOM + +win_content = <&2 +echo "test=value" +EOM + +agents.each do |agent| + step "Agent #{agent}: create external executable fact" + + # assume we're running as root + if agent['platform'] =~ /windows/ + if on(agent, facter('kernelmajversion')).stdout.chomp.to_f < 6.0 + factsd = 'C:/Documents and Settings/All Users/Application Data/PuppetLabs/facter/facts.d' + else + factsd = 'C:/ProgramData/PuppetLabs/facter/facts.d' + end + ext = '.bat' + content = win_content + else + factsd = '/etc/facter/facts.d' + ext = '.sh' + content = unix_content + end + + + step "Agent #{agent}: create facts.d directory" + on(agent, "mkdir -p '#{factsd}'") + + + step "Agent #{agent}: create external fact" + ext_fact = "#{factsd}/external_fact#{ext}" + + teardown do + on(agent, "rm -f '#{ext_fact}'") + end + + create_remote_file(agent, ext_fact, content) + + step "Agent #{agent}: make it executable" + on(agent, "chmod +x '#{ext_fact}'") + + step "Agent #{agent}: ensure it only executes once" + on(agent, facter) do + lines = stderr.split('\n') + times = lines.count { |line| line =~ /SCRIPT CALLED/ } + if times == 1 + step "External executable fact executed once" + else + fail_test "External fact executed #{times} times, expected once: #{stderr}" + end + end +end diff --git a/lib/facter/util/collection.rb b/lib/facter/util/collection.rb index 7c02ce0049..4b91e10461 100644 --- a/lib/facter/util/collection.rb +++ b/lib/facter/util/collection.rb @@ -106,13 +106,13 @@ def list def load(name) internal_loader.load(name) - external_loader.load(self) + load_external_facts end # Load all known facts. def load_all internal_loader.load_all - external_loader.load(self) + load_external_facts end def internal_loader @@ -146,4 +146,11 @@ def value(name) def canonicalize(name) name.to_s.downcase.to_sym end + + def load_external_facts + if ! @external_facts_loaded + @external_facts_loaded = true + external_loader.load(self) + end + end end From c4090f7de6b96f11ad295e7cba5d7d55132dd342 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Fri, 6 Dec 2013 11:25:01 -0800 Subject: [PATCH 1477/3753] (#22944) Reload external facts after the collection is flushed Previously, external facts were not reloaded after `Facter::Util::Collection#flush` was called, which doesn't match the semantics of the `flush` method. This commit ensures we are capable of reloading external facts after `flush` is called. Note that this isn't an issue when `Facter.clear` is called, because that discards the collection entirely, so external facts will be reloaded if necessary. --- lib/facter/util/collection.rb | 1 + spec/unit/util/collection_spec.rb | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/facter/util/collection.rb b/lib/facter/util/collection.rb index 4b91e10461..63233cbd94 100644 --- a/lib/facter/util/collection.rb +++ b/lib/facter/util/collection.rb @@ -96,6 +96,7 @@ def fact(name) # Flush all cached values. def flush @facts.each { |name, fact| fact.flush } + @external_facts_loaded = nil end # Return a list of all of the facts. diff --git a/spec/unit/util/collection_spec.rb b/spec/unit/util/collection_spec.rb index 1c92cf2c36..6e20d26071 100755 --- a/spec/unit/util/collection_spec.rb +++ b/spec/unit/util/collection_spec.rb @@ -209,7 +209,8 @@ end describe "external facts" do - let(:collection) { Facter::Util::Collection.new(internal_loader, SingleFactLoader.new(:test_fact, "fact value")) } + let(:external_loader) { SingleFactLoader.new(:test_fact, "fact value") } + let(:collection) { Facter::Util::Collection.new(internal_loader, external_loader) } it "loads when a specific fact is requested" do collection.fact(:test_fact).value.should == "fact value" @@ -225,6 +226,21 @@ facts.should == [["test_fact", "fact value"]] end + + it "are loaded only once" do + external_loader.expects(:load).with(collection) + + collection.load_all + collection.load_all + end + + it "are reloaded after flushing" do + external_loader.expects(:load).with(collection).twice + + collection.load_all + collection.flush + collection.load_all + end end class SingleFactLoader From f7147db531c1281a40118a44f21b2414736fb9d6 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Fri, 6 Dec 2013 13:34:45 -0800 Subject: [PATCH 1478/3753] (maint) Fix up Solaris acceptance test (don't output to stderr) --- lib/facter/zfs_version.rb | 2 +- spec/unit/zfs_version_spec.rb | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/facter/zfs_version.rb b/lib/facter/zfs_version.rb index 72af6c7b56..25c81d6f4d 100644 --- a/lib/facter/zfs_version.rb +++ b/lib/facter/zfs_version.rb @@ -3,7 +3,7 @@ Facter.add('zfs_version') do setcode do if Facter::Util::Resolution.which('zfs') - zfs_help = Facter::Util::Resolution.exec('zfs -?') + zfs_help = Facter::Util::Resolution.exec('zfs -? 2> /dev/null') zfs_has_upgrade = zfs_help.match(/^\s+upgrade/) unless zfs_help.nil? if zfs_has_upgrade zfs_v = Facter::Util::Resolution.exec('zfs upgrade -v') diff --git a/spec/unit/zfs_version_spec.rb b/spec/unit/zfs_version_spec.rb index 1f23718256..daef329dee 100644 --- a/spec/unit/zfs_version_spec.rb +++ b/spec/unit/zfs_version_spec.rb @@ -17,42 +17,42 @@ end it "should return nil on old versions of Solaris 10" do - Facter::Util::Resolution.stubs(:exec).with("zfs -?").returns(my_fixture_read('zfs_old')) + Facter::Util::Resolution.stubs(:exec).with("zfs -? 2> /dev/null").returns(my_fixture_read('zfs_old')) Facter.fact(:zfs_version).value.should == nil end it "should return correct version on Solaris 10" do - Facter::Util::Resolution.stubs(:exec).with("zfs -?").returns(my_fixture_read('zfs_new')) + Facter::Util::Resolution.stubs(:exec).with("zfs -? 2> /dev/null").returns(my_fixture_read('zfs_new')) Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(my_fixture_read('solaris_10')) Facter.fact(:zfs_version).value.should == "3" end it "should return correct version on Solaris 11" do - Facter::Util::Resolution.stubs(:exec).with("zfs -?").returns(my_fixture_read('zfs_new')) + Facter::Util::Resolution.stubs(:exec).with("zfs -? 2> /dev/null").returns(my_fixture_read('zfs_new')) Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(my_fixture_read('solaris_11')) Facter.fact(:zfs_version).value.should == "5" end it "should return correct version on FreeBSD 8.2" do - Facter::Util::Resolution.stubs(:exec).with("zfs -?").returns(my_fixture_read('zfs_new')) + Facter::Util::Resolution.stubs(:exec).with("zfs -? 2> /dev/null").returns(my_fixture_read('zfs_new')) Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(my_fixture_read('freebsd_8.2')) Facter.fact(:zfs_version).value.should == "4" end it "should return correct version on FreeBSD 9.0" do - Facter::Util::Resolution.stubs(:exec).with("zfs -?").returns(my_fixture_read('zfs_new')) + Facter::Util::Resolution.stubs(:exec).with("zfs -? 2> /dev/null").returns(my_fixture_read('zfs_new')) Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(my_fixture_read('freebsd_9.0')) Facter.fact(:zfs_version).value.should == "5" end it "should return correct version on Linux with ZFS-fuse" do - Facter::Util::Resolution.stubs(:exec).with("zfs -?").returns(my_fixture_read('zfs_new')) + Facter::Util::Resolution.stubs(:exec).with("zfs -? 2> /dev/null").returns(my_fixture_read('zfs_new')) Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(my_fixture_read('linux-fuse_0.6.9')) Facter.fact(:zfs_version).value.should == "4" end it "should return correct version on Linux with zfsonlinux" do - Facter::Util::Resolution.stubs(:exec).with("zfs -?").returns(my_fixture_read('zfs_new')) + Facter::Util::Resolution.stubs(:exec).with("zfs -? 2> /dev/null").returns(my_fixture_read('zfs_new')) Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(my_fixture_read('zfsonlinux_0.6.1')) Facter.fact(:zfs_version).value.should == "5" end @@ -64,7 +64,7 @@ end it "should return nil if zfs fails to run" do - Facter::Util::Resolution.stubs(:exec).with("zfs -?").returns(nil) + Facter::Util::Resolution.stubs(:exec).with("zfs -? 2> /dev/null").returns(nil) Facter.fact(:zfs_version).value.should == nil end @@ -75,7 +75,7 @@ with("zfs"). returns(nil,nil,"/usr/bin/zfs") Facter::Util::Resolution.stubs(:exec). - with("zfs -?"). + with("zfs -? 2> /dev/null"). returns(my_fixture_read('zfs_new')) Facter::Util::Resolution.stubs(:exec). with("zfs upgrade -v"). From 5f37f82576e18486bbc48b04bcdeefbe28ad7011 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Fri, 6 Dec 2013 14:00:47 -0800 Subject: [PATCH 1479/3753] (#23377) Fix parsing of 'zfs -?' stdout to allow zfs version detection Facter detects zfs version by first running 'zfs -?' to verify that it can call 'zfs upgrade -v' safely. Prior to this patch, the parsing of the output of 'zfs -?' assumed a different (earlier?) format for the output of 'zfs -?' and thus would miss that the 'zfs upgrade' sub-command is in fact supported. This patch loosens the regex so that 'upgrade' doesn't have to be the first word on a line, and also adds a new fixture to match the current output of 'zfs -?'. --- lib/facter/zfs_version.rb | 2 +- spec/fixtures/unit/zfs_version/zfs_solaris11_help | 6 ++++++ spec/unit/zfs_version_spec.rb | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 spec/fixtures/unit/zfs_version/zfs_solaris11_help diff --git a/lib/facter/zfs_version.rb b/lib/facter/zfs_version.rb index 25c81d6f4d..6f5eef20bd 100644 --- a/lib/facter/zfs_version.rb +++ b/lib/facter/zfs_version.rb @@ -4,7 +4,7 @@ setcode do if Facter::Util::Resolution.which('zfs') zfs_help = Facter::Util::Resolution.exec('zfs -? 2> /dev/null') - zfs_has_upgrade = zfs_help.match(/^\s+upgrade/) unless zfs_help.nil? + zfs_has_upgrade = zfs_help.match(/\A.*upgrade.*\z/m) unless zfs_help.nil? if zfs_has_upgrade zfs_v = Facter::Util::Resolution.exec('zfs upgrade -v') zfs_version = zfs_v.scan(/^\s+(\d+)\s+/m).flatten.last unless zfs_v.nil? diff --git a/spec/fixtures/unit/zfs_version/zfs_solaris11_help b/spec/fixtures/unit/zfs_version/zfs_solaris11_help new file mode 100644 index 0000000000..6d7a1d7863 --- /dev/null +++ b/spec/fixtures/unit/zfs_version/zfs_solaris11_help @@ -0,0 +1,6 @@ +The following commands are supported: +allow clone create destroy diff get +groupspace help hold holds inherit key +list mount promote receive release rename +rollback send set share snapshot unallow +unmount unshare upgrade userspace diff --git a/spec/unit/zfs_version_spec.rb b/spec/unit/zfs_version_spec.rb index daef329dee..03bf96f053 100644 --- a/spec/unit/zfs_version_spec.rb +++ b/spec/unit/zfs_version_spec.rb @@ -28,7 +28,7 @@ end it "should return correct version on Solaris 11" do - Facter::Util::Resolution.stubs(:exec).with("zfs -? 2> /dev/null").returns(my_fixture_read('zfs_new')) + Facter::Util::Resolution.stubs(:exec).with("zfs -? 2> /dev/null").returns(my_fixture_read('zfs_solaris11_help')) Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(my_fixture_read('solaris_11')) Facter.fact(:zfs_version).value.should == "5" end From 85e23b7e470bbfbd7f29fcd22830424327a25b26 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Thu, 5 Dec 2013 20:56:50 -0800 Subject: [PATCH 1480/3753] (Maint) Remove puppet specific setup steps These steps are not needed when executing facter acceptance. --- acceptance/setup/02_PuppetWorkArounds.rb | 21 ------- acceptance/setup/03_PuppetMasterSanity.rb | 24 ------- acceptance/setup/04_ValidateSignCert.rb | 28 --------- acceptance/setup/06_InstallModules.rb | 76 ----------------------- 4 files changed, 149 deletions(-) delete mode 100644 acceptance/setup/02_PuppetWorkArounds.rb delete mode 100755 acceptance/setup/03_PuppetMasterSanity.rb delete mode 100755 acceptance/setup/04_ValidateSignCert.rb delete mode 100644 acceptance/setup/06_InstallModules.rb diff --git a/acceptance/setup/02_PuppetWorkArounds.rb b/acceptance/setup/02_PuppetWorkArounds.rb deleted file mode 100644 index d7b1f71a16..0000000000 --- a/acceptance/setup/02_PuppetWorkArounds.rb +++ /dev/null @@ -1,21 +0,0 @@ -test_name 'work arounds for bugs' do - hosts.each do |host| - next if host['platform'].include? 'windows' - - step "REVISIT: see #9862, this step should not be required for agents" do - on host, "getent group puppet || groupadd puppet" - - if host['platform'].include? 'solaris' - useradd_opts = '-d /puppet -m -s /bin/sh -g puppet puppet' - else - useradd_opts = 'puppet -g puppet -G puppet' - end - - on host, "getent passwd puppet || useradd #{useradd_opts}" - end - - step "REVISIT: Work around bug #5794 not creating reports as required" do - on host, "mkdir -p /tmp/reports && chown puppet:puppet /tmp/reports" - end - end -end diff --git a/acceptance/setup/03_PuppetMasterSanity.rb b/acceptance/setup/03_PuppetMasterSanity.rb deleted file mode 100755 index 6d1df4cae9..0000000000 --- a/acceptance/setup/03_PuppetMasterSanity.rb +++ /dev/null @@ -1,24 +0,0 @@ -test_name "Puppet Master sanity checks: PID file and SSL dir creation" - -pidfile = '/var/lib/puppet/run/master.pid' - -hostname = on(master, 'facter hostname').stdout.strip -fqdn = on(master, 'facter fqdn').stdout.strip - -master_conf = { - :main => { - :dns_alt_names => "puppet,#{hostname},#{fqdn}", - :verbose => true, - :noop => true, - }, -} - -with_puppet_running_on(master, master_conf) do - # SSL dir created? - step "SSL dir created?" - on master, "[ -d #{master['puppetpath']}/ssl ]" - - # PID file exists? - step "PID file created?" - on master, "[ -f #{pidfile} ]" -end diff --git a/acceptance/setup/04_ValidateSignCert.rb b/acceptance/setup/04_ValidateSignCert.rb deleted file mode 100755 index 7c9e309b5b..0000000000 --- a/acceptance/setup/04_ValidateSignCert.rb +++ /dev/null @@ -1,28 +0,0 @@ -test_name "Validate Sign Cert" - -hostname = on(master, 'facter hostname').stdout.strip -fqdn = on(master, 'facter fqdn').stdout.strip - -master_conf = { - :master => { - :dns_alt_names => "puppet,#{hostname},#{fqdn}", - }, -} - -step "Master: Start Puppet Master" -with_puppet_running_on(master, master_conf) do - hosts.each do |host| - next if host['roles'].include? 'master' - - step "Agents: Run agent --test first time to gen CSR" - on host, puppet_agent("--test"), :acceptable_exit_codes => [1] - end - - # Sign all waiting certs - step "Master: sign all certs" - on master, puppet_cert("--sign --all"), :acceptable_exit_codes => [0,24] - - step "Agents: Run agent --test second time to obtain signed cert" - on agents, puppet_agent("--test"), :acceptable_exit_codes => [0,2] -end - diff --git a/acceptance/setup/06_InstallModules.rb b/acceptance/setup/06_InstallModules.rb deleted file mode 100644 index 00243010b2..0000000000 --- a/acceptance/setup/06_InstallModules.rb +++ /dev/null @@ -1,76 +0,0 @@ -require 'pathname' - -# Given an array of modules specified by the --modules command line option, -# Parse all of them into an array of usable hash structures. -class PuppetModules - attr_reader :modules - - def initialize(modules=[]) - @modules = modules - end - - def list - return [] unless modules - modules.collect do |uri| - git_url, git_ref = uri.split '#' - folder = Pathname.new(git_url).basename('.git') - name = folder.to_s.split('-', 2)[1] || folder.to_s - { - :name => name, - :url => git_url, - :folder => folder.to_s, - :ref => git_ref, - :protocol => git_url.split(':')[0].intern, - } - end - end -end - -def install_git_module(mod, hosts) - # The idea here is that each test can symlink the modules they want from a - # temporary directory to this location. This will preserve the global - # state of the system while allowing individual test cases to quickly run - # with a module "installed" in the module path. - moddir = "/opt/puppet-git-repos" - target = "#{moddir}/#{mod[:name]}" - - step "Clone #{mod[:url]} if needed" - on hosts, "test -d #{moddir} || mkdir -p #{moddir}" - on hosts, "test -d #{target} || git clone #{mod[:url]} #{target}" - step "Update #{mod[:name]} and check out revision #{mod[:ref]}" - - commands = ["cd #{target}", - "remote rm origin", - "remote add origin #{mod[:url]}", - "fetch origin", - "checkout -f #{mod[:ref]}", - "reset --hard refs/remotes/origin/#{mod[:ref]}", - "clean -fdx", - ] - - on hosts, commands.join(" && git ") -end - -def install_scp_module(mod, hosts) - moddir = "/opt/puppet-git-repos" - target = "#{moddir}/#{mod[:name]}" - - step "Purge #{target} if needed" - on hosts, "test -d #{target} && rm -rf #{target} || true" - - step "Copy #{mod[:name]} to hosts" - scp_to hosts, mod[:url].split(':', 2)[1], target -end - -modules = PuppetModules.new(options[:modules]).list - -step "Masters: Install Puppet Modules" -masters = hosts.select { |host| host['roles'].include? 'master' } - -modules.each do |mod| - if mod[:protocol] == :scp - install_scp_module(mod, masters) - else - install_git_module(mod, masters) - end -end From 7f8d57b1f8df08f7deafc9d9c7853fffeab9ffaa Mon Sep 17 00:00:00 2001 From: Melissa Stone Date: Mon, 9 Dec 2013 10:29:56 -0800 Subject: [PATCH 1481/3753] (maint) Add fedora 20 to build defaults --- ext/build_defaults.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/build_defaults.yaml b/ext/build_defaults.yaml index 93769ac4a7..bb071538c7 100644 --- a/ext/build_defaults.yaml +++ b/ext/build_defaults.yaml @@ -9,7 +9,7 @@ gpg_name: 'info@puppetlabs.com' gpg_key: '4BD6EC30' sign_tar: FALSE # a space separated list of mock configs -final_mocks: 'pl-el-5-i386 pl-el-5-x86_64 pl-el-6-i386 pl-el-6-x86_64 pl-fedora-18-i386 pl-fedora-18-x86_64 pl-fedora-19-i386 pl-fedora-19-x86_64' +final_mocks: 'pl-el-5-i386 pl-el-5-x86_64 pl-el-6-i386 pl-el-6-x86_64 pl-fedora-18-i386 pl-fedora-18-x86_64 pl-fedora-19-i386 pl-fedora-19-x86_64 pl-fedora-20-i386 pl-fedora-20-x86_64' yum_host: 'yum.puppetlabs.com' yum_repo_path: '/opt/repository/yum/' build_gem: TRUE From b158c96e71e13e6aa907b30998e17f368b75a89d Mon Sep 17 00:00:00 2001 From: Melissa Stone Date: Mon, 9 Dec 2013 11:45:50 -0800 Subject: [PATCH 1482/3753] (packaging) Update FACTERVERSION to 1.7.4-rc1 --- lib/facter/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/version.rb b/lib/facter/version.rb index 10a0d346bf..d1f7f302c3 100644 --- a/lib/facter/version.rb +++ b/lib/facter/version.rb @@ -1,6 +1,6 @@ module Facter if not defined? FACTERVERSION then - FACTERVERSION = '1.7.3' + FACTERVERSION = '1.7.4-rc1' end ## From ce0675a0c5545fab967da968c551b48577ac7822 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Tue, 10 Dec 2013 08:08:11 +0000 Subject: [PATCH 1483/3753] Switch to passing fact maps. Before this change, cfacterlib had a bunch of dump_xyz functions that all used cout directly. This change converts those all to get_xyz with a map reference passed in, so that the caller can choose what to do with the map. --- Makefile | 6 +- cfacter.cc | 46 ++++---- cfacterlib.cc | 282 +++++++++++++++++++++++++------------------------- cfacterlib.h | 39 +++---- 4 files changed, 189 insertions(+), 184 deletions(-) diff --git a/Makefile b/Makefile index 29f3ee617b..c93e62b1dc 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,11 @@ cfacter: cfacter.cc cfacterlib.cc cfacterlib.h - g++ -g -o cfacter cfacter.cc cfacterlib.cc + g++ -std=c++0x -g -o cfacter cfacter.cc cfacterlib.cc cfacterlib.o: cfacterlib.cc cfacterlib.h - g++ -g -fPIC -c -o $@ cfacterlib.cc + g++ -std=c++0x -g -fPIC -c -o $@ cfacterlib.cc cfacterlib.so: cfacterlib.o - g++ -g -o $@ $^ -fPIC -shared + g++ -std=c++0x -g -o $@ $^ -fPIC -shared missing: -$(shell facter | grep "=>" | cut -f1 -d' ' | sort > /tmp/facter.txt) diff --git a/cfacter.cc b/cfacter.cc index 55cc96937e..8fe7e7e88a 100644 --- a/cfacter.cc +++ b/cfacter.cc @@ -1,32 +1,38 @@ #include "cfacterlib.h" #include +#include +#include #include using namespace std; int main(int argc, char **argv) { - // facter version itself -- report? if so, report facter 'equivalent'? - cout << "facterversion => 1.7.3" << endl; - - dump_network_facts(); - dump_kernel_facts(); - dump_blockdevice_facts(); - dump_operatingsystem_facts(); - dump_uptime_facts(); - dump_virtual_facts(); - dump_hardwired_facts(); - dump_misc_facts(); - dump_ruby_lib_versions(); - dump_mem_facts(); - dump_selinux_facts(); - dump_ssh_facts(); - dump_processor_facts(); - dump_architecture_facts(); - dump_dmidecode_facts(); - dump_filesystems_facts(); - dump_hostname_facts(); + std::map facts; + facts["facterversion"] = "3.0.0"; + + get_network_facts(facts); + get_kernel_facts(facts); + get_blockdevice_facts(facts); + get_operatingsystem_facts(facts); + get_uptime_facts(facts); + get_virtual_facts(facts); + get_hardwired_facts(facts); + get_misc_facts(facts); + get_ruby_lib_versions(facts); + get_mem_facts(facts); + get_selinux_facts(facts); + get_ssh_facts(facts); + get_processor_facts(facts); + get_architecture_facts(facts); + get_dmidecode_facts(facts); + get_filesystems_facts(facts); + get_hostname_facts(facts); + typedef map::iterator iter; + for (iter i = facts.begin(); i != facts.end(); ++i) { + cout << i->first << " => " << i->second << endl; + } exit(0); } diff --git a/cfacterlib.cc b/cfacterlib.cc index a58ac59fe6..4c8bffd9ae 100644 --- a/cfacterlib.cc +++ b/cfacterlib.cc @@ -1,4 +1,3 @@ - #include #include #include @@ -112,7 +111,7 @@ string read_oneline_file(const string file_path) return line; } -void dump_network_facts() +void get_network_facts(fact_map& facts) { struct ifreq *ifr; struct ifconf ifc; @@ -169,9 +168,9 @@ void dump_network_facts() interfaces += ","; const char *ipaddress = inet_ntoa(ip_addr); - cout << "ipaddress_" << r->ifr_name << " => " << ipaddress << endl; + facts[string("ipaddress_") + r->ifr_name] = ipaddress; if (primaryInterface) - cout << "ipaddress => " << ipaddress << endl; + facts["ipaddress"] = ipaddress; // mtu if (ioctl(s, SIOCGIFMTU, r) < 0) { @@ -179,7 +178,7 @@ void dump_network_facts() exit(1); } - cout << "mtu_" << r->ifr_name << " => " << r->ifr_mtu << endl; + facts[string("mtu_") + r->ifr_name] = to_string(r->ifr_mtu); if (primaryInterface) ; // no unmarked version of this network fact // netmask and network are both derived from the same ioctl @@ -191,18 +190,18 @@ void dump_network_facts() // netmask struct in_addr netmask_addr = ((struct sockaddr_in *)&r->ifr_netmask)->sin_addr; const char *netmask = inet_ntoa(netmask_addr); - cout << "netmask_" << r->ifr_name << " => " << netmask << endl; + facts[string("netmask_") + r->ifr_name] = netmask; if (primaryInterface) - cout << "netmask => " << netmask << endl; + facts["netmask"] = netmask; // mess of casting to get the network address struct in_addr network_addr; network_addr.s_addr = (in_addr_t) uint32_t(netmask_addr.s_addr) & uint32_t(ip_addr.s_addr); string network = inet_ntoa(network_addr); - cout << "network_" << r->ifr_name << " => " << network << endl; + facts[string("network_") + r->ifr_name] = network; if (primaryInterface) - cout << "network => " << network << endl; + facts["network"] = network; // and the mac address (but not for loopback) if (strcmp(r->ifr_name, "lo")) { @@ -218,32 +217,32 @@ void dump_network_facts() mac_bytes[0], mac_bytes[1], mac_bytes[2], mac_bytes[3], mac_bytes[4], mac_bytes[5]); - // and dump it out - cout << "macaddress_" << r->ifr_name << " => " << mac_address << endl; + // and get it out + facts[string("macaddress_") + r->ifr_name] = mac_address; if (primaryInterface) - cout << "macaddress => " << mac_address << endl; + facts["macaddress"] = mac_address; } } - cout << "interfaces => " << interfaces << endl; + facts["interfaces"] = interfaces; close(s); free(ifr); } -void dump_kernel_facts() +void get_kernel_facts(fact_map& facts) { // this is linux-only, so there you have it - cout << "kernel => Linux" << endl; + facts["kernel"] = "Linux"; string kernelrelease = read_oneline_file("/proc/sys/kernel/osrelease"); - cout << "kernelrelease => " << kernelrelease << endl; + facts["kernelrelease"] = kernelrelease; string kernelversion = kernelrelease.substr(0, kernelrelease.find("-")); - cout << "kernelversion => " << kernelversion << endl; + facts["kernelversion"] = kernelversion; string kernelmajversion = kernelversion.substr(0, kernelversion.rfind(".")); - cout << "kernelmajversion => " << kernelmajversion << endl; + facts["kernelmajversion"] = kernelmajversion; } -static void dump_lsb_facts() +static void get_lsb_facts(fact_map& facts) { std::ifstream lsb_release_file("/etc/lsb-release", std::ifstream::in); std::string line; @@ -253,61 +252,61 @@ static void dump_lsb_facts() string value = line.substr(sep + 1, string::npos); if (key == "DISTRIB_ID") { - cout << "lsbdistid => " << value << endl; - cout << "operatingsystem => " << value << endl; - cout << "osfamily => Debian" << endl; + facts["lsbdistid"] = value; + facts["operatingsystem"] = value; + facts["osfamily"] = "Debian"; } else if (key == "DISTRIB_RELEASE") { - cout << "lsbdistrelease => " << value << endl; - cout << "operatingsystemrelease => " << value << endl; - cout << "lsbmajdistrelease => " << value.substr(0, value.find(".")) << endl; + facts["lsbdistrelease"] = value; + facts["operatingsystemrelease"] = value; + facts["lsbmajdistrelease"] = value.substr(0, value.find(".")); } else if (key == "DISTRIB_CODENAME") - cout << "lsbdistcodename => " << value << endl; + facts["lsbdistcodename"] = value; else if (key == "DISTRIB_DESCRIPTION") - cout << "lsbdistdescription => " << value << endl; + facts["lsbdistdescription"] = value; } } // gonna need to pick a regex library to do os facts rights given all the variants // for now, just fedora ;> -static void dump_redhat_facts() +static void get_redhat_facts(fact_map& facts) { if (file_exist("/etc/redhat-release")) { - cout << "osfamily => RedHat" << endl; + facts["osfamily"] = "RedHat"; string redhat_release = read_oneline_file("/etc/redhat-release"); vector tokens; tokenize(redhat_release, tokens); if (tokens.size() >= 2 && tokens[0] == "Fedora" && tokens[1] == "release") { - cout << "operatingsystem => Fedora" << endl; + facts["operatingsystem"] = "Fedora"; if (tokens.size() >= 3) { - cout << "operatingsystemrelease => " << tokens[2] << endl; - cout << "operatingsystemmajrelease => " << tokens[2] << endl; + facts["operatingsystemrelease"] = tokens[2]; + facts["operatingsystemmajrelease"] = tokens[2]; } } else - cout << "operatingsystem => RedHat" << endl; + facts["operatingsystem"] = "RedHat"; } } -void dump_operatingsystem_facts() +void get_operatingsystem_facts(fact_map& facts) { - dump_lsb_facts(); - dump_redhat_facts(); + get_lsb_facts(facts); + get_redhat_facts(facts); } -void dump_uptime_facts() +void get_uptime_facts(fact_map& facts) { string uptime = read_oneline_file("/proc/uptime"); unsigned int uptime_seconds; sscanf(uptime.c_str(), "%ud", &uptime_seconds); unsigned int uptime_hours = uptime_seconds / 3600; unsigned int uptime_days = uptime_hours / 24; - cout << "uptime_seconds => " << uptime_seconds << endl; - cout << "uptime_hours => " << uptime_hours << endl; - cout << "uptime_days => " << uptime_days << endl; - cout << "uptime => " << uptime_days << " days" << endl; + facts["uptime_seconds"] = to_string(uptime_seconds); + facts["uptime_hours"] = to_string(uptime_hours); + facts["uptime_days"] = to_string(uptime_days); + facts["uptime"] = to_string(uptime_days) + " days"; } string popen_stdout(string cmd) @@ -324,7 +323,7 @@ string popen_stdout(string cmd) return cmd_output; } -void dump_virtual_facts() +void get_virtual_facts(fact_map& facts) { // poked at the real facter's virtual support, some combo of file existence // plus lspci plus dmidecode @@ -335,33 +334,33 @@ void dump_virtual_facts() // virtual could be discovered in lots of places so requires some special handling - cout << "is_virtual => false" << endl; - cout << "virtual => physical" << endl; + facts["is_virtual"] = "false"; + facts["virtual"] = "physical"; } // placeholders for some hardwired facts, cuz not sure what to do with them -void dump_hardwired_facts() +void get_hardwired_facts(fact_map& facts) { - cout << "ps => ps -ef" << endl; // what is this? - cout << "uniqueid => 007f0101" << endl; // ?? + facts["ps"] = "ps -ef"; // what is this? + facts["uniqueid"] = "007f0101"; // ?? } // versions of things we don't have if we're not running ruby // omit or 'undef' or ...? for now, omit but collect them here -void dump_ruby_lib_versions() +void get_ruby_lib_versions(fact_map& facts) { /* - cout << "puppetversion => undef" << endl; - cout << "augeasversion => undef" << endl; - cout << "rubysitedir => undef" << endl; - cout << "rubyversion => undef" << endl; + facts["puppetversion => undef"; + facts["augeasversion => undef"; + facts["rubysitedir => undef"; + facts["rubyversion => undef"; */ } // block devices -void dump_blockdevice_facts() +void get_blockdevice_facts(fact_map& facts) { string blockdevices = ""; DIR *sys_block_dir = opendir("/sys/block"); @@ -390,45 +389,48 @@ void dump_blockdevice_facts() blockdevices += bd->d_name; string model_file = "/sys/block/" + string(bd->d_name) + "/device/model"; - cout << "blockdevice_" << bd->d_name << "_model => " << - read_oneline_file(model_file) << endl; + facts[string("blockdevice_") + bd->d_name + "_model"] = + read_oneline_file(model_file); string vendor_file = "/sys/block/" + string(bd->d_name) + "/device/vendor"; - cout << "blockdevice_" << bd->d_name << "_vendor => " << - read_oneline_file(vendor_file) << endl; + facts[string("blockdevice_") + bd->d_name + "_vendor"] = + read_oneline_file(vendor_file); string size_file = "/sys/block/" + string(bd->d_name) + "/size"; string size_line = read_oneline_file(size_file); int64_t size; // SCNd64 didn't work here?? sscanf(size_line.c_str(), "%lld", (long long int *)&size); - cout << "blockdevice_" << bd->d_name << "_size => " << size * 512 << endl; + facts[string("blockdevice_") + bd->d_name + "_size"] = to_string(size * 512); } - cout << "blockdevices => " << blockdevices << endl; + facts["blockdevices"] = blockdevices; } -void dump_misc_facts() +void get_misc_facts(fact_map& facts) { - cout << "path => " << getenv("PATH") << endl; + facts["path"] = getenv("PATH"); string whoami = popen_stdout("whoami"); - cout << "id => " << trim(whoami) << endl; + facts["id"] = trim(whoami); //timezone char tzstring[16]; time_t t = time(NULL); struct tm *loc = localtime(&t); strftime(tzstring, sizeof(tzstring - 1), "%Z", loc); - cout << "timezone => " << tzstring << endl; + facts["timezone"] = tzstring; } -// dump just one fact, optionally in two formats -static void dump_mem_fact(std::string fact_name, int fact_value, bool dump_mb_variant = true) +// get just one fact, optionally in two formats +static void get_mem_fact(std::string fact_name, int fact_value, fact_map& facts, + bool get_mb_variant = true) { float fact_value_scaled = fact_value / 1024.0; + char float_buf[32]; - if (dump_mb_variant) { - cout << fact_name << "_mb => " << fixed << setprecision(2) << fact_value_scaled << endl; + if (get_mb_variant) { + snprintf(float_buf, sizeof(float_buf) - 1, "%.2f", fact_value_scaled); + facts[string(fact_name) + "_mb"] = float_buf; } int scale_index; @@ -437,20 +439,21 @@ static void dump_mem_fact(std::string fact_name, int fact_value, bool dump_mb_va fact_value_scaled /= 1024.0, ++scale_index) ; std::string scale[4] = {"MB", "GB", "TB", "PB"}; // oh yeah, petabytes ... - - cout << fact_name << " => " << fixed << setprecision(2) << fact_value_scaled << scale[scale_index] << endl; + + snprintf(float_buf, sizeof(float_buf) - 1, "%.2f", fact_value_scaled); + facts[fact_name] = string(float_buf) + scale[scale_index]; } -void dump_mem_facts() +void get_mem_facts(fact_map& facts) { std::ifstream oneline_file("/proc/meminfo", std::ifstream::in); std::string line; // The MemFree fact is the sum of MemFree + Buffer + Cached from /proc/meninfo, - // so sum that one as we go, and dump it out at the end. - // The other three memory facts are straight from /proc/meminfo, so dump those + // so sum that one as we go, and get it out at the end. + // The other three memory facts are straight from /proc/meminfo, so get those // as we go. - // All four facts are dumped in two formats: + // All four facts are geted in two formats: // _mb => %.2f // => %.2f %s (where the suffix string is one of MB/GB/TB) // And then there is a ninth fact, 'memorytotal', which is the same as 'memorysize'. @@ -467,19 +470,19 @@ void dump_mem_facts() if (tokens[0] == "MemTotal:") { int mem_total = atoi(tokens[1].c_str()); - dump_mem_fact("memorysize", mem_total); - dump_mem_fact("memorytotal", mem_total, false); + get_mem_fact("memorysize", mem_total, facts); + get_mem_fact("memorytotal", mem_total, facts, false); } else if (tokens[0] == "MemFree:" || tokens[0] == "Cached:" || tokens[0] == "Buffers:") { memoryfree += atoi(tokens[1].c_str()); } else if (tokens[0] == "SwapTotal:") - dump_mem_fact("swapsize", atoi(tokens[1].c_str())); + get_mem_fact("swapsize", atoi(tokens[1].c_str()), facts); else if (tokens[0] == "SwapFree:") - dump_mem_fact("swapfree", atoi(tokens[1].c_str())); + get_mem_fact("swapfree", atoi(tokens[1].c_str()), facts); } - dump_mem_fact("memoryfree", memoryfree); + get_mem_fact("memoryfree", memoryfree, facts); } static string get_selinux_path() @@ -516,39 +519,39 @@ static bool selinux() string selinux_enforce_path = selinux_path + "/enforce"; string security_attr_path = "/proc/self/attr/current"; - if (file_exist(selinux_enforce_path) && file_exist(security_attr_path) && read_oneline_file(security_attr_path) != "kernel") + if (file_exist(selinux_enforce_path) && file_exist(security_attr_path) && + read_oneline_file(security_attr_path) != "kernel") return true; return false; } -void dump_selinux_facts() +void get_selinux_facts(fact_map& facts) { if (!selinux()) { - cout << "selinux => false" << endl; + facts["selinux"] = "false"; return; } - map selinux_map; - selinux_map["selinux"] = "true"; + facts["selinux"] = "true"; // defaults from facter - selinux_map["selinux_enforced"] = "false"; - selinux_map["selinux_policyversion"] = "unknown"; - selinux_map["selinux_current_mode"] = "unknown"; - selinux_map["selinux_config_mode"] = "unknown"; - selinux_map["selinux_config_policy"] = "unknown"; - selinux_map["selinux_mode"] = "unknown"; + facts["selinux_enforced"] = "false"; + facts["selinux_policyversion"] = "unknown"; + facts["selinux_current_mode"] = "unknown"; + facts["selinux_config_mode"] = "unknown"; + facts["selinux_config_policy"] = "unknown"; + facts["selinux_mode"] = "unknown"; string selinux_path = get_selinux_path(); string selinux_enforce_path = selinux_path + "/enforce"; if (file_exist(selinux_enforce_path)) - selinux_map["selinux_enforced"] = ((read_oneline_file(selinux_enforce_path) == "1") ? "true" : "false"); + facts["selinux_enforced"] = ((read_oneline_file(selinux_enforce_path) == "1") ? "true" : "false"); string selinux_policyvers_path = selinux_path + "/policyvers"; if (file_exist(selinux_policyvers_path)) - selinux_map["selinux_policyversion"] = read_oneline_file(selinux_policyvers_path); + facts["selinux_policyversion"] = read_oneline_file(selinux_policyvers_path); string selinux_cmd = "/usr/sbin/sestatus"; FILE* pipe = popen(selinux_cmd.c_str(), "r"); @@ -561,27 +564,22 @@ void dump_selinux_facts() split(buffer, ':', elems); if (elems.size() < 2) continue; // shouldn't happen if (elems[0] == "Current mode") { - selinux_map["selinux_current_mode"] = trim(elems[1]); + facts["selinux_current_mode"] = trim(elems[1]); } else if (elems[0] == "Mode from config file") { - selinux_map["selinux_config_mode"] = trim(elems[1]); + facts["selinux_config_mode"] = trim(elems[1]); } else if (elems[0] == "Policy from config file") { - selinux_map["selinux_config_policy"] = trim(elems[1]); - selinux_map["selinux_mode"] = trim(elems[1]); + facts["selinux_config_policy"] = trim(elems[1]); + facts["selinux_mode"] = trim(elems[1]); } } } pclose(pipe); - - typedef map::iterator iter; - for (iter i = selinux_map.begin(); i != selinux_map.end(); ++i) { - cout << i->first << " => " << i->second << endl; - } } -static void dump_ssh_fact(string fact_name, string path_name) +static void get_ssh_fact(string fact_name, string path_name, fact_map& facts) { string ssh_directories[] = { "/etc/ssh", @@ -598,7 +596,7 @@ static void dump_ssh_fact(string fact_name, string path_name) vector tokens; tokenize(trim(key), tokens); if (tokens.size() < 2) continue; // should never happen - cout << fact_name << " => " << tokens[1] << endl; + facts[fact_name] = tokens[1]; // skpping the finger print facts, which require base64 decode and sha libs // on the cmd line it would be something like the result of these two: @@ -611,21 +609,21 @@ static void dump_ssh_fact(string fact_name, string path_name) } // no support for the sshfp facts, which require base64/sha1sum code -void dump_ssh_facts() +void get_ssh_facts(fact_map& facts) { // not til C++11 do we have static initialization of stl maps - map fact_map; - fact_map["sshdsakey"] = "ssh_host_dsa_key.pub"; - fact_map["sshrsakey"] = "ssh_host_rsa_key.pub"; - fact_map["sshecdsakey"] = "ssh_host_ecdsa_key.pub"; + map ssh_facts; + ssh_facts["sshdsakey"] = "ssh_host_dsa_key.pub"; + ssh_facts["sshrsakey"] = "ssh_host_rsa_key.pub"; + ssh_facts["sshecdsakey"] = "ssh_host_ecdsa_key.pub"; typedef map::iterator iter; - for (iter i = fact_map.begin(); i != fact_map.end(); ++i) { - dump_ssh_fact(i->first, i->second); + for (iter i = ssh_facts.begin(); i != ssh_facts.end(); ++i) { + get_ssh_fact(i->first, i->second, facts); } } -static void dump_physicalprocessorcount_fact() +static void get_physicalprocessorcount_fact(fact_map& facts) { // So, facter has logic to use /sys and fallback to /proc // but I don't know why the /sys support was added; research needed. @@ -638,7 +636,6 @@ static void dump_physicalprocessorcount_fact() char buf[10]; snprintf(buf, sizeof(buf) - 1, "%u", i); string cpu_phys_file = sysfs_cpu_directory + "/cpu" + buf + "/topology/physical_package_id"; - cout << cpu_phys_file << endl; if (!file_exist(cpu_phys_file)) break; @@ -647,14 +644,14 @@ static void dump_physicalprocessorcount_fact() sort(package_ids.begin(), package_ids.end()); unique(package_ids.begin(), package_ids.end()); - cout << "physicalprocessorcount => " << package_ids.size() << endl; + facts["physicalprocessorcount"] = to_string(package_ids.size()); } else { // here's where the fall back to /proc/cpuinfo would go } } -void dump_processorcount_fact() +void get_processorcount_fact(fact_map& facts) { std::ifstream cpuinfo_file("/proc/cpuinfo", std::ifstream::in); std::string line; @@ -672,21 +669,21 @@ void dump_processorcount_fact() } else if (key == "model name") { string tmp = line.substr(sep + 1, string::npos); - cout << "processor" << current_processor_number << " => " << trim(tmp) << endl; + facts[string("processor") + current_processor_number] = trim(tmp); } } // this was added after 1.7.3, omit for now, needs investigation - if (false) cout << "activeprocessorcount => " << processor_count << endl; - cout << "processorcount => " << processor_count << endl; + if (false) facts["activeprocessorcount"] = processor_count; + facts["processorcount"] = to_string(processor_count); } -void dump_processor_facts() +void get_processor_facts(fact_map& facts) { - dump_physicalprocessorcount_fact(); - dump_processorcount_fact(); + get_physicalprocessorcount_fact(facts); + get_processorcount_fact(facts); } -void dump_architecture_facts() +void get_architecture_facts(fact_map& facts) { struct utsname uts; if (uname(&uts) == 0) { @@ -695,13 +692,13 @@ void dump_architecture_facts() // relies on 'uname -p' here and that commonizes, this should perhaps just shell out // and not reproduce that logic. Regardless, need to survey cross-platform here and // take it from there. - cout << "hardwaremodel => " << uts.machine << endl; - cout << "hardwareisa => " << uts.machine << endl; - cout << "architecture => " << uts.machine << endl; + facts["hardwaremodel"] = uts.machine; + facts["hardwareisa"] = uts.machine; + facts["architecture"] = uts.machine; } } -void dump_dmidecode_facts() +void get_dmidecode_facts(fact_map& facts) { // from a time perspective simulate expense with lspci and dmidecode invocations string dmidecode_output = popen_stdout("/usr/sbin/dmidecode"); @@ -757,17 +754,17 @@ void dump_dmidecode_facts() if (ci_key == "vendor") { string tmp = line.substr(sep + 1, string::npos); string value = trim(tmp); - cout << "bios_vendor => " << value << endl; + facts["bios_vendor"] = value; } if (ci_key == "version") { string tmp = line.substr(sep + 1, string::npos); string value = trim(tmp); - cout << "bios_version => " << value << endl; + facts["bios_version"] = value; } if (ci_key == "release date") { string tmp = line.substr(sep + 1, string::npos); string value = trim(tmp); - cout << "bios_release_date => " << value << endl; + facts["bios_release_date"] = value; } } else if (dmi_section == base_board_information) { @@ -775,17 +772,17 @@ void dump_dmidecode_facts() if (ci_key == "manufacturer") { string tmp = line.substr(sep + 1, string::npos); string value = trim(tmp); - cout << "boardmanufacturer => " << value << endl; + facts["boardmanufacturer"] = value; } if (ci_key == "product name" || ci_key == "product") { string tmp = line.substr(sep + 1, string::npos); string value = trim(tmp); - cout << "boardproductname => " << value << endl; + facts["boardproductname"] = value; } if (ci_key == "serial number") { string tmp = line.substr(sep + 1, string::npos); string value = trim(tmp); - cout << "boardserialnumber => " << value << endl; + facts["boardserialnumber"] = value; } } else if (dmi_section == system_information) { @@ -793,22 +790,22 @@ void dump_dmidecode_facts() if (ci_key == "manufacturer") { string tmp = line.substr(sep + 1, string::npos); string value = trim(tmp); - cout << "manufacturer => " << value << endl; + facts["manufacturer"] = value; } if (ci_key == "product name" || ci_key == "product") { string tmp = line.substr(sep + 1, string::npos); string value = trim(tmp); - cout << "productname => " << value << endl; + facts["productname"] = value; } if (ci_key == "serial number") { string tmp = line.substr(sep + 1, string::npos); string value = trim(tmp); - cout << "serialnumber => " << value << endl; + facts["serialnumber"] = value; } if (ci_key == "uuid") { string tmp = line.substr(sep + 1, string::npos); string value = trim(tmp); - cout << "uuid => " << value << endl; + facts["uuid"] = value; } } else if (dmi_section == chassis_information) { @@ -816,14 +813,14 @@ void dump_dmidecode_facts() if (ci_key == "chassis type" || ci_key == "type") { string tmp = line.substr(sep + 1, string::npos); string value = trim(tmp); - cout << "type => " << value << endl; + facts["type"] = value; } } } } } -void dump_filesystems_facts() +void get_filesystems_facts(fact_map& facts) { std::ifstream cpuinfo_file("/proc/filesystems", std::ifstream::in); std::string line; @@ -837,16 +834,17 @@ void dump_filesystems_facts() filesystems += trim(line); } - cout << "filesystems => " << filesystems << endl; + facts["filesystems"] = filesystems; } -void dump_hostname_facts() +void get_hostname_facts(fact_map& facts) { // there's some history here, perhaps just port the facter conditional straight across? // so this is short-term string hostname_output = popen_stdout("hostname"); unsigned sep = hostname_output.find("."); - string hostname = hostname_output.substr(0, sep); + string hostname1 = hostname_output.substr(0, sep); + string hostname = trim(hostname1); ifstream resolv_conf_file("/etc/resolv.conf", std::ifstream::in); string line; @@ -865,7 +863,7 @@ void dump_hostname_facts() if (domain.empty() && !search.empty()) domain = search; - cout << "hostname => " << hostname << endl; - cout << "domain => " << domain << endl; - cout << "fqdn => " << hostname << "." << domain << endl; + facts["hostname"] = hostname; + facts["domain"] = domain; + facts["fqdn"] = hostname + "." + domain; } diff --git a/cfacterlib.h b/cfacterlib.h index b780ebb55f..6fa6c800dc 100644 --- a/cfacterlib.h +++ b/cfacterlib.h @@ -1,21 +1,22 @@ -extern "C" { +#include +#include - void dump_network_facts(); - void dump_kernel_facts(); - void dump_blockdevice_facts(); - void dump_operatingsystem_facts(); - void dump_uptime_facts(); - void dump_virtual_facts(); - void dump_hardwired_facts(); - void dump_misc_facts(); - void dump_ruby_lib_versions(); - void dump_mem_facts(); - void dump_selinux_facts(); - void dump_ssh_facts(); - void dump_processor_facts(); - void dump_architecture_facts(); - void dump_dmidecode_facts(); - void dump_filesystems_facts(); - void dump_hostname_facts(); +typedef std::map fact_map; -} +void get_network_facts(fact_map&); +void get_kernel_facts(fact_map&); +void get_blockdevice_facts(fact_map&); +void get_operatingsystem_facts(fact_map&); +void get_uptime_facts(fact_map&); +void get_virtual_facts(fact_map&); +void get_hardwired_facts(fact_map&); +void get_misc_facts(fact_map&); +void get_ruby_lib_versions(fact_map&); +void get_mem_facts(fact_map&); +void get_selinux_facts(fact_map&); +void get_ssh_facts(fact_map&); +void get_processor_facts(fact_map&); +void get_architecture_facts(fact_map&); +void get_dmidecode_facts(fact_map&); +void get_filesystems_facts(fact_map&); +void get_hostname_facts(fact_map&); From 1fb66ac93db525c27aeadf3ea3d2057f1e00ed84 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Wed, 11 Dec 2013 22:40:55 +0000 Subject: [PATCH 1484/3753] Add rapidjson v0.11 --- Makefile | 4 +- rapidjson/document.h | 821 +++++++++++++++++++++++++++++++++++ rapidjson/filestream.h | 46 ++ rapidjson/internal/pow10.h | 54 +++ rapidjson/internal/stack.h | 82 ++++ rapidjson/internal/strfunc.h | 24 + rapidjson/prettywriter.h | 156 +++++++ rapidjson/rapidjson.h | 525 ++++++++++++++++++++++ rapidjson/reader.h | 683 +++++++++++++++++++++++++++++ rapidjson/stringbuffer.h | 49 +++ rapidjson/writer.h | 241 ++++++++++ 11 files changed, 2683 insertions(+), 2 deletions(-) create mode 100644 rapidjson/document.h create mode 100644 rapidjson/filestream.h create mode 100644 rapidjson/internal/pow10.h create mode 100644 rapidjson/internal/stack.h create mode 100644 rapidjson/internal/strfunc.h create mode 100644 rapidjson/prettywriter.h create mode 100644 rapidjson/rapidjson.h create mode 100644 rapidjson/reader.h create mode 100644 rapidjson/stringbuffer.h create mode 100644 rapidjson/writer.h diff --git a/Makefile b/Makefile index c93e62b1dc..790b1a7873 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ cfacter: cfacter.cc cfacterlib.cc cfacterlib.h - g++ -std=c++0x -g -o cfacter cfacter.cc cfacterlib.cc + g++ -std=c++0x -g -o cfacter cfacter.cc cfacterlib.cc -I rapidjson cfacterlib.o: cfacterlib.cc cfacterlib.h - g++ -std=c++0x -g -fPIC -c -o $@ cfacterlib.cc + g++ -std=c++0x -g -fPIC -c -o $@ cfacterlib.cc -I rapidjson cfacterlib.so: cfacterlib.o g++ -std=c++0x -g -o $@ $^ -fPIC -shared diff --git a/rapidjson/document.h b/rapidjson/document.h new file mode 100644 index 0000000000..402b65d871 --- /dev/null +++ b/rapidjson/document.h @@ -0,0 +1,821 @@ +#ifndef RAPIDJSON_DOCUMENT_H_ +#define RAPIDJSON_DOCUMENT_H_ + +#include "reader.h" +#include "internal/strfunc.h" +#include // placement new + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4127) // conditional expression is constant +#endif + +namespace rapidjson { + +/////////////////////////////////////////////////////////////////////////////// +// GenericValue + +//! Represents a JSON value. Use Value for UTF8 encoding and default allocator. +/*! + A JSON value can be one of 7 types. This class is a variant type supporting + these types. + + Use the Value if UTF8 and default allocator + + \tparam Encoding Encoding of the value. (Even non-string values need to have the same encoding in a document) + \tparam Allocator Allocator type for allocating memory of object, array and string. +*/ +#pragma pack (push, 4) +template > +class GenericValue { +public: + //! Name-value pair in an object. + struct Member { + GenericValue name; //!< name of member (must be a string) + GenericValue value; //!< value of member. + }; + + typedef Encoding EncodingType; //!< Encoding type from template parameter. + typedef Allocator AllocatorType; //!< Allocator type from template parameter. + typedef typename Encoding::Ch Ch; //!< Character type derived from Encoding. + typedef Member* MemberIterator; //!< Member iterator for iterating in object. + typedef const Member* ConstMemberIterator; //!< Constant member iterator for iterating in object. + typedef GenericValue* ValueIterator; //!< Value iterator for iterating in array. + typedef const GenericValue* ConstValueIterator; //!< Constant value iterator for iterating in array. + + //!@name Constructors and destructor. + //@{ + + //! Default constructor creates a null value. + GenericValue() : flags_(kNullFlag) {} + + //! Copy constructor is not permitted. +private: + GenericValue(const GenericValue& rhs); + +public: + + //! Constructor with JSON value type. + /*! This creates a Value of specified type with default content. + \param type Type of the value. + \note Default content for number is zero. + */ + GenericValue(Type type) { + static const unsigned defaultFlags[7] = { + kNullFlag, kFalseFlag, kTrueFlag, kObjectFlag, kArrayFlag, kConstStringFlag, + kNumberFlag | kIntFlag | kUintFlag | kInt64Flag | kUint64Flag | kDoubleFlag + }; + RAPIDJSON_ASSERT(type <= kNumberType); + flags_ = defaultFlags[type]; + memset(&data_, 0, sizeof(data_)); + } + + //! Constructor for boolean value. + GenericValue(bool b) : flags_(b ? kTrueFlag : kFalseFlag) {} + + //! Constructor for int value. + GenericValue(int i) : flags_(kNumberIntFlag) { + data_.n.i64 = i; + if (i >= 0) + flags_ |= kUintFlag | kUint64Flag; + } + + //! Constructor for unsigned value. + GenericValue(unsigned u) : flags_(kNumberUintFlag) { + data_.n.u64 = u; + if (!(u & 0x80000000)) + flags_ |= kIntFlag | kInt64Flag; + } + + //! Constructor for int64_t value. + GenericValue(int64_t i64) : flags_(kNumberInt64Flag) { + data_.n.i64 = i64; + if (i64 >= 0) { + flags_ |= kNumberUint64Flag; + if (!(i64 & 0xFFFFFFFF00000000LL)) + flags_ |= kUintFlag; + if (!(i64 & 0xFFFFFFFF80000000LL)) + flags_ |= kIntFlag; + } + else if (i64 >= -2147483648LL) + flags_ |= kIntFlag; + } + + //! Constructor for uint64_t value. + GenericValue(uint64_t u64) : flags_(kNumberUint64Flag) { + data_.n.u64 = u64; + if (!(u64 & 0x8000000000000000ULL)) + flags_ |= kInt64Flag; + if (!(u64 & 0xFFFFFFFF00000000ULL)) + flags_ |= kUintFlag; + if (!(u64 & 0xFFFFFFFF80000000ULL)) + flags_ |= kIntFlag; + } + + //! Constructor for double value. + GenericValue(double d) : flags_(kNumberDoubleFlag) { data_.n.d = d; } + + //! Constructor for constant string (i.e. do not make a copy of string) + GenericValue(const Ch* s, SizeType length) { + RAPIDJSON_ASSERT(s != NULL); + flags_ = kConstStringFlag; + data_.s.str = s; + data_.s.length = length; + } + + //! Constructor for constant string (i.e. do not make a copy of string) + GenericValue(const Ch* s) { SetStringRaw(s, internal::StrLen(s)); } + + //! Constructor for copy-string (i.e. do make a copy of string) + GenericValue(const Ch* s, SizeType length, Allocator& allocator) { SetStringRaw(s, length, allocator); } + + //! Constructor for copy-string (i.e. do make a copy of string) + GenericValue(const Ch*s, Allocator& allocator) { SetStringRaw(s, internal::StrLen(s), allocator); } + + //! Destructor. + /*! Need to destruct elements of array, members of object, or copy-string. + */ + ~GenericValue() { + if (Allocator::kNeedFree) { // Shortcut by Allocator's trait + switch(flags_) { + case kArrayFlag: + for (GenericValue* v = data_.a.elements; v != data_.a.elements + data_.a.size; ++v) + v->~GenericValue(); + Allocator::Free(data_.a.elements); + break; + + case kObjectFlag: + for (Member* m = data_.o.members; m != data_.o.members + data_.o.size; ++m) { + m->name.~GenericValue(); + m->value.~GenericValue(); + } + Allocator::Free(data_.o.members); + break; + + case kCopyStringFlag: + Allocator::Free(const_cast(data_.s.str)); + break; + } + } + } + + //@} + + //!@name Assignment operators + //@{ + + //! Assignment with move semantics. + /*! \param rhs Source of the assignment. It will become a null value after assignment. + */ + GenericValue& operator=(GenericValue& rhs) { + RAPIDJSON_ASSERT(this != &rhs); + this->~GenericValue(); + memcpy(this, &rhs, sizeof(GenericValue)); + rhs.flags_ = kNullFlag; + return *this; + } + + //! Assignment with primitive types. + /*! \tparam T Either Type, int, unsigned, int64_t, uint64_t, const Ch* + \param value The value to be assigned. + */ + template + GenericValue& operator=(T value) { + this->~GenericValue(); + new (this) GenericValue(value); + return *this; + } + //@} + + //!@name Type + //@{ + + Type GetType() const { return static_cast(flags_ & kTypeMask); } + bool IsNull() const { return flags_ == kNullFlag; } + bool IsFalse() const { return flags_ == kFalseFlag; } + bool IsTrue() const { return flags_ == kTrueFlag; } + bool IsBool() const { return (flags_ & kBoolFlag) != 0; } + bool IsObject() const { return flags_ == kObjectFlag; } + bool IsArray() const { return flags_ == kArrayFlag; } + bool IsNumber() const { return (flags_ & kNumberFlag) != 0; } + bool IsInt() const { return (flags_ & kIntFlag) != 0; } + bool IsUint() const { return (flags_ & kUintFlag) != 0; } + bool IsInt64() const { return (flags_ & kInt64Flag) != 0; } + bool IsUint64() const { return (flags_ & kUint64Flag) != 0; } + bool IsDouble() const { return (flags_ & kDoubleFlag) != 0; } + bool IsString() const { return (flags_ & kStringFlag) != 0; } + + //@} + + //!@name Null + //@{ + + GenericValue& SetNull() { this->~GenericValue(); new (this) GenericValue(); return *this; } + + //@} + + //!@name Bool + //@{ + + bool GetBool() const { RAPIDJSON_ASSERT(IsBool()); return flags_ == kTrueFlag; } + GenericValue& SetBool(bool b) { this->~GenericValue(); new (this) GenericValue(b); return *this; } + + //@} + + //!@name Object + //@{ + + //! Set this value as an empty object. + GenericValue& SetObject() { this->~GenericValue(); new (this) GenericValue(kObjectType); return *this; } + + //! Get the value associated with the object's name. + GenericValue& operator[](const Ch* name) { + if (Member* member = FindMember(name)) + return member->value; + else { + static GenericValue NullValue; + return NullValue; + } + } + const GenericValue& operator[](const Ch* name) const { return const_cast(*this)[name]; } + + //! Member iterators. + ConstMemberIterator MemberBegin() const { RAPIDJSON_ASSERT(IsObject()); return data_.o.members; } + ConstMemberIterator MemberEnd() const { RAPIDJSON_ASSERT(IsObject()); return data_.o.members + data_.o.size; } + MemberIterator MemberBegin() { RAPIDJSON_ASSERT(IsObject()); return data_.o.members; } + MemberIterator MemberEnd() { RAPIDJSON_ASSERT(IsObject()); return data_.o.members + data_.o.size; } + + //! Check whether a member exists in the object. + bool HasMember(const Ch* name) const { return FindMember(name) != 0; } + + //! Add a member (name-value pair) to the object. + /*! \param name A string value as name of member. + \param value Value of any type. + \param allocator Allocator for reallocating memory. + \return The value itself for fluent API. + \note The ownership of name and value will be transfered to this object if success. + */ + GenericValue& AddMember(GenericValue& name, GenericValue& value, Allocator& allocator) { + RAPIDJSON_ASSERT(IsObject()); + RAPIDJSON_ASSERT(name.IsString()); + Object& o = data_.o; + if (o.size >= o.capacity) { + if (o.capacity == 0) { + o.capacity = kDefaultObjectCapacity; + o.members = (Member*)allocator.Malloc(o.capacity * sizeof(Member)); + } + else { + SizeType oldCapacity = o.capacity; + o.capacity *= 2; + o.members = (Member*)allocator.Realloc(o.members, oldCapacity * sizeof(Member), o.capacity * sizeof(Member)); + } + } + o.members[o.size].name.RawAssign(name); + o.members[o.size].value.RawAssign(value); + o.size++; + return *this; + } + + GenericValue& AddMember(const Ch* name, Allocator& nameAllocator, GenericValue& value, Allocator& allocator) { + GenericValue n(name, internal::StrLen(name), nameAllocator); + return AddMember(n, value, allocator); + } + + GenericValue& AddMember(const Ch* name, GenericValue& value, Allocator& allocator) { + GenericValue n(name, internal::StrLen(name)); + return AddMember(n, value, allocator); + } + + template + GenericValue& AddMember(const Ch* name, T value, Allocator& allocator) { + GenericValue n(name, internal::StrLen(name)); + GenericValue v(value); + return AddMember(n, v, allocator); + } + + //! Remove a member in object by its name. + /*! \param name Name of member to be removed. + \return Whether the member existed. + \note Removing member is implemented by moving the last member. So the ordering of members is changed. + */ + bool RemoveMember(const Ch* name) { + RAPIDJSON_ASSERT(IsObject()); + if (Member* m = FindMember(name)) { + RAPIDJSON_ASSERT(data_.o.size > 0); + RAPIDJSON_ASSERT(data_.o.members != 0); + + Member* last = data_.o.members + (data_.o.size - 1); + if (data_.o.size > 1 && m != last) { + // Move the last one to this place + m->name = last->name; + m->value = last->value; + } + else { + // Only one left, just destroy + m->name.~GenericValue(); + m->value.~GenericValue(); + } + --data_.o.size; + return true; + } + return false; + } + + //@} + + //!@name Array + //@{ + + //! Set this value as an empty array. + GenericValue& SetArray() { this->~GenericValue(); new (this) GenericValue(kArrayType); return *this; } + + //! Get the number of elements in array. + SizeType Size() const { RAPIDJSON_ASSERT(IsArray()); return data_.a.size; } + + //! Get the capacity of array. + SizeType Capacity() const { RAPIDJSON_ASSERT(IsArray()); return data_.a.capacity; } + + //! Check whether the array is empty. + bool Empty() const { RAPIDJSON_ASSERT(IsArray()); return data_.a.size == 0; } + + //! Remove all elements in the array. + /*! This function do not deallocate memory in the array, i.e. the capacity is unchanged. + */ + void Clear() { + RAPIDJSON_ASSERT(IsArray()); + for (SizeType i = 0; i < data_.a.size; ++i) + data_.a.elements[i].~GenericValue(); + data_.a.size = 0; + } + + //! Get an element from array by index. + /*! \param index Zero-based index of element. + \note +\code +Value a(kArrayType); +a.PushBack(123); +int x = a[0].GetInt(); // Error: operator[ is ambiguous, as 0 also mean a null pointer of const char* type. +int y = a[SizeType(0)].GetInt(); // Cast to SizeType will work. +int z = a[0u].GetInt(); // This works too. +\endcode + */ + GenericValue& operator[](SizeType index) { + RAPIDJSON_ASSERT(IsArray()); + RAPIDJSON_ASSERT(index < data_.a.size); + return data_.a.elements[index]; + } + const GenericValue& operator[](SizeType index) const { return const_cast(*this)[index]; } + + //! Element iterator + ValueIterator Begin() { RAPIDJSON_ASSERT(IsArray()); return data_.a.elements; } + ValueIterator End() { RAPIDJSON_ASSERT(IsArray()); return data_.a.elements + data_.a.size; } + ConstValueIterator Begin() const { return const_cast(*this).Begin(); } + ConstValueIterator End() const { return const_cast(*this).End(); } + + //! Request the array to have enough capacity to store elements. + /*! \param newCapacity The capacity that the array at least need to have. + \param allocator The allocator for allocating memory. It must be the same one use previously. + \return The value itself for fluent API. + */ + GenericValue& Reserve(SizeType newCapacity, Allocator &allocator) { + RAPIDJSON_ASSERT(IsArray()); + if (newCapacity > data_.a.capacity) { + data_.a.elements = (GenericValue*)allocator.Realloc(data_.a.elements, data_.a.capacity * sizeof(GenericValue), newCapacity * sizeof(GenericValue)); + data_.a.capacity = newCapacity; + } + return *this; + } + + //! Append a value at the end of the array. + /*! \param value The value to be appended. + \param allocator The allocator for allocating memory. It must be the same one use previously. + \return The value itself for fluent API. + \note The ownership of the value will be transfered to this object if success. + \note If the number of elements to be appended is known, calls Reserve() once first may be more efficient. + */ + GenericValue& PushBack(GenericValue& value, Allocator& allocator) { + RAPIDJSON_ASSERT(IsArray()); + if (data_.a.size >= data_.a.capacity) + Reserve(data_.a.capacity == 0 ? kDefaultArrayCapacity : data_.a.capacity * 2, allocator); + data_.a.elements[data_.a.size++].RawAssign(value); + return *this; + } + + template + GenericValue& PushBack(T value, Allocator& allocator) { + GenericValue v(value); + return PushBack(v, allocator); + } + + //! Remove the last element in the array. + GenericValue& PopBack() { + RAPIDJSON_ASSERT(IsArray()); + RAPIDJSON_ASSERT(!Empty()); + data_.a.elements[--data_.a.size].~GenericValue(); + return *this; + } + //@} + + //!@name Number + //@{ + + int GetInt() const { RAPIDJSON_ASSERT(flags_ & kIntFlag); return data_.n.i.i; } + unsigned GetUint() const { RAPIDJSON_ASSERT(flags_ & kUintFlag); return data_.n.u.u; } + int64_t GetInt64() const { RAPIDJSON_ASSERT(flags_ & kInt64Flag); return data_.n.i64; } + uint64_t GetUint64() const { RAPIDJSON_ASSERT(flags_ & kUint64Flag); return data_.n.u64; } + + double GetDouble() const { + RAPIDJSON_ASSERT(IsNumber()); + if ((flags_ & kDoubleFlag) != 0) return data_.n.d; // exact type, no conversion. + if ((flags_ & kIntFlag) != 0) return data_.n.i.i; // int -> double + if ((flags_ & kUintFlag) != 0) return data_.n.u.u; // unsigned -> double + if ((flags_ & kInt64Flag) != 0) return (double)data_.n.i64; // int64_t -> double (may lose precision) + RAPIDJSON_ASSERT((flags_ & kUint64Flag) != 0); return (double)data_.n.u64; // uint64_t -> double (may lose precision) + } + + GenericValue& SetInt(int i) { this->~GenericValue(); new (this) GenericValue(i); return *this; } + GenericValue& SetUint(unsigned u) { this->~GenericValue(); new (this) GenericValue(u); return *this; } + GenericValue& SetInt64(int64_t i64) { this->~GenericValue(); new (this) GenericValue(i64); return *this; } + GenericValue& SetUint64(uint64_t u64) { this->~GenericValue(); new (this) GenericValue(u64); return *this; } + GenericValue& SetDouble(double d) { this->~GenericValue(); new (this) GenericValue(d); return *this; } + + //@} + + //!@name String + //@{ + + const Ch* GetString() const { RAPIDJSON_ASSERT(IsString()); return data_.s.str; } + + //! Get the length of string. + /*! Since rapidjson permits "\u0000" in the json string, strlen(v.GetString()) may not equal to v.GetStringLength(). + */ + SizeType GetStringLength() const { RAPIDJSON_ASSERT(IsString()); return data_.s.length; } + + //! Set this value as a string without copying source string. + /*! This version has better performance with supplied length, and also support string containing null character. + \param s source string pointer. + \param length The length of source string, excluding the trailing null terminator. + \return The value itself for fluent API. + */ + GenericValue& SetString(const Ch* s, SizeType length) { this->~GenericValue(); SetStringRaw(s, length); return *this; } + + //! Set this value as a string without copying source string. + /*! \param s source string pointer. + \return The value itself for fluent API. + */ + GenericValue& SetString(const Ch* s) { return SetString(s, internal::StrLen(s)); } + + //! Set this value as a string by copying from source string. + /*! This version has better performance with supplied length, and also support string containing null character. + \param s source string. + \param length The length of source string, excluding the trailing null terminator. + \param allocator Allocator for allocating copied buffer. Commonly use document.GetAllocator(). + \return The value itself for fluent API. + */ + GenericValue& SetString(const Ch* s, SizeType length, Allocator& allocator) { this->~GenericValue(); SetStringRaw(s, length, allocator); return *this; } + + //! Set this value as a string by copying from source string. + /*! \param s source string. + \param allocator Allocator for allocating copied buffer. Commonly use document.GetAllocator(). + \return The value itself for fluent API. + */ + GenericValue& SetString(const Ch* s, Allocator& allocator) { SetString(s, internal::StrLen(s), allocator); return *this; } + + //@} + + //! Generate events of this value to a Handler. + /*! This function adopts the GoF visitor pattern. + Typical usage is to output this JSON value as JSON text via Writer, which is a Handler. + It can also be used to deep clone this value via GenericDocument, which is also a Handler. + \tparam Handler type of handler. + \param handler An object implementing concept Handler. + */ + template + const GenericValue& Accept(Handler& handler) const { + switch(GetType()) { + case kNullType: handler.Null(); break; + case kFalseType: handler.Bool(false); break; + case kTrueType: handler.Bool(true); break; + + case kObjectType: + handler.StartObject(); + for (Member* m = data_.o.members; m != data_.o.members + data_.o.size; ++m) { + handler.String(m->name.data_.s.str, m->name.data_.s.length, false); + m->value.Accept(handler); + } + handler.EndObject(data_.o.size); + break; + + case kArrayType: + handler.StartArray(); + for (GenericValue* v = data_.a.elements; v != data_.a.elements + data_.a.size; ++v) + v->Accept(handler); + handler.EndArray(data_.a.size); + break; + + case kStringType: + handler.String(data_.s.str, data_.s.length, false); + break; + + case kNumberType: + if (IsInt()) handler.Int(data_.n.i.i); + else if (IsUint()) handler.Uint(data_.n.u.u); + else if (IsInt64()) handler.Int64(data_.n.i64); + else if (IsUint64()) handler.Uint64(data_.n.u64); + else handler.Double(data_.n.d); + break; + } + return *this; + } + +private: + template + friend class GenericDocument; + + enum { + kBoolFlag = 0x100, + kNumberFlag = 0x200, + kIntFlag = 0x400, + kUintFlag = 0x800, + kInt64Flag = 0x1000, + kUint64Flag = 0x2000, + kDoubleFlag = 0x4000, + kStringFlag = 0x100000, + kCopyFlag = 0x200000, + + // Initial flags of different types. + kNullFlag = kNullType, + kTrueFlag = kTrueType | kBoolFlag, + kFalseFlag = kFalseType | kBoolFlag, + kNumberIntFlag = kNumberType | kNumberFlag | kIntFlag | kInt64Flag, + kNumberUintFlag = kNumberType | kNumberFlag | kUintFlag | kUint64Flag | kInt64Flag, + kNumberInt64Flag = kNumberType | kNumberFlag | kInt64Flag, + kNumberUint64Flag = kNumberType | kNumberFlag | kUint64Flag, + kNumberDoubleFlag = kNumberType | kNumberFlag | kDoubleFlag, + kConstStringFlag = kStringType | kStringFlag, + kCopyStringFlag = kStringType | kStringFlag | kCopyFlag, + kObjectFlag = kObjectType, + kArrayFlag = kArrayType, + + kTypeMask = 0xFF // bitwise-and with mask of 0xFF can be optimized by compiler + }; + + static const SizeType kDefaultArrayCapacity = 16; + static const SizeType kDefaultObjectCapacity = 16; + + struct String { + const Ch* str; + SizeType length; + unsigned hashcode; //!< reserved + }; // 12 bytes in 32-bit mode, 16 bytes in 64-bit mode + + // By using proper binary layout, retrieval of different integer types do not need conversions. + union Number { +#if RAPIDJSON_ENDIAN == RAPIDJSON_LITTLEENDIAN + struct I { + int i; + char padding[4]; + }i; + struct U { + unsigned u; + char padding2[4]; + }u; +#else + struct I { + char padding[4]; + int i; + }i; + struct U { + char padding2[4]; + unsigned u; + }u; +#endif + int64_t i64; + uint64_t u64; + double d; + }; // 8 bytes + + struct Object { + Member* members; + SizeType size; + SizeType capacity; + }; // 12 bytes in 32-bit mode, 16 bytes in 64-bit mode + + struct Array { + GenericValue* elements; + SizeType size; + SizeType capacity; + }; // 12 bytes in 32-bit mode, 16 bytes in 64-bit mode + + union Data { + String s; + Number n; + Object o; + Array a; + }; // 12 bytes in 32-bit mode, 16 bytes in 64-bit mode + + //! Find member by name. + Member* FindMember(const Ch* name) { + RAPIDJSON_ASSERT(name); + RAPIDJSON_ASSERT(IsObject()); + + SizeType length = internal::StrLen(name); + + Object& o = data_.o; + for (Member* member = o.members; member != data_.o.members + data_.o.size; ++member) + if (length == member->name.data_.s.length && memcmp(member->name.data_.s.str, name, length * sizeof(Ch)) == 0) + return member; + + return 0; + } + const Member* FindMember(const Ch* name) const { return const_cast(*this).FindMember(name); } + + // Initialize this value as array with initial data, without calling destructor. + void SetArrayRaw(GenericValue* values, SizeType count, Allocator& alloctaor) { + flags_ = kArrayFlag; + data_.a.elements = (GenericValue*)alloctaor.Malloc(count * sizeof(GenericValue)); + memcpy(data_.a.elements, values, count * sizeof(GenericValue)); + data_.a.size = data_.a.capacity = count; + } + + //! Initialize this value as object with initial data, without calling destructor. + void SetObjectRaw(Member* members, SizeType count, Allocator& alloctaor) { + flags_ = kObjectFlag; + data_.o.members = (Member*)alloctaor.Malloc(count * sizeof(Member)); + memcpy(data_.o.members, members, count * sizeof(Member)); + data_.o.size = data_.o.capacity = count; + } + + //! Initialize this value as constant string, without calling destructor. + void SetStringRaw(const Ch* s, SizeType length) { + RAPIDJSON_ASSERT(s != NULL); + flags_ = kConstStringFlag; + data_.s.str = s; + data_.s.length = length; + } + + //! Initialize this value as copy string with initial data, without calling destructor. + void SetStringRaw(const Ch* s, SizeType length, Allocator& allocator) { + RAPIDJSON_ASSERT(s != NULL); + flags_ = kCopyStringFlag; + data_.s.str = (Ch *)allocator.Malloc((length + 1) * sizeof(Ch)); + data_.s.length = length; + memcpy(const_cast(data_.s.str), s, length * sizeof(Ch)); + const_cast(data_.s.str)[length] = '\0'; + } + + //! Assignment without calling destructor + void RawAssign(GenericValue& rhs) { + memcpy(this, &rhs, sizeof(GenericValue)); + rhs.flags_ = kNullFlag; + } + + Data data_; + unsigned flags_; +}; +#pragma pack (pop) + +//! Value with UTF8 encoding. +typedef GenericValue > Value; + +/////////////////////////////////////////////////////////////////////////////// +// GenericDocument + +//! A document for parsing JSON text as DOM. +/*! + \implements Handler + \tparam Encoding encoding for both parsing and string storage. + \tparam Alloactor allocator for allocating memory for the DOM, and the stack during parsing. +*/ +template > +class GenericDocument : public GenericValue { +public: + typedef typename Encoding::Ch Ch; //!< Character type derived from Encoding. + typedef GenericValue ValueType; //!< Value type of the document. + typedef Allocator AllocatorType; //!< Allocator type from template parameter. + + //! Constructor + /*! \param allocator Optional allocator for allocating stack memory. + \param stackCapacity Initial capacity of stack in bytes. + */ + GenericDocument(Allocator* allocator = 0, size_t stackCapacity = kDefaultStackCapacity) : stack_(allocator, stackCapacity), parseError_(0), errorOffset_(0) {} + + //! Parse JSON text from an input stream. + /*! \tparam parseFlags Combination of ParseFlag. + \param stream Input stream to be parsed. + \return The document itself for fluent API. + */ + template + GenericDocument& ParseStream(Stream& stream) { + ValueType::SetNull(); // Remove existing root if exist + GenericReader reader; + if (reader.template Parse(stream, *this)) { + RAPIDJSON_ASSERT(stack_.GetSize() == sizeof(ValueType)); // Got one and only one root object + this->RawAssign(*stack_.template Pop(1)); // Add this-> to prevent issue 13. + parseError_ = 0; + errorOffset_ = 0; + } + else { + parseError_ = reader.GetParseError(); + errorOffset_ = reader.GetErrorOffset(); + ClearStack(); + } + return *this; + } + + //! Parse JSON text from a mutable string. + /*! \tparam parseFlags Combination of ParseFlag. + \param str Mutable zero-terminated string to be parsed. + \return The document itself for fluent API. + */ + template + GenericDocument& ParseInsitu(Ch* str) { + GenericInsituStringStream s(str); + return ParseStream(s); + } + + //! Parse JSON text from a read-only string. + /*! \tparam parseFlags Combination of ParseFlag (must not contain kParseInsituFlag). + \param str Read-only zero-terminated string to be parsed. + */ + template + GenericDocument& Parse(const Ch* str) { + RAPIDJSON_ASSERT(!(parseFlags & kParseInsituFlag)); + GenericStringStream s(str); + return ParseStream(s); + } + + //! Whether a parse error was occured in the last parsing. + bool HasParseError() const { return parseError_ != 0; } + + //! Get the message of parsing error. + const char* GetParseError() const { return parseError_; } + + //! Get the offset in character of the parsing error. + size_t GetErrorOffset() const { return errorOffset_; } + + //! Get the allocator of this document. + Allocator& GetAllocator() { return stack_.GetAllocator(); } + + //! Get the capacity of stack in bytes. + size_t GetStackCapacity() const { return stack_.GetCapacity(); } + +private: + // Prohibit assignment + GenericDocument& operator=(const GenericDocument&); + + friend class GenericReader; // for Reader to call the following private handler functions + + // Implementation of Handler + void Null() { new (stack_.template Push()) ValueType(); } + void Bool(bool b) { new (stack_.template Push()) ValueType(b); } + void Int(int i) { new (stack_.template Push()) ValueType(i); } + void Uint(unsigned i) { new (stack_.template Push()) ValueType(i); } + void Int64(int64_t i) { new (stack_.template Push()) ValueType(i); } + void Uint64(uint64_t i) { new (stack_.template Push()) ValueType(i); } + void Double(double d) { new (stack_.template Push()) ValueType(d); } + + void String(const Ch* str, SizeType length, bool copy) { + if (copy) + new (stack_.template Push()) ValueType(str, length, GetAllocator()); + else + new (stack_.template Push()) ValueType(str, length); + } + + void StartObject() { new (stack_.template Push()) ValueType(kObjectType); } + + void EndObject(SizeType memberCount) { + typename ValueType::Member* members = stack_.template Pop(memberCount); + stack_.template Top()->SetObjectRaw(members, (SizeType)memberCount, GetAllocator()); + } + + void StartArray() { new (stack_.template Push()) ValueType(kArrayType); } + + void EndArray(SizeType elementCount) { + ValueType* elements = stack_.template Pop(elementCount); + stack_.template Top()->SetArrayRaw(elements, elementCount, GetAllocator()); + } + + void ClearStack() { + if (Allocator::kNeedFree) + while (stack_.GetSize() > 0) // Here assumes all elements in stack array are GenericValue (Member is actually 2 GenericValue objects) + (stack_.template Pop(1))->~ValueType(); + else + stack_.Clear(); + } + + static const size_t kDefaultStackCapacity = 1024; + internal::Stack stack_; + const char* parseError_; + size_t errorOffset_; +}; + +typedef GenericDocument > Document; + +} // namespace rapidjson + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif // RAPIDJSON_DOCUMENT_H_ diff --git a/rapidjson/filestream.h b/rapidjson/filestream.h new file mode 100644 index 0000000000..24573aa482 --- /dev/null +++ b/rapidjson/filestream.h @@ -0,0 +1,46 @@ +#ifndef RAPIDJSON_FILESTREAM_H_ +#define RAPIDJSON_FILESTREAM_H_ + +#include + +namespace rapidjson { + +//! Wrapper of C file stream for input or output. +/*! + This simple wrapper does not check the validity of the stream. + \implements Stream +*/ +class FileStream { +public: + typedef char Ch; //!< Character type. Only support char. + + FileStream(FILE* fp) : fp_(fp), count_(0) { Read(); } + char Peek() const { return current_; } + char Take() { char c = current_; Read(); return c; } + size_t Tell() const { return count_; } + void Put(char c) { fputc(c, fp_); } + + // Not implemented + char* PutBegin() { return 0; } + size_t PutEnd(char*) { return 0; } + +private: + void Read() { + RAPIDJSON_ASSERT(fp_ != 0); + int c = fgetc(fp_); + if (c != EOF) { + current_ = (char)c; + count_++; + } + else + current_ = '\0'; + } + + FILE* fp_; + char current_; + size_t count_; +}; + +} // namespace rapidjson + +#endif // RAPIDJSON_FILESTREAM_H_ diff --git a/rapidjson/internal/pow10.h b/rapidjson/internal/pow10.h new file mode 100644 index 0000000000..0852539e77 --- /dev/null +++ b/rapidjson/internal/pow10.h @@ -0,0 +1,54 @@ +#ifndef RAPIDJSON_POW10_ +#define RAPIDJSON_POW10_ + +namespace rapidjson { +namespace internal { + +//! Computes integer powers of 10 in double (10.0^n). +/*! This function uses lookup table for fast and accurate results. + \param n positive/negative exponent. Must <= 308. + \return 10.0^n +*/ +inline double Pow10(int n) { + static const double e[] = { // 1e-308...1e308: 617 * 8 bytes = 4936 bytes + 1e-308,1e-307,1e-306,1e-305,1e-304,1e-303,1e-302,1e-301,1e-300, + 1e-299,1e-298,1e-297,1e-296,1e-295,1e-294,1e-293,1e-292,1e-291,1e-290,1e-289,1e-288,1e-287,1e-286,1e-285,1e-284,1e-283,1e-282,1e-281,1e-280, + 1e-279,1e-278,1e-277,1e-276,1e-275,1e-274,1e-273,1e-272,1e-271,1e-270,1e-269,1e-268,1e-267,1e-266,1e-265,1e-264,1e-263,1e-262,1e-261,1e-260, + 1e-259,1e-258,1e-257,1e-256,1e-255,1e-254,1e-253,1e-252,1e-251,1e-250,1e-249,1e-248,1e-247,1e-246,1e-245,1e-244,1e-243,1e-242,1e-241,1e-240, + 1e-239,1e-238,1e-237,1e-236,1e-235,1e-234,1e-233,1e-232,1e-231,1e-230,1e-229,1e-228,1e-227,1e-226,1e-225,1e-224,1e-223,1e-222,1e-221,1e-220, + 1e-219,1e-218,1e-217,1e-216,1e-215,1e-214,1e-213,1e-212,1e-211,1e-210,1e-209,1e-208,1e-207,1e-206,1e-205,1e-204,1e-203,1e-202,1e-201,1e-200, + 1e-199,1e-198,1e-197,1e-196,1e-195,1e-194,1e-193,1e-192,1e-191,1e-190,1e-189,1e-188,1e-187,1e-186,1e-185,1e-184,1e-183,1e-182,1e-181,1e-180, + 1e-179,1e-178,1e-177,1e-176,1e-175,1e-174,1e-173,1e-172,1e-171,1e-170,1e-169,1e-168,1e-167,1e-166,1e-165,1e-164,1e-163,1e-162,1e-161,1e-160, + 1e-159,1e-158,1e-157,1e-156,1e-155,1e-154,1e-153,1e-152,1e-151,1e-150,1e-149,1e-148,1e-147,1e-146,1e-145,1e-144,1e-143,1e-142,1e-141,1e-140, + 1e-139,1e-138,1e-137,1e-136,1e-135,1e-134,1e-133,1e-132,1e-131,1e-130,1e-129,1e-128,1e-127,1e-126,1e-125,1e-124,1e-123,1e-122,1e-121,1e-120, + 1e-119,1e-118,1e-117,1e-116,1e-115,1e-114,1e-113,1e-112,1e-111,1e-110,1e-109,1e-108,1e-107,1e-106,1e-105,1e-104,1e-103,1e-102,1e-101,1e-100, + 1e-99, 1e-98, 1e-97, 1e-96, 1e-95, 1e-94, 1e-93, 1e-92, 1e-91, 1e-90, 1e-89, 1e-88, 1e-87, 1e-86, 1e-85, 1e-84, 1e-83, 1e-82, 1e-81, 1e-80, + 1e-79, 1e-78, 1e-77, 1e-76, 1e-75, 1e-74, 1e-73, 1e-72, 1e-71, 1e-70, 1e-69, 1e-68, 1e-67, 1e-66, 1e-65, 1e-64, 1e-63, 1e-62, 1e-61, 1e-60, + 1e-59, 1e-58, 1e-57, 1e-56, 1e-55, 1e-54, 1e-53, 1e-52, 1e-51, 1e-50, 1e-49, 1e-48, 1e-47, 1e-46, 1e-45, 1e-44, 1e-43, 1e-42, 1e-41, 1e-40, + 1e-39, 1e-38, 1e-37, 1e-36, 1e-35, 1e-34, 1e-33, 1e-32, 1e-31, 1e-30, 1e-29, 1e-28, 1e-27, 1e-26, 1e-25, 1e-24, 1e-23, 1e-22, 1e-21, 1e-20, + 1e-19, 1e-18, 1e-17, 1e-16, 1e-15, 1e-14, 1e-13, 1e-12, 1e-11, 1e-10, 1e-9, 1e-8, 1e-7, 1e-6, 1e-5, 1e-4, 1e-3, 1e-2, 1e-1, 1e+0, + 1e+1, 1e+2, 1e+3, 1e+4, 1e+5, 1e+6, 1e+7, 1e+8, 1e+9, 1e+10, 1e+11, 1e+12, 1e+13, 1e+14, 1e+15, 1e+16, 1e+17, 1e+18, 1e+19, 1e+20, + 1e+21, 1e+22, 1e+23, 1e+24, 1e+25, 1e+26, 1e+27, 1e+28, 1e+29, 1e+30, 1e+31, 1e+32, 1e+33, 1e+34, 1e+35, 1e+36, 1e+37, 1e+38, 1e+39, 1e+40, + 1e+41, 1e+42, 1e+43, 1e+44, 1e+45, 1e+46, 1e+47, 1e+48, 1e+49, 1e+50, 1e+51, 1e+52, 1e+53, 1e+54, 1e+55, 1e+56, 1e+57, 1e+58, 1e+59, 1e+60, + 1e+61, 1e+62, 1e+63, 1e+64, 1e+65, 1e+66, 1e+67, 1e+68, 1e+69, 1e+70, 1e+71, 1e+72, 1e+73, 1e+74, 1e+75, 1e+76, 1e+77, 1e+78, 1e+79, 1e+80, + 1e+81, 1e+82, 1e+83, 1e+84, 1e+85, 1e+86, 1e+87, 1e+88, 1e+89, 1e+90, 1e+91, 1e+92, 1e+93, 1e+94, 1e+95, 1e+96, 1e+97, 1e+98, 1e+99, 1e+100, + 1e+101,1e+102,1e+103,1e+104,1e+105,1e+106,1e+107,1e+108,1e+109,1e+110,1e+111,1e+112,1e+113,1e+114,1e+115,1e+116,1e+117,1e+118,1e+119,1e+120, + 1e+121,1e+122,1e+123,1e+124,1e+125,1e+126,1e+127,1e+128,1e+129,1e+130,1e+131,1e+132,1e+133,1e+134,1e+135,1e+136,1e+137,1e+138,1e+139,1e+140, + 1e+141,1e+142,1e+143,1e+144,1e+145,1e+146,1e+147,1e+148,1e+149,1e+150,1e+151,1e+152,1e+153,1e+154,1e+155,1e+156,1e+157,1e+158,1e+159,1e+160, + 1e+161,1e+162,1e+163,1e+164,1e+165,1e+166,1e+167,1e+168,1e+169,1e+170,1e+171,1e+172,1e+173,1e+174,1e+175,1e+176,1e+177,1e+178,1e+179,1e+180, + 1e+181,1e+182,1e+183,1e+184,1e+185,1e+186,1e+187,1e+188,1e+189,1e+190,1e+191,1e+192,1e+193,1e+194,1e+195,1e+196,1e+197,1e+198,1e+199,1e+200, + 1e+201,1e+202,1e+203,1e+204,1e+205,1e+206,1e+207,1e+208,1e+209,1e+210,1e+211,1e+212,1e+213,1e+214,1e+215,1e+216,1e+217,1e+218,1e+219,1e+220, + 1e+221,1e+222,1e+223,1e+224,1e+225,1e+226,1e+227,1e+228,1e+229,1e+230,1e+231,1e+232,1e+233,1e+234,1e+235,1e+236,1e+237,1e+238,1e+239,1e+240, + 1e+241,1e+242,1e+243,1e+244,1e+245,1e+246,1e+247,1e+248,1e+249,1e+250,1e+251,1e+252,1e+253,1e+254,1e+255,1e+256,1e+257,1e+258,1e+259,1e+260, + 1e+261,1e+262,1e+263,1e+264,1e+265,1e+266,1e+267,1e+268,1e+269,1e+270,1e+271,1e+272,1e+273,1e+274,1e+275,1e+276,1e+277,1e+278,1e+279,1e+280, + 1e+281,1e+282,1e+283,1e+284,1e+285,1e+286,1e+287,1e+288,1e+289,1e+290,1e+291,1e+292,1e+293,1e+294,1e+295,1e+296,1e+297,1e+298,1e+299,1e+300, + 1e+301,1e+302,1e+303,1e+304,1e+305,1e+306,1e+307,1e+308 + }; + RAPIDJSON_ASSERT(n <= 308); + return n < -308 ? 0.0 : e[n + 308]; +} + +} // namespace internal +} // namespace rapidjson + +#endif // RAPIDJSON_POW10_ diff --git a/rapidjson/internal/stack.h b/rapidjson/internal/stack.h new file mode 100644 index 0000000000..3138b961f3 --- /dev/null +++ b/rapidjson/internal/stack.h @@ -0,0 +1,82 @@ +#ifndef RAPIDJSON_INTERNAL_STACK_H_ +#define RAPIDJSON_INTERNAL_STACK_H_ + +namespace rapidjson { +namespace internal { + +/////////////////////////////////////////////////////////////////////////////// +// Stack + +//! A type-unsafe stack for storing different types of data. +/*! \tparam Allocator Allocator for allocating stack memory. +*/ +template +class Stack { +public: + Stack(Allocator* allocator, size_t stack_capacity) : allocator_(allocator), own_allocator_(0), stack_(0), stack_top_(0), stack_end_(0), stack_capacity_(stack_capacity) { + RAPIDJSON_ASSERT(stack_capacity_ > 0); + if (!allocator_) + own_allocator_ = allocator_ = new Allocator(); + stack_top_ = stack_ = (char*)allocator_->Malloc(stack_capacity_); + stack_end_ = stack_ + stack_capacity_; + } + + ~Stack() { + Allocator::Free(stack_); + delete own_allocator_; // Only delete if it is owned by the stack + } + + void Clear() { /*stack_top_ = 0;*/ stack_top_ = stack_; } + + template + T* Push(size_t count = 1) { + // Expand the stack if needed + if (stack_top_ + sizeof(T) * count >= stack_end_) { + size_t new_capacity = stack_capacity_ * 2; + size_t size = GetSize(); + size_t new_size = GetSize() + sizeof(T) * count; + if (new_capacity < new_size) + new_capacity = new_size; + stack_ = (char*)allocator_->Realloc(stack_, stack_capacity_, new_capacity); + stack_capacity_ = new_capacity; + stack_top_ = stack_ + size; + stack_end_ = stack_ + stack_capacity_; + } + T* ret = (T*)stack_top_; + stack_top_ += sizeof(T) * count; + return ret; + } + + template + T* Pop(size_t count) { + RAPIDJSON_ASSERT(GetSize() >= count * sizeof(T)); + stack_top_ -= count * sizeof(T); + return (T*)stack_top_; + } + + template + T* Top() { + RAPIDJSON_ASSERT(GetSize() >= sizeof(T)); + return (T*)(stack_top_ - sizeof(T)); + } + + template + T* Bottom() { return (T*)stack_; } + + Allocator& GetAllocator() { return *allocator_; } + size_t GetSize() const { return stack_top_ - stack_; } + size_t GetCapacity() const { return stack_capacity_; } + +private: + Allocator* allocator_; + Allocator* own_allocator_; + char *stack_; + char *stack_top_; + char *stack_end_; + size_t stack_capacity_; +}; + +} // namespace internal +} // namespace rapidjson + +#endif // RAPIDJSON_STACK_H_ diff --git a/rapidjson/internal/strfunc.h b/rapidjson/internal/strfunc.h new file mode 100644 index 0000000000..47b8ac0757 --- /dev/null +++ b/rapidjson/internal/strfunc.h @@ -0,0 +1,24 @@ +#ifndef RAPIDJSON_INTERNAL_STRFUNC_H_ +#define RAPIDJSON_INTERNAL_STRFUNC_H_ + +namespace rapidjson { +namespace internal { + +//! Custom strlen() which works on different character types. +/*! \tparam Ch Character type (e.g. char, wchar_t, short) + \param s Null-terminated input string. + \return Number of characters in the string. + \note This has the same semantics as strlen(), the return value is not number of Unicode codepoints. +*/ +template +inline SizeType StrLen(const Ch* s) { + const Ch* p = s; + while (*p != '\0') + ++p; + return SizeType(p - s); +} + +} // namespace internal +} // namespace rapidjson + +#endif // RAPIDJSON_INTERNAL_STRFUNC_H_ diff --git a/rapidjson/prettywriter.h b/rapidjson/prettywriter.h new file mode 100644 index 0000000000..662b3929da --- /dev/null +++ b/rapidjson/prettywriter.h @@ -0,0 +1,156 @@ +#ifndef RAPIDJSON_PRETTYWRITER_H_ +#define RAPIDJSON_PRETTYWRITER_H_ + +#include "writer.h" + +namespace rapidjson { + +//! Writer with indentation and spacing. +/*! + \tparam Stream Type of ouptut stream. + \tparam Encoding Encoding of both source strings and output. + \tparam Allocator Type of allocator for allocating memory of stack. +*/ +template, typename Allocator = MemoryPoolAllocator<> > +class PrettyWriter : public Writer { +public: + typedef Writer Base; + typedef typename Base::Ch Ch; + + //! Constructor + /*! \param stream Output stream. + \param allocator User supplied allocator. If it is null, it will create a private one. + \param levelDepth Initial capacity of + */ + PrettyWriter(Stream& stream, Allocator* allocator = 0, size_t levelDepth = Base::kDefaultLevelDepth) : + Base(stream, allocator, levelDepth), indentChar_(' '), indentCharCount_(4) {} + + //! Set custom indentation. + /*! \param indentChar Character for indentation. Must be whitespace character (' ', '\t', '\n', '\r'). + \param indentCharCount Number of indent characters for each indentation level. + \note The default indentation is 4 spaces. + */ + PrettyWriter& SetIndent(Ch indentChar, unsigned indentCharCount) { + RAPIDJSON_ASSERT(indentChar == ' ' || indentChar == '\t' || indentChar == '\n' || indentChar == '\r'); + indentChar_ = indentChar; + indentCharCount_ = indentCharCount; + return *this; + } + + //@name Implementation of Handler. + //@{ + + PrettyWriter& Null() { PrettyPrefix(kNullType); Base::WriteNull(); return *this; } + PrettyWriter& Bool(bool b) { PrettyPrefix(b ? kTrueType : kFalseType); Base::WriteBool(b); return *this; } + PrettyWriter& Int(int i) { PrettyPrefix(kNumberType); Base::WriteInt(i); return *this; } + PrettyWriter& Uint(unsigned u) { PrettyPrefix(kNumberType); Base::WriteUint(u); return *this; } + PrettyWriter& Int64(int64_t i64) { PrettyPrefix(kNumberType); Base::WriteInt64(i64); return *this; } + PrettyWriter& Uint64(uint64_t u64) { PrettyPrefix(kNumberType); Base::WriteUint64(u64); return *this; } + PrettyWriter& Double(double d) { PrettyPrefix(kNumberType); Base::WriteDouble(d); return *this; } + + PrettyWriter& String(const Ch* str, SizeType length, bool copy = false) { + (void)copy; + PrettyPrefix(kStringType); + Base::WriteString(str, length); + return *this; + } + + PrettyWriter& StartObject() { + PrettyPrefix(kObjectType); + new (Base::level_stack_.template Push()) typename Base::Level(false); + Base::WriteStartObject(); + return *this; + } + + PrettyWriter& EndObject(SizeType memberCount = 0) { + (void)memberCount; + RAPIDJSON_ASSERT(Base::level_stack_.GetSize() >= sizeof(typename Base::Level)); + RAPIDJSON_ASSERT(!Base::level_stack_.template Top()->inArray); + bool empty = Base::level_stack_.template Pop(1)->valueCount == 0; + + if (!empty) { + Base::stream_.Put('\n'); + WriteIndent(); + } + Base::WriteEndObject(); + return *this; + } + + PrettyWriter& StartArray() { + PrettyPrefix(kArrayType); + new (Base::level_stack_.template Push()) typename Base::Level(true); + Base::WriteStartArray(); + return *this; + } + + PrettyWriter& EndArray(SizeType memberCount = 0) { + (void)memberCount; + RAPIDJSON_ASSERT(Base::level_stack_.GetSize() >= sizeof(typename Base::Level)); + RAPIDJSON_ASSERT(Base::level_stack_.template Top()->inArray); + bool empty = Base::level_stack_.template Pop(1)->valueCount == 0; + + if (!empty) { + Base::stream_.Put('\n'); + WriteIndent(); + } + Base::WriteEndArray(); + return *this; + } + + //@} + + //! Simpler but slower overload. + PrettyWriter& String(const Ch* str) { return String(str, internal::StrLen(str)); } + +protected: + void PrettyPrefix(Type type) { + (void)type; + if (Base::level_stack_.GetSize() != 0) { // this value is not at root + typename Base::Level* level = Base::level_stack_.template Top(); + + if (level->inArray) { + if (level->valueCount > 0) { + Base::stream_.Put(','); // add comma if it is not the first element in array + Base::stream_.Put('\n'); + } + else + Base::stream_.Put('\n'); + WriteIndent(); + } + else { // in object + if (level->valueCount > 0) { + if (level->valueCount % 2 == 0) { + Base::stream_.Put(','); + Base::stream_.Put('\n'); + } + else { + Base::stream_.Put(':'); + Base::stream_.Put(' '); + } + } + else + Base::stream_.Put('\n'); + + if (level->valueCount % 2 == 0) + WriteIndent(); + } + if (!level->inArray && level->valueCount % 2 == 0) + RAPIDJSON_ASSERT(type == kStringType); // if it's in object, then even number should be a name + level->valueCount++; + } + else + RAPIDJSON_ASSERT(type == kObjectType || type == kArrayType); + } + + void WriteIndent() { + size_t count = (Base::level_stack_.GetSize() / sizeof(typename Base::Level)) * indentCharCount_; + PutN(Base::stream_, indentChar_, count); + } + + Ch indentChar_; + unsigned indentCharCount_; +}; + +} // namespace rapidjson + +#endif // RAPIDJSON_RAPIDJSON_H_ diff --git a/rapidjson/rapidjson.h b/rapidjson/rapidjson.h new file mode 100644 index 0000000000..357eab453d --- /dev/null +++ b/rapidjson/rapidjson.h @@ -0,0 +1,525 @@ +#ifndef RAPIDJSON_RAPIDJSON_H_ +#define RAPIDJSON_RAPIDJSON_H_ + +// Copyright (c) 2011-2012 Milo Yip (miloyip@gmail.com) +// Version 0.11 + +#include // malloc(), realloc(), free() +#include // memcpy() + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_NO_INT64DEFINE + +// Here defines int64_t and uint64_t types in global namespace. +// If user have their own definition, can define RAPIDJSON_NO_INT64DEFINE to disable this. +#ifndef RAPIDJSON_NO_INT64DEFINE +#ifdef _MSC_VER +typedef __int64 int64_t; +typedef unsigned __int64 uint64_t; +#else +#include +#endif +#endif // RAPIDJSON_NO_INT64TYPEDEF + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_ENDIAN +#define RAPIDJSON_LITTLEENDIAN 0 //!< Little endian machine +#define RAPIDJSON_BIGENDIAN 1 //!< Big endian machine + +//! Endianness of the machine. +/*! GCC provided macro for detecting endianness of the target machine. But other + compilers may not have this. User can define RAPIDJSON_ENDIAN to either + RAPIDJSON_LITTLEENDIAN or RAPIDJSON_BIGENDIAN. +*/ +#ifndef RAPIDJSON_ENDIAN +#ifdef __BYTE_ORDER__ +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define RAPIDJSON_ENDIAN RAPIDJSON_LITTLEENDIAN +#else +#define RAPIDJSON_ENDIAN RAPIDJSON_BIGENDIAN +#endif // __BYTE_ORDER__ +#else +#define RAPIDJSON_ENDIAN RAPIDJSON_LITTLEENDIAN // Assumes little endian otherwise. +#endif +#endif // RAPIDJSON_ENDIAN + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_SSE2/RAPIDJSON_SSE42/RAPIDJSON_SIMD + +// Enable SSE2 optimization. +//#define RAPIDJSON_SSE2 + +// Enable SSE4.2 optimization. +//#define RAPIDJSON_SSE42 + +#if defined(RAPIDJSON_SSE2) || defined(RAPIDJSON_SSE42) +#define RAPIDJSON_SIMD +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_NO_SIZETYPEDEFINE + +#ifndef RAPIDJSON_NO_SIZETYPEDEFINE +namespace rapidjson { +//! Use 32-bit array/string indices even for 64-bit platform, instead of using size_t. +/*! User may override the SizeType by defining RAPIDJSON_NO_SIZETYPEDEFINE. +*/ +typedef unsigned SizeType; +} // namespace rapidjson +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_ASSERT + +//! Assertion. +/*! By default, rapidjson uses C assert() for assertion. + User can override it by defining RAPIDJSON_ASSERT(x) macro. +*/ +#ifndef RAPIDJSON_ASSERT +#include +#define RAPIDJSON_ASSERT(x) assert(x) +#endif // RAPIDJSON_ASSERT + +/////////////////////////////////////////////////////////////////////////////// +// Helpers + +#define RAPIDJSON_MULTILINEMACRO_BEGIN do { +#define RAPIDJSON_MULTILINEMACRO_END \ +} while((void)0, 0) + +namespace rapidjson { + +/////////////////////////////////////////////////////////////////////////////// +// Allocator + +/*! \class rapidjson::Allocator + \brief Concept for allocating, resizing and freeing memory block. + + Note that Malloc() and Realloc() are non-static but Free() is static. + + So if an allocator need to support Free(), it needs to put its pointer in + the header of memory block. + +\code +concept Allocator { + static const bool kNeedFree; //!< Whether this allocator needs to call Free(). + + // Allocate a memory block. + // \param size of the memory block in bytes. + // \returns pointer to the memory block. + void* Malloc(size_t size); + + // Resize a memory block. + // \param originalPtr The pointer to current memory block. Null pointer is permitted. + // \param originalSize The current size in bytes. (Design issue: since some allocator may not book-keep this, explicitly pass to it can save memory.) + // \param newSize the new size in bytes. + void* Realloc(void* originalPtr, size_t originalSize, size_t newSize); + + // Free a memory block. + // \param pointer to the memory block. Null pointer is permitted. + static void Free(void *ptr); +}; +\endcode +*/ + +/////////////////////////////////////////////////////////////////////////////// +// CrtAllocator + +//! C-runtime library allocator. +/*! This class is just wrapper for standard C library memory routines. + \implements Allocator +*/ +class CrtAllocator { +public: + static const bool kNeedFree = true; + void* Malloc(size_t size) { return malloc(size); } + void* Realloc(void* originalPtr, size_t originalSize, size_t newSize) { (void)originalSize; return realloc(originalPtr, newSize); } + static void Free(void *ptr) { free(ptr); } +}; + +/////////////////////////////////////////////////////////////////////////////// +// MemoryPoolAllocator + +//! Default memory allocator used by the parser and DOM. +/*! This allocator allocate memory blocks from pre-allocated memory chunks. + + It does not free memory blocks. And Realloc() only allocate new memory. + + The memory chunks are allocated by BaseAllocator, which is CrtAllocator by default. + + User may also supply a buffer as the first chunk. + + If the user-buffer is full then additional chunks are allocated by BaseAllocator. + + The user-buffer is not deallocated by this allocator. + + \tparam BaseAllocator the allocator type for allocating memory chunks. Default is CrtAllocator. + \implements Allocator +*/ +template +class MemoryPoolAllocator { +public: + static const bool kNeedFree = false; //!< Tell users that no need to call Free() with this allocator. (concept Allocator) + + //! Constructor with chunkSize. + /*! \param chunkSize The size of memory chunk. The default is kDefaultChunkSize. + \param baseAllocator The allocator for allocating memory chunks. + */ + MemoryPoolAllocator(size_t chunkSize = kDefaultChunkCapacity, BaseAllocator* baseAllocator = 0) : + chunkHead_(0), chunk_capacity_(chunkSize), userBuffer_(0), baseAllocator_(baseAllocator), ownBaseAllocator_(0) + { + if (!baseAllocator_) + ownBaseAllocator_ = baseAllocator_ = new BaseAllocator(); + AddChunk(chunk_capacity_); + } + + //! Constructor with user-supplied buffer. + /*! The user buffer will be used firstly. When it is full, memory pool allocates new chunk with chunk size. + + The user buffer will not be deallocated when this allocator is destructed. + + \param buffer User supplied buffer. + \param size Size of the buffer in bytes. It must at least larger than sizeof(ChunkHeader). + \param chunkSize The size of memory chunk. The default is kDefaultChunkSize. + \param baseAllocator The allocator for allocating memory chunks. + */ + MemoryPoolAllocator(char *buffer, size_t size, size_t chunkSize = kDefaultChunkCapacity, BaseAllocator* baseAllocator = 0) : + chunkHead_(0), chunk_capacity_(chunkSize), userBuffer_(buffer), baseAllocator_(baseAllocator), ownBaseAllocator_(0) + { + RAPIDJSON_ASSERT(buffer != 0); + RAPIDJSON_ASSERT(size > sizeof(ChunkHeader)); + chunkHead_ = (ChunkHeader*)buffer; + chunkHead_->capacity = size - sizeof(ChunkHeader); + chunkHead_->size = 0; + chunkHead_->next = 0; + } + + //! Destructor. + /*! This deallocates all memory chunks, excluding the user-supplied buffer. + */ + ~MemoryPoolAllocator() { + Clear(); + delete ownBaseAllocator_; + } + + //! Deallocates all memory chunks, excluding the user-supplied buffer. + void Clear() { + while(chunkHead_ != 0 && chunkHead_ != (ChunkHeader *)userBuffer_) { + ChunkHeader* next = chunkHead_->next; + baseAllocator_->Free(chunkHead_); + chunkHead_ = next; + } + } + + //! Computes the total capacity of allocated memory chunks. + /*! \return total capacity in bytes. + */ + size_t Capacity() { + size_t capacity = 0; + for (ChunkHeader* c = chunkHead_; c != 0; c = c->next) + capacity += c->capacity; + return capacity; + } + + //! Computes the memory blocks allocated. + /*! \return total used bytes. + */ + size_t Size() { + size_t size = 0; + for (ChunkHeader* c = chunkHead_; c != 0; c = c->next) + size += c->size; + return size; + } + + //! Allocates a memory block. (concept Allocator) + void* Malloc(size_t size) { + size = (size + 3) & ~3; // Force aligning size to 4 + + if (chunkHead_->size + size > chunkHead_->capacity) + AddChunk(chunk_capacity_ > size ? chunk_capacity_ : size); + + char *buffer = (char *)(chunkHead_ + 1) + chunkHead_->size; + RAPIDJSON_ASSERT(((uintptr_t)buffer & 3) == 0); // returned buffer is aligned to 4 + chunkHead_->size += size; + + return buffer; + } + + //! Resizes a memory block (concept Allocator) + void* Realloc(void* originalPtr, size_t originalSize, size_t newSize) { + if (originalPtr == 0) + return Malloc(newSize); + + // Do not shrink if new size is smaller than original + if (originalSize >= newSize) + return originalPtr; + + // Simply expand it if it is the last allocation and there is sufficient space + if (originalPtr == (char *)(chunkHead_ + 1) + chunkHead_->size - originalSize) { + size_t increment = newSize - originalSize; + increment = (increment + 3) & ~3; // Force aligning size to 4 + if (chunkHead_->size + increment <= chunkHead_->capacity) { + chunkHead_->size += increment; + RAPIDJSON_ASSERT(((uintptr_t)originalPtr & 3) == 0); // returned buffer is aligned to 4 + return originalPtr; + } + } + + // Realloc process: allocate and copy memory, do not free original buffer. + void* newBuffer = Malloc(newSize); + RAPIDJSON_ASSERT(newBuffer != 0); // Do not handle out-of-memory explicitly. + return memcpy(newBuffer, originalPtr, originalSize); + } + + //! Frees a memory block (concept Allocator) + static void Free(void *) {} // Do nothing + +private: + //! Creates a new chunk. + /*! \param capacity Capacity of the chunk in bytes. + */ + void AddChunk(size_t capacity) { + ChunkHeader* chunk = (ChunkHeader*)baseAllocator_->Malloc(sizeof(ChunkHeader) + capacity); + chunk->capacity = capacity; + chunk->size = 0; + chunk->next = chunkHead_; + chunkHead_ = chunk; + } + + static const int kDefaultChunkCapacity = 64 * 1024; //!< Default chunk capacity. + + //! Chunk header for perpending to each chunk. + /*! Chunks are stored as a singly linked list. + */ + struct ChunkHeader { + size_t capacity; //!< Capacity of the chunk in bytes (excluding the header itself). + size_t size; //!< Current size of allocated memory in bytes. + ChunkHeader *next; //!< Next chunk in the linked list. + }; + + ChunkHeader *chunkHead_; //!< Head of the chunk linked-list. Only the head chunk serves allocation. + size_t chunk_capacity_; //!< The minimum capacity of chunk when they are allocated. + char *userBuffer_; //!< User supplied buffer. + BaseAllocator* baseAllocator_; //!< base allocator for allocating memory chunks. + BaseAllocator* ownBaseAllocator_; //!< base allocator created by this object. +}; + +/////////////////////////////////////////////////////////////////////////////// +// Encoding + +/*! \class rapidjson::Encoding + \brief Concept for encoding of Unicode characters. + +\code +concept Encoding { + typename Ch; //! Type of character. + + //! \brief Encode a Unicode codepoint to a buffer. + //! \param buffer pointer to destination buffer to store the result. It should have sufficient size of encoding one character. + //! \param codepoint An unicode codepoint, ranging from 0x0 to 0x10FFFF inclusively. + //! \returns the pointer to the next character after the encoded data. + static Ch* Encode(Ch *buffer, unsigned codepoint); +}; +\endcode +*/ + +/////////////////////////////////////////////////////////////////////////////// +// UTF8 + +//! UTF-8 encoding. +/*! http://en.wikipedia.org/wiki/UTF-8 + \tparam CharType Type for storing 8-bit UTF-8 data. Default is char. + \implements Encoding +*/ +template +struct UTF8 { + typedef CharType Ch; + + static Ch* Encode(Ch *buffer, unsigned codepoint) { + if (codepoint <= 0x7F) + *buffer++ = codepoint & 0xFF; + else if (codepoint <= 0x7FF) { + *buffer++ = 0xC0 | ((codepoint >> 6) & 0xFF); + *buffer++ = 0x80 | ((codepoint & 0x3F)); + } + else if (codepoint <= 0xFFFF) { + *buffer++ = 0xE0 | ((codepoint >> 12) & 0xFF); + *buffer++ = 0x80 | ((codepoint >> 6) & 0x3F); + *buffer++ = 0x80 | (codepoint & 0x3F); + } + else { + RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); + *buffer++ = 0xF0 | ((codepoint >> 18) & 0xFF); + *buffer++ = 0x80 | ((codepoint >> 12) & 0x3F); + *buffer++ = 0x80 | ((codepoint >> 6) & 0x3F); + *buffer++ = 0x80 | (codepoint & 0x3F); + } + return buffer; + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// UTF16 + +//! UTF-16 encoding. +/*! http://en.wikipedia.org/wiki/UTF-16 + \tparam CharType Type for storing 16-bit UTF-16 data. Default is wchar_t. C++11 may use char16_t instead. + \implements Encoding +*/ +template +struct UTF16 { + typedef CharType Ch; + + static Ch* Encode(Ch* buffer, unsigned codepoint) { + if (codepoint <= 0xFFFF) { + RAPIDJSON_ASSERT(codepoint < 0xD800 || codepoint > 0xDFFF); // Code point itself cannot be surrogate pair + *buffer++ = static_cast(codepoint); + } + else { + RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); + unsigned v = codepoint - 0x10000; + *buffer++ = static_cast((v >> 10) + 0xD800); + *buffer++ = (v & 0x3FF) + 0xDC00; + } + return buffer; + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// UTF32 + +//! UTF-32 encoding. +/*! http://en.wikipedia.org/wiki/UTF-32 + \tparam Ch Type for storing 32-bit UTF-32 data. Default is unsigned. C++11 may use char32_t instead. + \implements Encoding +*/ +template +struct UTF32 { + typedef CharType Ch; + + static Ch *Encode(Ch* buffer, unsigned codepoint) { + RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); + *buffer++ = codepoint; + return buffer; + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// Stream + +/*! \class rapidjson::Stream + \brief Concept for reading and writing characters. + + For read-only stream, no need to implement PutBegin(), Put() and PutEnd(). + + For write-only stream, only need to implement Put(). + +\code +concept Stream { + typename Ch; //!< Character type of the stream. + + //! Read the current character from stream without moving the read cursor. + Ch Peek() const; + + //! Read the current character from stream and moving the read cursor to next character. + Ch Take(); + + //! Get the current read cursor. + //! \return Number of characters read from start. + size_t Tell(); + + //! Begin writing operation at the current read pointer. + //! \return The begin writer pointer. + Ch* PutBegin(); + + //! Write a character. + void Put(Ch c); + + //! End the writing operation. + //! \param begin The begin write pointer returned by PutBegin(). + //! \return Number of characters written. + size_t PutEnd(Ch* begin); +} +\endcode +*/ + +//! Put N copies of a character to a stream. +template +inline void PutN(Stream& stream, Ch c, size_t n) { + for (size_t i = 0; i < n; i++) + stream.Put(c); +} + +/////////////////////////////////////////////////////////////////////////////// +// StringStream + +//! Read-only string stream. +/*! \implements Stream +*/ +template +struct GenericStringStream { + typedef typename Encoding::Ch Ch; + + GenericStringStream(const Ch *src) : src_(src), head_(src) {} + + Ch Peek() const { return *src_; } + Ch Take() { return *src_++; } + size_t Tell() const { return src_ - head_; } + + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + void Put(Ch) { RAPIDJSON_ASSERT(false); } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + + const Ch* src_; //!< Current read position. + const Ch* head_; //!< Original head of the string. +}; + +typedef GenericStringStream > StringStream; + +/////////////////////////////////////////////////////////////////////////////// +// InsituStringStream + +//! A read-write string stream. +/*! This string stream is particularly designed for in-situ parsing. + \implements Stream +*/ +template +struct GenericInsituStringStream { + typedef typename Encoding::Ch Ch; + + GenericInsituStringStream(Ch *src) : src_(src), dst_(0), head_(src) {} + + // Read + Ch Peek() { return *src_; } + Ch Take() { return *src_++; } + size_t Tell() { return src_ - head_; } + + // Write + Ch* PutBegin() { return dst_ = src_; } + void Put(Ch c) { RAPIDJSON_ASSERT(dst_ != 0); *dst_++ = c; } + size_t PutEnd(Ch* begin) { return dst_ - begin; } + + Ch* src_; + Ch* dst_; + Ch* head_; +}; + +typedef GenericInsituStringStream > InsituStringStream; + +/////////////////////////////////////////////////////////////////////////////// +// Type + +//! Type of JSON value +enum Type { + kNullType = 0, //!< null + kFalseType = 1, //!< false + kTrueType = 2, //!< true + kObjectType = 3, //!< object + kArrayType = 4, //!< array + kStringType = 5, //!< string + kNumberType = 6, //!< number +}; + +} // namespace rapidjson + +#endif // RAPIDJSON_RAPIDJSON_H_ diff --git a/rapidjson/reader.h b/rapidjson/reader.h new file mode 100644 index 0000000000..cb3e826a54 --- /dev/null +++ b/rapidjson/reader.h @@ -0,0 +1,683 @@ +#ifndef RAPIDJSON_READER_H_ +#define RAPIDJSON_READER_H_ + +// Copyright (c) 2011 Milo Yip (miloyip@gmail.com) +// Version 0.1 + +#include "rapidjson.h" +#include "internal/pow10.h" +#include "internal/stack.h" +#include + +#ifdef RAPIDJSON_SSE42 +#include +#elif defined(RAPIDJSON_SSE2) +#include +#endif + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4127) // conditional expression is constant +#endif + +#ifndef RAPIDJSON_PARSE_ERROR +#define RAPIDJSON_PARSE_ERROR(msg, offset) \ + RAPIDJSON_MULTILINEMACRO_BEGIN \ + parseError_ = msg; \ + errorOffset_ = offset; \ + longjmp(jmpbuf_, 1); \ + RAPIDJSON_MULTILINEMACRO_END +#endif + +namespace rapidjson { + +/////////////////////////////////////////////////////////////////////////////// +// ParseFlag + +//! Combination of parseFlags +enum ParseFlag { + kParseDefaultFlags = 0, //!< Default parse flags. Non-destructive parsing. Text strings are decoded into allocated buffer. + kParseInsituFlag = 1 //!< In-situ(destructive) parsing. +}; + +/////////////////////////////////////////////////////////////////////////////// +// Handler + +/*! \class rapidjson::Handler + \brief Concept for receiving events from GenericReader upon parsing. +\code +concept Handler { + typename Ch; + + void Null(); + void Bool(bool b); + void Int(int i); + void Uint(unsigned i); + void Int64(int64_t i); + void Uint64(uint64_t i); + void Double(double d); + void String(const Ch* str, SizeType length, bool copy); + void StartObject(); + void EndObject(SizeType memberCount); + void StartArray(); + void EndArray(SizeType elementCount); +}; +\endcode +*/ +/////////////////////////////////////////////////////////////////////////////// +// BaseReaderHandler + +//! Default implementation of Handler. +/*! This can be used as base class of any reader handler. + \implements Handler +*/ +template > +struct BaseReaderHandler { + typedef typename Encoding::Ch Ch; + + void Default() {} + void Null() { Default(); } + void Bool(bool) { Default(); } + void Int(int) { Default(); } + void Uint(unsigned) { Default(); } + void Int64(int64_t) { Default(); } + void Uint64(uint64_t) { Default(); } + void Double(double) { Default(); } + void String(const Ch*, SizeType, bool) { Default(); } + void StartObject() { Default(); } + void EndObject(SizeType) { Default(); } + void StartArray() { Default(); } + void EndArray(SizeType) { Default(); } +}; + +/////////////////////////////////////////////////////////////////////////////// +// SkipWhitespace + +//! Skip the JSON white spaces in a stream. +/*! \param stream A input stream for skipping white spaces. + \note This function has SSE2/SSE4.2 specialization. +*/ +template +void SkipWhitespace(Stream& stream) { + Stream s = stream; // Use a local copy for optimization + while (s.Peek() == ' ' || s.Peek() == '\n' || s.Peek() == '\r' || s.Peek() == '\t') + s.Take(); + stream = s; +} + +#ifdef RAPIDJSON_SSE42 +//! Skip whitespace with SSE 4.2 pcmpistrm instruction, testing 16 8-byte characters at once. +inline const char *SkipWhitespace_SIMD(const char* p) { + static const char whitespace[16] = " \n\r\t"; + __m128i w = _mm_loadu_si128((const __m128i *)&whitespace[0]); + + for (;;) { + __m128i s = _mm_loadu_si128((const __m128i *)p); + unsigned r = _mm_cvtsi128_si32(_mm_cmpistrm(w, s, _SIDD_UBYTE_OPS | _SIDD_CMP_EQUAL_ANY | _SIDD_BIT_MASK | _SIDD_NEGATIVE_POLARITY)); + if (r == 0) // all 16 characters are whitespace + p += 16; + else { // some of characters may be non-whitespace +#ifdef _MSC_VER // Find the index of first non-whitespace + unsigned long offset; + if (_BitScanForward(&offset, r)) + return p + offset; +#else + if (r != 0) + return p + __builtin_ffs(r) - 1; +#endif + } + } +} + +#elif defined(RAPIDJSON_SSE2) + +//! Skip whitespace with SSE2 instructions, testing 16 8-byte characters at once. +inline const char *SkipWhitespace_SIMD(const char* p) { + static const char whitespaces[4][17] = { + " ", + "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n", + "\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r", + "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"}; + + __m128i w0 = _mm_loadu_si128((const __m128i *)&whitespaces[0][0]); + __m128i w1 = _mm_loadu_si128((const __m128i *)&whitespaces[1][0]); + __m128i w2 = _mm_loadu_si128((const __m128i *)&whitespaces[2][0]); + __m128i w3 = _mm_loadu_si128((const __m128i *)&whitespaces[3][0]); + + for (;;) { + __m128i s = _mm_loadu_si128((const __m128i *)p); + __m128i x = _mm_cmpeq_epi8(s, w0); + x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w1)); + x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w2)); + x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w3)); + unsigned short r = ~_mm_movemask_epi8(x); + if (r == 0) // all 16 characters are whitespace + p += 16; + else { // some of characters may be non-whitespace +#ifdef _MSC_VER // Find the index of first non-whitespace + unsigned long offset; + if (_BitScanForward(&offset, r)) + return p + offset; +#else + if (r != 0) + return p + __builtin_ffs(r) - 1; +#endif + } + } +} + +#endif // RAPIDJSON_SSE2 + +#ifdef RAPIDJSON_SIMD +//! Template function specialization for InsituStringStream +template<> inline void SkipWhitespace(InsituStringStream& stream) { + stream.src_ = const_cast(SkipWhitespace_SIMD(stream.src_)); +} + +//! Template function specialization for StringStream +template<> inline void SkipWhitespace(StringStream& stream) { + stream.src_ = SkipWhitespace_SIMD(stream.src_); +} +#endif // RAPIDJSON_SIMD + +/////////////////////////////////////////////////////////////////////////////// +// GenericReader + +//! SAX-style JSON parser. Use Reader for UTF8 encoding and default allocator. +/*! GenericReader parses JSON text from a stream, and send events synchronously to an + object implementing Handler concept. + + It needs to allocate a stack for storing a single decoded string during + non-destructive parsing. + + For in-situ parsing, the decoded string is directly written to the source + text string, no temporary buffer is required. + + A GenericReader object can be reused for parsing multiple JSON text. + + \tparam Encoding Encoding of both the stream and the parse output. + \tparam Allocator Allocator type for stack. +*/ +template > +class GenericReader { +public: + typedef typename Encoding::Ch Ch; + + //! Constructor. + /*! \param allocator Optional allocator for allocating stack memory. (Only use for non-destructive parsing) + \param stackCapacity stack capacity in bytes for storing a single decoded string. (Only use for non-destructive parsing) + */ + GenericReader(Allocator* allocator = 0, size_t stackCapacity = kDefaultStackCapacity) : stack_(allocator, stackCapacity), parseError_(0), errorOffset_(0) {} + + //! Parse JSON text. + /*! \tparam parseFlags Combination of ParseFlag. + \tparam Stream Type of input stream. + \tparam Handler Type of handler which must implement Handler concept. + \param stream Input stream to be parsed. + \param handler The handler to receive events. + \return Whether the parsing is successful. + */ + template + bool Parse(Stream& stream, Handler& handler) { + parseError_ = 0; + errorOffset_ = 0; + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4611) // interaction between '_setjmp' and C++ object destruction is non-portable +#endif + if (setjmp(jmpbuf_)) { +#ifdef _MSC_VER +#pragma warning(pop) +#endif + stack_.Clear(); + return false; + } + + SkipWhitespace(stream); + + if (stream.Peek() == '\0') + RAPIDJSON_PARSE_ERROR("Text only contains white space(s)", stream.Tell()); + else { + switch (stream.Peek()) { + case '{': ParseObject(stream, handler); break; + case '[': ParseArray(stream, handler); break; + default: RAPIDJSON_PARSE_ERROR("Expect either an object or array at root", stream.Tell()); + } + SkipWhitespace(stream); + + if (stream.Peek() != '\0') + RAPIDJSON_PARSE_ERROR("Nothing should follow the root object or array.", stream.Tell()); + } + + return true; + } + + bool HasParseError() const { return parseError_ != 0; } + const char* GetParseError() const { return parseError_; } + size_t GetErrorOffset() const { return errorOffset_; } + +private: + // Parse object: { string : value, ... } + template + void ParseObject(Stream& stream, Handler& handler) { + RAPIDJSON_ASSERT(stream.Peek() == '{'); + stream.Take(); // Skip '{' + handler.StartObject(); + SkipWhitespace(stream); + + if (stream.Peek() == '}') { + stream.Take(); + handler.EndObject(0); // empty object + return; + } + + for (SizeType memberCount = 0;;) { + if (stream.Peek() != '"') { + RAPIDJSON_PARSE_ERROR("Name of an object member must be a string", stream.Tell()); + break; + } + + ParseString(stream, handler); + SkipWhitespace(stream); + + if (stream.Take() != ':') { + RAPIDJSON_PARSE_ERROR("There must be a colon after the name of object member", stream.Tell()); + break; + } + SkipWhitespace(stream); + + ParseValue(stream, handler); + SkipWhitespace(stream); + + ++memberCount; + + switch(stream.Take()) { + case ',': SkipWhitespace(stream); break; + case '}': handler.EndObject(memberCount); return; + default: RAPIDJSON_PARSE_ERROR("Must be a comma or '}' after an object member", stream.Tell()); + } + } + } + + // Parse array: [ value, ... ] + template + void ParseArray(Stream& stream, Handler& handler) { + RAPIDJSON_ASSERT(stream.Peek() == '['); + stream.Take(); // Skip '[' + handler.StartArray(); + SkipWhitespace(stream); + + if (stream.Peek() == ']') { + stream.Take(); + handler.EndArray(0); // empty array + return; + } + + for (SizeType elementCount = 0;;) { + ParseValue(stream, handler); + ++elementCount; + SkipWhitespace(stream); + + switch (stream.Take()) { + case ',': SkipWhitespace(stream); break; + case ']': handler.EndArray(elementCount); return; + default: RAPIDJSON_PARSE_ERROR("Must be a comma or ']' after an array element.", stream.Tell()); + } + } + } + + template + void ParseNull(Stream& stream, Handler& handler) { + RAPIDJSON_ASSERT(stream.Peek() == 'n'); + stream.Take(); + + if (stream.Take() == 'u' && stream.Take() == 'l' && stream.Take() == 'l') + handler.Null(); + else + RAPIDJSON_PARSE_ERROR("Invalid value", stream.Tell() - 1); + } + + template + void ParseTrue(Stream& stream, Handler& handler) { + RAPIDJSON_ASSERT(stream.Peek() == 't'); + stream.Take(); + + if (stream.Take() == 'r' && stream.Take() == 'u' && stream.Take() == 'e') + handler.Bool(true); + else + RAPIDJSON_PARSE_ERROR("Invalid value", stream.Tell()); + } + + template + void ParseFalse(Stream& stream, Handler& handler) { + RAPIDJSON_ASSERT(stream.Peek() == 'f'); + stream.Take(); + + if (stream.Take() == 'a' && stream.Take() == 'l' && stream.Take() == 's' && stream.Take() == 'e') + handler.Bool(false); + else + RAPIDJSON_PARSE_ERROR("Invalid value", stream.Tell() - 1); + } + + // Helper function to parse four hexidecimal digits in \uXXXX in ParseString(). + template + unsigned ParseHex4(Stream& stream) { + Stream s = stream; // Use a local copy for optimization + unsigned codepoint = 0; + for (int i = 0; i < 4; i++) { + Ch c = s.Take(); + codepoint <<= 4; + codepoint += c; + if (c >= '0' && c <= '9') + codepoint -= '0'; + else if (c >= 'A' && c <= 'F') + codepoint -= 'A' - 10; + else if (c >= 'a' && c <= 'f') + codepoint -= 'a' - 10; + else + RAPIDJSON_PARSE_ERROR("Incorrect hex digit after \\u escape", s.Tell() - 1); + } + stream = s; // Restore stream + return codepoint; + } + + // Parse string, handling the prefix and suffix double quotes and escaping. + template + void ParseString(Stream& stream, Handler& handler) { +#define Z16 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + static const Ch escape[256] = { + Z16, Z16, 0, 0,'\"', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'/', + Z16, Z16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'\\', 0, 0, 0, + 0, 0,'\b', 0, 0, 0,'\f', 0, 0, 0, 0, 0, 0, 0,'\n', 0, + 0, 0,'\r', 0,'\t', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16 + }; +#undef Z16 + + Stream s = stream; // Use a local copy for optimization + RAPIDJSON_ASSERT(s.Peek() == '\"'); + s.Take(); // Skip '\"' + Ch *head; + SizeType len; + if (parseFlags & kParseInsituFlag) + head = s.PutBegin(); + else + len = 0; + +#define RAPIDJSON_PUT(x) \ + do { \ + if (parseFlags & kParseInsituFlag) \ + s.Put(x); \ + else { \ + *stack_.template Push() = x; \ + ++len; \ + } \ + } while(false) + + for (;;) { + Ch c = s.Take(); + if (c == '\\') { // Escape + Ch e = s.Take(); + if ((sizeof(Ch) == 1 || e < 256) && escape[(unsigned char)e]) + RAPIDJSON_PUT(escape[(unsigned char)e]); + else if (e == 'u') { // Unicode + unsigned codepoint = ParseHex4(s); + if (codepoint >= 0xD800 && codepoint <= 0xDBFF) { // Handle UTF-16 surrogate pair + if (s.Take() != '\\' || s.Take() != 'u') { + RAPIDJSON_PARSE_ERROR("Missing the second \\u in surrogate pair", s.Tell() - 2); + return; + } + unsigned codepoint2 = ParseHex4(s); + if (codepoint2 < 0xDC00 || codepoint2 > 0xDFFF) { + RAPIDJSON_PARSE_ERROR("The second \\u in surrogate pair is invalid", s.Tell() - 2); + return; + } + codepoint = (((codepoint - 0xD800) << 10) | (codepoint2 - 0xDC00)) + 0x10000; + } + + Ch buffer[4]; + SizeType count = SizeType(Encoding::Encode(buffer, codepoint) - &buffer[0]); + + if (parseFlags & kParseInsituFlag) + for (SizeType i = 0; i < count; i++) + s.Put(buffer[i]); + else { + memcpy(stack_.template Push(count), buffer, count * sizeof(Ch)); + len += count; + } + } + else { + RAPIDJSON_PARSE_ERROR("Unknown escape character", stream.Tell() - 1); + return; + } + } + else if (c == '"') { // Closing double quote + if (parseFlags & kParseInsituFlag) { + size_t length = s.PutEnd(head); + RAPIDJSON_ASSERT(length <= 0xFFFFFFFF); + RAPIDJSON_PUT('\0'); // null-terminate the string + handler.String(head, SizeType(length), false); + } + else { + RAPIDJSON_PUT('\0'); + handler.String(stack_.template Pop(len), len - 1, true); + } + stream = s; // restore stream + return; + } + else if (c == '\0') { + RAPIDJSON_PARSE_ERROR("lacks ending quotation before the end of string", stream.Tell() - 1); + return; + } + else if ((unsigned)c < 0x20) { // RFC 4627: unescaped = %x20-21 / %x23-5B / %x5D-10FFFF + RAPIDJSON_PARSE_ERROR("Incorrect unescaped character in string", stream.Tell() - 1); + return; + } + else + RAPIDJSON_PUT(c); // Normal character, just copy + } +#undef RAPIDJSON_PUT + } + + template + void ParseNumber(Stream& stream, Handler& handler) { + Stream s = stream; // Local copy for optimization + // Parse minus + bool minus = false; + if (s.Peek() == '-') { + minus = true; + s.Take(); + } + + // Parse int: zero / ( digit1-9 *DIGIT ) + unsigned i; + bool try64bit = false; + if (s.Peek() == '0') { + i = 0; + s.Take(); + } + else if (s.Peek() >= '1' && s.Peek() <= '9') { + i = s.Take() - '0'; + + if (minus) + while (s.Peek() >= '0' && s.Peek() <= '9') { + if (i >= 214748364) { // 2^31 = 2147483648 + if (i != 214748364 || s.Peek() > '8') { + try64bit = true; + break; + } + } + i = i * 10 + (s.Take() - '0'); + } + else + while (s.Peek() >= '0' && s.Peek() <= '9') { + if (i >= 429496729) { // 2^32 - 1 = 4294967295 + if (i != 429496729 || s.Peek() > '5') { + try64bit = true; + break; + } + } + i = i * 10 + (s.Take() - '0'); + } + } + else { + RAPIDJSON_PARSE_ERROR("Expect a value here.", stream.Tell()); + return; + } + + // Parse 64bit int + uint64_t i64 = 0; + bool useDouble = false; + if (try64bit) { + i64 = i; + if (minus) + while (s.Peek() >= '0' && s.Peek() <= '9') { + if (i64 >= 922337203685477580uLL) // 2^63 = 9223372036854775808 + if (i64 != 922337203685477580uLL || s.Peek() > '8') { + useDouble = true; + break; + } + i64 = i64 * 10 + (s.Take() - '0'); + } + else + while (s.Peek() >= '0' && s.Peek() <= '9') { + if (i64 >= 1844674407370955161uLL) // 2^64 - 1 = 18446744073709551615 + if (i64 != 1844674407370955161uLL || s.Peek() > '5') { + useDouble = true; + break; + } + i64 = i64 * 10 + (s.Take() - '0'); + } + } + + // Force double for big integer + double d = 0.0; + if (useDouble) { + d = (double)i64; + while (s.Peek() >= '0' && s.Peek() <= '9') { + if (d >= 1E307) { + RAPIDJSON_PARSE_ERROR("Number too big to store in double", stream.Tell()); + return; + } + d = d * 10 + (s.Take() - '0'); + } + } + + // Parse frac = decimal-point 1*DIGIT + int expFrac = 0; + if (s.Peek() == '.') { + if (!useDouble) { + d = try64bit ? (double)i64 : (double)i; + useDouble = true; + } + s.Take(); + + if (s.Peek() >= '0' && s.Peek() <= '9') { + d = d * 10 + (s.Take() - '0'); + --expFrac; + } + else { + RAPIDJSON_PARSE_ERROR("At least one digit in fraction part", stream.Tell()); + return; + } + + while (s.Peek() >= '0' && s.Peek() <= '9') { + if (expFrac > -16) { + d = d * 10 + (s.Peek() - '0'); + --expFrac; + } + s.Take(); + } + } + + // Parse exp = e [ minus / plus ] 1*DIGIT + int exp = 0; + if (s.Peek() == 'e' || s.Peek() == 'E') { + if (!useDouble) { + d = try64bit ? (double)i64 : (double)i; + useDouble = true; + } + s.Take(); + + bool expMinus = false; + if (s.Peek() == '+') + s.Take(); + else if (s.Peek() == '-') { + s.Take(); + expMinus = true; + } + + if (s.Peek() >= '0' && s.Peek() <= '9') { + exp = s.Take() - '0'; + while (s.Peek() >= '0' && s.Peek() <= '9') { + exp = exp * 10 + (s.Take() - '0'); + if (exp > 308) { + RAPIDJSON_PARSE_ERROR("Number too big to store in double", stream.Tell()); + return; + } + } + } + else { + RAPIDJSON_PARSE_ERROR("At least one digit in exponent", s.Tell()); + return; + } + + if (expMinus) + exp = -exp; + } + + // Finish parsing, call event according to the type of number. + if (useDouble) { + d *= internal::Pow10(exp + expFrac); + handler.Double(minus ? -d : d); + } + else { + if (try64bit) { + if (minus) + handler.Int64(-(int64_t)i64); + else + handler.Uint64(i64); + } + else { + if (minus) + handler.Int(-(int)i); + else + handler.Uint(i); + } + } + + stream = s; // restore stream + } + + // Parse any JSON value + template + void ParseValue(Stream& stream, Handler& handler) { + switch (stream.Peek()) { + case 'n': ParseNull (stream, handler); break; + case 't': ParseTrue (stream, handler); break; + case 'f': ParseFalse (stream, handler); break; + case '"': ParseString(stream, handler); break; + case '{': ParseObject(stream, handler); break; + case '[': ParseArray (stream, handler); break; + default : ParseNumber(stream, handler); + } + } + + static const size_t kDefaultStackCapacity = 256; //!< Default stack capacity in bytes for storing a single decoded string. + internal::Stack stack_; //!< A stack for storing decoded string temporarily during non-destructive parsing. + jmp_buf jmpbuf_; //!< setjmp buffer for fast exit from nested parsing function calls. + const char* parseError_; + size_t errorOffset_; +}; // class GenericReader + +//! Reader with UTF8 encoding and default allocator. +typedef GenericReader > Reader; + +} // namespace rapidjson + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif // RAPIDJSON_READER_H_ diff --git a/rapidjson/stringbuffer.h b/rapidjson/stringbuffer.h new file mode 100644 index 0000000000..34cff58ef9 --- /dev/null +++ b/rapidjson/stringbuffer.h @@ -0,0 +1,49 @@ +#ifndef RAPIDJSON_STRINGBUFFER_H_ +#define RAPIDJSON_STRINGBUFFER_H_ + +#include "rapidjson.h" +#include "internal/stack.h" + +namespace rapidjson { + +//! Represents an in-memory output stream. +/*! + \tparam Encoding Encoding of the stream. + \tparam Allocator type for allocating memory buffer. + \implements Stream +*/ +template +struct GenericStringBuffer { + typedef typename Encoding::Ch Ch; + + GenericStringBuffer(Allocator* allocator = 0, size_t capacity = kDefaultCapacity) : stack_(allocator, capacity) {} + + void Put(Ch c) { *stack_.template Push() = c; } + + void Clear() { stack_.Clear(); } + + const char* GetString() const { + // Push and pop a null terminator. This is safe. + *stack_.template Push() = '\0'; + stack_.template Pop(1); + + return stack_.template Bottom(); + } + + size_t Size() const { return stack_.GetSize(); } + + static const size_t kDefaultCapacity = 256; + mutable internal::Stack stack_; +}; + +typedef GenericStringBuffer > StringBuffer; + +//! Implement specialized version of PutN() with memset() for better performance. +template<> +inline void PutN(GenericStringBuffer >& stream, char c, size_t n) { + memset(stream.stack_.Push(n), c, n * sizeof(c)); +} + +} // namespace rapidjson + +#endif // RAPIDJSON_STRINGBUFFER_H_ diff --git a/rapidjson/writer.h b/rapidjson/writer.h new file mode 100644 index 0000000000..9d674b7bf3 --- /dev/null +++ b/rapidjson/writer.h @@ -0,0 +1,241 @@ +#ifndef RAPIDJSON_WRITER_H_ +#define RAPIDJSON_WRITER_H_ + +#include "rapidjson.h" +#include "internal/stack.h" +#include "internal/strfunc.h" +#include // snprintf() or _sprintf_s() +#include // placement new + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4127) // conditional expression is constant +#endif + +namespace rapidjson { + +//! JSON writer +/*! Writer implements the concept Handler. + It generates JSON text by events to an output stream. + + User may programmatically calls the functions of a writer to generate JSON text. + + On the other side, a writer can also be passed to objects that generates events, + + for example Reader::Parse() and Document::Accept(). + + \tparam Stream Type of ouptut stream. + \tparam Encoding Encoding of both source strings and output. + \implements Handler +*/ +template, typename Allocator = MemoryPoolAllocator<> > +class Writer { +public: + typedef typename Encoding::Ch Ch; + + Writer(Stream& stream, Allocator* allocator = 0, size_t levelDepth = kDefaultLevelDepth) : + stream_(stream), level_stack_(allocator, levelDepth * sizeof(Level)) {} + + //@name Implementation of Handler + //@{ + Writer& Null() { Prefix(kNullType); WriteNull(); return *this; } + Writer& Bool(bool b) { Prefix(b ? kTrueType : kFalseType); WriteBool(b); return *this; } + Writer& Int(int i) { Prefix(kNumberType); WriteInt(i); return *this; } + Writer& Uint(unsigned u) { Prefix(kNumberType); WriteUint(u); return *this; } + Writer& Int64(int64_t i64) { Prefix(kNumberType); WriteInt64(i64); return *this; } + Writer& Uint64(uint64_t u64) { Prefix(kNumberType); WriteUint64(u64); return *this; } + Writer& Double(double d) { Prefix(kNumberType); WriteDouble(d); return *this; } + + Writer& String(const Ch* str, SizeType length, bool copy = false) { + (void)copy; + Prefix(kStringType); + WriteString(str, length); + return *this; + } + + Writer& StartObject() { + Prefix(kObjectType); + new (level_stack_.template Push()) Level(false); + WriteStartObject(); + return *this; + } + + Writer& EndObject(SizeType memberCount = 0) { + (void)memberCount; + RAPIDJSON_ASSERT(level_stack_.GetSize() >= sizeof(Level)); + RAPIDJSON_ASSERT(!level_stack_.template Top()->inArray); + level_stack_.template Pop(1); + WriteEndObject(); + return *this; + } + + Writer& StartArray() { + Prefix(kArrayType); + new (level_stack_.template Push()) Level(true); + WriteStartArray(); + return *this; + } + + Writer& EndArray(SizeType elementCount = 0) { + (void)elementCount; + RAPIDJSON_ASSERT(level_stack_.GetSize() >= sizeof(Level)); + RAPIDJSON_ASSERT(level_stack_.template Top()->inArray); + level_stack_.template Pop(1); + WriteEndArray(); + return *this; + } + //@} + + //! Simpler but slower overload. + Writer& String(const Ch* str) { return String(str, internal::StrLen(str)); } + +protected: + //! Information for each nested level + struct Level { + Level(bool inArray_) : inArray(inArray_), valueCount(0) {} + bool inArray; //!< true if in array, otherwise in object + size_t valueCount; //!< number of values in this level + }; + + static const size_t kDefaultLevelDepth = 32; + + void WriteNull() { + stream_.Put('n'); stream_.Put('u'); stream_.Put('l'); stream_.Put('l'); + } + + void WriteBool(bool b) { + if (b) { + stream_.Put('t'); stream_.Put('r'); stream_.Put('u'); stream_.Put('e'); + } + else { + stream_.Put('f'); stream_.Put('a'); stream_.Put('l'); stream_.Put('s'); stream_.Put('e'); + } + } + + void WriteInt(int i) { + if (i < 0) { + stream_.Put('-'); + i = -i; + } + WriteUint((unsigned)i); + } + + void WriteUint(unsigned u) { + char buffer[10]; + char *p = buffer; + do { + *p++ = (u % 10) + '0'; + u /= 10; + } while (u > 0); + + do { + --p; + stream_.Put(*p); + } while (p != buffer); + } + + void WriteInt64(int64_t i64) { + if (i64 < 0) { + stream_.Put('-'); + i64 = -i64; + } + WriteUint64((uint64_t)i64); + } + + void WriteUint64(uint64_t u64) { + char buffer[20]; + char *p = buffer; + do { + *p++ = char(u64 % 10) + '0'; + u64 /= 10; + } while (u64 > 0); + + do { + --p; + stream_.Put(*p); + } while (p != buffer); + } + + //! \todo Optimization with custom double-to-string converter. + void WriteDouble(double d) { + char buffer[100]; +#if _MSC_VER + int ret = sprintf_s(buffer, sizeof(buffer), "%g", d); +#else + int ret = snprintf(buffer, sizeof(buffer), "%g", d); +#endif + RAPIDJSON_ASSERT(ret >= 1); + for (int i = 0; i < ret; i++) + stream_.Put(buffer[i]); + } + + void WriteString(const Ch* str, SizeType length) { + static const char hexDigits[] = "0123456789ABCDEF"; + static const char escape[256] = { +#define Z16 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + //0 1 2 3 4 5 6 7 8 9 A B C D E F + 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'b', 't', 'n', 'u', 'f', 'r', 'u', 'u', // 00 + 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', // 10 + 0, 0, '"', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20 + Z16, Z16, // 30~4F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'\\', 0, 0, 0, // 50 + Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16 // 60~FF +#undef Z16 + }; + + stream_.Put('\"'); + for (const Ch* p = str; p != str + length; ++p) { + if ((sizeof(Ch) == 1 || *p < 256) && escape[(unsigned char)*p]) { + stream_.Put('\\'); + stream_.Put(escape[(unsigned char)*p]); + if (escape[(unsigned char)*p] == 'u') { + stream_.Put('0'); + stream_.Put('0'); + stream_.Put(hexDigits[(*p) >> 4]); + stream_.Put(hexDigits[(*p) & 0xF]); + } + } + else + stream_.Put(*p); + } + stream_.Put('\"'); + } + + void WriteStartObject() { stream_.Put('{'); } + void WriteEndObject() { stream_.Put('}'); } + void WriteStartArray() { stream_.Put('['); } + void WriteEndArray() { stream_.Put(']'); } + + void Prefix(Type type) { + (void)type; + if (level_stack_.GetSize() != 0) { // this value is not at root + Level* level = level_stack_.template Top(); + if (level->valueCount > 0) { + if (level->inArray) + stream_.Put(','); // add comma if it is not the first element in array + else // in object + stream_.Put((level->valueCount % 2 == 0) ? ',' : ':'); + } + if (!level->inArray && level->valueCount % 2 == 0) + RAPIDJSON_ASSERT(type == kStringType); // if it's in object, then even number should be a name + level->valueCount++; + } + else + RAPIDJSON_ASSERT(type == kObjectType || type == kArrayType); + } + + Stream& stream_; + internal::Stack level_stack_; + +private: + // Prohibit assignment for VC C4512 warning + Writer& operator=(const Writer& w); +}; + +} // namespace rapidjson + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif // RAPIDJSON_RAPIDJSON_H_ From 85446be8d4a23f3f7b049ad6fdd32db8d0cfabcf Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Thu, 12 Dec 2013 07:36:03 +0000 Subject: [PATCH 1485/3753] Output as json rather than hash rockets --- Makefile | 4 ++-- cfacter.cc | 29 ++++++++++++++++++++++++++--- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 790b1a7873..fa5fac83ad 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ cfacter: cfacter.cc cfacterlib.cc cfacterlib.h - g++ -std=c++0x -g -o cfacter cfacter.cc cfacterlib.cc -I rapidjson + g++ -std=c++0x -g -o cfacter cfacter.cc cfacterlib.cc -I . cfacterlib.o: cfacterlib.cc cfacterlib.h - g++ -std=c++0x -g -fPIC -c -o $@ cfacterlib.cc -I rapidjson + g++ -std=c++0x -g -fPIC -c -o $@ cfacterlib.cc -I . cfacterlib.so: cfacterlib.o g++ -std=c++0x -g -o $@ $^ -fPIC -shared diff --git a/cfacter.cc b/cfacter.cc index 8fe7e7e88a..0b5bee4b8f 100644 --- a/cfacter.cc +++ b/cfacter.cc @@ -5,6 +5,10 @@ #include #include +#include "rapidjson/document.h" +#include "rapidjson/prettywriter.h" +#include "rapidjson/stringbuffer.h" + using namespace std; int main(int argc, char **argv) @@ -30,9 +34,28 @@ int main(int argc, char **argv) get_filesystems_facts(facts); get_hostname_facts(facts); - typedef map::iterator iter; - for (iter i = facts.begin(); i != facts.end(); ++i) { - cout << i->first << " => " << i->second << endl; + if (0) { + typedef map::iterator iter; + for (iter i = facts.begin(); i != facts.end(); ++i) { + cout << i->first << " => " << i->second << endl; + } + } + else { + rapidjson::Document json; + json.SetObject(); + + rapidjson::Document::AllocatorType& allocator = json.GetAllocator(); + + typedef map::iterator iter; + for (iter i = facts.begin(); i != facts.end(); ++i) { + json.AddMember(i->first.c_str(), i->second.c_str(), allocator); + } + + rapidjson::StringBuffer buf; + rapidjson::Writer writer(buf); + json.Accept(writer); + + cout << buf.GetString() << endl; } exit(0); } From f7c24591595d0ac35b26cb91aa55afbdc01779ae Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Thu, 12 Dec 2013 16:58:52 -0800 Subject: [PATCH 1486/3753] (maint) Add a Gemfile for acceptance testing --- acceptance/Gemfile | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 acceptance/Gemfile diff --git a/acceptance/Gemfile b/acceptance/Gemfile new file mode 100644 index 0000000000..4d65b1877c --- /dev/null +++ b/acceptance/Gemfile @@ -0,0 +1,7 @@ +source ENV['GEM_SOURCE'] || "https://rubygems.org/" + +gem "beaker", "1.2.0" + +if File.exists? "#{__FILE__}.local" + eval(File.read("#{__FILE__}.local"), binding) +end From 5baa336eaf4b7b79d8990ba9c5d258f88d4530fb Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Thu, 12 Dec 2013 16:58:52 -0800 Subject: [PATCH 1487/3753] (maint) Add a Gemfile for acceptance testing --- acceptance/Gemfile | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 acceptance/Gemfile diff --git a/acceptance/Gemfile b/acceptance/Gemfile new file mode 100644 index 0000000000..4d65b1877c --- /dev/null +++ b/acceptance/Gemfile @@ -0,0 +1,7 @@ +source ENV['GEM_SOURCE'] || "https://rubygems.org/" + +gem "beaker", "1.2.0" + +if File.exists? "#{__FILE__}.local" + eval(File.read("#{__FILE__}.local"), binding) +end From 9b4237caaaa99dd01d6a9d50532ab807343798db Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Fri, 13 Dec 2013 19:43:25 +0000 Subject: [PATCH 1488/3753] Add external facts support --- cfacter.cc | 4 ++++ cfacterlib.cc | 47 +++++++++++++++++++++++++++++++++++++++++++++++ cfacterlib.h | 2 ++ 3 files changed, 53 insertions(+) diff --git a/cfacter.cc b/cfacter.cc index 0b5bee4b8f..c5057a5078 100644 --- a/cfacter.cc +++ b/cfacter.cc @@ -15,6 +15,9 @@ int main(int argc, char **argv) { std::map facts; facts["facterversion"] = "3.0.0"; + + list external_directories; + external_directories.push_back("/etc/facter/facts.d"); get_network_facts(facts); get_kernel_facts(facts); @@ -33,6 +36,7 @@ int main(int argc, char **argv) get_dmidecode_facts(facts); get_filesystems_facts(facts); get_hostname_facts(facts); + get_external_facts(facts, external_directories); if (0) { typedef map::iterator iter; diff --git a/cfacterlib.cc b/cfacterlib.cc index 4c8bffd9ae..7142eba721 100644 --- a/cfacterlib.cc +++ b/cfacterlib.cc @@ -867,3 +867,50 @@ void get_hostname_facts(fact_map& facts) facts["domain"] = domain; facts["fqdn"] = hostname + "." + domain; } + +static void get_external_facts_from_executable(fact_map& facts, string executable) +{ + // executable + FILE *stdout = popen(executable.c_str(), "r"); + if (stdout) + { + while (!feof(stdout)) + { + const int buffer_len = 32 * 1024; + char buffer[buffer_len]; + if (fgets(buffer, buffer_len, stdout)) + { + vector elems; + split(buffer, '=', elems); + if (elems.size() != 2) continue; // shouldn't happen + string key = trim(elems[0]); + string val = trim(elems[1]); + facts[key] = val; + } + } + } +} + +static void get_external_facts(fact_map& facts, string directory) +{ + DIR *external_dir = opendir(directory.c_str()); + struct dirent *external_fact; + + while (external_fact = readdir(external_dir)) { + string full_path = directory + "/" + external_fact->d_name; + + if (access(full_path.c_str(), X_OK) != 0) + continue; + + get_external_facts_from_executable(facts, full_path); + } +} + +void get_external_facts(fact_map& facts, list directories) +{ + list::iterator iter; + for (iter = directories.begin(); iter != directories.end(); ++iter) + { + get_external_facts(facts, *iter); + } +} diff --git a/cfacterlib.h b/cfacterlib.h index 6fa6c800dc..28f9d829ff 100644 --- a/cfacterlib.h +++ b/cfacterlib.h @@ -1,3 +1,4 @@ +#include #include #include @@ -20,3 +21,4 @@ void get_architecture_facts(fact_map&); void get_dmidecode_facts(fact_map&); void get_filesystems_facts(fact_map&); void get_hostname_facts(fact_map&); +void get_external_facts(fact_map&, std::list directories); From 1a365ed53b0e1dcd79fa72c075ec0ef82ccdf66a Mon Sep 17 00:00:00 2001 From: Derek Yarnell Date: Mon, 16 Dec 2013 16:18:11 -0500 Subject: [PATCH 1489/3753] Adds support for building facter on RHEL7 via spec. --- ext/redhat/facter.spec.erb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ext/redhat/facter.spec.erb b/ext/redhat/facter.spec.erb index 46dc9c1855..26ed5d94d1 100644 --- a/ext/redhat/facter.spec.erb +++ b/ext/redhat/facter.spec.erb @@ -1,6 +1,6 @@ -# Fedora 17 ships with ruby 1.9, which uses vendorlibdir instead +# Fedora 17 ships with ruby 1.9, RHEL 7 with ruby 2.0, which use vendorlibdir instead # of sitelibdir -%if 0%{?fedora} >= 17 +%if 0%{?fedora} >= 17 || 0%{?rhel} >= 7 %global facter_libdir %(ruby -rrbconfig -e 'puts RbConfig::CONFIG["vendorlibdir"]') %else %global facter_libdir %(ruby -rrbconfig -e 'puts RbConfig::CONFIG["sitelibdir"]') @@ -39,8 +39,8 @@ Requires: virt-what Requires: ruby >= 1.8.5 BuildRequires: ruby >= 1.8.5 -# In Fedora 17 ruby-rdoc is called rubygem-rdoc -%if 0%{?fedora} >= 17 +# In Fedora 17+ or RHEL 7+ ruby-rdoc is called rubygem-rdoc +%if 0%{?fedora} >= 17 || 0%{?rhel} >= 7 BuildRequires: rubygem-rdoc %else BuildRequires: ruby-rdoc From 09cd4ea9b86226c319fd21247f87479ae0d41505 Mon Sep 17 00:00:00 2001 From: Melissa Stone Date: Tue, 17 Dec 2013 12:17:21 -0800 Subject: [PATCH 1490/3753] (packaging) Update FACTERVERSION to 1.7.4 --- lib/facter/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/version.rb b/lib/facter/version.rb index d1f7f302c3..ff609b8924 100644 --- a/lib/facter/version.rb +++ b/lib/facter/version.rb @@ -1,6 +1,6 @@ module Facter if not defined? FACTERVERSION then - FACTERVERSION = '1.7.4-rc1' + FACTERVERSION = '1.7.4' end ## From 4e41e421afb080e7409c4bca280c7f9a09ecce01 Mon Sep 17 00:00:00 2001 From: Joshua Hoblitt Date: Mon, 16 Dec 2013 21:52:10 -0800 Subject: [PATCH 1491/3753] (FACT-163) refactor fact [file] loading This patch simplifies the logic for locating and loading fact .rb files. It reduces the search space for a fact files to only the configured search dir paths (non-recursively). --- lib/facter/util/loader.rb | 103 ++++++++++------- spec/unit/util/loader_spec.rb | 206 +++++++++++----------------------- 2 files changed, 132 insertions(+), 177 deletions(-) diff --git a/lib/facter/util/loader.rb b/lib/facter/util/loader.rb index f0e5fee6a5..6001f15fba 100644 --- a/lib/facter/util/loader.rb +++ b/lib/facter/util/loader.rb @@ -7,43 +7,46 @@ class Facter::Util::Loader def initialize @loaded = [] - @valid_path = {} end # Load all resolutions for a single fact. + # + # @api public + # @param name [Symbol] def load(fact) # Now load from the search path shortname = fact.to_s.downcase load_env(shortname) filename = shortname + ".rb" - search_path.each do |dir| - # Load individual files - file = File.join(dir, filename) - load_file(file) if FileTest.exist?(file) + paths = search_path + unless paths.nil? + paths.each do |dir| + # Load individual files + file = File.join(dir, filename) - # And load any directories matching the name - factdir = File.join(dir, shortname) - load_dir(factdir) if FileTest.directory?(factdir) + load_file(file) if File.file?(file) + end end end # Load all facts from all directories. + # + # @api public def load_all return if defined?(@loaded_all) load_env - search_path.each do |dir| - next unless FileTest.directory?(dir) - - Dir.entries(dir).sort.each do |file| - path = File.join(dir, file) - if File.directory?(path) - load_dir(path) - elsif file =~ /\.rb$/ - load_file(File.join(dir, file)) + paths = search_path + unless paths.nil? + paths.each do |dir| + # dir is already an absolute path + Dir.glob(File.join(dir, '*.rb')).each do |dirent| + path = File.join(dir, dirent) + # exclude dirs that end with .rb + load_file(path) if File.file?(path) end end end @@ -51,43 +54,67 @@ def load_all @loaded_all = true end - # The list of directories we're going to search through for facts. + # List of directories to search for fact files. + # + # Search paths are gathered from the following sources: + # + # 1. $LOAD_PATH entries are expanded to absolute paths + # 2. ENV['FACTERLIB'] is split and expanded to absolute paths + # 3. Entries from Facter::search_path are used verbatim + # + # A warning will be generated for any path(s) from Facter::search_path that + # are not an absolute path to an existing directory. + # + # @api public + # @return [Array] def search_path result = [] - result += $LOAD_PATH.collect { |d| File.join(d, "facter") } - if ENV.include?("FACTERLIB") - result += ENV["FACTERLIB"].split(File::PATH_SEPARATOR) + result += $LOAD_PATH.map { |path| File.expand_path('facter', path) } + + if ENV.include?('FACTERLIB') + ENV['FACTERLIB'].split(File::PATH_SEPARATOR).each do |path| + result << File.expand_path(path) + end end - # This allows others to register additional paths we should search. - result += Facter.search_path + # silently ignore bad search paths from $LOAD_PATH and FACTERLIB + result = result.select { |path| valid_search_path?(path) } - result.select do |dir| - good = valid_search_path? dir - Facter.debugonce("Relative directory #{dir} removed from search path.") unless good - good + # This allows others to register additional paths we should search. + # We are assuming that these are already absolute paths. + result += Facter.search_path.select do |path| + valid = valid_search_path?(path) + Facter.warn "Excluding #{path} from search path. Fact file paths must be an absolute directory" unless valid + valid end - end - def valid_search_path?(path) - return @valid_path[path] unless @valid_path[path].nil? - - return @valid_path[path] = Pathname.new(path).absolute? + # remove any dups + result.uniq end - private :valid_search_path? private - def load_dir(dir) - return if dir =~ /\/\.+$/ or dir =~ /\/util$/ or dir =~ /\/lib$/ - - Dir.entries(dir).find_all { |f| f =~ /\.rb$/ }.sort.each do |file| - load_file(File.join(dir, file)) + # Validate that a path string is a valid to use for loading loading fact .rb + # files from. The path must both be absolute and a directory. + # + # @api private + # @param path [String] + # @return [Boolean] + def valid_search_path?(path) + unless File.directory?(path) and Pathname.new(path).absolute? + return false end + + true end + # Load a file and record is paths to prevent duplicate loads. + # + # @api private + # @params file [String] The *absolute path* to the file to load def load_file(file) return if @loaded.include? file + # We have to specify Kernel.load, because we have a load method. begin # Store the file path so we don't try to reload it diff --git a/spec/unit/util/loader_spec.rb b/spec/unit/util/loader_spec.rb index d0b870667d..632db37c1d 100755 --- a/spec/unit/util/loader_spec.rb +++ b/spec/unit/util/loader_spec.rb @@ -3,19 +3,6 @@ require 'spec_helper' require 'facter/util/loader' -# loader subclass for making assertions about file/directory ordering -class TestLoader < Facter::Util::Loader - def loaded_files - @loaded_files ||= [] - end - - def load_file(file) - loaded_files << file - super - end -end - - describe Facter::Util::Loader do before :each do Facter::Util::Loader.any_instance.unstub(:load_all) @@ -40,15 +27,6 @@ def load_file(file) @settings.stubs(:value).returns "/eh" end - it "should cache the result of a previous check" do - Pathname.any_instance.expects(:absolute?).returns(true).once - - # we explicitly want two calls here to check that we get - # the second from the cache - @loader.should be_valid_search_path "/foo" - @loader.should be_valid_search_path "/foo" - end - # Used to have test for " " as a directory since that should # be a relative directory, but on Windows in both 1.8.7 and # 1.9.3 it is an absolute directory (WTF Windows). Considering @@ -67,7 +45,15 @@ def load_file(file) ' /', ' \/', ].each do |dir| - it "should be false for relative path #{dir}" do + it "should be false for relative path to non-directory #{dir}" do + File.stubs(:directory?).with(dir).returns false + + @loader.should_not be_valid_search_path dir + end + + it "should be false for relative path to directory #{dir}" do + File.stubs(:directory?).with(dir).returns true + @loader.should_not be_valid_search_path dir end end @@ -83,7 +69,15 @@ def load_file(file) '/ ', '/ /..', ].each do |dir| - it "should be true for absolute path #{dir}" do + it "should be false for absolute path to non-directory #{dir}" do + File.stubs(:directory?).with(dir).returns false + + @loader.should_not be_valid_search_path dir + end + + it "should be true for absolute path to directory #{dir}" do + File.stubs(:directory?).with(dir).returns true + @loader.should be_valid_search_path dir end end @@ -97,8 +91,10 @@ def load_file(file) end it "should include the facter subdirectory of all paths in ruby LOAD_PATH" do - dirs = $LOAD_PATH.collect { |d| File.join(d, "facter") } + dirs = $LOAD_PATH.collect { |d| File.expand_path('facter', d) } @loader.stubs(:valid_search_path?).returns(true) + File.stubs(:directory?).returns true + paths = @loader.search_path dirs.each do |dir| @@ -106,15 +102,6 @@ def load_file(file) end end - it "should warn the user when an invalid search path has been excluded" do - dirs = $LOAD_PATH.collect { |d| File.join(d, "facter") } - @loader.stubs(:valid_search_path?).returns(false) - dirs.each do |dir| - Facter.expects(:debugonce).with("Relative directory #{dir} removed from search path.").once - end - paths = @loader.search_path - end - it "should exclude invalid search paths" do dirs = $LOAD_PATH.collect { |d| File.join(d, "facter") } @loader.stubs(:valid_search_path?).returns(false) @@ -126,14 +113,28 @@ def load_file(file) it "should include all search paths registered with Facter" do Facter.expects(:search_path).returns %w{/one /two} + @loader.stubs(:valid_search_path?).returns true + paths = @loader.search_path paths.should be_include("/one") paths.should be_include("/two") end + it "should warn on invalid search paths registered with Facter" do + Facter.expects(:search_path).returns %w{/one /two} + @loader.stubs(:valid_search_path?).returns false + @loader.stubs(:valid_search_path?).with('/one').returns true + @loader.stubs(:valid_search_path?).with('/two').returns false + Facter.expects(:warn).with('Excluding /two from search path. Fact file paths must be an absolute directory').once + + paths = @loader.search_path + paths.should be_include("/one") + end + describe "and the FACTERLIB environment variable is set" do it "should include all paths in FACTERLIB" do Facter::Util::Resolution.with_env "FACTERLIB" => "/one/path#{File::PATH_SEPARATOR}/two/path" do + @loader.stubs(:valid_search_path?).returns true paths = @loader.search_path %w{/one/path /two/path}.each do |dir| paths.should be_include(dir) @@ -159,22 +160,20 @@ def load_file(file) it "should load any files in the search path with names matching the fact name" do @loader.expects(:search_path).returns %w{/one/dir /two/dir} - FileTest.stubs(:exist?).returns false - FileTest.expects(:exist?).with("/one/dir/testing.rb").returns true - FileTest.expects(:exist?).with("/two/dir/testing.rb").returns true - + File.stubs(:file?).returns false + File.expects(:file?).with("/one/dir/testing.rb").returns true Kernel.expects(:load).with("/one/dir/testing.rb") - Kernel.expects(:load).with("/two/dir/testing.rb") @loader.load(:testing) end - it 'should load any ruby files in directories matching the fact name in the search path in sorted order regardless of the order returned by Dir.entries' do - @loader = TestLoader.new - + it 'should not load any ruby files from subdirectories matching the fact name in the search path' do @loader.stubs(:search_path).returns %w{/one/dir} - FileTest.stubs(:exist?).returns false - FileTest.stubs(:directory?).with("/one/dir/testing").returns true + File.stubs(:file?).returns false + File.expects(:file?).with("/one/dir/testing.rb").returns true + Kernel.expects(:load).with("/one/dir/testing.rb") + + File.stubs(:directory?).with("/one/dir/testing").returns true @loader.stubs(:search_path).returns %w{/one/dir} Dir.stubs(:entries).with("/one/dir/testing").returns %w{foo.rb bar.rb} @@ -183,29 +182,14 @@ def load_file(file) Kernel.stubs(:load).with(f) end - @loader.load(:testing) - @loader.loaded_files.should == %w{/one/dir/testing/bar.rb /one/dir/testing/foo.rb} - end - - it "should load any ruby files in directories matching the fact name in the search path" do - @loader.expects(:search_path).returns %w{/one/dir} - FileTest.stubs(:exist?).returns false - FileTest.expects(:directory?).with("/one/dir/testing").returns true - - Dir.expects(:entries).with("/one/dir/testing").returns %w{two.rb} - - Kernel.expects(:load).with("/one/dir/testing/two.rb") - @loader.load(:testing) end it "should not load files that don't end in '.rb'" do @loader.expects(:search_path).returns %w{/one/dir} - FileTest.stubs(:exist?).returns false - FileTest.expects(:directory?).with("/one/dir/testing").returns true - - Dir.expects(:entries).with("/one/dir/testing").returns %w{one} - + File.stubs(:file?).returns false + File.expects(:file?).with("/one/dir/testing.rb").returns false + File.expects(:exist?).with("/one/dir/testing").never Kernel.expects(:load).never @loader.load(:testing) @@ -217,107 +201,51 @@ def load_file(file) @loader = Facter::Util::Loader.new @loader.stubs(:search_path).returns [] - FileTest.stubs(:directory?).returns true - end - - it "should skip directories that do not exist" do - @loader.expects(:search_path).returns %w{/one/dir} - - FileTest.expects(:directory?).with("/one/dir").returns false - - Dir.expects(:entries).with("/one/dir").never - - @loader.load_all + File.stubs(:directory?).returns true end it "should load all files in all search paths" do @loader.expects(:search_path).returns %w{/one/dir /two/dir} - Dir.expects(:entries).with("/one/dir").returns %w{a.rb b.rb} - Dir.expects(:entries).with("/two/dir").returns %w{c.rb d.rb} - - %w{/one/dir/a.rb /one/dir/b.rb /two/dir/c.rb /two/dir/d.rb}.each { |f| Kernel.expects(:load).with(f) } - - @loader.load_all - end - - it "should load all files in all subdirectories in all search paths" do - @loader.expects(:search_path).returns %w{/one/dir /two/dir} - - Dir.expects(:entries).with("/one/dir").returns %w{a} - Dir.expects(:entries).with("/two/dir").returns %w{b} - - %w{/one/dir/a /two/dir/b}.each { |f| File.expects(:directory?).with(f).returns true } - - Dir.expects(:entries).with("/one/dir/a").returns %w{c.rb} - Dir.expects(:entries).with("/two/dir/b").returns %w{d.rb} - - %w{/one/dir/a/c.rb /two/dir/b/d.rb}.each { |f| Kernel.expects(:load).with(f) } - - @loader.load_all - end - - it 'should load all files in sorted order for any given directory regardless of the order returned by Dir.entries' do - @loader = TestLoader.new - - @loader.stubs(:search_path).returns %w{/one/dir} - Dir.stubs(:entries).with("/one/dir").returns %w{foo.rb bar.rb} - - %w{/one/dir}.each { |f| File.stubs(:directory?).with(f).returns true } + Dir.expects(:glob).with('/one/dir/*.rb').returns %w{a.rb b.rb} + Dir.expects(:glob).with('/two/dir/*.rb').returns %w{c.rb d.rb} - %w{/one/dir/foo.rb /one/dir/bar.rb}.each do |f| - File.stubs(:directory?).with(f).returns false + %w{/one/dir/a.rb /one/dir/b.rb /two/dir/c.rb /two/dir/d.rb}.each do |f| + File.expects(:file?).with(f).returns true Kernel.expects(:load).with(f) end @loader.load_all - - @loader.loaded_files.should == %w{/one/dir/bar.rb /one/dir/foo.rb} - end - - it "should not load files in the util subdirectory" do - @loader.expects(:search_path).returns %w{/one/dir} - - Dir.expects(:entries).with("/one/dir").returns %w{util} - - File.expects(:directory?).with("/one/dir/util").returns true - - Dir.expects(:entries).with("/one/dir/util").never - - @loader.load_all end - it "should not load files in a lib subdirectory" do - @loader.expects(:search_path).returns %w{/one/dir} - - Dir.expects(:entries).with("/one/dir").returns %w{lib} - - File.expects(:directory?).with("/one/dir/lib").returns true - - Dir.expects(:entries).with("/one/dir/lib").never - - @loader.load_all - end - - it "should not load files in '.' or '..'" do - @loader.expects(:search_path).returns %w{/one/dir} + it "should not try to load subdirectories of search paths" do + @loader.expects(:search_path).returns %w{/one/dir /two/dir} - Dir.expects(:entries).with("/one/dir").returns %w{. ..} + # a.rb is a directory + Dir.expects(:glob).with('/one/dir/*.rb').returns %w{a.rb b.rb} + File.expects(:file?).with('/one/dir/a.rb').returns false + File.expects(:file?).with('/one/dir/b.rb').returns true + Kernel.expects(:load).with('/one/dir/b.rb') - File.expects(:entries).with("/one/dir/.").never - File.expects(:entries).with("/one/dir/..").never + # c.rb is a directory + Dir.expects(:glob).with('/two/dir/*.rb').returns %w{c.rb d.rb} + File.expects(:file?).with('/two/dir/c.rb').returns false + File.expects(:file?).with('/two/dir/d.rb').returns true + Kernel.expects(:load).with('/two/dir/d.rb') @loader.load_all end it "should not raise an exception when a file is unloadable" do @loader.expects(:search_path).returns %w{/one/dir} - Dir.expects(:entries).with("/one/dir").returns %w{a.rb} + + Dir.expects(:glob).with('/one/dir/*.rb').returns %w{a.rb} + File.expects(:file?).with('/one/dir/a.rb').returns true Kernel.expects(:load).with("/one/dir/a.rb").raises(LoadError) Facter.expects(:warn) - lambda { @loader.load_all }.should_not raise_error + expect { @loader.load_all }.to_not raise_error end it "should load all facts from the environment" do From 25e65311fb45af0bceb41871248ce7651a9eaae2 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Wed, 18 Dec 2013 14:23:44 -0800 Subject: [PATCH 1492/3753] (maint) Align acceptance/Gemfile's beaker with puppet's acceptance/Gemfile --- acceptance/Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acceptance/Gemfile b/acceptance/Gemfile index 4d65b1877c..ccd15ed227 100644 --- a/acceptance/Gemfile +++ b/acceptance/Gemfile @@ -1,6 +1,6 @@ source ENV['GEM_SOURCE'] || "https://rubygems.org/" -gem "beaker", "1.2.0" +gem "beaker", "~> 1.3.1" if File.exists? "#{__FILE__}.local" eval(File.read("#{__FILE__}.local"), binding) From 20141c59301f4ff491bf7a5f216a3f974d9943b7 Mon Sep 17 00:00:00 2001 From: Joshua Hoblitt Date: Thu, 19 Dec 2013 07:17:10 -0800 Subject: [PATCH 1493/3753] (maint) restrict redcarpet gem to <= 2.3.0 Per the tag annotation from the redcarpet 3.0.0 release, 2.3.0 is the last version compatible with ruby 1.8.7. https://github.com/vmg/redcarpet/releases/tag/v3.0.0 --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index e4c7c8f1ae..8fd33e2cfe 100644 --- a/Gemfile +++ b/Gemfile @@ -9,7 +9,7 @@ platforms :ruby do gem 'watchr', :group => :development gem 'pry', :group => :development gem 'yard', :group => :development - gem 'redcarpet', :group => :development + gem 'redcarpet', '<= 2.3.0', :group => :development end group :development, :test do From 4ec07ca7634c01039860a91752e50a669914dd95 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Thu, 19 Dec 2013 09:58:38 -0800 Subject: [PATCH 1494/3753] (fact-163) Remove relative path for FACTERLIB paths (which was unintentional) --- lib/facter/util/loader.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/facter/util/loader.rb b/lib/facter/util/loader.rb index 6001f15fba..1d4bfefd85 100644 --- a/lib/facter/util/loader.rb +++ b/lib/facter/util/loader.rb @@ -59,7 +59,7 @@ def load_all # Search paths are gathered from the following sources: # # 1. $LOAD_PATH entries are expanded to absolute paths - # 2. ENV['FACTERLIB'] is split and expanded to absolute paths + # 2. ENV['FACTERLIB'] is split and used verbatim # 3. Entries from Facter::search_path are used verbatim # # A warning will be generated for any path(s) from Facter::search_path that @@ -73,7 +73,7 @@ def search_path if ENV.include?('FACTERLIB') ENV['FACTERLIB'].split(File::PATH_SEPARATOR).each do |path| - result << File.expand_path(path) + result << path end end From 4faecb0e97cba7396da91598658aaecd137f7be1 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Thu, 19 Dec 2013 22:02:41 -0800 Subject: [PATCH 1495/3753] (fact-163) Fix path construction in load_all --- lib/facter/util/loader.rb | 3 +-- spec/unit/util/loader_spec.rb | 10 +++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/facter/util/loader.rb b/lib/facter/util/loader.rb index 1d4bfefd85..4b94676712 100644 --- a/lib/facter/util/loader.rb +++ b/lib/facter/util/loader.rb @@ -43,8 +43,7 @@ def load_all unless paths.nil? paths.each do |dir| # dir is already an absolute path - Dir.glob(File.join(dir, '*.rb')).each do |dirent| - path = File.join(dir, dirent) + Dir.glob(File.join(dir, '*.rb')).each do |path| # exclude dirs that end with .rb load_file(path) if File.file?(path) end diff --git a/spec/unit/util/loader_spec.rb b/spec/unit/util/loader_spec.rb index 632db37c1d..e537dd10ba 100755 --- a/spec/unit/util/loader_spec.rb +++ b/spec/unit/util/loader_spec.rb @@ -207,8 +207,8 @@ it "should load all files in all search paths" do @loader.expects(:search_path).returns %w{/one/dir /two/dir} - Dir.expects(:glob).with('/one/dir/*.rb').returns %w{a.rb b.rb} - Dir.expects(:glob).with('/two/dir/*.rb').returns %w{c.rb d.rb} + Dir.expects(:glob).with('/one/dir/*.rb').returns %w{/one/dir/a.rb /one/dir/b.rb} + Dir.expects(:glob).with('/two/dir/*.rb').returns %w{/two/dir/c.rb /two/dir/d.rb} %w{/one/dir/a.rb /one/dir/b.rb /two/dir/c.rb /two/dir/d.rb}.each do |f| File.expects(:file?).with(f).returns true @@ -222,13 +222,13 @@ @loader.expects(:search_path).returns %w{/one/dir /two/dir} # a.rb is a directory - Dir.expects(:glob).with('/one/dir/*.rb').returns %w{a.rb b.rb} + Dir.expects(:glob).with('/one/dir/*.rb').returns %w{/one/dir/a.rb /one/dir/b.rb} File.expects(:file?).with('/one/dir/a.rb').returns false File.expects(:file?).with('/one/dir/b.rb').returns true Kernel.expects(:load).with('/one/dir/b.rb') # c.rb is a directory - Dir.expects(:glob).with('/two/dir/*.rb').returns %w{c.rb d.rb} + Dir.expects(:glob).with('/two/dir/*.rb').returns %w{/two/dir/c.rb /two/dir/d.rb} File.expects(:file?).with('/two/dir/c.rb').returns false File.expects(:file?).with('/two/dir/d.rb').returns true Kernel.expects(:load).with('/two/dir/d.rb') @@ -239,7 +239,7 @@ it "should not raise an exception when a file is unloadable" do @loader.expects(:search_path).returns %w{/one/dir} - Dir.expects(:glob).with('/one/dir/*.rb').returns %w{a.rb} + Dir.expects(:glob).with('/one/dir/*.rb').returns %w{/one/dir/a.rb} File.expects(:file?).with('/one/dir/a.rb').returns true Kernel.expects(:load).with("/one/dir/a.rb").raises(LoadError) From 58901c0101e8c8d7f1ec80bcb9356e61d7cadee3 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Tue, 10 Dec 2013 16:40:21 -0800 Subject: [PATCH 1496/3753] (fact-79) Document builtin facts in a json schema Prior to this commit there wasn't validatable documentation for the types of built-in facts. This schema matches against the type of built-in facts (at least on the platforms I tested: osx, precise, fedora18). This initial schema only uses primitive types (string, integer, boolean) but could be enhanced with enumerations (e.g. for kernel, which only assumes a limited set of values) or tighter regexes (e.g. for network addresses that follow a fixed pattern). --- schema/facter.json | 125 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 schema/facter.json diff --git a/schema/facter.json b/schema/facter.json new file mode 100644 index 0000000000..34ad2764fd --- /dev/null +++ b/schema/facter.json @@ -0,0 +1,125 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Facter built-in facts", + "description": "All the built-in facts for facter cross-platform", + "type": "object", + "patternProperties": { + "^blockdevice_[A-Za-z0-9]+_size$" : { "type": "integer" }, + "^blockdevice_[A-Za-z0-9]+_vendor$" : { "type": "string" }, + "^blockdevice_[A-Za-z0-9]+_model$" : { "type": "string" }, + "^mtu_[A-Za-z0-9]+$" : { "type": "string" }, + "^netmask_[A-Za-z0-9]+$" : { "type": "string" }, + "^network_[A-Za-z0-9]+$" : { "type": "string" }, + "^ipaddress_[A-Za-z0-9]+$" : { "type": "string" }, + "^macaddress_[A-Za-z0-9]+$" : { "type": "string" }, + "^processor[0-9]+" : { "type": "string" } + }, + "properties": { + "architecture" : { "type": "string" }, + "activeprocessorcount" : { "type": "string" }, + "augeasversion" : { "type": "string" }, + "bios_release_date" : { "type": "string" }, + "bios_vendor" : { "type": "string" }, + "bios_version" : { "type": "string" }, + "blockdevices" : { "type": "string" }, + "boardmanufacturer" : { "type": "string" }, + "boardproductname" : { "type": "string" }, + "boardserialnumber" : { "type": "string" }, + "domain" : { "type": "string" }, + "facterversion" : { "type": "string" }, + "filesystems" : { "type": "string" }, + "fqdn" : { "type": "string" }, + "gid" : { "type": "string" }, + "hardwareisa" : { "type": "string" }, + "hardwaremodel" : { "type": "string" }, + "hostname" : { "type": "string" }, + "id" : { "type": "string" }, + "interfaces" : { "type": "string" }, + "ipaddress" : { "type": "string" }, + "is_virtual" : { "type": "string" }, + "kernel" : { "type": "string" }, + "kernelmajversion" : { "type": "string" }, + "kernelrelease" : { "type": "string" }, + "kernelversion" : { "type": "string" }, + "lsbdistcodename" : { "type": "string" }, + "lsbdistdescription" : { "type": "string" }, + "lsbdistid" : { "type": "string" }, + "lsbdistrelease" : { "type": "string" }, + "lsbmajdistrelease" : { "type": "string" }, + "macaddress" : { "type": "string" }, + "macosx_buildversion" : { "type": "string" }, + "macosx_productname" : { "type": "string" }, + "macosx_productversion" : { "type": "string" }, + "macosx_productversion_major" : { "type": "string" }, + "macosx_productversion_minor" : { "type": "string" }, + "manufacturer" : { "type": "string" }, + "memoryfree" : { "type": "string" }, + "memoryfree_mb" : { "type": "string" }, + "memorysize" : { "type": "string" }, + "memorysize_mb" : { "type": "string" }, + "memorytotal" : { "type": "string" }, + "netmask" : { "type": "string" }, + "operatingsystem" : { "type": "string" }, + "operatingsystemmajrelease" : { "type": "string" }, + "operatingsystemrelease" : { "type": "string" }, + "osfamily" : { "type": "string" }, + "path" : { "type": "string" }, + "physicalprocessorcount" : { "type": "integer" }, + "processorcount" : { "type": "string" }, + "productname" : { "type": "string" }, + "ps" : { "type": "string" }, + "rubysitedir" : { "type": "string" }, + "rubyversion" : { "type": "string" }, + "selinux" : { "type": "string" }, + "selinux_config_mode" : { "type": "string" }, + "selinux_config_policy" : { "type": "string" }, + "selinux_current_mode" : { "type": "string" }, + "selinux_enforced" : { "type": "string" }, + "selinux_mode" : { "type": "string" }, + "selinux_policyversion" : { "type": "string" }, + "serialnumber" : { "type": "string" }, + "sp_boot_mode" : { "type": "string" }, + "sp_boot_rom_version" : { "type": "string" }, + "sp_boot_volume" : { "type": "string" }, + "sp_cpu_type" : { "type": "string" }, + "sp_current_processor_speed" : { "type": "string" }, + "sp_kernel_version" : { "type": "string" }, + "sp_l2_cache_core" : { "type": "string" }, + "sp_l3_cache" : { "type": "string" }, + "sp_local_host_name" : { "type": "string" }, + "sp_machine_model" : { "type": "string" }, + "sp_machine_name" : { "type": "string" }, + "sp_mmm_entry" : { "type": "string" }, + "sp_number_processors" : { "type": "string" }, + "sp_os_version" : { "type": "string" }, + "sp_packages" : { "type": "string" }, + "sp_physical_memory" : { "type": "string" }, + "sp_platform_uuid" : { "type": "string" }, + "sp_secure_vm" : { "type": "string" }, + "sp_serial_number" : { "type": "string" }, + "sp_smc_version_system" : { "type": "string" }, + "sp_uptime" : { "type": "string" }, + "sp_user_name" : { "type": "string" }, + "sshdsakey" : { "type": "string" }, + "sshecdsakey" : { "type": "string" }, + "sshfp_dsa" : { "type": "string" }, + "sshfp_ecdsa" : { "type": "string" }, + "sshfp_rsa" : { "type": "string" }, + "sshrsakey" : { "type": "string" }, + "swapencrypted" : { "type": "boolean" }, + "swapfree" : { "type": "string" }, + "swapfree_mb" : { "type": "string" }, + "swapsize" : { "type": "string" }, + "swapsize_mb" : { "type": "string" }, + "timezone" : { "type": "string" }, + "totalprocessorcount" : { "type": "string" }, + "type" : { "type": "string" }, + "uniqueid" : { "type": "string" }, + "uptime" : { "type": "string" }, + "uptime_days" : { "type": "integer" }, + "uptime_hours" : { "type": "integer" }, + "uptime_seconds" : { "type": "integer" }, + "uuid" : { "type": "string" }, + "virtual" : { "type": "string" } + } +} From beb37b2513a23aefc149d5e9737f396fd596fbd8 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Thu, 12 Dec 2013 11:57:20 -0800 Subject: [PATCH 1497/3753] (fact-79) Add facter_json_output_validate.rb Prior to this commit there was no validation of facter output against a schema. This commit adds an acceptance test to validate facter output against the recently added schema. Note that the acceptance test is currently confined off of a number of platforms, notably those which come with ruby 1.8.7, since that doesn't include the json gem. We should remove those confines as acceptance jobs are enhanced to add the gem to those platforms. This also adds a quicky validate.rb script for dev use. --- acceptance/Gemfile | 7 +++++++ acceptance/tests/facter_json_output_validates.rb | 16 ++++++++++++++++ schema/validate.rb | 8 ++++++++ 3 files changed, 31 insertions(+) create mode 100644 acceptance/tests/facter_json_output_validates.rb create mode 100755 schema/validate.rb diff --git a/acceptance/Gemfile b/acceptance/Gemfile index ccd15ed227..8878829c50 100644 --- a/acceptance/Gemfile +++ b/acceptance/Gemfile @@ -2,6 +2,13 @@ source ENV['GEM_SOURCE'] || "https://rubygems.org/" gem "beaker", "~> 1.3.1" +# json-schema does not support windows, so use the 'ruby' platform to exclude it on windows +platforms :ruby do + # json-schema uses multi_json, but chokes with multi_json 1.7.9, so prefer 1.7.7 + gem "multi_json", "1.7.7", :require => false + gem "json-schema", "2.1.1", :require => false +end + if File.exists? "#{__FILE__}.local" eval(File.read("#{__FILE__}.local"), binding) end diff --git a/acceptance/tests/facter_json_output_validates.rb b/acceptance/tests/facter_json_output_validates.rb new file mode 100644 index 0000000000..9716b14320 --- /dev/null +++ b/acceptance/tests/facter_json_output_validates.rb @@ -0,0 +1,16 @@ +require 'json' +require 'json-schema' + +test_name "Running facter --json should validate against the schema" + +confine :except, :platform => 'ubuntu-10.04' +confine :except, :platform => 'el-6' +confine :except, :platform => 'el-5' + +agents.each do |agent| + step "Agent #{agent}: run 'facter --json' and validate" + on(agent, facter('--json')) do + schema = JSON.parse(File.read("../schema/facter.json")) + fail_test "facter --json was invalid" unless JSON::Validator.validate!(schema, stdout) + end +end diff --git a/schema/validate.rb b/schema/validate.rb new file mode 100755 index 0000000000..42fcd1753c --- /dev/null +++ b/schema/validate.rb @@ -0,0 +1,8 @@ +#!/usr/bin/env ruby + +require 'json' +require 'json-schema' + +facts = `bundle exec facter --json` +schema = JSON.parse(File.read("schema/facter.json")) +JSON::Validator.validate!(schema, facts) From c436f400fa3d76b3f773f9d6ae7607f17309d638 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Tue, 17 Dec 2013 17:28:25 -0800 Subject: [PATCH 1498/3753] (fact-79) Commonize non-nil physicalprocessorcount values to string Prior to this commit, physicalprocessorcount returned an integer on non-sunos platforms, but a string on sunos. Also, all the other processorcount-related facts returned strings regardless. For consistency cross-platform and cross-processorcount-facts, this changes all physicalprocessorcount values to be strings. Note this doesn't change the one case where physicalprocessorcount returns nil, as that is being addressed by fact-107. --- lib/facter/physicalprocessorcount.rb | 6 +++--- schema/facter.json | 2 +- spec/unit/physicalprocessorcount_spec.rb | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/facter/physicalprocessorcount.rb b/lib/facter/physicalprocessorcount.rb index dbfa35be31..06a488122a 100644 --- a/lib/facter/physicalprocessorcount.rb +++ b/lib/facter/physicalprocessorcount.rb @@ -39,7 +39,7 @@ lookup_pattern = "#{sysfs_cpu_directory}" + "/cpu*/topology/physical_package_id" - Dir.glob(lookup_pattern).collect { |f| Facter::Util::Resolution.exec("cat #{f}")}.uniq.size + Dir.glob(lookup_pattern).collect { |f| Facter::Util::Resolution.exec("cat #{f}")}.uniq.size.to_s else # @@ -50,7 +50,7 @@ # str = Facter::Util::Resolution.exec("grep 'physical.\\+:' /proc/cpuinfo") - if str then str.scan(/\d+/).uniq.size; end + if str then str.scan(/\d+/).uniq.size.to_s; end end end end @@ -59,7 +59,7 @@ confine :kernel => :windows setcode do require 'facter/util/wmi' - Facter::Util::WMI.execquery("select Name from Win32_Processor").Count + Facter::Util::WMI.execquery("select Name from Win32_Processor").Count.to_s end end diff --git a/schema/facter.json b/schema/facter.json index 34ad2764fd..c689c6cde6 100644 --- a/schema/facter.json +++ b/schema/facter.json @@ -64,7 +64,7 @@ "operatingsystemrelease" : { "type": "string" }, "osfamily" : { "type": "string" }, "path" : { "type": "string" }, - "physicalprocessorcount" : { "type": "integer" }, + "physicalprocessorcount" : { "type": "string" }, "processorcount" : { "type": "string" }, "productname" : { "type": "string" }, "ps" : { "type": "string" }, diff --git a/spec/unit/physicalprocessorcount_spec.rb b/spec/unit/physicalprocessorcount_spec.rb index a36eed47f9..1e957fc936 100755 --- a/spec/unit/physicalprocessorcount_spec.rb +++ b/spec/unit/physicalprocessorcount_spec.rb @@ -14,7 +14,7 @@ Dir.stubs(:glob).with("/sys/devices/system/cpu/cpu*/topology/physical_package_id").returns(["/sys/devices/system/cpu/cpu0/topology/physical_package_id"]) Facter::Util::Resolution.stubs(:exec).with("cat /sys/devices/system/cpu/cpu0/topology/physical_package_id").returns("0") - Facter.fact(:physicalprocessorcount).value.should == 1 + Facter.fact(:physicalprocessorcount).value.should == "1" end it "should return four physical CPUs" do @@ -30,7 +30,7 @@ Facter::Util::Resolution.stubs(:exec).with("cat /sys/devices/system/cpu/cpu2/topology/physical_package_id").returns("2") Facter::Util::Resolution.stubs(:exec).with("cat /sys/devices/system/cpu/cpu3/topology/physical_package_id").returns("3") - Facter.fact(:physicalprocessorcount).value.should == 4 + Facter.fact(:physicalprocessorcount).value.should == "4" end end @@ -43,7 +43,7 @@ Facter::Util::WMI.expects(:execquery).with("select Name from Win32_Processor").returns(ole) ole.stubs(:Count).returns(4) - Facter.fact(:physicalprocessorcount).value.should == 4 + Facter.fact(:physicalprocessorcount).value.should == "4" end end From cfbcbdc114c798a3b80ed9beb68fd4c236f735c5 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Thu, 18 Oct 2012 12:20:23 -0700 Subject: [PATCH 1499/3753] (maint) Add newlines to all files (whitespace only) Without this patch there are a number of files that do not have newlines. This is a problem because it makes it difficult to use `perl -pl -i -e ...` --- spec/unit/manufacturer_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/unit/manufacturer_spec.rb b/spec/unit/manufacturer_spec.rb index dd412458b9..cacf470f27 100644 --- a/spec/unit/manufacturer_spec.rb +++ b/spec/unit/manufacturer_spec.rb @@ -112,4 +112,4 @@ end -end \ No newline at end of file +end From b04d7012e6f85c2fe028deb7225036994ad3175d Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Thu, 18 Oct 2012 12:22:56 -0700 Subject: [PATCH 1500/3753] (maint) Remove rspec from shebang lines Without this patch Ruby 1.9 is still complaining loudly about trying to parse the spec files. I'd prefer to remove the shebang lines entirely, but doing so will cause encoding errors in Ruby 1.9. This patch strives for a happy middle ground of convincing Ruby it is actually working with Ruby while not confusing it to think it should exec() to rspec. This patch is the result of the following command run against the source tree: find spec -type f -print0 | \ xargs -0 perl -pl -i -e 's,^\#\!\s?/(.*)rspec,\#! /usr/bin/env ruby,' --- spec/unit/filesystems_spec.rb | 2 +- spec/unit/kernel_spec.rb | 2 +- spec/unit/kernelmajversion_spec.rb | 2 +- spec/unit/kernelrelease_spec.rb | 2 +- spec/unit/kernelversion_spec.rb | 2 +- spec/unit/ssh_spec.rb | 2 +- spec/unit/zonename_spec.rb | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/spec/unit/filesystems_spec.rb b/spec/unit/filesystems_spec.rb index ab52adaea7..8ed6da233a 100644 --- a/spec/unit/filesystems_spec.rb +++ b/spec/unit/filesystems_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/kernel_spec.rb b/spec/unit/kernel_spec.rb index 79bc895c85..37d3431810 100644 --- a/spec/unit/kernel_spec.rb +++ b/spec/unit/kernel_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/kernelmajversion_spec.rb b/spec/unit/kernelmajversion_spec.rb index 1661e02aa1..426a87cd10 100644 --- a/spec/unit/kernelmajversion_spec.rb +++ b/spec/unit/kernelmajversion_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/kernelrelease_spec.rb b/spec/unit/kernelrelease_spec.rb index 99710874b1..8566d4aa8b 100644 --- a/spec/unit/kernelrelease_spec.rb +++ b/spec/unit/kernelrelease_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/kernelversion_spec.rb b/spec/unit/kernelversion_spec.rb index ea50069eb2..98cedccd5f 100644 --- a/spec/unit/kernelversion_spec.rb +++ b/spec/unit/kernelversion_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' diff --git a/spec/unit/ssh_spec.rb b/spec/unit/ssh_spec.rb index e1556deb3d..7fd029dd07 100755 --- a/spec/unit/ssh_spec.rb +++ b/spec/unit/ssh_spec.rb @@ -1,4 +1,4 @@ -#! /usr/bin/env ruby -S rspec +#! /usr/bin/env ruby require 'spec_helper' require 'facter/ssh' diff --git a/spec/unit/zonename_spec.rb b/spec/unit/zonename_spec.rb index b768642b81..b0c5b31967 100644 --- a/spec/unit/zonename_spec.rb +++ b/spec/unit/zonename_spec.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env rspec +#! /usr/bin/env ruby require 'spec_helper' require 'facter' From 5e4e265dae87026121ca1727ad19db5c9ca7c9d3 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Fri, 20 Dec 2013 12:23:38 -0800 Subject: [PATCH 1501/3753] (fact-79) Enforce that all built-in facts must have a schema defined --- schema/facter.json | 1 + 1 file changed, 1 insertion(+) diff --git a/schema/facter.json b/schema/facter.json index c689c6cde6..2c90536e1f 100644 --- a/schema/facter.json +++ b/schema/facter.json @@ -3,6 +3,7 @@ "title": "Facter built-in facts", "description": "All the built-in facts for facter cross-platform", "type": "object", + "additionalProperties": false, "patternProperties": { "^blockdevice_[A-Za-z0-9]+_size$" : { "type": "integer" }, "^blockdevice_[A-Za-z0-9]+_vendor$" : { "type": "string" }, From 7ae5189530078699958fe86c1c941bf7f7f75849 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Fri, 20 Dec 2013 12:45:32 -0800 Subject: [PATCH 1502/3753] (fact-79) Add a README.md for the schema and clearer validate.rb output --- schema/README.md | 34 ++++++++++++++++++++++++++++++++++ schema/validate.rb | 10 +++++++++- 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 schema/README.md diff --git a/schema/README.md b/schema/README.md new file mode 100644 index 0000000000..a69234f147 --- /dev/null +++ b/schema/README.md @@ -0,0 +1,34 @@ +Facter Schema +============= + +This directory contains a schema to document the types of facter built-in +facts. As new facts are added, this schema should be augmened to document +the type of the new facts. + +Currently, this is used for acceptance testing. + +Developer Validation +-------------------- + +As an aid to validation while developing a new fact, there is a simple +script `validate.rb`. It takes no parameters and assumes it is run from +the root of the facter tree. + +E.g. a successful run would look like: + + > schema/validate.rb + Passed validation! + +A failed run, after adding a new fact `brand_new_fact` but not updating the +schema, would look like: + + > schema/validate.rb + The property '#/' contains additional properties ["brand_new_fact"] outside of the schema when none are allowed in schema c88b014f-e52a-5479-8720-916c32f56475# + Failed validation. + +A failed run, after introducing a regression by changing `changed_fact` +from an integer to a string, would look like: + + > schema/validate.rb + The property '#/changed_fact' of type String did not match the following type: integer in schema d588f6fa-bc4a-5283-8830-23015f66c410# + Failed validation. diff --git a/schema/validate.rb b/schema/validate.rb index 42fcd1753c..3f4379a59d 100755 --- a/schema/validate.rb +++ b/schema/validate.rb @@ -5,4 +5,12 @@ facts = `bundle exec facter --json` schema = JSON.parse(File.read("schema/facter.json")) -JSON::Validator.validate!(schema, facts) +errors = JSON::Validator.fully_validate(schema, facts) +if errors.empty? + puts "Passed validation!" + exit 0 +else + puts errors + puts "Failed validation." + exit 1 +end From e38555dc8b4f95a2f52b4cfcd1019f9fd5cdb626 Mon Sep 17 00:00:00 2001 From: Andrew Parker Date: Fri, 30 Nov 2012 11:44:34 -0800 Subject: [PATCH 1503/3753] (Maint) Remove loader test dependence on ENV Previously the test for checking that FACTER_* environment variables get turned into facts was sensitive to the actual environment variables with which the test ran. This caused incorrect test failures if there was such an environment variable during the test run. This changes the loader to be able to be given the environment hash that it should look at and changes the tests to stop relying on the environment for testing. Conflicts: lib/facter/util/loader.rb spec/unit/util/loader_spec.rb --- lib/facter/util/loader.rb | 11 +++-- spec/unit/util/loader_spec.rb | 84 ++++++++++++++++++++--------------- 2 files changed, 52 insertions(+), 43 deletions(-) diff --git a/lib/facter/util/loader.rb b/lib/facter/util/loader.rb index 4b94676712..a8fb88d19f 100644 --- a/lib/facter/util/loader.rb +++ b/lib/facter/util/loader.rb @@ -5,8 +5,9 @@ # Load facts on demand. class Facter::Util::Loader - def initialize + def initialize(environment_vars = ENV) @loaded = [] + @environment_vars = environment_vars end # Load all resolutions for a single fact. @@ -70,10 +71,8 @@ def search_path result = [] result += $LOAD_PATH.map { |path| File.expand_path('facter', path) } - if ENV.include?('FACTERLIB') - ENV['FACTERLIB'].split(File::PATH_SEPARATOR).each do |path| - result << path - end + if @environment_vars.include?("FACTERLIB") + result += @environment_vars["FACTERLIB"].split(File::PATH_SEPARATOR) end # silently ignore bad search paths from $LOAD_PATH and FACTERLIB @@ -131,7 +130,7 @@ def load_file(file) # all will be loaded. def load_env(fact = nil) # Load from the environment, if possible - ENV.each do |name, value| + @environment_vars.each do |name, value| # Skip anything that doesn't match our regex. next unless name =~ /^facter_?(\w+)$/i env_name = $1 diff --git a/spec/unit/util/loader_spec.rb b/spec/unit/util/loader_spec.rb index e537dd10ba..e0e0908773 100755 --- a/spec/unit/util/loader_spec.rb +++ b/spec/unit/util/loader_spec.rb @@ -8,6 +8,14 @@ Facter::Util::Loader.any_instance.unstub(:load_all) end + def loader_from(places) + env = places[:env] || {} + search_path = places[:search_path] || [] + loader = Facter::Util::Loader.new(env) + loader.stubs(:search_path).returns search_path + loader + end + it "should have a method for loading individual facts by name" do Facter::Util::Loader.new.should respond_to(:load) end @@ -133,48 +141,46 @@ describe "and the FACTERLIB environment variable is set" do it "should include all paths in FACTERLIB" do - Facter::Util::Resolution.with_env "FACTERLIB" => "/one/path#{File::PATH_SEPARATOR}/two/path" do - @loader.stubs(:valid_search_path?).returns true - paths = @loader.search_path - %w{/one/path /two/path}.each do |dir| - paths.should be_include(dir) - end + loader = Facter::Util::Loader.new("FACTERLIB" => "/one/path#{File::PATH_SEPARATOR}/two/path") + + loader.stubs(:valid_search_path?).returns true + paths = loader.search_path + %w{/one/path /two/path}.each do |dir| + paths.should be_include(dir) end end end end describe "when loading facts" do - before do - @loader = Facter::Util::Loader.new - @loader.stubs(:search_path).returns [] - end - it "should load values from the matching environment variable if one is present" do + loader = loader_from(:env => { "facter_testing" => "yayness" }) + Facter.expects(:add).with("testing") - Facter::Util::Resolution.with_env "facter_testing" => "yayness" do - @loader.load(:testing) - end + loader.load(:testing) end it "should load any files in the search path with names matching the fact name" do - @loader.expects(:search_path).returns %w{/one/dir /two/dir} + loader = loader_from(:search_path => %w{/one/dir /two/dir}) + + loader.expects(:search_path).returns %w{/one/dir /two/dir} File.stubs(:file?).returns false File.expects(:file?).with("/one/dir/testing.rb").returns true + Kernel.expects(:load).with("/one/dir/testing.rb") - @loader.load(:testing) + loader.load(:testing) end it 'should not load any ruby files from subdirectories matching the fact name in the search path' do - @loader.stubs(:search_path).returns %w{/one/dir} + loader = Facter::Util::Loader.new File.stubs(:file?).returns false File.expects(:file?).with("/one/dir/testing.rb").returns true Kernel.expects(:load).with("/one/dir/testing.rb") File.stubs(:directory?).with("/one/dir/testing").returns true - @loader.stubs(:search_path).returns %w{/one/dir} + loader.stubs(:search_path).returns %w{/one/dir} Dir.stubs(:entries).with("/one/dir/testing").returns %w{foo.rb bar.rb} %w{/one/dir/testing/foo.rb /one/dir/testing/bar.rb}.each do |f| @@ -182,30 +188,32 @@ Kernel.stubs(:load).with(f) end - @loader.load(:testing) + loader.load(:testing) end it "should not load files that don't end in '.rb'" do - @loader.expects(:search_path).returns %w{/one/dir} + loader = Facter::Util::Loader.new + loader.expects(:search_path).returns %w{/one/dir} File.stubs(:file?).returns false File.expects(:file?).with("/one/dir/testing.rb").returns false File.expects(:exist?).with("/one/dir/testing").never Kernel.expects(:load).never - @loader.load(:testing) + loader.load(:testing) end end describe "when loading all facts" do + let(:loader) { Facter::Util::Loader.new } + before :each do - @loader = Facter::Util::Loader.new - @loader.stubs(:search_path).returns [] + loader.stubs(:search_path).returns [] File.stubs(:directory?).returns true end it "should load all files in all search paths" do - @loader.expects(:search_path).returns %w{/one/dir /two/dir} + loader = loader_from(:search_path => %w{/one/dir /two/dir}) Dir.expects(:glob).with('/one/dir/*.rb').returns %w{/one/dir/a.rb /one/dir/b.rb} Dir.expects(:glob).with('/two/dir/*.rb').returns %w{/two/dir/c.rb /two/dir/d.rb} @@ -215,11 +223,11 @@ Kernel.expects(:load).with(f) end - @loader.load_all + loader.load_all end it "should not try to load subdirectories of search paths" do - @loader.expects(:search_path).returns %w{/one/dir /two/dir} + loader.expects(:search_path).returns %w{/one/dir /two/dir} # a.rb is a directory Dir.expects(:glob).with('/one/dir/*.rb').returns %w{/one/dir/a.rb /one/dir/b.rb} @@ -233,11 +241,11 @@ File.expects(:file?).with('/two/dir/d.rb').returns true Kernel.expects(:load).with('/two/dir/d.rb') - @loader.load_all + loader.load_all end it "should not raise an exception when a file is unloadable" do - @loader.expects(:search_path).returns %w{/one/dir} + loader.expects(:search_path).returns %w{/one/dir} Dir.expects(:glob).with('/one/dir/*.rb').returns %w{/one/dir/a.rb} File.expects(:file?).with('/one/dir/a.rb').returns true @@ -245,29 +253,31 @@ Kernel.expects(:load).with("/one/dir/a.rb").raises(LoadError) Facter.expects(:warn) - expect { @loader.load_all }.to_not raise_error + expect { loader.load_all }.to_not raise_error end it "should load all facts from the environment" do Facter::Util::Resolution.with_env "facter_one" => "yayness", "facter_two" => "boo" do - @loader.load_all + loader.load_all end Facter.value(:one).should == 'yayness' Facter.value(:two).should == 'boo' end it "should only load all facts one time" do - @loader.expects(:load_env).once - @loader.load_all - @loader.load_all + loader = loader_from(:env => {}) + loader.expects(:load_env).once + + loader.load_all + loader.load_all end end it "should load facts on the facter search path only once" do facterlibdir = File.expand_path(File.dirname(__FILE__) + '../../../fixtures/unit/util/loader') - Facter::Util::Resolution.with_env 'FACTERLIB' => facterlibdir do - Facter::Util::Loader.new.load_all - Facter.value(:nosuchfact).should be_nil - end + + Facter::Util::Loader.new('FACTERLIB' => facterlibdir).load_all + + Facter.value(:nosuchfact).should be_nil end end From 098a248fa15d32fb0e68a614ccf1e4384a9622b8 Mon Sep 17 00:00:00 2001 From: Andrew Parker Date: Fri, 30 Nov 2012 12:04:27 -0800 Subject: [PATCH 1504/3753] (Maint) Remove unneeded mocking There were mocks for something called 'settings', which were never used. This removes them. --- spec/unit/util/loader_spec.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/spec/unit/util/loader_spec.rb b/spec/unit/util/loader_spec.rb index e0e0908773..8ba1185c73 100755 --- a/spec/unit/util/loader_spec.rb +++ b/spec/unit/util/loader_spec.rb @@ -31,8 +31,6 @@ def loader_from(places) describe "#valid_seach_path?" do before :each do @loader = Facter::Util::Loader.new - @settings = mock 'settings' - @settings.stubs(:value).returns "/eh" end # Used to have test for " " as a directory since that should @@ -94,8 +92,6 @@ def loader_from(places) describe "when determining the search path" do before do @loader = Facter::Util::Loader.new - @settings = mock 'settings' - @settings.stubs(:value).returns "/eh" end it "should include the facter subdirectory of all paths in ruby LOAD_PATH" do From 4ce8f7d543f2b1f988103d4098a74a106e704ebe Mon Sep 17 00:00:00 2001 From: Patrick Carlisle Date: Mon, 3 Dec 2012 17:44:45 -0800 Subject: [PATCH 1505/3753] Document Facter API with YARD This adds YARD documentation with `@api public` tags to the public API for facter. This is a first pass, so it's probably missing some things that should be considered public. Conflicts: lib/facter/util/fact.rb lib/facter/util/resolution.rb --- .yardopts | 5 + README.md | 3 +- lib/facter.rb | 230 ++++++++++++++++++++++++------ lib/facter/processor.rb | 5 +- lib/facter/util/collection.rb | 5 +- lib/facter/util/fact.rb | 51 +++++-- lib/facter/util/file_read.rb | 11 +- lib/facter/util/nothing_loader.rb | 9 +- lib/facter/util/resolution.rb | 179 +++++++++++++++++++---- lib/facter/util/values.rb | 2 +- lib/facter/version.rb | 79 +++++----- spec/unit/facter_spec.rb | 9 +- 12 files changed, 438 insertions(+), 150 deletions(-) diff --git a/.yardopts b/.yardopts index 0036282bd9..a552a9bc42 100644 --- a/.yardopts +++ b/.yardopts @@ -3,4 +3,9 @@ --verbose --markup markdown --readme README_DEVELOPER.md +--tag status +--transitive-tag status +--tag comment +--hide-tag comment +--no-transitive-tag api lib/**/*.rb diff --git a/README.md b/README.md index 5bed36e772..a4aa40b923 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,7 @@ Facter This package is largely meant to be a library for collecting facts about your system. These facts are mostly strings (i.e., not numbers), and are things -like the output of `uname`, public ssh and cfengine keys, the number of -processors, etc. +like the output of `uname`, public ssh keys, the number of processors, etc. See `bin/facter` for an example of the interface. diff --git a/lib/facter.rb b/lib/facter.rb index 760dfac52d..eff1665b57 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -16,8 +16,20 @@ require 'facter/version' +# Functions as a hash of 'facts' about your system system, such as MAC +# address, IP address, architecture, etc. +# +# @example Retrieve a fact +# puts Facter['operatingsystem'].value +# +# @example Retrieve all facts +# Facter.to_hash +# => { "kernel"=>"Linux", "uptime_days"=>0, "ipaddress"=>"10.0.0.1" } +# +# @api public module Facter - # This is just so the other classes have the constant. + # Most core functionality of facter is implemented in `Facter::Util`. + # @api public module Util; end require 'facter/util/fact' @@ -27,28 +39,25 @@ module Util; end include Comparable include Enumerable - # = Facter - # Functions as a hash of 'facts' you might care about about your - # system, such as mac address, IP address, Video card, etc. - # returns them dynamically - - # == Synopsis - # - # Generally, treat Facter as a hash: - # == Example - # require 'facter' - # puts Facter['operatingsystem'] - # - + # @api private GREEN = "" + # @api private RESET = "" + # @api private @@debug = 0 + # @api private @@timing = 0 + # @api private @@messages = {} + # @api private @@debug_messages = {} # module methods + # Accessor for the collection object which holds all the facts + # @return [Facter::Util::Collection] the collection of facts + # + # @api private def self.collection unless defined?(@collection) and @collection @collection = Facter::Util::Collection.new( @@ -58,7 +67,10 @@ def self.collection @collection end - # Add some debugging + # Prints a debug message if debugging is turned on + # + # @param string [String] the debug message + # @return [void] def self.debug(string) if string.nil? return @@ -68,7 +80,13 @@ def self.debug(string) end end - # Debug once. + # Prints a debug message only once. + # + # @note Uniqueness is based on the string, not the specific location + # of the method call. + # + # @param msg [String] the debug message + # @return [void] def self.debugonce(msg) if msg and not msg.empty? and @@debug_messages[msg].nil? @@debug_messages[msg] = true @@ -76,21 +94,31 @@ def self.debugonce(msg) end end + # Returns whether debugging output is turned on def self.debugging? @@debug != 0 end - # show the timing information + # Prints a timing + # + # @param string [String] the time to print + # @return [void] + # + # @api private def self.show_time(string) puts "#{GREEN}#{string}#{RESET}" if string and Facter.timing? end + # Returns whether timing output is turned on + # + # @api private def self.timing? @@timing != 0 end - # Facter.json? is meant to provide a lightweight way to check if the JSON - # "feature" is available. + # Returns whether the JSON "feature" is available. + # + # @api private def self.json? begin require 'json' @@ -100,34 +128,94 @@ def self.json? end end - # Return a fact object by name. If you use this, you still have to call - # 'value' on it to retrieve the actual value. + # Returns a fact object by name. If you use this, you still have to + # call {Facter::Util::Fact#value `value`} on it to retrieve the actual + # value. + # + # @param name [String] the name of the fact + # + # @return [Facter::Util::Fact, nil] The fact object, or nil if no fact + # is found. + # + # @api public def self.[](name) collection.fact(name) end - class << self - [:fact, :flush, :list, :value].each do |method| - define_method(method) do |*args| - collection.send(method, *args) - end - end + # (see []) + def self.fact(name) + collection.fact(name) + end - [:list, :to_hash].each do |method| - define_method(method) do |*args| - collection.load_all - collection.send(method, *args) - end - end + # Flushes cached values for all facts. This does not cause code to be + # reloaded; it only clears the cached results. + # + # @return [void] + # + # @api public + def self.flush + collection.flush + end + + # Lists all fact names + # + # @return [Array] array of fact names + # + # @api public + def self.list + collection.list end + # Gets the value for a fact. Returns `nil` if no such fact exists. + # + # @param name [String] the fact name + # @return [Object, nil] the value of the fact, or nil if no fact is + # found + # + # @api public + def self.value(name) + collection.value(name) + end - # Add a resolution mechanism for a named fact. This does not distinguish - # between adding a new fact and adding a new way to resolve a fact. + # Gets a hash mapping fact names to their values + # + # @return [Hash{String => Object}] the hash of fact names and values + # + # @api public + def self.to_hash + collection.load_all + collection.to_hash + end + + # Adds a {Facter::Util::Resolution resolution} mechanism for a named + # fact. This does not distinguish between adding a new fact and adding + # a new way to resolve a fact. + # + # @overload add(name, options = {}, { || ... }) + # @param name [String] the fact name + # @param options [Hash] optional parameters for the fact - attributes + # of {Facter::Util::Fact} and {Facter::Util::Resolution} can be + # supplied here + # @option options [Integer] :timeout set the + # {Facter::Util::Resolution#timeout timeout} for this resolution + # @param block [Proc] a block defining a fact resolution + # + # @return [Facter::Util::Fact] the fact object, which includes any previously + # defined resolutions + # + # @api public def self.add(name, options = {}, &block) collection.add(name, options, &block) end + # Iterates over fact names and values + # + # @yieldparam [String] name the fact name + # @yieldparam [String] value the current value of the fact + # + # @return [void] + # + # @api public def self.each # Make sure all facts are loaded. collection.load_all @@ -140,6 +228,8 @@ def self.each class << self # Allow users to call fact names directly on the Facter class, # either retrieving the value or comparing it to an existing value. + # + # @api private def method_missing(name, *args) question = false if name.to_s =~ /\?$/ @@ -168,19 +258,30 @@ def method_missing(name, *args) end end - # Clear all facts. Mostly used for testing. + # Clears all cached values and removes all facts from memory. + # + # @return [void] + # + # @api public def self.clear Facter.flush Facter.reset end - # Clear all messages. Used only in testing. Can't add to self.clear - # because we don't want to warn multiple times for items that are warnonce'd + # Clears the seen state of warning messages. See {warnonce}. + # + # @return [void] + # + # @api private def self.clear_messages @@messages.clear end - # Set debugging on or off. + # Sets debugging on or off. + # + # @return [void] + # + # @api private def self.debugging(bit) if bit case bit @@ -206,7 +307,11 @@ def self.debugging(bit) end end - # Set timing on or off. + # Sets whether timing messages are displayed. + # + # @return [void] + # + # @api private def self.timing(bit) if bit case bit @@ -223,6 +328,12 @@ def self.timing(bit) end end + # Prints a warning message. The message is only printed if debugging + # is enabled. + # + # @param msg [String] the warning message to be printed + # + # @return [void] def self.warn(msg) if Facter.debugging? and msg and not msg.empty? msg = [msg] unless msg.respond_to? :each @@ -230,7 +341,16 @@ def self.warn(msg) end end - # Warn once. + # Prints a warning message only once per process. Each unique string + # is printed once. + # + # @note Unlike {warn} the message will be printed even if debugging is + # not turned on. This behavior is likely to change and should not be + # relied on. + # + # @param msg [String] the warning message to be printed + # + # @return [void] def self.warnonce(msg) if msg and not msg.empty? and @@messages[msg].nil? @@messages[msg] = true @@ -238,24 +358,44 @@ def self.warnonce(msg) end end - # Remove them all. + # Removes all facts from memory. Use this when the fact code has + # changed on disk and needs to be reloaded. + # + # @return [void] + # + # @api public def self.reset @collection = nil end - # Load all of the default facts, and then everything from disk. + # Loads all facts. + # + # @return [void] + # + # @api public def self.loadfacts collection.load_all end @search_path = [] - # Register a directory to search through. + # Registers directories to be searched for facts. Relative paths will + # be interpreted in the current working directory. + # + # @param dirs [String] directories to search + # + # @return [void] + # + # @api public def self.search(*dirs) @search_path += dirs end - # Return our registered search directories. + # Returns the registered search directories. + # + # @return [Array] An array of the directories searched + # + # @api public def self.search_path @search_path.dup end diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb index 1d11d07210..b79af2d739 100644 --- a/lib/facter/processor.rb +++ b/lib/facter/processor.rb @@ -22,8 +22,9 @@ require 'thread' require 'facter/util/processor' -## We have to enumerate these outside a Facter.add block to get the processorN descriptions iteratively -## (but we need them inside the Facter.add block above for tests on processorcount to work) +# We have to enumerate these outside a Facter.add block to get the processorN +# descriptions iteratively (but we need them inside the Facter.add block above +# for tests on processorcount to work) processor_list = case Facter::Util::Processor.kernel_fact_value when "AIX" Facter::Util::Processor.aix_processor_list diff --git a/lib/facter/util/collection.rb b/lib/facter/util/collection.rb index 63233cbd94..120dd1c97e 100644 --- a/lib/facter/util/collection.rb +++ b/lib/facter/util/collection.rb @@ -4,6 +4,8 @@ # Manage which facts exist and how we access them. Largely just a wrapper # around a hash of facts. +# +# @api private class Facter::Util::Collection def initialize(internal_loader, external_loader) @@ -12,8 +14,7 @@ def initialize(internal_loader, external_loader) @external_loader = external_loader end - # Return a fact object by name. If you use this, you still have to call - # 'value' on it to retrieve the actual value. + # Return a fact object by name. def [](name) value(name) end diff --git a/lib/facter/util/fact.rb b/lib/facter/util/fact.rb index 3ada605e2f..f7fcc1a16e 100644 --- a/lib/facter/util/fact.rb +++ b/lib/facter/util/fact.rb @@ -1,12 +1,28 @@ require 'facter' require 'facter/util/resolution' +# This class represents a fact. Each fact has a name and multiple +# {Facter::Util::Resolution resolutions}. +# +# Create facts using {Facter.add} +# +# @api public class Facter::Util::Fact - TIMEOUT = 5 - - attr_accessor :name, :ldapname - - # Create a new fact, with no resolution mechanisms. + # The name of the fact + # @return [String] + attr_accessor :name + + # @return [String] + # @deprecated + attr_accessor :ldapname + + # Creates a new fact, with no resolution mechanisms. See {Facter.add} + # for the public API for creating facts. + # @param name [String] the fact name + # @param options [Hash] optional parameters + # @option options [String] :ldapname set the ldapname property on the fact + # + # @api private def initialize(name, options = {}) @name = name.to_s.downcase.intern @@ -28,8 +44,13 @@ def initialize(name, options = {}) @value = nil end - # Add a new resolution mechanism. This requires a block, which will then - # be evaluated in the context of the new mechanism. + # Adds a new {Facter::Util::Resolution resolution}. This requires a + # block, which will then be evaluated in the context of the new + # resolution. + # + # @return [void] + # + # @api private def add(value = nil, &block) begin resolve = Facter::Util::Resolution.new(@name) @@ -44,17 +65,21 @@ def add(value = nil, &block) end end - ## - # Flush any cached values. If the resolver has a callback block defined - # using the on_flush DSL method, then invoke that block by sending a message - # to Resolution#flush. + # Flushs any cached values. + # + # @return [void] + # + # @api private def flush @resolves.each { |r| r.flush } @value = nil end - # Return the value for a given fact. Searches through all of the mechanisms - # and returns either the first value or nil. + # Returns the value for this fact. This searches all resolutions by + # suitability and weight (see {Facter::Util::Resolution}). If no + # suitable resolution is found, it returns nil. + # + # @api public def value return @value if @value diff --git a/lib/facter/util/file_read.rb b/lib/facter/util/file_read.rb index 82404a3e1c..2b911acc8e 100644 --- a/lib/facter/util/file_read.rb +++ b/lib/facter/util/file_read.rb @@ -1,6 +1,6 @@ module Facter module Util -## + # {Facter::Util::FileRead} is a utility module intended to provide easily # mockable methods that delegate to simple file read methods. The intent is to # avoid the need to execute the `cat` system command or `File.read` directly in @@ -11,16 +11,17 @@ module Util # # @api public module FileRead - ## # read returns the raw content of a file as a string. If the file does not # exist, or the process does not have permission to read the file then nil is # returned. # # @api public # - # @return [String] the raw contents of the file at {path} or {nil} if the - # file cannot be read because it does not exist or the process does not have - # permission to read the file. + # @param path [String] the path to be read + # + # @return [String, nil] the raw contents of the file or `nil` if the + # file cannot be read because it does not exist or the process does not have + # permission to read the file. def self.read(path) File.read(path) rescue Errno::ENOENT, Errno::EACCES => detail diff --git a/lib/facter/util/nothing_loader.rb b/lib/facter/util/nothing_loader.rb index 6e337c3ab5..e700aa1cd1 100644 --- a/lib/facter/util/nothing_loader.rb +++ b/lib/facter/util/nothing_loader.rb @@ -1,13 +1,10 @@ -# An external fact loader that doesn't load anything - -# This makes it possible to disable loading -# of external facts - module Facter module Util + # An external fact loader that doesn't load anything + # This makes it possible to disable loading + # of external facts class NothingLoader - def load(collection) end end diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index a19b81aa9a..3003e66669 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -1,22 +1,39 @@ -# An actual fact resolution mechanism. These are largely just chunks of -# code, with optional confinements restricting the mechanisms to only working on -# specific systems. Note that the confinements are always ANDed, so any -# confinements specified must all be true for the resolution to be -# suitable. require 'facter/util/confine' require 'facter/util/config' require 'timeout' +# This represents a fact resolution. A resolution is a concrete +# implementation of a fact. A single fact can have many resolutions and +# the correct resolution will be chosen at runtime. Each time +# {Facter.add} is called, a new resolution is created and added to the +# set of resolutions for the fact named in the call. Each resolution +# has a {#has_weight weight}, which defines its priority over other +# resolutions, and a set of {#confine _confinements_}, which defines the +# conditions under which it will be chosen. All confinements must be +# satisfied for a fact to be considered _suitable_. +# +# @api public class Facter::Util::Resolution - attr_accessor :interpreter, :code, :name, :timeout + # The timeout, in seconds, for evaluating this resolution. The default + # is 0 which is equivalent to no timeout. This can be set using the + # options hash argument to {Facter.add}. + # @return [Integer] + # @api public + attr_accessor :timeout + + # @api private + attr_accessor :interpreter, :code, :name attr_writer :value, :weight INTERPRETER = Facter::Util::Config.is_windows? ? "cmd.exe" : "/bin/sh" # Returns the locations to be searched when looking for a binary. This # is currently determined by the +PATH+ environment variable plus - # /sbin and /usr/sbin when run on unix + # `/sbin` and `/usr/sbin` when run on unix + # + # @return [Array] the paths to be searched for binaries + # @api private def self.search_paths if Facter::Util::Config.is_windows? ENV['PATH'].split(File::PATH_SEPARATOR) @@ -28,12 +45,18 @@ def self.search_paths end end - # Determine the full path to a binary. If the supplied filename does not + # Determines the full path to a binary. If the supplied filename does not # already describe an absolute path then different locations (determined - # by self.search_paths) will be searched for a match. + # by {search_paths}) will be searched for a match. # # Returns nil if no matching executable can be found otherwise returns # the expanded pathname. + # + # @param bin [String] the executable to locate + # @return [String,nil] the full path to the executable or nil if not + # found + # + # @api public def self.which(bin) if absolute_path?(bin) return bin if File.executable?(bin) @@ -70,6 +93,9 @@ def self.which(bin) # Determine in a platform-specific way whether a path is absolute. This # defaults to the local platform if none is specified. + # + # @param path [String] the path to check + # @param platform [:posix,:windows,nil] the platform logic to use def self.absolute_path?(path, platform=nil) # Escape once for the string literal, and once for the regex. slash = '[\\\\/]' @@ -83,13 +109,14 @@ def self.absolute_path?(path, platform=nil) !! (path =~ regexes[platform]) end - # Expand the executable of a commandline to an absolute path. The executable - # is the first word of the commandline. If the executable contains spaces, - # it has be but in double quotes to be properly recognized. + # Given a command line, this returns the command line with the + # executable written as an absolute path. If the executable contains + # spaces, it has be but in double quotes to be properly recognized. # - # Returns the commandline with the expanded binary or nil if the binary - # can't be found. If the path to the binary contains quotes, the whole binary - # is put in quotes. + # @param command [String] the command line + # + # @return [String, nil] the command line with the executable's path + # expanded, or nil if the executable cannot be found. def self.expand_command(command) if match = /^"(.+?)"(?:\s+(.*))?/.match(command) exe, arguments = match.captures @@ -109,13 +136,18 @@ def self.expand_command(command) end end + # Overrides environment variables within a block of code. The + # specified values will be set for the duration of the block, after + # which the original values (if any) will be restored. + # + # @overload with_env(values, { || ... }) # - # Call this method with a block of code for which you would like to temporarily modify - # one or more environment variables; the specified values will be set for the duration - # of your block, after which the original values (if any) will be restored. + # @param values [HashString>] A hash of the environment + # variables to override # - # [values] a Hash containing the key/value pairs of any environment variables that you - # would like to temporarily override + # @return [void] + # + # @api public def self.with_env(values) old = {} values.each do |var, value| @@ -143,18 +175,32 @@ def self.with_env(values) rv end - # Execute a program and return the output of that program. + # Executes a program and return the output of that program. # # Returns nil if the program can't be found, or if there is a problem # executing the code. # + # @param code [String] the program to run + # @return [String, nil] the output of the program or nil + # + # @api public + # + # @note Since Facter 1.5.8 this strips trailing newlines from the + # returned value. If a fact will be used by versions of Facter older + # than 1.5.8 then you should call chomp the returned string. + # + # @overload exec(code) + # @overload exec(code, interpreter = nil) + # @param [String] interpreter unused, only exists for backwards + # compatibility + # @deprecated def self.exec(code, interpreter = nil) Facter.warnonce "The interpreter parameter to 'exec' is deprecated and will be removed in a future version." if interpreter ## Set LANG to force i18n to C for the duration of this exec; this ensures that any code that parses the ## output of the command can expect it to be in a consistent / predictable format / locale with_env "LANG" => "C" do - + if expanded_code = expand_command(code) # if we can find the binary, we'll run the command with the expanded path to the binary code = expanded_code @@ -164,7 +210,7 @@ def self.exec(code, interpreter = nil) return nil unless Facter::Util::Config.is_windows? return nil if absolute_path?(code) end - + out = nil begin @@ -177,7 +223,7 @@ def self.exec(code, interpreter = nil) Facter.warn(detail) return nil end - + if out == "" return nil else @@ -186,18 +232,56 @@ def self.exec(code, interpreter = nil) end end - # Add a new confine to the resolution mechanism. + # Sets the conditions for this resolution to be used. This takes a + # hash of fact names and values. Every fact must match the values + # given for that fact, otherwise this resolution will not be + # considered suitable. The values given for a fact can be an array, in + # which case the value of the fact must be in the array for it to + # match. + # + # @param confines [Hash{String => Object}] a hash of facts and the + # values they should have in order for this resolution to be + # used + # + # @example Confining to Linux + # Facter.add(:powerstates) do + # # This resolution only makes sense on linux systems + # confine :kernel => "Linux" + # setcode do + # Facter::Util::Resolution.exec('cat /sys/power/states') + # end + # end + # + # @return [void] + # + # @api public def confine(confines) confines.each do |fact, values| @confines.push Facter::Util::Confine.new(fact, *values) end end + # Sets the weight of this resolution. If multiple suitable resolutions + # are found, the one with the highest weight will be used. If weight + # is not given, the number of confines set on a resolution will be + # used as its weight (so that the most specific resolution is used). + # + # @param weight [Integer] the weight of this resolution + # + # @return [void] + # + # @api public def has_weight(weight) @weight = weight end # Create a new resolution mechanism. + # + # @param name [String] The name of the resolution. This is mostly + # unused and resolutions are treated as anonymous. + # @return [void] + # + # @api private def initialize(name) @name = name @confines = [] @@ -206,7 +290,13 @@ def initialize(name) @weight = nil end - # Return the importance of this resolution. + # Returns the importance of this resolution. If the weight was not + # given, the number of confines is used instead (so that a more + # specific resolution wins over a less specific one). + # + # @return [Integer] the weight of this resolution + # + # @api private def weight if @weight @weight @@ -215,14 +305,32 @@ def weight end end - # We need this as a getter for 'timeout', because some versions - # of ruby seem to already have a 'timeout' method and we can't - # seem to override the instance methods, somehow. + # (see #timeout) + # This is another name for {#timeout}. + # @comment We need this as a getter for 'timeout', because some versions + # of ruby seem to already have a 'timeout' method and we can't + # seem to override the instance methods, somehow. def limit @timeout end - # Set our code for returning a value. + # Sets the code block or external program that will be evaluated to + # get the value of the fact. + # + # @return [void] + # + # @overload setcode(string) + # Sets an external program to call to get the value of the resolution + # @param [String] string the external program to run to get the + # value + # + # @overload setcode(&block) + # Sets the resolution's value by evaluating a block at runtime + # @param [Proc] block The block to determine the resolution's value. + # This block is run when the fact is evaluated. Errors raised from + # inside the block are rescued and printed to stderr. + # + # @api public def setcode(string = nil, interp = nil, &block) Facter.warnonce "The interpreter parameter to 'setcode' is deprecated and will be removed in a future version." if interp if string @@ -267,17 +375,21 @@ def flush @on_flush_block.call if @on_flush_block end + # @deprecated def interpreter Facter.warnonce "The 'Facter::Util::Resolution.interpreter' method is deprecated and will be removed in a future version." @interpreter end + # @deprecated def interpreter=(interp) Facter.warnonce "The 'Facter::Util::Resolution.interpreter=' method is deprecated and will be removed in a future version." @interpreter = interp end # Is this resolution mechanism suitable on the system in question? + # + # @api private def suitable? unless defined? @suitable @suitable = ! @confines.detect { |confine| ! confine.true? } @@ -286,11 +398,16 @@ def suitable? return @suitable end + # (see value) + # @deprecated def to_s return self.value() end - # How we get a value for our resolution mechanism. + # Evaluates the code block or external program to get the value of the + # fact. + # + # @api private def value return @value if @value result = nil diff --git a/lib/facter/util/values.rb b/lib/facter/util/values.rb index a8a5bfab64..d69198767a 100644 --- a/lib/facter/util/values.rb +++ b/lib/facter/util/values.rb @@ -1,6 +1,6 @@ -# A util module for facter containing helper methods module Facter module Util + # A util module for facter containing helper methods module Values module_function diff --git a/lib/facter/version.rb b/lib/facter/version.rb index ff609b8924..784b65e1be 100644 --- a/lib/facter/version.rb +++ b/lib/facter/version.rb @@ -3,52 +3,51 @@ module Facter FACTERVERSION = '1.7.4' end - ## - # version is a public API method intended to always provide a fast and - # lightweight way to determine the version of Facter. + # Returns the running version of Facter. # - # The intent is that software external to Facter be able to determine the - # Facter version with no side-effects. The expected use is: + # @comment The intent is that software external to Facter be able to + # determine the Facter version with no side-effects. The expected + # use is: # # require 'facter/version' # version = Facter.version # - # This function has the following ordering precedence. This precedence list - # is designed to facilitate automated packaging tasks by simply writing to - # the VERSION file in the same directory as this source file. + # This function has the following ordering precedence. This precedence list + # is designed to facilitate automated packaging tasks by simply writing to + # the VERSION file in the same directory as this source file. # - # 1. If a version has been explicitly assigned using the Facter.version= - # method, return that version. - # 2. If there is a VERSION file, read the contents, trim any - # trailing whitespace, and return that version string. - # 3. Return the value of the Facter::FACTERVERSION constant hard-coded into - # the source code. + # 1. If a version has been explicitly assigned using the Facter.version= + # method, return that version. + # 2. If there is a VERSION file, read the contents, trim any + # trailing whitespace, and return that version string. + # 3. Return the value of the Facter::FACTERVERSION constant hard-coded into + # the source code. # - # If there is no VERSION file, the method must return the version string of - # the nearest parent version that is an officially released version. That is - # to say, if a branch named 3.1.x contains 25 patches on top of the most - # recent official release of 3.1.1, then the version method must return the - # string "3.1.1" if no "VERSION" file is present. + # If there is no VERSION file, the method must return the version string of + # the nearest parent version that is an officially released version. That is + # to say, if a branch named 3.1.x contains 25 patches on top of the most + # recent official release of 3.1.1, then the version method must return the + # string "3.1.1" if no "VERSION" file is present. # - # By design the version identifier is _not_ intended to vary during the life - # a process. There is no guarantee provided that writing to the VERSION file - # while a Puppet process is running will cause the version string to be - # updated. On the contrary, the contents of the VERSION are cached to reduce - # filesystem accesses. + # By design the version identifier is _not_ intended to vary during the life of + # a process. There is no guarantee provided that writing to the VERSION file + # while a Puppet process is running will cause the version string to be + # updated. On the contrary, the contents of the VERSION are cached to reduce + # filesystem accesses. # - # The VERSION file is intended to be used by package maintainers who may be - # applying patches or otherwise changing the software version in a manner - # that warrants a different software version identifier. The VERSION file is - # intended to be managed and owned by the release process and packaging - # related tasks, and as such should not reside in version control. The - # FACTERVERSION constant is intended to be version controlled in history. + # The VERSION file is intended to be used by package maintainers who may be + # applying patches or otherwise changing the software version in a manner + # that warrants a different software version identifier. The VERSION file is + # intended to be managed and owned by the release process and packaging + # related tasks, and as such should not reside in version control. The + # FACTERVERSION constant is intended to be version controlled in history. # - # Ideally, this behavior will allow package maintainers to precisely specify - # the version of the software they're packaging as in the following example: + # Ideally, this behavior will allow package maintainers to precisely specify + # the version of the software they're packaging as in the following example: # - # $ git describe > lib/facter/VERSION - # $ ruby -r facter -e 'puts Facter.version' - # 1.6.14-6-g66f2c99 + # $ git describe > lib/facter/VERSION + # $ ruby -r facter -e 'puts Facter.version' + # 1.6.14-6-g66f2c99 # # @api public # @@ -62,17 +61,21 @@ def self.version @facter_version ||= FACTERVERSION end + # Sets the Facter version + # + # @return [void] + # + # @api private def self.version=(version) @facter_version = version end - ## - # read_version_file reads the content of the "VERSION" file that lives in the + # This reads the content of the "VERSION" file that lives in the # same directory as this source code file. # # @api private # - # @return [String] for example: "1.6.14-6-gea42046" or nil if the VERSION + # @return [String] the version -- for example: "1.6.14-6-gea42046" or nil if the VERSION # file does not exist. def self.read_version_file(path) if File.exists?(path) diff --git a/spec/unit/facter_spec.rb b/spec/unit/facter_spec.rb index 3f9d086c1a..1b69d561de 100755 --- a/spec/unit/facter_spec.rb +++ b/spec/unit/facter_spec.rb @@ -17,8 +17,8 @@ end it "should delegate the :fact method to the collection" do - Facter.collection.expects(:fact) - Facter.fact + Facter.collection.expects(:fact).with("afact") + Facter.fact("afact") end it "should delegate the :list method to the collection" do @@ -28,7 +28,6 @@ it "should load all facts when listing" do Facter.collection.expects(:load_all) - Facter.collection.stubs(:list) Facter.list end @@ -44,8 +43,8 @@ end it "should delegate the :value method to the collection" do - Facter.collection.expects(:value) - Facter.value + Facter.collection.expects(:value).with("myvaluefact") + Facter.value("myvaluefact") end it "should delegate the :each method to the collection" do From 0dbfebd6439ee755273a5b3ba18f637e9e679dea Mon Sep 17 00:00:00 2001 From: Patrick Carlisle Date: Mon, 10 Dec 2012 14:33:07 -0800 Subject: [PATCH 1506/3753] Use the API to test resolution weight --- spec/unit/util/fact_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/unit/util/fact_spec.rb b/spec/unit/util/fact_spec.rb index 49a6d9fdcc..166817ed4d 100755 --- a/spec/unit/util/fact_spec.rb +++ b/spec/unit/util/fact_spec.rb @@ -48,9 +48,9 @@ end it "should re-sort the resolutions by weight, so the most restricted resolutions are first" do - @fact.add { self.value = "1"; self.weight = 1 } - @fact.add { self.value = "2"; self.weight = 2 } - @fact.add { self.value = "0"; self.weight = 0 } + @fact.add { has_weight 1; setcode { "1" } } + @fact.add { has_weight 2; setcode { "2" } } + @fact.add { has_weight 0; setcode { "0" } } @fact.value.should == "2" end end From 9a2ff516d307f6aeda044e7c2dc65ae5b2f41b09 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Wed, 13 Feb 2013 14:53:00 -0800 Subject: [PATCH 1507/3753] (doc) Update COMMITTERS and CONTRIBUTING Without this patch the documentation describing our workflow and process is out of date for Facter. This is a problem because community contributors and people new to Puppet Labs have a more difficult time learning how to submit contributions and merge contributions into the codebase. This patch addresses the problem by copying the COMMITTERS and CONTRIBUTING documents from Puppet and customizing them specifically to Facter. [ci skip] --- COMMITTERS.md | 185 ++++++++++++++++++++++++++++++ CONTRIBUTING.md | 294 ++++++++++-------------------------------------- 2 files changed, 246 insertions(+), 233 deletions(-) create mode 100644 COMMITTERS.md diff --git a/COMMITTERS.md b/COMMITTERS.md new file mode 100644 index 0000000000..6f8184643d --- /dev/null +++ b/COMMITTERS.md @@ -0,0 +1,185 @@ +Committing changes to Facter +==== + +We would like to make it easier for community members to contribute to facter +using pull requests, even if it makes the task of reviewing and committing +these changes a little harder. Pull requests are only ever based on a single +branch, however, we maintain more than one active branch. As a result +contributors should target their changes at the master branch. This makes the +process of contributing a little easier for the contributor since they don't +need to concern themselves with the question, "What branch do I base my changes +on?" This is already called out in the [CONTRIBUTING.md](http://goo.gl/XRH2J). + +Therefore, it is the responsibility of the committer to re-base the change set +on the appropriate branch which should receive the contribution. + +The rest of this document addresses the concerns of the committer. This +document will help guide the committer decide which branch to base, or re-base +a contribution on top of. This document also describes our branch management +strategy, which is closely related to the decision of what branch to commit +changes into. + +Terminology +==== + +Many of these terms have more than one meaning. For the purposes of this +document, the following terms refer to specific things. + +**contributor** - A person who makes a change to facter and submits a change +set in the form of a pull request. + +**change set** - A set of discrete patches which combined together form a +contribution. A change set takes the form of Git commits and is submitted to +facter in the form of a pull request. + +**committer** - A person responsible for reviewing a pull request and then +making the decision what base branch to merge the change set into. + +**base branch** - A branch in Git that contains an active history of changes +and will eventually be released using semantic version guidelines. The branch +named master will always exist as a base branch. All other base branches will +be associated with a specific released version of facter, e.g. 1.6.x and 1.7.x. + +Committer Guide +==== + +This section provides a guide to follow while committing change sets to facter +base branches. + +How to decide what release(s) should be patched +--- + +This section provides a guide to help a committer decide the specific base +branch that a change set should be merged into. + +The latest minor release of a major release is the only base branch that should +be patched. Older minor releases in a major release do not get patched. Before +the switch to [semantic versions](http://semver.org/) committers did not have +to think about the difference between minor and major releases. Committing to +the latest minor release of a major release is a policy intended to limit the +number of active base branches that must be managed. + +Security patches are handled as a special case. Security patches may be +applied to earlier minor releases of a major release. + +How to commit a change set to multiple base branches +--- + +A change set may apply to multiple releases. In this situation the change set +needs to be committed to multiple base branches. This section provides a guide +for how to merge patches across releases, e.g. 2.7 is patched, how should the +changes be applied to 3.0? + +First, merge the change set into the lowest numbered base branch, e.g. 2.7. +Next, merge the changed base branch up through all later base branches by using +the `--no-ff --log` git merge options. We commonly refer to this as our "merge +up process" because we merge in once, then merge up multiple times. + +When a new minor release branch is created (e.g. 3.1.x) then the previous one +is deleted (e.g. 3.0.x). Any security or urgent fixes that might have to be +applied to the older code line is done by creating an ad-hoc branch from the +tag of the last patch release of the old minor line. + +Code review checklist +--- + +This section aims to provide a checklist of things to look for when reviewing a +pull request and determining if the change set should be merged into a base +branch: + + * All tests pass + * Are there any platform gotchas? (Does a change make an assumption about + platform specific behavior that is incompatible with other platforms? e.g. + Windows paths vs. POSIX paths.) + * Is the change backwards compatible? (It should be) + * Are there YARD docs for API changes? + * Does the change set also require documentation changes? If so is the + documentation being kept up to date? + * Does the change set include clean code? (software code that is formatted + correctly and in an organized manner so that another coder can easily read + or modify it.) HINT: `git diff master --check` + * Does the change set conform to the contributing guide? + + +Commit citizen guidelines: +--- + +This section aims to provide guidelines for being a good commit citizen by +paying attention to our automated build tools. + + * Don’t push on a broken build. (A broken build is defined as a failing job + in the [Facter](https://jenkins.puppetlabs.com/view/Facter/) + page.) + * Watch the build until your changes have gone through green + * Update the ticket status and target version. The target version field in + our issue tracker should be updated to be the next release of facter. For + example, if the most recent release of facter is 1.6.17 and you merge a + backwards compatible change set into master, then the target version should + be 1.7.0 in the issue tracker.) + * Ensure the pull request is closed (Hint: amend your merge commit to contain + the string `closes #123` where 123 is the pull request number. + +Example Procedure +==== + +This section helps a committer rebase a contribution onto an earlier base +branch, then merge into the base branch and up through all active base +branches. + +Suppose a contributor submits a pull request based on master. The change set +fixes a bug reported against facter 1.7.1 which is the most recently released +version of facter. + +In this example the committer should rebase the change set onto the 1.7.x +branch since this is a bug rather than new functionality. + +First, the committer pulls down the branch using the `hub` gem. This tool +automates the process of adding the remote repository and creating a local +branch to track the remote branch. + + $ hub checkout https://github.com/puppetlabs/facter/pull/1234 + Branch jeffmccune-fix_foo_error set up to track remote branch fix_foo_error from jeffmccune. + Switched to a new branch 'jeffmccune-fix_foo_error' + +At this point the topic branch is a descendant of master, but we want it to +descend from 1.7.x. The committer creates a new branch then re-bases the +change set: + + $ git branch bug/1.7.x/fix_foo_error + $ git rebase --onto 1.7.x master bug/1.7.x/fix_foo_error + First, rewinding head to replay your work on top of it... + Applying: (#23456) Fix FooError that always bites users in 1.7.1 + +The `git rebase` command may be interpreted as, "First, check out the branch +named `bug/1.7.x/fix_foo_error`, then take the changes that were previously +based on `master` and re-base them onto `1.7.x`. + +Now that we have a topic branch containing the change set based on the correct +release branch, the committer merges in: + + $ git checkout 1.7.x + Switched to branch '1.7.x' + $ git merge --no-ff --log bug/1.7.x/fix_foo_error + Merge made by the 'recursive' strategy. + foo | 0 + 1 file changed, 0 insertions(+), 0 deletions(-) + create mode 100644 foo + +Once merged into the first base branch, the committer merges up: + + $ git checkout master + Switched to branch 'master' + $ git merge --no-ff --log 1.7.x + Merge made by the 'recursive' strategy. + foo | 0 + 1 file changed, 0 insertions(+), 0 deletions(-) + create mode 100644 foo + +Once the change set has been merged "in and up." the committer pushes. (Note, +the checklist should be complete at this point.) Note that both the 1.7.x and +master branches are being pushed at the same time. + + $ git push puppetlabs master:master 1.7.x:1.7.x + +That's it! The committer then updates the pull request, updates the issue in +our issue tracker, and keeps an eye on the build status. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 926701606a..44d68a5593 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,237 +1,65 @@ -Checklist/Outline (The short version) -================================================= - - * Getting Started: - - Make sure you have a [Redmine account](http://projects.puppetlabs.com) - - Submit a ticket for your issue, assuming one does not already exist. - - Decide what to base your work off of - * `1.6.x`: bug fixes only - * `2.x`: new features that are not breaking changes - * `master`: new features that are breaking changes - - * Making Changes: - - Make sure you have a [GitHub account](https://github.com/signup/free) - - Fork the repository on GitHub - - Make commits of logical units. - - Check for unnecessary whitespace with "git diff --check" before committing. - - Make sure your commit messages are in the proper format - - Make sure you have added the necessary tests for your changes - - Run _all_ the tests to assure nothing else was accidentally broken - - * Submitting Changes: - - Sign the [Contributor License Agreement](https://projects.puppetlabs.com/contributor_licenses/sign) - - Push your changes to a topic branch in your fork of the repository. - - Submit a pull request to the repository in the puppetlabs organization. - - Update your Redmine ticket - -The long version -================ - - 0. Create a Redmine ticket for the change you'd like to make. - - It's very important that there be a Redmine ticket for the change - you are making. Considering the number of contributions which are - submitted, it is crucial that we know we can find the ticket on Redmine. - - Before making a ticket however, be sure that one does not already exist. - You can do this by searching Redmine or by trying a Google search which - includes `sites:projects.puppetlabs.com` in addition to some of the keywords - related to your issue. - - If you do not find a ticket that that accurately describes the work - you're going to be doing, go ahead and create one. But be sure to - look for related tickets and add them to the 'related tickets' section. - - 1. Decide what to base your work on. - - In general, you should always base your work on the oldest - branch that your change is relevant to, and it will be - eventually merged up. Currently, branches will be merged up as - follows: - 1.6.x => 2.x => master - - Currently, this is how you should decide where to target your changes: - - A bug fix should be based off the the earliest place where it is - relevant. If it first appears in `1.6.x`, then it should be - targeted here and eventually merged up to `2.x` and master. - - New features which are _backwards compatible_ should be targeted - at the next release, which currently is `2.x`. - - New features that are _breaking changes_ should be targeted at - `master`. - - Part of deciding what to what your work should be based off of includes naming - your topic branch to reflect this. Your branch name should have the following - format: - `ticket/target_branch/ticket_number_short_description_of_issuee` - - For example, if you are fixing a bug relating to a hostname problem on aix, - which has Redmine ticket number 12345, then your branch should be named: - `ticket/2.x/12345_fix_hostname_on_aix` - - There is a good chance that if you submit a pull request _from_ master _to_ master, - Puppet Labs developers will suspect that you're not sure about the process. This is - why clear naming of branches and basing your work off the right place will be - extremely helpful in ensuring that your submission is reviewed and merged. Often times - if your change is targeted at the wrong place, we will bounce it back to you and wait - to review it until it has been retargeted. - - 2. Make separate commits for logically separate changes. - - Please break your commits down into logically consistent units - which include new or changed tests relevent to the rest of the - change. The goal of doing this is to make the diff easier to - read for whoever is reviewing your code. In general, the easier - your diff is to read, the more likely someone will be happy to - review it and get it into the code base. - - If you're going to refactor a piece of code, please do so as a - separate commit from your feature or bug fix changes. - - It's crucial that your changes include tests to make - sure the bug isn't re-introduced, and that the feature isn't - accidentally broken. - - Describe the technical detail of the change(s). If your - description starts to get too long, that's a good sign that you - probably need to split up your commit into more finely grained - pieces. - - Commits which plainly describe the the things which help - reviewers check the patch and future developers understand the - code are much more likely to be merged in with a minimum of - bike-shedding or requested changes. Ideally, the commit message - would include information, and be in a form suitable for - inclusion in the release notes for the version of Facter that - includes them. - - Please also check that you are not introducing any trailing - whitespaces or other "whitespace errors". You can do this by - running "git diff --check" on your changes before you commit. - - When writing commit messages, please be sure they meet - [these standards](https://github.com/erlang/otp/wiki/Writing-good-commit-messages), and please include the ticket number in your - short summary. It should look something like this: `(#12345) Fix this issue in Facter` - - 3. Sign the Contributor License Agreement - - Before we can accept your changes, we do need a signed Puppet - Labs Contributor License Agreement (CLA). - - You can access the CLA via the - [Contributor License Agreement link](https://projects.puppetlabs.com/contributor_licenses/sign) - in the top menu bar of our Redmine instance. Once you've signed - the CLA, a badge will show up next to your name on the - [Puppet Project Overview Page](http://projects.puppetlabs.com/projects/puppet?jump=welcome), - and your name will be listed under "Contributor License Signers" - section. - - If you have any questions about the CLA, please feel free to - contact Puppet Labs via email at cla-submissions@puppetlabs.com. - - 4. Sending your patches - - To submit your changes via a GitHub pull request, you must - have them on a topic branch, instead of directly on "master" - or one of the release, or RC branches. It makes things much easier - to keep track of, especially if you decide to work on another thing - before your first change is merged in. - - GitHub has some pretty good - [general documentation](http://help.github.com/) on using - their site. They also have documentation on - [creating pull requests](http://help.github.com/send-pull-requests/). - - In general, after pushing your topic branch up to your - repository on GitHub, you'll switch to the branch in the - GitHub UI and click "Pull Request" towards the top of the page - in order to open a pull request. - - You'll want to make sure that you have the appropriate - destination branch in the repository under the puppetlabs - organization. This should be the same branch that you based - your changes off of. - - 5. Update the related Redmine ticket. - - You should update the Redmine ticket associated - with the change you submitted to include the location of your branch - on the `branch` field of the ticket, and change the status to - "In Topic Branch Pending Review", along with any other commentary - you may wish to make. - -How to track the status of your change after it's been submitted -================================================================ - -Shortly after opening a pull request, there should be an automatic -email sent via GitHub. This notification is used to let the Puppet -development community know about your requested change to give them a -chance to review, test, and comment on the change(s). - -We do our best to comment on or merge submitted changes within a about week. -However, if there hasn't been any commentary on the pull request or -mailed patches, and it hasn't been merged in after a week, then feel -free to ask for an update by replying on the mailing list to the -automatic notification or mailed patches. It probably wasn't -intentional, and probably just slipped through the cracks. - -Additional Resources -==================== - -* [Getting additional help](http://projects.puppetlabs.com/projects/puppet/wiki/Getting_Help) - -* [Writing tests](http://projects.puppetlabs.com/projects/puppet/wiki/Development_Writing_Tests) - +# How to contribute + +Third-party patches are essential for keeping facter great. We simply can't +access the huge number of platforms and myriad configurations for running +facter. We want to keep it as easy as possible to contribute changes that +get things working in your environment. There are a few guidelines that we +need contributors to follow so that we can have a chance of keeping on +top of things. + +## Getting Started + +* Make sure you have a [Redmine account](http://projects.puppetlabs.com) +* Make sure you have a [GitHub account](https://github.com/signup/free) +* Submit a ticket for your issue, assuming one does not already exist. + * Clearly describe the issue including steps to reproduce when it is a bug. + * Make sure you fill in the earliest version that you know has the issue. +* Fork the repository on GitHub + +## Making Changes + +* Create a topic branch from where you want to base your work. + * This is usually the master branch. + * Only target release branches if you are certain your fix must be on that + branch. + * To quickly create a topic branch based on master; `git branch + fix/master/my_contribution master` then checkout the new branch with `git + checkout fix/master/my_contribution`. Please avoid working directly on the + `master` branch. +* Make commits of logical units. +* Check for unnecessary whitespace with `git diff --check` before committing. +* Make sure your commit messages are in the proper format. + +```` + (#99999) Make the example in CONTRIBUTING imperative and concrete + + Without this patch applied the example commit message in the CONTRIBUTING + document is not a concrete example. This is a problem because the + contributor is left to imagine what the commit message should look like + based on a description rather than an example. This patch fixes the + problem by making the example concrete and imperative. + + The first line is a real life imperative statement with a ticket number + from our issue tracker. The body describes the behavior without the patch, + why this is a problem, and how the patch fixes the problem when applied. +```` + +* Make sure you have added the necessary tests for your changes. +* Run _all_ the tests to assure nothing else was accidentally broken. + +## Submitting Changes + +* Sign the [Contributor License Agreement](https://projects.puppetlabs.com/contributor_licenses/sign). +* Push your changes to a topic branch in your fork of the repository. +* Submit a pull request to the repository in the puppetlabs organization. +* Update your Redmine ticket to mark that you have submitted code and are ready for it to be reviewed. + * Include a link to the pull request in the ticket + +# Additional Resources + +* [More information on contributing](http://links.puppetlabs.com/contribute-to-puppet) * [Bug tracker (Redmine)](http://projects.puppetlabs.com) - * [Contributor License Agreement](https://projects.puppetlabs.com/contributor_licenses/sign) - * [General GitHub documentation](http://help.github.com/) - * [GitHub pull request documentation](http://help.github.com/send-pull-requests/) - -If you have commit access to the repository -=========================================== - -Even if you have commit access to the repository, you'll still need to -go through the process above, and have someone else review and merge -in your changes. The rule is that all changes must be reviewed by a -developer on the project (that didn't write the code) to ensure that -all changes go through a code review process. - -Having someone other than the author of the topic branch recorded as -performing the merge is the record that they performed the code -review. - - * Merging topic branches - - When merging code from a topic branch into the integration branch - (Ex: master, 2.7.x, 1.6.x, etc.), there should always be a merge - commit. You can accomplish this by always providing the `--no-ff` - flag to `git merge`. - - git merge --no-ff --log tickets/master/1234-fix-something-broken - - The reason for always forcing this merge commit is that it - provides a consistent way to look up what changes & commits were - in a topic branch, whether that topic branch had one, or 500 - commits. For example, if the merge commit had an abbreviated - SHA-1 of `coffeebad`, then you could use the following `git log` - invocation to show you which commits it brought in: - - git log coffeebad^1..coffeebad^2 - - The following would show you which changes were made on the topic - branch: - - git diff coffeebad^1...coffeebad^2 - - Because we _always_ merge the topic branch into the integration - branch the first parent (`^1`) of a merge commit will be the most - recent commit on the integration branch from just before we merged - in the topic, and the second parent (`^2`) will always be the most - recent commit that was made in the topic branch. This also serves - as the record of who performed the code review, as mentioned - above. +* #puppet-dev IRC channel on freenode.org From c84164578fabc481960f35fe87c2d14a24b9c83a Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Mon, 11 Mar 2013 11:04:18 -0700 Subject: [PATCH 1508/3753] (maint) Update link to the contributor license agreement New link: http://links.puppetlabs.com/cla as per Eric. [ci skip] --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 44d68a5593..7d06a977e7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -49,7 +49,7 @@ top of things. ## Submitting Changes -* Sign the [Contributor License Agreement](https://projects.puppetlabs.com/contributor_licenses/sign). +* Sign the [Contributor License Agreement](http://links.puppetlabs.com/cla). * Push your changes to a topic branch in your fork of the repository. * Submit a pull request to the repository in the puppetlabs organization. * Update your Redmine ticket to mark that you have submitted code and are ready for it to be reviewed. @@ -59,7 +59,7 @@ top of things. * [More information on contributing](http://links.puppetlabs.com/contribute-to-puppet) * [Bug tracker (Redmine)](http://projects.puppetlabs.com) -* [Contributor License Agreement](https://projects.puppetlabs.com/contributor_licenses/sign) +* [Contributor License Agreement](http://links.puppetlabs.com/cla) * [General GitHub documentation](http://help.github.com/) * [GitHub pull request documentation](http://help.github.com/send-pull-requests/) * #puppet-dev IRC channel on freenode.org From 9036329c1c42df03f921b2da061e82cfa0ce2962 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Fri, 20 Dec 2013 14:27:23 -0800 Subject: [PATCH 1509/3753] (fact-79) Add a bunch of missing facts --- schema/facter.json | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/schema/facter.json b/schema/facter.json index 2c90536e1f..bb8b2996b1 100644 --- a/schema/facter.json +++ b/schema/facter.json @@ -9,10 +9,11 @@ "^blockdevice_[A-Za-z0-9]+_vendor$" : { "type": "string" }, "^blockdevice_[A-Za-z0-9]+_model$" : { "type": "string" }, "^mtu_[A-Za-z0-9]+$" : { "type": "string" }, - "^netmask_[A-Za-z0-9]+$" : { "type": "string" }, - "^network_[A-Za-z0-9]+$" : { "type": "string" }, - "^ipaddress_[A-Za-z0-9]+$" : { "type": "string" }, - "^macaddress_[A-Za-z0-9]+$" : { "type": "string" }, + "^netmask_[A-Za-z0-9_]+$" : { "type": "string" }, + "^network_[A-Za-z0-9_]+$" : { "type": "string" }, + "^ipaddress_[A-Za-z0-9_]+$" : { "type": "string" }, + "^ipaddress6_[A-Za-z0-9_]+$" : { "type": "string" }, + "^macaddress_[A-Za-z0-9_]+$" : { "type": "string" }, "^processor[0-9]+" : { "type": "string" } }, "properties": { @@ -26,6 +27,7 @@ "boardmanufacturer" : { "type": "string" }, "boardproductname" : { "type": "string" }, "boardserialnumber" : { "type": "string" }, + "cfkey" : { "type": "string" }, "domain" : { "type": "string" }, "facterversion" : { "type": "string" }, "filesystems" : { "type": "string" }, @@ -37,6 +39,7 @@ "id" : { "type": "string" }, "interfaces" : { "type": "string" }, "ipaddress" : { "type": "string" }, + "ipaddress6" : { "type": "string" }, "is_virtual" : { "type": "string" }, "kernel" : { "type": "string" }, "kernelmajversion" : { "type": "string" }, @@ -47,6 +50,7 @@ "lsbdistid" : { "type": "string" }, "lsbdistrelease" : { "type": "string" }, "lsbmajdistrelease" : { "type": "string" }, + "lsbrelease" : { "type": "string" }, "macaddress" : { "type": "string" }, "macosx_buildversion" : { "type": "string" }, "macosx_productname" : { "type": "string" }, @@ -67,8 +71,10 @@ "path" : { "type": "string" }, "physicalprocessorcount" : { "type": "string" }, "processorcount" : { "type": "string" }, + "processor" : { "type": "string" }, "productname" : { "type": "string" }, "ps" : { "type": "string" }, + "puppetversion" : { "type": "string" }, "rubysitedir" : { "type": "string" }, "rubyversion" : { "type": "string" }, "selinux" : { "type": "string" }, @@ -121,6 +127,11 @@ "uptime_hours" : { "type": "integer" }, "uptime_seconds" : { "type": "integer" }, "uuid" : { "type": "string" }, - "virtual" : { "type": "string" } + "virtual" : { "type": "string" }, + "vlans" : { "type": "string" }, + "xendomains" : { "type": "string" }, + "zfs_version" : { "type": "string" }, + "zonename" : { "type": "string" }, + "zpool_version" : { "type": "string" } } } From 8ebffec5426297178781894d3814f821910776ad Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Fri, 20 Dec 2013 16:02:02 -0800 Subject: [PATCH 1510/3753] (fact-79) Extend acceptance test to ruby 1.8.x platforms --- acceptance/tests/facter_json_output_validates.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/acceptance/tests/facter_json_output_validates.rb b/acceptance/tests/facter_json_output_validates.rb index 9716b14320..675e19cc0d 100644 --- a/acceptance/tests/facter_json_output_validates.rb +++ b/acceptance/tests/facter_json_output_validates.rb @@ -3,11 +3,11 @@ test_name "Running facter --json should validate against the schema" -confine :except, :platform => 'ubuntu-10.04' -confine :except, :platform => 'el-6' -confine :except, :platform => 'el-5' - agents.each do |agent| + step "Install json gem (needed on older platforms)" + win_cmd_prefix = 'cmd /c ' if agent['platform'] =~ /windows/ + on(agent, "#{win_cmd_prefix}gem install json") + step "Agent #{agent}: run 'facter --json' and validate" on(agent, facter('--json')) do schema = JSON.parse(File.read("../schema/facter.json")) From fccc67a8b9ad61a8c9b44e745d2200da9500480e Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Sat, 21 Dec 2013 08:43:12 -0800 Subject: [PATCH 1511/3753] (fact-79) Specify the agent name in the step --- acceptance/tests/facter_json_output_validates.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acceptance/tests/facter_json_output_validates.rb b/acceptance/tests/facter_json_output_validates.rb index 675e19cc0d..b76c28bcf2 100644 --- a/acceptance/tests/facter_json_output_validates.rb +++ b/acceptance/tests/facter_json_output_validates.rb @@ -4,7 +4,7 @@ test_name "Running facter --json should validate against the schema" agents.each do |agent| - step "Install json gem (needed on older platforms)" + step "Agent #{agent}: Install json gem (needed on older platforms)" win_cmd_prefix = 'cmd /c ' if agent['platform'] =~ /windows/ on(agent, "#{win_cmd_prefix}gem install json") From daa68fda17a8ac3455d79cdb4a9892f30103814b Mon Sep 17 00:00:00 2001 From: Jasper Lievisse Adriaanse Date: Sat, 21 Dec 2013 21:33:34 +0100 Subject: [PATCH 1512/3753] Add support for ssh-ed25519 keys --- lib/facter/ssh.rb | 7 +++++-- spec/unit/ssh_spec.rb | 3 ++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/facter/ssh.rb b/lib/facter/ssh.rb index 8d3c4bbd69..1314b1f943 100644 --- a/lib/facter/ssh.rb +++ b/lib/facter/ssh.rb @@ -11,8 +11,11 @@ ## Facts related to SSH ## -{"SSHDSAKey" => { :file => "ssh_host_dsa_key.pub", :sshfprrtype => 2 } , "SSHRSAKey" => { :file => "ssh_host_rsa_key.pub", :sshfprrtype => 1 }, "SSHECDSAKey" => { :file => "ssh_host_ecdsa_key.pub", :sshfprrtype => 3 } }.each do |name,key| - +{"SSHDSAKey" => { :file => "ssh_host_dsa_key.pub", :sshfprrtype => 2 }, + "SSHRSAKey" => { :file => "ssh_host_rsa_key.pub", :sshfprrtype => 1 }, + "SSHECDSAKey" => { :file => "ssh_host_ecdsa_key.pub", :sshfprrtype => 3 }, + "SSHED25519Key" => { :file => "ssh_host_ed25519_key.pub", :sshfprrtype => 4 } }.each do |name,key| + Facter.add(name) do setcode do value = nil diff --git a/spec/unit/ssh_spec.rb b/spec/unit/ssh_spec.rb index 7fd029dd07..d53c1f5f48 100755 --- a/spec/unit/ssh_spec.rb +++ b/spec/unit/ssh_spec.rb @@ -22,7 +22,8 @@ # fingerprints extracted from ssh-keygen -r '' -f /etc/ssh/ssh_host_dsa_key.pub { 'SSHRSAKey' => [ 'ssh_host_rsa_key.pub' , "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDrs+KtR8hjasELsyCiiBplUeIi77hEHzTSQt1ALG7N4IgtMg27ZAcq0tl2/O9ZarQuClc903pgionbM9Q98CtAIoqgJwdtsor7ETRmzwrcY/mvI7ne51UzQy4Eh9WrplfpNyg+EVO0FUC7mBcay6JY30QKasePp+g4MkwK5cuTzOCzd9up9KELonlH7tTm2L0YI4HhZugwVoTFulCAZvPICxSk1B/fEKyGSZVfY/UxZNqg9g2Wyvq5u40xQ5eO882UwhB3w4IbmRnPKcyotAcqOJxA7hToMKtEmFct+vjHE8T37w8axE/1X9mdvy8IZbkEBL1cupqqb8a8vU1QTg1z", "SSHFP 1 1 1e4f163a1747d0d1a08a29972c9b5d94ee5705d0\nSSHFP 1 2 4e834c91e423d6085ed6dfb880a59e2f1b04f17c1dc17da07708af67c5ab6045" ], 'SSHDSAKey' => [ 'ssh_host_dsa_key.pub' , "ssh-dss AAAAB3NzaC1kc3MAAACBAKjmRez14aZT6OKhHrsw19s7u30AdghwHFQbtC+L781YjJ3UV0/WQoZ8NaDL4ovuvW23RuO49tsqSNcVHg+PtRiN2iTVAS2h55TFhaPKhTs+i0NH3p3Ze8LNSYuz8uK7a+nTxysz47GYTHiE1ke8KXe5wGKDO1TO/MUgpDbwx72LAAAAFQD9yMJCnZMiKzA7J1RNkwvgCyBKSQAAAIAtWBAsuRM0F2fdCe+F/JmgyryQmRIT5vP8E1ww3t3ywdLHklN7UMkaEKBW/TN/jj1JOGXtZ2v5XI+0VNoNKD/7dnCGzNViRT/jjfyVi6l5UMg4Q52Gv0RXJoBJpxNqFOU2niSsy8hioyE39W6LJYWJtQozGpH/KKgkCSvxBn5hlAAAAIB1yo/YD0kQICOO0KE+UMMaKtV7FwyedFJsxsWYwZfHXGwWskf0d2+lPhd9qwdbmSvySE8Qrlvu+W+X8AipwGkItSnj16ORF8kO3lfABa+7L4BLDtumt7ybjBPcHOy3n28dd07TmMtyWvLjOb0mcxPo+TwDLtHd3L/3C1Dh41jRPg==\n", "SSHFP 2 1 f63dfe8da99f50ffbcfa40a61161cee29d109f70\nSSHFP 2 2 5f57aa6be9baddd71b6049ed5d8639664a7ddf92ce293e3887f16ad0f2d459d9" ], - 'SSHECDSAKey' => [ 'ssh_host_ecdsa_key.pub' , 'ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBIuKHtgXQUIrXSVNKC7uY+ZOF7jjfqYNU7Cb/IncDOZ7jW44dxsfBzRJwS5sTHERjBinJskY87mmwY07NFF5GoE=', "SSHFP 3 1 091a088fd3500ad9e35ce201c5101646cbf6ff98\nSSHFP 3 2 1dd2aa8f29b539337316e2862b28c196c68ffe0af78fccf9e50625635677e50f"] + 'SSHECDSAKey' => [ 'ssh_host_ecdsa_key.pub' , 'ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBIuKHtgXQUIrXSVNKC7uY+ZOF7jjfqYNU7Cb/IncDOZ7jW44dxsfBzRJwS5sTHERjBinJskY87mmwY07NFF5GoE=', "SSHFP 3 1 091a088fd3500ad9e35ce201c5101646cbf6ff98\nSSHFP 3 2 1dd2aa8f29b539337316e2862b28c196c68ffe0af78fccf9e50625635677e50f"], + 'SSHED25519Key' => [ 'ssh_host_ed25519_key.pub' , 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAkxkUMKV0H7Z0KDgfMs+iKQFwJhKUDg8GImV/BwN48X', "SSHFP 4 1 216d49ff3581a42c7a2d4064f2356b375367d493\nSSHFP 4 2 95e3aa6f86bc2dcc46f1e9e5ea930c790afc0669fcf237c4d7b0c8e386ef2790"] }.each_pair do |fact, data| describe "#{fact}" do let(:filename) { data[0] } From 9876059f67ae3af3202646fe2b685f52c99cbde7 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Sat, 21 Dec 2013 22:18:45 -0800 Subject: [PATCH 1513/3753] (fact-79) Convert ip and mac addreses to use a regex --- schema/facter.json | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/schema/facter.json b/schema/facter.json index bb8b2996b1..05ff0c897d 100644 --- a/schema/facter.json +++ b/schema/facter.json @@ -4,16 +4,26 @@ "description": "All the built-in facts for facter cross-platform", "type": "object", "additionalProperties": false, + "definitions": { + "ipaddress" : { + "type": "string", + "pattern" : "^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$" + }, + "macaddress" : { + "type": "string", + "pattern" : "^(([0-9a-fA-F]){2}\:){5}([0-9a-fA-F]){2}$" + } + }, "patternProperties": { "^blockdevice_[A-Za-z0-9]+_size$" : { "type": "integer" }, "^blockdevice_[A-Za-z0-9]+_vendor$" : { "type": "string" }, "^blockdevice_[A-Za-z0-9]+_model$" : { "type": "string" }, "^mtu_[A-Za-z0-9]+$" : { "type": "string" }, - "^netmask_[A-Za-z0-9_]+$" : { "type": "string" }, - "^network_[A-Za-z0-9_]+$" : { "type": "string" }, - "^ipaddress_[A-Za-z0-9_]+$" : { "type": "string" }, + "^netmask_[A-Za-z0-9_]+$" : { "$ref" : "#/definitions/ipaddress" }, + "^network_[A-Za-z0-9_]+$" : { "$ref" : "#/definitions/ipaddress" }, + "^ipaddress_[A-Za-z0-9_]+$" : { "$ref" : "#/definitions/ipaddress" }, "^ipaddress6_[A-Za-z0-9_]+$" : { "type": "string" }, - "^macaddress_[A-Za-z0-9_]+$" : { "type": "string" }, + "^macaddress_[A-Za-z0-9_]+$" : { "$ref" : "#/definitions/macaddress" }, "^processor[0-9]+" : { "type": "string" } }, "properties": { @@ -38,7 +48,7 @@ "hostname" : { "type": "string" }, "id" : { "type": "string" }, "interfaces" : { "type": "string" }, - "ipaddress" : { "type": "string" }, + "ipaddress" : { "$ref" : "#/definitions/ipaddress" }, "ipaddress6" : { "type": "string" }, "is_virtual" : { "type": "string" }, "kernel" : { "type": "string" }, @@ -51,7 +61,7 @@ "lsbdistrelease" : { "type": "string" }, "lsbmajdistrelease" : { "type": "string" }, "lsbrelease" : { "type": "string" }, - "macaddress" : { "type": "string" }, + "macaddress" : { "$ref" : "#/definitions/macaddress" }, "macosx_buildversion" : { "type": "string" }, "macosx_productname" : { "type": "string" }, "macosx_productversion" : { "type": "string" }, @@ -63,7 +73,7 @@ "memorysize" : { "type": "string" }, "memorysize_mb" : { "type": "string" }, "memorytotal" : { "type": "string" }, - "netmask" : { "type": "string" }, + "netmask" : { "$ref" : "#/definitions/ipaddress" }, "operatingsystem" : { "type": "string" }, "operatingsystemmajrelease" : { "type": "string" }, "operatingsystemrelease" : { "type": "string" }, From 77168fe21ad5a72cf3da28a523abe5e9ab64efe3 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 23 Dec 2013 11:43:29 -0800 Subject: [PATCH 1514/3753] (maint) Clarify physicalprocessorcount example titles --- spec/unit/physicalprocessorcount_spec.rb | 32 ++++++++++++------------ 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/spec/unit/physicalprocessorcount_spec.rb b/spec/unit/physicalprocessorcount_spec.rb index d721c5ee70..b9bbc195ce 100755 --- a/spec/unit/physicalprocessorcount_spec.rb +++ b/spec/unit/physicalprocessorcount_spec.rb @@ -17,14 +17,14 @@ File.stubs(:exists?).with('/sys/devices/system/cpu').returns(true) end - it "should return one physical CPU" do + it "returns 1 when there is 1 CPU with 1 core" do Dir.stubs(:glob).with("/sys/devices/system/cpu/cpu*/topology/physical_package_id").returns(["/sys/devices/system/cpu/cpu0/topology/physical_package_id"]) File.stubs(:read).with("/sys/devices/system/cpu/cpu0/topology/physical_package_id").returns("0") Facter.fact(:physicalprocessorcount).value.should == "1" end - it "should still return one physical CPU if on a multicore processor" do + it "returns 1 when there is 1 CPU with 4 cores" do Dir.expects(:glob).with("/sys/devices/system/cpu/cpu*/topology/physical_package_id").returns([ "/sys/devices/system/cpu/cpu0/topology/physical_package_id", "/sys/devices/system/cpu/cpu1/topology/physical_package_id", @@ -39,7 +39,7 @@ Facter.fact(:physicalprocessorcount).value.should == "1" end - it "should return four physical CPUs" do + it "returns 4 when there are 4 CPUs each with one core" do Dir.stubs(:glob).with("/sys/devices/system/cpu/cpu*/topology/physical_package_id").returns(%w{ /sys/devices/system/cpu/cpu0/topology/physical_package_id /sys/devices/system/cpu/cpu1/topology/physical_package_id @@ -55,7 +55,7 @@ Facter.fact(:physicalprocessorcount).value.should == "4" end - it "should return four physical CPUs if on 4 multicore processors" do + it "returns 4 when there are 4 CPUs each with two cores" do Dir.expects(:glob).with("/sys/devices/system/cpu/cpu*/topology/physical_package_id").returns(%w{ /sys/devices/system/cpu/cpu0/topology/physical_package_id /sys/devices/system/cpu/cpu1/topology/physical_package_id @@ -84,27 +84,27 @@ File.stubs(:exists?).with('/sys/devices/system/cpu').returns(false) end - it "should return 1 physical CPU when there are multiple cores" do - @cpuinfo = cpuinfo_fixture_read('amd64dual') - File.stubs(:read).with("/proc/cpuinfo").returns(@cpuinfo) + it "returns 1 when there is 1 amd64 CPU with 2 cores" do + cpuinfo = cpuinfo_fixture_read('amd64dual') + File.stubs(:read).with("/proc/cpuinfo").returns(cpuinfo) Facter.fact(:physicalprocessorcount).value.should == "1" end - it "should return 2 physical CPUs when there are 2 singlecore CPUs" do - @cpuinfo = cpuinfo_fixture_read('two_singlecore') - File.stubs(:read).with("/proc/cpuinfo").returns(@cpuinfo) + it "returns 2 when there are 2 CPUs each with 1 core" do + cpuinfo = cpuinfo_fixture_read('two_singlecore') + File.stubs(:read).with("/proc/cpuinfo").returns(cpuinfo) Facter.fact(:physicalprocessorcount).value.should == "2" end - it "should return 2 physical CPUs when there are 2 multicore CPUs" do - @cpuinfo = cpuinfo_fixture_read('two_multicore') - File.stubs(:read).with("/proc/cpuinfo").returns(@cpuinfo) + it "returns 2 when there are 2 CPUS each with 2 cores" do + cpuinfo = cpuinfo_fixture_read('two_multicore') + File.stubs(:read).with("/proc/cpuinfo").returns(cpuinfo) Facter.fact(:physicalprocessorcount).value.should == "2" end - it "should return 2 physical CPUs when there are 2 multicore CPUs" do - @cpuinfo = cpuinfo_fixture_read('amd64twentyfour') - File.stubs(:read).with("/proc/cpuinfo").returns(@cpuinfo) + it "returns 2 when there are 2 CPUs each with 12 cores" do + cpuinfo = cpuinfo_fixture_read('amd64twentyfour') + File.stubs(:read).with("/proc/cpuinfo").returns(cpuinfo) Facter.fact(:physicalprocessorcount).value.should == "2" end end From b5da6edacca39aab81ac70cd5c0cd586b371aebb Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 23 Dec 2013 12:57:08 -0800 Subject: [PATCH 1515/3753] (maint) Don't setup puppet.conf for facter acceptance setup --- acceptance/setup/01_TestSetup.rb | 9 --------- 1 file changed, 9 deletions(-) diff --git a/acceptance/setup/01_TestSetup.rb b/acceptance/setup/01_TestSetup.rb index ee71151f2f..6e50a30dc8 100755 --- a/acceptance/setup/01_TestSetup.rb +++ b/acceptance/setup/01_TestSetup.rb @@ -28,13 +28,4 @@ end end end - - step "Agents: create basic puppet.conf" do - agents.each do |agent| - puppetconf = File.join(agent['puppetpath'], 'puppet.conf') - - on agent, "echo '[agent]' > #{puppetconf} && " + - "echo server=#{master} >> #{puppetconf}" - end - end end From 647c0a449e14aba079cb44aa31119c9ed265c4ca Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 23 Dec 2013 13:05:42 -0800 Subject: [PATCH 1516/3753] (maint) processorcount on solaris should return a string --- lib/facter/processor.rb | 2 +- spec/unit/processor_spec.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb index b79af2d739..586d43d39c 100644 --- a/lib/facter/processor.rb +++ b/lib/facter/processor.rb @@ -178,6 +178,6 @@ end end - result + result.to_s end end diff --git a/spec/unit/processor_spec.rb b/spec/unit/processor_spec.rb index 6dad114061..c3894fde49 100755 --- a/spec/unit/processor_spec.rb +++ b/spec/unit/processor_spec.rb @@ -241,7 +241,7 @@ def sysfs_cpu_stubs(count) Facter::Util::Resolution.expects(:exec).with("/usr/sbin/psrinfo").never Facter::Util::Resolution.expects(:exec).with("/usr/bin/kstat cpu_info").returns(self.send("kstat_#{arch}".intern)) - Facter.fact(:processorcount).value.should == 8 + Facter.fact(:processorcount).value.should == '8' end end end @@ -253,7 +253,7 @@ def sysfs_cpu_stubs(count) fixture_data = File.read(fixtures('processorcount','solaris-psrinfo')) Facter::Util::Resolution.expects(:exec).with("/usr/bin/kstat cpu_info").never Facter::Util::Resolution.expects(:exec).with("/usr/sbin/psrinfo").returns(fixture_data) - Facter.fact(:processorcount).value.should == 24 + Facter.fact(:processorcount).value.should == '24' end end end From e81bd67b183bdf2fde4571b2f188f1fc48c94264 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Mon, 23 Dec 2013 13:15:25 -0800 Subject: [PATCH 1517/3753] (fact-79) Don't try 'gem install json' on windows --- acceptance/tests/facter_json_output_validates.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/acceptance/tests/facter_json_output_validates.rb b/acceptance/tests/facter_json_output_validates.rb index b76c28bcf2..a2c320d7d6 100644 --- a/acceptance/tests/facter_json_output_validates.rb +++ b/acceptance/tests/facter_json_output_validates.rb @@ -5,8 +5,7 @@ agents.each do |agent| step "Agent #{agent}: Install json gem (needed on older platforms)" - win_cmd_prefix = 'cmd /c ' if agent['platform'] =~ /windows/ - on(agent, "#{win_cmd_prefix}gem install json") + on(agent, "gem install json") unless agent['platform'] =~ /windows/ step "Agent #{agent}: run 'facter --json' and validate" on(agent, facter('--json')) do From 6231ec1321ebf42da26b5ee73c491ca889270a74 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Mon, 23 Dec 2013 13:37:48 -0800 Subject: [PATCH 1518/3753] (fact-79): Add Solaris zone facts --- schema/facter.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/schema/facter.json b/schema/facter.json index 05ff0c897d..304fb5db4d 100644 --- a/schema/facter.json +++ b/schema/facter.json @@ -142,6 +142,13 @@ "xendomains" : { "type": "string" }, "zfs_version" : { "type": "string" }, "zonename" : { "type": "string" }, + "zones" : { "type": "integer" }, + "zone_global_iptype" : { "type": "string" }, + "zone_global_name" : { "type": "string" }, + "zone_global_path" : { "type": "string" }, + "zone_global_brand" : { "type": "string" }, + "zone_global_id" : { "type": "string" }, + "zone_global_status" : { "type": "string" }, "zpool_version" : { "type": "string" } } } From 32062c14590389478016b1fb7b4d0eca5d2eca90 Mon Sep 17 00:00:00 2001 From: Andrew Parker Date: Mon, 23 Dec 2013 14:48:20 -0800 Subject: [PATCH 1519/3753] (doc) Update to point to the new Jira instance We've moved from Redmine to Jira. Docs need to point to the new location. --- CONTRIBUTING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7d06a977e7..91ac3213e2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,7 +9,7 @@ top of things. ## Getting Started -* Make sure you have a [Redmine account](http://projects.puppetlabs.com) +* Make sure you have a [Jira account](http://tickets.puppetlabs.com) * Make sure you have a [GitHub account](https://github.com/signup/free) * Submit a ticket for your issue, assuming one does not already exist. * Clearly describe the issue including steps to reproduce when it is a bug. @@ -52,13 +52,13 @@ top of things. * Sign the [Contributor License Agreement](http://links.puppetlabs.com/cla). * Push your changes to a topic branch in your fork of the repository. * Submit a pull request to the repository in the puppetlabs organization. -* Update your Redmine ticket to mark that you have submitted code and are ready for it to be reviewed. +* Update your ticket to mark that you have submitted code and are ready for it to be reviewed. * Include a link to the pull request in the ticket # Additional Resources * [More information on contributing](http://links.puppetlabs.com/contribute-to-puppet) -* [Bug tracker (Redmine)](http://projects.puppetlabs.com) +* [Bug tracker (Jira)](http://tickets.puppetlabs.com) * [Contributor License Agreement](http://links.puppetlabs.com/cla) * [General GitHub documentation](http://help.github.com/) * [GitHub pull request documentation](http://help.github.com/send-pull-requests/) From 7fd3d1fe802c7114aeceeee2851cb399f644a9bb Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Mon, 23 Dec 2013 15:25:18 -0800 Subject: [PATCH 1520/3753] (fact-79) Enforce standard format for all macaddresses Prior to this patch, the 'macaddress' fact was standardized, but the 'macaddress_' facts were not. This patch adds a spec test to catch this and the fix to use the same method for both. --- lib/facter/interfaces.rb | 7 ++++++- spec/unit/interfaces_spec.rb | 10 ++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/facter/interfaces.rb b/lib/facter/interfaces.rb index 4ace619bbf..75706c7cce 100644 --- a/lib/facter/interfaces.rb +++ b/lib/facter/interfaces.rb @@ -15,6 +15,7 @@ # require 'facter/util/ip' +require 'facter/util/macaddress' # Note that most of this only works on a fixed list of platforms; notably, Darwin # is missing. @@ -34,7 +35,11 @@ %w{ipaddress ipaddress6 macaddress netmask mtu}.each do |label| Facter.add(label + "_" + Facter::Util::IP.alphafy(interface)) do setcode do - Facter::Util::IP.get_interface_value(interface, label) + value = Facter::Util::IP.get_interface_value(interface, label) + if label == "macaddress" + value = Facter::Util::Macaddress.standardize(value) + end + value end end end diff --git a/spec/unit/interfaces_spec.rb b/spec/unit/interfaces_spec.rb index 41a7d660f0..1b244c50af 100755 --- a/spec/unit/interfaces_spec.rb +++ b/spec/unit/interfaces_spec.rb @@ -31,6 +31,16 @@ Facter::Util::IP.stubs(:get_interfaces).returns ["Local Area Connection", "Loopback \"Pseudo-Interface\" (#1)"] Facter.fact(:interfaces).value.should == %{Local_Area_Connection,Loopback__Pseudo_Interface____1_} end + + it "should properly format a mac address" do + Facter::Util::IP.stubs(:get_interfaces).returns ["net0"] + Facter::Util::IP.stubs(:get_interface_value).returns "0:12:34:56:78:90" + + Facter.collection.internal_loader.load(:interfaces) + + fact = Facter.fact("macaddress_net0".intern) + fact.value.should eq("00:12:34:56:78:90") + end end describe "Netmask handling on Linux" do From 3690b91f77afb6f6d57bc77ed8e408b84491b1cd Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Mon, 23 Dec 2013 16:19:29 -0800 Subject: [PATCH 1521/3753] (fact-79) Add lsbminordistrelease --- schema/facter.json | 1 + 1 file changed, 1 insertion(+) diff --git a/schema/facter.json b/schema/facter.json index 304fb5db4d..1bb5c6da62 100644 --- a/schema/facter.json +++ b/schema/facter.json @@ -60,6 +60,7 @@ "lsbdistid" : { "type": "string" }, "lsbdistrelease" : { "type": "string" }, "lsbmajdistrelease" : { "type": "string" }, + "lsbminordistrelease" : { "type": "string" }, "lsbrelease" : { "type": "string" }, "macaddress" : { "$ref" : "#/definitions/macaddress" }, "macosx_buildversion" : { "type": "string" }, From 4d99ecdaf4cd271c805f1bd6ea719861537a2fef Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Mon, 23 Dec 2013 16:42:04 -0800 Subject: [PATCH 1522/3753] (fact-79) Standardize macaddress format --- lib/facter/util/ip.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index 4d40d618d7..424ed3c989 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -12,6 +12,8 @@ require 'facter/util/ip/hpux' require 'facter/util/ip/gnu_k_free_bsd' +require 'facter/util/macaddress' + # A base module for collecting IP related info from all kinds of platforms. class Facter::Util::IP INTERFACE_KEYS = %w[ipaddress ipaddress6 macaddress netmask mtu] @@ -178,7 +180,7 @@ def stringified_interfaces end # Defines all of the dynamic interface facts derived from parsing the output - # of the network interface ouput. The interface facts are dynamic, so this + # of the network interface output. The interface facts are dynamic, so this # method has the behavior of figuring out what facts need to be added and how # they should be resolved. # @@ -196,7 +198,11 @@ def add_dynamic_interface_facts # Don't resolve if the interface has since been deleted if keys_hash = model.interfaces_hash[interface] - keys_hash[key] + if key == "macaddress" + Facter::Util::Macaddress.standardize(keys_hash[key]) + else + keys_hash[key] + end end end From d75e5c62b41bfcb8ab9ecb1f9d99a767612de30f Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Thu, 26 Dec 2013 23:32:15 -0800 Subject: [PATCH 1523/3753] (maint) Remove monkey patch Prior to this patch, running spec tests with ruby 2.x would result in deprecation warnings like so: /Users/kylo/.rbenv/versions/2.1.0/lib/ruby/2.1.0/delegate.rb:343: warning: IO#lines is deprecated; use #each_line instead This patch removes the monkey patching that led to those warnings, and simply changes the code to use each_line rather than lines, which is apparently supported in all the ruby versions from 1.8.5 to 2.x. --- lib/facter/selinux.rb | 2 +- lib/facter/util/monkey_patches.rb | 31 -------------------- spec/unit/util/monkey_patches_spec.rb | 42 --------------------------- 3 files changed, 1 insertion(+), 74 deletions(-) delete mode 100644 spec/unit/util/monkey_patches_spec.rb diff --git a/lib/facter/selinux.rb b/lib/facter/selinux.rb index 4d8e801456..0c54cb9e43 100644 --- a/lib/facter/selinux.rb +++ b/lib/facter/selinux.rb @@ -22,7 +22,7 @@ def selinux_mount_point # a hang. Reading from other parts of /proc does not seem to cause this problem. # The work around is to read the file in another process. # -- andy Fri Aug 31 2012 - selinux_line = Facter::Util::Resolution.exec('cat /proc/self/mounts').lines.find { |line| line =~ /selinuxfs/ } + selinux_line = Facter::Util::Resolution.exec('cat /proc/self/mounts').each_line.find { |line| line =~ /selinuxfs/ } if selinux_line path = selinux_line.split[1] end diff --git a/lib/facter/util/monkey_patches.rb b/lib/facter/util/monkey_patches.rb index 0946137e92..54bbff83d6 100644 --- a/lib/facter/util/monkey_patches.rb +++ b/lib/facter/util/monkey_patches.rb @@ -2,38 +2,7 @@ # version 1.8.5. This allows us to use RbConfig in place of the older Config in # our code and still be compatible with at least Ruby 1.8.1. require 'rbconfig' -require 'enumerator' unless defined? ::RbConfig ::RbConfig = ::Config end - -module Facter - module Util - module MonkeyPatches - module Lines - def lines(separator = $/) - if block_given? - self.each_line(separator) {|line| yield line } - return self - else - return enum_for(:each_line, separator) - end - end - end - end - end -end - -public -class String - unless method_defined? :lines - include Facter::Util::MonkeyPatches::Lines - end -end - -class IO - unless method_defined? :lines - include Facter::Util::MonkeyPatches::Lines - end -end diff --git a/spec/unit/util/monkey_patches_spec.rb b/spec/unit/util/monkey_patches_spec.rb deleted file mode 100644 index 13d3fa6a4e..0000000000 --- a/spec/unit/util/monkey_patches_spec.rb +++ /dev/null @@ -1,42 +0,0 @@ -require 'spec_helper' -require 'tempfile' - -describe 'Monkey Patches' do - let(:subject) { "a b c d e f\ng h i j" } - - context 'String' do - it "should respond to lines" do - subject.lines.to_a.should == ["a b c d e f\n", "g h i j"] - end - it "should accept a block" do - our_lines = [] - subject.lines do |line| our_lines << line end - our_lines.should == ["a b c d e f\n", "g h i j"] - end - end - - context 'IO' do - it "should respond to lines" do - our_lines = nil - Tempfile.open("lines") do | file | - file.write(subject) - file.flush - file.rewind - our_lines = file.lines.to_a - end - our_lines.should == ["a b c d e f\n", "g h i j"] - end - it "should accept a block" do - our_lines = [] - file = Tempfile.new("lines") - file.write(subject) - file.flush - file.rewind - file.lines.each do |line| our_lines << line end - file.unlink - our_lines.should == ["a b c d e f\n", "g h i j"] - end - end - -end - From 84a981e0db5b61e786c5953ea68aaeb8da3009d3 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 12 Dec 2013 17:20:21 -0800 Subject: [PATCH 1524/3753] (refactor) Remove double negation from resolution confine check --- lib/facter/util/resolution.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 3003e66669..91e9c36d51 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -391,8 +391,8 @@ def interpreter=(interp) # # @api private def suitable? - unless defined? @suitable - @suitable = ! @confines.detect { |confine| ! confine.true? } + if @suitable.nil? + @suitable = @confines.all? { |confine| confine.true? } end return @suitable From 0850724e7a285e1b2d9e74048b9ea03677256021 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 16 Dec 2013 10:33:57 -0800 Subject: [PATCH 1525/3753] (maint) clean up Facter::Application#run Move requires to top level, simplify how facts are sorted --- lib/facter/application.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/facter/application.rb b/lib/facter/application.rb index d94be0f9a7..94268c2e6b 100644 --- a/lib/facter/application.rb +++ b/lib/facter/application.rb @@ -1,3 +1,6 @@ +require 'optparse' +require 'facter' + module Facter module Application @@ -17,9 +20,6 @@ def self.create_nothing_loader end def self.run(argv) - require 'optparse' - require 'facter' - options = parse(argv) # Accept fact names to return from the command line @@ -70,7 +70,7 @@ def self.run(argv) puts value end else - facts.sort_by{ |fact| fact.first }.each do |name,value| + facts.sort_by { |(name, value)| name }.each do |name,value| puts "#{name} => #{value}" end end From f1bf77b9af4bb61c4597f19bfe443f6844e1b1fc Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 16 Dec 2013 14:03:10 -0800 Subject: [PATCH 1526/3753] (FACT-159) Extract facter command line formatting This extracts facter's command line formatting to a separate file and calls out plaintext formatting as a specific case. This allows us a clean way of removing the plaintext behavior as we move towards structured facts. --- lib/facter/application.rb | 41 ++++++++++++----------------------- lib/facter/util/formatter.rb | 38 ++++++++++++++++++++++++++++++++ spec/unit/application_spec.rb | 32 +++++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 27 deletions(-) create mode 100644 lib/facter/util/formatter.rb diff --git a/lib/facter/application.rb b/lib/facter/application.rb index 94268c2e6b..4300ac5daf 100644 --- a/lib/facter/application.rb +++ b/lib/facter/application.rb @@ -1,5 +1,6 @@ require 'optparse' require 'facter' +require 'facter/util/formatter' module Facter module Application @@ -44,37 +45,21 @@ def self.run(argv) # Print everything if they didn't ask for specific facts. facts ||= Facter.to_hash - # Print the facts as YAML and exit - if options[:yaml] - require 'yaml' - puts YAML.dump(facts) - exit(0) - end - - # Print the facts as JSON and exit - if options[:json] - begin - require 'json' - puts JSON.dump(facts) - exit(0) - rescue LoadError - $stderr.puts "You do not have JSON support in your version of Ruby. JSON output disabled" - exit(1) - end - end + output = nil - # Print the value of a single fact, otherwise print a list sorted by fact - # name and separated by "=>" - if facts.length == 1 - if value = facts.values.first - puts value - end + if options[:yaml] + output = Facter::Util::Formatter.format_yaml(facts) + elsif options[:json] + output = Facter::Util::Formatter.format_json(facts) + elsif options[:plaintext] + output = Facter::Util::Formatter.format_plaintext(facts) else - facts.sort_by { |(name, value)| name }.each do |name,value| - puts "#{name} => #{value}" - end + output = Facter::Util::Formatter.format_plaintext(facts) end + puts output + exit(0) + rescue => e if options && options[:trace] raise e @@ -135,6 +120,8 @@ def self.parse(argv) opts.on("-j", "--json", "Emit facts in JSON format.") { |v| options[:json] = v } + opts.on("--plaintext", + "Emit facts in plaintext format.") { |v| options[:plaintext] = v } opts.on("--trace", "Enable backtraces.") { |v| options[:trace] = v } opts.on("--external-dir DIR", diff --git a/lib/facter/util/formatter.rb b/lib/facter/util/formatter.rb new file mode 100644 index 0000000000..55eb380b59 --- /dev/null +++ b/lib/facter/util/formatter.rb @@ -0,0 +1,38 @@ +require 'yaml' + +module Facter + module Util + module Formatter + + def self.format_json(hash) + if Facter.json? + JSON.pretty_generate(hash) + else + raise "Cannot format facts as JSON; 'json' library is not present" + end + end + + def self.format_yaml(hash) + YAML.dump(hash) + end + + def self.format_plaintext(hash) + output = '' + + # Print the value of a single fact, otherwise print a list sorted by fact + # name and separated by "=>" + if hash.length == 1 + if value = hash.values.first + output = value + end + else + hash.sort_by { |(name, value)| name }.each do |name,value| + output << "#{name} => #{value}\n" + end + end + + output + end + end + end +end diff --git a/spec/unit/application_spec.rb b/spec/unit/application_spec.rb index 8cb9584f73..c881090cbe 100644 --- a/spec/unit/application_spec.rb +++ b/spec/unit/application_spec.rb @@ -53,4 +53,36 @@ argv.should == ['uptime', 'virtual'] end end + + describe "formatting facts" do + before do + Facter.stubs(:to_hash) + Facter.stubs(:value) + Facter::Application.stubs(:puts) + end + + it "delegates YAML formatting" do + Facter::Util::Formatter.expects(:format_yaml) + Facter::Application.stubs(:exit).with(0) + Facter::Application.run(['--yaml']) + end + + it "delegates JSON formatting", :if => Facter.json? do + Facter::Util::Formatter.expects(:format_json) + Facter::Application.stubs(:exit).with(0) + Facter::Application.run(['--json']) + end + + it "delegates plaintext formatting" do + Facter::Util::Formatter.expects(:format_plaintext) + Facter::Application.stubs(:exit).with(0) + Facter::Application.run(['--plaintext']) + end + + it "defaults to plaintext" do + Facter::Util::Formatter.expects(:format_plaintext) + Facter::Application.stubs(:exit).with(0) + Facter::Application.run([]) + end + end end From 521ead9096db1f9a87bd82138ea5a770aae99886 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Fri, 3 Jan 2014 13:45:08 -0800 Subject: [PATCH 1527/3753] (FACT-186) Express windows specific gem dependencies Previously, if a ruby module on windows tried to use facter as a library, the calling module had to know about facter's platform specific dependencies. This comes up when trying to develop and test puppet modules on windows. This commit adds the windows specific gems (which mirror the Gemfile) to project_data.yaml. The build pipeline will automatically create an x86-mingw32 gem in addition to the generic one. --- ext/project_data.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ext/project_data.yaml b/ext/project_data.yaml index 65d59bcaa5..60fbffc2cd 100644 --- a/ext/project_data.yaml +++ b/ext/project_data.yaml @@ -13,3 +13,13 @@ gem_require_path: 'lib' gem_test_files: 'spec/**/*' gem_executables: 'facter' gem_default_executables: 'facter' +gem_platform_dependencies: + x86-mingw32: + gem_runtime_dependencies: + ffi: '~> 1.9.0' + sys-admin: '~> 1.5.6' + win32-api: '~> 1.4.8' + win32-dir: '~> 0.4.3' + windows-api: '~> 0.4.2' + windows-pr: '~> 1.2.2' + win32console: '~> 1.3.2' From 6ab0a03b53994dd7dd4a0243c31a4d81613dfccf Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Fri, 3 Jan 2014 16:29:51 -0800 Subject: [PATCH 1528/3753] (maint) Workaround travis/rubygems failures with ruby 1.8.7 --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 52dfb9e672..c2d1b6a4bf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,6 @@ +before_install: + - gem update --system 2.1.11 + - gem --version language: ruby bundler_args: --without development script: "bundle exec rake spec SPEC_OPTS='--color --format documentation'" From e5ad5cb13823e023b78cde3606d1262123279d67 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Tue, 7 Jan 2014 14:13:41 -0800 Subject: [PATCH 1529/3753] Revert "(maint) Workaround travis/rubygems failures with ruby 1.8.7" This reverts commit 6ab0a03b53994dd7dd4a0243c31a4d81613dfccf. --- .travis.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index c2d1b6a4bf..52dfb9e672 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,3 @@ -before_install: - - gem update --system 2.1.11 - - gem --version language: ruby bundler_args: --without development script: "bundle exec rake spec SPEC_OPTS='--color --format documentation'" From 751a7fb9d7d261898ce6ae74325087b4078d14d4 Mon Sep 17 00:00:00 2001 From: Clay Caviness Date: Thu, 8 Aug 2013 14:21:18 -0400 Subject: [PATCH 1530/3753] 21760/22005 update preflight to delete old lib dir In order to install in the top-level site_ruby directory, we need to clean out any installs in the old site_ruby/1.8 location. We're doing this by setting a variable in the package:apple task. If it's set, we remove all files there as well as files in the new location. While I'm here: * update erb to strip leading and trailing spaces * validate critical variables so a script is never generated with dangerous paths * only target top-level files/dirs in lib, since we're doing rm -RF anyway --- ext/osx/preflight.erb | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/ext/osx/preflight.erb b/ext/osx/preflight.erb index 3cb271a8c0..6e185f83f5 100755 --- a/ext/osx/preflight.erb +++ b/ext/osx/preflight.erb @@ -8,23 +8,31 @@ # ${3} is the destination volume so that this works correctly # when being installed to volumes other than the current OS. -<% begin %> -<% require 'rubygems' %> -<% rescue LoadError %> -<% end %> -<% require 'rake' %> +<%- begin -%> + <%- require 'rubygems' -%> +<%- rescue LoadError -%> +<%- end -%> +<%- require 'rake' -%> + +<%- ["@apple_libdir", "@apple_sbindir", "@apple_bindir", "@apple_docdir", "@package_name"].each do |i| -%> + <%- val = eval(i) -%> + <%- raise "Critical variable #{i} is unset!" if val.nil? or val.empty? -%> +<%- end -%> + +# remove ruby library files +<%- Dir.chdir("lib") -%> +<%- [@apple_old_libdir, @apple_libdir].select {|i| i}.each do |libdir| -%> + <%- FileList["*"].each do |file| -%> +/bin/rm -Rf "${3}<%= libdir %>/<%= file %>" + <%- end -%> +<%- end -%> -# remove libdir -<% Dir.chdir("lib") %> -<% FileList["**/*"].select {|i| File.file?(i)}.each do |file| %> -/bin/rm -Rf "${3}<%= @apple_libdir %>/<%=file%>" -<% end %> # remove bin files -<% Dir.chdir("../bin") %> -<% FileList["**/*"].select {|i| File.file?(i)}.each do |file| %> -/bin/rm -Rf "${3}<%= @apple_bindir %>/<%=file%>" -<% end %> -<% Dir.chdir("..") %> +<%- Dir.chdir("../bin") -%> +<%- FileList["*"].each do |file| -%> +/bin/rm -Rf "${3}<%= @apple_bindir %>/<%= file %>" +<%- end -%> +<%- Dir.chdir("..") -%> # remove old doc files -/bin/rm -Rf "${3}<%= @apple_docdir %>/<%=@package_name%>" +/bin/rm -Rf "${3}<%= @apple_docdir %>/<%= @package_name %>" From 9773b994d93ac28423cc009677142cd8fc2a4ba2 Mon Sep 17 00:00:00 2001 From: Clay Caviness Date: Tue, 13 Aug 2013 15:41:48 -0400 Subject: [PATCH 1531/3753] Use Dir.glob(), don't require rubygems or rake, use Dir.chdir as a block --- ext/osx/preflight.erb | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/ext/osx/preflight.erb b/ext/osx/preflight.erb index 6e185f83f5..ab7d9afce1 100755 --- a/ext/osx/preflight.erb +++ b/ext/osx/preflight.erb @@ -8,31 +8,26 @@ # ${3} is the destination volume so that this works correctly # when being installed to volumes other than the current OS. -<%- begin -%> - <%- require 'rubygems' -%> -<%- rescue LoadError -%> -<%- end -%> -<%- require 'rake' -%> - <%- ["@apple_libdir", "@apple_sbindir", "@apple_bindir", "@apple_docdir", "@package_name"].each do |i| -%> <%- val = eval(i) -%> <%- raise "Critical variable #{i} is unset!" if val.nil? or val.empty? -%> <%- end -%> # remove ruby library files -<%- Dir.chdir("lib") -%> -<%- [@apple_old_libdir, @apple_libdir].select {|i| i}.each do |libdir| -%> - <%- FileList["*"].each do |file| -%> +<%- Dir.chdir("lib") do -%> + <%- [@apple_old_libdir, @apple_libdir].compact.each do |libdir| -%> + <%- Dir.glob("*").each do |file| -%> /bin/rm -Rf "${3}<%= libdir %>/<%= file %>" + <%- end -%> <%- end -%> <%- end -%> # remove bin files -<%- Dir.chdir("../bin") -%> -<%- FileList["*"].each do |file| -%> +<%- Dir.chdir("bin") do -%> + <%- Dir.glob("*").each do |file| -%> /bin/rm -Rf "${3}<%= @apple_bindir %>/<%= file %>" + <%- end -%> <%- end -%> -<%- Dir.chdir("..") -%> # remove old doc files /bin/rm -Rf "${3}<%= @apple_docdir %>/<%= @package_name %>" From 190825c1fc7cf3836a60d72ccf6deff6e03ea440 Mon Sep 17 00:00:00 2001 From: Clay Caviness Date: Tue, 20 Aug 2013 09:19:42 +0100 Subject: [PATCH 1532/3753] use instance_variable_get instead of eval --- ext/osx/preflight.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) mode change 100755 => 100644 ext/osx/preflight.erb diff --git a/ext/osx/preflight.erb b/ext/osx/preflight.erb old mode 100755 new mode 100644 index ab7d9afce1..994cbddc90 --- a/ext/osx/preflight.erb +++ b/ext/osx/preflight.erb @@ -9,7 +9,7 @@ # when being installed to volumes other than the current OS. <%- ["@apple_libdir", "@apple_sbindir", "@apple_bindir", "@apple_docdir", "@package_name"].each do |i| -%> - <%- val = eval(i) -%> + <%- val = instance_variable_get(i) -%> <%- raise "Critical variable #{i} is unset!" if val.nil? or val.empty? -%> <%- end -%> From d32cbcb38b61e5c863b80c300a2df7108dad637f Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Tue, 27 Aug 2013 18:30:31 -0700 Subject: [PATCH 1533/3753] (21868) Move facter libary target to /Library/Ruby/Site We currently drop the facter library files in /usr/lib/ruby/site_ruby/1.8 OSX, which is a symlink to /Library/Ruby/Site/1.8. As of OSX 10.9 Maverick this will no longer work because it will be ruby 2.0.0. There are hard way and easy ways to solve this problem. A hard way would be to somehow introspect the target host at install time, and then dynamically copy the payload into the correct ruby version specific path. Another hard way woul be to create different OSX packages for different versions of OSX. Or, we could pick the easy way, which is to just install into /Library/Ruby/Site (the equivalent of /usr/lib/ruby/site_ruby). Since facter will work on all rubies shipped with supported versions of OSX, this will "just work". Signed-off-by: Moses Mendoza --- ext/osx/file_mapping.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/osx/file_mapping.yaml b/ext/osx/file_mapping.yaml index 8ee36ad8d1..755d96490a 100644 --- a/ext/osx/file_mapping.yaml +++ b/ext/osx/file_mapping.yaml @@ -1,6 +1,6 @@ directories: lib: - path: 'usr/lib/ruby/site_ruby/1.8' + path: 'Library/Ruby/Site' owner: 'root' group: 'wheel' perms: '0644' From 294ef91c4b0bcb6df6edee6cb4dc1583b769f653 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Fri, 3 Jan 2014 15:15:01 -0800 Subject: [PATCH 1534/3753] (FACT-186) Consolidate gem runtime dependencies Previously, the Gemfile and ext/project_data.yaml both described gem runtime dependencies, the former requiring an exact match, the latter a pessimistic match. This commit makes project_data.yaml be the single source of truth, and it specifies exact versions. Rubygems and bundler have different ideas about what constitutes a platform. Rubygems requires an arch and os, e.g. x86-mingw32, while bundler has a hard-coded list, see Bundler::Dependency::PLATFORM_MAP. As result, we need to use the rubygems platform when building the facter gem, but use the bundler platform when expressing gem dependencies. --- Gemfile | 20 ++++++++++++-------- ext/project_data.yaml | 20 +++++++++++++------- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/Gemfile b/Gemfile index 8fd33e2cfe..446e8ca862 100644 --- a/Gemfile +++ b/Gemfile @@ -20,14 +20,18 @@ group :development, :test do gem 'puppetlabs_spec_helper' end -platform :mswin, :mingw do - gem "ffi", "1.9.0", :require => false - gem "sys-admin", "1.5.6", :require => false - gem "win32-api", "1.4.8", :require => false - gem "win32-dir", "0.4.3", :require => false - gem "windows-api", "0.4.2", :require => false - gem "windows-pr", "1.2.2", :require => false - gem "win32console", "1.3.2", :require => false +require 'yaml' +data = YAML.load_file(File.join(File.dirname(__FILE__), 'ext', 'project_data.yaml')) +bundle_platforms = data['bundle_platforms'] +data['gem_platform_dependencies'].each_pair do |gem_platform, info| + if bundle_deps = info['gem_runtime_dependencies'] + bundle_platform = bundle_platforms[gem_platform] or raise "Missing bundle_platform" + platform(bundle_platform.intern) do + bundle_deps.each_pair do |name, version| + gem(name, version, :require => false) + end + end + end end gem 'facter', ">= 1.0.0", :path => File.expand_path("..", __FILE__) diff --git a/ext/project_data.yaml b/ext/project_data.yaml index 60fbffc2cd..1a19a12047 100644 --- a/ext/project_data.yaml +++ b/ext/project_data.yaml @@ -14,12 +14,18 @@ gem_test_files: 'spec/**/*' gem_executables: 'facter' gem_default_executables: 'facter' gem_platform_dependencies: + universal-darwin: + gem_runtime_dependencies: + CFPropertyList: '2.2.6' x86-mingw32: gem_runtime_dependencies: - ffi: '~> 1.9.0' - sys-admin: '~> 1.5.6' - win32-api: '~> 1.4.8' - win32-dir: '~> 0.4.3' - windows-api: '~> 0.4.2' - windows-pr: '~> 1.2.2' - win32console: '~> 1.3.2' + ffi: '1.9.0' + sys-admin: '1.5.6' + win32-api: '1.4.8' + win32-dir: '0.4.3' + windows-api: '0.4.2' + windows-pr: '1.2.2' + win32console: '1.3.2' +bundle_platforms: + universal-darwin: ruby + x86-mingw32: mingw From ab4d7c4775f6948039368114f88a33c080289e68 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 9 Jan 2014 11:07:44 -0800 Subject: [PATCH 1535/3753] (FACT-134) Add module for normalizing fact values This commits adds a module for normalizing fact values to UTF-8, and validating that the string is properly encoded, e.g. a string may claim to be UTF-8 encoded, but actually contain binary data. --- lib/facter/util/normalization.rb | 98 ++++++++++++++++++++++++++++ spec/unit/util/normalization_spec.rb | 97 +++++++++++++++++++++++++++ 2 files changed, 195 insertions(+) create mode 100644 lib/facter/util/normalization.rb create mode 100644 spec/unit/util/normalization_spec.rb diff --git a/lib/facter/util/normalization.rb b/lib/facter/util/normalization.rb new file mode 100644 index 0000000000..693deb835b --- /dev/null +++ b/lib/facter/util/normalization.rb @@ -0,0 +1,98 @@ +module Facter + module Util + module Normalization + class NormalizationError < StandardError; end + + VALID_TYPES = [Integer, Float, TrueClass, FalseClass, NilClass, String, Array, Hash] + + module_function + + # Recursively normalize the given data structure + # + # @api public + # @raise [NormalizationError] If the data structure contained an invalid element. + # @return [void] + def normalize(value) + case value + when Integer, Float, TrueClass, FalseClass, NilClass + true + when String + normalize_string(value) + when Array + normalize_array(value) + when Hash + normalize_hash(value) + else + raise NormalizationError, "Expected #{value} to be one of #{VALID_TYPES.inspect}, but was #{value.class}" + end + end + + # @!method normalize_string(value) + # + # Attempt to normalize and validate the given string. + # + # On Ruby 1.8 the string is checked by stripping out all non UTF-8 + # characters and comparing the converted string to the original. If they + # do not match then the string is considered invalid. + # + # On Ruby 1.9+, the string is validate by checking that the string encoding + # is UTF-8 and that the string content matches the encoding. If the string + # is not an expected encoding then it is converted to UTF-8. + # + # @api public + # @raise [NormalizationError] If the string used an unsupported encoding or did not match its encoding + # @param value [String] + # @return [void] + + if RUBY_VERSION =~ /^1\.8/ + require 'iconv' + + def normalize_string(value) + converted = Iconv.conv('UTF-8//IGNORE', 'UTF-8', value) + if converted != value + raise NormalizationError, "String #{value.inspect} is not valid UTF-8" + end + end + else + def normalize_string(value) + unless value.encoding == Encoding::UTF_8 + begin + value.encode!(Encoding::UTF_8) + rescue EncodingError + raise NormalizationError, "String encoding #{value.encoding} is not UTF-8 and could not be converted to UTF-8" + end + end + + unless value.valid_encoding? + raise NormalizationError, "String #{value.inspect} doesn't match the reported encoding #{value.encoding}" + end + end + end + + # Validate all elements of the array. + # + # @api public + # @raise [NormalizationError] If one of the elements failed validation + # @param value [Array] + # @return [void] + def normalize_array(value) + value.each do |elem| + normalize(elem) + end + end + + # Validate all keys and values of the hash. + # + # @api public + # @raise [NormalizationError] If one of the keys or values failed normalization + # @param value [Hash] + # @return [void] + def normalize_hash(value) + value.each_pair do |k, v| + normalize(k) + normalize(v) + end + end + end + end +end diff --git a/spec/unit/util/normalization_spec.rb b/spec/unit/util/normalization_spec.rb new file mode 100644 index 0000000000..9e7cb0ecd9 --- /dev/null +++ b/spec/unit/util/normalization_spec.rb @@ -0,0 +1,97 @@ +# encoding: utf-8 + +require 'spec_helper' +require 'facter/util/normalization' + +describe Facter::Util::Normalization do + + subject { described_class } + + describe "validating strings" do + describe "and string encoding is supported", :if => String.instance_methods.include?(:encoding) do + it "accepts strings that are ASCII and match their encoding and converts them to UTF-8" do + str = "ASCII".encode(Encoding::ASCII) + subject.normalize(str) + expect(str.encoding).to eq(Encoding::UTF_8) + end + + it "accepts strings that are UTF-8 and match their encoding" do + str = "let's make a ☃!".encode(Encoding::UTF_8) + subject.normalize(str) + end + + it "converts valid non UTF-8 strings to UTF-8" do + str = "let's make a ☃!".encode(Encoding::UTF_16LE) + subject.normalize(str) + expect(str.encoding).to eq(Encoding::UTF_8) + end + + it "rejects strings that are not UTF-8 and do not match their claimed encoding" do + invalid_shift_jis = "\xFF\x5C!".force_encoding(Encoding::SHIFT_JIS) + expect { + subject.normalize(invalid_shift_jis) + }.to raise_error(Facter::Util::Normalization::NormalizationError, /String encoding Shift_JIS is not UTF-8 and could not be converted to UTF-8/) + end + + it "rejects strings that claim to be UTF-8 encoded but aren't" do + str = "\255ay!".force_encoding(Encoding::UTF_8) + expect { + subject.normalize(str) + }.to raise_error(Facter::Util::Normalization::NormalizationError, /String.*doesn't match the reported encoding UTF-8/) + end + end + + describe "and string encoding is not supported", :unless => String.instance_methods.include?(:encoding) do + it "accepts strings that are UTF-8 and match their encoding" do + str = "let's make a ☃!" + subject.normalize(str) + end + + it "rejects strings that are not UTF-8" do + str = "let's make a \255\255\255!" + expect { + subject.normalize(str) + }.to raise_error(Facter::Util::Normalization::NormalizationError, /String .* is not valid UTF-8/) + end + end + end + + describe "validating arrays" do + it "normalizes each element in the array" do + arr = ['first', 'second', ['third', 'fourth']] + + subject.expects(:normalize).with('first') + subject.expects(:normalize).with('second') + subject.expects(:normalize).with(['third', 'fourth']) + + subject.normalize_array(arr) + end + end + + describe "validating hashes" do + it "normalizes each element in the array" do + hsh = {'first' => 'second', 'third' => ['fourth', 'fifth']} + + subject.expects(:normalize).with('first') + subject.expects(:normalize).with('second') + subject.expects(:normalize).with('third') + subject.expects(:normalize).with(['fourth', 'fifth']) + + subject.normalize_hash(hsh) + end + end + + [1, 1.0, true, false, nil].each do |val| + it "accepts #{val.inspect}:#{val.class}" do + subject.normalize(val) + end + end + + [:sym, Object.new, Set.new].each do |val| + it "rejects #{val.inspect}:#{val.class}" do + expect { + subject.normalize(val) + }.to raise_error(Facter::Util::Normalization::NormalizationError, /Expected .*but was #{val.class}/ ) + end + end +end From 9d2ef21c908fab3fea421322d7ec308fe54e648f Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 12 Dec 2013 14:08:00 -0800 Subject: [PATCH 1536/3753] (FACT-134) Normalize and validate fact values This commit normalizes fact values to UTF-8 and verifies that the resulting encoding is valid. On Windows, the default string encoding depends on the active code page. When adding an environment based fact, e.g. FACTER_one=bar, the value is frozen and cannot be encoded in place. So we dup fact values that come from the environment. --- lib/facter/util/loader.rb | 2 +- lib/facter/util/resolution.rb | 7 ++++++- spec/unit/util/resolution_spec.rb | 23 +++++++++++------------ 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/lib/facter/util/loader.rb b/lib/facter/util/loader.rb index a8fb88d19f..f08a971a63 100644 --- a/lib/facter/util/loader.rb +++ b/lib/facter/util/loader.rb @@ -141,7 +141,7 @@ def load_env(fact = nil) Facter.add($1) do has_weight 1_000_000 - setcode { value } + setcode { value.dup } end # Short-cut, if we are only looking for one value. diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 3003e66669..dc8708c509 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -1,5 +1,6 @@ require 'facter/util/confine' require 'facter/util/config' +require 'facter/util/normalization' require 'timeout' @@ -440,7 +441,11 @@ def value ms = (finishtime - starttime) * 1000 Facter.show_time "#{self.name}: #{"%.2f" % ms}ms" - return nil if result == "" + Facter::Util::Normalization.normalize(result) + return result + rescue Facter::Util::Normalization::NormalizationError => e + Facter.warn "Fact resolution #{self.name} resolved to an invalid value: #{e.message}" + nil end end diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index e0300bf915..51767df298 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -210,10 +210,11 @@ class FlushFakeError < StandardError; end @resolve.value.should == "yup" end - it "should return nil if the value is an empty string" do + it "it validates the resolved value" do @resolve.setcode "/bin/foo" Facter::Util::Resolution.expects(:exec).once.returns "" - @resolve.value.should be_nil + Facter::Util::Normalization.expects(:normalize).with "" + @resolve.value.should eq "" end end @@ -229,10 +230,11 @@ class FlushFakeError < StandardError; end @resolve.value.should == "yup" end - it "should return nil if the value is an empty string" do + it "it validates the resolved value" do @resolve.setcode "/bin/foo" Facter::Util::Resolution.expects(:exec).once.returns "" - @resolve.value.should be_nil + Facter::Util::Normalization.expects(:normalize).with "" + @resolve.value.should eq "" end end end @@ -249,14 +251,11 @@ class FlushFakeError < StandardError; end @resolve.value.should == "yayness" end - it "should return nil if the value is an empty string" do - @resolve.setcode { "" } - @resolve.value.should be_nil - end - - it "should return nil if the value is an empty block" do - @resolve.setcode { "" } - @resolve.value.should be_nil + it "it validates the resolved value" do + @resolve.setcode "/bin/foo" + Facter::Util::Resolution.expects(:exec).once.returns "" + Facter::Util::Normalization.expects(:normalize).with "" + @resolve.value.should eq "" end it "should use its limit method to determine the timeout, to avoid conflict when a 'timeout' method exists for some other reason" do From 95928792b2023577729c34d1bc9c6545ddec8825 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Thu, 9 Jan 2014 16:37:39 -0800 Subject: [PATCH 1537/3753] (FACT-134) Normalize copies of fact string values Previously, facter would fail to normalize a frozen, non-UTF8 encoded string, such as the value returned by the `rubyversion` fact. This commit changes the normalization module to make a copy of the resolved value, and perform string encoding on that. It also undoes the change to Factet::Util::Loader that dup'ed environment variables of the form `FACTER_*`. Paired-with: Andy Parker --- lib/facter/util/loader.rb | 2 +- lib/facter/util/normalization.rb | 22 +++++------- lib/facter/util/resolution.rb | 2 -- spec/unit/util/normalization_spec.rb | 52 ++++++++++++++++------------ spec/unit/util/resolution_spec.rb | 29 +++++++++------- 5 files changed, 56 insertions(+), 51 deletions(-) diff --git a/lib/facter/util/loader.rb b/lib/facter/util/loader.rb index f08a971a63..a8fb88d19f 100644 --- a/lib/facter/util/loader.rb +++ b/lib/facter/util/loader.rb @@ -141,7 +141,7 @@ def load_env(fact = nil) Facter.add($1) do has_weight 1_000_000 - setcode { value.dup } + setcode { value } end # Short-cut, if we are only looking for one value. diff --git a/lib/facter/util/normalization.rb b/lib/facter/util/normalization.rb index 693deb835b..f3149f6cb4 100644 --- a/lib/facter/util/normalization.rb +++ b/lib/facter/util/normalization.rb @@ -15,7 +15,7 @@ class NormalizationError < StandardError; end def normalize(value) case value when Integer, Float, TrueClass, FalseClass, NilClass - true + value when String normalize_string(value) when Array @@ -52,20 +52,19 @@ def normalize_string(value) if converted != value raise NormalizationError, "String #{value.inspect} is not valid UTF-8" end + value end else def normalize_string(value) - unless value.encoding == Encoding::UTF_8 - begin - value.encode!(Encoding::UTF_8) - rescue EncodingError - raise NormalizationError, "String encoding #{value.encoding} is not UTF-8 and could not be converted to UTF-8" - end - end + value = value.encode(Encoding::UTF_8) unless value.valid_encoding? raise NormalizationError, "String #{value.inspect} doesn't match the reported encoding #{value.encoding}" end + + value + rescue EncodingError + raise NormalizationError, "String encoding #{value.encoding} is not UTF-8 and could not be converted to UTF-8" end end @@ -76,7 +75,7 @@ def normalize_string(value) # @param value [Array] # @return [void] def normalize_array(value) - value.each do |elem| + value.collect do |elem| normalize(elem) end end @@ -88,10 +87,7 @@ def normalize_array(value) # @param value [Hash] # @return [void] def normalize_hash(value) - value.each_pair do |k, v| - normalize(k) - normalize(v) - end + Hash[value.collect { |k, v| [ normalize(k), normalize(v) ] } ] end end end diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 140e60baf0..4160c9b2d5 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -442,8 +442,6 @@ def value Facter.show_time "#{self.name}: #{"%.2f" % ms}ms" Facter::Util::Normalization.normalize(result) - - return result rescue Facter::Util::Normalization::NormalizationError => e Facter.warn "Fact resolution #{self.name} resolved to an invalid value: #{e.message}" nil diff --git a/spec/unit/util/normalization_spec.rb b/spec/unit/util/normalization_spec.rb index 9e7cb0ecd9..34f9d1079e 100644 --- a/spec/unit/util/normalization_spec.rb +++ b/spec/unit/util/normalization_spec.rb @@ -7,23 +7,38 @@ subject { described_class } + def utf16(str) + str.encode(Encoding::UTF_16LE) + end + + def utf8(str) + str.encode(Encoding::UTF_8) + end + describe "validating strings" do describe "and string encoding is supported", :if => String.instance_methods.include?(:encoding) do it "accepts strings that are ASCII and match their encoding and converts them to UTF-8" do str = "ASCII".encode(Encoding::ASCII) - subject.normalize(str) - expect(str.encoding).to eq(Encoding::UTF_8) + normalized_str = subject.normalize(str) + expect(normalized_str.encoding).to eq(Encoding::UTF_8) end it "accepts strings that are UTF-8 and match their encoding" do str = "let's make a ☃!".encode(Encoding::UTF_8) - subject.normalize(str) + expect(subject.normalize(str)).to eq(str) end it "converts valid non UTF-8 strings to UTF-8" do str = "let's make a ☃!".encode(Encoding::UTF_16LE) - subject.normalize(str) - expect(str.encoding).to eq(Encoding::UTF_8) + enc = subject.normalize(str).encoding + expect(enc).to eq(Encoding::UTF_8) + end + + it "normalizes a frozen string returning a non-frozen string" do + str = "factvalue".encode(Encoding::UTF_16LE).freeze + normalized_str = subject.normalize(str) + expect(normalized_str.encoding).to eq(Encoding::UTF_8) + expect(normalized_str).to_not be_frozen end it "rejects strings that are not UTF-8 and do not match their claimed encoding" do @@ -44,7 +59,7 @@ describe "and string encoding is not supported", :unless => String.instance_methods.include?(:encoding) do it "accepts strings that are UTF-8 and match their encoding" do str = "let's make a ☃!" - subject.normalize(str) + expect(subject.normalize(str)).to eq(str) end it "rejects strings that are not UTF-8" do @@ -56,34 +71,27 @@ end end - describe "validating arrays" do + describe "normalizing arrays" do it "normalizes each element in the array" do - arr = ['first', 'second', ['third', 'fourth']] + arr = [utf16('first'), utf16('second'), [utf16('third'), utf16('fourth')]] + expected_arr = [utf8('first'), utf8('second'), [utf8('third'), utf8('fourth')]] - subject.expects(:normalize).with('first') - subject.expects(:normalize).with('second') - subject.expects(:normalize).with(['third', 'fourth']) - - subject.normalize_array(arr) + expect(subject.normalize_array(arr)).to eq(expected_arr) end end - describe "validating hashes" do + describe "normalizing hashes" do it "normalizes each element in the array" do - hsh = {'first' => 'second', 'third' => ['fourth', 'fifth']} - - subject.expects(:normalize).with('first') - subject.expects(:normalize).with('second') - subject.expects(:normalize).with('third') - subject.expects(:normalize).with(['fourth', 'fifth']) + hsh = {utf16('first') => utf16('second'), utf16('third') => [utf16('fourth'), utf16('fifth')]} + expected_hsh = {utf8('first') => utf8('second'), utf8('third') => [utf8('fourth'), utf8('fifth')]} - subject.normalize_hash(hsh) + expect(subject.normalize_hash(hsh)).to eq(expected_hsh) end end [1, 1.0, true, false, nil].each do |val| it "accepts #{val.inspect}:#{val.class}" do - subject.normalize(val) + expect(subject.normalize(val)).to eq(val) end end diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index 51767df298..755b252427 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -181,6 +181,8 @@ class FlushFakeError < StandardError; end end describe "when returning the value" do + let(:utf16_string) { "".encode(Encoding::UTF_16LE).freeze } + before do @resolve = Facter::Util::Resolution.new("yay") end @@ -210,11 +212,12 @@ class FlushFakeError < StandardError; end @resolve.value.should == "yup" end - it "it validates the resolved value" do + it "it normalizes the resolved value" do @resolve.setcode "/bin/foo" - Facter::Util::Resolution.expects(:exec).once.returns "" - Facter::Util::Normalization.expects(:normalize).with "" - @resolve.value.should eq "" + + Facter::Util::Resolution.expects(:exec).once.returns(utf16_string) + + expect(@resolve.value).to eq(utf16_string.encode(Encoding::UTF_8)) end end @@ -230,11 +233,12 @@ class FlushFakeError < StandardError; end @resolve.value.should == "yup" end - it "it validates the resolved value" do + it "it normalizes the resolved value" do @resolve.setcode "/bin/foo" - Facter::Util::Resolution.expects(:exec).once.returns "" - Facter::Util::Normalization.expects(:normalize).with "" - @resolve.value.should eq "" + + Facter::Util::Resolution.expects(:exec).once.returns(utf16_string) + + expect(@resolve.value).to eq(utf16_string.encode(Encoding::UTF_8)) end end end @@ -251,11 +255,10 @@ class FlushFakeError < StandardError; end @resolve.value.should == "yayness" end - it "it validates the resolved value" do - @resolve.setcode "/bin/foo" - Facter::Util::Resolution.expects(:exec).once.returns "" - Facter::Util::Normalization.expects(:normalize).with "" - @resolve.value.should eq "" + it "it normalizes the resolved value" do + @resolve.setcode { utf16_string } + + expect(@resolve.value).to eq(utf16_string.encode(Encoding::UTF_8)) end it "should use its limit method to determine the timeout, to avoid conflict when a 'timeout' method exists for some other reason" do From 1de2d8d7ad823e744d5861440c87cd675170549b Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Thu, 9 Jan 2014 17:20:20 -0800 Subject: [PATCH 1538/3753] (FACT-134) Redo tests so the work on ruby 1.8.x No String#encode or Encoding in ruby 1.8.x, update tests accordingly. --- spec/unit/util/normalization_spec.rb | 12 ++++++++++-- spec/unit/util/resolution_spec.rb | 24 ++++++++++++++++++++---- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/spec/unit/util/normalization_spec.rb b/spec/unit/util/normalization_spec.rb index 34f9d1079e..51c3ef1ec9 100644 --- a/spec/unit/util/normalization_spec.rb +++ b/spec/unit/util/normalization_spec.rb @@ -8,11 +8,19 @@ subject { described_class } def utf16(str) - str.encode(Encoding::UTF_16LE) + if String.method_defined?(:encode) && defined?(::Encoding) + str.encode(Encoding::UTF_16LE) + else + str + end end def utf8(str) - str.encode(Encoding::UTF_8) + if String.method_defined?(:encode) && defined?(::Encoding) + str.encode(Encoding::UTF_8) + else + str + end end describe "validating strings" do diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index 755b252427..78bc151123 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -181,7 +181,23 @@ class FlushFakeError < StandardError; end end describe "when returning the value" do - let(:utf16_string) { "".encode(Encoding::UTF_16LE).freeze } + let(:fact_value) { "" } + + let(:utf16_string) do + if String.method_defined?(:encode) && defined?(::Encoding) + fact_value.encode(Encoding::UTF_16LE).freeze + else + [0x00, 0x00].pack('C*').freeze + end + end + + let(:expected_value) do + if String.method_defined?(:encode) && defined?(::Encoding) + fact_value.encode(Encoding::UTF_8).freeze + else + [0x00, 0x00].pack('C*').freeze + end + end before do @resolve = Facter::Util::Resolution.new("yay") @@ -217,7 +233,7 @@ class FlushFakeError < StandardError; end Facter::Util::Resolution.expects(:exec).once.returns(utf16_string) - expect(@resolve.value).to eq(utf16_string.encode(Encoding::UTF_8)) + expect(@resolve.value).to eq(expected_value) end end @@ -238,7 +254,7 @@ class FlushFakeError < StandardError; end Facter::Util::Resolution.expects(:exec).once.returns(utf16_string) - expect(@resolve.value).to eq(utf16_string.encode(Encoding::UTF_8)) + expect(@resolve.value).to eq(expected_value) end end end @@ -258,7 +274,7 @@ class FlushFakeError < StandardError; end it "it normalizes the resolved value" do @resolve.setcode { utf16_string } - expect(@resolve.value).to eq(utf16_string.encode(Encoding::UTF_8)) + expect(@resolve.value).to eq(expected_value) end it "should use its limit method to determine the timeout, to avoid conflict when a 'timeout' method exists for some other reason" do From d9449edb4a87dd3eb538126de792d0895aae9162 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 30 Dec 2013 17:35:39 -0800 Subject: [PATCH 1539/3753] Extract Facter execution methods to mixin The command execution methods in Facter::Util::Resolution were cluttering up the class and breaking the single responsbility principle. Leaving them around increases the complexity of refactoring the Resolution class. This commit extracts methods related to command execution to a mixin and extends the Resolution class with the mixin to maintain compatibility. In the long term we should deprecate the compatibility methods. --- lib/facter/util/execution.rb | 214 ++++++++++++++++++++ lib/facter/util/resolution.rb | 211 +------------------ spec/unit/util/execution_spec.rb | 209 +++++++++++++++++++ spec/unit/util/resolution_spec.rb | 324 ------------------------------ 4 files changed, 432 insertions(+), 526 deletions(-) create mode 100644 lib/facter/util/execution.rb create mode 100644 spec/unit/util/execution_spec.rb diff --git a/lib/facter/util/execution.rb b/lib/facter/util/execution.rb new file mode 100644 index 0000000000..875470ab12 --- /dev/null +++ b/lib/facter/util/execution.rb @@ -0,0 +1,214 @@ +require 'facter/util/config' + +module Facter + module Util + module Execution + + module_function + + # Returns the locations to be searched when looking for a binary. This + # is currently determined by the +PATH+ environment variable plus + # `/sbin` and `/usr/sbin` when run on unix + # + # @return [Array] the paths to be searched for binaries + # @api private + def search_paths + if Facter::Util::Config.is_windows? + ENV['PATH'].split(File::PATH_SEPARATOR) + else + # Make sure facter is usable even for non-root users. Most commands + # in /sbin (like ifconfig) can be run as non priviledged users as + # long as they do not modify anything - which we do not do with facter + ENV['PATH'].split(File::PATH_SEPARATOR) + [ '/sbin', '/usr/sbin' ] + end + end + + # Determines the full path to a binary. If the supplied filename does not + # already describe an absolute path then different locations (determined + # by {search_paths}) will be searched for a match. + # + # Returns nil if no matching executable can be found otherwise returns + # the expanded pathname. + # + # @param bin [String] the executable to locate + # @return [String,nil] the full path to the executable or nil if not + # found + # + # @api public + def which(bin) + if absolute_path?(bin) + return bin if File.executable?(bin) + if Facter::Util::Config.is_windows? and File.extname(bin).empty? + exts = ENV['PATHEXT'] + exts = exts ? exts.split(File::PATH_SEPARATOR) : %w[.COM .EXE .BAT .CMD] + exts.each do |ext| + destext = bin + ext + if File.executable?(destext) + Facter.warnonce("Using Facter::Util::Execution.which with an absolute path like #{bin} but no fileextension is deprecated. Please add the correct extension (#{ext})") + return destext + end + end + end + else + search_paths.each do |dir| + dest = File.join(dir, bin) + if Facter::Util::Config.is_windows? + dest.gsub!(File::SEPARATOR, File::ALT_SEPARATOR) + if File.extname(dest).empty? + exts = ENV['PATHEXT'] + exts = exts ? exts.split(File::PATH_SEPARATOR) : %w[.COM .EXE .BAT .CMD] + exts.each do |ext| + destext = dest + ext + return destext if File.executable?(destext) + end + end + end + return dest if File.executable?(dest) + end + end + nil + end + + # Determine in a platform-specific way whether a path is absolute. This + # defaults to the local platform if none is specified. + # + # @param path [String] the path to check + # @param platform [:posix,:windows,nil] the platform logic to use + def absolute_path?(path, platform=nil) + # Escape once for the string literal, and once for the regex. + slash = '[\\\\/]' + name = '[^\\\\/]+' + regexes = { + :windows => %r!^(([A-Z]:#{slash})|(#{slash}#{slash}#{name}#{slash}#{name})|(#{slash}#{slash}\?#{slash}#{name}))!i, + :posix => %r!^/!, + } + platform ||= Facter::Util::Config.is_windows? ? :windows : :posix + + !! (path =~ regexes[platform]) + end + + # Given a command line, this returns the command line with the + # executable written as an absolute path. If the executable contains + # spaces, it has be but in double quotes to be properly recognized. + # + # @param command [String] the command line + # + # @return [String, nil] the command line with the executable's path + # expanded, or nil if the executable cannot be found. + def expand_command(command) + if match = /^"(.+?)"(?:\s+(.*))?/.match(command) + exe, arguments = match.captures + exe = which(exe) and [ "\"#{exe}\"", arguments ].compact.join(" ") + elsif match = /^'(.+?)'(?:\s+(.*))?/.match(command) and not Facter::Util::Config.is_windows? + exe, arguments = match.captures + exe = which(exe) and [ "'#{exe}'", arguments ].compact.join(" ") + else + exe, arguments = command.split(/ /,2) + if exe = which(exe) + # the binary was not quoted which means it contains no spaces. But the + # full path to the binary may do so. + exe = "\"#{exe}\"" if exe =~ /\s/ and Facter::Util::Config.is_windows? + exe = "'#{exe}'" if exe =~ /\s/ and not Facter::Util::Config.is_windows? + [ exe, arguments ].compact.join(" ") + end + end + end + + # Overrides environment variables within a block of code. The + # specified values will be set for the duration of the block, after + # which the original values (if any) will be restored. + # + # @overload with_env(values, { || ... }) + # + # @param values [HashString>] A hash of the environment + # variables to override + # + # @return [void] + # + # @api public + def with_env(values) + old = {} + values.each do |var, value| + # save the old value if it exists + if old_val = ENV[var] + old[var] = old_val + end + # set the new (temporary) value for the environment variable + ENV[var] = value + end + # execute the caller's block, capture the return value + rv = yield + # use an ensure block to make absolutely sure we restore the variables + ensure + # restore the old values + values.each do |var, value| + if old.include?(var) + ENV[var] = old[var] + else + # if there was no old value, delete the key from the current environment variables hash + ENV.delete(var) + end + end + # return the captured return value + rv + end + + # Executes a program and return the output of that program. + # + # Returns nil if the program can't be found, or if there is a problem + # executing the code. + # + # @param code [String] the program to run + # @return [String, nil] the output of the program or nil + # + # @api public + # + # @note Since Facter 1.5.8 this strips trailing newlines from the + # returned value. If a fact will be used by versions of Facter older + # than 1.5.8 then you should call chomp the returned string. + # + # @overload exec(code) + # @overload exec(code, interpreter = nil) + # @param [String] interpreter unused, only exists for backwards + # compatibility + # @deprecated + def exec(code, interpreter = nil) + Facter.warnonce "The interpreter parameter to 'exec' is deprecated and will be removed in a future version." if interpreter + + ## Set LANG to force i18n to C for the duration of this exec; this ensures that any code that parses the + ## output of the command can expect it to be in a consistent / predictable format / locale + with_env "LANG" => "C" do + + if expanded_code = expand_command(code) + # if we can find the binary, we'll run the command with the expanded path to the binary + code = expanded_code + else + # if we cannot find the binary return nil on posix. On windows we'll still try to run the + # command in case it is a shell-builtin. In case it is not, windows will raise Errno::ENOENT + return nil unless Facter::Util::Config.is_windows? + return nil if absolute_path?(code) + end + + out = nil + + begin + out = %x{#{code}}.chomp + Facter.warnonce "Using Facter::Util::Execution.exec with a shell built-in is deprecated. Most built-ins can be replaced with native ruby commands. If you really have to run a built-in, pass \"cmd /c your_builtin\" as a command (command responsible for this message was \"#{code}\")" unless expanded_code + rescue Errno::ENOENT => detail + # command not found on Windows + return nil + rescue => detail + Facter.warn(detail) + return nil + end + + if out == "" + return nil + else + return out + end + end + end + end + end +end diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 4160c9b2d5..742bb98020 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -1,6 +1,7 @@ require 'facter/util/confine' require 'facter/util/config' require 'facter/util/normalization' +require 'facter/util/execution' require 'timeout' @@ -29,208 +30,14 @@ class Facter::Util::Resolution INTERPRETER = Facter::Util::Config.is_windows? ? "cmd.exe" : "/bin/sh" - # Returns the locations to be searched when looking for a binary. This - # is currently determined by the +PATH+ environment variable plus - # `/sbin` and `/usr/sbin` when run on unix - # - # @return [Array] the paths to be searched for binaries - # @api private - def self.search_paths - if Facter::Util::Config.is_windows? - ENV['PATH'].split(File::PATH_SEPARATOR) - else - # Make sure facter is usable even for non-root users. Most commands - # in /sbin (like ifconfig) can be run as non priviledged users as - # long as they do not modify anything - which we do not do with facter - ENV['PATH'].split(File::PATH_SEPARATOR) + [ '/sbin', '/usr/sbin' ] - end - end - - # Determines the full path to a binary. If the supplied filename does not - # already describe an absolute path then different locations (determined - # by {search_paths}) will be searched for a match. - # - # Returns nil if no matching executable can be found otherwise returns - # the expanded pathname. - # - # @param bin [String] the executable to locate - # @return [String,nil] the full path to the executable or nil if not - # found - # - # @api public - def self.which(bin) - if absolute_path?(bin) - return bin if File.executable?(bin) - if Facter::Util::Config.is_windows? and File.extname(bin).empty? - exts = ENV['PATHEXT'] - exts = exts ? exts.split(File::PATH_SEPARATOR) : %w[.COM .EXE .BAT .CMD] - exts.each do |ext| - destext = bin + ext - if File.executable?(destext) - Facter.warnonce("Using Facter::Util::Resolution.which with an absolute path like #{bin} but no fileextension is deprecated. Please add the correct extension (#{ext})") - return destext - end - end - end - else - search_paths.each do |dir| - dest = File.join(dir, bin) - if Facter::Util::Config.is_windows? - dest.gsub!(File::SEPARATOR, File::ALT_SEPARATOR) - if File.extname(dest).empty? - exts = ENV['PATHEXT'] - exts = exts ? exts.split(File::PATH_SEPARATOR) : %w[.COM .EXE .BAT .CMD] - exts.each do |ext| - destext = dest + ext - return destext if File.executable?(destext) - end - end - end - return dest if File.executable?(dest) - end - end - nil - end - - # Determine in a platform-specific way whether a path is absolute. This - # defaults to the local platform if none is specified. - # - # @param path [String] the path to check - # @param platform [:posix,:windows,nil] the platform logic to use - def self.absolute_path?(path, platform=nil) - # Escape once for the string literal, and once for the regex. - slash = '[\\\\/]' - name = '[^\\\\/]+' - regexes = { - :windows => %r!^(([A-Z]:#{slash})|(#{slash}#{slash}#{name}#{slash}#{name})|(#{slash}#{slash}\?#{slash}#{name}))!i, - :posix => %r!^/!, - } - platform ||= Facter::Util::Config.is_windows? ? :windows : :posix - - !! (path =~ regexes[platform]) - end - - # Given a command line, this returns the command line with the - # executable written as an absolute path. If the executable contains - # spaces, it has be but in double quotes to be properly recognized. - # - # @param command [String] the command line - # - # @return [String, nil] the command line with the executable's path - # expanded, or nil if the executable cannot be found. - def self.expand_command(command) - if match = /^"(.+?)"(?:\s+(.*))?/.match(command) - exe, arguments = match.captures - exe = which(exe) and [ "\"#{exe}\"", arguments ].compact.join(" ") - elsif match = /^'(.+?)'(?:\s+(.*))?/.match(command) and not Facter::Util::Config.is_windows? - exe, arguments = match.captures - exe = which(exe) and [ "'#{exe}'", arguments ].compact.join(" ") - else - exe, arguments = command.split(/ /,2) - if exe = which(exe) - # the binary was not quoted which means it contains no spaces. But the - # full path to the binary may do so. - exe = "\"#{exe}\"" if exe =~ /\s/ and Facter::Util::Config.is_windows? - exe = "'#{exe}'" if exe =~ /\s/ and not Facter::Util::Config.is_windows? - [ exe, arguments ].compact.join(" ") - end - end - end - - # Overrides environment variables within a block of code. The - # specified values will be set for the duration of the block, after - # which the original values (if any) will be restored. - # - # @overload with_env(values, { || ... }) - # - # @param values [HashString>] A hash of the environment - # variables to override - # - # @return [void] - # - # @api public - def self.with_env(values) - old = {} - values.each do |var, value| - # save the old value if it exists - if old_val = ENV[var] - old[var] = old_val - end - # set the new (temporary) value for the environment variable - ENV[var] = value - end - # execute the caller's block, capture the return value - rv = yield - # use an ensure block to make absolutely sure we restore the variables - ensure - # restore the old values - values.each do |var, value| - if old.include?(var) - ENV[var] = old[var] - else - # if there was no old value, delete the key from the current environment variables hash - ENV.delete(var) - end - end - # return the captured return value - rv - end - - # Executes a program and return the output of that program. - # - # Returns nil if the program can't be found, or if there is a problem - # executing the code. - # - # @param code [String] the program to run - # @return [String, nil] the output of the program or nil - # - # @api public - # - # @note Since Facter 1.5.8 this strips trailing newlines from the - # returned value. If a fact will be used by versions of Facter older - # than 1.5.8 then you should call chomp the returned string. - # - # @overload exec(code) - # @overload exec(code, interpreter = nil) - # @param [String] interpreter unused, only exists for backwards - # compatibility - # @deprecated - def self.exec(code, interpreter = nil) - Facter.warnonce "The interpreter parameter to 'exec' is deprecated and will be removed in a future version." if interpreter - - ## Set LANG to force i18n to C for the duration of this exec; this ensures that any code that parses the - ## output of the command can expect it to be in a consistent / predictable format / locale - with_env "LANG" => "C" do - - if expanded_code = expand_command(code) - # if we can find the binary, we'll run the command with the expanded path to the binary - code = expanded_code - else - # if we cannot find the binary return nil on posix. On windows we'll still try to run the - # command in case it is a shell-builtin. In case it is not, windows will raise Errno::ENOENT - return nil unless Facter::Util::Config.is_windows? - return nil if absolute_path?(code) - end - - out = nil - - begin - out = %x{#{code}}.chomp - Facter.warnonce "Using Facter::Util::Resolution.exec with a shell built-in is deprecated. Most built-ins can be replaced with native ruby commands. If you really have to run a built-in, pass \"cmd /c your_builtin\" as a command (command responsible for this message was \"#{code}\")" unless expanded_code - rescue Errno::ENOENT => detail - # command not found on Windows - return nil - rescue => detail - Facter.warn(detail) - return nil - end - - if out == "" - return nil - else - return out - end - end + extend Facter::Util::Execution + class << self + # Expose command execution methods that were extracted into + # Facter::Util::Execution from Facter::Util::Resolution in Facter 2.0.0 for + # compatibility. + # + # @deprecated + public :search_paths, :which, :absolute_path?, :expand_command, :with_env, :exec end # Sets the conditions for this resolution to be used. This takes a diff --git a/spec/unit/util/execution_spec.rb b/spec/unit/util/execution_spec.rb new file mode 100644 index 0000000000..74dacf6d22 --- /dev/null +++ b/spec/unit/util/execution_spec.rb @@ -0,0 +1,209 @@ +require 'spec_helper' +require 'facter/util/execution' + +describe Facter::Util::Execution do + + describe "#search_paths" do + context "on windows", :as_platform => :windows do + it "should use the PATH environment variable to determine locations" do + ENV.expects(:[]).with('PATH').returns 'C:\Windows;C:\Windows\System32' + described_class.search_paths.should == %w{C:\Windows C:\Windows\System32} + end + end + + context "on posix", :as_platform => :posix do + it "should use the PATH environment variable plus /sbin and /usr/sbin on unix" do + ENV.expects(:[]).with('PATH').returns "/bin:/usr/bin" + described_class.search_paths.should == %w{/bin /usr/bin /sbin /usr/sbin} + end + end + end + + describe "#which" do + context "when run on posix", :as_platform => :posix do + before :each do + described_class.stubs(:search_paths).returns [ '/bin', '/sbin', '/usr/sbin'] + end + + context "and provided with an absolute path" do + it "should return the binary if executable" do + File.expects(:executable?).with('/opt/foo').returns true + described_class.which('/opt/foo').should == '/opt/foo' + end + + it "should return nil if the binary is not executable" do + File.expects(:executable?).with('/opt/foo').returns false + described_class.which('/opt/foo').should be_nil + end + end + + context "and not provided with an absolute path" do + it "should return the absolute path if found" do + File.expects(:executable?).with('/bin/foo').returns false + File.expects(:executable?).with('/sbin/foo').returns true + File.expects(:executable?).with('/usr/sbin/foo').never + described_class.which('foo').should == '/sbin/foo' + end + + it "should return nil if not found" do + File.expects(:executable?).with('/bin/foo').returns false + File.expects(:executable?).with('/sbin/foo').returns false + File.expects(:executable?).with('/usr/sbin/foo').returns false + described_class.which('foo').should be_nil + end + end + end + + context "when run on windows", :as_platform => :windows do + before :each do + described_class.stubs(:search_paths).returns ['C:\Windows\system32', 'C:\Windows', 'C:\Windows\System32\Wbem' ] + ENV.stubs(:[]).with('PATHEXT').returns nil + end + + context "and provided with an absolute path" do + it "should return the binary if executable" do + File.expects(:executable?).with('C:\Tools\foo.exe').returns true + File.expects(:executable?).with('\\\\remote\dir\foo.exe').returns true + described_class.which('C:\Tools\foo.exe').should == 'C:\Tools\foo.exe' + described_class.which('\\\\remote\dir\foo.exe').should == '\\\\remote\dir\foo.exe' + end + + it "should return the binary with added extension if executable" do + ['.COM', '.BAT', '.CMD', '' ].each do |ext| + File.stubs(:executable?).with('C:\Windows\system32\netsh'+ext).returns false + end + File.expects(:executable?).with('C:\Windows\system32\netsh.EXE').returns true + + Facter.expects(:warnonce).with('Using Facter::Util::Execution.which with an absolute path like C:\\Windows\\system32\\netsh but no fileextension is deprecated. Please add the correct extension (.EXE)') + described_class.which('C:\Windows\system32\netsh').should == 'C:\Windows\system32\netsh.EXE' + end + + it "should return nil if the binary is not executable" do + File.expects(:executable?).with('C:\Tools\foo.exe').returns false + File.expects(:executable?).with('\\\\remote\dir\foo.exe').returns false + described_class.which('C:\Tools\foo.exe').should be_nil + described_class.which('\\\\remote\dir\foo.exe').should be_nil + end + end + + context "and not provided with an absolute path" do + it "should return the absolute path if found" do + File.expects(:executable?).with('C:\Windows\system32\foo.exe').returns false + File.expects(:executable?).with('C:\Windows\foo.exe').returns true + File.expects(:executable?).with('C:\Windows\System32\Wbem\foo.exe').never + described_class.which('foo.exe').should == 'C:\Windows\foo.exe' + end + + it "should return the absolute path with file extension if found" do + ['.COM', '.EXE', '.BAT', '.CMD', '' ].each do |ext| + File.stubs(:executable?).with('C:\Windows\system32\foo'+ext).returns false + File.stubs(:executable?).with('C:\Windows\System32\Wbem\foo'+ext).returns false + end + ['.COM', '.BAT', '.CMD', '' ].each do |ext| + File.stubs(:executable?).with('C:\Windows\foo'+ext).returns false + end + File.stubs(:executable?).with('C:\Windows\foo.EXE').returns true + + described_class.which('foo').should == 'C:\Windows\foo.EXE' + end + + it "should return nil if not found" do + File.expects(:executable?).with('C:\Windows\system32\foo.exe').returns false + File.expects(:executable?).with('C:\Windows\foo.exe').returns false + File.expects(:executable?).with('C:\Windows\System32\Wbem\foo.exe').returns false + described_class.which('foo.exe').should be_nil + end + end + end + + describe "#expand_command" do + context "on windows", :as_platform => :windows do + it "should expand binary" do + described_class.expects(:which).with('cmd').returns 'C:\Windows\System32\cmd' + described_class.expand_command( + 'cmd /c echo foo > C:\bar' + ).should == 'C:\Windows\System32\cmd /c echo foo > C:\bar' + end + + it "should expand double quoted binary" do + described_class.expects(:which).with('my foo').returns 'C:\My Tools\my foo.exe' + described_class.expand_command('"my foo" /a /b').should == '"C:\My Tools\my foo.exe" /a /b' + end + + it "should not expand single quoted binary" do + described_class.expects(:which).with('\'C:\My').returns nil + described_class.expand_command('\'C:\My Tools\foo.exe\' /a /b').should be_nil + end + + it "should quote expanded binary if found in path with spaces" do + described_class.expects(:which).with('foo').returns 'C:\My Tools\foo.exe' + described_class.expand_command('foo /a /b').should == '"C:\My Tools\foo.exe" /a /b' + end + + it "should return nil if not found" do + described_class.expects(:which).with('foo').returns nil + described_class.expand_command('foo /a | stuff >> /dev/null').should be_nil + end + end + + context "on unix", :as_platform => :posix do + it "should expand binary" do + described_class.expects(:which).with('foo').returns '/bin/foo' + described_class.expand_command('foo -a | stuff >> /dev/null').should == '/bin/foo -a | stuff >> /dev/null' + end + + it "should expand double quoted binary" do + described_class.expects(:which).with('/tmp/my foo').returns '/tmp/my foo' + described_class.expand_command(%q{"/tmp/my foo" bar}).should == %q{"/tmp/my foo" bar} + end + + it "should expand single quoted binary" do + described_class.expects(:which).with('my foo').returns '/home/bob/my path/my foo' + described_class.expand_command(%q{'my foo' -a}).should == %q{'/home/bob/my path/my foo' -a} + end + + it "should quote expanded binary if found in path with spaces" do + described_class.expects(:which).with('foo.sh').returns '/home/bob/my tools/foo.sh' + described_class.expand_command('foo.sh /a /b').should == %q{'/home/bob/my tools/foo.sh' /a /b} + end + + it "should return nil if not found" do + described_class.expects(:which).with('foo').returns nil + described_class.expand_command('foo -a | stuff >> /dev/null').should be_nil + end + end + end + + end + + describe "#absolute_path?" do + context "when run on unix", :as_platform => :posix do + %w[/ /foo /foo/../bar //foo //Server/Foo/Bar //?/C:/foo/bar /\Server/Foo /foo//bar/baz].each do |path| + it "should return true for #{path}" do + described_class.should be_absolute_path(path) + end + end + + %w[. ./foo \foo C:/foo \\Server\Foo\Bar \\?\C:\foo\bar \/?/foo\bar \/Server/foo foo//bar/baz].each do |path| + it "should return false for #{path}" do + described_class.should_not be_absolute_path(path) + end + end + end + + context "when run on windows", :as_platform => :windows do + %w[C:/foo C:\foo \\\\Server\Foo\Bar \\\\?\C:\foo\bar //Server/Foo/Bar //?/C:/foo/bar /\?\C:/foo\bar \/Server\Foo/Bar c:/foo//bar//baz].each do |path| + it "should return true for #{path}" do + described_class.should be_absolute_path(path) + end + end + + %w[/ . ./foo \foo /foo /foo/../bar //foo C:foo/bar foo//bar/baz].each do |path| + it "should return false for #{path}" do + described_class.should_not be_absolute_path(path) + end + end + end + end + +end diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index 78bc151123..3ac79e0ee2 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -391,328 +391,4 @@ class FlushFakeError < StandardError; end @resolve.should be_suitable end end - - it "should have a class method for executing code" do - Facter::Util::Resolution.should respond_to(:exec) - end - - # taken from puppet: spec/unit/util_spec.rb - describe "#absolute_path?" do - context "when run on unix", :as_platform => :posix do - %w[/ /foo /foo/../bar //foo //Server/Foo/Bar //?/C:/foo/bar /\Server/Foo /foo//bar/baz].each do |path| - it "should return true for #{path}" do - Facter::Util::Resolution.should be_absolute_path(path) - end - end - - %w[. ./foo \foo C:/foo \\Server\Foo\Bar \\?\C:\foo\bar \/?/foo\bar \/Server/foo foo//bar/baz].each do |path| - it "should return false for #{path}" do - Facter::Util::Resolution.should_not be_absolute_path(path) - end - end - end - - context "when run on windows", :as_platform => :windows do - %w[C:/foo C:\foo \\\\Server\Foo\Bar \\\\?\C:\foo\bar //Server/Foo/Bar //?/C:/foo/bar /\?\C:/foo\bar \/Server\Foo/Bar c:/foo//bar//baz].each do |path| - it "should return true for #{path}" do - Facter::Util::Resolution.should be_absolute_path(path) - end - end - - %w[/ . ./foo \foo /foo /foo/../bar //foo C:foo/bar foo//bar/baz].each do |path| - it "should return false for #{path}" do - Facter::Util::Resolution.should_not be_absolute_path(path) - end - end - end - end - - describe "#search_paths" do - context "on windows", :as_platform => :windows do - it "should use the PATH environment variable to determine locations" do - ENV.expects(:[]).with('PATH').returns 'C:\Windows;C:\Windows\System32' - Facter::Util::Resolution.search_paths.should == %w{C:\Windows C:\Windows\System32} - end - end - - context "on posix", :as_platform => :posix do - it "should use the PATH environment variable plus /sbin and /usr/sbin on unix" do - ENV.expects(:[]).with('PATH').returns "/bin:/usr/bin" - Facter::Util::Resolution.search_paths.should == %w{/bin /usr/bin /sbin /usr/sbin} - end - end - end - - describe "#which" do - context "when run on posix", :as_platform => :posix do - before :each do - Facter::Util::Resolution.stubs(:search_paths).returns [ '/bin', '/sbin', '/usr/sbin'] - end - - context "and provided with an absolute path" do - it "should return the binary if executable" do - File.expects(:executable?).with('/opt/foo').returns true - Facter::Util::Resolution.which('/opt/foo').should == '/opt/foo' - end - - it "should return nil if the binary is not executable" do - File.expects(:executable?).with('/opt/foo').returns false - Facter::Util::Resolution.which('/opt/foo').should be_nil - end - end - - context "and not provided with an absolute path" do - it "should return the absolute path if found" do - File.expects(:executable?).with('/bin/foo').returns false - File.expects(:executable?).with('/sbin/foo').returns true - File.expects(:executable?).with('/usr/sbin/foo').never - Facter::Util::Resolution.which('foo').should == '/sbin/foo' - end - - it "should return nil if not found" do - File.expects(:executable?).with('/bin/foo').returns false - File.expects(:executable?).with('/sbin/foo').returns false - File.expects(:executable?).with('/usr/sbin/foo').returns false - Facter::Util::Resolution.which('foo').should be_nil - end - end - end - - context "when run on windows", :as_platform => :windows do - before :each do - Facter::Util::Resolution.stubs(:search_paths).returns ['C:\Windows\system32', 'C:\Windows', 'C:\Windows\System32\Wbem' ] - ENV.stubs(:[]).with('PATHEXT').returns nil - end - - context "and provided with an absolute path" do - it "should return the binary if executable" do - File.expects(:executable?).with('C:\Tools\foo.exe').returns true - File.expects(:executable?).with('\\\\remote\dir\foo.exe').returns true - Facter::Util::Resolution.which('C:\Tools\foo.exe').should == 'C:\Tools\foo.exe' - Facter::Util::Resolution.which('\\\\remote\dir\foo.exe').should == '\\\\remote\dir\foo.exe' - end - - it "should return the binary with added extension if executable" do - ['.COM', '.BAT', '.CMD', '' ].each do |ext| - File.stubs(:executable?).with('C:\Windows\system32\netsh'+ext).returns false - end - File.expects(:executable?).with('C:\Windows\system32\netsh.EXE').returns true - - Facter.expects(:warnonce).with('Using Facter::Util::Resolution.which with an absolute path like C:\\Windows\\system32\\netsh but no fileextension is deprecated. Please add the correct extension (.EXE)') - Facter::Util::Resolution.which('C:\Windows\system32\netsh').should == 'C:\Windows\system32\netsh.EXE' - end - - it "should return nil if the binary is not executable" do - File.expects(:executable?).with('C:\Tools\foo.exe').returns false - File.expects(:executable?).with('\\\\remote\dir\foo.exe').returns false - Facter::Util::Resolution.which('C:\Tools\foo.exe').should be_nil - Facter::Util::Resolution.which('\\\\remote\dir\foo.exe').should be_nil - end - end - - context "and not provided with an absolute path" do - it "should return the absolute path if found" do - File.expects(:executable?).with('C:\Windows\system32\foo.exe').returns false - File.expects(:executable?).with('C:\Windows\foo.exe').returns true - File.expects(:executable?).with('C:\Windows\System32\Wbem\foo.exe').never - Facter::Util::Resolution.which('foo.exe').should == 'C:\Windows\foo.exe' - end - - it "should return the absolute path with file extension if found" do - ['.COM', '.EXE', '.BAT', '.CMD', '' ].each do |ext| - File.stubs(:executable?).with('C:\Windows\system32\foo'+ext).returns false - File.stubs(:executable?).with('C:\Windows\System32\Wbem\foo'+ext).returns false - end - ['.COM', '.BAT', '.CMD', '' ].each do |ext| - File.stubs(:executable?).with('C:\Windows\foo'+ext).returns false - end - File.stubs(:executable?).with('C:\Windows\foo.EXE').returns true - - Facter::Util::Resolution.which('foo').should == 'C:\Windows\foo.EXE' - end - - it "should return nil if not found" do - File.expects(:executable?).with('C:\Windows\system32\foo.exe').returns false - File.expects(:executable?).with('C:\Windows\foo.exe').returns false - File.expects(:executable?).with('C:\Windows\System32\Wbem\foo.exe').returns false - Facter::Util::Resolution.which('foo.exe').should be_nil - end - end - end - - describe "#expand_command" do - context "on windows", :as_platform => :windows do - it "should expand binary" do - Facter::Util::Resolution.expects(:which).with('cmd').returns 'C:\Windows\System32\cmd' - Facter::Util::Resolution.expand_command( - 'cmd /c echo foo > C:\bar' - ).should == 'C:\Windows\System32\cmd /c echo foo > C:\bar' - end - - it "should expand double quoted binary" do - Facter::Util::Resolution.expects(:which).with('my foo').returns 'C:\My Tools\my foo.exe' - Facter::Util::Resolution.expand_command('"my foo" /a /b').should == '"C:\My Tools\my foo.exe" /a /b' - end - - it "should not expand single quoted binary" do - Facter::Util::Resolution.expects(:which).with('\'C:\My').returns nil - Facter::Util::Resolution.expand_command('\'C:\My Tools\foo.exe\' /a /b').should be_nil - end - - it "should quote expanded binary if found in path with spaces" do - Facter::Util::Resolution.expects(:which).with('foo').returns 'C:\My Tools\foo.exe' - Facter::Util::Resolution.expand_command('foo /a /b').should == '"C:\My Tools\foo.exe" /a /b' - end - - it "should return nil if not found" do - Facter::Util::Resolution.expects(:which).with('foo').returns nil - Facter::Util::Resolution.expand_command('foo /a | stuff >> /dev/null').should be_nil - end - end - - context "on unix", :as_platform => :posix do - it "should expand binary" do - Facter::Util::Resolution.expects(:which).with('foo').returns '/bin/foo' - Facter::Util::Resolution.expand_command('foo -a | stuff >> /dev/null').should == '/bin/foo -a | stuff >> /dev/null' - end - - it "should expand double quoted binary" do - Facter::Util::Resolution.expects(:which).with('/tmp/my foo').returns '/tmp/my foo' - Facter::Util::Resolution.expand_command(%q{"/tmp/my foo" bar}).should == %q{"/tmp/my foo" bar} - end - - it "should expand single quoted binary" do - Facter::Util::Resolution.expects(:which).with('my foo').returns '/home/bob/my path/my foo' - Facter::Util::Resolution.expand_command(%q{'my foo' -a}).should == %q{'/home/bob/my path/my foo' -a} - end - - it "should quote expanded binary if found in path with spaces" do - Facter::Util::Resolution.expects(:which).with('foo.sh').returns '/home/bob/my tools/foo.sh' - Facter::Util::Resolution.expand_command('foo.sh /a /b').should == %q{'/home/bob/my tools/foo.sh' /a /b} - end - - it "should return nil if not found" do - Facter::Util::Resolution.expects(:which).with('foo').returns nil - Facter::Util::Resolution.expand_command('foo -a | stuff >> /dev/null').should be_nil - end - end - end - - end - - # It's not possible, AFAICT, to mock %x{}, so I can't really test this bit. - describe "when executing code" do - # set up some command strings, making sure we get the right version for both unix and windows - echo_command = Facter::Util::Config.is_windows? ? 'cmd.exe /c "echo foo"' : 'echo foo' - echo_env_var_command = Facter::Util::Config.is_windows? ? 'cmd.exe /c "echo %%%s%%"' : 'echo $%s' - - it "should deprecate the interpreter parameter" do - Facter.expects(:warnonce).with("The interpreter parameter to 'exec' is deprecated and will be removed in a future version.") - Facter::Util::Resolution.exec("/something", "/bin/perl") - end - - # execute a simple echo command - it "should execute the binary" do - Facter::Util::Resolution.exec(echo_command).should == "foo" - end - - it "should override the LANG environment variable" do - Facter::Util::Resolution.exec(echo_env_var_command % 'LANG').should == "C" - end - - it "should respect other overridden environment variables" do - Facter::Util::Resolution.with_env( {"FOO" => "foo"} ) do - Facter::Util::Resolution.exec(echo_env_var_command % 'FOO').should == "foo" - end - end - - it "should restore overridden LANG environment variable after execution" do - # we're going to call with_env in a nested fashion, to make sure that the environment gets restored properly - # at each level - Facter::Util::Resolution.with_env( {"LANG" => "foo"} ) do - # Resolution.exec always overrides 'LANG' for its own execution scope - Facter::Util::Resolution.exec(echo_env_var_command % 'LANG').should == "C" - # But after 'exec' completes, we should see our value restored - ENV['LANG'].should == "foo" - # Now we'll do a nested call to with_env - Facter::Util::Resolution.with_env( {"LANG" => "bar"} ) do - # During 'exec' it should still be 'C' - Facter::Util::Resolution.exec(echo_env_var_command % 'LANG').should == "C" - # After exec it should be restored to our current value for this level of the nesting... - ENV['LANG'].should == "bar" - end - # Now we've dropped out of one level of nesting, - ENV['LANG'].should == "foo" - # Call exec one more time just for kicks - Facter::Util::Resolution.exec(echo_env_var_command % 'LANG').should == "C" - # One last check at our current nesting level. - ENV['LANG'].should == "foo" - end - end - - context "when run on unix", :as_platform => :posix do - context "binary is present" do - it "should run the command if path to binary is absolute" do - Facter::Util::Resolution.expects(:expand_command).with('/usr/bin/uname -m').returns('/usr/bin/uname -m') - Facter::Util::Resolution.expects(:`).with('/usr/bin/uname -m').returns 'x86_64' - Facter::Util::Resolution.exec('/usr/bin/uname -m').should == 'x86_64' - end - - it "should run the expanded command if path to binary not absolute" do - Facter::Util::Resolution.expects(:expand_command).with('uname -m').returns('/usr/bin/uname -m') - Facter::Util::Resolution.expects(:`).with('/usr/bin/uname -m').returns 'x86_64' - Facter::Util::Resolution.exec('uname -m').should == 'x86_64' - end - end - - context "binary is not present" do - it "should not run the command if path to binary is absolute" do - Facter::Util::Resolution.expects(:expand_command).with('/usr/bin/uname -m').returns nil - Facter::Util::Resolution.expects(:`).with('/usr/bin/uname -m').never - Facter::Util::Resolution.exec('/usr/bin/uname -m').should be_nil - end - it "should not run the command if path to binary is not absolute" do - Facter::Util::Resolution.expects(:expand_command).with('uname -m').returns nil - Facter::Util::Resolution.expects(:`).with('uname -m').never - Facter::Util::Resolution.exec('uname -m').should be_nil - end - end - end - - context "when run on windows", :as_platform => :windows do - context "binary is present" do - it "should run the command if path to binary is absolute" do - Facter::Util::Resolution.expects(:expand_command).with(%q{C:\Windows\foo.exe /a /b}).returns(%q{C:\Windows\foo.exe /a /b}) - Facter::Util::Resolution.expects(:`).with(%q{C:\Windows\foo.exe /a /b}).returns 'bar' - Facter::Util::Resolution.exec(%q{C:\Windows\foo.exe /a /b}).should == 'bar' - end - - it "should run the expanded command if path to binary not absolute" do - Facter::Util::Resolution.expects(:expand_command).with(%q{foo.exe /a /b}).returns(%q{C:\Windows\foo.exe /a /b}) - Facter::Util::Resolution.expects(:`).with(%q{C:\Windows\foo.exe /a /b}).returns 'bar' - Facter::Util::Resolution.exec(%q{foo.exe /a /b}).should == 'bar' - end - end - - context "binary is not present" do - it "should not run the command if path to binary is absolute" do - Facter::Util::Resolution.expects(:expand_command).with(%q{C:\Windows\foo.exe /a /b}).returns nil - Facter::Util::Resolution.expects(:`).with(%q{C:\Windows\foo.exe /a /b}).never - Facter::Util::Resolution.exec(%q{C:\Windows\foo.exe /a /b}).should be_nil - end - it "should try to run the command and return output of a shell-builtin" do - Facter::Util::Resolution.expects(:expand_command).with(%q{echo foo}).returns nil - Facter::Util::Resolution.expects(:`).with(%q{echo foo}).returns 'foo' - Facter.expects(:warnonce).with 'Using Facter::Util::Resolution.exec with a shell built-in is deprecated. Most built-ins can be replaced with native ruby commands. If you really have to run a built-in, pass "cmd /c your_builtin" as a command (command responsible for this message was "echo foo")' - Facter::Util::Resolution.exec(%q{echo foo}).should == 'foo' - end - it "should try to run the command and return nil if not shell-builtin" do - Facter::Util::Resolution.expects(:expand_command).with(%q{echo foo}).returns nil - Facter::Util::Resolution.stubs(:`).with(%q{echo foo}).raises Errno::ENOENT, 'some_error_message' - Facter.expects(:warnonce).never - Facter::Util::Resolution.exec(%q{echo foo}).should be_nil - end - end - end - end end From 1998cda3e1ecc6c9e15fa16e9c974132c9db4aa1 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Fri, 10 Jan 2014 12:06:59 -0800 Subject: [PATCH 1540/3753] Move execution module from util to core --- lib/facter/{util => core}/execution.rb | 2 +- lib/facter/util/resolution.rb | 5 +++-- spec/unit/{util => core}/execution_spec.rb | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) rename lib/facter/{util => core}/execution.rb (99%) rename spec/unit/{util => core}/execution_spec.rb (99%) diff --git a/lib/facter/util/execution.rb b/lib/facter/core/execution.rb similarity index 99% rename from lib/facter/util/execution.rb rename to lib/facter/core/execution.rb index 875470ab12..7173730d27 100644 --- a/lib/facter/util/execution.rb +++ b/lib/facter/core/execution.rb @@ -1,7 +1,7 @@ require 'facter/util/config' module Facter - module Util + module Core module Execution module_function diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 742bb98020..c7e6b83dc8 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -1,7 +1,7 @@ require 'facter/util/confine' require 'facter/util/config' require 'facter/util/normalization' -require 'facter/util/execution' +require 'facter/core/execution' require 'timeout' @@ -30,7 +30,8 @@ class Facter::Util::Resolution INTERPRETER = Facter::Util::Config.is_windows? ? "cmd.exe" : "/bin/sh" - extend Facter::Util::Execution + extend Facter::Core::Execution + class << self # Expose command execution methods that were extracted into # Facter::Util::Execution from Facter::Util::Resolution in Facter 2.0.0 for diff --git a/spec/unit/util/execution_spec.rb b/spec/unit/core/execution_spec.rb similarity index 99% rename from spec/unit/util/execution_spec.rb rename to spec/unit/core/execution_spec.rb index 74dacf6d22..e535f05124 100644 --- a/spec/unit/util/execution_spec.rb +++ b/spec/unit/core/execution_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' -require 'facter/util/execution' +require 'facter/core/execution' -describe Facter::Util::Execution do +describe Facter::Core::Execution do describe "#search_paths" do context "on windows", :as_platform => :windows do From ecbe3cab0ceb7229880ed45ed9a97da47f9aa93b Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Fri, 9 Aug 2013 14:29:45 -0400 Subject: [PATCH 1541/3753] (#22107) Force ASCII-8BIT encoding on raw data in property lists Ruby-1.9 and up default to UTF-8 encoding for strings, but CFData is used to store bytestrings in plists as read from file-like objects. So, disregard the encoding returned from `obj.read`, treating the string as a bytestring. --- lib/facter/util/cfpropertylist/lib/rbCFPropertyList.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/facter/util/cfpropertylist/lib/rbCFPropertyList.rb b/lib/facter/util/cfpropertylist/lib/rbCFPropertyList.rb index cd7fcf1cf1..3acdbba3d9 100644 --- a/lib/facter/util/cfpropertylist/lib/rbCFPropertyList.rb +++ b/lib/facter/util/cfpropertylist/lib/rbCFPropertyList.rb @@ -164,7 +164,12 @@ def guess(object, options = {}) when Object.const_defined?('BigDecimal') && object.is_a?(BigDecimal) CFReal.new(object) when object.respond_to?(:read) - CFData.new(object.read(), CFData::DATA_RAW) + raw_data = object.read + # treat the data as a bytestring (ASCII-8BIT) if Ruby supports it. Do this by forcing + # the encoding, on the assumption that the bytes were read correctly, and just tagged with + # an inappropriate encoding, rather than transcoding. + raw_data.force_encoding(Encoding::ASCII_8BIT) if raw_data.respond_to?(:force_encoding) + CFData.new(raw_data, CFData::DATA_RAW) when options[:converter_method] && object.respond_to?(options[:converter_method]) if options[:converter_with_opts] Facter::Util::CFPropertyList.guess(object.send(options[:converter_method],options),options) From 299b8651ca755a56779a90c07a338e42b5f59513 Mon Sep 17 00:00:00 2001 From: Matthaus Owens Date: Fri, 10 Jan 2014 17:31:54 -0800 Subject: [PATCH 1542/3753] (maint) Redirect dmidecode stderr to /dev/null Previously most calls to dmidecode had stderr redirected to /dev/null. dmidecode tends to have error messages that go to stderr during execution, which pollutes facter runs. This commit updates the remaining calls to dmidecode to also redirect stderr to /dev/null so that facter runs can continue in blissful ignorance of any dmidecode errors. --- lib/facter/virtual.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 4416261a6d..4ef0ba0a06 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -133,7 +133,7 @@ end # Parse dmidecode - output = Facter::Util::Resolution.exec('dmidecode') + output = Facter::Util::Resolution.exec('dmidecode 2> /dev/null') if output lines = output.split("\n") next "parallels" if lines.any? {|l| l =~ /Parallels/ } From b72da8b8277cea28a8a28780463fa7bd31186544 Mon Sep 17 00:00:00 2001 From: Matthaus Owens Date: Mon, 13 Jan 2014 13:48:09 -0800 Subject: [PATCH 1543/3753] (maint) Update virtual spec to match dmidecode invocation A previous commit updated the dmidecode invocation in util/virtual.rb, but it did not update the corresponding spec test, which stubs the invocation to return 'something'. This commit updates the virtual spec tests to stub the updated invocation. --- spec/unit/util/virtual_spec.rb | 2 +- spec/unit/virtual_spec.rb | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/spec/unit/util/virtual_spec.rb b/spec/unit/util/virtual_spec.rb index f4dca6f212..5caaacb0db 100755 --- a/spec/unit/util/virtual_spec.rb +++ b/spec/unit/util/virtual_spec.rb @@ -91,7 +91,7 @@ it "should identify kvm" do Facter::Util::Virtual.stubs(:kvm?).returns(true) - Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("something") + Facter::Util::Resolution.stubs(:exec).with('dmidecode 2> /dev/null').returns("something") Facter::Util::Virtual.kvm_type().should == "kvm" end diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index c53c009280..509c4be7da 100755 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -108,7 +108,7 @@ it "should be vmware with VMWare vendor name from dmidecode" do Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) - Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("On Board Device 1 Information\nType: Video\nStatus: Disabled\nDescription: VMware SVGA II") + Facter::Util::Resolution.stubs(:exec).with('dmidecode 2> /dev/null').returns("On Board Device 1 Information\nType: Video\nStatus: Disabled\nDescription: VMware SVGA II") Facter.fact(:virtual).value.should == "vmware" end @@ -134,33 +134,33 @@ it "should be xenhvm with Xen HVM vendor name from dmidecode" do Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) - Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("System Information\nManufacturer: Xen\nProduct Name: HVM domU") + Facter::Util::Resolution.stubs(:exec).with('dmidecode 2> /dev/null').returns("System Information\nManufacturer: Xen\nProduct Name: HVM domU") Facter.fact(:virtual).value.should == "xenhvm" end it "should be parallels with Parallels vendor name from dmidecode" do Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) - Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("On Board Device Information\nType: Video\nStatus: Disabled\nDescription: Parallels Video Adapter") + Facter::Util::Resolution.stubs(:exec).with('dmidecode 2> /dev/null').returns("On Board Device Information\nType: Video\nStatus: Disabled\nDescription: Parallels Video Adapter") Facter.fact(:virtual).value.should == "parallels" end it "should be virtualbox with VirtualBox vendor name from dmidecode" do Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) - Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("BIOS Information\nVendor: innotek GmbH\nVersion: VirtualBox\n\nSystem Information\nManufacturer: innotek GmbH\nProduct Name: VirtualBox\nFamily: Virtual Machine") + Facter::Util::Resolution.stubs(:exec).with('dmidecode 2> /dev/null').returns("BIOS Information\nVendor: innotek GmbH\nVersion: VirtualBox\n\nSystem Information\nManufacturer: innotek GmbH\nProduct Name: VirtualBox\nFamily: Virtual Machine") Facter.fact(:virtual).value.should == "virtualbox" end it "should be rhev with RHEV Hypervisor product name from dmidecode" do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) - Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("Product Name: RHEV Hypervisor") + Facter::Util::Resolution.stubs(:exec).with('dmidecode 2> /dev/null').returns("Product Name: RHEV Hypervisor") Facter.fact(:virtual).value.should == "rhev" end it "should be ovirt with oVirt Node product name from dmidecode" do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) - Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("Product Name: oVirt Node") + Facter::Util::Resolution.stubs(:exec).with('dmidecode 2> /dev/null').returns("Product Name: oVirt Node") Facter.fact(:virtual).value.should == "ovirt" end @@ -171,7 +171,7 @@ it "should be hyperv with Microsoft vendor name from dmidecode" do Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) - Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("System Information\nManufacturer: Microsoft Corporation\nProduct Name: Virtual Machine") + Facter::Util::Resolution.stubs(:exec).with('dmidecode 2> /dev/null').returns("System Information\nManufacturer: Microsoft Corporation\nProduct Name: Virtual Machine") Facter.fact(:virtual).value.should == "hyperv" end @@ -208,7 +208,7 @@ it "(#20236) is vmware when dmidecode contains vmware and lspci returns insufficient information" do Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns("garbage\ninformation\n") - Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("On Board Device 1 Information\nType: Video\nStatus: Disabled\nDescription: VMware SVGA II") + Facter::Util::Resolution.stubs(:exec).with('dmidecode 2> /dev/null').returns("On Board Device 1 Information\nType: Video\nStatus: Disabled\nDescription: VMware SVGA II") Facter.fact(:virtual).value.should eq("vmware") end end @@ -222,7 +222,7 @@ it "should be vmware with VMWare vendor name from prtdiag" do Facter.fact(:hardwaremodel).stubs(:value).returns(nil) Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) - Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns(nil) + Facter::Util::Resolution.stubs(:exec).with('dmidecode 2> /dev/null').returns(nil) Facter::Util::Resolution.stubs(:exec).with('prtdiag').returns("System Configuration: VMware, Inc. VMware Virtual Platform") Facter.fact(:virtual).value.should == "vmware" end @@ -230,7 +230,7 @@ it "should be parallels with Parallels vendor name from prtdiag" do Facter.fact(:hardwaremodel).stubs(:value).returns(nil) Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) - Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns(nil) + Facter::Util::Resolution.stubs(:exec).with('dmidecode 2> /dev/null').returns(nil) Facter::Util::Resolution.stubs(:exec).with('prtdiag').returns("System Configuration: Parallels Virtual Platform") Facter.fact(:virtual).value.should == "parallels" end @@ -238,7 +238,7 @@ it "should be virtualbox with VirtualBox vendor name from prtdiag" do Facter.fact(:hardwaremodel).stubs(:value).returns(nil) Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) - Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns(nil) + Facter::Util::Resolution.stubs(:exec).with('dmidecode 2> /dev/null').returns(nil) Facter::Util::Resolution.stubs(:exec).with('prtdiag').returns("System Configuration: innotek GmbH VirtualBox") Facter.fact(:virtual).value.should == "virtualbox" end @@ -250,7 +250,7 @@ Facter.fact(:kernel).stubs(:value).returns("OpenBSD") Facter.fact(:hardwaremodel).stubs(:value).returns(nil) Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) - Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns(nil) + Facter::Util::Resolution.stubs(:exec).with('dmidecode 2> /dev/null').returns(nil) end it "should be parallels with Parallels product name from sysctl" do From 1da7261da2dfeca26f7763de6da5c2f0c691b6b7 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Mon, 13 Jan 2014 18:08:35 -0800 Subject: [PATCH 1544/3753] (fact-207) Issue deprecation warning for ldapname --- lib/facter/util/fact.rb | 4 +++- spec/unit/util/fact_spec.rb | 9 +++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/facter/util/fact.rb b/lib/facter/util/fact.rb index f7fcc1a16e..1158253b8d 100644 --- a/lib/facter/util/fact.rb +++ b/lib/facter/util/fact.rb @@ -30,7 +30,9 @@ def initialize(name, options = {}) # worst we'll have one. If we add more, this should be made more efficient. options.each do |name, value| case name - when :ldapname; self.ldapname = value + when :ldapname + Facter.warnonce("ldapname is deprecated and will be removed in a future version") + self.ldapname = value else raise ArgumentError, "Invalid fact option '%s'" % name end diff --git a/spec/unit/util/fact_spec.rb b/spec/unit/util/fact_spec.rb index 166817ed4d..c09410061d 100755 --- a/spec/unit/util/fact_spec.rb +++ b/spec/unit/util/fact_spec.rb @@ -12,12 +12,9 @@ Facter::Util::Fact.new("YayNess").name.should == :yayness end - it "should default to its name converted to a string as its ldapname" do - Facter::Util::Fact.new("YayNess").ldapname.should == "yayness" - end - - it "should allow specifying the ldap name at initialization" do - Facter::Util::Fact.new("YayNess", :ldapname => "fooness").ldapname.should == "fooness" + it "should issue a deprecation warning for use of ldapname" do + Facter.expects(:warnonce).with("ldapname is deprecated and will be removed in a future version") + Facter::Util::Fact.new("YayNess", :ldapname => "fooness") end it "should fail if an unknown option is provided" do From 842adf77603580cb36fecaabbd643d77d46e0c55 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Wed, 15 Jan 2014 13:17:54 -0800 Subject: [PATCH 1545/3753] (FACT-207) Remove all uses of :ldapname Commit 1da7261da deprecated the use of :ldapname for facts; this commit removes the use of :ldapname from the related facts and test coverage. --- lib/facter/hostname.rb | 2 +- lib/facter/ipaddress.rb | 2 +- spec/unit/facter_spec.rb | 12 ------------ spec/unit/util/collection_spec.rb | 23 +---------------------- 4 files changed, 3 insertions(+), 36 deletions(-) diff --git a/lib/facter/hostname.rb b/lib/facter/hostname.rb index 1f80fb3091..7cfc297acf 100644 --- a/lib/facter/hostname.rb +++ b/lib/facter/hostname.rb @@ -11,7 +11,7 @@ # Caveats: # -Facter.add(:hostname, :ldapname => "cn") do +Facter.add(:hostname) do setcode do hostname = nil if name = Facter::Util::Resolution.exec('hostname') diff --git a/lib/facter/ipaddress.rb b/lib/facter/ipaddress.rb index f4b46b1242..1f8400c0a1 100644 --- a/lib/facter/ipaddress.rb +++ b/lib/facter/ipaddress.rb @@ -121,7 +121,7 @@ end end -Facter.add(:ipaddress, :ldapname => "iphostnumber", :timeout => 2) do +Facter.add(:ipaddress, :timeout => 2) do setcode do if Facter.value(:kernel) == 'windows' require 'win32/resolv' diff --git a/spec/unit/facter_spec.rb b/spec/unit/facter_spec.rb index 1b69d561de..9c3bdef0a6 100755 --- a/spec/unit/facter_spec.rb +++ b/spec/unit/facter_spec.rb @@ -101,18 +101,6 @@ end end - describe "Facter[:hostname]" do - it "should have its ldapname set to 'cn'" do - Facter[:hostname].ldapname.should == "cn" - end - end - - describe "Facter[:ipaddress]" do - it "should have its ldapname set to 'iphostnumber'" do - Facter[:ipaddress].ldapname.should == "iphostnumber" - end - end - # #33 Make sure we only get one mac address it "should only return one mac address" do if macaddress = Facter.value(:macaddress) diff --git a/spec/unit/util/collection_spec.rb b/spec/unit/util/collection_spec.rb index 6e20d26071..8e211a86be 100755 --- a/spec/unit/util/collection_spec.rb +++ b/spec/unit/util/collection_spec.rb @@ -27,15 +27,7 @@ end it "should accept options" do - collection.add(:myname, :ldapname => "whatever") { } - end - - it "should set any appropriate options on the fact instances" do - # Use a real fact instance, because we're using respond_to? - fact = Facter::Util::Fact.new(:myname) - - collection.add(:myname, :ldapname => "testing") - collection.fact(:myname).ldapname.should == "testing" + collection.add(:myname, :timeout => 1) { } end it "should set appropriate options on the resolution instance" do @@ -48,19 +40,6 @@ collection.add(:myname, :timeout => "myval") {} end - it "should not pass fact-specific options to resolutions" do - fact = Facter::Util::Fact.new(:myname) - Facter::Util::Fact.expects(:new).with(:myname).returns fact - - resolve = Facter::Util::Resolution.new(:myname) {} - fact.expects(:add).returns resolve - - fact.expects(:ldapname=).with("foo") - resolve.expects(:timeout=).with("myval") - - collection.add(:myname, :timeout => "myval", :ldapname => "foo") {} - end - it "should fail if invalid options are provided" do lambda { collection.add(:myname, :foo => :bar) }.should raise_error(ArgumentError) end From 818b58b7773d14d06fea53f4706403eceb4b9514 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Wed, 15 Jan 2014 13:17:54 -0800 Subject: [PATCH 1546/3753] (FACT-207) Remove all uses of :ldapname Commit 1da7261da deprecated the use of :ldapname for facts; this commit removes the use of :ldapname from the related facts and test coverage. --- lib/facter/hostname.rb | 2 +- lib/facter/ipaddress.rb | 2 +- spec/unit/facter_spec.rb | 12 ------------ spec/unit/util/collection_spec.rb | 23 +---------------------- 4 files changed, 3 insertions(+), 36 deletions(-) diff --git a/lib/facter/hostname.rb b/lib/facter/hostname.rb index 2371ec8796..4f98b671cb 100644 --- a/lib/facter/hostname.rb +++ b/lib/facter/hostname.rb @@ -11,7 +11,7 @@ # Caveats: # -Facter.add(:hostname, :ldapname => "cn") do +Facter.add(:hostname) do setcode do hostname = nil if name = Facter::Util::Resolution.exec('hostname') diff --git a/lib/facter/ipaddress.rb b/lib/facter/ipaddress.rb index f4b46b1242..1f8400c0a1 100644 --- a/lib/facter/ipaddress.rb +++ b/lib/facter/ipaddress.rb @@ -121,7 +121,7 @@ end end -Facter.add(:ipaddress, :ldapname => "iphostnumber", :timeout => 2) do +Facter.add(:ipaddress, :timeout => 2) do setcode do if Facter.value(:kernel) == 'windows' require 'win32/resolv' diff --git a/spec/unit/facter_spec.rb b/spec/unit/facter_spec.rb index 1b69d561de..9c3bdef0a6 100755 --- a/spec/unit/facter_spec.rb +++ b/spec/unit/facter_spec.rb @@ -101,18 +101,6 @@ end end - describe "Facter[:hostname]" do - it "should have its ldapname set to 'cn'" do - Facter[:hostname].ldapname.should == "cn" - end - end - - describe "Facter[:ipaddress]" do - it "should have its ldapname set to 'iphostnumber'" do - Facter[:ipaddress].ldapname.should == "iphostnumber" - end - end - # #33 Make sure we only get one mac address it "should only return one mac address" do if macaddress = Facter.value(:macaddress) diff --git a/spec/unit/util/collection_spec.rb b/spec/unit/util/collection_spec.rb index 6e20d26071..8e211a86be 100755 --- a/spec/unit/util/collection_spec.rb +++ b/spec/unit/util/collection_spec.rb @@ -27,15 +27,7 @@ end it "should accept options" do - collection.add(:myname, :ldapname => "whatever") { } - end - - it "should set any appropriate options on the fact instances" do - # Use a real fact instance, because we're using respond_to? - fact = Facter::Util::Fact.new(:myname) - - collection.add(:myname, :ldapname => "testing") - collection.fact(:myname).ldapname.should == "testing" + collection.add(:myname, :timeout => 1) { } end it "should set appropriate options on the resolution instance" do @@ -48,19 +40,6 @@ collection.add(:myname, :timeout => "myval") {} end - it "should not pass fact-specific options to resolutions" do - fact = Facter::Util::Fact.new(:myname) - Facter::Util::Fact.expects(:new).with(:myname).returns fact - - resolve = Facter::Util::Resolution.new(:myname) {} - fact.expects(:add).returns resolve - - fact.expects(:ldapname=).with("foo") - resolve.expects(:timeout=).with("myval") - - collection.add(:myname, :timeout => "myval", :ldapname => "foo") {} - end - it "should fail if invalid options are provided" do lambda { collection.add(:myname, :foo => :bar) }.should raise_error(ArgumentError) end From 85a284f4d4dcc27b1f30d8fb7e0c623006b6d442 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Thu, 16 Jan 2014 15:17:36 +0000 Subject: [PATCH 1547/3753] Check opendir() for success before using it --- cfacterlib.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cfacterlib.cc b/cfacterlib.cc index 7142eba721..75f1c03c19 100644 --- a/cfacterlib.cc +++ b/cfacterlib.cc @@ -894,6 +894,9 @@ static void get_external_facts_from_executable(fact_map& facts, string executabl static void get_external_facts(fact_map& facts, string directory) { DIR *external_dir = opendir(directory.c_str()); + if (external_dir == NULL) + return; + struct dirent *external_fact; while (external_fact = readdir(external_dir)) { From d7d2cd1ede48dc4aa22756e0b072f778e56d2557 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Thu, 16 Jan 2014 15:19:38 +0000 Subject: [PATCH 1548/3753] Add some nominal compile-time support for OSX. Previously this wouldn't compile on OSX (10.8). This change adds some clang flags and some osx ifdefs such that the code compiles. However it will still segfault if run on osx :) But wanted to save these bits away in case I try to make it work on osx at a later date. --- Makefile | 17 ++++++++++++++--- cfacterlib.cc | 36 ++++++++++++++++++++++++++++-------- 2 files changed, 42 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index fa5fac83ad..bb633953c8 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,22 @@ +# Base compiler flags: debug and c++0x (maybe c++11 later?) +CCFLAGS = -std=c++0x -g + +UNAME_S := $(shell uname -s) +ifeq ($(UNAME_S),Darwin) + # because Darwin has two c++ libs and this one works better + # need to investigate this more + CCFLAGS += --stdlib=libc++ + CCFLAGS += -Wno-tautological-constant-out-of-range-compare +endif + cfacter: cfacter.cc cfacterlib.cc cfacterlib.h - g++ -std=c++0x -g -o cfacter cfacter.cc cfacterlib.cc -I . + g++ ${CCFLAGS} -o cfacter cfacter.cc cfacterlib.cc -I C cfacterlib.o: cfacterlib.cc cfacterlib.h - g++ -std=c++0x -g -fPIC -c -o $@ cfacterlib.cc -I . + g++ ${CCFLAGS} -fPIC -c -o $@ cfacterlib.cc -I . cfacterlib.so: cfacterlib.o - g++ -std=c++0x -g -o $@ $^ -fPIC -shared + g++ ${CCFLAGS} -o $@ $^ -fPIC -shared missing: -$(shell facter | grep "=>" | cut -f1 -d' ' | sort > /tmp/facter.txt) diff --git a/cfacterlib.cc b/cfacterlib.cc index 75f1c03c19..99c1eb4149 100644 --- a/cfacterlib.cc +++ b/cfacterlib.cc @@ -8,7 +8,12 @@ #include #include #include +#ifdef __linux__ #include +#endif +#ifdef __APPLE__ +#include +#endif #include #include #include @@ -179,7 +184,8 @@ void get_network_facts(fact_map& facts) } facts[string("mtu_") + r->ifr_name] = to_string(r->ifr_mtu); - if (primaryInterface) ; // no unmarked version of this network fact + if (primaryInterface) + ; // no unmarked version of this network fact // netmask and network are both derived from the same ioctl if (ioctl(s, SIOCGIFNETMASK, r) < 0) { @@ -187,6 +193,7 @@ void get_network_facts(fact_map& facts) exit(1); } +#ifndef __APPLE__ // ifr_netmask isn't supported, might need to use SC library // netmask struct in_addr netmask_addr = ((struct sockaddr_in *)&r->ifr_netmask)->sin_addr; const char *netmask = inet_ntoa(netmask_addr); @@ -202,12 +209,14 @@ void get_network_facts(fact_map& facts) facts[string("network_") + r->ifr_name] = network; if (primaryInterface) facts["network"] = network; +#endif +#ifndef __APPLE__ // SIOCGIFHWADDR isn't supported, might need to use SC library // and the mac address (but not for loopback) if (strcmp(r->ifr_name, "lo")) { if (ioctl(s, SIOCGIFHWADDR, r) < 0) { - perror("ioctl SIOCGIFHWADDR"); - exit(1); + perror("ioctl SIOCGIFHWADDR"); + exit(1); } // extract mac into a string, okay a char array @@ -222,6 +231,7 @@ void get_network_facts(fact_map& facts) if (primaryInterface) facts["macaddress"] = mac_address; } +#endif } facts["interfaces"] = interfaces; @@ -232,6 +242,7 @@ void get_network_facts(fact_map& facts) void get_kernel_facts(fact_map& facts) { +#ifdef __linux__ // this is linux-only, so there you have it facts["kernel"] = "Linux"; string kernelrelease = read_oneline_file("/proc/sys/kernel/osrelease"); @@ -240,6 +251,11 @@ void get_kernel_facts(fact_map& facts) facts["kernelversion"] = kernelversion; string kernelmajversion = kernelversion.substr(0, kernelversion.rfind(".")); facts["kernelmajversion"] = kernelmajversion; +#else +#ifdef __APPLE__ + facts["kernel"] = "Darwin"; +#endif +#endif } static void get_lsb_facts(fact_map& facts) @@ -315,7 +331,7 @@ string popen_stdout(string cmd) string cmd_output = ""; char buf[1024]; size_t bytesRead; - while (bytesRead = fread(buf, 1, sizeof(buf) - 1, cmd_fd)) { + while ((bytesRead = fread(buf, 1, sizeof(buf) - 1, cmd_fd))) { buf[bytesRead] = 0; cmd_output += buf; } @@ -363,17 +379,22 @@ void get_ruby_lib_versions(fact_map& facts) void get_blockdevice_facts(fact_map& facts) { string blockdevices = ""; + DIR *sys_block_dir = opendir("/sys/block"); + if (sys_block_dir == NULL) { + return; + } + struct dirent *bd; + while ((bd = readdir(sys_block_dir))) { - while (bd = readdir(sys_block_dir)) { bool real_block_device = false; string device_dir_path = "/sys/block/"; device_dir_path += bd->d_name; DIR *device_dir = opendir(device_dir_path.c_str()); struct dirent *subdir; - while (subdir = readdir(device_dir)) { + while ((subdir = readdir(device_dir))) { if (strcmp(subdir->d_name, "device") == 0) { // we have a winner real_block_device = true; @@ -700,7 +721,6 @@ void get_architecture_facts(fact_map& facts) void get_dmidecode_facts(fact_map& facts) { - // from a time perspective simulate expense with lspci and dmidecode invocations string dmidecode_output = popen_stdout("/usr/sbin/dmidecode"); std::stringstream ss(dmidecode_output); string line; @@ -899,7 +919,7 @@ static void get_external_facts(fact_map& facts, string directory) struct dirent *external_fact; - while (external_fact = readdir(external_dir)) { + while ((external_fact = readdir(external_dir))) { string full_path = directory + "/" + external_fact->d_name; if (access(full_path.c_str(), X_OK) != 0) From a64a1fa0af7ee8b81b713c082ec41ab165e43c27 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 13 Jan 2014 11:00:18 -0800 Subject: [PATCH 1549/3753] (maint) extract Execution#with_env method specs --- spec/unit/core/execution_spec.rb | 59 ++++++++++++++++++++++++++++++ spec/unit/util/resolution_spec.rb | 60 ------------------------------- 2 files changed, 59 insertions(+), 60 deletions(-) diff --git a/spec/unit/core/execution_spec.rb b/spec/unit/core/execution_spec.rb index e535f05124..9a6fc38d92 100644 --- a/spec/unit/core/execution_spec.rb +++ b/spec/unit/core/execution_spec.rb @@ -3,6 +3,65 @@ describe Facter::Core::Execution do + describe "#with_env" do + it "should execute the caller's block with the specified env vars" do + test_env = { "LANG" => "C", "FOO" => "BAR" } + Facter::Core::Execution.with_env test_env do + test_env.keys.each do |key| + ENV[key].should == test_env[key] + end + end + end + + it "should restore pre-existing environment variables to their previous values" do + orig_env = {} + new_env = {} + # an arbitrary sentinel value to use to temporarily set the environment vars to + sentinel_value = "Abracadabra" + + # grab some values from the existing ENV (arbitrarily choosing 3 here) + ENV.keys.first(3).each do |key| + # save the original values so that we can test against them later + orig_env[key] = ENV[key] + # create bogus temp values for the chosen keys + new_env[key] = sentinel_value + end + + # verify that, during the 'with_env', the new values are used + Facter::Util::Resolution.with_env new_env do + orig_env.keys.each do |key| + ENV[key].should == new_env[key] + end + end + + # verify that, after the 'with_env', the old values are restored + orig_env.keys.each do |key| + ENV[key].should == orig_env[key] + end + end + + it "should not be affected by a 'return' statement in the yield block" do + @sentinel_var = :resolution_test_foo.to_s + + # the intent of this test case is to test a yield block that contains a return statement. However, it's illegal + # to use a return statement outside of a method, so we need to create one here to give scope to the 'return' + def handy_method() + ENV[@sentinel_var] = "foo" + new_env = { @sentinel_var => "bar" } + + Facter::Util::Resolution.with_env new_env do + ENV[@sentinel_var].should == "bar" + return + end + end + + handy_method() + + ENV[@sentinel_var].should == "foo" + + end + end + describe "#search_paths" do context "on windows", :as_platform => :windows do it "should use the PATH environment variable to determine locations" do diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index 3ac79e0ee2..ed2c76259b 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -51,66 +51,6 @@ res.limit.should == "testing" end - - describe "when overriding environment variables" do - it "should execute the caller's block with the specified env vars" do - test_env = { "LANG" => "C", "FOO" => "BAR" } - Facter::Util::Resolution.with_env test_env do - test_env.keys.each do |key| - ENV[key].should == test_env[key] - end - end - end - - it "should restore pre-existing environment variables to their previous values" do - orig_env = {} - new_env = {} - # an arbitrary sentinel value to use to temporarily set the environment vars to - sentinel_value = "Abracadabra" - - # grab some values from the existing ENV (arbitrarily choosing 3 here) - ENV.keys.first(3).each do |key| - # save the original values so that we can test against them later - orig_env[key] = ENV[key] - # create bogus temp values for the chosen keys - new_env[key] = sentinel_value - end - - # verify that, during the 'with_env', the new values are used - Facter::Util::Resolution.with_env new_env do - orig_env.keys.each do |key| - ENV[key].should == new_env[key] - end - end - - # verify that, after the 'with_env', the old values are restored - orig_env.keys.each do |key| - ENV[key].should == orig_env[key] - end - end - - it "should not be affected by a 'return' statement in the yield block" do - @sentinel_var = :resolution_test_foo.to_s - - # the intent of this test case is to test a yield block that contains a return statement. However, it's illegal - # to use a return statement outside of a method, so we need to create one here to give scope to the 'return' - def handy_method() - ENV[@sentinel_var] = "foo" - new_env = { @sentinel_var => "bar" } - - Facter::Util::Resolution.with_env new_env do - ENV[@sentinel_var].should == "bar" - return - end - end - - handy_method() - - ENV[@sentinel_var].should == "foo" - - end - end - describe "when setting the code" do before do Facter.stubs(:warnonce) From 05ee7ce19d25658fd3783da87c6d374559526790 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 23 Dec 2013 14:49:37 -0800 Subject: [PATCH 1550/3753] (FACT-65) Allow resolutions to be created and looked up by name --- lib/facter/util/fact.rb | 36 +++++++++++++++++++++++++++++++++- lib/facter/util/resolution.rb | 12 +++++++++--- spec/unit/util/fact_spec.rb | 37 +++++++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 4 deletions(-) diff --git a/lib/facter/util/fact.rb b/lib/facter/util/fact.rb index 1158253b8d..845c1a21ec 100644 --- a/lib/facter/util/fact.rb +++ b/lib/facter/util/fact.rb @@ -67,7 +67,41 @@ def add(value = nil, &block) end end - # Flushs any cached values. + # Define a new named resolution or return an existing resolution with + # the given name. + # + # @param resolve_name [String] The name of the resolve to define or look up + # @return [void] + # + # @api public + def define_resolution(resolve_name, &block) + resolve = self.resolution(resolve_name) + + if resolve.nil? + resolve = Facter::Util::Resolution.new(resolve_name) + resolve.instance_eval(&block) if block + @resolves << resolve + else + resolve.instance_eval(&block) if block + end + + rescue => e + Facter.warn "Unable to add resolve #{resolve_name} for fact #{@name}: #{e}" + end + + # Retrieve an existing resolution by name + # + # @param name [String] + # + # @return [Facter::Util::Resolution, nil] The resolution if exists, nil if + # it doesn't exist or name is nil + def resolution(name) + return nil if name.nil? + + @resolves.find { |resolve| resolve.name == name } + end + + # Flushes any cached values. # # @return [void] # diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index c7e6b83dc8..732bf68f17 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -24,8 +24,15 @@ class Facter::Util::Resolution # @api public attr_accessor :timeout + # @!attribute [rw] name + # The name of this resolution. The resolution name should be unique with + # respect to the given fact. + # @return [String] + # @api public + attr_accessor :name + # @api private - attr_accessor :interpreter, :code, :name + attr_accessor :code attr_writer :value, :weight INTERPRETER = Facter::Util::Config.is_windows? ? "cmd.exe" : "/bin/sh" @@ -86,8 +93,7 @@ def has_weight(weight) # Create a new resolution mechanism. # - # @param name [String] The name of the resolution. This is mostly - # unused and resolutions are treated as anonymous. + # @param name [String] The name of the resolution. # @return [void] # # @api private diff --git a/spec/unit/util/fact_spec.rb b/spec/unit/util/fact_spec.rb index c09410061d..a22f3e9f7a 100755 --- a/spec/unit/util/fact_spec.rb +++ b/spec/unit/util/fact_spec.rb @@ -52,6 +52,43 @@ end end + describe "looking up resolutions by name" do + subject(:fact) { described_class.new('yay') } + + it "returns nil if no such resolution exists" do + expect(fact.resolution('nope')).to be_nil + end + + it "never returns anonymous resolutions" do + fact.add() { setcode { 'anonymous' } } + + expect(fact.resolution(nil)).to be_nil + end + end + + describe "adding resolution mechanisms by name" do + subject(:fact) { described_class.new('yay') } + + it "creates a new resolution if no such resolution exists" do + res = stub 'resolution', :name => 'named' + Facter::Util::Resolution.expects(:new).once.with('named').returns(res) + + fact.define_resolution('named') + + expect(fact.resolution('named')).to eq res + end + + it "returns existing resolutions by name" do + res = stub 'resolution', :name => 'named' + Facter::Util::Resolution.expects(:new).once.with('named').returns(res) + + fact.define_resolution('named') + fact.define_resolution('named') + + expect(fact.resolution('named')).to eq res + end + end + it "should be able to return a value" do Facter::Util::Fact.new("yay").should respond_to(:value) end From 1e63a6bf36ebdf1322ea5972f0cdec7cbc30a522 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Wed, 15 Jan 2014 16:15:10 -0800 Subject: [PATCH 1551/3753] (refactor) Extract fact options handling --- lib/facter/util/fact.rb | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/lib/facter/util/fact.rb b/lib/facter/util/fact.rb index 845c1a21ec..203635be1e 100644 --- a/lib/facter/util/fact.rb +++ b/lib/facter/util/fact.rb @@ -26,17 +26,7 @@ class Facter::Util::Fact def initialize(name, options = {}) @name = name.to_s.downcase.intern - # LAK:NOTE: This is slow for many options, but generally we won't have any and at - # worst we'll have one. If we add more, this should be made more efficient. - options.each do |name, value| - case name - when :ldapname - Facter.warnonce("ldapname is deprecated and will be removed in a future version") - self.ldapname = value - else - raise ArgumentError, "Invalid fact option '%s'" % name - end - end + set_options(options) @ldapname ||= @name.to_s @@ -136,6 +126,19 @@ def value end end + def set_options(options) + # LAK:NOTE: This is slow for many options, but generally we won't have any and at + # worst we'll have one. If we add more, this should be made more efficient. + options.each do |name, value| + case name + when :ldapname + Facter.warnonce("ldapname is deprecated and will be removed in a future version") + self.ldapname = value + else + raise ArgumentError, "Invalid fact option '%s'" % name + end + end + end private From 9a02a2fa348cc5621acdf959bfd10c34fe2f1ee2 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Wed, 15 Jan 2014 16:23:46 -0800 Subject: [PATCH 1552/3753] (FACT-207) Simplify Fact options hash evaluation Facts only accept the :ldapname option which is deprecated, and it doesn't look like there will be any other options added. This commit simplifies the options hash parsing and modifies the hash in place so that other objects don't have to know about extra hash options. --- lib/facter/util/fact.rb | 19 +++++++------------ spec/unit/util/fact_spec.rb | 4 ---- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/lib/facter/util/fact.rb b/lib/facter/util/fact.rb index 203635be1e..663c155fbd 100644 --- a/lib/facter/util/fact.rb +++ b/lib/facter/util/fact.rb @@ -26,7 +26,7 @@ class Facter::Util::Fact def initialize(name, options = {}) @name = name.to_s.downcase.intern - set_options(options) + extract_ldapname_option!(options) @ldapname ||= @name.to_s @@ -126,17 +126,12 @@ def value end end - def set_options(options) - # LAK:NOTE: This is slow for many options, but generally we won't have any and at - # worst we'll have one. If we add more, this should be made more efficient. - options.each do |name, value| - case name - when :ldapname - Facter.warnonce("ldapname is deprecated and will be removed in a future version") - self.ldapname = value - else - raise ArgumentError, "Invalid fact option '%s'" % name - end + # @api private + # @deprecated + def extract_ldapname_option!(options) + if options[:ldapname] + Facter.warnonce("ldapname is deprecated and will be removed in a future version") + self.ldapname = options.delete(:ldapname) end end diff --git a/spec/unit/util/fact_spec.rb b/spec/unit/util/fact_spec.rb index a22f3e9f7a..3e2f20eb53 100755 --- a/spec/unit/util/fact_spec.rb +++ b/spec/unit/util/fact_spec.rb @@ -17,10 +17,6 @@ Facter::Util::Fact.new("YayNess", :ldapname => "fooness") end - it "should fail if an unknown option is provided" do - lambda { Facter::Util::Fact.new('yay', :foo => :bar) }.should raise_error(ArgumentError) - end - it "should have a method for adding resolution mechanisms" do Facter::Util::Fact.new("yay").should respond_to(:add) end From 37ac68ed5141ca4c755178607886e62ac4c1d8cd Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 30 Dec 2013 11:12:33 -0800 Subject: [PATCH 1553/3753] (FACT-65) Collections should be able to directly create facts The existing `Collection#add` method would create a new fact and resolution at the same time. This means that facts themselves could not be created and manipulated. This commit adds `#define_fact` so that new facts can be created by themselves. This commit also removes tests inside of the collection for the :ldapname fact option since that's going the way of the dinosaurs. --- lib/facter/util/collection.rb | 30 ++++++++++++++++++++++++++++++ spec/unit/util/collection_spec.rb | 27 +++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/lib/facter/util/collection.rb b/lib/facter/util/collection.rb index 120dd1c97e..76df9495d9 100644 --- a/lib/facter/util/collection.rb +++ b/lib/facter/util/collection.rb @@ -19,8 +19,38 @@ def [](name) value(name) end + # Define a new fact or extend an existing fact. + # + # @param name [Symbol] The name of the fact to define + # @param options [Hash] A hash of options to set on the fact + # + # @return [Facter::Util::Fact] The fact that was defined + def define_fact(name, options = {}, &block) + name = canonicalize(name) + + fact = @facts[name] + + if fact.nil? + fact = Facter::Util::Fact.new(name, options) + @facts[name] = fact + end + + if block_given? + fact.instance_eval(&block) + end + + fact + rescue => e + Facter.warn "Unable to add fact #{name}: #{e}" + end + # Add a resolution mechanism for a named fact. This does not distinguish # between adding a new fact and adding a new way to resolve a fact. + # + # @param name [Symbol] The name of the fact to define + # @param options [Hash] A hash of options to set on the fact and resolution + # + # @return [Facter::Util::Fact] The fact that was defined def add(name, options = {}, &block) name = canonicalize(name) diff --git a/spec/unit/util/collection_spec.rb b/spec/unit/util/collection_spec.rb index 8e211a86be..afca69b352 100755 --- a/spec/unit/util/collection_spec.rb +++ b/spec/unit/util/collection_spec.rb @@ -66,6 +66,33 @@ end end + describe "when only defining facts" do + it "creates a new fact if no such fact exists" do + fact = Facter::Util::Fact.new(:newfact) + Facter::Util::Fact.expects(:new).with(:newfact, {}).returns fact + expect(collection.define_fact(:newfact)).to equal fact + end + + it "returns an existing fact if the fact has already been defined" do + fact = collection.define_fact(:newfact) + expect(collection.define_fact(:newfact)).to equal fact + end + + it "passes options to newly generated facts" do + Facter.stubs(:warnonce) + fact = collection.define_fact(:newfact, :ldapname => 'NewFact') + expect(fact.ldapname).to eq 'NewFact' + end + + it "logs a warning if the fact could not be defined" do + Facter.expects(:warn).with("Unable to add fact newfact: kaboom!") + + collection.define_fact(:newfact) do + raise "kaboom!" + end + end + end + describe "when retrieving facts" do before do @fact = collection.add("YayNess") From dc65014afc5849d2d987cf636c8ee1836491f4d5 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 30 Dec 2013 12:01:45 -0800 Subject: [PATCH 1554/3753] (maint) set resolution properties from an options hash --- lib/facter/util/resolution.rb | 22 ++++++++++++++++++++++ spec/unit/util/resolution_spec.rb | 25 +++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 732bf68f17..c36ecc9376 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -105,6 +105,28 @@ def initialize(name) @weight = nil end + def set_options(options) + ret = {} + + if options[:value] + @value = options.delete(:value) + end + + if options[:timeout] + @timeout = options.delete(:timeout) + end + + if options[:weight] + @weight = options.delete(:weight) + end + + if not options.keys.empty? + raise ArgumentError, "Invalid resolution options #{options.keys.inspect}" + end + + ret + end + # Returns the importance of this resolution. If the weight was not # given, the number of confines is used instead (so that a more # specific resolution wins over a less specific one). diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index ed2c76259b..a6b954e408 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -331,4 +331,29 @@ class FlushFakeError < StandardError; end @resolve.should be_suitable end end + + describe "setting options" do + subject(:resolution) { described_class.new(:foo) } + + it "can set the value" do + resolution.set_options(:value => 'something') + expect(resolution.value).to eq 'something' + end + + it "can set the timeout" do + resolution.set_options(:timeout => 314) + expect(resolution.limit).to eq 314 + end + + it "can set the weight" do + resolution.set_options(:weight => 27) + expect(resolution.weight).to eq 27 + end + + it "fails on unhandled options" do + expect do + resolution.set_options(:foo => 'bar') + end.to raise_error(ArgumentError, /Invalid resolution options.*foo/) + end + end end From 697a3c3e5666ad28e069877f8e304480522902e4 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 30 Dec 2013 12:29:12 -0800 Subject: [PATCH 1555/3753] (maint) decouple option settings on facts and resolutions --- lib/facter/util/collection.rb | 22 ++-------------------- lib/facter/util/resolution.rb | 6 +++--- spec/unit/util/collection_spec.rb | 2 +- 3 files changed, 6 insertions(+), 24 deletions(-) diff --git a/lib/facter/util/collection.rb b/lib/facter/util/collection.rb index 76df9495d9..ea5b95d2f2 100644 --- a/lib/facter/util/collection.rb +++ b/lib/facter/util/collection.rb @@ -60,14 +60,7 @@ def add(name, options = {}, &block) @facts[name] = fact end - # Set any fact-appropriate options. - options.each do |opt, value| - method = opt.to_s + "=" - if fact.respond_to?(method) - fact.send(method, value) - options.delete(opt) - end - end + fact.extract_ldapname_option!(options) if block_given? resolve = fact.add(&block) @@ -77,18 +70,7 @@ def add(name, options = {}, &block) # Set any resolve-appropriate options if resolve - # If the resolve was actually added, set any resolve-appropriate options - options.each do |opt, value| - method = opt.to_s + "=" - if resolve.respond_to?(method) - resolve.send(method, value) - options.delete(opt) - end - end - end - - unless options.empty? - raise ArgumentError, "Invalid facter option(s) %s" % options.keys.collect { |k| k.to_s }.join(",") + resolve.set_options(options) end return fact diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index c36ecc9376..c5c159fb05 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -108,15 +108,15 @@ def initialize(name) def set_options(options) ret = {} - if options[:value] + if options.has_key?(:value) @value = options.delete(:value) end - if options[:timeout] + if options.has_key?(:timeout) @timeout = options.delete(:timeout) end - if options[:weight] + if options.has_key?(:weight) @weight = options.delete(:weight) end diff --git a/spec/unit/util/collection_spec.rb b/spec/unit/util/collection_spec.rb index afca69b352..c77f42944b 100755 --- a/spec/unit/util/collection_spec.rb +++ b/spec/unit/util/collection_spec.rb @@ -46,7 +46,7 @@ describe "and a block is provided" do it "should use the block to add a resolution to the fact" do - fact = mock 'fact' + fact = mock 'fact', :extract_ldapname_option! => nil Facter::Util::Fact.expects(:new).returns fact fact.expects(:add) From 6f7406215b93057afb882b3157357985a3f7fb5c Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Wed, 15 Jan 2014 16:33:02 -0800 Subject: [PATCH 1556/3753] (maint) Reuse fact creation logic in collection --- lib/facter/util/collection.rb | 34 +++++++++++++++---------------- spec/unit/util/collection_spec.rb | 5 +++-- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/lib/facter/util/collection.rb b/lib/facter/util/collection.rb index ea5b95d2f2..abf83b1e91 100644 --- a/lib/facter/util/collection.rb +++ b/lib/facter/util/collection.rb @@ -26,14 +26,7 @@ def [](name) # # @return [Facter::Util::Fact] The fact that was defined def define_fact(name, options = {}, &block) - name = canonicalize(name) - - fact = @facts[name] - - if fact.nil? - fact = Facter::Util::Fact.new(name, options) - @facts[name] = fact - end + fact = create_or_return_fact(name, options) if block_given? fact.instance_eval(&block) @@ -52,15 +45,7 @@ def define_fact(name, options = {}, &block) # # @return [Facter::Util::Fact] The fact that was defined def add(name, options = {}, &block) - name = canonicalize(name) - - unless fact = @facts[name] - fact = Facter::Util::Fact.new(name) - - @facts[name] = fact - end - - fact.extract_ldapname_option!(options) + fact = create_or_return_fact(name, options) if block_given? resolve = fact.add(&block) @@ -157,6 +142,21 @@ def value(name) private + def create_or_return_fact(name, options) + name = canonicalize(name) + + fact = @facts[name] + + if fact.nil? + fact = Facter::Util::Fact.new(name, options) + @facts[name] = fact + else + fact.extract_ldapname_option!(options) + end + + fact + end + def canonicalize(name) name.to_s.downcase.to_sym end diff --git a/spec/unit/util/collection_spec.rb b/spec/unit/util/collection_spec.rb index c77f42944b..441497817d 100755 --- a/spec/unit/util/collection_spec.rb +++ b/spec/unit/util/collection_spec.rb @@ -32,7 +32,7 @@ it "should set appropriate options on the resolution instance" do fact = Facter::Util::Fact.new(:myname) - Facter::Util::Fact.expects(:new).with(:myname).returns fact + Facter::Util::Fact.expects(:new).with(:myname, {:timeout => 'myval'}).returns fact resolve = Facter::Util::Resolution.new(:myname) {} fact.expects(:add).returns resolve @@ -46,7 +46,8 @@ describe "and a block is provided" do it "should use the block to add a resolution to the fact" do - fact = mock 'fact', :extract_ldapname_option! => nil + fact = mock 'fact' + fact.stubs(:extract_ldapname_option!) Facter::Util::Fact.expects(:new).returns fact fact.expects(:add) From 23345ebf840b9a74a5e5823c6d63314155f403e0 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 13 Jan 2014 14:48:23 -0800 Subject: [PATCH 1557/3753] (FACT-65) Allow resolutions to specify their name as an option --- lib/facter/util/resolution.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index c5c159fb05..4129309413 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -106,7 +106,9 @@ def initialize(name) end def set_options(options) - ret = {} + if options[:name] + @name = options.delete(:name) + end if options.has_key?(:value) @value = options.delete(:value) @@ -123,8 +125,6 @@ def set_options(options) if not options.keys.empty? raise ArgumentError, "Invalid resolution options #{options.keys.inspect}" end - - ret end # Returns the importance of this resolution. If the weight was not From 5e3d6f35bd734b68d76469bb7a859d85f6f0f182 Mon Sep 17 00:00:00 2001 From: Melissa Stone Date: Thu, 16 Jan 2014 14:58:57 -0800 Subject: [PATCH 1558/3753] (packaging) Update FACTERVERSION to 1.7.5-rc1 --- lib/facter/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/version.rb b/lib/facter/version.rb index ff609b8924..7cfddf010d 100644 --- a/lib/facter/version.rb +++ b/lib/facter/version.rb @@ -1,6 +1,6 @@ module Facter if not defined? FACTERVERSION then - FACTERVERSION = '1.7.4' + FACTERVERSION = '1.7.5-rc1' end ## From e09d34ca9e373c5bc29b9fcb3c5b78ac87a01536 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Thu, 16 Jan 2014 20:07:55 -0800 Subject: [PATCH 1559/3753] (maint) Fix one more dmidecode stub in a test only on master --- spec/unit/virtual_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index edfa8670f5..2968c4283d 100755 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -182,7 +182,7 @@ it "should be kvm with Bochs vendor name from dmidecode" do Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) - Facter::Util::Resolution.stubs(:exec).with('dmidecode').returns("Manufacturer: Bochs") + Facter::Util::Resolution.stubs(:exec).with('dmidecode 2> /dev/null').returns("Manufacturer: Bochs") Facter.fact(:virtual).value.should == "kvm" end From d93d68400b6905558a2107a9fe8c7a90704fbd28 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Fri, 17 Jan 2014 16:27:02 -0600 Subject: [PATCH 1560/3753] (FACT-186) Pessimistic version binding gem dependencies This loosens the Win32 platform gems and CFPropertyList to allow the third version number (patch) to be determined at install time. This allows better compatibility as we make future updates to dependency versions. Paired with Josh Cooper --- ext/project_data.yaml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ext/project_data.yaml b/ext/project_data.yaml index 1a19a12047..3dba3c29d6 100644 --- a/ext/project_data.yaml +++ b/ext/project_data.yaml @@ -16,16 +16,16 @@ gem_default_executables: 'facter' gem_platform_dependencies: universal-darwin: gem_runtime_dependencies: - CFPropertyList: '2.2.6' + CFPropertyList: '~> 2.2.6' x86-mingw32: gem_runtime_dependencies: - ffi: '1.9.0' - sys-admin: '1.5.6' - win32-api: '1.4.8' - win32-dir: '0.4.3' - windows-api: '0.4.2' - windows-pr: '1.2.2' - win32console: '1.3.2' + ffi: '~> 1.9.0' + sys-admin: '~> 1.5.6' + win32-api: '~> 1.4.8' + win32-dir: '~> 0.4.3' + windows-api: '~> 0.4.2' + windows-pr: '~> 1.2.2' + win32console: '~> 1.3.2' bundle_platforms: universal-darwin: ruby x86-mingw32: mingw From 07c8f64b84012841f1f5570ec1e7f85c0c93c27a Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Sat, 18 Jan 2014 08:05:01 -0800 Subject: [PATCH 1561/3753] Restructure so that library interface functions map those of facter (kinda) --- .gitignore | 1 + Makefile | 12 +- cfacter.cc | 65 +--- cfacterimpl.cc | 940 +++++++++++++++++++++++++++++++++++++++++++++++ cfacterimpl.h | 24 ++ cfacterlib.cc | 968 ++++--------------------------------------------- cfacterlib.h | 36 +- 7 files changed, 1059 insertions(+), 987 deletions(-) create mode 100644 cfacterimpl.cc create mode 100644 cfacterimpl.h diff --git a/.gitignore b/.gitignore index b736df2351..801930fe09 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +*.swp *.so *.o *~ diff --git a/Makefile b/Makefile index bb633953c8..272d8c042c 100644 --- a/Makefile +++ b/Makefile @@ -9,15 +9,21 @@ ifeq ($(UNAME_S),Darwin) CCFLAGS += -Wno-tautological-constant-out-of-range-compare endif -cfacter: cfacter.cc cfacterlib.cc cfacterlib.h - g++ ${CCFLAGS} -o cfacter cfacter.cc cfacterlib.cc -I C +cfacter: cfacter.cc cfacterlib.cc cfacterimpl.cc cfacterlib.h cfacterimpl.h + g++ ${CCFLAGS} -o cfacter cfacter.cc cfacterlib.cc cfacterimpl.cc -I C -cfacterlib.o: cfacterlib.cc cfacterlib.h +cfacterlib.o: cfacterlib.cc cfacterlib.h cfacterimpl.h g++ ${CCFLAGS} -fPIC -c -o $@ cfacterlib.cc -I . +cfacterimpl.o: cfacterimpl.cc cfacterimpl.h + g++ ${CCFLAGS} -fPIC -c -o $@ cfacterimpl.cc -I . + cfacterlib.so: cfacterlib.o g++ ${CCFLAGS} -o $@ $^ -fPIC -shared +clean: + -rm cfacterlib.o cfacterimpl.o cfacterlib.so cfacter 2> /dev/null + missing: -$(shell facter | grep "=>" | cut -f1 -d' ' | sort > /tmp/facter.txt) -$(shell ./cfacter | grep "=>" | cut -f1 -d' ' | sort > /tmp/cfacter.txt) diff --git a/cfacter.cc b/cfacter.cc index c5057a5078..06002b15ff 100644 --- a/cfacter.cc +++ b/cfacter.cc @@ -1,65 +1,18 @@ #include "cfacterlib.h" #include -#include -#include -#include - -#include "rapidjson/document.h" -#include "rapidjson/prettywriter.h" -#include "rapidjson/stringbuffer.h" using namespace std; int main(int argc, char **argv) { - std::map facts; - facts["facterversion"] = "3.0.0"; - - list external_directories; - external_directories.push_back("/etc/facter/facts.d"); - - get_network_facts(facts); - get_kernel_facts(facts); - get_blockdevice_facts(facts); - get_operatingsystem_facts(facts); - get_uptime_facts(facts); - get_virtual_facts(facts); - get_hardwired_facts(facts); - get_misc_facts(facts); - get_ruby_lib_versions(facts); - get_mem_facts(facts); - get_selinux_facts(facts); - get_ssh_facts(facts); - get_processor_facts(facts); - get_architecture_facts(facts); - get_dmidecode_facts(facts); - get_filesystems_facts(facts); - get_hostname_facts(facts); - get_external_facts(facts, external_directories); - - if (0) { - typedef map::iterator iter; - for (iter i = facts.begin(); i != facts.end(); ++i) { - cout << i->first << " => " << i->second << endl; - } - } - else { - rapidjson::Document json; - json.SetObject(); - - rapidjson::Document::AllocatorType& allocator = json.GetAllocator(); - - typedef map::iterator iter; - for (iter i = facts.begin(); i != facts.end(); ++i) { - json.AddMember(i->first.c_str(), i->second.c_str(), allocator); + loadfacts(); + + #define MAX_LEN_FACTS_JSON_STRING (1024 * 1024) // go crazy here + char facts_json[MAX_LEN_FACTS_JSON_STRING]; + if (to_json(facts_json, MAX_LEN_FACTS_JSON_STRING) < 0) { + cout << "Wow, that's a lot of facts" << endl; + exit(1); } - - rapidjson::StringBuffer buf; - rapidjson::Writer writer(buf); - json.Accept(writer); - - cout << buf.GetString() << endl; - } - exit(0); -} + cout << facts_json << endl; +} diff --git a/cfacterimpl.cc b/cfacterimpl.cc new file mode 100644 index 0000000000..18bb3922d2 --- /dev/null +++ b/cfacterimpl.cc @@ -0,0 +1,940 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef __linux__ +#include +#endif +#ifdef __APPLE__ +#include +#endif +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cfacterlib.h" +#include "cfacterimpl.h" + +using namespace std; + +// For case-insensitive strings, define ci_string +// Thank you, Herb Sutter: http://www.gotw.ca/gotw/029.htm +// +struct ci_char_traits : public char_traits +// just inherit all the other functions +// that we don't need to override +{ + static bool eq( char c1, char c2 ) + { return toupper(c1) == toupper(c2); } + + static bool ne( char c1, char c2 ) + { return toupper(c1) != toupper(c2); } + + static bool lt( char c1, char c2 ) + { return toupper(c1) < toupper(c2); } + + static int compare( const char* s1, + const char* s2, + size_t n ) { + return strncasecmp( s1, s2, n ); + // if available on your compiler, + // otherwise you can roll your own + } + + static const char* + find( const char* s, int n, char a ) { + while( n-- > 0 && toupper(*s) != toupper(a) ) { + ++s; + } + return s; + } +}; + +typedef basic_string ci_string; + +// trim from start +static inline std::string <rim(std::string &s) { + s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun(std::isspace)))); + return s; +} + +// trim from end +static inline std::string &rtrim(std::string &s) { + s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun(std::isspace))).base(), s.end()); + return s; +} + +// trim from both ends +static inline std::string &trim(std::string &s) { + return ltrim(rtrim(s)); +} + +static inline void tokenize(std::string &s, vector &tokens) { + istringstream iss(s); + copy(istream_iterator(iss), + istream_iterator(), + back_inserter >(tokens)); +} + +static inline void split(const string &s, char delim, vector &elems) { + stringstream ss(s); + string item; + while (getline(ss, item, delim)) { + elems.push_back(item); + } +} + +static bool file_exist (string filename) +{ + struct stat buffer; + return stat (filename.c_str(), &buffer) == 0; +} + +// handy for some /proc and /sys files +string read_oneline_file(const string file_path) +{ + std::ifstream oneline_file(file_path.c_str(), std::ifstream::in); + std::string line; + std::getline(oneline_file, line); + return line; +} + +void get_network_facts(fact_map& facts) +{ + struct ifreq *ifr; + struct ifconf ifc; + int s, i; + int numif; + + // find number of interfaces. + memset(&ifc, 0, sizeof(ifc)); + ifc.ifc_ifcu.ifcu_req = NULL; + ifc.ifc_len = 0; + + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + perror("socket"); + exit(1); + } + + if (ioctl(s, SIOCGIFCONF, &ifc) < 0) { + perror("ioctl"); + exit(1); + } + + if ((ifr = static_cast(malloc(ifc.ifc_len))) == NULL) { + perror("malloc"); + exit(1); + } + ifc.ifc_ifcu.ifcu_req = ifr; + + if (ioctl(s, SIOCGIFCONF, &ifc) < 0) { + perror("ioctl SIOCGIFCONF"); + exit(1); + } + + string interfaces = ""; + + bool primaryInterfacePrinted = false; + numif = ifc.ifc_len / sizeof(struct ifreq); + for (i = 0; i < numif; i++) { + struct ifreq *r = &ifr[i]; + struct in_addr ip_addr = ((struct sockaddr_in *)&r->ifr_addr)->sin_addr; + + // no idea what the real algorithm is to identify the unmarked + // interface, i.e. the one that facter reports as just 'ipaddress' + // here just take the first one that's not 'lo' + bool primaryInterface = false; + if (!primaryInterfacePrinted && strcmp(r->ifr_name, "lo")) { + // this is the chosen interface + primaryInterface = true; + primaryInterfacePrinted = true; + } + + // build up 'interfaces' fact as we go, appending comma after all but last + interfaces += r->ifr_name; + if (i < numif - 1) + interfaces += ","; + + const char *ipaddress = inet_ntoa(ip_addr); + facts[string("ipaddress_") + r->ifr_name] = ipaddress; + if (primaryInterface) + facts["ipaddress"] = ipaddress; + + // mtu + if (ioctl(s, SIOCGIFMTU, r) < 0) { + perror("ioctl SIOCGIFMTU"); + exit(1); + } + + facts[string("mtu_") + r->ifr_name] = to_string(r->ifr_mtu); + if (primaryInterface) + ; // no unmarked version of this network fact + + // netmask and network are both derived from the same ioctl + if (ioctl(s, SIOCGIFNETMASK, r) < 0) { + perror("ioctl SIOCGIFNETMASK"); + exit(1); + } + +#ifndef __APPLE__ // ifr_netmask isn't supported, might need to use SC library + // netmask + struct in_addr netmask_addr = ((struct sockaddr_in *)&r->ifr_netmask)->sin_addr; + const char *netmask = inet_ntoa(netmask_addr); + facts[string("netmask_") + r->ifr_name] = netmask; + if (primaryInterface) + facts["netmask"] = netmask; + + // mess of casting to get the network address + struct in_addr network_addr; + network_addr.s_addr = + (in_addr_t) uint32_t(netmask_addr.s_addr) & uint32_t(ip_addr.s_addr); + string network = inet_ntoa(network_addr); + facts[string("network_") + r->ifr_name] = network; + if (primaryInterface) + facts["network"] = network; +#endif + +#ifndef __APPLE__ // SIOCGIFHWADDR isn't supported, might need to use SC library + // and the mac address (but not for loopback) + if (strcmp(r->ifr_name, "lo")) { + if (ioctl(s, SIOCGIFHWADDR, r) < 0) { + perror("ioctl SIOCGIFHWADDR"); + exit(1); + } + + // extract mac into a string, okay a char array + uint8_t *mac_bytes = (uint8_t *)((sockaddr *)&r->ifr_hwaddr)->sa_data; + char mac_address[18]; + sprintf(mac_address, "%02x:%02x:%02x:%02x:%02x:%02x", + mac_bytes[0], mac_bytes[1], mac_bytes[2], + mac_bytes[3], mac_bytes[4], mac_bytes[5]); + + // and get it out + facts[string("macaddress_") + r->ifr_name] = mac_address; + if (primaryInterface) + facts["macaddress"] = mac_address; + } +#endif + } + + facts["interfaces"] = interfaces; + + close(s); + free(ifr); +} + +void get_kernel_facts(fact_map& facts) +{ +#ifdef __linux__ + // this is linux-only, so there you have it + facts["kernel"] = "Linux"; + string kernelrelease = read_oneline_file("/proc/sys/kernel/osrelease"); + facts["kernelrelease"] = kernelrelease; + string kernelversion = kernelrelease.substr(0, kernelrelease.find("-")); + facts["kernelversion"] = kernelversion; + string kernelmajversion = kernelversion.substr(0, kernelversion.rfind(".")); + facts["kernelmajversion"] = kernelmajversion; +#else +#ifdef __APPLE__ + facts["kernel"] = "Darwin"; +#endif +#endif +} + +static void get_lsb_facts(fact_map& facts) +{ + std::ifstream lsb_release_file("/etc/lsb-release", std::ifstream::in); + std::string line; + while (std::getline(lsb_release_file, line)) { + unsigned sep = line.find("="); + string key = line.substr(0, sep); + string value = line.substr(sep + 1, string::npos); + + if (key == "DISTRIB_ID") { + facts["lsbdistid"] = value; + facts["operatingsystem"] = value; + facts["osfamily"] = "Debian"; + } + else if (key == "DISTRIB_RELEASE") { + facts["lsbdistrelease"] = value; + facts["operatingsystemrelease"] = value; + facts["lsbmajdistrelease"] = value.substr(0, value.find(".")); + } + else if (key == "DISTRIB_CODENAME") + facts["lsbdistcodename"] = value; + else if (key == "DISTRIB_DESCRIPTION") + facts["lsbdistdescription"] = value; + } +} + +// gonna need to pick a regex library to do os facts rights given all the variants +// for now, just fedora ;> + +static void get_redhat_facts(fact_map& facts) +{ + if (file_exist("/etc/redhat-release")) { + facts["osfamily"] = "RedHat"; + string redhat_release = read_oneline_file("/etc/redhat-release"); + vector tokens; + tokenize(redhat_release, tokens); + if (tokens.size() >= 2 && tokens[0] == "Fedora" && tokens[1] == "release") { + facts["operatingsystem"] = "Fedora"; + if (tokens.size() >= 3) { + facts["operatingsystemrelease"] = tokens[2]; + facts["operatingsystemmajrelease"] = tokens[2]; + } + } + else + facts["operatingsystem"] = "RedHat"; + } +} + +void get_operatingsystem_facts(fact_map& facts) +{ + get_lsb_facts(facts); + get_redhat_facts(facts); +} + +void get_uptime_facts(fact_map& facts) +{ + string uptime = read_oneline_file("/proc/uptime"); + unsigned int uptime_seconds; + sscanf(uptime.c_str(), "%ud", &uptime_seconds); + unsigned int uptime_hours = uptime_seconds / 3600; + unsigned int uptime_days = uptime_hours / 24; + facts["uptime_seconds"] = to_string(uptime_seconds); + facts["uptime_hours"] = to_string(uptime_hours); + facts["uptime_days"] = to_string(uptime_days); + facts["uptime"] = to_string(uptime_days) + " days"; +} + +string popen_stdout(string cmd) +{ + FILE *cmd_fd = popen(cmd.c_str(), "r"); + string cmd_output = ""; + char buf[1024]; + size_t bytesRead; + while ((bytesRead = fread(buf, 1, sizeof(buf) - 1, cmd_fd))) { + buf[bytesRead] = 0; + cmd_output += buf; + } + pclose(cmd_fd); + return cmd_output; +} + +void get_virtual_facts(fact_map& facts) +{ + // poked at the real facter's virtual support, some combo of file existence + // plus lspci plus dmidecode + + // instead of parsing all of lspci, how about looking for vendors in lspci -n? + // or walking the /sys/bus/pci/devices files. or don't sweat it, the total + // lspci time is ~40 ms. + + // virtual could be discovered in lots of places so requires some special handling + + facts["is_virtual"] = "false"; + facts["virtual"] = "physical"; + +} + +// placeholders for some hardwired facts, cuz not sure what to do with them +void get_hardwired_facts(fact_map& facts) +{ + facts["ps"] = "ps -ef"; // what is this? + facts["uniqueid"] = "007f0101"; // ?? +} + + +// versions of things we don't have if we're not running ruby +// omit or 'undef' or ...? for now, omit but collect them here +void get_ruby_lib_versions(fact_map& facts) +{ + /* + facts["puppetversion => undef"; + facts["augeasversion => undef"; + facts["rubysitedir => undef"; + facts["rubyversion => undef"; + */ +} + +// block devices +void get_blockdevice_facts(fact_map& facts) +{ + string blockdevices = ""; + + DIR *sys_block_dir = opendir("/sys/block"); + if (sys_block_dir == NULL) { + return; + } + + struct dirent *bd; + while ((bd = readdir(sys_block_dir))) { + + bool real_block_device = false; + string device_dir_path = "/sys/block/"; + device_dir_path += bd->d_name; + + DIR *device_dir = opendir(device_dir_path.c_str()); + struct dirent *subdir; + while ((subdir = readdir(device_dir))) { + if (strcmp(subdir->d_name, "device") == 0) { + // we have a winner + real_block_device = true; + break; + } + } + + if (!real_block_device) continue; + + // add it to the blockdevices list, careful with the comma + if (!blockdevices.empty()) + blockdevices += ","; + blockdevices += bd->d_name; + + string model_file = "/sys/block/" + string(bd->d_name) + "/device/model"; + facts[string("blockdevice_") + bd->d_name + "_model"] = + read_oneline_file(model_file); + + string vendor_file = "/sys/block/" + string(bd->d_name) + "/device/vendor"; + facts[string("blockdevice_") + bd->d_name + "_vendor"] = + read_oneline_file(vendor_file); + + string size_file = "/sys/block/" + string(bd->d_name) + "/size"; + string size_line = read_oneline_file(size_file); + int64_t size; + // SCNd64 didn't work here?? + sscanf(size_line.c_str(), "%lld", (long long int *)&size); + facts[string("blockdevice_") + bd->d_name + "_size"] = to_string(size * 512); + } + + facts["blockdevices"] = blockdevices; +} + +void get_misc_facts(fact_map& facts) +{ + facts["path"] = getenv("PATH"); + string whoami = popen_stdout("whoami"); + facts["id"] = trim(whoami); + + //timezone + char tzstring[16]; + time_t t = time(NULL); + struct tm *loc = localtime(&t); + strftime(tzstring, sizeof(tzstring - 1), "%Z", loc); + facts["timezone"] = tzstring; +} + +// get just one fact, optionally in two formats +static void get_mem_fact(std::string fact_name, int fact_value, fact_map& facts, + bool get_mb_variant = true) +{ + float fact_value_scaled = fact_value / 1024.0; + char float_buf[32]; + + if (get_mb_variant) { + snprintf(float_buf, sizeof(float_buf) - 1, "%.2f", fact_value_scaled); + facts[string(fact_name) + "_mb"] = float_buf; + } + + int scale_index; + for (scale_index = 0; + fact_value_scaled > 1024.0; + fact_value_scaled /= 1024.0, ++scale_index) ; + + std::string scale[4] = {"MB", "GB", "TB", "PB"}; // oh yeah, petabytes ... + + snprintf(float_buf, sizeof(float_buf) - 1, "%.2f", fact_value_scaled); + facts[fact_name] = string(float_buf) + scale[scale_index]; +} + +void get_mem_facts(fact_map& facts) +{ + std::ifstream oneline_file("/proc/meminfo", std::ifstream::in); + std::string line; + + // The MemFree fact is the sum of MemFree + Buffer + Cached from /proc/meninfo, + // so sum that one as we go, and get it out at the end. + // The other three memory facts are straight from /proc/meminfo, so get those + // as we go. + // All four facts are geted in two formats: + // _mb => %.2f + // => %.2f %s (where the suffix string is one of MB/GB/TB) + // And then there is a ninth fact, 'memorytotal', which is the same as 'memorysize'. + // + + // NB: this all assumes that all values are in KB. + + unsigned int memoryfree = 0; + + while (std::getline(oneline_file, line)) { + vector tokens; + tokenize(line, tokens); + if (tokens.size() < 3) continue; // should never happen + + if (tokens[0] == "MemTotal:") { + int mem_total = atoi(tokens[1].c_str()); + get_mem_fact("memorysize", mem_total, facts); + get_mem_fact("memorytotal", mem_total, facts, false); + } + else if (tokens[0] == "MemFree:" || tokens[0] == "Cached:" || tokens[0] == "Buffers:") { + memoryfree += atoi(tokens[1].c_str()); + } + else if (tokens[0] == "SwapTotal:") + get_mem_fact("swapsize", atoi(tokens[1].c_str()), facts); + else if (tokens[0] == "SwapFree:") + get_mem_fact("swapfree", atoi(tokens[1].c_str()), facts); + } + + get_mem_fact("memoryfree", memoryfree, facts); +} + +static string get_selinux_path() +{ + static string selinux_path = ""; + static bool inited = false; + + if (inited) + return selinux_path; + + std::ifstream mounts("/proc/self/mounts", std::ifstream::in); + std::string line; + + while (std::getline(mounts, line)) { + vector tokens; + tokenize(line, tokens); + if (tokens.size() < 2) continue; + if (tokens[0] != "selinuxfs") continue; + + selinux_path = tokens[1]; + break; + } + + inited = true; + + return selinux_path; +} + +static bool selinux() +{ + string selinux_path = get_selinux_path(); + if (selinux_path.empty()) + return false; + + string selinux_enforce_path = selinux_path + "/enforce"; + string security_attr_path = "/proc/self/attr/current"; + if (file_exist(selinux_enforce_path) && file_exist(security_attr_path) && + read_oneline_file(security_attr_path) != "kernel") + return true; + + return false; +} + +void get_selinux_facts(fact_map& facts) +{ + if (!selinux()) { + facts["selinux"] = "false"; + return; + } + + facts["selinux"] = "true"; + + // defaults from facter + facts["selinux_enforced"] = "false"; + facts["selinux_policyversion"] = "unknown"; + facts["selinux_current_mode"] = "unknown"; + facts["selinux_config_mode"] = "unknown"; + facts["selinux_config_policy"] = "unknown"; + facts["selinux_mode"] = "unknown"; + + string selinux_path = get_selinux_path(); + + string selinux_enforce_path = selinux_path + "/enforce"; + if (file_exist(selinux_enforce_path)) + facts["selinux_enforced"] = ((read_oneline_file(selinux_enforce_path) == "1") ? "true" : "false"); + + string selinux_policyvers_path = selinux_path + "/policyvers"; + if (file_exist(selinux_policyvers_path)) + facts["selinux_policyversion"] = read_oneline_file(selinux_policyvers_path); + + string selinux_cmd = "/usr/sbin/sestatus"; + FILE* pipe = popen(selinux_cmd.c_str(), "r"); + if (!pipe) return; + + char buffer[512]; // seems like a lot, but there's no constant available + while (!feof(pipe)) { + if (fgets(buffer, 128, pipe) != NULL) { + vector elems; + split(buffer, ':', elems); + if (elems.size() < 2) continue; // shouldn't happen + if (elems[0] == "Current mode") { + facts["selinux_current_mode"] = trim(elems[1]); + } + else if (elems[0] == "Mode from config file") { + facts["selinux_config_mode"] = trim(elems[1]); + } + else if (elems[0] == "Policy from config file") { + facts["selinux_config_policy"] = trim(elems[1]); + facts["selinux_mode"] = trim(elems[1]); + } + } + } + + pclose(pipe); +} + +static void get_ssh_fact(string fact_name, string path_name, fact_map& facts) +{ + string ssh_directories[] = { + "/etc/ssh", + "/usr/local/etc/ssh", + "/etc", + "/usr/local/etc", + "/etc/opt/ssh", + }; + + for (int i = 0; i < sizeof(ssh_directories) / sizeof(string); ++i) { + string full_path = ssh_directories[i] + "/" + path_name; + if (file_exist(full_path)) { + string key = read_oneline_file(full_path); + vector tokens; + tokenize(trim(key), tokens); + if (tokens.size() < 2) continue; // should never happen + facts[fact_name] = tokens[1]; + + // skpping the finger print facts, which require base64 decode and sha libs + // on the cmd line it would be something like the result of these two: + // "cat " + full_path + " | cut -d' ' -f 2 | base64 -d - | sha256sum - | cut -d' ' -f 1" + // "cat " + full_path + " | cut -d' ' -f 2 | base64 -d - | sha1sum - | cut -d' ' -f 1" + + break; + } + } +} + +// no support for the sshfp facts, which require base64/sha1sum code +void get_ssh_facts(fact_map& facts) +{ + // not til C++11 do we have static initialization of stl maps + map ssh_facts; + ssh_facts["sshdsakey"] = "ssh_host_dsa_key.pub"; + ssh_facts["sshrsakey"] = "ssh_host_rsa_key.pub"; + ssh_facts["sshecdsakey"] = "ssh_host_ecdsa_key.pub"; + + typedef map::iterator iter; + for (iter i = ssh_facts.begin(); i != ssh_facts.end(); ++i) { + get_ssh_fact(i->first, i->second, facts); + } +} + +static void get_physicalprocessorcount_fact(fact_map& facts) +{ + // So, facter has logic to use /sys and fallback to /proc + // but I don't know why the /sys support was added; research needed. + // Since sys is the default, just reproduce that logic for now. + + string sysfs_cpu_directory = "/sys/devices/system/cpu"; + vector package_ids; + if (file_exist(sysfs_cpu_directory)) { + for (int i = 0; ; i++) { + char buf[10]; + snprintf(buf, sizeof(buf) - 1, "%u", i); + string cpu_phys_file = sysfs_cpu_directory + "/cpu" + buf + "/topology/physical_package_id"; + if (!file_exist(cpu_phys_file)) + break; + + package_ids.push_back(read_oneline_file(cpu_phys_file)); + } + + sort(package_ids.begin(), package_ids.end()); + unique(package_ids.begin(), package_ids.end()); + facts["physicalprocessorcount"] = to_string(package_ids.size()); + } + else { + // here's where the fall back to /proc/cpuinfo would go + } +} + +void get_processorcount_fact(fact_map& facts) +{ + std::ifstream cpuinfo_file("/proc/cpuinfo", std::ifstream::in); + std::string line; + int processor_count = 0; + string current_processor_number; + while (std::getline(cpuinfo_file, line)) { + unsigned sep = line.find(":"); + string tmp = line.substr(0, sep); + string key = trim(tmp); + + if (key == "processor") { + ++processor_count; + string tmp = line.substr(sep + 1, string::npos); + current_processor_number = trim(tmp); + } + else if (key == "model name") { + string tmp = line.substr(sep + 1, string::npos); + facts[string("processor") + current_processor_number] = trim(tmp); + } + } + // this was added after 1.7.3, omit for now, needs investigation + if (false) facts["activeprocessorcount"] = processor_count; + facts["processorcount"] = to_string(processor_count); +} + +void get_processor_facts(fact_map& facts) +{ + get_physicalprocessorcount_fact(facts); + get_processorcount_fact(facts); +} + +void get_architecture_facts(fact_map& facts) +{ + struct utsname uts; + if (uname(&uts) == 0) { + // This is cheating at some level because these are all the same on x86_64 linux. + // Otoh, some of these may be compiled-in for a C version. And then if facter + // relies on 'uname -p' here and that commonizes, this should perhaps just shell out + // and not reproduce that logic. Regardless, need to survey cross-platform here and + // take it from there. + facts["hardwaremodel"] = uts.machine; + facts["hardwareisa"] = uts.machine; + facts["architecture"] = uts.machine; + } +} + +void get_dmidecode_facts(fact_map& facts) +{ + string dmidecode_output = popen_stdout("/usr/sbin/dmidecode"); + std::stringstream ss(dmidecode_output); + string line; + + enum { + bios_information, + base_board_information, + system_information, + chassis_information, + unknown + } dmi_section = unknown; + + while (std::getline(ss, line)) { + if (line.empty()) continue; + + // enable case-insensitive compares + ci_string ci_line = line.c_str(); + + // identify the dmi section, they all begin at the beginning of a line + // and there are only a handful of interest to us + if (ci_line == "BIOS Information") { + dmi_section = bios_information; + continue; + } + else if (ci_line == "Base Board Information") { + dmi_section = base_board_information; + continue; + } + else if (ci_line == "System Information") { + dmi_section = system_information; + continue; + } + else if (ci_line == "Chassis Information" || ci_line == "system enclosure or chassis") { + dmi_section = chassis_information; + continue; + } + else if (ci_line[0] >= 'A' && ci_line[0] <= 'Z') { + dmi_section = unknown; + continue; + } + + // if we're in the middle of an unknown section, skip + if (dmi_section == unknown) continue; + + size_t sep = line.find(":"); + if (sep != string::npos) { + string tmp = line.substr(0, sep); + string key = trim(tmp); + if (dmi_section == bios_information) { + ci_string ci_key = key.c_str(); + if (ci_key == "vendor") { + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); + facts["bios_vendor"] = value; + } + if (ci_key == "version") { + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); + facts["bios_version"] = value; + } + if (ci_key == "release date") { + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); + facts["bios_release_date"] = value; + } + } + else if (dmi_section == base_board_information) { + ci_string ci_key = key.c_str(); + if (ci_key == "manufacturer") { + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); + facts["boardmanufacturer"] = value; + } + if (ci_key == "product name" || ci_key == "product") { + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); + facts["boardproductname"] = value; + } + if (ci_key == "serial number") { + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); + facts["boardserialnumber"] = value; + } + } + else if (dmi_section == system_information) { + ci_string ci_key = key.c_str(); + if (ci_key == "manufacturer") { + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); + facts["manufacturer"] = value; + } + if (ci_key == "product name" || ci_key == "product") { + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); + facts["productname"] = value; + } + if (ci_key == "serial number") { + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); + facts["serialnumber"] = value; + } + if (ci_key == "uuid") { + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); + facts["uuid"] = value; + } + } + else if (dmi_section == chassis_information) { + ci_string ci_key = key.c_str(); + if (ci_key == "chassis type" || ci_key == "type") { + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); + facts["type"] = value; + } + } + } + } +} + +void get_filesystems_facts(fact_map& facts) +{ + std::ifstream cpuinfo_file("/proc/filesystems", std::ifstream::in); + std::string line; + string filesystems = ""; + while (std::getline(cpuinfo_file, line)) { + if (line.find("nodev") != string::npos || line.find("fuseblk") != string::npos) + continue; + + if (!filesystems.empty()) + filesystems += ","; + + filesystems += trim(line); + } + facts["filesystems"] = filesystems; +} + +void get_hostname_facts(fact_map& facts) +{ + // there's some history here, perhaps just port the facter conditional straight across? + // so this is short-term + string hostname_output = popen_stdout("hostname"); + unsigned sep = hostname_output.find("."); + string hostname1 = hostname_output.substr(0, sep); + string hostname = trim(hostname1); + + ifstream resolv_conf_file("/etc/resolv.conf", std::ifstream::in); + string line; + string domain; + string search; + while (std::getline(resolv_conf_file, line)) { + vector elems; + tokenize(line, elems); + if (elems.size() >= 2) { + if (elems[0] == "domain") + domain = trim(elems[1]); + else if (elems[0] == "search") + search = trim(elems[1]); + } + } + if (domain.empty() && !search.empty()) + domain = search; + + facts["hostname"] = hostname; + facts["domain"] = domain; + facts["fqdn"] = hostname + "." + domain; +} + +static void get_external_facts_from_executable(fact_map& facts, string executable) +{ + // executable + FILE *stdout = popen(executable.c_str(), "r"); + if (stdout) + { + while (!feof(stdout)) + { + const int buffer_len = 32 * 1024; + char buffer[buffer_len]; + if (fgets(buffer, buffer_len, stdout)) + { + vector elems; + split(buffer, '=', elems); + if (elems.size() != 2) continue; // shouldn't happen + string key = trim(elems[0]); + string val = trim(elems[1]); + facts[key] = val; + } + } + } +} + +static void get_external_facts(fact_map& facts, string directory) +{ + DIR *external_dir = opendir(directory.c_str()); + if (external_dir == NULL) + return; + + struct dirent *external_fact; + + while ((external_fact = readdir(external_dir))) { + string full_path = directory + "/" + external_fact->d_name; + + if (access(full_path.c_str(), X_OK) != 0) + continue; + + get_external_facts_from_executable(facts, full_path); + } +} + +void get_external_facts(fact_map& facts, list directories) +{ + list::iterator iter; + for (iter = directories.begin(); iter != directories.end(); ++iter) + { + get_external_facts(facts, *iter); + } +} diff --git a/cfacterimpl.h b/cfacterimpl.h new file mode 100644 index 0000000000..28f9d829ff --- /dev/null +++ b/cfacterimpl.h @@ -0,0 +1,24 @@ +#include +#include +#include + +typedef std::map fact_map; + +void get_network_facts(fact_map&); +void get_kernel_facts(fact_map&); +void get_blockdevice_facts(fact_map&); +void get_operatingsystem_facts(fact_map&); +void get_uptime_facts(fact_map&); +void get_virtual_facts(fact_map&); +void get_hardwired_facts(fact_map&); +void get_misc_facts(fact_map&); +void get_ruby_lib_versions(fact_map&); +void get_mem_facts(fact_map&); +void get_selinux_facts(fact_map&); +void get_ssh_facts(fact_map&); +void get_processor_facts(fact_map&); +void get_architecture_facts(fact_map&); +void get_dmidecode_facts(fact_map&); +void get_filesystems_facts(fact_map&); +void get_hostname_facts(fact_map&); +void get_external_facts(fact_map&, std::list directories); diff --git a/cfacterlib.cc b/cfacterlib.cc index 99c1eb4149..0e962846e0 100644 --- a/cfacterlib.cc +++ b/cfacterlib.cc @@ -1,939 +1,95 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifdef __linux__ -#include -#endif -#ifdef __APPLE__ -#include -#endif -#include -#include -#include +#include "cfacterlib.h" +#include "cfacterimpl.h" -#include -#include #include -#include -#include #include #include -#include -#include -#include -#include +#include -#include "cfacterlib.h" +#include "rapidjson/document.h" +#include "rapidjson/prettywriter.h" +#include "rapidjson/stringbuffer.h" using namespace std; -// For case-insensitive strings, define ci_string -// Thank you, Herb Sutter: http://www.gotw.ca/gotw/029.htm -// -struct ci_char_traits : public char_traits -// just inherit all the other functions -// that we don't need to override -{ - static bool eq( char c1, char c2 ) - { return toupper(c1) == toupper(c2); } - - static bool ne( char c1, char c2 ) - { return toupper(c1) != toupper(c2); } - - static bool lt( char c1, char c2 ) - { return toupper(c1) < toupper(c2); } +std::map facts; - static int compare( const char* s1, - const char* s2, - size_t n ) { - return strncasecmp( s1, s2, n ); - // if available on your compiler, - // otherwise you can roll your own - } - - static const char* - find( const char* s, int n, char a ) { - while( n-- > 0 && toupper(*s) != toupper(a) ) { - ++s; - } - return s; - } -}; - -typedef basic_string ci_string; - -// trim from start -static inline std::string <rim(std::string &s) { - s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun(std::isspace)))); - return s; -} - -// trim from end -static inline std::string &rtrim(std::string &s) { - s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun(std::isspace))).base(), s.end()); - return s; -} - -// trim from both ends -static inline std::string &trim(std::string &s) { - return ltrim(rtrim(s)); -} - -static inline void tokenize(std::string &s, vector &tokens) { - istringstream iss(s); - copy(istream_iterator(iss), - istream_iterator(), - back_inserter >(tokens)); -} - -static inline void split(const string &s, char delim, vector &elems) { - stringstream ss(s); - string item; - while (getline(ss, item, delim)) { - elems.push_back(item); - } -} - -static bool file_exist (string filename) +void clear() { - struct stat buffer; - return stat (filename.c_str(), &buffer) == 0; + facts.erase(facts.begin(), facts.end()); } -// handy for some /proc and /sys files -string read_oneline_file(const string file_path) +void loadfacts() { - std::ifstream oneline_file(file_path.c_str(), std::ifstream::in); - std::string line; - std::getline(oneline_file, line); - return line; -} - -void get_network_facts(fact_map& facts) -{ - struct ifreq *ifr; - struct ifconf ifc; - int s, i; - int numif; - - // find number of interfaces. - memset(&ifc, 0, sizeof(ifc)); - ifc.ifc_ifcu.ifcu_req = NULL; - ifc.ifc_len = 0; - - if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { - perror("socket"); - exit(1); - } - - if (ioctl(s, SIOCGIFCONF, &ifc) < 0) { - perror("ioctl"); - exit(1); - } - - if ((ifr = static_cast(malloc(ifc.ifc_len))) == NULL) { - perror("malloc"); - exit(1); - } - ifc.ifc_ifcu.ifcu_req = ifr; - - if (ioctl(s, SIOCGIFCONF, &ifc) < 0) { - perror("ioctl SIOCGIFCONF"); - exit(1); - } - - string interfaces = ""; - - bool primaryInterfacePrinted = false; - numif = ifc.ifc_len / sizeof(struct ifreq); - for (i = 0; i < numif; i++) { - struct ifreq *r = &ifr[i]; - struct in_addr ip_addr = ((struct sockaddr_in *)&r->ifr_addr)->sin_addr; - - // no idea what the real algorithm is to identify the unmarked - // interface, i.e. the one that facter reports as just 'ipaddress' - // here just take the first one that's not 'lo' - bool primaryInterface = false; - if (!primaryInterfacePrinted && strcmp(r->ifr_name, "lo")) { - // this is the chosen interface - primaryInterface = true; - primaryInterfacePrinted = true; - } - - // build up 'interfaces' fact as we go, appending comma after all but last - interfaces += r->ifr_name; - if (i < numif - 1) - interfaces += ","; - - const char *ipaddress = inet_ntoa(ip_addr); - facts[string("ipaddress_") + r->ifr_name] = ipaddress; - if (primaryInterface) - facts["ipaddress"] = ipaddress; - - // mtu - if (ioctl(s, SIOCGIFMTU, r) < 0) { - perror("ioctl SIOCGIFMTU"); - exit(1); - } - - facts[string("mtu_") + r->ifr_name] = to_string(r->ifr_mtu); - if (primaryInterface) - ; // no unmarked version of this network fact - - // netmask and network are both derived from the same ioctl - if (ioctl(s, SIOCGIFNETMASK, r) < 0) { - perror("ioctl SIOCGIFNETMASK"); - exit(1); - } - -#ifndef __APPLE__ // ifr_netmask isn't supported, might need to use SC library - // netmask - struct in_addr netmask_addr = ((struct sockaddr_in *)&r->ifr_netmask)->sin_addr; - const char *netmask = inet_ntoa(netmask_addr); - facts[string("netmask_") + r->ifr_name] = netmask; - if (primaryInterface) - facts["netmask"] = netmask; - - // mess of casting to get the network address - struct in_addr network_addr; - network_addr.s_addr = - (in_addr_t) uint32_t(netmask_addr.s_addr) & uint32_t(ip_addr.s_addr); - string network = inet_ntoa(network_addr); - facts[string("network_") + r->ifr_name] = network; - if (primaryInterface) - facts["network"] = network; -#endif - -#ifndef __APPLE__ // SIOCGIFHWADDR isn't supported, might need to use SC library - // and the mac address (but not for loopback) - if (strcmp(r->ifr_name, "lo")) { - if (ioctl(s, SIOCGIFHWADDR, r) < 0) { - perror("ioctl SIOCGIFHWADDR"); - exit(1); - } - - // extract mac into a string, okay a char array - uint8_t *mac_bytes = (uint8_t *)((sockaddr *)&r->ifr_hwaddr)->sa_data; - char mac_address[18]; - sprintf(mac_address, "%02x:%02x:%02x:%02x:%02x:%02x", - mac_bytes[0], mac_bytes[1], mac_bytes[2], - mac_bytes[3], mac_bytes[4], mac_bytes[5]); - - // and get it out - facts[string("macaddress_") + r->ifr_name] = mac_address; - if (primaryInterface) - facts["macaddress"] = mac_address; - } -#endif - } - - facts["interfaces"] = interfaces; - - close(s); - free(ifr); -} + facts["facterversion"] = "3.0.0"; -void get_kernel_facts(fact_map& facts) -{ -#ifdef __linux__ - // this is linux-only, so there you have it - facts["kernel"] = "Linux"; - string kernelrelease = read_oneline_file("/proc/sys/kernel/osrelease"); - facts["kernelrelease"] = kernelrelease; - string kernelversion = kernelrelease.substr(0, kernelrelease.find("-")); - facts["kernelversion"] = kernelversion; - string kernelmajversion = kernelversion.substr(0, kernelversion.rfind(".")); - facts["kernelmajversion"] = kernelmajversion; -#else -#ifdef __APPLE__ - facts["kernel"] = "Darwin"; -#endif -#endif -} - -static void get_lsb_facts(fact_map& facts) -{ - std::ifstream lsb_release_file("/etc/lsb-release", std::ifstream::in); - std::string line; - while (std::getline(lsb_release_file, line)) { - unsigned sep = line.find("="); - string key = line.substr(0, sep); - string value = line.substr(sep + 1, string::npos); - - if (key == "DISTRIB_ID") { - facts["lsbdistid"] = value; - facts["operatingsystem"] = value; - facts["osfamily"] = "Debian"; - } - else if (key == "DISTRIB_RELEASE") { - facts["lsbdistrelease"] = value; - facts["operatingsystemrelease"] = value; - facts["lsbmajdistrelease"] = value.substr(0, value.find(".")); - } - else if (key == "DISTRIB_CODENAME") - facts["lsbdistcodename"] = value; - else if (key == "DISTRIB_DESCRIPTION") - facts["lsbdistdescription"] = value; - } -} - -// gonna need to pick a regex library to do os facts rights given all the variants -// for now, just fedora ;> - -static void get_redhat_facts(fact_map& facts) -{ - if (file_exist("/etc/redhat-release")) { - facts["osfamily"] = "RedHat"; - string redhat_release = read_oneline_file("/etc/redhat-release"); - vector tokens; - tokenize(redhat_release, tokens); - if (tokens.size() >= 2 && tokens[0] == "Fedora" && tokens[1] == "release") { - facts["operatingsystem"] = "Fedora"; - if (tokens.size() >= 3) { - facts["operatingsystemrelease"] = tokens[2]; - facts["operatingsystemmajrelease"] = tokens[2]; - } - } - else - facts["operatingsystem"] = "RedHat"; - } -} - -void get_operatingsystem_facts(fact_map& facts) -{ - get_lsb_facts(facts); - get_redhat_facts(facts); -} - -void get_uptime_facts(fact_map& facts) -{ - string uptime = read_oneline_file("/proc/uptime"); - unsigned int uptime_seconds; - sscanf(uptime.c_str(), "%ud", &uptime_seconds); - unsigned int uptime_hours = uptime_seconds / 3600; - unsigned int uptime_days = uptime_hours / 24; - facts["uptime_seconds"] = to_string(uptime_seconds); - facts["uptime_hours"] = to_string(uptime_hours); - facts["uptime_days"] = to_string(uptime_days); - facts["uptime"] = to_string(uptime_days) + " days"; -} - -string popen_stdout(string cmd) -{ - FILE *cmd_fd = popen(cmd.c_str(), "r"); - string cmd_output = ""; - char buf[1024]; - size_t bytesRead; - while ((bytesRead = fread(buf, 1, sizeof(buf) - 1, cmd_fd))) { - buf[bytesRead] = 0; - cmd_output += buf; - } - pclose(cmd_fd); - return cmd_output; -} - -void get_virtual_facts(fact_map& facts) -{ - // poked at the real facter's virtual support, some combo of file existence - // plus lspci plus dmidecode - - // instead of parsing all of lspci, how about looking for vendors in lspci -n? - // or walking the /sys/bus/pci/devices files. or don't sweat it, the total - // lspci time is ~40 ms. - - // virtual could be discovered in lots of places so requires some special handling + list external_directories; + external_directories.push_back("/etc/facter/facts.d"); - facts["is_virtual"] = "false"; - facts["virtual"] = "physical"; - -} - -// placeholders for some hardwired facts, cuz not sure what to do with them -void get_hardwired_facts(fact_map& facts) -{ - facts["ps"] = "ps -ef"; // what is this? - facts["uniqueid"] = "007f0101"; // ?? -} - - -// versions of things we don't have if we're not running ruby -// omit or 'undef' or ...? for now, omit but collect them here -void get_ruby_lib_versions(fact_map& facts) -{ - /* - facts["puppetversion => undef"; - facts["augeasversion => undef"; - facts["rubysitedir => undef"; - facts["rubyversion => undef"; - */ -} - -// block devices -void get_blockdevice_facts(fact_map& facts) -{ - string blockdevices = ""; - - DIR *sys_block_dir = opendir("/sys/block"); - if (sys_block_dir == NULL) { - return; - } - - struct dirent *bd; - while ((bd = readdir(sys_block_dir))) { - - bool real_block_device = false; - string device_dir_path = "/sys/block/"; - device_dir_path += bd->d_name; - - DIR *device_dir = opendir(device_dir_path.c_str()); - struct dirent *subdir; - while ((subdir = readdir(device_dir))) { - if (strcmp(subdir->d_name, "device") == 0) { - // we have a winner - real_block_device = true; - break; - } - } - - if (!real_block_device) continue; - - // add it to the blockdevices list, careful with the comma - if (!blockdevices.empty()) - blockdevices += ","; - blockdevices += bd->d_name; - - string model_file = "/sys/block/" + string(bd->d_name) + "/device/model"; - facts[string("blockdevice_") + bd->d_name + "_model"] = - read_oneline_file(model_file); - - string vendor_file = "/sys/block/" + string(bd->d_name) + "/device/vendor"; - facts[string("blockdevice_") + bd->d_name + "_vendor"] = - read_oneline_file(vendor_file); - - string size_file = "/sys/block/" + string(bd->d_name) + "/size"; - string size_line = read_oneline_file(size_file); - int64_t size; - // SCNd64 didn't work here?? - sscanf(size_line.c_str(), "%lld", (long long int *)&size); - facts[string("blockdevice_") + bd->d_name + "_size"] = to_string(size * 512); - } - - facts["blockdevices"] = blockdevices; -} - -void get_misc_facts(fact_map& facts) -{ - facts["path"] = getenv("PATH"); - string whoami = popen_stdout("whoami"); - facts["id"] = trim(whoami); - - //timezone - char tzstring[16]; - time_t t = time(NULL); - struct tm *loc = localtime(&t); - strftime(tzstring, sizeof(tzstring - 1), "%Z", loc); - facts["timezone"] = tzstring; -} - -// get just one fact, optionally in two formats -static void get_mem_fact(std::string fact_name, int fact_value, fact_map& facts, - bool get_mb_variant = true) -{ - float fact_value_scaled = fact_value / 1024.0; - char float_buf[32]; - - if (get_mb_variant) { - snprintf(float_buf, sizeof(float_buf) - 1, "%.2f", fact_value_scaled); - facts[string(fact_name) + "_mb"] = float_buf; - } - - int scale_index; - for (scale_index = 0; - fact_value_scaled > 1024.0; - fact_value_scaled /= 1024.0, ++scale_index) ; - - std::string scale[4] = {"MB", "GB", "TB", "PB"}; // oh yeah, petabytes ... - - snprintf(float_buf, sizeof(float_buf) - 1, "%.2f", fact_value_scaled); - facts[fact_name] = string(float_buf) + scale[scale_index]; -} - -void get_mem_facts(fact_map& facts) -{ - std::ifstream oneline_file("/proc/meminfo", std::ifstream::in); - std::string line; - - // The MemFree fact is the sum of MemFree + Buffer + Cached from /proc/meninfo, - // so sum that one as we go, and get it out at the end. - // The other three memory facts are straight from /proc/meminfo, so get those - // as we go. - // All four facts are geted in two formats: - // _mb => %.2f - // => %.2f %s (where the suffix string is one of MB/GB/TB) - // And then there is a ninth fact, 'memorytotal', which is the same as 'memorysize'. - // - - // NB: this all assumes that all values are in KB. - - unsigned int memoryfree = 0; - - while (std::getline(oneline_file, line)) { - vector tokens; - tokenize(line, tokens); - if (tokens.size() < 3) continue; // should never happen - - if (tokens[0] == "MemTotal:") { - int mem_total = atoi(tokens[1].c_str()); - get_mem_fact("memorysize", mem_total, facts); - get_mem_fact("memorytotal", mem_total, facts, false); - } - else if (tokens[0] == "MemFree:" || tokens[0] == "Cached:" || tokens[0] == "Buffers:") { - memoryfree += atoi(tokens[1].c_str()); - } - else if (tokens[0] == "SwapTotal:") - get_mem_fact("swapsize", atoi(tokens[1].c_str()), facts); - else if (tokens[0] == "SwapFree:") - get_mem_fact("swapfree", atoi(tokens[1].c_str()), facts); - } - - get_mem_fact("memoryfree", memoryfree, facts); -} - -static string get_selinux_path() -{ - static string selinux_path = ""; - static bool inited = false; - - if (inited) - return selinux_path; - - std::ifstream mounts("/proc/self/mounts", std::ifstream::in); - std::string line; - - while (std::getline(mounts, line)) { - vector tokens; - tokenize(line, tokens); - if (tokens.size() < 2) continue; - if (tokens[0] != "selinuxfs") continue; - - selinux_path = tokens[1]; - break; - } - - inited = true; - - return selinux_path; -} - -static bool selinux() -{ - string selinux_path = get_selinux_path(); - if (selinux_path.empty()) - return false; - - string selinux_enforce_path = selinux_path + "/enforce"; - string security_attr_path = "/proc/self/attr/current"; - if (file_exist(selinux_enforce_path) && file_exist(security_attr_path) && - read_oneline_file(security_attr_path) != "kernel") - return true; - - return false; -} - -void get_selinux_facts(fact_map& facts) -{ - if (!selinux()) { - facts["selinux"] = "false"; - return; - } - - facts["selinux"] = "true"; - - // defaults from facter - facts["selinux_enforced"] = "false"; - facts["selinux_policyversion"] = "unknown"; - facts["selinux_current_mode"] = "unknown"; - facts["selinux_config_mode"] = "unknown"; - facts["selinux_config_policy"] = "unknown"; - facts["selinux_mode"] = "unknown"; - - string selinux_path = get_selinux_path(); - - string selinux_enforce_path = selinux_path + "/enforce"; - if (file_exist(selinux_enforce_path)) - facts["selinux_enforced"] = ((read_oneline_file(selinux_enforce_path) == "1") ? "true" : "false"); - - string selinux_policyvers_path = selinux_path + "/policyvers"; - if (file_exist(selinux_policyvers_path)) - facts["selinux_policyversion"] = read_oneline_file(selinux_policyvers_path); - - string selinux_cmd = "/usr/sbin/sestatus"; - FILE* pipe = popen(selinux_cmd.c_str(), "r"); - if (!pipe) return; - - char buffer[512]; // seems like a lot, but there's no constant available - while (!feof(pipe)) { - if (fgets(buffer, 128, pipe) != NULL) { - vector elems; - split(buffer, ':', elems); - if (elems.size() < 2) continue; // shouldn't happen - if (elems[0] == "Current mode") { - facts["selinux_current_mode"] = trim(elems[1]); - } - else if (elems[0] == "Mode from config file") { - facts["selinux_config_mode"] = trim(elems[1]); - } - else if (elems[0] == "Policy from config file") { - facts["selinux_config_policy"] = trim(elems[1]); - facts["selinux_mode"] = trim(elems[1]); - } - } - } - - pclose(pipe); -} - -static void get_ssh_fact(string fact_name, string path_name, fact_map& facts) -{ - string ssh_directories[] = { - "/etc/ssh", - "/usr/local/etc/ssh", - "/etc", - "/usr/local/etc", - "/etc/opt/ssh", - }; - - for (int i = 0; i < sizeof(ssh_directories) / sizeof(string); ++i) { - string full_path = ssh_directories[i] + "/" + path_name; - if (file_exist(full_path)) { - string key = read_oneline_file(full_path); - vector tokens; - tokenize(trim(key), tokens); - if (tokens.size() < 2) continue; // should never happen - facts[fact_name] = tokens[1]; - - // skpping the finger print facts, which require base64 decode and sha libs - // on the cmd line it would be something like the result of these two: - // "cat " + full_path + " | cut -d' ' -f 2 | base64 -d - | sha256sum - | cut -d' ' -f 1" - // "cat " + full_path + " | cut -d' ' -f 2 | base64 -d - | sha1sum - | cut -d' ' -f 1" - - break; - } - } -} - -// no support for the sshfp facts, which require base64/sha1sum code -void get_ssh_facts(fact_map& facts) -{ - // not til C++11 do we have static initialization of stl maps - map ssh_facts; - ssh_facts["sshdsakey"] = "ssh_host_dsa_key.pub"; - ssh_facts["sshrsakey"] = "ssh_host_rsa_key.pub"; - ssh_facts["sshecdsakey"] = "ssh_host_ecdsa_key.pub"; - - typedef map::iterator iter; - for (iter i = ssh_facts.begin(); i != ssh_facts.end(); ++i) { - get_ssh_fact(i->first, i->second, facts); - } -} - -static void get_physicalprocessorcount_fact(fact_map& facts) -{ - // So, facter has logic to use /sys and fallback to /proc - // but I don't know why the /sys support was added; research needed. - // Since sys is the default, just reproduce that logic for now. - - string sysfs_cpu_directory = "/sys/devices/system/cpu"; - vector package_ids; - if (file_exist(sysfs_cpu_directory)) { - for (int i = 0; ; i++) { - char buf[10]; - snprintf(buf, sizeof(buf) - 1, "%u", i); - string cpu_phys_file = sysfs_cpu_directory + "/cpu" + buf + "/topology/physical_package_id"; - if (!file_exist(cpu_phys_file)) - break; - - package_ids.push_back(read_oneline_file(cpu_phys_file)); - } - - sort(package_ids.begin(), package_ids.end()); - unique(package_ids.begin(), package_ids.end()); - facts["physicalprocessorcount"] = to_string(package_ids.size()); + get_network_facts(facts); + get_kernel_facts(facts); + get_blockdevice_facts(facts); + get_operatingsystem_facts(facts); + get_uptime_facts(facts); + get_virtual_facts(facts); + get_hardwired_facts(facts); + get_misc_facts(facts); + get_ruby_lib_versions(facts); + get_mem_facts(facts); + get_selinux_facts(facts); + get_ssh_facts(facts); + get_processor_facts(facts); + get_architecture_facts(facts); + get_dmidecode_facts(facts); + get_filesystems_facts(facts); + get_hostname_facts(facts); + get_external_facts(facts, external_directories); +} + +int to_json(char *facts_json, size_t facts_len) +{ + if (0) { + typedef map::iterator iter; + for (iter i = facts.begin(); i != facts.end(); ++i) { + cout << i->first << " => " << i->second << endl; + } } else { - // here's where the fall back to /proc/cpuinfo would go - } -} - -void get_processorcount_fact(fact_map& facts) -{ - std::ifstream cpuinfo_file("/proc/cpuinfo", std::ifstream::in); - std::string line; - int processor_count = 0; - string current_processor_number; - while (std::getline(cpuinfo_file, line)) { - unsigned sep = line.find(":"); - string tmp = line.substr(0, sep); - string key = trim(tmp); - - if (key == "processor") { - ++processor_count; - string tmp = line.substr(sep + 1, string::npos); - current_processor_number = trim(tmp); - } - else if (key == "model name") { - string tmp = line.substr(sep + 1, string::npos); - facts[string("processor") + current_processor_number] = trim(tmp); - } - } - // this was added after 1.7.3, omit for now, needs investigation - if (false) facts["activeprocessorcount"] = processor_count; - facts["processorcount"] = to_string(processor_count); -} - -void get_processor_facts(fact_map& facts) -{ - get_physicalprocessorcount_fact(facts); - get_processorcount_fact(facts); -} - -void get_architecture_facts(fact_map& facts) -{ - struct utsname uts; - if (uname(&uts) == 0) { - // This is cheating at some level because these are all the same on x86_64 linux. - // Otoh, some of these may be compiled-in for a C version. And then if facter - // relies on 'uname -p' here and that commonizes, this should perhaps just shell out - // and not reproduce that logic. Regardless, need to survey cross-platform here and - // take it from there. - facts["hardwaremodel"] = uts.machine; - facts["hardwareisa"] = uts.machine; - facts["architecture"] = uts.machine; - } -} - -void get_dmidecode_facts(fact_map& facts) -{ - string dmidecode_output = popen_stdout("/usr/sbin/dmidecode"); - std::stringstream ss(dmidecode_output); - string line; + rapidjson::Document json; + json.SetObject(); - enum { - bios_information, - base_board_information, - system_information, - chassis_information, - unknown - } dmi_section = unknown; + rapidjson::Document::AllocatorType& allocator = json.GetAllocator(); - while (std::getline(ss, line)) { - if (line.empty()) continue; - - // enable case-insensitive compares - ci_string ci_line = line.c_str(); - - // identify the dmi section, they all begin at the beginning of a line - // and there are only a handful of interest to us - if (ci_line == "BIOS Information") { - dmi_section = bios_information; - continue; - } - else if (ci_line == "Base Board Information") { - dmi_section = base_board_information; - continue; - } - else if (ci_line == "System Information") { - dmi_section = system_information; - continue; - } - else if (ci_line == "Chassis Information" || ci_line == "system enclosure or chassis") { - dmi_section = chassis_information; - continue; - } - else if (ci_line[0] >= 'A' && ci_line[0] <= 'Z') { - dmi_section = unknown; - continue; - } - - // if we're in the middle of an unknown section, skip - if (dmi_section == unknown) continue; - - size_t sep = line.find(":"); - if (sep != string::npos) { - string tmp = line.substr(0, sep); - string key = trim(tmp); - if (dmi_section == bios_information) { - ci_string ci_key = key.c_str(); - if (ci_key == "vendor") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); - facts["bios_vendor"] = value; - } - if (ci_key == "version") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); - facts["bios_version"] = value; - } - if (ci_key == "release date") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); - facts["bios_release_date"] = value; - } - } - else if (dmi_section == base_board_information) { - ci_string ci_key = key.c_str(); - if (ci_key == "manufacturer") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); - facts["boardmanufacturer"] = value; - } - if (ci_key == "product name" || ci_key == "product") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); - facts["boardproductname"] = value; - } - if (ci_key == "serial number") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); - facts["boardserialnumber"] = value; - } - } - else if (dmi_section == system_information) { - ci_string ci_key = key.c_str(); - if (ci_key == "manufacturer") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); - facts["manufacturer"] = value; - } - if (ci_key == "product name" || ci_key == "product") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); - facts["productname"] = value; - } - if (ci_key == "serial number") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); - facts["serialnumber"] = value; - } - if (ci_key == "uuid") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); - facts["uuid"] = value; - } - } - else if (dmi_section == chassis_information) { - ci_string ci_key = key.c_str(); - if (ci_key == "chassis type" || ci_key == "type") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); - facts["type"] = value; - } - } - } - } -} - -void get_filesystems_facts(fact_map& facts) -{ - std::ifstream cpuinfo_file("/proc/filesystems", std::ifstream::in); - std::string line; - string filesystems = ""; - while (std::getline(cpuinfo_file, line)) { - if (line.find("nodev") != string::npos || line.find("fuseblk") != string::npos) - continue; - - if (!filesystems.empty()) - filesystems += ","; - - filesystems += trim(line); - } - facts["filesystems"] = filesystems; -} - -void get_hostname_facts(fact_map& facts) -{ - // there's some history here, perhaps just port the facter conditional straight across? - // so this is short-term - string hostname_output = popen_stdout("hostname"); - unsigned sep = hostname_output.find("."); - string hostname1 = hostname_output.substr(0, sep); - string hostname = trim(hostname1); - - ifstream resolv_conf_file("/etc/resolv.conf", std::ifstream::in); - string line; - string domain; - string search; - while (std::getline(resolv_conf_file, line)) { - vector elems; - tokenize(line, elems); - if (elems.size() >= 2) { - if (elems[0] == "domain") - domain = trim(elems[1]); - else if (elems[0] == "search") - search = trim(elems[1]); + typedef map::iterator iter; + for (iter i = facts.begin(); i != facts.end(); ++i) { + json.AddMember(i->first.c_str(), i->second.c_str(), allocator); } - } - if (domain.empty() && !search.empty()) - domain = search; - facts["hostname"] = hostname; - facts["domain"] = domain; - facts["fqdn"] = hostname + "." + domain; -} + rapidjson::StringBuffer buf; + rapidjson::Writer writer(buf); + json.Accept(writer); -static void get_external_facts_from_executable(fact_map& facts, string executable) -{ - // executable - FILE *stdout = popen(executable.c_str(), "r"); - if (stdout) - { - while (!feof(stdout)) - { - const int buffer_len = 32 * 1024; - char buffer[buffer_len]; - if (fgets(buffer, buffer_len, stdout)) - { - vector elems; - split(buffer, '=', elems); - if (elems.size() != 2) continue; // shouldn't happen - string key = trim(elems[0]); - string val = trim(elems[1]); - facts[key] = val; - } - } + cout << buf.GetString() << endl; + return 0; } } -static void get_external_facts(fact_map& facts, string directory) +int value(const char *fact, char *value, size_t value_len) { - DIR *external_dir = opendir(directory.c_str()); - if (external_dir == NULL) - return; + typedef map::iterator iter; + iter i = facts.find(fact); + if (i == facts.end()) + return -1; - struct dirent *external_fact; + if (i->second.size() > (value_len - 1)) + return -1; - while ((external_fact = readdir(external_dir))) { - string full_path = directory + "/" + external_fact->d_name; + strncpy(value, i->second.c_str(), value_len); - if (access(full_path.c_str(), X_OK) != 0) - continue; - - get_external_facts_from_executable(facts, full_path); - } + return 0; } -void get_external_facts(fact_map& facts, list directories) +void search_external(const char *dirs) { - list::iterator iter; - for (iter = directories.begin(); iter != directories.end(); ++iter) - { - get_external_facts(facts, *iter); - } + // TODO } diff --git a/cfacterlib.h b/cfacterlib.h index 28f9d829ff..74cd12a83d 100644 --- a/cfacterlib.h +++ b/cfacterlib.h @@ -1,24 +1,16 @@ -#include -#include -#include +#ifndef __CFACTERLIB_IMPL_H__ +#define __CFACTERLIB_IMPL_H__ -typedef std::map fact_map; +#include -void get_network_facts(fact_map&); -void get_kernel_facts(fact_map&); -void get_blockdevice_facts(fact_map&); -void get_operatingsystem_facts(fact_map&); -void get_uptime_facts(fact_map&); -void get_virtual_facts(fact_map&); -void get_hardwired_facts(fact_map&); -void get_misc_facts(fact_map&); -void get_ruby_lib_versions(fact_map&); -void get_mem_facts(fact_map&); -void get_selinux_facts(fact_map&); -void get_ssh_facts(fact_map&); -void get_processor_facts(fact_map&); -void get_architecture_facts(fact_map&); -void get_dmidecode_facts(fact_map&); -void get_filesystems_facts(fact_map&); -void get_hostname_facts(fact_map&); -void get_external_facts(fact_map&, std::list directories); +extern "C" { + +void clear(); +void loadfacts(); +int to_json(char *facts, size_t facts_len); +int value(const char *fact, char *value, size_t value_len); +void search_external(const char *dirs); + +} + +#endif From ef07912cd6d3dca1166384633112685b355c729d Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Mon, 20 Jan 2014 09:20:11 -0800 Subject: [PATCH 1562/3753] Add --help and --version --- cfacter.cc | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 74 insertions(+), 3 deletions(-) diff --git a/cfacter.cc b/cfacter.cc index 06002b15ff..8ffbec378a 100644 --- a/cfacter.cc +++ b/cfacter.cc @@ -1,18 +1,89 @@ #include "cfacterlib.h" +#include #include using namespace std; +void help() +{ + cout << +"Synopsis\n" +"========\n" +"\n" +"Collect and display facts about the system.\n" +"\n" +"Usage\n" +"=====\n" +"\n" +" cfacter [-h|--help] [-v|--version] [-j|--json] [fact] [fact] [...]\n" +"\n" +"Description\n" +"===========\n" +"\n" +"Collect and display facts about the current system. The library behind\n" +"Facter is easy to expand, making Facter an easy way to collect information\n" +"about a system.\n" +"\n" +"If no facts are specifically asked for, then all facts will be returned.\n" +"\n" +"EXAMPLE\n" +"=======\n" +" cfacter kernel\n" +"\n" +"USAGE\n" +"=====\n" +" -j, --json Emit facts in JSON format.\n" +" -v, --version Print the version and exit.\n" +" -h, --help Print this help message and exit.\n" + ; +} + +void version() +{ + cout << "0.0.1" << endl; +} int main(int argc, char **argv) { + static struct option long_options[] = + { + {"help", no_argument, NULL, 'h'}, + {"json", no_argument, NULL, 'j'}, + {"version", no_argument, NULL, 'v'}, + {NULL, 0, NULL, 0} + }; + + // loop over all of the options + int ch; + while ((ch = getopt_long(argc, argv, "hjv", long_options, NULL)) != -1) + { + // check to see if a single character or long option came through + switch (ch) + { + // short option 'v' + case 'v': + version(); + exit(0); + + // short option 'h' + case 'h': + help(); + exit(0); + + // short option 'j' + case 'j': + // do json, that's the only output option atm + break; + } + } + loadfacts(); - + #define MAX_LEN_FACTS_JSON_STRING (1024 * 1024) // go crazy here char facts_json[MAX_LEN_FACTS_JSON_STRING]; if (to_json(facts_json, MAX_LEN_FACTS_JSON_STRING) < 0) { - cout << "Wow, that's a lot of facts" << endl; + cout << "Wow, that's a lot of facts" << endl; exit(1); } cout << facts_json << endl; -} +} From 92a03c5d4c1fa2fc9a3912b542b7c4e628f8d237 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Mon, 20 Jan 2014 09:21:56 -0800 Subject: [PATCH 1563/3753] Update TODO --- TODO | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/TODO b/TODO index f50b3e3ae6..181efedb9c 100644 --- a/TODO +++ b/TODO @@ -1,8 +1,7 @@ -* Add regex support (e.g. for os and processor file parsing) -* Allow 'facter ' (currently returns all facts) -- still *retrieve* all facts but only return the one? or introspect what's available? -* Split out into app, core lib, libs per functional group (network, storage, selinux, processors, etc) -* Use dynamic loading of libs (dlopen, etc) -* autoconf build -* is 'weight' needed or is there a better way? +* ffi wrapper +* Allow 'facter ' (currently returns all facts) +* Handle non-string values * virtual -* fact dependencies \ No newline at end of file +* cmake or autoconf? +* Add regex support (e.g. for os and processor file parsing) +* fact dependencies? From ec76470616ea4660df3a38b585921fc969929e7d Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Mon, 20 Jan 2014 16:23:56 -0800 Subject: [PATCH 1564/3753] Suppress stderr coming from dmidecode. --- TODO | 1 + cfacterimpl.cc | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/TODO b/TODO index 181efedb9c..4ed028d5fe 100644 --- a/TODO +++ b/TODO @@ -3,5 +3,6 @@ * Handle non-string values * virtual * cmake or autoconf? +* abstract out command execution method * Add regex support (e.g. for os and processor file parsing) * fact dependencies? diff --git a/cfacterimpl.cc b/cfacterimpl.cc index 18bb3922d2..7ff9168c0f 100644 --- a/cfacterimpl.cc +++ b/cfacterimpl.cc @@ -353,7 +353,6 @@ void get_virtual_facts(fact_map& facts) facts["is_virtual"] = "false"; facts["virtual"] = "physical"; - } // placeholders for some hardwired facts, cuz not sure what to do with them @@ -722,7 +721,7 @@ void get_architecture_facts(fact_map& facts) void get_dmidecode_facts(fact_map& facts) { - string dmidecode_output = popen_stdout("/usr/sbin/dmidecode"); + string dmidecode_output = popen_stdout("/usr/sbin/dmidecode 2> /dev/null"); std::stringstream ss(dmidecode_output); string line; From d5c71b4b69c00b6ff11d98fefd711554d1d607e9 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Mon, 20 Jan 2014 16:26:47 -0800 Subject: [PATCH 1565/3753] Add reporting of individual facts --- TODO | 2 +- cfacter.cc | 25 ++++++++++++++++++++----- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/TODO b/TODO index 4ed028d5fe..bcc158627e 100644 --- a/TODO +++ b/TODO @@ -1,6 +1,6 @@ * ffi wrapper -* Allow 'facter ' (currently returns all facts) * Handle non-string values +* Normalize value and to_json to both return json? * virtual * cmake or autoconf? * abstract out command execution method diff --git a/cfacter.cc b/cfacter.cc index 8ffbec378a..25c41c9310 100644 --- a/cfacter.cc +++ b/cfacter.cc @@ -78,12 +78,27 @@ int main(int argc, char **argv) } loadfacts(); - + #define MAX_LEN_FACTS_JSON_STRING (1024 * 1024) // go crazy here char facts_json[MAX_LEN_FACTS_JSON_STRING]; - if (to_json(facts_json, MAX_LEN_FACTS_JSON_STRING) < 0) { - cout << "Wow, that's a lot of facts" << endl; - exit(1); + + if (optind == argc) // display all facts + { + if (to_json(facts_json, MAX_LEN_FACTS_JSON_STRING) < 0) { + cout << "Wow, that's a lot of facts" << endl; + exit(1); + } + cout << facts_json << endl; + } + else + { + // display requested fact(s) + while (optind < argc) + { + // fix me + if (value(argv[optind], facts_json, MAX_LEN_FACTS_JSON_STRING) == 0) + cout << facts_json << endl; + ++optind; + } } - cout << facts_json << endl; } From 8f45e7193ba4231f54e112c8bc9ae111e700c2be Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 7 Jan 2014 13:59:20 -0800 Subject: [PATCH 1566/3753] (FACT-65) Extract Resolvable and Suitable mixins The Facter::Util::Resolution class contained behavior for determining if a resolution was suitable, the precedence of the resolution, code for safely resolving values, and code for actually resolving information. This commit breaks up the Resolution class into a Suitable mixin for determining if a resolution can run and the precedence of that resolution, and a Resolvable mixin for handling behavior around the resolution of a given fact. --- lib/facter/core/resolvable.rb | 96 ++++++++++++++ lib/facter/core/suitable.rb | 80 ++++++++++++ lib/facter/util/resolution.rb | 178 +++----------------------- spec/unit/core/resolvable_spec.rb | 116 +++++++++++++++++ spec/unit/core/suitable_spec.rb | 70 +++++++++++ spec/unit/util/resolution_spec.rb | 202 +----------------------------- 6 files changed, 384 insertions(+), 358 deletions(-) create mode 100644 lib/facter/core/resolvable.rb create mode 100644 lib/facter/core/suitable.rb create mode 100644 spec/unit/core/resolvable_spec.rb create mode 100644 spec/unit/core/suitable_spec.rb diff --git a/lib/facter/core/resolvable.rb b/lib/facter/core/resolvable.rb new file mode 100644 index 0000000000..420e0ab44c --- /dev/null +++ b/lib/facter/core/resolvable.rb @@ -0,0 +1,96 @@ +require 'timeout' + +# The resolvable mixin defines behavior for evaluating and returning fact +# resolutions. +# +# Classes including this mixin should implement at #name method describing +# the value being resolved and a #resolve_value that actually executes the code +# to resolve the value. +module Facter::Core::Resolvable + + # The timeout, in seconds, for evaluating this resolution. + # @return [Integer] + # @api public + attr_accessor :timeout + + # Return the timeout period for resolving a value. + # (see #timeout) + # @return [Numeric] + # @comment requiring 'timeout' stdlib class causes Object#timeout to be + # defined which delegates to Timeout.timeout. This method may potentially + # overwrite the #timeout attr_reader on this class, so we define #limit to + # avoid conflicts. + def limit + @timeout || 0 + end + + ## + # on_flush accepts a block and executes the block when the resolution's value + # is flushed. This makes it possible to model a single, expensive system + # call inside of a Ruby object and then define multiple dynamic facts which + # resolve by sending messages to the model instance. If one of the dynamic + # facts is flushed then it can, in turn, flush the data stored in the model + # instance to keep all of the dynamic facts in sync without making multiple, + # expensive, system calls. + # + # Please see the Solaris zones fact for an example of how this feature may be + # used. + # + # @see Facter::Util::Fact#flush + # @see Facter::Util::Resolution#flush + # + # @api public + def on_flush(&block) + @on_flush_block = block + end + + ## + # flush executes the block, if any, stored by the {on_flush} method + # + # @see Facter::Util::Fact#flush + # @see Facter::Util::Resolution#on_flush + # + # @api private + def flush + @on_flush_block.call if @on_flush_block + end + + def value + result = nil + + with_timing do + Timeout.timeout(limit) do + result = resolve_value + end + end + + Facter::Util::Normalization.normalize(result) + + rescue Timeout::Error => detail + Facter.warn "Timed out seeking value for #{self.name}" + + # This call avoids zombies -- basically, create a thread that will + # dezombify all of the child processes that we're ignoring because + # of the timeout. + Thread.new { Process.waitall } + return nil + rescue Facter::Util::Normalization::NormalizationError => e + Facter.warn "Fact resolution #{self.name} resolved to an invalid value: #{e.message}" + return nil + rescue => details + Facter.warn "Could not retrieve #{self.name}: #{details.message}" + return nil + end + + private + + def with_timing + starttime = Time.now.to_f + + yield + + finishtime = Time.now.to_f + ms = (finishtime - starttime) * 1000 + Facter.show_time "#{self.name}: #{"%.2f" % ms}ms" + end +end diff --git a/lib/facter/core/suitable.rb b/lib/facter/core/suitable.rb new file mode 100644 index 0000000000..3f4a7bc468 --- /dev/null +++ b/lib/facter/core/suitable.rb @@ -0,0 +1,80 @@ +require 'facter' + +# The Suitable mixin provides mechanisms for confining objects to run on +# certain platforms and determining the run precedence of these objects. +# +# Classes that include the Suitable mixin should define a `#confines` method +# that returns an Array of zero or more Facter::Util::Confine objects. +module Facter::Core::Suitable + + attr_writer :weight + + # Sets the weight of this resolution. If multiple suitable resolutions + # are found, the one with the highest weight will be used. If weight + # is not given, the number of confines set on a resolution will be + # used as its weight (so that the most specific resolution is used). + # + # @param weight [Integer] the weight of this resolution + # + # @return [void] + # + # @api public + def has_weight(weight) + @weight = weight + end + + # Sets the conditions for this resolution to be used. This takes a + # hash of fact names and values. Every fact must match the values + # given for that fact, otherwise this resolution will not be + # considered suitable. The values given for a fact can be an array, in + # which case the value of the fact must be in the array for it to + # match. + # + # @param confines [Hash{String => Object}] a hash of facts and the + # values they should have in order for this resolution to be + # used + # + # @example Confining to Linux + # Facter.add(:powerstates) do + # # This resolution only makes sense on linux systems + # confine :kernel => "Linux" + # setcode do + # Facter::Util::Resolution.exec('cat /sys/power/states') + # end + # end + # + # @return [void] + # + # @api public + def confine(confines) + confines.each do |fact, values| + @confines.push Facter::Util::Confine.new(fact, *values) + end + end + + # Returns the importance of this resolution. If the weight was not + # given, the number of confines is used instead (so that a more + # specific resolution wins over a less specific one). + # + # @return [Integer] the weight of this resolution + # + # @api private + def weight + if @weight + @weight + else + @confines.length + end + end + + # Is this resolution mechanism suitable on the system in question? + # + # @api private + def suitable? + unless defined? @suitable + @suitable = ! @confines.detect { |confine| ! confine.true? } + end + + return @suitable + end +end diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 4129309413..6bb4842e06 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -2,8 +2,8 @@ require 'facter/util/config' require 'facter/util/normalization' require 'facter/core/execution' - -require 'timeout' +require 'facter/core/resolvable' +require 'facter/core/suitable' # This represents a fact resolution. A resolution is a concrete # implementation of a fact. A single fact can have many resolutions and @@ -17,23 +17,9 @@ # # @api public class Facter::Util::Resolution - # The timeout, in seconds, for evaluating this resolution. The default - # is 0 which is equivalent to no timeout. This can be set using the - # options hash argument to {Facter.add}. - # @return [Integer] - # @api public - attr_accessor :timeout - - # @!attribute [rw] name - # The name of this resolution. The resolution name should be unique with - # respect to the given fact. - # @return [String] - # @api public - attr_accessor :name - # @api private attr_accessor :code - attr_writer :value, :weight + attr_writer :value INTERPRETER = Facter::Util::Config.is_windows? ? "cmd.exe" : "/bin/sh" @@ -48,48 +34,15 @@ class << self public :search_paths, :which, :absolute_path?, :expand_command, :with_env, :exec end - # Sets the conditions for this resolution to be used. This takes a - # hash of fact names and values. Every fact must match the values - # given for that fact, otherwise this resolution will not be - # considered suitable. The values given for a fact can be an array, in - # which case the value of the fact must be in the array for it to - # match. - # - # @param confines [Hash{String => Object}] a hash of facts and the - # values they should have in order for this resolution to be - # used - # - # @example Confining to Linux - # Facter.add(:powerstates) do - # # This resolution only makes sense on linux systems - # confine :kernel => "Linux" - # setcode do - # Facter::Util::Resolution.exec('cat /sys/power/states') - # end - # end - # - # @return [void] - # - # @api public - def confine(confines) - confines.each do |fact, values| - @confines.push Facter::Util::Confine.new(fact, *values) - end - end + include Facter::Core::Resolvable + include Facter::Core::Suitable - # Sets the weight of this resolution. If multiple suitable resolutions - # are found, the one with the highest weight will be used. If weight - # is not given, the number of confines set on a resolution will be - # used as its weight (so that the most specific resolution is used). - # - # @param weight [Integer] the weight of this resolution - # - # @return [void] - # + # @!attribute [rw] name + # The name of this resolution. The resolution name should be unique with + # respect to the given fact. + # @return [String] # @api public - def has_weight(weight) - @weight = weight - end + attr_accessor :name # Create a new resolution mechanism. # @@ -127,30 +80,6 @@ def set_options(options) end end - # Returns the importance of this resolution. If the weight was not - # given, the number of confines is used instead (so that a more - # specific resolution wins over a less specific one). - # - # @return [Integer] the weight of this resolution - # - # @api private - def weight - if @weight - @weight - else - @confines.length - end - end - - # (see #timeout) - # This is another name for {#timeout}. - # @comment We need this as a getter for 'timeout', because some versions - # of ruby seem to already have a 'timeout' method and we can't - # seem to override the instance methods, somehow. - def limit - @timeout - end - # Sets the code block or external program that will be evaluated to # get the value of the fact. # @@ -181,37 +110,6 @@ def setcode(string = nil, interp = nil, &block) end end - ## - # on_flush accepts a block and executes the block when the resolution's value - # is flushed. This makes it possible to model a single, expensive system - # call inside of a Ruby object and then define multiple dynamic facts which - # resolve by sending messages to the model instance. If one of the dynamic - # facts is flushed then it can, in turn, flush the data stored in the model - # instance to keep all of the dynamic facts in sync without making multiple, - # expensive, system calls. - # - # Please see the Solaris zones fact for an example of how this feature may be - # used. - # - # @see Facter::Util::Fact#flush - # @see Facter::Util::Resolution#flush - # - # @api public - def on_flush(&block) - @on_flush_block = block - end - - ## - # flush executes the block, if any, stored by the {on_flush} method - # - # @see Facter::Util::Fact#flush - # @see Facter::Util::Resolution#on_flush - # - # @api private - def flush - @on_flush_block.call if @on_flush_block - end - # @deprecated def interpreter Facter.warnonce "The 'Facter::Util::Resolution.interpreter' method is deprecated and will be removed in a future version." @@ -224,62 +122,22 @@ def interpreter=(interp) @interpreter = interp end - # Is this resolution mechanism suitable on the system in question? - # - # @api private - def suitable? - if @suitable.nil? - @suitable = @confines.all? { |confine| confine.true? } - end - - return @suitable - end - # (see value) # @deprecated def to_s return self.value() end - # Evaluates the code block or external program to get the value of the - # fact. - # - # @api private - def value - return @value if @value - result = nil - return result if @code == nil - - starttime = Time.now.to_f + private - begin - Timeout.timeout(limit) do - if @code.is_a?(Proc) - result = @code.call() - else - result = Facter::Util::Resolution.exec(@code) - end - end - rescue Timeout::Error => detail - Facter.warn "Timed out seeking value for %s" % self.name + def resolve_value + return @value if @value + return nil if @code.nil? - # This call avoids zombies -- basically, create a thread that will - # dezombify all of the child processes that we're ignoring because - # of the timeout. - Thread.new { Process.waitall } - return nil - rescue => details - Facter.warn "Could not retrieve %s: %s" % [self.name, details] - return nil + if @code.is_a? Proc + @code.call() + else + Facter::Util::Resolution.exec(@code) end - - finishtime = Time.now.to_f - ms = (finishtime - starttime) * 1000 - Facter.show_time "#{self.name}: #{"%.2f" % ms}ms" - - Facter::Util::Normalization.normalize(result) - rescue Facter::Util::Normalization::NormalizationError => e - Facter.warn "Fact resolution #{self.name} resolved to an invalid value: #{e.message}" - nil end end diff --git a/spec/unit/core/resolvable_spec.rb b/spec/unit/core/resolvable_spec.rb new file mode 100644 index 0000000000..69d9c334a9 --- /dev/null +++ b/spec/unit/core/resolvable_spec.rb @@ -0,0 +1,116 @@ +require 'spec_helper' +require 'facter/core/resolvable' + +describe Facter::Core::Resolvable do + + class ResolvableClass + def initialize(name) + @name = name + end + attr_accessor :name, :resolve_value + include Facter::Core::Resolvable + end + + subject { ResolvableClass.new('resolvable') } + + it "has a default timeout of 0 seconds" do + expect(subject.limit).to eq 0 + end + + it "can specify a custom timeout" do + subject.timeout = 10 + expect(subject.limit).to eq 10 + end + + describe "generating a value" do + let(:fact_value) { "" } + + let(:utf16_string) do + if String.method_defined?(:encode) && defined?(::Encoding) + fact_value.encode(Encoding::UTF_16LE).freeze + else + [0x00, 0x00].pack('C*').freeze + end + end + + let(:expected_value) do + if String.method_defined?(:encode) && defined?(::Encoding) + fact_value.encode(Encoding::UTF_8).freeze + else + [0x00, 0x00].pack('C*').freeze + end + end + + it "returns the results of #resolve_value" do + subject.resolve_value = 'stuff' + expect(subject.value).to eq 'stuff' + end + + it "normalizes the resolved value" do + subject.resolve_value = fact_value + expect(subject.value).to eq(expected_value) + end + + it "returns nil if an exception was raised" do + subject.expects(:resolve_value).raises RuntimeError, "kaboom!" + expect(subject.value).to eq nil + end + + it "logs a warning if an exception was raised" do + subject.expects(:resolve_value).raises RuntimeError, "kaboom!" + Facter.expects(:warn).with('Could not retrieve resolvable: kaboom!') + expect(subject.value).to eq nil + end + end + + describe "timing out" do + it "uses #limit instead of #timeout to determine the timeout period" do + subject.expects(:timeout).never + subject.expects(:limit).returns 25 + + Timeout.expects(:timeout).with(25) + subject.value + end + + it "times out after the provided timeout" do + def subject.resolve_value + sleep 2 + end + subject.timeout = 0.1 + subject.value + end + + it "returns nil if the timeout was reached" do + Timeout.expects(:timeout).raises Timeout::Error + + expect(subject.value).to be_nil + end + + + it "starts a thread to wait on all child processes if the timeout was reached" do + Thread.expects(:new).yields + Process.expects(:waitall) + + Timeout.expects(:timeout).raises Timeout::Error + + subject.value + end + end + + describe 'callbacks when flushing facts' do + class FlushFakeError < StandardError; end + + context '#on_flush' do + it 'accepts a block with on_flush' do + subject.on_flush() { raise NotImplementedError } + end + end + + context '#flush' do + it 'calls the block passed to on_flush' do + subject.on_flush() { raise FlushFakeError } + expect { subject.flush }.to raise_error FlushFakeError + end + end + end +end diff --git a/spec/unit/core/suitable_spec.rb b/spec/unit/core/suitable_spec.rb new file mode 100644 index 0000000000..feadb41e34 --- /dev/null +++ b/spec/unit/core/suitable_spec.rb @@ -0,0 +1,70 @@ +require 'spec_helper' +require 'facter/core/suitable' + +describe Facter::Core::Suitable do + + class SuitableClass + def initialize + @confines = [] + end + attr_reader :confines + include Facter::Core::Suitable + end + + subject { SuitableClass.new } + + it "can add confines" do + subject.confine :kernel => 'Linux' + end + + it "creates a Facter::Util::Confine object for the confine call" do + subject.confine :kernel => 'Linux' + conf = subject.confines.first + expect(conf).to be_a_kind_of Facter::Util::Confine + expect(conf.fact).to eq :kernel + expect(conf.values).to eq ['Linux'] + end + + describe "determining weight" do + it "is zero if no confines are set" do + expect(subject.weight).to eq 0 + end + + it "defaults to the number of confines" do + subject.confine :kernel => 'Linux' + expect(subject.weight).to eq 1 + end + + it "can be explicitly set" do + subject.has_weight 10 + expect(subject.weight).to eq 10 + end + + it "prefers an explicit weight over the number of confines" do + subject.confine :kernel => 'Linux' + subject.has_weight 11 + expect(subject.weight).to eq 11 + end + end + + describe "determining suitability" do + it "is true if all confines for the object evaluate to true" do + subject.confine :kernel => 'Linux' + subject.confine :operatingsystem => 'Redhat' + + subject.confines.each { |confine| confine.stubs(:true?).returns true } + + expect(subject).to be_suitable + end + + it "is false if any confines for the object evaluate to false" do + subject.confine :kernel => 'Linux' + subject.confine :operatingsystem => 'Redhat' + + subject.confines.first.stubs(:true?).returns true + subject.confines.first.stubs(:true?).returns false + + expect(subject).to_not be_suitable + end + end +end diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index a6b954e408..4e4b3f9b1a 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -6,11 +6,11 @@ describe Facter::Util::Resolution do include FacterSpec::ConfigHelper - it "should require a name" do - lambda { Facter::Util::Resolution.new }.should raise_error(ArgumentError) + it "requires a name" do + expect { Facter::Util::Resolution.new }.to raise_error(ArgumentError) end - it "should have a name" do + it "can return its name" do Facter::Util::Resolution.new("yay").name.should == "yay" end @@ -20,22 +20,6 @@ resolve.value.should == "foo" end - it "should have a method for setting the weight" do - Facter::Util::Resolution.new("yay").should respond_to(:has_weight) - end - - it "should have a method for setting the code" do - Facter::Util::Resolution.new("yay").should respond_to(:setcode) - end - - it "should support a timeout value" do - Facter::Util::Resolution.new("yay").should respond_to(:timeout=) - end - - it "should default to a timeout of 0 seconds" do - Facter::Util::Resolution.new("yay").limit.should == 0 - end - it "should default to nil for code" do Facter::Util::Resolution.new("yay").code.should be_nil end @@ -45,12 +29,6 @@ Facter::Util::Resolution.new("yay").interpreter.should be_nil end - it "should provide a 'limit' method that returns the timeout" do - res = Facter::Util::Resolution.new("yay") - res.timeout = "testing" - res.limit.should == "testing" - end - describe "when setting the code" do before do Facter.stubs(:warnonce) @@ -91,54 +69,11 @@ end it "should fail if neither a string nor block has been provided" do - lambda { @resolve.setcode }.should raise_error(ArgumentError) - end - end - - describe 'callbacks when flushing facts' do - class FlushFakeError < StandardError; end - - subject do - Facter::Util::Resolution.new("jeff") - end - - context '#on_flush' do - it 'accepts a block with on_flush' do - subject.on_flush() { raise NotImplementedError } - end - end - - context '#flush' do - it 'calls the block passed to on_flush' do - subject.on_flush() { raise FlushFakeError } - expect { subject.flush }.to raise_error FlushFakeError - end + expect { @resolve.setcode }.to raise_error(ArgumentError) end end - it "should be able to return a value" do - Facter::Util::Resolution.new("yay").should respond_to(:value) - end - describe "when returning the value" do - let(:fact_value) { "" } - - let(:utf16_string) do - if String.method_defined?(:encode) && defined?(::Encoding) - fact_value.encode(Encoding::UTF_16LE).freeze - else - [0x00, 0x00].pack('C*').freeze - end - end - - let(:expected_value) do - if String.method_defined?(:encode) && defined?(::Encoding) - fact_value.encode(Encoding::UTF_8).freeze - else - [0x00, 0x00].pack('C*').freeze - end - end - before do @resolve = Facter::Util::Resolution.new("yay") end @@ -167,14 +102,6 @@ class FlushFakeError < StandardError; end @resolve.value.should == "yup" end - - it "it normalizes the resolved value" do - @resolve.setcode "/bin/foo" - - Facter::Util::Resolution.expects(:exec).once.returns(utf16_string) - - expect(@resolve.value).to eq(expected_value) - end end describe "on non-windows systems" do @@ -188,14 +115,6 @@ class FlushFakeError < StandardError; end @resolve.value.should == "yup" end - - it "it normalizes the resolved value" do - @resolve.setcode "/bin/foo" - - Facter::Util::Resolution.expects(:exec).once.returns(utf16_string) - - expect(@resolve.value).to eq(expected_value) - end end end @@ -210,41 +129,6 @@ class FlushFakeError < StandardError; end @resolve.setcode { "yayness" } @resolve.value.should == "yayness" end - - it "it normalizes the resolved value" do - @resolve.setcode { utf16_string } - - expect(@resolve.value).to eq(expected_value) - end - - it "should use its limit method to determine the timeout, to avoid conflict when a 'timeout' method exists for some other reason" do - @resolve.expects(:timeout).never - @resolve.expects(:limit).returns "foo" - Timeout.expects(:timeout).with("foo") - - @resolve.setcode { sleep 2; "raise This is a test"} - @resolve.value - end - - it "should timeout after the provided timeout" do - Facter.expects(:warn) - @resolve.timeout = 0.1 - @resolve.setcode { sleep 2; raise "This is a test" } - Thread.expects(:new).yields - - @resolve.value.should be_nil - end - - it "should waitall to avoid zombies if the timeout is exceeded" do - Facter.stubs(:warn) - @resolve.timeout = 0.1 - @resolve.setcode { sleep 2; raise "This is a test" } - - Thread.expects(:new).yields - Process.expects(:waitall) - - @resolve.value - end end end @@ -254,84 +138,6 @@ class FlushFakeError < StandardError; end @resolve.to_s.should == "myval" end - it "should allow the adding of confines" do - Facter::Util::Resolution.new("yay").should respond_to(:confine) - end - - it "should provide a method for returning the number of confines" do - @resolve = Facter::Util::Resolution.new("yay") - @resolve.confine "one" => "foo", "two" => "fee" - @resolve.weight.should == 2 - end - - it "should return 0 confines when no confines have been added" do - Facter::Util::Resolution.new("yay").weight.should == 0 - end - - it "should provide a way to set the weight" do - @resolve = Facter::Util::Resolution.new("yay") - @resolve.has_weight(45) - @resolve.weight.should == 45 - end - - it "should allow the weight to override the number of confines" do - @resolve = Facter::Util::Resolution.new("yay") - @resolve.confine "one" => "foo", "two" => "fee" - @resolve.weight.should == 2 - @resolve.has_weight(45) - @resolve.weight.should == 45 - end - - it "should have a method for determining if it is suitable" do - Facter::Util::Resolution.new("yay").should respond_to(:suitable?) - end - - describe "when adding confines" do - before do - @resolve = Facter::Util::Resolution.new("yay") - end - - it "should accept a hash of fact names and values" do - lambda { @resolve.confine :one => "two" }.should_not raise_error - end - - it "should create a Util::Confine instance for every argument in the provided hash" do - Facter::Util::Confine.expects(:new).with("one", "foo") - Facter::Util::Confine.expects(:new).with("two", "fee") - - @resolve.confine "one" => "foo", "two" => "fee" - end - - end - - describe "when determining suitability" do - before do - @resolve = Facter::Util::Resolution.new("yay") - end - - it "should always be suitable if no confines have been added" do - @resolve.should be_suitable - end - - it "should be unsuitable if any provided confines return false" do - confine1 = mock 'confine1', :true? => true - confine2 = mock 'confine2', :true? => false - Facter::Util::Confine.expects(:new).times(2).returns(confine1).then.returns(confine2) - @resolve.confine :one => :two, :three => :four - - @resolve.should_not be_suitable - end - - it "should be suitable if all provided confines return true" do - confine1 = mock 'confine1', :true? => true - confine2 = mock 'confine2', :true? => true - Facter::Util::Confine.expects(:new).times(2).returns(confine1).then.returns(confine2) - @resolve.confine :one => :two, :three => :four - - @resolve.should be_suitable - end - end - describe "setting options" do subject(:resolution) { described_class.new(:foo) } From ddfd832c3f19d892a1438354d98d7d7db5929d58 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 16 Jan 2014 12:10:54 -0800 Subject: [PATCH 1567/3753] (maint) Remove normalization tests from resolvable --- spec/unit/core/resolvable_spec.rb | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/spec/unit/core/resolvable_spec.rb b/spec/unit/core/resolvable_spec.rb index 69d9c334a9..5555974f86 100644 --- a/spec/unit/core/resolvable_spec.rb +++ b/spec/unit/core/resolvable_spec.rb @@ -23,32 +23,15 @@ def initialize(name) end describe "generating a value" do - let(:fact_value) { "" } - - let(:utf16_string) do - if String.method_defined?(:encode) && defined?(::Encoding) - fact_value.encode(Encoding::UTF_16LE).freeze - else - [0x00, 0x00].pack('C*').freeze - end - end - - let(:expected_value) do - if String.method_defined?(:encode) && defined?(::Encoding) - fact_value.encode(Encoding::UTF_8).freeze - else - [0x00, 0x00].pack('C*').freeze - end - end - it "returns the results of #resolve_value" do subject.resolve_value = 'stuff' expect(subject.value).to eq 'stuff' end it "normalizes the resolved value" do - subject.resolve_value = fact_value - expect(subject.value).to eq(expected_value) + Facter::Util::Normalization.expects(:normalize).returns 'stuff' + subject.resolve_value = 'stuff' + expect(subject.value).to eq('stuff') end it "returns nil if an exception was raised" do From 3431c2dda7e3e0d75d46e58f34b8b8c6335f9601 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 7 Jan 2014 13:58:11 -0800 Subject: [PATCH 1568/3753] (FACT-237) Add directed graph for handling dependencies --- lib/facter/core/directed_graph.rb | 45 +++++++++++++++ spec/unit/core/directed_graph_spec.rb | 79 +++++++++++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 lib/facter/core/directed_graph.rb create mode 100644 spec/unit/core/directed_graph_spec.rb diff --git a/lib/facter/core/directed_graph.rb b/lib/facter/core/directed_graph.rb new file mode 100644 index 0000000000..bbdd5db9fa --- /dev/null +++ b/lib/facter/core/directed_graph.rb @@ -0,0 +1,45 @@ +require 'set' + +module Facter + module Core + class DirectedGraph < Hash + include TSort + + def acyclic? + cycles.empty? + end + + def cycles + cycles = [] + each_strongly_connected_component do |component| + cycles << component if component.size > 1 + end + cycles + end + + alias tsort_each_node each_key + + def tsort_each_child(node) + fetch(node, []).each do |child| + yield child + end + end + + def tsort + missing = Set.new(self.values.flatten) - Set.new(self.keys) + + if not missing.empty? + raise MissingVertex, "Cannot sort elements; cannot depend on missing elements #{missing.to_a}" + end + + super + + rescue TSort::Cyclic + raise CycleError, "Cannot sort elements; found the following cycles: #{cycles.inspect}" + end + + class CycleError < StandardError; end + class MissingVertex < StandardError; end + end + end +end diff --git a/spec/unit/core/directed_graph_spec.rb b/spec/unit/core/directed_graph_spec.rb new file mode 100644 index 0000000000..1b68237b6b --- /dev/null +++ b/spec/unit/core/directed_graph_spec.rb @@ -0,0 +1,79 @@ +require 'spec_helper' +require 'facter/core/directed_graph' + +describe Facter::Core::DirectedGraph do + subject(:graph) { described_class.new } + + describe "detecting cycles" do + it "is acyclic if the graph is empty" do + expect(graph).to be_acyclic + end + + it "is acyclic if the graph has no edges" do + graph[:one] = [] + graph[:two] = [] + + expect(graph).to be_acyclic + end + + it "is acyclic if a vertex has an edge to itself" do + graph[:one] = [:one] + expect(graph).to be_acyclic + end + + it "is acyclic if there are no loops in the graph" do + graph[:one] = [:two, :three] + graph[:two] = [:four] + graph[:three] = [:four] + graph[:four] = [] + + expect(graph).to be_acyclic + end + + it "is cyclic if there is a loop in the graph" do + graph[:one] = [:two] + graph[:two] = [:one] + expect(graph).to_not be_acyclic + end + + it "can return the cycles in the graph" do + graph[:one] = [:two] + graph[:two] = [:one] + + graph[:three] = [:four] + graph[:four] = [:three] + + first_cycle = graph.cycles.find { |cycle| cycle.include? :one } + second_cycle = graph.cycles.find { |cycle| cycle.include? :three } + + expect(first_cycle).to include :two + expect(second_cycle).to include :four + end + end + + describe "sorting" do + it "returns the vertices in topologically sorted order" do + graph[:one] = [:two, :three] + graph[:two] = [:three] + graph[:three] = [] + expect(graph.tsort).to eq [:three, :two, :one] + end + + it "raises an error if there is a cycle in the graph" do + graph[:one] = [:two] + graph[:two] = [:one] + + expect { + graph.tsort + }.to raise_error(Facter::Core::DirectedGraph::CycleError, /found the following cycles:/) + end + + it "raises an error if there is an edge to a non-existent vertex" do + graph[:one] = [:two, :three] + graph[:two] = [:three] + expect { + graph.tsort + }.to raise_error(Facter::Core::DirectedGraph::MissingVertex, /missing elements.*three/) + end + end +end From b5c42931725aa2f6f1b4c6531ed006d29f7ab62e Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 7 Jan 2014 13:59:38 -0800 Subject: [PATCH 1569/3753] (FACT-237) Add aggregate resolution class --- lib/facter/core/aggregate.rb | 185 +++++++++++++++++++++++++++++++ lib/facter/core/resolvable.rb | 1 - spec/unit/core/aggregate_spec.rb | 130 ++++++++++++++++++++++ 3 files changed, 315 insertions(+), 1 deletion(-) create mode 100644 lib/facter/core/aggregate.rb create mode 100644 spec/unit/core/aggregate_spec.rb diff --git a/lib/facter/core/aggregate.rb b/lib/facter/core/aggregate.rb new file mode 100644 index 0000000000..20b51cd3d3 --- /dev/null +++ b/lib/facter/core/aggregate.rb @@ -0,0 +1,185 @@ +require 'facter' +require 'facter/core/directed_graph' +require 'facter/core/suitable' +require 'facter/core/resolvable' + +# Aggregates provide a mechanism for facts to be resolved in multiple steps. +# +# Aggregates are evaluated in two parts: generating individual chunks and then +# aggregating all chunks together. Each chunk is a block of code that generates +# a value, and may depend on other chunks when it runs. After all chunks have +# been evaluated they are passed to the aggregate block as Hash. +# The aggregate block converts the individual chunks into a single value that is +# returned as the final value of the aggregate. +# +# @api public +# @since 2.0.0 +class Facter::Core::Aggregate + + include Facter::Core::Suitable + include Facter::Core::Resolvable + + # @!attribute [r] name + # @return [Symbol] The name of the aggregate resolution + attr_reader :name + + # @!attribute [r] deps + # @api private + # @return [Facter::Core::DirectedGraph] + attr_reader :deps + + # @!attribute [r] confines + # @return [Array] An array of confines restricting + # this to a specific platform + # @see Facter::Core::Suitable + attr_reader :confines + + def initialize(name) + @name = name + + @confines = [] + @chunks = {} + + @aggregate = nil + @deps = Facter::Core::DirectedGraph.new + end + + def set_options(options) + if options[:name] + @name = options.delete(:name) + end + + if options.has_key?(:timeout) + @timeout = options.delete(:timeout) + end + + if options.has_key?(:weight) + @weight = options.delete(:weight) + end + + if not options.keys.empty? + raise ArgumentError, "Invalid aggregate options #{options.keys.inspect}" + end + end + + # Define a new chunk for the given aggregate + # + # @api public + # + # @example Defining a chunk with no dependencies + # aggregate.chunk(:mountpoints) do + # # generate mountpoint information + # end + # + # @example Defining an chunk to add mount options + # aggregate.chunk(:mount_options, :require => [:mountpoints]) do |mountpoints| + # # `mountpoints` is the result of the previous chunk + # # generate mount option information based on the mountpoints + # end + # + # @param name [Symbol] A name unique to this aggregate describing the chunk + # @param opts [Hash] + # @options opts [Array, Symbol] :require One or more chunks + # to evaluate and pass to this block. + # @yield [*Object] Zero or more chunk results + # + # @return [void] + def chunk(name, opts = {}, &block) + if not block_given? + raise ArgumentError, "#{self.class.name}#chunk requires a block" + end + + deps = Array(opts.delete(:require)) + + if not opts.empty? + raise ArgumentError, "Unexpected options passed to #{self.class.name}#chunk: #{opts.keys.inspect}" + end + + @deps[name] = deps + @chunks[name] = block + end + + # Define how all chunks should be combined + # + # @api public + # + # @example Merge all chunks + # aggregate.aggregate do |chunks| + # final_result = {} + # chunks.each_value do |chunk| + # final_result.deep_merge(chunk) + # end + # final_result + # end + # + # @example Sum all chunks + # aggregate.aggregate do |chunks| + # total = 0 + # chunks.each_value do |chunk| + # total += chunk + # end + # total + # end + # + # @yield [Hash] A hash containing chunk names and + # chunk values + # + # @return [void] + def aggregate(&block) + if block_given? + @aggregate = block + else + raise ArgumentError, "#{self.class.name}#aggregate requires a block" + end + end + + private + + # Evaluate the results of this aggregate. + # + # @see Facter::Core::Resolvable#value + # @return [Object] + def resolve_value + chunk_results = run_chunks() + aggregate_results(chunk_results) + end + + # Order all chunks based on their dependencies and evaluate each one, passing + # dependent chunks as needed. + # + # @return [Hash] A hash containing the chunk that + # generated value and the related value. + def run_chunks + results = {} + order_chunks.each do |(name, block)| + input = @deps[name].map { |dep_name| results[dep_name] } + + results[name] = block.call(*input) + end + + results + end + + # Process the results of all chunks with the aggregate block and return the results. + # @return [Object] + def aggregate_results(results) + @aggregate.call(results) + end + + # Order chunks based on their dependencies + # + # @return [Array] A list of chunk names and blocks in evaluation order. + def order_chunks + if not @deps.acyclic? + raise DependencyError, "Could not order chunks; found the following dependency cycles: #{@deps.cycles.inspect}" + end + + sorted_names = @deps.tsort + + sorted_names.map do |name| + [name, @chunks[name]] + end + end + + class DependencyError < StandardError; end +end diff --git a/lib/facter/core/resolvable.rb b/lib/facter/core/resolvable.rb index 420e0ab44c..7228262f16 100644 --- a/lib/facter/core/resolvable.rb +++ b/lib/facter/core/resolvable.rb @@ -65,7 +65,6 @@ def value end Facter::Util::Normalization.normalize(result) - rescue Timeout::Error => detail Facter.warn "Timed out seeking value for #{self.name}" diff --git a/spec/unit/core/aggregate_spec.rb b/spec/unit/core/aggregate_spec.rb new file mode 100644 index 0000000000..e1d9ab477a --- /dev/null +++ b/spec/unit/core/aggregate_spec.rb @@ -0,0 +1,130 @@ +require 'spec_helper' +require 'facter/core/aggregate' + +describe Facter::Core::Aggregate do + + subject do + obj = described_class.new('aggregated') + obj.aggregate { |chunks| chunks.values } + obj + end + + it "can be resolved" do + expect(subject).to be_a_kind_of Facter::Core::Resolvable + end + + it "can be confined and weighted" do + expect(subject).to be_a_kind_of Facter::Core::Suitable + end + + describe "setting options" do + + it "can set the timeout" do + subject.set_options(:timeout => 314) + expect(subject.limit).to eq 314 + end + + it "can set the weight" do + subject.set_options(:weight => 27) + expect(subject.weight).to eq 27 + end + + it "can set the name" do + subject.set_options(:name => 'something') + expect(subject.name).to eq 'something' + end + + it "fails on unhandled options" do + expect do + subject.set_options(:foo => 'bar') + end.to raise_error(ArgumentError, /Invalid aggregate options .*foo/) + end + end + + describe "declaring chunks" do + it "requires that an chunk is given a block" do + expect { subject.chunk(:fail) }.to raise_error(ArgumentError, /requires a block/) + end + + it "allows an chunk to have a list of requirements" do + subject.chunk(:data, :require => [:other]) { } + expect(subject.deps[:data]).to eq [:other] + end + + it "converts a single chunk requirement to an array" do + subject.chunk(:data, :require => :other) { } + expect(subject.deps[:data]).to eq [:other] + end + + it "raises an error when an unhandled option is passed" do + expect { + subject.chunk(:data, :before => [:other]) { } + }.to raise_error(ArgumentError, /Unexpected options.*#chunk: .*before/) + end + end + + describe "handling interactions between chunks" do + it "generates a warning when there is a dependency cycle in chunks" do + subject.chunk(:first, :require => [:second]) { } + subject.chunk(:second, :require => [:first]) { } + + Facter.expects(:warn) do |msg| + expect(msg).to match /dependency cycles: .*[:first, :second]/ + end + + subject.value + end + + it "passes all requested chunk results to the depending chunk" do + subject.chunk(:first) { 'foo' } + subject.chunk(:second, :require => [:first]) do |first| + "#{first} bar" + end + + output = subject.value + expect(output).to include 'foo' + expect(output).to include 'foo bar' + end + + it "clones and freezes chunk results passed to other chunks" + end + + describe "evaluating chunks" do + it "emits a warning and returns nil when a chunk raises an error" do + Facter.expects(:warn) do |msg| + expect(msg).to match /Could not run chunk boom.*kaboom!/ + end + + subject.chunk(:boom) { raise 'kaboom!' } + subject.value + end + end + + describe "aggregating chunks" do + it "passes all chunk results as a hash to the aggregate block" do + subject.chunk(:data) { 'data chunk' } + subject.chunk(:datum) { 'datum chunk' } + + subject.aggregate do |chunks| + expect(chunks).to eq(:data => 'data chunk', :datum => 'datum chunk') + end + + subject.value + end + + it "uses the result of the aggregate block as the value" do + subject.aggregate { "who needs chunks anyways" } + expect(subject.value).to eq "who needs chunks anyways" + end + + it "generates a warning and returns if the aggregate raises an error" do + subject.aggregate { raise 'kaboom!' } + + Facter.expects(:warn) do |msg| + expect(msg).to match /Could not aggregate chunks for boom.*kaboom!/ + end + + subject.value + end + end +end From 733d2af2abad516df124b7c0cf43eb2124825867 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 6 Jan 2014 10:38:09 -0800 Subject: [PATCH 1570/3753] (FACT-237) Implement deep_merge for composing facts --- lib/facter/util/values.rb | 37 +++++++++++++++ spec/unit/util/values_spec.rb | 85 +++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 spec/unit/util/values_spec.rb diff --git a/lib/facter/util/values.rb b/lib/facter/util/values.rb index d69198767a..938f6fe20c 100644 --- a/lib/facter/util/values.rb +++ b/lib/facter/util/values.rb @@ -4,6 +4,43 @@ module Util module Values module_function + # Perform a deep merge of two nested data structures. + # + # @param left [Object] + # @param right [Object] + # @param path [Array] The traversal path followed when merging nested hashes + # + # @return [Object] The merged data structure. + def deep_merge(left, right, path = [], &block) + ret = nil + + if left.is_a? Hash and right.is_a? Hash + ret = left.merge(right) do |key, left_val, right_val| + path.push(key) + merged = deep_merge(left_val, right_val, path) + path.pop + merged + end + elsif left.is_a? Array and right.is_a? Array + ret = left.dup.concat(right) + elsif right.nil? + ret = left + elsif left.nil? + ret = right + elsif left.nil? and right.nil? + ret = nil + else + msg = "Cannot merge #{left.inspect}:#{left.class} and #{right.inspect}:#{right.class}" + if not path.empty? + msg << " at root" + msg << path.map { |part| "[#{part.inspect}]" }.join + end + raise ArgumentError, msg + end + + ret + end + def convert(value) value = value.to_s if value.is_a?(Symbol) value = value.downcase if value.is_a?(String) diff --git a/spec/unit/util/values_spec.rb b/spec/unit/util/values_spec.rb new file mode 100644 index 0000000000..570386ac24 --- /dev/null +++ b/spec/unit/util/values_spec.rb @@ -0,0 +1,85 @@ +require 'spec_helper' +require 'facter/util/values' + +describe Facter::Util::Values do + describe 'deep_merge' do + it "non-destructively concatenates arrays" do + first = %w[foo bar] + second = %w[baz quux] + + expect(described_class.deep_merge(first, second)).to eq %w[foo bar baz quux] + expect(first).to eq %w[foo bar] + expect(second).to eq %w[baz quux] + end + + it "returns the left value if the right value is nil" do + expect(described_class.deep_merge("left", nil)).to eq "left" + end + + it "returns the right value if the left value is nil" do + expect(described_class.deep_merge(nil, "right")).to eq "right" + end + + it "returns nil if both values are nil" do + expect(described_class.deep_merge(nil, nil)).to be_nil + end + + describe "with hashes" do + it "merges when keys do not overlap" do + + first = {:foo => 'bar'} + second = {:baz => 'quux'} + + expect(described_class.deep_merge(first, second)).to eq(:foo => 'bar', :baz => 'quux') + expect(first).to eq(:foo => 'bar') + expect(second).to eq(:baz => 'quux') + end + + it "concatenates arrays when both keys are arrays" do + first = {:foo => %w[bar]} + second = {:foo => %w[baz quux]} + + expect(described_class.deep_merge(first, second)).to eq(:foo => %w[bar baz quux]) + expect(first).to eq(:foo => %w[bar]) + expect(second).to eq(:foo => %w[baz quux]) + end + + it "merges hashes when both keys are hashes" do + first = {:foo => {:pb => 'lead', :ag => 'silver'}} + second = {:foo => {:au => 'gold', :na => 'sodium'}} + + expect(described_class.deep_merge(first, second)).to eq( + :foo => { + :pb => 'lead', + :ag => 'silver', + :au => 'gold', + :na => 'sodium' + } + ) + end + + it "prints the data structure path if an error is raised" do + first = {:foo => {:bar => {:baz => {:quux => true}}}} + second = {:foo => {:bar => {:baz => {:quux => false}}}} + + expect { + described_class.deep_merge(first, second) + }.to raise_error(ArgumentError, /Cannot merge .*at .*foo.*bar.*baz.*quux/) + end + end + + describe "with unmergable scalar values" do + [ + [true, false], + [1, 2], + ['up', 'down'] + ].each do |(left, right)| + it "raises an error when merging #{left}:#{left.class} and #{right}:#{right.class}" do + expect { + described_class.deep_merge(left, right) + }.to raise_error(ArgumentError, /Cannot merge #{left.inspect}:#{left.class} and #{right.inspect}:#{right.class}/) + end + end + end + end +end From c5a0ecd9ffca2f31a2f0fc7aa4913eba082913ce Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 6 Jan 2014 11:07:48 -0800 Subject: [PATCH 1571/3753] (FACT-237) Implement deep_freeze for composed facts --- lib/facter/util/values.rb | 25 +++++++++++++++++++++ spec/unit/util/values_spec.rb | 41 +++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/lib/facter/util/values.rb b/lib/facter/util/values.rb index 938f6fe20c..f1e667cd2d 100644 --- a/lib/facter/util/values.rb +++ b/lib/facter/util/values.rb @@ -4,6 +4,31 @@ module Util module Values module_function + # Duplicate and deeply freeze a given data structure + # + # @param value [Object] The structure to freeze + # @return [void] + def deep_freeze(value) + case value + when Numeric, Symbol, TrueClass, FalseClass, NilClass + # These are immutable values, we can safely ignore them + value + when String + value.dup.freeze + when Array + value.map do |entry| + deep_freeze(entry) + end.freeze + when Hash + value.inject({}) do |hash, (key, value)| + hash[deep_freeze(key)] = deep_freeze(value) + hash + end.freeze + else + raise ArgumentError, "Cannot deep freeze #{value}:#{value.class}" + end + end + # Perform a deep merge of two nested data structures. # # @param left [Object] diff --git a/spec/unit/util/values_spec.rb b/spec/unit/util/values_spec.rb index 570386ac24..f3bb7f9314 100644 --- a/spec/unit/util/values_spec.rb +++ b/spec/unit/util/values_spec.rb @@ -2,6 +2,47 @@ require 'facter/util/values' describe Facter::Util::Values do + describe 'deep_freeze' do + it "it dups and freezes strings" do + input = "hi" + output = described_class.deep_freeze(input) + expect(input).to_not be_frozen + expect(output).to be_frozen + end + + it "freezes arrays and each element in the array" do + input = %w[one two three] + output = described_class.deep_freeze(input) + + input.each { |entry| expect(entry).to_not be_frozen } + output.each { |entry| expect(entry).to be_frozen } + + expect(input).to_not be_frozen + expect(output).to be_frozen + end + + it "freezes hashes and each key and value in the hash" do + input = {'one' => 'two', 'three' => 'four'} + + output = described_class.deep_freeze(input) + + input.each_pair do |key, val| + # Ruby freezes all string keys, so these will always be frozen + expect(key).to be_frozen + expect(val).to_not be_frozen + end + + output.each_pair do |key, val| + expect(key).to be_frozen + expect(val).to be_frozen + end + + expect(input).to_not be_frozen + expect(output).to be_frozen + end + + end + describe 'deep_merge' do it "non-destructively concatenates arrays" do first = %w[foo bar] From 1921fa3acf22e5849277f83e0e4cbbdb30f73cd3 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Fri, 17 Jan 2014 09:41:21 -0800 Subject: [PATCH 1572/3753] (FACT-237) Use specific error classes for deep_merge and deep_freeze --- lib/facter/util/values.rb | 8 ++++++-- spec/unit/util/values_spec.rb | 9 +++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/lib/facter/util/values.rb b/lib/facter/util/values.rb index f1e667cd2d..a7048d54da 100644 --- a/lib/facter/util/values.rb +++ b/lib/facter/util/values.rb @@ -4,6 +4,8 @@ module Util module Values module_function + class DeepFreezeError < StandardError; end + # Duplicate and deeply freeze a given data structure # # @param value [Object] The structure to freeze @@ -25,10 +27,12 @@ def deep_freeze(value) hash end.freeze else - raise ArgumentError, "Cannot deep freeze #{value}:#{value.class}" + raise DeepFreezeError, "Cannot deep freeze #{value}:#{value.class}" end end + class DeepMergeError < StandardError; end + # Perform a deep merge of two nested data structures. # # @param left [Object] @@ -60,7 +64,7 @@ def deep_merge(left, right, path = [], &block) msg << " at root" msg << path.map { |part| "[#{part.inspect}]" }.join end - raise ArgumentError, msg + raise DeepMergeError, msg end ret diff --git a/spec/unit/util/values_spec.rb b/spec/unit/util/values_spec.rb index f3bb7f9314..557eb195e6 100644 --- a/spec/unit/util/values_spec.rb +++ b/spec/unit/util/values_spec.rb @@ -41,6 +41,11 @@ expect(output).to be_frozen end + it "raises an error when given a structure that cannot be deeply frozen" do + expect { + described_class.deep_freeze(Set.new) + }.to raise_error(Facter::Util::Values::DeepFreezeError, /Cannot deep freeze.*Set/) + end end describe 'deep_merge' do @@ -105,7 +110,7 @@ expect { described_class.deep_merge(first, second) - }.to raise_error(ArgumentError, /Cannot merge .*at .*foo.*bar.*baz.*quux/) + }.to raise_error(Facter::Util::Values::DeepMergeError, /Cannot merge .*at .*foo.*bar.*baz.*quux/) end end @@ -118,7 +123,7 @@ it "raises an error when merging #{left}:#{left.class} and #{right}:#{right.class}" do expect { described_class.deep_merge(left, right) - }.to raise_error(ArgumentError, /Cannot merge #{left.inspect}:#{left.class} and #{right.inspect}:#{right.class}/) + }.to raise_error(Facter::Util::Values::DeepMergeError, /Cannot merge #{left.inspect}:#{left.class} and #{right.inspect}:#{right.class}/) end end end From a4b2fa147c0b9ea6fc1e52cbbf219eb84e2de233 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 16 Jan 2014 17:24:00 -0800 Subject: [PATCH 1573/3753] (FACT-237) Freeze results of each aggregate chunk --- lib/facter/core/aggregate.rb | 4 +++- spec/unit/core/aggregate_spec.rb | 13 ++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/facter/core/aggregate.rb b/lib/facter/core/aggregate.rb index 20b51cd3d3..ff199fc1c2 100644 --- a/lib/facter/core/aggregate.rb +++ b/lib/facter/core/aggregate.rb @@ -2,6 +2,7 @@ require 'facter/core/directed_graph' require 'facter/core/suitable' require 'facter/core/resolvable' +require 'facter/util/values' # Aggregates provide a mechanism for facts to be resolved in multiple steps. # @@ -154,7 +155,8 @@ def run_chunks order_chunks.each do |(name, block)| input = @deps[name].map { |dep_name| results[dep_name] } - results[name] = block.call(*input) + output = block.call(*input) + results[name] = Facter::Util::Values.deep_freeze(output) end results diff --git a/spec/unit/core/aggregate_spec.rb b/spec/unit/core/aggregate_spec.rb index e1d9ab477a..6f85022321 100644 --- a/spec/unit/core/aggregate_spec.rb +++ b/spec/unit/core/aggregate_spec.rb @@ -86,7 +86,18 @@ expect(output).to include 'foo bar' end - it "clones and freezes chunk results passed to other chunks" + it "clones and freezes chunk results passed to other chunks" do + subject.chunk(:first) { 'foo' } + subject.chunk(:second, :require => [:first]) do |first| + expect(first).to be_frozen + end + + subject.aggregate do |chunks| + chunks.values.each do |chunk| + expect(chunk).to be_frozen + end + end + end end describe "evaluating chunks" do From 083e0aa86766612161d6af965d5e5bba6f5e6942 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Fri, 17 Jan 2014 09:49:20 -0800 Subject: [PATCH 1574/3753] (FACT-237) Define default aggregate action --- lib/facter/core/aggregate.rb | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/lib/facter/core/aggregate.rb b/lib/facter/core/aggregate.rb index ff199fc1c2..99f3f729b6 100644 --- a/lib/facter/core/aggregate.rb +++ b/lib/facter/core/aggregate.rb @@ -162,10 +162,27 @@ def run_chunks results end - # Process the results of all chunks with the aggregate block and return the results. + # Process the results of all chunks with the aggregate block and return the + # results. If no aggregate block has been specified, fall back to deep + # merging the given data structure + # + # @param results [Hash] A hash of chunk names and the output + # of that chunk. # @return [Object] def aggregate_results(results) - @aggregate.call(results) + if @aggregate + @aggregate.call(results) + else + default_aggregate(results) + end + end + + def default_aggregate(results) + Facter::Util::Values.deep_merge(results.keys) + rescue Facter::Util::Values::DeepMergeError => e + raise ArgumentError, "No aggregate block specified and could not deep merge" + + " all chunks, either specify an aggregate block or ensure that all chunks" + + " return deep mergable structures. (Original error: #{e.message})", e.backtrace end # Order chunks based on their dependencies From e4141d67a46f39e5a53d5c1a03b2e69599514f2f Mon Sep 17 00:00:00 2001 From: Ryan McKern Date: Wed, 22 Jan 2014 16:41:10 -0800 Subject: [PATCH 1575/3753] (RE-814) Add initial support for building in a RHEL 7 mock RHEL7 support is being added for x86_64 only; we'll fix/expand this if upstream releases a 32-bit version of RHEL7. --- ext/build_defaults.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/build_defaults.yaml b/ext/build_defaults.yaml index bb071538c7..f2bec369d4 100644 --- a/ext/build_defaults.yaml +++ b/ext/build_defaults.yaml @@ -9,7 +9,7 @@ gpg_name: 'info@puppetlabs.com' gpg_key: '4BD6EC30' sign_tar: FALSE # a space separated list of mock configs -final_mocks: 'pl-el-5-i386 pl-el-5-x86_64 pl-el-6-i386 pl-el-6-x86_64 pl-fedora-18-i386 pl-fedora-18-x86_64 pl-fedora-19-i386 pl-fedora-19-x86_64 pl-fedora-20-i386 pl-fedora-20-x86_64' +final_mocks: 'pl-el-5-i386 pl-el-5-x86_64 pl-el-6-i386 pl-el-6-x86_64 pl-el-7-x86_64 pl-fedora-18-i386 pl-fedora-18-x86_64 pl-fedora-19-i386 pl-fedora-19-x86_64 pl-fedora-20-i386 pl-fedora-20-x86_64' yum_host: 'yum.puppetlabs.com' yum_repo_path: '/opt/repository/yum/' build_gem: TRUE From 14d3f65c0b57b3b4781e3ff71446fa4a5433d2b9 Mon Sep 17 00:00:00 2001 From: john Date: Sat, 21 Sep 2013 21:11:32 -0500 Subject: [PATCH 1576/3753] (#22636) Allow list of external fact directories to be appended to Currently, the only way to override the external facts directories is to write your own Facter::Util::FactLoader and replace the existing loader with this. This commit allows you to simply set or append to the already existing ext_facts_dirs variable which makes it much more extensible for use by other programs like puppet. --- lib/facter/util/config.rb | 28 +++++++++++++++++++--------- spec/unit/util/config_spec.rb | 20 ++++++++++++++++++++ 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/lib/facter/util/config.rb b/lib/facter/util/config.rb index 26c2c8514e..0db5a9d476 100644 --- a/lib/facter/util/config.rb +++ b/lib/facter/util/config.rb @@ -29,23 +29,33 @@ def self.windows_data_dir end end + def self.external_facts_dirs=(dir) + @external_facts_dirs = dir + end + def self.external_facts_dirs + @external_facts_dirs + end + + def self.setup_default_ext_facts_dirs if Facter::Util::Root.root? windows_dir = windows_data_dir if windows_dir.nil? then - ["/etc/facter/facts.d", "/etc/puppetlabs/facter/facts.d"] + @external_facts_dirs = ["/etc/facter/facts.d", "/etc/puppetlabs/facter/facts.d"] else - [File.join(windows_dir, 'PuppetLabs', 'facter', 'facts.d')] + @external_facts_dirs = [File.join(windows_dir, 'PuppetLabs', 'facter', 'facts.d')] end else - [File.expand_path(File.join("~", ".facter", "facts.d"))] + @external_facts_dirs = [File.expand_path(File.join("~", ".facter", "facts.d"))] end end -end -if Facter::Util::Config.is_windows? - require 'win32/dir' - require 'facter/util/windows_root' -else - require 'facter/util/unix_root' + if Facter::Util::Config.is_windows? + require 'win32/dir' + require 'facter/util/windows_root' + else + require 'facter/util/unix_root' + end + + setup_default_ext_facts_dirs end diff --git a/spec/unit/util/config_spec.rb b/spec/unit/util/config_spec.rb index 25afcf0b65..830eb0909d 100644 --- a/spec/unit/util/config_spec.rb +++ b/spec/unit/util/config_spec.rb @@ -41,24 +41,44 @@ it "should return the default value for linux" do Facter::Util::Config.stubs(:is_windows?).returns(false) Facter::Util::Config.stubs(:windows_data_dir).returns(nil) + Facter::Util::Config.setup_default_ext_facts_dirs Facter::Util::Config.external_facts_dirs.should == ["/etc/facter/facts.d", "/etc/puppetlabs/facter/facts.d"] end it "should return the default value for windows 2008" do Facter::Util::Config.stubs(:is_windows?).returns(true) Facter::Util::Config.stubs(:windows_data_dir).returns("C:\\ProgramData") + Facter::Util::Config.setup_default_ext_facts_dirs Facter::Util::Config.external_facts_dirs.should == [File.join("C:\\ProgramData", 'PuppetLabs', 'facter', 'facts.d')] end it "should return the default value for windows 2003R2" do Facter::Util::Config.stubs(:is_windows?).returns(true) Facter::Util::Config.stubs(:windows_data_dir).returns("C:\\Documents") + Facter::Util::Config.setup_default_ext_facts_dirs Facter::Util::Config.external_facts_dirs.should == [File.join("C:\\Documents", 'PuppetLabs', 'facter', 'facts.d')] end it "returns the users home directory when not root" do Facter::Util::Root.stubs(:root?).returns(false) + Facter::Util::Config.setup_default_ext_facts_dirs Facter::Util::Config.external_facts_dirs.should == [File.expand_path(File.join("~", ".facter", "facts.d"))] end + + it "includes additional values when user appends to the list" do + Facter::Util::Config.setup_default_ext_facts_dirs + original_values = Facter::Util::Config.external_facts_dirs.dup + new_value = '/usr/share/newdir' + Facter::Util::Config.external_facts_dirs << new_value + Facter::Util::Config.external_facts_dirs.should == original_values + [new_value] + end + + it "should only output new values when explicitly set" do + Facter::Util::Config.setup_default_ext_facts_dirs + new_value = ['/usr/share/newdir'] + Facter::Util::Config.external_facts_dirs = new_value + Facter::Util::Config.external_facts_dirs.should == new_value + end + end end From 5e369e062571022e41c88465309f853d165688c2 Mon Sep 17 00:00:00 2001 From: John Julien Date: Tue, 15 Oct 2013 22:23:15 -0500 Subject: [PATCH 1577/3753] (#9546) Do not execute com, cmd, exe, or bat files if not on windows Facter currently only executes com, exe, and bat files if on a windows system. It should also exclude these on unix flavored systems since the interpreter for these "cmd.exe" will not be present. This also supports the use of pluginsync for external facts where in a hybrid environment external facts for windows may end up getting syncd to a unix system. Conflicts: spec/unit/util/parser_spec.rb --- spec/unit/util/parser_spec.rb | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/spec/unit/util/parser_spec.rb b/spec/unit/util/parser_spec.rb index 54bc1e452c..f6e28e5999 100755 --- a/spec/unit/util/parser_spec.rb +++ b/spec/unit/util/parser_spec.rb @@ -149,6 +149,28 @@ def expects_parser_to_return_nil_for_directory(path) end end + context "exe, bat, cmd, and com files" do + let :cmds do ["/tmp/foo.bat", "/tmp/foo.cmd", "/tmp/foo.exe", "/tmp/foo.com"] end + + before :each do + cmds.each {|cmd| + File.stubs(:executable?).with(cmd).returns(true) + File.stubs(:file?).with(cmd).returns(true) + } + end + + it "should return nothing parser if not on windows" do + Facter::Util::Config.stubs(:is_windows?).returns(false) + cmds.each {|cmd| Facter::Util::Parser.parser_for(cmd).should be_an_instance_of(Facter::Util::Parser::NothingParser) } + end + + it "should return script parser if on windows" do + Facter::Util::Config.stubs(:is_windows?).returns(true) + cmds.each {|cmd| Facter::Util::Parser.parser_for(cmd).should be_an_instance_of(Facter::Util::Parser::ScriptParser) } + end + + end + describe "powershell parser" do let(:ps1) { "/tmp/foo.ps1" } From 1df74949a4ffa223720ac35ea76f13c99c4a3e4b Mon Sep 17 00:00:00 2001 From: Ryan McKern Date: Wed, 22 Jan 2014 16:41:10 -0800 Subject: [PATCH 1578/3753] (RE-814) Add initial support for building in a RHEL 7 mock RHEL7 support is being added for x86_64 only; we'll fix/expand this if upstream releases a 32-bit version of RHEL7. --- ext/build_defaults.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/build_defaults.yaml b/ext/build_defaults.yaml index bb071538c7..f2bec369d4 100644 --- a/ext/build_defaults.yaml +++ b/ext/build_defaults.yaml @@ -9,7 +9,7 @@ gpg_name: 'info@puppetlabs.com' gpg_key: '4BD6EC30' sign_tar: FALSE # a space separated list of mock configs -final_mocks: 'pl-el-5-i386 pl-el-5-x86_64 pl-el-6-i386 pl-el-6-x86_64 pl-fedora-18-i386 pl-fedora-18-x86_64 pl-fedora-19-i386 pl-fedora-19-x86_64 pl-fedora-20-i386 pl-fedora-20-x86_64' +final_mocks: 'pl-el-5-i386 pl-el-5-x86_64 pl-el-6-i386 pl-el-6-x86_64 pl-el-7-x86_64 pl-fedora-18-i386 pl-fedora-18-x86_64 pl-fedora-19-i386 pl-fedora-19-x86_64 pl-fedora-20-i386 pl-fedora-20-x86_64' yum_host: 'yum.puppetlabs.com' yum_repo_path: '/opt/repository/yum/' build_gem: TRUE From ddb8cfb39e6dcc34dfd5ef76d45b16d550728b7b Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 23 Jan 2014 12:16:55 -0800 Subject: [PATCH 1579/3753] (maint) Pin windows compiled gem versions Since the windows spec boxes may not have dev tools installed, they may not be able to install newer versions of the windows API gems. This commit ensures that we won't have to worry about an unexpected version of one of those gems failing to install. --- ext/project_data.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/project_data.yaml b/ext/project_data.yaml index 3dba3c29d6..792341d6db 100644 --- a/ext/project_data.yaml +++ b/ext/project_data.yaml @@ -19,9 +19,9 @@ gem_platform_dependencies: CFPropertyList: '~> 2.2.6' x86-mingw32: gem_runtime_dependencies: - ffi: '~> 1.9.0' - sys-admin: '~> 1.5.6' - win32-api: '~> 1.4.8' + ffi: '1.9.0' + sys-admin: '1.5.6' + win32-api: '1.4.8' win32-dir: '~> 0.4.3' windows-api: '~> 0.4.2' windows-pr: '~> 1.2.2' From 1590a4c5475da93f8f609a14b7f9a4e63511f8dc Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Wed, 22 Jan 2014 23:00:29 -0800 Subject: [PATCH 1580/3753] (fact-194) Add Facter.search_external{,_path} --- lib/facter.rb | 20 ++++++++++++++++++++ spec/unit/facter_spec.rb | 23 +++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/lib/facter.rb b/lib/facter.rb index eff1665b57..da433d0845 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -399,4 +399,24 @@ def self.search(*dirs) def self.search_path @search_path.dup end + + # Registers directories to be searched for external facts. + # + # @param dirs [Array] directories to search + # + # @return [void] + # + # @api public + def self.search_external(dirs) + Facter::Util::Config.external_facts_dirs += dirs + end + + # Returns the registered search directories. + # + # @return [Array] An array of the directories searched + # + # @api public + def self.search_external_path + Facter::Util::Config.external_facts_dirs.dup + end end diff --git a/spec/unit/facter_spec.rb b/spec/unit/facter_spec.rb index 9c3bdef0a6..2279c27019 100755 --- a/spec/unit/facter_spec.rb +++ b/spec/unit/facter_spec.rb @@ -116,6 +116,14 @@ Facter.should respond_to(:search_path) end + it "should have a method for registering directories to search for external facts" do + Facter.should respond_to(:search_external) + end + + it "should have a method for returning the registered search directories for external facts" do + Facter.should respond_to(:search_external_path) + end + it "should have a method to query debugging mode" do Facter.should respond_to(:debugging?) end @@ -253,4 +261,19 @@ Facter.search_path.should == %w{/my/dir /other/dir} end end + + describe "when registering directories to search for external facts" do + it "should allow registration of a directory" do + Facter.search_external ["/my/dir"] + end + + it "should allow registration of multiple directories" do + Facter.search_external ["/my/dir", "/other/dir"] + end + + it "should return all registered directories when asked" do + Facter.search_external ["/my/dir", "/other/dir"] + Facter.search_external_path.should include("/my/dir", "/other/dir") + end + end end From 1deddfecba46009cf973870cb7ea4b83936ddf24 Mon Sep 17 00:00:00 2001 From: Derek Yarnell Date: Mon, 16 Dec 2013 16:18:11 -0500 Subject: [PATCH 1581/3753] Adds support for building facter on RHEL7 via spec. --- ext/redhat/facter.spec.erb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ext/redhat/facter.spec.erb b/ext/redhat/facter.spec.erb index 9e3321a6eb..aeb1289421 100644 --- a/ext/redhat/facter.spec.erb +++ b/ext/redhat/facter.spec.erb @@ -1,6 +1,6 @@ -# Fedora 17 ships with ruby 1.9, which uses vendorlibdir instead +# Fedora 17 ships with ruby 1.9, RHEL 7 with ruby 2.0, which use vendorlibdir instead # of sitelibdir -%if 0%{?fedora} >= 17 +%if 0%{?fedora} >= 17 || 0%{?rhel} >= 7 %global facter_libdir %(ruby -rrbconfig -e 'puts RbConfig::CONFIG["vendorlibdir"]') %else %global facter_libdir %(ruby -rrbconfig -e 'puts RbConfig::CONFIG["sitelibdir"]') @@ -35,8 +35,8 @@ Requires: virt-what Requires: ruby >= 1.8.5 BuildRequires: ruby >= 1.8.5 -# In Fedora 17 ruby-rdoc is called rubygem-rdoc -%if 0%{?fedora} >= 17 +# In Fedora 17+ or RHEL 7+ ruby-rdoc is called rubygem-rdoc +%if 0%{?fedora} >= 17 || 0%{?rhel} >= 7 BuildRequires: rubygem-rdoc %else BuildRequires: ruby-rdoc From 7a78e74df96691dffb2e9b6ad6fcdb787ffd40e4 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 13 Aug 2013 11:30:16 -0700 Subject: [PATCH 1582/3753] Merge pull request #509 from mzeren-vmw/fix/22060/undefined_path_in_macaddress (fact-202) Fix undefined path in macaddress.rb --- lib/facter/macaddress.rb | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/lib/facter/macaddress.rb b/lib/facter/macaddress.rb index 3bcc92751a..3d0dffa8df 100644 --- a/lib/facter/macaddress.rb +++ b/lib/facter/macaddress.rb @@ -10,19 +10,6 @@ require 'facter/util/macaddress' require 'facter/util/ip' -Facter.add(:macaddress) do - confine :kernel => 'Linux' - has_weight 10 # about an order of magnitude faster - setcode do - begin - Dir.glob('/sys/class/net/*').reject {|x| x[-3..-1] == '/lo' }.first - path and File.read(path + '/address') - rescue Exception - nil - end - end -end - Facter.add(:macaddress) do confine :kernel => 'Linux' setcode do From 865b4cae5d6acdb3f5f33ae6619a8d0b37c3c404 Mon Sep 17 00:00:00 2001 From: Ryan McKern Date: Wed, 22 Jan 2014 16:41:10 -0800 Subject: [PATCH 1583/3753] Add initial support for building in a RHEL 7 mock RHEL7 support is being added for x86_64 only; we'll fix/expand this if upstream releases a 32-bit version of RHEL7. --- ext/build_defaults.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/build_defaults.yaml b/ext/build_defaults.yaml index bb071538c7..f2bec369d4 100644 --- a/ext/build_defaults.yaml +++ b/ext/build_defaults.yaml @@ -9,7 +9,7 @@ gpg_name: 'info@puppetlabs.com' gpg_key: '4BD6EC30' sign_tar: FALSE # a space separated list of mock configs -final_mocks: 'pl-el-5-i386 pl-el-5-x86_64 pl-el-6-i386 pl-el-6-x86_64 pl-fedora-18-i386 pl-fedora-18-x86_64 pl-fedora-19-i386 pl-fedora-19-x86_64 pl-fedora-20-i386 pl-fedora-20-x86_64' +final_mocks: 'pl-el-5-i386 pl-el-5-x86_64 pl-el-6-i386 pl-el-6-x86_64 pl-el-7-x86_64 pl-fedora-18-i386 pl-fedora-18-x86_64 pl-fedora-19-i386 pl-fedora-19-x86_64 pl-fedora-20-i386 pl-fedora-20-x86_64' yum_host: 'yum.puppetlabs.com' yum_repo_path: '/opt/repository/yum/' build_gem: TRUE From dd58ded10dca7eed359bd270fd700867eb75e7f5 Mon Sep 17 00:00:00 2001 From: Spencer Krum Date: Tue, 28 Jan 2014 13:01:15 -0800 Subject: [PATCH 1584/3753] Minor doc fix --- lib/facter/core/execution.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/core/execution.rb b/lib/facter/core/execution.rb index 7173730d27..65a5dcf03e 100644 --- a/lib/facter/core/execution.rb +++ b/lib/facter/core/execution.rb @@ -89,7 +89,7 @@ def absolute_path?(path, platform=nil) # Given a command line, this returns the command line with the # executable written as an absolute path. If the executable contains - # spaces, it has be but in double quotes to be properly recognized. + # spaces, it has to be put in double quotes to be properly recognized. # # @param command [String] the command line # From 5b90132eeabcfd9cb34b1c6357d0ea69baa65cb9 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 28 Jan 2014 16:11:21 -0800 Subject: [PATCH 1585/3753] (FACT-94) Unvendor CFPropertyList Vendoring CFPropertyList inside of Facter has proven to be very problematic. Since it's possible to treat CFPropertyList as a real dependency via gems and can be bundled at build time for DMGs, there's no real need to vendor it inside of Facter. This commit removes the vendored code and prevents facter/util/macosx from being loaded on non-osx platforms. --- lib/facter/macosx.rb | 4 +- lib/facter/util/cfpropertylist.rb | 6 - lib/facter/util/cfpropertylist/LICENSE | 19 - lib/facter/util/cfpropertylist/README | 44 -- lib/facter/util/cfpropertylist/Rakefile | 44 -- lib/facter/util/cfpropertylist/THANKS | 7 - .../util/cfpropertylist/lib/cfpropertylist.rb | 6 - .../lib/rbBinaryCFPropertyList.rb | 562 ------------------ .../util/cfpropertylist/lib/rbCFPlistError.rb | 26 - .../cfpropertylist/lib/rbCFPropertyList.rb | 407 ------------- .../util/cfpropertylist/lib/rbCFTypes.rb | 244 -------- .../util/cfpropertylist/lib/rbLibXMLParser.rb | 135 ----- .../cfpropertylist/lib/rbNokogiriParser.rb | 140 ----- .../util/cfpropertylist/lib/rbREXMLParser.rb | 136 ----- lib/facter/util/macosx.rb | 11 +- spec/unit/util/macosx_spec.rb | 2 +- 16 files changed, 8 insertions(+), 1785 deletions(-) delete mode 100644 lib/facter/util/cfpropertylist.rb delete mode 100644 lib/facter/util/cfpropertylist/LICENSE delete mode 100644 lib/facter/util/cfpropertylist/README delete mode 100644 lib/facter/util/cfpropertylist/Rakefile delete mode 100644 lib/facter/util/cfpropertylist/THANKS delete mode 100644 lib/facter/util/cfpropertylist/lib/cfpropertylist.rb delete mode 100644 lib/facter/util/cfpropertylist/lib/rbBinaryCFPropertyList.rb delete mode 100644 lib/facter/util/cfpropertylist/lib/rbCFPlistError.rb delete mode 100644 lib/facter/util/cfpropertylist/lib/rbCFPropertyList.rb delete mode 100644 lib/facter/util/cfpropertylist/lib/rbCFTypes.rb delete mode 100644 lib/facter/util/cfpropertylist/lib/rbLibXMLParser.rb delete mode 100644 lib/facter/util/cfpropertylist/lib/rbNokogiriParser.rb delete mode 100644 lib/facter/util/cfpropertylist/lib/rbREXMLParser.rb diff --git a/lib/facter/macosx.rb b/lib/facter/macosx.rb index 74a0cc7b42..c106f9bbd1 100644 --- a/lib/facter/macosx.rb +++ b/lib/facter/macosx.rb @@ -24,9 +24,9 @@ # at this point in time. # In particular, Installed Software might be an interesting addition. -require 'facter/util/macosx' - if Facter.value(:kernel) == "Darwin" + require 'facter/util/macosx' + Facter::Util::Macosx.hardware_overview.each do |fact, value| Facter.add("sp_#{fact}") do confine :kernel => :darwin diff --git a/lib/facter/util/cfpropertylist.rb b/lib/facter/util/cfpropertylist.rb deleted file mode 100644 index 337e0bb3ae..0000000000 --- a/lib/facter/util/cfpropertylist.rb +++ /dev/null @@ -1,6 +0,0 @@ -# -*- coding: utf-8 -*- - -require File.join(File.dirname(__FILE__), 'cfpropertylist', 'lib', 'rbCFPropertyList.rb') - - -# eof diff --git a/lib/facter/util/cfpropertylist/LICENSE b/lib/facter/util/cfpropertylist/LICENSE deleted file mode 100644 index ba6ffb28d3..0000000000 --- a/lib/facter/util/cfpropertylist/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2010 Christian Kruse, - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - diff --git a/lib/facter/util/cfpropertylist/README b/lib/facter/util/cfpropertylist/README deleted file mode 100644 index 4763f88fd3..0000000000 --- a/lib/facter/util/cfpropertylist/README +++ /dev/null @@ -1,44 +0,0 @@ -CFPropertyList implementation -class to read, manipulate and write both XML and binary property list -files (plist(5)) as defined by Apple. Have a look at CFPropertyList::List -for more documentation. - -== Installation - -You could either use ruby gems and install it via - - gem install CFPropertyList - -or you could clone this repository and place it somewhere in your load path. - -== Example - require 'cfpropertylist' - - # create a arbitrary data structure of basic data types - data = { - 'name' => 'John Doe', - 'missing' => true, - 'last_seen' => Time.now, - 'friends' => ['Jane Doe','Julian Doe'], - 'likes' => { - 'me' => false - } - } - - # create CFPropertyList::List object - plist = CFPropertyList::List.new - - # call CFPropertyList.guess() to create corresponding CFType values - plist.value = CFPropertyList.guess(data) - - # write plist to file - plist.save("example.plist", CFPropertyList::List::FORMAT_BINARY) - - # … later, read it again - plist = CFPropertyList::List.new(:file => "example.plist") - data = CFPropertyList.native_types(plist.value) - -Author:: Christian Kruse (mailto:cjk@wwwtech.de) -Copyright:: Copyright (c) 2010 -License:: MIT License - diff --git a/lib/facter/util/cfpropertylist/Rakefile b/lib/facter/util/cfpropertylist/Rakefile deleted file mode 100644 index 6dd90bd8a8..0000000000 --- a/lib/facter/util/cfpropertylist/Rakefile +++ /dev/null @@ -1,44 +0,0 @@ -require 'rubygems' - -require 'rubygems/package_task' -require 'rdoc/task' -require 'rake/testtask' - -spec = Gem::Specification.new do |s| - s.name = "CFPropertyList" - s.version = "2.1" - s.author = "Christian Kruse" - s.email = "cjk@wwwtech.de" - s.homepage = "http://github.com/ckruse/CFPropertyList" - s.platform = Gem::Platform::RUBY - s.summary = "Read, write and manipulate both binary and XML property lists as defined by apple" - s.description = "This is a module to read, write and manipulate both binary and XML property lists as defined by apple." - s.files = FileList["lib/*"].to_a - s.require_path = "lib" - #s.autorequire = "name" - #s.test_files = FileList["{test}/**/*test.rb"].to_a - s.has_rdoc = true - s.extra_rdoc_files = ["README"] - s.add_development_dependency("rake",">=0.7.0") -end - -desc 'Generate RDoc documentation for the CFPropertyList module.' -Rake::RDocTask.new do |rdoc| - files = ['README', 'LICENSE', 'lib/*.rb'] - rdoc.rdoc_files.add(files) - rdoc.main = 'README' - rdoc.title = 'CFPropertyList RDoc' - rdoc.rdoc_dir = 'doc' - rdoc.options << '--line-numbers' << '--inline-source' << '-c utf8' -end - -Gem::PackageTask.new(spec) do |pkg| - pkg.need_tar = true -end - -Rake::TestTask.new do |test| - test.libs << 'test' - test.test_files = Dir.glob('test/test*.rb') -end - -# eof diff --git a/lib/facter/util/cfpropertylist/THANKS b/lib/facter/util/cfpropertylist/THANKS deleted file mode 100644 index c981a51af5..0000000000 --- a/lib/facter/util/cfpropertylist/THANKS +++ /dev/null @@ -1,7 +0,0 @@ -Special thanks to: - -Steve Madsen for providing a lot of performance patches and bugfixes! -Have a look at his Github account: - - - diff --git a/lib/facter/util/cfpropertylist/lib/cfpropertylist.rb b/lib/facter/util/cfpropertylist/lib/cfpropertylist.rb deleted file mode 100644 index 29fd8a1a9f..0000000000 --- a/lib/facter/util/cfpropertylist/lib/cfpropertylist.rb +++ /dev/null @@ -1,6 +0,0 @@ -# -*- coding: utf-8 -*- - -require File.dirname(__FILE__) + '/rbCFPropertyList.rb' - - -# eof diff --git a/lib/facter/util/cfpropertylist/lib/rbBinaryCFPropertyList.rb b/lib/facter/util/cfpropertylist/lib/rbBinaryCFPropertyList.rb deleted file mode 100644 index 98f5c87d88..0000000000 --- a/lib/facter/util/cfpropertylist/lib/rbBinaryCFPropertyList.rb +++ /dev/null @@ -1,562 +0,0 @@ -# -*- coding: utf-8 -*- - -module Facter::Util::CFPropertyList - # Binary PList parser class - class Binary - # Read a binary plist file - def load(opts) - @unique_table = {} - @count_objects = 0 - @object_refs = 0 - - @written_object_count = 0 - @object_table = [] - @object_ref_size = 0 - - @offsets = [] - - fd = nil - if(opts.has_key?(:file)) - fd = File.open(opts[:file],"rb") - file = opts[:file] - else - fd = StringIO.new(opts[:data],"rb") - file = "" - end - - # first, we read the trailer: 32 byte from the end - fd.seek(-32,IO::SEEK_END) - buff = fd.read(32) - - offset_size, object_ref_size, number_of_objects, top_object, table_offset = buff.unpack "x6CCx4Nx4Nx4N" - - # after that, get the offset table - fd.seek(table_offset, IO::SEEK_SET) - coded_offset_table = fd.read(number_of_objects * offset_size) - raise CFFormatError.new("#{file}: Format error!") unless coded_offset_table.bytesize == number_of_objects * offset_size - - @count_objects = number_of_objects - - # decode offset table - formats = ["","C*","n*","(H6)*","N*"] - @offsets = coded_offset_table.unpack(formats[offset_size]) - if(offset_size == 3) - 0.upto(@offsets.size-1) { |i| @offsets[i] = @offsets[i].to_i(16) } - end - - @object_ref_size = object_ref_size - val = read_binary_object_at(file,fd,top_object) - - fd.close - val - end - - - # Convert Facter::Util::CFPropertyList to binary format; since we have to count our objects we simply unique CFDictionary and CFArray - def to_str(opts={}) - @unique_table = {} - @count_objects = 0 - @object_refs = 0 - - @written_object_count = 0 - @object_table = [] - - @offsets = [] - - binary_str = "bplist00" - - @object_refs = count_object_refs(opts[:root]) - - opts[:root].to_binary(self) - - next_offset = 8 - offsets = @object_table.map do |object| - offset = next_offset - next_offset += object.bytesize - offset - end - binary_str << @object_table.join - - table_offset = next_offset - offset_size = Binary.bytes_needed(table_offset) - - if offset_size < 8 - # Fast path: encode the entire offset array at once. - binary_str << offsets.pack((%w(C n N N)[offset_size - 1]) + '*') - else - # Slow path: host may be little or big endian, must pack each offset - # separately. - offsets.each do |offset| - binary_str << "#{Binary.pack_it_with_size(offset_size,offset)}" - end - end - - binary_str << [offset_size, object_ref_size(@object_refs)].pack("x6CC") - binary_str << [@object_table.size].pack("x4N") - binary_str << [0].pack("x4N") - binary_str << [table_offset].pack("x4N") - - binary_str - end - - def object_ref_size object_refs - Binary.bytes_needed(object_refs) - end - - # read a „null” type (i.e. null byte, marker byte, bool value) - def read_binary_null_type(length) - case length - when 0 then 0 # null byte - when 8 then CFBoolean.new(false) - when 9 then CFBoolean.new(true) - when 15 then 15 # fill type - else - raise CFFormatError.new("unknown null type: #{length}") - end - end - protected :read_binary_null_type - - # read a binary int value - def read_binary_int(fname,fd,length) - if length > 3 - raise CFFormatError.new("Integer greater than 8 bytes: #{length}") - end - - nbytes = 1 << length - - buff = fd.read(nbytes) - - CFInteger.new( - case length - when 0 then buff.unpack("C")[0] - when 1 then buff.unpack("n")[0] - when 2 then buff.unpack("N")[0] - when 3 - hiword,loword = buff.unpack("NN") - if (hiword & 0x80000000) != 0 - # 8 byte integers are always signed, and are negative when bit 63 is - # set. Decoding into either a Fixnum or Bignum is tricky, however, - # because the size of a Fixnum varies among systems, and Ruby - # doesn't consider the number to be negative, and won't sign extend. - -(2**63 - ((hiword & 0x7fffffff) << 32 | loword)) - else - hiword << 32 | loword - end - end - ) - end - protected :read_binary_int - - # read a binary real value - def read_binary_real(fname,fd,length) - raise CFFormatError.new("Real greater than 8 bytes: #{length}") if length > 3 - - nbytes = 1 << length - buff = fd.read(nbytes) - - CFReal.new( - case length - when 0 # 1 byte float? must be an error - raise CFFormatError.new("got #{length+1} byte float, must be an error!") - when 1 # 2 byte float? must be an error - raise CFFormatError.new("got #{length+1} byte float, must be an error!") - when 2 then - buff.reverse.unpack("f")[0] - when 3 then - buff.reverse.unpack("d")[0] - else - fail "unexpected length: #{length}" - end - ) - end - protected :read_binary_real - - # read a binary date value - def read_binary_date(fname,fd,length) - raise CFFormatError.new("Date greater than 8 bytes: #{length}") if length > 3 - - nbytes = 1 << length - buff = fd.read(nbytes) - - CFDate.new( - case length - when 0 then # 1 byte CFDate is an error - raise CFFormatError.new("#{length+1} byte CFDate, error") - when 1 then # 2 byte CFDate is an error - raise CFFormatError.new("#{length+1} byte CFDate, error") - when 2 then - buff.reverse.unpack("f")[0] - when 3 then - buff.reverse.unpack("d")[0] - end, - CFDate::TIMESTAMP_APPLE - ) - end - protected :read_binary_date - - # Read a binary data value - def read_binary_data(fname,fd,length) - CFData.new(read_fd(fd, length), CFData::DATA_RAW) - end - protected :read_binary_data - - def read_fd fd, length - length > 0 ? fd.read(length) : "" - end - - # Read a binary string value - def read_binary_string(fname,fd,length) - buff = read_fd fd, length - @unique_table[buff] = true unless @unique_table.has_key?(buff) - CFString.new(buff) - end - protected :read_binary_string - - # Convert the given string from one charset to another - def Binary.charset_convert(str,from,to="UTF-8") - return str.clone.force_encoding(from).encode(to) if str.respond_to?("encode") - Iconv.conv(to,from,str) - end - - # Count characters considering character set - def Binary.charset_strlen(str,charset="UTF-8") - if str.respond_to?(:encode) - size = str.length - else - utf8_str = Iconv.conv("UTF-8",charset,str) - size = utf8_str.scan(/./mu).size - end - - # UTF-16 code units in the range D800-DBFF are the beginning of - # a surrogate pair, and count as one additional character for - # length calculation. - if charset =~ /^UTF-16/ - if str.respond_to?(:encode) - str.bytes.to_a.each_slice(2) { |pair| size += 1 if (0xd8..0xdb).include?(pair[0]) } - else - str.split('').each_slice(2) { |pair| size += 1 if ("\xd8".."\xdb").include?(pair[0]) } - end - end - - size - end - - # Read a unicode string value, coded as UTF-16BE - def read_binary_unicode_string(fname,fd,length) - # The problem is: we get the length of the string IN CHARACTERS; - # since a char in UTF-16 can be 16 or 32 bit long, we don't really know - # how long the string is in bytes - buff = fd.read(2*length) - - @unique_table[buff] = true unless @unique_table.has_key?(buff) - CFString.new(Binary.charset_convert(buff,"UTF-16BE","UTF-8")) - end - protected :read_binary_unicode_string - - # Read an binary array value, including contained objects - def read_binary_array(fname,fd,length) - ary = [] - - # first: read object refs - if(length != 0) - buff = fd.read(length * @object_ref_size) - objects = buff.unpack(@object_ref_size == 1 ? "C*" : "n*") - - # now: read objects - 0.upto(length-1) do |i| - object = read_binary_object_at(fname,fd,objects[i]) - ary.push object - end - end - - CFArray.new(ary) - end - protected :read_binary_array - - # Read a dictionary value, including contained objects - def read_binary_dict(fname,fd,length) - dict = {} - - # first: read keys - if(length != 0) then - buff = fd.read(length * @object_ref_size) - keys = buff.unpack(@object_ref_size == 1 ? "C*" : "n*") - - # second: read object refs - buff = fd.read(length * @object_ref_size) - objects = buff.unpack(@object_ref_size == 1 ? "C*" : "n*") - - # read real keys and objects - 0.upto(length-1) do |i| - key = read_binary_object_at(fname,fd,keys[i]) - object = read_binary_object_at(fname,fd,objects[i]) - dict[key.value] = object - end - end - - CFDictionary.new(dict) - end - protected :read_binary_dict - - # Read an object type byte, decode it and delegate to the correct reader function - def read_binary_object(fname,fd) - # first: read the marker byte - buff = fd.read(1) - - object_length = buff.unpack("C*") - object_length = object_length[0] & 0xF - - buff = buff.unpack("H*") - object_type = buff[0][0].chr - - if(object_type != "0" && object_length == 15) then - object_length = read_binary_object(fname,fd) - object_length = object_length.value - end - - case object_type - when '0' # null, false, true, fillbyte - read_binary_null_type(object_length) - when '1' # integer - read_binary_int(fname,fd,object_length) - when '2' # real - read_binary_real(fname,fd,object_length) - when '3' # date - read_binary_date(fname,fd,object_length) - when '4' # data - read_binary_data(fname,fd,object_length) - when '5' # byte string, usually utf8 encoded - read_binary_string(fname,fd,object_length) - when '6' # unicode string (utf16be) - read_binary_unicode_string(fname,fd,object_length) - when 'a' # array - read_binary_array(fname,fd,object_length) - when 'd' # dictionary - read_binary_dict(fname,fd,object_length) - end - end - protected :read_binary_object - - # Read an object type byte at position $pos, decode it and delegate to the correct reader function - def read_binary_object_at(fname,fd,pos) - position = @offsets[pos] - fd.seek(position,IO::SEEK_SET) - read_binary_object(fname,fd) - end - protected :read_binary_object_at - - # pack an +int+ of +nbytes+ with size - def Binary.pack_it_with_size(nbytes,int) - case nbytes - when 1 then [int].pack('c') - when 2 then [int].pack('n') - when 4 then [int].pack('N') - when 8 - [int >> 32, int & 0xFFFFFFFF].pack('NN') - else - raise CFFormatError.new("Don't know how to pack #{nbytes} byte integer") - end - end - - def Binary.pack_int_array_with_size(nbytes, array) - case nbytes - when 1 then array.pack('C*') - when 2 then array.pack('n*') - when 4 then array.pack('N*') - when 8 - array.map { |int| [int >> 32, int & 0xFFFFFFFF].pack('NN') }.join - else - raise CFFormatError.new("Don't know how to pack #{nbytes} byte integer") - end - end - - # calculate how many bytes are needed to save +count+ - def Binary.bytes_needed(count) - case - when count < 2**8 then 1 - when count < 2**16 then 2 - when count < 2**32 then 4 - when count < 2**64 then 8 - else - raise CFFormatError.new("Data size too large: #{count}") - end - end - - # Create a type byte for binary format as defined by apple - def Binary.type_bytes(type, length) - if length < 15 - [(type << 4) | length].pack('C') - else - bytes = [(type << 4) | 0xF] - if length <= 0xFF - bytes.push(0x10, length).pack('CCC') # 1 byte length - elsif length <= 0xFFFF - bytes.push(0x11, length).pack('CCn') # 2 byte length - elsif length <= 0xFFFFFFFF - bytes.push(0x12, length).pack('CCN') # 4 byte length - elsif length <= 0x7FFFFFFFFFFFFFFF - bytes.push(0x13, length >> 32, length & 0xFFFFFFFF).pack('CCNN') # 8 byte length - else - raise CFFormatError.new("Integer too large: #{int}") - end - end - end - - def count_object_refs(object) - case object - when CFArray - contained_refs = 0 - object.value.each do |element| - if CFArray === element || CFDictionary === element - contained_refs += count_object_refs(element) - end - end - return object.value.size + contained_refs - when CFDictionary - contained_refs = 0 - object.value.each_value do |value| - if CFArray === value || CFDictionary === value - contained_refs += count_object_refs(value) - end - end - return object.value.keys.size * 2 + contained_refs - else - return 0 - end - end - - def Binary.ascii_string?(str) - if str.respond_to?(:ascii_only?) - str.ascii_only? - else - str !~ /[\x80-\xFF]/mn - end - end - - # Uniques and transforms a string value to binary format and adds it to the object table - def string_to_binary(val) - val = val.to_s - - @unique_table[val] ||= begin - if !Binary.ascii_string?(val) - utf8_strlen = Binary.charset_strlen(val, "UTF-8") - val = Binary.charset_convert(val,"UTF-8","UTF-16BE") - bdata = Binary.type_bytes(0b0110, Binary.charset_strlen(val,"UTF-16BE")) - - val.force_encoding("ASCII-8BIT") if val.respond_to?("encode") - @object_table[@written_object_count] = bdata << val - else - utf8_strlen = val.bytesize - bdata = Binary.type_bytes(0b0101,val.bytesize) - @object_table[@written_object_count] = bdata << val - end - @written_object_count += 1 - @written_object_count - 1 - end - end - - # Codes an integer to binary format - def int_to_binary(value) - nbytes = 0 - nbytes = 1 if value > 0xFF # 1 byte integer - nbytes += 1 if value > 0xFFFF # 4 byte integer - nbytes += 1 if value > 0xFFFFFFFF # 8 byte integer - nbytes = 3 if value < 0 # 8 byte integer, since signed - - Binary.type_bytes(0b0001, nbytes) << - if nbytes < 3 - [value].pack( - if nbytes == 0 then "C" - elsif nbytes == 1 then "n" - else "N" - end - ) - else - # 64 bit signed integer; we need the higher and the lower 32 bit of the value - high_word = value >> 32 - low_word = value & 0xFFFFFFFF - [high_word,low_word].pack("NN") - end - end - - # Codes a real value to binary format - def real_to_binary(val) - Binary.type_bytes(0b0010,3) << [val].pack("d").reverse - end - - # Converts a numeric value to binary and adds it to the object table - def num_to_binary(value) - @object_table[@written_object_count] = - if value.is_a?(CFInteger) - int_to_binary(value.value) - else - real_to_binary(value.value) - end - - @written_object_count += 1 - @written_object_count - 1 - end - - # Convert date value (apple format) to binary and adds it to the object table - def date_to_binary(val) - val = val.getutc.to_f - CFDate::DATE_DIFF_APPLE_UNIX # CFDate is a real, number of seconds since 01/01/2001 00:00:00 GMT - - @object_table[@written_object_count] = - (Binary.type_bytes(0b0011, 3) << [val].pack("d").reverse) - - @written_object_count += 1 - @written_object_count - 1 - end - - # Convert a bool value to binary and add it to the object table - def bool_to_binary(val) - - @object_table[@written_object_count] = val ? "\x9" : "\x8" # 0x9 is 1001, type indicator for true; 0x8 is 1000, type indicator for false - @written_object_count += 1 - @written_object_count - 1 - end - - # Convert data value to binary format and add it to the object table - def data_to_binary(val) - @object_table[@written_object_count] = - (Binary.type_bytes(0b0100, val.bytesize) << val) - - @written_object_count += 1 - @written_object_count - 1 - end - - # Convert array to binary format and add it to the object table - def array_to_binary(val) - saved_object_count = @written_object_count - @written_object_count += 1 - #@object_refs += val.value.size - - values = val.value.map { |v| v.to_binary(self) } - bdata = Binary.type_bytes(0b1010, val.value.size) << - Binary.pack_int_array_with_size(object_ref_size(@object_refs), - values) - - @object_table[saved_object_count] = bdata - saved_object_count - end - - # Convert dictionary to binary format and add it to the object table - def dict_to_binary(val) - saved_object_count = @written_object_count - @written_object_count += 1 - - #@object_refs += val.value.keys.size * 2 - - keys_and_values = val.value.keys.map { |k| CFString.new(k).to_binary(self) } - keys_and_values.concat(val.value.values.map { |v| v.to_binary(self) }) - - bdata = Binary.type_bytes(0b1101,val.value.size) << - Binary.pack_int_array_with_size(object_ref_size(@object_refs), keys_and_values) - - @object_table[saved_object_count] = bdata - return saved_object_count - end - end -end - -# eof diff --git a/lib/facter/util/cfpropertylist/lib/rbCFPlistError.rb b/lib/facter/util/cfpropertylist/lib/rbCFPlistError.rb deleted file mode 100644 index 07ee1c141f..0000000000 --- a/lib/facter/util/cfpropertylist/lib/rbCFPlistError.rb +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Exceptions used: -# CFPlistError:: General base exception -# CFFormatError:: Format error -# CFTypeError:: Type error -# -# Easy and simple :-) -# -# Author:: Christian Kruse (mailto:cjk@wwwtech.de) -# Copyright:: Copyright (c) 2010 -# License:: MIT License - -# general plist error. All exceptions thrown are derived from this class. -class CFPlistError < Exception -end - -# Exception thrown when format errors occur -class CFFormatError < CFPlistError -end - -# Exception thrown when type errors occur -class CFTypeError < CFPlistError -end - -# eof diff --git a/lib/facter/util/cfpropertylist/lib/rbCFPropertyList.rb b/lib/facter/util/cfpropertylist/lib/rbCFPropertyList.rb deleted file mode 100644 index 3acdbba3d9..0000000000 --- a/lib/facter/util/cfpropertylist/lib/rbCFPropertyList.rb +++ /dev/null @@ -1,407 +0,0 @@ -# -*- coding: utf-8 -*- - -require 'kconv' -require 'date' -require 'time' - -# -# Facter::Util::CFPropertyList implementation -# -# class to read, manipulate and write both XML and binary property list -# files (plist(5)) as defined by Apple. Have a look at Facter::Util::CFPropertyList::List -# for more documentation. -# -# == Example -# require 'cfpropertylist' -# -# # create a arbitrary data structure of basic data types -# data = { -# 'name' => 'John Doe', -# 'missing' => true, -# 'last_seen' => Time.now, -# 'friends' => ['Jane Doe','Julian Doe'], -# 'likes' => { -# 'me' => false -# } -# } -# -# # create Facter::Util::CFPropertyList::List object -# plist = Facter::Util::CFPropertyList::List.new -# -# # call Facter::Util::CFPropertyList.guess() to create corresponding CFType values -# # pass in optional :convert_unknown_to_string => true to convert things like symbols into strings. -# plist.value = Facter::Util::CFPropertyList.guess(data) -# -# # write plist to file -# plist.save("example.plist", Facter::Util::CFPropertyList::List::FORMAT_BINARY) -# -# # … later, read it again -# plist = Facter::Util::CFPropertyList::List.new(:file => "example.plist") -# data = Facter::Util::CFPropertyList.native_types(plist.value) -# -# Author:: Christian Kruse (mailto:cjk@wwwtech.de) -# Copyright:: Copyright (c) 2010 -# License:: MIT License -module Facter::Util::CFPropertyList - # interface class for PList parsers - class ParserInterface - # load a plist - def load(opts={}) - return "" - end - - # convert a plist to string - def to_str(opts={}) - return true - end - end - - class XMLParserInterface < ParserInterface - def new_node(name) - end - - def new_text(val) - end - - def append_node(parent, child) - end - end -end - -class String - unless("".respond_to?(:blob) && "".respond_to?(:blob=)) then - # The blob status of this string (to set to true if a binary string) - attr_accessor :blob - end - - unless("".respond_to?(:blob?)) then - # Returns whether or not +str+ is a blob. - # @return [true,false] If true, this string contains binary data. If false, its a regular string - def blob? - blob - end - end - - unless("".respond_to?(:bytesize)) then - def bytesize - self.length - end - end -end - -dirname = File.dirname(__FILE__) -require dirname + '/rbCFPlistError.rb' -require dirname + '/rbCFTypes.rb' -require dirname + '/rbBinaryCFPropertyList.rb' - -require 'iconv' unless "".respond_to?("encode") - -begin - Enumerable::Enumerator.new([]) -rescue NameError => e - module Enumerable - class Enumerator - end - end -end - -begin - require dirname + '/rbLibXMLParser.rb' - try_nokogiri = false -rescue LoadError => e - try_nokogiri = true -end - -if try_nokogiri then - begin - require dirname + '/rbNokogiriParser.rb' - rescue LoadError => e - require dirname + '/rbREXMLParser.rb' - end -end - - -module Facter::Util::CFPropertyList - # Create CFType hierarchy by guessing the correct CFType, e.g. - # - # x = { - # 'a' => ['b','c','d'] - # } - # cftypes = Facter::Util::CFPropertyList.guess(x) - # - # pass optional options hash. Only possible value actually: - # +convert_unknown_to_string+:: Convert unknown objects to string calling to_str() - # +converter_method+:: Convert unknown objects to known objects calling +method_name+ - # - # cftypes = Facter::Util::CFPropertyList.guess(x,:convert_unknown_to_string => true,:converter_method => :to_hash, :converter_with_opts => true) - def guess(object, options = {}) - case object - when Fixnum, Integer then CFInteger.new(object) - when Float then CFReal.new(object) - when TrueClass, FalseClass then CFBoolean.new(object) - - when String - object.blob? ? CFData.new(object, CFData::DATA_RAW) : CFString.new(object) - - when Time, DateTime, Date then CFDate.new(object) - - when Array, Enumerator, Enumerable::Enumerator - ary = Array.new - object.each do |o| - ary.push Facter::Util::CFPropertyList.guess(o, options) - end - CFArray.new(ary) - - when Hash - hsh = Hash.new - object.each_pair do |k,v| - k = k.to_s if k.is_a?(Symbol) - hsh[k] = Facter::Util::CFPropertyList.guess(v, options) - end - CFDictionary.new(hsh) - else - case - when Object.const_defined?('BigDecimal') && object.is_a?(BigDecimal) - CFReal.new(object) - when object.respond_to?(:read) - raw_data = object.read - # treat the data as a bytestring (ASCII-8BIT) if Ruby supports it. Do this by forcing - # the encoding, on the assumption that the bytes were read correctly, and just tagged with - # an inappropriate encoding, rather than transcoding. - raw_data.force_encoding(Encoding::ASCII_8BIT) if raw_data.respond_to?(:force_encoding) - CFData.new(raw_data, CFData::DATA_RAW) - when options[:converter_method] && object.respond_to?(options[:converter_method]) - if options[:converter_with_opts] - Facter::Util::CFPropertyList.guess(object.send(options[:converter_method],options),options) - else - Facter::Util::CFPropertyList.guess(object.send(options[:converter_method]),options) - end - when options[:convert_unknown_to_string] - CFString.new(object.to_s) - else - raise CFTypeError.new("Unknown class #{object.class.to_s}. Try using :convert_unknown_to_string if you want to use unknown object types!") - end - end - end - - # Converts a CFType hiercharchy to native Ruby types - def native_types(object,keys_as_symbols=false) - return if object.nil? - - if(object.is_a?(CFDate) || object.is_a?(CFString) || object.is_a?(CFInteger) || object.is_a?(CFReal) || object.is_a?(CFBoolean)) then - return object.value - elsif(object.is_a?(CFData)) then - return object.decoded_value - elsif(object.is_a?(CFArray)) then - ary = [] - object.value.each do - |v| - ary.push Facter::Util::CFPropertyList.native_types(v) - end - - return ary - elsif(object.is_a?(CFDictionary)) then - hsh = {} - object.value.each_pair do - |k,v| - k = k.to_sym if keys_as_symbols - hsh[k] = Facter::Util::CFPropertyList.native_types(v) - end - - return hsh - end - end - - module_function :guess, :native_types - - # Class representing a Facter::Util::CFPropertyList. Instanciate with #new - class List - # Format constant for binary format - FORMAT_BINARY = 1 - - # Format constant for XML format - FORMAT_XML = 2 - - # Format constant for automatic format recognizing - FORMAT_AUTO = 0 - - @@parsers = [Binary,XML] - - # Path of PropertyList - attr_accessor :filename - # Path of PropertyList - attr_accessor :format - # the root value in the plist file - attr_accessor :value - - # initialize a new Facter::Util::CFPropertyList, arguments are: - # - # :file:: Parse a file - # :format:: Format is one of FORMAT_BINARY or FORMAT_XML. Defaults to FORMAT_AUTO - # :data:: Parse a string - # - # All arguments are optional - def initialize(opts={}) - @filename = opts[:file] - @format = opts[:format] || FORMAT_AUTO - @data = opts[:data] - - load(@filename) unless @filename.nil? - load_str(@data) unless @data.nil? - end - - # Load an XML PropertyList - # filename = nil:: The filename to read from; if nil, read from the file defined by instance variable +filename+ - def load_xml(filename=nil) - load(filename,List::FORMAT_XML) - end - - # read a binary plist file - # filename = nil:: The filename to read from; if nil, read from the file defined by instance variable +filename+ - def load_binary(filename=nil) - load(filename,List::FORMAT_BINARY) - end - - # load a plist from a XML string - # str:: The string containing the plist - def load_xml_str(str=nil) - load_str(str,List::FORMAT_XML) - end - - # load a plist from a binary string - # str:: The string containing the plist - def load_binary_str(str=nil) - load_str(str,List::FORMAT_BINARY) - end - - # load a plist from a string - # str = nil:: The string containing the plist - # format = nil:: The format of the plist - def load_str(str=nil,format=nil) - str = @data if str.nil? - format = @format if format.nil? - - @value = {} - case format - when List::FORMAT_BINARY, List::FORMAT_XML then - prsr = @@parsers[format-1].new - @value = prsr.load({:data => str}) - - when List::FORMAT_AUTO then # what we now do is ugly, but neccessary to recognize the file format - filetype = str[0..5] - version = str[6..7] - - prsr = nil - if filetype == "bplist" then - raise CFFormatError.new("Wong file version #{version}") unless version == "00" - prsr = Binary.new - else - prsr = XML.new - end - - @value = prsr.load({:data => str}) - end - end - - # Read a plist file - # file = nil:: The filename of the file to read. If nil, use +filename+ instance variable - # format = nil:: The format of the plist file. Auto-detect if nil - def load(file=nil,format=nil) - file = @filename if file.nil? - format = @format if format.nil? - @value = {} - - raise IOError.new("File #{file} not readable!") unless File.readable? file - - case format - when List::FORMAT_BINARY, List::FORMAT_XML then - prsr = @@parsers[format-1].new - @value = prsr.load({:file => file}) - - when List::FORMAT_AUTO then # what we now do is ugly, but neccessary to recognize the file format - magic_number = IO.read(file,8) - filetype = magic_number[0..5] - version = magic_number[6..7] - - prsr = nil - if filetype == "bplist" then - raise CFFormatError.new("Wong file version #{version}") unless version == "00" - prsr = Binary.new - else - prsr = XML.new - end - - @value = prsr.load({:file => file}) - end - end - - # Serialize Facter::Util::CFPropertyList object to specified format and write it to file - # file = nil:: The filename of the file to write to. Uses +filename+ instance variable if nil - # format = nil:: The format to save in. Uses +format+ instance variable if nil - def save(file=nil,format=nil,opts={}) - format = @format if format.nil? - file = @filename if file.nil? - - raise CFFormatError.new("Format #{format} not supported, use List::FORMAT_BINARY or List::FORMAT_XML") if format != FORMAT_BINARY && format != FORMAT_XML - - if(!File.exists?(file)) then - raise IOError.new("File #{file} not writable!") unless File.writable?(File.dirname(file)) - elsif(!File.writable?(file)) then - raise IOError.new("File #{file} not writable!") - end - - opts[:root] = @value - prsr = @@parsers[format-1].new - content = prsr.to_str(opts) - - File.open(file, 'wb') { - |fd| - fd.write content - } - end - - # convert plist to string - # format = List::FORMAT_BINARY:: The format to save the plist - # opts={}:: Pass parser options - def to_str(format=List::FORMAT_BINARY,opts={}) - prsr = @@parsers[format-1].new - opts[:root] = @value - return prsr.to_str(opts) - end - end -end - -class Array - # convert an array to plist format - def to_plist(options={}) - options[:plist_format] ||= Facter::Util::CFPropertyList::List::FORMAT_BINARY - - plist = Facter::Util::CFPropertyList::List.new - plist.value = Facter::Util::CFPropertyList.guess(self, options) - plist.to_str(options[:plist_format]) - end -end - -class Enumerator - # convert an array to plist format - def to_plist(options={}) - options[:plist_format] ||= Facter::Util::CFPropertyList::List::FORMAT_BINARY - - plist = Facter::Util::CFPropertyList::List.new - plist.value = Facter::Util::CFPropertyList.guess(self, options) - plist.to_str(options[:plist_format]) - end -end - -class Hash - # convert a hash to plist format - def to_plist(options={}) - options[:plist_format] ||= Facter::Util::CFPropertyList::List::FORMAT_BINARY - - plist = Facter::Util::CFPropertyList::List.new - plist.value = Facter::Util::CFPropertyList.guess(self, options) - plist.to_str(options[:plist_format]) - end -end - -# eof diff --git a/lib/facter/util/cfpropertylist/lib/rbCFTypes.rb b/lib/facter/util/cfpropertylist/lib/rbCFTypes.rb deleted file mode 100644 index 547801f030..0000000000 --- a/lib/facter/util/cfpropertylist/lib/rbCFTypes.rb +++ /dev/null @@ -1,244 +0,0 @@ -# -*- coding: utf-8 -*- -# -# CFTypes, e.g. CFString, CFInteger -# needed to create unambiguous plists -# -# Author:: Christian Kruse (mailto:cjk@wwwtech.de) -# Copyright:: Copyright (c) 2009 -# License:: MIT License - -require 'base64' - -module Facter::Util::CFPropertyList - # This class defines the base class for all CFType classes - # - class CFType - # value of the type - attr_accessor :value - - def initialize(value=nil) - @value = value - end - - def to_xml(parser) - end - - def to_binary(bplist) end - end - - # This class holds string values, both, UTF-8 and UTF-16BE - # It will convert the value to UTF-16BE if necessary (i.e. if non-ascii char contained) - class CFString < CFType - # convert to XML - def to_xml(parser) - n = parser.new_node('string') - n = parser.append_node(n, parser.new_text(@value)) unless @value.nil? - n - end - - # convert to binary - def to_binary(bplist) - bplist.string_to_binary(@value); - end - end - - # This class holds integer/fixnum values - class CFInteger < CFType - # convert to XML - def to_xml(parser) - n = parser.new_node('integer') - n = parser.append_node(n, parser.new_text(@value.to_s)) - n - end - - # convert to binary - def to_binary(bplist) - bplist.num_to_binary(self) - end - end - - # This class holds float values - class CFReal < CFType - # convert to XML - def to_xml(parser) - n = parser.new_node('real') - n = parser.append_node(n, parser.new_text(@value.to_s)) - n - end - - # convert to binary - def to_binary(bplist) - bplist.num_to_binary(self) - end - end - - # This class holds Time values. While Apple uses seconds since 2001, - # the rest of the world uses seconds since 1970. So if you access value - # directly, you get the Time class. If you access via get_value you either - # geht the timestamp or the Apple timestamp - class CFDate < CFType - TIMESTAMP_APPLE = 0 - TIMESTAMP_UNIX = 1; - DATE_DIFF_APPLE_UNIX = 978307200 - - # create a XML date strimg from a time object - def CFDate.date_string(val) - # 2009-05-13T20:23:43Z - val.getutc.strftime("%Y-%m-%dT%H:%M:%SZ") - end - - # parse a XML date string - def CFDate.parse_date(val) - # 2009-05-13T20:23:43Z - val =~ %r{^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z$} - year,month,day,hour,min,sec = $1, $2, $3, $4, $5, $6 - return Time.utc(year,month,day,hour,min,sec).getlocal - end - - # set value to defined state - def initialize(value = nil,format=CFDate::TIMESTAMP_UNIX) - if(value.is_a?(Time) || value.nil?) then - @value = value.nil? ? Time.now : value - elsif value.instance_of? Date - @value = Time.utc(value.year, value.month, value.day, 0, 0, 0) - elsif value.instance_of? DateTime - @value = value.to_time.utc - else - set_value(value,format) - end - end - - # set value with timestamp, either Apple or UNIX - def set_value(value,format=CFDate::TIMESTAMP_UNIX) - if(format == CFDate::TIMESTAMP_UNIX) then - @value = Time.at(value) - else - @value = Time.at(value + CFDate::DATE_DIFF_APPLE_UNIX) - end - end - - # get timestamp, either UNIX or Apple timestamp - def get_value(format=CFDate::TIMESTAMP_UNIX) - if(format == CFDate::TIMESTAMP_UNIX) then - @value.to_i - else - @value.to_f - CFDate::DATE_DIFF_APPLE_UNIX - end - end - - # convert to XML - def to_xml(parser) - n = parser.new_node('date') - n = parser.append_node(n, parser.new_text(CFDate::date_string(@value))) - n - end - - # convert to binary - def to_binary(bplist) - bplist.date_to_binary(@value) - end - end - - # This class contains a boolean value - class CFBoolean < CFType - # convert to XML - def to_xml(parser) - parser.new_node(@value ? 'true' : 'false') - end - - # convert to binary - def to_binary(bplist) - bplist.bool_to_binary(@value); - end - end - - # This class contains binary data values - class CFData < CFType - # Base64 encoded data - DATA_BASE64 = 0 - # Raw data - DATA_RAW = 1 - - # set value to defined state, either base64 encoded or raw - def initialize(value=nil,format=DATA_BASE64) - if(format == DATA_RAW) - @raw_value = value - @raw_value.blob = true - else - @value = value - end - end - - # get base64 encoded value - def encoded_value - @value ||= "\n#{Base64.encode64(@raw_value).gsub("\n", '').scan(/.{1,76}/).join("\n")}\n" - end - - # get base64 decoded value - def decoded_value - @raw_value ||= String.new(Base64.decode64(@value)) - @raw_value.blob = true - @raw_value - end - - # convert to XML - def to_xml(parser) - n = parser.new_node('data') - n = parser.append_node(n, parser.new_text(encoded_value())) - n - end - - # convert to binary - def to_binary(bplist) - bplist.data_to_binary(decoded_value()) - end - end - - # This class contains an array of values - class CFArray < CFType - # create a new array CFType - def initialize(val=[]) - @value = val - end - - # convert to XML - def to_xml(parser) - n = parser.new_node('array') - @value.each do |v| - n = parser.append_node(n, v.to_xml(parser)) - end - n - end - - # convert to binary - def to_binary(bplist) - bplist.array_to_binary(self) - end - end - - # this class contains a hash of values - class CFDictionary < CFType - # Create new CFDictonary type. - def initialize(value={}) - @value = value - end - - # convert to XML - def to_xml(parser) - n = parser.new_node('dict') - @value.each_pair do |key, value| - k = parser.append_node(parser.new_node('key'), parser.new_text(key.to_s)) - n = parser.append_node(n, k) - n = parser.append_node(n, value.to_xml(parser)) - end - n - end - - # convert to binary - def to_binary(bplist) - bplist.dict_to_binary(self) - end - end -end - -# eof diff --git a/lib/facter/util/cfpropertylist/lib/rbLibXMLParser.rb b/lib/facter/util/cfpropertylist/lib/rbLibXMLParser.rb deleted file mode 100644 index b27835da52..0000000000 --- a/lib/facter/util/cfpropertylist/lib/rbLibXMLParser.rb +++ /dev/null @@ -1,135 +0,0 @@ -# -*- coding: utf-8 -*- - -require 'libxml' - -module Facter::Util::CFPropertyList - # XML parser - class XML < XMLParserInterface - # read a XML file - # opts:: - # * :file - The filename of the file to load - # * :data - The data to parse - def load(opts) - if(opts.has_key?(:file)) then - doc = LibXML::XML::Document.file(opts[:file],:options => LibXML::XML::Parser::Options::NOBLANKS|LibXML::XML::Parser::Options::NOENT) - else - doc = LibXML::XML::Document.string(opts[:data],:options => LibXML::XML::Parser::Options::NOBLANKS|LibXML::XML::Parser::Options::NOENT) - end - - root = doc.root.first - return import_xml(root) - end - - # serialize Facter::Util::CFPropertyList object to XML - # opts = {}:: Specify options: :formatted - Use indention and line breaks - def to_str(opts={}) - doc = LibXML::XML::Document.new - - doc.root = LibXML::XML::Node.new('plist') - doc.encoding = LibXML::XML::Encoding::UTF_8 - - doc.root['version'] = '1.0' - doc.root << opts[:root].to_xml(self) - - # ugly hack, but there's no other possibility I know - str = doc.to_s(:indent => opts[:formatted]) - str1 = String.new - first = false - str.each_line do |line| - str1 << line - unless(first) then - str1 << "\n" if line =~ /^\s*<\?xml/ - end - - first = true - end - - str1.force_encoding('UTF-8') if str1.respond_to?(:force_encoding) - return str1 - end - - def new_node(name) - LibXML::XML::Node.new(name) - end - - def new_text(val) - LibXML::XML::Node.new_text(val) - end - - def append_node(parent, child) - parent << child - end - - protected - - # get the value of a DOM node - def get_value(n) - content = if n.children? - n.first.content - else - n.content - end - - content.force_encoding('UTF-8') if content.respond_to?(:force_encoding) - content - end - - # import the XML values - def import_xml(node) - ret = nil - - case node.name - when 'dict' - hsh = Hash.new - key = nil - - if node.children? then - node.children.each do |n| - next if n.text? # avoid a bug of libxml - next if n.comment? - - if n.name == "key" then - key = get_value(n) - else - raise CFFormatError.new("Format error!") if key.nil? - hsh[key] = import_xml(n) - key = nil - end - end - end - - ret = CFDictionary.new(hsh) - - when 'array' - ary = Array.new - - if node.children? then - node.children.each do |n| - ary.push import_xml(n) - end - end - - ret = CFArray.new(ary) - - when 'true' - ret = CFBoolean.new(true) - when 'false' - ret = CFBoolean.new(false) - when 'real' - ret = CFReal.new(get_value(node).to_f) - when 'integer' - ret = CFInteger.new(get_value(node).to_i) - when 'string' - ret = CFString.new(get_value(node)) - when 'data' - ret = CFData.new(get_value(node)) - when 'date' - ret = CFDate.new(CFDate.parse_date(get_value(node))) - end - - return ret - end - end -end - -# eof diff --git a/lib/facter/util/cfpropertylist/lib/rbNokogiriParser.rb b/lib/facter/util/cfpropertylist/lib/rbNokogiriParser.rb deleted file mode 100644 index 3edb8fe1ec..0000000000 --- a/lib/facter/util/cfpropertylist/lib/rbNokogiriParser.rb +++ /dev/null @@ -1,140 +0,0 @@ -# -*- coding: utf-8 -*- - -require 'nokogiri' - -module Facter::Util::CFPropertyList - # XML parser - class XML < ParserInterface - # read a XML file - # opts:: - # * :file - The filename of the file to load - # * :data - The data to parse - def load(opts) - if(opts.has_key?(:file)) then - File.open(opts[:file], "rb") { |fd| doc = Nokogiri::XML::Document.parse(fd, nil, nil, Nokogiri::XML::ParseOptions::NOBLANKS|Nokogiri::XML::ParseOptions::NOENT) } - else - doc = Nokogiri::XML::Document.parse(opts[:data], nil, nil, Nokogiri::XML::ParseOptions::NOBLANKS|Nokogiri::XML::ParseOptions::NOENT) - end - - root = doc.root.children.first - return import_xml(root) - end - - # serialize Facter::Util::CFPropertyList object to XML - # opts = {}:: Specify options: :formatted - Use indention and line breaks - def to_str(opts={}) - doc = Nokogiri::XML::Document.new - @doc = doc - - doc.root = doc.create_element 'plist', :version => '1.0' - doc.encoding = 'UTF-8' - - doc.root << opts[:root].to_xml(self) - - # ugly hack, but there's no other possibility I know - s_opts = Nokogiri::XML::Node::SaveOptions::AS_XML - s_opts |= Nokogiri::XML::Node::SaveOptions::FORMAT if opts[:formatted] - - str = doc.serialize(:save_with => s_opts) - str1 = String.new - first = false - str.each_line do |line| - str1 << line - unless(first) then - str1 << "\n" if line =~ /^\s*<\?xml/ - end - - first = true - end - - str1.force_encoding('UTF-8') if str1.respond_to?(:force_encoding) - return str1 - end - - def new_node(name) - @doc.create_element name - end - - def new_text(val) - @doc.create_text_node val - end - - def append_node(parent, child) - parent << child - end - - protected - - # get the value of a DOM node - def get_value(n) - content = if n.children.empty? - n.content - else - n.children.first.content - end - - content.force_encoding('UTF-8') if content.respond_to?(:force_encoding) - content - end - - # import the XML values - def import_xml(node) - ret = nil - - case node.name - when 'dict' - hsh = Hash.new - key = nil - children = node.children - - unless children.empty? then - children.each do |n| - next if n.text? # avoid a bug of libxml - next if n.comment? - - if n.name == "key" then - key = get_value(n) - else - raise CFFormatError.new("Format error!") if key.nil? - hsh[key] = import_xml(n) - key = nil - end - end - end - - ret = CFDictionary.new(hsh) - - when 'array' - ary = Array.new - children = node.children - - unless children.empty? then - children.each do |n| - ary.push import_xml(n) - end - end - - ret = CFArray.new(ary) - - when 'true' - ret = CFBoolean.new(true) - when 'false' - ret = CFBoolean.new(false) - when 'real' - ret = CFReal.new(get_value(node).to_f) - when 'integer' - ret = CFInteger.new(get_value(node).to_i) - when 'string' - ret = CFString.new(get_value(node)) - when 'data' - ret = CFData.new(get_value(node)) - when 'date' - ret = CFDate.new(CFDate.parse_date(get_value(node))) - end - - return ret - end - end -end - -# eof diff --git a/lib/facter/util/cfpropertylist/lib/rbREXMLParser.rb b/lib/facter/util/cfpropertylist/lib/rbREXMLParser.rb deleted file mode 100644 index 9b0e044c33..0000000000 --- a/lib/facter/util/cfpropertylist/lib/rbREXMLParser.rb +++ /dev/null @@ -1,136 +0,0 @@ -# -*- coding: utf-8 -*- - -require 'rexml/document' - -module Facter::Util::CFPropertyList - # XML parser - class XML < ParserInterface - # read a XML file - # opts:: - # * :file - The filename of the file to load - # * :data - The data to parse - def load(opts) - if(opts.has_key?(:file)) then - File.open(opts[:file], "rb") { |fd| doc = REXML::Document.new(fd) } - else - doc = REXML::Document.new(opts[:data]) - end - - root = doc.root.elements[1] - return import_xml(root) - end - - # serialize Facter::Util::CFPropertyList object to XML - # opts = {}:: Specify options: :formatted - Use indention and line breaks - def to_str(opts={}) - doc = REXML::Document.new - @doc = doc - - doc.context[:attribute_quote] = :quote - - doc.add_element 'plist', {'version' => '1.0'} - doc.root << opts[:root].to_xml(self) - - formatter = if opts[:formatted] then - f = REXML::Formatters::Pretty.new(2) - f.compact = true - f - else - REXML::Formatters::Default.new - end - - str = formatter.write(doc.root, "") - str1 = "\n\n" + str + "\n" - str1.force_encoding('UTF-8') if str1.respond_to?(:force_encoding) - - return str1 - end - - def new_node(name) - #LibXML::XML::Node.new(name) - REXML::Element.new(name) - end - - def new_text(val) - val - end - - def append_node(parent, child) - if child.is_a?(String) then - parent.add_text child - else - parent.elements << child - end - parent - end - - protected - - # get the value of a DOM node - def get_value(n) - content = n.text - - content.force_encoding('UTF-8') if content.respond_to?(:force_encoding) - content - end - - # import the XML values - def import_xml(node) - ret = nil - - case node.name - when 'dict' - hsh = Hash.new - key = nil - - if node.has_elements? then - node.elements.each do |n| - #print n.name + "\n" - next if n.name == '#text' # avoid a bug of libxml - next if n.name == '#comment' - - if n.name == "key" then - key = get_value(n) - else - raise CFFormatError.new("Format error!") if key.nil? - hsh[key] = import_xml(n) - key = nil - end - end - end - - ret = CFDictionary.new(hsh) - - when 'array' - ary = Array.new - - if node.has_elements? then - node.elements.each do |n| - ary.push import_xml(n) - end - end - - ret = CFArray.new(ary) - - when 'true' - ret = CFBoolean.new(true) - when 'false' - ret = CFBoolean.new(false) - when 'real' - ret = CFReal.new(get_value(node).to_f) - when 'integer' - ret = CFInteger.new(get_value(node).to_i) - when 'string' - ret = CFString.new(get_value(node)) - when 'data' - ret = CFData.new(get_value(node)) - when 'date' - ret = CFDate.new(CFDate.parse_date(get_value(node))) - end - - return ret - end - end -end - -# eof diff --git a/lib/facter/util/macosx.rb b/lib/facter/util/macosx.rb index 4dc4e4be51..17da5b8fc7 100644 --- a/lib/facter/util/macosx.rb +++ b/lib/facter/util/macosx.rb @@ -6,8 +6,7 @@ ## module Facter::Util::Macosx - require 'thread' - require 'facter/util/cfpropertylist' + require 'cfpropertylist' require 'facter/util/resolution' Plist_Xml_Doctype = '' @@ -26,13 +25,13 @@ def self.intern_xml(xml) xml.gsub!( bad_xml_doctype, Plist_Xml_Doctype ) Facter.debug("Had to fix plist with incorrect DOCTYPE declaration") end - plist = Facter::Util::CFPropertyList::List.new + plist = CFPropertyList::List.new begin plist.load_str(xml) - rescue => e - fail("A plist file could not be properly read by Facter::Util::CFPropertyList: #{e.inspect}") + rescue CFFormatError => e + raise RuntimeError, "A plist file could not be properly read by CFPropertyList: #{e.message}", e.backtrace end - Facter::Util::CFPropertyList.native_types(plist.value) + CFPropertyList.native_types(plist.value) end # Return an xml result, modified as we need it. diff --git a/spec/unit/util/macosx_spec.rb b/spec/unit/util/macosx_spec.rb index f633263c9d..94b967aabd 100755 --- a/spec/unit/util/macosx_spec.rb +++ b/spec/unit/util/macosx_spec.rb @@ -45,7 +45,7 @@ STDERR.stubs(:<<) expect { Facter::Util::Macosx.intern_xml('xml<--->') - }.to raise_error(RuntimeError, /A plist file could not be properly read by Facter::Util::CFPropertyList/) + }.to raise_error(RuntimeError, /A plist file could not be properly read by CFPropertyList/) end describe "when collecting profiler data" do From e4c86894fdf38f84303031c6559b4fe1d7638e8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Dal=C3=A9n?= Date: Thu, 21 Mar 2013 16:09:57 +0100 Subject: [PATCH 1586/3753] (#19845) Allow a block to be supplied as a confine Allows a confine to take only a block parameter like: confine { File.exist? '/bin/foo' } Or take a fact name and a block: confine :ipaddress do |addr| require 'ipaddr' IPAddr.new('192.168.0.0/16').include? addr end This is similar to what Puppet allows in provider confines. Backported onto facter-2 by Adrien Thebo Conflicts: lib/facter/util/resolution.rb spec/unit/util/resolution_spec.rb --- lib/facter/core/suitable.rb | 18 +++++++++++--- lib/facter/util/confine.rb | 38 +++++++++++++++++++++++++---- spec/unit/core/suitable_spec.rb | 42 ++++++++++++++++++++++++++------- spec/unit/util/confine_spec.rb | 21 +++++++++++++++++ 4 files changed, 104 insertions(+), 15 deletions(-) diff --git a/lib/facter/core/suitable.rb b/lib/facter/core/suitable.rb index 3f4a7bc468..e34b5e8a46 100644 --- a/lib/facter/core/suitable.rb +++ b/lib/facter/core/suitable.rb @@ -46,9 +46,21 @@ def has_weight(weight) # @return [void] # # @api public - def confine(confines) - confines.each do |fact, values| - @confines.push Facter::Util::Confine.new(fact, *values) + def confine(confines = nil, &block) + case confines + when Hash + confines.each do |fact, values| + @confines.push Facter::Util::Confine.new(fact, *values) + end + else + if block + if confines + @confines.push Facter::Util::Confine.new(confines, &block) + else + @confines.push Facter::Util::Confine.new(&block) + end + else + end end end diff --git a/lib/facter/util/confine.rb b/lib/facter/util/confine.rb index 3bb504bb74..eac539ce35 100644 --- a/lib/facter/util/confine.rb +++ b/lib/facter/util/confine.rb @@ -10,19 +10,40 @@ class Facter::Util::Confine # Add the restriction. Requires the fact name, an operator, and the value # we're comparing to. - def initialize(fact, *values) - raise ArgumentError, "The fact name must be provided" unless fact - raise ArgumentError, "One or more values must be provided" if values.empty? + # + # @param fact [Symbol] Name of the fact + # @param values [Array] One or more values to match against. + # They can be any type that provides a === method. + # @param block [Proc] Alternatively a block can be supplied as a check. The fact + # value will be passed as the argument to the block. If the block returns + # true then the fact will be enabled, otherwise it will be disabled. + def initialize(fact = nil, *values, &block) + raise ArgumentError, "The fact name must be provided" unless fact or block_given? + if values.empty? and not block_given? + raise ArgumentError, "One or more values or a block must be provided" + end @fact = fact @values = values + @block = block end def to_s + return @block.to_s if @block return "'%s' '%s'" % [@fact, @values.join(",")] end # Evaluate the fact, returning true or false. + # if we have a block paramter then we only evaluate that instead def true? + if @block and not @fact then + begin + return !! @block.call + rescue StandardError => error + Facter.debug "Confine raised #{error.class} #{error}" + return false + end + end + unless fact = Facter[@fact] Facter.debug "No fact for %s" % @fact return false @@ -31,6 +52,15 @@ def true? return false if value.nil? - return @values.any? { |v| convert(v) === value } + if @block then + begin + return !! @block.call(value) + rescue StandardError => error + Facter.debug "Confine raised #{error.class} #{error}" + return false + end + end + + return @values.any? do |v| convert(v) === value end end end diff --git a/spec/unit/core/suitable_spec.rb b/spec/unit/core/suitable_spec.rb index feadb41e34..4c0b1fde06 100644 --- a/spec/unit/core/suitable_spec.rb +++ b/spec/unit/core/suitable_spec.rb @@ -13,16 +13,42 @@ def initialize subject { SuitableClass.new } - it "can add confines" do - subject.confine :kernel => 'Linux' + describe "confining on facts" do + it "can add confines with a fact and a single value" do + subject.confine :kernel => 'Linux' + end + + it "creates a Facter::Util::Confine object for the confine call" do + subject.confine :kernel => 'Linux' + conf = subject.confines.first + expect(conf).to be_a_kind_of Facter::Util::Confine + expect(conf.fact).to eq :kernel + expect(conf.values).to eq ['Linux'] + end end - it "creates a Facter::Util::Confine object for the confine call" do - subject.confine :kernel => 'Linux' - conf = subject.confines.first - expect(conf).to be_a_kind_of Facter::Util::Confine - expect(conf.fact).to eq :kernel - expect(conf.values).to eq ['Linux'] + describe "confining on blocks" do + it "can add a single fact with a block parameter" do + subject.confine(:one) { true } + end + + it "creates a Util::Confine instance for the provided fact with block parameter" do + block = lambda { true } + Facter::Util::Confine.expects(:new).with("one") + + subject.confine("one", &block) + end + + it "should accept a single block parameter" do + subject.confine() { true } + end + + it "should create a Util::Confine instance for the provided block parameter" do + block = lambda { true } + Facter::Util::Confine.expects(:new) + + subject.confine(&block) + end end describe "determining weight" do diff --git a/spec/unit/util/confine_spec.rb b/spec/unit/util/confine_spec.rb index 5bdd8daf8e..f6b11b5d7c 100755 --- a/spec/unit/util/confine_spec.rb +++ b/spec/unit/util/confine_spec.rb @@ -123,5 +123,26 @@ def confined(fact_value, *confines) it "should return false if none of the provided ranges matches the fact's value" do confined(8, (5..7)).should be_false end + + it "should accept and evaluate a block argument against the fact" do + @fact.expects(:value).returns 'foo' + confine = Facter::Util::Confine.new :yay do |f| f === 'foo' end + confine.true?.should be_true + end + + it "should return false if the block raises a StandardError when checking a fact" do + @fact.stubs(:value).returns 'foo' + confine = Facter::Util::Confine.new :yay do |f| raise StandardError end + confine.true?.should be_false + end + + it "should accept and evaluate only a block argument" do + Facter::Util::Confine.new { true }.true?.should be_true + Facter::Util::Confine.new { false }.true?.should be_false + end + + it "should return false if the block raises a StandardError" do + Facter::Util::Confine.new { raise StandardError }.true?.should be_false + end end end From 21f10c05398bc3699f64f5c732c18f48dfc1010d Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Wed, 3 Apr 2013 13:40:37 -0700 Subject: [PATCH 1587/3753] (doc) Update the Resolution#confine API docs Without this patch, the API documentation for the Facter::Util::Resolution#confine is a bit muddled and confusing. This confusion is caused by the different, overloaded, forms of arguments that #confine accepts. This patch addresses the problem by using the @overload YARD tag to separate out each form, document the arguments for each form, and provide a clear example for each form. Back ported onto facter-2 by Adrien thebo Conflicts: lib/facter/util/resolution.rb --- lib/facter/core/suitable.rb | 61 ++++++++++++++++++++++++++----------- 1 file changed, 43 insertions(+), 18 deletions(-) diff --git a/lib/facter/core/suitable.rb b/lib/facter/core/suitable.rb index e34b5e8a46..1b3c04fceb 100644 --- a/lib/facter/core/suitable.rb +++ b/lib/facter/core/suitable.rb @@ -23,29 +23,54 @@ def has_weight(weight) @weight = weight end - # Sets the conditions for this resolution to be used. This takes a - # hash of fact names and values. Every fact must match the values - # given for that fact, otherwise this resolution will not be - # considered suitable. The values given for a fact can be an array, in - # which case the value of the fact must be in the array for it to - # match. + # Sets the conditions for this resolution to be used. This method accepts + # multiple forms of arguments to determine suitability. # - # @param confines [Hash{String => Object}] a hash of facts and the - # values they should have in order for this resolution to be - # used + # @return [void] + # + # @api public # - # @example Confining to Linux - # Facter.add(:powerstates) do - # # This resolution only makes sense on linux systems - # confine :kernel => "Linux" - # setcode do - # Facter::Util::Resolution.exec('cat /sys/power/states') + # @overload confine(confines) + # Confine a fact to a specific fact value or values. This form takes a + # hash of fact names and values. Every fact must match the values given for + # that fact, otherwise this resolution will not be considered suitable. The + # values given for a fact can be an array, in which case the value of the + # fact must be in the array for it to match. + # @param [Hash{String,Symbol=>String,Array}] confines set of facts identified by the hash keys whose + # fact value must match the argument value. + # @example Confining to Linux + # Facter.add(:powerstates) do + # # This resolution only makes sense on linux systems + # confine :kernel => "Linux" + # setcode do + # File.read('/sys/power/states') + # end # end - # end # - # @return [void] + # @overload confine(confines, &block) + # Confine a fact to a block with the value of a specified fact yielded to + # the block. + # @param [String,Symbol] confines the fact name whose value should be + # yielded to the block + # @param [Proc] block determines the suitability of the fact. If the block + # evaluates to `false` or `nil` then the confined fact will not be + # evaluated. + # @yield [value] the value of the fact identified by {confines} + # @example Confine the fact to a host with an ipaddress in a specific + # subnet + # confine :ipaddress do |addr| + # require 'ipaddr' + # IPAddr.new('192.168.0.0/16').include? addr + # end # - # @api public + # @overload confine(&block) + # Confine a fact to a block. The fact will be evaluated only if the block + # evaluates to something other than `false` or `nil`. + # @param [Proc] block determines the suitability of the fact. If the block + # evaluates to `false` or `nil` then the confined fact will not be + # evaluated. + # @example Confine the fact to systems with a specific file. + # confine { File.exist? '/bin/foo' } def confine(confines = nil, &block) case confines when Hash From 940f35aea5a53cb699b7c7fd6107c55686c8c47a Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Sat, 1 Feb 2014 12:31:50 -0800 Subject: [PATCH 1588/3753] Update TODO --- TODO | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/TODO b/TODO index bcc158627e..df8c84bb64 100644 --- a/TODO +++ b/TODO @@ -1,3 +1,4 @@ +Please * ffi wrapper * Handle non-string values * Normalize value and to_json to both return json? @@ -5,4 +6,10 @@ * cmake or autoconf? * abstract out command execution method * Add regex support (e.g. for os and processor file parsing) +* Allow 'facter ' (currently returns all facts) + +Not sure +* Split out into app, core lib, libs per functional group (network, storage, selinux, processors, etc) +* Use dynamic loading of libs (dlopen, etc) +* is 'weight' needed? * fact dependencies? From 95c422d80226e533d7570ae342b2df463bfd79f7 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Sat, 1 Feb 2014 12:33:19 -0800 Subject: [PATCH 1589/3753] Add the debug symbols file to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 801930fe09..f2a135fe83 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ *.o *~ cfacter +cfacter.DSYM From 2e8d10a39e5cf16e8954e77007af91d1601adff2 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Sat, 1 Feb 2014 14:37:24 -0800 Subject: [PATCH 1590/3753] Fix version reporting and to_json() behavior --- cfacterlib.cc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cfacterlib.cc b/cfacterlib.cc index 0e962846e0..23d91e8831 100644 --- a/cfacterlib.cc +++ b/cfacterlib.cc @@ -21,7 +21,7 @@ void clear() void loadfacts() { - facts["facterversion"] = "3.0.0"; + facts["cfacterversion"] = "0.0.1"; list external_directories; external_directories.push_back("/etc/facter/facts.d"); @@ -69,7 +69,9 @@ int to_json(char *facts_json, size_t facts_len) rapidjson::Writer writer(buf); json.Accept(writer); - cout << buf.GetString() << endl; + // FIXME can rapidjson write straight into the provided char array in a safe manner? + // if not roll my own strncpy which doesn't zero-pad and returns success rather than the ptr + strncpy(facts_json, buf.GetString(), facts_len); return 0; } } From a990d52538993ebff4db0292b18be1dc7f561112 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Sat, 1 Feb 2014 14:37:52 -0800 Subject: [PATCH 1591/3753] Fix and rename the libcfacter.so target --- Makefile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 272d8c042c..765ddc6509 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,8 @@ ifeq ($(UNAME_S),Darwin) CCFLAGS += -Wno-tautological-constant-out-of-range-compare endif +all: cfacter libcfacter.so + cfacter: cfacter.cc cfacterlib.cc cfacterimpl.cc cfacterlib.h cfacterimpl.h g++ ${CCFLAGS} -o cfacter cfacter.cc cfacterlib.cc cfacterimpl.cc -I C @@ -18,7 +20,7 @@ cfacterlib.o: cfacterlib.cc cfacterlib.h cfacterimpl.h cfacterimpl.o: cfacterimpl.cc cfacterimpl.h g++ ${CCFLAGS} -fPIC -c -o $@ cfacterimpl.cc -I . -cfacterlib.so: cfacterlib.o +libcfacter.so: cfacterlib.o cfacterimpl.o g++ ${CCFLAGS} -o $@ $^ -fPIC -shared clean: From 5d68616f38a4290e54618cecd5e39ec6c74ce256 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Sat, 1 Feb 2014 15:00:36 -0800 Subject: [PATCH 1592/3753] Add an initial ffi wrapper --- TODO | 2 +- cfacter.rb | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 cfacter.rb diff --git a/TODO b/TODO index df8c84bb64..3194d2931b 100644 --- a/TODO +++ b/TODO @@ -1,5 +1,5 @@ Please -* ffi wrapper +* complete ffi wrapper * Handle non-string values * Normalize value and to_json to both return json? * virtual diff --git a/cfacter.rb b/cfacter.rb new file mode 100644 index 0000000000..1bd2c068de --- /dev/null +++ b/cfacter.rb @@ -0,0 +1,30 @@ +require 'ffi' +require 'json' + +module CFacter + private + extend FFI::Library + ffi_lib "./libcfacter.so" + + # to_json is used for to_hash but no need to make it public + attach_function :to_json, [:pointer, :size_t], :int + + class Constants + JSON_STRING_MAX_LEN = 1024000 + end + + public + attach_function :clear, [], :void + attach_function :loadfacts, [], :void + attach_function :value, [:pointer, :pointer, :size_t], :int + attach_function :search_external, [:pointer], :void + + def self.to_hash + ptr = FFI::MemoryPointer.new(:char, Constants::JSON_STRING_MAX_LEN) + success = to_json(ptr, Constants::JSON_STRING_MAX_LEN) + if success != 0 + return {} + end + JSON.parse(ptr.read_string()) + end +end From dc7375ea14e2f7c3c9224461afb3813c3f2ec577 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Sat, 1 Feb 2014 21:02:42 -0800 Subject: [PATCH 1593/3753] Make the ffi wrapper a gem --- gem/.gitignore | 1 + gem/README | 2 ++ gem/cfacter.gemspec | 11 +++++++++++ cfacter.rb => gem/lib/cfacter.rb | 2 +- 4 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 gem/.gitignore create mode 100644 gem/README create mode 100644 gem/cfacter.gemspec rename cfacter.rb => gem/lib/cfacter.rb (96%) diff --git a/gem/.gitignore b/gem/.gitignore new file mode 100644 index 0000000000..a68922fec9 --- /dev/null +++ b/gem/.gitignore @@ -0,0 +1 @@ +cfacter-*gem diff --git a/gem/README b/gem/README new file mode 100644 index 0000000000..f7af1d1c64 --- /dev/null +++ b/gem/README @@ -0,0 +1,2 @@ +A very very thin gem wrapper around libcfacter.so, intended for use as a facter +replacement in ruby client such as puppet. diff --git a/gem/cfacter.gemspec b/gem/cfacter.gemspec new file mode 100644 index 0000000000..b1f2285e2d --- /dev/null +++ b/gem/cfacter.gemspec @@ -0,0 +1,11 @@ +Gem::Specification.new do |s| + s.name = 'cfacter' + s.version = '0.0.1' + s.summary = "cfacter" + s.description = "A lightweight facter replacement" + s.authors = ["Kylo Ginsberg"] + s.email = 'kylo@kylo.net' + s.files = ["lib/cfacter.rb"] + s.add_runtime_dependency 'json', '~> 1.8', '>= 1.8.1' + s.add_runtime_dependency 'ffi', '~> 1.9', '>= 1.9.3' +end diff --git a/cfacter.rb b/gem/lib/cfacter.rb similarity index 96% rename from cfacter.rb rename to gem/lib/cfacter.rb index 1bd2c068de..29ea485015 100644 --- a/cfacter.rb +++ b/gem/lib/cfacter.rb @@ -4,7 +4,7 @@ module CFacter private extend FFI::Library - ffi_lib "./libcfacter.so" + ffi_lib "libcfacter.so" # to_json is used for to_hash but no need to make it public attach_function :to_json, [:pointer, :size_t], :int From 9eed73fd4fdd9d6539d3c1974cdf1598019e6153 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Sat, 1 Feb 2014 22:12:32 -0800 Subject: [PATCH 1594/3753] Add an install target (with a temporary location) --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index 765ddc6509..8012a3edd1 100644 --- a/Makefile +++ b/Makefile @@ -23,6 +23,9 @@ cfacterimpl.o: cfacterimpl.cc cfacterimpl.h libcfacter.so: cfacterlib.o cfacterimpl.o g++ ${CCFLAGS} -o $@ $^ -fPIC -shared +install: libcfacter.so + -cp libcfacter.so $(HOME)/lib + clean: -rm cfacterlib.o cfacterimpl.o cfacterlib.so cfacter 2> /dev/null From 8ce77891d3397d012ea7da2fc60a4b07b5d89af3 Mon Sep 17 00:00:00 2001 From: Melissa Stone Date: Mon, 3 Feb 2014 10:02:08 -0800 Subject: [PATCH 1595/3753] (packaging) Update FACTERVERSION to 1.7.5-rc2 --- lib/facter/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/version.rb b/lib/facter/version.rb index 7cfddf010d..4f33d57311 100644 --- a/lib/facter/version.rb +++ b/lib/facter/version.rb @@ -1,6 +1,6 @@ module Facter if not defined? FACTERVERSION then - FACTERVERSION = '1.7.5-rc1' + FACTERVERSION = '1.7.5-rc2' end ## From fc93a4facba224af45dc574fdbd5dccb335e5f70 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 3 Feb 2014 09:55:38 -0800 Subject: [PATCH 1596/3753] (maint) Extract Facter logging functionality --- lib/facter.rb | 153 +------------------------------ lib/facter/core/logging.rb | 159 +++++++++++++++++++++++++++++++++ spec/unit/core/logging_spec.rb | 113 +++++++++++++++++++++++ spec/unit/facter_spec.rb | 121 ------------------------- 4 files changed, 274 insertions(+), 272 deletions(-) create mode 100644 lib/facter/core/logging.rb create mode 100644 spec/unit/core/logging_spec.rb diff --git a/lib/facter.rb b/lib/facter.rb index da433d0845..22449129da 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -39,18 +39,8 @@ module Util; end include Comparable include Enumerable - # @api private - GREEN = "" - # @api private - RESET = "" - # @api private - @@debug = 0 - # @api private - @@timing = 0 - # @api private - @@messages = {} - # @api private - @@debug_messages = {} + require 'facter/core/logging' + extend Facter::Core::Logging # module methods @@ -67,55 +57,6 @@ def self.collection @collection end - # Prints a debug message if debugging is turned on - # - # @param string [String] the debug message - # @return [void] - def self.debug(string) - if string.nil? - return - end - if self.debugging? - puts GREEN + string + RESET - end - end - - # Prints a debug message only once. - # - # @note Uniqueness is based on the string, not the specific location - # of the method call. - # - # @param msg [String] the debug message - # @return [void] - def self.debugonce(msg) - if msg and not msg.empty? and @@debug_messages[msg].nil? - @@debug_messages[msg] = true - debug(msg) - end - end - - # Returns whether debugging output is turned on - def self.debugging? - @@debug != 0 - end - - # Prints a timing - # - # @param string [String] the time to print - # @return [void] - # - # @api private - def self.show_time(string) - puts "#{GREEN}#{string}#{RESET}" if string and Facter.timing? - end - - # Returns whether timing output is turned on - # - # @api private - def self.timing? - @@timing != 0 - end - # Returns whether the JSON "feature" is available. # # @api private @@ -268,96 +209,6 @@ def self.clear Facter.reset end - # Clears the seen state of warning messages. See {warnonce}. - # - # @return [void] - # - # @api private - def self.clear_messages - @@messages.clear - end - - # Sets debugging on or off. - # - # @return [void] - # - # @api private - def self.debugging(bit) - if bit - case bit - when TrueClass; @@debug = 1 - when FalseClass; @@debug = 0 - when Fixnum - if bit > 0 - @@debug = 1 - else - @@debug = 0 - end - when String; - if bit.downcase == 'off' - @@debug = 0 - else - @@debug = 1 - end - else - @@debug = 0 - end - else - @@debug = 0 - end - end - - # Sets whether timing messages are displayed. - # - # @return [void] - # - # @api private - def self.timing(bit) - if bit - case bit - when TrueClass; @@timing = 1 - when Fixnum - if bit > 0 - @@timing = 1 - else - @@timing = 0 - end - end - else - @@timing = 0 - end - end - - # Prints a warning message. The message is only printed if debugging - # is enabled. - # - # @param msg [String] the warning message to be printed - # - # @return [void] - def self.warn(msg) - if Facter.debugging? and msg and not msg.empty? - msg = [msg] unless msg.respond_to? :each - msg.each { |line| Kernel.warn line } - end - end - - # Prints a warning message only once per process. Each unique string - # is printed once. - # - # @note Unlike {warn} the message will be printed even if debugging is - # not turned on. This behavior is likely to change and should not be - # relied on. - # - # @param msg [String] the warning message to be printed - # - # @return [void] - def self.warnonce(msg) - if msg and not msg.empty? and @@messages[msg].nil? - @@messages[msg] = true - Kernel.warn(msg) - end - end - # Removes all facts from memory. Use this when the fact code has # changed on disk and needs to be reloaded. # diff --git a/lib/facter/core/logging.rb b/lib/facter/core/logging.rb new file mode 100644 index 0000000000..c466b0c8a9 --- /dev/null +++ b/lib/facter/core/logging.rb @@ -0,0 +1,159 @@ +require 'facter' + +module Facter::Core::Logging + + extend self + + # @api private + GREEN = "" + # @api private + RESET = "" + + # @api private + @@debug = 0 + # @api private + @@timing = 0 + # @api private + @@warn_messages = {} + # @api private + @@debug_messages = {} + + # Prints a debug message if debugging is turned on + # + # @param string [String] the debug message + # @return [void] + def debug(string) + if string.nil? + return + end + if self.debugging? + puts GREEN + string + RESET + end + end + + # Prints a debug message only once. + # + # @note Uniqueness is based on the string, not the specific location + # of the method call. + # + # @param msg [String] the debug message + # @return [void] + def debugonce(msg) + if msg and not msg.empty? and @@debug_messages[msg].nil? + @@debug_messages[msg] = true + debug(msg) + end + end + + # Prints a warning message. The message is only printed if debugging + # is enabled. + # + # @param msg [String] the warning message to be printed + # + # @return [void] + def warn(msg) + if self.debugging? and msg and not msg.empty? + msg = [msg] unless msg.respond_to? :each + msg.each { |line| Kernel.warn line } + end + end + + # Prints a warning message only once per process. Each unique string + # is printed once. + # + # @note Unlike {warn} the message will be printed even if debugging is + # not turned on. This behavior is likely to change and should not be + # relied on. + # + # @param msg [String] the warning message to be printed + # + # @return [void] + def warnonce(msg) + if msg and not msg.empty? and @@warn_messages[msg].nil? + @@warn_messages[msg] = true + Kernel.warn(msg) + end + end + + # Print timing information + # + # @param string [String] the time to print + # @return [void] + # + # @api private + def show_time(string) + puts "#{GREEN}#{string}#{RESET}" if string and self.timing? + end + + # Sets debugging on or off. + # + # @return [void] + # + # @api private + def debugging(bit) + if bit + case bit + when TrueClass; @@debug = 1 + when FalseClass; @@debug = 0 + when Fixnum + if bit > 0 + @@debug = 1 + else + @@debug = 0 + end + when String; + if bit.downcase == 'off' + @@debug = 0 + else + @@debug = 1 + end + else + @@debug = 0 + end + else + @@debug = 0 + end + end + + # Returns whether debugging output is turned on + def debugging? + @@debug != 0 + end + + # Sets whether timing messages are displayed. + # + # @return [void] + # + # @api private + def timing(bit) + if bit + case bit + when TrueClass; @@timing = 1 + when Fixnum + if bit > 0 + @@timing = 1 + else + @@timing = 0 + end + end + else + @@timing = 0 + end + end + + # Returns whether timing output is turned on + # + # @api private + def timing? + @@timing != 0 + end + + # Clears the seen state of warning messages. See {warnonce}. + # + # @return [void] + # + # @api private + def clear_messages + @@warn_messages.clear + end +end diff --git a/spec/unit/core/logging_spec.rb b/spec/unit/core/logging_spec.rb new file mode 100644 index 0000000000..14e09c43a7 --- /dev/null +++ b/spec/unit/core/logging_spec.rb @@ -0,0 +1,113 @@ +require 'spec_helper' +require 'facter/core/logging' + +describe Facter::Core::Logging do + + subject { described_class } + + describe "when warning" do + it "should warn if debugging is enabled" do + subject.debugging(true) + Kernel.stubs(:warn) + Kernel.expects(:warn).with('foo') + subject.warn('foo') + end + + it "should not warn if debugging is enabled but nil is passed" do + subject.debugging(true) + Kernel.stubs(:warn) + Kernel.expects(:warn).never + subject.warn(nil) + end + + it "should not warn if debugging is enabled but an empyt string is passed" do + subject.debugging(true) + Kernel.stubs(:warn) + Kernel.expects(:warn).never + subject.warn('') + end + + it "should not warn if debugging is disabled" do + subject.debugging(false) + Kernel.stubs(:warn) + Kernel.expects(:warn).never + subject.warn('foo') + end + + it "should warn for any given element for an array if debugging is enabled" do + subject.debugging(true) + Kernel.stubs(:warn) + Kernel.expects(:warn).with('foo') + Kernel.expects(:warn).with('bar') + subject.warn( ['foo','bar']) + end + end + + describe "when warning once" do + it "should only warn once" do + Kernel.stubs(:warnonce) + Kernel.expects(:warn).with('foo').once + subject.warnonce('foo') + subject.warnonce('foo') + end + + it "should not warnonce if nil is passed" do + Kernel.stubs(:warn) + Kernel.expects(:warnonce).never + subject.warnonce(nil) + end + + it "should not warnonce if an empty string is passed" do + Kernel.stubs(:warn) + Kernel.expects(:warnonce).never + subject.warnonce('') + end + end + + describe "when setting debugging mode" do + it "should have debugging enabled using 1" do + subject.debugging(1) + subject.should be_debugging + end + it "should have debugging enabled using true" do + subject.debugging(true) + subject.should be_debugging + end + it "should have debugging enabled using any string except off" do + subject.debugging('aaaaa') + subject.should be_debugging + end + it "should have debugging disabled using 0" do + subject.debugging(0) + subject.should_not be_debugging + end + it "should have debugging disabled using false" do + subject.debugging(false) + subject.should_not be_debugging + end + it "should have debugging disabled using the string 'off'" do + subject.debugging('off') + subject.should_not be_debugging + end + end + + describe "when setting timing mode" do + it "should have timing enabled using 1" do + subject.timing(1) + subject.should be_timing + end + it "should have timing enabled using true" do + subject.timing(true) + subject.should be_timing + end + it "should have timing disabled using 0" do + subject.timing(0) + subject.should_not be_timing + end + it "should have timing disabled using false" do + subject.timing(false) + subject.should_not be_timing + end + end + +end diff --git a/spec/unit/facter_spec.rb b/spec/unit/facter_spec.rb index 2279c27019..42204dd678 100755 --- a/spec/unit/facter_spec.rb +++ b/spec/unit/facter_spec.rb @@ -124,127 +124,6 @@ Facter.should respond_to(:search_external_path) end - it "should have a method to query debugging mode" do - Facter.should respond_to(:debugging?) - end - - it "should have a method to query timing mode" do - Facter.should respond_to(:timing?) - end - - it "should have a method to show timing information" do - Facter.should respond_to(:show_time) - end - - it "should have a method to warn" do - Facter.should respond_to(:warn) - end - - describe "when warning" do - it "should warn if debugging is enabled" do - Facter.debugging(true) - Kernel.stubs(:warn) - Kernel.expects(:warn).with('foo') - Facter.warn('foo') - end - - it "should not warn if debugging is enabled but nil is passed" do - Facter.debugging(true) - Kernel.stubs(:warn) - Kernel.expects(:warn).never - Facter.warn(nil) - end - - it "should not warn if debugging is enabled but an empyt string is passed" do - Facter.debugging(true) - Kernel.stubs(:warn) - Kernel.expects(:warn).never - Facter.warn('') - end - - it "should not warn if debugging is disabled" do - Facter.debugging(false) - Kernel.stubs(:warn) - Kernel.expects(:warn).never - Facter.warn('foo') - end - - it "should warn for any given element for an array if debugging is enabled" do - Facter.debugging(true) - Kernel.stubs(:warn) - Kernel.expects(:warn).with('foo') - Kernel.expects(:warn).with('bar') - Facter.warn( ['foo','bar']) - end - end - - describe "when warning once" do - it "should only warn once" do - Kernel.stubs(:warnonce) - Kernel.expects(:warn).with('foo').once - Facter.warnonce('foo') - Facter.warnonce('foo') - end - - it "should not warnonce if nil is passed" do - Kernel.stubs(:warn) - Kernel.expects(:warnonce).never - Facter.warnonce(nil) - end - - it "should not warnonce if an empty string is passed" do - Kernel.stubs(:warn) - Kernel.expects(:warnonce).never - Facter.warnonce('') - end - end - - describe "when setting debugging mode" do - it "should have debugging enabled using 1" do - Facter.debugging(1) - Facter.should be_debugging - end - it "should have debugging enabled using true" do - Facter.debugging(true) - Facter.should be_debugging - end - it "should have debugging enabled using any string except off" do - Facter.debugging('aaaaa') - Facter.should be_debugging - end - it "should have debugging disabled using 0" do - Facter.debugging(0) - Facter.should_not be_debugging - end - it "should have debugging disabled using false" do - Facter.debugging(false) - Facter.should_not be_debugging - end - it "should have debugging disabled using the string 'off'" do - Facter.debugging('off') - Facter.should_not be_debugging - end - end - - describe "when setting timing mode" do - it "should have timing enabled using 1" do - Facter.timing(1) - Facter.should be_timing - end - it "should have timing enabled using true" do - Facter.timing(true) - Facter.should be_timing - end - it "should have timing disabled using 0" do - Facter.timing(0) - Facter.should_not be_timing - end - it "should have timing disabled using false" do - Facter.timing(false) - Facter.should_not be_timing - end - end - describe "when registering directories to search" do after { Facter.instance_variable_set("@search_path", []) } From 905f2090ab79d072e0a668a2da5ce4f38efd4fe4 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 3 Feb 2014 10:51:13 -0800 Subject: [PATCH 1597/3753] (maint) Simplify logging controls Ruby has these neat things called booleans that we can use to indicate true/false values. Because of this we can directly specify the truthiness of something instead of accepting arbitrary strings, numbers, and other classes and cocercing them to booleans. --- lib/facter/core/logging.rb | 61 ++++++++++------------------------ spec/unit/core/logging_spec.rb | 55 ++++++++++++------------------ 2 files changed, 38 insertions(+), 78 deletions(-) diff --git a/lib/facter/core/logging.rb b/lib/facter/core/logging.rb index c466b0c8a9..b908daf151 100644 --- a/lib/facter/core/logging.rb +++ b/lib/facter/core/logging.rb @@ -10,9 +10,9 @@ module Facter::Core::Logging RESET = "" # @api private - @@debug = 0 + @@debug = false # @api private - @@timing = 0 + @@timing = false # @api private @@warn_messages = {} # @api private @@ -85,67 +85,40 @@ def show_time(string) puts "#{GREEN}#{string}#{RESET}" if string and self.timing? end - # Sets debugging on or off. + # Enable or disable logging of debug messages # + # @param bool [true, false] # @return [void] # # @api private - def debugging(bit) - if bit - case bit - when TrueClass; @@debug = 1 - when FalseClass; @@debug = 0 - when Fixnum - if bit > 0 - @@debug = 1 - else - @@debug = 0 - end - when String; - if bit.downcase == 'off' - @@debug = 0 - else - @@debug = 1 - end - else - @@debug = 0 - end - else - @@debug = 0 - end + def debugging(bool) + @@debug = bool end - # Returns whether debugging output is turned on + # Is debugging enabled? + # + # @return [true, false] + # + # @api private def debugging? - @@debug != 0 + @@debug end - # Sets whether timing messages are displayed. + # Enable or disable logging of timing information # + # @param bool [true, false] # @return [void] # # @api private - def timing(bit) - if bit - case bit - when TrueClass; @@timing = 1 - when Fixnum - if bit > 0 - @@timing = 1 - else - @@timing = 0 - end - end - else - @@timing = 0 - end + def timing(bool) + @@timing = bool end # Returns whether timing output is turned on # # @api private def timing? - @@timing != 0 + @@timing end # Clears the seen state of warning messages. See {warnonce}. diff --git a/spec/unit/core/logging_spec.rb b/spec/unit/core/logging_spec.rb index 14e09c43a7..2258912a4b 100644 --- a/spec/unit/core/logging_spec.rb +++ b/spec/unit/core/logging_spec.rb @@ -64,50 +64,37 @@ end end - describe "when setting debugging mode" do - it "should have debugging enabled using 1" do - subject.debugging(1) - subject.should be_debugging - end - it "should have debugging enabled using true" do + describe "when setting the debugging mode" do + it "is enabled when the given value is true" do subject.debugging(true) - subject.should be_debugging - end - it "should have debugging enabled using any string except off" do - subject.debugging('aaaaa') - subject.should be_debugging - end - it "should have debugging disabled using 0" do - subject.debugging(0) - subject.should_not be_debugging + expect(subject.debugging?).to be_true end - it "should have debugging disabled using false" do + + it "is disabled when the given value is false" do subject.debugging(false) - subject.should_not be_debugging + expect(subject.debugging?).to be_false end - it "should have debugging disabled using the string 'off'" do - subject.debugging('off') - subject.should_not be_debugging + + it "is disabled when the given value is nil" do + subject.debugging(nil) + expect(subject.debugging?).to be_false end end - describe "when setting timing mode" do - it "should have timing enabled using 1" do - subject.timing(1) - subject.should be_timing - end - it "should have timing enabled using true" do + describe "when setting the timing mode" do + it "is enabled when the given value is true" do subject.timing(true) - subject.should be_timing + expect(subject.timing?).to be_true end - it "should have timing disabled using 0" do - subject.timing(0) - subject.should_not be_timing - end - it "should have timing disabled using false" do + + it "is disabled when the given value is false" do subject.timing(false) - subject.should_not be_timing + expect(subject.timing?).to be_false end - end + it "is disabled when the given value is nil" do + subject.timing(nil) + expect(subject.timing?).to be_false + end + end end From 421748c99e4b163be87b34ddd62b7f4ca290f14c Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 3 Feb 2014 11:03:50 -0800 Subject: [PATCH 1598/3753] (maint) Simplify argument handling for `warn` messages `Facter::Core::Logging.warn` could accept a string or Array of strings, but it adds needless complexity and can be better handled by calling classes. This simplifies the warn method to accept and log a string. --- lib/facter/core/logging.rb | 3 +-- spec/unit/core/logging_spec.rb | 8 -------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/lib/facter/core/logging.rb b/lib/facter/core/logging.rb index b908daf151..a573f4ab86 100644 --- a/lib/facter/core/logging.rb +++ b/lib/facter/core/logging.rb @@ -53,8 +53,7 @@ def debugonce(msg) # @return [void] def warn(msg) if self.debugging? and msg and not msg.empty? - msg = [msg] unless msg.respond_to? :each - msg.each { |line| Kernel.warn line } + Kernel.warn msg end end diff --git a/spec/unit/core/logging_spec.rb b/spec/unit/core/logging_spec.rb index 2258912a4b..44ae3c5117 100644 --- a/spec/unit/core/logging_spec.rb +++ b/spec/unit/core/logging_spec.rb @@ -33,14 +33,6 @@ Kernel.expects(:warn).never subject.warn('foo') end - - it "should warn for any given element for an array if debugging is enabled" do - subject.debugging(true) - Kernel.stubs(:warn) - Kernel.expects(:warn).with('foo') - Kernel.expects(:warn).with('bar') - subject.warn( ['foo','bar']) - end end describe "when warning once" do From 6c6c3945d558849610f2b58b2d04c63498c9c0f1 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 3 Feb 2014 11:06:00 -0800 Subject: [PATCH 1599/3753] (FACT-273) Always log warnings regardless of log level If a class is trying to emit a warning the expected behavior is to log it, since if it's worth warning about it's worth seeing every time, and if it's not worth warning about every time about then it should debug message. This commit ensures that all warnings are actually handled. --- lib/facter/core/logging.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/core/logging.rb b/lib/facter/core/logging.rb index a573f4ab86..185440d3c6 100644 --- a/lib/facter/core/logging.rb +++ b/lib/facter/core/logging.rb @@ -52,7 +52,7 @@ def debugonce(msg) # # @return [void] def warn(msg) - if self.debugging? and msg and not msg.empty? + if msg and not msg.empty? Kernel.warn msg end end From b34eb1e2e6abcad892f0bac04fb9bb1506e1c73d Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 3 Feb 2014 11:29:19 -0800 Subject: [PATCH 1600/3753] (maint) Delegate #warnonce printing to #warn --- lib/facter/core/logging.rb | 4 ++-- spec/unit/core/logging_spec.rb | 17 ++--------------- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/lib/facter/core/logging.rb b/lib/facter/core/logging.rb index 185440d3c6..c7e8674467 100644 --- a/lib/facter/core/logging.rb +++ b/lib/facter/core/logging.rb @@ -68,9 +68,9 @@ def warn(msg) # # @return [void] def warnonce(msg) - if msg and not msg.empty? and @@warn_messages[msg].nil? + if @@warn_messages[msg].nil? + self.warn(msg) @@warn_messages[msg] = true - Kernel.warn(msg) end end diff --git a/spec/unit/core/logging_spec.rb b/spec/unit/core/logging_spec.rb index 44ae3c5117..7182e82f44 100644 --- a/spec/unit/core/logging_spec.rb +++ b/spec/unit/core/logging_spec.rb @@ -36,24 +36,11 @@ end describe "when warning once" do - it "should only warn once" do - Kernel.stubs(:warnonce) - Kernel.expects(:warn).with('foo').once + it "only logs a given warning string once" do + subject.expects(:warn).with('foo').once subject.warnonce('foo') subject.warnonce('foo') end - - it "should not warnonce if nil is passed" do - Kernel.stubs(:warn) - Kernel.expects(:warnonce).never - subject.warnonce(nil) - end - - it "should not warnonce if an empty string is passed" do - Kernel.stubs(:warn) - Kernel.expects(:warnonce).never - subject.warnonce('') - end end describe "when setting the debugging mode" do From 3e5db044513b2549e28df6a45ffabecd74df741f Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 3 Feb 2014 11:59:23 -0800 Subject: [PATCH 1601/3753] (maint) Emit warnings when log methods called without string --- lib/facter/core/logging.rb | 19 +++++++---- spec/unit/core/logging_spec.rb | 59 ++++++++++++++++++++++++---------- 2 files changed, 54 insertions(+), 24 deletions(-) diff --git a/lib/facter/core/logging.rb b/lib/facter/core/logging.rb index c7e8674467..9f213a7c57 100644 --- a/lib/facter/core/logging.rb +++ b/lib/facter/core/logging.rb @@ -20,14 +20,16 @@ module Facter::Core::Logging # Prints a debug message if debugging is turned on # - # @param string [String] the debug message + # @param msg [String] the debug message # @return [void] - def debug(string) - if string.nil? - return - end + def debug(msg) if self.debugging? - puts GREEN + string + RESET + if msg.nil? or msg.empty? + invoker = caller[0].slice(/.*:\d+/) + self.warn "#{self.class}#debug invoked with invalid message #{msg.inspect}:#{msg.class} at #{invoker}" + else + puts GREEN + msg + RESET + end end end @@ -52,7 +54,10 @@ def debugonce(msg) # # @return [void] def warn(msg) - if msg and not msg.empty? + if msg.nil? or msg.empty? + invoker = caller[0].slice(/.*:\d+/) + Kernel.warn "#{self.class}#debug invoked with invalid message #{msg.inspect}:#{msg.class} at #{invoker}" + else Kernel.warn msg end end diff --git a/spec/unit/core/logging_spec.rb b/spec/unit/core/logging_spec.rb index 7182e82f44..8bcf9e36ec 100644 --- a/spec/unit/core/logging_spec.rb +++ b/spec/unit/core/logging_spec.rb @@ -5,33 +5,58 @@ subject { described_class } + after(:all) do + subject.debugging(false) + subject.timing(false) + end + + describe "emitting debug messages" do + it "doesn't log a message when debugging is disabled" do + subject.debugging(false) + subject.expects(:puts).never + subject.debug("foo") + end + + describe "and debugging is enabled" do + before { subject.debugging(true) } + it "emits a warning when called with nil" do + subject.expects(:warn).with { |msg| expect(msg).to match /invalid message nil:NilClass/ } + subject.debug(nil) + end + + it "emits a warning when called with an empty string" do + subject.expects(:warn).with { |msg| expect(msg).to match /invalid message "":String/ } + subject.debug("") + end + + it "prints the message when logging is enabled" do + subject.expects(:puts).with { |msg| expect(msg).to match /foo/ } + subject.debug("foo") + end + end + end + describe "when warning" do - it "should warn if debugging is enabled" do + it "emits a warning when given a string" do subject.debugging(true) - Kernel.stubs(:warn) Kernel.expects(:warn).with('foo') subject.warn('foo') end - it "should not warn if debugging is enabled but nil is passed" do - subject.debugging(true) - Kernel.stubs(:warn) - Kernel.expects(:warn).never - subject.warn(nil) + it "emits a warning regardless of log level" do + subject.debugging(false) + Kernel.expects(:warn).with "foo" + subject.warn "foo" end - it "should not warn if debugging is enabled but an empyt string is passed" do - subject.debugging(true) - Kernel.stubs(:warn) - Kernel.expects(:warn).never - subject.warn('') + it "emits a warning if nil is passed" do + Kernel.expects(:warn).with { |msg| expect(msg).to match /invalid message nil:NilClass/ } + subject.warn(nil) end - it "should not warn if debugging is disabled" do - subject.debugging(false) - Kernel.stubs(:warn) - Kernel.expects(:warn).never - subject.warn('foo') + it "emits a warning if an empty string is passed" do + Kernel.expects(:warn).with { |msg| expect(msg).to match /invalid message "":String/ } + subject.warn('') end end From 0d8ec3413657f71fa1d5c790eaf0a2d66d133c42 Mon Sep 17 00:00:00 2001 From: Franz Pletz Date: Sat, 13 Jul 2013 17:13:28 +0200 Subject: [PATCH 1602/3753] (#20216) Print timings to stderr instead of stdout Facter prints both timings and facts to stdout if timing is enabled. To facilitate separating both outputs the timings should be printed to stderr instead. --- lib/facter/core/logging.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/core/logging.rb b/lib/facter/core/logging.rb index 9f213a7c57..7c46d96dac 100644 --- a/lib/facter/core/logging.rb +++ b/lib/facter/core/logging.rb @@ -86,7 +86,7 @@ def warnonce(msg) # # @api private def show_time(string) - puts "#{GREEN}#{string}#{RESET}" if string and self.timing? + $stderr.puts "#{GREEN}#{string}#{RESET}" if string and self.timing? end # Enable or disable logging of debug messages From a0391397cfcaece697c4a28982e97382718db473 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 3 Feb 2014 16:03:12 -0800 Subject: [PATCH 1603/3753] (maint) Remove Facter.method_missing The ability to use method_missing was added in 2006 but was never documented, explained, or generally acknowledge. It was a constant surprise when invoking a non-existent method on Facter would cause all facts to be loaded and possibly a completely nonsensical value would be returned. This commit removes the given code so we can return to a more sane state of affairs. --- lib/facter.rb | 33 --------------------------------- lib/facter/util/netmask.rb | 4 ++-- spec/unit/facter_spec.rb | 16 ---------------- 3 files changed, 2 insertions(+), 51 deletions(-) diff --git a/lib/facter.rb b/lib/facter.rb index 22449129da..6277aeb1aa 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -166,39 +166,6 @@ def self.each end end - class << self - # Allow users to call fact names directly on the Facter class, - # either retrieving the value or comparing it to an existing value. - # - # @api private - def method_missing(name, *args) - question = false - if name.to_s =~ /\?$/ - question = true - name = name.to_s.sub(/\?$/,'') - end - - if fact = collection.fact(name) - if question - value = fact.value.downcase - args.each do |arg| - if arg.to_s.downcase == value - return true - end - end - - # If we got this far, there was no match. - return false - else - return fact.value - end - else - # Else, fail like a normal missing method. - raise NoMethodError, "Could not find fact '%s'" % name - end - end - end - # Clears all cached values and removes all facts from memory. # # @return [void] diff --git a/lib/facter/util/netmask.rb b/lib/facter/util/netmask.rb index 41503c87fb..c12e3f2ad7 100644 --- a/lib/facter/util/netmask.rb +++ b/lib/facter/util/netmask.rb @@ -8,13 +8,13 @@ def self.get_netmask when 'Linux' ops = { :ifconfig_opts => ['2>/dev/null'], - :regex => %r{#{Facter.ipaddress}.*?(?:Mask:|netmask)\s*(#{ipregex})}x, + :regex => %r{#{Facter.value(:ipaddress)}.*?(?:Mask:|netmask)\s*(#{ipregex})}x, :munge => nil, } when 'SunOS' ops = { :ifconfig_opts => ['-a'], - :regex => %r{\s+ inet \s #{Facter.ipaddress} \s netmask \s (\w{8})}x, + :regex => %r{\s+ inet \s #{Facter.value(:ipaddress)} \s netmask \s (\w{8})}x, :munge => Proc.new { |mask| mask.scan(/../).collect do |byte| byte.to_i(16) end.join('.') } } when 'FreeBSD','NetBSD','OpenBSD', 'Darwin', 'GNU/kFreeBSD', 'DragonFly' diff --git a/spec/unit/facter_spec.rb b/spec/unit/facter_spec.rb index 42204dd678..6817446002 100755 --- a/spec/unit/facter_spec.rb +++ b/spec/unit/facter_spec.rb @@ -77,22 +77,6 @@ end end - describe "when asked for a fact as an undefined Facter class method" do - describe "and the collection is already initialized" do - it "should return the fact's value" do - Facter.collection - Facter.ipaddress.should == Facter['ipaddress'].value - end - end - - describe "and the collection has been just reset" do - it "should return the fact's value" do - Facter.reset - Facter.ipaddress.should == Facter['ipaddress'].value - end - end - end - describe "when passed code as a block" do it "should execute the provided block" do Facter.add("block_testing") { setcode { "foo" } } From 6258ae04da56fdf9af4c77f3219ec1511f04c163 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Tue, 4 Feb 2014 13:53:02 -0800 Subject: [PATCH 1604/3753] (maint) Remove test which tests nothing (but can provoke a failure in tests run after it) --- spec/unit/core/resolvable_spec.rb | 8 -------- 1 file changed, 8 deletions(-) diff --git a/spec/unit/core/resolvable_spec.rb b/spec/unit/core/resolvable_spec.rb index 5555974f86..a35bce5aec 100644 --- a/spec/unit/core/resolvable_spec.rb +++ b/spec/unit/core/resolvable_spec.rb @@ -55,14 +55,6 @@ def initialize(name) subject.value end - it "times out after the provided timeout" do - def subject.resolve_value - sleep 2 - end - subject.timeout = 0.1 - subject.value - end - it "returns nil if the timeout was reached" do Timeout.expects(:timeout).raises Timeout::Error From b6a0a65a34192a1e8b2755e528cbffd7d00a251c Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Wed, 5 Feb 2014 11:42:57 -0800 Subject: [PATCH 1605/3753] (fact-81) Use Emit.dump rather than to_plist See https://github.com/puppetlabs/facter/pull/499 and the linked redmine tickets for backstory. The basic issue here is that CFPropertyList monkey-patches some core ruby classes with a to_plist method, which defaults to returning binary data. This code was originally written to the vendored CFPropertyList previously included in facter (but unvendored in fact-94), which provided a different to_plist implementation returning text data. This commit avoids the conflicting to_plist implementations by calling its own dump method, thus no change in behavior. --- lib/facter/util/plist/generator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/util/plist/generator.rb b/lib/facter/util/plist/generator.rb index 8f9ea3a47f..6cbc4b346b 100644 --- a/lib/facter/util/plist/generator.rb +++ b/lib/facter/util/plist/generator.rb @@ -51,7 +51,7 @@ def self.dump(obj, envelope = true) # Writes the serialized object's plist to the specified filename. def self.save_plist(obj, filename) File.open(filename, 'wb') do |f| - f.write(obj.to_plist) + f.write(Plist::Emit.dump(obj)) end end From d56f472c2ffda12e572f8f849513efc34dc81b66 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Wed, 5 Feb 2014 13:28:34 -0800 Subject: [PATCH 1606/3753] (fact-273) Return a string for stub of :kernel fact Previous to this change, this spec test was stubbing :kernel as :windows. But now facter a) does type-checking on fact values (Symbol is not allowed) and b) warns on type-check errors. Changing this stub to return 'windows' reduces warning messages during spec runs. --- spec/unit/util/macaddress_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/unit/util/macaddress_spec.rb b/spec/unit/util/macaddress_spec.rb index 17e55006de..7435dfb6bf 100755 --- a/spec/unit/util/macaddress_spec.rb +++ b/spec/unit/util/macaddress_spec.rb @@ -90,7 +90,7 @@ include FacterSpec::WindowsNetwork before :each do - Facter.fact(:kernel).stubs(:value).returns(:windows) + Facter.fact(:kernel).stubs(:value).returns('windows') Facter::Util::Registry.stubs(:hklm_read).returns(nic_bindings) end From 9c221b7f321e6ee57354cfd2867a8c26413e3f0d Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Wed, 5 Feb 2014 13:35:04 -0800 Subject: [PATCH 1607/3753] (fact-273) Expect a warning message that can be expected Add a warning message expectation to suppress the warning message on stdout/stderr during spec runs. Also, remove a stub that duplicated a stub in the before block. --- spec/unit/util/macaddress_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/unit/util/macaddress_spec.rb b/spec/unit/util/macaddress_spec.rb index 7435dfb6bf..df513b61ec 100755 --- a/spec/unit/util/macaddress_spec.rb +++ b/spec/unit/util/macaddress_spec.rb @@ -67,11 +67,11 @@ it "should warn about the lack of default" do Facter.expects(:warn).with("Could not find a default route. Using first non-loopback interface") - Facter::Util::Macaddress::Darwin.stubs(:default_interface).returns('') Facter::Util::Macaddress::Darwin.macaddress end it "should return the macaddress of the first non-loopback interface" do + Facter.expects(:warn).with("Could not find a default route. Using first non-loopback interface") Facter::Util::Macaddress::Darwin.macaddress.should == fallback_macaddress end end From 319abe8b9a61061ca3d75edf762db7d887317d5e Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Wed, 5 Feb 2014 09:43:11 -0800 Subject: [PATCH 1608/3753] (maint) Don't warn on non-existent paths Facter was emitting warnings whenever a path specified by `Facter.search_path` was not a directory, but Puppet was automatically adding two paths via the `factpath` setting that seem to never exist. This could cause hundreds of warnings to be generated, none of which are really that useful. This commit changes the warnings to only be emitted when a manually specified path is not absolute and silently ignores all paths that aren't directories. --- lib/facter/util/loader.rb | 43 +++++++++++++---------------- spec/unit/util/loader_spec.rb | 51 +++++++++++++++++++++-------------- 2 files changed, 50 insertions(+), 44 deletions(-) diff --git a/lib/facter/util/loader.rb b/lib/facter/util/loader.rb index a8fb88d19f..25339fe82b 100644 --- a/lib/facter/util/loader.rb +++ b/lib/facter/util/loader.rb @@ -54,56 +54,51 @@ def load_all @loaded_all = true end - # List of directories to search for fact files. + # List directories to search for fact files. # # Search paths are gathered from the following sources: # # 1. $LOAD_PATH entries are expanded to absolute paths # 2. ENV['FACTERLIB'] is split and used verbatim - # 3. Entries from Facter::search_path are used verbatim + # 3. Entries from Facter.search_path are used verbatim # - # A warning will be generated for any path(s) from Facter::search_path that - # are not an absolute path to an existing directory. + # A warning will be generated for paths in Facter.search_path that are not + # absolute directories. # # @api public # @return [Array] def search_path - result = [] - result += $LOAD_PATH.map { |path| File.expand_path('facter', path) } + search_paths = [] + search_paths += $LOAD_PATH.map { |path| File.expand_path('facter', path) } if @environment_vars.include?("FACTERLIB") - result += @environment_vars["FACTERLIB"].split(File::PATH_SEPARATOR) + search_paths += @environment_vars["FACTERLIB"].split(File::PATH_SEPARATOR) end - # silently ignore bad search paths from $LOAD_PATH and FACTERLIB - result = result.select { |path| valid_search_path?(path) } + search_paths.select! { |path| valid_search_path?(path) } - # This allows others to register additional paths we should search. - # We are assuming that these are already absolute paths. - result += Facter.search_path.select do |path| - valid = valid_search_path?(path) - Facter.warn "Excluding #{path} from search path. Fact file paths must be an absolute directory" unless valid - valid + Facter.search_path.each do |path| + if valid_search_path?(path) + search_paths << path + else + Facter.warn "Excluding #{path} from search path. Fact file paths must be an absolute directory" + end end - # remove any dups - result.uniq + search_paths.select! { |path| File.directory?(path) } + + search_paths.uniq end private - # Validate that a path string is a valid to use for loading loading fact .rb - # files from. The path must both be absolute and a directory. + # Validate that the given path is valid, ie it is an absolute path. # # @api private # @param path [String] # @return [Boolean] def valid_search_path?(path) - unless File.directory?(path) and Pathname.new(path).absolute? - return false - end - - true + Pathname.new(path).absolute? end # Load a file and record is paths to prevent duplicate loads. diff --git a/spec/unit/util/loader_spec.rb b/spec/unit/util/loader_spec.rb index 8ba1185c73..2867f36b0f 100755 --- a/spec/unit/util/loader_spec.rb +++ b/spec/unit/util/loader_spec.rb @@ -51,15 +51,7 @@ def loader_from(places) ' /', ' \/', ].each do |dir| - it "should be false for relative path to non-directory #{dir}" do - File.stubs(:directory?).with(dir).returns false - - @loader.should_not be_valid_search_path dir - end - - it "should be false for relative path to directory #{dir}" do - File.stubs(:directory?).with(dir).returns true - + it "should be false for relative path #{dir}" do @loader.should_not be_valid_search_path dir end end @@ -75,15 +67,7 @@ def loader_from(places) '/ ', '/ /..', ].each do |dir| - it "should be false for absolute path to non-directory #{dir}" do - File.stubs(:directory?).with(dir).returns false - - @loader.should_not be_valid_search_path dir - end - - it "should be true for absolute path to directory #{dir}" do - File.stubs(:directory?).with(dir).returns true - + it "should be true for absolute path #{dir}" do @loader.should be_valid_search_path dir end end @@ -119,26 +103,53 @@ def loader_from(places) Facter.expects(:search_path).returns %w{/one /two} @loader.stubs(:valid_search_path?).returns true + File.stubs(:directory?).returns false + File.stubs(:directory?).with('/one').returns true + File.stubs(:directory?).with('/two').returns true + paths = @loader.search_path paths.should be_include("/one") paths.should be_include("/two") end it "should warn on invalid search paths registered with Facter" do + Facter.expects(:search_path).returns %w{/one two/three} + @loader.stubs(:valid_search_path?).returns false + @loader.stubs(:valid_search_path?).with('/one').returns true + @loader.stubs(:valid_search_path?).with('two/three').returns false + Facter.expects(:warn).with('Excluding two/three from search path. Fact file paths must be an absolute directory').once + + File.stubs(:directory?).returns false + File.stubs(:directory?).with('/one').returns true + + paths = @loader.search_path + paths.should be_include("/one") + paths.should_not be_include("two/three") + end + + it "should strip paths that are valid paths but not are not present" do Facter.expects(:search_path).returns %w{/one /two} @loader.stubs(:valid_search_path?).returns false @loader.stubs(:valid_search_path?).with('/one').returns true - @loader.stubs(:valid_search_path?).with('/two').returns false - Facter.expects(:warn).with('Excluding /two from search path. Fact file paths must be an absolute directory').once + @loader.stubs(:valid_search_path?).with('/two').returns true + + File.stubs(:directory?).returns false + File.stubs(:directory?).with('/one').returns true + File.stubs(:directory?).with('/two').returns false paths = @loader.search_path paths.should be_include("/one") + paths.should_not be_include('/two') end describe "and the FACTERLIB environment variable is set" do it "should include all paths in FACTERLIB" do loader = Facter::Util::Loader.new("FACTERLIB" => "/one/path#{File::PATH_SEPARATOR}/two/path") + File.stubs(:directory?).returns false + File.stubs(:directory?).with('/one/path').returns true + File.stubs(:directory?).with('/two/path').returns true + loader.stubs(:valid_search_path?).returns true paths = loader.search_path %w{/one/path /two/path}.each do |dir| From fae7a76aca25806e299c4d38f3bc7d19a125ccb8 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Wed, 5 Feb 2014 13:46:45 -0800 Subject: [PATCH 1609/3753] (fact-273) Remove a test which was a subset of the following test --- spec/unit/core/resolvable_spec.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/spec/unit/core/resolvable_spec.rb b/spec/unit/core/resolvable_spec.rb index a35bce5aec..a237ed348f 100644 --- a/spec/unit/core/resolvable_spec.rb +++ b/spec/unit/core/resolvable_spec.rb @@ -34,11 +34,6 @@ def initialize(name) expect(subject.value).to eq('stuff') end - it "returns nil if an exception was raised" do - subject.expects(:resolve_value).raises RuntimeError, "kaboom!" - expect(subject.value).to eq nil - end - it "logs a warning if an exception was raised" do subject.expects(:resolve_value).raises RuntimeError, "kaboom!" Facter.expects(:warn).with('Could not retrieve resolvable: kaboom!') From 5de81029b1cc77cbc855248a978ec74fb749e2d9 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Wed, 5 Feb 2014 13:50:57 -0800 Subject: [PATCH 1610/3753] (fact-273) Add expectation for Facter.warn on timeouts --- spec/unit/core/resolvable_spec.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/unit/core/resolvable_spec.rb b/spec/unit/core/resolvable_spec.rb index a237ed348f..e2ed98ed5d 100644 --- a/spec/unit/core/resolvable_spec.rb +++ b/spec/unit/core/resolvable_spec.rb @@ -51,6 +51,7 @@ def initialize(name) end it "returns nil if the timeout was reached" do + Facter.expects(:warn).with("Timed out seeking value for resolvable") Timeout.expects(:timeout).raises Timeout::Error expect(subject.value).to be_nil @@ -61,6 +62,7 @@ def initialize(name) Thread.expects(:new).yields Process.expects(:waitall) + Facter.expects(:warn).with("Timed out seeking value for resolvable") Timeout.expects(:timeout).raises Timeout::Error subject.value From b53fc4d0c8a0a456b92ed8b5ca07dd7f13de7804 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Wed, 5 Feb 2014 13:56:04 -0800 Subject: [PATCH 1611/3753] (fact-273) Add expectation for Facter.warn on exceptions retrieving operatingsystemrelease --- spec/unit/operatingsystemrelease_spec.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/unit/operatingsystemrelease_spec.rb b/spec/unit/operatingsystemrelease_spec.rb index 7aab1e0fd4..e929f97a0c 100755 --- a/spec/unit/operatingsystemrelease_spec.rb +++ b/spec/unit/operatingsystemrelease_spec.rb @@ -133,6 +133,7 @@ it "should fallback to the kernelrelease fact if /etc/release is empty" do Facter::Util::FileRead.stubs(:read).with('/etc/release'). raises EOFError + Facter.expects(:warn).with("Could not retrieve operatingsystemrelease: EOFError") Facter.fact(:operatingsystemrelease).value. should == Facter.fact(:kernelrelease).value end @@ -140,6 +141,7 @@ it "should fallback to the kernelrelease fact if /etc/release is not present" do Facter::Util::FileRead.stubs(:read).with('/etc/release'). raises Errno::ENOENT + Facter.expects(:warn).with("Could not retrieve operatingsystemrelease: No such file or directory") Facter.fact(:operatingsystemrelease).value. should == Facter.fact(:kernelrelease).value end From b67894bce5110bf2012b245b6c016fc67d7374a1 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Wed, 5 Feb 2014 14:39:36 -0800 Subject: [PATCH 1612/3753] (fact-273) Add expectation for Facter.warn on ignored file extensions --- spec/unit/util/directory_loader_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/unit/util/directory_loader_spec.rb b/spec/unit/util/directory_loader_spec.rb index 79ad2b2cd5..db99d15603 100644 --- a/spec/unit/util/directory_loader_spec.rb +++ b/spec/unit/util/directory_loader_spec.rb @@ -55,6 +55,7 @@ %w{bak orig}.each do |ext| it "should ignore files with an extension of '#{ext}'" do + Facter.expects(:warn).with(regexp_matches(/#{ext}/)) write_to_file("data" + ".#{ext}", "foo=bar") subject.load(collection) From b1f70c33f4865c621bffe3421feacdf3006822d1 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Wed, 5 Feb 2014 13:54:57 -0800 Subject: [PATCH 1613/3753] (doc) Update documentation for Facter#search --- lib/facter.rb | 4 ++-- lib/facter/util/loader.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/facter.rb b/lib/facter.rb index 6277aeb1aa..68b3d7b0fc 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -197,8 +197,8 @@ def self.loadfacts @search_path = [] - # Registers directories to be searched for facts. Relative paths will - # be interpreted in the current working directory. + # Register directories to be searched for facts. The registered directories + # must be absolute paths or they will be ignored. # # @param dirs [String] directories to search # diff --git a/lib/facter/util/loader.rb b/lib/facter/util/loader.rb index 25339fe82b..4a71e1e4ff 100644 --- a/lib/facter/util/loader.rb +++ b/lib/facter/util/loader.rb @@ -75,7 +75,7 @@ def search_path search_paths += @environment_vars["FACTERLIB"].split(File::PATH_SEPARATOR) end - search_paths.select! { |path| valid_search_path?(path) } + search_paths.delete_if { |path| ! valid_search_path?(path) } Facter.search_path.each do |path| if valid_search_path?(path) @@ -85,7 +85,7 @@ def search_path end end - search_paths.select! { |path| File.directory?(path) } + search_paths.delete_if { |path| ! File.directory?(path) } search_paths.uniq end From 329b609553dc53d6fb871c1b6694a45faf8cd171 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Wed, 5 Feb 2014 15:25:54 -0800 Subject: [PATCH 1614/3753] (maint) Update loader specs to use let blocks over ivars --- spec/unit/util/loader_spec.rb | 40 ++++++++++++++++------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/spec/unit/util/loader_spec.rb b/spec/unit/util/loader_spec.rb index 2867f36b0f..667d96c3a1 100755 --- a/spec/unit/util/loader_spec.rb +++ b/spec/unit/util/loader_spec.rb @@ -29,9 +29,7 @@ def loader_from(places) end describe "#valid_seach_path?" do - before :each do - @loader = Facter::Util::Loader.new - end + let(:loader) { Facter::Util::Loader.new } # Used to have test for " " as a directory since that should # be a relative directory, but on Windows in both 1.8.7 and @@ -52,7 +50,7 @@ def loader_from(places) ' \/', ].each do |dir| it "should be false for relative path #{dir}" do - @loader.should_not be_valid_search_path dir + loader.should_not be_valid_search_path dir end end [ @@ -68,22 +66,20 @@ def loader_from(places) '/ /..', ].each do |dir| it "should be true for absolute path #{dir}" do - @loader.should be_valid_search_path dir + loader.should be_valid_search_path dir end end end describe "when determining the search path" do - before do - @loader = Facter::Util::Loader.new - end + let(:loader) { Facter::Util::Loader.new } it "should include the facter subdirectory of all paths in ruby LOAD_PATH" do dirs = $LOAD_PATH.collect { |d| File.expand_path('facter', d) } - @loader.stubs(:valid_search_path?).returns(true) + loader.stubs(:valid_search_path?).returns(true) File.stubs(:directory?).returns true - paths = @loader.search_path + paths = loader.search_path dirs.each do |dir| paths.should be_include(dir) @@ -92,8 +88,8 @@ def loader_from(places) it "should exclude invalid search paths" do dirs = $LOAD_PATH.collect { |d| File.join(d, "facter") } - @loader.stubs(:valid_search_path?).returns(false) - paths = @loader.search_path + loader.stubs(:valid_search_path?).returns(false) + paths = loader.search_path dirs.each do |dir| paths.should_not be_include(dir) end @@ -101,43 +97,43 @@ def loader_from(places) it "should include all search paths registered with Facter" do Facter.expects(:search_path).returns %w{/one /two} - @loader.stubs(:valid_search_path?).returns true + loader.stubs(:valid_search_path?).returns true File.stubs(:directory?).returns false File.stubs(:directory?).with('/one').returns true File.stubs(:directory?).with('/two').returns true - paths = @loader.search_path + paths = loader.search_path paths.should be_include("/one") paths.should be_include("/two") end it "should warn on invalid search paths registered with Facter" do Facter.expects(:search_path).returns %w{/one two/three} - @loader.stubs(:valid_search_path?).returns false - @loader.stubs(:valid_search_path?).with('/one').returns true - @loader.stubs(:valid_search_path?).with('two/three').returns false + loader.stubs(:valid_search_path?).returns false + loader.stubs(:valid_search_path?).with('/one').returns true + loader.stubs(:valid_search_path?).with('two/three').returns false Facter.expects(:warn).with('Excluding two/three from search path. Fact file paths must be an absolute directory').once File.stubs(:directory?).returns false File.stubs(:directory?).with('/one').returns true - paths = @loader.search_path + paths = loader.search_path paths.should be_include("/one") paths.should_not be_include("two/three") end it "should strip paths that are valid paths but not are not present" do Facter.expects(:search_path).returns %w{/one /two} - @loader.stubs(:valid_search_path?).returns false - @loader.stubs(:valid_search_path?).with('/one').returns true - @loader.stubs(:valid_search_path?).with('/two').returns true + loader.stubs(:valid_search_path?).returns false + loader.stubs(:valid_search_path?).with('/one').returns true + loader.stubs(:valid_search_path?).with('/two').returns true File.stubs(:directory?).returns false File.stubs(:directory?).with('/one').returns true File.stubs(:directory?).with('/two').returns false - paths = @loader.search_path + paths = loader.search_path paths.should be_include("/one") paths.should_not be_include('/two') end From c57c7dcf3a16d295c776b64c31e65cf6699d717f Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Wed, 5 Feb 2014 15:30:34 -0800 Subject: [PATCH 1615/3753] (fact-273) Add expectation for Facter.warn on resolutions that throw an exception --- spec/unit/util/collection_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/unit/util/collection_spec.rb b/spec/unit/util/collection_spec.rb index 441497817d..3a80613092 100755 --- a/spec/unit/util/collection_spec.rb +++ b/spec/unit/util/collection_spec.rb @@ -56,6 +56,7 @@ end it "should discard resolutions that throw an exception when added" do + Facter.expects(:warn).with("Unable to add resolve for yay: ") lambda { collection.add('yay') do raise From 9e59aeb7db0a61123538bbbcfdc9fc14356e99ca Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Wed, 5 Feb 2014 11:30:41 -0800 Subject: [PATCH 1616/3753] (maint) Reset facter search path when resetting facts The previous implementation of Facter search paths was only additive and there was no way to remove Facter search paths once added. This is especially problematic when running the Puppet specs which wind up reloading Facter frequently and adding search paths, and can wind up adding thousands of meaningless paths. This commit resets the Facter search path on `Facter.reset` so that when facts are reset, the known search pats are reset as well. --- lib/facter.rb | 13 +++++++++++-- spec/unit/facter_spec.rb | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/facter.rb b/lib/facter.rb index 68b3d7b0fc..e85c99b1a2 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -184,6 +184,7 @@ def self.clear # @api public def self.reset @collection = nil + reset_search_path! end # Loads all facts. @@ -195,8 +196,6 @@ def self.loadfacts collection.load_all end - @search_path = [] - # Register directories to be searched for facts. The registered directories # must be absolute paths or they will be ignored. # @@ -218,6 +217,16 @@ def self.search_path @search_path.dup end + # Reset the Facter search directories. + # + # @api private + # @return [void] + def self.reset_search_path! + @search_path = [] + end + + reset_search_path! + # Registers directories to be searched for external facts. # # @param dirs [Array] directories to search diff --git a/spec/unit/facter_spec.rb b/spec/unit/facter_spec.rb index 6817446002..1b87ef3634 100755 --- a/spec/unit/facter_spec.rb +++ b/spec/unit/facter_spec.rb @@ -109,7 +109,7 @@ end describe "when registering directories to search" do - after { Facter.instance_variable_set("@search_path", []) } + after { Facter.reset_search_path! } it "should allow registration of a directory" do Facter.search "/my/dir" From 04148ec09cd083ab8bd7a14b867505bc029fc4c9 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Thu, 6 Feb 2014 14:43:28 -0800 Subject: [PATCH 1617/3753] (Maint) Handle NoMethodError on 2003 Commit b5a4c68f1 modified the operatingsystemrelease fact to return more meaningful Windows versions, e.g. 2003 instead of the kernelrelease fact value, 5.2.3790. However, on (some?) non-R2 versions of 2003, the othertypedescription method is not available on the WMI class Win32_OperatingSystem[1]. As a result, facter would output to stderr the following message: Could not retrieve operatingsystemrelease: unknown property or method: `othertypedescription' HRESULT error code:0x80020006 Unknown name. This commit simply rescues the NoMethodError and outputs 2003. --- lib/facter/operatingsystemrelease.rb | 6 +++++- spec/unit/operatingsystemrelease_spec.rb | 8 ++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index a563da76ef..3b9f7bc2d7 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -199,7 +199,11 @@ if os.producttype == 1 "XP" else - os.othertypedescription == "R2" ? "2003 R2" : "2003" + begin + os.othertypedescription == "R2" ? "2003 R2" : "2003" + rescue NoMethodError + "2003" + end end else Facter[:kernelrelease].value diff --git a/spec/unit/operatingsystemrelease_spec.rb b/spec/unit/operatingsystemrelease_spec.rb index fc281e0efe..5be0221e6a 100755 --- a/spec/unit/operatingsystemrelease_spec.rb +++ b/spec/unit/operatingsystemrelease_spec.rb @@ -223,6 +223,14 @@ end end + it "reports '2003' if the WMI method othertypedescription does not exist" do + os = mock('os', :version => '5.2.3790', :producttype => 2) + os.stubs(:othertypedescription).raises(NoMethodError) + + Facter::Util::WMI.expects(:execquery).returns([os]) + Facter.fact(:operatingsystemrelease).value.should == '2003' + end + context "Unknown Windows version" do before :each do Facter.fact(:kernelrelease).stubs(:value).returns("X.Y.ZZZZ") From 4eac9dfa86e90d9b401485dd0e898baf8d05a101 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 6 Feb 2014 10:41:13 -0800 Subject: [PATCH 1618/3753] (FACT-321) Remove interpreter arg for command execution --- lib/facter/core/execution.rb | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/lib/facter/core/execution.rb b/lib/facter/core/execution.rb index 7173730d27..f9880ffda2 100644 --- a/lib/facter/core/execution.rb +++ b/lib/facter/core/execution.rb @@ -166,14 +166,7 @@ def with_env(values) # @note Since Facter 1.5.8 this strips trailing newlines from the # returned value. If a fact will be used by versions of Facter older # than 1.5.8 then you should call chomp the returned string. - # - # @overload exec(code) - # @overload exec(code, interpreter = nil) - # @param [String] interpreter unused, only exists for backwards - # compatibility - # @deprecated - def exec(code, interpreter = nil) - Facter.warnonce "The interpreter parameter to 'exec' is deprecated and will be removed in a future version." if interpreter + def exec(code) ## Set LANG to force i18n to C for the duration of this exec; this ensures that any code that parses the ## output of the command can expect it to be in a consistent / predictable format / locale From e02fb64ce450ac64ceed8dfd6cec916bbbadd21e Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 6 Feb 2014 10:42:16 -0800 Subject: [PATCH 1619/3753] (FACT-321) Remove interpeter arg from resolution --- lib/facter/util/resolution.rb | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 6bb4842e06..8dee32ffe0 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -21,8 +21,6 @@ class Facter::Util::Resolution attr_accessor :code attr_writer :value - INTERPRETER = Facter::Util::Config.is_windows? ? "cmd.exe" : "/bin/sh" - extend Facter::Core::Execution class << self @@ -97,8 +95,7 @@ def set_options(options) # inside the block are rescued and printed to stderr. # # @api public - def setcode(string = nil, interp = nil, &block) - Facter.warnonce "The interpreter parameter to 'setcode' is deprecated and will be removed in a future version." if interp + def setcode(string = nil, &block) if string @code = string @interpreter = interp || INTERPRETER @@ -110,18 +107,6 @@ def setcode(string = nil, interp = nil, &block) end end - # @deprecated - def interpreter - Facter.warnonce "The 'Facter::Util::Resolution.interpreter' method is deprecated and will be removed in a future version." - @interpreter - end - - # @deprecated - def interpreter=(interp) - Facter.warnonce "The 'Facter::Util::Resolution.interpreter=' method is deprecated and will be removed in a future version." - @interpreter = interp - end - # (see value) # @deprecated def to_s From 3d11d4e42750f918136b1a5fa98dcecd38c0073e Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 6 Feb 2014 10:52:57 -0800 Subject: [PATCH 1620/3753] (FACT-321) Remove deprecated interpreter from resolution --- lib/facter/util/resolution.rb | 1 - spec/unit/util/resolution_spec.rb | 22 ---------------------- 2 files changed, 23 deletions(-) diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 8dee32ffe0..1607f1f2b1 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -98,7 +98,6 @@ def set_options(options) def setcode(string = nil, &block) if string @code = string - @interpreter = interp || INTERPRETER else unless block_given? raise ArgumentError, "You must pass either code or a block" diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index 4e4b3f9b1a..17e06886cc 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -24,34 +24,12 @@ Facter::Util::Resolution.new("yay").code.should be_nil end - it "should default to nil for interpreter" do - Facter.expects(:warnonce).with("The 'Facter::Util::Resolution.interpreter' method is deprecated and will be removed in a future version.") - Facter::Util::Resolution.new("yay").interpreter.should be_nil - end - describe "when setting the code" do before do Facter.stubs(:warnonce) @resolve = Facter::Util::Resolution.new("yay") end - it "should deprecate the interpreter argument to 'setcode'" do - Facter.expects(:warnonce).with("The interpreter parameter to 'setcode' is deprecated and will be removed in a future version.") - @resolve.setcode "foo", "bar" - @resolve.interpreter.should == "bar" - end - - it "should deprecate the interpreter= method" do - Facter.expects(:warnonce).with("The 'Facter::Util::Resolution.interpreter=' method is deprecated and will be removed in a future version.") - @resolve.interpreter = "baz" - @resolve.interpreter.should == "baz" - end - - it "should deprecate the interpreter method" do - Facter.expects(:warnonce).with("The 'Facter::Util::Resolution.interpreter' method is deprecated and will be removed in a future version.") - @resolve.interpreter - end - it "should set the code to any provided string" do @resolve.setcode "foo" @resolve.code.should == "foo" From 1d876748f3d619ec4ab1a9092327b6002face557 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 6 Feb 2014 10:54:38 -0800 Subject: [PATCH 1621/3753] (FACT-321) Remove deprecated 'memorytotal' fact --- lib/facter/memory.rb | 11 ----------- spec/unit/memory_spec.rb | 7 ------- 2 files changed, 18 deletions(-) diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb index a8b05906ef..b9f1d82540 100644 --- a/lib/facter/memory.rb +++ b/lib/facter/memory.rb @@ -160,14 +160,3 @@ "%.2f" % [(swapfree.to_f / 1024.0) / 1024.0] end end - -# http://projects.puppetlabs.com/issues/11436 -# -# Unifying naming for the amount of physical memory in a given host. -# This fact is DEPRECATED and will be removed in Facter 2.0 per -# http://projects.puppetlabs.com/issues/11466 -Facter.add("MemoryTotal") do - setcode do - Facter.value("memorysize") - end -end diff --git a/spec/unit/memory_spec.rb b/spec/unit/memory_spec.rb index 311a742d78..dd1eb76c67 100755 --- a/spec/unit/memory_spec.rb +++ b/spec/unit/memory_spec.rb @@ -534,13 +534,6 @@ Facter::Util::WMI.stubs(:execquery).returns([computer]) Facter.fact(:memorysize_mb).value.should == '3999.55' - Facter.fact(:MemoryTotal).value.should == '3.91 GB' end end - - it "should use the memorysize fact for the memorytotal fact" do - Facter.fact("memorysize").expects(:value).once.returns "16.00 GB" - Facter::Util::Resolution.expects(:exec).never - Facter.fact(:memorytotal).value.should == "16.00 GB" - end end From 99841e2c1aaf589aa017f3383b19d3e8f5ea33ad Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 6 Feb 2014 10:58:29 -0800 Subject: [PATCH 1622/3753] (FACT-321) Remove deprecated Resolution#to_s() --- lib/facter/util/resolution.rb | 6 ------ spec/unit/util/resolution_spec.rb | 6 ------ 2 files changed, 12 deletions(-) diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 1607f1f2b1..37423f4d9e 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -106,12 +106,6 @@ def setcode(string = nil, &block) end end - # (see value) - # @deprecated - def to_s - return self.value() - end - private def resolve_value diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index 17e06886cc..83d8cd7570 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -110,12 +110,6 @@ end end - it "should return its value when converted to a string" do - @resolve = Facter::Util::Resolution.new("yay") - @resolve.expects(:value).returns "myval" - @resolve.to_s.should == "myval" - end - describe "setting options" do subject(:resolution) { described_class.new(:foo) } From 0500739ade4a8eb2b511c7ec83290a17b9e7c06d Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 6 Feb 2014 11:00:09 -0800 Subject: [PATCH 1623/3753] (FACT-321) Remove ENV['DESTDIR'] and --test from install.rb --- install.rb | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/install.rb b/install.rb index 0ce2f9e323..f8cbdc4b78 100755 --- a/install.rb +++ b/install.rb @@ -160,10 +160,6 @@ def prepare_installation opts.on('--[no-]man', 'Presents the creation of man pages.', 'Default on.') do |onman| InstallOptions.man = onman end - opts.on('--[no-]tests', 'Prevents the execution of unit tests.', 'Default off.') do |ontest| - InstallOptions.tests = ontest - warn "The tests flag has never worked in Facter, is deprecated as of Nov 29, 2012, and will be removed in a future release of Facter." - end opts.on('--destdir[=OPTIONAL]', 'Installation prefix for all targets', 'Default essentially /') do |destdir| InstallOptions.destdir = destdir end @@ -233,18 +229,7 @@ def prepare_installation mandir = RbConfig::CONFIG['mandir'] end - # To be deprecated once people move over to using --destdir option - if (destdir = ENV['DESTDIR']) - warn "DESTDIR is deprecated. Use --destdir instead." - bindir = join(destdir, bindir) - mandir = join(destdir, mandir) - sitelibdir = join(destdir, sitelibdir) - - FileUtils.makedirs(bindir) - FileUtils.makedirs(mandir) - FileUtils.makedirs(sitelibdir) - # This is the new way forward - elsif (destdir = InstallOptions.destdir) + if (destdir = InstallOptions.destdir) bindir = join(destdir, bindir) mandir = join(destdir, mandir) sitelibdir = join(destdir, sitelibdir) From 6632a232168a44bbdb765945474dc83e0c64e247 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 6 Feb 2014 11:02:25 -0800 Subject: [PATCH 1624/3753] (FACT-321) Remove deprecated windows shell builtin execution --- lib/facter/core/execution.rb | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/lib/facter/core/execution.rb b/lib/facter/core/execution.rb index f9880ffda2..cdcd03fa5a 100644 --- a/lib/facter/core/execution.rb +++ b/lib/facter/core/execution.rb @@ -176,20 +176,13 @@ def exec(code) # if we can find the binary, we'll run the command with the expanded path to the binary code = expanded_code else - # if we cannot find the binary return nil on posix. On windows we'll still try to run the - # command in case it is a shell-builtin. In case it is not, windows will raise Errno::ENOENT - return nil unless Facter::Util::Config.is_windows? - return nil if absolute_path?(code) + return nil end out = nil begin out = %x{#{code}}.chomp - Facter.warnonce "Using Facter::Util::Execution.exec with a shell built-in is deprecated. Most built-ins can be replaced with native ruby commands. If you really have to run a built-in, pass \"cmd /c your_builtin\" as a command (command responsible for this message was \"#{code}\")" unless expanded_code - rescue Errno::ENOENT => detail - # command not found on Windows - return nil rescue => detail Facter.warn(detail) return nil From f9386528c2629944de11960fb18f6cfe668b50a1 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 10 Feb 2014 09:58:04 -0800 Subject: [PATCH 1625/3753] (FACT-321) Remove deprecated file extension expansion This commit removes the automatic expansion of Windows commands that have an absolute path but do not have a file extension. --- lib/facter/core/execution.rb | 11 ----------- spec/unit/core/execution_spec.rb | 10 ---------- 2 files changed, 21 deletions(-) diff --git a/lib/facter/core/execution.rb b/lib/facter/core/execution.rb index cdcd03fa5a..f500a9aeba 100644 --- a/lib/facter/core/execution.rb +++ b/lib/facter/core/execution.rb @@ -38,17 +38,6 @@ def search_paths def which(bin) if absolute_path?(bin) return bin if File.executable?(bin) - if Facter::Util::Config.is_windows? and File.extname(bin).empty? - exts = ENV['PATHEXT'] - exts = exts ? exts.split(File::PATH_SEPARATOR) : %w[.COM .EXE .BAT .CMD] - exts.each do |ext| - destext = bin + ext - if File.executable?(destext) - Facter.warnonce("Using Facter::Util::Execution.which with an absolute path like #{bin} but no fileextension is deprecated. Please add the correct extension (#{ext})") - return destext - end - end - end else search_paths.each do |dir| dest = File.join(dir, bin) diff --git a/spec/unit/core/execution_spec.rb b/spec/unit/core/execution_spec.rb index 9a6fc38d92..c90df4875b 100644 --- a/spec/unit/core/execution_spec.rb +++ b/spec/unit/core/execution_spec.rb @@ -127,16 +127,6 @@ def handy_method() described_class.which('\\\\remote\dir\foo.exe').should == '\\\\remote\dir\foo.exe' end - it "should return the binary with added extension if executable" do - ['.COM', '.BAT', '.CMD', '' ].each do |ext| - File.stubs(:executable?).with('C:\Windows\system32\netsh'+ext).returns false - end - File.expects(:executable?).with('C:\Windows\system32\netsh.EXE').returns true - - Facter.expects(:warnonce).with('Using Facter::Util::Execution.which with an absolute path like C:\\Windows\\system32\\netsh but no fileextension is deprecated. Please add the correct extension (.EXE)') - described_class.which('C:\Windows\system32\netsh').should == 'C:\Windows\system32\netsh.EXE' - end - it "should return nil if the binary is not executable" do File.expects(:executable?).with('C:\Tools\foo.exe').returns false File.expects(:executable?).with('\\\\remote\dir\foo.exe').returns false From 4961ef51dafbd423827f98504d003807ae46a9c9 Mon Sep 17 00:00:00 2001 From: Melissa Stone Date: Mon, 10 Feb 2014 10:07:38 -0800 Subject: [PATCH 1626/3753] (packaging) Update FACTERVERSION to 1.7.5 --- lib/facter/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/version.rb b/lib/facter/version.rb index 4f33d57311..5dfb817e4a 100644 --- a/lib/facter/version.rb +++ b/lib/facter/version.rb @@ -1,6 +1,6 @@ module Facter if not defined? FACTERVERSION then - FACTERVERSION = '1.7.5-rc2' + FACTERVERSION = '1.7.5' end ## From 48ea431e6ac76382a05c53eeaee1ed6097c798aa Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 11 Feb 2014 13:23:01 -0800 Subject: [PATCH 1627/3753] (maint) refactor solaris virtual fact to use timeouts The previous implementation of the solaris virtual fact used a resolution inside of a resolution because the original author (me) didn't know that the executed code already _was_ a resolution. This commit deduplicates the resolution and makes the entire resolution timeout like one would expect. --- lib/facter/virtual.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 4ef0ba0a06..416aff1c66 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -55,13 +55,12 @@ Facter.add("virtual") do confine :kernel => 'SunOS' has_weight 10 + self.timeout = 6 + setcode do next "zone" if Facter::Util::Virtual.zone? - resolver = Facter::Util::Resolution.new('prtdiag') - resolver.timeout = 6 - resolver.setcode('prtdiag') - output = resolver.value + output = Facter::Util::Resolution.exec('prtdiag') if output lines = output.split("\n") next "parallels" if lines.any? {|l| l =~ /Parallels/ } From c8df160008357e2ea7459a66e19fc6964ac7b041 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 11 Feb 2014 14:07:44 -0800 Subject: [PATCH 1628/3753] (FACT-273) Resolutions should be able to reference their facts The fact-resolution relationship is one to many, and there is information in the fact that we need in the resolution. This commit directly couples facts and resolutions for this purpose. This commit also adds a healthy refactoring of the fact and resolution tests to use modern rspec expectations and use imperative phrasing in specifications. --- lib/facter/core/aggregate.rb | 8 ++- lib/facter/util/fact.rb | 4 +- lib/facter/util/resolution.rb | 8 ++- spec/unit/core/aggregate_spec.rb | 4 +- spec/unit/util/collection_spec.rb | 2 +- spec/unit/util/fact_spec.rb | 102 ++++++++++++------------------ spec/unit/util/resolution_spec.rb | 60 +++++++++--------- 7 files changed, 91 insertions(+), 97 deletions(-) diff --git a/lib/facter/core/aggregate.rb b/lib/facter/core/aggregate.rb index 99f3f729b6..fa98dfff36 100644 --- a/lib/facter/core/aggregate.rb +++ b/lib/facter/core/aggregate.rb @@ -35,8 +35,14 @@ class Facter::Core::Aggregate # @see Facter::Core::Suitable attr_reader :confines - def initialize(name) + # @!attribute [r] fact + # @return [Facter::Util::Fact] + # @api private + attr_reader :fact + + def initialize(name, fact) @name = name + @fact = fact @confines = [] @chunks = {} diff --git a/lib/facter/util/fact.rb b/lib/facter/util/fact.rb index 663c155fbd..75f6580514 100644 --- a/lib/facter/util/fact.rb +++ b/lib/facter/util/fact.rb @@ -45,7 +45,7 @@ def initialize(name, options = {}) # @api private def add(value = nil, &block) begin - resolve = Facter::Util::Resolution.new(@name) + resolve = Facter::Util::Resolution.new(@name, self) resolve.instance_eval(&block) if block @resolves << resolve @@ -68,7 +68,7 @@ def define_resolution(resolve_name, &block) resolve = self.resolution(resolve_name) if resolve.nil? - resolve = Facter::Util::Resolution.new(resolve_name) + resolve = Facter::Util::Resolution.new(resolve_name, self) resolve.instance_eval(&block) if block @resolves << resolve else diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 37423f4d9e..af0c7a39eb 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -42,14 +42,20 @@ class << self # @api public attr_accessor :name + # @!attribute [r] fact + # @return [Facter::Util::Fact] + # @api private + attr_reader :fact + # Create a new resolution mechanism. # # @param name [String] The name of the resolution. # @return [void] # # @api private - def initialize(name) + def initialize(name, fact) @name = name + @fact = fact @confines = [] @value = nil @timeout = 0 diff --git a/spec/unit/core/aggregate_spec.rb b/spec/unit/core/aggregate_spec.rb index 6f85022321..875734bf72 100644 --- a/spec/unit/core/aggregate_spec.rb +++ b/spec/unit/core/aggregate_spec.rb @@ -3,8 +3,10 @@ describe Facter::Core::Aggregate do + let(:fact) { stub('stub_fact', :name => 'stub_fact') } + subject do - obj = described_class.new('aggregated') + obj = described_class.new('aggregated', fact) obj.aggregate { |chunks| chunks.values } obj end diff --git a/spec/unit/util/collection_spec.rb b/spec/unit/util/collection_spec.rb index 3a80613092..b11b46829a 100755 --- a/spec/unit/util/collection_spec.rb +++ b/spec/unit/util/collection_spec.rb @@ -34,7 +34,7 @@ fact = Facter::Util::Fact.new(:myname) Facter::Util::Fact.expects(:new).with(:myname, {:timeout => 'myval'}).returns fact - resolve = Facter::Util::Resolution.new(:myname) {} + resolve = Facter::Util::Resolution.new(:myname, fact) {} fact.expects(:add).returns resolve collection.add(:myname, :timeout => "myval") {} diff --git a/spec/unit/util/fact_spec.rb b/spec/unit/util/fact_spec.rb index 3e2f20eb53..e1b2ce0eb7 100755 --- a/spec/unit/util/fact_spec.rb +++ b/spec/unit/util/fact_spec.rb @@ -4,47 +4,36 @@ require 'facter/util/fact' describe Facter::Util::Fact do - it "should require a name" do - lambda { Facter::Util::Fact.new }.should raise_error(ArgumentError) + + subject(:fact) { Facter::Util::Fact.new("yay") } + + let(:resolution) { Facter::Util::Resolution.new("yay", fact) } + + it "requires a name" do + expect { Facter::Util::Fact.new }.to raise_error(ArgumentError) end - it "should always downcase the name and convert it to a symbol" do + it "downcases the name and converts it to a symbol" do Facter::Util::Fact.new("YayNess").name.should == :yayness end - it "should issue a deprecation warning for use of ldapname" do + it "issues a deprecation warning for use of ldapname" do Facter.expects(:warnonce).with("ldapname is deprecated and will be removed in a future version") Facter::Util::Fact.new("YayNess", :ldapname => "fooness") end - it "should have a method for adding resolution mechanisms" do - Facter::Util::Fact.new("yay").should respond_to(:add) - end - describe "when adding resolution mechanisms" do - before do - @fact = Facter::Util::Fact.new("yay") + it "can create a new resolution instance with a block" do + Facter::Util::Resolution.expects(:new).at_least_once.returns resolution - @resolution = Facter::Util::Resolution.new("yay") + fact.add { } end - it "should be to create a new resolution instance with a block" do - Facter::Util::Resolution.expects(:new).returns @resolution - - @fact.add { } - end - it "should instance_eval the passed block on the new resolution" do - @fact.add { + it "instance_evals the passed block on the new resolution" do + fact.add { setcode { "foo" } } - @fact.value.should == "foo" - end - - it "should re-sort the resolutions by weight, so the most restricted resolutions are first" do - @fact.add { has_weight 1; setcode { "1" } } - @fact.add { has_weight 2; setcode { "2" } } - @fact.add { has_weight 0; setcode { "0" } } - @fact.value.should == "2" + expect(fact.value).to eq "foo" end end @@ -67,7 +56,7 @@ it "creates a new resolution if no such resolution exists" do res = stub 'resolution', :name => 'named' - Facter::Util::Resolution.expects(:new).once.with('named').returns(res) + Facter::Util::Resolution.expects(:new).once.with('named', fact).returns(res) fact.define_resolution('named') @@ -76,7 +65,7 @@ it "returns existing resolutions by name" do res = stub 'resolution', :name => 'named' - Facter::Util::Resolution.expects(:new).once.with('named').returns(res) + Facter::Util::Resolution.expects(:new).once.with('named', fact).returns(res) fact.define_resolution('named') fact.define_resolution('named') @@ -85,57 +74,48 @@ end end - it "should be able to return a value" do + it "can able to return a value" do Facter::Util::Fact.new("yay").should respond_to(:value) end describe "when returning a value" do before do - @fact = Facter::Util::Fact.new("yay") + fact = Facter::Util::Fact.new("yay") end - it "should return nil if there are no resolutions" do + it "returns nil if there are no resolutions" do Facter::Util::Fact.new("yay").value.should be_nil end - it "should return the first value returned by a resolution" do - r1 = stub 'r1', :weight => 2, :value => nil, :suitable? => true - r2 = stub 'r2', :weight => 1, :value => "yay", :suitable? => true - r3 = stub 'r3', :weight => 0, :value => "foo", :suitable? => true - Facter::Util::Resolution.expects(:new).times(3).returns(r1).returns(r2).returns(r3) - @fact.add { } - @fact.add { } - @fact.add { } - - @fact.value.should == "yay" + it "prefers the highest weight resolution" do + fact.add { has_weight 1; setcode { "1" } } + fact.add { has_weight 2; setcode { "2" } } + fact.add { has_weight 0; setcode { "0" } } + expect(fact.value).to eq "2" end - it "should short-cut returning the value once one is found" do - r1 = stub 'r1', :weight => 2, :value => "foo", :suitable? => true - r2 = stub 'r2', :weight => 1, :suitable? => true # would fail if 'value' were asked for - Facter::Util::Resolution.expects(:new).times(2).returns(r1).returns(r2) - @fact.add { } - @fact.add { } - - @fact.value + it "returns the first value returned by a resolution" do + fact.add { has_weight 1; setcode { "1" } } + fact.add { has_weight 2; setcode { nil } } + fact.add { has_weight 0; setcode { "0" } } + expect(fact.value).to eq "1" end - it "should skip unsuitable resolutions" do - r1 = stub 'r1', :weight => 2, :suitable? => false # would fail if 'value' were asked for' - r2 = stub 'r2', :weight => 1, :value => "yay", :suitable? => true - Facter::Util::Resolution.expects(:new).times(2).returns(r1).returns(r2) - @fact.add { } - @fact.add { } + it "skips unsuitable resolutions" do + fact.add { has_weight 1; setcode { "1" } } + fact.add do + def suitable?; false; end + has_weight 2 + setcode { 2 } + end - @fact.value.should == "yay" + expect(fact.value).to eq "1" end - it "should return nil if the value is the empty string" do - r1 = stub 'r1', :suitable? => true, :value => "" - Facter::Util::Resolution.expects(:new).returns r1 - @fact.add { } + it "returns nil if the value is the empty string" do + fact.add { setcode { "" } } - @fact.value.should be_nil + expect(fact.value).to be_nil end end diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index 83d8cd7570..bdbe03295b 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -6,65 +6,67 @@ describe Facter::Util::Resolution do include FacterSpec::ConfigHelper + subject(:resolution) { described_class.new(:foo, stub_fact) } + + let(:stub_fact) { stub('fact', :name => :stubfact) } + it "requires a name" do expect { Facter::Util::Resolution.new }.to raise_error(ArgumentError) end + it "requires a fact" do + expect { Facter::Util::Resolution.new('yay') }.to raise_error(ArgumentError) + end + it "can return its name" do - Facter::Util::Resolution.new("yay").name.should == "yay" + expect(resolution.name).to eq :foo end it "should be able to set the value" do - resolve = Facter::Util::Resolution.new("yay") - resolve.value = "foo" - resolve.value.should == "foo" + resolution.value = "foo" + expect(resolution.value).to eq "foo" end it "should default to nil for code" do - Facter::Util::Resolution.new("yay").code.should be_nil + expect(resolution.code).to be_nil end describe "when setting the code" do before do Facter.stubs(:warnonce) - @resolve = Facter::Util::Resolution.new("yay") end it "should set the code to any provided string" do - @resolve.setcode "foo" - @resolve.code.should == "foo" + resolution.setcode "foo" + expect(resolution.code).to eq "foo" end it "should set the code to any provided block" do block = lambda { } - @resolve.setcode(&block) - @resolve.code.should equal(block) + resolution.setcode(&block) + resolution.code.should equal(block) end it "should prefer the string over a block" do - @resolve.setcode("foo") { } - @resolve.code.should == "foo" + resolution.setcode("foo") { } + expect(resolution.code).to eq "foo" end it "should fail if neither a string nor block has been provided" do - expect { @resolve.setcode }.to raise_error(ArgumentError) + expect { resolution.setcode }.to raise_error(ArgumentError) end end describe "when returning the value" do - before do - @resolve = Facter::Util::Resolution.new("yay") - end - it "should return any value that has been provided" do - @resolve.value = "foo" - @resolve.value.should == "foo" + resolution.value = "foo" + expect(resolution.value).to eq "foo" end describe "and setcode has not been called" do it "should return nil" do Facter::Util::Resolution.expects(:exec).with(nil, nil).never - @resolve.value.should be_nil + resolution.value.should be_nil end end @@ -75,10 +77,10 @@ end it "should return the result of executing the code" do - @resolve.setcode "/bin/foo" + resolution.setcode "/bin/foo" Facter::Util::Resolution.expects(:exec).once.with("/bin/foo").returns "yup" - @resolve.value.should == "yup" + expect(resolution.value).to eq "yup" end end @@ -88,31 +90,29 @@ end it "should return the result of executing the code" do - @resolve.setcode "/bin/foo" + resolution.setcode "/bin/foo" Facter::Util::Resolution.expects(:exec).once.with("/bin/foo").returns "yup" - @resolve.value.should == "yup" + expect(resolution.value).to eq "yup" end end end describe "and the code is a block" do it "should warn but not fail if the code fails" do - @resolve.setcode { raise "feh" } + resolution.setcode { raise "feh" } Facter.expects(:warn) - @resolve.value.should be_nil + resolution.value.should be_nil end it "should return the value returned by the block" do - @resolve.setcode { "yayness" } - @resolve.value.should == "yayness" + resolution.setcode { "yayness" } + expect(resolution.value).to eq "yayness" end end end describe "setting options" do - subject(:resolution) { described_class.new(:foo) } - it "can set the value" do resolution.set_options(:value => 'something') expect(resolution.value).to eq 'something' From 928282afbcb18f2216ed2047588ab8515f90197e Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 11 Feb 2014 15:26:22 -0800 Subject: [PATCH 1629/3753] (maint) Add more context when resolutions fail to evaluate --- lib/facter/core/resolvable.rb | 12 ++++++++---- spec/unit/core/resolvable_spec.rb | 10 +++++++--- spec/unit/operatingsystemrelease_spec.rb | 7 ++----- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/lib/facter/core/resolvable.rb b/lib/facter/core/resolvable.rb index 7228262f16..84459a56b4 100644 --- a/lib/facter/core/resolvable.rb +++ b/lib/facter/core/resolvable.rb @@ -66,7 +66,7 @@ def value Facter::Util::Normalization.normalize(result) rescue Timeout::Error => detail - Facter.warn "Timed out seeking value for #{self.name}" + Facter.warn "Timed out after #{limit} seconds while resolving #{qualified_name}" # This call avoids zombies -- basically, create a thread that will # dezombify all of the child processes that we're ignoring because @@ -74,10 +74,10 @@ def value Thread.new { Process.waitall } return nil rescue Facter::Util::Normalization::NormalizationError => e - Facter.warn "Fact resolution #{self.name} resolved to an invalid value: #{e.message}" + Facter.warn "Fact resolution #{qualified_name} resolved to an invalid value: #{e.message}" return nil rescue => details - Facter.warn "Could not retrieve #{self.name}: #{details.message}" + Facter.warn "Could not retrieve #{qualified_name}: #{details.message}" return nil end @@ -90,6 +90,10 @@ def with_timing finishtime = Time.now.to_f ms = (finishtime - starttime) * 1000 - Facter.show_time "#{self.name}: #{"%.2f" % ms}ms" + Facter.show_time "#{qualified_name}: #{"%.2f" % ms}ms" + end + + def qualified_name + "fact='#{@fact.name.to_s}', resolution='#{@name || ''}'" end end diff --git a/spec/unit/core/resolvable_spec.rb b/spec/unit/core/resolvable_spec.rb index e2ed98ed5d..1b877f082a 100644 --- a/spec/unit/core/resolvable_spec.rb +++ b/spec/unit/core/resolvable_spec.rb @@ -6,8 +6,10 @@ class ResolvableClass def initialize(name) @name = name + @fact = Facter::Util::Fact.new("stub fact") end attr_accessor :name, :resolve_value + attr_reader :fact include Facter::Core::Resolvable end @@ -36,7 +38,7 @@ def initialize(name) it "logs a warning if an exception was raised" do subject.expects(:resolve_value).raises RuntimeError, "kaboom!" - Facter.expects(:warn).with('Could not retrieve resolvable: kaboom!') + Facter.expects(:warn).with(regexp_matches(/Could not retrieve .*: kaboom!/)) expect(subject.value).to eq nil end end @@ -51,9 +53,11 @@ def initialize(name) end it "returns nil if the timeout was reached" do - Facter.expects(:warn).with("Timed out seeking value for resolvable") + Facter.expects(:warn).with(regexp_matches(/Timed out after 0\.1 seconds while resolving/)) Timeout.expects(:timeout).raises Timeout::Error + subject.timeout = 0.1 + expect(subject.value).to be_nil end @@ -62,7 +66,7 @@ def initialize(name) Thread.expects(:new).yields Process.expects(:waitall) - Facter.expects(:warn).with("Timed out seeking value for resolvable") + Facter.stubs(:warn) Timeout.expects(:timeout).raises Timeout::Error subject.value diff --git a/spec/unit/operatingsystemrelease_spec.rb b/spec/unit/operatingsystemrelease_spec.rb index e929f97a0c..781ee98ecd 100755 --- a/spec/unit/operatingsystemrelease_spec.rb +++ b/spec/unit/operatingsystemrelease_spec.rb @@ -127,13 +127,10 @@ end context "malformed /etc/release files" do - before :each do - Facter::Util::Resolution.any_instance.stubs(:warn) - end it "should fallback to the kernelrelease fact if /etc/release is empty" do Facter::Util::FileRead.stubs(:read).with('/etc/release'). raises EOFError - Facter.expects(:warn).with("Could not retrieve operatingsystemrelease: EOFError") + Facter.expects(:warn).with(regexp_matches(/Could not retrieve fact='operatingsystemrelease'.*EOFError/)) Facter.fact(:operatingsystemrelease).value. should == Facter.fact(:kernelrelease).value end @@ -141,7 +138,7 @@ it "should fallback to the kernelrelease fact if /etc/release is not present" do Facter::Util::FileRead.stubs(:read).with('/etc/release'). raises Errno::ENOENT - Facter.expects(:warn).with("Could not retrieve operatingsystemrelease: No such file or directory") + Facter.expects(:warn).with(regexp_matches(/Could not retrieve fact='operatingsystemrelease'.*No such file or directory/)) Facter.fact(:operatingsystemrelease).value. should == Facter.fact(:kernelrelease).value end From 7a581bbb73c2d0f7290047a0e19217085bd78a9b Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 30 Jan 2014 14:32:33 -0800 Subject: [PATCH 1630/3753] [FACT-239] Cleanly evaluate blocks in resolution context doing an `#instance_eval` on another object is a little bit evil and entangles the calling object and object being evaluated. This commit defines an `#evaluate` method on resolutions so that resolutions can define the behavior of evaluating a block. --- lib/facter/core/aggregate.rb | 4 ++++ lib/facter/util/fact.rb | 6 +++--- lib/facter/util/resolution.rb | 24 ++++++++++++++++++++++++ spec/unit/core/aggregate_spec.rb | 7 +++++++ spec/unit/util/fact_spec.rb | 11 ----------- spec/unit/util/resolution_spec.rb | 16 ++++++++++++++++ 6 files changed, 54 insertions(+), 14 deletions(-) diff --git a/lib/facter/core/aggregate.rb b/lib/facter/core/aggregate.rb index fa98dfff36..5637f947f0 100644 --- a/lib/facter/core/aggregate.rb +++ b/lib/facter/core/aggregate.rb @@ -69,6 +69,10 @@ def set_options(options) end end + def evaluate(&block) + instance_eval(&block) + end + # Define a new chunk for the given aggregate # # @api public diff --git a/lib/facter/util/fact.rb b/lib/facter/util/fact.rb index 75f6580514..b4a3e5861c 100644 --- a/lib/facter/util/fact.rb +++ b/lib/facter/util/fact.rb @@ -47,7 +47,7 @@ def add(value = nil, &block) begin resolve = Facter::Util::Resolution.new(@name, self) - resolve.instance_eval(&block) if block + resolve.evaluate(&block) if block @resolves << resolve resolve @@ -69,10 +69,10 @@ def define_resolution(resolve_name, &block) if resolve.nil? resolve = Facter::Util::Resolution.new(resolve_name, self) - resolve.instance_eval(&block) if block + resolve.evaluate(&block) if block @resolves << resolve else - resolve.instance_eval(&block) if block + resolve.evaluate(&block) if block end rescue => e diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index af0c7a39eb..007a4b88d9 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -62,6 +62,30 @@ def initialize(name, fact) @weight = nil end + # Evaluate the given block in the context of this resolution. If a block has + # already been evaluated emit a warning to that effect. + # + # @return [void] + def evaluate(&block) + if @last_evaluated + msg = "Already evaluated #{@name}" + msg << " at #{@last_evaluated}" if msg.is_a? String + msg << ", reevaluating anyways" + Facter.warn msg + end + + instance_eval(&block) + + # Ruby 1.9+ provides the source location of procs which can provide useful + # debugging information if a resolution is being evaluated twice. Since 1.8 + # doesn't support this we opportunistically provide this information. + if block.respond_to? :source_location + @last_evaluated = block.source_location.join(':') + else + @last_evaluated = true + end + end + def set_options(options) if options[:name] @name = options.delete(:name) diff --git a/spec/unit/core/aggregate_spec.rb b/spec/unit/core/aggregate_spec.rb index 875734bf72..fd4bcb67bf 100644 --- a/spec/unit/core/aggregate_spec.rb +++ b/spec/unit/core/aggregate_spec.rb @@ -140,4 +140,11 @@ subject.value end end + + describe "evaluating" do + it "evaluates the block in the context of the aggregate" do + subject.expects(:has_weight).with(5) + subject.evaluate { has_weight(5) } + end + end end diff --git a/spec/unit/util/fact_spec.rb b/spec/unit/util/fact_spec.rb index e1b2ce0eb7..46624a34b7 100755 --- a/spec/unit/util/fact_spec.rb +++ b/spec/unit/util/fact_spec.rb @@ -23,12 +23,6 @@ end describe "when adding resolution mechanisms" do - it "can create a new resolution instance with a block" do - Facter::Util::Resolution.expects(:new).at_least_once.returns resolution - - fact.add { } - end - it "instance_evals the passed block on the new resolution" do fact.add { setcode { "foo" } @@ -52,7 +46,6 @@ end describe "adding resolution mechanisms by name" do - subject(:fact) { described_class.new('yay') } it "creates a new resolution if no such resolution exists" do res = stub 'resolution', :name => 'named' @@ -74,10 +67,6 @@ end end - it "can able to return a value" do - Facter::Util::Fact.new("yay").should respond_to(:value) - end - describe "when returning a value" do before do fact = Facter::Util::Fact.new("yay") diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index bdbe03295b..2d7c119f5c 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -134,4 +134,20 @@ end.to raise_error(ArgumentError, /Invalid resolution options.*foo/) end end + + describe "evaluating" do + it "evaluates the block in the context of the given resolution" do + subject.expects(:has_weight).with(5) + subject.evaluate { has_weight(5) } + end + + it "raises a warning if the resolution is evaluated twice" do + Facter.expects(:warn).with do |msg| + expect(msg).to match /Already evaluated foo at.*reevaluating anyways/ + end + + subject.evaluate { } + subject.evaluate { } + end + end end From e86ff8dc98f2a782868974439f42894c08067d33 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Fri, 31 Jan 2014 12:18:37 -0800 Subject: [PATCH 1631/3753] (FACT-239) Delegate Fact#add to Fact#define_resolution --- lib/facter/util/fact.rb | 2 +- spec/unit/util/collection_spec.rb | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/facter/util/fact.rb b/lib/facter/util/fact.rb index b4a3e5861c..baec577757 100644 --- a/lib/facter/util/fact.rb +++ b/lib/facter/util/fact.rb @@ -76,7 +76,7 @@ def define_resolution(resolve_name, &block) end rescue => e - Facter.warn "Unable to add resolve #{resolve_name} for fact #{@name}: #{e}" + Facter.warn "Unable to add resolve #{resolve_name.inspect} for fact #{@name}: #{e}" end # Retrieve an existing resolution by name diff --git a/spec/unit/util/collection_spec.rb b/spec/unit/util/collection_spec.rb index b11b46829a..3b975d193a 100755 --- a/spec/unit/util/collection_spec.rb +++ b/spec/unit/util/collection_spec.rb @@ -56,14 +56,13 @@ end it "should discard resolutions that throw an exception when added" do - Facter.expects(:warn).with("Unable to add resolve for yay: ") - lambda { + Facter.expects(:warn).with(regexp_matches(/Unable to add resolve .* kaboom!/)) + expect { collection.add('yay') do - raise - setcode { 'yay' } + raise "kaboom!" end - }.should_not raise_error - collection.value('yay').should be_nil + }.to_not raise_error + expect(collection.value('yay')).to be_nil end end end From b53427303bba400bf27270eeefb818efb1897bba Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Fri, 31 Jan 2014 12:42:15 -0800 Subject: [PATCH 1632/3753] (FACT-239) Move resolution interaction to facts Facter::Util::Collection never directly interacts with resolutions, so it doesn't make a lot of sense for the collection to be setting options on resolution instances. This commit pushes the last of that entanglement into Util::Fact, which does interact with resolutions. --- lib/facter/util/collection.rb | 11 +---------- lib/facter/util/fact.rb | 26 +++++++++++--------------- spec/unit/util/collection_spec.rb | 9 ++------- spec/unit/util/fact_spec.rb | 4 ++-- 4 files changed, 16 insertions(+), 34 deletions(-) diff --git a/lib/facter/util/collection.rb b/lib/facter/util/collection.rb index abf83b1e91..aac876f6db 100644 --- a/lib/facter/util/collection.rb +++ b/lib/facter/util/collection.rb @@ -47,16 +47,7 @@ def define_fact(name, options = {}, &block) def add(name, options = {}, &block) fact = create_or_return_fact(name, options) - if block_given? - resolve = fact.add(&block) - else - resolve = fact.add - end - - # Set any resolve-appropriate options - if resolve - resolve.set_options(options) - end + fact.add(options, &block) return fact end diff --git a/lib/facter/util/fact.rb b/lib/facter/util/fact.rb index baec577757..38d0418dee 100644 --- a/lib/facter/util/fact.rb +++ b/lib/facter/util/fact.rb @@ -40,38 +40,34 @@ def initialize(name, options = {}) # block, which will then be evaluated in the context of the new # resolution. # - # @return [void] + # @param options [Hash] A hash of options to set on the resolution + # + # @return [Facter::Util::Resolution] # # @api private - def add(value = nil, &block) - begin - resolve = Facter::Util::Resolution.new(@name, self) - - resolve.evaluate(&block) if block - @resolves << resolve - - resolve - rescue => e - Facter.warn "Unable to add resolve for #{@name}: #{e}" - nil - end + def add(options = {}, &block) + define_resolution(nil, options, &block) end # Define a new named resolution or return an existing resolution with # the given name. # # @param resolve_name [String] The name of the resolve to define or look up - # @return [void] + # @param options [Hash] A hash of options to set on the resolution + # @return [Facter::Util::Resolution] # # @api public - def define_resolution(resolve_name, &block) + def define_resolution(resolve_name, options = {}, &block) resolve = self.resolution(resolve_name) if resolve.nil? resolve = Facter::Util::Resolution.new(resolve_name, self) + resolve.set_options(options) unless options.empty? + resolve.evaluate(&block) if block @resolves << resolve else + resolve.set_options(options) unless options.empty? resolve.evaluate(&block) if block end diff --git a/spec/unit/util/collection_spec.rb b/spec/unit/util/collection_spec.rb index 3b975d193a..78591f4a6f 100755 --- a/spec/unit/util/collection_spec.rb +++ b/spec/unit/util/collection_spec.rb @@ -30,20 +30,15 @@ collection.add(:myname, :timeout => 1) { } end - it "should set appropriate options on the resolution instance" do + it "passes resolution specific options to the fact" do fact = Facter::Util::Fact.new(:myname) Facter::Util::Fact.expects(:new).with(:myname, {:timeout => 'myval'}).returns fact - resolve = Facter::Util::Resolution.new(:myname, fact) {} - fact.expects(:add).returns resolve + fact.expects(:add).with({:timeout => 'myval'}) collection.add(:myname, :timeout => "myval") {} end - it "should fail if invalid options are provided" do - lambda { collection.add(:myname, :foo => :bar) }.should raise_error(ArgumentError) - end - describe "and a block is provided" do it "should use the block to add a resolution to the fact" do fact = mock 'fact' diff --git a/spec/unit/util/fact_spec.rb b/spec/unit/util/fact_spec.rb index 46624a34b7..8c50e5b3f0 100755 --- a/spec/unit/util/fact_spec.rb +++ b/spec/unit/util/fact_spec.rb @@ -47,8 +47,9 @@ describe "adding resolution mechanisms by name" do + let(:res) { stub 'resolution', :name => 'named', :set_options => nil } + it "creates a new resolution if no such resolution exists" do - res = stub 'resolution', :name => 'named' Facter::Util::Resolution.expects(:new).once.with('named', fact).returns(res) fact.define_resolution('named') @@ -57,7 +58,6 @@ end it "returns existing resolutions by name" do - res = stub 'resolution', :name => 'named' Facter::Util::Resolution.expects(:new).once.with('named', fact).returns(res) fact.define_resolution('named') From b93d261f7d9e8c1c3b9a4da9d0fcf4f0392e95fc Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Fri, 31 Jan 2014 14:23:53 -0800 Subject: [PATCH 1633/3753] (FACT-239) Expose resolution type controls This commit exposes the ability to create different types of resolutions. When a resolution is being created the `:type` option can be specified, creating either a simple resolution (using `:type => :simple`) or aggregate (using `:type => :aggregate`). If no type is given the type defaults to simple. If a resolution is redefined the requested type is compared against the currently existing resolution. If the types are mismatched an error will be raised since there's no particularly good way to resolve this discrepancy. --- lib/facter/core/aggregate.rb | 4 ++ lib/facter/util/fact.rb | 47 ++++++++++++++++------ spec/unit/util/fact_spec.rb | 77 ++++++++++++++++++++---------------- 3 files changed, 81 insertions(+), 47 deletions(-) diff --git a/lib/facter/core/aggregate.rb b/lib/facter/core/aggregate.rb index 5637f947f0..948fad866a 100644 --- a/lib/facter/core/aggregate.rb +++ b/lib/facter/core/aggregate.rb @@ -144,6 +144,10 @@ def aggregate(&block) end end + def resolution_type + :aggregate + end + private # Evaluate the results of this aggregate. diff --git a/lib/facter/util/fact.rb b/lib/facter/util/fact.rb index 38d0418dee..587bd33dec 100644 --- a/lib/facter/util/fact.rb +++ b/lib/facter/util/fact.rb @@ -1,5 +1,6 @@ require 'facter' require 'facter/util/resolution' +require 'facter/core/aggregate' # This class represents a fact. Each fact has a name and multiple # {Facter::Util::Resolution resolutions}. @@ -52,27 +53,23 @@ def add(options = {}, &block) # Define a new named resolution or return an existing resolution with # the given name. # - # @param resolve_name [String] The name of the resolve to define or look up + # @param resolution_name [String] The name of the resolve to define or look up # @param options [Hash] A hash of options to set on the resolution # @return [Facter::Util::Resolution] # # @api public - def define_resolution(resolve_name, options = {}, &block) - resolve = self.resolution(resolve_name) + def define_resolution(resolution_name, options = {}, &block) - if resolve.nil? - resolve = Facter::Util::Resolution.new(resolve_name, self) - resolve.set_options(options) unless options.empty? + resolution_type = options.delete(:type) || :simple - resolve.evaluate(&block) if block - @resolves << resolve - else - resolve.set_options(options) unless options.empty? - resolve.evaluate(&block) if block - end + resolve = create_or_return_resolution(resolution_name, resolution_type) + + resolve.set_options(options) unless options.empty? + resolve.evaluate(&block) if block + resolve rescue => e - Facter.warn "Unable to add resolve #{resolve_name.inspect} for fact #{@name}: #{e}" + Facter.warn "Unable to add resolve #{resolution_name.inspect} for fact #{@name}: #{e}" end # Retrieve an existing resolution by name @@ -184,4 +181,28 @@ def announce_when_no_value_found(value) def normalize_value(value) value == "" ? nil : value end + + def create_or_return_resolution(resolution_name, resolution_type) + resolve = self.resolution(resolution_name) + + if resolve + if resolution_type != resolve.resolution_type + raise ArgumentError, "Cannot return resolution #{resolution_name} with type" + + " #{resolution_type}; already defined as #{resolve.resolution_type}" + end + else + case resolution_type + when :simple + resolve = Facter::Util::Resolution.new(resolution_name, self) + when :aggregate + resolve = Facter::Core::Aggregate.new(resolution_name, self) + else + raise ArgumentError, "Expected resolution type to be one of (:simple, :aggregate) but was #{resolution_type}" + end + + @resolves << resolve + end + + resolve + end end diff --git a/spec/unit/util/fact_spec.rb b/spec/unit/util/fact_spec.rb index 8c50e5b3f0..f3aebef6cb 100755 --- a/spec/unit/util/fact_spec.rb +++ b/spec/unit/util/fact_spec.rb @@ -13,8 +13,8 @@ expect { Facter::Util::Fact.new }.to raise_error(ArgumentError) end - it "downcases the name and converts it to a symbol" do - Facter::Util::Fact.new("YayNess").name.should == :yayness + it "downcases and converts the name to a symbol" do + expect(Facter::Util::Fact.new("YayNess").name).to eq :yayness end it "issues a deprecation warning for use of ldapname" do @@ -22,12 +22,10 @@ Facter::Util::Fact.new("YayNess", :ldapname => "fooness") end - describe "when adding resolution mechanisms" do - it "instance_evals the passed block on the new resolution" do - fact.add { - setcode { "foo" } - } - expect(fact.value).to eq "foo" + describe "when adding resolution mechanisms using #add" do + it "delegates to #define_resolution with an anonymous resolution" do + subject.expects(:define_resolution).with(nil, {}) + subject.add end end @@ -47,7 +45,12 @@ describe "adding resolution mechanisms by name" do - let(:res) { stub 'resolution', :name => 'named', :set_options => nil } + let(:res) do + stub 'resolution', + :name => 'named', + :set_options => nil, + :resolution_type => :simple + end it "creates a new resolution if no such resolution exists" do Facter::Util::Resolution.expects(:new).once.with('named', fact).returns(res) @@ -57,6 +60,29 @@ expect(fact.resolution('named')).to eq res end + it "creates a simple resolution when the type is nil" do + fact.define_resolution('named') + expect(fact.resolution('named')).to be_a_kind_of Facter::Util::Resolution + end + + it "creates a simple resolution when the type is :simple" do + fact.define_resolution('named', :type => :simple) + expect(fact.resolution('named')).to be_a_kind_of Facter::Util::Resolution + end + + it "creates an aggregate resolution when the type is :aggregate" do + fact.define_resolution('named', :type => :aggregate) + expect(fact.resolution('named')).to be_a_kind_of Facter::Core::Aggregate + end + + it "raises an error if there is an existing resolution with a different type" do + pending "We need to stop rescuing all errors when instantiating resolutions" + fact.define_resolution('named') + expect { + fact.define_resolution('named', :type => :aggregate) + }.to raise_error(ArgumentError, /Cannot return resolution.*already defined as simple/) + end + it "returns existing resolutions by name" do Facter::Util::Resolution.expects(:new).once.with('named', fact).returns(res) @@ -68,10 +94,6 @@ end describe "when returning a value" do - before do - fact = Facter::Util::Fact.new("yay") - end - it "returns nil if there are no resolutions" do Facter::Util::Fact.new("yay").value.should be_nil end @@ -112,28 +134,15 @@ def suitable?; false; end subject do Facter::Util::Fact.new(:foo) end - context 'basic facts using setcode' do - it "flushes the cached value when invoked" do - system = mock('some_system_call') - system.expects(:data).twice.returns(100,200) - subject.add { setcode { system.data } } - 5.times { subject.value.should == 100 } - subject.flush - subject.value.should == 200 - end - end - context 'facts using setcode and on_flush' do - it 'invokes the block passed to on_flush' do - model = { :data => "Hello World" } - subject.add do - on_flush { model[:data] = "FLUSHED!" } - setcode { model[:data] } - end - subject.value.should == "Hello World" - subject.flush - subject.value.should == "FLUSHED!" - end + it "invokes #flush on all resolutions" do + simple = subject.add(:type => :simple) + simple.expects(:flush) + + aggregate = subject.add(:type => :aggregate) + aggregate.expects(:flush) + + subject.flush end end end From 0bb22462a0f3376189a3e8ac2a0419942f8ae80f Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Fri, 31 Jan 2014 15:41:18 -0800 Subject: [PATCH 1634/3753] (maint) Iteratively deep merge values for default aggregate Without this commit the aggregate deep merge behavior would try to pass all chunk values to the deep merge method at once, which was pretty broken. This commit uses inject to iterately merge all values. --- lib/facter/core/aggregate.rb | 4 +++- spec/unit/core/aggregate_spec.rb | 10 +++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/facter/core/aggregate.rb b/lib/facter/core/aggregate.rb index 948fad866a..619bf1c92e 100644 --- a/lib/facter/core/aggregate.rb +++ b/lib/facter/core/aggregate.rb @@ -192,7 +192,9 @@ def aggregate_results(results) end def default_aggregate(results) - Facter::Util::Values.deep_merge(results.keys) + results.values.inject do |result, current| + Facter::Util::Values.deep_merge(result, current) + end rescue Facter::Util::Values::DeepMergeError => e raise ArgumentError, "No aggregate block specified and could not deep merge" + " all chunks, either specify an aggregate block or ensure that all chunks" + diff --git a/spec/unit/core/aggregate_spec.rb b/spec/unit/core/aggregate_spec.rb index fd4bcb67bf..f22d15085e 100644 --- a/spec/unit/core/aggregate_spec.rb +++ b/spec/unit/core/aggregate_spec.rb @@ -5,11 +5,7 @@ let(:fact) { stub('stub_fact', :name => 'stub_fact') } - subject do - obj = described_class.new('aggregated', fact) - obj.aggregate { |chunks| chunks.values } - obj - end + subject { obj = described_class.new('aggregated', fact) } it "can be resolved" do expect(subject).to be_a_kind_of Facter::Core::Resolvable @@ -78,9 +74,9 @@ end it "passes all requested chunk results to the depending chunk" do - subject.chunk(:first) { 'foo' } + subject.chunk(:first) { ['foo'] } subject.chunk(:second, :require => [:first]) do |first| - "#{first} bar" + [first[0] + ' bar'] end output = subject.value From 71f1d66906c9fc0ea6261416a63770dcc022580e Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 3 Feb 2014 14:54:23 -0800 Subject: [PATCH 1635/3753] (maint) Clean up facter specs This reorganizes tests against the top level collection, deletes some tests that have absolutely nothing to do with the Facter object, and removes tests that check for a method since other tests will fail if those methods are removed. --- spec/unit/facter_spec.rb | 138 +++++++++++++-------------------------- 1 file changed, 47 insertions(+), 91 deletions(-) diff --git a/spec/unit/facter_spec.rb b/spec/unit/facter_spec.rb index 1b87ef3634..9cf03f8193 100755 --- a/spec/unit/facter_spec.rb +++ b/spec/unit/facter_spec.rb @@ -3,62 +3,60 @@ require 'spec_helper' describe Facter do - it "should have a method for returning its collection" do - Facter.should respond_to(:collection) - end - - it "should cache the collection" do + it "caches the collection" do Facter.collection.should equal(Facter.collection) end - it "should delegate the :flush method to the collection" do - Facter.collection.expects(:flush) - Facter.flush - end + describe "methods on the collection" do + it "delegates the :flush method to the collection" do + Facter.collection.expects(:flush) + Facter.flush + end - it "should delegate the :fact method to the collection" do - Facter.collection.expects(:fact).with("afact") - Facter.fact("afact") - end + it "delegates the :fact method to the collection" do + Facter.collection.expects(:fact).with("afact") + Facter.fact("afact") + end - it "should delegate the :list method to the collection" do - Facter.collection.expects(:list) - Facter.list - end + it "delegates the :list method to the collection" do + Facter.collection.expects(:list) + Facter.list + end - it "should load all facts when listing" do - Facter.collection.expects(:load_all) - Facter.list - end + it "loads all facts when listing" do + Facter.collection.expects(:load_all) + Facter.list + end - it "should delegate the :to_hash method to the collection" do - Facter.collection.expects(:to_hash) - Facter.to_hash - end + it "delegates the :to_hash method to the collection" do + Facter.collection.expects(:to_hash) + Facter.to_hash + end - it "should load all facts when calling :to_hash" do - Facter.collection.expects(:load_all) - Facter.collection.stubs(:to_hash) - Facter.to_hash - end + it "loads all facts when calling :to_hash" do + Facter.collection.expects(:load_all) + Facter.collection.stubs(:to_hash) + Facter.to_hash + end - it "should delegate the :value method to the collection" do - Facter.collection.expects(:value).with("myvaluefact") - Facter.value("myvaluefact") - end + it "delegates the :value method to the collection" do + Facter.collection.expects(:value).with("myvaluefact") + Facter.value("myvaluefact") + end - it "should delegate the :each method to the collection" do - Facter.collection.expects(:each) - Facter.each - end + it "delegates the :each method to the collection" do + Facter.collection.expects(:each) + Facter.each + end - it "should load all facts when calling :each" do - Facter.collection.expects(:load_all) - Facter.collection.stubs(:each) - Facter.each + it "loads all facts when calling :each" do + Facter.collection.expects(:load_all) + Facter.collection.stubs(:each) + Facter.each + end end - it "should yield to the block when using :each" do + it "yields to the block when using :each" do Facter.collection.stubs(:load_all) Facter.collection.stubs(:each).yields "foo" result = [] @@ -66,75 +64,33 @@ result.should == %w{foo} end - describe "when provided code as a string" do - it "should execute the code in the shell" do - test_command = Facter::Util::Config.is_windows? ? 'cmd.exe /c echo yup' : 'echo yup' - Facter.add("shell_testing") do - setcode test_command - end - - Facter["shell_testing"].value.should == "yup" - end - end - - describe "when passed code as a block" do - it "should execute the provided block" do - Facter.add("block_testing") { setcode { "foo" } } - - Facter["block_testing"].value.should == "foo" - end - end - - # #33 Make sure we only get one mac address - it "should only return one mac address" do - if macaddress = Facter.value(:macaddress) - macaddress.should_not be_include(" ") - end - end - - it "should have a method for registering directories to search" do - Facter.should respond_to(:search) - end - - it "should have a method for returning the registered search directories" do - Facter.should respond_to(:search_path) - end - - it "should have a method for registering directories to search for external facts" do - Facter.should respond_to(:search_external) - end - - it "should have a method for returning the registered search directories for external facts" do - Facter.should respond_to(:search_external_path) - end - describe "when registering directories to search" do after { Facter.reset_search_path! } - it "should allow registration of a directory" do + it "allows registration of a directory" do Facter.search "/my/dir" end - it "should allow registration of multiple directories" do + it "allows registration of multiple directories" do Facter.search "/my/dir", "/other/dir" end - it "should return all registered directories when asked" do + it "returns all registered directories when asked" do Facter.search "/my/dir", "/other/dir" Facter.search_path.should == %w{/my/dir /other/dir} end end describe "when registering directories to search for external facts" do - it "should allow registration of a directory" do + it "allows registration of a directory" do Facter.search_external ["/my/dir"] end - it "should allow registration of multiple directories" do + it "allows registration of multiple directories" do Facter.search_external ["/my/dir", "/other/dir"] end - it "should return all registered directories when asked" do + it "returns all registered directories when asked" do Facter.search_external ["/my/dir", "/other/dir"] Facter.search_external_path.should include("/my/dir", "/other/dir") end From c5d4654a2846080143a2c0bf0886b9567a36ac51 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 3 Feb 2014 10:04:28 -0800 Subject: [PATCH 1636/3753] (FACT-239) Implement Facter.define_fact This allows calling classes to create or reopen a fact by itself, without interacting with resolutions. --- lib/facter.rb | 13 +++++++++++++ spec/unit/facter_spec.rb | 10 ++++++++++ 2 files changed, 23 insertions(+) diff --git a/lib/facter.rb b/lib/facter.rb index e85c99b1a2..4e102a872f 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -128,6 +128,19 @@ def self.to_hash collection.to_hash end + # Define a new fact or extend an existing fact. + # + # @param name [Symbol] The name of the fact to define + # @param options [Hash] A hash of options to set on the fact + # + # @return [Facter::Util::Fact] The fact that was defined + # + # @api public + # @see {Facter::Util::Collection#define_fact} + def self.define_fact(name, options = {}, &block) + collection.define_fact(name, options, &block) + end + # Adds a {Facter::Util::Resolution resolution} mechanism for a named # fact. This does not distinguish between adding a new fact and adding # a new way to resolve a fact. diff --git a/spec/unit/facter_spec.rb b/spec/unit/facter_spec.rb index 9cf03f8193..62870f0d91 100755 --- a/spec/unit/facter_spec.rb +++ b/spec/unit/facter_spec.rb @@ -49,6 +49,16 @@ Facter.each end + it "delegates the :add method to the collection" do + Facter.collection.expects(:add).with("factname", {}) + Facter.add("factname") + end + + it "delegates the :define_fact method to the collection" do + Facter.collection.expects(:define_fact).with("factname", {}) + Facter.define_fact("factname") + end + it "loads all facts when calling :each" do Facter.collection.expects(:load_all) Facter.collection.stubs(:each) From 71f650f364b908ad624515b1efe9d6c873ae56dc Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 11 Feb 2014 17:56:36 -0800 Subject: [PATCH 1637/3753] (maint) Remove nonfunctional tests Passing a block to #expects() ignores the passed block which means that this code was catching the wrong thing and we don't want to test this anyways. --- spec/unit/core/aggregate_spec.rb | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/spec/unit/core/aggregate_spec.rb b/spec/unit/core/aggregate_spec.rb index f22d15085e..64b4450dfb 100644 --- a/spec/unit/core/aggregate_spec.rb +++ b/spec/unit/core/aggregate_spec.rb @@ -98,17 +98,6 @@ end end - describe "evaluating chunks" do - it "emits a warning and returns nil when a chunk raises an error" do - Facter.expects(:warn) do |msg| - expect(msg).to match /Could not run chunk boom.*kaboom!/ - end - - subject.chunk(:boom) { raise 'kaboom!' } - subject.value - end - end - describe "aggregating chunks" do it "passes all chunk results as a hash to the aggregate block" do subject.chunk(:data) { 'data chunk' } @@ -125,16 +114,6 @@ subject.aggregate { "who needs chunks anyways" } expect(subject.value).to eq "who needs chunks anyways" end - - it "generates a warning and returns if the aggregate raises an error" do - subject.aggregate { raise 'kaboom!' } - - Facter.expects(:warn) do |msg| - expect(msg).to match /Could not aggregate chunks for boom.*kaboom!/ - end - - subject.value - end end describe "evaluating" do From 35aca22d76a4c4f36031261f978584739311cecb Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Wed, 12 Feb 2014 09:45:26 -0800 Subject: [PATCH 1638/3753] (maint) Explicitly require tsort The DirectedGraph class was trying to use the TSort library but that library was not explicitly required, which is surprising that this ever worked. This commit adds the relevant require to sort this out. --- lib/facter/core/directed_graph.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/facter/core/directed_graph.rb b/lib/facter/core/directed_graph.rb index bbdd5db9fa..6edcabad29 100644 --- a/lib/facter/core/directed_graph.rb +++ b/lib/facter/core/directed_graph.rb @@ -1,4 +1,5 @@ require 'set' +require 'tsort' module Facter module Core From 1edaea5ff6eea4c14c24290b19c1ba4cbaed2538 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 13 Feb 2014 11:27:46 -0800 Subject: [PATCH 1639/3753] (maint) Update facts to use Facter::Core::Execution When Facter::Core::Execution was extracted, aliases were added from Facter::Util::Resolution to Facter::Core::Execution for compatibility. This commit updates all facts using execution methods to use the new code directly instead of relying on an alias. --- lib/facter/domain.rb | 4 +- lib/facter/filesystems.rb | 2 +- lib/facter/hardwaremodel.rb | 2 +- lib/facter/hostname.rb | 4 +- lib/facter/ipaddress.rb | 2 +- lib/facter/kernel.rb | 2 +- lib/facter/kernelrelease.rb | 2 +- lib/facter/ldom.rb | 2 +- lib/facter/lsbdistcodename.rb | 2 +- lib/facter/lsbdistdescription.rb | 2 +- lib/facter/lsbdistid.rb | 2 +- lib/facter/lsbdistrelease.rb | 2 +- lib/facter/lsbrelease.rb | 2 +- lib/facter/macaddress.rb | 2 +- lib/facter/memory.rb | 16 ++--- lib/facter/operatingsystem.rb | 2 +- lib/facter/operatingsystemrelease.rb | 2 +- lib/facter/physicalprocessorcount.rb | 8 +-- lib/facter/processor.rb | 14 ++--- lib/facter/selinux.rb | 8 +-- lib/facter/util/architecture.rb | 4 +- lib/facter/util/ec2.rb | 2 +- lib/facter/util/file_read.rb | 2 +- lib/facter/util/ip.rb | 6 +- lib/facter/util/macosx.rb | 4 +- lib/facter/util/manufacturer.rb | 6 +- lib/facter/util/memory.rb | 26 ++++---- lib/facter/util/parser.rb | 4 +- lib/facter/util/processor.rb | 30 ++++----- lib/facter/util/resolution.rb | 2 +- lib/facter/util/solaris_zones.rb | 2 +- lib/facter/util/uptime.rb | 6 +- lib/facter/util/virtual.rb | 20 +++--- lib/facter/util/xendomains.rb | 2 +- lib/facter/virtual.rb | 8 +-- lib/facter/zfs_version.rb | 4 +- lib/facter/zpool_version.rb | 4 +- spec/unit/domain_spec.rb | 10 +-- spec/unit/filesystems_spec.rb | 2 +- spec/unit/hardwareisa_spec.rb | 10 +-- spec/unit/hardwaremodel_spec.rb | 2 +- spec/unit/hostname_spec.rb | 8 +-- spec/unit/id_spec.rb | 6 +- spec/unit/ipaddress6_spec.rb | 8 +-- spec/unit/ipaddress_spec.rb | 2 +- spec/unit/kernel_spec.rb | 2 +- spec/unit/kernelrelease_spec.rb | 6 +- spec/unit/kernelversion_spec.rb | 2 +- spec/unit/ldom_spec.rb | 4 +- spec/unit/lsbdistcodename_spec.rb | 4 +- spec/unit/lsbdistdescription_spec.rb | 4 +- spec/unit/lsbdistid_spec.rb | 4 +- spec/unit/lsbdistrelease_spec.rb | 4 +- spec/unit/lsbrelease_spec.rb | 4 +- spec/unit/memory_spec.rb | 48 +++++++-------- spec/unit/operatingsystem_spec.rb | 2 +- spec/unit/operatingsystemrelease_spec.rb | 2 +- spec/unit/physicalprocessorcount_spec.rb | 14 ++--- spec/unit/processor_spec.rb | 18 +++--- spec/unit/selinux_spec.rb | 4 +- spec/unit/uniqueid_spec.rb | 6 +- spec/unit/util/ec2_spec.rb | 12 ++-- spec/unit/util/ip_spec.rb | 4 +- spec/unit/util/macosx_spec.rb | 14 ++--- spec/unit/util/manufacturer_spec.rb | 6 +- spec/unit/util/parser_spec.rb | 6 +- spec/unit/util/processor_spec.rb | 4 +- spec/unit/util/resolution_spec.rb | 6 +- spec/unit/util/solaris_zones_spec.rb | 10 +-- spec/unit/util/uptime_spec.rb | 2 +- spec/unit/util/virtual_spec.rb | 32 +++++----- spec/unit/util/xendomains_spec.rb | 4 +- spec/unit/virtual_spec.rb | 78 ++++++++++++------------ spec/unit/zfs_version_spec.rb | 22 +++---- spec/unit/zonename_spec.rb | 2 +- spec/unit/zones_spec.rb | 2 +- spec/unit/zpool_version_spec.rb | 22 +++---- 77 files changed, 308 insertions(+), 308 deletions(-) diff --git a/lib/facter/domain.rb b/lib/facter/domain.rb index 9709d92977..b8a4ae8a29 100644 --- a/lib/facter/domain.rb +++ b/lib/facter/domain.rb @@ -41,11 +41,11 @@ basic_hostname end - if name = Facter::Util::Resolution.exec(hostname_command) \ + if name = Facter::Core::Execution.exec(hostname_command) \ and name =~ /.*?\.(.+$)/ return_value = $1 - elsif Facter.value(:kernel) != "windows" and domain = Facter::Util::Resolution.exec('dnsdomainname 2> /dev/null') \ + elsif Facter.value(:kernel) != "windows" and domain = Facter::Core::Execution.exec('dnsdomainname 2> /dev/null') \ and domain =~ /.+/ return_value = domain diff --git a/lib/facter/filesystems.rb b/lib/facter/filesystems.rb index a6f028cf5e..6388032330 100644 --- a/lib/facter/filesystems.rb +++ b/lib/facter/filesystems.rb @@ -18,7 +18,7 @@ # This is due to some problems with IO#read in Ruby and reading content of # the "proc" file system that was reported more than once in the past ... file_systems = [] - Facter::Util::Resolution.exec('cat /proc/filesystems 2> /dev/null').each_line do |line| + Facter::Core::Execution.exec('cat /proc/filesystems 2> /dev/null').each_line do |line| # Remove bloat ... line.strip! diff --git a/lib/facter/hardwaremodel.rb b/lib/facter/hardwaremodel.rb index 48a1b934e7..7f54ec0db0 100644 --- a/lib/facter/hardwaremodel.rb +++ b/lib/facter/hardwaremodel.rb @@ -18,7 +18,7 @@ Facter.add(:hardwaremodel) do confine :operatingsystem => :aix setcode do - model = Facter::Util::Resolution.exec('lsattr -El sys0 -a modelname') + model = Facter::Core::Execution.exec('lsattr -El sys0 -a modelname') if model =~ /modelname\s(\S+)\s/ $1 end diff --git a/lib/facter/hostname.rb b/lib/facter/hostname.rb index 4f98b671cb..8847bfaf5d 100644 --- a/lib/facter/hostname.rb +++ b/lib/facter/hostname.rb @@ -14,7 +14,7 @@ Facter.add(:hostname) do setcode do hostname = nil - if name = Facter::Util::Resolution.exec('hostname') + if name = Facter::Core::Execution.exec('hostname') if name =~ /(.*?)\./ hostname = $1 else @@ -28,6 +28,6 @@ Facter.add(:hostname) do confine :kernel => :darwin, :kernelrelease => "R7" setcode do - Facter::Util::Resolution.exec('/usr/sbin/scutil --get LocalHostName') + Facter::Core::Execution.exec('/usr/sbin/scutil --get LocalHostName') end end diff --git a/lib/facter/ipaddress.rb b/lib/facter/ipaddress.rb index 1f8400c0a1..99b2a90ca9 100644 --- a/lib/facter/ipaddress.rb +++ b/lib/facter/ipaddress.rb @@ -155,7 +155,7 @@ if hostname = Facter.value(:hostname) # we need Hostname to exist for this to work host = nil - if host = Facter::Util::Resolution.exec("host #{hostname}") + if host = Facter::Core::Execution.exec("host #{hostname}") list = host.chomp.split(/\s/) if defined? list[-1] and list[-1] =~ /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ diff --git a/lib/facter/kernel.rb b/lib/facter/kernel.rb index 31ac22d334..d391a2440d 100644 --- a/lib/facter/kernel.rb +++ b/lib/facter/kernel.rb @@ -16,7 +16,7 @@ if Facter::Util::Config.is_windows? 'windows' else - Facter::Util::Resolution.exec("uname -s") + Facter::Core::Execution.exec("uname -s") end end end diff --git a/lib/facter/kernelrelease.rb b/lib/facter/kernelrelease.rb index 182cfe8420..fe7b34b652 100644 --- a/lib/facter/kernelrelease.rb +++ b/lib/facter/kernelrelease.rb @@ -23,7 +23,7 @@ Facter.add(:kernelrelease) do confine :kernel => "hp-ux" setcode do - version = Facter::Util::Resolution.exec('uname -r') + version = Facter::Core::Execution.exec('uname -r') version[2..-1] end end diff --git a/lib/facter/ldom.rb b/lib/facter/ldom.rb index 3e6f91f26a..b7cb7bbc3e 100644 --- a/lib/facter/ldom.rb +++ b/lib/facter/ldom.rb @@ -1,5 +1,5 @@ if Facter.value(:kernel) == 'SunOS' - virtinfo = Facter::Util::Resolution.exec('virtinfo -ap') + virtinfo = Facter::Core::Execution.exec('virtinfo -ap') # Convert virtinfo parseable output format to array of arrays. # DOMAINROLE|impl=LDoms|control=true|io=true|service=true|root=true diff --git a/lib/facter/lsbdistcodename.rb b/lib/facter/lsbdistcodename.rb index 0c0f5d9131..8b191dd017 100644 --- a/lib/facter/lsbdistcodename.rb +++ b/lib/facter/lsbdistcodename.rb @@ -13,6 +13,6 @@ Facter.add(:lsbdistcodename) do confine :kernel => [ :linux, :"gnu/kfreebsd" ] setcode do - Facter::Util::Resolution.exec('lsb_release -c -s 2>/dev/null') + Facter::Core::Execution.exec('lsb_release -c -s 2>/dev/null') end end diff --git a/lib/facter/lsbdistdescription.rb b/lib/facter/lsbdistdescription.rb index a4275b2757..0250b94113 100644 --- a/lib/facter/lsbdistdescription.rb +++ b/lib/facter/lsbdistdescription.rb @@ -13,7 +13,7 @@ Facter.add(:lsbdistdescription) do confine :kernel => [ :linux, :"gnu/kfreebsd" ] setcode do - if output = Facter::Util::Resolution.exec('lsb_release -d -s 2>/dev/null') + if output = Facter::Core::Execution.exec('lsb_release -d -s 2>/dev/null') # the output may be quoted (at least it is on gentoo) output.sub(/^"(.*)"$/,'\1') end diff --git a/lib/facter/lsbdistid.rb b/lib/facter/lsbdistid.rb index 065ef9b2b3..e77ccbc378 100644 --- a/lib/facter/lsbdistid.rb +++ b/lib/facter/lsbdistid.rb @@ -13,6 +13,6 @@ Facter.add(:lsbdistid) do confine :kernel => [ :linux, :"gnu/kfreebsd" ] setcode do - Facter::Util::Resolution.exec('lsb_release -i -s 2>/dev/null') + Facter::Core::Execution.exec('lsb_release -i -s 2>/dev/null') end end diff --git a/lib/facter/lsbdistrelease.rb b/lib/facter/lsbdistrelease.rb index ae5a9b22ed..c691b36584 100644 --- a/lib/facter/lsbdistrelease.rb +++ b/lib/facter/lsbdistrelease.rb @@ -13,6 +13,6 @@ Facter.add(:lsbdistrelease) do confine :kernel => [ :linux, :"gnu/kfreebsd" ] setcode do - Facter::Util::Resolution.exec('lsb_release -r -s 2>/dev/null') + Facter::Core::Execution.exec('lsb_release -r -s 2>/dev/null') end end diff --git a/lib/facter/lsbrelease.rb b/lib/facter/lsbrelease.rb index a1728bb1c4..9b89bc6e9d 100644 --- a/lib/facter/lsbrelease.rb +++ b/lib/facter/lsbrelease.rb @@ -13,6 +13,6 @@ Facter.add(:lsbrelease) do confine :kernel => [ :linux, :"gnu/kfreebsd" ] setcode do - Facter::Util::Resolution.exec('lsb_release -v -s 2>/dev/null') + Facter::Core::Execution.exec('lsb_release -v -s 2>/dev/null') end end diff --git a/lib/facter/macaddress.rb b/lib/facter/macaddress.rb index 3d0dffa8df..c0b47d5f8d 100644 --- a/lib/facter/macaddress.rb +++ b/lib/facter/macaddress.rb @@ -39,7 +39,7 @@ confine :osfamily => "Solaris" setcode do ether = [] - output = Facter::Util::Resolution.exec("/usr/bin/netstat -np") + output = Facter::Core::Execution.exec("/usr/bin/netstat -np") output.each_line do |s| ether.push($1) if s =~ /(?:SPLA)\s+(\w{2}:\w{2}:\w{2}:\w{2}:\w{2}:\w{2})/ end diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb index b9f1d82540..8ef0c4fc78 100644 --- a/lib/facter/memory.rb +++ b/lib/facter/memory.rb @@ -86,7 +86,7 @@ Facter.add("SwapEncrypted") do confine :kernel => :Darwin setcode do - swap = Facter::Util::Resolution.exec('sysctl vm.swapusage') + swap = Facter::Core::Execution.exec('sysctl vm.swapusage') encrypted = false if swap =~ /\(encrypted\)/ then encrypted = true; end encrypted @@ -99,7 +99,7 @@ Facter.add("memorysize_mb") do confine :kernel => :sunos # Total memory size available from prtconf - pconf = Facter::Util::Resolution.exec('/usr/sbin/prtconf 2>/dev/null') + pconf = Facter::Core::Execution.exec('/usr/sbin/prtconf 2>/dev/null') phymem = "" pconf.each_line do |line| if line =~ /^Memory size:\s+(\d+) Megabytes/ @@ -143,8 +143,8 @@ Facter.add("swapsize_mb") do confine :kernel => :dragonfly setcode do - page_size = Facter::Util::Resolution.exec("/sbin/sysctl -n hw.pagesize").to_f - swaptotal = Facter::Util::Resolution.exec("/sbin/sysctl -n vm.swap_size").to_f * page_size + page_size = Facter::Core::Execution.exec("/sbin/sysctl -n hw.pagesize").to_f + swaptotal = Facter::Core::Execution.exec("/sbin/sysctl -n vm.swap_size").to_f * page_size "%.2f" % [(swaptotal.to_f / 1024.0) / 1024.0] end end @@ -152,10 +152,10 @@ Facter.add("swapfree_mb") do confine :kernel => :dragonfly setcode do - page_size = Facter::Util::Resolution.exec("/sbin/sysctl -n hw.pagesize").to_f - swaptotal = Facter::Util::Resolution.exec("/sbin/sysctl -n vm.swap_size").to_f * page_size - swap_anon_use = Facter::Util::Resolution.exec("/sbin/sysctl -n vm.swap_anon_use").to_f * page_size - swap_cache_use = Facter::Util::Resolution.exec("/sbin/sysctl -n vm.swap_cache_use").to_f * page_size + page_size = Facter::Core::Execution.exec("/sbin/sysctl -n hw.pagesize").to_f + swaptotal = Facter::Core::Execution.exec("/sbin/sysctl -n vm.swap_size").to_f * page_size + swap_anon_use = Facter::Core::Execution.exec("/sbin/sysctl -n vm.swap_anon_use").to_f * page_size + swap_cache_use = Facter::Core::Execution.exec("/sbin/sysctl -n vm.swap_cache_use").to_f * page_size swapfree = swaptotal - swap_anon_use - swap_cache_use "%.2f" % [(swapfree.to_f / 1024.0) / 1024.0] end diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index f8e666e6ab..20f6feb3a5 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -16,7 +16,7 @@ setcode do # Use uname -v because /etc/release can change in zones under SmartOS. # It's apparently not trustworthy enough to rely on for this fact. - output = Facter::Util::Resolution.exec('uname -v') + output = Facter::Core::Execution.exec('uname -v') if output =~ /^joyent_/ "SmartOS" elsif output =~ /^oi_/ diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index f0d3512a8a..d2adcd9739 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -141,7 +141,7 @@ Facter.add(:operatingsystemrelease) do confine :operatingsystem => %w{VMwareESX} setcode do - release = Facter::Util::Resolution.exec('vmware -v') + release = Facter::Core::Execution.exec('vmware -v') if match = /VMware ESX .*?(\d.*)/.match(release) match[1] end diff --git a/lib/facter/physicalprocessorcount.rb b/lib/facter/physicalprocessorcount.rb index 06a488122a..6973545211 100644 --- a/lib/facter/physicalprocessorcount.rb +++ b/lib/facter/physicalprocessorcount.rb @@ -39,7 +39,7 @@ lookup_pattern = "#{sysfs_cpu_directory}" + "/cpu*/topology/physical_package_id" - Dir.glob(lookup_pattern).collect { |f| Facter::Util::Resolution.exec("cat #{f}")}.uniq.size.to_s + Dir.glob(lookup_pattern).collect { |f| Facter::Core::Execution.exec("cat #{f}")}.uniq.size.to_s else # @@ -48,7 +48,7 @@ # We assume that /proc/cpuinfo has what we need and is so then we need # to make sure that we only count unique entries ... # - str = Facter::Util::Resolution.exec("grep 'physical.\\+:' /proc/cpuinfo") + str = Facter::Core::Execution.exec("grep 'physical.\\+:' /proc/cpuinfo") if str then str.scan(/\d+/).uniq.size.to_s; end end @@ -75,9 +75,9 @@ cmd = "/usr/sbin/psrinfo" result = nil if (major_version > 5) or (major_version == 5 and minor_version >= 8) then - result = Facter::Util::Resolution.exec("#{cmd} -p") + result = Facter::Core::Execution.exec("#{cmd} -p") else - output = Facter::Util::Resolution.exec(cmd) + output = Facter::Core::Execution.exec(cmd) result = output.split("\n").length.to_s end end diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb index 586d43d39c..57d9459e45 100644 --- a/lib/facter/processor.rb +++ b/lib/facter/processor.rb @@ -91,21 +91,21 @@ Facter.add("Processor") do confine :kernel => :openbsd setcode do - Facter::Util::Resolution.exec("uname -p") + Facter::Core::Execution.exec("uname -p") end end Facter.add("ProcessorCount") do confine :kernel => :openbsd setcode do - Facter::Util::Resolution.exec("sysctl -n hw.ncpu") + Facter::Core::Execution.exec("sysctl -n hw.ncpu") end end Facter.add("ProcessorCount") do confine :kernel => :Darwin setcode do - Facter::Util::Resolution.exec("sysctl -n hw.ncpu") + Facter::Core::Execution.exec("sysctl -n hw.ncpu") end end @@ -150,14 +150,14 @@ Facter.add("Processor") do confine :kernel => [:dragonfly,:freebsd] setcode do - Facter::Util::Resolution.exec("sysctl -n hw.model") + Facter::Core::Execution.exec("sysctl -n hw.model") end end Facter.add("ProcessorCount") do confine :kernel => [:dragonfly,:freebsd] setcode do - Facter::Util::Resolution.exec("sysctl -n hw.ncpu") + Facter::Core::Execution.exec("sysctl -n hw.ncpu") end end @@ -169,11 +169,11 @@ result = nil if (major_version > 5) or (major_version == 5 and minor_version >= 8) then - if kstat = Facter::Util::Resolution.exec("/usr/bin/kstat cpu_info") + if kstat = Facter::Core::Execution.exec("/usr/bin/kstat cpu_info") result = kstat.scan(/\bcore_id\b\s+\d+/).uniq.length end else - if output = Facter::Util::Resolution.exec("/usr/sbin/psrinfo") then + if output = Facter::Core::Execution.exec("/usr/sbin/psrinfo") then result = output.split("\n").length end end diff --git a/lib/facter/selinux.rb b/lib/facter/selinux.rb index 0c54cb9e43..9599d966c4 100644 --- a/lib/facter/selinux.rb +++ b/lib/facter/selinux.rb @@ -22,7 +22,7 @@ def selinux_mount_point # a hang. Reading from other parts of /proc does not seem to cause this problem. # The work around is to read the file in another process. # -- andy Fri Aug 31 2012 - selinux_line = Facter::Util::Resolution.exec('cat /proc/self/mounts').each_line.find { |line| line =~ /selinuxfs/ } + selinux_line = Facter::Core::Execution.exec('cat /proc/self/mounts').each_line.find { |line| line =~ /selinuxfs/ } if selinux_line path = selinux_line.split[1] end @@ -72,7 +72,7 @@ def selinux_mount_point confine :selinux => :true setcode do result = 'unknown' - mode = Facter::Util::Resolution.exec(sestatus_cmd) + mode = Facter::Core::Execution.exec(sestatus_cmd) mode.each_line { |l| result = $1 if l =~ /^Current mode\:\s+(\w+)$/i } result.chomp end @@ -82,7 +82,7 @@ def selinux_mount_point confine :selinux => :true setcode do result = 'unknown' - mode = Facter::Util::Resolution.exec(sestatus_cmd) + mode = Facter::Core::Execution.exec(sestatus_cmd) mode.each_line { |l| result = $1 if l =~ /^Mode from config file\:\s+(\w+)$/i } result.chomp end @@ -92,7 +92,7 @@ def selinux_mount_point confine :selinux => :true setcode do result = 'unknown' - mode = Facter::Util::Resolution.exec(sestatus_cmd) + mode = Facter::Core::Execution.exec(sestatus_cmd) mode.each_line { |l| result = $1 if l =~ /^Policy from config file\:\s+(\w+)$/i } result.chomp end diff --git a/lib/facter/util/architecture.rb b/lib/facter/util/architecture.rb index c1686353a9..1aeebbd53b 100644 --- a/lib/facter/util/architecture.rb +++ b/lib/facter/util/architecture.rb @@ -2,11 +2,11 @@ module Facter::Util::Architecture ## - # lsattr is intended to directly delegate to Facter::Util::Resolution.exec in + # lsattr is intended to directly delegate to Facter::Core::Execution.exec in # an effort to make the processorX facts easier to test. See also the # {lsdev} method. def self.lsattr(command="lsattr -El proc0 -a type") - Facter::Util::Resolution.exec(command) + Facter::Core::Execution.exec(command) end ## diff --git a/lib/facter/util/ec2.rb b/lib/facter/util/ec2.rb index c5c393ac3f..c6e5dcadd4 100644 --- a/lib/facter/util/ec2.rb +++ b/lib/facter/util/ec2.rb @@ -52,7 +52,7 @@ def has_ec2_arp? "arp -an" end - if arp_table = Facter::Util::Resolution.exec(arp_command) + if arp_table = Facter::Core::Execution.exec(arp_command) return true if arp_table.match(mac_address_re) end return false diff --git a/lib/facter/util/file_read.rb b/lib/facter/util/file_read.rb index 2b911acc8e..c92185a618 100644 --- a/lib/facter/util/file_read.rb +++ b/lib/facter/util/file_read.rb @@ -7,7 +7,7 @@ module Util # Ruby, as mocking these behaviors can have wide-ranging effects. # # All Facter facts are encouraged to use this method instead of File.read or -# Facter::Util::Resolution.exec('cat ...') +# Facter::Core::Execution.exec('cat ...') # # @api public module FileRead diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index c15a6a1a75..81bb7063f4 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -96,7 +96,7 @@ def self.get_all_interface_output # # @return [String] the output of `ifconfig #{arguments} 2>/dev/null` or nil def self.exec_ifconfig(additional_arguments=[]) - Facter::Util::Resolution.exec("#{self.get_ifconfig} #{additional_arguments.join(' ')}") + Facter::Core::Execution.exec("#{self.get_ifconfig} #{additional_arguments.join(' ')}") end ## # get_ifconfig looks up the ifconfig binary @@ -110,7 +110,7 @@ def self.get_ifconfig # hpux_netstat_in is a delegate method that allows us to stub netstat -in # without stubbing exec. def self.hpux_netstat_in - Facter::Util::Resolution.exec("/bin/netstat -in") + Facter::Core::Execution.exec("/bin/netstat -in") end def self.get_infiniband_macaddress(interface) @@ -160,7 +160,7 @@ def self.hpux_ifconfig_interface(interface) end def self.hpux_lanscan - Facter::Util::Resolution.exec("/usr/sbin/lanscan") + Facter::Core::Execution.exec("/usr/sbin/lanscan") end def self.get_output_for_interface_and_label(interface, label) diff --git a/lib/facter/util/macosx.rb b/lib/facter/util/macosx.rb index 17da5b8fc7..73a79167c6 100644 --- a/lib/facter/util/macosx.rb +++ b/lib/facter/util/macosx.rb @@ -15,7 +15,7 @@ module Facter::Util::Macosx # by looking at the _name key of the _items dict for each _dataType def self.profiler_xml(data_field) - Facter::Util::Resolution.exec("/usr/sbin/system_profiler -xml #{data_field}") + Facter::Core::Execution.exec("/usr/sbin/system_profiler -xml #{data_field}") end def self.intern_xml(xml) @@ -57,7 +57,7 @@ def self.os_overview def self.sw_vers ver = Hash.new [ "productName", "productVersion", "buildVersion" ].each do |option| - ver["macosx_#{option}"] = Facter::Util::Resolution.exec("/usr/bin/sw_vers -#{option}").strip + ver["macosx_#{option}"] = Facter::Core::Execution.exec("/usr/bin/sw_vers -#{option}").strip end productversion = ver["macosx_productVersion"] if not productversion.nil? diff --git a/lib/facter/util/manufacturer.rb b/lib/facter/util/manufacturer.rb index ecaa97febd..183c52fc5c 100644 --- a/lib/facter/util/manufacturer.rb +++ b/lib/facter/util/manufacturer.rb @@ -55,7 +55,7 @@ def self.sysctl_find_system_info(name) Facter.add(facterkey) do confine :kernel => [:openbsd, :darwin] setcode do - Facter::Util::Resolution.exec("sysctl -n #{sysctlkey} 2>/dev/null") + Facter::Core::Execution.exec("sysctl -n #{sysctlkey} 2>/dev/null") end end end @@ -63,7 +63,7 @@ def self.sysctl_find_system_info(name) def self.prtdiag_sparc_find_system_info() # Parses prtdiag for a SPARC architecture string, won't work with Solaris x86 - output = Facter::Util::Resolution.exec('/usr/sbin/prtdiag 2>/dev/null') + output = Facter::Core::Execution.exec('/usr/sbin/prtdiag 2>/dev/null') # System Configuration: Sun Microsystems sun4u Sun SPARC Enterprise M3000 Server if output and output =~ /^System Configuration:\s+(.+?)\s+(sun\d+\S+)\s+(.+)/ @@ -81,7 +81,7 @@ def self.prtdiag_sparc_find_system_info() Facter.add('serialnumber') do setcode do - Facter::Util::Resolution.exec("/usr/sbin/sneep") + Facter::Core::Execution.exec("/usr/sbin/sneep") end end end diff --git a/lib/facter/util/memory.rb b/lib/facter/util/memory.rb index 58dca4233f..818b0fd86b 100644 --- a/lib/facter/util/memory.rb +++ b/lib/facter/util/memory.rb @@ -35,7 +35,7 @@ def self.scale_number(size, multiplier) def self.vmstat_find_free_memory(args = []) cmd = 'vmstat' cmd += (' ' + args.join(' ')) unless args.empty? - row = Facter::Util::Resolution.exec(cmd).split("\n")[-1] + row = Facter::Core::Execution.exec(cmd).split("\n")[-1] if row =~ /^\s*\d+\s*\d+\s*\d+\s*\d+\s*(\d+)/ memfree = $1 end @@ -52,7 +52,7 @@ def self.vmstat_darwin_find_free_memory() pagesize = 0 memspecfree = 0 - vmstats = Facter::Util::Resolution.exec('vm_stat') + vmstats = Facter::Core::Execution.exec('vm_stat') vmstats.each_line do |vmline| case when vmline =~ /page\ssize\sof\s(\d+)\sbytes/ @@ -71,7 +71,7 @@ def self.vmstat_darwin_find_free_memory() # it's the third value on the line starting with memory # svmon can be run by non root users def self.svmon_aix_find_free_memory() - Facter::Util::Resolution.exec("/usr/bin/svmon -O unit=KB") =~ /^memory\s+\d+\s+\d+\s+(\d+)\s+/ + Facter::Core::Execution.exec("/usr/bin/svmon -O unit=KB") =~ /^memory\s+\d+\s+\d+\s+(\d+)\s+/ $1 end @@ -112,15 +112,15 @@ def self.mem_size(kernel = Facter.value(:kernel)) def self.mem_size_info(kernel = Facter.value(:kernel)) case kernel when /OpenBSD/i - Facter::Util::Resolution.exec("sysctl hw.physmem | cut -d'=' -f2") + Facter::Core::Execution.exec("sysctl hw.physmem | cut -d'=' -f2") when /FreeBSD/i - Facter::Util::Resolution.exec("sysctl -n hw.physmem") + Facter::Core::Execution.exec("sysctl -n hw.physmem") when /Darwin/i - Facter::Util::Resolution.exec("sysctl -n hw.memsize") + Facter::Core::Execution.exec("sysctl -n hw.memsize") when /Dragonfly/i - Facter::Util::Resolution.exec("sysctl -n hw.physmem") + Facter::Core::Execution.exec("sysctl -n hw.physmem") when /AIX/i - if Facter::Util::Resolution.exec("/usr/bin/svmon -O unit=KB") =~ /^memory\s+(\d+)\s+/ + if Facter::Core::Execution.exec("/usr/bin/svmon -O unit=KB") =~ /^memory\s+(\d+)\s+/ $1 end end @@ -150,15 +150,15 @@ def self.swap_free(kernel = Facter.value(:kernel)) def self.swap_info(kernel = Facter.value(:kernel)) case kernel when /AIX/i - (Facter.value(:id) == "root") ? Facter::Util::Resolution.exec('swap -l 2>/dev/null') : nil + (Facter.value(:id) == "root") ? Facter::Core::Execution.exec('swap -l 2>/dev/null') : nil when /OpenBSD/i - Facter::Util::Resolution.exec('swapctl -s') + Facter::Core::Execution.exec('swapctl -s') when /FreeBSD/i - Facter::Util::Resolution.exec('swapinfo -k') + Facter::Core::Execution.exec('swapinfo -k') when /Darwin/i - Facter::Util::Resolution.exec('sysctl vm.swapusage') + Facter::Core::Execution.exec('sysctl vm.swapusage') when /SunOS/i - Facter::Util::Resolution.exec('/usr/sbin/swap -l 2>/dev/null') + Facter::Core::Execution.exec('/usr/sbin/swap -l 2>/dev/null') end end diff --git a/lib/facter/util/parser.rb b/lib/facter/util/parser.rb index 1f87134937..19f9d1732f 100644 --- a/lib/facter/util/parser.rb +++ b/lib/facter/util/parser.rb @@ -119,7 +119,7 @@ def parse_results class ScriptParser < Base def parse_results - KeyValuePairOutputFormat.parse Facter::Util::Resolution.exec(quote(filename)) + KeyValuePairOutputFormat.parse Facter::Core::Execution.exec(quote(filename)) end private @@ -142,7 +142,7 @@ class PowershellParser < Base # Returns a hash of facts from powershell output def parse_results shell_command = "powershell -NoProfile -NonInteractive -NoLogo -ExecutionPolicy Bypass -File \"#{filename}\"" - KeyValuePairOutputFormat.parse Facter::Util::Resolution.exec(shell_command) + KeyValuePairOutputFormat.parse Facter::Core::Execution.exec(shell_command) end end diff --git a/lib/facter/util/processor.rb b/lib/facter/util/processor.rb index 335dcf6fa5..05a5f98280 100644 --- a/lib/facter/util/processor.rb +++ b/lib/facter/util/processor.rb @@ -47,19 +47,19 @@ def self.aix_processor_list end ## - # lsdev is intended to directly delegate to Facter::Util::Resolution.exec in an + # lsdev is intended to directly delegate to Facter::Core::Execution.exec in an # effort to make the processorX facts easier to test by stubbing only the # behaviors we need to stub to get the output of the system command. def self.lsdev(command="lsdev -Cc processor") - Facter::Util::Resolution.exec(command) + Facter::Core::Execution.exec(command) end ## - # lsattr is intended to directly delegate to Facter::Util::Resolution.exec in + # lsattr is intended to directly delegate to Facter::Core::Execution.exec in # an effort to make the processorX facts easier to test. See also the # {lsdev} method. def self.lsattr(command="lsattr -El proc0 -a type") - Facter::Util::Resolution.exec(command) + Facter::Core::Execution.exec(command) end ## @@ -181,34 +181,34 @@ def self.read_unistd_h(path) private_class_method :read_unistd_h ## - # machinfo delegates directly to Facter::Util::Resolution.exec, as with lsdev + # machinfo delegates directly to Facter::Core::Execution.exec, as with lsdev # above. def self.machinfo(command="/usr/contrib/bin/machinfo") - Facter::Util::Resolution.exec(command) + Facter::Core::Execution.exec(command) end ## - # model delegates directly to Facter::Util::Resolution.exec. + # model delegates directly to Facter::Core::Execution.exec. def self.model(command="model") - Facter::Util::Resolution.exec(command) + Facter::Core::Execution.exec(command) end ## - # ioscan delegates directly to Facter::Util::Resolution.exec. + # ioscan delegates directly to Facter::Core::Execution.exec. def self.ioscan(command="ioscan -fknCprocessor") - Facter::Util::Resolution.exec(command) + Facter::Core::Execution.exec(command) end ## - # getconf_cpu_version delegates directly to Facter::Util::Resolution.exec. + # getconf_cpu_version delegates directly to Facter::Core::Execution.exec. def self.getconf_cpu_version(command="getconf CPU_VERSION") - Facter::Util::Resolution.exec(command) + Facter::Core::Execution.exec(command) end ## - # getconf_cpu_chip_type delegates directly to Facter::Util::Resolution.exec. + # getconf_cpu_chip_type delegates directly to Facter::Core::Execution.exec. def self.getconf_cpu_chip_type(command="getconf CPU_CHIP_TYPE") - Facter::Util::Resolution.exec(command) + Facter::Core::Execution.exec(command) end def self.enum_cpuinfo @@ -272,7 +272,7 @@ def self.enum_kstat processor_num = -1 processor_list = [] Thread::exclusive do - kstat = Facter::Util::Resolution.exec('/usr/bin/kstat cpu_info') + kstat = Facter::Core::Execution.exec('/usr/bin/kstat cpu_info') if kstat kstat.each_line do |l| if l =~ /cpu_info(\d+)/ diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 007a4b88d9..d7564c2830 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -145,7 +145,7 @@ def resolve_value if @code.is_a? Proc @code.call() else - Facter::Util::Resolution.exec(@code) + Facter::Core::Execution.exec(@code) end end end diff --git a/lib/facter/util/solaris_zones.rb b/lib/facter/util/solaris_zones.rb index 28bfbe39b4..881baa7635 100644 --- a/lib/facter/util/solaris_zones.rb +++ b/lib/facter/util/solaris_zones.rb @@ -93,7 +93,7 @@ def add_dynamic_facts # # @return [Hash] the parsed output of the zoneadm command def refresh - @zoneadm_output = Facter::Util::Resolution.exec(zoneadm_cmd) + @zoneadm_output = Facter::Core::Execution.exec(zoneadm_cmd) parse! end diff --git a/lib/facter/util/uptime.rb b/lib/facter/util/uptime.rb index 2fab9f46a3..8af2af2516 100644 --- a/lib/facter/util/uptime.rb +++ b/lib/facter/util/uptime.rb @@ -20,19 +20,19 @@ def self.get_uptime_seconds_win private def self.uptime_proc_uptime - if output = Facter::Util::Resolution.exec("/bin/cat #{uptime_file} 2>/dev/null") + if output = Facter::Core::Execution.exec("/bin/cat #{uptime_file} 2>/dev/null") output.chomp.split(" ").first.to_i end end def self.uptime_sysctl - if output = Facter::Util::Resolution.exec("#{uptime_sysctl_cmd} 2>/dev/null") + if output = Facter::Core::Execution.exec("#{uptime_sysctl_cmd} 2>/dev/null") compute_uptime(Time.at(output.match(/\d+/)[0].to_i)) end end def self.uptime_executable - if output = Facter::Util::Resolution.exec("#{uptime_executable_cmd} 2>/dev/null") + if output = Facter::Core::Execution.exec("#{uptime_executable_cmd} 2>/dev/null") up=0 if output =~ /(\d+) day(?:s|\(s\))?,\s+(\d+):(\d+)/ # Regexp handles Solaris, AIX, HP-UX, and Tru64. diff --git a/lib/facter/util/virtual.rb b/lib/facter/util/virtual.rb index d22478137d..e65583876c 100644 --- a/lib/facter/util/virtual.rb +++ b/lib/facter/util/virtual.rb @@ -4,7 +4,7 @@ module Facter::Util::Virtual ## # virt_what is a delegating helper method intended to make it easier to stub # the system call without affecting other calls to - # Facter::Util::Resolution.exec + # Facter::Core::Execution.exec # # Per https://bugzilla.redhat.com/show_bug.cgi?id=719611 when run as a # non-root user the virt-what command may emit an error message on stdout, @@ -12,7 +12,7 @@ module Facter::Util::Virtual # method ensures stderr is redirected and that error messages are stripped # from stdout. def self.virt_what(command = "virt-what") - command = Facter::Util::Resolution.which(command) + command = Facter::Core::Execution.which(command) return unless command if Facter.value(:kernel) == 'windows' @@ -20,15 +20,15 @@ def self.virt_what(command = "virt-what") else redirected_cmd = "#{command} 2>/dev/null" end - output = Facter::Util::Resolution.exec redirected_cmd + output = Facter::Core::Execution.exec redirected_cmd output.gsub(/^virt-what: .*$/, '') if output end ## # lspci is a delegating helper method intended to make it easier to stub the - # system call without affecting other calls to Facter::Util::Resolution.exec + # system call without affecting other calls to Facter::Core::Execution.exec def self.lspci(command = "lspci 2>/dev/null") - Facter::Util::Resolution.exec command + Facter::Core::Execution.exec command end def self.openvz? @@ -40,7 +40,7 @@ def self.openvz_type return false unless self.openvz? return false unless FileTest.exists?( '/proc/self/status' ) - envid = Facter::Util::Resolution.exec( 'grep "envID" /proc/self/status' ) + envid = Facter::Core::Execution.exec( 'grep "envID" /proc/self/status' ) if envid =~ /^envID:\s+0$/i return 'openvzhn' elsif envid =~ /^envID:\s+(\d+)$/i @@ -56,7 +56,7 @@ def self.openvz_cloudlinux? def self.zone? return true if FileTest.directory?("/.SUNWnative") - z = Facter::Util::Resolution.exec("/sbin/zonename") + z = Facter::Core::Execution.exec("/sbin/zonename") return false unless z return z.chomp != 'global' end @@ -92,7 +92,7 @@ def self.kvm? txt = if FileTest.exists?("/proc/cpuinfo") File.read("/proc/cpuinfo") elsif ["FreeBSD", "OpenBSD"].include? Facter.value(:kernel) - Facter::Util::Resolution.exec("/sbin/sysctl -n hw.model") + Facter::Core::Execution.exec("/sbin/sysctl -n hw.model") end (txt =~ /QEMU Virtual CPU/) ? true : false end @@ -123,11 +123,11 @@ def self.jail? when "FreeBSD" then "/sbin" when "GNU/kFreeBSD" then "/bin" end - Facter::Util::Resolution.exec("#{path}/sysctl -n security.jail.jailed") == "1" + Facter::Core::Execution.exec("#{path}/sysctl -n security.jail.jailed") == "1" end def self.hpvm? - Facter::Util::Resolution.exec("/usr/bin/getconf MACHINE_MODEL").chomp =~ /Virtual Machine/ + Facter::Core::Execution.exec("/usr/bin/getconf MACHINE_MODEL").chomp =~ /Virtual Machine/ end def self.zlinux? diff --git a/lib/facter/util/xendomains.rb b/lib/facter/util/xendomains.rb index 6b0d403ba8..5432ddd7b3 100644 --- a/lib/facter/util/xendomains.rb +++ b/lib/facter/util/xendomains.rb @@ -2,7 +2,7 @@ # module Facter::Util::Xendomains def self.get_domains - if xm_list = Facter::Util::Resolution.exec('/usr/sbin/xm list 2>/dev/null') + if xm_list = Facter::Core::Execution.exec('/usr/sbin/xm list 2>/dev/null') domains = xm_list.split("\n").reject { |line| line =~ /^(Name|Domain-0)/ } domains.map { |line| line.split(/\s/)[0] }.join(',') end diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 416aff1c66..bc07bb94d6 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -60,7 +60,7 @@ setcode do next "zone" if Facter::Util::Virtual.zone? - output = Facter::Util::Resolution.exec('prtdiag') + output = Facter::Core::Execution.exec('prtdiag') if output lines = output.split("\n") next "parallels" if lines.any? {|l| l =~ /Parallels/ } @@ -91,7 +91,7 @@ confine :kernel => 'OpenBSD' has_weight 10 setcode do - output = Facter::Util::Resolution.exec('sysctl -n hw.product 2>/dev/null') + output = Facter::Core::Execution.exec('sysctl -n hw.product 2>/dev/null') if output lines = output.split("\n") next "parallels" if lines.any? {|l| l =~ /Parallels/ } @@ -132,7 +132,7 @@ end # Parse dmidecode - output = Facter::Util::Resolution.exec('dmidecode 2> /dev/null') + output = Facter::Core::Execution.exec('dmidecode 2> /dev/null') if output lines = output.split("\n") next "parallels" if lines.any? {|l| l =~ /Parallels/ } @@ -145,7 +145,7 @@ end # Sample output of vmware -v `VMware Server 1.0.5 build-80187` - output = Facter::Util::Resolution.exec("vmware -v") + output = Facter::Core::Execution.exec("vmware -v") if output mdata = output.match /(\S+)\s+(\S+)/ next "#{mdata[1]}_#{mdata[2]}".downcase if mdata diff --git a/lib/facter/zfs_version.rb b/lib/facter/zfs_version.rb index 8eeee11db5..3427eee040 100644 --- a/lib/facter/zfs_version.rb +++ b/lib/facter/zfs_version.rb @@ -2,8 +2,8 @@ Facter.add('zfs_version') do setcode do - if Facter::Util::Resolution.which('zfs') - zfs_v = Facter::Util::Resolution.exec('zfs upgrade -v') + if Facter::Core::Execution.which('zfs') + zfs_v = Facter::Core::Execution.exec('zfs upgrade -v') zfs_version = zfs_v.scan(/^\s+(\d+)\s+/m).flatten.last unless zfs_v.nil? end end diff --git a/lib/facter/zpool_version.rb b/lib/facter/zpool_version.rb index d182b2dc77..05a117a75f 100644 --- a/lib/facter/zpool_version.rb +++ b/lib/facter/zpool_version.rb @@ -2,8 +2,8 @@ Facter.add('zpool_version') do setcode do - if Facter::Util::Resolution.which('zpool') - zpool_v = Facter::Util::Resolution.exec('zpool upgrade -v') + if Facter::Core::Execution.which('zpool') + zpool_v = Facter::Core::Execution.exec('zpool upgrade -v') zpool_version = zpool_v.match(/ZFS pool version (\d+)./).captures.first unless zpool_v.nil? end end diff --git a/spec/unit/domain_spec.rb b/spec/unit/domain_spec.rb index a2c13851ba..c3701d51cb 100755 --- a/spec/unit/domain_spec.rb +++ b/spec/unit/domain_spec.rb @@ -24,11 +24,11 @@ def resolv_conf_contains(*lines) let(:dnsdomain_command) { "dnsdomainname 2> /dev/null" } def the_hostname_is(value) - Facter::Util::Resolution.stubs(:exec).with(hostname_command).returns(value) + Facter::Core::Execution.stubs(:exec).with(hostname_command).returns(value) end def the_dnsdomainname_is(value) - Facter::Util::Resolution.stubs(:exec).with(dnsdomain_command).returns(value) + Facter::Core::Execution.stubs(:exec).with(dnsdomain_command).returns(value) end before do @@ -197,7 +197,7 @@ def expects_dnsdomains(domains) it "should return nil" do expects_dnsdomains([nil]) - Facter::Util::Resolution.stubs(:exec).with(hostname_command).returns('sometest') + Facter::Core::Execution.stubs(:exec).with(hostname_command).returns('sometest') FileTest.stubs(:exists?).with("/etc/resolv.conf").returns(false) Facter.fact(:domain).value.should be_nil @@ -288,8 +288,8 @@ def expects_dnsdomains(domains) describe scenario[:scenario] do before(:each) do - Facter::Util::Resolution.stubs(:exec).with("hostname -f 2> /dev/null").returns(scenario[:hostname]) - Facter::Util::Resolution.stubs(:exec).with("dnsdomainname 2> /dev/null").returns(scenario[:dnsdomainname]) + Facter::Core::Execution.stubs(:exec).with("hostname -f 2> /dev/null").returns(scenario[:hostname]) + Facter::Core::Execution.stubs(:exec).with("dnsdomainname 2> /dev/null").returns(scenario[:dnsdomainname]) resolv_conf_contains( "search #{scenario[:resolve_search]}", "domain #{scenario[:resolve_domain]}" diff --git a/spec/unit/filesystems_spec.rb b/spec/unit/filesystems_spec.rb index 8ed6da233a..fbe3805623 100644 --- a/spec/unit/filesystems_spec.rb +++ b/spec/unit/filesystems_spec.rb @@ -14,7 +14,7 @@ before :each do Facter.fact(:kernel).stubs(:value).returns('Linux') fixture_data = my_fixture_read('linux') - Facter::Util::Resolution.expects(:exec) \ + Facter::Core::Execution.expects(:exec) \ .with('cat /proc/filesystems 2> /dev/null').returns(fixture_data) Facter.collection.internal_loader.load(:filesystems) end diff --git a/spec/unit/hardwareisa_spec.rb b/spec/unit/hardwareisa_spec.rb index a709e8c742..39bb7050b6 100755 --- a/spec/unit/hardwareisa_spec.rb +++ b/spec/unit/hardwareisa_spec.rb @@ -6,35 +6,35 @@ describe "Hardwareisa fact" do it "should match uname -p on Linux" do Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter::Util::Resolution.stubs(:exec).with("uname -p").returns("Inky") + Facter::Core::Execution.stubs(:exec).with("uname -p").returns("Inky") Facter.fact(:hardwareisa).value.should == "Inky" end it "should match uname -p on Darwin" do Facter.fact(:kernel).stubs(:value).returns("Darwin") - Facter::Util::Resolution.stubs(:exec).with("uname -p").returns("Blinky") + Facter::Core::Execution.stubs(:exec).with("uname -p").returns("Blinky") Facter.fact(:hardwareisa).value.should == "Blinky" end it "should match uname -p on SunOS" do Facter.fact(:kernel).stubs(:value).returns("SunOS") - Facter::Util::Resolution.stubs(:exec).with("uname -p").returns("Pinky") + Facter::Core::Execution.stubs(:exec).with("uname -p").returns("Pinky") Facter.fact(:hardwareisa).value.should == "Pinky" end it "should match uname -p on FreeBSD" do Facter.fact(:kernel).stubs(:value).returns("FreeBSD") - Facter::Util::Resolution.stubs(:exec).with("uname -p").returns("Clyde") + Facter::Core::Execution.stubs(:exec).with("uname -p").returns("Clyde") Facter.fact(:hardwareisa).value.should == "Clyde" end it "should match uname -m on HP-UX" do Facter.fact(:kernel).stubs(:value).returns("HP-UX") - Facter::Util::Resolution.stubs(:exec).with("uname -m").returns("Pac-Man") + Facter::Core::Execution.stubs(:exec).with("uname -m").returns("Pac-Man") Facter.fact(:hardwareisa).value.should == "Pac-Man" end diff --git a/spec/unit/hardwaremodel_spec.rb b/spec/unit/hardwaremodel_spec.rb index 29c893db5b..0a8e9ee750 100644 --- a/spec/unit/hardwaremodel_spec.rb +++ b/spec/unit/hardwaremodel_spec.rb @@ -6,7 +6,7 @@ describe "Hardwaremodel fact" do it "should match uname -m by default" do Facter.fact(:kernel).stubs(:value).returns("Darwin") - Facter::Util::Resolution.stubs(:exec).with("uname -m").returns("Inky") + Facter::Core::Execution.stubs(:exec).with("uname -m").returns("Inky") Facter.fact(:hardwaremodel).value.should == "Inky" end diff --git a/spec/unit/hostname_spec.rb b/spec/unit/hostname_spec.rb index 0c31493f66..a78703170f 100755 --- a/spec/unit/hostname_spec.rb +++ b/spec/unit/hostname_spec.rb @@ -11,17 +11,17 @@ end it "should use the hostname command" do - Facter::Util::Resolution.expects(:exec).with('hostname').at_least_once + Facter::Core::Execution.expects(:exec).with('hostname').at_least_once Facter.fact(:hostname).value.should be_nil end it "should use hostname as the fact if unqualified" do - Facter::Util::Resolution.stubs(:exec).with('hostname').returns('host1') + Facter::Core::Execution.stubs(:exec).with('hostname').returns('host1') Facter.fact(:hostname).value.should == "host1" end it "should truncate the domain name if qualified" do - Facter::Util::Resolution.stubs(:exec).with('hostname').returns('host1.example.com') + Facter::Core::Execution.stubs(:exec).with('hostname').returns('host1.example.com') Facter.fact(:hostname).value.should == "host1" end end @@ -33,7 +33,7 @@ end it "should use scutil to get the hostname" do - Facter::Util::Resolution.expects(:exec).with('/usr/sbin/scutil --get LocalHostName').returns("host1") + Facter::Core::Execution.expects(:exec).with('/usr/sbin/scutil --get LocalHostName').returns("host1") Facter.fact(:hostname).value.should == "host1" end end diff --git a/spec/unit/id_spec.rb b/spec/unit/id_spec.rb index e89aae4d18..b2eff37378 100755 --- a/spec/unit/id_spec.rb +++ b/spec/unit/id_spec.rb @@ -12,7 +12,7 @@ it "should return the current user" do given_a_configuration_of(:is_windows => k == 'windows') Facter.fact(:kernel).stubs(:value).returns(k) - Facter::Util::Resolution.expects(:exec).once.with('whoami').returns 'bar' + Facter::Core::Execution.expects(:exec).once.with('whoami').returns 'bar' Facter.fact(:id).value.should == 'bar' end @@ -21,8 +21,8 @@ it "should return the current user on Solaris" do given_a_configuration_of(:is_windows => false) - Facter::Util::Resolution.stubs(:exec).with('uname -s').returns('SunOS') - Facter::Util::Resolution.expects(:exec).once.with('/usr/xpg4/bin/id -un').returns 'bar' + Facter::Core::Execution.stubs(:exec).with('uname -s').returns('SunOS') + Facter::Core::Execution.expects(:exec).once.with('/usr/xpg4/bin/id -un').returns 'bar' Facter.fact(:id).value.should == 'bar' end diff --git a/spec/unit/ipaddress6_spec.rb b/spec/unit/ipaddress6_spec.rb index 1c9153d288..729fb9bfcb 100755 --- a/spec/unit/ipaddress6_spec.rb +++ b/spec/unit/ipaddress6_spec.rb @@ -15,7 +15,7 @@ def ifconfig_fixture(filename) end it "should return ipaddress6 information for Darwin" do - Facter::Util::Resolution.stubs(:exec).with('uname -s').returns('Darwin') + Facter::Core::Execution.stubs(:exec).with('uname -s').returns('Darwin') Facter::Util::IP.stubs(:get_ifconfig).returns("/sbin/ifconfig") Facter::Util::IP.stubs(:exec_ifconfig).with(["-a"]). returns(ifconfig_fixture('darwin_ifconfig_all_with_multiple_interfaces')) @@ -24,7 +24,7 @@ def ifconfig_fixture(filename) end it "should return ipaddress6 information for Linux" do - Facter::Util::Resolution.stubs(:exec).with('uname -s').returns('Linux') + Facter::Core::Execution.stubs(:exec).with('uname -s').returns('Linux') Facter::Util::IP.stubs(:get_ifconfig).returns("/sbin/ifconfig") Facter::Util::IP.stubs(:exec_ifconfig).with(["2>/dev/null"]). returns(ifconfig_fixture('linux_ifconfig_all_with_multiple_interfaces')) @@ -33,7 +33,7 @@ def ifconfig_fixture(filename) end it "should return ipaddress6 information for Linux with recent net-tools" do - Facter::Util::Resolution.stubs(:exec).with('uname -s').returns('Linux') + Facter::Core::Execution.stubs(:exec).with('uname -s').returns('Linux') Facter::Util::IP.stubs(:get_ifconfig).returns("/sbin/ifconfig") Facter::Util::IP.stubs(:exec_ifconfig).with(["2>/dev/null"]). returns(ifconfig_fixture('ifconfig_net_tools_1.60.txt')) @@ -42,7 +42,7 @@ def ifconfig_fixture(filename) end it "should return ipaddress6 information for Solaris" do - Facter::Util::Resolution.stubs(:exec).with('uname -s').returns('SunOS') + Facter::Core::Execution.stubs(:exec).with('uname -s').returns('SunOS') Facter::Util::IP.stubs(:get_ifconfig).returns("/usr/sbin/ifconfig") Facter::Util::IP.stubs(:exec_ifconfig).with(["-a"]). returns(ifconfig_fixture('sunos_ifconfig_all_with_multiple_interfaces')) diff --git a/spec/unit/ipaddress_spec.rb b/spec/unit/ipaddress_spec.rb index 674b74370f..39eb97c3bb 100755 --- a/spec/unit/ipaddress_spec.rb +++ b/spec/unit/ipaddress_spec.rb @@ -60,7 +60,7 @@ def expect_ifconfig_parse(address, fixture) context "when you have no active network adapter" do it "should return nil if there are no active (or any) network adapters" do Facter::Util::WMI.expects(:execquery).with(Facter::Util::IP::Windows::WMI_IP_INFO_QUERY).returns([]) - Facter::Util::Resolution.stubs(:exec) + Facter::Core::Execution.stubs(:exec) Facter.value(:ipaddress).should == nil end diff --git a/spec/unit/kernel_spec.rb b/spec/unit/kernel_spec.rb index 37d3431810..ab80b8015c 100644 --- a/spec/unit/kernel_spec.rb +++ b/spec/unit/kernel_spec.rb @@ -16,7 +16,7 @@ describe "on everything else" do it "should return the kernel using 'uname -s'" do given_a_configuration_of(:is_windows => false) - Facter::Util::Resolution.stubs(:exec).with('uname -s').returns("test_kernel") + Facter::Core::Execution.stubs(:exec).with('uname -s').returns("test_kernel") Facter.fact(:kernel).value.should == 'test_kernel' end diff --git a/spec/unit/kernelrelease_spec.rb b/spec/unit/kernelrelease_spec.rb index 8566d4aa8b..94eec126f0 100644 --- a/spec/unit/kernelrelease_spec.rb +++ b/spec/unit/kernelrelease_spec.rb @@ -21,7 +21,7 @@ describe "on AIX" do before do Facter.fact(:kernel).stubs(:value).returns("aix") - Facter::Util::Resolution.stubs(:exec).with('oslevel -s').returns("test_kernel") + Facter::Core::Execution.stubs(:exec).with('oslevel -s').returns("test_kernel") end it "should return the kernel release" do @@ -32,7 +32,7 @@ describe "on HP-UX" do before do Facter.fact(:kernel).stubs(:value).returns("hp-ux") - Facter::Util::Resolution.stubs(:exec).with('uname -r').returns("B.11.31") + Facter::Core::Execution.stubs(:exec).with('uname -r').returns("B.11.31") end it "should remove preceding letters" do @@ -43,7 +43,7 @@ describe "on everything else" do before do Facter.fact(:kernel).stubs(:value).returns("linux") - Facter::Util::Resolution.stubs(:exec).with('uname -r').returns("test_kernel") + Facter::Core::Execution.stubs(:exec).with('uname -r').returns("test_kernel") end it "should return the kernel release" do diff --git a/spec/unit/kernelversion_spec.rb b/spec/unit/kernelversion_spec.rb index 98cedccd5f..93ee06164d 100644 --- a/spec/unit/kernelversion_spec.rb +++ b/spec/unit/kernelversion_spec.rb @@ -7,7 +7,7 @@ describe "on Solaris/Sun OS" do before do Facter.fact(:kernel).stubs(:value).returns('sunos') - Facter::Util::Resolution.stubs(:exec).with('uname -v').returns("1.234.5") + Facter::Core::Execution.stubs(:exec).with('uname -v').returns("1.234.5") end it "should return the kernel version using 'uname -v'" do diff --git a/spec/unit/ldom_spec.rb b/spec/unit/ldom_spec.rb index f97d4a8765..416134c82e 100644 --- a/spec/unit/ldom_spec.rb +++ b/spec/unit/ldom_spec.rb @@ -15,7 +15,7 @@ def ldom_fixtures(filename) before :each do # For virtinfo documentation: # http://docs.oracle.com/cd/E23824_01/html/821-1462/virtinfo-1m.html - Facter::Util::Resolution.stubs(:exec).with("virtinfo -ap"). + Facter::Core::Execution.stubs(:exec).with("virtinfo -ap"). returns(ldom_fixtures('ldom_v1')) Facter.collection.internal_loader.load(:ldom) end @@ -63,7 +63,7 @@ def ldom_fixtures(filename) describe "when running on non ldom hardware" do before :each do - Facter::Util::Resolution.stubs(:exec).with("virtinfo -ap").returns(nil) + Facter::Core::Execution.stubs(:exec).with("virtinfo -ap").returns(nil) Facter.collection.internal_loader.load(:ldom) end diff --git a/spec/unit/lsbdistcodename_spec.rb b/spec/unit/lsbdistcodename_spec.rb index d0297ca288..6516c4c2fe 100755 --- a/spec/unit/lsbdistcodename_spec.rb +++ b/spec/unit/lsbdistcodename_spec.rb @@ -11,12 +11,12 @@ end it "should return the codename through lsb_release -c -s 2>/dev/null" do - Facter::Util::Resolution.stubs(:exec).with('lsb_release -c -s 2>/dev/null').returns 'n/a' + Facter::Core::Execution.stubs(:exec).with('lsb_release -c -s 2>/dev/null').returns 'n/a' Facter.fact(:lsbdistcodename).value.should == 'n/a' end it "should return nil if lsb_release is not installed" do - Facter::Util::Resolution.stubs(:exec).with('lsb_release -c -s 2>/dev/null').returns nil + Facter::Core::Execution.stubs(:exec).with('lsb_release -c -s 2>/dev/null').returns nil Facter.fact(:lsbdistcodename).value.should be_nil end end diff --git a/spec/unit/lsbdistdescription_spec.rb b/spec/unit/lsbdistdescription_spec.rb index a73612ea0d..bca930eb53 100755 --- a/spec/unit/lsbdistdescription_spec.rb +++ b/spec/unit/lsbdistdescription_spec.rb @@ -11,12 +11,12 @@ end it "should return the description through lsb_release -d -s 2>/dev/null" do - Facter::Util::Resolution.stubs(:exec).with('lsb_release -d -s 2>/dev/null').returns '"Gentoo Base System release 2.1"' + Facter::Core::Execution.stubs(:exec).with('lsb_release -d -s 2>/dev/null').returns '"Gentoo Base System release 2.1"' Facter.fact(:lsbdistdescription).value.should == 'Gentoo Base System release 2.1' end it "should return nil if lsb_release is not installed" do - Facter::Util::Resolution.stubs(:exec).with('lsb_release -d -s 2>/dev/null').returns nil + Facter::Core::Execution.stubs(:exec).with('lsb_release -d -s 2>/dev/null').returns nil Facter.fact(:lsbdistdescription).value.should be_nil end end diff --git a/spec/unit/lsbdistid_spec.rb b/spec/unit/lsbdistid_spec.rb index b204a5f6f3..bec2fc5c84 100755 --- a/spec/unit/lsbdistid_spec.rb +++ b/spec/unit/lsbdistid_spec.rb @@ -11,12 +11,12 @@ end it "should return the id through lsb_release -i -s 2>/dev/null" do - Facter::Util::Resolution.stubs(:exec).with('lsb_release -i -s 2>/dev/null').returns 'Gentoo' + Facter::Core::Execution.stubs(:exec).with('lsb_release -i -s 2>/dev/null').returns 'Gentoo' Facter.fact(:lsbdistid).value.should == 'Gentoo' end it "should return nil if lsb_release is not installed 2>/dev/null" do - Facter::Util::Resolution.stubs(:exec).with('lsb_release -i -s 2>/dev/null').returns nil + Facter::Core::Execution.stubs(:exec).with('lsb_release -i -s 2>/dev/null').returns nil Facter.fact(:lsbdistid).value.should be_nil end end diff --git a/spec/unit/lsbdistrelease_spec.rb b/spec/unit/lsbdistrelease_spec.rb index c356e901e7..1d9476dd18 100755 --- a/spec/unit/lsbdistrelease_spec.rb +++ b/spec/unit/lsbdistrelease_spec.rb @@ -11,12 +11,12 @@ end it "should return the release through lsb_release -r -s 2>/dev/null" do - Facter::Util::Resolution.stubs(:exec).with('lsb_release -r -s 2>/dev/null').returns '2.1' + Facter::Core::Execution.stubs(:exec).with('lsb_release -r -s 2>/dev/null').returns '2.1' Facter.fact(:lsbdistrelease).value.should == '2.1' end it "should return nil if lsb_release is not installed" do - Facter::Util::Resolution.stubs(:exec).with('lsb_release -r -s 2>/dev/null').returns nil + Facter::Core::Execution.stubs(:exec).with('lsb_release -r -s 2>/dev/null').returns nil Facter.fact(:lsbdistrelease).value.should be_nil end end diff --git a/spec/unit/lsbrelease_spec.rb b/spec/unit/lsbrelease_spec.rb index b0d5c93823..18bf75143a 100755 --- a/spec/unit/lsbrelease_spec.rb +++ b/spec/unit/lsbrelease_spec.rb @@ -11,12 +11,12 @@ end it "should return the release through lsb_release -v -s 2>/dev/null" do - Facter::Util::Resolution.stubs(:exec).with('lsb_release -v -s 2>/dev/null').returns 'n/a' + Facter::Core::Execution.stubs(:exec).with('lsb_release -v -s 2>/dev/null').returns 'n/a' Facter.fact(:lsbrelease).value.should == 'n/a' end it "should return nil if lsb_release is not installed" do - Facter::Util::Resolution.stubs(:exec).with('lsb_release -v -s 2>/dev/null').returns nil + Facter::Core::Execution.stubs(:exec).with('lsb_release -v -s 2>/dev/null').returns nil Facter.fact(:lsbrelease).value.should be_nil end end diff --git a/spec/unit/memory_spec.rb b/spec/unit/memory_spec.rb index dd1eb76c67..53a31983ec 100755 --- a/spec/unit/memory_spec.rb +++ b/spec/unit/memory_spec.rb @@ -48,7 +48,7 @@ before(:each) do Facter.clear Facter.fact(:kernel).stubs(:value).returns("Darwin") - Facter::Util::Resolution.stubs(:exec).with('sysctl -n hw.memsize').returns('8589934592') + Facter::Core::Execution.stubs(:exec).with('sysctl -n hw.memsize').returns('8589934592') sample_vm_stat = </dev/null').returns(swapusage) + Facter::Core::Execution.stubs(:exec).with('swap -l 2>/dev/null').returns(swapusage) svmon = </dev/null').returns sample_prtconf + Facter::Core::Execution.stubs(:exec).with('/usr/sbin/prtconf 2>/dev/null').returns sample_prtconf vmstat_lines = </dev/null').returns sample_swap_line + Facter::Core::Execution.stubs(:exec).with('/usr/sbin/swap -l 2>/dev/null').returns sample_swap_line Facter.collection.internal_loader.load(:memory) end @@ -316,7 +316,7 @@ /dev/swap 4294967295,4294967295 16 2097136 2097136 /dev/swap2 4294967295,4294967295 16 2097136 2097136 SWAP - Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/swap -l 2>/dev/null').returns sample_swap_line + Facter::Core::Execution.stubs(:exec).with('/usr/sbin/swap -l 2>/dev/null').returns sample_swap_line Facter.collection.internal_loader.load(:memory) end @@ -339,7 +339,7 @@ describe "when no swap exists" do before(:each) do - Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/swap -l 2>/dev/null').returns "" + Facter::Core::Execution.stubs(:exec).with('/usr/sbin/swap -l 2>/dev/null').returns "" Facter.collection.internal_loader.load(:memory) end @@ -368,19 +368,19 @@ Facter.fact(:kernel).stubs(:value).returns("dragonfly") swapusage = "total: 148342k bytes allocated = 0k used, 148342k available" - Facter::Util::Resolution.stubs(:exec).with('/sbin/sysctl -n hw.pagesize').returns("4096") - Facter::Util::Resolution.stubs(:exec).with('/sbin/sysctl -n vm.swap_size').returns("128461") - Facter::Util::Resolution.stubs(:exec).with('/sbin/sysctl -n vm.swap_anon_use').returns("2635") - Facter::Util::Resolution.stubs(:exec).with('/sbin/sysctl -n vm.swap_cache_use').returns("0") + Facter::Core::Execution.stubs(:exec).with('/sbin/sysctl -n hw.pagesize').returns("4096") + Facter::Core::Execution.stubs(:exec).with('/sbin/sysctl -n vm.swap_size').returns("128461") + Facter::Core::Execution.stubs(:exec).with('/sbin/sysctl -n vm.swap_anon_use').returns("2635") + Facter::Core::Execution.stubs(:exec).with('/sbin/sysctl -n vm.swap_cache_use').returns("0") vmstat = < "oi_151a", }.each_pair do |distribution, string| it "should be #{distribution} if uname -v is '#{string}'" do - Facter::Util::Resolution.stubs(:exec).with('uname -v').returns(string) + Facter::Core::Execution.stubs(:exec).with('uname -v').returns(string) Facter.fact(:operatingsystem).value.should == distribution end end diff --git a/spec/unit/operatingsystemrelease_spec.rb b/spec/unit/operatingsystemrelease_spec.rb index 781ee98ecd..dd9370d2e4 100755 --- a/spec/unit/operatingsystemrelease_spec.rb +++ b/spec/unit/operatingsystemrelease_spec.rb @@ -51,7 +51,7 @@ Facter.fact(:kernelrelease).stubs(:value).returns("4.1.0") Facter.fact(:operatingsystem).stubs(:value).returns("VMwareESX") - Facter::Util::Resolution.stubs(:exec).with('vmware -v').returns('foo') + Facter::Core::Execution.stubs(:exec).with('vmware -v').returns('foo') Facter.fact(:operatingsystemrelease).value end diff --git a/spec/unit/physicalprocessorcount_spec.rb b/spec/unit/physicalprocessorcount_spec.rb index 1e957fc936..acdd646462 100755 --- a/spec/unit/physicalprocessorcount_spec.rb +++ b/spec/unit/physicalprocessorcount_spec.rb @@ -12,7 +12,7 @@ it "should return one physical CPU" do Dir.stubs(:glob).with("/sys/devices/system/cpu/cpu*/topology/physical_package_id").returns(["/sys/devices/system/cpu/cpu0/topology/physical_package_id"]) - Facter::Util::Resolution.stubs(:exec).with("cat /sys/devices/system/cpu/cpu0/topology/physical_package_id").returns("0") + Facter::Core::Execution.stubs(:exec).with("cat /sys/devices/system/cpu/cpu0/topology/physical_package_id").returns("0") Facter.fact(:physicalprocessorcount).value.should == "1" end @@ -25,10 +25,10 @@ /sys/devices/system/cpu/cpu3/topology/physical_package_id }) - Facter::Util::Resolution.stubs(:exec).with("cat /sys/devices/system/cpu/cpu0/topology/physical_package_id").returns("0") - Facter::Util::Resolution.stubs(:exec).with("cat /sys/devices/system/cpu/cpu1/topology/physical_package_id").returns("1") - Facter::Util::Resolution.stubs(:exec).with("cat /sys/devices/system/cpu/cpu2/topology/physical_package_id").returns("2") - Facter::Util::Resolution.stubs(:exec).with("cat /sys/devices/system/cpu/cpu3/topology/physical_package_id").returns("3") + Facter::Core::Execution.stubs(:exec).with("cat /sys/devices/system/cpu/cpu0/topology/physical_package_id").returns("0") + Facter::Core::Execution.stubs(:exec).with("cat /sys/devices/system/cpu/cpu1/topology/physical_package_id").returns("1") + Facter::Core::Execution.stubs(:exec).with("cat /sys/devices/system/cpu/cpu2/topology/physical_package_id").returns("2") + Facter::Core::Execution.stubs(:exec).with("cat /sys/devices/system/cpu/cpu3/topology/physical_package_id").returns("3") Facter.fact(:physicalprocessorcount).value.should == "4" end @@ -58,7 +58,7 @@ Facter.fact(:kernel).stubs(:value).returns(:sunos) Facter.stubs(:value).with(:kernelrelease).returns(release) - Facter::Util::Resolution.expects(:exec).with("/usr/sbin/psrinfo -p").returns("1") + Facter::Core::Execution.expects(:exec).with("/usr/sbin/psrinfo -p").returns("1") Facter.fact(:physicalprocessorcount).value.should == "1" end end @@ -68,7 +68,7 @@ Facter.fact(:kernel).stubs(:value).returns(:sunos) Facter.stubs(:value).with(:kernelrelease).returns(release) - Facter::Util::Resolution.expects(:exec).with("/usr/sbin/psrinfo").returns(psrinfo) + Facter::Core::Execution.expects(:exec).with("/usr/sbin/psrinfo").returns(psrinfo) Facter.fact(:physicalprocessorcount).value.should == "2" end end diff --git a/spec/unit/processor_spec.rb b/spec/unit/processor_spec.rb index c3894fde49..717f942b12 100755 --- a/spec/unit/processor_spec.rb +++ b/spec/unit/processor_spec.rb @@ -185,35 +185,35 @@ def sysfs_cpu_stubs(count) it "should be 2 on dual-processor Darwin box" do Facter.fact(:kernel).stubs(:value).returns("Darwin") - Facter::Util::Resolution.stubs(:exec).with("sysctl -n hw.ncpu").returns('2') + Facter::Core::Execution.stubs(:exec).with("sysctl -n hw.ncpu").returns('2') Facter.fact(:processorcount).value.should == "2" end it "should be 2 on dual-processor OpenBSD box" do Facter.fact(:kernel).stubs(:value).returns("OpenBSD") - Facter::Util::Resolution.stubs(:exec).with("sysctl -n hw.ncpu").returns('2') + Facter::Core::Execution.stubs(:exec).with("sysctl -n hw.ncpu").returns('2') Facter.fact(:processorcount).value.should == "2" end it "should be 2 on dual-processor FreeBSD box" do Facter.fact(:kernel).stubs(:value).returns("FreeBSD") - Facter::Util::Resolution.stubs(:exec).with("sysctl -n hw.ncpu").returns('2') + Facter::Core::Execution.stubs(:exec).with("sysctl -n hw.ncpu").returns('2') Facter.fact(:processorcount).value.should == "2" end it "should print the correct CPU Model on FreeBSD" do Facter.fact(:kernel).stubs(:value).returns("FreeBSD") - Facter::Util::Resolution.stubs(:exec).with("sysctl -n hw.model").returns('SomeVendor CPU 3GHz') + Facter::Core::Execution.stubs(:exec).with("sysctl -n hw.model").returns('SomeVendor CPU 3GHz') Facter.fact(:processor).value.should == "SomeVendor CPU 3GHz" end it "should be 2 on dual-processor DragonFly box" do Facter.fact(:kernel).stubs(:value).returns("DragonFly") - Facter::Util::Resolution.stubs(:exec).with("sysctl -n hw.ncpu").returns('2') + Facter::Core::Execution.stubs(:exec).with("sysctl -n hw.ncpu").returns('2') Facter.fact(:processorcount).value.should == "2" end @@ -239,8 +239,8 @@ def sysfs_cpu_stubs(count) it "uses kstat on release #{release} (#{arch})" do Facter.stubs(:value).with(:kernelrelease).returns(release) - Facter::Util::Resolution.expects(:exec).with("/usr/sbin/psrinfo").never - Facter::Util::Resolution.expects(:exec).with("/usr/bin/kstat cpu_info").returns(self.send("kstat_#{arch}".intern)) + Facter::Core::Execution.expects(:exec).with("/usr/sbin/psrinfo").never + Facter::Core::Execution.expects(:exec).with("/usr/bin/kstat cpu_info").returns(self.send("kstat_#{arch}".intern)) Facter.fact(:processorcount).value.should == '8' end end @@ -251,8 +251,8 @@ def sysfs_cpu_stubs(count) Facter.stubs(:value).with(:kernelrelease).returns(release) fixture_data = File.read(fixtures('processorcount','solaris-psrinfo')) - Facter::Util::Resolution.expects(:exec).with("/usr/bin/kstat cpu_info").never - Facter::Util::Resolution.expects(:exec).with("/usr/sbin/psrinfo").returns(fixture_data) + Facter::Core::Execution.expects(:exec).with("/usr/bin/kstat cpu_info").never + Facter::Core::Execution.expects(:exec).with("/usr/sbin/psrinfo").returns(fixture_data) Facter.fact(:processorcount).value.should == '24' end end diff --git a/spec/unit/selinux_spec.rb b/spec/unit/selinux_spec.rb index 647fbb65c6..9241becad5 100755 --- a/spec/unit/selinux_spec.rb +++ b/spec/unit/selinux_spec.rb @@ -105,7 +105,7 @@ end def sestatus_is(status) - Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/sestatus').returns(status) + Facter::Core::Execution.stubs(:exec).with('/usr/sbin/sestatus').returns(status) end def mounts_does_not_exist @@ -114,7 +114,7 @@ def mounts_does_not_exist def mounts_contains(*lines) FileTest.expects(:exists?).with("/proc/self/mounts").returns true - Facter::Util::Resolution.expects(:exec).with("cat /proc/self/mounts").returns(lines.join("\n")) + Facter::Core::Execution.expects(:exec).with("cat /proc/self/mounts").returns(lines.join("\n")) end end diff --git a/spec/unit/uniqueid_spec.rb b/spec/unit/uniqueid_spec.rb index e9870d39fd..93adb7900a 100755 --- a/spec/unit/uniqueid_spec.rb +++ b/spec/unit/uniqueid_spec.rb @@ -6,21 +6,21 @@ describe "Uniqueid fact" do it "should match hostid on Solaris" do Facter.fact(:kernel).stubs(:value).returns("SunOS") - Facter::Util::Resolution.stubs(:exec).with("hostid").returns("Larry") + Facter::Core::Execution.stubs(:exec).with("hostid").returns("Larry") Facter.fact(:uniqueid).value.should == "Larry" end it "should match hostid on Linux" do Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter::Util::Resolution.stubs(:exec).with("hostid").returns("Curly") + Facter::Core::Execution.stubs(:exec).with("hostid").returns("Curly") Facter.fact(:uniqueid).value.should == "Curly" end it "should match hostid on AIX" do Facter.fact(:kernel).stubs(:value).returns("AIX") - Facter::Util::Resolution.stubs(:exec).with("hostid").returns("Moe") + Facter::Core::Execution.stubs(:exec).with("hostid").returns("Moe") Facter.fact(:uniqueid).value.should == "Moe" end diff --git a/spec/unit/util/ec2_spec.rb b/spec/unit/util/ec2_spec.rb index c894e597a3..f963db6c32 100755 --- a/spec/unit/util/ec2_spec.rb +++ b/spec/unit/util/ec2_spec.rb @@ -17,21 +17,21 @@ it "should succeed if arp table contains fe:ff:ff:ff:ff:ff" do ec2arp = my_fixture_read("linux-arp-ec2.out") - Facter::Util::Resolution.expects(:exec).with("arp -an").\ + Facter::Core::Execution.expects(:exec).with("arp -an").\ at_least_once.returns(ec2arp) Facter::Util::EC2.has_ec2_arp?.should == true end it "should succeed if arp table contains FE:FF:FF:FF:FF:FF" do ec2arp = my_fixture_read("centos-arp-ec2.out") - Facter::Util::Resolution.expects(:exec).with("arp -an").\ + Facter::Core::Execution.expects(:exec).with("arp -an").\ at_least_once.returns(ec2arp) Facter::Util::EC2.has_ec2_arp?.should == true end it "should fail if arp table does not contain fe:ff:ff:ff:ff:ff" do ec2arp = my_fixture_read("linux-arp-not-ec2.out") - Facter::Util::Resolution.expects(:exec).with("arp -an"). + Facter::Core::Execution.expects(:exec).with("arp -an"). at_least_once.returns(ec2arp) Facter::Util::EC2.has_ec2_arp?.should == false end @@ -45,14 +45,14 @@ it "should succeed if arp table contains fe-ff-ff-ff-ff-ff" do ec2arp = my_fixture_read("windows-2008-arp-a.out") - Facter::Util::Resolution.expects(:exec).with("arp -a").\ + Facter::Core::Execution.expects(:exec).with("arp -a").\ at_least_once.returns(ec2arp) Facter::Util::EC2.has_ec2_arp?.should == true end it "should fail if arp table does not contain fe-ff-ff-ff-ff-ff" do ec2arp = my_fixture_read("windows-2008-arp-a-not-ec2.out") - Facter::Util::Resolution.expects(:exec).with("arp -a"). + Facter::Core::Execution.expects(:exec).with("arp -a"). at_least_once.returns(ec2arp) Facter::Util::EC2.has_ec2_arp?.should == false end @@ -66,7 +66,7 @@ it "should fail if arp table does not contain fe:ff:ff:ff:ff:ff" do ec2arp = my_fixture_read("solaris8_arp_a_not_ec2.out") - Facter::Util::Resolution.expects(:exec).with("arp -a"). + Facter::Core::Execution.expects(:exec).with("arp -a"). at_least_once.returns(ec2arp) Facter::Util::EC2.has_ec2_arp?.should == false diff --git a/spec/unit/util/ip_spec.rb b/spec/unit/util/ip_spec.rb index 67772b46b3..ace30e0054 100755 --- a/spec/unit/util/ip_spec.rb +++ b/spec/unit/util/ip_spec.rb @@ -408,14 +408,14 @@ def self.hpux_examples it "support additional arguments" do Facter::Util::IP.stubs(:get_ifconfig).returns("/sbin/ifconfig") - Facter::Util::Resolution.stubs(:exec).with("/sbin/ifconfig -a") + Facter::Core::Execution.stubs(:exec).with("/sbin/ifconfig -a") Facter::Util::IP.exec_ifconfig(["-a"]) end it "joins multiple arguments correctly" do Facter::Util::IP.stubs(:get_ifconfig).returns("/sbin/ifconfig") - Facter::Util::Resolution.stubs(:exec).with("/sbin/ifconfig -a -e -i -j") + Facter::Core::Execution.stubs(:exec).with("/sbin/ifconfig -a -e -i -j") Facter::Util::IP.exec_ifconfig(["-a","-e","-i","-j"]) end diff --git a/spec/unit/util/macosx_spec.rb b/spec/unit/util/macosx_spec.rb index 94b967aabd..d65aa085a8 100755 --- a/spec/unit/util/macosx_spec.rb +++ b/spec/unit/util/macosx_spec.rb @@ -27,7 +27,7 @@ end it "should be able to retrieve profiler data as xml for a given data field" do - Facter::Util::Resolution.expects(:exec).with("/usr/sbin/system_profiler -xml foo").returns "yay" + Facter::Core::Execution.expects(:exec).with("/usr/sbin/system_profiler -xml foo").returns "yay" Facter::Util::Macosx.profiler_xml("foo").should == "yay" end @@ -80,17 +80,17 @@ describe "when working out software version" do before do - Facter::Util::Resolution.expects(:exec).with("/usr/bin/sw_vers -productName").returns "Mac OS X" - Facter::Util::Resolution.expects(:exec).with("/usr/bin/sw_vers -buildVersion").returns "9J62" + Facter::Core::Execution.expects(:exec).with("/usr/bin/sw_vers -productName").returns "Mac OS X" + Facter::Core::Execution.expects(:exec).with("/usr/bin/sw_vers -buildVersion").returns "9J62" end it "should have called sw_vers three times when determining software version" do - Facter::Util::Resolution.expects(:exec).with("/usr/bin/sw_vers -productVersion").returns "10.5.7" + Facter::Core::Execution.expects(:exec).with("/usr/bin/sw_vers -productVersion").returns "10.5.7" Facter::Util::Macosx.sw_vers end it "should return a hash with the correct keys when determining software version" do - Facter::Util::Resolution.expects(:exec).with("/usr/bin/sw_vers -productVersion").returns "10.5.7" + Facter::Core::Execution.expects(:exec).with("/usr/bin/sw_vers -productVersion").returns "10.5.7" Facter::Util::Macosx.sw_vers.keys.sort.should == ["macosx_productName", "macosx_buildVersion", "macosx_productversion_minor", @@ -99,14 +99,14 @@ end it "should split a product version of 'x.y.z' into separate hash entries correctly" do - Facter::Util::Resolution.expects(:exec).with("/usr/bin/sw_vers -productVersion").returns "1.2.3" + Facter::Core::Execution.expects(:exec).with("/usr/bin/sw_vers -productVersion").returns "1.2.3" sw_vers = Facter::Util::Macosx.sw_vers sw_vers["macosx_productversion_major"].should == "1.2" sw_vers["macosx_productversion_minor"].should == "3" end it "should treat a product version of 'x.y' as 'x.y.0" do - Facter::Util::Resolution.expects(:exec).with("/usr/bin/sw_vers -productVersion").returns "2.3" + Facter::Core::Execution.expects(:exec).with("/usr/bin/sw_vers -productVersion").returns "2.3" Facter::Util::Macosx.sw_vers["macosx_productversion_minor"].should == "0" end end diff --git a/spec/unit/util/manufacturer_spec.rb b/spec/unit/util/manufacturer_spec.rb index 20ba72f89b..f1dea563b2 100755 --- a/spec/unit/util/manufacturer_spec.rb +++ b/spec/unit/util/manufacturer_spec.rb @@ -18,14 +18,14 @@ end it "should parse prtdiag output on a sunfire v120" do - Facter::Util::Resolution.stubs(:exec).returns(my_fixture_read("solaris_sunfire_v120_prtdiag")) + Facter::Core::Execution.stubs(:exec).returns(my_fixture_read("solaris_sunfire_v120_prtdiag")) Facter::Manufacturer.prtdiag_sparc_find_system_info() Facter.value(:manufacturer).should == "Sun Microsystems" Facter.value(:productname).should == "Sun Fire V120 (UltraSPARC-IIe 648MHz)" end it "should parse prtdiag output on a t5220" do - Facter::Util::Resolution.stubs(:exec).returns(my_fixture_read("solaris_t5220_prtdiag")) + Facter::Core::Execution.stubs(:exec).returns(my_fixture_read("solaris_t5220_prtdiag")) Facter::Manufacturer.prtdiag_sparc_find_system_info() Facter.value(:manufacturer).should == "Sun Microsystems" Facter.value(:productname).should == "SPARC Enterprise T5220" @@ -36,7 +36,7 @@ Facter.fact(:kernel).stubs(:value).returns("SunOS") Facter.fact(:hardwareisa).stubs(:value).returns("sparc") - Facter::Util::Resolution.stubs(:exec).with(regexp_matches(/prtdiag/)).returns(nil) + Facter::Core::Execution.stubs(:exec).with(regexp_matches(/prtdiag/)).returns(nil) Facter::Manufacturer.prtdiag_sparc_find_system_info() Facter.value(:manufacturer).should_not == "Sun Microsystems" end diff --git a/spec/unit/util/parser_spec.rb b/spec/unit/util/parser_spec.rb index f6e28e5999..f7e0af108e 100755 --- a/spec/unit/util/parser_spec.rb +++ b/spec/unit/util/parser_spec.rb @@ -95,7 +95,7 @@ let(:data_in_txt) { "one=two\nthree=four\n" } def expects_script_to_return(path, content, result) - Facter::Util::Resolution.stubs(:exec).with(path).returns(content) + Facter::Core::Execution.stubs(:exec).with(path).returns(content) File.stubs(:executable?).with(path).returns(true) File.stubs(:file?).with(path).returns(true) @@ -123,7 +123,7 @@ def expects_parser_to_return_nil_for_directory(path) it "quotes scripts with spaces" do path = "/h a s s p a c e s#{ext}" - Facter::Util::Resolution.expects(:exec).with("\"#{path}\"").returns(data_in_txt) + Facter::Core::Execution.expects(:exec).with("\"#{path}\"").returns(data_in_txt) expects_script_to_return(path, data_in_txt, data) end @@ -176,7 +176,7 @@ def expects_parser_to_return_nil_for_directory(path) def expects_to_parse_powershell(cmd, content, result) Facter::Util::Config.stubs(:is_windows?).returns(true) - Facter::Util::Resolution.stubs(:exec).returns(content) + Facter::Core::Execution.stubs(:exec).returns(content) File.stubs(:file?).with(ps1).returns(true) Facter::Util::Parser.parser_for(cmd).results.should == result diff --git a/spec/unit/util/processor_spec.rb b/spec/unit/util/processor_spec.rb index 5637a7b182..a659acbf1e 100755 --- a/spec/unit/util/processor_spec.rb +++ b/spec/unit/util/processor_spec.rb @@ -70,14 +70,14 @@ it "should get the processor description on Solaris (x86)" do Facter.fact(:architecture).stubs(:value).returns("i86pc") - Facter::Util::Resolution.stubs(:exec).with("/usr/bin/kstat cpu_info").returns(my_fixture_read("solaris-i86pc")) + Facter::Core::Execution.stubs(:exec).with("/usr/bin/kstat cpu_info").returns(my_fixture_read("solaris-i86pc")) Facter::Util::Processor.enum_kstat[0].should == "Intel(r) Core(tm) i5 CPU M 450 @ 2.40GHz" end it "should get the processor description on Solaris (SPARC64)" do Facter.fact(:architecture).stubs(:value).returns("sun4u") - Facter::Util::Resolution.stubs(:exec).with("/usr/bin/kstat cpu_info").returns(my_fixture_read("solaris-sun4u")) + Facter::Core::Execution.stubs(:exec).with("/usr/bin/kstat cpu_info").returns(my_fixture_read("solaris-sun4u")) Facter::Util::Processor.enum_kstat[0].should == "SPARC64-VII" Facter::Util::Processor.enum_kstat[1].should == "SPARC64-VII" diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index 2d7c119f5c..71bf16809b 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -65,7 +65,7 @@ describe "and setcode has not been called" do it "should return nil" do - Facter::Util::Resolution.expects(:exec).with(nil, nil).never + Facter::Core::Execution.expects(:exec).with(nil, nil).never resolution.value.should be_nil end end @@ -78,7 +78,7 @@ it "should return the result of executing the code" do resolution.setcode "/bin/foo" - Facter::Util::Resolution.expects(:exec).once.with("/bin/foo").returns "yup" + Facter::Core::Execution.expects(:exec).once.with("/bin/foo").returns "yup" expect(resolution.value).to eq "yup" end @@ -91,7 +91,7 @@ it "should return the result of executing the code" do resolution.setcode "/bin/foo" - Facter::Util::Resolution.expects(:exec).once.with("/bin/foo").returns "yup" + Facter::Core::Execution.expects(:exec).once.with("/bin/foo").returns "yup" expect(resolution.value).to eq "yup" end diff --git a/spec/unit/util/solaris_zones_spec.rb b/spec/unit/util/solaris_zones_spec.rb index c5c48ef1e6..c7d4a05ff2 100644 --- a/spec/unit/util/solaris_zones_spec.rb +++ b/spec/unit/util/solaris_zones_spec.rb @@ -47,7 +47,7 @@ describe '#refresh' do it 'executes the zoneadm_cmd' do - Facter::Util::Resolution.expects(:exec).with(subject.zoneadm_cmd).returns(zone_list) + Facter::Core::Execution.expects(:exec).with(subject.zoneadm_cmd).returns(zone_list) subject.refresh end end @@ -69,7 +69,7 @@ it 'uses a single read of the system information for all of the dynamically generated zone facts' do given_initial_zone_facts # <= single read happens here - Facter::Util::Resolution.expects(:exec).never + Facter::Core::Execution.expects(:exec).never Facter.fact(:zone_zoneA_id).value Facter.fact(:zone_local_id).value end @@ -101,7 +101,7 @@ given_initial_zone_facts when_facts_have_been_resolved_then_flushed - Facter::Util::Resolution.expects(:exec).once.returns(zone_list2) + Facter::Core::Execution.expects(:exec).once.returns(zone_list2) Facter.fact(:zones).value Facter.fact(:zone_zoneA_id).value Facter.fact(:zone_local_id).value @@ -111,7 +111,7 @@ end def given_initial_zone_facts - Facter::Util::Resolution.stubs(:exec). + Facter::Core::Execution.stubs(:exec). with(subject.zoneadm_cmd). returns(zone_list) described_class.add_facts @@ -121,7 +121,7 @@ def when_facts_have_been_resolved_then_flushed Facter.fact(:zones).value Facter.fact(:zone_zoneA_id).value Facter.fact(:zone_local_id).value - Facter::Util::Resolution.stubs(:exec).returns(zone_list2) + Facter::Core::Execution.stubs(:exec).returns(zone_list2) Facter.flush end end diff --git a/spec/unit/util/uptime_spec.rb b/spec/unit/util/uptime_spec.rb index 4bbbb752d5..06472a0796 100755 --- a/spec/unit/util/uptime_spec.rb +++ b/spec/unit/util/uptime_spec.rb @@ -99,7 +99,7 @@ test_cases.each do |uptime, expected| it "should return #{expected} for #{uptime}" do - Facter::Util::Resolution.stubs(:exec).with('uptime 2>/dev/null').returns(uptime) + Facter::Core::Execution.stubs(:exec).with('uptime 2>/dev/null').returns(uptime) Facter.fact(:uptime_seconds).value.should == expected end end diff --git a/spec/unit/util/virtual_spec.rb b/spec/unit/util/virtual_spec.rb index 5caaacb0db..9414f8ecaf 100755 --- a/spec/unit/util/virtual_spec.rb +++ b/spec/unit/util/virtual_spec.rb @@ -30,31 +30,31 @@ it "should identify openvzhn when /proc/self/status has envID of 0" do Facter::Util::Virtual.stubs(:openvz?).returns(true) FileTest.stubs(:exists?).with("/proc/self/status").returns(true) - Facter::Util::Resolution.stubs(:exec).with('grep "envID" /proc/self/status').returns("envID: 0") + Facter::Core::Execution.stubs(:exec).with('grep "envID" /proc/self/status').returns("envID: 0") Facter::Util::Virtual.openvz_type().should == "openvzhn" end it "should identify openvzve when /proc/self/status has envID of 0" do Facter::Util::Virtual.stubs(:openvz?).returns(true) FileTest.stubs(:exists?).with('/proc/self/status').returns(true) - Facter::Util::Resolution.stubs(:exec).with('grep "envID" /proc/self/status').returns("envID: 666") + Facter::Core::Execution.stubs(:exec).with('grep "envID" /proc/self/status').returns("envID: 666") Facter::Util::Virtual.openvz_type().should == "openvzve" end it "should not attempt to identify openvz when /proc/self/status has no envID" do Facter::Util::Virtual.stubs(:openvz?).returns(true) FileTest.stubs(:exists?).with('/proc/self/status').returns(true) - Facter::Util::Resolution.stubs(:exec).with('grep "envID" /proc/self/status').returns("") + Facter::Core::Execution.stubs(:exec).with('grep "envID" /proc/self/status').returns("") Facter::Util::Virtual.openvz_type().should be_nil end it "should identify Solaris zones when non-global zone" do - Facter::Util::Resolution.stubs(:exec).with("/sbin/zonename").returns("somezone") + Facter::Core::Execution.stubs(:exec).with("/sbin/zonename").returns("somezone") Facter::Util::Virtual.should be_zone end it "should not identify Solaris zones when global zone" do - Facter::Util::Resolution.stubs(:exec).with("/sbin/zonename").returns("global") + Facter::Core::Execution.stubs(:exec).with("/sbin/zonename").returns("global") Facter::Util::Virtual.should_not be_zone end @@ -91,7 +91,7 @@ it "should identify kvm" do Facter::Util::Virtual.stubs(:kvm?).returns(true) - Facter::Util::Resolution.stubs(:exec).with('dmidecode 2> /dev/null').returns("something") + Facter::Core::Execution.stubs(:exec).with('dmidecode 2> /dev/null').returns("something") Facter::Util::Virtual.kvm_type().should == "kvm" end @@ -202,38 +202,38 @@ it "should detect kvm on FreeBSD" do FileTest.stubs(:exists?).with("/proc/cpuinfo").returns(false) Facter.fact(:kernel).stubs(:value).returns("FreeBSD") - Facter::Util::Resolution.stubs(:exec).with("/sbin/sysctl -n hw.model").returns("QEMU Virtual CPU version 0.12.4") + Facter::Core::Execution.stubs(:exec).with("/sbin/sysctl -n hw.model").returns("QEMU Virtual CPU version 0.12.4") Facter::Util::Virtual.should be_kvm end it "should detect kvm on OpenBSD" do FileTest.stubs(:exists?).with("/proc/cpuinfo").returns(false) Facter.fact(:kernel).stubs(:value).returns("OpenBSD") - Facter::Util::Resolution.stubs(:exec).with("/sbin/sysctl -n hw.model").returns('QEMU Virtual CPU version (cpu64-rhel6) ("AuthenticAMD" 686-class, 512KB L2 cache)') + Facter::Core::Execution.stubs(:exec).with("/sbin/sysctl -n hw.model").returns('QEMU Virtual CPU version (cpu64-rhel6) ("AuthenticAMD" 686-class, 512KB L2 cache)') Facter::Util::Virtual.should be_kvm end it "should identify FreeBSD jail when in jail" do Facter.fact(:kernel).stubs(:value).returns("FreeBSD") - Facter::Util::Resolution.stubs(:exec).with("/sbin/sysctl -n security.jail.jailed").returns("1") + Facter::Core::Execution.stubs(:exec).with("/sbin/sysctl -n security.jail.jailed").returns("1") Facter::Util::Virtual.should be_jail end it "should not identify GNU/kFreeBSD jail when not in jail" do Facter.fact(:kernel).stubs(:value).returns("GNU/kFreeBSD") - Facter::Util::Resolution.stubs(:exec).with("/bin/sysctl -n security.jail.jailed").returns("0") + Facter::Core::Execution.stubs(:exec).with("/bin/sysctl -n security.jail.jailed").returns("0") Facter::Util::Virtual.should_not be_jail end it "should detect hpvm on HP-UX" do Facter.fact(:kernel).stubs(:value).returns("HP-UX") - Facter::Util::Resolution.stubs(:exec).with("/usr/bin/getconf MACHINE_MODEL").returns('ia64 hp server Integrity Virtual Machine') + Facter::Core::Execution.stubs(:exec).with("/usr/bin/getconf MACHINE_MODEL").returns('ia64 hp server Integrity Virtual Machine') Facter::Util::Virtual.should be_hpvm end it "should not detect hpvm on HP-UX when not in hpvm" do Facter.fact(:kernel).stubs(:value).returns("HP-UX") - Facter::Util::Resolution.stubs(:exec).with("/usr/bin/getconf MACHINE_MODEL").returns('ia64 hp server rx660') + Facter::Core::Execution.stubs(:exec).with("/usr/bin/getconf MACHINE_MODEL").returns('ia64 hp server rx660') Facter::Util::Virtual.should_not be_hpvm end @@ -263,8 +263,8 @@ shared_examples_for "virt-what" do |kernel, path, null_device| before(:each) do Facter.fact(:kernel).stubs(:value).returns(kernel) - Facter::Util::Resolution.expects(:which).with("virt-what").returns(path) - Facter::Util::Resolution.expects(:exec).with("#{path} 2>#{null_device}") + Facter::Core::Execution.expects(:which).with("virt-what").returns(path) + Facter::Core::Execution.expects(:exec).with("#{path} 2>#{null_device}") end it "on #{kernel} virt-what is at #{path} and stderr is sent to #{null_device}" do @@ -278,8 +278,8 @@ it "should strip out warnings on stdout from virt-what" do virt_what_warning = "virt-what: this script must be run as root" Facter.fact(:kernel).stubs(:value).returns('linux') - Facter::Util::Resolution.expects(:which).with('virt-what').returns "/usr/bin/virt-what" - Facter::Util::Resolution.expects(:exec).with('/usr/bin/virt-what 2>/dev/null').returns virt_what_warning + Facter::Core::Execution.expects(:which).with('virt-what').returns "/usr/bin/virt-what" + Facter::Core::Execution.expects(:exec).with('/usr/bin/virt-what 2>/dev/null').returns virt_what_warning Facter::Util::Virtual.virt_what.should_not match /^virt-what: / end end diff --git a/spec/unit/util/xendomains_spec.rb b/spec/unit/util/xendomains_spec.rb index 8649488898..b1c32e30f8 100755 --- a/spec/unit/util/xendomains_spec.rb +++ b/spec/unit/util/xendomains_spec.rb @@ -7,13 +7,13 @@ describe ".get_domains" do it "should return a list of running Xen Domains on Xen0" do xen0_domains = my_fixture_read("xendomains") - Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/xm list 2>/dev/null').returns(xen0_domains) + Facter::Core::Execution.stubs(:exec).with('/usr/sbin/xm list 2>/dev/null').returns(xen0_domains) Facter::Util::Xendomains.get_domains.should == %{web01,mailserver} end describe "when xm list isn't executable" do it "should be nil" do - Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/xm list 2>/dev/null').returns(nil) + Facter::Core::Execution.stubs(:exec).with('/usr/sbin/xm list 2>/dev/null').returns(nil) Facter::Util::Xendomains.get_domains.should == nil end end diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index 509c4be7da..a733abd7d3 100755 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -78,7 +78,7 @@ Facter.fact(:kernel).stubs(:value).returns("Linux") Facter.fact(:operatingsystem).stubs(:value).returns("Linux") - Facter::Util::Resolution.stubs(:exec).with("vmware -v").returns false + Facter::Core::Execution.stubs(:exec).with("vmware -v").returns false FileTest.stubs(:exists?).with("/proc/sys/xen").returns false FileTest.stubs(:exists?).with("/sys/bus/xen").returns false @@ -97,18 +97,18 @@ end it "should be vmware with VMware vendor name from lspci 2>/dev/null" do - Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns("00:0f.0 VGA compatible controller: VMware Inc [VMware SVGA II] PCI Display Adapter") + Facter::Core::Execution.stubs(:exec).with('lspci 2>/dev/null').returns("00:0f.0 VGA compatible controller: VMware Inc [VMware SVGA II] PCI Display Adapter") Facter.fact(:virtual).value.should == "vmware" end it "should be virtualbox with VirtualBox vendor name from lspci 2>/dev/null" do - Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns("00:02.0 VGA compatible controller: InnoTek Systemberatung GmbH VirtualBox Graphics Adapter") + Facter::Core::Execution.stubs(:exec).with('lspci 2>/dev/null').returns("00:02.0 VGA compatible controller: InnoTek Systemberatung GmbH VirtualBox Graphics Adapter") Facter.fact(:virtual).value.should == "virtualbox" end it "should be vmware with VMWare vendor name from dmidecode" do - Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) - Facter::Util::Resolution.stubs(:exec).with('dmidecode 2> /dev/null').returns("On Board Device 1 Information\nType: Video\nStatus: Disabled\nDescription: VMware SVGA II") + Facter::Core::Execution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) + Facter::Core::Execution.stubs(:exec).with('dmidecode 2> /dev/null').returns("On Board Device 1 Information\nType: Video\nStatus: Disabled\nDescription: VMware SVGA II") Facter.fact(:virtual).value.should == "vmware" end @@ -128,50 +128,50 @@ end it "should be xenhvm with Xen HVM vendor name from lspci 2>/dev/null" do - Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns("00:03.0 Unassigned class [ff80]: XenSource, Inc. Xen Platform Device (rev 01)") + Facter::Core::Execution.stubs(:exec).with('lspci 2>/dev/null').returns("00:03.0 Unassigned class [ff80]: XenSource, Inc. Xen Platform Device (rev 01)") Facter.fact(:virtual).value.should == "xenhvm" end it "should be xenhvm with Xen HVM vendor name from dmidecode" do - Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) - Facter::Util::Resolution.stubs(:exec).with('dmidecode 2> /dev/null').returns("System Information\nManufacturer: Xen\nProduct Name: HVM domU") + Facter::Core::Execution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) + Facter::Core::Execution.stubs(:exec).with('dmidecode 2> /dev/null').returns("System Information\nManufacturer: Xen\nProduct Name: HVM domU") Facter.fact(:virtual).value.should == "xenhvm" end it "should be parallels with Parallels vendor name from dmidecode" do - Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) - Facter::Util::Resolution.stubs(:exec).with('dmidecode 2> /dev/null').returns("On Board Device Information\nType: Video\nStatus: Disabled\nDescription: Parallels Video Adapter") + Facter::Core::Execution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) + Facter::Core::Execution.stubs(:exec).with('dmidecode 2> /dev/null').returns("On Board Device Information\nType: Video\nStatus: Disabled\nDescription: Parallels Video Adapter") Facter.fact(:virtual).value.should == "parallels" end it "should be virtualbox with VirtualBox vendor name from dmidecode" do - Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) - Facter::Util::Resolution.stubs(:exec).with('dmidecode 2> /dev/null').returns("BIOS Information\nVendor: innotek GmbH\nVersion: VirtualBox\n\nSystem Information\nManufacturer: innotek GmbH\nProduct Name: VirtualBox\nFamily: Virtual Machine") + Facter::Core::Execution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) + Facter::Core::Execution.stubs(:exec).with('dmidecode 2> /dev/null').returns("BIOS Information\nVendor: innotek GmbH\nVersion: VirtualBox\n\nSystem Information\nManufacturer: innotek GmbH\nProduct Name: VirtualBox\nFamily: Virtual Machine") Facter.fact(:virtual).value.should == "virtualbox" end it "should be rhev with RHEV Hypervisor product name from dmidecode" do Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) - Facter::Util::Resolution.stubs(:exec).with('dmidecode 2> /dev/null').returns("Product Name: RHEV Hypervisor") + Facter::Core::Execution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) + Facter::Core::Execution.stubs(:exec).with('dmidecode 2> /dev/null').returns("Product Name: RHEV Hypervisor") Facter.fact(:virtual).value.should == "rhev" end it "should be ovirt with oVirt Node product name from dmidecode" do Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) - Facter::Util::Resolution.stubs(:exec).with('dmidecode 2> /dev/null').returns("Product Name: oVirt Node") + Facter::Core::Execution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) + Facter::Core::Execution.stubs(:exec).with('dmidecode 2> /dev/null').returns("Product Name: oVirt Node") Facter.fact(:virtual).value.should == "ovirt" end it "should be hyperv with Microsoft vendor name from lspci 2>/dev/null" do - Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns("00:08.0 VGA compatible controller: Microsoft Corporation Hyper-V virtual VGA") + Facter::Core::Execution.stubs(:exec).with('lspci 2>/dev/null').returns("00:08.0 VGA compatible controller: Microsoft Corporation Hyper-V virtual VGA") Facter.fact(:virtual).value.should == "hyperv" end it "should be hyperv with Microsoft vendor name from dmidecode" do - Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) - Facter::Util::Resolution.stubs(:exec).with('dmidecode 2> /dev/null').returns("System Information\nManufacturer: Microsoft Corporation\nProduct Name: Virtual Machine") + Facter::Core::Execution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) + Facter::Core::Execution.stubs(:exec).with('dmidecode 2> /dev/null').returns("System Information\nManufacturer: Microsoft Corporation\nProduct Name: Virtual Machine") Facter.fact(:virtual).value.should == "hyperv" end @@ -186,7 +186,7 @@ end it "should be gce with gce vendor name from lspci 2>/dev/null" do - Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns("00:05.0 Class 8007: Google, Inc. Device 6442") + Facter::Core::Execution.stubs(:exec).with('lspci 2>/dev/null').returns("00:05.0 Class 8007: Google, Inc. Device 6442") Facter.fact(:virtual).value.should == "gce" end end @@ -207,69 +207,69 @@ end it "(#20236) is vmware when dmidecode contains vmware and lspci returns insufficient information" do - Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns("garbage\ninformation\n") - Facter::Util::Resolution.stubs(:exec).with('dmidecode 2> /dev/null').returns("On Board Device 1 Information\nType: Video\nStatus: Disabled\nDescription: VMware SVGA II") + Facter::Core::Execution.stubs(:exec).with('lspci 2>/dev/null').returns("garbage\ninformation\n") + Facter::Core::Execution.stubs(:exec).with('dmidecode 2> /dev/null').returns("On Board Device 1 Information\nType: Video\nStatus: Disabled\nDescription: VMware SVGA II") Facter.fact(:virtual).value.should eq("vmware") end end describe "on Solaris" do before(:each) do - Facter::Util::Resolution.stubs(:exec).with("vmware -v").returns false + Facter::Core::Execution.stubs(:exec).with("vmware -v").returns false Facter.fact(:kernel).stubs(:value).returns("SunOS") end it "should be vmware with VMWare vendor name from prtdiag" do Facter.fact(:hardwaremodel).stubs(:value).returns(nil) - Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) - Facter::Util::Resolution.stubs(:exec).with('dmidecode 2> /dev/null').returns(nil) - Facter::Util::Resolution.stubs(:exec).with('prtdiag').returns("System Configuration: VMware, Inc. VMware Virtual Platform") + Facter::Core::Execution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) + Facter::Core::Execution.stubs(:exec).with('dmidecode 2> /dev/null').returns(nil) + Facter::Core::Execution.stubs(:exec).with('prtdiag').returns("System Configuration: VMware, Inc. VMware Virtual Platform") Facter.fact(:virtual).value.should == "vmware" end it "should be parallels with Parallels vendor name from prtdiag" do Facter.fact(:hardwaremodel).stubs(:value).returns(nil) - Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) - Facter::Util::Resolution.stubs(:exec).with('dmidecode 2> /dev/null').returns(nil) - Facter::Util::Resolution.stubs(:exec).with('prtdiag').returns("System Configuration: Parallels Virtual Platform") + Facter::Core::Execution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) + Facter::Core::Execution.stubs(:exec).with('dmidecode 2> /dev/null').returns(nil) + Facter::Core::Execution.stubs(:exec).with('prtdiag').returns("System Configuration: Parallels Virtual Platform") Facter.fact(:virtual).value.should == "parallels" end it "should be virtualbox with VirtualBox vendor name from prtdiag" do Facter.fact(:hardwaremodel).stubs(:value).returns(nil) - Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) - Facter::Util::Resolution.stubs(:exec).with('dmidecode 2> /dev/null').returns(nil) - Facter::Util::Resolution.stubs(:exec).with('prtdiag').returns("System Configuration: innotek GmbH VirtualBox") + Facter::Core::Execution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) + Facter::Core::Execution.stubs(:exec).with('dmidecode 2> /dev/null').returns(nil) + Facter::Core::Execution.stubs(:exec).with('prtdiag').returns("System Configuration: innotek GmbH VirtualBox") Facter.fact(:virtual).value.should == "virtualbox" end end describe "on OpenBSD" do before do - Facter::Util::Resolution.stubs(:exec).with("vmware -v").returns false + Facter::Core::Execution.stubs(:exec).with("vmware -v").returns false Facter.fact(:kernel).stubs(:value).returns("OpenBSD") Facter.fact(:hardwaremodel).stubs(:value).returns(nil) - Facter::Util::Resolution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) - Facter::Util::Resolution.stubs(:exec).with('dmidecode 2> /dev/null').returns(nil) + Facter::Core::Execution.stubs(:exec).with('lspci 2>/dev/null').returns(nil) + Facter::Core::Execution.stubs(:exec).with('dmidecode 2> /dev/null').returns(nil) end it "should be parallels with Parallels product name from sysctl" do - Facter::Util::Resolution.stubs(:exec).with('sysctl -n hw.product 2>/dev/null').returns("Parallels Virtual Platform") + Facter::Core::Execution.stubs(:exec).with('sysctl -n hw.product 2>/dev/null').returns("Parallels Virtual Platform") Facter.fact(:virtual).value.should == "parallels" end it "should be vmware with VMware product name from sysctl" do - Facter::Util::Resolution.stubs(:exec).with('sysctl -n hw.product 2>/dev/null').returns("VMware Virtual Platform") + Facter::Core::Execution.stubs(:exec).with('sysctl -n hw.product 2>/dev/null').returns("VMware Virtual Platform") Facter.fact(:virtual).value.should == "vmware" end it "should be virtualbox with VirtualBox product name from sysctl" do - Facter::Util::Resolution.stubs(:exec).with('sysctl -n hw.product 2>/dev/null').returns("VirtualBox") + Facter::Core::Execution.stubs(:exec).with('sysctl -n hw.product 2>/dev/null').returns("VirtualBox") Facter.fact(:virtual).value.should == "virtualbox" end it "should be xenhvm with Xen HVM product name from sysctl" do - Facter::Util::Resolution.stubs(:exec).with('sysctl -n hw.product 2>/dev/null').returns("HVM domU") + Facter::Core::Execution.stubs(:exec).with('sysctl -n hw.product 2>/dev/null').returns("HVM domU") Facter.fact(:virtual).value.should == "xenhvm" end end diff --git a/spec/unit/zfs_version_spec.rb b/spec/unit/zfs_version_spec.rb index 2bfc01e384..039dfaf1c4 100644 --- a/spec/unit/zfs_version_spec.rb +++ b/spec/unit/zfs_version_spec.rb @@ -15,52 +15,52 @@ # Solaris 11 11/11 (ga) 33 5 before :each do - Facter::Util::Resolution.stubs(:which).with("zfs").returns("/usr/bin/zfs") + Facter::Core::Execution.stubs(:which).with("zfs").returns("/usr/bin/zfs") end it "should return correct version on Solaris 10" do - Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(my_fixture_read('solaris_10')) + Facter::Core::Execution.stubs(:exec).with("zfs upgrade -v").returns(my_fixture_read('solaris_10')) Facter.fact(:zfs_version).value.should == "3" end it "should return correct version on Solaris 11" do - Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(my_fixture_read('solaris_11')) + Facter::Core::Execution.stubs(:exec).with("zfs upgrade -v").returns(my_fixture_read('solaris_11')) Facter.fact(:zfs_version).value.should == "5" end it "should return correct version on FreeBSD 8.2" do - Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(my_fixture_read('freebsd_8.2')) + Facter::Core::Execution.stubs(:exec).with("zfs upgrade -v").returns(my_fixture_read('freebsd_8.2')) Facter.fact(:zfs_version).value.should == "4" end it "should return correct version on FreeBSD 9.0" do - Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(my_fixture_read('freebsd_9.0')) + Facter::Core::Execution.stubs(:exec).with("zfs upgrade -v").returns(my_fixture_read('freebsd_9.0')) Facter.fact(:zfs_version).value.should == "5" end it "should return correct version on Linux with ZFS-fuse" do - Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(my_fixture_read('linux-fuse_0.6.9')) + Facter::Core::Execution.stubs(:exec).with("zfs upgrade -v").returns(my_fixture_read('linux-fuse_0.6.9')) Facter.fact(:zfs_version).value.should == "4" end it "should return nil if zfs command is not available" do - Facter::Util::Resolution.stubs(:which).with("zfs").returns(nil) - Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(my_fixture_read('linux-fuse_0.6.9')) + Facter::Core::Execution.stubs(:which).with("zfs").returns(nil) + Facter::Core::Execution.stubs(:exec).with("zfs upgrade -v").returns(my_fixture_read('linux-fuse_0.6.9')) Facter.fact(:zfs_version).value.should == nil end it "should return nil if zfs fails to run" do - Facter::Util::Resolution.stubs(:exec).with("zfs upgrade -v").returns(nil) + Facter::Core::Execution.stubs(:exec).with("zfs upgrade -v").returns(nil) Facter.fact(:zfs_version).value.should == nil end it "handles the zfs command becoming available at a later point in time" do # Simulate Puppet configuring the zfs tools from a persistent daemon by # simulating three sequential responses to which('zfs'). - Facter::Util::Resolution.stubs(:which). + Facter::Core::Execution.stubs(:which). with("zfs"). returns(nil,nil,"/usr/bin/zfs") - Facter::Util::Resolution.stubs(:exec). + Facter::Core::Execution.stubs(:exec). with("zfs upgrade -v"). returns(my_fixture_read('linux-fuse_0.6.9')) diff --git a/spec/unit/zonename_spec.rb b/spec/unit/zonename_spec.rb index b0c5b31967..23a2f98199 100644 --- a/spec/unit/zonename_spec.rb +++ b/spec/unit/zonename_spec.rb @@ -7,7 +7,7 @@ it "should return global zone" do Facter.fact(:kernel).stubs(:value).returns("SunOS") - Facter::Util::Resolution.stubs(:exec).with("zonename").returns('global') + Facter::Core::Execution.stubs(:exec).with("zonename").returns('global') Facter.fact(:zonename).value.should == "global" end diff --git a/spec/unit/zones_spec.rb b/spec/unit/zones_spec.rb index 62fdf6ef29..55e3d14b2d 100644 --- a/spec/unit/zones_spec.rb +++ b/spec/unit/zones_spec.rb @@ -10,7 +10,7 @@ -:local:configured:/::native:shared -:zoneA:stopped:/::native:shared EOF - Facter::Util::Resolution.stubs(:exec).with('/usr/sbin/zoneadm list -cp').returns(zone_list) + Facter::Core::Execution.stubs(:exec).with('/usr/sbin/zoneadm list -cp').returns(zone_list) Facter.collection.internal_loader.load(:zones) end diff --git a/spec/unit/zpool_version_spec.rb b/spec/unit/zpool_version_spec.rb index fc6a952a79..169bcef3b2 100644 --- a/spec/unit/zpool_version_spec.rb +++ b/spec/unit/zpool_version_spec.rb @@ -15,52 +15,52 @@ # Solaris 11 11/11 (ga) 33 5 before :each do - Facter::Util::Resolution.stubs(:which).with("zpool").returns("/usr/bin/zpool") + Facter::Core::Execution.stubs(:which).with("zpool").returns("/usr/bin/zpool") end it "should return correct version on Solaris 10" do - Facter::Util::Resolution.stubs(:exec).with("zpool upgrade -v").returns(my_fixture_read('solaris_10')) + Facter::Core::Execution.stubs(:exec).with("zpool upgrade -v").returns(my_fixture_read('solaris_10')) Facter.fact(:zpool_version).value.should == "22" end it "should return correct version on Solaris 11" do - Facter::Util::Resolution.stubs(:exec).with("zpool upgrade -v").returns(my_fixture_read('solaris_11')) + Facter::Core::Execution.stubs(:exec).with("zpool upgrade -v").returns(my_fixture_read('solaris_11')) Facter.fact(:zpool_version).value.should == "33" end it "should return correct version on FreeBSD 8.2" do - Facter::Util::Resolution.stubs(:exec).with("zpool upgrade -v").returns(my_fixture_read('freebsd_8.2')) + Facter::Core::Execution.stubs(:exec).with("zpool upgrade -v").returns(my_fixture_read('freebsd_8.2')) Facter.fact(:zpool_version).value.should == "15" end it "should return correct version on FreeBSD 9.0" do - Facter::Util::Resolution.stubs(:exec).with("zpool upgrade -v").returns(my_fixture_read('freebsd_9.0')) + Facter::Core::Execution.stubs(:exec).with("zpool upgrade -v").returns(my_fixture_read('freebsd_9.0')) Facter.fact(:zpool_version).value.should == "28" end it "should return correct version on Linux with ZFS-fuse" do - Facter::Util::Resolution.stubs(:exec).with("zpool upgrade -v").returns(my_fixture_read('linux-fuse_0.6.9')) + Facter::Core::Execution.stubs(:exec).with("zpool upgrade -v").returns(my_fixture_read('linux-fuse_0.6.9')) Facter.fact(:zpool_version).value.should == "23" end it "should return nil if zpool is not available" do - Facter::Util::Resolution.stubs(:which).with("zpool").returns(nil) - Facter::Util::Resolution.stubs(:exec).with("zpool upgrade -v").returns(my_fixture_read('linux-fuse_0.6.9')) + Facter::Core::Execution.stubs(:which).with("zpool").returns(nil) + Facter::Core::Execution.stubs(:exec).with("zpool upgrade -v").returns(my_fixture_read('linux-fuse_0.6.9')) Facter.fact(:zpool_version).value.should == nil end it "should return nil if zpool fails to run" do - Facter::Util::Resolution.stubs(:exec).with("zpool upgrade -v").returns(nil) + Facter::Core::Execution.stubs(:exec).with("zpool upgrade -v").returns(nil) Facter.fact(:zpool_version).value.should == nil end it "handles the zpool command becoming available" do # Simulate Puppet configuring the zfs tools from a persistent daemon by # simulating three sequential responses to which('zpool'). - Facter::Util::Resolution.stubs(:which). + Facter::Core::Execution.stubs(:which). with("zpool"). returns(nil,nil,"/usr/bin/zpool") - Facter::Util::Resolution.stubs(:exec). + Facter::Core::Execution.stubs(:exec). with("zpool upgrade -v"). returns(my_fixture_read('linux-fuse_0.6.9')) From 5dc37582711de2895ddb728c70958e5bf2e5d586 Mon Sep 17 00:00:00 2001 From: Rob Braden Date: Thu, 13 Feb 2014 11:42:09 -0800 Subject: [PATCH 1640/3753] (fact-242)(packaging) Remove Fedora 18 from the default set of package builds --- ext/build_defaults.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/build_defaults.yaml b/ext/build_defaults.yaml index f2bec369d4..0e471a13f2 100644 --- a/ext/build_defaults.yaml +++ b/ext/build_defaults.yaml @@ -9,7 +9,7 @@ gpg_name: 'info@puppetlabs.com' gpg_key: '4BD6EC30' sign_tar: FALSE # a space separated list of mock configs -final_mocks: 'pl-el-5-i386 pl-el-5-x86_64 pl-el-6-i386 pl-el-6-x86_64 pl-el-7-x86_64 pl-fedora-18-i386 pl-fedora-18-x86_64 pl-fedora-19-i386 pl-fedora-19-x86_64 pl-fedora-20-i386 pl-fedora-20-x86_64' +final_mocks: 'pl-el-5-i386 pl-el-5-x86_64 pl-el-6-i386 pl-el-6-x86_64 pl-el-7-x86_64 pl-fedora-19-i386 pl-fedora-19-x86_64 pl-fedora-20-i386 pl-fedora-20-x86_64' yum_host: 'yum.puppetlabs.com' yum_repo_path: '/opt/repository/yum/' build_gem: TRUE From bae6c15ea801c13cc1a028d25bcb14f8b27dc803 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 13 Feb 2014 13:38:24 -0800 Subject: [PATCH 1641/3753] (FACT-322) Remove special casing of resolution command Resolutions could accept either a command to execute or a block to run to evaluate when resolving. However this behavior meant that special behavior had to be inserted into the execution command in case a command returned nil. This commit converts the #setcode(String) invocation to create a block that will evaluate a command and normalize the output so that Facter::Core::Execution doesn't have to be responsible for this. --- lib/facter/util/resolution.rb | 27 +++++++++----- spec/unit/util/resolution_spec.rb | 62 ++++++++++--------------------- 2 files changed, 36 insertions(+), 53 deletions(-) diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index d7564c2830..ae9ccb21d8 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -127,25 +127,32 @@ def set_options(options) # @api public def setcode(string = nil, &block) if string - @code = string - else - unless block_given? - raise ArgumentError, "You must pass either code or a block" + @code = Proc.new do + output = Facter::Core::Execution.exec(string) + if output.nil? + nil + elsif output.empty? + nil + else + output + end end + elsif block_given? @code = block + else + raise ArgumentError, "You must pass either code or a block" end end private def resolve_value - return @value if @value - return nil if @code.nil? - - if @code.is_a? Proc + if @value + @value + elsif @code.nil? + nil + elsif @code @code.call() - else - Facter::Core::Execution.exec(@code) end end end diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index 71bf16809b..fbfdaec977 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -22,12 +22,12 @@ expect(resolution.name).to eq :foo end - it "should be able to set the value" do + it "can explicitly set a value" do resolution.value = "foo" expect(resolution.value).to eq "foo" end - it "should default to nil for code" do + it "defaults to nil for code" do expect(resolution.code).to be_nil end @@ -36,76 +36,52 @@ Facter.stubs(:warnonce) end - it "should set the code to any provided string" do + it "creates a block when given a command" do resolution.setcode "foo" - expect(resolution.code).to eq "foo" + expect(resolution.code).to be_a_kind_of Proc end - it "should set the code to any provided block" do + it "stores the provided block when given a block" do block = lambda { } resolution.setcode(&block) resolution.code.should equal(block) end - it "should prefer the string over a block" do - resolution.setcode("foo") { } - expect(resolution.code).to eq "foo" + + it "prefers a command over a block" do + block = lambda { } + resolution.setcode("foo", &block) + expect(resolution.code).to_not eq block end - it "should fail if neither a string nor block has been provided" do + it "fails if neither a string nor block has been provided" do expect { resolution.setcode }.to raise_error(ArgumentError) end end describe "when returning the value" do - it "should return any value that has been provided" do + it "returns any value that has been provided" do resolution.value = "foo" expect(resolution.value).to eq "foo" end describe "and setcode has not been called" do - it "should return nil" do - Facter::Core::Execution.expects(:exec).with(nil, nil).never - resolution.value.should be_nil + it "returns nil" do + expect(resolution.value).to be_nil end end describe "and the code is a string" do - describe "on windows" do - before do - given_a_configuration_of(:is_windows => true) - end - - it "should return the result of executing the code" do - resolution.setcode "/bin/foo" - Facter::Core::Execution.expects(:exec).once.with("/bin/foo").returns "yup" + it "returns the result of executing the code" do + resolution.setcode "/bin/foo" + Facter::Core::Execution.expects(:exec).once.with("/bin/foo").returns "yup" - expect(resolution.value).to eq "yup" - end - end - - describe "on non-windows systems" do - before do - given_a_configuration_of(:is_windows => false) - end - - it "should return the result of executing the code" do - resolution.setcode "/bin/foo" - Facter::Core::Execution.expects(:exec).once.with("/bin/foo").returns "yup" - - expect(resolution.value).to eq "yup" - end + expect(resolution.value).to eq "yup" end end describe "and the code is a block" do - it "should warn but not fail if the code fails" do - resolution.setcode { raise "feh" } - Facter.expects(:warn) - resolution.value.should be_nil - end - - it "should return the value returned by the block" do + it "returns the value returned by the block" do resolution.setcode { "yayness" } expect(resolution.value).to eq "yayness" end From 41e766e5fb4a478f407c041c469f0bdf8633584b Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 13 Feb 2014 13:55:26 -0800 Subject: [PATCH 1642/3753] (FACT-322) Remove execution empty string special casing This removes the special casing for commands that return the empty string. Callers that were checking the result of .exec with a boolean check will need to call #empty? on the result. --- lib/facter/core/execution.rb | 8 ++------ lib/facter/util/resolution.rb | 8 +------- lib/facter/util/uptime.rb | 11 ++++++++--- 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/lib/facter/core/execution.rb b/lib/facter/core/execution.rb index f500a9aeba..3b99486331 100644 --- a/lib/facter/core/execution.rb +++ b/lib/facter/core/execution.rb @@ -168,7 +168,7 @@ def exec(code) return nil end - out = nil + out = '' begin out = %x{#{code}}.chomp @@ -177,11 +177,7 @@ def exec(code) return nil end - if out == "" - return nil - else - return out - end + out end end end diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index ae9ccb21d8..336e7c6711 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -129,13 +129,7 @@ def setcode(string = nil, &block) if string @code = Proc.new do output = Facter::Core::Execution.exec(string) - if output.nil? - nil - elsif output.empty? - nil - else - output - end + output.empty? ? nil : output end elsif block_given? @code = block diff --git a/lib/facter/util/uptime.rb b/lib/facter/util/uptime.rb index 8af2af2516..4df1cb0e27 100644 --- a/lib/facter/util/uptime.rb +++ b/lib/facter/util/uptime.rb @@ -20,19 +20,24 @@ def self.get_uptime_seconds_win private def self.uptime_proc_uptime - if output = Facter::Core::Execution.exec("/bin/cat #{uptime_file} 2>/dev/null") + output = Facter::Core::Execution.exec("/bin/cat #{uptime_file} 2>/dev/null") + + if not output.empty? output.chomp.split(" ").first.to_i end end def self.uptime_sysctl - if output = Facter::Core::Execution.exec("#{uptime_sysctl_cmd} 2>/dev/null") + output = Facter::Core::Execution.exec("#{uptime_sysctl_cmd} 2>/dev/null") + if not output.empty? compute_uptime(Time.at(output.match(/\d+/)[0].to_i)) end end def self.uptime_executable - if output = Facter::Core::Execution.exec("#{uptime_executable_cmd} 2>/dev/null") + output = Facter::Core::Execution.exec("#{uptime_executable_cmd} 2>/dev/null") + + if not output.empty? up=0 if output =~ /(\d+) day(?:s|\(s\))?,\s+(\d+):(\d+)/ # Regexp handles Solaris, AIX, HP-UX, and Tru64. From 40863af41562e67e85853c56c664946c8b0733c3 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 13 Feb 2014 14:50:05 -0800 Subject: [PATCH 1643/3753] (FACT-322) Remove fact special casing of empty string When facts were evaluating resolutions, return values that were either the empty string or nil would be ignored. This commit removes the special casing on the empty string and fixes up some resolutions that were relying on the behavior. --- lib/facter/domain.rb | 20 +++++++++++++------- lib/facter/util/fact.rb | 6 +----- spec/unit/util/fact_spec.rb | 6 ------ 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/lib/facter/domain.rb b/lib/facter/domain.rb index b8a4ae8a29..ac9aa686f9 100644 --- a/lib/facter/domain.rb +++ b/lib/facter/domain.rb @@ -64,8 +64,10 @@ return_value ||= domain return_value ||= search end - return_value = '' if return_value.nil? - return_value.gsub(/\.$/, '') + + if return_value + return_value.gsub(/\.$/, '') + end end end @@ -73,10 +75,13 @@ confine :kernel => :windows setcode do require 'facter/util/registry' - domain = "" + + domain = nil regvalue = Facter::Util::Registry.hklm_read('SYSTEM\CurrentControlSet\Services\Tcpip\Parameters', 'Domain') - domain = regvalue if regvalue - if domain == "" + + if regvalue and not regvalue.empty? + domain = regvalue + else require 'facter/util/wmi' Facter::Util::WMI.execquery("select DNSDomain from Win32_NetworkAdapterConfiguration where IPEnabled = True").each { |nic| if nic.DNSDomain && nic.DNSDomain.length > 0 @@ -86,8 +91,9 @@ } end - domain ||= '' - domain.gsub(/\.$/, '') + if domain + domain.gsub(/\.$/, '') + end end end diff --git a/lib/facter/util/fact.rb b/lib/facter/util/fact.rb index 587bd33dec..0224ffc091 100644 --- a/lib/facter/util/fact.rb +++ b/lib/facter/util/fact.rb @@ -158,7 +158,7 @@ def sort_by_weight(resolutions) def find_first_real_value(resolutions) resolutions.each do |resolve| - value = normalize_value(resolve.value) + value = resolve.value if not value.nil? return value end @@ -178,10 +178,6 @@ def announce_when_no_value_found(value) end end - def normalize_value(value) - value == "" ? nil : value - end - def create_or_return_resolution(resolution_name, resolution_type) resolve = self.resolution(resolution_name) diff --git a/spec/unit/util/fact_spec.rb b/spec/unit/util/fact_spec.rb index f3aebef6cb..1aa1822df4 100755 --- a/spec/unit/util/fact_spec.rb +++ b/spec/unit/util/fact_spec.rb @@ -122,12 +122,6 @@ def suitable?; false; end expect(fact.value).to eq "1" end - - it "returns nil if the value is the empty string" do - fact.add { setcode { "" } } - - expect(fact.value).to be_nil - end end describe '#flush' do From 88d23b0652228b508dd90c80846f8043afb67bf9 Mon Sep 17 00:00:00 2001 From: Matthaus Owens Date: Thu, 13 Feb 2014 16:45:30 -0800 Subject: [PATCH 1644/3753] (FACT-332) Add cfpropertylist task to facter As cfpropertylist is no longer vendored within facter, but it is still needed at runtime for facter, this commit adds a simple task to fetch, unpack, and stage cfpropertylist within the facter libdir, where it will be installed automatically in the apple package. This task is intended to be used only when building apple packages. --- tasks/cfppropertylist.rake | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 tasks/cfppropertylist.rake diff --git a/tasks/cfppropertylist.rake b/tasks/cfppropertylist.rake new file mode 100644 index 0000000000..4b2db3d118 --- /dev/null +++ b/tasks/cfppropertylist.rake @@ -0,0 +1,11 @@ +task 'cfpropertylist' do + cfp_version = "2.2.7" + libdir = File.join(Pkg::Config.project_root, "lib") + source = "https://github.com/ckruse/CFPropertyList/archive/cfpropertylist-#{cfp_version}.tar.gz" + target_dir = Pkg::Util::File.mktemp + target = File.join(target_dir, "cfpropertylist") + Pkg::Util::Net.fetch_uri(source, target) + Pkg::Util::File.untar_into(target, target_dir, "--strip-components 1") + mv(Dir.glob("#{File.join(target_dir, "lib")}/cfpropertylist*"), libdir) + mv(Dir.glob("#{target_dir}/{LICENSE,README,THANKS}"), File.join(libdir, "cfpropertylist")) +end From 1002e2aa62cfd72d952f78650594483d7519c9f3 Mon Sep 17 00:00:00 2001 From: Matthaus Owens Date: Thu, 13 Feb 2014 16:46:00 -0800 Subject: [PATCH 1645/3753] (FACT-332) Make the package:apple task depend on cfpropertylist Now that the cfpropertylist task exists, this commit defines it as a pre-task for the package:apple task. This will ensure that cfpropertylist is fetched and unpacked before making the apple package, but not before other packaging tasks, such as deb or rpm. --- ext/project_data.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ext/project_data.yaml b/ext/project_data.yaml index 792341d6db..094fca1b06 100644 --- a/ext/project_data.yaml +++ b/ext/project_data.yaml @@ -29,3 +29,5 @@ gem_platform_dependencies: bundle_platforms: universal-darwin: ruby x86-mingw32: mingw +pre_tasks: + 'package:apple': 'cfpropertylist' From 86d4f610c2a0601cc3d7e36c2a2306bbdb9f6c6a Mon Sep 17 00:00:00 2001 From: Matthaus Owens Date: Thu, 13 Feb 2014 16:46:11 -0800 Subject: [PATCH 1646/3753] (maint) Load extra tasks before loading packaging Because the apple task now depends on the cfpropertylist task, the packaging will verify that such a task exists when loaded. This means that the extra facter tasks must be loaded before we load the packaging tools. This commit moves the facter task loading to before the packaging tool loading. --- Rakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index acc0dfa09e..ed2ff2ad3b 100644 --- a/Rakefile +++ b/Rakefile @@ -7,6 +7,7 @@ require 'facter/version' $LOAD_PATH << File.join(File.dirname(__FILE__), 'tasks') require 'rake' +Dir['tasks/**/*.rake'].each { |t| load t } begin load File.join(File.dirname(__FILE__), 'ext', 'packaging', 'packaging.rake') @@ -23,7 +24,6 @@ end end end -Dir['tasks/**/*.rake'].each { |t| load t } build_defs_file = 'ext/build_defaults.yaml' if File.exist?(build_defs_file) From aa9ee28bfb0480fbb76d7d3beeea4eab2728cfbf Mon Sep 17 00:00:00 2001 From: Matthaus Owens Date: Fri, 14 Feb 2014 10:24:38 -0800 Subject: [PATCH 1647/3753] (FACT-332) Warn when the packaging tasks aren't loaded --- tasks/cfppropertylist.rake | 11 ----------- tasks/cfpropertylist.rake | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 11 deletions(-) delete mode 100644 tasks/cfppropertylist.rake create mode 100644 tasks/cfpropertylist.rake diff --git a/tasks/cfppropertylist.rake b/tasks/cfppropertylist.rake deleted file mode 100644 index 4b2db3d118..0000000000 --- a/tasks/cfppropertylist.rake +++ /dev/null @@ -1,11 +0,0 @@ -task 'cfpropertylist' do - cfp_version = "2.2.7" - libdir = File.join(Pkg::Config.project_root, "lib") - source = "https://github.com/ckruse/CFPropertyList/archive/cfpropertylist-#{cfp_version}.tar.gz" - target_dir = Pkg::Util::File.mktemp - target = File.join(target_dir, "cfpropertylist") - Pkg::Util::Net.fetch_uri(source, target) - Pkg::Util::File.untar_into(target, target_dir, "--strip-components 1") - mv(Dir.glob("#{File.join(target_dir, "lib")}/cfpropertylist*"), libdir) - mv(Dir.glob("#{target_dir}/{LICENSE,README,THANKS}"), File.join(libdir, "cfpropertylist")) -end diff --git a/tasks/cfpropertylist.rake b/tasks/cfpropertylist.rake new file mode 100644 index 0000000000..907dd8d3bf --- /dev/null +++ b/tasks/cfpropertylist.rake @@ -0,0 +1,15 @@ +task 'cfpropertylist' do + if defined? Pkg::Config and Pkg::Config.project_root + cfp_version = "2.2.7" + libdir = File.join(Pkg::Config.project_root, "lib") + source = "https://github.com/ckruse/CFPropertyList/archive/cfpropertylist-#{cfp_version}.tar.gz" + target_dir = Pkg::Util::File.mktemp + target = File.join(target_dir, "cfpropertylist") + Pkg::Util::Net.fetch_uri(source, target) + Pkg::Util::File.untar_into(target, target_dir, "--strip-components 1") + mv(Dir.glob("#{File.join(target_dir, "lib")}/cfpropertylist*"), libdir) + mv(Dir.glob("#{target_dir}/{LICENSE,README,THANKS}"), File.join(libdir, "cfpropertylist")) + else + warn "It looks like the packaging tasks have not been loaded. You'll need to `rake package:bootstrap` before using this task" + end +end From 19382e4b8d6466897db02899e594717305bdf651 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Fri, 14 Feb 2014 12:12:35 -0800 Subject: [PATCH 1648/3753] (maint) Return the empty string on exec error Commit 8a14998 removed most of the special casing around executing commands and changed calling code to always assume that .exec would return a string. However the error cases in .exec would still return nil which would break this assumption. This commit updates .exec to return the empty string on error. It's somewhat questionable to do this but if we want to fail hard on errors we should raise an exception instead of returning nil. --- lib/facter/core/execution.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/facter/core/execution.rb b/lib/facter/core/execution.rb index 3b99486331..8d5916dea8 100644 --- a/lib/facter/core/execution.rb +++ b/lib/facter/core/execution.rb @@ -165,7 +165,7 @@ def exec(code) # if we can find the binary, we'll run the command with the expanded path to the binary code = expanded_code else - return nil + return '' end out = '' @@ -174,7 +174,7 @@ def exec(code) out = %x{#{code}}.chomp rescue => detail Facter.warn(detail) - return nil + return '' end out From bc85e8ddcd75167ef185da520dddddc8ab021c60 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Fri, 14 Feb 2014 12:29:04 -0800 Subject: [PATCH 1649/3753] (doc) Update documentation for Exection.exec --- lib/facter/core/execution.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/core/execution.rb b/lib/facter/core/execution.rb index 8d5916dea8..bac2be9ee6 100644 --- a/lib/facter/core/execution.rb +++ b/lib/facter/core/execution.rb @@ -148,7 +148,7 @@ def with_env(values) # executing the code. # # @param code [String] the program to run - # @return [String, nil] the output of the program or nil + # @return [String] the output of the program or the empty string on error # # @api public # From a7f4cb6da3bb866c53b9d33b3ae8ec6430b2723c Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 10 Feb 2014 11:59:42 -0800 Subject: [PATCH 1650/3753] (FACT-327) Move Process.waitall to Execution.exec Commit d24504e8 added functionality for waiting on child processes if we aborted command execution early, but this only really applies to command execution. This commit relocates the waitall functionality to Execution.exec so it's clearer what we're waiting on and why. --- lib/facter/core/execution.rb | 20 ++++++++++++- lib/facter/core/resolvable.rb | 5 ---- spec/unit/core/execution_spec.rb | 48 +++++++++++++++++++++++++++++++ spec/unit/core/resolvable_spec.rb | 11 ------- 4 files changed, 67 insertions(+), 17 deletions(-) diff --git a/lib/facter/core/execution.rb b/lib/facter/core/execution.rb index bac2be9ee6..87b6cf6f9c 100644 --- a/lib/facter/core/execution.rb +++ b/lib/facter/core/execution.rb @@ -171,10 +171,28 @@ def exec(code) out = '' begin + wait_for_child = true out = %x{#{code}}.chomp + wait_for_child = false rescue => detail - Facter.warn(detail) + Facter.warn(detail.message) return '' + ensure + if wait_for_child + # We need to ensure that if this code exits early then any spawned + # children will be reaped. Process execution is frequently + # terminated using Timeout.timeout but since the timeout isn't in + # this scope we can't rescue the raised exception. The best that + # we can do is determine if the child has exited, and if it hasn't + # then we need to spawn a thread to wait for the child. + # + # Due to the limitations of Ruby 1.8 there aren't good ways to + # asynchronously run a command and grab the PID of that command + # using the standard library. The best we can do is blindly wait + # on all processes and hope for the best. This issue is described + # at https://tickets.puppetlabs.com/browse/FACT-150 + Thread.new { Process.waitall } + end end out diff --git a/lib/facter/core/resolvable.rb b/lib/facter/core/resolvable.rb index 84459a56b4..46bbad43e2 100644 --- a/lib/facter/core/resolvable.rb +++ b/lib/facter/core/resolvable.rb @@ -67,11 +67,6 @@ def value Facter::Util::Normalization.normalize(result) rescue Timeout::Error => detail Facter.warn "Timed out after #{limit} seconds while resolving #{qualified_name}" - - # This call avoids zombies -- basically, create a thread that will - # dezombify all of the child processes that we're ignoring because - # of the timeout. - Thread.new { Process.waitall } return nil rescue Facter::Util::Normalization::NormalizationError => e Facter.warn "Fact resolution #{qualified_name} resolved to an invalid value: #{e.message}" diff --git a/spec/unit/core/execution_spec.rb b/spec/unit/core/execution_spec.rb index c90df4875b..bf8d925678 100644 --- a/spec/unit/core/execution_spec.rb +++ b/spec/unit/core/execution_spec.rb @@ -255,4 +255,52 @@ def handy_method() end end + + describe "#exec" do + + it "switches LANG to C when executing the command" do + described_class.expects(:with_env).with('LANG' => 'C') + described_class.exec('foo') + end + + it "switches LC_ALL to C when executing the command" + + it "expands the command before running it" do + described_class.stubs(:`).returns '' + described_class.expects(:expand_command).with('foo').returns '/bin/foo' + described_class.exec('foo') + end + + it "returns an empty string when the command could not be expanded" do + described_class.expects(:expand_command).with('foo').returns nil + expect(described_class.exec('foo')).to be_empty + end + + it "logs a warning and returns an empty string when the command execution fails" do + described_class.expects(:`).with("/bin/foo").raises "kaboom!" + Facter.expects(:warn).with("kaboom!") + + described_class.expects(:expand_command).with('foo').returns '/bin/foo' + + expect(described_class.exec("foo")).to be_empty + end + + it "launches a thread to wait on children if the command was interrupted" do + described_class.expects(:`).with("/bin/foo").raises "kaboom!" + described_class.expects(:expand_command).with('foo').returns '/bin/foo' + + Facter.stubs(:warn) + Thread.expects(:new).yields + Process.expects(:waitall).once + + described_class.exec("foo") + end + + it "returns the output of the command" do + described_class.expects(:`).with("/bin/foo").returns 'hi' + described_class.expects(:expand_command).with('foo').returns '/bin/foo' + + expect(described_class.exec("foo")).to eq 'hi' + end + end end diff --git a/spec/unit/core/resolvable_spec.rb b/spec/unit/core/resolvable_spec.rb index 1b877f082a..b7a2e0c8c7 100644 --- a/spec/unit/core/resolvable_spec.rb +++ b/spec/unit/core/resolvable_spec.rb @@ -60,17 +60,6 @@ def initialize(name) expect(subject.value).to be_nil end - - - it "starts a thread to wait on all child processes if the timeout was reached" do - Thread.expects(:new).yields - Process.expects(:waitall) - - Facter.stubs(:warn) - Timeout.expects(:timeout).raises Timeout::Error - - subject.value - end end describe 'callbacks when flushing facts' do From 491660a64b993d22aca3ea86f25e0fc97f75150f Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 10 Feb 2014 12:52:34 -0800 Subject: [PATCH 1651/3753] (FACT-327) Extract command execution base class --- lib/facter/core/execution.rb | 144 ++--------- lib/facter/core/execution/base.rb | 96 ++++++++ lib/facter/core/execution/ruby18.rb | 46 ++++ spec/unit/core/execution/base_spec.rb | 257 ++++++++++++++++++++ spec/unit/core/execution/ruby18_spec.rb | 59 +++++ spec/unit/core/execution_spec.rb | 311 ++---------------------- 6 files changed, 497 insertions(+), 416 deletions(-) create mode 100644 lib/facter/core/execution/base.rb create mode 100644 lib/facter/core/execution/ruby18.rb create mode 100644 spec/unit/core/execution/base_spec.rb create mode 100644 spec/unit/core/execution/ruby18_spec.rb diff --git a/lib/facter/core/execution.rb b/lib/facter/core/execution.rb index 87b6cf6f9c..cff46fee64 100644 --- a/lib/facter/core/execution.rb +++ b/lib/facter/core/execution.rb @@ -4,6 +4,15 @@ module Facter module Core module Execution + require 'facter/core/execution/base' + require 'facter/core/execution/ruby18' + + @@impl = Facter::Core::Execution::Ruby18.new + + def self.impl + @@impl + end + module_function # Returns the locations to be searched when looking for a binary. This @@ -13,14 +22,7 @@ module Execution # @return [Array] the paths to be searched for binaries # @api private def search_paths - if Facter::Util::Config.is_windows? - ENV['PATH'].split(File::PATH_SEPARATOR) - else - # Make sure facter is usable even for non-root users. Most commands - # in /sbin (like ifconfig) can be run as non priviledged users as - # long as they do not modify anything - which we do not do with facter - ENV['PATH'].split(File::PATH_SEPARATOR) + [ '/sbin', '/usr/sbin' ] - end + @@impl.search_paths end # Determines the full path to a binary. If the supplied filename does not @@ -36,26 +38,7 @@ def search_paths # # @api public def which(bin) - if absolute_path?(bin) - return bin if File.executable?(bin) - else - search_paths.each do |dir| - dest = File.join(dir, bin) - if Facter::Util::Config.is_windows? - dest.gsub!(File::SEPARATOR, File::ALT_SEPARATOR) - if File.extname(dest).empty? - exts = ENV['PATHEXT'] - exts = exts ? exts.split(File::PATH_SEPARATOR) : %w[.COM .EXE .BAT .CMD] - exts.each do |ext| - destext = dest + ext - return destext if File.executable?(destext) - end - end - end - return dest if File.executable?(dest) - end - end - nil + @@impl.which(bin) end # Determine in a platform-specific way whether a path is absolute. This @@ -63,17 +46,8 @@ def which(bin) # # @param path [String] the path to check # @param platform [:posix,:windows,nil] the platform logic to use - def absolute_path?(path, platform=nil) - # Escape once for the string literal, and once for the regex. - slash = '[\\\\/]' - name = '[^\\\\/]+' - regexes = { - :windows => %r!^(([A-Z]:#{slash})|(#{slash}#{slash}#{name}#{slash}#{name})|(#{slash}#{slash}\?#{slash}#{name}))!i, - :posix => %r!^/!, - } - platform ||= Facter::Util::Config.is_windows? ? :windows : :posix - - !! (path =~ regexes[platform]) + def absolute_path?(path, platform = nil) + @@impl.absolute_path?(path, platform) end # Given a command line, this returns the command line with the @@ -85,22 +59,7 @@ def absolute_path?(path, platform=nil) # @return [String, nil] the command line with the executable's path # expanded, or nil if the executable cannot be found. def expand_command(command) - if match = /^"(.+?)"(?:\s+(.*))?/.match(command) - exe, arguments = match.captures - exe = which(exe) and [ "\"#{exe}\"", arguments ].compact.join(" ") - elsif match = /^'(.+?)'(?:\s+(.*))?/.match(command) and not Facter::Util::Config.is_windows? - exe, arguments = match.captures - exe = which(exe) and [ "'#{exe}'", arguments ].compact.join(" ") - else - exe, arguments = command.split(/ /,2) - if exe = which(exe) - # the binary was not quoted which means it contains no spaces. But the - # full path to the binary may do so. - exe = "\"#{exe}\"" if exe =~ /\s/ and Facter::Util::Config.is_windows? - exe = "'#{exe}'" if exe =~ /\s/ and not Facter::Util::Config.is_windows? - [ exe, arguments ].compact.join(" ") - end - end + @@impl.expand_command(command) end # Overrides environment variables within a block of code. The @@ -115,31 +74,8 @@ def expand_command(command) # @return [void] # # @api public - def with_env(values) - old = {} - values.each do |var, value| - # save the old value if it exists - if old_val = ENV[var] - old[var] = old_val - end - # set the new (temporary) value for the environment variable - ENV[var] = value - end - # execute the caller's block, capture the return value - rv = yield - # use an ensure block to make absolutely sure we restore the variables - ensure - # restore the old values - values.each do |var, value| - if old.include?(var) - ENV[var] = old[var] - else - # if there was no old value, delete the key from the current environment variables hash - ENV.delete(var) - end - end - # return the captured return value - rv + def with_env(values, &block) + @@impl.with_env(values, &block) end # Executes a program and return the output of that program. @@ -151,52 +87,8 @@ def with_env(values) # @return [String] the output of the program or the empty string on error # # @api public - # - # @note Since Facter 1.5.8 this strips trailing newlines from the - # returned value. If a fact will be used by versions of Facter older - # than 1.5.8 then you should call chomp the returned string. - def exec(code) - - ## Set LANG to force i18n to C for the duration of this exec; this ensures that any code that parses the - ## output of the command can expect it to be in a consistent / predictable format / locale - with_env "LANG" => "C" do - - if expanded_code = expand_command(code) - # if we can find the binary, we'll run the command with the expanded path to the binary - code = expanded_code - else - return '' - end - - out = '' - - begin - wait_for_child = true - out = %x{#{code}}.chomp - wait_for_child = false - rescue => detail - Facter.warn(detail.message) - return '' - ensure - if wait_for_child - # We need to ensure that if this code exits early then any spawned - # children will be reaped. Process execution is frequently - # terminated using Timeout.timeout but since the timeout isn't in - # this scope we can't rescue the raised exception. The best that - # we can do is determine if the child has exited, and if it hasn't - # then we need to spawn a thread to wait for the child. - # - # Due to the limitations of Ruby 1.8 there aren't good ways to - # asynchronously run a command and grab the PID of that command - # using the standard library. The best we can do is blindly wait - # on all processes and hope for the best. This issue is described - # at https://tickets.puppetlabs.com/browse/FACT-150 - Thread.new { Process.waitall } - end - end - - out - end + def exec(command) + @@impl.exec(command) end end end diff --git a/lib/facter/core/execution/base.rb b/lib/facter/core/execution/base.rb new file mode 100644 index 0000000000..88165fedb3 --- /dev/null +++ b/lib/facter/core/execution/base.rb @@ -0,0 +1,96 @@ +class Facter::Core::Execution::Base + + def search_paths + if Facter::Util::Config.is_windows? + ENV['PATH'].split(File::PATH_SEPARATOR) + else + # Make sure facter is usable even for non-root users. Most commands + # in /sbin (like ifconfig) can be run as non priviledged users as + # long as they do not modify anything - which we do not do with facter + ENV['PATH'].split(File::PATH_SEPARATOR) + [ '/sbin', '/usr/sbin' ] + end + end + + def which(bin) + if absolute_path?(bin) + return bin if File.executable?(bin) + else + search_paths.each do |dir| + dest = File.join(dir, bin) + if Facter::Util::Config.is_windows? + dest.gsub!(File::SEPARATOR, File::ALT_SEPARATOR) + if File.extname(dest).empty? + exts = ENV['PATHEXT'] + exts = exts ? exts.split(File::PATH_SEPARATOR) : %w[.COM .EXE .BAT .CMD] + exts.each do |ext| + destext = dest + ext + return destext if File.executable?(destext) + end + end + end + return dest if File.executable?(dest) + end + end + nil + end + + def absolute_path?(path, platform=nil) + # Escape once for the string literal, and once for the regex. + slash = '[\\\\/]' + name = '[^\\\\/]+' + regexes = { + :windows => %r!^(([A-Z]:#{slash})|(#{slash}#{slash}#{name}#{slash}#{name})|(#{slash}#{slash}\?#{slash}#{name}))!i, + :posix => %r!^/!, + } + platform ||= Facter::Util::Config.is_windows? ? :windows : :posix + + !! (path =~ regexes[platform]) + end + + def expand_command(command) + if match = /^"(.+?)"(?:\s+(.*))?/.match(command) + exe, arguments = match.captures + exe = which(exe) and [ "\"#{exe}\"", arguments ].compact.join(" ") + elsif match = /^'(.+?)'(?:\s+(.*))?/.match(command) and not Facter::Util::Config.is_windows? + exe, arguments = match.captures + exe = which(exe) and [ "'#{exe}'", arguments ].compact.join(" ") + else + exe, arguments = command.split(/ /,2) + if exe = which(exe) + # the binary was not quoted which means it contains no spaces. But the + # full path to the binary may do so. + exe = "\"#{exe}\"" if exe =~ /\s/ and Facter::Util::Config.is_windows? + exe = "'#{exe}'" if exe =~ /\s/ and not Facter::Util::Config.is_windows? + [ exe, arguments ].compact.join(" ") + end + end + end + + def with_env(values) + old = {} + values.each do |var, value| + # save the old value if it exists + if old_val = ENV[var] + old[var] = old_val + end + # set the new (temporary) value for the environment variable + ENV[var] = value + end + # execute the caller's block, capture the return value + rv = yield + # use an ensure block to make absolutely sure we restore the variables + ensure + # restore the old values + values.each do |var, value| + if old.include?(var) + ENV[var] = old[var] + else + # if there was no old value, delete the key from the current environment variables hash + ENV.delete(var) + end + end + # return the captured return value + rv + end + +end diff --git a/lib/facter/core/execution/ruby18.rb b/lib/facter/core/execution/ruby18.rb new file mode 100644 index 0000000000..55251a74a8 --- /dev/null +++ b/lib/facter/core/execution/ruby18.rb @@ -0,0 +1,46 @@ +class Facter::Core::Execution::Ruby18 < Facter::Core::Execution::Base + + def exec(code) + + ## Set LANG to force i18n to C for the duration of this exec; this ensures that any code that parses the + ## output of the command can expect it to be in a consistent / predictable format / locale + with_env "LANG" => "C" do + + if expanded_code = expand_command(code) + # if we can find the binary, we'll run the command with the expanded path to the binary + code = expanded_code + else + return '' + end + + out = '' + + begin + wait_for_child = true + out = %x{#{code}}.chomp + wait_for_child = false + rescue => detail + Facter.warn(detail.message) + return '' + ensure + if wait_for_child + # We need to ensure that if this code exits early then any spawned + # children will be reaped. Process execution is frequently + # terminated using Timeout.timeout but since the timeout isn't in + # this scope we can't rescue the raised exception. The best that + # we can do is determine if the child has exited, and if it hasn't + # then we need to spawn a thread to wait for the child. + # + # Due to the limitations of Ruby 1.8 there aren't good ways to + # asynchronously run a command and grab the PID of that command + # using the standard library. The best we can do is blindly wait + # on all processes and hope for the best. This issue is described + # at https://tickets.puppetlabs.com/browse/FACT-150 + Thread.new { Process.waitall } + end + end + + out + end + end +end diff --git a/spec/unit/core/execution/base_spec.rb b/spec/unit/core/execution/base_spec.rb new file mode 100644 index 0000000000..d7e22faa94 --- /dev/null +++ b/spec/unit/core/execution/base_spec.rb @@ -0,0 +1,257 @@ +require 'spec_helper' +require 'facter/core/execution' + +describe Facter::Core::Execution::Base do + + describe "#with_env" do + it "should execute the caller's block with the specified env vars" do + test_env = { "LANG" => "C", "FOO" => "BAR" } + subject.with_env test_env do + test_env.keys.each do |key| + ENV[key].should == test_env[key] + end + end + end + + it "should restore pre-existing environment variables to their previous values" do + orig_env = {} + new_env = {} + # an arbitrary sentinel value to use to temporarily set the environment vars to + sentinel_value = "Abracadabra" + + # grab some values from the existing ENV (arbitrarily choosing 3 here) + ENV.keys.first(3).each do |key| + # save the original values so that we can test against them later + orig_env[key] = ENV[key] + # create bogus temp values for the chosen keys + new_env[key] = sentinel_value + end + + # verify that, during the 'with_env', the new values are used + subject.with_env new_env do + orig_env.keys.each do |key| + ENV[key].should == new_env[key] + end + end + + # verify that, after the 'with_env', the old values are restored + orig_env.keys.each do |key| + ENV[key].should == orig_env[key] + end + end + + it "should not be affected by a 'return' statement in the yield block" do + @sentinel_var = :resolution_test_foo.to_s + + # the intent of this test case is to test a yield block that contains a return statement. However, it's illegal + # to use a return statement outside of a method, so we need to create one here to give scope to the 'return' + def handy_method() + ENV[@sentinel_var] = "foo" + new_env = { @sentinel_var => "bar" } + + subject.with_env new_env do + ENV[@sentinel_var].should == "bar" + return + end + end + + handy_method() + + ENV[@sentinel_var].should == "foo" + + end + end + + describe "#search_paths" do + context "on windows", :as_platform => :windows do + it "should use the PATH environment variable to determine locations" do + ENV.expects(:[]).with('PATH').returns 'C:\Windows;C:\Windows\System32' + subject.search_paths.should == %w{C:\Windows C:\Windows\System32} + end + end + + context "on posix", :as_platform => :posix do + it "should use the PATH environment variable plus /sbin and /usr/sbin on unix" do + ENV.expects(:[]).with('PATH').returns "/bin:/usr/bin" + subject.search_paths.should == %w{/bin /usr/bin /sbin /usr/sbin} + end + end + end + + describe "#which" do + context "when run on posix", :as_platform => :posix do + before :each do + subject.stubs(:search_paths).returns [ '/bin', '/sbin', '/usr/sbin'] + end + + context "and provided with an absolute path" do + it "should return the binary if executable" do + File.expects(:executable?).with('/opt/foo').returns true + subject.which('/opt/foo').should == '/opt/foo' + end + + it "should return nil if the binary is not executable" do + File.expects(:executable?).with('/opt/foo').returns false + subject.which('/opt/foo').should be_nil + end + end + + context "and not provided with an absolute path" do + it "should return the absolute path if found" do + File.expects(:executable?).with('/bin/foo').returns false + File.expects(:executable?).with('/sbin/foo').returns true + File.expects(:executable?).with('/usr/sbin/foo').never + subject.which('foo').should == '/sbin/foo' + end + + it "should return nil if not found" do + File.expects(:executable?).with('/bin/foo').returns false + File.expects(:executable?).with('/sbin/foo').returns false + File.expects(:executable?).with('/usr/sbin/foo').returns false + subject.which('foo').should be_nil + end + end + end + + context "when run on windows", :as_platform => :windows do + before :each do + subject.stubs(:search_paths).returns ['C:\Windows\system32', 'C:\Windows', 'C:\Windows\System32\Wbem' ] + ENV.stubs(:[]).with('PATHEXT').returns nil + end + + context "and provided with an absolute path" do + it "should return the binary if executable" do + File.expects(:executable?).with('C:\Tools\foo.exe').returns true + File.expects(:executable?).with('\\\\remote\dir\foo.exe').returns true + subject.which('C:\Tools\foo.exe').should == 'C:\Tools\foo.exe' + subject.which('\\\\remote\dir\foo.exe').should == '\\\\remote\dir\foo.exe' + end + + it "should return nil if the binary is not executable" do + File.expects(:executable?).with('C:\Tools\foo.exe').returns false + File.expects(:executable?).with('\\\\remote\dir\foo.exe').returns false + subject.which('C:\Tools\foo.exe').should be_nil + subject.which('\\\\remote\dir\foo.exe').should be_nil + end + end + + context "and not provided with an absolute path" do + it "should return the absolute path if found" do + File.expects(:executable?).with('C:\Windows\system32\foo.exe').returns false + File.expects(:executable?).with('C:\Windows\foo.exe').returns true + File.expects(:executable?).with('C:\Windows\System32\Wbem\foo.exe').never + subject.which('foo.exe').should == 'C:\Windows\foo.exe' + end + + it "should return the absolute path with file extension if found" do + ['.COM', '.EXE', '.BAT', '.CMD', '' ].each do |ext| + File.stubs(:executable?).with('C:\Windows\system32\foo'+ext).returns false + File.stubs(:executable?).with('C:\Windows\System32\Wbem\foo'+ext).returns false + end + ['.COM', '.BAT', '.CMD', '' ].each do |ext| + File.stubs(:executable?).with('C:\Windows\foo'+ext).returns false + end + File.stubs(:executable?).with('C:\Windows\foo.EXE').returns true + + subject.which('foo').should == 'C:\Windows\foo.EXE' + end + + it "should return nil if not found" do + File.expects(:executable?).with('C:\Windows\system32\foo.exe').returns false + File.expects(:executable?).with('C:\Windows\foo.exe').returns false + File.expects(:executable?).with('C:\Windows\System32\Wbem\foo.exe').returns false + subject.which('foo.exe').should be_nil + end + end + end + + describe "#expand_command" do + context "on windows", :as_platform => :windows do + it "should expand binary" do + subject.expects(:which).with('cmd').returns 'C:\Windows\System32\cmd' + subject.expand_command( + 'cmd /c echo foo > C:\bar' + ).should == 'C:\Windows\System32\cmd /c echo foo > C:\bar' + end + + it "should expand double quoted binary" do + subject.expects(:which).with('my foo').returns 'C:\My Tools\my foo.exe' + subject.expand_command('"my foo" /a /b').should == '"C:\My Tools\my foo.exe" /a /b' + end + + it "should not expand single quoted binary" do + subject.expects(:which).with('\'C:\My').returns nil + subject.expand_command('\'C:\My Tools\foo.exe\' /a /b').should be_nil + end + + it "should quote expanded binary if found in path with spaces" do + subject.expects(:which).with('foo').returns 'C:\My Tools\foo.exe' + subject.expand_command('foo /a /b').should == '"C:\My Tools\foo.exe" /a /b' + end + + it "should return nil if not found" do + subject.expects(:which).with('foo').returns nil + subject.expand_command('foo /a | stuff >> /dev/null').should be_nil + end + end + + context "on unix", :as_platform => :posix do + it "should expand binary" do + subject.expects(:which).with('foo').returns '/bin/foo' + subject.expand_command('foo -a | stuff >> /dev/null').should == '/bin/foo -a | stuff >> /dev/null' + end + + it "should expand double quoted binary" do + subject.expects(:which).with('/tmp/my foo').returns '/tmp/my foo' + subject.expand_command(%q{"/tmp/my foo" bar}).should == %q{"/tmp/my foo" bar} + end + + it "should expand single quoted binary" do + subject.expects(:which).with('my foo').returns '/home/bob/my path/my foo' + subject.expand_command(%q{'my foo' -a}).should == %q{'/home/bob/my path/my foo' -a} + end + + it "should quote expanded binary if found in path with spaces" do + subject.expects(:which).with('foo.sh').returns '/home/bob/my tools/foo.sh' + subject.expand_command('foo.sh /a /b').should == %q{'/home/bob/my tools/foo.sh' /a /b} + end + + it "should return nil if not found" do + subject.expects(:which).with('foo').returns nil + subject.expand_command('foo -a | stuff >> /dev/null').should be_nil + end + end + end + + end + + describe "#absolute_path?" do + context "when run on unix", :as_platform => :posix do + %w[/ /foo /foo/../bar //foo //Server/Foo/Bar //?/C:/foo/bar /\Server/Foo /foo//bar/baz].each do |path| + it "should return true for #{path}" do + subject.should be_absolute_path(path) + end + end + + %w[. ./foo \foo C:/foo \\Server\Foo\Bar \\?\C:\foo\bar \/?/foo\bar \/Server/foo foo//bar/baz].each do |path| + it "should return false for #{path}" do + subject.should_not be_absolute_path(path) + end + end + end + + context "when run on windows", :as_platform => :windows do + %w[C:/foo C:\foo \\\\Server\Foo\Bar \\\\?\C:\foo\bar //Server/Foo/Bar //?/C:/foo/bar /\?\C:/foo\bar \/Server\Foo/Bar c:/foo//bar//baz].each do |path| + it "should return true for #{path}" do + subject.should be_absolute_path(path) + end + end + + %w[/ . ./foo \foo /foo /foo/../bar //foo C:foo/bar foo//bar/baz].each do |path| + it "should return false for #{path}" do + subject.should_not be_absolute_path(path) + end + end + end + end +end diff --git a/spec/unit/core/execution/ruby18_spec.rb b/spec/unit/core/execution/ruby18_spec.rb new file mode 100644 index 0000000000..10c0c1ac81 --- /dev/null +++ b/spec/unit/core/execution/ruby18_spec.rb @@ -0,0 +1,59 @@ +require 'spec_helper' + +describe Facter::Core::Execution::Ruby18 do + + describe "#exec" do + + it "switches LANG to C when executing the command" do + subject.expects(:with_env).with('LANG' => 'C') + subject.exec('foo') + end + + it "switches LC_ALL to C when executing the command" + + it "expands the command before running it" do + subject.stubs(:`).returns '' + subject.expects(:expand_command).with('foo').returns '/bin/foo' + subject.exec('foo') + end + + it "returns an empty string when the command could not be expanded" do + subject.expects(:expand_command).with('foo').returns nil + expect(subject.exec('foo')).to be_empty + end + + it "logs a warning and returns an empty string when the command execution fails" do + subject.expects(:`).with("/bin/foo").raises "kaboom!" + Facter.expects(:warn).with("kaboom!") + + subject.expects(:expand_command).with('foo').returns '/bin/foo' + + expect(subject.exec("foo")).to be_empty + end + + it "launches a thread to wait on children if the command was interrupted" do + subject.expects(:`).with("/bin/foo").raises "kaboom!" + subject.expects(:expand_command).with('foo').returns '/bin/foo' + + Facter.stubs(:warn) + Thread.expects(:new).yields + Process.expects(:waitall).once + + subject.exec("foo") + end + + it "returns the output of the command" do + subject.expects(:`).with("/bin/foo").returns 'hi' + subject.expects(:expand_command).with('foo').returns '/bin/foo' + + expect(subject.exec("foo")).to eq 'hi' + end + + it "strips off trailing newlines" do + subject.expects(:`).with("/bin/foo").returns "hi\n" + subject.expects(:expand_command).with('foo').returns '/bin/foo' + + expect(subject.exec("foo")).to eq 'hi' + end + end +end diff --git a/spec/unit/core/execution_spec.rb b/spec/unit/core/execution_spec.rb index bf8d925678..c36358f3ec 100644 --- a/spec/unit/core/execution_spec.rb +++ b/spec/unit/core/execution_spec.rb @@ -2,305 +2,36 @@ require 'facter/core/execution' describe Facter::Core::Execution do + subject { described_class} + let(:impl) { described_class.impl } - describe "#with_env" do - it "should execute the caller's block with the specified env vars" do - test_env = { "LANG" => "C", "FOO" => "BAR" } - Facter::Core::Execution.with_env test_env do - test_env.keys.each do |key| - ENV[key].should == test_env[key] - end - end - end - - it "should restore pre-existing environment variables to their previous values" do - orig_env = {} - new_env = {} - # an arbitrary sentinel value to use to temporarily set the environment vars to - sentinel_value = "Abracadabra" - - # grab some values from the existing ENV (arbitrarily choosing 3 here) - ENV.keys.first(3).each do |key| - # save the original values so that we can test against them later - orig_env[key] = ENV[key] - # create bogus temp values for the chosen keys - new_env[key] = sentinel_value - end - - # verify that, during the 'with_env', the new values are used - Facter::Util::Resolution.with_env new_env do - orig_env.keys.each do |key| - ENV[key].should == new_env[key] - end - end - - # verify that, after the 'with_env', the old values are restored - orig_env.keys.each do |key| - ENV[key].should == orig_env[key] - end - end - - it "should not be affected by a 'return' statement in the yield block" do - @sentinel_var = :resolution_test_foo.to_s - - # the intent of this test case is to test a yield block that contains a return statement. However, it's illegal - # to use a return statement outside of a method, so we need to create one here to give scope to the 'return' - def handy_method() - ENV[@sentinel_var] = "foo" - new_env = { @sentinel_var => "bar" } - - Facter::Util::Resolution.with_env new_env do - ENV[@sentinel_var].should == "bar" - return - end - end - - handy_method() - - ENV[@sentinel_var].should == "foo" - - end + it "delegates #search_paths to the implementation" do + impl.expects(:search_paths) + subject.search_paths end - describe "#search_paths" do - context "on windows", :as_platform => :windows do - it "should use the PATH environment variable to determine locations" do - ENV.expects(:[]).with('PATH').returns 'C:\Windows;C:\Windows\System32' - described_class.search_paths.should == %w{C:\Windows C:\Windows\System32} - end - end - - context "on posix", :as_platform => :posix do - it "should use the PATH environment variable plus /sbin and /usr/sbin on unix" do - ENV.expects(:[]).with('PATH').returns "/bin:/usr/bin" - described_class.search_paths.should == %w{/bin /usr/bin /sbin /usr/sbin} - end - end + it "delegates #which to the implementation" do + impl.expects(:which).with('waffles') + subject.which('waffles') end - describe "#which" do - context "when run on posix", :as_platform => :posix do - before :each do - described_class.stubs(:search_paths).returns [ '/bin', '/sbin', '/usr/sbin'] - end - - context "and provided with an absolute path" do - it "should return the binary if executable" do - File.expects(:executable?).with('/opt/foo').returns true - described_class.which('/opt/foo').should == '/opt/foo' - end - - it "should return nil if the binary is not executable" do - File.expects(:executable?).with('/opt/foo').returns false - described_class.which('/opt/foo').should be_nil - end - end - - context "and not provided with an absolute path" do - it "should return the absolute path if found" do - File.expects(:executable?).with('/bin/foo').returns false - File.expects(:executable?).with('/sbin/foo').returns true - File.expects(:executable?).with('/usr/sbin/foo').never - described_class.which('foo').should == '/sbin/foo' - end - - it "should return nil if not found" do - File.expects(:executable?).with('/bin/foo').returns false - File.expects(:executable?).with('/sbin/foo').returns false - File.expects(:executable?).with('/usr/sbin/foo').returns false - described_class.which('foo').should be_nil - end - end - end - - context "when run on windows", :as_platform => :windows do - before :each do - described_class.stubs(:search_paths).returns ['C:\Windows\system32', 'C:\Windows', 'C:\Windows\System32\Wbem' ] - ENV.stubs(:[]).with('PATHEXT').returns nil - end - - context "and provided with an absolute path" do - it "should return the binary if executable" do - File.expects(:executable?).with('C:\Tools\foo.exe').returns true - File.expects(:executable?).with('\\\\remote\dir\foo.exe').returns true - described_class.which('C:\Tools\foo.exe').should == 'C:\Tools\foo.exe' - described_class.which('\\\\remote\dir\foo.exe').should == '\\\\remote\dir\foo.exe' - end - - it "should return nil if the binary is not executable" do - File.expects(:executable?).with('C:\Tools\foo.exe').returns false - File.expects(:executable?).with('\\\\remote\dir\foo.exe').returns false - described_class.which('C:\Tools\foo.exe').should be_nil - described_class.which('\\\\remote\dir\foo.exe').should be_nil - end - end - - context "and not provided with an absolute path" do - it "should return the absolute path if found" do - File.expects(:executable?).with('C:\Windows\system32\foo.exe').returns false - File.expects(:executable?).with('C:\Windows\foo.exe').returns true - File.expects(:executable?).with('C:\Windows\System32\Wbem\foo.exe').never - described_class.which('foo.exe').should == 'C:\Windows\foo.exe' - end - - it "should return the absolute path with file extension if found" do - ['.COM', '.EXE', '.BAT', '.CMD', '' ].each do |ext| - File.stubs(:executable?).with('C:\Windows\system32\foo'+ext).returns false - File.stubs(:executable?).with('C:\Windows\System32\Wbem\foo'+ext).returns false - end - ['.COM', '.BAT', '.CMD', '' ].each do |ext| - File.stubs(:executable?).with('C:\Windows\foo'+ext).returns false - end - File.stubs(:executable?).with('C:\Windows\foo.EXE').returns true - - described_class.which('foo').should == 'C:\Windows\foo.EXE' - end - - it "should return nil if not found" do - File.expects(:executable?).with('C:\Windows\system32\foo.exe').returns false - File.expects(:executable?).with('C:\Windows\foo.exe').returns false - File.expects(:executable?).with('C:\Windows\System32\Wbem\foo.exe').returns false - described_class.which('foo.exe').should be_nil - end - end - end - - describe "#expand_command" do - context "on windows", :as_platform => :windows do - it "should expand binary" do - described_class.expects(:which).with('cmd').returns 'C:\Windows\System32\cmd' - described_class.expand_command( - 'cmd /c echo foo > C:\bar' - ).should == 'C:\Windows\System32\cmd /c echo foo > C:\bar' - end - - it "should expand double quoted binary" do - described_class.expects(:which).with('my foo').returns 'C:\My Tools\my foo.exe' - described_class.expand_command('"my foo" /a /b').should == '"C:\My Tools\my foo.exe" /a /b' - end - - it "should not expand single quoted binary" do - described_class.expects(:which).with('\'C:\My').returns nil - described_class.expand_command('\'C:\My Tools\foo.exe\' /a /b').should be_nil - end - - it "should quote expanded binary if found in path with spaces" do - described_class.expects(:which).with('foo').returns 'C:\My Tools\foo.exe' - described_class.expand_command('foo /a /b').should == '"C:\My Tools\foo.exe" /a /b' - end - - it "should return nil if not found" do - described_class.expects(:which).with('foo').returns nil - described_class.expand_command('foo /a | stuff >> /dev/null').should be_nil - end - end - - context "on unix", :as_platform => :posix do - it "should expand binary" do - described_class.expects(:which).with('foo').returns '/bin/foo' - described_class.expand_command('foo -a | stuff >> /dev/null').should == '/bin/foo -a | stuff >> /dev/null' - end - - it "should expand double quoted binary" do - described_class.expects(:which).with('/tmp/my foo').returns '/tmp/my foo' - described_class.expand_command(%q{"/tmp/my foo" bar}).should == %q{"/tmp/my foo" bar} - end - - it "should expand single quoted binary" do - described_class.expects(:which).with('my foo').returns '/home/bob/my path/my foo' - described_class.expand_command(%q{'my foo' -a}).should == %q{'/home/bob/my path/my foo' -a} - end - - it "should quote expanded binary if found in path with spaces" do - described_class.expects(:which).with('foo.sh').returns '/home/bob/my tools/foo.sh' - described_class.expand_command('foo.sh /a /b').should == %q{'/home/bob/my tools/foo.sh' /a /b} - end - - it "should return nil if not found" do - described_class.expects(:which).with('foo').returns nil - described_class.expand_command('foo -a | stuff >> /dev/null').should be_nil - end - end - end - + it "delegates #absolute_path? to the implementation" do + impl.expects(:absolute_path?).with('waffles', nil) + subject.absolute_path?('waffles') end - describe "#absolute_path?" do - context "when run on unix", :as_platform => :posix do - %w[/ /foo /foo/../bar //foo //Server/Foo/Bar //?/C:/foo/bar /\Server/Foo /foo//bar/baz].each do |path| - it "should return true for #{path}" do - described_class.should be_absolute_path(path) - end - end - - %w[. ./foo \foo C:/foo \\Server\Foo\Bar \\?\C:\foo\bar \/?/foo\bar \/Server/foo foo//bar/baz].each do |path| - it "should return false for #{path}" do - described_class.should_not be_absolute_path(path) - end - end - end - - context "when run on windows", :as_platform => :windows do - %w[C:/foo C:\foo \\\\Server\Foo\Bar \\\\?\C:\foo\bar //Server/Foo/Bar //?/C:/foo/bar /\?\C:/foo\bar \/Server\Foo/Bar c:/foo//bar//baz].each do |path| - it "should return true for #{path}" do - described_class.should be_absolute_path(path) - end - end - - %w[/ . ./foo \foo /foo /foo/../bar //foo C:foo/bar foo//bar/baz].each do |path| - it "should return false for #{path}" do - described_class.should_not be_absolute_path(path) - end - end - end + it "delegates #absolute_path? with an optional platform to the implementation" do + impl.expects(:absolute_path?).with('waffles', :windows) + subject.absolute_path?('waffles', :windows) end + it "delegates #expand_command to the implementation" do + impl.expects(:expand_command).with('waffles') + subject.expand_command('waffles') + end - describe "#exec" do - - it "switches LANG to C when executing the command" do - described_class.expects(:with_env).with('LANG' => 'C') - described_class.exec('foo') - end - - it "switches LC_ALL to C when executing the command" - - it "expands the command before running it" do - described_class.stubs(:`).returns '' - described_class.expects(:expand_command).with('foo').returns '/bin/foo' - described_class.exec('foo') - end - - it "returns an empty string when the command could not be expanded" do - described_class.expects(:expand_command).with('foo').returns nil - expect(described_class.exec('foo')).to be_empty - end - - it "logs a warning and returns an empty string when the command execution fails" do - described_class.expects(:`).with("/bin/foo").raises "kaboom!" - Facter.expects(:warn).with("kaboom!") - - described_class.expects(:expand_command).with('foo').returns '/bin/foo' - - expect(described_class.exec("foo")).to be_empty - end - - it "launches a thread to wait on children if the command was interrupted" do - described_class.expects(:`).with("/bin/foo").raises "kaboom!" - described_class.expects(:expand_command).with('foo').returns '/bin/foo' - - Facter.stubs(:warn) - Thread.expects(:new).yields - Process.expects(:waitall).once - - described_class.exec("foo") - end - - it "returns the output of the command" do - described_class.expects(:`).with("/bin/foo").returns 'hi' - described_class.expects(:expand_command).with('foo').returns '/bin/foo' - - expect(described_class.exec("foo")).to eq 'hi' - end + it "delegates #exec to the implementation" do + impl.expects(:exec).with('waffles') + subject.exec('waffles') end end From 74840eb1291a71cc6df4c8d7c41ddb26a3c17005 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Wed, 12 Feb 2014 19:58:58 -0800 Subject: [PATCH 1652/3753] (FACT-327) Extract windows/posix execution classes --- lib/facter/core/execution.rb | 11 +- lib/facter/core/execution/base.rb | 109 +++++------- lib/facter/core/execution/posix.rb | 50 ++++++ lib/facter/core/execution/ruby18.rb | 46 ----- lib/facter/core/execution/windows.rb | 57 +++++++ spec/unit/core/execution/base_spec.rb | 204 ++++------------------- spec/unit/core/execution/posix_spec.rb | 86 ++++++++++ spec/unit/core/execution/ruby18_spec.rb | 59 ------- spec/unit/core/execution/windows_spec.rb | 106 ++++++++++++ 9 files changed, 383 insertions(+), 345 deletions(-) create mode 100644 lib/facter/core/execution/posix.rb delete mode 100644 lib/facter/core/execution/ruby18.rb create mode 100644 lib/facter/core/execution/windows.rb create mode 100644 spec/unit/core/execution/posix_spec.rb delete mode 100644 spec/unit/core/execution/ruby18_spec.rb create mode 100644 spec/unit/core/execution/windows_spec.rb diff --git a/lib/facter/core/execution.rb b/lib/facter/core/execution.rb index cff46fee64..353d0fa6bc 100644 --- a/lib/facter/core/execution.rb +++ b/lib/facter/core/execution.rb @@ -5,9 +5,14 @@ module Core module Execution require 'facter/core/execution/base' - require 'facter/core/execution/ruby18' + require 'facter/core/execution/windows' + require 'facter/core/execution/posix' - @@impl = Facter::Core::Execution::Ruby18.new + @@impl = if Facter::Util::Config.is_windows? + Facter::Core::Execution::Windows.new + else + Facter::Core::Execution::Posix.new + end def self.impl @@impl @@ -52,7 +57,7 @@ def absolute_path?(path, platform = nil) # Given a command line, this returns the command line with the # executable written as an absolute path. If the executable contains - # spaces, it has be but in double quotes to be properly recognized. + # spaces, it has be put in double quotes to be properly recognized. # # @param command [String] the command line # diff --git a/lib/facter/core/execution/base.rb b/lib/facter/core/execution/base.rb index 88165fedb3..cdeda13c27 100644 --- a/lib/facter/core/execution/base.rb +++ b/lib/facter/core/execution/base.rb @@ -1,71 +1,5 @@ class Facter::Core::Execution::Base - def search_paths - if Facter::Util::Config.is_windows? - ENV['PATH'].split(File::PATH_SEPARATOR) - else - # Make sure facter is usable even for non-root users. Most commands - # in /sbin (like ifconfig) can be run as non priviledged users as - # long as they do not modify anything - which we do not do with facter - ENV['PATH'].split(File::PATH_SEPARATOR) + [ '/sbin', '/usr/sbin' ] - end - end - - def which(bin) - if absolute_path?(bin) - return bin if File.executable?(bin) - else - search_paths.each do |dir| - dest = File.join(dir, bin) - if Facter::Util::Config.is_windows? - dest.gsub!(File::SEPARATOR, File::ALT_SEPARATOR) - if File.extname(dest).empty? - exts = ENV['PATHEXT'] - exts = exts ? exts.split(File::PATH_SEPARATOR) : %w[.COM .EXE .BAT .CMD] - exts.each do |ext| - destext = dest + ext - return destext if File.executable?(destext) - end - end - end - return dest if File.executable?(dest) - end - end - nil - end - - def absolute_path?(path, platform=nil) - # Escape once for the string literal, and once for the regex. - slash = '[\\\\/]' - name = '[^\\\\/]+' - regexes = { - :windows => %r!^(([A-Z]:#{slash})|(#{slash}#{slash}#{name}#{slash}#{name})|(#{slash}#{slash}\?#{slash}#{name}))!i, - :posix => %r!^/!, - } - platform ||= Facter::Util::Config.is_windows? ? :windows : :posix - - !! (path =~ regexes[platform]) - end - - def expand_command(command) - if match = /^"(.+?)"(?:\s+(.*))?/.match(command) - exe, arguments = match.captures - exe = which(exe) and [ "\"#{exe}\"", arguments ].compact.join(" ") - elsif match = /^'(.+?)'(?:\s+(.*))?/.match(command) and not Facter::Util::Config.is_windows? - exe, arguments = match.captures - exe = which(exe) and [ "'#{exe}'", arguments ].compact.join(" ") - else - exe, arguments = command.split(/ /,2) - if exe = which(exe) - # the binary was not quoted which means it contains no spaces. But the - # full path to the binary may do so. - exe = "\"#{exe}\"" if exe =~ /\s/ and Facter::Util::Config.is_windows? - exe = "'#{exe}'" if exe =~ /\s/ and not Facter::Util::Config.is_windows? - [ exe, arguments ].compact.join(" ") - end - end - end - def with_env(values) old = {} values.each do |var, value| @@ -93,4 +27,47 @@ def with_env(values) rv end + def exec(code) + + ## Set LANG to force i18n to C for the duration of this exec; this ensures that any code that parses the + ## output of the command can expect it to be in a consistent / predictable format / locale + with_env "LANG" => "C" do + + if expanded_code = expand_command(code) + # if we can find the binary, we'll run the command with the expanded path to the binary + code = expanded_code + else + return '' + end + + out = '' + + begin + wait_for_child = true + out = %x{#{code}}.chomp + wait_for_child = false + rescue => detail + Facter.warn(detail.message) + return '' + ensure + if wait_for_child + # We need to ensure that if this code exits early then any spawned + # children will be reaped. Process execution is frequently + # terminated using Timeout.timeout but since the timeout isn't in + # this scope we can't rescue the raised exception. The best that + # we can do is determine if the child has exited, and if it hasn't + # then we need to spawn a thread to wait for the child. + # + # Due to the limitations of Ruby 1.8 there aren't good ways to + # asynchronously run a command and grab the PID of that command + # using the standard library. The best we can do is blindly wait + # on all processes and hope for the best. This issue is described + # at https://tickets.puppetlabs.com/browse/FACT-150 + Thread.new { Process.waitall } + end + end + + out + end + end end diff --git a/lib/facter/core/execution/posix.rb b/lib/facter/core/execution/posix.rb new file mode 100644 index 0000000000..f710ff9d89 --- /dev/null +++ b/lib/facter/core/execution/posix.rb @@ -0,0 +1,50 @@ +class Facter::Core::Execution::Posix < Facter::Core::Execution::Base + + DEFAULT_SEARCH_PATHS = ['/sbin', '/usr/sbin'] + + def search_paths + # Make sure facter is usable even for non-root users. Most commands + # in /sbin (like ifconfig) can be run as non privileged users as + # long as they do not modify anything - which we do not do with facter + ENV['PATH'].split(File::PATH_SEPARATOR) + DEFAULT_SEARCH_PATHS + end + + def which(bin) + if absolute_path?(bin) + return bin if File.executable?(bin) + else + search_paths.each do |dir| + dest = File.join(dir, bin) + return dest if File.executable?(dest) + end + end + nil + end + + ABSOLUTE_PATH_REGEX = %r{^/} + + def absolute_path?(path) + !! (path =~ ABSOLUTE_PATH_REGEX) + end + + DOUBLE_QUOTED_COMMAND = /^"(.+?)"(?:\s+(.*))?/ + SINGLE_QUOTED_COMMAND = /^'(.+?)'(?:\s+(.*))?/ + + def expand_command(command) + exe = nil + args = nil + + if (match = (command.match(DOUBLE_QUOTED_COMMAND) || command.match(SINGLE_QUOTED_COMMAND))) + exe, args = match.captures + else + exe, args = command.split(/ /,2) + end + + if exe and (expanded = which(exe)) + expanded = "'#{expanded}'" if expanded.match(/\s/) + expanded << " #{args}" if args + + return expanded + end + end +end diff --git a/lib/facter/core/execution/ruby18.rb b/lib/facter/core/execution/ruby18.rb deleted file mode 100644 index 55251a74a8..0000000000 --- a/lib/facter/core/execution/ruby18.rb +++ /dev/null @@ -1,46 +0,0 @@ -class Facter::Core::Execution::Ruby18 < Facter::Core::Execution::Base - - def exec(code) - - ## Set LANG to force i18n to C for the duration of this exec; this ensures that any code that parses the - ## output of the command can expect it to be in a consistent / predictable format / locale - with_env "LANG" => "C" do - - if expanded_code = expand_command(code) - # if we can find the binary, we'll run the command with the expanded path to the binary - code = expanded_code - else - return '' - end - - out = '' - - begin - wait_for_child = true - out = %x{#{code}}.chomp - wait_for_child = false - rescue => detail - Facter.warn(detail.message) - return '' - ensure - if wait_for_child - # We need to ensure that if this code exits early then any spawned - # children will be reaped. Process execution is frequently - # terminated using Timeout.timeout but since the timeout isn't in - # this scope we can't rescue the raised exception. The best that - # we can do is determine if the child has exited, and if it hasn't - # then we need to spawn a thread to wait for the child. - # - # Due to the limitations of Ruby 1.8 there aren't good ways to - # asynchronously run a command and grab the PID of that command - # using the standard library. The best we can do is blindly wait - # on all processes and hope for the best. This issue is described - # at https://tickets.puppetlabs.com/browse/FACT-150 - Thread.new { Process.waitall } - end - end - - out - end - end -end diff --git a/lib/facter/core/execution/windows.rb b/lib/facter/core/execution/windows.rb new file mode 100644 index 0000000000..1fa3ba7610 --- /dev/null +++ b/lib/facter/core/execution/windows.rb @@ -0,0 +1,57 @@ +class Facter::Core::Execution::Windows < Facter::Core::Execution::Base + + def search_paths + ENV['PATH'].split(File::PATH_SEPARATOR) + end + + DEFAULT_COMMAND_EXTENSIONS = %w[.COM .EXE .BAT .CMD] + + def which(bin) + if absolute_path?(bin) + return bin if File.executable?(bin) + else + search_paths.each do |dir| + dest = File.join(dir, bin) + dest.gsub!(File::SEPARATOR, File::ALT_SEPARATOR) + if File.extname(dest).empty? + exts = ENV['PATHEXT'] + exts = exts ? exts.split(File::PATH_SEPARATOR) : DEFAULT_COMMAND_EXTENSIONS + exts.each do |ext| + destext = dest + ext + return destext if File.executable?(destext) + end + end + return dest if File.executable?(dest) + end + end + nil + end + + slash = '[\\\\/]' + name = '[^\\\\/]+' + ABSOLUTE_PATH_REGEX = %r!^(([A-Z]:#{slash})|(#{slash}#{slash}#{name}#{slash}#{name})|(#{slash}#{slash}\?#{slash}#{name}))!i + + def absolute_path?(path) + !! (path =~ ABSOLUTE_PATH_REGEX) + end + + DOUBLE_QUOTED_COMMAND = /^"(.+?)"(?:\s+(.*))?/ + + def expand_command(command) + exe = nil + args = nil + + if (match = command.match(DOUBLE_QUOTED_COMMAND)) + exe, args = match.captures + else + exe, args = command.split(/ /,2) + end + + if exe and (expanded = which(exe)) + expanded = "\"#{expanded}\"" if expanded.match(/\s+/) + expanded << " #{args}" if args + + return expanded + end + end +end diff --git a/spec/unit/core/execution/base_spec.rb b/spec/unit/core/execution/base_spec.rb index d7e22faa94..652f392dd6 100644 --- a/spec/unit/core/execution/base_spec.rb +++ b/spec/unit/core/execution/base_spec.rb @@ -62,196 +62,58 @@ def handy_method() end end - describe "#search_paths" do - context "on windows", :as_platform => :windows do - it "should use the PATH environment variable to determine locations" do - ENV.expects(:[]).with('PATH').returns 'C:\Windows;C:\Windows\System32' - subject.search_paths.should == %w{C:\Windows C:\Windows\System32} - end - end + describe "#exec" do - context "on posix", :as_platform => :posix do - it "should use the PATH environment variable plus /sbin and /usr/sbin on unix" do - ENV.expects(:[]).with('PATH').returns "/bin:/usr/bin" - subject.search_paths.should == %w{/bin /usr/bin /sbin /usr/sbin} - end + it "switches LANG to C when executing the command" do + subject.expects(:with_env).with('LANG' => 'C') + subject.exec('foo') end - end - - describe "#which" do - context "when run on posix", :as_platform => :posix do - before :each do - subject.stubs(:search_paths).returns [ '/bin', '/sbin', '/usr/sbin'] - end - - context "and provided with an absolute path" do - it "should return the binary if executable" do - File.expects(:executable?).with('/opt/foo').returns true - subject.which('/opt/foo').should == '/opt/foo' - end - it "should return nil if the binary is not executable" do - File.expects(:executable?).with('/opt/foo').returns false - subject.which('/opt/foo').should be_nil - end - end + it "switches LC_ALL to C when executing the command" - context "and not provided with an absolute path" do - it "should return the absolute path if found" do - File.expects(:executable?).with('/bin/foo').returns false - File.expects(:executable?).with('/sbin/foo').returns true - File.expects(:executable?).with('/usr/sbin/foo').never - subject.which('foo').should == '/sbin/foo' - end - - it "should return nil if not found" do - File.expects(:executable?).with('/bin/foo').returns false - File.expects(:executable?).with('/sbin/foo').returns false - File.expects(:executable?).with('/usr/sbin/foo').returns false - subject.which('foo').should be_nil - end - end + it "expands the command before running it" do + subject.stubs(:`).returns '' + subject.expects(:expand_command).with('foo').returns '/bin/foo' + subject.exec('foo') end - context "when run on windows", :as_platform => :windows do - before :each do - subject.stubs(:search_paths).returns ['C:\Windows\system32', 'C:\Windows', 'C:\Windows\System32\Wbem' ] - ENV.stubs(:[]).with('PATHEXT').returns nil - end - - context "and provided with an absolute path" do - it "should return the binary if executable" do - File.expects(:executable?).with('C:\Tools\foo.exe').returns true - File.expects(:executable?).with('\\\\remote\dir\foo.exe').returns true - subject.which('C:\Tools\foo.exe').should == 'C:\Tools\foo.exe' - subject.which('\\\\remote\dir\foo.exe').should == '\\\\remote\dir\foo.exe' - end - - it "should return nil if the binary is not executable" do - File.expects(:executable?).with('C:\Tools\foo.exe').returns false - File.expects(:executable?).with('\\\\remote\dir\foo.exe').returns false - subject.which('C:\Tools\foo.exe').should be_nil - subject.which('\\\\remote\dir\foo.exe').should be_nil - end - end - - context "and not provided with an absolute path" do - it "should return the absolute path if found" do - File.expects(:executable?).with('C:\Windows\system32\foo.exe').returns false - File.expects(:executable?).with('C:\Windows\foo.exe').returns true - File.expects(:executable?).with('C:\Windows\System32\Wbem\foo.exe').never - subject.which('foo.exe').should == 'C:\Windows\foo.exe' - end - - it "should return the absolute path with file extension if found" do - ['.COM', '.EXE', '.BAT', '.CMD', '' ].each do |ext| - File.stubs(:executable?).with('C:\Windows\system32\foo'+ext).returns false - File.stubs(:executable?).with('C:\Windows\System32\Wbem\foo'+ext).returns false - end - ['.COM', '.BAT', '.CMD', '' ].each do |ext| - File.stubs(:executable?).with('C:\Windows\foo'+ext).returns false - end - File.stubs(:executable?).with('C:\Windows\foo.EXE').returns true - - subject.which('foo').should == 'C:\Windows\foo.EXE' - end - - it "should return nil if not found" do - File.expects(:executable?).with('C:\Windows\system32\foo.exe').returns false - File.expects(:executable?).with('C:\Windows\foo.exe').returns false - File.expects(:executable?).with('C:\Windows\System32\Wbem\foo.exe').returns false - subject.which('foo.exe').should be_nil - end - end + it "returns an empty string when the command could not be expanded" do + subject.expects(:expand_command).with('foo').returns nil + expect(subject.exec('foo')).to be_empty end - describe "#expand_command" do - context "on windows", :as_platform => :windows do - it "should expand binary" do - subject.expects(:which).with('cmd').returns 'C:\Windows\System32\cmd' - subject.expand_command( - 'cmd /c echo foo > C:\bar' - ).should == 'C:\Windows\System32\cmd /c echo foo > C:\bar' - end - - it "should expand double quoted binary" do - subject.expects(:which).with('my foo').returns 'C:\My Tools\my foo.exe' - subject.expand_command('"my foo" /a /b').should == '"C:\My Tools\my foo.exe" /a /b' - end - - it "should not expand single quoted binary" do - subject.expects(:which).with('\'C:\My').returns nil - subject.expand_command('\'C:\My Tools\foo.exe\' /a /b').should be_nil - end - - it "should quote expanded binary if found in path with spaces" do - subject.expects(:which).with('foo').returns 'C:\My Tools\foo.exe' - subject.expand_command('foo /a /b').should == '"C:\My Tools\foo.exe" /a /b' - end + it "logs a warning and returns an empty string when the command execution fails" do + subject.expects(:`).with("/bin/foo").raises "kaboom!" + Facter.expects(:warn).with("kaboom!") - it "should return nil if not found" do - subject.expects(:which).with('foo').returns nil - subject.expand_command('foo /a | stuff >> /dev/null').should be_nil - end - end + subject.expects(:expand_command).with('foo').returns '/bin/foo' - context "on unix", :as_platform => :posix do - it "should expand binary" do - subject.expects(:which).with('foo').returns '/bin/foo' - subject.expand_command('foo -a | stuff >> /dev/null').should == '/bin/foo -a | stuff >> /dev/null' - end + expect(subject.exec("foo")).to be_empty + end - it "should expand double quoted binary" do - subject.expects(:which).with('/tmp/my foo').returns '/tmp/my foo' - subject.expand_command(%q{"/tmp/my foo" bar}).should == %q{"/tmp/my foo" bar} - end + it "launches a thread to wait on children if the command was interrupted" do + subject.expects(:`).with("/bin/foo").raises "kaboom!" + subject.expects(:expand_command).with('foo').returns '/bin/foo' - it "should expand single quoted binary" do - subject.expects(:which).with('my foo').returns '/home/bob/my path/my foo' - subject.expand_command(%q{'my foo' -a}).should == %q{'/home/bob/my path/my foo' -a} - end + Facter.stubs(:warn) + Thread.expects(:new).yields + Process.expects(:waitall).once - it "should quote expanded binary if found in path with spaces" do - subject.expects(:which).with('foo.sh').returns '/home/bob/my tools/foo.sh' - subject.expand_command('foo.sh /a /b').should == %q{'/home/bob/my tools/foo.sh' /a /b} - end - - it "should return nil if not found" do - subject.expects(:which).with('foo').returns nil - subject.expand_command('foo -a | stuff >> /dev/null').should be_nil - end - end + subject.exec("foo") end - end - - describe "#absolute_path?" do - context "when run on unix", :as_platform => :posix do - %w[/ /foo /foo/../bar //foo //Server/Foo/Bar //?/C:/foo/bar /\Server/Foo /foo//bar/baz].each do |path| - it "should return true for #{path}" do - subject.should be_absolute_path(path) - end - end + it "returns the output of the command" do + subject.expects(:`).with("/bin/foo").returns 'hi' + subject.expects(:expand_command).with('foo').returns '/bin/foo' - %w[. ./foo \foo C:/foo \\Server\Foo\Bar \\?\C:\foo\bar \/?/foo\bar \/Server/foo foo//bar/baz].each do |path| - it "should return false for #{path}" do - subject.should_not be_absolute_path(path) - end - end + expect(subject.exec("foo")).to eq 'hi' end - context "when run on windows", :as_platform => :windows do - %w[C:/foo C:\foo \\\\Server\Foo\Bar \\\\?\C:\foo\bar //Server/Foo/Bar //?/C:/foo/bar /\?\C:/foo\bar \/Server\Foo/Bar c:/foo//bar//baz].each do |path| - it "should return true for #{path}" do - subject.should be_absolute_path(path) - end - end + it "strips off trailing newlines" do + subject.expects(:`).with("/bin/foo").returns "hi\n" + subject.expects(:expand_command).with('foo').returns '/bin/foo' - %w[/ . ./foo \foo /foo /foo/../bar //foo C:foo/bar foo//bar/baz].each do |path| - it "should return false for #{path}" do - subject.should_not be_absolute_path(path) - end - end + expect(subject.exec("foo")).to eq 'hi' end end end diff --git a/spec/unit/core/execution/posix_spec.rb b/spec/unit/core/execution/posix_spec.rb new file mode 100644 index 0000000000..1acd8e48cf --- /dev/null +++ b/spec/unit/core/execution/posix_spec.rb @@ -0,0 +1,86 @@ +require 'spec_helper' + +describe Facter::Core::Execution::Posix, :as_plaform => :posix do + + describe "#search_paths" do + it "should use the PATH environment variable plus /sbin and /usr/sbin on unix" do + ENV.expects(:[]).with('PATH').returns "/bin:/usr/bin" + subject.search_paths.should == %w{/bin /usr/bin /sbin /usr/sbin} + end + end + + describe "#which" do + before :each do + subject.stubs(:search_paths).returns [ '/bin', '/sbin', '/usr/sbin'] + end + + context "and provided with an absolute path" do + it "should return the binary if executable" do + File.expects(:executable?).with('/opt/foo').returns true + subject.which('/opt/foo').should == '/opt/foo' + end + + it "should return nil if the binary is not executable" do + File.expects(:executable?).with('/opt/foo').returns false + subject.which('/opt/foo').should be_nil + end + end + + context "and not provided with an absolute path" do + it "should return the absolute path if found" do + File.expects(:executable?).with('/bin/foo').returns false + File.expects(:executable?).with('/sbin/foo').returns true + File.expects(:executable?).with('/usr/sbin/foo').never + subject.which('foo').should == '/sbin/foo' + end + + it "should return nil if not found" do + File.expects(:executable?).with('/bin/foo').returns false + File.expects(:executable?).with('/sbin/foo').returns false + File.expects(:executable?).with('/usr/sbin/foo').returns false + subject.which('foo').should be_nil + end + end + end + + describe "#expand_command" do + it "should expand binary" do + subject.expects(:which).with('foo').returns '/bin/foo' + subject.expand_command('foo -a | stuff >> /dev/null').should == '/bin/foo -a | stuff >> /dev/null' + end + + it "should expand double quoted binary" do + subject.expects(:which).with('/tmp/my foo').returns '/tmp/my foo' + subject.expand_command(%q{"/tmp/my foo" bar}).should == %q{'/tmp/my foo' bar} + end + + it "should expand single quoted binary" do + subject.expects(:which).with('my foo').returns '/home/bob/my path/my foo' + subject.expand_command(%q{'my foo' -a}).should == %q{'/home/bob/my path/my foo' -a} + end + + it "should quote expanded binary if found in path with spaces" do + subject.expects(:which).with('foo.sh').returns '/home/bob/my tools/foo.sh' + subject.expand_command('foo.sh /a /b').should == %q{'/home/bob/my tools/foo.sh' /a /b} + end + + it "should return nil if not found" do + subject.expects(:which).with('foo').returns nil + subject.expand_command('foo -a | stuff >> /dev/null').should be_nil + end + end + + describe "#absolute_path?" do + %w[/ /foo /foo/../bar //foo //Server/Foo/Bar //?/C:/foo/bar /\Server/Foo /foo//bar/baz].each do |path| + it "should return true for #{path}" do + subject.should be_absolute_path(path) + end + end + + %w[. ./foo \foo C:/foo \\Server\Foo\Bar \\?\C:\foo\bar \/?/foo\bar \/Server/foo foo//bar/baz].each do |path| + it "should return false for #{path}" do + subject.should_not be_absolute_path(path) + end + end + end +end diff --git a/spec/unit/core/execution/ruby18_spec.rb b/spec/unit/core/execution/ruby18_spec.rb deleted file mode 100644 index 10c0c1ac81..0000000000 --- a/spec/unit/core/execution/ruby18_spec.rb +++ /dev/null @@ -1,59 +0,0 @@ -require 'spec_helper' - -describe Facter::Core::Execution::Ruby18 do - - describe "#exec" do - - it "switches LANG to C when executing the command" do - subject.expects(:with_env).with('LANG' => 'C') - subject.exec('foo') - end - - it "switches LC_ALL to C when executing the command" - - it "expands the command before running it" do - subject.stubs(:`).returns '' - subject.expects(:expand_command).with('foo').returns '/bin/foo' - subject.exec('foo') - end - - it "returns an empty string when the command could not be expanded" do - subject.expects(:expand_command).with('foo').returns nil - expect(subject.exec('foo')).to be_empty - end - - it "logs a warning and returns an empty string when the command execution fails" do - subject.expects(:`).with("/bin/foo").raises "kaboom!" - Facter.expects(:warn).with("kaboom!") - - subject.expects(:expand_command).with('foo').returns '/bin/foo' - - expect(subject.exec("foo")).to be_empty - end - - it "launches a thread to wait on children if the command was interrupted" do - subject.expects(:`).with("/bin/foo").raises "kaboom!" - subject.expects(:expand_command).with('foo').returns '/bin/foo' - - Facter.stubs(:warn) - Thread.expects(:new).yields - Process.expects(:waitall).once - - subject.exec("foo") - end - - it "returns the output of the command" do - subject.expects(:`).with("/bin/foo").returns 'hi' - subject.expects(:expand_command).with('foo').returns '/bin/foo' - - expect(subject.exec("foo")).to eq 'hi' - end - - it "strips off trailing newlines" do - subject.expects(:`).with("/bin/foo").returns "hi\n" - subject.expects(:expand_command).with('foo').returns '/bin/foo' - - expect(subject.exec("foo")).to eq 'hi' - end - end -end diff --git a/spec/unit/core/execution/windows_spec.rb b/spec/unit/core/execution/windows_spec.rb new file mode 100644 index 0000000000..39ff1d1d1a --- /dev/null +++ b/spec/unit/core/execution/windows_spec.rb @@ -0,0 +1,106 @@ +require 'spec_helper' + +describe Facter::Core::Execution::Windows, :as_platform => :windows do + + describe "#search_paths" do + it "should use the PATH environment variable to determine locations" do + ENV.expects(:[]).with('PATH').returns 'C:\Windows;C:\Windows\System32' + subject.search_paths.should == %w{C:\Windows C:\Windows\System32} + end + end + + describe "#which" do + before :each do + subject.stubs(:search_paths).returns ['C:\Windows\system32', 'C:\Windows', 'C:\Windows\System32\Wbem' ] + ENV.stubs(:[]).with('PATHEXT').returns nil + end + + context "and provided with an absolute path" do + it "should return the binary if executable" do + File.expects(:executable?).with('C:\Tools\foo.exe').returns true + File.expects(:executable?).with('\\\\remote\dir\foo.exe').returns true + subject.which('C:\Tools\foo.exe').should == 'C:\Tools\foo.exe' + subject.which('\\\\remote\dir\foo.exe').should == '\\\\remote\dir\foo.exe' + end + + it "should return nil if the binary is not executable" do + File.expects(:executable?).with('C:\Tools\foo.exe').returns false + File.expects(:executable?).with('\\\\remote\dir\foo.exe').returns false + subject.which('C:\Tools\foo.exe').should be_nil + subject.which('\\\\remote\dir\foo.exe').should be_nil + end + end + + context "and not provided with an absolute path" do + it "should return the absolute path if found" do + File.expects(:executable?).with('C:\Windows\system32\foo.exe').returns false + File.expects(:executable?).with('C:\Windows\foo.exe').returns true + File.expects(:executable?).with('C:\Windows\System32\Wbem\foo.exe').never + subject.which('foo.exe').should == 'C:\Windows\foo.exe' + end + + it "should return the absolute path with file extension if found" do + ['.COM', '.EXE', '.BAT', '.CMD', '' ].each do |ext| + File.stubs(:executable?).with('C:\Windows\system32\foo'+ext).returns false + File.stubs(:executable?).with('C:\Windows\System32\Wbem\foo'+ext).returns false + end + ['.COM', '.BAT', '.CMD', '' ].each do |ext| + File.stubs(:executable?).with('C:\Windows\foo'+ext).returns false + end + File.stubs(:executable?).with('C:\Windows\foo.EXE').returns true + + subject.which('foo').should == 'C:\Windows\foo.EXE' + end + + it "should return nil if not found" do + File.expects(:executable?).with('C:\Windows\system32\foo.exe').returns false + File.expects(:executable?).with('C:\Windows\foo.exe').returns false + File.expects(:executable?).with('C:\Windows\System32\Wbem\foo.exe').returns false + subject.which('foo.exe').should be_nil + end + end + end + + describe "#expand_command" do + it "should expand binary" do + subject.expects(:which).with('cmd').returns 'C:\Windows\System32\cmd' + subject.expand_command( + 'cmd /c echo foo > C:\bar' + ).should == 'C:\Windows\System32\cmd /c echo foo > C:\bar' + end + + it "should expand double quoted binary" do + subject.expects(:which).with('my foo').returns 'C:\My Tools\my foo.exe' + subject.expand_command('"my foo" /a /b').should == '"C:\My Tools\my foo.exe" /a /b' + end + + it "should not expand single quoted binary" do + subject.expects(:which).with('\'C:\My').returns nil + subject.expand_command('\'C:\My Tools\foo.exe\' /a /b').should be_nil + end + + it "should quote expanded binary if found in path with spaces" do + subject.expects(:which).with('foo').returns 'C:\My Tools\foo.exe' + subject.expand_command('foo /a /b').should == '"C:\My Tools\foo.exe" /a /b' + end + + it "should return nil if not found" do + subject.expects(:which).with('foo').returns nil + subject.expand_command('foo /a | stuff >> NUL').should be_nil + end + end + + describe "#absolute_path?" do + %w[C:/foo C:\foo \\\\Server\Foo\Bar \\\\?\C:\foo\bar //Server/Foo/Bar //?/C:/foo/bar /\?\C:/foo\bar \/Server\Foo/Bar c:/foo//bar//baz].each do |path| + it "should return true for #{path}" do + subject.should be_absolute_path(path) + end + end + + %w[/ . ./foo \foo /foo /foo/../bar //foo C:foo/bar foo//bar/baz].each do |path| + it "should return false for #{path}" do + subject.should_not be_absolute_path(path) + end + end + end +end From 01b8f2da14f303aa9ac04dc8e636a0bdea81940f Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Fri, 14 Feb 2014 13:45:33 -0800 Subject: [PATCH 1653/3753] (maint) zoneadm_output should always be a string --- lib/facter/util/solaris_zones.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/facter/util/solaris_zones.rb b/lib/facter/util/solaris_zones.rb index 881baa7635..e6b287d87c 100644 --- a/lib/facter/util/solaris_zones.rb +++ b/lib/facter/util/solaris_zones.rb @@ -103,11 +103,11 @@ def refresh # # @api private def parse! - if @zoneadm_output - rows = @zoneadm_output.split("\n").collect { |line| line.split(':') } - else + if @zoneadm_output.empty? Facter.debug "Cannot parse zone facts, #{zoneadm_cmd} returned no output" rows = [] + else + rows = @zoneadm_output.split("\n").collect { |line| line.split(':') } end @zone_hash = rows.inject({}) do |memo, fields| From a3811875b7468d00c11f7950345129b4dacc5be8 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Fri, 14 Feb 2014 16:20:42 -0800 Subject: [PATCH 1654/3753] (maint) Add zone_global_uuid to schema --- schema/facter.json | 1 + 1 file changed, 1 insertion(+) diff --git a/schema/facter.json b/schema/facter.json index 304fb5db4d..da40fe3b03 100644 --- a/schema/facter.json +++ b/schema/facter.json @@ -148,6 +148,7 @@ "zone_global_path" : { "type": "string" }, "zone_global_brand" : { "type": "string" }, "zone_global_id" : { "type": "string" }, + "zone_global_uuid" : { "type": "string" }, "zone_global_status" : { "type": "string" }, "zpool_version" : { "type": "string" } } From 2600f04211d6316f78416ff1bfedcbb70a6f0bfd Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Sun, 16 Feb 2014 08:18:26 -0800 Subject: [PATCH 1655/3753] (maint) Update docs to reflect use of facter-2 branch As we prepared for the release of Facter 2.0, we decided to do that development on a facter-2 branch rather than master. For details on this decision, see: https://groups.google.com/forum/#!msg/puppet-dev/Q24GLe6s1_4/hPwHgvVhnpIJ This commit reflects this change into the COMMITTERS.md and CONTRIBUTING.md documents. It's mostly replacing 'master' with 'facter-2' in the appropriate places, but it also removes some old assumptions, e.g. about per-release branches. --- COMMITTERS.md | 59 ++++++++++++++++++++++++++++--------------------- CONTRIBUTING.md | 10 ++++----- 2 files changed, 39 insertions(+), 30 deletions(-) diff --git a/COMMITTERS.md b/COMMITTERS.md index 6f8184643d..91e2ba0749 100644 --- a/COMMITTERS.md +++ b/COMMITTERS.md @@ -5,7 +5,7 @@ We would like to make it easier for community members to contribute to facter using pull requests, even if it makes the task of reviewing and committing these changes a little harder. Pull requests are only ever based on a single branch, however, we maintain more than one active branch. As a result -contributors should target their changes at the master branch. This makes the +contributors should target their changes at the facter-2 branch. This makes the process of contributing a little easier for the contributor since they don't need to concern themselves with the question, "What branch do I base my changes on?" This is already called out in the [CONTRIBUTING.md](http://goo.gl/XRH2J). @@ -37,8 +37,7 @@ making the decision what base branch to merge the change set into. **base branch** - A branch in Git that contains an active history of changes and will eventually be released using semantic version guidelines. The branch -named master will always exist as a base branch. All other base branches will -be associated with a specific released version of facter, e.g. 1.6.x and 1.7.x. +named master will always exist as a base branch. Committer Guide ==== @@ -97,7 +96,7 @@ branch: documentation being kept up to date? * Does the change set include clean code? (software code that is formatted correctly and in an organized manner so that another coder can easily read - or modify it.) HINT: `git diff master --check` + or modify it.) HINT: `git diff --check` * Does the change set conform to the contributing guide? @@ -113,9 +112,9 @@ paying attention to our automated build tools. * Watch the build until your changes have gone through green * Update the ticket status and target version. The target version field in our issue tracker should be updated to be the next release of facter. For - example, if the most recent release of facter is 1.6.17 and you merge a - backwards compatible change set into master, then the target version should - be 1.7.0 in the issue tracker.) + example, if the most recent release of facter is 2.0.1 and you merge a + backwards compatible change set into facter-2, then the target version should + be 2.1.0 in the issue tracker.) * Ensure the pull request is closed (Hint: amend your merge commit to contain the string `closes #123` where 123 is the pull request number. @@ -126,11 +125,11 @@ This section helps a committer rebase a contribution onto an earlier base branch, then merge into the base branch and up through all active base branches. -Suppose a contributor submits a pull request based on master. The change set -fixes a bug reported against facter 1.7.1 which is the most recently released +Suppose a contributor submits a pull request based on facter-2. The change set +fixes a bug reported against facter 2.0.1 which is the most recently released version of facter. -In this example the committer should rebase the change set onto the 1.7.x +In this example the committer should rebase the change set onto the stable branch since this is a bug rather than new functionality. First, the committer pulls down the branch using the `hub` gem. This tool @@ -141,45 +140,55 @@ branch to track the remote branch. Branch jeffmccune-fix_foo_error set up to track remote branch fix_foo_error from jeffmccune. Switched to a new branch 'jeffmccune-fix_foo_error' -At this point the topic branch is a descendant of master, but we want it to -descend from 1.7.x. The committer creates a new branch then re-bases the +At this point the topic branch is a descendant of facter-2, but we want it to +descend from stable. The committer creates a new branch then re-bases the change set: - $ git branch bug/1.7.x/fix_foo_error - $ git rebase --onto 1.7.x master bug/1.7.x/fix_foo_error + $ git branch bug/stable/fix_foo_error + $ git rebase --onto stable master bug/stable/fix_foo_error First, rewinding head to replay your work on top of it... - Applying: (#23456) Fix FooError that always bites users in 1.7.1 + Applying: (#23456) Fix FooError that always bites users in 2.0.1 The `git rebase` command may be interpreted as, "First, check out the branch -named `bug/1.7.x/fix_foo_error`, then take the changes that were previously -based on `master` and re-base them onto `1.7.x`. +named `bug/stable/fix_foo_error`, then take the changes that were previously +based on `facter-2` and re-base them onto `stable`. Now that we have a topic branch containing the change set based on the correct release branch, the committer merges in: - $ git checkout 1.7.x - Switched to branch '1.7.x' - $ git merge --no-ff --log bug/1.7.x/fix_foo_error + $ git checkout stable + Switched to branch 'stable' + $ git merge --no-ff --log bug/stable/fix_foo_error Merge made by the 'recursive' strategy. foo | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 foo -Once merged into the first base branch, the committer merges up: +Once merged into the first base branch, the committer merges up to facter-2: + + $ git checkout facter-2 + Switched to branch 'facter-2' + $ git merge --no-ff --log stable + Merge made by the 'recursive' strategy. + foo | 0 + 1 file changed, 0 insertions(+), 0 deletions(-) + create mode 100644 foo + +And then merges up to master: $ git checkout master Switched to branch 'master' - $ git merge --no-ff --log 1.7.x + $ git merge --no-ff --log stable Merge made by the 'recursive' strategy. foo | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 foo Once the change set has been merged "in and up." the committer pushes. (Note, -the checklist should be complete at this point.) Note that both the 1.7.x and -master branches are being pushed at the same time. +the checklist should be complete at this point.) Note that the stable, +facter-2 and master branches are being pushed at the same time. - $ git push puppetlabs master:master 1.7.x:1.7.x + $ git push puppetlabs master:master facter-2:facter-2 stable:stable That's it! The committer then updates the pull request, updates the issue in our issue tracker, and keeps an eye on the build status. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 91ac3213e2..d7626e9b88 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,13 +19,13 @@ top of things. ## Making Changes * Create a topic branch from where you want to base your work. - * This is usually the master branch. + * This is usually the facter-2 branch. * Only target release branches if you are certain your fix must be on that branch. - * To quickly create a topic branch based on master; `git branch - fix/master/my_contribution master` then checkout the new branch with `git - checkout fix/master/my_contribution`. Please avoid working directly on the - `master` branch. + * To quickly create a topic branch based on facter-2; `git branch + fix/facter-2/my_contribution facter-2` then checkout the new branch with `git + checkout fix/facter-2/my_contribution`. Please avoid working directly on the + `facter-2` branch. * Make commits of logical units. * Check for unnecessary whitespace with `git diff --check` before committing. * Make sure your commit messages are in the proper format. From f7b09c73dbe8e8fc67594986f11e44821d4e5ad4 Mon Sep 17 00:00:00 2001 From: Chris Portman Date: Fri, 22 Nov 2013 23:51:12 +1100 Subject: [PATCH 1656/3753] (FACT-234) Add blockdevice uuid fact Add facts to show the uuid of partitions. Looks for the partition of block devices found in /sys/block//* Then looks for the a link to the partition device in /dev/disk/by-uuid. The link name is the UUID. JIRA: FACT-234 --- lib/facter/blockdevices.rb | 55 ++++++++++++++++++++++++++++++-- spec/unit/blockdevices_spec.rb | 58 +++++++++++++++++++++++++++++++++- 2 files changed, 109 insertions(+), 4 deletions(-) diff --git a/lib/facter/blockdevices.rb b/lib/facter/blockdevices.rb index e0ddc48fb4..28da29f8ce 100644 --- a/lib/facter/blockdevices.rb +++ b/lib/facter/blockdevices.rb @@ -34,7 +34,6 @@ # Only supports Linux 2.6+ at this time, due to the reliance on sysfs # - # Fact: blockdevices # # Purpose: @@ -47,6 +46,30 @@ # Block devices must have been identified using sysfs information # +# Fact: blockdevice__partitions +# +# Purpose: +# Returns a comma separated list of partitions on the block device. +# +# Resolution: +# Parses the contents of /sys/block/* +# +# Caveats: +# Only supports Linux 2.6+ at this time, due to the reliance on sysfs +# + +# Fact: blockdevice__uuid +# +# Purpose: +# Returns the UUID of the partitions on blockdevices. +# +# Resolution: +# Parses /dev/disk/by-uuid and resolves the links back to the partitions in /dev +# +# Caveats: +# Only supports Linux 2.6+ at this time, due to the reliance on sysfs +# + # Author: Jason Gill require 'facter' @@ -72,6 +95,8 @@ sizefile = sysfs_block_directory + device + "/size" vendorfile = sysfs_device_directory + "/vendor" modelfile = sysfs_device_directory + "/model" + partitions = Dir.glob(sysfs_block_directory + device + "/#{device}*").map { |d| File.basename(d) } + devdisk_by_uuid_directory = '/dev/disk/by-uuid/' if File.exist?(sizefile) Facter.add("blockdevice_#{device}_size".to_sym) do @@ -91,8 +116,33 @@ end end - end + unless partitions.empty? + Facter.add("blockdevice_#{device}_partitions") do + setcode { partitions.join(',') } + end + end + partitions.each do |part| + Facter.add("blockdevice_#{part}_uuid") do + setcode do + uuid = nil + if File.directory?(devdisk_by_uuid_directory) + Dir.entries(devdisk_by_uuid_directory).each do |file| + qualified_file = "#{devdisk_by_uuid_directory}#{file}" + + #A uuid is 16 octets long (RFC4122) which is 32hex chars + 4 '-'s + next unless file.length == 36 + next unless File.symlink?(qualified_file) + next unless File.readlink(qualified_file).match(%r[(?:\.\./\.\./|/dev/)#{part}$]) + + uuid = file + end + end + uuid + end + end + end + end end # Return a comma-seperated list of block devices found @@ -101,5 +151,4 @@ setcode { blockdevices.sort.join(',') } end end - end diff --git a/spec/unit/blockdevices_spec.rb b/spec/unit/blockdevices_spec.rb index 54620b8e07..97ef493412 100644 --- a/spec/unit/blockdevices_spec.rb +++ b/spec/unit/blockdevices_spec.rb @@ -47,6 +47,44 @@ IO.stubs(:read).with(stubdir + "/device/vendor").returns(vendor) if vendor IO.stubs(:read).with(stubdir + "/device/model").returns(model) if model end + + #Stubs relating to the Partition UUID facts + File.stubs(:exist?).with('/dev/disk/by-uuid/').returns(true) + + Dir.stubs(:glob).with("/sys/block/hda/hda*").returns([]) + + Dir.stubs(:glob).with("/sys/block/sda/sda*").returns([ + '/sys/block/sda/sda1', + '/sys/block/sda/sda2', + ]) + + Dir.stubs(:glob).with("/sys/block/sdb/sdb*").returns([ + '/sys/block/sda/sdb1', + '/sys/block/sda/sdb2', + ]) + + File.stubs(:directory?).returns(true) + + Dir.stubs(:entries).with('/dev/disk/by-uuid/').returns([ + ".", #wont match the length requirements + "..", #wont match the length requirements + "11111111-1111-1111-1111-111111111111", #Should be valid + "22222222-2222-2222-2222-222222222222", #Should be valid + "33333333-3333-3333-3333-333333333333", #Should be valid + "44444444-4444-4444-4444-444444444444", #Wont match the link regex + "55555555-5555-5555-5555-555555555555" #Wont be a symlink + ]) + + File.stubs(:readlink).with('/dev/disk/by-uuid/11111111-1111-1111-1111-111111111111').returns('../../sda1') + File.stubs(:readlink).with('/dev/disk/by-uuid/22222222-2222-2222-2222-222222222222').returns('../../sda2') + File.stubs(:readlink).with('/dev/disk/by-uuid/33333333-3333-3333-3333-333333333333').returns('../../sdb1') + File.stubs(:readlink).with('/dev/disk/by-uuid/44444444-4444-4444-4444-444444444444').returns('/dont/match/regex/sdb2') + + File.stubs(:symlink?).with('/dev/disk/by-uuid/11111111-1111-1111-1111-111111111111').returns(true) + File.stubs(:symlink?).with('/dev/disk/by-uuid/22222222-2222-2222-2222-222222222222').returns(true) + File.stubs(:symlink?).with('/dev/disk/by-uuid/33333333-3333-3333-3333-333333333333').returns(true) + File.stubs(:symlink?).with('/dev/disk/by-uuid/44444444-4444-4444-4444-444444444444').returns(true) + File.stubs(:symlink?).with('/dev/disk/by-uuid/55555555-5555-5555-5555-555555555555').returns(false) end it "should report three block devices, hda, sda, and sdb, with accurate information from sda and sdb, and without invalid . or .. entries" do @@ -64,16 +102,32 @@ Facter.fact("blockdevice_#{device}_size".to_sym).should_not == nil Facter.fact("blockdevice_#{device}_vendor".to_sym).should_not == nil Facter.fact("blockdevice_#{device}_model".to_sym).should_not == nil + Facter.fact("blockdevice_#{device}_partitions".to_sym).should_not == nil end Facter.fact(:blockdevice_sda_model).value.should == "WDC WD5000AAKS-0" Facter.fact(:blockdevice_sda_vendor).value.should == "ATA" Facter.fact(:blockdevice_sda_size).value.should == 500107862016 + Facter.fact(:blockdevice_sda_partitions).value.should == 'sda1,sda2' Facter.fact(:blockdevice_sdb_model).value.should == "PERC H700" Facter.fact(:blockdevice_sdb_vendor).value.should == "DELL" Facter.fact(:blockdevice_sdb_size).value.should == 4499246678016 + Facter.fact(:blockdevice_sdb_partitions).value.should == 'sdb1,sdb2' + + #These partitions should have a UUID + %w{ sda1 sda2 sdb1 }.each do |d| + Facter.value("blockdevice_#{d}_uuid".to_sym).should_not == nil + end + #These should not create a UUID fact + [ ".", "..", "sdb2" ].each do |d| + Facter.value("partition_#{d}_uuid".to_sym).should == nil + end + + Facter.fact(:blockdevice_sda1_uuid).value.should == "11111111-1111-1111-1111-111111111111" + Facter.fact(:blockdevice_sda2_uuid).value.should == "22222222-2222-2222-2222-222222222222" + Facter.fact(:blockdevice_sdb1_uuid).value.should == "33333333-3333-3333-3333-333333333333" end end @@ -88,6 +142,9 @@ File.stubs(:exist?).with("/sys/block/../device").returns(false) File.stubs(:exist?).with("/sys/block/xda/device").returns(false) File.stubs(:exist?).with("/sys/block/ydb/device").returns(false) + + #This is here to surpress errors when Dir.entries is run in relation to uuids + Dir.stubs(:entries).with('/dev/disk/by-uuid/').returns([]) end it "should not exist with invalid entries in /sys/block" do @@ -104,6 +161,5 @@ end end - end end From 7c3e366087038d2c219c883735cdded60ac441c7 Mon Sep 17 00:00:00 2001 From: Stefan Schulte Date: Tue, 18 Feb 2014 01:59:23 +0100 Subject: [PATCH 1657/3753] (maint) Remove trailing spaces in blockdevices_spec --- spec/unit/blockdevices_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/unit/blockdevices_spec.rb b/spec/unit/blockdevices_spec.rb index 97ef493412..9d12cd931d 100644 --- a/spec/unit/blockdevices_spec.rb +++ b/spec/unit/blockdevices_spec.rb @@ -64,7 +64,7 @@ ]) File.stubs(:directory?).returns(true) - + Dir.stubs(:entries).with('/dev/disk/by-uuid/').returns([ ".", #wont match the length requirements "..", #wont match the length requirements @@ -113,7 +113,7 @@ Facter.fact(:blockdevice_sdb_model).value.should == "PERC H700" Facter.fact(:blockdevice_sdb_vendor).value.should == "DELL" Facter.fact(:blockdevice_sdb_size).value.should == 4499246678016 - Facter.fact(:blockdevice_sdb_partitions).value.should == 'sdb1,sdb2' + Facter.fact(:blockdevice_sdb_partitions).value.should == 'sdb1,sdb2' #These partitions should have a UUID %w{ sda1 sda2 sdb1 }.each do |d| From 0b1d424ae67a79d0072f4d2dad409a2ad79ddc5a Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Tue, 18 Feb 2014 07:51:10 -0800 Subject: [PATCH 1658/3753] (fact-234) Add newly defined blockdevice dynamic facts to the schema --- schema/facter.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/schema/facter.json b/schema/facter.json index da40fe3b03..70bac16fe0 100644 --- a/schema/facter.json +++ b/schema/facter.json @@ -18,6 +18,8 @@ "^blockdevice_[A-Za-z0-9]+_size$" : { "type": "integer" }, "^blockdevice_[A-Za-z0-9]+_vendor$" : { "type": "string" }, "^blockdevice_[A-Za-z0-9]+_model$" : { "type": "string" }, + "^blockdevice_[A-Za-z0-9]+_uuid$" : { "type": "string" }, + "^blockdevice_[A-Za-z0-9]+_partitions$" : { "type": "string" }, "^mtu_[A-Za-z0-9]+$" : { "type": "string" }, "^netmask_[A-Za-z0-9_]+$" : { "$ref" : "#/definitions/ipaddress" }, "^network_[A-Za-z0-9_]+$" : { "$ref" : "#/definitions/ipaddress" }, From 6464e5b2cc022fbbb9c7af843a76911b2affe5f7 Mon Sep 17 00:00:00 2001 From: Luis Fernandez Alvarez Date: Wed, 24 Jul 2013 11:32:23 +0200 Subject: [PATCH 1659/3753] (FACT-341) - Added operatingsystemrelease support to Windows Without this patch, the fact 'operatingsystemrelease' was set to kernel version fact for all the windows flavors. This patchs brings the option of having the common release name for the windows machines, something that can help in writing specific puppet manifests and other tasks according to the specific windows version. The patch is based on a WMI call to Win32_OperatingSystem. This aproach has some drawbacks but I think the code is simple and easy to mantain and update. Anyway, extracting the windows version is not an easy task for Microsoft as well: http://msdn.microsoft.com/en-us/library/windows/desktop/ms724429%28v=vs.85%29.aspx The original Redmine issue is (#7621) Conflicts: lib/facter/operatingsystemrelease.rb --- lib/facter/operatingsystemrelease.rb | 29 ++++++++++++++ spec/unit/operatingsystemrelease_spec.rb | 51 ++++++++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index d2adcd9739..c0578581f6 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -188,6 +188,35 @@ end end +Facter.add(:operatingsystemrelease) do + confine :operatingsystem => :windows + setcode do + require 'facter/util/wmi' + result = nil + Facter::Util::WMI.execquery("SELECT version, producttype FROM Win32_OperatingSystem").each do |os| + result = + case os.version + when /^6\.2/ + os.producttype == 1 ? "8" : "2012" + when /^6\.1/ + os.producttype == 1 ? "7" : "2008 R2" + when /^6\.0/ + os.producttype == 1 ? "Vista" : "2008" + when /^5\.2/ + if os.producttype == 1 + "XP" + else + os.othertypedescription == "R2" ? "2003 R2" : "2003" + end + else + Facter[:kernelrelease].value + end + break + end + result + end +end + Facter.add(:operatingsystemrelease) do setcode do Facter[:kernelrelease].value end end diff --git a/spec/unit/operatingsystemrelease_spec.rb b/spec/unit/operatingsystemrelease_spec.rb index dd9370d2e4..4cf68c60a5 100755 --- a/spec/unit/operatingsystemrelease_spec.rb +++ b/spec/unit/operatingsystemrelease_spec.rb @@ -152,6 +152,57 @@ end end + describe "with operatingsystem reported as Windows" do + require 'facter/util/wmi' + before do + Facter.fact(:kernel).stubs(:value).returns("windows") + end + + { + ['5.2.3790', 1] => "XP", + ['6.0.6002', 1] => "Vista", + ['6.0.6002', 2] => "2008", + ['6.0.6002', 3] => "2008", + ['6.1.7601', 1] => "7", + ['6.1.7601', 2] => "2008 R2", + ['6.1.7601', 3] => "2008 R2", + ['6.2.9200', 1] => "8", + ['6.2.9200', 2] => "2012", + ['6.2.9200', 3] => "2012", + }.each do |os_values, expected_output| + it "should be #{expected_output} with Version #{os_values[0]} and ProductType #{os_values[1]}" do + os = mock('os', :version => os_values[0], :producttype => os_values[1]) + Facter::Util::WMI.expects(:execquery).returns([os]) + Facter.fact(:operatingsystemrelease).value.should == expected_output + end + end + + { + ['5.2.3790', 2, ""] => "2003", + ['5.2.3790', 2, "R2"] => "2003 R2", + ['5.2.3790', 3, ""] => "2003", + ['5.2.3790', 3, "R2"] => "2003 R2", + }.each do |os_values, expected_output| + it "should be #{expected_output} with Version #{os_values[0]} and ProductType #{os_values[1]} and OtherTypeDescription #{os_values[2]}" do + os = mock('os', :version => os_values[0], :producttype => os_values[1], :othertypedescription => os_values[2]) + Facter::Util::WMI.expects(:execquery).returns([os]) + Facter.fact(:operatingsystemrelease).value.should == expected_output + end + end + + context "Unknown Windows version" do + before :each do + Facter.fact(:kernelrelease).stubs(:value).returns("X.Y.ZZZZ") + end + + it "should be kernel version value with unknown values " do + os = mock('os', :version => "X.Y.ZZZZ") + Facter::Util::WMI.expects(:execquery).returns([os]) + Facter.fact(:operatingsystemrelease).value.should == "X.Y.ZZZZ" + end + end + end + context "Ubuntu" do let(:lsbrelease) { 'DISTRIB_ID=Ubuntu\nDISTRIB_RELEASE=10.04\nDISTRIB_CODENAME=lucid\nDISTRIB_DESCRIPTION="Ubuntu 10.04.4 LTS"'} before :each do From 832b59a5cebd08eca004fbf946bdae1037eb6142 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Thu, 6 Feb 2014 14:43:28 -0800 Subject: [PATCH 1660/3753] (Maint) Handle NoMethodError on 2003 Commit b5a4c68f1 modified the operatingsystemrelease fact to return more meaningful Windows versions, e.g. 2003 instead of the kernelrelease fact value, 5.2.3790. However, on (some?) non-R2 versions of 2003, the othertypedescription method is not available on the WMI class Win32_OperatingSystem[1]. As a result, facter would output to stderr the following message: Could not retrieve operatingsystemrelease: unknown property or method: `othertypedescription' HRESULT error code:0x80020006 Unknown name. This commit simply rescues the NoMethodError and outputs 2003. --- lib/facter/operatingsystemrelease.rb | 6 +++++- spec/unit/operatingsystemrelease_spec.rb | 8 ++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index c0578581f6..a57abe7091 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -206,7 +206,11 @@ if os.producttype == 1 "XP" else - os.othertypedescription == "R2" ? "2003 R2" : "2003" + begin + os.othertypedescription == "R2" ? "2003 R2" : "2003" + rescue NoMethodError + "2003" + end end else Facter[:kernelrelease].value diff --git a/spec/unit/operatingsystemrelease_spec.rb b/spec/unit/operatingsystemrelease_spec.rb index 4cf68c60a5..9a77dac509 100755 --- a/spec/unit/operatingsystemrelease_spec.rb +++ b/spec/unit/operatingsystemrelease_spec.rb @@ -190,6 +190,14 @@ end end + it "reports '2003' if the WMI method othertypedescription does not exist" do + os = mock('os', :version => '5.2.3790', :producttype => 2) + os.stubs(:othertypedescription).raises(NoMethodError) + + Facter::Util::WMI.expects(:execquery).returns([os]) + Facter.fact(:operatingsystemrelease).value.should == '2003' + end + context "Unknown Windows version" do before :each do Facter.fact(:kernelrelease).stubs(:value).returns("X.Y.ZZZZ") From ffea596bc97d598e669b0cc2441a600ba65974a0 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Sat, 22 Feb 2014 15:05:31 -0800 Subject: [PATCH 1661/3753] Pin ffi to 1.9.0 to match puppet Previously I specified the ffi gem with a pessimistic version, but that conflicted with puppet's fixed pinning of ffi to 1.9.0. Puppet's fixed pinning is really just for windows (the only platform that requires ffi currently), but it would take a little more work than I am up for to puppet/Gemfile to support platform dependencies more loosely, so taking the easy out and just pinning it here to match. For now. --- gem/cfacter.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gem/cfacter.gemspec b/gem/cfacter.gemspec index b1f2285e2d..0752b5fc90 100644 --- a/gem/cfacter.gemspec +++ b/gem/cfacter.gemspec @@ -7,5 +7,5 @@ Gem::Specification.new do |s| s.email = 'kylo@kylo.net' s.files = ["lib/cfacter.rb"] s.add_runtime_dependency 'json', '~> 1.8', '>= 1.8.1' - s.add_runtime_dependency 'ffi', '~> 1.9', '>= 1.9.3' + s.add_runtime_dependency 'ffi', '1.9.0' end From f30d8726abc1731671b316364f3822fee271b827 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Sat, 22 Feb 2014 15:10:00 -0800 Subject: [PATCH 1662/3753] Tweak the Ruby interface to mimic facter's --- gem/lib/cfacter.rb | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/gem/lib/cfacter.rb b/gem/lib/cfacter.rb index 29ea485015..16e17d3756 100644 --- a/gem/lib/cfacter.rb +++ b/gem/lib/cfacter.rb @@ -16,8 +16,6 @@ class Constants public attach_function :clear, [], :void attach_function :loadfacts, [], :void - attach_function :value, [:pointer, :pointer, :size_t], :int - attach_function :search_external, [:pointer], :void def self.to_hash ptr = FFI::MemoryPointer.new(:char, Constants::JSON_STRING_MAX_LEN) @@ -27,4 +25,32 @@ def self.to_hash end JSON.parse(ptr.read_string()) end + + def self.search(*dirs) + # no ruby load paths for cfacter + end + + def self.value(name) + ptr = FFI::MemoryPointer.new(:char, Constants::JSON_STRING_MAX_LEN) + success = c_value(name.to_s, ptr, Constants::JSON_STRING_MAX_LEN) + if success != 0 + return "" + end + ptr.read_string() + end + + CFact = Struct.new(:value) + + def self.[](name) + CFact.new(value(name)) + end + + def search_external(dirs) + dirs.each { |dir| c_search_external(dir) } + end + + private + attach_function :c_value, :value, [:string, :pointer, :size_t], :int + attach_function :c_search_external, :search_external, [:pointer], :void + end From 1f004da5b98c1baa9bde38f0d19e439ef4ff3450 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Sat, 22 Feb 2014 15:22:40 -0800 Subject: [PATCH 1663/3753] Skip directories in a configured external fact directory --- cfacterimpl.cc | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cfacterimpl.cc b/cfacterimpl.cc index 7ff9168c0f..2fa1c8ba7e 100644 --- a/cfacterimpl.cc +++ b/cfacterimpl.cc @@ -918,10 +918,17 @@ static void get_external_facts(fact_map& facts, string directory) return; struct dirent *external_fact; + struct stat s; while ((external_fact = readdir(external_dir))) { string full_path = directory + "/" + external_fact->d_name; + if (stat(full_path.c_str(), &s) != 0) + continue; + + if (s.st_mode & S_IFDIR) + continue; + if (access(full_path.c_str(), X_OK) != 0) continue; From bb5f7e3338a28d77952e51b30202810be9b6dcca Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Sat, 22 Feb 2014 21:43:07 -0800 Subject: [PATCH 1664/3753] Make loadfacts() idempotent and call it implicitly for to_json() and value() --- cfacterlib.cc | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/cfacterlib.cc b/cfacterlib.cc index 23d91e8831..0b49f79922 100644 --- a/cfacterlib.cc +++ b/cfacterlib.cc @@ -21,6 +21,15 @@ void clear() void loadfacts() { + // Make loadfacts() idempotent, if client has reason to + // want a reload, they should clear() then loadfacts(). + // + // This goes along with to_json() and value() always calling + // loadfacts(). + // + if (!facts.empty()) + return; + facts["cfacterversion"] = "0.0.1"; list external_directories; @@ -48,6 +57,8 @@ void loadfacts() int to_json(char *facts_json, size_t facts_len) { + loadfacts(); + if (0) { typedef map::iterator iter; for (iter i = facts.begin(); i != facts.end(); ++i) { @@ -78,6 +89,8 @@ int to_json(char *facts_json, size_t facts_len) int value(const char *fact, char *value, size_t value_len) { + loadfacts(); + typedef map::iterator iter; iter i = facts.find(fact); if (i == facts.end()) From 790da64f3c04b6c59eb0aa9341be5f93456e164c Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 24 Feb 2014 08:14:59 -0800 Subject: [PATCH 1665/3753] (FACT-346) Remove deprecated selinux_mode fact See also https://projects.puppetlabs.com/issues/6677 --- lib/facter/selinux.rb | 11 ----------- spec/unit/selinux_spec.rb | 6 ------ 2 files changed, 17 deletions(-) diff --git a/lib/facter/selinux.rb b/lib/facter/selinux.rb index 9599d966c4..1bbeeaaf71 100644 --- a/lib/facter/selinux.rb +++ b/lib/facter/selinux.rb @@ -97,14 +97,3 @@ def selinux_mount_point result.chomp end end - -# This is a legacy fact which returns the old selinux_mode fact value to prevent -# breakages of existing manifests. It should be removed at the next major release. -# See ticket #6677. - -Facter.add("selinux_mode") do - confine :selinux => :true - setcode do - Facter.value(:selinux_config_policy) - end -end diff --git a/spec/unit/selinux_spec.rb b/spec/unit/selinux_spec.rb index 9241becad5..49964b899e 100755 --- a/spec/unit/selinux_spec.rb +++ b/spec/unit/selinux_spec.rb @@ -96,12 +96,6 @@ Facter.fact(:selinux_config_policy).value.should == "targeted" end - - it "should ensure legacy selinux_mode facts returns same value as selinux_config_policy fact" do - Facter.fact(:selinux_config_policy).stubs(:value).returns("targeted") - - Facter.fact(:selinux_mode).value.should == "targeted" - end end def sestatus_is(status) From 518c2f1e990bd720a779e210e1768432ba18ca7f Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 24 Feb 2014 14:04:09 -0800 Subject: [PATCH 1666/3753] (FACT-272) Format facter --help to work with ronn This commit updates the output of facter --help to create markdown that can be converted to a man page via ronn. --- lib/facter/application.rb | 56 +++++++++++++++++++++++++++++---------- 1 file changed, 42 insertions(+), 14 deletions(-) diff --git a/lib/facter/application.rb b/lib/facter/application.rb index 4300ac5daf..9d51237017 100644 --- a/lib/facter/application.rb +++ b/lib/facter/application.rb @@ -80,18 +80,23 @@ def self.parse(argv) options = {} parser = OptionParser.new do |opts| opts.banner = <<-BANNER -Synopsis -======== +facter(8) -- Gather system information +====== + +SYNOPSIS +-------- Collect and display facts about the system. -Usage -===== +USAGE +----- - facter [-d|--debug] [-h|--help] [-p|--puppet] [-v|--version] [-y|--yaml] [-j|--json] [--external-dir DIR] [--no-external-dir] [fact] [fact] [...] + facter [-h|--help] [-t|--timing] [-d|--debug] [-p|--puppet] [-v|--version] + [-y|--yaml] [-j|--json] [--plaintext] [--external-dir DIR] [--no-external-dir] + [fact] [fact] [...] -Description -=========== +DESCRIPTION +----------- Collect and display facts about the current system. The library behind Facter is easy to expand, making Facter an easy way to collect information @@ -100,19 +105,42 @@ def self.parse(argv) If no facts are specifically asked for, then all facts will be returned. EXAMPLE -======= - facter kernel +------- + +Display all facts: + + $ facter + architecture => amd64 + blockdevices => sda,sr0 + domain => example.com + fqdn => puppet.example.com + hardwaremodel => x86_64 + [...] + +Display a single fact: + + $ facter kernel + Linux + +Format facts as JSON: + + $ facter --json architecture kernel hardwaremodel + { + "architecture": "amd64", + "kernel": "Linux", + "hardwaremodel": "x86_64" + } AUTHOR -====== +------ Luke Kanies COPYRIGHT -========= - Copyright (c) 2011-2012 Puppet Labs, Inc Licensed under the Apache 2.0 license +--------- + Copyright (c) 2011-2014 Puppet Labs, Inc Licensed under the Apache 2.0 license -USAGE -===== +OPTIONS +------- BANNER opts.on("-y", "--yaml", From 47ab11a122a1a0375ec5db35e88346e0b26689b0 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 24 Feb 2014 14:06:05 -0800 Subject: [PATCH 1667/3753] (FACT-272) Update manpage based on facter --help --- man/man8/facter.8 | 149 +++++++++++++++++++++++++++++++--------------- 1 file changed, 100 insertions(+), 49 deletions(-) diff --git a/man/man8/facter.8 b/man/man8/facter.8 index 476c8ce55c..433d7c2908 100644 --- a/man/man8/facter.8 +++ b/man/man8/facter.8 @@ -1,51 +1,102 @@ -.TH "FACTER" "8" "September 2012" "Puppet Labs, Inc" "Facter manual" -.SH NAME -Facter - Collect and display facts about the system. -. -.SH SYNOPSIS -.sp -Collect and display facts about the system. -.SH USAGE -.INDENT 0.0 -.INDENT 3.5 -facter [\-d|\-\-debug] [\-h|\-\-help] [\-p|\-\-puppet] [\-v|\-\-version] [\-y|\-\-yaml] [\-j|\-\-json] [fact] [fact] [...] -.UNINDENT -.UNINDENT -.SH DESCRIPTION -.sp -Collect and display facts about the current system. The library behind -Facter is easy to expand, making Facter an easy way to collect -information about a system from within the shell or within Ruby. -.sp -If no facts are specifically asked for, then all facts will be returned. -.SH OPTIONS -.sp -yaml: Emit facts in YAML format. -.sp -json: Emit facts in JSON format. -.sp -puppet: Load the Puppet libraries, thus allowing Facter to load Puppet specific (custom) facts -.sp -version: Print the version and exit. -.sp -help: Print this help message. -.sp -debug: Enable debugging. -.sp -trace: Enable backtraces. -.sp -timing: Enable timing. -.SH EXAMPLE -.INDENT 0.0 -.INDENT 3.5 -facter kernel -.UNINDENT -.UNINDENT -.SH AUTHOR -.sp +.\" generated with Ronn/v0.7.3 +.\" http://github.com/rtomayko/ronn/tree/0.7.3 +. +.TH "FACTER" "8" "February 2014" "" "" +. +.SH "NAME" +\fBfacter\fR \- Gather system information +. +.SH "SYNOPSIS" +Collect and display facts about the system\. +. +.SH "USAGE" +. +.nf + +facter [\-h|\-\-help] [\-t|\-\-timing] [\-d|\-\-debug] [\-p|\-\-puppet] [\-v|\-\-version] + [\-y|\-\-yaml] [\-j|\-\-json] [\-\-plaintext] [\-\-external\-dir DIR] [\-\-no\-external\-dir] + [fact] [fact] [\.\.\.] +. +.fi +. +.SH "DESCRIPTION" +Collect and display facts about the current system\. The library behind Facter is easy to expand, making Facter an easy way to collect information about a system from within the shell or within Ruby\. +. +.P +If no facts are specifically asked for, then all facts will be returned\. +. +.SH "EXAMPLE" +Display all facts: +. +.IP "" 4 +. +.nf + +$ facter +architecture => amd64 +blockdevices => sda,sr0 +domain => example\.com +fqdn => puppet\.example\.com +hardwaremodel => x86_64 +[\.\.\.] +. +.fi +. +.IP "" 0 +. +.P +Display a single fact: +. +.IP "" 4 +. +.nf + +$ facter kernel +Linux +. +.fi +. +.IP "" 0 +. +.P +Format facts as JSON: +. +.IP "" 4 +. +.nf + +$ facter \-\-json architecture kernel hardwaremodel +{ + "architecture": "amd64", + "kernel": "Linux", + "hardwaremodel": "x86_64" +} +. +.fi +. +.IP "" 0 +. +.SH "AUTHOR" Luke Kanies -.SH COPYRIGHT -.sp -Copyright (c) 2012 Puppet Labs, Inc Licensed under the Apache 2.0 -license . +.SH "COPYRIGHT" +Copyright (c) 2011\-2014 Puppet Labs, Inc Licensed under the Apache 2\.0 license +. +.SH "OPTIONS" +. +.nf + +\-y, \-\-yaml Emit facts in YAML format\. +\-j, \-\-json Emit facts in JSON format\. + \-\-plaintext Emit facts in plaintext format\. + \-\-trace Enable backtraces\. + \-\-external\-dir DIR The directory to use for external facts\. + \-\-no\-external\-dir Turn off external facts\. +\-d, \-\-debug Enable debugging\. +\-t, \-\-timing Enable timing\. +\-p, \-\-puppet Load the Puppet libraries, thus allowing Facter to load Puppet\-specific facts\. +\-v, \-\-version Print the version and exit\. +\-h, \-\-help Print this help message\. +. +.fi + From 3c3ef4cfec2dd7b57aa17ad29f9cb602ab617e96 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 24 Feb 2014 14:24:37 -0800 Subject: [PATCH 1668/3753] Revert "Merge branch 'feature/facter-2/patition_uuid_fact' into facter-2" This reverts commit 33a4f18aa473b450f887e4eb4c8639c01403ea76, reversing changes made to 8102a72a90c43cd16e62c946e1b6646a42c08cd2. --- lib/facter/blockdevices.rb | 55 ++------------------------------ spec/unit/blockdevices_spec.rb | 58 +--------------------------------- 2 files changed, 4 insertions(+), 109 deletions(-) diff --git a/lib/facter/blockdevices.rb b/lib/facter/blockdevices.rb index 28da29f8ce..e0ddc48fb4 100644 --- a/lib/facter/blockdevices.rb +++ b/lib/facter/blockdevices.rb @@ -34,6 +34,7 @@ # Only supports Linux 2.6+ at this time, due to the reliance on sysfs # + # Fact: blockdevices # # Purpose: @@ -46,30 +47,6 @@ # Block devices must have been identified using sysfs information # -# Fact: blockdevice__partitions -# -# Purpose: -# Returns a comma separated list of partitions on the block device. -# -# Resolution: -# Parses the contents of /sys/block/* -# -# Caveats: -# Only supports Linux 2.6+ at this time, due to the reliance on sysfs -# - -# Fact: blockdevice__uuid -# -# Purpose: -# Returns the UUID of the partitions on blockdevices. -# -# Resolution: -# Parses /dev/disk/by-uuid and resolves the links back to the partitions in /dev -# -# Caveats: -# Only supports Linux 2.6+ at this time, due to the reliance on sysfs -# - # Author: Jason Gill require 'facter' @@ -95,8 +72,6 @@ sizefile = sysfs_block_directory + device + "/size" vendorfile = sysfs_device_directory + "/vendor" modelfile = sysfs_device_directory + "/model" - partitions = Dir.glob(sysfs_block_directory + device + "/#{device}*").map { |d| File.basename(d) } - devdisk_by_uuid_directory = '/dev/disk/by-uuid/' if File.exist?(sizefile) Facter.add("blockdevice_#{device}_size".to_sym) do @@ -116,33 +91,8 @@ end end - unless partitions.empty? - Facter.add("blockdevice_#{device}_partitions") do - setcode { partitions.join(',') } - end - end - - partitions.each do |part| - Facter.add("blockdevice_#{part}_uuid") do - setcode do - uuid = nil - if File.directory?(devdisk_by_uuid_directory) - Dir.entries(devdisk_by_uuid_directory).each do |file| - qualified_file = "#{devdisk_by_uuid_directory}#{file}" - - #A uuid is 16 octets long (RFC4122) which is 32hex chars + 4 '-'s - next unless file.length == 36 - next unless File.symlink?(qualified_file) - next unless File.readlink(qualified_file).match(%r[(?:\.\./\.\./|/dev/)#{part}$]) - - uuid = file - end - end - uuid - end - end - end end + end # Return a comma-seperated list of block devices found @@ -151,4 +101,5 @@ setcode { blockdevices.sort.join(',') } end end + end diff --git a/spec/unit/blockdevices_spec.rb b/spec/unit/blockdevices_spec.rb index 9d12cd931d..54620b8e07 100644 --- a/spec/unit/blockdevices_spec.rb +++ b/spec/unit/blockdevices_spec.rb @@ -47,44 +47,6 @@ IO.stubs(:read).with(stubdir + "/device/vendor").returns(vendor) if vendor IO.stubs(:read).with(stubdir + "/device/model").returns(model) if model end - - #Stubs relating to the Partition UUID facts - File.stubs(:exist?).with('/dev/disk/by-uuid/').returns(true) - - Dir.stubs(:glob).with("/sys/block/hda/hda*").returns([]) - - Dir.stubs(:glob).with("/sys/block/sda/sda*").returns([ - '/sys/block/sda/sda1', - '/sys/block/sda/sda2', - ]) - - Dir.stubs(:glob).with("/sys/block/sdb/sdb*").returns([ - '/sys/block/sda/sdb1', - '/sys/block/sda/sdb2', - ]) - - File.stubs(:directory?).returns(true) - - Dir.stubs(:entries).with('/dev/disk/by-uuid/').returns([ - ".", #wont match the length requirements - "..", #wont match the length requirements - "11111111-1111-1111-1111-111111111111", #Should be valid - "22222222-2222-2222-2222-222222222222", #Should be valid - "33333333-3333-3333-3333-333333333333", #Should be valid - "44444444-4444-4444-4444-444444444444", #Wont match the link regex - "55555555-5555-5555-5555-555555555555" #Wont be a symlink - ]) - - File.stubs(:readlink).with('/dev/disk/by-uuid/11111111-1111-1111-1111-111111111111').returns('../../sda1') - File.stubs(:readlink).with('/dev/disk/by-uuid/22222222-2222-2222-2222-222222222222').returns('../../sda2') - File.stubs(:readlink).with('/dev/disk/by-uuid/33333333-3333-3333-3333-333333333333').returns('../../sdb1') - File.stubs(:readlink).with('/dev/disk/by-uuid/44444444-4444-4444-4444-444444444444').returns('/dont/match/regex/sdb2') - - File.stubs(:symlink?).with('/dev/disk/by-uuid/11111111-1111-1111-1111-111111111111').returns(true) - File.stubs(:symlink?).with('/dev/disk/by-uuid/22222222-2222-2222-2222-222222222222').returns(true) - File.stubs(:symlink?).with('/dev/disk/by-uuid/33333333-3333-3333-3333-333333333333').returns(true) - File.stubs(:symlink?).with('/dev/disk/by-uuid/44444444-4444-4444-4444-444444444444').returns(true) - File.stubs(:symlink?).with('/dev/disk/by-uuid/55555555-5555-5555-5555-555555555555').returns(false) end it "should report three block devices, hda, sda, and sdb, with accurate information from sda and sdb, and without invalid . or .. entries" do @@ -102,32 +64,16 @@ Facter.fact("blockdevice_#{device}_size".to_sym).should_not == nil Facter.fact("blockdevice_#{device}_vendor".to_sym).should_not == nil Facter.fact("blockdevice_#{device}_model".to_sym).should_not == nil - Facter.fact("blockdevice_#{device}_partitions".to_sym).should_not == nil end Facter.fact(:blockdevice_sda_model).value.should == "WDC WD5000AAKS-0" Facter.fact(:blockdevice_sda_vendor).value.should == "ATA" Facter.fact(:blockdevice_sda_size).value.should == 500107862016 - Facter.fact(:blockdevice_sda_partitions).value.should == 'sda1,sda2' Facter.fact(:blockdevice_sdb_model).value.should == "PERC H700" Facter.fact(:blockdevice_sdb_vendor).value.should == "DELL" Facter.fact(:blockdevice_sdb_size).value.should == 4499246678016 - Facter.fact(:blockdevice_sdb_partitions).value.should == 'sdb1,sdb2' - - #These partitions should have a UUID - %w{ sda1 sda2 sdb1 }.each do |d| - Facter.value("blockdevice_#{d}_uuid".to_sym).should_not == nil - end - #These should not create a UUID fact - [ ".", "..", "sdb2" ].each do |d| - Facter.value("partition_#{d}_uuid".to_sym).should == nil - end - - Facter.fact(:blockdevice_sda1_uuid).value.should == "11111111-1111-1111-1111-111111111111" - Facter.fact(:blockdevice_sda2_uuid).value.should == "22222222-2222-2222-2222-222222222222" - Facter.fact(:blockdevice_sdb1_uuid).value.should == "33333333-3333-3333-3333-333333333333" end end @@ -142,9 +88,6 @@ File.stubs(:exist?).with("/sys/block/../device").returns(false) File.stubs(:exist?).with("/sys/block/xda/device").returns(false) File.stubs(:exist?).with("/sys/block/ydb/device").returns(false) - - #This is here to surpress errors when Dir.entries is run in relation to uuids - Dir.stubs(:entries).with('/dev/disk/by-uuid/').returns([]) end it "should not exist with invalid entries in /sys/block" do @@ -161,5 +104,6 @@ end end + end end From df7609450b8ac38d6f1e89c84cd5bdf8dc1e182c Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 24 Feb 2014 14:24:56 -0800 Subject: [PATCH 1669/3753] Revert "(fact-234) Add newly defined blockdevice dynamic facts to the schema" This reverts commit 0b1d424ae67a79d0072f4d2dad409a2ad79ddc5a. --- schema/facter.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/schema/facter.json b/schema/facter.json index 70bac16fe0..da40fe3b03 100644 --- a/schema/facter.json +++ b/schema/facter.json @@ -18,8 +18,6 @@ "^blockdevice_[A-Za-z0-9]+_size$" : { "type": "integer" }, "^blockdevice_[A-Za-z0-9]+_vendor$" : { "type": "string" }, "^blockdevice_[A-Za-z0-9]+_model$" : { "type": "string" }, - "^blockdevice_[A-Za-z0-9]+_uuid$" : { "type": "string" }, - "^blockdevice_[A-Za-z0-9]+_partitions$" : { "type": "string" }, "^mtu_[A-Za-z0-9]+$" : { "type": "string" }, "^netmask_[A-Za-z0-9_]+$" : { "$ref" : "#/definitions/ipaddress" }, "^network_[A-Za-z0-9_]+$" : { "$ref" : "#/definitions/ipaddress" }, From fb89f8f94a4ae79084f5d43fa98a9005f06b429b Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 24 Feb 2014 15:03:08 -0800 Subject: [PATCH 1670/3753] Revert "Merge branch 'facter-2'" This reverts commit c3ae5259fb57c853428fe83b5158f3a00dd2db51, reversing changes made to e8ab81eb1bbbff1e8dc8e64ba3be9c89b57d7036. --- schema/facter.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/schema/facter.json b/schema/facter.json index 6636a25268..c47dd12658 100644 --- a/schema/facter.json +++ b/schema/facter.json @@ -18,6 +18,8 @@ "^blockdevice_[A-Za-z0-9]+_size$" : { "type": "integer" }, "^blockdevice_[A-Za-z0-9]+_vendor$" : { "type": "string" }, "^blockdevice_[A-Za-z0-9]+_model$" : { "type": "string" }, + "^blockdevice_[A-Za-z0-9]+_uuid$" : { "type": "string" }, + "^blockdevice_[A-Za-z0-9]+_partitions$" : { "type": "string" }, "^mtu_[A-Za-z0-9]+$" : { "type": "string" }, "^netmask_[A-Za-z0-9_]+$" : { "$ref" : "#/definitions/ipaddress" }, "^network_[A-Za-z0-9_]+$" : { "$ref" : "#/definitions/ipaddress" }, From f4ae5eddaa9e7b06077091b7d3e2356604e18eb6 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 24 Feb 2014 15:07:51 -0800 Subject: [PATCH 1671/3753] (maint) Properly merge up c3ae525 The merge commit in c3ae525 was completely broken, this commit actually merges the revert commit. --- lib/facter/blockdevices.rb | 39 +----------------- lib/facter/util/blockdevices.rb | 16 -------- lib/facter/util/blockdevices/linux.rb | 28 +------------ spec/unit/blockdevices_spec.rb | 58 +-------------------------- 4 files changed, 4 insertions(+), 137 deletions(-) diff --git a/lib/facter/blockdevices.rb b/lib/facter/blockdevices.rb index a445cfb6e0..98aab69ffa 100644 --- a/lib/facter/blockdevices.rb +++ b/lib/facter/blockdevices.rb @@ -34,6 +34,7 @@ # Only supports Linux 2.6+ at this time, due to the reliance on sysfs # + # Fact: blockdevices # # Purpose: @@ -46,30 +47,6 @@ # Block devices must have been identified using sysfs information # -# Fact: blockdevice__partitions -# -# Purpose: -# Returns a comma separated list of partitions on the block device. -# -# Resolution: -# Parses the contents of /sys/block/* -# -# Caveats: -# Only supports Linux 2.6+ at this time, due to the reliance on sysfs -# - -# Fact: blockdevice__uuid -# -# Purpose: -# Returns the UUID of the partitions on blockdevices. -# -# Resolution: -# Parses /dev/disk/by-uuid and resolves the links back to the partitions in /dev -# -# Caveats: -# Only supports Linux 2.6+ at this time, due to the reliance on sysfs -# - # Author: Jason Gill require 'facter' @@ -90,19 +67,7 @@ end Facter.add("blockdevice_#{device}_model") do - setcode { Facter::Util::Blockdevices.device_model(device) } - end - - Facter.add("blockdevice_#{device}_partitions") do - confine :kernel => "Linux" - setcode { Facter::Util::Blockdevices.device_partitions(device).join(',') } - end - - Facter::Util::Blockdevices.device_partitions(device).each do |part| - Facter.add("blockdevice_#{part}_uuid") do - confine :kernel => "Linux" - setcode { Facter::Util::Blockdevices.partition_uuid(part) } - end + setcode { Facter::Util::Blockdevices.device_model(device)} end end end diff --git a/lib/facter/util/blockdevices.rb b/lib/facter/util/blockdevices.rb index 7dde6cb0b3..4bcef493b9 100644 --- a/lib/facter/util/blockdevices.rb +++ b/lib/facter/util/blockdevices.rb @@ -12,10 +12,6 @@ module NoImplementation def self.devices [] end - - def self.device_partitions - [] - end end def self.implementation @@ -38,18 +34,6 @@ def self.device_size(device_name) implementation.device_size(device_name) end - def self.device_partitions(device_name) - if Facter.value(:kernel) == 'Linux' - implementation.device_partitions(device_name) - else - NoImplementation.device_partitions - end - end - - def self.partition_uuid(partition_name) - implementation.partition_uuid(partition_name) - end - def self.available? !self.devices.empty? end diff --git a/lib/facter/util/blockdevices/linux.rb b/lib/facter/util/blockdevices/linux.rb index 93f76f92c8..0c57a3a632 100644 --- a/lib/facter/util/blockdevices/linux.rb +++ b/lib/facter/util/blockdevices/linux.rb @@ -2,8 +2,7 @@ module Facter::Util::Blockdevices module Linux # Only Linux 2.6+ kernels support sysfs which is required to easily get device details - SYSFS_BLOCK_DIRECTORY = '/sys/block/' - DEVDISK_BY_UUID_DIRECTORY = '/dev/disk/by-uuid/' + SYSFS_BLOCK_DIRECTORY = '/sys/block/' def self.read_if_exists(f) if File.exist?(f) @@ -48,31 +47,6 @@ def self.device_dir(device) SYSFS_BLOCK_DIRECTORY + device + "/device" end - def self.device_partitions(device) - Dir.glob( SYSFS_BLOCK_DIRECTORY + device + "/#{device}*" ).map do |d| - File.basename(d) - end - end - - def self.partition_uuid(partition) - if File.directory?(DEVDISK_BY_UUID_DIRECTORY) - Dir.entries(DEVDISK_BY_UUID_DIRECTORY).each do |file| - uuid = nil - qualified_file = "#{DEVDISK_BY_UUID_DIRECTORY}#{file}" - - #A uuid is 16 octets long (RFC4122) which is 32hex chars + 4 '-'s - next unless file.length == 36 - next unless File.symlink?(qualified_file) - next unless File.readlink(qualified_file).match(%r[(?:\.\./\.\./|/dev/)#{partition}$]) - - uuid = file - return uuid - end - end - - uuid - end - end end diff --git a/spec/unit/blockdevices_spec.rb b/spec/unit/blockdevices_spec.rb index 5f8acb5c53..0d62eadb47 100644 --- a/spec/unit/blockdevices_spec.rb +++ b/spec/unit/blockdevices_spec.rb @@ -129,44 +129,6 @@ IO.stubs(:read).with(stubdir + "/device/vendor").returns(vendor) if vendor IO.stubs(:read).with(stubdir + "/device/model").returns(model) if model end - - #Stubs relating to the Partition UUID facts - File.stubs(:exist?).with('/dev/disk/by-uuid/').returns(true) - - Dir.stubs(:glob).with("/sys/block/hda/hda*").returns([]) - - Dir.stubs(:glob).with("/sys/block/sda/sda*").returns([ - '/sys/block/sda/sda1', - '/sys/block/sda/sda2', - ]) - - Dir.stubs(:glob).with("/sys/block/sdb/sdb*").returns([ - '/sys/block/sda/sdb1', - '/sys/block/sda/sdb2', - ]) - - File.stubs(:directory?).returns(true) - - Dir.stubs(:entries).with('/dev/disk/by-uuid/').returns([ - ".", #wont match the length requirements - "..", #wont match the length requirements - "11111111-1111-1111-1111-111111111111", #Should be valid - "22222222-2222-2222-2222-222222222222", #Should be valid - "33333333-3333-3333-3333-333333333333", #Should be valid - "44444444-4444-4444-4444-444444444444", #Wont match the link regex - "55555555-5555-5555-5555-555555555555" #Wont be a symlink - ]) - - File.stubs(:readlink).with('/dev/disk/by-uuid/11111111-1111-1111-1111-111111111111').returns('../../sda1') - File.stubs(:readlink).with('/dev/disk/by-uuid/22222222-2222-2222-2222-222222222222').returns('../../sda2') - File.stubs(:readlink).with('/dev/disk/by-uuid/33333333-3333-3333-3333-333333333333').returns('../../sdb1') - File.stubs(:readlink).with('/dev/disk/by-uuid/44444444-4444-4444-4444-444444444444').returns('/dont/match/regex/sdb2') - - File.stubs(:symlink?).with('/dev/disk/by-uuid/11111111-1111-1111-1111-111111111111').returns(true) - File.stubs(:symlink?).with('/dev/disk/by-uuid/22222222-2222-2222-2222-222222222222').returns(true) - File.stubs(:symlink?).with('/dev/disk/by-uuid/33333333-3333-3333-3333-333333333333').returns(true) - File.stubs(:symlink?).with('/dev/disk/by-uuid/44444444-4444-4444-4444-444444444444').returns(true) - File.stubs(:symlink?).with('/dev/disk/by-uuid/55555555-5555-5555-5555-555555555555').returns(false) end it "should report three block devices, hda, sda, and sdb, with accurate information from sda and sdb, and without invalid . or .. entries" do @@ -184,32 +146,16 @@ Facter.value("blockdevice_#{device}_size".to_sym).should_not == nil Facter.value("blockdevice_#{device}_vendor".to_sym).should_not == nil Facter.value("blockdevice_#{device}_model".to_sym).should_not == nil - Facter.value("blockdevice_#{device}_partitions".to_sym).should_not == nil end Facter.fact(:blockdevice_sda_model).value.should == "WDC WD5000AAKS-0" Facter.fact(:blockdevice_sda_vendor).value.should == "ATA" Facter.fact(:blockdevice_sda_size).value.should == 500107862016 - Facter.fact(:blockdevice_sda_partitions).value.should == 'sda1,sda2' Facter.fact(:blockdevice_sdb_model).value.should == "PERC H700" Facter.fact(:blockdevice_sdb_vendor).value.should == "DELL" Facter.fact(:blockdevice_sdb_size).value.should == 4499246678016 - Facter.fact(:blockdevice_sdb_partitions).value.should == 'sdb1,sdb2' - - #These partitions should have a UUID - %w{ sda1 sda2 sdb1 }.each do |d| - Facter.value("blockdevice_#{d}_uuid".to_sym).should_not == nil - end - #These should not create a UUID fact - [ ".", "..", "sdb2" ].each do |d| - Facter.value("partition_#{d}_uuid".to_sym).should == nil - end - - Facter.fact(:blockdevice_sda1_uuid).value.should == "11111111-1111-1111-1111-111111111111" - Facter.fact(:blockdevice_sda2_uuid).value.should == "22222222-2222-2222-2222-222222222222" - Facter.fact(:blockdevice_sdb1_uuid).value.should == "33333333-3333-3333-3333-333333333333" end end @@ -224,9 +170,6 @@ File.stubs(:exist?).with("/sys/block/../device").returns(false) File.stubs(:exist?).with("/sys/block/xda/device").returns(false) File.stubs(:exist?).with("/sys/block/ydb/device").returns(false) - - #This is here to surpress errors when Dir.entries is run in relation to uuids - Dir.stubs(:entries).with('/dev/disk/by-uuid/').returns([]) end it "should not exist with invalid entries in /sys/block" do @@ -243,5 +186,6 @@ end end + end end From f82cf015d2f098a7ba688127a9b83d6ae021f3df Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Tue, 25 Feb 2014 11:09:26 -0800 Subject: [PATCH 1672/3753] (FACT-346) Remove selinux_mode from schema The selinux_mode fact was removed in 544260f, removing from the schema also. --- schema/facter.json | 1 - 1 file changed, 1 deletion(-) diff --git a/schema/facter.json b/schema/facter.json index da40fe3b03..ddf9191aa9 100644 --- a/schema/facter.json +++ b/schema/facter.json @@ -92,7 +92,6 @@ "selinux_config_policy" : { "type": "string" }, "selinux_current_mode" : { "type": "string" }, "selinux_enforced" : { "type": "string" }, - "selinux_mode" : { "type": "string" }, "selinux_policyversion" : { "type": "string" }, "serialnumber" : { "type": "string" }, "sp_boot_mode" : { "type": "string" }, From a2ba187763c61ae309cffaa63c66b1d05d2c9dd7 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Wed, 26 Feb 2014 17:51:13 -0800 Subject: [PATCH 1673/3753] Implement building with CMake. Moved code for the cfacter executable under the exe directory. Moved code for the cfacter shared library under the lib directory. Moved the rapidjson code to be under the lib directory for now (probably belongs in a shared location). --- .gitignore | 17 ++++-- CMakeLists.txt | 14 +++++ Makefile | 37 ------------ README.md | 56 +++++++++++++++++++ exe/CMakeLists.txt | 27 +++++++++ cfacter.cc => exe/cfacter.cc | 0 lib/CMakeLists.txt | 13 +++++ cfacterimpl.cc => lib/cfacterimpl.cc | 28 +++++----- cfacterimpl.h => lib/cfacterimpl.h | 0 cfacterlib.cc => lib/cfacterlib.cc | 9 +-- cfacterlib.h => lib/cfacterlib.h | 0 {rapidjson => lib/rapidjson}/document.h | 0 {rapidjson => lib/rapidjson}/filestream.h | 0 {rapidjson => lib/rapidjson}/internal/pow10.h | 0 {rapidjson => lib/rapidjson}/internal/stack.h | 0 .../rapidjson}/internal/strfunc.h | 0 {rapidjson => lib/rapidjson}/prettywriter.h | 0 {rapidjson => lib/rapidjson}/rapidjson.h | 0 {rapidjson => lib/rapidjson}/reader.h | 0 {rapidjson => lib/rapidjson}/stringbuffer.h | 0 {rapidjson => lib/rapidjson}/writer.h | 0 version.h.in | 8 +++ 22 files changed, 148 insertions(+), 61 deletions(-) create mode 100644 CMakeLists.txt delete mode 100644 Makefile create mode 100644 exe/CMakeLists.txt rename cfacter.cc => exe/cfacter.cc (100%) create mode 100644 lib/CMakeLists.txt rename cfacterimpl.cc => lib/cfacterimpl.cc (99%) rename cfacterimpl.h => lib/cfacterimpl.h (100%) rename cfacterlib.cc => lib/cfacterlib.cc (94%) rename cfacterlib.h => lib/cfacterlib.h (100%) rename {rapidjson => lib/rapidjson}/document.h (100%) rename {rapidjson => lib/rapidjson}/filestream.h (100%) rename {rapidjson => lib/rapidjson}/internal/pow10.h (100%) rename {rapidjson => lib/rapidjson}/internal/stack.h (100%) rename {rapidjson => lib/rapidjson}/internal/strfunc.h (100%) rename {rapidjson => lib/rapidjson}/prettywriter.h (100%) rename {rapidjson => lib/rapidjson}/rapidjson.h (100%) rename {rapidjson => lib/rapidjson}/reader.h (100%) rename {rapidjson => lib/rapidjson}/stringbuffer.h (100%) rename {rapidjson => lib/rapidjson}/writer.h (100%) create mode 100644 version.h.in diff --git a/.gitignore b/.gitignore index f2a135fe83..39730f97dc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,13 @@ *.swp -*.so -*.o -*~ -cfacter -cfacter.DSYM + +# CMake +CMakeCache.txt +CMakeFiles +Makefile +cmake_install.cmake +install_manifest.txt + +# Generated files +/version.h +/exe/cfacter +/lib/libcfacter.so diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000000..c0135f526c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 2.8) +project(CFACTER) + +set(CFACTER_VERSION_MAJOR 0) +set(CFACTER_VERSION_MINOR 1) +set(CFACTER_VERSION_PATCH 0) + +# Generate a file containing the above version numbers +configure_file ( + "${PROJECT_SOURCE_DIR}/version.h.in" + "${PROJECT_SOURCE_DIR}/version.h" +) + +add_subdirectory(exe) \ No newline at end of file diff --git a/Makefile b/Makefile deleted file mode 100644 index 8012a3edd1..0000000000 --- a/Makefile +++ /dev/null @@ -1,37 +0,0 @@ -# Base compiler flags: debug and c++0x (maybe c++11 later?) -CCFLAGS = -std=c++0x -g - -UNAME_S := $(shell uname -s) -ifeq ($(UNAME_S),Darwin) - # because Darwin has two c++ libs and this one works better - # need to investigate this more - CCFLAGS += --stdlib=libc++ - CCFLAGS += -Wno-tautological-constant-out-of-range-compare -endif - -all: cfacter libcfacter.so - -cfacter: cfacter.cc cfacterlib.cc cfacterimpl.cc cfacterlib.h cfacterimpl.h - g++ ${CCFLAGS} -o cfacter cfacter.cc cfacterlib.cc cfacterimpl.cc -I C - -cfacterlib.o: cfacterlib.cc cfacterlib.h cfacterimpl.h - g++ ${CCFLAGS} -fPIC -c -o $@ cfacterlib.cc -I . - -cfacterimpl.o: cfacterimpl.cc cfacterimpl.h - g++ ${CCFLAGS} -fPIC -c -o $@ cfacterimpl.cc -I . - -libcfacter.so: cfacterlib.o cfacterimpl.o - g++ ${CCFLAGS} -o $@ $^ -fPIC -shared - -install: libcfacter.so - -cp libcfacter.so $(HOME)/lib - -clean: - -rm cfacterlib.o cfacterimpl.o cfacterlib.so cfacter 2> /dev/null - -missing: - -$(shell facter | grep "=>" | cut -f1 -d' ' | sort > /tmp/facter.txt) - -$(shell ./cfacter | grep "=>" | cut -f1 -d' ' | sort > /tmp/cfacter.txt) - -@$(shell diff /tmp/facter.txt /tmp/cfacter.txt > /tmp/facterdiff.txt | true) - -@cat /tmp/facterdiff.txt - -@rm /tmp/facter.txt /tmp/cfacter.txt /tmp/facterdiff.txt diff --git a/README.md b/README.md index a825f9ca8a..015001f1b7 100644 --- a/README.md +++ b/README.md @@ -2,3 +2,59 @@ cfacter ======= Tinkering with a C/C++ facter + +Requirements +------------ + +* CMake >= 2.8 + +Generating Build Files +---------------------- + +Before building cfacter, use `cmake` to generate build files: + +`$ cmake .` + +Build +----- + +To build cfacter, use 'make': + +`$ make` + +To build cfacter with debug information: + +`$ cmake -DCMAKE_BUILD_TYPE=Debug . && make clean all` + +To turn off debug information: + +`$ cmake -DCMAKE_BUILD_TYPE= . && make clean all` + +Run +--- + +You can run cfacter from where it was built: + +`$ exe/cfacter` + +Install +------- + +You can install cfacter into your system: + +`$ sudo make install` + +By default, this will install cfacter into `/usr/local/bin`, using an install prefix of `/usr/local`. + +To install with a different prefix: + +`$ cmake -DCMAKE_INSTALL_PREFIX=/usr . && sudo make clean install` + +This would install cfacter into `/usr/bin`. + +Uninstall +--------- + +Run the following command to remove files that were previously installed: + +`$ sudo xargs rm < install_manifest.txt` \ No newline at end of file diff --git a/exe/CMakeLists.txt b/exe/CMakeLists.txt new file mode 100644 index 0000000000..6d95761029 --- /dev/null +++ b/exe/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 2.8) +project(CFACTER) + +set(CFACTER_SOURCES + ${PROJECT_SOURCE_DIR}/cfacter.cc +) + +set(LIBCFACTER_DIR ../lib) + +add_subdirectory(${LIBCFACTER_DIR} ${LIBCFACTER_DIR}) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra -Werror -Wno-unused-parameter") + +include_directories( + ${PROJECT_SOURCE_DIR}/${LIBCFACTER_DIR} +) + +link_directories( + ${PROJECT_SOURCE_DIR}/${LIBCFACTER_DIR} +) + +# Add install location of lib to installed cfacter +set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") + +add_executable(cfacter ${CFACTER_SOURCES}) +target_link_libraries(cfacter libcfacter) +install(TARGETS cfacter DESTINATION bin) \ No newline at end of file diff --git a/cfacter.cc b/exe/cfacter.cc similarity index 100% rename from cfacter.cc rename to exe/cfacter.cc diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt new file mode 100644 index 0000000000..a282135997 --- /dev/null +++ b/lib/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 2.8) +project(CFACTER) + +set(CFACTERLIB_SOURCES + ${PROJECT_SOURCE_DIR}/cfacterlib.cc + ${PROJECT_SOURCE_DIR}/cfacterimpl.cc +) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra -Werror -Wno-unused-parameter") + +add_library(libcfacter SHARED ${CFACTERLIB_SOURCES}) +set_target_properties(libcfacter PROPERTIES PREFIX "") +install (TARGETS libcfacter DESTINATION lib) \ No newline at end of file diff --git a/cfacterimpl.cc b/lib/cfacterimpl.cc similarity index 99% rename from cfacterimpl.cc rename to lib/cfacterimpl.cc index 2fa1c8ba7e..d351da14a2 100644 --- a/cfacterimpl.cc +++ b/lib/cfacterimpl.cc @@ -1,9 +1,9 @@ #include #include -#include -#include +#include +#include #include -#include +#include #include #include #include @@ -185,9 +185,7 @@ void get_network_facts(fact_map& facts) } facts[string("mtu_") + r->ifr_name] = to_string(r->ifr_mtu); - if (primaryInterface) - ; // no unmarked version of this network fact - + // netmask and network are both derived from the same ioctl if (ioctl(s, SIOCGIFNETMASK, r) < 0) { perror("ioctl SIOCGIFNETMASK"); @@ -267,7 +265,7 @@ static void get_lsb_facts(fact_map& facts) unsigned sep = line.find("="); string key = line.substr(0, sep); string value = line.substr(sep + 1, string::npos); - + if (key == "DISTRIB_ID") { facts["lsbdistid"] = value; facts["operatingsystem"] = value; @@ -350,7 +348,7 @@ void get_virtual_facts(fact_map& facts) // lspci time is ~40 ms. // virtual could be discovered in lots of places so requires some special handling - + facts["is_virtual"] = "false"; facts["virtual"] = "physical"; } @@ -401,7 +399,7 @@ void get_blockdevice_facts(fact_map& facts) break; } } - + if (!real_block_device) continue; // add it to the blockdevices list, careful with the comma @@ -448,7 +446,7 @@ static void get_mem_fact(std::string fact_name, int fact_value, fact_map& facts, { float fact_value_scaled = fact_value / 1024.0; char float_buf[32]; - + if (get_mb_variant) { snprintf(float_buf, sizeof(float_buf) - 1, "%.2f", fact_value_scaled); facts[string(fact_name) + "_mb"] = float_buf; @@ -458,9 +456,9 @@ static void get_mem_fact(std::string fact_name, int fact_value, fact_map& facts, for (scale_index = 0; fact_value_scaled > 1024.0; fact_value_scaled /= 1024.0, ++scale_index) ; - + std::string scale[4] = {"MB", "GB", "TB", "PB"}; // oh yeah, petabytes ... - + snprintf(float_buf, sizeof(float_buf) - 1, "%.2f", fact_value_scaled); facts[fact_name] = string(float_buf) + scale[scale_index]; } @@ -522,7 +520,7 @@ static string get_selinux_path() tokenize(line, tokens); if (tokens.size() < 2) continue; if (tokens[0] != "selinuxfs") continue; - + selinux_path = tokens[1]; break; } @@ -610,7 +608,7 @@ static void get_ssh_fact(string fact_name, string path_name, fact_map& facts) "/etc/opt/ssh", }; - for (int i = 0; i < sizeof(ssh_directories) / sizeof(string); ++i) { + for (size_t i = 0; i < sizeof(ssh_directories) / sizeof(string); ++i) { string full_path = ssh_directories[i] + "/" + path_name; if (file_exist(full_path)) { string key = read_oneline_file(full_path); @@ -865,7 +863,7 @@ void get_hostname_facts(fact_map& facts) unsigned sep = hostname_output.find("."); string hostname1 = hostname_output.substr(0, sep); string hostname = trim(hostname1); - + ifstream resolv_conf_file("/etc/resolv.conf", std::ifstream::in); string line; string domain; diff --git a/cfacterimpl.h b/lib/cfacterimpl.h similarity index 100% rename from cfacterimpl.h rename to lib/cfacterimpl.h diff --git a/cfacterlib.cc b/lib/cfacterlib.cc similarity index 94% rename from cfacterlib.cc rename to lib/cfacterlib.cc index 0b49f79922..7bf960b8b2 100644 --- a/cfacterlib.cc +++ b/lib/cfacterlib.cc @@ -1,10 +1,11 @@ +#include "../version.h" #include "cfacterlib.h" #include "cfacterimpl.h" #include #include #include -#include +#include #include "rapidjson/document.h" #include "rapidjson/prettywriter.h" @@ -30,11 +31,11 @@ void loadfacts() if (!facts.empty()) return; - facts["cfacterversion"] = "0.0.1"; + facts["cfacterversion"] = CFACTER_VERSION; list external_directories; external_directories.push_back("/etc/facter/facts.d"); - + get_network_facts(facts); get_kernel_facts(facts); get_blockdevice_facts(facts); @@ -55,7 +56,7 @@ void loadfacts() get_external_facts(facts, external_directories); } -int to_json(char *facts_json, size_t facts_len) +int to_json(char *facts_json, size_t facts_len) { loadfacts(); diff --git a/cfacterlib.h b/lib/cfacterlib.h similarity index 100% rename from cfacterlib.h rename to lib/cfacterlib.h diff --git a/rapidjson/document.h b/lib/rapidjson/document.h similarity index 100% rename from rapidjson/document.h rename to lib/rapidjson/document.h diff --git a/rapidjson/filestream.h b/lib/rapidjson/filestream.h similarity index 100% rename from rapidjson/filestream.h rename to lib/rapidjson/filestream.h diff --git a/rapidjson/internal/pow10.h b/lib/rapidjson/internal/pow10.h similarity index 100% rename from rapidjson/internal/pow10.h rename to lib/rapidjson/internal/pow10.h diff --git a/rapidjson/internal/stack.h b/lib/rapidjson/internal/stack.h similarity index 100% rename from rapidjson/internal/stack.h rename to lib/rapidjson/internal/stack.h diff --git a/rapidjson/internal/strfunc.h b/lib/rapidjson/internal/strfunc.h similarity index 100% rename from rapidjson/internal/strfunc.h rename to lib/rapidjson/internal/strfunc.h diff --git a/rapidjson/prettywriter.h b/lib/rapidjson/prettywriter.h similarity index 100% rename from rapidjson/prettywriter.h rename to lib/rapidjson/prettywriter.h diff --git a/rapidjson/rapidjson.h b/lib/rapidjson/rapidjson.h similarity index 100% rename from rapidjson/rapidjson.h rename to lib/rapidjson/rapidjson.h diff --git a/rapidjson/reader.h b/lib/rapidjson/reader.h similarity index 100% rename from rapidjson/reader.h rename to lib/rapidjson/reader.h diff --git a/rapidjson/stringbuffer.h b/lib/rapidjson/stringbuffer.h similarity index 100% rename from rapidjson/stringbuffer.h rename to lib/rapidjson/stringbuffer.h diff --git a/rapidjson/writer.h b/lib/rapidjson/writer.h similarity index 100% rename from rapidjson/writer.h rename to lib/rapidjson/writer.h diff --git a/version.h.in b/version.h.in new file mode 100644 index 0000000000..247a4c82f6 --- /dev/null +++ b/version.h.in @@ -0,0 +1,8 @@ +#define CFACTER_VERSION_MAJOR @CFACTER_VERSION_MAJOR@ +#define CFACTER_VERSION_MINOR @CFACTER_VERSION_MINOR@ +#define CFACTER_VERSION_PATCH @CFACTER_VERSION_PATCH@ + +#define STRINGIFYX(x) #x +#define STRINGIFY(x) STRINGIFYX(x) + +#define CFACTER_VERSION STRINGIFY(CFACTER_VERSION_MAJOR) "." STRINGIFY(CFACTER_VERSION_MINOR) "." STRINGIFY(CFACTER_VERSION_PATCH) \ No newline at end of file From 52005bbef96d4ac55e42c50278ee2f8fb815d4d9 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Wed, 26 Feb 2014 22:52:49 -0800 Subject: [PATCH 1674/3753] Fix build on OSX. Ignore Clang warning treated as error coming from rapidjson. --- .gitignore | 1 + lib/CMakeLists.txt | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index 39730f97dc..4a4f24f641 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ install_manifest.txt /version.h /exe/cfacter /lib/libcfacter.so +/lib/libcfacter.dylib diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index a282135997..fe5af5ff10 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -8,6 +8,10 @@ set(CFACTERLIB_SOURCES set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra -Werror -Wno-unused-parameter") +if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-tautological-constant-out-of-range-compare") +endif() + add_library(libcfacter SHARED ${CFACTERLIB_SOURCES}) set_target_properties(libcfacter PROPERTIES PREFIX "") install (TARGETS libcfacter DESTINATION lib) \ No newline at end of file From e07c48f9f8aff226e3e36c6efad72518af066659 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 13 Feb 2014 10:21:26 -0800 Subject: [PATCH 1675/3753] (FACT-349) Surface error messages when rescuing exceptions --- lib/facter/application.rb | 16 ++++++---------- lib/facter/core/logging.rb | 33 +++++++++++++++++++++++++++++++++ lib/facter/core/resolvable.rb | 10 +++++----- lib/facter/util/collection.rb | 2 +- lib/facter/util/fact.rb | 2 +- lib/facter/util/loader.rb | 2 +- lib/facter/util/parser.rb | 4 +--- spec/unit/application_spec.rb | 15 ++++++++++++++- 8 files changed, 62 insertions(+), 22 deletions(-) diff --git a/lib/facter/application.rb b/lib/facter/application.rb index 9d51237017..0618c206de 100644 --- a/lib/facter/application.rb +++ b/lib/facter/application.rb @@ -11,7 +11,7 @@ def self.create_directory_loader(dir) begin Facter::Util::Config.ext_fact_loader = Facter::Util::DirectoryLoader.loader_for(dir) rescue Facter::Util::DirectoryLoader::NoSuchDirectoryError => error - $stderr.puts "Specified external facts directory #{dir} does not exist." + Facter.log_exception(error, "Specified external facts directory #{dir} does not exist.") exit(1) end end @@ -36,8 +36,8 @@ def self.run(argv) begin facts[name] = Facter.value(name) rescue => error - $stderr.puts "Could not retrieve #{name}: #{error}" - exit 10 + Facter.log_exception(error, "Could not retrieve #{name}: #{error.message}") + exit(10) end end end @@ -61,12 +61,8 @@ def self.run(argv) exit(0) rescue => e - if options && options[:trace] - raise e - else - $stderr.puts "Error: #{e}" - exit(12) - end + Facter.log_exception(e) + exit(12) end private @@ -151,7 +147,7 @@ def self.parse(argv) opts.on("--plaintext", "Emit facts in plaintext format.") { |v| options[:plaintext] = v } opts.on("--trace", - "Enable backtraces.") { |v| options[:trace] = v } + "Enable backtraces.") { |v| Facter.trace(true) } opts.on("--external-dir DIR", "The directory to use for external facts.") { |v| create_directory_loader(v) } opts.on("--no-external-dir", diff --git a/lib/facter/core/logging.rb b/lib/facter/core/logging.rb index 7c46d96dac..4cc2632724 100644 --- a/lib/facter/core/logging.rb +++ b/lib/facter/core/logging.rb @@ -13,6 +13,9 @@ module Facter::Core::Logging @@debug = false # @api private @@timing = false + # @api private + @@trace = false + # @api private @@warn_messages = {} # @api private @@ -79,6 +82,28 @@ def warnonce(msg) end end + def log_exception(exception, message = :default) + self.warn(format_exception(exception, message, @@trace)) + end + + def format_exception(exception, message, trace) + arr = [] + + if message == :default + arr << exception.message + elsif message + arr << message + end + + if trace + arr.concat(exception.backtrace) + end + + arr.flatten.join("\n") + end + + # Print an exception message, and optionally a backtrace if trace is set + # Print timing information # # @param string [String] the time to print @@ -125,6 +150,14 @@ def timing? @@timing end + def trace(bool) + @@trace = bool + end + + def trace? + @@trace + end + # Clears the seen state of warning messages. See {warnonce}. # # @return [void] diff --git a/lib/facter/core/resolvable.rb b/lib/facter/core/resolvable.rb index 46bbad43e2..615aebe311 100644 --- a/lib/facter/core/resolvable.rb +++ b/lib/facter/core/resolvable.rb @@ -66,13 +66,13 @@ def value Facter::Util::Normalization.normalize(result) rescue Timeout::Error => detail - Facter.warn "Timed out after #{limit} seconds while resolving #{qualified_name}" + Facter.log_exception(detail, "Timed out after #{limit} seconds while resolving #{qualified_name}") return nil - rescue Facter::Util::Normalization::NormalizationError => e - Facter.warn "Fact resolution #{qualified_name} resolved to an invalid value: #{e.message}" + rescue Facter::Util::Normalization::NormalizationError => detail + Facter.log_exception(detail, "Fact resolution #{qualified_name} resolved to an invalid value: #{detail.message}") return nil - rescue => details - Facter.warn "Could not retrieve #{qualified_name}: #{details.message}" + rescue => detail + Facter.log_exception(detail, "Could not retrieve #{qualified_name}: #{detail.message}") return nil end diff --git a/lib/facter/util/collection.rb b/lib/facter/util/collection.rb index aac876f6db..be3f6f8dca 100644 --- a/lib/facter/util/collection.rb +++ b/lib/facter/util/collection.rb @@ -34,7 +34,7 @@ def define_fact(name, options = {}, &block) fact rescue => e - Facter.warn "Unable to add fact #{name}: #{e}" + Facter.log_exception(e, "Unable to add fact #{name}: #{e}") end # Add a resolution mechanism for a named fact. This does not distinguish diff --git a/lib/facter/util/fact.rb b/lib/facter/util/fact.rb index 0224ffc091..304cbf7660 100644 --- a/lib/facter/util/fact.rb +++ b/lib/facter/util/fact.rb @@ -69,7 +69,7 @@ def define_resolution(resolution_name, options = {}, &block) resolve rescue => e - Facter.warn "Unable to add resolve #{resolution_name.inspect} for fact #{@name}: #{e}" + Facter.log_exception(e, "Unable to add resolve #{resolution_name.inspect} for fact #{@name}: #{e.message}") end # Retrieve an existing resolution by name diff --git a/lib/facter/util/loader.rb b/lib/facter/util/loader.rb index 4a71e1e4ff..0f31bd5a68 100644 --- a/lib/facter/util/loader.rb +++ b/lib/facter/util/loader.rb @@ -117,7 +117,7 @@ def load_file(file) # Don't store the path if the file can't be loaded # in case it's loadable later on. @loaded.delete(file) - Facter.warn "Error loading fact #{file} #{detail}" + Facter.log_exception(detail, "Error loading fact #{file}: #{detail.message}") end end diff --git a/lib/facter/util/parser.rb b/lib/facter/util/parser.rb index 19f9d1732f..103be7ce15 100644 --- a/lib/facter/util/parser.rb +++ b/lib/facter/util/parser.rb @@ -55,9 +55,7 @@ def content def results parse_results rescue Exception => detail - Facter.warn("Failed to handle #{filename} as #{self.class} facts") - Facter.warn("detail: #{detail.class}: #{detail.message}") - Facter.debug(detail.backtrace.join("\n\t")) + Facter.log_exception(detail, "Failed to handle #{filename} as #{self.class} facts: #{detail.message}") nil end diff --git a/spec/unit/application_spec.rb b/spec/unit/application_spec.rb index c881090cbe..7f1e54131c 100644 --- a/spec/unit/application_spec.rb +++ b/spec/unit/application_spec.rb @@ -8,7 +8,7 @@ Facter::Application.parse(['architecture', 'kernel']).should == {} end - [:yaml, :json, :trace].each do |option_key| + [:yaml, :json].each do |option_key| it "sets options[:#{option_key}] when given --#{option_key}" do options = Facter::Application.parse(["--#{option_key}"]) options[option_key].should be_true @@ -31,6 +31,13 @@ end end + it "enables tracing when given --trace" do + Facter.trace(false) + Facter::Application.parse(['--trace']) + Facter.should be_trace + Facter.trace(false) + end + ['-t', '--timing'].each do |option| it "enables timing when given #{option}" do Facter.timing(false) @@ -52,6 +59,12 @@ Facter::Application.parse(argv) argv.should == ['uptime', 'virtual'] end + + after(:all) do + Facter.debugging(false) + Facter.timing(false) + Facter.trace(false) + end end describe "formatting facts" do From 893a1c0db6baefef97b7b280bb0f0817fac309d2 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 27 Feb 2014 13:59:24 -0800 Subject: [PATCH 1676/3753] (maint) Handle string output in zfs/zpool version facts --- lib/facter/zfs_version.rb | 2 +- lib/facter/zpool_version.rb | 2 +- spec/unit/zfs_version_spec.rb | 2 +- spec/unit/zpool_version_spec.rb | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/facter/zfs_version.rb b/lib/facter/zfs_version.rb index 3427eee040..d05e360b22 100644 --- a/lib/facter/zfs_version.rb +++ b/lib/facter/zfs_version.rb @@ -4,7 +4,7 @@ setcode do if Facter::Core::Execution.which('zfs') zfs_v = Facter::Core::Execution.exec('zfs upgrade -v') - zfs_version = zfs_v.scan(/^\s+(\d+)\s+/m).flatten.last unless zfs_v.nil? + zfs_version = zfs_v.scan(/^\s+(\d+)\s+/m).flatten.last unless zfs_v.empty? end end end diff --git a/lib/facter/zpool_version.rb b/lib/facter/zpool_version.rb index 05a117a75f..d1b7ede297 100644 --- a/lib/facter/zpool_version.rb +++ b/lib/facter/zpool_version.rb @@ -4,7 +4,7 @@ setcode do if Facter::Core::Execution.which('zpool') zpool_v = Facter::Core::Execution.exec('zpool upgrade -v') - zpool_version = zpool_v.match(/ZFS pool version (\d+)./).captures.first unless zpool_v.nil? + zpool_version = zpool_v.match(/ZFS pool version (\d+)./).captures.first unless zpool_v.empty? end end end diff --git a/spec/unit/zfs_version_spec.rb b/spec/unit/zfs_version_spec.rb index 039dfaf1c4..678a4194f7 100644 --- a/spec/unit/zfs_version_spec.rb +++ b/spec/unit/zfs_version_spec.rb @@ -50,7 +50,7 @@ end it "should return nil if zfs fails to run" do - Facter::Core::Execution.stubs(:exec).with("zfs upgrade -v").returns(nil) + Facter::Core::Execution.stubs(:exec).with("zfs upgrade -v").returns('') Facter.fact(:zfs_version).value.should == nil end diff --git a/spec/unit/zpool_version_spec.rb b/spec/unit/zpool_version_spec.rb index 169bcef3b2..569e4fd316 100644 --- a/spec/unit/zpool_version_spec.rb +++ b/spec/unit/zpool_version_spec.rb @@ -50,7 +50,7 @@ end it "should return nil if zpool fails to run" do - Facter::Core::Execution.stubs(:exec).with("zpool upgrade -v").returns(nil) + Facter::Core::Execution.stubs(:exec).with("zpool upgrade -v").returns('') Facter.fact(:zpool_version).value.should == nil end From 7e5af1e5fcafee9b328b9b50b09a996ab7674581 Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Thu, 27 Feb 2014 14:17:15 -0800 Subject: [PATCH 1677/3753] (FACT-351) Fix netmask fact on Darwin and *BSD During facter2 development, the method_missing logic for resolving fact values has been removed, e.g. Facter.ipaddress. There was one case left over that when called would produce: Could not retrieve fact='netmask', resolution='': undefined method `ipaddress' for Facter:Module and no backtrace. This commit patches up the fact and adds a spec test. --- lib/facter/util/netmask.rb | 2 +- spec/fixtures/unit/netmask/darwin_10_8_5.txt | 30 ++++++++++++++++++++ spec/unit/netmask_spec.rb | 9 ++++++ 3 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 spec/fixtures/unit/netmask/darwin_10_8_5.txt diff --git a/lib/facter/util/netmask.rb b/lib/facter/util/netmask.rb index c12e3f2ad7..61258a98aa 100644 --- a/lib/facter/util/netmask.rb +++ b/lib/facter/util/netmask.rb @@ -20,7 +20,7 @@ def self.get_netmask when 'FreeBSD','NetBSD','OpenBSD', 'Darwin', 'GNU/kFreeBSD', 'DragonFly' ops = { :ifconfig_opts => ['-a'], - :regex => %r{\s+ inet \s #{Facter.ipaddress} \s netmask \s 0x(\w{8})}x, + :regex => %r{\s+ inet \s #{Facter.value(:ipaddress)} \s netmask \s 0x(\w{8})}x, :munge => Proc.new { |mask| mask.scan(/../).collect do |byte| byte.to_i(16) end.join('.') } } end diff --git a/spec/fixtures/unit/netmask/darwin_10_8_5.txt b/spec/fixtures/unit/netmask/darwin_10_8_5.txt new file mode 100644 index 0000000000..f120f181ee --- /dev/null +++ b/spec/fixtures/unit/netmask/darwin_10_8_5.txt @@ -0,0 +1,30 @@ +lo0: flags=8049 mtu 16384 + options=3 + inet6 fe80::1%lo0 prefixlen 64 scopeid 0x1 + inet 127.0.0.1 netmask 0xff000000 + inet6 ::1 prefixlen 128 +gif0: flags=8010 mtu 1280 +stf0: flags=0<> mtu 1280 +en0: flags=8863 mtu 1500 + ether 28:cf:e9:1a:8a:2d + inet6 fe80::2acf:e9ff:fe1a:8a2d%en0 prefixlen 64 scopeid 0x5 + inet 10.16.141.17 netmask 0xfffffc00 broadcast 10.16.143.255 + media: autoselect + status: active +p2p0: flags=8843 mtu 2304 + ether 0a:cf:e9:1a:8a:2d + media: autoselect + status: inactive +vmnet1: flags=8863 mtu 1500 + ether 00:50:56:c0:00:01 + inet 192.168.51.1 netmask 0xffffff00 broadcast 192.168.51.255 +vmnet8: flags=8863 mtu 1500 + ether 00:50:56:c0:00:08 + inet 172.16.138.1 netmask 0xffffff00 broadcast 172.16.138.255 +en3: flags=8863 mtu 1500 + options=4 + ether 70:11:24:8c:33:df + inet6 fe80::7211:24ff:fe8c:33df%en3 prefixlen 64 scopeid 0x7 + inet 10.16.16.205 netmask 0xfffffc00 broadcast 10.16.19.255 + media: autoselect (100baseTX ) + status: active diff --git a/spec/unit/netmask_spec.rb b/spec/unit/netmask_spec.rb index 8e81cc87fe..c981f51c3b 100755 --- a/spec/unit/netmask_spec.rb +++ b/spec/unit/netmask_spec.rb @@ -27,6 +27,15 @@ "ifconfig_ubuntu_1204.txt" end + context "on Darwin" do + before :each do + Facter.fact(:kernel).stubs(:value).returns("Darwin") + end + + example_behavior_for "netmask from ifconfig output", + "Darwin 10.8.5", "255.255.252.0", "darwin_10_8_5.txt" + end + context "on Windows" do require 'facter/util/wmi' require 'facter/util/registry' From 680f32e1b159f861d292eb901498cfaa4dc6938f Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 27 Feb 2014 15:21:46 -0800 Subject: [PATCH 1678/3753] (maint) Simplify aggregate error message --- lib/facter/core/aggregate.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/facter/core/aggregate.rb b/lib/facter/core/aggregate.rb index 619bf1c92e..775171a92d 100644 --- a/lib/facter/core/aggregate.rb +++ b/lib/facter/core/aggregate.rb @@ -196,9 +196,9 @@ def default_aggregate(results) Facter::Util::Values.deep_merge(result, current) end rescue Facter::Util::Values::DeepMergeError => e - raise ArgumentError, "No aggregate block specified and could not deep merge" + - " all chunks, either specify an aggregate block or ensure that all chunks" + - " return deep mergable structures. (Original error: #{e.message})", e.backtrace + raise ArgumentError, "Could not deep merge all chunks (Original error: " + + "#{e.message}), ensure that chunks return either an Array or Hash or " + + "override the aggregate block", e.backtrace end # Order chunks based on their dependencies From 195162370eef2efc3b0b5941b4ed6adfc333232e Mon Sep 17 00:00:00 2001 From: Melissa Stone Date: Fri, 28 Feb 2014 11:11:41 -0800 Subject: [PATCH 1679/3753] (packaging) Update FACTERVERSION to 2.0.1-rc1 --- lib/facter/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/version.rb b/lib/facter/version.rb index a347dc79b0..ac194ae945 100644 --- a/lib/facter/version.rb +++ b/lib/facter/version.rb @@ -1,6 +1,6 @@ module Facter if not defined? FACTERVERSION then - FACTERVERSION = '1.7.5' + FACTERVERSION = '2.0.1-rc1' end # Returns the running version of Facter. From a3c88e47fba0ac8c9009b35ee167502202303075 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Sat, 1 Mar 2014 10:56:52 -0800 Subject: [PATCH 1680/3753] Make formatting consistent The formatting inconsistencies got out of control in no time due to my bouncing between editors on different platforms. So this commit is a result of: astyle -n --style=kr *cc *h --- cfacter.cc | 117 ++-- cfacterimpl.cc | 1393 ++++++++++++++++++++++++------------------------ cfacterlib.cc | 115 ++-- cfacterlib.h | 10 +- 4 files changed, 810 insertions(+), 825 deletions(-) diff --git a/cfacter.cc b/cfacter.cc index 25c41c9310..42adc605d1 100644 --- a/cfacter.cc +++ b/cfacter.cc @@ -6,36 +6,36 @@ using namespace std; void help() { - cout << -"Synopsis\n" -"========\n" -"\n" -"Collect and display facts about the system.\n" -"\n" -"Usage\n" -"=====\n" -"\n" -" cfacter [-h|--help] [-v|--version] [-j|--json] [fact] [fact] [...]\n" -"\n" -"Description\n" -"===========\n" -"\n" -"Collect and display facts about the current system. The library behind\n" -"Facter is easy to expand, making Facter an easy way to collect information\n" -"about a system.\n" -"\n" -"If no facts are specifically asked for, then all facts will be returned.\n" -"\n" -"EXAMPLE\n" -"=======\n" -" cfacter kernel\n" -"\n" -"USAGE\n" -"=====\n" -" -j, --json Emit facts in JSON format.\n" -" -v, --version Print the version and exit.\n" -" -h, --help Print this help message and exit.\n" - ; + cout << + "Synopsis\n" + "========\n" + "\n" + "Collect and display facts about the system.\n" + "\n" + "Usage\n" + "=====\n" + "\n" + " cfacter [-h|--help] [-v|--version] [-j|--json] [fact] [fact] [...]\n" + "\n" + "Description\n" + "===========\n" + "\n" + "Collect and display facts about the current system. The library behind\n" + "Facter is easy to expand, making Facter an easy way to collect information\n" + "about a system.\n" + "\n" + "If no facts are specifically asked for, then all facts will be returned.\n" + "\n" + "EXAMPLE\n" + "=======\n" + " cfacter kernel\n" + "\n" + "USAGE\n" + "=====\n" + " -j, --json Emit facts in JSON format.\n" + " -v, --version Print the version and exit.\n" + " -h, --help Print this help message and exit.\n" + ; } void version() @@ -45,56 +45,49 @@ void version() int main(int argc, char **argv) { - static struct option long_options[] = - { - {"help", no_argument, NULL, 'h'}, - {"json", no_argument, NULL, 'j'}, - {"version", no_argument, NULL, 'v'}, - {NULL, 0, NULL, 0} + static struct option long_options[] = { + {"help", no_argument, NULL, 'h'}, + {"json", no_argument, NULL, 'j'}, + {"version", no_argument, NULL, 'v'}, + {NULL, 0, NULL, 0} }; // loop over all of the options int ch; - while ((ch = getopt_long(argc, argv, "hjv", long_options, NULL)) != -1) - { - // check to see if a single character or long option came through - switch (ch) - { - // short option 'v' - case 'v': - version(); - exit(0); + while ((ch = getopt_long(argc, argv, "hjv", long_options, NULL)) != -1) { + // check to see if a single character or long option came through + switch (ch) { + // short option 'v' + case 'v': + version(); + exit(0); - // short option 'h' - case 'h': - help(); - exit(0); + // short option 'h' + case 'h': + help(); + exit(0); - // short option 'j' - case 'j': - // do json, that's the only output option atm - break; - } + // short option 'j' + case 'j': + // do json, that's the only output option atm + break; + } } loadfacts(); - #define MAX_LEN_FACTS_JSON_STRING (1024 * 1024) // go crazy here +#define MAX_LEN_FACTS_JSON_STRING (1024 * 1024) // go crazy here char facts_json[MAX_LEN_FACTS_JSON_STRING]; - if (optind == argc) // display all facts - { + if (optind == argc) { // display all facts if (to_json(facts_json, MAX_LEN_FACTS_JSON_STRING) < 0) { cout << "Wow, that's a lot of facts" << endl; exit(1); } cout << facts_json << endl; - } - else - { + } else { // display requested fact(s) - while (optind < argc) - { + while (optind < argc) { // fix me if (value(argv[optind], facts_json, MAX_LEN_FACTS_JSON_STRING) == 0) cout << facts_json << endl; diff --git a/cfacterimpl.cc b/cfacterimpl.cc index 2fa1c8ba7e..eaa62aa698 100644 --- a/cfacterimpl.cc +++ b/cfacterimpl.cc @@ -42,247 +42,258 @@ struct ci_char_traits : public char_traits // just inherit all the other functions // that we don't need to override { - static bool eq( char c1, char c2 ) - { return toupper(c1) == toupper(c2); } + static bool eq( char c1, char c2 ) + { + return toupper(c1) == toupper(c2); + } - static bool ne( char c1, char c2 ) - { return toupper(c1) != toupper(c2); } + static bool ne( char c1, char c2 ) + { + return toupper(c1) != toupper(c2); + } - static bool lt( char c1, char c2 ) - { return toupper(c1) < toupper(c2); } + static bool lt( char c1, char c2 ) + { + return toupper(c1) < toupper(c2); + } - static int compare( const char* s1, - const char* s2, - size_t n ) { - return strncasecmp( s1, s2, n ); - // if available on your compiler, - // otherwise you can roll your own - } + static int compare( const char* s1, + const char* s2, + size_t n ) + { + return strncasecmp( s1, s2, n ); + // if available on your compiler, + // otherwise you can roll your own + } - static const char* - find( const char* s, int n, char a ) { - while( n-- > 0 && toupper(*s) != toupper(a) ) { - ++s; + static const char* + find( const char* s, int n, char a ) + { + while( n-- > 0 && toupper(*s) != toupper(a) ) { + ++s; + } + return s; } - return s; - } }; typedef basic_string ci_string; // trim from start -static inline std::string <rim(std::string &s) { - s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun(std::isspace)))); - return s; +static inline std::string <rim(std::string &s) +{ + s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun(std::isspace)))); + return s; } // trim from end -static inline std::string &rtrim(std::string &s) { - s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun(std::isspace))).base(), s.end()); - return s; +static inline std::string &rtrim(std::string &s) +{ + s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun(std::isspace))).base(), s.end()); + return s; } // trim from both ends -static inline std::string &trim(std::string &s) { - return ltrim(rtrim(s)); +static inline std::string &trim(std::string &s) +{ + return ltrim(rtrim(s)); } -static inline void tokenize(std::string &s, vector &tokens) { - istringstream iss(s); - copy(istream_iterator(iss), - istream_iterator(), - back_inserter >(tokens)); +static inline void tokenize(std::string &s, vector &tokens) +{ + istringstream iss(s); + copy(istream_iterator(iss), + istream_iterator(), + back_inserter >(tokens)); } -static inline void split(const string &s, char delim, vector &elems) { - stringstream ss(s); - string item; - while (getline(ss, item, delim)) { - elems.push_back(item); - } +static inline void split(const string &s, char delim, vector &elems) +{ + stringstream ss(s); + string item; + while (getline(ss, item, delim)) { + elems.push_back(item); + } } static bool file_exist (string filename) { - struct stat buffer; - return stat (filename.c_str(), &buffer) == 0; + struct stat buffer; + return stat (filename.c_str(), &buffer) == 0; } // handy for some /proc and /sys files string read_oneline_file(const string file_path) { - std::ifstream oneline_file(file_path.c_str(), std::ifstream::in); - std::string line; - std::getline(oneline_file, line); - return line; + std::ifstream oneline_file(file_path.c_str(), std::ifstream::in); + std::string line; + std::getline(oneline_file, line); + return line; } void get_network_facts(fact_map& facts) { - struct ifreq *ifr; - struct ifconf ifc; - int s, i; - int numif; - - // find number of interfaces. - memset(&ifc, 0, sizeof(ifc)); - ifc.ifc_ifcu.ifcu_req = NULL; - ifc.ifc_len = 0; - - if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { - perror("socket"); - exit(1); - } - - if (ioctl(s, SIOCGIFCONF, &ifc) < 0) { - perror("ioctl"); - exit(1); - } - - if ((ifr = static_cast(malloc(ifc.ifc_len))) == NULL) { - perror("malloc"); - exit(1); - } - ifc.ifc_ifcu.ifcu_req = ifr; - - if (ioctl(s, SIOCGIFCONF, &ifc) < 0) { - perror("ioctl SIOCGIFCONF"); - exit(1); - } - - string interfaces = ""; - - bool primaryInterfacePrinted = false; - numif = ifc.ifc_len / sizeof(struct ifreq); - for (i = 0; i < numif; i++) { - struct ifreq *r = &ifr[i]; - struct in_addr ip_addr = ((struct sockaddr_in *)&r->ifr_addr)->sin_addr; - - // no idea what the real algorithm is to identify the unmarked - // interface, i.e. the one that facter reports as just 'ipaddress' - // here just take the first one that's not 'lo' - bool primaryInterface = false; - if (!primaryInterfacePrinted && strcmp(r->ifr_name, "lo")) { - // this is the chosen interface - primaryInterface = true; - primaryInterfacePrinted = true; - } - - // build up 'interfaces' fact as we go, appending comma after all but last - interfaces += r->ifr_name; - if (i < numif - 1) - interfaces += ","; - - const char *ipaddress = inet_ntoa(ip_addr); - facts[string("ipaddress_") + r->ifr_name] = ipaddress; - if (primaryInterface) - facts["ipaddress"] = ipaddress; - - // mtu - if (ioctl(s, SIOCGIFMTU, r) < 0) { - perror("ioctl SIOCGIFMTU"); - exit(1); - } - - facts[string("mtu_") + r->ifr_name] = to_string(r->ifr_mtu); - if (primaryInterface) - ; // no unmarked version of this network fact - - // netmask and network are both derived from the same ioctl - if (ioctl(s, SIOCGIFNETMASK, r) < 0) { - perror("ioctl SIOCGIFNETMASK"); - exit(1); - } + struct ifreq *ifr; + struct ifconf ifc; + int s, i; + int numif; -#ifndef __APPLE__ // ifr_netmask isn't supported, might need to use SC library - // netmask - struct in_addr netmask_addr = ((struct sockaddr_in *)&r->ifr_netmask)->sin_addr; - const char *netmask = inet_ntoa(netmask_addr); - facts[string("netmask_") + r->ifr_name] = netmask; - if (primaryInterface) - facts["netmask"] = netmask; - - // mess of casting to get the network address - struct in_addr network_addr; - network_addr.s_addr = - (in_addr_t) uint32_t(netmask_addr.s_addr) & uint32_t(ip_addr.s_addr); - string network = inet_ntoa(network_addr); - facts[string("network_") + r->ifr_name] = network; - if (primaryInterface) - facts["network"] = network; -#endif + // find number of interfaces. + memset(&ifc, 0, sizeof(ifc)); + ifc.ifc_ifcu.ifcu_req = NULL; + ifc.ifc_len = 0; -#ifndef __APPLE__ // SIOCGIFHWADDR isn't supported, might need to use SC library - // and the mac address (but not for loopback) - if (strcmp(r->ifr_name, "lo")) { - if (ioctl(s, SIOCGIFHWADDR, r) < 0) { - perror("ioctl SIOCGIFHWADDR"); + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + perror("socket"); exit(1); - } + } - // extract mac into a string, okay a char array - uint8_t *mac_bytes = (uint8_t *)((sockaddr *)&r->ifr_hwaddr)->sa_data; - char mac_address[18]; - sprintf(mac_address, "%02x:%02x:%02x:%02x:%02x:%02x", - mac_bytes[0], mac_bytes[1], mac_bytes[2], - mac_bytes[3], mac_bytes[4], mac_bytes[5]); + if (ioctl(s, SIOCGIFCONF, &ifc) < 0) { + perror("ioctl"); + exit(1); + } - // and get it out - facts[string("macaddress_") + r->ifr_name] = mac_address; - if (primaryInterface) - facts["macaddress"] = mac_address; + if ((ifr = static_cast(malloc(ifc.ifc_len))) == NULL) { + perror("malloc"); + exit(1); } + ifc.ifc_ifcu.ifcu_req = ifr; + + if (ioctl(s, SIOCGIFCONF, &ifc) < 0) { + perror("ioctl SIOCGIFCONF"); + exit(1); + } + + string interfaces = ""; + + bool primaryInterfacePrinted = false; + numif = ifc.ifc_len / sizeof(struct ifreq); + for (i = 0; i < numif; i++) { + struct ifreq *r = &ifr[i]; + struct in_addr ip_addr = ((struct sockaddr_in *)&r->ifr_addr)->sin_addr; + + // no idea what the real algorithm is to identify the unmarked + // interface, i.e. the one that facter reports as just 'ipaddress' + // here just take the first one that's not 'lo' + bool primaryInterface = false; + if (!primaryInterfacePrinted && strcmp(r->ifr_name, "lo")) { + // this is the chosen interface + primaryInterface = true; + primaryInterfacePrinted = true; + } + + // build up 'interfaces' fact as we go, appending comma after all but last + interfaces += r->ifr_name; + if (i < numif - 1) + interfaces += ","; + + const char *ipaddress = inet_ntoa(ip_addr); + facts[string("ipaddress_") + r->ifr_name] = ipaddress; + if (primaryInterface) + facts["ipaddress"] = ipaddress; + + // mtu + if (ioctl(s, SIOCGIFMTU, r) < 0) { + perror("ioctl SIOCGIFMTU"); + exit(1); + } + + facts[string("mtu_") + r->ifr_name] = to_string(r->ifr_mtu); + if (primaryInterface) + ; // no unmarked version of this network fact + + // netmask and network are both derived from the same ioctl + if (ioctl(s, SIOCGIFNETMASK, r) < 0) { + perror("ioctl SIOCGIFNETMASK"); + exit(1); + } + +#ifndef __APPLE__ // ifr_netmask isn't supported, might need to use SC library + // netmask + struct in_addr netmask_addr = ((struct sockaddr_in *)&r->ifr_netmask)->sin_addr; + const char *netmask = inet_ntoa(netmask_addr); + facts[string("netmask_") + r->ifr_name] = netmask; + if (primaryInterface) + facts["netmask"] = netmask; + + // mess of casting to get the network address + struct in_addr network_addr; + network_addr.s_addr = + (in_addr_t) uint32_t(netmask_addr.s_addr) & uint32_t(ip_addr.s_addr); + string network = inet_ntoa(network_addr); + facts[string("network_") + r->ifr_name] = network; + if (primaryInterface) + facts["network"] = network; #endif - } - facts["interfaces"] = interfaces; +#ifndef __APPLE__ // SIOCGIFHWADDR isn't supported, might need to use SC library + // and the mac address (but not for loopback) + if (strcmp(r->ifr_name, "lo")) { + if (ioctl(s, SIOCGIFHWADDR, r) < 0) { + perror("ioctl SIOCGIFHWADDR"); + exit(1); + } + + // extract mac into a string, okay a char array + uint8_t *mac_bytes = (uint8_t *)((sockaddr *)&r->ifr_hwaddr)->sa_data; + char mac_address[18]; + sprintf(mac_address, "%02x:%02x:%02x:%02x:%02x:%02x", + mac_bytes[0], mac_bytes[1], mac_bytes[2], + mac_bytes[3], mac_bytes[4], mac_bytes[5]); + + // and get it out + facts[string("macaddress_") + r->ifr_name] = mac_address; + if (primaryInterface) + facts["macaddress"] = mac_address; + } +#endif + } + + facts["interfaces"] = interfaces; - close(s); - free(ifr); + close(s); + free(ifr); } void get_kernel_facts(fact_map& facts) { #ifdef __linux__ - // this is linux-only, so there you have it - facts["kernel"] = "Linux"; - string kernelrelease = read_oneline_file("/proc/sys/kernel/osrelease"); - facts["kernelrelease"] = kernelrelease; - string kernelversion = kernelrelease.substr(0, kernelrelease.find("-")); - facts["kernelversion"] = kernelversion; - string kernelmajversion = kernelversion.substr(0, kernelversion.rfind(".")); - facts["kernelmajversion"] = kernelmajversion; + // this is linux-only, so there you have it + facts["kernel"] = "Linux"; + string kernelrelease = read_oneline_file("/proc/sys/kernel/osrelease"); + facts["kernelrelease"] = kernelrelease; + string kernelversion = kernelrelease.substr(0, kernelrelease.find("-")); + facts["kernelversion"] = kernelversion; + string kernelmajversion = kernelversion.substr(0, kernelversion.rfind(".")); + facts["kernelmajversion"] = kernelmajversion; #else #ifdef __APPLE__ - facts["kernel"] = "Darwin"; + facts["kernel"] = "Darwin"; #endif #endif } static void get_lsb_facts(fact_map& facts) { - std::ifstream lsb_release_file("/etc/lsb-release", std::ifstream::in); - std::string line; - while (std::getline(lsb_release_file, line)) { - unsigned sep = line.find("="); - string key = line.substr(0, sep); - string value = line.substr(sep + 1, string::npos); - - if (key == "DISTRIB_ID") { - facts["lsbdistid"] = value; - facts["operatingsystem"] = value; - facts["osfamily"] = "Debian"; - } - else if (key == "DISTRIB_RELEASE") { - facts["lsbdistrelease"] = value; - facts["operatingsystemrelease"] = value; - facts["lsbmajdistrelease"] = value.substr(0, value.find(".")); - } - else if (key == "DISTRIB_CODENAME") - facts["lsbdistcodename"] = value; - else if (key == "DISTRIB_DESCRIPTION") - facts["lsbdistdescription"] = value; - } + std::ifstream lsb_release_file("/etc/lsb-release", std::ifstream::in); + std::string line; + while (std::getline(lsb_release_file, line)) { + unsigned sep = line.find("="); + string key = line.substr(0, sep); + string value = line.substr(sep + 1, string::npos); + + if (key == "DISTRIB_ID") { + facts["lsbdistid"] = value; + facts["operatingsystem"] = value; + facts["osfamily"] = "Debian"; + } else if (key == "DISTRIB_RELEASE") { + facts["lsbdistrelease"] = value; + facts["operatingsystemrelease"] = value; + facts["lsbmajdistrelease"] = value.substr(0, value.find(".")); + } else if (key == "DISTRIB_CODENAME") + facts["lsbdistcodename"] = value; + else if (key == "DISTRIB_DESCRIPTION") + facts["lsbdistdescription"] = value; + } } // gonna need to pick a regex library to do os facts rights given all the variants @@ -290,76 +301,75 @@ static void get_lsb_facts(fact_map& facts) static void get_redhat_facts(fact_map& facts) { - if (file_exist("/etc/redhat-release")) { - facts["osfamily"] = "RedHat"; - string redhat_release = read_oneline_file("/etc/redhat-release"); - vector tokens; - tokenize(redhat_release, tokens); - if (tokens.size() >= 2 && tokens[0] == "Fedora" && tokens[1] == "release") { - facts["operatingsystem"] = "Fedora"; - if (tokens.size() >= 3) { - facts["operatingsystemrelease"] = tokens[2]; - facts["operatingsystemmajrelease"] = tokens[2]; - } + if (file_exist("/etc/redhat-release")) { + facts["osfamily"] = "RedHat"; + string redhat_release = read_oneline_file("/etc/redhat-release"); + vector tokens; + tokenize(redhat_release, tokens); + if (tokens.size() >= 2 && tokens[0] == "Fedora" && tokens[1] == "release") { + facts["operatingsystem"] = "Fedora"; + if (tokens.size() >= 3) { + facts["operatingsystemrelease"] = tokens[2]; + facts["operatingsystemmajrelease"] = tokens[2]; + } + } else + facts["operatingsystem"] = "RedHat"; } - else - facts["operatingsystem"] = "RedHat"; - } } void get_operatingsystem_facts(fact_map& facts) { - get_lsb_facts(facts); - get_redhat_facts(facts); + get_lsb_facts(facts); + get_redhat_facts(facts); } void get_uptime_facts(fact_map& facts) { - string uptime = read_oneline_file("/proc/uptime"); - unsigned int uptime_seconds; - sscanf(uptime.c_str(), "%ud", &uptime_seconds); - unsigned int uptime_hours = uptime_seconds / 3600; - unsigned int uptime_days = uptime_hours / 24; - facts["uptime_seconds"] = to_string(uptime_seconds); - facts["uptime_hours"] = to_string(uptime_hours); - facts["uptime_days"] = to_string(uptime_days); - facts["uptime"] = to_string(uptime_days) + " days"; + string uptime = read_oneline_file("/proc/uptime"); + unsigned int uptime_seconds; + sscanf(uptime.c_str(), "%ud", &uptime_seconds); + unsigned int uptime_hours = uptime_seconds / 3600; + unsigned int uptime_days = uptime_hours / 24; + facts["uptime_seconds"] = to_string(uptime_seconds); + facts["uptime_hours"] = to_string(uptime_hours); + facts["uptime_days"] = to_string(uptime_days); + facts["uptime"] = to_string(uptime_days) + " days"; } string popen_stdout(string cmd) { - FILE *cmd_fd = popen(cmd.c_str(), "r"); - string cmd_output = ""; - char buf[1024]; - size_t bytesRead; - while ((bytesRead = fread(buf, 1, sizeof(buf) - 1, cmd_fd))) { - buf[bytesRead] = 0; - cmd_output += buf; - } - pclose(cmd_fd); - return cmd_output; + FILE *cmd_fd = popen(cmd.c_str(), "r"); + string cmd_output = ""; + char buf[1024]; + size_t bytesRead; + while ((bytesRead = fread(buf, 1, sizeof(buf) - 1, cmd_fd))) { + buf[bytesRead] = 0; + cmd_output += buf; + } + pclose(cmd_fd); + return cmd_output; } void get_virtual_facts(fact_map& facts) { - // poked at the real facter's virtual support, some combo of file existence - // plus lspci plus dmidecode + // poked at the real facter's virtual support, some combo of file existence + // plus lspci plus dmidecode - // instead of parsing all of lspci, how about looking for vendors in lspci -n? - // or walking the /sys/bus/pci/devices files. or don't sweat it, the total - // lspci time is ~40 ms. + // instead of parsing all of lspci, how about looking for vendors in lspci -n? + // or walking the /sys/bus/pci/devices files. or don't sweat it, the total + // lspci time is ~40 ms. - // virtual could be discovered in lots of places so requires some special handling - - facts["is_virtual"] = "false"; - facts["virtual"] = "physical"; + // virtual could be discovered in lots of places so requires some special handling + + facts["is_virtual"] = "false"; + facts["virtual"] = "physical"; } // placeholders for some hardwired facts, cuz not sure what to do with them void get_hardwired_facts(fact_map& facts) { - facts["ps"] = "ps -ef"; // what is this? - facts["uniqueid"] = "007f0101"; // ?? + facts["ps"] = "ps -ef"; // what is this? + facts["uniqueid"] = "007f0101"; // ?? } @@ -367,580 +377,563 @@ void get_hardwired_facts(fact_map& facts) // omit or 'undef' or ...? for now, omit but collect them here void get_ruby_lib_versions(fact_map& facts) { - /* - facts["puppetversion => undef"; - facts["augeasversion => undef"; - facts["rubysitedir => undef"; - facts["rubyversion => undef"; - */ + /* + facts["puppetversion => undef"; + facts["augeasversion => undef"; + facts["rubysitedir => undef"; + facts["rubyversion => undef"; + */ } // block devices void get_blockdevice_facts(fact_map& facts) { - string blockdevices = ""; - - DIR *sys_block_dir = opendir("/sys/block"); - if (sys_block_dir == NULL) { - return; - } - - struct dirent *bd; - while ((bd = readdir(sys_block_dir))) { + string blockdevices = ""; - bool real_block_device = false; - string device_dir_path = "/sys/block/"; - device_dir_path += bd->d_name; - - DIR *device_dir = opendir(device_dir_path.c_str()); - struct dirent *subdir; - while ((subdir = readdir(device_dir))) { - if (strcmp(subdir->d_name, "device") == 0) { - // we have a winner - real_block_device = true; - break; - } + DIR *sys_block_dir = opendir("/sys/block"); + if (sys_block_dir == NULL) { + return; } - - if (!real_block_device) continue; - - // add it to the blockdevices list, careful with the comma - if (!blockdevices.empty()) - blockdevices += ","; - blockdevices += bd->d_name; - string model_file = "/sys/block/" + string(bd->d_name) + "/device/model"; - facts[string("blockdevice_") + bd->d_name + "_model"] = - read_oneline_file(model_file); - - string vendor_file = "/sys/block/" + string(bd->d_name) + "/device/vendor"; - facts[string("blockdevice_") + bd->d_name + "_vendor"] = - read_oneline_file(vendor_file); - - string size_file = "/sys/block/" + string(bd->d_name) + "/size"; - string size_line = read_oneline_file(size_file); - int64_t size; - // SCNd64 didn't work here?? - sscanf(size_line.c_str(), "%lld", (long long int *)&size); - facts[string("blockdevice_") + bd->d_name + "_size"] = to_string(size * 512); - } + struct dirent *bd; + while ((bd = readdir(sys_block_dir))) { + + bool real_block_device = false; + string device_dir_path = "/sys/block/"; + device_dir_path += bd->d_name; + + DIR *device_dir = opendir(device_dir_path.c_str()); + struct dirent *subdir; + while ((subdir = readdir(device_dir))) { + if (strcmp(subdir->d_name, "device") == 0) { + // we have a winner + real_block_device = true; + break; + } + } + + if (!real_block_device) continue; + + // add it to the blockdevices list, careful with the comma + if (!blockdevices.empty()) + blockdevices += ","; + blockdevices += bd->d_name; + + string model_file = "/sys/block/" + string(bd->d_name) + "/device/model"; + facts[string("blockdevice_") + bd->d_name + "_model"] = + read_oneline_file(model_file); + + string vendor_file = "/sys/block/" + string(bd->d_name) + "/device/vendor"; + facts[string("blockdevice_") + bd->d_name + "_vendor"] = + read_oneline_file(vendor_file); + + string size_file = "/sys/block/" + string(bd->d_name) + "/size"; + string size_line = read_oneline_file(size_file); + int64_t size; + // SCNd64 didn't work here?? + sscanf(size_line.c_str(), "%lld", (long long int *)&size); + facts[string("blockdevice_") + bd->d_name + "_size"] = to_string(size * 512); + } - facts["blockdevices"] = blockdevices; + facts["blockdevices"] = blockdevices; } void get_misc_facts(fact_map& facts) { - facts["path"] = getenv("PATH"); - string whoami = popen_stdout("whoami"); - facts["id"] = trim(whoami); + facts["path"] = getenv("PATH"); + string whoami = popen_stdout("whoami"); + facts["id"] = trim(whoami); - //timezone - char tzstring[16]; - time_t t = time(NULL); - struct tm *loc = localtime(&t); - strftime(tzstring, sizeof(tzstring - 1), "%Z", loc); - facts["timezone"] = tzstring; + //timezone + char tzstring[16]; + time_t t = time(NULL); + struct tm *loc = localtime(&t); + strftime(tzstring, sizeof(tzstring - 1), "%Z", loc); + facts["timezone"] = tzstring; } // get just one fact, optionally in two formats static void get_mem_fact(std::string fact_name, int fact_value, fact_map& facts, bool get_mb_variant = true) { - float fact_value_scaled = fact_value / 1024.0; - char float_buf[32]; - - if (get_mb_variant) { - snprintf(float_buf, sizeof(float_buf) - 1, "%.2f", fact_value_scaled); - facts[string(fact_name) + "_mb"] = float_buf; - } - - int scale_index; - for (scale_index = 0; - fact_value_scaled > 1024.0; - fact_value_scaled /= 1024.0, ++scale_index) ; - - std::string scale[4] = {"MB", "GB", "TB", "PB"}; // oh yeah, petabytes ... - - snprintf(float_buf, sizeof(float_buf) - 1, "%.2f", fact_value_scaled); - facts[fact_name] = string(float_buf) + scale[scale_index]; -} + float fact_value_scaled = fact_value / 1024.0; + char float_buf[32]; -void get_mem_facts(fact_map& facts) -{ - std::ifstream oneline_file("/proc/meminfo", std::ifstream::in); - std::string line; - - // The MemFree fact is the sum of MemFree + Buffer + Cached from /proc/meninfo, - // so sum that one as we go, and get it out at the end. - // The other three memory facts are straight from /proc/meminfo, so get those - // as we go. - // All four facts are geted in two formats: - // _mb => %.2f - // => %.2f %s (where the suffix string is one of MB/GB/TB) - // And then there is a ninth fact, 'memorytotal', which is the same as 'memorysize'. - // + if (get_mb_variant) { + snprintf(float_buf, sizeof(float_buf) - 1, "%.2f", fact_value_scaled); + facts[string(fact_name) + "_mb"] = float_buf; + } - // NB: this all assumes that all values are in KB. + int scale_index; + for (scale_index = 0; + fact_value_scaled > 1024.0; + fact_value_scaled /= 1024.0, ++scale_index) ; - unsigned int memoryfree = 0; + std::string scale[4] = {"MB", "GB", "TB", "PB"}; // oh yeah, petabytes ... - while (std::getline(oneline_file, line)) { - vector tokens; - tokenize(line, tokens); - if (tokens.size() < 3) continue; // should never happen + snprintf(float_buf, sizeof(float_buf) - 1, "%.2f", fact_value_scaled); + facts[fact_name] = string(float_buf) + scale[scale_index]; +} - if (tokens[0] == "MemTotal:") { - int mem_total = atoi(tokens[1].c_str()); - get_mem_fact("memorysize", mem_total, facts); - get_mem_fact("memorytotal", mem_total, facts, false); - } - else if (tokens[0] == "MemFree:" || tokens[0] == "Cached:" || tokens[0] == "Buffers:") { - memoryfree += atoi(tokens[1].c_str()); +void get_mem_facts(fact_map& facts) +{ + std::ifstream oneline_file("/proc/meminfo", std::ifstream::in); + std::string line; + + // The MemFree fact is the sum of MemFree + Buffer + Cached from /proc/meninfo, + // so sum that one as we go, and get it out at the end. + // The other three memory facts are straight from /proc/meminfo, so get those + // as we go. + // All four facts are geted in two formats: + // _mb => %.2f + // => %.2f %s (where the suffix string is one of MB/GB/TB) + // And then there is a ninth fact, 'memorytotal', which is the same as 'memorysize'. + // + + // NB: this all assumes that all values are in KB. + + unsigned int memoryfree = 0; + + while (std::getline(oneline_file, line)) { + vector tokens; + tokenize(line, tokens); + if (tokens.size() < 3) continue; // should never happen + + if (tokens[0] == "MemTotal:") { + int mem_total = atoi(tokens[1].c_str()); + get_mem_fact("memorysize", mem_total, facts); + get_mem_fact("memorytotal", mem_total, facts, false); + } else if (tokens[0] == "MemFree:" || tokens[0] == "Cached:" || tokens[0] == "Buffers:") { + memoryfree += atoi(tokens[1].c_str()); + } else if (tokens[0] == "SwapTotal:") + get_mem_fact("swapsize", atoi(tokens[1].c_str()), facts); + else if (tokens[0] == "SwapFree:") + get_mem_fact("swapfree", atoi(tokens[1].c_str()), facts); } - else if (tokens[0] == "SwapTotal:") - get_mem_fact("swapsize", atoi(tokens[1].c_str()), facts); - else if (tokens[0] == "SwapFree:") - get_mem_fact("swapfree", atoi(tokens[1].c_str()), facts); - } - get_mem_fact("memoryfree", memoryfree, facts); + get_mem_fact("memoryfree", memoryfree, facts); } static string get_selinux_path() { - static string selinux_path = ""; - static bool inited = false; + static string selinux_path = ""; + static bool inited = false; - if (inited) - return selinux_path; + if (inited) + return selinux_path; - std::ifstream mounts("/proc/self/mounts", std::ifstream::in); - std::string line; + std::ifstream mounts("/proc/self/mounts", std::ifstream::in); + std::string line; - while (std::getline(mounts, line)) { - vector tokens; - tokenize(line, tokens); - if (tokens.size() < 2) continue; - if (tokens[0] != "selinuxfs") continue; - - selinux_path = tokens[1]; - break; - } + while (std::getline(mounts, line)) { + vector tokens; + tokenize(line, tokens); + if (tokens.size() < 2) continue; + if (tokens[0] != "selinuxfs") continue; - inited = true; + selinux_path = tokens[1]; + break; + } + + inited = true; - return selinux_path; + return selinux_path; } static bool selinux() { - string selinux_path = get_selinux_path(); - if (selinux_path.empty()) - return false; + string selinux_path = get_selinux_path(); + if (selinux_path.empty()) + return false; - string selinux_enforce_path = selinux_path + "/enforce"; - string security_attr_path = "/proc/self/attr/current"; - if (file_exist(selinux_enforce_path) && file_exist(security_attr_path) && - read_oneline_file(security_attr_path) != "kernel") - return true; + string selinux_enforce_path = selinux_path + "/enforce"; + string security_attr_path = "/proc/self/attr/current"; + if (file_exist(selinux_enforce_path) && file_exist(security_attr_path) && + read_oneline_file(security_attr_path) != "kernel") + return true; - return false; + return false; } void get_selinux_facts(fact_map& facts) { - if (!selinux()) { - facts["selinux"] = "false"; - return; - } - - facts["selinux"] = "true"; - - // defaults from facter - facts["selinux_enforced"] = "false"; - facts["selinux_policyversion"] = "unknown"; - facts["selinux_current_mode"] = "unknown"; - facts["selinux_config_mode"] = "unknown"; - facts["selinux_config_policy"] = "unknown"; - facts["selinux_mode"] = "unknown"; - - string selinux_path = get_selinux_path(); - - string selinux_enforce_path = selinux_path + "/enforce"; - if (file_exist(selinux_enforce_path)) - facts["selinux_enforced"] = ((read_oneline_file(selinux_enforce_path) == "1") ? "true" : "false"); - - string selinux_policyvers_path = selinux_path + "/policyvers"; - if (file_exist(selinux_policyvers_path)) - facts["selinux_policyversion"] = read_oneline_file(selinux_policyvers_path); - - string selinux_cmd = "/usr/sbin/sestatus"; - FILE* pipe = popen(selinux_cmd.c_str(), "r"); - if (!pipe) return; - - char buffer[512]; // seems like a lot, but there's no constant available - while (!feof(pipe)) { - if (fgets(buffer, 128, pipe) != NULL) { - vector elems; - split(buffer, ':', elems); - if (elems.size() < 2) continue; // shouldn't happen - if (elems[0] == "Current mode") { - facts["selinux_current_mode"] = trim(elems[1]); - } - else if (elems[0] == "Mode from config file") { - facts["selinux_config_mode"] = trim(elems[1]); - } - else if (elems[0] == "Policy from config file") { - facts["selinux_config_policy"] = trim(elems[1]); - facts["selinux_mode"] = trim(elems[1]); - } - } - } - - pclose(pipe); + if (!selinux()) { + facts["selinux"] = "false"; + return; + } + + facts["selinux"] = "true"; + + // defaults from facter + facts["selinux_enforced"] = "false"; + facts["selinux_policyversion"] = "unknown"; + facts["selinux_current_mode"] = "unknown"; + facts["selinux_config_mode"] = "unknown"; + facts["selinux_config_policy"] = "unknown"; + facts["selinux_mode"] = "unknown"; + + string selinux_path = get_selinux_path(); + + string selinux_enforce_path = selinux_path + "/enforce"; + if (file_exist(selinux_enforce_path)) + facts["selinux_enforced"] = ((read_oneline_file(selinux_enforce_path) == "1") ? "true" : "false"); + + string selinux_policyvers_path = selinux_path + "/policyvers"; + if (file_exist(selinux_policyvers_path)) + facts["selinux_policyversion"] = read_oneline_file(selinux_policyvers_path); + + string selinux_cmd = "/usr/sbin/sestatus"; + FILE* pipe = popen(selinux_cmd.c_str(), "r"); + if (!pipe) return; + + char buffer[512]; // seems like a lot, but there's no constant available + while (!feof(pipe)) { + if (fgets(buffer, 128, pipe) != NULL) { + vector elems; + split(buffer, ':', elems); + if (elems.size() < 2) continue; // shouldn't happen + if (elems[0] == "Current mode") { + facts["selinux_current_mode"] = trim(elems[1]); + } else if (elems[0] == "Mode from config file") { + facts["selinux_config_mode"] = trim(elems[1]); + } else if (elems[0] == "Policy from config file") { + facts["selinux_config_policy"] = trim(elems[1]); + facts["selinux_mode"] = trim(elems[1]); + } + } + } + + pclose(pipe); } static void get_ssh_fact(string fact_name, string path_name, fact_map& facts) { - string ssh_directories[] = { - "/etc/ssh", - "/usr/local/etc/ssh", - "/etc", - "/usr/local/etc", - "/etc/opt/ssh", - }; - - for (int i = 0; i < sizeof(ssh_directories) / sizeof(string); ++i) { - string full_path = ssh_directories[i] + "/" + path_name; - if (file_exist(full_path)) { - string key = read_oneline_file(full_path); - vector tokens; - tokenize(trim(key), tokens); - if (tokens.size() < 2) continue; // should never happen - facts[fact_name] = tokens[1]; - - // skpping the finger print facts, which require base64 decode and sha libs - // on the cmd line it would be something like the result of these two: - // "cat " + full_path + " | cut -d' ' -f 2 | base64 -d - | sha256sum - | cut -d' ' -f 1" - // "cat " + full_path + " | cut -d' ' -f 2 | base64 -d - | sha1sum - | cut -d' ' -f 1" - - break; + string ssh_directories[] = { + "/etc/ssh", + "/usr/local/etc/ssh", + "/etc", + "/usr/local/etc", + "/etc/opt/ssh", + }; + + for (int i = 0; i < sizeof(ssh_directories) / sizeof(string); ++i) { + string full_path = ssh_directories[i] + "/" + path_name; + if (file_exist(full_path)) { + string key = read_oneline_file(full_path); + vector tokens; + tokenize(trim(key), tokens); + if (tokens.size() < 2) continue; // should never happen + facts[fact_name] = tokens[1]; + + // skpping the finger print facts, which require base64 decode and sha libs + // on the cmd line it would be something like the result of these two: + // "cat " + full_path + " | cut -d' ' -f 2 | base64 -d - | sha256sum - | cut -d' ' -f 1" + // "cat " + full_path + " | cut -d' ' -f 2 | base64 -d - | sha1sum - | cut -d' ' -f 1" + + break; + } } - } } // no support for the sshfp facts, which require base64/sha1sum code void get_ssh_facts(fact_map& facts) { - // not til C++11 do we have static initialization of stl maps - map ssh_facts; - ssh_facts["sshdsakey"] = "ssh_host_dsa_key.pub"; - ssh_facts["sshrsakey"] = "ssh_host_rsa_key.pub"; - ssh_facts["sshecdsakey"] = "ssh_host_ecdsa_key.pub"; + // not til C++11 do we have static initialization of stl maps + map ssh_facts; + ssh_facts["sshdsakey"] = "ssh_host_dsa_key.pub"; + ssh_facts["sshrsakey"] = "ssh_host_rsa_key.pub"; + ssh_facts["sshecdsakey"] = "ssh_host_ecdsa_key.pub"; - typedef map::iterator iter; - for (iter i = ssh_facts.begin(); i != ssh_facts.end(); ++i) { - get_ssh_fact(i->first, i->second, facts); - } + typedef map::iterator iter; + for (iter i = ssh_facts.begin(); i != ssh_facts.end(); ++i) { + get_ssh_fact(i->first, i->second, facts); + } } static void get_physicalprocessorcount_fact(fact_map& facts) { - // So, facter has logic to use /sys and fallback to /proc - // but I don't know why the /sys support was added; research needed. - // Since sys is the default, just reproduce that logic for now. - - string sysfs_cpu_directory = "/sys/devices/system/cpu"; - vector package_ids; - if (file_exist(sysfs_cpu_directory)) { - for (int i = 0; ; i++) { - char buf[10]; - snprintf(buf, sizeof(buf) - 1, "%u", i); - string cpu_phys_file = sysfs_cpu_directory + "/cpu" + buf + "/topology/physical_package_id"; - if (!file_exist(cpu_phys_file)) - break; - - package_ids.push_back(read_oneline_file(cpu_phys_file)); + // So, facter has logic to use /sys and fallback to /proc + // but I don't know why the /sys support was added; research needed. + // Since sys is the default, just reproduce that logic for now. + + string sysfs_cpu_directory = "/sys/devices/system/cpu"; + vector package_ids; + if (file_exist(sysfs_cpu_directory)) { + for (int i = 0; ; i++) { + char buf[10]; + snprintf(buf, sizeof(buf) - 1, "%u", i); + string cpu_phys_file = sysfs_cpu_directory + "/cpu" + buf + "/topology/physical_package_id"; + if (!file_exist(cpu_phys_file)) + break; + + package_ids.push_back(read_oneline_file(cpu_phys_file)); + } + + sort(package_ids.begin(), package_ids.end()); + unique(package_ids.begin(), package_ids.end()); + facts["physicalprocessorcount"] = to_string(package_ids.size()); + } else { + // here's where the fall back to /proc/cpuinfo would go } - - sort(package_ids.begin(), package_ids.end()); - unique(package_ids.begin(), package_ids.end()); - facts["physicalprocessorcount"] = to_string(package_ids.size()); - } - else { - // here's where the fall back to /proc/cpuinfo would go - } } void get_processorcount_fact(fact_map& facts) { - std::ifstream cpuinfo_file("/proc/cpuinfo", std::ifstream::in); - std::string line; - int processor_count = 0; - string current_processor_number; - while (std::getline(cpuinfo_file, line)) { - unsigned sep = line.find(":"); - string tmp = line.substr(0, sep); - string key = trim(tmp); - - if (key == "processor") { - ++processor_count; - string tmp = line.substr(sep + 1, string::npos); - current_processor_number = trim(tmp); - } - else if (key == "model name") { - string tmp = line.substr(sep + 1, string::npos); - facts[string("processor") + current_processor_number] = trim(tmp); + std::ifstream cpuinfo_file("/proc/cpuinfo", std::ifstream::in); + std::string line; + int processor_count = 0; + string current_processor_number; + while (std::getline(cpuinfo_file, line)) { + unsigned sep = line.find(":"); + string tmp = line.substr(0, sep); + string key = trim(tmp); + + if (key == "processor") { + ++processor_count; + string tmp = line.substr(sep + 1, string::npos); + current_processor_number = trim(tmp); + } else if (key == "model name") { + string tmp = line.substr(sep + 1, string::npos); + facts[string("processor") + current_processor_number] = trim(tmp); + } } - } - // this was added after 1.7.3, omit for now, needs investigation - if (false) facts["activeprocessorcount"] = processor_count; - facts["processorcount"] = to_string(processor_count); + // this was added after 1.7.3, omit for now, needs investigation + if (false) facts["activeprocessorcount"] = processor_count; + facts["processorcount"] = to_string(processor_count); } void get_processor_facts(fact_map& facts) { - get_physicalprocessorcount_fact(facts); - get_processorcount_fact(facts); + get_physicalprocessorcount_fact(facts); + get_processorcount_fact(facts); } void get_architecture_facts(fact_map& facts) { - struct utsname uts; - if (uname(&uts) == 0) { - // This is cheating at some level because these are all the same on x86_64 linux. - // Otoh, some of these may be compiled-in for a C version. And then if facter - // relies on 'uname -p' here and that commonizes, this should perhaps just shell out - // and not reproduce that logic. Regardless, need to survey cross-platform here and - // take it from there. - facts["hardwaremodel"] = uts.machine; - facts["hardwareisa"] = uts.machine; - facts["architecture"] = uts.machine; - } + struct utsname uts; + if (uname(&uts) == 0) { + // This is cheating at some level because these are all the same on x86_64 linux. + // Otoh, some of these may be compiled-in for a C version. And then if facter + // relies on 'uname -p' here and that commonizes, this should perhaps just shell out + // and not reproduce that logic. Regardless, need to survey cross-platform here and + // take it from there. + facts["hardwaremodel"] = uts.machine; + facts["hardwareisa"] = uts.machine; + facts["architecture"] = uts.machine; + } } void get_dmidecode_facts(fact_map& facts) { - string dmidecode_output = popen_stdout("/usr/sbin/dmidecode 2> /dev/null"); - std::stringstream ss(dmidecode_output); - string line; - - enum { - bios_information, - base_board_information, - system_information, - chassis_information, - unknown - } dmi_section = unknown; - - while (std::getline(ss, line)) { - if (line.empty()) continue; - - // enable case-insensitive compares - ci_string ci_line = line.c_str(); - - // identify the dmi section, they all begin at the beginning of a line - // and there are only a handful of interest to us - if (ci_line == "BIOS Information") { - dmi_section = bios_information; - continue; - } - else if (ci_line == "Base Board Information") { - dmi_section = base_board_information; - continue; - } - else if (ci_line == "System Information") { - dmi_section = system_information; - continue; - } - else if (ci_line == "Chassis Information" || ci_line == "system enclosure or chassis") { - dmi_section = chassis_information; - continue; - } - else if (ci_line[0] >= 'A' && ci_line[0] <= 'Z') { - dmi_section = unknown; - continue; - } - - // if we're in the middle of an unknown section, skip - if (dmi_section == unknown) continue; - - size_t sep = line.find(":"); - if (sep != string::npos) { - string tmp = line.substr(0, sep); - string key = trim(tmp); - if (dmi_section == bios_information) { - ci_string ci_key = key.c_str(); - if (ci_key == "vendor") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); - facts["bios_vendor"] = value; - } - if (ci_key == "version") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); - facts["bios_version"] = value; - } - if (ci_key == "release date") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); - facts["bios_release_date"] = value; - } - } - else if (dmi_section == base_board_information) { - ci_string ci_key = key.c_str(); - if (ci_key == "manufacturer") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); - facts["boardmanufacturer"] = value; - } - if (ci_key == "product name" || ci_key == "product") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); - facts["boardproductname"] = value; - } - if (ci_key == "serial number") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); - facts["boardserialnumber"] = value; - } - } - else if (dmi_section == system_information) { - ci_string ci_key = key.c_str(); - if (ci_key == "manufacturer") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); - facts["manufacturer"] = value; - } - if (ci_key == "product name" || ci_key == "product") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); - facts["productname"] = value; - } - if (ci_key == "serial number") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); - facts["serialnumber"] = value; - } - if (ci_key == "uuid") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); - facts["uuid"] = value; - } - } - else if (dmi_section == chassis_information) { - ci_string ci_key = key.c_str(); - if (ci_key == "chassis type" || ci_key == "type") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); - facts["type"] = value; - } - } - } - } + string dmidecode_output = popen_stdout("/usr/sbin/dmidecode 2> /dev/null"); + std::stringstream ss(dmidecode_output); + string line; + + enum { + bios_information, + base_board_information, + system_information, + chassis_information, + unknown + } dmi_section = unknown; + + while (std::getline(ss, line)) { + if (line.empty()) continue; + + // enable case-insensitive compares + ci_string ci_line = line.c_str(); + + // identify the dmi section, they all begin at the beginning of a line + // and there are only a handful of interest to us + if (ci_line == "BIOS Information") { + dmi_section = bios_information; + continue; + } else if (ci_line == "Base Board Information") { + dmi_section = base_board_information; + continue; + } else if (ci_line == "System Information") { + dmi_section = system_information; + continue; + } else if (ci_line == "Chassis Information" || ci_line == "system enclosure or chassis") { + dmi_section = chassis_information; + continue; + } else if (ci_line[0] >= 'A' && ci_line[0] <= 'Z') { + dmi_section = unknown; + continue; + } + + // if we're in the middle of an unknown section, skip + if (dmi_section == unknown) continue; + + size_t sep = line.find(":"); + if (sep != string::npos) { + string tmp = line.substr(0, sep); + string key = trim(tmp); + if (dmi_section == bios_information) { + ci_string ci_key = key.c_str(); + if (ci_key == "vendor") { + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); + facts["bios_vendor"] = value; + } + if (ci_key == "version") { + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); + facts["bios_version"] = value; + } + if (ci_key == "release date") { + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); + facts["bios_release_date"] = value; + } + } else if (dmi_section == base_board_information) { + ci_string ci_key = key.c_str(); + if (ci_key == "manufacturer") { + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); + facts["boardmanufacturer"] = value; + } + if (ci_key == "product name" || ci_key == "product") { + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); + facts["boardproductname"] = value; + } + if (ci_key == "serial number") { + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); + facts["boardserialnumber"] = value; + } + } else if (dmi_section == system_information) { + ci_string ci_key = key.c_str(); + if (ci_key == "manufacturer") { + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); + facts["manufacturer"] = value; + } + if (ci_key == "product name" || ci_key == "product") { + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); + facts["productname"] = value; + } + if (ci_key == "serial number") { + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); + facts["serialnumber"] = value; + } + if (ci_key == "uuid") { + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); + facts["uuid"] = value; + } + } else if (dmi_section == chassis_information) { + ci_string ci_key = key.c_str(); + if (ci_key == "chassis type" || ci_key == "type") { + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); + facts["type"] = value; + } + } + } + } } void get_filesystems_facts(fact_map& facts) { - std::ifstream cpuinfo_file("/proc/filesystems", std::ifstream::in); - std::string line; - string filesystems = ""; - while (std::getline(cpuinfo_file, line)) { - if (line.find("nodev") != string::npos || line.find("fuseblk") != string::npos) - continue; + std::ifstream cpuinfo_file("/proc/filesystems", std::ifstream::in); + std::string line; + string filesystems = ""; + while (std::getline(cpuinfo_file, line)) { + if (line.find("nodev") != string::npos || line.find("fuseblk") != string::npos) + continue; - if (!filesystems.empty()) - filesystems += ","; + if (!filesystems.empty()) + filesystems += ","; - filesystems += trim(line); - } - facts["filesystems"] = filesystems; + filesystems += trim(line); + } + facts["filesystems"] = filesystems; } void get_hostname_facts(fact_map& facts) { - // there's some history here, perhaps just port the facter conditional straight across? - // so this is short-term - string hostname_output = popen_stdout("hostname"); - unsigned sep = hostname_output.find("."); - string hostname1 = hostname_output.substr(0, sep); - string hostname = trim(hostname1); - - ifstream resolv_conf_file("/etc/resolv.conf", std::ifstream::in); - string line; - string domain; - string search; - while (std::getline(resolv_conf_file, line)) { - vector elems; - tokenize(line, elems); - if (elems.size() >= 2) { - if (elems[0] == "domain") - domain = trim(elems[1]); - else if (elems[0] == "search") - search = trim(elems[1]); - } - } - if (domain.empty() && !search.empty()) - domain = search; - - facts["hostname"] = hostname; - facts["domain"] = domain; - facts["fqdn"] = hostname + "." + domain; + // there's some history here, perhaps just port the facter conditional straight across? + // so this is short-term + string hostname_output = popen_stdout("hostname"); + unsigned sep = hostname_output.find("."); + string hostname1 = hostname_output.substr(0, sep); + string hostname = trim(hostname1); + + ifstream resolv_conf_file("/etc/resolv.conf", std::ifstream::in); + string line; + string domain; + string search; + while (std::getline(resolv_conf_file, line)) { + vector elems; + tokenize(line, elems); + if (elems.size() >= 2) { + if (elems[0] == "domain") + domain = trim(elems[1]); + else if (elems[0] == "search") + search = trim(elems[1]); + } + } + if (domain.empty() && !search.empty()) + domain = search; + + facts["hostname"] = hostname; + facts["domain"] = domain; + facts["fqdn"] = hostname + "." + domain; } static void get_external_facts_from_executable(fact_map& facts, string executable) { - // executable - FILE *stdout = popen(executable.c_str(), "r"); - if (stdout) - { - while (!feof(stdout)) - { - const int buffer_len = 32 * 1024; - char buffer[buffer_len]; - if (fgets(buffer, buffer_len, stdout)) - { - vector elems; - split(buffer, '=', elems); - if (elems.size() != 2) continue; // shouldn't happen - string key = trim(elems[0]); - string val = trim(elems[1]); - facts[key] = val; - } + // executable + FILE *stdout = popen(executable.c_str(), "r"); + if (stdout) { + while (!feof(stdout)) { + const int buffer_len = 32 * 1024; + char buffer[buffer_len]; + if (fgets(buffer, buffer_len, stdout)) { + vector elems; + split(buffer, '=', elems); + if (elems.size() != 2) continue; // shouldn't happen + string key = trim(elems[0]); + string val = trim(elems[1]); + facts[key] = val; + } + } } - } } static void get_external_facts(fact_map& facts, string directory) { - DIR *external_dir = opendir(directory.c_str()); - if (external_dir == NULL) - return; + DIR *external_dir = opendir(directory.c_str()); + if (external_dir == NULL) + return; - struct dirent *external_fact; - struct stat s; + struct dirent *external_fact; + struct stat s; - while ((external_fact = readdir(external_dir))) { - string full_path = directory + "/" + external_fact->d_name; + while ((external_fact = readdir(external_dir))) { + string full_path = directory + "/" + external_fact->d_name; - if (stat(full_path.c_str(), &s) != 0) - continue; + if (stat(full_path.c_str(), &s) != 0) + continue; - if (s.st_mode & S_IFDIR) - continue; + if (s.st_mode & S_IFDIR) + continue; - if (access(full_path.c_str(), X_OK) != 0) - continue; + if (access(full_path.c_str(), X_OK) != 0) + continue; - get_external_facts_from_executable(facts, full_path); - } + get_external_facts_from_executable(facts, full_path); + } } void get_external_facts(fact_map& facts, list directories) { - list::iterator iter; - for (iter = directories.begin(); iter != directories.end(); ++iter) - { - get_external_facts(facts, *iter); - } + list::iterator iter; + for (iter = directories.begin(); iter != directories.end(); ++iter) { + get_external_facts(facts, *iter); + } } diff --git a/cfacterlib.cc b/cfacterlib.cc index 0b49f79922..98d28652fd 100644 --- a/cfacterlib.cc +++ b/cfacterlib.cc @@ -21,70 +21,69 @@ void clear() void loadfacts() { - // Make loadfacts() idempotent, if client has reason to - // want a reload, they should clear() then loadfacts(). - // - // This goes along with to_json() and value() always calling - // loadfacts(). - // - if (!facts.empty()) - return; - - facts["cfacterversion"] = "0.0.1"; - - list external_directories; - external_directories.push_back("/etc/facter/facts.d"); - - get_network_facts(facts); - get_kernel_facts(facts); - get_blockdevice_facts(facts); - get_operatingsystem_facts(facts); - get_uptime_facts(facts); - get_virtual_facts(facts); - get_hardwired_facts(facts); - get_misc_facts(facts); - get_ruby_lib_versions(facts); - get_mem_facts(facts); - get_selinux_facts(facts); - get_ssh_facts(facts); - get_processor_facts(facts); - get_architecture_facts(facts); - get_dmidecode_facts(facts); - get_filesystems_facts(facts); - get_hostname_facts(facts); - get_external_facts(facts, external_directories); + // Make loadfacts() idempotent, if client has reason to + // want a reload, they should clear() then loadfacts(). + // + // This goes along with to_json() and value() always calling + // loadfacts(). + // + if (!facts.empty()) + return; + + facts["cfacterversion"] = "0.0.1"; + + list external_directories; + external_directories.push_back("/etc/facter/facts.d"); + + get_network_facts(facts); + get_kernel_facts(facts); + get_blockdevice_facts(facts); + get_operatingsystem_facts(facts); + get_uptime_facts(facts); + get_virtual_facts(facts); + get_hardwired_facts(facts); + get_misc_facts(facts); + get_ruby_lib_versions(facts); + get_mem_facts(facts); + get_selinux_facts(facts); + get_ssh_facts(facts); + get_processor_facts(facts); + get_architecture_facts(facts); + get_dmidecode_facts(facts); + get_filesystems_facts(facts); + get_hostname_facts(facts); + get_external_facts(facts, external_directories); } int to_json(char *facts_json, size_t facts_len) { - loadfacts(); - - if (0) { - typedef map::iterator iter; - for (iter i = facts.begin(); i != facts.end(); ++i) { - cout << i->first << " => " << i->second << endl; - } - } - else { - rapidjson::Document json; - json.SetObject(); - - rapidjson::Document::AllocatorType& allocator = json.GetAllocator(); + loadfacts(); - typedef map::iterator iter; - for (iter i = facts.begin(); i != facts.end(); ++i) { - json.AddMember(i->first.c_str(), i->second.c_str(), allocator); + if (0) { + typedef map::iterator iter; + for (iter i = facts.begin(); i != facts.end(); ++i) { + cout << i->first << " => " << i->second << endl; + } + } else { + rapidjson::Document json; + json.SetObject(); + + rapidjson::Document::AllocatorType& allocator = json.GetAllocator(); + + typedef map::iterator iter; + for (iter i = facts.begin(); i != facts.end(); ++i) { + json.AddMember(i->first.c_str(), i->second.c_str(), allocator); + } + + rapidjson::StringBuffer buf; + rapidjson::Writer writer(buf); + json.Accept(writer); + + // FIXME can rapidjson write straight into the provided char array in a safe manner? + // if not roll my own strncpy which doesn't zero-pad and returns success rather than the ptr + strncpy(facts_json, buf.GetString(), facts_len); + return 0; } - - rapidjson::StringBuffer buf; - rapidjson::Writer writer(buf); - json.Accept(writer); - - // FIXME can rapidjson write straight into the provided char array in a safe manner? - // if not roll my own strncpy which doesn't zero-pad and returns success rather than the ptr - strncpy(facts_json, buf.GetString(), facts_len); - return 0; - } } int value(const char *fact, char *value, size_t value_len) diff --git a/cfacterlib.h b/cfacterlib.h index 74cd12a83d..ef379b99d8 100644 --- a/cfacterlib.h +++ b/cfacterlib.h @@ -5,11 +5,11 @@ extern "C" { -void clear(); -void loadfacts(); -int to_json(char *facts, size_t facts_len); -int value(const char *fact, char *value, size_t value_len); -void search_external(const char *dirs); + void clear(); + void loadfacts(); + int to_json(char *facts, size_t facts_len); + int value(const char *fact, char *value, size_t value_len); + void search_external(const char *dirs); } From d33c84b5a290649da1b41f7f7194135176827a83 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Sat, 1 Mar 2014 12:27:16 -0800 Subject: [PATCH 1681/3753] Add lint target using cpplint.py; jury's still out --- Makefile | 5 + ext/cpplint.py | 4753 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 4758 insertions(+) create mode 100755 ext/cpplint.py diff --git a/Makefile b/Makefile index 8012a3edd1..7996709cd8 100644 --- a/Makefile +++ b/Makefile @@ -35,3 +35,8 @@ missing: -@$(shell diff /tmp/facter.txt /tmp/cfacter.txt > /tmp/facterdiff.txt | true) -@cat /tmp/facterdiff.txt -@rm /tmp/facter.txt /tmp/cfacter.txt /tmp/facterdiff.txt + +# Just experimenting with cpplint at this point; not sure what I like and dislike. +.PHONY: lint +lint: + -@ext/cpplint.py --filter=-build/include,-legal/copyright,-readability/streams,-whitespace/braces,-whitespace/line_length,-runtime/arrays,-readability/todo *cc 2>&1 | less diff --git a/ext/cpplint.py b/ext/cpplint.py new file mode 100755 index 0000000000..76d0735278 --- /dev/null +++ b/ext/cpplint.py @@ -0,0 +1,4753 @@ +#!/usr/bin/python +# +# Copyright (c) 2009 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""Does google-lint on c++ files. + +The goal of this script is to identify places in the code that *may* +be in non-compliance with google style. It does not attempt to fix +up these problems -- the point is to educate. It does also not +attempt to find all problems, or to ensure that everything it does +find is legitimately a problem. + +In particular, we can get very confused by /* and // inside strings! +We do a small hack, which is to ignore //'s with "'s after them on the +same line, but it is far from perfect (in either direction). +""" + +import codecs +import copy +import getopt +import math # for log +import os +import re +import sre_compile +import string +import sys +import unicodedata + + +_USAGE = """ +Syntax: cpplint.py [--verbose=#] [--output=vs7] [--filter=-x,+y,...] + [--counting=total|toplevel|detailed] [--root=subdir] + [--linelength=digits] + [file] ... + + The style guidelines this tries to follow are those in + http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml + + Every problem is given a confidence score from 1-5, with 5 meaning we are + certain of the problem, and 1 meaning it could be a legitimate construct. + This will miss some errors, and is not a substitute for a code review. + + To suppress false-positive errors of a certain category, add a + 'NOLINT(category)' comment to the line. NOLINT or NOLINT(*) + suppresses errors of all categories on that line. + + The files passed in will be linted; at least one file must be provided. + Default linted extensions are .cc, .cpp, .cu, .cuh and .h. Change the + extensions with the --extensions flag. + + Flags: + + output=vs7 + By default, the output is formatted to ease emacs parsing. Visual Studio + compatible output (vs7) may also be used. Other formats are unsupported. + + verbose=# + Specify a number 0-5 to restrict errors to certain verbosity levels. + + filter=-x,+y,... + Specify a comma-separated list of category-filters to apply: only + error messages whose category names pass the filters will be printed. + (Category names are printed with the message and look like + "[whitespace/indent]".) Filters are evaluated left to right. + "-FOO" and "FOO" means "do not print categories that start with FOO". + "+FOO" means "do print categories that start with FOO". + + Examples: --filter=-whitespace,+whitespace/braces + --filter=whitespace,runtime/printf,+runtime/printf_format + --filter=-,+build/include_what_you_use + + To see a list of all the categories used in cpplint, pass no arg: + --filter= + + counting=total|toplevel|detailed + The total number of errors found is always printed. If + 'toplevel' is provided, then the count of errors in each of + the top-level categories like 'build' and 'whitespace' will + also be printed. If 'detailed' is provided, then a count + is provided for each category like 'build/class'. + + root=subdir + The root directory used for deriving header guard CPP variable. + By default, the header guard CPP variable is calculated as the relative + path to the directory that contains .git, .hg, or .svn. When this flag + is specified, the relative path is calculated from the specified + directory. If the specified directory does not exist, this flag is + ignored. + + Examples: + Assuing that src/.git exists, the header guard CPP variables for + src/chrome/browser/ui/browser.h are: + + No flag => CHROME_BROWSER_UI_BROWSER_H_ + --root=chrome => BROWSER_UI_BROWSER_H_ + --root=chrome/browser => UI_BROWSER_H_ + + linelength=digits + This is the allowed line length for the project. The default value is + 80 characters. + + Examples: + --linelength=120 + + extensions=extension,extension,... + The allowed file extensions that cpplint will check + + Examples: + --extensions=hpp,cpp +""" + +# We categorize each error message we print. Here are the categories. +# We want an explicit list so we can list them all in cpplint --filter=. +# If you add a new error message with a new category, add it to the list +# here! cpplint_unittest.py should tell you if you forget to do this. +_ERROR_CATEGORIES = [ + 'build/class', + 'build/deprecated', + 'build/endif_comment', + 'build/explicit_make_pair', + 'build/forward_decl', + 'build/header_guard', + 'build/include', + 'build/include_alpha', + 'build/include_order', + 'build/include_what_you_use', + 'build/namespaces', + 'build/printf_format', + 'build/storage_class', + 'legal/copyright', + 'readability/alt_tokens', + 'readability/braces', + 'readability/casting', + 'readability/check', + 'readability/constructors', + 'readability/fn_size', + 'readability/function', + 'readability/multiline_comment', + 'readability/multiline_string', + 'readability/namespace', + 'readability/nolint', + 'readability/nul', + 'readability/streams', + 'readability/todo', + 'readability/utf8', + 'runtime/arrays', + 'runtime/casting', + 'runtime/explicit', + 'runtime/int', + 'runtime/init', + 'runtime/invalid_increment', + 'runtime/member_string_references', + 'runtime/memset', + 'runtime/operator', + 'runtime/printf', + 'runtime/printf_format', + 'runtime/references', + 'runtime/string', + 'runtime/threadsafe_fn', + 'runtime/vlog', + 'whitespace/blank_line', + 'whitespace/braces', + 'whitespace/comma', + 'whitespace/comments', + 'whitespace/empty_conditional_body', + 'whitespace/empty_loop_body', + 'whitespace/end_of_line', + 'whitespace/ending_newline', + 'whitespace/forcolon', + 'whitespace/indent', + 'whitespace/line_length', + 'whitespace/newline', + 'whitespace/operators', + 'whitespace/parens', + 'whitespace/semicolon', + 'whitespace/tab', + 'whitespace/todo' + ] + +# The default state of the category filter. This is overrided by the --filter= +# flag. By default all errors are on, so only add here categories that should be +# off by default (i.e., categories that must be enabled by the --filter= flags). +# All entries here should start with a '-' or '+', as in the --filter= flag. +_DEFAULT_FILTERS = ['-build/include_alpha'] + +# We used to check for high-bit characters, but after much discussion we +# decided those were OK, as long as they were in UTF-8 and didn't represent +# hard-coded international strings, which belong in a separate i18n file. + + +# C++ headers +_CPP_HEADERS = frozenset([ + # Legacy + 'algobase.h', + 'algo.h', + 'alloc.h', + 'builtinbuf.h', + 'bvector.h', + 'complex.h', + 'defalloc.h', + 'deque.h', + 'editbuf.h', + 'fstream.h', + 'function.h', + 'hash_map', + 'hash_map.h', + 'hash_set', + 'hash_set.h', + 'hashtable.h', + 'heap.h', + 'indstream.h', + 'iomanip.h', + 'iostream.h', + 'istream.h', + 'iterator.h', + 'list.h', + 'map.h', + 'multimap.h', + 'multiset.h', + 'ostream.h', + 'pair.h', + 'parsestream.h', + 'pfstream.h', + 'procbuf.h', + 'pthread_alloc', + 'pthread_alloc.h', + 'rope', + 'rope.h', + 'ropeimpl.h', + 'set.h', + 'slist', + 'slist.h', + 'stack.h', + 'stdiostream.h', + 'stl_alloc.h', + 'stl_relops.h', + 'streambuf.h', + 'stream.h', + 'strfile.h', + 'strstream.h', + 'tempbuf.h', + 'tree.h', + 'type_traits.h', + 'vector.h', + # 17.6.1.2 C++ library headers + 'algorithm', + 'array', + 'atomic', + 'bitset', + 'chrono', + 'codecvt', + 'complex', + 'condition_variable', + 'deque', + 'exception', + 'forward_list', + 'fstream', + 'functional', + 'future', + 'initializer_list', + 'iomanip', + 'ios', + 'iosfwd', + 'iostream', + 'istream', + 'iterator', + 'limits', + 'list', + 'locale', + 'map', + 'memory', + 'mutex', + 'new', + 'numeric', + 'ostream', + 'queue', + 'random', + 'ratio', + 'regex', + 'set', + 'sstream', + 'stack', + 'stdexcept', + 'streambuf', + 'string', + 'strstream', + 'system_error', + 'thread', + 'tuple', + 'typeindex', + 'typeinfo', + 'type_traits', + 'unordered_map', + 'unordered_set', + 'utility', + 'valarray', + 'vector', + # 17.6.1.2 C++ headers for C library facilities + 'cassert', + 'ccomplex', + 'cctype', + 'cerrno', + 'cfenv', + 'cfloat', + 'cinttypes', + 'ciso646', + 'climits', + 'clocale', + 'cmath', + 'csetjmp', + 'csignal', + 'cstdalign', + 'cstdarg', + 'cstdbool', + 'cstddef', + 'cstdint', + 'cstdio', + 'cstdlib', + 'cstring', + 'ctgmath', + 'ctime', + 'cuchar', + 'cwchar', + 'cwctype', + ]) + +# Assertion macros. These are defined in base/logging.h and +# testing/base/gunit.h. Note that the _M versions need to come first +# for substring matching to work. +_CHECK_MACROS = [ + 'DCHECK', 'CHECK', + 'EXPECT_TRUE_M', 'EXPECT_TRUE', + 'ASSERT_TRUE_M', 'ASSERT_TRUE', + 'EXPECT_FALSE_M', 'EXPECT_FALSE', + 'ASSERT_FALSE_M', 'ASSERT_FALSE', + ] + +# Replacement macros for CHECK/DCHECK/EXPECT_TRUE/EXPECT_FALSE +_CHECK_REPLACEMENT = dict([(m, {}) for m in _CHECK_MACROS]) + +for op, replacement in [('==', 'EQ'), ('!=', 'NE'), + ('>=', 'GE'), ('>', 'GT'), + ('<=', 'LE'), ('<', 'LT')]: + _CHECK_REPLACEMENT['DCHECK'][op] = 'DCHECK_%s' % replacement + _CHECK_REPLACEMENT['CHECK'][op] = 'CHECK_%s' % replacement + _CHECK_REPLACEMENT['EXPECT_TRUE'][op] = 'EXPECT_%s' % replacement + _CHECK_REPLACEMENT['ASSERT_TRUE'][op] = 'ASSERT_%s' % replacement + _CHECK_REPLACEMENT['EXPECT_TRUE_M'][op] = 'EXPECT_%s_M' % replacement + _CHECK_REPLACEMENT['ASSERT_TRUE_M'][op] = 'ASSERT_%s_M' % replacement + +for op, inv_replacement in [('==', 'NE'), ('!=', 'EQ'), + ('>=', 'LT'), ('>', 'LE'), + ('<=', 'GT'), ('<', 'GE')]: + _CHECK_REPLACEMENT['EXPECT_FALSE'][op] = 'EXPECT_%s' % inv_replacement + _CHECK_REPLACEMENT['ASSERT_FALSE'][op] = 'ASSERT_%s' % inv_replacement + _CHECK_REPLACEMENT['EXPECT_FALSE_M'][op] = 'EXPECT_%s_M' % inv_replacement + _CHECK_REPLACEMENT['ASSERT_FALSE_M'][op] = 'ASSERT_%s_M' % inv_replacement + +# Alternative tokens and their replacements. For full list, see section 2.5 +# Alternative tokens [lex.digraph] in the C++ standard. +# +# Digraphs (such as '%:') are not included here since it's a mess to +# match those on a word boundary. +_ALT_TOKEN_REPLACEMENT = { + 'and': '&&', + 'bitor': '|', + 'or': '||', + 'xor': '^', + 'compl': '~', + 'bitand': '&', + 'and_eq': '&=', + 'or_eq': '|=', + 'xor_eq': '^=', + 'not': '!', + 'not_eq': '!=' + } + +# Compile regular expression that matches all the above keywords. The "[ =()]" +# bit is meant to avoid matching these keywords outside of boolean expressions. +# +# False positives include C-style multi-line comments and multi-line strings +# but those have always been troublesome for cpplint. +_ALT_TOKEN_REPLACEMENT_PATTERN = re.compile( + r'[ =()](' + ('|'.join(_ALT_TOKEN_REPLACEMENT.keys())) + r')(?=[ (]|$)') + + +# These constants define types of headers for use with +# _IncludeState.CheckNextIncludeOrder(). +_C_SYS_HEADER = 1 +_CPP_SYS_HEADER = 2 +_LIKELY_MY_HEADER = 3 +_POSSIBLE_MY_HEADER = 4 +_OTHER_HEADER = 5 + +# These constants define the current inline assembly state +_NO_ASM = 0 # Outside of inline assembly block +_INSIDE_ASM = 1 # Inside inline assembly block +_END_ASM = 2 # Last line of inline assembly block +_BLOCK_ASM = 3 # The whole block is an inline assembly block + +# Match start of assembly blocks +_MATCH_ASM = re.compile(r'^\s*(?:asm|_asm|__asm|__asm__)' + r'(?:\s+(volatile|__volatile__))?' + r'\s*[{(]') + + +_regexp_compile_cache = {} + +# Finds occurrences of NOLINT or NOLINT(...). +_RE_SUPPRESSION = re.compile(r'\bNOLINT\b(\([^)]*\))?') + +# {str, set(int)}: a map from error categories to sets of linenumbers +# on which those errors are expected and should be suppressed. +_error_suppressions = {} + +# The root directory used for deriving header guard CPP variable. +# This is set by --root flag. +_root = None + +# The allowed line length of files. +# This is set by --linelength flag. +_line_length = 80 + +# The allowed extensions for file names +# This is set by --extensions flag. +_valid_extensions = set(['cc', 'h', 'cpp', 'cu', 'cuh']) + +def ParseNolintSuppressions(filename, raw_line, linenum, error): + """Updates the global list of error-suppressions. + + Parses any NOLINT comments on the current line, updating the global + error_suppressions store. Reports an error if the NOLINT comment + was malformed. + + Args: + filename: str, the name of the input file. + raw_line: str, the line of input text, with comments. + linenum: int, the number of the current line. + error: function, an error handler. + """ + # FIXME(adonovan): "NOLINT(" is misparsed as NOLINT(*). + matched = _RE_SUPPRESSION.search(raw_line) + if matched: + category = matched.group(1) + if category in (None, '(*)'): # => "suppress all" + _error_suppressions.setdefault(None, set()).add(linenum) + else: + if category.startswith('(') and category.endswith(')'): + category = category[1:-1] + if category in _ERROR_CATEGORIES: + _error_suppressions.setdefault(category, set()).add(linenum) + else: + error(filename, linenum, 'readability/nolint', 5, + 'Unknown NOLINT error category: %s' % category) + + +def ResetNolintSuppressions(): + "Resets the set of NOLINT suppressions to empty." + _error_suppressions.clear() + + +def IsErrorSuppressedByNolint(category, linenum): + """Returns true if the specified error category is suppressed on this line. + + Consults the global error_suppressions map populated by + ParseNolintSuppressions/ResetNolintSuppressions. + + Args: + category: str, the category of the error. + linenum: int, the current line number. + Returns: + bool, True iff the error should be suppressed due to a NOLINT comment. + """ + return (linenum in _error_suppressions.get(category, set()) or + linenum in _error_suppressions.get(None, set())) + +def Match(pattern, s): + """Matches the string with the pattern, caching the compiled regexp.""" + # The regexp compilation caching is inlined in both Match and Search for + # performance reasons; factoring it out into a separate function turns out + # to be noticeably expensive. + if pattern not in _regexp_compile_cache: + _regexp_compile_cache[pattern] = sre_compile.compile(pattern) + return _regexp_compile_cache[pattern].match(s) + + +def ReplaceAll(pattern, rep, s): + """Replaces instances of pattern in a string with a replacement. + + The compiled regex is kept in a cache shared by Match and Search. + + Args: + pattern: regex pattern + rep: replacement text + s: search string + + Returns: + string with replacements made (or original string if no replacements) + """ + if pattern not in _regexp_compile_cache: + _regexp_compile_cache[pattern] = sre_compile.compile(pattern) + return _regexp_compile_cache[pattern].sub(rep, s) + + +def Search(pattern, s): + """Searches the string for the pattern, caching the compiled regexp.""" + if pattern not in _regexp_compile_cache: + _regexp_compile_cache[pattern] = sre_compile.compile(pattern) + return _regexp_compile_cache[pattern].search(s) + + +class _IncludeState(dict): + """Tracks line numbers for includes, and the order in which includes appear. + + As a dict, an _IncludeState object serves as a mapping between include + filename and line number on which that file was included. + + Call CheckNextIncludeOrder() once for each header in the file, passing + in the type constants defined above. Calls in an illegal order will + raise an _IncludeError with an appropriate error message. + + """ + # self._section will move monotonically through this set. If it ever + # needs to move backwards, CheckNextIncludeOrder will raise an error. + _INITIAL_SECTION = 0 + _MY_H_SECTION = 1 + _C_SECTION = 2 + _CPP_SECTION = 3 + _OTHER_H_SECTION = 4 + + _TYPE_NAMES = { + _C_SYS_HEADER: 'C system header', + _CPP_SYS_HEADER: 'C++ system header', + _LIKELY_MY_HEADER: 'header this file implements', + _POSSIBLE_MY_HEADER: 'header this file may implement', + _OTHER_HEADER: 'other header', + } + _SECTION_NAMES = { + _INITIAL_SECTION: "... nothing. (This can't be an error.)", + _MY_H_SECTION: 'a header this file implements', + _C_SECTION: 'C system header', + _CPP_SECTION: 'C++ system header', + _OTHER_H_SECTION: 'other header', + } + + def __init__(self): + dict.__init__(self) + self.ResetSection() + + def ResetSection(self): + # The name of the current section. + self._section = self._INITIAL_SECTION + # The path of last found header. + self._last_header = '' + + def SetLastHeader(self, header_path): + self._last_header = header_path + + def CanonicalizeAlphabeticalOrder(self, header_path): + """Returns a path canonicalized for alphabetical comparison. + + - replaces "-" with "_" so they both cmp the same. + - removes '-inl' since we don't require them to be after the main header. + - lowercase everything, just in case. + + Args: + header_path: Path to be canonicalized. + + Returns: + Canonicalized path. + """ + return header_path.replace('-inl.h', '.h').replace('-', '_').lower() + + def IsInAlphabeticalOrder(self, clean_lines, linenum, header_path): + """Check if a header is in alphabetical order with the previous header. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + header_path: Canonicalized header to be checked. + + Returns: + Returns true if the header is in alphabetical order. + """ + # If previous section is different from current section, _last_header will + # be reset to empty string, so it's always less than current header. + # + # If previous line was a blank line, assume that the headers are + # intentionally sorted the way they are. + if (self._last_header > header_path and + not Match(r'^\s*$', clean_lines.elided[linenum - 1])): + return False + return True + + def CheckNextIncludeOrder(self, header_type): + """Returns a non-empty error message if the next header is out of order. + + This function also updates the internal state to be ready to check + the next include. + + Args: + header_type: One of the _XXX_HEADER constants defined above. + + Returns: + The empty string if the header is in the right order, or an + error message describing what's wrong. + + """ + error_message = ('Found %s after %s' % + (self._TYPE_NAMES[header_type], + self._SECTION_NAMES[self._section])) + + last_section = self._section + + if header_type == _C_SYS_HEADER: + if self._section <= self._C_SECTION: + self._section = self._C_SECTION + else: + self._last_header = '' + return error_message + elif header_type == _CPP_SYS_HEADER: + if self._section <= self._CPP_SECTION: + self._section = self._CPP_SECTION + else: + self._last_header = '' + return error_message + elif header_type == _LIKELY_MY_HEADER: + if self._section <= self._MY_H_SECTION: + self._section = self._MY_H_SECTION + else: + self._section = self._OTHER_H_SECTION + elif header_type == _POSSIBLE_MY_HEADER: + if self._section <= self._MY_H_SECTION: + self._section = self._MY_H_SECTION + else: + # This will always be the fallback because we're not sure + # enough that the header is associated with this file. + self._section = self._OTHER_H_SECTION + else: + assert header_type == _OTHER_HEADER + self._section = self._OTHER_H_SECTION + + if last_section != self._section: + self._last_header = '' + + return '' + + +class _CppLintState(object): + """Maintains module-wide state..""" + + def __init__(self): + self.verbose_level = 1 # global setting. + self.error_count = 0 # global count of reported errors + # filters to apply when emitting error messages + self.filters = _DEFAULT_FILTERS[:] + self.counting = 'total' # In what way are we counting errors? + self.errors_by_category = {} # string to int dict storing error counts + + # output format: + # "emacs" - format that emacs can parse (default) + # "vs7" - format that Microsoft Visual Studio 7 can parse + self.output_format = 'emacs' + + def SetOutputFormat(self, output_format): + """Sets the output format for errors.""" + self.output_format = output_format + + def SetVerboseLevel(self, level): + """Sets the module's verbosity, and returns the previous setting.""" + last_verbose_level = self.verbose_level + self.verbose_level = level + return last_verbose_level + + def SetCountingStyle(self, counting_style): + """Sets the module's counting options.""" + self.counting = counting_style + + def SetFilters(self, filters): + """Sets the error-message filters. + + These filters are applied when deciding whether to emit a given + error message. + + Args: + filters: A string of comma-separated filters (eg "+whitespace/indent"). + Each filter should start with + or -; else we die. + + Raises: + ValueError: The comma-separated filters did not all start with '+' or '-'. + E.g. "-,+whitespace,-whitespace/indent,whitespace/badfilter" + """ + # Default filters always have less priority than the flag ones. + self.filters = _DEFAULT_FILTERS[:] + for filt in filters.split(','): + clean_filt = filt.strip() + if clean_filt: + self.filters.append(clean_filt) + for filt in self.filters: + if not (filt.startswith('+') or filt.startswith('-')): + raise ValueError('Every filter in --filters must start with + or -' + ' (%s does not)' % filt) + + def ResetErrorCounts(self): + """Sets the module's error statistic back to zero.""" + self.error_count = 0 + self.errors_by_category = {} + + def IncrementErrorCount(self, category): + """Bumps the module's error statistic.""" + self.error_count += 1 + if self.counting in ('toplevel', 'detailed'): + if self.counting != 'detailed': + category = category.split('/')[0] + if category not in self.errors_by_category: + self.errors_by_category[category] = 0 + self.errors_by_category[category] += 1 + + def PrintErrorCounts(self): + """Print a summary of errors by category, and the total.""" + for category, count in self.errors_by_category.iteritems(): + sys.stderr.write('Category \'%s\' errors found: %d\n' % + (category, count)) + sys.stderr.write('Total errors found: %d\n' % self.error_count) + +_cpplint_state = _CppLintState() + + +def _OutputFormat(): + """Gets the module's output format.""" + return _cpplint_state.output_format + + +def _SetOutputFormat(output_format): + """Sets the module's output format.""" + _cpplint_state.SetOutputFormat(output_format) + + +def _VerboseLevel(): + """Returns the module's verbosity setting.""" + return _cpplint_state.verbose_level + + +def _SetVerboseLevel(level): + """Sets the module's verbosity, and returns the previous setting.""" + return _cpplint_state.SetVerboseLevel(level) + + +def _SetCountingStyle(level): + """Sets the module's counting options.""" + _cpplint_state.SetCountingStyle(level) + + +def _Filters(): + """Returns the module's list of output filters, as a list.""" + return _cpplint_state.filters + + +def _SetFilters(filters): + """Sets the module's error-message filters. + + These filters are applied when deciding whether to emit a given + error message. + + Args: + filters: A string of comma-separated filters (eg "whitespace/indent"). + Each filter should start with + or -; else we die. + """ + _cpplint_state.SetFilters(filters) + + +class _FunctionState(object): + """Tracks current function name and the number of lines in its body.""" + + _NORMAL_TRIGGER = 250 # for --v=0, 500 for --v=1, etc. + _TEST_TRIGGER = 400 # about 50% more than _NORMAL_TRIGGER. + + def __init__(self): + self.in_a_function = False + self.lines_in_function = 0 + self.current_function = '' + + def Begin(self, function_name): + """Start analyzing function body. + + Args: + function_name: The name of the function being tracked. + """ + self.in_a_function = True + self.lines_in_function = 0 + self.current_function = function_name + + def Count(self): + """Count line in current function body.""" + if self.in_a_function: + self.lines_in_function += 1 + + def Check(self, error, filename, linenum): + """Report if too many lines in function body. + + Args: + error: The function to call with any errors found. + filename: The name of the current file. + linenum: The number of the line to check. + """ + if Match(r'T(EST|est)', self.current_function): + base_trigger = self._TEST_TRIGGER + else: + base_trigger = self._NORMAL_TRIGGER + trigger = base_trigger * 2**_VerboseLevel() + + if self.lines_in_function > trigger: + error_level = int(math.log(self.lines_in_function / base_trigger, 2)) + # 50 => 0, 100 => 1, 200 => 2, 400 => 3, 800 => 4, 1600 => 5, ... + if error_level > 5: + error_level = 5 + error(filename, linenum, 'readability/fn_size', error_level, + 'Small and focused functions are preferred:' + ' %s has %d non-comment lines' + ' (error triggered by exceeding %d lines).' % ( + self.current_function, self.lines_in_function, trigger)) + + def End(self): + """Stop analyzing function body.""" + self.in_a_function = False + + +class _IncludeError(Exception): + """Indicates a problem with the include order in a file.""" + pass + + +class FileInfo: + """Provides utility functions for filenames. + + FileInfo provides easy access to the components of a file's path + relative to the project root. + """ + + def __init__(self, filename): + self._filename = filename + + def FullName(self): + """Make Windows paths like Unix.""" + return os.path.abspath(self._filename).replace('\\', '/') + + def RepositoryName(self): + """FullName after removing the local path to the repository. + + If we have a real absolute path name here we can try to do something smart: + detecting the root of the checkout and truncating /path/to/checkout from + the name so that we get header guards that don't include things like + "C:\Documents and Settings\..." or "/home/username/..." in them and thus + people on different computers who have checked the source out to different + locations won't see bogus errors. + """ + fullname = self.FullName() + + if os.path.exists(fullname): + project_dir = os.path.dirname(fullname) + + if os.path.exists(os.path.join(project_dir, ".svn")): + # If there's a .svn file in the current directory, we recursively look + # up the directory tree for the top of the SVN checkout + root_dir = project_dir + one_up_dir = os.path.dirname(root_dir) + while os.path.exists(os.path.join(one_up_dir, ".svn")): + root_dir = os.path.dirname(root_dir) + one_up_dir = os.path.dirname(one_up_dir) + + prefix = os.path.commonprefix([root_dir, project_dir]) + return fullname[len(prefix) + 1:] + + # Not SVN <= 1.6? Try to find a git, hg, or svn top level directory by + # searching up from the current path. + root_dir = os.path.dirname(fullname) + while (root_dir != os.path.dirname(root_dir) and + not os.path.exists(os.path.join(root_dir, ".git")) and + not os.path.exists(os.path.join(root_dir, ".hg")) and + not os.path.exists(os.path.join(root_dir, ".svn"))): + root_dir = os.path.dirname(root_dir) + + if (os.path.exists(os.path.join(root_dir, ".git")) or + os.path.exists(os.path.join(root_dir, ".hg")) or + os.path.exists(os.path.join(root_dir, ".svn"))): + prefix = os.path.commonprefix([root_dir, project_dir]) + return fullname[len(prefix) + 1:] + + # Don't know what to do; header guard warnings may be wrong... + return fullname + + def Split(self): + """Splits the file into the directory, basename, and extension. + + For 'chrome/browser/browser.cc', Split() would + return ('chrome/browser', 'browser', '.cc') + + Returns: + A tuple of (directory, basename, extension). + """ + + googlename = self.RepositoryName() + project, rest = os.path.split(googlename) + return (project,) + os.path.splitext(rest) + + def BaseName(self): + """File base name - text after the final slash, before the final period.""" + return self.Split()[1] + + def Extension(self): + """File extension - text following the final period.""" + return self.Split()[2] + + def NoExtension(self): + """File has no source file extension.""" + return '/'.join(self.Split()[0:2]) + + def IsSource(self): + """File has a source file extension.""" + return self.Extension()[1:] in ('c', 'cc', 'cpp', 'cxx') + + +def _ShouldPrintError(category, confidence, linenum): + """If confidence >= verbose, category passes filter and is not suppressed.""" + + # There are three ways we might decide not to print an error message: + # a "NOLINT(category)" comment appears in the source, + # the verbosity level isn't high enough, or the filters filter it out. + if IsErrorSuppressedByNolint(category, linenum): + return False + if confidence < _cpplint_state.verbose_level: + return False + + is_filtered = False + for one_filter in _Filters(): + if one_filter.startswith('-'): + if category.startswith(one_filter[1:]): + is_filtered = True + elif one_filter.startswith('+'): + if category.startswith(one_filter[1:]): + is_filtered = False + else: + assert False # should have been checked for in SetFilter. + if is_filtered: + return False + + return True + + +def Error(filename, linenum, category, confidence, message): + """Logs the fact we've found a lint error. + + We log where the error was found, and also our confidence in the error, + that is, how certain we are this is a legitimate style regression, and + not a misidentification or a use that's sometimes justified. + + False positives can be suppressed by the use of + "cpplint(category)" comments on the offending line. These are + parsed into _error_suppressions. + + Args: + filename: The name of the file containing the error. + linenum: The number of the line containing the error. + category: A string used to describe the "category" this bug + falls under: "whitespace", say, or "runtime". Categories + may have a hierarchy separated by slashes: "whitespace/indent". + confidence: A number from 1-5 representing a confidence score for + the error, with 5 meaning that we are certain of the problem, + and 1 meaning that it could be a legitimate construct. + message: The error message. + """ + if _ShouldPrintError(category, confidence, linenum): + _cpplint_state.IncrementErrorCount(category) + if _cpplint_state.output_format == 'vs7': + sys.stderr.write('%s(%s): %s [%s] [%d]\n' % ( + filename, linenum, message, category, confidence)) + elif _cpplint_state.output_format == 'eclipse': + sys.stderr.write('%s:%s: warning: %s [%s] [%d]\n' % ( + filename, linenum, message, category, confidence)) + else: + sys.stderr.write('%s:%s: %s [%s] [%d]\n' % ( + filename, linenum, message, category, confidence)) + + +# Matches standard C++ escape sequences per 2.13.2.3 of the C++ standard. +_RE_PATTERN_CLEANSE_LINE_ESCAPES = re.compile( + r'\\([abfnrtv?"\\\']|\d+|x[0-9a-fA-F]+)') +# Matches strings. Escape codes should already be removed by ESCAPES. +_RE_PATTERN_CLEANSE_LINE_DOUBLE_QUOTES = re.compile(r'"[^"]*"') +# Matches characters. Escape codes should already be removed by ESCAPES. +_RE_PATTERN_CLEANSE_LINE_SINGLE_QUOTES = re.compile(r"'.'") +# Matches multi-line C++ comments. +# This RE is a little bit more complicated than one might expect, because we +# have to take care of space removals tools so we can handle comments inside +# statements better. +# The current rule is: We only clear spaces from both sides when we're at the +# end of the line. Otherwise, we try to remove spaces from the right side, +# if this doesn't work we try on left side but only if there's a non-character +# on the right. +_RE_PATTERN_CLEANSE_LINE_C_COMMENTS = re.compile( + r"""(\s*/\*.*\*/\s*$| + /\*.*\*/\s+| + \s+/\*.*\*/(?=\W)| + /\*.*\*/)""", re.VERBOSE) + + +def IsCppString(line): + """Does line terminate so, that the next symbol is in string constant. + + This function does not consider single-line nor multi-line comments. + + Args: + line: is a partial line of code starting from the 0..n. + + Returns: + True, if next character appended to 'line' is inside a + string constant. + """ + + line = line.replace(r'\\', 'XX') # after this, \\" does not match to \" + return ((line.count('"') - line.count(r'\"') - line.count("'\"'")) & 1) == 1 + + +def CleanseRawStrings(raw_lines): + """Removes C++11 raw strings from lines. + + Before: + static const char kData[] = R"( + multi-line string + )"; + + After: + static const char kData[] = "" + (replaced by blank line) + ""; + + Args: + raw_lines: list of raw lines. + + Returns: + list of lines with C++11 raw strings replaced by empty strings. + """ + + delimiter = None + lines_without_raw_strings = [] + for line in raw_lines: + if delimiter: + # Inside a raw string, look for the end + end = line.find(delimiter) + if end >= 0: + # Found the end of the string, match leading space for this + # line and resume copying the original lines, and also insert + # a "" on the last line. + leading_space = Match(r'^(\s*)\S', line) + line = leading_space.group(1) + '""' + line[end + len(delimiter):] + delimiter = None + else: + # Haven't found the end yet, append a blank line. + line = '' + + else: + # Look for beginning of a raw string. + # See 2.14.15 [lex.string] for syntax. + matched = Match(r'^(.*)\b(?:R|u8R|uR|UR|LR)"([^\s\\()]*)\((.*)$', line) + if matched: + delimiter = ')' + matched.group(2) + '"' + + end = matched.group(3).find(delimiter) + if end >= 0: + # Raw string ended on same line + line = (matched.group(1) + '""' + + matched.group(3)[end + len(delimiter):]) + delimiter = None + else: + # Start of a multi-line raw string + line = matched.group(1) + '""' + + lines_without_raw_strings.append(line) + + # TODO(unknown): if delimiter is not None here, we might want to + # emit a warning for unterminated string. + return lines_without_raw_strings + + +def FindNextMultiLineCommentStart(lines, lineix): + """Find the beginning marker for a multiline comment.""" + while lineix < len(lines): + if lines[lineix].strip().startswith('/*'): + # Only return this marker if the comment goes beyond this line + if lines[lineix].strip().find('*/', 2) < 0: + return lineix + lineix += 1 + return len(lines) + + +def FindNextMultiLineCommentEnd(lines, lineix): + """We are inside a comment, find the end marker.""" + while lineix < len(lines): + if lines[lineix].strip().endswith('*/'): + return lineix + lineix += 1 + return len(lines) + + +def RemoveMultiLineCommentsFromRange(lines, begin, end): + """Clears a range of lines for multi-line comments.""" + # Having // dummy comments makes the lines non-empty, so we will not get + # unnecessary blank line warnings later in the code. + for i in range(begin, end): + lines[i] = '// dummy' + + +def RemoveMultiLineComments(filename, lines, error): + """Removes multiline (c-style) comments from lines.""" + lineix = 0 + while lineix < len(lines): + lineix_begin = FindNextMultiLineCommentStart(lines, lineix) + if lineix_begin >= len(lines): + return + lineix_end = FindNextMultiLineCommentEnd(lines, lineix_begin) + if lineix_end >= len(lines): + error(filename, lineix_begin + 1, 'readability/multiline_comment', 5, + 'Could not find end of multi-line comment') + return + RemoveMultiLineCommentsFromRange(lines, lineix_begin, lineix_end + 1) + lineix = lineix_end + 1 + + +def CleanseComments(line): + """Removes //-comments and single-line C-style /* */ comments. + + Args: + line: A line of C++ source. + + Returns: + The line with single-line comments removed. + """ + commentpos = line.find('//') + if commentpos != -1 and not IsCppString(line[:commentpos]): + line = line[:commentpos].rstrip() + # get rid of /* ... */ + return _RE_PATTERN_CLEANSE_LINE_C_COMMENTS.sub('', line) + + +class CleansedLines(object): + """Holds 3 copies of all lines with different preprocessing applied to them. + + 1) elided member contains lines without strings and comments, + 2) lines member contains lines without comments, and + 3) raw_lines member contains all the lines without processing. + All these three members are of , and of the same length. + """ + + def __init__(self, lines): + self.elided = [] + self.lines = [] + self.raw_lines = lines + self.num_lines = len(lines) + self.lines_without_raw_strings = CleanseRawStrings(lines) + for linenum in range(len(self.lines_without_raw_strings)): + self.lines.append(CleanseComments( + self.lines_without_raw_strings[linenum])) + elided = self._CollapseStrings(self.lines_without_raw_strings[linenum]) + self.elided.append(CleanseComments(elided)) + + def NumLines(self): + """Returns the number of lines represented.""" + return self.num_lines + + @staticmethod + def _CollapseStrings(elided): + """Collapses strings and chars on a line to simple "" or '' blocks. + + We nix strings first so we're not fooled by text like '"http://"' + + Args: + elided: The line being processed. + + Returns: + The line with collapsed strings. + """ + if not _RE_PATTERN_INCLUDE.match(elided): + # Remove escaped characters first to make quote/single quote collapsing + # basic. Things that look like escaped characters shouldn't occur + # outside of strings and chars. + elided = _RE_PATTERN_CLEANSE_LINE_ESCAPES.sub('', elided) + elided = _RE_PATTERN_CLEANSE_LINE_SINGLE_QUOTES.sub("''", elided) + elided = _RE_PATTERN_CLEANSE_LINE_DOUBLE_QUOTES.sub('""', elided) + return elided + + +def FindEndOfExpressionInLine(line, startpos, depth, startchar, endchar): + """Find the position just after the matching endchar. + + Args: + line: a CleansedLines line. + startpos: start searching at this position. + depth: nesting level at startpos. + startchar: expression opening character. + endchar: expression closing character. + + Returns: + On finding matching endchar: (index just after matching endchar, 0) + Otherwise: (-1, new depth at end of this line) + """ + for i in xrange(startpos, len(line)): + if line[i] == startchar: + depth += 1 + elif line[i] == endchar: + depth -= 1 + if depth == 0: + return (i + 1, 0) + return (-1, depth) + + +def CloseExpression(clean_lines, linenum, pos): + """If input points to ( or { or [ or <, finds the position that closes it. + + If lines[linenum][pos] points to a '(' or '{' or '[' or '<', finds the + linenum/pos that correspond to the closing of the expression. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + pos: A position on the line. + + Returns: + A tuple (line, linenum, pos) pointer *past* the closing brace, or + (line, len(lines), -1) if we never find a close. Note we ignore + strings and comments when matching; and the line we return is the + 'cleansed' line at linenum. + """ + + line = clean_lines.elided[linenum] + startchar = line[pos] + if startchar not in '({[<': + return (line, clean_lines.NumLines(), -1) + if startchar == '(': endchar = ')' + if startchar == '[': endchar = ']' + if startchar == '{': endchar = '}' + if startchar == '<': endchar = '>' + + # Check first line + (end_pos, num_open) = FindEndOfExpressionInLine( + line, pos, 0, startchar, endchar) + if end_pos > -1: + return (line, linenum, end_pos) + + # Continue scanning forward + while linenum < clean_lines.NumLines() - 1: + linenum += 1 + line = clean_lines.elided[linenum] + (end_pos, num_open) = FindEndOfExpressionInLine( + line, 0, num_open, startchar, endchar) + if end_pos > -1: + return (line, linenum, end_pos) + + # Did not find endchar before end of file, give up + return (line, clean_lines.NumLines(), -1) + + +def FindStartOfExpressionInLine(line, endpos, depth, startchar, endchar): + """Find position at the matching startchar. + + This is almost the reverse of FindEndOfExpressionInLine, but note + that the input position and returned position differs by 1. + + Args: + line: a CleansedLines line. + endpos: start searching at this position. + depth: nesting level at endpos. + startchar: expression opening character. + endchar: expression closing character. + + Returns: + On finding matching startchar: (index at matching startchar, 0) + Otherwise: (-1, new depth at beginning of this line) + """ + for i in xrange(endpos, -1, -1): + if line[i] == endchar: + depth += 1 + elif line[i] == startchar: + depth -= 1 + if depth == 0: + return (i, 0) + return (-1, depth) + + +def ReverseCloseExpression(clean_lines, linenum, pos): + """If input points to ) or } or ] or >, finds the position that opens it. + + If lines[linenum][pos] points to a ')' or '}' or ']' or '>', finds the + linenum/pos that correspond to the opening of the expression. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + pos: A position on the line. + + Returns: + A tuple (line, linenum, pos) pointer *at* the opening brace, or + (line, 0, -1) if we never find the matching opening brace. Note + we ignore strings and comments when matching; and the line we + return is the 'cleansed' line at linenum. + """ + line = clean_lines.elided[linenum] + endchar = line[pos] + if endchar not in ')}]>': + return (line, 0, -1) + if endchar == ')': startchar = '(' + if endchar == ']': startchar = '[' + if endchar == '}': startchar = '{' + if endchar == '>': startchar = '<' + + # Check last line + (start_pos, num_open) = FindStartOfExpressionInLine( + line, pos, 0, startchar, endchar) + if start_pos > -1: + return (line, linenum, start_pos) + + # Continue scanning backward + while linenum > 0: + linenum -= 1 + line = clean_lines.elided[linenum] + (start_pos, num_open) = FindStartOfExpressionInLine( + line, len(line) - 1, num_open, startchar, endchar) + if start_pos > -1: + return (line, linenum, start_pos) + + # Did not find startchar before beginning of file, give up + return (line, 0, -1) + + +def CheckForCopyright(filename, lines, error): + """Logs an error if no Copyright message appears at the top of the file.""" + + # We'll say it should occur by line 10. Don't forget there's a + # dummy line at the front. + for line in xrange(1, min(len(lines), 11)): + if re.search(r'Copyright', lines[line], re.I): break + else: # means no copyright line was found + error(filename, 0, 'legal/copyright', 5, + 'No copyright message found. ' + 'You should have a line: "Copyright [year] "') + + +def GetHeaderGuardCPPVariable(filename): + """Returns the CPP variable that should be used as a header guard. + + Args: + filename: The name of a C++ header file. + + Returns: + The CPP variable that should be used as a header guard in the + named file. + + """ + + # Restores original filename in case that cpplint is invoked from Emacs's + # flymake. + filename = re.sub(r'_flymake\.h$', '.h', filename) + filename = re.sub(r'/\.flymake/([^/]*)$', r'/\1', filename) + + fileinfo = FileInfo(filename) + file_path_from_root = fileinfo.RepositoryName() + if _root: + file_path_from_root = re.sub('^' + _root + os.sep, '', file_path_from_root) + return re.sub(r'[-./\s]', '_', file_path_from_root).upper() + '_' + + +def CheckForHeaderGuard(filename, lines, error): + """Checks that the file contains a header guard. + + Logs an error if no #ifndef header guard is present. For other + headers, checks that the full pathname is used. + + Args: + filename: The name of the C++ header file. + lines: An array of strings, each representing a line of the file. + error: The function to call with any errors found. + """ + + cppvar = GetHeaderGuardCPPVariable(filename) + + ifndef = None + ifndef_linenum = 0 + define = None + endif = None + endif_linenum = 0 + for linenum, line in enumerate(lines): + linesplit = line.split() + if len(linesplit) >= 2: + # find the first occurrence of #ifndef and #define, save arg + if not ifndef and linesplit[0] == '#ifndef': + # set ifndef to the header guard presented on the #ifndef line. + ifndef = linesplit[1] + ifndef_linenum = linenum + if not define and linesplit[0] == '#define': + define = linesplit[1] + # find the last occurrence of #endif, save entire line + if line.startswith('#endif'): + endif = line + endif_linenum = linenum + + if not ifndef: + error(filename, 0, 'build/header_guard', 5, + 'No #ifndef header guard found, suggested CPP variable is: %s' % + cppvar) + return + + if not define: + error(filename, 0, 'build/header_guard', 5, + 'No #define header guard found, suggested CPP variable is: %s' % + cppvar) + return + + # The guard should be PATH_FILE_H_, but we also allow PATH_FILE_H__ + # for backward compatibility. + if ifndef != cppvar: + error_level = 0 + if ifndef != cppvar + '_': + error_level = 5 + + ParseNolintSuppressions(filename, lines[ifndef_linenum], ifndef_linenum, + error) + error(filename, ifndef_linenum, 'build/header_guard', error_level, + '#ifndef header guard has wrong style, please use: %s' % cppvar) + + if define != ifndef: + error(filename, 0, 'build/header_guard', 5, + '#ifndef and #define don\'t match, suggested CPP variable is: %s' % + cppvar) + return + + if endif != ('#endif // %s' % cppvar): + error_level = 0 + if endif != ('#endif // %s' % (cppvar + '_')): + error_level = 5 + + ParseNolintSuppressions(filename, lines[endif_linenum], endif_linenum, + error) + error(filename, endif_linenum, 'build/header_guard', error_level, + '#endif line should be "#endif // %s"' % cppvar) + + +def CheckForBadCharacters(filename, lines, error): + """Logs an error for each line containing bad characters. + + Two kinds of bad characters: + + 1. Unicode replacement characters: These indicate that either the file + contained invalid UTF-8 (likely) or Unicode replacement characters (which + it shouldn't). Note that it's possible for this to throw off line + numbering if the invalid UTF-8 occurred adjacent to a newline. + + 2. NUL bytes. These are problematic for some tools. + + Args: + filename: The name of the current file. + lines: An array of strings, each representing a line of the file. + error: The function to call with any errors found. + """ + for linenum, line in enumerate(lines): + if u'\ufffd' in line: + error(filename, linenum, 'readability/utf8', 5, + 'Line contains invalid UTF-8 (or Unicode replacement character).') + if '\0' in line: + error(filename, linenum, 'readability/nul', 5, 'Line contains NUL byte.') + + +def CheckForNewlineAtEOF(filename, lines, error): + """Logs an error if there is no newline char at the end of the file. + + Args: + filename: The name of the current file. + lines: An array of strings, each representing a line of the file. + error: The function to call with any errors found. + """ + + # The array lines() was created by adding two newlines to the + # original file (go figure), then splitting on \n. + # To verify that the file ends in \n, we just have to make sure the + # last-but-two element of lines() exists and is empty. + if len(lines) < 3 or lines[-2]: + error(filename, len(lines) - 2, 'whitespace/ending_newline', 5, + 'Could not find a newline character at the end of the file.') + + +def CheckForMultilineCommentsAndStrings(filename, clean_lines, linenum, error): + """Logs an error if we see /* ... */ or "..." that extend past one line. + + /* ... */ comments are legit inside macros, for one line. + Otherwise, we prefer // comments, so it's ok to warn about the + other. Likewise, it's ok for strings to extend across multiple + lines, as long as a line continuation character (backslash) + terminates each line. Although not currently prohibited by the C++ + style guide, it's ugly and unnecessary. We don't do well with either + in this lint program, so we warn about both. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # Remove all \\ (escaped backslashes) from the line. They are OK, and the + # second (escaped) slash may trigger later \" detection erroneously. + line = line.replace('\\\\', '') + + if line.count('/*') > line.count('*/'): + error(filename, linenum, 'readability/multiline_comment', 5, + 'Complex multi-line /*...*/-style comment found. ' + 'Lint may give bogus warnings. ' + 'Consider replacing these with //-style comments, ' + 'with #if 0...#endif, ' + 'or with more clearly structured multi-line comments.') + + if (line.count('"') - line.count('\\"')) % 2: + error(filename, linenum, 'readability/multiline_string', 5, + 'Multi-line string ("...") found. This lint script doesn\'t ' + 'do well with such strings, and may give bogus warnings. ' + 'Use C++11 raw strings or concatenation instead.') + + +threading_list = ( + ('asctime(', 'asctime_r('), + ('ctime(', 'ctime_r('), + ('getgrgid(', 'getgrgid_r('), + ('getgrnam(', 'getgrnam_r('), + ('getlogin(', 'getlogin_r('), + ('getpwnam(', 'getpwnam_r('), + ('getpwuid(', 'getpwuid_r('), + ('gmtime(', 'gmtime_r('), + ('localtime(', 'localtime_r('), + ('rand(', 'rand_r('), + ('strtok(', 'strtok_r('), + ('ttyname(', 'ttyname_r('), + ) + + +def CheckPosixThreading(filename, clean_lines, linenum, error): + """Checks for calls to thread-unsafe functions. + + Much code has been originally written without consideration of + multi-threading. Also, engineers are relying on their old experience; + they have learned posix before threading extensions were added. These + tests guide the engineers to use thread-safe functions (when using + posix directly). + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + for single_thread_function, multithread_safe_function in threading_list: + ix = line.find(single_thread_function) + # Comparisons made explicit for clarity -- pylint: disable=g-explicit-bool-comparison + if ix >= 0 and (ix == 0 or (not line[ix - 1].isalnum() and + line[ix - 1] not in ('_', '.', '>'))): + error(filename, linenum, 'runtime/threadsafe_fn', 2, + 'Consider using ' + multithread_safe_function + + '...) instead of ' + single_thread_function + + '...) for improved thread safety.') + + +def CheckVlogArguments(filename, clean_lines, linenum, error): + """Checks that VLOG() is only used for defining a logging level. + + For example, VLOG(2) is correct. VLOG(INFO), VLOG(WARNING), VLOG(ERROR), and + VLOG(FATAL) are not. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + if Search(r'\bVLOG\((INFO|ERROR|WARNING|DFATAL|FATAL)\)', line): + error(filename, linenum, 'runtime/vlog', 5, + 'VLOG() should be used with numeric verbosity level. ' + 'Use LOG() if you want symbolic severity levels.') + + +# Matches invalid increment: *count++, which moves pointer instead of +# incrementing a value. +_RE_PATTERN_INVALID_INCREMENT = re.compile( + r'^\s*\*\w+(\+\+|--);') + + +def CheckInvalidIncrement(filename, clean_lines, linenum, error): + """Checks for invalid increment *count++. + + For example following function: + void increment_counter(int* count) { + *count++; + } + is invalid, because it effectively does count++, moving pointer, and should + be replaced with ++*count, (*count)++ or *count += 1. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + if _RE_PATTERN_INVALID_INCREMENT.match(line): + error(filename, linenum, 'runtime/invalid_increment', 5, + 'Changing pointer instead of value (or unused value of operator*).') + + +class _BlockInfo(object): + """Stores information about a generic block of code.""" + + def __init__(self, seen_open_brace): + self.seen_open_brace = seen_open_brace + self.open_parentheses = 0 + self.inline_asm = _NO_ASM + + def CheckBegin(self, filename, clean_lines, linenum, error): + """Run checks that applies to text up to the opening brace. + + This is mostly for checking the text after the class identifier + and the "{", usually where the base class is specified. For other + blocks, there isn't much to check, so we always pass. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + pass + + def CheckEnd(self, filename, clean_lines, linenum, error): + """Run checks that applies to text after the closing brace. + + This is mostly used for checking end of namespace comments. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + pass + + +class _ClassInfo(_BlockInfo): + """Stores information about a class.""" + + def __init__(self, name, class_or_struct, clean_lines, linenum): + _BlockInfo.__init__(self, False) + self.name = name + self.starting_linenum = linenum + self.is_derived = False + if class_or_struct == 'struct': + self.access = 'public' + self.is_struct = True + else: + self.access = 'private' + self.is_struct = False + + # Remember initial indentation level for this class. Using raw_lines here + # instead of elided to account for leading comments. + initial_indent = Match(r'^( *)\S', clean_lines.raw_lines[linenum]) + if initial_indent: + self.class_indent = len(initial_indent.group(1)) + else: + self.class_indent = 0 + + # Try to find the end of the class. This will be confused by things like: + # class A { + # } *x = { ... + # + # But it's still good enough for CheckSectionSpacing. + self.last_line = 0 + depth = 0 + for i in range(linenum, clean_lines.NumLines()): + line = clean_lines.elided[i] + depth += line.count('{') - line.count('}') + if not depth: + self.last_line = i + break + + def CheckBegin(self, filename, clean_lines, linenum, error): + # Look for a bare ':' + if Search('(^|[^:]):($|[^:])', clean_lines.elided[linenum]): + self.is_derived = True + + def CheckEnd(self, filename, clean_lines, linenum, error): + # Check that closing brace is aligned with beginning of the class. + # Only do this if the closing brace is indented by only whitespaces. + # This means we will not check single-line class definitions. + indent = Match(r'^( *)\}', clean_lines.elided[linenum]) + if indent and len(indent.group(1)) != self.class_indent: + if self.is_struct: + parent = 'struct ' + self.name + else: + parent = 'class ' + self.name + error(filename, linenum, 'whitespace/indent', 3, + 'Closing brace should be aligned with beginning of %s' % parent) + + +class _NamespaceInfo(_BlockInfo): + """Stores information about a namespace.""" + + def __init__(self, name, linenum): + _BlockInfo.__init__(self, False) + self.name = name or '' + self.starting_linenum = linenum + + def CheckEnd(self, filename, clean_lines, linenum, error): + """Check end of namespace comments.""" + line = clean_lines.raw_lines[linenum] + + # Check how many lines is enclosed in this namespace. Don't issue + # warning for missing namespace comments if there aren't enough + # lines. However, do apply checks if there is already an end of + # namespace comment and it's incorrect. + # + # TODO(unknown): We always want to check end of namespace comments + # if a namespace is large, but sometimes we also want to apply the + # check if a short namespace contained nontrivial things (something + # other than forward declarations). There is currently no logic on + # deciding what these nontrivial things are, so this check is + # triggered by namespace size only, which works most of the time. + if (linenum - self.starting_linenum < 10 + and not Match(r'};*\s*(//|/\*).*\bnamespace\b', line)): + return + + # Look for matching comment at end of namespace. + # + # Note that we accept C style "/* */" comments for terminating + # namespaces, so that code that terminate namespaces inside + # preprocessor macros can be cpplint clean. + # + # We also accept stuff like "// end of namespace ." with the + # period at the end. + # + # Besides these, we don't accept anything else, otherwise we might + # get false negatives when existing comment is a substring of the + # expected namespace. + if self.name: + # Named namespace + if not Match((r'};*\s*(//|/\*).*\bnamespace\s+' + re.escape(self.name) + + r'[\*/\.\\\s]*$'), + line): + error(filename, linenum, 'readability/namespace', 5, + 'Namespace should be terminated with "// namespace %s"' % + self.name) + else: + # Anonymous namespace + if not Match(r'};*\s*(//|/\*).*\bnamespace[\*/\.\\\s]*$', line): + error(filename, linenum, 'readability/namespace', 5, + 'Namespace should be terminated with "// namespace"') + + +class _PreprocessorInfo(object): + """Stores checkpoints of nesting stacks when #if/#else is seen.""" + + def __init__(self, stack_before_if): + # The entire nesting stack before #if + self.stack_before_if = stack_before_if + + # The entire nesting stack up to #else + self.stack_before_else = [] + + # Whether we have already seen #else or #elif + self.seen_else = False + + +class _NestingState(object): + """Holds states related to parsing braces.""" + + def __init__(self): + # Stack for tracking all braces. An object is pushed whenever we + # see a "{", and popped when we see a "}". Only 3 types of + # objects are possible: + # - _ClassInfo: a class or struct. + # - _NamespaceInfo: a namespace. + # - _BlockInfo: some other type of block. + self.stack = [] + + # Stack of _PreprocessorInfo objects. + self.pp_stack = [] + + def SeenOpenBrace(self): + """Check if we have seen the opening brace for the innermost block. + + Returns: + True if we have seen the opening brace, False if the innermost + block is still expecting an opening brace. + """ + return (not self.stack) or self.stack[-1].seen_open_brace + + def InNamespaceBody(self): + """Check if we are currently one level inside a namespace body. + + Returns: + True if top of the stack is a namespace block, False otherwise. + """ + return self.stack and isinstance(self.stack[-1], _NamespaceInfo) + + def UpdatePreprocessor(self, line): + """Update preprocessor stack. + + We need to handle preprocessors due to classes like this: + #ifdef SWIG + struct ResultDetailsPageElementExtensionPoint { + #else + struct ResultDetailsPageElementExtensionPoint : public Extension { + #endif + + We make the following assumptions (good enough for most files): + - Preprocessor condition evaluates to true from #if up to first + #else/#elif/#endif. + + - Preprocessor condition evaluates to false from #else/#elif up + to #endif. We still perform lint checks on these lines, but + these do not affect nesting stack. + + Args: + line: current line to check. + """ + if Match(r'^\s*#\s*(if|ifdef|ifndef)\b', line): + # Beginning of #if block, save the nesting stack here. The saved + # stack will allow us to restore the parsing state in the #else case. + self.pp_stack.append(_PreprocessorInfo(copy.deepcopy(self.stack))) + elif Match(r'^\s*#\s*(else|elif)\b', line): + # Beginning of #else block + if self.pp_stack: + if not self.pp_stack[-1].seen_else: + # This is the first #else or #elif block. Remember the + # whole nesting stack up to this point. This is what we + # keep after the #endif. + self.pp_stack[-1].seen_else = True + self.pp_stack[-1].stack_before_else = copy.deepcopy(self.stack) + + # Restore the stack to how it was before the #if + self.stack = copy.deepcopy(self.pp_stack[-1].stack_before_if) + else: + # TODO(unknown): unexpected #else, issue warning? + pass + elif Match(r'^\s*#\s*endif\b', line): + # End of #if or #else blocks. + if self.pp_stack: + # If we saw an #else, we will need to restore the nesting + # stack to its former state before the #else, otherwise we + # will just continue from where we left off. + if self.pp_stack[-1].seen_else: + # Here we can just use a shallow copy since we are the last + # reference to it. + self.stack = self.pp_stack[-1].stack_before_else + # Drop the corresponding #if + self.pp_stack.pop() + else: + # TODO(unknown): unexpected #endif, issue warning? + pass + + def Update(self, filename, clean_lines, linenum, error): + """Update nesting state with current line. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # Update pp_stack first + self.UpdatePreprocessor(line) + + # Count parentheses. This is to avoid adding struct arguments to + # the nesting stack. + if self.stack: + inner_block = self.stack[-1] + depth_change = line.count('(') - line.count(')') + inner_block.open_parentheses += depth_change + + # Also check if we are starting or ending an inline assembly block. + if inner_block.inline_asm in (_NO_ASM, _END_ASM): + if (depth_change != 0 and + inner_block.open_parentheses == 1 and + _MATCH_ASM.match(line)): + # Enter assembly block + inner_block.inline_asm = _INSIDE_ASM + else: + # Not entering assembly block. If previous line was _END_ASM, + # we will now shift to _NO_ASM state. + inner_block.inline_asm = _NO_ASM + elif (inner_block.inline_asm == _INSIDE_ASM and + inner_block.open_parentheses == 0): + # Exit assembly block + inner_block.inline_asm = _END_ASM + + # Consume namespace declaration at the beginning of the line. Do + # this in a loop so that we catch same line declarations like this: + # namespace proto2 { namespace bridge { class MessageSet; } } + while True: + # Match start of namespace. The "\b\s*" below catches namespace + # declarations even if it weren't followed by a whitespace, this + # is so that we don't confuse our namespace checker. The + # missing spaces will be flagged by CheckSpacing. + namespace_decl_match = Match(r'^\s*namespace\b\s*([:\w]+)?(.*)$', line) + if not namespace_decl_match: + break + + new_namespace = _NamespaceInfo(namespace_decl_match.group(1), linenum) + self.stack.append(new_namespace) + + line = namespace_decl_match.group(2) + if line.find('{') != -1: + new_namespace.seen_open_brace = True + line = line[line.find('{') + 1:] + + # Look for a class declaration in whatever is left of the line + # after parsing namespaces. The regexp accounts for decorated classes + # such as in: + # class LOCKABLE API Object { + # }; + # + # Templates with class arguments may confuse the parser, for example: + # template , + # class Vector = vector > + # class HeapQueue { + # + # Because this parser has no nesting state about templates, by the + # time it saw "class Comparator", it may think that it's a new class. + # Nested templates have a similar problem: + # template < + # typename ExportedType, + # typename TupleType, + # template class ImplTemplate> + # + # To avoid these cases, we ignore classes that are followed by '=' or '>' + class_decl_match = Match( + r'\s*(template\s*<[\w\s<>,:]*>\s*)?' + r'(class|struct)\s+([A-Z_]+\s+)*(\w+(?:::\w+)*)' + r'(([^=>]|<[^<>]*>|<[^<>]*<[^<>]*>\s*>)*)$', line) + if (class_decl_match and + (not self.stack or self.stack[-1].open_parentheses == 0)): + self.stack.append(_ClassInfo( + class_decl_match.group(4), class_decl_match.group(2), + clean_lines, linenum)) + line = class_decl_match.group(5) + + # If we have not yet seen the opening brace for the innermost block, + # run checks here. + if not self.SeenOpenBrace(): + self.stack[-1].CheckBegin(filename, clean_lines, linenum, error) + + # Update access control if we are inside a class/struct + if self.stack and isinstance(self.stack[-1], _ClassInfo): + classinfo = self.stack[-1] + access_match = Match( + r'^(.*)\b(public|private|protected|signals)(\s+(?:slots\s*)?)?' + r':(?:[^:]|$)', + line) + if access_match: + classinfo.access = access_match.group(2) + + # Check that access keywords are indented +1 space. Skip this + # check if the keywords are not preceded by whitespaces. + indent = access_match.group(1) + if (len(indent) != classinfo.class_indent + 1 and + Match(r'^\s*$', indent)): + if classinfo.is_struct: + parent = 'struct ' + classinfo.name + else: + parent = 'class ' + classinfo.name + slots = '' + if access_match.group(3): + slots = access_match.group(3) + error(filename, linenum, 'whitespace/indent', 3, + '%s%s: should be indented +1 space inside %s' % ( + access_match.group(2), slots, parent)) + + # Consume braces or semicolons from what's left of the line + while True: + # Match first brace, semicolon, or closed parenthesis. + matched = Match(r'^[^{;)}]*([{;)}])(.*)$', line) + if not matched: + break + + token = matched.group(1) + if token == '{': + # If namespace or class hasn't seen a opening brace yet, mark + # namespace/class head as complete. Push a new block onto the + # stack otherwise. + if not self.SeenOpenBrace(): + self.stack[-1].seen_open_brace = True + else: + self.stack.append(_BlockInfo(True)) + if _MATCH_ASM.match(line): + self.stack[-1].inline_asm = _BLOCK_ASM + elif token == ';' or token == ')': + # If we haven't seen an opening brace yet, but we already saw + # a semicolon, this is probably a forward declaration. Pop + # the stack for these. + # + # Similarly, if we haven't seen an opening brace yet, but we + # already saw a closing parenthesis, then these are probably + # function arguments with extra "class" or "struct" keywords. + # Also pop these stack for these. + if not self.SeenOpenBrace(): + self.stack.pop() + else: # token == '}' + # Perform end of block checks and pop the stack. + if self.stack: + self.stack[-1].CheckEnd(filename, clean_lines, linenum, error) + self.stack.pop() + line = matched.group(2) + + def InnermostClass(self): + """Get class info on the top of the stack. + + Returns: + A _ClassInfo object if we are inside a class, or None otherwise. + """ + for i in range(len(self.stack), 0, -1): + classinfo = self.stack[i - 1] + if isinstance(classinfo, _ClassInfo): + return classinfo + return None + + def CheckCompletedBlocks(self, filename, error): + """Checks that all classes and namespaces have been completely parsed. + + Call this when all lines in a file have been processed. + Args: + filename: The name of the current file. + error: The function to call with any errors found. + """ + # Note: This test can result in false positives if #ifdef constructs + # get in the way of brace matching. See the testBuildClass test in + # cpplint_unittest.py for an example of this. + for obj in self.stack: + if isinstance(obj, _ClassInfo): + error(filename, obj.starting_linenum, 'build/class', 5, + 'Failed to find complete declaration of class %s' % + obj.name) + elif isinstance(obj, _NamespaceInfo): + error(filename, obj.starting_linenum, 'build/namespaces', 5, + 'Failed to find complete declaration of namespace %s' % + obj.name) + + +def CheckForNonStandardConstructs(filename, clean_lines, linenum, + nesting_state, error): + r"""Logs an error if we see certain non-ANSI constructs ignored by gcc-2. + + Complain about several constructs which gcc-2 accepts, but which are + not standard C++. Warning about these in lint is one way to ease the + transition to new compilers. + - put storage class first (e.g. "static const" instead of "const static"). + - "%lld" instead of %qd" in printf-type functions. + - "%1$d" is non-standard in printf-type functions. + - "\%" is an undefined character escape sequence. + - text after #endif is not allowed. + - invalid inner-style forward declaration. + - >? and ?= and )\?=?\s*(\w+|[+-]?\d+)(\.\d*)?', + line): + error(filename, linenum, 'build/deprecated', 3, + '>? and ))?' + # r'\s*const\s*' + type_name + '\s*&\s*\w+\s*;' + error(filename, linenum, 'runtime/member_string_references', 2, + 'const string& members are dangerous. It is much better to use ' + 'alternatives, such as pointers or simple constants.') + + # Everything else in this function operates on class declarations. + # Return early if the top of the nesting stack is not a class, or if + # the class head is not completed yet. + classinfo = nesting_state.InnermostClass() + if not classinfo or not classinfo.seen_open_brace: + return + + # The class may have been declared with namespace or classname qualifiers. + # The constructor and destructor will not have those qualifiers. + base_classname = classinfo.name.split('::')[-1] + + # Look for single-argument constructors that aren't marked explicit. + # Technically a valid construct, but against style. + args = Match(r'\s+(?:inline\s+)?%s\s*\(([^,()]+)\)' + % re.escape(base_classname), + line) + if (args and + args.group(1) != 'void' and + not Match(r'(const\s+)?%s(\s+const)?\s*(?:<\w+>\s*)?&' + % re.escape(base_classname), args.group(1).strip())): + error(filename, linenum, 'runtime/explicit', 5, + 'Single-argument constructors should be marked explicit.') + + +def CheckSpacingForFunctionCall(filename, line, linenum, error): + """Checks for the correctness of various spacing around function calls. + + Args: + filename: The name of the current file. + line: The text of the line to check. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + + # Since function calls often occur inside if/for/while/switch + # expressions - which have their own, more liberal conventions - we + # first see if we should be looking inside such an expression for a + # function call, to which we can apply more strict standards. + fncall = line # if there's no control flow construct, look at whole line + for pattern in (r'\bif\s*\((.*)\)\s*{', + r'\bfor\s*\((.*)\)\s*{', + r'\bwhile\s*\((.*)\)\s*[{;]', + r'\bswitch\s*\((.*)\)\s*{'): + match = Search(pattern, line) + if match: + fncall = match.group(1) # look inside the parens for function calls + break + + # Except in if/for/while/switch, there should never be space + # immediately inside parens (eg "f( 3, 4 )"). We make an exception + # for nested parens ( (a+b) + c ). Likewise, there should never be + # a space before a ( when it's a function argument. I assume it's a + # function argument when the char before the whitespace is legal in + # a function name (alnum + _) and we're not starting a macro. Also ignore + # pointers and references to arrays and functions coz they're too tricky: + # we use a very simple way to recognize these: + # " (something)(maybe-something)" or + # " (something)(maybe-something," or + # " (something)[something]" + # Note that we assume the contents of [] to be short enough that + # they'll never need to wrap. + if ( # Ignore control structures. + not Search(r'\b(if|for|while|switch|return|new|delete|catch|sizeof)\b', + fncall) and + # Ignore pointers/references to functions. + not Search(r' \([^)]+\)\([^)]*(\)|,$)', fncall) and + # Ignore pointers/references to arrays. + not Search(r' \([^)]+\)\[[^\]]+\]', fncall)): + if Search(r'\w\s*\(\s(?!\s*\\$)', fncall): # a ( used for a fn call + error(filename, linenum, 'whitespace/parens', 4, + 'Extra space after ( in function call') + elif Search(r'\(\s+(?!(\s*\\)|\()', fncall): + error(filename, linenum, 'whitespace/parens', 2, + 'Extra space after (') + if (Search(r'\w\s+\(', fncall) and + not Search(r'#\s*define|typedef', fncall) and + not Search(r'\w\s+\((\w+::)*\*\w+\)\(', fncall)): + error(filename, linenum, 'whitespace/parens', 4, + 'Extra space before ( in function call') + # If the ) is followed only by a newline or a { + newline, assume it's + # part of a control statement (if/while/etc), and don't complain + if Search(r'[^)]\s+\)\s*[^{\s]', fncall): + # If the closing parenthesis is preceded by only whitespaces, + # try to give a more descriptive error message. + if Search(r'^\s+\)', fncall): + error(filename, linenum, 'whitespace/parens', 2, + 'Closing ) should be moved to the previous line') + else: + error(filename, linenum, 'whitespace/parens', 2, + 'Extra space before )') + + +def IsBlankLine(line): + """Returns true if the given line is blank. + + We consider a line to be blank if the line is empty or consists of + only white spaces. + + Args: + line: A line of a string. + + Returns: + True, if the given line is blank. + """ + return not line or line.isspace() + + +def CheckForFunctionLengths(filename, clean_lines, linenum, + function_state, error): + """Reports for long function bodies. + + For an overview why this is done, see: + http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Write_Short_Functions + + Uses a simplistic algorithm assuming other style guidelines + (especially spacing) are followed. + Only checks unindented functions, so class members are unchecked. + Trivial bodies are unchecked, so constructors with huge initializer lists + may be missed. + Blank/comment lines are not counted so as to avoid encouraging the removal + of vertical space and comments just to get through a lint check. + NOLINT *on the last line of a function* disables this check. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + function_state: Current function name and lines in body so far. + error: The function to call with any errors found. + """ + lines = clean_lines.lines + line = lines[linenum] + raw = clean_lines.raw_lines + raw_line = raw[linenum] + joined_line = '' + + starting_func = False + regexp = r'(\w(\w|::|\*|\&|\s)*)\(' # decls * & space::name( ... + match_result = Match(regexp, line) + if match_result: + # If the name is all caps and underscores, figure it's a macro and + # ignore it, unless it's TEST or TEST_F. + function_name = match_result.group(1).split()[-1] + if function_name == 'TEST' or function_name == 'TEST_F' or ( + not Match(r'[A-Z_]+$', function_name)): + starting_func = True + + if starting_func: + body_found = False + for start_linenum in xrange(linenum, clean_lines.NumLines()): + start_line = lines[start_linenum] + joined_line += ' ' + start_line.lstrip() + if Search(r'(;|})', start_line): # Declarations and trivial functions + body_found = True + break # ... ignore + elif Search(r'{', start_line): + body_found = True + function = Search(r'((\w|:)*)\(', line).group(1) + if Match(r'TEST', function): # Handle TEST... macros + parameter_regexp = Search(r'(\(.*\))', joined_line) + if parameter_regexp: # Ignore bad syntax + function += parameter_regexp.group(1) + else: + function += '()' + function_state.Begin(function) + break + if not body_found: + # No body for the function (or evidence of a non-function) was found. + error(filename, linenum, 'readability/fn_size', 5, + 'Lint failed to find start of function body.') + elif Match(r'^\}\s*$', line): # function end + function_state.Check(error, filename, linenum) + function_state.End() + elif not Match(r'^\s*$', line): + function_state.Count() # Count non-blank/non-comment lines. + + +_RE_PATTERN_TODO = re.compile(r'^//(\s*)TODO(\(.+?\))?:?(\s|$)?') + + +def CheckComment(comment, filename, linenum, error): + """Checks for common mistakes in TODO comments. + + Args: + comment: The text of the comment from the line in question. + filename: The name of the current file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + match = _RE_PATTERN_TODO.match(comment) + if match: + # One whitespace is correct; zero whitespace is handled elsewhere. + leading_whitespace = match.group(1) + if len(leading_whitespace) > 1: + error(filename, linenum, 'whitespace/todo', 2, + 'Too many spaces before TODO') + + username = match.group(2) + if not username: + error(filename, linenum, 'readability/todo', 2, + 'Missing username in TODO; it should look like ' + '"// TODO(my_username): Stuff."') + + middle_whitespace = match.group(3) + # Comparisons made explicit for correctness -- pylint: disable=g-explicit-bool-comparison + if middle_whitespace != ' ' and middle_whitespace != '': + error(filename, linenum, 'whitespace/todo', 2, + 'TODO(my_username) should be followed by a space') + +def CheckAccess(filename, clean_lines, linenum, nesting_state, error): + """Checks for improper use of DISALLOW* macros. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + nesting_state: A _NestingState instance which maintains information about + the current stack of nested blocks being parsed. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] # get rid of comments and strings + + matched = Match((r'\s*(DISALLOW_COPY_AND_ASSIGN|' + r'DISALLOW_EVIL_CONSTRUCTORS|' + r'DISALLOW_IMPLICIT_CONSTRUCTORS)'), line) + if not matched: + return + if nesting_state.stack and isinstance(nesting_state.stack[-1], _ClassInfo): + if nesting_state.stack[-1].access != 'private': + error(filename, linenum, 'readability/constructors', 3, + '%s must be in the private: section' % matched.group(1)) + + else: + # Found DISALLOW* macro outside a class declaration, or perhaps it + # was used inside a function when it should have been part of the + # class declaration. We could issue a warning here, but it + # probably resulted in a compiler error already. + pass + + +def FindNextMatchingAngleBracket(clean_lines, linenum, init_suffix): + """Find the corresponding > to close a template. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: Current line number. + init_suffix: Remainder of the current line after the initial <. + + Returns: + True if a matching bracket exists. + """ + line = init_suffix + nesting_stack = ['<'] + while True: + # Find the next operator that can tell us whether < is used as an + # opening bracket or as a less-than operator. We only want to + # warn on the latter case. + # + # We could also check all other operators and terminate the search + # early, e.g. if we got something like this "a(),;\[\]]*([<>(),;\[\]])(.*)$', line) + if match: + # Found an operator, update nesting stack + operator = match.group(1) + line = match.group(2) + + if nesting_stack[-1] == '<': + # Expecting closing angle bracket + if operator in ('<', '(', '['): + nesting_stack.append(operator) + elif operator == '>': + nesting_stack.pop() + if not nesting_stack: + # Found matching angle bracket + return True + elif operator == ',': + # Got a comma after a bracket, this is most likely a template + # argument. We have not seen a closing angle bracket yet, but + # it's probably a few lines later if we look for it, so just + # return early here. + return True + else: + # Got some other operator. + return False + + else: + # Expecting closing parenthesis or closing bracket + if operator in ('<', '(', '['): + nesting_stack.append(operator) + elif operator in (')', ']'): + # We don't bother checking for matching () or []. If we got + # something like (] or [), it would have been a syntax error. + nesting_stack.pop() + + else: + # Scan the next line + linenum += 1 + if linenum >= len(clean_lines.elided): + break + line = clean_lines.elided[linenum] + + # Exhausted all remaining lines and still no matching angle bracket. + # Most likely the input was incomplete, otherwise we should have + # seen a semicolon and returned early. + return True + + +def FindPreviousMatchingAngleBracket(clean_lines, linenum, init_prefix): + """Find the corresponding < that started a template. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: Current line number. + init_prefix: Part of the current line before the initial >. + + Returns: + True if a matching bracket exists. + """ + line = init_prefix + nesting_stack = ['>'] + while True: + # Find the previous operator + match = Search(r'^(.*)([<>(),;\[\]])[^<>(),;\[\]]*$', line) + if match: + # Found an operator, update nesting stack + operator = match.group(2) + line = match.group(1) + + if nesting_stack[-1] == '>': + # Expecting opening angle bracket + if operator in ('>', ')', ']'): + nesting_stack.append(operator) + elif operator == '<': + nesting_stack.pop() + if not nesting_stack: + # Found matching angle bracket + return True + elif operator == ',': + # Got a comma before a bracket, this is most likely a + # template argument. The opening angle bracket is probably + # there if we look for it, so just return early here. + return True + else: + # Got some other operator. + return False + + else: + # Expecting opening parenthesis or opening bracket + if operator in ('>', ')', ']'): + nesting_stack.append(operator) + elif operator in ('(', '['): + nesting_stack.pop() + + else: + # Scan the previous line + linenum -= 1 + if linenum < 0: + break + line = clean_lines.elided[linenum] + + # Exhausted all earlier lines and still no matching angle bracket. + return False + + +def CheckSpacing(filename, clean_lines, linenum, nesting_state, error): + """Checks for the correctness of various spacing issues in the code. + + Things we check for: spaces around operators, spaces after + if/for/while/switch, no spaces around parens in function calls, two + spaces between code and comment, don't start a block with a blank + line, don't end a function with a blank line, don't add a blank line + after public/protected/private, don't have too many blank lines in a row. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + nesting_state: A _NestingState instance which maintains information about + the current stack of nested blocks being parsed. + error: The function to call with any errors found. + """ + + # Don't use "elided" lines here, otherwise we can't check commented lines. + # Don't want to use "raw" either, because we don't want to check inside C++11 + # raw strings, + raw = clean_lines.lines_without_raw_strings + line = raw[linenum] + + # Before nixing comments, check if the line is blank for no good + # reason. This includes the first line after a block is opened, and + # blank lines at the end of a function (ie, right before a line like '}' + # + # Skip all the blank line checks if we are immediately inside a + # namespace body. In other words, don't issue blank line warnings + # for this block: + # namespace { + # + # } + # + # A warning about missing end of namespace comments will be issued instead. + if IsBlankLine(line) and not nesting_state.InNamespaceBody(): + elided = clean_lines.elided + prev_line = elided[linenum - 1] + prevbrace = prev_line.rfind('{') + # TODO(unknown): Don't complain if line before blank line, and line after, + # both start with alnums and are indented the same amount. + # This ignores whitespace at the start of a namespace block + # because those are not usually indented. + if prevbrace != -1 and prev_line[prevbrace:].find('}') == -1: + # OK, we have a blank line at the start of a code block. Before we + # complain, we check if it is an exception to the rule: The previous + # non-empty line has the parameters of a function header that are indented + # 4 spaces (because they did not fit in a 80 column line when placed on + # the same line as the function name). We also check for the case where + # the previous line is indented 6 spaces, which may happen when the + # initializers of a constructor do not fit into a 80 column line. + exception = False + if Match(r' {6}\w', prev_line): # Initializer list? + # We are looking for the opening column of initializer list, which + # should be indented 4 spaces to cause 6 space indentation afterwards. + search_position = linenum-2 + while (search_position >= 0 + and Match(r' {6}\w', elided[search_position])): + search_position -= 1 + exception = (search_position >= 0 + and elided[search_position][:5] == ' :') + else: + # Search for the function arguments or an initializer list. We use a + # simple heuristic here: If the line is indented 4 spaces; and we have a + # closing paren, without the opening paren, followed by an opening brace + # or colon (for initializer lists) we assume that it is the last line of + # a function header. If we have a colon indented 4 spaces, it is an + # initializer list. + exception = (Match(r' {4}\w[^\(]*\)\s*(const\s*)?(\{\s*$|:)', + prev_line) + or Match(r' {4}:', prev_line)) + + if not exception: + error(filename, linenum, 'whitespace/blank_line', 2, + 'Redundant blank line at the start of a code block ' + 'should be deleted.') + # Ignore blank lines at the end of a block in a long if-else + # chain, like this: + # if (condition1) { + # // Something followed by a blank line + # + # } else if (condition2) { + # // Something else + # } + if linenum + 1 < clean_lines.NumLines(): + next_line = raw[linenum + 1] + if (next_line + and Match(r'\s*}', next_line) + and next_line.find('} else ') == -1): + error(filename, linenum, 'whitespace/blank_line', 3, + 'Redundant blank line at the end of a code block ' + 'should be deleted.') + + matched = Match(r'\s*(public|protected|private):', prev_line) + if matched: + error(filename, linenum, 'whitespace/blank_line', 3, + 'Do not leave a blank line after "%s:"' % matched.group(1)) + + # Next, we complain if there's a comment too near the text + commentpos = line.find('//') + if commentpos != -1: + # Check if the // may be in quotes. If so, ignore it + # Comparisons made explicit for clarity -- pylint: disable=g-explicit-bool-comparison + if (line.count('"', 0, commentpos) - + line.count('\\"', 0, commentpos)) % 2 == 0: # not in quotes + # Allow one space for new scopes, two spaces otherwise: + if (not Match(r'^\s*{ //', line) and + ((commentpos >= 1 and + line[commentpos-1] not in string.whitespace) or + (commentpos >= 2 and + line[commentpos-2] not in string.whitespace))): + error(filename, linenum, 'whitespace/comments', 2, + 'At least two spaces is best between code and comments') + # There should always be a space between the // and the comment + commentend = commentpos + 2 + if commentend < len(line) and not line[commentend] == ' ': + # but some lines are exceptions -- e.g. if they're big + # comment delimiters like: + # //---------------------------------------------------------- + # or are an empty C++ style Doxygen comment, like: + # /// + # or C++ style Doxygen comments placed after the variable: + # ///< Header comment + # //!< Header comment + # or they begin with multiple slashes followed by a space: + # //////// Header comment + match = (Search(r'[=/-]{4,}\s*$', line[commentend:]) or + Search(r'^/$', line[commentend:]) or + Search(r'^!< ', line[commentend:]) or + Search(r'^/< ', line[commentend:]) or + Search(r'^/+ ', line[commentend:])) + if not match: + error(filename, linenum, 'whitespace/comments', 4, + 'Should have a space between // and comment') + CheckComment(line[commentpos:], filename, linenum, error) + + line = clean_lines.elided[linenum] # get rid of comments and strings + + # Don't try to do spacing checks for operator methods + line = re.sub(r'operator(==|!=|<|<<|<=|>=|>>|>)\(', 'operator\(', line) + + # We allow no-spaces around = within an if: "if ( (a=Foo()) == 0 )". + # Otherwise not. Note we only check for non-spaces on *both* sides; + # sometimes people put non-spaces on one side when aligning ='s among + # many lines (not that this is behavior that I approve of...) + if Search(r'[\w.]=[\w.]', line) and not Search(r'\b(if|while) ', line): + error(filename, linenum, 'whitespace/operators', 4, + 'Missing spaces around =') + + # It's ok not to have spaces around binary operators like + - * /, but if + # there's too little whitespace, we get concerned. It's hard to tell, + # though, so we punt on this one for now. TODO. + + # You should always have whitespace around binary operators. + # + # Check <= and >= first to avoid false positives with < and >, then + # check non-include lines for spacing around < and >. + match = Search(r'[^<>=!\s](==|!=|<=|>=)[^<>=!\s]', line) + if match: + error(filename, linenum, 'whitespace/operators', 3, + 'Missing spaces around %s' % match.group(1)) + # We allow no-spaces around << when used like this: 10<<20, but + # not otherwise (particularly, not when used as streams) + # Also ignore using ns::operator<<; + match = Search(r'(operator|\S)(?:L|UL|ULL|l|ul|ull)?<<(\S)', line) + if (match and + not (match.group(1).isdigit() and match.group(2).isdigit()) and + not (match.group(1) == 'operator' and match.group(2) == ';')): + error(filename, linenum, 'whitespace/operators', 3, + 'Missing spaces around <<') + elif not Match(r'#.*include', line): + # Avoid false positives on -> + reduced_line = line.replace('->', '') + + # Look for < that is not surrounded by spaces. This is only + # triggered if both sides are missing spaces, even though + # technically should should flag if at least one side is missing a + # space. This is done to avoid some false positives with shifts. + match = Search(r'[^\s<]<([^\s=<].*)', reduced_line) + if (match and + not FindNextMatchingAngleBracket(clean_lines, linenum, match.group(1))): + error(filename, linenum, 'whitespace/operators', 3, + 'Missing spaces around <') + + # Look for > that is not surrounded by spaces. Similar to the + # above, we only trigger if both sides are missing spaces to avoid + # false positives with shifts. + match = Search(r'^(.*[^\s>])>[^\s=>]', reduced_line) + if (match and + not FindPreviousMatchingAngleBracket(clean_lines, linenum, + match.group(1))): + error(filename, linenum, 'whitespace/operators', 3, + 'Missing spaces around >') + + # We allow no-spaces around >> for almost anything. This is because + # C++11 allows ">>" to close nested templates, which accounts for + # most cases when ">>" is not followed by a space. + # + # We still warn on ">>" followed by alpha character, because that is + # likely due to ">>" being used for right shifts, e.g.: + # value >> alpha + # + # When ">>" is used to close templates, the alphanumeric letter that + # follows would be part of an identifier, and there should still be + # a space separating the template type and the identifier. + # type> alpha + match = Search(r'>>[a-zA-Z_]', line) + if match: + error(filename, linenum, 'whitespace/operators', 3, + 'Missing spaces around >>') + + # There shouldn't be space around unary operators + match = Search(r'(!\s|~\s|[\s]--[\s;]|[\s]\+\+[\s;])', line) + if match: + error(filename, linenum, 'whitespace/operators', 4, + 'Extra space for operator %s' % match.group(1)) + + # A pet peeve of mine: no spaces after an if, while, switch, or for + match = Search(r' (if\(|for\(|while\(|switch\()', line) + if match: + error(filename, linenum, 'whitespace/parens', 5, + 'Missing space before ( in %s' % match.group(1)) + + # For if/for/while/switch, the left and right parens should be + # consistent about how many spaces are inside the parens, and + # there should either be zero or one spaces inside the parens. + # We don't want: "if ( foo)" or "if ( foo )". + # Exception: "for ( ; foo; bar)" and "for (foo; bar; )" are allowed. + match = Search(r'\b(if|for|while|switch)\s*' + r'\(([ ]*)(.).*[^ ]+([ ]*)\)\s*{\s*$', + line) + if match: + if len(match.group(2)) != len(match.group(4)): + if not (match.group(3) == ';' and + len(match.group(2)) == 1 + len(match.group(4)) or + not match.group(2) and Search(r'\bfor\s*\(.*; \)', line)): + error(filename, linenum, 'whitespace/parens', 5, + 'Mismatching spaces inside () in %s' % match.group(1)) + if len(match.group(2)) not in [0, 1]: + error(filename, linenum, 'whitespace/parens', 5, + 'Should have zero or one spaces inside ( and ) in %s' % + match.group(1)) + + # You should always have a space after a comma (either as fn arg or operator) + # + # This does not apply when the non-space character following the + # comma is another comma, since the only time when that happens is + # for empty macro arguments. + # + # We run this check in two passes: first pass on elided lines to + # verify that lines contain missing whitespaces, second pass on raw + # lines to confirm that those missing whitespaces are not due to + # elided comments. + if Search(r',[^,\s]', line) and Search(r',[^,\s]', raw[linenum]): + error(filename, linenum, 'whitespace/comma', 3, + 'Missing space after ,') + + # You should always have a space after a semicolon + # except for few corner cases + # TODO(unknown): clarify if 'if (1) { return 1;}' is requires one more + # space after ; + if Search(r';[^\s};\\)/]', line): + error(filename, linenum, 'whitespace/semicolon', 3, + 'Missing space after ;') + + # Next we will look for issues with function calls. + CheckSpacingForFunctionCall(filename, line, linenum, error) + + # Except after an opening paren, or after another opening brace (in case of + # an initializer list, for instance), you should have spaces before your + # braces. And since you should never have braces at the beginning of a line, + # this is an easy test. + match = Match(r'^(.*[^ ({]){', line) + if match: + # Try a bit harder to check for brace initialization. This + # happens in one of the following forms: + # Constructor() : initializer_list_{} { ... } + # Constructor{}.MemberFunction() + # Type variable{}; + # FunctionCall(type{}, ...); + # LastArgument(..., type{}); + # LOG(INFO) << type{} << " ..."; + # map_of_type[{...}] = ...; + # + # We check for the character following the closing brace, and + # silence the warning if it's one of those listed above, i.e. + # "{.;,)<]". + # + # To account for nested initializer list, we allow any number of + # closing braces up to "{;,)<". We can't simply silence the + # warning on first sight of closing brace, because that would + # cause false negatives for things that are not initializer lists. + # Silence this: But not this: + # Outer{ if (...) { + # Inner{...} if (...){ // Missing space before { + # }; } + # + # There is a false negative with this approach if people inserted + # spurious semicolons, e.g. "if (cond){};", but we will catch the + # spurious semicolon with a separate check. + (endline, endlinenum, endpos) = CloseExpression( + clean_lines, linenum, len(match.group(1))) + trailing_text = '' + if endpos > -1: + trailing_text = endline[endpos:] + for offset in xrange(endlinenum + 1, + min(endlinenum + 3, clean_lines.NumLines() - 1)): + trailing_text += clean_lines.elided[offset] + if not Match(r'^[\s}]*[{.;,)<\]]', trailing_text): + error(filename, linenum, 'whitespace/braces', 5, + 'Missing space before {') + + # Make sure '} else {' has spaces. + if Search(r'}else', line): + error(filename, linenum, 'whitespace/braces', 5, + 'Missing space before else') + + # You shouldn't have spaces before your brackets, except maybe after + # 'delete []' or 'new char * []'. + if Search(r'\w\s+\[', line) and not Search(r'delete\s+\[', line): + error(filename, linenum, 'whitespace/braces', 5, + 'Extra space before [') + + # You shouldn't have a space before a semicolon at the end of the line. + # There's a special case for "for" since the style guide allows space before + # the semicolon there. + if Search(r':\s*;\s*$', line): + error(filename, linenum, 'whitespace/semicolon', 5, + 'Semicolon defining empty statement. Use {} instead.') + elif Search(r'^\s*;\s*$', line): + error(filename, linenum, 'whitespace/semicolon', 5, + 'Line contains only semicolon. If this should be an empty statement, ' + 'use {} instead.') + elif (Search(r'\s+;\s*$', line) and + not Search(r'\bfor\b', line)): + error(filename, linenum, 'whitespace/semicolon', 5, + 'Extra space before last semicolon. If this should be an empty ' + 'statement, use {} instead.') + + # In range-based for, we wanted spaces before and after the colon, but + # not around "::" tokens that might appear. + if (Search('for *\(.*[^:]:[^: ]', line) or + Search('for *\(.*[^: ]:[^:]', line)): + error(filename, linenum, 'whitespace/forcolon', 2, + 'Missing space around colon in range-based for loop') + + +def CheckSectionSpacing(filename, clean_lines, class_info, linenum, error): + """Checks for additional blank line issues related to sections. + + Currently the only thing checked here is blank line before protected/private. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + class_info: A _ClassInfo objects. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + # Skip checks if the class is small, where small means 25 lines or less. + # 25 lines seems like a good cutoff since that's the usual height of + # terminals, and any class that can't fit in one screen can't really + # be considered "small". + # + # Also skip checks if we are on the first line. This accounts for + # classes that look like + # class Foo { public: ... }; + # + # If we didn't find the end of the class, last_line would be zero, + # and the check will be skipped by the first condition. + if (class_info.last_line - class_info.starting_linenum <= 24 or + linenum <= class_info.starting_linenum): + return + + matched = Match(r'\s*(public|protected|private):', clean_lines.lines[linenum]) + if matched: + # Issue warning if the line before public/protected/private was + # not a blank line, but don't do this if the previous line contains + # "class" or "struct". This can happen two ways: + # - We are at the beginning of the class. + # - We are forward-declaring an inner class that is semantically + # private, but needed to be public for implementation reasons. + # Also ignores cases where the previous line ends with a backslash as can be + # common when defining classes in C macros. + prev_line = clean_lines.lines[linenum - 1] + if (not IsBlankLine(prev_line) and + not Search(r'\b(class|struct)\b', prev_line) and + not Search(r'\\$', prev_line)): + # Try a bit harder to find the beginning of the class. This is to + # account for multi-line base-specifier lists, e.g.: + # class Derived + # : public Base { + end_class_head = class_info.starting_linenum + for i in range(class_info.starting_linenum, linenum): + if Search(r'\{\s*$', clean_lines.lines[i]): + end_class_head = i + break + if end_class_head < linenum - 1: + error(filename, linenum, 'whitespace/blank_line', 3, + '"%s:" should be preceded by a blank line' % matched.group(1)) + + +def GetPreviousNonBlankLine(clean_lines, linenum): + """Return the most recent non-blank line and its line number. + + Args: + clean_lines: A CleansedLines instance containing the file contents. + linenum: The number of the line to check. + + Returns: + A tuple with two elements. The first element is the contents of the last + non-blank line before the current line, or the empty string if this is the + first non-blank line. The second is the line number of that line, or -1 + if this is the first non-blank line. + """ + + prevlinenum = linenum - 1 + while prevlinenum >= 0: + prevline = clean_lines.elided[prevlinenum] + if not IsBlankLine(prevline): # if not a blank line... + return (prevline, prevlinenum) + prevlinenum -= 1 + return ('', -1) + + +def CheckBraces(filename, clean_lines, linenum, error): + """Looks for misplaced braces (e.g. at the end of line). + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + + line = clean_lines.elided[linenum] # get rid of comments and strings + + if Match(r'\s*{\s*$', line): + # We allow an open brace to start a line in the case where someone is using + # braces in a block to explicitly create a new scope, which is commonly used + # to control the lifetime of stack-allocated variables. Braces are also + # used for brace initializers inside function calls. We don't detect this + # perfectly: we just don't complain if the last non-whitespace character on + # the previous non-blank line is ',', ';', ':', '(', '{', or '}', or if the + # previous line starts a preprocessor block. + prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0] + if (not Search(r'[,;:}{(]\s*$', prevline) and + not Match(r'\s*#', prevline)): + error(filename, linenum, 'whitespace/braces', 4, + '{ should almost always be at the end of the previous line') + + # An else clause should be on the same line as the preceding closing brace. + if Match(r'\s*else\s*', line): + prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0] + if Match(r'\s*}\s*$', prevline): + error(filename, linenum, 'whitespace/newline', 4, + 'An else should appear on the same line as the preceding }') + + # If braces come on one side of an else, they should be on both. + # However, we have to worry about "else if" that spans multiple lines! + if Search(r'}\s*else[^{]*$', line) or Match(r'[^}]*else\s*{', line): + if Search(r'}\s*else if([^{]*)$', line): # could be multi-line if + # find the ( after the if + pos = line.find('else if') + pos = line.find('(', pos) + if pos > 0: + (endline, _, endpos) = CloseExpression(clean_lines, linenum, pos) + if endline[endpos:].find('{') == -1: # must be brace after if + error(filename, linenum, 'readability/braces', 5, + 'If an else has a brace on one side, it should have it on both') + else: # common case: else not followed by a multi-line if + error(filename, linenum, 'readability/braces', 5, + 'If an else has a brace on one side, it should have it on both') + + # Likewise, an else should never have the else clause on the same line + if Search(r'\belse [^\s{]', line) and not Search(r'\belse if\b', line): + error(filename, linenum, 'whitespace/newline', 4, + 'Else clause should never be on same line as else (use 2 lines)') + + # In the same way, a do/while should never be on one line + if Match(r'\s*do [^\s{]', line): + error(filename, linenum, 'whitespace/newline', 4, + 'do/while clauses should not be on a single line') + + # Block bodies should not be followed by a semicolon. Due to C++11 + # brace initialization, there are more places where semicolons are + # required than not, so we use a whitelist approach to check these + # rather than a blacklist. These are the places where "};" should + # be replaced by just "}": + # 1. Some flavor of block following closing parenthesis: + # for (;;) {}; + # while (...) {}; + # switch (...) {}; + # Function(...) {}; + # if (...) {}; + # if (...) else if (...) {}; + # + # 2. else block: + # if (...) else {}; + # + # 3. const member function: + # Function(...) const {}; + # + # 4. Block following some statement: + # x = 42; + # {}; + # + # 5. Block at the beginning of a function: + # Function(...) { + # {}; + # } + # + # Note that naively checking for the preceding "{" will also match + # braces inside multi-dimensional arrays, but this is fine since + # that expression will not contain semicolons. + # + # 6. Block following another block: + # while (true) {} + # {}; + # + # 7. End of namespaces: + # namespace {}; + # + # These semicolons seems far more common than other kinds of + # redundant semicolons, possibly due to people converting classes + # to namespaces. For now we do not warn for this case. + # + # Try matching case 1 first. + match = Match(r'^(.*\)\s*)\{', line) + if match: + # Matched closing parenthesis (case 1). Check the token before the + # matching opening parenthesis, and don't warn if it looks like a + # macro. This avoids these false positives: + # - macro that defines a base class + # - multi-line macro that defines a base class + # - macro that defines the whole class-head + # + # But we still issue warnings for macros that we know are safe to + # warn, specifically: + # - TEST, TEST_F, TEST_P, MATCHER, MATCHER_P + # - TYPED_TEST + # - INTERFACE_DEF + # - EXCLUSIVE_LOCKS_REQUIRED, SHARED_LOCKS_REQUIRED, LOCKS_EXCLUDED: + # + # We implement a whitelist of safe macros instead of a blacklist of + # unsafe macros, even though the latter appears less frequently in + # google code and would have been easier to implement. This is because + # the downside for getting the whitelist wrong means some extra + # semicolons, while the downside for getting the blacklist wrong + # would result in compile errors. + # + # In addition to macros, we also don't want to warn on compound + # literals. + closing_brace_pos = match.group(1).rfind(')') + opening_parenthesis = ReverseCloseExpression( + clean_lines, linenum, closing_brace_pos) + if opening_parenthesis[2] > -1: + line_prefix = opening_parenthesis[0][0:opening_parenthesis[2]] + macro = Search(r'\b([A-Z_]+)\s*$', line_prefix) + if ((macro and + macro.group(1) not in ( + 'TEST', 'TEST_F', 'MATCHER', 'MATCHER_P', 'TYPED_TEST', + 'EXCLUSIVE_LOCKS_REQUIRED', 'SHARED_LOCKS_REQUIRED', + 'LOCKS_EXCLUDED', 'INTERFACE_DEF')) or + Search(r'\s+=\s*$', line_prefix)): + match = None + + else: + # Try matching cases 2-3. + match = Match(r'^(.*(?:else|\)\s*const)\s*)\{', line) + if not match: + # Try matching cases 4-6. These are always matched on separate lines. + # + # Note that we can't simply concatenate the previous line to the + # current line and do a single match, otherwise we may output + # duplicate warnings for the blank line case: + # if (cond) { + # // blank line + # } + prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0] + if prevline and Search(r'[;{}]\s*$', prevline): + match = Match(r'^(\s*)\{', line) + + # Check matching closing brace + if match: + (endline, endlinenum, endpos) = CloseExpression( + clean_lines, linenum, len(match.group(1))) + if endpos > -1 and Match(r'^\s*;', endline[endpos:]): + # Current {} pair is eligible for semicolon check, and we have found + # the redundant semicolon, output warning here. + # + # Note: because we are scanning forward for opening braces, and + # outputting warnings for the matching closing brace, if there are + # nested blocks with trailing semicolons, we will get the error + # messages in reversed order. + error(filename, endlinenum, 'readability/braces', 4, + "You don't need a ; after a }") + + +def CheckEmptyBlockBody(filename, clean_lines, linenum, error): + """Look for empty loop/conditional body with only a single semicolon. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + + # Search for loop keywords at the beginning of the line. Because only + # whitespaces are allowed before the keywords, this will also ignore most + # do-while-loops, since those lines should start with closing brace. + # + # We also check "if" blocks here, since an empty conditional block + # is likely an error. + line = clean_lines.elided[linenum] + matched = Match(r'\s*(for|while|if)\s*\(', line) + if matched: + # Find the end of the conditional expression + (end_line, end_linenum, end_pos) = CloseExpression( + clean_lines, linenum, line.find('(')) + + # Output warning if what follows the condition expression is a semicolon. + # No warning for all other cases, including whitespace or newline, since we + # have a separate check for semicolons preceded by whitespace. + if end_pos >= 0 and Match(r';', end_line[end_pos:]): + if matched.group(1) == 'if': + error(filename, end_linenum, 'whitespace/empty_conditional_body', 5, + 'Empty conditional bodies should use {}') + else: + error(filename, end_linenum, 'whitespace/empty_loop_body', 5, + 'Empty loop bodies should use {} or continue') + + +def CheckCheck(filename, clean_lines, linenum, error): + """Checks the use of CHECK and EXPECT macros. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + + # Decide the set of replacement macros that should be suggested + lines = clean_lines.elided + check_macro = None + start_pos = -1 + for macro in _CHECK_MACROS: + i = lines[linenum].find(macro) + if i >= 0: + check_macro = macro + + # Find opening parenthesis. Do a regular expression match here + # to make sure that we are matching the expected CHECK macro, as + # opposed to some other macro that happens to contain the CHECK + # substring. + matched = Match(r'^(.*\b' + check_macro + r'\s*)\(', lines[linenum]) + if not matched: + continue + start_pos = len(matched.group(1)) + break + if not check_macro or start_pos < 0: + # Don't waste time here if line doesn't contain 'CHECK' or 'EXPECT' + return + + # Find end of the boolean expression by matching parentheses + (last_line, end_line, end_pos) = CloseExpression( + clean_lines, linenum, start_pos) + if end_pos < 0: + return + if linenum == end_line: + expression = lines[linenum][start_pos + 1:end_pos - 1] + else: + expression = lines[linenum][start_pos + 1:] + for i in xrange(linenum + 1, end_line): + expression += lines[i] + expression += last_line[0:end_pos - 1] + + # Parse expression so that we can take parentheses into account. + # This avoids false positives for inputs like "CHECK((a < 4) == b)", + # which is not replaceable by CHECK_LE. + lhs = '' + rhs = '' + operator = None + while expression: + matched = Match(r'^\s*(<<|<<=|>>|>>=|->\*|->|&&|\|\||' + r'==|!=|>=|>|<=|<|\()(.*)$', expression) + if matched: + token = matched.group(1) + if token == '(': + # Parenthesized operand + expression = matched.group(2) + (end, _) = FindEndOfExpressionInLine(expression, 0, 1, '(', ')') + if end < 0: + return # Unmatched parenthesis + lhs += '(' + expression[0:end] + expression = expression[end:] + elif token in ('&&', '||'): + # Logical and/or operators. This means the expression + # contains more than one term, for example: + # CHECK(42 < a && a < b); + # + # These are not replaceable with CHECK_LE, so bail out early. + return + elif token in ('<<', '<<=', '>>', '>>=', '->*', '->'): + # Non-relational operator + lhs += token + expression = matched.group(2) + else: + # Relational operator + operator = token + rhs = matched.group(2) + break + else: + # Unparenthesized operand. Instead of appending to lhs one character + # at a time, we do another regular expression match to consume several + # characters at once if possible. Trivial benchmark shows that this + # is more efficient when the operands are longer than a single + # character, which is generally the case. + matched = Match(r'^([^-=!<>()&|]+)(.*)$', expression) + if not matched: + matched = Match(r'^(\s*\S)(.*)$', expression) + if not matched: + break + lhs += matched.group(1) + expression = matched.group(2) + + # Only apply checks if we got all parts of the boolean expression + if not (lhs and operator and rhs): + return + + # Check that rhs do not contain logical operators. We already know + # that lhs is fine since the loop above parses out && and ||. + if rhs.find('&&') > -1 or rhs.find('||') > -1: + return + + # At least one of the operands must be a constant literal. This is + # to avoid suggesting replacements for unprintable things like + # CHECK(variable != iterator) + # + # The following pattern matches decimal, hex integers, strings, and + # characters (in that order). + lhs = lhs.strip() + rhs = rhs.strip() + match_constant = r'^([-+]?(\d+|0[xX][0-9a-fA-F]+)[lLuU]{0,3}|".*"|\'.*\')$' + if Match(match_constant, lhs) or Match(match_constant, rhs): + # Note: since we know both lhs and rhs, we can provide a more + # descriptive error message like: + # Consider using CHECK_EQ(x, 42) instead of CHECK(x == 42) + # Instead of: + # Consider using CHECK_EQ instead of CHECK(a == b) + # + # We are still keeping the less descriptive message because if lhs + # or rhs gets long, the error message might become unreadable. + error(filename, linenum, 'readability/check', 2, + 'Consider using %s instead of %s(a %s b)' % ( + _CHECK_REPLACEMENT[check_macro][operator], + check_macro, operator)) + + +def CheckAltTokens(filename, clean_lines, linenum, error): + """Check alternative keywords being used in boolean expressions. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # Avoid preprocessor lines + if Match(r'^\s*#', line): + return + + # Last ditch effort to avoid multi-line comments. This will not help + # if the comment started before the current line or ended after the + # current line, but it catches most of the false positives. At least, + # it provides a way to workaround this warning for people who use + # multi-line comments in preprocessor macros. + # + # TODO(unknown): remove this once cpplint has better support for + # multi-line comments. + if line.find('/*') >= 0 or line.find('*/') >= 0: + return + + for match in _ALT_TOKEN_REPLACEMENT_PATTERN.finditer(line): + error(filename, linenum, 'readability/alt_tokens', 2, + 'Use operator %s instead of %s' % ( + _ALT_TOKEN_REPLACEMENT[match.group(1)], match.group(1))) + + +def GetLineWidth(line): + """Determines the width of the line in column positions. + + Args: + line: A string, which may be a Unicode string. + + Returns: + The width of the line in column positions, accounting for Unicode + combining characters and wide characters. + """ + if isinstance(line, unicode): + width = 0 + for uc in unicodedata.normalize('NFC', line): + if unicodedata.east_asian_width(uc) in ('W', 'F'): + width += 2 + elif not unicodedata.combining(uc): + width += 1 + return width + else: + return len(line) + + +def CheckStyle(filename, clean_lines, linenum, file_extension, nesting_state, + error): + """Checks rules from the 'C++ style rules' section of cppguide.html. + + Most of these rules are hard to test (naming, comment style), but we + do what we can. In particular we check for 2-space indents, line lengths, + tab usage, spaces inside code, etc. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + file_extension: The extension (without the dot) of the filename. + nesting_state: A _NestingState instance which maintains information about + the current stack of nested blocks being parsed. + error: The function to call with any errors found. + """ + + # Don't use "elided" lines here, otherwise we can't check commented lines. + # Don't want to use "raw" either, because we don't want to check inside C++11 + # raw strings, + raw_lines = clean_lines.lines_without_raw_strings + line = raw_lines[linenum] + + if line.find('\t') != -1: + error(filename, linenum, 'whitespace/tab', 1, + 'Tab found; better to use spaces') + + # One or three blank spaces at the beginning of the line is weird; it's + # hard to reconcile that with 2-space indents. + # NOTE: here are the conditions rob pike used for his tests. Mine aren't + # as sophisticated, but it may be worth becoming so: RLENGTH==initial_spaces + # if(RLENGTH > 20) complain = 0; + # if(match($0, " +(error|private|public|protected):")) complain = 0; + # if(match(prev, "&& *$")) complain = 0; + # if(match(prev, "\\|\\| *$")) complain = 0; + # if(match(prev, "[\",=><] *$")) complain = 0; + # if(match($0, " <<")) complain = 0; + # if(match(prev, " +for \\(")) complain = 0; + # if(prevodd && match(prevprev, " +for \\(")) complain = 0; + initial_spaces = 0 + cleansed_line = clean_lines.elided[linenum] + while initial_spaces < len(line) and line[initial_spaces] == ' ': + initial_spaces += 1 + if line and line[-1].isspace(): + error(filename, linenum, 'whitespace/end_of_line', 4, + 'Line ends in whitespace. Consider deleting these extra spaces.') + # There are certain situations we allow one space, notably for section labels + elif ((initial_spaces == 1 or initial_spaces == 3) and + not Match(r'\s*\w+\s*:\s*$', cleansed_line)): + error(filename, linenum, 'whitespace/indent', 3, + 'Weird number of spaces at line-start. ' + 'Are you using a 2-space indent?') + + # Check if the line is a header guard. + is_header_guard = False + if file_extension == 'h': + cppvar = GetHeaderGuardCPPVariable(filename) + if (line.startswith('#ifndef %s' % cppvar) or + line.startswith('#define %s' % cppvar) or + line.startswith('#endif // %s' % cppvar)): + is_header_guard = True + # #include lines and header guards can be long, since there's no clean way to + # split them. + # + # URLs can be long too. It's possible to split these, but it makes them + # harder to cut&paste. + # + # The "$Id:...$" comment may also get very long without it being the + # developers fault. + if (not line.startswith('#include') and not is_header_guard and + not Match(r'^\s*//.*http(s?)://\S*$', line) and + not Match(r'^// \$Id:.*#[0-9]+ \$$', line)): + line_width = GetLineWidth(line) + extended_length = int((_line_length * 1.25)) + if line_width > extended_length: + error(filename, linenum, 'whitespace/line_length', 4, + 'Lines should very rarely be longer than %i characters' % + extended_length) + elif line_width > _line_length: + error(filename, linenum, 'whitespace/line_length', 2, + 'Lines should be <= %i characters long' % _line_length) + + if (cleansed_line.count(';') > 1 and + # for loops are allowed two ;'s (and may run over two lines). + cleansed_line.find('for') == -1 and + (GetPreviousNonBlankLine(clean_lines, linenum)[0].find('for') == -1 or + GetPreviousNonBlankLine(clean_lines, linenum)[0].find(';') != -1) and + # It's ok to have many commands in a switch case that fits in 1 line + not ((cleansed_line.find('case ') != -1 or + cleansed_line.find('default:') != -1) and + cleansed_line.find('break;') != -1)): + error(filename, linenum, 'whitespace/newline', 0, + 'More than one command on the same line') + + # Some more style checks + CheckBraces(filename, clean_lines, linenum, error) + CheckEmptyBlockBody(filename, clean_lines, linenum, error) + CheckAccess(filename, clean_lines, linenum, nesting_state, error) + CheckSpacing(filename, clean_lines, linenum, nesting_state, error) + CheckCheck(filename, clean_lines, linenum, error) + CheckAltTokens(filename, clean_lines, linenum, error) + classinfo = nesting_state.InnermostClass() + if classinfo: + CheckSectionSpacing(filename, clean_lines, classinfo, linenum, error) + + +_RE_PATTERN_INCLUDE_NEW_STYLE = re.compile(r'#include +"[^/]+\.h"') +_RE_PATTERN_INCLUDE = re.compile(r'^\s*#\s*include\s*([<"])([^>"]*)[>"].*$') +# Matches the first component of a filename delimited by -s and _s. That is: +# _RE_FIRST_COMPONENT.match('foo').group(0) == 'foo' +# _RE_FIRST_COMPONENT.match('foo.cc').group(0) == 'foo' +# _RE_FIRST_COMPONENT.match('foo-bar_baz.cc').group(0) == 'foo' +# _RE_FIRST_COMPONENT.match('foo_bar-baz.cc').group(0) == 'foo' +_RE_FIRST_COMPONENT = re.compile(r'^[^-_.]+') + + +def _DropCommonSuffixes(filename): + """Drops common suffixes like _test.cc or -inl.h from filename. + + For example: + >>> _DropCommonSuffixes('foo/foo-inl.h') + 'foo/foo' + >>> _DropCommonSuffixes('foo/bar/foo.cc') + 'foo/bar/foo' + >>> _DropCommonSuffixes('foo/foo_internal.h') + 'foo/foo' + >>> _DropCommonSuffixes('foo/foo_unusualinternal.h') + 'foo/foo_unusualinternal' + + Args: + filename: The input filename. + + Returns: + The filename with the common suffix removed. + """ + for suffix in ('test.cc', 'regtest.cc', 'unittest.cc', + 'inl.h', 'impl.h', 'internal.h'): + if (filename.endswith(suffix) and len(filename) > len(suffix) and + filename[-len(suffix) - 1] in ('-', '_')): + return filename[:-len(suffix) - 1] + return os.path.splitext(filename)[0] + + +def _IsTestFilename(filename): + """Determines if the given filename has a suffix that identifies it as a test. + + Args: + filename: The input filename. + + Returns: + True if 'filename' looks like a test, False otherwise. + """ + if (filename.endswith('_test.cc') or + filename.endswith('_unittest.cc') or + filename.endswith('_regtest.cc')): + return True + else: + return False + + +def _ClassifyInclude(fileinfo, include, is_system): + """Figures out what kind of header 'include' is. + + Args: + fileinfo: The current file cpplint is running over. A FileInfo instance. + include: The path to a #included file. + is_system: True if the #include used <> rather than "". + + Returns: + One of the _XXX_HEADER constants. + + For example: + >>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'stdio.h', True) + _C_SYS_HEADER + >>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'string', True) + _CPP_SYS_HEADER + >>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'foo/foo.h', False) + _LIKELY_MY_HEADER + >>> _ClassifyInclude(FileInfo('foo/foo_unknown_extension.cc'), + ... 'bar/foo_other_ext.h', False) + _POSSIBLE_MY_HEADER + >>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'foo/bar.h', False) + _OTHER_HEADER + """ + # This is a list of all standard c++ header files, except + # those already checked for above. + is_cpp_h = include in _CPP_HEADERS + + if is_system: + if is_cpp_h: + return _CPP_SYS_HEADER + else: + return _C_SYS_HEADER + + # If the target file and the include we're checking share a + # basename when we drop common extensions, and the include + # lives in . , then it's likely to be owned by the target file. + target_dir, target_base = ( + os.path.split(_DropCommonSuffixes(fileinfo.RepositoryName()))) + include_dir, include_base = os.path.split(_DropCommonSuffixes(include)) + if target_base == include_base and ( + include_dir == target_dir or + include_dir == os.path.normpath(target_dir + '/../public')): + return _LIKELY_MY_HEADER + + # If the target and include share some initial basename + # component, it's possible the target is implementing the + # include, so it's allowed to be first, but we'll never + # complain if it's not there. + target_first_component = _RE_FIRST_COMPONENT.match(target_base) + include_first_component = _RE_FIRST_COMPONENT.match(include_base) + if (target_first_component and include_first_component and + target_first_component.group(0) == + include_first_component.group(0)): + return _POSSIBLE_MY_HEADER + + return _OTHER_HEADER + + + +def CheckIncludeLine(filename, clean_lines, linenum, include_state, error): + """Check rules that are applicable to #include lines. + + Strings on #include lines are NOT removed from elided line, to make + certain tasks easier. However, to prevent false positives, checks + applicable to #include lines in CheckLanguage must be put here. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + include_state: An _IncludeState instance in which the headers are inserted. + error: The function to call with any errors found. + """ + fileinfo = FileInfo(filename) + + line = clean_lines.lines[linenum] + + # "include" should use the new style "foo/bar.h" instead of just "bar.h" + if _RE_PATTERN_INCLUDE_NEW_STYLE.search(line): + error(filename, linenum, 'build/include', 4, + 'Include the directory when naming .h files') + + # we shouldn't include a file more than once. actually, there are a + # handful of instances where doing so is okay, but in general it's + # not. + match = _RE_PATTERN_INCLUDE.search(line) + if match: + include = match.group(2) + is_system = (match.group(1) == '<') + if include in include_state: + error(filename, linenum, 'build/include', 4, + '"%s" already included at %s:%s' % + (include, filename, include_state[include])) + else: + include_state[include] = linenum + + # We want to ensure that headers appear in the right order: + # 1) for foo.cc, foo.h (preferred location) + # 2) c system files + # 3) cpp system files + # 4) for foo.cc, foo.h (deprecated location) + # 5) other google headers + # + # We classify each include statement as one of those 5 types + # using a number of techniques. The include_state object keeps + # track of the highest type seen, and complains if we see a + # lower type after that. + error_message = include_state.CheckNextIncludeOrder( + _ClassifyInclude(fileinfo, include, is_system)) + if error_message: + error(filename, linenum, 'build/include_order', 4, + '%s. Should be: %s.h, c system, c++ system, other.' % + (error_message, fileinfo.BaseName())) + canonical_include = include_state.CanonicalizeAlphabeticalOrder(include) + if not include_state.IsInAlphabeticalOrder( + clean_lines, linenum, canonical_include): + error(filename, linenum, 'build/include_alpha', 4, + 'Include "%s" not in alphabetical order' % include) + include_state.SetLastHeader(canonical_include) + + # Look for any of the stream classes that are part of standard C++. + match = _RE_PATTERN_INCLUDE.match(line) + if match: + include = match.group(2) + if Match(r'(f|ind|io|i|o|parse|pf|stdio|str|)?stream$', include): + # Many unit tests use cout, so we exempt them. + if not _IsTestFilename(filename): + error(filename, linenum, 'readability/streams', 3, + 'Streams are highly discouraged.') + + +def _GetTextInside(text, start_pattern): + r"""Retrieves all the text between matching open and close parentheses. + + Given a string of lines and a regular expression string, retrieve all the text + following the expression and between opening punctuation symbols like + (, [, or {, and the matching close-punctuation symbol. This properly nested + occurrences of the punctuations, so for the text like + printf(a(), b(c())); + a call to _GetTextInside(text, r'printf\(') will return 'a(), b(c())'. + start_pattern must match string having an open punctuation symbol at the end. + + Args: + text: The lines to extract text. Its comments and strings must be elided. + It can be single line and can span multiple lines. + start_pattern: The regexp string indicating where to start extracting + the text. + Returns: + The extracted text. + None if either the opening string or ending punctuation could not be found. + """ + # TODO(sugawarayu): Audit cpplint.py to see what places could be profitably + # rewritten to use _GetTextInside (and use inferior regexp matching today). + + # Give opening punctuations to get the matching close-punctuations. + matching_punctuation = {'(': ')', '{': '}', '[': ']'} + closing_punctuation = set(matching_punctuation.itervalues()) + + # Find the position to start extracting text. + match = re.search(start_pattern, text, re.M) + if not match: # start_pattern not found in text. + return None + start_position = match.end(0) + + assert start_position > 0, ( + 'start_pattern must ends with an opening punctuation.') + assert text[start_position - 1] in matching_punctuation, ( + 'start_pattern must ends with an opening punctuation.') + # Stack of closing punctuations we expect to have in text after position. + punctuation_stack = [matching_punctuation[text[start_position - 1]]] + position = start_position + while punctuation_stack and position < len(text): + if text[position] == punctuation_stack[-1]: + punctuation_stack.pop() + elif text[position] in closing_punctuation: + # A closing punctuation without matching opening punctuations. + return None + elif text[position] in matching_punctuation: + punctuation_stack.append(matching_punctuation[text[position]]) + position += 1 + if punctuation_stack: + # Opening punctuations left without matching close-punctuations. + return None + # punctuations match. + return text[start_position:position - 1] + + +# Patterns for matching call-by-reference parameters. +# +# Supports nested templates up to 2 levels deep using this messy pattern: +# < (?: < (?: < [^<>]* +# > +# | [^<>] )* +# > +# | [^<>] )* +# > +_RE_PATTERN_IDENT = r'[_a-zA-Z]\w*' # =~ [[:alpha:]][[:alnum:]]* +_RE_PATTERN_TYPE = ( + r'(?:const\s+)?(?:typename\s+|class\s+|struct\s+|union\s+|enum\s+)?' + r'(?:\w|' + r'\s*<(?:<(?:<[^<>]*>|[^<>])*>|[^<>])*>|' + r'::)+') +# A call-by-reference parameter ends with '& identifier'. +_RE_PATTERN_REF_PARAM = re.compile( + r'(' + _RE_PATTERN_TYPE + r'(?:\s*(?:\bconst\b|[*]))*\s*' + r'&\s*' + _RE_PATTERN_IDENT + r')\s*(?:=[^,()]+)?[,)]') +# A call-by-const-reference parameter either ends with 'const& identifier' +# or looks like 'const type& identifier' when 'type' is atomic. +_RE_PATTERN_CONST_REF_PARAM = ( + r'(?:.*\s*\bconst\s*&\s*' + _RE_PATTERN_IDENT + + r'|const\s+' + _RE_PATTERN_TYPE + r'\s*&\s*' + _RE_PATTERN_IDENT + r')') + + +def CheckLanguage(filename, clean_lines, linenum, file_extension, + include_state, nesting_state, error): + """Checks rules from the 'C++ language rules' section of cppguide.html. + + Some of these rules are hard to test (function overloading, using + uint32 inappropriately), but we do the best we can. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + file_extension: The extension (without the dot) of the filename. + include_state: An _IncludeState instance in which the headers are inserted. + nesting_state: A _NestingState instance which maintains information about + the current stack of nested blocks being parsed. + error: The function to call with any errors found. + """ + # If the line is empty or consists of entirely a comment, no need to + # check it. + line = clean_lines.elided[linenum] + if not line: + return + + match = _RE_PATTERN_INCLUDE.search(line) + if match: + CheckIncludeLine(filename, clean_lines, linenum, include_state, error) + return + + # Reset include state across preprocessor directives. This is meant + # to silence warnings for conditional includes. + if Match(r'^\s*#\s*(?:ifdef|elif|else|endif)\b', line): + include_state.ResetSection() + + # Make Windows paths like Unix. + fullname = os.path.abspath(filename).replace('\\', '/') + + # TODO(unknown): figure out if they're using default arguments in fn proto. + + # Check to see if they're using an conversion function cast. + # I just try to capture the most common basic types, though there are more. + # Parameterless conversion functions, such as bool(), are allowed as they are + # probably a member operator declaration or default constructor. + match = Search( + r'(\bnew\s+)?\b' # Grab 'new' operator, if it's there + r'(int|float|double|bool|char|int32|uint32|int64|uint64)' + r'(\([^)].*)', line) + if match: + matched_new = match.group(1) + matched_type = match.group(2) + matched_funcptr = match.group(3) + + # gMock methods are defined using some variant of MOCK_METHODx(name, type) + # where type may be float(), int(string), etc. Without context they are + # virtually indistinguishable from int(x) casts. Likewise, gMock's + # MockCallback takes a template parameter of the form return_type(arg_type), + # which looks much like the cast we're trying to detect. + # + # std::function<> wrapper has a similar problem. + # + # Return types for function pointers also look like casts if they + # don't have an extra space. + if (matched_new is None and # If new operator, then this isn't a cast + not (Match(r'^\s*MOCK_(CONST_)?METHOD\d+(_T)?\(', line) or + Search(r'\bMockCallback<.*>', line) or + Search(r'\bstd::function<.*>', line)) and + not (matched_funcptr and + Match(r'\((?:[^() ]+::\s*\*\s*)?[^() ]+\)\s*\(', + matched_funcptr))): + # Try a bit harder to catch gmock lines: the only place where + # something looks like an old-style cast is where we declare the + # return type of the mocked method, and the only time when we + # are missing context is if MOCK_METHOD was split across + # multiple lines. The missing MOCK_METHOD is usually one or two + # lines back, so scan back one or two lines. + # + # It's not possible for gmock macros to appear in the first 2 + # lines, since the class head + section name takes up 2 lines. + if (linenum < 2 or + not (Match(r'^\s*MOCK_(?:CONST_)?METHOD\d+(?:_T)?\((?:\S+,)?\s*$', + clean_lines.elided[linenum - 1]) or + Match(r'^\s*MOCK_(?:CONST_)?METHOD\d+(?:_T)?\(\s*$', + clean_lines.elided[linenum - 2]))): + error(filename, linenum, 'readability/casting', 4, + 'Using deprecated casting style. ' + 'Use static_cast<%s>(...) instead' % + matched_type) + + CheckCStyleCast(filename, linenum, line, clean_lines.raw_lines[linenum], + 'static_cast', + r'\((int|float|double|bool|char|u?int(16|32|64))\)', error) + + # This doesn't catch all cases. Consider (const char * const)"hello". + # + # (char *) "foo" should always be a const_cast (reinterpret_cast won't + # compile). + if CheckCStyleCast(filename, linenum, line, clean_lines.raw_lines[linenum], + 'const_cast', r'\((char\s?\*+\s?)\)\s*"', error): + pass + else: + # Check pointer casts for other than string constants + CheckCStyleCast(filename, linenum, line, clean_lines.raw_lines[linenum], + 'reinterpret_cast', r'\((\w+\s?\*+\s?)\)', error) + + # In addition, we look for people taking the address of a cast. This + # is dangerous -- casts can assign to temporaries, so the pointer doesn't + # point where you think. + match = Search( + r'(?:&\(([^)]+)\)[\w(])|' + r'(?:&(static|dynamic|down|reinterpret)_cast\b)', line) + if match and match.group(1) != '*': + error(filename, linenum, 'runtime/casting', 4, + ('Are you taking an address of a cast? ' + 'This is dangerous: could be a temp var. ' + 'Take the address before doing the cast, rather than after')) + + # Create an extended_line, which is the concatenation of the current and + # next lines, for more effective checking of code that may span more than one + # line. + if linenum + 1 < clean_lines.NumLines(): + extended_line = line + clean_lines.elided[linenum + 1] + else: + extended_line = line + + # Check for people declaring static/global STL strings at the top level. + # This is dangerous because the C++ language does not guarantee that + # globals with constructors are initialized before the first access. + match = Match( + r'((?:|static +)(?:|const +))string +([a-zA-Z0-9_:]+)\b(.*)', + line) + # Make sure it's not a function. + # Function template specialization looks like: "string foo(...". + # Class template definitions look like: "string Foo::Method(...". + # + # Also ignore things that look like operators. These are matched separately + # because operator names cross non-word boundaries. If we change the pattern + # above, we would decrease the accuracy of matching identifiers. + if (match and + not Search(r'\boperator\W', line) and + not Match(r'\s*(<.*>)?(::[a-zA-Z0-9_]+)?\s*\(([^"]|$)', match.group(3))): + error(filename, linenum, 'runtime/string', 4, + 'For a static/global string constant, use a C style string instead: ' + '"%schar %s[]".' % + (match.group(1), match.group(2))) + + if Search(r'\b([A-Za-z0-9_]*_)\(\1\)', line): + error(filename, linenum, 'runtime/init', 4, + 'You seem to be initializing a member variable with itself.') + + if file_extension == 'h': + # TODO(unknown): check that 1-arg constructors are explicit. + # How to tell it's a constructor? + # (handled in CheckForNonStandardConstructs for now) + # TODO(unknown): check that classes have DISALLOW_EVIL_CONSTRUCTORS + # (level 1 error) + pass + + # Check if people are using the verboten C basic types. The only exception + # we regularly allow is "unsigned short port" for port. + if Search(r'\bshort port\b', line): + if not Search(r'\bunsigned short port\b', line): + error(filename, linenum, 'runtime/int', 4, + 'Use "unsigned short" for ports, not "short"') + else: + match = Search(r'\b(short|long(?! +double)|long long)\b', line) + if match: + error(filename, linenum, 'runtime/int', 4, + 'Use int16/int64/etc, rather than the C type %s' % match.group(1)) + + # When snprintf is used, the second argument shouldn't be a literal. + match = Search(r'snprintf\s*\(([^,]*),\s*([0-9]*)\s*,', line) + if match and match.group(2) != '0': + # If 2nd arg is zero, snprintf is used to calculate size. + error(filename, linenum, 'runtime/printf', 3, + 'If you can, use sizeof(%s) instead of %s as the 2nd arg ' + 'to snprintf.' % (match.group(1), match.group(2))) + + # Check if some verboten C functions are being used. + if Search(r'\bsprintf\b', line): + error(filename, linenum, 'runtime/printf', 5, + 'Never use sprintf. Use snprintf instead.') + match = Search(r'\b(strcpy|strcat)\b', line) + if match: + error(filename, linenum, 'runtime/printf', 4, + 'Almost always, snprintf is better than %s' % match.group(1)) + + # Check if some verboten operator overloading is going on + # TODO(unknown): catch out-of-line unary operator&: + # class X {}; + # int operator&(const X& x) { return 42; } // unary operator& + # The trick is it's hard to tell apart from binary operator&: + # class Y { int operator&(const Y& x) { return 23; } }; // binary operator& + if Search(r'\boperator\s*&\s*\(\s*\)', line): + error(filename, linenum, 'runtime/operator', 4, + 'Unary operator& is dangerous. Do not use it.') + + # Check for suspicious usage of "if" like + # } if (a == b) { + if Search(r'\}\s*if\s*\(', line): + error(filename, linenum, 'readability/braces', 4, + 'Did you mean "else if"? If not, start a new line for "if".') + + # Check for potential format string bugs like printf(foo). + # We constrain the pattern not to pick things like DocidForPrintf(foo). + # Not perfect but it can catch printf(foo.c_str()) and printf(foo->c_str()) + # TODO(sugawarayu): Catch the following case. Need to change the calling + # convention of the whole function to process multiple line to handle it. + # printf( + # boy_this_is_a_really_long_variable_that_cannot_fit_on_the_prev_line); + printf_args = _GetTextInside(line, r'(?i)\b(string)?printf\s*\(') + if printf_args: + match = Match(r'([\w.\->()]+)$', printf_args) + if match and match.group(1) != '__VA_ARGS__': + function_name = re.search(r'\b((?:string)?printf)\s*\(', + line, re.I).group(1) + error(filename, linenum, 'runtime/printf', 4, + 'Potential format string bug. Do %s("%%s", %s) instead.' + % (function_name, match.group(1))) + + # Check for potential memset bugs like memset(buf, sizeof(buf), 0). + match = Search(r'memset\s*\(([^,]*),\s*([^,]*),\s*0\s*\)', line) + if match and not Match(r"^''|-?[0-9]+|0x[0-9A-Fa-f]$", match.group(2)): + error(filename, linenum, 'runtime/memset', 4, + 'Did you mean "memset(%s, 0, %s)"?' + % (match.group(1), match.group(2))) + + if Search(r'\busing namespace\b', line): + error(filename, linenum, 'build/namespaces', 5, + 'Do not use namespace using-directives. ' + 'Use using-declarations instead.') + + # Detect variable-length arrays. + match = Match(r'\s*(.+::)?(\w+) [a-z]\w*\[(.+)];', line) + if (match and match.group(2) != 'return' and match.group(2) != 'delete' and + match.group(3).find(']') == -1): + # Split the size using space and arithmetic operators as delimiters. + # If any of the resulting tokens are not compile time constants then + # report the error. + tokens = re.split(r'\s|\+|\-|\*|\/|<<|>>]', match.group(3)) + is_const = True + skip_next = False + for tok in tokens: + if skip_next: + skip_next = False + continue + + if Search(r'sizeof\(.+\)', tok): continue + if Search(r'arraysize\(\w+\)', tok): continue + + tok = tok.lstrip('(') + tok = tok.rstrip(')') + if not tok: continue + if Match(r'\d+', tok): continue + if Match(r'0[xX][0-9a-fA-F]+', tok): continue + if Match(r'k[A-Z0-9]\w*', tok): continue + if Match(r'(.+::)?k[A-Z0-9]\w*', tok): continue + if Match(r'(.+::)?[A-Z][A-Z0-9_]*', tok): continue + # A catch all for tricky sizeof cases, including 'sizeof expression', + # 'sizeof(*type)', 'sizeof(const type)', 'sizeof(struct StructName)' + # requires skipping the next token because we split on ' ' and '*'. + if tok.startswith('sizeof'): + skip_next = True + continue + is_const = False + break + if not is_const: + error(filename, linenum, 'runtime/arrays', 1, + 'Do not use variable-length arrays. Use an appropriately named ' + "('k' followed by CamelCase) compile-time constant for the size.") + + # If DISALLOW_EVIL_CONSTRUCTORS, DISALLOW_COPY_AND_ASSIGN, or + # DISALLOW_IMPLICIT_CONSTRUCTORS is present, then it should be the last thing + # in the class declaration. + match = Match( + (r'\s*' + r'(DISALLOW_(EVIL_CONSTRUCTORS|COPY_AND_ASSIGN|IMPLICIT_CONSTRUCTORS))' + r'\(.*\);$'), + line) + if match and linenum + 1 < clean_lines.NumLines(): + next_line = clean_lines.elided[linenum + 1] + # We allow some, but not all, declarations of variables to be present + # in the statement that defines the class. The [\w\*,\s]* fragment of + # the regular expression below allows users to declare instances of + # the class or pointers to instances, but not less common types such + # as function pointers or arrays. It's a tradeoff between allowing + # reasonable code and avoiding trying to parse more C++ using regexps. + if not Search(r'^\s*}[\w\*,\s]*;', next_line): + error(filename, linenum, 'readability/constructors', 3, + match.group(1) + ' should be the last thing in the class') + + # Check for use of unnamed namespaces in header files. Registration + # macros are typically OK, so we allow use of "namespace {" on lines + # that end with backslashes. + if (file_extension == 'h' + and Search(r'\bnamespace\s*{', line) + and line[-1] != '\\'): + error(filename, linenum, 'build/namespaces', 4, + 'Do not use unnamed namespaces in header files. See ' + 'http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Namespaces' + ' for more information.') + +def CheckForNonConstReference(filename, clean_lines, linenum, + nesting_state, error): + """Check for non-const references. + + Separate from CheckLanguage since it scans backwards from current + line, instead of scanning forward. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + nesting_state: A _NestingState instance which maintains information about + the current stack of nested blocks being parsed. + error: The function to call with any errors found. + """ + # Do nothing if there is no '&' on current line. + line = clean_lines.elided[linenum] + if '&' not in line: + return + + # Long type names may be broken across multiple lines, usually in one + # of these forms: + # LongType + # ::LongTypeContinued &identifier + # LongType:: + # LongTypeContinued &identifier + # LongType< + # ...>::LongTypeContinued &identifier + # + # If we detected a type split across two lines, join the previous + # line to current line so that we can match const references + # accordingly. + # + # Note that this only scans back one line, since scanning back + # arbitrary number of lines would be expensive. If you have a type + # that spans more than 2 lines, please use a typedef. + if linenum > 1: + previous = None + if Match(r'\s*::(?:[\w<>]|::)+\s*&\s*\S', line): + # previous_line\n + ::current_line + previous = Search(r'\b((?:const\s*)?(?:[\w<>]|::)+[\w<>])\s*$', + clean_lines.elided[linenum - 1]) + elif Match(r'\s*[a-zA-Z_]([\w<>]|::)+\s*&\s*\S', line): + # previous_line::\n + current_line + previous = Search(r'\b((?:const\s*)?(?:[\w<>]|::)+::)\s*$', + clean_lines.elided[linenum - 1]) + if previous: + line = previous.group(1) + line.lstrip() + else: + # Check for templated parameter that is split across multiple lines + endpos = line.rfind('>') + if endpos > -1: + (_, startline, startpos) = ReverseCloseExpression( + clean_lines, linenum, endpos) + if startpos > -1 and startline < linenum: + # Found the matching < on an earlier line, collect all + # pieces up to current line. + line = '' + for i in xrange(startline, linenum + 1): + line += clean_lines.elided[i].strip() + + # Check for non-const references in function parameters. A single '&' may + # found in the following places: + # inside expression: binary & for bitwise AND + # inside expression: unary & for taking the address of something + # inside declarators: reference parameter + # We will exclude the first two cases by checking that we are not inside a + # function body, including one that was just introduced by a trailing '{'. + # TODO(unknwon): Doesn't account for preprocessor directives. + # TODO(unknown): Doesn't account for 'catch(Exception& e)' [rare]. + check_params = False + if not nesting_state.stack: + check_params = True # top level + elif (isinstance(nesting_state.stack[-1], _ClassInfo) or + isinstance(nesting_state.stack[-1], _NamespaceInfo)): + check_params = True # within class or namespace + elif Match(r'.*{\s*$', line): + if (len(nesting_state.stack) == 1 or + isinstance(nesting_state.stack[-2], _ClassInfo) or + isinstance(nesting_state.stack[-2], _NamespaceInfo)): + check_params = True # just opened global/class/namespace block + # We allow non-const references in a few standard places, like functions + # called "swap()" or iostream operators like "<<" or ">>". Do not check + # those function parameters. + # + # We also accept & in static_assert, which looks like a function but + # it's actually a declaration expression. + whitelisted_functions = (r'(?:[sS]wap(?:<\w:+>)?|' + r'operator\s*[<>][<>]|' + r'static_assert|COMPILE_ASSERT' + r')\s*\(') + if Search(whitelisted_functions, line): + check_params = False + elif not Search(r'\S+\([^)]*$', line): + # Don't see a whitelisted function on this line. Actually we + # didn't see any function name on this line, so this is likely a + # multi-line parameter list. Try a bit harder to catch this case. + for i in xrange(2): + if (linenum > i and + Search(whitelisted_functions, clean_lines.elided[linenum - i - 1])): + check_params = False + break + + if check_params: + decls = ReplaceAll(r'{[^}]*}', ' ', line) # exclude function body + for parameter in re.findall(_RE_PATTERN_REF_PARAM, decls): + if not Match(_RE_PATTERN_CONST_REF_PARAM, parameter): + error(filename, linenum, 'runtime/references', 2, + 'Is this a non-const reference? ' + 'If so, make const or use a pointer: ' + + ReplaceAll(' *<', '<', parameter)) + + +def CheckCStyleCast(filename, linenum, line, raw_line, cast_type, pattern, + error): + """Checks for a C-style cast by looking for the pattern. + + Args: + filename: The name of the current file. + linenum: The number of the line to check. + line: The line of code to check. + raw_line: The raw line of code to check, with comments. + cast_type: The string for the C++ cast to recommend. This is either + reinterpret_cast, static_cast, or const_cast, depending. + pattern: The regular expression used to find C-style casts. + error: The function to call with any errors found. + + Returns: + True if an error was emitted. + False otherwise. + """ + match = Search(pattern, line) + if not match: + return False + + # Exclude lines with sizeof, since sizeof looks like a cast. + sizeof_match = Match(r'.*sizeof\s*$', line[0:match.start(1) - 1]) + if sizeof_match: + return False + + # operator++(int) and operator--(int) + if (line[0:match.start(1) - 1].endswith(' operator++') or + line[0:match.start(1) - 1].endswith(' operator--')): + return False + + # A single unnamed argument for a function tends to look like old + # style cast. If we see those, don't issue warnings for deprecated + # casts, instead issue warnings for unnamed arguments where + # appropriate. + # + # These are things that we want warnings for, since the style guide + # explicitly require all parameters to be named: + # Function(int); + # Function(int) { + # ConstMember(int) const; + # ConstMember(int) const { + # ExceptionMember(int) throw (...); + # ExceptionMember(int) throw (...) { + # PureVirtual(int) = 0; + # + # These are functions of some sort, where the compiler would be fine + # if they had named parameters, but people often omit those + # identifiers to reduce clutter: + # (FunctionPointer)(int); + # (FunctionPointer)(int) = value; + # Function((function_pointer_arg)(int)) + # ; + # <(FunctionPointerTemplateArgument)(int)>; + remainder = line[match.end(0):] + if Match(r'^\s*(?:;|const\b|throw\b|=|>|\{|\))', remainder): + # Looks like an unnamed parameter. + + # Don't warn on any kind of template arguments. + if Match(r'^\s*>', remainder): + return False + + # Don't warn on assignments to function pointers, but keep warnings for + # unnamed parameters to pure virtual functions. Note that this pattern + # will also pass on assignments of "0" to function pointers, but the + # preferred values for those would be "nullptr" or "NULL". + matched_zero = Match(r'^\s=\s*(\S+)\s*;', remainder) + if matched_zero and matched_zero.group(1) != '0': + return False + + # Don't warn on function pointer declarations. For this we need + # to check what came before the "(type)" string. + if Match(r'.*\)\s*$', line[0:match.start(0)]): + return False + + # Don't warn if the parameter is named with block comments, e.g.: + # Function(int /*unused_param*/); + if '/*' in raw_line: + return False + + # Passed all filters, issue warning here. + error(filename, linenum, 'readability/function', 3, + 'All parameters should be named in a function') + return True + + # At this point, all that should be left is actual casts. + error(filename, linenum, 'readability/casting', 4, + 'Using C-style cast. Use %s<%s>(...) instead' % + (cast_type, match.group(1))) + + return True + + +_HEADERS_CONTAINING_TEMPLATES = ( + ('', ('deque',)), + ('', ('unary_function', 'binary_function', + 'plus', 'minus', 'multiplies', 'divides', 'modulus', + 'negate', + 'equal_to', 'not_equal_to', 'greater', 'less', + 'greater_equal', 'less_equal', + 'logical_and', 'logical_or', 'logical_not', + 'unary_negate', 'not1', 'binary_negate', 'not2', + 'bind1st', 'bind2nd', + 'pointer_to_unary_function', + 'pointer_to_binary_function', + 'ptr_fun', + 'mem_fun_t', 'mem_fun', 'mem_fun1_t', 'mem_fun1_ref_t', + 'mem_fun_ref_t', + 'const_mem_fun_t', 'const_mem_fun1_t', + 'const_mem_fun_ref_t', 'const_mem_fun1_ref_t', + 'mem_fun_ref', + )), + ('', ('numeric_limits',)), + ('', ('list',)), + ('', ('map', 'multimap',)), + ('', ('allocator',)), + ('', ('queue', 'priority_queue',)), + ('', ('set', 'multiset',)), + ('', ('stack',)), + ('', ('char_traits', 'basic_string',)), + ('', ('pair',)), + ('', ('vector',)), + + # gcc extensions. + # Note: std::hash is their hash, ::hash is our hash + ('', ('hash_map', 'hash_multimap',)), + ('', ('hash_set', 'hash_multiset',)), + ('', ('slist',)), + ) + +_RE_PATTERN_STRING = re.compile(r'\bstring\b') + +_re_pattern_algorithm_header = [] +for _template in ('copy', 'max', 'min', 'min_element', 'sort', 'swap', + 'transform'): + # Match max(..., ...), max(..., ...), but not foo->max, foo.max or + # type::max(). + _re_pattern_algorithm_header.append( + (re.compile(r'[^>.]\b' + _template + r'(<.*?>)?\([^\)]'), + _template, + '')) + +_re_pattern_templates = [] +for _header, _templates in _HEADERS_CONTAINING_TEMPLATES: + for _template in _templates: + _re_pattern_templates.append( + (re.compile(r'(\<|\b)' + _template + r'\s*\<'), + _template + '<>', + _header)) + + +def FilesBelongToSameModule(filename_cc, filename_h): + """Check if these two filenames belong to the same module. + + The concept of a 'module' here is a as follows: + foo.h, foo-inl.h, foo.cc, foo_test.cc and foo_unittest.cc belong to the + same 'module' if they are in the same directory. + some/path/public/xyzzy and some/path/internal/xyzzy are also considered + to belong to the same module here. + + If the filename_cc contains a longer path than the filename_h, for example, + '/absolute/path/to/base/sysinfo.cc', and this file would include + 'base/sysinfo.h', this function also produces the prefix needed to open the + header. This is used by the caller of this function to more robustly open the + header file. We don't have access to the real include paths in this context, + so we need this guesswork here. + + Known bugs: tools/base/bar.cc and base/bar.h belong to the same module + according to this implementation. Because of this, this function gives + some false positives. This should be sufficiently rare in practice. + + Args: + filename_cc: is the path for the .cc file + filename_h: is the path for the header path + + Returns: + Tuple with a bool and a string: + bool: True if filename_cc and filename_h belong to the same module. + string: the additional prefix needed to open the header file. + """ + + if not filename_cc.endswith('.cc'): + return (False, '') + filename_cc = filename_cc[:-len('.cc')] + if filename_cc.endswith('_unittest'): + filename_cc = filename_cc[:-len('_unittest')] + elif filename_cc.endswith('_test'): + filename_cc = filename_cc[:-len('_test')] + filename_cc = filename_cc.replace('/public/', '/') + filename_cc = filename_cc.replace('/internal/', '/') + + if not filename_h.endswith('.h'): + return (False, '') + filename_h = filename_h[:-len('.h')] + if filename_h.endswith('-inl'): + filename_h = filename_h[:-len('-inl')] + filename_h = filename_h.replace('/public/', '/') + filename_h = filename_h.replace('/internal/', '/') + + files_belong_to_same_module = filename_cc.endswith(filename_h) + common_path = '' + if files_belong_to_same_module: + common_path = filename_cc[:-len(filename_h)] + return files_belong_to_same_module, common_path + + +def UpdateIncludeState(filename, include_state, io=codecs): + """Fill up the include_state with new includes found from the file. + + Args: + filename: the name of the header to read. + include_state: an _IncludeState instance in which the headers are inserted. + io: The io factory to use to read the file. Provided for testability. + + Returns: + True if a header was succesfully added. False otherwise. + """ + headerfile = None + try: + headerfile = io.open(filename, 'r', 'utf8', 'replace') + except IOError: + return False + linenum = 0 + for line in headerfile: + linenum += 1 + clean_line = CleanseComments(line) + match = _RE_PATTERN_INCLUDE.search(clean_line) + if match: + include = match.group(2) + # The value formatting is cute, but not really used right now. + # What matters here is that the key is in include_state. + include_state.setdefault(include, '%s:%d' % (filename, linenum)) + return True + + +def CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error, + io=codecs): + """Reports for missing stl includes. + + This function will output warnings to make sure you are including the headers + necessary for the stl containers and functions that you use. We only give one + reason to include a header. For example, if you use both equal_to<> and + less<> in a .h file, only one (the latter in the file) of these will be + reported as a reason to include the . + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + include_state: An _IncludeState instance. + error: The function to call with any errors found. + io: The IO factory to use to read the header file. Provided for unittest + injection. + """ + required = {} # A map of header name to linenumber and the template entity. + # Example of required: { '': (1219, 'less<>') } + + for linenum in xrange(clean_lines.NumLines()): + line = clean_lines.elided[linenum] + if not line or line[0] == '#': + continue + + # String is special -- it is a non-templatized type in STL. + matched = _RE_PATTERN_STRING.search(line) + if matched: + # Don't warn about strings in non-STL namespaces: + # (We check only the first match per line; good enough.) + prefix = line[:matched.start()] + if prefix.endswith('std::') or not prefix.endswith('::'): + required[''] = (linenum, 'string') + + for pattern, template, header in _re_pattern_algorithm_header: + if pattern.search(line): + required[header] = (linenum, template) + + # The following function is just a speed up, no semantics are changed. + if not '<' in line: # Reduces the cpu time usage by skipping lines. + continue + + for pattern, template, header in _re_pattern_templates: + if pattern.search(line): + required[header] = (linenum, template) + + # The policy is that if you #include something in foo.h you don't need to + # include it again in foo.cc. Here, we will look at possible includes. + # Let's copy the include_state so it is only messed up within this function. + include_state = include_state.copy() + + # Did we find the header for this file (if any) and succesfully load it? + header_found = False + + # Use the absolute path so that matching works properly. + abs_filename = FileInfo(filename).FullName() + + # For Emacs's flymake. + # If cpplint is invoked from Emacs's flymake, a temporary file is generated + # by flymake and that file name might end with '_flymake.cc'. In that case, + # restore original file name here so that the corresponding header file can be + # found. + # e.g. If the file name is 'foo_flymake.cc', we should search for 'foo.h' + # instead of 'foo_flymake.h' + abs_filename = re.sub(r'_flymake\.cc$', '.cc', abs_filename) + + # include_state is modified during iteration, so we iterate over a copy of + # the keys. + header_keys = include_state.keys() + for header in header_keys: + (same_module, common_path) = FilesBelongToSameModule(abs_filename, header) + fullpath = common_path + header + if same_module and UpdateIncludeState(fullpath, include_state, io): + header_found = True + + # If we can't find the header file for a .cc, assume it's because we don't + # know where to look. In that case we'll give up as we're not sure they + # didn't include it in the .h file. + # TODO(unknown): Do a better job of finding .h files so we are confident that + # not having the .h file means there isn't one. + if filename.endswith('.cc') and not header_found: + return + + # All the lines have been processed, report the errors found. + for required_header_unstripped in required: + template = required[required_header_unstripped][1] + if required_header_unstripped.strip('<>"') not in include_state: + error(filename, required[required_header_unstripped][0], + 'build/include_what_you_use', 4, + 'Add #include ' + required_header_unstripped + ' for ' + template) + + +_RE_PATTERN_EXPLICIT_MAKEPAIR = re.compile(r'\bmake_pair\s*<') + + +def CheckMakePairUsesDeduction(filename, clean_lines, linenum, error): + """Check that make_pair's template arguments are deduced. + + G++ 4.6 in C++0x mode fails badly if make_pair's template arguments are + specified explicitly, and such use isn't intended in any case. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + match = _RE_PATTERN_EXPLICIT_MAKEPAIR.search(line) + if match: + error(filename, linenum, 'build/explicit_make_pair', + 4, # 4 = high confidence + 'For C++11-compatibility, omit template arguments from make_pair' + ' OR use pair directly OR if appropriate, construct a pair directly') + + +def ProcessLine(filename, file_extension, clean_lines, line, + include_state, function_state, nesting_state, error, + extra_check_functions=[]): + """Processes a single line in the file. + + Args: + filename: Filename of the file that is being processed. + file_extension: The extension (dot not included) of the file. + clean_lines: An array of strings, each representing a line of the file, + with comments stripped. + line: Number of line being processed. + include_state: An _IncludeState instance in which the headers are inserted. + function_state: A _FunctionState instance which counts function lines, etc. + nesting_state: A _NestingState instance which maintains information about + the current stack of nested blocks being parsed. + error: A callable to which errors are reported, which takes 4 arguments: + filename, line number, error level, and message + extra_check_functions: An array of additional check functions that will be + run on each source line. Each function takes 4 + arguments: filename, clean_lines, line, error + """ + raw_lines = clean_lines.raw_lines + ParseNolintSuppressions(filename, raw_lines[line], line, error) + nesting_state.Update(filename, clean_lines, line, error) + if nesting_state.stack and nesting_state.stack[-1].inline_asm != _NO_ASM: + return + CheckForFunctionLengths(filename, clean_lines, line, function_state, error) + CheckForMultilineCommentsAndStrings(filename, clean_lines, line, error) + CheckStyle(filename, clean_lines, line, file_extension, nesting_state, error) + CheckLanguage(filename, clean_lines, line, file_extension, include_state, + nesting_state, error) + CheckForNonConstReference(filename, clean_lines, line, nesting_state, error) + CheckForNonStandardConstructs(filename, clean_lines, line, + nesting_state, error) + CheckVlogArguments(filename, clean_lines, line, error) + CheckPosixThreading(filename, clean_lines, line, error) + CheckInvalidIncrement(filename, clean_lines, line, error) + CheckMakePairUsesDeduction(filename, clean_lines, line, error) + for check_fn in extra_check_functions: + check_fn(filename, clean_lines, line, error) + +def ProcessFileData(filename, file_extension, lines, error, + extra_check_functions=[]): + """Performs lint checks and reports any errors to the given error function. + + Args: + filename: Filename of the file that is being processed. + file_extension: The extension (dot not included) of the file. + lines: An array of strings, each representing a line of the file, with the + last element being empty if the file is terminated with a newline. + error: A callable to which errors are reported, which takes 4 arguments: + filename, line number, error level, and message + extra_check_functions: An array of additional check functions that will be + run on each source line. Each function takes 4 + arguments: filename, clean_lines, line, error + """ + lines = (['// marker so line numbers and indices both start at 1'] + lines + + ['// marker so line numbers end in a known way']) + + include_state = _IncludeState() + function_state = _FunctionState() + nesting_state = _NestingState() + + ResetNolintSuppressions() + + CheckForCopyright(filename, lines, error) + + if file_extension == 'h': + CheckForHeaderGuard(filename, lines, error) + + RemoveMultiLineComments(filename, lines, error) + clean_lines = CleansedLines(lines) + for line in xrange(clean_lines.NumLines()): + ProcessLine(filename, file_extension, clean_lines, line, + include_state, function_state, nesting_state, error, + extra_check_functions) + nesting_state.CheckCompletedBlocks(filename, error) + + CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error) + + # We check here rather than inside ProcessLine so that we see raw + # lines rather than "cleaned" lines. + CheckForBadCharacters(filename, lines, error) + + CheckForNewlineAtEOF(filename, lines, error) + +def ProcessFile(filename, vlevel, extra_check_functions=[]): + """Does google-lint on a single file. + + Args: + filename: The name of the file to parse. + + vlevel: The level of errors to report. Every error of confidence + >= verbose_level will be reported. 0 is a good default. + + extra_check_functions: An array of additional check functions that will be + run on each source line. Each function takes 4 + arguments: filename, clean_lines, line, error + """ + + _SetVerboseLevel(vlevel) + + try: + # Support the UNIX convention of using "-" for stdin. Note that + # we are not opening the file with universal newline support + # (which codecs doesn't support anyway), so the resulting lines do + # contain trailing '\r' characters if we are reading a file that + # has CRLF endings. + # If after the split a trailing '\r' is present, it is removed + # below. If it is not expected to be present (i.e. os.linesep != + # '\r\n' as in Windows), a warning is issued below if this file + # is processed. + + if filename == '-': + lines = codecs.StreamReaderWriter(sys.stdin, + codecs.getreader('utf8'), + codecs.getwriter('utf8'), + 'replace').read().split('\n') + else: + lines = codecs.open(filename, 'r', 'utf8', 'replace').read().split('\n') + + carriage_return_found = False + # Remove trailing '\r'. + for linenum in range(len(lines)): + if lines[linenum].endswith('\r'): + lines[linenum] = lines[linenum].rstrip('\r') + carriage_return_found = True + + except IOError: + sys.stderr.write( + "Skipping input '%s': Can't open for reading\n" % filename) + return + + # Note, if no dot is found, this will give the entire filename as the ext. + file_extension = filename[filename.rfind('.') + 1:] + + # When reading from stdin, the extension is unknown, so no cpplint tests + # should rely on the extension. + if filename != '-' and file_extension not in _valid_extensions: + sys.stderr.write('Ignoring %s; not a valid file name ' + '(%s)\n' % (filename, ', '.join(_valid_extensions))) + else: + ProcessFileData(filename, file_extension, lines, Error, + extra_check_functions) + if carriage_return_found and os.linesep != '\r\n': + # Use 0 for linenum since outputting only one error for potentially + # several lines. + Error(filename, 0, 'whitespace/newline', 1, + 'One or more unexpected \\r (^M) found;' + 'better to use only a \\n') + + sys.stderr.write('Done processing %s\n' % filename) + + +def PrintUsage(message): + """Prints a brief usage string and exits, optionally with an error message. + + Args: + message: The optional error message. + """ + sys.stderr.write(_USAGE) + if message: + sys.exit('\nFATAL ERROR: ' + message) + else: + sys.exit(1) + + +def PrintCategories(): + """Prints a list of all the error-categories used by error messages. + + These are the categories used to filter messages via --filter. + """ + sys.stderr.write(''.join(' %s\n' % cat for cat in _ERROR_CATEGORIES)) + sys.exit(0) + + +def ParseArguments(args): + """Parses the command line arguments. + + This may set the output format and verbosity level as side-effects. + + Args: + args: The command line arguments: + + Returns: + The list of filenames to lint. + """ + try: + (opts, filenames) = getopt.getopt(args, '', ['help', 'output=', 'verbose=', + 'counting=', + 'filter=', + 'root=', + 'linelength=', + 'extensions=']) + except getopt.GetoptError: + PrintUsage('Invalid arguments.') + + verbosity = _VerboseLevel() + output_format = _OutputFormat() + filters = '' + counting_style = '' + + for (opt, val) in opts: + if opt == '--help': + PrintUsage(None) + elif opt == '--output': + if val not in ('emacs', 'vs7', 'eclipse'): + PrintUsage('The only allowed output formats are emacs, vs7 and eclipse.') + output_format = val + elif opt == '--verbose': + verbosity = int(val) + elif opt == '--filter': + filters = val + if not filters: + PrintCategories() + elif opt == '--counting': + if val not in ('total', 'toplevel', 'detailed'): + PrintUsage('Valid counting options are total, toplevel, and detailed') + counting_style = val + elif opt == '--root': + global _root + _root = val + elif opt == '--linelength': + global _line_length + try: + _line_length = int(val) + except ValueError: + PrintUsage('Line length must be digits.') + elif opt == '--extensions': + global _valid_extensions + try: + _valid_extensions = set(val.split(',')) + except ValueError: + PrintUsage('Extensions must be comma seperated list.') + + if not filenames: + PrintUsage('No files were specified.') + + _SetOutputFormat(output_format) + _SetVerboseLevel(verbosity) + _SetFilters(filters) + _SetCountingStyle(counting_style) + + return filenames + + +def main(): + filenames = ParseArguments(sys.argv[1:]) + + # Change stderr to write with replacement characters so we don't die + # if we try to print something containing non-ASCII characters. + sys.stderr = codecs.StreamReaderWriter(sys.stderr, + codecs.getreader('utf8'), + codecs.getwriter('utf8'), + 'replace') + + _cpplint_state.ResetErrorCounts() + for filename in filenames: + ProcessFile(filename, _cpplint_state.verbose_level) + _cpplint_state.PrintErrorCounts() + + sys.exit(_cpplint_state.error_count > 0) + + +if __name__ == '__main__': + main() From 58cfe32f17e784665699c8d60c2849031c3ec22d Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Sat, 1 Mar 2014 12:27:42 -0800 Subject: [PATCH 1682/3753] Fix up some of cpplint's whitespace whines --- cfacter.cc | 4 ++-- cfacterimpl.cc | 41 +++++++++++++++++++++-------------------- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/cfacter.cc b/cfacter.cc index 42adc605d1..c9547d0ad1 100644 --- a/cfacter.cc +++ b/cfacter.cc @@ -35,7 +35,7 @@ void help() " -j, --json Emit facts in JSON format.\n" " -v, --version Print the version and exit.\n" " -h, --help Print this help message and exit.\n" - ; + ""; } void version() @@ -79,7 +79,7 @@ int main(int argc, char **argv) #define MAX_LEN_FACTS_JSON_STRING (1024 * 1024) // go crazy here char facts_json[MAX_LEN_FACTS_JSON_STRING]; - if (optind == argc) { // display all facts + if (optind == argc) { // display all facts if (to_json(facts_json, MAX_LEN_FACTS_JSON_STRING) < 0) { cout << "Wow, that's a lot of facts" << endl; exit(1); diff --git a/cfacterimpl.cc b/cfacterimpl.cc index eaa62aa698..cef672ce4f 100644 --- a/cfacterimpl.cc +++ b/cfacterimpl.cc @@ -42,34 +42,34 @@ struct ci_char_traits : public char_traits // just inherit all the other functions // that we don't need to override { - static bool eq( char c1, char c2 ) + static bool eq(char c1, char c2) { return toupper(c1) == toupper(c2); } - static bool ne( char c1, char c2 ) + static bool ne(char c1, char c2) { return toupper(c1) != toupper(c2); } - static bool lt( char c1, char c2 ) + static bool lt(char c1, char c2) { return toupper(c1) < toupper(c2); } - static int compare( const char* s1, - const char* s2, - size_t n ) + static int compare(const char* s1, + const char* s2, + size_t n) { - return strncasecmp( s1, s2, n ); + return strncasecmp(s1, s2, n); // if available on your compiler, // otherwise you can roll your own } static const char* - find( const char* s, int n, char a ) + find(const char* s, int n, char a) { - while( n-- > 0 && toupper(*s) != toupper(a) ) { + while (n-- > 0 && toupper(*s) != toupper(a)) { ++s; } return s; @@ -115,7 +115,7 @@ static inline void split(const string &s, char delim, vector &elems) } } -static bool file_exist (string filename) +static bool file_exist(string filename) { struct stat buffer; return stat (filename.c_str(), &buffer) == 0; @@ -198,8 +198,7 @@ void get_network_facts(fact_map& facts) } facts[string("mtu_") + r->ifr_name] = to_string(r->ifr_mtu); - if (primaryInterface) - ; // no unmarked version of this network fact + if (primaryInterface) {} // no unmarked version of this network fact // netmask and network are both derived from the same ioctl if (ioctl(s, SIOCGIFNETMASK, r) < 0) { @@ -289,10 +288,11 @@ static void get_lsb_facts(fact_map& facts) facts["lsbdistrelease"] = value; facts["operatingsystemrelease"] = value; facts["lsbmajdistrelease"] = value.substr(0, value.find(".")); - } else if (key == "DISTRIB_CODENAME") + } else if (key == "DISTRIB_CODENAME") { facts["lsbdistcodename"] = value; - else if (key == "DISTRIB_DESCRIPTION") + } else if (key == "DISTRIB_DESCRIPTION") { facts["lsbdistdescription"] = value; + } } } @@ -312,8 +312,9 @@ static void get_redhat_facts(fact_map& facts) facts["operatingsystemrelease"] = tokens[2]; facts["operatingsystemmajrelease"] = tokens[2]; } - } else + } else { facts["operatingsystem"] = "RedHat"; + } } } @@ -397,7 +398,6 @@ void get_blockdevice_facts(fact_map& facts) struct dirent *bd; while ((bd = readdir(sys_block_dir))) { - bool real_block_device = false; string device_dir_path = "/sys/block/"; device_dir_path += bd->d_name; @@ -444,7 +444,7 @@ void get_misc_facts(fact_map& facts) string whoami = popen_stdout("whoami"); facts["id"] = trim(whoami); - //timezone + // timezone char tzstring[16]; time_t t = time(NULL); struct tm *loc = localtime(&t); @@ -467,7 +467,7 @@ static void get_mem_fact(std::string fact_name, int fact_value, fact_map& facts, int scale_index; for (scale_index = 0; fact_value_scaled > 1024.0; - fact_value_scaled /= 1024.0, ++scale_index) ; + fact_value_scaled /= 1024.0, ++scale_index) {} std::string scale[4] = {"MB", "GB", "TB", "PB"}; // oh yeah, petabytes ... @@ -505,10 +505,11 @@ void get_mem_facts(fact_map& facts) get_mem_fact("memorytotal", mem_total, facts, false); } else if (tokens[0] == "MemFree:" || tokens[0] == "Cached:" || tokens[0] == "Buffers:") { memoryfree += atoi(tokens[1].c_str()); - } else if (tokens[0] == "SwapTotal:") + } else if (tokens[0] == "SwapTotal:") { get_mem_fact("swapsize", atoi(tokens[1].c_str()), facts); - else if (tokens[0] == "SwapFree:") + } else if (tokens[0] == "SwapFree:") { get_mem_fact("swapfree", atoi(tokens[1].c_str()), facts); + } } get_mem_fact("memoryfree", memoryfree, facts); From 2b63e9cef0f606ae3c9381c2a779f033186873c4 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Sat, 1 Mar 2014 15:51:16 -0800 Subject: [PATCH 1683/3753] Add check target (using cppcheck) and fix the leak it found; woot --- Makefile | 4 ++++ cfacterimpl.cc | 1 + 2 files changed, 5 insertions(+) diff --git a/Makefile b/Makefile index 7996709cd8..829bdc2ec6 100644 --- a/Makefile +++ b/Makefile @@ -40,3 +40,7 @@ missing: .PHONY: lint lint: -@ext/cpplint.py --filter=-build/include,-legal/copyright,-readability/streams,-whitespace/braces,-whitespace/line_length,-runtime/arrays,-readability/todo *cc 2>&1 | less + +.PHONY: check +check: + -@cppcheck . diff --git a/cfacterimpl.cc b/cfacterimpl.cc index cef672ce4f..d172c4be65 100644 --- a/cfacterimpl.cc +++ b/cfacterimpl.cc @@ -903,6 +903,7 @@ static void get_external_facts_from_executable(fact_map& facts, string executabl facts[key] = val; } } + pclose(stdout); } } From 640c93ea9623b0ed1242cb089ed07ed33f34fd1d Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Sat, 1 Mar 2014 16:23:00 -0800 Subject: [PATCH 1684/3753] Make it (almost) clean by cpplint.py standards (in part by filtering) --- Makefile | 2 +- cfacter.cc | 11 +++++------ cfacterimpl.cc | 17 +++++++++-------- cfacterlib.cc | 47 +++++++++++++++++++---------------------------- 4 files changed, 34 insertions(+), 43 deletions(-) diff --git a/Makefile b/Makefile index 829bdc2ec6..5469779b98 100644 --- a/Makefile +++ b/Makefile @@ -39,7 +39,7 @@ missing: # Just experimenting with cpplint at this point; not sure what I like and dislike. .PHONY: lint lint: - -@ext/cpplint.py --filter=-build/include,-legal/copyright,-readability/streams,-whitespace/braces,-whitespace/line_length,-runtime/arrays,-readability/todo *cc 2>&1 | less + -@ext/cpplint.py --filter=-runtime/references,-build/include,-legal/copyright,-readability/streams,-whitespace/braces,-whitespace/line_length,-runtime/arrays,-readability/todo *cc 2>&1 | less .PHONY: check check: diff --git a/cfacter.cc b/cfacter.cc index c9547d0ad1..06d30a450c 100644 --- a/cfacter.cc +++ b/cfacter.cc @@ -3,10 +3,9 @@ #include #include -using namespace std; void help() { - cout << + std::cout << "Synopsis\n" "========\n" "\n" @@ -40,7 +39,7 @@ void help() void version() { - cout << "0.0.1" << endl; + std::cout << "0.0.1" << std::endl; } int main(int argc, char **argv) @@ -81,16 +80,16 @@ int main(int argc, char **argv) if (optind == argc) { // display all facts if (to_json(facts_json, MAX_LEN_FACTS_JSON_STRING) < 0) { - cout << "Wow, that's a lot of facts" << endl; + std::cout << "Wow, that's a lot of facts" << std::endl; exit(1); } - cout << facts_json << endl; + std::cout << facts_json << std::endl; } else { // display requested fact(s) while (optind < argc) { // fix me if (value(argv[optind], facts_json, MAX_LEN_FACTS_JSON_STRING) == 0) - cout << facts_json << endl; + std::cout << facts_json << std::endl; ++optind; } } diff --git a/cfacterimpl.cc b/cfacterimpl.cc index d172c4be65..3b194c38cf 100644 --- a/cfacterimpl.cc +++ b/cfacterimpl.cc @@ -233,11 +233,12 @@ void get_network_facts(fact_map& facts) } // extract mac into a string, okay a char array - uint8_t *mac_bytes = (uint8_t *)((sockaddr *)&r->ifr_hwaddr)->sa_data; + uint8_t *mac_bytes = reinterpret_cast((reinterpret_cast(&r->ifr_hwaddr))->sa_data); char mac_address[18]; - sprintf(mac_address, "%02x:%02x:%02x:%02x:%02x:%02x", - mac_bytes[0], mac_bytes[1], mac_bytes[2], - mac_bytes[3], mac_bytes[4], mac_bytes[5]); + snprintf(mac_address, sizeof(mac_address), + "%02x:%02x:%02x:%02x:%02x:%02x", + mac_bytes[0], mac_bytes[1], mac_bytes[2], + mac_bytes[3], mac_bytes[4], mac_bytes[5]); // and get it out facts[string("macaddress_") + r->ifr_name] = mac_address; @@ -430,8 +431,7 @@ void get_blockdevice_facts(fact_map& facts) string size_file = "/sys/block/" + string(bd->d_name) + "/size"; string size_line = read_oneline_file(size_file); int64_t size; - // SCNd64 didn't work here?? - sscanf(size_line.c_str(), "%lld", (long long int *)&size); + sscanf(size_line.c_str(), "%" SCNd64, &size); facts[string("blockdevice_") + bd->d_name + "_size"] = to_string(size * 512); } @@ -447,8 +447,9 @@ void get_misc_facts(fact_map& facts) // timezone char tzstring[16]; time_t t = time(NULL); - struct tm *loc = localtime(&t); - strftime(tzstring, sizeof(tzstring - 1), "%Z", loc); + struct tm loc; + localtime_r(&t, &loc); + strftime(tzstring, sizeof(tzstring - 1), "%Z", &loc); facts["timezone"] = tzstring; } diff --git a/cfacterlib.cc b/cfacterlib.cc index 98d28652fd..87a317fbe6 100644 --- a/cfacterlib.cc +++ b/cfacterlib.cc @@ -10,8 +10,6 @@ #include "rapidjson/prettywriter.h" #include "rapidjson/stringbuffer.h" -using namespace std; - std::map facts; void clear() @@ -32,7 +30,7 @@ void loadfacts() facts["cfacterversion"] = "0.0.1"; - list external_directories; + std::list external_directories; external_directories.push_back("/etc/facter/facts.d"); get_network_facts(facts); @@ -59,38 +57,31 @@ int to_json(char *facts_json, size_t facts_len) { loadfacts(); - if (0) { - typedef map::iterator iter; - for (iter i = facts.begin(); i != facts.end(); ++i) { - cout << i->first << " => " << i->second << endl; - } - } else { - rapidjson::Document json; - json.SetObject(); - - rapidjson::Document::AllocatorType& allocator = json.GetAllocator(); - - typedef map::iterator iter; - for (iter i = facts.begin(); i != facts.end(); ++i) { - json.AddMember(i->first.c_str(), i->second.c_str(), allocator); - } - - rapidjson::StringBuffer buf; - rapidjson::Writer writer(buf); - json.Accept(writer); - - // FIXME can rapidjson write straight into the provided char array in a safe manner? - // if not roll my own strncpy which doesn't zero-pad and returns success rather than the ptr - strncpy(facts_json, buf.GetString(), facts_len); - return 0; + rapidjson::Document json; + json.SetObject(); + + rapidjson::Document::AllocatorType& allocator = json.GetAllocator(); + + typedef std::map::iterator iter; + for (iter i = facts.begin(); i != facts.end(); ++i) { + json.AddMember(i->first.c_str(), i->second.c_str(), allocator); } + + rapidjson::StringBuffer buf; + rapidjson::Writer writer(buf); + json.Accept(writer); + + // FIXME can rapidjson write straight into the provided char array in a safe manner? + // if not roll my own strncpy which doesn't zero-pad and returns success rather than the ptr + strncpy(facts_json, buf.GetString(), facts_len); + return 0; } int value(const char *fact, char *value, size_t value_len) { loadfacts(); - typedef map::iterator iter; + typedef std::map::iterator iter; iter i = facts.find(fact); if (i == facts.end()) return -1; From ccef484046c3f362d70d09ffd4c7359271a5244c Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Sat, 1 Mar 2014 16:44:42 -0800 Subject: [PATCH 1685/3753] Remove 'using namespace std' from cfacterlib.cc This removes the last cpplint nag (besides those currently filtered). I'm not sold on the inherent evil of such using-directives, but since I was not being consistent about specifying namespaces, this was a good scrub exercise, at least for the time being. --- cfacterimpl.cc | 246 ++++++++++++++++++++++++------------------------- 1 file changed, 122 insertions(+), 124 deletions(-) diff --git a/cfacterimpl.cc b/cfacterimpl.cc index 3b194c38cf..5a3922675c 100644 --- a/cfacterimpl.cc +++ b/cfacterimpl.cc @@ -33,12 +33,10 @@ #include "cfacterlib.h" #include "cfacterimpl.h" -using namespace std; - // For case-insensitive strings, define ci_string // Thank you, Herb Sutter: http://www.gotw.ca/gotw/029.htm // -struct ci_char_traits : public char_traits +struct ci_char_traits : public std::char_traits // just inherit all the other functions // that we don't need to override { @@ -76,7 +74,7 @@ struct ci_char_traits : public char_traits } }; -typedef basic_string ci_string; +typedef std::basic_string ci_string; // trim from start static inline std::string <rim(std::string &s) @@ -98,31 +96,31 @@ static inline std::string &trim(std::string &s) return ltrim(rtrim(s)); } -static inline void tokenize(std::string &s, vector &tokens) +static inline void tokenize(std::string &s, std::vector &tokens) { - istringstream iss(s); - copy(istream_iterator(iss), - istream_iterator(), - back_inserter >(tokens)); + std::istringstream iss(s); + copy(std::istream_iterator(iss), + std::istream_iterator(), + std::back_inserter >(tokens)); } -static inline void split(const string &s, char delim, vector &elems) +static inline void split(const std::string &s, char delim, std::vector &elems) { - stringstream ss(s); - string item; + std::stringstream ss(s); + std::string item; while (getline(ss, item, delim)) { elems.push_back(item); } } -static bool file_exist(string filename) +static bool file_exist(std::string filename) { struct stat buffer; return stat (filename.c_str(), &buffer) == 0; } // handy for some /proc and /sys files -string read_oneline_file(const string file_path) +std::string read_oneline_file(const std::string file_path) { std::ifstream oneline_file(file_path.c_str(), std::ifstream::in); std::string line; @@ -163,7 +161,7 @@ void get_network_facts(fact_map& facts) exit(1); } - string interfaces = ""; + std::string interfaces = ""; bool primaryInterfacePrinted = false; numif = ifc.ifc_len / sizeof(struct ifreq); @@ -187,7 +185,7 @@ void get_network_facts(fact_map& facts) interfaces += ","; const char *ipaddress = inet_ntoa(ip_addr); - facts[string("ipaddress_") + r->ifr_name] = ipaddress; + facts[std::string("ipaddress_") + r->ifr_name] = ipaddress; if (primaryInterface) facts["ipaddress"] = ipaddress; @@ -197,7 +195,7 @@ void get_network_facts(fact_map& facts) exit(1); } - facts[string("mtu_") + r->ifr_name] = to_string(r->ifr_mtu); + facts[std::string("mtu_") + r->ifr_name] = std::to_string(r->ifr_mtu); if (primaryInterface) {} // no unmarked version of this network fact // netmask and network are both derived from the same ioctl @@ -210,7 +208,7 @@ void get_network_facts(fact_map& facts) // netmask struct in_addr netmask_addr = ((struct sockaddr_in *)&r->ifr_netmask)->sin_addr; const char *netmask = inet_ntoa(netmask_addr); - facts[string("netmask_") + r->ifr_name] = netmask; + facts[std::string("netmask_") + r->ifr_name] = netmask; if (primaryInterface) facts["netmask"] = netmask; @@ -218,8 +216,8 @@ void get_network_facts(fact_map& facts) struct in_addr network_addr; network_addr.s_addr = (in_addr_t) uint32_t(netmask_addr.s_addr) & uint32_t(ip_addr.s_addr); - string network = inet_ntoa(network_addr); - facts[string("network_") + r->ifr_name] = network; + std::string network = inet_ntoa(network_addr); + facts[std::string("network_") + r->ifr_name] = network; if (primaryInterface) facts["network"] = network; #endif @@ -241,7 +239,7 @@ void get_network_facts(fact_map& facts) mac_bytes[3], mac_bytes[4], mac_bytes[5]); // and get it out - facts[string("macaddress_") + r->ifr_name] = mac_address; + facts[std::string("macaddress_") + r->ifr_name] = mac_address; if (primaryInterface) facts["macaddress"] = mac_address; } @@ -259,11 +257,11 @@ void get_kernel_facts(fact_map& facts) #ifdef __linux__ // this is linux-only, so there you have it facts["kernel"] = "Linux"; - string kernelrelease = read_oneline_file("/proc/sys/kernel/osrelease"); + std::string kernelrelease = read_oneline_file("/proc/sys/kernel/osrelease"); facts["kernelrelease"] = kernelrelease; - string kernelversion = kernelrelease.substr(0, kernelrelease.find("-")); + std::string kernelversion = kernelrelease.substr(0, kernelrelease.find("-")); facts["kernelversion"] = kernelversion; - string kernelmajversion = kernelversion.substr(0, kernelversion.rfind(".")); + std::string kernelmajversion = kernelversion.substr(0, kernelversion.rfind(".")); facts["kernelmajversion"] = kernelmajversion; #else #ifdef __APPLE__ @@ -278,8 +276,8 @@ static void get_lsb_facts(fact_map& facts) std::string line; while (std::getline(lsb_release_file, line)) { unsigned sep = line.find("="); - string key = line.substr(0, sep); - string value = line.substr(sep + 1, string::npos); + std::string key = line.substr(0, sep); + std::string value = line.substr(sep + 1, std::string::npos); if (key == "DISTRIB_ID") { facts["lsbdistid"] = value; @@ -304,8 +302,8 @@ static void get_redhat_facts(fact_map& facts) { if (file_exist("/etc/redhat-release")) { facts["osfamily"] = "RedHat"; - string redhat_release = read_oneline_file("/etc/redhat-release"); - vector tokens; + std::string redhat_release = read_oneline_file("/etc/redhat-release"); + std::vector tokens; tokenize(redhat_release, tokens); if (tokens.size() >= 2 && tokens[0] == "Fedora" && tokens[1] == "release") { facts["operatingsystem"] = "Fedora"; @@ -327,21 +325,21 @@ void get_operatingsystem_facts(fact_map& facts) void get_uptime_facts(fact_map& facts) { - string uptime = read_oneline_file("/proc/uptime"); + std::string uptime = read_oneline_file("/proc/uptime"); unsigned int uptime_seconds; sscanf(uptime.c_str(), "%ud", &uptime_seconds); unsigned int uptime_hours = uptime_seconds / 3600; unsigned int uptime_days = uptime_hours / 24; - facts["uptime_seconds"] = to_string(uptime_seconds); - facts["uptime_hours"] = to_string(uptime_hours); - facts["uptime_days"] = to_string(uptime_days); - facts["uptime"] = to_string(uptime_days) + " days"; + facts["uptime_seconds"] = std::to_string(uptime_seconds); + facts["uptime_hours"] = std::to_string(uptime_hours); + facts["uptime_days"] = std::to_string(uptime_days); + facts["uptime"] = std::to_string(uptime_days) + " days"; } -string popen_stdout(string cmd) +std::string popen_stdout(std::string cmd) { FILE *cmd_fd = popen(cmd.c_str(), "r"); - string cmd_output = ""; + std::string cmd_output = ""; char buf[1024]; size_t bytesRead; while ((bytesRead = fread(buf, 1, sizeof(buf) - 1, cmd_fd))) { @@ -390,7 +388,7 @@ void get_ruby_lib_versions(fact_map& facts) // block devices void get_blockdevice_facts(fact_map& facts) { - string blockdevices = ""; + std::string blockdevices = ""; DIR *sys_block_dir = opendir("/sys/block"); if (sys_block_dir == NULL) { @@ -400,7 +398,7 @@ void get_blockdevice_facts(fact_map& facts) struct dirent *bd; while ((bd = readdir(sys_block_dir))) { bool real_block_device = false; - string device_dir_path = "/sys/block/"; + std::string device_dir_path = "/sys/block/"; device_dir_path += bd->d_name; DIR *device_dir = opendir(device_dir_path.c_str()); @@ -420,19 +418,19 @@ void get_blockdevice_facts(fact_map& facts) blockdevices += ","; blockdevices += bd->d_name; - string model_file = "/sys/block/" + string(bd->d_name) + "/device/model"; - facts[string("blockdevice_") + bd->d_name + "_model"] = + std::string model_file = "/sys/block/" + std::string(bd->d_name) + "/device/model"; + facts[std::string("blockdevice_") + bd->d_name + "_model"] = read_oneline_file(model_file); - string vendor_file = "/sys/block/" + string(bd->d_name) + "/device/vendor"; - facts[string("blockdevice_") + bd->d_name + "_vendor"] = + std::string vendor_file = "/sys/block/" + std::string(bd->d_name) + "/device/vendor"; + facts[std::string("blockdevice_") + bd->d_name + "_vendor"] = read_oneline_file(vendor_file); - string size_file = "/sys/block/" + string(bd->d_name) + "/size"; - string size_line = read_oneline_file(size_file); + std::string size_file = "/sys/block/" + std::string(bd->d_name) + "/size"; + std::string size_line = read_oneline_file(size_file); int64_t size; sscanf(size_line.c_str(), "%" SCNd64, &size); - facts[string("blockdevice_") + bd->d_name + "_size"] = to_string(size * 512); + facts[std::string("blockdevice_") + bd->d_name + "_size"] = std::to_string(size * 512); } facts["blockdevices"] = blockdevices; @@ -441,7 +439,7 @@ void get_blockdevice_facts(fact_map& facts) void get_misc_facts(fact_map& facts) { facts["path"] = getenv("PATH"); - string whoami = popen_stdout("whoami"); + std::string whoami = popen_stdout("whoami"); facts["id"] = trim(whoami); // timezone @@ -462,7 +460,7 @@ static void get_mem_fact(std::string fact_name, int fact_value, fact_map& facts, if (get_mb_variant) { snprintf(float_buf, sizeof(float_buf) - 1, "%.2f", fact_value_scaled); - facts[string(fact_name) + "_mb"] = float_buf; + facts[std::string(fact_name) + "_mb"] = float_buf; } int scale_index; @@ -473,7 +471,7 @@ static void get_mem_fact(std::string fact_name, int fact_value, fact_map& facts, std::string scale[4] = {"MB", "GB", "TB", "PB"}; // oh yeah, petabytes ... snprintf(float_buf, sizeof(float_buf) - 1, "%.2f", fact_value_scaled); - facts[fact_name] = string(float_buf) + scale[scale_index]; + facts[fact_name] = std::string(float_buf) + scale[scale_index]; } void get_mem_facts(fact_map& facts) @@ -496,7 +494,7 @@ void get_mem_facts(fact_map& facts) unsigned int memoryfree = 0; while (std::getline(oneline_file, line)) { - vector tokens; + std::vector tokens; tokenize(line, tokens); if (tokens.size() < 3) continue; // should never happen @@ -516,10 +514,10 @@ void get_mem_facts(fact_map& facts) get_mem_fact("memoryfree", memoryfree, facts); } -static string get_selinux_path() +static std::string get_selinux_path() { - static string selinux_path = ""; - static bool inited = false; + static std::string selinux_path = ""; + static bool inited = false; if (inited) return selinux_path; @@ -528,7 +526,7 @@ static string get_selinux_path() std::string line; while (std::getline(mounts, line)) { - vector tokens; + std::vector tokens; tokenize(line, tokens); if (tokens.size() < 2) continue; if (tokens[0] != "selinuxfs") continue; @@ -544,12 +542,12 @@ static string get_selinux_path() static bool selinux() { - string selinux_path = get_selinux_path(); + std::string selinux_path = get_selinux_path(); if (selinux_path.empty()) return false; - string selinux_enforce_path = selinux_path + "/enforce"; - string security_attr_path = "/proc/self/attr/current"; + std::string selinux_enforce_path = selinux_path + "/enforce"; + std::string security_attr_path = "/proc/self/attr/current"; if (file_exist(selinux_enforce_path) && file_exist(security_attr_path) && read_oneline_file(security_attr_path) != "kernel") return true; @@ -574,24 +572,24 @@ void get_selinux_facts(fact_map& facts) facts["selinux_config_policy"] = "unknown"; facts["selinux_mode"] = "unknown"; - string selinux_path = get_selinux_path(); + std::string selinux_path = get_selinux_path(); - string selinux_enforce_path = selinux_path + "/enforce"; + std::string selinux_enforce_path = selinux_path + "/enforce"; if (file_exist(selinux_enforce_path)) facts["selinux_enforced"] = ((read_oneline_file(selinux_enforce_path) == "1") ? "true" : "false"); - string selinux_policyvers_path = selinux_path + "/policyvers"; + std::string selinux_policyvers_path = selinux_path + "/policyvers"; if (file_exist(selinux_policyvers_path)) facts["selinux_policyversion"] = read_oneline_file(selinux_policyvers_path); - string selinux_cmd = "/usr/sbin/sestatus"; + std::string selinux_cmd = "/usr/sbin/sestatus"; FILE* pipe = popen(selinux_cmd.c_str(), "r"); if (!pipe) return; char buffer[512]; // seems like a lot, but there's no constant available while (!feof(pipe)) { if (fgets(buffer, 128, pipe) != NULL) { - vector elems; + std::vector elems; split(buffer, ':', elems); if (elems.size() < 2) continue; // shouldn't happen if (elems[0] == "Current mode") { @@ -608,9 +606,9 @@ void get_selinux_facts(fact_map& facts) pclose(pipe); } -static void get_ssh_fact(string fact_name, string path_name, fact_map& facts) +static void get_ssh_fact(std::string fact_name, std::string path_name, fact_map& facts) { - string ssh_directories[] = { + std::string ssh_directories[] = { "/etc/ssh", "/usr/local/etc/ssh", "/etc", @@ -618,11 +616,11 @@ static void get_ssh_fact(string fact_name, string path_name, fact_map& facts) "/etc/opt/ssh", }; - for (int i = 0; i < sizeof(ssh_directories) / sizeof(string); ++i) { - string full_path = ssh_directories[i] + "/" + path_name; + for (int i = 0; i < sizeof(ssh_directories) / sizeof(std::string); ++i) { + std::string full_path = ssh_directories[i] + "/" + path_name; if (file_exist(full_path)) { - string key = read_oneline_file(full_path); - vector tokens; + std::string key = read_oneline_file(full_path); + std::vector tokens; tokenize(trim(key), tokens); if (tokens.size() < 2) continue; // should never happen facts[fact_name] = tokens[1]; @@ -641,12 +639,12 @@ static void get_ssh_fact(string fact_name, string path_name, fact_map& facts) void get_ssh_facts(fact_map& facts) { // not til C++11 do we have static initialization of stl maps - map ssh_facts; + std::map ssh_facts; ssh_facts["sshdsakey"] = "ssh_host_dsa_key.pub"; ssh_facts["sshrsakey"] = "ssh_host_rsa_key.pub"; ssh_facts["sshecdsakey"] = "ssh_host_ecdsa_key.pub"; - typedef map::iterator iter; + typedef std::map::iterator iter; for (iter i = ssh_facts.begin(); i != ssh_facts.end(); ++i) { get_ssh_fact(i->first, i->second, facts); } @@ -658,13 +656,13 @@ static void get_physicalprocessorcount_fact(fact_map& facts) // but I don't know why the /sys support was added; research needed. // Since sys is the default, just reproduce that logic for now. - string sysfs_cpu_directory = "/sys/devices/system/cpu"; - vector package_ids; + std::string sysfs_cpu_directory = "/sys/devices/system/cpu"; + std::vector package_ids; if (file_exist(sysfs_cpu_directory)) { for (int i = 0; ; i++) { char buf[10]; snprintf(buf, sizeof(buf) - 1, "%u", i); - string cpu_phys_file = sysfs_cpu_directory + "/cpu" + buf + "/topology/physical_package_id"; + std::string cpu_phys_file = sysfs_cpu_directory + "/cpu" + buf + "/topology/physical_package_id"; if (!file_exist(cpu_phys_file)) break; @@ -673,7 +671,7 @@ static void get_physicalprocessorcount_fact(fact_map& facts) sort(package_ids.begin(), package_ids.end()); unique(package_ids.begin(), package_ids.end()); - facts["physicalprocessorcount"] = to_string(package_ids.size()); + facts["physicalprocessorcount"] = std::to_string(package_ids.size()); } else { // here's where the fall back to /proc/cpuinfo would go } @@ -684,24 +682,24 @@ void get_processorcount_fact(fact_map& facts) std::ifstream cpuinfo_file("/proc/cpuinfo", std::ifstream::in); std::string line; int processor_count = 0; - string current_processor_number; + std::string current_processor_number; while (std::getline(cpuinfo_file, line)) { unsigned sep = line.find(":"); - string tmp = line.substr(0, sep); - string key = trim(tmp); + std::string tmp = line.substr(0, sep); + std::string key = trim(tmp); if (key == "processor") { ++processor_count; - string tmp = line.substr(sep + 1, string::npos); + std::string tmp = line.substr(sep + 1, std::string::npos); current_processor_number = trim(tmp); } else if (key == "model name") { - string tmp = line.substr(sep + 1, string::npos); - facts[string("processor") + current_processor_number] = trim(tmp); + std::string tmp = line.substr(sep + 1, std::string::npos); + facts[std::string("processor") + current_processor_number] = trim(tmp); } } // this was added after 1.7.3, omit for now, needs investigation if (false) facts["activeprocessorcount"] = processor_count; - facts["processorcount"] = to_string(processor_count); + facts["processorcount"] = std::to_string(processor_count); } void get_processor_facts(fact_map& facts) @@ -727,9 +725,9 @@ void get_architecture_facts(fact_map& facts) void get_dmidecode_facts(fact_map& facts) { - string dmidecode_output = popen_stdout("/usr/sbin/dmidecode 2> /dev/null"); + std::string dmidecode_output = popen_stdout("/usr/sbin/dmidecode 2> /dev/null"); std::stringstream ss(dmidecode_output); - string line; + std::string line; enum { bios_information, @@ -768,70 +766,70 @@ void get_dmidecode_facts(fact_map& facts) if (dmi_section == unknown) continue; size_t sep = line.find(":"); - if (sep != string::npos) { - string tmp = line.substr(0, sep); - string key = trim(tmp); + if (sep != std::string::npos) { + std::string tmp = line.substr(0, sep); + std::string key = trim(tmp); if (dmi_section == bios_information) { ci_string ci_key = key.c_str(); if (ci_key == "vendor") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); + std::string tmp = line.substr(sep + 1, std::string::npos); + std::string value = trim(tmp); facts["bios_vendor"] = value; } if (ci_key == "version") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); + std::string tmp = line.substr(sep + 1, std::string::npos); + std::string value = trim(tmp); facts["bios_version"] = value; } if (ci_key == "release date") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); + std::string tmp = line.substr(sep + 1, std::string::npos); + std::string value = trim(tmp); facts["bios_release_date"] = value; } } else if (dmi_section == base_board_information) { ci_string ci_key = key.c_str(); if (ci_key == "manufacturer") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); + std::string tmp = line.substr(sep + 1, std::string::npos); + std::string value = trim(tmp); facts["boardmanufacturer"] = value; } if (ci_key == "product name" || ci_key == "product") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); + std::string tmp = line.substr(sep + 1, std::string::npos); + std::string value = trim(tmp); facts["boardproductname"] = value; } if (ci_key == "serial number") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); + std::string tmp = line.substr(sep + 1, std::string::npos); + std::string value = trim(tmp); facts["boardserialnumber"] = value; } } else if (dmi_section == system_information) { ci_string ci_key = key.c_str(); if (ci_key == "manufacturer") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); + std::string tmp = line.substr(sep + 1, std::string::npos); + std::string value = trim(tmp); facts["manufacturer"] = value; } if (ci_key == "product name" || ci_key == "product") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); + std::string tmp = line.substr(sep + 1, std::string::npos); + std::string value = trim(tmp); facts["productname"] = value; } if (ci_key == "serial number") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); + std::string tmp = line.substr(sep + 1, std::string::npos); + std::string value = trim(tmp); facts["serialnumber"] = value; } if (ci_key == "uuid") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); + std::string tmp = line.substr(sep + 1, std::string::npos); + std::string value = trim(tmp); facts["uuid"] = value; } } else if (dmi_section == chassis_information) { ci_string ci_key = key.c_str(); if (ci_key == "chassis type" || ci_key == "type") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); + std::string tmp = line.substr(sep + 1, std::string::npos); + std::string value = trim(tmp); facts["type"] = value; } } @@ -843,9 +841,9 @@ void get_filesystems_facts(fact_map& facts) { std::ifstream cpuinfo_file("/proc/filesystems", std::ifstream::in); std::string line; - string filesystems = ""; + std::string filesystems = ""; while (std::getline(cpuinfo_file, line)) { - if (line.find("nodev") != string::npos || line.find("fuseblk") != string::npos) + if (line.find("nodev") != std::string::npos || line.find("fuseblk") != std::string::npos) continue; if (!filesystems.empty()) @@ -860,17 +858,17 @@ void get_hostname_facts(fact_map& facts) { // there's some history here, perhaps just port the facter conditional straight across? // so this is short-term - string hostname_output = popen_stdout("hostname"); + std::string hostname_output = popen_stdout("hostname"); unsigned sep = hostname_output.find("."); - string hostname1 = hostname_output.substr(0, sep); - string hostname = trim(hostname1); + std::string hostname1 = hostname_output.substr(0, sep); + std::string hostname = trim(hostname1); - ifstream resolv_conf_file("/etc/resolv.conf", std::ifstream::in); - string line; - string domain; - string search; + std::ifstream resolv_conf_file("/etc/resolv.conf", std::ifstream::in); + std::string line; + std::string domain; + std::string search; while (std::getline(resolv_conf_file, line)) { - vector elems; + std::vector elems; tokenize(line, elems); if (elems.size() >= 2) { if (elems[0] == "domain") @@ -887,7 +885,7 @@ void get_hostname_facts(fact_map& facts) facts["fqdn"] = hostname + "." + domain; } -static void get_external_facts_from_executable(fact_map& facts, string executable) +static void get_external_facts_from_executable(fact_map& facts, std::string executable) { // executable FILE *stdout = popen(executable.c_str(), "r"); @@ -896,11 +894,11 @@ static void get_external_facts_from_executable(fact_map& facts, string executabl const int buffer_len = 32 * 1024; char buffer[buffer_len]; if (fgets(buffer, buffer_len, stdout)) { - vector elems; + std::vector elems; split(buffer, '=', elems); if (elems.size() != 2) continue; // shouldn't happen - string key = trim(elems[0]); - string val = trim(elems[1]); + std::string key = trim(elems[0]); + std::string val = trim(elems[1]); facts[key] = val; } } @@ -908,7 +906,7 @@ static void get_external_facts_from_executable(fact_map& facts, string executabl } } -static void get_external_facts(fact_map& facts, string directory) +static void get_external_facts(fact_map& facts, std::string directory) { DIR *external_dir = opendir(directory.c_str()); if (external_dir == NULL) @@ -918,7 +916,7 @@ static void get_external_facts(fact_map& facts, string directory) struct stat s; while ((external_fact = readdir(external_dir))) { - string full_path = directory + "/" + external_fact->d_name; + std::string full_path = directory + "/" + external_fact->d_name; if (stat(full_path.c_str(), &s) != 0) continue; @@ -933,9 +931,9 @@ static void get_external_facts(fact_map& facts, string directory) } } -void get_external_facts(fact_map& facts, list directories) +void get_external_facts(fact_map& facts, std::list directories) { - list::iterator iter; + std::list::iterator iter; for (iter = directories.begin(); iter != directories.end(); ++iter) { get_external_facts(facts, *iter); } From 012c7be94d9aa744c3b9b32a77f7417ca8441e58 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Sat, 1 Mar 2014 16:57:26 -0800 Subject: [PATCH 1686/3753] Scrub whitespace --- cfacter.cc | 58 +++++++++++++++++++++++++------------------------- cfacterimpl.cc | 2 +- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/cfacter.cc b/cfacter.cc index 06d30a450c..b7d4f82c70 100644 --- a/cfacter.cc +++ b/cfacter.cc @@ -6,35 +6,35 @@ void help() { std::cout << - "Synopsis\n" - "========\n" - "\n" - "Collect and display facts about the system.\n" - "\n" - "Usage\n" - "=====\n" - "\n" - " cfacter [-h|--help] [-v|--version] [-j|--json] [fact] [fact] [...]\n" - "\n" - "Description\n" - "===========\n" - "\n" - "Collect and display facts about the current system. The library behind\n" - "Facter is easy to expand, making Facter an easy way to collect information\n" - "about a system.\n" - "\n" - "If no facts are specifically asked for, then all facts will be returned.\n" - "\n" - "EXAMPLE\n" - "=======\n" - " cfacter kernel\n" - "\n" - "USAGE\n" - "=====\n" - " -j, --json Emit facts in JSON format.\n" - " -v, --version Print the version and exit.\n" - " -h, --help Print this help message and exit.\n" - ""; + "Synopsis\n" + "========\n" + "\n" + "Collect and display facts about the system.\n" + "\n" + "Usage\n" + "=====\n" + "\n" + " cfacter [-h|--help] [-v|--version] [-j|--json] [fact] [fact] [...]\n" + "\n" + "Description\n" + "===========\n" + "\n" + "Collect and display facts about the current system. The library behind\n" + "Facter is easy to expand, making Facter an easy way to collect information\n" + "about a system.\n" + "\n" + "If no facts are specifically asked for, then all facts will be returned.\n" + "\n" + "EXAMPLE\n" + "=======\n" + " cfacter kernel\n" + "\n" + "USAGE\n" + "=====\n" + " -j, --json Emit facts in JSON format.\n" + " -v, --version Print the version and exit.\n" + " -h, --help Print this help message and exit.\n" + ""; } void version() diff --git a/cfacterimpl.cc b/cfacterimpl.cc index 5a3922675c..e6901f595f 100644 --- a/cfacterimpl.cc +++ b/cfacterimpl.cc @@ -313,7 +313,7 @@ static void get_redhat_facts(fact_map& facts) } } else { facts["operatingsystem"] = "RedHat"; - } + } } } From c60f11e6a917d5cf12f3fa8b5293e33d7c0de023 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 3 Mar 2014 10:12:16 -0800 Subject: [PATCH 1687/3753] (FACT-356) Return nil when no vlans are present --- lib/facter/util/vlans.rb | 21 +++++++++------------ spec/unit/util/vlans_spec.rb | 33 +++++++++++++++++++++++++++++---- 2 files changed, 38 insertions(+), 16 deletions(-) diff --git a/lib/facter/util/vlans.rb b/lib/facter/util/vlans.rb index 0a48ec890b..e42a28f5f0 100644 --- a/lib/facter/util/vlans.rb +++ b/lib/facter/util/vlans.rb @@ -2,23 +2,20 @@ # module Facter::Util::Vlans def self.get_vlan_config - output = "" - if File.exists?('/proc/net/vlan/config') and File.readable?('/proc/net/vlan/config') - output = File.open('/proc/net/vlan/config').read - end - output + if File.exist?('/proc/net/vlan/config') and File.readable?('/proc/net/vlan/config') + File.read('/proc/net/vlan/config') + end end def self.get_vlans - vlans = Array.new - if self.get_vlan_config - self.get_vlan_config.each_line do |line| - if line =~ /^([0-9A-Za-z]+)\.([0-9]+) / - vlans.insert(-1, $~[2]) if $~[2] + if (config = self.get_vlan_config) + vlans = [] + config.each_line do |line| + if (match = line.match(/^([0-9A-Za-z]+)\.([0-9]+) /)) + vlans << match[2] if match[2] end end + vlans.join(',') end - - vlans.join(',') end end diff --git a/spec/unit/util/vlans_spec.rb b/spec/unit/util/vlans_spec.rb index 70de170fc6..caed3a62bb 100755 --- a/spec/unit/util/vlans_spec.rb +++ b/spec/unit/util/vlans_spec.rb @@ -4,9 +4,34 @@ require 'facter/util/vlans' describe Facter::Util::Vlans do - it "should return a list of vlans on Linux" do - linux_vlanconfig = my_fixture_read("linux_vlan_config") - Facter::Util::Vlans.stubs(:get_vlan_config).returns(linux_vlanconfig) - Facter::Util::Vlans.get_vlans().should == %{400,300,200,100} + let(:vlan_file) { "/proc/net/vlan/config" } + + describe "reading the vlan configuration" do + it "uses the contents of /proc/net/vlan/config" do + File.expects(:exist?).with(vlan_file).returns true + File.expects(:readable?).with(vlan_file).returns true + File.expects(:read).with(vlan_file).returns "vlan contents here" + + expect(Facter::Util::Vlans.get_vlan_config).to eq "vlan contents here" + end + + it "returns nil when /proc/net/vlan/config is absent" do + File.expects(:exist?).with(vlan_file).returns false + expect(Facter::Util::Vlans.get_vlan_config).to be_nil + end + end + + describe "parsing the vlan configuration" do + let(:vlan_content) { my_fixture_read("linux_vlan_config") } + + it "returns a list of vlans on Linux when vlans are configured" do + Facter::Util::Vlans.stubs(:get_vlan_config).returns(vlan_content) + expect(Facter::Util::Vlans.get_vlans()).to eq %{400,300,200,100} + end + + it "returns nil when no vlans are configured" do + Facter::Util::Vlans.stubs(:get_vlan_config).returns(nil) + expect(Facter::Util::Vlans.get_vlans()).to be_nil + end end end From 94c218867240d93f9ece9f35028703a1859726c5 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Wed, 30 Jan 2013 14:59:10 -0800 Subject: [PATCH 1688/3753] Use simpler DSL for declaring external commands. When a fact is simply the result of running an external command, a block isn't needed - just the command string. This changes a bunch of facts to use that shorter, simpler form as an example to those who follow about how they should be declared. Signed-off-by: Daniel Pittman Conflicts: lib/facter/physicalprocessorcount.rb lib/facter/processor.rb --- lib/facter/hostname.rb | 4 +--- lib/facter/lsbdistcodename.rb | 4 +--- lib/facter/lsbdistid.rb | 4 +--- lib/facter/lsbdistrelease.rb | 4 +--- lib/facter/lsbrelease.rb | 4 +--- lib/facter/physicalprocessorcount.rb | 14 ++++++-------- lib/facter/processor.rb | 25 +++++-------------------- lib/facter/util/manufacturer.rb | 8 ++------ 8 files changed, 18 insertions(+), 49 deletions(-) diff --git a/lib/facter/hostname.rb b/lib/facter/hostname.rb index 8847bfaf5d..d630c4f717 100644 --- a/lib/facter/hostname.rb +++ b/lib/facter/hostname.rb @@ -27,7 +27,5 @@ Facter.add(:hostname) do confine :kernel => :darwin, :kernelrelease => "R7" - setcode do - Facter::Core::Execution.exec('/usr/sbin/scutil --get LocalHostName') - end + setcode '/usr/sbin/scutil --get LocalHostName' end diff --git a/lib/facter/lsbdistcodename.rb b/lib/facter/lsbdistcodename.rb index 8b191dd017..01ec03ecc9 100644 --- a/lib/facter/lsbdistcodename.rb +++ b/lib/facter/lsbdistcodename.rb @@ -12,7 +12,5 @@ Facter.add(:lsbdistcodename) do confine :kernel => [ :linux, :"gnu/kfreebsd" ] - setcode do - Facter::Core::Execution.exec('lsb_release -c -s 2>/dev/null') - end + setcode 'lsb_release -c -s 2>/dev/null' end diff --git a/lib/facter/lsbdistid.rb b/lib/facter/lsbdistid.rb index e77ccbc378..79290fb6fc 100644 --- a/lib/facter/lsbdistid.rb +++ b/lib/facter/lsbdistid.rb @@ -12,7 +12,5 @@ Facter.add(:lsbdistid) do confine :kernel => [ :linux, :"gnu/kfreebsd" ] - setcode do - Facter::Core::Execution.exec('lsb_release -i -s 2>/dev/null') - end + setcode 'lsb_release -i -s 2>/dev/null' end diff --git a/lib/facter/lsbdistrelease.rb b/lib/facter/lsbdistrelease.rb index c691b36584..95e17eb61a 100644 --- a/lib/facter/lsbdistrelease.rb +++ b/lib/facter/lsbdistrelease.rb @@ -12,7 +12,5 @@ Facter.add(:lsbdistrelease) do confine :kernel => [ :linux, :"gnu/kfreebsd" ] - setcode do - Facter::Core::Execution.exec('lsb_release -r -s 2>/dev/null') - end + setcode 'lsb_release -r -s 2>/dev/null' end diff --git a/lib/facter/lsbrelease.rb b/lib/facter/lsbrelease.rb index 9b89bc6e9d..9fc4adf161 100644 --- a/lib/facter/lsbrelease.rb +++ b/lib/facter/lsbrelease.rb @@ -12,7 +12,5 @@ Facter.add(:lsbrelease) do confine :kernel => [ :linux, :"gnu/kfreebsd" ] - setcode do - Facter::Core::Execution.exec('lsb_release -v -s 2>/dev/null') - end + setcode 'lsb_release -v -s 2>/dev/null' end diff --git a/lib/facter/physicalprocessorcount.rb b/lib/facter/physicalprocessorcount.rb index 6973545211..68917af3bc 100644 --- a/lib/facter/physicalprocessorcount.rb +++ b/lib/facter/physicalprocessorcount.rb @@ -66,19 +66,17 @@ Facter.add('physicalprocessorcount') do confine :kernel => :sunos + # (#16526) The -p flag was not added until Solaris 8. Our intent is to + # split the kernel release fact value on the dot, take the second element, + # and disable the -p flag for values < 8 when. setcode do - # (#16526) The -p flag was not added until Solaris 8. Our intent is to - # split the kernel release fact value on the dot, take the second element, - # and disable the -p flag for values < 8 when. kernelrelease = Facter.value(:kernelrelease) (major_version, minor_version) = kernelrelease.split(".").map { |str| str.to_i } - cmd = "/usr/sbin/psrinfo" - result = nil if (major_version > 5) or (major_version == 5 and minor_version >= 8) then - result = Facter::Core::Execution.exec("#{cmd} -p") + Facter::Core::Execution.exec("/usr/sbin/psrinfo -p") else - output = Facter::Core::Execution.exec(cmd) - result = output.split("\n").length.to_s + output = Facter::Core::Execution.exec("/usr/sbin/psrinfo") + output.split("\n").length.to_s end end end diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb index 57d9459e45..3b254c0b46 100644 --- a/lib/facter/processor.rb +++ b/lib/facter/processor.rb @@ -90,23 +90,12 @@ Facter.add("Processor") do confine :kernel => :openbsd - setcode do - Facter::Core::Execution.exec("uname -p") - end -end - -Facter.add("ProcessorCount") do - confine :kernel => :openbsd - setcode do - Facter::Core::Execution.exec("sysctl -n hw.ncpu") - end + setcode "uname -p" end Facter.add("ProcessorCount") do confine :kernel => :Darwin - setcode do - Facter::Core::Execution.exec("sysctl -n hw.ncpu") - end + setcode "sysctl -n hw.ncpu" end if Facter.value(:kernel) == "windows" @@ -149,16 +138,12 @@ Facter.add("Processor") do confine :kernel => [:dragonfly,:freebsd] - setcode do - Facter::Core::Execution.exec("sysctl -n hw.model") - end + setcode "sysctl -n hw.model" end Facter.add("ProcessorCount") do - confine :kernel => [:dragonfly,:freebsd] - setcode do - Facter::Core::Execution.exec("sysctl -n hw.ncpu") - end + confine :kernel => [:dragonfly,:freebsd,:openbsd] + setcode "sysctl -n hw.ncpu" end Facter.add("ProcessorCount") do diff --git a/lib/facter/util/manufacturer.rb b/lib/facter/util/manufacturer.rb index 183c52fc5c..6f93e6d9ed 100644 --- a/lib/facter/util/manufacturer.rb +++ b/lib/facter/util/manufacturer.rb @@ -54,9 +54,7 @@ def self.sysctl_find_system_info(name) name.each do |sysctlkey,facterkey| Facter.add(facterkey) do confine :kernel => [:openbsd, :darwin] - setcode do - Facter::Core::Execution.exec("sysctl -n #{sysctlkey} 2>/dev/null") - end + setcode "sysctl -n #{sysctlkey} 2>/dev/null" end end end @@ -80,9 +78,7 @@ def self.prtdiag_sparc_find_system_info() end Facter.add('serialnumber') do - setcode do - Facter::Core::Execution.exec("/usr/sbin/sneep") - end + setcode "/usr/sbin/sneep" end end From ac366f376403c67b347441b0bc14d55266f4e5ea Mon Sep 17 00:00:00 2001 From: Melissa Stone Date: Tue, 4 Mar 2014 14:10:57 -0800 Subject: [PATCH 1689/3753] (FACT-342) Add trusty cows These cows are so trusty, you know they'll just never let you down! As a side note, this is for Trusty Tahr, also knows as Ubuntu 14.04 --- ext/build_defaults.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/build_defaults.yaml b/ext/build_defaults.yaml index f2bec369d4..ce3e318de3 100644 --- a/ext/build_defaults.yaml +++ b/ext/build_defaults.yaml @@ -2,7 +2,7 @@ packaging_url: 'git://github.com/puppetlabs/packaging.git --branch=master' packaging_repo: 'packaging' default_cow: 'base-squeeze-i386.cow' -cows: 'base-lucid-i386.cow base-lucid-amd64.cow base-precise-i386.cow base-precise-amd64.cow base-quantal-i386.cow base-quantal-amd64.cow base-raring-i386.cow base-raring-amd64.cow base-saucy-i386.cow base-saucy-amd64.cow base-sid-i386.cow base-sid-amd64.cow base-squeeze-i386.cow base-squeeze-amd64.cow base-stable-i386.cow base-stable-amd64.cow base-testing-i386.cow base-testing-amd64.cow base-unstable-i386.cow base-unstable-amd64.cow base-wheezy-i386.cow base-wheezy-amd64.cow' +cows: 'base-lucid-i386.cow base-lucid-amd64.cow base-precise-i386.cow base-precise-amd64.cow base-quantal-i386.cow base-quantal-amd64.cow base-raring-i386.cow base-raring-amd64.cow base-saucy-i386.cow base-saucy-amd64.cow base-sid-i386.cow base-sid-amd64.cow base-squeeze-i386.cow base-squeeze-amd64.cow base-stable-i386.cow base-stable-amd64.cow base-testing-i386.cow base-testing-amd64.cow base-trusty-i386.cow base-trusty-amd64.cow base-unstable-i386.cow base-unstable-amd64.cow base-wheezy-i386.cow base-wheezy-amd64.cow' pbuild_conf: '/etc/pbuilderrc' packager: 'puppetlabs' gpg_name: 'info@puppetlabs.com' From ef08e6238c36a2f9c580df72b3a0339f7916268f Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Tue, 4 Mar 2014 22:47:23 -0800 Subject: [PATCH 1690/3753] Restore cpplint and cppcheck targets --- CMakeLists.txt | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 74b7022ae0..05833fb5cc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,3 +12,45 @@ configure_file ( ) add_subdirectory(exe) + +# +# Add cpplint target +# +include(FindPythonInterp) +if (NOT PYTHONINTERP_FOUND) + message(STATUS "Python not found; 'cpplint' target will not be available") +else() + set(CPPLINT_FILTER + "-build/include" # Why? + "-legal/copyright" # Not yet + "-runtime/references" # Not sure about this religion + "-readability/streams" # What? + "-whitespace/braces" # Is there a k&r setting? + "-whitespace/line_length" # Well yeah, but ... not just now + "-runtime/arrays" # Sizing an array with a 'const int' doesn't make it variable sized + "-readability/todo" # Seriously? todo comments need to identify an owner? pffft + ) + + FILE (GLOB ALL_SOURCES lib/*.cc exe/*.cc) + + set(CPPLINT_PATH "ext/cpplint.py") + + set(CPPLINT_ARGS "") + if (CPPLINT_FILTER) + string(REPLACE ";" "," CPPLINT_FILTER "${CPPLINT_FILTER}") + set(CPPLINT_ARGS "${CPPLINT_ARGS}--filter=${CPPLINT_FILTER}") + endif() + if (MSVC) + set(CPPLINT_ARGS "${CPPLINT_ARGS} --output=vs7") + endif() + + add_custom_target(cpplint + COMMAND ${PYTHON_EXECUTABLE} ${CPPLINT_PATH} ${CPPLINT_ARGS} ${ALL_SOURCES} + VERBATIM + ) + +endif() + +add_custom_target(cppcheck + COMMAND cppcheck lib exe +) From a3426ed20b11ddca04d1bf9a163d9fc84ef8ce16 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Tue, 4 Mar 2014 23:02:55 -0800 Subject: [PATCH 1691/3753] Update TODO --- TODO | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/TODO b/TODO index 3194d2931b..b7f9fbd2d3 100644 --- a/TODO +++ b/TODO @@ -1,12 +1,9 @@ Please -* complete ffi wrapper -* Handle non-string values +* Handle non-string values, conform to facter.json schema * Normalize value and to_json to both return json? * virtual -* cmake or autoconf? * abstract out command execution method * Add regex support (e.g. for os and processor file parsing) -* Allow 'facter ' (currently returns all facts) Not sure * Split out into app, core lib, libs per functional group (network, storage, selinux, processors, etc) From 802841597df846a510d34074ac04c5cfa2563365 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Thu, 6 Mar 2014 22:33:57 -0800 Subject: [PATCH 1692/3753] Remove TODO in favor of github issues --- TODO | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 TODO diff --git a/TODO b/TODO deleted file mode 100644 index b7f9fbd2d3..0000000000 --- a/TODO +++ /dev/null @@ -1,12 +0,0 @@ -Please -* Handle non-string values, conform to facter.json schema -* Normalize value and to_json to both return json? -* virtual -* abstract out command execution method -* Add regex support (e.g. for os and processor file parsing) - -Not sure -* Split out into app, core lib, libs per functional group (network, storage, selinux, processors, etc) -* Use dynamic loading of libs (dlopen, etc) -* is 'weight' needed? -* fact dependencies? From e5d18709c21c42e0c4e7d97d7af9753ab2f05051 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Thu, 6 Mar 2014 22:57:12 -0800 Subject: [PATCH 1693/3753] Replace iterator typedefs with auto; yay c++11 --- lib/cfacterimpl.cc | 7 ++----- lib/cfacterlib.cc | 6 ++---- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/lib/cfacterimpl.cc b/lib/cfacterimpl.cc index 91a8ff038b..fcab5acd93 100644 --- a/lib/cfacterimpl.cc +++ b/lib/cfacterimpl.cc @@ -28,7 +28,6 @@ #include #include #include -#include #include "cfacterlib.h" #include "cfacterimpl.h" @@ -644,8 +643,7 @@ void get_ssh_facts(fact_map& facts) ssh_facts["sshrsakey"] = "ssh_host_rsa_key.pub"; ssh_facts["sshecdsakey"] = "ssh_host_ecdsa_key.pub"; - typedef std::map::iterator iter; - for (iter i = ssh_facts.begin(); i != ssh_facts.end(); ++i) { + for (auto i = ssh_facts.begin(); i != ssh_facts.end(); ++i) { get_ssh_fact(i->first, i->second, facts); } } @@ -933,8 +931,7 @@ static void get_external_facts(fact_map& facts, std::string directory) void get_external_facts(fact_map& facts, std::list directories) { - std::list::iterator iter; - for (iter = directories.begin(); iter != directories.end(); ++iter) { + for (auto iter = directories.begin(); iter != directories.end(); ++iter) { get_external_facts(facts, *iter); } } diff --git a/lib/cfacterlib.cc b/lib/cfacterlib.cc index b03db3e937..504dc6faaa 100644 --- a/lib/cfacterlib.cc +++ b/lib/cfacterlib.cc @@ -63,8 +63,7 @@ int to_json(char *facts_json, size_t facts_len) rapidjson::Document::AllocatorType& allocator = json.GetAllocator(); - typedef std::map::iterator iter; - for (iter i = facts.begin(); i != facts.end(); ++i) { + for (auto i = facts.begin(); i != facts.end(); ++i) { json.AddMember(i->first.c_str(), i->second.c_str(), allocator); } @@ -82,8 +81,7 @@ int value(const char *fact, char *value, size_t value_len) { loadfacts(); - typedef std::map::iterator iter; - iter i = facts.find(fact); + auto i = facts.find(fact); if (i == facts.end()) return -1; From 7a9c5b17d72389e32972d733368f9c64e2b6bd89 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Thu, 6 Mar 2014 23:02:36 -0800 Subject: [PATCH 1694/3753] Change list of external fact directories to a const reference --- lib/cfacterimpl.cc | 2 +- lib/cfacterimpl.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/cfacterimpl.cc b/lib/cfacterimpl.cc index fcab5acd93..1622d185bc 100644 --- a/lib/cfacterimpl.cc +++ b/lib/cfacterimpl.cc @@ -929,7 +929,7 @@ static void get_external_facts(fact_map& facts, std::string directory) } } -void get_external_facts(fact_map& facts, std::list directories) +void get_external_facts(fact_map& facts, const std::list& directories) { for (auto iter = directories.begin(); iter != directories.end(); ++iter) { get_external_facts(facts, *iter); diff --git a/lib/cfacterimpl.h b/lib/cfacterimpl.h index 28f9d829ff..2e9163a0e6 100644 --- a/lib/cfacterimpl.h +++ b/lib/cfacterimpl.h @@ -21,4 +21,4 @@ void get_architecture_facts(fact_map&); void get_dmidecode_facts(fact_map&); void get_filesystems_facts(fact_map&); void get_hostname_facts(fact_map&); -void get_external_facts(fact_map&, std::list directories); +void get_external_facts(fact_map&, const std::list&); From ab78e3a6ad290feafb285f5c4fc892065b9198cc Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 4 Mar 2014 11:12:20 -0800 Subject: [PATCH 1695/3753] (FACT-357) Raise errors when command execution fails The behavior of returning an empty string when a command cannot be run is surprising and makes it impossible to determine if a command failed to run or simply had no output. This commit allows users to determine if an error should be raised on error, or if a default value should be returned instead. It also updates calling code to respect this new behavior. --- lib/facter/core/execution.rb | 18 +++++++-- lib/facter/core/execution/base.rb | 28 +++++++++----- lib/facter/util/resolution.rb | 8 +++- spec/unit/core/execution/base_spec.rb | 31 ++++++++++----- spec/unit/core/execution_spec.rb | 2 +- spec/unit/hardwareisa_spec.rb | 10 ++--- spec/unit/hardwaremodel_spec.rb | 4 +- spec/unit/hostname_spec.rb | 2 +- spec/unit/id_spec.rb | 4 +- spec/unit/kernelrelease_spec.rb | 54 +++++++++++++-------------- spec/unit/kernelversion_spec.rb | 26 ++++++------- spec/unit/lsbdistcodename_spec.rb | 12 +++--- spec/unit/lsbdistid_spec.rb | 12 +++--- spec/unit/lsbdistrelease_spec.rb | 4 +- spec/unit/lsbrelease_spec.rb | 12 +++--- spec/unit/processor_spec.rb | 10 ++--- spec/unit/uniqueid_spec.rb | 6 +-- spec/unit/util/ip_spec.rb | 4 ++ spec/unit/util/resolution_spec.rb | 2 +- spec/unit/zonename_spec.rb | 2 +- 20 files changed, 146 insertions(+), 105 deletions(-) diff --git a/lib/facter/core/execution.rb b/lib/facter/core/execution.rb index 353d0fa6bc..84a854672f 100644 --- a/lib/facter/core/execution.rb +++ b/lib/facter/core/execution.rb @@ -89,12 +89,24 @@ def with_env(values, &block) # executing the code. # # @param code [String] the program to run - # @return [String] the output of the program or the empty string on error + # @param options [Hash] + # + # @option options [Object] :on_fail How to behave when the command could + # not be run. Specifying :raise will raise an error, anything else will + # return that object on failure. Default is :raise. + # + # @raise [Facter::Core::Execution::ExecutionFailure] If the command does + # not exist or could not be executed. + # + # @return [String] the output of the program, or the value of :on_fail if + # command execution failed and :on_fail was specified. # # @api public - def exec(command) - @@impl.exec(command) + def exec(command, options = {}) + @@impl.exec(command, options) end + + class ExecutionFailure < StandardError; end end end end diff --git a/lib/facter/core/execution/base.rb b/lib/facter/core/execution/base.rb index cdeda13c27..95a5b93b57 100644 --- a/lib/facter/core/execution/base.rb +++ b/lib/facter/core/execution/base.rb @@ -27,31 +27,39 @@ def with_env(values) rv end - def exec(code) + def exec(command, options = {}) + + on_fail = options.fetch(:on_fail, :raise) ## Set LANG to force i18n to C for the duration of this exec; this ensures that any code that parses the ## output of the command can expect it to be in a consistent / predictable format / locale with_env "LANG" => "C" do - if expanded_code = expand_command(code) - # if we can find the binary, we'll run the command with the expanded path to the binary - code = expanded_code - else - return '' + expanded_command = expand_command(command) + + if expanded_command.nil? + if on_fail == :raise + raise Facter::Core::Execution::ExecutionFailure.new, "Could not execute '#{command}': command not found" + else + return on_fail + end end out = '' begin wait_for_child = true - out = %x{#{code}}.chomp + out = %x{#{expanded_command}}.chomp wait_for_child = false rescue => detail - Facter.warn(detail.message) - return '' + if on_fail == :raise + raise Facter::Core::Execution::ExecutionFailure.new, "Failed while executing '#{expanded_command}': #{detail.message}" + else + return on_fail + end ensure if wait_for_child - # We need to ensure that if this code exits early then any spawned + # We need to ensure that if this command exits early then any spawned # children will be reaped. Process execution is frequently # terminated using Timeout.timeout but since the timeout isn't in # this scope we can't rescue the raised exception. The best that diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 336e7c6711..4ac25e45a7 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -128,8 +128,12 @@ def set_options(options) def setcode(string = nil, &block) if string @code = Proc.new do - output = Facter::Core::Execution.exec(string) - output.empty? ? nil : output + output = Facter::Core::Execution.exec(string, :on_fail => nil) + if output.nil? or output.empty? + nil + else + output + end end elsif block_given? @code = block diff --git a/spec/unit/core/execution/base_spec.rb b/spec/unit/core/execution/base_spec.rb index 652f392dd6..00fdbd8bb5 100644 --- a/spec/unit/core/execution/base_spec.rb +++ b/spec/unit/core/execution/base_spec.rb @@ -77,18 +77,31 @@ def handy_method() subject.exec('foo') end - it "returns an empty string when the command could not be expanded" do - subject.expects(:expand_command).with('foo').returns nil - expect(subject.exec('foo')).to be_empty + describe "and the command is not present" do + it "raises an error when the :on_fail behavior is :raise" do + subject.expects(:expand_command).with('foo').returns nil + expect { subject.exec('foo') }.to raise_error(Facter::Core::Execution::ExecutionFailure) + end + + it "returns the given value when :on_fail is set to a value" do + subject.expects(:expand_command).with('foo').returns nil + expect(subject.exec('foo', :on_fail => nil)).to be_nil + end end - it "logs a warning and returns an empty string when the command execution fails" do - subject.expects(:`).with("/bin/foo").raises "kaboom!" - Facter.expects(:warn).with("kaboom!") + describe "when command execution fails" do + before do + subject.expects(:`).with("/bin/foo").raises "kaboom!" + subject.expects(:expand_command).with('foo').returns '/bin/foo' + end - subject.expects(:expand_command).with('foo').returns '/bin/foo' + it "raises an error when the :on_fail behavior is :raise" do + expect { subject.exec('foo') }.to raise_error(Facter::Core::Execution::ExecutionFailure) + end - expect(subject.exec("foo")).to be_empty + it "returns the given value when :on_fail is set to a value" do + expect(subject.exec('foo', :on_fail => nil)).to be_nil + end end it "launches a thread to wait on children if the command was interrupted" do @@ -99,7 +112,7 @@ def handy_method() Thread.expects(:new).yields Process.expects(:waitall).once - subject.exec("foo") + subject.exec("foo", :on_fail => nil) end it "returns the output of the command" do diff --git a/spec/unit/core/execution_spec.rb b/spec/unit/core/execution_spec.rb index c36358f3ec..201345f545 100644 --- a/spec/unit/core/execution_spec.rb +++ b/spec/unit/core/execution_spec.rb @@ -31,7 +31,7 @@ end it "delegates #exec to the implementation" do - impl.expects(:exec).with('waffles') + impl.expects(:exec).with('waffles', {}) subject.exec('waffles') end end diff --git a/spec/unit/hardwareisa_spec.rb b/spec/unit/hardwareisa_spec.rb index 39bb7050b6..8559ae9d7f 100755 --- a/spec/unit/hardwareisa_spec.rb +++ b/spec/unit/hardwareisa_spec.rb @@ -6,35 +6,35 @@ describe "Hardwareisa fact" do it "should match uname -p on Linux" do Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter::Core::Execution.stubs(:exec).with("uname -p").returns("Inky") + Facter::Core::Execution.stubs(:exec).with("uname -p", anything).returns("Inky") Facter.fact(:hardwareisa).value.should == "Inky" end it "should match uname -p on Darwin" do Facter.fact(:kernel).stubs(:value).returns("Darwin") - Facter::Core::Execution.stubs(:exec).with("uname -p").returns("Blinky") + Facter::Core::Execution.stubs(:exec).with("uname -p", anything).returns("Blinky") Facter.fact(:hardwareisa).value.should == "Blinky" end it "should match uname -p on SunOS" do Facter.fact(:kernel).stubs(:value).returns("SunOS") - Facter::Core::Execution.stubs(:exec).with("uname -p").returns("Pinky") + Facter::Core::Execution.stubs(:exec).with("uname -p", anything).returns("Pinky") Facter.fact(:hardwareisa).value.should == "Pinky" end it "should match uname -p on FreeBSD" do Facter.fact(:kernel).stubs(:value).returns("FreeBSD") - Facter::Core::Execution.stubs(:exec).with("uname -p").returns("Clyde") + Facter::Core::Execution.stubs(:exec).with("uname -p", anything).returns("Clyde") Facter.fact(:hardwareisa).value.should == "Clyde" end it "should match uname -m on HP-UX" do Facter.fact(:kernel).stubs(:value).returns("HP-UX") - Facter::Core::Execution.stubs(:exec).with("uname -m").returns("Pac-Man") + Facter::Core::Execution.stubs(:exec).with("uname -m", anything).returns("Pac-Man") Facter.fact(:hardwareisa).value.should == "Pac-Man" end diff --git a/spec/unit/hardwaremodel_spec.rb b/spec/unit/hardwaremodel_spec.rb index 0a8e9ee750..b4c65ec0b5 100644 --- a/spec/unit/hardwaremodel_spec.rb +++ b/spec/unit/hardwaremodel_spec.rb @@ -6,7 +6,7 @@ describe "Hardwaremodel fact" do it "should match uname -m by default" do Facter.fact(:kernel).stubs(:value).returns("Darwin") - Facter::Core::Execution.stubs(:exec).with("uname -m").returns("Inky") + Facter::Core::Execution.stubs(:exec).with("uname -m", anything).returns("Inky") Facter.fact(:hardwaremodel).value.should == "Inky" end @@ -23,7 +23,7 @@ Facter::Util::WMI.expects(:execquery).returns([cpu]) Facter.fact(:hardwaremodel).value.should == "i486" - end + end it "should detect i686" do cpu = mock('cpu', :Architecture => 0, :Level => 6) diff --git a/spec/unit/hostname_spec.rb b/spec/unit/hostname_spec.rb index a78703170f..9350c4f252 100755 --- a/spec/unit/hostname_spec.rb +++ b/spec/unit/hostname_spec.rb @@ -33,7 +33,7 @@ end it "should use scutil to get the hostname" do - Facter::Core::Execution.expects(:exec).with('/usr/sbin/scutil --get LocalHostName').returns("host1") + Facter::Core::Execution.expects(:exec).with('/usr/sbin/scutil --get LocalHostName', anything).returns("host1") Facter.fact(:hostname).value.should == "host1" end end diff --git a/spec/unit/id_spec.rb b/spec/unit/id_spec.rb index b2eff37378..f5f4ad4e39 100755 --- a/spec/unit/id_spec.rb +++ b/spec/unit/id_spec.rb @@ -12,7 +12,7 @@ it "should return the current user" do given_a_configuration_of(:is_windows => k == 'windows') Facter.fact(:kernel).stubs(:value).returns(k) - Facter::Core::Execution.expects(:exec).once.with('whoami').returns 'bar' + Facter::Core::Execution.expects(:exec).once.with('whoami', anything).returns 'bar' Facter.fact(:id).value.should == 'bar' end @@ -22,7 +22,7 @@ it "should return the current user on Solaris" do given_a_configuration_of(:is_windows => false) Facter::Core::Execution.stubs(:exec).with('uname -s').returns('SunOS') - Facter::Core::Execution.expects(:exec).once.with('/usr/xpg4/bin/id -un').returns 'bar' + Facter::Core::Execution.expects(:exec).once.with('/usr/xpg4/bin/id -un', anything).returns 'bar' Facter.fact(:id).value.should == 'bar' end diff --git a/spec/unit/kernelrelease_spec.rb b/spec/unit/kernelrelease_spec.rb index 94eec126f0..12c4d05bbe 100644 --- a/spec/unit/kernelrelease_spec.rb +++ b/spec/unit/kernelrelease_spec.rb @@ -2,52 +2,52 @@ require 'spec_helper' -describe "Kernel release fact" do +describe "Kernel release fact" do - describe "on Windows" do - before do + describe "on Windows" do + before do Facter.fact(:kernel).stubs(:value).returns("windows") require 'facter/util/wmi' version = stubs 'version' version.stubs(:Version).returns("test_kernel") Facter::Util::WMI.stubs(:execquery).with("SELECT Version from Win32_OperatingSystem").returns([version]) end - - it "should return the kernel release" do + + it "should return the kernel release" do Facter.fact(:kernelrelease).value.should == "test_kernel" - end - end + end + end - describe "on AIX" do - before do + describe "on AIX" do + before do Facter.fact(:kernel).stubs(:value).returns("aix") - Facter::Core::Execution.stubs(:exec).with('oslevel -s').returns("test_kernel") - end - - it "should return the kernel release" do + Facter::Core::Execution.stubs(:exec).with('oslevel -s', anything).returns("test_kernel") + end + + it "should return the kernel release" do Facter.fact(:kernelrelease).value.should == "test_kernel" - end - end + end + end describe "on HP-UX" do before do - Facter.fact(:kernel).stubs(:value).returns("hp-ux") + Facter.fact(:kernel).stubs(:value).returns("hp-ux") Facter::Core::Execution.stubs(:exec).with('uname -r').returns("B.11.31") - end - + end + it "should remove preceding letters" do Facter.fact(:kernelrelease).value.should == "11.31" - end - end + end + end - describe "on everything else" do + describe "on everything else" do before do Facter.fact(:kernel).stubs(:value).returns("linux") - Facter::Core::Execution.stubs(:exec).with('uname -r').returns("test_kernel") - end - + Facter::Core::Execution.stubs(:exec).with('uname -r', anything).returns("test_kernel") + end + it "should return the kernel release" do Facter.fact(:kernelrelease).value.should == "test_kernel" - end - end -end + end + end +end diff --git a/spec/unit/kernelversion_spec.rb b/spec/unit/kernelversion_spec.rb index 93ee06164d..6d20665292 100644 --- a/spec/unit/kernelversion_spec.rb +++ b/spec/unit/kernelversion_spec.rb @@ -3,29 +3,29 @@ require 'spec_helper' describe "Kernel version fact" do - + describe "on Solaris/Sun OS" do before do Facter.fact(:kernel).stubs(:value).returns('sunos') - Facter::Core::Execution.stubs(:exec).with('uname -v').returns("1.234.5") - end - - it "should return the kernel version using 'uname -v'" do + Facter::Core::Execution.stubs(:exec).with('uname -v', anything).returns("1.234.5") + end + + it "should return the kernel version using 'uname -v'" do Facter.fact(:kernelversion).value.should == "1.234.5" - end + end end - + describe "on everything else" do - before do + before do Facter.fact(:kernel).stubs(:value).returns('linux') Facter.fact(:kernelrelease).stubs(:value).returns('1.23.4-56') - end - - it "should return the kernel version using kernel release" do + end + + it "should return the kernel version using kernel release" do Facter.fact(:kernelversion).value.should == "1.23.4" - end + end end -end +end diff --git a/spec/unit/lsbdistcodename_spec.rb b/spec/unit/lsbdistcodename_spec.rb index 6516c4c2fe..ab0a4cbd1b 100755 --- a/spec/unit/lsbdistcodename_spec.rb +++ b/spec/unit/lsbdistcodename_spec.rb @@ -10,14 +10,14 @@ Facter.fact(:kernel).stubs(:value).returns kernel end - it "should return the codename through lsb_release -c -s 2>/dev/null" do - Facter::Core::Execution.stubs(:exec).with('lsb_release -c -s 2>/dev/null').returns 'n/a' - Facter.fact(:lsbdistcodename).value.should == 'n/a' + it "returns the codename through lsb_release -c -s 2>/dev/null" do + Facter::Core::Execution.impl.stubs(:exec).with('lsb_release -c -s 2>/dev/null', anything).returns 'n/a' + expect(Facter.fact(:lsbdistcodename).value).to eq 'n/a' end - it "should return nil if lsb_release is not installed" do - Facter::Core::Execution.stubs(:exec).with('lsb_release -c -s 2>/dev/null').returns nil - Facter.fact(:lsbdistcodename).value.should be_nil + it "returns nil if lsb_release is not installed" do + Facter::Core::Execution.impl.stubs(:expand_command).with('lsb_release -c -s 2>/dev/null').returns nil + expect(Facter.fact(:lsbdistcodename).value).to be_nil end end end diff --git a/spec/unit/lsbdistid_spec.rb b/spec/unit/lsbdistid_spec.rb index bec2fc5c84..53cc961f60 100755 --- a/spec/unit/lsbdistid_spec.rb +++ b/spec/unit/lsbdistid_spec.rb @@ -10,14 +10,14 @@ Facter.fact(:kernel).stubs(:value).returns kernel end - it "should return the id through lsb_release -i -s 2>/dev/null" do - Facter::Core::Execution.stubs(:exec).with('lsb_release -i -s 2>/dev/null').returns 'Gentoo' - Facter.fact(:lsbdistid).value.should == 'Gentoo' + it "returns the id through lsb_release -i -s 2>/dev/null" do + Facter::Core::Execution.impl.stubs(:exec).with('lsb_release -i -s 2>/dev/null', anything).returns 'Gentoo' + expect(Facter.fact(:lsbdistid).value).to eq 'Gentoo' end - it "should return nil if lsb_release is not installed 2>/dev/null" do - Facter::Core::Execution.stubs(:exec).with('lsb_release -i -s 2>/dev/null').returns nil - Facter.fact(:lsbdistid).value.should be_nil + it "returns nil if lsb_release is not installed" do + Facter::Core::Execution.impl.stubs(:expand_command).with('lsb_release -i -s 2>/dev/null').returns nil + expect(Facter.fact(:lsbdistid).value).to be_nil end end end diff --git a/spec/unit/lsbdistrelease_spec.rb b/spec/unit/lsbdistrelease_spec.rb index 1d9476dd18..9e8d5d2e25 100755 --- a/spec/unit/lsbdistrelease_spec.rb +++ b/spec/unit/lsbdistrelease_spec.rb @@ -11,12 +11,12 @@ end it "should return the release through lsb_release -r -s 2>/dev/null" do - Facter::Core::Execution.stubs(:exec).with('lsb_release -r -s 2>/dev/null').returns '2.1' + Facter::Core::Execution.stubs(:exec).with('lsb_release -r -s 2>/dev/null', anything).returns '2.1' Facter.fact(:lsbdistrelease).value.should == '2.1' end it "should return nil if lsb_release is not installed" do - Facter::Core::Execution.stubs(:exec).with('lsb_release -r -s 2>/dev/null').returns nil + Facter::Core::Execution.stubs(:exec).with('lsb_release -r -s 2>/dev/null', anything).returns nil Facter.fact(:lsbdistrelease).value.should be_nil end end diff --git a/spec/unit/lsbrelease_spec.rb b/spec/unit/lsbrelease_spec.rb index 18bf75143a..eaddedda8c 100755 --- a/spec/unit/lsbrelease_spec.rb +++ b/spec/unit/lsbrelease_spec.rb @@ -10,14 +10,14 @@ Facter.fact(:kernel).stubs(:value).returns kernel end - it "should return the release through lsb_release -v -s 2>/dev/null" do - Facter::Core::Execution.stubs(:exec).with('lsb_release -v -s 2>/dev/null').returns 'n/a' - Facter.fact(:lsbrelease).value.should == 'n/a' + it "returns the release through lsb_release -v -s 2>/dev/null" do + Facter::Core::Execution.impl.stubs(:exec).with('lsb_release -v -s 2>/dev/null', anything).returns 'n/a' + expect(Facter.fact(:lsbrelease).value).to eq 'n/a' end - it "should return nil if lsb_release is not installed" do - Facter::Core::Execution.stubs(:exec).with('lsb_release -v -s 2>/dev/null').returns nil - Facter.fact(:lsbrelease).value.should be_nil + it "returns nil if lsb_release is not installed" do + Facter::Core::Execution.impl.stubs(:expand_command).with('lsb_release -v -s 2>/dev/null').returns nil + expect(Facter.fact(:lsbrelease).value).to be_nil end end end diff --git a/spec/unit/processor_spec.rb b/spec/unit/processor_spec.rb index 717f942b12..d9d5c67187 100755 --- a/spec/unit/processor_spec.rb +++ b/spec/unit/processor_spec.rb @@ -185,35 +185,35 @@ def sysfs_cpu_stubs(count) it "should be 2 on dual-processor Darwin box" do Facter.fact(:kernel).stubs(:value).returns("Darwin") - Facter::Core::Execution.stubs(:exec).with("sysctl -n hw.ncpu").returns('2') + Facter::Core::Execution.stubs(:exec).with("sysctl -n hw.ncpu", anything).returns('2') Facter.fact(:processorcount).value.should == "2" end it "should be 2 on dual-processor OpenBSD box" do Facter.fact(:kernel).stubs(:value).returns("OpenBSD") - Facter::Core::Execution.stubs(:exec).with("sysctl -n hw.ncpu").returns('2') + Facter::Core::Execution.stubs(:exec).with("sysctl -n hw.ncpu", anything).returns('2') Facter.fact(:processorcount).value.should == "2" end it "should be 2 on dual-processor FreeBSD box" do Facter.fact(:kernel).stubs(:value).returns("FreeBSD") - Facter::Core::Execution.stubs(:exec).with("sysctl -n hw.ncpu").returns('2') + Facter::Core::Execution.stubs(:exec).with("sysctl -n hw.ncpu", anything).returns('2') Facter.fact(:processorcount).value.should == "2" end it "should print the correct CPU Model on FreeBSD" do Facter.fact(:kernel).stubs(:value).returns("FreeBSD") - Facter::Core::Execution.stubs(:exec).with("sysctl -n hw.model").returns('SomeVendor CPU 3GHz') + Facter::Core::Execution.stubs(:exec).with("sysctl -n hw.model", anything).returns('SomeVendor CPU 3GHz') Facter.fact(:processor).value.should == "SomeVendor CPU 3GHz" end it "should be 2 on dual-processor DragonFly box" do Facter.fact(:kernel).stubs(:value).returns("DragonFly") - Facter::Core::Execution.stubs(:exec).with("sysctl -n hw.ncpu").returns('2') + Facter::Core::Execution.stubs(:exec).with("sysctl -n hw.ncpu", anything).returns('2') Facter.fact(:processorcount).value.should == "2" end diff --git a/spec/unit/uniqueid_spec.rb b/spec/unit/uniqueid_spec.rb index 93adb7900a..261dee31fc 100755 --- a/spec/unit/uniqueid_spec.rb +++ b/spec/unit/uniqueid_spec.rb @@ -6,21 +6,21 @@ describe "Uniqueid fact" do it "should match hostid on Solaris" do Facter.fact(:kernel).stubs(:value).returns("SunOS") - Facter::Core::Execution.stubs(:exec).with("hostid").returns("Larry") + Facter::Core::Execution.stubs(:exec).with("hostid", anything).returns("Larry") Facter.fact(:uniqueid).value.should == "Larry" end it "should match hostid on Linux" do Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter::Core::Execution.stubs(:exec).with("hostid").returns("Curly") + Facter::Core::Execution.stubs(:exec).with("hostid", anything).returns("Curly") Facter.fact(:uniqueid).value.should == "Curly" end it "should match hostid on AIX" do Facter.fact(:kernel).stubs(:value).returns("AIX") - Facter::Core::Execution.stubs(:exec).with("hostid").returns("Moe") + Facter::Core::Execution.stubs(:exec).with("hostid", anything).returns("Moe") Facter.fact(:uniqueid).value.should == "Moe" end diff --git a/spec/unit/util/ip_spec.rb b/spec/unit/util/ip_spec.rb index ace30e0054..22922f3d24 100755 --- a/spec/unit/util/ip_spec.rb +++ b/spec/unit/util/ip_spec.rb @@ -401,10 +401,13 @@ def self.hpux_examples describe "exec_ifconfig" do it "uses get_ifconfig" do + Facter::Core::Execution.stubs(:exec) + Facter::Util::IP.stubs(:get_ifconfig).returns("/sbin/ifconfig").once Facter::Util::IP.exec_ifconfig end + it "support additional arguments" do Facter::Util::IP.stubs(:get_ifconfig).returns("/sbin/ifconfig") @@ -412,6 +415,7 @@ def self.hpux_examples Facter::Util::IP.exec_ifconfig(["-a"]) end + it "joins multiple arguments correctly" do Facter::Util::IP.stubs(:get_ifconfig).returns("/sbin/ifconfig") diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index fbfdaec977..3a0b028426 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -74,7 +74,7 @@ describe "and the code is a string" do it "returns the result of executing the code" do resolution.setcode "/bin/foo" - Facter::Core::Execution.expects(:exec).once.with("/bin/foo").returns "yup" + Facter::Core::Execution.expects(:exec).once.with("/bin/foo", anything).returns "yup" expect(resolution.value).to eq "yup" end diff --git a/spec/unit/zonename_spec.rb b/spec/unit/zonename_spec.rb index 23a2f98199..eeb522fc0c 100644 --- a/spec/unit/zonename_spec.rb +++ b/spec/unit/zonename_spec.rb @@ -7,7 +7,7 @@ it "should return global zone" do Facter.fact(:kernel).stubs(:value).returns("SunOS") - Facter::Core::Execution.stubs(:exec).with("zonename").returns('global') + Facter::Core::Execution.stubs(:exec).with("zonename", anything).returns('global') Facter.fact(:zonename).value.should == "global" end From 9bb5e11013e39ca55540ec8f21dc182db39740dc Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 4 Mar 2014 14:21:29 -0800 Subject: [PATCH 1696/3753] (FACT-357) Extract virtual fact vmware resolution --- lib/facter/virtual.rb | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index bc07bb94d6..98c2451f71 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -144,15 +144,23 @@ next "ovirt" if lines.any? {|l| l =~ /Product Name: oVirt Node/ } end + # Default to 'physical' + next 'physical' + end +end + +Facter.add("virtual") do + confine do + Facter::Core::Execution.which('vmware') + end + + setcode do # Sample output of vmware -v `VMware Server 1.0.5 build-80187` output = Facter::Core::Execution.exec("vmware -v") if output mdata = output.match /(\S+)\s+(\S+)/ next "#{mdata[1]}_#{mdata[2]}".downcase if mdata end - - # Default to 'physical' - next 'physical' end end From 85a6998694c9c2c1d48f562c4334f067936632c9 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Fri, 7 Mar 2014 15:15:13 -0800 Subject: [PATCH 1697/3753] (maint) Only create LDOM facts if virtinfo available --- lib/facter/ldom.rb | 2 +- spec/unit/ldom_spec.rb | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/facter/ldom.rb b/lib/facter/ldom.rb index b7cb7bbc3e..e10a55ac46 100644 --- a/lib/facter/ldom.rb +++ b/lib/facter/ldom.rb @@ -1,4 +1,4 @@ -if Facter.value(:kernel) == 'SunOS' +if Facter.value(:kernel) == 'SunOS' and Facter::Core::Execution.which('virtinfo') virtinfo = Facter::Core::Execution.exec('virtinfo -ap') # Convert virtinfo parseable output format to array of arrays. diff --git a/spec/unit/ldom_spec.rb b/spec/unit/ldom_spec.rb index 416134c82e..2aff3ca795 100644 --- a/spec/unit/ldom_spec.rb +++ b/spec/unit/ldom_spec.rb @@ -15,6 +15,7 @@ def ldom_fixtures(filename) before :each do # For virtinfo documentation: # http://docs.oracle.com/cd/E23824_01/html/821-1462/virtinfo-1m.html + Facter::Core::Execution.stubs(:which).with("virtinfo").returns 'virtinfo' Facter::Core::Execution.stubs(:exec).with("virtinfo -ap"). returns(ldom_fixtures('ldom_v1')) Facter.collection.internal_loader.load(:ldom) @@ -63,7 +64,7 @@ def ldom_fixtures(filename) describe "when running on non ldom hardware" do before :each do - Facter::Core::Execution.stubs(:exec).with("virtinfo -ap").returns(nil) + Facter::Core::Execution.stubs(:which).with("virtinfo").returns(nil) Facter.collection.internal_loader.load(:ldom) end From 46be425ebdd0aeebf5fa9e7ea5b874f12d475301 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Sat, 8 Mar 2014 07:46:49 -0800 Subject: [PATCH 1698/3753] Use same one-line 'source' syntax used in acceptance/Gemfile --- Gemfile | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Gemfile b/Gemfile index 446e8ca862..1ca3f56de6 100644 --- a/Gemfile +++ b/Gemfile @@ -1,8 +1,4 @@ -if gem_source = ENV['GEM_SOURCE'] - source gem_source -else - source "https://rubygems.org/" -end +source ENV['GEM_SOURCE'] || "https://rubygems.org/" # C Ruby (MRI) or Rubinius, but NOT Windows platforms :ruby do From dbf53c5652e9dea65cf4f807de4948fd937c75b9 Mon Sep 17 00:00:00 2001 From: Rob Braden Date: Thu, 13 Feb 2014 11:42:09 -0800 Subject: [PATCH 1699/3753] (fact-242)(packaging) Remove Fedora 18 from the default set of package builds --- ext/build_defaults.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/build_defaults.yaml b/ext/build_defaults.yaml index ce3e318de3..a42c1f60dc 100644 --- a/ext/build_defaults.yaml +++ b/ext/build_defaults.yaml @@ -9,7 +9,7 @@ gpg_name: 'info@puppetlabs.com' gpg_key: '4BD6EC30' sign_tar: FALSE # a space separated list of mock configs -final_mocks: 'pl-el-5-i386 pl-el-5-x86_64 pl-el-6-i386 pl-el-6-x86_64 pl-el-7-x86_64 pl-fedora-18-i386 pl-fedora-18-x86_64 pl-fedora-19-i386 pl-fedora-19-x86_64 pl-fedora-20-i386 pl-fedora-20-x86_64' +final_mocks: 'pl-el-5-i386 pl-el-5-x86_64 pl-el-6-i386 pl-el-6-x86_64 pl-el-7-x86_64 pl-fedora-19-i386 pl-fedora-19-x86_64 pl-fedora-20-i386 pl-fedora-20-x86_64' yum_host: 'yum.puppetlabs.com' yum_repo_path: '/opt/repository/yum/' build_gem: TRUE From c9299dae8638cf03f3d5fe773f82c7f880a01e33 Mon Sep 17 00:00:00 2001 From: Rob Braden Date: Thu, 13 Feb 2014 11:42:09 -0800 Subject: [PATCH 1700/3753] (fact-242)(packaging) Remove Fedora 18 from the default set of package builds --- ext/build_defaults.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/build_defaults.yaml b/ext/build_defaults.yaml index ce3e318de3..a42c1f60dc 100644 --- a/ext/build_defaults.yaml +++ b/ext/build_defaults.yaml @@ -9,7 +9,7 @@ gpg_name: 'info@puppetlabs.com' gpg_key: '4BD6EC30' sign_tar: FALSE # a space separated list of mock configs -final_mocks: 'pl-el-5-i386 pl-el-5-x86_64 pl-el-6-i386 pl-el-6-x86_64 pl-el-7-x86_64 pl-fedora-18-i386 pl-fedora-18-x86_64 pl-fedora-19-i386 pl-fedora-19-x86_64 pl-fedora-20-i386 pl-fedora-20-x86_64' +final_mocks: 'pl-el-5-i386 pl-el-5-x86_64 pl-el-6-i386 pl-el-6-x86_64 pl-el-7-x86_64 pl-fedora-19-i386 pl-fedora-19-x86_64 pl-fedora-20-i386 pl-fedora-20-x86_64' yum_host: 'yum.puppetlabs.com' yum_repo_path: '/opt/repository/yum/' build_gem: TRUE From 92aba9038d5f4424b4495c2369b431ec3637a5e1 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 10 Mar 2014 11:50:16 -0700 Subject: [PATCH 1701/3753] (maint) Confine lsbdistdescription to depend on lsb_release --- lib/facter/lsbdistdescription.rb | 4 ++++ spec/unit/lsbdistdescription_spec.rb | 13 ++++++------- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/facter/lsbdistdescription.rb b/lib/facter/lsbdistdescription.rb index 0250b94113..409e76f91b 100644 --- a/lib/facter/lsbdistdescription.rb +++ b/lib/facter/lsbdistdescription.rb @@ -12,6 +12,10 @@ Facter.add(:lsbdistdescription) do confine :kernel => [ :linux, :"gnu/kfreebsd" ] + confine do + Facter::Core::Execution.which("lsb_release") + end + setcode do if output = Facter::Core::Execution.exec('lsb_release -d -s 2>/dev/null') # the output may be quoted (at least it is on gentoo) diff --git a/spec/unit/lsbdistdescription_spec.rb b/spec/unit/lsbdistdescription_spec.rb index bca930eb53..8a4d7c4a25 100755 --- a/spec/unit/lsbdistdescription_spec.rb +++ b/spec/unit/lsbdistdescription_spec.rb @@ -10,16 +10,15 @@ Facter.fact(:kernel).stubs(:value).returns kernel end - it "should return the description through lsb_release -d -s 2>/dev/null" do - Facter::Core::Execution.stubs(:exec).with('lsb_release -d -s 2>/dev/null').returns '"Gentoo Base System release 2.1"' - Facter.fact(:lsbdistdescription).value.should == 'Gentoo Base System release 2.1' + it "returns the description through lsb_release -d -s 2>/dev/null" do + Facter::Core::Execution.impl.stubs(:exec).with('lsb_release -d -s 2>/dev/null', anything).returns '"Gentoo Base System release 2.1"' + expect(Facter.fact(:lsbdistdescription).value).to eq 'Gentoo Base System release 2.1' end - it "should return nil if lsb_release is not installed" do - Facter::Core::Execution.stubs(:exec).with('lsb_release -d -s 2>/dev/null').returns nil - Facter.fact(:lsbdistdescription).value.should be_nil + it "returns nil if lsb_release is not installed" do + Facter::Core::Execution.stubs(:which).with('lsb_release').returns nil + expect(Facter.fact(:lsbdistdescription).value).to be_nil end end end - end From aa1881a90b27b2fe4644b3eac7242bfe33cb1bca Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 10 Mar 2014 12:09:37 -0700 Subject: [PATCH 1702/3753] (maint) Stub all confines for lsbdistdescription on Solaris --- spec/unit/lsbdistdescription_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/unit/lsbdistdescription_spec.rb b/spec/unit/lsbdistdescription_spec.rb index 8a4d7c4a25..cf4f672838 100755 --- a/spec/unit/lsbdistdescription_spec.rb +++ b/spec/unit/lsbdistdescription_spec.rb @@ -11,7 +11,8 @@ end it "returns the description through lsb_release -d -s 2>/dev/null" do - Facter::Core::Execution.impl.stubs(:exec).with('lsb_release -d -s 2>/dev/null', anything).returns '"Gentoo Base System release 2.1"' + Facter::Core::Execution.stubs(:which).with('lsb_release').returns '/usr/bin/lsb_release' + Facter::Core::Execution.stubs(:exec).with('lsb_release -d -s 2>/dev/null', anything).returns '"Gentoo Base System release 2.1"' expect(Facter.fact(:lsbdistdescription).value).to eq 'Gentoo Base System release 2.1' end From 3280be490091869dbf07c5ad37fea432b2a51db7 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 10 Mar 2014 12:16:06 -0700 Subject: [PATCH 1703/3753] (maint) Update gid specs to respect exec args --- spec/unit/gid_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/unit/gid_spec.rb b/spec/unit/gid_spec.rb index 5cd877c273..7a63bd8e1f 100755 --- a/spec/unit/gid_spec.rb +++ b/spec/unit/gid_spec.rb @@ -5,7 +5,7 @@ describe "on systems with id" do it "should return the current group" do Facter::Core::Execution.expects(:which).with('id').returns(true) - Facter::Core::Execution.expects(:exec).once.with('id -ng').returns 'bar' + Facter::Core::Execution.expects(:exec).once.with('id -ng', anything).returns 'bar' Facter.fact(:gid).value.should == 'bar' end From 129242762fbab469a1da5d60cbb602337776bdb9 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 10 Mar 2014 12:35:43 -0700 Subject: [PATCH 1704/3753] (maint) Return nil on dns command lookup fail This updates the domain fact to ignore the output of nonexistent commands, rather than raising an error. While it would be better to refactor the domain fact into smaller resolutions that can be confined based on the presence of the command that's a more ambitious refactor than what I can do right now. --- lib/facter/domain.rb | 4 ++-- spec/unit/domain_spec.rb | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/facter/domain.rb b/lib/facter/domain.rb index ac9aa686f9..3c2dc4c7c4 100644 --- a/lib/facter/domain.rb +++ b/lib/facter/domain.rb @@ -41,11 +41,11 @@ basic_hostname end - if name = Facter::Core::Execution.exec(hostname_command) \ + if name = Facter::Core::Execution.exec(hostname_command, :on_fail => nil) \ and name =~ /.*?\.(.+$)/ return_value = $1 - elsif Facter.value(:kernel) != "windows" and domain = Facter::Core::Execution.exec('dnsdomainname 2> /dev/null') \ + elsif Facter.value(:kernel) != "windows" and domain = Facter::Core::Execution.exec('dnsdomainname 2> /dev/null', :on_fail => nil) \ and domain =~ /.+/ return_value = domain diff --git a/spec/unit/domain_spec.rb b/spec/unit/domain_spec.rb index c3701d51cb..f7dae51776 100755 --- a/spec/unit/domain_spec.rb +++ b/spec/unit/domain_spec.rb @@ -24,11 +24,11 @@ def resolv_conf_contains(*lines) let(:dnsdomain_command) { "dnsdomainname 2> /dev/null" } def the_hostname_is(value) - Facter::Core::Execution.stubs(:exec).with(hostname_command).returns(value) + Facter::Core::Execution.stubs(:exec).with(hostname_command, {:on_fail => nil}).returns(value) end def the_dnsdomainname_is(value) - Facter::Core::Execution.stubs(:exec).with(dnsdomain_command).returns(value) + Facter::Core::Execution.stubs(:exec).with(dnsdomain_command, {:on_fail => nil}).returns(value) end before do @@ -197,7 +197,7 @@ def expects_dnsdomains(domains) it "should return nil" do expects_dnsdomains([nil]) - Facter::Core::Execution.stubs(:exec).with(hostname_command).returns('sometest') + Facter::Core::Execution.stubs(:exec).with(hostname_command, {:on_fail => nil}).returns('sometest') FileTest.stubs(:exists?).with("/etc/resolv.conf").returns(false) Facter.fact(:domain).value.should be_nil @@ -288,8 +288,8 @@ def expects_dnsdomains(domains) describe scenario[:scenario] do before(:each) do - Facter::Core::Execution.stubs(:exec).with("hostname -f 2> /dev/null").returns(scenario[:hostname]) - Facter::Core::Execution.stubs(:exec).with("dnsdomainname 2> /dev/null").returns(scenario[:dnsdomainname]) + Facter::Core::Execution.stubs(:exec).with("hostname -f 2> /dev/null", {:on_fail => nil}).returns(scenario[:hostname]) + Facter::Core::Execution.stubs(:exec).with("dnsdomainname 2> /dev/null", {:on_fail => nil}).returns(scenario[:dnsdomainname]) resolv_conf_contains( "search #{scenario[:resolve_search]}", "domain #{scenario[:resolve_domain]}" From cf899a357175a482c89b53fa3cf9ca79e5338927 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 10 Mar 2014 12:35:43 -0700 Subject: [PATCH 1705/3753] (maint) Return nil on sysctl command lookup fail See also 7fc668519f8e. --- lib/facter/util/uptime.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/util/uptime.rb b/lib/facter/util/uptime.rb index 4df1cb0e27..7f74c2ff53 100644 --- a/lib/facter/util/uptime.rb +++ b/lib/facter/util/uptime.rb @@ -28,7 +28,7 @@ def self.uptime_proc_uptime end def self.uptime_sysctl - output = Facter::Core::Execution.exec("#{uptime_sysctl_cmd} 2>/dev/null") + output = Facter::Core::Execution.exec("#{uptime_sysctl_cmd} 2>/dev/null", :on_fail => nil) if not output.empty? compute_uptime(Time.at(output.match(/\d+/)[0].to_i)) end From e6f453be70b9afd43b127de9ce22bcadd13adf08 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 10 Mar 2014 17:20:14 -0700 Subject: [PATCH 1706/3753] (maint) Return nil on uptime command fail --- lib/facter/util/uptime.rb | 6 +++--- spec/unit/util/uptime_spec.rb | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/facter/util/uptime.rb b/lib/facter/util/uptime.rb index 7f74c2ff53..4ec592eeaf 100644 --- a/lib/facter/util/uptime.rb +++ b/lib/facter/util/uptime.rb @@ -29,15 +29,15 @@ def self.uptime_proc_uptime def self.uptime_sysctl output = Facter::Core::Execution.exec("#{uptime_sysctl_cmd} 2>/dev/null", :on_fail => nil) - if not output.empty? + if output compute_uptime(Time.at(output.match(/\d+/)[0].to_i)) end end def self.uptime_executable - output = Facter::Core::Execution.exec("#{uptime_executable_cmd} 2>/dev/null") + output = Facter::Core::Execution.exec("#{uptime_executable_cmd} 2>/dev/null", :on_fail => nil) - if not output.empty? + if output up=0 if output =~ /(\d+) day(?:s|\(s\))?,\s+(\d+):(\d+)/ # Regexp handles Solaris, AIX, HP-UX, and Tru64. diff --git a/spec/unit/util/uptime_spec.rb b/spec/unit/util/uptime_spec.rb index 06472a0796..a6940edb83 100755 --- a/spec/unit/util/uptime_spec.rb +++ b/spec/unit/util/uptime_spec.rb @@ -99,14 +99,14 @@ test_cases.each do |uptime, expected| it "should return #{expected} for #{uptime}" do - Facter::Core::Execution.stubs(:exec).with('uptime 2>/dev/null').returns(uptime) + Facter::Core::Execution.stubs(:exec).with('uptime 2>/dev/null', {:on_fail => nil}).returns(uptime) Facter.fact(:uptime_seconds).value.should == expected end end describe "nor is 'uptime' command" do before :each do - Facter::Util::Uptime.stubs(:uptime_executable_cmd).returns("cat \"#{@nonexistent_file}\"") + Facter::Core::Execution.stubs(:exec).with('uptime 2>/dev/null', {:on_fail => nil}).returns(nil) end it "should return nil" do From f8365356d6f339f878643190ddc269e371d7a9b7 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 10 Mar 2014 17:20:14 -0700 Subject: [PATCH 1707/3753] (maint) Return nil on zoneadm command fail --- lib/facter/util/solaris_zones.rb | 8 ++++---- spec/unit/util/solaris_zones_spec.rb | 4 ++-- spec/unit/zones_spec.rb | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/facter/util/solaris_zones.rb b/lib/facter/util/solaris_zones.rb index e6b287d87c..e0c4d5e3e4 100644 --- a/lib/facter/util/solaris_zones.rb +++ b/lib/facter/util/solaris_zones.rb @@ -93,7 +93,7 @@ def add_dynamic_facts # # @return [Hash] the parsed output of the zoneadm command def refresh - @zoneadm_output = Facter::Core::Execution.exec(zoneadm_cmd) + @zoneadm_output = Facter::Core::Execution.exec(zoneadm_cmd, {:on_fail => nil}) parse! end @@ -103,11 +103,11 @@ def refresh # # @api private def parse! - if @zoneadm_output.empty? + if @zoneadm_output + rows = @zoneadm_output.split("\n").collect { |line| line.split(':') } + else Facter.debug "Cannot parse zone facts, #{zoneadm_cmd} returned no output" rows = [] - else - rows = @zoneadm_output.split("\n").collect { |line| line.split(':') } end @zone_hash = rows.inject({}) do |memo, fields| diff --git a/spec/unit/util/solaris_zones_spec.rb b/spec/unit/util/solaris_zones_spec.rb index c7d4a05ff2..c444c8e427 100644 --- a/spec/unit/util/solaris_zones_spec.rb +++ b/spec/unit/util/solaris_zones_spec.rb @@ -47,7 +47,7 @@ describe '#refresh' do it 'executes the zoneadm_cmd' do - Facter::Core::Execution.expects(:exec).with(subject.zoneadm_cmd).returns(zone_list) + Facter::Core::Execution.expects(:exec).with(subject.zoneadm_cmd, {:on_fail => nil}).returns(zone_list) subject.refresh end end @@ -112,7 +112,7 @@ def given_initial_zone_facts Facter::Core::Execution.stubs(:exec). - with(subject.zoneadm_cmd). + with(subject.zoneadm_cmd, {:on_fail => nil}). returns(zone_list) described_class.add_facts end diff --git a/spec/unit/zones_spec.rb b/spec/unit/zones_spec.rb index 55e3d14b2d..11563f4ab9 100644 --- a/spec/unit/zones_spec.rb +++ b/spec/unit/zones_spec.rb @@ -10,7 +10,7 @@ -:local:configured:/::native:shared -:zoneA:stopped:/::native:shared EOF - Facter::Core::Execution.stubs(:exec).with('/usr/sbin/zoneadm list -cp').returns(zone_list) + Facter::Core::Execution.stubs(:exec).with('/usr/sbin/zoneadm list -cp', {:on_fail => nil}).returns(zone_list) Facter.collection.internal_loader.load(:zones) end From 59cc3ba7f649ac07576975615f7a719b4a9b9c9b Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 10 Mar 2014 17:53:10 -0700 Subject: [PATCH 1708/3753] (maint) Restrict rackspace facts based on executable --- lib/facter/rackspace.rb | 4 ++++ spec/unit/rackspace_spec.rb | 2 ++ 2 files changed, 6 insertions(+) diff --git a/lib/facter/rackspace.rb b/lib/facter/rackspace.rb index 7ade15ebe0..42ad773f8f 100644 --- a/lib/facter/rackspace.rb +++ b/lib/facter/rackspace.rb @@ -8,6 +8,10 @@ # Facter.add(:is_rsc) do + confine do + Facter::Core::Execution.which("/usr/bin/xenstore-read") + end + setcode do result = Facter::Core::Execution.exec("/usr/bin/xenstore-read vm-data/provider_data/provider") if result == "Rackspace" diff --git a/spec/unit/rackspace_spec.rb b/spec/unit/rackspace_spec.rb index 977c09d3cb..0794c60e8f 100644 --- a/spec/unit/rackspace_spec.rb +++ b/spec/unit/rackspace_spec.rb @@ -7,6 +7,7 @@ describe "on Rackspace Cloud" do before :each do Facter.collection.internal_loader.load(:rackspace) + Facter::Core::Execution.stubs(:which).returns '/usr/bin/xenstore-read' end it "should set is_rsc to true" do @@ -30,6 +31,7 @@ describe "not on Rackspace Cloud" do before do Facter.collection.internal_loader.load(:rackspace) + Facter::Core::Execution.stubs(:which).returns nil end it "shouldn't set is_rsc" do From 8e5e9e9eda63e113e2053963c3da927cfe62ca6a Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 11 Mar 2014 15:44:35 -0700 Subject: [PATCH 1709/3753] (FACT-379) Don't return VLANs when only header is present On CentOS 5 the vlans file is always present regardless of if any vlans is defined, which behaves like there are VLANs defined but doesn't match any regexps. This commit explicitly ensures that we have a non-empty list of VLANs before returning anything. --- lib/facter/util/vlans.rb | 2 +- spec/fixtures/unit/util/vlans/centos-5-no-vlans | 2 ++ spec/unit/util/vlans_spec.rb | 5 +++++ 3 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 spec/fixtures/unit/util/vlans/centos-5-no-vlans diff --git a/lib/facter/util/vlans.rb b/lib/facter/util/vlans.rb index e42a28f5f0..5082380a95 100644 --- a/lib/facter/util/vlans.rb +++ b/lib/facter/util/vlans.rb @@ -15,7 +15,7 @@ def self.get_vlans vlans << match[2] if match[2] end end - vlans.join(',') + vlans.join(',') unless vlans.empty? end end end diff --git a/spec/fixtures/unit/util/vlans/centos-5-no-vlans b/spec/fixtures/unit/util/vlans/centos-5-no-vlans new file mode 100644 index 0000000000..22028c8afc --- /dev/null +++ b/spec/fixtures/unit/util/vlans/centos-5-no-vlans @@ -0,0 +1,2 @@ +VLAN Dev name | VLAN ID +Name-Type: VLAN_NAME_TYPE_RAW_PLUS_VID_NO_PAD diff --git a/spec/unit/util/vlans_spec.rb b/spec/unit/util/vlans_spec.rb index caed3a62bb..989c914b75 100755 --- a/spec/unit/util/vlans_spec.rb +++ b/spec/unit/util/vlans_spec.rb @@ -33,5 +33,10 @@ Facter::Util::Vlans.stubs(:get_vlan_config).returns(nil) expect(Facter::Util::Vlans.get_vlans()).to be_nil end + + it "returns nil when only the vlan header is returned" do + Facter::Util::Vlans.stubs(:get_vlan_config).returns(my_fixture_read("centos-5-no-vlans")) + expect(Facter::Util::Vlans.get_vlans()).to be_nil + end end end From f8c124dfff1a6e2e5163cf00fb4b1ee353377090 Mon Sep 17 00:00:00 2001 From: Ryan McKern Date: Wed, 12 Mar 2014 10:38:32 -0700 Subject: [PATCH 1710/3753] (packaging) Update FACTERVERSION to 2.0.1-rc2 --- lib/facter/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/version.rb b/lib/facter/version.rb index ac194ae945..0d72561341 100644 --- a/lib/facter/version.rb +++ b/lib/facter/version.rb @@ -1,6 +1,6 @@ module Facter if not defined? FACTERVERSION then - FACTERVERSION = '2.0.1-rc1' + FACTERVERSION = '2.0.1-rc2' end # Returns the running version of Facter. From 6d9b76988d7c299fd965dbc5ccbb5be5fbae188b Mon Sep 17 00:00:00 2001 From: Dan Lidral-Porter Date: Wed, 12 Mar 2014 11:14:46 -0700 Subject: [PATCH 1711/3753] Include std iterators --- lib/cfacterimpl.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/cfacterimpl.cc b/lib/cfacterimpl.cc index 1622d185bc..e8cba536e3 100644 --- a/lib/cfacterimpl.cc +++ b/lib/cfacterimpl.cc @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include From 86c8b00d05499fe735e6e7574c917e70b1213626 Mon Sep 17 00:00:00 2001 From: Dan Lidral-Porter Date: Wed, 12 Mar 2014 11:17:06 -0700 Subject: [PATCH 1712/3753] Let strftime use entire tzstring array. The second argument (`maxsize`) to strftime includes the terminating null char, so we can just give it the size of tzstring. Not that this fixes the glaring problem with this function, but at least cfacter compiles now! --- lib/cfacterimpl.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cfacterimpl.cc b/lib/cfacterimpl.cc index e8cba536e3..bf2dd531c6 100644 --- a/lib/cfacterimpl.cc +++ b/lib/cfacterimpl.cc @@ -447,7 +447,7 @@ void get_misc_facts(fact_map& facts) time_t t = time(NULL); struct tm loc; localtime_r(&t, &loc); - strftime(tzstring, sizeof(tzstring - 1), "%Z", &loc); + strftime(tzstring, sizeof(tzstring), "%Z", &loc); facts["timezone"] = tzstring; } From 00817d7bbdc8a1c1f72a00b48a8eea33dc77bd59 Mon Sep 17 00:00:00 2001 From: Garrett Honeycutt Date: Tue, 21 Jan 2014 00:03:10 -0500 Subject: [PATCH 1713/3753] Enable fash finish in Travis http://blog.travis-ci.com/2013-11-27-fast-finishing-builds/ --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 52dfb9e672..2342bc0641 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,7 @@ rvm: - 2.0.0 - ruby-head matrix: + fast_finish: true allow_failures: - rvm: ruby-head - rvm: 2.0.0 From d13d937ea19f9be06a8742ddefabdd83cbfb5707 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Sun, 16 Mar 2014 13:57:23 -0700 Subject: [PATCH 1714/3753] Convert some code to idiomatic C++11 using range for and nonmember begin/end --- lib/cfacterimpl.cc | 12 ++++++------ lib/cfacterlib.cc | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/cfacterimpl.cc b/lib/cfacterimpl.cc index bf2dd531c6..9a84aca45f 100644 --- a/lib/cfacterimpl.cc +++ b/lib/cfacterimpl.cc @@ -644,8 +644,8 @@ void get_ssh_facts(fact_map& facts) ssh_facts["sshrsakey"] = "ssh_host_rsa_key.pub"; ssh_facts["sshecdsakey"] = "ssh_host_ecdsa_key.pub"; - for (auto i = ssh_facts.begin(); i != ssh_facts.end(); ++i) { - get_ssh_fact(i->first, i->second, facts); + for (auto i : ssh_facts) { + get_ssh_fact(i.first, i.second, facts); } } @@ -668,8 +668,8 @@ static void get_physicalprocessorcount_fact(fact_map& facts) package_ids.push_back(read_oneline_file(cpu_phys_file)); } - sort(package_ids.begin(), package_ids.end()); - unique(package_ids.begin(), package_ids.end()); + sort(begin(package_ids), end(package_ids)); + unique(begin(package_ids), end(package_ids)); facts["physicalprocessorcount"] = std::to_string(package_ids.size()); } else { // here's where the fall back to /proc/cpuinfo would go @@ -932,7 +932,7 @@ static void get_external_facts(fact_map& facts, std::string directory) void get_external_facts(fact_map& facts, const std::list& directories) { - for (auto iter = directories.begin(); iter != directories.end(); ++iter) { - get_external_facts(facts, *iter); + for (auto dir : directories) { + get_external_facts(facts, dir); } } diff --git a/lib/cfacterlib.cc b/lib/cfacterlib.cc index 504dc6faaa..4dca5fdc62 100644 --- a/lib/cfacterlib.cc +++ b/lib/cfacterlib.cc @@ -63,8 +63,8 @@ int to_json(char *facts_json, size_t facts_len) rapidjson::Document::AllocatorType& allocator = json.GetAllocator(); - for (auto i = facts.begin(); i != facts.end(); ++i) { - json.AddMember(i->first.c_str(), i->second.c_str(), allocator); + for (auto i : facts) { + json.AddMember(i.first.c_str(), i.second.c_str(), allocator); } rapidjson::StringBuffer buf; From b18d4ba761b405412a13e0f9f20fb3d2ff295814 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Mon, 17 Mar 2014 21:00:05 -0700 Subject: [PATCH 1715/3753] Use static stl initializers, and use const references in range-for --- lib/cfacterimpl.cc | 20 ++++++++++---------- lib/cfacterlib.cc | 5 ++--- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/lib/cfacterimpl.cc b/lib/cfacterimpl.cc index 9a84aca45f..ce8e55662e 100644 --- a/lib/cfacterimpl.cc +++ b/lib/cfacterimpl.cc @@ -608,7 +608,7 @@ void get_selinux_facts(fact_map& facts) static void get_ssh_fact(std::string fact_name, std::string path_name, fact_map& facts) { - std::string ssh_directories[] = { + std::vector ssh_directories = { "/etc/ssh", "/usr/local/etc/ssh", "/etc", @@ -616,8 +616,8 @@ static void get_ssh_fact(std::string fact_name, std::string path_name, fact_map& "/etc/opt/ssh", }; - for (size_t i = 0; i < sizeof(ssh_directories) / sizeof(std::string); ++i) { - std::string full_path = ssh_directories[i] + "/" + path_name; + for (auto const &ssh_directory : ssh_directories) { + std::string full_path = ssh_directory + "/" + path_name; if (file_exist(full_path)) { std::string key = read_oneline_file(full_path); std::vector tokens; @@ -638,13 +638,13 @@ static void get_ssh_fact(std::string fact_name, std::string path_name, fact_map& // no support for the sshfp facts, which require base64/sha1sum code void get_ssh_facts(fact_map& facts) { - // not til C++11 do we have static initialization of stl maps - std::map ssh_facts; - ssh_facts["sshdsakey"] = "ssh_host_dsa_key.pub"; - ssh_facts["sshrsakey"] = "ssh_host_rsa_key.pub"; - ssh_facts["sshecdsakey"] = "ssh_host_ecdsa_key.pub"; + std::map ssh_facts = { + { "sshdsakey", "ssh_host_dsa_key.pub" }, + { "sshrsakey", "ssh_host_rsa_key.pub" }, + { "sshecdsakey", "ssh_host_ecdsa_key.pub" }, + }; - for (auto i : ssh_facts) { + for (auto const& i : ssh_facts) { get_ssh_fact(i.first, i.second, facts); } } @@ -932,7 +932,7 @@ static void get_external_facts(fact_map& facts, std::string directory) void get_external_facts(fact_map& facts, const std::list& directories) { - for (auto dir : directories) { + for (auto const& dir : directories) { get_external_facts(facts, dir); } } diff --git a/lib/cfacterlib.cc b/lib/cfacterlib.cc index 4dca5fdc62..d61bca15c8 100644 --- a/lib/cfacterlib.cc +++ b/lib/cfacterlib.cc @@ -31,8 +31,7 @@ void loadfacts() facts["cfacterversion"] = CFACTER_VERSION; - std::list external_directories; - external_directories.push_back("/etc/facter/facts.d"); + std::list external_directories = { "/etc/facter/facts.d" }; get_network_facts(facts); get_kernel_facts(facts); @@ -63,7 +62,7 @@ int to_json(char *facts_json, size_t facts_len) rapidjson::Document::AllocatorType& allocator = json.GetAllocator(); - for (auto i : facts) { + for (auto const& i : facts) { json.AddMember(i.first.c_str(), i.second.c_str(), allocator); } From b58108ec2be0bf8ee719f4c583faaf83efe79044 Mon Sep 17 00:00:00 2001 From: Stefan Schulte Date: Thu, 9 Jan 2014 22:32:17 +0100 Subject: [PATCH 1716/3753] (fact-155) Fix operatingsystemrelease for Solaris 11 The /etc/release file has changed from Solaris 10 to Solaris 11 and because the regular expression does not match anymore the operatingsystemrelease shows the kernelrelease fact on Solaris 11. Change the regular expression to also identify Solaris 11 --- lib/facter/operatingsystemrelease.rb | 5 +++++ spec/unit/operatingsystemrelease_spec.rb | 2 ++ 2 files changed, 7 insertions(+) diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index a57abe7091..8d94ffcd49 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -181,8 +181,13 @@ setcode do if release = Facter::Util::FileRead.read('/etc/release') line = release.split("\n").first.chomp + # Solaris 10: Solaris 10 10/09 s10x_u8wos_08a X86 + # Solaris 11 (old naming scheme): Oracle Solaris 11 11/11 X86 + # Solaris 11 (new naming scheme): Oracle Solaris 11.1 SPARC if match = /\s+s(\d+)[sx]?(_u\d+)?.*(?:SPARC|X86)/.match(line) match.captures.join('') + elsif match = /Solaris ([0-9\.]+(?:\s*[0-9\.\/]+))\s*(?:SPARC|X86)/.match(line) + match.captures[0] end end end diff --git a/spec/unit/operatingsystemrelease_spec.rb b/spec/unit/operatingsystemrelease_spec.rb index 9a77dac509..93c4c87349 100755 --- a/spec/unit/operatingsystemrelease_spec.rb +++ b/spec/unit/operatingsystemrelease_spec.rb @@ -119,6 +119,8 @@ 'Solaris 10 10/09 s10x_u8wos_08a X86' => '10_u8', 'Oracle Solaris 10 9/10 s10x_u9wos_14a X86' => '10_u9', 'Oracle Solaris 10 8/11 s10x_u10wos_17b X86' => '10_u10', + 'Oracle Solaris 11 11/11 X86' => '11 11/11', + 'Oracle Solaris 11.1 SPARC' => '11.1' }.each do |fakeinput,expected_output| it "should be able to parse a release of #{fakeinput}" do Facter::Util::FileRead.stubs(:read).with('/etc/release').returns fakeinput From 49e94df1e08d26446b7dc0244c9fda602d786398 Mon Sep 17 00:00:00 2001 From: Stefan Schulte Date: Thu, 9 Jan 2014 23:22:30 +0100 Subject: [PATCH 1717/3753] (fact-155) Add operatingsystemmajrelease fact for Solaris --- lib/facter/operatingsystemmajrelease.rb | 17 ++++++++++++++-- spec/unit/operatingsystemmajrelease_spec.rb | 22 +++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/lib/facter/operatingsystemmajrelease.rb b/lib/facter/operatingsystemmajrelease.rb index f530125314..f058a9f144 100644 --- a/lib/facter/operatingsystemmajrelease.rb +++ b/lib/facter/operatingsystemmajrelease.rb @@ -2,8 +2,11 @@ # # Purpose: Returns the major release of the operating system. # -# Resolution: splits down the operatingsystemrelease fact at decimal point for -# osfamily RedHat derivatives and Debian. +# Resolution: +# Splits down the operatingsystemrelease fact at decimal point for +# osfamily RedHat derivatives and Debian. +# Uses operatingsystemrelease to the first non decimal character for +# operatingsystem Solaris # # This should be the same as lsbmajdistrelease, but on minimal systems there # are too many dependencies to use LSB @@ -13,6 +16,7 @@ #"Debian" "Fedora" "Gentoo" "Mandrake" "Mandriva" "MeeGo" "OEL" "OpenSuSE" #"OracleLinux" "OVS" "PSBM" "RedHat" "Scientific" "Slackware" "Slamd64" "SLC" #"SLED" "SLES" "SuSE" "Ubuntu" "VMWareESX" + Facter.add(:operatingsystemmajrelease) do confine :operatingsystem => [ :Amazon, @@ -31,3 +35,12 @@ Facter.value('operatingsystemrelease').split('.').first end end + +Facter.add(:operatingsystemmajrelease) do + confine :operatingsystem => :solaris + setcode do + if match = Facter.value(:operatingsystemrelease).match(/^(\d+)/) + match.captures[0] + end + end +end diff --git a/spec/unit/operatingsystemmajrelease_spec.rb b/spec/unit/operatingsystemmajrelease_spec.rb index 9c1a467437..9973b23ab3 100644 --- a/spec/unit/operatingsystemmajrelease_spec.rb +++ b/spec/unit/operatingsystemmajrelease_spec.rb @@ -13,4 +13,26 @@ end end end + + context "on Solaris operatingsystems" do + before :each do + Facter.fact(:kernel).stubs(:value).returns("SunOS") + Facter.fact(:operatingsystem).stubs(:value).returns("Solaris") + end + + it "should correctly derive from operatingsystemrelease on solaris 10" do + Facter.fact(:operatingsystemrelease).expects(:value).returns("10_u8") + Facter.fact(:operatingsystemmajrelease).value.should == "10" + end + + it "should correctly derive from operatingsystemrelease on solaris 11 (old version scheme)" do + Facter.fact(:operatingsystemrelease).expects(:value).returns("11 11/11") + Facter.fact(:operatingsystemmajrelease).value.should == "11" + end + + it "should correctly derive from operatingsystemrelease on solaris 11 (new version scheme)" do + Facter.fact(:operatingsystemrelease).expects(:value).returns("11.1") + Facter.fact(:operatingsystemmajrelease).value.should == "11" + end + end end From 9870c02979c0513d71316d15263b1511d1a619e5 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Fri, 21 Mar 2014 11:26:26 -0700 Subject: [PATCH 1718/3753] Minor code cleanup. Added scoped_descriptor for managing a socket descriptor resource. Added missing include guards. Added missing "const&" for various inferred types (auto). Added cmake generated files for XCode to gitignore. Removed naked malloc/free. Removed "std::" qualifier in .cc files in favor of "using namespace std". Removed unnecessary usages of "struct" in variable declarations. Replace usages of NULL with C++11 nullptr. Fixed version number in help output. Conflicts: lib/cfacterimpl.cc lib/cfacterlib.cc Updated: CMakeLists.txt to update cpplint target to tolerate namespaces. --- .gitignore | 6 + CMakeLists.txt | 1 + exe/cfacter.cc | 25 +-- lib/cfacterimpl.cc | 387 +++++++++++++++++++--------------------- lib/cfacterimpl.h | 7 +- lib/cfacterlib.cc | 10 +- lib/cfacterlib.h | 4 +- lib/scoped_resource.hpp | 89 +++++++++ 8 files changed, 312 insertions(+), 217 deletions(-) create mode 100644 lib/scoped_resource.hpp diff --git a/.gitignore b/.gitignore index 4a4f24f641..c0766d8527 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *.swp +.DS_Store # CMake CMakeCache.txt @@ -6,9 +7,14 @@ CMakeFiles Makefile cmake_install.cmake install_manifest.txt +/CFACTER.* +CFACTER.build +CMakeScripts # Generated files /version.h /exe/cfacter +/exe/Debug /lib/libcfacter.so /lib/libcfacter.dylib +/lib/Debug diff --git a/CMakeLists.txt b/CMakeLists.txt index 05833fb5cc..c50df8c9c8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,7 @@ if (NOT PYTHONINTERP_FOUND) else() set(CPPLINT_FILTER "-build/include" # Why? + "-build/namespaces" # What's a namespace to do "-legal/copyright" # Not yet "-runtime/references" # Not sure about this religion "-readability/streams" # What? diff --git a/exe/cfacter.cc b/exe/cfacter.cc index b7d4f82c70..dcc1ba2845 100644 --- a/exe/cfacter.cc +++ b/exe/cfacter.cc @@ -1,11 +1,13 @@ #include "cfacterlib.h" - +#include "../version.h" #include #include +using namespace std; + void help() { - std::cout << + cout << "Synopsis\n" "========\n" "\n" @@ -39,21 +41,22 @@ void help() void version() { - std::cout << "0.0.1" << std::endl; + cout << CFACTER_VERSION << endl; } int main(int argc, char **argv) { + // TODO: use Boost.Program_options? static struct option long_options[] = { - {"help", no_argument, NULL, 'h'}, - {"json", no_argument, NULL, 'j'}, - {"version", no_argument, NULL, 'v'}, - {NULL, 0, NULL, 0} + {"help", no_argument, nullptr, 'h'}, + {"json", no_argument, nullptr, 'j'}, + {"version", no_argument, nullptr, 'v'}, + {nullptr, 0, nullptr, 0} }; // loop over all of the options int ch; - while ((ch = getopt_long(argc, argv, "hjv", long_options, NULL)) != -1) { + while ((ch = getopt_long(argc, argv, "hjv", long_options, nullptr)) != -1) { // check to see if a single character or long option came through switch (ch) { // short option 'v' @@ -80,16 +83,16 @@ int main(int argc, char **argv) if (optind == argc) { // display all facts if (to_json(facts_json, MAX_LEN_FACTS_JSON_STRING) < 0) { - std::cout << "Wow, that's a lot of facts" << std::endl; + cout << "Wow, that's a lot of facts" << endl; exit(1); } - std::cout << facts_json << std::endl; + cout << facts_json << endl; } else { // display requested fact(s) while (optind < argc) { // fix me if (value(argv[optind], facts_json, MAX_LEN_FACTS_JSON_STRING) == 0) - std::cout << facts_json << std::endl; + cout << facts_json << endl; ++optind; } } diff --git a/lib/cfacterimpl.cc b/lib/cfacterimpl.cc index ce8e55662e..0fa9eb0e7e 100644 --- a/lib/cfacterimpl.cc +++ b/lib/cfacterimpl.cc @@ -32,11 +32,15 @@ #include "cfacterlib.h" #include "cfacterimpl.h" +#include "scoped_resource.hpp" + +using namespace std; +using namespace cfacter; // For case-insensitive strings, define ci_string // Thank you, Herb Sutter: http://www.gotw.ca/gotw/029.htm // -struct ci_char_traits : public std::char_traits +struct ci_char_traits : public char_traits // just inherit all the other functions // that we don't need to override { @@ -55,17 +59,14 @@ struct ci_char_traits : public std::char_traits return toupper(c1) < toupper(c2); } - static int compare(const char* s1, - const char* s2, - size_t n) + static int compare(char const* s1, char const* s2, size_t n) { return strncasecmp(s1, s2, n); // if available on your compiler, // otherwise you can roll your own } - static const char* - find(const char* s, int n, char a) + static char const* find(char const* s, int n, char a) { while (n-- > 0 && toupper(*s) != toupper(a)) { ++s; @@ -74,164 +75,155 @@ struct ci_char_traits : public std::char_traits } }; -typedef std::basic_string ci_string; +typedef basic_string ci_string; // trim from start -static inline std::string <rim(std::string &s) +static inline string& ltrim(string& s) { - s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun(std::isspace)))); + s.erase(s.begin(), find_if(s.begin(), s.end(), not1(ptr_fun(isspace)))); return s; } // trim from end -static inline std::string &rtrim(std::string &s) +static inline string& rtrim(string& s) { - s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun(std::isspace))).base(), s.end()); + s.erase(find_if(s.rbegin(), s.rend(), not1(ptr_fun(isspace))).base(), s.end()); return s; } // trim from both ends -static inline std::string &trim(std::string &s) +static inline string& trim(string& s) { return ltrim(rtrim(s)); } -static inline void tokenize(std::string &s, std::vector &tokens) +static inline void tokenize(string const& s, vector& tokens) { - std::istringstream iss(s); - copy(std::istream_iterator(iss), - std::istream_iterator(), - std::back_inserter >(tokens)); + istringstream iss(s); + copy(istream_iterator(iss), + istream_iterator(), + back_inserter >(tokens)); } -static inline void split(const std::string &s, char delim, std::vector &elems) +static inline void split(string const& s, char delim, vector& elems) { - std::stringstream ss(s); - std::string item; + stringstream ss(s); + string item; while (getline(ss, item, delim)) { elems.push_back(item); } } -static bool file_exist(std::string filename) +static bool file_exist(string const& filename) { struct stat buffer; return stat (filename.c_str(), &buffer) == 0; } // handy for some /proc and /sys files -std::string read_oneline_file(const std::string file_path) +string read_oneline_file(string const& file_path) { - std::ifstream oneline_file(file_path.c_str(), std::ifstream::in); - std::string line; - std::getline(oneline_file, line); + ifstream oneline_file(file_path.c_str(), ifstream::in); + string line; + getline(oneline_file, line); return line; } void get_network_facts(fact_map& facts) { - struct ifreq *ifr; - struct ifconf ifc; - int s, i; - int numif; - - // find number of interfaces. - memset(&ifc, 0, sizeof(ifc)); - ifc.ifc_ifcu.ifcu_req = NULL; - ifc.ifc_len = 0; + scoped_descriptor sock(socket(AF_INET, SOCK_DGRAM, 0)); - if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + if (sock < 0) { perror("socket"); exit(1); } - if (ioctl(s, SIOCGIFCONF, &ifc) < 0) { + // find number of interfaces. + ifconf ifc = {}; + if (ioctl(sock, SIOCGIFCONF, &ifc) < 0) { perror("ioctl"); exit(1); } - if ((ifr = static_cast(malloc(ifc.ifc_len))) == NULL) { - perror("malloc"); - exit(1); - } - ifc.ifc_ifcu.ifcu_req = ifr; + vector ifreqs(ifc.ifc_len / sizeof(ifreq)); + ifc.ifc_ifcu.ifcu_req = ifreqs.data(); - if (ioctl(s, SIOCGIFCONF, &ifc) < 0) { + if (ioctl(sock, SIOCGIFCONF, &ifc) < 0) { perror("ioctl SIOCGIFCONF"); exit(1); } - std::string interfaces = ""; + string interfaces = ""; bool primaryInterfacePrinted = false; - numif = ifc.ifc_len / sizeof(struct ifreq); - for (i = 0; i < numif; i++) { - struct ifreq *r = &ifr[i]; - struct in_addr ip_addr = ((struct sockaddr_in *)&r->ifr_addr)->sin_addr; + for (auto& req : ifreqs) { + auto const& ip_addr = reinterpret_cast(&req.ifr_addr)->sin_addr; // no idea what the real algorithm is to identify the unmarked // interface, i.e. the one that facter reports as just 'ipaddress' // here just take the first one that's not 'lo' bool primaryInterface = false; - if (!primaryInterfacePrinted && strcmp(r->ifr_name, "lo")) { + if (!primaryInterfacePrinted && strcmp(req.ifr_name, "lo")) { // this is the chosen interface primaryInterface = true; primaryInterfacePrinted = true; } - // build up 'interfaces' fact as we go, appending comma after all but last - interfaces += r->ifr_name; - if (i < numif - 1) - interfaces += ","; + // build up 'interfaces' fact as we go + if (!interfaces.empty()) { + interfaces += ","; + } + interfaces += req.ifr_name; - const char *ipaddress = inet_ntoa(ip_addr); - facts[std::string("ipaddress_") + r->ifr_name] = ipaddress; - if (primaryInterface) + char const* ipaddress = inet_ntoa(ip_addr); + facts[string("ipaddress_") + req.ifr_name] = ipaddress; + if (primaryInterface) { facts["ipaddress"] = ipaddress; + } // mtu - if (ioctl(s, SIOCGIFMTU, r) < 0) { + if (ioctl(sock, SIOCGIFMTU, &req) < 0) { perror("ioctl SIOCGIFMTU"); exit(1); } - facts[std::string("mtu_") + r->ifr_name] = std::to_string(r->ifr_mtu); + facts[string("mtu_") + req.ifr_name] = to_string(req.ifr_mtu); if (primaryInterface) {} // no unmarked version of this network fact // netmask and network are both derived from the same ioctl - if (ioctl(s, SIOCGIFNETMASK, r) < 0) { + if (ioctl(sock, SIOCGIFNETMASK, &req) < 0) { perror("ioctl SIOCGIFNETMASK"); exit(1); } #ifndef __APPLE__ // ifr_netmask isn't supported, might need to use SC library // netmask - struct in_addr netmask_addr = ((struct sockaddr_in *)&r->ifr_netmask)->sin_addr; + auto const& netmask_addr = reinterpret_cast(&req.ifr_netmask)->sin_addr; const char *netmask = inet_ntoa(netmask_addr); - facts[std::string("netmask_") + r->ifr_name] = netmask; + facts[string("netmask_") + req.ifr_name] = netmask; if (primaryInterface) facts["netmask"] = netmask; // mess of casting to get the network address - struct in_addr network_addr; + in_addr network_addr; network_addr.s_addr = (in_addr_t) uint32_t(netmask_addr.s_addr) & uint32_t(ip_addr.s_addr); - std::string network = inet_ntoa(network_addr); - facts[std::string("network_") + r->ifr_name] = network; + string network = inet_ntoa(network_addr); + facts[string("network_") + req.ifr_name] = network; if (primaryInterface) facts["network"] = network; #endif #ifndef __APPLE__ // SIOCGIFHWADDR isn't supported, might need to use SC library // and the mac address (but not for loopback) - if (strcmp(r->ifr_name, "lo")) { + if (strcmp(req.ifr_name, "lo")) { if (ioctl(s, SIOCGIFHWADDR, r) < 0) { perror("ioctl SIOCGIFHWADDR"); exit(1); } // extract mac into a string, okay a char array - uint8_t *mac_bytes = reinterpret_cast((reinterpret_cast(&r->ifr_hwaddr))->sa_data); + uint8_t *mac_bytes = reinterpret_cast((reinterpret_cast(&req.ifr_hwaddr))->sa_data); char mac_address[18]; snprintf(mac_address, sizeof(mac_address), "%02x:%02x:%02x:%02x:%02x:%02x", @@ -239,7 +231,7 @@ void get_network_facts(fact_map& facts) mac_bytes[3], mac_bytes[4], mac_bytes[5]); // and get it out - facts[std::string("macaddress_") + r->ifr_name] = mac_address; + facts[string("macaddress_") + req.ifr_name] = mac_address; if (primaryInterface) facts["macaddress"] = mac_address; } @@ -247,9 +239,6 @@ void get_network_facts(fact_map& facts) } facts["interfaces"] = interfaces; - - close(s); - free(ifr); } void get_kernel_facts(fact_map& facts) @@ -257,11 +246,11 @@ void get_kernel_facts(fact_map& facts) #ifdef __linux__ // this is linux-only, so there you have it facts["kernel"] = "Linux"; - std::string kernelrelease = read_oneline_file("/proc/sys/kernel/osrelease"); + string kernelrelease = read_oneline_file("/proc/sys/kernel/osrelease"); facts["kernelrelease"] = kernelrelease; - std::string kernelversion = kernelrelease.substr(0, kernelrelease.find("-")); + string kernelversion = kernelrelease.substr(0, kernelrelease.find("-")); facts["kernelversion"] = kernelversion; - std::string kernelmajversion = kernelversion.substr(0, kernelversion.rfind(".")); + string kernelmajversion = kernelversion.substr(0, kernelversion.rfind(".")); facts["kernelmajversion"] = kernelmajversion; #else #ifdef __APPLE__ @@ -272,12 +261,12 @@ void get_kernel_facts(fact_map& facts) static void get_lsb_facts(fact_map& facts) { - std::ifstream lsb_release_file("/etc/lsb-release", std::ifstream::in); - std::string line; - while (std::getline(lsb_release_file, line)) { + ifstream lsb_release_file("/etc/lsb-release", ifstream::in); + string line; + while (getline(lsb_release_file, line)) { unsigned sep = line.find("="); - std::string key = line.substr(0, sep); - std::string value = line.substr(sep + 1, std::string::npos); + string key = line.substr(0, sep); + string value = line.substr(sep + 1, string::npos); if (key == "DISTRIB_ID") { facts["lsbdistid"] = value; @@ -302,8 +291,8 @@ static void get_redhat_facts(fact_map& facts) { if (file_exist("/etc/redhat-release")) { facts["osfamily"] = "RedHat"; - std::string redhat_release = read_oneline_file("/etc/redhat-release"); - std::vector tokens; + string redhat_release = read_oneline_file("/etc/redhat-release"); + vector tokens; tokenize(redhat_release, tokens); if (tokens.size() >= 2 && tokens[0] == "Fedora" && tokens[1] == "release") { facts["operatingsystem"] = "Fedora"; @@ -325,21 +314,21 @@ void get_operatingsystem_facts(fact_map& facts) void get_uptime_facts(fact_map& facts) { - std::string uptime = read_oneline_file("/proc/uptime"); + string uptime = read_oneline_file("/proc/uptime"); unsigned int uptime_seconds; sscanf(uptime.c_str(), "%ud", &uptime_seconds); unsigned int uptime_hours = uptime_seconds / 3600; unsigned int uptime_days = uptime_hours / 24; - facts["uptime_seconds"] = std::to_string(uptime_seconds); - facts["uptime_hours"] = std::to_string(uptime_hours); - facts["uptime_days"] = std::to_string(uptime_days); - facts["uptime"] = std::to_string(uptime_days) + " days"; + facts["uptime_seconds"] = to_string(uptime_seconds); + facts["uptime_hours"] = to_string(uptime_hours); + facts["uptime_days"] = to_string(uptime_days); + facts["uptime"] = to_string(uptime_days) + " days"; } -std::string popen_stdout(std::string cmd) +string popen_stdout(string const& cmd) { FILE *cmd_fd = popen(cmd.c_str(), "r"); - std::string cmd_output = ""; + string cmd_output = ""; char buf[1024]; size_t bytesRead; while ((bytesRead = fread(buf, 1, sizeof(buf) - 1, cmd_fd))) { @@ -388,21 +377,21 @@ void get_ruby_lib_versions(fact_map& facts) // block devices void get_blockdevice_facts(fact_map& facts) { - std::string blockdevices = ""; + string blockdevices = ""; - DIR *sys_block_dir = opendir("/sys/block"); - if (sys_block_dir == NULL) { + DIR* sys_block_dir = opendir("/sys/block"); + if (!sys_block_dir) { return; } - struct dirent *bd; + dirent* bd; while ((bd = readdir(sys_block_dir))) { bool real_block_device = false; - std::string device_dir_path = "/sys/block/"; + string device_dir_path = "/sys/block/"; device_dir_path += bd->d_name; DIR *device_dir = opendir(device_dir_path.c_str()); - struct dirent *subdir; + dirent* subdir; while ((subdir = readdir(device_dir))) { if (strcmp(subdir->d_name, "device") == 0) { // we have a winner @@ -418,19 +407,19 @@ void get_blockdevice_facts(fact_map& facts) blockdevices += ","; blockdevices += bd->d_name; - std::string model_file = "/sys/block/" + std::string(bd->d_name) + "/device/model"; - facts[std::string("blockdevice_") + bd->d_name + "_model"] = + string model_file = "/sys/block/" + string(bd->d_name) + "/device/model"; + facts[string("blockdevice_") + bd->d_name + "_model"] = read_oneline_file(model_file); - std::string vendor_file = "/sys/block/" + std::string(bd->d_name) + "/device/vendor"; - facts[std::string("blockdevice_") + bd->d_name + "_vendor"] = + string vendor_file = "/sys/block/" + string(bd->d_name) + "/device/vendor"; + facts[string("blockdevice_") + bd->d_name + "_vendor"] = read_oneline_file(vendor_file); - std::string size_file = "/sys/block/" + std::string(bd->d_name) + "/size"; - std::string size_line = read_oneline_file(size_file); + string size_file = "/sys/block/" + string(bd->d_name) + "/size"; + string size_line = read_oneline_file(size_file); int64_t size; sscanf(size_line.c_str(), "%" SCNd64, &size); - facts[std::string("blockdevice_") + bd->d_name + "_size"] = std::to_string(size * 512); + facts[string("blockdevice_") + bd->d_name + "_size"] = to_string(size * 512); } facts["blockdevices"] = blockdevices; @@ -439,20 +428,20 @@ void get_blockdevice_facts(fact_map& facts) void get_misc_facts(fact_map& facts) { facts["path"] = getenv("PATH"); - std::string whoami = popen_stdout("whoami"); + string whoami = popen_stdout("whoami"); facts["id"] = trim(whoami); // timezone char tzstring[16]; - time_t t = time(NULL); - struct tm loc; + time_t t = time(nullptr); + tm loc; localtime_r(&t, &loc); strftime(tzstring, sizeof(tzstring), "%Z", &loc); facts["timezone"] = tzstring; } // get just one fact, optionally in two formats -static void get_mem_fact(std::string fact_name, int fact_value, fact_map& facts, +static void get_mem_fact(string const& fact_name, int fact_value, fact_map& facts, bool get_mb_variant = true) { float fact_value_scaled = fact_value / 1024.0; @@ -460,7 +449,7 @@ static void get_mem_fact(std::string fact_name, int fact_value, fact_map& facts, if (get_mb_variant) { snprintf(float_buf, sizeof(float_buf) - 1, "%.2f", fact_value_scaled); - facts[std::string(fact_name) + "_mb"] = float_buf; + facts[string(fact_name) + "_mb"] = float_buf; } int scale_index; @@ -468,16 +457,16 @@ static void get_mem_fact(std::string fact_name, int fact_value, fact_map& facts, fact_value_scaled > 1024.0; fact_value_scaled /= 1024.0, ++scale_index) {} - std::string scale[4] = {"MB", "GB", "TB", "PB"}; // oh yeah, petabytes ... + string scale[4] = {"MB", "GB", "TB", "PB"}; // oh yeah, petabytes ... snprintf(float_buf, sizeof(float_buf) - 1, "%.2f", fact_value_scaled); - facts[fact_name] = std::string(float_buf) + scale[scale_index]; + facts[fact_name] = string(float_buf) + scale[scale_index]; } void get_mem_facts(fact_map& facts) { - std::ifstream oneline_file("/proc/meminfo", std::ifstream::in); - std::string line; + ifstream oneline_file("/proc/meminfo", ifstream::in); + string line; // The MemFree fact is the sum of MemFree + Buffer + Cached from /proc/meninfo, // so sum that one as we go, and get it out at the end. @@ -493,8 +482,8 @@ void get_mem_facts(fact_map& facts) unsigned int memoryfree = 0; - while (std::getline(oneline_file, line)) { - std::vector tokens; + while (getline(oneline_file, line)) { + vector tokens; tokenize(line, tokens); if (tokens.size() < 3) continue; // should never happen @@ -514,19 +503,19 @@ void get_mem_facts(fact_map& facts) get_mem_fact("memoryfree", memoryfree, facts); } -static std::string get_selinux_path() +static string get_selinux_path() { - static std::string selinux_path = ""; + static string selinux_path = ""; static bool inited = false; if (inited) return selinux_path; - std::ifstream mounts("/proc/self/mounts", std::ifstream::in); - std::string line; + ifstream mounts("/proc/self/mounts", ifstream::in); + string line; - while (std::getline(mounts, line)) { - std::vector tokens; + while (getline(mounts, line)) { + vector tokens; tokenize(line, tokens); if (tokens.size() < 2) continue; if (tokens[0] != "selinuxfs") continue; @@ -542,12 +531,12 @@ static std::string get_selinux_path() static bool selinux() { - std::string selinux_path = get_selinux_path(); + string selinux_path = get_selinux_path(); if (selinux_path.empty()) return false; - std::string selinux_enforce_path = selinux_path + "/enforce"; - std::string security_attr_path = "/proc/self/attr/current"; + string selinux_enforce_path = selinux_path + "/enforce"; + string security_attr_path = "/proc/self/attr/current"; if (file_exist(selinux_enforce_path) && file_exist(security_attr_path) && read_oneline_file(security_attr_path) != "kernel") return true; @@ -572,24 +561,24 @@ void get_selinux_facts(fact_map& facts) facts["selinux_config_policy"] = "unknown"; facts["selinux_mode"] = "unknown"; - std::string selinux_path = get_selinux_path(); + string selinux_path = get_selinux_path(); - std::string selinux_enforce_path = selinux_path + "/enforce"; + string selinux_enforce_path = selinux_path + "/enforce"; if (file_exist(selinux_enforce_path)) facts["selinux_enforced"] = ((read_oneline_file(selinux_enforce_path) == "1") ? "true" : "false"); - std::string selinux_policyvers_path = selinux_path + "/policyvers"; + string selinux_policyvers_path = selinux_path + "/policyvers"; if (file_exist(selinux_policyvers_path)) facts["selinux_policyversion"] = read_oneline_file(selinux_policyvers_path); - std::string selinux_cmd = "/usr/sbin/sestatus"; + string selinux_cmd = "/usr/sbin/sestatus"; FILE* pipe = popen(selinux_cmd.c_str(), "r"); if (!pipe) return; char buffer[512]; // seems like a lot, but there's no constant available while (!feof(pipe)) { - if (fgets(buffer, 128, pipe) != NULL) { - std::vector elems; + if (fgets(buffer, 128, pipe) != nullptr) { + vector elems; split(buffer, ':', elems); if (elems.size() < 2) continue; // shouldn't happen if (elems[0] == "Current mode") { @@ -606,9 +595,9 @@ void get_selinux_facts(fact_map& facts) pclose(pipe); } -static void get_ssh_fact(std::string fact_name, std::string path_name, fact_map& facts) +static void get_ssh_fact(string const& fact_name, string const& path_name, fact_map& facts) { - std::vector ssh_directories = { + vector ssh_directories = { "/etc/ssh", "/usr/local/etc/ssh", "/etc", @@ -617,10 +606,10 @@ static void get_ssh_fact(std::string fact_name, std::string path_name, fact_map& }; for (auto const &ssh_directory : ssh_directories) { - std::string full_path = ssh_directory + "/" + path_name; + string full_path = ssh_directory + "/" + path_name; if (file_exist(full_path)) { - std::string key = read_oneline_file(full_path); - std::vector tokens; + string key = read_oneline_file(full_path); + vector tokens; tokenize(trim(key), tokens); if (tokens.size() < 2) continue; // should never happen facts[fact_name] = tokens[1]; @@ -638,7 +627,7 @@ static void get_ssh_fact(std::string fact_name, std::string path_name, fact_map& // no support for the sshfp facts, which require base64/sha1sum code void get_ssh_facts(fact_map& facts) { - std::map ssh_facts = { + map ssh_facts = { { "sshdsakey", "ssh_host_dsa_key.pub" }, { "sshrsakey", "ssh_host_rsa_key.pub" }, { "sshecdsakey", "ssh_host_ecdsa_key.pub" }, @@ -655,13 +644,13 @@ static void get_physicalprocessorcount_fact(fact_map& facts) // but I don't know why the /sys support was added; research needed. // Since sys is the default, just reproduce that logic for now. - std::string sysfs_cpu_directory = "/sys/devices/system/cpu"; - std::vector package_ids; + string sysfs_cpu_directory = "/sys/devices/system/cpu"; + vector package_ids; if (file_exist(sysfs_cpu_directory)) { for (int i = 0; ; i++) { char buf[10]; snprintf(buf, sizeof(buf) - 1, "%u", i); - std::string cpu_phys_file = sysfs_cpu_directory + "/cpu" + buf + "/topology/physical_package_id"; + string cpu_phys_file = sysfs_cpu_directory + "/cpu" + buf + "/topology/physical_package_id"; if (!file_exist(cpu_phys_file)) break; @@ -670,7 +659,7 @@ static void get_physicalprocessorcount_fact(fact_map& facts) sort(begin(package_ids), end(package_ids)); unique(begin(package_ids), end(package_ids)); - facts["physicalprocessorcount"] = std::to_string(package_ids.size()); + facts["physicalprocessorcount"] = to_string(package_ids.size()); } else { // here's where the fall back to /proc/cpuinfo would go } @@ -678,27 +667,27 @@ static void get_physicalprocessorcount_fact(fact_map& facts) void get_processorcount_fact(fact_map& facts) { - std::ifstream cpuinfo_file("/proc/cpuinfo", std::ifstream::in); - std::string line; + ifstream cpuinfo_file("/proc/cpuinfo", ifstream::in); + string line; int processor_count = 0; - std::string current_processor_number; - while (std::getline(cpuinfo_file, line)) { + string current_processor_number; + while (getline(cpuinfo_file, line)) { unsigned sep = line.find(":"); - std::string tmp = line.substr(0, sep); - std::string key = trim(tmp); + string tmp = line.substr(0, sep); + string key = trim(tmp); if (key == "processor") { ++processor_count; - std::string tmp = line.substr(sep + 1, std::string::npos); + string tmp = line.substr(sep + 1, string::npos); current_processor_number = trim(tmp); } else if (key == "model name") { - std::string tmp = line.substr(sep + 1, std::string::npos); - facts[std::string("processor") + current_processor_number] = trim(tmp); + string tmp = line.substr(sep + 1, string::npos); + facts[string("processor") + current_processor_number] = trim(tmp); } } // this was added after 1.7.3, omit for now, needs investigation if (false) facts["activeprocessorcount"] = processor_count; - facts["processorcount"] = std::to_string(processor_count); + facts["processorcount"] = to_string(processor_count); } void get_processor_facts(fact_map& facts) @@ -709,7 +698,7 @@ void get_processor_facts(fact_map& facts) void get_architecture_facts(fact_map& facts) { - struct utsname uts; + utsname uts; if (uname(&uts) == 0) { // This is cheating at some level because these are all the same on x86_64 linux. // Otoh, some of these may be compiled-in for a C version. And then if facter @@ -724,9 +713,9 @@ void get_architecture_facts(fact_map& facts) void get_dmidecode_facts(fact_map& facts) { - std::string dmidecode_output = popen_stdout("/usr/sbin/dmidecode 2> /dev/null"); - std::stringstream ss(dmidecode_output); - std::string line; + string dmidecode_output = popen_stdout("/usr/sbin/dmidecode 2> /dev/null"); + stringstream ss(dmidecode_output); + string line; enum { bios_information, @@ -736,7 +725,7 @@ void get_dmidecode_facts(fact_map& facts) unknown } dmi_section = unknown; - while (std::getline(ss, line)) { + while (getline(ss, line)) { if (line.empty()) continue; // enable case-insensitive compares @@ -765,70 +754,70 @@ void get_dmidecode_facts(fact_map& facts) if (dmi_section == unknown) continue; size_t sep = line.find(":"); - if (sep != std::string::npos) { - std::string tmp = line.substr(0, sep); - std::string key = trim(tmp); + if (sep != string::npos) { + string tmp = line.substr(0, sep); + string key = trim(tmp); if (dmi_section == bios_information) { ci_string ci_key = key.c_str(); if (ci_key == "vendor") { - std::string tmp = line.substr(sep + 1, std::string::npos); - std::string value = trim(tmp); + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); facts["bios_vendor"] = value; } if (ci_key == "version") { - std::string tmp = line.substr(sep + 1, std::string::npos); - std::string value = trim(tmp); + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); facts["bios_version"] = value; } if (ci_key == "release date") { - std::string tmp = line.substr(sep + 1, std::string::npos); - std::string value = trim(tmp); + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); facts["bios_release_date"] = value; } } else if (dmi_section == base_board_information) { ci_string ci_key = key.c_str(); if (ci_key == "manufacturer") { - std::string tmp = line.substr(sep + 1, std::string::npos); - std::string value = trim(tmp); + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); facts["boardmanufacturer"] = value; } if (ci_key == "product name" || ci_key == "product") { - std::string tmp = line.substr(sep + 1, std::string::npos); - std::string value = trim(tmp); + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); facts["boardproductname"] = value; } if (ci_key == "serial number") { - std::string tmp = line.substr(sep + 1, std::string::npos); - std::string value = trim(tmp); + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); facts["boardserialnumber"] = value; } } else if (dmi_section == system_information) { ci_string ci_key = key.c_str(); if (ci_key == "manufacturer") { - std::string tmp = line.substr(sep + 1, std::string::npos); - std::string value = trim(tmp); + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); facts["manufacturer"] = value; } if (ci_key == "product name" || ci_key == "product") { - std::string tmp = line.substr(sep + 1, std::string::npos); - std::string value = trim(tmp); + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); facts["productname"] = value; } if (ci_key == "serial number") { - std::string tmp = line.substr(sep + 1, std::string::npos); - std::string value = trim(tmp); + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); facts["serialnumber"] = value; } if (ci_key == "uuid") { - std::string tmp = line.substr(sep + 1, std::string::npos); - std::string value = trim(tmp); + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); facts["uuid"] = value; } } else if (dmi_section == chassis_information) { ci_string ci_key = key.c_str(); if (ci_key == "chassis type" || ci_key == "type") { - std::string tmp = line.substr(sep + 1, std::string::npos); - std::string value = trim(tmp); + string tmp = line.substr(sep + 1, string::npos); + string value = trim(tmp); facts["type"] = value; } } @@ -838,11 +827,11 @@ void get_dmidecode_facts(fact_map& facts) void get_filesystems_facts(fact_map& facts) { - std::ifstream cpuinfo_file("/proc/filesystems", std::ifstream::in); - std::string line; - std::string filesystems = ""; - while (std::getline(cpuinfo_file, line)) { - if (line.find("nodev") != std::string::npos || line.find("fuseblk") != std::string::npos) + ifstream cpuinfo_file("/proc/filesystems", ifstream::in); + string line; + string filesystems = ""; + while (getline(cpuinfo_file, line)) { + if (line.find("nodev") != string::npos || line.find("fuseblk") != string::npos) continue; if (!filesystems.empty()) @@ -857,17 +846,17 @@ void get_hostname_facts(fact_map& facts) { // there's some history here, perhaps just port the facter conditional straight across? // so this is short-term - std::string hostname_output = popen_stdout("hostname"); + string hostname_output = popen_stdout("hostname"); unsigned sep = hostname_output.find("."); - std::string hostname1 = hostname_output.substr(0, sep); - std::string hostname = trim(hostname1); - - std::ifstream resolv_conf_file("/etc/resolv.conf", std::ifstream::in); - std::string line; - std::string domain; - std::string search; - while (std::getline(resolv_conf_file, line)) { - std::vector elems; + string hostname1 = hostname_output.substr(0, sep); + string hostname = trim(hostname1); + + ifstream resolv_conf_file("/etc/resolv.conf", ifstream::in); + string line; + string domain; + string search; + while (getline(resolv_conf_file, line)) { + vector elems; tokenize(line, elems); if (elems.size() >= 2) { if (elems[0] == "domain") @@ -884,7 +873,7 @@ void get_hostname_facts(fact_map& facts) facts["fqdn"] = hostname + "." + domain; } -static void get_external_facts_from_executable(fact_map& facts, std::string executable) +static void get_external_facts_from_executable(fact_map& facts, string const& executable) { // executable FILE *stdout = popen(executable.c_str(), "r"); @@ -893,11 +882,11 @@ static void get_external_facts_from_executable(fact_map& facts, std::string exec const int buffer_len = 32 * 1024; char buffer[buffer_len]; if (fgets(buffer, buffer_len, stdout)) { - std::vector elems; + vector elems; split(buffer, '=', elems); if (elems.size() != 2) continue; // shouldn't happen - std::string key = trim(elems[0]); - std::string val = trim(elems[1]); + string key = trim(elems[0]); + string val = trim(elems[1]); facts[key] = val; } } @@ -905,17 +894,17 @@ static void get_external_facts_from_executable(fact_map& facts, std::string exec } } -static void get_external_facts(fact_map& facts, std::string directory) +static void get_external_facts(fact_map& facts, string const& directory) { DIR *external_dir = opendir(directory.c_str()); - if (external_dir == NULL) + if (!external_dir) return; - struct dirent *external_fact; + dirent* external_fact; struct stat s; while ((external_fact = readdir(external_dir))) { - std::string full_path = directory + "/" + external_fact->d_name; + string full_path = directory + "/" + external_fact->d_name; if (stat(full_path.c_str(), &s) != 0) continue; @@ -930,7 +919,7 @@ static void get_external_facts(fact_map& facts, std::string directory) } } -void get_external_facts(fact_map& facts, const std::list& directories) +void get_external_facts(fact_map& facts, list const& directories) { for (auto const& dir : directories) { get_external_facts(facts, dir); diff --git a/lib/cfacterimpl.h b/lib/cfacterimpl.h index 2e9163a0e6..657e32c860 100644 --- a/lib/cfacterimpl.h +++ b/lib/cfacterimpl.h @@ -1,3 +1,6 @@ +#ifndef __CFACTERIMPL_H__ +#define __CFACTERIMPL_H__ + #include #include #include @@ -21,4 +24,6 @@ void get_architecture_facts(fact_map&); void get_dmidecode_facts(fact_map&); void get_filesystems_facts(fact_map&); void get_hostname_facts(fact_map&); -void get_external_facts(fact_map&, const std::list&); +void get_external_facts(fact_map&, std::list const&); + +#endif \ No newline at end of file diff --git a/lib/cfacterlib.cc b/lib/cfacterlib.cc index d61bca15c8..2b05f7be38 100644 --- a/lib/cfacterlib.cc +++ b/lib/cfacterlib.cc @@ -11,7 +11,9 @@ #include "rapidjson/prettywriter.h" #include "rapidjson/stringbuffer.h" -std::map facts; +using namespace std; + +map facts; void clear() { @@ -31,7 +33,7 @@ void loadfacts() facts["cfacterversion"] = CFACTER_VERSION; - std::list external_directories = { "/etc/facter/facts.d" }; + list external_directories = { "/etc/facter/facts.d" }; get_network_facts(facts); get_kernel_facts(facts); @@ -76,11 +78,11 @@ int to_json(char *facts_json, size_t facts_len) return 0; } -int value(const char *fact, char *value, size_t value_len) +int value(const char *fact, char *value, size_t value_len) { loadfacts(); - auto i = facts.find(fact); + auto const& i = facts.find(fact); if (i == facts.end()) return -1; diff --git a/lib/cfacterlib.h b/lib/cfacterlib.h index ef379b99d8..e83ecb1c1c 100644 --- a/lib/cfacterlib.h +++ b/lib/cfacterlib.h @@ -1,5 +1,5 @@ -#ifndef __CFACTERLIB_IMPL_H__ -#define __CFACTERLIB_IMPL_H__ +#ifndef __CFACTERLIB_H__ +#define __CFACTERLIB_H__ #include diff --git a/lib/scoped_resource.hpp b/lib/scoped_resource.hpp new file mode 100644 index 0000000000..aca9ee0170 --- /dev/null +++ b/lib/scoped_resource.hpp @@ -0,0 +1,89 @@ +#ifndef __SCOPED_RESOURCE_HPP__ +#define __SCOPED_RESOURCE_HPP__ + +#include + +namespace cfacter { + +/** + Simple class that is used for the RAII pattern. + + Used to scope a resource. When it goes out of scope, a deleter + function is called to delete the resource. +*/ +template struct scoped_resource +{ + /** + Constructs a scoped_resource. + + Takes ownership of the given resource. + @param resource The resource to scope. + @param deleter The function to call when the resource goes out of scope. + */ + scoped_resource(T&& resource, std::function deleter) : + _resource(resource), + _deleter(deleter) + { + } + + // Force non-copyable + scoped_resource(scoped_resource const&) = delete; + scoped_resource& operator=(scoped_resource const&) = delete; + + // Force non-moveable + scoped_resource(scoped_resource&&) = delete; + scoped_resource& operator=(scoped_resource&&) = delete; + + /** + Destructs a scoped_resource. + */ + virtual ~scoped_resource() + { + _deleter(_resource); + } + + /** + Implicitly casts to T&. + */ + operator T&() + { + return _resource; + } + + /** + Implicitly casts to T const&. + */ + operator T const&() const + { + return _resource; + } + +private: + T _resource; + std::function _deleter; +}; + +/** + Represents a scoped file descriptor. + + Automatically closes the file descriptor when it goes out of scope. +*/ +struct scoped_descriptor : scoped_resource +{ + scoped_descriptor(int descriptor) : + scoped_resource(std::move(descriptor), close) + { + } + +private: + static void close(int descriptor) + { + if (descriptor >= 0) { + ::close(descriptor); + } + } +}; + +} // namespace cfacter + +#endif From 2b467fa2b097f41e4b29c41aa3c63dc71f0272ce Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Fri, 21 Mar 2014 11:26:51 -0700 Subject: [PATCH 1719/3753] (FACT-409) Preserve semantics of .exec Raising an error when .exec tries to run a non-existent command is a major backwards incompatible change. While we're on a breaking change boundary we're trying to minimize the upgrade pain, so to reduce this the behavior of .exec is preserved when running non-existent commands. In the long run .exec should be deprecated in favor of .execute. --- lib/facter/core/execution.rb | 21 ++++++++++++++++----- lib/facter/core/execution/base.rb | 2 +- lib/facter/hostname.rb | 2 +- lib/facter/ipaddress.rb | 2 +- lib/facter/kernelrelease.rb | 2 +- lib/facter/util/resolution.rb | 2 +- lib/facter/util/uptime.rb | 6 +++--- spec/unit/core/execution/base_spec.rb | 20 ++++++++++---------- spec/unit/core/execution_spec.rb | 9 +++++++-- spec/unit/hardwareisa_spec.rb | 10 +++++----- spec/unit/hardwaremodel_spec.rb | 2 +- spec/unit/hostname_spec.rb | 8 ++++---- spec/unit/id_spec.rb | 6 +++--- spec/unit/ipaddress_spec.rb | 2 +- spec/unit/kernelrelease_spec.rb | 6 +++--- spec/unit/kernelversion_spec.rb | 2 +- spec/unit/lsbdistcodename_spec.rb | 2 +- spec/unit/lsbdistid_spec.rb | 2 +- spec/unit/lsbdistrelease_spec.rb | 4 ++-- spec/unit/lsbrelease_spec.rb | 2 +- spec/unit/processor_spec.rb | 10 +++++----- spec/unit/uniqueid_spec.rb | 6 +++--- spec/unit/util/resolution_spec.rb | 2 +- spec/unit/util/uptime_spec.rb | 4 ++-- spec/unit/zonename_spec.rb | 2 +- 25 files changed, 76 insertions(+), 60 deletions(-) diff --git a/lib/facter/core/execution.rb b/lib/facter/core/execution.rb index 84a854672f..1536851040 100644 --- a/lib/facter/core/execution.rb +++ b/lib/facter/core/execution.rb @@ -83,10 +83,20 @@ def with_env(values, &block) @@impl.with_env(values, &block) end - # Executes a program and return the output of that program. + # Try to execute a command and return the output. # - # Returns nil if the program can't be found, or if there is a problem - # executing the code. + # @param code [String] the program to run + # + # @return [String] the output of the program, or nil if the command does + # not exist or could not be executed. + # + # @deprecated Use #{execute} instead + # @api public + def exec(command) + @@impl.execute(command, :on_fail => nil) + end + + # Execute a command and return the output of that program. # # @param code [String] the program to run # @param options [Hash] @@ -102,8 +112,9 @@ def with_env(values, &block) # command execution failed and :on_fail was specified. # # @api public - def exec(command, options = {}) - @@impl.exec(command, options) + # @since 2.0.1 + def execute(command, options = {}) + @@impl.execute(command, options) end class ExecutionFailure < StandardError; end diff --git a/lib/facter/core/execution/base.rb b/lib/facter/core/execution/base.rb index 95a5b93b57..8c67460a32 100644 --- a/lib/facter/core/execution/base.rb +++ b/lib/facter/core/execution/base.rb @@ -27,7 +27,7 @@ def with_env(values) rv end - def exec(command, options = {}) + def execute(command, options = {}) on_fail = options.fetch(:on_fail, :raise) diff --git a/lib/facter/hostname.rb b/lib/facter/hostname.rb index d630c4f717..014b5033dd 100644 --- a/lib/facter/hostname.rb +++ b/lib/facter/hostname.rb @@ -14,7 +14,7 @@ Facter.add(:hostname) do setcode do hostname = nil - if name = Facter::Core::Execution.exec('hostname') + if name = Facter::Core::Execution.execute('hostname') if name =~ /(.*?)\./ hostname = $1 else diff --git a/lib/facter/ipaddress.rb b/lib/facter/ipaddress.rb index 99b2a90ca9..631502085a 100644 --- a/lib/facter/ipaddress.rb +++ b/lib/facter/ipaddress.rb @@ -155,7 +155,7 @@ if hostname = Facter.value(:hostname) # we need Hostname to exist for this to work host = nil - if host = Facter::Core::Execution.exec("host #{hostname}") + if host = Facter::Core::Execution.execute("host #{hostname}") list = host.chomp.split(/\s/) if defined? list[-1] and list[-1] =~ /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ diff --git a/lib/facter/kernelrelease.rb b/lib/facter/kernelrelease.rb index fe7b34b652..b30a6080ff 100644 --- a/lib/facter/kernelrelease.rb +++ b/lib/facter/kernelrelease.rb @@ -23,7 +23,7 @@ Facter.add(:kernelrelease) do confine :kernel => "hp-ux" setcode do - version = Facter::Core::Execution.exec('uname -r') + version = Facter::Core::Execution.execute('uname -r') version[2..-1] end end diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 4ac25e45a7..0182a9ae63 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -128,7 +128,7 @@ def set_options(options) def setcode(string = nil, &block) if string @code = Proc.new do - output = Facter::Core::Execution.exec(string, :on_fail => nil) + output = Facter::Core::Execution.execute(string, :on_fail => nil) if output.nil? or output.empty? nil else diff --git a/lib/facter/util/uptime.rb b/lib/facter/util/uptime.rb index 4ec592eeaf..9b0eaca4e4 100644 --- a/lib/facter/util/uptime.rb +++ b/lib/facter/util/uptime.rb @@ -20,7 +20,7 @@ def self.get_uptime_seconds_win private def self.uptime_proc_uptime - output = Facter::Core::Execution.exec("/bin/cat #{uptime_file} 2>/dev/null") + output = Facter::Core::Execution.execute("/bin/cat #{uptime_file} 2>/dev/null") if not output.empty? output.chomp.split(" ").first.to_i @@ -28,14 +28,14 @@ def self.uptime_proc_uptime end def self.uptime_sysctl - output = Facter::Core::Execution.exec("#{uptime_sysctl_cmd} 2>/dev/null", :on_fail => nil) + output = Facter::Core::Execution.execute("#{uptime_sysctl_cmd} 2>/dev/null", :on_fail => nil) if output compute_uptime(Time.at(output.match(/\d+/)[0].to_i)) end end def self.uptime_executable - output = Facter::Core::Execution.exec("#{uptime_executable_cmd} 2>/dev/null", :on_fail => nil) + output = Facter::Core::Execution.execute("#{uptime_executable_cmd} 2>/dev/null", :on_fail => nil) if output up=0 diff --git a/spec/unit/core/execution/base_spec.rb b/spec/unit/core/execution/base_spec.rb index 00fdbd8bb5..5315ba85d2 100644 --- a/spec/unit/core/execution/base_spec.rb +++ b/spec/unit/core/execution/base_spec.rb @@ -62,11 +62,11 @@ def handy_method() end end - describe "#exec" do + describe "#execute" do it "switches LANG to C when executing the command" do subject.expects(:with_env).with('LANG' => 'C') - subject.exec('foo') + subject.execute('foo') end it "switches LC_ALL to C when executing the command" @@ -74,18 +74,18 @@ def handy_method() it "expands the command before running it" do subject.stubs(:`).returns '' subject.expects(:expand_command).with('foo').returns '/bin/foo' - subject.exec('foo') + subject.execute('foo') end describe "and the command is not present" do it "raises an error when the :on_fail behavior is :raise" do subject.expects(:expand_command).with('foo').returns nil - expect { subject.exec('foo') }.to raise_error(Facter::Core::Execution::ExecutionFailure) + expect { subject.execute('foo') }.to raise_error(Facter::Core::Execution::ExecutionFailure) end it "returns the given value when :on_fail is set to a value" do subject.expects(:expand_command).with('foo').returns nil - expect(subject.exec('foo', :on_fail => nil)).to be_nil + expect(subject.execute('foo', :on_fail => nil)).to be_nil end end @@ -96,11 +96,11 @@ def handy_method() end it "raises an error when the :on_fail behavior is :raise" do - expect { subject.exec('foo') }.to raise_error(Facter::Core::Execution::ExecutionFailure) + expect { subject.execute('foo') }.to raise_error(Facter::Core::Execution::ExecutionFailure) end it "returns the given value when :on_fail is set to a value" do - expect(subject.exec('foo', :on_fail => nil)).to be_nil + expect(subject.execute('foo', :on_fail => nil)).to be_nil end end @@ -112,21 +112,21 @@ def handy_method() Thread.expects(:new).yields Process.expects(:waitall).once - subject.exec("foo", :on_fail => nil) + subject.execute("foo", :on_fail => nil) end it "returns the output of the command" do subject.expects(:`).with("/bin/foo").returns 'hi' subject.expects(:expand_command).with('foo').returns '/bin/foo' - expect(subject.exec("foo")).to eq 'hi' + expect(subject.execute("foo")).to eq 'hi' end it "strips off trailing newlines" do subject.expects(:`).with("/bin/foo").returns "hi\n" subject.expects(:expand_command).with('foo').returns '/bin/foo' - expect(subject.exec("foo")).to eq 'hi' + expect(subject.execute("foo")).to eq 'hi' end end end diff --git a/spec/unit/core/execution_spec.rb b/spec/unit/core/execution_spec.rb index 201345f545..bb4b6794cf 100644 --- a/spec/unit/core/execution_spec.rb +++ b/spec/unit/core/execution_spec.rb @@ -30,8 +30,13 @@ subject.expand_command('waffles') end - it "delegates #exec to the implementation" do - impl.expects(:exec).with('waffles', {}) + it "delegates #exec to #execute" do + impl.expects(:execute).with('waffles', {:on_fail => nil}) subject.exec('waffles') end + + it "delegates #execute to the implementation" do + impl.expects(:execute).with('waffles', {}) + subject.execute('waffles') + end end diff --git a/spec/unit/hardwareisa_spec.rb b/spec/unit/hardwareisa_spec.rb index 8559ae9d7f..eba3a4f4aa 100755 --- a/spec/unit/hardwareisa_spec.rb +++ b/spec/unit/hardwareisa_spec.rb @@ -6,35 +6,35 @@ describe "Hardwareisa fact" do it "should match uname -p on Linux" do Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter::Core::Execution.stubs(:exec).with("uname -p", anything).returns("Inky") + Facter::Core::Execution.stubs(:execute).with("uname -p", anything).returns("Inky") Facter.fact(:hardwareisa).value.should == "Inky" end it "should match uname -p on Darwin" do Facter.fact(:kernel).stubs(:value).returns("Darwin") - Facter::Core::Execution.stubs(:exec).with("uname -p", anything).returns("Blinky") + Facter::Core::Execution.stubs(:execute).with("uname -p", anything).returns("Blinky") Facter.fact(:hardwareisa).value.should == "Blinky" end it "should match uname -p on SunOS" do Facter.fact(:kernel).stubs(:value).returns("SunOS") - Facter::Core::Execution.stubs(:exec).with("uname -p", anything).returns("Pinky") + Facter::Core::Execution.stubs(:execute).with("uname -p", anything).returns("Pinky") Facter.fact(:hardwareisa).value.should == "Pinky" end it "should match uname -p on FreeBSD" do Facter.fact(:kernel).stubs(:value).returns("FreeBSD") - Facter::Core::Execution.stubs(:exec).with("uname -p", anything).returns("Clyde") + Facter::Core::Execution.stubs(:execute).with("uname -p", anything).returns("Clyde") Facter.fact(:hardwareisa).value.should == "Clyde" end it "should match uname -m on HP-UX" do Facter.fact(:kernel).stubs(:value).returns("HP-UX") - Facter::Core::Execution.stubs(:exec).with("uname -m", anything).returns("Pac-Man") + Facter::Core::Execution.stubs(:execute).with("uname -m", anything).returns("Pac-Man") Facter.fact(:hardwareisa).value.should == "Pac-Man" end diff --git a/spec/unit/hardwaremodel_spec.rb b/spec/unit/hardwaremodel_spec.rb index b4c65ec0b5..c73378a303 100644 --- a/spec/unit/hardwaremodel_spec.rb +++ b/spec/unit/hardwaremodel_spec.rb @@ -6,7 +6,7 @@ describe "Hardwaremodel fact" do it "should match uname -m by default" do Facter.fact(:kernel).stubs(:value).returns("Darwin") - Facter::Core::Execution.stubs(:exec).with("uname -m", anything).returns("Inky") + Facter::Core::Execution.stubs(:execute).with("uname -m", anything).returns("Inky") Facter.fact(:hardwaremodel).value.should == "Inky" end diff --git a/spec/unit/hostname_spec.rb b/spec/unit/hostname_spec.rb index 9350c4f252..160e182c2c 100755 --- a/spec/unit/hostname_spec.rb +++ b/spec/unit/hostname_spec.rb @@ -11,17 +11,17 @@ end it "should use the hostname command" do - Facter::Core::Execution.expects(:exec).with('hostname').at_least_once + Facter::Core::Execution.expects(:execute).with('hostname').at_least_once Facter.fact(:hostname).value.should be_nil end it "should use hostname as the fact if unqualified" do - Facter::Core::Execution.stubs(:exec).with('hostname').returns('host1') + Facter::Core::Execution.stubs(:execute).with('hostname').returns('host1') Facter.fact(:hostname).value.should == "host1" end it "should truncate the domain name if qualified" do - Facter::Core::Execution.stubs(:exec).with('hostname').returns('host1.example.com') + Facter::Core::Execution.stubs(:execute).with('hostname').returns('host1.example.com') Facter.fact(:hostname).value.should == "host1" end end @@ -33,7 +33,7 @@ end it "should use scutil to get the hostname" do - Facter::Core::Execution.expects(:exec).with('/usr/sbin/scutil --get LocalHostName', anything).returns("host1") + Facter::Core::Execution.expects(:execute).with('/usr/sbin/scutil --get LocalHostName', anything).returns("host1") Facter.fact(:hostname).value.should == "host1" end end diff --git a/spec/unit/id_spec.rb b/spec/unit/id_spec.rb index f5f4ad4e39..9b50b706a8 100755 --- a/spec/unit/id_spec.rb +++ b/spec/unit/id_spec.rb @@ -12,7 +12,7 @@ it "should return the current user" do given_a_configuration_of(:is_windows => k == 'windows') Facter.fact(:kernel).stubs(:value).returns(k) - Facter::Core::Execution.expects(:exec).once.with('whoami', anything).returns 'bar' + Facter::Core::Execution.expects(:execute).once.with('whoami', anything).returns 'bar' Facter.fact(:id).value.should == 'bar' end @@ -21,8 +21,8 @@ it "should return the current user on Solaris" do given_a_configuration_of(:is_windows => false) - Facter::Core::Execution.stubs(:exec).with('uname -s').returns('SunOS') - Facter::Core::Execution.expects(:exec).once.with('/usr/xpg4/bin/id -un', anything).returns 'bar' + Facter.fact(:kernel).stubs(:value).returns 'SunOS' + Facter::Core::Execution.expects(:execute).once.with('/usr/xpg4/bin/id -un', anything).returns 'bar' Facter.fact(:id).value.should == 'bar' end diff --git a/spec/unit/ipaddress_spec.rb b/spec/unit/ipaddress_spec.rb index 39eb97c3bb..78b0c96aae 100755 --- a/spec/unit/ipaddress_spec.rb +++ b/spec/unit/ipaddress_spec.rb @@ -60,7 +60,7 @@ def expect_ifconfig_parse(address, fixture) context "when you have no active network adapter" do it "should return nil if there are no active (or any) network adapters" do Facter::Util::WMI.expects(:execquery).with(Facter::Util::IP::Windows::WMI_IP_INFO_QUERY).returns([]) - Facter::Core::Execution.stubs(:exec) + Facter::Core::Execution.stubs(:execute) Facter.value(:ipaddress).should == nil end diff --git a/spec/unit/kernelrelease_spec.rb b/spec/unit/kernelrelease_spec.rb index 12c4d05bbe..d0a5133e83 100644 --- a/spec/unit/kernelrelease_spec.rb +++ b/spec/unit/kernelrelease_spec.rb @@ -21,7 +21,7 @@ describe "on AIX" do before do Facter.fact(:kernel).stubs(:value).returns("aix") - Facter::Core::Execution.stubs(:exec).with('oslevel -s', anything).returns("test_kernel") + Facter::Core::Execution.stubs(:execute).with('oslevel -s', anything).returns("test_kernel") end it "should return the kernel release" do @@ -32,7 +32,7 @@ describe "on HP-UX" do before do Facter.fact(:kernel).stubs(:value).returns("hp-ux") - Facter::Core::Execution.stubs(:exec).with('uname -r').returns("B.11.31") + Facter::Core::Execution.stubs(:execute).with('uname -r').returns("B.11.31") end it "should remove preceding letters" do @@ -43,7 +43,7 @@ describe "on everything else" do before do Facter.fact(:kernel).stubs(:value).returns("linux") - Facter::Core::Execution.stubs(:exec).with('uname -r', anything).returns("test_kernel") + Facter::Core::Execution.stubs(:execute).with('uname -r', anything).returns("test_kernel") end it "should return the kernel release" do diff --git a/spec/unit/kernelversion_spec.rb b/spec/unit/kernelversion_spec.rb index 6d20665292..236de4e5ca 100644 --- a/spec/unit/kernelversion_spec.rb +++ b/spec/unit/kernelversion_spec.rb @@ -7,7 +7,7 @@ describe "on Solaris/Sun OS" do before do Facter.fact(:kernel).stubs(:value).returns('sunos') - Facter::Core::Execution.stubs(:exec).with('uname -v', anything).returns("1.234.5") + Facter::Core::Execution.stubs(:execute).with('uname -v', anything).returns("1.234.5") end it "should return the kernel version using 'uname -v'" do diff --git a/spec/unit/lsbdistcodename_spec.rb b/spec/unit/lsbdistcodename_spec.rb index ab0a4cbd1b..1cc8377717 100755 --- a/spec/unit/lsbdistcodename_spec.rb +++ b/spec/unit/lsbdistcodename_spec.rb @@ -11,7 +11,7 @@ end it "returns the codename through lsb_release -c -s 2>/dev/null" do - Facter::Core::Execution.impl.stubs(:exec).with('lsb_release -c -s 2>/dev/null', anything).returns 'n/a' + Facter::Core::Execution.impl.stubs(:execute).with('lsb_release -c -s 2>/dev/null', anything).returns 'n/a' expect(Facter.fact(:lsbdistcodename).value).to eq 'n/a' end diff --git a/spec/unit/lsbdistid_spec.rb b/spec/unit/lsbdistid_spec.rb index 53cc961f60..3516f14e75 100755 --- a/spec/unit/lsbdistid_spec.rb +++ b/spec/unit/lsbdistid_spec.rb @@ -11,7 +11,7 @@ end it "returns the id through lsb_release -i -s 2>/dev/null" do - Facter::Core::Execution.impl.stubs(:exec).with('lsb_release -i -s 2>/dev/null', anything).returns 'Gentoo' + Facter::Core::Execution.impl.stubs(:execute).with('lsb_release -i -s 2>/dev/null', anything).returns 'Gentoo' expect(Facter.fact(:lsbdistid).value).to eq 'Gentoo' end diff --git a/spec/unit/lsbdistrelease_spec.rb b/spec/unit/lsbdistrelease_spec.rb index 9e8d5d2e25..ece7423141 100755 --- a/spec/unit/lsbdistrelease_spec.rb +++ b/spec/unit/lsbdistrelease_spec.rb @@ -11,12 +11,12 @@ end it "should return the release through lsb_release -r -s 2>/dev/null" do - Facter::Core::Execution.stubs(:exec).with('lsb_release -r -s 2>/dev/null', anything).returns '2.1' + Facter::Core::Execution.stubs(:execute).with('lsb_release -r -s 2>/dev/null', anything).returns '2.1' Facter.fact(:lsbdistrelease).value.should == '2.1' end it "should return nil if lsb_release is not installed" do - Facter::Core::Execution.stubs(:exec).with('lsb_release -r -s 2>/dev/null', anything).returns nil + Facter::Core::Execution.stubs(:execute).with('lsb_release -r -s 2>/dev/null', anything).returns nil Facter.fact(:lsbdistrelease).value.should be_nil end end diff --git a/spec/unit/lsbrelease_spec.rb b/spec/unit/lsbrelease_spec.rb index eaddedda8c..84cdd5df00 100755 --- a/spec/unit/lsbrelease_spec.rb +++ b/spec/unit/lsbrelease_spec.rb @@ -11,7 +11,7 @@ end it "returns the release through lsb_release -v -s 2>/dev/null" do - Facter::Core::Execution.impl.stubs(:exec).with('lsb_release -v -s 2>/dev/null', anything).returns 'n/a' + Facter::Core::Execution.impl.stubs(:execute).with('lsb_release -v -s 2>/dev/null', anything).returns 'n/a' expect(Facter.fact(:lsbrelease).value).to eq 'n/a' end diff --git a/spec/unit/processor_spec.rb b/spec/unit/processor_spec.rb index d9d5c67187..533d484089 100755 --- a/spec/unit/processor_spec.rb +++ b/spec/unit/processor_spec.rb @@ -185,35 +185,35 @@ def sysfs_cpu_stubs(count) it "should be 2 on dual-processor Darwin box" do Facter.fact(:kernel).stubs(:value).returns("Darwin") - Facter::Core::Execution.stubs(:exec).with("sysctl -n hw.ncpu", anything).returns('2') + Facter::Core::Execution.stubs(:execute).with("sysctl -n hw.ncpu", anything).returns('2') Facter.fact(:processorcount).value.should == "2" end it "should be 2 on dual-processor OpenBSD box" do Facter.fact(:kernel).stubs(:value).returns("OpenBSD") - Facter::Core::Execution.stubs(:exec).with("sysctl -n hw.ncpu", anything).returns('2') + Facter::Core::Execution.stubs(:execute).with("sysctl -n hw.ncpu", anything).returns('2') Facter.fact(:processorcount).value.should == "2" end it "should be 2 on dual-processor FreeBSD box" do Facter.fact(:kernel).stubs(:value).returns("FreeBSD") - Facter::Core::Execution.stubs(:exec).with("sysctl -n hw.ncpu", anything).returns('2') + Facter::Core::Execution.stubs(:execute).with("sysctl -n hw.ncpu", anything).returns('2') Facter.fact(:processorcount).value.should == "2" end it "should print the correct CPU Model on FreeBSD" do Facter.fact(:kernel).stubs(:value).returns("FreeBSD") - Facter::Core::Execution.stubs(:exec).with("sysctl -n hw.model", anything).returns('SomeVendor CPU 3GHz') + Facter::Core::Execution.stubs(:execute).with("sysctl -n hw.model", anything).returns('SomeVendor CPU 3GHz') Facter.fact(:processor).value.should == "SomeVendor CPU 3GHz" end it "should be 2 on dual-processor DragonFly box" do Facter.fact(:kernel).stubs(:value).returns("DragonFly") - Facter::Core::Execution.stubs(:exec).with("sysctl -n hw.ncpu", anything).returns('2') + Facter::Core::Execution.stubs(:execute).with("sysctl -n hw.ncpu", anything).returns('2') Facter.fact(:processorcount).value.should == "2" end diff --git a/spec/unit/uniqueid_spec.rb b/spec/unit/uniqueid_spec.rb index 261dee31fc..63fe7e5552 100755 --- a/spec/unit/uniqueid_spec.rb +++ b/spec/unit/uniqueid_spec.rb @@ -6,21 +6,21 @@ describe "Uniqueid fact" do it "should match hostid on Solaris" do Facter.fact(:kernel).stubs(:value).returns("SunOS") - Facter::Core::Execution.stubs(:exec).with("hostid", anything).returns("Larry") + Facter::Core::Execution.stubs(:execute).with("hostid", anything).returns("Larry") Facter.fact(:uniqueid).value.should == "Larry" end it "should match hostid on Linux" do Facter.fact(:kernel).stubs(:value).returns("Linux") - Facter::Core::Execution.stubs(:exec).with("hostid", anything).returns("Curly") + Facter::Core::Execution.stubs(:execute).with("hostid", anything).returns("Curly") Facter.fact(:uniqueid).value.should == "Curly" end it "should match hostid on AIX" do Facter.fact(:kernel).stubs(:value).returns("AIX") - Facter::Core::Execution.stubs(:exec).with("hostid", anything).returns("Moe") + Facter::Core::Execution.stubs(:execute).with("hostid", anything).returns("Moe") Facter.fact(:uniqueid).value.should == "Moe" end diff --git a/spec/unit/util/resolution_spec.rb b/spec/unit/util/resolution_spec.rb index 3a0b028426..75992750e0 100755 --- a/spec/unit/util/resolution_spec.rb +++ b/spec/unit/util/resolution_spec.rb @@ -74,7 +74,7 @@ describe "and the code is a string" do it "returns the result of executing the code" do resolution.setcode "/bin/foo" - Facter::Core::Execution.expects(:exec).once.with("/bin/foo", anything).returns "yup" + Facter::Core::Execution.expects(:execute).once.with("/bin/foo", anything).returns "yup" expect(resolution.value).to eq "yup" end diff --git a/spec/unit/util/uptime_spec.rb b/spec/unit/util/uptime_spec.rb index a6940edb83..ca437a8dff 100755 --- a/spec/unit/util/uptime_spec.rb +++ b/spec/unit/util/uptime_spec.rb @@ -99,14 +99,14 @@ test_cases.each do |uptime, expected| it "should return #{expected} for #{uptime}" do - Facter::Core::Execution.stubs(:exec).with('uptime 2>/dev/null', {:on_fail => nil}).returns(uptime) + Facter::Core::Execution.stubs(:execute).with('uptime 2>/dev/null', {:on_fail => nil}).returns(uptime) Facter.fact(:uptime_seconds).value.should == expected end end describe "nor is 'uptime' command" do before :each do - Facter::Core::Execution.stubs(:exec).with('uptime 2>/dev/null', {:on_fail => nil}).returns(nil) + Facter::Core::Execution.stubs(:execute).with('uptime 2>/dev/null', {:on_fail => nil}).returns(nil) end it "should return nil" do diff --git a/spec/unit/zonename_spec.rb b/spec/unit/zonename_spec.rb index eeb522fc0c..5927dc1061 100644 --- a/spec/unit/zonename_spec.rb +++ b/spec/unit/zonename_spec.rb @@ -7,7 +7,7 @@ it "should return global zone" do Facter.fact(:kernel).stubs(:value).returns("SunOS") - Facter::Core::Execution.stubs(:exec).with("zonename", anything).returns('global') + Facter::Core::Execution.stubs(:execute).with("zonename", anything).returns('global') Facter.fact(:zonename).value.should == "global" end From ebd3d753a538c845bf6324dd63079b98a04b98fc Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 24 Mar 2014 14:32:51 -0700 Subject: [PATCH 1720/3753] (maint) Pin rake to the last 1.8 compatible version --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 1ca3f56de6..1e03d9470b 100644 --- a/Gemfile +++ b/Gemfile @@ -9,7 +9,7 @@ platforms :ruby do end group :development, :test do - gem 'rake' + gem 'rake', "~> 10.1.0" gem 'rspec', "~> 2.11.0" gem 'mocha', "~> 0.10.5" gem 'json', "~> 1.7", :platforms => :ruby From 548fb2fe12913feba5fbd7ed788ef3a93252f65f Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 24 Mar 2014 15:00:31 -0700 Subject: [PATCH 1721/3753] (maint) Update solaris zone facts to use .execute --- lib/facter/util/solaris_zones.rb | 2 +- spec/unit/util/solaris_zones_spec.rb | 10 +++++----- spec/unit/zones_spec.rb | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/facter/util/solaris_zones.rb b/lib/facter/util/solaris_zones.rb index e0c4d5e3e4..df9d661b95 100644 --- a/lib/facter/util/solaris_zones.rb +++ b/lib/facter/util/solaris_zones.rb @@ -93,7 +93,7 @@ def add_dynamic_facts # # @return [Hash] the parsed output of the zoneadm command def refresh - @zoneadm_output = Facter::Core::Execution.exec(zoneadm_cmd, {:on_fail => nil}) + @zoneadm_output = Facter::Core::Execution.execute(zoneadm_cmd, {:on_fail => nil}) parse! end diff --git a/spec/unit/util/solaris_zones_spec.rb b/spec/unit/util/solaris_zones_spec.rb index c444c8e427..6eb938834c 100644 --- a/spec/unit/util/solaris_zones_spec.rb +++ b/spec/unit/util/solaris_zones_spec.rb @@ -47,7 +47,7 @@ describe '#refresh' do it 'executes the zoneadm_cmd' do - Facter::Core::Execution.expects(:exec).with(subject.zoneadm_cmd, {:on_fail => nil}).returns(zone_list) + Facter::Core::Execution.expects(:execute).with(subject.zoneadm_cmd, {:on_fail => nil}).returns(zone_list) subject.refresh end end @@ -69,7 +69,7 @@ it 'uses a single read of the system information for all of the dynamically generated zone facts' do given_initial_zone_facts # <= single read happens here - Facter::Core::Execution.expects(:exec).never + Facter::Core::Execution.expects(:execute).never Facter.fact(:zone_zoneA_id).value Facter.fact(:zone_local_id).value end @@ -101,7 +101,7 @@ given_initial_zone_facts when_facts_have_been_resolved_then_flushed - Facter::Core::Execution.expects(:exec).once.returns(zone_list2) + Facter::Core::Execution.expects(:execute).once.returns(zone_list2) Facter.fact(:zones).value Facter.fact(:zone_zoneA_id).value Facter.fact(:zone_local_id).value @@ -111,7 +111,7 @@ end def given_initial_zone_facts - Facter::Core::Execution.stubs(:exec). + Facter::Core::Execution.stubs(:execute). with(subject.zoneadm_cmd, {:on_fail => nil}). returns(zone_list) described_class.add_facts @@ -121,7 +121,7 @@ def when_facts_have_been_resolved_then_flushed Facter.fact(:zones).value Facter.fact(:zone_zoneA_id).value Facter.fact(:zone_local_id).value - Facter::Core::Execution.stubs(:exec).returns(zone_list2) + Facter::Core::Execution.stubs(:execute).returns(zone_list2) Facter.flush end end diff --git a/spec/unit/zones_spec.rb b/spec/unit/zones_spec.rb index 11563f4ab9..dd6a35392a 100644 --- a/spec/unit/zones_spec.rb +++ b/spec/unit/zones_spec.rb @@ -10,7 +10,7 @@ -:local:configured:/::native:shared -:zoneA:stopped:/::native:shared EOF - Facter::Core::Execution.stubs(:exec).with('/usr/sbin/zoneadm list -cp', {:on_fail => nil}).returns(zone_list) + Facter::Core::Execution.stubs(:execute).with('/usr/sbin/zoneadm list -cp', {:on_fail => nil}).returns(zone_list) Facter.collection.internal_loader.load(:zones) end From 67e57de4fb9314382b950a7e016fd46046aa40a1 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 24 Mar 2014 15:22:25 -0700 Subject: [PATCH 1722/3753] (maint) Remove extra argument from domain .exec calls --- lib/facter/domain.rb | 4 ++-- spec/unit/domain_spec.rb | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/facter/domain.rb b/lib/facter/domain.rb index 3c2dc4c7c4..ac9aa686f9 100644 --- a/lib/facter/domain.rb +++ b/lib/facter/domain.rb @@ -41,11 +41,11 @@ basic_hostname end - if name = Facter::Core::Execution.exec(hostname_command, :on_fail => nil) \ + if name = Facter::Core::Execution.exec(hostname_command) \ and name =~ /.*?\.(.+$)/ return_value = $1 - elsif Facter.value(:kernel) != "windows" and domain = Facter::Core::Execution.exec('dnsdomainname 2> /dev/null', :on_fail => nil) \ + elsif Facter.value(:kernel) != "windows" and domain = Facter::Core::Execution.exec('dnsdomainname 2> /dev/null') \ and domain =~ /.+/ return_value = domain diff --git a/spec/unit/domain_spec.rb b/spec/unit/domain_spec.rb index f7dae51776..c3701d51cb 100755 --- a/spec/unit/domain_spec.rb +++ b/spec/unit/domain_spec.rb @@ -24,11 +24,11 @@ def resolv_conf_contains(*lines) let(:dnsdomain_command) { "dnsdomainname 2> /dev/null" } def the_hostname_is(value) - Facter::Core::Execution.stubs(:exec).with(hostname_command, {:on_fail => nil}).returns(value) + Facter::Core::Execution.stubs(:exec).with(hostname_command).returns(value) end def the_dnsdomainname_is(value) - Facter::Core::Execution.stubs(:exec).with(dnsdomain_command, {:on_fail => nil}).returns(value) + Facter::Core::Execution.stubs(:exec).with(dnsdomain_command).returns(value) end before do @@ -197,7 +197,7 @@ def expects_dnsdomains(domains) it "should return nil" do expects_dnsdomains([nil]) - Facter::Core::Execution.stubs(:exec).with(hostname_command, {:on_fail => nil}).returns('sometest') + Facter::Core::Execution.stubs(:exec).with(hostname_command).returns('sometest') FileTest.stubs(:exists?).with("/etc/resolv.conf").returns(false) Facter.fact(:domain).value.should be_nil @@ -288,8 +288,8 @@ def expects_dnsdomains(domains) describe scenario[:scenario] do before(:each) do - Facter::Core::Execution.stubs(:exec).with("hostname -f 2> /dev/null", {:on_fail => nil}).returns(scenario[:hostname]) - Facter::Core::Execution.stubs(:exec).with("dnsdomainname 2> /dev/null", {:on_fail => nil}).returns(scenario[:dnsdomainname]) + Facter::Core::Execution.stubs(:exec).with("hostname -f 2> /dev/null").returns(scenario[:hostname]) + Facter::Core::Execution.stubs(:exec).with("dnsdomainname 2> /dev/null").returns(scenario[:dnsdomainname]) resolv_conf_contains( "search #{scenario[:resolve_search]}", "domain #{scenario[:resolve_domain]}" From c376750ddf1da1404c5a986a74607932e39f4e12 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 24 Mar 2014 16:23:25 -0700 Subject: [PATCH 1723/3753] (maint) Update fedora test version to 19 --- .../config/{fedora-18.cfg => fedora-19.cfg} | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) rename acceptance/config/{fedora-18.cfg => fedora-19.cfg} (55%) diff --git a/acceptance/config/fedora-18.cfg b/acceptance/config/fedora-19.cfg similarity index 55% rename from acceptance/config/fedora-18.cfg rename to acceptance/config/fedora-19.cfg index ee6d8eb86d..2a999c4952 100644 --- a/acceptance/config/fedora-18.cfg +++ b/acceptance/config/fedora-19.cfg @@ -1,22 +1,20 @@ HOSTS: - fedora-18-64-1: + master: roles: - master - - database - agent - platform: fedora-18-x86_64 - template: fedora-18-x86_64 + platform: fedora-19-x86_64 hypervisor: vcloud - fedora-18-64-2: + template: Delivery/Quality Assurance/Templates/vCloud/fedora-19-x86_64 + agent: roles: - agent - platform: fedora-18-x86_64 - template: fedora-18-x86_64 + platform: fedora-19-i386 hypervisor: vcloud + template: Delivery/Quality Assurance/Templates/vCloud/fedora-19-i386 CONFIG: - nfs_server: none - consoleport: 443 + filecount: 12 datastore: instance0 - folder: Delivery/Quality Assurance/FOSS/Dynamic resourcepool: delivery/Quality Assurance/FOSS/Dynamic + folder: Delivery/Quality Assurance/FOSS/Dynamic pooling_api: http://vcloud.delivery.puppetlabs.net/ From d9a651858dbc97beef767651390ac45599aeb16b Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 24 Mar 2014 14:32:51 -0700 Subject: [PATCH 1724/3753] (maint) Pin rake to the last 1.8 compatible version --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 4135b39319..d7eb8d606a 100644 --- a/Gemfile +++ b/Gemfile @@ -10,7 +10,7 @@ platforms :ruby do end group :development, :test do - gem 'rake' + gem 'rake', "~> 10.1.0" gem 'rspec', "~> 2.11.0" gem 'mocha', "~> 0.10.5" gem 'json', "~> 1.7", :platforms => :ruby From cb056e3e15260d6810129b21972b8a771d8f2b6c Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 24 Mar 2014 16:23:25 -0700 Subject: [PATCH 1725/3753] (maint) Update fedora test version to 19 --- .../config/{fedora-18.cfg => fedora-19.cfg} | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) rename acceptance/config/{fedora-18.cfg => fedora-19.cfg} (55%) diff --git a/acceptance/config/fedora-18.cfg b/acceptance/config/fedora-19.cfg similarity index 55% rename from acceptance/config/fedora-18.cfg rename to acceptance/config/fedora-19.cfg index ee6d8eb86d..2a999c4952 100644 --- a/acceptance/config/fedora-18.cfg +++ b/acceptance/config/fedora-19.cfg @@ -1,22 +1,20 @@ HOSTS: - fedora-18-64-1: + master: roles: - master - - database - agent - platform: fedora-18-x86_64 - template: fedora-18-x86_64 + platform: fedora-19-x86_64 hypervisor: vcloud - fedora-18-64-2: + template: Delivery/Quality Assurance/Templates/vCloud/fedora-19-x86_64 + agent: roles: - agent - platform: fedora-18-x86_64 - template: fedora-18-x86_64 + platform: fedora-19-i386 hypervisor: vcloud + template: Delivery/Quality Assurance/Templates/vCloud/fedora-19-i386 CONFIG: - nfs_server: none - consoleport: 443 + filecount: 12 datastore: instance0 - folder: Delivery/Quality Assurance/FOSS/Dynamic resourcepool: delivery/Quality Assurance/FOSS/Dynamic + folder: Delivery/Quality Assurance/FOSS/Dynamic pooling_api: http://vcloud.delivery.puppetlabs.net/ From 22b0488b8a03ad036f6d716b9d94460d05c31ff8 Mon Sep 17 00:00:00 2001 From: Melissa Stone Date: Tue, 25 Mar 2014 10:26:13 -0700 Subject: [PATCH 1726/3753] (packaging) Update FACTERVERSION to 2.0.1-rc3 --- lib/facter/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/version.rb b/lib/facter/version.rb index 0d72561341..1563ee9593 100644 --- a/lib/facter/version.rb +++ b/lib/facter/version.rb @@ -1,6 +1,6 @@ module Facter if not defined? FACTERVERSION then - FACTERVERSION = '2.0.1-rc2' + FACTERVERSION = '2.0.1-rc3' end # Returns the running version of Facter. From 608c14cae7751a124f46e3a5ad6da08e8aa92225 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 25 Mar 2014 16:23:13 -0700 Subject: [PATCH 1727/3753] (maint) Update gid_spec to use Execution.execute --- lib/facter/gid.rb | 3 --- spec/unit/gid_spec.rb | 6 ++---- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/lib/facter/gid.rb b/lib/facter/gid.rb index 93f9ac82cb..e347c172f8 100644 --- a/lib/facter/gid.rb +++ b/lib/facter/gid.rb @@ -8,8 +8,5 @@ # Not supported in windows yet. Facter.add(:gid) do - confine do - Facter::Core::Execution.which('id') - end setcode 'id -ng' end diff --git a/spec/unit/gid_spec.rb b/spec/unit/gid_spec.rb index 7a63bd8e1f..1069af653e 100755 --- a/spec/unit/gid_spec.rb +++ b/spec/unit/gid_spec.rb @@ -4,8 +4,7 @@ describe "on systems with id" do it "should return the current group" do - Facter::Core::Execution.expects(:which).with('id').returns(true) - Facter::Core::Execution.expects(:exec).once.with('id -ng', anything).returns 'bar' + Facter::Core::Execution.expects(:execute).once.with('id -ng', anything).returns 'bar' Facter.fact(:gid).value.should == 'bar' end @@ -13,8 +12,7 @@ describe "on systems without id" do it "is not supported" do - Facter::Core::Execution.expects(:which).with('id').returns(false) - + Facter::Core::Execution.expects(:execute).with('id -ng', anything).returns(nil) Facter.fact(:gid).value.should == nil end end From aa2b9560b8c87e1dcb5288f0737008bf6e1321e9 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Wed, 26 Mar 2014 12:21:25 -0700 Subject: [PATCH 1728/3753] (FACT-447) Require Ruby 1.8.7 in redhat rpm spec --- ext/redhat/facter.spec.erb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/redhat/facter.spec.erb b/ext/redhat/facter.spec.erb index aeb1289421..288426bd54 100644 --- a/ext/redhat/facter.spec.erb +++ b/ext/redhat/facter.spec.erb @@ -24,7 +24,7 @@ Source0: http://puppetlabs.com/downloads/%{name}/%{name}-%{realversion}.t BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) -Requires: ruby >= 1.8.5 +Requires: ruby >= 1.8.7 Requires: which # dmidecode and pciutils are not available on all arches %ifarch %ix86 x86_64 ia64 @@ -32,8 +32,8 @@ Requires: dmidecode Requires: pciutils %endif Requires: virt-what -Requires: ruby >= 1.8.5 -BuildRequires: ruby >= 1.8.5 +Requires: ruby >= 1.8.7 +BuildRequires: ruby >= 1.8.7 # In Fedora 17+ or RHEL 7+ ruby-rdoc is called rubygem-rdoc %if 0%{?fedora} >= 17 || 0%{?rhel} >= 7 From 6a5e90293e9c96f8fd4f7f7baed05c14cbec3c5d Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Wed, 26 Mar 2014 12:22:39 -0700 Subject: [PATCH 1729/3753] (FACT-447) Remove 1.8.5 specific monkey patches --- lib/facter.rb | 1 - lib/facter/util/monkey_patches.rb | 8 -------- 2 files changed, 9 deletions(-) delete mode 100644 lib/facter/util/monkey_patches.rb diff --git a/lib/facter.rb b/lib/facter.rb index 4e102a872f..8566f3fd7a 100644 --- a/lib/facter.rb +++ b/lib/facter.rb @@ -34,7 +34,6 @@ module Util; end require 'facter/util/fact' require 'facter/util/collection' - require 'facter/util/monkey_patches' include Comparable include Enumerable diff --git a/lib/facter/util/monkey_patches.rb b/lib/facter/util/monkey_patches.rb deleted file mode 100644 index 54bbff83d6..0000000000 --- a/lib/facter/util/monkey_patches.rb +++ /dev/null @@ -1,8 +0,0 @@ -# This provides an alias for RbConfig to Config for versions of Ruby older then -# version 1.8.5. This allows us to use RbConfig in place of the older Config in -# our code and still be compatible with at least Ruby 1.8.1. -require 'rbconfig' - -unless defined? ::RbConfig - ::RbConfig = ::Config -end From c2bc3c5092bb70bec752aae5c4d75e7288831a17 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 6 Mar 2014 12:41:15 -0800 Subject: [PATCH 1730/3753] (FACT-375) Add ci boot from artifacts script File taken from puppet:acceptance/bin/ci-bootstrap-from-artifacts.sh@3c79f8a28ce6f9759070ec8c8f874a142aaf400e --- acceptance/bin/ci-bootstrap-from-artifacts.sh | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100755 acceptance/bin/ci-bootstrap-from-artifacts.sh diff --git a/acceptance/bin/ci-bootstrap-from-artifacts.sh b/acceptance/bin/ci-bootstrap-from-artifacts.sh new file mode 100755 index 0000000000..36fdfbd499 --- /dev/null +++ b/acceptance/bin/ci-bootstrap-from-artifacts.sh @@ -0,0 +1,49 @@ +#! /usr/bin/env bash + +############################################################################### +# Initial preparation for a ci acceptance job in Jenkins. Crucially, it +# handles the untarring of the build artifact and bundle install, getting us to +# a state where we can then bundle exec rake the particular ci:test we want to +# run. +# +# Having this checked in in a script makes it much easier to have multiple +# acceptance jobs. It must be kept agnostic between Linux/Solaris/Windows +# builds, however. + +set -x + +# Use our internal rubygems mirror for the bundle install +if [ -z $GEM_SOURCE ]; then + export GEM_SOURCE='http://rubygems.delivery.puppetlabs.net/' +fi + +echo "SHA: ${SHA}" +echo "FORK: ${FORK}" +echo "BUILD_SELECTOR: ${BUILD_SELECTOR}" +echo "PACKAGE_BUILD_STATUS: ${PACKAGE_BUILD_STATUS}" + +rm -rf acceptance +mkdir acceptance +cd acceptance +tar -xzf ../acceptance-artifacts.tar.gz + +echo "===== This artifact is from =====" +cat creator.txt + +bundle install --without=development --path=.bundle/gems + +if [[ "${platform}" =~ 'solaris' ]]; then + repo_proxy=" :repo_proxy => false," +fi + +cat > local_options.rb <<-EOF +{ + :hosts_file => 'config/nodes/${platform}.yaml', + :ssh => { + :keys => ["${HOME}/.ssh/id_rsa-old.private"], + }, +${repo_proxy} +} +EOF + +[[ (-z "${PACKAGE_BUILD_STATUS}") || ("${PACKAGE_BUILD_STATUS}" = "success") ]] || exit 1 From 724e0fe21a997a615df8f4a11ef97d8b25068016 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 6 Mar 2014 12:55:05 -0800 Subject: [PATCH 1731/3753] (FACT-375) Copy CI acceptance test artifact task File taken from puppet:tasks/ci.rake@379c5afcec3d8f0c63a78669195b240a1a4f9a89 --- tasks/ci.rake | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 tasks/ci.rake diff --git a/tasks/ci.rake b/tasks/ci.rake new file mode 100644 index 0000000000..b082a1b495 --- /dev/null +++ b/tasks/ci.rake @@ -0,0 +1,29 @@ +require 'yaml' +require 'time' + +namespace "ci" do + task :spec do + ENV["LOG_SPEC_ORDER"] = "true" + sh %{rspec -r yarjuf -f JUnit -o result.xml -fp spec} + end + + desc "Tar up the acceptance/ directory so that package test runs have tests to run against." + task :acceptance_artifacts => :tag_creator do + Dir.chdir("acceptance") do + rm_f "acceptance-artifacts.tar.gz" + sh "tar -czv --exclude .bundle -f acceptance-artifacts.tar.gz *" + end + end + + task :tag_creator do + Dir.chdir("acceptance") do + File.open('creator.txt', 'w') do |fh| + YAML.dump({ + 'creator_id' => ENV['CREATOR'] || ENV['BUILD_URL'] || 'unknown', + 'created_on' => Time.now.iso8601, + 'commit' => (`git log -1 --oneline` rescue "unknown: #{$!}") + }, fh) + end + end + end +end From 162dfed4256910c46716cebb377036905474ebdd Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 6 Mar 2014 16:20:42 -0800 Subject: [PATCH 1732/3753] (FACT-375) Copy acceptance Rakefile File taken from puppet:acceptance/Rakefile@ba3b5d9807eaba07b0117daae21b7b711ebe4e86 --- acceptance/Gemfile | 1 + acceptance/Rakefile | 318 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 319 insertions(+) create mode 100644 acceptance/Rakefile diff --git a/acceptance/Gemfile b/acceptance/Gemfile index 8878829c50..057c0ff6d7 100644 --- a/acceptance/Gemfile +++ b/acceptance/Gemfile @@ -1,6 +1,7 @@ source ENV['GEM_SOURCE'] || "https://rubygems.org/" gem "beaker", "~> 1.3.1" +gem 'rake' # json-schema does not support windows, so use the 'ruby' platform to exclude it on windows platforms :ruby do diff --git a/acceptance/Rakefile b/acceptance/Rakefile new file mode 100644 index 0000000000..7f592f7848 --- /dev/null +++ b/acceptance/Rakefile @@ -0,0 +1,318 @@ +require 'rake/clean' +require 'pp' +require 'yaml' + +ONE_DAY_IN_SECS = 24 * 60 * 60 +REPO_CONFIGS_DIR = "repo-configs" +CLEAN.include('*.tar', REPO_CONFIGS_DIR, 'merged_options.rb') + +module HarnessOptions + + DEFAULTS = { + :type => 'git', + :helper => ['lib/helper.rb'], + :tests => ['tests'], + :log_level => 'debug', + :color => false, + :root_keys => true, + :ssh => { + :keys => ["id_rsa-acceptance"], + }, + :xml => true, + :timesync => true, + :repo_proxy => true, + :add_el_extras => true, + } + + class Aggregator + attr_reader :mode + + def initialize(mode) + @mode = mode + end + + def get_options(file_path) + puts file_path + if File.exists? file_path + options = eval(File.read(file_path), binding) + else + puts "No options file found at #{File.expand_path(file_path)}" + end + options || {} + end + + def get_mode_options + get_options("./config/#{mode}/options.rb") + end + + def get_local_options + get_options("./local_options.rb") + end + + def final_options(intermediary_options = {}) + mode_options = get_mode_options + local_overrides = get_local_options + final_options = DEFAULTS.merge(mode_options) + final_options.merge!(intermediary_options) + final_options.merge!(local_overrides) + return final_options + end + end + + def self.options(mode, options) + final_options = Aggregator.new(mode).final_options(options) + final_options + end +end + +def beaker_test(mode = :packages, options = {}) + final_options = HarnessOptions.options(mode, options) + + if mode == :git + puppet_fork = ENV['FORK'] || 'puppetlabs' + git_server = ENV['GIT_SERVER'] || 'github.com' + final_options[:install] << "git://#{git_server}/#{puppet_fork}/facter.git##{sha}" + end + + options_file = 'merged_options.rb' + File.open(options_file, 'w') do |merged| + merged.puts <<-EOS +# Copy this file to local_options.rb and adjust as needed if you wish to run +# with some local overrides. +EOS + merged.puts(final_options.pretty_inspect) + end + + tests = ENV['TESTS'] || ENV['TEST'] + tests_opt = "--tests=#{tests}" if tests + + config_opt = "--hosts=#{config}" if config + + overriding_options = ENV['OPTIONS'] + + args = ["--options-file", options_file, config_opt, tests_opt, overriding_options].compact + + begin + sh("beaker", *args) + ensure + if (hosts_file = config || final_options[:hosts_file]) && hosts_file !~ /preserved_config/ + cp(hosts_file, "log/latest/config.yml") + generate_config_for_latest_hosts if final_options[:preserve_hosts] || overriding_options =~ /--preserve-hosts/ + end + mv(options_file, "log/latest") + end +end + +def generate_config_for_latest_hosts + preserved_config_hash = { 'HOSTS' => {} } + + config_hash = YAML.load_file('log/latest/config.yml').to_hash + nodes = config_hash['HOSTS'].map do |node_label,hash| + { :node_label => node_label, :platform => hash['platform'] } + end + + pre_suite_log = File.read('log/latest/pre_suite-run.log') + nodes.each do |node_info| + hostname = /^(\w+) \(#{node_info[:node_label]}\)/.match(pre_suite_log)[1] + fqdn = "#{hostname}.delivery.puppetlabs.net" + preserved_config_hash['HOSTS'][fqdn] = { + 'roles' => [ 'agent'], + 'platform' => node_info[:platform], + } + preserved_config_hash['HOSTS'][fqdn]['roles'].unshift('master') if node_info[:node_label] =~ /master/ + end + pp preserved_config_hash + + File.open('log/latest/preserved_config.yaml', 'w') do |config_file| + YAML.dump(preserved_config_hash, config_file) + end +rescue Errno::ENOENT => e + puts "Couldn't generate log #{e}" +end + +def list_preserved_configurations(secs_ago = ONE_DAY_IN_SECS) + preserved = {} + Dir.glob('log/*_*').each do |dir| + preserved_config_path = "#{dir}/preserved_config.yaml" + yesterday = Time.now - secs_ago.to_i + if preserved_config = File.exists?(preserved_config_path) + directory = File.new(dir) + if directory.ctime > yesterday + hosts = [] + preserved_config = YAML.load_file(preserved_config_path).to_hash + preserved_config['HOSTS'].each do |hostname,values| + hosts << "#{hostname}: #{values['platform']}, #{values['roles']}" + end + preserved[hosts] = directory.to_path + end + end + end + preserved.map { |k,v| [v,k] }.sort { |a,b| a[0] <=> b[0] }.reverse +end + +def list_preserved_hosts(secs_ago = ONE_DAY_IN_SECS) + hosts = Set.new + Dir.glob('log/**/pre*suite*run.log').each do |log| + yesterday = Time.now - secs_ago.to_i + File.open(log, 'r') do |file| + if file.ctime > yesterday + file.each_line do |line| + matchdata = /^(\w+) \(.*?\) \$/.match(line.encode!('UTF-8', 'UTF-8', :invalid => :replace)) + hosts.add(matchdata[1]) if matchdata + end + end + end + end + hosts +end + +# Plagiarized from Beaker::Vcloud#cleanup +def destroy_preserved_hosts(hosts = nil, secs_ago = ONE_DAY_IN_SECS) + secs_ago ||= ONE_DAY_IN_SECS + hosts ||= list_preserved_hosts(secs_ago) + + require 'beaker/hypervisor/vsphere_helper' + vsphere_credentials = VsphereHelper.load_config("#{ENV['HOME']}/.fog") + + puts "Connecting to vSphere at #{vsphere_credentials[:server]}" + + " with credentials for #{vsphere_credentials[:user]}" + + vsphere_helper = VsphereHelper.new( vsphere_credentials ) + + vm_names = hosts.to_a + pp vm_names + vms = vsphere_helper.find_vms vm_names + vm_names.each do |name| + unless vm = vms[name] + puts "Couldn't find VM #{name} in vSphere!" + next + end + + if vm.runtime.powerState == 'poweredOn' + puts "Shutting down #{vm.name}" + start = Time.now + vm.PowerOffVM_Task.wait_for_completion + puts "Spent %.2f seconds halting #{vm.name}" % (Time.now - start) + end + + start = Time.now + vm.Destroy_Task + puts "Spent %.2f seconds destroying #{vm.name}" % (Time.now - start) + end + + vsphere_helper.close +end + +def print_preserved(preserved) + preserved.each_with_index do |entry,i| + puts "##{i}: #{entry[0]}" + entry[1].each { |h| puts " #{h}" } + end +end + +def beaker_run_type + type = ENV['TYPE'] || :packages + type = type.to_sym +end + +def sha + ENV['SHA'] +end + +def config + ENV['CONFIG'] +end + +namespace :ci do + + task :check_env do + raise(USAGE) unless sha + end + + namespace :test do + + USAGE = <<-EOS +Requires commit SHA to be put under test as environment variable: SHA=''. +Also must set CONFIG=config/nodes/foo.yaml or include it in an options.rb for Beaker. +You may set TESTS=path/to/test,and/more/tests. +You may set additional Beaker OPTIONS='--more --options' +If testing from git checkouts, you may optionally set the github fork to checkout from using FORK='other-puppet-fork'. +You may also optionally set the git server to checkout from using GIT_SERVER='my.host.with.git.daemon', if you have set up a `git daemon` to pull local commits from. (In this case, the FORK should be set to the path to the repository, and you will need to allow the git daemon to serve the repo (see `git help daemon`)). +If there is a Beaker options hash in a ./local_options.rb, it will be included. Commandline options set through the above environment variables will override settings in this file. +EOS + + desc <<-EOS +Run the acceptance tests through Beaker and install packages on the configuration targets. +#{USAGE} +EOS + task :packages => 'ci:check_env' do + beaker_test + end + + desc <<-EOS +Run the acceptance tests through Beaker and install from git on the configuration targets. +#{USAGE} +EOS + task :git => 'ci:check_env' do + beaker_test(:git) + end + end + + desc "Capture the master and agent hostname from the latest log and construct a preserved_config.yaml for re-running against preserved hosts without provisioning." + task :extract_preserved_config do + generate_config_for_latest_hosts + end + + desc <<-EOS +Run an acceptance test for a given node configuration and preserve the hosts. +Defaults to a packages run, but you can set it to 'git' with TYPE='git'. +#{USAGE} + EOS + task :test_and_preserve_hosts => 'ci:check_env' do + beaker_test(beaker_run_type, :preserve_hosts => true) + end + + desc "List acceptance runs from the past day which had hosts preserved." + task :list_preserved do + preserved = list_preserved_configurations + print_preserved(preserved) + end + + desc <<-EOS +Shutdown and destroy any hosts that we have preserved for testing. These should be reaped daily by scripts, but this will free up resources immediately. +Specify a list of comma separated HOST_NAMES if you have a set of dynamic vcloud host names you want to purge outside of what can be grepped from the logs. +You can go back through the last SECS_AGO logs. Default is one day ago in secs. + EOS + task :destroy_preserved_hosts do + host_names = ENV['HOST_NAMES'].split(',') if ENV['HOST_NAMES'] + secs_ago = ENV['SECS_AGO'] + destroy_preserved_hosts(host_names, secs_ago) + end + + desc <<-EOS +Rerun an acceptance test using the last captured preserved_config.yaml to skip provisioning. +Or specify a CONFIG_NUMBER from `rake ci:list_preserved`. +Uses the setup/rsync/pre-suite to rsync the local puppet source onto master and agent. +You may specify an RSYNC_FILTER_FILE as well. +You may skip purgeing and reinstalling puppet packages by including SKIP_PACKAGE_REINSTALL. +You may skip rsyncing local puppet files over to the tests hosts by including SKIP_RSYNC. +Defaults to a packages run, but you can set it to 'git' with TYPE='git'. + EOS + task :test_against_preserved_hosts do + config_number = (ENV['CONFIG_NUMBER'] || 0).to_i + preserved = list_preserved_configurations + print_preserved(preserved) + config_path = preserved[config_number][0] + puts "Using ##{config_number}: #{config_path}" + beaker_test(beaker_run_type, + :hosts_file => "#{config_path}/preserved_config.yaml", + :no_provision => true, + :preserve_hosts => true, + :pre_suite => ['setup/rsync/pre-suite'] + ) + end +end + +task :default do + sh('rake -T') +end From 303b84bde57223c7ac1b7d767643b4226d3e162a Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Fri, 7 Mar 2014 10:37:29 -0800 Subject: [PATCH 1733/3753] (FACT-375) Update acceptance configs to use Puppet layout --- acceptance/config/{centos-5.cfg => nodes/centos5.yaml} | 0 acceptance/config/{fedora-19.cfg => nodes/fedora19.yaml} | 0 acceptance/config/{ubuntu-1004.cfg => nodes/lucid.yaml} | 0 acceptance/config/{redhat-6.cfg => nodes/rhel6.yaml} | 0 acceptance/config/{solaris-11.cfg => nodes/solaris-11.yaml} | 0 acceptance/config/{windows-2003.cfg => nodes/windows-2003.yaml} | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename acceptance/config/{centos-5.cfg => nodes/centos5.yaml} (100%) rename acceptance/config/{fedora-19.cfg => nodes/fedora19.yaml} (100%) rename acceptance/config/{ubuntu-1004.cfg => nodes/lucid.yaml} (100%) rename acceptance/config/{redhat-6.cfg => nodes/rhel6.yaml} (100%) rename acceptance/config/{solaris-11.cfg => nodes/solaris-11.yaml} (100%) rename acceptance/config/{windows-2003.cfg => nodes/windows-2003.yaml} (100%) diff --git a/acceptance/config/centos-5.cfg b/acceptance/config/nodes/centos5.yaml similarity index 100% rename from acceptance/config/centos-5.cfg rename to acceptance/config/nodes/centos5.yaml diff --git a/acceptance/config/fedora-19.cfg b/acceptance/config/nodes/fedora19.yaml similarity index 100% rename from acceptance/config/fedora-19.cfg rename to acceptance/config/nodes/fedora19.yaml diff --git a/acceptance/config/ubuntu-1004.cfg b/acceptance/config/nodes/lucid.yaml similarity index 100% rename from acceptance/config/ubuntu-1004.cfg rename to acceptance/config/nodes/lucid.yaml diff --git a/acceptance/config/redhat-6.cfg b/acceptance/config/nodes/rhel6.yaml similarity index 100% rename from acceptance/config/redhat-6.cfg rename to acceptance/config/nodes/rhel6.yaml diff --git a/acceptance/config/solaris-11.cfg b/acceptance/config/nodes/solaris-11.yaml similarity index 100% rename from acceptance/config/solaris-11.cfg rename to acceptance/config/nodes/solaris-11.yaml diff --git a/acceptance/config/windows-2003.cfg b/acceptance/config/nodes/windows-2003.yaml similarity index 100% rename from acceptance/config/windows-2003.cfg rename to acceptance/config/nodes/windows-2003.yaml From 3bbe303d46144e0aa27b74ef341822b6956bd481 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Fri, 7 Mar 2014 12:05:55 -0800 Subject: [PATCH 1734/3753] (FACT-375) Make log directory for given job. This shouldn't be needed. --- acceptance/bin/ci-bootstrap-from-artifacts.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/acceptance/bin/ci-bootstrap-from-artifacts.sh b/acceptance/bin/ci-bootstrap-from-artifacts.sh index 36fdfbd499..a40b58f0eb 100755 --- a/acceptance/bin/ci-bootstrap-from-artifacts.sh +++ b/acceptance/bin/ci-bootstrap-from-artifacts.sh @@ -26,6 +26,7 @@ rm -rf acceptance mkdir acceptance cd acceptance tar -xzf ../acceptance-artifacts.tar.gz +mkdir -p log/latest echo "===== This artifact is from =====" cat creator.txt From b8bdac30bf0ac4c0e6b2e01c80a88155c2181c77 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Fri, 7 Mar 2014 15:06:39 -0800 Subject: [PATCH 1735/3753] (FACT-375) Install facter from packages in pre-suite --- acceptance/config/packages/options.rb | 5 +++ acceptance/lib/helper.rb | 1 + .../setup/packages/pre-suite/010_Install.rb | 32 +++++++++++++++++++ 3 files changed, 38 insertions(+) create mode 100644 acceptance/config/packages/options.rb create mode 100644 acceptance/lib/helper.rb create mode 100644 acceptance/setup/packages/pre-suite/010_Install.rb diff --git a/acceptance/config/packages/options.rb b/acceptance/config/packages/options.rb new file mode 100644 index 0000000000..5b9bd674b1 --- /dev/null +++ b/acceptance/config/packages/options.rb @@ -0,0 +1,5 @@ +{ + :pre_suite => [ + 'setup/packages/pre-suite/010_Install.rb', + ], +} diff --git a/acceptance/lib/helper.rb b/acceptance/lib/helper.rb new file mode 100644 index 0000000000..c2b5df8706 --- /dev/null +++ b/acceptance/lib/helper.rb @@ -0,0 +1 @@ +$LOAD_PATH << File.expand_path(File.dirname(__FILE__)) diff --git a/acceptance/setup/packages/pre-suite/010_Install.rb b/acceptance/setup/packages/pre-suite/010_Install.rb new file mode 100644 index 0000000000..45563528e6 --- /dev/null +++ b/acceptance/setup/packages/pre-suite/010_Install.rb @@ -0,0 +1,32 @@ +require 'puppet/acceptance/install_utils' + +extend Puppet::Acceptance::InstallUtils + +test_name "Install Packages" + +step "Install repositories on target machines..." do + + sha = ENV['SHA'] + repo_configs_dir = 'repo-configs' + + hosts.each do |host| + install_repos_on(host, sha, repo_configs_dir) + end +end + +PACKAGES = { + :redhat => [ + 'facter', + ], + :debian => [ + 'facter', + ], +# :solaris => [ +# 'puppet', +# ], +# :windows => [ +# 'puppet', +# ], +} + +install_packages_on(hosts, PACKAGES) From 362ca220639bf0cc7b378ae817aa2c175c8b747f Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Fri, 7 Mar 2014 16:20:19 -0800 Subject: [PATCH 1736/3753] (FACT-375) Add beaker dsl install utils --- acceptance/setup/packages/pre-suite/010_Install.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/acceptance/setup/packages/pre-suite/010_Install.rb b/acceptance/setup/packages/pre-suite/010_Install.rb index 45563528e6..fb67fcefe4 100644 --- a/acceptance/setup/packages/pre-suite/010_Install.rb +++ b/acceptance/setup/packages/pre-suite/010_Install.rb @@ -1,6 +1,7 @@ require 'puppet/acceptance/install_utils' - extend Puppet::Acceptance::InstallUtils +require 'beaker/dsl/install_utils' +extend Beaker::DSL::InstallUtils test_name "Install Packages" From 6b5f05265d9f535831ba495e5c760ae0cd02de2f Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 17 Mar 2014 10:30:02 -0700 Subject: [PATCH 1737/3753] (FACT-375) Update install_utils from puppet project --- .../lib/puppet/acceptance/install_utils.rb | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/acceptance/lib/puppet/acceptance/install_utils.rb b/acceptance/lib/puppet/acceptance/install_utils.rb index cca5b7741b..5df4235748 100644 --- a/acceptance/lib/puppet/acceptance/install_utils.rb +++ b/acceptance/lib/puppet/acceptance/install_utils.rb @@ -49,6 +49,107 @@ def install_packages_on(hosts, package_hash, options = {}) end return true end + + def fetch(base_url, file_name, dst_dir) + FileUtils.makedirs(dst_dir) + src = "#{base_url}/#{file_name}" + dst = File.join(dst_dir, file_name) + if File.exists?(dst) + logger.notify "Already fetched #{dst}" + else + logger.notify "Fetching: #{src}" + logger.notify " and saving to #{dst}" + open(src) do |remote| + File.open(dst, "w") do |file| + FileUtils.copy_stream(remote, file) + end + end + end + return dst + end + + def stop_firewall_on(host) + case host['platform'] + when /debian/ + on host, 'iptables -F' + when /fedora|el-7/ + on host, puppet('resource', 'service', 'firewalld', 'ensure=stopped') + when /el|centos/ + on host, puppet('resource', 'service', 'iptables', 'ensure=stopped') + when /ubuntu/ + on host, puppet('resource', 'service', 'ufw', 'ensure=stopped') + else + logger.notify("Not sure how to clear firewall on #{host['platform']}") + end + end + + def install_repos_on(host, sha, repo_configs_dir) + platform = host['platform'] + platform_configs_dir = File.join(repo_configs_dir,platform) + + case platform + when /^(fedora|el|centos)-(\d+)-(.+)$/ + variant = (($1 == 'centos') ? 'el' : $1) + fedora_prefix = ((variant == 'fedora') ? 'f' : '') + version = $2 + arch = $3 + + rpm = fetch( + "http://yum.puppetlabs.com/", + "puppetlabs-release-%s-%s.noarch.rpm" % [variant, version], + platform_configs_dir + ) + + pattern = "pl-puppet-%s-%s-%s%s-%s.repo" + repo_filename = pattern % [ + sha, + variant, + fedora_prefix, + version, + arch + ] + repo = fetch( + "http://builds.puppetlabs.lan/puppet/%s/repo_configs/rpm/" % sha, + repo_filename, + platform_configs_dir + ) + + on host, "rm -rf /root/*.repo; rm -rf /root/*.rpm" + + scp_to host, rpm, '/root' + scp_to host, repo, '/root' + + on host, "mv /root/*.repo /etc/yum.repos.d" + on host, "rpm -Uvh --force /root/*.rpm" + + when /^(debian|ubuntu)-([^-]+)-(.+)$/ + variant = $1 + version = $2 + arch = $3 + + deb = fetch( + "http://apt.puppetlabs.com/", + "puppetlabs-release-%s.deb" % version, + platform_configs_dir + ) + + list = fetch( + "http://builds.puppetlabs.lan/puppet/%s/repo_configs/deb/" % sha, + "pl-puppet-%s-%s.list" % [sha, version], + platform_configs_dir + ) + + on host, "rm -rf /root/*.list; rm -rf /root/*.deb" + + scp_to host, deb, '/root' + scp_to host, list, '/root' + + on host, "mv /root/*.list /etc/apt/sources.list.d" + on host, "dpkg -i --force-all /root/*.deb" + else + host.logger.notify("No repository installation step for #{platform} yet...") + end + end end end end From a2c1fa020a9ac8cfbd7d368c74f205c65ebbd780 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 17 Mar 2014 11:12:10 -0700 Subject: [PATCH 1738/3753] (FACT-375) Customize install_utils packages for facter --- acceptance/lib/puppet/acceptance/install_utils.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/acceptance/lib/puppet/acceptance/install_utils.rb b/acceptance/lib/puppet/acceptance/install_utils.rb index 5df4235748..f8350ec8fb 100644 --- a/acceptance/lib/puppet/acceptance/install_utils.rb +++ b/acceptance/lib/puppet/acceptance/install_utils.rb @@ -100,7 +100,7 @@ def install_repos_on(host, sha, repo_configs_dir) platform_configs_dir ) - pattern = "pl-puppet-%s-%s-%s%s-%s.repo" + pattern = "pl-facter-%s-%s-%s%s-%s.repo" repo_filename = pattern % [ sha, variant, @@ -109,7 +109,7 @@ def install_repos_on(host, sha, repo_configs_dir) arch ] repo = fetch( - "http://builds.puppetlabs.lan/puppet/%s/repo_configs/rpm/" % sha, + "http://builds.puppetlabs.lan/facter/%s/repo_configs/rpm/" % sha, repo_filename, platform_configs_dir ) @@ -134,8 +134,8 @@ def install_repos_on(host, sha, repo_configs_dir) ) list = fetch( - "http://builds.puppetlabs.lan/puppet/%s/repo_configs/deb/" % sha, - "pl-puppet-%s-%s.list" % [sha, version], + "http://builds.puppetlabs.lan/facter/%s/repo_configs/deb/" % sha, + "pl-facter-%s-%s.list" % [sha, version], platform_configs_dir ) From a8ba8df74c26ea5df76982c8446e65cc734c97ce Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 17 Mar 2014 11:50:38 -0700 Subject: [PATCH 1739/3753] (FACT-375) Include facter schema for acceptance tests In order to run acceptance tests the acceptance/ and schema/ directories need to be included in acceptance-artifacts.tar.gz. Originally we were cding to acceptance/ and tarring up everything in that directory which made it difficult to include another top level directory. This commit changes the logic to tar up the acceptance/ and schema/ directories from the project root so that we can easily preserve the original directory structure. --- acceptance/bin/ci-bootstrap-from-artifacts.sh | 6 ++---- tasks/ci.rake | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/acceptance/bin/ci-bootstrap-from-artifacts.sh b/acceptance/bin/ci-bootstrap-from-artifacts.sh index a40b58f0eb..9f6a81df44 100755 --- a/acceptance/bin/ci-bootstrap-from-artifacts.sh +++ b/acceptance/bin/ci-bootstrap-from-artifacts.sh @@ -23,9 +23,7 @@ echo "BUILD_SELECTOR: ${BUILD_SELECTOR}" echo "PACKAGE_BUILD_STATUS: ${PACKAGE_BUILD_STATUS}" rm -rf acceptance -mkdir acceptance -cd acceptance -tar -xzf ../acceptance-artifacts.tar.gz +tar -xzf acceptance-artifacts.tar.gz mkdir -p log/latest echo "===== This artifact is from =====" @@ -37,7 +35,7 @@ if [[ "${platform}" =~ 'solaris' ]]; then repo_proxy=" :repo_proxy => false," fi -cat > local_options.rb <<-EOF +cat > acceptance/local_options.rb <<-EOF { :hosts_file => 'config/nodes/${platform}.yaml', :ssh => { diff --git a/tasks/ci.rake b/tasks/ci.rake index b082a1b495..e758138523 100644 --- a/tasks/ci.rake +++ b/tasks/ci.rake @@ -9,10 +9,8 @@ namespace "ci" do desc "Tar up the acceptance/ directory so that package test runs have tests to run against." task :acceptance_artifacts => :tag_creator do - Dir.chdir("acceptance") do - rm_f "acceptance-artifacts.tar.gz" - sh "tar -czv --exclude .bundle -f acceptance-artifacts.tar.gz *" - end + rm_f "acceptance/acceptance-artifacts.tar.gz" + sh "tar -czv --exclude acceptance/.bundle -f acceptance-artifacts.tar.gz acceptance schema" end task :tag_creator do From 1d75e488f7530be3c10d86de5dce2d42705f2524 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 17 Mar 2014 15:13:22 -0700 Subject: [PATCH 1740/3753] (FACT-375) Update acceptance configs for Debian/Ubuntu --- acceptance/config/nodes/lucid.yaml | 18 ++++++++---------- acceptance/config/nodes/precise.yaml | 20 ++++++++++++++++++++ acceptance/config/nodes/squeeze.yaml | 20 ++++++++++++++++++++ acceptance/config/nodes/wheezy.yaml | 20 ++++++++++++++++++++ 4 files changed, 68 insertions(+), 10 deletions(-) create mode 100644 acceptance/config/nodes/precise.yaml create mode 100644 acceptance/config/nodes/squeeze.yaml create mode 100644 acceptance/config/nodes/wheezy.yaml diff --git a/acceptance/config/nodes/lucid.yaml b/acceptance/config/nodes/lucid.yaml index acf780d981..a9074eb3fb 100644 --- a/acceptance/config/nodes/lucid.yaml +++ b/acceptance/config/nodes/lucid.yaml @@ -1,22 +1,20 @@ HOSTS: - ubuntu-1004-64-1: + master: roles: - master - - database - agent - platform: ubuntu-10.04-amd64 - template: ubuntu-1004-x86_64 + platform: ubuntu-lucid-x86_64 hypervisor: vcloud - ubuntu-1004-32-1: + template: Delivery/Quality Assurance/Templates/vCloud/ubuntu-1004-x86_64 + agent: roles: - agent - platform: ubuntu-10.04-i386 - template: ubuntu-1004-i386 + platform: ubuntu-lucid-i386 hypervisor: vcloud + template: Delivery/Quality Assurance/Templates/vCloud/ubuntu-1004-i386 CONFIG: - nfs_server: none - consoleport: 443 + filecount: 12 datastore: instance0 - folder: Delivery/Quality Assurance/FOSS/Dynamic resourcepool: delivery/Quality Assurance/FOSS/Dynamic + folder: Delivery/Quality Assurance/FOSS/Dynamic pooling_api: http://vcloud.delivery.puppetlabs.net/ diff --git a/acceptance/config/nodes/precise.yaml b/acceptance/config/nodes/precise.yaml new file mode 100644 index 0000000000..22793b50a4 --- /dev/null +++ b/acceptance/config/nodes/precise.yaml @@ -0,0 +1,20 @@ +HOSTS: + master: + roles: + - master + - agent + platform: ubuntu-precise-x86_64 + hypervisor: vcloud + template: Delivery/Quality Assurance/Templates/vCloud/ubuntu-1204-x86_64 + agent: + roles: + - agent + platform: ubuntu-precise-i386 + hypervisor: vcloud + template: Delivery/Quality Assurance/Templates/vCloud/ubuntu-1204-i386 +CONFIG: + filecount: 12 + datastore: instance0 + resourcepool: delivery/Quality Assurance/FOSS/Dynamic + folder: Delivery/Quality Assurance/FOSS/Dynamic + pooling_api: http://vcloud.delivery.puppetlabs.net/ diff --git a/acceptance/config/nodes/squeeze.yaml b/acceptance/config/nodes/squeeze.yaml new file mode 100644 index 0000000000..d78609f58b --- /dev/null +++ b/acceptance/config/nodes/squeeze.yaml @@ -0,0 +1,20 @@ +HOSTS: + master: + roles: + - master + - agent + platform: debian-squeeze-x86_64 + hypervisor: vcloud + template: Delivery/Quality Assurance/Templates/vCloud/debian-6-x86_64 + agent: + roles: + - agent + platform: debian-squeeze-i386 + hypervisor: vcloud + template: Delivery/Quality Assurance/Templates/vCloud/debian-6-i386 +CONFIG: + filecount: 12 + datastore: instance0 + resourcepool: delivery/Quality Assurance/FOSS/Dynamic + folder: Delivery/Quality Assurance/FOSS/Dynamic + pooling_api: http://vcloud.delivery.puppetlabs.net/ diff --git a/acceptance/config/nodes/wheezy.yaml b/acceptance/config/nodes/wheezy.yaml new file mode 100644 index 0000000000..e805be6552 --- /dev/null +++ b/acceptance/config/nodes/wheezy.yaml @@ -0,0 +1,20 @@ +HOSTS: + master: + roles: + - master + - agent + platform: debian-wheezy-x86_64 + hypervisor: vcloud + template: Delivery/Quality Assurance/Templates/vCloud/debian-7-x86_64 + agent: + roles: + - agent + platform: debian-wheezy-i386 + hypervisor: vcloud + template: Delivery/Quality Assurance/Templates/vCloud/debian-7-i386 +CONFIG: + filecount: 12 + datastore: instance0 + resourcepool: delivery/Quality Assurance/FOSS/Dynamic + folder: Delivery/Quality Assurance/FOSS/Dynamic + pooling_api: http://vcloud.delivery.puppetlabs.net/ From 72923507f6993e4856e74e72cb5ed9c6ab8cdf66 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 18 Mar 2014 10:50:28 -0700 Subject: [PATCH 1741/3753] (FACT-375) Add rhel7 to acceptance configs --- acceptance/config/nodes/rhel7.yaml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 acceptance/config/nodes/rhel7.yaml diff --git a/acceptance/config/nodes/rhel7.yaml b/acceptance/config/nodes/rhel7.yaml new file mode 100644 index 0000000000..b5706dd475 --- /dev/null +++ b/acceptance/config/nodes/rhel7.yaml @@ -0,0 +1,20 @@ +HOSTS: + master: + roles: + - master + - agent + platform: el-7-x86_64 + hypervisor: vcloud + template: Delivery/Quality Assurance/Templates/vCloud/redhat-7-x86_64 + agent: + roles: + - agent + platform: el-7-x86_64 + hypervisor: vcloud + template: Delivery/Quality Assurance/Templates/vCloud/redhat-7-x86_64 +CONFIG: + filecount: 12 + datastore: instance0 + resourcepool: delivery/Quality Assurance/FOSS/Dynamic + folder: Delivery/Quality Assurance/FOSS/Dynamic + pooling_api: http://vcloud.delivery.puppetlabs.net/ From ea1aab91d601aa7c0cb4e6bb0519c7b8869b4824 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 18 Mar 2014 10:52:09 -0700 Subject: [PATCH 1742/3753] (FACT-375) add acceptance configs for fedora{19,20} --- acceptance/config/nodes/fedora20.yaml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 acceptance/config/nodes/fedora20.yaml diff --git a/acceptance/config/nodes/fedora20.yaml b/acceptance/config/nodes/fedora20.yaml new file mode 100644 index 0000000000..9a7bb3a806 --- /dev/null +++ b/acceptance/config/nodes/fedora20.yaml @@ -0,0 +1,20 @@ +HOSTS: + master: + roles: + - master + - agent + platform: fedora-20-x86_64 + hypervisor: vcloud + template: Delivery/Quality Assurance/Templates/vCloud/fedora-20-x86_64 + agent: + roles: + - agent + platform: fedora-20-i386 + hypervisor: vcloud + template: Delivery/Quality Assurance/Templates/vCloud/fedora-20-i386 +CONFIG: + filecount: 12 + datastore: instance0 + resourcepool: delivery/Quality Assurance/FOSS/Dynamic + folder: Delivery/Quality Assurance/FOSS/Dynamic + pooling_api: http://vcloud.delivery.puppetlabs.net/ From ac66d2299de142c38f0ecf1175994593ae22b01a Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 18 Mar 2014 10:52:44 -0700 Subject: [PATCH 1743/3753] (FACT-375) Add acceptance configs for solaris 10 --- acceptance/config/nodes/solaris10.yaml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 acceptance/config/nodes/solaris10.yaml diff --git a/acceptance/config/nodes/solaris10.yaml b/acceptance/config/nodes/solaris10.yaml new file mode 100644 index 0000000000..d35c4bbabc --- /dev/null +++ b/acceptance/config/nodes/solaris10.yaml @@ -0,0 +1,20 @@ +HOSTS: + master: + roles: + - master + - agent + platform: solaris-10-x86_64 + hypervisor: vcloud + template: Delivery/Quality Assurance/Templates/vCloud/solaris-10-x86_64 + agent: + roles: + - agent + platform: solaris-10-x86_64 + hypervisor: vcloud + template: Delivery/Quality Assurance/Templates/vCloud/solaris-10-x86_64 +CONFIG: + filecount: 12 + datastore: instance0 + resourcepool: delivery/Quality Assurance/FOSS/Dynamic + folder: Delivery/Quality Assurance/FOSS/Dynamic + pooling_api: http://vcloud.delivery.puppetlabs.net/ From 31dfa491c2370b4c607783e63bac721186689d61 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 18 Mar 2014 11:38:08 -0700 Subject: [PATCH 1744/3753] (FACT-375) Reorganize acceptance pre-suite --- acceptance/config/git/options.rb | 6 ++++++ acceptance/setup/{ => common}/00_EnvSetup.rb | 0 acceptance/setup/{ => git/pre-suite}/01_TestSetup.rb | 0 3 files changed, 6 insertions(+) create mode 100644 acceptance/config/git/options.rb rename acceptance/setup/{ => common}/00_EnvSetup.rb (100%) rename acceptance/setup/{ => git/pre-suite}/01_TestSetup.rb (100%) diff --git a/acceptance/config/git/options.rb b/acceptance/config/git/options.rb new file mode 100644 index 0000000000..31d2cddb39 --- /dev/null +++ b/acceptance/config/git/options.rb @@ -0,0 +1,6 @@ +{ + :pre_suite => [ + 'setup/common/00_EnvSetup.rb', + 'setup/git/pre-suite/01_TestSetup.rb', + ], +} diff --git a/acceptance/setup/00_EnvSetup.rb b/acceptance/setup/common/00_EnvSetup.rb similarity index 100% rename from acceptance/setup/00_EnvSetup.rb rename to acceptance/setup/common/00_EnvSetup.rb diff --git a/acceptance/setup/01_TestSetup.rb b/acceptance/setup/git/pre-suite/01_TestSetup.rb similarity index 100% rename from acceptance/setup/01_TestSetup.rb rename to acceptance/setup/git/pre-suite/01_TestSetup.rb From 8549751739efa1096311ec60f0d8fc64ae5a9d1a Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 18 Mar 2014 14:42:33 -0700 Subject: [PATCH 1745/3753] (FACT-375) Install json from system packages in acceptance tests --- acceptance/config/packages/options.rb | 1 + acceptance/setup/common/00_EnvSetup.rb | 2 ++ acceptance/tests/facter_json_output_validates.rb | 3 --- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/acceptance/config/packages/options.rb b/acceptance/config/packages/options.rb index 5b9bd674b1..a57af701be 100644 --- a/acceptance/config/packages/options.rb +++ b/acceptance/config/packages/options.rb @@ -1,5 +1,6 @@ { :pre_suite => [ + 'setup/common/00_EnvSetup.rb', 'setup/packages/pre-suite/010_Install.rb', ], } diff --git a/acceptance/setup/common/00_EnvSetup.rb b/acceptance/setup/common/00_EnvSetup.rb index 889d6cc4b8..f75b55d303 100644 --- a/acceptance/setup/common/00_EnvSetup.rb +++ b/acceptance/setup/common/00_EnvSetup.rb @@ -11,10 +11,12 @@ :redhat => [ 'git', 'ruby', + 'rubygem-json', ], :debian => [ ['git', 'git-core'], 'ruby', + 'libjson-ruby', ], :solaris => [ ['git', 'developer/versioning/git'], diff --git a/acceptance/tests/facter_json_output_validates.rb b/acceptance/tests/facter_json_output_validates.rb index a2c320d7d6..3a06c61dd3 100644 --- a/acceptance/tests/facter_json_output_validates.rb +++ b/acceptance/tests/facter_json_output_validates.rb @@ -4,9 +4,6 @@ test_name "Running facter --json should validate against the schema" agents.each do |agent| - step "Agent #{agent}: Install json gem (needed on older platforms)" - on(agent, "gem install json") unless agent['platform'] =~ /windows/ - step "Agent #{agent}: run 'facter --json' and validate" on(agent, facter('--json')) do schema = JSON.parse(File.read("../schema/facter.json")) From df687a4a3b35eada2e9397c30ec9b5911f01401c Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 18 Mar 2014 15:44:08 -0700 Subject: [PATCH 1746/3753] (FACT-375) cd into acceptance dir when initializing --- acceptance/bin/ci-bootstrap-from-artifacts.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/acceptance/bin/ci-bootstrap-from-artifacts.sh b/acceptance/bin/ci-bootstrap-from-artifacts.sh index 9f6a81df44..2b55a2e1ee 100755 --- a/acceptance/bin/ci-bootstrap-from-artifacts.sh +++ b/acceptance/bin/ci-bootstrap-from-artifacts.sh @@ -24,6 +24,8 @@ echo "PACKAGE_BUILD_STATUS: ${PACKAGE_BUILD_STATUS}" rm -rf acceptance tar -xzf acceptance-artifacts.tar.gz +cd acceptance + mkdir -p log/latest echo "===== This artifact is from =====" @@ -35,7 +37,7 @@ if [[ "${platform}" =~ 'solaris' ]]; then repo_proxy=" :repo_proxy => false," fi -cat > acceptance/local_options.rb <<-EOF +cat > local_options.rb <<-EOF { :hosts_file => 'config/nodes/${platform}.yaml', :ssh => { From adf1c2bc188c3182f6692e0a1deb63d01a288872 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Wed, 19 Mar 2014 13:09:20 -0700 Subject: [PATCH 1747/3753] (FACT-375) Update apt after installing new repo --- acceptance/lib/puppet/acceptance/install_utils.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/acceptance/lib/puppet/acceptance/install_utils.rb b/acceptance/lib/puppet/acceptance/install_utils.rb index f8350ec8fb..5c895cf23e 100644 --- a/acceptance/lib/puppet/acceptance/install_utils.rb +++ b/acceptance/lib/puppet/acceptance/install_utils.rb @@ -146,6 +146,7 @@ def install_repos_on(host, sha, repo_configs_dir) on host, "mv /root/*.list /etc/apt/sources.list.d" on host, "dpkg -i --force-all /root/*.deb" + on host, "apt-get update" else host.logger.notify("No repository installation step for #{platform} yet...") end From 5c721cf1f8e35083f352ef740950af3c64fd769a Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 27 Mar 2014 14:37:42 -0700 Subject: [PATCH 1748/3753] (packaging) Update FACTERVERSION to 2.0.1-rc4 --- lib/facter/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/version.rb b/lib/facter/version.rb index 1563ee9593..328a21ae68 100644 --- a/lib/facter/version.rb +++ b/lib/facter/version.rb @@ -1,6 +1,6 @@ module Facter if not defined? FACTERVERSION then - FACTERVERSION = '2.0.1-rc3' + FACTERVERSION = '2.0.1-rc4' end # Returns the running version of Facter. From 024289d72a97991454552e1f050ae61068dbb034 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Fri, 22 Nov 2013 15:38:01 -0800 Subject: [PATCH 1749/3753] (FACT-429) (#23269) Handle non-utf8 in dmidecode/smbios output Some hardware can emit DMI tables that contain arbitrary binary data, which can cause string operations like String#split to fail if the string is UTF-8. This ensures that the output of dmidecode or smbios is always handled as a binary string. --- lib/facter/util/manufacturer.rb | 26 +- .../unit/util/manufacturer/smartos_smbios | 533 ++++++++++++++++++ spec/unit/util/manufacturer_spec.rb | 11 + 3 files changed, 556 insertions(+), 14 deletions(-) create mode 100644 spec/fixtures/unit/util/manufacturer/smartos_smbios diff --git a/lib/facter/util/manufacturer.rb b/lib/facter/util/manufacturer.rb index 6f93e6d9ed..2622d5dc25 100644 --- a/lib/facter/util/manufacturer.rb +++ b/lib/facter/util/manufacturer.rb @@ -6,25 +6,23 @@ module Facter::Manufacturer def self.get_dmi_table() case Facter.value(:kernel) when 'Linux', 'GNU/kFreeBSD' - return nil unless FileTest.exists?("/usr/sbin/dmidecode") - - output=%x{/usr/sbin/dmidecode 2>/dev/null} + cmd = '/usr/sbin/dmidecode' when 'FreeBSD' - return nil unless FileTest.exists?("/usr/local/sbin/dmidecode") - - output=%x{/usr/local/sbin/dmidecode 2>/dev/null} + cmd = '/usr/local/sbin/dmidecode' when 'NetBSD', 'DragonFly' - return nil unless FileTest.exists?("/usr/pkg/sbin/dmidecode") - - output=%x{/usr/pkg/sbin/dmidecode 2>/dev/null} + cmd = '/usr/pkg/sbin/dmidecode' when 'SunOS' - return nil unless FileTest.exists?("/usr/sbin/smbios") + cmd = '/usr/sbin/smbios' + end + + if cmd and (output = Facter::Core::Execution.exec("#{cmd} 2>/dev/null")) + + if output.respond_to?(:force_encoding) + output.force_encoding(Encoding::ASCII_8BIT) + end - output=%x{/usr/sbin/smbios 2>/dev/null} - else - output=nil + return output end - return output end def self.dmi_find_system_info(name) diff --git a/spec/fixtures/unit/util/manufacturer/smartos_smbios b/spec/fixtures/unit/util/manufacturer/smartos_smbios new file mode 100644 index 0000000000..27d14aac7d --- /dev/null +++ b/spec/fixtures/unit/util/manufacturer/smartos_smbios @@ -0,0 +1,533 @@ +ID SIZE TYPE +0 63 SMB_TYPE_BIOS (BIOS information) + + Vendor: Phoenix Technologies Ltd. + Version String: V1.23D + Release Date: 06/11/2007 + Address Segment: 0xe3b4 + ROM Size: 524288 bytes + Image Size: 115904 bytes + Characteristics: 0x7fc8de80 + SMB_BIOSFL_PCI (PCI is supported) + SMB_BIOSFL_PLUGNPLAY (Plug and Play is supported) + SMB_BIOSFL_APM (APM is supported) + SMB_BIOSFL_FLASH (BIOS is Flash Upgradeable) + SMB_BIOSFL_SHADOW (BIOS shadowing is allowed) + SMB_BIOSFL_ESCD (ESCD support is available) + SMB_BIOSFL_CDBOOT (Boot from CD is supported) + SMB_BIOSFL_EDD (EDD Spec is supported) + SMB_BIOSFL_525_360K (int 0x13 5.25" 360K floppy) + SMB_BIOSFL_525_12M (int 0x13 5.25" 1.2M floppy) + SMB_BIOSFL_35_720K (int 0x13 3.5" 720K floppy) + SMB_BIOSFL_35_288M (int 0x13 3.5" 2.88M floppy) + SMB_BIOSFL_I5_PRINT (int 0x5 print screen svcs) + SMB_BIOSFL_I9_KBD (int 0x9 8042 keyboard svcs) + SMB_BIOSFL_I14_SER (int 0x14 serial svcs) + SMB_BIOSFL_I17_PRINTER (int 0x17 printer svcs) + SMB_BIOSFL_I10_CGA (int 0x10 CGA svcs) + Characteristics Extension Byte 1: 0x13 + SMB_BIOSXB1_ACPI (ACPI is supported) + SMB_BIOSXB1_USBL (USB legacy is supported) + SMB_BIOSXB1_LS120 (LS-120 boot is supported) + Characteristics Extension Byte 2: 0x0 + +ID SIZE TYPE +1 52 SMB_TYPE_SYSTEM (system information) + + Manufacturer: RIOWORKS + Product: HDAMA + Version: + Serial Number: 0123456789 + + UUID: 00000000-0000-0000-0000-000000000000 + Wake-Up Event: 0x6 (power switch) + SKU Number: + Family: + +ID SIZE TYPE +2 37 SMB_TYPE_BASEBOARD (base board) + + Manufacturer: RIOWORKS + Product: HDAMA-I + Version: + Serial Number: 0123456789 + + Chassis: 0 + Flags: 0x0 + Board Type: 0x0 + +ID SIZE TYPE +3 37 SMB_TYPE_CHASSIS (system enclosure or chassis) + + Manufacturer: RIOWORKS + Version: N/A + Serial Number: N/A + Asset Tag: N/A + + OEM Data: 0x1234 + Lock Present: N + Chassis Type: 0x7 (tower) + Boot-Up State: 0x2 (unknown) + Power Supply State: 0x2 (unknown) + Thermal State: 0x2 (unknown) + Chassis Height: 0u + Power Cords: 0 + Element Records: 0 + +ID SIZE TYPE +4 68 SMB_TYPE_PROCESSOR (processor) + + Manufacturer: AMD + Version: AMD + Location Tag: Socket 940 + + Family: 132 (Opteron) + CPUID: 0x178bfbff00020f12 + Type: 3 (central processor) + Socket Upgrade: 6 (none) + Socket Status: Populated + Processor Status: 1 (enabled) + Supported Voltages: 1.6V + External Clock Speed: Unknown + Maximum Speed: 3000MHz + Current Speed: 2200MHz + L1 Cache: 6 + L2 Cache: 7 + L3 Cache: None + +ID SIZE TYPE +5 67 SMB_TYPE_PROCESSOR (processor) + + Manufacturer: AMD + Version: AMD + Location Tag: Socket 940 + + Family: 132 (Opteron) + CPUID: 0x178bfbff00020f12 + Type: 3 (central processor) + Socket Upgrade: 6 (none) + Socket Status: Populated + Processor Status: 1 (enabled) + Supported Voltages: 1.6V + External Clock Speed: Unknown + Maximum Speed: 3000MHz + Current Speed: 2200MHz + L1 Cache: None + L2 Cache: None + L3 Cache: None + +ID SIZE TYPE +6 27 SMB_TYPE_CACHE (processor cache) + + Location Tag: L1 Cache + + Level: 1 + Maximum Installed Size: 131072 bytes + Installed Size: 131072 bytes + Speed: Unknown + Supported SRAM Types: 0x58 + SMB_CAT_BURST (burst) + SMB_CAT_PBURST (pipeline burst) + SMB_CAT_ASYNC (asynchronous) + Current SRAM Type: 0x40 (asynchronous) + Error Correction Type: 2 (unknown) + Logical Cache Type: 2 (unknown) + Associativity: 2 (unknown) + Mode: 1 (write-back) + Location: 0 (internal) + Flags: 0x1 + SMB_CAF_ENABLED (enabled at boot time) + +ID SIZE TYPE +7 27 SMB_TYPE_CACHE (processor cache) + + Location Tag: L2 Cache + + Level: 2 + Maximum Installed Size: 1048576 bytes + Installed Size: 2097152 bytes + Speed: Unknown + Supported SRAM Types: 0x38 + SMB_CAT_BURST (burst) + SMB_CAT_PBURST (pipeline burst) + SMB_CAT_SYNC (synchronous) + Current SRAM Type: 0x20 (synchronous) + Error Correction Type: 2 (unknown) + Logical Cache Type: 5 (unified) + Associativity: 2 (unknown) + Mode: 0 (write-through) + Location: 0 (internal) + Flags: 0x1 + SMB_CAF_ENABLED (enabled at boot time) + +ID SIZE TYPE +8 25 SMB_TYPE_SLOT (upgradeable system slot) + + Location Tag: PCI-X Slot 1 + + Reference Designator: PCI-X Slot 1 + Slot ID: 0x0 + Type: 0x10 (AGP 2X) + Width: 0x5 (32 bit) + Usage: 0x2 (unknown) + Length: 0x4 (long length) + Slot Characteristics 1: 0x4 + SMB_SLCH1_33V (provides 3.3V) + Slot Characteristics 2: 0x1 + SMB_SLCH2_PME (slot supports PME# signal) + Segment Group: 0 + Bus Number: 0 + Device/Function Number: 0 + +ID SIZE TYPE +9 25 SMB_TYPE_SLOT (upgradeable system slot) + + Location Tag: PCI-X Slot 2 + + Reference Designator: PCI-X Slot 2 + Slot ID: 0x1 + Type: 0x6 (PCI) + Width: 0x5 (32 bit) + Usage: 0x4 (in use) + Length: 0x4 (long length) + Slot Characteristics 1: 0x4 + SMB_SLCH1_33V (provides 3.3V) + Slot Characteristics 2: 0x1 + SMB_SLCH2_PME (slot supports PME# signal) + Segment Group: 0 + Bus Number: 0 + Device/Function Number: 0 + +ID SIZE TYPE +10 25 SMB_TYPE_SLOT (upgradeable system slot) + + Location Tag: PCI-X Slot 3 + + Reference Designator: PCI-X Slot 3 + Slot ID: 0x2 + Type: 0x6 (PCI) + Width: 0x5 (32 bit) + Usage: 0x3 (available) + Length: 0x4 (long length) + Slot Characteristics 1: 0x4 + SMB_SLCH1_33V (provides 3.3V) + Slot Characteristics 2: 0x1 + SMB_SLCH2_PME (slot supports PME# signal) + Segment Group: 0 + Bus Number: 0 + Device/Function Number: 0 + +ID SIZE TYPE +11 25 SMB_TYPE_SLOT (upgradeable system slot) + + Location Tag: PCI-X Slot 4 + + Reference Designator: PCI-X Slot 4 + Slot ID: 0x3 + Type: 0x6 (PCI) + Width: 0x5 (32 bit) + Usage: 0x3 (available) + Length: 0x4 (long length) + Slot Characteristics 1: 0x4 + SMB_SLCH1_33V (provides 3.3V) + Slot Characteristics 2: 0x1 + SMB_SLCH2_PME (slot supports PME# signal) + Segment Group: 0 + Bus Number: 0 + Device/Function Number: 0 + +ID SIZE TYPE +12 23 SMB_TYPE_SLOT (upgradeable system slot) + + Location Tag: PCI Slot 5 + + Reference Designator: PCI Slot 5 + Slot ID: 0x4 + Type: 0x6 (PCI) + Width: 0x5 (32 bit) + Usage: 0x3 (available) + Length: 0x4 (long length) + Slot Characteristics 1: 0x4 + SMB_SLCH1_33V (provides 3.3V) + Slot Characteristics 2: 0x1 + SMB_SLCH2_PME (slot supports PME# signal) + Segment Group: 0 + Bus Number: 0 + Device/Function Number: 0 + +ID SIZE TYPE +13 23 SMB_TYPE_SLOT (upgradeable system slot) + + Location Tag: PCI Slot 6 + + Reference Designator: PCI Slot 6 + Slot ID: 0x5 + Type: 0x6 (PCI) + Width: 0x5 (32 bit) + Usage: 0x3 (available) + Length: 0x4 (long length) + Slot Characteristics 1: 0x4 + SMB_SLCH1_33V (provides 3.3V) + Slot Characteristics 2: 0x1 + SMB_SLCH2_PME (slot supports PME# signal) + Segment Group: 0 + Bus Number: 0 + Device/Function Number: 0 + +ID SIZE TYPE +14 29 SMB_TYPE_SLOT (upgradeable system slot) + + Location Tag: PCI-X Riser Slot + + Reference Designator: PCI-X Riser Slot + Slot ID: 0x2 + Type: 0x12 (PCI-X) + Width: 0x6 (64 bit) + Usage: 0x3 (available) + Length: 0x4 (long length) + Slot Characteristics 1: 0x6 + SMB_SLCH1_5V (provides 5.0V) + SMB_SLCH1_33V (provides 3.3V) + Slot Characteristics 2: 0x1 + SMB_SLCH2_PME (slot supports PME# signal) + Segment Group: 0 + Bus Number: 0 + Device/Function Number: 0 + +ID SIZE TYPE +15 34 SMB_TYPE_OEMSTR (OEM string table) + + 0 + 0 + ������������������������� + +ID SIZE TYPE +16 29 SMB_TYPE_EVENTLOG (system event log) + + Log Area Size: 16 bytes + Header Offset: 0 + Data Offset: 16 + Data Access Method: 4 (GP Non-Volatile API Access) + Log Flags: 0x1 + SMB_EVFL_VALID (log area valid) + Log Header Format: 1 (DMTF log header type 1) + Update Token: 0x40 + Data Access Address: 0x0 + Type Descriptors: + 0: Log Type 0x8, Data Type 0x4 + 1: Log Type 0x1, Data Type 0x2 + 2: Log Type 0x2, Data Type 0x2 + +ID SIZE TYPE +17 15 SMB_TYPE_MEMARRAY (physical memory array) + + Location: 3 (system board or motherboard) + Use: 3 (system memory) + ECC: 5 (single-bit ECC) + Number of Slots/Sockets: 3 + Memory Error Data: Not Supported + Max Capacity: 34359738368 bytes + +ID SIZE TYPE +18 36 SMB_TYPE_MEMDEVICE (memory device) + + Location Tag: S0 + + Physical Memory Array: 17 + Memory Error Data: None + Total Width: 128 bits + Data Width: 64 bits + Size: 1073741824 bytes + Form Factor: 9 (DIMM) + Set: 1 + Memory Type: 3 (DRAM) + Flags: 0x80 + SMB_MDF_SYNC (synchronous) + Speed: Unknown + Device Locator: S0 + Bank Locator: Bank 0 + +ID SIZE TYPE +19 36 SMB_TYPE_MEMDEVICE (memory device) + + Location Tag: S1 + + Physical Memory Array: 17 + Memory Error Data: None + Total Width: 128 bits + Data Width: 64 bits + Size: 1073741824 bytes + Form Factor: 9 (DIMM) + Set: 1 + Memory Type: 3 (DRAM) + Flags: 0x80 + SMB_MDF_SYNC (synchronous) + Speed: Unknown + Device Locator: S1 + Bank Locator: Bank 0 + +ID SIZE TYPE +20 36 SMB_TYPE_MEMDEVICE (memory device) + + Location Tag: S2 + + Physical Memory Array: 17 + Memory Error Data: None + Total Width: 128 bits + Data Width: 64 bits + Size: 1073741824 bytes + Form Factor: 9 (DIMM) + Set: 2 + Memory Type: 3 (DRAM) + Flags: 0x80 + SMB_MDF_SYNC (synchronous) + Speed: Unknown + Device Locator: S2 + Bank Locator: Bank 1 + +ID SIZE TYPE +21 36 SMB_TYPE_MEMDEVICE (memory device) + + Location Tag: S3 + + Physical Memory Array: 17 + Memory Error Data: None + Total Width: 128 bits + Data Width: 64 bits + Size: 1073741824 bytes + Form Factor: 9 (DIMM) + Set: 2 + Memory Type: 3 (DRAM) + Flags: 0x80 + SMB_MDF_SYNC (synchronous) + Speed: Unknown + Device Locator: S3 + Bank Locator: Bank 1 + +ID SIZE TYPE +22 36 SMB_TYPE_MEMDEVICE (memory device) + + Location Tag: S4 + + Physical Memory Array: 17 + Memory Error Data: None + Total Width: 128 bits + Data Width: 64 bits + Size: 1073741824 bytes + Form Factor: 9 (DIMM) + Set: 5 + Memory Type: 3 (DRAM) + Flags: 0x80 + SMB_MDF_SYNC (synchronous) + Speed: Unknown + Device Locator: S4 + Bank Locator: Bank 2 + +ID SIZE TYPE +23 36 SMB_TYPE_MEMDEVICE (memory device) + + Location Tag: S5 + + Physical Memory Array: 17 + Memory Error Data: None + Total Width: 128 bits + Data Width: 64 bits + Size: 1073741824 bytes + Form Factor: 9 (DIMM) + Set: 5 + Memory Type: 3 (DRAM) + Flags: 0x80 + SMB_MDF_SYNC (synchronous) + Speed: Unknown + Device Locator: S5 + Bank Locator: Bank 2 + +ID SIZE TYPE +24 36 SMB_TYPE_MEMDEVICE (memory device) + + Location Tag: S6 + + Physical Memory Array: 17 + Memory Error Data: None + Total Width: 128 bits + Data Width: 64 bits + Size: 1073741824 bytes + Form Factor: 9 (DIMM) + Set: 6 + Memory Type: 3 (DRAM) + Flags: 0x80 + SMB_MDF_SYNC (synchronous) + Speed: Unknown + Device Locator: S6 + Bank Locator: Bank 3 + +ID SIZE TYPE +25 36 SMB_TYPE_MEMDEVICE (memory device) + + Location Tag: S7 + + Physical Memory Array: 17 + Memory Error Data: None + Total Width: 128 bits + Data Width: 64 bits + Size: 1073741824 bytes + Form Factor: 9 (DIMM) + Set: 6 + Memory Type: 3 (DRAM) + Flags: 0x80 + SMB_MDF_SYNC (synchronous) + Speed: Unknown + Device Locator: S7 + Bank Locator: Bank 3 + +ID SIZE TYPE +26 15 SMB_TYPE_MEMARRAYMAP (memory array mapped address) + + Physical Memory Array: 17 + Devices per Row: 2 + Physical Address: 0x0 + Size: 8589934592 bytes + +ID SIZE TYPE +27 19 SMB_TYPE_MEMDEVICEMAP (memory device mapped address) + + Memory Device: 18 + Memory Array Mapped Address: 26 + Physical Address: 0x0 + Size: 1073741824 bytes + Partition Row Position: 1 + Interleave Position: 0 + Interleave Data Depth: 1 + +ID SIZE TYPE +28 19 SMB_TYPE_MEMDEVICEMAP (memory device mapped address) + + Memory Device: 19 + Memory Array Mapped Address: 26 + Physical Address: 0x40000000 + Size: 1073741824 bytes + Partition Row Position: 1 + Interleave Position: 0 + Interleave Data Depth: 1 + +ID SIZE TYPE +29 19 SMB_TYPE_MEMDEVICEMAP (memory device mapped address) + + Memory Device: 20 + Memory Array Mapped Address: 26 + Physical Address: 0x80000000 + Size: 1073741824 bytes + Partition Row Position: 1 + Interleave Position: 0 + Interleave Data Depth: 1 + +ID SIZE TYPE +30 20 SMB_TYPE_BOOT (system boot status) + + Boot Status Code: 0xc + Boot Data (9 bytes): + + offset: 0 1 2 3 4 5 6 7 8 9 a b c d e f 0123456789abcdef + 0: 01020304 05060708 09 ......... + + diff --git a/spec/unit/util/manufacturer_spec.rb b/spec/unit/util/manufacturer_spec.rb index f1dea563b2..24aa91d839 100755 --- a/spec/unit/util/manufacturer_spec.rb +++ b/spec/unit/util/manufacturer_spec.rb @@ -63,6 +63,17 @@ Facter.value(:reldate).should == "12/01/2006" end + it "can parse smbios output that contains non-UTF8 characters" do + smbios_output = my_fixture_read("smartos_smbios") + Facter::Core::Execution.stubs(:exec).with('/usr/sbin/smbios 2>/dev/null').returns(smbios_output) + Facter.fact(:kernel).stubs(:value).returns("SunOS") + + query = { 'BIOS information' => [ { 'Release Date:' => 'reldate' } ] } + + Facter::Manufacturer.dmi_find_system_info(query) + Facter.value(:reldate).should == "06/11/2007" + end + it "should not split on dmi keys containing the string Handle" do dmidecode_output = <<-eos Handle 0x1000, DMI type 16, 15 bytes From 3bdb78459193d16b58324abfd8a882e533cac42f Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 1 Apr 2014 10:13:35 -0700 Subject: [PATCH 1750/3753] (packaging) Update FACTERVERSION to 2.0.1 --- lib/facter/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/version.rb b/lib/facter/version.rb index 328a21ae68..0dc068887b 100644 --- a/lib/facter/version.rb +++ b/lib/facter/version.rb @@ -1,6 +1,6 @@ module Facter if not defined? FACTERVERSION then - FACTERVERSION = '2.0.1-rc4' + FACTERVERSION = '2.0.1' end # Returns the running version of Facter. From 7b5b8492f3d47c05d778728f70a1fc9a8d221f17 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 31 Mar 2014 11:47:18 -0700 Subject: [PATCH 1751/3753] (maint) Define Util::Resolution#resolution_type The resolution_type method is needed when reopening a fact definition with the Facter 2.0 API: Facter.define_fact(:myfact) do define_resolution(:myres) do # [...] The aggregate resolution type implemented this but Util::Resolution did not, which could cause errors using the new syntax. This corrects the omission. --- lib/facter/util/resolution.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/facter/util/resolution.rb b/lib/facter/util/resolution.rb index 0182a9ae63..9b3efa097f 100644 --- a/lib/facter/util/resolution.rb +++ b/lib/facter/util/resolution.rb @@ -62,6 +62,10 @@ def initialize(name, fact) @weight = nil end + def resolution_type + :simple + end + # Evaluate the given block in the context of this resolution. If a block has # already been evaluated emit a warning to that effect. # From 1584dc15c4799fe01eacfac249145c6853c95af7 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Mon, 31 Mar 2014 10:39:33 -0700 Subject: [PATCH 1752/3753] (maint) Don't cache suitability information When testing a resolution, the confines may be tested to confirm suitability on a given platform and so may be changed without destroying and recreating the resolution. Because of this we need to recalculate suitability every time #suitable? method is invoked. In normal operation fact suitability is only determined once so this should have no performance impact. --- lib/facter/core/suitable.rb | 6 +----- spec/unit/core/suitable_spec.rb | 10 ++++++++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/facter/core/suitable.rb b/lib/facter/core/suitable.rb index 1b3c04fceb..0262470919 100644 --- a/lib/facter/core/suitable.rb +++ b/lib/facter/core/suitable.rb @@ -108,10 +108,6 @@ def weight # # @api private def suitable? - unless defined? @suitable - @suitable = ! @confines.detect { |confine| ! confine.true? } - end - - return @suitable + @confines.all? { |confine| confine.true? } end end diff --git a/spec/unit/core/suitable_spec.rb b/spec/unit/core/suitable_spec.rb index 4c0b1fde06..8277408799 100644 --- a/spec/unit/core/suitable_spec.rb +++ b/spec/unit/core/suitable_spec.rb @@ -92,5 +92,15 @@ def initialize expect(subject).to_not be_suitable end + + it "recalculates suitability on every invocation" do + subject.confine :kernel => 'Linux' + + subject.confines.first.stubs(:true?).returns false + expect(subject).to_not be_suitable + subject.confines.first.unstub(:true?) + subject.confines.first.stubs(:true?).returns true + expect(subject).to be_suitable + end end end From 33b507137558bede13231a3c419021be8a0a7436 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Wed, 2 Apr 2014 16:38:27 -0700 Subject: [PATCH 1753/3753] (FACT-185) Add helper for flattening structured facts --- lib/facter/util/values.rb | 29 +++++++++++++++++++++++++ spec/unit/util/values_spec.rb | 40 +++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/lib/facter/util/values.rb b/lib/facter/util/values.rb index a7048d54da..1fbb1b686e 100644 --- a/lib/facter/util/values.rb +++ b/lib/facter/util/values.rb @@ -1,3 +1,4 @@ + module Facter module Util # A util module for facter containing helper methods @@ -75,6 +76,34 @@ def convert(value) value = value.downcase if value.is_a?(String) value end + + # Flatten the given data structure to something that's suitable to return + # as flat facts. + # + # @param path [String] The fact path to be prefixed to the given value. + # @param structure [Object] The data structure to flatten. Nested hashes + # will be recursively flattened, everything else will be returned as-is. + # + # @return [Hash] The given data structure prefixed with the given path + def flatten_structure(path, structure) + results = {} + + if structure.is_a? Hash + structure.each_pair do |name, value| + new_path = "#{path}_#{name}".gsub(/\-|\//, '_') + results.merge! flatten_structure(new_path, value) + end + elsif structure.is_a? Array + structure.each_with_index do |value, index| + new_path = "#{path}_#{index}" + results.merge! flatten_structure(new_path, value) + end + else + results[path] = structure + end + + results + end end end end diff --git a/spec/unit/util/values_spec.rb b/spec/unit/util/values_spec.rb index 557eb195e6..bc347c49a6 100644 --- a/spec/unit/util/values_spec.rb +++ b/spec/unit/util/values_spec.rb @@ -128,4 +128,44 @@ end end end + + describe "flatten_structure" do + it "converts a string to a hash containing that string" do + input = "foo" + output = described_class.flatten_structure("path", input) + expect(output).to eq({"path" => "foo"}) + end + + it "converts an array to a hash with the array elements with indexes" do + input = ["foo"] + output = described_class.flatten_structure("path", input) + expect(output).to eq({"path_0" => "foo"}) + end + + it "prefixes a non-nested hash with the given path" do + input = {"foo" => "bar"} + output = described_class.flatten_structure("path", input) + expect(output).to eq({"path_foo" => "bar"}) + end + + it "flattens elements till it reaches the first non-flattenable structure" do + input = { + "first" => "second", + "arr" => ["zero", "one"], + "nested_array" => [ + "hash" => "string", + ], + "top" => {"middle" => ['bottom']}, + } + output = described_class.flatten_structure("path", input) + + expect(output).to eq({ + "path_first" => "second", + "path_arr_0" => "zero", + "path_arr_1" => "one", + "path_nested_array_0_hash" => "string", + "path_top_middle_0" => "bottom" + }) + end + end end From 5ca456f574147d55e28ddb90a72e7701a3d28200 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Wed, 2 Apr 2014 16:04:17 -0700 Subject: [PATCH 1754/3753] (FACT-185) Implement more modular ec2 query API The existing EC2 API implemented all methods on a module object, which made for a rather clumsy API that required intrusive stubbing to test. This commit implements a new object based API that should be cleaner to use. --- lib/facter/ec2/rest.rb | 129 +++++++++++++++++++ spec/fixtures/unit/ec2/rest/meta-data/root | 20 +++ spec/unit/ec2/rest_spec.rb | 140 +++++++++++++++++++++ 3 files changed, 289 insertions(+) create mode 100644 lib/facter/ec2/rest.rb create mode 100644 spec/fixtures/unit/ec2/rest/meta-data/root create mode 100644 spec/unit/ec2/rest_spec.rb diff --git a/lib/facter/ec2/rest.rb b/lib/facter/ec2/rest.rb new file mode 100644 index 0000000000..e9c1fcadde --- /dev/null +++ b/lib/facter/ec2/rest.rb @@ -0,0 +1,129 @@ +require 'timeout' +require 'open-uri' + +module Facter + module EC2 + CONNECTION_ERRORS = [ + Errno::EHOSTDOWN, + Errno::EHOSTUNREACH, + Errno::ENETUNREACH, + Errno::ECONNABORTED, + Errno::ECONNREFUSED, + Errno::ECONNRESET, + Errno::ETIMEDOUT, + ] + + class Base + def reachable?(retry_limit = 3) + timeout = 0.2 + able_to_connect = false + attempts = 0 + + begin + Timeout.timeout(timeout) do + open(@baseurl).read + end + able_to_connect = true + rescue OpenURI::HTTPError => e + if e.message.match /404 Not Found/i + able_to_connect = false + else + retry if attempts < retry_limit + end + rescue Timeout::Error + retry if attempts < retry_limit + rescue *CONNECTION_ERRORS + retry if attempts < retry_limit + ensure + attempts = attempts + 1 + end + + able_to_connect + end + end + + class Metadata < Base + + DEFAULT_URI = "http://169.254.169.254/latest/meta-data/" + + def initialize(uri = DEFAULT_URI) + @baseurl = uri + end + + def fetch(path = '') + results = {} + + keys = fetch_endpoint(path) + keys.each do |key| + if key.match(%r[/$]) + # If a metadata key is suffixed with '/' then it's a general metadata + # resource, so we have to recursively look up all the keys in the given + # collection. + name = key[0..-2] + results[name] = fetch("#{path}#{key}") + else + # This is a simple key/value pair, we can just query the given endpoint + # and store the results. + ret = fetch_endpoint("#{path}#{key}") + results[key] = ret.size > 1 ? ret : ret.first + end + end + + results + end + + # @param path [String] The path relative to the object base url + # + # @return [Array, NilClass] + def fetch_endpoint(path) + uri = @baseurl + path + body = open(uri).read + parse_results(body) + rescue OpenURI::HTTPError => e + if e.message.match /404 Not Found/i + return nil + else + Facter.log_exception(e, "Failed to fetch ec2 uri #{uri}: #{e.message}") + return nil + end + rescue *CONNECTION_ERRORS => e + Facter.log_exception(e, "Failed to fetch ec2 uri #{uri}: #{e.message}") + return nil + end + + private + + def parse_results(body) + lines = body.split("\n") + lines.map do |line| + if (match = line.match(/^(\d+)=.*$/)) + # Metadata arrays are formatted like '=/', so + # we need to extract the index from that output. + "#{match[1]}/" + else + line + end + end + end + end + + class Userdata < Base + DEFAULT_URI = "http://169.254.169.254/latest/user-data/" + + def initialize(uri = DEFAULT_URI) + @baseurl = uri + end + + def fetch + open(@baseurl).read + rescue OpenURI::HTTPError => e + if e.message.match /404 Not Found/i + return nil + else + Facter.log_exception(e, "Failed to fetch ec2 uri #{uri}: #{e.message}") + return nil + end + end + end + end +end diff --git a/spec/fixtures/unit/ec2/rest/meta-data/root b/spec/fixtures/unit/ec2/rest/meta-data/root new file mode 100644 index 0000000000..9ec3bbedad --- /dev/null +++ b/spec/fixtures/unit/ec2/rest/meta-data/root @@ -0,0 +1,20 @@ +ami-id +ami-launch-index +ami-manifest-path +block-device-mapping/ +hostname +instance-action +instance-id +instance-type +kernel-id +local-hostname +local-ipv4 +mac +metrics/ +network/ +placement/ +profile +public-hostname +public-ipv4 +public-keys/ +reservation-id diff --git a/spec/unit/ec2/rest_spec.rb b/spec/unit/ec2/rest_spec.rb new file mode 100644 index 0000000000..5c74b495f4 --- /dev/null +++ b/spec/unit/ec2/rest_spec.rb @@ -0,0 +1,140 @@ +require 'spec_helper' +require 'facter/ec2/rest' + +shared_examples_for "an ec2 rest querier" do + describe "determining if the uri is reachable" do + it "retries if the connection times out" do + subject.stubs(:open).returns(stub(:read => nil)) + Timeout.expects(:timeout).with(0.2).twice.raises(Timeout::Error).returns(true) + expect(subject).to be_reachable + end + + it "retries if the connection is reset" do + subject.expects(:open).twice.raises(Errno::ECONNREFUSED).returns(StringIO.new("woo")) + expect(subject).to be_reachable + end + + it "is false if the given uri returns a 404" do + subject.expects(:open).with(anything).once.raises(OpenURI::HTTPError.new("404 Not Found", StringIO.new("woo"))) + expect(subject).to_not be_reachable + end + end + +end + +describe Facter::EC2::Metadata do + + subject { described_class.new('http://0.0.0.0/latest/meta-data/') } + + let(:response) { StringIO.new } + + describe "fetching a metadata endpoint" do + it "splits the body into an array" do + response.string = my_fixture_read("meta-data/root") + subject.stubs(:open).with("http://0.0.0.0/latest/meta-data/").returns response + output = subject.fetch_endpoint('') + + expect(output).to eq %w[ + ami-id ami-launch-index ami-manifest-path block-device-mapping/ hostname + instance-action instance-id instance-type kernel-id local-hostname + local-ipv4 mac metrics/ network/ placement/ profile public-hostname + public-ipv4 public-keys/ reservation-id + ] + end + + it "reformats keys that are array indices" do + response.string = "0=adrien@grey/" + subject.stubs(:open).with("http://0.0.0.0/latest/meta-data/public-keys/").returns response + output = subject.fetch_endpoint("public-keys/") + + expect(output).to eq %w[0/] + end + + it "returns nil if the endpoint returns a 404" do + Facter.expects(:log_exception).never + subject.stubs(:open).with("http://0.0.0.0/latest/meta-data/public-keys/1/").raises OpenURI::HTTPError.new("404 Not Found", response) + output = subject.fetch_endpoint('public-keys/1/') + + expect(output).to be_nil + end + + it "logs an error if the endpoint raises a non-404 HTTPError" do + Facter.expects(:log_exception).with(instance_of(OpenURI::HTTPError), anything) + + subject.stubs(:open).with("http://0.0.0.0/latest/meta-data/").raises OpenURI::HTTPError.new("418 I'm a Teapot", response) + output = subject.fetch_endpoint("") + + expect(output).to be_nil + end + + it "logs an error if the endpoint raises a connection error" do + Facter.expects(:log_exception).with(instance_of(Errno::ECONNREFUSED), anything) + + subject.stubs(:open).with("http://0.0.0.0/latest/meta-data/").raises Errno::ECONNREFUSED + output = subject.fetch_endpoint('') + + expect(output).to be_nil + end + end + + describe "recursively fetching the EC2 metadata API" do + it "queries the given endpoint for metadata keys" do + subject.expects(:fetch_endpoint).with("").returns([]) + subject.fetch + end + + it "fetches the value for a simple metadata key" do + subject.expects(:fetch_endpoint).with("").returns(['indexthing']) + subject.expects(:fetch_endpoint).with("indexthing").returns(['first', 'second']) + + output = subject.fetch + expect(output).to eq({'indexthing' => ['first', 'second']}) + end + + it "unwraps metadata values that are in single element arrays" do + subject.expects(:fetch_endpoint).with("").returns(['ami-id']) + subject.expects(:fetch_endpoint).with("ami-id").returns(['i-12x']) + + output = subject.fetch + expect(output).to eq({'ami-id' => 'i-12x'}) + end + + it "recursively queries an endpoint if the key ends with '/'" do + subject.expects(:fetch_endpoint).with("").returns(['metrics/']) + subject.expects(:fetch_endpoint).with("metrics/").returns(['vhostmd']) + subject.expects(:fetch_endpoint).with("metrics/vhostmd").returns(['woo']) + + output = subject.fetch + expect(output).to eq({'metrics' => {'vhostmd' => 'woo'}}) + end + end + + it_behaves_like "an ec2 rest querier" +end + +describe Facter::EC2::Userdata do + + subject { described_class.new('http://0.0.0.0/latest/user-data/') } + + let(:response) { StringIO.new } + + describe "reaching the userdata" do + it "queries the userdata URI" do + subject.expects(:open).with('http://0.0.0.0/latest/user-data/').returns(response) + subject.fetch + end + + it "returns the result of the query without modification" do + response.string = "clooouuuuud" + subject.expects(:open).with('http://0.0.0.0/latest/user-data/').returns(response) + expect(subject.fetch).to eq "clooouuuuud" + end + + it "is nil if the URI returned a 404" do + subject.expects(:open).with('http://0.0.0.0/latest/user-data/').once.raises(OpenURI::HTTPError.new("404 Not Found", StringIO.new("woo"))) + expect(subject.fetch).to be_nil + end + end + + it_behaves_like "an ec2 rest querier" +end From 07f5dfbeee62aaab2c6028c21b5b308b13b3f267 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Wed, 2 Apr 2014 16:33:10 -0700 Subject: [PATCH 1755/3753] (FACT-185) Update ec2 facts to use new ec2 query classes --- lib/facter/ec2.rb | 59 ++++++----- spec/unit/ec2_spec.rb | 228 +++++++++++++++--------------------------- 2 files changed, 112 insertions(+), 175 deletions(-) diff --git a/lib/facter/ec2.rb b/lib/facter/ec2.rb index 09e0109528..2e575927cd 100644 --- a/lib/facter/ec2.rb +++ b/lib/facter/ec2.rb @@ -1,37 +1,44 @@ -require 'facter/util/ec2' -require 'open-uri' +require 'facter/ec2/rest' -def metadata(id = "") - open("http://169.254.169.254/2008-02-01/meta-data/#{id||=''}").read. - split("\n").each do |o| - key = "#{id}#{o.gsub(/\=.*$/, '/')}" - if key[-1..-1] != '/' - value = open("http://169.254.169.254/2008-02-01/meta-data/#{key}").read. - split("\n") - symbol = "ec2_#{key.gsub(/\-|\//, '_')}".to_sym - Facter.add(symbol) { setcode { value.join(',') } } - else - metadata(key) +Facter.define_fact(:ec2_metadata) do + define_resolution(:rest) do + confine do + Facter.value(:virtual).match /^xen/ + end + + @querier = Facter::EC2::Metadata.new + confine do + @querier.reachable? + end + + setcode do + @querier.fetch end end -rescue => details - Facter.warn "Could not retrieve ec2 metadata: #{details.message}" end -def userdata() - Facter.add(:ec2_userdata) do +Facter.define_fact(:ec2_userdata) do + define_resolution(:rest) do + confine do + Facter.value(:virtual).match /^xen/ + end + + @querier = Facter::EC2::Userdata.new + confine do + @querier.reachable? + end + setcode do - if userdata = Facter::Util::EC2.userdata - userdata.split - end + @querier.fetch end end end -if (Facter::Util::EC2.has_euca_mac? || Facter::Util::EC2.has_openstack_mac? || - Facter::Util::EC2.has_ec2_arp?) && Facter::Util::EC2.can_connect? - metadata - userdata -else - Facter.debug "Not an EC2 host" +# The flattened version of the EC2 facts are deprecated and will be removed in +# a future release of Facter. +if (ec2_metadata = Facter.value(:ec2_metadata)) + ec2_facts = Facter::Util::Values.flatten_structure("ec2", ec2_metadata) + ec2_facts.each_pair do |factname, factvalue| + Facter.add(factname, :value => factvalue) + end end diff --git a/spec/unit/ec2_spec.rb b/spec/unit/ec2_spec.rb index f26a61316f..bf2aa2f5ea 100755 --- a/spec/unit/ec2_spec.rb +++ b/spec/unit/ec2_spec.rb @@ -1,187 +1,117 @@ -#! /usr/bin/env ruby - require 'spec_helper' -require 'facter/util/ec2' - -describe "ec2 facts" do - # This is the standard prefix for making an API call in EC2 (or fake) - # environments. - let(:api_prefix) { "http://169.254.169.254/" } - - describe "when running on ec2" do - before :each do - # This is an ec2 instance, not a eucalyptus instance - Facter::Util::EC2.stubs(:has_euca_mac?).returns(false) - Facter::Util::EC2.stubs(:has_openstack_mac?).returns(false) - Facter::Util::EC2.stubs(:has_ec2_arp?).returns(true) - - # Assume we can connect - Facter::Util::EC2.stubs(:can_connect?).returns(true) - end - - it "should create flat meta-data facts" do - Object.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/meta-data/"). - at_least_once.returns(StringIO.new("foo")) +require 'facter/ec2/rest' - Object.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/meta-data/foo"). - at_least_once.returns(StringIO.new("bar")) +describe "ec2_metadata" do + let(:querier) { stub('EC2 metadata querier') } - Facter.collection.internal_loader.load(:ec2) - - Facter.fact(:ec2_foo).value.should == "bar" - end + before do + Facter::EC2::Metadata.stubs(:new).returns querier + Facter.collection.internal_loader.load(:ec2) + end - it "should create flat meta-data facts with comma seperation" do - Object.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/meta-data/"). - at_least_once.returns(StringIO.new("foo")) + subject { Facter.fact(:ec2_metadata).resolution(:rest) } - Object.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/meta-data/foo"). - at_least_once.returns(StringIO.new("bar\nbaz")) + it "is unsuitable if the virtual fact is not xen" do + Facter.fact(:virtual).stubs(:value).returns "kvm" + expect(subject).to_not be_suitable + end - Facter.collection.internal_loader.load(:ec2) + it "is unsuitable if ec2 endpoint is not reachable" do + Facter.fact(:virtual).stubs(:value).returns "xen" + querier.stubs(:reachable?).returns false + expect(subject).to_not be_suitable + end - Facter.fact(:ec2_foo).value.should == "bar,baz" + describe "when the ec2 endpoint is reachable" do + before do + querier.stubs(:reachable?).returns true end - it "should create structured meta-data facts" do - Object.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/meta-data/"). - at_least_once.returns(StringIO.new("foo/")) - - Object.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/meta-data/foo/"). - at_least_once.returns(StringIO.new("bar")) + it "is suitable if the virtual fact is xen" do + Facter.fact(:virtual).stubs(:value).returns "xen" + subject.suitable? - Object.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/meta-data/foo/bar"). - at_least_once.returns(StringIO.new("baz")) - - Facter.collection.internal_loader.load(:ec2) - - Facter.fact(:ec2_foo_bar).value.should == "baz" + expect(subject).to be_suitable end - it "should create ec2_user_data fact" do - # No meta-data - Object.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/meta-data/"). - at_least_once.returns(StringIO.new("")) - - Facter::Util::EC2.stubs(:read_uri). - with("#{api_prefix}/latest/user-data/"). - returns("test") - - Facter.collection.internal_loader.load(:ec2) - Facter.fact(:ec2_userdata).value.should == ["test"] + it "is suitable if the virtual fact is xenu" do + Facter.fact(:virtual).stubs(:value).returns "xenu" + expect(subject).to be_suitable end end - describe "when running on eucalyptus" do - before :each do - # Return false for ec2, true for eucalyptus - Facter::Util::EC2.stubs(:has_euca_mac?).returns(true) - Facter::Util::EC2.stubs(:has_openstack_mac?).returns(false) - Facter::Util::EC2.stubs(:has_ec2_arp?).returns(false) - - # Assume we can connect - Facter::Util::EC2.stubs(:can_connect?).returns(true) - end + it "resolves the value by recursively querying the rest endpoint" do + querier.expects(:fetch).returns({"hello" => "world"}) + expect(subject.value).to eq({"hello" => "world"}) + end +end - it "should create ec2_user_data fact" do - # No meta-data - Object.any_instance.expects(:open).\ - with("#{api_prefix}/2008-02-01/meta-data/").\ - at_least_once.returns(StringIO.new("")) +describe "ec2_userdata" do + let(:querier) { stub('EC2 metadata querier') } - Facter::Util::EC2.stubs(:read_uri). - with("#{api_prefix}/latest/user-data/"). - returns("test") + before do + Facter::EC2::Userdata.stubs(:new).returns querier + Facter.collection.internal_loader.load(:ec2) + end - # Force a fact load - Facter.collection.internal_loader.load(:ec2) + subject { Facter.fact(:ec2_userdata).resolution(:rest) } - Facter.fact(:ec2_userdata).value.should == ["test"] - end + it "is unsuitable if the virtual fact is not xen" do + Facter.fact(:virtual).stubs(:value).returns "kvm" + expect(subject).to_not be_suitable end - describe "when running on openstack" do - before :each do - # Return false for ec2, true for eucalyptus - Facter::Util::EC2.stubs(:has_openstack_mac?).returns(true) - Facter::Util::EC2.stubs(:has_euca_mac?).returns(false) - Facter::Util::EC2.stubs(:has_ec2_arp?).returns(false) + it "is unsuitable if ec2 endpoint is not reachable" do + Facter.fact(:virtual).stubs(:value).returns "xen" + querier.stubs(:reachable?).returns false + expect(subject).to_not be_suitable + end - # Assume we can connect - Facter::Util::EC2.stubs(:can_connect?).returns(true) + describe "when the ec2 endpoint is reachable" do + before do + querier.stubs(:reachable?).returns true end - it "should create ec2_user_data fact" do - # No meta-data - Object.any_instance.expects(:open).\ - with("#{api_prefix}/2008-02-01/meta-data/").\ - at_least_once.returns(StringIO.new("")) - - Facter::Util::EC2.stubs(:read_uri). - with("#{api_prefix}/latest/user-data/"). - returns("test") - - # Force a fact load - Facter.collection.internal_loader.load(:ec2) - - Facter.fact(:ec2_userdata).value.should == ["test"] + it "is suitable if the virtual fact is xen" do + Facter.fact(:virtual).stubs(:value).returns "xen" + expect(subject).to be_suitable end - it "should return nil if open fails" do - Facter.stubs(:warn) # do not pollute test output - Facter.expects(:warn).with('Could not retrieve ec2 metadata: host unreachable') - - Object.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/meta-data/"). - at_least_once.raises(RuntimeError, 'host unreachable') - - Facter::Util::EC2.stubs(:read_uri). - with("#{api_prefix}/latest/user-data/"). - raises(RuntimeError, 'host unreachable') - - # Force a fact load - Facter.collection.internal_loader.load(:ec2) - - Facter.fact(:ec2_userdata).value.should be_nil + it "is suitable if the virtual fact is xenu" do + Facter.fact(:virtual).stubs(:value).returns "xenu" + expect(subject).to be_suitable end + end + it "resolves the value by fetching the rest endpoint" do + querier.expects(:fetch).returns "user data!" + expect(subject.value).to eq "user data!" end +end - describe "when api connect test fails" do - before :each do - Facter.stubs(:warnonce) - end +describe "flattened versions of ec2 facts" do + # These facts are tricky to test because they are dynamic facts, and they are + # generated from a fact that is defined in the same file. In order to pull + # this off we need to define the ec2_metadata fact ahead of time so that we + # can stub the value, and then manually load the correct files. - it "should not populate ec2_userdata" do - # Emulate ec2 for now as it matters little to this test - Facter::Util::EC2.stubs(:has_euca_mac?).returns(true) - Facter::Util::EC2.stubs(:has_ec2_arp?).never - Facter::Util::EC2.expects(:can_connect?).at_least_once.returns(false) + it "unpacks the ec2_metadata fact" do + Facter.define_fact(:ec2_metadata).stubs(:value).returns({"hello" => "world"}) + Facter.collection.internal_loader.load(:ec2) - # The API should never be called at this point - Object.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/meta-data/").never - Object.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/user-data/").never + expect(Facter.value("ec2_hello")).to eq "world" + end - # Force a fact load - Facter.collection.internal_loader.load(:ec2) + it "does not set any flat ec2 facts if the ec2_metadata fact is nil" do + Facter.define_fact(:ec2_metadata).stubs(:value) + Facter.define_fact(:ec2_userdata).stubs(:value).returns(nil) - Facter.fact(:ec2_userdata).should == nil - end + Facter.collection.internal_loader.load(:ec2) - it "should rescue the exception" do - Facter::Util::EC2.expects(:open).with("#{api_prefix}:80/").raises(Timeout::Error) + all_facts = Facter.collection.to_hash - Facter::Util::EC2.should_not be_can_connect - end + ec2_facts = all_facts.keys.select { |k| k =~ /^ec2_/ } + expect(ec2_facts).to be_empty end + end From c11263749eb5aa63c3ae4097382dc84fccfd61f4 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Wed, 2 Apr 2014 17:00:08 -0700 Subject: [PATCH 1756/3753] (FACT-185) Deprecate the old Facter::Util::EC2 API With commit 5ca456f5 we have a new method of querying the EC2 API; the old methods can be deprecated. --- lib/facter/util/ec2.rb | 5 +++++ spec/unit/util/ec2_spec.rb | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/lib/facter/util/ec2.rb b/lib/facter/util/ec2.rb index c6e5dcadd4..b81c8febe8 100644 --- a/lib/facter/util/ec2.rb +++ b/lib/facter/util/ec2.rb @@ -11,6 +11,7 @@ class << self # The +wait_sec+ parameter provides you with an adjustable timeout. # def can_connect?(wait_sec=2) + Facter.warnonce("#{self}.#{__method__} is deprecated; see the Facter::EC2 classes instead") url = "http://169.254.169.254/" Timeout::timeout(wait_sec) {open(url)} return true @@ -23,6 +24,7 @@ def can_connect?(wait_sec=2) # Test if this host has a mac address used by Eucalyptus clouds, which # normally is +d0:0d+. def has_euca_mac? + Facter.warnonce("#{self}.#{__method__} is deprecated; see the Facter::EC2 classes instead") !!(Facter.value(:macaddress) =~ %r{^[dD]0:0[dD]:}) end @@ -30,12 +32,14 @@ def has_euca_mac? # normally starts with FA:16:3E (older versions of OpenStack # may generate mac addresses starting with 02:16:3E) def has_openstack_mac? + Facter.warnonce("#{self}.#{__method__} is deprecated; see the Facter::EC2 classes instead") !!(Facter.value(:macaddress) =~ %r{^(02|[fF][aA]):16:3[eE]}) end # Test if the host has an arp entry in its cache that matches the EC2 arp, # which is normally +fe:ff:ff:ff:ff:ff+. def has_ec2_arp? + Facter.warnonce("#{self}.#{__method__} is deprecated; see the Facter::EC2 classes instead") kernel = Facter.value(:kernel) mac_address_re = case kernel @@ -73,6 +77,7 @@ def has_ec2_arp? # # @return [String] containing the response body or `nil` def self.userdata(version="latest") + Facter.warnonce("#{self}.#{__method__} is deprecated; see the Facter::EC2 classes instead") uri = "http://169.254.169.254/#{version}/user-data/" begin read_uri(uri) diff --git a/spec/unit/util/ec2_spec.rb b/spec/unit/util/ec2_spec.rb index f963db6c32..7af59d8334 100755 --- a/spec/unit/util/ec2_spec.rb +++ b/spec/unit/util/ec2_spec.rb @@ -4,6 +4,10 @@ require 'facter/util/ec2' describe Facter::Util::EC2 do + before do + # Squelch deprecation notices + Facter.stubs(:warnonce) + end # This is the standard prefix for making an API call in EC2 (or fake) # environments. let(:api_prefix) { "http://169.254.169.254/" } From 7387801eececb18909edb310995672c412146fba Mon Sep 17 00:00:00 2001 From: Samuel Keeley Date: Sun, 19 Jan 2014 00:11:50 -0600 Subject: [PATCH 1757/3753] (FACT-231) Avoid system stutter for Darwin virtual fact Using SPDisplaysDataType causes lagging issues on pre-2013 Macs, so use other sources from system_profiler which do not have the issue and are also more reliable. --- lib/facter/virtual.rb | 29 ++++++++++++++++++----------- spec/unit/virtual_spec.rb | 19 +++++++------------ 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 98c2451f71..05f5e7948a 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -5,9 +5,11 @@ # Resolution: # Assumes physical unless proven otherwise. # -# On Darwin, use the macosx util module to acquire the SPDisplaysDataType, -# from that parse it to see if it's VMWare or Parallels pretending to be the -# display. +# On Darwin, use the macosx util module to acquire the SPHardwareDataType and +# SPEthernetDataType, from which it is possible to determine if the host is a +# VMware, Parallels, or VirtualBox. This previously used SPDisplaysDataType +# which was not reliable if running headless, and also caused lagging issues +# on actual Macs. # # On Linux, BSD, Solaris and HPUX: # Much of the logic here is obscured behind util/virtual.rb, which rather @@ -16,9 +18,9 @@ # contents of files in there. # If after all the other tests, it's still seen as physical, then it tries to # parse the output of the "lspci", "dmidecode" and "prtdiag" and parses them -# for obvious signs of being under VMWare, Parallels or VirtualBox. +# for obvious signs of being under VMware, Parallels or VirtualBox. # Finally it checks for the existence of vmware-vmx, which would hint it's -# VMWare. +# VMware. # # Caveats: # Many checks rely purely on existence of files. @@ -32,13 +34,18 @@ setcode do require 'facter/util/macosx' result = "physical" - output = Facter::Util::Macosx.profiler_data("SPDisplaysDataType") + # use SPHardwareDataType for VMware and VirtualBox, since it is the most + # reliable source. + output = Facter::Util::Macosx.profiler_data("SPHardwareDataType") if output.is_a?(Hash) - result = "parallels" if output["spdisplays_vendor-id"] =~ /0x1ab8/ - result = "parallels" if output["spdisplays_vendor"] =~ /[Pp]arallels/ - result = "vmware" if output["spdisplays_vendor-id"] =~ /0x15ad/ - result = "vmware" if output["spdisplays_vendor"] =~ /VM[wW]are/ - result = "virtualbox" if output["spdisplays_vendor-id"] =~ /0x80ee/ + result = "vmware" if output["machine_model"] =~ /VMware/ + result = "virtualbox" if output["boot_rom_version"] =~ /VirtualBox/ + end + # Parallels passes through almost all of the host's hardware info to the + # virtual machine, so use a different field. + output = Facter::Util::Macosx.profiler_data("SPEthernetDataType") + if output.is_a?(Hash) + result = "parallels" if output["spethernet_subsystem-vendor-id"] =~ /0x1ab8/ end result end diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index a733abd7d3..21afa14414 100755 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -52,23 +52,18 @@ Facter.fact(:kernel).stubs(:value).returns("Darwin") end - it "should be parallels with Parallels vendor id" do - Facter::Util::Macosx.stubs(:profiler_data).returns({ "spdisplays_vendor-id" => "0x1ab8" }) + it "should be parallels with Parallels ethernet vendor id" do + Facter::Util::Macosx.stubs(:profiler_data).returns({ "spethernet_subsystem-vendor-id" => "0x1ab8" }) Facter.fact(:virtual).value.should == "parallels" end - it "should be parallels with Parallels vendor name" do - Facter::Util::Macosx.stubs(:profiler_data).returns({ "spdisplays_vendor" => "Parallels" }) - Facter.fact(:virtual).value.should == "parallels" - end - - it "should be vmware with VMWare vendor id" do - Facter::Util::Macosx.stubs(:profiler_data).returns({ "spdisplays_vendor-id" => "0x15ad" }) - Facter.fact(:virtual).value.should == "vmware" + it "should be virtualbox with VirtualBox boot rom name" do + Facter::Util::Macosx.stubs(:profiler_data).returns({ "boot_rom_version" => "VirtualBox" }) + Facter.fact(:virtual).value.should == "virtualbox" end - it "should be vmware with VMWare vendor name" do - Facter::Util::Macosx.stubs(:profiler_data).returns({ "spdisplays_vendor" => "VMWare" }) + it "should be vmware with VMware machine model" do + Facter::Util::Macosx.stubs(:profiler_data).returns({ "machine_model" => "VMware7,1" }) Facter.fact(:virtual).value.should == "vmware" end end From b89bd6956504a2a1fd14bf166769f994aed8d4bb Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 1 Apr 2014 10:13:35 -0700 Subject: [PATCH 1758/3753] (packaging) Update FACTERVERSION to 2.0.1 --- lib/facter/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/version.rb b/lib/facter/version.rb index 328a21ae68..0dc068887b 100644 --- a/lib/facter/version.rb +++ b/lib/facter/version.rb @@ -1,6 +1,6 @@ module Facter if not defined? FACTERVERSION then - FACTERVERSION = '2.0.1-rc4' + FACTERVERSION = '2.0.1' end # Returns the running version of Facter. From 87d0b8b22f2cd1400e55c945494f085434e63f04 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Thu, 3 Apr 2014 11:35:06 -0700 Subject: [PATCH 1759/3753] (maint) Fix build on Linux. The zero-initialization of the ifconf struct can't be done with an initializer due to the differing definitions on Linux vs. OSX. Adding back the memset that was removed. Fixing a few old variable name usages that were in a Linux-specific Adding NetBeans files to gitignore. --- .gitignore | 3 +++ lib/cfacterimpl.cc | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index c0766d8527..6cc4d93f53 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ +# Misc files *.swp .DS_Store +/nbproject +compile_commands.json # CMake CMakeCache.txt diff --git a/lib/cfacterimpl.cc b/lib/cfacterimpl.cc index 0fa9eb0e7e..c5e3fa57b8 100644 --- a/lib/cfacterimpl.cc +++ b/lib/cfacterimpl.cc @@ -139,7 +139,8 @@ void get_network_facts(fact_map& facts) } // find number of interfaces. - ifconf ifc = {}; + ifconf ifc; + memset(&ifc, 0, sizeof(ifc)); if (ioctl(sock, SIOCGIFCONF, &ifc) < 0) { perror("ioctl"); exit(1); @@ -217,7 +218,7 @@ void get_network_facts(fact_map& facts) #ifndef __APPLE__ // SIOCGIFHWADDR isn't supported, might need to use SC library // and the mac address (but not for loopback) if (strcmp(req.ifr_name, "lo")) { - if (ioctl(s, SIOCGIFHWADDR, r) < 0) { + if (ioctl(sock, SIOCGIFHWADDR, &req) < 0) { perror("ioctl SIOCGIFHWADDR"); exit(1); } From ac616f3ab1616096654659dd2c2e5f45664ee6a0 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Thu, 3 Apr 2014 18:03:29 -0700 Subject: [PATCH 1760/3753] (CFACT-2) Add Google's RE2 library to cfacter. Adding Google's RE2 library to cfacter's build. We can now include and make use of the library. See https://code.google.com/p/re2/ for more information about the library. --- .gitignore | 2 ++ CMakeLists.txt | 28 ++++++++++++++++++++++------ README.md | 12 ++++++------ exe/CMakeLists.txt | 13 +++++-------- lib/CMakeLists.txt | 29 ++++++++++++++++++++++------- vendor/README.md | 7 +++++++ vendor/re2.cmake | 42 ++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 106 insertions(+), 27 deletions(-) create mode 100644 vendor/README.md create mode 100644 vendor/re2.cmake diff --git a/.gitignore b/.gitignore index 6cc4d93f53..eb60239376 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,5 @@ CMakeScripts /lib/libcfacter.so /lib/libcfacter.dylib /lib/Debug +/vendor/src +/vendor/tmp diff --git a/CMakeLists.txt b/CMakeLists.txt index c50df8c9c8..e158e16541 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 2.8) +cmake_minimum_required(VERSION 2.8.12) project(CFACTER) set(CFACTER_VERSION_MAJOR 0) @@ -7,10 +7,27 @@ set(CFACTER_VERSION_PATCH 1) # Generate a file containing the above version numbers configure_file ( - "${PROJECT_SOURCE_DIR}/version.h.in" - "${PROJECT_SOURCE_DIR}/version.h" + "${PROJECT_SOURCE_DIR}/version.h.in" + "${PROJECT_SOURCE_DIR}/version.h" ) +if(APPLE) + # Set the RPATH to OSX magic value @executable_path (where the loading executable is located) + set(CMAKE_INSTALL_RPATH "@executable_path/") + set(CMAKE_MACOSX_RPATH 1) +elseif(UNIX) + # Set the RPATH to be magic value $ORIGIN (where the loading executable is located) + set(CMAKE_INSTALL_RPATH "$ORIGIN") +endif() + + +# Set the default install path +if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "/opt/cfacter" CACHE PATH "default install path" FORCE) +endif() + +set(VENDOR_DIRECTORY "${PROJECT_SOURCE_DIR}/vendor") + add_subdirectory(exe) # @@ -22,7 +39,7 @@ if (NOT PYTHONINTERP_FOUND) else() set(CPPLINT_FILTER "-build/include" # Why? - "-build/namespaces" # What's a namespace to do + "-build/namespaces" # What's a namespace to do "-legal/copyright" # Not yet "-runtime/references" # Not sure about this religion "-readability/streams" # What? @@ -32,7 +49,7 @@ else() "-readability/todo" # Seriously? todo comments need to identify an owner? pffft ) - FILE (GLOB ALL_SOURCES lib/*.cc exe/*.cc) + file(GLOB ALL_SOURCES lib/*.cc exe/*.cc) set(CPPLINT_PATH "ext/cpplint.py") @@ -49,7 +66,6 @@ else() COMMAND ${PYTHON_EXECUTABLE} ${CPPLINT_PATH} ${CPPLINT_ARGS} ${ALL_SOURCES} VERBATIM ) - endif() add_custom_target(cppcheck diff --git a/README.md b/README.md index 015001f1b7..ed7b4aa31c 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ To build cfacter with debug information: To turn off debug information: -`$ cmake -DCMAKE_BUILD_TYPE= . && make clean all` +`$ cmake -UCMAKE_BUILD_TYPE . && make clean all` Run --- @@ -42,15 +42,15 @@ Install You can install cfacter into your system: -`$ sudo make install` +`$ make && sudo make install` -By default, this will install cfacter into `/usr/local/bin`, using an install prefix of `/usr/local`. +By default, this will install cfacter into `/opt/cfacter`. -To install with a different prefix: +To install to a different location, set the install prefix: -`$ cmake -DCMAKE_INSTALL_PREFIX=/usr . && sudo make clean install` +`$ cmake -DCMAKE_INSTALL_PREFIX=~/cfacter . && make clean install` -This would install cfacter into `/usr/bin`. +This would install cfacter into `~/cfacter`. Uninstall --------- diff --git a/exe/CMakeLists.txt b/exe/CMakeLists.txt index 6d95761029..6701825c1f 100644 --- a/exe/CMakeLists.txt +++ b/exe/CMakeLists.txt @@ -1,8 +1,8 @@ -cmake_minimum_required(VERSION 2.8) +cmake_minimum_required(VERSION 2.8.12) project(CFACTER) set(CFACTER_SOURCES - ${PROJECT_SOURCE_DIR}/cfacter.cc + ${PROJECT_SOURCE_DIR}/cfacter.cc ) set(LIBCFACTER_DIR ../lib) @@ -12,16 +12,13 @@ add_subdirectory(${LIBCFACTER_DIR} ${LIBCFACTER_DIR}) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra -Werror -Wno-unused-parameter") include_directories( - ${PROJECT_SOURCE_DIR}/${LIBCFACTER_DIR} + "${PROJECT_SOURCE_DIR}/${LIBCFACTER_DIR}" ) link_directories( - ${PROJECT_SOURCE_DIR}/${LIBCFACTER_DIR} + "${PROJECT_SOURCE_DIR}/${LIBCFACTER_DIR}" ) -# Add install location of lib to installed cfacter -set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") - add_executable(cfacter ${CFACTER_SOURCES}) target_link_libraries(cfacter libcfacter) -install(TARGETS cfacter DESTINATION bin) \ No newline at end of file +install(TARGETS cfacter DESTINATION .) \ No newline at end of file diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index fe5af5ff10..4a55e49ee6 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -1,17 +1,32 @@ -cmake_minimum_required(VERSION 2.8) +cmake_minimum_required(VERSION 2.8.12) project(CFACTER) +include(${VENDOR_DIRECTORY}/re2.cmake) set(CFACTERLIB_SOURCES - ${PROJECT_SOURCE_DIR}/cfacterlib.cc - ${PROJECT_SOURCE_DIR}/cfacterimpl.cc + "${PROJECT_SOURCE_DIR}/cfacterlib.cc" + "${PROJECT_SOURCE_DIR}/cfacterimpl.cc" ) -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra -Werror -Wno-unused-parameter") - if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-tautological-constant-out-of-range-compare") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra -Werror -Wno-unused-parameter -Wno-tautological-constant-out-of-range-compare") +elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra -Werror -Wno-unused-parameter") +elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel") +elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") endif() +# Add the library target without a prefix (name already has the 'lib') add_library(libcfacter SHARED ${CFACTERLIB_SOURCES}) set_target_properties(libcfacter PROPERTIES PREFIX "") -install (TARGETS libcfacter DESTINATION lib) \ No newline at end of file +install(TARGETS libcfacter DESTINATION .) + +# Set include directories +include_directories( + ${RE2_INCLUDE_DIRS} +) + +# Link in additional libraries +target_link_libraries(libcfacter + pthread + libre2 +) \ No newline at end of file diff --git a/vendor/README.md b/vendor/README.md new file mode 100644 index 0000000000..eea214463b --- /dev/null +++ b/vendor/README.md @@ -0,0 +1,7 @@ +Vendor Libraries +---------------- + +This directory contains third-party vendor libraries. + +The following libraries are used by cfacter: +* re2 - Google's regular expression library \ No newline at end of file diff --git a/vendor/re2.cmake b/vendor/re2.cmake new file mode 100644 index 0000000000..dc4d0627a5 --- /dev/null +++ b/vendor/re2.cmake @@ -0,0 +1,42 @@ +cmake_minimum_required(VERSION 2.8.12) +include(ExternalProject) + +set(RE2_SHARED_OBJECT_FILE "libre2.so.0") + +if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + # Fix the install name to be on the reference path + set(RE2_LD_FLAGS "-install_name @rpath/${RE2_SHARED_OBJECT_FILE}") +elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") + set(RE2_CPP_FLAGS "-Wno-unused-local-typedefs") +elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel") +elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") +endif() + +# Add an external project to build re2 +externalproject_add( + re2 + PREFIX "${VENDOR_DIRECTORY}" + URL "file://${VENDOR_DIRECTORY}/re2-20140304.tgz" + URL_MD5 "e82a6491efdf2bc928dc3779abcb3bc8" + CONFIGURE_COMMAND "" + BUILD_COMMAND make CPPFLAGS=${RE2_CPP_FLAGS} LDFLAGS=${RE2_LD_FLAGS} + BUILD_IN_SOURCE 1 + INSTALL_COMMAND "" + ALWAYS 1 +) + +# Set some useful variables based on the source directory +externalproject_get_property(re2 SOURCE_DIR) +set(RE2_INCLUDE_DIRS "${SOURCE_DIR}") +set(RE2_SHARED_OBJECT_PATH "${SOURCE_DIR}/obj/so/${RE2_SHARED_OBJECT_FILE}") +set(RE2_LIBRARIES "${RE2_SHARED_OBJECT_PATH}") + +set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "${SOURCE_DIR}/obj") + +add_library(libre2 SHARED IMPORTED) +set_target_properties(libre2 + PROPERTIES PREFIX "" + IMPORTED_LOCATION "${RE2_SHARED_OBJECT_PATH}" +) +add_dependencies(libre2 re2) +install(FILES "${RE2_SHARED_OBJECT_PATH}" DESTINATION .) \ No newline at end of file From 0a9e3fb3ec2b4ea0e4b1742a6274b2e95ff060bb Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Fri, 4 Apr 2014 16:02:51 -0700 Subject: [PATCH 1761/3753] (maint) Update to build out-of-source. Updating CMake files to support building "out-of-source". This is where you create a directory specifically for containing the build files and generating the Makefile in that directory. In addition to keeping the source directories from being cluttered with build outputs, this also allows us to generate different CMake configurations (debug vs. release, for example) side-by-side. Moving the rapidjson source out of the lib directory and into the vendor directory. --- .gitignore | 19 +- README.md | 33 +- exe/CMakeLists.txt | 11 +- lib/CMakeLists.txt | 12 +- lib/rapidjson/document.h | 821 ------------------------------- lib/rapidjson/filestream.h | 46 -- lib/rapidjson/internal/pow10.h | 54 -- lib/rapidjson/internal/stack.h | 82 --- lib/rapidjson/internal/strfunc.h | 24 - lib/rapidjson/prettywriter.h | 156 ------ lib/rapidjson/rapidjson.h | 525 -------------------- lib/rapidjson/reader.h | 683 ------------------------- lib/rapidjson/stringbuffer.h | 49 -- lib/rapidjson/writer.h | 241 --------- vendor/README.md | 3 +- vendor/rapidjson-0.11.tgz | Bin 0 -> 803361 bytes vendor/rapidjson.cmake | 19 + vendor/re2.cmake | 4 +- 18 files changed, 58 insertions(+), 2724 deletions(-) delete mode 100644 lib/rapidjson/document.h delete mode 100644 lib/rapidjson/filestream.h delete mode 100644 lib/rapidjson/internal/pow10.h delete mode 100644 lib/rapidjson/internal/stack.h delete mode 100644 lib/rapidjson/internal/strfunc.h delete mode 100644 lib/rapidjson/prettywriter.h delete mode 100644 lib/rapidjson/rapidjson.h delete mode 100644 lib/rapidjson/reader.h delete mode 100644 lib/rapidjson/stringbuffer.h delete mode 100644 lib/rapidjson/writer.h create mode 100644 vendor/rapidjson-0.11.tgz create mode 100644 vendor/rapidjson.cmake diff --git a/.gitignore b/.gitignore index eb60239376..7f9424303c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,22 +4,7 @@ /nbproject compile_commands.json -# CMake -CMakeCache.txt -CMakeFiles -Makefile -cmake_install.cmake -install_manifest.txt -/CFACTER.* -CFACTER.build -CMakeScripts - # Generated files /version.h -/exe/cfacter -/exe/Debug -/lib/libcfacter.so -/lib/libcfacter.dylib -/lib/Debug -/vendor/src -/vendor/tmp +/debug/ +/release/ diff --git a/README.md b/README.md index ed7b4aa31c..1950e03096 100644 --- a/README.md +++ b/README.md @@ -3,52 +3,63 @@ cfacter Tinkering with a C/C++ facter -Requirements ------------- +Build Requirements +------------------ * CMake >= 2.8 Generating Build Files ---------------------- +All examples start by assuming the current directory is the root of the repo. + Before building cfacter, use `cmake` to generate build files: -`$ cmake .` +`$ mkdir release` +`$ cd release` +`$ cmake ..` Build ----- To build cfacter, use 'make': +`$ cd release` `$ make` To build cfacter with debug information: -`$ cmake -DCMAKE_BUILD_TYPE=Debug . && make clean all` - -To turn off debug information: - -`$ cmake -UCMAKE_BUILD_TYPE . && make clean all` +`$ mkdir debug` +`$ cd debug` +`$ cmake -DCMAKE_BUILD_TYPE=Debug ..` +`$ make` Run --- You can run cfacter from where it was built: -`$ exe/cfacter` +`$ release/exe/cfacter` + +For a debug build: + +`$ debug/exe/cfacter` Install ------- You can install cfacter into your system: +`$ cd release` `$ make && sudo make install` By default, this will install cfacter into `/opt/cfacter`. To install to a different location, set the install prefix: -`$ cmake -DCMAKE_INSTALL_PREFIX=~/cfacter . && make clean install` +`$ cd release` +`$ cmake -DCMAKE_INSTALL_PREFIX=~/cfacter ..` +`$ make clean install` This would install cfacter into `~/cfacter`. @@ -57,4 +68,4 @@ Uninstall Run the following command to remove files that were previously installed: -`$ sudo xargs rm < install_manifest.txt` \ No newline at end of file +`$ sudo xargs rm < release/install_manifest.txt` diff --git a/exe/CMakeLists.txt b/exe/CMakeLists.txt index 6701825c1f..11b4c6c3c0 100644 --- a/exe/CMakeLists.txt +++ b/exe/CMakeLists.txt @@ -1,8 +1,7 @@ cmake_minimum_required(VERSION 2.8.12) -project(CFACTER) set(CFACTER_SOURCES - ${PROJECT_SOURCE_DIR}/cfacter.cc + ${CMAKE_CURRENT_LIST_DIR}/cfacter.cc ) set(LIBCFACTER_DIR ../lib) @@ -12,13 +11,9 @@ add_subdirectory(${LIBCFACTER_DIR} ${LIBCFACTER_DIR}) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra -Werror -Wno-unused-parameter") include_directories( - "${PROJECT_SOURCE_DIR}/${LIBCFACTER_DIR}" -) - -link_directories( - "${PROJECT_SOURCE_DIR}/${LIBCFACTER_DIR}" + ${LIBCFACTER_DIR} ) add_executable(cfacter ${CFACTER_SOURCES}) target_link_libraries(cfacter libcfacter) -install(TARGETS cfacter DESTINATION .) \ No newline at end of file +install(TARGETS cfacter DESTINATION .) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 4a55e49ee6..37bb64f3df 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -1,10 +1,10 @@ cmake_minimum_required(VERSION 2.8.12) -project(CFACTER) include(${VENDOR_DIRECTORY}/re2.cmake) +include(${VENDOR_DIRECTORY}/rapidjson.cmake) set(CFACTERLIB_SOURCES - "${PROJECT_SOURCE_DIR}/cfacterlib.cc" - "${PROJECT_SOURCE_DIR}/cfacterimpl.cc" + "${CMAKE_CURRENT_LIST_DIR}/cfacterlib.cc" + "${CMAKE_CURRENT_LIST_DIR}/cfacterimpl.cc" ) if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") @@ -23,10 +23,14 @@ install(TARGETS libcfacter DESTINATION .) # Set include directories include_directories( ${RE2_INCLUDE_DIRS} + ${RAPIDJSON_INCLUDE_DIRS} ) # Link in additional libraries target_link_libraries(libcfacter pthread libre2 -) \ No newline at end of file +) + +# Add a dependency on rapidjson +add_dependencies(libcfacter rapidjson) diff --git a/lib/rapidjson/document.h b/lib/rapidjson/document.h deleted file mode 100644 index 402b65d871..0000000000 --- a/lib/rapidjson/document.h +++ /dev/null @@ -1,821 +0,0 @@ -#ifndef RAPIDJSON_DOCUMENT_H_ -#define RAPIDJSON_DOCUMENT_H_ - -#include "reader.h" -#include "internal/strfunc.h" -#include // placement new - -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable : 4127) // conditional expression is constant -#endif - -namespace rapidjson { - -/////////////////////////////////////////////////////////////////////////////// -// GenericValue - -//! Represents a JSON value. Use Value for UTF8 encoding and default allocator. -/*! - A JSON value can be one of 7 types. This class is a variant type supporting - these types. - - Use the Value if UTF8 and default allocator - - \tparam Encoding Encoding of the value. (Even non-string values need to have the same encoding in a document) - \tparam Allocator Allocator type for allocating memory of object, array and string. -*/ -#pragma pack (push, 4) -template > -class GenericValue { -public: - //! Name-value pair in an object. - struct Member { - GenericValue name; //!< name of member (must be a string) - GenericValue value; //!< value of member. - }; - - typedef Encoding EncodingType; //!< Encoding type from template parameter. - typedef Allocator AllocatorType; //!< Allocator type from template parameter. - typedef typename Encoding::Ch Ch; //!< Character type derived from Encoding. - typedef Member* MemberIterator; //!< Member iterator for iterating in object. - typedef const Member* ConstMemberIterator; //!< Constant member iterator for iterating in object. - typedef GenericValue* ValueIterator; //!< Value iterator for iterating in array. - typedef const GenericValue* ConstValueIterator; //!< Constant value iterator for iterating in array. - - //!@name Constructors and destructor. - //@{ - - //! Default constructor creates a null value. - GenericValue() : flags_(kNullFlag) {} - - //! Copy constructor is not permitted. -private: - GenericValue(const GenericValue& rhs); - -public: - - //! Constructor with JSON value type. - /*! This creates a Value of specified type with default content. - \param type Type of the value. - \note Default content for number is zero. - */ - GenericValue(Type type) { - static const unsigned defaultFlags[7] = { - kNullFlag, kFalseFlag, kTrueFlag, kObjectFlag, kArrayFlag, kConstStringFlag, - kNumberFlag | kIntFlag | kUintFlag | kInt64Flag | kUint64Flag | kDoubleFlag - }; - RAPIDJSON_ASSERT(type <= kNumberType); - flags_ = defaultFlags[type]; - memset(&data_, 0, sizeof(data_)); - } - - //! Constructor for boolean value. - GenericValue(bool b) : flags_(b ? kTrueFlag : kFalseFlag) {} - - //! Constructor for int value. - GenericValue(int i) : flags_(kNumberIntFlag) { - data_.n.i64 = i; - if (i >= 0) - flags_ |= kUintFlag | kUint64Flag; - } - - //! Constructor for unsigned value. - GenericValue(unsigned u) : flags_(kNumberUintFlag) { - data_.n.u64 = u; - if (!(u & 0x80000000)) - flags_ |= kIntFlag | kInt64Flag; - } - - //! Constructor for int64_t value. - GenericValue(int64_t i64) : flags_(kNumberInt64Flag) { - data_.n.i64 = i64; - if (i64 >= 0) { - flags_ |= kNumberUint64Flag; - if (!(i64 & 0xFFFFFFFF00000000LL)) - flags_ |= kUintFlag; - if (!(i64 & 0xFFFFFFFF80000000LL)) - flags_ |= kIntFlag; - } - else if (i64 >= -2147483648LL) - flags_ |= kIntFlag; - } - - //! Constructor for uint64_t value. - GenericValue(uint64_t u64) : flags_(kNumberUint64Flag) { - data_.n.u64 = u64; - if (!(u64 & 0x8000000000000000ULL)) - flags_ |= kInt64Flag; - if (!(u64 & 0xFFFFFFFF00000000ULL)) - flags_ |= kUintFlag; - if (!(u64 & 0xFFFFFFFF80000000ULL)) - flags_ |= kIntFlag; - } - - //! Constructor for double value. - GenericValue(double d) : flags_(kNumberDoubleFlag) { data_.n.d = d; } - - //! Constructor for constant string (i.e. do not make a copy of string) - GenericValue(const Ch* s, SizeType length) { - RAPIDJSON_ASSERT(s != NULL); - flags_ = kConstStringFlag; - data_.s.str = s; - data_.s.length = length; - } - - //! Constructor for constant string (i.e. do not make a copy of string) - GenericValue(const Ch* s) { SetStringRaw(s, internal::StrLen(s)); } - - //! Constructor for copy-string (i.e. do make a copy of string) - GenericValue(const Ch* s, SizeType length, Allocator& allocator) { SetStringRaw(s, length, allocator); } - - //! Constructor for copy-string (i.e. do make a copy of string) - GenericValue(const Ch*s, Allocator& allocator) { SetStringRaw(s, internal::StrLen(s), allocator); } - - //! Destructor. - /*! Need to destruct elements of array, members of object, or copy-string. - */ - ~GenericValue() { - if (Allocator::kNeedFree) { // Shortcut by Allocator's trait - switch(flags_) { - case kArrayFlag: - for (GenericValue* v = data_.a.elements; v != data_.a.elements + data_.a.size; ++v) - v->~GenericValue(); - Allocator::Free(data_.a.elements); - break; - - case kObjectFlag: - for (Member* m = data_.o.members; m != data_.o.members + data_.o.size; ++m) { - m->name.~GenericValue(); - m->value.~GenericValue(); - } - Allocator::Free(data_.o.members); - break; - - case kCopyStringFlag: - Allocator::Free(const_cast(data_.s.str)); - break; - } - } - } - - //@} - - //!@name Assignment operators - //@{ - - //! Assignment with move semantics. - /*! \param rhs Source of the assignment. It will become a null value after assignment. - */ - GenericValue& operator=(GenericValue& rhs) { - RAPIDJSON_ASSERT(this != &rhs); - this->~GenericValue(); - memcpy(this, &rhs, sizeof(GenericValue)); - rhs.flags_ = kNullFlag; - return *this; - } - - //! Assignment with primitive types. - /*! \tparam T Either Type, int, unsigned, int64_t, uint64_t, const Ch* - \param value The value to be assigned. - */ - template - GenericValue& operator=(T value) { - this->~GenericValue(); - new (this) GenericValue(value); - return *this; - } - //@} - - //!@name Type - //@{ - - Type GetType() const { return static_cast(flags_ & kTypeMask); } - bool IsNull() const { return flags_ == kNullFlag; } - bool IsFalse() const { return flags_ == kFalseFlag; } - bool IsTrue() const { return flags_ == kTrueFlag; } - bool IsBool() const { return (flags_ & kBoolFlag) != 0; } - bool IsObject() const { return flags_ == kObjectFlag; } - bool IsArray() const { return flags_ == kArrayFlag; } - bool IsNumber() const { return (flags_ & kNumberFlag) != 0; } - bool IsInt() const { return (flags_ & kIntFlag) != 0; } - bool IsUint() const { return (flags_ & kUintFlag) != 0; } - bool IsInt64() const { return (flags_ & kInt64Flag) != 0; } - bool IsUint64() const { return (flags_ & kUint64Flag) != 0; } - bool IsDouble() const { return (flags_ & kDoubleFlag) != 0; } - bool IsString() const { return (flags_ & kStringFlag) != 0; } - - //@} - - //!@name Null - //@{ - - GenericValue& SetNull() { this->~GenericValue(); new (this) GenericValue(); return *this; } - - //@} - - //!@name Bool - //@{ - - bool GetBool() const { RAPIDJSON_ASSERT(IsBool()); return flags_ == kTrueFlag; } - GenericValue& SetBool(bool b) { this->~GenericValue(); new (this) GenericValue(b); return *this; } - - //@} - - //!@name Object - //@{ - - //! Set this value as an empty object. - GenericValue& SetObject() { this->~GenericValue(); new (this) GenericValue(kObjectType); return *this; } - - //! Get the value associated with the object's name. - GenericValue& operator[](const Ch* name) { - if (Member* member = FindMember(name)) - return member->value; - else { - static GenericValue NullValue; - return NullValue; - } - } - const GenericValue& operator[](const Ch* name) const { return const_cast(*this)[name]; } - - //! Member iterators. - ConstMemberIterator MemberBegin() const { RAPIDJSON_ASSERT(IsObject()); return data_.o.members; } - ConstMemberIterator MemberEnd() const { RAPIDJSON_ASSERT(IsObject()); return data_.o.members + data_.o.size; } - MemberIterator MemberBegin() { RAPIDJSON_ASSERT(IsObject()); return data_.o.members; } - MemberIterator MemberEnd() { RAPIDJSON_ASSERT(IsObject()); return data_.o.members + data_.o.size; } - - //! Check whether a member exists in the object. - bool HasMember(const Ch* name) const { return FindMember(name) != 0; } - - //! Add a member (name-value pair) to the object. - /*! \param name A string value as name of member. - \param value Value of any type. - \param allocator Allocator for reallocating memory. - \return The value itself for fluent API. - \note The ownership of name and value will be transfered to this object if success. - */ - GenericValue& AddMember(GenericValue& name, GenericValue& value, Allocator& allocator) { - RAPIDJSON_ASSERT(IsObject()); - RAPIDJSON_ASSERT(name.IsString()); - Object& o = data_.o; - if (o.size >= o.capacity) { - if (o.capacity == 0) { - o.capacity = kDefaultObjectCapacity; - o.members = (Member*)allocator.Malloc(o.capacity * sizeof(Member)); - } - else { - SizeType oldCapacity = o.capacity; - o.capacity *= 2; - o.members = (Member*)allocator.Realloc(o.members, oldCapacity * sizeof(Member), o.capacity * sizeof(Member)); - } - } - o.members[o.size].name.RawAssign(name); - o.members[o.size].value.RawAssign(value); - o.size++; - return *this; - } - - GenericValue& AddMember(const Ch* name, Allocator& nameAllocator, GenericValue& value, Allocator& allocator) { - GenericValue n(name, internal::StrLen(name), nameAllocator); - return AddMember(n, value, allocator); - } - - GenericValue& AddMember(const Ch* name, GenericValue& value, Allocator& allocator) { - GenericValue n(name, internal::StrLen(name)); - return AddMember(n, value, allocator); - } - - template - GenericValue& AddMember(const Ch* name, T value, Allocator& allocator) { - GenericValue n(name, internal::StrLen(name)); - GenericValue v(value); - return AddMember(n, v, allocator); - } - - //! Remove a member in object by its name. - /*! \param name Name of member to be removed. - \return Whether the member existed. - \note Removing member is implemented by moving the last member. So the ordering of members is changed. - */ - bool RemoveMember(const Ch* name) { - RAPIDJSON_ASSERT(IsObject()); - if (Member* m = FindMember(name)) { - RAPIDJSON_ASSERT(data_.o.size > 0); - RAPIDJSON_ASSERT(data_.o.members != 0); - - Member* last = data_.o.members + (data_.o.size - 1); - if (data_.o.size > 1 && m != last) { - // Move the last one to this place - m->name = last->name; - m->value = last->value; - } - else { - // Only one left, just destroy - m->name.~GenericValue(); - m->value.~GenericValue(); - } - --data_.o.size; - return true; - } - return false; - } - - //@} - - //!@name Array - //@{ - - //! Set this value as an empty array. - GenericValue& SetArray() { this->~GenericValue(); new (this) GenericValue(kArrayType); return *this; } - - //! Get the number of elements in array. - SizeType Size() const { RAPIDJSON_ASSERT(IsArray()); return data_.a.size; } - - //! Get the capacity of array. - SizeType Capacity() const { RAPIDJSON_ASSERT(IsArray()); return data_.a.capacity; } - - //! Check whether the array is empty. - bool Empty() const { RAPIDJSON_ASSERT(IsArray()); return data_.a.size == 0; } - - //! Remove all elements in the array. - /*! This function do not deallocate memory in the array, i.e. the capacity is unchanged. - */ - void Clear() { - RAPIDJSON_ASSERT(IsArray()); - for (SizeType i = 0; i < data_.a.size; ++i) - data_.a.elements[i].~GenericValue(); - data_.a.size = 0; - } - - //! Get an element from array by index. - /*! \param index Zero-based index of element. - \note -\code -Value a(kArrayType); -a.PushBack(123); -int x = a[0].GetInt(); // Error: operator[ is ambiguous, as 0 also mean a null pointer of const char* type. -int y = a[SizeType(0)].GetInt(); // Cast to SizeType will work. -int z = a[0u].GetInt(); // This works too. -\endcode - */ - GenericValue& operator[](SizeType index) { - RAPIDJSON_ASSERT(IsArray()); - RAPIDJSON_ASSERT(index < data_.a.size); - return data_.a.elements[index]; - } - const GenericValue& operator[](SizeType index) const { return const_cast(*this)[index]; } - - //! Element iterator - ValueIterator Begin() { RAPIDJSON_ASSERT(IsArray()); return data_.a.elements; } - ValueIterator End() { RAPIDJSON_ASSERT(IsArray()); return data_.a.elements + data_.a.size; } - ConstValueIterator Begin() const { return const_cast(*this).Begin(); } - ConstValueIterator End() const { return const_cast(*this).End(); } - - //! Request the array to have enough capacity to store elements. - /*! \param newCapacity The capacity that the array at least need to have. - \param allocator The allocator for allocating memory. It must be the same one use previously. - \return The value itself for fluent API. - */ - GenericValue& Reserve(SizeType newCapacity, Allocator &allocator) { - RAPIDJSON_ASSERT(IsArray()); - if (newCapacity > data_.a.capacity) { - data_.a.elements = (GenericValue*)allocator.Realloc(data_.a.elements, data_.a.capacity * sizeof(GenericValue), newCapacity * sizeof(GenericValue)); - data_.a.capacity = newCapacity; - } - return *this; - } - - //! Append a value at the end of the array. - /*! \param value The value to be appended. - \param allocator The allocator for allocating memory. It must be the same one use previously. - \return The value itself for fluent API. - \note The ownership of the value will be transfered to this object if success. - \note If the number of elements to be appended is known, calls Reserve() once first may be more efficient. - */ - GenericValue& PushBack(GenericValue& value, Allocator& allocator) { - RAPIDJSON_ASSERT(IsArray()); - if (data_.a.size >= data_.a.capacity) - Reserve(data_.a.capacity == 0 ? kDefaultArrayCapacity : data_.a.capacity * 2, allocator); - data_.a.elements[data_.a.size++].RawAssign(value); - return *this; - } - - template - GenericValue& PushBack(T value, Allocator& allocator) { - GenericValue v(value); - return PushBack(v, allocator); - } - - //! Remove the last element in the array. - GenericValue& PopBack() { - RAPIDJSON_ASSERT(IsArray()); - RAPIDJSON_ASSERT(!Empty()); - data_.a.elements[--data_.a.size].~GenericValue(); - return *this; - } - //@} - - //!@name Number - //@{ - - int GetInt() const { RAPIDJSON_ASSERT(flags_ & kIntFlag); return data_.n.i.i; } - unsigned GetUint() const { RAPIDJSON_ASSERT(flags_ & kUintFlag); return data_.n.u.u; } - int64_t GetInt64() const { RAPIDJSON_ASSERT(flags_ & kInt64Flag); return data_.n.i64; } - uint64_t GetUint64() const { RAPIDJSON_ASSERT(flags_ & kUint64Flag); return data_.n.u64; } - - double GetDouble() const { - RAPIDJSON_ASSERT(IsNumber()); - if ((flags_ & kDoubleFlag) != 0) return data_.n.d; // exact type, no conversion. - if ((flags_ & kIntFlag) != 0) return data_.n.i.i; // int -> double - if ((flags_ & kUintFlag) != 0) return data_.n.u.u; // unsigned -> double - if ((flags_ & kInt64Flag) != 0) return (double)data_.n.i64; // int64_t -> double (may lose precision) - RAPIDJSON_ASSERT((flags_ & kUint64Flag) != 0); return (double)data_.n.u64; // uint64_t -> double (may lose precision) - } - - GenericValue& SetInt(int i) { this->~GenericValue(); new (this) GenericValue(i); return *this; } - GenericValue& SetUint(unsigned u) { this->~GenericValue(); new (this) GenericValue(u); return *this; } - GenericValue& SetInt64(int64_t i64) { this->~GenericValue(); new (this) GenericValue(i64); return *this; } - GenericValue& SetUint64(uint64_t u64) { this->~GenericValue(); new (this) GenericValue(u64); return *this; } - GenericValue& SetDouble(double d) { this->~GenericValue(); new (this) GenericValue(d); return *this; } - - //@} - - //!@name String - //@{ - - const Ch* GetString() const { RAPIDJSON_ASSERT(IsString()); return data_.s.str; } - - //! Get the length of string. - /*! Since rapidjson permits "\u0000" in the json string, strlen(v.GetString()) may not equal to v.GetStringLength(). - */ - SizeType GetStringLength() const { RAPIDJSON_ASSERT(IsString()); return data_.s.length; } - - //! Set this value as a string without copying source string. - /*! This version has better performance with supplied length, and also support string containing null character. - \param s source string pointer. - \param length The length of source string, excluding the trailing null terminator. - \return The value itself for fluent API. - */ - GenericValue& SetString(const Ch* s, SizeType length) { this->~GenericValue(); SetStringRaw(s, length); return *this; } - - //! Set this value as a string without copying source string. - /*! \param s source string pointer. - \return The value itself for fluent API. - */ - GenericValue& SetString(const Ch* s) { return SetString(s, internal::StrLen(s)); } - - //! Set this value as a string by copying from source string. - /*! This version has better performance with supplied length, and also support string containing null character. - \param s source string. - \param length The length of source string, excluding the trailing null terminator. - \param allocator Allocator for allocating copied buffer. Commonly use document.GetAllocator(). - \return The value itself for fluent API. - */ - GenericValue& SetString(const Ch* s, SizeType length, Allocator& allocator) { this->~GenericValue(); SetStringRaw(s, length, allocator); return *this; } - - //! Set this value as a string by copying from source string. - /*! \param s source string. - \param allocator Allocator for allocating copied buffer. Commonly use document.GetAllocator(). - \return The value itself for fluent API. - */ - GenericValue& SetString(const Ch* s, Allocator& allocator) { SetString(s, internal::StrLen(s), allocator); return *this; } - - //@} - - //! Generate events of this value to a Handler. - /*! This function adopts the GoF visitor pattern. - Typical usage is to output this JSON value as JSON text via Writer, which is a Handler. - It can also be used to deep clone this value via GenericDocument, which is also a Handler. - \tparam Handler type of handler. - \param handler An object implementing concept Handler. - */ - template - const GenericValue& Accept(Handler& handler) const { - switch(GetType()) { - case kNullType: handler.Null(); break; - case kFalseType: handler.Bool(false); break; - case kTrueType: handler.Bool(true); break; - - case kObjectType: - handler.StartObject(); - for (Member* m = data_.o.members; m != data_.o.members + data_.o.size; ++m) { - handler.String(m->name.data_.s.str, m->name.data_.s.length, false); - m->value.Accept(handler); - } - handler.EndObject(data_.o.size); - break; - - case kArrayType: - handler.StartArray(); - for (GenericValue* v = data_.a.elements; v != data_.a.elements + data_.a.size; ++v) - v->Accept(handler); - handler.EndArray(data_.a.size); - break; - - case kStringType: - handler.String(data_.s.str, data_.s.length, false); - break; - - case kNumberType: - if (IsInt()) handler.Int(data_.n.i.i); - else if (IsUint()) handler.Uint(data_.n.u.u); - else if (IsInt64()) handler.Int64(data_.n.i64); - else if (IsUint64()) handler.Uint64(data_.n.u64); - else handler.Double(data_.n.d); - break; - } - return *this; - } - -private: - template - friend class GenericDocument; - - enum { - kBoolFlag = 0x100, - kNumberFlag = 0x200, - kIntFlag = 0x400, - kUintFlag = 0x800, - kInt64Flag = 0x1000, - kUint64Flag = 0x2000, - kDoubleFlag = 0x4000, - kStringFlag = 0x100000, - kCopyFlag = 0x200000, - - // Initial flags of different types. - kNullFlag = kNullType, - kTrueFlag = kTrueType | kBoolFlag, - kFalseFlag = kFalseType | kBoolFlag, - kNumberIntFlag = kNumberType | kNumberFlag | kIntFlag | kInt64Flag, - kNumberUintFlag = kNumberType | kNumberFlag | kUintFlag | kUint64Flag | kInt64Flag, - kNumberInt64Flag = kNumberType | kNumberFlag | kInt64Flag, - kNumberUint64Flag = kNumberType | kNumberFlag | kUint64Flag, - kNumberDoubleFlag = kNumberType | kNumberFlag | kDoubleFlag, - kConstStringFlag = kStringType | kStringFlag, - kCopyStringFlag = kStringType | kStringFlag | kCopyFlag, - kObjectFlag = kObjectType, - kArrayFlag = kArrayType, - - kTypeMask = 0xFF // bitwise-and with mask of 0xFF can be optimized by compiler - }; - - static const SizeType kDefaultArrayCapacity = 16; - static const SizeType kDefaultObjectCapacity = 16; - - struct String { - const Ch* str; - SizeType length; - unsigned hashcode; //!< reserved - }; // 12 bytes in 32-bit mode, 16 bytes in 64-bit mode - - // By using proper binary layout, retrieval of different integer types do not need conversions. - union Number { -#if RAPIDJSON_ENDIAN == RAPIDJSON_LITTLEENDIAN - struct I { - int i; - char padding[4]; - }i; - struct U { - unsigned u; - char padding2[4]; - }u; -#else - struct I { - char padding[4]; - int i; - }i; - struct U { - char padding2[4]; - unsigned u; - }u; -#endif - int64_t i64; - uint64_t u64; - double d; - }; // 8 bytes - - struct Object { - Member* members; - SizeType size; - SizeType capacity; - }; // 12 bytes in 32-bit mode, 16 bytes in 64-bit mode - - struct Array { - GenericValue* elements; - SizeType size; - SizeType capacity; - }; // 12 bytes in 32-bit mode, 16 bytes in 64-bit mode - - union Data { - String s; - Number n; - Object o; - Array a; - }; // 12 bytes in 32-bit mode, 16 bytes in 64-bit mode - - //! Find member by name. - Member* FindMember(const Ch* name) { - RAPIDJSON_ASSERT(name); - RAPIDJSON_ASSERT(IsObject()); - - SizeType length = internal::StrLen(name); - - Object& o = data_.o; - for (Member* member = o.members; member != data_.o.members + data_.o.size; ++member) - if (length == member->name.data_.s.length && memcmp(member->name.data_.s.str, name, length * sizeof(Ch)) == 0) - return member; - - return 0; - } - const Member* FindMember(const Ch* name) const { return const_cast(*this).FindMember(name); } - - // Initialize this value as array with initial data, without calling destructor. - void SetArrayRaw(GenericValue* values, SizeType count, Allocator& alloctaor) { - flags_ = kArrayFlag; - data_.a.elements = (GenericValue*)alloctaor.Malloc(count * sizeof(GenericValue)); - memcpy(data_.a.elements, values, count * sizeof(GenericValue)); - data_.a.size = data_.a.capacity = count; - } - - //! Initialize this value as object with initial data, without calling destructor. - void SetObjectRaw(Member* members, SizeType count, Allocator& alloctaor) { - flags_ = kObjectFlag; - data_.o.members = (Member*)alloctaor.Malloc(count * sizeof(Member)); - memcpy(data_.o.members, members, count * sizeof(Member)); - data_.o.size = data_.o.capacity = count; - } - - //! Initialize this value as constant string, without calling destructor. - void SetStringRaw(const Ch* s, SizeType length) { - RAPIDJSON_ASSERT(s != NULL); - flags_ = kConstStringFlag; - data_.s.str = s; - data_.s.length = length; - } - - //! Initialize this value as copy string with initial data, without calling destructor. - void SetStringRaw(const Ch* s, SizeType length, Allocator& allocator) { - RAPIDJSON_ASSERT(s != NULL); - flags_ = kCopyStringFlag; - data_.s.str = (Ch *)allocator.Malloc((length + 1) * sizeof(Ch)); - data_.s.length = length; - memcpy(const_cast(data_.s.str), s, length * sizeof(Ch)); - const_cast(data_.s.str)[length] = '\0'; - } - - //! Assignment without calling destructor - void RawAssign(GenericValue& rhs) { - memcpy(this, &rhs, sizeof(GenericValue)); - rhs.flags_ = kNullFlag; - } - - Data data_; - unsigned flags_; -}; -#pragma pack (pop) - -//! Value with UTF8 encoding. -typedef GenericValue > Value; - -/////////////////////////////////////////////////////////////////////////////// -// GenericDocument - -//! A document for parsing JSON text as DOM. -/*! - \implements Handler - \tparam Encoding encoding for both parsing and string storage. - \tparam Alloactor allocator for allocating memory for the DOM, and the stack during parsing. -*/ -template > -class GenericDocument : public GenericValue { -public: - typedef typename Encoding::Ch Ch; //!< Character type derived from Encoding. - typedef GenericValue ValueType; //!< Value type of the document. - typedef Allocator AllocatorType; //!< Allocator type from template parameter. - - //! Constructor - /*! \param allocator Optional allocator for allocating stack memory. - \param stackCapacity Initial capacity of stack in bytes. - */ - GenericDocument(Allocator* allocator = 0, size_t stackCapacity = kDefaultStackCapacity) : stack_(allocator, stackCapacity), parseError_(0), errorOffset_(0) {} - - //! Parse JSON text from an input stream. - /*! \tparam parseFlags Combination of ParseFlag. - \param stream Input stream to be parsed. - \return The document itself for fluent API. - */ - template - GenericDocument& ParseStream(Stream& stream) { - ValueType::SetNull(); // Remove existing root if exist - GenericReader reader; - if (reader.template Parse(stream, *this)) { - RAPIDJSON_ASSERT(stack_.GetSize() == sizeof(ValueType)); // Got one and only one root object - this->RawAssign(*stack_.template Pop(1)); // Add this-> to prevent issue 13. - parseError_ = 0; - errorOffset_ = 0; - } - else { - parseError_ = reader.GetParseError(); - errorOffset_ = reader.GetErrorOffset(); - ClearStack(); - } - return *this; - } - - //! Parse JSON text from a mutable string. - /*! \tparam parseFlags Combination of ParseFlag. - \param str Mutable zero-terminated string to be parsed. - \return The document itself for fluent API. - */ - template - GenericDocument& ParseInsitu(Ch* str) { - GenericInsituStringStream s(str); - return ParseStream(s); - } - - //! Parse JSON text from a read-only string. - /*! \tparam parseFlags Combination of ParseFlag (must not contain kParseInsituFlag). - \param str Read-only zero-terminated string to be parsed. - */ - template - GenericDocument& Parse(const Ch* str) { - RAPIDJSON_ASSERT(!(parseFlags & kParseInsituFlag)); - GenericStringStream s(str); - return ParseStream(s); - } - - //! Whether a parse error was occured in the last parsing. - bool HasParseError() const { return parseError_ != 0; } - - //! Get the message of parsing error. - const char* GetParseError() const { return parseError_; } - - //! Get the offset in character of the parsing error. - size_t GetErrorOffset() const { return errorOffset_; } - - //! Get the allocator of this document. - Allocator& GetAllocator() { return stack_.GetAllocator(); } - - //! Get the capacity of stack in bytes. - size_t GetStackCapacity() const { return stack_.GetCapacity(); } - -private: - // Prohibit assignment - GenericDocument& operator=(const GenericDocument&); - - friend class GenericReader; // for Reader to call the following private handler functions - - // Implementation of Handler - void Null() { new (stack_.template Push()) ValueType(); } - void Bool(bool b) { new (stack_.template Push()) ValueType(b); } - void Int(int i) { new (stack_.template Push()) ValueType(i); } - void Uint(unsigned i) { new (stack_.template Push()) ValueType(i); } - void Int64(int64_t i) { new (stack_.template Push()) ValueType(i); } - void Uint64(uint64_t i) { new (stack_.template Push()) ValueType(i); } - void Double(double d) { new (stack_.template Push()) ValueType(d); } - - void String(const Ch* str, SizeType length, bool copy) { - if (copy) - new (stack_.template Push()) ValueType(str, length, GetAllocator()); - else - new (stack_.template Push()) ValueType(str, length); - } - - void StartObject() { new (stack_.template Push()) ValueType(kObjectType); } - - void EndObject(SizeType memberCount) { - typename ValueType::Member* members = stack_.template Pop(memberCount); - stack_.template Top()->SetObjectRaw(members, (SizeType)memberCount, GetAllocator()); - } - - void StartArray() { new (stack_.template Push()) ValueType(kArrayType); } - - void EndArray(SizeType elementCount) { - ValueType* elements = stack_.template Pop(elementCount); - stack_.template Top()->SetArrayRaw(elements, elementCount, GetAllocator()); - } - - void ClearStack() { - if (Allocator::kNeedFree) - while (stack_.GetSize() > 0) // Here assumes all elements in stack array are GenericValue (Member is actually 2 GenericValue objects) - (stack_.template Pop(1))->~ValueType(); - else - stack_.Clear(); - } - - static const size_t kDefaultStackCapacity = 1024; - internal::Stack stack_; - const char* parseError_; - size_t errorOffset_; -}; - -typedef GenericDocument > Document; - -} // namespace rapidjson - -#ifdef _MSC_VER -#pragma warning(pop) -#endif - -#endif // RAPIDJSON_DOCUMENT_H_ diff --git a/lib/rapidjson/filestream.h b/lib/rapidjson/filestream.h deleted file mode 100644 index 24573aa482..0000000000 --- a/lib/rapidjson/filestream.h +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef RAPIDJSON_FILESTREAM_H_ -#define RAPIDJSON_FILESTREAM_H_ - -#include - -namespace rapidjson { - -//! Wrapper of C file stream for input or output. -/*! - This simple wrapper does not check the validity of the stream. - \implements Stream -*/ -class FileStream { -public: - typedef char Ch; //!< Character type. Only support char. - - FileStream(FILE* fp) : fp_(fp), count_(0) { Read(); } - char Peek() const { return current_; } - char Take() { char c = current_; Read(); return c; } - size_t Tell() const { return count_; } - void Put(char c) { fputc(c, fp_); } - - // Not implemented - char* PutBegin() { return 0; } - size_t PutEnd(char*) { return 0; } - -private: - void Read() { - RAPIDJSON_ASSERT(fp_ != 0); - int c = fgetc(fp_); - if (c != EOF) { - current_ = (char)c; - count_++; - } - else - current_ = '\0'; - } - - FILE* fp_; - char current_; - size_t count_; -}; - -} // namespace rapidjson - -#endif // RAPIDJSON_FILESTREAM_H_ diff --git a/lib/rapidjson/internal/pow10.h b/lib/rapidjson/internal/pow10.h deleted file mode 100644 index 0852539e77..0000000000 --- a/lib/rapidjson/internal/pow10.h +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef RAPIDJSON_POW10_ -#define RAPIDJSON_POW10_ - -namespace rapidjson { -namespace internal { - -//! Computes integer powers of 10 in double (10.0^n). -/*! This function uses lookup table for fast and accurate results. - \param n positive/negative exponent. Must <= 308. - \return 10.0^n -*/ -inline double Pow10(int n) { - static const double e[] = { // 1e-308...1e308: 617 * 8 bytes = 4936 bytes - 1e-308,1e-307,1e-306,1e-305,1e-304,1e-303,1e-302,1e-301,1e-300, - 1e-299,1e-298,1e-297,1e-296,1e-295,1e-294,1e-293,1e-292,1e-291,1e-290,1e-289,1e-288,1e-287,1e-286,1e-285,1e-284,1e-283,1e-282,1e-281,1e-280, - 1e-279,1e-278,1e-277,1e-276,1e-275,1e-274,1e-273,1e-272,1e-271,1e-270,1e-269,1e-268,1e-267,1e-266,1e-265,1e-264,1e-263,1e-262,1e-261,1e-260, - 1e-259,1e-258,1e-257,1e-256,1e-255,1e-254,1e-253,1e-252,1e-251,1e-250,1e-249,1e-248,1e-247,1e-246,1e-245,1e-244,1e-243,1e-242,1e-241,1e-240, - 1e-239,1e-238,1e-237,1e-236,1e-235,1e-234,1e-233,1e-232,1e-231,1e-230,1e-229,1e-228,1e-227,1e-226,1e-225,1e-224,1e-223,1e-222,1e-221,1e-220, - 1e-219,1e-218,1e-217,1e-216,1e-215,1e-214,1e-213,1e-212,1e-211,1e-210,1e-209,1e-208,1e-207,1e-206,1e-205,1e-204,1e-203,1e-202,1e-201,1e-200, - 1e-199,1e-198,1e-197,1e-196,1e-195,1e-194,1e-193,1e-192,1e-191,1e-190,1e-189,1e-188,1e-187,1e-186,1e-185,1e-184,1e-183,1e-182,1e-181,1e-180, - 1e-179,1e-178,1e-177,1e-176,1e-175,1e-174,1e-173,1e-172,1e-171,1e-170,1e-169,1e-168,1e-167,1e-166,1e-165,1e-164,1e-163,1e-162,1e-161,1e-160, - 1e-159,1e-158,1e-157,1e-156,1e-155,1e-154,1e-153,1e-152,1e-151,1e-150,1e-149,1e-148,1e-147,1e-146,1e-145,1e-144,1e-143,1e-142,1e-141,1e-140, - 1e-139,1e-138,1e-137,1e-136,1e-135,1e-134,1e-133,1e-132,1e-131,1e-130,1e-129,1e-128,1e-127,1e-126,1e-125,1e-124,1e-123,1e-122,1e-121,1e-120, - 1e-119,1e-118,1e-117,1e-116,1e-115,1e-114,1e-113,1e-112,1e-111,1e-110,1e-109,1e-108,1e-107,1e-106,1e-105,1e-104,1e-103,1e-102,1e-101,1e-100, - 1e-99, 1e-98, 1e-97, 1e-96, 1e-95, 1e-94, 1e-93, 1e-92, 1e-91, 1e-90, 1e-89, 1e-88, 1e-87, 1e-86, 1e-85, 1e-84, 1e-83, 1e-82, 1e-81, 1e-80, - 1e-79, 1e-78, 1e-77, 1e-76, 1e-75, 1e-74, 1e-73, 1e-72, 1e-71, 1e-70, 1e-69, 1e-68, 1e-67, 1e-66, 1e-65, 1e-64, 1e-63, 1e-62, 1e-61, 1e-60, - 1e-59, 1e-58, 1e-57, 1e-56, 1e-55, 1e-54, 1e-53, 1e-52, 1e-51, 1e-50, 1e-49, 1e-48, 1e-47, 1e-46, 1e-45, 1e-44, 1e-43, 1e-42, 1e-41, 1e-40, - 1e-39, 1e-38, 1e-37, 1e-36, 1e-35, 1e-34, 1e-33, 1e-32, 1e-31, 1e-30, 1e-29, 1e-28, 1e-27, 1e-26, 1e-25, 1e-24, 1e-23, 1e-22, 1e-21, 1e-20, - 1e-19, 1e-18, 1e-17, 1e-16, 1e-15, 1e-14, 1e-13, 1e-12, 1e-11, 1e-10, 1e-9, 1e-8, 1e-7, 1e-6, 1e-5, 1e-4, 1e-3, 1e-2, 1e-1, 1e+0, - 1e+1, 1e+2, 1e+3, 1e+4, 1e+5, 1e+6, 1e+7, 1e+8, 1e+9, 1e+10, 1e+11, 1e+12, 1e+13, 1e+14, 1e+15, 1e+16, 1e+17, 1e+18, 1e+19, 1e+20, - 1e+21, 1e+22, 1e+23, 1e+24, 1e+25, 1e+26, 1e+27, 1e+28, 1e+29, 1e+30, 1e+31, 1e+32, 1e+33, 1e+34, 1e+35, 1e+36, 1e+37, 1e+38, 1e+39, 1e+40, - 1e+41, 1e+42, 1e+43, 1e+44, 1e+45, 1e+46, 1e+47, 1e+48, 1e+49, 1e+50, 1e+51, 1e+52, 1e+53, 1e+54, 1e+55, 1e+56, 1e+57, 1e+58, 1e+59, 1e+60, - 1e+61, 1e+62, 1e+63, 1e+64, 1e+65, 1e+66, 1e+67, 1e+68, 1e+69, 1e+70, 1e+71, 1e+72, 1e+73, 1e+74, 1e+75, 1e+76, 1e+77, 1e+78, 1e+79, 1e+80, - 1e+81, 1e+82, 1e+83, 1e+84, 1e+85, 1e+86, 1e+87, 1e+88, 1e+89, 1e+90, 1e+91, 1e+92, 1e+93, 1e+94, 1e+95, 1e+96, 1e+97, 1e+98, 1e+99, 1e+100, - 1e+101,1e+102,1e+103,1e+104,1e+105,1e+106,1e+107,1e+108,1e+109,1e+110,1e+111,1e+112,1e+113,1e+114,1e+115,1e+116,1e+117,1e+118,1e+119,1e+120, - 1e+121,1e+122,1e+123,1e+124,1e+125,1e+126,1e+127,1e+128,1e+129,1e+130,1e+131,1e+132,1e+133,1e+134,1e+135,1e+136,1e+137,1e+138,1e+139,1e+140, - 1e+141,1e+142,1e+143,1e+144,1e+145,1e+146,1e+147,1e+148,1e+149,1e+150,1e+151,1e+152,1e+153,1e+154,1e+155,1e+156,1e+157,1e+158,1e+159,1e+160, - 1e+161,1e+162,1e+163,1e+164,1e+165,1e+166,1e+167,1e+168,1e+169,1e+170,1e+171,1e+172,1e+173,1e+174,1e+175,1e+176,1e+177,1e+178,1e+179,1e+180, - 1e+181,1e+182,1e+183,1e+184,1e+185,1e+186,1e+187,1e+188,1e+189,1e+190,1e+191,1e+192,1e+193,1e+194,1e+195,1e+196,1e+197,1e+198,1e+199,1e+200, - 1e+201,1e+202,1e+203,1e+204,1e+205,1e+206,1e+207,1e+208,1e+209,1e+210,1e+211,1e+212,1e+213,1e+214,1e+215,1e+216,1e+217,1e+218,1e+219,1e+220, - 1e+221,1e+222,1e+223,1e+224,1e+225,1e+226,1e+227,1e+228,1e+229,1e+230,1e+231,1e+232,1e+233,1e+234,1e+235,1e+236,1e+237,1e+238,1e+239,1e+240, - 1e+241,1e+242,1e+243,1e+244,1e+245,1e+246,1e+247,1e+248,1e+249,1e+250,1e+251,1e+252,1e+253,1e+254,1e+255,1e+256,1e+257,1e+258,1e+259,1e+260, - 1e+261,1e+262,1e+263,1e+264,1e+265,1e+266,1e+267,1e+268,1e+269,1e+270,1e+271,1e+272,1e+273,1e+274,1e+275,1e+276,1e+277,1e+278,1e+279,1e+280, - 1e+281,1e+282,1e+283,1e+284,1e+285,1e+286,1e+287,1e+288,1e+289,1e+290,1e+291,1e+292,1e+293,1e+294,1e+295,1e+296,1e+297,1e+298,1e+299,1e+300, - 1e+301,1e+302,1e+303,1e+304,1e+305,1e+306,1e+307,1e+308 - }; - RAPIDJSON_ASSERT(n <= 308); - return n < -308 ? 0.0 : e[n + 308]; -} - -} // namespace internal -} // namespace rapidjson - -#endif // RAPIDJSON_POW10_ diff --git a/lib/rapidjson/internal/stack.h b/lib/rapidjson/internal/stack.h deleted file mode 100644 index 3138b961f3..0000000000 --- a/lib/rapidjson/internal/stack.h +++ /dev/null @@ -1,82 +0,0 @@ -#ifndef RAPIDJSON_INTERNAL_STACK_H_ -#define RAPIDJSON_INTERNAL_STACK_H_ - -namespace rapidjson { -namespace internal { - -/////////////////////////////////////////////////////////////////////////////// -// Stack - -//! A type-unsafe stack for storing different types of data. -/*! \tparam Allocator Allocator for allocating stack memory. -*/ -template -class Stack { -public: - Stack(Allocator* allocator, size_t stack_capacity) : allocator_(allocator), own_allocator_(0), stack_(0), stack_top_(0), stack_end_(0), stack_capacity_(stack_capacity) { - RAPIDJSON_ASSERT(stack_capacity_ > 0); - if (!allocator_) - own_allocator_ = allocator_ = new Allocator(); - stack_top_ = stack_ = (char*)allocator_->Malloc(stack_capacity_); - stack_end_ = stack_ + stack_capacity_; - } - - ~Stack() { - Allocator::Free(stack_); - delete own_allocator_; // Only delete if it is owned by the stack - } - - void Clear() { /*stack_top_ = 0;*/ stack_top_ = stack_; } - - template - T* Push(size_t count = 1) { - // Expand the stack if needed - if (stack_top_ + sizeof(T) * count >= stack_end_) { - size_t new_capacity = stack_capacity_ * 2; - size_t size = GetSize(); - size_t new_size = GetSize() + sizeof(T) * count; - if (new_capacity < new_size) - new_capacity = new_size; - stack_ = (char*)allocator_->Realloc(stack_, stack_capacity_, new_capacity); - stack_capacity_ = new_capacity; - stack_top_ = stack_ + size; - stack_end_ = stack_ + stack_capacity_; - } - T* ret = (T*)stack_top_; - stack_top_ += sizeof(T) * count; - return ret; - } - - template - T* Pop(size_t count) { - RAPIDJSON_ASSERT(GetSize() >= count * sizeof(T)); - stack_top_ -= count * sizeof(T); - return (T*)stack_top_; - } - - template - T* Top() { - RAPIDJSON_ASSERT(GetSize() >= sizeof(T)); - return (T*)(stack_top_ - sizeof(T)); - } - - template - T* Bottom() { return (T*)stack_; } - - Allocator& GetAllocator() { return *allocator_; } - size_t GetSize() const { return stack_top_ - stack_; } - size_t GetCapacity() const { return stack_capacity_; } - -private: - Allocator* allocator_; - Allocator* own_allocator_; - char *stack_; - char *stack_top_; - char *stack_end_; - size_t stack_capacity_; -}; - -} // namespace internal -} // namespace rapidjson - -#endif // RAPIDJSON_STACK_H_ diff --git a/lib/rapidjson/internal/strfunc.h b/lib/rapidjson/internal/strfunc.h deleted file mode 100644 index 47b8ac0757..0000000000 --- a/lib/rapidjson/internal/strfunc.h +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef RAPIDJSON_INTERNAL_STRFUNC_H_ -#define RAPIDJSON_INTERNAL_STRFUNC_H_ - -namespace rapidjson { -namespace internal { - -//! Custom strlen() which works on different character types. -/*! \tparam Ch Character type (e.g. char, wchar_t, short) - \param s Null-terminated input string. - \return Number of characters in the string. - \note This has the same semantics as strlen(), the return value is not number of Unicode codepoints. -*/ -template -inline SizeType StrLen(const Ch* s) { - const Ch* p = s; - while (*p != '\0') - ++p; - return SizeType(p - s); -} - -} // namespace internal -} // namespace rapidjson - -#endif // RAPIDJSON_INTERNAL_STRFUNC_H_ diff --git a/lib/rapidjson/prettywriter.h b/lib/rapidjson/prettywriter.h deleted file mode 100644 index 662b3929da..0000000000 --- a/lib/rapidjson/prettywriter.h +++ /dev/null @@ -1,156 +0,0 @@ -#ifndef RAPIDJSON_PRETTYWRITER_H_ -#define RAPIDJSON_PRETTYWRITER_H_ - -#include "writer.h" - -namespace rapidjson { - -//! Writer with indentation and spacing. -/*! - \tparam Stream Type of ouptut stream. - \tparam Encoding Encoding of both source strings and output. - \tparam Allocator Type of allocator for allocating memory of stack. -*/ -template, typename Allocator = MemoryPoolAllocator<> > -class PrettyWriter : public Writer { -public: - typedef Writer Base; - typedef typename Base::Ch Ch; - - //! Constructor - /*! \param stream Output stream. - \param allocator User supplied allocator. If it is null, it will create a private one. - \param levelDepth Initial capacity of - */ - PrettyWriter(Stream& stream, Allocator* allocator = 0, size_t levelDepth = Base::kDefaultLevelDepth) : - Base(stream, allocator, levelDepth), indentChar_(' '), indentCharCount_(4) {} - - //! Set custom indentation. - /*! \param indentChar Character for indentation. Must be whitespace character (' ', '\t', '\n', '\r'). - \param indentCharCount Number of indent characters for each indentation level. - \note The default indentation is 4 spaces. - */ - PrettyWriter& SetIndent(Ch indentChar, unsigned indentCharCount) { - RAPIDJSON_ASSERT(indentChar == ' ' || indentChar == '\t' || indentChar == '\n' || indentChar == '\r'); - indentChar_ = indentChar; - indentCharCount_ = indentCharCount; - return *this; - } - - //@name Implementation of Handler. - //@{ - - PrettyWriter& Null() { PrettyPrefix(kNullType); Base::WriteNull(); return *this; } - PrettyWriter& Bool(bool b) { PrettyPrefix(b ? kTrueType : kFalseType); Base::WriteBool(b); return *this; } - PrettyWriter& Int(int i) { PrettyPrefix(kNumberType); Base::WriteInt(i); return *this; } - PrettyWriter& Uint(unsigned u) { PrettyPrefix(kNumberType); Base::WriteUint(u); return *this; } - PrettyWriter& Int64(int64_t i64) { PrettyPrefix(kNumberType); Base::WriteInt64(i64); return *this; } - PrettyWriter& Uint64(uint64_t u64) { PrettyPrefix(kNumberType); Base::WriteUint64(u64); return *this; } - PrettyWriter& Double(double d) { PrettyPrefix(kNumberType); Base::WriteDouble(d); return *this; } - - PrettyWriter& String(const Ch* str, SizeType length, bool copy = false) { - (void)copy; - PrettyPrefix(kStringType); - Base::WriteString(str, length); - return *this; - } - - PrettyWriter& StartObject() { - PrettyPrefix(kObjectType); - new (Base::level_stack_.template Push()) typename Base::Level(false); - Base::WriteStartObject(); - return *this; - } - - PrettyWriter& EndObject(SizeType memberCount = 0) { - (void)memberCount; - RAPIDJSON_ASSERT(Base::level_stack_.GetSize() >= sizeof(typename Base::Level)); - RAPIDJSON_ASSERT(!Base::level_stack_.template Top()->inArray); - bool empty = Base::level_stack_.template Pop(1)->valueCount == 0; - - if (!empty) { - Base::stream_.Put('\n'); - WriteIndent(); - } - Base::WriteEndObject(); - return *this; - } - - PrettyWriter& StartArray() { - PrettyPrefix(kArrayType); - new (Base::level_stack_.template Push()) typename Base::Level(true); - Base::WriteStartArray(); - return *this; - } - - PrettyWriter& EndArray(SizeType memberCount = 0) { - (void)memberCount; - RAPIDJSON_ASSERT(Base::level_stack_.GetSize() >= sizeof(typename Base::Level)); - RAPIDJSON_ASSERT(Base::level_stack_.template Top()->inArray); - bool empty = Base::level_stack_.template Pop(1)->valueCount == 0; - - if (!empty) { - Base::stream_.Put('\n'); - WriteIndent(); - } - Base::WriteEndArray(); - return *this; - } - - //@} - - //! Simpler but slower overload. - PrettyWriter& String(const Ch* str) { return String(str, internal::StrLen(str)); } - -protected: - void PrettyPrefix(Type type) { - (void)type; - if (Base::level_stack_.GetSize() != 0) { // this value is not at root - typename Base::Level* level = Base::level_stack_.template Top(); - - if (level->inArray) { - if (level->valueCount > 0) { - Base::stream_.Put(','); // add comma if it is not the first element in array - Base::stream_.Put('\n'); - } - else - Base::stream_.Put('\n'); - WriteIndent(); - } - else { // in object - if (level->valueCount > 0) { - if (level->valueCount % 2 == 0) { - Base::stream_.Put(','); - Base::stream_.Put('\n'); - } - else { - Base::stream_.Put(':'); - Base::stream_.Put(' '); - } - } - else - Base::stream_.Put('\n'); - - if (level->valueCount % 2 == 0) - WriteIndent(); - } - if (!level->inArray && level->valueCount % 2 == 0) - RAPIDJSON_ASSERT(type == kStringType); // if it's in object, then even number should be a name - level->valueCount++; - } - else - RAPIDJSON_ASSERT(type == kObjectType || type == kArrayType); - } - - void WriteIndent() { - size_t count = (Base::level_stack_.GetSize() / sizeof(typename Base::Level)) * indentCharCount_; - PutN(Base::stream_, indentChar_, count); - } - - Ch indentChar_; - unsigned indentCharCount_; -}; - -} // namespace rapidjson - -#endif // RAPIDJSON_RAPIDJSON_H_ diff --git a/lib/rapidjson/rapidjson.h b/lib/rapidjson/rapidjson.h deleted file mode 100644 index 357eab453d..0000000000 --- a/lib/rapidjson/rapidjson.h +++ /dev/null @@ -1,525 +0,0 @@ -#ifndef RAPIDJSON_RAPIDJSON_H_ -#define RAPIDJSON_RAPIDJSON_H_ - -// Copyright (c) 2011-2012 Milo Yip (miloyip@gmail.com) -// Version 0.11 - -#include // malloc(), realloc(), free() -#include // memcpy() - -/////////////////////////////////////////////////////////////////////////////// -// RAPIDJSON_NO_INT64DEFINE - -// Here defines int64_t and uint64_t types in global namespace. -// If user have their own definition, can define RAPIDJSON_NO_INT64DEFINE to disable this. -#ifndef RAPIDJSON_NO_INT64DEFINE -#ifdef _MSC_VER -typedef __int64 int64_t; -typedef unsigned __int64 uint64_t; -#else -#include -#endif -#endif // RAPIDJSON_NO_INT64TYPEDEF - -/////////////////////////////////////////////////////////////////////////////// -// RAPIDJSON_ENDIAN -#define RAPIDJSON_LITTLEENDIAN 0 //!< Little endian machine -#define RAPIDJSON_BIGENDIAN 1 //!< Big endian machine - -//! Endianness of the machine. -/*! GCC provided macro for detecting endianness of the target machine. But other - compilers may not have this. User can define RAPIDJSON_ENDIAN to either - RAPIDJSON_LITTLEENDIAN or RAPIDJSON_BIGENDIAN. -*/ -#ifndef RAPIDJSON_ENDIAN -#ifdef __BYTE_ORDER__ -#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ -#define RAPIDJSON_ENDIAN RAPIDJSON_LITTLEENDIAN -#else -#define RAPIDJSON_ENDIAN RAPIDJSON_BIGENDIAN -#endif // __BYTE_ORDER__ -#else -#define RAPIDJSON_ENDIAN RAPIDJSON_LITTLEENDIAN // Assumes little endian otherwise. -#endif -#endif // RAPIDJSON_ENDIAN - -/////////////////////////////////////////////////////////////////////////////// -// RAPIDJSON_SSE2/RAPIDJSON_SSE42/RAPIDJSON_SIMD - -// Enable SSE2 optimization. -//#define RAPIDJSON_SSE2 - -// Enable SSE4.2 optimization. -//#define RAPIDJSON_SSE42 - -#if defined(RAPIDJSON_SSE2) || defined(RAPIDJSON_SSE42) -#define RAPIDJSON_SIMD -#endif - -/////////////////////////////////////////////////////////////////////////////// -// RAPIDJSON_NO_SIZETYPEDEFINE - -#ifndef RAPIDJSON_NO_SIZETYPEDEFINE -namespace rapidjson { -//! Use 32-bit array/string indices even for 64-bit platform, instead of using size_t. -/*! User may override the SizeType by defining RAPIDJSON_NO_SIZETYPEDEFINE. -*/ -typedef unsigned SizeType; -} // namespace rapidjson -#endif - -/////////////////////////////////////////////////////////////////////////////// -// RAPIDJSON_ASSERT - -//! Assertion. -/*! By default, rapidjson uses C assert() for assertion. - User can override it by defining RAPIDJSON_ASSERT(x) macro. -*/ -#ifndef RAPIDJSON_ASSERT -#include -#define RAPIDJSON_ASSERT(x) assert(x) -#endif // RAPIDJSON_ASSERT - -/////////////////////////////////////////////////////////////////////////////// -// Helpers - -#define RAPIDJSON_MULTILINEMACRO_BEGIN do { -#define RAPIDJSON_MULTILINEMACRO_END \ -} while((void)0, 0) - -namespace rapidjson { - -/////////////////////////////////////////////////////////////////////////////// -// Allocator - -/*! \class rapidjson::Allocator - \brief Concept for allocating, resizing and freeing memory block. - - Note that Malloc() and Realloc() are non-static but Free() is static. - - So if an allocator need to support Free(), it needs to put its pointer in - the header of memory block. - -\code -concept Allocator { - static const bool kNeedFree; //!< Whether this allocator needs to call Free(). - - // Allocate a memory block. - // \param size of the memory block in bytes. - // \returns pointer to the memory block. - void* Malloc(size_t size); - - // Resize a memory block. - // \param originalPtr The pointer to current memory block. Null pointer is permitted. - // \param originalSize The current size in bytes. (Design issue: since some allocator may not book-keep this, explicitly pass to it can save memory.) - // \param newSize the new size in bytes. - void* Realloc(void* originalPtr, size_t originalSize, size_t newSize); - - // Free a memory block. - // \param pointer to the memory block. Null pointer is permitted. - static void Free(void *ptr); -}; -\endcode -*/ - -/////////////////////////////////////////////////////////////////////////////// -// CrtAllocator - -//! C-runtime library allocator. -/*! This class is just wrapper for standard C library memory routines. - \implements Allocator -*/ -class CrtAllocator { -public: - static const bool kNeedFree = true; - void* Malloc(size_t size) { return malloc(size); } - void* Realloc(void* originalPtr, size_t originalSize, size_t newSize) { (void)originalSize; return realloc(originalPtr, newSize); } - static void Free(void *ptr) { free(ptr); } -}; - -/////////////////////////////////////////////////////////////////////////////// -// MemoryPoolAllocator - -//! Default memory allocator used by the parser and DOM. -/*! This allocator allocate memory blocks from pre-allocated memory chunks. - - It does not free memory blocks. And Realloc() only allocate new memory. - - The memory chunks are allocated by BaseAllocator, which is CrtAllocator by default. - - User may also supply a buffer as the first chunk. - - If the user-buffer is full then additional chunks are allocated by BaseAllocator. - - The user-buffer is not deallocated by this allocator. - - \tparam BaseAllocator the allocator type for allocating memory chunks. Default is CrtAllocator. - \implements Allocator -*/ -template -class MemoryPoolAllocator { -public: - static const bool kNeedFree = false; //!< Tell users that no need to call Free() with this allocator. (concept Allocator) - - //! Constructor with chunkSize. - /*! \param chunkSize The size of memory chunk. The default is kDefaultChunkSize. - \param baseAllocator The allocator for allocating memory chunks. - */ - MemoryPoolAllocator(size_t chunkSize = kDefaultChunkCapacity, BaseAllocator* baseAllocator = 0) : - chunkHead_(0), chunk_capacity_(chunkSize), userBuffer_(0), baseAllocator_(baseAllocator), ownBaseAllocator_(0) - { - if (!baseAllocator_) - ownBaseAllocator_ = baseAllocator_ = new BaseAllocator(); - AddChunk(chunk_capacity_); - } - - //! Constructor with user-supplied buffer. - /*! The user buffer will be used firstly. When it is full, memory pool allocates new chunk with chunk size. - - The user buffer will not be deallocated when this allocator is destructed. - - \param buffer User supplied buffer. - \param size Size of the buffer in bytes. It must at least larger than sizeof(ChunkHeader). - \param chunkSize The size of memory chunk. The default is kDefaultChunkSize. - \param baseAllocator The allocator for allocating memory chunks. - */ - MemoryPoolAllocator(char *buffer, size_t size, size_t chunkSize = kDefaultChunkCapacity, BaseAllocator* baseAllocator = 0) : - chunkHead_(0), chunk_capacity_(chunkSize), userBuffer_(buffer), baseAllocator_(baseAllocator), ownBaseAllocator_(0) - { - RAPIDJSON_ASSERT(buffer != 0); - RAPIDJSON_ASSERT(size > sizeof(ChunkHeader)); - chunkHead_ = (ChunkHeader*)buffer; - chunkHead_->capacity = size - sizeof(ChunkHeader); - chunkHead_->size = 0; - chunkHead_->next = 0; - } - - //! Destructor. - /*! This deallocates all memory chunks, excluding the user-supplied buffer. - */ - ~MemoryPoolAllocator() { - Clear(); - delete ownBaseAllocator_; - } - - //! Deallocates all memory chunks, excluding the user-supplied buffer. - void Clear() { - while(chunkHead_ != 0 && chunkHead_ != (ChunkHeader *)userBuffer_) { - ChunkHeader* next = chunkHead_->next; - baseAllocator_->Free(chunkHead_); - chunkHead_ = next; - } - } - - //! Computes the total capacity of allocated memory chunks. - /*! \return total capacity in bytes. - */ - size_t Capacity() { - size_t capacity = 0; - for (ChunkHeader* c = chunkHead_; c != 0; c = c->next) - capacity += c->capacity; - return capacity; - } - - //! Computes the memory blocks allocated. - /*! \return total used bytes. - */ - size_t Size() { - size_t size = 0; - for (ChunkHeader* c = chunkHead_; c != 0; c = c->next) - size += c->size; - return size; - } - - //! Allocates a memory block. (concept Allocator) - void* Malloc(size_t size) { - size = (size + 3) & ~3; // Force aligning size to 4 - - if (chunkHead_->size + size > chunkHead_->capacity) - AddChunk(chunk_capacity_ > size ? chunk_capacity_ : size); - - char *buffer = (char *)(chunkHead_ + 1) + chunkHead_->size; - RAPIDJSON_ASSERT(((uintptr_t)buffer & 3) == 0); // returned buffer is aligned to 4 - chunkHead_->size += size; - - return buffer; - } - - //! Resizes a memory block (concept Allocator) - void* Realloc(void* originalPtr, size_t originalSize, size_t newSize) { - if (originalPtr == 0) - return Malloc(newSize); - - // Do not shrink if new size is smaller than original - if (originalSize >= newSize) - return originalPtr; - - // Simply expand it if it is the last allocation and there is sufficient space - if (originalPtr == (char *)(chunkHead_ + 1) + chunkHead_->size - originalSize) { - size_t increment = newSize - originalSize; - increment = (increment + 3) & ~3; // Force aligning size to 4 - if (chunkHead_->size + increment <= chunkHead_->capacity) { - chunkHead_->size += increment; - RAPIDJSON_ASSERT(((uintptr_t)originalPtr & 3) == 0); // returned buffer is aligned to 4 - return originalPtr; - } - } - - // Realloc process: allocate and copy memory, do not free original buffer. - void* newBuffer = Malloc(newSize); - RAPIDJSON_ASSERT(newBuffer != 0); // Do not handle out-of-memory explicitly. - return memcpy(newBuffer, originalPtr, originalSize); - } - - //! Frees a memory block (concept Allocator) - static void Free(void *) {} // Do nothing - -private: - //! Creates a new chunk. - /*! \param capacity Capacity of the chunk in bytes. - */ - void AddChunk(size_t capacity) { - ChunkHeader* chunk = (ChunkHeader*)baseAllocator_->Malloc(sizeof(ChunkHeader) + capacity); - chunk->capacity = capacity; - chunk->size = 0; - chunk->next = chunkHead_; - chunkHead_ = chunk; - } - - static const int kDefaultChunkCapacity = 64 * 1024; //!< Default chunk capacity. - - //! Chunk header for perpending to each chunk. - /*! Chunks are stored as a singly linked list. - */ - struct ChunkHeader { - size_t capacity; //!< Capacity of the chunk in bytes (excluding the header itself). - size_t size; //!< Current size of allocated memory in bytes. - ChunkHeader *next; //!< Next chunk in the linked list. - }; - - ChunkHeader *chunkHead_; //!< Head of the chunk linked-list. Only the head chunk serves allocation. - size_t chunk_capacity_; //!< The minimum capacity of chunk when they are allocated. - char *userBuffer_; //!< User supplied buffer. - BaseAllocator* baseAllocator_; //!< base allocator for allocating memory chunks. - BaseAllocator* ownBaseAllocator_; //!< base allocator created by this object. -}; - -/////////////////////////////////////////////////////////////////////////////// -// Encoding - -/*! \class rapidjson::Encoding - \brief Concept for encoding of Unicode characters. - -\code -concept Encoding { - typename Ch; //! Type of character. - - //! \brief Encode a Unicode codepoint to a buffer. - //! \param buffer pointer to destination buffer to store the result. It should have sufficient size of encoding one character. - //! \param codepoint An unicode codepoint, ranging from 0x0 to 0x10FFFF inclusively. - //! \returns the pointer to the next character after the encoded data. - static Ch* Encode(Ch *buffer, unsigned codepoint); -}; -\endcode -*/ - -/////////////////////////////////////////////////////////////////////////////// -// UTF8 - -//! UTF-8 encoding. -/*! http://en.wikipedia.org/wiki/UTF-8 - \tparam CharType Type for storing 8-bit UTF-8 data. Default is char. - \implements Encoding -*/ -template -struct UTF8 { - typedef CharType Ch; - - static Ch* Encode(Ch *buffer, unsigned codepoint) { - if (codepoint <= 0x7F) - *buffer++ = codepoint & 0xFF; - else if (codepoint <= 0x7FF) { - *buffer++ = 0xC0 | ((codepoint >> 6) & 0xFF); - *buffer++ = 0x80 | ((codepoint & 0x3F)); - } - else if (codepoint <= 0xFFFF) { - *buffer++ = 0xE0 | ((codepoint >> 12) & 0xFF); - *buffer++ = 0x80 | ((codepoint >> 6) & 0x3F); - *buffer++ = 0x80 | (codepoint & 0x3F); - } - else { - RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); - *buffer++ = 0xF0 | ((codepoint >> 18) & 0xFF); - *buffer++ = 0x80 | ((codepoint >> 12) & 0x3F); - *buffer++ = 0x80 | ((codepoint >> 6) & 0x3F); - *buffer++ = 0x80 | (codepoint & 0x3F); - } - return buffer; - } -}; - -/////////////////////////////////////////////////////////////////////////////// -// UTF16 - -//! UTF-16 encoding. -/*! http://en.wikipedia.org/wiki/UTF-16 - \tparam CharType Type for storing 16-bit UTF-16 data. Default is wchar_t. C++11 may use char16_t instead. - \implements Encoding -*/ -template -struct UTF16 { - typedef CharType Ch; - - static Ch* Encode(Ch* buffer, unsigned codepoint) { - if (codepoint <= 0xFFFF) { - RAPIDJSON_ASSERT(codepoint < 0xD800 || codepoint > 0xDFFF); // Code point itself cannot be surrogate pair - *buffer++ = static_cast(codepoint); - } - else { - RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); - unsigned v = codepoint - 0x10000; - *buffer++ = static_cast((v >> 10) + 0xD800); - *buffer++ = (v & 0x3FF) + 0xDC00; - } - return buffer; - } -}; - -/////////////////////////////////////////////////////////////////////////////// -// UTF32 - -//! UTF-32 encoding. -/*! http://en.wikipedia.org/wiki/UTF-32 - \tparam Ch Type for storing 32-bit UTF-32 data. Default is unsigned. C++11 may use char32_t instead. - \implements Encoding -*/ -template -struct UTF32 { - typedef CharType Ch; - - static Ch *Encode(Ch* buffer, unsigned codepoint) { - RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); - *buffer++ = codepoint; - return buffer; - } -}; - -/////////////////////////////////////////////////////////////////////////////// -// Stream - -/*! \class rapidjson::Stream - \brief Concept for reading and writing characters. - - For read-only stream, no need to implement PutBegin(), Put() and PutEnd(). - - For write-only stream, only need to implement Put(). - -\code -concept Stream { - typename Ch; //!< Character type of the stream. - - //! Read the current character from stream without moving the read cursor. - Ch Peek() const; - - //! Read the current character from stream and moving the read cursor to next character. - Ch Take(); - - //! Get the current read cursor. - //! \return Number of characters read from start. - size_t Tell(); - - //! Begin writing operation at the current read pointer. - //! \return The begin writer pointer. - Ch* PutBegin(); - - //! Write a character. - void Put(Ch c); - - //! End the writing operation. - //! \param begin The begin write pointer returned by PutBegin(). - //! \return Number of characters written. - size_t PutEnd(Ch* begin); -} -\endcode -*/ - -//! Put N copies of a character to a stream. -template -inline void PutN(Stream& stream, Ch c, size_t n) { - for (size_t i = 0; i < n; i++) - stream.Put(c); -} - -/////////////////////////////////////////////////////////////////////////////// -// StringStream - -//! Read-only string stream. -/*! \implements Stream -*/ -template -struct GenericStringStream { - typedef typename Encoding::Ch Ch; - - GenericStringStream(const Ch *src) : src_(src), head_(src) {} - - Ch Peek() const { return *src_; } - Ch Take() { return *src_++; } - size_t Tell() const { return src_ - head_; } - - Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } - void Put(Ch) { RAPIDJSON_ASSERT(false); } - size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } - - const Ch* src_; //!< Current read position. - const Ch* head_; //!< Original head of the string. -}; - -typedef GenericStringStream > StringStream; - -/////////////////////////////////////////////////////////////////////////////// -// InsituStringStream - -//! A read-write string stream. -/*! This string stream is particularly designed for in-situ parsing. - \implements Stream -*/ -template -struct GenericInsituStringStream { - typedef typename Encoding::Ch Ch; - - GenericInsituStringStream(Ch *src) : src_(src), dst_(0), head_(src) {} - - // Read - Ch Peek() { return *src_; } - Ch Take() { return *src_++; } - size_t Tell() { return src_ - head_; } - - // Write - Ch* PutBegin() { return dst_ = src_; } - void Put(Ch c) { RAPIDJSON_ASSERT(dst_ != 0); *dst_++ = c; } - size_t PutEnd(Ch* begin) { return dst_ - begin; } - - Ch* src_; - Ch* dst_; - Ch* head_; -}; - -typedef GenericInsituStringStream > InsituStringStream; - -/////////////////////////////////////////////////////////////////////////////// -// Type - -//! Type of JSON value -enum Type { - kNullType = 0, //!< null - kFalseType = 1, //!< false - kTrueType = 2, //!< true - kObjectType = 3, //!< object - kArrayType = 4, //!< array - kStringType = 5, //!< string - kNumberType = 6, //!< number -}; - -} // namespace rapidjson - -#endif // RAPIDJSON_RAPIDJSON_H_ diff --git a/lib/rapidjson/reader.h b/lib/rapidjson/reader.h deleted file mode 100644 index cb3e826a54..0000000000 --- a/lib/rapidjson/reader.h +++ /dev/null @@ -1,683 +0,0 @@ -#ifndef RAPIDJSON_READER_H_ -#define RAPIDJSON_READER_H_ - -// Copyright (c) 2011 Milo Yip (miloyip@gmail.com) -// Version 0.1 - -#include "rapidjson.h" -#include "internal/pow10.h" -#include "internal/stack.h" -#include - -#ifdef RAPIDJSON_SSE42 -#include -#elif defined(RAPIDJSON_SSE2) -#include -#endif - -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable : 4127) // conditional expression is constant -#endif - -#ifndef RAPIDJSON_PARSE_ERROR -#define RAPIDJSON_PARSE_ERROR(msg, offset) \ - RAPIDJSON_MULTILINEMACRO_BEGIN \ - parseError_ = msg; \ - errorOffset_ = offset; \ - longjmp(jmpbuf_, 1); \ - RAPIDJSON_MULTILINEMACRO_END -#endif - -namespace rapidjson { - -/////////////////////////////////////////////////////////////////////////////// -// ParseFlag - -//! Combination of parseFlags -enum ParseFlag { - kParseDefaultFlags = 0, //!< Default parse flags. Non-destructive parsing. Text strings are decoded into allocated buffer. - kParseInsituFlag = 1 //!< In-situ(destructive) parsing. -}; - -/////////////////////////////////////////////////////////////////////////////// -// Handler - -/*! \class rapidjson::Handler - \brief Concept for receiving events from GenericReader upon parsing. -\code -concept Handler { - typename Ch; - - void Null(); - void Bool(bool b); - void Int(int i); - void Uint(unsigned i); - void Int64(int64_t i); - void Uint64(uint64_t i); - void Double(double d); - void String(const Ch* str, SizeType length, bool copy); - void StartObject(); - void EndObject(SizeType memberCount); - void StartArray(); - void EndArray(SizeType elementCount); -}; -\endcode -*/ -/////////////////////////////////////////////////////////////////////////////// -// BaseReaderHandler - -//! Default implementation of Handler. -/*! This can be used as base class of any reader handler. - \implements Handler -*/ -template > -struct BaseReaderHandler { - typedef typename Encoding::Ch Ch; - - void Default() {} - void Null() { Default(); } - void Bool(bool) { Default(); } - void Int(int) { Default(); } - void Uint(unsigned) { Default(); } - void Int64(int64_t) { Default(); } - void Uint64(uint64_t) { Default(); } - void Double(double) { Default(); } - void String(const Ch*, SizeType, bool) { Default(); } - void StartObject() { Default(); } - void EndObject(SizeType) { Default(); } - void StartArray() { Default(); } - void EndArray(SizeType) { Default(); } -}; - -/////////////////////////////////////////////////////////////////////////////// -// SkipWhitespace - -//! Skip the JSON white spaces in a stream. -/*! \param stream A input stream for skipping white spaces. - \note This function has SSE2/SSE4.2 specialization. -*/ -template -void SkipWhitespace(Stream& stream) { - Stream s = stream; // Use a local copy for optimization - while (s.Peek() == ' ' || s.Peek() == '\n' || s.Peek() == '\r' || s.Peek() == '\t') - s.Take(); - stream = s; -} - -#ifdef RAPIDJSON_SSE42 -//! Skip whitespace with SSE 4.2 pcmpistrm instruction, testing 16 8-byte characters at once. -inline const char *SkipWhitespace_SIMD(const char* p) { - static const char whitespace[16] = " \n\r\t"; - __m128i w = _mm_loadu_si128((const __m128i *)&whitespace[0]); - - for (;;) { - __m128i s = _mm_loadu_si128((const __m128i *)p); - unsigned r = _mm_cvtsi128_si32(_mm_cmpistrm(w, s, _SIDD_UBYTE_OPS | _SIDD_CMP_EQUAL_ANY | _SIDD_BIT_MASK | _SIDD_NEGATIVE_POLARITY)); - if (r == 0) // all 16 characters are whitespace - p += 16; - else { // some of characters may be non-whitespace -#ifdef _MSC_VER // Find the index of first non-whitespace - unsigned long offset; - if (_BitScanForward(&offset, r)) - return p + offset; -#else - if (r != 0) - return p + __builtin_ffs(r) - 1; -#endif - } - } -} - -#elif defined(RAPIDJSON_SSE2) - -//! Skip whitespace with SSE2 instructions, testing 16 8-byte characters at once. -inline const char *SkipWhitespace_SIMD(const char* p) { - static const char whitespaces[4][17] = { - " ", - "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n", - "\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r", - "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"}; - - __m128i w0 = _mm_loadu_si128((const __m128i *)&whitespaces[0][0]); - __m128i w1 = _mm_loadu_si128((const __m128i *)&whitespaces[1][0]); - __m128i w2 = _mm_loadu_si128((const __m128i *)&whitespaces[2][0]); - __m128i w3 = _mm_loadu_si128((const __m128i *)&whitespaces[3][0]); - - for (;;) { - __m128i s = _mm_loadu_si128((const __m128i *)p); - __m128i x = _mm_cmpeq_epi8(s, w0); - x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w1)); - x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w2)); - x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w3)); - unsigned short r = ~_mm_movemask_epi8(x); - if (r == 0) // all 16 characters are whitespace - p += 16; - else { // some of characters may be non-whitespace -#ifdef _MSC_VER // Find the index of first non-whitespace - unsigned long offset; - if (_BitScanForward(&offset, r)) - return p + offset; -#else - if (r != 0) - return p + __builtin_ffs(r) - 1; -#endif - } - } -} - -#endif // RAPIDJSON_SSE2 - -#ifdef RAPIDJSON_SIMD -//! Template function specialization for InsituStringStream -template<> inline void SkipWhitespace(InsituStringStream& stream) { - stream.src_ = const_cast(SkipWhitespace_SIMD(stream.src_)); -} - -//! Template function specialization for StringStream -template<> inline void SkipWhitespace(StringStream& stream) { - stream.src_ = SkipWhitespace_SIMD(stream.src_); -} -#endif // RAPIDJSON_SIMD - -/////////////////////////////////////////////////////////////////////////////// -// GenericReader - -//! SAX-style JSON parser. Use Reader for UTF8 encoding and default allocator. -/*! GenericReader parses JSON text from a stream, and send events synchronously to an - object implementing Handler concept. - - It needs to allocate a stack for storing a single decoded string during - non-destructive parsing. - - For in-situ parsing, the decoded string is directly written to the source - text string, no temporary buffer is required. - - A GenericReader object can be reused for parsing multiple JSON text. - - \tparam Encoding Encoding of both the stream and the parse output. - \tparam Allocator Allocator type for stack. -*/ -template > -class GenericReader { -public: - typedef typename Encoding::Ch Ch; - - //! Constructor. - /*! \param allocator Optional allocator for allocating stack memory. (Only use for non-destructive parsing) - \param stackCapacity stack capacity in bytes for storing a single decoded string. (Only use for non-destructive parsing) - */ - GenericReader(Allocator* allocator = 0, size_t stackCapacity = kDefaultStackCapacity) : stack_(allocator, stackCapacity), parseError_(0), errorOffset_(0) {} - - //! Parse JSON text. - /*! \tparam parseFlags Combination of ParseFlag. - \tparam Stream Type of input stream. - \tparam Handler Type of handler which must implement Handler concept. - \param stream Input stream to be parsed. - \param handler The handler to receive events. - \return Whether the parsing is successful. - */ - template - bool Parse(Stream& stream, Handler& handler) { - parseError_ = 0; - errorOffset_ = 0; - -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable : 4611) // interaction between '_setjmp' and C++ object destruction is non-portable -#endif - if (setjmp(jmpbuf_)) { -#ifdef _MSC_VER -#pragma warning(pop) -#endif - stack_.Clear(); - return false; - } - - SkipWhitespace(stream); - - if (stream.Peek() == '\0') - RAPIDJSON_PARSE_ERROR("Text only contains white space(s)", stream.Tell()); - else { - switch (stream.Peek()) { - case '{': ParseObject(stream, handler); break; - case '[': ParseArray(stream, handler); break; - default: RAPIDJSON_PARSE_ERROR("Expect either an object or array at root", stream.Tell()); - } - SkipWhitespace(stream); - - if (stream.Peek() != '\0') - RAPIDJSON_PARSE_ERROR("Nothing should follow the root object or array.", stream.Tell()); - } - - return true; - } - - bool HasParseError() const { return parseError_ != 0; } - const char* GetParseError() const { return parseError_; } - size_t GetErrorOffset() const { return errorOffset_; } - -private: - // Parse object: { string : value, ... } - template - void ParseObject(Stream& stream, Handler& handler) { - RAPIDJSON_ASSERT(stream.Peek() == '{'); - stream.Take(); // Skip '{' - handler.StartObject(); - SkipWhitespace(stream); - - if (stream.Peek() == '}') { - stream.Take(); - handler.EndObject(0); // empty object - return; - } - - for (SizeType memberCount = 0;;) { - if (stream.Peek() != '"') { - RAPIDJSON_PARSE_ERROR("Name of an object member must be a string", stream.Tell()); - break; - } - - ParseString(stream, handler); - SkipWhitespace(stream); - - if (stream.Take() != ':') { - RAPIDJSON_PARSE_ERROR("There must be a colon after the name of object member", stream.Tell()); - break; - } - SkipWhitespace(stream); - - ParseValue(stream, handler); - SkipWhitespace(stream); - - ++memberCount; - - switch(stream.Take()) { - case ',': SkipWhitespace(stream); break; - case '}': handler.EndObject(memberCount); return; - default: RAPIDJSON_PARSE_ERROR("Must be a comma or '}' after an object member", stream.Tell()); - } - } - } - - // Parse array: [ value, ... ] - template - void ParseArray(Stream& stream, Handler& handler) { - RAPIDJSON_ASSERT(stream.Peek() == '['); - stream.Take(); // Skip '[' - handler.StartArray(); - SkipWhitespace(stream); - - if (stream.Peek() == ']') { - stream.Take(); - handler.EndArray(0); // empty array - return; - } - - for (SizeType elementCount = 0;;) { - ParseValue(stream, handler); - ++elementCount; - SkipWhitespace(stream); - - switch (stream.Take()) { - case ',': SkipWhitespace(stream); break; - case ']': handler.EndArray(elementCount); return; - default: RAPIDJSON_PARSE_ERROR("Must be a comma or ']' after an array element.", stream.Tell()); - } - } - } - - template - void ParseNull(Stream& stream, Handler& handler) { - RAPIDJSON_ASSERT(stream.Peek() == 'n'); - stream.Take(); - - if (stream.Take() == 'u' && stream.Take() == 'l' && stream.Take() == 'l') - handler.Null(); - else - RAPIDJSON_PARSE_ERROR("Invalid value", stream.Tell() - 1); - } - - template - void ParseTrue(Stream& stream, Handler& handler) { - RAPIDJSON_ASSERT(stream.Peek() == 't'); - stream.Take(); - - if (stream.Take() == 'r' && stream.Take() == 'u' && stream.Take() == 'e') - handler.Bool(true); - else - RAPIDJSON_PARSE_ERROR("Invalid value", stream.Tell()); - } - - template - void ParseFalse(Stream& stream, Handler& handler) { - RAPIDJSON_ASSERT(stream.Peek() == 'f'); - stream.Take(); - - if (stream.Take() == 'a' && stream.Take() == 'l' && stream.Take() == 's' && stream.Take() == 'e') - handler.Bool(false); - else - RAPIDJSON_PARSE_ERROR("Invalid value", stream.Tell() - 1); - } - - // Helper function to parse four hexidecimal digits in \uXXXX in ParseString(). - template - unsigned ParseHex4(Stream& stream) { - Stream s = stream; // Use a local copy for optimization - unsigned codepoint = 0; - for (int i = 0; i < 4; i++) { - Ch c = s.Take(); - codepoint <<= 4; - codepoint += c; - if (c >= '0' && c <= '9') - codepoint -= '0'; - else if (c >= 'A' && c <= 'F') - codepoint -= 'A' - 10; - else if (c >= 'a' && c <= 'f') - codepoint -= 'a' - 10; - else - RAPIDJSON_PARSE_ERROR("Incorrect hex digit after \\u escape", s.Tell() - 1); - } - stream = s; // Restore stream - return codepoint; - } - - // Parse string, handling the prefix and suffix double quotes and escaping. - template - void ParseString(Stream& stream, Handler& handler) { -#define Z16 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 - static const Ch escape[256] = { - Z16, Z16, 0, 0,'\"', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'/', - Z16, Z16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'\\', 0, 0, 0, - 0, 0,'\b', 0, 0, 0,'\f', 0, 0, 0, 0, 0, 0, 0,'\n', 0, - 0, 0,'\r', 0,'\t', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16 - }; -#undef Z16 - - Stream s = stream; // Use a local copy for optimization - RAPIDJSON_ASSERT(s.Peek() == '\"'); - s.Take(); // Skip '\"' - Ch *head; - SizeType len; - if (parseFlags & kParseInsituFlag) - head = s.PutBegin(); - else - len = 0; - -#define RAPIDJSON_PUT(x) \ - do { \ - if (parseFlags & kParseInsituFlag) \ - s.Put(x); \ - else { \ - *stack_.template Push() = x; \ - ++len; \ - } \ - } while(false) - - for (;;) { - Ch c = s.Take(); - if (c == '\\') { // Escape - Ch e = s.Take(); - if ((sizeof(Ch) == 1 || e < 256) && escape[(unsigned char)e]) - RAPIDJSON_PUT(escape[(unsigned char)e]); - else if (e == 'u') { // Unicode - unsigned codepoint = ParseHex4(s); - if (codepoint >= 0xD800 && codepoint <= 0xDBFF) { // Handle UTF-16 surrogate pair - if (s.Take() != '\\' || s.Take() != 'u') { - RAPIDJSON_PARSE_ERROR("Missing the second \\u in surrogate pair", s.Tell() - 2); - return; - } - unsigned codepoint2 = ParseHex4(s); - if (codepoint2 < 0xDC00 || codepoint2 > 0xDFFF) { - RAPIDJSON_PARSE_ERROR("The second \\u in surrogate pair is invalid", s.Tell() - 2); - return; - } - codepoint = (((codepoint - 0xD800) << 10) | (codepoint2 - 0xDC00)) + 0x10000; - } - - Ch buffer[4]; - SizeType count = SizeType(Encoding::Encode(buffer, codepoint) - &buffer[0]); - - if (parseFlags & kParseInsituFlag) - for (SizeType i = 0; i < count; i++) - s.Put(buffer[i]); - else { - memcpy(stack_.template Push(count), buffer, count * sizeof(Ch)); - len += count; - } - } - else { - RAPIDJSON_PARSE_ERROR("Unknown escape character", stream.Tell() - 1); - return; - } - } - else if (c == '"') { // Closing double quote - if (parseFlags & kParseInsituFlag) { - size_t length = s.PutEnd(head); - RAPIDJSON_ASSERT(length <= 0xFFFFFFFF); - RAPIDJSON_PUT('\0'); // null-terminate the string - handler.String(head, SizeType(length), false); - } - else { - RAPIDJSON_PUT('\0'); - handler.String(stack_.template Pop(len), len - 1, true); - } - stream = s; // restore stream - return; - } - else if (c == '\0') { - RAPIDJSON_PARSE_ERROR("lacks ending quotation before the end of string", stream.Tell() - 1); - return; - } - else if ((unsigned)c < 0x20) { // RFC 4627: unescaped = %x20-21 / %x23-5B / %x5D-10FFFF - RAPIDJSON_PARSE_ERROR("Incorrect unescaped character in string", stream.Tell() - 1); - return; - } - else - RAPIDJSON_PUT(c); // Normal character, just copy - } -#undef RAPIDJSON_PUT - } - - template - void ParseNumber(Stream& stream, Handler& handler) { - Stream s = stream; // Local copy for optimization - // Parse minus - bool minus = false; - if (s.Peek() == '-') { - minus = true; - s.Take(); - } - - // Parse int: zero / ( digit1-9 *DIGIT ) - unsigned i; - bool try64bit = false; - if (s.Peek() == '0') { - i = 0; - s.Take(); - } - else if (s.Peek() >= '1' && s.Peek() <= '9') { - i = s.Take() - '0'; - - if (minus) - while (s.Peek() >= '0' && s.Peek() <= '9') { - if (i >= 214748364) { // 2^31 = 2147483648 - if (i != 214748364 || s.Peek() > '8') { - try64bit = true; - break; - } - } - i = i * 10 + (s.Take() - '0'); - } - else - while (s.Peek() >= '0' && s.Peek() <= '9') { - if (i >= 429496729) { // 2^32 - 1 = 4294967295 - if (i != 429496729 || s.Peek() > '5') { - try64bit = true; - break; - } - } - i = i * 10 + (s.Take() - '0'); - } - } - else { - RAPIDJSON_PARSE_ERROR("Expect a value here.", stream.Tell()); - return; - } - - // Parse 64bit int - uint64_t i64 = 0; - bool useDouble = false; - if (try64bit) { - i64 = i; - if (minus) - while (s.Peek() >= '0' && s.Peek() <= '9') { - if (i64 >= 922337203685477580uLL) // 2^63 = 9223372036854775808 - if (i64 != 922337203685477580uLL || s.Peek() > '8') { - useDouble = true; - break; - } - i64 = i64 * 10 + (s.Take() - '0'); - } - else - while (s.Peek() >= '0' && s.Peek() <= '9') { - if (i64 >= 1844674407370955161uLL) // 2^64 - 1 = 18446744073709551615 - if (i64 != 1844674407370955161uLL || s.Peek() > '5') { - useDouble = true; - break; - } - i64 = i64 * 10 + (s.Take() - '0'); - } - } - - // Force double for big integer - double d = 0.0; - if (useDouble) { - d = (double)i64; - while (s.Peek() >= '0' && s.Peek() <= '9') { - if (d >= 1E307) { - RAPIDJSON_PARSE_ERROR("Number too big to store in double", stream.Tell()); - return; - } - d = d * 10 + (s.Take() - '0'); - } - } - - // Parse frac = decimal-point 1*DIGIT - int expFrac = 0; - if (s.Peek() == '.') { - if (!useDouble) { - d = try64bit ? (double)i64 : (double)i; - useDouble = true; - } - s.Take(); - - if (s.Peek() >= '0' && s.Peek() <= '9') { - d = d * 10 + (s.Take() - '0'); - --expFrac; - } - else { - RAPIDJSON_PARSE_ERROR("At least one digit in fraction part", stream.Tell()); - return; - } - - while (s.Peek() >= '0' && s.Peek() <= '9') { - if (expFrac > -16) { - d = d * 10 + (s.Peek() - '0'); - --expFrac; - } - s.Take(); - } - } - - // Parse exp = e [ minus / plus ] 1*DIGIT - int exp = 0; - if (s.Peek() == 'e' || s.Peek() == 'E') { - if (!useDouble) { - d = try64bit ? (double)i64 : (double)i; - useDouble = true; - } - s.Take(); - - bool expMinus = false; - if (s.Peek() == '+') - s.Take(); - else if (s.Peek() == '-') { - s.Take(); - expMinus = true; - } - - if (s.Peek() >= '0' && s.Peek() <= '9') { - exp = s.Take() - '0'; - while (s.Peek() >= '0' && s.Peek() <= '9') { - exp = exp * 10 + (s.Take() - '0'); - if (exp > 308) { - RAPIDJSON_PARSE_ERROR("Number too big to store in double", stream.Tell()); - return; - } - } - } - else { - RAPIDJSON_PARSE_ERROR("At least one digit in exponent", s.Tell()); - return; - } - - if (expMinus) - exp = -exp; - } - - // Finish parsing, call event according to the type of number. - if (useDouble) { - d *= internal::Pow10(exp + expFrac); - handler.Double(minus ? -d : d); - } - else { - if (try64bit) { - if (minus) - handler.Int64(-(int64_t)i64); - else - handler.Uint64(i64); - } - else { - if (minus) - handler.Int(-(int)i); - else - handler.Uint(i); - } - } - - stream = s; // restore stream - } - - // Parse any JSON value - template - void ParseValue(Stream& stream, Handler& handler) { - switch (stream.Peek()) { - case 'n': ParseNull (stream, handler); break; - case 't': ParseTrue (stream, handler); break; - case 'f': ParseFalse (stream, handler); break; - case '"': ParseString(stream, handler); break; - case '{': ParseObject(stream, handler); break; - case '[': ParseArray (stream, handler); break; - default : ParseNumber(stream, handler); - } - } - - static const size_t kDefaultStackCapacity = 256; //!< Default stack capacity in bytes for storing a single decoded string. - internal::Stack stack_; //!< A stack for storing decoded string temporarily during non-destructive parsing. - jmp_buf jmpbuf_; //!< setjmp buffer for fast exit from nested parsing function calls. - const char* parseError_; - size_t errorOffset_; -}; // class GenericReader - -//! Reader with UTF8 encoding and default allocator. -typedef GenericReader > Reader; - -} // namespace rapidjson - -#ifdef _MSC_VER -#pragma warning(pop) -#endif - -#endif // RAPIDJSON_READER_H_ diff --git a/lib/rapidjson/stringbuffer.h b/lib/rapidjson/stringbuffer.h deleted file mode 100644 index 34cff58ef9..0000000000 --- a/lib/rapidjson/stringbuffer.h +++ /dev/null @@ -1,49 +0,0 @@ -#ifndef RAPIDJSON_STRINGBUFFER_H_ -#define RAPIDJSON_STRINGBUFFER_H_ - -#include "rapidjson.h" -#include "internal/stack.h" - -namespace rapidjson { - -//! Represents an in-memory output stream. -/*! - \tparam Encoding Encoding of the stream. - \tparam Allocator type for allocating memory buffer. - \implements Stream -*/ -template -struct GenericStringBuffer { - typedef typename Encoding::Ch Ch; - - GenericStringBuffer(Allocator* allocator = 0, size_t capacity = kDefaultCapacity) : stack_(allocator, capacity) {} - - void Put(Ch c) { *stack_.template Push() = c; } - - void Clear() { stack_.Clear(); } - - const char* GetString() const { - // Push and pop a null terminator. This is safe. - *stack_.template Push() = '\0'; - stack_.template Pop(1); - - return stack_.template Bottom(); - } - - size_t Size() const { return stack_.GetSize(); } - - static const size_t kDefaultCapacity = 256; - mutable internal::Stack stack_; -}; - -typedef GenericStringBuffer > StringBuffer; - -//! Implement specialized version of PutN() with memset() for better performance. -template<> -inline void PutN(GenericStringBuffer >& stream, char c, size_t n) { - memset(stream.stack_.Push(n), c, n * sizeof(c)); -} - -} // namespace rapidjson - -#endif // RAPIDJSON_STRINGBUFFER_H_ diff --git a/lib/rapidjson/writer.h b/lib/rapidjson/writer.h deleted file mode 100644 index 9d674b7bf3..0000000000 --- a/lib/rapidjson/writer.h +++ /dev/null @@ -1,241 +0,0 @@ -#ifndef RAPIDJSON_WRITER_H_ -#define RAPIDJSON_WRITER_H_ - -#include "rapidjson.h" -#include "internal/stack.h" -#include "internal/strfunc.h" -#include // snprintf() or _sprintf_s() -#include // placement new - -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable : 4127) // conditional expression is constant -#endif - -namespace rapidjson { - -//! JSON writer -/*! Writer implements the concept Handler. - It generates JSON text by events to an output stream. - - User may programmatically calls the functions of a writer to generate JSON text. - - On the other side, a writer can also be passed to objects that generates events, - - for example Reader::Parse() and Document::Accept(). - - \tparam Stream Type of ouptut stream. - \tparam Encoding Encoding of both source strings and output. - \implements Handler -*/ -template, typename Allocator = MemoryPoolAllocator<> > -class Writer { -public: - typedef typename Encoding::Ch Ch; - - Writer(Stream& stream, Allocator* allocator = 0, size_t levelDepth = kDefaultLevelDepth) : - stream_(stream), level_stack_(allocator, levelDepth * sizeof(Level)) {} - - //@name Implementation of Handler - //@{ - Writer& Null() { Prefix(kNullType); WriteNull(); return *this; } - Writer& Bool(bool b) { Prefix(b ? kTrueType : kFalseType); WriteBool(b); return *this; } - Writer& Int(int i) { Prefix(kNumberType); WriteInt(i); return *this; } - Writer& Uint(unsigned u) { Prefix(kNumberType); WriteUint(u); return *this; } - Writer& Int64(int64_t i64) { Prefix(kNumberType); WriteInt64(i64); return *this; } - Writer& Uint64(uint64_t u64) { Prefix(kNumberType); WriteUint64(u64); return *this; } - Writer& Double(double d) { Prefix(kNumberType); WriteDouble(d); return *this; } - - Writer& String(const Ch* str, SizeType length, bool copy = false) { - (void)copy; - Prefix(kStringType); - WriteString(str, length); - return *this; - } - - Writer& StartObject() { - Prefix(kObjectType); - new (level_stack_.template Push()) Level(false); - WriteStartObject(); - return *this; - } - - Writer& EndObject(SizeType memberCount = 0) { - (void)memberCount; - RAPIDJSON_ASSERT(level_stack_.GetSize() >= sizeof(Level)); - RAPIDJSON_ASSERT(!level_stack_.template Top()->inArray); - level_stack_.template Pop(1); - WriteEndObject(); - return *this; - } - - Writer& StartArray() { - Prefix(kArrayType); - new (level_stack_.template Push()) Level(true); - WriteStartArray(); - return *this; - } - - Writer& EndArray(SizeType elementCount = 0) { - (void)elementCount; - RAPIDJSON_ASSERT(level_stack_.GetSize() >= sizeof(Level)); - RAPIDJSON_ASSERT(level_stack_.template Top()->inArray); - level_stack_.template Pop(1); - WriteEndArray(); - return *this; - } - //@} - - //! Simpler but slower overload. - Writer& String(const Ch* str) { return String(str, internal::StrLen(str)); } - -protected: - //! Information for each nested level - struct Level { - Level(bool inArray_) : inArray(inArray_), valueCount(0) {} - bool inArray; //!< true if in array, otherwise in object - size_t valueCount; //!< number of values in this level - }; - - static const size_t kDefaultLevelDepth = 32; - - void WriteNull() { - stream_.Put('n'); stream_.Put('u'); stream_.Put('l'); stream_.Put('l'); - } - - void WriteBool(bool b) { - if (b) { - stream_.Put('t'); stream_.Put('r'); stream_.Put('u'); stream_.Put('e'); - } - else { - stream_.Put('f'); stream_.Put('a'); stream_.Put('l'); stream_.Put('s'); stream_.Put('e'); - } - } - - void WriteInt(int i) { - if (i < 0) { - stream_.Put('-'); - i = -i; - } - WriteUint((unsigned)i); - } - - void WriteUint(unsigned u) { - char buffer[10]; - char *p = buffer; - do { - *p++ = (u % 10) + '0'; - u /= 10; - } while (u > 0); - - do { - --p; - stream_.Put(*p); - } while (p != buffer); - } - - void WriteInt64(int64_t i64) { - if (i64 < 0) { - stream_.Put('-'); - i64 = -i64; - } - WriteUint64((uint64_t)i64); - } - - void WriteUint64(uint64_t u64) { - char buffer[20]; - char *p = buffer; - do { - *p++ = char(u64 % 10) + '0'; - u64 /= 10; - } while (u64 > 0); - - do { - --p; - stream_.Put(*p); - } while (p != buffer); - } - - //! \todo Optimization with custom double-to-string converter. - void WriteDouble(double d) { - char buffer[100]; -#if _MSC_VER - int ret = sprintf_s(buffer, sizeof(buffer), "%g", d); -#else - int ret = snprintf(buffer, sizeof(buffer), "%g", d); -#endif - RAPIDJSON_ASSERT(ret >= 1); - for (int i = 0; i < ret; i++) - stream_.Put(buffer[i]); - } - - void WriteString(const Ch* str, SizeType length) { - static const char hexDigits[] = "0123456789ABCDEF"; - static const char escape[256] = { -#define Z16 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 - //0 1 2 3 4 5 6 7 8 9 A B C D E F - 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'b', 't', 'n', 'u', 'f', 'r', 'u', 'u', // 00 - 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', // 10 - 0, 0, '"', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20 - Z16, Z16, // 30~4F - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'\\', 0, 0, 0, // 50 - Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16 // 60~FF -#undef Z16 - }; - - stream_.Put('\"'); - for (const Ch* p = str; p != str + length; ++p) { - if ((sizeof(Ch) == 1 || *p < 256) && escape[(unsigned char)*p]) { - stream_.Put('\\'); - stream_.Put(escape[(unsigned char)*p]); - if (escape[(unsigned char)*p] == 'u') { - stream_.Put('0'); - stream_.Put('0'); - stream_.Put(hexDigits[(*p) >> 4]); - stream_.Put(hexDigits[(*p) & 0xF]); - } - } - else - stream_.Put(*p); - } - stream_.Put('\"'); - } - - void WriteStartObject() { stream_.Put('{'); } - void WriteEndObject() { stream_.Put('}'); } - void WriteStartArray() { stream_.Put('['); } - void WriteEndArray() { stream_.Put(']'); } - - void Prefix(Type type) { - (void)type; - if (level_stack_.GetSize() != 0) { // this value is not at root - Level* level = level_stack_.template Top(); - if (level->valueCount > 0) { - if (level->inArray) - stream_.Put(','); // add comma if it is not the first element in array - else // in object - stream_.Put((level->valueCount % 2 == 0) ? ',' : ':'); - } - if (!level->inArray && level->valueCount % 2 == 0) - RAPIDJSON_ASSERT(type == kStringType); // if it's in object, then even number should be a name - level->valueCount++; - } - else - RAPIDJSON_ASSERT(type == kObjectType || type == kArrayType); - } - - Stream& stream_; - internal::Stack level_stack_; - -private: - // Prohibit assignment for VC C4512 warning - Writer& operator=(const Writer& w); -}; - -} // namespace rapidjson - -#ifdef _MSC_VER -#pragma warning(pop) -#endif - -#endif // RAPIDJSON_RAPIDJSON_H_ diff --git a/vendor/README.md b/vendor/README.md index eea214463b..9fe8521485 100644 --- a/vendor/README.md +++ b/vendor/README.md @@ -4,4 +4,5 @@ Vendor Libraries This directory contains third-party vendor libraries. The following libraries are used by cfacter: -* re2 - Google's regular expression library \ No newline at end of file +* [re2](https://code.google.com/p/re2/) - Google's regular expression library +* [rapidjson](https://code.google.com/p/rapidjson) - A fast JSON parsing/generating library. \ No newline at end of file diff --git a/vendor/rapidjson-0.11.tgz b/vendor/rapidjson-0.11.tgz new file mode 100644 index 0000000000000000000000000000000000000000..2897a49069d6ae1353a064faa2fc602bb591c524 GIT binary patch literal 803361 zcmY(KV{|1!8?8@l+nmY7o}Ad2aAMoZ#I|kQwr$%sC$@d=eD{7=f2(${THULv_paxy zB8h+nY}4{90w6BBEL}E6n@HCkR?^uk>QwlqJ*!94ex4SWwrtiNbmviy8?;cK#VWP% z+F_$e_>&u;>j7rt=S!}yQ%RV6%)KYJYG^P8exqorP>qPpCyQX8)m1!Ie{L@|S#a2w zeC^uU;d(y0Wj{VX9&>YtdtcqnJWg4Ch0QE4SFf*X?X0bCt$KBLS1)rkySO;_D4ge* z^QD<$Q&~eQrn!gVM49O9VzmUA4D2C&-Y6u#zSi7*tblHJy5D@d-_8avD@-)I4R{cI8n6H5z(Dhb(r4twZE7s1-&drW~^_b5ue$e%&t%6g@LY?a8`()H6>8(D> zS2y(UP1m-DCiYFom3KznkiDT*50|F4ZvOE8uxEQiFQ3ntj>pTT$F0xDrOSXU{_5rC zuhxe4mZp}vrj8cXjjb&oz|DuL58_pgS2wfufX`qG;g`+jAD<#;zMaJRV}ucqBdB64 z8{}r~`#uZWI_{_iaciD)OTB@9%*22B%*lm=d_Qweky}wdaXxldb#&)JBd#nDK1VSy zIr@*3fSbMS8g7b%2HtwM zkQPDoryvTRf4!po)L#fsKEd3(d~YOuK4xu&pqSg$haHr+gSFk9fG-=yQ_#Wx6@nCJ zL0QK$DGwld;*77D&u*MS9riX!Vn;;Zk*lXC`Uet`o~o&;W6cf`P|h{O|MRR5!$l7k z$nWm@@Ycul{S_TVasH@J7j{PFgh@WdH=fZVnmiyaQ;3)RQH zvHrA@A{M^n8_nif~q7}~TZWZYv<<4&^pQ}LL z($cq#29>TYm8s?H%;B*$y8>jApR&&}Gbs2aHA}>uuIm@22gAP#AiA8>;l&fX@gILa zIV;#)lJw;?bq(7U+&ABGAJ@)t4(Xs|&?_sNHmQ`<`FLwtmNncjt2bzez!3`clte8I zU?VE>m*qH@Gtqe#pYivRlzS<0^9|Q|A1_YpgVAT^uiIpIS+VPoI4#QGzvLp5TrKHa zH+3Cw#Xy~}^H;OW(wyZwD|l#^w4~1>(V=YILN3jSJAELESN4IPxucG#tBrcx*3J8J zu++>d47Weq=Bibds+P(kJFw`jeF?Gxp7;pB?vpNJ_D<+!-Fh1TpwK;gp1Kclh zxdM2(rmPJfTfVDI9QToAfMR%Dm|hVPd$V4A;z0tko14Y&px1fO{l5hY`k(9Pe@*R{ zpE>`T+m8P!GN6LvqHm&{+lIyM6NeRJUBe-KWFio0l-AonJ=pN0IarBUPv?+SHY zLRd{0mGONf?Du)!@kXejI-8I3PwWK?Oy_jYYTIGx`OQ={X$)GM`I zKQM%`?=`wId|lIM&!TPCh0N|{kwtX$hen3dBkyA;?)>{0H#TuO6TcT1Z_`+yC7 zV5tUXJ2#3F(tx{z55Y@;oFhb%D(oV44A?hSt40;JhyqM;5FN5Wk2jS?9$ z{zx+Sz5*gE14VE8K57QY(*k4RL+68y*-@R-icLS2!>I7Brs{L1e71B9!tzpO} zF^#}bYm14ciSXT3eQ}sz=?ZY#(BQ#tEBV3eWG>k=E|I&Ay>9&TQ#P^=Hghp-(8X3r zIp1cDJhhdbaJUNSLA%kWttYt2d)jTEkjqVV9nEdX8yNyCt(jF*O#_vV=)(*4yeWM|K*c71Wd& z)4)|3*+(VK2xp*M9No`=1h$7wBWUna|Axb1Qf+tP6S=}ByeBW_`&(RK>(M;Q6->)k z%!he?I^AkkFkMD69^Pe0*RK6j*1r}-h9YhZ4Aij+L~{KpwI3nnyDXj^axvUg7`Yx zCb3SVBrMAo)N=Fkffb`}W3iVjl`LD-We_wm#`#ct#~s|AZc4JA@Vk zdjEs=_4xS=dQ=2wjk;+?xkdvisP3wH-2r1TI7k2RhNYU$BE%~CQ_new7N#AdHx<0q z&d|`bqdZ*&=XC$Tmgm4q?uhFP)uy_}rzQAv?wa7-avIqw@tpVw^4`bGz1oZ%N?RM% z7qC%|vjpFwL4#v6$A2UlWXsAwg8SS8y1uBaVSMfRXN-3{IG=?tF{>aqLSO#= zub!r+rbZC|a6%r4f*&7Xn4!ggf%{wn>X_kZ{%4}{|J!;AE__ZjcfTHdTueP&e*Lp7 z>P?Wv^FEoepT~j^szwXP{}Ja1ee}4hssGc0Ua#(rze>d2uxEWf3i-46CUHpwK`qWH zUp_JSbf+MkPt5%uJ&^Dg;JFx-ah{Qp@gI|(0e|zk{IdGcGu+e#K};pT@)IAc&x07> zs0V!pzhIQWy&yq74c(KV+aELgpEb_UV>5@q&X+;v0Y6wDVu9j<^No z)q;NhOBma$Sp%S{W2x7FFEw@M?leG_=uqoJRWn9R+9b4+tg(zX4ADW_$#TM(9N$uVfkCh z1Ue@V^fnBmf)_42uYwcG%pAzJtKHh=q4{ZmxeLs}vhg`dWFJY#jDw%CROjv5D>eB# zUXJ3c30lS}vtw)H=3zdw zA>S6}s{RB3rewbpYc_C~g9D&25!CNsvfqV1-S(fwt2v?p6M*+|W!{VdJKR_6T;zct z!5C%Td9PJ0h3JT_4-hY&Cr_swcllyFK>Pj&#@PxRL;=EqMQLt3eyro=_~Y-)Z6PX) zY=|W<#jR~(+nCFh6igF81Ie$iSxJO{E`5=JB))3DRwFL3f!jeiLx)Wv8wTzHulhGd z_#ptmTS;Tb)_sD_#K~PK^6D^Joy9ZmJLskz?vXu$84+Cqbm8w0oP$$zsc~=Ei(Mwt zqAX|yANj2v!s_Vl)E&J=c9HFWY0t=Gj#m)@>+3sR4DfFHbe}==K zLn1f2fH&fw)a$)4?Vu=tewNAyF3nD|v5=F?U+c3_b>|&F|Ax#Ao>QiOb%)Mr0#ejX(k}yHEE?p{@ zylgLhJuyk_$^u7~lgD*!J0{Ps0J81j`dpuA%`mC&5UUdGQo%cL-c z`l-?PxZjb)3C4Q%Lk;*9YJ*Sd#$se{n_x*ECS>F5#2HCt*SFt!8Bb!qTR(K?e-ZJI z^nDR&aOocCTD}!nkWi5MzFD8iKMj!D!<3_G3$;|7A*R%TKhT)Mym;e^5_9F9kNQ9X7QML7BaBeP zjdqf4AA5@I#`o6#Ju4LggSmj<6C~fq>X|wKLzAx=V4*9oms_j4Ow8A{ zaXz0|bK2de?{bUomz z50Nggd6$TYsm-X`l zAA=BtAtbwROQ^96ls^P#eGfd)+LozFL5?K72}4MLahOg&S1aLI=|fmj6A{nvyFVU2 ze1%N)7B=1%p(GNrYHo*84{l8bclUs}9DM}OBNt|{#{9mg`%zrTjJY}8+y(E$o3H`4 z&<%kR{=gQgXZ(Q359W)&dv-%xIL5AL$2_8b_2`f1GjSib;E17!Nlut?lu|g~0ykXj zYN#qygmdL47|iIW?C4X#&xAU+T_gxu5#MJfPvZM02noLjuo~B)I7!^}eH}etu^H#P z5U}a#r6ZooHL((^b?*xmV@mbFld*I9rnkQ6S;4ju4_^ zZs(8u8;!430f)0c!IwE99Y!PRhCT@_2&)FQxv~2N+ z-QY@o_FTK13{hT%Jflbh51BQ_`>t2+)*#*s5tax*>iJ)TMa{wN4hdEH)b8H{>a(Wp zgh28ggkI`I7?+neUN?i8(@G{g##h_n;6dDXyIp7@73cfw!O^acgG7@<8}cnCBBDHa zar|q$A$fsN;kJbfPi-zIxikeky#&9Z9fSNEbdcBxj@Y%`cARTqj$JC@o<4g2XS~N! zN52-KBM@v(m=l9pj`Ch;Ra^|e+M1{ae1XgvTV#{c9J`v6FZaAc zfP0q$`mb#8B^J}Vv}iC^R44NuytKFtef_(k)}!K;EjP-Y4rX@KtxTd(&<&A# zs?Dy8-ug`?!E*X~Z_tO6B#TIUJ0LOp@rP@9$cUZ4G&pO?Jw5vFAHQ;Uom~%}uBx^I z2c*sctN~(|a(<#2gAX}y)TQw+_^nlN@XY6Lp$Y=OC(RRtdnx{`Q%n{IS_Isjz4?=4 zS(yZtV$>fn4^hjfewPqq&~7_>_;^XR!HFnZ{v8uXb8FQj2u`EhvSvvbXdpcxA^$+hXkaV+9_l5PwM;o!anRH8%sW9Phn)FM;Rt}dK8y&T^4SF`x&go zX`@PyE0SHaPG;Rhb`9I;d%yZyt8}RZH9icX!PgKMEb=lb0FeB9wJ1Za96WN7VsHL+ z-6G=!BLFbA!=MPCjRR<%A=ie`!X~YHAHDakf+N^nvjA#+6N;?get{VBHb^HG5W{?W zA=d@N4=;0LV77|8XP3tbM-}zIEC2}1g-b8mx`t2ujTo@1vk z?UnLm@C`e|wRa4}6wDIhkIR^hNKeP6kPG=$aDpumuT7Q+)_Tw?#we)$&g@qwUQWn` z@N#) z<&Wgi-^i{fPk^oo=5`x1i8>UEgoJ=-GXv@TpU3#MESBt!s*5(-;?W{`7Cc%=vER)H z{Dun667|%_meE~J?i7KO&O?d(6)HkN{rM^;_nG$zzY%h1+fqKiHZ~@M1G|Q`YL~qo z*8D8nS<_F-X+ZFs!yXfaEezj2#%Xb9+^Mr|INw8Wo(GwkTAG2H@LH#9!4mNn>0)uW%g z2sR>doEW_4)U8-^*XojCp(QH+sMFykODjDUPKGUBV9acW4tlAbI^WvImDy#UK0e5o z6uO;19qJm3*}!+()u}6kx++s_Jx9MG(3B04V>ct(8s>{^LQu^h><15%f0|$qNUMSY zwr=++71=$ryLwz3FB(84i?DzQ=*n}#FH}wm2n&}d3*r0j3eZDA{Bv?8h2oU0d)@c5 ztLCQ2Hy@f9Z_mOG=*4KXJ<^rFOF9o@0eXBu8lN`J6gV3pOsg@b@MrC8iR)h3HLRxO z^2BTXl)K+F1O100WOjcYGGEz%LY$@E{}Yej{{`?(J&+ISe{ht3NAvwlPeUc>9w^7R zweohaJo&0D{|~~r=SB-8KXlc-E#QJa?n6N$MbppGS%j@Xw`$5E8Hb~zc~HE{w^rxk z1~dF&0~5+2WKGgwv&(Eb?nv-dIYP>HVvMZQHCQTKmo{^Nj_ zF0?VAjdQ6FpTy&}0G?qczhyY_#q!X)-X8A6AVzd3}nQh!)QsHGyM)@XR)&qs7Lql1EN=e{a{Q-|#zBjS9L%yZf zk}+-+49(5#PwMN-^U0A$$4jK>uFoDxPUP4+zqj|vh_P#-GO=7OEH@I*>-fp&hS131 zcXM<{PhOd>t=!JzX}a08&=5Z3^(QqP^o}qRl)Ak`jg81LF5zG+rMlll5so zcpBGAy`+;PhY2q*6-mVPQ5t^MROgQ+bt>7FVma%liRlBi$PEAdV2IOHmX*muBJr(W zqlNDhYpFn$*=s0Q2ye@~gF0>rs z#1apz3STcP)t1Ho%A^{U2Yjxric;0b5gVs+7pn^Mm#M($)cpi+c-LWoy7Mr!47Fq5RjC9Q~kI?U(r#p3pO$jSr!Jts|G5U z=JIQpAvOqL{P5+tqhOuS~IC2a^#72LWRkq8?+IGH|uFZS``eFss;<(lCmGV{e}gc%rucPziTRxoz(w%GLgR-K^i&d zpe~zDQi%hdrjLoFRZKW-L^jNPr8$1!^uNt4BSu^j18{@V@u4ZBQ%asEo@ zP)U@^41j=SWVc|;MS$Vfw-C(C$dlMXZSW<3aeqAcDzlB2n60%VsV*XU5)^P{40Hem|JD zh6P_(V``j{6&1)<_o=H9EgT4<3m^gy8dEt5+5KGgllo*{M?dE+L~is!BkaI~ z#z}DNut98YCfXfnmP0|*Lk2=+f%3*V_6N}JSt1J7weBVyND~+kdLj|IiGn+Rlv<1Y z^o=a0ycJxNeaOSolYYdGrO%dDfHhF+fVz{r2ndhJ3?CBBK}a5zm)3hD9F0UlF+?{i zihY-7bA#yF%S-355B`}&hWf~b(*bMQ;~=I?1S>&Ih7}T?eKW*H=%KobkzlD6OE%Gn zgNa5_Pzd#FR%R$K9j8`KV&^iQ9y2@S7`}P&`s|BHzzJD=jIn{3NI9XFlC0rp#XuPyD};bN|!M@t^kNwy-akbeO3S+yjaS&_;&T z=l{=foIiHfQtv?P)GK+^JzBdHgPdt=YwvS_uP?9Z< znDN1rgt#e6d@JI-&8K+?W4@a`hq5>>PURm6e@vhL9YSB8YI+8HP}HdsELf%9t)iR>Aj2~t6os@%fAZP-4`f7 zC*zi+`@`y^XZhbZ(IxrY7WH+V(|X-y7?})u8NYc>0X6@0NqOA)FaB!71CfBg+YEuW zqK@4_HC2yU-@f?A??t?XAXhPt!`y30YpKrs`g^lW|V zU83P| zny!ggb`~{nhPGcV&x(l$PgVxMxFN2hJA)!IB*&uuG4ysOJ#b-SQaH{M0hsk~qFNfW zxBFV@?wc(}OOgq@zp{l05xAHN?7*V}4i3s2^zJ;4qYfa_e||^Jlo90!`bCU4Y}n(n zD}IL3bCI*?UFq^Xqe^d8zKAL=&S&8LfTHyz6wyk=2*DQ$_(9Z7;u^VkT{JNXsl3^F+Ecn?pdl<~LSDVX0 z7MUhr>o)9D^?z&I%g4o4>|rG<~y7kXASF@IXgRRjRbO1*>VHw z7^>K0^>?CUP8)Zb-SwkWzm6)QeBFdi?Vxl&F>z^=NaU(Ed31=XNmy-yLoeBIQMMg| z9daSUJTa)YG3DA{rqCJAJZoLMQ3ysJ(UHbDG#eh5`8I}hS-in;OHNshH`Wz;dpYYa8_WcVVB#r;CaDfSYEMe(kP*J z{?zA?F*SA{14vO1zI@(0m$>D7P~V=)3vo=R3si>skLSgiSXke5#2}O$iR}U>HHtJN z#erK17FZdC$+8(ldYG~;KhZMfm>S4(nAYtslPr{ZV=welaWXb<{N|lDjzerS$9xj# z;1?IJo-AS%VCy-UOU{Ik&q@-LU&;(oMSX`{y3pt08q1M*l%s%nt_LVRg%oqkI9-Uj z)nFy7FJ<1xE4PR(R-?w-lREVGHdr|~1nqiSj3B9WsjmgzmQxUMKI-o0qYuG`)cZ9B z@4uhkzhE8-LQ#Z%!2Z|?toawr>pC7l?un+CYplmv(Nnec3N^;_N~-VX6t8W?2|&X; zjhC;r^n*&?cV1?fYbFD;@FpW`gtEfrD>^(sv-CNRJB~z_#ZpS5~MkYi?W{_sh1vtP96 zdZt*#mR-a!MH#L6RyG25St|iBg$z3HuywZ+9bef53$> zjx4@?vEitU4FQ4q3Ocy}@vo$Fb*9Zj3huVpx;wd1Ec%q5o{~mkm9- zo+Za@R3Y?RSGhfVl`ZPX+qw>~G%g0-#2GbWv_WF2TqOGes$5ulq*bhgd4a>nirqTX zevTHq|G}rl5ZA-j)MVMU_lj=Pg3eInAnnd^?7p1k)pKQ!%J;N3qT|7iF+q>@zoo7? zbxfhW)Tc!q=7wt@p7p>~St<Q7ZUcZtF!nIM)qHa#0(rmRMFMe};#)Fn)OLzg zUfblF{(4^_FwfSg8Gca9LaW8vKX@InO8<%l{n-_3o7u!2{&m9|BAIB*Hq06_1r8UQ zZpQ)Fv8g@wmI!Z|l%*?i(yUv=DDXqMW^tU(l_W5wZ(Pda8@rm+L-E@XE;#eMUp)-s zPS28MBFT&b9?#Z98sBaLS{B83c ze~_fHY1QCWxI9iVpI|>P<=c`{3d??5sr1b z=_YXm6Ul7ctKY<5^%Ar<*ck{HKSY7aa9+aVJV<&O3PyF5kbqytH$fObHZSvnDQsIB+JCiWUV*8{dP#eb|B@|~odH)Huzo06DMHT)u74{I%&PX5t4X9C z_tc*u^TD2c`LK59(JdOeFWMq)>#vEYeHV7dil->4WGtOO>c(6Qgg$@Lgh1KK02JN? zIeVc8jxiMd=@AY3(>>fHKfO7Z?8u_KaUay zNtZM*B+CB6e;bb{(Yo*t`bTLg4Sv}xU4I458=zu!7thPK!;oZxU1n;B&2cPC<;Iv7 z>ig2SkHXmBipC|?)d%JSpmW zz7^R8I5lpqkVboeVq2wH{p}gQ4jEm2VjAjZjc7zZy)EHZ@yF^8@j;xEoD-_z~m#zYm-GB-IjD=QZKAg=;q`a_IliH9XiLP zW7p*Lj5Wv98eZq;@0tOXE(WzwdiGy&#zihfJ7u!txKo08~b+3H>D2W0+F%Ow^eL z!QgL{Jvge;DoO{7r@fp_4PY@>PWm&m@FA38IrnV~cPmqBknoQgk5mk?B=H?%|D{Z< z6-hWhpYM|WjF5<2@`%}t;>`~)g~WFR`{tmI$ciE`AN6RS;$Jl{`YXX;D53g? zdf@AZ)Z5>)lqe$mQFd!YKOa;RUuvyrBhF$iPNGC`$q?0cS^xB$cY388=y_8))_$b~ zG&un&Zw@^NQMnMDLBqoH(Ijo%z&=+lZ#j~awo&?DI5k7)e{uPW3Dz|2$u90<{f5*r zA-gW%{oiD^Or+OgS;7gAFQAfjbXTcl;N3m%kEReLlL6A~%I@qJO?iYyk%R3qXS&11 z&KaK}^P|Ys)%?bh?;Aea*Kkl zPBJdbVooLPVSaFf&-3CCj922`a`RgQL20(uo3qn#k-Eka)(r#P4}}vRDGZE^6?fw7 zkqqPe>=g2vnAYkA?aQjYo+!-pwGm`Y6w2;GI@+W5fE3wZwoYUEvX#-V<{sZg8K$x^ zuni4vU!XjD zP@qYwX1X+G!>Q+o5Gj!q40bW~qvVjoD~P-XPNQT6*cFR)!MgJN;tPS4Jh8_m@6j#d z#ZE%Zj=rDXJ99zT$;0qqBo0SbN6BjCxnRZmUKR~tn)I^E*gsW;6TZPzw0@}Dxo4Jd zruk)#@P$AjM%l#r02lRUtoT9hCq;jhrZhH%Af?r*rYT(53TqOIYPpWuZt})a#QJ*# z=DrVLP7i+;lozofct52XIdrlt_ncwGU3iQoJcJer{C760^xC17)C#^sQi#N`+>kl< zv6m{fR+rS5#4x=g)M6b1l3D9cBicfpMpnYwbkekDz!0*6nYD-4;;obXd91B0E9YWK zlZ}%bfm_s`MKm^g?nfj$Yh`S2`8-T@4H_b+TXp}Q*k>F_P->m4(lVApgU(qoK68uY@QGuLpL5+0{qDzFkvsA=2{Hp-y2KPma z@gP52q9!Z5>Am32W}G>V?k{U7Q6yGqdImX6HJfiRC^tz^9A_lWv5)c4e_+ub4Uaj` zX-Mvjt9O9})--H=lP#|{o|dZilQj@vhfISXI_g(xLe?h6oQGTk>9J$8WSQmO;?)gv z-NG(FeZ#I~b8aR7l-jmGi%tid^#|e^*0iI=VhAz+plbb)4P@J^n6m9DO>7Wy4BiyS z7|J%6WO!8=SrX7CB{6F-UKXM|n3gNLUdn~-vbS(HB9uiRDKSvZ8xxwLL%NyTfY4g^ z-M1>;K;je`zad{LY4QZ0N+UHVd3JlX$)8$(1-2xYTj87b8#8u-0Nlp(1k5O0AzZbb zOlzn;>vpfou`}&Z%7O{9c(i3n&5UKuIY%BWVO<2C`$KH7OssyAL9LqBUz zr8Bw9?!iYLB>Y1INl-1J#T!ZuiZDqln9QDD@@`Iucdrl0J{By$u19Ob4eMR!GnNw@ z0t3@NbM7`i6W!-m0JoEy-sc7li{~54(|+7ve3A!y(NEZwnunpD>inlu+z7tD&YO${ zruGMhla+?pyr3i<6C76jYAo48eM{(-{D-BdeB#e-;t-O}=MRjZDBfw~?i(q-94`p_ zXRq(DtE{dRMPf%EX%sXIEoL)174rNS*^6;cE+}W0pKf;rlE2zt|6crZh)m!~X1xtA zE%(=yez7_l2}CKT;U`ul&L_a>DvG7jQ*PDQ8;=_3=4L7c)QPTra(2psoYD6tPbP<&;r8R zY<(;mIK-GI48Nq?0{!o0a5SOm@P#GZ;U;2^__QHM07MkCrqmPg~@RQSJ=vdj%YV?nN=w(%min05q8 zr9?9HD7PwOL7;Tn=bECXLsufPE9$Rs8!;GA279*p8Tl(=S}sS@ZjXwDA}-V$(O9Df z!l60PTot9iv6RyR1@P&tJI_YaVIr_Kd-zPY9d?t=I!&@nowf7#HDn0E94CQk%})B zX!*M?Xf%f2OPDbIHCi8xzCL>Rw9$$i8nO#Oyp-waTj^#P<##-JPc7F#@(-%Q*Q zHnHq%vN6mR;s+0<+ZK+wcm_^rQmK2;g05|gkpU#Vo*#iU4a`m7-Uc3&tY0#FnK03E z(p#As;dxc_xVPZ2ydgNpQJAEoGR9q$gPc+9Z{h0FQ7bh;ee!0^_&|vTzsi+F;hD(K zXd|$*Sq%3;@Fh-wtT4^(7uVFV z25o57y}&_8e7&DW!i#SJxy%8rxrkKQ4_5X^+1aCcm0~%3BkUK_rlX*Uk8yj#6j|gCqN=k&u~JnTq?)nP@H}sU??stdSBwUIfC{TV z=6;g-b+u?2<8ru^bc{P5X_c`8=@`-#Y7~E^B7y8;ha5DhgSM#jl9Whru^*&&)156t z>f)>!S7!u?EpmDRZq4?wld(mz*o#pMpO~A^eoattL28h|jCPdCy!u>s(d0l?+scm6 zm)tv!<+34aGd^U5(>PYj_nz}F_+AO_tRChIouJMP78en=P^SE$weIywso3dAGs8c> ze$B+VsMF5J#>4#B-F5mYiqA4T&sWdJ6d|EtwI^PI6x4ioF`VW#wnKGUU{cWln_-W+ zs)dPB2{&>g>nQ^_krSsIgltG(0!yMQx{`nv{+TMptYJ?TK>x76Yi`i5HbO=h(Jrsl zhTiKo8yJ?=>Q&Cx=2HuQJ}{(#Fvetu2Yx4fS%Pn$+;lJNfJTr>mRBlzOHIvNiDmg$ zokl{kP#64li$pUASx7JYG!p)Ox^7a$vyp*^!Nea?yGc#sbkKc^sZq)L%7!(4%;TsI zL?1UNt*LirTDNF%FspSj-qFuu<(>aEnb3Qrq#ykdevjdh={7o51}V!F?OR><+0bBJ zUW*e6nxsnY=eFCbM7#u4MoL+Z;J5n`$GH4DBKu>aWeYbK z6z-%~h4Ys_`3@bMM@2C$u9e5q9xA^0`}kvn2Eq^k&1L3C_^~XPBEy5!;&73$R2mU# z)i0=VMY?=eD3{7gaauulonTg>@h-8IE&Mst#x&zeGqbz`wk*+zG1O>TtC|h#j~aKf z6;jF+85qp6nPpDIFMk|&WzAf&3Y0udg7;EN1~{KpHnAy#H?E{YBC)HWZDQ7#i+f2c z(?!55-Z<2ihJ%QgX5~~k1GdgEQW$#{J+?x5IrdFPqC6AYMyZD2OLsn0)vU!Fbh!g` zD+Kzh>g@g*Qb_N0mpjZx;rdNW`2x&sgbS(B{Lgp=lFLTP32axtU(pu_Q=9e}@ zW`ju|+a78Zn3ZhyoC71d3zq?zCuMnf(jb0(%@(XpkY3foq z4x|YqSQ<;;QcPgH2n|-mNt=yxGP;O@vO0@3Tst$0QDV5`OCzE&%XJ;9U_OB)wQzh& zlp}4`0NmLE%pqlX_@WSLA)7f`Z7f+U?LVmFs%EPn1BJ?(_(Qw;L{n|huV%YIw!{C7 zd#n!aSx_rx3XV8r?;SHgB3GnhrZy#1>4#J!_lVfM>GyB4(2P2@0&08we6ec7UMXd+ zwTnsJB2rKV#mP{T41#I6#%gZrg&T$t(n=yWeCareZ!-l5)4K+nR4k}B9|~Tj^{wpBNpF8w>TON6 z9yYMHRjdu`b>p6R2xGQ%aPb_Dd&S{d=Yq}xQ1)Jgo%v4)(=HebTLd}4fp(=L{5E=o ze{gh8nrCLF1+sq+X?pHI7ocVVe1C$?v^4u{GBxLhcY#5_8Q`*YGS=^cncp~#6Qa4! zzCdphKA6praiFxNGUnSBC!vd=zz-Q$O$(%M3UU{kI1f9Z5dSGo8EG%hJ*t}PQsT9&g8%&{M zzrjY>x4Z-$gs6f}gJrr=mK~_Pr6?1E*og)`PDXG3!iIT_CUH1jk`;bQ;QbpxxZSlu z;a8_^oFJYr5uq$szmfh;+G!@MM@O7jEUnnzXgfxA5;Pu4l3jGiGRV$=YJ)GBPI}gK z3nACY!@h>2n69>WpFR+Zvl3MYa^&L z9JVMFc&0B->r4>oCw8%z39_)W#`Iqu3pgoU6SAYM-w_d*6z3M#BwV!TBB0Su|tsDf)2_M zBEwTusBML(T=*__$PkigPWI^_}g*A+?AK7by`Emju7Mb&u`R(rKoqm+O zkE0idgehGZRP3^z7};;(_P5ho*3eFXb4Zog=K~;NAcuV~>fO%<_tmVJZf^Z60B*x) z65Pm=C4O{^G%-%w8W`(lpxqhWzvN$9yTaqzA+Vs>?){t&S9Q^G861NBd) z+*hCxPUq-sqpsyj$K@OKho+I1VS?MVg4}bkyx*=E9sz!~8mdsISyF$9pr_0zH5o5b zDD6B~u7P*(IP(GK>^hGX-% zpGPWnFs2!QR-4UlOi4xH;!^K(xbo|+G|XtW-20q{)u_>>YHd$w3ZjDal>N`3*!l-%gcWcf(P6c-04^l` za|q@4=esIbM%|AfoH9{0Nveh{=i9nYTfN%!TW4OzW)yl#$TQ{AK z_5CEGiBNJ!@`0Xu@SK96iz|cgwsp@XK_%VB5*3VWSp%HMpc& zT^XlE$nbEz>>6$IKn^+8aju+61Dt5805q9va~qC}WUU5u;Culd1mq>Oh92FBc1G2M zp*x1_wB@;TT7fQJs1kxlKdGVa4(IyDP-VW*Kp|a}7vU;}J{B6Jw%@4(caISJg_O&ZGA0$jHtrLbcte>DHtnNT%xXqVrqaa(^dwkF?{XD0wd z8#`3LjDFTl-Wp-avh#YU(}sMbQzSxc*p3IMHteR<@f(#n%Kz~jaZHp{_yr_D-@b&l zmNU#EPnTr+A)3eSTqk>XL6SULNIm{%g}Nq|e>4(Zvkz(5G>qyz9d+%ZbC+Vb>l5Wm zPpnrDmI-BoNo}csI`H}Pp!q%27}<%7dcl7&y0D`y@8}T)Q-)r@1U5?6PRahP5JEtJmYG<_ zSyXX-M#Z6wOPcEiwfotXaZpN3$jGU)&aQ%-p({~KE{KSIj;-{L4pEljc$4Hok77m| z6}H&r@1rT?%REdDVxS@_YJfsp=I#1x!pB|L8-^Qr<-TY43R&T4OMKO2EP9?kg1{)V zi*rmn#3jNd^o5(AH2HUaP5i#Fbr0Y$iDMU$Iw7=^{m<`giO7Jxs+-MBIj+a*2rLC@ zKE+o6+zQiUkPuw88T}i!n?%BHfciBCnk3)mSY7|BA-H zTZk(`f5g0(@pJDRT(U96PlYjsziP zHwsJ20c}e1)Q=YL@6j07bBnv!8w70+DX}e+y!Q$tR)Q=*?iEXaw&+=UD@{k~t}qYV zeOu|r5mhJCRFopYpcp2i-)QZ_1z~^}(5tCoB^c4I&1;gh4b=F(-Yf1@-)?XZojg?2O!u}q=g86eSk}s&Y3qv$W~LGn8r_m36QS1NDyF=d2WZ8#$jGu%BpalBG<=BI*8vZYiJoc=U6I~A-ods3o1amn+~CGE@#=Et1O(n}3^pAn9Lk-GQEF*|G+%QigEo}RG1a)%}necTl zEjuv&{BmVQ!C#;&+0PE(Ck8?Ff_@QarI03|!I&q4QXXHSb8BcGDBF-Nfg36)H&oe z-(5b7gjUm_0b|uHrm&PD9@l|pY3yr&{PrVQx~$zOSJLq%dv9)WdCYVPjnk^%d*AxJ zBHG1`Jt2`Qfl2~{g0ec8xTp=$NaRRXzU>U@M;c;e6ljiLRh0$n<$1$qZ|7h6>$9Q! z`hfqTN^a-vp^7BZuK9)J69TDp0IdnnE(5F??o^jfL?J7>_~RP;(f2_1hX2FVoxVQF zuc*mkiTCNZ3u!h8TU#Xgc7EVt zr{(rlmam#);J74V8Z9`Hpsa_i;IB;Ku8Brk`=KRCppsPYF_#oa*p1%*pzNNyD{I0n z00$l0w%M`Sv2EM7(;ane+qP}nwrwYSpZ?DI1?ReMYt%M<3!YWW;bws$GZ&k{M0$}ux&iqggZIzuyfF5^{%#(y7(-Y+xy|NcTHkvQ= zg|{Q=4%?ADg6jHvqXd~qPd#OUCxVyG;$}1|4N}SQ(-9ywu{$q+IpvD39TPA1 zg3dK>oOM*IAk%7`QZs(o$nI-$tp$6FZ${0><}-cxOQ{#y+Ltm|C$L7ke7e!ti_RKH zT4Hf+&D`s&=rkBdxR9bXl=&Clrh(cD0JZ_dOp9DliS3e>*#3e}HmS!SnVtQ%WE)O(XMX^^CW6tVNc|I1OmObZtU8FgZ=S^me9$L1 z7mz93zMi6YRM^t|0XJ~bnp&oLoD8aU=9<28WG0((f=88_HAItjF!}+;nkjOJmM#!nBCBpY_ zQi$@y{xnhe6<^*i>sGt`)vh;7^D=o}!Ad7BbtO_!aFYj{h%rxV7?Mj@_Y*1+V%6Ha z>XfWf)5Th>5?^z^oCjpCs32+&QnwiYXBmY;?be#+wH*>K2aza1X!L3FZYc5GP{#zs z#8_>A?A+7bD-fvaaJ|eEop__v?H;Ja)Pls;K4q2{hZVe;f)&^{@qqUz0MbIoFUG=k z1fQkN7bR0#n$C@=Md$1>7VWm!#C5vtkEVJ2eFBrw!s0TSA!J&j+`gzhZH0sfzeX)9 zNUp36>*6CKNxiHk#>9H_L;sX5T0NBb+M5TGgh=7*hZj5yBFlJZ1Dui=jULh^1AVh^ zU`i&Ii3yjgE3T`i#x~(`K&64J2s% zk)m6Ba}z1#N9pA&=Fg#weYwv$@zf%sjL}#m2n9UuzFvfq`s&{+-wF%b|rR5@4&?i^nawFugDlu-VYu*{IZ?u&Y;W-C(W65r>wE>&y^zwMnh*P`{FO)2{sfW znjF=mxPt|ta0ZWi1vV&E+t&b_ueij}3X*T<2Hdc-5y=+Er`guZEyK#3!hN8@Cy>qz z)Lidu1&FaBg4qn9DX37Ul4xdv)~QADY85i8yeu8GZAQsL zrQwa~ZkP&zhSQ`gQ&0R2ooeB|Sf1O$&gmT)6#P(w3U7}wO3!w`U!Ceq@`F!$NV7qf z_9yzzGX`6mdWAh7!{Q&mhDEb%)JVVUrYjf_suGgyP)K2Wnkw7Tl5~hiGw;Y0l)A(N zK1-U9gkG=u;Yk{OI33pJmgvvb%D8uoib>D(>62^gg(?cQuy}HMj zZox8KV@&WXdoZp$OlT@%F>7TmNUE*$eAY9fWbL)jRF<`$Wr#@146BmilUx0>P)ciU zuWCeg*M4~3*?d{->mu7wME87Niu3Sw40bRiH6q(Fr?KLI;}Cafhy;}sqYvM(T&~Bb zns@2xcyrrwMV3}^4>DY&$PiPH(yOaa7tR~Pi~G@}&&jn(fjgGxg`yLiyrfEx=5X9_ zm>MrHxq2?;2|7*oy?DON;JJpc$3BQjlcG_GgkS&+CejD?F+$^{P=c%~BTj3#6l{`h zOYqV8bNuBC2hK{9v~Z^~UGmvdE4ov}?PStLIh|f8sfNeu$ODIg z=%K&;fAQRFw%60u5`yr+C?~a);6t{srQoHqVYni=^h8FCE)kN}h6(gy{Lu{Qcv=XD zD@1YdF7C=~!aG|yl})l=V7!>^%Eg%D8Amm;tXyYh?))^L1I3Bzabt-FB(scD7r|1M zi|_Oq55k4f_05rD`@Tg8;xAxy<*`l96uUD^OUg}e;W0+{r^fJ_wy_LSvvQGt?|G9r zOU1uW2V_*0095p&oGY&-CQzJO$Ktv?kd>|qERI^>L6Eu1 zE%c(;MP^>CXMau;Qe1zWB2XWY#6vPV24c}C%MH>^dB5n(m!B-ulm+!cH%G-dtEMTifVLZI=k{CL zx}-!86oiPF>#UHE+0U&6+lKMb5mh|T&cE*iX)Rt5v$3E>LjKu}TGF#?-J&KYlq+Aq9Bzk-JzqgNdzAJ3Azf+=c@t0Mh z9+s!q>(S3bC5#<>O5Rsd13`XEYu3UiVFP=CSQFc!o9=jLH5&1zUT=?Jmi2Yi2PZH6 zlMXb!$ymkJS{Ca}_BkEr?%K_afqN1vi(;5DGI(Pmrpv#z{KJ`M@#&aLoIiEXC`zE)Lg~+~=u6k#9vUU~{An*N<^}+7lrF_Vl+nLIDZ5~)kW2XMPT0n(ulX3m1X5%Xm zIxuQGcgYOzmUwgyZpd=9z*eD1F*q~g!LC4MWnJ_{1JJTc-n%%xUPx zD1;_^c-06M{p~x!byCa9Bx0A6s4gc(A)k*Q_=9J%kAzejy!S`AnC$+CLkx+NYyz%` z09=Ww+y@EH7O(SK^~}D>{VLazaSo}B+oF|~Ps4L~R(d!PDc@c&Oeh2RwpEAH7HR66 z2meRYfw|or4XeUV3N3isDOy1}&IgVX9GL{7xRfv$=}? z7L)*;DV)>c)EHBV_<;-F*d6T=9Ye$SlU)Xmp~G|-EvHuUP#NmS5+2e)1$Q%&f1$eI zx%7AYI!_aBBSTf1gIWsvI*3)G5)n!RK1N#ntJ#z{D~&kCAF$bh4g~THc@U3 zPTh^seBQcgYj-9p@ef)z{{4~r^m{oO8yOx@ z<85kYSZC2J=Lkl2d3Rw3I_m?MYHuHP_2-1(g;Gn=16P`^gQXUWDgvwZHpK=fO_;~4 z1(}b7MAQgSaS*>E6R+)_!tY&jOY}IH!Q)L&8&CVQNnc*B4b7tSfsTtR6~~~ zY*vCE6BffeE3qXu_X$5cMWw1+IPwa z=w#=$iK*7HwUt#GhYl=Vng{}!PL=LP+$Sa-%N%a@6JNz;9eXKoIywqvb~1m(@@amk zT||f4ZG1{RCvHSvQ0=6*AyFTwE5|x_a$`^iQhb18{=!jKnqvRCKMnAy_KSWOz;ub+ zdDJYSUmTZT&$$&o-9tXqMO3J*LVT0-AThH)Va5$S`ODyy2s$Ef=rz^92H=Yg(X5K^Bb`T+SA zgEd&RhPs9qq-rHH`u7GxrnMFrM$Q*`mLngbnn|&517BH}wjeK;E#_8of^QUvb?QY0RqV)n~ zTgx;kU!F@t)gjY#*&nHXxSgl3|5T1YQ@5!0E`4m_8mh?)_yiz*^@Ocgg&Vsh(V0aY zeMZ*MwD*g%nyQl3*B`;j=JO;+({=Q)50}K*g~~CM<-yQ6=?C727Wh!`k3!?Btp&x$ zN-9xQ!{7HBDWR!?CWc0^Q*(h@&%_6}k?NArc$De144)WkQ48UU5Hw!j^2>-ZS%eAw z&P9|<*0vrvFNtSU$HbIUqlKkT;eU#nlm1evrT}Fv_osmn;1RvFJ`p=CFXO1$`6Ou9jUd^APNz-4Cf%>9J%>5*S+DH-`|NWINJ`NLN1ceyhJBW7FF zw|A%Bls=ffl`~wxZLN99jC@<)M5!5=SD`C}CuJlLYpgjO+$sC*wx9B*1N(YN zogRKbb~+uh=>!6c_&NqQA1Qi=xE>E(I6+UftaVK&^yiAX%?s$a1Z*%NDHR*>G;vD; zy~?mPYS-sa1mI1BGfO!K{>FJcE&Q>Z9ZtN^))zV zP%Bue>R?0Mcs=-GZ27OK9t51Tf+D{;B&1!gY9h&c^9-%(?d9A9^06C1&f`Cu1$wp3 zFR`w6ExZs=mtS zqtwIgTk`*M&wA%^riRDo5d<@@#97r!}dSjl|mZzE$v@br*xD{ei7QG&XA4v)+b+yEpQT%Deb5A4Tz?F{2fz ziO|4?E<(7*bEsIj8}vjH*FIss$E6NG8>vI#LQ$hg{aztYWP3azkcE3Fl7?hPI))b) z4Gz9@Z}xtPiRP$l57CLFpH@z@o|Dsea?`lj%UppQQ-vP6Wwf1guUYAN>}O{>0S6do zAavVRvMhYd(^Mwnj-ljWTExarf3E@RomTky1dElm^uW#u(0I5M=xtOWX)ML!IY9+t z6Ah(|QC^r*2kp)Zc*RA@t2z4dT#ggvW{!{OR3lRkHYhwOQ<9DU{W|wbv3yy}eEW8k zb<>Mu_gL_y^cIy?H>bUvQ!nHw#xjPVxj@GeIAHgLwp%A4&0`lf_sbA0n)eEEokRz{ zcR3GXfxnEz+7O7_*P5UD#U92$R5jbza$t13r0-%}Yug@?`IiCz=RMOOfvSohNfSZa zYjQ~`$WV=W?E`f@E@0{LQN?1RhTfU#o_h~9lA5f}L&z=(%o# zESje(ZE?~asCvV4x=BnyiOWA5tIP@BaWJJw%bpEi1dbUU^K6SMV2F`7xa5*Ux_)Sq z^K>a$OFsQJLSq)Y8lVHta0NQ>GelOH2?Wv0Mpc+SeHkKT0yw-=L6=V;79QVS89zsx zIhZ7)akbs-E&S{i?I-(7>Y9GvCZt!A&s1PIgR^Gn*2Kgcqg~M!G~T`5!;_&NZCX>( z<*WHSi=XdGnA0a=g>rXlB)HWS$NaV~!tE&BPmyyE>f&=jV#-Tl$N68(*gTMx2>aYpI_)|KGD_ddtl}&Lw$0+;@4p?h*2;Z| zO8&VRs+`%Wby{q0Xseg>qAo$y18>LUzfuw=Fm04+FqAhN!j5K&A|$9Om(L#8wtPI} za{1911IzN)==e+a#CI6SUrTwo1>!a6GIkw7CDRbvAJr{u78s`Zwg&`1bcZ2L>qRSs z02saL`W|=x#LU({%|ui|Aiev2a_?kUyI!#PI{Sl=EvQ6!xi6E{ z2@?1P9fDO+AQ~q1b${)h@SVkErR_a(-!Dsdgq}6TgdqJHVMEepOj*?~kh z8n5lc^#7*IH2u+G827$DkKzkBA2;-QLx`k|4GQCyx&)aT#;Nhs=3wWOOtY4c4FRat7d?QyY z-mQoaXmlNnkrk^P_{qOe6adQ}eUSq8NL^)?Sn$4BH(-8p;|{U7&zPtotwcWg2X9GW z?ni?)fo+a<&Ob6`o2>o~K^6@cTp9^kslc8n1vZQ{Yoe9EC_P#nI&PKi{>Ga#f1EO6 z@lXpT+=RFjXH=6wan7o?6Hj&Q0LZ**e@OK;n-IOi&G z3L{d7cvA$5r_w=anZ;ohiR__@tQU+uY1xuxqf016z){NA51j3mU?rKK4iNFY7lh9d z=e`))$9NOWsn>#+Mqf+5bdgL$mX9mvul+T*`oMod8pA8*CJID}fYYBI8zn`~#5rE4 zhWj$D2(9jLneNEMhleadKJc@agkgO_+$r%{tsqTKH=Ku+w{}1-e*5coU%Oc1exvCa zLwS=dm@ZdEuYIdbKEE$zM713Be zF$YBM=908`DMSqni)ZnGgUFW6JV7b=5ozztvZG%T`c0rqJIzhi+L22^IpY@us~2Dh zoU%hY{37=2p7>XE;(q~m*)cJfnl!0r~iQa8o1){7ZrE{*WQB2@>XwTN`eA zp;5AJf(*%{D+`d+P8z$noGc;_+->>l=Z+i(ND*tEd<^w1V)KoxlA6Mq#H1Lqio$~U zQcLZcP*+KHUU4rno5gq?OTwR%H$nmUUya-?E=CO>V&u0O*_%>s#bL z1Soj32vyeWWr5HZ%mo#B1nrN8$ga}FXqPOk5ocenSYv8!FW-mW*H2x<|02+IsH4!B zrSWKYLOBXuq!l$*);mtRwX?FDISRjN4@elOJY0nOv?4av;20GxfazOEnkm(}JtMN= zZ5SIvxTi8uo$WBilpLp0+E)yh@Qi%XjtxVK2_d3qs&SMK(sYkz=1C^qy&qIwds}Zo zy~9RzkZ}Em0P6M<-xt;To{eFHPRZquWx!}meNRtMXLt48>EkxRM|;nBz+z)_b0r5y zj)T3OJ^${;w(4c3&fU4jKgR~E<19UYl~C3!qU+5P@=a|EF#{ot^J!2<>EyI`;?V#& z8t{1me7~qfYpu<&Gcy*vYpm@F1#;~70>Od%yTDg_y%nD9l&=VfFNe2Vrq!d~qFkUy z+kAy4{d^_B??l|QdXVtT4Ce^Q1M}ijb51&@T=vOS@@KV-iU`^H7p@>jW&qM#yOGsjnV0h0;` zM@6|0UGDwnw4F0`)*sY#ot}xsRLM zmXJ&!k8Mj`<;JEVic?|pIe<6pCNG+&EL*Wg;tk9oSd?VgVA#gp#*G$10ef9+OHVwYDwsv^7R z9%0$g?sH-#ECjo|F!h(j`ulKyq`(8UN@r8>?7w;Wovm`&%^S51qdAZWG7O3BeJwVO z?cQ@ajA>e4s$jkO-~>qzd4@ZO3z}=NwXd2hQt*yQ+?TebUpa!rOz>_Fcb6Bh@T_~M zk%0gfyD*X2<^iN?jt}~y%<)ro8BL-Hnk+!!E{GPYf-47->SQs(D&q5Y~Csr{{s zfhH+9vTMnv?h4j1{ZM(=xB?dr!rOQ?dKuU-k^1I>t!l+XJ6e~V<#R1hM z+Y708x@)aMCupEGJ)XT&a5W^Sf0Xs|r_r&c9X?>OY!i5%!+Q_xUcY0#0K&cjr?0oC zkH7zGhyeccad_{5xqrV~4}dR^*Ux+aha39qf4~C$JCFj{djxbi0J=>41DJ=D@$~q3 z*d<^t4kDi(Uo6sGVqITo9N=}ocHWIzoi@%~H=}`SP)C~pMD}e}Q;FC$IrPhHF$;m0 zKdCRfXH5e{Q-9KAyiD3?V5#j7tdH;UW9ci74GgT8JBFbJ zU?=(W-cIk-cTc`&M4^f>24DP@HSz4D4l6n5#=3vqKTv+m;`xClm$po*OBpTUX-3J14dRihN))q#KJ+Y8mMIQTFenV!76zW;r}8H2WBnnDDH@nf{?!yfP-{;LCp}Qret)worLTC>vUoUFXc@5B} z8>Yxsh}7TAy2BxzNr=Wfv)8i+{f5|(wmIS%PWR0NM$f+&{2JL4T8Fw2Y@?;9 zNM#7De(P;+Wd66mlYsxEsR^LWdjJh^mmX*lK+s|g27gU~b8oA2ykr-1oY0x@u9D!+8INI4x^DvS#O4P>)(#FPeAv$?D{ zHjdV-kBxb)pa#yA)j!!r_aBX>$A3YpWZQHg z5Ucvxz5dEzXTX>99cfng4>wRC0_h7HID2|21FZeW8~!K9Z;1gu*{*uG0iBKM|9q;H zk6a1we+$Y0orwU>&wzy@*OCs>`v{ikp{IOOX)EmK2%6&`*Tkh z49Fb2?ibhccEHhJ@vM)>+5AHH(;OvVDtMW@+{4Q8C2!lJSBPF5**()~dH17WnXdI7 zCo-q2Tb5JlGW$@uKNKDbeo55+-i_#7Ye0Vi8Pj`uQSB|edYsYBt5a*AAdtFBqV+!6 z>C?))FROE##~a~rJLNRIu$(WZ*kY?Rz99#3K+QHB4bJ+q!013L+n3Js$=U*bxc&N%2Nz>fUag(aqGx%`n&#- z?yIQ%{7An&EWtv+CvY6Oy!=q*lXE@M7#1c~*S)2=yCljasJXfp9Vej9vCI?<-JU7D zt&VgGQ-8aW3OKvD?kzlX11r1(dB=hJLdjTyD=<%FNB?+*>VHQ(^{n!LycqA;{~n+F zz>%AgnJnPwW3}1WYa!qNehoj5fj(~maO_{ISa-6#f?1PC4M@)JRD2IL&NV&X5t#iF zJInePreiBjPjnyG$=X`GYg-#T>wbW3A{(DisOLcp%<*?KcgvKOfb+`RUX9Iwe?gWp zjyLgYgK8oG`)U7JCF)p`C*{*Hx`0B5CMJSECS?eU4LK~km;u%%v{BcGeU{IXg?8`ZI;IqGI}D2Ch>v^_F7L8e6YQY)}Bu+THoym7z=VPDpM;ogA9{qfK|1 zuG)`2y^tyqnA`1fYriRp3H7>r&;3b&JQE`}kwiaU`&C7)HJrja;bu#uBhNxL^YDwD z+DH}^A1tXV{muWST0?LiL|AIYvnJf-KvBrU?YXj}Bj>&<7$jms)&L0hbulvI?ur$# zz3F+!|LuL}YT|Q=!>51T`MbXp&Lqqj#$mtX(5uz{O{aL?VR>d{?x$cCgZ~wlU=4%* z+i;|#)BS_L?#38-p%e8lD&UkwC&p~NID$VcFXpq(FMF( z&XU0N(cdj?uW=O8>*g>S-Tk~xA@k6J+~=<0w03j1+Z7iJimMM8za0ny|6rm#G#@To zze^)}%3` zkMFlF_0j1(=X~{8RbOi660e5}D>6kyrE_7;8Ui!KiLSNk@-~q4Ayeys%3Fpz~ zNGFPr??I9y!QOe&{o?B%cg=^0q{r{XHjh0E#~w1nEp|wV`ozbWEj>HhYvP*~;9sL6 zSAdl;DxK^I4)A41#asTv2!k-%8#ZBgyjwdNRHzE$kJTO>ojRzlvOiO(NT|l)BJQ4Y z>Dt~CF+vEM>b}is(L>4kv$OmsMl!mj1!dmfeWa03^Cu#9C#BDNlMLsXR1M78Jg5~4 z?3v-kE~P^wsDwj?#8j8WO2ZQww28{euB{q5x>fCl4%pFb#0kCTHNZt z;cfAKlu7}+B7Cgta(mky_wMW)JR9CP@ED zx6sWX27(0*=Mi%JFcS|)@j7{&0Ei6MWAT?UVwe_vm!|yrP#8{GM1brK-i0*S_$p~$ zMR&llU%&aCgzgjyY|{-tTcz_s;}dW-(M#LAh=849WxQwJ3X7njMshxm z#w2PdZPKKkd6%S2t7GCR-f3~g{?SU)nl46$-UWMco7xNS9X!iC(C^LCP_vE#+cIKMI%N{Z2xV(0Sshht5YP-IZg=HHz3)`-y0yl&W^JI6Z15mSX z4R^51iKuTF0hdR0ZTanBiDO8F4{ggY(R*Eu6dMYV4)ac=&)mQe*bs_*loe+4QG0xW z#y;By=k_A}m^0(shB zTIK)U`K|SGsXap3GSK zoTcsGv&SMr^lL!g*U5W>Jz(W`0NU&Xz`_jJ*Z|Zb1%U6}J)8@V5siK*qp-2;6}TkS z1;|Ue)%9gimd|6-GgaiDN=yGuJZS3B?3*%w6;~w-R6ErU1_R#9f6MSizXj+PapD8N z`zL^}<~bPuT|W@sfOIzivCn{00e|}80G;E2ws?zSVC@?a1GqZ{*tOWeVt!u+fXRG9 zrfD3uaWP;-Sm0~ub3algzH1)6!tN6;a5E3A-j3WiJ$!#2k=|hW zfDrv|ZVqcjdnUY<_*(Tb?%vXqDz^W%hTt(d#TzThPE$O6af5U^7xKL#OMH zm`6g`ZV-23kyY5ZxIHVf@s5+@rXOxP6aQCZqdf_8$jlr0lH~qKe-j-@2!6g8u*lYO zXoW>rN&Iaausp)q0tDT7KBJ6H#O7h?^3=WGZVzeaaa@uWUaSi(H&T`JC`gQUu#zqm zd_l(0NEN%F(K~q2e-bPv&C%Ww3#Bq({M)0K&@J0W9F-Q5YqD^~0ZYAp343v$YfsZ4 zx}(8G0qGjQ_gZX|o!==lN?NXCm?saj<&4oYcfJ*J7iZlri^G z%~M`_I6sf^D45|qAz4m^UQL5TZam2dgPaxo_`W7O=868$q|wb0C2_@(taf@E!s9K? zOF=Foc_sq+7DXtdoHNVV;NK!Cl<880GirPMK*EQ{I0ec8PONai8(G0B4@T>2&Y~wA z9|A)eg~w|7+Tfe^^F~x?>+_uEVl=@DdYn|{-I0|h&^+T7$9{QE}-V#$t5#NX=DhB*F!C!gu z1jWit(#@VSfY<5OZOcC51Rrc8mOkArWxx5(x=-sqHfXJhTV0J!tV5BFQX2-?Co^qz zxBBq@_<%SpZuYu9_pHB;7jY{C*T%CkG>CX!e z==yhfUY*Cp`zje@GW$nCP3NnmaqMw_N{R^IVzgW!7tFxqlARi;*ZU(m9Sn$`^t<>j zzE}HqYlNn8Rha#3LbbkAbyVa*^w4p~d=9Qrhm^$pkw@PBK1J+TCy<-KF8 zOm;z4j#4K(JMSB$98~NuT8lq3vZ@V`GdstOQ`I8*^LRQ9fq_(>dfYT&2oSi!yHP+8 zl)gKCdUNO15xVtsHL6$awAr5$To)(p*{o!=N+YX!b#II$L9KS@GRa zWCu#0+OK$ToNIiPf4a&R%7nMd6ndfD)i$%dp`8-f%ewizz90F0+KmO(%O~hNpLTCO z;gklGcN5423*wtGhan{_$DJg4f=y8aX1&wpL+3TRNk-h{lA@)bW#Jpz z2`fF>9AfE=v&^N)5(`g2jyQzs(&VxN;393Bt6~5jT#~G+?mhWTF8@_4(uJ}&Usi60@rRtiS=$MlJ1?75Zfm5fh%H9#L z@4FCdkB*z2kZl-bE_mNU5DjMBq0r2(v!)*@4kLvI^dfsY=b99sQ6CP%F`&FB$5x3M zT#^hw`*(^@ok-Dn<2Q#gZ!IbQ_$XCimqmZ3lAKZ+azPsmsj}oyD=!il-ng^Ra2}|* z*X*NM^vko3{+`_6(@2c=|08@I+rUSTjTBAmcULo+B5}k1#84NC>u?;KYZ~>|^Lnf4 z{aiWI5vGnX=8${YVX~E~>9q`ZyW4f*4bNzoW!E!c zSaE%CQ(&2y{ad#(UooP*s#MblYqrL7BHcQ1P_CrZ``kY~J z3KWzU*lPL)7A6CCr}^kU|MjC;{p)fV-tM&o3U<93e(H|{0+NBZ<~hmVzzINr8L(IM zE1)PexdjL(_a$@kwLb+qfhKegC;9sMwYM6y>iGT!xJII-u4Bk=S2MkhZQu-XEWX3C z9e(8j)GB{3bjD$D`VyDVnG8d<-u;4DT(-w1QU8kMH=fvKXJNf#X;BQovGv+943?D zaU3WmVdeB&f}r$nDAlwrDlle|^fW2&`(GPafN{;AGp^qvF_SUIYb~mXvpsM0p=cP- z&evU%`%mMPrb*xsR8mek(>oHru06NNl#c$_Nk<5cQ-ehtK}N&Ie~7YDEUBYQ!|YnVVnfA+4Im@EMn%N zO=n!ALvS$-$0ijgqDMoxX(_E#xUbY}XK`7+CsF?J=_9x1$DNfBi-7U9Dn8|zD&W~E zJZulisq%&6nL4%dr6iDN_j@NC)D}`Z({;^=hHSTvt#H9yTF4t$ei(_)*Ze+FKlK(q zXbAhYTkaiWMCZ$u)isT#A}BbV42C=>`h}0hc~i=#n?1o7PYQ}H?IV?;Q5Fv_!{}23 zR#0*Oa<;w-7B{ltLi?{sWdi<}UD(SR_*LNn_?0epA1s{C_`&+fKz6jlGIgGC6dmf) z?zko-pg_=Zqol@rV4lK)zd~4ADus^QIgn++7!v`e4%9BH%9}VPu{=x3cpIEc{{hv|^ z@Ox>Qvw77x((oTr@}`Y)5(ZUm;FiuT{X~nVq2YNEPxR-=X{`!u*aeOoNk{lzh|Drp zurt^ihfH>UYa1Mpqk;>O1S$u$PX|5zzxfB2XitVWsgin*)c4f?0{KeA`F_C%YFsX(Aw$dzD)kvAg1))5foS#XUb8cxatCpMn&13)PiGf2veqvn!|(f?0nta~x4FA97~IqP2LP77F6?C( zuR4D3fe2J#3zZ=REFtC5n&iLTJ%E>CW+U7vd4kA@fjj&He>>wT)wusD$?3IJ`*b)7 z_L^S!>J{`kHUHVw?{{+MSF?9^;p^e2=hS`l#VHeBd776#HP--f+jn%SH1V??n@NXA ztOp$n=ON$BZQA$LE347cRj7aj#W8EN9fU^kevdxqDe?C%tnNk|j6_D*oxwJs_#FBceeG~J?)u}lc6PR%y6W)(w?HWkJz6-$yXu4n<4YcF>x ztmTmU$qdD`%MrZnDdr-3*ILdrgv$J$EZFMANJMCfv4K&ti3kob_2l0$TAiY%c+sK;Gvh6;ZiyJy+F1SApxmZ7n^sfT2&_po z#=}mi3QfAzMb2Vba%7&S$BzPpPlNB!AH=0l`Oy;vd*frud@o-HHq~}K2=P4n=aY(X zCj^lf@B#`$)f2qt*Y2M9{BKpm3L~TAhC&I(o8uSwZgh_@F#pm=B+|zC`Lc7@jQOl$ti_oXS|PaB0-kNkOUzzEPQ2M zNc`?D#kP2DCJV9>=1qDECS1}+baD9R+)Qtj#6M|Y@J{xc$r8^dxU8KJ3$w#T>f-Cx z0D)}zL{K?VpI}f(`NcE7%xK6qcO~#BS}9c%g@VN^dTK5iiHR60POUh+!WrOGtE^>o<8-na|SZP?PKF)01Cm*Gyg% z=2f6nC)ChG9AjFOvQXg<18g7|JcgTagnS=;zu`P=SV~#jTN?P4)jJ>0ZXfTHmNV<= z@yPHrCSeLY_q#ycaArSCOA;@o(Pm-wpGJc9a!zRyz%Q}gk;?9{<+oJ^ssvvB-err@ z%jbzQZp#Ge8@o?@go-WIZrb`$Cc_wOPC_fh@k)6~^@@NPmk8x{RJ z4?SKAP~FlZg36Lyn}T=pZ@@iztmWmE?j3YVcMJ{H-p}O8g(@J2{=l0kYH8*>5Buv( zMFGyU3|3NfA}$*^F9c6;GLB(?8iGCUn;1+o9XnPgIjpgd>`=1BVdVOZ55`V^+mUdyh9JVVXj!c?^iA#DmaJvcyP>HKH$ML?{*x z%G@C{Rt+`_k}?Z21%?_ewv53dnmLLzuS}WhW(auLKn~!>lhbfw8cqss=fI3CI~(QU zhufSs4WqFiX9Kz`((MLE8;u~xNd&ZH7Dr_48lv!_L(&^P!`j*%HA2r4<+AzZ%FP>k zM7XJCmkXH3LI+xsTVrTr2JV(kH%AqL@Y^f;;~20j@vS3+DpX7;5lFl~IC}f~Mep$0LfL;v@?Ue$v||z7 z)IAf09u#7l=7yi%adMGO20bj!k-=d` z(fVKed|eQHC3#{C_*vpYfr&>U%=kDxnP>BySvn<(IUyX22!@{QZUeH1zHOx%xIzmU zDdo&2QA~Q>UxF`(RnD0l(8^lu;^ADHJ3-DVPWjq~!#-BD-MoFZ0Tmjt(z^UJnPn7K z6uI*m&LFAeWc*5KZG957EWRJN#@hB8?$bW%i`M>+(;^%$F1@QkVOca76_F@0B3mvZ zj?%`KnJhfHt4K2WED}wo5%0xAU_pAcCQRiD5w|;|dSMO4%L1+tz-;vC;#aWqMnqxh z$bJ!ps-5!uZaSkavHe~T+*Aw|^S|`m>k4&a?#(x$V@vC5@VMLpFlZ4^P7V54T#;7` z&-wr^rsH_zf~c8$o`3brqh0JtR=G%$57TUdeD7rzNwS7v0r~eq4MDj4g0N9Vid~Xi zP}_GbJT^)z$F6u7NB$)vtXX_m&2iUOYGPz8WYROXSMJkE|zN^7VJm=vS!7 zIydd4mRjf5>$Q~}Y{BS<+-vo&du&(Q0yv|yNNhv$5uB%ieR?g)>X`B>ibLPWgD$gc z|5|%oTGk$hc2t83agK+s0snC^cqf3svhaUf0{vuJ;J+_}{L;|7i=k80i)GbX(RPB)p2w8LtA(Lv%aDUi zGd|6?ul+QUz^bQeaMe>*<51r&X9}mfL`=v!j!9iNu2Iuf6u($GP5APTw+}+3+|Ye!HbPqiy|bNB_E~f8B>)f$9%* zfrt9nBmL`Z{pukSt9HxWc*RM7d;rZUO*lTlK5iczBa1n#Iq4^6m!MRv5k0 zEsn6qf+t0+3uavIiO4$_?BJe2#fE;c3N6Sf%|X3`G`rEmgk z2~iah66<+1xlt0oQ1#|CsE+=WY0q6Uo^i%eXK^VcU{`)ho&&{^v zmSlM1nM?>qRqGs9hy-t>*j(vobb&Fm3exVcbwVgR)a zgObHCHi6YtwY!6f<*mLo6ojg$*;t3gXZ$Zp#psi^2~sv?99*A#v^XYQYC90H$sPtN zhOuC%M4}W0N9!B*&a(6s{*uO>`Ntpryp|F?mA*GwIOWA+*({sTRMbfef?|M*!%1N>PRq z`pD~~(v$jetg#xsHW8P?x{tkKtm|#p&Q+%kaa#ObyRxX_2V0?MB;O zHKv&5YXCp3!V2`7)x3^ni+_0vAt&7P;!(1qWp=zM(4`470eY!sFMID2dKGmSdF4qK zRmWPcupA9TQ# z5FV99h}m2~JvOwiwLvIQ;>AG43Az@nN)6cvaJ`L6xFK^d;Oj76CI{(BTCjKbKjtZV zN%Mdr*;mk(#X=0et_O+mWJ(ik%C#(hWQO=Dv18j;yv9_cR=Vtl5%Mql7}#M#zUL+k za@W1IhH=6>pIm1ZQo%6g!hBH2-tcH6$+zOnjzJ-Aun}uKRvFcr0qI+jfC)yB>RmCy2 zt_W;6#ae!IXXQx9Jv8vCY~KZ@!${YF3kJIhgTtZ z;WKZ+1FWhMi{ZQi4!H?m?F5&G(#~Cgiv;6HJ4Ge3G#0k)KKKp&2w<{jdYHPrMcPgJtJ&p6fonF3; zOXPohuyt>h|LI1aRsOdx`e~T|?Fzep(f;4w+S%GJ<$rsy%KvyH&no}hD*xLm|66_j zx8VeZpS_+EfA;>`>uIlD@3oZ}4k#-VP`pNF6Uq-)Tuc)LmcTt&q;HNvP*?M}?l-dW z?E3G<=q$2FhaRki|{Cj~REwfMH$5+`#KBHe`*hq5FX+jxxJ1?Mm+l(oaL z)@s8QoMr;OtNA3P^eD->$sh+z%1KgK;U(u&IJ@%sF|_)RIfW#llx%cpQv5_{ld{N} zimKr$DuxE8C=ACuQ9;(G7-bOM*rw_t4?Xw2WR9sIgMdaP)}82J~e$ZMJU*ek`ENnV<SYN`{2-cU>s=4}NN}cAFvUOV(pGs%OrA-ps6s|#DBKEF1jAa`w29}7 z`i)!~Ilka)D_2ZmR*=xuH5XQ-R5r%JgKHauh1ECNYiV-cVK|=X-K-9gunGp#IkKI@ zzgqzEg+Gnt|9m^x>|e5t<>mkPcD8m_^1mB-R`UNZ`e`8lzvAv+wEyovdbD-Vm;XO{ zw37ed$g`6FujKzL`G39qza-Eniiq-j^UY{VOK*jKmOpUHwZI25hb`j%d(s)`kKg3Q zV3Zzjo_=e+8>Qp4$lZ5v%b8^EE2x^5UeTq+dDqA5x%-N($lZ5vWjn<;S_|^i{;$2C z_n*H#=pDW6{e+YM>iOTIcKiABmoI+k!Pn?+w7o?c&&{j9;H%E9TVyUDkKu}ujPdyI zQ;G*bugJ(lWt5>X4F0)g##3E!!`@BG%FvPea<)5VTBU*FRWdSe1oVo6$Z5BWU=u*1 zpaeIxtIgy1wF)qYZfbY#>?&{@+q+^M=oGzU7ZnErao5En->7Ta9n}NR$%H!aZN(J$ z{u#710G9)xC_fyyt`#bA31JJpkG_k{WIa~D4`2BH7E(7_hdP!s2I6MwZ%~nMq~9A9 z8}ufTyR*kl-?<}=I}RH*bP`aL9iTVmy!==aEm9C$1ab02te=THaD`j$rRQL6rhUQ0C|q!H+0Q|LxJv z?}1wJ4C9~eMXg|G+k86Xk=}_m==uNNOzmjDU5@FT?l%!MzQp-5RFh1fIQ{mF$G%W_oEoVQN zva-;|$r!!s5-+x^c@6sukU|qv4ZTxet|eY1Uh+HB@voGXu}dX_upUP(&rz%zHGtg- zkqIb-$#qJevVYqLA_D~GN&B`8FDWTIUAqm(w~BoK0Gu|EHXBA7`fclXcO>CmKQ7vB z)hPVkjY2~iEO^trSR z&iU(cWR${V(4BcM*}( zbCR6DVWK3vsDy1bZED-hZ>KF3(B6)fS;3k-K0NHybpRxW0WS*MevBGiMm!*)Kmz=w zL{=^64mIN9-N+e_=G6+9>MbhHovK@yFptk(9~_isv5Z#LDaCD--Q@9K#oiV=)hQ=U z#C@z(?x^G$u`15WlG%Imr1#{n|JDA`shYQ{=ba|9UqNlW;w=BX)4fq_7wXV_maALE z0PealeBKU*acMm8X*okSN(O8`6`iFqS>aF)9~pA8hiUCPFzn#gi1Zw|NrKp8h(LX7 z@j&k}t1|x{WIaxnQo4*d5m~tk#mf-?ANZwMOn^Z>p%Ly@1rBLNt?qWm;?W7jS9?x} zviBXc>S7hSmEDgQ^U%I0yYcZWP4eT)LJsF0bOW>z_5x7&z)rHFM{`--9wVi4kSuI4 zvqE~Z zw|KPi`Pg*8K57>|Z~gPyz3N@Od;t%IG_H{hp#@dI6VG|IZf@?1;yZWujt+IKig8%u zc!qweJxW>h*xFJJ?xkUG|HVs}Ga^+iLS>ewR^e3^p59_r!M^Oy*R7oe8>g7X=?JZ4 zPU6{de3At7!DvBDEvunZu4y6h^kNMTI5>tcxM`@d#g4L9I8uA1h2tfK z{WO%QiUPTp6FH4ADo!XitzKqAlgiP=@L8~3E1;G4p)-(8sGBRS4HKvaq{u}q6r@Bw z=5SRP2g*6OL5l(lVreW%H(j*q1g4Pgi+yJkSm zPF4Z{D7RCjzS?F4<7?V)_J8fb(qA~1Vy>d>RRS@bt*3q%@TuiempX8kzOSn%bZ=K7 z=CiY8HXLQ=t!ObE%+JlSywN)pU4G=U#sZZ<)M5YeGEm6On$W9zj#RDgp>^uW(H7Nc zz~S3Zm@=uSe%q?$du^NrK56a7z3y|E9pwL4lgQ{3(MTS;MZMxu5n0}BE?B;ozP&h= zCA9PR@o5s~ilx>96%%7~JTwU`2un<*vb}sX65$k&DOghj-~x zr_LhU5T3IHmjzvkDl1>*{LU0oCpsmycug~sD}Q6VQJ(G|tVi%D^r{8kQkOS|YxHVUPlAmWUUNSJN+7_W>70+Vi4V{yec_*AHArDpXg%O)jW`l>urb>zm+aut@+;ah`Dxa`eO`&1X7shhSZ7nb$t9 zVHw)uU5;=RQ;G-GI4X?S&Ls5Mzy}gX65bB=%PMh01l6M6c%BzMNwno})SFd(+GSRn z&oW}4Mu&$h7#6~3uEe|A4LBUUy;=QhVZc6-3cJDF)ic2Yz`7XGd|dH%bQvx7QU_h0 zOs{KGHDFk;^J1lsl#@mY#$XkPo8NXP$Lw&UM>1U`R3#ZxwjQL`h3X|CsA{#QCDT~gGuZ*RO-OfI>c0UNZZ!BoU{7lS_Nsk zoLc8bqa7c0{F*^+ z%NnraTNPOMx>owE?Qps(Ukbh7y>-jZgT~QEdWFmh8;{c$bGcsEp@MSe@hJ%zye&TL znpUlx8M_uN2{ht|sl2=HrS+h-MRhQ!h?Q?EMj#x;i1R(^q;!(Ui!O+nS&} z0-mnaQZT}m`|u~!on!8?I5!deJSmY1noz~ygunLs$B-e}1re{ei48 zo7R<+=rp?j7qcY!WceS0`QIPjf3V{JZ{%6s|G(&`!TtY=yMNLCzqj*XyOjUs{=HTH zryF^~_y6rJy8lPfvr#rnfp2@3&2kiiN8b#G=!lT@i)=G3R@eX4_5XkU^}jCrO2f1& zfrKn3tx8B(Ks496`IqGg)p=%6W`S~nUk+c~+tFf*d@=>h3b^s`|9iP&A9ay8NBc)_ z-}G?c4_~}Jh_?MN``^Q;BY&`^Z@hE`>Y-k&2Uij=Ex7&B{-f9pRhKxM!sWxo-nt3H zt(&bG_?-<4*koYw`8p&}CBd@>=@!q;RZYX5Wtl_@;pSn={5YQcs)v73V_si%oB9_9 zW>W+B`rzp8>leL)*RNl`Zlm4uEGbaJu(`R}C^oVQA57lOs4w66mN7EH{Tn@QFk`(c zT$t$dZv9D}?nb@d&-=ao*FU`J^)R?{DWq|mJJqR*U%UXPk2rsDTkd;P&evqIX~t2H z@E<2EE0vXCbZ60CG?precZPN*XUe>Q!v3RU6Azif+;S_2yF1Dw+GpKpi&Gf85Y9Hf zEjW1w{%_aGAk#kUkWyCqVKU3JnII*XmyQt1UOt!93ak7TNfi&@^j?1dBhW8^tgw|r z9J-%uU!pYn2JxD2ayy%FN8F`Y{8Z9dX;5-|?ND#mfbEw5mi}((%L8AX+%J0Yky=U5 z>^dA1wi>4X?m!j&HzSJU7r;{hQkgPD=WE4uu$=EQvw^4a-x^K^wNg13!Lj2il|$!! zt{k~i;lQ9U@Apb19T+DwQ5N4E*Hc1#0>gy4+sW_&O3bOaNYFN;UoeDcjN#`InpnfC z_@L|zmPhRvlPZrc=tGp_MK6i6X)-}jV$F%|%#wJ}L3WX{(@%l91L}lMmTXp=a*Rzx zQ~Pad960g)K*jwOOfd8)W^{`CSgj4btPQq5%5ocp(-^be1291Z0hy*VU=jznIC!&Z zo`-56DM}a%1y_*ZA#15~0SdDexy4L|tWK2es3PP(sLzpT-Nf*77i#ESGhaf4Fz0B{ zh8RjoA&7dp*#{OK2Hoi3#mk=#e(FZ8mvc-#B|^@YM>cad$FWJLZj()0i1pzEhfH`v z&ExaOACXG~k-S!wpnZu2I`#-`e9P0fho^(}cEXvJ3RutJ-MBkLDfcJ(D<36G(bym^hapEDh zPGEcZcMWBd_0g$4HgPA&fr)zsNIrn^w?n-Vr5;v*(-oeMyiQ%#LQRjYS6MH{Q-5;s zotmJ#aN8kIaY~|rI~$2V&H>&Z1G^llURva&@kwU6b}v^O0eYi(xLxN9AYbtXEqudY zc)^=*F3wO&g-V9|GI}qxkhTF`R0K2F1rO)8$D2T1z3*`JVE$oQA?4vS9GML$;~nFQ zLbTW0<&SW{u1DT`I7#iyFcj#VTC3Y5O=wrSdF1YB8K^#;S$%Sg^8ss-yU^#xy*az? z-f;WeAGL(b{r#c5xS5ohHUueR3_%73GnG5{hs!EUO##H5&D;@tiz3wc$`zye>O;Q{T; z!D9s}iZ%oql$~VOgy@;r`xZ*166+%2y(O_^toH_B#D*1ckD5k<47J9T-aw`$hIjor zUZUW_LI*Ksn>A~J7lsYZT>^N`;I*~s^&y12eWq_lhZALkktY#*=wMM~BC?6!tXhU$ zwZ^a*-i+cAUW+cw4XKOw9?$i$+zmmZG19!HJe=_cdO^+U5wqJU4~|LlSj9}6o4B+xn*N64q{StK5ui#IHl>`1cosi?GI}Tvsl}8{gvSmdK#%cH_t=-WClRLki6lxR zz~T=E?mAFdA4=jWe6+~Je(6seKE@M+rVk@#sJXDYZQGsAod;WR?R?-KczxPk(BX1V z6w;-uk<4XjadjiQ@|<-_y{uTKa*1BNN);+)cC7*tje}{~1G(hld=gJ+!3`|JQ+elA zhcBL#D7vM^8MS%Eq9J6Kr-g`>E0=7kS9 zakJS6W#9qXxk`W$5`|Yxgv}UqBAOOk85Cl?7z1ZpYB7|JJ#l;%Dt3)9p%y`@wVbbv ztXY%ru0Or7(db6*|9jf>%Io28&iCJSzBaQ_+98$;@jIi8;ai>PyOR3tv4ec+sw68+ z%ss6Y=14P^T|Tr9Sp_y3Ofh3Xg^&u&> z9IgdF%llqfU|Y%EI@JIvSwF=L{(7wb8pfkMxrxTOk;KR*DSJ&> z%4c@8iuixKxBg@{BFfMAqJN3<-~M-tK>rB}Y>I{8p0jv&?QgV_y#iw6sOf|KP4lz5 zAIk8<-85U>z=jc+(%C)B2`*~i)>}pOKf4g+!SwJuNY7@1on7Y%rEh{%$MUAvyoElsyZpRm=s68D|C>)vuyi=4$ zIr)nP)lukvnT8U_jdck_SX$+J=yjK1m98<m-?jyBv$s21P@DsT$H*ccPTi7T8zA70FZ9ky&Gxjq)?l(9QM zO<@PMC%6LExr~`{$6YYBgwY6bBIc_)J-oW4l^emwH*B1QtV}}+37C)mc-mg)9!|XL z8QM06!iOPyJt^JRp!9C3Xdn+wD4rW4L)ra2%q#kf=%Oyy0HHy{arTBSo?O^(Wn4Sd z1p`wWnb7hu2B?0z$I@H3|f}ABF|8Q_* z88dsJeJkmOkhCFg2#~Sw4Z#uBj(tmjRc>DstiL%At|)yf1cxa6|987`UFvD%|9h6q z&{yiRPJow){{=r*{(m>}to(n!=%<1I?-h6d;{Ct9v*Yjo?fa|vUpMj?heVomXTs4p z5gADPCCQwSP5G1XHFO;ReDM0s;ma3S4@bFrZuqI+|LAloJOJLXaZOyJjZ2*Wj~?7# z?f)BjR_Fg0{Vd-9SKa*!_x}U<%isSGR`EY>=2^x6Tm=AG1prwh{^ygIKmGLbMJWEK z^~H@40>vcG$B+T_r=xj}|J_PHVA9*D^`sU3OQLb$>1)D95agfsf9*Xzcm=3$?|e;x z>&E%8hz77wlMmBuRzxTB$@|SHI?5=L9Xgvlg^7pNeV$GR*?HcLF0wg}2qh*G*c2Y5 z%T2&ad42Gd({0j=@82Fif65?XJEL9qDeP17etwF&8Gz>iv!D84U1U>JWx-kn~6SH0ift5O@rzI++zWsFIFN6$*j!XI0a} zw%7aN#oH&n9$RjyzhD5ifxzF1%FC=%=Cl=L)-j(f;4Q|LEbizyG&(R{EbCc~-&JaU< zcZl5kFz%D@F<#Tx^61Am&`j<2e41j-kWWOb(JM z5F~6M`Or^bOyJ@ixAlE%GIZD58vS2?{k3Cvk&Qr+W=W}rkiZS^je$m;i=sLkGc`R_($w*rJY}lxzD6*o^COTpa zUEU}0$VNlVvwun62G2MBiF$%LYUai8YiBUB zDXkp9GnvH4BN|F)IIW(+VJea%-1nOnoSNZdtl0L^-}5?X&pL&mna2N^E5`mNtS{K1VD{g$+O}g!WDeunoj) z6&RPF;tLq+9PXLGEn?1|^nDV&dq;6?@vTX*WrN74zzSd{DU5EUMhXK_W%HSQjMk#H zg*yzpq0Tev`c8e<%}IfqTnQJ|Ofz4c0}DMG==@u1`7jij14GyGLJ0l9MW|s51Y8=~ zZ0v-2Vkq{WLaw~X$TFVKC<+PYV}{|LYzVRsvmt7iX0XRHilS=|m|Z;i5aaps*r9x- z?QqgHuNXT;Jm7Y6fs$MN|Ig6$tHQ|^9hw{$0ME|-%Gr4V0|iAdsP~5G4YGuTB`NwX zQzY5TNKdcOhsnJ5_Nev9v zKHWwA(p{y;RC*-R>`C47JX)H&f#S`O$j>PWB0FG4bbU=nI5h3!c)*N6-U-jFo<%){ z9LmA+ing;Iu3K|sPcG@<$`e~^&UjK`Q{;@JF)*nIgBU8GTI|ajAH!4zW0K8E;Kk5=c^* zY2%W(INS)7X4GgEh>7-E#pP$Mat=!)#8)$afaJpFjLc$KT-<}Tn2EL@hX)6EOICOI z@n%Td87i-Yw4cke*$v`~*rN-Zi+kg$7*)WZcPmgwV>pHmco)>;4vr>$~7T+s^P z)&A=@2O&EE^Wj=70a%;Q>VH=qNxLT1yuVRTTzu&fo4FxemvT=_!}bz_s8Qj6wBOqo%!2POEIg+i9C)4RLK`QeG(S~grG=il z&FKq;BR;VaC^ARTVVQ$;7AkdFTUn>kl|Xn}sjFh$X~6haa)2afIQN5DdL|iE=q^jE zaGb;_z|2`2kZ@kg9MvHD!bKGZ?4*RJi zLT+z^w9kqg)V<8o42FqNX6-ErZ;gT$uuxK23lr4T#3bXD1&-2#DfUF?J7}36vBbJ7 z8xaeMwv3Lw)Us2M1Ve^YmAa#|;M3ybc$!NY_&qMBum~W?TCtQDY8b= z!;&tT90Kmcd zmnCrDNA@7G_A;$m%IrB5xo7^^hsBuL`M9#l8RnOf3dZS#5EL;lYq(+yiilS!W$MHG z(QyjwnU+aocsx{t;ViIil!ERIZ{)g^SW^y-8Co%x6s3CMFfa>@nrUeA*?fi#BE_Q= zK~@E&%@Z|7;UgnTGoyyq;n5iRu3pXrRD~|JXI=}|aM)B-KjMF&jmXh2bi*=SYl{}Fa4As0y>~k_2 zm?(nE6x`%a`sgTMJSuC&i-ZYoud9I=jcgkiOUG>&QvzMv8b(Wce~w2L)dYRFnxi?v z&(-yLWZdd9TKqq^s#vIL11%zCw7zr6BSlNvQ=UkIG<#9Yc&a18Yz$aJzgQ|XK1osN zr?wa6^kV4tLGBJYX+)e(`8tbZ8kKsnrwBQ@^m8NVR3o8G25_xvN{5zRCS{@YjhIrh zzCirD_87orD^H1uz!YDy1c?Y=5a1qc=pM_nr<~@vqSq2?FwpCO(gR91?LL?Gy6-kD z@qrsGb5ta}PcE8ZiMf;NrP2WeLv2cUVGp~Su5jG@#huYHg#%?jdXcEZ3UuMx!rF6l zx<_mlV7)O~b8$A6V+g7o{3NxGHU?n$0{oUaT?? zvGwUSptPA79n`Slhw~9_d9imv9f?0I@i14_YaDF5%3_a>AF}Bq$w&<{Pof!gDIF)# zC_SFV09bo`w#Bm>B^bjQ(5XS*iAwWTe9%MG)X7xOsGB#>)dvZmZF+0)UR|pDkUTj-?ivu~!BET#^X*!yLTgE80D=BBM@aa(%$vjg|o<<^cN&b7|`6T7Tz~#=rg?Fjf8WmPG2$25c;F< zp$yGf=F#3IK*=~QTn3LbZSu_y`R6=CHbf9{PHWqcZydi*a?P9OBNTN46(dnZ@?o05 zd3KgW?f-f77j#}gQIBgBh%1qE#bz_H9UV{85vQ94Qq`%L!y3a&Zw3cIgpBE+a=1_v zIY@J2k;$Y`C6%q7#vm~IyueD zuu}%mMA#_1Rn=l5v)f zSgxQMslszwG43Km`>+V>jXXDy8e2x*W(9N_d(%6KXUFkLvdBnwpX?aNFgYpAg32V! z(J5GtgI01H89s_^IgOefni84}ic52>tYX=QLMN!zgOtOTAX`lv3^p~nh+rX7T4i7l ziiAwJwIzU=9zf+J@eNFmlhgPtMS0@TIc{isDVtj*^f0-BhlP7FQ3)&4Tnn>=Jk#93 zAj9THB9QVWD|0QWwF{49#UNy(W|63A@F*CL8VOKHyn~CL<=YDvgM!!7S(R$-hJVp! z`u?7bQtkBQk>%1}Fp3~dtvyQ463PEAo0I{wScdj19^0D-n+}-;dTjYv<;s*e8m9v? zl^j}WolCw*M*Ncy>!k&;?2*-m<9Qqt?|ks`S;w`qV(&Ddt?j37H8z|e?Qgs|~qT7OoG1(>V%Z#573o9KVDcn$=mTc{e z0ErjTmh=v#tpf9$(`#-}ea=ToM#^M6-^JN6M`6u1!Zs=6eeI7}GuA(RU0299gCSRU zw}<8F!ovV}Yf6Nc4at;)j2^8w>7<_+?S&A>jpG;(FLeMYH8v8157SZFhjSpIQ%1w# zgeMG$6clh3q~mH0!>sku${gtuUhH6T$4a|2kh4a^5#=M`!Y3e59$+d_atptNc_Ybf zYrAFKI*07#=-hy$@%Izh{cupw0TfSI-aJT8QWDipa2YAjD4dMAUs$qZzIc-LqPB;W z`#N79itK$dDNE$9ZC5N>ir!h{uSt7RoUp5zqUI!P93*%NJ67Uzrt#j4gq045L%dTQ zqp<_={kEzEW zExiunqMk)WMvcgc+&1Ge%CvCtpfb{2D75lw6q5{jK6!76aBz_P8Dl2_eR#&ovjaIk zzIPVtzHpk1rlg~sPX@`1L;_K&iIR2Q5#MI#s?+8Prg>*30U?!+{?^|SQ;Hhr$cS~prCjr5EYU(SuA>G)8X1vBV9|Dm|0Qd6?7z5b_?|^Oyzb8 zNbna}9j^k+{Uv;M z$L&@Aj~jWy_TSrEWd98p#!)s)fw6##V~!$(=$qjX*|@A))+AKXwjbm%VK5k+*m*>^J` z(B0wa^}#{!uWp_Keiv;oG;im4+-!ElBZS&+k#CD!_$(gvM<@?eofIEskf~*N>;97tMyWP{>Miq6BbJj1YQ3I5`x(PDL7i&jJnYsZ5@DzQU&Lj$O@I2|bqPi$&FA%|+3^p?t`yUx`SnoeB1 z-h!>$pJga_MTvMr2>jh02zIFW4JvEp@N3F0Frs?lJVI{b}# z(X>8}(VO%H7F4bU>o_Lwl5v#M?BlP2>bPrF!ec^|WHWjL8|q!ZVhd}-X;aNBYj5Nd zmNcYLX{LHPpNL9cSywwo=2ljAfw5qwoNVbBzLV!`pNKUqZhYl8t9C=Q z&Kgdy3coBNYvO8f`4=k28@91Ql{ThvI&*7&jF@ID=*9w+lo!hqNCm5WjEH6_3()!Q zt&fI^ibhLjY}4gn;t84fZdf5JWM+kEsBjczHb-k1iOyz9^}b=N-rKV*D;%2$Itn_P z8)9t_s}Y^&m2;qYD3aw+3C~+$^GbPiCpN8yIUK4;xC0H#7af>M3tMeHO?|s)a#Cb7 z77^p_Hs>}E2*z4MZR)HZ4s2jFDcz)lM#u1+pjkBOj;!t~D<)Qzv44&`!1!eo zl_pxvBT&0|Y_FGM(5SWQX)Hy{GGW8PK?1vo6xnLE69426AueK3|H8&rYm#^pSJR3_ z&tDU5o2s+cyNORqUji_&S(AY>9D}2GfZe9+ zs@o;_mji<)%%PLwPYByZHeVa?y#k}vFuSRaOb14BitcgV5{uP|rfofwSX>P!@mMWV zxq-pCYC&W!QaPX%7V|2_QL~sQ$@{K$)#7_iI;2Y$(X2sM+LR>&m?60}3a5s_qH#k4 zkaN_R0*>=(l#Hjv1$CDe(43654hKn`KaOD8uTdn`Ny>iTV@%UT7b zJY?z)(~=~3F-+l~aSE(&G>RuDbKIRM;Ua>J{q2*v2u}i!tvdJ7O)4T6Rw83GW1B-@ zPYP;!6II1sCfl2AvP0(S`7~9L=}|j~%lTDI1_E&5fMt{e{61{_{5ypDg5r*n@djRA zks1-$<2lEbUE}S4&ayXH;beK0v%}nOEOK!0MgOxx769jkVx(mg7j?v`J01faAILmaFslNz7$SGY!MR4 zT7Dqkcw1Vgd>`2p8}kh+l6QzoWAKu|eRtO#!8$Z>-TJ@A z_8(`-4E1H3r<;A$J1o=2^7bDOcJ6Jh^uIUqtn5F&=%=Cm#}#(};{E?{`;ou@x3*XL zKX2r5bhY%zD2taG9I8{>PWEqyOh$=8unF0nDAQ3*aB60#9DQGzH*~ zkGF!jbiGq_X5kuU8{4*R+qUhBZKL9(V%uiLHY&Dl+s5D7-Fx)tGfwx-x>;9i-F%qu z^Gx8CU+mJ<>#Z$3bNJlu?Zn;?qevNi$#>(dG3UlXgRoIqE#2FQjN1ZdPg=xP z-^SD`u9S(GWI6-?H~z(J4%TMbO2(#LwNP;r6hlXkb|m?Wx`s@q_&)50rW3Ux&Xm)0 zQj=SDOkq1cXRW}i$tBRNl(8R!-068W#8xd(k~+E)Cj4EoUcuRk6tz?_IAd3w;F7!= z?1kE=I&D_Pi?w_A)DNas(LIPlvvfQig8(B?EnL_r`dJoQid-NK`c`Omg}XEY8|MPtoi7djh5^WOWrADBMiX+z z9~BShGGL$2r6vyhAdRw(fQF96Z7z_9jLPWa-fLW3{B9iR7U5ziOO1V#%4I0Rz*cTknQTz6U@o~gOl~|(6(z0p@1hjVh1a?)iCBhcRgd?-U2tO_XXdnF*86d{-w3#9>Oe`pbA6iQ3iUj8JUfh37WM2=->_QgLC4kO$@Yc$z> z=Uny9A&D+WogNh9Phz3J>V%bY|LBqQl_w0WHm&(m5EeCC+U+3>+NZ}$G-t?{UmO?z zk?&BTH9)!_+RrydVO&=-MS#;UyFJFk)EU%ZP{2NFLmy$mjqEOqr)6Yurru|bR7Lkp zFDNR97k(zDF#kt~8^%A9WS9MB zR?(Uz_Mx zmKhNJAw3c*88RmGl#QWk7>?QV;Gz>m-q!i!XIyo&DAe4OG9S^6&RV=`U-)af%@pk6 zDLu2U?AiTFHWtq2;$&fIMX>iv4s3O9XOMr>_kn<#Q!D-*Gg6mZ=TH7j-Vyk`!N{j$ z;~nE*Rr_^By_a;##Ch41GyaV|aIm_Dh~yL{nNi;FB)teH5;yf94e#a@1YYStn^CFX@#JTPJcTk_#<~pg*D4lj6%jUhlT(cd{5dN$ zazp1Qqdn}UvFL7T~4ZW4ewyQ(c(*aG%^JiSJg|1!g11kEE5- zKH~iDuVB(tjc|+d4hZL^1ps~)=l}2)Z3%#X`+s^cZ#Fl2{AYgbQLP%^gg+aMM*!^? z;D@_pe;b|}{RIzjT&JNQME?t9aMk=w7fLu00=HjAoUrl{QrfqNCrOw8&G!44nv#2p6cwa%12uRME3qD+JZ$+ zBD-oG6Xk@;=t~dl6wMvYOZ-SUbp!6`sY59eL7bV2u)^Uo(Nu2T{tH<02MRKw-Yyap zGGY~EJUjlhgVB?g+$eSM^O&oZV(`}T^)YXw6iWvT!?t(B&-cif_)z?kf;L;;PP|>F zvIK@DPibZnmY0`fG0@(n;!ihLEKDsQ*C~~xt54NhOg7VMGIPBwNzDlg@ntKnT*OCa z&~>)WHcrYSHYri%D-voCl9f*FQd~zK=3h~!q@)2OhUMBUZt0SjUw(E=gaR5ARBZDs zr$WTN(NCH+>UNJyTW~%{tK3uTx-qi&m8puA?XL~;;%AD8V#BzQS%OC@n&5LB=ckp( zlc6+9k!|@+mqFcLCvz}uAfPijfm5>@M6$2GIW{-=p-i-RKrsWdAwhjQeJfET8^L~L ze+~itRE~)N+Spf~F-$lAfX(p-EsjNB3q6@baS+*yXtk-hrhKQE`kiA20F& zo9)clXFqb!l-j+R4wKpgN&U>2rBUHQic7Mm{&!K4?_GH{cc8M!hJhw1GtL?ZLu%{> zLn&BC3ww0x<2PO$O(pcZJ-eh!P+rx=!)V)*S<|b(y?VXU0)&p(sHNY@)19gIPkUx_ zbQurIIk7$L}!mtY0`by@fB^nh4RHHe(}+Oi{@sQB|u7p`5bYCr4;@2Z8+g@esvBST)s#8K%ARzWIUf%=uQ`b;6~s?K1jadBKB zfHhOlF;wzqe}Q*6+Wop}=iVZxsCgQ;oElnFi;csvYd-LiiG*91bp*zyNRAJ{pw1oe zRIUULshT&hBnQT%j6;4v(#{f z2)BKgVo?d6OtOT~ml=^}lZmrn23jV_v%9;LfF5>)aJ7#2D4h32CwxR=cVk&%)k#R% z++U<8@AR`!ybRCs)Or5hGwP%Re1BxS3^ zebAS}SFEt1Q~Zc>hcV~P}x9CzIfSlKPR;%rpbGP8^@Zls0Q{F!Rhm#T01sbVgf>E#jXoXLpfmMsTGMk{rD-V0O#Z#Ia^ zI_&~pT&c<&&K{Pw4Tlf_g}or0gDOGc9Dn?eBuTnJ#!rhsM%d`lD=?+nj)p5wWqNdu zY9#?ZISXVbbt9*yy_dT?_t0~*UaU;Y;yG|o0b2<5R~g)-*vvkgV$d|Uc^;O}RNWcz zUXTbZHXWU*XrYs;w{^SPIQMXMB^_d7>Mnb#Gw?0C+Pu(gq!T9gdbqfj!(n^i;wsBM z2W|esYSy+iAMda)2W+(I%UG-vTvC#PI*2%Wjz}UrfgzUZD^8j3tZOG2OP0 zttfI;vY>ao^D2sIl~r(mDFr4nomjE+D8WyGh`o(05G#Mcxb!}5Iss+bop$C_ww?#8 zz2U|tO+mQy1~1)al(cOF9M!MXc#BM2EYsCZDKM2DQ?nZKX7@d(YOE03MLHCAcmnW2 zWl~_62oJ}(Mqi&jW2IYf5Yxb|TgG*{ot-4*%A8!j2EBDy31l_Xh0*EW;r};K2?nOj zT2j?~7H8Sd3(;bv|6>+c&4D*_knVXBoHoOgdsPqr2Lm_P(J}XgMv%oLQC-{fcxIOr zi9IsQL>9Kv0=&7)im!okh@Hz|U{%X}pS6XpLITU$w1<=Xq_UPvOr89=r{M?ba`Y3T;R<@gl+ozY*3~bsj$5X_ zt?@U-wRk!1(bGBaB;#}DzuidtZBBRxdnE`!CBvm5_WuV-k^A%iBPof8r_m|yRzwyt zVWi`dIgUtFh)gL|?V*MPT?vOR&08$?Iik(aJp?X zBp=t3E!ad8lBupUpKkU>aax^(qD)q_7SWReA_=nQgGbd~OY)ZiHS<}TyAVO@+ zr<9DZ8@U3PeuI9@?HZ%REeO1^XC!DEc=W7?9?{aQB?~P`%TM_#*U?4EK2uKZT-I>3 z$VvYBoY-Zs(DBCqC(a-BCmJUKwcT#mB-v_jzkqDwU8EpIGkp)59DEN#*R+;R|0m z7JbM>X60PXXlmTBdq!m?KnU%T$Ia;%?kSDbon!hGMEl>{NZ<$9qYQZ~@gHV%Y=iyA zH8F?y7cmqai{LyG9sHOQpj^o;ZKPO|K4$AN$NW$(?7;gNqw#*ZoF>*x5o>ThWOe}WUA zs?VP-SIAXwYwPGYE<<#YZ7PTfFy;uJIMf{HAaR3}cJC<1g0b0mHw)lxSwpwGU(-Q&K=h%VyT})W``Gm53q;IPnqtGT~>b&=V z77>%igdOjdMv!1(%o=y&#X!z~s@XULHEVmP4i}BUV3YqsI-Aw+AdNhAZM-oTsZ&Ae z^nxLizk3#WaE~3CGMD5NDZj#aYss0*W-MQ`GzzC`W!vW?rqGJDbUSKJKe-a;owkt% z>Bii0F>paR?mq2#NWJg75l75U%bxdR^gd;WEH+{EUR-)%fddOc5rYXyI;N6IvNRuG zjrVSk*wKcZ>ATR^Ml@-Eyi;t8_uVTwkG)$6jW;dGdJ?f5p)yy@{X zXs;f+v;BC#OMWa|i^qV`wo*t2Vi;FW4IuIioLj4tcAmvx%+%ntzT@dP>|)cYuCbYm zwET7D$s|SY4&`7833p!~+UtY)$U!;nX~UcO07NGKV8f4`R`4QDu<`nU!N8Iv4jG7Nbg^oT9gjDeLl`>9t0f zz0#tKlFbSI;nha;t566~3(2zPa95RX-qCfna22ww4YYko)9Q+eLuFW(Cf>xA%IAmK6fEqg;*b}j!MC&m67P+*K`M+=;*jU)%z$}zx}WBhl;EA+8R*DUH>axVhR^UhxW+-^;QVVYW1t?Z&`%{ro2L=?J$Y)=GO7%F>t?@ zY`J{S(qJHbG(}04>nv49mveBPN*x}hD9c4fa7pMXH$?hGv%PXZE*VqX8aPi)0jUO$ zUVu)%X0fLfgx8Yg3so~L(xCtOpp5p?G)i+57v1L2{<3U5r}6~GrwB3+4qTHZ)@6C< z_=Xt0K{KjBd#1ZTYM(Q5x~2C>3l3Wp#a6fN|xstCrGSb+roLVDlaf=%($y3 z6*!z`4gQ!COKl2UE-on}%mfnMzZ&~_pTz&f>CO#TSFPny6!*;}<0MPi=UOUwY`U{f zm)g2wLZ{2rGPhz2d}ioW5zK{}!wz3bti48?`4U}^Q9#0x{|1AEu$OTU1`2!X;LkKA z?JqQx&5&)3mTs?NcSq-1LxaVDdYijB6<<@1Mko>>A*K;osYU2p_QEyoczepb1c`FStEv)98uzyNK$aIva2w}j!9BL zb$&6|s^GClc_nt;SZmVvtQ3oOm?BT%n3l+Evjku#>J~X#W6|Mok^yl``cdz4gXwXSOHIsXBK$<5#2Rqc}HH6Ir(y=J194#FHn z`^GNodzUEkbY>aZia=o?8C~5G$cVZnyIrkxA>1k^asEZlBEj0i4fb4|U&^VhzG>Fb zJx0N6nwzVrik?Bx9Nd;|IAlr$go&Uf!QFQ=I6TuUTFyvyO@-~SdxTzr9LS&RqPNDr z{e42`6O68qs>#hO$50C4X}DDDrQQ*%a=})kf0NlCCHSt5=kUZIVM=n2Q(z0+4nx}y z&}IKw4>D$ZRtDftr{*pOF$J4uc$7P10Kc0hWrG(0{ZTOv(DU25wC;UPegtI4f71fU z5KPRZ`o;-AiSoy1+1uVIoL1(pZuyCT(%-%(`kQayfM*$d0e8D&b12;>U-$tbi8H;# ze@f#$qriypyMN2(*ug~{3J^=pJm>XtYe7(AV1R3mJ)FqyiT%SMPN+VDypL%uF&lqL z;IC)i$tUp|p)yUosbK!RMgcWV5F(|C57ytokimtxxmu2;b|}0d(H5D%Ho49rV&uR| z`Od75D2#9hMgNZFWJiEa;JNN&%g7mC(DNko;D)?wd7(U@ikaejJk$VC&mPF5iOsBM zF?fR_c89-!D(sPsSpH!?iFvHA>C>`QMl#D%Xy+=!n69TKSftfNcwG-IZwtGXX|hzv zfb+xL!pJTzm{+M5kz37}I51PLV^L=HbmE0zgx>V<*g~!r1zq_I(4GE1pvx$jx0NQp z{SPW>A>7hE^ZyCGulf7V&9FQZrfqyh{RbqJy!yv!{E(94|2HYYzac-CjcfT#`#~gR zLr(enyHs?J$-n(rc^yc!yszk7dZwc&L8|= z)FAVAzejpM7O1n|Z8`6ct?lUiza0cuwT}Q@85VBe{&R{~-@U0@y`BJvlzV{v4WCX9 zfb-s$P%(;)G4a%kMP9PVR3NH{n}B=);E`L@EuK4N;kQVdOO};X{Q3;|=2NU+a#^$J ze`B27URv+-1MKz(J@{K(^gj4)N#fE5d`uSNQPE-(wVNj4a16Jb3r9#)x^8hjwv2?6{`#@mC9mE;%=R8I?H zzx*!t%Jl{pvSnRfUy&QVPy_-+!d_JIV{y!>ve2Qfmc1Xp_{e+LJ0+o?N}6O$OGde7 z=5j9Kb(|!x77Eh^dVF3!KYdw8IJ4F+iwC_ZVv((r4TbzivQ&OUG-eOXZe<#vmGN~Q zE+%8*MJLri)S27@ zb%annVWLREUA?bC2BzDVHn90+Me{t;dk-|wzxykA^dQ|!*qxzRNa=AccE!0ym0RL< zvYa<<+=cy_ee8lY`5#fJzCEcV280EtvB?3|{^#Zw{KOmzwesRntNpkNp?0cI6RGaE z>shY$MiqU6cvY+yE{2u%LecM7zdwGZeyQT-`-wsYk>WBMKBYq$Eu%C<2|z5U5n|@X zk}Rw}J8OU{-<1?lPwK;+N0)~RPxxPO@pGbS0t7L-(uREC67O*JC`@YmW8nB$t%r1}&jxZ$Iiga)Jxqu~mOlGaBW43d0g(I>2$?S=``e_=q6STjeWp8q zg>Oz~QKBHWW{I(aQH56bQcvgxMfC3#j(BfUR1f-4%!9aC239}Ces;f@L9^;4=42d( zty#p!nVo1My1oQiIWQzFQNlk={qHzV2+JCD2&F~h;k09VMQCpY5*d6B&_eRq(0!5K zyG{(>C@pP=loP3KgtEfRzqgPc0n96c~(3L~V&P=+`Q-F!J71bRq{MeTCxbpUW|6!h1O*TP&_U zL#G6iJ>mJ{2?6?twx&^)s(c&vV^@oPf4PNI*sH`g56aA`HS}^e zcIfu`_tn8)a{2+`ccB3m>> zE8{^lifg_Z>Cs9o-0POA{B??I$4nm%ea6H@I+6xn2L1h#9a|kf2`?0+q$!i1vRlWl zMms%)8`$7YhC3SlF9xBE$mTSnge$LD{zc*pJ#zjHAsnilV1UXxwpc=Wa~S9)UaSWy zmr_~!|92m-p3W^}%! zzZ8bwz8~a&Q=I!hvDztM#rkx6a*5(D+yDq1HfsSMkL`v4z-(T`=Z!N!ZlnL~#XoLk zg>dHvaJ{c8@_n(ep0SZ`@a-^q|8fsd5cqi@er1dTGCv%Qpt23VWuF|b48C`U%0fOp z^xVy@Od!7e;gI) zxl{n)_5M2M_r3Dp53!K>&I|mYyaN=)jAXp_mf^$G*5L#ywAO zOp_xq+VD8XraoA3jCY|WB)SB?y;e%YayU($nP*Uj zON%t-!|Ccjk-=u{Obpb8%bRE{YU4bJy{z37TKp=rHPw-@I+hRQX)wRODet8Tk-Q62 z#Bu~Szo`n@V#1Rffv!c#{fEN`*f#KDbxsJ8(SLeu)xnS*@7~P-Q$_HQRkjP~*Ol&U z*U{%nk3?q!Q#zQZE!oz1v%rsO#VhBArBARCS(%Ww`uY*AIDR4Z`0aM{@$E%|~$v z2>$E5JUp(EdsjV>_Dd^1wT6I0KJEaPkMPgAy^U_p+r@rIMfd&DtH(9ev?))k$yhp=Jo;HBljRPZsN36khub10t%)s^Z#tQFCGPoyY4wz!B2& zBrU1CH}}@!_Twp6Y#96OU@Hf-E%I$?54Y?w>MLo5*C7XZm0Mtqm&e!V65(VlR#0Wc zpSRRKw!4hdr*`KW@2X4SnfLvD6`N4nZ-mmHca`^YaKr&I=U(uJFem#OZxnearQ}naOJI%rPUov}V}rBI{-IOyC)GB6h(c!N)Pu(cvrt!MpvI*C>$U{W~QnJ0iZyMG98 zzS^os)bY zx(dnOdl=ilYB3F-cr(X#wvsBQODyA5}pXGSI~gcV;H5A%7z^Qph{%B$}arJfKnuAY{b68hr;UBu}s!GQ~guafouakaPN%K2}g8H7P~ z^GimN@{9br;Kr?(ZB6#|qDv?ebU(%a4%|5!1t}imjvlUO6#e(F=X)MFbcX?XqG12plXl*uwMvC;^{L zdSv8o^n`5{LTWh(i4`Zz(c@NQiODgW+1CL%JTDr&5z69p=kK18AKC$a$|~4!x?7@a za&N3P9&(sqi+Rj6(~?*3j#EZty8b;e3#fckZ z*F?!Qjs%|9M+gTzenI*r)0?HF7(aYeS)iJ%ghXp!!my-H1oAWyk+2!Ws(7-FnFj)|8z}hFSfc}e+MCDnrvD%bTUB)Dm+Yo$$xQm)>Q#j}o^6*WlG>*IlaQwy2V{h% zC>H4eRcf0JPfmE8c~~8FYBUoc;wkBRqgXSfXSRBqraIfc1vZ+tsU=5&pakg7k%KME zz+svwtVu)rqjboyU>Hx9nPDv$4GHl-K7naa?13&8(WDM_Aw9%)93uvTXD$G24qaoz z2XFD>`o6))G-J*@wkefF3u;m~908o&n)i$bweXsvL2Mf%p8oWT?MlU*GvG8yN@JLu z+)BbM*5z87*U7r*^{+jxS|$PdsYWR+dVSr(g7ESlrP!hSky z;cLxp%UUM1)vyzFwaWpoM(g_%Ft|N5w`0_ce~UqU<^|&&yYb6K!F1dz8r%j{$tbLF zXg4zPjo=nv;MV2nM~B)3mG)oWTnd)i#yOSU*g|bb$`|8|2^h|DFjcnq@H-z+pA=h% z{+V7Y!520tc#QV?G~ETZ3A48JIR5_bOf_{-cW6XXtYjhAhy`JV^**SL%_ICrvj9Rzojb6LlhsiNkAmS6v{BR>9OtW6P6dk=&AFZB>;q z7X$y!Lgd+t8*`1JUHO2>Ue7}$(ixki=Vx?C{^r!OY?$v1I+5y6Lyk(x9TM11{ zf&FK3lDA0HsLVcFXLs9c+lC%5+qsv7J;M9gDiZ02TaCevzr<~6H%1LQPQ-U>k=r(x zI2iSu(EP22lPfHN-4m`ckRii#n`fqXXV2s+2P`ZgKOAneQnDV(_>(rr2J9pb;)0CGi%DBCHziZnFjgEbzh@!adCQ%~?rSoqlg1vT;ecUi#7~p`7L*ES)5bKY zTNjwzNYZ0d50~HsYmp8i(AcbY56vcDbvQ7bVxnZfE6*q;;G?=*h>K*qEK=|fbHlIF z$>XosVv$|XO+GU{uen0FrGf{#7Y3b(0cVbW184$-p(vTHrYCRzm7C2{7Qgb_bh-hp zRdZzHm{C>5UvB=KDI2yW54oh zvFqlQEXR2>LZX2juWY-n{c?7*jKJIHbujON5m+E>NrtBdTQhx=gb0X%Rms_F_5kx5 z)@@DgMhz$iH~&e~24UYRNl=Zguu6ZcuJLa(@MCN63eWRVM?x7|@a4i3PtzdE)42%#9Ys+4L{BhmcnBx30gG5y=yecLo|nB-Ervz@3P3;+r4ad@L~I_V zit%!?nELVvPLDxh&*$V6%{Z!bA?$ZL2-DyMzS42?k{P6DT*w_(N%i)*m`wj4@5=J? z0O#I-3$*jf(H^ujr^U*IgV2hAUF^r-jobvK4QM=eY}6&y_GV0{Wb!Wc|ELJulL$N& zlWDjsEApH#HaJ=u;9DYu@qt!zrhG6S4oZ++S;@rChP?+>vsH^O}E0;#eR5{-#MHRu6w;j@)Rn9JdlJxKUe9FH}1bqnL? zQGFT^I2YlH{qXdMGH4bVSh-=NN%;Hf`*}yoVy5eoO_^#0ORjB;mn8drQcilJ*xsc& z?}{hD?J8AS`$6BHOK{UI0IEBAJ@-BKeLW2_ZebrzB?gAO;3rvp-flNOW7Cl@ z5lPnemY-sW;Q{OPkjeTUWR3iD*nw0a{jEy@`J)Xlxzui%2pw{F;XS1l(xi|K0Rlr#Ho6ku{F zn8*M9Qs|1y`VUa!P`xz**cv+#_C9Slz7zvRi{CRo){6*pXSDhWjg+{cXcg_EyTf=Iu&zOa)iCgAVf;jYoJFs?w{ zDlF-R{*OL1^+mBtldB*nZ?Tg+lQ#Wi;r@aMHY9xCGEnH$O{`pbiD)_|F1a|qmK7}2 zg0UA*Vi(2xQ2{+XvujPTD%vpcz?jK+NzR}PHSH%($K3@RiC|Yb(7sDsriVpxJgCmI zXOl0kzOUTkiJJC%1jfRnhQVR@36{aCOfcAx?96?*W$xhybJB@(?uT{D%hv4llM2ak zGLo|``Xc+KA-b^A2oJGB4o;B#FdU?~NU!Y{lHj0m zPsEYNj;w=06xZmcl%$8Hs2;IRP8)ke3u2fX-<1o0ZROUhog{#>gM=l+ z4ne;eto2&tJ#&XO04)h##PZO=@GhFS*`Cuv%6e!b5x}oq7v%5vBvuBy%6Fj=pcZ;}P{T zg8mUq%Q_nO!>U?2Kx!sa(sPjnWOYs19lBvc2nvJjw=~}tj}uS}|K$4<5#e_D!uS-P1yqDL9iYq7N}PjF6d~E z@4od2fI2)wYL1^X*=HU)Cf<;%oXgk@pTKk-8#%`_!B%)xAH8eXhsG}k*O+b^aB}}j zePToKM2AJp<^BB&avUh}2Q?%a)fo?_g)pw22j0sbqWGdsTqWD?atlf93#{e3tVDt* z4>_{wI8wpoNMhO8J4q587gz zqv5mh_Gv(ZXKJZRt-dEPVm~oIl~FYxAFLbO&W|@mcLr*rlnjFo5&>Rfr$&z3hwY+`OcthFO*tiOs9){1i6`itRCqTk0f z%CBB}E|XCI#dtOcQFm;^36BTWYw%=UTM`HFm_tj&E8Am>GMD0(kz`X49SBzn%~Y9f zFovtn3Ta@>|8*1)nfDn78ScDHs*ijYWUe-~67awLid)D5?d)2a+`neGtw^__#>vrv z5Sqdr>!TEF3S z;SxgZlQ+HcF|SU?4`AX2=S^F7EsRD_xjiocp4^0$Y*7^ncx7?m6D5%ZP1hdg3U2EN zAy}2JLQksYVkP;ja4JGQp^Y4%izg=FZv0!F1@c%s9cZ*FJ68Eiv))J}ARxp6k``S*Ib@2-ZNva1$-Zw5?C zxyrv?S5Fbh3{k(D#@U;zQCC358S9TnfK$d>b?cis3UHG|iTm>H@A1(70{9a3FFgcc zxA~(0YGn-!AKn0S?_Xbr9`|0SJa+?v)A0P$>H??vKF+WAb-;!ix5Xr}-s7~UmeVz( zw{D>uvRvg~T+u&cf4+gpMZ57{vCE9mfx4~-ad$|uGmo0oFapEltZpIRF7uJ$hg`0) zHB=E57WoI)NtMedJ>22OtJ)F4hrUo{QI}%(x+173!v#?k5c5TXwxo6B#Y{||+m=eB zeh!<;+vT6s=CwE4_?~=-9dH{jl@Cc5fMVpq{*P^91C(4}f4T#f1aEXFx=mk2uxQ!}Y>LD^) zBcXBr;*&EjY)hvTBg)U*dfv z;~vIxD$}md)F!GQx1pa0f<6)30pF_J5#L1%eT{z2B(F0&(VcfpRzJo+hr5BCSeK4y zU3a`>{kpoY;b0W!CM6EcY2`!B(;O;m*}^fT;{i9JZM4(nuF@lEiL1;(y};X)ZbOlQidHartcY$@pM@`k#1vhvl6+RBadkL*qP>=vSFX#ERQImU7t)KR;1Q)<^7Uld95KAYI-DdUWIWWv(656f^yIfPkC5j& zAbU?Tu29$oa{hlzO0Qy%8_&E=UQNfR1Cr?$Fq2EWXvG^jKA@wUfths_teUNJ zGwX3|6&G_>a15Fbi0GN4Irbwcq-%QxJ3PJf4&5fuuw6k>VY8aONS{!#0?F{U|5cy? z79j8V=vGldek_{*gQ0jBV!w)RJD!`JdO3c!^)$4@AvYCV059imB&46kYElr};vm~i zH|pGRd;{ci!!zK{@`8uH!nQPS;L_B!$+qTMD_V5%260>6IWAZORP-jFlT6LJ$T{CO z$m58mj)Rz0>zSF6VW!YivS#$a6zIZ|Tx;HMHO7RPB}dLaiRsrIqRf~*)igKI5a(ny z!KZ#N@PK!ye7t9MTY)T+!KveJW`LC?S}GDAyw_^{`p7>)Q5RobAHB5B_Ui&Nh41cE zeMP3SCZ=~nW0w@(wfB1%v-62XR`Wl=ML@Gj%w@i8Jar$9vA#UQ3DZMs5aEO zv^J20|I*W$@Y^#le%YPEhyg*6SIyp{h0144GbCzY6&F-$Sw{$@=x{u& zfE4`L0U@cs{AVkF=)a-WI|Fd%%=_VVf{*`;Y{}jN8b^8nBaIe{*KYs@fe-!B4t3-x zr<;ci1srz)1s-}S(y+UVsKWO=7XbH(K=OyIZUIFsWR5A=NbLh4VngkUQmi)`F~SRk zT)bIA5Cb1ab?QgHbolLwoU$m+bsWNH#9sds<&5s25k)6rN4YYk3Iv?siB^2GgpsZ` z?E|MF5s*Nwjo$D-?+jRw0DLM8XQuZcxJMzE!HR?{_~I980W%yN*FUuJ*roRyP#}2p z=C5&`Hwu`^<-33A70}h)X*&UQzTSS;b~^0jcCT`;eLYO~_wQX#kL@YjbFA(c8bD}C zix|LY!Ptd`-!2aA9_=yc0iffjpACEYSADu)j!hwNK%Si4L~^H>$nRjBC(n4P;H;)2 z$fZysD=aI}xTp$iTAy2T5_RjaZyLPl36KZ@Z=Gbz>HdwM!9Y#RgQ<;9uElVe-;=#7MN3Y!?GHU_S|gieP=wj{8!sanzW| zwV$Wapi!RxPD3zLx*YVFM1;Y2h?7j;b(Q8MyX%ks%NDR3+0Js>S1ziQEA3V2#*}2C zR;D4Gy=1v6#!O>@+DDdzXy&}=S7chfhDXfXaf6h?R+d(VlR=cjier6e$G6Gcy_#Fp zH;Ktq&+Gku@;k?*BF8lswTu`TFdO(sw-v>jH6^A8ykQjuS1}jrB7Kcu_%83Dyu^M+ zqs^yznnnQ2ZM(5j1bzxVbR_W{$IG_$rZxB-=D~0LHN~O!U^!T zg3WEDwZqieI5sE)83PGnKq77J!?*HeItTVIFc~VqmOq#2ZJYx#K;odL2pPRzE;reO zbM*wN0m;9OBZ?tG#dgRT0@0YO2@bJ6`!6aNLFrat;Z(#UD08_aIIV=Zqz!B2_u2T7 z*k;MDybsk)$yjTS}3}W1%!AjSAv{t$y&z1OZursW~vE|4>4k9bB>({jr|_d6LM$Sx1o-@M2W2s7VTh zq+hvyu!-#uHKE-gqMjj=+45oFth68|xszV3I#wM!0^ig+Zl?J+5R?v`VC7h+UblnD zhvvBN?UzYSvhMD`8B7O~$wI25=l%ICsr)BmASTJxSO*{y#n8uALEAot72o`!K(^5WcR5Z6}o zWnI$B5qqAMeIP~ttR$z|&~%dCCzZ$>Lv54_Xhk6DC9(=|tl8%T^aPj13rOrtMN0&7 z!NI08l+r-&KGi-pv-`Ap5K3%;fA5Wb$r8HA2K(>h$ItHVKX>xE zxBvd3KU3O&XW;%x{Qqd}$+IV!^Pk7}_TM}C;I0R)vS^LNNhVJ~NdRDhia*E0D_F7d zs&2J-qxID+s^46g{p~;@(;>R8?JKfJ=fH0b&s%^(p2^rdqv$spzXhIFlt=R|kMc{3 zTMjgMOD#Q2+FH#!J3ZbzJ4WSB>T4%^M{jlx&WsW_Hwht|%`b$lF)f3d2sz|<61h+X z!LDn_Z|MDH)f>b>Ii3EzJOYAeSa!#b(?Z&~DOu6JPEd$nIDa*X=nKw?xGvxb&#Hx*<1@>w3S4Hoi<_+a3&NNA!XF zbv0m|k0o6?PGwOwI~8H(K<(DCJ4>)l_nLLEe87M)gho<{yrlf1=W;ViP1u?o*Io$G z#Nfv_`I=tj7N0k3Lv=j5Lz7q@(M?Tr#q*owPz8=sk{uyBa!9;BWWf7E6FJCTKiGHpzw#)jOb^Z_pUTf#$3H7@0P`Nd=vY^tMv zt1r9y2`sK?RFyQ{T1lgJEGn}5pXl1WsM(CsjHe9b=Cr!{Z@VPr3-K)y*{NymtZq+; zG!w!~LMbGw;Ux{P|A2#QG>ppPo_#!LRk-DK@9Xx`teZ1-_6Cb9au0hhoWm=8v9kSJ zuCl?3@ewd51rdwH5zdk3{qlXkeBUqMc9edvvms4?aT!TroKXCMPzl7reAP=K-M?Qz^)^*0 zDm7fVWu*y!WqNM!f!ld97&guGLx0hA1)Yobk+(d$#$aDBo)rXTB*V3v$bOM z#g&!9nGkdb7YFau@ag5+rUwxV!Py)4U~~L39>#6?U#}VGh$TChO1xB-!kAXyh0yu1 zIT4fA6gd3865#oC0)Tf+QkVTncS3)rw!q=mfCv zTji*cvZ4A+L*noC-{M8#TTW4bQbbn0UD?hzdJu{nvzpVG&DW8URkjK^+p|gq+Fww( z4C6E&en!C}x98SElk|I9l(@8$j1FN0@C}VcKw79m3IN|V;sf%>&+s3xz54o`W(JdL z(e1-l4N}0XdAn`uLKGIe5VwrKvJao3pSmp!%(@y6Mt@Zc3X_?Wh=()gW=SSqc9kLtmL; z3pK^2oFik+3Jfq>W0-)T(5{ISCRZg$@W{tb%Ee=rFdfCce&$Gch0>j!m<)n>MfMSCH5tN$FQ%o*CP zIf;Xz=1E_NbXfXWapCy1!VfPwV_Q%wE0UYma(OzWD3@2Z%`AIYu)rKkQ94ZG+4RPo z%`p=qhI@i_GP3`xDwWwi1o<57ny~*!A1tX24aNRvd=hYTQDk(>%E3Hw=E^Epn))J9 zRpk2!MHcvGl@r_tdgkyMSqs%*1@Uety`boIEIVSSde zUo%|w*)Pe8Ysv*1Hj^glD9X2yeyN3@UwP(R?9XAlas?57`p1_7d(osmX&8B?TeWTF z)bv0<)s{MMc5IWCm34=j*c)r;Rj|V5UZ){r^z|271#DUP(3mMujZy9dHT&H`G>l7Z z&mCfw;9&M&Chc~`tU-=l%dR21po-UZM@_%P;zib)Tw{%An30&Zps}qfnYeC7!mbW{ zr&Sh?=cm%jblmp6rTcx&h-K{?NoG59{9iX3+)n&YF#d0S?f(AXyZPM5|NWsqGsgc- z!Tl5C|DM51FaPtiwfp?9ck;Q9|I39EaK(BZ4ZbxjZvo2O>e7;UuUT8XK0Q8x>|Jok z-??(U<7h|Mm3mM-qVwsRSaV%^^-vuoy-)f$Nu}ZI4@OBh`3L3EkiRi;oFqC(1<{5i zemr}hfOK#fh~DWfJTfQ5!}xR3A3Mjhm_vh|2M6H&76SMy(@{ay%qpj#3`$)P%IB%5 zdNmsYG3?u0A`zgLMwM51!tNnE7 zb3TltPwc#phDXwPCI*y0(-ELIiKyi8yxZc}N2!;~N8K@7qB{Wa z9TfjC!5y5p6+j8{$E3ktz59U4Wzwm;W<^JtP6VmJ>j0L^!#;>SywLo&N#SK*En@6j zVYx8DZUK3gh=Y=)sv%aL5d2hv_HV+?U!zZDHty?LVr_EJfP`+a>A9%}$}d~u(S}%%k0t$d zV{us@)<<=*@qazqsKaJwPHsG$lynzSdI3n+#oRfG02O>H*WL&gf{{cqmb(&ONfInd zWc4}~PeJ)!MV!*Q6zi0MdrMT-pDsTJtNv55EFQ}T>l=@&bA+aZZ35q6OFM0n=!4$!}cjnw_AeB}sC0e5`9& z;m#&PEJQ!6B+7>Ka;5P!2=OK!-93YVi-k}b0WL>`CHHw6gYpC)3 z5)*wx5tV=f!;bzmQM&V?@_gevI;uKn-@zupe?NC<7EA;DZJxtbaLL!`ykihP)bo-) z+eJ9jM1CtLeVq$<%PIlp!&HxKvgyn!VEm+vs^GT3RYgBFay}%ZlG({&^cQf5!g&t( zW=poKcjtbIdL1QOM=s^uF|FFFIW!iJ!d(Tvh8XT!U zjPMvb%2(7OQ`W&_$8$7OD*M>&tnqpCTi@66ScRD}cTpy)w=&Y3wd#~}VCK5VAvNUt zlN#ZY=S`Xg#`_hbygV-o-XjS)5QvNQ)hhNf?u;T=>UP3Byo%Ra!cfb4UV z{9b3gp0N0$X`5T(C(M#-oEKS3yJ^0xsXj0j44m9cFppxZ@B|+A&ib^ zJ`t`TNse1u5y26u<)~vz_#Dq{wgl-@wnaZ~5knPCg_Uh;kf^#^5Mir@kmu^>VOlP` zj-xZ{&XwU0uJKlxju$&`-;eVWg1X!6-Tz>L{b;G3>Jgvl%m zPvNpdU?&i%%WJLv4&LK(q$q=|$O(nXI$PMyVr)EW4hMQ65&BK$On0daTA7(Evu@-O zr;U|?wG6|CP`}o9-%we+cm=i$U`s*6BfMKA)@m)nRYvMs9S}i6Jtx;*(e6YaQwSJY zG={!KU0Tj3Hi>~b{a)wFniyGW0)%%E_TZ$)M4m`&D#>Uu6>@_#x~k@EJ{O<~we$QK zj}FwY{T>a9>n_g+3hEihV^q=crZwU%xNfiq9z%HPAhcf-Dct`S(X~qJ+$_eE_P9_d zGnTEnyzJJ+tERfq6=0woPUNWL{Irs0f7I`SOA$1{CMoX%f+A4oUq8q;Lv3gDc!FiAr!LR3tC32@o+JI4f4;c6VSqb@G;&8{cwd$6KZ0 zob-Xf)u_{`7-bjS|^tmlG?k=TJ6Dxw{AQk$nwK)N8$ zI+AtNk`gPhgajqm1hLLkenXg!8nXflm%HJ1ZEqpum@XaCO_tX`)Ln`r{mE3*4RW14 ztmH6FP#2!G%i=1qzrUKax;k5{NvrCtOL%7aHXBsLvo#)c61)V8bWtQPhqd4w#(Kzc zJyoh;t}QX39Bm1w1y;;e%{UcTi%MBk_SMK$`~w3@`C7Y~R1VSWB))~zD`EmDcH zYHYWEhmvaxV=m}$2Zp5DR&6s2G+#zWofsbim8Ep?0MK!qU}xV6Y)YN!h= z#IDPTLdvd!d>@Aa_hzt#5Z;7(Z#&ti+^SY!GhiE|{6hlG6@gc@k1jL-bDlE&)#rR_ zpHAJt63bioaCuoQiAT@4aDmr(fq`@~QVe|!q&X7( zVN16Dk)ndo8)F#%eVh!X^b@;;ItF5cFc+`(kB(2&en|C8C}#|Sdh@J4;;W_gAzgq* z&zMiCN#XFS8m{$CYbuhXwWEgsx=amAI*nlu=nllBuFZzMbL4@Y(7pyaJ+cg~BE?@) zBqzBhNcc8Llf!c(E^Wn}uuD@Q!`*Ae#<7_a6-p^ql++Kc>Y0>&Y(a7I6DGl|g&lSN zWe=Vw^d4t{lxaZ*NX||H;i!O~n zGllpt@N;f@Ry>=A{j@GJA(S1pUJ4fHsBh+KX2If^2}Hku@q(*l?fixljJo)O4i}B! zuzm!gLsCs3fH6y8TlX^9hA`PG#Kf?_DZ{8sIB_qT5(||uV5vQYF!`Vrd3dw-3i*nYbWXL?$_@VAlj#TRGOt~XTZseQ%vbW9^KyD-&X zYU69CiJ3b83m7Ef4voQ}bQ{y>e|ft0=sy3;-F)uzzx<&;Q|5n}f%_-%|C6<68_zuc zfAaLv{r&HE@_DGTzli5J$!dNtw&%PTs0+By$D^c^jIQ{jW;$vmedkG(rtxs(6&lI~ zpFc|a@>v{p`7;SB{?d#GvM%JB5NEHCch7#_JA%B4>yPCSuda%pcbAvN_w}{8hl64C zu^WlYXb4(4BxFlrKP4LC@ee;d)#vy6sVXL-^Zs}QzEiF7KPr zxw&Bi?c+!NK19H&<8&3Gr@;sYT7s&$#xUF1W^OHN&m87TYVv`vHDSWF_uaY zV&;}%n1gTeP=%8zv@G1~!ZAKVGA%d4Iu17JNyRZHHDBt66$PZx*o9p~v*!I?_aAKd z4<7js9(xwYCTv}s>q7kVpF;iE;6EPmACGy=?j7LjKX`H@zT%`SXbwz#xUC`SeB7aPD0Pr?9zjN=KKk=D zhsglDi+)fSqtT$Tx*GTDm&vDO5Vw-3-XDHkgB&40JH7dmuGD^W}BL; zt1S5IJo-~Uf4BC*yA2gK)7ui7GBgRt#$_6L^{S|q4ZoH3a4TzHzF!mn6!Pz*wpf7S zDv93Omt9qH9V!A|+uP!4wOWYvt5?NnKk{4Om=$g8uPT|AN$Qenzi5M1}wkd<>(~S@e=g%N&$Gr>VIzsj>Q;aGVzNP<1N=u3%k*&laWq+ANBf@{=ZtUPWb(M}-igp?fA&#JhL!OucL0RvtK z%v|kpuL%oOUArM1OVvGgXa*fNRAkpm6I?>#R*lY&qmck-*pZy47#x8|3P@98B!Ndg zl@+7eA6(V_l~c~`rg`=;9)Yq115$%GHObbp>(#x(SD`7#5=C=O3QLe57)8DWFdp^c zv)iHp@(D3~>nqou2ZjrRYu+T%5a%<^9xv7SNu1#sp|1GJfkf^A&Lb*DF--=b@~o8e ziQU~JxNGiH`>3g4y}$%8@O*xr5CZo;*;#(wjeVvU;KNy3f z6%M>Itj_5nPsf-!tv&7tyrI5@_^IkA{ZFyf{or3BGO2H=&f}}JLNEE5b!OP`$p3-Y z&kX8-8@P+AdKG_QYWl%Wdt@?SV1t$}_=YOrRD?|=sk#JB;iVoPp8vN*I-GfM0vi z%%)!io5~O^FOel+`b4c3f~<)79m=SK2NY&e5Z$pv5@J*m^c0EIrRAcJ-}^W)~<_5>S)rY!c{MCS2E2USM5elt?mQN>a?yHtk?;M5ac@5X&q4N>MHS<_T$X*d9g# zx_6mC8pDc1Ar^@yWD&tvM7Zdq)ljfAEUk#!pD63?YQZ6M6hl+jgq8sIW(9H17f=);;4D2oK1x< z0LXPiOMn1_lMGl;s59N5oNCbOXJtDOG=j|#j`mK_Ypx@zm8@0``K~YSt}@ubHFNaS zJ*MeVh4IgcY)wFuHK*^cw$+tmNSBZ)n^mwu94Fp~2U`f)cR#i?rQo`xl1cL6c7Zh> znk<;pV=x}4iXObh&7mi<#Zdsl8>s=q?+TOd{ZE`*jlgXBVsi1af@#}oeJDgW_||M;H&_<{deDe4z3RMxH%wbas;+o4erxD&R<^eON| z60a2F^0}1Fk8+o<#dZuA|*oGh&@+3C;h6LybBSDo0E z{MIp;>aP~n9}EHzh{IUn@N||`Xw~DlCa1W~F23b%-5f@7^MaxgVJrG93VKtIrMa@aeLx-o#Vdan8+H9vnF>$Os2V42E0L0ECPlaDw# z{h}>8SfpUy6cdoIMQHvl8L>gi8zq-qG*GtBp@^&O1_ORq^v$XPE?PYZyLb7*};_Clx7~w9y;4>;Tc2# zM2=Aesmg=o1QK~|gt22&hgU)6!gDCiS~~&=nk~D^-wGv+D5c!}@JkJ3MsMR|5p3sy zv%${AQT^#r^78q42j+u-QInF8_t>O4cnxKZ&++ipu-{b{9Iv<4O}*3;jmf34-*a1K zbAWGULnyQ0yVpB!=M_?DJd@H+uXRK?%AMouB*jn%xxEt+9_c7+B_&+PZqAp=cc#!Q zSvET+^jw?!)EVVx6}Ig%**hA2ld;e}Ui<8ZW?Lhbf2<|~Cv}2v9+3v|xJhR{k;=qv zOnA2&6->B%>#312`Ra`~{T>qq#y(Wb-SNW$^B?~aE@@1wGZR*A5iH+}*NbrKrie>& zRR`LZ+GTm-lE~c>aUn^xlOEpSQ7m2gnD~;_U61bK&GmY2VAk*Dq88Hs1liv^_zX@VEM-ae{@ z$0oIuf&vm(;>76uZT*mLV!K@CcnBpVGYT?EHUYXLIL=imJQdo?Y15OidBWLaln@X{ zf|-fJCBebuhVuCX%4a&DoIyOR1F`aDvwgefeJmc1@;f$H(zwUnp`$UCY3yZx3_%%A zyZ}0O8>2|lOW~MMTa(q{*`|4}7f`e-Q+6zR;cf+OOXh1}5Mi=$+oynpc732Thilq; zDE(Fz^`5}PRGt4Z`v=9oYs5Sg9}OnGb2-LZK26GQXOP%HzllG}qL?4lrV&RZHyHw^ zlt#g9VX+iSxJKi2Usi^TDbtI72OKXetmcorRAoi|=q5e+vs#XB1-Py1iqV%-thp-I z*D^?GF&^A4i|72rq4;26dD+`DKPO88SunQ2KoHOcm_x$iyjXT^OME734kogs+*)H2 z)DfgjLpJf96f`Q-#qBLN;8{fSNkj-UNc49}j-$Q;LrqdstUzzG^q3)sjCP>s7r+R< zI$B!-J$=6AG-+oSCpQ;<5%|11ST6r4!Ix5c)Iw6KMGWbZ895_>(s4d+x8o8hDCQrc#6 zxqeN4y(t-L(?`&cggZcE=@vU23Ybo6x)|F}<{iUXj5+9;ajtox0~!y-Q1r8IZXn9# z2l;cO+%hMphcc1FmIQ2(P8|vjaGF`Sw^p|LIH@}FR8{sD60*8ZyMz>(P({|-$}O~+ zQ!=|^$F5!$B*q`sQQe;$_oBng-q-CmE&Q%dxb9xvvsDAUi4gNvx<-X^2FOkt;x4`W$W z%#6NcF!dXzWx{rj=f>f{_! zJRw-fQaN0bNG_sI8{%K0oQchw*IYs1D^xiX@-Hk7g!NK$cRdV_+;xCf)n@Lv+Qk=!flUT;|Xb}kn zG8-TN09iXs7I<&@0;5wEabsKiy>?%At=(69{&v5PV2lr}eXcOT`%&queTRTCs13f5 zokK-M!uiA5dDQ$=!J=+-f=y15Iq>n153Cs*HcO|yQ9`~2Ji{HfY3}Ql>WD^yF1Y2P zk^p4Ffs23c3RN84QA1aW2zc{!^Gqv|L^N!vf>Qq-L#mAPD}6e0#ajaK)|~Bi63uyT z3EwBWz~)_uXqo8=U3cmoiBL1hD{g~TG+xcI zRw#dx5eKtSXZF$#BtwM}ww4dgkhoJx8#$TXdHwt4^{4oMvOA{(iSLM8iP53>JE=qx zZ25IQ2vxBs)ys^4!=SJF!!9IfLlN|v#8v8Ey3VV%vA8V%nys~@`XPiA?m>Yo4@^$m zWP~qZ5mw@!a&GVqi7xZN7LvT;6oE|ZR48y?hSeDsV%6A^Dy~8Jh@)fkk^rN1{?yS8 zNu+t0Lq;%OGmq#XV1a-#R0%+*&;R@6+4|aj{?9x4+~@!OLw}~s|2qTsPvHOc^~W1)PkjD={Os{P z|KG)@I0tWD>R)_*IRu4iVoqR|$G0#CFv}F&ZpCf7QQ-7=?`-$&+mnO2hr*T|3`U(* zRHFzC;y}52-CV>H&Ni*ec*We=2!Qt-=*Ro7-yZCp?Y}wMJ9@KoaJF-BaJaj3a(KjA zoJZO}hfv)}UsYJYm{)BA^eMp3J(LAODdtxuoW42Q+u8l|&WnRR6L6>V`Uc9Q$$(BE zq|Ri+{>-yY?DKz(`mH|L_9WgtsgYskHL0KXDZd&_3TAZzjUm977(m#4WH~fU@ZoHytkCj55xc_s8tsTr<2Oj zN1KCALM@?m)6dQC*Xpu>{J%>6ONmaPQSz{OIUcxq=$ijXb+FCq>aPG?@4)#B+<4$0 zv-KcWur5hXuG%Un%tQIEqQT(y6h_NH!RCAMr3&`RCfdY%;=0Hy#*?YaL1^bB1EglK z=Dta~xKepdY4zrs{#Y~5>gsjbF?6hL@^5AoAIOI++O*V2LKT~C@@C_OgFa!*pgrRe zl^r%y()H__WmL(eXD(`OG=6g*R8%%m%Fq3hPUYPqq9F$3^G?zf?sHj@p5r0@=VUk< zM;-B75JtuBq{-P!7guS4ylbG{ltKEryB*iwlllf|8VeQiz$KWj_eE-k9(hb{mY*44$o;dNqV2-6c0ObH33)DzFbDS$v;pa zn$pBWDt}I zz=bTBnD?+7fyuD023`u7B4p&e%-vE}>2C~*a|2geuh+jd1aAQf-?IF?b8xzcdjO;S zzutM9*-hw+?J4d`>MQfPMY_>I6)^jaqLwmnkZ|oiX#nZpl-5Z5P^!F&L<1Z9w}$5} zKv5c*(@p1x&L*$YpPW^2)kP&ORP#5tDmz_O_07`a%fXfH;9_N}W$wiFT6R;(T3u!o ze=*ki+rmT$cNU4nE)hY6`y$$Pwx=+`C z3@-masq^0&lD7aw%5i2@%9Hl8tvCHqoHodH?F{}PQKY~%=(tE~@1>+GWxkS1%H>7e zgSwG^EtUCH(iPl$HRp}3{l>lJw0x2Em2eSoHx*$~F@+q769DL#G$gE=RNIKG<#yt2 zUw*qXXnSp%$8wKgctQLa;1q9T2=z%yT)n@#nd1tLK^d^PINi zvt~DEyu@?Yr%R2{b7#QHV7Zk)M7M$F)>aTt`8b0pyRDcbiX~jv76;a5&b{6ohB6Qa zU?84Fs)=Za+M@9}HRJ&g(vygpLYwy=lR`AcahT$zhnOj|YAl~c!sx}r8wSct- ze5$mqgO&wz0mt9Ri4@G>4LIsI=EM>h+T@O)g674VSgWCtz2By2adaA95+`VSWM)Uu zUeF^r>A41n*U><KUkQlZc(x zSGch}JxvWXb%sexhPe#GG>^|Q@(&J~s0T_KdAXkxYl#XB|yhs!pa;JzxCxKym7?Xx(Z<{>%0ODxA3G+iv6L=!Hah7a!oPtDwUcxDcq zJY*T`hvX)|$oOk^!^)YmmJ@SUlBJ-`1#mwHPujd2;eP&i8l8e}PxnF33Ntz@3~;q* z@SPL;e?Pjj2l+P|T=zVbAZwQd^u5VpXRheaG`!kf=?=#?v{`q?Qj?R@zV#^!jl=F> zaB=_^YWd>^qw$Rsn>xI9DC1gUrY^j92L?BXp)ue($6a(*D>Aw3Joh3qwhoRl?Yr5{ za(AJ9B zFC?TYCqHg(JdZ)g9V03T0uBz2D z=bqjO7%0xIoH-!85Jo0L$)nHs{rsL1tfY`)B7zM~Rsw}ih4UoO*OL}4iNz@995$sa z$nt(GCi|L7=sYvm`+Tn9g55!U1r9eX?lHF+rFS3fE4u@=rya(CpR?v8rP8xm4mX@L z9x9&O0GSN|7+o$&v#M`DV+sd8=L*OcR`U;kXt-<60;w;O zE8#y>&2QM@#h&P11ByX|zv_#BW+A6*5DoKmFa)r=WI7>Xyo@`e$Q>cb{%1z2v;b0s zqtJ5(n9oR7NIAlms??M8&A1y6lP0;(d^I^%3zx_;y(=|{waYVhj?9^MIbK?21h7WI zCF7oKU2eP1J8|X&$s9pG57UDmn$GD#Q-aSql&RVj^v>bzi|0}q|B?1z4QBG3W<3*r z8jn2k;m)ec3i;gCY!iL)PuL3Y0vLxNj9m~Ji@XbhY8wJBAlR@0^qbz#%P%JDhoGY5rh!^pq_z;L!IYsLZ#YX*yC-p}njjqy$dJLH#x&t^Z7Nh7>42g?| zahgIP-gz=wX(w@~rD6oYXO7;t2dVQAnJsb;Tbe{&LRmk+*uty5Z&HYb_hb&+0bU4FYqF4qy>WXt2{8kvYJLzsaKO{TR_W3!1obwS_+2KQLzNB=>?fm2rA{3zx@;?6^L z6y5VV!gq>2VoAOXh9V#iH8GF7bafAb9N^FKaFl@CfLsu%!}Dt1W+YfSRsn*|IYA4Y zmNBZcPlJ|(Y~_lu)Z?Dv`A1Xx%=8eYaG-n(;Nx2ItS)0IdLst9^3_k$&TKTyViW>;{G+KE>)j?eX=ehi$vX)ye7xQTmq!0x*7Fw zfX-t*MwIfLdmli-T!Nim8NDfWujLZ-9C5_xkTV^E5$UW$qnKH%ec~pSLmZMTXfj^P8xP)Yqn)W30kw ztK?MApvDGvl#o5c#rftNJp*sXS*M8z&AdDH38;gb=1%(D=RUB@`HB`2eHI82D;kb7 z*xK;17BC2ysv3Bn$pF)q80TUD@2{#w& zw=#-v(Kw8Ahxv|%Do-X%94ce_lG0*|!J0uOFNQuY24Acb{eC1`AXX<1y)hRukJND6 zld0=>C&2vWq7?M}!Kys)*Bg_h$yslJgsWVo%{AMZmTZX(K9gt9w?eM*w)2-Z1CIvlzBp`v6kT-NfYas4$)Nhq6j>4L;)BCU zTHKPqH6o>MTGN&6g=-4DckNnWmL>0{vB@u^CtD{$wp?vyO-->OMUUq6RI~DmWcXD^ z9_zpR;PmHThcZl}i%;>DJBaRr$d*EX8SL~g6Q@YbO0s;QJtxh$$B z5qgMqJ?$P#&{c~h&0MqyGRDNWSV~I7qejV>6*d{PlO9n>GO9Us-D6G{0Yfw4qiIPx z;3#qwx8ot*(r5;hk&ttwJBg4cpU~bKIq6)am}mQz$tR;v`LoeZ!ql@Bq@Ezk6DN(j zt74S5{K+E>co+C2iyw8xh903(LV+K^6Mb{tAWJYvw`!-l83LZI;x$neFp9d9&f+qg zl=2je>BW=>U|K?pnvfOuFF~Hn^ccVqw{9o6aOK5}h*+p- ztp*`~2*?FFKMOd9Ke{8-P*8%lZ}}x@?c*jeSK9N0N>U%vIvYTnyo6-L&g|>R^bCKa zg~aFYJf^%M$&8R)bc~`dUm35O51Zw5le`uJlv~iw#0K2- z1Em*E3bUUaINj{28f|1w1t!%y{sZ71?cUqNX zw6aZNncxMC22yf!8k4mq?iTWQD_EeaO&k^vf?y023{IUWf&=o9kw34=`0{H`f3Mlf zFp=6OEDUZHE@_CG3nglR-Esun*XBAS5j~|V%XtCaXg0a@K*^<`vNvfCf;Qv)S;(7) z!d9nQYYG~F=z85!xs=bnO`hvr!TYrZo5tQnP1!Fsq7?p<;xqLfu5OQqpX2;Isb{;i9RX7b+_-4~iwsVGrn6V6+ zjmEyh4%D<*wz^ra*q+)KI(>o6)GuH(bZM0O0htX2B?Mj!hQ=L-2U#rQzsq$crA@4^ z5?EHk+K$~9hR@s! z%fh_mUtH$;-b0g8bL`0;xQ|A|sF%V`%N0{octvEtA;(RH&6yO>st7oCJ@->9Zv&;( zI6&5`6Vqaq-FxO8&ryG|?t5E++tAAL^&2aF&Dlc^%OrnA1{F}Q9o2&y1TClnbLgrN`J z%aB(te+40%6Z{!KGo}t4$miWXqms|KnyY3|FDaB5m~Z@6f(O~$HnZofvbmm2V{W!5 zla;DeCmo&iW_=AHVRdy@Ixw*p5F)0k7Bv*gTyIA5fpKCCj*&@xMSjlL;h4pbtP^E1 z!AWLaFvT)^)k4Jh-U7lD^TH}e{bRT^s1@Q=dh-k3$1LbtW)43Ih0K{rGF2)_O?j>%2a*3 zUHM&TT3IW<0!15rC`U~H%M)@bnfC1y@$GfvYZ36*6~|#g92UX<@-$mQVtxA*dt05j zV|u+6QR@p?b7j-pXmqhN{5S?J{kE=2;xHQx{!8E1`YGu_K5_W-rr$F+3>MYY7I4QM z6@mFzF$&0A&RW>#S2Pwjk@adLY~!o5ign|3fd54uHO? zgV+w_i_j4l@1|IY)=QMxC{gB7i87B%lzF0e@cTXa6k?C)*rmr!-x6(;d)9rvk@Ngf z&hy7P&!4F0Acts(aXcXu%CTk8B-m1=Cc<`Jb#ei~@`FER?imk5MR7|kzFQ2f0n5v( z43pm3y5B8w0V_XIs|6#92-?A~X`E}qWnLyXSUYYm{$HI2vf^pWNkJ*>VU9Np3kw$a zZHfEZB3XLO!{_qh4~v_&>wdJSFfG#GC9%FHCyt?1F~C-!fvSsGBxm!C6Ks;{`n5OO zY_OpZXpPiuEH6XX7C-oNkn}!-gP=?Qql(3hlVvr#{@QSjyt!smGQ))bh5IXnNNoZl z^>PFoxY>af=bVT<1ZO~&9~pW%T!pmr0MhSWNL6C~U46c0(X4t?65^%|H8>}O2B(@} zK%Kr_X9Su%8F@l2?9uE?wwz))GHJogcd(XA1b>l4fNQN|;x=Lpp2p2&r}D@#y*U4g393fdfmg-$MTtD8`P-a~37M zPXvZVFG(<5Y+WMHrKZPyA&dcPg{T1t*~T&LZ@oKvqu}9q#|1J2si%))~4@o3i45SIc)CRQ3;5UkRb2vaDKrDGcGp zNMA&d&`78G?Mdm|5{rwIk-zr$f!G1Zopcf#W?6q0SJ-=w%}Z8phi~(nWwqJxosC0^ z+nP+!3v3Tn$zM;)9D~; z#sc!P&;9BC^Lu@U(I9F4x15C4(M2+B4Wi-bY88Gr2ZPmg*j()-=V$o7j(^TjOVFGz z>AEwyjkUG4r;i^C`1|bX6a2fjLBH|y=+RTLzW!|E(Z-`^kDhLbwT)+wpKkm~tbP6N z|LITTbQBFGDuY<|>0%uB;&3@xtlchvLtPQ(@4w3DfBE14*FXKwfBMtws3{JQ#a}g- z!?QpAhx~6t{`ddN|KRukR}SRP$;pxW3N`*8`QIzA$p2)Y|8LEHw;l}!ow)w@aWss2 zqeRZZ-&f@*u5Uhh^r*cNtu;4)>i0i(;`d)R)^;z3{cgOuzP7ve!_&RTD{D`8*H#|C zeDcG}&Z8fmth{*iV*Sz67cX|6KHa^A;rVy}l;{6hxqlM>uRndX_RQ!1jVJf~eRdC%G;O01i;%&++g|(g2n~X}?DABWMek34Fy>+b-gCYIX}JE#DXv zI!U6VRYmeIqBQ>2(7XjGbD2)6yK?xWO0{fgOhX$$&noJ)j(pGUz1Y9bv3xuYow3Wi zH#=JiG+u=fyAyZfDSpL?Wh&K+QUk(w@{LAL_~U|eG{J^OI4@Xtb>2!lNK<1|=+m%} z;-6~H*yjy1)?ypVS?~$CCgfpbN5Mfy-ArtdaU#@oFtm$WD;~ye=I&RLinTA6nlBw( z%G?}O=D8i$zl6u7dGmmnm{dXj5Ib>(*h*G_T%E0YL<5B!Fk&MEU z1|7$pHs;*UTrE^)q{5MFXUDI14i3UhW9}|w<7{4R9;dAH2|*$@2}juGVs!=R$YNO>Eq44mjayfWe;L;A=?FeH#q52 z=>(LmbPzX_cG8rkuj-6CamnC!YwAaioz2NI3RFd{pQh1AOs|Tlw-l{ttI(i79HH&K z((1?h+JNo=j#fbp6vyM9I3A5#NnfmQh@<|;ar{g4ITpt!2Q}4zni0B458Ro?De0MN z%&K*R2_6pGHenc6x=||@QvXcB-i8z+|H>U=tKT(DgR>GxEqyBg%^-`EJny%!_qy7?7baX=Ce?UUmT9gI%#Wy*~-l44JN||FW=5oWZ{*b zh}<={*&kfx&O-U|w*xrs_yx4n@aSv?mB&!ubJLBvU58-rsR_;5CYW&O=0B7p0VGn> zT~~qmo2Ku&l<_VJ|!*ainCEf5ei<4R(@@!JK%PTW*v2c-6PV=jI-l% zs#E?z*&pc-U18~t*@IHcN*-amqeIZmaC}R)sjp$q!?ZYhhQBpDZvo0&wwKC6<2ck;RS|M^3Iru6@rf%_-%|Kn#*9&LF1|K!=)<9q(Ui_b&l{Ue^^B&+$o*q*a5 z#-pT@jIQ`2x@^fUmX2CU-+zP#A?I1rmrvuU>y*nrO8VWXmkhR1ClzO}k9W_0-aCRQ z(Dlc1c%@SO^X~Gp_`be2_i!+bK6WE<84Y`AaBC$go*^~F;~#!_Dynoaq@Su{B07hg z-et?J@j#r%@Te6JhB4d%(yALp%ym^3ze08{H2o<{qtw4f!+vM?LW-`Y9J>k{dYTt* zrsd{^*cNMWOeeoR-x6zI)?cY?WEPmzfksi%%Yt-rhf9pLUZmq3m++a$7={DbiKU7x zyu4f$A?vWYKskGfr9@WYipK(w0r8}-O`Q5lUkrT16WpRpX_PH&fh+m)>5@5xH|J}u zKJSxLfU;|f@5K6ARV+j3uC`dyi&aS}Yql?XsOaSbb+f0J4h~=1UhV777(Ep5J|Dxe z;JZf~Dl?xKKu)hEMvzdr->%Sa$2k2a{wN3Xvq>^8o~!Qa11bH!>hx$!tb8^RAMW_0 zRTE!US@3BgOlL3`S7`L~C2B0_TJ^_KyG>-QpRCRsz|&hFLATqA(%cTu(1(I9v@mPt zgie5nlG|pfx3nr+R8@i5&BL?flb5?^$9ub{M|)=n`!CMUs&wofi!_#~*kf)yTK}A+ z63|L$RZ{prW~8ug*NDQG+W zAFR5m94xHUsgU@Q@{3Q=XxNE+x;3}~&=B2{EcpO~_N#i#sTkh!+3S-}DR^cK$2}0b z68FAH!GmCwbYsJ`6_lN-Ma&3rfidM0t`ZEAk}oW$6PleU9i8?1y??~RKAgW_Y`Oq- zA2bX~K?F$aCz!8Bw$k7~*WI5R?$1Z=&&Tf1C+^Ru?$2lL&+pxzKj@#VxA?XX>avLf zqr!Dt7+g%$@Cq1;IE4|%K|a44(T&4$861p9H8~(7@hOf6;HY6jf0inIB$qd;AT8co zVe3sTJ@pdr1j}S%g!J19G6iL*!;aoPI`goI&BKWaE?o0@9R5GX{gFwh=^WBH(BYcq zzSI+dM=!upr>|Xf#=WH3Z^bH33P@fyFgPE6swlZM|9<}c`}gPX+wXhthwn%8cq`?b z(}M#b;|KYxy=vdTu9oE*kdWtL)uWQj(9-;-!uA(%p81RsvzJlllXj>AZ>F>v4dCk) zxf4kV)6}h|+7o97Z;?@~$%jpO1;y=l(o7&DJn>SJs9?=ZXRGK@9~I@Xb4oJ!(UYXN*UHv*^@stC*o+)jU#f|FDiJhhssttXbZ z?8u`l5ZBY1TeMnBfknw@!JX1Myf-o@5ZFhY{C?iDvZ4js@O#~~`Pd9s4v_bSK;D-F za$W@Fyc~>n5sY>@7`-AGy>c*yMKFftV2p}jj3&TXT_q^19uNr6{2WU?Tr$+h3-Nwc zpckOz0Pq}ciAZ#$9@o*F43QYehi_`~`MKmJ9~5#NK#+2Lxvj4tRK>oJjlK7#H(QU8y+@piZ1N)NMbVE6d`tcird; zq{cXn+v5&&=f9)RQQ90PgOTOG=c^*qv-t00aLmiJcubNgYQb2wI}u7T>tRVAm80Yw zceJ7lp{yVo<+krLW)Oigd5wwou-_H^CTNasZ~gTSKlwYbW+vv{4bl}#UiC>}(dJn6 z({$4>CZCDt&w=8`^Y8_$UBLB$>ibwq^C8ljev-zccGBroP%mQ-FDsALZ`kCW$vUIw zQa9WN@sw%T61&8pZao*w=i2$@W`_#(*8*H>7C~|tiK`G9d2H{ELt#(3;db$?j}m3y z(!m-5YczhNFXkxCwDwAl6XO#?m6>q zUT)@ek^^;+akM^@krbcS(Sew|-XTkwD+e^=yO_aivkrJ^KK1<(!J3}NODII@>I^^K zrG_}31>|WlOmqFjG%(G*7I{$FJrYH5UHx1`-?hGtIpF!l8`I#eE;q>on@usxgqS<= z@A&Eu7BQu^QcL4T8uf&MuY#-3-U1ecrkh%4dqqHvyxAS zY1i>-R`M*|iR<`OEBR!+^*TPqN{*DYV;)zu_4|(3Gh6}8a?>4*t}?Qp)8((%^G@}m z9K6`WUlK?(+QOJqxm@~!pTvQ`lbdc0bsJS@ZZ@Vr2RkvWyEx0^CD}|qR(}4r+q!; zhM2gmjeBAG%~HO{Hcm!Jk<}h@mmV%K^4A|@zRueO;AGOvEvCD3PJj17qwHt3RVT`b z%Ik4wl)#`tw(Ass^$1CGq@cUaV{UAX=qi8|?W>NMe=)E3eI6#_{)GM?^IPU@nUz@! z08%pD-C{-9BC2xFb55Q;=aLM2@o60B+K3aj_E~U*9vT{N@OVJ`^(x#C3b>#s% z0@(7IVm8m-tuFAOTE9I{RU;|pt)4Rbp?BhX#L6AH{Y(A#|6VMD*HkTSwvoxm>wM8k zLzj8*r4qeWNdWjcJ5lLP)Io{XfkG)d27dQFenbpWd|L)o9eE$@H_qbN%WrOLcX1^z z*Bak7fdy=QmwS7r9;$Ct4O>{kwS>U>-a=Q^}xL3M}TD;&cg1xh;zo zDi9M~B0#BhGoxQFf-CDL0Gm0oj^Vr-ZH_KNpUw6qz@`ybpi4pBi^*3B*3lGwlUCB( zy}FSS18kS8JOiLzBL7y!?hkOd_O65{^Lr_P@M`Bw__p)`SNGP$Xi46|rN4gJei z>txHW3RX_{)cNEnm*y;-!z#_$;XD^~oqY=2EpilK-#l~g^RV=DbzbQ5c2k~$Th`}3 z;<3|<(9UJV466fkIg(uzNFNqfMGdIcZ1tHdT*%{=sSz1+@lC_NqPc6gp|bJ+`K<_} zctm-aS_iwV_-mZ!FN;o_D^m(CGO-d~aOMolCP;0i%~&(dFfmm)H5Qv>L6c7^iw#6& zC`Pf?^k!>sIw)+zG+xe~6`d^Q9GdN@Hw&^X76gxz((<`gy;enyGc)wQ2uf#K7crx+XYa~OkIA8u477<)j5k)US3N* zHa}p?>b?Y%;@ryG>*kmhdshqWN*(gk`7NwVcFWqvWWTGqj+%5X=(~p$nxG6`*U%wS z>`$}pQdIx6mT4_@9Vr}|7^sZ*68bIOkI=bzA)6Hz{b#pYt)v4c&nuyk_B3#fNyGr6 z2vnDn-|=G9Nwi43zCXEYiZ_0%N= z=~0yOneT@x47z?pJy~5&b>jbihBJ3{9n0CuTBWc6{yWExRR5X>>E+4{tJ zO_kfx3QS*M0c=_2IXh-`?T>Z9MG}Ub$COOMZBv&Zfbzw;PcyG`X6+ZQ(|BgpgA$HvfeUVn0cAz5ayTIl3K3wk0mTZegEDo^JP49@gM)5_;|@? z82T}A0V6L6ua^M&2Iv2`-#)m*|66(P&i^m^xi0=+AN%Lw|HgxFAAg(0|3}|$-r@gk zJemCO_&+yayEiMM7U>1sUzL%sxCYy|ty#2w(dgY!f$=GfsYuMdCrdI3w)7JiuG&YPdaUz>=`xAdS!<|~w&n$$PP zu@7y@E|D&VRXDchvtFbwq)mRNs*UQk{AIcIoY!n$vSlkCL-p(A8J2k)R_g>V`HV^# z*Gg_T>;GEN?AN_qqxA*zz;PcrC)=VzVr|+2w=L!b#=G_cHRJW9fPVm>xkiYRb zKixBKR@NZkJd`h_INH>>R&(3UTTXv z-ON>*i5Lmy85iVw)*m)ki%DMsYY}`vBSMXaNu^geh?FlimJl2X6dQT}tD@2WljqKx z1tjDwZD2&!Rb-WAT8cxAXf{=3VXIEs#+&O`A z{Temi1Adg2cTP|=ZOmzz%fIwJII;ai-S5j^rPaukNegli4zFC>%Modd6f2EjyL79T z3meviS#+k|uAd~B9{QP-Z&0I1sK(kXg$Bql`Zu) z6oQ_a8m_0I@0o8hG{EYYXtbL6y;dAY@g9u8J4gJ^6AEE|HUyO13Ft+$1}^*$!$HcZ z^6&KAsa{U3qeZw@sZ`*xRj?dgn9PLML#pAjG-yTaC2+7uCbO9}2<9+Tz}Vh4W$}x8 zpgCLM`jafO#eV8-R*Gj*=#aSi0#LPq+eU_>vPBK2*F*?{m{h~IHE~T}tW}y~`AOC= znQuh*_{=p<+AEo|$SHhSD^Lg|0wGvu9-atb=z%b{2(A=#lLPh5bXW7Is@+Pa1p^&4 z$S0*x;fjBw=)&(1xi5r_aS=!qlmpqs4-*~!s29wXk(>fvF!w@o-1&B@%aTvf3k=Mx~L>V zk$SVbio@zo-Z5!kMP9Krcj*aN z05=8I>D0QPJMhvMP7o`(ZNlh)wcd2`PVa@aj5ugR5~+I1qgyvFIa^B;-FQa~Uv-t& zlKhkXxaxLjPTyylCE&KYlf9^C2@3XvY)52~4YH@13BI7&lCu!M`bKTQ$c!bz=ow45Duso|1>8< zv6z&iaN)SmKs-C-U3MwCz%?8h4TR}xj`ia;B+a94WX@z>m>JMe74_T0yRzs?qOix8 zvMn|I4`UD%{oc}+VG(aMIc9QXdS<5Iz?Aq)HFq{xr)#!~d@8NeoYUEA+CU2O%nBiA zLCt1?Ixpe~GoH>2Hi+I;nbmXQeHNrscpyg|lrX{tlhg}u4K&XlI=mnc%5ZTGzwC~s zfN6FXiJLgmYn2;F$%;w6dVZfWr&CpLP~Glp)`BV%ab!`&lu*EHqyN9Y{rCEQAUC-G z`{AQUclMuKdG74LU-WZb{J)78*j)U7^e~J6n-ACT@c%X*+yCnn_nDY4bo#=Z<&arS zY)>%rl6=8*g}hIgb`vP`@Jb;{TEB7&i~EzM;nhf z9)AnCv%b0c?c+QDuiJU<&i^m^SqT5>`_229hyRE#8T^O8{-7RxzG;>}UpW3RzWz5o5`tJC)W_U_3}YX<}~cIz!K0Y$V!aJ3tS{ouSYsM6cMX}P_B zx_x+fybEtl;imv%li?cu-#S~KLmBbv#U|8O+O3!x+CMO35cgvD`_As}>5G&0?oQ!X zJ&a`>hs=v!q!Zh3?zL2vObb|ON~@D81D()B()r1ok~ZZl&&pbXYC7wxHEa48{hv#h z{PXSqb|J!+-^MCtb z|7H8T^3y;6y|MznXSLc`tQX9@HS{n1*gN;#wy9~S?UPQ+G(M+oVo%hC-$mbNG@tjNr?E-~l*8=I zSJQ;1lT6SNBI>hs8j4WEULpxd0DC4LbPzXYjOMhn*WBIRYaY%Y7FwWHGWNScAN}oh z>zQ#;E46`_4Ag`osqU*J7y+q;cK>FNiai{Aj5NE{+p7 zj26xddtOYr)7D1Ry)3vHioMPwe-r^wjnNG!Q%nXt@a;m0)eP-fG@b-FSqdnt1z|7= zFxdljEsh$un_n zWktO`iv#SRPWEZc%+eUS2jgB0>?yKh67@n(BrCw(0UOkHYH=JV@L!?f%b|rnahZ4r zlSG{X|0kDpaZ6C<%%^0;0j0awY$!G!#UtbgFTE@2mooKs>DDiEI&1%M(V48`t>4TJZLQj9=bTd>wI?JxzWYyHLU2zbM{UA}Y# zIK?6mYN#EFfYl}SQcw&8mC~zzTGlW{sa<2SojxgW#_5z74L4FUtv@Nw!fA29b4b_k zO^JV>=UV)~A?VlZ<3{%XJN@77Ja_#67yVp=|Gy#j&&B_Z2M@COzaBn*aF_q-TX3|BsEwcln=h=egtmzUXIh{J$yoFM$6WS^tj* z-#)zK|8C`R_&@qzDess4YV&=rBcpjOoR;%W^Ki~f%@jH>Q)aH3JM6QA`MuZiU-h}p z`LB2Gx_#W>{D1iPF8=p+p1bq^i+&cz|C?g}-2CqkA8qFFfAjACr`vf7?7v5?=1%Kq zp7l3hs_f{V%?~^!PsHc;8(8Y@`^+0uv^cKO@J%qreIb_l^BAxVu{j^ZNvYb zWIs@LvTe#vwi~f+v&bSydY^cTu7aMnYLgX?l1o{3|%f$Ng7$1*+}6H(W*D49GZpWu!kgQMSxm2hFa{c7bIPFqTrD< z<~BwS9W)E1#OMVOoQF}2`q#cnQxdw?=DDo~NzQ(Zt0g(HEc>*x73kJmu-?`bp@tW- z7q5!CbX8_A58r8yz`_)yy1&A7lyg!uQ`6b)=8TMYSd%_?So80IHP#KiJ5eT$ryus{ z-EW3M=*Pk+bVzx^33oVOB(oq5yJX^0XG*<`6)ms%rbzLg-z5K&7f=65XK~-3+Ug3yU%-ENFoGWI3fB33h~B|I464Q z&_%5RUQ5IJeGsdmFhST^9%zfYSItn8km>}ei6lT|lnA2?_~n~rG%jaaY!Y6c@10?k z7kV5A7f?j=fqpn0spIIqA07kam(MOa!B4HQchKkCiSeqQdgk*1`AWS&j41xpa&rIE z2eQf)R-rEiHQ2zJeY-!!TOG1*j{&l>?_MI-=c^Bgg#+mLNXP(VvS0ZUiqg*l-r3i* zA@c5{fp})vY4tNJ?=tJ~m@Og({nX0tshM;1<%=hUUjTW``)uztvUb>y78(K1#_q(1 zK*6TsF?^WBUco4|yg}`d{Polf={b)sF@=ecaOPVYPA#)8bs6P-8m3E|HL9mK-C$#Q z_S;QK)wC2||D=-l!MKA`T~c$a1GaSswRDzaBrb7Scmkm5UcT>A%JPDt=qu|BzK|2b zjPK*|oD%xJA!lazfpMsy29foG_l(tLD`tujBF!w-^N~lB{^u8z8QiR3CUrU&>=jm* zd!Xj`a}oyl52ms`j9`DWJr+Pg?lG`)UoN@7W;H9(=@1GQ=?M(`aVbKvz3e%^&&nwT>J|LcP{-+ z2c;ncDz73T@oouN|0P!aE}?|A)L?d~IlT0)67}Bq$7!ep)j(l{g+f%DNO2BQa^sO= z*2B=vn4()ot4q+G4gnRD>DemBL9pO~S#kXj9>(l5U-%ZuD*aWMsb6(LRC@lLUc9OH zg1GwbO_lCYkz4q>c!$o@r&f)(uy>}>BR?@$oxKGzcLp0aqF=vO{z&a@;a~WIo28$B zlyG}nD&{zyOP!7&^b-^T2Y&1~xc6gO2qeLY`HF2>10Q*#D`Sk7^7=XOPgb$d` zAWqm=BZ33%O;)F)ETo*i%s`<`6)*Gp^)0bC&YWK9H(|umgib zS+|~V@Mny(_OO_4K{uY@wa3^)L6c^9MPsxF=eAsnFn`s^dyk$UVM0Oomc@4204!=8 z5BfzbhAWDhtO^kpnC(>FrNnMqTg~vQ1q}+XZ>yZ2EBs(xt^H)-21HT9Ezl3DY)FEO z&bz_+U~=i>|J5ZNqaakEcsKFRb%Z~Iw&OFDW`kp=nut?d*l@{rW@GjT#5%T@!`ddw z>s)Lj80+l#B`La^R!&A`;u4ZcIwTuqFdD6vP5| z7A=-Yv29AMxb7Q#`3`T%+?fR1Vo`4s zE7^vIH`b%j9tGGE`bosWC+Ye+ zHXz)p`R#px-|tNA+*w$Y`s+>|j22xsmH0d~KkakRyp%J~{N|(F{KV`S)_I1A_k%eY zu)yqdOVxe`?uGB|63kf6EHE4MG=D;z2{%9nbXiGX*p+*RB_WC2!ygu(t2iroSxg1&}SUMafVDGbQMs*GK~Ggj~)LH&jkem z65euYZNl@gj(0Pq83~%WJ)1RS@0RlTYljsnkEJedDH|X?vlK@|3yY!3T!u+a$%4~U zOCA1P@k$tNzb@}gNme|{`5Whh$Qg4^%PR2UjMOa+h|hg)Te0-kN2*2U={DaJZA1g> zSm&KW1b9;P8Jl1dUH+LUcH!p}`OZUk4mCRr4!F+Kor7)6P>N#iDJ4^-47ZrBG$Xan zgbm*A?f(=uC3D*5ygf*u*h!hR!wCMmvJ6YsISo%3S!FXmLP{BK&o6m>5N=1~tMuDP z%Y#G@xn|uozFhd-VJ!eC>je{#yu1F)Cd?|`k0>{kUH0#$a8V0TL$gZH7HN${NnGF8v-O^pca^JX*07fUR?3=XI}Q?u(ae;}aA!)J6feDTZRTQR_wTf5+*ubQ4Y_4W zcvB4yS97S?$QwV^BNBtlbJ3`1nG5suqMpaCEs|RYRXux)HKL6*bM;JxB};JoUQyvW zL?chx?GOuQMm%%){$P7Lp9dG{0|txGsF=MWhL~7kI7F}T6z@-mwpiIni$@Xq&a!Q5^BAyBv4R44V2warJS``sXldZ}hSrYxDEluL7!ZxfYWZHwDZUI;d z{$As^>C!AyjW_Sy@~{j}d23m?!ml4;Ahxh+d69}loHEkHem}GHdaeUUjZU|V!Y?P~ zpKM)R?xT(T^GlHBUo>tR2kgv%%fg2)?AU{hl0vA2wwyr0 z9B8cH;+b!^Xvxwd^M+zR6>S}Fjk=0@{;XhcFRT?FUgjfi8=+?0^Ov)>}G7#K@#sJ zZS*pB$8y47P6|E=@%IvOt}R+gFL?p~6a!k2ps#*mwIEZ?`BX3z`BF5>w32@wdu46T zm;w%S_;Don?}(nj9&NjCEnruNbPlnp32cK@k;dvQG2kSG zJK%-Hi)74x@kKLMt4Snj!(riR;3!OBv)^pkBdPrqP3d}`(B~aV;O`~s=FR%_-4eb! z2SMd3n#Ocg1Vc9H^@0TBJf}f2u$p6O#&~|F8fkSo2)YApveWFX#*+-k^5&hact*u? z%@zNro2GlhmQ2pQ;-j17o}xn+r`Y z<=A7hYw!K5G88HvfCdNZpQD*$q90+ThagZhT&@oH zStMHavkb??*k7iI%@iN4ncvNOTY*Xp&P ziAls4Ze(?(Hb-OJf;b2`t2E7&K^ZZphR-zcB>SE!6(-G*gE97#Xn5ft`F-HUC_};m zjEws@?i57Q7x(rGLiWxVmG#6QH7~qi=vk_1DVg`Fg-MRu$q5J$r|Q`=wVrd+mBux| zV(VDCW|ZeBd*~ES`EH!{b@BhIHq=&mUk+hLYQd{AcP=ASp)Cu!*qruq^=jGu$oE;; zSz04EMa!LIk)167Hn!|)Tq?-aqm=#8aZCDp=0o~rCSgQFuX2hz1hXKE&)S*{)1vjq zu@@$IYo#gktNBZ16OD2QF-3fz;-@EeS-XsuVQR~HuO||VU3j7TPRA+akeno*PELOx z&5v}7?AcAI|0)_vW>t3^SWo8UU?SbwH1kqyVHmGuzb4?r|Jg5%TRTkf^g7O#9^0YG6mevLk1VsgITX^ARe%?NGBh@v7*D(&e%W}U z7k@vR_{o-{+sL~^4;_4geZf_!tFvIz56~N&9duB2-?+w#I9XwA3T67o0gzS&2i9QI8 za#~o`>e!nMs-&_N9Ry|Q$h$DxH%L`fVIYd*TFe(v$rrnFifqo?LhFNDqEy3LRIE&5 zD$0QOJ#?GL=lAe-AHBp}hgN(|s`1Rb?|tpUYW!F2F~ATEZlqH+uW$&|Oj-QOf*U+% zL;3%za|O+ONJCMojjDRasXC~%_2EtUj=rTPMZHT);SxfTsibPgLPVrs*=hx8Ej8oF zWlQ)^K_j*jEZfQk{eP4G|Db9Qbp73%jdyQ0-*K@w58l~dzs~kfu>f&&5%ieGn9QYi zlV9`PdA_gFRS$WC=)h?KFG8+1Htils)xz)03w z@jMGiSy6;W!HTau2TlcknBCyvvW$$G`mOoWS62ry35 zXf@BgkBzqpWuih{nFJU9@XB(d_2gJyQOI2Q%?sC6XJN3;AThXb)9zrwlxK8!9gjHaep*5%a=OWL{! z;>i@Y_U~@1mV~EENa|B}PaHNXWnO`_SW>fYK|FG@`?p|iE>*Y{&L~_*iA5fVnICdK z)n{hu=1#>OiS2c6YT|k?c8#fRCN9P>h7IHVjoCp2uwM9>z&&8jJhjJq2!!}I{hv^o zs6b7}Wn|uaU7X_ZN?HYM*N8htsS?e}fM5Vfx?kJG%OJXAop$!6t02oTnC9os17zwauJFK%Kl_#T;X34k@K<#_o zfvyQ0-T-8QAtpB(LIG%&!ui-S3sqq~>1uK*)zLjmTBMdpEg*_m(}-RV?lYmJyyw2w z6H}Cq>IWZC3pI+Sq{)>wS@@8Pcv!WC)z5sqb%0!~ffqyPV|a7v`=RC$DL#-zKV%a1 zTFO5yYtGQ{$b&IY$IPV?s%JC7*qhG*j+jW#j^E6a69`K6y)W7l3*=#8cGf1?)ExMx~7(V2H#d+k>Z@9@&24 zOP5eq!5LGhKSuue^`bBuKPhO%`Qq00T~QARPz&% zl$CmtC#PDI=ErwpHkIvdBmYlO3Nqhfy414lDV!EH9qXE-GtE@}&T z=F{5xvh%i1D_iiI3`nHG&wo2hXM z2NuXDP%wu~(tk%3n#0DGRV>P`F}IPw7Y$6aMwuAc;&v+e^(%~qD-7R~yB-ohzVLVa z;l!&s4V}l)CEo;7P~I&Hd@#CUY!k^ZR*Cje1Ih|#Lz;gRHy3Ng*xPZ| zK$wM(nxx}6lHw&OF?%o$l4MMHvrAhTlU*Wr5cl!=2O#43s~vI^wMCm!_Y=JTf;ReE z>+G!Xtb#XKw%nrSM-8;x>-)`K9-d}2yy%$a{2(69M=Pt5m>ru!#d+VC=><7(iIC}t3!&i@N`&wUyUOglK3C>NH@`^ za8PJZ4WsD&>U1p4?(#1xV|m@6JeP=!vBf1=ymaEB!3x=hPYx9pv&u z_^#_q&peDEq`KS!$nmD3cW=^mzINK7q@pWkIWUs|UUZqDfC+zN+&F=whjE(0NatD3 z1<2~#b@`M?7cAkXbsX>+`@V_)7w7+_;gAuIV>-2|V!kv(j%~B$nQ|E>C+U=`hxA&( zcKnI-L#!bh=u2flN{=oXx;9h5=mjW*lW0APdedQ~bwf_x!>z3}8KIMx&@JmoOow_~ zKbyRS^Vt&@c26B1>|9%bV&}rVJS3MdnIV@KY1>lO_2j0R4vuPNB{vAGjI*-2q5KvS zlH~d*q|S12VgZA~bDSwB=_0cwrQ(MEK5K|&!cw-WLTSi)r_(!FAP=HV$IbIlDtlP9 zig{5HYx3nXyr5N}P@0>^E4tI3!7ctXa&I@_zl={*oD~GG*#gT7J2!9VQ~4*Su`HY% z^cNn^ZHob>+Kf8&`GB7`gP(su2c<>OYK7T}C&lPv-W&2t?8i(7(RNeXviy`93wluy zrN5~k1v^X>aHP_npm!xfR-OvrT1&Z2K5kyzNqZtxGo-AAsXO;f}*G@fMy=%oVuZiPfCGlr@^Nsnm`E z?wY}h`-CojaxLej{0O(UQpDjV33V2{m~??gM?r{sCDXFI7PISyPQ_=cmJV>`!SdRs z`bKS-(2l;UPPvP;rajiMXrO@^A0IKNT@VEHji@WnYs&6c051=BWTr0zx@*JS?EMW&pn_H@ z$gdDOKUyEkC`0O(VCGRsaSE0RR%s-AYj5eYzO^PZ6-Pc>E~gRMWi=}*zJIyNTkq|u z%WLVzM0+giV3Vitb6sq*)PAN@NRG_UbtAbM@t*5$Bb0jvLAIN4Z@<-v+PPq=8L{%$ zq@}FfWR{vH3R7*6RqZWBZW#D|LpQ~~vulw#o~1zPl#=9uT>Q4H_D^4u4w)-^Kn~{)ElW1hv$Y? zeD;Z$*=RbuGqrcpImVTJ{;X)O`EARs#$FHxqv@z@3!b;QTjY z57ckOAQY9)ZDQJb4FEu~UcGz@?e?7w`$sN;;^tmXiQAgT@Vu zWVgf#p~%Jz*b2&$lcG>PNJ3fe;u98~`yzNdf!2lBwP3HR;}84CRMO!Vts;0H-COu7 z5_4Tyz8(9_+1j#Wj_Ht>_#92tmfa}j+0A7DD{#`zv;PNfnV$zq-C}^EVG^}V* z9;@)$O*4D9Z9EQZ`}(L29Ibuq z-oyJ2FoAE$cl3gm#MM<6g$7mY-BV6%_S}9YH`m**f(Tyv-spLy;xY!nS7cm09>jd!;_s)j5;e$j#r+|;ehPBMiY*f;isN@b<}$K ztU8!X##?J^mzS3f#JffmpI4vb$e*rx&vAYuZ$jbn(R3m|@!~F3L;dq$I`Tr1B-4}+ z{C^6E#)ecc>P~fF1de}#u>c|Y?S(*PF%-jZNbCo;ieeK+2hi#+Q6Eb*;f_IQzK?hku)R z=yCwuq+Ih|zNs(0WWrmP3fwa{I<1iR?fAR>@z;-zD%%gyqWx2IZ+9Dgl_$YjFbpPF zCUJSWuVFfzYeQ3cP#o2vvUBd_vOCfs;Hr?XywOn438&PenU-cprRjVE6cfz3AgeBo zd#MGr@pR}jZgbXhAQtaQ34@j#%xfI`nFt>W(K)a(n(DXstfseR(j`x@s?f-<9}QaR zX2YJK^bpcr}9%Eb? zco$@YR$GK2e*$LxvXoYla+UVxtxabB@j%e z79*l|jRG?2eilNyD2230O~;SxmThn?XXq;x!N8C!yfCVO(pA{>ZX66rsJBySlAzV13LO* z1ulTJGY-xNlRYm!4?^d?RIr0x_}21XZ#z4XTjNhs_dm=$typ$Zu_sP3P+GsoJ8|!r znLB3Yj+wa;Gn0B)>sT>Li#GHx{2^>RtoNBOpY>fI96yf^t3dZ+uT+e@5nt3HOkaQ< zvJ2Jf70BlomfvaV)gcxUpCMaf+mUR*UL;`)AUn!?$PHWis^ttnN5ukWA3rz0R+Y- zQK5Dhc>0uM#)X^37WZ%%7`R4BWGGr3p$3}wKXiRsUV@zOaEPaAmy+jRQeT?PlW751 zr7i}6aHjbQt9SH$mEI6{@yc8%>xHZg90q5x7hhqAtE;^80OZ}lX*w8IY+Uk{-B497 zlB)AP8!7fwDE{v}W|a%7?Sc?vB~i}hU zAJZ1oC2uI~6J(JKcps#Jr^n9$_OQu`KfJ=so{3n#5cO~buy8~F-0NO3#Njx00G$Th zGYAz4B|D(2-7pi~5bv>z-}B~T3M^dZ5nwEV59Snu4yHjepfmwALSA3p6l09#XhE4p znPfG?UK|Czx<>MS53jRR+rxnL)O$hr-Rrt~b*N5$M1}1kW{W)BuB+W?7cV}XPNE4& zdA=fZR6b$-@&Ztj?MOqJ@w-*m8ks? zB#2(kb02>%*Ok;=|A6CvF+G=2lcL{EfQ8Exu1?1^(?gIcVf!gJYY=v|Glq|nB~t1w zCFF%Ij391~keV*h$|7(>&83PWbu1ed(rGp-6_?&WB6_bfMA1Tj!5mB;2jh{e`k=`X zYN;^AmykG=O4*l(%mB|`!x&FpL3neI1qbTO@6e4kabkdSL1lb5fooT>6 zhXWg7RWg>DV<-ilY;V?aU|Kn2vJwpWmRV2jP`Z&!%|4AzPPBe76et%rwqDW10wjL} ze>moZt3GTf-XKisglPywGdd3mZS7IvJv2OU=Tb@2EFULl2$L%5#=&@^YO(*fX@D5d zA2;DBfB4Pvj7D&u4yU8BZUF%TsEDTt(THtVD~={55n~r4GX_{|P@$F3hYtC2`C&9Y zAK>P8>4p{DHK?Q`k^sah;0DOZrS(?&p`PtE!Cm5W68h>yLoW^(mzwP#sW71^+nlhx z%qqa!S1$MtWhu58B32wkBvTU6K|w-7n$9E|j}apH#KP}CAT9W|u^~&49t4843?w_T zQ<2^390bl`>cc`z#ZB}taM>_zGNOWN9=7wvXhjCoejm@v!d5J9MjJl?`-f5DQ$E0- zf%lrocU(9?9Nf<{;*;cDd^PQ%%O)`_*)9nBg;NBU0uVh!dMZS-Gnw{+2u)mxM&Lx) z3-UcYkG+(aQSF^pzJs;Bomy{M$st872kixHKbpG==zu@;#yE1yt=t#888cCIlTVE$ql4}SNiK+QzgeJ;$15bl7J7U7jj zG!-&y_mSH`RHS~&4rW2oy4t=vzr-7XM|>2&4#FPZFt!`zc#ohz8N2Sv`iU2|;8 ziF-Yqby4O)AZ{yV&!(`9<_(j<5)LrJ7BgtOFC4i@6sOX>fvO#R|5@ zY86>+O(qyqE&>nhYwpq;CebOB(gH^BR5-Ar{a#(AV+YR?S`Pwft#+onrwRrmw`fPX z<4BYLfYNp1wN|~N`kL$#xR1RqkZZdo7(qmq<;H;4Ibx52Mz>4HDUA~tPDHu!FuD?p zaHbQRXd?F*2WL|wM8X*zc6ld|+Y~MZI{`Eqpc&peDosnsb876zZ2&sD{3(#P+1Oyn zE5nXmVDCSuc60UdLuwfr$zRqEI@D>i*UOv@Y%d`4!Rtm4h7`vg5`%&s3!B*;J|VFM ztmzmC7&a^^nUH46ZUF+~nxPDh1ztKR&*MWjgEmdNNw*>jY3ntFwfP9{$w2Ug!!&go zTaH@IoxN7U5*8tEsoYzP11#XJ3GJ-yAM7u`Y#ujvwK}7AdLSb0D;!2=SpX%D?3L`5 z9FQc=fV`*vdIE>zS~Hw1CDsBliILTDsIR@tXrWo_VB~YkhS5TiJ0Kn{^wF*ocUeKV z3XB4wj$Oq;T$1axa|v!1HS?10W#~Gf$vs(Ym4QZa0VlM;4i2v-13U^e0ZpzFx+wj(KAp_!$Ska{Iqg%W zaU1Xl7;SYh_K~?K`6^G85T@^Jke~UPZT~yiKR#-|I5|Ez`V=$3_3eL~@bXUoe=E-fvRV0l6i*rzCUJL4o)Ls!`+#_wU_DuGt&IoyHbCRl0l%J{_Oqymvb6+ z14i<^^1>Vb9pNu%e5e>op{!BEN!&`P5A{9XY6PCO__Z~g-E zm^Yk6Txy$>8pmqei{mM9Mo+u)rt9Zt$s(|2Q98anMyq5e73n^<(_^FI^<1!E}U$Rn{eJ9Ush@zSevgjEKVR`*PDg+J$NvmeCpe$R|M-6s)CfNPISQN--{aQp1%3=r z@^nQ1+TDDQ`9;+c?9S8o_-7;buH5h81e*Q9i+dgx!Cy}MdtNy8hwA$waPu&k_w;Y3 zMEri5s8{}!hCsg>1N>!ov=_YhU>w*1j`Mxs{{{Zn=m+#^_>cdYsF&U^{(CQh&wc*u zFUc?d$O8tKn>h560PS=jXajHibPPWm7xL5Y>d*r;>b_U6yfB1GjR*LzGh|)j2v8MP zX54qT_Ymoq`rd<z3i{;%X5;D;eA3C_=5*gB5UH2DJE&0)Mat}CaXAG}^L zQZM|N5a9xUojDEsF!XwAH%j~q_zZs!ov;518*>!_MSG3aFFhCVes5}i{bvsUfA~{- z|GQ7^<0k%pkM8{cZs)n<|G(&GCjb9guzx=P|LyvNod4g$jXV6mjR$X?pxps?$86qGH#R%0lKo5K@UM& zT+JvdW)$jEMp2QO>3ASd@6H;gt!0NQZ$fc+NxI0iGw2{_4_!}a-^%l^)8Je1fzeRr zTf#RyClw#10uQ24}0*|S7eTiMi~Fc%K;F^9%}5vS)m0;qWj-_UkDD*ryY1tjsK1x(<9+Pw<-t*< zsScY*$L;Nt-R6-xJUKc%=(H40Yp31W-fgz`T00Hs9ePx)A6omzs`IM3yX(%fa`1Y; zb%aB=r>I`E)NZ@^Vz-4I(Ij`;N3HE+oK5=IHY^+rZ?|6Q9JaRG@CU3#3+B>1`l&8$ zc3S`K1PZ}NwbR^deg{*l%~=uHlI@ctx~mtLp>y)0bKE{YIc}-%4i0u`oja|gAKKfk z&J(qJ(4oaT>9p#Vo#t_qI))}-rQkFC{onJ*Z0~DW6&Pa^s_xL@ z9_-^(0Y+K}M?YbsxCXTLb@lpH3*H~$deTZYaS1xGV%x`dG3XB#@VJuBQSG z+TU*B*8^Pgoii)Y)Xo-ugtKYBEW|KD7HwDEZ3@waex0{{Q;@x#0G|5lzm z{{M@9X5hch{`tv$%)@`gmkj<>{?A9BVSaO;FC718(tq~N`Q$!sp#QnE|K8SfNB_U* zX9oU%ChVVw{|_E-JlM?Ye;$2%r~kQ?XO^wD@@&RasYrGqas^I}AZz0_{y{KQsT^GR z@kQWYR#xSyRJhNjBz25-f{c8*ObXlg`G@#GbZiWbaOc~yrY z#X)zV5+BtwmDIhkPOj0boG`nm><^ZwU|sb+48%cQ4w};GS4BrX)M>iWG?8dgc9qk< zmnK4i-PF+~m0gy}FSR1EP^<_MQ#d9Ov5o0;O)qP|DB>rg048=nWN%4z=?{naFRl?9 z;IL;>Mq!1&O%Y0XQm`abj#B0E^e``1G$yxgWimTe&}|*PBHKsX6h9OCL)-!odAC(9 zh`WubKj}6?f3mFZopb!Di51f({mRb~xt@Q8`bTo#=EzbUQVY61fwfKyh~*y*bmE7O z>L$5AX%cbzGS~qMRm6*pVS8e`7xcwM2iuBPc9PocDw@V7zNAjcuO~ET)LCKDI2ipl zU+II9l$XBtysilF8jww)n4%xpTT-dlJ|{V&1*GsG?C}iUApL$HJ&`SMZdUOb3{f?5 zxiw)J&9=0Tj=jr`%B$!ScFSbd>PE4?@iNi3UD_+A!H1SAj3iQ^!k!wiAg15CQw{Zh zR3(~D$cvS73$dH%M0_^d{R*t4=sqbR3_63o9})iZB1`La_=}(#3-_-oDWNfxM9i`0 zafFWf$XWmhp_jIJo%Vwdf=MINA!GCuV*!9Y2J`@g-Me>BJoFt{gV&YHF-_N#Ku5Y` zxDZr~UcAU5n-tR(agtXxkHE1T2WRM}0w{F3j5)_vFdlr>iU8#(@>rcCEKq^NgHHR$ zRrU{#dny?K>ZTuRtDDO(h4U%E07e1EI;Ol9`{(|L#$cJsHj(1~`A?=9>T(EVbE1PH zV_p+YS@Wx+$PF3&<^h+rKd}yl!%TzzrEY|wyQB^Ih=Hk_k5)GxX6hOywPXagf4Mpi zFuKY#fCYA#Kw<0zhQq41_EE1?nt)?I0{9#0x7YTL2!-;7WA=@y2RBndySM* zJiM*kPDaVjEwO`t9l+%r7I`Q>t~4EjbS?zI78AoB$BgkCgY=p)pz z<Rp*C&An1PgJu2sG281gN!a(r`cuo#5 zm+3Sn@?@jR7H^uw6X@u23?0yWrfT-#$7wkGR+t{gu;YzWtNQ3eBN3dsUTTdJH+Ks0 z@khASe!_5{*Yt3FtMTpn7Pd zGdEsYcVZlyq}=2%nBX zsuKb>^vZd!lPwT;m2wWWt8~~2466|aP!fs8WCVDW!f^aL1jQQke?v%L*54CpK&&75guQ!L9&KiB5?enm^bwShwPSFyE@*H z$TUT0WjZFFDq;o$yM*PU9r$YO6HkdZmLN~WQ{)WIfVqMIQGAt}G|6OH5l1F?B7-7DWS2oF$tjk;Wu8NrK6r6W{u%FehH(pz|j z(FBW69wH(VN{4_zt_Bn5%!1Fbk62JnFQz#8X)Cl#RK zl&2s<2<`b}45`MQXV`Iiz(GtgH#5OABIy*<1yt}xA~fg&_ySez303+I&nS>5Ia5K~ zj)SC4Ceah7N5?g+x*IubLHPgqD<`RDh#)5646upQ5`W(2?)pp^y+}CQgJ7<Z34N<4}Iy)`c{^_tz|FwW12r zm|;X^Tp!FMfhavG!hbuulyDjIV{w`cE1yh-CEc7tC>5nq^V0@gj{GEC0{R(WW&5iL zhh}(Otig$*DDgT5Y;uJpCmL%+L%zk6Ku>A}+W3j5C{&A6%b>oI$Rek^#v_iv0{dny z14{}(H6+QpB%nZz6IPVmN}O{mt?!2yK^%o6N_>RppY1nAggw#Vm`p)@_nFXlU7TDK z*;4*#tnq|4PB8%p@3}}6s96Gj6y%b+Ued`WruD}mUjz_#xHz><68L$fJiPEp zYl}FYjvFBERtK0EU_vb<&=te*oe(3z41JE`G6&~CFbNF6#0mpX?2u}42xB>v-Wg2e1HLnznm%pylf~W&0 zo+o;0Lez1ANl8nKapu!a7$~A)0gTvMMV=c^GM&nzqS0qfC)daY)7N?Yd>A!N8aAa~ z4`}3m#pL>%L|PvMjA73E zUV7&8xj;~qrM0ZK@-N5&fr^d9PqV3*OiWHdeyR%hKX5KTheu9<4!&vCnxxPn1psf7 zgQax}=L19>;QcqGml!1%-M2XM9wna)~t0!5GxIUd>L>SKTqq`$&c0#)7&30 zI$Ein-Q9|v+A7txg3v`dE7F8bH5|#>-j2Qs2L9}&id;4;Et}r8Z!L`?nfCiYh_FXV z2xWIlng#BzlI!j`?e4gz(q0aY!d|#OO#ef!(9lc8EE-p^WDRu|X`a@oI3|3W_xx0? ziH*4FD>m2HAK_TY#zhEiqkvCKss&!)$8e#7q++vKeO3mhrz0Z<%)-eoR!+IN9!6yb$EF|h?ym&s`*)$W|7YjDJhx+-FQAABT_(kn;P_ee0jI#KFSie4HPn16 zYh;D-x5s>TRUt#TzBVzAhLTxH%bMyh}H#uz>{plHFfn)?~kP(H9-6YCJWVG}DHYFP_?|=-4 zj2uWtNu^?v5@X$Z1$j2%*4T$qdsGr?3`L!QcH724xRvce6eX_da74TMDb zLz`<4$z#I$X)mLv()q|p(w&~MlZ2HV5o;_Z6-f??IHzq($Qkfy%SLbQe?bX2nOz7H zP9#YTum&G3)@$y`^aTrh!U{P{{L=hKmu}f3?B-Q)mc~I)=-Yw)pzoOe?flS5+PiC3 zBNe5?E~G3F9f0Wy=^=z}OaD z*d&$9+@Wri4OP;7r|c(hKaHknAEA(6j(~ zOutL1a*eE6(QXH3AE92zXB7v;Gub2A9^*AdX@`OgnVMU6EXzT8I6v2lC0b~-)QAHDqSlg z&S>n>>3$O=nn5?1Tv?imE}q1GL7vwN>u-6Kx~ZBmyiwM0D}~>(b{1h{W`oUmBHpYJ za;~U?CUf*>8^H3ob=rB=JZkNn?zUeXHIIHOC4D@TCG9un7|A#%js`^uMXluKr0+;r zr3=K6m!^HrZY{22Mj30(kgBZEX|R&o?u<}0Z_>N%{qJG6fR~HTyHeQ)DRWCssWi%0 zJQjc(N9Dg}jfPj2bz~Ct0G*My5WN&JzlQY@XS(mUx0gi^!-1{jYLm7is=59Y4+Y{A zE)gla0p~Q5T;vRRW84NqFbyO$ydnKko;35dy@77m6ILs=}mHnI^mA9QP9!BL@uR8#}JxqNw!8_ zCc1`~puyJiXvD|M9kJUzI!gAD7urUq#h25uE!Qy!+s(&#bOw81Zm>(7$s=L+&!o= zya)g(-8wQ!lsSz(y8n`S-mnkk3fHb9vCt$a;ect5S%%EIx|sB+vIf+2<>kRaI+_gj z?Huf5vhkPg{njazE9bK`Wy#Eg^W>*I0uT+HScOa&&>-cQrk>!3?I%kgMc~COy>*Or z-EOsZ=yb#yjS7lf#^lI)vVvjbi)S0TV~g$)#i?=){9g?vEB24r0fq$C5XwouhcPw z4%neWO9KZON$Pj&>Mnv~S`gN&rnc6_OUP%>+M zj;27sHs-baR}~k@T(t!(oH%JOq9$cWT$|`{NQ$3mzLY4Mf#`0U89z@=boi4EJM9UG zYklK`$MkU;``Q4`&5;__OUOVLatiRCFe*exjb*H&Zzu4Oe=|puR*~pM$Li>)m#0RO zYy72li_|D)P&038FSeSyc4}%iZ>8R|r^|?=f?IaEuY`M7XIowX~U45C)uCeYtMIJV!@Rc2#Vqnt3|`Bvrdr{h-# z`=_lRTiYkcnDb=0x-2d$z_|#-TE_wt8QNiPvEqO5yHoa#71pF5O~p}CuV=28XWElW zlHxD4 zg}^G+G<8JPo``t>Im+>LG_F^8Cy}ER1(wcYL~epqNyw`njVSbOhqlUmMLu0ifOl6m z2GldJK2t~CawS((AA=-%!1)vv0dF3>tLQNswX6xs;cdYv?4XNbTXpx$VIs_ti)6F5 z!h_Uq&_z^HO*nCVR*E9Kf=*#{6rse=R1~B4#(YmJO@o*0!8MkQ=ZI5;dD3+6)o5}%p*Ka#lnIr_MM{-^cF5AO26-_CQF|Kp2(X5c@@J$#lvuAl#D z{o&&Wcln=gH>8B{(;%5Z4r)3b&{BcCU6IAZPR z$Q=&te4g~-(9kDm5YMd6&69{jL!T#)IKj@RrxJ%|K2J7r+^)}&P@K`CIR!k5x70|k z#WLzZRkLr;T9)E7FMX?7I#FH0xFWEhC?;f!wSqEl_&B?={Ejns8sk2gOYG=`N|+{$ zr!i(!XjDuQj4=S(l91OfohjlZs|`D1gQJ*Vv9Qz?CJDnP$V-Yw+ebRpsFZo!N{2Qn z9@6FG<0+h`LWg7D8;UR7ioOCpvD-8Z7<{c3y7`Z$MtItLzoY zs+L}$tVR7)I=AA(t(^GiSXd-U+l&Dh+4;H1C=E5)D{hmp2NZ8z+(e~fLguua zp8AzGV>?2J?vOB1G*>!{N{Xf`nOvc|O=4*=G<1SK@UeQo!-aRSEbdSTOTKxho~dfz zOD5GPm5O*db8Hra!fB1tcT*4K5PeEL)ak9B*w$8u3crNa{SMo1g%`DIwobKPGKghv zt(t4Ez_0SB# zqf7xfP3UyFQC-e%%~=!;^BV&Ieu|Z{d-DQHm+p+NRK+__fMBJ2!LdMzcG|^4wdFM+ zzQLrntkQ@()g4BmPp_QwLeHjs4!orVTu5{)Me1{dsKcd;5He{*O{1LnMA|a+1jD3G z62CtkvcgyI5MoxWF1+D+j84n2B?%C9yjy|8vw(O?Q*dx5$Y)dY`hpJWwV{5{Ej(yE zpdcV1I+X1KFM+XI2{7ZQPYG6GZ@Cqpf+P^CbSRIWjKqHHg_-EtWnzF$A{8vJF4v-T zLL3liT;ioAom%P7fBsr~&ZEOPbVYf-O>H{iKOi}^nz!1aIDO3AGbBDg_D2y)k_3P$ zS<;XNbg7(R=t8HkGp8xM)m62c!*=cIUuV^SIK?YY>J&N{j&B^J<28KMi$$qqJWjBhBqC+9WRs9bHU@7bJ{)Qk`fz6OvKtLKu=v=l z9tt%~3o!3fpE@ThA;~v%3P@=|6M{fe@2u((2PiLDw<&l|o)KvxoiWBipx}a> z@IEl+z+M8^yZaOE*GFO!@|!k`^MVR3z;3_y8pN}0ToyV%kPN{2T5-Q^ZMnZ|m?#&C z!zrhv-b#O*)~xrxRz4Q>q@%7@yA(Pz#y|w@6iJ=R_N+hGmeXGG#G59|a^(G5!mL&B zCW^^44(&?n*)wafPb!*sOAEhlzij=8VolBI>;8T98sB%|@IL9d-wN6+XY4)-bNN;7 z3zYsoz$mU4SDoM-vCi2O_uJN%`Ve9f(*j2b}NS;PEN6_Y5O;K>A&F<`0zjIt3db7dRc6$yVyC>Hz!C0f-K zL6YiOX8^S-ipJ+GBoQ zjh>VV9T*9wo<3DoMhzXMipLRybQQmvW*wM@_G%e`rmnf~g&NL5JvHzIs(++zbRX_+ z7xZ$Bnx0V8*@OX*yKvwkZJ#8kKJw46w-1h5J12XGdQSRxX>S;i4ZKBfIvV@E<*I3q zplXijWq^3p_|Bj3KGu-XR#?l5-%$FP(&TOGl1ABkh7%-t>Jo2)Fbjaqz#4e63MBIQ zya}NWHj@O|VHE!2$59G~4xKy+s*;TU^a&s{W>cTNy&&yg6zGH zwqtC?+4yqfU3;SA3^``bBx+&4iBz5#H5(ISh6#AQBrzv30M(SUYj0#x+K4pARPwbK z<2-73<;&^Bsh+{{>c3Wuxfz3p2APqkoUNL8&7{p0LGO_WL`}YzAKW$! z*GnsT=3hl(6~V-~*7adrpXh7r_~d9mKN8V-2!1PXf7tJKr z^w1jP_ns)sFZX(OFW643w^rv(JwwGn%oZKGZ9@`9Aj#Q3Jv=)2jtmXD;dF`c>NTC( zQ!jgu{iP0e4r*D|?edm72{H1Q4K*ll$ev!IoHu2`n&9CKK>f(~613PE@Hv=HQ1`}S zQa^(tWY20;5R7s))UWnL>I4^c-rBm^p1Q2xM+V8G2tk6=_IhW6c>+M^u{TQhfM^<} z{KS-voL~l9z&%_g?U4hewpN8$FntM_WG^F)AX^)m$_8XD8++%>YS7hYoGWSUk$@(R z*_pHI`arN6p#&Lt`z3Oil3Ak%y>U#i*w{E&+nOgEz=Us;9O2Uhjx4&w6w_PVHx1Iw5j1H{;EtxDSrnw@ zEApLy_L&@ca4O&p!9*BgXKKr<&r389r&zDHtWP*<<*R3E{{#~lGL!hSz1v#R0Pzg* zD)_Lq^>PPO(^G}j=aiZ1wBN%cxq4Zp=9KD>0Y_>jG`cP=nz9Hx{Tjc7R=!TEb-lxl zE^1L~%eriTI86pMr#>s-Hf(15SEpeH$b}l_qVi6r%kU?>HZ!+PeX|D9Si14@Rcrfu zota=bOfulBwIAksWo!jGLv)=~js`u|Kb%sds4LKypf0PoFp6FTq~|Rwm0;t(+S(Gm z_|{gc<7_9#zCbMg*<1AZMzQK^K zHFyS1^eN%vp~2}X5M?Ek6p>-tUXUUB0q!uS>`VO^wy-VjnjvTF>ITvX5dipr!PL@Z zeZ!%(+WW^x+CLWuLNkn~bYA1lOn5$Xb_>k?6eDv11e>l|J~oML`DQ{+O{#`bH0E&v zgwky$!z*i6qFYSY9W&uziYCegG_i|CID8p0nV#HXr`d3<#g-Q3-sjVPfwO`pQ{z$i7eSw)mn0LY!)UJ9K8mu}f-INSU8 zWwP*-CS__Nn&vCqGD0G8U){K6v@~jfK`H@Tj5s9_#fymq&4^7w_Ju9f6%i%jP}_x4 zEqu9?DxhlmoD!)>#38c@>*~L%YE^A%N)F%Y0OAtMMptnxxrbctz~Td3mdNB0_B3wW zJW+bfY6%~~d48jFa|a$4e%U;3?h=Y%x^_zKdl05~t=r2-_rQ8)nB!pV(^Ws1T@C7R z;!ZU5rnj31lWeVwHNePs#CpAiS)qN*Ikib`((k#lI_(#pj3kqtP4WJSH=B! zDxjhg`+ZUoe5CynAgbxFDQ|%pb83CMUl!Y{tIbD`;N*G^0=Lvvx*J^?4Yo^f*O#8i zXNDmVow*#p{%8Yl+HD!$YOSDM(u_b-{T_UGTBB`SCe0$5+#Q)b!;UbshKqs_f9i|4 zN?kuGZ@9{GEe{>XLN0Q)1z6BeRV}=>A5?6VA`qz<-jM3S8Wz5NSG8H59SfQ*eLHgb zU2}~*-?bBFMo790VtB-*PTPRUW>YbwZ)?l^DVWWyV?SCki`R!;D&ldaNoH)ZSjyOP zC6}?yklX4uE7o%6br9DQ%zs3hZrq*Jrk#1JoEJ7-oT|ey@|AIn`?1hOr>8T#MLD6w zMno7Rd!;I_Skm_-zZB^>t@YzxYN?uJ#g^~+^$~FNnAz$>y#!p8U7f9v1hjMqj_GV= zMU4EG^0Z)IY;l7}jc;X~G5~9cJTS}lS?6AeQrqLQCXIkC^r+?xhv3;7fDq7A&H7WL zuB^GW1>DYQNyS+*XjxwXt@0=JmQ{^zZIxAIeZe6+v?hE%qao=THzGnT%4f(x;v{Tr zR4}u%C6M6O;F&l*8v?;MuW=f1La{D#tD@Wr4orEc|37=*w%x{+CFW$UtgP@oc`mWr zBtU^6DcSNgY0(3bkc13x0YJ+h%Tp-=MY2X93XK9tv2FKvOj#fk)*N|^*Gn`64mDRv8CQB7{PfET^Xys}FHE3=STf7OR|lgs6SIxMD5mh@ zq@GaAURq~l+yCq@9nyJKyq)QeDZ;&g#z8>IsL2sp;n*^yMX^LOCG7%XfHPz(hc)^x z3zUA~ORN+X)Ql!lkBZ%?J-RA$sG?Ybca95lzr155P0LntS(+K1F)dG(lT3f(T4@b9 zJN5-vC-p7OuzU_M^L#&C8Y@Ot;iHA23w_G<3PL`Ctzvfybh-Yte4(LetnaXYt&IaP zFYFuzEXvlM;U(9rmNzt-h2xj-z&32fHTjm!dB`Y%B|eQY&gS5HqGM9 ztP9FDDMKp??W3!43f#;|j_^RdARIB+`cv{E>))_B;_arFqMUfshj zM+b2NC<0pC<>vY%?q>arSPhqPftuw#ig$u-yj+q^gjT<$I0^q}@Y zJ(dB6urRZ)=VK1oj0byPfC8IlBQX`IIpVGIxpa-pvGQJ4nf0fLuUNzqJg6CvGkAcg z!Pz{j@6yl6f{Db2?KvHr2k#;u#zF!K=|Ul{851|gFKl~*}YE(wdrYPOXb^=LVguVgY+v)(V>en@~NLo|=bEeo^BMk)^2 zMlrA0xS>Lgu|D!oQ$pF8$0X(C=@j;x2m~7|2er~TdBMP`LG=x5K;VGP7955rr}(!Wd{eMv+AXg{C@E}zchDvmORpl^D(#ja%Dt84p_BH!<|@?J%vA8A zs0(%VE+&8Tkt93fO)vx(-?Q-`LSs3MXHRk8O){EV(u|u8ZiqCG7`2FUrirov^8rHF z(4xqAiRir=r5C`gFwHJ$S}Bi?NKH_1N=`ZmbT}$;iy@T++`8B5&C+nVNAX+}AQ^Mu zd^jF*gFBiO9SaV{6)`pfoF~bf)A@y3a8P~D<;k>TmTH^0Iy5FsrG1r4h~1D@xUjI0 zKpwExzKQuB+2xM1Is*YQHtq_nYarDSSrSl3l%^_D>R}p>?%8LGCUx2)vV(@2+d^JR z*RITxa?Z~AOC!0aS&EG(!q`PnjW!$FY^8Jv*?yKeo9i?MlB|Yj59dZcJL1nim`2%k zJ!jDyjmiIH%;e*Q3`N;)LRmpFcX1Fy(^1fWh$&+LML@d05c0G0B)iV33ZwxJJ4O4i zyeBHm=rPg52vC^CJGPpu-CY<2{*M@7){(kA7PKouOt0jc^vMX%Zv-xLJaRZ4HCv6) z4rY5V*bC~OT1Q^v2t#B#UP`k~M0~aN_v3X;Dleo6v>QBq8La*QTp%I^c=Tf+_bO6o zww0Iuh<10!us4=ElZK=#%v?R2)=W1Z_3_Kff(i{g@Tb8}uxW=%^L1b9jceplGrcfd zn8GL%3j_5ff~9llcNZuck{pFn0E&3T@;aGZuXL}X%W9I&GjhU020orO#IXp^*A)eZ z&=?wL+!Wt4(%3GCeGsy6H&|dAdR<`KHDrW-3>UxaoBYuQL1x-Y2b=u%RZWovkTM)2 z6n#v|C{4Z9xIAA1FzV%`5hkaOF)C5BJ@?~zX_UKYC~&~l>}{BS*e&&xz}t(` zo)CMi@*6f`$J~y&?s=E2CA@ds_&~WML<33Gub99%p3BCiYbLEhX58R47`dj@JoGY| zieso3@y56Yah5f9MWBWo>Vm+257A8Qg_Ples~7KZv_!HdBhIyD}q=*8KU_y0utdTwt!0FLd$WmVsDGDxpsjXT22{lnzQLBa= z1Gvjh0j}l-3%noGZJ{i?(NUN?30Yf0OG?Z-t>e!w39NR7_ab3y5;vh)p9iDjY=3Z* zUG2hqd{ln7dP`Dv1JR%eNj@;B8Bx=o&G=toWQw#O7#w>0T>04eWGC2>G~{*@IN*(l!&dO5 z0}I!bye2Lrb)NXwiTHxeY6@#=))lT9E-5EP#=W^eq1$w94@`LjtpO=vK5=7|tSXu8 zCrnj+*LF)rtL^BAhUz*^s#dgezQ2C9zf^#uqjhJf%*nY5lA#rURE{+SBWdDUkq{G^ ziG)RA!b>yj;;|D8C0CoW*n)$1#WmBaJ6=@+$=1oQ%*RhCB5OBM9vukTJSo}?4(BSo zar|Ki9NBFau@}e~^Pe)k6uY$>UaN-xT(O!R>&C!Tu^WNdlhu$-@+KRi=3|k(yd*Qs zcs3LBBOsX28v%$rGSx~KR<=Xn5gKZT(z@oXRRHv3BxjG356t==CFA)=&6>={jxzaT z$Q%xpfdU|QU?bXhbCzz3kP72Q%*PU#uNl08qc)_~7nc)%^|>8)u}NG4)YTM4Z>UC! zC{aB5Weku#7K}ltAygm{~2^Wczv(n;8 zLo%;1OcOOmWIxQ)OE}*lX=B*2h(kV_VB4zkHPe&XOMYe5I9Q1g5623OXpO)0po-3_ z5hIJt1)cRUl8Wg;$x4=Xi#q~*P<~{*CYd=B^qSSJ%#KC0yndO=ha}dcLa6a$rWiNu z5s-2~8Rf_nk(>a|*kwA^$c_+}oncJal9X-c8F<+_DwysPeQZTn$P|7jkEGwi| zoz`H_w_Eef{EUqrwHMa#|8!euj!Z!YNQ5^ z?;Xk_Jc~DyuBQNctt!=6evP)v?J|ouF%2BA(`D>K1vS>Jhs(z1B*M>6+Ale=gnN*DDS<$?PjC7 zE$`Z%l5i}E9S_&Ht&_u}HVGbOu`|qt{4)5-2T~;5kv;Xcg&(4awUvnQY=OBs20kPK z`tg(%SG8xAR`2e|9mrA@Ubl{V?S@&m%y|6D%%!d7GHrEEUUyD9{kAX7dDrRyC-UmQ zw-5T^_HM;<6wn*_h2_#p`LUucGTU0Sx~OL@@~+|;tId?x5+ukFQN8u~UAJ~JtiyCE zJV6sFtAz>L$<`@FGHXsCV$jqt>JIr3w#0S}=}sa1OHj@c`(y zaq=fl{$i1*t|*0VX;v$t)d|CgBT_s`pFPsVbC0#M8`?xE4xQ*et1 zhq}89{i5sxBlM_>p%hQ7IStp*|B=pVogLQP@(wsTM&qtM44J{W&3S?Y5x!W5LYF+T zmM)|LQ4Yw9Ti5Z}NE{o-YUPTC%gdr0jVwYMfk=2maj0?~6z4|m<8{<8KsgX9EQ-gL zl*0##2k&H|`t@l=BmoZN$ZCGQ53S)0JJ zC=RF;jiy;#HDxr;1V94rllY-wz^0I~l^|#{0y3my!AG8dKt3LXRd(YMX@A!~IPbUV zJnuO-Nr8hRN;AR`|LOSt29cnW!B)DW7Pl62`sM~f(1YH(o&NVBrggyp@a>*n!XXfo zq3|Dch+e7%Bw*3>vQ|+;GFYqL#vA><4!S^Q*0SEv>mt z!Ox1Jm!jV7y4YZIC}a^tald}w)a*BS!Cq(Udgf+Q#r(E2Df8&EVkOcg?; zJ4R<=B3Dc(&DtrYr)?#GUy_JbiZKUI(yAk-cJM>TQAjHt0mm*w9(B0relDYR^?+N< zA$mzh+aV*bAQv!Tb#wFc?DOp9=UEfOb3E$W=Fz=AYP|{1y6xAU@4`m#;WhP!DCBXC zF+OAb1r%V2Yw%b)g4^Jl#dR>Kx_l+5C%YU!e?FxD=0AVlp?_v^N~SGuK2%{-EkH5@ z5s>J!JZ{(L)rPl3>m2Z%A*4pVTchM+3U|fU zZ1C}NXL$Fb8D*1?+C~A7@Tm9wQ{Q-~bje|;i=*Q;S$-&G6k_(kAHw6-ci|aYSOH`7 zBOkw$)1%Hw|6%Ox)x{G>TXCEY=c5=!3Ma76tr}emj{@A>7#~;J3`%A#;Ep#VtVu|6 zsp7f+Jj3uG@8R$J)#cAUgC4hh1P!+yaYP+FWR%Lw*7eS=6Q*f-E#K-Y7Z;NZYmq?! z@S5y1SxkIo3^#$OksL<&6#9x~b&*W~JJXmFyQ5w58rB=nBbE6Y$832Zi&#a8%$ zs$r3eu1w1;<|6W?-|F_y&wy7tZLtz)J|Sw6B4frkconX>&l|yZ!w@jGvRJq~1)IaW z*F>$qC7Zx)V0KZjxGdW@F`IUs#z{%H_3B z&-)Km*QkyqwcFk9Y1dYU+L&Ia-Rp%o_3km{kI-E(t5E)TZ#uN&MRFvjGRbP zykBz)Rm#T*gr!VIG`>p&D=^kBCGC_U z&+6LP_Q!3dqLjEKo|PgHP$2)}fIHlU1K#WCm3Ny=)A5Y}JARZ+qiya`UXMYHyiPhz zt`fuscbxqp93~Dj@y~!hbhms7w%;w@yq5i^(&8~9Fz1)!sJ2#QAktK{fpJ9xr{A`_ zy|(f_zev<+*Nz{z&M_9?P^~;!%N1Fjucr-XnF3`gFot>nc+r4!)R6+rn29VS_RfGZ zNrpA}7mAAx{;nILG`;}LWPY$Cysw5j>l{+P3-3Z0&ZoZJ`yMI*N}O|1We|@paqPKK zIF70sa0Vqu`2}vSIGN4{? zHyKAE)Jq^p+C{Z8PHF_mfO?q}Q{NYbmfE=nwnQVVDZ1Ue?rC)5IrekQeShy9VclUF4Yp$Q9vpl_Q+Lx4BQ&0t^)9v0xul#UsfJeIm-cDDD1`c+Jx(4 z6UCr;iLYZh7BNUqBg~lzmF|#0q})Xs;+_KSIyzy@I(8#BtK%eCE=Z zQTetzP-$aHQ1mB`FR^3s_pa4F>72YFvqaBd&PIgyLctr^Af6ALYV-QX$2^jFt&GdQvh^isNE!YX63%fl!MfoJ-{AHDg6oRer^ug$o0 z$W_XcqNDw?0I}l%gbMl{aIr57EKCIx68Z6|#79x^RUKgQv4qMZYrJJhn15$R zb3$=(_NsdUG$OPHgE`y~9Jbqo71TaB}=@Dv1<3i-g zpNkdPPZ+&*Z)5bx7QxW&E?`AU2^J(6?v0uyD2E+@M^E$_pgY9_^m+O;v7R=U(>She zBy}hHhO(=z;ZO(TgQYz5CzOoQnZ4$88EghS4JStO;e1jvb?uBXF4qW^Twl-b0W~9- zK;iw8QDnZOwX6mfUbFk*t(s}ESY>yYMQ!;-lwH+@w9BR|Dg_p<>SLB9+1yvDUTzm0 zsNX<9y3GfsUZbV~+lu$JbLxul!hN*+IaA?RIcqO-4;8-(JO^B*@{bZ8N2EqWY4Q`~Ea)<(=I*taX zz2G}0^+j|U+(y$RG65$@lWW3QHG|d!DAjeOx(9RXxl+49pGIz=Zd06Q>>I%?g(p34 zypmP6Y1#ITToD#3a#|_W)S)bcGojd^AM9fm+^2Jns|T2HYUa`mIDL4qda%j?8i;>K zHvtlwR9eA1)nuuq#l3|)VDe2$VJG8UN!D_?Jdj+l*<6^nUIf&|h&p{QAB>ImPGTp? zN4pvOQQBwMz*SRVLt<9Q=Zd@^Fk)juCIgav%<Y|oEjw;11qC6;VSS5 zcqfhI^T#mF8g{iI{01B;2HL`@)N2;81z<#1g?REQIf7-;l1U@(C00#+XV=qXiajBx%s{@)K1?>ae_Ao2f-K37tDXtwT-k0 z5)!y1N9kQW?F0G+>V8TJsWH^0S8s5o4%54F!?JQ?bA+yB2?ptO%29r^yI4~umo_at z17CiV8Q{bG6!sof`zN6@hx418dk1>f=t1x`mLlk}jhZRds;igx&W+Zs8pUyl&iO9` z^-AKyXLX^HD~XtlDfdWhVwpz4xICykKnEI84UBy+OwPJ~&v};6s}-BM16*-sh!2)% zdSn918)^k2zu}SU6_kjQAU{bJEzX9rMd|70PL6EHwCe{)rzh4)8R z>oEA4zrX8t`t1j>t?Bh=`g+#weA|LbuqmiJ+*>4Jtnqk&Ou?FnfK71K!l=VpIx!TM zZ{R5AGE}S{JPm4v9r?yx?p+!d?}!6Ld>YouN$7?hL?h^R-WHq$XfE)7^{!cs@Z29WQD1(-Qm zVR9bDuHKbHS;Zt~uJpYe04eDxZ5qMru-ERtK18hS>a?rP{RKKc#pwUjtotj>_#bDQ z|Inz_Hh=i2u|H&-pZj>W`b8`KAg4aHDg;Ek_yqqpJqb7*c9aBXvIO}8wGs>I@`8V9 zb7(Z}C%7ePB%b<3)*^)O@2>B~KkOr`W{ecnc#J(#p>`M3b9xNMV+lkU5%(;7C@+7BD!2+;5fV&i^dPdbeByW|&@TyuUempz2ny zq6efC>_8GdhL61Q@p+-g{ebU&`eh?mE{)X6Ji7Sf|14cSSPKKpt?F=f zuo(5O5D#flLP%w5*pB{FhFmrT-?ro7{%tmuPoH+)oHgI8Gt;7O%-J$F4RUK2?(VZqC4kWNU%1Y(QFdij0Xj_2l{)w89$XjfI zb@SFq>oSQ)L%ba=LvJY&LZgX_p6_A`8iK8OVcb;;gxZif#WIorD{c~I8o5NNER9hC*xT7V8ap<9}AX-(f8xLKF~x%#=IGL z?eP-vD+#G@jY)7THutl;Xj0?W>x<)&c4G}3VuI};3CPlY_3E-=`Ph#LbyH~LfWE;E zqUfBsja0RfOI4gRGY0M$4K#;9J;F&In!Z;|=$YmsjeCiu3Izf|;^d7wOQ1Q3%_LWI z9izo=G&C+*!^K3JNO3*CwkQkv@lsVi&tp@SfyoINmK&EuQP|x*1u9-c)u@-^;rW?( zFFrDa4u71OQzz+ov&8%FQJ=z!){S9u>7*nS0UWZ<$+xYe&Y{dDgdgYaCH2_n<4CN* zG44b@(a`7frS#bqBR8?CP7hCOx1(q{`o}A>_(7lb`Ys%t$tV(^-^+9~O7F;c@H!rR z5XH)h7&w#OZF81j7FS9I!x=C!dcCpvF>I;I%GnoJyi^sgo0Esh~_J;FG=!Ya(u~we(9obt@@Cu3^WqnB_ih@jL`kJjOZ>?=@wc(h< z=3St6o6~XF%D9N`S*LV3YB(}i6aT|G2Hpe&_SVEGB&vCLH}_@{82YhS76462JOv6A zm0zm`O*n3_-T04lfNioGN^Sv2uFymCYMfF62{bNv7<_RF&d-iI2d#cP?6r>DVe0_V z0~wWNHiSsY$H4$H5{f=jbnBfiXr#3B`f}&jrTFw94|Uh7CIdniKN zPen;b<@iwSuv*upWqHs_P6_=0&sxf`uBZ*3&$Ze}(qi1f4^*=rqLuB z{%e+ww}8W(4l&`~{TA)?RyG}MHADKoi6j%6S2qWP$M>H{{DOU{HOo+pFjJLpM7>54KR=HcY1d5*=PR+{yl?#{~P|{@Bg!0lNjw)zG994 z1^-^>iu?!r{J##;ntvoju$_Xb7^qt_V) z^_||LAWhSpVh*8caxph`!$bC`*5r`5nopWrpbckUFBKGe< zcghKRm&{lgC`uN?VmtC>FNLZ^Y16!7_}Rz}kh8?`$S4yN`6B{F5s7S7Q?tr?U_0Y< zmJDJ^+KcfgC=+Idg9SF>JI2SoOjfMR_bbEfySe!so zzAG(M+QOu~&(HK+==8enK>~J93a9J>E@jzXv=tJV{Q)P z;$U|83o_?tZ^-cwo>JBY7|KmLBZb?Lg0rb@7fKpfa)2U(^m2BGz$1`l!&gkEi3DPt zp<)DKi&7dZ6!+hDdO`2>b^l$f+YaFSS@-nY&SCp7c=bKJYzGIYXWw@_Z{GHUx2H#k z?QSnBBK`+uB6Id~ZywvJnGV2-tiR|i#p=iT-(jvbbycmAr^@AS|6?cmMn=^-t9 zuigE&bI|VX1xKenTDWr**rJ-C)o)Sv&CpPt~H8Ft#I-S4q!To2lfM)2-!8=iM@d1=*JxEwuLy@S4647!BHq>0)2 z1}E(|N1ZqAlY=%EIK_tEb$abOY+k2_1v=C@Km>GqPIJdigR#+ez4pPmLAx7tUI(qi zZ#y`CDG%__>j?PJ@*TXD)i((z=Wvdu*>3Owk9Tt#-T&j&O_YpK=NHue2_FXC=)Mm8 zQo?E6gH%%Y9rD6MfP;Xk3u z@8VIJl*%d0&WlvmK1|Y*%DwN8Uv*k1$Y`5qC#{1c0`B{pizFHcsGB|@JbV%zj9C1K zey|K4)~~8KMRstDFOWMt z0Jf4W9Rar>6bOD=Dash&Vfo795q46juD^f3t_j^uuxsDVnLT1}FqpzDO-F_ba1!iN z9Ign{zN1QcG7HwX)+ss%-2NjP(5&D*9h|e3u@S!uF@&bdqCue=D8d$(Y)xtQAz}T8 z-y+8sje?uxBbv@rs43Es+%#9L!Eo^53~KeXQt*bQIyXW{i_!vTOTzDIX(3U3O$m8rmXJp}7_c?S4AYY~^5P;Rp#_mQ=o(jI9_O zhD<53gJ^(G0Jyhq3?MwevI7Jw{DsM5eH;&q#F(botmGA3U?a zVmr`iY57KqD zFLqNp$HVL$tocENx^*+pn%A(^=J-L| z%)*H_+BR!+&i2tL9$!IG3>^1`)e36JF1+pb3LxUROi@hI2xTJ!IL(xmoT8kS;n08s zXS#sZ^ysfkO14Rm)8MQ>L^=U|vkrtAN*ipmWczEVou(NbodJ%jp6eY$HW5s?ck+g_ z+pl397|q4U?e6v#12d6_0rbJv-P6G}Tn4(x9}4Ju;pI@#IM9rh9aLTv8sZL~kT3dO z>SR>9l(;8kBZ;Pui7s2_rb}R$^u{3mQP$O`X6G-j;1=_$?3*RZgQ;6gf)nu7<(Hth zk3&+Hl0yOeLx>5%WNVfxhT$`BE|3%Q1}?`-4o>2D&xCyP-Pcy}It!zai#IqzooUXS z!hFo)SqvqXYgL#4w?V{f}Hc7IjqsgNU##`O&BtyAOX&+AJpq zzBOFCf)LH!uV$|>jGjX6+jumA%hhIw!D|s14Qg@os@Vv3p4CfQ33r}xLzOT@0i@n*x?p>m!~dCwgMPM3LFYL7U;&2#L|?8s z9LIy!NcpN}0Uxf4CV9L(%XU*BEghl~_g+C(cgzW~L-#{j3Oyz_&9E&IXuBx@ab;uS z=fleOWUBdgEw~?~<3U7cGOIZ}pE(5-4BVW9N)VpL=wxTlEh8atq4P#9D|dQ8B0m^j`M{c zch`WhZcTVUmlYVrr&EKx+mw;XxqcQ4J&w)}-u3$30CPU0;@cji7MzQIdOo|{c~NA` zoLC1e%!^zB-kW&l3-xN!JXIUUBaF!L!;jjjvz<;eK){zv2(56!#14rVBrONorHB*PQ5eaLowQ3;2kifp zQq)yg#0jUSk+bFUyds$=$>KCkT{&|)_9bLBroaw%iEt|%=QV1n*2%iijF|e{Lw6e_9BG4V!dSVqOtEI>u7G!*=9yD@i~<(9x|@_{yiC3P2cki zRIXvf+}x-esJ6RK=U3OrtVkf|3o;uLzmlT1DG;w}I+l|u23a57I|Azx`5Wx&bBwLh z$yyg#I-0Xkr&g5um)G6ly8byXU}laDRjwjfu-9YE&PijU*9DZCAM-*sO_-0u*eVfi z^}P3a&G;nLYWNmrh>zE!bzBgA@-;m9(|X+oeeue+3T6L!y>1Z!y+FY{yx3hgkMoO7 z#h(3G+_2Ju)f*AnEsz+M35P(LaZo-4KLNSXJzsJdvLH6qC~PjKZoY8j99x!PxBOi? zWjnJ%neb~S=J%b#XeO|zH41Mz!k8`hCWR$=Y%{N-BQnvL;vpdDSu%1boQR0l+~H=% zUdygjCfg0xNkNdNp%VP-NZff(!yS4q0kUICbEgrq3yIt=NnlF(X{RP&Xe`0Hi7qfj?jUE8d*fo|(mSx&cXvlq{59tZEBV>?`F>*>>iI^`FQ;o>)2LD##? zPj1ZRNYc!ZLflEB%e;__rR;DEksC3)DN2TtgaVR7Wj4P=ThutiJ0x;%5$%=eP%AUD zQQqlvoSgP0_ye67mpHBg3=>lwL<7vGHM*}NiivzE+`48_o>=4?Gau@O`+E4^DOEU8 zRLWnR=z?bG3GsLmppVvB5K4X{thzcNVp}EZZbKgG*fSymUR6?h8$ag4C|Hp=rR*uJ zdM+#-5B<>A>1rtbk-wJYeUEr!bKhI;oL6X6cnV~?y=!!nhz`nT(jH}mwTO#hVitPhXP+f|9Ci{Lw1>K#=n}D0Rv-(?Fw2g zAT-1*kYdALy3XachF)JRR;8QzBxMq42#lV$Sof=N8*#{IoG&E?xrqkT)Jw8n3Vgbb zOUftA-iwk0#oEgN0PXj3%$(D*$J5~jVA!49{FRGqND#(v0K|923%i7=tF-9w`Ur16 zpcJf3*rH^bSH7ZnBiR1xtFM;ks_@imlWu4QJ&)z5mNS#JG%W%k_|tzX0bxmo)O5_yA2C5-Yf!CCcq@{;}uibo<0PBk&aq5;)4^_xu zrFAC>7@ml7VeCR^E#6`t)|K0(hLrjM5dr}WL?VHgGnjO-49Hd1h-q4@E+f$ zqg&j%)h4SVn@tv*m-E*WmQTPm>rQ~xsnQKU#b<8MxN7*^g$msezB1s(;0KF*(En;4 z=1cm2>%id4sCoU!K0d+!`^EP2zx#jwRX%_B|Nj5_vy}h$uR8q?od2Dj7his{oj?EZ zi8-3C=Dv}W ze(?0Ead|aK-+f>5-N5JCgW5j>*M6Tujd#(wuE&vLog-^zQvFWmQ%vaUWSL*jtS_yf z6|@-yXB0(~_W6{Q5x?W*h2zwx<-^sYy?@hf9XH8X1icJ|DeZL+LjFd8aMC)$w5j3S zaOHQNFL8Cb6(fto^Gh(=LSUlQv6{vbklYS1x|g0S+zja7Y{Zy(J|?{kxpbnHy~V1! z0^Y>;JR9b9&vODf?kA-qVq8HPnb9(OdQYmv)Hs+EZl#c(oWvI%ZL&991_*ncHqi}} zgoCSPv?4ZIG5Ct@TeS^#W_&Szn;TRrd+U2itU=uRWD z7wSvin=Bn6?xh6FvIDucb@Z*XPDons=~xqHQiC`EWKXXNF;z%yTsSb06 zj({r9%@>)2(kY&jM}{rP9vZnqXMjp(ec&2wxd}cB(TXHjwhLrIdeegqvxTShEChHP z=1a#D0BWZs{mvEYUC%EsM^5J%Kgbuugu;EalsSa;y>^Oo!eV?A4)Il?BJAf)my}`s z)UAilL)Maovou$&e?3j_N}ywg&wc3NOO;(ic`@+t4f!@>O~(&Ajo`y^^wF;0j|$MJ z{qtWzH-Eqnw|o^|$D>K8v9d+s%m-+56W>Yl)hW5&{c)baNRYT7R+9+|6JT3Yv|`Rm zr0_6cYT1D%Ew9t_UZpobnHs?iZ;}y!VT}A~=Cz2B)fpetYr-YYZ_0mHG;i^U2aTG6 zq6RuNnc{KvH*z}Tl=M{LJd5eb)g*cO5Q>LM7S3k((kpuvWKJN4vdJ(mEzZjc!!0ej zGizl%u2b{of0Iey6uTEyq0$1Y_A$uq^JkKeQZ37I8v$XiZf@f9mxS|I_4+#cyCJzI^9!$`*q=Y6 zz`QGYGc8WM<)(|IiBgRX9&-*LBJV)K9N_0wTy~tuDr@Lu`{W(fIK~c=;`NXz0NIKp zzNAlb&>LKwGzs1jr#hVnH*vKW0eu?XaVIZ>TE3FFOMRodGAFgW+s1yaa7yaNw7$Ah zVTc;K-l#blG}sc07ZgpuDi;*s>Y8oFBxy5{M!;#^liNL9@76(ptm?Fvq0GNzz%DrUEx90D(@|qm$7w5v+7lIpVcx(gZvk8pJ9$Vv0Yq%8VL)wal;O_Hm$XAE zlCbTyHU0~~b1NiE{PowsWsQroUd1b4&xa3{ig6GE_xY@nxuS)nlZ9H!(NtG-cpj`PS-$<^{7};sR;2Cqt5ijz z=Qb?ioU3MENJ*wOa;5m8l&~~&u9SVDDL5TW?H6_4m~@>;GUtoYLn$HTa;}Ix)iQY+ zIbS;e-qiDHg_87gT6|xU zI=6UQ7u7Q7&aY?=Y@x0k!r-Nz(L#yp^g5M?w$O%i0E3tAIW3fwLkOkhj222b2M~?1 z>^Uuzbk85AKXVRhu`C}#%92KBv`|9NAYOxVNQ-3(CyxT{4ry`45!G8V%Q*Obrj?kn5P_x`_UfeZ};}=R7SnPQOFmF`SVKnRNy1a5lu;Y2+r! z8R1CJA2}mZ=PTK(-~yH~CV@Q@yi#Xo!fOW5tFcSYOm*XOZeokMe!XdGJCV$8UmKi$UZoSNs&{%?+FQaok7%1_cpn2zA;zr9>NKpvmR{pI00jIl=J7a;1?JF3sez z>)J{!BcGp$m#nJqp+h!v#`09i!?M<~B|6I%Zj*n+6><@s9hF79_C+y1QBd6#L5=%j4`NRkZI|}p ziZq5rtLcmN8ReDg7D({0L|G~>XRSBZ@()m%_dxx(Ji} z`6>&(!C62LIH^6pMPKCD4=X#qg&wCOm3IdrQ{=E%I73={b*DyKOWK77V`BWSnfTOF zkunEj`bKA#o<3{l=z9R*fC6WMJ}RcML3ZeuW>mL@SrDS>MKVKGHl3t7`K(h)?{R!Z zp`HlJt}qZ&CX!@?wrFr|pcex=rO-o*C=R*iwn@0^9Kx?Xh^vCr?tF}rh|@6xtjepr zx{Sx2w)p{PT^!0{_xeYAW5L+xWTlk=Mkxri?JmS@USqUm5@OsmLi5UCQL@W>^;YIG zfRNZ#yty)qZ_xjZGESgO^gydOh$JVo&oeE)@5-(Z()oDSxfGZ(_11zx!`84}v4{o< z548*88x2gpQ~E=9u>FVnyzn=?CpVVc;6i6Wypc!wRV6DP8^)dOw= zohHdw>+Yn@9NPvs+o)D)bBCu>nWmdsxxK#@-0~qz8c^IDLf!S_tR;X4$o)ORTe+a( zGDAXrhCh?t;P!{)M_rJ!c*-uO0Pe|n69$hk&a6GhvF3St>w{`IIpajKuhYUyoN8Ek zj#OFmMv0^N6cNT{<3QG@g;6M##q|(}Yo=ppnR3G^y1#ZtFj2 zBR@6*`t6P7M^S_O@Q2UW{%Hx`{Gz|IEww=5HQNeF%vlX)S` zFTMCz%*w@4%h>qvA|^NfOsE0FLslS@{=Vi%p*bFs>us&S5q#vaOZbM5b};5zW`bzI z>>sY`hxndxFOqO)6gzEpA4FFuH9%?IhcOz6LMh`>;*|TR_`XAil<3F9Swi>%E*8ow zmJbltFgPEpmZ5Fdh!rX#Wpi?>7YoCvt!K!lLPd|xhwm;`*3U7GY~f1AwnN9)fi54r`6cU2Vh-nP4tkiHhJAY(f`!<8y|A7>6Ee zpikB%I@${ntza40e&P0MPtL~)nNpszhQaKoH8wEPB0n0o@PH)u=eox7*fqDySO5oX zj1d_S*9(x|nMs&JV66pmZ(=0j%_utK;3mcGih!eRnD!}3Aa`(2XJ^9R12h;7Hao7A zNKimG*ZPkXHZY(zMohR*=iGigQ!^^g_Rh?^*NLc=I!FC>7n@Z}D=v#uyriIG%43)z z7{4_%iE;%Y^b`&@$q8=D&;Ng)sDPBFygFvi9*%k9JqaHt4=8Ur7j*|sH!Fgw_B z8;S+^fDF;QafG4fjFF^DX$onV>H)<@*g~&IfCEfVqPPQrQcXrn=i`Q3?58q27@M8j z63Z@7Rl4jh*11bY!-1s2BkNSfHCE)yc30d6+je23L=3`*iHw~5T4Se?>4(gvSCg5C zb`c3oH2_pVtG{?uV7xE@x1+ZKfaiS>UtYp$8960kjW}W{3^jsljxw&%NL5p~7&LrZ z3Pq=x25V!=8(lQ)s*7nQjry2puDJoMz4C@k%eAizHbga3y|jo?Hm!BA?1;O*G)BZh zD<(#TFVEqaG5%=TkX;6ghf{_s0JKKiKUhBMUrBAOh;yqB%=+3TH?sKzJzsnP%&C}5 zA+i14L7Q9y!}fRW15|;&ZJit*0nSTG&6KY-@Tx2ri&lE=w}I)04zO((od!E6=k2i9 zZgmgdx^ea_BpTX@RXZ4Gj5)ec#&11?oy{O)LL427rOx;i!eD9~)z}Ovl<36^-FwQw zC|JdfSoJ^#kDf#uiigH?`D&D2Fz>0ybVS+_IFjnoh6_{XsBaa^KQol1#3hK z25Np4{Q1GM#Vbu0y==riMcYOXn(aE+HvoY8$i@pnKygg*L2@&{apld_D0!c%mzFvW zYAfDHZbTYIQ%hvPjD{=++{{T*XX(Um9m@jex@YnTQoYi?lwB~>XgZ52h)f{Ct}F4n zriKWJ&G_mpsO0X>C+YYoCQBST;IIc)ZjLcx(bo9p2HEUcOzInQRC5s)mddy_CM*F3 zG?6f63tN(_Cu631D0IL@D#|$BSlLx$B3_(OofUo*Q!u4m33Pc}qdwR}p3YXfD@EK~ z$y{Y694cR2>N>sF(b4I<5WU(%K-6KY*Xz7F32O`B*P@J;p^|(zRi(!koh)-=tB)aY zT739^%@M6(nkm}hX-Eqe)+9dJFTWnd_8wmQy%pzLaux0XLo;^LU|~h!gsRoS#v8$7~DzW}yL|Am{LyCWa|1t!zBaf`Je>;~!Dzf}60+o|qDhuVQR% zb{C884Z9}eiNgMH3#0)Y-^&o8VA?O83qp*w zSf~+DLs+p8&fO#=La83GvW}G-fJ7FdeEXL@>pvj5z?5S`f<&~}nx~HfW=>tU#+JLKY(L?U{4RuRW5a^Ws61&pnN?=h$d5H=%VT3+QbW(E% zw?UrH)+XN7#PM=HFb)ibi;o;`0T+{-k`-0;l)4O}7jz!ca4UXiuEfYZT$5Joxzp~? zN?o&LZp(r05{p+~2vQT$ucA%N!(c^WH(W6JbzpTCPy5t!C1m|pu=PMszcqLvNu0Zl zF(8FXKSrqFX9~@Qu?G(~py3S*9)HW=_`rI*5E<(TD(Dv-6hj$M-zIv3+>9!$V`B&o zDuNP2x4wfIqkkt*w$I~S8xknnBX|Dn6?MoQ3grO*Fed{r$Coi@F^}#u4xN96CRXBg&`L-^12PD-3EfT?xQAL9`Jk#Ev&nH;E=B|cz0PDT zukICbHQ1a0R(sP$U(T7qkx?9&35Nv2q&9bsg2K#K52AYjb7xP5UAhLDm6jCF>!kz} zvk6CxG@WjdBG>uQ$;Zh*fOIyHPhWV28t~}Fv*o*G{o4agi!kiLAx`c2FD^ng-pehh zfW)$kQeNhDIu-Jg5mm|Ze9C}yxt8qolNaud4$=`V*l_+v0Gg@QkbN0iK_>A}8uWWX z%jjGY4@+9*7oX4~9qdALY-;vvTN6G77fGebinT!Ro3d8`(|=0)>YPQAeqwH zu}3!;Vvn!;*u@M%!lVb=0U=F*{ugqFE6~Meu*1g3O6?IZ7l}!ZjTC|+C>sz0aqs~& zW9|(6L87O`UleO!!{)UweIq?P9x-DJ8)%!ixnsPeX-(BMcRToo1_F$ZshNdjZ}&I8 zY~F$$vtA&-Zhy@c!`I%bopFHJOyi%aso!l(!WX1hkyKBZv&W&g@zs&M$u>+OOn7Qe z?V5R7hAo6W?E0i2ZH;88DUgMzTeC8>lG;Gq!ub*#@=Mqo`I>19I)1LVpWw|2Jo2${*JRR3g!XVDfqMTZV!iJj?x#Co0qnMn` zswx{|VI>9=a20VSYWgPn5IZzJ_vta~W~@Z=W$`Rb(VN4%v2uw;Li`PBrF3f+PK>OJ zjc)~>29Z=avr`u-bQCO%p&>I&yjh9*S4ucBm&DLu;atk6|CqVCS~dp=`j6G5QBXi* zAaHSS4;Smu3(m%BP=q}>$~EKJ%MDl)spWKT$}@`QjT_;XRn?Xw`pPEbu7sE322<*# zDV{x&m%oZA?T#kU3)nc{>dS=jaJP^n0`}8MZ3k*{V@}FRSe4o$-3awY5VEjc zXb(j-O)KSISiGRwk!dFs)64i#Kiy*E`-I`ue z2=bhY*WOjw(XKTtESjAJ!xW6!$}!L&YELDQ}anU8}NWwZNUN@Qt+t71{3IrrstE2xy8{m9Hw_;X>4Ij zSQuqwcjH21n6JKNobyXbtP{i6AeU89Gp_F-0sFek6JyDkQR zTf?EkENO*gkqlkG1mO8Q_gn4Tr|z0P3#e`1Rp2%gWb0_X9AWzUruJ#8zT|>e*2S(j z?0}UpWs}u(I^-n~gDVl|Y-Z7A>^gpuXXuz?&}nr{%S-5cvE8EeYG>wgnqC2HY9naI z$QFQ@QtSgd#G{m3(q^k-77%gw{3L809fc@P?jg31GHt`?LeU|g!Va(s)~Ax}fW){$ zQuAsnxeoYBGy#L5^aNeeDw<5j+Kjv5_D;&w$io#u!>NG25PPw0t~Md#yNS?0i%4A* z_wsefPHMW^p{#ZYBMF0`24YPWMlZphdj)e1m zBOfw!1O#TotJ`qc(fK%Y#G#3}>>I@|!?TH2z@7~m=^f2Z<>CQ2&5=#K5&bhif~HN@ zZd)fq5v<0+a83&uQGLmuq(YuT}^ZgH|0DA;pV zF}~rZk&PK_ksDLhfXa{tRd9sXF|g+tsfOrciO9k^9Py43_ zr$>CJcHY3tcJVDXt&4u_*q{TXj0}HhPP8X0@O>Xv9HVpujUhZA#wH$&!NXS5(%`cR zL`3ekjw*+KY*lLk^P19l)Owz3a4RMNwu$RvjB5dLfKC5Z$eJY5lQM74_VsEFu!#iU~bY<-!?#)-l;S{VT_-N|TZqDl#l zcQQl?y(QdjWNe~wWo_OWt9YqeosESHT)z2>x5R~m#Y=8De>?5Vh`Csg*|G!Xw;OI? zQ>l?Z48;=pk+Fr%e1cRU`RS3;F6&%&#ug$4uU<}*SaZUmdE|&vvG}7ZaUjb9zg&2* zLy41odTQ)w-V5SgWe5Ap0lxPEBZJ?+8{Uh^Q#i6nj2lT5aWejB1AE8F;e5Eg zv8dx&0ymDYM&z-9F`&ta+D**q`3rv#J0>Zgo60PNH`P?7F;AD zyhy@Ds|V>`XuT^AUBZzgsv?4T3VUhZQ9yH8kSu4o<41kV)Xl__;#jJ|P25Bzz{^-1 z8u<|diTxwS3&X*YXxADCa^HD&Gg zJvk(q$;GxP=DTe6O?qQg#EQaT+2<&qB|xwtwDWD05RHXM+4&F{9`4@V#YMoVxx3o6 z@hZ&u8R(Gc@L6#+IZRm(uBGRTC&2dLqD<)q~&6 zkbBWwy}_twAh9NhDL6>2HFtV&)%*=+=fejpiFe|XOOiLxuUZy64Bot>6t1+dA6l$^ z+094oV*lTe8_hADr6W>7fXYA!>5FQSus;Xm5kw&bKt6`1hOx_WrYd9Dca7&9h!Tsr zU<8!mh(e5v8HH$63h#pANC`+9j*KHH<=f)Bhht?8C4rGEDdyTbnZB4ddNX0V0??o= zjQFjw8KBS{IeSRgRz-sWN+-E>z@T4_Wq}$V?meW@Du|S;v{*(7RU-Hn3NW^&)XS)+ z9#AJuFVH_%$VUl|Z7%X+mszMYqpT0hDY-iCo?!qvk7dT4vyBox zO;kUy*PA;^KT_B^hdX4Em+RMUBiVcEv%q1iAtI~1oXWf26P4L7|l11=MSMb#I|8cHPA?s^q%b4%oeix-VV zy?>cS^Vch&wc=(zalvG*xQ$=EU=Ua?b;c0 zI$`6;#eaN-AmUmnkv_|*Mj_|PYLE0(cXA5+xwzXabhxvZ(QGzNfP~PTkGbunTLwD1 ziSQx>bhye&s8x$i)nwHuHypi2g@V{>h002}*R#e1@+BtfZN%DCMgF`gR8qmAFyC^U z^T07KcRDXOl!g-|%00GZc9Lf(hafP=cT<9@XAV(7c-D2MEVUBWq#Uwan%Ka`^ou2X z!X>6!!kN!W3^JI*!Mt&7m1&VwnjPIT=*9i@|>ABo#NolhSl&d@fr`rGs_z~kuHSfnZWB9n6O zF^*70TE~<38%}PJ-f%v6`Z6YvmI*HuxwEvLTM{8XL8oV8W{tx9*=K`v0^^*_re9Y{ z!1ou+v-hgntCmmimr0+O6%V&uMu*QTt`b6Srg=XQzvH^~4o=V7hhe|lI%s29>S!=c zt689Ol~1r=qRjEwiO0)!e7gXpMixqTmWvADx~Y@zKhq9gKvR5YGct{n7%x39{H{3d zYdAb+wGE>X?kD1B@9E0Ud+lD>>l~jQ zwZm>(g2wPp2AGXJp5{WansMq}GJ2wRJroG_Bk4DA8XTr>TFl$f$vR`SVx+i!E_$f>{r8-ud473vGlFK|-vN6$2Uicd;vP@h!1HG7MG-i1ob zY?ib#iDwieu{9pLO_JcFWI_Z8_=VH&`5isBZ|rV-Pto+3n?n&>mYfbJ|6AtV6XRo)PP`^T2WEQ_mBX8mo z*F|ZM&0)lxkm{ONN=IBMpBt!6t|II9tlVF!|W7!f9ZG6yJw}pOu|Jx`h)B7jYM}(*%%;l>UGQ*9fwd-5xMlIp>(0 z1&9nw1NwJ(==&?;^bhkq!PGs`=!C{C7}V=xjnm^v)eV`U$uNW-1i@v*e!teqP1a70 zUnRQ(vF=eY#LIop(K27$O8^|GHcP+z?z{CYSmhY(K%v0-CL6dryrQ!S`C$S=o{Z+1 z5m?+L<9SA@h*p6Qx&xw=9b7iaHGBn2O1%;{Ck?&btYcxNtD+VZXLLnGKqeKV{BHSOw4KWMC`h2Vfg0c{2&bLYUJ7b_JDmQ zpFRy<&6ClPHCuS+g~&g>_=21mC=8Njg_QhH#O>k#a7oG2FTTXYYn%{FYZsRT*NNe+ zWT}K7dl`fT67%H67qnFa{EZ$vYnZDh*xBYQB8F{QDeK-0u517-Gdn%%ob;C(3lC*9 zS~!wQJX5^^+_Q?af#ny&Z0K?WHOwzxa>RAaO2aVc#H{U~9-h{2N6~Qfk5_c}H-TcQ z?`qmuG1N|pImUF4$XUD6G~{>_EBc6T;XreWjht_hByJT%P}k%*@Q4?v?plgUDHU(| zxq%c7X~jJ`i~i)-*1Sr^joGhBkT_fqX-bakh8&fbG5{}310v#FiZD6X6)rJ3qR5fR znM@WG=|scCX`DCL)b}FB_`E|{Y|7q~lPr?F3WZmspo{-FNAZ&hPeu+YP8>s0Wd#`G zjPxB8UM@9CRdOjRmUV^OC_ej1VxC2IzUDIs-9 zXPJz7{4|(`23TXA9CSqDWc}qPZs9d-(XgSAM~b<5XSyUV)$ArUTUGoe#p}V49i^YL zG$}Si3PX+4+U9<)S5wxQ`MEm;0Uw2<~z*r@7rlGv@ z@iNxR2B$~GzG)U4)YM9F?tft=37Vl#Ox$L;ybCX6z$I*0uX_;kH^9g3?n&ziz8r;b z!~Z*2nht_KPpR2=MP$ zFTSL|+t2tneV#vm5$x=I_3Zhx=U+X4@hsSW_Wb$tozH^p-*&3ypE=6lU?>wLd9Ua3 zI4&;-#V#)ww8K*g%-?@6pa1mV{_|)5@w3m4qXFtczEhlo&p!Jv@b4M?``_>nfB&E5 znr!v^UHOVN{ulgvoh$Mm?DPK$w|f)QFvQJ2&Vlzp!Cm~>AGcr?clN$~{`~S;v_05k zy#D^!kW+L58`O2NW^npCIBs_j-onq; ztIkoU|2=j2y3;?w?p~jEIdREZtK07!oFBEi!P$BD?6lVoaDIoK-oa6;bKE{`!Wb}Z zWc%CpNk8blZ5BRs?7epP+s;9|w-+3p_GsbGduKq^fx;>}41h3_9JdbKmTscK^%nk*Y-w_1pqH~K`%w1$_9ncECRJm=ZVG$HTb4}{+_CbP zqDd<&klT6vxkORj)>TpOtCg)>`Ux50jk6KoSQOX`l@Q_{<%xWXmI`dnO*iZyrNlkt z+v5)szQ90Hl1TR?N9WVLnslkKDOjYk$SErf$-J7zmkd6TkUZr?1#HMBri>hJe}M4= zU{E9SY{f3{iY^}qiLFQiM#*=6C-Q8OF`&8^5nqea1qM6L`dXshZxZ$Z)d-j6O{}aG zq#VIdD@BnAJS1o}qTu7+K?ul^^bb}({OYuO1bC8U#5oDQ5zw<3PRI;$^SvY$H0P%1 z_2HiFVlvtG$!T-N#<=~^n+;Dnp<PcJ^{F+taCe`D{;)W_5k&2(YMhw5Tm_M4sJ} zE<(vc&g-F_fC`*=!C3wZZ6+SJ1bG-#n8c4JDK$2CXG538GqO9x%t*q3(O?!v(`j^Xiy%d6 z6(gv}4p|fzl9ShLj!FR@2T#6+KN`WBQ{Vow>Bm^r#r0nX|J3fDgm7KKz20*o&AIK8 zkeM+Ea4baci{pmrhaGxMz&SXfF_GOSY8(osNj!=g`i$eE0Y)9h+BOfqL~uKe>9ha} zn6dO9s!6J^aJFA_22m0?EKt3Ky4%!&zAmhYk#V|828IFCfi3+DshE7>=(fo$&j(~7 z1Z_#u6HJhq26&^BD)J5}c8ZHKWAC~W@KznK&CjF#7ht;_u-yUwQX+V@H<@QI2GmXG zJ7X?p^9yy`fUpmY**z%}5ClYF;dq%gS5~_1-Q7QT+p-7arDUg|gGeAwNSmB5lQ<3rcD7Cnq6%TFWUsIL2(qJ zfkJtU!FAkMsGg!XgbNrxNy&_QGcpMa>ul(^RT7n7BXBupdK9eC2J^wmlNT`X*c@{S z_UbhZf-I|QIm;6E-dU{%lO$90qP-wZNP!G49JGD;xVzh%O%Lajn%Weus9!kB zvog)aeQ`hI2+`LVuv`vZetSqlK^C*z2nCj++1bD%f=wOB04jpD@bE=O5j+ic2oJkS zEUAiSB!7uHmFV>kep5fv46-rBPrIzSl^VHT8^P+o)IQJZzh7+_i%QsW-Dy%YUm;QJ zo#IB|x7lsK3ESWG+b4(ZL)}64$ijPovXyW&2Gdv`5SqA`VW3>R8W7)U`qthvGxok|5t{I;XpfO$XLZ+N{`*1>U1v zw}q5xbgu}WuEou(CPvpXNpMh+&eg@ng@j!P_gBD#){XBj>bfP+8yW8?pi_Ky+7!Jh zp%;l&OMYM!PM+L!P{|G@k;5SI;cBhEsxGzBrLhkxTvx@MsoshLEYvpS8=Gy^gKxO; zT^Y@fEf0lzhKx<974C z#~arEIK#*$!;<5SpeQw2r5W&L1>GcP;Co?NQWdf zn(b7Z(OVPJb(r1PFLAUmNBBQ{U;o28T8LI#*Sm{kR}@j>=9BlUzx(~?YwNZ8#?wY~ zYj^MKm;2xR;}7rO{}}$ypML)3&#OeIlw0K6L3xdSv+;Dbo*z(&k$y$>;Qi(M@%!of z+56j)X7cv??$m1QQE_elv`*Z4ZM{`|_s{fBPEf6UWvMp&LX;4$S3=L~`}YGnevwA{ zhMi{P`5*&?g&rqngNqIZ{yh9mq!E}Uf)yGbUXG_(Z+3h4Zgl}2X+bzbhq;;?EK2SA z*&LJ4I|(b4^n&Z0UFX%y(ILy#UX+@p5N3pJWO~c{MTGdnI!72~T#xTc23RQki_5Tk zC&J(5wKWeQaCA(b|FYs7seF<3=DHm7C%h+g%E}tz)AiQ(wK5rqeg7Sb48-yLSU{?- z7r;=9!^CTPOT~NlKwEumW4SU17?ab=M-Io_BJ_oKKDQyAMVbVQ$OHQ7h*+>V-Nl|m zSQT9%6vb|^T5rCIXFU3v!8UK$vy^Pa$D5bYEE-Yh;Q2K6v#Agni^tlq_ku3=jGGM+ zw{E;+!6Xm)Mj3=*h&H_79pMBS*!l{b`fhNUd}MI}tHLpBI^DqRYpzi~a&G@-Zm_|G zBLyM(f(uEIj#6)JLes|wLF@1^eBJ6Cos&=nmzP6D`XpQB?r0SMqCNYH_-ZNnh;l|U zlwI+inZIcS8zfqNN^Ak^$wlred&^^ML-zC{iPuZpDKdbZhmEZ0c?mGFT#!6{ z>N=$83q~66*A=O^#`)V(AQkE>vMAO^lKdwF(uD=BI9qz2FbkcVM#25ycHsC_bi_og zy08NItjk}v1&pqoQ9tqTc%4N3FFAS@z2%k_4)y!@_E_Um^2v55kCV#QH{JwH7IpAGl79KrbtN-xhPrv-U`m2C<;m`>>k>JK3OlcY# z`q3jdJPJ_;PjZm^SDJ&_3Bj>Z;2fS=`XL@Kcls)i-xHsW`#tkqhHi36A8lQ4?qefd zj#|D0?v8945b~UjvP$kZ~?(_nx+_QI1O4%dt@1lDaU7@yuRM%ohMwV+gi5j1gzTNTI zqsSs8=SqbJ`b0nN2i>#_0x=^@@kF>ciDXMg;h5|XBDkj{8`}wXS&-$|i9VLr`9|vq zRF`S*Hg_mjtd*|Hbxgw5@%$#9LJhW{+1-VXFi#+@7>wX@lnf-Ws+F*E8H$2|!b)*7 zZmuW|BV!gaCPAj(jDIh9`ZRIKNf^$DF;Pr{Sqa{iW2Q*1qRNtlTP}pL5X@$nf}8E7 zv%G+-3Xc7Pco2S{0{XGIi^oDfr6RgF8WSGaHB@&3&Z48I(I2U9iv_);U%QJLTMZCg zqYDJ$4GHnK!Sta>%+PF^dHgHR%aJ?Z$Bt+<1l{4%F=@6uq zE0jUsTTKFfa$a9bkBIZ9Llu($XI}7Nj|8nD(O-VUo=<4rpR7t>g?decLIDjTJHg5+ zLSghCt!>a;2HSXmtfyU~=jL&Vk+N|o@OvX>*i zVQE2)6TdYu&r*>6@nmruRdUIy-3zF$|f4Hab3 zAf7%oIs}hWVJdOQG6#A`S7Lq5%39d{IvN~k8?suYlxH74j9xaMX2vK?#qS(q&vLR? z@yw+D%dR-hL?W;WjLK4J4z&$x+IM=uRGZ-@{+@){Hl)+F{;q(;DIh<-rt zmFQ_h4(bM-&b2kOT;&%6vJ-kK?5H2#QrK#IU(l!LQ5H7ypd>3EiIK1*19mPe40#C%hj%gd0#34ToM(Q6G=ez@jdn)jayHq|{?@fG zGouUG$!4Epe!DTuOjlP|S65e8*X1J3fQ;8%Vv!irmw z4o?o_(Z%Fa9crlXuh~j&(=5A}eM=q6@b`4U>J{e8Vn@D_WLf0A`dLIpDm7gUDR6k3 zH;nI+$-QR4XLXm7lO=3hXS zHXTgpt_kCL0V<}fiOen7;}Ygno5ygg6gf!NvI?70S&;r8Uyv?fOk0}PsZ%D)O_CWF z?gnw`^i#)dG)EUIZsH~;9MyE-S6X>bpPIGjcxrfiyWQTgXQvb|8JP#UO^}v3AXB>~Jf!;^Ei}W+!hP}{{M~?cg zOM-7TcYI#ZE?MQmcWjk%&?|b=A^Rjs%{W#4P4>^C9pByd3_{+4u)_IA4tO8g1F2(t zTE&+;MZFcvx&Rx35NncUjQ{$tRj1gZ(S%&tO_Cl(2st*zA_v{1KdWE&ACAuqwj1)yhZuu#2Hu#eM_lC z*f1->hqfyYIw|0_oRV_sMTv<r0O&<--$iw2}^JIXL z8YZq_4w(8E{e}ms$)mCW*}r=pCSUXyK`4hh{8nHD{|*sllTKj^ZHii0$`?LnP~-Elfo<;<55qg1YbBM51lV6OPtHHxr!x_rsO(R#iEUc9K8s!l11~6;dD=b zR#?c$!g%vpZ!YHnmjO}Yd~%dRJN>P$znsQ5afSz#Tz+ZJ8$O}ZG2e!D{cRRuuA}S< z1*uwVwKt%T=6X4J15|>a+Rt#F!oPm0S?g};`yY2tjxmDsTlijnpZk6HbpY`B z5BwhgI@mqiJ%YcG&g`5JVOYyCG2`cM%nn1BfDmxkh&II_YI8#*(Adf%7@9VbKjZ!7%%jT9-$s ztelBP=S)#`N=@Ai`qMG}XOnF-bjWR##Qf(#mgwTzXd?A7px-DzmU7;m;Kg75@>OF| zBpN0GGsOxyHv90+G0ox|r!JOM^CHDx$}4iILbNpZ;mI?hqh`U{;|6`6BtHX|m_Mkn z417Yiw_G(#{F<^-Vv@%+qZ|kc=WO#~l6%FVk7-B;{jd(dq5Kp?`yoC9n&R^Y#U)lI zTP2xzN#)$H$5cuS^^~7q@BS3N-97uBuco(yf3g+*w?bY$w=IdOAay z?P?l24;8!}@c!O%g+`(DZX{RAk_NuSSEvfm-Lyw3m&nLw4p#x4 z2~^!js~Lo+AuONW{oq&n^~aOLvu+i#E-XM8Z}faj(=nbTeJqQjx%x+kVX)%v@lPZ> zp0JAVKezj^AIE2I7gzv9e5;H1uf;-Vjz5^GPOF9QOcqzBKHm-(>5osJO|HiZ#g@&J z#GGxr)@U4>|CNs**z}p9DvOZzPxMopKatm0n3Qx}_kcFaMDyM+NV!XGd21m{1w%vW z?#_G@WtOgGl=tpSYq$7ULFCzU!*zNS*A0%k5!;qF{j>vuJQN)(qAxC$8=YHJuQH}7 zgC-=F&&q?9;rh_s5$l-!FU-pVT8NciZ_+;)WbLY_*7|es2G&lQEX|;n)jY{5jVds@Y#K#4!;aTDeq)=CIUu%&p@d?(p?`RPk}v*4;M zo}Y>ZkESbAdx4Y&bLI;Q2OBeyOJ&CP*9vFHE=JhTp^y;9heBIw6`|PloyYN&D8#Vm zop!r_4ZFwtu1UShiDNK{hePHh$7;MBqsaBJdP^6voy{+0-xb;gh#=b@I~W#sPdmnJ zWJ3P@-!{1>>k13%QLTM|_SWVf!V$5V=n|_6Hf=&K99K08}6qp}_7kh_aO# zb8*@|GwbB9RIw|QgxlsEJ_TY8o#1kUI+$jZu}WMFG!(Igk>_X#MRgt<6Y=QoF6v!! zgrK>lDHDQ6DfT-%LW+Mi#B8}Q9AV>7sQgt{35zSS<=j>08IqM0zV+Zm_&Ndh;r*%fS`(L@awjG5n;p zIvvf>5b0zuRN%s4@f~HjAe}A&WxS>yK8gn~@`CC?YRPXxw{+~1jg$kAZf<}B8Eges zQGEI833gK=Rzb0IC`rq_P&N2q zCL$;f9*2lfXlX=yo6RHK0nvwLaPqPW z;th;T{46Fp;^Y1Je0uR6%*++r=t>#ea0dA#YI3+W$rtFiQqpefWT`Z41u~8t4c93v z9o$4&LMQoNnxPvE=HEmanX0!Uhn!-0C74u9I2YeV=xWgnu&mKp+8y2e5M}k+AWdrx z&Md|>rYw?T$b9prrWzEQ3tDp1CW!^wEB-0M1MJzr;YEc+rAtJ7XROB@3T;>U$~<_!**Z@qffU)0LMG@BHE^7Dno@bz`e135)HN>s=LsTo zz;Pn^hH->f`urtAfD;sDaOjr<1KUnLo5RuM`KC;tQ1wZWXIM4$Zc@@esIJZP%{8t? zXfic9<-aPqmIOjS?j_f7(AUwMvjCOsFm4&#qp3p@^3bK2$dEG&*b*WI>nFyHM!9~= zm7qY@^;rmde&N>V;9J$05RpQ}5PNF0+$>Ah8}T;sYUHx*LRs@VzhEUBej$BHZeKd0 zV+n~p!y*;ky&H4J0*;O_ydz{|M?UDEeA;S$b2Owey(Fj<-FzhQ?EOgWR+7J^_C*fc zG9c%&uxIjdZ!=g&a10cFB&7A}mEtdqg^)R;Od{mP%=}|Ldg)K)^itK6=bJlq%LK-u z#^sCc;ONcqtMK*iPw3-N_2%%{rW=q;0uRX)WSL~HAV~B-re(t0h z)0EpzZ}ruJ>!OMG;*+d*J+8ZLnmBsO8?!!p_KJ+kRpX8M$C*%$1(~Bfl_ylZ+p!XM zN?t?iA#EHHjk9)fxy+b<7IO681(V5N{t0GNqJ2tJ>Bjlb+U*mwB0T4OZ&YRh7dC*> zN8~{1XexdphHh2=B#&fJmDw&mc6%a)e2n*}NU1Zbl}*^lvi&00bOR{UJjgy;{`@^o z%vDp%INY+Zn(&Fb}W)^kQ@4`KS6Xf5^|L`cMC>f0nEN^dYN%(fZ%~`o+d(as9*NPwW4W z_<2^S|Mc~z`cI$gKYgnI^r`;Sr}|Hy>OU=0|LK`u4Z20P2zi1Lb7xXc<{*RBf174k zD^7i9b`Z63$Ps!H;~iHtl*6E?9_pk%;s++N$W(0E_gsno%!+@yRP1z2kzqX{b~^90 zi?<=IZBL%-L>4v~u2#h8L)8R8)Y6Y_cfr{#HKia@TCDZx2G^K^tAx3 zrEbpOGS5dr9$#FGTcgYTV$Wj!7Z(<4^BsJ2%54ru9FbzUaXOM)TDB@dSbJAcfa-IRrPDF#Nt=X49Od_PA%(1Yes3XMPOqbp-a}z8_5TjO2sSyr zz73ODOLLT1tAR;aY$5{O*R54I#gwTn!FSIla^-G<&emV7Z~lZCnsh>b{_kJJWGc|` zLpL+a+iH<0T)~3ey97wp2i@nC-0}#fuUM8(c;*-INr^9Tmx?jZ5TZpw`ZB!*y+Ca%pX?z^UbZ&2YZ*((bXxt?|$_{{H_lN zZs+Qsdd@-YAeBcBU<{XCh0`15NyE4kI8-qdtW<$zmZAhk2m1)kH6^G(=R+qWken#U ziPs{;K#0Wc6)(|nqZ16{i6t1R`00?{#La+N%0}!XBNq!oItP7SEpx<#Gt=u$yS~f0 zla_{I2WbGA*{xv_lR$1z@%Rh<_48mlM&fSrjGyB(IM=nyq5*FTuHz7N1@vU(@wVJ) z#&kx{HrF<{9)Pk1D&q%`?lUEZ<=26SqWEm*Zi45lLo>nQO7Z=_XJ; zt5SWu4S+LN_8Bf7Ln+r1Cg#!T$z#Ai|RbMc+jslugJMbj=M1C)3c*FqU)oHt&+%^IXMn+cbUw&O4+R9 z2*VHzEcxQ|4lC|0N@$szORtFA z(q-B1>E7X?RNh&QR-3`+bi*CdL3XUGmfDq`d{tlhdIvJ)%WooC%>k@P8lpeDaEa@*8!%Ats1Uza^kFQ z6s9NaY{=e|n7W|}siUXSa7YRLa)OCYlFZ?_WlS$F(9e9uQD0xD=M*WEjL3IXigmnI ze5e9JU8c2AvSa;jbDfHZP*DN=ZT;@+?v}s(N3c%%WYZZ%S(e^Pu2!bJ_2n8S@L$O^ zCUxo%$Umkj@h?pc4Qs-uuThL%dmwbT%*7~f)ID+vW9FK$(UPQ&#`ziN+u)rnM4U{` zzH1hzxLi^xCoI>2UOFIhPKkSu(3eh=4d`C*{{6bi!+eeow_9F=)rtnI^BTOb zXz>31ycSVKi)dbh^NI%N^BN2)8Vu$&7*#YF&1;ZVG|1*Pm{c^F%xiE{(cq@4fn1sE z*PLjxu!4JDCUXH;Oz4FCvt>S#RZikfpLz|Xtl%K|vPv#FqQ+4)|7`PcOj}KHCVI0(sz2VUNp$(xQ{j-2tzNZY z+vBaSN4hJT`zWbZz?mel#1k3KCVK$gEI zeot88+9}xi{Zpj3+)9GZ$T_cv-vPcaM`i!yquTxTITn2qM`7pOlxAEMA-8^k&$tkk z6Qx6`3UM9Dry_ul3KE#a?-9%2wm(|M=C(=lzjAX|mOh^D(2d0M-GfiUfE*t18Px8v zzDUk@b7pE|*N{}9jEGHvT0tbEo3Li0MZB~cS9nQ9Y5k&GYLllrZ)}&RUtdzTO{`d9 zOUC&Xm!{~vN@)E|-~8g(=L?;}>ORbWlB$Q(%r8ChR*Qw%eLNw1J;;}KdzEDk+oTfr zBgv<2;1F{{x58!7hsmjwNwE!_Q*!6jGF@@>UdjU)0 z!03NcMWRIL`5MzSBd+yDVo~l;AB9!RY|-FZFuc7A)3pi`lCOT7%)?|ChV|X=W3t;| zm1b*I)}4QGyNiGx%yE@$D-ET^66=!mx!-efk`yAL<*0WLU7e`BR!(XuPP>X`G`^=_BTo<-ST9u*8+`AN) z4ap};-<>x~XWiGQKkV6*#ggD3?*?FEGInQ^`}JE#eZg}MJQhp1(SNAkNITe9&kk`5 z8KzNR^6GjEnABTxjmgPK%4wFl*gXwS#FGyny-QpLhvb>$1|S2cIJDeL7C1zNl=SQP z8q>DXMI*MRs_K-}GU5ZKMCP(LCVk5g;=IjtQjd}Fc=zn^hi>@e-uJsF;aRxfJvcn> z?yFX($A4Y!sHf|lkgT2n3#Z@k;j&FG8lOoGj=Fj_P)%F&u)4=aCGQeN9>Tz8M*cKB zTqXX|1Gkq~@Ui3O;gFmu?C<6#d6)Wf__;4$9liN(_Xv|@93H=N2vyjD6v~rIdR4E% zc0Za#RO6?OJ4f9bTPhP$_?XN`rT=m9DG=;-8H)W_BAJ!S4z9Ng@vNNp$I(u4D&>>i zUlv^=?H_})i`DD^u}=|V-p`WpU+hN*FCP;E@|gIquh&1tfBYdopW?s%SN|*(|Mfwu zf6@BiSl@W@Whwsa%TMuNf5=ZU{_C4h@n4_fzdpr(eTx736#w-p{)^(jt}o;1F!n;g zUPsy0G!MSV7Y!$+(!8**pzef;HD<*MSxp1ubb~JdbQOcX2eOUeNV&aUd5AlKzyV$8mZW&6&^~;o9Xp6Zf{x- zK1zmDvLAAmwDWt4MwmE0DL^uq*j_9<71p!1ok0=APN#c^O(v(@nP*Ct zo@Nzyq7FmqkP{l>ExNR6I-|Tg#ShQ$yVq1nsgV2K0x?-@idfeTD#j~s#urTwV#|!7 zWK#0!VC+g>#Ge}sSa^|ZG@uXXi6k@f9HCOpw)gU-yl$Ib;`yHpOYVt&MYOxJ;8Jodk!yUb390n``I4M7UqSRg7q2aXY{mP^!L z#zkC2jb>3KBq+Ka%0DE)by>ldQ|(8|Jd0M*rr#^ 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

    @fw6^%Y!w+zi^nG} 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�dHjc4`sDimhyG;A|Mf`!IQd_xHkMZ6@(*L5 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 z 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

      %6` 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 (PS+m&fL@ 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 literal 0 HcmV?d00001 diff --git a/vendor/rapidjson.cmake b/vendor/rapidjson.cmake new file mode 100644 index 0000000000..371d04726d --- /dev/null +++ b/vendor/rapidjson.cmake @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12) +include(ExternalProject) + +# Add an external project to build rapidjson +externalproject_add( + rapidjson + PREFIX "${PROJECT_BINARY_DIR}" + URL "file://${VENDOR_DIRECTORY}/rapidjson-0.11.tgz" + URL_MD5 "a648ac1a286a85f0741151b9c8db56a4" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + BUILD_IN_SOURCE 1 + INSTALL_COMMAND "" + ALWAYS 1 +) + +# Set some useful variables based on the source directory +externalproject_get_property(rapidjson SOURCE_DIR) +set(RAPIDJSON_INCLUDE_DIRS "${SOURCE_DIR}/include") diff --git a/vendor/re2.cmake b/vendor/re2.cmake index dc4d0627a5..0053931a55 100644 --- a/vendor/re2.cmake +++ b/vendor/re2.cmake @@ -15,7 +15,7 @@ endif() # Add an external project to build re2 externalproject_add( re2 - PREFIX "${VENDOR_DIRECTORY}" + PREFIX "${PROJECT_BINARY_DIR}" URL "file://${VENDOR_DIRECTORY}/re2-20140304.tgz" URL_MD5 "e82a6491efdf2bc928dc3779abcb3bc8" CONFIGURE_COMMAND "" @@ -39,4 +39,4 @@ set_target_properties(libre2 IMPORTED_LOCATION "${RE2_SHARED_OBJECT_PATH}" ) add_dependencies(libre2 re2) -install(FILES "${RE2_SHARED_OBJECT_PATH}" DESTINATION .) \ No newline at end of file +install(FILES "${RE2_SHARED_OBJECT_PATH}" DESTINATION .) From 4b8c7a61b49ae2dd52b5074bc0e4d190e13a94a1 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Mon, 7 Apr 2014 16:10:51 -0700 Subject: [PATCH 1762/3753] (maint) Handle case where :install wasn't in the options file --- acceptance/Rakefile | 1 + 1 file changed, 1 insertion(+) diff --git a/acceptance/Rakefile b/acceptance/Rakefile index 7f592f7848..7bb7dce7a0 100644 --- a/acceptance/Rakefile +++ b/acceptance/Rakefile @@ -71,6 +71,7 @@ def beaker_test(mode = :packages, options = {}) if mode == :git puppet_fork = ENV['FORK'] || 'puppetlabs' git_server = ENV['GIT_SERVER'] || 'github.com' + final_options[:install] ||= [] final_options[:install] << "git://#{git_server}/#{puppet_fork}/facter.git##{sha}" end From b444b32328e3ddc5f6ef30752333eebd385a8b0b Mon Sep 17 00:00:00 2001 From: Daniele Sluijters Date: Wed, 9 Apr 2014 20:45:12 +0200 Subject: [PATCH 1763/3753] debian/control: Recommend lsb-release. Recommending lsb-release should be enough to have it pulled in by default on most Debian distributions solving the issue where the lsb* facts and those depending on them weren't working. --- ext/debian/control | 1 + 1 file changed, 1 insertion(+) diff --git a/ext/debian/control b/ext/debian/control index 468349f890..7775fa83c6 100644 --- a/ext/debian/control +++ b/ext/debian/control @@ -9,6 +9,7 @@ Homepage: http://www.puppetlabs.com Package: facter Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, ruby | ruby-interpreter, dmidecode [i386 amd64 ia64], virt-what, pciutils +Recommends: lsb-release Description: Ruby module for collecting simple facts about a host operating system Some of the facts are preconfigured, such as the hostname and the operating system. Additional facts can be added through simple Ruby scripts. From 204e6c335917afb6fc4743203625a0acd87eccfe Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Wed, 9 Apr 2014 14:42:31 -0700 Subject: [PATCH 1764/3753] (CFACT-3) Refactor library and implement kernel/os facts. NOTE: this removes all but the kernel-related facts and JSON output. the removed facts and JSON output will be added back in subsequent commits. Refactoring the cfacter library to seperate out types into various related namespaces. Adding an execution framework for running arbitrary external commands from fact resolutions. Adding a resolver type responsible for resolving one or more facts. Adding a fact type that represents a name-value pair. Adding a values that represent simple strings or arrays ("hash" value still TODO). Adding utility string functions. The following facts were added: kernel kernelmajrelease kernelrelease kernelversion lsbdistid (Linux) operatingsystem --- CMakeLists.txt | 5 +- README.md | 28 +- exe/CMakeLists.txt | 2 +- exe/cfacter.cc | 31 +- gem/lib/cfacter.rb | 1 - lib/CMakeLists.txt | 43 +- lib/cfacterimpl.cc | 928 ------------------ lib/cfacterimpl.h | 29 - lib/cfacterlib.cc | 100 -- lib/{ => inc}/cfacterlib.h | 5 +- lib/inc/execution/execution.hpp | 181 ++++ lib/inc/facts/array_value.hpp | 64 ++ lib/inc/facts/fact.hpp | 59 ++ lib/inc/facts/fact_map.hpp | 136 +++ lib/inc/facts/fact_resolver.hpp | 102 ++ lib/inc/facts/linux/lsb_resolver.hpp | 43 + .../facts/linux/operating_system_resolver.hpp | 33 + lib/inc/facts/posix/kernel_resolver.hpp | 63 ++ .../facts/posix/operating_system_resolver.hpp | 42 + lib/inc/facts/string_value.hpp | 58 ++ lib/inc/facts/value.hpp | 46 + lib/inc/util/file.hpp | 30 + lib/inc/util/option_set.hpp | 228 +++++ lib/inc/util/posix/scoped_descriptor.hpp | 34 + lib/inc/util/scoped_resource.hpp | 79 ++ lib/inc/util/string.hpp | 145 +++ lib/scoped_resource.hpp | 89 -- lib/src/cfacterlib.cc | 29 + lib/src/execution/posix/execution.cc | 175 ++++ lib/src/facts/array_value.cc | 28 + lib/src/facts/fact.cc | 15 + lib/src/facts/fact_map.cc | 102 ++ lib/src/facts/fact_resolver.cc | 17 + lib/src/facts/linux/fact.cc | 18 + lib/src/facts/linux/lsb_resolver.cc | 26 + .../facts/linux/operating_system_resolver.cc | 174 ++++ lib/src/facts/osx/fact.cc | 16 + lib/src/facts/posix/kernel_resolver.cc | 74 ++ .../facts/posix/operating_system_resolver.cc | 27 + lib/src/facts/string_value.cc | 12 + lib/src/util/posix/file.cc | 28 + lib/src/util/string.cc | 102 ++ 42 files changed, 2251 insertions(+), 1196 deletions(-) delete mode 100644 lib/cfacterimpl.cc delete mode 100644 lib/cfacterimpl.h delete mode 100644 lib/cfacterlib.cc rename lib/{ => inc}/cfacterlib.h (53%) create mode 100644 lib/inc/execution/execution.hpp create mode 100644 lib/inc/facts/array_value.hpp create mode 100644 lib/inc/facts/fact.hpp create mode 100644 lib/inc/facts/fact_map.hpp create mode 100644 lib/inc/facts/fact_resolver.hpp create mode 100644 lib/inc/facts/linux/lsb_resolver.hpp create mode 100644 lib/inc/facts/linux/operating_system_resolver.hpp create mode 100644 lib/inc/facts/posix/kernel_resolver.hpp create mode 100644 lib/inc/facts/posix/operating_system_resolver.hpp create mode 100644 lib/inc/facts/string_value.hpp create mode 100644 lib/inc/facts/value.hpp create mode 100644 lib/inc/util/file.hpp create mode 100644 lib/inc/util/option_set.hpp create mode 100644 lib/inc/util/posix/scoped_descriptor.hpp create mode 100644 lib/inc/util/scoped_resource.hpp create mode 100644 lib/inc/util/string.hpp delete mode 100644 lib/scoped_resource.hpp create mode 100644 lib/src/cfacterlib.cc create mode 100644 lib/src/execution/posix/execution.cc create mode 100644 lib/src/facts/array_value.cc create mode 100644 lib/src/facts/fact.cc create mode 100644 lib/src/facts/fact_map.cc create mode 100644 lib/src/facts/fact_resolver.cc create mode 100644 lib/src/facts/linux/fact.cc create mode 100644 lib/src/facts/linux/lsb_resolver.cc create mode 100644 lib/src/facts/linux/operating_system_resolver.cc create mode 100644 lib/src/facts/osx/fact.cc create mode 100644 lib/src/facts/posix/kernel_resolver.cc create mode 100644 lib/src/facts/posix/operating_system_resolver.cc create mode 100644 lib/src/facts/string_value.cc create mode 100644 lib/src/util/posix/file.cc create mode 100644 lib/src/util/string.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index e158e16541..860fec61fe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,8 +2,8 @@ cmake_minimum_required(VERSION 2.8.12) project(CFACTER) set(CFACTER_VERSION_MAJOR 0) -set(CFACTER_VERSION_MINOR 0) -set(CFACTER_VERSION_PATCH 1) +set(CFACTER_VERSION_MINOR 1) +set(CFACTER_VERSION_PATCH 0) # Generate a file containing the above version numbers configure_file ( @@ -20,7 +20,6 @@ elseif(UNIX) set(CMAKE_INSTALL_RPATH "$ORIGIN") endif() - # Set the default install path if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(CMAKE_INSTALL_PREFIX "/opt/cfacter" CACHE PATH "default install path" FORCE) diff --git a/README.md b/README.md index 1950e03096..59e897bb44 100644 --- a/README.md +++ b/README.md @@ -15,24 +15,24 @@ All examples start by assuming the current directory is the root of the repo. Before building cfacter, use `cmake` to generate build files: -`$ mkdir release` -`$ cd release` -`$ cmake ..` + $ mkdir release + $ cd release + $ cmake .. Build ----- To build cfacter, use 'make': -`$ cd release` -`$ make` + $ cd release + $ make To build cfacter with debug information: -`$ mkdir debug` -`$ cd debug` -`$ cmake -DCMAKE_BUILD_TYPE=Debug ..` -`$ make` + $ mkdir debug + $ cd debug + $ cmake -DCMAKE_BUILD_TYPE=Debug .. + $ make Run --- @@ -50,16 +50,16 @@ Install You can install cfacter into your system: -`$ cd release` -`$ make && sudo make install` + $ cd release + $ make && sudo make install By default, this will install cfacter into `/opt/cfacter`. To install to a different location, set the install prefix: -`$ cd release` -`$ cmake -DCMAKE_INSTALL_PREFIX=~/cfacter ..` -`$ make clean install` + $ cd release + $ cmake -DCMAKE_INSTALL_PREFIX=~/cfacter .. + $ make clean install This would install cfacter into `~/cfacter`. diff --git a/exe/CMakeLists.txt b/exe/CMakeLists.txt index 11b4c6c3c0..371f8d1edc 100644 --- a/exe/CMakeLists.txt +++ b/exe/CMakeLists.txt @@ -11,7 +11,7 @@ add_subdirectory(${LIBCFACTER_DIR} ${LIBCFACTER_DIR}) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra -Werror -Wno-unused-parameter") include_directories( - ${LIBCFACTER_DIR} + ${LIBCFACTER_DIR}/inc ) add_executable(cfacter ${CFACTER_SOURCES}) diff --git a/exe/cfacter.cc b/exe/cfacter.cc index dcc1ba2845..1815ca4ca9 100644 --- a/exe/cfacter.cc +++ b/exe/cfacter.cc @@ -1,9 +1,11 @@ -#include "cfacterlib.h" +#include #include "../version.h" #include #include +#include using namespace std; +using namespace cfacter::facts; void help() { @@ -71,29 +73,14 @@ int main(int argc, char **argv) // short option 'j' case 'j': - // do json, that's the only output option atm + // TODO: properly support this break; } } - loadfacts(); - -#define MAX_LEN_FACTS_JSON_STRING (1024 * 1024) // go crazy here - char facts_json[MAX_LEN_FACTS_JSON_STRING]; - - if (optind == argc) { // display all facts - if (to_json(facts_json, MAX_LEN_FACTS_JSON_STRING) < 0) { - cout << "Wow, that's a lot of facts" << endl; - exit(1); - } - cout << facts_json << endl; - } else { - // display requested fact(s) - while (optind < argc) { - // fix me - if (value(argv[optind], facts_json, MAX_LEN_FACTS_JSON_STRING) == 0) - cout << facts_json << endl; - ++optind; - } - } + // TODO: re-implement JSON output + fact_map::instance().each([](string const& name, value const* val) { + cout << name << " => " << (!val ? "" : val->to_string()) << "\n"; + return false; + }); } diff --git a/gem/lib/cfacter.rb b/gem/lib/cfacter.rb index 16e17d3756..3c7bced171 100644 --- a/gem/lib/cfacter.rb +++ b/gem/lib/cfacter.rb @@ -15,7 +15,6 @@ class Constants public attach_function :clear, [], :void - attach_function :loadfacts, [], :void def self.to_hash ptr = FFI::MemoryPointer.new(:char, Constants::JSON_STRING_MAX_LEN) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 37bb64f3df..d4b1ba0a13 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -2,11 +2,7 @@ cmake_minimum_required(VERSION 2.8.12) include(${VENDOR_DIRECTORY}/re2.cmake) include(${VENDOR_DIRECTORY}/rapidjson.cmake) -set(CFACTERLIB_SOURCES - "${CMAKE_CURRENT_LIST_DIR}/cfacterlib.cc" - "${CMAKE_CURRENT_LIST_DIR}/cfacterimpl.cc" -) - +# Set compiler-specific flags if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra -Werror -Wno-unused-parameter -Wno-tautological-constant-out-of-range-compare") elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") @@ -15,13 +11,48 @@ elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel") elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") endif() +# Set the common (platform-independent) sources +set(LIBCFACTER_COMMON_SOURCES + "${CMAKE_CURRENT_LIST_DIR}/src/cfacterlib.cc" + "${CMAKE_CURRENT_LIST_DIR}/src/facts/array_value.cc" + "${CMAKE_CURRENT_LIST_DIR}/src/facts/fact.cc" + "${CMAKE_CURRENT_LIST_DIR}/src/facts/fact_map.cc" + "${CMAKE_CURRENT_LIST_DIR}/src/facts/fact_resolver.cc" + "${CMAKE_CURRENT_LIST_DIR}/src/facts/string_value.cc" + "${CMAKE_CURRENT_LIST_DIR}/src/util/string.cc" +) + +# Set the POSIX sources if on a POSIX platform +if (UNIX) + set(LIBCFACTER_POSIX_SOURCES + "${CMAKE_CURRENT_LIST_DIR}/src/execution/posix/execution.cc" + "${CMAKE_CURRENT_LIST_DIR}/src/facts/posix/kernel_resolver.cc" + "${CMAKE_CURRENT_LIST_DIR}/src/facts/posix/operating_system_resolver.cc" + "${CMAKE_CURRENT_LIST_DIR}/src/util/posix/file.cc" + ) +endif() + +# Set the platform-specific sources +if ("${CMAKE_SYSTEM_NAME}" MATCHES "Darwin") + set(LIBCFACTER_PLATFORM_SOURCES + "${CMAKE_CURRENT_LIST_DIR}/src/facts/osx/fact.cc" + ) +elseif ("${CMAKE_SYSTEM_NAME}" MATCHES "Linux") + set(LIBCFACTER_PLATFORM_SOURCES + "${CMAKE_CURRENT_LIST_DIR}/src/facts/linux/fact.cc" + "${CMAKE_CURRENT_LIST_DIR}/src/facts/linux/operating_system_resolver.cc" + "${CMAKE_CURRENT_LIST_DIR}/src/facts/linux/lsb_resolver.cc" + ) +endif() + # Add the library target without a prefix (name already has the 'lib') -add_library(libcfacter SHARED ${CFACTERLIB_SOURCES}) +add_library(libcfacter SHARED ${LIBCFACTER_COMMON_SOURCES} ${LIBCFACTER_POSIX_SOURCES} ${LIBCFACTER_PLATFORM_SOURCES}) set_target_properties(libcfacter PROPERTIES PREFIX "") install(TARGETS libcfacter DESTINATION .) # Set include directories include_directories( + inc ${RE2_INCLUDE_DIRS} ${RAPIDJSON_INCLUDE_DIRS} ) diff --git a/lib/cfacterimpl.cc b/lib/cfacterimpl.cc deleted file mode 100644 index c5e3fa57b8..0000000000 --- a/lib/cfacterimpl.cc +++ /dev/null @@ -1,928 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifdef __linux__ -#include -#endif -#ifdef __APPLE__ -#include -#endif -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "cfacterlib.h" -#include "cfacterimpl.h" -#include "scoped_resource.hpp" - -using namespace std; -using namespace cfacter; - -// For case-insensitive strings, define ci_string -// Thank you, Herb Sutter: http://www.gotw.ca/gotw/029.htm -// -struct ci_char_traits : public char_traits -// just inherit all the other functions -// that we don't need to override -{ - static bool eq(char c1, char c2) - { - return toupper(c1) == toupper(c2); - } - - static bool ne(char c1, char c2) - { - return toupper(c1) != toupper(c2); - } - - static bool lt(char c1, char c2) - { - return toupper(c1) < toupper(c2); - } - - static int compare(char const* s1, char const* s2, size_t n) - { - return strncasecmp(s1, s2, n); - // if available on your compiler, - // otherwise you can roll your own - } - - static char const* find(char const* s, int n, char a) - { - while (n-- > 0 && toupper(*s) != toupper(a)) { - ++s; - } - return s; - } -}; - -typedef basic_string ci_string; - -// trim from start -static inline string& ltrim(string& s) -{ - s.erase(s.begin(), find_if(s.begin(), s.end(), not1(ptr_fun(isspace)))); - return s; -} - -// trim from end -static inline string& rtrim(string& s) -{ - s.erase(find_if(s.rbegin(), s.rend(), not1(ptr_fun(isspace))).base(), s.end()); - return s; -} - -// trim from both ends -static inline string& trim(string& s) -{ - return ltrim(rtrim(s)); -} - -static inline void tokenize(string const& s, vector& tokens) -{ - istringstream iss(s); - copy(istream_iterator(iss), - istream_iterator(), - back_inserter >(tokens)); -} - -static inline void split(string const& s, char delim, vector& elems) -{ - stringstream ss(s); - string item; - while (getline(ss, item, delim)) { - elems.push_back(item); - } -} - -static bool file_exist(string const& filename) -{ - struct stat buffer; - return stat (filename.c_str(), &buffer) == 0; -} - -// handy for some /proc and /sys files -string read_oneline_file(string const& file_path) -{ - ifstream oneline_file(file_path.c_str(), ifstream::in); - string line; - getline(oneline_file, line); - return line; -} - -void get_network_facts(fact_map& facts) -{ - scoped_descriptor sock(socket(AF_INET, SOCK_DGRAM, 0)); - - if (sock < 0) { - perror("socket"); - exit(1); - } - - // find number of interfaces. - ifconf ifc; - memset(&ifc, 0, sizeof(ifc)); - if (ioctl(sock, SIOCGIFCONF, &ifc) < 0) { - perror("ioctl"); - exit(1); - } - - vector ifreqs(ifc.ifc_len / sizeof(ifreq)); - ifc.ifc_ifcu.ifcu_req = ifreqs.data(); - - if (ioctl(sock, SIOCGIFCONF, &ifc) < 0) { - perror("ioctl SIOCGIFCONF"); - exit(1); - } - - string interfaces = ""; - - bool primaryInterfacePrinted = false; - for (auto& req : ifreqs) { - auto const& ip_addr = reinterpret_cast(&req.ifr_addr)->sin_addr; - - // no idea what the real algorithm is to identify the unmarked - // interface, i.e. the one that facter reports as just 'ipaddress' - // here just take the first one that's not 'lo' - bool primaryInterface = false; - if (!primaryInterfacePrinted && strcmp(req.ifr_name, "lo")) { - // this is the chosen interface - primaryInterface = true; - primaryInterfacePrinted = true; - } - - // build up 'interfaces' fact as we go - if (!interfaces.empty()) { - interfaces += ","; - } - interfaces += req.ifr_name; - - char const* ipaddress = inet_ntoa(ip_addr); - facts[string("ipaddress_") + req.ifr_name] = ipaddress; - if (primaryInterface) { - facts["ipaddress"] = ipaddress; - } - - // mtu - if (ioctl(sock, SIOCGIFMTU, &req) < 0) { - perror("ioctl SIOCGIFMTU"); - exit(1); - } - - facts[string("mtu_") + req.ifr_name] = to_string(req.ifr_mtu); - if (primaryInterface) {} // no unmarked version of this network fact - - // netmask and network are both derived from the same ioctl - if (ioctl(sock, SIOCGIFNETMASK, &req) < 0) { - perror("ioctl SIOCGIFNETMASK"); - exit(1); - } - -#ifndef __APPLE__ // ifr_netmask isn't supported, might need to use SC library - // netmask - auto const& netmask_addr = reinterpret_cast(&req.ifr_netmask)->sin_addr; - const char *netmask = inet_ntoa(netmask_addr); - facts[string("netmask_") + req.ifr_name] = netmask; - if (primaryInterface) - facts["netmask"] = netmask; - - // mess of casting to get the network address - in_addr network_addr; - network_addr.s_addr = - (in_addr_t) uint32_t(netmask_addr.s_addr) & uint32_t(ip_addr.s_addr); - string network = inet_ntoa(network_addr); - facts[string("network_") + req.ifr_name] = network; - if (primaryInterface) - facts["network"] = network; -#endif - -#ifndef __APPLE__ // SIOCGIFHWADDR isn't supported, might need to use SC library - // and the mac address (but not for loopback) - if (strcmp(req.ifr_name, "lo")) { - if (ioctl(sock, SIOCGIFHWADDR, &req) < 0) { - perror("ioctl SIOCGIFHWADDR"); - exit(1); - } - - // extract mac into a string, okay a char array - uint8_t *mac_bytes = reinterpret_cast((reinterpret_cast(&req.ifr_hwaddr))->sa_data); - char mac_address[18]; - snprintf(mac_address, sizeof(mac_address), - "%02x:%02x:%02x:%02x:%02x:%02x", - mac_bytes[0], mac_bytes[1], mac_bytes[2], - mac_bytes[3], mac_bytes[4], mac_bytes[5]); - - // and get it out - facts[string("macaddress_") + req.ifr_name] = mac_address; - if (primaryInterface) - facts["macaddress"] = mac_address; - } -#endif - } - - facts["interfaces"] = interfaces; -} - -void get_kernel_facts(fact_map& facts) -{ -#ifdef __linux__ - // this is linux-only, so there you have it - facts["kernel"] = "Linux"; - string kernelrelease = read_oneline_file("/proc/sys/kernel/osrelease"); - facts["kernelrelease"] = kernelrelease; - string kernelversion = kernelrelease.substr(0, kernelrelease.find("-")); - facts["kernelversion"] = kernelversion; - string kernelmajversion = kernelversion.substr(0, kernelversion.rfind(".")); - facts["kernelmajversion"] = kernelmajversion; -#else -#ifdef __APPLE__ - facts["kernel"] = "Darwin"; -#endif -#endif -} - -static void get_lsb_facts(fact_map& facts) -{ - ifstream lsb_release_file("/etc/lsb-release", ifstream::in); - string line; - while (getline(lsb_release_file, line)) { - unsigned sep = line.find("="); - string key = line.substr(0, sep); - string value = line.substr(sep + 1, string::npos); - - if (key == "DISTRIB_ID") { - facts["lsbdistid"] = value; - facts["operatingsystem"] = value; - facts["osfamily"] = "Debian"; - } else if (key == "DISTRIB_RELEASE") { - facts["lsbdistrelease"] = value; - facts["operatingsystemrelease"] = value; - facts["lsbmajdistrelease"] = value.substr(0, value.find(".")); - } else if (key == "DISTRIB_CODENAME") { - facts["lsbdistcodename"] = value; - } else if (key == "DISTRIB_DESCRIPTION") { - facts["lsbdistdescription"] = value; - } - } -} - -// gonna need to pick a regex library to do os facts rights given all the variants -// for now, just fedora ;> - -static void get_redhat_facts(fact_map& facts) -{ - if (file_exist("/etc/redhat-release")) { - facts["osfamily"] = "RedHat"; - string redhat_release = read_oneline_file("/etc/redhat-release"); - vector tokens; - tokenize(redhat_release, tokens); - if (tokens.size() >= 2 && tokens[0] == "Fedora" && tokens[1] == "release") { - facts["operatingsystem"] = "Fedora"; - if (tokens.size() >= 3) { - facts["operatingsystemrelease"] = tokens[2]; - facts["operatingsystemmajrelease"] = tokens[2]; - } - } else { - facts["operatingsystem"] = "RedHat"; - } - } -} - -void get_operatingsystem_facts(fact_map& facts) -{ - get_lsb_facts(facts); - get_redhat_facts(facts); -} - -void get_uptime_facts(fact_map& facts) -{ - string uptime = read_oneline_file("/proc/uptime"); - unsigned int uptime_seconds; - sscanf(uptime.c_str(), "%ud", &uptime_seconds); - unsigned int uptime_hours = uptime_seconds / 3600; - unsigned int uptime_days = uptime_hours / 24; - facts["uptime_seconds"] = to_string(uptime_seconds); - facts["uptime_hours"] = to_string(uptime_hours); - facts["uptime_days"] = to_string(uptime_days); - facts["uptime"] = to_string(uptime_days) + " days"; -} - -string popen_stdout(string const& cmd) -{ - FILE *cmd_fd = popen(cmd.c_str(), "r"); - string cmd_output = ""; - char buf[1024]; - size_t bytesRead; - while ((bytesRead = fread(buf, 1, sizeof(buf) - 1, cmd_fd))) { - buf[bytesRead] = 0; - cmd_output += buf; - } - pclose(cmd_fd); - return cmd_output; -} - -void get_virtual_facts(fact_map& facts) -{ - // poked at the real facter's virtual support, some combo of file existence - // plus lspci plus dmidecode - - // instead of parsing all of lspci, how about looking for vendors in lspci -n? - // or walking the /sys/bus/pci/devices files. or don't sweat it, the total - // lspci time is ~40 ms. - - // virtual could be discovered in lots of places so requires some special handling - - facts["is_virtual"] = "false"; - facts["virtual"] = "physical"; -} - -// placeholders for some hardwired facts, cuz not sure what to do with them -void get_hardwired_facts(fact_map& facts) -{ - facts["ps"] = "ps -ef"; // what is this? - facts["uniqueid"] = "007f0101"; // ?? -} - - -// versions of things we don't have if we're not running ruby -// omit or 'undef' or ...? for now, omit but collect them here -void get_ruby_lib_versions(fact_map& facts) -{ - /* - facts["puppetversion => undef"; - facts["augeasversion => undef"; - facts["rubysitedir => undef"; - facts["rubyversion => undef"; - */ -} - -// block devices -void get_blockdevice_facts(fact_map& facts) -{ - string blockdevices = ""; - - DIR* sys_block_dir = opendir("/sys/block"); - if (!sys_block_dir) { - return; - } - - dirent* bd; - while ((bd = readdir(sys_block_dir))) { - bool real_block_device = false; - string device_dir_path = "/sys/block/"; - device_dir_path += bd->d_name; - - DIR *device_dir = opendir(device_dir_path.c_str()); - dirent* subdir; - while ((subdir = readdir(device_dir))) { - if (strcmp(subdir->d_name, "device") == 0) { - // we have a winner - real_block_device = true; - break; - } - } - - if (!real_block_device) continue; - - // add it to the blockdevices list, careful with the comma - if (!blockdevices.empty()) - blockdevices += ","; - blockdevices += bd->d_name; - - string model_file = "/sys/block/" + string(bd->d_name) + "/device/model"; - facts[string("blockdevice_") + bd->d_name + "_model"] = - read_oneline_file(model_file); - - string vendor_file = "/sys/block/" + string(bd->d_name) + "/device/vendor"; - facts[string("blockdevice_") + bd->d_name + "_vendor"] = - read_oneline_file(vendor_file); - - string size_file = "/sys/block/" + string(bd->d_name) + "/size"; - string size_line = read_oneline_file(size_file); - int64_t size; - sscanf(size_line.c_str(), "%" SCNd64, &size); - facts[string("blockdevice_") + bd->d_name + "_size"] = to_string(size * 512); - } - - facts["blockdevices"] = blockdevices; -} - -void get_misc_facts(fact_map& facts) -{ - facts["path"] = getenv("PATH"); - string whoami = popen_stdout("whoami"); - facts["id"] = trim(whoami); - - // timezone - char tzstring[16]; - time_t t = time(nullptr); - tm loc; - localtime_r(&t, &loc); - strftime(tzstring, sizeof(tzstring), "%Z", &loc); - facts["timezone"] = tzstring; -} - -// get just one fact, optionally in two formats -static void get_mem_fact(string const& fact_name, int fact_value, fact_map& facts, - bool get_mb_variant = true) -{ - float fact_value_scaled = fact_value / 1024.0; - char float_buf[32]; - - if (get_mb_variant) { - snprintf(float_buf, sizeof(float_buf) - 1, "%.2f", fact_value_scaled); - facts[string(fact_name) + "_mb"] = float_buf; - } - - int scale_index; - for (scale_index = 0; - fact_value_scaled > 1024.0; - fact_value_scaled /= 1024.0, ++scale_index) {} - - string scale[4] = {"MB", "GB", "TB", "PB"}; // oh yeah, petabytes ... - - snprintf(float_buf, sizeof(float_buf) - 1, "%.2f", fact_value_scaled); - facts[fact_name] = string(float_buf) + scale[scale_index]; -} - -void get_mem_facts(fact_map& facts) -{ - ifstream oneline_file("/proc/meminfo", ifstream::in); - string line; - - // The MemFree fact is the sum of MemFree + Buffer + Cached from /proc/meninfo, - // so sum that one as we go, and get it out at the end. - // The other three memory facts are straight from /proc/meminfo, so get those - // as we go. - // All four facts are geted in two formats: - // _mb => %.2f - // => %.2f %s (where the suffix string is one of MB/GB/TB) - // And then there is a ninth fact, 'memorytotal', which is the same as 'memorysize'. - // - - // NB: this all assumes that all values are in KB. - - unsigned int memoryfree = 0; - - while (getline(oneline_file, line)) { - vector tokens; - tokenize(line, tokens); - if (tokens.size() < 3) continue; // should never happen - - if (tokens[0] == "MemTotal:") { - int mem_total = atoi(tokens[1].c_str()); - get_mem_fact("memorysize", mem_total, facts); - get_mem_fact("memorytotal", mem_total, facts, false); - } else if (tokens[0] == "MemFree:" || tokens[0] == "Cached:" || tokens[0] == "Buffers:") { - memoryfree += atoi(tokens[1].c_str()); - } else if (tokens[0] == "SwapTotal:") { - get_mem_fact("swapsize", atoi(tokens[1].c_str()), facts); - } else if (tokens[0] == "SwapFree:") { - get_mem_fact("swapfree", atoi(tokens[1].c_str()), facts); - } - } - - get_mem_fact("memoryfree", memoryfree, facts); -} - -static string get_selinux_path() -{ - static string selinux_path = ""; - static bool inited = false; - - if (inited) - return selinux_path; - - ifstream mounts("/proc/self/mounts", ifstream::in); - string line; - - while (getline(mounts, line)) { - vector tokens; - tokenize(line, tokens); - if (tokens.size() < 2) continue; - if (tokens[0] != "selinuxfs") continue; - - selinux_path = tokens[1]; - break; - } - - inited = true; - - return selinux_path; -} - -static bool selinux() -{ - string selinux_path = get_selinux_path(); - if (selinux_path.empty()) - return false; - - string selinux_enforce_path = selinux_path + "/enforce"; - string security_attr_path = "/proc/self/attr/current"; - if (file_exist(selinux_enforce_path) && file_exist(security_attr_path) && - read_oneline_file(security_attr_path) != "kernel") - return true; - - return false; -} - -void get_selinux_facts(fact_map& facts) -{ - if (!selinux()) { - facts["selinux"] = "false"; - return; - } - - facts["selinux"] = "true"; - - // defaults from facter - facts["selinux_enforced"] = "false"; - facts["selinux_policyversion"] = "unknown"; - facts["selinux_current_mode"] = "unknown"; - facts["selinux_config_mode"] = "unknown"; - facts["selinux_config_policy"] = "unknown"; - facts["selinux_mode"] = "unknown"; - - string selinux_path = get_selinux_path(); - - string selinux_enforce_path = selinux_path + "/enforce"; - if (file_exist(selinux_enforce_path)) - facts["selinux_enforced"] = ((read_oneline_file(selinux_enforce_path) == "1") ? "true" : "false"); - - string selinux_policyvers_path = selinux_path + "/policyvers"; - if (file_exist(selinux_policyvers_path)) - facts["selinux_policyversion"] = read_oneline_file(selinux_policyvers_path); - - string selinux_cmd = "/usr/sbin/sestatus"; - FILE* pipe = popen(selinux_cmd.c_str(), "r"); - if (!pipe) return; - - char buffer[512]; // seems like a lot, but there's no constant available - while (!feof(pipe)) { - if (fgets(buffer, 128, pipe) != nullptr) { - vector elems; - split(buffer, ':', elems); - if (elems.size() < 2) continue; // shouldn't happen - if (elems[0] == "Current mode") { - facts["selinux_current_mode"] = trim(elems[1]); - } else if (elems[0] == "Mode from config file") { - facts["selinux_config_mode"] = trim(elems[1]); - } else if (elems[0] == "Policy from config file") { - facts["selinux_config_policy"] = trim(elems[1]); - facts["selinux_mode"] = trim(elems[1]); - } - } - } - - pclose(pipe); -} - -static void get_ssh_fact(string const& fact_name, string const& path_name, fact_map& facts) -{ - vector ssh_directories = { - "/etc/ssh", - "/usr/local/etc/ssh", - "/etc", - "/usr/local/etc", - "/etc/opt/ssh", - }; - - for (auto const &ssh_directory : ssh_directories) { - string full_path = ssh_directory + "/" + path_name; - if (file_exist(full_path)) { - string key = read_oneline_file(full_path); - vector tokens; - tokenize(trim(key), tokens); - if (tokens.size() < 2) continue; // should never happen - facts[fact_name] = tokens[1]; - - // skpping the finger print facts, which require base64 decode and sha libs - // on the cmd line it would be something like the result of these two: - // "cat " + full_path + " | cut -d' ' -f 2 | base64 -d - | sha256sum - | cut -d' ' -f 1" - // "cat " + full_path + " | cut -d' ' -f 2 | base64 -d - | sha1sum - | cut -d' ' -f 1" - - break; - } - } -} - -// no support for the sshfp facts, which require base64/sha1sum code -void get_ssh_facts(fact_map& facts) -{ - map ssh_facts = { - { "sshdsakey", "ssh_host_dsa_key.pub" }, - { "sshrsakey", "ssh_host_rsa_key.pub" }, - { "sshecdsakey", "ssh_host_ecdsa_key.pub" }, - }; - - for (auto const& i : ssh_facts) { - get_ssh_fact(i.first, i.second, facts); - } -} - -static void get_physicalprocessorcount_fact(fact_map& facts) -{ - // So, facter has logic to use /sys and fallback to /proc - // but I don't know why the /sys support was added; research needed. - // Since sys is the default, just reproduce that logic for now. - - string sysfs_cpu_directory = "/sys/devices/system/cpu"; - vector package_ids; - if (file_exist(sysfs_cpu_directory)) { - for (int i = 0; ; i++) { - char buf[10]; - snprintf(buf, sizeof(buf) - 1, "%u", i); - string cpu_phys_file = sysfs_cpu_directory + "/cpu" + buf + "/topology/physical_package_id"; - if (!file_exist(cpu_phys_file)) - break; - - package_ids.push_back(read_oneline_file(cpu_phys_file)); - } - - sort(begin(package_ids), end(package_ids)); - unique(begin(package_ids), end(package_ids)); - facts["physicalprocessorcount"] = to_string(package_ids.size()); - } else { - // here's where the fall back to /proc/cpuinfo would go - } -} - -void get_processorcount_fact(fact_map& facts) -{ - ifstream cpuinfo_file("/proc/cpuinfo", ifstream::in); - string line; - int processor_count = 0; - string current_processor_number; - while (getline(cpuinfo_file, line)) { - unsigned sep = line.find(":"); - string tmp = line.substr(0, sep); - string key = trim(tmp); - - if (key == "processor") { - ++processor_count; - string tmp = line.substr(sep + 1, string::npos); - current_processor_number = trim(tmp); - } else if (key == "model name") { - string tmp = line.substr(sep + 1, string::npos); - facts[string("processor") + current_processor_number] = trim(tmp); - } - } - // this was added after 1.7.3, omit for now, needs investigation - if (false) facts["activeprocessorcount"] = processor_count; - facts["processorcount"] = to_string(processor_count); -} - -void get_processor_facts(fact_map& facts) -{ - get_physicalprocessorcount_fact(facts); - get_processorcount_fact(facts); -} - -void get_architecture_facts(fact_map& facts) -{ - utsname uts; - if (uname(&uts) == 0) { - // This is cheating at some level because these are all the same on x86_64 linux. - // Otoh, some of these may be compiled-in for a C version. And then if facter - // relies on 'uname -p' here and that commonizes, this should perhaps just shell out - // and not reproduce that logic. Regardless, need to survey cross-platform here and - // take it from there. - facts["hardwaremodel"] = uts.machine; - facts["hardwareisa"] = uts.machine; - facts["architecture"] = uts.machine; - } -} - -void get_dmidecode_facts(fact_map& facts) -{ - string dmidecode_output = popen_stdout("/usr/sbin/dmidecode 2> /dev/null"); - stringstream ss(dmidecode_output); - string line; - - enum { - bios_information, - base_board_information, - system_information, - chassis_information, - unknown - } dmi_section = unknown; - - while (getline(ss, line)) { - if (line.empty()) continue; - - // enable case-insensitive compares - ci_string ci_line = line.c_str(); - - // identify the dmi section, they all begin at the beginning of a line - // and there are only a handful of interest to us - if (ci_line == "BIOS Information") { - dmi_section = bios_information; - continue; - } else if (ci_line == "Base Board Information") { - dmi_section = base_board_information; - continue; - } else if (ci_line == "System Information") { - dmi_section = system_information; - continue; - } else if (ci_line == "Chassis Information" || ci_line == "system enclosure or chassis") { - dmi_section = chassis_information; - continue; - } else if (ci_line[0] >= 'A' && ci_line[0] <= 'Z') { - dmi_section = unknown; - continue; - } - - // if we're in the middle of an unknown section, skip - if (dmi_section == unknown) continue; - - size_t sep = line.find(":"); - if (sep != string::npos) { - string tmp = line.substr(0, sep); - string key = trim(tmp); - if (dmi_section == bios_information) { - ci_string ci_key = key.c_str(); - if (ci_key == "vendor") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); - facts["bios_vendor"] = value; - } - if (ci_key == "version") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); - facts["bios_version"] = value; - } - if (ci_key == "release date") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); - facts["bios_release_date"] = value; - } - } else if (dmi_section == base_board_information) { - ci_string ci_key = key.c_str(); - if (ci_key == "manufacturer") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); - facts["boardmanufacturer"] = value; - } - if (ci_key == "product name" || ci_key == "product") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); - facts["boardproductname"] = value; - } - if (ci_key == "serial number") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); - facts["boardserialnumber"] = value; - } - } else if (dmi_section == system_information) { - ci_string ci_key = key.c_str(); - if (ci_key == "manufacturer") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); - facts["manufacturer"] = value; - } - if (ci_key == "product name" || ci_key == "product") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); - facts["productname"] = value; - } - if (ci_key == "serial number") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); - facts["serialnumber"] = value; - } - if (ci_key == "uuid") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); - facts["uuid"] = value; - } - } else if (dmi_section == chassis_information) { - ci_string ci_key = key.c_str(); - if (ci_key == "chassis type" || ci_key == "type") { - string tmp = line.substr(sep + 1, string::npos); - string value = trim(tmp); - facts["type"] = value; - } - } - } - } -} - -void get_filesystems_facts(fact_map& facts) -{ - ifstream cpuinfo_file("/proc/filesystems", ifstream::in); - string line; - string filesystems = ""; - while (getline(cpuinfo_file, line)) { - if (line.find("nodev") != string::npos || line.find("fuseblk") != string::npos) - continue; - - if (!filesystems.empty()) - filesystems += ","; - - filesystems += trim(line); - } - facts["filesystems"] = filesystems; -} - -void get_hostname_facts(fact_map& facts) -{ - // there's some history here, perhaps just port the facter conditional straight across? - // so this is short-term - string hostname_output = popen_stdout("hostname"); - unsigned sep = hostname_output.find("."); - string hostname1 = hostname_output.substr(0, sep); - string hostname = trim(hostname1); - - ifstream resolv_conf_file("/etc/resolv.conf", ifstream::in); - string line; - string domain; - string search; - while (getline(resolv_conf_file, line)) { - vector elems; - tokenize(line, elems); - if (elems.size() >= 2) { - if (elems[0] == "domain") - domain = trim(elems[1]); - else if (elems[0] == "search") - search = trim(elems[1]); - } - } - if (domain.empty() && !search.empty()) - domain = search; - - facts["hostname"] = hostname; - facts["domain"] = domain; - facts["fqdn"] = hostname + "." + domain; -} - -static void get_external_facts_from_executable(fact_map& facts, string const& executable) -{ - // executable - FILE *stdout = popen(executable.c_str(), "r"); - if (stdout) { - while (!feof(stdout)) { - const int buffer_len = 32 * 1024; - char buffer[buffer_len]; - if (fgets(buffer, buffer_len, stdout)) { - vector elems; - split(buffer, '=', elems); - if (elems.size() != 2) continue; // shouldn't happen - string key = trim(elems[0]); - string val = trim(elems[1]); - facts[key] = val; - } - } - pclose(stdout); - } -} - -static void get_external_facts(fact_map& facts, string const& directory) -{ - DIR *external_dir = opendir(directory.c_str()); - if (!external_dir) - return; - - dirent* external_fact; - struct stat s; - - while ((external_fact = readdir(external_dir))) { - string full_path = directory + "/" + external_fact->d_name; - - if (stat(full_path.c_str(), &s) != 0) - continue; - - if (s.st_mode & S_IFDIR) - continue; - - if (access(full_path.c_str(), X_OK) != 0) - continue; - - get_external_facts_from_executable(facts, full_path); - } -} - -void get_external_facts(fact_map& facts, list const& directories) -{ - for (auto const& dir : directories) { - get_external_facts(facts, dir); - } -} diff --git a/lib/cfacterimpl.h b/lib/cfacterimpl.h deleted file mode 100644 index 657e32c860..0000000000 --- a/lib/cfacterimpl.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef __CFACTERIMPL_H__ -#define __CFACTERIMPL_H__ - -#include -#include -#include - -typedef std::map fact_map; - -void get_network_facts(fact_map&); -void get_kernel_facts(fact_map&); -void get_blockdevice_facts(fact_map&); -void get_operatingsystem_facts(fact_map&); -void get_uptime_facts(fact_map&); -void get_virtual_facts(fact_map&); -void get_hardwired_facts(fact_map&); -void get_misc_facts(fact_map&); -void get_ruby_lib_versions(fact_map&); -void get_mem_facts(fact_map&); -void get_selinux_facts(fact_map&); -void get_ssh_facts(fact_map&); -void get_processor_facts(fact_map&); -void get_architecture_facts(fact_map&); -void get_dmidecode_facts(fact_map&); -void get_filesystems_facts(fact_map&); -void get_hostname_facts(fact_map&); -void get_external_facts(fact_map&, std::list const&); - -#endif \ No newline at end of file diff --git a/lib/cfacterlib.cc b/lib/cfacterlib.cc deleted file mode 100644 index 2b05f7be38..0000000000 --- a/lib/cfacterlib.cc +++ /dev/null @@ -1,100 +0,0 @@ -#include "../version.h" -#include "cfacterlib.h" -#include "cfacterimpl.h" - -#include -#include -#include -#include - -#include "rapidjson/document.h" -#include "rapidjson/prettywriter.h" -#include "rapidjson/stringbuffer.h" - -using namespace std; - -map facts; - -void clear() -{ - facts.erase(facts.begin(), facts.end()); -} - -void loadfacts() -{ - // Make loadfacts() idempotent, if client has reason to - // want a reload, they should clear() then loadfacts(). - // - // This goes along with to_json() and value() always calling - // loadfacts(). - // - if (!facts.empty()) - return; - - facts["cfacterversion"] = CFACTER_VERSION; - - list external_directories = { "/etc/facter/facts.d" }; - - get_network_facts(facts); - get_kernel_facts(facts); - get_blockdevice_facts(facts); - get_operatingsystem_facts(facts); - get_uptime_facts(facts); - get_virtual_facts(facts); - get_hardwired_facts(facts); - get_misc_facts(facts); - get_ruby_lib_versions(facts); - get_mem_facts(facts); - get_selinux_facts(facts); - get_ssh_facts(facts); - get_processor_facts(facts); - get_architecture_facts(facts); - get_dmidecode_facts(facts); - get_filesystems_facts(facts); - get_hostname_facts(facts); - get_external_facts(facts, external_directories); -} - -int to_json(char *facts_json, size_t facts_len) -{ - loadfacts(); - - rapidjson::Document json; - json.SetObject(); - - rapidjson::Document::AllocatorType& allocator = json.GetAllocator(); - - for (auto const& i : facts) { - json.AddMember(i.first.c_str(), i.second.c_str(), allocator); - } - - rapidjson::StringBuffer buf; - rapidjson::Writer writer(buf); - json.Accept(writer); - - // FIXME can rapidjson write straight into the provided char array in a safe manner? - // if not roll my own strncpy which doesn't zero-pad and returns success rather than the ptr - strncpy(facts_json, buf.GetString(), facts_len); - return 0; -} - -int value(const char *fact, char *value, size_t value_len) -{ - loadfacts(); - - auto const& i = facts.find(fact); - if (i == facts.end()) - return -1; - - if (i->second.size() > (value_len - 1)) - return -1; - - strncpy(value, i->second.c_str(), value_len); - - return 0; -} - -void search_external(const char *dirs) -{ - // TODO -} diff --git a/lib/cfacterlib.h b/lib/inc/cfacterlib.h similarity index 53% rename from lib/cfacterlib.h rename to lib/inc/cfacterlib.h index e83ecb1c1c..1c803f7d7a 100644 --- a/lib/cfacterlib.h +++ b/lib/inc/cfacterlib.h @@ -6,9 +6,8 @@ extern "C" { void clear(); - void loadfacts(); - int to_json(char *facts, size_t facts_len); - int value(const char *fact, char *value, size_t value_len); + int to_json(char *facts, size_t facts_len); + int get_value(const char *fact, char *value, size_t value_len); void search_external(const char *dirs); } diff --git a/lib/inc/execution/execution.hpp b/lib/inc/execution/execution.hpp new file mode 100644 index 0000000000..d109fc39d6 --- /dev/null +++ b/lib/inc/execution/execution.hpp @@ -0,0 +1,181 @@ +#ifndef __EXECUTION_HPP__ +#define __EXECUTION_HPP__ + +#include +#include +#include +#include "../util/option_set.hpp" + +namespace cfacter { namespace execution { + + /** + * The supported execution options. + */ + enum class execution_options + { + /** + * No options. + */ + none = 0, + /** + * Redirect stderr to stdout. If not specified, stderr is redirected + * to null. + */ + redirect_stderr = (1 << 1), + /** + * Throw an exception if the child process exits with a nonzero status. + */ + throw_on_nonzero_exit = (1 << 2), + /** + * Throw an exception if the child process is terminated due to a signal. + */ + throw_on_signal = (1 << 3), + /** + * Automatically trim output leading and trailing whitespace. + */ + trim_output = (1 << 4), + /** + * A combination of all throw options. + */ + throw_on_failure = throw_on_nonzero_exit | throw_on_signal, + /** + * The default execution options. + */ + defaults = redirect_stderr | trim_output| throw_on_failure, + }; + + /** + * Base class for execution exceptions. + */ + struct execution_exception : std::runtime_error + { + /** + * Constructs a execution_exception. + * @param message The exception message. + */ + explicit execution_exception(std::string const& message) : std::runtime_error(message) {} + }; + + /** + * Base class for execution failures. + */ + struct execution_failure_exception : std::runtime_error + { + /** + * Constructs a execution_failure_exception. + * @param output The child process output. + * @param message The exception message. + */ + execution_failure_exception(std::string const& output, std::string const& message) : + std::runtime_error(message), + _output(output) + { + } + + /** + * Gets the child process output. + * @return Returns the child process output. + */ + std::string const& output() const { return _output; } + + private: + std::string _output; + }; + + /** + * Exception that is thrown when a child exits with a non-zero status code. + */ + struct child_exit_exception : execution_failure_exception + { + /** + * Constructs a child_exit_exception. + * @param status_code The exit status code of the child process. + * @param output The child process output. + * @param message The exception message. + */ + child_exit_exception(int status_code, std::string const& output, std::string const& message) : + execution_failure_exception(output, message), + _status_code(status_code) + { + } + + /** + * Gets the child process exit status code. + * @return Returns the child process exit status code. + */ + int status_code() const { return _status_code; } + + private: + int _status_code; + }; + + /** + * Exception that is thrown when a child exists due to a signal. + */ + struct child_signal_exception : execution_failure_exception + { + /** + * Constructs a child_signal_exception. + * @param signal The signal code that terminated the child process. + * @param output The child process output. + * @param message The exception message. + */ + child_signal_exception(int signal, std::string const& output, std::string const& message) : + execution_failure_exception(output, message), + _signal(signal) + { + } + + /** + * Gets the signal that terminated the child process. + * @return Returns the signal that terminated the child process. + */ + int signal() const { return _signal; } + + private: + int _signal; + }; + + /** + * Executes the given program. + * @param file The name or path of the program to execute. + * @param options The execution options. + * @return Returns the child process output. + */ + std::string execute( + std::string const& file, + cfacter::util::option_set const& options = { execution_options::defaults } + ); + + /** + * Executes the given program. + * @param file The name or path of the program to execute. + * @param arguments The arguments to pass to the program. + * @param options The execution options. + * @return Returns the child process output. + */ + std::string execute( + std::string const& file, + std::vector const& arguments, + cfacter::util::option_set const& options = { execution_options::defaults } + ); + + /** + * Executes the given program. + * @param file The name or path of the program to execute. + * @param arguments The arguments to pass to the program. + * @param environment The environment variables to pass to the child process. + * @param options The execution options. + * @return Returns the child process output. + */ + std::string execute( + std::string const& file, + std::vector const& arguments, + std::vector const& environment, + cfacter::util::option_set const& options = { execution_options::defaults } + ); + +} } // namespace cfacter::execution + +#endif + diff --git a/lib/inc/facts/array_value.hpp b/lib/inc/facts/array_value.hpp new file mode 100644 index 0000000000..430c2bfa58 --- /dev/null +++ b/lib/inc/facts/array_value.hpp @@ -0,0 +1,64 @@ +#ifndef __ARRAY_VALUE_HPP__ +#define __ARRAY_VALUE_HPP__ + +#include "value.hpp" +#include +#include + +namespace cfacter { namespace facts { + + /** + * Represents an array of values. + */ + struct array_value : value + { + /** + * Constructs an array_value. + */ + array_value() + { + } + + /** + * Constructs an array value. + * @param elements The elements that make up the array. + */ + array_value(std::vector>&& elements) : + _elements(std::move(elements)) + { + } + + // Force non-copyable + array_value(array_value const&) = delete; + array_value& operator=(array_value const&) = delete; + + // Allow movable + array_value(array_value&&) = default; + array_value& operator=(array_value&&) = default; + + /** + * Converts the value to a string representation. + * @return Returns the string representation of the value. + */ + std::string to_string() const; + + /** + * Gets the vector of elements in the array. + * @return Returns the vector of elements in the array. + */ + std::vector>& elements() { return _elements; } + + /** + * Gets the vector of elements in the array. + * @return Returns the vector of elements in the array. + */ + std::vector> const& elements() const { return _elements; } + + private: + std::vector> _elements; + }; + +} } // namespace cfacter::facts + +#endif + diff --git a/lib/inc/facts/fact.hpp b/lib/inc/facts/fact.hpp new file mode 100644 index 0000000000..0aeece2a69 --- /dev/null +++ b/lib/inc/facts/fact.hpp @@ -0,0 +1,59 @@ +#ifndef __FACT_HPP__ +#define __FACT_HPP__ + +#include "value.hpp" +#include +#include +#include + +namespace cfacter { namespace facts { + + /** + * Represents a fact. + */ + struct fact + { + /** + * Constructs a fact with the given name. + */ + fact(std::string&& name, std::unique_ptr && value) : + _name(std::move(name)), + _value(std::move(value)) + { + } + + /** + * Gets the name of the fact. + * @return Returns the name of the fact. + */ + std::string const& name() const { return _name; } + + /** + * Gets the fact's value + * @return Returns the fact's value. + */ + value const* val() const { return _value.get(); } + + // Force non-copyable + fact(fact const&) = delete; + fact& operator=(fact const&) = delete; + + // Allow movable + fact(fact&&) = default; + fact& operator=(fact&&) = default; + + private: + std::string _name; + std::unique_ptr _value; + }; + + /** + * Called to populate all facts. + */ + void populate_common_facts(); + void populate_platform_facts(); + +} } // namespace cfacter:facts + +#endif + diff --git a/lib/inc/facts/fact_map.hpp b/lib/inc/facts/fact_map.hpp new file mode 100644 index 0000000000..3ea6ddb380 --- /dev/null +++ b/lib/inc/facts/fact_map.hpp @@ -0,0 +1,136 @@ +#ifndef __FACT_MAP_HPP__ +#define __FACT_MAP_HPP__ + +#include +#include +#include +#include +#include +#include "value.hpp" +#include "fact.hpp" +#include "fact_resolver.hpp" + +namespace cfacter { namespace facts { + + /** + * Thrown when a fact already exists in the map. + */ + struct fact_exists_exception : std::runtime_error + { + /** + * Constructs a fact_exists_exception. + * @param message The exception message. + */ + explicit fact_exists_exception(std::string const& message) : std::runtime_error(message) {} + }; + + /** + * Thrown when a fact already has an associated resolver. + */ + struct resolver_exists_exception : std::runtime_error + { + /** + * Constructs a fact_exists_exception. + * @param message The exception message. + */ + explicit resolver_exists_exception(std::string const& message) : std::runtime_error(message) {} + }; + + /** + * Represents the fact map. + */ + struct fact_map + { + typedef std::map fact_map_type; + typedef std::map> resolver_map_type; + + /** + * Adds a fact resolver to the fact map. + * @tparam T The fact resolver type to add to the map. Must be default-constructable. + */ + template + void add_resolver() + { + std::shared_ptr resolver(new T()); + + for (auto const& fact_name : resolver->names()) { + auto const& it = _resolvers.lower_bound(fact_name); + if (it != _resolvers.end() && !(_resolvers.key_comp()(fact_name, it->first))) { + throw resolver_exists_exception("a resolver for fact " + fact_name + " already exists."); + } + _resolvers.insert(it, make_pair(fact_name, resolver)); + } + } + + /** + * Adds a fact to the map. + * @param f The fact to add to the map. + */ + void add_fact(fact&& f); + + /** + * Removes a fact by name. + * @param name The name of the fact to remove. + */ + void remove(std::string const& name); + + /** + * Clears the entire fact map. + */ + void clear(); + + /** + * Checks to see if the fact map is empty. + * @return Returns true if the fact map is entry or false if it is not. + */ + bool empty() const; + + /** + * Gets a fact by name. + * @param name The name of the fact to get. + * @return Returns a pointer to the fact or nullptr if the fact is not in the map. + */ + fact const* get_fact(std::string const& name); + + /** + * Gets a fact value by name. + * @param name The name of the fact to get the value of. + * @return Returns a pointer to the fact value or nullptr if the fact is not in the map or the value is not the expected type. + */ + template + T const* get_value(std::string const& name) + { + auto f = get_fact(name); + if (!f) { + return nullptr; + } + return dynamic_cast(f->val()); + } + + /** + * Enumerates all facts in the map. + * @param func The callback function called for each fact in the map. + */ + void each(std::function func); + + /** + * Gets the singleton instance of the fact map. + * @return Returns the singleton instance of the fact map. + */ + static fact_map& instance(); + + private: + //fact_map() {} + void load(); + void resolve_facts(); + + static fact_map _instance; + + fact_map_type _facts; + resolver_map_type _resolvers; + }; + +}} // namespace cfacter::facts + +#endif + diff --git a/lib/inc/facts/fact_resolver.hpp b/lib/inc/facts/fact_resolver.hpp new file mode 100644 index 0000000000..6a9a77bc1a --- /dev/null +++ b/lib/inc/facts/fact_resolver.hpp @@ -0,0 +1,102 @@ +#ifndef __RESOLVER_HPP__ +#define __RESOLVER_HPP__ + +#include +#include +#include +#include +#include "fact.hpp" + +namespace cfacter { namespace facts { + + /** + * Thrown when a circular fact resolution is detected. + */ + struct circular_resolution_exception : std::runtime_error + { + /** + * Constructs a circular_resolution_exception. + * @param message The exception message. + */ + explicit circular_resolution_exception(std::string const& message) : std::runtime_error(message) {} + }; + + /** + * Utility type for managing resolution cycles. + */ + struct cycle_guard + { + explicit cycle_guard(bool& value) : + _value(value) + { + _value = true; + } + + ~cycle_guard() + { + _value = false; + } + + private: + bool& _value; + }; + + struct fact_map; + + /** + * Base class for fact resolvers. + * A fact resolver is responsible for resolving one or more facts. + */ + struct fact_resolver + { + /** + * Constructs a fact_resolver. + * @param names The fact names the resolver is responsible for. + */ + fact_resolver(std::initializer_list const& names) : + _names(names.begin(), names.end()), + _resolving(false) + { + } + + /** + * Destructs the fact_resolver. + */ + virtual ~fact_resolver() {} + + // Force non-copyable + fact_resolver(fact_resolver const&) = delete; + fact_resolver& operator=(fact_resolver const&) = delete; + + // Allow movable + fact_resolver(fact_resolver&&) = default; + fact_resolver& operator=(fact_resolver&&) = default; + + /** + * Gets the fact names the resolver is responsible for resolving. + * @return Returns a vector of fact names. + */ + std::vector const& names() { return _names; } + + /** + * Called to resolve all facts the resolver is responsible for. + * @param facts The fact map that is resolving facts. + */ + void resolve(fact_map& facts); + + protected: + /** + * Called to resolve all facts the resolver is responsible for. + * @param facts The fact map that is resolving facts. + */ + virtual void resolve_facts(fact_map& facts) = 0; + + private: + std::vector _names; + bool _resolving; + }; + +}} // namespace cfacter::facts + +#endif + diff --git a/lib/inc/facts/linux/lsb_resolver.hpp b/lib/inc/facts/linux/lsb_resolver.hpp new file mode 100644 index 0000000000..a3b722a073 --- /dev/null +++ b/lib/inc/facts/linux/lsb_resolver.hpp @@ -0,0 +1,43 @@ +#ifndef __LINUX_LSB_RESOLVER_HPP__ +#define __LINUX_LSB_RESOLVER_HPP__ + +#include "../fact_resolver.hpp" + +namespace cfacter { namespace facts { namespace linux { + + /** + * Responsible for resolving Linux Standard Base facts. + */ + struct lsb_resolver : fact_resolver + { + // Constants for responsible facts + constexpr static char const* lsb_dist_id_name = "lsbdistid"; + + /** + * Constructs the lsb_resolver. + */ + lsb_resolver() : + fact_resolver({ + lsb_dist_id_name, + }) + { + } + + protected: + /** + * Called to resolve all facts the resolver is responsible for. + * @param facts The fact map that is resolving facts. + */ + void resolve_facts(fact_map& facts); + + /** + * Called to resolve the LSB dist id fact. + * @param facts The fact map that is resolving facts. + */ + virtual void resolve_dist_id(fact_map& facts); + }; + +}}} // namespace cfacter::facts::linux + +#endif + diff --git a/lib/inc/facts/linux/operating_system_resolver.hpp b/lib/inc/facts/linux/operating_system_resolver.hpp new file mode 100644 index 0000000000..7dcb80b3b0 --- /dev/null +++ b/lib/inc/facts/linux/operating_system_resolver.hpp @@ -0,0 +1,33 @@ +#ifndef __LINUX_OPERATING_SYSTEM_RESOLVER_HPP__ +#define __LINUX_OPERATING_SYSTEM_RESOLVER_HPP__ + +#include "../posix/operating_system_resolver.hpp" +#include "../string_value.hpp" + +namespace cfacter { namespace facts { namespace linux { + + /** + * Responsible for resolving operating system facts. + */ + struct operating_system_resolver : posix::operating_system_resolver + { + protected: + /** + * Called to resolve the operating system fact. + * @param facts The fact map that is resolving facts. + */ + virtual void resolve_operating_system(fact_map& facts); + + private: + static std::string check_cumulus_linux(); + static std::string check_debian_linux(string_value const* dist_id); + static std::string check_oracle_linux(); + static std::string check_redhat_linux(); + static std::string check_suse_linux(); + static std::string check_other_linux(); + }; + +}}} // namespace cfacter::facts::linux + +#endif + diff --git a/lib/inc/facts/posix/kernel_resolver.hpp b/lib/inc/facts/posix/kernel_resolver.hpp new file mode 100644 index 0000000000..fe0c03fc33 --- /dev/null +++ b/lib/inc/facts/posix/kernel_resolver.hpp @@ -0,0 +1,63 @@ +#ifndef __POSIX_KERNEL_RESOLVER_HPP__ +#define __POSIX_KERNEL_RESOLVER_HPP__ + +#include "../fact_resolver.hpp" + +namespace cfacter { namespace facts { namespace posix { + + /** + * Responsible for resolving kernel facts. + */ + struct kernel_resolver : fact_resolver + { + // Constants for responsible facts + constexpr static char const* kernel_name = "kernel"; + constexpr static char const* kernel_version_name = "kernelversion"; + constexpr static char const* kernel_release_name = "kernelrelease"; + constexpr static char const* kernel_maj_release_name = "kernelmajrelease"; + + /** + * Constructs the kernel_resolver. + */ + kernel_resolver() : + fact_resolver({ + kernel_name, + kernel_version_name, + kernel_release_name, + kernel_maj_release_name + }) + { + } + + protected: + /** + * Called to resolve all facts the resolver is responsible for. + * @param facts The fact map that is resolving facts. + */ + virtual void resolve_facts(fact_map& facts); + /** + * Called to resolve the kernel fact. + * @param facts The fact map that is resolving facts. + */ + virtual void resolve_kernel(fact_map& facts); + /** + * Called to resolve the kernel release fact. + * @param facts The fact map that is resolving facts. + */ + virtual void resolve_kernel_release(fact_map& facts); + /** + * Called to resolve the kernel version fact. + * @param facts The fact map that is resolving facts. + */ + virtual void resolve_kernel_version(fact_map& facts); + /** + * Called to resolve the kernel major version fact. + * @param facts The fact map that is resolving facts. + */ + virtual void resolve_kernel_major_version(fact_map& facts); + }; + +}}} // namespace cfacter::facts::posix + +#endif + diff --git a/lib/inc/facts/posix/operating_system_resolver.hpp b/lib/inc/facts/posix/operating_system_resolver.hpp new file mode 100644 index 0000000000..d15babc1ce --- /dev/null +++ b/lib/inc/facts/posix/operating_system_resolver.hpp @@ -0,0 +1,42 @@ +#ifndef __POSIX_OPERATING_SYSTEM_RESOLVER_HPP__ +#define __POSIX_OPERATING_SYSTEM_RESOLVER_HPP__ + +#include "../fact_resolver.hpp" + +namespace cfacter { namespace facts { namespace posix { + + /** + * Responsible for resolving operating system facts. + */ + struct operating_system_resolver : fact_resolver + { + // Constants for responsible facts + constexpr static char const* operating_system_name = "operatingsystem"; + + /** + * Constructs the operating_system_resolver. + */ + operating_system_resolver() : + fact_resolver({ + operating_system_name, + }) + { + } + + protected: + /** + * Called to resolve all facts the resolver is responsible for. + * @param facts The fact map that is resolving facts. + */ + void resolve_facts(fact_map& facts); + /** + * Called to resolve the operating system fact. + * @param facts The fact map that is resolving facts. + */ + virtual void resolve_operating_system(fact_map& facts); + }; + +}}} // namespace cfacter::facts::posix + +#endif + diff --git a/lib/inc/facts/string_value.hpp b/lib/inc/facts/string_value.hpp new file mode 100644 index 0000000000..7b2b4dd1f9 --- /dev/null +++ b/lib/inc/facts/string_value.hpp @@ -0,0 +1,58 @@ +#ifndef __STRING_VALUE_HPP__ +#define __STRING_VALUE_HPP__ + +#include "value.hpp" + +namespace cfacter { namespace facts { + + /** + * Represents a simple string value. + */ + struct string_value : value + { + /** + * Constructs a string_value. + * @param value The string value. + */ + explicit string_value(std::string& value) : + _value(std::move(value)) + { + } + + /** + * Constructs a string_value. + * @param value The string value. + */ + explicit string_value(std::string const& value) : + _value(value) + { + } + + // Force non-copyable + string_value(string_value const&) = delete; + string_value& operator=(string_value const&) = delete; + + // Allow movable + string_value(string_value&&) = default; + string_value& operator=(string_value&&) = default; + + /** + * Converts the value to a string representation. + * @return Returns the string representation of the value. + */ + std::string to_string() const; + + /** + * Gets the string value. + * @return Returns the string value. + */ + std::string const& value() const { return _value; } + + private: + std::string _value; + }; + +} } // namespace cfacter::facts + +#endif + diff --git a/lib/inc/facts/value.hpp b/lib/inc/facts/value.hpp new file mode 100644 index 0000000000..e9934893a9 --- /dev/null +++ b/lib/inc/facts/value.hpp @@ -0,0 +1,46 @@ +#ifndef __VALUE_HPP__ +#define __VALUE_HPP__ + +#include +#include +#include + +namespace cfacter { namespace facts { + + /** + * Base class for values. + */ + struct value + { + value() {} + + /** + * Converts the value to a string representation. + * @return Returns the string representation of the value. + */ + virtual std::string to_string() const = 0; + + // Force non-copyable + value(value const&) = delete; + value& operator=(value const&) = delete; + + // Allow movable + value(value&&) = default; + value& operator=(value&&) = default; + }; + + /** + * Utility function for making a value. + * @param args The arguments to the value's constructor. + * @return Returns a unique pointer to the value. + */ + template + std::unique_ptr make_value(Args&& ...args) + { + return std::unique_ptr(new T(std::forward(args)...)); + } + +} } // namespace cfacter::facts + +#endif + diff --git a/lib/inc/util/file.hpp b/lib/inc/util/file.hpp new file mode 100644 index 0000000000..acb3c1f85e --- /dev/null +++ b/lib/inc/util/file.hpp @@ -0,0 +1,30 @@ +#ifndef __FILE_HPP__ +#define __FILE_HPP__ + +#include + +namespace cfacter { namespace util { + + /** + * Utility type for interacting with files. + */ + struct file + { + /** + * Determines if the given path exists. + * @param path The path to check for existence. + * @return Returns true if the path exists or false if it does not or access denied. + */ + static bool exists(std::string const& path); + + /** + * Reads the entire contents of the given file into a string. + * @param path The path of the file to read. + * @return Returns the file contents as a string or empty string if the file cannot be read. + */ + static std::string read(std::string const& path); + }; + +}} // namespace cfacter::util + +#endif diff --git a/lib/inc/util/option_set.hpp b/lib/inc/util/option_set.hpp new file mode 100644 index 0000000000..abc286e347 --- /dev/null +++ b/lib/inc/util/option_set.hpp @@ -0,0 +1,228 @@ +#ifndef __OPTION_SET_HPP__ +#define __OPTION_SET_HPP__ + +#include +#include +#include +#include + +namespace cfacter { namespace util { + /** + * Represents a set of options (flags). + * Adapted from http://stackoverflow.com/a/4226975/530189 + * @tparam T The enum class type that makes up the avilable options. + */ + template + struct option_set + { + /** + * The underlying enum type for the option set. + */ + typedef T enum_type; + /** + * The value type for the enum type. + */ + typedef typename std::underlying_type::type value_type; + + /** + * Constructs an empty option_set. + */ + option_set() : option_set(value_type(0)) + { + } + + /** + * Constructs an option_set with the given list of options. + * @param values The option values to store in the list. + */ + option_set(std::initializer_list const& values) + { + // Simply bitwise OR all the values together. + _value = std::accumulate( + std::begin(values), + std::end(values), + value_type(0), + [](value_type acc, enum_type value) { + return acc | static_cast(value); + } + ); + } + + /** + * Constructs an option_set with an existing bitfield value. + * @param value + */ + explicit option_set(value_type value) : _value(value) + { + } + + /** + * Cast operator to value_type. + */ + operator value_type() const + { + return _value; + } + + /** + * Used to test if an option is present in the set. + * @param option The option to check for. + * @return Returns true if the option is in the set or false if it is not. + */ + bool operator [](enum_type option) const + { + return test(option); + } + + /** + * Sets all options to true. + * @return Returns this option_set. + */ + option_set& set_all() + { + _value = ~value_type(0); + return *this; + } + + /** + * Sets the given option to true. + * @param option The option to set to true. + * @return Returns this option_set. + */ + option_set& set(enum_type option) + { + _value |= option; + return *this; + } + + /** + * Clears the given option from the set. + * @param option The option to clear. + * @return Returns this option_set. + */ + option_set& clear(enum_type option) + { + _value &= ~option; + return *this; + } + + /** + * Resets the option_set by clearing all options. + * @return Returns this option_set. + */ + option_set& reset() + { + _value = value_type(0); + return *this; + } + + /** + * Toggles all options in the set. + * @return Returns this option_set. + */ + option_set& toggle() + { + _value = ~_value; + return *this; + } + + /** + * Toggles a specific option in the set. + * @param option The option to toggle. + * @return Returns this option_set. + */ + option_set& toggle(enum_type option) + { + _value ^= option; + return *this; + } + + /** + * Gets the count of options in the set. + * @return Returns the count of options in the set. + */ + size_t count() const + { + // Do a simple bit count + value_type bits = _value; + size_t total = 0; + for (; bits != 0; ++total) + { + bits &= bits - 1; // clear the least significant bit set + } + return total; + } + + /** + * Gets the bit size of the option_set. + * The size is based on how many bits are present in the underlying value_type. + * @return Returns the bit size of the option_set. + */ + constexpr size_t size() const + { + return sizeof(value_type)*8; + } + + /** + * Tests if the given option is in the set. + * @param option The option to test for. + * @return Returns true if the option is in the set or false if it is not. + */ + bool test(enum_type option) const + { + return (_value & static_cast(option)) > 0; + } + + /** + * Checks to see if the option_set is empty. + * @return Returns true if the set is empty (no options) or false if there are options in the set. + */ + bool empty() const + { + return _value == 0; + } + + private: + value_type _value; + }; + + /** + * Bitwise AND operator for option_set. + * @param lhs The lefthand option_set. + * @param rhs The righthand option_set. + * @return Returns an option_set that is the bitwise AND of the two given option_sets. + */ + template + option_set operator &(option_set const& lhs, option_set const& rhs) + { + return option_set(option_set::value_type(lhs) & option_set::value_type(rhs)); + } + + /** + * Bitwise OR operator for option_set. + * @param lhs The lefthand option_set. + * @param rhs The righthand option_set. + * @return Returns an option_set that is the bitwise OR of the two given option_sets. + */ + template + option_set operator |(option_set const& lhs, option_set const& rhs) + { + return option_set(option_set::value_type(lhs) | option_set::value_type(rhs)); + } + + /** + * Bitwise XOR operator for option_set. + * @param lhs The lefthand option_set. + * @param rhs The righthand option_set. + * @return Returns an option_set that is the bitwise XOR of the two given option_sets. + */ + + template + option_set operator ^(option_set const& lhs, option_set const& rhs) + { + return option_set(option_set::value_type(lhs) ^ option_set::value_type(rhs)); + } + +} } // namespace cfacter::util +#endif + diff --git a/lib/inc/util/posix/scoped_descriptor.hpp b/lib/inc/util/posix/scoped_descriptor.hpp new file mode 100644 index 0000000000..749875f5f4 --- /dev/null +++ b/lib/inc/util/posix/scoped_descriptor.hpp @@ -0,0 +1,34 @@ +#ifndef __POSIX_SCOPED_DESCRIPTOR_HPP__ +#define __POSIX_SCOPED_DESCRIPTOR_HPP__ + +#include "../scoped_resource.hpp" +#include + +namespace cfacter { namespace util { namespace posix { + /** + * Represents a scoped file descriptor for POSIX systems. + * Automatically closes the file descriptor when it goes out of scope. + */ + struct scoped_descriptor : scoped_resource + { + /** + * Constructs a scoped_descriptor. + * @param descriptor The file descriptor to close when destroyed. + */ + scoped_descriptor(int descriptor) : + scoped_resource(std::move(descriptor), close) + { + } + + private: + static void close(int descriptor) + { + if (descriptor >= 0) { + ::close(descriptor); + } + } + }; + +} } } // namespace cfacter::util::posix + +#endif diff --git a/lib/inc/util/scoped_resource.hpp b/lib/inc/util/scoped_resource.hpp new file mode 100644 index 0000000000..03912f9e3d --- /dev/null +++ b/lib/inc/util/scoped_resource.hpp @@ -0,0 +1,79 @@ +#ifndef __SCOPED_RESOURCE_HPP__ +#define __SCOPED_RESOURCE_HPP__ + +#include + +namespace cfacter { namespace util { + /** + * Simple class that is used for the RAII pattern. + * Used to scope a resource. When it goes out of scope, a deleter + * function is called to delete the resource. + * @tparam T The type of resource being scoped. + */ + template struct scoped_resource + { + /** + * Constructs a scoped_resource. + * Takes ownership of the given resource. + * @param resource The resource to scope. + * @param deleter The function to call when the resource goes out of scope. + */ + scoped_resource(T&& resource, std::function deleter) : + _resource(resource), + _deleter(deleter) + { + } + + // Force non-copyable + scoped_resource(scoped_resource const&) = delete; + scoped_resource& operator=(scoped_resource const&) = delete; + + // Allow moving + scoped_resource(scoped_resource&&) = default; + scoped_resource& operator=(scoped_resource&&) = default; + + /** + * Destructs a scoped_resource. + */ + virtual ~scoped_resource() + { + release(); + } + + /** + * Implicitly casts to T&. + * @return Returns reference-to-T. + */ + operator T&() + { + return _resource; + } + + /** + * Implicitly casts to T const&. + * @return Returns const-reference-to-T. + */ + operator T const&() const + { + return _resource; + } + + /** + * Releases the resource before destruction. + */ + void release() + { + if (_deleter) { + _deleter(_resource); + _deleter = std::function(); + } + } + + private: + T _resource; + std::function _deleter; + }; + +} } // namespace cfacter::util + +#endif diff --git a/lib/inc/util/string.hpp b/lib/inc/util/string.hpp new file mode 100644 index 0000000000..3ead4cb19e --- /dev/null +++ b/lib/inc/util/string.hpp @@ -0,0 +1,145 @@ +#ifndef __STRING_HPP__ +#define __STRING_HPP__ + +#include +#include + +// TODO: non-standard header +#include "string.h" + +namespace cfacter { namespace util { + + /** + * In-place trims whitespace from the start (left side) of a string. + * @param str The string to trim whitespace from. + * @return Returns the given string. + */ + std::string& ltrim(std::string& str); + /** + * In-place trims whitespace from the start (left side) of a string. + * @param str The string to trim whitespace from. + * @return Returns the given string. + */ + std::string& ltrim(std::string&& str); + + /** + * In-place trims whitespace from the end (right side) of a string. + * @param str The string to trim whitespace from. + * @return Returns the given string. + */ + std::string& rtrim(std::string& str); + /** + * In-place trims whitespace from the end (right side) of a string. + * @param str The string to trim whitespace from. + * @return Returns the given string. + */ + std::string& rtrim(std::string&& str); + + /** + * In-place trims whitespace from the the start and end (both sides) of a string. + * @param str The string to trim whitespace from. + * @return Returns the given string. + */ + std::string& trim(std::string& str); + /** + * In-place trims whitespace from the the start and end (both sides) of a string. + * @param str The string to trim whitespace from. + * @return Returns the given string. + */ + std::string& trim(std::string&& str); + + /** + * Tokenizes the given string. + * Tokens are separated by whitespace. + * @param str The string to tokenize. + * @return Returns a vector of tokens. + */ + std::vector tokenize(std::string const& str); + + /** + * Splits a string into parts based on a delimiter. + * @param str The string to split. + * @param delim The delimiter to split on. + * @return Returns a vector of parts. + */ + std::vector split(std::string const& str, char delim = ' '); + + /** + * Joins a vector of strings together with the given delimiter. + * @param strings The vector of strings to join. + * @param delimiter The delimiter to use between strings. + * @return Returns a string that is the join of the given string. + */ + std::string join(std::vector const& strings, std::string const& delimiter); + + /** + * Converts the given string to lowercase. + * @param str The string to convert to lowercase. + * @return Returns the given string. + */ + std::string& to_lower(std::string& str); + /** + * Converts the given string to lowercase. + * @param str The string to convert to lowercase. + * @return Returns the given string. + */ + std::string& to_lower(std::string&& str); + + /** + * Converts the given string to uppercase. + * @param str The string to convert to uppercase. + * @return Returns the given string. + */ + std::string& to_upper(std::string& str); + /** + * Converts the given string to uppercase. + * @param str The string to convert to uppercase. + * @return Returns the given string. + */ + std::string& to_upper(std::string&& str); + + /** + * Character trait type for case-insensitive comparisons. + * Based on Herb Sutter's post: http://www.gotw.ca/gotw/029.htm + */ + struct ci_char_traits : public std::char_traits + { + static bool eq(char c1, char c2) + { + return ::toupper(c1) == ::toupper(c2); + } + + static bool ne(char c1, char c2) + { + return ::toupper(c1) != ::toupper(c2); + } + + static bool lt(char c1, char c2) + { + return ::toupper(c1) < ::toupper(c2); + } + + static int compare(char const* s1, char const* s2, size_t n) + { + // TODO: this isn't a standard function; we'll need an #ifdef to support non-POSIX platforms + return strncasecmp(s1, s2, n); + } + + static char const* find(char const* s, int n, char a) + { + while (n-- > 0 && toupper(*s) != toupper(a)) { + ++s; + } + return s; + } + }; + + /** + * Represents a case-insensitive string. + */ + typedef std::basic_string ci_string; + +}} // namespace cfacter::util + +#endif /* STRING_HPP */ + diff --git a/lib/scoped_resource.hpp b/lib/scoped_resource.hpp deleted file mode 100644 index aca9ee0170..0000000000 --- a/lib/scoped_resource.hpp +++ /dev/null @@ -1,89 +0,0 @@ -#ifndef __SCOPED_RESOURCE_HPP__ -#define __SCOPED_RESOURCE_HPP__ - -#include - -namespace cfacter { - -/** - Simple class that is used for the RAII pattern. - - Used to scope a resource. When it goes out of scope, a deleter - function is called to delete the resource. -*/ -template struct scoped_resource -{ - /** - Constructs a scoped_resource. - - Takes ownership of the given resource. - @param resource The resource to scope. - @param deleter The function to call when the resource goes out of scope. - */ - scoped_resource(T&& resource, std::function deleter) : - _resource(resource), - _deleter(deleter) - { - } - - // Force non-copyable - scoped_resource(scoped_resource const&) = delete; - scoped_resource& operator=(scoped_resource const&) = delete; - - // Force non-moveable - scoped_resource(scoped_resource&&) = delete; - scoped_resource& operator=(scoped_resource&&) = delete; - - /** - Destructs a scoped_resource. - */ - virtual ~scoped_resource() - { - _deleter(_resource); - } - - /** - Implicitly casts to T&. - */ - operator T&() - { - return _resource; - } - - /** - Implicitly casts to T const&. - */ - operator T const&() const - { - return _resource; - } - -private: - T _resource; - std::function _deleter; -}; - -/** - Represents a scoped file descriptor. - - Automatically closes the file descriptor when it goes out of scope. -*/ -struct scoped_descriptor : scoped_resource -{ - scoped_descriptor(int descriptor) : - scoped_resource(std::move(descriptor), close) - { - } - -private: - static void close(int descriptor) - { - if (descriptor >= 0) { - ::close(descriptor); - } - } -}; - -} // namespace cfacter - -#endif diff --git a/lib/src/cfacterlib.cc b/lib/src/cfacterlib.cc new file mode 100644 index 0000000000..68df72bcb4 --- /dev/null +++ b/lib/src/cfacterlib.cc @@ -0,0 +1,29 @@ +#include + +#include "rapidjson/document.h" +#include "rapidjson/prettywriter.h" +#include "rapidjson/stringbuffer.h" + +#include + +using namespace std; +using namespace cfacter::facts; + +int to_json(char *facts_json, size_t facts_len) +{ + // TODO: re-implement this with support for structured facts + strncpy(facts_json, "", facts_len); + return 0; +} + +int get_value(const char *fact, char *value, size_t value_len) +{ + // TODO: reimplement this with support for structured facts + strncpy(value, "", value_len); + return 0; +} + +void search_external(const char *dirs) +{ + // TODO +} diff --git a/lib/src/execution/posix/execution.cc b/lib/src/execution/posix/execution.cc new file mode 100644 index 0000000000..b2c4ae8d7b --- /dev/null +++ b/lib/src/execution/posix/execution.cc @@ -0,0 +1,175 @@ +#include +#include +#include +#include +#include +#include + +#include + +using namespace std; +using namespace cfacter::util; +using namespace cfacter::util::posix; + +namespace cfacter { namespace execution { + + static string execute( + string const& file, + vector const* arguments, + vector const* environment, + option_set const& options); + + string execute( + string const& file, + option_set const& options) + { + return execute(file, nullptr, nullptr, options); + } + + string execute( + string const& file, + vector const& arguments, + option_set const& options) + { + return execute(file, &arguments, nullptr, options); + } + + string execute( + string const& file, + vector const& arguments, + vector const& environment, + option_set const& options) + { + return execute(file, &arguments, &environment, options); + } + + string execute( + string const& file, + vector const* arguments, + vector const* environment, + option_set const& options) + { + // Create the pipes for stdin/stdout/stderr redirection + int pipes[2]; + if (pipe(pipes) < 0) { + throw execution_exception("failed to allocate pipe for input redirection."); + } + scoped_descriptor stdin_read(pipes[0]); + scoped_descriptor stdin_write(pipes[1]); + + if (pipe(pipes) < 0) { + throw execution_exception("failed to allocate pipe for output redirection."); + } + scoped_descriptor stdout_read(pipes[0]); + scoped_descriptor stdout_write(pipes[1]); + + // Fork the child process + pid_t child = fork(); + if (child < 0) { + throw execution_exception("failed to fork child process."); + } + + // A non-zero child pid means we're running in the context of the parent process + if (child) + { + // Close the unused descriptors + stdin_read.release(); + stdout_write.release(); + + // TODO: take a vector of bytes to write to stdin? + stdin_write.release(); + + // Read the child output into a string + ostringstream output; + char buffer[4096]; + int count; + do + { + count = read(stdout_read, buffer, sizeof(buffer)); + if (count < 0) { + throw new execution_exception("failed to read child output."); + } + output.write(buffer, count); + } + while (count > 0); + + // Wait for the child to exit + int status; + waitpid(child, &status, 0); + if (WIFEXITED(status)) { + status = WEXITSTATUS(status); + if (status != 0 && options[execution_options::throw_on_nonzero_exit]) { + throw child_exit_exception(status, output.str(), "child process returned non-zero exit status."); + } + } else if (WIFSIGNALED(status)) { + status = WTERMSIG(status); + if (options[execution_options::throw_on_signal]) { + throw child_signal_exception(status, output.str(), "child process was terminated by signal."); + } + } + if (options[execution_options::trim_output]) { + return trim(output.str()); + } + return output.str(); + } + + // Child continues here + try + { + if (dup2(stdin_read, STDIN_FILENO) == -1) { + throw execution_exception("failed to redirect child stdin."); + } + + if (dup2(stdout_write, STDOUT_FILENO) == -1) { + throw execution_exception("failed to redirect child stdout."); + } + + if (options[execution_options::redirect_stderr]) { + if (dup2(stdout_write, STDERR_FILENO) == -1) { + throw execution_exception("failed to redirect child stderr."); + } + } + else { + // Redirect to null + scoped_descriptor dev_null(open("/dev/null", O_RDONLY)); + if (dev_null < 0 || dup2(dev_null, STDERR_FILENO) == -1) { + throw execution_exception("failed to redirect child stderr to null."); + } + } + + // Release the parent descriptors before the exec + stdin_read.release(); + stdin_write.release(); + stdout_read.release(); + stdout_write.release(); + + // Build a vector of pointers to the arguments + // The first element is the program name + // The given program arguments then follow + // The last element is a null to terminate the array + vector args((arguments ? arguments->size() : 1) + 2 /* argv[0] + null */); + args[0] = file.c_str(); + if (arguments) { + for (size_t i = 0; i < arguments->size(); ++i) { + args[i + 1] = arguments->at(i).c_str(); + } + } + + // TODO: set up environment if specified (execvpe is a GNU extension) + exit(execvp(file.c_str(), const_cast(args.data()))); + } + catch (exception& ex) + { + // Write out any exception message to "stderr" and exit + if (options[execution_options::redirect_stderr]) { + string message = ex.what(); + message += "\n"; + write(stdout_write, message.c_str(), message.size()); + } + exit(-1); + } + + // CHILD DOES NOT RETURN + } + +} } // namespace cfacter::executions diff --git a/lib/src/facts/array_value.cc b/lib/src/facts/array_value.cc new file mode 100644 index 0000000000..a50981bdee --- /dev/null +++ b/lib/src/facts/array_value.cc @@ -0,0 +1,28 @@ +#include +#include + +using namespace std; + +namespace cfacter { namespace facts { + + string array_value::to_string() const + { + ostringstream result; + + // Write out the elements in the array + result << "["; + bool first = true; + for (auto const& element : _elements) { + if (!first) { + result << ", "; + } + else { + first = true; + } + result << element->to_string(); + } + result << "]"; + return result.str(); + } + +} } // namespace cfacter::facts diff --git a/lib/src/facts/fact.cc b/lib/src/facts/fact.cc new file mode 100644 index 0000000000..f246963819 --- /dev/null +++ b/lib/src/facts/fact.cc @@ -0,0 +1,15 @@ +#include "../../../version.h" +#include +#include + +using namespace std; + +namespace cfacter { namespace facts { + + void populate_common_facts() + { + auto& facts = fact_map::instance(); + facts.add_fact(fact("cfacterversion", make_value(CFACTER_VERSION))); + } + +}} // namespace cfacter::facts diff --git a/lib/src/facts/fact_map.cc b/lib/src/facts/fact_map.cc new file mode 100644 index 0000000000..55afb3e2b9 --- /dev/null +++ b/lib/src/facts/fact_map.cc @@ -0,0 +1,102 @@ +#include +#include + +using namespace std; + +namespace cfacter { namespace facts { + + fact_map fact_map::_instance; + + void fact_map::add_fact(fact&& f) + { + // Search for the fact first + auto const& it = _facts.lower_bound(f.name()); + if (it != _facts.end() && !(_facts.key_comp()(f.name(), it->first))) { + throw fact_exists_exception("fact " + f.name() + " already exists."); + } + _facts.insert(it, std::make_pair(f.name(), std::move(f))); + } + + void fact_map::remove(string const& name) + { + _facts.erase(name); + } + + void fact_map::clear() + { + _facts.clear(); + _resolvers.clear(); + } + + bool fact_map::empty() const + { + return _facts.empty() && _resolvers.empty(); + } + + fact const* fact_map::get_fact(std::string const& name) + { + load(); + + auto it = _facts.find(name); + if (it == _facts.end()) { + // Check the resolvers + auto const& resolver_it = _resolvers.find(name); + if (resolver_it == _resolvers.end()) { + // Fact not found + return nullptr; + } + auto resolver = resolver_it->second; + // Remove all facts mapped to this resolver + for (auto const& name : resolver->names()) { + _resolvers.erase(name); + } + // Add all resolved facts + resolver->resolve(*this); + + it = _facts.find(name); + if (it == _facts.end()) { + // Fact not found after resolution + return nullptr; + } + } + return &it->second; + } + + + void fact_map::each(std::function func) + { + load(); + resolve_facts(); + + std::find_if(begin(_facts), end(_facts), [&func](fact_map_type::value_type const& it) { + return func(it.first, it.second.val()); + }); + } + + fact_map& fact_map::instance() + { + return fact_map::_instance; + } + + void fact_map::load() + { + if (!empty()) { + return; + } + populate_common_facts(); + populate_platform_facts(); + } + + void fact_map::resolve_facts() + { + // Go through every resolver and get each fact + while (!_resolvers.empty()) + { + // Copy the string from the key as resolving the facts will + // destruct the resolver entry in the map + string name = _resolvers.begin()->first; + get_fact(name); + } + } + +}} // namespace cfacter::facts diff --git a/lib/src/facts/fact_resolver.cc b/lib/src/facts/fact_resolver.cc new file mode 100644 index 0000000000..37f4e57f22 --- /dev/null +++ b/lib/src/facts/fact_resolver.cc @@ -0,0 +1,17 @@ +#include +#include + +using namespace std; + +namespace cfacter { namespace facts { + + void fact_resolver::resolve(fact_map& facts) + { + if (_resolving) { + throw circular_resolution_exception("a cycle in fact resolution was detected."); + } + cycle_guard guard(_resolving); + return resolve_facts(facts); + } + +}} // namespace cfacter::facts diff --git a/lib/src/facts/linux/fact.cc b/lib/src/facts/linux/fact.cc new file mode 100644 index 0000000000..95f643c5ed --- /dev/null +++ b/lib/src/facts/linux/fact.cc @@ -0,0 +1,18 @@ +#include +#include +#include +#include + +using namespace std; + +namespace cfacter { namespace facts { + + void populate_platform_facts() + { + auto& facts = fact_map::instance(); + facts.add_resolver(); + facts.add_resolver(); + facts.add_resolver(); + } + +} } // namespace cfacter::facts \ No newline at end of file diff --git a/lib/src/facts/linux/lsb_resolver.cc b/lib/src/facts/linux/lsb_resolver.cc new file mode 100644 index 0000000000..32fb7fbc78 --- /dev/null +++ b/lib/src/facts/linux/lsb_resolver.cc @@ -0,0 +1,26 @@ +#include +#include +#include +#include + +using namespace std; +using namespace cfacter::execution; + +namespace cfacter { namespace facts { namespace linux { + + void lsb_resolver::resolve_facts(fact_map& facts) + { + // Resolve all lsb-related facts + resolve_dist_id(facts); + } + + void lsb_resolver::resolve_dist_id(fact_map& facts) + { + string value = execute("lsb_release", {"-i", "-s"}, { execution_options::trim_output }); + if (value.empty()) { + return; + } + facts.add_fact(fact(lsb_dist_id_name, make_value(std::move(value)))); + } + +}}} // namespace cfacter::facts::linux diff --git a/lib/src/facts/linux/operating_system_resolver.cc b/lib/src/facts/linux/operating_system_resolver.cc new file mode 100644 index 0000000000..73a6589786 --- /dev/null +++ b/lib/src/facts/linux/operating_system_resolver.cc @@ -0,0 +1,174 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace cfacter::util; + +namespace cfacter { namespace facts { namespace linux { + + void operating_system_resolver::resolve_operating_system(fact_map& facts) + { + auto dist_id = facts.get_value(lsb_resolver::lsb_dist_id_name); + + // Start by checking for Cumulus Linux + string value = check_cumulus_linux(); + + // Check for Debian next + if (value.empty()) { + value = check_debian_linux(dist_id); + } + + // Check for Oracle Enterprise Linux next + if (value.empty()) { + value = check_oracle_linux(); + } + + // Check for RedHat next + if (value.empty()) { + value = check_redhat_linux(); + } + + // Check for SuSE next + if (value.empty()) { + value = check_suse_linux(); + } + + // Check for some other Linux next + if (value.empty()) { + value = check_other_linux(); + } + + // If no value, default to the base implementation + if (value.empty()) { + posix::operating_system_resolver::resolve_operating_system(facts); + return; + } + + // Add the fact + facts.add_fact(fact(operating_system_name, make_value(value))); + } + + string operating_system_resolver::check_cumulus_linux() + { + // Check for Cumulus Linux + if (file::exists("/etc/os-release")) { + string contents = trim(file::read("/etc/os-release")); + string release; + if (RE2::FullMatch(contents, "^NAME=[\"']?(.+?)[\"']?$", &release)) { + RE2::GlobalReplace(&release, "[^a-zA-Z]", ""); + if (release == "CumulusLinux") { + return "CumulusLinux"; + } + } + } + return string(); + } + + string operating_system_resolver::check_debian_linux(string_value const* dist_id) + { + // Check for Debian variants + if (file::exists("/etc/debian_version")) { + if (dist_id) { + if (dist_id->value() == "Ubuntu") { + return "Ubuntu"; + } + if (dist_id->value() == "LinuxMint") { + return "LinuxMint"; + } + } + return "Debian"; + } + return string(); + } + + string operating_system_resolver::check_oracle_linux() + { + if (file::exists("/etc/enterprise-release")) { + if (file::exists("/etc/ovs-release")) { + return "OVS"; + } + return "OEL"; + } + return string(); + } + + string operating_system_resolver::check_redhat_linux() + { + if (file::exists("/etc/redhat-release")) { + static map regexs { + { "(?i)centos", "CentOS" }, + { "(?i)scientific linux CERN", "SLC" }, + { "(?i)scientific linux release", "Scientific" }, + { "(?i)^cloudlinux", "CloudLinux" }, + { "(?i)Ascendos", "Ascendos" }, + { "(?i)^XenServer", "XenServer" }, + { "XCP", "XCP" }, + { "(?i)^Parallels Server Bare Metal", "PSBM" }, + { "^Fedora release", "Fedora" }, + }; + + string contents = trim(file::read("/etc/redhat-release")); + for (auto const& kvp : regexs) { + if (RE2::PartialMatch(contents, kvp.first)) { + return kvp.second; + } + } + return "RedHat"; + } + return string(); + } + + string operating_system_resolver::check_suse_linux() + { + if (file::exists("/etc/SuSE-release")) { + static map regexs { + { "(?i)^SUSE LINUX Enterprise Server", "SLES" }, + { "(?i)^SUSE LINUX Enterprise Desktop", "SLED" }, + { "(?i)^openSUSE", "OpenSuSE" }, + }; + + string contents = trim(file::read("/etc/SuSE-release")); + for (auto const& kvp : regexs) { + if (RE2::PartialMatch(contents, kvp.first)) { + return kvp.second; + } + } + return "SuSE"; + } + return string(); + } + + string operating_system_resolver::check_other_linux() + { + static map files { + { "/etc/openwrt_release", "OpenWrt" }, + { "/etc/gentoo-release", "Gentoo" }, + { "/etc/mandriva-release", "Mandriva" }, + { "/etc/mandrake-release", "Mandrake" }, + { "/etc/meego-release", "MeeGo" }, + { "/etc/arch-release", "Archlinux" }, + { "/etc/oracle-release", "OracleLinux" }, + { "/etc/vmware-release", "VMWareESX" }, + { "/etc/bluewhite64-version", "Bluewhite64" }, + { "/etc/slamd64-version", "Slamd64" }, + { "/etc/slackware-version", "Slackware" }, + { "/etc/alpine-release", "Alpine" }, + { "/etc/mageia-release", "Mageia" }, + { "/etc/system-release", "Amazon" }, + }; + + for (auto const& kvp : files) { + if (file::exists(kvp.first)) { + return kvp.second; + } + } + return string(); + } + +}}} // namespace cfacter::facts::linux diff --git a/lib/src/facts/osx/fact.cc b/lib/src/facts/osx/fact.cc new file mode 100644 index 0000000000..4aafe5d127 --- /dev/null +++ b/lib/src/facts/osx/fact.cc @@ -0,0 +1,16 @@ +#include +#include +#include + +using namespace std; + +namespace cfacter { namespace facts { + + void populate_platform_facts() + { + auto& facts = fact_map::instance(); + facts.add_resolver(); + facts.add_resolver(); + } + +} } // namespace cfacter::facts \ No newline at end of file diff --git a/lib/src/facts/posix/kernel_resolver.cc b/lib/src/facts/posix/kernel_resolver.cc new file mode 100644 index 0000000000..7e14ffefd8 --- /dev/null +++ b/lib/src/facts/posix/kernel_resolver.cc @@ -0,0 +1,74 @@ +#include +#include +#include +#include +#include + +using namespace std; +using namespace cfacter::execution; +using namespace cfacter::util; + +namespace cfacter { namespace facts { namespace posix { + + void kernel_resolver::resolve_facts(fact_map& facts) + { + // Resolve all kernel-related facts + resolve_kernel(facts); + resolve_kernel_release(facts); + resolve_kernel_version(facts); + resolve_kernel_major_version(facts); + } + + void kernel_resolver::resolve_kernel(fact_map& facts) + { + string value = execute("uname", {"-s"}, { execution_options::trim_output }); + if (value.empty()) { + return; + } + facts.add_fact(fact(kernel_name, make_value(std::move(value)))); + } + + void kernel_resolver::resolve_kernel_release(fact_map& facts) + { + string value = execute("uname", {"-r"}, { execution_options::trim_output }); + if (value.empty()) { + return; + } + facts.add_fact(fact(kernel_release_name, make_value(std::move(value)))); + } + + void kernel_resolver::resolve_kernel_version(fact_map& facts) + { + auto version = facts.get_value(kernel_release_name); + if (!version) { + return; + } + // Use everything up until the first - character in the kernel release fact + string value = version->value(); + auto pos = value.find('-'); + if (pos != string::npos) { + value = value.substr(0, pos); + } + facts.add_fact(fact(kernel_version_name, make_value(std::move(value)))); + } + + void kernel_resolver::resolve_kernel_major_version(fact_map& facts) + { + auto version = facts.get_value(kernel_release_name); + if (!version) { + return; + } + + // Use everything up until the second '.' character in the kernel release fact + string value = version->value(); + auto pos = value.find('.'); + if (pos != string::npos) { + pos = value.find('.', pos + 1); + if (pos != string::npos) { + value = value.substr(0, pos); + } + } + facts.add_fact(fact(kernel_maj_release_name, make_value(std::move(value)))); + } + +}}} // namespace cfacter::facts::posix diff --git a/lib/src/facts/posix/operating_system_resolver.cc b/lib/src/facts/posix/operating_system_resolver.cc new file mode 100644 index 0000000000..7702019416 --- /dev/null +++ b/lib/src/facts/posix/operating_system_resolver.cc @@ -0,0 +1,27 @@ +#include +#include +#include +#include + +using namespace std; + +namespace cfacter { namespace facts { namespace posix { + + void operating_system_resolver::resolve_facts(fact_map& facts) + { + // Resolve all operating system related facts + resolve_operating_system(facts); + } + + void operating_system_resolver::resolve_operating_system(fact_map& facts) + { + // Default to the same value as the kernel + auto kernel = facts.get_value(kernel_resolver::kernel_name); + if (!kernel) { + return; + } + + facts.add_fact(fact(operating_system_name, make_value(kernel->value()))); + } + +}}} // namespace cfacter::facts::posix diff --git a/lib/src/facts/string_value.cc b/lib/src/facts/string_value.cc new file mode 100644 index 0000000000..db72d0c6a1 --- /dev/null +++ b/lib/src/facts/string_value.cc @@ -0,0 +1,12 @@ +#include + +using namespace std; + +namespace cfacter { namespace facts { + + string string_value::to_string() const + { + return "\"" + _value + "\""; + } + +} } // namespace cfacter::facts \ No newline at end of file diff --git a/lib/src/util/posix/file.cc b/lib/src/util/posix/file.cc new file mode 100644 index 0000000000..be34ceff6c --- /dev/null +++ b/lib/src/util/posix/file.cc @@ -0,0 +1,28 @@ +#include +#include +#include +#include + +using namespace std; + +namespace cfacter { namespace util { + + bool file::exists(string const& path) + { + struct stat buffer; + return stat(path.c_str(), &buffer) == 0; + } + + // TODO: this is standard-compliant, so it can be shared between POSIX and Windows + string file::read(string const& path) + { + ifstream in(path, std::ios::in | std::ios::binary); + ostringstream contents; + if (in) + { + contents << in.rdbuf(); + } + return contents.str(); + } + +}} // namespace cfacter::util \ No newline at end of file diff --git a/lib/src/util/string.cc b/lib/src/util/string.cc new file mode 100644 index 0000000000..16a1934e37 --- /dev/null +++ b/lib/src/util/string.cc @@ -0,0 +1,102 @@ +#include +#include +#include +#include +#include + +using namespace std; + +namespace cfacter { namespace util { + + string& ltrim(string& str) + { + str.erase(str.begin(), find_if(str.begin(), str.end(), [](char c) { return !isspace(c); })); + return str; + } + + string& ltrim(string&& str) + { + return ltrim(str); + } + + string& rtrim(string& str) + { + str.erase(find_if(str.rbegin(), str.rend(), [](char c) { return !isspace(c); }).base(), str.end()); + return str; + } + + string& rtrim(string&& str) + { + return rtrim(str); + } + + string& trim(std::string& str) + { + return ltrim(rtrim(str)); + } + + string& trim(string&& str) + { + return trim(str); + } + + vector tokenize(string const& str) + { + istringstream stream(str); + return vector(istream_iterator(stream), istream_iterator()); + } + + vector split(string const& str, char delim) + { + vector parts; + istringstream stream(str); + + string part; + while (getline(stream, part, delim)) { + if (part.empty()) { + continue; + } + parts.push_back(std::move(part)); + } + return parts; + } + + string join(vector const& strings, string const& delimiter) + { + ostringstream stream; + + bool first = true; + for (auto const& str : strings) { + if (!first) { + stream << delimiter; + } else { + first = false; + } + stream << str; + } + return stream.str(); + } + + string& to_lower(string& str) + { + transform(str.begin(), str.end(), str.begin(), ::tolower); + return str; + } + + string& to_lower(string&& str) + { + return to_lower(str); + } + + string& to_upper(string& str) + { + transform(str.begin(), str.end(), str.begin(), ::toupper); + return str; + } + + string& to_upper(string&& str) + { + return to_upper(str); + } + +}} // namespace cfacter::util From 372d72fa89f57efcf7eeea66318b3aecf462bd88 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Wed, 9 Apr 2014 18:03:59 -0700 Subject: [PATCH 1765/3753] (CFACT-3) Fix unnecessary element in execution arguments vector. There is an off-by-one error when passed a nullptr for arguments in execute. This causes the vector of argument pointers to be one element too big. However, since the element is zero-initialized, the extra null isn't causing any harm other than allocating one pointer too many. --- lib/src/execution/posix/execution.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/execution/posix/execution.cc b/lib/src/execution/posix/execution.cc index b2c4ae8d7b..2d7f2b7da5 100644 --- a/lib/src/execution/posix/execution.cc +++ b/lib/src/execution/posix/execution.cc @@ -147,7 +147,7 @@ namespace cfacter { namespace execution { // The first element is the program name // The given program arguments then follow // The last element is a null to terminate the array - vector args((arguments ? arguments->size() : 1) + 2 /* argv[0] + null */); + vector args((arguments ? arguments->size() : 0) + 2 /* argv[0] + null */); args[0] = file.c_str(); if (arguments) { for (size_t i = 0; i < arguments->size(); ++i) { From a82ac09b49bedfa85495bc3e6eec165e7622bd0a Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Wed, 9 Apr 2014 18:17:58 -0700 Subject: [PATCH 1766/3753] (CFACT-3) Fix fact resolution cycle detection. Because the resolver entries were removed before calling resolve, any cycle in the fact resolution was broken because a nullptr was returned instead. By removing the resolver entries after the resolve occurs, we can properly detect a resolution cycle. --- lib/src/facts/fact_map.cc | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/src/facts/fact_map.cc b/lib/src/facts/fact_map.cc index 55afb3e2b9..b7bc4811a7 100644 --- a/lib/src/facts/fact_map.cc +++ b/lib/src/facts/fact_map.cc @@ -14,6 +14,9 @@ namespace cfacter { namespace facts { if (it != _facts.end() && !(_facts.key_comp()(f.name(), it->first))) { throw fact_exists_exception("fact " + f.name() + " already exists."); } + + // Remove any resolver for this fact and add the fact + _resolvers.erase(f.name()); _facts.insert(it, std::make_pair(f.name(), std::move(f))); } @@ -46,12 +49,14 @@ namespace cfacter { namespace facts { return nullptr; } auto resolver = resolver_it->second; - // Remove all facts mapped to this resolver + + // Resolve the facts + resolver->resolve(*this); + + // Remove any associated facts that didn't resolve for (auto const& name : resolver->names()) { _resolvers.erase(name); } - // Add all resolved facts - resolver->resolve(*this); it = _facts.find(name); if (it == _facts.end()) { From 957350222c9f96b5f9ee71f6d61827e4af1c21c7 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Wed, 9 Apr 2014 18:53:44 -0700 Subject: [PATCH 1767/3753] (CFACT-3) Fix cpplint target. The refactoring and the previous change to support generating CMake files into sub-directories broke the cpplint target. The fix is to properly glob recursively for source files and also reference the python script based on the project source directory. --- CMakeLists.txt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 860fec61fe..f6f87b9f3a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,23 +42,25 @@ else() "-legal/copyright" # Not yet "-runtime/references" # Not sure about this religion "-readability/streams" # What? + "-readability/namespace" # Ignore nested namespace comment formatting "-whitespace/braces" # Is there a k&r setting? "-whitespace/line_length" # Well yeah, but ... not just now "-runtime/arrays" # Sizing an array with a 'const int' doesn't make it variable sized "-readability/todo" # Seriously? todo comments need to identify an owner? pffft + "-whitespace/empty_loop_body" # Can't handle do { ... } while(expr); ) - file(GLOB ALL_SOURCES lib/*.cc exe/*.cc) + file(GLOB_RECURSE ALL_SOURCES lib/*.cc lib/*.h lib/*.hpp exe/*.cc exe/*.h exe/*.hpp) - set(CPPLINT_PATH "ext/cpplint.py") + set(CPPLINT_PATH "${PROJECT_SOURCE_DIR}/ext/cpplint.py") - set(CPPLINT_ARGS "") + set(CPPLINT_ARGS "--extensions=cc,hpp,h") if (CPPLINT_FILTER) string(REPLACE ";" "," CPPLINT_FILTER "${CPPLINT_FILTER}") - set(CPPLINT_ARGS "${CPPLINT_ARGS}--filter=${CPPLINT_FILTER}") + set(CPPLINT_ARGS "${CPPLINT_ARGS};--filter=${CPPLINT_FILTER}") endif() if (MSVC) - set(CPPLINT_ARGS "${CPPLINT_ARGS} --output=vs7") + set(CPPLINT_ARGS "${CPPLINT_ARGS};--output=vs7") endif() add_custom_target(cpplint From 5319e4e52a1c237b009af2d9b32c297e06e24aaf Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Wed, 9 Apr 2014 19:34:44 -0700 Subject: [PATCH 1768/3753] (CFACT-3) Fix cpplint errors. Fixing the cpplint script to treat .hpp files as headers. Fixing all the various errors cpplint is currently complaining about. --- ext/cpplint.py | 9 ++++---- lib/inc/cfacterlib.h | 8 +++---- lib/inc/execution/execution.hpp | 23 ++++++++----------- lib/inc/facts/array_value.hpp | 12 +++++----- lib/inc/facts/fact.hpp | 10 ++++---- lib/inc/facts/fact_map.hpp | 12 +++++----- lib/inc/facts/fact_resolver.hpp | 16 ++++++------- lib/inc/facts/linux/lsb_resolver.hpp | 10 ++++---- .../facts/linux/operating_system_resolver.hpp | 12 +++++----- lib/inc/facts/posix/kernel_resolver.hpp | 10 ++++---- .../facts/posix/operating_system_resolver.hpp | 10 ++++---- lib/inc/facts/string_value.hpp | 10 ++++---- lib/inc/facts/value.hpp | 8 +++---- lib/inc/util/file.hpp | 8 +++---- lib/inc/util/option_set.hpp | 15 ++++++------ lib/inc/util/posix/scoped_descriptor.hpp | 12 +++++----- lib/inc/util/scoped_resource.hpp | 12 +++++----- lib/inc/util/string.hpp | 9 ++++---- lib/src/execution/posix/execution.cc | 5 ++-- lib/src/facts/array_value.cc | 5 ++-- lib/src/facts/fact.cc | 2 +- lib/src/facts/fact_map.cc | 2 +- lib/src/facts/fact_resolver.cc | 2 +- lib/src/facts/linux/fact.cc | 2 +- lib/src/facts/linux/lsb_resolver.cc | 2 +- .../facts/linux/operating_system_resolver.cc | 2 +- lib/src/facts/osx/fact.cc | 2 +- lib/src/facts/posix/kernel_resolver.cc | 2 +- .../facts/posix/operating_system_resolver.cc | 2 +- lib/src/facts/string_value.cc | 2 +- lib/src/util/posix/file.cc | 2 +- lib/src/util/string.cc | 2 +- 32 files changed, 116 insertions(+), 124 deletions(-) diff --git a/ext/cpplint.py b/ext/cpplint.py index 76d0735278..b51c3433e5 100755 --- a/ext/cpplint.py +++ b/ext/cpplint.py @@ -2180,6 +2180,7 @@ def CheckForNonStandardConstructs(filename, clean_lines, linenum, line) if (args and args.group(1) != 'void' and + not args.group(1).startswith('std::initializer_list<') and not Match(r'(const\s+)?%s(\s+const)?\s*(?:<\w+>\s*)?&' % re.escape(base_classname), args.group(1).strip())): error(filename, linenum, 'runtime/explicit', 5, @@ -3400,7 +3401,7 @@ def CheckStyle(filename, clean_lines, linenum, file_extension, nesting_state, # Check if the line is a header guard. is_header_guard = False - if file_extension == 'h': + if file_extension in ['h', 'hpp']: cppvar = GetHeaderGuardCPPVariable(filename) if (line.startswith('#ifndef %s' % cppvar) or line.startswith('#define %s' % cppvar) or @@ -3867,7 +3868,7 @@ def CheckLanguage(filename, clean_lines, linenum, file_extension, error(filename, linenum, 'runtime/init', 4, 'You seem to be initializing a member variable with itself.') - if file_extension == 'h': + if file_extension in ['h', 'hpp']: # TODO(unknown): check that 1-arg constructors are explicit. # How to tell it's a constructor? # (handled in CheckForNonStandardConstructs for now) @@ -4011,7 +4012,7 @@ def CheckLanguage(filename, clean_lines, linenum, file_extension, # Check for use of unnamed namespaces in header files. Registration # macros are typically OK, so we allow use of "namespace {" on lines # that end with backslashes. - if (file_extension == 'h' + if (file_extension in ['h', 'hpp'] and Search(r'\bnamespace\s*{', line) and line[-1] != '\\'): error(filename, linenum, 'build/namespaces', 4, @@ -4552,7 +4553,7 @@ def ProcessFileData(filename, file_extension, lines, error, CheckForCopyright(filename, lines, error) - if file_extension == 'h': + if file_extension in ['h', 'hpp']: CheckForHeaderGuard(filename, lines, error) RemoveMultiLineComments(filename, lines, error) diff --git a/lib/inc/cfacterlib.h b/lib/inc/cfacterlib.h index 1c803f7d7a..1fe01a8aad 100644 --- a/lib/inc/cfacterlib.h +++ b/lib/inc/cfacterlib.h @@ -1,15 +1,13 @@ -#ifndef __CFACTERLIB_H__ -#define __CFACTERLIB_H__ +#ifndef LIB_INC_CFACTERLIB_H_ +#define LIB_INC_CFACTERLIB_H_ #include extern "C" { - void clear(); int to_json(char *facts, size_t facts_len); int get_value(const char *fact, char *value, size_t value_len); void search_external(const char *dirs); - } -#endif +#endif // LIB_INC_CFACTERLIB_H_ diff --git a/lib/inc/execution/execution.hpp b/lib/inc/execution/execution.hpp index d109fc39d6..0cd2ecd392 100644 --- a/lib/inc/execution/execution.hpp +++ b/lib/inc/execution/execution.hpp @@ -1,5 +1,5 @@ -#ifndef __EXECUTION_HPP__ -#define __EXECUTION_HPP__ +#ifndef LIB_INC_EXECUTION_EXECUTION_HPP_ +#define LIB_INC_EXECUTION_EXECUTION_HPP_ #include #include @@ -78,7 +78,7 @@ namespace cfacter { namespace execution { */ std::string const& output() const { return _output; } - private: + private: std::string _output; }; @@ -105,7 +105,7 @@ namespace cfacter { namespace execution { */ int status_code() const { return _status_code; } - private: + private: int _status_code; }; @@ -132,7 +132,7 @@ namespace cfacter { namespace execution { */ int signal() const { return _signal; } - private: + private: int _signal; }; @@ -144,8 +144,7 @@ namespace cfacter { namespace execution { */ std::string execute( std::string const& file, - cfacter::util::option_set const& options = { execution_options::defaults } - ); + cfacter::util::option_set const& options = { execution_options::defaults }); /** * Executes the given program. @@ -157,8 +156,7 @@ namespace cfacter { namespace execution { std::string execute( std::string const& file, std::vector const& arguments, - cfacter::util::option_set const& options = { execution_options::defaults } - ); + cfacter::util::option_set const& options = { execution_options::defaults }); /** * Executes the given program. @@ -172,10 +170,9 @@ namespace cfacter { namespace execution { std::string const& file, std::vector const& arguments, std::vector const& environment, - cfacter::util::option_set const& options = { execution_options::defaults } - ); + cfacter::util::option_set const& options = { execution_options::defaults }); -} } // namespace cfacter::execution +}} // namespace cfacter::execution -#endif +#endif // LIB_INC_EXECUTION_EXECUTION_HPP_ diff --git a/lib/inc/facts/array_value.hpp b/lib/inc/facts/array_value.hpp index 430c2bfa58..0609c2d891 100644 --- a/lib/inc/facts/array_value.hpp +++ b/lib/inc/facts/array_value.hpp @@ -1,5 +1,5 @@ -#ifndef __ARRAY_VALUE_HPP__ -#define __ARRAY_VALUE_HPP__ +#ifndef LIB_INC_FACTS_ARRAY_VALUE_HPP_ +#define LIB_INC_FACTS_ARRAY_VALUE_HPP_ #include "value.hpp" #include @@ -23,7 +23,7 @@ namespace cfacter { namespace facts { * Constructs an array value. * @param elements The elements that make up the array. */ - array_value(std::vector>&& elements) : + explicit array_value(std::vector>&& elements) : _elements(std::move(elements)) { } @@ -54,11 +54,11 @@ namespace cfacter { namespace facts { */ std::vector> const& elements() const { return _elements; } - private: + private: std::vector> _elements; }; -} } // namespace cfacter::facts +}} // namespace cfacter::facts -#endif +#endif // LIB_INC_FACTS_ARRAY_VALUE_HPP_ diff --git a/lib/inc/facts/fact.hpp b/lib/inc/facts/fact.hpp index 0aeece2a69..8e06c652ac 100644 --- a/lib/inc/facts/fact.hpp +++ b/lib/inc/facts/fact.hpp @@ -1,5 +1,5 @@ -#ifndef __FACT_HPP__ -#define __FACT_HPP__ +#ifndef LIB_INC_FACTS_FACT_HPP_ +#define LIB_INC_FACTS_FACT_HPP_ #include "value.hpp" #include @@ -42,7 +42,7 @@ namespace cfacter { namespace facts { fact(fact&&) = default; fact& operator=(fact&&) = default; - private: + private: std::string _name; std::unique_ptr _value; }; @@ -53,7 +53,7 @@ namespace cfacter { namespace facts { void populate_common_facts(); void populate_platform_facts(); -} } // namespace cfacter:facts +}} // namespace cfacter:facts -#endif +#endif // LIB_INC_FACTS_FACT_HPP_ diff --git a/lib/inc/facts/fact_map.hpp b/lib/inc/facts/fact_map.hpp index 3ea6ddb380..3a5113ac01 100644 --- a/lib/inc/facts/fact_map.hpp +++ b/lib/inc/facts/fact_map.hpp @@ -1,5 +1,5 @@ -#ifndef __FACT_MAP_HPP__ -#define __FACT_MAP_HPP__ +#ifndef LIB_INC_FACTS_FACT_MAP_HPP_ +#define LIB_INC_FACTS_FACT_MAP_HPP_ #include #include @@ -119,8 +119,8 @@ namespace cfacter { namespace facts { */ static fact_map& instance(); - private: - //fact_map() {} + private: + fact_map() {} void load(); void resolve_facts(); @@ -130,7 +130,7 @@ namespace cfacter { namespace facts { resolver_map_type _resolvers; }; -}} // namespace cfacter::facts +}} // namespace cfacter::facts -#endif +#endif // LIB_INC_FACTS_FACT_MAP_HPP_ diff --git a/lib/inc/facts/fact_resolver.hpp b/lib/inc/facts/fact_resolver.hpp index 6a9a77bc1a..78f23d04fc 100644 --- a/lib/inc/facts/fact_resolver.hpp +++ b/lib/inc/facts/fact_resolver.hpp @@ -1,5 +1,5 @@ -#ifndef __RESOLVER_HPP__ -#define __RESOLVER_HPP__ +#ifndef LIB_INC_FACTS_FACT_RESOLVER_HPP_ +#define LIB_INC_FACTS_FACT_RESOLVER_HPP_ #include #include @@ -37,7 +37,7 @@ namespace cfacter { namespace facts { _value = false; } - private: + private: bool& _value; }; @@ -53,7 +53,7 @@ namespace cfacter { namespace facts { * Constructs a fact_resolver. * @param names The fact names the resolver is responsible for. */ - fact_resolver(std::initializer_list const& names) : + explicit fact_resolver(std::initializer_list const& names) : _names(names.begin(), names.end()), _resolving(false) { @@ -84,19 +84,19 @@ namespace cfacter { namespace facts { */ void resolve(fact_map& facts); - protected: + protected: /** * Called to resolve all facts the resolver is responsible for. * @param facts The fact map that is resolving facts. */ virtual void resolve_facts(fact_map& facts) = 0; - private: + private: std::vector _names; bool _resolving; }; -}} // namespace cfacter::facts +}} // namespace cfacter::facts -#endif +#endif // LIB_INC_FACTS_FACT_RESOLVER_HPP_ diff --git a/lib/inc/facts/linux/lsb_resolver.hpp b/lib/inc/facts/linux/lsb_resolver.hpp index a3b722a073..3d227730db 100644 --- a/lib/inc/facts/linux/lsb_resolver.hpp +++ b/lib/inc/facts/linux/lsb_resolver.hpp @@ -1,5 +1,5 @@ -#ifndef __LINUX_LSB_RESOLVER_HPP__ -#define __LINUX_LSB_RESOLVER_HPP__ +#ifndef LIB_INC_FACTS_LINUX_LSB_RESOLVER_HPP_ +#define LIB_INC_FACTS_LINUX_LSB_RESOLVER_HPP_ #include "../fact_resolver.hpp" @@ -23,7 +23,7 @@ namespace cfacter { namespace facts { namespace linux { { } - protected: + protected: /** * Called to resolve all facts the resolver is responsible for. * @param facts The fact map that is resolving facts. @@ -37,7 +37,7 @@ namespace cfacter { namespace facts { namespace linux { virtual void resolve_dist_id(fact_map& facts); }; -}}} // namespace cfacter::facts::linux +}}} // namespace cfacter::facts::linux -#endif +#endif // LIB_INC_FACTS_LINUX_LSB_RESOLVER_HPP_ diff --git a/lib/inc/facts/linux/operating_system_resolver.hpp b/lib/inc/facts/linux/operating_system_resolver.hpp index 7dcb80b3b0..a86d58f6ba 100644 --- a/lib/inc/facts/linux/operating_system_resolver.hpp +++ b/lib/inc/facts/linux/operating_system_resolver.hpp @@ -1,5 +1,5 @@ -#ifndef __LINUX_OPERATING_SYSTEM_RESOLVER_HPP__ -#define __LINUX_OPERATING_SYSTEM_RESOLVER_HPP__ +#ifndef LIB_INC_FACTS_LINUX_OPERATING_SYSTEM_RESOLVER_HPP_ +#define LIB_INC_FACTS_LINUX_OPERATING_SYSTEM_RESOLVER_HPP_ #include "../posix/operating_system_resolver.hpp" #include "../string_value.hpp" @@ -11,14 +11,14 @@ namespace cfacter { namespace facts { namespace linux { */ struct operating_system_resolver : posix::operating_system_resolver { - protected: + protected: /** * Called to resolve the operating system fact. * @param facts The fact map that is resolving facts. */ virtual void resolve_operating_system(fact_map& facts); - private: + private: static std::string check_cumulus_linux(); static std::string check_debian_linux(string_value const* dist_id); static std::string check_oracle_linux(); @@ -27,7 +27,7 @@ namespace cfacter { namespace facts { namespace linux { static std::string check_other_linux(); }; -}}} // namespace cfacter::facts::linux +}}} // namespace cfacter::facts::linux -#endif +#endif // LIB_INC_FACTS_LINUX_OPERATING_SYSTEM_RESOLVER_HPP_ diff --git a/lib/inc/facts/posix/kernel_resolver.hpp b/lib/inc/facts/posix/kernel_resolver.hpp index fe0c03fc33..c569da0ea7 100644 --- a/lib/inc/facts/posix/kernel_resolver.hpp +++ b/lib/inc/facts/posix/kernel_resolver.hpp @@ -1,5 +1,5 @@ -#ifndef __POSIX_KERNEL_RESOLVER_HPP__ -#define __POSIX_KERNEL_RESOLVER_HPP__ +#ifndef LIB_INC_FACTS_POSIX_KERNEL_RESOLVER_HPP_ +#define LIB_INC_FACTS_POSIX_KERNEL_RESOLVER_HPP_ #include "../fact_resolver.hpp" @@ -29,7 +29,7 @@ namespace cfacter { namespace facts { namespace posix { { } - protected: + protected: /** * Called to resolve all facts the resolver is responsible for. * @param facts The fact map that is resolving facts. @@ -57,7 +57,7 @@ namespace cfacter { namespace facts { namespace posix { virtual void resolve_kernel_major_version(fact_map& facts); }; -}}} // namespace cfacter::facts::posix +}}} // namespace cfacter::facts::posix -#endif +#endif // LIB_INC_FACTS_POSIX_KERNEL_RESOLVER_HPP_ diff --git a/lib/inc/facts/posix/operating_system_resolver.hpp b/lib/inc/facts/posix/operating_system_resolver.hpp index d15babc1ce..e25b32e90c 100644 --- a/lib/inc/facts/posix/operating_system_resolver.hpp +++ b/lib/inc/facts/posix/operating_system_resolver.hpp @@ -1,5 +1,5 @@ -#ifndef __POSIX_OPERATING_SYSTEM_RESOLVER_HPP__ -#define __POSIX_OPERATING_SYSTEM_RESOLVER_HPP__ +#ifndef LIB_INC_FACTS_POSIX_OPERATING_SYSTEM_RESOLVER_HPP_ +#define LIB_INC_FACTS_POSIX_OPERATING_SYSTEM_RESOLVER_HPP_ #include "../fact_resolver.hpp" @@ -23,7 +23,7 @@ namespace cfacter { namespace facts { namespace posix { { } - protected: + protected: /** * Called to resolve all facts the resolver is responsible for. * @param facts The fact map that is resolving facts. @@ -36,7 +36,7 @@ namespace cfacter { namespace facts { namespace posix { virtual void resolve_operating_system(fact_map& facts); }; -}}} // namespace cfacter::facts::posix +}}} // namespace cfacter::facts::posix -#endif +#endif // LIB_INC_FACTS_POSIX_OPERATING_SYSTEM_RESOLVER_HPP_ diff --git a/lib/inc/facts/string_value.hpp b/lib/inc/facts/string_value.hpp index 7b2b4dd1f9..fc7692264e 100644 --- a/lib/inc/facts/string_value.hpp +++ b/lib/inc/facts/string_value.hpp @@ -1,5 +1,5 @@ -#ifndef __STRING_VALUE_HPP__ -#define __STRING_VALUE_HPP__ +#ifndef LIB_INC_FACTS_STRING_VALUE_HPP_ +#define LIB_INC_FACTS_STRING_VALUE_HPP_ #include "value.hpp" @@ -48,11 +48,11 @@ namespace cfacter { namespace facts { */ std::string const& value() const { return _value; } - private: + private: std::string _value; }; -} } // namespace cfacter::facts +}} // namespace cfacter::facts -#endif +#endif // LIB_INC_FACTS_STRING_VALUE_HPP_ diff --git a/lib/inc/facts/value.hpp b/lib/inc/facts/value.hpp index e9934893a9..fe746fc0f3 100644 --- a/lib/inc/facts/value.hpp +++ b/lib/inc/facts/value.hpp @@ -1,5 +1,5 @@ -#ifndef __VALUE_HPP__ -#define __VALUE_HPP__ +#ifndef LIB_INC_FACTS_VALUE_HPP_ +#define LIB_INC_FACTS_VALUE_HPP_ #include #include @@ -40,7 +40,7 @@ namespace cfacter { namespace facts { return std::unique_ptr(new T(std::forward(args)...)); } -} } // namespace cfacter::facts +}} // namespace cfacter::facts -#endif +#endif // LIB_INC_FACTS_VALUE_HPP_ diff --git a/lib/inc/util/file.hpp b/lib/inc/util/file.hpp index acb3c1f85e..d9ef3a77f4 100644 --- a/lib/inc/util/file.hpp +++ b/lib/inc/util/file.hpp @@ -1,5 +1,5 @@ -#ifndef __FILE_HPP__ -#define __FILE_HPP__ +#ifndef LIB_INC_UTIL_FILE_HPP_ +#define LIB_INC_UTIL_FILE_HPP_ #include @@ -25,6 +25,6 @@ namespace cfacter { namespace util { static std::string read(std::string const& path); }; -}} // namespace cfacter::util +}} // namespace cfacter::util -#endif +#endif // LIB_INC_UTIL_FILE_HPP_ diff --git a/lib/inc/util/option_set.hpp b/lib/inc/util/option_set.hpp index abc286e347..15f88d4369 100644 --- a/lib/inc/util/option_set.hpp +++ b/lib/inc/util/option_set.hpp @@ -1,5 +1,5 @@ -#ifndef __OPTION_SET_HPP__ -#define __OPTION_SET_HPP__ +#ifndef LIB_INC_UTIL_OPTION_SET_HPP_ +#define LIB_INC_UTIL_OPTION_SET_HPP_ #include #include @@ -44,8 +44,7 @@ namespace cfacter { namespace util { value_type(0), [](value_type acc, enum_type value) { return acc | static_cast(value); - } - ); + }); } /** @@ -148,7 +147,7 @@ namespace cfacter { namespace util { size_t total = 0; for (; bits != 0; ++total) { - bits &= bits - 1; // clear the least significant bit set + bits &= bits - 1; // clear the least significant bit set } return total; } @@ -182,7 +181,7 @@ namespace cfacter { namespace util { return _value == 0; } - private: + private: value_type _value; }; @@ -223,6 +222,6 @@ namespace cfacter { namespace util { return option_set(option_set::value_type(lhs) ^ option_set::value_type(rhs)); } -} } // namespace cfacter::util -#endif +}} // namespace cfacter::util +#endif // LIB_INC_UTIL_OPTION_SET_HPP_ diff --git a/lib/inc/util/posix/scoped_descriptor.hpp b/lib/inc/util/posix/scoped_descriptor.hpp index 749875f5f4..1240b1ecb7 100644 --- a/lib/inc/util/posix/scoped_descriptor.hpp +++ b/lib/inc/util/posix/scoped_descriptor.hpp @@ -1,5 +1,5 @@ -#ifndef __POSIX_SCOPED_DESCRIPTOR_HPP__ -#define __POSIX_SCOPED_DESCRIPTOR_HPP__ +#ifndef LIB_INC_UTIL_POSIX_SCOPED_DESCRIPTOR_HPP_ +#define LIB_INC_UTIL_POSIX_SCOPED_DESCRIPTOR_HPP_ #include "../scoped_resource.hpp" #include @@ -15,12 +15,12 @@ namespace cfacter { namespace util { namespace posix { * Constructs a scoped_descriptor. * @param descriptor The file descriptor to close when destroyed. */ - scoped_descriptor(int descriptor) : + explicit scoped_descriptor(int descriptor) : scoped_resource(std::move(descriptor), close) { } - private: + private: static void close(int descriptor) { if (descriptor >= 0) { @@ -29,6 +29,6 @@ namespace cfacter { namespace util { namespace posix { } }; -} } } // namespace cfacter::util::posix +}}} // namespace cfacter::util::posix -#endif +#endif // LIB_INC_UTIL_POSIX_SCOPED_DESCRIPTOR_HPP_ diff --git a/lib/inc/util/scoped_resource.hpp b/lib/inc/util/scoped_resource.hpp index 03912f9e3d..80d96cd61c 100644 --- a/lib/inc/util/scoped_resource.hpp +++ b/lib/inc/util/scoped_resource.hpp @@ -1,5 +1,5 @@ -#ifndef __SCOPED_RESOURCE_HPP__ -#define __SCOPED_RESOURCE_HPP__ +#ifndef LIB_INC_UTIL_SCOPED_RESOURCE_HPP_ +#define LIB_INC_UTIL_SCOPED_RESOURCE_HPP_ #include @@ -25,7 +25,7 @@ namespace cfacter { namespace util { } // Force non-copyable - scoped_resource(scoped_resource const&) = delete; + explicit scoped_resource(scoped_resource const&) = delete; scoped_resource& operator=(scoped_resource const&) = delete; // Allow moving @@ -69,11 +69,11 @@ namespace cfacter { namespace util { } } - private: + private: T _resource; std::function _deleter; }; -} } // namespace cfacter::util +}} // namespace cfacter::util -#endif +#endif // LIB_INC_UTIL_SCOPED_RESOURCE_HPP_ diff --git a/lib/inc/util/string.hpp b/lib/inc/util/string.hpp index 3ead4cb19e..665f2eaad7 100644 --- a/lib/inc/util/string.hpp +++ b/lib/inc/util/string.hpp @@ -1,5 +1,5 @@ -#ifndef __STRING_HPP__ -#define __STRING_HPP__ +#ifndef LIB_INC_UTIL_STRING_HPP_ +#define LIB_INC_UTIL_STRING_HPP_ #include #include @@ -139,7 +139,6 @@ namespace cfacter { namespace util { */ typedef std::basic_string ci_string; -}} // namespace cfacter::util - -#endif /* STRING_HPP */ +}} // namespace cfacter::util +#endif // LIB_INC_UTIL_STRING_HPP_ diff --git a/lib/src/execution/posix/execution.cc b/lib/src/execution/posix/execution.cc index 2d7f2b7da5..21a2de5f14 100644 --- a/lib/src/execution/posix/execution.cc +++ b/lib/src/execution/posix/execution.cc @@ -128,8 +128,7 @@ namespace cfacter { namespace execution { if (dup2(stdout_write, STDERR_FILENO) == -1) { throw execution_exception("failed to redirect child stderr."); } - } - else { + } else { // Redirect to null scoped_descriptor dev_null(open("/dev/null", O_RDONLY)); if (dev_null < 0 || dup2(dev_null, STDERR_FILENO) == -1) { @@ -172,4 +171,4 @@ namespace cfacter { namespace execution { // CHILD DOES NOT RETURN } -} } // namespace cfacter::executions +}} // namespace cfacter::executions diff --git a/lib/src/facts/array_value.cc b/lib/src/facts/array_value.cc index a50981bdee..b33b668d44 100644 --- a/lib/src/facts/array_value.cc +++ b/lib/src/facts/array_value.cc @@ -15,8 +15,7 @@ namespace cfacter { namespace facts { for (auto const& element : _elements) { if (!first) { result << ", "; - } - else { + } else { first = true; } result << element->to_string(); @@ -25,4 +24,4 @@ namespace cfacter { namespace facts { return result.str(); } -} } // namespace cfacter::facts +}} // namespace cfacter::facts diff --git a/lib/src/facts/fact.cc b/lib/src/facts/fact.cc index f246963819..b39fc1a456 100644 --- a/lib/src/facts/fact.cc +++ b/lib/src/facts/fact.cc @@ -12,4 +12,4 @@ namespace cfacter { namespace facts { facts.add_fact(fact("cfacterversion", make_value(CFACTER_VERSION))); } -}} // namespace cfacter::facts +}} // namespace cfacter::facts diff --git a/lib/src/facts/fact_map.cc b/lib/src/facts/fact_map.cc index b7bc4811a7..94671ac232 100644 --- a/lib/src/facts/fact_map.cc +++ b/lib/src/facts/fact_map.cc @@ -104,4 +104,4 @@ namespace cfacter { namespace facts { } } -}} // namespace cfacter::facts +}} // namespace cfacter::facts diff --git a/lib/src/facts/fact_resolver.cc b/lib/src/facts/fact_resolver.cc index 37f4e57f22..67a378cbea 100644 --- a/lib/src/facts/fact_resolver.cc +++ b/lib/src/facts/fact_resolver.cc @@ -14,4 +14,4 @@ namespace cfacter { namespace facts { return resolve_facts(facts); } -}} // namespace cfacter::facts +}} // namespace cfacter::facts diff --git a/lib/src/facts/linux/fact.cc b/lib/src/facts/linux/fact.cc index 95f643c5ed..0de02b13e0 100644 --- a/lib/src/facts/linux/fact.cc +++ b/lib/src/facts/linux/fact.cc @@ -15,4 +15,4 @@ namespace cfacter { namespace facts { facts.add_resolver(); } -} } // namespace cfacter::facts \ No newline at end of file +}} // namespace cfacter::facts diff --git a/lib/src/facts/linux/lsb_resolver.cc b/lib/src/facts/linux/lsb_resolver.cc index 32fb7fbc78..6486b6ab04 100644 --- a/lib/src/facts/linux/lsb_resolver.cc +++ b/lib/src/facts/linux/lsb_resolver.cc @@ -23,4 +23,4 @@ namespace cfacter { namespace facts { namespace linux { facts.add_fact(fact(lsb_dist_id_name, make_value(std::move(value)))); } -}}} // namespace cfacter::facts::linux +}}} // namespace cfacter::facts::linux diff --git a/lib/src/facts/linux/operating_system_resolver.cc b/lib/src/facts/linux/operating_system_resolver.cc index 73a6589786..20e7a30efd 100644 --- a/lib/src/facts/linux/operating_system_resolver.cc +++ b/lib/src/facts/linux/operating_system_resolver.cc @@ -171,4 +171,4 @@ namespace cfacter { namespace facts { namespace linux { return string(); } -}}} // namespace cfacter::facts::linux +}}} // namespace cfacter::facts::linux diff --git a/lib/src/facts/osx/fact.cc b/lib/src/facts/osx/fact.cc index 4aafe5d127..b1a08b2b03 100644 --- a/lib/src/facts/osx/fact.cc +++ b/lib/src/facts/osx/fact.cc @@ -13,4 +13,4 @@ namespace cfacter { namespace facts { facts.add_resolver(); } -} } // namespace cfacter::facts \ No newline at end of file +}} // namespace cfacter::facts diff --git a/lib/src/facts/posix/kernel_resolver.cc b/lib/src/facts/posix/kernel_resolver.cc index 7e14ffefd8..b3b0751c04 100644 --- a/lib/src/facts/posix/kernel_resolver.cc +++ b/lib/src/facts/posix/kernel_resolver.cc @@ -71,4 +71,4 @@ namespace cfacter { namespace facts { namespace posix { facts.add_fact(fact(kernel_maj_release_name, make_value(std::move(value)))); } -}}} // namespace cfacter::facts::posix +}}} // namespace cfacter::facts::posix diff --git a/lib/src/facts/posix/operating_system_resolver.cc b/lib/src/facts/posix/operating_system_resolver.cc index 7702019416..27593fa7f8 100644 --- a/lib/src/facts/posix/operating_system_resolver.cc +++ b/lib/src/facts/posix/operating_system_resolver.cc @@ -24,4 +24,4 @@ namespace cfacter { namespace facts { namespace posix { facts.add_fact(fact(operating_system_name, make_value(kernel->value()))); } -}}} // namespace cfacter::facts::posix +}}} // namespace cfacter::facts::posix diff --git a/lib/src/facts/string_value.cc b/lib/src/facts/string_value.cc index db72d0c6a1..f3ade68c63 100644 --- a/lib/src/facts/string_value.cc +++ b/lib/src/facts/string_value.cc @@ -9,4 +9,4 @@ namespace cfacter { namespace facts { return "\"" + _value + "\""; } -} } // namespace cfacter::facts \ No newline at end of file +}} // namespace cfacter::facts diff --git a/lib/src/util/posix/file.cc b/lib/src/util/posix/file.cc index be34ceff6c..7d808bb57e 100644 --- a/lib/src/util/posix/file.cc +++ b/lib/src/util/posix/file.cc @@ -25,4 +25,4 @@ namespace cfacter { namespace util { return contents.str(); } -}} // namespace cfacter::util \ No newline at end of file +}} // namespace cfacter::util diff --git a/lib/src/util/string.cc b/lib/src/util/string.cc index 16a1934e37..73ea4c8c02 100644 --- a/lib/src/util/string.cc +++ b/lib/src/util/string.cc @@ -99,4 +99,4 @@ namespace cfacter { namespace util { return to_upper(str); } -}} // namespace cfacter::util +}} // namespace cfacter::util From d074ba3983c6e5c390894f086962f1689a225c7d Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Wed, 9 Apr 2014 20:02:21 -0700 Subject: [PATCH 1769/3753] (CFACT-3) Remove unnecessary fact type. The fact type as just functioning as a key-value pair after the last refactoring. It was therefore redundant given that the fact_map already maps a name to a value. --- lib/CMakeLists.txt | 6 +- lib/inc/facts/fact.hpp | 59 ------------- lib/inc/facts/fact_map.hpp | 38 +++++---- lib/inc/facts/fact_resolver.hpp | 1 - lib/src/facts/fact.cc | 5 +- lib/src/facts/fact_map.cc | 83 +++++++++---------- lib/src/facts/linux/lsb_resolver.cc | 2 +- .../facts/linux/operating_system_resolver.cc | 4 +- lib/src/facts/linux/{fact.cc => platform.cc} | 3 +- lib/src/facts/osx/{fact.cc => platform.cc} | 3 +- lib/src/facts/posix/kernel_resolver.cc | 12 +-- .../facts/posix/operating_system_resolver.cc | 4 +- 12 files changed, 80 insertions(+), 140 deletions(-) delete mode 100644 lib/inc/facts/fact.hpp rename lib/src/facts/linux/{fact.cc => platform.cc} (84%) rename lib/src/facts/osx/{fact.cc => platform.cc} (81%) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index d4b1ba0a13..b79c392e8e 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -35,13 +35,13 @@ endif() # Set the platform-specific sources if ("${CMAKE_SYSTEM_NAME}" MATCHES "Darwin") set(LIBCFACTER_PLATFORM_SOURCES - "${CMAKE_CURRENT_LIST_DIR}/src/facts/osx/fact.cc" + "${CMAKE_CURRENT_LIST_DIR}/src/facts/osx/platform.cc" ) elseif ("${CMAKE_SYSTEM_NAME}" MATCHES "Linux") set(LIBCFACTER_PLATFORM_SOURCES - "${CMAKE_CURRENT_LIST_DIR}/src/facts/linux/fact.cc" - "${CMAKE_CURRENT_LIST_DIR}/src/facts/linux/operating_system_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/linux/lsb_resolver.cc" + "${CMAKE_CURRENT_LIST_DIR}/src/facts/linux/operating_system_resolver.cc" + "${CMAKE_CURRENT_LIST_DIR}/src/facts/linux/platform.cc" ) endif() diff --git a/lib/inc/facts/fact.hpp b/lib/inc/facts/fact.hpp deleted file mode 100644 index 8e06c652ac..0000000000 --- a/lib/inc/facts/fact.hpp +++ /dev/null @@ -1,59 +0,0 @@ -#ifndef LIB_INC_FACTS_FACT_HPP_ -#define LIB_INC_FACTS_FACT_HPP_ - -#include "value.hpp" -#include -#include -#include - -namespace cfacter { namespace facts { - - /** - * Represents a fact. - */ - struct fact - { - /** - * Constructs a fact with the given name. - */ - fact(std::string&& name, std::unique_ptr && value) : - _name(std::move(name)), - _value(std::move(value)) - { - } - - /** - * Gets the name of the fact. - * @return Returns the name of the fact. - */ - std::string const& name() const { return _name; } - - /** - * Gets the fact's value - * @return Returns the fact's value. - */ - value const* val() const { return _value.get(); } - - // Force non-copyable - fact(fact const&) = delete; - fact& operator=(fact const&) = delete; - - // Allow movable - fact(fact&&) = default; - fact& operator=(fact&&) = default; - - private: - std::string _name; - std::unique_ptr _value; - }; - - /** - * Called to populate all facts. - */ - void populate_common_facts(); - void populate_platform_facts(); - -}} // namespace cfacter:facts - -#endif // LIB_INC_FACTS_FACT_HPP_ - diff --git a/lib/inc/facts/fact_map.hpp b/lib/inc/facts/fact_map.hpp index 3a5113ac01..f12e19f530 100644 --- a/lib/inc/facts/fact_map.hpp +++ b/lib/inc/facts/fact_map.hpp @@ -7,11 +7,24 @@ #include #include #include "value.hpp" -#include "fact.hpp" #include "fact_resolver.hpp" namespace cfacter { namespace facts { + struct fact_map; + + /** + * Called to populate common facts. + * @param facts The fact map being populated. + */ + void populate_common_facts(fact_map& facts); + + /** + * Called to populate platform-specific facts. + * @param facts The fact map being populated. + */ + void populate_platform_facts(fact_map& facts); + /** * Thrown when a fact already exists in the map. */ @@ -41,7 +54,7 @@ namespace cfacter { namespace facts { */ struct fact_map { - typedef std::map fact_map_type; + typedef std::map> fact_map_type; typedef std::map> resolver_map_type; /** @@ -64,9 +77,10 @@ namespace cfacter { namespace facts { /** * Adds a fact to the map. - * @param f The fact to add to the map. + * @param name The name of the fact. + * @param value The value of the fact. */ - void add_fact(fact&& f); + void add(std::string&& name, std::unique_ptr&& value); /** * Removes a fact by name. @@ -85,26 +99,15 @@ namespace cfacter { namespace facts { */ bool empty() const; - /** - * Gets a fact by name. - * @param name The name of the fact to get. - * @return Returns a pointer to the fact or nullptr if the fact is not in the map. - */ - fact const* get_fact(std::string const& name); - /** * Gets a fact value by name. * @param name The name of the fact to get the value of. * @return Returns a pointer to the fact value or nullptr if the fact is not in the map or the value is not the expected type. */ template - T const* get_value(std::string const& name) + T const* get(std::string const& name) { - auto f = get_fact(name); - if (!f) { - return nullptr; - } - return dynamic_cast(f->val()); + return dynamic_cast(get_value(name)); } /** @@ -123,6 +126,7 @@ namespace cfacter { namespace facts { fact_map() {} void load(); void resolve_facts(); + value const* get_value(std::string const& name); static fact_map _instance; diff --git a/lib/inc/facts/fact_resolver.hpp b/lib/inc/facts/fact_resolver.hpp index 78f23d04fc..a030b30e61 100644 --- a/lib/inc/facts/fact_resolver.hpp +++ b/lib/inc/facts/fact_resolver.hpp @@ -5,7 +5,6 @@ #include #include #include -#include "fact.hpp" namespace cfacter { namespace facts { diff --git a/lib/src/facts/fact.cc b/lib/src/facts/fact.cc index b39fc1a456..0681ccf997 100644 --- a/lib/src/facts/fact.cc +++ b/lib/src/facts/fact.cc @@ -6,10 +6,9 @@ using namespace std; namespace cfacter { namespace facts { - void populate_common_facts() + void populate_common_facts(fact_map& facts) { - auto& facts = fact_map::instance(); - facts.add_fact(fact("cfacterversion", make_value(CFACTER_VERSION))); + facts.add("cfacterversion", make_value(CFACTER_VERSION)); } }} // namespace cfacter::facts diff --git a/lib/src/facts/fact_map.cc b/lib/src/facts/fact_map.cc index 94671ac232..9b8fbb0c92 100644 --- a/lib/src/facts/fact_map.cc +++ b/lib/src/facts/fact_map.cc @@ -7,17 +7,17 @@ namespace cfacter { namespace facts { fact_map fact_map::_instance; - void fact_map::add_fact(fact&& f) + void fact_map::add(std::string&& name, std::unique_ptr&& value) { // Search for the fact first - auto const& it = _facts.lower_bound(f.name()); - if (it != _facts.end() && !(_facts.key_comp()(f.name(), it->first))) { - throw fact_exists_exception("fact " + f.name() + " already exists."); + auto const& it = _facts.lower_bound(name); + if (it != _facts.end() && !(_facts.key_comp()(name, it->first))) { + throw fact_exists_exception("fact " + name + " already exists."); } // Remove any resolver for this fact and add the fact - _resolvers.erase(f.name()); - _facts.insert(it, std::make_pair(f.name(), std::move(f))); + _resolvers.erase(name); + _facts.insert(it, std::make_pair(name, std::move(value))); } void fact_map::remove(string const& name) @@ -36,45 +36,13 @@ namespace cfacter { namespace facts { return _facts.empty() && _resolvers.empty(); } - fact const* fact_map::get_fact(std::string const& name) - { - load(); - - auto it = _facts.find(name); - if (it == _facts.end()) { - // Check the resolvers - auto const& resolver_it = _resolvers.find(name); - if (resolver_it == _resolvers.end()) { - // Fact not found - return nullptr; - } - auto resolver = resolver_it->second; - - // Resolve the facts - resolver->resolve(*this); - - // Remove any associated facts that didn't resolve - for (auto const& name : resolver->names()) { - _resolvers.erase(name); - } - - it = _facts.find(name); - if (it == _facts.end()) { - // Fact not found after resolution - return nullptr; - } - } - return &it->second; - } - - void fact_map::each(std::function func) { load(); resolve_facts(); std::find_if(begin(_facts), end(_facts), [&func](fact_map_type::value_type const& it) { - return func(it.first, it.second.val()); + return func(it.first, it.second.get()); }); } @@ -88,8 +56,8 @@ namespace cfacter { namespace facts { if (!empty()) { return; } - populate_common_facts(); - populate_platform_facts(); + populate_common_facts(*this); + populate_platform_facts(*this); } void fact_map::resolve_facts() @@ -100,8 +68,39 @@ namespace cfacter { namespace facts { // Copy the string from the key as resolving the facts will // destruct the resolver entry in the map string name = _resolvers.begin()->first; - get_fact(name); + get_value(name); + } + } + + value const* fact_map::get_value(std::string const& name) + { + load(); + + auto it = _facts.find(name); + if (it == _facts.end()) { + // Check the resolvers + auto const& resolver_it = _resolvers.find(name); + if (resolver_it == _resolvers.end()) { + // Fact not found + return nullptr; + } + auto resolver = resolver_it->second; + + // Resolve the facts + resolver->resolve(*this); + + // Remove any associated facts that didn't resolve + for (auto const& name : resolver->names()) { + _resolvers.erase(name); + } + + it = _facts.find(name); + if (it == _facts.end()) { + // Fact not found after resolution + return nullptr; + } } + return it->second.get(); } }} // namespace cfacter::facts diff --git a/lib/src/facts/linux/lsb_resolver.cc b/lib/src/facts/linux/lsb_resolver.cc index 6486b6ab04..df631bd78a 100644 --- a/lib/src/facts/linux/lsb_resolver.cc +++ b/lib/src/facts/linux/lsb_resolver.cc @@ -20,7 +20,7 @@ namespace cfacter { namespace facts { namespace linux { if (value.empty()) { return; } - facts.add_fact(fact(lsb_dist_id_name, make_value(std::move(value)))); + facts.add(lsb_dist_id_name, make_value(std::move(value))); } }}} // namespace cfacter::facts::linux diff --git a/lib/src/facts/linux/operating_system_resolver.cc b/lib/src/facts/linux/operating_system_resolver.cc index 20e7a30efd..f387488103 100644 --- a/lib/src/facts/linux/operating_system_resolver.cc +++ b/lib/src/facts/linux/operating_system_resolver.cc @@ -14,7 +14,7 @@ namespace cfacter { namespace facts { namespace linux { void operating_system_resolver::resolve_operating_system(fact_map& facts) { - auto dist_id = facts.get_value(lsb_resolver::lsb_dist_id_name); + auto dist_id = facts.get(lsb_resolver::lsb_dist_id_name); // Start by checking for Cumulus Linux string value = check_cumulus_linux(); @@ -51,7 +51,7 @@ namespace cfacter { namespace facts { namespace linux { } // Add the fact - facts.add_fact(fact(operating_system_name, make_value(value))); + facts.add(operating_system_name, make_value(value)); } string operating_system_resolver::check_cumulus_linux() diff --git a/lib/src/facts/linux/fact.cc b/lib/src/facts/linux/platform.cc similarity index 84% rename from lib/src/facts/linux/fact.cc rename to lib/src/facts/linux/platform.cc index 0de02b13e0..0a9fdb384e 100644 --- a/lib/src/facts/linux/fact.cc +++ b/lib/src/facts/linux/platform.cc @@ -7,9 +7,8 @@ using namespace std; namespace cfacter { namespace facts { - void populate_platform_facts() + void populate_platform_facts(fact_map& facts) { - auto& facts = fact_map::instance(); facts.add_resolver(); facts.add_resolver(); facts.add_resolver(); diff --git a/lib/src/facts/osx/fact.cc b/lib/src/facts/osx/platform.cc similarity index 81% rename from lib/src/facts/osx/fact.cc rename to lib/src/facts/osx/platform.cc index b1a08b2b03..05e1e61446 100644 --- a/lib/src/facts/osx/fact.cc +++ b/lib/src/facts/osx/platform.cc @@ -6,9 +6,8 @@ using namespace std; namespace cfacter { namespace facts { - void populate_platform_facts() + void populate_platform_facts(fact_map& facts) { - auto& facts = fact_map::instance(); facts.add_resolver(); facts.add_resolver(); } diff --git a/lib/src/facts/posix/kernel_resolver.cc b/lib/src/facts/posix/kernel_resolver.cc index b3b0751c04..7a60375911 100644 --- a/lib/src/facts/posix/kernel_resolver.cc +++ b/lib/src/facts/posix/kernel_resolver.cc @@ -25,7 +25,7 @@ namespace cfacter { namespace facts { namespace posix { if (value.empty()) { return; } - facts.add_fact(fact(kernel_name, make_value(std::move(value)))); + facts.add(kernel_name, make_value(std::move(value))); } void kernel_resolver::resolve_kernel_release(fact_map& facts) @@ -34,12 +34,12 @@ namespace cfacter { namespace facts { namespace posix { if (value.empty()) { return; } - facts.add_fact(fact(kernel_release_name, make_value(std::move(value)))); + facts.add(kernel_release_name, make_value(std::move(value))); } void kernel_resolver::resolve_kernel_version(fact_map& facts) { - auto version = facts.get_value(kernel_release_name); + auto version = facts.get(kernel_release_name); if (!version) { return; } @@ -49,12 +49,12 @@ namespace cfacter { namespace facts { namespace posix { if (pos != string::npos) { value = value.substr(0, pos); } - facts.add_fact(fact(kernel_version_name, make_value(std::move(value)))); + facts.add(kernel_version_name, make_value(std::move(value))); } void kernel_resolver::resolve_kernel_major_version(fact_map& facts) { - auto version = facts.get_value(kernel_release_name); + auto version = facts.get(kernel_release_name); if (!version) { return; } @@ -68,7 +68,7 @@ namespace cfacter { namespace facts { namespace posix { value = value.substr(0, pos); } } - facts.add_fact(fact(kernel_maj_release_name, make_value(std::move(value)))); + facts.add(kernel_maj_release_name, make_value(std::move(value))); } }}} // namespace cfacter::facts::posix diff --git a/lib/src/facts/posix/operating_system_resolver.cc b/lib/src/facts/posix/operating_system_resolver.cc index 27593fa7f8..42de55343e 100644 --- a/lib/src/facts/posix/operating_system_resolver.cc +++ b/lib/src/facts/posix/operating_system_resolver.cc @@ -16,12 +16,12 @@ namespace cfacter { namespace facts { namespace posix { void operating_system_resolver::resolve_operating_system(fact_map& facts) { // Default to the same value as the kernel - auto kernel = facts.get_value(kernel_resolver::kernel_name); + auto kernel = facts.get(kernel_resolver::kernel_name); if (!kernel) { return; } - facts.add_fact(fact(operating_system_name, make_value(kernel->value()))); + facts.add(operating_system_name, make_value(kernel->value())); } }}} // namespace cfacter::facts::posix From ac4b11e26085afa93268b97b737a5a6814ff7020 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Thu, 10 Apr 2014 13:59:24 -0700 Subject: [PATCH 1770/3753] (CFACT-3) Remove spawn of uname in favor of uname syscall. There is a POSIX uname function we can use rather than spawning the uname utility. Replace calls to execute with a single call to uname. --- lib/inc/facts/posix/kernel_resolver.hpp | 7 +++++-- lib/src/facts/posix/kernel_resolver.cc | 19 +++++++++++-------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/lib/inc/facts/posix/kernel_resolver.hpp b/lib/inc/facts/posix/kernel_resolver.hpp index c569da0ea7..48980517a2 100644 --- a/lib/inc/facts/posix/kernel_resolver.hpp +++ b/lib/inc/facts/posix/kernel_resolver.hpp @@ -2,6 +2,7 @@ #define LIB_INC_FACTS_POSIX_KERNEL_RESOLVER_HPP_ #include "../fact_resolver.hpp" +#include namespace cfacter { namespace facts { namespace posix { @@ -38,13 +39,15 @@ namespace cfacter { namespace facts { namespace posix { /** * Called to resolve the kernel fact. * @param facts The fact map that is resolving facts. + * @param name The result of the uname call. */ - virtual void resolve_kernel(fact_map& facts); + virtual void resolve_kernel(fact_map& facts, utsname const& name); /** * Called to resolve the kernel release fact. * @param facts The fact map that is resolving facts. + * @param name The result of the uname call. */ - virtual void resolve_kernel_release(fact_map& facts); + virtual void resolve_kernel_release(fact_map& facts, utsname const& name); /** * Called to resolve the kernel version fact. * @param facts The fact map that is resolving facts. diff --git a/lib/src/facts/posix/kernel_resolver.cc b/lib/src/facts/posix/kernel_resolver.cc index 7a60375911..79d2a830d0 100644 --- a/lib/src/facts/posix/kernel_resolver.cc +++ b/lib/src/facts/posix/kernel_resolver.cc @@ -1,36 +1,39 @@ #include #include #include -#include #include using namespace std; -using namespace cfacter::execution; using namespace cfacter::util; namespace cfacter { namespace facts { namespace posix { void kernel_resolver::resolve_facts(fact_map& facts) { + utsname name; + memset(&name, 0, sizeof(name)); + if (uname(&name) != 0) { + return; + } // Resolve all kernel-related facts - resolve_kernel(facts); - resolve_kernel_release(facts); + resolve_kernel(facts, name); + resolve_kernel_release(facts, name); resolve_kernel_version(facts); resolve_kernel_major_version(facts); } - void kernel_resolver::resolve_kernel(fact_map& facts) + void kernel_resolver::resolve_kernel(fact_map& facts, utsname const& name) { - string value = execute("uname", {"-s"}, { execution_options::trim_output }); + string value = name.sysname; if (value.empty()) { return; } facts.add(kernel_name, make_value(std::move(value))); } - void kernel_resolver::resolve_kernel_release(fact_map& facts) + void kernel_resolver::resolve_kernel_release(fact_map& facts, utsname const& name) { - string value = execute("uname", {"-r"}, { execution_options::trim_output }); + string value = name.release; if (value.empty()) { return; } From 2ce8849b75339f577f08375adabfdbf50d932c71 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Thu, 10 Apr 2014 15:34:29 -0700 Subject: [PATCH 1771/3753] (FACT-375) Fix json gem installation on solaris --- acceptance/setup/common/00_EnvSetup.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/acceptance/setup/common/00_EnvSetup.rb b/acceptance/setup/common/00_EnvSetup.rb index f75b55d303..be19c45ec6 100644 --- a/acceptance/setup/common/00_EnvSetup.rb +++ b/acceptance/setup/common/00_EnvSetup.rb @@ -21,6 +21,7 @@ :solaris => [ ['git', 'developer/versioning/git'], ['ruby', 'runtime/ruby-18'], + # there isn't a package for json, so it is installed later via gems ], :windows => [ 'git', @@ -30,7 +31,8 @@ install_packages_on(hosts, PACKAGES, :check_if_exists => true) hosts.each do |host| - if host['platform'] =~ /windows/ + case host['platform'] + when /windows/ step "#{host} Install ruby from git" install_from_git(host, "/opt/puppet-git-repos", :name => 'puppet-win32-ruby', :path => 'git://github.com/puppetlabs/puppet-win32-ruby') on host, 'cd /opt/puppet-git-repos/puppet-win32-ruby; cp -r ruby/* /' @@ -38,5 +40,8 @@ on host, 'cd /lib; icacls ruby /reset /T' on host, 'ruby --version' on host, 'cmd /c gem list' + when /solaris/ + step "#{host} Install json from rubygems" + on host, 'gem install json' end end From a6ac376af7bf41d832e62f6571c06caecc539643 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Mon, 14 Apr 2014 16:12:36 -0700 Subject: [PATCH 1772/3753] (CFACT-3) Code review feedback changes. Fixing comments made to GH-9. --- exe/cfacter.cc | 1 - gem/lib/cfacter.rb | 1 + lib/inc/cfacterlib.h | 5 +++-- lib/inc/facts/fact_map.hpp | 2 +- lib/inc/facts/value.hpp | 7 ++++++- lib/inc/util/option_set.hpp | 5 +++-- lib/inc/util/string.hpp | 8 ++------ lib/src/cfacterlib.cc | 7 ++++++- lib/src/facts/fact_map.cc | 2 +- lib/src/facts/string_value.cc | 2 +- lib/src/util/posix/file.cc | 11 +++++++---- lib/src/util/string.cc | 5 +++++ 12 files changed, 36 insertions(+), 20 deletions(-) diff --git a/exe/cfacter.cc b/exe/cfacter.cc index 1815ca4ca9..2c99aafb56 100644 --- a/exe/cfacter.cc +++ b/exe/cfacter.cc @@ -1,4 +1,3 @@ -#include #include "../version.h" #include #include diff --git a/gem/lib/cfacter.rb b/gem/lib/cfacter.rb index 3c7bced171..16e17d3756 100644 --- a/gem/lib/cfacter.rb +++ b/gem/lib/cfacter.rb @@ -15,6 +15,7 @@ class Constants public attach_function :clear, [], :void + attach_function :loadfacts, [], :void def self.to_hash ptr = FFI::MemoryPointer.new(:char, Constants::JSON_STRING_MAX_LEN) diff --git a/lib/inc/cfacterlib.h b/lib/inc/cfacterlib.h index 1fe01a8aad..5c7318b28b 100644 --- a/lib/inc/cfacterlib.h +++ b/lib/inc/cfacterlib.h @@ -5,8 +5,9 @@ extern "C" { void clear(); - int to_json(char *facts, size_t facts_len); - int get_value(const char *fact, char *value, size_t value_len); + void loadfacts(); + int to_json(char *facts, size_t facts_len); + int value(const char *fact, char *value, size_t value_len); void search_external(const char *dirs); } diff --git a/lib/inc/facts/fact_map.hpp b/lib/inc/facts/fact_map.hpp index f12e19f530..cd27e8bf43 100644 --- a/lib/inc/facts/fact_map.hpp +++ b/lib/inc/facts/fact_map.hpp @@ -43,7 +43,7 @@ namespace cfacter { namespace facts { struct resolver_exists_exception : std::runtime_error { /** - * Constructs a fact_exists_exception. + * Constructs a resolver_exists_exception. * @param message The exception message. */ explicit resolver_exists_exception(std::string const& message) : std::runtime_error(message) {} diff --git a/lib/inc/facts/value.hpp b/lib/inc/facts/value.hpp index fe746fc0f3..1b6c135572 100644 --- a/lib/inc/facts/value.hpp +++ b/lib/inc/facts/value.hpp @@ -12,6 +12,9 @@ namespace cfacter { namespace facts { */ struct value { + /** + * Constructs a value. + */ value() {} /** @@ -31,8 +34,10 @@ namespace cfacter { namespace facts { /** * Utility function for making a value. + * @tparam T The type of the value being constructed. + * @tparam Args The variadic types for the value's constructor. * @param args The arguments to the value's constructor. - * @return Returns a unique pointer to the value. + * @return Returns a unique pointer to the constructed value. */ template std::unique_ptr make_value(Args&& ...args) diff --git a/lib/inc/util/option_set.hpp b/lib/inc/util/option_set.hpp index 15f88d4369..eb0b176618 100644 --- a/lib/inc/util/option_set.hpp +++ b/lib/inc/util/option_set.hpp @@ -10,7 +10,7 @@ namespace cfacter { namespace util { /** * Represents a set of options (flags). * Adapted from http://stackoverflow.com/a/4226975/530189 - * @tparam T The enum class type that makes up the avilable options. + * @tparam T The enum class type that makes up the available options. */ template struct option_set @@ -19,6 +19,7 @@ namespace cfacter { namespace util { * The underlying enum type for the option set. */ typedef T enum_type; + /** * The value type for the enum type. */ @@ -169,7 +170,7 @@ namespace cfacter { namespace util { */ bool test(enum_type option) const { - return (_value & static_cast(option)) > 0; + return _value & static_cast(option); } /** diff --git a/lib/inc/util/string.hpp b/lib/inc/util/string.hpp index 665f2eaad7..0b18df07db 100644 --- a/lib/inc/util/string.hpp +++ b/lib/inc/util/string.hpp @@ -119,15 +119,11 @@ namespace cfacter { namespace util { return ::toupper(c1) < ::toupper(c2); } - static int compare(char const* s1, char const* s2, size_t n) - { - // TODO: this isn't a standard function; we'll need an #ifdef to support non-POSIX platforms - return strncasecmp(s1, s2, n); - } + static int compare(char const* s1, char const* s2, size_t n); static char const* find(char const* s, int n, char a) { - while (n-- > 0 && toupper(*s) != toupper(a)) { + while (n-- > 0 && ::toupper(*s) != ::toupper(a)) { ++s; } return s; diff --git a/lib/src/cfacterlib.cc b/lib/src/cfacterlib.cc index 68df72bcb4..24c4ddccc7 100644 --- a/lib/src/cfacterlib.cc +++ b/lib/src/cfacterlib.cc @@ -9,6 +9,11 @@ using namespace std; using namespace cfacter::facts; +void loadfacts() +{ + // This is a no-op of the fact map +} + int to_json(char *facts_json, size_t facts_len) { // TODO: re-implement this with support for structured facts @@ -16,7 +21,7 @@ int to_json(char *facts_json, size_t facts_len) return 0; } -int get_value(const char *fact, char *value, size_t value_len) +int value(const char *fact, char *value, size_t value_len) { // TODO: reimplement this with support for structured facts strncpy(value, "", value_len); diff --git a/lib/src/facts/fact_map.cc b/lib/src/facts/fact_map.cc index 9b8fbb0c92..e1fd6f60f0 100644 --- a/lib/src/facts/fact_map.cc +++ b/lib/src/facts/fact_map.cc @@ -17,7 +17,7 @@ namespace cfacter { namespace facts { // Remove any resolver for this fact and add the fact _resolvers.erase(name); - _facts.insert(it, std::make_pair(name, std::move(value))); + _facts.insert(it, std::make_pair(std::move(name), std::move(value))); } void fact_map::remove(string const& name) diff --git a/lib/src/facts/string_value.cc b/lib/src/facts/string_value.cc index f3ade68c63..0d07ad6be4 100644 --- a/lib/src/facts/string_value.cc +++ b/lib/src/facts/string_value.cc @@ -6,7 +6,7 @@ namespace cfacter { namespace facts { string string_value::to_string() const { - return "\"" + _value + "\""; + return _value; } }} // namespace cfacter::facts diff --git a/lib/src/util/posix/file.cc b/lib/src/util/posix/file.cc index 7d808bb57e..1dcca80bcb 100644 --- a/lib/src/util/posix/file.cc +++ b/lib/src/util/posix/file.cc @@ -10,16 +10,19 @@ namespace cfacter { namespace util { bool file::exists(string const& path) { struct stat buffer; - return stat(path.c_str(), &buffer) == 0; + if (stat(path.c_str(), &buffer) != 0) { + return false; + } + + return S_ISREG(buffer.st_mode); } - // TODO: this is standard-compliant, so it can be shared between POSIX and Windows + // TODO: this is standard-compliant, so it should shared between POSIX and Windows string file::read(string const& path) { ifstream in(path, std::ios::in | std::ios::binary); ostringstream contents; - if (in) - { + if (in) { contents << in.rdbuf(); } return contents.str(); diff --git a/lib/src/util/string.cc b/lib/src/util/string.cc index 73ea4c8c02..5eb870676b 100644 --- a/lib/src/util/string.cc +++ b/lib/src/util/string.cc @@ -99,4 +99,9 @@ namespace cfacter { namespace util { return to_upper(str); } + int ci_char_traits::compare(char const* s1, char const* s2, size_t n) + { + return strncasecmp(s1, s2, n); + } + }} // namespace cfacter::util From 82ab289495dbd6dcf2f764458f042e5855cf3d6b Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Thu, 10 Apr 2014 13:18:04 -0700 Subject: [PATCH 1773/3753] (CFACT-3) Implement LSB facts. Implementing the remaining LSB facts: - lsbdistrelease - lsbdistcodename - lsbdistdescription - lsbmajdistrelease - lsbminordistrelease - lsbrelease --- lib/inc/facts/linux/lsb_resolver.hpp | 38 +++++++++++++++++ lib/inc/util/string.hpp | 46 ++++++++++++-------- lib/src/facts/linux/lsb_resolver.cc | 64 ++++++++++++++++++++++++++++ lib/src/util/string.cc | 31 ++++++++------ 4 files changed, 148 insertions(+), 31 deletions(-) diff --git a/lib/inc/facts/linux/lsb_resolver.hpp b/lib/inc/facts/linux/lsb_resolver.hpp index 3d227730db..c6637699e7 100644 --- a/lib/inc/facts/linux/lsb_resolver.hpp +++ b/lib/inc/facts/linux/lsb_resolver.hpp @@ -12,6 +12,13 @@ namespace cfacter { namespace facts { namespace linux { { // Constants for responsible facts constexpr static char const* lsb_dist_id_name = "lsbdistid"; + constexpr static char const* lsb_dist_release_name = "lsbdistrelease"; + constexpr static char const* lsb_dist_codename_name = "lsbdistcodename"; + constexpr static char const* lsb_dist_description_name = "lsbdistdescription"; + constexpr static char const* lsb_dist_maj_release_name = "lsbmajdistrelease"; + constexpr static char const* lsb_dist_minor_release_name = "lsbminordistrelease"; + constexpr static char const* lsb_release_name = "lsbrelease"; + /** * Constructs the lsb_resolver. @@ -19,6 +26,12 @@ namespace cfacter { namespace facts { namespace linux { lsb_resolver() : fact_resolver({ lsb_dist_id_name, + lsb_dist_release_name, + lsb_dist_codename_name, + lsb_dist_description_name, + lsb_dist_maj_release_name, + lsb_dist_minor_release_name, + lsb_release_name, }) { } @@ -35,6 +48,31 @@ namespace cfacter { namespace facts { namespace linux { * @param facts The fact map that is resolving facts. */ virtual void resolve_dist_id(fact_map& facts); + /** + * Called to resolve the LSB dist release fact. + * @param facts The fact map that is resolving facts. + */ + virtual void resolve_dist_release(fact_map& facts); + /** + * Called to resolve the LSB dist codename fact. + * @param facts The fact map that is resolving facts. + */ + virtual void resolve_dist_codename(fact_map& facts); + /** + * Called to resolve the LSB dist description fact. + * @param facts The fact map that is resolving facts. + */ + virtual void resolve_dist_description(fact_map& facts); + /** + * Called to resolve the LSB dist major and minor release fact. + * @param facts The fact map that is resolving facts. + */ + virtual void resolve_dist_version(fact_map& facts); + /** + * Called to resolve the LSB release fact. + * @param facts The fact map that is resolving facts. + */ + virtual void resolve_release(fact_map& facts); }; }}} // namespace cfacter::facts::linux diff --git a/lib/inc/util/string.hpp b/lib/inc/util/string.hpp index 0b18df07db..ca24adff39 100644 --- a/lib/inc/util/string.hpp +++ b/lib/inc/util/string.hpp @@ -3,50 +3,60 @@ #include #include +#include +#include // TODO: non-standard header #include "string.h" namespace cfacter { namespace util { + extern std::initializer_list default_trim_set; + /** - * In-place trims whitespace from the start (left side) of a string. - * @param str The string to trim whitespace from. + * In-place trims characters from the start (left side) of a string. + * @param str The string to trim characters from. + * @param set The set of characters to trim. Defaults to whitespace characters. * @return Returns the given string. */ - std::string& ltrim(std::string& str); + std::string& ltrim(std::string& str, std::initializer_list const& set = default_trim_set); /** - * In-place trims whitespace from the start (left side) of a string. - * @param str The string to trim whitespace from. + * In-place trims characters from the start (left side) of a string. + * @param str The string to trim characters from. + * @param set The set of characters to trim. Defaults to whitespace characters. * @return Returns the given string. */ - std::string& ltrim(std::string&& str); + std::string& ltrim(std::string&& str, std::initializer_list const& set = default_trim_set); /** - * In-place trims whitespace from the end (right side) of a string. - * @param str The string to trim whitespace from. + * In-place trims characters from the end (right side) of a string. + * @param str The string to trim characters from. + * @param set The set of characters to trim. Defaults to whitespace characters. * @return Returns the given string. */ - std::string& rtrim(std::string& str); + std::string& rtrim(std::string& str, std::initializer_list const& set = default_trim_set); /** - * In-place trims whitespace from the end (right side) of a string. - * @param str The string to trim whitespace from. + * In-place trims characters from the end (right side) of a string. + * @param str The string to trim characters from. + * @param set The set of characters to trim. Defaults to whitespace characters. * @return Returns the given string. */ - std::string& rtrim(std::string&& str); + std::string& rtrim(std::string&& str, std::initializer_list const& set = default_trim_set); /** - * In-place trims whitespace from the the start and end (both sides) of a string. - * @param str The string to trim whitespace from. + * In-place trims characters from the the start and end (both sides) of a string. + * @param str The string to trim characters from. + * @param set The set of characters to trim. Defaults to whitespace characters. * @return Returns the given string. */ - std::string& trim(std::string& str); + std::string& trim(std::string& str, std::initializer_list const& set = default_trim_set); /** - * In-place trims whitespace from the the start and end (both sides) of a string. - * @param str The string to trim whitespace from. + * In-place trims characters from the the start and end (both sides) of a string. + * @param str The string to trim characters from. + * @param set The set of characters to trim. Defaults to whitespace characters. * @return Returns the given string. */ - std::string& trim(std::string&& str); + std::string& trim(std::string&& str, std::initializer_list const& set = default_trim_set); /** * Tokenizes the given string. diff --git a/lib/src/facts/linux/lsb_resolver.cc b/lib/src/facts/linux/lsb_resolver.cc index df631bd78a..acf4e67186 100644 --- a/lib/src/facts/linux/lsb_resolver.cc +++ b/lib/src/facts/linux/lsb_resolver.cc @@ -2,8 +2,11 @@ #include #include #include +#include +#include using namespace std; +using namespace cfacter::util; using namespace cfacter::execution; namespace cfacter { namespace facts { namespace linux { @@ -12,6 +15,11 @@ namespace cfacter { namespace facts { namespace linux { { // Resolve all lsb-related facts resolve_dist_id(facts); + resolve_dist_release(facts); + resolve_dist_codename(facts); + resolve_dist_description(facts); + resolve_dist_version(facts); + resolve_release(facts); } void lsb_resolver::resolve_dist_id(fact_map& facts) @@ -23,4 +31,60 @@ namespace cfacter { namespace facts { namespace linux { facts.add(lsb_dist_id_name, make_value(std::move(value))); } + void lsb_resolver::resolve_dist_release(fact_map& facts) + { + string value = execute("lsb_release", {"-r", "-s"}, { execution_options::trim_output }); + if (value.empty()) { + return; + } + facts.add(lsb_dist_release_name, make_value(std::move(value))); + } + + void lsb_resolver::resolve_dist_codename(fact_map& facts) + { + string value = execute("lsb_release", {"-c", "-s"}, { execution_options::trim_output }); + if (value.empty()) { + return; + } + facts.add(lsb_dist_codename_name, make_value(std::move(value))); + } + + void lsb_resolver::resolve_dist_description(fact_map& facts) + { + string value = execute("lsb_release", {"-d", "-s"}, { execution_options::trim_output }); + if (value.empty()) { + return; + } + + // The value may be quoted; trim the quotes + facts.add(lsb_dist_description_name, make_value(std::move(trim(value, { '\"' })))); + } + + void lsb_resolver::resolve_dist_version(fact_map& facts) + { + auto dist_release = facts.get(lsb_dist_release_name); + if (!dist_release) { + return; + } + string major; + string minor; + if (!RE2::PartialMatch(dist_release->value(), "(\\d+)\\.(\\d*)", &major, &minor)) { + major = dist_release->value(); + } + facts.add(lsb_dist_maj_release_name, make_value(std::move(major))); + + if (!minor.empty()) { + facts.add(lsb_dist_minor_release_name, make_value(std::move(minor))); + } + } + + void lsb_resolver::resolve_release(fact_map& facts) + { + string value = execute("lsb_release", {"-v", "-s"}, { execution_options::trim_output }); + if (value.empty()) { + return; + } + facts.add(lsb_release_name, make_value(std::move(value))); + } + }}} // namespace cfacter::facts::linux diff --git a/lib/src/util/string.cc b/lib/src/util/string.cc index 5eb870676b..b2e19dd767 100644 --- a/lib/src/util/string.cc +++ b/lib/src/util/string.cc @@ -1,5 +1,4 @@ #include -#include #include #include #include @@ -8,36 +7,42 @@ using namespace std; namespace cfacter { namespace util { - string& ltrim(string& str) + initializer_list default_trim_set = { '\r', '\n', ' ', '\t', '\v', '\f' }; + + string& ltrim(string& str, initializer_list const& set) { - str.erase(str.begin(), find_if(str.begin(), str.end(), [](char c) { return !isspace(c); })); + str.erase(str.begin(), find_if(str.begin(), str.end(), [&set](char c) { + return find(set.begin(), set.end(), c) == set.end(); + })); return str; } - string& ltrim(string&& str) + string& ltrim(string&& str, initializer_list const& set) { - return ltrim(str); + return ltrim(str, set); } - string& rtrim(string& str) + string& rtrim(string& str, initializer_list const& set) { - str.erase(find_if(str.rbegin(), str.rend(), [](char c) { return !isspace(c); }).base(), str.end()); + str.erase(find_if(str.rbegin(), str.rend(), [&set](char c) { + return find(set.begin(), set.end(), c) == set.end(); + }).base(), str.end()); return str; } - string& rtrim(string&& str) + string& rtrim(string&& str, initializer_list const& set) { - return rtrim(str); + return rtrim(str, set); } - string& trim(std::string& str) + string& trim(std::string& str, initializer_list const& set) { - return ltrim(rtrim(str)); + return ltrim(rtrim(str, set), set); } - string& trim(string&& str) + string& trim(string&& str, initializer_list const& set) { - return trim(str); + return trim(str, set); } vector tokenize(string const& str) From 9b8079b2b9bee32508157b4883d452b903a5cdcf Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Thu, 10 Apr 2014 14:47:28 -0700 Subject: [PATCH 1774/3753] (CFACT-3) Implement osfamily fact. Implementing the osfamily fact in the base POSIX provider. A future Windows implementation can simply return the kernel fact. --- .../facts/posix/operating_system_resolver.hpp | 7 +++ .../facts/posix/operating_system_resolver.cc | 57 +++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/lib/inc/facts/posix/operating_system_resolver.hpp b/lib/inc/facts/posix/operating_system_resolver.hpp index e25b32e90c..51150809f8 100644 --- a/lib/inc/facts/posix/operating_system_resolver.hpp +++ b/lib/inc/facts/posix/operating_system_resolver.hpp @@ -12,6 +12,7 @@ namespace cfacter { namespace facts { namespace posix { { // Constants for responsible facts constexpr static char const* operating_system_name = "operatingsystem"; + constexpr static char const* os_family_name = "osfamily"; /** * Constructs the operating_system_resolver. @@ -19,6 +20,7 @@ namespace cfacter { namespace facts { namespace posix { operating_system_resolver() : fact_resolver({ operating_system_name, + os_family_name }) { } @@ -34,6 +36,11 @@ namespace cfacter { namespace facts { namespace posix { * @param facts The fact map that is resolving facts. */ virtual void resolve_operating_system(fact_map& facts); + /** + * Called to resolve the os family fact. + * @param facts The fact map that is resolving facts. + */ + virtual void resolve_os_family(fact_map& facts); }; }}} // namespace cfacter::facts::posix diff --git a/lib/src/facts/posix/operating_system_resolver.cc b/lib/src/facts/posix/operating_system_resolver.cc index 42de55343e..8ef6decea2 100644 --- a/lib/src/facts/posix/operating_system_resolver.cc +++ b/lib/src/facts/posix/operating_system_resolver.cc @@ -2,6 +2,7 @@ #include #include #include +#include using namespace std; @@ -11,6 +12,7 @@ namespace cfacter { namespace facts { namespace posix { { // Resolve all operating system related facts resolve_operating_system(facts); + resolve_os_family(facts); } void operating_system_resolver::resolve_operating_system(fact_map& facts) @@ -24,4 +26,59 @@ namespace cfacter { namespace facts { namespace posix { facts.add(operating_system_name, make_value(kernel->value())); } + void operating_system_resolver::resolve_os_family(fact_map& facts) + { + // Get the operating system fact + auto os = facts.get(operating_system_name); + string value; + if (os) { + static std::map systems = { + { "RedHat", "RedHat" }, + { "Fedora", "RedHat" }, + { "CentOS", "RedHat" }, + { "Scientific", "RedHat" }, + { "SLC", "RedHat" }, + { "Ascendos", "RedHat" }, + { "CloudLinux", "RedHat" }, + { "PSBM", "RedHat" }, + { "OracleLinux", "RedHat" }, + { "OVS", "RedHat" }, + { "OEL", "RedHat" }, + { "Amazon", "RedHat" }, + { "XenServer", "RedHat" }, + { "LinuxMint", "Debian" }, + { "Ubuntu", "Debian" }, + { "Debian", "Debian" }, + { "SLES", "Suse" }, + { "SLED", "Suse" }, + { "OpenSuSE", "Suse" }, + { "SuSE", "Suse" }, + { "Solaris", "Solaris" }, + { "Nexenta", "Solaris" }, + { "OmniOS", "Solaris" }, + { "OpenIndiana", "Solaris" }, + { "SmartOS", "Solaris" }, + { "Gentoo", "Gentoo" }, + { "Archlinux", "Archlinux" }, + { "Mandrake", "Mandrake" }, + { "Mandriva", "Mandrake" }, + { "Mageia", "Mandrake" }, + }; + auto const& it = systems.find(os->value()); + if (it != systems.end()) { + value = it->second; + } + } + + if (value.empty()) { + // Default to the same value as the kernel + auto kernel = facts.get(kernel_resolver::kernel_name); + if (!kernel) { + return; + } + value = kernel->value(); + } + facts.add(os_family_name, make_value(std::move(value))); + } + }}} // namespace cfacter::facts::posix From 9a0e11dbd805ff9891ff3e85d670f1224d181207 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Thu, 10 Apr 2014 14:55:16 -0700 Subject: [PATCH 1775/3753] (CFACT-3) Implement the operatingsystemrelease fact. Implementing the operatingsystemrelease fact for all platforms except Solaris and Windows. Implementing additional utility functions. Moving various hardcoded values for OS, OS family, and release files into a single location. --- lib/inc/facts/linux/fact.hpp | 22 ++ lib/inc/facts/linux/lsb_resolver.hpp | 25 +- .../facts/linux/operating_system_resolver.hpp | 6 + lib/inc/facts/linux/release_file.hpp | 39 +++ lib/inc/facts/posix/fact.hpp | 22 ++ lib/inc/facts/posix/kernel_resolver.hpp | 15 +- .../facts/posix/operating_system_resolver.hpp | 15 +- lib/inc/facts/posix/os.hpp | 54 ++++ lib/inc/facts/posix/os_family.hpp | 22 ++ lib/inc/util/file.hpp | 7 + lib/inc/util/string.hpp | 16 ++ lib/src/execution/posix/execution.cc | 1 + lib/src/facts/linux/lsb_resolver.cc | 16 +- .../facts/linux/operating_system_resolver.cc | 232 ++++++++++++++---- lib/src/facts/posix/kernel_resolver.cc | 12 +- .../facts/posix/operating_system_resolver.cc | 84 ++++--- lib/src/util/posix/file.cc | 11 + lib/src/util/string.cc | 10 + 18 files changed, 476 insertions(+), 133 deletions(-) create mode 100644 lib/inc/facts/linux/fact.hpp create mode 100644 lib/inc/facts/linux/release_file.hpp create mode 100644 lib/inc/facts/posix/fact.hpp create mode 100644 lib/inc/facts/posix/os.hpp create mode 100644 lib/inc/facts/posix/os_family.hpp diff --git a/lib/inc/facts/linux/fact.hpp b/lib/inc/facts/linux/fact.hpp new file mode 100644 index 0000000000..169a89910f --- /dev/null +++ b/lib/inc/facts/linux/fact.hpp @@ -0,0 +1,22 @@ +#ifndef LIB_INC_FACTS_LINUX_FACT_HPP_ +#define LIB_INC_FACTS_LINUX_FACT_HPP_ + +namespace cfacter { namespace facts { namespace linux { + + /** + * Stores the constant fact names. + */ + struct fact + { + constexpr static char const* lsb_dist_id = "lsbdistid"; + constexpr static char const* lsb_dist_release = "lsbdistrelease"; + constexpr static char const* lsb_dist_codename = "lsbdistcodename"; + constexpr static char const* lsb_dist_description = "lsbdistdescription"; + constexpr static char const* lsb_dist_major_release = "lsbmajdistrelease"; + constexpr static char const* lsb_dist_minor_release = "lsbminordistrelease"; + constexpr static char const* lsb_release = "lsbrelease"; + }; + +}}} // namespace cfacter::facts::linux + +#endif // LIB_INC_FACTS_LINUX_FACT_HPP_ diff --git a/lib/inc/facts/linux/lsb_resolver.hpp b/lib/inc/facts/linux/lsb_resolver.hpp index c6637699e7..1778d42593 100644 --- a/lib/inc/facts/linux/lsb_resolver.hpp +++ b/lib/inc/facts/linux/lsb_resolver.hpp @@ -2,6 +2,7 @@ #define LIB_INC_FACTS_LINUX_LSB_RESOLVER_HPP_ #include "../fact_resolver.hpp" +#include "fact.hpp" namespace cfacter { namespace facts { namespace linux { @@ -10,28 +11,18 @@ namespace cfacter { namespace facts { namespace linux { */ struct lsb_resolver : fact_resolver { - // Constants for responsible facts - constexpr static char const* lsb_dist_id_name = "lsbdistid"; - constexpr static char const* lsb_dist_release_name = "lsbdistrelease"; - constexpr static char const* lsb_dist_codename_name = "lsbdistcodename"; - constexpr static char const* lsb_dist_description_name = "lsbdistdescription"; - constexpr static char const* lsb_dist_maj_release_name = "lsbmajdistrelease"; - constexpr static char const* lsb_dist_minor_release_name = "lsbminordistrelease"; - constexpr static char const* lsb_release_name = "lsbrelease"; - - /** * Constructs the lsb_resolver. */ lsb_resolver() : fact_resolver({ - lsb_dist_id_name, - lsb_dist_release_name, - lsb_dist_codename_name, - lsb_dist_description_name, - lsb_dist_maj_release_name, - lsb_dist_minor_release_name, - lsb_release_name, + fact::lsb_dist_id, + fact::lsb_dist_release, + fact::lsb_dist_codename, + fact::lsb_dist_description, + fact::lsb_dist_major_release, + fact::lsb_dist_minor_release, + fact::lsb_release, }) { } diff --git a/lib/inc/facts/linux/operating_system_resolver.hpp b/lib/inc/facts/linux/operating_system_resolver.hpp index a86d58f6ba..67ca0d3526 100644 --- a/lib/inc/facts/linux/operating_system_resolver.hpp +++ b/lib/inc/facts/linux/operating_system_resolver.hpp @@ -18,6 +18,12 @@ namespace cfacter { namespace facts { namespace linux { */ virtual void resolve_operating_system(fact_map& facts); + /** + * Called to resolve the operating system release fact. + * @param facts The fact map that is resolving facts. + */ + virtual void resolve_operating_system_release(fact_map& facts); + private: static std::string check_cumulus_linux(); static std::string check_debian_linux(string_value const* dist_id); diff --git a/lib/inc/facts/linux/release_file.hpp b/lib/inc/facts/linux/release_file.hpp new file mode 100644 index 0000000000..7403639373 --- /dev/null +++ b/lib/inc/facts/linux/release_file.hpp @@ -0,0 +1,39 @@ +#ifndef LIB_INC_FACTS_LINUX_RELEASE_FILE_HPP_ +#define LIB_INC_FACTS_LINUX_RELEASE_FILE_HPP_ + +namespace cfacter { namespace facts { namespace linux { + + /** + * Stores the constant release file names. + */ + struct release_file + { + constexpr static char const* redhat = "/etc/redhat-release"; + constexpr static char const* fedora = "/etc/fedora-release"; + constexpr static char const* meego = "/etc/meego-release"; + constexpr static char const* oracle_linux = "/etc/oracle-release"; + constexpr static char const* oracle_enterprise_linux = "/etc/enterprise-release"; + constexpr static char const* oracle_vm_linux = "/etc/ovs-release"; + constexpr static char const* debian = "/etc/debian_version"; + constexpr static char const* alpine = "/etc/alpine-release"; + constexpr static char const* suse = "/etc/SuSE-release"; + constexpr static char const* os = "/etc/os-release"; + constexpr static char const* lsb = "/etc/lsb-release"; + constexpr static char const* gentoo = "/etc/gentoo-release"; + constexpr static char const* openwrt = "/etc/openwrt_release"; + constexpr static char const* openwrt_version = "/etc/openwrt_version"; + constexpr static char const* mandriva = "/etc/mandriva-release"; + constexpr static char const* mandrake = "/etc/mandrake-release"; + constexpr static char const* archlinux = "/etc/arch-release"; + constexpr static char const* vmware_esx = "/etc/vmware-release"; + constexpr static char const* bluewhite = "/etc/bluewhite64-version"; + constexpr static char const* slack_amd64 = "/etc/slamd64-version"; + constexpr static char const* slackware = "/etc/slackware-version"; + constexpr static char const* mageia = "/etc/mageia-release"; + constexpr static char const* amazon = "/etc/system-release"; + constexpr static char const* linux_mint_info = "/etc/linuxmint/info"; + }; + +}}} // namespace cfacter::facts::linux + +#endif // LIB_INC_FACTS_LINUX_RELEASE_FILE_HPP_ diff --git a/lib/inc/facts/posix/fact.hpp b/lib/inc/facts/posix/fact.hpp new file mode 100644 index 0000000000..a9c42d8891 --- /dev/null +++ b/lib/inc/facts/posix/fact.hpp @@ -0,0 +1,22 @@ +#ifndef LIB_INC_FACTS_POSIX_FACT_HPP_ +#define LIB_INC_FACTS_POSIX_FACT_HPP_ + +namespace cfacter { namespace facts { namespace posix { + + /** + * Stores the constant fact names. + */ + struct fact + { + constexpr static char const* kernel = "kernel"; + constexpr static char const* kernel_version = "kernelversion"; + constexpr static char const* kernel_release = "kernelrelease"; + constexpr static char const* kernel_major_release = "kernelmajrelease"; + constexpr static char const* operating_system = "operatingsystem"; + constexpr static char const* os_family = "osfamily"; + constexpr static char const* operating_system_release = "operatingsystemrelease"; + }; + +}}} // namespace cfacter::facts::posix + +#endif // LIB_INC_FACTS_POSIX_FACT_HPP_ diff --git a/lib/inc/facts/posix/kernel_resolver.hpp b/lib/inc/facts/posix/kernel_resolver.hpp index 48980517a2..40c2113352 100644 --- a/lib/inc/facts/posix/kernel_resolver.hpp +++ b/lib/inc/facts/posix/kernel_resolver.hpp @@ -2,6 +2,7 @@ #define LIB_INC_FACTS_POSIX_KERNEL_RESOLVER_HPP_ #include "../fact_resolver.hpp" +#include "fact.hpp" #include namespace cfacter { namespace facts { namespace posix { @@ -11,21 +12,15 @@ namespace cfacter { namespace facts { namespace posix { */ struct kernel_resolver : fact_resolver { - // Constants for responsible facts - constexpr static char const* kernel_name = "kernel"; - constexpr static char const* kernel_version_name = "kernelversion"; - constexpr static char const* kernel_release_name = "kernelrelease"; - constexpr static char const* kernel_maj_release_name = "kernelmajrelease"; - /** * Constructs the kernel_resolver. */ kernel_resolver() : fact_resolver({ - kernel_name, - kernel_version_name, - kernel_release_name, - kernel_maj_release_name + fact::kernel, + fact::kernel_version, + fact::kernel_release, + fact::kernel_major_release }) { } diff --git a/lib/inc/facts/posix/operating_system_resolver.hpp b/lib/inc/facts/posix/operating_system_resolver.hpp index 51150809f8..1339902ac0 100644 --- a/lib/inc/facts/posix/operating_system_resolver.hpp +++ b/lib/inc/facts/posix/operating_system_resolver.hpp @@ -2,6 +2,7 @@ #define LIB_INC_FACTS_POSIX_OPERATING_SYSTEM_RESOLVER_HPP_ #include "../fact_resolver.hpp" +#include "fact.hpp" namespace cfacter { namespace facts { namespace posix { @@ -10,17 +11,14 @@ namespace cfacter { namespace facts { namespace posix { */ struct operating_system_resolver : fact_resolver { - // Constants for responsible facts - constexpr static char const* operating_system_name = "operatingsystem"; - constexpr static char const* os_family_name = "osfamily"; - /** * Constructs the operating_system_resolver. */ operating_system_resolver() : fact_resolver({ - operating_system_name, - os_family_name + fact::operating_system, + fact::os_family, + fact::operating_system_release, }) { } @@ -41,6 +39,11 @@ namespace cfacter { namespace facts { namespace posix { * @param facts The fact map that is resolving facts. */ virtual void resolve_os_family(fact_map& facts); + /** + * Called to resolve the operating system release fact. + * @param facts The fact map that is resolving facts. + */ + virtual void resolve_operating_system_release(fact_map& facts); }; }}} // namespace cfacter::facts::posix diff --git a/lib/inc/facts/posix/os.hpp b/lib/inc/facts/posix/os.hpp new file mode 100644 index 0000000000..da103fe2bd --- /dev/null +++ b/lib/inc/facts/posix/os.hpp @@ -0,0 +1,54 @@ +#ifndef LIB_INC_FACTS_POSIX_OS_HPP_ +#define LIB_INC_FACTS_POSIX_OS_HPP_ + +namespace cfacter { namespace facts { namespace posix { + + /** + * Stores the constant operating system names. + */ + struct os + { + constexpr static char const* redhat = "RedHat"; + constexpr static char const* centos = "CentOS"; + constexpr static char const* fedora = "Fedora"; + constexpr static char const* scientific = "Scientific"; + constexpr static char const* scientific_cern = "SLC"; + constexpr static char const* ascendos = "Ascendos"; + constexpr static char const* cloud_linux = "CloudLinux"; + constexpr static char const* psbm = "PSBM"; + constexpr static char const* oracle_linux = "OracleLinux"; + constexpr static char const* oracle_vm_linux = "OVS"; + constexpr static char const* oracle_enterprise_linux = "OEL"; + constexpr static char const* amazon = "Amazon"; + constexpr static char const* xen_server = "XenServer"; + constexpr static char const* linux_mint = "LinuxMint"; + constexpr static char const* ubuntu = "Ubuntu"; + constexpr static char const* debian = "Debian"; + constexpr static char const* suse_enterprise_server = "SLES"; + constexpr static char const* suse_enterprise_desktop = "SLED"; + constexpr static char const* open_suse = "OpenSuSE"; + constexpr static char const* suse = "SuSE"; + constexpr static char const* solaris = "Solaris"; + constexpr static char const* nexenta = "Nexenta"; + constexpr static char const* omni = "OmniOS"; + constexpr static char const* open_indiana = "OpenIndiana"; + constexpr static char const* smart = "SmartOS"; + constexpr static char const* gentoo = "Gentoo"; + constexpr static char const* archlinux = "Archlinux"; + constexpr static char const* mandrake = "Mandrake"; + constexpr static char const* mandriva = "Mandriva"; + constexpr static char const* mageia = "Mageia"; + constexpr static char const* openwrt = "OpenWrt"; + constexpr static char const* meego = "MeeGo"; + constexpr static char const* vmware_esx = "VMWareESX"; + constexpr static char const* bluewhite = "Bluewhite64"; + constexpr static char const* slack_amd64 = "Slamd64"; + constexpr static char const* slackware = "Slackware"; + constexpr static char const* alpine = "Alpine"; + constexpr static char const* cumulus = "CumulusLinux"; + constexpr static char const* zen_cloud_platform = "XCP"; + }; + +}}} // namespace cfacter::facts::posix + +#endif // LIB_INC_FACTS_POSIX_OS_HPP_ diff --git a/lib/inc/facts/posix/os_family.hpp b/lib/inc/facts/posix/os_family.hpp new file mode 100644 index 0000000000..aa0141cc03 --- /dev/null +++ b/lib/inc/facts/posix/os_family.hpp @@ -0,0 +1,22 @@ +#ifndef LIB_INC_FACTS_POSIX_OS_FAMILY_HPP_ +#define LIB_INC_FACTS_POSIX_OS_FAMILY_HPP_ + +namespace cfacter { namespace facts { namespace posix { + + /** + * Stores the constant operating system family names. + */ + struct os_family + { + constexpr static char const* redhat = "RedHat"; + constexpr static char const* debian = "Debian"; + constexpr static char const* suse = "Suse"; + constexpr static char const* solaris = "Solaris"; + constexpr static char const* gentoo = "Gentoo"; + constexpr static char const* archlinux = "Archlinux"; + constexpr static char const* mandrake = "Mandrake"; + }; + +}}} // namespace cfacter::facts::posix + +#endif // LIB_INC_FACTS_POSIX_OS_FAMILY_HPP_ diff --git a/lib/inc/util/file.hpp b/lib/inc/util/file.hpp index d9ef3a77f4..e4dac0e6a7 100644 --- a/lib/inc/util/file.hpp +++ b/lib/inc/util/file.hpp @@ -23,6 +23,13 @@ namespace cfacter { namespace util { * @return Returns the file contents as a string or empty string if the file cannot be read. */ static std::string read(std::string const& path); + + /** + * Reads the first line of the given file into a string. + * @param path The path of the file to read. + * @return Returns the first line of the file or an empty string if the file cannot be read. + */ + static std::string read_first_line(std::string const& path); }; }} // namespace cfacter::util diff --git a/lib/inc/util/string.hpp b/lib/inc/util/string.hpp index ca24adff39..da6deb10d3 100644 --- a/lib/inc/util/string.hpp +++ b/lib/inc/util/string.hpp @@ -11,6 +11,22 @@ namespace cfacter { namespace util { + /** + * Checks if the given string starts with the given prefix. + * @param str The string to check against. + * @param prefix The prefix to check with. + * @return Returns true if the given string starts with the given prefix or false if otherwise. + */ + bool starts_with(std::string const& str, std::string const& prefix); + + /** + * Checks if the given string ends with the given suffix. + * @param str The string to check against. + * @param suffix The suffix to check with. + * @return Returns true if the given string ends with the given suffix or false if otherwise. + */ + bool ends_with(std::string const& str, std::string const& suffix); + extern std::initializer_list default_trim_set; /** diff --git a/lib/src/execution/posix/execution.cc b/lib/src/execution/posix/execution.cc index 21a2de5f14..c64ce61741 100644 --- a/lib/src/execution/posix/execution.cc +++ b/lib/src/execution/posix/execution.cc @@ -155,6 +155,7 @@ namespace cfacter { namespace execution { } // TODO: set up environment if specified (execvpe is a GNU extension) + // TODO: regardless of passed environment variables, we need to force a C locale exit(execvp(file.c_str(), const_cast(args.data()))); } catch (exception& ex) diff --git a/lib/src/facts/linux/lsb_resolver.cc b/lib/src/facts/linux/lsb_resolver.cc index acf4e67186..c90b0e57c1 100644 --- a/lib/src/facts/linux/lsb_resolver.cc +++ b/lib/src/facts/linux/lsb_resolver.cc @@ -28,7 +28,7 @@ namespace cfacter { namespace facts { namespace linux { if (value.empty()) { return; } - facts.add(lsb_dist_id_name, make_value(std::move(value))); + facts.add(fact::lsb_dist_id, make_value(std::move(value))); } void lsb_resolver::resolve_dist_release(fact_map& facts) @@ -37,7 +37,7 @@ namespace cfacter { namespace facts { namespace linux { if (value.empty()) { return; } - facts.add(lsb_dist_release_name, make_value(std::move(value))); + facts.add(fact::lsb_dist_release, make_value(std::move(value))); } void lsb_resolver::resolve_dist_codename(fact_map& facts) @@ -46,7 +46,7 @@ namespace cfacter { namespace facts { namespace linux { if (value.empty()) { return; } - facts.add(lsb_dist_codename_name, make_value(std::move(value))); + facts.add(fact::lsb_dist_codename, make_value(std::move(value))); } void lsb_resolver::resolve_dist_description(fact_map& facts) @@ -57,12 +57,12 @@ namespace cfacter { namespace facts { namespace linux { } // The value may be quoted; trim the quotes - facts.add(lsb_dist_description_name, make_value(std::move(trim(value, { '\"' })))); + facts.add(fact::lsb_dist_description, make_value(std::move(trim(value, { '\"' })))); } void lsb_resolver::resolve_dist_version(fact_map& facts) { - auto dist_release = facts.get(lsb_dist_release_name); + auto dist_release = facts.get(fact::lsb_dist_release); if (!dist_release) { return; } @@ -71,10 +71,10 @@ namespace cfacter { namespace facts { namespace linux { if (!RE2::PartialMatch(dist_release->value(), "(\\d+)\\.(\\d*)", &major, &minor)) { major = dist_release->value(); } - facts.add(lsb_dist_maj_release_name, make_value(std::move(major))); + facts.add(fact::lsb_dist_major_release, make_value(std::move(major))); if (!minor.empty()) { - facts.add(lsb_dist_minor_release_name, make_value(std::move(minor))); + facts.add(fact::lsb_dist_minor_release, make_value(std::move(minor))); } } @@ -84,7 +84,7 @@ namespace cfacter { namespace facts { namespace linux { if (value.empty()) { return; } - facts.add(lsb_release_name, make_value(std::move(value))); + facts.add(fact::lsb_release, make_value(std::move(value))); } }}} // namespace cfacter::facts::linux diff --git a/lib/src/facts/linux/operating_system_resolver.cc b/lib/src/facts/linux/operating_system_resolver.cc index f387488103..468ec101ec 100644 --- a/lib/src/facts/linux/operating_system_resolver.cc +++ b/lib/src/facts/linux/operating_system_resolver.cc @@ -1,5 +1,8 @@ +#include #include #include +#include +#include #include #include #include @@ -9,12 +12,14 @@ using namespace std; using namespace cfacter::util; +using namespace cfacter::execution; +using namespace cfacter::facts::posix; namespace cfacter { namespace facts { namespace linux { void operating_system_resolver::resolve_operating_system(fact_map& facts) { - auto dist_id = facts.get(lsb_resolver::lsb_dist_id_name); + auto dist_id = facts.get(fact::lsb_dist_id); // Start by checking for Cumulus Linux string value = check_cumulus_linux(); @@ -51,19 +56,147 @@ namespace cfacter { namespace facts { namespace linux { } // Add the fact - facts.add(operating_system_name, make_value(value)); + facts.add(posix::fact::operating_system, make_value(value)); + } + + void operating_system_resolver::resolve_operating_system_release(fact_map& facts) + { + auto operating_system = facts.get(posix::fact::operating_system); + if (!operating_system) { + // Use the base implementation + posix::operating_system_resolver::resolve_operating_system_release(facts); + return; + } + + // Map of release files that contain a "release X.X.X" on the first line + static map release_files = { + { string(os::centos), string(release_file::redhat) }, + { string(os::redhat), string(release_file::redhat) }, + { string(os::scientific), string(release_file::redhat) }, + { string(os::scientific_cern), string(release_file::redhat) }, + { string(os::ascendos), string(release_file::redhat) }, + { string(os::cloud_linux), string(release_file::redhat) }, + { string(os::psbm), string(release_file::redhat) }, + { string(os::xen_server), string(release_file::redhat) }, + { string(os::fedora), string(release_file::fedora) }, + { string(os::meego), string(release_file::meego) }, + { string(os::oracle_linux), string(release_file::oracle_linux) }, + { string(os::oracle_enterprise_linux), string(release_file::oracle_enterprise_linux) }, + { string(os::oracle_vm_linux), string(release_file::oracle_vm_linux) }, + }; + + string value; + auto it = release_files.find(operating_system->value()); + if (it != release_files.end()) { + string contents = file::read_first_line(it->second); + if (ends_with(contents, "(Rawhide)")) { + value = "Rawhide"; + } else { + RE2::PartialMatch(contents, "release (\\d[\\d.]*)", &value); + } + } + + // Debian uses the entire contents of the release file as the version + if (value.empty() && operating_system->value() == os::debian) { + value = rtrim(file::read(release_file::debian)); + } + + // Alpine uses the entire contents of the release file as the version + if (value.empty() && operating_system->value() == os::alpine) { + value = rtrim(file::read(release_file::alpine)); + } + + // Check for SuSE related distros, read the release file + if (value.empty() && ( + operating_system->value() == os::suse || + operating_system->value() == os::suse_enterprise_server || + operating_system->value() == os::suse_enterprise_desktop || + operating_system->value() == os::open_suse)) { + string contents = file::read(release_file::suse); + string major; + string minor; + if (RE2::PartialMatch(contents, "(?m)^VERSION\\s*=\\s*(\\d+)\\.?(\\d+)?", &major, &minor)) { + // Check that we have a minor version; if not, use the patch level + if (minor.empty()) { + if (!RE2::PartialMatch(contents, "(?m)^PATCHLEVEL\\s*=\\s*(\\d+)", &minor)) { + minor = "0"; + } + } + value = major + "." + minor; + } else { + value = "unknown"; + } + } + + // Read version files of particular operating systems + if (value.empty()) { + const char* file = nullptr; + string regex; + if (operating_system->value() == os::ubuntu) { + file = release_file::lsb; + regex = "(?m)^DISTRIB_RELEASE=(\\d+\\.\\d+)(?:\\.\\d+)*"; + } else if (operating_system->value() == os::slackware) { + file = release_file::slackware; + regex = "Slackware ([0-9.]+)"; + } else if (operating_system->value() == os::mageia) { + file = release_file::mageia; + regex = "Mageia release ([0-9.]+)"; + } else if (operating_system->value() == os::bluewhite) { + file = release_file::bluewhite; + regex = "(?m)^\\s*\\w+\\s+(\\d+\\.\\d+)"; + } else if (operating_system->value() == os::slack_amd64) { + file = release_file::slack_amd64; + regex = "(?m)^\\s*\\w+\\s+(\\d+\\.\\d+)"; + } else if (operating_system->value() == os::cumulus) { + file = release_file::os; + regex = "(?m)^VERSION_ID\\s*=\\s*(\\d+\\.\\d+\\.\\d+)"; + } else if (operating_system->value() == os::linux_mint) { + file = release_file::linux_mint_info; + regex = "(?m)^RELEASE=(\\d+)"; + } else if (operating_system->value() == os::openwrt) { + file = release_file::openwrt_version; + regex = "(?m)^(\\d+\\.\\d+.*)"; + } + if (file) { + string contents = file::read(file); + RE2::PartialMatch(contents, regex, &value); + } + } + + // For VMware ESX, execute the vmware tool + if (value.empty() && operating_system->value() == os::vmware_esx) { + string output = execute("vmware", { "-v" }, { execution_options::trim_output }); + RE2::PartialMatch(output, "VMware ESX .*?(\\d.*)", &value); + } + + // For Amazon, use the lsbdistrelease fact + if (value.empty() && operating_system->value() == os::amazon) { + auto release = facts.get(fact::lsb_dist_release); + if (release) { + facts.add(posix::fact::operating_system_release, make_value(release->value())); + return; + } + } + + // Use the base implementation if we have no value + if (value.empty()) { + posix::operating_system_resolver::resolve_operating_system_release(facts); + return; + } + + facts.add(posix::fact::operating_system_release, make_value(std::move(value))); } string operating_system_resolver::check_cumulus_linux() { - // Check for Cumulus Linux - if (file::exists("/etc/os-release")) { - string contents = trim(file::read("/etc/os-release")); + // Check for Cumulus Linux in a generic os-release file + if (file::exists(release_file::os)) { + string contents = trim(file::read(release_file::os)); string release; - if (RE2::FullMatch(contents, "^NAME=[\"']?(.+?)[\"']?$", &release)) { + if (RE2::PartialMatch(contents, "(?m)^NAME=[\"']?(.+?)[\"']?$", &release)) { RE2::GlobalReplace(&release, "[^a-zA-Z]", ""); - if (release == "CumulusLinux") { - return "CumulusLinux"; + if (release == os::cumulus) { + return release; } } } @@ -73,73 +206,70 @@ namespace cfacter { namespace facts { namespace linux { string operating_system_resolver::check_debian_linux(string_value const* dist_id) { // Check for Debian variants - if (file::exists("/etc/debian_version")) { + if (file::exists(release_file::debian)) { if (dist_id) { - if (dist_id->value() == "Ubuntu") { - return "Ubuntu"; - } - if (dist_id->value() == "LinuxMint") { - return "LinuxMint"; + if (dist_id->value() == os::ubuntu || dist_id->value() == os::linux_mint) { + return dist_id->value(); } } - return "Debian"; + return os::debian; } return string(); } string operating_system_resolver::check_oracle_linux() { - if (file::exists("/etc/enterprise-release")) { - if (file::exists("/etc/ovs-release")) { - return "OVS"; + if (file::exists(release_file::oracle_enterprise_linux)) { + if (file::exists(release_file::oracle_vm_linux)) { + return os::oracle_vm_linux; } - return "OEL"; + return os::oracle_enterprise_linux; } return string(); } string operating_system_resolver::check_redhat_linux() { - if (file::exists("/etc/redhat-release")) { + if (file::exists(release_file::redhat)) { static map regexs { - { "(?i)centos", "CentOS" }, - { "(?i)scientific linux CERN", "SLC" }, - { "(?i)scientific linux release", "Scientific" }, - { "(?i)^cloudlinux", "CloudLinux" }, - { "(?i)Ascendos", "Ascendos" }, - { "(?i)^XenServer", "XenServer" }, - { "XCP", "XCP" }, - { "(?i)^Parallels Server Bare Metal", "PSBM" }, - { "^Fedora release", "Fedora" }, + { "(?i)centos", string(os::centos) }, + { "(?i)scientific linux CERN", string(os::scientific_cern) }, + { "(?i)scientific linux release", string(os::scientific) }, + { "(?im)^cloudlinux", string(os::cloud_linux) }, + { "(?i)Ascendos", string(os::ascendos) }, + { "(?im)^XenServer", string(os::xen_server) }, + { "XCP", string(os::zen_cloud_platform) }, + { "(?im)^Parallels Server Bare Metal", string(os::psbm) }, + { "(?m)^Fedora release", string(os::fedora) }, }; - string contents = trim(file::read("/etc/redhat-release")); + string contents = trim(file::read(release_file::redhat)); for (auto const& kvp : regexs) { if (RE2::PartialMatch(contents, kvp.first)) { return kvp.second; } } - return "RedHat"; + return os::redhat; } return string(); } string operating_system_resolver::check_suse_linux() { - if (file::exists("/etc/SuSE-release")) { + if (file::exists(release_file::suse)) { static map regexs { - { "(?i)^SUSE LINUX Enterprise Server", "SLES" }, - { "(?i)^SUSE LINUX Enterprise Desktop", "SLED" }, - { "(?i)^openSUSE", "OpenSuSE" }, + { "(?im)^SUSE LINUX Enterprise Server", string(os::suse_enterprise_server) }, + { "(?im)^SUSE LINUX Enterprise Desktop", string(os::suse_enterprise_desktop) }, + { "(?im)^openSUSE", string(os::open_suse) }, }; - string contents = trim(file::read("/etc/SuSE-release")); + string contents = trim(file::read(release_file::suse)); for (auto const& kvp : regexs) { if (RE2::PartialMatch(contents, kvp.first)) { return kvp.second; } } - return "SuSE"; + return os::suse; } return string(); } @@ -147,20 +277,20 @@ namespace cfacter { namespace facts { namespace linux { string operating_system_resolver::check_other_linux() { static map files { - { "/etc/openwrt_release", "OpenWrt" }, - { "/etc/gentoo-release", "Gentoo" }, - { "/etc/mandriva-release", "Mandriva" }, - { "/etc/mandrake-release", "Mandrake" }, - { "/etc/meego-release", "MeeGo" }, - { "/etc/arch-release", "Archlinux" }, - { "/etc/oracle-release", "OracleLinux" }, - { "/etc/vmware-release", "VMWareESX" }, - { "/etc/bluewhite64-version", "Bluewhite64" }, - { "/etc/slamd64-version", "Slamd64" }, - { "/etc/slackware-version", "Slackware" }, - { "/etc/alpine-release", "Alpine" }, - { "/etc/mageia-release", "Mageia" }, - { "/etc/system-release", "Amazon" }, + { string(release_file::openwrt), string(os::openwrt) }, + { string(release_file::gentoo), string(os::gentoo) }, + { string(release_file::mandriva), string(os::mandriva) }, + { string(release_file::mandrake), string(os::mandrake) }, + { string(release_file::meego), string(os::meego) }, + { string(release_file::archlinux), string(os::archlinux) }, + { string(release_file::oracle_linux), string(os::oracle_linux) }, + { string(release_file::vmware_esx), string(os::vmware_esx) }, + { string(release_file::bluewhite), string(os::bluewhite) }, + { string(release_file::slack_amd64), string(os::slack_amd64) }, + { string(release_file::slackware), string(os::slackware) }, + { string(release_file::alpine), string(os::alpine) }, + { string(release_file::mageia), string(os::mageia) }, + { string(release_file::amazon), string(os::amazon) }, }; for (auto const& kvp : files) { diff --git a/lib/src/facts/posix/kernel_resolver.cc b/lib/src/facts/posix/kernel_resolver.cc index 79d2a830d0..7f90cfebff 100644 --- a/lib/src/facts/posix/kernel_resolver.cc +++ b/lib/src/facts/posix/kernel_resolver.cc @@ -28,7 +28,7 @@ namespace cfacter { namespace facts { namespace posix { if (value.empty()) { return; } - facts.add(kernel_name, make_value(std::move(value))); + facts.add(fact::kernel, make_value(std::move(value))); } void kernel_resolver::resolve_kernel_release(fact_map& facts, utsname const& name) @@ -37,12 +37,12 @@ namespace cfacter { namespace facts { namespace posix { if (value.empty()) { return; } - facts.add(kernel_release_name, make_value(std::move(value))); + facts.add(fact::kernel_release, make_value(std::move(value))); } void kernel_resolver::resolve_kernel_version(fact_map& facts) { - auto version = facts.get(kernel_release_name); + auto version = facts.get(fact::kernel_release); if (!version) { return; } @@ -52,12 +52,12 @@ namespace cfacter { namespace facts { namespace posix { if (pos != string::npos) { value = value.substr(0, pos); } - facts.add(kernel_version_name, make_value(std::move(value))); + facts.add(fact::kernel_version, make_value(std::move(value))); } void kernel_resolver::resolve_kernel_major_version(fact_map& facts) { - auto version = facts.get(kernel_release_name); + auto version = facts.get(fact::kernel_release); if (!version) { return; } @@ -71,7 +71,7 @@ namespace cfacter { namespace facts { namespace posix { value = value.substr(0, pos); } } - facts.add(kernel_maj_release_name, make_value(std::move(value))); + facts.add(fact::kernel_major_release, make_value(std::move(value))); } }}} // namespace cfacter::facts::posix diff --git a/lib/src/facts/posix/operating_system_resolver.cc b/lib/src/facts/posix/operating_system_resolver.cc index 8ef6decea2..8b92be9922 100644 --- a/lib/src/facts/posix/operating_system_resolver.cc +++ b/lib/src/facts/posix/operating_system_resolver.cc @@ -1,5 +1,7 @@ #include #include +#include +#include #include #include #include @@ -13,56 +15,57 @@ namespace cfacter { namespace facts { namespace posix { // Resolve all operating system related facts resolve_operating_system(facts); resolve_os_family(facts); + resolve_operating_system_release(facts); } void operating_system_resolver::resolve_operating_system(fact_map& facts) { // Default to the same value as the kernel - auto kernel = facts.get(kernel_resolver::kernel_name); + auto kernel = facts.get(fact::kernel); if (!kernel) { return; } - facts.add(operating_system_name, make_value(kernel->value())); + facts.add(fact::operating_system, make_value(kernel->value())); } void operating_system_resolver::resolve_os_family(fact_map& facts) { // Get the operating system fact - auto os = facts.get(operating_system_name); + auto os = facts.get(fact::operating_system); string value; if (os) { static std::map systems = { - { "RedHat", "RedHat" }, - { "Fedora", "RedHat" }, - { "CentOS", "RedHat" }, - { "Scientific", "RedHat" }, - { "SLC", "RedHat" }, - { "Ascendos", "RedHat" }, - { "CloudLinux", "RedHat" }, - { "PSBM", "RedHat" }, - { "OracleLinux", "RedHat" }, - { "OVS", "RedHat" }, - { "OEL", "RedHat" }, - { "Amazon", "RedHat" }, - { "XenServer", "RedHat" }, - { "LinuxMint", "Debian" }, - { "Ubuntu", "Debian" }, - { "Debian", "Debian" }, - { "SLES", "Suse" }, - { "SLED", "Suse" }, - { "OpenSuSE", "Suse" }, - { "SuSE", "Suse" }, - { "Solaris", "Solaris" }, - { "Nexenta", "Solaris" }, - { "OmniOS", "Solaris" }, - { "OpenIndiana", "Solaris" }, - { "SmartOS", "Solaris" }, - { "Gentoo", "Gentoo" }, - { "Archlinux", "Archlinux" }, - { "Mandrake", "Mandrake" }, - { "Mandriva", "Mandrake" }, - { "Mageia", "Mandrake" }, + { string(os::redhat), string(os_family::redhat) }, + { string(os::fedora), string(os_family::redhat) }, + { string(os::centos), string(os_family::redhat) }, + { string(os::scientific), string(os_family::redhat) }, + { string(os::scientific_cern), string(os_family::redhat) }, + { string(os::ascendos), string(os_family::redhat) }, + { string(os::cloud_linux), string(os_family::redhat) }, + { string(os::psbm), string(os_family::redhat) }, + { string(os::oracle_linux), string(os_family::redhat) }, + { string(os::oracle_vm_linux), string(os_family::redhat) }, + { string(os::oracle_enterprise_linux), string(os_family::redhat) }, + { string(os::amazon), string(os_family::redhat) }, + { string(os::xen_server), string(os_family::redhat) }, + { string(os::linux_mint), string(os_family::debian) }, + { string(os::ubuntu), string(os_family::debian) }, + { string(os::debian), string(os_family::debian) }, + { string(os::suse_enterprise_server), string(os_family::suse) }, + { string(os::suse_enterprise_desktop), string(os_family::suse) }, + { string(os::open_suse), string(os_family::suse) }, + { string(os::suse), string(os_family::suse) }, + { string(os::solaris), string(os_family::solaris) }, + { string(os::nexenta), string(os_family::solaris) }, + { string(os::omni), string(os_family::solaris) }, + { string(os::open_indiana), string(os_family::solaris) }, + { string(os::smart), string(os_family::solaris) }, + { string(os::gentoo), string(os_family::gentoo) }, + { string(os::archlinux), string(os_family::archlinux) }, + { string(os::mandrake), string(os_family::mandrake) }, + { string(os::mandriva), string(os_family::mandrake) }, + { string(os::mageia), string(os_family::mandrake) }, }; auto const& it = systems.find(os->value()); if (it != systems.end()) { @@ -72,13 +75,24 @@ namespace cfacter { namespace facts { namespace posix { if (value.empty()) { // Default to the same value as the kernel - auto kernel = facts.get(kernel_resolver::kernel_name); + auto kernel = facts.get(fact::kernel); if (!kernel) { return; } value = kernel->value(); } - facts.add(os_family_name, make_value(std::move(value))); + facts.add(fact::os_family, make_value(std::move(value))); + } + + void operating_system_resolver::resolve_operating_system_release(fact_map& facts) + { + // Default to the same value as the kernelrelease fact + auto release = facts.get(fact::kernel_release); + if (!release) { + return; + } + + facts.add(fact::operating_system_release, make_value(release->value())); } }}} // namespace cfacter::facts::posix diff --git a/lib/src/util/posix/file.cc b/lib/src/util/posix/file.cc index 1dcca80bcb..d040883103 100644 --- a/lib/src/util/posix/file.cc +++ b/lib/src/util/posix/file.cc @@ -28,4 +28,15 @@ namespace cfacter { namespace util { return contents.str(); } + // TODO: this is standard-compliant, so it can be shared between POSIX and Windows + string file::read_first_line(string const& path) + { + ifstream in(path); + string value; + if (getline(in, value)) { + return value; + } + return string(); + } + }} // namespace cfacter::util diff --git a/lib/src/util/string.cc b/lib/src/util/string.cc index b2e19dd767..190d751729 100644 --- a/lib/src/util/string.cc +++ b/lib/src/util/string.cc @@ -9,6 +9,16 @@ namespace cfacter { namespace util { initializer_list default_trim_set = { '\r', '\n', ' ', '\t', '\v', '\f' }; + bool starts_with(string const& str, string const& prefix) + { + return prefix.size() <= str.size() && equal(prefix.begin(), prefix.end(), str.begin()); + } + + bool ends_with(string const& str, string const& suffix) + { + return suffix.size() <= str.size() && equal(suffix.rbegin(), suffix.rend(), str.rbegin()); + } + string& ltrim(string& str, initializer_list const& set) { str.erase(str.begin(), find_if(str.begin(), str.end(), [&set](char c) { From 7218c5f9e0390bbe154bc0a50a96d764743972f5 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Fri, 11 Apr 2014 12:08:41 -0700 Subject: [PATCH 1776/3753] (CFACT-3) Implement operatingsystemmajrelease fact. Implementing the operatingsystemmajrelease fact for the various Linux distros it is confined to. --- .../facts/linux/operating_system_resolver.hpp | 6 ++++ lib/inc/facts/posix/fact.hpp | 1 + .../facts/posix/operating_system_resolver.hpp | 6 ++++ .../facts/linux/operating_system_resolver.cc | 31 +++++++++++++++++++ .../facts/posix/operating_system_resolver.cc | 1 + 5 files changed, 45 insertions(+) diff --git a/lib/inc/facts/linux/operating_system_resolver.hpp b/lib/inc/facts/linux/operating_system_resolver.hpp index 67ca0d3526..5a1c2f93ed 100644 --- a/lib/inc/facts/linux/operating_system_resolver.hpp +++ b/lib/inc/facts/linux/operating_system_resolver.hpp @@ -24,6 +24,12 @@ namespace cfacter { namespace facts { namespace linux { */ virtual void resolve_operating_system_release(fact_map& facts); + /** + * Called to resolve the operating system major release fact. + * @param facts The fact map that is resolving facts. + */ + virtual void resolve_operating_system_major_release(fact_map& facts); + private: static std::string check_cumulus_linux(); static std::string check_debian_linux(string_value const* dist_id); diff --git a/lib/inc/facts/posix/fact.hpp b/lib/inc/facts/posix/fact.hpp index a9c42d8891..4650695bc0 100644 --- a/lib/inc/facts/posix/fact.hpp +++ b/lib/inc/facts/posix/fact.hpp @@ -15,6 +15,7 @@ namespace cfacter { namespace facts { namespace posix { constexpr static char const* operating_system = "operatingsystem"; constexpr static char const* os_family = "osfamily"; constexpr static char const* operating_system_release = "operatingsystemrelease"; + constexpr static char const* operating_system_major_release = "operatingsystemmajrelease"; }; }}} // namespace cfacter::facts::posix diff --git a/lib/inc/facts/posix/operating_system_resolver.hpp b/lib/inc/facts/posix/operating_system_resolver.hpp index 1339902ac0..7b60c2efa8 100644 --- a/lib/inc/facts/posix/operating_system_resolver.hpp +++ b/lib/inc/facts/posix/operating_system_resolver.hpp @@ -19,6 +19,7 @@ namespace cfacter { namespace facts { namespace posix { fact::operating_system, fact::os_family, fact::operating_system_release, + fact::operating_system_major_release }) { } @@ -44,6 +45,11 @@ namespace cfacter { namespace facts { namespace posix { * @param facts The fact map that is resolving facts. */ virtual void resolve_operating_system_release(fact_map& facts); + /** + * Called to resolve the operating system major release fact. + * @param facts The fact map that is resolving facts. + */ + virtual void resolve_operating_system_major_release(fact_map& facts) {} }; }}} // namespace cfacter::facts::posix diff --git a/lib/src/facts/linux/operating_system_resolver.cc b/lib/src/facts/linux/operating_system_resolver.cc index 468ec101ec..af52918bbf 100644 --- a/lib/src/facts/linux/operating_system_resolver.cc +++ b/lib/src/facts/linux/operating_system_resolver.cc @@ -187,6 +187,37 @@ namespace cfacter { namespace facts { namespace linux { facts.add(posix::fact::operating_system_release, make_value(std::move(value))); } + void operating_system_resolver::resolve_operating_system_major_release(fact_map& facts) { + auto operating_system = facts.get(posix::fact::operating_system); + auto os_release = facts.get(posix::fact::operating_system_release); + + if (!operating_system || + !os_release || !( + operating_system->value() == os::amazon || + operating_system->value() == os::centos || + operating_system->value() == os::cloud_linux || + operating_system->value() == os::debian || + operating_system->value() == os::fedora || + operating_system->value() == os::oracle_enterprise_linux || + operating_system->value() == os::oracle_vm_linux || + operating_system->value() == os::redhat || + operating_system->value() == os::scientific || + operating_system->value() == os::scientific_cern || + operating_system->value() == os::cumulus)) + { + // Use the base implementation + posix::operating_system_resolver::resolve_operating_system_major_release(facts); + return; + } + + string value = os_release->value(); + auto pos = value.find('.'); + if (pos != string::npos) { + value = value.substr(0, pos); + } + facts.add(posix::fact::operating_system_major_release, make_value(std::move(value))); + } + string operating_system_resolver::check_cumulus_linux() { // Check for Cumulus Linux in a generic os-release file diff --git a/lib/src/facts/posix/operating_system_resolver.cc b/lib/src/facts/posix/operating_system_resolver.cc index 8b92be9922..6f1fd70715 100644 --- a/lib/src/facts/posix/operating_system_resolver.cc +++ b/lib/src/facts/posix/operating_system_resolver.cc @@ -16,6 +16,7 @@ namespace cfacter { namespace facts { namespace posix { resolve_operating_system(facts); resolve_os_family(facts); resolve_operating_system_release(facts); + resolve_operating_system_major_release(facts); } void operating_system_resolver::resolve_operating_system(fact_map& facts) From daaddc88f9e9e71165293c968580e4d3a9afb2b7 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Wed, 16 Apr 2014 13:27:19 -0700 Subject: [PATCH 1777/3753] (maint) Make trim_output the default for execute. Currently none of the calls to execute are using the default options and are instead using just trim_output. As this is the most common usage so far, set it as the default execution options. --- lib/inc/execution/execution.hpp | 2 +- lib/src/facts/linux/lsb_resolver.cc | 10 +++++----- lib/src/facts/linux/operating_system_resolver.cc | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/inc/execution/execution.hpp b/lib/inc/execution/execution.hpp index 0cd2ecd392..faa610c3b5 100644 --- a/lib/inc/execution/execution.hpp +++ b/lib/inc/execution/execution.hpp @@ -41,7 +41,7 @@ namespace cfacter { namespace execution { /** * The default execution options. */ - defaults = redirect_stderr | trim_output| throw_on_failure, + defaults = trim_output, }; /** diff --git a/lib/src/facts/linux/lsb_resolver.cc b/lib/src/facts/linux/lsb_resolver.cc index c90b0e57c1..ac2ec981e7 100644 --- a/lib/src/facts/linux/lsb_resolver.cc +++ b/lib/src/facts/linux/lsb_resolver.cc @@ -24,7 +24,7 @@ namespace cfacter { namespace facts { namespace linux { void lsb_resolver::resolve_dist_id(fact_map& facts) { - string value = execute("lsb_release", {"-i", "-s"}, { execution_options::trim_output }); + string value = execute("lsb_release", {"-i", "-s"}); if (value.empty()) { return; } @@ -33,7 +33,7 @@ namespace cfacter { namespace facts { namespace linux { void lsb_resolver::resolve_dist_release(fact_map& facts) { - string value = execute("lsb_release", {"-r", "-s"}, { execution_options::trim_output }); + string value = execute("lsb_release", {"-r", "-s"}); if (value.empty()) { return; } @@ -42,7 +42,7 @@ namespace cfacter { namespace facts { namespace linux { void lsb_resolver::resolve_dist_codename(fact_map& facts) { - string value = execute("lsb_release", {"-c", "-s"}, { execution_options::trim_output }); + string value = execute("lsb_release", {"-c", "-s"}); if (value.empty()) { return; } @@ -51,7 +51,7 @@ namespace cfacter { namespace facts { namespace linux { void lsb_resolver::resolve_dist_description(fact_map& facts) { - string value = execute("lsb_release", {"-d", "-s"}, { execution_options::trim_output }); + string value = execute("lsb_release", {"-d", "-s"}); if (value.empty()) { return; } @@ -80,7 +80,7 @@ namespace cfacter { namespace facts { namespace linux { void lsb_resolver::resolve_release(fact_map& facts) { - string value = execute("lsb_release", {"-v", "-s"}, { execution_options::trim_output }); + string value = execute("lsb_release", {"-v", "-s"}); if (value.empty()) { return; } diff --git a/lib/src/facts/linux/operating_system_resolver.cc b/lib/src/facts/linux/operating_system_resolver.cc index af52918bbf..b8ef4965aa 100644 --- a/lib/src/facts/linux/operating_system_resolver.cc +++ b/lib/src/facts/linux/operating_system_resolver.cc @@ -165,7 +165,7 @@ namespace cfacter { namespace facts { namespace linux { // For VMware ESX, execute the vmware tool if (value.empty() && operating_system->value() == os::vmware_esx) { - string output = execute("vmware", { "-v" }, { execution_options::trim_output }); + string output = execute("vmware", { "-v" }); RE2::PartialMatch(output, "VMware ESX .*?(\\d.*)", &value); } From ed7f8c2897151c599249babfd34b720b230f4068 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Wed, 16 Apr 2014 13:28:14 -0700 Subject: [PATCH 1778/3753] (maint) Use tellp instead of flag for checking if ostringstream is empty. --- lib/src/facts/array_value.cc | 5 +---- lib/src/util/string.cc | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/lib/src/facts/array_value.cc b/lib/src/facts/array_value.cc index b33b668d44..b9f44d25f2 100644 --- a/lib/src/facts/array_value.cc +++ b/lib/src/facts/array_value.cc @@ -11,12 +11,9 @@ namespace cfacter { namespace facts { // Write out the elements in the array result << "["; - bool first = true; for (auto const& element : _elements) { - if (!first) { + if (result.tellp() != 0) { result << ", "; - } else { - first = true; } result << element->to_string(); } diff --git a/lib/src/util/string.cc b/lib/src/util/string.cc index 190d751729..0006da9e69 100644 --- a/lib/src/util/string.cc +++ b/lib/src/util/string.cc @@ -80,12 +80,9 @@ namespace cfacter { namespace util { { ostringstream stream; - bool first = true; for (auto const& str : strings) { - if (!first) { + if (stream.tellp() != 0) { stream << delimiter; - } else { - first = false; } stream << str; } From 8a36caef793d6d361c165ad40ed22cd300375d15 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Wed, 16 Apr 2014 13:29:59 -0700 Subject: [PATCH 1779/3753] (maint) Fix fact_resolver taking initializer list instead of vector r-value reference. There's no need for fact_resolver to take the list of fact names by initialization value since vector is implicitably convertable. This prevents copying from the previously given initialization list. --- lib/inc/facts/fact_resolver.hpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/inc/facts/fact_resolver.hpp b/lib/inc/facts/fact_resolver.hpp index a030b30e61..81c34e79fc 100644 --- a/lib/inc/facts/fact_resolver.hpp +++ b/lib/inc/facts/fact_resolver.hpp @@ -4,7 +4,6 @@ #include #include #include -#include namespace cfacter { namespace facts { @@ -52,8 +51,8 @@ namespace cfacter { namespace facts { * Constructs a fact_resolver. * @param names The fact names the resolver is responsible for. */ - explicit fact_resolver(std::initializer_list const& names) : - _names(names.begin(), names.end()), + explicit fact_resolver(std::vector&& names) : + _names(std::move(names)), _resolving(false) { } From da7c1308a52dff7b13f97f87b2b248b1325b4ea7 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Wed, 16 Apr 2014 13:32:18 -0700 Subject: [PATCH 1780/3753] (maint) Moving fact constants into a single file. There's no reason to split up the fact constants based on platform. This will just cause duplicate later when we have to implement facts on Windows. --- lib/inc/facts/fact.hpp | 31 +++++++++++++++++++ lib/inc/facts/linux/fact.hpp | 22 ------------- lib/inc/facts/linux/lsb_resolver.hpp | 2 +- lib/inc/facts/posix/fact.hpp | 23 -------------- lib/inc/facts/posix/kernel_resolver.hpp | 2 +- .../facts/posix/operating_system_resolver.hpp | 3 +- .../facts/linux/operating_system_resolver.cc | 14 ++++----- 7 files changed, 41 insertions(+), 56 deletions(-) create mode 100644 lib/inc/facts/fact.hpp delete mode 100644 lib/inc/facts/linux/fact.hpp delete mode 100644 lib/inc/facts/posix/fact.hpp diff --git a/lib/inc/facts/fact.hpp b/lib/inc/facts/fact.hpp new file mode 100644 index 0000000000..12757fd8ec --- /dev/null +++ b/lib/inc/facts/fact.hpp @@ -0,0 +1,31 @@ +#ifndef LIB_INC_FACTS_FACT_HPP_ +#define LIB_INC_FACTS_FACT_HPP_ + +namespace cfacter { namespace facts { + + /** + * Stores the constant fact names. + */ + struct fact + { + constexpr static char const* kernel = "kernel"; + constexpr static char const* kernel_version = "kernelversion"; + constexpr static char const* kernel_release = "kernelrelease"; + constexpr static char const* kernel_major_release = "kernelmajrelease"; + constexpr static char const* operating_system = "operatingsystem"; + constexpr static char const* os_family = "osfamily"; + constexpr static char const* operating_system_release = "operatingsystemrelease"; + constexpr static char const* operating_system_major_release = "operatingsystemmajrelease"; + constexpr static char const* hostname = "hostname"; + constexpr static char const* lsb_dist_id = "lsbdistid"; + constexpr static char const* lsb_dist_release = "lsbdistrelease"; + constexpr static char const* lsb_dist_codename = "lsbdistcodename"; + constexpr static char const* lsb_dist_description = "lsbdistdescription"; + constexpr static char const* lsb_dist_major_release = "lsbmajdistrelease"; + constexpr static char const* lsb_dist_minor_release = "lsbminordistrelease"; + constexpr static char const* lsb_release = "lsbrelease"; + }; + +}} // namespace cfacter::facts + +#endif // LIB_INC_FACTS_FACT_HPP_ diff --git a/lib/inc/facts/linux/fact.hpp b/lib/inc/facts/linux/fact.hpp deleted file mode 100644 index 169a89910f..0000000000 --- a/lib/inc/facts/linux/fact.hpp +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef LIB_INC_FACTS_LINUX_FACT_HPP_ -#define LIB_INC_FACTS_LINUX_FACT_HPP_ - -namespace cfacter { namespace facts { namespace linux { - - /** - * Stores the constant fact names. - */ - struct fact - { - constexpr static char const* lsb_dist_id = "lsbdistid"; - constexpr static char const* lsb_dist_release = "lsbdistrelease"; - constexpr static char const* lsb_dist_codename = "lsbdistcodename"; - constexpr static char const* lsb_dist_description = "lsbdistdescription"; - constexpr static char const* lsb_dist_major_release = "lsbmajdistrelease"; - constexpr static char const* lsb_dist_minor_release = "lsbminordistrelease"; - constexpr static char const* lsb_release = "lsbrelease"; - }; - -}}} // namespace cfacter::facts::linux - -#endif // LIB_INC_FACTS_LINUX_FACT_HPP_ diff --git a/lib/inc/facts/linux/lsb_resolver.hpp b/lib/inc/facts/linux/lsb_resolver.hpp index 1778d42593..81df9cec4b 100644 --- a/lib/inc/facts/linux/lsb_resolver.hpp +++ b/lib/inc/facts/linux/lsb_resolver.hpp @@ -2,7 +2,7 @@ #define LIB_INC_FACTS_LINUX_LSB_RESOLVER_HPP_ #include "../fact_resolver.hpp" -#include "fact.hpp" +#include "../fact.hpp" namespace cfacter { namespace facts { namespace linux { diff --git a/lib/inc/facts/posix/fact.hpp b/lib/inc/facts/posix/fact.hpp deleted file mode 100644 index 4650695bc0..0000000000 --- a/lib/inc/facts/posix/fact.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef LIB_INC_FACTS_POSIX_FACT_HPP_ -#define LIB_INC_FACTS_POSIX_FACT_HPP_ - -namespace cfacter { namespace facts { namespace posix { - - /** - * Stores the constant fact names. - */ - struct fact - { - constexpr static char const* kernel = "kernel"; - constexpr static char const* kernel_version = "kernelversion"; - constexpr static char const* kernel_release = "kernelrelease"; - constexpr static char const* kernel_major_release = "kernelmajrelease"; - constexpr static char const* operating_system = "operatingsystem"; - constexpr static char const* os_family = "osfamily"; - constexpr static char const* operating_system_release = "operatingsystemrelease"; - constexpr static char const* operating_system_major_release = "operatingsystemmajrelease"; - }; - -}}} // namespace cfacter::facts::posix - -#endif // LIB_INC_FACTS_POSIX_FACT_HPP_ diff --git a/lib/inc/facts/posix/kernel_resolver.hpp b/lib/inc/facts/posix/kernel_resolver.hpp index 40c2113352..489638cb14 100644 --- a/lib/inc/facts/posix/kernel_resolver.hpp +++ b/lib/inc/facts/posix/kernel_resolver.hpp @@ -2,7 +2,7 @@ #define LIB_INC_FACTS_POSIX_KERNEL_RESOLVER_HPP_ #include "../fact_resolver.hpp" -#include "fact.hpp" +#include "../fact.hpp" #include namespace cfacter { namespace facts { namespace posix { diff --git a/lib/inc/facts/posix/operating_system_resolver.hpp b/lib/inc/facts/posix/operating_system_resolver.hpp index 7b60c2efa8..f2b3326e9b 100644 --- a/lib/inc/facts/posix/operating_system_resolver.hpp +++ b/lib/inc/facts/posix/operating_system_resolver.hpp @@ -2,7 +2,7 @@ #define LIB_INC_FACTS_POSIX_OPERATING_SYSTEM_RESOLVER_HPP_ #include "../fact_resolver.hpp" -#include "fact.hpp" +#include "../fact.hpp" namespace cfacter { namespace facts { namespace posix { @@ -55,4 +55,3 @@ namespace cfacter { namespace facts { namespace posix { }}} // namespace cfacter::facts::posix #endif // LIB_INC_FACTS_POSIX_OPERATING_SYSTEM_RESOLVER_HPP_ - diff --git a/lib/src/facts/linux/operating_system_resolver.cc b/lib/src/facts/linux/operating_system_resolver.cc index b8ef4965aa..6b73b91f0a 100644 --- a/lib/src/facts/linux/operating_system_resolver.cc +++ b/lib/src/facts/linux/operating_system_resolver.cc @@ -56,12 +56,12 @@ namespace cfacter { namespace facts { namespace linux { } // Add the fact - facts.add(posix::fact::operating_system, make_value(value)); + facts.add(fact::operating_system, make_value(value)); } void operating_system_resolver::resolve_operating_system_release(fact_map& facts) { - auto operating_system = facts.get(posix::fact::operating_system); + auto operating_system = facts.get(fact::operating_system); if (!operating_system) { // Use the base implementation posix::operating_system_resolver::resolve_operating_system_release(facts); @@ -173,7 +173,7 @@ namespace cfacter { namespace facts { namespace linux { if (value.empty() && operating_system->value() == os::amazon) { auto release = facts.get(fact::lsb_dist_release); if (release) { - facts.add(posix::fact::operating_system_release, make_value(release->value())); + facts.add(fact::operating_system_release, make_value(release->value())); return; } } @@ -184,12 +184,12 @@ namespace cfacter { namespace facts { namespace linux { return; } - facts.add(posix::fact::operating_system_release, make_value(std::move(value))); + facts.add(fact::operating_system_release, make_value(std::move(value))); } void operating_system_resolver::resolve_operating_system_major_release(fact_map& facts) { - auto operating_system = facts.get(posix::fact::operating_system); - auto os_release = facts.get(posix::fact::operating_system_release); + auto operating_system = facts.get(fact::operating_system); + auto os_release = facts.get(fact::operating_system_release); if (!operating_system || !os_release || !( @@ -215,7 +215,7 @@ namespace cfacter { namespace facts { namespace linux { if (pos != string::npos) { value = value.substr(0, pos); } - facts.add(posix::fact::operating_system_major_release, make_value(std::move(value))); + facts.add(fact::operating_system_major_release, make_value(std::move(value))); } string operating_system_resolver::check_cumulus_linux() From b9e5e6d062db52c6b9e5d715cee858e41b151fa0 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Wed, 16 Apr 2014 15:23:40 -0700 Subject: [PATCH 1781/3753] (maint) Don't evaluate ec2_metadata fact when loading for tests --- spec/unit/ec2_spec.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/unit/ec2_spec.rb b/spec/unit/ec2_spec.rb index bf2aa2f5ea..109c9700e8 100755 --- a/spec/unit/ec2_spec.rb +++ b/spec/unit/ec2_spec.rb @@ -7,11 +7,14 @@ before do Facter::EC2::Metadata.stubs(:new).returns querier Facter.collection.internal_loader.load(:ec2) + # Prevent flattened facts from forcing evaluation of the ec2 metadata fact + Facter.stubs(:value).with(:ec2_metadata) end subject { Facter.fact(:ec2_metadata).resolution(:rest) } it "is unsuitable if the virtual fact is not xen" do + querier.stubs(:reachable?).returns false Facter.fact(:virtual).stubs(:value).returns "kvm" expect(subject).to_not be_suitable end @@ -57,6 +60,7 @@ subject { Facter.fact(:ec2_userdata).resolution(:rest) } it "is unsuitable if the virtual fact is not xen" do + querier.stubs(:reachable?).returns(true) Facter.fact(:virtual).stubs(:value).returns "kvm" expect(subject).to_not be_suitable end From a417998966928c55dd0ed104608b7ac4311c3a62 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Wed, 16 Apr 2014 17:23:25 -0700 Subject: [PATCH 1782/3753] (maint) Ensure specs don't generate flattened ec2 facts --- spec/unit/ec2_spec.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/spec/unit/ec2_spec.rb b/spec/unit/ec2_spec.rb index 109c9700e8..28ad20a536 100755 --- a/spec/unit/ec2_spec.rb +++ b/spec/unit/ec2_spec.rb @@ -6,9 +6,11 @@ before do Facter::EC2::Metadata.stubs(:new).returns querier - Facter.collection.internal_loader.load(:ec2) + # Prevent flattened facts from forcing evaluation of the ec2 metadata fact Facter.stubs(:value).with(:ec2_metadata) + Facter.collection.internal_loader.load(:ec2) + Facter.unstub(:value) end subject { Facter.fact(:ec2_metadata).resolution(:rest) } @@ -54,7 +56,11 @@ before do Facter::EC2::Userdata.stubs(:new).returns querier + + # Prevent flattened facts from forcing evaluation of the ec2 metadata fact + Facter.stubs(:value).with(:ec2_metadata) Facter.collection.internal_loader.load(:ec2) + Facter.unstub(:value) end subject { Facter.fact(:ec2_userdata).resolution(:rest) } From 14ebdfa14452e94f4dd32fbbcad5d7c0a900a469 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Thu, 17 Apr 2014 14:10:40 -0700 Subject: [PATCH 1783/3753] (maint) Fix cpplint detection of C-style casts. cpplint was making the assumption that std::function would always be prefixed by the namespace. This caused function to be interpreted as a C-style cast which generated an error. The fix is to remove the std:: prefix; just "function<...>" is enough. --- ext/cpplint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/cpplint.py b/ext/cpplint.py index b51c3433e5..34cc8307e1 100755 --- a/ext/cpplint.py +++ b/ext/cpplint.py @@ -3784,7 +3784,7 @@ def CheckLanguage(filename, clean_lines, linenum, file_extension, if (matched_new is None and # If new operator, then this isn't a cast not (Match(r'^\s*MOCK_(CONST_)?METHOD\d+(_T)?\(', line) or Search(r'\bMockCallback<.*>', line) or - Search(r'\bstd::function<.*>', line)) and + Search(r'\bfunction<.*>', line)) and not (matched_funcptr and Match(r'\((?:[^() ]+::\s*\*\s*)?[^() ]+\)\s*\(', matched_funcptr))): From 06ba8434c724f9129c6b13714bbd4f18d67d69e5 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Thu, 17 Apr 2014 14:12:40 -0700 Subject: [PATCH 1784/3753] (maint) Fix invalid use of new when throwing exception. Exceptions should never be thrown using new (I'm looking at you, cpplint!). --- lib/src/execution/posix/execution.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/execution/posix/execution.cc b/lib/src/execution/posix/execution.cc index c64ce61741..f145bcb698 100644 --- a/lib/src/execution/posix/execution.cc +++ b/lib/src/execution/posix/execution.cc @@ -87,7 +87,7 @@ namespace cfacter { namespace execution { { count = read(stdout_read, buffer, sizeof(buffer)); if (count < 0) { - throw new execution_exception("failed to read child output."); + throw execution_exception("failed to read child output."); } output.write(buffer, count); } From 5e5a93ba361a2c48b1722e6a5bab2483219c1c23 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Thu, 17 Apr 2014 14:19:33 -0700 Subject: [PATCH 1785/3753] (maint) Clean up code. - Fix redundant usages of specifying fully-qualified std namespace. - Fix string_value constructor to take r-value reference; this prevents unintended moves. - Fix redundant returning of string() when the C++11 initializer syntax is sufficient. - Fix missing virtual specified on virtual function; C++ doesn't require it, but I prefer it so that you can tell the function is virtual at a glance. --- lib/inc/facts/linux/lsb_resolver.hpp | 2 +- lib/inc/facts/string_value.hpp | 2 +- lib/src/facts/linux/lsb_resolver.cc | 14 +++++++------- lib/src/facts/linux/operating_system_resolver.cc | 16 ++++++++-------- lib/src/facts/posix/kernel_resolver.cc | 8 ++++---- lib/src/facts/posix/operating_system_resolver.cc | 4 ++-- lib/src/util/posix/file.cc | 4 ++-- lib/src/util/string.cc | 4 ++-- 8 files changed, 27 insertions(+), 27 deletions(-) diff --git a/lib/inc/facts/linux/lsb_resolver.hpp b/lib/inc/facts/linux/lsb_resolver.hpp index 81df9cec4b..ba016f116d 100644 --- a/lib/inc/facts/linux/lsb_resolver.hpp +++ b/lib/inc/facts/linux/lsb_resolver.hpp @@ -32,7 +32,7 @@ namespace cfacter { namespace facts { namespace linux { * Called to resolve all facts the resolver is responsible for. * @param facts The fact map that is resolving facts. */ - void resolve_facts(fact_map& facts); + virtual void resolve_facts(fact_map& facts); /** * Called to resolve the LSB dist id fact. diff --git a/lib/inc/facts/string_value.hpp b/lib/inc/facts/string_value.hpp index fc7692264e..0d3df5e2c3 100644 --- a/lib/inc/facts/string_value.hpp +++ b/lib/inc/facts/string_value.hpp @@ -14,7 +14,7 @@ namespace cfacter { namespace facts { * Constructs a string_value. * @param value The string value. */ - explicit string_value(std::string& value) : + explicit string_value(std::string&& value) : _value(std::move(value)) { } diff --git a/lib/src/facts/linux/lsb_resolver.cc b/lib/src/facts/linux/lsb_resolver.cc index ac2ec981e7..99890b81b7 100644 --- a/lib/src/facts/linux/lsb_resolver.cc +++ b/lib/src/facts/linux/lsb_resolver.cc @@ -28,7 +28,7 @@ namespace cfacter { namespace facts { namespace linux { if (value.empty()) { return; } - facts.add(fact::lsb_dist_id, make_value(std::move(value))); + facts.add(fact::lsb_dist_id, make_value(move(value))); } void lsb_resolver::resolve_dist_release(fact_map& facts) @@ -37,7 +37,7 @@ namespace cfacter { namespace facts { namespace linux { if (value.empty()) { return; } - facts.add(fact::lsb_dist_release, make_value(std::move(value))); + facts.add(fact::lsb_dist_release, make_value(move(value))); } void lsb_resolver::resolve_dist_codename(fact_map& facts) @@ -46,7 +46,7 @@ namespace cfacter { namespace facts { namespace linux { if (value.empty()) { return; } - facts.add(fact::lsb_dist_codename, make_value(std::move(value))); + facts.add(fact::lsb_dist_codename, make_value(move(value))); } void lsb_resolver::resolve_dist_description(fact_map& facts) @@ -57,7 +57,7 @@ namespace cfacter { namespace facts { namespace linux { } // The value may be quoted; trim the quotes - facts.add(fact::lsb_dist_description, make_value(std::move(trim(value, { '\"' })))); + facts.add(fact::lsb_dist_description, make_value(move(trim(value, { '\"' })))); } void lsb_resolver::resolve_dist_version(fact_map& facts) @@ -71,10 +71,10 @@ namespace cfacter { namespace facts { namespace linux { if (!RE2::PartialMatch(dist_release->value(), "(\\d+)\\.(\\d*)", &major, &minor)) { major = dist_release->value(); } - facts.add(fact::lsb_dist_major_release, make_value(std::move(major))); + facts.add(fact::lsb_dist_major_release, make_value(move(major))); if (!minor.empty()) { - facts.add(fact::lsb_dist_minor_release, make_value(std::move(minor))); + facts.add(fact::lsb_dist_minor_release, make_value(move(minor))); } } @@ -84,7 +84,7 @@ namespace cfacter { namespace facts { namespace linux { if (value.empty()) { return; } - facts.add(fact::lsb_release, make_value(std::move(value))); + facts.add(fact::lsb_release, make_value(move(value))); } }}} // namespace cfacter::facts::linux diff --git a/lib/src/facts/linux/operating_system_resolver.cc b/lib/src/facts/linux/operating_system_resolver.cc index 6b73b91f0a..f9c1a5c877 100644 --- a/lib/src/facts/linux/operating_system_resolver.cc +++ b/lib/src/facts/linux/operating_system_resolver.cc @@ -184,7 +184,7 @@ namespace cfacter { namespace facts { namespace linux { return; } - facts.add(fact::operating_system_release, make_value(std::move(value))); + facts.add(fact::operating_system_release, make_value(move(value))); } void operating_system_resolver::resolve_operating_system_major_release(fact_map& facts) { @@ -215,7 +215,7 @@ namespace cfacter { namespace facts { namespace linux { if (pos != string::npos) { value = value.substr(0, pos); } - facts.add(fact::operating_system_major_release, make_value(std::move(value))); + facts.add(fact::operating_system_major_release, make_value(move(value))); } string operating_system_resolver::check_cumulus_linux() @@ -231,7 +231,7 @@ namespace cfacter { namespace facts { namespace linux { } } } - return string(); + return {}; } string operating_system_resolver::check_debian_linux(string_value const* dist_id) @@ -245,7 +245,7 @@ namespace cfacter { namespace facts { namespace linux { } return os::debian; } - return string(); + return {}; } string operating_system_resolver::check_oracle_linux() @@ -256,7 +256,7 @@ namespace cfacter { namespace facts { namespace linux { } return os::oracle_enterprise_linux; } - return string(); + return {}; } string operating_system_resolver::check_redhat_linux() @@ -282,7 +282,7 @@ namespace cfacter { namespace facts { namespace linux { } return os::redhat; } - return string(); + return {}; } string operating_system_resolver::check_suse_linux() @@ -302,7 +302,7 @@ namespace cfacter { namespace facts { namespace linux { } return os::suse; } - return string(); + return {}; } string operating_system_resolver::check_other_linux() @@ -329,7 +329,7 @@ namespace cfacter { namespace facts { namespace linux { return kvp.second; } } - return string(); + return {}; } }}} // namespace cfacter::facts::linux diff --git a/lib/src/facts/posix/kernel_resolver.cc b/lib/src/facts/posix/kernel_resolver.cc index 7f90cfebff..1e337a2c7f 100644 --- a/lib/src/facts/posix/kernel_resolver.cc +++ b/lib/src/facts/posix/kernel_resolver.cc @@ -28,7 +28,7 @@ namespace cfacter { namespace facts { namespace posix { if (value.empty()) { return; } - facts.add(fact::kernel, make_value(std::move(value))); + facts.add(fact::kernel, make_value(move(value))); } void kernel_resolver::resolve_kernel_release(fact_map& facts, utsname const& name) @@ -37,7 +37,7 @@ namespace cfacter { namespace facts { namespace posix { if (value.empty()) { return; } - facts.add(fact::kernel_release, make_value(std::move(value))); + facts.add(fact::kernel_release, make_value(move(value))); } void kernel_resolver::resolve_kernel_version(fact_map& facts) @@ -52,7 +52,7 @@ namespace cfacter { namespace facts { namespace posix { if (pos != string::npos) { value = value.substr(0, pos); } - facts.add(fact::kernel_version, make_value(std::move(value))); + facts.add(fact::kernel_version, make_value(move(value))); } void kernel_resolver::resolve_kernel_major_version(fact_map& facts) @@ -71,7 +71,7 @@ namespace cfacter { namespace facts { namespace posix { value = value.substr(0, pos); } } - facts.add(fact::kernel_major_release, make_value(std::move(value))); + facts.add(fact::kernel_major_release, make_value(move(value))); } }}} // namespace cfacter::facts::posix diff --git a/lib/src/facts/posix/operating_system_resolver.cc b/lib/src/facts/posix/operating_system_resolver.cc index 6f1fd70715..39c8194bf3 100644 --- a/lib/src/facts/posix/operating_system_resolver.cc +++ b/lib/src/facts/posix/operating_system_resolver.cc @@ -36,7 +36,7 @@ namespace cfacter { namespace facts { namespace posix { auto os = facts.get(fact::operating_system); string value; if (os) { - static std::map systems = { + static map systems = { { string(os::redhat), string(os_family::redhat) }, { string(os::fedora), string(os_family::redhat) }, { string(os::centos), string(os_family::redhat) }, @@ -82,7 +82,7 @@ namespace cfacter { namespace facts { namespace posix { } value = kernel->value(); } - facts.add(fact::os_family, make_value(std::move(value))); + facts.add(fact::os_family, make_value(move(value))); } void operating_system_resolver::resolve_operating_system_release(fact_map& facts) diff --git a/lib/src/util/posix/file.cc b/lib/src/util/posix/file.cc index d040883103..acf005ea2b 100644 --- a/lib/src/util/posix/file.cc +++ b/lib/src/util/posix/file.cc @@ -20,7 +20,7 @@ namespace cfacter { namespace util { // TODO: this is standard-compliant, so it should shared between POSIX and Windows string file::read(string const& path) { - ifstream in(path, std::ios::in | std::ios::binary); + ifstream in(path, ios::in | ios::binary); ostringstream contents; if (in) { contents << in.rdbuf(); @@ -36,7 +36,7 @@ namespace cfacter { namespace util { if (getline(in, value)) { return value; } - return string(); + return {}; } }} // namespace cfacter::util diff --git a/lib/src/util/string.cc b/lib/src/util/string.cc index 0006da9e69..bddefcefb4 100644 --- a/lib/src/util/string.cc +++ b/lib/src/util/string.cc @@ -45,7 +45,7 @@ namespace cfacter { namespace util { return rtrim(str, set); } - string& trim(std::string& str, initializer_list const& set) + string& trim(string& str, initializer_list const& set) { return ltrim(rtrim(str, set), set); } @@ -71,7 +71,7 @@ namespace cfacter { namespace util { if (part.empty()) { continue; } - parts.push_back(std::move(part)); + parts.push_back(move(part)); } return parts; } From 4087ccd39f20a874c880db3b9d0270bf7caa7a60 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Fri, 18 Apr 2014 15:34:55 -0700 Subject: [PATCH 1786/3753] (maint) Fix fact resolution cycle in Linux distro detection. A resolver should query for facts that it is currently resolving without performing a resolution. --- lib/src/facts/linux/lsb_resolver.cc | 2 +- lib/src/facts/linux/operating_system_resolver.cc | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/src/facts/linux/lsb_resolver.cc b/lib/src/facts/linux/lsb_resolver.cc index 99890b81b7..218d44b9ad 100644 --- a/lib/src/facts/linux/lsb_resolver.cc +++ b/lib/src/facts/linux/lsb_resolver.cc @@ -62,7 +62,7 @@ namespace cfacter { namespace facts { namespace linux { void lsb_resolver::resolve_dist_version(fact_map& facts) { - auto dist_release = facts.get(fact::lsb_dist_release); + auto dist_release = facts.get(fact::lsb_dist_release, false); if (!dist_release) { return; } diff --git a/lib/src/facts/linux/operating_system_resolver.cc b/lib/src/facts/linux/operating_system_resolver.cc index f9c1a5c877..888289e80b 100644 --- a/lib/src/facts/linux/operating_system_resolver.cc +++ b/lib/src/facts/linux/operating_system_resolver.cc @@ -61,7 +61,7 @@ namespace cfacter { namespace facts { namespace linux { void operating_system_resolver::resolve_operating_system_release(fact_map& facts) { - auto operating_system = facts.get(fact::operating_system); + auto operating_system = facts.get(fact::operating_system, false); if (!operating_system) { // Use the base implementation posix::operating_system_resolver::resolve_operating_system_release(facts); @@ -188,8 +188,8 @@ namespace cfacter { namespace facts { namespace linux { } void operating_system_resolver::resolve_operating_system_major_release(fact_map& facts) { - auto operating_system = facts.get(fact::operating_system); - auto os_release = facts.get(fact::operating_system_release); + auto operating_system = facts.get(fact::operating_system, false); + auto os_release = facts.get(fact::operating_system_release, false); if (!operating_system || !os_release || !( From e73d132f7069107dea5a2128fa7d7fe33fc95f20 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Fri, 18 Apr 2014 15:36:10 -0700 Subject: [PATCH 1787/3753] (maint) Clean up standard header uses. Remove include of iostream. Add cctype/cstring where appropriate. --- lib/inc/util/option_set.hpp | 1 - lib/inc/util/string.hpp | 12 +++++------- lib/src/facts/posix/kernel_resolver.cc | 1 + lib/src/util/string.cc | 1 + 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/inc/util/option_set.hpp b/lib/inc/util/option_set.hpp index eb0b176618..0b13d73800 100644 --- a/lib/inc/util/option_set.hpp +++ b/lib/inc/util/option_set.hpp @@ -1,7 +1,6 @@ #ifndef LIB_INC_UTIL_OPTION_SET_HPP_ #define LIB_INC_UTIL_OPTION_SET_HPP_ -#include #include #include #include diff --git a/lib/inc/util/string.hpp b/lib/inc/util/string.hpp index da6deb10d3..c7473fee16 100644 --- a/lib/inc/util/string.hpp +++ b/lib/inc/util/string.hpp @@ -5,9 +5,7 @@ #include #include #include - -// TODO: non-standard header -#include "string.h" +#include namespace cfacter { namespace util { @@ -132,24 +130,24 @@ namespace cfacter { namespace util { { static bool eq(char c1, char c2) { - return ::toupper(c1) == ::toupper(c2); + return std::toupper(c1) == std::toupper(c2); } static bool ne(char c1, char c2) { - return ::toupper(c1) != ::toupper(c2); + return std::toupper(c1) != std::toupper(c2); } static bool lt(char c1, char c2) { - return ::toupper(c1) < ::toupper(c2); + return std::toupper(c1) < std::toupper(c2); } static int compare(char const* s1, char const* s2, size_t n); static char const* find(char const* s, int n, char a) { - while (n-- > 0 && ::toupper(*s) != ::toupper(a)) { + while (n-- > 0 && std::toupper(*s) != std::toupper(a)) { ++s; } return s; diff --git a/lib/src/facts/posix/kernel_resolver.cc b/lib/src/facts/posix/kernel_resolver.cc index 1e337a2c7f..7ace3e1509 100644 --- a/lib/src/facts/posix/kernel_resolver.cc +++ b/lib/src/facts/posix/kernel_resolver.cc @@ -2,6 +2,7 @@ #include #include #include +#include using namespace std; using namespace cfacter::util; diff --git a/lib/src/util/string.cc b/lib/src/util/string.cc index bddefcefb4..8f53837292 100644 --- a/lib/src/util/string.cc +++ b/lib/src/util/string.cc @@ -2,6 +2,7 @@ #include #include #include +#include using namespace std; From 392f43ce9d23c5ddf7dc4db52393ffb83e1f6005 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Fri, 18 Apr 2014 15:36:45 -0700 Subject: [PATCH 1788/3753] (CFACT-22) Implement network facts for Linux. Implementing network facts for Linux and OSX (and probably the BSDs too). This also includes a refactoring to fact_map and fact_resolver to support matching a resolver by a regular expression. The following facts were implemented for Linux and OSX: - hostname - ipaddress - ipaddress_ - ipaddress6 - ipaddress6_ - netmask - netmask_ - netmask6 - netmask6_ - network - network_ - network6 - network6_ - macaddress - macaddress_ - mtu_ - interfaces --- lib/CMakeLists.txt | 5 + lib/inc/facts/bsd/networking_resolver.hpp | 29 ++++ lib/inc/facts/fact.hpp | 9 + lib/inc/facts/fact_map.hpp | 57 +++---- lib/inc/facts/fact_resolver.hpp | 36 +++- lib/inc/facts/linux/networking_resolver.hpp | 39 +++++ lib/inc/facts/osx/networking_resolver.hpp | 45 +++++ lib/inc/facts/posix/networking_resolver.hpp | 95 +++++++++++ lib/inc/util/bsd/scoped_ifaddrs.hpp | 34 ++++ lib/src/facts/bsd/networking_resolver.cc | 173 ++++++++++++++++++++ lib/src/facts/fact_map.cc | 109 ++++++++---- lib/src/facts/fact_resolver.cc | 30 ++++ lib/src/facts/linux/networking_resolver.cc | 49 ++++++ lib/src/facts/linux/platform.cc | 8 +- lib/src/facts/osx/networking_resolver.cc | 55 +++++++ lib/src/facts/osx/platform.cc | 6 +- lib/src/facts/posix/networking_resolver.cc | 113 +++++++++++++ 17 files changed, 812 insertions(+), 80 deletions(-) create mode 100644 lib/inc/facts/bsd/networking_resolver.hpp create mode 100644 lib/inc/facts/linux/networking_resolver.hpp create mode 100644 lib/inc/facts/osx/networking_resolver.hpp create mode 100644 lib/inc/facts/posix/networking_resolver.hpp create mode 100644 lib/inc/util/bsd/scoped_ifaddrs.hpp create mode 100644 lib/src/facts/bsd/networking_resolver.cc create mode 100644 lib/src/facts/linux/networking_resolver.cc create mode 100644 lib/src/facts/osx/networking_resolver.cc create mode 100644 lib/src/facts/posix/networking_resolver.cc diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index b79c392e8e..8f8c0afe92 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -27,6 +27,7 @@ if (UNIX) set(LIBCFACTER_POSIX_SOURCES "${CMAKE_CURRENT_LIST_DIR}/src/execution/posix/execution.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/posix/kernel_resolver.cc" + "${CMAKE_CURRENT_LIST_DIR}/src/facts/posix/networking_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/posix/operating_system_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/util/posix/file.cc" ) @@ -35,11 +36,15 @@ endif() # Set the platform-specific sources if ("${CMAKE_SYSTEM_NAME}" MATCHES "Darwin") set(LIBCFACTER_PLATFORM_SOURCES + "${CMAKE_CURRENT_LIST_DIR}/src/facts/bsd/networking_resolver.cc" + "${CMAKE_CURRENT_LIST_DIR}/src/facts/osx/networking_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/osx/platform.cc" ) elseif ("${CMAKE_SYSTEM_NAME}" MATCHES "Linux") set(LIBCFACTER_PLATFORM_SOURCES + "${CMAKE_CURRENT_LIST_DIR}/src/facts/bsd/networking_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/linux/lsb_resolver.cc" + "${CMAKE_CURRENT_LIST_DIR}/src/facts/linux/networking_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/linux/operating_system_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/linux/platform.cc" ) diff --git a/lib/inc/facts/bsd/networking_resolver.hpp b/lib/inc/facts/bsd/networking_resolver.hpp new file mode 100644 index 0000000000..02393251e6 --- /dev/null +++ b/lib/inc/facts/bsd/networking_resolver.hpp @@ -0,0 +1,29 @@ +#ifndef LIB_INC_FACTS_BSD_NETWORKING_RESOLVER_HPP_ +#define LIB_INC_FACTS_BSD_NETWORKING_RESOLVER_HPP_ + +#include "../posix/networking_resolver.hpp" +#include + +namespace cfacter { namespace facts { namespace bsd { + + /** + * Responsible for resolving networking facts. + */ + struct networking_resolver : posix::networking_resolver + { + protected: + /** + * Called to resolve interface facts. + * @param facts The fact map that is resolving facts. + */ + virtual void resolve_interface_facts(fact_map& facts); + + private: + void resolve_address(fact_map& facts, ifaddrs const* addr, bool primary); + void resolve_network(fact_map& facts, ifaddrs const* addr, bool primary); + void resolve_mtu(fact_map& facts, ifaddrs const* addr); + }; + +}}} // namespace cfacter::facts::bsd + +#endif // LIB_INC_FACTS_BSD_NETWORKING_RESOLVER_HPP_ diff --git a/lib/inc/facts/fact.hpp b/lib/inc/facts/fact.hpp index 12757fd8ec..940428c4c6 100644 --- a/lib/inc/facts/fact.hpp +++ b/lib/inc/facts/fact.hpp @@ -24,6 +24,15 @@ namespace cfacter { namespace facts { constexpr static char const* lsb_dist_major_release = "lsbmajdistrelease"; constexpr static char const* lsb_dist_minor_release = "lsbminordistrelease"; constexpr static char const* lsb_release = "lsbrelease"; + constexpr static char const* ipaddress = "ipaddress"; + constexpr static char const* ipaddress6 = "ipaddress6"; + constexpr static char const* mtu = "mtu"; + constexpr static char const* netmask = "netmask"; + constexpr static char const* netmask6 = "netmask6"; + constexpr static char const* network = "network"; + constexpr static char const* network6 = "network6"; + constexpr static char const* macaddress = "macaddress"; + constexpr static char const* interfaces = "interfaces"; }; }} // namespace cfacter::facts diff --git a/lib/inc/facts/fact_map.hpp b/lib/inc/facts/fact_map.hpp index cd27e8bf43..e45ec9ee4f 100644 --- a/lib/inc/facts/fact_map.hpp +++ b/lib/inc/facts/fact_map.hpp @@ -1,7 +1,7 @@ #ifndef LIB_INC_FACTS_FACT_MAP_HPP_ #define LIB_INC_FACTS_FACT_MAP_HPP_ -#include +#include #include #include #include @@ -11,20 +11,6 @@ namespace cfacter { namespace facts { - struct fact_map; - - /** - * Called to populate common facts. - * @param facts The fact map being populated. - */ - void populate_common_facts(fact_map& facts); - - /** - * Called to populate platform-specific facts. - * @param facts The fact map being populated. - */ - void populate_platform_facts(fact_map& facts); - /** * Thrown when a fact already exists in the map. */ @@ -58,22 +44,11 @@ namespace cfacter { namespace facts { typedef std::map> resolver_map_type; /** - * Adds a fact resolver to the fact map. - * @tparam T The fact resolver type to add to the map. Must be default-constructable. - */ - template - void add_resolver() - { - std::shared_ptr resolver(new T()); - - for (auto const& fact_name : resolver->names()) { - auto const& it = _resolvers.lower_bound(fact_name); - if (it != _resolvers.end() && !(_resolvers.key_comp()(fact_name, it->first))) { - throw resolver_exists_exception("a resolver for fact " + fact_name + " already exists."); - } - _resolvers.insert(it, make_pair(fact_name, resolver)); - } - } + * Adds a resolver to the fact map. + * @param resolver The resolver to add to the map. + */ + void add(std::shared_ptr const& resolver); + /** * Adds a fact to the map. @@ -82,6 +57,12 @@ namespace cfacter { namespace facts { */ void add(std::string&& name, std::unique_ptr&& value); + /** + * Removes a resolver from the fact map. + * @param resolver The resolver to remove from the map. + */ + void remove(std::shared_ptr const& resolver); + /** * Removes a fact by name. * @param name The name of the fact to remove. @@ -101,13 +82,15 @@ namespace cfacter { namespace facts { /** * Gets a fact value by name. + * @tparam T The expected type of the value. * @param name The name of the fact to get the value of. + * @param resolve True if resolution should take place or false if not. * @return Returns a pointer to the fact value or nullptr if the fact is not in the map or the value is not the expected type. */ template - T const* get(std::string const& name) + T const* get(std::string const& name, bool resolve = true) { - return dynamic_cast(get_value(name)); + return dynamic_cast(get_value(name, resolve)); } /** @@ -124,14 +107,16 @@ namespace cfacter { namespace facts { private: fact_map() {} - void load(); + void load_facts(); void resolve_facts(); - value const* get_value(std::string const& name); + std::shared_ptr find_resolver(std::string const& name); + value const* get_value(std::string const& name, bool resolve); static fact_map _instance; fact_map_type _facts; - resolver_map_type _resolvers; + std::list> _resolvers; + resolver_map_type _resolver_map; }; }} // namespace cfacter::facts diff --git a/lib/inc/facts/fact_resolver.hpp b/lib/inc/facts/fact_resolver.hpp index 81c34e79fc..a1208ad551 100644 --- a/lib/inc/facts/fact_resolver.hpp +++ b/lib/inc/facts/fact_resolver.hpp @@ -5,6 +5,11 @@ #include #include +// Forward declare RE2 so users of this header don't have to include re2 +namespace re2 { + class RE2; +} + namespace cfacter { namespace facts { /** @@ -19,6 +24,18 @@ namespace cfacter { namespace facts { explicit circular_resolution_exception(std::string const& message) : std::runtime_error(message) {} }; + /** + * Thrown when a resolver is constructed with an invalid fact name pattern. + */ + struct invalid_name_pattern_exception : std::runtime_error + { + /** + * Constructs a invalid_name_pattern_exception. + * @param message The exception message. + */ + explicit invalid_name_pattern_exception(std::string const& message) : std::runtime_error(message) {} + }; + /** * Utility type for managing resolution cycles. */ @@ -51,16 +68,12 @@ namespace cfacter { namespace facts { * Constructs a fact_resolver. * @param names The fact names the resolver is responsible for. */ - explicit fact_resolver(std::vector&& names) : - _names(std::move(names)), - _resolving(false) - { - } + fact_resolver(std::vector&& names, std::vector const& patterns = {}); /** * Destructs the fact_resolver. */ - virtual ~fact_resolver() {} + virtual ~fact_resolver(); // Force non-copyable fact_resolver(fact_resolver const&) = delete; @@ -74,7 +87,7 @@ namespace cfacter { namespace facts { * Gets the fact names the resolver is responsible for resolving. * @return Returns a vector of fact names. */ - std::vector const& names() { return _names; } + std::vector const& names() const { return _names; } /** * Called to resolve all facts the resolver is responsible for. @@ -82,6 +95,14 @@ namespace cfacter { namespace facts { */ void resolve(fact_map& facts); + /** + * Checks whether or not the resolver can resolve the given fact name. + * Note that it is not required to return true if the given name is in the names vector. + * @param name The fact name to check. + * @return Returns true if the resolver can resolve the given name or false if it cannot. + */ + bool can_resolve(std::string const& name) const; + protected: /** * Called to resolve all facts the resolver is responsible for. @@ -91,6 +112,7 @@ namespace cfacter { namespace facts { private: std::vector _names; + std::vector> _regexes; bool _resolving; }; diff --git a/lib/inc/facts/linux/networking_resolver.hpp b/lib/inc/facts/linux/networking_resolver.hpp new file mode 100644 index 0000000000..5bef06d896 --- /dev/null +++ b/lib/inc/facts/linux/networking_resolver.hpp @@ -0,0 +1,39 @@ +#ifndef LIB_INC_FACTS_LINUX_NETWORKING_RESOLVER_HPP_ +#define LIB_INC_FACTS_LINUX_NETWORKING_RESOLVER_HPP_ + +#include "../bsd/networking_resolver.hpp" + +namespace cfacter { namespace facts { namespace linux { + + /** + * Responsible for resolving networking facts. + */ + struct networking_resolver : bsd::networking_resolver + { + protected: + /** + * Determines if the given sock address is a link layer address. + * @param addr The socket address to check. + * @returns Returns true if the socket address is a link layer address or false if it is not. + */ + virtual bool is_link_address(sockaddr const* addr) const; + + /** + * Gets the bytes of the link address. + * @param addr The socket address representing the link address. + * @return Returns a pointer to the address bytes or nullptr if not a link address. + */ + virtual uint8_t const* get_link_address_bytes(sockaddr const* addr) const; + + /** + * Gets the MTU of the link layer data. + * @param interface The name of the link layer interface. + * @param data The data pointer from the link layer interface. + * @return Returns The MTU of the interface or -1 if there's no MTU. + */ + virtual int get_link_mtu(std::string const& interface, void* data) const; + }; + +}}} // namespace cfacter::facts::linux + +#endif // LIB_INC_FACTS_LINUX_NETWORKING_RESOLVER_HPP_ diff --git a/lib/inc/facts/osx/networking_resolver.hpp b/lib/inc/facts/osx/networking_resolver.hpp new file mode 100644 index 0000000000..b2bc9c74de --- /dev/null +++ b/lib/inc/facts/osx/networking_resolver.hpp @@ -0,0 +1,45 @@ +#ifndef LIB_INC_FACTS_OSX_NETWORKING_RESOLVER_HPP_ +#define LIB_INC_FACTS_OSX_NETWORKING_RESOLVER_HPP_ + +#include "../bsd/networking_resolver.hpp" + +namespace cfacter { namespace facts { namespace osx { + + /** + * Responsible for resolving networking facts. + */ + struct networking_resolver : bsd::networking_resolver + { + protected: + /** + * Determines if the given sock address is a link layer address. + * @param addr The socket address to check. + * @returns Returns true if the socket address is a link layer address or false if it is not. + */ + virtual bool is_link_address(sockaddr const* addr) const; + + /** + * Gets the bytes of the link address. + * @param addr The socket address representing the link address. + * @return Returns a pointer to the address bytes or nullptr if not a link address. + */ + virtual uint8_t const* get_link_address_bytes(sockaddr const* addr) const; + + /** + * Gets the MTU of the link layer data. + * @param interface The name of the link layer interface. + * @param data The data pointer from the link layer interface. + * @return Returns The MTU of the interface or -1 if there's no MTU. + */ + virtual int get_link_mtu(std::string const& interface, void* data) const; + + /** + * Called to resolve the hostname fact. + * @param facts The fact map that is resolving facts. + */ + virtual void resolve_hostname(fact_map& facts); + }; + +}}} // namespace cfacter::facts::osx + +#endif // LIB_INC_FACTS_OSX_NETWORKING_RESOLVER_HPP_ diff --git a/lib/inc/facts/posix/networking_resolver.hpp b/lib/inc/facts/posix/networking_resolver.hpp new file mode 100644 index 0000000000..cb57d0fe37 --- /dev/null +++ b/lib/inc/facts/posix/networking_resolver.hpp @@ -0,0 +1,95 @@ +#ifndef LIB_INC_FACTS_POSIX_NETWORKING_RESOLVER_HPP_ +#define LIB_INC_FACTS_POSIX_NETWORKING_RESOLVER_HPP_ + +#include "../fact_resolver.hpp" +#include "../fact.hpp" +#include + +namespace cfacter { namespace facts { namespace posix { + + /** + * Responsible for resolving networking facts. + */ + struct networking_resolver : fact_resolver + { + /** + * Constructs the networking_resolver. + */ + networking_resolver() : + fact_resolver({ + fact::hostname, + fact::ipaddress, + fact::ipaddress6, + fact::netmask, + fact::network, + fact::macaddress, + fact::interfaces, + }, { + std::string("^") + fact::ipaddress + "_", + std::string("^") + fact::ipaddress6 + "_", + std::string("^") + fact::mtu + "_", + std::string("^") + fact::netmask + "_", + std::string("^") + fact::network + "_", + std::string("^") + fact::macaddress + "_", + }) + { + } + + /** + * Utility function to convert a socket address to a IPv4 or IPv6 string representation. + * @param addr The socket address to convert to a string. + * @param mask The mask to apply to the address. + * @return Returns the IPv4 or IPv6 representation or an empty string if the family isn't supported. + */ + std::string address_to_string(sockaddr const* addr, sockaddr const* mask = nullptr) const; + + /** + * Utility function to convert the bytes of a MAC address to a string. + * @param bytes The bytes of the MAC address; expected to be 6 bytes long. + */ + static std::string macaddress_to_string(uint8_t const* bytes); + + protected: + /** + * Determines if the given sock address is a link layer address. + * @param addr The socket address to check. + * @returns Returns true if the socket address is a link layer address or false if it is not. + */ + virtual bool is_link_address(sockaddr const* addr) const = 0; + + /** + * Gets the bytes of the link address. + * @param addr The socket address representing the link address. + * @return Returns a pointer to the address bytes or nullptr if not a link address. + */ + virtual uint8_t const* get_link_address_bytes(sockaddr const* addr) const = 0; + + /** + * Gets the MTU of the link layer data. + * @param interface The name of the link layer interface. + * @param data The data pointer from the link layer interface. + * @return Returns The MTU of the interface or -1 if there's no MTU. + */ + virtual int get_link_mtu(std::string const& interface, void* data) const = 0; + + /** + * Called to resolve all facts the resolver is responsible for. + * @param facts The fact map that is resolving facts. + */ + void resolve_facts(fact_map& facts); + /** + * Called to resolve the hostname fact. + * @param facts The fact map that is resolving facts. + */ + virtual void resolve_hostname(fact_map& facts); + /** + * Called to resolve interface facts. + * @param facts The fact map that is resolving facts. + */ + virtual void resolve_interface_facts(fact_map& facts) = 0; + }; + +}}} // namespace cfacter::facts::posix + +#endif // LIB_INC_FACTS_POSIX_NETWORKING_RESOLVER_HPP_ + diff --git a/lib/inc/util/bsd/scoped_ifaddrs.hpp b/lib/inc/util/bsd/scoped_ifaddrs.hpp new file mode 100644 index 0000000000..212c9f430c --- /dev/null +++ b/lib/inc/util/bsd/scoped_ifaddrs.hpp @@ -0,0 +1,34 @@ +#ifndef LIB_INC_UTIL_BSD_SCOPED_IFADDRS_HPP_ +#define LIB_INC_UTIL_BSD_SCOPED_IFADDRS_HPP_ + +#include "../scoped_resource.hpp" +#include + +namespace cfacter { namespace util { namespace bsd { + + /** + * Represents a scoped ifaddrs pointer that automatically is freed when it goes out of scope. + */ + struct scoped_ifaddrs : scoped_resource + { + /** + * Constructs a scoped_descriptor. + * @param descriptor The file descriptor to close when destroyed. + */ + explicit scoped_ifaddrs(ifaddrs* addrs) : + scoped_resource(std::move(addrs), free) + { + } + + private: + static void free(ifaddrs* addrs) + { + if (addrs) { + ::freeifaddrs(addrs); + } + } + }; + +}}} // namespace cfacter::util::bsd + +#endif // LIB_INC_UTIL_BSD_SCOPED_IFADDRS_HPP_ diff --git a/lib/src/facts/bsd/networking_resolver.cc b/lib/src/facts/bsd/networking_resolver.cc new file mode 100644 index 0000000000..8cb2fcdd11 --- /dev/null +++ b/lib/src/facts/bsd/networking_resolver.cc @@ -0,0 +1,173 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace cfacter::util; +using namespace cfacter::util::bsd; + +namespace cfacter { namespace facts { namespace bsd { + + void networking_resolver::resolve_interface_facts(fact_map& facts) + { + // Get the linked list of interfaces + ifaddrs* ptr = nullptr; + if (getifaddrs(&ptr) != 0 || !ptr) { + // TODO: log failure + return; + } + + // Scope the head ifaddrs ptr + scoped_ifaddrs addrs(ptr); + + // Map an interface to entries describing that interface + multimap interface_map; + for (; ptr; ptr = ptr->ifa_next) { + // We only support IPv4, IPv6, and link interfaces + if (ptr->ifa_addr->sa_family != AF_INET && + ptr->ifa_addr->sa_family != AF_INET6 && + !is_link_address(ptr->ifa_addr)) { + continue; + } + + interface_map.insert({ ptr->ifa_name, ptr }); + } + + ostringstream interfaces; + bool found_primary = false; + + // Walk the interfaces + decltype(interface_map.begin()) addr_it; + for (auto it = interface_map.begin(); it != interface_map.end(); it = addr_it) { + string const& interface = it->first; + bool primary = false; + + auto range = interface_map.equal_range(it->first); + + // Walk the the addresses for the interface to check if it's primary + if (!found_primary) { + for (addr_it = range.first; addr_it != range.second; ++addr_it) { + ifaddrs const* addr = addr_it->second; + if (addr->ifa_addr->sa_family != AF_INET && + addr->ifa_addr->sa_family != AF_INET6) { + continue; + } + + string ip = address_to_string(addr->ifa_addr, addr->ifa_netmask); + primary = !starts_with(ip, "127.") && ip != "::1"; + if (primary) { + found_primary = true; + } + } + } + + // Walk the addresses again and resolve facts + for (addr_it = range.first; addr_it != range.second; ++addr_it) { + ifaddrs const* addr = addr_it->second; + resolve_address(facts, addr, primary); + resolve_network(facts, addr, primary); + resolve_mtu(facts, addr); + } + + // Add the interface to the interfaces fact + if (interfaces.tellp() != 0) { + interfaces << ","; + } + + interfaces << interface; + } + + string value = interfaces.str(); + if (value.empty()) { + return; + } + facts.add(fact::interfaces, make_value(move(value))); + } + + void networking_resolver::resolve_address(fact_map& facts, ifaddrs const* addr, bool primary) + { + string factname; + + // The fact name is based on the address type + if (addr->ifa_addr->sa_family == AF_INET) { + factname = fact::ipaddress; + } else if (addr->ifa_addr->sa_family == AF_INET6) { + factname = fact::ipaddress6; + } else if (is_link_address(addr->ifa_addr)) { + factname = fact::macaddress; + } + + string address = address_to_string(addr->ifa_addr); + if (address.empty()) { + return; + } + + // Check to see if the fact already exists; interfaces can have multiple addresses of the same type + string interface_factname = factname + "_" + addr->ifa_name; + if (facts.get(interface_factname, false)) { + return; + } + + facts.add(move(interface_factname), make_value(address)); + if (primary) { + facts.add(move(factname), make_value(move(address))); + } + } + + void networking_resolver::resolve_network(fact_map& facts, ifaddrs const* addr, bool primary) + { + // Limit these facts to IPv4 and IPv6 with a netmask address + if ((addr->ifa_addr->sa_family != AF_INET && + addr->ifa_addr->sa_family != AF_INET6) || !addr->ifa_netmask) { + return; + } + + // Set the netmask fact + string factname = addr->ifa_addr->sa_family == AF_INET ? fact::netmask : fact::netmask6; + string netmask = address_to_string(addr->ifa_netmask); + + // Check to see if the fact already exists; interfaces can have multiple addresses of the same type + string interface_factname = factname + "_" + addr->ifa_name; + if (facts.get(interface_factname, false)) { + return; + } + + facts.add(move(interface_factname), make_value(netmask)); + + if (primary) { + facts.add(move(factname), make_value(move(netmask))); + } + + // Set the network fact + factname = addr->ifa_addr->sa_family == AF_INET ? fact::network : fact::network6; + string network = address_to_string(addr->ifa_addr, addr->ifa_netmask); + interface_factname = factname + "_" + addr->ifa_name; + facts.add(move(interface_factname), make_value(network)); + + if (primary) { + facts.add(move(factname), make_value(move(network))); + } + } + + void networking_resolver::resolve_mtu(fact_map& facts, ifaddrs const* addr) + { + // The MTU exists on link addresses + if (!is_link_address(addr->ifa_addr) || !addr->ifa_data) { + return; + } + + int mtu = get_link_mtu(addr->ifa_name, addr->ifa_data); + if (mtu == -1) { + return; + } + facts.add(string(fact::mtu) + '_' + addr->ifa_name, make_value(to_string(mtu))); + } + +}}} // namespace cfacter::facts::bsd diff --git a/lib/src/facts/fact_map.cc b/lib/src/facts/fact_map.cc index e1fd6f60f0..2c6284f5c3 100644 --- a/lib/src/facts/fact_map.cc +++ b/lib/src/facts/fact_map.cc @@ -5,9 +5,33 @@ using namespace std; namespace cfacter { namespace facts { + /** + * Called to populate common facts. + * @param facts The fact map being populated. + */ + extern void populate_common_facts(fact_map& facts); + + /** + * Called to populate platform-specific facts. + * @param facts The fact map being populated. + */ + extern void populate_platform_facts(fact_map& facts); + fact_map fact_map::_instance; - void fact_map::add(std::string&& name, std::unique_ptr&& value) + void fact_map::add(shared_ptr const& resolver) + { + for (auto const& fact_name : resolver->names()) { + auto const& it = _resolver_map.lower_bound(fact_name); + if (it != _resolver_map.end() && !(_resolver_map.key_comp()(fact_name, it->first))) { + throw resolver_exists_exception("a resolver for fact " + fact_name + " already exists."); + } + _resolver_map.insert(it, make_pair(fact_name, resolver)); + } + _resolvers.push_back(resolver); + } + + void fact_map::add(string&& name, unique_ptr&& value) { // Search for the fact first auto const& it = _facts.lower_bound(name); @@ -15,13 +39,27 @@ namespace cfacter { namespace facts { throw fact_exists_exception("fact " + name + " already exists."); } - // Remove any resolver for this fact and add the fact - _resolvers.erase(name); - _facts.insert(it, std::make_pair(std::move(name), std::move(value))); + // Remove any mapped resolver for this fact + _resolver_map.erase(name); + _facts.insert(it, make_pair(move(name), move(value))); + } + + void fact_map::remove(shared_ptr const& resolver) + { + if (!resolver) { + return; + } + + // Remove all fact associations + for (auto const& name : resolver->names()) { + _resolver_map.erase(name); + } + _resolvers.remove(resolver); } void fact_map::remove(string const& name) { + _resolver_map.erase(name); _facts.erase(name); } @@ -29,6 +67,7 @@ namespace cfacter { namespace facts { { _facts.clear(); _resolvers.clear(); + _resolver_map.clear(); } bool fact_map::empty() const @@ -36,12 +75,12 @@ namespace cfacter { namespace facts { return _facts.empty() && _resolvers.empty(); } - void fact_map::each(std::function func) + void fact_map::each(function func) { - load(); + load_facts(); resolve_facts(); - std::find_if(begin(_facts), end(_facts), [&func](fact_map_type::value_type const& it) { + find_if(begin(_facts), end(_facts), [&func](fact_map_type::value_type const& it) { return func(it.first, it.second.get()); }); } @@ -51,7 +90,7 @@ namespace cfacter { namespace facts { return fact_map::_instance; } - void fact_map::load() + void fact_map::load_facts() { if (!empty()) { return; @@ -62,45 +101,51 @@ namespace cfacter { namespace facts { void fact_map::resolve_facts() { - // Go through every resolver and get each fact - while (!_resolvers.empty()) - { - // Copy the string from the key as resolving the facts will - // destruct the resolver entry in the map - string name = _resolvers.begin()->first; - get_value(name); + for (auto& resolver : _resolvers) { + resolver->resolve(*this); } + _resolvers.clear(); + _resolver_map.clear(); } - value const* fact_map::get_value(std::string const& name) + value const* fact_map::get_value(string const& name, bool resolve) { - load(); + load_facts(); + // Lookup the fact auto it = _facts.find(name); - if (it == _facts.end()) { - // Check the resolvers - auto const& resolver_it = _resolvers.find(name); - if (resolver_it == _resolvers.end()) { - // Fact not found + while (it == _facts.end()) { + // Look for a resolver for this fact + auto resolver = resolve ? find_resolver(name) : nullptr; + if (!resolver) { return nullptr; } - auto resolver = resolver_it->second; // Resolve the facts resolver->resolve(*this); + remove(resolver); - // Remove any associated facts that didn't resolve - for (auto const& name : resolver->names()) { - _resolvers.erase(name); - } - + // Try to find the fact again it = _facts.find(name); - if (it == _facts.end()) { - // Fact not found after resolution - return nullptr; - } } return it->second.get(); } + shared_ptr fact_map::find_resolver(string const& name) + { + // Check the map first to see if we know the fact by name + auto const& it = _resolver_map.find(name); + if (it != _resolver_map.end()) { + return it->second; + } + + // Otherwise, do a linear search for a resolver that can resolve the fact + for (auto const& resolver : _resolvers) { + if (resolver->can_resolve(name)) { + return resolver; + } + } + return nullptr; + } + }} // namespace cfacter::facts diff --git a/lib/src/facts/fact_resolver.cc b/lib/src/facts/fact_resolver.cc index 67a378cbea..e37184343c 100644 --- a/lib/src/facts/fact_resolver.cc +++ b/lib/src/facts/fact_resolver.cc @@ -1,10 +1,29 @@ #include #include +#include using namespace std; +using namespace re2; namespace cfacter { namespace facts { + fact_resolver::fact_resolver(vector&& names, vector const& patterns) : + _names(move(names)), + _resolving(false) + { + for (auto const& pattern : patterns) { + auto regex = unique_ptr(new RE2(pattern)); + if (!regex->error().empty()) { + throw invalid_name_pattern_exception(regex->error()); + } + _regexes.push_back(move(regex)); + } + } + + fact_resolver::~fact_resolver() + { + } + void fact_resolver::resolve(fact_map& facts) { if (_resolving) { @@ -14,4 +33,15 @@ namespace cfacter { namespace facts { return resolve_facts(facts); } + bool fact_resolver::can_resolve(string const& name) const + { + // Check to see if any of our regexes match + for (auto const& regex : _regexes) { + if (RE2::PartialMatch(name, *regex)) { + return true; + } + } + return false; + } + }} // namespace cfacter::facts diff --git a/lib/src/facts/linux/networking_resolver.cc b/lib/src/facts/linux/networking_resolver.cc new file mode 100644 index 0000000000..42c13d03ba --- /dev/null +++ b/lib/src/facts/linux/networking_resolver.cc @@ -0,0 +1,49 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace cfacter::util::posix; + +namespace cfacter { namespace facts { namespace linux { + + bool networking_resolver::is_link_address(sockaddr const* addr) const + { + return addr && addr->sa_family == AF_PACKET; + } + + uint8_t const* networking_resolver::get_link_address_bytes(sockaddr const* addr) const + { + if (!is_link_address(addr)) { + return nullptr; + } + sockaddr_ll const* link_addr = reinterpret_cast(addr); + if (link_addr->sll_halen != 6) { + return nullptr; + } + return reinterpret_cast(link_addr->sll_addr); + } + + int networking_resolver::get_link_mtu(string const& interface, void* data) const + { + // Unfortunately in Linux, the data points at interface statistics + // Nothing useful for us, so we need to use ioctl to query the MTU + ifreq req; + memset(&req, 0, sizeof(req)); + strncpy(req.ifr_name, interface.c_str(), sizeof(req.ifr_name)); + + scoped_descriptor sock(socket(AF_INET, SOCK_DGRAM, 0)); + + if (ioctl(sock, SIOCGIFMTU, &req) != 0) { + // TODO: log failure + return -1; + } + return req.ifr_mtu; + } + +}}} // namespace cfacter::facts::linux diff --git a/lib/src/facts/linux/platform.cc b/lib/src/facts/linux/platform.cc index 0a9fdb384e..f0c83b99cd 100644 --- a/lib/src/facts/linux/platform.cc +++ b/lib/src/facts/linux/platform.cc @@ -2,6 +2,7 @@ #include #include #include +#include using namespace std; @@ -9,9 +10,10 @@ namespace cfacter { namespace facts { void populate_platform_facts(fact_map& facts) { - facts.add_resolver(); - facts.add_resolver(); - facts.add_resolver(); + facts.add(make_shared()); + facts.add(make_shared()); + facts.add(make_shared()); + facts.add(make_shared()); } }} // namespace cfacter::facts diff --git a/lib/src/facts/osx/networking_resolver.cc b/lib/src/facts/osx/networking_resolver.cc new file mode 100644 index 0000000000..002fbd4dbc --- /dev/null +++ b/lib/src/facts/osx/networking_resolver.cc @@ -0,0 +1,55 @@ +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace cfacter::execution; + +namespace cfacter { namespace facts { namespace osx { + + bool networking_resolver::is_link_address(sockaddr const* addr) const + { + return addr && addr->sa_family == AF_LINK; + } + + uint8_t const* networking_resolver::get_link_address_bytes(sockaddr const* addr) const + { + if (!is_link_address(addr)) { + return nullptr; + } + sockaddr_dl const* link_addr = reinterpret_cast(addr); + if (link_addr->sdl_alen != 6) { + return nullptr; + } + return reinterpret_cast(LLADDR(link_addr)); + } + + int networking_resolver::get_link_mtu(string const& interface, void* data) const + { + if (!data) { + return -1; + } + return reinterpret_cast(data)->ifi_mtu; + } + + void networking_resolver::resolve_hostname(fact_map& facts) + { + auto version = facts.get(fact::kernel_release); + if (!version || version->value() != "R7") { + posix::networking_resolver::resolve_hostname(facts); + return; + } + + string value = execute("/usr/sbin/scutil", { "--get LocalHostName" }); + if (!value.empty()) { + posix::networking_resolver::resolve_hostname(facts); + return; + } + + facts.add(fact::hostname, make_value(move(value))); + } + +}}} // namespace cfacter::facts::osx diff --git a/lib/src/facts/osx/platform.cc b/lib/src/facts/osx/platform.cc index 05e1e61446..0a17b4719f 100644 --- a/lib/src/facts/osx/platform.cc +++ b/lib/src/facts/osx/platform.cc @@ -1,6 +1,7 @@ #include #include #include +#include using namespace std; @@ -8,8 +9,9 @@ namespace cfacter { namespace facts { void populate_platform_facts(fact_map& facts) { - facts.add_resolver(); - facts.add_resolver(); + facts.add(make_shared()); + facts.add(make_shared()); + facts.add(make_shared()); } }} // namespace cfacter::facts diff --git a/lib/src/facts/posix/networking_resolver.cc b/lib/src/facts/posix/networking_resolver.cc new file mode 100644 index 0000000000..51eba0416e --- /dev/null +++ b/lib/src/facts/posix/networking_resolver.cc @@ -0,0 +1,113 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +namespace cfacter { namespace facts { namespace posix { + + void networking_resolver::resolve_facts(fact_map& facts) + { + resolve_hostname(facts); + resolve_interface_facts(facts); + } + + void networking_resolver::resolve_hostname(fact_map& facts) + { + int max = sysconf(_SC_HOST_NAME_MAX); + vector name(max); + if (gethostname(name.data(), max) != 0) { + // TODO: log + return; + } + + // Use everything up to the first period + string value = name.data(); + size_t pos = value.find('.'); + if (pos != string::npos) { + value = value.substr(0, pos); + } + if (value.empty()) { + return; + } + + facts.add(fact::hostname, make_value(move(value))); + } + + string networking_resolver::address_to_string(sockaddr const* addr, sockaddr const* mask) const + { + if (!addr) { + return {}; + } + + // Check for IPv4 and IPv6 + if (addr->sa_family == AF_INET) { + in_addr ip = reinterpret_cast(addr)->sin_addr; + + // Apply an IPv4 mask + if (mask && mask->sa_family == addr->sa_family) { + ip.s_addr &= reinterpret_cast(mask)->sin_addr.s_addr; + } + return inet_ntoa(ip); + } else if (addr->sa_family == AF_INET6) { + in6_addr ip = reinterpret_cast(addr)->sin6_addr; + + // Apply an IPv6 mask + if (mask && mask->sa_family == addr->sa_family) { + in6_addr mask_ip = reinterpret_cast(mask)->sin6_addr; + for (size_t i = 0; i < 16; ++i) { + ip.s6_addr[i] &= mask_ip.s6_addr[i]; + } + } + char buffer[INET6_ADDRSTRLEN] = {}; + inet_ntop(AF_INET6, &ip, buffer, sizeof(buffer)); + return buffer; + } else { + auto link_addr = get_link_address_bytes(addr); + if (link_addr) { + return macaddress_to_string(reinterpret_cast(link_addr)); + } + } + + return {}; + } + + string networking_resolver::macaddress_to_string(uint8_t const* bytes) + { + if (!bytes) { + return {}; + } + + // Ignore MAC address "0" + bool nonzero = false; + for (size_t i = 0; i < 6; ++i) { + if (bytes[i] != 0) { + nonzero = true; + break; + } + } + if (!nonzero) { + return {}; + } + + ostringstream stream; + for (size_t i = 0; i < 6; ++i) { + if (stream.tellp() != 0) { + stream << ":"; + } + stream << hex << setw(2) << setfill('0') << static_cast(bytes[i]); + } + + return stream.str(); + } + +}}} // namespace cfacter::facts::posix From a2553847bbaa0e327e058246972eef0c67948eac Mon Sep 17 00:00:00 2001 From: jcitarello Date: Fri, 24 Jan 2014 09:29:30 -0600 Subject: [PATCH 1789/3753] (FACT-249) Support netmask fact on AIX --- lib/facter/netmask.rb | 2 +- lib/facter/util/netmask.rb | 2 +- spec/fixtures/unit/netmask/ifconfig_aix_7.txt | 3 +++ spec/unit/netmask_spec.rb | 11 +++++++++++ 4 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 spec/fixtures/unit/netmask/ifconfig_aix_7.txt diff --git a/lib/facter/netmask.rb b/lib/facter/netmask.rb index 4730e5fe0d..8195568877 100644 --- a/lib/facter/netmask.rb +++ b/lib/facter/netmask.rb @@ -16,7 +16,7 @@ require 'facter/util/netmask' Facter.add("netmask") do - confine :kernel => [ :sunos, :linux, :freebsd, :openbsd, :netbsd, :darwin, :"gnu/kfreebsd", :dragonfly ] + confine :kernel => [ :sunos, :linux, :freebsd, :openbsd, :netbsd, :darwin, :"gnu/kfreebsd", :dragonfly, :AIX ] setcode do Facter::NetMask.get_netmask end diff --git a/lib/facter/util/netmask.rb b/lib/facter/util/netmask.rb index 61258a98aa..765c2fce60 100644 --- a/lib/facter/util/netmask.rb +++ b/lib/facter/util/netmask.rb @@ -11,7 +11,7 @@ def self.get_netmask :regex => %r{#{Facter.value(:ipaddress)}.*?(?:Mask:|netmask)\s*(#{ipregex})}x, :munge => nil, } - when 'SunOS' + when 'SunOS','AIX' ops = { :ifconfig_opts => ['-a'], :regex => %r{\s+ inet \s #{Facter.value(:ipaddress)} \s netmask \s (\w{8})}x, diff --git a/spec/fixtures/unit/netmask/ifconfig_aix_7.txt b/spec/fixtures/unit/netmask/ifconfig_aix_7.txt new file mode 100644 index 0000000000..4221586700 --- /dev/null +++ b/spec/fixtures/unit/netmask/ifconfig_aix_7.txt @@ -0,0 +1,3 @@ +en0: flags=1e080863,480 + inet 10.16.45.25 netmask 0xffffff00 broadcast 10.16.45.255 + tcp_sendspace 262144 tcp_recvspace 262144 rfc1323 1 diff --git a/spec/unit/netmask_spec.rb b/spec/unit/netmask_spec.rb index c981f51c3b..226fa1106e 100755 --- a/spec/unit/netmask_spec.rb +++ b/spec/unit/netmask_spec.rb @@ -27,6 +27,17 @@ "ifconfig_ubuntu_1204.txt" end + context "on AIX" do + before :each do + Facter.fact(:kernel).stubs(:value).returns("AIX") + end + + example_behavior_for "netmask from ifconfig output", + "AIX 7", "255.255.255.0", + "ifconfig_aix_7.txt" + + end + context "on Darwin" do before :each do Facter.fact(:kernel).stubs(:value).returns("Darwin") From f7efb50789cba2025f368aa1aea580e4f0349c2e Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Mon, 21 Apr 2014 23:39:21 -0700 Subject: [PATCH 1790/3753] (CFACT-22) Fix code review issues. Fixing issues related to GH-13 review feedback. --- lib/inc/facts/fact_resolver.hpp | 1 + lib/inc/facts/linux/networking_resolver.hpp | 5 +++++ lib/inc/util/bsd/scoped_ifaddrs.hpp | 14 ++++++++++++++ lib/inc/util/scoped_resource.hpp | 11 ++++++++++- lib/src/facts/bsd/networking_resolver.cc | 14 +++++++------- lib/src/facts/fact_map.cc | 4 ++++ lib/src/facts/osx/networking_resolver.cc | 4 ++-- 7 files changed, 43 insertions(+), 10 deletions(-) diff --git a/lib/inc/facts/fact_resolver.hpp b/lib/inc/facts/fact_resolver.hpp index a1208ad551..53abb8acfe 100644 --- a/lib/inc/facts/fact_resolver.hpp +++ b/lib/inc/facts/fact_resolver.hpp @@ -67,6 +67,7 @@ namespace cfacter { namespace facts { /** * Constructs a fact_resolver. * @param names The fact names the resolver is responsible for. + * @param patterns Regular expression patterns for additional ("dynamic") facts the resolver is responsible for. */ fact_resolver(std::vector&& names, std::vector const& patterns = {}); diff --git a/lib/inc/facts/linux/networking_resolver.hpp b/lib/inc/facts/linux/networking_resolver.hpp index 5bef06d896..32136072b7 100644 --- a/lib/inc/facts/linux/networking_resolver.hpp +++ b/lib/inc/facts/linux/networking_resolver.hpp @@ -7,6 +7,11 @@ namespace cfacter { namespace facts { namespace linux { /** * Responsible for resolving networking facts. + * + * The Linux networking_resolver inherits from the BSD networking_resolver. + * This is because getifaddrs is a BSD concept that was implemented in Linux. + * The only thing this resolver is responsible for is handing link-level + * addressing and MTU. */ struct networking_resolver : bsd::networking_resolver { diff --git a/lib/inc/util/bsd/scoped_ifaddrs.hpp b/lib/inc/util/bsd/scoped_ifaddrs.hpp index 212c9f430c..9b7702fb6e 100644 --- a/lib/inc/util/bsd/scoped_ifaddrs.hpp +++ b/lib/inc/util/bsd/scoped_ifaddrs.hpp @@ -11,6 +11,20 @@ namespace cfacter { namespace util { namespace bsd { */ struct scoped_ifaddrs : scoped_resource { + /** + * Default constructor. + * This constructor will handle calling getifaddrs. + */ + scoped_ifaddrs() + { + // Get the linked list of interfaces + if (getifaddrs(&_resource) != 0) { + _resource = nullptr; + } else { + _deleter = free; + } + } + /** * Constructs a scoped_descriptor. * @param descriptor The file descriptor to close when destroyed. diff --git a/lib/inc/util/scoped_resource.hpp b/lib/inc/util/scoped_resource.hpp index 80d96cd61c..e1ef3e4c78 100644 --- a/lib/inc/util/scoped_resource.hpp +++ b/lib/inc/util/scoped_resource.hpp @@ -12,6 +12,15 @@ namespace cfacter { namespace util { */ template struct scoped_resource { + /** + * Default constructor for scoped_resource. + */ + scoped_resource() : + _resource(), + _deleter() + { + } + /** * Constructs a scoped_resource. * Takes ownership of the given resource. @@ -69,7 +78,7 @@ namespace cfacter { namespace util { } } - private: + protected: T _resource; std::function _deleter; }; diff --git a/lib/src/facts/bsd/networking_resolver.cc b/lib/src/facts/bsd/networking_resolver.cc index 8cb2fcdd11..f1bed09919 100644 --- a/lib/src/facts/bsd/networking_resolver.cc +++ b/lib/src/facts/bsd/networking_resolver.cc @@ -17,19 +17,16 @@ namespace cfacter { namespace facts { namespace bsd { void networking_resolver::resolve_interface_facts(fact_map& facts) { - // Get the linked list of interfaces - ifaddrs* ptr = nullptr; - if (getifaddrs(&ptr) != 0 || !ptr) { + // Scope the head ifaddrs ptr + scoped_ifaddrs addrs; + if (!addrs) { // TODO: log failure return; } - // Scope the head ifaddrs ptr - scoped_ifaddrs addrs(ptr); - // Map an interface to entries describing that interface multimap interface_map; - for (; ptr; ptr = ptr->ifa_next) { + for (ifaddrs* ptr = addrs; ptr; ptr = ptr->ifa_next) { // We only support IPv4, IPv6, and link interfaces if (ptr->ifa_addr->sa_family != AF_INET && ptr->ifa_addr->sa_family != AF_INET6 && @@ -102,6 +99,9 @@ namespace cfacter { namespace facts { namespace bsd { factname = fact::ipaddress6; } else if (is_link_address(addr->ifa_addr)) { factname = fact::macaddress; + } else { + // Unsupported address + return; } string address = address_to_string(addr->ifa_addr); diff --git a/lib/src/facts/fact_map.cc b/lib/src/facts/fact_map.cc index 2c6284f5c3..facbcb0417 100644 --- a/lib/src/facts/fact_map.cc +++ b/lib/src/facts/fact_map.cc @@ -21,6 +21,10 @@ namespace cfacter { namespace facts { void fact_map::add(shared_ptr const& resolver) { + if (!resolver) { + return; + } + for (auto const& fact_name : resolver->names()) { auto const& it = _resolver_map.lower_bound(fact_name); if (it != _resolver_map.end() && !(_resolver_map.key_comp()(fact_name, it->first))) { diff --git a/lib/src/facts/osx/networking_resolver.cc b/lib/src/facts/osx/networking_resolver.cc index 002fbd4dbc..653e39d61e 100644 --- a/lib/src/facts/osx/networking_resolver.cc +++ b/lib/src/facts/osx/networking_resolver.cc @@ -39,13 +39,13 @@ namespace cfacter { namespace facts { namespace osx { { auto version = facts.get(fact::kernel_release); if (!version || version->value() != "R7") { - posix::networking_resolver::resolve_hostname(facts); + bsd::networking_resolver::resolve_hostname(facts); return; } string value = execute("/usr/sbin/scutil", { "--get LocalHostName" }); if (!value.empty()) { - posix::networking_resolver::resolve_hostname(facts); + bsd::networking_resolver::resolve_hostname(facts); return; } From 9f83d99a5182b5155433b15cc05207e8d51900cf Mon Sep 17 00:00:00 2001 From: Moses Mendoza Date: Sun, 16 Feb 2014 09:43:14 -0800 Subject: [PATCH 1791/3753] (FACT-247) Correct assumed regex for AIX netmask fact On AIX, the printed output of ifconfig is similar to the best family with respect to netmask (that is, prepends the value with 0x): [0] [AIX] root@pe-aix-61-agent:~ # uname -a AIX pe-aix-61-agent 1 6 00F7FD3A4C00 powerpc unknown AIX [0] [AIX] root@pe-aix-61-agent:~ # /etc/ifconfig en0 en0: flags=1e080863,480 inet netmask 0xffffff00 broadcast tcp_sendspace 262144 tcp_recvspace 262144 rfc1323 1 Note netmask 0xffffff00, which is 255.255.255.0. This means the regex block in facter/util/netmask.rb that AIX belongs to is the one with the various bsd variants. Signed-off-by: Moses Mendoza --- lib/facter/util/netmask.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/facter/util/netmask.rb b/lib/facter/util/netmask.rb index 765c2fce60..4c4cf01433 100644 --- a/lib/facter/util/netmask.rb +++ b/lib/facter/util/netmask.rb @@ -11,13 +11,13 @@ def self.get_netmask :regex => %r{#{Facter.value(:ipaddress)}.*?(?:Mask:|netmask)\s*(#{ipregex})}x, :munge => nil, } - when 'SunOS','AIX' + when 'SunOS' ops = { :ifconfig_opts => ['-a'], :regex => %r{\s+ inet \s #{Facter.value(:ipaddress)} \s netmask \s (\w{8})}x, :munge => Proc.new { |mask| mask.scan(/../).collect do |byte| byte.to_i(16) end.join('.') } } - when 'FreeBSD','NetBSD','OpenBSD', 'Darwin', 'GNU/kFreeBSD', 'DragonFly' + when 'FreeBSD','NetBSD','OpenBSD', 'Darwin', 'GNU/kFreeBSD', 'DragonFly', 'AIX' ops = { :ifconfig_opts => ['-a'], :regex => %r{\s+ inet \s #{Facter.value(:ipaddress)} \s netmask \s 0x(\w{8})}x, From f8f9e0388d212e7e3b967ab43dfb1737d32e4029 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Mon, 21 Apr 2014 14:09:47 -0700 Subject: [PATCH 1792/3753] (CFACT-24) Add log4cxx to the build. Adding Apache's log4cxx to the build process. --- CMakeLists.txt | 6 ++++++ PostInstall.cmake | 2 ++ exe/CMakeLists.txt | 3 ++- lib/CMakeLists.txt | 4 ++-- vendor/apr.cmake | 43 ++++++++++++++++++++++++++++++++++++++++++ vendor/aprutil.cmake | 44 +++++++++++++++++++++++++++++++++++++++++++ vendor/log4cxx.cmake | 45 ++++++++++++++++++++++++++++++++++++++++++++ vendor/log4cxx.patch | 22 ++++++++++++++++++++++ vendor/re2.cmake | 2 +- 9 files changed, 167 insertions(+), 4 deletions(-) create mode 100644 PostInstall.cmake create mode 100644 vendor/apr.cmake create mode 100644 vendor/aprutil.cmake create mode 100644 vendor/log4cxx.cmake create mode 100644 vendor/log4cxx.patch diff --git a/CMakeLists.txt b/CMakeLists.txt index f6f87b9f3a..b1554ad3ed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,7 +25,11 @@ if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(CMAKE_INSTALL_PREFIX "/opt/cfacter" CACHE PATH "default install path" FORCE) endif() +# Include vendor libraries set(VENDOR_DIRECTORY "${PROJECT_SOURCE_DIR}/vendor") +include(${VENDOR_DIRECTORY}/re2.cmake) +include(${VENDOR_DIRECTORY}/rapidjson.cmake) +include(${VENDOR_DIRECTORY}/log4cxx.cmake) add_subdirectory(exe) @@ -72,3 +76,5 @@ endif() add_custom_target(cppcheck COMMAND cppcheck lib exe ) + +install(SCRIPT "${CMAKE_SOURCE_DIR}/PostInstall.cmake") diff --git a/PostInstall.cmake b/PostInstall.cmake new file mode 100644 index 0000000000..43445c9476 --- /dev/null +++ b/PostInstall.cmake @@ -0,0 +1,2 @@ + +# TODO: Fixup RPATH of dependencies on install for both OSX and Linux \ No newline at end of file diff --git a/exe/CMakeLists.txt b/exe/CMakeLists.txt index 371f8d1edc..aa9f1a9377 100644 --- a/exe/CMakeLists.txt +++ b/exe/CMakeLists.txt @@ -12,8 +12,9 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra -Werror -Wno-un include_directories( ${LIBCFACTER_DIR}/inc + ${LOG4CXX_INCLUDE_DIRS} ) add_executable(cfacter ${CFACTER_SOURCES}) -target_link_libraries(cfacter libcfacter) +target_link_libraries(cfacter libcfacter liblog4cxx) install(TARGETS cfacter DESTINATION .) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 8f8c0afe92..cb67efe204 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -1,6 +1,4 @@ cmake_minimum_required(VERSION 2.8.12) -include(${VENDOR_DIRECTORY}/re2.cmake) -include(${VENDOR_DIRECTORY}/rapidjson.cmake) # Set compiler-specific flags if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") @@ -60,12 +58,14 @@ include_directories( inc ${RE2_INCLUDE_DIRS} ${RAPIDJSON_INCLUDE_DIRS} + ${LOG4CXX_INCLUDE_DIRS} ) # Link in additional libraries target_link_libraries(libcfacter pthread libre2 + liblog4cxx ) # Add a dependency on rapidjson diff --git a/vendor/apr.cmake b/vendor/apr.cmake new file mode 100644 index 0000000000..23bd917d97 --- /dev/null +++ b/vendor/apr.cmake @@ -0,0 +1,43 @@ +cmake_minimum_required(VERSION 2.8.12) +include(ExternalProject) + +set(APR_SHARED_OBJECT_FILE "libapr-1.0.so") + +if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + set(APR_SHARED_OBJECT_FILE "libapr-1.0.dylib") + set(APR_LD_FLAGS "-Wl,-install_name,@rpath/${APR_SHARED_OBJECT_FILE}") + set(APR_CPP_FLAGS "-Wno-empty-body") +elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") +elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel") +elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") +endif() + +# Add an external project to build apr +externalproject_add( + apr + PREFIX "${PROJECT_BINARY_DIR}" + URL "file://${VENDOR_DIRECTORY}/apr-1.5.0.tar.gz" + URL_MD5 "6419a8f7e89ad51b5bad7b0c84cc818c" + CONFIGURE_COMMAND ./configure --enable-shared --disable-static CXXFLAGS=${APR_CPP_FLAGS} LDFLAGS=${APR_LD_FLAGS} + BUILD_COMMAND make + BUILD_IN_SOURCE 1 + INSTALL_COMMAND "" + ALWAYS 1 +) + +# Set some useful variables based on the source directory +externalproject_get_property(apr SOURCE_DIR) +set(APR_INCLUDE_DIRS "${SOURCE_DIR}/include") +set(APR_SHARED_OBJECT_PATH "${SOURCE_DIR}/.libs/${APR_SHARED_OBJECT_FILE}") +set(APR_LIBRARIES "${APR_SHARED_OBJECT_PATH}") +set(APR_CONFIG_PATH "${SOURCE_DIR}/apr-1-config") + +set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "${SOURCE_DIR}/.libs") + +add_library(libapr SHARED IMPORTED) +set_target_properties(libapr + PROPERTIES PREFIX "" + IMPORTED_LOCATION "${APR_SHARED_OBJECT_PATH}" +) +add_dependencies(libapr apr) +install(FILES "${APR_SHARED_OBJECT_PATH}" DESTINATION .) diff --git a/vendor/aprutil.cmake b/vendor/aprutil.cmake new file mode 100644 index 0000000000..5c3f3c9b25 --- /dev/null +++ b/vendor/aprutil.cmake @@ -0,0 +1,44 @@ +cmake_minimum_required(VERSION 2.8.12) +include(ExternalProject) +include(${VENDOR_DIRECTORY}/apr.cmake) + +set(APR_UTIL_SHARED_OBJECT_FILE "libaprutil-1.0.so") + +if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + set(APR_UTIL_SHARED_OBJECT_FILE "libaprutil-1.0.dylib") + set(APR_UTIL_LD_FLAGS "-Wl,-install_name,@rpath/${APR_UTIL_SHARED_OBJECT_FILE},-rpath,${CMAKE_BINARY_DIR}/src/apr/.libs") +elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") +elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel") +elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") +endif() + +# Add an external project to build apr-util +externalproject_add( + aprutil + PREFIX "${PROJECT_BINARY_DIR}" + URL "file://${VENDOR_DIRECTORY}/apr-util-1.5.3.tar.gz" + URL_MD5 "71a11d037240b292f824ba1eb537b4e3" + CONFIGURE_COMMAND ./configure --with-apr=${APR_CONFIG_PATH} CXXFLAGS=${APR_UTIL_CPP_FLAGS} APRUTIL_LDFLAGS=${APR_UTIL_LD_FLAGS} + BUILD_COMMAND make + BUILD_IN_SOURCE 1 + INSTALL_COMMAND "" + ALWAYS 1 +) +add_dependencies(aprutil apr) + +# Set some useful variables based on the source directory +externalproject_get_property(aprutil SOURCE_DIR) +set(APR_UTIL_INCLUDE_DIRS "${SOURCE_DIR}/include") +set(APR_UTIL_SHARED_OBJECT_PATH "${SOURCE_DIR}/.libs/${APR_UTIL_SHARED_OBJECT_FILE}") +set(APR_UTIL_LIBRARIES "${APR_UTIL_SHARED_OBJECT_PATH}") +set(APR_UTIL_CONFIG_PATH "${SOURCE_DIR}/apu-1-config") + +set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "${SOURCE_DIR}/.libs") + +add_library(libaprutil SHARED IMPORTED) +set_target_properties(libaprutil + PROPERTIES PREFIX "" + IMPORTED_LOCATION "${APR_UTIL_SHARED_OBJECT_PATH}" +) +add_dependencies(libaprutil aprutil) +install(FILES "${APR_UTIL_SHARED_OBJECT_PATH}" DESTINATION .) diff --git a/vendor/log4cxx.cmake b/vendor/log4cxx.cmake new file mode 100644 index 0000000000..72c827229b --- /dev/null +++ b/vendor/log4cxx.cmake @@ -0,0 +1,45 @@ +cmake_minimum_required(VERSION 2.8.12) +include(ExternalProject) +include(${VENDOR_DIRECTORY}/aprutil.cmake) + +set(LOG4CXX_SHARED_OBJECT_FILE "liblog4cxx.10.0.0.so") + +if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + set(LOG4CXX_SHARED_OBJECT_FILE "liblog4cxx.10.0.0.dylib") + set(LOG4CXX_LD_FLAGS "-Wl,-install_name,@rpath/${LOG4CXX_SHARED_OBJECT_FILE},-rpath,${CMAKE_BINARY_DIR}/src/aprutil/.libs,-rpath,${CMAKE_BINARY_DIR}/src/apr/.libs") + set(LOG4CXX_CPP_FLAGS "-Wno-empty-body") +elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") +elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel") +elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") +endif() + +# Add an external project to build log4cxx +externalproject_add( + log4cxx + PREFIX "${PROJECT_BINARY_DIR}" + URL "file://${VENDOR_DIRECTORY}/apache-log4cxx-0.10.0.tar.gz" + URL_MD5 "b30ffb8da3665178e68940ff7a61084c" + CONFIGURE_COMMAND ./configure --enable-shared --disable-static --with-apr=${APR_CONFIG_PATH} --with-apr-util=${APR_UTIL_CONFIG_PATH} CXXFLAGS=${LOG4CXX_CPP_FLAGS} LDFLAGS=${LOG4CXX_LD_FLAGS} + PATCH_COMMAND patch -p1 < ${VENDOR_DIRECTORY}/log4cxx.patch + BUILD_COMMAND make + BUILD_IN_SOURCE 1 + INSTALL_COMMAND "" + ALWAYS 1 +) +add_dependencies(log4cxx aprutil) + +# Set some useful variables based on the source directory +externalproject_get_property(log4cxx SOURCE_DIR) +set(LOG4CXX_INCLUDE_DIRS "${SOURCE_DIR}/src/main/include") +set(LOG4CXX_SHARED_OBJECT_PATH "${SOURCE_DIR}/src/main/cpp/.libs/${LOG4CXX_SHARED_OBJECT_FILE}") +set(LOG4CXX_LIBRARIES "${LOG4CXX_SHARED_OBJECT_PATH}") + +set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "${SOURCE_DIR}/src/main/cpp/*.o;${SOURCE_DIR}/src/main/cpp/.libs") + +add_library(liblog4cxx SHARED IMPORTED) +set_target_properties(liblog4cxx + PROPERTIES PREFIX "" + IMPORTED_LOCATION "${LOG4CXX_SHARED_OBJECT_PATH}" +) +add_dependencies(liblog4cxx log4cxx) +install(FILES "${LOG4CXX_SHARED_OBJECT_PATH}" DESTINATION .) diff --git a/vendor/log4cxx.patch b/vendor/log4cxx.patch new file mode 100644 index 0000000000..06cb4c3dbb --- /dev/null +++ b/vendor/log4cxx.patch @@ -0,0 +1,22 @@ +--- a/src/main/include/log4cxx/helpers/simpledateformat.h ++++ b/src/main/include/log4cxx/helpers/simpledateformat.h +@@ -27,10 +27,9 @@ + + #include + #include ++#include + #include + +-namespace std { class locale; } +- + namespace log4cxx + { + namespace helpers +--- a/src/main/cpp/stringhelper.cpp ++++ b/src/main/cpp/stringhelper.cpp +@@ -28,6 +28,7 @@ + #endif + #include + #include ++#include + #include diff --git a/vendor/re2.cmake b/vendor/re2.cmake index 0053931a55..4032b8b1ec 100644 --- a/vendor/re2.cmake +++ b/vendor/re2.cmake @@ -5,7 +5,7 @@ set(RE2_SHARED_OBJECT_FILE "libre2.so.0") if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") # Fix the install name to be on the reference path - set(RE2_LD_FLAGS "-install_name @rpath/${RE2_SHARED_OBJECT_FILE}") + set(RE2_LD_FLAGS "-Wl,-install_name,@rpath/${RE2_SHARED_OBJECT_FILE}") elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") set(RE2_CPP_FLAGS "-Wno-unused-local-typedefs") elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel") From 33c434de8e464eeed1005973a74e6371a06d91fc Mon Sep 17 00:00:00 2001 From: Matthaus Owens Date: Tue, 22 Apr 2014 15:36:48 -0700 Subject: [PATCH 1793/3753] (FACT-462) Raring went EOL in Jan 2014, so we should no longer build packages for it. --- ext/build_defaults.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/build_defaults.yaml b/ext/build_defaults.yaml index a42c1f60dc..84a9679f75 100644 --- a/ext/build_defaults.yaml +++ b/ext/build_defaults.yaml @@ -2,7 +2,7 @@ packaging_url: 'git://github.com/puppetlabs/packaging.git --branch=master' packaging_repo: 'packaging' default_cow: 'base-squeeze-i386.cow' -cows: 'base-lucid-i386.cow base-lucid-amd64.cow base-precise-i386.cow base-precise-amd64.cow base-quantal-i386.cow base-quantal-amd64.cow base-raring-i386.cow base-raring-amd64.cow base-saucy-i386.cow base-saucy-amd64.cow base-sid-i386.cow base-sid-amd64.cow base-squeeze-i386.cow base-squeeze-amd64.cow base-stable-i386.cow base-stable-amd64.cow base-testing-i386.cow base-testing-amd64.cow base-trusty-i386.cow base-trusty-amd64.cow base-unstable-i386.cow base-unstable-amd64.cow base-wheezy-i386.cow base-wheezy-amd64.cow' +cows: 'base-lucid-i386.cow base-lucid-amd64.cow base-precise-i386.cow base-precise-amd64.cow base-quantal-i386.cow base-quantal-amd64.cow base-saucy-i386.cow base-saucy-amd64.cow base-sid-i386.cow base-sid-amd64.cow base-squeeze-i386.cow base-squeeze-amd64.cow base-stable-i386.cow base-stable-amd64.cow base-testing-i386.cow base-testing-amd64.cow base-trusty-i386.cow base-trusty-amd64.cow base-unstable-i386.cow base-unstable-amd64.cow base-wheezy-i386.cow base-wheezy-amd64.cow' pbuild_conf: '/etc/pbuilderrc' packager: 'puppetlabs' gpg_name: 'info@puppetlabs.com' From 55b83f5f3629ca665b41a0ce495563954ead5202 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Mon, 21 Apr 2014 22:55:20 -0700 Subject: [PATCH 1794/3753] (CFACT-24) Add Boost to the build. Adding Boost to the build. Currently we only build program_options. --- CMakeLists.txt | 3 ++- exe/CMakeLists.txt | 3 ++- lib/CMakeLists.txt | 1 + {ext => scripts}/cpplint.py | 0 scripts/osx_boost_names.sh | 27 +++++++++++++++++++++ vendor/boost.cmake | 47 +++++++++++++++++++++++++++++++++++++ 6 files changed, 79 insertions(+), 2 deletions(-) rename {ext => scripts}/cpplint.py (100%) create mode 100755 scripts/osx_boost_names.sh create mode 100644 vendor/boost.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index b1554ad3ed..73a47e2e5d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,6 +30,7 @@ set(VENDOR_DIRECTORY "${PROJECT_SOURCE_DIR}/vendor") include(${VENDOR_DIRECTORY}/re2.cmake) include(${VENDOR_DIRECTORY}/rapidjson.cmake) include(${VENDOR_DIRECTORY}/log4cxx.cmake) +include(${VENDOR_DIRECTORY}/boost.cmake) add_subdirectory(exe) @@ -56,7 +57,7 @@ else() file(GLOB_RECURSE ALL_SOURCES lib/*.cc lib/*.h lib/*.hpp exe/*.cc exe/*.h exe/*.hpp) - set(CPPLINT_PATH "${PROJECT_SOURCE_DIR}/ext/cpplint.py") + set(CPPLINT_PATH "${PROJECT_SOURCE_DIR}/scripts/cpplint.py") set(CPPLINT_ARGS "--extensions=cc,hpp,h") if (CPPLINT_FILTER) diff --git a/exe/CMakeLists.txt b/exe/CMakeLists.txt index aa9f1a9377..9b745b82f3 100644 --- a/exe/CMakeLists.txt +++ b/exe/CMakeLists.txt @@ -13,8 +13,9 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra -Werror -Wno-un include_directories( ${LIBCFACTER_DIR}/inc ${LOG4CXX_INCLUDE_DIRS} + ${BOOST_INCLUDE_DIRS} ) add_executable(cfacter ${CFACTER_SOURCES}) -target_link_libraries(cfacter libcfacter liblog4cxx) +target_link_libraries(cfacter libcfacter liblog4cxx libboost_program_options) install(TARGETS cfacter DESTINATION .) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index cb67efe204..b9665eecfd 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -59,6 +59,7 @@ include_directories( ${RE2_INCLUDE_DIRS} ${RAPIDJSON_INCLUDE_DIRS} ${LOG4CXX_INCLUDE_DIRS} + ${BOOST_INCLUDE_DIRS} ) # Link in additional libraries diff --git a/ext/cpplint.py b/scripts/cpplint.py similarity index 100% rename from ext/cpplint.py rename to scripts/cpplint.py diff --git a/scripts/osx_boost_names.sh b/scripts/osx_boost_names.sh new file mode 100755 index 0000000000..f6fa2400b2 --- /dev/null +++ b/scripts/osx_boost_names.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +if [ "$#" -eq 0 ]; then + echo "usage: osx_boost_names " + exit 1 +fi + +# Search for all dynamic libraries in the given source directory +files=$(find $1 -type f -name '*.dylib') +for i in $files +do + # Fix the install_name so that it starts with @rpath + filename=$(basename $i) + echo "Fixing boost install names for: $filename" + install_name_tool -id @rpath/$filename $i + if [ "$?" != "0" ]; then + exit 1 + fi + # Look for all the other dynamic libraries and fix their references to this library + for j in $files + do + if [ "$i" == "$j" ]; then + continue + fi + install_name_tool -change $filename @rpath/$filename $j + done +done diff --git a/vendor/boost.cmake b/vendor/boost.cmake new file mode 100644 index 0000000000..7c9ba4e51b --- /dev/null +++ b/vendor/boost.cmake @@ -0,0 +1,47 @@ +cmake_minimum_required(VERSION 2.8.12) +include(ExternalProject) + +# Set the Boost libraries we need built here +set(BOOST_TO_BUILD program_options) + +# Add an external project to build boost +externalproject_add( + boost + PREFIX "${PROJECT_BINARY_DIR}" + URL "file://${VENDOR_DIRECTORY}/boost_1_55_0.tar.gz" + URL_MD5 "93780777cfbf999a600f62883bd54b17" + CONFIGURE_COMMAND ./bootstrap.sh --with-libraries=${BOOST_TO_BUILD} + BUILD_COMMAND ./b2 + BUILD_IN_SOURCE 1 + INSTALL_COMMAND "" + ALWAYS 1 +) + +# Set some useful variables based on the source directory +externalproject_get_property(boost SOURCE_DIR) +set(BOOST_INCLUDE_DIRS ${SOURCE_DIR}) + +if(APPLE) + externalproject_add_step( + boost + installnames + COMMAND ${PROJECT_SOURCE_DIR}/scripts/osx_boost_names.sh ${SOURCE_DIR}/stage/lib + COMMENT "Fixing boost install names" + ALWAYS 1 + DEPENDEES build + ) +endif() + +if (APPLE) + set(BOOST_SO_EXT "dylib") +else() + set(BOOST_SO_EXT "so") +endif() + +foreach(BOOST_LIBRARY ${BOOST_TO_BUILD}) + set(BOOST_LIBRARY_TARGET libboost_${BOOST_LIBRARY}) + add_library(${BOOST_LIBRARY_TARGET} SHARED IMPORTED) + set(BOOST_LIBRARY_LOCATION "${SOURCE_DIR}/stage/lib/libboost_${BOOST_LIBRARY}.${BOOST_SO_EXT}") + set_target_properties(${BOOST_LIBRARY_TARGET} PROPERTIES PREFIX "" IMPORTED_LOCATION ${BOOST_LIBRARY_LOCATION}) + install(FILES ${BOOST_LIBRARY_LOCATION} DESTINATION .) +endforeach() From 804fbf03d84c7d76bf0bce6d5dd864cde76f2d2d Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Mon, 21 Apr 2014 23:03:27 -0700 Subject: [PATCH 1795/3753] (CFACT-24) Implement more logging in cfacter. Replacing usage of getopt with Boost's program_options library, which is cross-platform. Implementing configuring the log4cxx logger with --debug, --verbose, or --propfile to specify a properties file. Implementing printing only requested facts from command line. Implementing more logging when resolving facts and in failure cases. --- exe/cfacter.cc | 235 +++++++++++++----- lib/inc/facts/fact_map.hpp | 10 + lib/inc/facts/fact_resolver.hpp | 10 +- lib/inc/facts/linux/lsb_resolver.hpp | 4 +- lib/inc/facts/posix/kernel_resolver.hpp | 4 +- lib/inc/facts/posix/networking_resolver.hpp | 9 +- .../facts/posix/operating_system_resolver.hpp | 4 +- lib/src/execution/posix/execution.cc | 42 +++- lib/src/facts/bsd/networking_resolver.cc | 8 +- lib/src/facts/fact_map.cc | 8 + lib/src/facts/fact_resolver.cc | 10 +- lib/src/facts/linux/networking_resolver.cc | 8 +- lib/src/facts/posix/kernel_resolver.cc | 7 + lib/src/facts/posix/networking_resolver.cc | 21 +- 14 files changed, 287 insertions(+), 93 deletions(-) diff --git a/exe/cfacter.cc b/exe/cfacter.cc index 2c99aafb56..561c166a94 100644 --- a/exe/cfacter.cc +++ b/exe/cfacter.cc @@ -1,85 +1,188 @@ #include "../version.h" -#include #include #include +#include +#include +#include +#include +#include +#include +#include using namespace std; +using namespace log4cxx; +using namespace cfacter::util; using namespace cfacter::facts; +using boost::format; +namespace po = boost::program_options; -void help() +static LoggerPtr logger = Logger::getLogger("cfacter.main"); + +void help(po::options_description& desc) { cout << - "Synopsis\n" - "========\n" - "\n" - "Collect and display facts about the system.\n" - "\n" - "Usage\n" - "=====\n" - "\n" - " cfacter [-h|--help] [-v|--version] [-j|--json] [fact] [fact] [...]\n" - "\n" - "Description\n" - "===========\n" - "\n" - "Collect and display facts about the current system. The library behind\n" - "Facter is easy to expand, making Facter an easy way to collect information\n" - "about a system.\n" - "\n" - "If no facts are specifically asked for, then all facts will be returned.\n" - "\n" - "EXAMPLE\n" - "=======\n" - " cfacter kernel\n" - "\n" - "USAGE\n" - "=====\n" - " -j, --json Emit facts in JSON format.\n" - " -v, --version Print the version and exit.\n" - " -h, --help Print this help message and exit.\n" - ""; + "Synopsis\n" + "========\n" + "\n" + "Collect and display facts about the system.\n" + "\n" + "Usage\n" + "=====\n" + "\n" + " cfacter [options] [fact] [fact] [...]\n" + "\n" + "Options\n" + "=======\n\n" << desc << + "\nDescription\n" + "===========\n" + "\n" + "Collect and display facts about the current system. The library behind\n" + "cfacter is easy to expand, making cfacter an easy way to collect information\n" + "about a system.\n" + "\n" + "If no facts are specifically asked for, then all facts will be returned.\n" + "\n" + "Example\n" + "=======\n\n" + " cfacter kernel\n"; } -void version() +void configure_logger(LevelPtr level, string const& properties_file) { - cout << CFACTER_VERSION << endl; + if (!properties_file.empty() && file::exists(properties_file)) { + PropertyConfigurator::configure(properties_file); + return; + } + + // If no configuration file given, use default settings + LayoutPtr layout = new PatternLayout("%d %-5p %c - %m%n"); + AppenderPtr appender = new ConsoleAppender(layout); + Logger::getRootLogger()->addAppender(appender); + Logger::getRootLogger()->setLevel(level); } -int main(int argc, char **argv) +void log_command_line(int argc, char** argv) { - // TODO: use Boost.Program_options? - static struct option long_options[] = { - {"help", no_argument, nullptr, 'h'}, - {"json", no_argument, nullptr, 'j'}, - {"version", no_argument, nullptr, 'v'}, - {nullptr, 0, nullptr, 0} - }; - - // loop over all of the options - int ch; - while ((ch = getopt_long(argc, argv, "hjv", long_options, nullptr)) != -1) { - // check to see if a single character or long option came through - switch (ch) { - // short option 'v' - case 'v': - version(); - exit(0); - - // short option 'h' - case 'h': - help(); - exit(0); - - // short option 'j' - case 'j': - // TODO: properly support this - break; + if (!logger->isInfoEnabled()) { + return; + } + ostringstream command_line; + for (int i = 1; i < argc; ++i) { + if (command_line.tellp() != 0) { + command_line << ' '; } + command_line << argv[i]; + } + LOG4CXX_INFO(logger, (format("Executed with command line: %1%") % command_line.str()).str()); +} + +void log_requested_facts(vector const& facts) +{ + if (!logger->isInfoEnabled()) { + return; } + ostringstream requested_facts; + for (auto const& fact : facts) { + if (requested_facts.tellp() != 0) { + requested_facts << ' '; + } + requested_facts << fact; + } + LOG4CXX_INFO(logger, (format("Resolving requested facts: %1%") % requested_facts.str()).str()); +} + +int main(int argc, char **argv) +{ + try + { + string properties_file; + + // Build a list of options visible on the command line + // Keep this list sorted alphabetically + po::options_description visible_options(""); + visible_options.add_options() + ("debug,d", "Enable debug output.") + ("help", "Print this help message.") + ("propfile,p", po::value(&properties_file), "Configure logging with a log4cxx properties file.") + ("verbose", "Enable verbose (info) output.") + ("version,v", "Print the version and exit."); + + // Build a list of "hidden" options that are not visible on the command line + po::options_description hidden_options(""); + hidden_options.add_options() + ("fact", po::value>()); - // TODO: re-implement JSON output - fact_map::instance().each([](string const& name, value const* val) { - cout << name << " => " << (!val ? "" : val->to_string()) << "\n"; - return false; - }); + // Create the supported command line options (visible + hidden) + po::options_description command_line_options; + command_line_options.add(visible_options).add(hidden_options); + + // Build a list of positional options (in our case, just fact names) + po::positional_options_description positional_options; + positional_options.add("fact", -1); + + po::variables_map vm; + try { + po::store(po::command_line_parser(argc, argv). + options(command_line_options).positional(positional_options).run(), vm); + + // Check for a help option first before notifying + if (vm.count("help")) { + help(visible_options); + return EXIT_SUCCESS; + } + + po::notify(vm); + } + catch(po::error& ex) { + cerr << "error: " << ex.what() << "\n\n"; + help(visible_options); + return EXIT_FAILURE; + } + + // Check for printing the version + if (vm.count("version")) { + cout << CFACTER_VERSION << endl; + return EXIT_SUCCESS; + } + + // Get the logging level + LevelPtr log_level = Level::getWarn(); + if (vm.count("debug")) { + log_level = Level::getDebug(); + } else if (vm.count("verbose")) { + log_level = Level::getInfo(); + } + + // Configure the logger + configure_logger(log_level, properties_file); + log_command_line(argc, argv); + + fact_map& facts = fact_map::instance(); + if (vm.count("fact")) { + auto const& requested_facts = vm["fact"].as>(); + log_requested_facts(requested_facts); + + // Print only the given facts + for (auto const& fact : requested_facts) { + auto value = facts[fact]; + if (!value) { + continue; + } + cout << format("%1% => %2%\n") % fact % value->to_string(); + } + } else { + LOG4CXX_INFO(logger, "Resolving all facts."); + + // Print all facts in the map + facts.each([](string const& name, value const* value) { + if (value) { + cout << format("%1% => %2%\n") % name % value->to_string(); + } + return false; + }); + } + } catch (exception& ex) { + LOG4CXX_FATAL(logger, (format("Unhandled exception: %1%") % ex.what()).str()); + return EXIT_FAILURE; + } } diff --git a/lib/inc/facts/fact_map.hpp b/lib/inc/facts/fact_map.hpp index e45ec9ee4f..afd384cb94 100644 --- a/lib/inc/facts/fact_map.hpp +++ b/lib/inc/facts/fact_map.hpp @@ -93,6 +93,16 @@ namespace cfacter { namespace facts { return dynamic_cast(get_value(name, resolve)); } + /** + * Gets a fact value by name + * @param name The name of the fact to get the value of. + * @return Returns a pointer to the fact value or nullptr if the fact is not in the map. + */ + value const* operator[](std::string const& name) + { + return get_value(name, true); + } + /** * Enumerates all facts in the map. * @param func The callback function called for each fact in the map. diff --git a/lib/inc/facts/fact_resolver.hpp b/lib/inc/facts/fact_resolver.hpp index 53abb8acfe..d126329c47 100644 --- a/lib/inc/facts/fact_resolver.hpp +++ b/lib/inc/facts/fact_resolver.hpp @@ -66,10 +66,11 @@ namespace cfacter { namespace facts { { /** * Constructs a fact_resolver. + * @param name The fact resolver name. * @param names The fact names the resolver is responsible for. * @param patterns Regular expression patterns for additional ("dynamic") facts the resolver is responsible for. */ - fact_resolver(std::vector&& names, std::vector const& patterns = {}); + fact_resolver(std::string&& name, std::vector&& names, std::vector const& patterns = {}); /** * Destructs the fact_resolver. @@ -84,6 +85,12 @@ namespace cfacter { namespace facts { fact_resolver(fact_resolver&&) = default; fact_resolver& operator=(fact_resolver&&) = default; + /** + * Gets the name of the fact resolver. + * @return Returns the fact resolver's name. + */ + std::string const& name() const { return _name; } + /** * Gets the fact names the resolver is responsible for resolving. * @return Returns a vector of fact names. @@ -112,6 +119,7 @@ namespace cfacter { namespace facts { virtual void resolve_facts(fact_map& facts) = 0; private: + std::string _name; std::vector _names; std::vector> _regexes; bool _resolving; diff --git a/lib/inc/facts/linux/lsb_resolver.hpp b/lib/inc/facts/linux/lsb_resolver.hpp index ba016f116d..667072e493 100644 --- a/lib/inc/facts/linux/lsb_resolver.hpp +++ b/lib/inc/facts/linux/lsb_resolver.hpp @@ -15,7 +15,9 @@ namespace cfacter { namespace facts { namespace linux { * Constructs the lsb_resolver. */ lsb_resolver() : - fact_resolver({ + fact_resolver( + "Linux Standard Base", + { fact::lsb_dist_id, fact::lsb_dist_release, fact::lsb_dist_codename, diff --git a/lib/inc/facts/posix/kernel_resolver.hpp b/lib/inc/facts/posix/kernel_resolver.hpp index 489638cb14..e28dc1fe11 100644 --- a/lib/inc/facts/posix/kernel_resolver.hpp +++ b/lib/inc/facts/posix/kernel_resolver.hpp @@ -16,7 +16,9 @@ namespace cfacter { namespace facts { namespace posix { * Constructs the kernel_resolver. */ kernel_resolver() : - fact_resolver({ + fact_resolver( + "kernel", + { fact::kernel, fact::kernel_version, fact::kernel_release, diff --git a/lib/inc/facts/posix/networking_resolver.hpp b/lib/inc/facts/posix/networking_resolver.hpp index cb57d0fe37..6afcb7477d 100644 --- a/lib/inc/facts/posix/networking_resolver.hpp +++ b/lib/inc/facts/posix/networking_resolver.hpp @@ -15,8 +15,10 @@ namespace cfacter { namespace facts { namespace posix { /** * Constructs the networking_resolver. */ - networking_resolver() : - fact_resolver({ + explicit networking_resolver() : + fact_resolver( + "networking", + { fact::hostname, fact::ipaddress, fact::ipaddress6, @@ -24,7 +26,8 @@ namespace cfacter { namespace facts { namespace posix { fact::network, fact::macaddress, fact::interfaces, - }, { + }, + { std::string("^") + fact::ipaddress + "_", std::string("^") + fact::ipaddress6 + "_", std::string("^") + fact::mtu + "_", diff --git a/lib/inc/facts/posix/operating_system_resolver.hpp b/lib/inc/facts/posix/operating_system_resolver.hpp index f2b3326e9b..1c22ae49ad 100644 --- a/lib/inc/facts/posix/operating_system_resolver.hpp +++ b/lib/inc/facts/posix/operating_system_resolver.hpp @@ -15,7 +15,9 @@ namespace cfacter { namespace facts { namespace posix { * Constructs the operating_system_resolver. */ operating_system_resolver() : - fact_resolver({ + fact_resolver( + "operating system", + { fact::operating_system, fact::os_family, fact::operating_system_release, diff --git a/lib/src/execution/posix/execution.cc b/lib/src/execution/posix/execution.cc index f145bcb698..95e5095ceb 100644 --- a/lib/src/execution/posix/execution.cc +++ b/lib/src/execution/posix/execution.cc @@ -4,15 +4,38 @@ #include #include #include - #include +#include +#include using namespace std; using namespace cfacter::util; using namespace cfacter::util::posix; +using namespace log4cxx; +using boost::format; + +static LoggerPtr logger = Logger::getLogger("cfacter.execution.posix"); namespace cfacter { namespace execution { + void log_execution(string const& file, vector const* arguments) + { + if (!logger->isDebugEnabled()) { + return; + } + + ostringstream command_line; + command_line << file; + + if (arguments) { + for (auto const& argument : *arguments) { + command_line << ' ' << argument; + } + } + + LOG4CXX_DEBUG(logger, (format("Executing command: %1%") % command_line.str()).str()); + } + static string execute( string const& file, vector const* arguments, @@ -49,6 +72,8 @@ namespace cfacter { namespace execution { vector const* environment, option_set const& options) { + log_execution(file, arguments); + // Create the pipes for stdin/stdout/stderr redirection int pipes[2]; if (pipe(pipes) < 0) { @@ -94,23 +119,26 @@ namespace cfacter { namespace execution { while (count > 0); // Wait for the child to exit + string result = output.str(); int status; waitpid(child, &status, 0); if (WIFEXITED(status)) { - status = WEXITSTATUS(status); + status = static_cast(WEXITSTATUS(status)); + LOG4CXX_DEBUG(logger, (format("Process exited with status code %1% and output: %2%") % status % result).str()); if (status != 0 && options[execution_options::throw_on_nonzero_exit]) { - throw child_exit_exception(status, output.str(), "child process returned non-zero exit status."); + throw child_exit_exception(status, result, "child process returned non-zero exit status."); } } else if (WIFSIGNALED(status)) { - status = WTERMSIG(status); + status = static_cast(WTERMSIG(status)); + LOG4CXX_DEBUG(logger, (format("Process was signaled with signal %1% and output: %2%") % status % result).str()); if (options[execution_options::throw_on_signal]) { - throw child_signal_exception(status, output.str(), "child process was terminated by signal."); + throw child_signal_exception(status, result, "child process was terminated by signal."); } } if (options[execution_options::trim_output]) { - return trim(output.str()); + return trim(result); } - return output.str(); + return result; } // Child continues here diff --git a/lib/src/facts/bsd/networking_resolver.cc b/lib/src/facts/bsd/networking_resolver.cc index f1bed09919..638aa3bdee 100644 --- a/lib/src/facts/bsd/networking_resolver.cc +++ b/lib/src/facts/bsd/networking_resolver.cc @@ -8,10 +8,16 @@ #include #include #include +#include +#include using namespace std; using namespace cfacter::util; using namespace cfacter::util::bsd; +using namespace log4cxx; +using boost::format; + +static LoggerPtr logger = Logger::getLogger("cfacter.facts.bsd.network_resolver"); namespace cfacter { namespace facts { namespace bsd { @@ -20,7 +26,7 @@ namespace cfacter { namespace facts { namespace bsd { // Scope the head ifaddrs ptr scoped_ifaddrs addrs; if (!addrs) { - // TODO: log failure + LOG4CXX_WARN(logger, (format("getifaddrs failed with %1%: interface facts unavailable.") % errno).str()); return; } diff --git a/lib/src/facts/fact_map.cc b/lib/src/facts/fact_map.cc index facbcb0417..68363f1e30 100644 --- a/lib/src/facts/fact_map.cc +++ b/lib/src/facts/fact_map.cc @@ -1,7 +1,13 @@ #include #include +#include +#include using namespace std; +using namespace log4cxx; +using boost::format; + +static LoggerPtr logger = Logger::getLogger("cfacter.facts.fact_map"); namespace cfacter { namespace facts { @@ -43,6 +49,8 @@ namespace cfacter { namespace facts { throw fact_exists_exception("fact " + name + " already exists."); } + LOG4CXX_DEBUG(logger, (format("fact %1% has resolved to \"%2%\".") % name % (value ? value->to_string() : "")).str()); + // Remove any mapped resolver for this fact _resolver_map.erase(name); _facts.insert(it, make_pair(move(name), move(value))); diff --git a/lib/src/facts/fact_resolver.cc b/lib/src/facts/fact_resolver.cc index e37184343c..1c52755a58 100644 --- a/lib/src/facts/fact_resolver.cc +++ b/lib/src/facts/fact_resolver.cc @@ -1,13 +1,20 @@ #include #include #include +#include +#include using namespace std; using namespace re2; +using namespace log4cxx; +using boost::format; + +static LoggerPtr logger = Logger::getLogger("cfacter.facts.fact_resolver"); namespace cfacter { namespace facts { - fact_resolver::fact_resolver(vector&& names, vector const& patterns) : + fact_resolver::fact_resolver(string&& name, vector&& names, vector const& patterns) : + _name(move(name)), _names(move(names)), _resolving(false) { @@ -26,6 +33,7 @@ namespace cfacter { namespace facts { void fact_resolver::resolve(fact_map& facts) { + LOG4CXX_DEBUG(logger, (format("resolving %1% facts.") % _name).str()); if (_resolving) { throw circular_resolution_exception("a cycle in fact resolution was detected."); } diff --git a/lib/src/facts/linux/networking_resolver.cc b/lib/src/facts/linux/networking_resolver.cc index 42c13d03ba..ee09f87960 100644 --- a/lib/src/facts/linux/networking_resolver.cc +++ b/lib/src/facts/linux/networking_resolver.cc @@ -6,9 +6,15 @@ #include #include #include +#include +#include using namespace std; using namespace cfacter::util::posix; +using namespace log4cxx; +using boost::format; + +static LoggerPtr logger = Logger::getLogger("cfacter.facts.linux.network_resolver"); namespace cfacter { namespace facts { namespace linux { @@ -40,7 +46,7 @@ namespace cfacter { namespace facts { namespace linux { scoped_descriptor sock(socket(AF_INET, SOCK_DGRAM, 0)); if (ioctl(sock, SIOCGIFMTU, &req) != 0) { - // TODO: log failure + LOG4CXX_WARN(logger, (format("ioctl failed with %1%: interface MTU fact unavailable.") % errno).str()); return -1; } return req.ifr_mtu; diff --git a/lib/src/facts/posix/kernel_resolver.cc b/lib/src/facts/posix/kernel_resolver.cc index 7ace3e1509..522909f48a 100644 --- a/lib/src/facts/posix/kernel_resolver.cc +++ b/lib/src/facts/posix/kernel_resolver.cc @@ -3,9 +3,15 @@ #include #include #include +#include +#include using namespace std; using namespace cfacter::util; +using namespace log4cxx; +using boost::format; + +static LoggerPtr logger = Logger::getLogger("cfacter.facts.posix.kernel_resolver"); namespace cfacter { namespace facts { namespace posix { @@ -14,6 +20,7 @@ namespace cfacter { namespace facts { namespace posix { utsname name; memset(&name, 0, sizeof(name)); if (uname(&name) != 0) { + LOG4CXX_WARN(logger, (format("uname failed with %1%: kernel facts unavailable.") % errno).str()); return; } // Resolve all kernel-related facts diff --git a/lib/src/facts/posix/networking_resolver.cc b/lib/src/facts/posix/networking_resolver.cc index 51eba0416e..6dd5f742e1 100644 --- a/lib/src/facts/posix/networking_resolver.cc +++ b/lib/src/facts/posix/networking_resolver.cc @@ -10,8 +10,14 @@ #include #include #include +#include +#include using namespace std; +using namespace log4cxx; +using boost::format; + +static LoggerPtr logger = Logger::getLogger("cfacter.facts.posix.networking_resolver"); namespace cfacter { namespace facts { namespace posix { @@ -26,7 +32,7 @@ namespace cfacter { namespace facts { namespace posix { int max = sysconf(_SC_HOST_NAME_MAX); vector name(max); if (gethostname(name.data(), max) != 0) { - // TODO: log + LOG4CXX_WARN(logger, (format("gethostname failed with %1%: hostname fact unavailable.") % errno).str()); return; } @@ -99,15 +105,10 @@ namespace cfacter { namespace facts { namespace posix { return {}; } - ostringstream stream; - for (size_t i = 0; i < 6; ++i) { - if (stream.tellp() != 0) { - stream << ":"; - } - stream << hex << setw(2) << setfill('0') << static_cast(bytes[i]); - } - - return stream.str(); + return (format("%02x:%02x:%02x:%02x:%02x:%02x") % + static_cast(bytes[0]) % static_cast(bytes[1]) % + static_cast(bytes[2]) % static_cast(bytes[3]) % + static_cast(bytes[4]) % static_cast(bytes[5])).str(); } }}} // namespace cfacter::facts::posix From 23c8d769ef00f5716e01bfcb845085b4db6b2af9 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Mon, 21 Apr 2014 23:59:07 -0700 Subject: [PATCH 1796/3753] (maint) Fix build errors on Linux. Fixing build error caused by a warning coming from Boost about an unused local typedef. We can simply ignore this type of warning. Fixing the shared object file name for log4cxx under Linux. Fixing the log4cxx patch to build on Linux. Fixing the file extensions for apr, aprutil, and boost. --- exe/CMakeLists.txt | 9 ++++++++- vendor/apr.cmake | 2 +- vendor/aprutil.cmake | 2 +- vendor/boost.cmake | 2 +- vendor/log4cxx.cmake | 2 +- vendor/log4cxx.patch | 38 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 50 insertions(+), 5 deletions(-) diff --git a/exe/CMakeLists.txt b/exe/CMakeLists.txt index 9b745b82f3..e280b2e1e1 100644 --- a/exe/CMakeLists.txt +++ b/exe/CMakeLists.txt @@ -8,7 +8,14 @@ set(LIBCFACTER_DIR ../lib) add_subdirectory(${LIBCFACTER_DIR} ${LIBCFACTER_DIR}) -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra -Werror -Wno-unused-parameter") +# Set compiler-specific flags +if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra -Werror -Wno-unused-parameter -Wno-tautological-constant-out-of-range-compare") +elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra -Werror -Wno-unused-parameter -Wno-unused-local-typedefs") +elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel") +elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") +endif() include_directories( ${LIBCFACTER_DIR}/inc diff --git a/vendor/apr.cmake b/vendor/apr.cmake index 23bd917d97..44b40c36b3 100644 --- a/vendor/apr.cmake +++ b/vendor/apr.cmake @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 2.8.12) include(ExternalProject) -set(APR_SHARED_OBJECT_FILE "libapr-1.0.so") +set(APR_SHARED_OBJECT_FILE "libapr-1.so.0.5.0") if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") set(APR_SHARED_OBJECT_FILE "libapr-1.0.dylib") diff --git a/vendor/aprutil.cmake b/vendor/aprutil.cmake index 5c3f3c9b25..f216b1059e 100644 --- a/vendor/aprutil.cmake +++ b/vendor/aprutil.cmake @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 2.8.12) include(ExternalProject) include(${VENDOR_DIRECTORY}/apr.cmake) -set(APR_UTIL_SHARED_OBJECT_FILE "libaprutil-1.0.so") +set(APR_UTIL_SHARED_OBJECT_FILE "libaprutil-1.so.0.5.3") if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") set(APR_UTIL_SHARED_OBJECT_FILE "libaprutil-1.0.dylib") diff --git a/vendor/boost.cmake b/vendor/boost.cmake index 7c9ba4e51b..a17ecb2fd1 100644 --- a/vendor/boost.cmake +++ b/vendor/boost.cmake @@ -35,7 +35,7 @@ endif() if (APPLE) set(BOOST_SO_EXT "dylib") else() - set(BOOST_SO_EXT "so") + set(BOOST_SO_EXT "so.1.55.0") endif() foreach(BOOST_LIBRARY ${BOOST_TO_BUILD}) diff --git a/vendor/log4cxx.cmake b/vendor/log4cxx.cmake index 72c827229b..d24ae1be85 100644 --- a/vendor/log4cxx.cmake +++ b/vendor/log4cxx.cmake @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 2.8.12) include(ExternalProject) include(${VENDOR_DIRECTORY}/aprutil.cmake) -set(LOG4CXX_SHARED_OBJECT_FILE "liblog4cxx.10.0.0.so") +set(LOG4CXX_SHARED_OBJECT_FILE "liblog4cxx.so.10.0.0") if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") set(LOG4CXX_SHARED_OBJECT_FILE "liblog4cxx.10.0.0.dylib") diff --git a/vendor/log4cxx.patch b/vendor/log4cxx.patch index 06cb4c3dbb..e5ca00936f 100644 --- a/vendor/log4cxx.patch +++ b/vendor/log4cxx.patch @@ -20,3 +20,41 @@ #include +#include #include + + +--- a/src/main/cpp/inputstreamreader.cpp ++++ b/src/main/cpp/inputstreamreader.cpp +@@ -20,7 +20,9 @@ + #include + #include + #include ++#include + ++using namespace std; + using namespace log4cxx; + using namespace log4cxx::helpers; + +--- a/src/main/cpp/socketoutputstream.cpp ++++ b/src/main/cpp/socketoutputstream.cpp +@@ -19,7 +19,9 @@ + #include + #include + #include ++#include + ++using namespace std; + using namespace log4cxx; + using namespace log4cxx::helpers; + +--- a/src/examples/cpp/console.cpp ++++ b/src/examples/cpp/console.cpp +@@ -22,7 +22,10 @@ + #include + #include + #include ++#include ++#include + ++using namespace std; + using namespace log4cxx; + using namespace log4cxx::helpers; From 63f48a0f10e3ce22ed19ae075d168973a49d87c4 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Tue, 22 Apr 2014 11:36:13 -0700 Subject: [PATCH 1797/3753] (maint) Prevent external projects from always building. There's no reason to pass ALWAYS 1 to the external projects. This prevents us from relinking cfacter on OSX because we're fixing boost's install names. --- vendor/apr.cmake | 1 - vendor/aprutil.cmake | 1 - vendor/boost.cmake | 2 -- vendor/log4cxx.cmake | 1 - vendor/rapidjson.cmake | 1 - vendor/re2.cmake | 1 - 6 files changed, 7 deletions(-) diff --git a/vendor/apr.cmake b/vendor/apr.cmake index 44b40c36b3..37c8ce2e3a 100644 --- a/vendor/apr.cmake +++ b/vendor/apr.cmake @@ -22,7 +22,6 @@ externalproject_add( BUILD_COMMAND make BUILD_IN_SOURCE 1 INSTALL_COMMAND "" - ALWAYS 1 ) # Set some useful variables based on the source directory diff --git a/vendor/aprutil.cmake b/vendor/aprutil.cmake index f216b1059e..777eacedb2 100644 --- a/vendor/aprutil.cmake +++ b/vendor/aprutil.cmake @@ -22,7 +22,6 @@ externalproject_add( BUILD_COMMAND make BUILD_IN_SOURCE 1 INSTALL_COMMAND "" - ALWAYS 1 ) add_dependencies(aprutil apr) diff --git a/vendor/boost.cmake b/vendor/boost.cmake index a17ecb2fd1..adca799ede 100644 --- a/vendor/boost.cmake +++ b/vendor/boost.cmake @@ -14,7 +14,6 @@ externalproject_add( BUILD_COMMAND ./b2 BUILD_IN_SOURCE 1 INSTALL_COMMAND "" - ALWAYS 1 ) # Set some useful variables based on the source directory @@ -27,7 +26,6 @@ if(APPLE) installnames COMMAND ${PROJECT_SOURCE_DIR}/scripts/osx_boost_names.sh ${SOURCE_DIR}/stage/lib COMMENT "Fixing boost install names" - ALWAYS 1 DEPENDEES build ) endif() diff --git a/vendor/log4cxx.cmake b/vendor/log4cxx.cmake index d24ae1be85..5513ec3742 100644 --- a/vendor/log4cxx.cmake +++ b/vendor/log4cxx.cmake @@ -24,7 +24,6 @@ externalproject_add( BUILD_COMMAND make BUILD_IN_SOURCE 1 INSTALL_COMMAND "" - ALWAYS 1 ) add_dependencies(log4cxx aprutil) diff --git a/vendor/rapidjson.cmake b/vendor/rapidjson.cmake index 371d04726d..5ac393e98d 100644 --- a/vendor/rapidjson.cmake +++ b/vendor/rapidjson.cmake @@ -11,7 +11,6 @@ externalproject_add( BUILD_COMMAND "" BUILD_IN_SOURCE 1 INSTALL_COMMAND "" - ALWAYS 1 ) # Set some useful variables based on the source directory diff --git a/vendor/re2.cmake b/vendor/re2.cmake index 4032b8b1ec..892bee92a7 100644 --- a/vendor/re2.cmake +++ b/vendor/re2.cmake @@ -22,7 +22,6 @@ externalproject_add( BUILD_COMMAND make CPPFLAGS=${RE2_CPP_FLAGS} LDFLAGS=${RE2_LD_FLAGS} BUILD_IN_SOURCE 1 INSTALL_COMMAND "" - ALWAYS 1 ) # Set some useful variables based on the source directory From 7ab68cd087f45ec8ca4603b1cadf2e20943e4531 Mon Sep 17 00:00:00 2001 From: Leslie Carr Date: Mon, 31 Mar 2014 13:18:43 -0700 Subject: [PATCH 1798/3753] (FACT-451) Add Cumulus Linux distro to Debian OS family Cumulus Linux should report Debian for the osfamily fact. --- lib/facter/osfamily.rb | 2 +- spec/unit/osfamily_spec.rb | 61 +++++++++++++++++++------------------- 2 files changed, 32 insertions(+), 31 deletions(-) diff --git a/lib/facter/osfamily.rb b/lib/facter/osfamily.rb index b9c27c1ae0..1a8536e871 100644 --- a/lib/facter/osfamily.rb +++ b/lib/facter/osfamily.rb @@ -18,7 +18,7 @@ case Facter.value(:operatingsystem) when "RedHat", "Fedora", "CentOS", "Scientific", "SLC", "Ascendos", "CloudLinux", "PSBM", "OracleLinux", "OVS", "OEL", "Amazon", "XenServer" "RedHat" - when "LinuxMint", "Ubuntu", "Debian" + when "LinuxMint", "Ubuntu", "Debian", "CumulusLinux" "Debian" when "SLES", "SLED", "OpenSuSE", "SuSE" "Suse" diff --git a/spec/unit/osfamily_spec.rb b/spec/unit/osfamily_spec.rb index 64180c3569..76fc7cfeac 100644 --- a/spec/unit/osfamily_spec.rb +++ b/spec/unit/osfamily_spec.rb @@ -5,36 +5,37 @@ describe "OS Family fact" do { - 'Archlinux' => 'Archlinux', - 'SmartOS' => 'Solaris', - 'OpenIndiana' => 'Solaris', - 'OmniOS' => 'Solaris', - 'Nexenta' => 'Solaris', - 'Solaris' => 'Solaris', - 'Ubuntu' => 'Debian', - 'Debian' => 'Debian', - 'LinuxMint' => 'Debian', - 'Gentoo' => 'Gentoo', - 'Fedora' => 'RedHat', - 'Amazon' => 'RedHat', - 'OracleLinux' => 'RedHat', - 'OVS' => 'RedHat', - 'OEL' => 'RedHat', - 'CentOS' => 'RedHat', - 'SLC' => 'RedHat', - 'Scientific' => 'RedHat', - 'CloudLinux' => 'RedHat', - 'PSBM' => 'RedHat', - 'Ascendos' => 'RedHat', - 'XenServer' => 'RedHat', - 'RedHat' => 'RedHat', - 'SLES' => 'Suse', - 'SLED' => 'Suse', - 'OpenSuSE' => 'Suse', - 'SuSE' => 'Suse', - 'Mageia' => 'Mandrake', - 'Mandriva' => 'Mandrake', - 'Mandrake' => 'Mandrake' + 'Archlinux' => 'Archlinux', + 'SmartOS' => 'Solaris', + 'OpenIndiana' => 'Solaris', + 'OmniOS' => 'Solaris', + 'Nexenta' => 'Solaris', + 'Solaris' => 'Solaris', + 'Ubuntu' => 'Debian', + 'Debian' => 'Debian', + 'LinuxMint' => 'Debian', + 'CumulusLinux' => 'Debian', + 'Gentoo' => 'Gentoo', + 'Fedora' => 'RedHat', + 'Amazon' => 'RedHat', + 'OracleLinux' => 'RedHat', + 'OVS' => 'RedHat', + 'OEL' => 'RedHat', + 'CentOS' => 'RedHat', + 'SLC' => 'RedHat', + 'Scientific' => 'RedHat', + 'CloudLinux' => 'RedHat', + 'PSBM' => 'RedHat', + 'Ascendos' => 'RedHat', + 'XenServer' => 'RedHat', + 'RedHat' => 'RedHat', + 'SLES' => 'Suse', + 'SLED' => 'Suse', + 'OpenSuSE' => 'Suse', + 'SuSE' => 'Suse', + 'Mageia' => 'Mandrake', + 'Mandriva' => 'Mandrake', + 'Mandrake' => 'Mandrake' }.each do |os,family| it "should return #{family} on operatingsystem #{os}" do Facter.fact(:operatingsystem).stubs(:value).returns os From 919034f4de449bd56f60de6e7f748046aede76c0 Mon Sep 17 00:00:00 2001 From: Leslie Carr Date: Mon, 31 Mar 2014 13:18:43 -0700 Subject: [PATCH 1799/3753] (FACT-451) Add Cumulus Linux distro to Debian OS family Cumulus Linux should report Debian for the osfamily fact. Conflicts: lib/facter/osfamily.rb spec/unit/osfamily_spec.rb --- lib/facter/osfamily.rb | 2 +- spec/unit/osfamily_spec.rb | 59 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 spec/unit/osfamily_spec.rb diff --git a/lib/facter/osfamily.rb b/lib/facter/osfamily.rb index 6162ef8929..f7008c1d42 100644 --- a/lib/facter/osfamily.rb +++ b/lib/facter/osfamily.rb @@ -18,7 +18,7 @@ case Facter.value(:operatingsystem) when "RedHat", "Fedora", "CentOS", "Scientific", "SLC", "Ascendos", "CloudLinux", "PSBM", "OracleLinux", "OVS", "OEL", "Amazon", "XenServer" "RedHat" - when "Ubuntu", "Debian" + when "Ubuntu", "Debian", "CumulusLinux" "Debian" when "SLES", "SLED", "OpenSuSE", "SuSE" "Suse" diff --git a/spec/unit/osfamily_spec.rb b/spec/unit/osfamily_spec.rb new file mode 100644 index 0000000000..d0a1210501 --- /dev/null +++ b/spec/unit/osfamily_spec.rb @@ -0,0 +1,59 @@ +#! /usr/bin/env ruby + +require 'spec_helper' + +describe "OS Family fact" do + + { + 'Archlinux' => 'Archlinux', + 'SmartOS' => 'Solaris', + 'OpenIndiana' => 'Solaris', + 'OmniOS' => 'Solaris', + 'Nexenta' => 'Solaris', + 'Solaris' => 'Solaris', + 'Ubuntu' => 'Debian', + 'Debian' => 'Debian', + 'CumulusLinux' => 'Debian', + 'Gentoo' => 'Gentoo', + 'Fedora' => 'RedHat', + 'Amazon' => 'RedHat', + 'OracleLinux' => 'RedHat', + 'OVS' => 'RedHat', + 'OEL' => 'RedHat', + 'CentOS' => 'RedHat', + 'SLC' => 'RedHat', + 'Scientific' => 'RedHat', + 'CloudLinux' => 'RedHat', + 'PSBM' => 'RedHat', + 'Ascendos' => 'RedHat', + 'XenServer' => 'RedHat', + 'RedHat' => 'RedHat', + 'SLES' => 'Suse', + 'SLED' => 'Suse', + 'OpenSuSE' => 'Suse', + 'SuSE' => 'Suse', + }.each do |os,family| + it "should return #{family} on operatingsystem #{os}" do + Facter.fact(:operatingsystem).stubs(:value).returns os + Facter.fact(:osfamily).value.should == family + end + end + + [ + 'MeeGo', + 'VMWareESX', + 'Bluewhite64', + 'Slamd64', + 'Slackware', + 'Alpine', + 'ESXi', + 'windows', + 'HP-UX' + ].each do |os| + it "should return the kernel fact on operatingsystem #{os}" do + Facter.fact(:operatingsystem).stubs(:value).returns os + Facter.fact(:kernel).stubs(:value).returns 'random_kernel_fact' + Facter.fact(:osfamily).value.should == 'random_kernel_fact' + end + end +end From 632041b2aba05ee85b431a802288273b922ca639 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Sun, 9 Feb 2014 22:20:24 -0800 Subject: [PATCH 1800/3753] (FACT-189) Add LXC detection to virtual and is_virtual fact Without this patch the virtual and is_virtual facts are returning "physical" and "false" respectively. This is a problem because Linux Containers are a virtual environment and it is useful to configure things differently in lxc when compared to other virtual environments. This patch addresses the problem by detecting if the process is running inside of a linux container by looking at the cgroup entries for the init process. This method is described at http://stackoverflow.com/questions/20010199/determining-if-a-process-runs-inside-lxc-docker# Basically, if /proc/1/cgroup looks like this, we're not in a lxc container: 9:hugetlb:/ 8:perf_event:/ 7:blkio:/ 6:freezer:/ 5:devices:/ 4:memory:/ 3:cpuacct:/ 2:cpu:/ 1:cpuset:/ But if they look like this then we are in a container: 9:hugetlb:/lxc/e411045bbbc61eca5d3af7eb0764c30833606f51d20c176f406afbdb47bb04be 8:perf_event:/lxc/e411045bbbc61eca5d3af7eb0764c30833606f51d20c176f406afbdb47bb04be 7:blkio:/lxc/e411045bbbc61eca5d3af7eb0764c30833606f51d20c176f406afbdb47bb04be 6:freezer:/lxc/e411045bbbc61eca5d3af7eb0764c30833606f51d20c176f406afbdb47bb04be 5:devices:/lxc/e411045bbbc61eca5d3af7eb0764c30833606f51d20c176f406afbdb47bb04be 4:memory:/lxc/e411045bbbc61eca5d3af7eb0764c30833606f51d20c176f406afbdb47bb04be 3:cpuacct:/lxc/e411045bbbc61eca5d3af7eb0764c30833606f51d20c176f406afbdb47bb04be 2:cpu:/lxc/e411045bbbc61eca5d3af7eb0764c30833606f51d20c176f406afbdb47bb04be 1:cpuset:/lxc/e411045bbbc61eca5d3af7eb0764c30833606f51d20c176f406afbdb47bb04be The identifier is the container id and might be useful for a future fact to identify the container Facter is running in. --- lib/facter/util/virtual.rb | 13 ++++++++ lib/facter/virtual.rb | 13 ++++++++ .../virtual/proc_1_cgroup/in_a_container | 9 ++++++ .../virtual/proc_1_cgroup/not_in_a_container | 9 ++++++ spec/unit/util/virtual_spec.rb | 30 +++++++++++++++++++ spec/unit/virtual_spec.rb | 18 +++++++++++ 6 files changed, 92 insertions(+) create mode 100644 spec/fixtures/virtual/proc_1_cgroup/in_a_container create mode 100644 spec/fixtures/virtual/proc_1_cgroup/not_in_a_container diff --git a/lib/facter/util/virtual.rb b/lib/facter/util/virtual.rb index e65583876c..8d225957c4 100644 --- a/lib/facter/util/virtual.rb +++ b/lib/facter/util/virtual.rb @@ -1,4 +1,5 @@ require 'facter/util/file_read' +require 'pathname' module Facter::Util::Virtual ## @@ -130,6 +131,18 @@ def self.hpvm? Facter::Core::Execution.exec("/usr/bin/getconf MACHINE_MODEL").chomp =~ /Virtual Machine/ end + ## + # lxc? returns true if the process is running inside of a linux container. + # Implementation derived from + # http://stackoverflow.com/questions/20010199/determining-if-a-process-runs-inside-lxc-docker + def self.lxc? + path = Pathname.new('/proc/1/cgroup') + return false unless path.readable? + lxc_hierarchies = path.readlines.map {|l| l.split(":")[2].to_s.start_with? '/lxc/' } + return true if lxc_hierarchies.include?(true) + return false + end + def self.zlinux? "zlinux" end diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 05f5e7948a..516db2e578 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -251,6 +251,19 @@ end end end + +## +# virtual fact specific to linux containers. This also works for Docker +# containers running in lxc. +Facter.add("virtual") do + has_weight 700 + confine :kernel => "Linux" + + setcode do + "lxc" if Facter::Util::Virtual.lxc? + end +end + # Fact: is_virtual # # Purpose: returning true or false for if a machine is virtualised or not. diff --git a/spec/fixtures/virtual/proc_1_cgroup/in_a_container b/spec/fixtures/virtual/proc_1_cgroup/in_a_container new file mode 100644 index 0000000000..bed734fedc --- /dev/null +++ b/spec/fixtures/virtual/proc_1_cgroup/in_a_container @@ -0,0 +1,9 @@ +9:hugetlb:/lxc/e411045bbbc61eca5d3af7eb0764c30833606f51d20c176f406afbdb47bb04be +8:perf_event:/lxc/e411045bbbc61eca5d3af7eb0764c30833606f51d20c176f406afbdb47bb04be +7:blkio:/lxc/e411045bbbc61eca5d3af7eb0764c30833606f51d20c176f406afbdb47bb04be +6:freezer:/lxc/e411045bbbc61eca5d3af7eb0764c30833606f51d20c176f406afbdb47bb04be +5:devices:/lxc/e411045bbbc61eca5d3af7eb0764c30833606f51d20c176f406afbdb47bb04be +4:memory:/lxc/e411045bbbc61eca5d3af7eb0764c30833606f51d20c176f406afbdb47bb04be +3:cpuacct:/lxc/e411045bbbc61eca5d3af7eb0764c30833606f51d20c176f406afbdb47bb04be +2:cpu:/lxc/e411045bbbc61eca5d3af7eb0764c30833606f51d20c176f406afbdb47bb04be +1:cpuset:/lxc/e411045bbbc61eca5d3af7eb0764c30833606f51d20c176f406afbdb47bb04be diff --git a/spec/fixtures/virtual/proc_1_cgroup/not_in_a_container b/spec/fixtures/virtual/proc_1_cgroup/not_in_a_container new file mode 100644 index 0000000000..07c59578ce --- /dev/null +++ b/spec/fixtures/virtual/proc_1_cgroup/not_in_a_container @@ -0,0 +1,9 @@ +9:hugetlb:/ +8:perf_event:/ +7:blkio:/ +6:freezer:/ +5:devices:/ +4:memory:/ +3:cpuacct:/ +2:cpu:/ +1:cpuset:/ diff --git a/spec/unit/util/virtual_spec.rb b/spec/unit/util/virtual_spec.rb index 9414f8ecaf..e9ef65186c 100755 --- a/spec/unit/util/virtual_spec.rb +++ b/spec/unit/util/virtual_spec.rb @@ -291,4 +291,34 @@ context "on windows" do it_should_behave_like "virt-what", "windows", 'c:\windows\system32\virt-what', "NUL" end + + describe '.lxc?' do + subject do + Facter::Util::Virtual.lxc? + end + + fixture_path = fixtures('virtual', 'proc_1_cgroup') + + context '/proc/1/cgroup has at least one hierarchy rooted in /lxc/' do + before :each do + fakepath = Pathname.new(File.join(fixture_path, 'in_a_container')) + Pathname.stubs(:new).with('/proc/1/cgroup').returns(fakepath) + end + + it 'is true' do + subject.should be_true + end + end + + context '/proc/1/cgroup has no hierarchies rooted in /lxc/' do + before :each do + fakepath = Pathname.new(File.join(fixture_path, 'not_in_a_container')) + Pathname.stubs(:new).with('/proc/1/cgroup').returns(fakepath) + end + + it 'is false' do + subject.should be_false + end + end + end end diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index 21afa14414..fe0aa0d6ee 100755 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -6,6 +6,7 @@ describe "Virtual fact" do before(:each) do + Facter::Util::Virtual.stubs(:lxc?).returns(false) Facter::Util::Virtual.stubs(:zone?).returns(false) Facter::Util::Virtual.stubs(:openvz?).returns(false) Facter::Util::Virtual.stubs(:vserver?).returns(false) @@ -170,6 +171,17 @@ Facter.fact(:virtual).value.should == "hyperv" end + context "In a Linux Container (LXC)" do + before :each do + Facter.fact(:kernel).stubs(:value).returns("Linux") + end + + it 'is "lxc" when Facter::Util::Virtual.lxc? is true' do + Facter::Util::Virtual.stubs(:lxc?).returns(true) + Facter.fact(:virtual).value.should == 'lxc' + end + end + context "In Google Compute Engine" do before :each do Facter.fact(:kernel).stubs(:value).returns("Linux") @@ -462,4 +474,10 @@ Facter.fact(:virtual).stubs(:value).returns("ovirt") Facter.fact(:is_virtual).value.should == "true" end + + it "should be true when running in LXC" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:virtual).stubs(:value).returns("lxc") + Facter.fact(:is_virtual).value.should == "true" + end end From a57fd57f78f1d8aad012d5a5b8eea38cb1c4c4cb Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Wed, 23 Apr 2014 15:21:26 -0700 Subject: [PATCH 1801/3753] Revert "(FACT-451) Add Cumulus Linux distro to Debian OS family" This partially reverts commit 919034f4de449bd56f60de6e7f748046aede76c0; keeping the addition of the missing osfamily spec file. The CumulusLinux distro detection isn't present in stable; backing out the change to osfamily for CumulusLinux. --- lib/facter/osfamily.rb | 2 +- spec/unit/osfamily_spec.rb | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/facter/osfamily.rb b/lib/facter/osfamily.rb index f7008c1d42..6162ef8929 100644 --- a/lib/facter/osfamily.rb +++ b/lib/facter/osfamily.rb @@ -18,7 +18,7 @@ case Facter.value(:operatingsystem) when "RedHat", "Fedora", "CentOS", "Scientific", "SLC", "Ascendos", "CloudLinux", "PSBM", "OracleLinux", "OVS", "OEL", "Amazon", "XenServer" "RedHat" - when "Ubuntu", "Debian", "CumulusLinux" + when "Ubuntu", "Debian" "Debian" when "SLES", "SLED", "OpenSuSE", "SuSE" "Suse" diff --git a/spec/unit/osfamily_spec.rb b/spec/unit/osfamily_spec.rb index d0a1210501..f1c16f212b 100644 --- a/spec/unit/osfamily_spec.rb +++ b/spec/unit/osfamily_spec.rb @@ -13,7 +13,6 @@ 'Solaris' => 'Solaris', 'Ubuntu' => 'Debian', 'Debian' => 'Debian', - 'CumulusLinux' => 'Debian', 'Gentoo' => 'Gentoo', 'Fedora' => 'RedHat', 'Amazon' => 'RedHat', From 4199e6b087a6855dfca0825d031932a35963a752 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Wed, 23 Apr 2014 16:42:38 -0700 Subject: [PATCH 1802/3753] (FACT-459) Fix MTU interface facts not being reported for Archlinux. The regular expression being used assumes that MTU is uppercase and followed by a colon. On Archlinux, ifconfig (net-tools 1.60+) reports a lowercase "mtu" followed by a space. --- lib/facter/util/ip.rb | 2 +- spec/unit/interfaces_spec.rb | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/facter/util/ip.rb b/lib/facter/util/ip.rb index 81bb7063f4..5431cf7199 100644 --- a/lib/facter/util/ip.rb +++ b/lib/facter/util/ip.rb @@ -9,7 +9,7 @@ module Facter::Util::IP :ipaddress6 => /inet6 (?:addr: )?((?![fe80|::1])(?>[0-9,a-f,A-F]*\:{1,2})+[0-9,a-f,A-F]{0,4})/, :macaddress => /(?:ether|HWaddr)\s+((\w{1,2}:){5,}\w{1,2})/, :netmask => /(?:Mask:|netmask )([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/, - :mtu => /MTU:(\d+)/ + :mtu => /MTU:?\s*(\d+)/i }, :bsd => { :aliases => [:openbsd, :netbsd, :freebsd, :darwin, :"gnu/kfreebsd", :dragonfly], diff --git a/spec/unit/interfaces_spec.rb b/spec/unit/interfaces_spec.rb index 1b244c50af..cfe78c2c25 100755 --- a/spec/unit/interfaces_spec.rb +++ b/spec/unit/interfaces_spec.rb @@ -4,15 +4,15 @@ require 'shared_formats/parses' require 'facter/util/ip' -shared_examples_for "netmask_* from ifconfig output" do |platform, interface, address, fixture| - it "correctly on #{platform} interface #{interface}" do +shared_examples_for "interface netmask and mtu from ifconfig output" do |platform, interface, netmask, mtu, fixture| + it "should be correct on #{platform} for interface #{interface}" do Facter::Util::IP.stubs(:exec_ifconfig).returns(my_fixture_read(fixture)) Facter::Util::IP.stubs(:get_output_for_interface_and_label). returns(my_fixture_read("#{fixture}.#{interface}")) Facter.collection.internal_loader.load(:interfaces) - fact = Facter.fact("netmask_#{interface}".intern) - fact.value.should eq(address) + Facter.fact("netmask_#{interface}".intern).value.should eq(netmask) + Facter.fact("mtu_#{interface}".intern).value.should eq(mtu) end end @@ -43,15 +43,15 @@ end end -describe "Netmask handling on Linux" do +describe "Netmask and MTU handling on Linux" do before :each do Facter.fact(:kernel).stubs(:value).returns("Linux") end - example_behavior_for "netmask_* from ifconfig output", + example_behavior_for "interface netmask and mtu from ifconfig output", "Archlinux (net-tools 1.60)", "em1", - "255.255.255.0", "ifconfig_net_tools_1.60.txt" - example_behavior_for "netmask_* from ifconfig output", + "255.255.255.0", "1500", "ifconfig_net_tools_1.60.txt" + example_behavior_for "interface netmask and mtu from ifconfig output", "Archlinux (net-tools 1.60)", "lo", - "255.0.0.0", "ifconfig_net_tools_1.60.txt" + "255.0.0.0", "16436", "ifconfig_net_tools_1.60.txt" end From fed391ff0f7fa62e62ba63a80d4850e6f646c65b Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Thu, 24 Apr 2014 13:43:14 -0700 Subject: [PATCH 1803/3753] (CFACT-24) Hide the log4cxx logging implementation. Adding macros that hide the usage of log4cxx and make formatting of messages easier. Instead of using the log4cxx macros, use the LOG_DECLARE_NAMESPACE and LOG_ macros. --- exe/cfacter.cc | 15 +-- lib/CMakeLists.txt | 1 + lib/inc/logging/logging.hpp | 101 +++++++++++++++++++++ lib/src/execution/posix/execution.cc | 16 ++-- lib/src/facts/bsd/networking_resolver.cc | 9 +- lib/src/facts/fact_map.cc | 9 +- lib/src/facts/fact_resolver.cc | 9 +- lib/src/facts/linux/networking_resolver.cc | 9 +- lib/src/facts/posix/kernel_resolver.cc | 9 +- lib/src/facts/posix/networking_resolver.cc | 7 +- lib/src/logging/logging.cc | 46 ++++++++++ 11 files changed, 180 insertions(+), 51 deletions(-) create mode 100644 lib/inc/logging/logging.hpp create mode 100644 lib/src/logging/logging.cc diff --git a/exe/cfacter.cc b/exe/cfacter.cc index 561c166a94..f6268249fd 100644 --- a/exe/cfacter.cc +++ b/exe/cfacter.cc @@ -1,6 +1,7 @@ #include "../version.h" #include #include +#include #include #include #include @@ -16,7 +17,7 @@ using namespace cfacter::facts; using boost::format; namespace po = boost::program_options; -static LoggerPtr logger = Logger::getLogger("cfacter.main"); +LOG_DECLARE_NAMESPACE("main"); void help(po::options_description& desc) { @@ -63,7 +64,7 @@ void configure_logger(LevelPtr level, string const& properties_file) void log_command_line(int argc, char** argv) { - if (!logger->isInfoEnabled()) { + if (!LOG_IS_INFO_ENABLED()) { return; } ostringstream command_line; @@ -73,12 +74,12 @@ void log_command_line(int argc, char** argv) } command_line << argv[i]; } - LOG4CXX_INFO(logger, (format("Executed with command line: %1%") % command_line.str()).str()); + LOG_INFO("Executed with command line: %1%.", command_line.str()); } void log_requested_facts(vector const& facts) { - if (!logger->isInfoEnabled()) { + if (!LOG_IS_INFO_ENABLED()) { return; } ostringstream requested_facts; @@ -88,7 +89,7 @@ void log_requested_facts(vector const& facts) } requested_facts << fact; } - LOG4CXX_INFO(logger, (format("Resolving requested facts: %1%") % requested_facts.str()).str()); + LOG_INFO("Resolving requested facts: %1%.", requested_facts.str()); } int main(int argc, char **argv) @@ -171,7 +172,7 @@ int main(int argc, char **argv) cout << format("%1% => %2%\n") % fact % value->to_string(); } } else { - LOG4CXX_INFO(logger, "Resolving all facts."); + LOG_INFO("Resolving all facts."); // Print all facts in the map facts.each([](string const& name, value const* value) { @@ -182,7 +183,7 @@ int main(int argc, char **argv) }); } } catch (exception& ex) { - LOG4CXX_FATAL(logger, (format("Unhandled exception: %1%") % ex.what()).str()); + LOG_FATAL("Unhandled exception: %1%.", ex.what()); return EXIT_FAILURE; } } diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index b9665eecfd..fdd9eb2aa9 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -18,6 +18,7 @@ set(LIBCFACTER_COMMON_SOURCES "${CMAKE_CURRENT_LIST_DIR}/src/facts/fact_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/string_value.cc" "${CMAKE_CURRENT_LIST_DIR}/src/util/string.cc" + "${CMAKE_CURRENT_LIST_DIR}/src/logging/logging.cc" ) # Set the POSIX sources if on a POSIX platform diff --git a/lib/inc/logging/logging.hpp b/lib/inc/logging/logging.hpp new file mode 100644 index 0000000000..afcfcf4514 --- /dev/null +++ b/lib/inc/logging/logging.hpp @@ -0,0 +1,101 @@ +#ifndef LIB_INC_LOGGING_LOGGING_HPP_H_ +#define LIB_INC_LOGGING_LOGGING_HPP_H_ + +// To use this header, you must: +// - Have log4cxx and Boost on the include path +// - Link in log4cxx +// - Configure log4cxx at runtime before any logging takes place +#include +#include + +#define LOG_DECLARE_NAMESPACE(ns) static log4cxx::LoggerPtr g_logger = log4cxx::Logger::getLogger("puppetlabs.cfacter." ns); +#define LOG_MESSAGE(level, format, ...) \ + do { \ + cfacter::logging::log(g_logger, level, format, ##__VA_ARGS__); \ + } while(0) +#define LOG_DEBUG(format, ...) LOG_MESSAGE(cfacter::logging::log_level::debug, format, ##__VA_ARGS__) +#define LOG_INFO(format, ...) LOG_MESSAGE(cfacter::logging::log_level::info, format, ##__VA_ARGS__) +#define LOG_WARNING(format, ...) LOG_MESSAGE(cfacter::logging::log_level::warning, format, ##__VA_ARGS__) +#define LOG_ERROR(format, ...) LOG_MESSAGE(cfacter::logging::log_level::error, format, ##__VA_ARGS__) +#define LOG_FATAL(format, ...) LOG_MESSAGE(cfacter::logging::log_level::fatal, format, ##__VA_ARGS__) +#define LOG_IS_ENABLED(level) cfacter::logging::is_log_enabled(g_logger, level) +#define LOG_IS_DEBUG_ENABLED() LOG_IS_ENABLED(cfacter::logging::log_level::debug) +#define LOG_IS_INFO_ENABLED() LOG_IS_ENABLED(cfacter::logging::log_level::info) +#define LOG_IS_WARNING_ENABLED() LOG_IS_ENABLED(cfacter::logging::log_level::warning) +#define LOG_IS_ERROR_ENABLED() LOG_IS_ENABLED(cfacter::logging::log_level::error) +#define LOG_IS_FATAL_ENABLED() LOG_IS_ENABLED(cfacter::logging::log_level::fatal) + +namespace cfacter { namespace logging { + + /** + * Represents the supported logging levels. + */ + enum class log_level + { + debug, + info, + warning, + error, + fatal + }; + + /** + * Determines if the given log level is enabled for the given logger. + * @param logger The logger to check. + * @param level The logging level to check. + * @return Returns true if the logging level is enabled or false if it is not. + */ + bool is_log_enabled(log4cxx::LoggerPtr logger, log_level level); + + /** + * Logs a given message to the given logger. + * @param logger The logger to log the message to. + * @param level The logging level to log with. + * @param message The message to log. + */ + void log(log4cxx::LoggerPtr logger, log_level level, std::string const& message); + + /** + * Logs a given format message to the given logger. + * @param logger The logger to log the message to. + * @param level The logging level to log with. + * @param message The message being formatted. + */ + void log(log4cxx::LoggerPtr logger, log_level level, boost::format& message); + + /** + * Logs a given format message to the given logger. + * @tparam T The type of the first argument. + * @tparam TArgs The types of the remaining arguments. + * @param logger The logger to log to. + * @param level The logging level to log with. + * @param message The message being formatted. + * @param arg The first argument to the message. + * @param args The remaining arguments to the message. + */ + template + void log(log4cxx::LoggerPtr logger, log_level level, boost::format& message, T arg, TArgs... args) + { + message % arg; + log(logger, level, message, std::forward(args)...); + } + + /** + * Logs a given format message to the given logger. + * @tparam TArgs The types of the arguments to format the message with. + * @param logger The logger to log to. + * @param level The logging level to log with. + * @param format The message format. + * @param args The remaining arguments to the message. + */ + template + void log(log4cxx::LoggerPtr logger, log_level level, std::string const& format, TArgs... args) + { + boost::format message(format); + log(logger, level, message, std::forward(args)...); + } + +}} // namespace cfacter::logging + +#endif // LIB_INC_LOGGING_LOGGING_HPP_H_ + diff --git a/lib/src/execution/posix/execution.cc b/lib/src/execution/posix/execution.cc index 95e5095ceb..f396473a3a 100644 --- a/lib/src/execution/posix/execution.cc +++ b/lib/src/execution/posix/execution.cc @@ -1,26 +1,23 @@ #include #include #include +#include #include #include #include #include -#include -#include using namespace std; using namespace cfacter::util; using namespace cfacter::util::posix; -using namespace log4cxx; -using boost::format; -static LoggerPtr logger = Logger::getLogger("cfacter.execution.posix"); +LOG_DECLARE_NAMESPACE("execution.posix"); namespace cfacter { namespace execution { void log_execution(string const& file, vector const* arguments) { - if (!logger->isDebugEnabled()) { + if (!LOG_IS_DEBUG_ENABLED()) { return; } @@ -32,8 +29,7 @@ namespace cfacter { namespace execution { command_line << ' ' << argument; } } - - LOG4CXX_DEBUG(logger, (format("Executing command: %1%") % command_line.str()).str()); + LOG_DEBUG("Executing command: %1%", command_line.str()); } static string execute( @@ -124,13 +120,13 @@ namespace cfacter { namespace execution { waitpid(child, &status, 0); if (WIFEXITED(status)) { status = static_cast(WEXITSTATUS(status)); - LOG4CXX_DEBUG(logger, (format("Process exited with status code %1% and output: %2%") % status % result).str()); + LOG_DEBUG("Process exited with status code %1% and output: %2%", status, result); if (status != 0 && options[execution_options::throw_on_nonzero_exit]) { throw child_exit_exception(status, result, "child process returned non-zero exit status."); } } else if (WIFSIGNALED(status)) { status = static_cast(WTERMSIG(status)); - LOG4CXX_DEBUG(logger, (format("Process was signaled with signal %1% and output: %2%") % status % result).str()); + LOG_DEBUG("Process was signaled with signal %1% and output: %2%", status, result); if (options[execution_options::throw_on_signal]) { throw child_signal_exception(status, result, "child process was terminated by signal."); } diff --git a/lib/src/facts/bsd/networking_resolver.cc b/lib/src/facts/bsd/networking_resolver.cc index 638aa3bdee..e5e17fa8d9 100644 --- a/lib/src/facts/bsd/networking_resolver.cc +++ b/lib/src/facts/bsd/networking_resolver.cc @@ -3,21 +3,18 @@ #include #include #include +#include #include #include #include #include #include -#include -#include using namespace std; using namespace cfacter::util; using namespace cfacter::util::bsd; -using namespace log4cxx; -using boost::format; -static LoggerPtr logger = Logger::getLogger("cfacter.facts.bsd.network_resolver"); +LOG_DECLARE_NAMESPACE("facts.bsd.networking"); namespace cfacter { namespace facts { namespace bsd { @@ -26,7 +23,7 @@ namespace cfacter { namespace facts { namespace bsd { // Scope the head ifaddrs ptr scoped_ifaddrs addrs; if (!addrs) { - LOG4CXX_WARN(logger, (format("getifaddrs failed with %1%: interface facts unavailable.") % errno).str()); + LOG_WARNING("getifaddrs failed with %1%: interface facts are unavailable.", errno); return; } diff --git a/lib/src/facts/fact_map.cc b/lib/src/facts/fact_map.cc index 68363f1e30..b955286292 100644 --- a/lib/src/facts/fact_map.cc +++ b/lib/src/facts/fact_map.cc @@ -1,13 +1,10 @@ #include +#include #include -#include -#include using namespace std; -using namespace log4cxx; -using boost::format; -static LoggerPtr logger = Logger::getLogger("cfacter.facts.fact_map"); +LOG_DECLARE_NAMESPACE("facts"); namespace cfacter { namespace facts { @@ -49,7 +46,7 @@ namespace cfacter { namespace facts { throw fact_exists_exception("fact " + name + " already exists."); } - LOG4CXX_DEBUG(logger, (format("fact %1% has resolved to \"%2%\".") % name % (value ? value->to_string() : "")).str()); + LOG_DEBUG("fact %1% has resolved to \"%2%\".", name, value ? value->to_string() : ""); // Remove any mapped resolver for this fact _resolver_map.erase(name); diff --git a/lib/src/facts/fact_resolver.cc b/lib/src/facts/fact_resolver.cc index 1c52755a58..b59263f6c2 100644 --- a/lib/src/facts/fact_resolver.cc +++ b/lib/src/facts/fact_resolver.cc @@ -1,15 +1,12 @@ #include #include +#include #include -#include -#include using namespace std; using namespace re2; -using namespace log4cxx; -using boost::format; -static LoggerPtr logger = Logger::getLogger("cfacter.facts.fact_resolver"); +LOG_DECLARE_NAMESPACE("facts.resolver"); namespace cfacter { namespace facts { @@ -33,7 +30,7 @@ namespace cfacter { namespace facts { void fact_resolver::resolve(fact_map& facts) { - LOG4CXX_DEBUG(logger, (format("resolving %1% facts.") % _name).str()); + LOG_DEBUG("resolving %1% facts.", _name); if (_resolving) { throw circular_resolution_exception("a cycle in fact resolution was detected."); } diff --git a/lib/src/facts/linux/networking_resolver.cc b/lib/src/facts/linux/networking_resolver.cc index ee09f87960..7219cd407e 100644 --- a/lib/src/facts/linux/networking_resolver.cc +++ b/lib/src/facts/linux/networking_resolver.cc @@ -2,19 +2,16 @@ #include #include #include +#include #include #include #include #include -#include -#include using namespace std; using namespace cfacter::util::posix; -using namespace log4cxx; -using boost::format; -static LoggerPtr logger = Logger::getLogger("cfacter.facts.linux.network_resolver"); +LOG_DECLARE_NAMESPACE("facts.linux.networking"); namespace cfacter { namespace facts { namespace linux { @@ -46,7 +43,7 @@ namespace cfacter { namespace facts { namespace linux { scoped_descriptor sock(socket(AF_INET, SOCK_DGRAM, 0)); if (ioctl(sock, SIOCGIFMTU, &req) != 0) { - LOG4CXX_WARN(logger, (format("ioctl failed with %1%: interface MTU fact unavailable.") % errno).str()); + LOG_WARNING("ioctl failed with %1%: interface MTU fact is unavailable for interface %2%.", errno, interface); return -1; } return req.ifr_mtu; diff --git a/lib/src/facts/posix/kernel_resolver.cc b/lib/src/facts/posix/kernel_resolver.cc index 522909f48a..673a549e7a 100644 --- a/lib/src/facts/posix/kernel_resolver.cc +++ b/lib/src/facts/posix/kernel_resolver.cc @@ -2,16 +2,13 @@ #include #include #include +#include #include -#include -#include using namespace std; using namespace cfacter::util; -using namespace log4cxx; -using boost::format; -static LoggerPtr logger = Logger::getLogger("cfacter.facts.posix.kernel_resolver"); +LOG_DECLARE_NAMESPACE("facts.posix.kernel"); namespace cfacter { namespace facts { namespace posix { @@ -20,7 +17,7 @@ namespace cfacter { namespace facts { namespace posix { utsname name; memset(&name, 0, sizeof(name)); if (uname(&name) != 0) { - LOG4CXX_WARN(logger, (format("uname failed with %1%: kernel facts unavailable.") % errno).str()); + LOG_WARNING("uname failed with %1%: kernel facts are unavailable.", errno); return; } // Resolve all kernel-related facts diff --git a/lib/src/facts/posix/networking_resolver.cc b/lib/src/facts/posix/networking_resolver.cc index 6dd5f742e1..a5f482719f 100644 --- a/lib/src/facts/posix/networking_resolver.cc +++ b/lib/src/facts/posix/networking_resolver.cc @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -11,13 +12,11 @@ #include #include #include -#include using namespace std; -using namespace log4cxx; using boost::format; -static LoggerPtr logger = Logger::getLogger("cfacter.facts.posix.networking_resolver"); +LOG_DECLARE_NAMESPACE("facts.posix.networking"); namespace cfacter { namespace facts { namespace posix { @@ -32,7 +31,7 @@ namespace cfacter { namespace facts { namespace posix { int max = sysconf(_SC_HOST_NAME_MAX); vector name(max); if (gethostname(name.data(), max) != 0) { - LOG4CXX_WARN(logger, (format("gethostname failed with %1%: hostname fact unavailable.") % errno).str()); + LOG_WARNING("gethostname failed with %1%: hostname fact is unavailable.", errno); return; } diff --git a/lib/src/logging/logging.cc b/lib/src/logging/logging.cc new file mode 100644 index 0000000000..70300431ac --- /dev/null +++ b/lib/src/logging/logging.cc @@ -0,0 +1,46 @@ +#include + +using namespace std; +using namespace log4cxx; +using boost::format; + +namespace cfacter { namespace logging { + + bool is_log_enabled(LoggerPtr logger, log_level level) { + if (level == log_level::debug) { + return logger->isDebugEnabled(); + } else if (level == log_level::info) { + return logger->isInfoEnabled(); + } else if (level == log_level::warning) { + return logger->isWarnEnabled(); + } else if (level == log_level::error) { + return logger->isErrorEnabled(); + } else if (level == log_level::fatal) { + return logger->isFatalEnabled(); + } + return false; + } + + void log(LoggerPtr logger, log_level level, string const& message) + { + if (level == log_level::debug) { + LOG4CXX_DEBUG(logger, message); + } else if (level == log_level::info) { + LOG4CXX_INFO(logger, message); + } else if (level == log_level::warning) { + LOG4CXX_WARN(logger, message); + } else if (level == log_level::error) { + LOG4CXX_ERROR(logger, message); + } else if (level == log_level::fatal) { + LOG4CXX_FATAL(logger, message); + } else { + LOG4CXX_DEBUG(logger, "Invalid logging level used."); + } + } + + void log(LoggerPtr logger, log_level level, format& message) + { + log(logger, level, message.str()); + } + +}} // namespace cfacter::logging \ No newline at end of file From 2b84a696ea43a57bd573cf9e220535c27afd61e1 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Thu, 24 Apr 2014 19:24:07 -0700 Subject: [PATCH 1804/3753] (maint) Rename cfacter in sources to facter. - Change libcfacter to libfacter; keeping cfacter as the executable for now so that there is no confusion with existing facter. - Change "cfacter" namespace to "facter" in libfacter. - Move headers into a "facter" subdirectory so that they can be included with a library name prefix like other libraries. - Update include guards so that cpplint is happy. - No functionality change other than logging namespace is now "puppetlabs.facter.XXX". --- CMakeLists.txt | 2 +- exe/CMakeLists.txt | 8 ++-- exe/cfacter.cc | 10 ++--- gem/README | 2 +- gem/lib/cfacter.rb | 2 +- lib/CMakeLists.txt | 20 +++++----- lib/inc/{ => facter}/execution/execution.hpp | 16 ++++---- lib/inc/{cfacterlib.h => facter/facterlib.h} | 6 +-- lib/inc/{ => facter}/facts/array_value.hpp | 10 ++--- .../facts/bsd/networking_resolver.hpp | 10 ++--- lib/inc/{ => facter}/facts/fact.hpp | 10 ++--- lib/inc/{ => facter}/facts/fact_map.hpp | 10 ++--- lib/inc/{ => facter}/facts/fact_resolver.hpp | 10 ++--- .../{ => facter}/facts/linux/lsb_resolver.hpp | 10 ++--- .../facts/linux/networking_resolver.hpp | 10 ++--- .../facts/linux/operating_system_resolver.hpp | 10 ++--- .../{ => facter}/facts/linux/release_file.hpp | 10 ++--- .../facts/osx/networking_resolver.hpp | 10 ++--- .../facts/posix/kernel_resolver.hpp | 10 ++--- .../facts/posix/networking_resolver.hpp | 11 +++--- .../facts/posix/operating_system_resolver.hpp | 10 ++--- lib/inc/{ => facter}/facts/posix/os.hpp | 10 ++--- .../{ => facter}/facts/posix/os_family.hpp | 10 ++--- lib/inc/{ => facter}/facts/string_value.hpp | 10 ++--- lib/inc/{ => facter}/facts/value.hpp | 10 ++--- lib/inc/{ => facter}/logging/logging.hpp | 39 +++++++++---------- .../{ => facter}/util/bsd/scoped_ifaddrs.hpp | 10 ++--- lib/inc/{ => facter}/util/file.hpp | 10 ++--- lib/inc/{ => facter}/util/option_set.hpp | 10 ++--- .../util/posix/scoped_descriptor.hpp | 10 ++--- lib/inc/{ => facter}/util/scoped_resource.hpp | 10 ++--- lib/inc/{ => facter}/util/string.hpp | 10 ++--- lib/src/execution/posix/execution.cc | 18 ++++----- lib/src/{cfacterlib.cc => facterlib.cc} | 6 +-- lib/src/facts/array_value.cc | 6 +-- lib/src/facts/bsd/networking_resolver.cc | 20 +++++----- lib/src/facts/fact.cc | 8 ++-- lib/src/facts/fact_map.cc | 8 ++-- lib/src/facts/fact_resolver.cc | 10 ++--- lib/src/facts/linux/lsb_resolver.cc | 18 ++++----- lib/src/facts/linux/networking_resolver.cc | 16 ++++---- .../facts/linux/operating_system_resolver.cc | 28 ++++++------- lib/src/facts/linux/platform.cc | 14 +++---- lib/src/facts/osx/networking_resolver.cc | 14 +++---- lib/src/facts/osx/platform.cc | 12 +++--- lib/src/facts/posix/kernel_resolver.cc | 16 ++++---- lib/src/facts/posix/networking_resolver.cc | 12 +++--- .../facts/posix/operating_system_resolver.cc | 15 ++++--- lib/src/facts/string_value.cc | 6 +-- lib/src/logging/logging.cc | 6 +-- lib/src/util/posix/file.cc | 6 +-- lib/src/util/string.cc | 6 +-- 52 files changed, 289 insertions(+), 292 deletions(-) rename lib/inc/{ => facter}/execution/execution.hpp (90%) rename lib/inc/{cfacterlib.h => facter/facterlib.h} (71%) rename lib/inc/{ => facter}/facts/array_value.hpp (88%) rename lib/inc/{ => facter}/facts/bsd/networking_resolver.hpp (72%) rename lib/inc/{ => facter}/facts/fact.hpp (91%) rename lib/inc/{ => facter}/facts/fact_map.hpp (95%) rename lib/inc/{ => facter}/facts/fact_resolver.hpp (94%) rename lib/inc/{ => facter}/facts/linux/lsb_resolver.hpp (89%) rename lib/inc/{ => facter}/facts/linux/networking_resolver.hpp (85%) rename lib/inc/{ => facter}/facts/linux/operating_system_resolver.hpp (82%) rename lib/inc/{ => facter}/facts/linux/release_file.hpp (88%) rename lib/inc/{ => facter}/facts/osx/networking_resolver.hpp (84%) rename lib/inc/{ => facter}/facts/posix/kernel_resolver.hpp (87%) rename lib/inc/{ => facter}/facts/posix/networking_resolver.hpp (92%) rename lib/inc/{ => facter}/facts/posix/operating_system_resolver.hpp (85%) rename lib/inc/{ => facter}/facts/posix/os.hpp (92%) rename lib/inc/{ => facter}/facts/posix/os_family.hpp (69%) rename lib/inc/{ => facter}/facts/string_value.hpp (86%) rename lib/inc/{ => facter}/facts/value.hpp (86%) rename lib/inc/{ => facter}/logging/logging.hpp (67%) rename lib/inc/{ => facter}/util/bsd/scoped_ifaddrs.hpp (81%) rename lib/inc/{ => facter}/util/file.hpp (86%) rename lib/inc/{ => facter}/util/option_set.hpp (97%) rename lib/inc/{ => facter}/util/posix/scoped_descriptor.hpp (74%) rename lib/inc/{ => facter}/util/scoped_resource.hpp (91%) rename lib/inc/{ => facter}/util/string.hpp (97%) rename lib/src/{cfacterlib.cc => facterlib.cc} (85%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 73a47e2e5d..4c7b38ebbe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,7 +59,7 @@ else() set(CPPLINT_PATH "${PROJECT_SOURCE_DIR}/scripts/cpplint.py") - set(CPPLINT_ARGS "--extensions=cc,hpp,h") + set(CPPLINT_ARGS "--extensions=cc,hpp,h;--root=lib/inc") if (CPPLINT_FILTER) string(REPLACE ";" "," CPPLINT_FILTER "${CPPLINT_FILTER}") set(CPPLINT_ARGS "${CPPLINT_ARGS};--filter=${CPPLINT_FILTER}") diff --git a/exe/CMakeLists.txt b/exe/CMakeLists.txt index e280b2e1e1..e5a5272015 100644 --- a/exe/CMakeLists.txt +++ b/exe/CMakeLists.txt @@ -4,9 +4,9 @@ set(CFACTER_SOURCES ${CMAKE_CURRENT_LIST_DIR}/cfacter.cc ) -set(LIBCFACTER_DIR ../lib) +set(LIBFACTER_DIR ../lib) -add_subdirectory(${LIBCFACTER_DIR} ${LIBCFACTER_DIR}) +add_subdirectory(${LIBFACTER_DIR} ${LIBFACTER_DIR}) # Set compiler-specific flags if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") @@ -18,11 +18,11 @@ elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") endif() include_directories( - ${LIBCFACTER_DIR}/inc + ${LIBFACTER_DIR}/inc ${LOG4CXX_INCLUDE_DIRS} ${BOOST_INCLUDE_DIRS} ) add_executable(cfacter ${CFACTER_SOURCES}) -target_link_libraries(cfacter libcfacter liblog4cxx libboost_program_options) +target_link_libraries(cfacter libfacter liblog4cxx libboost_program_options) install(TARGETS cfacter DESTINATION .) diff --git a/exe/cfacter.cc b/exe/cfacter.cc index f6268249fd..5f87fc905e 100644 --- a/exe/cfacter.cc +++ b/exe/cfacter.cc @@ -1,8 +1,8 @@ #include "../version.h" #include -#include -#include -#include +#include +#include +#include #include #include #include @@ -12,8 +12,8 @@ using namespace std; using namespace log4cxx; -using namespace cfacter::util; -using namespace cfacter::facts; +using namespace facter::util; +using namespace facter::facts; using boost::format; namespace po = boost::program_options; diff --git a/gem/README b/gem/README index f7af1d1c64..f10827bb33 100644 --- a/gem/README +++ b/gem/README @@ -1,2 +1,2 @@ -A very very thin gem wrapper around libcfacter.so, intended for use as a facter +A very very thin gem wrapper around libfacter.so, intended for use as a facter replacement in ruby client such as puppet. diff --git a/gem/lib/cfacter.rb b/gem/lib/cfacter.rb index 16e17d3756..58cd14f672 100644 --- a/gem/lib/cfacter.rb +++ b/gem/lib/cfacter.rb @@ -4,7 +4,7 @@ module CFacter private extend FFI::Library - ffi_lib "libcfacter.so" + ffi_lib "libfacter.so" # to_json is used for to_hash but no need to make it public attach_function :to_json, [:pointer, :size_t], :int diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index fdd9eb2aa9..b22a4d7ed9 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -10,8 +10,8 @@ elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") endif() # Set the common (platform-independent) sources -set(LIBCFACTER_COMMON_SOURCES - "${CMAKE_CURRENT_LIST_DIR}/src/cfacterlib.cc" +set(LIBFACTER_COMMON_SOURCES + "${CMAKE_CURRENT_LIST_DIR}/src/facterlib.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/array_value.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/fact.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/fact_map.cc" @@ -23,7 +23,7 @@ set(LIBCFACTER_COMMON_SOURCES # Set the POSIX sources if on a POSIX platform if (UNIX) - set(LIBCFACTER_POSIX_SOURCES + set(LIBFACTER_POSIX_SOURCES "${CMAKE_CURRENT_LIST_DIR}/src/execution/posix/execution.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/posix/kernel_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/posix/networking_resolver.cc" @@ -34,13 +34,13 @@ endif() # Set the platform-specific sources if ("${CMAKE_SYSTEM_NAME}" MATCHES "Darwin") - set(LIBCFACTER_PLATFORM_SOURCES + set(LIBFACTER_PLATFORM_SOURCES "${CMAKE_CURRENT_LIST_DIR}/src/facts/bsd/networking_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/osx/networking_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/osx/platform.cc" ) elseif ("${CMAKE_SYSTEM_NAME}" MATCHES "Linux") - set(LIBCFACTER_PLATFORM_SOURCES + set(LIBFACTER_PLATFORM_SOURCES "${CMAKE_CURRENT_LIST_DIR}/src/facts/bsd/networking_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/linux/lsb_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/linux/networking_resolver.cc" @@ -50,9 +50,9 @@ elseif ("${CMAKE_SYSTEM_NAME}" MATCHES "Linux") endif() # Add the library target without a prefix (name already has the 'lib') -add_library(libcfacter SHARED ${LIBCFACTER_COMMON_SOURCES} ${LIBCFACTER_POSIX_SOURCES} ${LIBCFACTER_PLATFORM_SOURCES}) -set_target_properties(libcfacter PROPERTIES PREFIX "") -install(TARGETS libcfacter DESTINATION .) +add_library(libfacter SHARED ${LIBFACTER_COMMON_SOURCES} ${LIBFACTER_POSIX_SOURCES} ${LIBFACTER_PLATFORM_SOURCES}) +set_target_properties(libfacter PROPERTIES PREFIX "") +install(TARGETS libfacter DESTINATION .) # Set include directories include_directories( @@ -64,11 +64,11 @@ include_directories( ) # Link in additional libraries -target_link_libraries(libcfacter +target_link_libraries(libfacter pthread libre2 liblog4cxx ) # Add a dependency on rapidjson -add_dependencies(libcfacter rapidjson) +add_dependencies(libfacter rapidjson) diff --git a/lib/inc/execution/execution.hpp b/lib/inc/facter/execution/execution.hpp similarity index 90% rename from lib/inc/execution/execution.hpp rename to lib/inc/facter/execution/execution.hpp index faa610c3b5..80f24c0cfd 100644 --- a/lib/inc/execution/execution.hpp +++ b/lib/inc/facter/execution/execution.hpp @@ -1,12 +1,12 @@ -#ifndef LIB_INC_EXECUTION_EXECUTION_HPP_ -#define LIB_INC_EXECUTION_EXECUTION_HPP_ +#ifndef FACTER_EXECUTION_EXECUTION_HPP_ +#define FACTER_EXECUTION_EXECUTION_HPP_ #include #include #include #include "../util/option_set.hpp" -namespace cfacter { namespace execution { +namespace facter { namespace execution { /** * The supported execution options. @@ -144,7 +144,7 @@ namespace cfacter { namespace execution { */ std::string execute( std::string const& file, - cfacter::util::option_set const& options = { execution_options::defaults }); + facter::util::option_set const& options = { execution_options::defaults }); /** * Executes the given program. @@ -156,7 +156,7 @@ namespace cfacter { namespace execution { std::string execute( std::string const& file, std::vector const& arguments, - cfacter::util::option_set const& options = { execution_options::defaults }); + facter::util::option_set const& options = { execution_options::defaults }); /** * Executes the given program. @@ -170,9 +170,9 @@ namespace cfacter { namespace execution { std::string const& file, std::vector const& arguments, std::vector const& environment, - cfacter::util::option_set const& options = { execution_options::defaults }); + facter::util::option_set const& options = { execution_options::defaults }); -}} // namespace cfacter::execution +}} // namespace facter::execution -#endif // LIB_INC_EXECUTION_EXECUTION_HPP_ +#endif // FACTER_EXECUTION_EXECUTION_HPP_ diff --git a/lib/inc/cfacterlib.h b/lib/inc/facter/facterlib.h similarity index 71% rename from lib/inc/cfacterlib.h rename to lib/inc/facter/facterlib.h index 5c7318b28b..bb5bffb728 100644 --- a/lib/inc/cfacterlib.h +++ b/lib/inc/facter/facterlib.h @@ -1,5 +1,5 @@ -#ifndef LIB_INC_CFACTERLIB_H_ -#define LIB_INC_CFACTERLIB_H_ +#ifndef FACTER_FACTERLIB_H_ +#define FACTER_FACTERLIB_H_ #include @@ -11,4 +11,4 @@ extern "C" { void search_external(const char *dirs); } -#endif // LIB_INC_CFACTERLIB_H_ +#endif // FACTER_FACTERLIB_H_ diff --git a/lib/inc/facts/array_value.hpp b/lib/inc/facter/facts/array_value.hpp similarity index 88% rename from lib/inc/facts/array_value.hpp rename to lib/inc/facter/facts/array_value.hpp index 0609c2d891..f35a77d06f 100644 --- a/lib/inc/facts/array_value.hpp +++ b/lib/inc/facter/facts/array_value.hpp @@ -1,11 +1,11 @@ -#ifndef LIB_INC_FACTS_ARRAY_VALUE_HPP_ -#define LIB_INC_FACTS_ARRAY_VALUE_HPP_ +#ifndef FACTER_FACTS_ARRAY_VALUE_HPP_ +#define FACTER_FACTS_ARRAY_VALUE_HPP_ #include "value.hpp" #include #include -namespace cfacter { namespace facts { +namespace facter { namespace facts { /** * Represents an array of values. @@ -58,7 +58,7 @@ namespace cfacter { namespace facts { std::vector> _elements; }; -}} // namespace cfacter::facts +}} // namespace facter::facts -#endif // LIB_INC_FACTS_ARRAY_VALUE_HPP_ +#endif // FACTER_FACTS_ARRAY_VALUE_HPP_ diff --git a/lib/inc/facts/bsd/networking_resolver.hpp b/lib/inc/facter/facts/bsd/networking_resolver.hpp similarity index 72% rename from lib/inc/facts/bsd/networking_resolver.hpp rename to lib/inc/facter/facts/bsd/networking_resolver.hpp index 02393251e6..8b661caaf1 100644 --- a/lib/inc/facts/bsd/networking_resolver.hpp +++ b/lib/inc/facter/facts/bsd/networking_resolver.hpp @@ -1,10 +1,10 @@ -#ifndef LIB_INC_FACTS_BSD_NETWORKING_RESOLVER_HPP_ -#define LIB_INC_FACTS_BSD_NETWORKING_RESOLVER_HPP_ +#ifndef FACTER_FACTS_BSD_NETWORKING_RESOLVER_HPP_ +#define FACTER_FACTS_BSD_NETWORKING_RESOLVER_HPP_ #include "../posix/networking_resolver.hpp" #include -namespace cfacter { namespace facts { namespace bsd { +namespace facter { namespace facts { namespace bsd { /** * Responsible for resolving networking facts. @@ -24,6 +24,6 @@ namespace cfacter { namespace facts { namespace bsd { void resolve_mtu(fact_map& facts, ifaddrs const* addr); }; -}}} // namespace cfacter::facts::bsd +}}} // namespace facter::facts::bsd -#endif // LIB_INC_FACTS_BSD_NETWORKING_RESOLVER_HPP_ +#endif // FACTER_FACTS_BSD_NETWORKING_RESOLVER_HPP_ diff --git a/lib/inc/facts/fact.hpp b/lib/inc/facter/facts/fact.hpp similarity index 91% rename from lib/inc/facts/fact.hpp rename to lib/inc/facter/facts/fact.hpp index 940428c4c6..c99f903952 100644 --- a/lib/inc/facts/fact.hpp +++ b/lib/inc/facter/facts/fact.hpp @@ -1,7 +1,7 @@ -#ifndef LIB_INC_FACTS_FACT_HPP_ -#define LIB_INC_FACTS_FACT_HPP_ +#ifndef FACTER_FACTS_FACT_HPP_ +#define FACTER_FACTS_FACT_HPP_ -namespace cfacter { namespace facts { +namespace facter { namespace facts { /** * Stores the constant fact names. @@ -35,6 +35,6 @@ namespace cfacter { namespace facts { constexpr static char const* interfaces = "interfaces"; }; -}} // namespace cfacter::facts +}} // namespace facter::facts -#endif // LIB_INC_FACTS_FACT_HPP_ +#endif // FACTER_FACTS_FACT_HPP_ diff --git a/lib/inc/facts/fact_map.hpp b/lib/inc/facter/facts/fact_map.hpp similarity index 95% rename from lib/inc/facts/fact_map.hpp rename to lib/inc/facter/facts/fact_map.hpp index afd384cb94..0d8bcf7d2f 100644 --- a/lib/inc/facts/fact_map.hpp +++ b/lib/inc/facter/facts/fact_map.hpp @@ -1,5 +1,5 @@ -#ifndef LIB_INC_FACTS_FACT_MAP_HPP_ -#define LIB_INC_FACTS_FACT_MAP_HPP_ +#ifndef FACTER_FACTS_FACT_MAP_HPP_ +#define FACTER_FACTS_FACT_MAP_HPP_ #include #include @@ -9,7 +9,7 @@ #include "value.hpp" #include "fact_resolver.hpp" -namespace cfacter { namespace facts { +namespace facter { namespace facts { /** * Thrown when a fact already exists in the map. @@ -129,7 +129,7 @@ namespace cfacter { namespace facts { resolver_map_type _resolver_map; }; -}} // namespace cfacter::facts +}} // namespace facter::facts -#endif // LIB_INC_FACTS_FACT_MAP_HPP_ +#endif // FACTER_FACTS_FACT_MAP_HPP_ diff --git a/lib/inc/facts/fact_resolver.hpp b/lib/inc/facter/facts/fact_resolver.hpp similarity index 94% rename from lib/inc/facts/fact_resolver.hpp rename to lib/inc/facter/facts/fact_resolver.hpp index d126329c47..d95904d85b 100644 --- a/lib/inc/facts/fact_resolver.hpp +++ b/lib/inc/facter/facts/fact_resolver.hpp @@ -1,5 +1,5 @@ -#ifndef LIB_INC_FACTS_FACT_RESOLVER_HPP_ -#define LIB_INC_FACTS_FACT_RESOLVER_HPP_ +#ifndef FACTER_FACTS_FACT_RESOLVER_HPP_ +#define FACTER_FACTS_FACT_RESOLVER_HPP_ #include #include @@ -10,7 +10,7 @@ namespace re2 { class RE2; } -namespace cfacter { namespace facts { +namespace facter { namespace facts { /** * Thrown when a circular fact resolution is detected. @@ -125,7 +125,7 @@ namespace cfacter { namespace facts { bool _resolving; }; -}} // namespace cfacter::facts +}} // namespace facter::facts -#endif // LIB_INC_FACTS_FACT_RESOLVER_HPP_ +#endif // FACTER_FACTS_FACT_RESOLVER_HPP_ diff --git a/lib/inc/facts/linux/lsb_resolver.hpp b/lib/inc/facter/facts/linux/lsb_resolver.hpp similarity index 89% rename from lib/inc/facts/linux/lsb_resolver.hpp rename to lib/inc/facter/facts/linux/lsb_resolver.hpp index 667072e493..15bd69a2bc 100644 --- a/lib/inc/facts/linux/lsb_resolver.hpp +++ b/lib/inc/facter/facts/linux/lsb_resolver.hpp @@ -1,10 +1,10 @@ -#ifndef LIB_INC_FACTS_LINUX_LSB_RESOLVER_HPP_ -#define LIB_INC_FACTS_LINUX_LSB_RESOLVER_HPP_ +#ifndef FACTER_FACTS_LINUX_LSB_RESOLVER_HPP_ +#define FACTER_FACTS_LINUX_LSB_RESOLVER_HPP_ #include "../fact_resolver.hpp" #include "../fact.hpp" -namespace cfacter { namespace facts { namespace linux { +namespace facter { namespace facts { namespace linux { /** * Responsible for resolving Linux Standard Base facts. @@ -68,7 +68,7 @@ namespace cfacter { namespace facts { namespace linux { virtual void resolve_release(fact_map& facts); }; -}}} // namespace cfacter::facts::linux +}}} // namespace facter::facts::linux -#endif // LIB_INC_FACTS_LINUX_LSB_RESOLVER_HPP_ +#endif // FACTER_FACTS_LINUX_LSB_RESOLVER_HPP_ diff --git a/lib/inc/facts/linux/networking_resolver.hpp b/lib/inc/facter/facts/linux/networking_resolver.hpp similarity index 85% rename from lib/inc/facts/linux/networking_resolver.hpp rename to lib/inc/facter/facts/linux/networking_resolver.hpp index 32136072b7..3ea5308a79 100644 --- a/lib/inc/facts/linux/networking_resolver.hpp +++ b/lib/inc/facter/facts/linux/networking_resolver.hpp @@ -1,9 +1,9 @@ -#ifndef LIB_INC_FACTS_LINUX_NETWORKING_RESOLVER_HPP_ -#define LIB_INC_FACTS_LINUX_NETWORKING_RESOLVER_HPP_ +#ifndef FACTER_FACTS_LINUX_NETWORKING_RESOLVER_HPP_ +#define FACTER_FACTS_LINUX_NETWORKING_RESOLVER_HPP_ #include "../bsd/networking_resolver.hpp" -namespace cfacter { namespace facts { namespace linux { +namespace facter { namespace facts { namespace linux { /** * Responsible for resolving networking facts. @@ -39,6 +39,6 @@ namespace cfacter { namespace facts { namespace linux { virtual int get_link_mtu(std::string const& interface, void* data) const; }; -}}} // namespace cfacter::facts::linux +}}} // namespace facter::facts::linux -#endif // LIB_INC_FACTS_LINUX_NETWORKING_RESOLVER_HPP_ +#endif // FACTER_FACTS_LINUX_NETWORKING_RESOLVER_HPP_ diff --git a/lib/inc/facts/linux/operating_system_resolver.hpp b/lib/inc/facter/facts/linux/operating_system_resolver.hpp similarity index 82% rename from lib/inc/facts/linux/operating_system_resolver.hpp rename to lib/inc/facter/facts/linux/operating_system_resolver.hpp index 5a1c2f93ed..33606b397c 100644 --- a/lib/inc/facts/linux/operating_system_resolver.hpp +++ b/lib/inc/facter/facts/linux/operating_system_resolver.hpp @@ -1,10 +1,10 @@ -#ifndef LIB_INC_FACTS_LINUX_OPERATING_SYSTEM_RESOLVER_HPP_ -#define LIB_INC_FACTS_LINUX_OPERATING_SYSTEM_RESOLVER_HPP_ +#ifndef FACTER_FACTS_LINUX_OPERATING_SYSTEM_RESOLVER_HPP_ +#define FACTER_FACTS_LINUX_OPERATING_SYSTEM_RESOLVER_HPP_ #include "../posix/operating_system_resolver.hpp" #include "../string_value.hpp" -namespace cfacter { namespace facts { namespace linux { +namespace facter { namespace facts { namespace linux { /** * Responsible for resolving operating system facts. @@ -39,7 +39,7 @@ namespace cfacter { namespace facts { namespace linux { static std::string check_other_linux(); }; -}}} // namespace cfacter::facts::linux +}}} // namespace facter::facts::linux -#endif // LIB_INC_FACTS_LINUX_OPERATING_SYSTEM_RESOLVER_HPP_ +#endif // FACTER_FACTS_LINUX_OPERATING_SYSTEM_RESOLVER_HPP_ diff --git a/lib/inc/facts/linux/release_file.hpp b/lib/inc/facter/facts/linux/release_file.hpp similarity index 88% rename from lib/inc/facts/linux/release_file.hpp rename to lib/inc/facter/facts/linux/release_file.hpp index 7403639373..cc54b70ea5 100644 --- a/lib/inc/facts/linux/release_file.hpp +++ b/lib/inc/facter/facts/linux/release_file.hpp @@ -1,7 +1,7 @@ -#ifndef LIB_INC_FACTS_LINUX_RELEASE_FILE_HPP_ -#define LIB_INC_FACTS_LINUX_RELEASE_FILE_HPP_ +#ifndef FACTER_FACTS_LINUX_RELEASE_FILE_HPP_ +#define FACTER_FACTS_LINUX_RELEASE_FILE_HPP_ -namespace cfacter { namespace facts { namespace linux { +namespace facter { namespace facts { namespace linux { /** * Stores the constant release file names. @@ -34,6 +34,6 @@ namespace cfacter { namespace facts { namespace linux { constexpr static char const* linux_mint_info = "/etc/linuxmint/info"; }; -}}} // namespace cfacter::facts::linux +}}} // namespace facter::facts::linux -#endif // LIB_INC_FACTS_LINUX_RELEASE_FILE_HPP_ +#endif // FACTER_FACTS_LINUX_RELEASE_FILE_HPP_ diff --git a/lib/inc/facts/osx/networking_resolver.hpp b/lib/inc/facter/facts/osx/networking_resolver.hpp similarity index 84% rename from lib/inc/facts/osx/networking_resolver.hpp rename to lib/inc/facter/facts/osx/networking_resolver.hpp index b2bc9c74de..97f9ec6a05 100644 --- a/lib/inc/facts/osx/networking_resolver.hpp +++ b/lib/inc/facter/facts/osx/networking_resolver.hpp @@ -1,9 +1,9 @@ -#ifndef LIB_INC_FACTS_OSX_NETWORKING_RESOLVER_HPP_ -#define LIB_INC_FACTS_OSX_NETWORKING_RESOLVER_HPP_ +#ifndef FACTER_FACTS_OSX_NETWORKING_RESOLVER_HPP_ +#define FACTER_FACTS_OSX_NETWORKING_RESOLVER_HPP_ #include "../bsd/networking_resolver.hpp" -namespace cfacter { namespace facts { namespace osx { +namespace facter { namespace facts { namespace osx { /** * Responsible for resolving networking facts. @@ -40,6 +40,6 @@ namespace cfacter { namespace facts { namespace osx { virtual void resolve_hostname(fact_map& facts); }; -}}} // namespace cfacter::facts::osx +}}} // namespace facter::facts::osx -#endif // LIB_INC_FACTS_OSX_NETWORKING_RESOLVER_HPP_ +#endif // FACTER_FACTS_OSX_NETWORKING_RESOLVER_HPP_ diff --git a/lib/inc/facts/posix/kernel_resolver.hpp b/lib/inc/facter/facts/posix/kernel_resolver.hpp similarity index 87% rename from lib/inc/facts/posix/kernel_resolver.hpp rename to lib/inc/facter/facts/posix/kernel_resolver.hpp index e28dc1fe11..3740ffccac 100644 --- a/lib/inc/facts/posix/kernel_resolver.hpp +++ b/lib/inc/facter/facts/posix/kernel_resolver.hpp @@ -1,11 +1,11 @@ -#ifndef LIB_INC_FACTS_POSIX_KERNEL_RESOLVER_HPP_ -#define LIB_INC_FACTS_POSIX_KERNEL_RESOLVER_HPP_ +#ifndef FACTER_FACTS_POSIX_KERNEL_RESOLVER_HPP_ +#define FACTER_FACTS_POSIX_KERNEL_RESOLVER_HPP_ #include "../fact_resolver.hpp" #include "../fact.hpp" #include -namespace cfacter { namespace facts { namespace posix { +namespace facter { namespace facts { namespace posix { /** * Responsible for resolving kernel facts. @@ -57,7 +57,7 @@ namespace cfacter { namespace facts { namespace posix { virtual void resolve_kernel_major_version(fact_map& facts); }; -}}} // namespace cfacter::facts::posix +}}} // namespace facter::facts::posix -#endif // LIB_INC_FACTS_POSIX_KERNEL_RESOLVER_HPP_ +#endif // FACTER_FACTS_POSIX_KERNEL_RESOLVER_HPP_ diff --git a/lib/inc/facts/posix/networking_resolver.hpp b/lib/inc/facter/facts/posix/networking_resolver.hpp similarity index 92% rename from lib/inc/facts/posix/networking_resolver.hpp rename to lib/inc/facter/facts/posix/networking_resolver.hpp index 6afcb7477d..f3797d0ed3 100644 --- a/lib/inc/facts/posix/networking_resolver.hpp +++ b/lib/inc/facter/facts/posix/networking_resolver.hpp @@ -1,11 +1,11 @@ -#ifndef LIB_INC_FACTS_POSIX_NETWORKING_RESOLVER_HPP_ -#define LIB_INC_FACTS_POSIX_NETWORKING_RESOLVER_HPP_ +#ifndef FACTER_FACTS_POSIX_NETWORKING_RESOLVER_HPP_ +#define FACTER_FACTS_POSIX_NETWORKING_RESOLVER_HPP_ #include "../fact_resolver.hpp" #include "../fact.hpp" #include -namespace cfacter { namespace facts { namespace posix { +namespace facter { namespace facts { namespace posix { /** * Responsible for resolving networking facts. @@ -92,7 +92,6 @@ namespace cfacter { namespace facts { namespace posix { virtual void resolve_interface_facts(fact_map& facts) = 0; }; -}}} // namespace cfacter::facts::posix - -#endif // LIB_INC_FACTS_POSIX_NETWORKING_RESOLVER_HPP_ +}}} // namespace facter::facts::posix +#endif // FACTER_FACTS_POSIX_NETWORKING_RESOLVER_HPP_ diff --git a/lib/inc/facts/posix/operating_system_resolver.hpp b/lib/inc/facter/facts/posix/operating_system_resolver.hpp similarity index 85% rename from lib/inc/facts/posix/operating_system_resolver.hpp rename to lib/inc/facter/facts/posix/operating_system_resolver.hpp index 1c22ae49ad..4c2f36c251 100644 --- a/lib/inc/facts/posix/operating_system_resolver.hpp +++ b/lib/inc/facter/facts/posix/operating_system_resolver.hpp @@ -1,10 +1,10 @@ -#ifndef LIB_INC_FACTS_POSIX_OPERATING_SYSTEM_RESOLVER_HPP_ -#define LIB_INC_FACTS_POSIX_OPERATING_SYSTEM_RESOLVER_HPP_ +#ifndef FACTER_FACTS_POSIX_OPERATING_SYSTEM_RESOLVER_HPP_ +#define FACTER_FACTS_POSIX_OPERATING_SYSTEM_RESOLVER_HPP_ #include "../fact_resolver.hpp" #include "../fact.hpp" -namespace cfacter { namespace facts { namespace posix { +namespace facter { namespace facts { namespace posix { /** * Responsible for resolving operating system facts. @@ -54,6 +54,6 @@ namespace cfacter { namespace facts { namespace posix { virtual void resolve_operating_system_major_release(fact_map& facts) {} }; -}}} // namespace cfacter::facts::posix +}}} // namespace facter::facts::posix -#endif // LIB_INC_FACTS_POSIX_OPERATING_SYSTEM_RESOLVER_HPP_ +#endif // FACTER_FACTS_POSIX_OPERATING_SYSTEM_RESOLVER_HPP_ diff --git a/lib/inc/facts/posix/os.hpp b/lib/inc/facter/facts/posix/os.hpp similarity index 92% rename from lib/inc/facts/posix/os.hpp rename to lib/inc/facter/facts/posix/os.hpp index da103fe2bd..f7da1cbee5 100644 --- a/lib/inc/facts/posix/os.hpp +++ b/lib/inc/facter/facts/posix/os.hpp @@ -1,7 +1,7 @@ -#ifndef LIB_INC_FACTS_POSIX_OS_HPP_ -#define LIB_INC_FACTS_POSIX_OS_HPP_ +#ifndef FACTER_FACTS_POSIX_OS_HPP_ +#define FACTER_FACTS_POSIX_OS_HPP_ -namespace cfacter { namespace facts { namespace posix { +namespace facter { namespace facts { namespace posix { /** * Stores the constant operating system names. @@ -49,6 +49,6 @@ namespace cfacter { namespace facts { namespace posix { constexpr static char const* zen_cloud_platform = "XCP"; }; -}}} // namespace cfacter::facts::posix +}}} // namespace facter::facts::posix -#endif // LIB_INC_FACTS_POSIX_OS_HPP_ +#endif // FACTER_FACTS_POSIX_OS_HPP_ diff --git a/lib/inc/facts/posix/os_family.hpp b/lib/inc/facter/facts/posix/os_family.hpp similarity index 69% rename from lib/inc/facts/posix/os_family.hpp rename to lib/inc/facter/facts/posix/os_family.hpp index aa0141cc03..19b2ad7e08 100644 --- a/lib/inc/facts/posix/os_family.hpp +++ b/lib/inc/facter/facts/posix/os_family.hpp @@ -1,7 +1,7 @@ -#ifndef LIB_INC_FACTS_POSIX_OS_FAMILY_HPP_ -#define LIB_INC_FACTS_POSIX_OS_FAMILY_HPP_ +#ifndef FACTER_FACTS_POSIX_OS_FAMILY_HPP_ +#define FACTER_FACTS_POSIX_OS_FAMILY_HPP_ -namespace cfacter { namespace facts { namespace posix { +namespace facter { namespace facts { namespace posix { /** * Stores the constant operating system family names. @@ -17,6 +17,6 @@ namespace cfacter { namespace facts { namespace posix { constexpr static char const* mandrake = "Mandrake"; }; -}}} // namespace cfacter::facts::posix +}}} // namespace facter::facts::posix -#endif // LIB_INC_FACTS_POSIX_OS_FAMILY_HPP_ +#endif // FACTER_FACTS_POSIX_OS_FAMILY_HPP_ diff --git a/lib/inc/facts/string_value.hpp b/lib/inc/facter/facts/string_value.hpp similarity index 86% rename from lib/inc/facts/string_value.hpp rename to lib/inc/facter/facts/string_value.hpp index 0d3df5e2c3..07fd0260f8 100644 --- a/lib/inc/facts/string_value.hpp +++ b/lib/inc/facter/facts/string_value.hpp @@ -1,9 +1,9 @@ -#ifndef LIB_INC_FACTS_STRING_VALUE_HPP_ -#define LIB_INC_FACTS_STRING_VALUE_HPP_ +#ifndef FACTER_FACTS_STRING_VALUE_HPP_ +#define FACTER_FACTS_STRING_VALUE_HPP_ #include "value.hpp" -namespace cfacter { namespace facts { +namespace facter { namespace facts { /** * Represents a simple string value. @@ -52,7 +52,7 @@ namespace cfacter { namespace facts { std::string _value; }; -}} // namespace cfacter::facts +}} // namespace facter::facts -#endif // LIB_INC_FACTS_STRING_VALUE_HPP_ +#endif // FACTER_FACTS_STRING_VALUE_HPP_ diff --git a/lib/inc/facts/value.hpp b/lib/inc/facter/facts/value.hpp similarity index 86% rename from lib/inc/facts/value.hpp rename to lib/inc/facter/facts/value.hpp index 1b6c135572..498fd28249 100644 --- a/lib/inc/facts/value.hpp +++ b/lib/inc/facter/facts/value.hpp @@ -1,11 +1,11 @@ -#ifndef LIB_INC_FACTS_VALUE_HPP_ -#define LIB_INC_FACTS_VALUE_HPP_ +#ifndef FACTER_FACTS_VALUE_HPP_ +#define FACTER_FACTS_VALUE_HPP_ #include #include #include -namespace cfacter { namespace facts { +namespace facter { namespace facts { /** * Base class for values. @@ -45,7 +45,7 @@ namespace cfacter { namespace facts { return std::unique_ptr(new T(std::forward(args)...)); } -}} // namespace cfacter::facts +}} // namespace facter::facts -#endif // LIB_INC_FACTS_VALUE_HPP_ +#endif // FACTER_FACTS_VALUE_HPP_ diff --git a/lib/inc/logging/logging.hpp b/lib/inc/facter/logging/logging.hpp similarity index 67% rename from lib/inc/logging/logging.hpp rename to lib/inc/facter/logging/logging.hpp index afcfcf4514..cf76098728 100644 --- a/lib/inc/logging/logging.hpp +++ b/lib/inc/facter/logging/logging.hpp @@ -1,5 +1,5 @@ -#ifndef LIB_INC_LOGGING_LOGGING_HPP_H_ -#define LIB_INC_LOGGING_LOGGING_HPP_H_ +#ifndef FACTER_LOGGING_LOGGING_HPP_ +#define FACTER_LOGGING_LOGGING_HPP_ // To use this header, you must: // - Have log4cxx and Boost on the include path @@ -8,24 +8,24 @@ #include #include -#define LOG_DECLARE_NAMESPACE(ns) static log4cxx::LoggerPtr g_logger = log4cxx::Logger::getLogger("puppetlabs.cfacter." ns); +#define LOG_DECLARE_NAMESPACE(ns) static log4cxx::LoggerPtr g_logger = log4cxx::Logger::getLogger("puppetlabs.facter." ns); #define LOG_MESSAGE(level, format, ...) \ do { \ - cfacter::logging::log(g_logger, level, format, ##__VA_ARGS__); \ - } while(0) -#define LOG_DEBUG(format, ...) LOG_MESSAGE(cfacter::logging::log_level::debug, format, ##__VA_ARGS__) -#define LOG_INFO(format, ...) LOG_MESSAGE(cfacter::logging::log_level::info, format, ##__VA_ARGS__) -#define LOG_WARNING(format, ...) LOG_MESSAGE(cfacter::logging::log_level::warning, format, ##__VA_ARGS__) -#define LOG_ERROR(format, ...) LOG_MESSAGE(cfacter::logging::log_level::error, format, ##__VA_ARGS__) -#define LOG_FATAL(format, ...) LOG_MESSAGE(cfacter::logging::log_level::fatal, format, ##__VA_ARGS__) -#define LOG_IS_ENABLED(level) cfacter::logging::is_log_enabled(g_logger, level) -#define LOG_IS_DEBUG_ENABLED() LOG_IS_ENABLED(cfacter::logging::log_level::debug) -#define LOG_IS_INFO_ENABLED() LOG_IS_ENABLED(cfacter::logging::log_level::info) -#define LOG_IS_WARNING_ENABLED() LOG_IS_ENABLED(cfacter::logging::log_level::warning) -#define LOG_IS_ERROR_ENABLED() LOG_IS_ENABLED(cfacter::logging::log_level::error) -#define LOG_IS_FATAL_ENABLED() LOG_IS_ENABLED(cfacter::logging::log_level::fatal) + facter::logging::log(g_logger, level, format, ##__VA_ARGS__); \ + } while (0) +#define LOG_DEBUG(format, ...) LOG_MESSAGE(facter::logging::log_level::debug, format, ##__VA_ARGS__) +#define LOG_INFO(format, ...) LOG_MESSAGE(facter::logging::log_level::info, format, ##__VA_ARGS__) +#define LOG_WARNING(format, ...) LOG_MESSAGE(facter::logging::log_level::warning, format, ##__VA_ARGS__) +#define LOG_ERROR(format, ...) LOG_MESSAGE(facter::logging::log_level::error, format, ##__VA_ARGS__) +#define LOG_FATAL(format, ...) LOG_MESSAGE(facter::logging::log_level::fatal, format, ##__VA_ARGS__) +#define LOG_IS_ENABLED(level) facter::logging::is_log_enabled(g_logger, level) +#define LOG_IS_DEBUG_ENABLED() LOG_IS_ENABLED(facter::logging::log_level::debug) +#define LOG_IS_INFO_ENABLED() LOG_IS_ENABLED(facter::logging::log_level::info) +#define LOG_IS_WARNING_ENABLED() LOG_IS_ENABLED(facter::logging::log_level::warning) +#define LOG_IS_ERROR_ENABLED() LOG_IS_ENABLED(facter::logging::log_level::error) +#define LOG_IS_FATAL_ENABLED() LOG_IS_ENABLED(facter::logging::log_level::fatal) -namespace cfacter { namespace logging { +namespace facter { namespace logging { /** * Represents the supported logging levels. @@ -95,7 +95,6 @@ namespace cfacter { namespace logging { log(logger, level, message, std::forward(args)...); } -}} // namespace cfacter::logging - -#endif // LIB_INC_LOGGING_LOGGING_HPP_H_ +}} // namespace facter::logging +#endif // FACTER_LOGGING_LOGGING_HPP_ diff --git a/lib/inc/util/bsd/scoped_ifaddrs.hpp b/lib/inc/facter/util/bsd/scoped_ifaddrs.hpp similarity index 81% rename from lib/inc/util/bsd/scoped_ifaddrs.hpp rename to lib/inc/facter/util/bsd/scoped_ifaddrs.hpp index 9b7702fb6e..d5b179829c 100644 --- a/lib/inc/util/bsd/scoped_ifaddrs.hpp +++ b/lib/inc/facter/util/bsd/scoped_ifaddrs.hpp @@ -1,10 +1,10 @@ -#ifndef LIB_INC_UTIL_BSD_SCOPED_IFADDRS_HPP_ -#define LIB_INC_UTIL_BSD_SCOPED_IFADDRS_HPP_ +#ifndef FACTER_UTIL_BSD_SCOPED_IFADDRS_HPP_ +#define FACTER_UTIL_BSD_SCOPED_IFADDRS_HPP_ #include "../scoped_resource.hpp" #include -namespace cfacter { namespace util { namespace bsd { +namespace facter { namespace util { namespace bsd { /** * Represents a scoped ifaddrs pointer that automatically is freed when it goes out of scope. @@ -43,6 +43,6 @@ namespace cfacter { namespace util { namespace bsd { } }; -}}} // namespace cfacter::util::bsd +}}} // namespace facter::util::bsd -#endif // LIB_INC_UTIL_BSD_SCOPED_IFADDRS_HPP_ +#endif // FACTER_UTIL_BSD_SCOPED_IFADDRS_HPP_ diff --git a/lib/inc/util/file.hpp b/lib/inc/facter/util/file.hpp similarity index 86% rename from lib/inc/util/file.hpp rename to lib/inc/facter/util/file.hpp index e4dac0e6a7..1b2a73375e 100644 --- a/lib/inc/util/file.hpp +++ b/lib/inc/facter/util/file.hpp @@ -1,9 +1,9 @@ -#ifndef LIB_INC_UTIL_FILE_HPP_ -#define LIB_INC_UTIL_FILE_HPP_ +#ifndef FACTER_UTIL_FILE_HPP_ +#define FACTER_UTIL_FILE_HPP_ #include -namespace cfacter { namespace util { +namespace facter { namespace util { /** * Utility type for interacting with files. @@ -32,6 +32,6 @@ namespace cfacter { namespace util { static std::string read_first_line(std::string const& path); }; -}} // namespace cfacter::util +}} // namespace facter::util -#endif // LIB_INC_UTIL_FILE_HPP_ +#endif // FACTER_UTIL_FILE_HPP_ diff --git a/lib/inc/util/option_set.hpp b/lib/inc/facter/util/option_set.hpp similarity index 97% rename from lib/inc/util/option_set.hpp rename to lib/inc/facter/util/option_set.hpp index 0b13d73800..ab0c4fef60 100644 --- a/lib/inc/util/option_set.hpp +++ b/lib/inc/facter/util/option_set.hpp @@ -1,11 +1,11 @@ -#ifndef LIB_INC_UTIL_OPTION_SET_HPP_ -#define LIB_INC_UTIL_OPTION_SET_HPP_ +#ifndef FACTER_UTIL_OPTION_SET_HPP_ +#define FACTER_UTIL_OPTION_SET_HPP_ #include #include #include -namespace cfacter { namespace util { +namespace facter { namespace util { /** * Represents a set of options (flags). * Adapted from http://stackoverflow.com/a/4226975/530189 @@ -222,6 +222,6 @@ namespace cfacter { namespace util { return option_set(option_set::value_type(lhs) ^ option_set::value_type(rhs)); } -}} // namespace cfacter::util +}} // namespace facter::util -#endif // LIB_INC_UTIL_OPTION_SET_HPP_ +#endif // FACTER_UTIL_OPTION_SET_HPP_ diff --git a/lib/inc/util/posix/scoped_descriptor.hpp b/lib/inc/facter/util/posix/scoped_descriptor.hpp similarity index 74% rename from lib/inc/util/posix/scoped_descriptor.hpp rename to lib/inc/facter/util/posix/scoped_descriptor.hpp index 1240b1ecb7..ab230f4946 100644 --- a/lib/inc/util/posix/scoped_descriptor.hpp +++ b/lib/inc/facter/util/posix/scoped_descriptor.hpp @@ -1,10 +1,10 @@ -#ifndef LIB_INC_UTIL_POSIX_SCOPED_DESCRIPTOR_HPP_ -#define LIB_INC_UTIL_POSIX_SCOPED_DESCRIPTOR_HPP_ +#ifndef FACTER_UTIL_POSIX_SCOPED_DESCRIPTOR_HPP_ +#define FACTER_UTIL_POSIX_SCOPED_DESCRIPTOR_HPP_ #include "../scoped_resource.hpp" #include -namespace cfacter { namespace util { namespace posix { +namespace facter { namespace util { namespace posix { /** * Represents a scoped file descriptor for POSIX systems. * Automatically closes the file descriptor when it goes out of scope. @@ -29,6 +29,6 @@ namespace cfacter { namespace util { namespace posix { } }; -}}} // namespace cfacter::util::posix +}}} // namespace facter::util::posix -#endif // LIB_INC_UTIL_POSIX_SCOPED_DESCRIPTOR_HPP_ +#endif // FACTER_UTIL_POSIX_SCOPED_DESCRIPTOR_HPP_ diff --git a/lib/inc/util/scoped_resource.hpp b/lib/inc/facter/util/scoped_resource.hpp similarity index 91% rename from lib/inc/util/scoped_resource.hpp rename to lib/inc/facter/util/scoped_resource.hpp index e1ef3e4c78..0e50db2496 100644 --- a/lib/inc/util/scoped_resource.hpp +++ b/lib/inc/facter/util/scoped_resource.hpp @@ -1,9 +1,9 @@ -#ifndef LIB_INC_UTIL_SCOPED_RESOURCE_HPP_ -#define LIB_INC_UTIL_SCOPED_RESOURCE_HPP_ +#ifndef FACTER_UTIL_SCOPED_RESOURCE_HPP_ +#define FACTER_UTIL_SCOPED_RESOURCE_HPP_ #include -namespace cfacter { namespace util { +namespace facter { namespace util { /** * Simple class that is used for the RAII pattern. * Used to scope a resource. When it goes out of scope, a deleter @@ -83,6 +83,6 @@ namespace cfacter { namespace util { std::function _deleter; }; -}} // namespace cfacter::util +}} // namespace facter::util -#endif // LIB_INC_UTIL_SCOPED_RESOURCE_HPP_ +#endif // FACTER_UTIL_SCOPED_RESOURCE_HPP_ diff --git a/lib/inc/util/string.hpp b/lib/inc/facter/util/string.hpp similarity index 97% rename from lib/inc/util/string.hpp rename to lib/inc/facter/util/string.hpp index c7473fee16..970d9576e1 100644 --- a/lib/inc/util/string.hpp +++ b/lib/inc/facter/util/string.hpp @@ -1,5 +1,5 @@ -#ifndef LIB_INC_UTIL_STRING_HPP_ -#define LIB_INC_UTIL_STRING_HPP_ +#ifndef FACTER_UTIL_STRING_HPP_ +#define FACTER_UTIL_STRING_HPP_ #include #include @@ -7,7 +7,7 @@ #include #include -namespace cfacter { namespace util { +namespace facter { namespace util { /** * Checks if the given string starts with the given prefix. @@ -159,6 +159,6 @@ namespace cfacter { namespace util { */ typedef std::basic_string ci_string; -}} // namespace cfacter::util +}} // namespace facter::util -#endif // LIB_INC_UTIL_STRING_HPP_ +#endif // FACTER_UTIL_STRING_HPP_ diff --git a/lib/src/execution/posix/execution.cc b/lib/src/execution/posix/execution.cc index f396473a3a..e7fede3eb8 100644 --- a/lib/src/execution/posix/execution.cc +++ b/lib/src/execution/posix/execution.cc @@ -1,19 +1,19 @@ -#include -#include -#include -#include +#include +#include +#include +#include #include #include #include #include using namespace std; -using namespace cfacter::util; -using namespace cfacter::util::posix; +using namespace facter::util; +using namespace facter::util::posix; LOG_DECLARE_NAMESPACE("execution.posix"); -namespace cfacter { namespace execution { +namespace facter { namespace execution { void log_execution(string const& file, vector const* arguments) { @@ -36,7 +36,7 @@ namespace cfacter { namespace execution { string const& file, vector const* arguments, vector const* environment, - option_set const& options); + option_set const& options); string execute( string const& file, @@ -196,4 +196,4 @@ namespace cfacter { namespace execution { // CHILD DOES NOT RETURN } -}} // namespace cfacter::executions +}} // namespace facter::executions diff --git a/lib/src/cfacterlib.cc b/lib/src/facterlib.cc similarity index 85% rename from lib/src/cfacterlib.cc rename to lib/src/facterlib.cc index 24c4ddccc7..4e166e8c98 100644 --- a/lib/src/cfacterlib.cc +++ b/lib/src/facterlib.cc @@ -1,13 +1,13 @@ -#include +#include +#include #include "rapidjson/document.h" #include "rapidjson/prettywriter.h" #include "rapidjson/stringbuffer.h" -#include using namespace std; -using namespace cfacter::facts; +using namespace facter::facts; void loadfacts() { diff --git a/lib/src/facts/array_value.cc b/lib/src/facts/array_value.cc index b9f44d25f2..22da077114 100644 --- a/lib/src/facts/array_value.cc +++ b/lib/src/facts/array_value.cc @@ -1,9 +1,9 @@ -#include +#include #include using namespace std; -namespace cfacter { namespace facts { +namespace facter { namespace facts { string array_value::to_string() const { @@ -21,4 +21,4 @@ namespace cfacter { namespace facts { return result.str(); } -}} // namespace cfacter::facts +}} // namespace facter::facts diff --git a/lib/src/facts/bsd/networking_resolver.cc b/lib/src/facts/bsd/networking_resolver.cc index e5e17fa8d9..151bb7c191 100644 --- a/lib/src/facts/bsd/networking_resolver.cc +++ b/lib/src/facts/bsd/networking_resolver.cc @@ -1,9 +1,9 @@ -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -11,12 +11,12 @@ #include using namespace std; -using namespace cfacter::util; -using namespace cfacter::util::bsd; +using namespace facter::util; +using namespace facter::util::bsd; LOG_DECLARE_NAMESPACE("facts.bsd.networking"); -namespace cfacter { namespace facts { namespace bsd { +namespace facter { namespace facts { namespace bsd { void networking_resolver::resolve_interface_facts(fact_map& facts) { @@ -173,4 +173,4 @@ namespace cfacter { namespace facts { namespace bsd { facts.add(string(fact::mtu) + '_' + addr->ifa_name, make_value(to_string(mtu))); } -}}} // namespace cfacter::facts::bsd +}}} // namespace facter::facts::bsd diff --git a/lib/src/facts/fact.cc b/lib/src/facts/fact.cc index 0681ccf997..7bf60b5395 100644 --- a/lib/src/facts/fact.cc +++ b/lib/src/facts/fact.cc @@ -1,14 +1,14 @@ #include "../../../version.h" -#include -#include +#include +#include using namespace std; -namespace cfacter { namespace facts { +namespace facter { namespace facts { void populate_common_facts(fact_map& facts) { facts.add("cfacterversion", make_value(CFACTER_VERSION)); } -}} // namespace cfacter::facts +}} // namespace facter::facts diff --git a/lib/src/facts/fact_map.cc b/lib/src/facts/fact_map.cc index b955286292..d2f3840d1b 100644 --- a/lib/src/facts/fact_map.cc +++ b/lib/src/facts/fact_map.cc @@ -1,12 +1,12 @@ -#include -#include +#include +#include #include using namespace std; LOG_DECLARE_NAMESPACE("facts"); -namespace cfacter { namespace facts { +namespace facter { namespace facts { /** * Called to populate common facts. @@ -157,4 +157,4 @@ namespace cfacter { namespace facts { return nullptr; } -}} // namespace cfacter::facts +}} // namespace facter::facts diff --git a/lib/src/facts/fact_resolver.cc b/lib/src/facts/fact_resolver.cc index b59263f6c2..eb16f35d1d 100644 --- a/lib/src/facts/fact_resolver.cc +++ b/lib/src/facts/fact_resolver.cc @@ -1,6 +1,6 @@ -#include -#include -#include +#include +#include +#include #include using namespace std; @@ -8,7 +8,7 @@ using namespace re2; LOG_DECLARE_NAMESPACE("facts.resolver"); -namespace cfacter { namespace facts { +namespace facter { namespace facts { fact_resolver::fact_resolver(string&& name, vector&& names, vector const& patterns) : _name(move(name)), @@ -49,4 +49,4 @@ namespace cfacter { namespace facts { return false; } -}} // namespace cfacter::facts +}} // namespace facter::facts diff --git a/lib/src/facts/linux/lsb_resolver.cc b/lib/src/facts/linux/lsb_resolver.cc index 218d44b9ad..945fd5e6a6 100644 --- a/lib/src/facts/linux/lsb_resolver.cc +++ b/lib/src/facts/linux/lsb_resolver.cc @@ -1,15 +1,15 @@ -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include using namespace std; -using namespace cfacter::util; -using namespace cfacter::execution; +using namespace facter::util; +using namespace facter::execution; -namespace cfacter { namespace facts { namespace linux { +namespace facter { namespace facts { namespace linux { void lsb_resolver::resolve_facts(fact_map& facts) { @@ -87,4 +87,4 @@ namespace cfacter { namespace facts { namespace linux { facts.add(fact::lsb_release, make_value(move(value))); } -}}} // namespace cfacter::facts::linux +}}} // namespace facter::facts::linux diff --git a/lib/src/facts/linux/networking_resolver.cc b/lib/src/facts/linux/networking_resolver.cc index 7219cd407e..c2935e6b77 100644 --- a/lib/src/facts/linux/networking_resolver.cc +++ b/lib/src/facts/linux/networking_resolver.cc @@ -1,19 +1,19 @@ -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include #include #include using namespace std; -using namespace cfacter::util::posix; +using namespace facter::util::posix; LOG_DECLARE_NAMESPACE("facts.linux.networking"); -namespace cfacter { namespace facts { namespace linux { +namespace facter { namespace facts { namespace linux { bool networking_resolver::is_link_address(sockaddr const* addr) const { @@ -49,4 +49,4 @@ namespace cfacter { namespace facts { namespace linux { return req.ifr_mtu; } -}}} // namespace cfacter::facts::linux +}}} // namespace facter::facts::linux diff --git a/lib/src/facts/linux/operating_system_resolver.cc b/lib/src/facts/linux/operating_system_resolver.cc index 888289e80b..4ef8a97aeb 100644 --- a/lib/src/facts/linux/operating_system_resolver.cc +++ b/lib/src/facts/linux/operating_system_resolver.cc @@ -1,21 +1,21 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include using namespace std; -using namespace cfacter::util; -using namespace cfacter::execution; -using namespace cfacter::facts::posix; +using namespace facter::util; +using namespace facter::execution; +using namespace facter::facts::posix; -namespace cfacter { namespace facts { namespace linux { +namespace facter { namespace facts { namespace linux { void operating_system_resolver::resolve_operating_system(fact_map& facts) { @@ -332,4 +332,4 @@ namespace cfacter { namespace facts { namespace linux { return {}; } -}}} // namespace cfacter::facts::linux +}}} // namespace facter::facts::linux diff --git a/lib/src/facts/linux/platform.cc b/lib/src/facts/linux/platform.cc index f0c83b99cd..128bcfb72b 100644 --- a/lib/src/facts/linux/platform.cc +++ b/lib/src/facts/linux/platform.cc @@ -1,12 +1,12 @@ -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include using namespace std; -namespace cfacter { namespace facts { +namespace facter { namespace facts { void populate_platform_facts(fact_map& facts) { @@ -16,4 +16,4 @@ namespace cfacter { namespace facts { facts.add(make_shared()); } -}} // namespace cfacter::facts +}} // namespace facter::facts diff --git a/lib/src/facts/osx/networking_resolver.cc b/lib/src/facts/osx/networking_resolver.cc index 653e39d61e..7f36663ea2 100644 --- a/lib/src/facts/osx/networking_resolver.cc +++ b/lib/src/facts/osx/networking_resolver.cc @@ -1,14 +1,14 @@ -#include -#include -#include -#include +#include +#include +#include +#include #include #include using namespace std; -using namespace cfacter::execution; +using namespace facter::execution; -namespace cfacter { namespace facts { namespace osx { +namespace facter { namespace facts { namespace osx { bool networking_resolver::is_link_address(sockaddr const* addr) const { @@ -52,4 +52,4 @@ namespace cfacter { namespace facts { namespace osx { facts.add(fact::hostname, make_value(move(value))); } -}}} // namespace cfacter::facts::osx +}}} // namespace facter::facts::osx diff --git a/lib/src/facts/osx/platform.cc b/lib/src/facts/osx/platform.cc index 0a17b4719f..23e43d8f4f 100644 --- a/lib/src/facts/osx/platform.cc +++ b/lib/src/facts/osx/platform.cc @@ -1,11 +1,11 @@ -#include -#include -#include -#include +#include +#include +#include +#include using namespace std; -namespace cfacter { namespace facts { +namespace facter { namespace facts { void populate_platform_facts(fact_map& facts) { @@ -14,4 +14,4 @@ namespace cfacter { namespace facts { facts.add(make_shared()); } -}} // namespace cfacter::facts +}} // namespace facter::facts diff --git a/lib/src/facts/posix/kernel_resolver.cc b/lib/src/facts/posix/kernel_resolver.cc index 673a549e7a..1b86bf4970 100644 --- a/lib/src/facts/posix/kernel_resolver.cc +++ b/lib/src/facts/posix/kernel_resolver.cc @@ -1,16 +1,16 @@ -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include using namespace std; -using namespace cfacter::util; +using namespace facter::util; LOG_DECLARE_NAMESPACE("facts.posix.kernel"); -namespace cfacter { namespace facts { namespace posix { +namespace facter { namespace facts { namespace posix { void kernel_resolver::resolve_facts(fact_map& facts) { @@ -79,4 +79,4 @@ namespace cfacter { namespace facts { namespace posix { facts.add(fact::kernel_major_release, make_value(move(value))); } -}}} // namespace cfacter::facts::posix +}}} // namespace facter::facts::posix diff --git a/lib/src/facts/posix/networking_resolver.cc b/lib/src/facts/posix/networking_resolver.cc index a5f482719f..0ec60e34a4 100644 --- a/lib/src/facts/posix/networking_resolver.cc +++ b/lib/src/facts/posix/networking_resolver.cc @@ -1,7 +1,7 @@ -#include -#include -#include -#include +#include +#include +#include +#include #include #include #include @@ -18,7 +18,7 @@ using boost::format; LOG_DECLARE_NAMESPACE("facts.posix.networking"); -namespace cfacter { namespace facts { namespace posix { +namespace facter { namespace facts { namespace posix { void networking_resolver::resolve_facts(fact_map& facts) { @@ -110,4 +110,4 @@ namespace cfacter { namespace facts { namespace posix { static_cast(bytes[4]) % static_cast(bytes[5])).str(); } -}}} // namespace cfacter::facts::posix +}}} // namespace facter::facts::posix diff --git a/lib/src/facts/posix/operating_system_resolver.cc b/lib/src/facts/posix/operating_system_resolver.cc index 39c8194bf3..520c50e5d5 100644 --- a/lib/src/facts/posix/operating_system_resolver.cc +++ b/lib/src/facts/posix/operating_system_resolver.cc @@ -1,14 +1,13 @@ -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include using namespace std; -namespace cfacter { namespace facts { namespace posix { +namespace facter { namespace facts { namespace posix { void operating_system_resolver::resolve_facts(fact_map& facts) { @@ -96,4 +95,4 @@ namespace cfacter { namespace facts { namespace posix { facts.add(fact::operating_system_release, make_value(release->value())); } -}}} // namespace cfacter::facts::posix +}}} // namespace facter::facts::posix diff --git a/lib/src/facts/string_value.cc b/lib/src/facts/string_value.cc index 0d07ad6be4..8a347fb6ce 100644 --- a/lib/src/facts/string_value.cc +++ b/lib/src/facts/string_value.cc @@ -1,12 +1,12 @@ -#include +#include using namespace std; -namespace cfacter { namespace facts { +namespace facter { namespace facts { string string_value::to_string() const { return _value; } -}} // namespace cfacter::facts +}} // namespace facter::facts diff --git a/lib/src/logging/logging.cc b/lib/src/logging/logging.cc index 70300431ac..df29d7aaee 100644 --- a/lib/src/logging/logging.cc +++ b/lib/src/logging/logging.cc @@ -1,10 +1,10 @@ -#include +#include using namespace std; using namespace log4cxx; using boost::format; -namespace cfacter { namespace logging { +namespace facter { namespace logging { bool is_log_enabled(LoggerPtr logger, log_level level) { if (level == log_level::debug) { @@ -43,4 +43,4 @@ namespace cfacter { namespace logging { log(logger, level, message.str()); } -}} // namespace cfacter::logging \ No newline at end of file +}} // namespace facter::logging diff --git a/lib/src/util/posix/file.cc b/lib/src/util/posix/file.cc index acf005ea2b..94d183b089 100644 --- a/lib/src/util/posix/file.cc +++ b/lib/src/util/posix/file.cc @@ -1,11 +1,11 @@ -#include +#include #include #include #include using namespace std; -namespace cfacter { namespace util { +namespace facter { namespace util { bool file::exists(string const& path) { @@ -39,4 +39,4 @@ namespace cfacter { namespace util { return {}; } -}} // namespace cfacter::util +}} // namespace facter::util diff --git a/lib/src/util/string.cc b/lib/src/util/string.cc index 8f53837292..7ea4e9da4c 100644 --- a/lib/src/util/string.cc +++ b/lib/src/util/string.cc @@ -1,4 +1,4 @@ -#include +#include #include #include #include @@ -6,7 +6,7 @@ using namespace std; -namespace cfacter { namespace util { +namespace facter { namespace util { initializer_list default_trim_set = { '\r', '\n', ' ', '\t', '\v', '\f' }; @@ -117,4 +117,4 @@ namespace cfacter { namespace util { return strncasecmp(s1, s2, n); } -}} // namespace cfacter::util +}} // namespace facter::util From be9b62b823706aa549cb3b4f6038d0f05a13a9d8 Mon Sep 17 00:00:00 2001 From: Ryan Langseth Date: Fri, 7 Feb 2014 23:33:51 -0600 Subject: [PATCH 1805/3753] (FACT-466) Make kernelmajversion useful in FreeBSD FreeBSD has used a 2-element release number since before 3.0. The current kernalmajversion returns the first two elements of kernelversion, which is exactly the same as kernelversion on FreeBSD. This fact now returns just the first element when on FreeBSD. --- lib/facter/kernelmajversion.rb | 8 ++++++++ spec/unit/kernelmajversion_spec.rb | 23 ++++++++++++++--------- 2 files changed, 22 insertions(+), 9 deletions(-) mode change 100644 => 100755 spec/unit/kernelmajversion_spec.rb diff --git a/lib/facter/kernelmajversion.rb b/lib/facter/kernelmajversion.rb index f7302d4ea6..641f1e7aed 100644 --- a/lib/facter/kernelmajversion.rb +++ b/lib/facter/kernelmajversion.rb @@ -4,6 +4,7 @@ # # Resolution: # Takes the first 2 elements of the kernel version as delimited by periods. +# Takes the first element of the kernel version on FreeBSD # # Caveats: # @@ -13,3 +14,10 @@ Facter.value(:kernelversion).split('.')[0..1].join('.') end end + +Facter.add("kernelmajversion") do + confine :kernel => :FreeBSD + setcode do + Facter.value(:kernelversion).split('.')[0] + end +end diff --git a/spec/unit/kernelmajversion_spec.rb b/spec/unit/kernelmajversion_spec.rb old mode 100644 new mode 100755 index 426a87cd10..f63d7124ef --- a/spec/unit/kernelmajversion_spec.rb +++ b/spec/unit/kernelmajversion_spec.rb @@ -3,15 +3,20 @@ require 'spec_helper' describe "Kernel major version fact" do - - before do - Facter.fact(:kernelversion).stubs(:value).returns("12.34.56") - end - - it "should return the kernel major release using the kernel release" do - Facter.fact(:kernelmajversion).value.should == "12.34" - end -end + context "when the kernelrelease fact contains three components" do + it "returns the first two components" do + Facter.fact(:kernelversion).stubs(:value).returns("12.34.56") + Facter.fact(:kernelmajversion).value.should == "12.34" + end + end + context "when the kernelrelease fact only contains two components" do + it "returns the first component" do + Facter.fact(:kernel).stubs(:value).returns('FreeBSD') + Facter.fact(:kernelversion).stubs(:value).returns("9.2") + Facter.fact(:kernelmajversion).value.should == "9" + end + end +end From 65a9f5b617e28646fe75cd8a86c448d45b22feca Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Thu, 24 Apr 2014 22:58:42 -0700 Subject: [PATCH 1806/3753] (maint) Add an initial travis configuration --- .travis.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..be88fda105 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,21 @@ +language: cpp +compiler: + - gcc + +before_install: + # 'python-software-properties' provides add-apt-repository + - sudo apt-get -y install python-software-properties + # which is needed to add the ppa that has gcc 4.8 + - sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test + - sudo apt-get update + # which is needed to install gcc 4.8 + - sudo apt-get -y install gcc-4.8 + - sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.8 50 + # and g++ 4.8 + - sudo apt-get -y install g++-4.8 + - sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.8 50 + # and we need a newer cmake, so build it ourselves (maybe should build this and host it somewhere) + - wget http://www.cmake.org/files/v2.8/cmake-2.8.12.2.tar.gz -O $HOME/cmake-2.8.12.2.tar.gz + - pushd $HOME && tar xzf cmake-2.8.12.2.tar.gz && cd $HOME/cmake-2.8.12.2 && ./configure --prefix=$HOME && make && make install && popd + +script: mkdir release && cd release && $HOME/bin/cmake .. && make From 3fa87f3722c8e67f7cd38163ed09044beeb0707d Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Fri, 25 Apr 2014 08:29:44 -0700 Subject: [PATCH 1807/3753] (CFACT-24) Add color output to log messages. Adding cyan, green, yellow, and red output to log messages if ouputting to a terminal. Adding some additional debug output in the fact map when removing a resolver for a fact and the fact isn't present in the map. --- lib/CMakeLists.txt | 2 +- lib/src/facts/fact_map.cc | 16 ++++++++++++++ lib/src/logging/{ => posix}/logging.cc | 30 +++++++++++++++++++++----- 3 files changed, 42 insertions(+), 6 deletions(-) rename lib/src/logging/{ => posix}/logging.cc (60%) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index b22a4d7ed9..84ff104f60 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -18,7 +18,6 @@ set(LIBFACTER_COMMON_SOURCES "${CMAKE_CURRENT_LIST_DIR}/src/facts/fact_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/string_value.cc" "${CMAKE_CURRENT_LIST_DIR}/src/util/string.cc" - "${CMAKE_CURRENT_LIST_DIR}/src/logging/logging.cc" ) # Set the POSIX sources if on a POSIX platform @@ -29,6 +28,7 @@ if (UNIX) "${CMAKE_CURRENT_LIST_DIR}/src/facts/posix/networking_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/posix/operating_system_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/util/posix/file.cc" + "${CMAKE_CURRENT_LIST_DIR}/src/logging/posix/logging.cc" ) endif() diff --git a/lib/src/facts/fact_map.cc b/lib/src/facts/fact_map.cc index d2f3840d1b..9b2460bbc9 100644 --- a/lib/src/facts/fact_map.cc +++ b/lib/src/facts/fact_map.cc @@ -61,6 +61,12 @@ namespace facter { namespace facts { // Remove all fact associations for (auto const& name : resolver->names()) { + if (LOG_IS_DEBUG_ENABLED()) { + auto it = _facts.find(name); + if (it == _facts.end()) { + LOG_DEBUG("fact %1% was not resolved.", name); + } + } _resolver_map.erase(name); } _resolvers.remove(resolver); @@ -114,6 +120,16 @@ namespace facter { namespace facts { resolver->resolve(*this); } _resolvers.clear(); + + // Log any facts that didn't resolve + if (LOG_IS_DEBUG_ENABLED()) { + for (auto kvp : _resolver_map) { + auto it = _facts.find(kvp.first); + if (it == _facts.end()) { + LOG_DEBUG("fact %1% was not resolved.", kvp.first); + } + } + } _resolver_map.clear(); } diff --git a/lib/src/logging/logging.cc b/lib/src/logging/posix/logging.cc similarity index 60% rename from lib/src/logging/logging.cc rename to lib/src/logging/posix/logging.cc index df29d7aaee..2774f3cd39 100644 --- a/lib/src/logging/logging.cc +++ b/lib/src/logging/posix/logging.cc @@ -1,4 +1,6 @@ #include +#include +#include using namespace std; using namespace log4cxx; @@ -21,18 +23,36 @@ namespace facter { namespace logging { return false; } + string cyan(string const& message) { + return "\33[0;36m" + message + "\33[0m"; + } + + string green(string const& message) { + return "\33[0;32m" + message + "\33[0m"; + } + + string yellow(string const& message) { + return "\33[0;33m" + message + "\33[0m"; + } + + string red(string const& message) { + return "\33[0;31m" + message + "\33[0m"; + } + void log(LoggerPtr logger, log_level level, string const& message) { + static bool color = isatty(fileno(stdout)); + if (level == log_level::debug) { - LOG4CXX_DEBUG(logger, message); + LOG4CXX_DEBUG(logger, (color ? cyan(message) : message)); } else if (level == log_level::info) { - LOG4CXX_INFO(logger, message); + LOG4CXX_INFO(logger, (color ? green(message) : message)); } else if (level == log_level::warning) { - LOG4CXX_WARN(logger, message); + LOG4CXX_WARN(logger, (color ? yellow(message) : message)); } else if (level == log_level::error) { - LOG4CXX_ERROR(logger, message); + LOG4CXX_ERROR(logger, (color ? red(message) : message)); } else if (level == log_level::fatal) { - LOG4CXX_FATAL(logger, message); + LOG4CXX_FATAL(logger, (color ? red(message) : message)); } else { LOG4CXX_DEBUG(logger, "Invalid logging level used."); } From 99d2c7bcaff3e88170eb1c31558c31a0a1e42c50 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Sun, 27 Apr 2014 07:38:55 -0700 Subject: [PATCH 1808/3753] (maint) Add debug and release targets (each w/ cpplint) --- .travis.yml | 7 ++++++- travis_target.sh | 29 +++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100755 travis_target.sh diff --git a/.travis.yml b/.travis.yml index be88fda105..6b89182dd0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,4 +18,9 @@ before_install: - wget http://www.cmake.org/files/v2.8/cmake-2.8.12.2.tar.gz -O $HOME/cmake-2.8.12.2.tar.gz - pushd $HOME && tar xzf cmake-2.8.12.2.tar.gz && cd $HOME/cmake-2.8.12.2 && ./configure --prefix=$HOME && make && make install && popd -script: mkdir release && cd release && $HOME/bin/cmake .. && make +script: ./travis_target.sh + +env: + - TRAVIS_TARGET=CPPLINT + - TRAVIS_TARGET=RELEASE + - TRAVIS_TARGET=DEBUG diff --git a/travis_target.sh b/travis_target.sh new file mode 100755 index 0000000000..04c4abd518 --- /dev/null +++ b/travis_target.sh @@ -0,0 +1,29 @@ +#! /bin/bash + +# Travis cpp jobs construct a matrix based on environment variables +# (and the value of 'compiler'). In order to test multiple builds +# (release/debug/cpplint), this uses a TRAVIS_TARGET env var to +# do the right thing. +# +# Note that it assumes cmake is at $HOME/bin which is an artifact +# of the before_install step in this project's .travis.yml. + +function travis_make() +{ + mkdir $1 && cd $1 + + # cmake + [ $1 == "debug" ] && export CMAKE_VARS=" -DCMAKE_BUILD_TYPE=Debug " + $HOME/bin/cmake $CMAKE_VARS .. + + # make + [ $1 == "cpplint" ] && export MAKE_TARGET=" cpplint " + make $MAKE_TARGET +} + +case $TRAVIS_TARGET in + "CPPLINT" ) travis_make cpplint ;; + "RELEASE" ) travis_make release ;; + "DEBUG" ) travis_make debug ;; + *) echo "Nothing to do!" +esac From a82ee8ac08b9b7906d23bcaf8768a4fa01cda223 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Sun, 27 Apr 2014 07:38:55 -0700 Subject: [PATCH 1809/3753] (maint) Quiet up building cmake to /dev/null Travis only gives us 10000 lines of logs and building cmake chews up 2000+ of that (and won't be changing), so this gives a better shot at seeing failure causes. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6b89182dd0..023ee3c08a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,7 @@ before_install: - sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.8 50 # and we need a newer cmake, so build it ourselves (maybe should build this and host it somewhere) - wget http://www.cmake.org/files/v2.8/cmake-2.8.12.2.tar.gz -O $HOME/cmake-2.8.12.2.tar.gz - - pushd $HOME && tar xzf cmake-2.8.12.2.tar.gz && cd $HOME/cmake-2.8.12.2 && ./configure --prefix=$HOME && make && make install && popd + - pushd $HOME && tar xzf cmake-2.8.12.2.tar.gz && cd $HOME/cmake-2.8.12.2 && ./configure --prefix=$HOME > /dev/null && make > /dev/null && make install > /dev/null && popd script: ./travis_target.sh From 47719a7b46e0965784c1e5919cb0f95977414982 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Sun, 27 Apr 2014 19:10:49 -0500 Subject: [PATCH 1810/3753] (maint) Fix kernel major version fact name. The fact name cfacter was using was "kernelmajrelease". The correct fact name is "kernelmajversion". --- lib/inc/facter/facts/fact.hpp | 2 +- lib/inc/facter/facts/posix/kernel_resolver.hpp | 2 +- lib/src/facts/posix/kernel_resolver.cc | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/inc/facter/facts/fact.hpp b/lib/inc/facter/facts/fact.hpp index c99f903952..075f13dc28 100644 --- a/lib/inc/facter/facts/fact.hpp +++ b/lib/inc/facter/facts/fact.hpp @@ -11,7 +11,7 @@ namespace facter { namespace facts { constexpr static char const* kernel = "kernel"; constexpr static char const* kernel_version = "kernelversion"; constexpr static char const* kernel_release = "kernelrelease"; - constexpr static char const* kernel_major_release = "kernelmajrelease"; + constexpr static char const* kernel_major_version = "kernelmajversion"; constexpr static char const* operating_system = "operatingsystem"; constexpr static char const* os_family = "osfamily"; constexpr static char const* operating_system_release = "operatingsystemrelease"; diff --git a/lib/inc/facter/facts/posix/kernel_resolver.hpp b/lib/inc/facter/facts/posix/kernel_resolver.hpp index 3740ffccac..ffeebd669c 100644 --- a/lib/inc/facter/facts/posix/kernel_resolver.hpp +++ b/lib/inc/facter/facts/posix/kernel_resolver.hpp @@ -22,7 +22,7 @@ namespace facter { namespace facts { namespace posix { fact::kernel, fact::kernel_version, fact::kernel_release, - fact::kernel_major_release + fact::kernel_major_version }) { } diff --git a/lib/src/facts/posix/kernel_resolver.cc b/lib/src/facts/posix/kernel_resolver.cc index 1b86bf4970..e1586e5ec7 100644 --- a/lib/src/facts/posix/kernel_resolver.cc +++ b/lib/src/facts/posix/kernel_resolver.cc @@ -76,7 +76,7 @@ namespace facter { namespace facts { namespace posix { value = value.substr(0, pos); } } - facts.add(fact::kernel_major_release, make_value(move(value))); + facts.add(fact::kernel_major_version, make_value(move(value))); } }}} // namespace facter::facts::posix From 83bc2b93b5b62f0962b149f3a64a1277002abca1 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Fri, 25 Apr 2014 08:36:16 -0700 Subject: [PATCH 1811/3753] (CFACT-22) Add missing domain and fqdn facts to networking resolver. Adding the domain and fqdn facts to the networking resolver. --- lib/inc/facter/facts/fact.hpp | 2 + .../facts/posix/networking_resolver.hpp | 7 ++ lib/inc/facter/util/posix/scoped_addrinfo.hpp | 66 +++++++++++++++++++ lib/src/facts/bsd/networking_resolver.cc | 3 +- lib/src/facts/linux/networking_resolver.cc | 2 +- lib/src/facts/posix/kernel_resolver.cc | 2 +- lib/src/facts/posix/networking_resolver.cc | 43 +++++++++++- 7 files changed, 120 insertions(+), 5 deletions(-) create mode 100644 lib/inc/facter/util/posix/scoped_addrinfo.hpp diff --git a/lib/inc/facter/facts/fact.hpp b/lib/inc/facter/facts/fact.hpp index c99f903952..6faab1a961 100644 --- a/lib/inc/facter/facts/fact.hpp +++ b/lib/inc/facter/facts/fact.hpp @@ -33,6 +33,8 @@ namespace facter { namespace facts { constexpr static char const* network6 = "network6"; constexpr static char const* macaddress = "macaddress"; constexpr static char const* interfaces = "interfaces"; + constexpr static char const* domain = "domain"; + constexpr static char const* fqdn = "fqdn"; }; }} // namespace facter::facts diff --git a/lib/inc/facter/facts/posix/networking_resolver.hpp b/lib/inc/facter/facts/posix/networking_resolver.hpp index f3797d0ed3..2a0223836d 100644 --- a/lib/inc/facter/facts/posix/networking_resolver.hpp +++ b/lib/inc/facter/facts/posix/networking_resolver.hpp @@ -26,6 +26,8 @@ namespace facter { namespace facts { namespace posix { fact::network, fact::macaddress, fact::interfaces, + fact::domain, + fact::fqdn, }, { std::string("^") + fact::ipaddress + "_", @@ -85,6 +87,11 @@ namespace facter { namespace facts { namespace posix { * @param facts The fact map that is resolving facts. */ virtual void resolve_hostname(fact_map& facts); + /** + * Called to resolve the domain and fqdn facts. + * @param facts The fact map that is resolving facts. + */ + virtual void resolve_domain(fact_map& facts); /** * Called to resolve interface facts. * @param facts The fact map that is resolving facts. diff --git a/lib/inc/facter/util/posix/scoped_addrinfo.hpp b/lib/inc/facter/util/posix/scoped_addrinfo.hpp new file mode 100644 index 0000000000..68f6b452b3 --- /dev/null +++ b/lib/inc/facter/util/posix/scoped_addrinfo.hpp @@ -0,0 +1,66 @@ +#ifndef FACTER_UTIL_POSIX_SCOPED_ADDRINFO_HPP_ +#define FACTER_UTIL_POSIX_SCOPED_ADDRINFO_HPP_ + +#include "../scoped_resource.hpp" +#include +#include +#include +#include + +namespace facter { namespace util { namespace posix { + + /** + * Represents a scoped file descriptor for POSIX systems. + * Automatically frees the address information pointer when it goes out of scope. + */ + struct scoped_addrinfo : scoped_resource + { + /** + * Constructs a scoped_addrinfo. + * @param info The address info to free when destroyed. + */ + explicit scoped_addrinfo(std::string const& hostname) + { + addrinfo hints; + std::memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_CANONNAME; + + _result = getaddrinfo(hostname.c_str(), nullptr, &hints, &_resource); + if (_result != 0) { + _resource = nullptr; + } else { + _deleter = free; + } + } + + /** + * Constructs a scoped_addrinfo. + * @param info The address info to free when destroyed. + */ + explicit scoped_addrinfo(addrinfo* info) : + scoped_resource(std::move(info), free), + _result(0) + { + } + + /** + * Returns the result of any call to getaddrinfo. + * @returns Returns the result of any call to getaddrinfo. + */ + int result() const { return _result; } + + private: + static void free(addrinfo* info) + { + if (info) { + ::freeaddrinfo(info); + } + } + int _result; + }; + +}}} // namespace facter::util::posix + +#endif // FACTER_UTIL_POSIX_SCOPED_ADDRINFO_HPP_ diff --git a/lib/src/facts/bsd/networking_resolver.cc b/lib/src/facts/bsd/networking_resolver.cc index 151bb7c191..c0e4945305 100644 --- a/lib/src/facts/bsd/networking_resolver.cc +++ b/lib/src/facts/bsd/networking_resolver.cc @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -23,7 +24,7 @@ namespace facter { namespace facts { namespace bsd { // Scope the head ifaddrs ptr scoped_ifaddrs addrs; if (!addrs) { - LOG_WARNING("getifaddrs failed with %1%: interface facts are unavailable.", errno); + LOG_WARNING("getifaddrs failed: %1% (%2%): interface facts are unavailable.", strerror(errno), errno); return; } diff --git a/lib/src/facts/linux/networking_resolver.cc b/lib/src/facts/linux/networking_resolver.cc index c2935e6b77..bb822516b6 100644 --- a/lib/src/facts/linux/networking_resolver.cc +++ b/lib/src/facts/linux/networking_resolver.cc @@ -43,7 +43,7 @@ namespace facter { namespace facts { namespace linux { scoped_descriptor sock(socket(AF_INET, SOCK_DGRAM, 0)); if (ioctl(sock, SIOCGIFMTU, &req) != 0) { - LOG_WARNING("ioctl failed with %1%: interface MTU fact is unavailable for interface %2%.", errno, interface); + LOG_WARNING("ioctl failed: %1% (%2%): interface MTU fact is unavailable for interface %3%.", strerror(errno), errno, interface); return -1; } return req.ifr_mtu; diff --git a/lib/src/facts/posix/kernel_resolver.cc b/lib/src/facts/posix/kernel_resolver.cc index 1b86bf4970..45302402dc 100644 --- a/lib/src/facts/posix/kernel_resolver.cc +++ b/lib/src/facts/posix/kernel_resolver.cc @@ -17,7 +17,7 @@ namespace facter { namespace facts { namespace posix { utsname name; memset(&name, 0, sizeof(name)); if (uname(&name) != 0) { - LOG_WARNING("uname failed with %1%: kernel facts are unavailable.", errno); + LOG_WARNING("uname failed: %1% (%2%): kernel facts are unavailable.", strerror(errno), errno); return; } // Resolve all kernel-related facts diff --git a/lib/src/facts/posix/networking_resolver.cc b/lib/src/facts/posix/networking_resolver.cc index 0ec60e34a4..1ae5026f89 100644 --- a/lib/src/facts/posix/networking_resolver.cc +++ b/lib/src/facts/posix/networking_resolver.cc @@ -2,6 +2,8 @@ #include #include #include +#include +#include #include #include #include @@ -9,11 +11,13 @@ #include #include #include +#include #include -#include #include using namespace std; +using namespace facter::util; +using namespace facter::util::posix; using boost::format; LOG_DECLARE_NAMESPACE("facts.posix.networking"); @@ -23,6 +27,7 @@ namespace facter { namespace facts { namespace posix { void networking_resolver::resolve_facts(fact_map& facts) { resolve_hostname(facts); + resolve_domain(facts); resolve_interface_facts(facts); } @@ -31,7 +36,7 @@ namespace facter { namespace facts { namespace posix { int max = sysconf(_SC_HOST_NAME_MAX); vector name(max); if (gethostname(name.data(), max) != 0) { - LOG_WARNING("gethostname failed with %1%: hostname fact is unavailable.", errno); + LOG_WARNING("gethostname failed: %1% (%2%): hostname fact is unavailable.", strerror(errno), errno); return; } @@ -48,6 +53,40 @@ namespace facter { namespace facts { namespace posix { facts.add(fact::hostname, make_value(move(value))); } + void networking_resolver::resolve_domain(fact_map& facts) + { + auto hostname = facts.get(fact::hostname, false); + if (!hostname) { + LOG_WARNING("domain and fqdn facts cannot be resolved without the hostname fact."); + return; + } + + // Retrieve the fully-qualified domain name + scoped_addrinfo info(hostname->value()); + if (info.result() != 0) { + if (info.result() == EAI_NONAME) { + LOG_WARNING("domain is unknown for host %1%: domain and fqdn facts are unavailable.", hostname->value()); + return; + } + LOG_WARNING("getaddrinfo failed: %1% (%2%): domain and fqdn facts are unavailable.", gai_strerror(info.result()), info.result()); + return; + } + + if (!info) { + return; + } + + // Add the FQDN fact + string domain = static_cast(info)->ai_canonname; + facts.add(fact::fqdn, make_value(domain)); + + // Trim off the hostname from the FQDN to get the domain fact + if (starts_with(domain, hostname->value() + ".")) { + domain = domain.substr(hostname->value().length() + 1); + } + facts.add(fact::domain, make_value(move(domain))); + } + string networking_resolver::address_to_string(sockaddr const* addr, sockaddr const* mask) const { if (!addr) { From 0fe6cc4346aae6c451f9b5d73a037331e6ae266e Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Mon, 28 Apr 2014 20:30:26 -0700 Subject: [PATCH 1812/3753] (maint) Remove vendor libraries in favor of find_package. Removing Boost, log4cxx, and RE2 from the vendor directory. Instead, use find_package to locate pre-existing binary packages. --- .travis.yml | 6 +- CMakeLists.txt | 33 ++++++++--- PostInstall.cmake | 2 - README.md | 16 ++++-- exe/CMakeLists.txt | 4 +- lib/CMakeLists.txt | 4 +- scripts/osx_boost_names.sh | 27 --------- travis_target.sh => scripts/travis_target.sh | 0 vendor/FindLOG4CXX.cmake | 59 +++++++++++++++++++ vendor/FindRE2.cmake | 55 ++++++++++++++++++ vendor/apr.cmake | 42 -------------- vendor/aprutil.cmake | 43 -------------- vendor/boost.cmake | 45 --------------- vendor/log4cxx.cmake | 44 -------------- vendor/log4cxx.patch | 60 -------------------- vendor/re2.cmake | 41 ------------- 16 files changed, 159 insertions(+), 322 deletions(-) delete mode 100644 PostInstall.cmake delete mode 100755 scripts/osx_boost_names.sh rename travis_target.sh => scripts/travis_target.sh (100%) create mode 100644 vendor/FindLOG4CXX.cmake create mode 100644 vendor/FindRE2.cmake delete mode 100644 vendor/apr.cmake delete mode 100644 vendor/aprutil.cmake delete mode 100644 vendor/boost.cmake delete mode 100644 vendor/log4cxx.cmake delete mode 100644 vendor/log4cxx.patch delete mode 100644 vendor/re2.cmake diff --git a/.travis.yml b/.travis.yml index 023ee3c08a..df81a15063 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,8 +17,12 @@ before_install: # and we need a newer cmake, so build it ourselves (maybe should build this and host it somewhere) - wget http://www.cmake.org/files/v2.8/cmake-2.8.12.2.tar.gz -O $HOME/cmake-2.8.12.2.tar.gz - pushd $HOME && tar xzf cmake-2.8.12.2.tar.gz && cd $HOME/cmake-2.8.12.2 && ./configure --prefix=$HOME > /dev/null && make > /dev/null && make install > /dev/null && popd + # Install dependencies of cfacter + - sudo apt-get -y install libboost-filesystem1.48-dev libboost-program-options1.48-dev liblog4cxx10-dev + - wget https://re2.googlecode.com/files/re2-20140304.tgz -O $HOME/re2-20140304.tgz + - pushd $HOME && tar xzf re2-20140304.tgz && cd $HOME/re2 && make > /dev/null && sudo make install > /dev/null && popd -script: ./travis_target.sh +script: ./scripts/travis_target.sh env: - TRAVIS_TARGET=CPPLINT diff --git a/CMakeLists.txt b/CMakeLists.txt index 4c7b38ebbe..5c548435a1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,9 @@ cmake_minimum_required(VERSION 2.8.12) project(CFACTER) +set(VENDOR_DIRECTORY "${PROJECT_SOURCE_DIR}/vendor") +list(APPEND CMAKE_MODULE_PATH ${VENDOR_DIRECTORY}) + set(CFACTER_VERSION_MAJOR 0) set(CFACTER_VERSION_MINOR 1) set(CFACTER_VERSION_PATCH 0) @@ -11,6 +14,27 @@ configure_file ( "${PROJECT_SOURCE_DIR}/version.h" ) +# Find Boost +find_package(Boost 1.48 COMPONENTS program_options filesystem REQUIRED) +if (NOT Boost_FOUND) + message(FATAL_ERROR "Boost 1.48 or newer is required. Please install it before building.") +endif() + +# Find Log4cxx +find_package(LOG4CXX REQUIRED) +if (NOT LOG4CXX_FOUND) + message(FATAL_ERROR "Log4cxx version 10.0 is required. Please install it before building.") +endif() + +# Find RE2 +find_package(RE2 REQUIRED) +if (NOT RE2_FOUND) + message(FATAL_ERROR "RE2 is required. Please install it before building.") +endif() + +# Include vendor libraries +include(${VENDOR_DIRECTORY}/rapidjson.cmake) + if(APPLE) # Set the RPATH to OSX magic value @executable_path (where the loading executable is located) set(CMAKE_INSTALL_RPATH "@executable_path/") @@ -25,13 +49,6 @@ if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(CMAKE_INSTALL_PREFIX "/opt/cfacter" CACHE PATH "default install path" FORCE) endif() -# Include vendor libraries -set(VENDOR_DIRECTORY "${PROJECT_SOURCE_DIR}/vendor") -include(${VENDOR_DIRECTORY}/re2.cmake) -include(${VENDOR_DIRECTORY}/rapidjson.cmake) -include(${VENDOR_DIRECTORY}/log4cxx.cmake) -include(${VENDOR_DIRECTORY}/boost.cmake) - add_subdirectory(exe) # @@ -77,5 +94,3 @@ endif() add_custom_target(cppcheck COMMAND cppcheck lib exe ) - -install(SCRIPT "${CMAKE_SOURCE_DIR}/PostInstall.cmake") diff --git a/PostInstall.cmake b/PostInstall.cmake deleted file mode 100644 index 43445c9476..0000000000 --- a/PostInstall.cmake +++ /dev/null @@ -1,2 +0,0 @@ - -# TODO: Fixup RPATH of dependencies on install for both OSX and Linux \ No newline at end of file diff --git a/README.md b/README.md index 59e897bb44..4a472de631 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,15 @@ Tinkering with a C/C++ facter Build Requirements ------------------ -* CMake >= 2.8 +* CMake >= 2.8.12 +* Boost C++ Libraries >= 1.48 +* Apache log4cxx >= 10.0 +* Google's RE2 library Generating Build Files ---------------------- -All examples start by assuming the current directory is the root of the repo. +All of the following examples start by assuming the current directory is the root of the repo. Before building cfacter, use `cmake` to generate build files: @@ -19,6 +22,13 @@ Before building cfacter, use `cmake` to generate build files: $ cd release $ cmake .. +To generate build files with debug information: + + $ mkdir debug + $ cd debug + $ cmake -DCMAKE_BUILD_TYPE=Debug .. + + Build ----- @@ -29,9 +39,7 @@ To build cfacter, use 'make': To build cfacter with debug information: - $ mkdir debug $ cd debug - $ cmake -DCMAKE_BUILD_TYPE=Debug .. $ make Run diff --git a/exe/CMakeLists.txt b/exe/CMakeLists.txt index e5a5272015..a5a1a5595d 100644 --- a/exe/CMakeLists.txt +++ b/exe/CMakeLists.txt @@ -20,9 +20,9 @@ endif() include_directories( ${LIBFACTER_DIR}/inc ${LOG4CXX_INCLUDE_DIRS} - ${BOOST_INCLUDE_DIRS} + ${Boost_INCLUDE_DIRS} ) add_executable(cfacter ${CFACTER_SOURCES}) -target_link_libraries(cfacter libfacter liblog4cxx libboost_program_options) +target_link_libraries(cfacter libfacter ${LOG4CXX_LIBRARIES} ${Boost_LIBRARIES}) install(TARGETS cfacter DESTINATION .) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 84ff104f60..aee40c6066 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -66,8 +66,8 @@ include_directories( # Link in additional libraries target_link_libraries(libfacter pthread - libre2 - liblog4cxx + ${RE2_LIBRARIES} + ${LOG4CXX_LIBRARIES} ) # Add a dependency on rapidjson diff --git a/scripts/osx_boost_names.sh b/scripts/osx_boost_names.sh deleted file mode 100755 index f6fa2400b2..0000000000 --- a/scripts/osx_boost_names.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/sh - -if [ "$#" -eq 0 ]; then - echo "usage: osx_boost_names " - exit 1 -fi - -# Search for all dynamic libraries in the given source directory -files=$(find $1 -type f -name '*.dylib') -for i in $files -do - # Fix the install_name so that it starts with @rpath - filename=$(basename $i) - echo "Fixing boost install names for: $filename" - install_name_tool -id @rpath/$filename $i - if [ "$?" != "0" ]; then - exit 1 - fi - # Look for all the other dynamic libraries and fix their references to this library - for j in $files - do - if [ "$i" == "$j" ]; then - continue - fi - install_name_tool -change $filename @rpath/$filename $j - done -done diff --git a/travis_target.sh b/scripts/travis_target.sh similarity index 100% rename from travis_target.sh rename to scripts/travis_target.sh diff --git a/vendor/FindLOG4CXX.cmake b/vendor/FindLOG4CXX.cmake new file mode 100644 index 0000000000..a42e8d47ca --- /dev/null +++ b/vendor/FindLOG4CXX.cmake @@ -0,0 +1,59 @@ +################################################################################ +# +# CMake script for finding Log4cxx. +# The default CMake search process is used to locate files. +# +# This script creates the following variables: +# LOG4CXX_FOUND: Boolean that indicates if the package was found +# LOG4CXX_INCLUDE_DIRS: Paths to the necessary header files +# LOG4CXX_LIBRARIES: Package libraries +# LOG4CXX_LIBRARY_DIRS: Path to package libraries +# +################################################################################ + +include(FindPackageHandleStandardArgs) + +# See if LOG4CXX_ROOT is not already set in CMake +IF (NOT LOG4CXX_ROOT) + # See if LOG4CXX_ROOT is set in process environment + IF ( NOT $ENV{LOG4CXX_ROOT} STREQUAL "" ) + SET (LOG4CXX_ROOT "$ENV{LOG4CXX_ROOT}") + MESSAGE(STATUS "Detected LOG4CXX_ROOT set to '${LOG4CXX_ROOT}'") + ENDIF () +ENDIF () + +# If LOG4CXX_ROOT is available, set up our hints +IF (LOG4CXX_ROOT) + SET (LOG4CXX_INCLUDE_HINTS HINTS "${LOG4CXX_ROOT}/include" "${LOG4CXX_ROOT}") + SET (LOG4CXX_LIBRARY_HINTS HINTS "${LOG4CXX_ROOT}/lib") +ENDIF () + +# Find headers and libraries +find_path(LOG4CXX_INCLUDE_DIR NAMES log4cxx/log4cxx.h ${LOG4CXX_INCLUDE_HINTS}) +find_library(LOG4CXX_LIBRARY NAMES log4cxx ${LOG4CXX_LIBRARY_HINTS}) +find_library(LOG4CXXD_LIBRARY NAMES log4cxx${CMAKE_DEBUG_POSTFIX} ${LOG4CXX_LIBRARY_HINTS}) + +# Set LOG4CXX_FOUND honoring the QUIET and REQUIRED arguments +find_package_handle_standard_args(LOG4CXX DEFAULT_MSG LOG4CXX_LIBRARY LOG4CXX_INCLUDE_DIR) + +# Output variables +if(LOG4CXX_FOUND) + # Include dirs + set(LOG4CXX_INCLUDE_DIRS ${LOG4CXX_INCLUDE_DIR}) + + # Libraries + if(LOG4CXX_LIBRARY) + set(LOG4CXX_LIBRARIES optimized ${LOG4CXX_LIBRARY}) + else(LOG4CXX_LIBRARY) + set(LOG4CXX_LIBRARIES "") + endif(LOG4CXX_LIBRARY) + if(LOG4CXXD_LIBRARY) + set(LOG4CXX_LIBRARIES debug ${LOG4CXXD_LIBRARY} ${LOG4CXX_LIBRARIES}) + endif(LOG4CXXD_LIBRARY) + + # Link dirs + get_filename_component(LOG4CXX_LIBRARY_DIRS ${LOG4CXX_LIBRARY} PATH) +endif() + +# Advanced options for not cluttering the cmake UIs +mark_as_advanced(LOG4CXX_INCLUDE_DIR LOG4CXX_LIBRARY) \ No newline at end of file diff --git a/vendor/FindRE2.cmake b/vendor/FindRE2.cmake new file mode 100644 index 0000000000..e6752d0d52 --- /dev/null +++ b/vendor/FindRE2.cmake @@ -0,0 +1,55 @@ +################################################################################ +# +# CMake script for finding RE2. +# The default CMake search process is used to locate files. +# +# This script creates the following variables: +# RE2_FOUND: Boolean that indicates if the package was found +# RE2_INCLUDE_DIRS: Paths to the necessary header files +# RE2_LIBRARIES: Package libraries +# RE2_LIBRARY_DIRS: Path to package libraries +# +################################################################################ + +include(FindPackageHandleStandardArgs) + +# See if RE2_ROOT is not already set in CMake +if (NOT RE2_ROOT) + # See if RE2_ROOT is set in process environment + if (NOT $ENV{RE2_ROOT} STREQUAL "") + set(RE2_ROOT "$ENV{RE2_ROOT}") + message(STATUS "Detected RE2_ROOT set to '${RE2_ROOT}'") + endif() +endif() + +# If RE2_ROOT is available, set up our hints +if (RE2_ROOT) + set(RE2_INCLUDE_HINTS HINTS "${RE2_ROOT}/include" "${RE2_ROOT}") + set(RE2_LIBRARY_HINTS HINTS "${RE2_ROOT}/lib") +endif() + +# Find headers and libraries +find_path(RE2_INCLUDE_DIR NAMES re2/re2.h ${RE2_INCLUDE_HINTS}) +find_library(RE2_LIBRARY NAMES re2 ${RE2_LIBRARY_HINTS}) + +# Set RE2_FOUND honoring the QUIET and REQUIRED arguments +find_package_handle_standard_args(RE2 DEFAULT_MSG RE2_LIBRARY RE2_INCLUDE_DIR) + +# Output variables +if(RE2_FOUND) + # Include dirs + set(RE2_INCLUDE_DIRS ${RE2_INCLUDE_DIR}) + + # Libraries + if(RE2_LIBRARY) + set(RE2_LIBRARIES ${RE2_LIBRARY}) + else() + set(RE2_LIBRARIES "") + endif() + + # Link dirs + get_filename_component(RE2_LIBRARY_DIRS ${RE2_LIBRARY} PATH) +endif() + +# Advanced options for not cluttering the cmake UIs +mark_as_advanced(RE2_INCLUDE_DIR RE2_LIBRARY) \ No newline at end of file diff --git a/vendor/apr.cmake b/vendor/apr.cmake deleted file mode 100644 index 37c8ce2e3a..0000000000 --- a/vendor/apr.cmake +++ /dev/null @@ -1,42 +0,0 @@ -cmake_minimum_required(VERSION 2.8.12) -include(ExternalProject) - -set(APR_SHARED_OBJECT_FILE "libapr-1.so.0.5.0") - -if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") - set(APR_SHARED_OBJECT_FILE "libapr-1.0.dylib") - set(APR_LD_FLAGS "-Wl,-install_name,@rpath/${APR_SHARED_OBJECT_FILE}") - set(APR_CPP_FLAGS "-Wno-empty-body") -elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") -elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel") -elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") -endif() - -# Add an external project to build apr -externalproject_add( - apr - PREFIX "${PROJECT_BINARY_DIR}" - URL "file://${VENDOR_DIRECTORY}/apr-1.5.0.tar.gz" - URL_MD5 "6419a8f7e89ad51b5bad7b0c84cc818c" - CONFIGURE_COMMAND ./configure --enable-shared --disable-static CXXFLAGS=${APR_CPP_FLAGS} LDFLAGS=${APR_LD_FLAGS} - BUILD_COMMAND make - BUILD_IN_SOURCE 1 - INSTALL_COMMAND "" -) - -# Set some useful variables based on the source directory -externalproject_get_property(apr SOURCE_DIR) -set(APR_INCLUDE_DIRS "${SOURCE_DIR}/include") -set(APR_SHARED_OBJECT_PATH "${SOURCE_DIR}/.libs/${APR_SHARED_OBJECT_FILE}") -set(APR_LIBRARIES "${APR_SHARED_OBJECT_PATH}") -set(APR_CONFIG_PATH "${SOURCE_DIR}/apr-1-config") - -set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "${SOURCE_DIR}/.libs") - -add_library(libapr SHARED IMPORTED) -set_target_properties(libapr - PROPERTIES PREFIX "" - IMPORTED_LOCATION "${APR_SHARED_OBJECT_PATH}" -) -add_dependencies(libapr apr) -install(FILES "${APR_SHARED_OBJECT_PATH}" DESTINATION .) diff --git a/vendor/aprutil.cmake b/vendor/aprutil.cmake deleted file mode 100644 index 777eacedb2..0000000000 --- a/vendor/aprutil.cmake +++ /dev/null @@ -1,43 +0,0 @@ -cmake_minimum_required(VERSION 2.8.12) -include(ExternalProject) -include(${VENDOR_DIRECTORY}/apr.cmake) - -set(APR_UTIL_SHARED_OBJECT_FILE "libaprutil-1.so.0.5.3") - -if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") - set(APR_UTIL_SHARED_OBJECT_FILE "libaprutil-1.0.dylib") - set(APR_UTIL_LD_FLAGS "-Wl,-install_name,@rpath/${APR_UTIL_SHARED_OBJECT_FILE},-rpath,${CMAKE_BINARY_DIR}/src/apr/.libs") -elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") -elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel") -elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") -endif() - -# Add an external project to build apr-util -externalproject_add( - aprutil - PREFIX "${PROJECT_BINARY_DIR}" - URL "file://${VENDOR_DIRECTORY}/apr-util-1.5.3.tar.gz" - URL_MD5 "71a11d037240b292f824ba1eb537b4e3" - CONFIGURE_COMMAND ./configure --with-apr=${APR_CONFIG_PATH} CXXFLAGS=${APR_UTIL_CPP_FLAGS} APRUTIL_LDFLAGS=${APR_UTIL_LD_FLAGS} - BUILD_COMMAND make - BUILD_IN_SOURCE 1 - INSTALL_COMMAND "" -) -add_dependencies(aprutil apr) - -# Set some useful variables based on the source directory -externalproject_get_property(aprutil SOURCE_DIR) -set(APR_UTIL_INCLUDE_DIRS "${SOURCE_DIR}/include") -set(APR_UTIL_SHARED_OBJECT_PATH "${SOURCE_DIR}/.libs/${APR_UTIL_SHARED_OBJECT_FILE}") -set(APR_UTIL_LIBRARIES "${APR_UTIL_SHARED_OBJECT_PATH}") -set(APR_UTIL_CONFIG_PATH "${SOURCE_DIR}/apu-1-config") - -set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "${SOURCE_DIR}/.libs") - -add_library(libaprutil SHARED IMPORTED) -set_target_properties(libaprutil - PROPERTIES PREFIX "" - IMPORTED_LOCATION "${APR_UTIL_SHARED_OBJECT_PATH}" -) -add_dependencies(libaprutil aprutil) -install(FILES "${APR_UTIL_SHARED_OBJECT_PATH}" DESTINATION .) diff --git a/vendor/boost.cmake b/vendor/boost.cmake deleted file mode 100644 index adca799ede..0000000000 --- a/vendor/boost.cmake +++ /dev/null @@ -1,45 +0,0 @@ -cmake_minimum_required(VERSION 2.8.12) -include(ExternalProject) - -# Set the Boost libraries we need built here -set(BOOST_TO_BUILD program_options) - -# Add an external project to build boost -externalproject_add( - boost - PREFIX "${PROJECT_BINARY_DIR}" - URL "file://${VENDOR_DIRECTORY}/boost_1_55_0.tar.gz" - URL_MD5 "93780777cfbf999a600f62883bd54b17" - CONFIGURE_COMMAND ./bootstrap.sh --with-libraries=${BOOST_TO_BUILD} - BUILD_COMMAND ./b2 - BUILD_IN_SOURCE 1 - INSTALL_COMMAND "" -) - -# Set some useful variables based on the source directory -externalproject_get_property(boost SOURCE_DIR) -set(BOOST_INCLUDE_DIRS ${SOURCE_DIR}) - -if(APPLE) - externalproject_add_step( - boost - installnames - COMMAND ${PROJECT_SOURCE_DIR}/scripts/osx_boost_names.sh ${SOURCE_DIR}/stage/lib - COMMENT "Fixing boost install names" - DEPENDEES build - ) -endif() - -if (APPLE) - set(BOOST_SO_EXT "dylib") -else() - set(BOOST_SO_EXT "so.1.55.0") -endif() - -foreach(BOOST_LIBRARY ${BOOST_TO_BUILD}) - set(BOOST_LIBRARY_TARGET libboost_${BOOST_LIBRARY}) - add_library(${BOOST_LIBRARY_TARGET} SHARED IMPORTED) - set(BOOST_LIBRARY_LOCATION "${SOURCE_DIR}/stage/lib/libboost_${BOOST_LIBRARY}.${BOOST_SO_EXT}") - set_target_properties(${BOOST_LIBRARY_TARGET} PROPERTIES PREFIX "" IMPORTED_LOCATION ${BOOST_LIBRARY_LOCATION}) - install(FILES ${BOOST_LIBRARY_LOCATION} DESTINATION .) -endforeach() diff --git a/vendor/log4cxx.cmake b/vendor/log4cxx.cmake deleted file mode 100644 index 5513ec3742..0000000000 --- a/vendor/log4cxx.cmake +++ /dev/null @@ -1,44 +0,0 @@ -cmake_minimum_required(VERSION 2.8.12) -include(ExternalProject) -include(${VENDOR_DIRECTORY}/aprutil.cmake) - -set(LOG4CXX_SHARED_OBJECT_FILE "liblog4cxx.so.10.0.0") - -if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") - set(LOG4CXX_SHARED_OBJECT_FILE "liblog4cxx.10.0.0.dylib") - set(LOG4CXX_LD_FLAGS "-Wl,-install_name,@rpath/${LOG4CXX_SHARED_OBJECT_FILE},-rpath,${CMAKE_BINARY_DIR}/src/aprutil/.libs,-rpath,${CMAKE_BINARY_DIR}/src/apr/.libs") - set(LOG4CXX_CPP_FLAGS "-Wno-empty-body") -elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") -elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel") -elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") -endif() - -# Add an external project to build log4cxx -externalproject_add( - log4cxx - PREFIX "${PROJECT_BINARY_DIR}" - URL "file://${VENDOR_DIRECTORY}/apache-log4cxx-0.10.0.tar.gz" - URL_MD5 "b30ffb8da3665178e68940ff7a61084c" - CONFIGURE_COMMAND ./configure --enable-shared --disable-static --with-apr=${APR_CONFIG_PATH} --with-apr-util=${APR_UTIL_CONFIG_PATH} CXXFLAGS=${LOG4CXX_CPP_FLAGS} LDFLAGS=${LOG4CXX_LD_FLAGS} - PATCH_COMMAND patch -p1 < ${VENDOR_DIRECTORY}/log4cxx.patch - BUILD_COMMAND make - BUILD_IN_SOURCE 1 - INSTALL_COMMAND "" -) -add_dependencies(log4cxx aprutil) - -# Set some useful variables based on the source directory -externalproject_get_property(log4cxx SOURCE_DIR) -set(LOG4CXX_INCLUDE_DIRS "${SOURCE_DIR}/src/main/include") -set(LOG4CXX_SHARED_OBJECT_PATH "${SOURCE_DIR}/src/main/cpp/.libs/${LOG4CXX_SHARED_OBJECT_FILE}") -set(LOG4CXX_LIBRARIES "${LOG4CXX_SHARED_OBJECT_PATH}") - -set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "${SOURCE_DIR}/src/main/cpp/*.o;${SOURCE_DIR}/src/main/cpp/.libs") - -add_library(liblog4cxx SHARED IMPORTED) -set_target_properties(liblog4cxx - PROPERTIES PREFIX "" - IMPORTED_LOCATION "${LOG4CXX_SHARED_OBJECT_PATH}" -) -add_dependencies(liblog4cxx log4cxx) -install(FILES "${LOG4CXX_SHARED_OBJECT_PATH}" DESTINATION .) diff --git a/vendor/log4cxx.patch b/vendor/log4cxx.patch deleted file mode 100644 index e5ca00936f..0000000000 --- a/vendor/log4cxx.patch +++ /dev/null @@ -1,60 +0,0 @@ ---- a/src/main/include/log4cxx/helpers/simpledateformat.h -+++ b/src/main/include/log4cxx/helpers/simpledateformat.h -@@ -27,10 +27,9 @@ - - #include - #include -+#include - #include - --namespace std { class locale; } -- - namespace log4cxx - { - namespace helpers ---- a/src/main/cpp/stringhelper.cpp -+++ b/src/main/cpp/stringhelper.cpp -@@ -28,6 +28,7 @@ - #endif - #include - #include -+#include - #include - - ---- a/src/main/cpp/inputstreamreader.cpp -+++ b/src/main/cpp/inputstreamreader.cpp -@@ -20,7 +20,9 @@ - #include - #include - #include -+#include - -+using namespace std; - using namespace log4cxx; - using namespace log4cxx::helpers; - ---- a/src/main/cpp/socketoutputstream.cpp -+++ b/src/main/cpp/socketoutputstream.cpp -@@ -19,7 +19,9 @@ - #include - #include - #include -+#include - -+using namespace std; - using namespace log4cxx; - using namespace log4cxx::helpers; - ---- a/src/examples/cpp/console.cpp -+++ b/src/examples/cpp/console.cpp -@@ -22,7 +22,10 @@ - #include - #include - #include -+#include -+#include - -+using namespace std; - using namespace log4cxx; - using namespace log4cxx::helpers; diff --git a/vendor/re2.cmake b/vendor/re2.cmake deleted file mode 100644 index 892bee92a7..0000000000 --- a/vendor/re2.cmake +++ /dev/null @@ -1,41 +0,0 @@ -cmake_minimum_required(VERSION 2.8.12) -include(ExternalProject) - -set(RE2_SHARED_OBJECT_FILE "libre2.so.0") - -if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") - # Fix the install name to be on the reference path - set(RE2_LD_FLAGS "-Wl,-install_name,@rpath/${RE2_SHARED_OBJECT_FILE}") -elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") - set(RE2_CPP_FLAGS "-Wno-unused-local-typedefs") -elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel") -elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") -endif() - -# Add an external project to build re2 -externalproject_add( - re2 - PREFIX "${PROJECT_BINARY_DIR}" - URL "file://${VENDOR_DIRECTORY}/re2-20140304.tgz" - URL_MD5 "e82a6491efdf2bc928dc3779abcb3bc8" - CONFIGURE_COMMAND "" - BUILD_COMMAND make CPPFLAGS=${RE2_CPP_FLAGS} LDFLAGS=${RE2_LD_FLAGS} - BUILD_IN_SOURCE 1 - INSTALL_COMMAND "" -) - -# Set some useful variables based on the source directory -externalproject_get_property(re2 SOURCE_DIR) -set(RE2_INCLUDE_DIRS "${SOURCE_DIR}") -set(RE2_SHARED_OBJECT_PATH "${SOURCE_DIR}/obj/so/${RE2_SHARED_OBJECT_FILE}") -set(RE2_LIBRARIES "${RE2_SHARED_OBJECT_PATH}") - -set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "${SOURCE_DIR}/obj") - -add_library(libre2 SHARED IMPORTED) -set_target_properties(libre2 - PROPERTIES PREFIX "" - IMPORTED_LOCATION "${RE2_SHARED_OBJECT_PATH}" -) -add_dependencies(libre2 re2) -install(FILES "${RE2_SHARED_OBJECT_PATH}" DESTINATION .) From 73bc9ad3b22bf83398cfa69d7cd661ab026baf5a Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Tue, 29 Apr 2014 11:45:15 -0700 Subject: [PATCH 1813/3753] (CFACT-8) Add unit test framework. Adding gmock to vendor directory. Building a test executable for libfacter. Implementing, at first, unit test for string utility functions. Adding tests to travis CI. --- CMakeLists.txt | 1 + lib/CMakeLists.txt | 2 + lib/inc/facter/util/string.hpp | 2 +- lib/src/util/string.cc | 11 +++- lib/tests/CMakeLists.txt | 40 ++++++++++++++ lib/tests/util/string.cc | 97 +++++++++++++++++++++++++++++++++ scripts/travis_target.sh | 25 ++++++++- vendor/gmock-1.7.0.zip | Bin 0 -> 2167746 bytes vendor/gmock.cmake | 17 ++++++ 9 files changed, 191 insertions(+), 4 deletions(-) create mode 100644 lib/tests/CMakeLists.txt create mode 100644 lib/tests/util/string.cc create mode 100644 vendor/gmock-1.7.0.zip create mode 100644 vendor/gmock.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 5c548435a1..d57d8cba2a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,6 +34,7 @@ endif() # Include vendor libraries include(${VENDOR_DIRECTORY}/rapidjson.cmake) +include(${VENDOR_DIRECTORY}/gmock.cmake) if(APPLE) # Set the RPATH to OSX magic value @executable_path (where the loading executable is located) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index aee40c6066..1846efe19f 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -72,3 +72,5 @@ target_link_libraries(libfacter # Add a dependency on rapidjson add_dependencies(libfacter rapidjson) + +add_subdirectory(tests) diff --git a/lib/inc/facter/util/string.hpp b/lib/inc/facter/util/string.hpp index 970d9576e1..f4875b0d8e 100644 --- a/lib/inc/facter/util/string.hpp +++ b/lib/inc/facter/util/string.hpp @@ -94,7 +94,7 @@ namespace facter { namespace util { * @param delimiter The delimiter to use between strings. * @return Returns a string that is the join of the given string. */ - std::string join(std::vector const& strings, std::string const& delimiter); + std::string join(std::vector const& strings, std::string const& delimiter = " "); /** * Converts the given string to lowercase. diff --git a/lib/src/util/string.cc b/lib/src/util/string.cc index 7ea4e9da4c..a94aef97ed 100644 --- a/lib/src/util/string.cc +++ b/lib/src/util/string.cc @@ -12,11 +12,17 @@ namespace facter { namespace util { bool starts_with(string const& str, string const& prefix) { + if (prefix.size() == 0) { + return true; + } return prefix.size() <= str.size() && equal(prefix.begin(), prefix.end(), str.begin()); } bool ends_with(string const& str, string const& suffix) { + if (suffix.size() == 0) { + return true; + } return suffix.size() <= str.size() && equal(suffix.rbegin(), suffix.rend(), str.rbegin()); } @@ -81,8 +87,11 @@ namespace facter { namespace util { { ostringstream stream; + bool first = true; for (auto const& str : strings) { - if (stream.tellp() != 0) { + if (first) { + first = false; + } else { stream << delimiter; } stream << str; diff --git a/lib/tests/CMakeLists.txt b/lib/tests/CMakeLists.txt new file mode 100644 index 0000000000..4090dec70a --- /dev/null +++ b/lib/tests/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 2.8.12) + +# Set compiler-specific flags +if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra -Werror -Wno-unused-parameter -Wno-tautological-constant-out-of-range-compare") +elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra -Werror -Wno-unused-parameter -Wno-unused-local-typedefs") +elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel") +elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") +endif() + +# Set the common (platform-independent) sources +set(LIBFACTER_TESTS_COMMON_SOURCES + "${CMAKE_CURRENT_LIST_DIR}/util/string.cc" +) + +# Set the POSIX sources if on a POSIX platform +if (UNIX) + set(LIBFACTER_TESTS_POSIX_SOURCES + ) +endif() + +# Set the platform-specific sources +if ("${CMAKE_SYSTEM_NAME}" MATCHES "Darwin") + set(LIBFACTER_TESTS_PLATFORM_SOURCES + ) +elseif ("${CMAKE_SYSTEM_NAME}" MATCHES "Linux") + set(LIBFACTER_TESTS_PLATFORM_SOURCES + ) +endif() + +include_directories( + ../inc + ${LOG4CXX_INCLUDE_DIRS} + ${Boost_INCLUDE_DIRS} + ${GMOCK_INCLUDE_DIRS} +) + +add_executable(libfacter_test ${LIBFACTER_TESTS_COMMON_SOURCES} ${LIBFACTER_TESTS_PLATFORM_SOURCES} ${LIBFACTER_TESTS_POSIX_SOURCES}) +target_link_libraries(libfacter_test libfacter ${LOG4CXX_LIBRARIES} ${Boost_LIBRARIES} ${GMOCK_LIBRARIES}) diff --git a/lib/tests/util/string.cc b/lib/tests/util/string.cc new file mode 100644 index 0000000000..652481cf39 --- /dev/null +++ b/lib/tests/util/string.cc @@ -0,0 +1,97 @@ +#include +#include + +using namespace facter::util; +using testing::ElementsAre; + +TEST(facter_util_string, starts_with) { + ASSERT_FALSE(starts_with("hello", "world")); + ASSERT_TRUE(starts_with("hello world", "hello")); + ASSERT_FALSE(starts_with("", "hello")); + ASSERT_TRUE(starts_with("", "")); + ASSERT_TRUE(starts_with("hello", "")); +} + +TEST(facter_util_string, ends_with) { + ASSERT_TRUE(ends_with("hello world", "world")); + ASSERT_FALSE(ends_with("hello world", "hello")); + ASSERT_TRUE(ends_with("hello world", "")); + ASSERT_TRUE(ends_with("", "")); + ASSERT_TRUE(ends_with("hello", "")); +} + +TEST(facter_util_string, ltrim) { + ASSERT_EQ("", ltrim("")); + ASSERT_EQ("hello world", ltrim(" hello world")); + ASSERT_EQ("hello world ", ltrim(" hello world ")); + ASSERT_EQ("hello world", ltrim("hello world")); + ASSERT_EQ("hello world", ltrim("???hello world", { '?' })); + ASSERT_EQ("hello world?!!", ltrim("?!?hello world?!!", { '?', '!' })); + ASSERT_EQ(" hello world ", ltrim(" hello world ", { })); +} + +TEST(facter_util_string, rtrim) { + ASSERT_EQ("", rtrim("")); + ASSERT_EQ("hello world", rtrim("hello world ")); + ASSERT_EQ(" hello world", rtrim(" hello world ")); + ASSERT_EQ("hello world", rtrim("hello world")); + ASSERT_EQ("hello world", rtrim("hello world???", { '?' })); + ASSERT_EQ("?!?hello world", rtrim("?!?hello world?!!", { '?', '!' })); + ASSERT_EQ(" hello world ", rtrim(" hello world ", { })); +} + +TEST(facter_util_string, trim) { + ASSERT_EQ("", trim("")); + ASSERT_EQ("hello world", trim(" hello world ")); + ASSERT_EQ("hello world", trim(" hello world")); + ASSERT_EQ("hello world", trim("hello world ")); + ASSERT_EQ("hello world", trim("hello world")); + ASSERT_EQ("hello world", trim("hello world???", { '?' })); + ASSERT_EQ("hello world", trim("?!?hello world?!!", { '?', '!' })); + ASSERT_EQ(" hello world ", trim(" hello world ", { })); +} + +TEST(facter_util_string, tokenize) { + ASSERT_THAT(tokenize("hello world"), ElementsAre("hello", "world")); + ASSERT_THAT(tokenize(" hello world ! "), ElementsAre("hello", "world", "!")); + ASSERT_EQ(0u, tokenize("").size()); +} + +TEST(facter_util_string, split) { + ASSERT_THAT(split("hello world"), ElementsAre("hello", "world")); + ASSERT_THAT(split(" hello world ! "), ElementsAre("hello", "world", "!")); + ASSERT_THAT(split("xhelloxworldx!x", 'x'), ElementsAre("hello", "world", "!")); + ASSERT_THAT(split("xhello x worldx!x", 'x'), ElementsAre("hello ", " world", "!")); + ASSERT_EQ(0u, split("").size()); +} + +TEST(facter_util_string, join) { + ASSERT_EQ("hello world", join({"hello", "world"})); + ASSERT_EQ("hello world", join({"hello", "world"}, " ")); + ASSERT_EQ("foo!!!bar!!!baz", join({"foo", "bar", "baz"}, "!!!")); + ASSERT_EQ("", join({""}, "!!")); + ASSERT_EQ("!!", join({"", ""}, "!!")); + ASSERT_EQ("", join({}, " ")); +} + +TEST(facter_util_string, to_lower) { + ASSERT_EQ("hello world!", to_lower("Hello World!")); + ASSERT_EQ("hello world!", to_lower("HELLO WORLD!")); + ASSERT_EQ("hello world!", to_lower("hello world!")); + ASSERT_EQ("", to_lower("")); +} + +TEST(facter_util_string, to_upper) { + ASSERT_EQ("HELLO WORLD!", to_upper("hELLO wORLD!")); + ASSERT_EQ("HELLO WORLD!", to_upper("hello world!")); + ASSERT_EQ("HELLO WORLD!", to_upper("HELLO WORLD!")); + ASSERT_EQ("", to_upper("")); +} + +TEST(facter_util_string, ci_string) { + ASSERT_EQ("hello world!", ci_string("HELLO WORLD!")); + ASSERT_EQ("HELLO world!", ci_string("hello WORLD!")); + ASSERT_EQ("hello WORLD!", ci_string("HELLO world!")); + ASSERT_NE("not hello WORLD!", ci_string("hello WORLD!")); + ASSERT_EQ("", ci_string("")); +} diff --git a/scripts/travis_target.sh b/scripts/travis_target.sh index 04c4abd518..4de8c74ca5 100755 --- a/scripts/travis_target.sh +++ b/scripts/travis_target.sh @@ -12,13 +12,34 @@ function travis_make() { mkdir $1 && cd $1 - # cmake + # Generate build files [ $1 == "debug" ] && export CMAKE_VARS=" -DCMAKE_BUILD_TYPE=Debug " $HOME/bin/cmake $CMAKE_VARS .. + if [ $? -ne 0 ]; then + echo "cmake failed." + exit 1 + fi - # make + # Build cfacter [ $1 == "cpplint" ] && export MAKE_TARGET=" cpplint " make $MAKE_TARGET + if [ $? -ne 0 ]; then + echo "build failed." + exit 1 + fi + + # Run library tests if not doing cpplint + if [ $1 != "cpplint" ]; then + lib/tests/libfacter_test + local lib_test_status=$? + + # TODO: run executable tests + + if [ $lib_test_status -ne 0 ]; then + echo "tests reported an error." + exit 1 + fi + fi } case $TRAVIS_TARGET in diff --git a/vendor/gmock-1.7.0.zip b/vendor/gmock-1.7.0.zip new file mode 100644 index 0000000000000000000000000000000000000000..2f0edb9b7ae0b925c8692ac80a1c23e99f039a56 GIT binary patch literal 2167746 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

      1ZxOR^pCVO#~CHRXKkb6{@Z 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

      sHA6p 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%0AhIFT z)|ci~8epjMoRZ|R*?rCCkj&xf^hh&dk=;-1TZYW3YMEosQ?s9_1nA-M>vl;_Pi5z@ z|NObPd@xQu;IKrLeLx|MzRTQFM-WXt!w=ot@efroG_0l+#H5LtltXf2n=qax?NhXt}IuSIn%O>d~vb^TUw^Zl7pe0hA(}oHmxttk$Z5ad%8t| z%6T!#T6ra1+KF1@C_y(M6gJ6UO^6_~(lTc@Nc>)~T;W?jz63O8BL&=;9*H7cwF7&o z8Q;AjSDQK?zzoH>v_=C0&ReC*IB$9&N8)nNPlg4^)<|Kx0hw!YK&o06jS0;SR9#6T3mIdHJ7h`C!MX!VM-yd& zZ5*mll4@iFOVfa9KZhZr^U}ZaB;yC)3KE+amn)>pVidR;Vy;M}K(Mn>ZRpxL!%eYbA5`p~&6p@FhkdPvAbrlSxOg5$vvYk_2 zN)=lXqoLaL&3(FB6$92yZ3f@$(<=5D70rlMkpUgIP`CIf`mH1=F_p~Ef{xGR0!(pv zQ_L6ef2oXEb@~xmL{zeJPuKa5rGla?P64bD-CzZP^D5|-DBuz(S$ixBMmmMqVz%UkwQYr!)0ngemQpVJ#3Ss**rvCRHNgx%WtyM-Ua-=2q3;J4W&( z_~!_NiNkUwN8$EN=P@_*sw+6hsjth+<=n4PNqNN~=u}XGj`#}FGQJLF9G}y~L9edD z6=O+`qHA>LTN^w_n7jv`+J||env~1<3|=yM7#_)zzlCW1eRpKYsDTW3Xd~!YTc$Z~Vde=~a`)h2wD%!)b>G zC?Q5yKbcV+50r}{RnSb1G)kDWJTr_2-@Q9-Hhx>Bu-gy|r78nZX=M;n0qTGc=K%gW zschAYU9TU)u`0f)C?SGA9WJ^I%?KjkFa(Yb)M<%N168|cz8|X;*hGpYzK0zPju2&C zRY6;ee5o$T5XG*;hYUMZhqAE6L+i#Y?hr~y_QT6?s4l3aHY0NlAsCR8<}yn3&Pz3A zNnFYfk(Nivj`Hn*B_#Z^ zgfI+aG{7O0mOuUL28F_T9E~X6rnFr8bW_GhFlC?tfY>W7|NYa=Kk$*%+C#>^3WlL% zpB(`k0H8k~Ri1pNR+o4+k)|s#Gx!qAo^Y{Deb`eaIJ=QTPHNYaEt4a>(S9M5H#);h z8$e*H)QR-qVAvd5P)Tjt)&f7lLKh4I6s>r`Mglxn6Nxbp6LnCkHR9gh-u52uJ%l;K zqLqn4qfcq``xH_KyDL_sx`H!N1gH9HS(X0feesV!IFJb|r-G0gbOKq^4?jI5)F0j` z@Og`I$9s4w?FJ7?@#v}BOao*RtP4fr+aj~)b?AR}I5V(R24k_4Px~ChjWEzvaD{TLZ z-M%)l{r~3-0GBZpjo_nXkNxW;;Bcyms+g1$YtE7*Q-S&aWx$9^IW2%sH#UGzB23t7 ze503XqesHW?{va0ZYXF*!+n#4T9JHLTZ4#zRLtoaLZ}c}I`f_f$kL!$bA5A(XjM{9 z8LB&!;S8W)cmqb0g2;^n_Rs*RI7cp#-p%JthQT*a(pxqkGTbc_o~!`gD|Ber;yv*$ z!t)EPFg(IbnONT?IvXj|zHgOkSCmi+1sUhn-J>9;Odi((9{71jrfWvolYm5(u;Dk5va+gB$buLE>;nTS0pew2_&V4v); z-|rnL`PSi2QzNH0c4D~~#3XkGc8$xd=l->?QMzqXa6qM3d39v zsoPWm<_bcd;Dapt-pCTfw-;F!@^Qcz+ntkHaY(IeS)V!%H?L|5#||BaADqs4`jO+p z!6@J>{N#f}w#Is1OmQRe*q%m$Gwk7Bxorkz1rdL>L1oIKl)rTLC4JDD7!_Gm07D0S z{w=(x;ZeCMZ1I5(o^;^@VW>MFh1@X}Ylwv*H*n1>;-6fvLf-We1unxB)a}>cit*zq zV4eBK)4GnVtB4e(raZ0^K&ejpfAXj+-2G>cf8S;xG(_g7fi>&)4IsG!MK6U-nDea5 zJekxAuC2w5k^pBwn7`8(!&3$s*T{kaP);?nQaN)Sh&_)x>1%pNQF_~3#a!{gu_~Hk zG%$fROHv0+S3Oh3z#Qxz@OhUn7jgp!-!?~B(T$*=m=MR!_WQ=s!SVa%l%$r&-BDm$ z9jWT5tU6ipwDxkSu8l*~pn&RbBcdq+99_;$azhRV#I%sR95f?Yijbi*8?EcxB5tUY zVOycNXcQ##)!FErBY2f1-|U_gV5jpQX}QSmjSEoW*bN{pli(676Lyjc3$}rzhr`H& zpw}apyUO|iS^#MkWQh70@KC_Ks!WPVgpRYXyeZyT~ukRI6DVnfIbqy=s2!z;MA^IgVbhC^z-IC z_PiDI)O+F8HZ1!k{9_}9c3hf{i)16e&$eG?8s;-8&Mh)R~zN-1JIutr=_$ZLD*&tVUfRB_z6?hht? z6{nWsNGzm;FWkQC|6QtWRBU<^;uM*vAp*t>aS&uIIs{3sg3dk;mx3$fRM8 zGQ(#_Wo@G|06JqhYwRZ%ocx}+7hy1KYb}@slRxblI15w$&tO6qb zIi5y_df}^Yt9Zm?j~s;_!Wjd%XmJ&hmrX6P947ly&ur7ihqDObl#nn&!Oxt-4r)%e zXrliHHpj^jIB0EtLEo3O0DHp4~2ZT4M8z zV!T#27v|#?-z8_)lxr$ZD4&KuIY?z~On7n*c5bMp)vLCnE)!)?$?r!*$QwtD5vxQDBwD#d;mG3mxB7 zi(@=;mHbJhaWwv-+0H!dgsP`)(NSBl}yO`2yY2;Pn(}{n$h4%*X5qKcD+&(pqm|uCXp%rfcL9#0RI~feKeLO zor~ZyP>~jN!9T?PMl^O{G74Gcn6i#a9mUqmixBuH?!Z43;zcK8@~vUnC_-#Z(y&yuHZv|OyVovVoQ6(*m%uT-!jZ4V z8H>5axDR0gP|YZUNK#hGHQ*MUqAEI&j~9}d5;5>n*hnJQ3WSF?fvBE*j%mC0@hTo# zWC3O}>cMN3vtii*wTw{sDH&Ck#c{7gx31!#huRIg%)`M4ARl8|68crVgD6N|_Wg^p zqD_M@jQJ7>;CCc@nQ}*KPSW>YUk5`Xb*Zd!fwQTFCoschTtvbe&yYg!xli(LcXnqx=YG=Reb!zrcz7%*-!fyHUXv$JBqy zCJ9JZ2}PCmg`5^OR{CjYOG+PW74PE)bq0TcrsR3q;iMXUwjneIB?-U?;YF}`eA)N4k&-rEhboJxsgB5g3Kbk<>}=+1~PH5DacW#xGxn^e?Kc z%a>8G4063dz?B-he;5eX%qUK9qJ(Q<16e#7JP3!f$}JhuSsO?(!odpb1RR#fVt3#W4evbheVqV}jh&#(h62*ubS`{z&z5B?TVp_t;R_@qYU@r z91(vD)gv}@L(;M8odm26V9Tqp^8us8VL=JVfW^C`1Bdnm$i3ebBREaOlB1m)Y$XT; z11oUOTk$fJyC!p3RonX7I;#ylWwU%(lMllgzuS=SfF-8*%-Y9iM%DgyMr`d*U4)k` z^-#1sMS5KPDn$~k{~=q+^oefF!YA$0%(+_lt#J7+ZOU{5dwv*i{qN(YvBka$Mj$4 zu;Xmq=kTOpfH2;15b;1K%qu3JUix?F~a zg9Y#=G+XnX{5d+HHe%1z1uPuOKjL@EL1wVE&_XX(++|%I4{tZXwUTZ^6{o=iksJYd z6`;#*#C`)QXvD{Q>X1el@*-e#e5&8o3FN?W#YIvA%lyM&Nd{Qw34{b&OFv*IJ=XM; zX><~<0aY+-h)RNyl4GdwBzz@ujsJbphJ9;Dc9u?u2I0PcYQI-URoaH-(o|wa9J%{rworc`E2dc zHYnTlR6zqBnjKW}pr`{fNu@s#I~MUi^^A}tKpi8-_23FIu8x6KnHSOXKD;qab=7as10L ziWm!y<1U#VVHbi8schis)B+I}i$JSD{y1%0i#4hoGm)~>cxdKMIQl>va0xxk6UldY z6y38tT%FDg^x1JluP?Eg*Th6Ne}DV3v3G=VH>wPwq)yn?E^g#FsxpzR^{_Y}mps0@ zvFp;&vxo5K^+45hVw3}3v&qyQdf=yXkv5YC5#!q zn)PhwIkr#Ov3;^|Y%d(!3&-}tvAu9?|5Y5@lltmK>zUcp^`nn;U2yk)Z||FnxV6RhzS!Ou+xudBUu^Fbw{`$W^*WZs z@Ja#xV_$pMcB|#&(vOGBx*jH(lSp<@)CYNav1jUR*?fvt1`P?e$YC7aNY09YFOjMO zAa*mUjm%>^O?V#y!O_1F;mb`D`@?Go3p40*OpESc`#0)lBU&Ty@&p>FFucd)pzv1t zUb9b0I28cO*hJ1;3e18%v~$zJ1l@hicmZRCe?M>#)v8xVk1b7wCNA1q$bX0#JMfw@ zKTo{UQ}67C(oqbOA;JR5yj50!Xo4seiSK}Nh=wW~j79B58Z_C~Rn8JaSzrJFn*=(k zA)Bn&0x3ET1{yX`8!eJ9CV?S?B3A?m`e49Hj!K<~2alfYK{Ymvp)rep%UDJ9Wis>f!SA;o%_P!8r9ZI}0Z zX#K>y{_a3<<*{b_e3!nUlJT4njYXaehH*$yMbU@8mn=&Y;TmeZ%$8NYU7>LbE_Uf^6 zi>UN5!60GRm%hWY>ZS)p?J*0Em{X^wt&ID21+}ad7bnhV<8xi+5uD3FGU|*w_@B+| zobd%S#+(OO$IR{8WPZtX&jGV2N?>)H-o?E@;8p$|H1wL@lM7g&T}3&&JC?z@>06)y zh5O3AazvtS4H=g(CKeFA6^?Z(wUWA#W*X0STs>?Y{jGk`e%Csg@wn=KXq`ioVYeTi zRWIzbDc_UBXZd-j3o{?TQ+eu<8&r0#em{q&oE*MxHfFrj+1PKt1@-K=cIn(AZ2NLF z#JYzj@pY)PS~4CWlrS1y`=BLg$Cd|}C2L4Gimd_>>^>O=mzaZ6q>4_VYa)SA5@i6A zL{#Cbe8doMf-qIdYTX=xYCzz3OYpkveRQ^s=IG3N$@^dPnz|^ zSDen-9%*Y=WwEg4*J39te+60{s8$h|E+VA+sx*u;XYiiPK3AT3o!;YrVfDA+LtTGr z5PTZ8y%*G6mzG+?3l#%b{!uavIxgep#U5Dis)|k8M0=F@))x%sqYuYooSmx-t^2h1 z5t^c4*d`hwIzt&!l9!J(uh&vH#6pN%Qk*bP>>HNgLI5fuiVAla&n8;b`+mCS@P0x*AgzZux&vIFQUOEEt(e z4%?OTn46T46%U@NvS+FY#Qxu~N-s+BgIvic{QDMv97dfFpm}yalpf^ENNhQ-dsqPQ ze23ayU*N5t($(rNuZ^GRU6~unLvE>?nB`*@l)zK#=`cX83uD14u3xz5I(QfdDh}fR z-`<~hwT*2FPAnm`&%rR&$XmzTf-^u$ycmLCv313{6Qs2ycbnA7G7#h9(*Kx7&WZ~ z6TT_$d0PKD6}Hk)8E(3Gdk?b3T`xW6QjvAxrJ7UD@i@^}Wwys!cFzVe%&|LUJymt_ z(05wxk(I~?hE8}tW<&xE@9j0Ce2uh(WTEzYj-0zx9KS>D?ybuCd9+k zjA-Kqb>lQX=ggdzbf^xwL_CS&gFaOWs{C@)HE&cIHEeZVYTh;MtVm|SwKko~z!Vlf z3eVAlp@xTnD**mK>vQ}#P8k^n}&q(!Ac;c}CNga85!4vK*j6BcwpC7@u z;&GtB=a^)~c}BApyjL0KFx5A08+T{8Q)ottRkm^^ZH?5pgm2UW%&$-Q)U4H${FhJ6 zr`hykZ{E-h3VnrxjA~g+_4nKL`ui={ zpF_Bdx;`Gn81$5-$wY5q5oK#;{5*&RT>6wyPT`e>_w+?Wy_gdtrv>(#ZmsZ;QC0y_ zt7&8q25E$K4aH9x7&aa(&#D%7HNUPGk|)A1l{8?+K*x1{ogT>NlvpMEojq2YHQJmJ zq)E&pJ>xq9!$Dm(-%*Yp1tM|?GSFdv6MR*8@NJ5oew(_d-`t+aim0#S$=E~IN<<{8 z+2#NwwJ@Qipr|fjs#{A)s-Xt=E=k^ogSX+oXzc5?aBwm=CuL_&F-s7-&>AZjYFbto znbyI2G0Xx<#TjpIgf^m@O2MXqP}FaCUkuE(+GKCvpI2#Ps_Xaa`n|e-udd&#>v!_5 zb1y1_+W&VN_OI}A@^jc{?>al;f1dpH$7O+;v7!Vy?~A&DLRLNM$9O{v@*-yXD*nXs!@rl$rS!&pGmY0u|{MddpXLl%z1d#b&-Q2F7^pZoYWTZx4GyDi_U<}!B?SeoNy?8w zjZ{!`C`lT% z<|r!GB%PtiCIO-h0mghn83PWs=XG_$u3DH_>;~-5z>=19tLF7NQllZMfv+$~pzp^N z13W;x+Q5o^rHj3o&pZ!;d=$7#DM_X#5>WJ;%ttrZ^C}*BP1Rx_>fAYlxj@zEaHoOS zw!`k)nl!a2hamPoz0l~*qqYkDd1i#JCcUdQ38wOGuYT6mKqQT^J#;|=(q{CAAOCeUd9(uYSHLONf;z8pc|5;q0!Dr2*hBYLbQ2}fhs)p zNlfXUxTdZ0NfT`*9d>kD8g;3p;444D%x9{`u@u13&(*mMaeF|o>FQT?bt7#LO#Glo z3C0>JVxM9{qfnTaL+YuBzJ)GPg8^R}xB5)lopJ&Nm5ew9Ivtt`rk+J0Zj_w>)ogqW zjF{7o#HlVvjET}{43ckzyagye1BYj>omHAJ6>mVpOZwKN5eP>!D49ZKA44f_UOm)< zm-}lj$fOdD7;qCDY9xANj}(Xfl8FYbuPrwjIq&QCor&dGBX1pF8#-kr14D``zHeFS z5k&latZ0;!lvIB%J!U1Jz-ZG;E~s4V;cT@qNgUa&qxlgP8TgGV;8ycDkN?k-b^y}E zj*~oaE-@M46z!ML4bgF16J%?5NMKrjlI*Aa|(4;+#Ov ze`)+Lz#ev=E!hOc42M%zh?%YfD8 zW9Hvyx-`_0`4&g~HYPtuXf$SJP%|cbZ%8OdZquVa|D>^FCunB>LN1{c<&_(D|BBrw z$AG%Ez|3DR-hB&| zbEImsM>?gK3veZ*bR#bm&)6BY*)F_+#MCPBk#!n*(X*0}=WNJbjQ0o+BHD`cK{V_f zh3W9M?@X`P941SdKv7)P1~ppkf)KKFk?EeWu)` z*Gm~ite8r+?pT+F2}r_kg4KlyJ{g5)NI_k95#`3)%wKwiXpwU1o~M)29X5e$P^4}yTqy}?x=|XFt{H&< z7?YgZ>T@bWCUa8hQw^d%8RQ|FL@8w{)=FhC!l22~bf^~BkxGS&+zM^f#nc45%{m8P z;5YuSoD~Q0@vrQba%APE{ zo~Q(}Q-~LJ1|cwcoA@$}b}Caj#+4|=IyQ*38N?|eLkVfeSd-_7urtAjt^U%-oLmzA zf%ZwMdCGDD%Gm9U$Yu^)MsUlSq|wW5fR;Dn9SVcRJYgB5-z5o31qlqlt(-PW-;TKA9OewqI zsM0|^4Ac0iBZuMjCU9}OcETU+K?pd%uIyWSNpRv6Plj~_0T3IR%%P?uMOYW9gxWEy zIq2nx@l)2v3NRAd$EvnBq5W)lb{L_tPvTw zz%Sc~Fy;$;mCsAAK%KqTjjcs#^Fu7iE!q2A3Atv^(YZNDykGJl>@m0l;Px<`MV|fEb~a&;I_oaoK??2>U=hAzVe0cnq0Z!4R4_(CUSd4I{aUX{|trB%x!A z)jDdklk_)LFV;ohBq0PDh02ca$qZc&x+CKJqy9ffAuxd$=B*ccbY7Rxm8_(`1YH?2 zjQ307AeB68c-2RGFtEM>8?6}#xdw+wVnL!f>Lsa@>OF94$>W^>x6_zlHVOH)M}Js? z6VQknEE3U8<&2Ala<}Cs;kF7B@U8j&5a6THH04~E4CHxZxv?_882pccHtNQM|5aj% zr2QPxO&!+7q!`}-=983K#F)ihkV6#8P^wfZ)BO7Nj8u&jJFX?De~uLO=Ag;PtOENM z$nNpg#d|4gEOe4AnzSnV%QlQdE{5nQ6ZEy|Dh z7hrmDj*+lXrW^I*!${IzaxtCXmn0l9M1<q8JT{ z4j!}MO0=nNA47@7bf!e;QNM@x@2X;pK|zLbL5^w-S{%`xM4R&PrYsSe zG7@$=#}#$6>6pRCs%*HaWV0LatzXnnM+4)1^4Nm@Sn(hX70~QaFPgoSma-r_l;ewJ z0T3-ku_DCu1jI|>f}}ngcVYQzj!;9zm8!5*3fUzgE~GNo@lcf;a*$(ANOaCuapR|=@9{-O)J$xK=AYlkmj?q ztKi8~zXg>-!{`bPzg2RJzFaSA#_O^9$S(U)Bwn~C&5z;NO}uak-UQ1+`P)=i<7<32 zIrT9W}E=YirpXPyEtc`chRA6SIS_f3x2M^}sN% zKmLe84*KH{>dFp@;J(*`1rrCP##n!>pqdZgyfG`F0Kl&^wb%l1 z7Au_0>X8PAu*_f7pWmzyBQ=0P2H>!NDrv7!`~00LQv8+EGQ=CTe?Y9Vf z&haeSad7IC;4?ZU-{?kB-lAy(#CEj{EX&}vlg?Hi=J$-!LtV*@wF0C4hWKywxRthH zmVhcL$R3n}LaJJ)Yr*WMiUDA^z{~K8BcEsMs`>1wKR_QJLMODN%{DZR5zv0czWqem z8E~hbC{2FO&?Cn(Zm~_Oe}|NTvSAZlKy#ToTjzDt9{o4S1+yP)Y;BurGUu*c9GXj= zq*xz414KA_Ozyx9msp-lh@`z`X5^VsQ?81~s^w#IY7uPMk3W@YfawNv9>nG2xMF%6bmi5CNjQt&UM4Fx&#Bk`xV)6^>vJ;zvx?; zcxbL*=rm5yi|i{H$F%|%&8P@u_{V!qF^P*ckFREK`MRaAd%@#3#_QjE^MV9r=4rN| zid;%?K$op6>!1VomPgbffsJcHm!HHPmq9Q00#4CVc5xmt`BCszQrB z^ftK4^hTLbhB-QFpC=@d>D<_3lZ`JZVRp=%G}?Hx#iH>l$4;sMLIi4fDdsX&P%X78 z->}B0yj(Ko4Z8M}i)y(<&+BLGkF6je&RD9Oz+=}LyaAoD`IO_)tw~g7EUK+@u_*>N zxuo(p@#pwKMsykhJ9bCP4wJa?b&0SWCi+{3am>Mc&W*3M!y-c?I|fEJgEJ9@R1Mm; zpS|;dO6!UhW7l*b&Z7iW8pRYi;8t%Ik;lVmcnKP-Ye&tIfv;4WleoO|0m%5)t70Pt2B={G_5!+%q#Zd` z_yiv0*<0JW#ZXbW7SgC&Rl3JaC-NsJa4c13f8Ng8;VAA6=h@|6fJ$e}#)h7y-XOWtUhz$(pGL&dJ@rCDmIW#&SA4f-0e%`7dXCrdl|Dy7L z;dn9fnCHvUA9$?vLXh(gOQz0}uGfy{9Her$ipdGNM1bfcD4_Ptw{n!3;cM~ADQ~i` z!G8p^q!oZcIMyf;@Ic$)UO^hkV7%X*8Ve$NyM(Jf{*E^PhVCEtA76yn$D@VIK}e`cuh1(NbDtHy1y3`0oILg0F5v#|EWw%0_ zAXyc<;V?u!UL0mb^S1IbGj(81nlGF%;i~_5DGRd|3vkVOiVADZ)7Rhk>&YAEaHl=q zRbRN#ANT*|m-=2nhmD4Vt7)+*%7`9!*{TOy~W5sFa9r7or26XnE4iDUHw(m|n z=wFDrJfWdcXlgTo*ywpqLvJQDnutQGy@N$oc|3<5Zi<|RarYoa#`9g!uKQfkQC`_@ zqT=dG3Huw9Vwm(r6leY~x);%Wa;TNoJn=?X7D}(q6gi zZ|sD}fpPsdkC;%QA>Kz}(pp9k4yaY1qbhzZRYCo<)qsWvTp!JLGHZcoyRXgyc& zNmeV-rh5({?aym71>I10<0Ip?a}28Yi0Z1dHar|%7?u6U%G5)wW25grO#6@3YmK>%4RPG= zUEL3xggp-P_}R7RR>#Koe}48i&u=u@Is$lL6Sn_&apT$75y(Sc0sD`ax3U~{1oYD^ ziT%goLS}(xjE})}R;f-P^-@i_LSFgQ2%$MClz^Xy@xU+Lm1pD#!?5rP7t+exu=e)MAberweK0LrGpd?L|r6BbU?}uVxVgsXUA$|1w0r=WFEDd%Tufbp6S# z-e1k+6#0DfrA%|~D%<|?oc6YCnaq!tl#UXnJ0;4Y81$myFq@x=EG_6Ortc67yelY$ z#&jOsxqx`GFQVIB%F(;3>;j_%#i!&Q$;=fs1L?*oU69~_#f6VlVl+i6qub2yn1$)& zEL30A&PVC#oH@McSP&{FDisSN-*VhWBM(D*URzmwu`tK?zOWj}0A%>yl^U1!D-7_n zhJCd3=!}QJ8b_mAQ5>8>iM;*DDMjyqgnNb@vofR715j9Q%2(swbPNJOxyKwP-7A$0 zk(~@=#l6w;Qv}KbsJB-ti%EU#SokJf-#=&*oq*Ai<5?fhc0srgIW3BZ2csgIO-|)f zEV=9}02VqcJhNh#@MG;gROhwU-=U|60t0&QMa*F{RCX(nR1bWIcV;IR4Y;}`nk~h3 zBuLGUxGsPAMHf7YMz;}2_G4IOiix>JBvx1aw zs2#H(6R5{yjSs#N2)g5~01ZV9v^1h|-p=p=`a5S`tEVrdc))Y^M^OL|WN(Z+lSueWR?a3$l1M;XnN9w)HWgJ$~)Fo#}vV%)BA z%`th5o}6*1&f4xXh~;%=E@)#WWHq&Ud85(LmiYoG!5`Q)oJbp(7qAl`G8+i?0JncC zA=jEYtFz(es^#4$m#HHvI+wMS`jFm2F}_qRhx}H>$Kp}x8^aq8Oy5>bGd8byq=*U2 zMgOz&zeP#Cq>G&*@#Zx)&S(9}-Bihi6dor6wNg9p`HfGT+<-%EZc%UahFejpA zHit1D|7R{ELQOZ%ax*M5x5*2s_JsgBOt|>Ad%6wNemUj#%(wx4uWfVh)C;#yxe}F~ zed&3ZopGr}F5b^Fn&+5Y{}X+RT*Bf#@ZWqaYpMzX6so6-1gL-lwoIY=H@;8+F)`wI z1$<1gB~v~iTQ;DO(M_S+PZRO9LAU(j07E%cPzfei1SRfIjL3N2Sj+@tjHK+h@r2O% zQS$k#&`Q_Vk*8E)^-5LXZ7=$*!iznT}~y#5tf zKir<#gpvev;u~xdDszTFRp~pCm_6;x$Nm$JKStb*LWNQ^X8hz6h*j}dwMIK?iTJU; z+pwidwpr{@8Wb-1fHpMtQ{nIr@qD!`f{1mg6t?ko3o8BMdP9e`$htAXo`)QG{BEZmt&E0Z!L?`BM z>M|R62`$$3#hW?{cJq@+8m-rcc82L0(a*S?M;;e^wLkJTKNt$;wJ9k0F~w$>)+rfb%(whjlHgXM2MkH2X*JzrB(KPGWAglPe)UV(i5r z@=bi5FEq$+O^uK*G9%7{Ou1<|`?9J)p$JytNas{~WxC^(jA)tAR=jRPD9y9OaBviM zqd^kXFai>NvV+Z}8@-P(YD_dZ2eUO|YaSf*-~jS` zlNQ!&eG&Imeay{zGC^FcN>x#Ts$f5gl(0`998zd>fnX;BAja96)XB5b{j7hT89xQj zN=u6lo{F&$S6{&yMCqtEoOTD~{AG5b__T4!;K=l{4yf}z+Ptb}G{a6tL^L=8)L-BHvKs{&#UUBxRL zJRc1(&!?0ImsWJwI*3!1`xs!KVWxYXl9X^xeU3?ZdI=|+(#WTW(6Hvin_s+gPe{Di zuV9|sLh?W2e7hGRWnj}|#rzGDaL?ommYw7__HJrvm5bq7SyyA?5$Bjj_^_w^n>dn! zK0z#z-Z9ez#S7{|M3_BW-!Nt-F~8o#Oq2FP&`~qc%=|Wc=;Onc7!+{MQP<$j%OyI; z67@kEouNc?B-s%ZdMeBzq%^(zvFZtT?pgG$NMA&`7oK{ElmUa6ISp!VW(U+E$gQJ`mBi~*?wKd<=4nSC zGmiX@=26 zJKs*~4P4{KHTttbzvtJ5A_%|9HdMGX7OuGhBAfkhZ-PhsMuZ0Hsewr(hW@@;9TXAs z2?EjTtzJ~?q{_p;bqfVf9oAQLPoxjAYC6B3Z|iSL+7-(EI+Yi!z5?^dAGu|JWWB8E z1!2&d2Ncbcy91@Ne1CR#NLHRc8TCC~^`ZrG4`zu&_iP{`=o*Y(lc0Sl{?Z@!Qm+_^n3UqDP>uEa!vx z0tg0Df#X3O;cb9N{)E-V2B7ELAU1B;44L<=%Z@~%Si^qWV+ua%$HNI(+Y5$LL|5tawxWryh@6K~ zQ^r|TGc9pgbz)4O69pp{OgWjj%jrwBBf-PN9X}i9<=+iQq~e9|S%Q6f1-$Yr=XG+E z+~FQ#l01{vRP#11FCW6BDzb1i(_I1ka{K7m6@$FNyb7~MGphD|CeCPY}yim*!~ zom)qtuq~YzYUvPGP`|zit(&Ao=187=1;>a)vsC2-A5f`m#KOx&R9ev(UzO&wqiZ}10?_}=)oldf{kC>ok6G>Y%`HvJ?@YFZ25N^_6c}(+ zGoFj8>e>nZ;PI;n51!i}fA}Dd!5Y^tlNq9eIYhVTeBcX>7Mc>X%V_6SreRr20F2Zt zJ(C3XA-qtYV`Lr7SSe3N01^Nx%1L4Yf-(?Hkr(Z$&gOHK|4~=^z3ij=xJql!_U+9N z&CUPp)Utc7m5I!YISZ5VTCL6L@=svi0akWAJD0M`%rzp*6grz}O1#FH-i-v^Ag_12 zih(oUQz&wzY6kPj`0QI%u!CR|GZ07JEtzOL**Lx_A?T-4!* zdKI`Yg*-$c42j86h^)$F9q&Q}sX4XYnF{a*&~HNw_#yvcXo5&9^oIk9i3Go9BB==i z1+!iBI}$T%$y_>}jA%^U(3mN>teyOBBLnQo~Q*XX^>)o|>5J0)yvsm!TdV=FIfBW{+`2N9i zs2dp;lNCj21RFh$X|!_{S~g$M#t4u$4ih~>h(Mxh3-gF}UGk+2s_6ORgh*@bGJK-_$!KsCu?WxU zYAJHYyb*^?s=h>bUi@+x9(}26g;rBzNe3*|?(s?qEp6m3a+T1>;gB1{5P=F-Z*ivne(Kf6fAhx^oTMyP5o2RZG|0opU)dwK9t4|+*u#ScQ zWZX(HVzw-|5neg^j>L3!@l=&GN95yrbgFJi?Lff8E3@3%GWJA2QqYk8N!K9}5DYU_ z_0EDCT|FL4{S|dT*$>)t6f%niwG^OJ7Tsr+mSDPiHLD{&xvaLd5Zwn^>v@-`FNY{Y zmw1&6q~Gj?nt*;{k3Zyx&U}R&6Pr|u0C9o3r3FAY3Jw;JIT}h#Nyz3!^uvjf`(twY zy5bGI3|I^iYf-57mQO&=9UyxTos5R_$?-hfl{5n{sfH;P@i?dn7U~NR3YyYq&%sed zt9H%oiMlS+cxS`%;d7&B{`OT=!VxoLq^+qZ(rbvOv6dX}k*dJ`4WdTZE{XpZ`n zXclx0~ZQ`YG>&(GMQ9jtK|AbmHp;)^dy~Y`lOqK+>T>5w~iG2g$|2-bcwX*_83L!V=9BD zt6*~aV651djQZMf1wTvA!@hQ4Fw18`_|+(_3s0e=>y{$zXe;A|Qde2NH7#*`ryi}* z!5~Y_%&1=D;oi)*b$>{i3{Yo7#b&6>E+WxIr2`H=mh?f|O+KdjJ1pI&jxtBrN#ZS| zuJG{5ixhu37jg;&zI_)Wg}E{ku=S7ve!O>fM)6V|tgY!wV*9!{dx7Wax@H>c3C;z| z;aflI)U3CT8icTbtJ6~9Z!Kq9gWHdW)J^S?0q8nW5Ia{5YF<;nAz1EcJA_xv54T$T z8{6BvpF5kopZ@A>>}+*5_V-)wcRIDA`OoRwvTI`!Li-pELwnyS^#@4<#`77v59g2R zqgY7uXv79t5aF;iWLe23$E$qN#2LPdF9##kr&?Z|U@(Knq219*0lNWEL~6D5(-1h3%7 zPd{ZS97uQQ>!K`9BPx!^+!1k9jh1{Ya9vRbLIf(Ha4?nv44Yw@#-eQ*qtTWhQb9M5_>`4Ea*QjoNo;hJBff zv5}AfJqX4^C;!x=qtO{z=jF`CeXbNqrOGWASvLfve<4NXQ|07p6c49aYiGZ`vD0pC zw3{8)ls5L8(Asx&j|@a>JExkb|MWdc%HF}5~f&5wn$8I zaAQosMo}BvCyijhVHj$XiQzO9nnPr(8>c{Gu5^4x-T__J6?q>GRYyC}+T)Gh*5G4O zYcF7U$5)sfp+Ag=S2k!o;;pU?lb?8?9`xfOLuRiPK&CH*JaMXNw}m$>J;s~>gdvz| zbM{~kB@70meqYQ9XM|1%I~^bhguwfZx(#)CV){x?sZf_N6fUZ(@>oWgEdqEMnQ5-@ zvf!kri^PgM8HGoV-|{Xzzlgeu3uFw8Sp=h%nxV?MRM!&mq!fBgK@i8nyG!zzqgh`yB{xsu&vLkhr5^1Ry>gME@Mc z19f9aWF~@v4rIxY$nv20Or^m_mxlBStzv_|c&|wymNi(Ny(a0i!C+b;MeHLcNg#3{ z7Az=-uX9SzvAo*=!?!IPqe?62ICL84Ya>n7yo)whoAY>j`nJW1gT%R+>oPYgvQwkC zhU6PEe0a;GUiq6S0S*1jBHQu)(b*y{C|B zC1${bZ8+dPt%R^~ax_r!jqd!lN4s8bu`(HYQ&YA64C$DWph1lAm}@Y|!z@Szjc~IY zgTO_RX9!my3K*?((4~5VN|@^*?8}|~#?yC6f|F`&!Lc7aT@2*!AkQkzWRL>T_cW+T ztd#Ulo2W8tK79;tuEpV`7C!yP9KR=|TXjUYQaS_?;!eY)da(R`)n>2Ply!uT;q^n1 zo&Uj_4LB1_w(v^Qxtg0Y_Ppj~#O4V`k8j#jN^53yUr!G_Nb#jL` zQPO7oATKgU;K0FY6Z_hl2^o**q6Q>%p*!L~^2#8l`s6qL4S9aN3jX**UKu|EAFUDq z$fMo_Po#;0?~c?tufE~JVJux$P+VQJ#a$C5I0X0L?iM__yF+jWhrvC#y99T4x8O3k zySvK(m+!Cpu)As=W>?GVo~b^4Ru|8h$H+ejmuQv1{dhQ|<_~Ac6?)&oF_#hf5pLJh z&@uz&H4|qOMy6yIYqV~pE@N3NO(QSHM-_ESYoXY<^K;wlwSQ*exH`a@(0NEQm!xpt z%@`x;Jp*Y$3xW1xxXif@7bnKV*jU*b1XK3(eoU{eGwBl9^h;Y_*?AZ$z4OoR0Nc*( z?JMj#Zpsz`cfPfcd9=8@RodWVY)N5p_O;FH zE|}P(c@uT0#`x<#N!nEDM(htFR&s?T^wwWdi3-fgZ%Z>24#Ty(M5l>sd0xYnf*%UD zds|=XpXyz)(55MKkH*g6mg@e2JvWkV%#FyVxGKMtHM1?_dYgU@;fk)9&#{Gawhy7W z>yUPWCfrU^34M_KcCuvr_Y+H!vwJ1bxfe4|2G;ua8=7l&dB)S`@4XG~jwyOI(%Ww? z(?d^=(5yEwwZH907bg=LKFGG8rn_-h8Hh)tk+cnPee&YtNF z%P`;SP1OTyS-{j}_(L3}dbai%M)8*ed2_wrwTqj|I{Zd3`t-&0JSKGodr_=V5o=!_kFiLM05h?HXu(L79!_$RWzut7WLIOQpa^^YV zgY!?icjkb29Y_?;Ii}}q7?TO??`=&@jtCCc(GJI0fLJ9v6v{tBl-+H6|0wAP2|W?( z3;t2!kIvXk-=j^+;lq0P2MiKj(~y`*s#HN1@`ncRIpIzZz~+zl4i`YS29o4S*;!XB zze7abu1u7DO^DH~QFyXs%zg(Tu%AVGUfT|VWtWrcc1Lkm|Dms)*2R%1nZMpoJ6V7< z_gH;+vJX$n`rgDtFXVn)fe~)L!+d9>OWAZ6_17h{ukgDz6}VE6Yrovv*bDLXDV1Y! zZe?Z~7t*0Buo1~#K;$tpXI{mlCc9n;>B=M6UwJPZvs7%$%Rm zl!GfY!dbVdrO0=|sDHAJi8+iREskJ_LIuX^ zWnh0egcGpA+c@zlRe%J`#zc|B4c88`C=BF1%zfZOtKf94bq`;I#b6X!OunG)GvLL0 z`vJt1ZGln}iMqrN0<(MW?&1FmmeQFRQTC89?fkyYTdc{bq(Nr2pjW;b0w48WZ-Dj!K!dPCRGD-Sk&AwuD^FB%@T?a^1-yv zr)Jy#WOWA|0D&tu7nktuUVo<>A&6aSLOqq8yNQ3OS)O-P!$Uw%Q9&)+`TX9tvcFs? zJ@p_Y;cWd`@v4+lNIA1vEeHhZeIySrnG10_u z3z4Q~tP)hlOjN?rCGfS2+$#R!2z)~qmRdhn7Cf2~_H#gca0zUd|8_jIdQRmkRljb1 z&cU_H&3J3`6$cZpRge1cRdHeh2OI*h>if(2@Lq$Jhfo%{GnFU*{+BTU&e-NZd1@HvJQ=< z$^8W7+bytUKl9~F_y*ro(8?p>*|UWv*|%f3hK>A2xI)5%@fOh9`YW=?uIIGfT;LQ* z`jt0&N{YHIbP9B2qHWXpRIaS1TzYDneGSOFdmXB-dwBre6?6Jcq@l#Q z2jS?!?;tt@&ZOYn^Dy5z%azNr6PdH={rC3MzE@A^TXHTs+C&Zbv_ z`Rt)WdqCt|(H#oO0$*?3QJ!SKpuk5v7CCM_M{)dQNHsR!Dqa+GGqT=Jy7&D+|5T+N z(>*N}MI*r7QzQC;%%6c4r&CaNtM>%zM`S|WBva2d6<}YeR8VK!yGi;Tq(uHS7x`SjXA*gSkgxx)}sv zEBbhxz(2~mBL*;j&Y#I#*aM8Fn*v@dbLfN{11TCzyajrAe5|UT9nruqGmlDj844jY zH%KyU-LkU}1b(^R@TQN_ImFQRN3j)wqKaI+@WgRDdQ6|&%4m%PK?pWA5Y!^NuSD!4 z8zo~l_b{<4*P}Iy?Vf#rdN}V@6nZ$FJp8}O-S2o;MvjY${^W2HcrOGXs!OTV_;}L8 z40BeAIeGT94J9O^R5(qxwOaazc}y1C#Gr zR_TJ3I*@P2p(3 z`_FNgE%~cvwO0J@&}`cE%vXm}w#SKUyjib)i=nRrPVgCZ?B*(Wn=L_wTqEx$Jl!T^ z5`0Z(XWzh6P7bwatK3huC_>4X{b(!4RO=~YkH%gp#Y_5cTuk8mrqy>7%I-&c39s&* zRj;RLb$9g+R+cP2cTkZz!AhB{22ZVPE zjuy= z4usOHU(>9iu~)lzVE2cwM&>N37LQQ2IqPIAC*7E27leP#o2PEM-#QSAb5e$^o^lR* z2b~ETu_Y~N?O3e)V-Bx&pQ{6QpK+!xVRwW+qQj~`B=4TP;CtV9CEis#Xn3)iiv+Nd z5LcK3B5`*IwN*2-*g(BpU~Fg zuwF1qpR35=;#k|_U}4b|J&k!G3h*xG?Rv(kiGJMv#muy}t71DOX6Yj|<$*Jrf4N{? zd$q^BaWYbE@^NKyy+R?J zxzU%TG}=Qn^vGzI?2tKzug^G7s4st!Y#XQa%R5AD91p2*=#IQiN9Aw zu1)SAdUU?(p8HnLLWDnY>~}<*gi2FAM2jyia&0CJh=(%-tpv0c+9 z%_FlW&Ay4V>M-GXjq94(lOxpuFOY=KZdvuStv(qfG7om|`J9;k&8wN-= z+0$Ad%T{)1@x#4NwbT7hwH=ZBl`2MHNI%@1_4v??P(mAg{)Hy)I6?yMrTrb|-5)Ps zK^BS`uiIqpPXcfMywv4G-bXu;D?%pF$u=G`VgS;S4bGt8$$>)(v?n%{QrE*{)^ zNah6bEvE-}D*0}K)RO)}wf%#1-dl@1cqXLlG5^8`U$J-{H+dpFLWo0lu3ZV6(@tiPQ0<}B?SW2>t;lJy7k+g?b~ z%&H|Nx$VhLDYLAPD3{-{&K77V!E}{Hr|-LXzvZnr7k{F=&`5qYBV+XVj-7%; zj-7HrqkJ9xMD`BxBx^=ahJD*QcH~$3tso>}@tR>MOsMv-kPfbXCvI^!ZZJ0aLJE95 zD;6Wv`9V0xPnNEAJ$HJmdf3Ul@8meS-yXf&NsbmNG!c;C=UqO>e4WT5)+b~t!|7`Gh3w~6EC`hFwc%#1DB% z{{+3o{9JH|?c9N`q~zAqAm0J`(4jhbuAvKlb#$Bpy~dRr9&7wlbNKLR_Yl2n-Fa~9 zKhNss=OIZ(au#Ip`N%|f8+g5j?=jH!aIba$N|c8b1R=mjiS&&O0%$xXtHv2AL;N&z z)AQ};BYDE`Y<1em?z=4Vr$P@hzE8wAfww8tRshIoGe-S_bNcz;W=lxz1 zLpZ0!pl@KnDQ?DS!YnSfFu^tlC9^jJ`+MO~m2x+rP&K)qO9| z7Ud`p(Tak2n~Ol*-Hyk-32nOZj7Asc8*?9ATKF6>X4%E5vBH7ySFBq*)68pqlCOhA zs8T`1-k}1_YIwbL&xeO`z#-u_lI7{-vfq|M2`;AD7?-7cg|F1iJV1|6#v^R=B%QV? zt^JFio_!?$A*uo()8qb43%BS?LBm;MGm(}t*89LGpu9bj#P7?0s{Uu6)RuA*`PE@^ z{rFd7jif!mCMEMBvtrd?VV`cKEc*Aw9ikDV|Du;Pv0oCD6q<2fAWRBUURoOC?-mXb zs+}sNi!`Oocw4DU`_I(OR#me}@H_?QZ4kFEWxoIe-sCczBHZkLAIje`bb9DxB542IA1Jw$aINfl41PDC)fzvUbYA=v{WGB>8T^O}wnb|V-Q-QR3ie?%g)12HDy6uptsrQI z4rrZsRJ9L?Y4naiivEPhe%Xd1<)DT#xIcDh_!GR1(aGi7Qb|!C0%qQ~K|xV6C)Iy_ zyXP)D9_OO8nJVxa!N?0yZRA7{|AnHCqcqV^Xa9on7hD6}j}3r%K5OoFn2g_L&Eb9- zzIq{%>PqiaE|L4nS`3omm>O)2jKxzeZ=i?htaPD-sDW8d?binMKOP5jDDwXXDe zvBoJtK{1y!Ck>wK~U4Y6?fS7mY&TdgJ?k$D^vXK5fNZhq9XWd(}#6K9VG3puT!CUE^7?>evaQ+X*#r;E}WNK$Gk2b`y~STvsa(Zhv~@%f>j$Vw{X^eX&J4uitm^*@t` z|7E~Twd(y5z5LbU49vvW_)EI=npsrfU%Ml4^-uI}iJgG|ixGbs4`nc=>aU?P6LEEbOQO#>Y#PeXrA_x1Veu~cL z!S}TOv%ppJm(%qKT+Jdi8J9QPRH#%L)DGq>QRj;n?yB?J{UUe z)zj?@pQGo!C%%3Wqt|Ph4JL_ZI_m&l%tYnjS`SUpPdxcs4*l=m>N0e7Iwy4rPKO$c zfjAk9Dwl)*3HTB=uWZ3?lJvVEqT^TE>qIIK<~UVk&oyLhjgT_>ryu=5jUre`ki6BP0? z%7LQPx4Fn(b*}yrBUGz6kS3E;efRu7VIZFK*x=zI)BsLNN;a^1PD%#CYj=5wIyn_S zYIuH3l|1tOY>jP(s$jy>f>g()qwnc&graFH%IlRhs9;h(@Eaxk(Z~Za1Nka`SIPjL zWR_cpqMGb)u)?}UMuYs=r(e_dYZ;l&OT>)%iRl5#S@35A4sze6h&N$}J=f!E7COOe z*2H>@y$;_59`{*gmaDV9g$bXf54d3+ytHOz-=NdZ=djo88kn{2>QJ*86mtc92$)!} zG>6tU5{oOyXKx|R|3Zi!Uk?%OQG*9%^mDfc-G%ZvIyywesX}mvS_zkoP=Drdd&^Wj zXkT2~oc5k&`6LfZYJHpfLNU@^R{V8AJYoDFu=4!}K4GZT%&DO*Ba!RkLP(&*lLgG* zOF#yD)4k!knt1K4oeO^v>(?nP{|K=v{t-7aqmisLGbb%A?DqDZwTLio8Bt|U%5$l6QL4%vmK##YrmNyj%#`o z9OtYWrAqELZ-#z<`L!pcuM=s86%$Y}V+cWAiRGJgW;n9wFR37|n5k48@}X@kk1_rJ z^}t%S1ipqA9OhPBCAC&sYT!W{bkBt0n+RF=*S`nP|KlA;IAdh_+47t14PU&DwKD^! zPXX{=Y4)`gE!soFH*~X4=17s%y@DkTXMz}ddhE}I#F9`%z#Ti@eVd37ULQEHpk^^r z7#zSkqc(dvNj-cUSOfe5YptUUODdm>dFSSueJ35SLIPt^km$#Ppeyos83t~FO>(<( zTP{Uh5D%j&B#Kx;f&JTp@9H()Lo+?zbV$6Mjfgr2`sA7?rHuz`t!C{!4JH}%m227m@A!@b5VRZi;)?7z} zT`oka-#Q;EBvK_Ujd!OJ>2TXNabP+>8nHm#qPef0lKNePrcbo54JuO=Bx)*^K z+sr&yDXdp13{SGePqKJVoLWS(glQ|JR*$|#{-oy%?1HQ97|f(A=_vErM`;Uz)X$aq z9^{THsfh*Zu*Kxu=qg3>eRWsV&fQ}wJuc>YH+{mlwLPF8ut(c7R&$MzL;Z=6Z9ffy z1{!wJy5K#i4jCQ+C`_RR+Jaa`}t9vsdFYRP=;=rej^9*MR%Y=hYcIY?TCf&;1PDdT_gBX zgjz`JJKN7v+DW-{JC+%wT5mCW82vE?+wd((%1X>Jt|rMjXY$ynhZ zoUzAVOOXQ3-?;BBBnK$OxPOXS?bq@=OuXTvZYlXR9{@Gpm*>FM?m)AQvfnD3o17fM@q03PAohl< zcHO|J7H?om z^`Y^ciPU4;8s#O!!aCn~j8hA^_XubpB{Xm_pnJ!^JKn$h2BM<8sRHNdkUnXCUV@PE zuluIXj*?kYWkp?*+Ja3|mZ*tqW4IN*c4*C~r7OmYbzm>OWUVXgh{|+APZqYfH+bcY zSRFZfKW+ZpE*%&(>UmopH-G8FQRf*`B^mL@!qSh?3sC2op+OD6#ao%8+9>2Nz-e|) zF@}LVdZK!y13>+{vD;tn?AF0U-^TO3J=1<<{$Jly{pI_?a({PN9uG(e{ndIXT?(M4 zqBjrzSDwy#S857BrJX^sLiXX0o2s=)YLT8y8G0UntSW};HJvjD^KiB@!fRro~8(&rdY3X zw61bIwuHudfDZ|4S!tV7WnS!$!fPu6>5rY1avG1n=LK{CQ|jyW>&F7CSe$GckvTjplH0bp3W4Qkw5#k)Ek6?#RttU&S9N4lBijm zcMmlR!ZI7Af*-VY7?Gs>!_8CnuH6Vi*`IyfLT3D&k0{(f4M;d0heFHP6jLz=YI@S@ z8u3E+NqbLdspiRNTdmcBhx1v7&>gqqkYrVdP}@>?q*ENRhx3ES=xy9S7wz0WRlfzM zd~iLd?t?d+H-k6o*Mju{lsXWtZSj>=rsed@Co)ZK@twEyTK0j5!Km39ZY}df7Xlsq z6FtC-9$>cr$6CP08uW(;-1}|LRrC_rSAQLM^|E`{r}nY4&_wldWZuUKkfXa!t?@Xb z@=ab3d7K@UFfKOW~@E4m7t1WQ=}dmm%=lMz3bk(dQ^9=XEse zkjI2f0)Qn@(C;|g$<-*)<;cKPy|6Orxz%L!!EJ&FAvSAm3Z z`cI#S#$*F|6L5>{v;G_3OAmt|cR7h(bU$KCxJnn3P9ta(JI_g?1|NJ@g|Gp~j6~2; z;*CqU8Ei1^Mh5*64E-Si_l}JGj*Je*#d~7*b=pL@eU;d!HtOz{^8K>$n0%u4tfcCT zl|&-3FQxRX#E&2pL0fT9b^kj(1jmGCk<{d%y|sQg^78!bdI#6(fu<{`*JiqIl9bsu zmM!dUcq}}dj2ZE2-6Leq5u!* zLyOLaAqSC|`E|?*>#L)&2>l~FD4hB{b)#K(JOUka3g|_(i-SYE3QDajs#Tm4vGf`l zqw|t8Tch(uPK~9ywij#jL#tb6Mu90w_6qwMD$fUNZT1Rq@3QdkV<_Mh9B>Np6EoYD z!MkXekS{kgeGcjC8}tq0=MO^zXw;(Q2CvLJBvV9PP%LTc*FKvIbWR>^)h}K8qfg*m zsC@!Q*UNdhC}nfC_SkHH=`T@61oQ2ca#zJJXzX?C$@HVP<;M)FwWI$_tBz0!FX}#L zRvwJ8bWF)$@F->9k7bzd@4zb6Q_pthzh+Pq1o_8sgquK_jR`D z9t^%bKW}dN)%u#``bgkRB3DIjMg)b6Yi@47RllIn-r8JWOVl9fyJF}2`=WtsrE;BU zX}faW(4v`$@QiDl9Guf>=h3y5aIDsC`j`f;k4 zMHzStdZOWu7LDy+9>>*#HL6b0{m#HHq72HPx#+N~E^uUKI}HS=7O(jE!%!Xv{@{#4 z_cBG`&yB+`meR+lhqx!bH7)%|el0e8Dj7(*Dp9lbRRjP+^yr64E0#U#Q$8yn@-<87 zd$cLAzu= zJ)RN&>I6fYMVcd8Kb;LL^{*uy?jG;nj@(^83*^VNUgnsX&M^+QZU{WPK=s=PCzmNq z=lYJRHZlp1DJ>lC=Bmy$hP8cK`2bmS7mBfa_?Y_bFk9%*rtpjS%d^k-S$$3Bp@a+E z1nI{}FU2-zMFT^M6I)8A=}`J#v?yI{f864MYiPYvB@sv6Eno8uaC`O2`p! z1Kn$f4FJD$p&*SXnVNMs{W!1pd~rmMw2QYxverwa)5_~aeT4%$W)d2WYf+D9J^HA( zZ$k;R&2Bon2xRmMcQ=#Vaayt$e+e}_wTk2n2Ge{|Ea)D2&N(x)N&5bI(>@Z@k&l5o zzLegW;t_uj921~bs2Z-<)lD70b1`nq2|eBKl9~dMBywI5|M*;zJj7>|K-ayqek-%C z!V}Wk@{1~^pfLfmw_5Wk&@gXb(89Yn;aSYRYVrEb!ldolgE7%jRfC?L?UrvyVqo}= zv3U2Xrgn3A?d_?l5tNzsEXf=jQFyqmgf;N(`x3fWD=q|a);MIYzXc??p|<(DHW0{b za0OqPHE|wTX)g6PD0q8A-OO*2X9o~;fk5fGa*HLvj+JmR0&<;@kj2Ld z2{1r+@ksssW2o%kT>|^*J@dSN(a-L{4I>WWY~2)+5pVK7 zV*V1uW{Y%_7A4Q8U`RWj<_;+iMBtm+FKwSwq?UV>BDA54$(XVLCm;NiWl zo`MK0nYQTx-EUpy&RD{1>Pw4B7N<8#6 za1y-ATLx`jj43ZbqXvl1f$*pHV3kF`q4h2IUukZwjaRA=RLgaQt`NvnkVlGY=}R5dTb6xt?*)_=bWMF(76?51=FJ!CJLgKS3NHG&3<_w&nC5#xlqppEqoOY z0#6H9_mP$Px2vZ_d1i2aF;A@mu~{$<4u8761A2dyN%j1$&we|Cx?;E>xtG+7u(j{W z?uC4&qnQ9fl9>Y(0NS2NKruE=!lm0JdsJw|za@q7Js400WY6uKb=+&fym|b3AI$dfcQyy^EUMWj7!^y5Rc9>?q;{7Og0MA=ykP6qeE$^v z(+$l;8~8@4Z9@$GiMOfAkL17Eoa$&CK*f8EtL`65nTnwr4h{&O{h<3I{6liKy;VXH zp(T6!G15#97HBJ;xGq2fcRoKkee<`>%pKrgf<82fYvC6W5&9wqTJr&379?2BqPPIF zFGYst9QgEV z{9P_&v9eUk+5TcgfDoY142WLOls^_q)v4VU4P>UW&J zZokd#2ahbrgKX#L65&&U*;P~b%0{CH-|cU=1c@rt#Gx@(7Q`filV@_w3Zd1Qcd3aY;*>O|aNWZ|fC`%~@$pv#r&cUCAJQvzdU3Sw8i1 zzDGB|96rp+n8Wn^GE=11LzJ0`p3c`!AKMA+e8fGpVPtr$aUKDb&1+6&GiF9Geh+WOrh*;C&>`bqa;t02rjs|Rs(xW;24 z!e3aS$gCwph$RfBysnk_&-7qZgE*)}NYL4TNDk8^Mq?t1cy|hudphJ{**>*8i_s#G zS-a!%m3c|s6D6Dcu}}Mr(K}B?Hwc~J8?;8i&N>JlSt;N###~;(%feG&60jtXKvpAF zn3&xR`Iy~xg=5tAGHL9c{;nHOUWjK?593&1=c4d@r75Cn@GTlTKIIOS`eWBEKcs&g z9g^&%EGxT_I61BKuh5y^JgR6!o^DZl*zDGcC=RHD%jBVz+=0N|5cjx9B5&urp@RQG zij{J^CPNX&G6jJk4R2#Gh({1|Jc}DH;ai|m=b0ue4~BPcRupts?{?&tvB%E0^*Pv+ zpQ8PzQ5;_xFHV_p69mk!o&;Mo)3`&uGn><-h#=S8nbLk}Y?5|}{7s-9#??HZ-!ca5 z@V_~hUFe=79JwBjiMa&qdl%57h!8X*@Ox<^=)?TR)aB1;!5M_Kf4(mv@8Q!=kb{Y` z|7SujT~+^c=|_vZpkw7wXpQ@Vthm4#WDjsEY3wQK)tqgHd9^*UBjlm?mXWdvok?F# zcV?t(J55cEUZzE}I6F6kq^S;XSb7snmIr^syA)H-;o4~@BIVYrL0zx1r3k`${&Z>m zP4l_JB3dJscE7t%kY&>DiqoBYa&Mi~0m8(nE>+Ffr4d>0hFLNDe4d`T@?iWtv z-uz%)`S`ffWgAqatTs1 zMX<~Oqb|vq#kVPwYbjQW4Zlt^F!Tn?PxUJF2KWa;MsF|b(-X8XlY)ACah4XGeM!OT z_n2e~9DAMik8Afmu$rN*=G`(>QC}G^WH*^L%MU42hCuB(E^zAan^f0qLBicMedrnX zXd~pHK=Sw2T1NQNo~3$CQ(zj}$H`njATCzq`7XIM_7^ImFtY+DzdYZqXw+V-VrVix zc`%Z7Lj`CF$g$?KfvdTWGyM6aYG95R;G8p^Bncx#MF3f>7kceJMz4hL!LhNzuqLs= zJF&AkWO!H&b$hr&vAWyvq+8roBxxPOIppes{8L^mkR$=M4pb$Bcy<7w4cx(RcUCTg zA3SBxL7GC%<^y7D7T}I80gH!TcmbNk_jn7Y^;WcVDgG=kxCMfLFn#4X;}qK^&88r# zXa5G~qWK%>(eokz(zO8i5ek899{~gbg|nPbrBjkwo>?b2?YoaL8gA z-Zm`f5%K_K_lY4QT!3msC4otdY}SEhR+^Iq6|z<7)}(nUnCM@lf>KtSuccJDB&rbA zCYk)_H955Uz4?KKX!Gv%+k1g(#mnl&r)|Y!9tL~rT>C_Gjg#70FWvqI^3Tx4Os+^S z8Kr2Mx~rhP0@|LKFI6FnUGo_ych~93PIj#am~+HX-WaFsuP5vqlU6S8bS=xeZIcrR zjg#hCaE>-i;zDufPBT-8bDsD6;>&O zoRsJfL@>k_Gtr?UN*_fyUNmbB^>?mE&MD!i7_SXcr1CzHD3 ze+})9k?f-V+UX1Z15z~X9yFY(&j=D(>ZSQOLfP8fm&jn#WV=Z zUc4EzaNV`x38RA-P;8Y|sow@utx&F`)R_~0!gGntZ<5lTVR1IcUZYS@g^-tmL8n1| zWaLrTe9_Pj&MN(WdABYuh?5{GkL{eyg>F>=;?=mjJ}iuxG?378=&r85nVYLq5bNH| zn|};6ot5d0`GOeP-TZOKVM%@YN+dbH@ddIH6rrJdr;(Tc>9dX9F`*tWZ|ijJ;%lMU zo;SxJH7YF^D^_eHY|qgF4k=Bmk*#6Vm)5P;Y3Zkadd!*y?Oqo(mg4i-QSD(O@WvY$zUe zm#j-yUcbp2fa>cm@42W%9DPK`=gT@yB0+;U=#e!oIDe`@a&)3TpA1To2trg+-g1#d zkAB8%A>Nl!Xp5AqNXZzNyyr3!z4S;I1zVG^0VOH>Bn0^9*qQR6`(QP~n-=9>mI^S;rD>)Gh8V$azm2U5JVUpTi@H0Ob^TB|0 z&{VqperP;UTltMOzU|^fDGG7lPQxVUwMr}uV0zyzpwd=1Z$VXTC3q_QdMnYp8fxlZ`0*L{hV`>c43J*8Df36a+F}5ZI^1 zUl?te2^Ad5UzVl~9l4H@s884#Ga)f41zo7E749%KkLcCKsv{ZGXk6H%?5lz$^^Df6 z^zL5nQ=@xcvb9)bScr^RBzWZzivoopUPkv?Catc|-knL(Uw!`BsdKH^{Bjp#O@p1c zBB2*lz0Paj7yHk*3rSwY^V;e3GeN-|dO|o&#`0W-@}AGzF(YwJAma__Z52RY_iGY1 zvrTkHx)xqa5M5pKj?QL^Jr59DoS>RN$Z~g9H_Du`;SQf=6q~-?Og%&~FEnWca~ia> zEw*FTfb__cu>-)%IIvnmI}GL1b!?7kf8)M5`1CDo*NtI*kL)dPdfTrxE8}7M5Vv6z zbl_HiVzgWi4m}CsVL@R_vle%;2{8RO+_iYwAnT!R#~&NLgPDK&xtx?2xNjtJs5pUn z%6?CmbPLl&v*wULY>3xXa>1R(k5ebHg7+C_lb#0dmmkmMCMTVfYuX{w)*oM1VNO^_ zc+WitY3UU_gm}!*ltmVTlHgN0jI1}bik~%}9gBZHxXZ)Qjb+a;%KrGZ^8NFkg#Lza zuYNqVyXS9H4?5!t9Mlu*G@eUVSr6SirPzu-H2m`(2C>7&`*{bospg3rdkx9qdf7N7;7Tda*K>t>*B`Suvd$Sf=Cjar{p3xh0s6D8GsIW$EY8Z zt*Y|<1E)bm*woeB&r`!AM1L9nb=1Ekm~M>v?*)WALN5~9#fb|_K4ZxYU^5Fu9& z)h{C+_{BDG-0w&M9=#J-5I?y_NV!QU)@LvuQY1eJCEf~tzC?M>Xw8LUf^F|_=Mq4^T^5PV7CVX?32|QpQ(KWQ$xWi|3d*q^ z7bBn^IB}k!?%wJTT^orD7cjZSO5*-F$R0mtku$*%u}An^v`{$B(`USd$#8sK z0u0hSV(BMU%nO7`!4JjvL7l3R)%4Q(TvXXBWQbse)e$)JdxqbTapme8q~6dpge2}G z$Y_aVh%DOC9gjq!^*p~3xz9HxS@F+a32u=Uv1<)ZX3Xarl{oG3MhHp%6ZC=HkIzJH){dOiAD=UI*falvscg^gw}P*Y?M>(Eqn# z8fWZWS0>(EEu2k0c~DjRgEw&hmK;jyq-2r$^=x?6oZze%Y;4f;C*&d5=>#cj=KdLR zVf97d+1(OAJ?tOywQzpiU0qNBtsC;NwU~U8m>QAsYgzCAdhjADn<@D}(A|}g4 zKEaP;M&ay-tnfh594485#?PiIod?$ne%+`q8iKVzMt}k%W97|QPETbKWQ(NYbO{nu zSXc)-E^H{BNWDVE=)5uh@^=V-eN!K#?*0MsGTz)$Hb9wm+eDc#*I zpZLr>gw?BmB_f<@R}~{U?1b7{k@=cf(kcaie7$iV!c@h#XJ~(^7jYxNgytj0^x5L; z*gnt@PWCyjZ!CeQTH9W)ZbQ}Ec{HdIz#SQxnj~M3>kJ&AIqnb^U>7>zHNjlXrSO-J zobU0M0ru4>ooodf1cKokEHXrpDSoirsPKv>Ym1%`su)e#xgezPSx>5L0&|7U=M>NJ z7Z7IIyKoWMSh4(ecNW}XX*~0lB8&4SJ|Ct1%zJV;DapU>ev94buqRUPN#M-#;)&g4 z2K|G-X+##l&9_Vm|ANfUr>>*blUTG$PjP$i7w#ji0Q*rNS9FaSHEpDwB_S&KxE$a2 zTYjx%GlWXiQFTnX)Iww&yS^Vq(8K+cUy2FIKTImp^NUkQV7V!~A!OJy|ZlD{w}Qb7Ew9sWDB@ zcS;UWITqPWy`X#sQI!Y%uS$eoJhYRBY(_6#p<@Y)rmcpm0D@7#BK4ySUN+8tX-n_Z z8g_*VNpouiUSxuAEvtuzh73whug))fx7MVhl)wfk`$*AZdtL$y!RPZ*lc`O!NNAx-}OGnM!Gvx;aPFM--z7Vg8&b=_z~6gcH}PN9=MWGJ()$iT5=wz4lTKd;a%% z$G_;X@ID6JsZpuS7YHn;4>ZJ7@dqm%e#H`KF{Yw}NZ9FKiMukRHkrGj zeJxhH!KGIMk9U;(j7|h?$HZ%vxMxH(Zr@M16LKz9vHUf4Q@!6M^G1tcc*}! zNMwvmTXzvvr}iBt&FvN!RY^jQfb{Rg?VEumn}1?Pex`LRX7pw>*||h%P#NEezuLi9 z6Yz#D1L_`Ur&_m2riC)X@xJKLG;=*X^6?RnWMoO(UJDIJ|L#J4KVW{^PxlrCa9L`; zQ~e>TXG{ONNwr?SvPOSwiD3Z2qAXD8?&us8hVk2_{oIt9HypIRg#?*iTWz)bC?Z5v zf4Z{rYPs~i0IZ~%kzugt_DSWIZx<@bIbDN~k7dv3zZ4YijT9Q@A7xv;{#?0uCi%2^ zxeR;cveDR-k%jiY65@02Vo1S_{-hv$=;scJ4O9hpk?5_^n@oOrcxS;6x$GnF*(_h& zSXf*+0Cc!6Z0%t%mr!rsczGJz!u*#1_zNP+%F+Yq^Mp0cuWT-DY`mci?}7}n{$y36 zp%mTw7Mn0`%$X$l`|f`9_9nt8HE7@D92?nIT6*>ppqiaHR#%##K`HtvfH?fppe^Gv z^G~C1Av->_(d!@-u~^_%_et4|BJ=U?C4F76-4^Pb)w6>h(uS%ndO zM+|WG6t0FjMXCGapR$?>FRh8;Eo&e(Bn=s^n?Pj01myGa!%rJ~8y^V;Js-4oHn$J9njQX&|1}LdA3E%KaDFw2Pfmxy(!v6A zc)IxyTKyxs-P`E(0(zYS>ZH-&BI+t{fW4@TyBHsi$aMfO2}dcq(gbNT8XU3v5c*3E zu7cxaaF*8jdN&!+|0W~4qdiMt(D4y%M4kLA&ZEIuJVa-Y^FeYEchN}+{jyvqD3Ff2 zF<%fvBZ3qS)txZ@_arkwx=5Ch(Ou`+2*fKm51}|jgonumzLNDoIQ;|&F%ks4$L;|z zgkkrLUIxyvphu82;{uOXswGHi1><_vC!?d-kzI`1tG~SyUEeW_ySI;EW;gbs@hrXD*!e4H{`aT7=Kg-LyBD-Re%fvUJm_E#nMQNJ z9`Mm@?YyrC03+DhZ6lEF*2h*GzHIN-sq1{pVE0|{vAMVT0sh>0+uClm|DrD6wc0z_ z-MigAI<*0u^LA_VV0&XP_;j%MX?MRF;QT;R0LIt)*xYKs7@*&v`Db&d9qfMq0m7Xw z!rA@2)7-;3*wYQ(HesY2Z?~J+9nE&Dwb$Hi!69ObUWDE__*;N=2)A&IG1UY;898Uj^H{`?PQe`%N0m)<%1Sx(6t*aPS5EetWRrq9ttYw3~Z-2cO!l-JLnu z^UttCFwzZZy+v!iyMuFP+-dIa{e_@$J!m)32c`+n_i%Y>)i!WB_F?rl+xBPBB`hXQ z%*;2~X};fXy>ISpHt~a91oXMJ-<*TZYwhC)E$SRF0=hk*x#On6*l4@nx#(P{-3?mr zf{m>|TR49C9`IqmCFn!TxA{RaiG%x$KDbxO2*iD+E##I)UT3UH36U8_N62!{BJ`IN?@H1c31yJ6VVie*ttN=q z*bC6Cqd|fYyAgZ{w1?bi*tsc0PoJ3Gh>sNkC2)-(cn3X3t}7jDrys92>3`WhM`?QE zjD+&*$1BiFPLr;7|FOnXlh*9-qrT)1wS$n;&lulb%R{KqXykp2-WBv_wc89~Z_$-Y zzFb=~qqn_>^nTiNDVE6pF~bWhQ^^TlzZN&|2Yf+dQ3-iQ>ErEkLXGn|fbSpmMnJJ&$4Pp8*4}*6bt!;GH-Pqdd;BUZ5{1d){H3j6`ug&SGudSs2 zcnD+|h$XZn=V-S54u*R?0+6I)qHsWHM>sExd;q$y%;b$3+p@p$sny9s_2)^u(j*cP_ zE{!z^DFQx>x+qT2M>|RXT{sMT@4~n@8UUZekMIu+(ijcxn6EzZj<}D(TGZBVs5_9h ziY-#x6nI0g%Ta5<2>KXi(;&m|3{Q6RF|0a>t(j%*Ca`KihE!M$AaID!>Dr37s=)v@ zdY9och z4lG*czeH1L*uk){4oA;Sga-JNh9_|F`Q;kjuXbuYmO0a0_locIAHkb9!Oj7&n*Ru9 zXQ8q6%4%zk2U}ZzS|6pOvojE(I<@B4;UGL3?(^R@GeqlSdDOAq5_W!*eSaSfIY>>; zh-fnAkkB6BqT@6{sOLq|HfCCcS9x$sAfga2mOXL85^?490LYMh3C?>V3VF$Z0nu8; zC^J0~}{Uz*E$gVWA} zp#b8~FZ$+yxMyu`eo<{S@T{#MfLh*TIsdZ1*;SIS^SvlM(kqbfNkAffOpZ8|R0+l| zhS(iSqkM-77xNwNN#G%dSi*{BP!RqB`b_i+gLmZ50$jv{rU-B84vvXr>JIqNq7Ht$ zMhE?*KYxtGx_6cB3|`5b&iH5m+v^=tF~NN^r7==I$Xsl!~}I-C6T1V z@aPMJK-xx!FD49=`_gpSU0XwS3>swUp9cr$)OgyrK$3uN$NAQ=bF}T@85;A-4C0y; zg^3A+kwFk76DL|sU#NR~VCvw%(xak2)!xYK>~z%WEoh~w4~i7&z+7Dg!@MxjKCAj%4*49Ss9 zJs*%)h@k1lIcFun{XGF?zn`59nl{l&f(;w$!kb&wCM}aoCjd%T2p?Mg#-P`H4QdPX zhau3~IYZ;K@Z~z!644O-sJW06U)LsL*}q%ri0FwT&@}#+<|?mT3Pec5D$#2f;yS}% z$~4K7!m~Z-r^Gl$ajtZge~gp~k6Rj?CYQJ=pjstxVr}Ju7hWNW3we(|j|MFb&A}4o z9Q6Mtw9=^KcoXqwnN1ms9KoAlfv0JdN#(O3+9XZ>H0r@Q6{9St*q}i2zhV8Fd+mQW z|NB#Ov;D2JvkRJ0dt+PaM!|;fTy=;cDVl=8@Cm3OKq%ob z2FinkPG;0Q#xtsn?i?haTYcG=2sE3wQ5wT(=5ZiBgQ2*+1(y-qQ>9V2j=d&*V4K~j z7oE@`S#8Gm#Ih2xJPyuaU9x@72%Ss@@kxwJnfU;h5FP=}!ZIo=Wn}a!3b}1J_jWe6 z$s$HppA9t61oOexF6m_l`%N;9;19Cvyx-gS=%Gv>!gMFPw7K{P+RPc&W#oEbDl9Dm z$+EP@;c}l{6LViFN#-<6JAgnP+R~0}XB8LchpXoqdr9| zB_t^8G@?%EH0~r5ZBDt15#PPY9^Z@XRrey*y-0O0Qr(MG_aeW}y~uc9kuo6h zY3IZeS0c{$mMj`}QG`;djZ!i|bd_rc(mwcm-f>LjmkkC&U)RDfoGWU+i$tV9C+&gw zM#5oIBw?)TBH_ZEP?YEnfgsgw1i@d)h-o(J1fa^tN2l^3`lP^$4Yf}S(N9VlhZIQC z!dWA41Rp>Oeawmlw(g?ryG9EeEYQf`#RGK5kcA6_GZhomkWB#H7w;E%|-i91Zm zt0;Y#Fi6?twqyju@uDptLIa?%Y-T%=@fL#BPEm9Kwd82n0}WQSG)scRdyUqrI!bc& zVARJvL!=3S()#g=K6o`_p+EYc*JP3F>I*S|w2pPkGF}1XrXa|qtiWJ0F1j-zoWh$V zL_f)eh%cC*^Jq2C!&)0NlC304-}b^&zUIJ{>7(K&s(Bsu`k3(?CrAGF z_{T{!H0!62id=2&$CzgBG9qC#R^XJ_;U2p>dB_c-e~w~QDd{>aVK5hQ8V|*+uKmx{ znYoC=;4#kivAS^KvGqYW$H(*Q3h~93m?^qt>+y-T$3wOc5BD( z`oSM>+%NQz)4l0+6HL}7np{`2iZYm+Jq>r(>eUbs`wKk%l1Gd5L;BS?AEl?A!|>=! ztxq~1l8eB%2iNmW642FPD$pOu%LJOJ!_}ByOa(I}MZCP0qC{7_APeeD9 zvvXWEp*e=8*9%z@U9qEdPDpLegJlP%p7n`D27{9PrP6=Y*|pMo!We{HF|@9RB8f|# z)SxOwA1n@RFd0#ZaN=<^R=Oc54QZYDp8I?d_R}7z4&*`EkBy}jND|Hpm$MroZNq_w zeanQFU_vrDTjxVVmlQ=HE71sPl+beTUcr)Gz=Pu}R0nX+W3th};=nk`RvY%>ZZ-j; z91=Z{r!oq~F7dLPm#|OrA+dxE3><3|rZJF9C+(fSai_8C@iB*ham7TMN&QvkBcT=~ z#nkmy!yv@OM5LP5BR-3J;lPnmqAswPKuHbcq9IPFKZnS`N8M)j$`hBClb8W=ay_>K zTp{*B!Zj{==M!wuF)RbJa%3~JPK()qAJMUy6^Q{;{9=DILOY|u+*+1BV_rU<(inCK z9~CjbA|hcp;h5PKF-J3~+h>n1r&{pRX1o6a>WrI01T zK=a1TUh{oxAJoTAA)a{rPRxI5w=(PYN)B}&FK_Fb${r6QqBvQ4WHS+y*jAx3VN0N> z^|^Q)Dce^!;;i1HZsA>}q1DP8jjTy{>0>fwqZDM_Z?Z;a4-CH79}{=c_LqbVOF#rh zae8X;rC8z(^}@@%>LP{=c;WTt3U#%^IG>oU4n-1SGNQ6(gA5}As9dxik#*Wdhz#{R z2Yf}&_s>xzFr!o{1)kC0Ko@W#QgaCP5m4cWY6nvcPxJ*>D5H^j^?42*u|gsZ_?c<0 zN$8Sfb9bw0k{zFibm5xhyq4519gDfrrG0kD2j7@#9BG&kVq?MJn9O~?V#MIo9ZEdF zA*SRyL7>yl@4wq${QkQ#c|^fuTJXo3T^me4j8KFm3k3P;(JcT9g2@O^-H>GmRAN$e z@j3!Q70#qsS&vSWFA?U!f4si_m`xMtU_-*1?u+fkjKM)IYMeA=`w>CstTYW71yRkT z&YyE+v|3qzTt2)+9pE*$2-e{%T=`6>xV@x$IcIzWvBv=2?dXIL9=U|KL-VxG`sAz< zMbMplHTBW!d&7-4hMS#(9W;>^gp#{E{>heT5WJg@!7Q@>ko@9KWk%ja|`zlZB?SL3h_Fmz8= zAs1vm3&P{?2E1OmdaxjiT^b9=q4AdfMbGzY#&^DJ3)iE)83wTLozKHWNP=Ic#b zq@k?=dv!z0E+-Uvtn556`f}VCaS?Q>Bfj^u7=v{Kn}Qa>zV+nc5cW^Zg%e-T<(L6+ zL)^9fPaB)fEI+`F=8dCpH$OAD3vNfL*wI&B-M9_3gk#COYCY9LS)y-LiVZp(-aY5XI?MNNEddnFvja*T(ijpkh>ysh6k4Z|^&PJdX$*~1_H3ks%9baDqI8C?qb^u>BsL2eG3~LlwNx;wxKLs$O|NA;}iR$cu zOB=Flj?iJr8G^B8gp9KL*pO5)_=m%|^C?^WOk7Ri+I+ka6PQ)|x1u0h(HKjE89FFy z*Tl)OBD+jIeckE&xzX9!d%xf5D7l@-ztN*IC27ZAGTCz5Z(GvgjiZKUXxG^CKgk%o zrwON8=5#UwSaX|uP&(OowfvK0K=Nv8&T0H4X^<>!;8sO`1p3-9=}$Z^87wQKC;0Wu zal-uXYOO3{P!3fm%iAa{m-E)kxkGTJ6~v8%**RhdUNF=x^OHF76U&I?j_V?fdngZ( z@78WV*pB<7uYAeVWhXL}bT9xwM*-+G31Kfy09C)p9Vg2^%#h5te9?XyrpA%NT>gY< z7W5%I0OI$dZ8>u;UbLC;R0^wEmqun`-*4}}-PrD+m(9+5s?l^5HY?vc|ylpZlqm`Mgv?Pk^>vXVaQv2Tf->8 zB^Hen57qxedX@~(lLFW#iKnDu#3}xv)l6}oZGzgn*X$r+>eLVkJA+Zbqdq1%IGuZy z>ERHURKP}uqmvW7yzIMyzvAHQ@91z4gJU%3J2dLZA$q?aPCCg(>vCT*7WhexM) z=;YpFet0@aF7c)gFCA5#N!3wZgdoPbF0nB50gWnxf!JxRE8z<$lTu>viUuYdZ>UOUa`V95-xetOAgfyfzr$L zoiX4{LmdNwBmw8FqM4$bcn+hl(b0$krv0O32zi_cN2wsQJ#$(sTm>suqk{g?#8lA-yxYb2ZXgq=c{c&3bxWM2;QtQNl z*k%;ist;_3?n}Osz%M9Pux|V|JGfMh&O5ks4lBb|hIP^__o7sC5S$ z{t`1 z9hxm5#V|aAsToo8E)neLYpl|wB`0`^&!rj3;+gd4{}l}qz5})d8Q)4$-+BJ%nMb6` zKL)d>Njh9ZsxljBfeNG2&pG;v)>I@}@c`tYAD#vWh)T9M6ob?X1w^P4P4vwR>=12L zry#==(If`j;hs9f#JF()=fU7Ns=`X-hFyy+JP&9O0oz%N#gN4>AH%Qv_(dB(*uwXq z8!av@6vo;~Ia1nN4*z*2&$gI6ya&d{ML+W~Nb1ujaI7sj>!Zkj(M6g<@K4J8p4Gn)w^dvz{2KJjKFxR1KmKJ`o$7E5zP-%zv zq9Zu($`1mcxGVG-1n=ZB%}r6Y$NP*Y7_n+Uz&JE3`b;9PTKxkKsNPOE6e$xM$_iwl z@Wh{v#Pc7ch9&ktvSZbYL%akBB`w|{Vq0l?8lQ6%lQvbSkyi3B3(M6}FXq54U?Z0p z?Ss-jpzmck=vuB+5f>~~W{BX7LX6N;O=U$CsKWRq@gepPFG$d#b4~h6pAM!PhG?bf z5p0_s4r^~aYRdNCt^RS6-Mv>Ttr0!N6DWEXJ47nIm zco1+baR+d3gxHUMVA6icH<)?k53t~wG3g%7*v$CNz+nBjyH9AldAGawaijgD9z??< zy2_zWg2T#tQE-n$j_4~eQ*nT`wz4L(dKVwo zR_Equ@FbIvOe8MvbU5?^QAugQ?6tsctA@WpYR?*U408%`&-=uLw-8IU0B0#MW zgD^M)NjON(!+(yh+2`1f_{vzepG-w5S*bMsYm~@1L-PW15?O_eT-X2=UfZtQSb(7hBVAWb~WZ``s5fxBH;&+fS6hV z`JrBP(pymUaZtT0GYGk7KTMGPGfvV#lnjh(mHwck;bJ&w1^wAOJ}se@+rZu%`;0t; zGh!W(@d)}XkD!#~r}jckDR(Fo0^{?Yw`;*Cg=yj&uh2>(heBHmN$Ao7+KrAx6psdk zgMCiQ<6=lk2z&&h1o9HJqX6=X;Bdx9Cvjw^{t0$30ZG0F2q&&|$tcC!_c{DaL(dP) z;-Dti95FK@^0MR>Id?FBbOfr0g^chIT?y&1fpqS!eRrOI5H?T+sAghtN=Yw9Pcz3SQD zV-t!QnK`wySgPzzbYJL8G{?PQ85+v%x4YfiX?wB)Y4DFzp-oA@!SlBc_fwn0c@|X< z=eOFJ3`A(6G)4qCTM5&W46p`Uyev_cAW>e6A>YgbG)}C>`9$ktP!W;~Q&FiE)m_UO zn(oPYm|)qnwgjxX2HQ`7nw}!gN|;s z@WpRBccp$azb;Aq0E=d4Ie8+7`|WP+)-HNsw`YZk&#j@yJVq4I8i&V2$}aJRvyp*} z&@ z<&xDIBr)kpy}?bM18wq~Nj{rLY|x#Ph36bzA7xY@^{|0f@^`Q7{dt0;urpR}AZ1Ji zHE>lA!!$k;`#aj7Q1Qv=}5lD{4WnIx*rgENOk z61}QCvHC5hULMxC9-U?Xk;#~I6>>H=-Bsg#G~B08YjY}drjxC79}`HWRx)RoQd_Es zICN-w?6kMEG-bU|Ss8Uw8Lj%z{fnVh3`lcFb(xmJ%aly7I`qgx7aNK=(|tdPn-mNk zf)GgNFS>K0tr;@G7$&f7h{Tp1p51F05clO$ZAl>~z8_?Y>UPeDgV*k6teY%{o`g** zPgCYdm<(|r$sZ!LEJ@ZrsC;l^WDcL0X2Z=H{-=SdCAd;B&5rSnXzoHIL!6N+`&Fj1 z^kljLX3iY6w~X*QFk3=bDb!cr*4k+uwdUQo@2>Fv1RYEzfKaYItZ*obN~Q}tWtU9r zsLs;03dRAe$Hk4bj-t!bnBY|Gm9MBNDqokFsY|jCln+E@;ENoVtPcLd zH_BCYVze)d>$*_Snv&lg2h#m$cyL}b!K1!!+EF;zN-q2P&!o~#U~1h2DkFuCPJ0^; zGVStT{LB7=)>Kvv37^WmUyvfyRW6)TH^%s_1R%tmR6;g!8-*;AozpUUl3pJT(LaK0 zsjvWm0+&LXL)+`zxN{nkNQ*Fd+WH>asd&4OC8Mg^v=Fl|64ZW| zJ*YDISsOE%w3s!W(sbW)3rU9~P*pg41%<0yq#_@r?UN>Q8gtp2A+mpiIGN% zlkwqCN1V;^sVYHb9Bj?0vEXV&5BDl1RJo%S%pwEcP>=#(uOSTi)|SxI;ccy=2lRN2 z*~v`Go{dh{QL6)z%|3*wodD8cd0FT>)A*Pkb|mHawL7Uvia+j1CCJuY&yAg(k`1&j zmYZ|%?|=J;sb$`6bd(EK(vK@0q-&40Zs&YLMMu8D0w@EK732Pe-0!-D50pCuazJ8m zAgfZI1o?`ziLYEb#X?&;6+y7u=g=6!YH{4b-a`?lzC>4?`<1T|(fcz)+FYPW>jZ=Q0@{o|x8;_!0`~#*i7^MNX0bU{PR z8*+w7jXI)B0o*!VG9)YQ<#ck5do9MLfx#aoUewa&kgqvO5Md zC(8Aeo+s>yO(UWdF!qqft~66126jR|9vaJP!gLUYh;q7UyLYhD0S$$m!uR>=1yf;~ z**#TLQgjd+HT%F$lRl@xkfhsI+!~4hOw<)oO^3Oi@hyin_tZ>2Lr2{OsCm31;EdFL z-V(+dat(NBLRSE0Y&nxnymTY4a@J>R(D>yI=P1qi1=Ux6Mmx5tkzZfstQzHrw00hM zbNz&=IX0XOMf2M%6vg5*1biQWN+cMSe6+a7sBat`1yi##wal0#tlBcfhQ-?{a7KIn zi{O#Zj|ImP-CGO{iS^w5v$?m|+G=w8%XbGmn-mMGB0ha7vsE@6G$+<|El^UgkSLG> zz>1b_yj9xmU9cv4x%IGI!E6X6jpliA!SPk{z7R- zLJXH2r8p9@J*qrGio1zlnGt{3l7eeYMrAtY9KNa|Hz_Otr<>|w@ksVz99&<%OMg>G zh9w_RTuH`fj3h299YtPCl zIGs<=BP6&=VGQZ#=v-W6QZ^2TfJ7*S#wuamCD8C_z)nS168O-k zjmNGM##v0_a5580ZV4gC-k-9dEdf;LVLX^r^F;wJTYk`Kl)E6dE9j|@zHj|J)oZ4D z-WaJU>=emaVMp$#*64l$KEoPv@IP~P$-!IGpLh$(@m8X*5bh*2%-+$#BmeCq_BFOF zn+uu-kp)f<{or}@z7B|Ehg67!KB{Nw09p)mze;9uw^Gqc^5yX@Io6bLuKsvDAUWta z{@X01_ozAQ0UTcG)yNiW!X>n-PRYqA(J+%C)Obsy z=%W)eTR0h$P{gjD_(}!KAcQV@ajTFms*c6!cUdfBQ+~h8O^|nzDLa;`Ssde~Tz%Pq zXw0;7^H9!MTjUR-lm&IoZoX9qPn<4{KqnMpE1`aK^vJaSf4=6XwxW(iu`(eJFqlks zM3-LEDQl<&gF$z3b3bKT>v|Ceyxa|0J@7ReEHBnX!ABk1yK#;dRk?@o(gwZ4^XPP_7gK+nZTUB`TSH+IqJbhZMjd50bS~Gjx|sc@E_OC%J*^4K=$v_& zDHto_b?0HsNB(&TR5J@D7i8yPv7LguDbEXz;K;l%p&OYOMq(@#)l}Fz6VX)oouQ{D z=&0~xJ3460XZdKO44vkBbonB8maaRdZrvk61|+g_1pM+$?z4_pXv&PnA;ATM5d_gg z(QU>JM1R!DiB?$xqvfSE8g?97;-fOC&Nw6mL?-#$5SNWEsr0VmAR&TW$f3;#o!{UU zB!=-(Ds*Ikn0+Pec15?#lU1tc+vm_f%DGbK3cp%^7C+>H)W%{!D7KN6Wsc5q=nNq2 zfhf_vvaHl{Q9$*djeybMf_o)Z0vIZ1~}N)&ET)>>-~swl3`N7)?msRbv&32O(3 zB1%)}hT1A)%C;(2wIlmpl6{}hS`}nDa&B#C-lE)*Q{-Tjrh~aP^ztW!v-)ET9NjZS zCwLu>g6-%GxdfmF3#YCjRsor$;FVa&o2_b&dE19gZMbCGnpo8@OUk@uOO0Z{ii46N z>A9o}v;{~bkYvBGgpyYOXrNMEdiqdqwMr30718NDR_!~UVdw$;@yD1UZ1%`9Y>Bmc zQH?2)QgnpoU{63h9?|C^IUv%_2 zsfy^WmabZajrLfsFJ$X=Dl*|29kPra&vIZiJ`WZgG1@7?PZ{k}<1uP1qdGZvWbKI{V*|& z-wZ)R$r>TH#A2!LEUisa#sIxmu1OQE@C>8tH;y7GZHmA@JYFJdp-pD|j1`X2A9J_j zTTgv?=e@DXd3D91LX~VUe%K`-+8JK8byYIq0}@coLo6}bvV5V`sA5yfpt#;hZYgw* z_l)q2F68EACgehSkz4mX@|_OAaGc@E&Tom`xri5SZ+DztSgh2O$jzRC=_R1gOU#*# ziA9sP-j1KO{o$0;*0PzpE)seI=d8|W6t~v0rbv}{J{c+nSq*OlI02e^bg1fN_*0N^ z!jn^V+s+r>>RN-ZAMwWFc+``N+cU`#rtGrw<`RU{OBFfWg&g^z&En`4D!H*vRgxgK z_r?R$U)nI-$P9u^ytE8&!$Hmm&q@HN-3VsUHxE9ZwY{E{%{H_`oz$R6bs;Ke-}{>! zTp=YC>(8eV?l)>>%4|#ty=sVnK#;MxysG|Em%~UoZrHR&OiBC~gU9oaX~7FYb9)^O ztE!(J*U z8Nri{Cu_L=Pk7<se8UCdTtoB@2$ni)N3~(ZL6#N z89FrOPTW-=;&^>-`e-^Eop**u?uTBtd~`kPpy!eGxoabp{eBBAo~VQ~M!Qr3vRB+} zvs&D_GF|NdCZ?OwS0zu_$jt>Qa% zKFCV&G2)&_)G3B%Z?Xqok?dpxER7V??qJ5sVXo%v%hsqbsdv;Jx0anmV>k{6<;)&7 zB@_zP7}vrkkDGpNu8wRMHL)+s|jXn3gF zs_Ezl^XqC#+O}dwPu|?HZBehwcD?vaSz4?@m0o&p-^OS3qgR3Onv5pxYiczx|EZ>? zI_u80RUp!SL~NO)0(nC(`Bf(V($a90!Q}dCiypCMJin!J+B(TAPy2#{OFM!Gam(k7 z&wMT~ZRPfn7bY)M8STarptNR^gW?BbsW}QtR*WftVga%AnKEW)1X8H>j3IEm>_X-` zuZ&J3{KPg4l^TaLq73cPXB*8bc(ie;)Uig&gMpi=&gO&;&iQCDbD3anWXSNRZ^R58 zQbf}ijO?}NA8Naz8>ocdd8*Rq3+~f>wBV%X>ZcXEZQyWmNtNPRyBi3@ZCH(wx znyDaYQ!53={Vz{+3y2rB)q)@m*$SR#)kdsNjn9F#+6$G`SWNW_kTq%xm$+LXs#z5z$3PWvPd*}7YH8WWF8^$CrK}+Q01c?dijWiQWUDPf%c2F({mx)s3#cY{pEf~xuA#o(L2JM5jN!iV@r|0++RKl_ z?@CuVnj$wkYg`33;~$Fb4w`@DHOgSS69!3Yj^@!4knXtuS=`;?_PAXZZx7zcUpa?A zZH`na+6>(dg3rh$bAr+Pg@iTqcqsb*j32INYhS8aGxF-(Rlm4T`h-c(} zF;dQ*lSZ8H#vZgU2MLVZR&+8$&oTZ;M@?k?3K6Kt6j_FrGlgd9)%?06qCa|Mzfx>v zeqD(yV>E-~Y@|hYh$5Zb)eyDtGY>%oaJ&|fkmdBUn@<+h9?h@&1WZ~pn^#RSCAnTqE`l;QQhPmUj zr)9$t`4%JJ;GSMpr!0b|ng;Fuj@)j|SW~Rhp8sHz*GZg9MWQfW%Q1 zF(7!yU}QxkDiKBzeO23NWLTAdi&3R(29}*V7PnQC?#4&PDfiRV7EdL}jI}O4dqt8%mS#Ty4s9? zaC9BQS4G2lYfSSrAlO?mTXS&`Ot@iXJ?T5?Fs&*n!;I6jT%ONZ>QFMMJM|JkOZ+Pt zp=!zbeUBsZ`NPU{B)NuGODi0i0-j)L&vf;GkSH9NzQ(O|>kCTw&?SX^7uBzmq^$Ix zxNNDqDJF4{~D1(L}rEC7}KbKJ)V8lJTt zsyLgqzu7A@#G``bn5vM3MlB1Db8}%mx6>2uaw>x;hmx2LF;Tfg$!g~Z>?U6=30CJJ zoe0@Z+4?P;`{38h)M6!IFg3G@Z4bhu9NiV84}PuAe@F@APQpG3Ik8TLAd(xMeLH0x z*2|FEiXh0Lv|muS&qZ42!p!;3#*%k2r4waYQFFEsHs~Uz5(O%|fP)gd#4tW_H1j@w zBl5Dc9X_I5&cryJ)LTw>l(kux0yP%_(kT(|L8Ky=&RCia#V(Xka#7V*`%}K#-gvL- zsY+eThW(w$I_d$#BGISL{St#tw0v6 zsVC<<1+wfPY;HE2TO=c>^pZtKa_T_NqQU5?7Hl0+m44Q|P-dF$W{m-l+v0XndZTRg zH1gPukBnl4w{dF94Xeb#QG=TH?8uM`I#x+vCsDz%y_TVYlPI8^4V6$wSQ9EdBpbj2 zu>q`@L6un;i>t$@z%66SYHdoIq7s3!X`TI;Gnns^Ol-C;gCo4mn@b(2^WTCf41O$qfNh;^7gNg%!;Y+Z>MK zUYE1}&ye%g8JjOy*rHvR41iL;M-}tpx&jIra>9cYC%n)OXH879l$S+xDS?t=N?Ez$ z=@Y$mPoA#ZJwb0-x6maEILF-?ObguO(R)aaFELs!k5|0F}`(>TA0V z=dy7ELG1t=QuMMxHX0mfN>e#lt&4H}#ifp+&|;LziLcYNn+tKSSe2XF6AjP0NWdy^ zsdZbDnW@euPey6BJM%?LJY;*h3GX>})Jhw&h`uY6{;Wn#*Z%+7=ZzpIV^+d`L{ z>GldQCV4^iA`b_N${>wj&d+l$YyRrZ*RvkQjTi4I1IlbG05ejft{km5dYU1{61Yz3 zu}6pLFdkxhqFBm-NnJ#cL2VRKciN@#WDJVTWWoc-;mLRu5$ZJ=PcTE$2|mKog5%M| z`BY=cIR7_qxT_K;?kp?bPBKkiJeM6!ogdFVCTtT^Vd#g0Bi^QLaY?f6JpAE|qw1I(cMIs>*;pS3iz+!!0dR-&W+JK|9BhOv=`a(S;(Z>c+ClR}GYuLY(~rA{LN5OfzY z)wkpTzb>cwQt#H*-bcfhG~#;j?486VJmhM6hAw3Uvaa%>&bo}x>Mj!rX^mf4B)M@$ zVRs&H#_{fuN{dJ}9{XaBhe1ZJBXp@Vl{IuQt*xYGvP#L0mRiflzzv(YUgtig#mX>}0cmV&v zN1eUq{=s&;%0k2Z8BP4t#17xXNa4ab-m+1<#RI)*a4|_~zT&h*ve~mYHKK@3$QjbXPV2t|Scjw2 zoYyaXOg_Kx>+HO63^$MC9UI+@H#l}sl3Y+^&NII&D&|tM136IK0lA;*9^{>7)cBV5 zajW>R5ka%HW&Cwu^b#6Zcj_x2vne#NY~ZF%7_T3y%o37>DpN4o6rsvOTeHcZlfGH% z(SVXr=CX#Xdb-xiqC5XZ+(kX!WeL-SEBi*QtOD*$lj09-XjejqeG4e+P@e|qTnGB@ z_Z}I@{-UiaiL`yE6{x4W7&5fl-oaSQdHcvZ%f`WjK zkEZG`rqprVV6^2%`eq>H5F^)s-IC(~eWb6v)y+_z3{c}n6dW@sM<2_pCPTUq$X#~I z?V{^&d_%p?x=w$BVZgShwet?T&DKe9jrjUvLnLkMRB1(hLs`T#x4h!BM~TE!E<&Pw z(%X9n&3e%M?@!ImcBlDY^&lD^iIPV+J)-ouM@)9uU0X7yvgJXSS~39*M@d)sh%hl# z88e{%LTmHdhVyk-i$2y8VJtz-F=ka@2I7#n;?jCvwu zCzT}d$uvBfJRGM{l6HW*9)0N`af!-F|9RBO)Uqi=94FnqRR~<@7!#B_WyZ2VAnAXc z`T}v>^!a=>n~SRUl}#tRii}psgRUz9R7p$^_F$u48c|dRUi2SZ1{_@?=SXEPwkqi0 zWul=dPR$&Dxf~Nqed8kJ_n5~!+}+C1U@>`(W^(0cY6R){8R6*KSm(b4d_oQECw#hE8k+m4L$nkI3{ z1`)mwQSW^0CG0+hkGAp5-yoB5(cPz&2CL+ z%nB_gMk6H%Tm6GR*1+g*i=(+p!(5gV6ccB2iIuI;X>YjtovF?_V+FM2KlwCDDXBV% ziIGl*e|Qz_Z|;5q0^Z)+*lePSwXZKQqrp525P4qCP>45ZnuJ|Y*>ggiS}v_9J{1r6 zX}QU#RAYo7Fs9T|eI-h1L(Sc&@w7i|w`R*kkt8>Ly z9HVNPEjZ*B7}`A#|2Zn6a6u-yr<8psziAL``bR2#=V|1>5ka{5d^?I5MX*-nGpT@yEFrs<0Y2v!aoM6c*$z0fLvOv!oKHlXZaie+#Vqp zM$BO>K-i1_#qn}apQ@4{=m;!plWOO$h}4n*D=x6l0nsm2zmd+8;0*>Hrz~dT*!7)& zu8!zvt3(yMF>D@6#uOKU-f2B15@cRmFYOeXc7SkXM|D zXazUz^XnPCm_>?jLEADiJJj-OzxY$~=FBK@KB4f#g0+dvvc{kjwdavX-j!N~>Z zp??BDkGN=#q`Wsy1v`o8Ce&Om&~ZAXLbLLc>)GtQ1gvX?hJkKst=q~^;Yl)0C8PTZ zC)~b>+4)2$){Nz6KujO^j7LJUP>i^M6RWe%O@ENFLzz`DaHyAr=p2N$9UOygWCM(r z!num9r|KxEDCL8l*5>Y3GdP1YKFxqGaYUV=H6|)`bdHjYVtjzY*u^`tR>HrV&^0V; zn)F|{6Y^xM2fIk2c7a8~F0QYw@wZ&Ait>HIU$6x;>Ozt%PAH}dfI!uiLcUy_bH{TU;JH5nW~aSqA$qRM>bWi`Tw=%axTtV zb*k>GYS(_>=cy@@wx ze>bzT(zIdAOXZAyc$Kn#+_{_LdkWU34BK&@m>S}WC;EuhdP1e!hf*gSPXIfw(}gn5 zje`aoNu&%Y#{S)Yx$-YF-v=jwqV#H*HS5h6>|ej5RIxeo+p2MetuB(AxNdd)5fq|N z^on*tIlalNbZ$j{^f7ZX)okg<0wyJ7@Ll+i&4N=VcSu;3%Aa!@^Zw+4h(NyJ0Cn_n z3!!Wfm>1a99iBXwcuPHRi8my=ZtwkSs|{yk!RfqU%TjeAz2^}I@Z?z;2)!nFo3?_T zjS=7<@P1Wu5f|Hf2oRYPoIjuyVLD1H87~1pS5t3WNITfDEA@2|id`m0zS8Pf%Cs)e z)?7LY+Kf)3Ru?%6khWfALd#Oq*AYxom0V}DIc7C9dx*d9DN=P|Bp)cCjthRk4oTB) zKY3p$7{VMx?M4xE4f#igC)wSYp|ujD(@lPzIpRU6tQiDxerTeGd$O9&=1^(&YI-Xs zb862}Gka)NA#!{4BNDtpHXw^2?WXJu>IB#^7)=0e7sRd;N#ioIsMr52T_v4}A318s zauzh*E6G^(=&O~JsCT|G($-&zE?^-KdrV@SwxvGf=VCY z2?HU$gt;0C5=4 zTt8XHZYRyNMIqSb;?LcBt~ z9YGDlqD<|{YRP~lL zzC?B&ENtH-8J?egn-3RneG#j2@;7PC>S2B2pHQ9tv8?_5X1EFa!{1as^s0;e&b?Zm z5ZoQ^F(>4cW7B8JoNs!0$`M(V19TEq(E@f!=lI#=MM)~sk51_>bwT=xK2k9%xuOHg z${gO-a6T8p z#Nf|BWxWJC&wV{8VwZMCjbS!V*Pt|kGX(Y&$)z)wMT>n?(~pUL$ErY@2Jxr!xgXe$ z{|Fud#|b0?16nZ0|07b%qhw%cz!)?A)h~1Zd%^Kc6qwEfy(kXs(TFuUaqTq^04-lL z&+Ye#BZ=C`UMH17Mu-08E-60FF))Z|>@TWo?{lphlUB$wx`AFvM#-xna16$Dw9xjX z`=ae>SFV)V$)Vo-#}HMi@wa7xWbdy7G^C+<h4rF+j&IlHOe&&Ydf#Uyp1bD_jFtX9!bOGHeT zl9wX-CDGN^V9w2rw`|81#(HiIBHcCDmQ&+m#wM>nSx0xGi_H*(xj?Zdv6 zB6b)Su^h*el+>i4u`cMIS4R;q-sRJ*wx~EZtf+P927H~QyI|Nw(SP)SS)3CXhYYPS!oJ^e zzT@cGDLpF9w@tXPTfjd)T^P1fN!ZhsPw#_f682=knuBVBzOHm@IpJIkwV=;YB+(i2 z+GVD|QnyQ%1vH2;SsE!U42N54H)>zY_Z1`@8?fsQ+Sx;P!nGxGxO2 z?!AYkdbYGvW6j}a@9)I)m`MhUAl_Ey6I(kSsO=Z4yhX?p0;xlIPBUZ{&inLDE8^yw z-StS#^{Cxtgume8&7}MT&(*m6;#!=`9eO;MUMD~60s{}o%? zFrG~X#5!yVYj5IQQWNXBo@gTXAq+Yr_rJoefqAgGB%~dgt($X8DDmQBJ?uq3IOt@5hcI!4O*^vs2ZblV$4`Hp?$g?8OvGBFtjznUL7=3~8g zOgORAwvf+8%^;6wsj0ZfA}V%Z8Rk*_W18Upq+wz1 z@8OIL_nzYEOSmV^8^!7RI;A1_FOr9jdT{3{k|yY-=SVWPOKVSi$pW(p*GiwX>3x(DIq*ETZ)_eTi<+ z{GdmS&SH&qyRiArK;H?5C35tAAoinj2iLVZ_yZxMUOVAU{{J$d01xs9v|SzuYR|ZxTB+ zqkw_T6MS_g7P4b`PtJ7NR_=3QLE1A~C%RkTM_&9GRZXq~wV!*%Zi)wZffP=sZb`38 z%F4~f7qcJ6ivD!fE$W=48FIj!M8!ULK_oJKBHlU?+>9HtH5N<9!9qCtpWD73BeuIE z)@&m>5LLZ~8|dUpYY1QcXCz$!6dm@1TB8)%f5Te(PT`!J3BZ5L3JHa{?xi#_6W90k zOhi3PYec6=Lrs5v#=K0&>IxBv_Z%u)lotjdXclldS(~DfbEJ=>T$EaT!U+#T(R2dE zA%+o+e%*RCg#}0_5q!&>82)aJ;!wc=<30paw@8Nx+b`s9OTjW znHPYWwUSa@e-~mEkRE9xSdJzmbFw3h(}%WiXMPhJs{(#V)5S7i28RJe1OH;DlpY=) z5Sb}ntw~(&_v`)xJ8aAP-VTHci%@A|jfML&^o>Z%u4{p;YIzy2u43neVE^+nL}WX& zs+C8wfua4NlT;o$9@wFUKr<~q*hado%C@A7$jC?Z3k&|C^k4`yfo8tuxX7Q3iqDLn zwKit;Gs6K-^eP97;GVb`ECQwZ=#P|^4@x5$+i(#?{IdRydtSMsThh7mGqn?4gIWW0 z3gV(J6m6cmG+X<*`IU?8+E9Hs72 z&uJE8Lbb|Ni7k@nD2dvv*?;XR%A4eC2a~+!C8H)~pry|6F0}`LNH+8aj)VOTZrYhd z(_Anxz9*RAW8M|D$tF|rypcqAJ7NG-DADgLlBIhRz*n5CXBQtVnk6c$P$Whs%fPe1 zPb_I3EbNrGQ4tW`eQgc#db1&|0&VB5I!Bs|fBii_xM`DGsERmqVBz8!M&&IF%?BAo z)uzb8{8AIi*sVLT7!*4xIe3IhRC$*df<9t#$^|7gSrT=;uWvGeNh@-?OtP;lOk zm%W1-DLuE z>sjCxpuFzqZBn+iZpjf*>2iAv3(>L>x6WAog-LuRATO|mXVgj`;I>*!x@FVM>ovz@G?F;(%ix2(V ziH6Nogp9Qe&RIK($p7?k9t!>70w7xUnkZ#_%h`v5w$dz5bzmAyzyi-al7u-I?%Y0}+|X!ey@=8{!>Gpc z3*^y_x6v_v=hMz{VH$CI`uhi}-7J5*fpbsE#tiyTb&c_!YTuRnin#XI6!+ZmNg%)T zmuPP&lNH*V?HWRlZtjJExxrd%B!zi@?nIM9NvkINU}Q79P3tu%81f&yf?_{82yEr!|1KIQqyWcpe$g?ae|rODM8&O_K0MK41@ z1kLhQwF;R<}7ZMouKTfK{N{0E}WI;Id z8q!K~Vwh^SH5)h&X134%&cd3=81~U+fSIoq;u$J^d;ReT&{iA+2iZ8kl@Jw44s?S zHW$*ue1s-ECr=Wjs}A$`EYl5rQ%Hj32Yx#_T%g%BO=2tOLV86euuAq;pQ}<|fl3Cm z917HhR)$b1;pXLIu>Km4m7xtNR1au3 z*g6TBWTxdRQA0WP6ahgswr%hL0@<KD5~(36!y^2avlR0aa(7B{ zZ2FHx@*?0&c4OHJTCU>`RWSLorE+eZE9^B=Ne7D+hZTsH`D;ysB~Z3iV*h72_~sfI z1GhO9oKlj~rw^Yp9X))?8BvDo)s(HoaH&L+@kWTT-tccy3nm8ID!WQXreO63>jqL$ zwF4qzd>H?s>^D|@>C+jvQhlFrXs-I7dkefDD$npW!pEY|%DUBppoO2t09HoEE&=zb zn0W!(CvBUka3GuQKW~46<*@4zMwg>bnPQ<8ITQ=#pzVEXDFX9??uUWCtD{EFT*Kmc z22}t~CJLm2({{`iqIXvw6%FB5%+ght1TbS;DXXj*zYHB{rL`3EVM%hD3bG2ec+qbl zo~|{f6%S{#D+F@&Lm$2^v_ME$zbFoJkdLEEj zCvzUvJWk)#KQ}u4wva!McQ3`|U>^M;=&c=FU<#nVvvacnl4EKjNrU!c_-OI@GxB@s1~KTyHo%+<=(r^lrrOS5$ZdVl;K-I7!5*xIM2y2AOu&9Vb@y##P0CsYk>ZPJ{UIb)v|LC3*f;8=Mr zAO1$|v);1#pkVLK;A?#EUxD+O&O~Zh*9y;rT2JghdTQxJ?1A!>jyXmE#$~r(v!Nz} z)W*F)_>KwFhac8RdD#fTC4c@{xTXvf{5y$)or64et*qT|2KGhV-H$<%yn#xBqo=CK zA)ee%8fuT@@b&0i=)~NBq<+n$M?~4ZW18%8Ro0(jI=euk@Zy)GH@^Q?yPP03n3JiA zg3T5aDM})8aDkLWN5aDT)0Y@yiUcpxpygDUM$&>`Z5GrRM)`+0nR?Rdh4yW~CR^GO z=7gVrXmV^YP%vz{vplfhw&f}jAUx6dA4=yRFs@uP4le8rNk_{^w7<;6xq{z7CO{_B zgH^JR|HJ6SBkF<5J{0+Xh)()nd6v0ac>_jx9Nof=m6<`ikO~#j1aL>RSjs{QnmI)1 zWr8`1)ZqLn-6&M2p=kx>Se8dc!wjP#U*+zF$`KPJd+3hfzH5&KEd-Z0y*B>aK=4?8 z{_7i`O)Y;nVYa`(8M1)48LwNm{VgF0+}heQz264_Gw8fqRyQr%_|5Jw&&X4k0Djio z68HYy_`&NyRs75Agg9~7T_H$s54^d}I>;J4THe3wo(70O5S_M!|93QJul>>=N82 zhj|9rxFUCxXxg&AD%jx*VC`c!LNnGYm z>fjIvNo^roCuu;kTcL)NdG+olUJ`U~Ikc@qUYXzexw}WD;z7OT(alBc|1UHr`kc-= zet!Sp0LJb6)qr1AYP^rlGXj5s$TaTdn}sG3<{3ouc45o*)&LjFHLLr}m+)LWelyO~ z?n4+k@6OOsiP}d0k>Ke3bn7RU3v`qeD$4`BTjouh&P^6imfVLS2C%RF6~~F;gZBHY zP!u<0bbvjg<#oFiBl~)-J3aS`m3#1Vm!ZkUEPAX#FQm_^1N6VSdpp1QzYgh}Ql^ms z-&+BC-|fJwV3hY7K*2WGcDwvQZ<@Luo!zf4Rz{Ae@;~IWFOy&K3kTQg>qsVpcgXi zDwwb(y42*AKELdA10V%@9Fv#Y&e{>sL3 zD-kAWHL_h;^L&ZsnCz30`L`9~n^4I0>6z#h`7Bn0v)#CyvZZctQL-S1=Uv{EUW_Yr zTeMQw@{LOm)ViYYak==jnU#4>z}~aq_yZHfgaLUPd#stPC{P^9na~-xyZt02c~pv_ zjZkMfBl6s*MFxF_T3&dI&Oq)x01YkH-ZSdq>6!u8`$6F6#w;m*Vg-6i!5vh^?Z-+m zd2@s|EI5IVhB)5OUZl`w8(-Pwf?4`olWtO;SX{wH8-)X-NY-XeF~O%-Sb|$l6JROo zzM?CraGj+4k4mI`t->eKPolc76{O|v&hFxK;CjHX3|d<6664ED;n9MI^Ar33mQ{p**rY~ zi5ZU*M`QWYy^4i$enoX?7VD&TmLkp@a`=4MEv%%SnnXvE%EBKo_y4i_sC<}s~=Xc!7~iBtB+$J_#b8idI8LYLe}Ub zVa&-k!c^KFL0T>P{78(+6%;wYQ8#q%9f?@`O*vy-b8_iBUD$pk@d7{O==(!o3Mh z^WP0lzIV6ow%J@z?qn3Hd%$ntns;|)5h~9Uku(A|)sh>pEaPd599nnb#WW8q7?K(Q zympWTy9KHoaYntnLf_Vd!9_=S;HPVIU1PX5<}DpGO^utf{&W9sZuwZfnnO*6sJ{l% z_c{CTYOj8-)y2A_h<K>szh7t+jcMDsedA^nsd)jYpGmVqff4KDczMJahS>iGR*TWp?{xI) z-KOQI^HF8HJxOk&9%NWkHr6v;S!p#fqS6@Hm=5z^y9!4jiKsB5%zo|h^qhl z&jmABB1JAdV6tvq>)m^ zYWcsRc$77~*!}I__`&poLo!si;PEgA{FL7dQleH>?Zwvt2ZKPz8&!tOKp7+XXjA8v z!9~c?k5p2~?Ym&&#d?lw;ZQ@yR6vs8GnA}bQjBzY!fPX;ORVai`5o9F7_yXMDa9vm zQ*K;!MB#5XTR?UQd+LbRg+ys^=R=@r*vvLBw?Y^rXJwH8@|`LS_jUR5kLAOzz`&L#TWDk}&s@fXwa*g9Cj96tMb)ZdzaV@bX0`ZlZCB%EZm_Z z5pp5^`D`>PW!LWNWDV6mbi~>-x0K&Y#mrPeK?&iQZZJdK%v>*=+)UF)gP^6&(iwI( ziAWrb5nC0u*wxAs@!m6XwXmC%08ymi+sfdz3zoV-6}UnY&E)1lhlFO@_-Uh5d0@x` zh!3f`sMxTWHH{8das4Hs@xhZ(D=P-_E3<;~^$&^*wmPxK)tg-xo{bteXT(D!6)*aIuXsFGjYQSH4 z1^mt2-ZJ7%H}B3Et|P{eiW%LpAPxSoe3$zje>q7@$qL1#Hu4t}eovx_=CcE!rs=9v zbN^>y-)-41VP~+bmN9$<3p%p8y;pv>%np9$6XU52s=SfUbU3OTwur>^H{`|r zMNoU`3KV4(LC_Jq_=Y>!+V4yyQC=7pjUaiWfQp3-Iy){s(FRVs<+hH1yY%BeIqTRF zq)TKEuR3RT2kDnzrSqhg&jGx_<02|l_~rjtO1*$8P?NFMded6EbcNw<*~Cj!s%9Xb z8yb1fP~^At$$Z@}vgL~;?fYWKa#IvALG5LHNG-`oB_SPacQBmXf%ngZeqW%U0FWl9 zXGcbGT&)yXY^d1~*eAs)t4>3GrkiUa}^_v-~Igzl2D0L_e(S79DUEz0c*~@QbQTcG{xob8l zdy}S5go)|t(W0bvYr?3035>gpdH!XxF$!lRWUMiSI!u;jw05-1Vo3STwfe>Ck6GwA z*C<+eHcMskJGKN!BpG5*DQ?O(yZeF>>xoQ<>=>l6vMcIWcueM}z4g5iGHPQKAd?uk zav7qmJ43@VG@~~*1v+l=r60CcadPwN7h{>{rE%({e&z}Ah=&VbV+z&3_e@56U+h1O zJcCSQ?mQx=*bUC?w;6%JPk@-#ND0>UZE zg(PB7hthq*6lOClB3qG}L05wR0)mq3%qWn=`ix>F>AaCw)Q5+SK*Q94K99tJ3M1^z zlN+PQUVm78Yn`;Nb`cWPRO40^Ng-eE&^#N^?f`Ul^2cp^FKaA=CiVvN9w4D_{h%jI z7O0k>HV&Aw#0I?#Ob$S0Xh+x1+vfV(`e(O;4BgaPMI#RFQP_XySvOQ!26PbJkgH|f zTujH3IS}VAJVZ5C1LE@z{5U#f)s96M6qbez{VxPkv_;sA`CG=2W$8smCq$X^XLgi@ z3Ok-0x}O~EeT!Hfou8)}_G*MwmTWvMZ7P2u!`h*_3d$6rU6Y%eQ%UHskBb>xt{;X#gCtebsUz zR@8SFR9ncsq&%oXc3+t#JuB2{ehI!?2V)^Dj7Q6v`o#4{E38~Iu}ExR;>as*YRbsY zEp9zFJ-DPj$zeI8|A)0_X^C-{zfN0*j9y7jw4{oxbaoKz=Q3X6iIj=EtvVjl$-Z(H zfHGcra7<^j@3E{2PM|N>jW^Z$$tt4`xU~dxmay;KZ2b^ z&&NM(eMDZvln<45YL4EY9&A`FWJrxRcjd=1VXWo_b{^+3`V)cEjAS7-k~MIkD(lvk1fd*|MChz-YM4T zMFJCs^?=1b^17|a)JvOM`=lxMsJI!11PnE01$WN;Z& z;OZU!BQkWf2nsU%=kO8`MN4nsiC(6QHQS_ar&<2Iz>TY7TU(_=<4!(!7+*C~)nRY{ zv9@VRBQ4LIdvauNr>n1C%L{f%F4tbi<+ST-lQCeNL^cRjbm2~81=M304 z2nqEbkJYo7Fnf(|6D*@@tC5keA|5x^D*RWcIqOOPx?y_%496#azZqK4r(9CiM^!p{ zSY^YtD8+W+(k}VCz`vGS*0rR9Z|&dw1LESojd+eLFe$KNo4(FLAh8#*Rw)e;ihxl1 zG;Io@wO(23Go1Z62WPdT+*Z0{A!BSc#ZAE}T@k8c(Pr3w zbiU(B3bfc@BoDQ62t&M@uv?ZM?kH%PVfW#bdWww8opm=cb2d{<{Q?YLYXlt~YH_G( z91!82qgshz#d^d$+YsrK5&DaH>U<6x;=t?NKV2*g+0`1QK$FI2?=Dl@oN~cT&0B=AgEs;Hg25UMg^f5 zv+9%`(RP@py0>FvLhXodFTvmF`XSSCnlo9P;FZ4eCxSAd6OS|Q@uGWo$4tUe;pA%V zF6nQ0o}ovwGA67hQDIg~Jf$C>uS5li2!XPa&~IzU%L4H7;=eU_8Jnu?R=eQplzp}T zyv$J6nGrjTp}w);rt{fl7dgZa3;pG6GLi|2CT-Z|ItynE^*>OcQI7bc`eJq{n7kC+ zVvsXiKFl=ZvO;&)NxsFs!omx-38!f{{XMzLLpB`adF+SaklJ}T#MHm$HJ#gs!;eJN_*0 z;0ppdAOj&0hveTmA4?2?c>J^l#)4*?I@IYm@nzD=KDSt}9M%(;2WoV68=})^0)Vd( zRS~}$Jkcm=GHHx{v*7YLen!HoLj-l4&eLX~4z0GE*IH>$aLx?inZ0I!|B)CnfEK>| z&INo{E50;Rx!Ngjkc&U$;3xJr;RZ2GU=G=SLT;LWC40^+J5oU)v-L3GDQ3;3hcUbk zy~n7cZgnbJ8snK1m%Sqm34XBRpnTU&Y)6E#(6AW%19 zZsq^{Ts>fcfWg|RfPlclQGtLic|Y9`S`+%->jZP$8@dyYY}b#y)@{9-MK^p>$nq#h z*IGS&(4e)dseWGP)suZU`g&ME1Hh!l*L|8-x*xvkQ^PX`3>kO8T>NGr&b+e35GS*J z@7_-QPaCUPeZ#q&6Z74FE>&}y|BR3T@Y!6C@L5O8i6O~DEmnqmO1bpBkZPoCF=$k) z1n}(KsiNW8R@($~nt8J=5`?Ljd7((XUyrcdS5Y_zp{P!xkgpsg!QQz{H%q z5m;sAD!9db%{$VUr_!7uzhcJ3hLpSq+Itfv=v;O!IT(IW)Y-m<{+ipja(9+fnvqc6hyp7<<$KX)YDu^3&3n}ff94VtpUKrBKJsV z#5?)-l+(t>)1XT<{ZWd0%uD5>=c!26;NkpTZo0?v^cFA6OXjz4ZrpDGtaTB$I|K+j z_Vmr(EN+bz7B@aD_^XP^>N}*|SPPYt9}0d(r{wXf6YyI)p!$x-+~1q0MK4XRbgH8R zk4q4dJYD-B!=y{^I2y93tAM5VoH>60kc5ST#l;Uv&n)(+v^aXAPfUlWxOvp8=ZHnW63^;_$2888%bI_{6$+}j2b_Z7~m z!ANQOQBNbm_3FC=loQib{P6Yq zqC5wL?PMoHv5*g3Xb0Thk`Hplgfu4D=Z%5+2odEY|@`vLYDmkpK;+b;fFg?39& zc3**42qlY5fwT?wQKhFyp^!8jHk@=+^jH^}M;$e@7SlpJmN1iAAlp$Ob+JjKhmi0Y zyrvXA!T%d2!GQQ)M$|82i~;$Lj95i<`&Eh#_uKP~VgoIEvM2BKG;-iXBIHng>1{Tx z`~k&Pzi>uo$uokRLFuHZXhshKTpK^om0@mKZI0=JlhG5OVN5LHg3KQQKrc&)0)E5y z*x#(yjiQQrteJXrwth>C)|Wr{PO42x6gJATk>jZx+e*tV&_cj0YXzQZSNo|95OC7DaEb(=mx zTO*uq1Be%pMAm6P@E7)nK)az!nIejaXEyK~0OI^eT(}1F9{l*QS@suYA=sd7|4Mt! zow`Z+pzg>$WPaXTI%xSCxOl1$^xvil2$0uw6>A_BD`X%?E;TMlln0`)3aIlpuVgxC zsQT{YoJ}FAYFl{0^q~04pn_=5)!#pq`b&OAR<(Q6H*&f>Xcf~Qk`4yt71}wr0xrCGSOd!?ZL4Zut`HBe*82GqtTaY${7we z>?7fq5g`IXrgPlGifdqF@T>ww<|)PE;-bZ*C{}7E@9AfQFh4@?eTYfv4IONJG%W~i zk_su{=heATR%1h<)giope(>+G*nX1E+&s4bQuc3EW#+`9K(v%9*T5N)n(VaW*C8ia ze=#G|!9zEVbT+6~I>+aAYNxRBEsb0w^L*Txpoe&4Bue)Kee5YCE@SyeE(l+0KtD$BQ>EE;t>O!v{wc88M~WADtV(~q&fjo?M&Vj8 zm{#L2Q5qu^&XEk$v{d|RPGl`zRLE~RH4P6>674{V5cRdM;`=d!U1-q8h$uKrBfz!1 zIkAtYOFYP|z+lmHrjGZ7MN~t+Wm*W;I?hvsR6_bsxfcHA zK0dveZxEh9tf+H`UOR&Nau<)!cavv26S8IF&W*+a+I7XHwwXAE2~^|GYP>8F>}Jq3 z2?@eh5AeSY#^sfR@r;cf5;d;SHI^dZsqL5mpxJXxi876HGk6LD&_#De4WFpgmN0f{ zeY9{XEMNudc)MNPkXU4PT4~Oe5Wkg;;Zk`$z#^_;^v!z{0^>EDbEWtvfn<6uAbWDO zb)B(|>sBgAp59r|eofdggl5Z5T&j@qv;Mjz>+-PCoM)-dq^2K3aRbJo#b!*z8=00^ zA4RZFk@Lydv(gdw7A={YFeGF*Y1e!cO1-W_5ESt!)n*mn_YT5_55#y3cVVY{s!T9E zF;5I!e#{1sz=cwr5MRfBU2Uq=L0H&G8B}386pefYvzq3j|04_oJ~DABh&$-NYnBw) z2yv-kD2*;21wN`7=L55qnFviOUD>vnqKGbZMr&w7BDz)=F3cJo9Pvkf;{2FE>Lu;g zE{Ktd%XJ{??0`bldM3IU%Aq*NiGoz86y=u%GJJUNrE1Oq;EYP1aa<@Y)o~qCQ75CK zP(p-KvMuj}cJL*!Jh5}d49>>Ac)4P2o@(A%_+6>izgzYikjSJNXsaCY|Q08r_(89xRxlEzrIXHcUnGoXN7|- z4VJm$^9x~dwn*u1Z(|V0WpK#nt@zi~OUenDdmjm8Jjfh>eoPUbTVEf`m-M5cSb1M+ z!u2Be*0mq|zfTG0H@#&gWgfLDOR02H1T)h}h8#HT=ub-KPkt6A#HFSfHk+@t2cE5) zwD)o(OHhG;Fp=qt%S~9C?N=Sgvr&*a`M6xQu}}xU`PpyCe^!?--xd1G^b*&f7Kgug z!>4ET;k(qN%X#IMKc%d6#qM{AQ*4%fzq4&?xh56lPp78H3fXl#6XdSVXH?0g+k>bX z7Gf6MtRHQpFb$lTiFAO}W|q|KsOzugGP4iO%+>5+Zxa$yA}zA;J<{u%4cBH@a-a-6 zv3(+0+hu{w@G+d=B`H8q)m62KYPQp?NIR+tvZPuUCA}14e09{M&+&RyZ<@!W1=+xI z^?;b@J}FRGd+m>v;|NcDX2xe4L40jVS|?tfh$UdtN{c7mU#?U2X}#S|7|#aSdPDyx z`&oLwEW2Dr*LB0PpD1IGpDaAwdcI6SYgugg7j!WZe@5Ov_4T?L z@k*WdNtSl}kwt3Bun$tyi*a>Qm2MS| z607-3yqk{e7Vha{Xr3@kZh=fBhg>~Mz943|Cms_fRr~Xxvr1@Z#E)$`Do|L}ZvFe~ zPCYc2b*CYXrNzso(DZ2VA=_J!Ke zAHBrzHg83EeFzQA%6|p23j=k>B_b`1>f1q_=Lw7ss%MK_$MYf162R? zbNF zh{=N^vm-a+!oubow|Myx5dsP>l#ZbIxPU~H$j!WxqnvP%_W>1o(hYujj8c7zk;TiF zLiKHh2d^n+|FjaNyFCQ^;v%bBO}U=;lt_k8bd z623RB3yr~@O8;89d$Yu&L#E(bS+$L%^(YJSsS?s)CI~($Wv0#aFJPMQGqR=|`fkx4 z^!(z^%-L7Zly`f1$klbG;a9VR2?80`)dQGXm~UYx<5Wh%tD<)Ei-yWYsVl)y4g&91 zH?SC{4#_K{wZZ5rqPuE~KQ=JN4k9wVlT%&6Z3V%|T0)JYdOeMi741jN<6agN2_phV zL}sM~*Q$e?6R9uw5q5I4ZH0&AWO9@bjTsSlnW4p{17~ zw#68`wCY3NF0n;EL`sZ;@OAC;r;a@MK@8wo7b-)c3BUQvpoS3f6wh}2taLKg zZM}~c^xISd%fH=iL(S3h@N*?|q7;jax8Yl{7p!*_4sz#yoLH)73-!Jdb*fp78=mto z^t0j?nILZCc4qDVqbXrnXzZJ3Z(BeEddp!l&&qEz=bi4K^cbxhwq zvNYLzGsT-j{SV`2Y+02%p#U2ujxg| z>oE#5DZ&|I26L&T^;t}mVtyxm8A9-dOI}}W5N|GT-7N2&5WSJOqG}yJwj}s4N_v;?d zdoq@&NUo<^vRg4GNps{b3p)muvXy`$!V5>)X!fonjr`x0FsNNXownU4#7=t3_JG$> z*XOZIFsnQZD0UyaAbk<$u+{+WsW+%4n3ktWk0$P=?tbyuikLod>0=MDOO%|YEw|e5 zrL^bzg1LYkVeR-@-PL*+&LRu^Q=Qe2V;e4%nWIs|U z;Z6x~EU11giGH{Uov6^7h(2r+pH8Pht4?HzdFI|QFsx8D^ zil@ho#1>nkrRXRyS_7)-T`a*(s@Ko9`Vs4OX|`gIS%y3xC}-IuA@9%=Z9>bFuM}7y zl>u)?g`}P6u+fO>mIqARkaeMx_*!}E2{!C#aA1;>Ih&>aCX+8~{Yp`EMqD_Iun40V z4IdKbjVvuywsGHtejiH9Q<1O`YI2`c*5oMg?=O;4A`UaF<-OK>{P(Xo1}3$0b9Bfu zzJN46`S^ZURler5ky^0_>Qk++Ou4K(q_2PGkx?xMZ4I}Og(m(P$NkS!iCP|gwQ{(2 zAiVg6=1wjjsg-vQ#AIhXUP2-7bND;@K1}Y{g0ilpjbq|Rn?d}WON-&SH6cG*nkQstW>9>xOa%-B=}&gHy;7Y(K#?tOzOI zjCPHDklDF;Pi9ZD#MqL5#xkjq4(Bq^yMw5FGUdyC!AAQ{Dv_trJ-s|;-wijRt6wF{ zT;OLu0R00pdRMvPfr+dl#2UkfaOT}EJfctVTpX@}S&DP(k!*%fip$;cF*Ws;*9Aq8 z!i@+N;?m!6+A)&YXEP){v`IQZwI{ABsG~?P)XfO|Vui{#s!XnSh+l`U`b~A)6VL*m zs$YL@;GwCI{hxlJwMcpY9pxle0tR_5Nub2lc#SzC{BZ9%;BZv~+HOIf%lr52Qh8UG z72PP%xKeGPH0RAxRd_xeKY>ThZMfqHJRZ0cMVjmjS$Pm{1g80xPH+3_yfNBxE?Ur zT?`#LyVR}(6X}D);c11?*5Cr%K`>9rgsh>C4gdR1QRwtsMY^6?Rbmaa#8TB&8ngym zF`l`_MsPd~rjS|*Tk&r|5G$W6-V)|~y=npLz-d8{;$JCHQNF*7(}~51}nZKk!tGHw}3A61i38R3xT;CSqWs7b&m&5G!thJb)4>KDTo1t1=1m=}zq@g8S6l#5Q!=eEgi= z677bDnz)8q5@y$+JFox^_bmz_NZsSnlF-PP6A zRlh2htzJl$xXLv{?N+RdUlo$#f}di${mWx~lplXwcP z8q>k$#jDLfM#%&U;?n~w$HS!0bv%|($~IcWY`EmHT_qg8MxMm|89m_hOAQ6$fO-wh zatu+XPkH18qZCnRy=!NImp;|kRc;66k(u4bE$sF3>GQ2u z@Y6!8gF502m`e_xq0v;8T-(_C^rQT=8nt$Z11NclvqqiNlUHfKB0UNmt4uWuH~*ZK3q3!$wacOmn~C#iebK^&+jB@Z-H~x3 zVKL|up@uA;PO1&^qRHp5K5x+0co)Nky{)GVS;i1+i$3P<&ID4+c=yIa!}Mc1L|cbr zg`XQT^1Y1nbRhPT=yVhpOwV?^y#sEY)337M__`~zE~YKI{OZbamaJINuH@DJ&HmY8 z|NLVA;_|%z3f-=g3Fx@lgo64sE|iUQjMk?XixCO7^jhw|T#z2P(Jv0<8FD;k*9CRl zOg^RfVyMoDXhZdOQ!>@6>6AD~5qMFC*WPsIX0W0!eyvoxB)$^cLjlJagKa+OIKGq~ z)Nx2a5F!v!RNXu5?IJq|ZVk_8@oY}s#=YxfdVW(=JVhK4gd90}slh3dT!rE*z8GTq|$){)!l&EY5 z$^m_tw{+yZyi0?}PSZ(0pI^x~M@_^i6>U34aeI?-%8{3fV*(-M;Zra=^0Gh^ zc6mjjVG%b&dppbB$^|hcSU)kr^CXcnFrVZJuLZS=50mQ{wWW`EEp_EcX^`rbYhR@u z$5#iOi%Q!$rYB4miK`Oya7T9Eg0Jdw{S%>suZ)9(>+ zx9vPOIh1!8j52uFo0)enSa6^Pv!xv$=&?bD1(jy>jSqd6(N%Kki*EypM zOcD_6KryYd8w5qo#l(xNEgsyu)yo)u9r3J}vu=y5o@Nt%7p%Ctb$T|Rpo!pQ!V6Xn zNkLtf;IMQPaV;&UCw^On-`&mfX&B(nL@&ko$gvONvQ}XKvb}Q8~ zAduLxyt6h-#u!w9F?(dJJkaWGQjW>}^;C=RS)($=c=WDiO}TncqCwl&@LjR(1ho(K zkK$vM&;kTebChLwm|kX(@L4&)F8b+pvxQ?ErB?^=F-D#+bY?b!-Ih}%O{}m`lOZlg zoOzJ{Q+iWCVyPv#oNnQD1n-G_O)K^GNSyE4}XqLOJ| zZ316$YkPc5zRIRuXtQ$Sq+}%h^YCacPzokgO&QWwRfp}cweAA`} z2N`F;AABE^Jx+T2Ix&8or0hCyex0-nzcgU=CAsV=kSCjDuHc4uh?}UX|4fav(ILG> z^@R%1uNYOATZVfO4-Ld+Jke+lrC1aLC>c^*H#4;E#1G~S>1le)0>9R5h@FC`MQFrd zp^vH)JDksr=%al>C3y?fM^#g{xhHeBQ+)ZNGl06rr!W5HJLW^xi=3K#(H=84V=iKPwY-4eD!t%M?+?8hQ*dp76H3;RV#G;Vy@D zviTF;B&5qW$1c$Kx3k&Z_OoZnq;sEsNbf)<#+__>^9=ubW-IrlvPxur?gpCraE}I= z!NcBUs&;~}U@yK1KH1zs!?moV2Ha9-De%3~QX-D*TK+7-kLa~}?sF4Q?MWEep3$|L#CJHvHmfdPE z+Djmvo$%lHm%wWh@S|iVI57f06h&{KRd&K;$S1O7pyYCQ$QogCvA1Sn(GIckq{^>XA_rK{A zS;;reDd-yfM1R~VgT?~IlM!$F&NPo-qu$hPS>#`-^@R0ZZKNd|?r-uaNvCa4q62L= z$sUKDRJBt=tQ1)dPf)CGi+A9p13UIQC(#|4}&AY zh3uhwlC^!%r?8jFH1HAbu<4Mm&+6ZoR3&F9Ok!y#y{elzy6X_hY+__i6c@a6v2-4W z7^cyn#@OkH@7?GJCa#zcmC-Ig&^N^qnVRw7^tPw?D$ho9vOQwR=(QM@;QpHX4#@na z(YD-%?*1kXXNb|Aqt2(XOJ(V-GZ>)6L6TDv>9EPtYi?tLj*|Cr?qmk8Om&&9AJo2( zd~Xcey^hRUf4{Zv zt+B`%-^lbsZk5$^Rs!`0cFm_b+74VO3J7)#xqyZG5GB{wz%pl~B>9oVY=WUiI7iNX zG8*X%5i*0wCswE^#c<)<6oKSO?arolJ&D63QT>)j&h}wrw23xkSk~(?+F+#f1*}Ec zvguuwRT0VE8CyeU^E%&jBi$(+p(6_`VT+L$Ke>|-D;SuUrXYk{uI<|PE-Gw5BQ za3XDQd)ZI$RAE4w!NqWGfFbXW5lo%$YFA`|#oMxlMX9=MtOeh?(eoxN%?LDHm zcUlVWp^;C`liUZ8K!^2Ni53)Wl2&Nauq-WGKIW9^6%r@cXz6H)9ImH*whV3-Y^#{(ENC6e!d-TU zz*`w}bFOKE3O{;*aKo@UU-Y$|x#YP^w_jI7OvaAB7Mm`)yUV+5a+HwI4DEH832#Z4 zn1ggY#*6nXp$8ob;j^*}OXb`e>z#>37^`r?+ys`T)L5{Jz2Mk)`coLdtD4%P{bjQI)NMwe zLhK7RY7=j8G{e(AF|6M_zC1e8o%n@oOpa#e=F!Q)e5^QaDpU8XOfUu^sipk>^=v5ITnutUwShc&0k+;D} zaBXaaOLe8}H{~_&cWAF9kb=C2a?pq?78^<;Y6vSD>#$QC5^-^(yK>QyO|Ye1?;1Z$ zh%<5}LlsA!#p`q2k(-KW4fyueDaIeL?AEvUc|p_#%yRyF4+FH`n*Ir&1CCqn1{801C2t5Z_ z(X(r8>E;Gmyy2I}fAVr%!f(4G6Q!U=4N{_bm4OoP;3?#IRFfckSukn~+9l~EZF5>s zw@B5^g#u0~KPoIydIO--L@f|ur+j?+c|*O;r2ya(sHBUd5^kswCtY1&U_>6*tXW&c zt;`Kv63K*@q`NK!MQujR%3Ku+t2s?>&=+iq7TPIcb5u{0Xb6%Jp`seDO#A@a9FcdK znsYhBuOMD%Dm%HAYZ}W!Mho--;2fSO3wuuuY087#+h0oi^;*!{WPrd4XS1hMq4w)1iDON7n_ zUiudvp*{m_GF!b{e%M>mv?%gRNu2uqUuHtR+$%6tmc*(|sV?&>n~ILff~tZ&pF@zc zYsF4Kn{ls|NMoVkD0(#9Ipb=s?nwU2zPFDK1T>c<{eqS;ej*uGw8~$6MvJtuOU?Q+`z?_9e)U2V{EcQvpW!%7qi`Q@5d-XTn`eJ7R7@KQv?_MHoj}R z*KvIrFBItSxcF8_UowjC2#701^L7cZrqH95?hF1&p*#Dn>8tu}NpZyZTpvn6NI?kj zcP-~Da7urdd;aDX?J9JyT(MW#n%pZHE?u|s9XEq-LK!Nh5<;ipDsG8MX-}h)Qst^p zD{A-HT`(T@Z?ge^&&O4>wYld8w-%Krko+V)?eQ3KBw6K3taOf2R{z+^3)dS)*oYjh z5!sv3bM`D(jhl9AEdEv=p9QUn9)<(Y!2@Wwuwj#g5;^JLb~FjU8f40DM^?=c(FO6Aln92mKdyTBRT4Pq@aqto~|368!2IsuEddK;aIAN|Cq6rpH0nDxOnW-DB)i^H;}6bzs1-;q7>e(4rJJqc`bEe z)ZBn~iCgUvRd4U|l*)~8%ldX&P`|4NV>yA)vKX;iYg#ZQIYddQs*FJ!@lP^hQhGx&XZ`RfGiPj3i= zzwnap&oS(1+dGt&U5+9OSLxHz+NNtbt${)XI4me;->pFMt)a6|ecj4GGqJOC7(Uh? zvaclmy6KuifG;QH*+BqA*!-?P8!$oqw!{WDdWBOT9ll(k2%q28j4g?${b6=Lk;WFb zgoV+Dc|R#NR;`i+agxbu{#HR9$NBp5*xB|roKu%~+Alg>c^)mRdI6(4gfRuREnl2m z+#e2g&C=7IA~~wgzrw-`XYOmw+vm=j-wWtev8{{S4Upa9_27iz>o?8MM+=+cps$(?umqdx&_+&ga#S6rQwgQQpCEean`K>^<%cmw zuf)ja6OAd;N(+QzrQ}`{1+vhs+ReRC6as5Y>)jT1GO()Pg8deVu+2R?nSKY!B`i!i zb-I7H|CSa{s1Kgn6G;blMoR$J4GRmGSmr~L+>!qA!Fk0{0Aj_t#!3O|SMg+qej{?A z%i02tkHsz~@j?p;c@hOZ=*5P=bWmw>`&m#_VmnHns09uc?nzFe5q7L}tqkra!Hp1C zNz)&$ars+!b1!~@Xml0j?X^3g+DQBrsYd-wlw{irE-c*YsQqw!h0oir?{=!S5=P~a zWe(o|%6HLv-lTdE@v#t>zpdmAeo?_2{Mhp5%st_u(WYwADn2SlsL0Q{(Q~?OWG)M6e?rDbaBj|tTJ@s`k z1R^$xhI3*h=ZqK7ZZ!avuyEN{OTiod!aGg!nY~!ZR>^1j8pCe{WC(`A?~g_WjvS0g z-}58Q18Z_yv$_-vNV#J>HE^6w_J?n>fwv|($*E!b#-i7DVsCq!s(YC%4kUhG)zCNG zbapc*EebT%5mY5Ks6uGaHrl(3sVunG7#o`iLQ?odog+MT-H&hi$(q$1e$ASwTE6qw2gwx65a~V_KN=1pF;qHo$Ic3GCWUMYLIXi@2;@W_mtE(vWODbRC%g6ik;TBpHi7IxrCA)l*~5gch8{cWD|AvHFJA<#sKSn z8t_<>X$~UgeFqzRPlEm|6YS^VgDnHbiRx!OrpnIDWRNZP?|hn}bGF;E#g+;e74MB^ zsYt&gb_}_=`I&i4FBOlX6+OXCu33HgIqdFVywS@Kt>PXi6NhL&Kn9#U{4E5inP#J& ztTDdO*1)IYuIP7A;h8lxc?1>2R7%=3rD5JBt=$Lvk;@(djCzww1bcflbm(g$<4s06QBTDf5IaAALTL=69$x zQ%Eblf9AdG-o&p+k*n9!G|?QA=NTf`d$3@El$Ql7f!X9by_rvwAIBpOZES{tw^dp9RvE=LCl5^Gu?ve<)s)~= z^K9@TnJrz{%g8?0EMO#?@)u|lE}jV7 zH2Dc^S-iVA!yR4xLTV#_#>nPe-rht!8`xkuNp40Ig@h=?*hJWY-f#*b#0Z-mkY(;P z(8oDpu7$Pu$}4(!aPN)zy4u!zue{3*lQDYvPW+UdW;~QrPI`xwHp@PfHggE$z|2Z6 zR6*oN#0YH?<0Y^`ig8_5Ap!vGvIB{9FS_0hL;NtpN*!`e;QJuWo40(1&&dluP$4>E zuAx(26{iagH!+UM-kG!YI1Z6C>9$PwRD?|=OSt!S<-Uuz_r#=f@)R*aiTLrscj;8D z`?AyM-awXi!6S10Tu$&c^PwT}2qMeU9dAS_mEjzSu^E(pW-z zFM`JevU__QnSc<}+uF5@wJn6rC`q*Wymg;~0DV;A%HzcyUpWA%V^-5g5hz4tM|ZF%HaDj&yL(%h>dS7aLwL$N^xJd#At$?^v#70{uD+ zcbJQouY5JIoolERzYSG5gcCDYh{SS;eQY{l#Q+;r^M*D^ChA8-Nky(@IJo<)-7joi z4gm)(-c(~92G+oowXB7scHAqu;4d0$K#WGa($xed1%o+z&#|Rk3n!QZYL`}$h}#ue zQj9mLV~Zz)C+`T8|9!WNS$i^@kDA4cf8%YmaOte1NDTpM10kU=FR>*38No^fg(v_; ztXYM7EZj`h#;|Ao5Y37b=fh|On&F7hcqS}D6t*QepcqO7Rl|`BZl;8BeD*M=+)y%b zS;IQl*1=qemuN5&)+;~^3S%S;lRYX$=P0@h}AR1uez2;YW&0F$9r}{l3>3Q_@MQ;bB8(v||xu}m~d36VQxy!t-Bco&` ztGx;K?3^LcVsK@~ne&a3K22IbT(38utNuvvjOKR8CNGOs;L|9xef6bnkulon70`#fiY(GB!cr-mvMgdEv4mSbix) zTSJ9`-}0;M=3HSXT+B2w_5O317V;}1UU4&@$uQv+xADactFd@GhH=)2(L-A=dSn5j zGuKQPL1myBzUT-d7YCGNTEN`oMv~Q_Sj%1gc-S&^ICgX^$emw-_@pQo^)lwTnNrSM z$Ej>U-UXEK6E2IMckX>fu5kogF}cAdVx79^`Oj3N)C}bHNY8rrvJ1ko*y$BL+{|S> zn@v+tAv9@eVLIuQfsV#89)utc8~kS1@X}Phf8FDTdDp0UPdD#eRo>s`cvjG?&|;#& zbNu+M!k;IFO6qbbjknr;KQey$rt@l_bJ#)h7RpZHCew`G5M<5q*_7f-J;QF0oVBsR zl@8aM!A53F6M%Ri%tW$`Tw|&*9QmBgAcHw<%&~F%CMM|%G0>1s`Ev1i-)zqo4K}JY zVH8g`f1+h-@g&P}A1zFMG2YRM=9wOv9ZJRzf*vNBXx(Maqf?mHi`zY!Pak8>&^9bt z|H3dg*YBI`K0+~ITw$_|Li*`Z@iDP%WgRPcXK**jK{kfDN-$Lr{4$}fB%@wv>SrrI zwASEZNtCg^NR(58di5bcqXPRmvH^Ff*(!X?(#t1otnR4*)cMi~*8 zxAg87l(X|Ygu_~17PN)~D73KT4+!Jm%?I(_q89hz^b^kxYLPxnsMfH}kgy&mwzd`O z3-&ME;F3>41i=(Psi;AHQt|!S8-z4qQ)u}d%9tg4{XsbJ&PO?Mlg9JFgS4LGx!PX8nO5zV3wjdcJ z;np*qBqPQ2&F)cEzD@5GIB0)9%ii#J0alRzoDdqhdcWB_{$c;9cPIeDefZ|H)vqv zJiDpn$z-uPy-650fEE%(Lb6XH$srvuslq7iS74^=k@84;)UpAc-N3 zof?seiHE)sAN*#&|N8jypxh@7=gYnR!9eB$+1!790w?M8iC67%#AzoV%d;NKWs^#l`2 zMbEeRh)7^s)~dR9#8nZ%2D_7^-tomsv@j{7(LzdU&*8$vPV~^_20m}^ z?s8@XOdZ8*&cH}toE)AsKaS$z=+8H#0CYfYX>DtQXK|{}=!T3rS;;bBOnKz7?d>k{ zN7&Gu2d5ARCMlLh5VSOd6@0`DYFRf`DW$fxFoBnfA+5M0dodjRlP5u?^2F>l1#l7h zAx+74-H@%aD+B~^8z_hidHiHw*Mu(>G$UgUbk665Sfh}OikWErjIR=e%pL->DaBU7 ztVzZ)w4BlFVDi^F+P)mr9|aE?Oi+49ix4i59$O?~xW4F8_*W@Q4%%(zk`LD72;$T0 ze2OL%^^PWWEP!Li2Tk_jXRBb|i4HRo>XLIdoiP1GOk2SU`9TmmB3ripWwR?W5BG^| zC={q-HaS$mC*N0<*j9;9acW29Pem4jPvcQvo)K57OwY_N48W&A;w5KD+1x9TY&ga7eU%-Bo$pLocTWQz{9Adh0DDCQl_yoi*`!eXO{d(T zW>Q<~pGY}Y$uTf5c^-Q0gW{Ppn@;r>(=?4^^s0C0?#{tb4eK5jmqJ@hhWAq5qW!BJ zEnG7SQIL&>`wE|X$nlF-TFEU^ZXr_noIa(gVB2&pbn$!`f_YoR| zUQeG!jrWsA%UmQ}lvg$xOfjLHpCe%9oL=`v<`fI zGrKh*^T;uJu+Rx#Py#-(kAv*t z@)}$wQ)*<%qt`*Xjn7^Z*phbUrdvD5zJm>r3X86Xl5Rryg?S3Zes(v@*uT)OdNWl! zYt$9Y&+du5K=CjqlYS?c@K(e(ukJfkB+tK%^z9UhQvjL_6z$+1`wq#4quxQj}UR}Z__n4l$qof^sO%@c;RATLV!tZfiuddgwbtiPBDDilnPT~l`%<-h z)N)OYU2=`oMPi$32(nX!WcLJO1zM3_V{mPhxQmy?h0E6xj)nVd1FV=RQOpZnml}~` ze6o~YrQA)&8yWN-y5xep%JsH%FiCkW;X{ND#3{QFY-a*A$yley;WQZ}DISCOF6SR& z++>4ngbzvdRFYd*8PQ`#v(-=vMJF`7Dhz@W^AI}(eIGTR zH$b;)Y&E31@W*>dRq1GZ1#YeML|9V)ioQRk0u}xPl_siR*;^U$AnsgR!AU$SeIX{d zigwS>4*L9C@A#s7cD#Rt=%D>K{k7lVLzY^|$Z_Fq_xNI;GSb#$)%}gW;OG_d)GUqF9iCg{DlR{E`KY*k2~scML1`9N76fzR&{@i zCTseYoa`A8MH1uCA3m`yd7h5gQ5BnT*%NS^qIyc+n57hh(T77WNDdX$z|X&+WWy!( z@~C^*-`L)G=KpBySOSSErg8x}7UW0}0$e`#fkLa?dnh9p(5VG-+lRuwQsC(f{k(l= z)dN%?LxPnwL)>uRZ2MoI6^5vgGI(Xs;U$=9TVSawIs~N?Wkfs*I&1i(RT>j$vZ&MT z!AHy241}ur1isud$tj;{-SlJPt9;UXVqy@zM365WOjOw~X)RcWVpq6R*}#Z!1AF$c z`y+I_dasCJU%;zjD)L$aF0c+!ky{EbPA))d5FO`_BEb(FJDk%BN)`|QoC-zaRSO>5k-o6Eh9*jCR;=U7Y63akw;dhR3ewkPRr~Ks?P77HA|;=ALkUI zOyv8?xv#MOej>}`rjKrsI`n17!W#QRY9niOR}XpdjlfTHI~q7%Hb?_Z1>dBOQHy%t2rQc2%}nWb(G! zv_v%H0sd;5jbF14{ju4osn}@qkOYIXaR$Pxm^CNM+8lJj`Oj5R;%_GsM*|{{!2du- zqZV8{fGaqxKlHOnUzW{s%8NCMB)iywSZ%HT)!aYoqS(Hg-{4y}HMq%%OBd2w^iy6= zYq18T;7JV949bW_vNBy>%WnxyxTgRnXoJHFsSz&(T@mm0^Rep%#0 zfSw2%lP2}oH`nC`v>lbZpJ28}bfa_KHeK`50IX4j=0_aneH-y7sUcL?z=%QdMRLA* z(t>Ln>2N-1tP~+2rBo}?QoC)DdXb1MBq|*S8hBGMc$n%N?7||^HDk#t;AOhXD&Ug1 zu^N8X8G|Zn`k2fy0`P#KdfxyQ=sgszoIu?Z5A+N&!qm)^JUY@1}~I4Djc`2%WCvRxQF|pm}1Eb54;uB*#W+f!reN*B}+5OB)%7mLD3v>}MPoa9H1s;)l@KkNhDVPnOSR zN}G94Z2+&ke6h8tXy_H3uYNjY*@z2lO z(Vy_=7lL@(^3jOYbTGLNst88DY^WT~u)-**APn%i4iJOp#we30o{m8Qu#5*3!r)$7 z$`52z5|JewF*2L$5mGVS5kHde@g<|s5n|UZgAt)X0;TiAgJ26RTyC%!?tKg$$8m8M zmp~5ItSG;lB-@JD`Gf2>G?gm4anZU7)jNPu?Uzza#)IX%LkiYveGB#muhsPb$jP`a zXiKne=>A)ytl9-r%FwgI+9}lm(m%Y7Kl$6uA%5xxa=S`aA!0XAK$%APT(CDj zk}k&E3B>D`3*CRdwbkYs&S7OPJK&0*467V<#X7;EM^27(jS3SOk;|6~Vo?*|zNoyI zXfV8Uppem9x3W$o=Mo-EBaC>&R=0ou``_cygIv*jVsd?rPlm8F>8Uhx%sfgb^G}P{ zETLgP>h4mkQC!9E*t0RHL}j@8H(JHPB7)<=fDZvUuRgPvvx^aGU5XX zO_)qzQ$A;EG%5mC23+?4SRz^GZPVBwxgJRaP?&(k$kiFvG_J~qX^AleQVwgN{p2X-M)ROm zM-8_u&N~{dJEh~~V;V=}Y&ZuYg==jJzYb`1U8Qs{LENT$GCWln~i!mSxdRmMMx)W&&V3-b5oa`dj7;8ED81k1v_IN8v<|wgT$@ zwZ~XM5QEg0Ex4A4O2D9qNF9yg$`XH|n-pip48s_$FG!d?r<65Ee9rf!-8q@4J@VVL1|<%EQ^5ZTM#CrQRsJ&|TAH{p$VtX2$w9BajtM`pmv$#&0T6)k*& zO+>s>*aZXc!XJs?3UshGLBOzt)O%n5&DckCZ_ z*!Q3lECn5pI2aW1-V44W)~_Bv-P~jC6pgXKhbK?$SL(B{B6X}ER8uF(kH02$KA&@==(n&WN{l{LrWSH{cXRRBUG z2>lLDhh(r?aVcd=aa#0CvVW6tuhF2U_#u%E9e81NUIc#D3t0QKxx|-%nv-d;!jgZt zbm>d3K{VnoS{eYNVXhUm#F5V53ygzg!^YLx-q}u1VabErX}Q^Uw7$Jgy-Kr9siD1n zFWc32S8AmdZLdF!C>(VzC#~d^8$gJhRwYIIFgUGAuWGYfV6E;qNYvvkJev8Hn=Q zbEgXB+r2{8v|M)AX|+1Cu6V6i!HM(e$r+Ug{4{#*Sn!Scy3@<;EUOcANG#9#}VDGVfn?s{KsK#FppEod-Hm zvUY8$Ws5`nr>lmocDhS?bi>c-+!3NpC+*NbvF1G1(UwjKl>jX(aOL3hlAP)HIsgbUC@6~{L*{U9k^CIsAp%f&zg0O#jZG}=e; z=mzF@b~_gP6}@95*ViCnNe{nb*NFiJms2W8l)@RdexHm+&^)~a;5zn6&(s$)z+r(a z6sqlXBP$8$Q>I%`(ZspicyethAlfqffc>%cF$j$WeSV*!GeA>YbguDu$wY>3n?JYL zw>e0Tkqs={OB^OcIJc?Vu%__Bs5yc1*fFhtFRg2qKYR>C&1aPONv&$?8;=T7$v0gD zu>0B+v8Mu4JKm`3n2*k;fD^$Qz)1KO8HZd5V1b(EW61HOAl{Cd$Cbt)^ovTcEwNq` z&Z`Gno@ZF(h812-l!MwbObC(xeX3EygEvPend zHk|jDR!6^n^^=g#&{%~0t5mn|LY-bVpVjHhb%t?%>sR(&8!4uumU&89@pGk4)mLsT zT*Yow3u%*d0@+A)1de1cu!I_PEYaw!UkNs=+?~DW-L+|?*b$w8&c8pJ-{~_97AW>$ z+n~{ruq?LWbECL62nR}NQD8Q|u~Bg$m(}%6Trkhq$!*m437!7pW2RmC#J)5>YLh%Iu%we zE#>Y!@4m4zto!3Z_ml#Ox<7UgE-$(Q>v%?RayFp?yTJ%dq&y?>ixD*GT?Ob0?^``M zIlkx}Uv{D2{j-BNk@p2~3aQO?CmA&HwR{rw#rYX`AG?bqbn>j?j?X~}NZ=_Ok$|M; z;k%QwLr+*AT@u`5ef`kla6G^ViTsPA=NqQoo#Ide&RdM_BDEF*@O(?Xvi-EHHV-RO-S0z zlO(#G&F;3JJyUD@I2}y0JiDIZ)%n?nt7p&u`1==s`W@^2cxIabpy2h3 z^yOK1|G!U9ddC-^-&$#;3!5^}he3hw7i`Gw`fXKEmg_6*qi0*q3nHtjjnU)b&rc`e68$W9-C?+LWTw2pYk-2!Uf?`6 zIR2{e73fEy;a3hh)zE@DSIsBGS7*&ohh0Exn|sjw zqP&PMXrBt7FDZ;mtW{DAYdfxPE zzExK8Kb9`#fA5l5ZB~Q%D^KrZ=cS@KnV=p5>^Y^RJTdIN#Hp9Y3H$KPT=FHa0g97R zYQ%drZ7v58GxwUJK3Ck){9EB^-&kkDZ=OU}euzxj+~e1Ll`SMtYTa4w%NGTR^$zhW zVKUZ=h<=x-3L1~@a=x#Z#)Ic!f7KDa+!|#f%?6GIv4L>=U?liRA_IDxLCR9Lk zZWK?^%!2U{b37LK-ZS|?=(IQFCr%D>c>yMtv~+e3pk_e3d>aoiBh`;`?qKyn47)OC z_ueJcYo_<~`Z}3%>>XwB=64p&u`qE73`q?@R@sFUq-$`ru{{v3CS*v*vJSnLRN@X< z_puU_2n0c+H*+{IVNy8&g(rv4Mz)se9hB&7i_@T)!`zG+$EZZo)rszdgMr$~QG3Y(5q$7PdJ~gkeYaR$!}r;Rf+Nf}`M!Y?A1=a!4+6R@T28)h9LBs%qKTKt zlFXvk(_UdqoZzVxg3x_)!L2u+U&#xNxITg2F9^|Z;{0SnPIL1@nv!R#)Z~3ksDnsh zOYC%o+DPqZR%L=8SGhthQ^}YwYvJ?!-J%c$@rjUKV~9kL0FqebpN8jWvDXi;;#oYB zxp3YO<)6lLn0EwX(C7;10#R8O10BNhbTX|`GhP*G_B6#OljnMzLn-oJ@5&&y z*|lBAMBlxicz{p*JBs!R#8%1{TBj0vSrRxVdM6+_I`04*Rtwx;Jz3DT?V_XrHd6z; z>m-od47&b_Dq)Dh_bHvX4!FIlt1YK9O*=T%%t`#MfH_^={mLw^{s|brQt_!9tt6a4 zi`>-1OSNTPdm~@L6#V(aQF(>i<9%ZVTA31N#Xp%D#ZjFz?tkG9>>X-RRG-tksEq#r z1wQ2%P=(UnUq_gDaDL~&bc`lSuhA&oQAj|OD&g-FVI7Bg+>5`S6YZ79fnqB#m*HAb zLErXvCE4}*W^m&b(Yi!{R*FfvE9)Vn(#PG7>HHvJ={}Nkq3HyzxqT%|C*or=)$ltx zKIwSWkdDF#{aKoPiaArj#B-RMxQ+9$8FxV2bGUQ}N0@mQ8(3#EDl3OMs%7-vR+QH+ zBwVA6hIKRfkII?kecJu;w0m&TKRxRn_FtWxz1_d~wi%rM?Px>iqRq_Lo!{p*FMd&r zE_9d}a%mx6TH$rnK)!DKKl)7z1*;~JmFW1AJCyH#b1z7xtU!oxyDGU}yNgy>-{r!^ zS{wt7Q?j{N0!g$tH5uY*GUc1?5^gUk_mvpFl$x)_^QDF(%wSQ`mE~aJRx24VYYj4R zVOe~Q-&nK!BSXnz8VlAJ@+Uva1|LB94?Z-XFhM+GgX-wb7vwVDx9(bts>}#ikjH(V z%vK#VO>a1`WhR>nd0%Fh>*8y&VcGaeE8}WaOQ7BuSI89a4E2v+604dW?LGJoNar3G zu%UC481uWE;m41udd63z`*Y@gwc%mHKUdF1n(8nOKmcoD`0~mY5ey;>-&%Q4zF^-w zeUO!}X#;Fz4g@380}bODh>iJxw?$NGvV(;baiK4!hNICIQKM0<^`sDv-zt=p5=IjI5w8T>>jb&R$FLw9I5N^@y)*bQMXnRNQ?H>Wb~Hz}FS!+A@*$C@PB$Rw~A- zADo<96eDqKGUiL~n7;~c4`EvorqVD#cae90A2e4&4dVDYew4{Sq;~=<;M@T$erbu< z*uOt(S znc=Sp_GNL9Yg?L9x#;;fQENU=4aPc9Pn;2TQLE4zLP=D|od8)troZvBTUehU3fv^H zKG#GIvgJQD7Zmi{tY2XY(Kdzwrg?&%a%Wb?dp1^?!7-5?LM7h7Auf423zXCSNwx1HPBc;rpSc32<&pZH_k#X=X^yL5t4 zKKT7%GMdHFE<0ZtMa$ORYd(Mehu{6~cmMV6A1;qt(FUSf!O!K9cl#ChwF}_!?OXDI zlgj5b6u1>p9Zqia)A>dB?M3fxH=1PkeN5;Jonc!Dfb2fU07w1e@@W6;b(etNUw6;w zYf72Bv*v@=K0x1DA#RNoEdratC$YaE;L4oBcgYJ7ruv-H3FBz@bLC01h3_80m(3@J zRA*cF-(O^|vMmX+&<@3U>AC@QjXj6>;+FaQ`QSnClZ* zO0?v&owOh)?0uWI*sQ{cEiFhu_g#0wn}TmsE0>pWk7) zc@7_Nc;WB<^rt_FgQsg?==^S5>~P;%vh*(Vghk2V!vUY9-$&8O)Goiu<5nPk2e4ibg~i+s4tgnD%508csY-M-QqVhD^XQI=XyT-=X~_vMG9stD{4*bT$rGTGgUr zt34eK9}!y~S5}1HA^1slJ&YfS#x}n=?W|1F$395%0#WcGEP(;+x921f8BgFhn6(-M z@(&4i=>wFEZsGrplS*)eWs~RulN)n3^iD(ES>@!roTV^u&`szsm#Tv_s6}TPX$UTv z;pN4v&F6mrU0~<}C!E^|KPT)TpZDaeHf3BM5V$5~S4hSP?Z93zljNR}01042ZoADu&&G1koBl@2nl29=toh zI1_GcZ|?&M7un_P`uQIM$AcI?7qcOHRP&!Q>DZD#`6LnM%}hdF;P)mK4~xtvDLT&d zwWtWxxAV~~efaeB z5y?G&_;Z1Xr_u9Y=oS9UiIA4tg>mXj1b$TL{TIcY60XX(s=T>n7_7450t_bq-?RXO zO|WzU+C`>|ke1&?hHJ5@3v`a3_344pFGKTT?OMdyDy}k})r-tcwVhzsY&+U~zQQtV z%P&qE`aL7d4L|tVQld-kYf!@L(X?Ko2fwr37uOno#j!s1*eog%87y;*u_7?v_gQqH zS&-p^SQXm%5Z%Jz?7iL4)<{q`e0RH?jM5LZXb0mvR7OIOAdKXc;09Vq@$_bnE~Cwy zvEK1RBFV(EnG8AJ8Ia%+9_S&L8qx}FUX*yR)74HPWEwxuA(gr`5BQhvU+3{C55ml& z(QR%6a^aKdZSG|zey>g%kz21*|DlUlu^;-9<;V}_7Gbp0hvW+*E^BZlrFkLmU>_970O<4)c`omtpLfqLp0Sq&<&9yKs3AIV z#KWP0V7>rl^y*=jxTMss(0cBZ1Tl=+D2Z84WYO*XCJBvfnC3Vev*Sp<6Wt63K9j^W znRDp22Sy{-bk=0(+uORh@%2B?8AuN;M9xgaEO0@HFCP@upzZH(>dthPD8dHb*wQ>@ z&*w>`XzAQW3>41s%%`pGW&u_SP(a>Y)O|8@t#B&N2}t27EZI>yPG|Xd+{hkUxG^EY zbJXr%Sg$Nhy#|K`@_UrUnCujT3XLp=KYo=ZpsV-bH!i3-QGY4$&*JcRadz2#_GcXc__#9LhPI;~FH|f)k{^{H{#T`|@*FSk+s7;XiS#o$Uok`7&uz^n=dYKP?9pY7r;E*8q-Q|zyl&0%R@ z-hq5UUz|{4#m;yVy(BF-O4GhMboS;Q((7uX1GL6c>g&*btHxB-#o~~?K(QT0Q?r)U z`}^AEfQ&~e=)I09SihNcZaSj1_PhVnR&jypg5j@ZHfSlA?9(qJyIrIG(>=-h{1F(i zgSe=OlR7sj^g+)8C6rDjXpW-M{JlPSV8tOg8k^UclJP1AxdY9xGd+BS!ua>pDtv{1 z{DD+dv;=Gm<&g_J=z9{kF)vZp;O}FK5eUA;d}HynQK(NeZ?mvz{64I0zk_f8&@hZk zZzdU~nGt+-jczaI@boJeQC_h22F}UB{&~0iKN}c=iHbF)eZYEcb8CqjaAT8h=w8rj z&}%XW7Grc~B91n4_o!XV-M>~vgjMix(akro=@!|$ykdai6te%3mF?^-s!G7oXxw*F@M_Zh7%h?(4rO^b)~_X*SBiB(QGuC=60|32OF5o)w(WuY_N;lGQyias-xusGPN#X)UMLXy;mq%#s!uZl`Xp^Dt%;EMHpbs z;x3FsHqoNRTA;JA8H9wNw|-Gz##Ui1+O~8ols>1iv(nHQ|V?kXlu7rgK{u;@N^LNN!s_=LL`Yi~H<_ zsZS|^@i!hde4-v^i0qUM<=b$+T{-C0$32EU;(Pl%7E>g(LA<7#r8+Mb)OjJj!4QIa z6(m(O;7U<}4!e+<4LM6-6QX&xv>{)OvUqmX6-PMUf%s=<&DHr1 z&ZSnMxl_bFgX*8LX+LAX3OOroHB3xyrk<}C#`Va#qyKr#-A67j((-YXrIP^oy)3ID{@g@! z{v3?&_yq6%&zD#czQB$UMK9Bv8}xK1KvV8^&ayv{?bL6++gemFHEy42?H5+FzUhB40)mB(;2yZ46?h2O=xfPF3x9k0m$JL z8&UGqA^OX5aDlux_1>Ny^$*^j_TO}mPP=FQW_BlN#@Su#ef|3X&2OaOYLX&)zsfzz zEL7g>;Rh%^-dnXVyR#oJTC@1QdT~^j_2wB*>avr%5djz6y`DxIR&MFil#_b-S~M~) zY>H0smD><$DFc76c!2?k@k%1{xNAiuzrF^Gajj+@9e3N&H@mIR?Dpt$yFG$#zx(`d zFFv>13+Oib+-_fg?r2}bX!kyQw68yRw69^bdobFy-(c3>>l#t=sH2!(XcnBe^!=9| zxzQJ$x>2?1@-w>hr*E}WWa(J@k2#W0(YWO93yY7-u`T3C4jRd-#nh~hlyO>#^xiAf0l?_N{EoBbn})NzFagk4U@;ms(2{<-oafAQHh zmz&nYj!f7|wZO?Sx6+FhmxOhNT@@pb^`n4UqCtIwwvHvk!KyDxRlR1l8phsp!Pi4Q z3Tkkx@>#=zDnC*HEn6{M<_lI$c|yOa_G)h+%U94MY4i0L)Iybf`NFDRLdCb?>Ls=M z;;A>8`Op4V^(>c{uaUj{e17E|?C^Tn%a60!x#I2NgMdTky_sh7JG|+scd&^r%^g~@ zI%MEwbd)$x3=g&l4cx{L6F(4&Wr zrC(%1phj@~{qL?!Wx`coT#FT=JkTdoziLZy=XSQ^63JB)?i`Rq+TF+hWb*F^`Ev3x zewX7S@I8fGh$MlV*!}{8I9e1!=i$n?CEw!SBL4VdPn1Q~0;XI%WStDyt-$MaGHjaB z`kLF@le;YUr+bjY$X|nG?1pUEN!hftktl%4VVr9=S@(P@E?}(k%(DJf()YW-xjc0i zOoCbM#pTlur-n#*{PYyhA2VdicR&+Db67W!vTp`+p5NIk_p5n=uU!dCCmr`oab0O$ zUvhW1mhjfH6UIH_u_IL8r!WASzFi7=%C4o)pSON#!NvBu4}i4U=dO~QbRsqTf1Ts= z+-|YK@1w>M9uV8^WLV`jsNk1kSYNZpvpB-f)uWRE^gMY?e8NV2)Y$CEV+ea zfSL(bMFrIbpQ&o9ot$(hYxz`VLsqb-t4>FLCUvO_Ryp&c=3|e%R@=C#Qpn%k-88$K zrnpu4`Cq+!C~}B`Iq_Wv&dLRQw)qt{U;Zy&pxt$hb?SB(3!0KUduXk|m%I@>%psUD&kz^v3mq!!gY`_}WB@*4J zfBr?19UEjOX!j_~KID|Z80~BGaXi^X-&4eh640T*=^aZ6E9u=@UD1Z1v7^W7eXvvm!`h#H5xsXrh z&U(&ViyYTnJ`6#!A+CY;Dr}?i6sAUd!YTS22ZR2K-7y^+Zs*X4wzmT|?l+kytEV=P z5QBx(_bke@`E)>2fioB9ACPDs+aub$opOV8E60I-7A+%mfPi}&jB=VF{|1_H9#&Q~J=f!Ut!YN3ZY(NYEZT#f9?Z2?}{^s%h3*q0naZ{1=DBfj^s@VF|Ss zFK!~*b+8-1h5&C1#9AqQN;`N=@&Jrr#I3T1%!wS=xl6WcVH z2zd*(F*Shj2OV)f)8hCaap7j5r;iDov2gyqcphD)n0E<}OgI#%BaU5lI0}Rtg-3=) zlpgrjWXnZSS19C|&@I@6(SK3XvW>Ss@ehChN8F^(?f1{0bBRd)<$}*&V8K6x1-XPP zSg7$|P~%0RX|7VP;j0$Woejfc1nJ*I+!{ed$&H^5Y9STLKWH!0pwOk0YQO%}@A^m?WUA4vqIZ)ccV zdBV-Er4!O&WXwqOQFi|lzH0JE8?yb8krNw@CN+S@J`HI%+Ms`*M9l?^nh=9XK63HB zz1?VH&PHv|M70VKS|F<;p4tkW7Zpqfm+09us?+B>L3BC3%iuGMj7EX1#GVAJ>4EMB z%uAA{g5?A~4>VVYp@=%`@syiBQ-+?3@imAu%oIjKpaW?q>7W8glMIw;v1vR3b?Wlz z7WR7i^!e5+_-UbUzQUmom`ll!aILba(dosd7;`h}oV{bELX_AI?S^$Imm>&`Uip0I?V7nYzGWrChp6SVc|k6Y2-gWdbY z_tHk8&Fpi>*^&`%eLBDfmkbmelonSW;kR~#-|xpvMc4d~3Q*1EH(DHgSMbntKkbhV@Nhw)Vk z8qCRg3&XbY;NmV>yFbK3ZVab;t~B~vJ)Pm{Y(CQ}KnI+nH~8Rq7pGGXMKjMeH4B|H z(Bni=QcxYijG8s)86&PSUb0)tuR!x5SmAT+?-aUodWblPluIapA7kva9anNm1Yius z@)R#6=+*y4^yGJ;YZbN>2K@+Yj{m7*RG`6)}gtFY|P^tyUs zH-R0CB!h1R=DNC(-%%q+>16(iik~_S8A#@Zt%$?((P)#U3Zcs{Qk2M+KFyX%upq zfPtnT_WM@iXJdH^sMFH(&r#FKSwj_dHt&*j&7<{2TKamYKz5qT0)xen7-LG1Q90EH ztAu*q7Em!p9?Nq1m`@xgQ0+T078<6omoRGBvBJ*{NxGMDo(`g8ROUy~=_sBg;oYKy zv|Mf=hqr z+uw_@AMnQK?VUh#G6_aBi;LD1M2H5^mm1_Vt?Ei*b17=Qwd;@-FOv(ZOVUTseBpq0o zb6{Fgu9R^=_*Ri|pvD7XgX1F6C)qv;{I7_Ubce+wLnU3kxlUlVxA_`X0ZQdn@~Eh!r3j8+DZFi2aNE+_;i}1>x0Y4#NCJhbFRG6Y zcWShwAY5wUA>#5hEKXPh&3w*5;tuP7RnnS8!EVM0YH1D|dz#oq7@R2O1H~4VL&g1l zm*wdv!dXWu52}wBx^OR02v(e0Zl@+25q`%7jF#{@6U>e5f94Da18Z|zizb@=1zv75 zp0P#`#cDG&Jz)e6$kuTlCN4hK3H z@nDcmH6hB;LI2?5r_(OCmrOpU(`4+)!FHZYy*cp)cbN zrZv$@;_&l9GDZw5O9#aWwM@W|Bk$Rg$|`R;dC@f(~Ym{b86h0j3pZv6hL1(&MWEClkAj=QGG4Rl-k11!Ys}0?V2hrZ?cD< z#bM$;V@mYY5w~d?TXk0pkA)MrOIvO?C(VApFyl=i}2~cDJ-JItgB6~U2KQ5$MUgaN8us9gf@AEsj z3y`_}F_cpFFXilFDk($b9M;1PzDJRg^mW-=KcqEPfvx4rkwT_OCS}2p4{QP>&6|V1 zBmvPS!&6)BNyrd5FerP0W=x|rJ8*JlqM+Qz4^$qli)Eg5)& z4sW%m9sC_}`X@uQ^bVZYFrLR7SWbY~o#|3ir99nbHtz_^Fr_)79I7VOQKjwEHvsLc zVKclT>i7g2@a~R|4xCERgr=MLR6I&yiK)lFICZ_I^xm5nNj}@WNhX+8#}^7$0JnJw zt%V63gZaHd_vHRKzgZ?Wxvop%f<*RR`o@Y8IKl-yoV0B^$b8U5Xl3OFeVDa=5@OWz zv=vHssNrVup6&)$yq>!+&@~|~nBD+dV0xSMoJ>zysd~ZD<5N82x^#h# zuQC;dchl@<8jsZ%6Ujb>)-)*Xf=nqNk_W+PXN&?l zHC$=Jspo_Z&iNUQ-UYZ4Oul4ip7y&Qc;tIL8cKQg#e}s9U01O1EJ0g=TXGdg&ZqIH zpRmbf)rY>ksCH}mKqe6mGkeC#7X`_m4knS17Mhq`+1_3 zh*0@8)l&Ey&^Eqnto^$41%v-eWB)osFOKxHhlcmK;>aF3EVM~fjf#{&PqP0Fo1K^E z>ESNvQ&0l`O-oijpy7GYCOoS@V3g<}2Gm`1LwX7Bz<6XZ%sY_sP{LC3YjLag~O2>;d_M zUFl9>g7#m@I#549DyOM2^K9Z&on~0VYdM9_S;q;9!#8t7UsVld`TpTyA7gtj&${v? zuN#dtX+1g$N^DCuO3-TsM@+)L!ulVbRZ?R~uew%g~I{)SbU{Nt`! z2KXjXxL;_NNC^+X(c;~p;skX7h9WJHWC}Jja-#g&t2cUg?I7n=d9AP)gi0;_ORoub z6)wFj2$O0zLxo1X3I)rKKYPqo-HA2heDy_mg`01M%5AHGj$+q*8A9`b&>dR%zA ztuXtbFgfdeUCktJ8kh9%ru{YCgLdQ<_S@39OMdqi@`n|hO4F#P z$1ffyohm4*G>)!Hlo5LclDw(D5Y>erIk~HM%K@6yqnrS3oS7`w8RG{Su6d2fMr?d% z%Zjt*^8*8Z-^Yfg{pKXOH)fdESQ>$m&hcAsrKcRJ)IVYjWz)xz@DHYecr}Eh2F~&l zArAZ*7o_k1*j=^piqkp*f_sx1puWJM+?TK_}V}T|U0N zact}mnlkP0WAIeYk4({6<+=I;>1P?dA`GAHTWQS{sV2jOW0X+B5k5KIC<$&L@PmN48z8> z4Pl>>*Jw~kHj~F-$+KyUXm0B9wd{q}$~l)~R!_}bO?>nWLPCaIP-Yw)9ejh0i3H-a zKo48*TgnM?)6kRAsS^6Uf(-@@X?|Rei>2~HR?vnx$o2>Y`XR~57jkN}mmQy&6Ti}z zeOz*Zw3ka9tT}$6;T9x^uG4Kut2qZ^Uoou*NUEF*{tVJZhzOf%F+^upL@4(M6$p1z z&N_CeX-ezz)dJ^Wm)hCf6CU*4H$Dq_FI=yy@Gs~X>ubkUnLD8+yX#5W+;bn3S)|$e zdFvNH1oJKe8>=^zreuoyDSE3ETHoB$Z)pKQnSQmei>XK8`xPxwF2p*PSO(OO9S_W4 z=oOsDZ|uaCMooV&UKIBZb26QIzk3A? z!mtH8E%cBouUR6Jm#a}6Lh`;Z?dI*mTHjgR{;TCZ6!y+`vCAEuhQNkt8bsUR{*u#? zGOw4;M`ff3melrWzqdi1z=fr&9Tt?Xt(tDqyxdee8YMUJi1SYSqGaMMiU}vbIbCdV zlXQd3=>f!_irK(pu-fC$$^fO4uxzZhlH>*N5gJ}PF}n(?IBhHsq(q%? z_%|&|VEvoyKKYmss(@WkVd-T{t8PI(z`Qy}xzELca1DbE$%r8pGX*|hCbhD};Zjy?1DGcDkCS>gk z4u@_5&`J3Om{d%h1_}vnu!!zN+0o@OE3pBmD(?i(V ikFW9)_?bnc?_9E4SyAy z-A>_l*@yx|p*mm%N*K=oT_*J2pc*)`Nj%IxrZM&);LaX_kgy8iU&S5#;o#|0@#ect z2_JEQb}71@U5iml_UjOOpU&}Fjl#?LG6jp?0abYjrhb1meLy@OO(t{j;=i81Xmu*y zSU2R6>O8saDmT+VT5VbrJr@^x!&S$+X5*!}UKdwS73 zIX?gLHCq3ce(>p2X`1a6!6F+>Q~LUmGySNEM!nW_r8((To3-lzHb`+D*qV}5(SBsJ zE86qXwkqbNC}`ciquY{?SSb}Nqt3H|Bd7x-kBiSR*NEZ+Nzw3*_XIW#kSUp$gqvWv zRn&gBs25pUYQ~mekm-CvkAoypnu`XY^_{IR`di)hD6IN-i5>=dDVM5K9NZrA+bYhZ z-pjY^dGu*?!003Flmauu5>(+?#klhTDh*y05;%o0)Q)F}t^a3pdAH!eJ&BOz;}7GG z^C%EaIVo+pM)EcxRgqmCN~F1m`rGk#9%y)L(>T$h-BKVG6t4^|{H_^qVT+>X+jKC^ z^6YwM+-~HOBtg+wg+ep=T(M*twM%(Qrx|bpDB)Ze7EKLLtr_FsPEpq=AVpAz6l4^z zqti0}Yx3sOj$HOZ9_rqjCOsjl?ctO6KrkW8d)Md6K3=RB8*(7Fm0`_`?d=VopcXoW z)XAW-8W71D8F*hcx%tgK-zE!@exJujPuK;+*$GoEuQ}(W2%Rz*8FbVPQRVRmq=OqQ z;u@i+tv7GL4j>JnXscC`BuOT3llU%BBD@BnEq%TsVYLWttwNc=ZvsuipS?=$?gH(? z9WP~uwEgU{%IDes$Y+5M_Idn~C)!47JVK4Us+GPDJaX*&q%8X8mOzAPV09B;I$Anb z8zEb@5^ME~Uzf&fcAAMh}#bB0%GkZm{m>W8~Jnruw9g%J594@I8$Ow199^s46lELjH9gy$EU6wQS zC0Q9TeRm_~CGx z-O+`2&6zWY$<_Sk2ByNJUfL;RZIsYF##wH#McObQgT&xuWRuwx6ZcLCy^$0k;_wy5 zv9ya%fizTl>_d6&AXlGZQUZD345>r3yL^`21w)FN`3&@DQ9ImfQX{leB{nxP+t4hD z=|Q!k0XGfGBL-jv;X#gx`cj13bv_x2*$fn$SDMxe&cPh6Go;?|8uZ-e&632^(L)AF#_Gz%y_hEG z@m+DYykRpXY$4HpMk0c(T@|rZT`SjUp1IGaA7pUp%qqsXaQ-r<=Vx8S&f>|kqlz=6 z8M(ndiIPe#tgP4=)n#QJr~b;Hbnb8CS)gbY=a=R1e+KrqU`c6n;a$aN7#O^*-vdJ=y(-n4~2R=dSd*}+EGLN&l=Po-=pY=n$C>_ z8VMF)hB~X=v&)Y7Qg#fdLa$oqlKeS?d+hMsb_m9a|9zPa;go8);atJr4Ual#m67`- za<5LK1Z3*`4i%v>`X|A^sxRXENy1q%iCv;CjzH1P35`ASu6OMy`AF{#j(z&Ps9W%;`l~1q4%;8p_m*5<#QzrWHVl@ZM=)ogx!$ICf$Q*fjFt%gdP_fya(D`PC5bsyas+Z)R zC45t(Zpe#%v&bD6?4n->ANrwEP4`#yed)Qxe%*qO$szgG6)XwXPW{U*)(8lqhMYRpEI{&hNVrHopbSe&$%ov7A#Ht=jm4i@?lBhXEn)3uONip>IJ#*TF=Bw0i44IPoP0lqO= zV#RrSuY^1Dkb&Ta`;-o*{g=2c7*<0pgbhQusO86GW4v74Sw*73vT_!lN6n& z$QC&%P)3muXg^GoX{W+D%Q>JHeemMq80#Ih>Rhk9=T+EJg&S7%IIpV$y8;(1_~QBw zU;nQ$a359>+_8R}N593K)@;9d1#8clfp{U(B2<3>yO4PW)U`<*D_y!i@fIAmx+2FFS z&K2!U2HlJ}ws4MH}f(|i?y_Ykl z@ZAGpfUo)Mn0yOj;ck94N(cOM<1c5~)B>}c8hF_}K5;qL24RKd(;YOH&PK%t!H5+6(Ullqjs@Xwtk!Llh%q2d9Uo z;e4v=P+xz2)eRM}K7=9}7)amBqds`54K`VOAMGtTR#(x4gM`LVE5w ze$m|U)QdcURoh;tSW?EG`Amb6XQ#CBKqbIc{ zr0s1c1V47+oyGg*dTvjIh8sipf}wW!!idNouNO1i5A4mvkJ;KLC@DisH=xmxWh*Uj z4Z1sc$5Kp%P-D3@-d7~>hFOwh5B$~_vl#Y1A*?&Z6a5|^2+2Qgs4w|-@4{G}9}bbW z%{|)7)_i{``@UkP|AlsRoL{x0Gezgg=(^vooNez+CX+1&uMWxq3o6rGusr0F`pBOZDdkVGA#Vfz0Yq?|7BiN?T`=Sr6$_ST1)M=`Xu;Ied%+Q_PtfrcVejNHv`(1zuDDiMR2MK zE=?VN$4t;a+zdIFQ&qvx%*crwyocckQ$L9pC_?(hgWIxj@M;b>^_||`_Lk%g2s1CR z*jSK4pe#X_;D)P&J6t8Ny^mb0iQ;}K!4mR2R0ivD;pfm4K9gPMR0^z_ztH!^{N6XDXaH^>%pBKyxYj`oniAS4Y z7J-mj*|3Aj?A0bvwaZ(<&Akkry23^Ga}7dYk?lpbz|J{C@(yml2I^L? z(^gTgy7?3~Ewx}W8-YGL?B8Ys>z&r`9C*yz+M`;h)MS}C3)8Zk-_EbEN5&$v3V~7y zKgf;Mw!bqSEe59pU3yLEW@6)@{e2UgT{77EnI#JTO(Tk3_lxwHQ@JsVKEOx8_jWyh_ zX`|(i{l@c1zwgvF@_VRxm1Q3q@^5bI|32c67KP#mt)W;V&a51y7$l2U*7T9=&ufAi zNe$F^hH@T35{4S;O(bIy&IbJ7Q|XsK#n-aupuYs9fg)r3DdvM@J$o*(KYVkpWgx9> zlG*7*f#;YULwyymebF{QZa3PRXk@({j+EX=4~APrp;9z&li-!?Ds4f}^7*JYQC zOTiX|jG0X46mWJsAIFnT2Cy7y-dV}>WW3j4SH48htq;eTyxtqj$twRL`~;9VhgT5?+81-iW&kuR=;*j&Ec$++9{P9D+gL7fRg$ta}z|`M8(WJDmPw*cfBku_5NEl_oX&* zA&Mtb>CbEyC&mcA%W`&;X-jk(8drWS}`<-EsHJ$J;vPXb2t_@2cvZG zf%PM7<1h2^owTJ7q@Z=@aAP1nN`QaqxzeEv7;ssN>K|rk__(EASe0}>%pNZv_TQeo z>>YL2G{%Zkzf+_TkLeIO#S^xHAwrYr9gQ!K-ey#-64#Df8m&w&b zxix5PMlKAR$q-@bxPRdz#XG5ooHAS=EoS%WfF57Bw@Xd$H!GG+WjTaybK!r%d)_Gs zn%RTi(0S$3Vos)OYjpa)W_@J@3kvtb!O79dS^u#6YX9=+qU}C7>mK?)UY~Wl$NtZs zx<^MR@5ry{(Gym*rQs17574gQQ@$u(CchR8kkrl^VSs_aAj(}zlcpIB6%5Z!^O2lr zZgkt`czJbl)_r|;a(R4+-|Q4i)9|;eO4B!`^30#@>ei#b*EFCfpAp#7z?M7d_#iFv zaR2zc$D`-1R>i-@WMnRd6 z=d-L)i0Ai=P-l`8FGk5a+dX?5eT=6mU8CvN%ZfExvh!rdcp`i_IRBY!l!ydH^;-d5 zgm3JY;uE!b-dHV?DZxNPrN+Qd+@nPYXb@PCfX0r|+XATQ1g?a;k)#qv6Zzz0vw>9` z;-6>Xl%a56$j)_EoYLcXA21v10GB`Ezi>bQ``;f?ev`U*bgdV^|HES|=7TBRbRJ#d zbGic6bN=ZO5-pD(f$( z@Z83y&*BdW4@9YHU0qhgdi!0UV*Q-<$XVkYx`{y7@grXpQvYoh69 zhT0=E+L&Ywmzv@#p*@yNKgKxRj%YP+2z)Q6GfmYsmK6k-ha=3r=GGx_Z&qKu%7-13 zhQtVUIU0!{qGSwIXJuKJ;IY zK{h*tqmMD?b&RJsIj30Z)9XMJKWJm&R*kAB2PC4wyRXiFdi%1se;j=F|DK!&fA>z# z*8-V3CnFP|Zvv&O;y$pbP~mjKW&2~)c2x8hPFIoTy?p=T;wSwJuhqwy-oY?cW3*** z5tK*?djbK&Coa|t)Tt^cj?d!Kngh>XN7~j2Ul9(koWXh~30iaFDPaNSz?b~`A$oiM z!$I_4o#!1r{=acUP;Km^Tu*7IPcwB={}j>jG&U=X6lLq^~)o&XU%So|u|uLC~cm8x3U@;1wl@ahSrLd!JrW z>3@g8op{&FBD(YQZIF!CQdQ!d*r>}YkvyPaVq0$I+!22P!*{*#C3i&c$}xojl=fR;sy$vMJCDW`eEeFpcuiYh-~55+n#FZ59p6VtAF zYvHT;Xv9b27)@lo>r;MaC`4vGDY}kS+!eg<*{(0$@gLjlHnyTgm9RC<=QbOS??!?L z@T{l*QRlO=H&obvWU4*$lQ4p1+Fb@d%reOiwDarX;s0Di;8bcaAQU1LEz(p zHW;|WX+xnI++@v`CnxuGXZ1e$ytrU~n|FxErVbUe5h19NcuO;f2j)IgBBHphJEKQ= zlpHsWLMff1j?DD46UQl=z1bOviB%3fiJonc1VVZ?o(T&o+UU(N1}DuX)vdTTI1)Y8 z(k!jR=HTX^PO}Lu7;*tx(&S2Qpp(jH4$V7Cg^A9d`1z_L0gDx@6$M;PwWW_L=Gmj7 zx*zK!`czMzM~%_4S$&4SaJICWFPJd57OUEL%t|zyV>NOLfxrbp!SO5Xb+d>+zp5Jh zD#a_P(MvtKKIB2Lfc2!Um0#Q2l{?kg)>dGWbCEPAQ1%ITU37`y zrXR#_hpniC|Mz}Ek9Pi>r_pok?iXg#8?hIpp@BaO?XdnWlYOG77SNuephOLPLzC!C z6Hxnx#yx?f1G2H$SD0?hOba*v{6mT%ehwqB+SkqgPC#%Kp$;VD0Nw zYJ3B&D%)@p`_aogCo|!A|82M5mM{Ie5dzBc{rntQIc$EwiM~LWNBTQQodX3H{?S8+ z;x=0W9KFo@-emUeizvrZef4Li&t6S4*drfLriW?LEHx!Q&7V1Hmk65;{m?8d9NYkK zZXypVYLCJBO}o9^RD~(MKnj<(r5DtnIUl{qGx?+3W^?Co)S7q z!ceO!Ado8GsX`;<5mnPt7hrQl4(QpCV6RkFu*+61XRx1bn~Q zoZofci_!fv2eqmSQ|*_*E^oQB@h$XHtD52}$GU~ATEr>8>KbjK%&j0llny)Qusbr| zy*z#9iilo`Rkt@~cYxblyxPgVh$L|NvR2uj^Ai7pd0eJXuCNR-mjF+tGB>=$nESNb zYU1c*Zp?8A67Ae zN_G$Z>izZ0S8&~!SYKCvy2VB;d%r5{1++&fETmNwH&GL0$vAJZ{=3M-+8*;&L1L6{{REkGQB8!aGa&7N8+`y|n`ULPbwS zMSK(u@W`CrUKP1!v||&)=As5LV(018G`z@FY*T2^&u>R!e<}JaHHPeO{DSmRl1=n} zu!>d{*awZuo0u^zO*)w;)-LTv_ZJ@BU!w1ri*(_sHaq2l- z16yQCTI01anUDLTP3Nmh*>8NQ;pe#M|6%HmXYP+GIS-Ku%H3|&`w(- z9_74$f);Y0o&_h|(p-Mor1eax*Nq0Z1+{E z{z&s)6#aB^8NJ#6p^K)w!`}J+OPz=sGoIzavhSBn3k|h9$thDbnzya@w0IPxx%X6& zOt5$%ZBvp_0?9?+LUE|BQ_bK2SR4ZpAfM4I<(Wj4TP$5J0z}-S$&~@$$j^{7cNG8~ zEud}CHCf7 zEEy%Je^q>Cs;((sFJ}46(Lm)n+hfZf^7*2-md>M1WI3=3op!tzJTF7O$JH&F%jahEwUVa zOc3qsyRzWiGo+H}WUT@ZV?4L}2PdaL_4kht`=G}5ULW_p>{ZDw5A>za1w^cPhM?Y| zxFf#nc4RQY0t)wx^`@ea6%G^X(oBd)Q8I^yIBA_6o-{v>;^FAeH>3`92HCi^9WmH1 zd(FZ+9^5*}yO{EzlJD#f#A9^no(wx{WmZ>_Uwfhz>94^fF~d?Y5@Xsxwa=FzG?LvY zY!`n_Bd~RV8uP(ba+BhX|7oN~4;P!FrR)uPtq|uXU@&V6S#JQVIR(+&Q%6l{FD-6`w$1tP=O)Y%Z+@yg51SH;)N_@PeQX-q$WUo11$?itm0$%Kb|J;(verVy2<6!W;`zMuMc5_c9}Y=$@T-|J@8#GhoTtp~P2$ zxkH}sG4D4wzl_OiH4Cb&d$rwZ)2gpjts|{eHBfBY?z~@8!<0s~Vo7PQv9t>s%!*?Q zREnx$)hwrK&F`}tFe=HT+7Ml=ieC%sh$>b!D+}v+g~gJ&_8UySLff7OlR^x%BmHy`RaUFO+~QaSaE#3eUv!Q8sE6 zOd6W50AKbAI$Mk{2q~jxj339NC4TJP@!jlUiAlqy;HhgafFouyFso(4Muo3YXE`NpAXs@bwLd#mB2pf5L|#`F-E~erO%_-^LF(I3kGXiSCopXcJ@O zVJJx2FdGo!cA2Ih5C{Th>p(|YO-?=WEv5<=`KK3KmMle2VD@Y#`5;DAZ49DNs8<4@ zkce3PhoX$-2AB6eLsQ0JX!}^Nl;e{b*NI0SQ&+Cinh#Xwu;z&wfn5k>gG?|A8Lg0* zxeT=70aBDYm>jrC`t>qR1S(8!eH0GSz$|0>H%6?n%Ly z8VPy#Und;kge#ld;b2o>OGH2)9PVH2M>zwjw<8irw9RD~>Gyw~)8)2AfDl z1rLLGf+3WzBd?sqw<1e(4Ix^tj)hiXh-{u*Nw%AT>5jM(H@6iPqqxNFH6qm}OGnb)>0>=tJ_b+n_Dii0LC4MU#_(DO2b zBvyAO%Qa7Uu%BDummAxC>7l?Igafs?%5Bgnw5yqV>(pei_&zHpRIe-FS*!bId;8I< zIIYR;U@%Sly}K*f66zqJKJKU$2H(@Xdk=_G8rQg`(y8-FYoAb!d7^K7qWUyhU%nO&%uATzfp~|(W~B3cO&{V9w9v8u7fp0Jd;y= z+A!=5rlM61ELpYV2nk0`2*l2c06D*6mL5ah^}Q=9*B;VAa7exj;d_-v|!;zdiT zryFg--eO(OdAmsq1()f;Cx4c$(%pqid?uIGoZJ> zAhUyw+&W3E2=1?Z9R^VlPafLzW`)C~jFvv}x%mHJJ2WU0t^zs(zIwIUjcSG)tL?La z5%LMY`V!rgIuV)~gU*B_#D`@R>yKGs*dPdBu#4k#k_;s!e6_(Ll#RfTae8yh$7IS0 zy9vlFhR-MIn4rn9M-i{el1lH_O)Uz!wj47OR9l3j z**0mg*3chVYJ$f#$>4u0|te}tYi zES+Y&RO*&BTnF4@5_-H>4Su;}7K4(mI7k+^N6;#BleD_t3ZpD0KLoKj84p?b-z{j; z){hs)j!B>pt3pxXqr;0s&FuLdDo^5N<4)Lk26cwF2_B>PmNZb4Yv7bIMf9HFp#5F8`FvbHV%$SmpB?Dq4 z+MM{;aF0tEg2`I8pPzHVW{`7jEZ+5ER14L5f-}u?l&a(W+4HTftqwg)DSmL?5x|y0 z_8MbazsoQmLKW$>28Otc)T+gXuHN|xHulFqY;pFc7&KB8bDCZT8~Z;9;EHBZ{F+jE zmIvR(u0sy=@(i1;<^c40Ny;JuVP_mr}nzM@lZLm~{e zfA^<9{h=kv(#JqH+^jK$4}Y7xX0X^&y9+eZbZK-!OuK8kpzOe54j1H>wd`SH%4jxr zD1gWg`E=Ja7A||tJ&Eo(J~`?gUsNzAyK9B?1Q3=1Ponq+4#$oIr2IM*Y04c6Ketsa zke?#$H@L3e++YpRT~X|zKf%xD+qavChZncE+vD+ep08O%zXCQr+#(=r^5wZdI67 zC%3-d+txDc=VKnbKEwJMMdcT=5)5aa+|X<1E`5*Ud_&|tYS`a>^D~X_=Y~_zc6Q3w zKixGGxYhT4i_j3B2=YNYYW(uco(0kjikEOJnT*Z7`zf4*7BRuKXC_%_ z`R-l2fL3}`UifZY`!rm{91^#EQ*!SK-juTYWJsNP6bQI*orw+1zZ{{XJ(Je7B(}`+ z&%vAS!GHJp%5a?`Jg;qDZwc+NSH3BtVQtcY}NJq#gFU@1>a*|-D?`FdWJWPzWl`LkO~5*ft|_+PQs-V0Brn~*Dq#jxYjNKM2+BpVykd8zjmltH`l;zMc*gQ&0=%!! zqsliCu%ZIlYjwur88px92I)HqFetu{8t@AR_JX{?Ut5ouOH^L!I$U(q6Wpr;O2A`! zCXZITl-#ar6*Py%P65^hz`$wcWN}`rMn+N8E&>jrgn^3@QEY7D9cL1kCImCYL^N7Z z6f7(702U1dzk2#Kh?}?8QM*a^C=Dlnt|mu!&A`pB+53XD;tGW{zQeE~1lz>uFp$_m z_DVDBOsGj-$)AL2D0iy*SLd5uPYI~aKsEeBZAgJ;rLx4Ilq@H3^ps7s6{nT7?cVPy z?7Ht81dmpxZ>g1k^I1L3g*%^!6)H}1X!UE~C z+2Rog9DSzsoDI7q^u5i9iazFyw-&l3CvwRh?CTuIk4CB_p~Ln_IS=WNM)7_UiC*Q) zuOiK;VBt_Kf=#a42GLe0jjOKft}_Fk!5<1n%A>m1mGxN^RDht=_8wfy3PdPug(G6s zaaV$dJdOe6Rzbh5;|=?+iAPiaxze^4+^iCtwTRiiGI<7Bz5c$sc(wyFN@H@z(Bap}a~oMW_#DsPOT@h~K(tu-O|A_K*AL7iZo5xBav3`Q_2Yd7t0A z5mYV5-2ih+*r9Pc$)ok%^)@=A!GC^>*&m|y_mg$woYO%U$Oi=MBmYkF=E>Qe=jtJ9 z{PxqQh5*%n`b0iIw@EsUZj(>(Fd3j9q6E~T7$v{MPci1`YWuc;k!+!D{Px?!h9*jh zM>p9NJ{@DDCrx?IKnI@I8sq$2WixrKM?Y!yS4AI(F>UAxZHK^#mL7a%;82GgmZn$f zOkS5U){eJV5k8&TpT&a@5tPN$VX`Y&;=*ql6urHDIURLTfe1KqC1$z$gPT8qZz zNQWtTX=E#SFGdt8JNWB2yD(Q<>pSxC)2F$QL9BOg{H=55Hd3lOh%p4=iZNd_t?$e~R#2`^kl6!QNnTgt;c!X_ z!nb&*z=4gEah}YYPw0bowAGIA?J&D;(g(~(`n40M{c9NE2*um}tN!Kj`DyoH2WiAd zlZ>Byc*pckKmLwZH2m@h>eOLlxwde$l;&|aIzNHovpm@`MB>Zr^rHYCbA-y8c%^Vi z#7#2OvcMF$1i=R|>QOrR08CMct=_4p02ey9;XQJ0(Qz8J1@E1KhXMyHOy-zd(RL*B zg0;x%XaZ1BC}0iqk@EG44#fe2`u4vu_}IK4j!s_poA36|j(f+iS(5V^FS<~FmTeaX z&kFu_R5U>PA86$U*)CxizW*R#_$bTnFs37$-`q+dNg`{Vb2rG6Qg^&-IL2of=K)Ky zqk_aw;9x%O<6rm^E)3X-f_07_w64@QJ`#z=gRic?ZtlS#@EzKTA#II*OJ?3a98}?@ z+YUd}HCXr<4%1mS$VSE_uDyIid(+7~_VJo%%G|*yrA#+Oh_Z;J?o>YP7+boqQrj*N zfxlFI1fsho62iiCQ}Bm0WaO7;&gS#b9`B5^*V80%3(2oi8XGPEN)0Zrn`tg?LU3~F z-6ND*k6y-#XrI}YwQv>tokvY>v+kr@&&KAVIs*cLK}ei{j#NUIbKzF#HKN#X69Z7P zh8K7~vnmaTMiO{LXY=VG;h-aDmB(G9r+hXcCqGctHaT#LQnh1)a`=xyZ;(I@P<@Ou zY-i2%4rh!1BhUh{YX&UAM-Yt<7yb8%($NYg350L=$AhEG!|q|f`9zh!?d>tBiJt_a zhI*oxa-tTw%z6rqb(i|}A7bD5FaiJg50wYIN=2xFLOI_xeA5HM;|UB8k?Dcalz^TG zOzkJgIwcrU_-9YFl%$bx_?;SJ)RTwL}qt}Mkddr1E1vQIKlKKR8%A# z>s7A~j3?(-6O^bbb5jEK^;Gf9a&?A=lRW;^A0~IOQKL-`*o!}n;MK;Qq91ZcL~ubJ z0QrTB8e8Vu_O^r&_%E6qQ}M7KdFC-l@@PgH=>t-er@cBnJ6< zL+fPG+VjmrizO$swW{VnbVn)7<13nixA7Fgt=Y2&9lGI$X;LeqpN^&+kvT)BZ;lGz z*DNHwa48IKAd%vHpHDyuhZA5EBO+PYr3hO@-rYnX9uXO*?H&>^s7qR@Md5QTrYKRo zEl;)eE;NqszBM3FHKhY#=qT+Yr4;CS7td~0wcf5P&~Mh&^fAOed|JFC6xghuEodx( zD=LQZ0fU`3QPaFj%ndrx#)2|kCZ~VDt-$)o^J9#$;1Ivkbdv8M$LNZ*sDis!bt+#gqvqypybC{TDhk9@ zFy;D9#GmkfAg+oB9(GAdPu)+re#>BR#EzoDpI4ri3s!5 z5=?vH>_#wq+eZsG| zsf)(K^unD2zM|o19lY~K0mj&|c5_dvl(3!IJtc^w334Wc10u;ZmHAjMw$blrv z#)${bO{VL)pRSZ3p&1t@5uvJ`bac+K8QT2mb;v|-K7z#X~gk@l=&K zVzOw!EMhzP{3;(z(8He(zVj#OfNu}QHjmCA(!iN!-C^3LnDxQEihr&0*C#uG$CLfxPwh@V9P5HHtKAG8bwWz zg#&hLAF_V^_WXx~=)XG8aT#WJz-~f@9%sX(&8Cs;LA){K$*aKcIVh`yNTog-cZ@Pm zwpGw==XVSmhNcL7Di*jyyn?g)yCx^(T(Snli4F2FX~29!`+=|U-X3!c!}OZ)f@b&# z&Y@gv=BE9G%Yd#8AeSFR{3JT`X>>Ke5fn#;d#pY>m! zoE^cH{-949xxiBTMX67jyc#Kb^WJ@J6m%`I;{w79O_>s|(i2y;K6sQ9>F{oX5O+%(==WqFNLhi3alwRY+$GMSjGsR>*zSE8h@#sVrxxYd$SWZWnNqQt5 zkL}zYkynY!DEb&SJ(@c!(xsbk;l^LQfU3eJmhoYQ9bCP6E4OZ8*A{yxP1Ip7vR^7G zIyq+_LJ*zYVufLxBbUc^E{}UZGUg7^sRm{XcVqT_(ZkzJR(a1C3zsHJ+_h;4?IPOG z3gm?D-t`M7ELXy7n_|%VHFMB|6W>wR=%|@?l1@>I^LH(|7I}g+({$Z@>h~oBc{K?l z1DS+88z-Iy`HoYT(RX}F&*rl{9VWrzm$GGYUr7XO9l82_Y0agZF1aJp5|7bqQ=OsA zaT}%d5H`i@058EUxpmZ?yqky$jPR;@TOl!mHhOHM?M&8qCGM<+F@uF`hIa)=U3|gv z2;Ii3;QYL=ub$nE?&8p-=u%|7R&&L4HBREA?B)iYzWodtBrvO^lwGy`xT^gA%Ol&+ zp;Z+xZNvYR*{qqzw{w#D#p$e`$Wr0T`BcxxLRM+f&iI{ao{K?jiY_5+NnxjFn!dpE zsBE#YbLQG_wkjvdeD8_9!4Ic-@J2!IS4-kkz2RjPuh6m6BJ~hJtKob?@FsPY zy?rQ8W}9#?7E9MN$%cr3JVf{CJ3AG!==!@)i0)p>q%N9)dd90UwgK0e6g`esqycVx zlTCWN^JHvDg2s(S1Uab0(rq`I(Y@Kf&B;DpB$t?Q4}PGAVKwy{dFX6o`!IiIQ;_-D!5d2A(*;+C8}FcmJmyC9^?`H`<(#lLzu>Mcii?Q-PvSr|NPmMme%?p&3W@t~q-^S5kpD zbC?YBe3U7pE`|iPKY9>bbuc?2KiGvf{0y`dbk~;2Qbp%O12Cgrhyuk>3!WtI zfVJOed7m8D`t0|&+#$;WI_Ed!;-23SU?YF3h#R7Ny~TP*8OXp9{HOUu+EFiUS!p+{ zs$&043oJK=ZKWVCS*p?zfiS?o*5Qhxe5n35d*Uxk`M0!VHa05O1Rb2Bxe80yKc zN~U^nJ`umd-Kf5gpHn>Ce$g%p11lnS1n?Z*EwpXCL5tDc9Pt!5hE{+^gfs8bfEdr4 zZwf^UnLvbaPw)rtI43@G@O9XRlQLC6Dh}7EVv98G<}jO~ya@_FK&ADC4RAJOj!J+n zXMA6u!VHKzcOO4;?i$BVUV~{=+$&Hq1K{FBgi8>1UI^1~Ty}bcY$t zhm;yE`$%aGO&yFYxHolt9M&kicXoXAaFLV~qj)y+jmQa1ahei(*mxX|Lwu?ZCK4!28&^eG^XLz3RjTX;o`&8f?Bh5s8o;s-Q z%+*|P6Ai>yusJ}O!2I?5SMziPYXJoSUi&#pNZMno47mkuN9PaYtCXA{*{9h(0J!!r zJtn7Bp)0Maaw-CO%XYfizYB-vUKN2N)x^|N-A>GQT;K3o(s zNYORj`${Ix2!+yenin74e5s%WANOXWAp!$wzTS(a<_NH|hM~`$;_0+da!%xn<+NbuagPo1^SC z>=nAd#zEsVHOJ8_U#z~#MTZ~5B{?Lt?1HbUs6X^sn zMH8<^)zKnMAGWoFv$EJbmD1DA+-gI$g^J9Fg{L5eOL|R8Mrrx5Iwdoc2Wg zVgC)>RnX(J5r+lw7Ev5|FQJ9|+yrVBm?$II8bL)`N-?>qaMC8;IBoCPP!+49GJKR= z&qyGV<6Yaij0CG|&7Z_1yU6{$Dm-YD+IG7>Fmp&Ftf`|xjg)bAKUphZVC&+8D#9R| zPL|cnyYHn8>6_c8ABjPM8&`MovDv8GK#i79VIAMi<_fjXCt{|`6p7EGqTJG(PjGy4 zV`P|h0R!`?Lozfgp(6(#y+b8sMZCGFZzMZZ_RhO+xbJQ%`mvatFp?(^q!j8i*kEL| zeo_r5A{+gr=A@cPY8Tn~KB)zYa7>|wZ@X^W@}_o20B@-tY!s%8U5k`BRKdyWuJJ}F zPegSbrtv7d5upN#<{X87OwqcVY+sFO^K+`VjxqKiOH1q|mz4#JiS%b|;+vut-hX@Y z5*CYI9YLS#qIc$7yu<1pg2X6Z@eaDIui8@0^X^+pICyZ@yXYP4A9c^pPR`Dwzeo7R z@yY&C@AdK9?(xN|{mY{ZR@rCST_JdT|2L>n_PI3#EQb zwR04kv8)Y^k<-i=8j~y=6E>h=&}dWCk_+TUu!)SX-^bGI&SN-WI8Sx!?$LJdej z3J3)rsC*E6_%pGQA9nXI-f&V9)TOjB78dvlV?~epbE|EJgg{2g9pMnty{v7h@#13| z%N!L`x&CnQ^r_o}yn=b=Xx#m)1W%+4?TwrTT5egI{^pOJEtfF$GRLP}lgKXT|Mon@^tNX#umY-H>2nH8Ae)|hI-y1`(Re!ribpNS0cYU+a-X+qgin&Hm zZGGBmFTMft4<5K9^y*{-o#rAV+tapbum27tX1Blp@&s1e+w<2T?D`--kNRibQ}~Dk zq2OG|sc}!z13?oo4c&Zv*Ks=)O$8-mi=LgLI~|Bi z)l%r#(d+LM--h1idQgCFbc$uCWgGGQQ#wi!50r%^nMQ+$$81`B25vPFeSHgbp66AM z$9U}^cxlPZ{NT`q#5Wv=&UQ1NbF_KI`I_CgLCCtllV;QOCdFH%^g)TEYdja=E?-sL zLF?4{F&Lz0r$w=;6qMEVT&*#1x0Xv!TbxWZoNtnGd45`+kX5(d5ks2u&H*|enxUmH zRV%$ig5X9)8aHj2|) zDSr)1DMm<5J8)^hr$9%v57+Q=wP{R;;vZive4D7=xTSTNxNrejJ5?S0qtJtx9;hP+ zTf^W%vAo8L3XQj@^8--F5BnEq`v+YVv=bMQ7r+Ju*D?%ltn(Hm@7Qltd_tKXDQ z+mFta7^qKQkTdby-~YZhoG!q3W_kivjJcr(FD(55tR2w`91vMZHrCX7-+}OtPbP zH7cw6Sg9uOgzDo!2&g|*`d;ZSm&*Iz`K~YVmF=ZR5Vtmpp%-f_LZ{K@=5yCwNrx-o zmz4CH!uJ*Lhkh)fH%tG9Kzj-e+#!Y@-k6ZB&^>N50L2;TrRH8KVxf7l+ z{|Js47nOP_&q{2VQI!uV#k_;@;MOO~$op+`G;+o7w8oyZgNH44t>>w4)ZR|3nl;s= zWzn9h*L9U+=jXlPW5k*u>8NG8;uW9o*>%tOw#Dj4{AZu>g706EANyz@zS!#%{wk05 zVsof^xVJo(L`K@FB2ar&cf6;p?3UUbOSlmxu z-BCrG3KVZXE4fpcUfP9;r+ru}c2PK}YRKj&4`QIm8~Ui9TEhgT9`FxD0`lYDhThH* zyE(I1*dls+u#UZtQsv@S&;|U2!W;75b#HR+ns0i)^cw#MK0-SnicCUA;&&*^>(Cu) zZ?%SXYh>+LEjTX)b;zkr85G_gX&#_B^je@$I2rs2A3a~lyn}3Vo!-o+n1+CJas=)s zp#wL4-{){5Sbhac=&hC8V1MGKbH6Usgq00Tu%Vl)=D-YaR`9&6OKITs&MJ!b#UbPr zm4Qs?BXmILpj(s&A-S+18je=-9=dKe1P*ySnom%a-Au?}BeNzI-#31nzi%iKW9R)O zOvn=SQb@{oeLaGUAEt7GQXV~Jaq5O8D-7jt?$DFjJ|>9Z=|s~FAT{I=^>tR2Lu+2P zwm>UH)c0Nc++IYPcFXA-E?x8zmu6w`{TZsp(3%val*;Sd>oz$ieE-d^C1QJf0v~$M z`(RlLpbaj=Qi_14^n$QuFo#=+Anz}FtN(<}KpRh=y9@}0gxrglp_yf7>ynk|x-Mph zEH6XvZ@CKUHk*Q2vgO_gz`hVJEhu}@kitzW4T2jAcy>zAEvI<7F`laUM07G4J)j*^#OEnJ&Xd@VLo!H>z7wKkF9LVlH;Bv59+G2_%g*u0DSaUMGgqh(1l?>*4Nc}mVa zK`nj~_rN^Sn0kbZE>wqq~{?TYG$O4S8To0jE97ygcX1k`@2GSl8b{&JS!04 z+%V+zr$B`IR(!MM%IW0f;$pZpr}L!b!cw^6A@%MS=TCG&km|`x0hU~@1XOrg7msMU z6#B5n3Uhpi9Sa!brs8|&XYesjN2n?3rNH^3PLt~qXY+{mPkW@_qhpJ_%s3)0kNfz_ ziIko5y;JEb+4yq-Ue`nhEPL~U?DuL#Fou+5r3X00mGAulEKKRlW6oI}MUDl!EuqY* zpu9@U*jaX*EgMhGS*Gb4oZ7^R8w!M!4yKCbQB9QdHAj!NVLFHvpLM`llL%`eX0!v7 zwN}y5Z@E>(;9wcct#?TTgCM{4nR;f$lWg)ZMhHA_ zzBWM@ciCt(cd%11l?mlsa%CM|GJs?FqL(v3*xdszYz6!-ln8LXdHy^|l0p6st5rFZ zQ#NiniMHvlev>|n{-$|ODxnFK`|kDyD}6S=R~kcRNV8(TK_G?!?QUWT%MY-wZK}Z#&M?SVDLQ3_A=YkPIr9L=2~z?b zGA|%nkGRF~qkf7e5cQeHZ!5rc*LnS-e?|9f{hXdRyXU7T$LGD5y`$d6Pq>5JauJ=sxqS8NsC%fZY=3$)CsUHogW&JZIHqks zt2W$Uin_G-+pNvlMPQ5L@GwHo$?djzCSQA2N@+lC@xH{PF{au>ti!)nyw_4cnr0!H z_2+l}*EgEUQTFK235qa`suZ`%XY5dY@eX8upi=$qxv3E5T4;wnT(==q$)Mt zvO>*gJXSmKaRxHsC?OXU&P)JOqq7znImzsRTT#C}R=g{@g;iKgc5#>q4rfT|=tb835k0S!P8X#mMQyfBNbOGuKLScChO6e`?lV^r zd{!!sL|ahzfgIB=Ox25(WGZ@3<=%a0Pd`ova zpb1;?NVt|9@4Zmb!P=TVgb)R15Y{OL`&X2fAb;!h6m+HU9&adDizv3qj1)7Nqi3`u z$d6A2dKnLdz$M5wh~Ds`!>byo?=>wSkg_Jgkeu)kVT#(iePrJe5@9so3VSi6Fgjrd>GEBK0>DLcbS0V!o_>a z&RbUsY}Jmg<{SeFvj3(iOxQi=&y)c}C!GP)75886%Vmiz2VHS-`?UnUTlW2?!z3_vd3){pzTQDZ~$7vzSxl`m9*O z$btagtK<{wjTLX{dP=HhXm?>xRUz0JCdf;umwOB7&|3!8T=v^S+V#f!2?t>J{BPJOK`-C$wh8X2uAI*XE^7yET3&s(|2q^ zDw^%mi3{La(@3W&GL53)%T_WZs?sQ>l%TnWqHb^XA&IqZjD+BuJ;kc!v<@{$Xl^Os z>-zbd{j)>3u6&EVb^CD5k*Z(K(1hEOUG*(utY&_#7w7HQ2N)4>>P`p zgY=uDN|E%gkICD?59UHIqCUzoHcwq7o&CYfjxn3Kxi<6J!^i;^+Y?tqetj$WW?;@v z%-hGyXOT5+Nehp98(YbiW&)NO?x;_U#ze97C-JgLInFN-nT`I=CI379(VGmDPpfNb zi$>^!{IW56-$@wmToDt=?%|dGYtrpFj%JWj6h%6aaAFp77y>z;bi#4o#<_SMF&rk0 zm9<0`ZI%s>)IGnC-kcntobe*>9l!3Mo}FBr9Go0IvYqXy_Zr&imKzFUzmL+~Xs~?# zaHOh}LF_k)dR1SJpIUij-gJC#uTHEvw_Ic?+>g(w^m&4L`LZ!eGCL;I77S|JbI}Qd5`;2`iMtqC zT0DZI={Iv!&M~mzlOgPe4^%$?4b?2VS>YJro^KFJo1&9)wavW~x+|aZA$X3}n;YV< z>Nd}t>>i{r9!IGph~)ZyrF@-m|8nrq9@Yu#|0I53UP}`h35)Y!}CdPaRg^jMYcD2HHY|{(Cz_xZ=IgC3+ z>B*&cqV(xgv;5yBj#qb*-rYlVDxwPcl*3_hn+?LNnnOo&fjerFFKQud0>_}M<7~1& ze3K3QTe^Ef5L2kdvgH7a$`zzkob#^9?Y)#zudr5s4I_u6IG^d3MX^wlmQS8kZA6oF zk2V^v_A_!bv^&qIOICRz7<*R<=RPiIXuRBeWU2w?@q%~87V=LS+*(A7Vxlq%MNN3+ z#)FxIML6Ext=;*kqrmCGf&2AWkyV5T;m z_&038d=uxdumOQ(YMO0e#0;ImDXK{Yk;k+a2zD1mC%_wOKaitdSh#q ziDT*d@pXnp0);_=yK|LvjW&5N?>s^@6eRIVy)PYnz=+5M)B^*V zN9r8HiI@o)X#fmyfKWYjR#cZ=&fV+8muQ)p!?^F+6vlKc z(6zQ7W`Gv-9e<_wjXH0>V&#JCHD{LQeyY+-7pmK|(%3pz37bSekP&*qow8jJZohD~ zKIYSF9yl+%U1G^ingVb+cbqs>bfk6!j*vZ(jrhfIUwiqV)yj z$;tFE1zof_!f5k3Emzx$T^v=76vxeNsALLA*oE)N(Z@E4AJz`Zdz3G4=^NV;kHFxc z1MTvc3(QCO?cM}-4~Xrmjg@al580d*z=p3=u&y$S_b%gj8+uG9e6UbE1S;2X%^0&S zw*$&WLXR6PP3olVLZs|!kyS0Q>Nxz((z*I)bGXDBVX-B1B_0>2>fgv>%gpbhEi`H^ zlD%J-)AtFPkWx4lwxaKLmoDk=s@G|W=Y9t3n%Z5sH|lr8ZfU~l0vBW#BxjQd#k-llVwN0~q zVXtbH45n3rL#j~rgt~V0M8LR}zPkA~X^fdZNuoqPBc!w9GO#%m<jo-bT&oME8K_kN%~i46&;S$=1yAA z4O#$lOuZ`Fj~XVi9nNapiT24YLCm5g9zt1V7k{8Poz3Y2c`{)IicD&zEX6h3w)g|K zf(--o8!?Ryqu&;!;BG*S&PebKslKY-=;|IK@&3Q*?+or;l9W+>nk+tU+sUA*ok+ZG zV87wm8tPN+do=504)TywUK7}+kW;85zsmw3Gdn6GyVhHe|DdKNS}EJgZ<%0M93Y!o&z@mFmK}R zW6~b7H;R36JOlpVWnAvWgaqOlVP#IG)>>d%%^R+JYAnmW^7ADau& zn|}EL4H%2I{fWzT$v3uIo})EmO<}4d%5lVwU&1W8&5^6Vu5&sWrU@M(di82`eE)ee zBDBb5{byb>t$CbF7Ef!O;izIb;-|0Q7CPHCFMbkpD35+AN}R-d8L|&x@aU%-E-;|5 z_pGh@b1J!eO3@L~6K~fi@nPy|-toI3&#{1=6Fj z*(ET7a`xdsrAII_tQAsNZ0zU1xGY8Gu~K0gldGmK#5b zF2pzSLBiW{6f6ch0abhn&RyPH=j@C>|0*|R{3#QPV7;GB%-5ttXT;2v;TT$b&7ba3 z&G1)dr7uEYMHX}KGB*6?Yp~gkgZ%0%Mi$bFg9|^Y8XpW_B~36mYi7qVQuf5MNse>|b{rzN}0*rzApwOVIMk=N>(0C7*Qjba_ft{QR%*6qd!X1C= zbFL86RGY1z#zUK5$tnx$g|E@+bQ+;6It}n-axzJdU=Yp1RgP6}-=;S=!k5KIgQ6eK zhLl-mT0~xHqJllJd6aJ=WfDB)<1tWO;KOFyTtRVcHVpEU#fy@ z@r7$BjPp0H%Y#OnhGD8ceVT>@*jz|vxv+C%;F>=6Ia}&38ThDJ8Dd`RZl|8x)HS|v zjk8>yxyWnGtUmWLh=D-Q_=fDf$wPOq4{zg7w=Jl(4p>$#c+M#59F z#`nXMC2-mMdYV)bp|_+@8R@y2X60VnD$g#vRyowaaeGs;6KnYYV3DTCSmUPnnwH zJHujl$0D@7zyz*_Y9H})K~3Fw7^!P?9p=5SRb*WN*f$l2XlvfBKqpk&*5L5`%Z`UcUIQZNx>_(%^Ep@u6RTS>p4IHkQ9H2#xK&!t0D9P$1hdmyiYd) zzXSox6k`HrdVrx7Ja|#5Xwc0ov|E|Gz4gc=g5ODv<13%KmpNO{tc9`734xzgm#eqlphxCqJy}eRJ zZ*J0D=~*PZ3u(Xx?$B9~V%{u<47aLCMqx9R!_e`+!rR`b7!i%yD4btqlvE@}Z|0Zr z6t^#ThEGEzZt<%%b~={4==SL0d^{J~T@x?iqnk-5l2LO;%q26)AmJ$L13uxa_Wcyy zo2Leq2Q=#8FvZ{36xHiq-Nebk7*v+g=t1%XN@E>=_`!%n?CH(Dg&YW}bK37^?!6wx z^Y-%e^sIY+-oHFP>+T=C*?)P|?H`;RcKZTs*ninQI(gTJ%`=#0^6crdU?Juq>~At& zQpzO3>W5RpLtgz&P^u2_znApLe39*wPc((+{q0vGkFNJU`lw02!)=>c*7E6L+-C${ zK*fAZ%_Z5l)i>NV!f1iyoK5Bf1pNq_n^5Q&#};9JaYQMgoKflwIWs!DCQ!#UF!{i$ z02Ppm8jC4oCLSG42YoN;(>Ndoq5e0gOWbFc3|z3v;0u)jarKa4x;iHqn9w%8<`e2_#e&> zO+87TLez}aV*pn5DSa83CUqd@vkhuBz&aIBQYwo!Mmle%@Jo|FvkoZ}#1RvUv~^#T z0mSq$<p6N≻p<+r}nA2kg?7 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�{Jb-Udv|K=x90ajD`*@tY{&PiWLjh` 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%0DE%DOm#!sE56LTN<6BD> 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%0A(X_ry1IJYL9u#!^wF*TOS?KMW8Z(ucbpZy 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

      &R+p4qmJ5naSD#&Ngm(%4R^{y&dk>`gXr{_~ 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
      ?3D@_ycVYb*Yy3(%4}K%KITu~ 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
      K&2v<6-L+=sj|R+w^<5$IRXn1;vc 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

      Ky@XG2U50Bu2WT+}cGZUrl^nEB1 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

      a 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://github.com/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%0A;iv33QB9fb@! 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 z zRm-5}NCz{1U%L65lSaGx6#H8mFXcDovh^$fNO&5Ufx^>ut_oAWaAt=uDYU2CcLzTG znyk?jkLvZY0a5;nE{x~MPdT3IDvN5(h^~}N%)(H#PGWjlrJ-B|5B$=UzE=2$-TWTC z|DmcbHKi51tE(a&7)5E@t*&~`I8=bC?_=Ba>BfBNM&lfJG|?3#&CU0T2ja2eb|*p@ z574MOTpjpI@}H=_)&W7liIl!U@_9l{dr|zfNcXS!9z)J%@UWbRPt?;N_? z!w}&4CV=dCS+tnUTTJ?CF1OmL>xmMI(sr)Mb{zDLqWfBLu5JSAm6u$vHDk~A2daP7 z{g9(_^yxaF#{hK$(G>h#WRgJJm9OpNHXk@k{T8(<_8kdxBV55W*|0VSLQY;I?-VGirT5Cs7y(XSlMJYEmt@V$v;Y>c54Yl9Yz{*S{)yd=U?tew`91^8576g)#tSd8NQa!PaJJVkBrFLm^E5 zt5?63mO`Gt(y`ue&>Dh%opV&0VK+@ma}x0y$JH$;*Q<)&*4de{Vsf+l z&r-R+$!JTrrMH%C4>%_!?O%_|h(j?>J)L>T3OA!$q2{GAbGbpN&SN&Ps|?0KHZGBq zJh7xcv)N8}028V?^T=-%>}?=03GEgO`cdU34`oGbwwp&v5FriI@h+E&=x)4ow!u35z+ zX)p1zqLI6_qoSwQW<^)q!tAmN6hP6t(c8XP)4@O_4}V~DL~5;?`L6}y1Z8>|=@=n? z60NV3h|83U&DFNP+GNkYaYDv-$WD;i#X=#8U|8=j=?;GV;wQnMfw2ha7ZkTgzD&=D zPs{Y>KK(eqaXWj|1dOq%CZ1qc{9LdT@s)u2yV%ZZCThi8FkE;1zlEG*I~nKuA-MNxh={eSI>ZT|C+GpscF%-Y}&u4o>X>}rO;yO=VI7cr~Ov*FdIeIZU5Zf zzc_E3M#l$7?K2DsRS$ve26JN$+8~iw-&yeXOsZ zTI|oq&|!$Vw`@BzcIVol_G@_4u~f15zHi@#{aq z-!?EVC(mto+BSGL1&H~HM`qWe|0iBxM&u`M>&*uJGC*?h2Fa;IRL_$nx|vKyThE`1 zwf#2jjWfuG0;==#kC)Fk|NO@nfBBB&ez>#KAsOHd92}Tu^P$(^hZzTQtNK{MIWL)I<+RU;FX0fJ-YUEx=gEX))w0XKVyLZ> zTQFjiR9kR5G#djWozdI3?0o=0SE=aKnhJ}m>o+X@#;6HLHz8YcelD^BtDb&)i z`53bMe=kaBd^?;!C+ZIfob&g{e{XP*9G#+5;5^|xtk(36QLj=<$m^Cwi z4*M^QPR3-m5eF&4q^cLorRqf(T6*+ z`2mqsdgEvW9d2X~G^z<2(xEL1@nn*LJPl~whfai|LC65To|^m8EGh+{(BjbDNY*mZ zK@ra;I}NHe%+;J>j)EkrPN)y|dSWL}DRhj@YQb5wRtQ!JRpq$k24uo&;R5lLjidp@ z;9_YsIYcmw2EZ7km*W^B!0Z;vvq6F(Wn-Hxq5!+KlH8L?k4_oQ;G=7DICPE~VE6F5 zOZYyyP*8-~I_Vq0@r@$flguqF<>af%Va)L)TzHNwVUs;+uXt9-5+^A^{UCG)&eMAH z=_S9=ukf$+B)Y=7 zKm^-Dpo8BYZ$@!vyUnlVju%0iJWb)rWK*tl(+a)U+dPRaa&4D6;ddW~HH!!Q9YuS+ z$<#;{>|F0d#}=Y2)kqG*cLFd!C1qbqC)?dH(yr@RP9;D6RfF z$g+>obYzq1C`lAwqk+2vlQ1BK!`~%LItuf+V}Cu%+Jo1DU-_}+T?P__` z(|DP+&S9WIHp#lOTr$Fatagm|2Qo|h5#I}S%V?GMmCRj;Psv!q@A&$}>rp~F3Nv(f zX>uR;CKxp5D2yO(*w$ge*b7?U!=+2uDEZ`Yxk_eK5)N|^%kaIe$ggckxI`KCt5)(K z!Ih+Q+Wz^py?@?4J!>CyU!R=4+dKcZ?%n>)Xie6lNyL|v-=#gzeo?a&x~;7R8qwDB z)lo>dh8YYRW=q&Vx^?yW-^C=d5FTHmL%H$SciDJHf3hP_!)R9}*K4b2`TgNp29_;{ zUK9fjOR~OuGOPmNNPU`&Nwb|p?K$nf62a$U^R;L`*KoAH8yregu%OjmBfM&Zj8Rx- ztMO|qnto^~nT=z?`hx!CM_KQqVg9|3^{0do%@`1p=wWNVK$r2ZcGsHK1$xN5JIQ3x zN#pYR6PstU3Gn+evFs3ElMM6bk5U5m~oQ;SK~~F5qnzRvfTC8zXA;#n@0Z+AM4&sPTSd_nuE^IzJ_vLXPBWL?eGTl2@D`x!f_!ZrOIr%dBEX)lr@Or=jc%;|Co+At$=k0(D_SC zw9ZC84gg>T**oarLS-Q4_r7aT(kDe;8k&a8|23wRi7DYmdS(93V4?5{wqJnrC}yi0}WD1gyYU=+s*8cDGKZ&kUUoh z39{inRSF9DZC39vNzry|CYa_4c*>d63Iz)ehwtz+DskZcMmaeYODa_`e*c|whxhB< z?yHltqrKyU{cg8GE@B7#ItMgMCU@{^wE34mZLDu>n*Sm3HowQ;;m;Cc2!b8VVxzlc z*V#00U7CM$qi&#w85E6szVY32!>oEYt((d1z?qEov_Afy_*2}5M(5f4bSQNi4wAtn zj&{iTN(oxJ?{0l_^ZW0<`|eNQet&V)h}IyRmHD|ia&EuuzIJYCeDe-H;J9-Kr((1s z?1P?MonJcV?RQXJAR1#sa zT1VL(=rM*sx?!t-XTCUyCkS%8(C2M>eG~2k-&FPb?`hoUUiHAI&#PCj5v{X;GHVlU zYdd^-fI=FOjqR!mFi)RGPb%tAnC@Ln)_D2-cQC{@)nb8U|J`?;{q9fgGj>yc*ZFBb z`ZGL*j}xk};EJvkic(;n1fvV)<^u{Z{O&J*`JNp-Er7oDyKRueeQVCvgL)i(wUi&C z=wz(7UvT?0ps>kk9ADqYf>3=l&2Jh`9t7n({*?HfO_0ZYNv!}7mc_PUb`8Qkxiu0_ zJWdA>A|CogfH_*a!A^#7_A++aNFhg&D$4(;gcy}RDMR+kwdnN>=z%_aGW}b4zL+;f z&tP}7ke5zw{hb!tRAjZs%i$B`mWQ2HsFDIWHtEG_dtsD5bqyykAZ0!@TcBso+a@wZ z$|vv#DzXvP4lwP~N7FL8G5@ceRJcfz2nXyf7Jx(H|t??O{}hv+(K#x z@`4#Aca#Yb6HH<3fY}`*DC}^_)eA7$i98_@bpZ^yU=S3_hw|%hE$?Pm=%Ft?ED=Y= zkUJ1(w1&@HD@UjM?>pybOpUFrJ%hq|b}_ly{N8gsh|TA0HUy7q`jZzOAi0|lbI2sa z>j{UtnBVKz+;1`+rrlYKLIegCw(w}0A+-2fow87xx-`MX^4! z>VpwWq-2@5BdmBhxyzz`TTw3xhBiJ>?Jn!>2DU~_Wkb5##SjarK~UW72!s(oNf1PG za&QA~qjD?xz7v&83#LKSTOHFC?vzH&%%b4u6&hUkn%P`i`0=njtv zC+eGjJ*XcBxCS0Zv{=EMe@}K8>~;jH7FA{{00OH9DEylK1}%>usNMNPt+7KCOoO%$ z-mrhhE`}4N&9X_K*~EI3HrPoj0uzQg1$BiC=mw>6?YvV8J1)3yJA7PH0o`_m{jy-T{gq-YQF7s|LS!IfC%1-*k^P#`5h$B)o+d9TxH zpPfG^FAJ<2Ls_Ew;J^|0`JjVXh@pY3Qv7&OEx#W{^vO*(gO>TH4_mDY!KqB2gz#C zbo!gPGhHTBZ8`sfwV;vj1vJNkCd3v!0^;Q<&&>{ z_^?s)?d+CvV9n$dOXU}x;$bAJooC#`a7J?dC?s@s9{kD$HAn0(IQ=s_{GFd&w4cA; zJL~C*H~zyUBRxh_)r=)ZkLrN}OBel&t%+ z-n_-lf>nHq(@>6fBPQD=5i$85_Q1 zz>qV&`3RWt9}%kg3jX*WMO3f^Y%%7M4Lj&M6t|!*QC6exV~i2-zJ+>Y@wis#k2r4; zv#I^j@2$Tx-+o_HgiEi78J3yh{OS_jUaaBib}nRjf!=GdCi{DxcKg5AAOsTxYb^T! z{hCT^iE2h;<6%%QXw>jEnF5O;Ix}HM8`eE4_j3Dhp@^^m4QFY-o=vw%-uWH998Ny@ zm+Y)pXI59VzIf|IhqVwXB{4@oafl)Zwv?tQJX{YC9K6)97-RKeScsg`5Nv#kYh4jx z6F?;}IR~h;dF0uUvWVyffN2DVDNp1*2aFa`^QSUD9P4w5=aphzfK@;xq6F+o0x5^v z?gki)l7@US%}8g^tvI*{tIB=7Bm@=ZDIM%$j||hM3*ulZl%-AUGMGZ^lg5`5++Y4>}!1%n) zOy8Cg$z^kKi3Dp6hW%ty8No*3d+4~mcUI<@2WXTyPU7*Y8JM%K7I(r-1AhhQgnlm` z(J#KtyZEk&=W{^-Y@aa{TNG8-V^Jizhs96tVEzs)>6n1%GOq(J(K-%brDFX4<&K6i zBU(>63*5!S#+Dfctz=YNZlZ(DvACG*bgJjNnzQk0050qgSYOH9+wq_^i*cy~g28EJ zPb7P+Hfh6y{9-?Qq55MOf8sa|ppaTRM=h2U$EhYO^C8oj~}rwPO3c zgg7=#^>`NFK~Umu>Et351MU>^o|*2CIJA#AuZ+%uM@_YUVNJ54zOp_C+Z7kk?3V%j zjTPeLV8XGXWnY5PhW+wY^Bm^Lzb;ZHk;bGfN-|48!qFvwP8P+#6?Q`E63( zdtiJ|`t)35+q&oV?hikptjEf{`Aarsat*N?v@zAL#2m=x1cMw5V9*O4p3czaqnC{y z)(yP%QJhau0m#iQHo)Skeejp1-~xVcI(&C})ZKq~+I`zTI&GhI>)D928E2!$hwA`KF$~7Zs25LE#O)Fbw9FJn;{bFVIV3yby~#Zfh1vudm)_T&dVc$L(hHWT)}S zVUHd=?2#Gv<>QAvf9$a5X4vSl!@ha!Y~PsK?ml|9Zyr0_H)giGX0|JTfU3P$6|7{b zs~BHsX56><{g++2(HGsiQP_0(h#}qW8+M8;U2Fd_SMn(sm#n@p`?~bo&bpxei*<<& zL(fOEHx0G<;*Aqt$uEJh@CFJG9oR&1o*WKyC@%>`2>y5PDeujGi7j;;aDdR;>Dbqd z^3BKckNm}>d(Jnl`2(r2lQ6@{p|;YC1-FEDgq70=AAh(}AupN?n~~ zwOEe5P0p_ea+K*oQ2Dc(PO7{l1DdyEsLdDbn(~5vQSZgxK<4kDS={F9Z>X6f`TUI) z-a^H<;qaDPeDhSB%-m;x4?WA}Wi|3J?@TYPgB_@chxu_f=}5jkco49ed9TOWbOf57 zcn7QK(%fcC7KaQ_MhA)Y#Bj+L4S<{Y0bQ$G5r-Xl!632Un36hEQs}`$i|er==F|vY z4ZWRDL;DJ%4Cyu*BPbCZzyEIA1PiXZ=F+SXP?YTeq`qTSzX9xOG7Gakn4; zlhMEH<@3>p_+5&NfcF%1A>smVZ2R*J;$Ttmori;OOAg}hEc&=+PlZHb22*bCla42B zci>ez?AO(7UC*tp;V8@9?e5hua%vFIU7>AGQZ_CfBn)7(InJe;tbIOZ7cdfeCRz6~ z>AF*3U7lJq7D4Uy;`V9!OM|E^zdXhJM@<>)9pHlC8dgoD?3+Q9r+4&9;lqu6e`07zYZ?lQSf zhupLKw<$c&?Gyq25Y>+0g4nV*!y>Oi1-}$U`a*u>z~fHA)!BF7*vOmA3!sHk*Gak2i% zvs&&l{-@8)+j9T@MMFc%f9A}-%EEoc`F+*Nc~^Mx?kjxxKYWLN^{GC3O{=5#{xz$8 zH~U`?yS2EY^^L?1 z9;bK7s!_1e<2BAPM3k!kjxiK8^Kr${nLZ>!A}|=}O_3u|2w`NvCjC%ooHqQo55W?hJlqyx8Aww-4F}NR-7M z4{d|sok(NW(9t{T=d~sbT_M)^0G34k^`6-VUmb6BuT)6S$o!;I=sgr{{*uZ7^4DT^ zHC))b%88IS%rQm)kp7^>&S%mb{=+WZl<4VGV(wUT|Gn6ZE>ozx1XrfH6mTGlUA1`> zFmDuG84^*t@1XIKi=sATNHL)s<`71ILePASH}2_&zW*~E(oOyS<|eg>_+M(c`2re# z?>D3twqc>ipG=Pz1<=%`+{1M(W_MNuiy=w>I^?b4j3{~Xw<(6R^zfR6$0E$LMceJd zaD;DPL?7S9_dq9fRo#T#r#d>H8tv9M@d-5gQv;^CJsXS3UG`;IyqJwe{f~yfecxCJ zm78($g(UfQ4l>_?>-^ec=s-mFmtphW9BjTd*nI0_vwxF~@+jlT<)L9McTnsB_Z?;n zjNx}9YC-|bq0ZR`&GB5h9*|-U(-XjfPK;~-rOu#XHcqco&`=aC$&!s0%A53RA{joC z$h5whKxyS6fn7<5D8ula0q3La?v?qfP9LrD@drW8q3Op6n%8AfCUKP>O2J*ZJV^VpEoY!ucp%i6I0k#Unk`GYMj)3X`CB7(9Rd<8LIIzDloU1w^|W7+DpL2@R*0 zqJ;FzodVkxtYHXGCZvxxe#6wMZZiWuyBgo^YJu8dYwtAco-hZY7aE}#WrQ}v2yNW| zc_aGWJH1ccC{1A6L_T|+&6(lG{XPJkGf@D@5Eq`|w|a)(mS_0lYtQgsN3!;!BqNZ@ zX9Yi>LA3Ae;NO)g_U+f6;Q!zyc)jovAWUCMH;}cb-&F>Q@h}wQE>q$*JlL6w0aB$V zL)!3|P|I3aC`q2td^t}wFe;s#%w9~KEzWIB;91`l(KUc-aqC{D*N z70oo4(kyt-m=TAPB?ZJ0sHj8b7tzzFtRB}XNzYTmoL}#6Y*4n=`-4AkY=HftPVh=!?{93B z5ViIjm%?XkrrWZ;<9&0gxkf@3iY6K+EIVlFiqUX7v3m$z3@_fo^_iRdL$8Mze z7OBE~KGUo4#;zN3%;F3Vh*|3}kna%4Q97L7WAjrhAsduwV@t;2>0q!LUMstTbO#FgEsHmhJx>0hC|U@Kb3z*bC~W%mQPIF4m} zQg(zZO=LP6V`C)f%Ies?gr+_LcJ%5atj?Nk)sFQgYxoaM+&XUqsTLrY!oHv-+ z42d$PcomgX0T^QHd6SchQRcBM=a1>eAq2&~6GNkZYR(cw4O>#UwLwnzD$dhhbPU4$ zAUYky!^GDuN|}}m&-FQEi$>K_wu^Fqv^&Ub(e+JE>*qc!=W4#|e#Yw{x*--!$TcJtwn%=kfaZcJ zs;p(OMGI^=Q2{}$&jGl`m&Ox1gZNfuf44G2qctYA0g1U0vYjlNCvCiHHU z@`a+C@}*+`9%XrYk9pPs%l+{6f(rKxfndRxJ4vQSZmki_tcr7dRI9sDp-(-EVDRvDt zQbP*Pa@do#hD)7TJm8F zz?2f9d&B-XOO2dl))9(h|4h}E(fULVl3aRN!IQUo%KQd&YVrJ$CDF$M)OLQpE`&7|IZ&9qOdL zwY)F3wSNv&1y2SE6qhI#O%QGedYS2v7gOx#)2plWzQ``oCujH&W>w~x8At@kR%;?S zg>Bz91HL!S3fo`%aeEH-sPTn9N8s9I%+3Uf;O=_Iex!Jxhh)+F`eUHwIa6GeKdoAbPdXwS#`=b1S3tNii^ zvx6c2J{=id0L|^oV2ahhgtD`-#0rg5vmdVEJqm@SuS?$QDXoYItSwg-6*NT>Dl?|M zuL~GS-5l^G@sKVVo|uXfvKLhv4%RZkS*dc542tz)<{^lMRZP|nj$32H*vrG4a@Er>-|x-h2H#$tUaA$q=gQ zxJLdCAea{hnqR=tTiaMsJ`iX(?!gBP^`UM->{~fWDt*Xh__B}6ZqVgZaP>s5{VD`9~ySB##6cu z0tz@%LH<|St??}|lXx&tWzq6hl!D>~5a`Vd--7VjYm!Z%yn%s0_}1|`8K1IJ@q)w0 zr+7krQGt#xGhv0Jadth9Z^aiA&^|TLq^N|R6BM!czNW_j@I1#N#F*I->U~ac4-4xLPMu=_< zF-$f7av_?9@LQ5>)H_XzMB5)ESkD$q()pV&Z>?Y=QjI+}_g7u2ZUvU72d&+my9b3v z(L55{o6ub5#2U_H=QK+M_KP9SR6jq zpQ_%e`MrA!-*>ex1k?P4O5#h6#O~G>mdb#lk@jrCI(Ch4+q*GT>B2fnrE~-XscgFG zRlRhiFbxJ;$BN&W`?nr8{<}G8wEio3ILy5P9$E$ubKdPeI}6*L3Jip-rEiOeLDpeA zunN@!ukUt}31&YjppM&9X>#X$fMAES@Ys*yiP?phQ@EpbUAC71Dxbo#?nb;_px@8$ zQ0yjG4qGO>EZBZL;{kxa9fY%IN5beNt=3tTQ*I<=u7?zdO?_9)*{{oSNelr(9 zKfCSbiF86l`PWoh!EXk)+fi-h^Y#}^{wvM>>rA~k(??GY^tj^8mYf#YBto-7A<&`h zegm-6@;W_?k}d`%(BHUa`2(DuOWFi?^+$;E++ERZMRC&IXb-Bz@8v(?SM~A~>QAwr zeB)OH^qcQFSpl?;U7B2p{u$vwXNpNxiH;FBgl3LbMuTq?g<6t2U&KE<%WO_xx@(m<|}7FBs=f^+xGr{b-5ZSKH6Oj$L2D=9-0O0 zYa)x|qy(F)uY~=Iw*f1ZgLxX=nTEMvq!2#p0-vzv0X9`C7clJBg3k>m+6#;X@_Q*$ZbVuw!uHC$R=77-r4A{tyyX(H|>cX$bz;tGHMQ47|_iqk`{~?NKW1hF0T{ zgmml@@_VP!-oTjJe3ZIjV<0A)e<_f=14d zdoRw~{3I_24JB#0ItoH;gAa-~YJeiDU|(VX7Uo~o7o1YX(o26$ z3ry~DS8W4$lPKsHQYDhZ17NnG8w74po4}B{1)z+rY>EA)Qz%&W9D@1vKA)$gRFBnU|0at`uxbk@7AJ zz2}pjO2wrEi~AbC@QQqgTyM*knG*L6SbQ3-D+VtI$lLDT3E)#PZz5&Q+HaCl8^cz$84fVKsAR^Aqm&=vd_p|ForiPCT59C30EMvmk>_&$*K@Tz2b$h*z( zyx?|QVD*t`V9s5HaL3v3>WgSMv;dYM-%Sj>&En_^c!%gHD1IR+i!=48X*2Us90bNTjn)U3 zk1q!fzzz&31%D5Mr*e8^vIb9(XUIN@VJ=`my#!eb+CvlcQlq_@O!{G(_4;^Ja^suY@SBKdmkko;=Q5Os>i%E+CqAUix?5!y+{@1n&gh1RUiQhtE&UX;E0fe%@e%f1>SUS;r1 zG^J=eN}Ohn-5{ab$DES9myB>SSr47*`VtzszS)F-46OGFIcp9XCU}FRmmWs6V#WyDFesfb$?;_@49rhU%FL5^oZNO{rBHJu+ECe zawB=&r1u6hY!4R`nvP!6nG*Ezh^|9UO=P8mfdg}s-aZGH-J9$V7^%V7aHH0WYv9xv zSYMXw?P#Gdy04)wOg2dz5PUuM=SIC1&3f?=GXp@eawGRpw&OV0KpZPRJjl^>=KSs@ zVGx8Z;B6t7RC&)LlC)iw;NYY8L+Lbc754h}Z1}I|k5D)}Tg4%_Wf?phrUD4I!M!=R zBUWB7-H$>@msnEZ;dyTwaRN4$>~^!EWN%drljdbm>0ppt#{;T6?V6&Av&bea|E6-W z#aTAX&=DZQ()327s@eDD6Km#hIi1XfGY5*9%rDs#blS`Ka_~?FhgpLmf7lJW0@>4I zK4L7O-h)wp;MPYEP?Q6ZK7rMMYcT9_sCj_kiMedFw36fn?-86{I#j31bg|M{?s17K z=J0RYl!o+gv%BO|f>{OhhVom_+geo{>cOn5C8XUHg)>>`Lu|}8E}Uq;z=`zX=yF4M z6`!Q0FDoY^D@8$pv!%Hr0{fSlo~$>pMRY)O(AD|^mmJ5#e1Mu&0J&kx)+IlH3qbJR z=R6#81dL2dH^4Bka1toQxS35fVqr(N$D~9CSgO3`J&TneJ_0Wvgs)o3>CxJcTK_wq5;emQq!b0yeFna^AlL zg<|u2IKhaSk8Qc-2Ay?YaG)Q86VxcTvX?*sY<7xZ^?*^MB9X7%bMxv8K*RRW`|Z>7 z!;|CAm+#T)xAgt{`_eM&B{G|=H%{^EC0BZ>g$B7+4J9?{M4Hw6z#Nd`KF~e6sG|Nz zS69^cqlU`nq{xGA-_do;C3Z?+XHllMQlB*e>2Ga)(ci0%M`72$PvkPlOSM$3;9$>?J63TX z9lmgQWqYqUTO za0sd5L1i%@;ycpwzN!=W^<4*(m`Jxv^TQ|Xg5m6hu;$mC4hll2l#I*_RQI#W!;j$( zcCwJy2s~|_dBdCllmLphT470CWb!77N1hPjBnW}@ctJu-5dtkBOu#puB;oE}!FFec zbYZWTGC`Vd^+@D%WMA@G;G=#XU-Cv-|B(mbdqz)!4XE0z?Lz8KR4@XK&*}cvq}Bv!7f}udmHgxU83Y$w(T-Y#z5+u5ybs&3g0_l_w(`PR3BUcZk^=Q2;^; zUqT#9v-lKT@Vc!0($rc988ZpJXF%N-+_i68-6{9d+4W$ zXp+SEpc(*RmqBPm30MvDK*vORDT3oV9roF5W(1qpQr61cgSk;>P`sf%=%`JHC5gv_ zho%vYj1F;?_9Wj+d`Xp8VD*Rzs58G>Jnc>(Vw9KzhhARYLb5V6v>Ti4XtBU`&1vC2SXwxJ26eiYH;7 zE?clX%+W9A`aoFGZX&)W(@`Hi1MG+FC+Rr=O^0>oWd9`S4It@<=rQ&ycm>eQbC!3h zEGIg9bgvUAQve{LY$Djs4GR2#@qAyrRZa z<$#8a#SlZB)$Yh;hkYqqic-E;t#wI$%;c7xo;?oUJmJ5uvc9>cYIZu8=I@%zI;e$_ zyCkx&PJ_ho)aeKWpkwiFTe-PhWba`R_ThMJNS$-awH8YayO*-;ihp%F= zTwrXv`0NT0IwkO1$lEb?9!S;EOu>G0T64DBu8Zqjps@=2oENMuPD#G=%>p6buZdKz z&^?RkCPrPK7TtQ0IyTrvzV<$JQ^lV4ujq%;bBX@CVFpG!zrayc06^-`ASCsD2~^2B zsb2iVHl^3fuDKc{Qc}K2^R9#_7r(kCBTDxw9ZbNL+y9oS3m+4ij85Q=_7H=N!^kOzGY%EROPq8rrwYx`J7afcysEv{OU|(9Ba64gM&-7}A75(DR zghMT;QXBN|LofPu-Gai%S$bDPKx!8^*z*`{_Rpgwsr=}t%8NzX2WyiNdCK&ij;o1)dx(N`-k#tOK#G}`;P3%>pq*i1;1 zKx}{m#+zL2jUq5=K(Kmtvy+5p_h^v3VBIr^!bpL#sqOJd*#VoxM!JJ4SE+b22};Ft zycWHSKPG#_{u!S*d#cs*8(j&Z%SnG9{kvg*reFym?=n^>X`zd$Szn zwb3gt%_=S)lr~D9tIlav?iII?@5%i?XlPvs{CTsBlK{Fuhu=70*?m>Jxusxag%d z0n0objB$dFPpM^Q1!p}EwNOdwujTNyfU{QxLd^*Lw~X+R^nGw(d8O2|%r@x3D=xV8 zD^F5zoTF&dLKrYV( z%Y1P)g4h2m2;4V2$mm$#S%Ui)ZCd~a8IA}+60C{40N%j-JmA&^ znh!YdD=1;ZokWky!;$;Se zDE7mgatekgeAIX$5Dn67)f+0#7}s-6ac&h-T4v#p z@k9BGvX|)24vi!x7jq0gne7C$)6PBI22%MHw&x6~{amGEH&l!vjV2gi$|7HG<@bcM zL*K^W7DCR%r&1#9@VIlncYJ=hci!$&ZkWAJyL(C}-9VTQ;xWVXbpk9D*0MNBU86p* z5wwHjLsNe`mVGF%KkOmIMo4|Jg0YyupX%wr*A$Xed0Vnc&Z7dVs(IH@p(_Tuax-pa zl%|%J!E&+9%>fKy8#b>1F{8QGTG79p*^v#&<0*OzH93G&JB&8vg$;QOse3uTGgyNz z>4gQOu2N+^Q!bv_!60`Liu?=(QF!pC;Tes;rs)@CrgGpBC^Hn)0)5)R%pfHSM7XVi z=9FL*>rQn{1gyndGCFao{x#l(i4PYB5l@ld{p2SW6Z>&r`i)w?is zr-wtpZG9KdvNqoz@VO7H^uN%K4(ls+bi$~U46eG(;A%T(GG1&pc(s5J=%h0B8Rx@h z@m}gYd&e2X61<#1XUT8q@u7H}+rz#-%w;A?>=B+y@~oG}^lEHhmpR!5*&)O|f{ZY; zxgT!9vPm37!m7L=g%lroIeO1!&e+G(>3XRAK6BHU*q~UO!vu<`HfCfjQAtG>z=~Rb z2}YbpWd=rKvSplbTZq&`4oxl>BxHT0&x)ARrvS*=fWH9kGYjs3p)E0mxn%ts7%qYr zQ#7LzyKsb;qvTg1phhr=UpcasES40NWQ3z4hB)9PL%C^r?8wR2aBl!Oi+VOqL1}!< zx-*+N;RH7hk79lsKcNk(n}r%QXf>!d-}@H%8K!&vq_Y(?$7AEAZSu?7<6RA%qA~mH z7_uK{#CEGJzt#PxKQdx2^>#>?@mv#au-9C>tvm_-Q(yWlrd?-Mb%Q8ux^)k?`EPdB zRS~Rc0$Wq2-%<*ZFf%H z0Kf7Av5gre1j-y_Ui|=h(}Q)t3L!phA6Izg?0ABYodMFk4}icNm|kD^rtmEKM9dMXm@-$5o_KY+zkFX_yC z5Qq2`*|MvtQ5wkHKnEpcu`K1Iq}l6N68XiquGu`5>c%SMezy-O9ywI2swa3}WQrnx%$g;^|UO!+4oM=&4GKhro;RDodPzE$JjqAK$vsWI(=rA$YXFyU{GlwabU>*HzvIdUDPKwB~nfxOITeD zbB$NSrR6PWL=Ul9?^t*ZAP@OYSzSq;bMtdu@ehv~=8zwn-^|@~kU-HeFttErglY4) zX*`^ylLtu>hhZcJ20YT@s|CgpvG?g{Fdc*S4?g0TxK@<3u0_#dmvR~O*!)u8G@Bhd z7+^K~Yg%jAYrnP`$@i@)Ab&JHFSG1pjsMMc|360b(X3E>4;ru~q|CxWie9m3Ax#(1 zejErQBm$`L45d1P$P6Xa8&QTLoHg@*&v;z=6kf|Vjr`)FW&{~MPO%;&$=Oqm{UOb{ zl7P6kk!PnvNjyjBDC|SDc1@e~xLs>Xp^^C*@k96=w4kDP0X1+x!Jv{A~$>c8K5I8J64)JiG(&SE_ z9gz<5gjK5G60H>|0d%nYHb^8;U%8s-U5Y_4?vse@O2vL3J7jg;vhJR^sx|(&#`(I! zeGA0hn$gs6_lNYVpIk-Vcb)z2Pwlh9*yvKR1w|xWi6bhcrE<(I;JQ+T*aBY`&KR{!EO`kI zc*AtbOz6_sGe$K|JXXDTOW)xLpbb4_6f&V|UxI9CIsP=c?L;WH8*bm%7|7xx7e?|w>wO+}r_w4Y{V zfRwP;ri*C3${?T@_N$FWHRS8}6AOoWh5v>rKLzYDwf*wO{^{KfQ z)_a4r_mSiyfbrMq?TBH?1F4a^bE7e!9L0%$@ww7w7f{J%F{;0xf#Kr@PhqI&yk9+@ zKkU9cd3AWyUXd6pD*cX;LNuoXpcF0G8iWW9qxU$!JbIU1qAM7LAHkwg1SZ`Vv^bbv z54=n+AIi`SunD>_NF_r^OGo1~9WmZXJmiGn@@O%+OMCeEy0ujTy;l!xn_xRg-Dbo8 zy!X6Q!)PWCqM_5y#m$_IS61-$eM9ofz-%Zu3;QQWCuiM*_UpZiqw}Wy;H-V%{&;iN zZXdfpe`y~boxDfCqNNwCXiI}DGVXz0!IgZGe3|rG&;wLkE0_b!Fbu@pmNcoW*-JrLLFPCui+9XD1iO2k^~yu{BPAE7TglDYYm1Y=xmmzgGZ|n@=%p2C!jo zI=V=UG~7Gx9MbIRsD0nr;R7#Xq=NM|mVEf=4@mEI7a)?G zHg)`rI1Xf9ok&HXa@N*s))av7J6;A4B)Kbd&K5)|SP$a=;m}Cac{(wBVV^#48@+ar z*A-AB%*WG7Rx8BwJ3@#v$%$j5Xq|1Jy^B7@;}q3s)Oty<21|CHOemiST@KcNCL1Ko zK|%bMgf2`sa!YZA+8k}P6iJvsprK%+(ofuhMaSU4X+b<3TS{*8Qbi|5CESU)lrWgc zho9;-=vrg{Je^M2H10CmxvYv6dMxcdVnZK8<$L(o=;y!xzC`S&h&JAy?Vb(gcF!y?w3obw1+UJYWe(JBvp^0EY6V zAcFm8blBAK>67?lLK6{e5G1*Rnx+`Aoy0?s=&%_U4{$Oc#gxJO6`RSNpMMUa)1qmsdqSMvT!N!(Hm#r2G2nx1^}@? z1no&Tre(|~#8?uwLUKE6_aJ8>yhSb(J3+KHu;_>qD~RIa18pXF1gD5itozjGloXNT zRqa3YU)>tsY%?5vim9$+JigAU#7Y-m2kP)a6B;)vL_NMBAsf8?s`JacSBHDY-e>>M zNyq#9@T9Zi@zg0AndpAwRJy|MJ&Ou9PKVUCdkfNzu-?M$%DntA-#b75Mg9V{`ZyCE z3`#Xd8>$vT2^D`N!(iyfg?^qm6-veNC@QVk&*M@$I=Y>C=Nzy>ouhJ-IkPy#QZ(Xl|V$tp}6KE^t2)JpcsRtzw@{G<({(VDw*oD&&!NhIP26ew&_6 zq;Nr>gAs~@BOEZNB=B`pAWN}-yMvw9= zDQ+5=QaT11neZnkj#DywlQR$@D-?JdJzql(1o*5yXA%Tp98MqxC(VXoC>91qqNmcD zxU~lV_` zRLP|dT^I7exq#)St>j-@Tfvj6Y-7Z-;enndB3Ny$tRtFiMcX*QAKfI5A>h zU%4kxbU=2?_7%b#D$^RxKmV9Qh@Z_77`FD%aOT=HL^V7*j|Z;_MdAIvu|V6`Lt=al ztSVdPCU&#ucTOV0@!q?3x5;0+d&5sCOZs`okg{L@2n&412G6CiN01_Gy0PtZgA|GV~j;X5+vCF!v_V zAkeeqgQidhC#B6$Cr2sY3|*vE!zZxXhU{4PWs6Mif$9H6?-tm3tK{UzN&BH7LqgN< zM^*E;2e?HJgDUFNdE} z5y2}lq`e8by}Z3e)sFQd04=2u*H>bcK5dU0J31+i zSy8x1x4`s3qcitE#PVN}bwb5b+i3koiBPPDJyS%MXfDsq2i$QmNIL0*RbRYEtj(|w z-f0i)5kq^(bFhM~;YqUMYp(3%1v6#lgycZX37^uoNv`pLz4y1J0cOE6(oPL}Jo{G<@g&Y-&Tro>1>V@#mQho~~Avn1;ko`hfPoN__ihB9T ztlS=oTvO7qiea-+0}!$E>{;q7a)D!N0J`;UaqKTff2GQh{lslZ7bRH-?*}btg_3>X ztelA%%F?96X`=1Yc6NWI+5I(osimnvgx<4ffAv6y?=F~{gQLTm|LWP(pE$l+oZKRp zUU^zFu_Z4x&817m;nvK{Y`Lq-AL_$UKNEaj<`P)Y<`z&@7x;l4@qEo1hZf^K)jF(V z;YK@d{l{sqOA8Nt1^JeMnYW5VX{OW=5bx(`7t!fnr=z#B+Qum2R8zPjZIMQ4h4#X5 zdfR1bI$xB_K5?yz$My?(_>8k%akw2#nv}x|jJ$*FZYxNk(y;y_MP=i%Z*13V_|1#H z;KSOhre*c<^eIl^d^OmjwP=fe>8Sc6vPL$Sk*4Cj>`Jm=9su{yjz=OMEjF28Kzen#zVuipow8iX&QeR!BfT)IBGjE6&dY%~Ln2Jr*geTAzpY2J&XUrsKf zw|hUe!E|?U*x7p}3sGUl(=?d({hVdNskVnXR*DAmw)UQ8kAgIJo+?}k78KGZ78%7P zx!_yK4%JnnnYjRF$3S?Q&+wJTl6WV$p7k>FI z^{hkY8Xn)U%x*H|Y7pR{S$KT@fx$&-g(`nJ7^obDT+&va2^lyn-MTGs&p|(_*p@;}hf&{5f-|i2Zqh*?LZg5=C%Mh7 zTPBUmT_ygN0rG9*UPQt`l7Ijdgie1z#ftz8!%yhfZQ?h}zG=^VjP#_L|3R)}~@LVbMg&e-)q?e?h5=IVSq2=gP0@=P^mKo=+ zLKRsjNfmGz!@b?zKRNxSyLWuhHDc`Ho8zwIy~5e~fxh%PClM>&As}~1-Vrx+Gg3Lh zJPdc0^(HXJ0+$I6sTRa#QBs!%J87L9oYX%J;{M>rYZM1sz3jHJ6;ZNZ@|rdKxOZby z-o;o46@6!aLK=fh_pskuDU&*ce&vl;#J?gRiJF!qBT=Re5c^yU%to>k`EcP!0Wn9% zV52^`Os-SV_@6~$^suoh(n@Q{D}^{WP8O3wUUaO=D~?)_-vIMb<>_rYh{sI~RpWH+ zn1;PSHGM8KBgK!gAhru}pb8Zbl6{+CLP^~DRy0EAVRWy++QM2W`U4lYpiO?(K5g%v zx6iuo+UIXi4!ZSY%s+UJsSQ3mV&{uScqOT`>-e$T9ujooWP>dT=@eO^}ed$WyNp9161FuOUnJr*)c18 z=)x!bQ4BMDl7o}d;-e=z7OlSXsr2=1y^o~O7jnRbs0Ncu2jCJiZ905AKP zI-89zkWxm>7(d?*=J>IwUQ*O84h=EP&3t!b z8d2ULmxV$CsD@w$9Z-trObm3c9Rb4dOo+lVVsT8DH&fLmv%oloA6BeGE-D*``7y(X zZS~$FQb#xap8xu0kcG#q`@g_L5dXfXem~HT`tRZgm>eR6_(XTfV6YCc@n$N3T0iSy z#_cjrKS3f0vs(Ky%P>0C$TtWp*yQ&wHZ)q2J%Pxxp5(n4vT8#Rg`|4L1QZ+*3;$r0 zF>7#n*AXy=V`GUaumna4Pk?X=x~#{k27=sxvA2@FugDoaacSJ^C+dG z1cSKv`_WMt$%zJ1z4w;J81ud_jWxv9c`R)%rsU$RQT-_yOT7_Jz(mXLP=S{k4taN9 zClufWJDcE8uqkaE*Vh~hvoq8@h_@o=^~jF%%LvFC16$xi4XhSpwpiWBAcV*F#8tEV zw!(CxvNuzoT~Z_^1&4VH`K(Pa!(CON$hgL;TQCJ#F`JaV;Y6sMHR2ZG^m zICrLIn#^RO#Op(o%1-tM(>`I27csLxG>C!Ae{)50v@u@np6Zf@flq~z^D>o5tm;l? zHIHeqmuu#iE8BhPp}-l0J+V3DHmC&J#Z0|bVlrENpBD?NSG8}gRDH9xwX`UXdveR0 zOkIC(btTv?vlob`5N2nCnv-XJrw3w3W4tIRB`CVT25R&suXlC1P&Eav=oc={`;!-f^a!-pomS-~{1qNOW*&i+3D2PdV%RgBC4uU<`Zqng0MYPxD*nE8ZW zeGzXgooK+^8kq@Z2oKA^%K4=?1H&K4d+p)%AcZ781_F(HMY0EXOsUoj{0X{SpXy|z zAO>%4If%8JtxaWYYtpdj^+%*oWZ(>6<`l>2FzIti_^`pj=Z%IQx9Rl_U6Uyl?8Zc9 zvH5(M-ePJp7!jCulT)w*H-HreSXK$jdpAaHBeqwl-m@#KPq*WOi{9Q1vD1 z^xzdI*e+ri!8?IdXh*l()+N1ctA>*ic%xOxFbo?5)9v`z^fzJ{Hjn9z^63UOWcB6h zmgPjZm>m1*V6t7?wjW*^GJf3l`s|kbT+i$CwA?4Y9nI0z2X5nfAXFhtuj|dtE;UrLSpnoV2hLTkTw6iF_N_Q z<27@KB2bX4LbAd~2j>S;+4Cg`Px%zi8(Q^tYI_tTlGk?a8ChRfIplMCd$DDa*&c*a zrh~@7;BR0~>+mN)48khfmRMqaDZLdi3#baOfNSWOL>+j z--e-m3LNGcfDKc?jCp~=A|%F!beB*LXJTbgLushNFu?L~_MJnbAwX5bZzi%Hg@Ekow6zxh<8iXq91bgu&K# zfBDPz4K9{`YtS~DHI(q7Z*vOMb$h#&=PNp=b}6XX4Y$5Kx$QoHGWsmq z{EOLCP{M27mGJU~uEXKvecGSgyeNUV<)lrI?cR?cu{4L=C@buj&*m1$J0{k6msxNiZo_IrrXl)3~%mzqJn zd!pV+bvt8e0&i!F?%ywdWS`0SmVI@v02X?NSDU{4#A!1Xc&P@qgAbgTmyRv32) z5FRN4nY|V#EZjlUylRqe5HAPC4^hqhf`Pq;U%;=8CDsyzm#P70X?mRZs*ojMnUT?> zRc|GGsu~5!VYX9%b^%c7v~sjKuY@zBNY*Y)9D)S{XJXNZbu)JajG zEvE<2*+Af{XV1L2d2JolhjbUqaMI^ua{q?a=t*e6!;)F=Dg9+WdnsxIsNAEYT+=%Za&q$|l;tZN;$d`(41U z`=N$O(hbNDwcT293VnFd8C1`}*2R?GVPRV~`3zGhja1CjP!N-?vB)mHH7iS6AlWt> zG-HpV&!wNWVV9V`vl(I2W$t)wp__9fm)ya=&UL(Wrb=WwWRK+g5dWwZ^^=I@D%XAy zZiWpDmtry5NNQrGQZDtQKd$T?D$8eVqRqc8|0L8%;K zCJkAR0O(eM#kMvUyF~56J@OWH6A4|U3txc_Ox*9@WIZB*2#Lk=!F1>=Zbka4V}zMJM^)nr2)ZV9i^1sR?7O!=CO zZcc`|Fr&*oPM4Qu|Cc}9-wwn8ZSKa+)`qz#lVLA|pepmh#rf;?KYytGAeV{Pxj<+t zue8uv$sfVxf+>O?dC2p}vALPKo|b2G`GS0I@#IKLl3)==HxqkcQ$}049a;3}zKp1Z zUJ!5CwIG(p{;}M)GPzkWH)}Cx`@-m{%<9$p>g?6_a-hA46_5VKyf9|( znu5|B%9t%4UJk{1DmE;WAU;pa=4MabOiOw;e_^$3MJs3MlAFw#BbFy=<1F8d&k|*`B{7KUH7crxi~uSbm_etlB(r! z8qAtv?$9_L=F#fTY7?B%%zwUt>JQQChvBMn&S`-QMDEb}V$55lI>0lW`l7+VR&9?_NDU=cquCuZE z^cH}g)cH9B9C#Wll;`I%oA6^j_(_w$D)>0u;(;FGaqygI@xhlTZu+3Z()co+@arNbD;>{;$&5UbrAzp>7I^!HBT@27>o&2DhMc#LgKnSpBG zfIY*1!(7Bw&e|yEx&HBR&i~2u4)*=*A0#2KJW&PKsgw0SCX@H@c4z*27k^Clv*D1< zE<`i`5y!N@f^={@P+>;Bjg-3fVhBOFq?|8O)_39_E3j6l;n_XgiC`sq8|(dL)$aZsBZ%ff=$VwuOCsB>bbPyA$CAqg+D<4>IMm?BhG*eiuXBCeB( zv^7LAN|5=$%zBUxKN_M)imk&_hXHJKg5f-JZoqLGqy^`l0ha;=D-5SlTTu_h>jJ&d z>Zk%xfGI!^^^wr^sZ7Nt1acF!0=I)jUc8Yn_k~=AW6b|ooYAmmJ)ZoD|3xcAkM>V$(Aq@ zw}8FrxC?*5OE?o?JM#89c+lEbPh2Dtu?N>(eqG-+lYn<<%Z3CR{+3Lfec0Bis>2Bi3xBhLD!Z;!Xe*{gAq*p1|NDb5Wx0G0;l*UdC%Hz9L#iSFTZ zt);iIWVFxp&YHQ4-N{3cTO>Ql(bKVcAkG*DKuJg}gN}qlmuuk$~aS^aak zpGlN9mqy}wL}$}+FQK3#Yn6vnBe#4uL?=HZs;yJt6qafSfPDGi8o7Z3HH_$^oMBrl zYIJkAxIa880EVW7CGZF$@!>50J{3F~-Xa<1+x~g~=;EM#(5*ie;csi})`*GsoS_Cs z!k2Qy7N|^e3eI&0$8{gV*zho6{&61)4R!=Px1~dFn(z|N+uoc zRWA;VCmk&ba#WSll#KX#D*4QEafUWGd3@jPCnIxEqjd_{i|^B0bHC6_7@PzGdo@Pa z6SEU96A6^=2V8vF{gmZV{2r5e!@-`Ii^GG=l?AsA2T5{}SFm4T9CSy7i$PAT{gw8kgW*Jci`fHO=4RCn(tw;h@(b{8%7Sk?Zih-V^cyc46^;T7bZoR6fiy=OQr^OLwfz7JP0>%=f zqCyy-A+VJuD!|*q+(sr^o6!bTGEWAf<#9BY%8F*InZIfqH5KoN^oJnN=O+#TSynOp z#%NNI1)eqAaYEHap?H`%)Uef9+M}{!M)yb@7z&NP&TBs8OFM-hZ5 z5Pihgh!^EuK>-)&4L?`p8)@d)j5hIqBhLB7-;-#hKeVFHKdLs7HtSXGkwq4(TV5>+ zg{p*FKvCrxE4!Lj!B{9eM8(uOImBmNPW1hw7+vNTp>+2mPUS1*thqTE@BGi|k_BSS zIpw-_$UouyKx`M6c-R&tVFg}9NcOYq*3lQL*y#Rt(B;siOxh4n4Pp9^5USDNnT0uQ zF-?2n?glZS52ais`(8w$J$VXI09TxSnDN5(%P2;cr)rg7U(xLViU>(W>;$YlSq%mbB};+@+M;ncP#nNE)wZffJ^{`ATxBD=p{VKy)ppy12d8Ko7;7AcZo zlcd;Tjq+7-FzBpRe5o>jpxbs8O#dADApP$YL~AIsYb$dLEH15-w55G}InLrf82D(Z zN)%C5G|VajoP2tj_r~cZW{X~s;A55Cyo9qH^-jV_gbAr~9$0C>9Jzkt_(4meC-qiA zw_>AwlLA~ijkt>qm;aPz)0`e$f}>N-Z^t&$i4wi~Eq~Y^Tcu%W!*6I84>=D9+G8>8 zg3?`5x?BB&pc(C=yT_?EkdgJE)<&2p@LaT>=zqWas+r`pA!Fw)LODIqVLvNDl2;SiH z!f^GHcvMkjNHTwZRsy<>w}+43$k`{IaFPv+QSfxQ*`SY4o_gm{Y8!fPYL-LjVJBJ5 zACyNce!hLCK9xcp%^NnS8hm| z<#^gqtFj47 z6g8$S?2%i0pXBRzouBrjKeaYt8)hRzZp;jQoAr|>nMSe)_Qv2RuL8ZNMp(s+RN}KS zqAc@hTQ#!nbVNzRz!U*b#hmUCRB&>Cm*Rv}OI9UuB7?kHG(&vM_G7*Ry*(xf{qzd+ z1x?@)oI<(C%#G&>wgIXQhA%%b`$=T#2N<%16aQk@k#+!&-FbrV0YWcZ>- z5DY*1D3dR4Wt*tCpS*!N?LT|=YMKuEX#RkNOgSubEZN7MElM;so-3S!F{pBNTND8{7K{7K1P&>j$|2bR@v#e9P$XD1o(@YW+p+zUvNLe5=rIH7BKMoS zk(S;=%WR+7JI5b-=9UpJwaP(lZB6F+uoNOZk&j|xjTRhDV?HaQH_xfJnoRR+r7nni z5v3*ZSaxuGMP4ShqUbVYTADfw;w6V~=E0x6fkNRD^XRa^2@W6L;L$Cd+T!RaiQ1ft z^iBee%O7srP`Q_da4Qw+>H-Hkf;MGtSaSmiTcY+PC>cGt!! zG>h4O7C^^$_paVJe!Eh>wlM^)Ul9dea^ewIjgIPRD`^$EIDOY(wa6hPm8PrKQ@$@5 z$ipZw4^5;Y@OMnZUG+Qg+pL^9uR> zmuI#nQwudO9mD^W)vQ>@cT?o~#pMhyWT|m*J;n2}P*s|xGkRy5=4=ofgG&fmQpo8U zm(TM&DqAe{npybuMsShT_YT=>_;8$i?=*3AJ;*L)wv9E>_d4nSvPtyTe_ZeHAML1A-V(K*)gMq>hB(Abaz}Pwb2YXGN{G?!>BPScpNQ= zgVFeg>-cu(&{*RFjcc=>htMszSr-oZ|YtvgP=2bnm>y9+P#9L5>RfoR@Yp7io*4V z^5F)UN?A33%FZ*G@P2DA4?D(;IT((>j!wkM=s9_St#& z{OqFLjM_h+w)fAw?f-5@$)wkygEqy?$vu9wLh4f*Q%*&nj>YAcjdEz;0yB=tT~qRa z4q^eCxmgVKeB>p=A^MnVfAGMzYHxLXdXNjP|Cy0ez*$>7H^FHZTNRxTRe~AuLgZ8o zmC2LX9kBBIEbpS@T9^F(<~wBBgwFXjy13`pn6QyPm5duKd%c5xN15S)1M{EyQ-&j6 z+Pu@QNmPaL2^X{7C~YeSaq(8=fiMY!`Byt!VU!Q{-y%=^c_shm4y?R0;?lI^Fk&#|J0xJI;@e(ykr=*jLaS5a1FRLM)^x z(rSXD1vX2`y`1o2!nwkdu?;%A!RRWIz9_`)2IAT(R+g&3F(lp6ocP{NH@Sx-%|PYo zY7k%3!>Dt6Q?++|&B0zyU+a zM0}oFAni=WT(1ia#Fyr97-nMDuiL$xrUSDVfB}HoPf736OguS1IJ!!_ z3?5p#j;x{hhy`_|9zKid9Y|UXKkeuIbV@ckepNGz3WW{tgq&ErWwWvE($xB;Znan_ zLA>*XLax?2{FHrUrv;@yk*d2(yx+*Ya62%H(-l zln)z)Y`Ex(oP9wPRs=(7smzOu?y`;mHflF^kbrHj@Zra2+K6Me zBknkce2Au62cpp$B*W{;4T-@Lo~f^3%OBYhMMCTF-o*QZ^pkiZ+CA}H*0SN;+LwFM z=CFDVd4j;kSA$VC<`q|d3nDJWPVChcuyyaqTmB{kyU=Bx4Z!;ee5eHo&_ns~i(`pW zJZ-9P)dlHxde(locYbl!?o!RD!=rXf+Pr9yK|>>TW0qZv3FvGK5((BaEQV0D)I5Fp z31tGaL_;S=RpG)!B`DAZ=o&Ld>wwWZWpVI~?ic|ILH^=h0Hz%&@FhCjmeA`PuYr! zj(D#8c`f|N#%kcP>u2a3kwthWhY5g=#}5PsgB`f2(-kY=kZBJ#0i$7<>WQ5Ara$hA zm7b73PSZIygkn{M zhYymg2{I&fylYyQ5pQ>`xSJSx7rMWP!h_ZkwmtQpnS+6lqz(o(6voN@WTmXYR>cPe z!XWCFmsRt-AGi(5n_FrejzKXRS9|!WUaLAlwT3HU9gikcNwrTmVkW{Avd^NR+z`#j zyFLjR8fI<4z;tXg8LFMo;tLPop^~y7UY|8KpzRBLo%UNAyPdLp%qAy@iN;AnN1 zcq8B^EIRhnc#vH)Q!#?(6qtUD(YotoUybqbQ>nL>GWGyTOXNhCl^L4}J^H;YN>S@Kr_>B!ygK>WLOD5YAH$}WFF;a`oNiG$ zEt+-Rdh@XR57MjL=kWN=`vzKV=8);2up`FcKS%Et=9J#1Ly$5_neU3?WLU3Nl5G`A z`4)TUz&2yv8xkWYl`$kHnGdEpfSiLyKp`X>$PLLRGE%>xM5Gv9Y5lrzCMmxHs4n&H z$;Em1{G{7CKQIJ#v}N*Hr>R||(|xzGu?U3@hwRA>`Vl9@Yeqk%IlQ)YCewbJ!C62@ zz}TD&$n6Y%XE=8Ol7TB9@%G+L#ufeuBml=5<84e#2ZUED=b2fpOF*%O87WI;s*tG} z6aWLv0&lQ-$sshbgmXyEwb8EvEOufkAEz96SEsYY& zt&Hq=1KLGC1yU1LuQP)%Dik2SXIIv&G)Q~w(MZM*Vw2P1WB~UStqU_YJ2ED#|1^mE zgCDPvR;^xk+t>n0vX8s_L9douRt97y=R1Bog`4z7-)q%r*O1+R?95$HX4<=$8-<9g z7}du8Msv0XNI!7l4#2CE40KY93~f(a3SRfU;h632-m4R{)82L77-rWs{PU=L);={K zAtU5G7kq2nA@_i!2~dV^dOK>_lM1GSlCeeZPVGx6Qp8)c!UJa%$q$F`(3AS`szW-R zVV5GM;I$*_?^6duw7Emm05v+vWhZTG@$^0&q>vAkm?fG7uWOeCCdm{EIrS{|ZRx7rbd!8zvunGdee z+?T3_-l2S|1V*f)g{o}v&!EL2rueC#1?MID7t(@84AFuTQ-XZhC-IuKQ|&Bf&2*yW zl>GU0cFT}Uk-^7ngKkB>pnbY`w)d_Je`*wMG7pFWkWEG|5|HSZ9sb#01O3a%X~SF{ z5}vR=(lA*3jxhukRI^D+wHf=?qX4FSt9{UBBJmVg(9yyhP0X0owC-N|7EPSN#G45e zmNm$K6-hBhNK89MX@I9di?j>ZaD26{Oo!|rA7;LF5O3_(I!J7|fZ02t0sc`KflUuM zkWE`d$%A5kjb#xUw5ZO$5yua@=VyESZD6!R8<6Lv4f517mqMsyd@Ur;nfETdVA7Mt zUeK!_v7NIQ6DXKbR{U$-+h;C%gA7ZODS`g# z*7?)s(v=cD@#za{CVu-%%9p>2eeQ0&EHj&)`1gg|9uYq9+WBWEHVTSbH zL7W1sq3}}*yRwhFbZm=BA%2mX9`1{mT>O_$!?8}gWeD7?(Gh}Ly{8MK zY_VMo%g`7L^`w&!z780H>RYAjgzj>yyz89rx*T8GoO=Zkv_Wh}(Y7LR8m+Hy+Tltv zTp@o+NvbMd`dy4RSrrvP9CIrKnd0$ z?Zx2cF;4saL@a=)iHTvFREBAuw0u}4Fp!-#!Pwo3js<^vgdnfG$^o7}_csNa8T10F zwYiE>$)OZ99H@=vCwkh(FYUssx9rEwzGzt#O5^3D9A(#ct5P2Tnx{r^nmh!vG}FD~ z-!T91t{5AYdce<0WR!hljHbc&<=eJx+qP}nwx(^nd)mgdZQC}cZQJ(F@1IRp*=+Vn zs#3|5-1>Iv)UA8ZNk>XRU<~aDcDflsn;7eqb7fN=OpaIY5P=og7w{i~>G^kkeV*U;uTP`+6vMw- zJ4W?9r8}lO09xLNdSJuc)^s_xT%{AL9!{e ztfys|)wAag1+UVYHYES?YA|8-W2!Rl{c5t{C z2+T;P)b(Ca()bz3pv(TGqYGiQPn^*&uad^V= zLYQNi0#opocrMYOeG)6S5t+ki-QhLoZ1UkD3+e+$;OdL+&zCFce_QqVHxwqUu488+ zp8rf}V)IR}G~oNZQ*4 zIxN$5qnNP#-j3O)X-W9?oo~ehPHJ{WbGljC1$e;)dl+|4{UZa+H){YRC2^Dn^Sm>TPpKO?F4gnaE*RVuzc5{rR z03)r!0q^}rjYr&xqHtD~pQNzh2rlHxB`3p34T^$T)uEf&I?Wb3k2iqMJMtkS+%?nd zD$t^GF^UCp_KC)Yp&Xan^}evUrILprq>5BF^i>)iUka%36!k6*r`!oPf$5#nB&Gk| zc{>4>?kahM+9l2YosCi}Ljp`qt^w|-f{yZ+GPN@}v#?EArKI@|sv0J{4)l>yM~8@@XqS(rlGSIvjyL z5m+vZL1kXF5<8}>-8xlcGSsqegB_hdZ=lJ;u#r5}fH*>3J=vanerVcB*77WwlyXMz zmpzfiud0!0j(PO&rRoEV`JLh!DMq$H#T{z{^HK$`TWOI{eOV2cLhuxb&`I&`S7zHz zaAyh5KI)KZ!>vv{E|bakn!m_T6O)EZad1%bDv|lkEwOx*p^X`b7pU3HUx+rHwN;)0 z`yurJ_)H8!9fZ!|xk5i^a>TogFb9xvR}HJ5Ab+tB+vecetgD%`ai^2prM>yRH-@@U zYZYUS$bbQeJ z1ZJ4=A|5?O)&JSJoACTk#>_+_~ebPo}oMx z8Ov}4e;{-ei|%hXHS`Oa8{gcNtLBW1!hL4LsAH~>qBBb~V#!tCw20EBD(9!*+72J# zUThlyy^XE)0f$aSO4y`)Zhj&qtW~72^Ae49`i%}&_YcJM&~HT6-*xy(hPmI$gdCCp^P}xMb8+L0>&ZgkQ&op zi>0ba9_rLHqJ$A2g79V-Bm2Wqyl7pezfOkCItwO!g_V#Y510dy#Z?7bYDKUNvqbbn zB90kjM#Q2i2XO}pZA}KB!Y;0L67zHUVEALGK*(Jb0>X*;A&a zwL4OP)K;S=Ln`N;wqXK+)YTu!K(!pb{QfA{`RB1gyxEjeI8b`NctkgAYu3q;Q>JII zdHtpU{(23`PkRV7`~}qn7sSCkc}omF;Vm}^R#fF^EJ^JF`ROLFj_?XW=ZryeAHDzx zS)>OQ!LB@1h7r4Fn>6y4lhS8>HWLJcB}uoVC5RN;HMb?W<|Lq(;D0PN!5~_6DUC_m z#~${UozY24T&=*;lcxd)zZ;{_-ru!l!d=aYXTc6f7@&fG_Y&d+Ap;R_-jSTFNBP#I z8=m3{oOzVBqU4lO5}gNa$Wy%SvW@DRFgId{zm4JTk%mQKzW;tq744;_HHN(lUZE{O z?ckT8HzEv7+pauBxrjl#QREX-T5Ro%FQq22g+nm%Df;oNb3 z8R;>CR#L1{kGlzomj?Na6}-TR)n`o_ToUYGcRf_P4deNZny1x6` z6D^2LH`te~Ajt$=eZ0cf;H;^3$^!ZcCUph9v7D`8ncqwoj%~{ixwSlL>`|HUk_v0-}dgZuFuxS-^Rbk=Mx=U3I;jA*wkPS{QeOJ`7O~1PQ$BC$@eb;^r_u|; zJJmNZ@mKTq*fw_$gBf&9{uL;Be&aTQkT4lb*tOogw`!qkOdC8TiZd3Dmqj}i_(7I~ zwLN2-`Yh69wJgkl;x?#N`GHVlR|Xy*B?3QCvXJ12M7F~8mxmJ9<*b#}0eSlUQ$?hWc3_!=1lWAkfzScWQ%!-hdO`FPZBL=NpdoW`Q% zan<}<)d^g-dF1E}oyExf#tEU{nVx1UzZjXD`_2*Jwm9jW&n8p=|~ zh$AEsM{Mx3r`Wd|#5`7^%telHJ>FFMMETC`07Pkp&o($Bi|{Z$Ax&jFeBy$L7Q?f&-XK#lDXU{g@Yvc}3|n$@ zC+$#DW)R(7mjdGmGT*ihS@i}aB-utDhIL_MF*F*r)J4^J)4{TCU@E8IvE&ln3|GD( z5o1FU5Y%S)i4yn6Z%m7$@rLX0d)r3|%J+)W?5XcehfB6?%hZWF-56vdRoc<~Y zRrVZ|>$tu(fhf&_cPgfmHJBBes$&YqIZOFy$Dk;LPpGmlcf0g{WAg#QQ4jGFZdoB< z!7y(l+cIu)7TXiM%1Hy0?lI*4c*T)WAiLyFG$(pT^5630qvPF)jUpPI(^T3-O{wi{ zGT#c8ARXxo=}DWaSn$G;`IK0BHF;p&iI)K_4Ldm6uUTAMn{^<=u(49=G|yM2!>T3%Td;qlUm)seIt)E zxRO6|>5XDmfGe8ydVyIjcATiYL9DyqqH!eY>}gUzg_MEH)Ii>^vR6KJBMp3EjzIGM8bXAdmI*kOZoXydXICGd^XVN1gK>&x$UzMHsK=}vZ zEHX@#Me%JG?)ozgZYun7BV5GU3w8q@ugZtUYpjG6u&nzh$Tg+G<3M7S%v3U-xt39wn%EwN^UC=jxjkg7^dao&P)l9i3V)JF zHYNjc9K*kv!`{90#W%>TM8(I-s}cIwQ(FrRgC9+z&aFXed2Z&pvFD3*D%y^puLkXI z(%{dxtFBzZb2zy$dZD!^Oz0MP$PYy{&cYdN)AF|G7#@*t>@el)`6syU3{rV6OfOVf zDG9=nHfe87J(aN1a^^SILk+gp5#R>NMM{f1(0^Lw>a-()9nPdcoVyO9mOSCm?y@?z zHrm!twAEmcG2NkLsR%Eb+@?>dC=Sp+E0%Rqn8-idB9Px>@}{6S1XmOfB{)9dvG>p& z);rc7Z#NyldFg7JP-$D#>kiB>hXMiR#~i^%OwcM7qX&~N%wa_uj_{>}j}G9}oVbzG zc*VIkb$~-fuasf}CRt$I3hT|N=Q+B->4GtQAN^vEUJ8yJIi)~etVBR4IolHYK_(mR zdq9<;FT)31*2w!j2N-?Z0TBt!KA%WbZ3Romdbs4=&q~gW;SQM+W=iS!{y~dAtnNC| z=!VxbPE>V2({N9C zA)k$rHA;=}F2Ux`&6%<6`269q=CtWiwjbu5-$8Z-dSwJkW^;o3^?y)I$F6!au&q7D zyAoO^lle#;P`~y)%uhikI4_3Mp@xuzAF}iQY7=S?`?HKX8sQ>Kykrl5N$p z)Om$!94vrj649oxcJic;qqX(4l5F#mTtHG#I#UyODe{yw791o&euQaG0Lr4KW0zY5%@)_Y!24Zs(r?Dk z6ptS>-4c*sRZ0LK;_bl_tj`}NX4w0^q)tUWi-vQMZOidbgZ^ow@!CsWPNDWM33~ln zz4L2)8zRI$NqFlZZ3J8sZgDBD-{X!xEDd})XI>Ex@dJu0j8$+ z(w}D~x%o7W;z?OUA>_qVUXIseoA|XEkgszPmItTM@wY!A#}*|F*&|um?2RIrRG!9X z#G_p1rhWyd5T5C15uxB1;^H($N_T)I7!1jjmH6JX0p%11B`YiI>%`>tUxVF);F`}Y z4$>PHMuFZcB5!j+sx%jvWDZ&@n$2*`>R+)R$qjKloZFRQ5AY0o)@*DiKyv)M9~V;u zEH2Efy>Q4C{{*BE41(Lze#QP>LKswoWn>@X0EUaOrZi>_ScfP~?x65khG6GK2E&dXTDxrBY~4e{Eq`n3!7TfGiu4>z?YF1{#9{`{blR{) zZ~?&#cB|G`U3~{0pSSt9iw1foZNHBasJy^FFDzu`iTzX@?Z1MfJjbdWydqD?8Hkh5 zXgx!Qp1OcfPXD8h%}O>W?23zjc3W`)HK9cw&%%hf`vv7c_xgEJNfI|e&x zCdH6(g42T{`UhEkQL-#bT~{>s^v@y)#q=`!*Dz_iXM`_;3~VpbotUG9kfY^+Ci<-{ zqZh6X$u( zW=e5TdOd(9Pj5%yX8LHV>1WoaEeo-JU&rQ(Wy#sKB~ohP80^NLj^fYaHG7Q}q-x={ zUI4qL^(9G8JHdWKH{;V4y%V3l*R%>VpM}j1Boh=Drr!@x{7H71@=Iws&OWudT*ufX z5<3wTeQ;9o{@!fIen*8>td)&y$RY*_O%I6FH8cc!(N_fqTyZF*B-kDv0082AS3^-?c&;opD5kN+{_M7qHP$L%Y+T z)cAvU#r?ZhkZW{4&BF4dx)^!}e5!FLyf{m-%G)vQa{jtYQytxjZIL6jX}f29 zW|7qon8q!7&pzMS$FUDoY!P&s5q|(8{krXgV{(Zxj?U}hG^OgxRN9B-Qey5zRSf52 z{FE#Ju@Ug5|#48Ve8dnq!(os!x{Onw|6VDcu&Ir=bmNY>|c8l;XR@`94?&bM-k z{my@%<3wJe0v*@n1)r+nA7tO3)!x8i)8iF`bP^{;R} zo!byX&6<(Yq#)Yw*V6QD2Kh+S+#xq$%X%}>SxPqKe9ewKqwPY(pu4yYB4;4F1^R?8 z{#LtNlT$BZM+`=6e26z%?4pJ9x@x4&=&_3qBK!z=-FR%ITDn1oXIiUCi!N9K#MWks zRbPC8FSMQ%nlRL6-;)JK`C#8`$a@O~1$EHZ_tkj?OXrC!T&j7q|e9fzht{Za;Sv&q1etMQ1Tc5azFVJiw9N83Q_4>sEcjW;rxQ(BmFckDI}# zRsBW{wutMEVOPFR`;-B5V=G!mBXtJk_R&f;&B=f3HC7*tUA-D7EnCvw*wxDT50S1Y z1Fp&=EB_2-V@GTXF<~mEhvWjY`lQ&UHybe?)u>|!AexO$mq|K!AswWHbN z8XA7EPt*8 zz1N@udjgJwQ05TnfiEOtq$&6y#S-Ljq1~HJ@H@U%PzWy4+1I^2u&9<(kw)kmQTeRaN@ijH%co zK?j2uD1iPde!XYv_3(8K{Q;jH*x-Db#072As4R`b>0J0FwCW(PP#<}6eAJw=bJ)&$ zm$Q^x#vYiW;gX2ta`Qg5{oq)!suE<;e>8FJTr(@y`<(P-3i%V@`<1)sSRs8ZwH+-{ zMsyPYVQ#rvaN4RO51mH}L&?;ik~Bu@hoBV!zP4&M^nE9hK0LNB>Tp;oP{R!ufTHl@ z5r$nUjS-MXiKSwI-Cb_$!Y7}Y&-zi^uqFcRoZ<$d-x*^NTc!)BhmQ$P@zmOQ9q*U= zqQ`#^z1dO8W8;UNgCRKXzcNDJXhT3+xo!BZe&)fN0S&i=1kIUdG^Cb=PUi#57){E4 zV0A5un(jI`BK@$TzLUxU$c;(C>W7R1=YhSPmI!t!SXnbn$bfT_<5GM-D{Y!JT5Qte zq=9w7Tvtt}jpjtstZs7`Ess=|eeL1cONSowkP-YGf{_y%Fh_`^vLC2HBNy-QmeT+g z!Wsy=i+JX;@!oJRF_ytBU+&Ig+Hy3vI1l1^D7a0CpN33w5nrE=)l5p`Ff~`Os3Anj zgwLZPwEG6xGwQW(h(py|At?;)aZDG9>gZ8?~6>%G3OsaG~0)KQ;-}% zGWSDTT=){f{=BCZt>uu`O7I9;0_)@1)VB)|@Z=fsc-&7nzt5w`B4@u>l}_&tcX(v! z@E}W@-pm?-?_ZmKjVHbzSsXbmuz#?4Eo(!vWCy5=2EcWelzgV|Q3qeeEngX7tcU2p z9Lv_%;{~iXqSXC0n-%*#lWTdrm+T}eZ7QL&SI}iFGvG_+mn)16Zjqzx72Gp$jOn+i zvu|Zg16`Yui0+qjo1u9sVm)!t*OiiYM`%}TKmLi3o*acb+Vr(Mz6Xcf@q*MBl6CjB zgT`dXIDU7%1#S_b`NGFI${y4tq5#oyu0*<0`5i0F9z6$g;Tv>$FY$lSF=lr$m@pH~ zyhmd#APMQbhbxsEkSFns!F512Gyp$iyb4urh6zSOmBgz4t$lYaW{;|ra1AGF&?TjvfMf)fp44$n2hg$!MR!#1K- z)a;uw=E2zxfJo0+^fdT|%$(hL!pP*01~s1Zu@R^>NU&r3^*Y(Vr{(?gd3cFoNaWz} zBXSw~K0vW0|MAyF)H?k*ehot4B`r6aKd2mMQdTL&5h?BLsqWA3`u7nKTI)SCq=jWm z_Fy!pzn0>PW|{kDw+A?n<%|`DN{tWbvf+@to!m>8AgX1$md}GagI-R{r4$w?+dk!} zXr&#`(VFuiZ2@TeO_H(H>&@PY7_45!wQUu!^SA1EAw8UDE;|JH1le6TQ@O>zS_Tf6 znl5VheS&CY$E%5MG@Vx56C+FUC>a<)QFG5R4x*88wrp3;CKJOKxh#9x(<)?oqwc)~ z+OrD=VhNX(g7kO4`@)|j;M#sJGb)wq`Xq;g4vSS3#t+X(+s$8(0j6p5I*Q$(q)Fst zOCsf>K)`!exF;N+=TP5gLbt}OX4<$JsC?dC;?L_xSdOjZ?=m9SC_KX-dqaU`hDzib#?Si^vF&snQ^NP?SYDR0yQnZ?6mjN=PA^5~3ZkaR1%R-{#1r0>z)!^?2NkwKN-)i4NdOtWzz-;qO7q{^(}5Q8Ws#Q!A^FVwUS6fw0zP$r z=sr8mi@=`Ieod~Fkrb_c*=*C>8saCMwmi@W=zc2?g7y<81>9cr(j$&ttdWZZ_XJfQn~uHtW8l?zj7}3+5Y+r`=|)D!EAu!2 z1y`PB&dcE=%k%tDaOpzwU z$wvto=@v!7RXfTD9jm=##6YIr1fo<8GQ5si-ROnwGuz1t4|CKv5=KWigf!*s}D& zO&>e+ZFfS!^AfHN)OSYk{nM!p?f5mI6)+E$e+u{VcUf85MEPJz12&H$Wfu3|H}bF&50NdA{HRvT=JLmw~e8o2p+9Gj1cO$D#)S%GK zt2LGj9^DGhA@O(NJKn#}6z*2D9rLOAzzW;8s~QUS5@};HM*ukkt?rW!naF%5oEgdy zQLO6?>L)vpzpdvDPMmg@>8*8tLEdaJ8Y_#=v6ZlkA6?K?EGQEa>s$f{ex>NFZTwcE zv@I_{%*T3R>2}*_BO<~ZZdffy^c+W2tkFHzz#agEDRYg9RQ+&Cc`pK)vdB>+_vGRF z3e8V1+f`iZnK49WAJ9=+mkkw=KA9)NX5kLZtWYg|f;J>@;M=iD7LsE$zzlQFWN$Fr ziBvJ!R)=A3sNt?np1~`Y6#7*KTszB=KguMPu|;0YphTn>Kt$nGR0Y3T^A~$Uv)3M; zLpQ2||9+B%Kc5i~zyQU<@yNqmhi|~tWzYKshi|o096p_Ej5|%nC?kx-@Ed`s4{kp< zv0XA8^Iq8l!<}s>p5})^Q3eDQ8t|VFH;P&0zZU;}!T{g`EbJUiZ0MNiIp`S~EL_Z- zT^RmrQd5NjfOrUVD|`MMt{yM|AkbDS001-s1pxR@K-7OBAOXWpCVUnJIlT~|0Kg<9 z06_VF5BOiP4UBAU4gM3K-o)g;b|{#emu2}scPLitPuyom?7q=(k%P||Z>Vc-wWVij z0jObRNj@X~%3y9WRJGBe*TRqL-LVJaxg^I@MuR8ZtX{d{YhF)+3Mt{y?)G=PJv$gR zkYMC6H=xom!OO-f0R!HGm6QpgXT8Fxyr7{YdjXyXmgwm9b^dQbhU%; z*b=c?mtHK%W%ndK;{Q`Vf09N3`E2NhVP0I0$|`cBm`oGTWQ3vfAe`Sw5VD9}D#JEV zcbzZJIM7zJsV*=#5M`!!D!z8ti_#yY(W4uYw{9)?Yo`sdkAwRs8ipuGO}zvcd#Pjp zmn8}>LNF4H++OmM*j6w04FYU1sAKh>v~EGuj@+)@JY|9FlIYk6+wrVm=XX&RUq9A{ z$r*_)+7m()W8OOSA8Ko~q6N&--9z{mqfp#>7^85YfjhrP^Aq^LyeBh3Jt|~RXxEE> z9aFh0_@2~v{-DkMGk+!ljtB=6jW^96N4h1Ad5EBRKq3SuwGt;} zGPs9jX>>W1`SGPFV$ozqqQ7-uJYt$UUIfJ(_K9@x>=1S38$!iNA>Z`UkN1Uidu zgFOgf&|(#b*Th&QkztOHCj8x-Cc}BgZ4|_+I~htPQqs?At~yg0+h%Aa-k`5Rlo>&K zfCMy-2E$b|j}cY?T#uDwMD{^4$_p!uN*l~&-PL;qkFT5#(?WV}897zTe4v%flwN9e zXqP6-dmlf(LnvR&L7z626d6Q&dxQiQI|!?*;5^K%W$d2Kx&Ksz$GIr4n+l2 z-ENBD;i0Px{4!imQfGVJyqHhK;ji0xv*CHDfbj|&bee$jpFV z1nUuZX~Btlb>r=yHCk-7`(1ldz=w@Xe^KDurc1PR&^@H&*8Z~h@$yXsm!C20?a%lO zv-zhGwcR!aytJjHKW}T+rPbRmnvQ%AaTUI!=Jv}m(?uhTX&%=xQ%MVW4c{r9HK(Rr zABgYU@K~P2A|Pf2q`JSi`+c$N6nrB?KX&JF<>fI2BVO!DfN@Su5q10R{v#domKP} zg6l0si{)_6MyizZ)9tVfE=%9tVqIJSdHHpBJe{?cF+C2|{=U~fadU0+e)}E%Leeq! zUZY4l>9WbgMN~>TCXZ8*thA(V#yg{uKk-XN1poC=ggCyNqchWQcIT(jS1X^gynK*L1qj_x zZAU~R?M4C-bKaBY6~O>1&Av7NJ0QNF2DWUJNIp~S3`79oUkx90NH6R(zDXq2#2IId z#b7LiA;ehUAVJ)fSQ9Zb21872TwOw4b(k8mZs@J!f+!LpY71p)^NYC}MD@XT`wIsODYxk@M_d5P#S34Y}Zdc)00iBdH63k9;ZWo{thcXvvCI>-y1d_z$|6#^%46= zQ<14Cdqa(6y~bg`ONf~U5w8OH3xv(z=VNXJ7QaCZN+{8d5^>1fvLObU*b9h{Xkgya zbspk5Y=7HD8R!{3P@dzhZC~7ZIxPNOJ3I5kZNR-)Pl(|Ot!cwkf3N3VSe~OEkA}l` z-GIxi8Q07)%Q)5-o+ug*J|`@MEKL!YO#kWg^;(x<++&*phib-2GGAZCUbx{az@Cor zkE9`>?pr|kq?*ZS&E{ShRKjaIU>60E$@Rlj>J=Em&|f0qB(QuAw-EFu(uG~{#*P^^ zS*^Ez3k)Z*mScJ}lq3)+_;h=2sWD6T*~2rgTbecte(x4JqzkiiTi$ITS-CI+5f}01 zj+ z``yrP>qe+h$c=(W!L<)Lj`rtWH)D|Rs;{eA+;XAUj&ao{yx6v#O)X2IVY@&w~#tG^b-Lc)^9cp#v4EAWa2P)Rg7@Q)Tdh=h6Sne=kqCl>amSH->%5YvsHl>9+Gk!@a4Uk!+>C;BhZ>ZyQQX1l5dMPDJUQ z%{d$tKpx2kS?74FS^Ls@f(Mu96rXsMQK8CSgB2<2-s`8)TS@TvT#Xr$H9DqKgY4wv z@#?Ih8?v{MHUOKwy}i*|aPtuqW`Z=XDek^7e8S0^QiDN};GRXUj|s`+D26Sw2SyCP zIF|5uxD0c7szf_riRV7tomM4^Ie)__hd9CQ(j9uT3Ry^VDH#BBj2N1^im;=%pgnoJY+R~b;0UBTU zAks!iDpriceWnc_nFDo2H%1bYAX#-KDxP~a3U(l6$QS`|!DihbStwkBYl0BU>`i z)q$S@KwwXQ9%Wgq>lnaA5a$qa_L)Zmh{RB^fHE`JPGL=D%XKul`WB~WN7I;aw}sSx zu{fK~@Xx279NWfh!1qY)yCFM6c$uv4gP^)qJo4C_v*%7(iMpBYh#W zeHb7S=L!O@XquC+PvFP>d6>_&?-+t6A6C}^@!%XP2hdIsshS)X%Tw7~Q?O1<8L*L8 zsZ3vn@UR!EG34k>*CWi38AkrgrB^`EjPT}tCyh9hr0AIk8B96by0cT`Z6M4)khs9I zA9Y@mUAZv{=F4~{%wHjS2?V6EHLo64eGgO(X>H3AZkm1F{jz_+b%0qS>w@%|*y+WO zLz(a~IgNq>_%l=If%Y=cH$Z${dQRqfL``29VGJzi4&%<-CAtf?p%P^Y#ONumM zymq&?zEChYexlOa*+j-|FU)eVp1}ECR%=f?XM%SDVpI%2peF_%j=%wXf4f=5ox^AM zUdE-^pAL&|b~fVfxYAiY-~U!=B@d9UVoa?})tNs`_OKCPf2|$i_6|ghg_AFTlAOLD z7J?~=g`Ih?L>Gaq{)ip^1n`~oVOoHhT*)Jp$Ox7+QN#7sb<|ZnviimBY;WYk%k>YG z;>?n18h&B0Dh|TTPaD4BbN)?tr)0lP74&^F+V>*juT51truq$3bM9A65dr$w7(is|3c`8L_q;p8Vs&r6MiW@Qs2GlS{3Fo zrzAa_;cU-?2+4b$?gg*h7PiJyqB~}4IvR;2Cd8RskK&2*H)4y%Ua%Vx1f|JxRF-AT z39pKGXN&1+J#5v?mcq*P@s=1ioDn9wiU#HkmW3A5uX z%Y_nt=d~pY1typVeM+cdkR4d)aW~uavHWR3j%2`_tQ&HFEcBns%yDLGCi88DE`Xt4 z%E5P*YYj!1-*y)zdxEZUV+jI+IR3>s)W{-kdJQV)l8abil!VtoqiFux#nIpu*bhX2;47G?m}!K&5d>=M+Nw9imhIDox7&(bp!wN^@LrU1^36W$OWCUim{~| zWH6E#$kU(^DT>NM5Xm8_Sh)zFc0(K$^yE(c?t1atJ^v&MG5K$pMK5nRaO&v*Um0>7 zNAF;2?nq=`&Gqkx=R>a2pP3 zH2V}_aR;qhV6@tvz4&Cx2#J!?xLfnps!JJ#r!-_F!3mksmY>Ec-+D3uu5hf%*2Xj5Zkengruu9d=dNxfB+iNJ@d!LPV z1kGI9Tv!;$9HO64yzH!1@HzHx#(*Cl~D=f3E>zrYS>lC@6x_ zYG{NQa@rlviyp;djQ)T?py1i{V;%O>@T%hw11h0wD3naCfg*sLRNSWN8SAs@tpHlCO`!l&3DY$4xfA0>pT(RQjP*>AdNDv!6v_(r{3*C}sWt zW2NvJMiy9mp+BK5a07-pQ$-r+)H-?vDqrgwMYS|D z0{QTCYWCz<4_c1t>zh$`n-iBN=)jbY7&V@Rxa0rO-eJ(mDi@J=5q1s9zlL25x$K~0l)G4%ed9VQ$RrzYOU9C==?0U4Io77m!0gq-0cjTMtoWx(6EARz z_ZJl;^}Nk@MY-fx*=CZXLz-Tdiil=<-8-wBHlz-KAZV3t|r23KoB zcVGoyg$FA15{k?`_}CU47~bA&k*6Dk_Qe&V_0%fUCIda~C56%Q&>%kTF7rePjFtgy zkr!y)e7Y+mMlMKJJ~^?w>-J`NDp6t`f=em#Lka+i*cyeq*l~jcf)8Bv13HB)u+8zy!a=1Xagsj zV$P#@M{kHj3&EVQj%rY8cJpZbr%vbj%H_8qu?|fwU&cslRGTi> zhuF<`n_Pr}WRYAxVGqC`zI&i3vnlNN(f{59v655+CTub7`!@S1Di$>RPNRH_g85>Gkn(B-hVk?wwxAq>m-K%UDHBOd1&+NY>t(n0`*vS@GL%P zKK>E8FbTW$8X9IlRt3%p3S2e?5BJS!s@e@jaw?EHUiecp%6~tsPjLSJQZ8@$x}4uv z`LZRWs7jc-RMns;*W!#R;kTRQ6_(p!chBd!&YIY>FXrrS$)PMl4USSG(pf5SLlu97 zk>wp|pO$9Pbsvw4x!G}7qni{bf?o32U7a8^JUtMx6ggsN-?AB-pBoGbjC;aSLni3* zliMtiAyMyn^X$}4JcW)f(OH`!)?B(qYfMB)jDz3L{nZ_M^~R~fFG&$9;x~atevhxa zv)@T)3{NbMilbo01@3x#*PlXx(!Fa#5 zeUi6?R?AP&V@(ZxqZSpUz3*g#wfyPD$VM$8SsL)zlPqj3g#aIaXiTps{|g0m33hlw zTn%R@hR`h_g5chZ`qQiPuO7)JY|W!KCowyg{Q8l7z)YkE!FaJxAJ4fg%f3q^6bbKL zWblzeKiLc?4V8&$@iSE+Ml?tK1#kwtC`nvdjAQ|v6g{=}KWhuxMRKb^OKD~+Nryk3 zmpZ}iG^EYV%N>gg^J<~7?jA45FT&jj+|4bs7SpFDj1;0U< zMA-pnjp^51*@}6CNaQcxZax`T&HjmS5C8dgE*-kV#a6e)vWRFswH!2eJEx$iFxEhJ zxGHL;ydT{Mt(^#{7Ub=tV5dxhi+~Hq`rJw-4X-uaT~DRf=>>LK) z$5wM{t zcL0=E#eeOw?T%})cG-QOBNdc-W2q0m=lC3VDRL$~6RYYj#Rg4wFpgerX!W5Q(#SnJ zTPnqYXV$8hi!Kc0AO0v+@F#-acFBim2cwX^JvpI68E0c>yI<@``)3&jLl2$73zOVH zBHB1Qam4OKnk!{fN$;ENfPBEyeA`^Ok0Hg1AsJF6mu@bg zq61PNyR2@qT%#xIb1F z$xyuVN>h>T9*n->rc0ntS!@6^m`tGK2>I&C%Me?XagV_s^puEe`Kcdc*JM>E)K=p* zGDWPR@>273)w{Kib)GH?nF{q#sEvI@YZINAtQ+btfFt#Ue4jx(gC$y{&rz z+R($LR-*jSLu9m4m%b9{EDXJkDm%1VeENnpLF;lr_pJ(n6^^eVe12{eUalW6oxwkc z-qw0;IW-t;QA%=ePa3^h`Fg|2sT|eFO~J3VYF=K#{lE!f#tshcJvj08OASg{uhCep z{l0)W5in&QHgrLL786y>H}1VUG;P3+a<8*w&e`0idJN8I;VoXP^4VzX*qE!AS%br+d+!x#UEpn&B0ey^&sf!Ubsyg!A-`75 zeJV**u;+C}CENARJai;CJ)@kT0>%0i_jn5KD!YQH&<^OSa^BzGZQ${9g9nGblq;tfz6p zvmjV|P^X3uN0RZ=%Gjo+a@&Q?SWIW>M3I?&&*SK+;^?7pIf3f!htRgsuhS8izIWNI zw=a=q4?6gPK&sK2&wY5B9Zf#C8zS%p;EUdZ1T9ep6<>~G_ zGZ0N4&YM_PdB&{`J#(#YYxhb2fkDZeknso)tlbLZ2zas?Nu}|i2kY^4_tx}ygMh~$ zvZ`T^knFt?QY%wytV4gm)D%gzrxACNgB*u;OB^PpnitqR{)V$uv1Xm>NJLFP9DnQL z115IeVHaapD%`errg#_!UA=IA9a{0D6(+tN**Peay8ZR=)ow?esSWDBTFK$;a@z(x zWO8}+Iv#e8?980X(9dPUMD<|>YRxJ?y;W~kA6NtMk0zk}hK;J?kbAB`P6u@&+e%tT z1%I)@Abmood6U^8v6?NlWyKwq&HqKADhqjXO=gO!2(+C0C#unv!^UZ@z&DsoReHbD zt~)fcWuf_CAv_t>1-MM8?8q-r3#l47%TP=D0H=2>HXIzlWTF8c*m{)%8+K4cuS;ru zgRneU@fj_@TKE+!Py#%5AzjA%SUS4d~A0F{+)buvN$H$%;yz%Isp0h)z z6D^;H3VcxC?06<8YelXNl$2bR!sE5ZvLox1w6$AZO~ts*tqrJ7k&?7lD&~A}{R8|5Os%#{rX6sn(Uo%0mwo1ly+L(hHr@<{6128 zJ1MgCF?6ZAajw}DF?b0W8n6yp6N$`^O<~lntYuvXm=u2BtUjNWZsN`Mk+oZ+tTk^g zFK;jFEN`py9xyU+`VP~E69;+95z)h6uwzq9-Xt_8(Riigsl^?KQ1#;>lPIVI6RJMB zl4Wdq;=~p>q#msoyxDO2jjq7SyO0}sEKPupMF?&5eaiKZ`M_+ZV!LkKfP$Q(fW;c% ziIMgw3P^@PZ3zJqR8r9G9+tYov^aHcxOY=!659EM%w}L z`xA~v3YM=)3-*~`VR92)D&$E^xGS;mkvV@2-bI-AgNYV|4R};V7?FXb>Z6GC>uB)e zSljKQH#YgvXD<+{>T^l9o(#Jp>75$$ILQG{%ajH+&zj(C}6{bea$F$fE+d%^F6(efvYH} zU)078%{Oof4G5%$lCyC#K}>3p&TXcpIO!dzKek;@6zpVAfA5&zvDmH~wz;5pRGLH1 zI%(xP-}L1Pbvee>E@^dem1n;VolvhiX?$XoK3J!!;Wd;%_;tx}Hh&DSO_7&M^~nTe z0vOy|J8;zHol6}3_f9G0zfBiGj{UzY>r+RCW{$?b!0ZX+4s7NC0JIeKeH`7;< z6eXwPS=qM~;FZeGmpOjHdv}3XXW(p~?2-iCgrtUG+pA2Z_69?{W7R!7;%%qsU8@2@ zzYUTdnsW-{AQ~1&hO;!TUbgKt?uRrOLwBk+@~qeCfd`-tw|=%5j1q<29#|P4k$(+E z3S$}3c%MP4fB>D(Qhl`#p68+i{Eu!HC0YM*B}D^{8;iWUwdk-b_*f2r6PAFiWjX@1 zblwc)10RR-85LiMAnF?*a9gtEJN%gRH2P5a+V&P&A}>gh^Xj>jHMo!G6uZ-zS^`_b z8|k--T2$w22iO3YT(jux;prd+%}@-&ukIjtj6^a`T?5)#3&8v$gguU_imN?t+Wh8t ze&m1~vFr6Y&D~U&5SBhaDiWAWtVmQAFp1O*dJ@VZWoQx^mg-;W-TU|Uzz1TDEPGfr z!J%=E`FykG5P*FEjv~;gZShanT}{PqLtKI|Fc;u&V^&WRYcLTbDw!i?qAe?!6;OAJ z#(X)004E;BD|gfQTqw5Uk8X}^=fo!w7YT6-%Yc2fzcQGE=FkE8&)|1k$DYOLxPq|R zxH#N~+*exWck!&bnktcrM$ekxN;hL!QVFmRF7wH2g_6LMYgF8b%Kr5E-g6*IMpm<& zLzT`f1wsAraK`M9!dUx)g9uc;OUV>AphM~TqcY)+A;^&XO+`ihjM{8r)Ci2o^K46~ z32&(GN}$4LL>+V8GGXf4Q8mp7-*C#&a*aTUf+H^%wy_Jgb1OQvJG*%ol^f%#y3L+c zad9BRW(&o;dvyRxrOzMvT^kdL#i^i0ME?VB_7712~Ap}gZh3r}>3R8jei zpodbZobSmyvl8;~QY7oGIt%|MsL~OldU;juJXAAI^Z2E(#QXSJrV>X<<_jQK0)4}h zfPbQM*9_ebva)g>gp1-F`J&FA*_r=Q9u*r2#SS9I*Kl&m5GEWgN_P_DX#t}k;?rX| z46K<;Y=Cu#fc$YgQWj95gYP5Y=y7yWxs!yN3POjzg`t z?pkpOqGVGZDF!lAw39-83kEg($}kW^23|z0S|`~|l`Lt+sLRF?%->e@50?|d-vnS| z#~JQR*9au(L`?w`+McucD=Wg9|0y{jL5p~Tf}T9rOVhXs&+#l(wy@4EW&~Z02dn_T z7s=w;S$2%`=U!bvk~@ji_ylS%Q!y9jojKTgtcmUYNZ#*O&59F$EFz11WOO;w4E{op`?~)Ao^YSUh<-+HIo*G$!h8 zpW8(Un;)SjWXq3;GRqr4cL?kdl_NTD_rHq7 z1i~j_35b9fYeLAWVy!t5gcY!mzv9lYr`YTjYL0C##+&r&eVmQVG91F13XIh&?KO;| zzOOi_^Hv_E7Q9rW?lK|KMx7;wD;7FN$0O$%VJ<2&oUaYqam|}}c6!CTp;!|h7eF-y z?BKRFO2j@jF3yEhP&gR06x&2Sx#WaziwHYvSyNXmB2n7nt;}50hC$;Cd#0rb)X;Hl z>Ez>&PzOSTzn|i>H%tqSn0(SywkzB$j8y%c2VroC*QV>;vZPLi~Yn zPnO^!Vw}&I3yt#5uA31ve`tHk@2-AWgGVt>`J`Pli~*iK_G#3R%yPeHppR)O9_1oi zInrZ?1L+)*Enx)9 zW3gu{P1-!)xqW_MSF&M)PRoyIJ|Q65z7<)1N9T@_Hkavi=7_*w-pj`!5N(Dg2XZrv zp2kp@lLRP|(=~sc4M~xt1q~9DgVDG%0}{*Qg{B29ZapXw46Pwc5>zl?v4UX?z|Jj- z{{in_ltsBEOfkIn6(`wBwfIP1q;3;Wz>X{jF{G`3x=0fX2-)^|v-P4t`y)Ae* zNX2xAJ$)<3SwKqqhG8xxz~Z+;BSWm!0r2e9R3bSZ)KSvu1(HoJOg#rqtZEy+7rk*2 z+)f@4zrj?1wElVpG3YA6Oe$ta>8@>Rf*~D1E75`teY>$H+p>c=iV7*giAE>@I@fEO z=w*)5sm2?m_|OD3rsi3#_L%a56wd2z0(uk4>X7%F%0>04mh8AGK0V2ajv+L9%LEXU z8;L9ntX}nM4+so{Wf`6VkhAL(slOjzQb=aGn={8Z@GpNtG=wfBC}Ue2%Hz`6#QgG| z;oP;eWw`TdHA_~)T#Ss6xQ`ZAv&33m^ynkgCnBSy#K?1R^vajJw}U6IK8=#Jk?iRn zuz1)O=@LWJ$};^B0`rhg)BDxI;eES0Uig~umk|vVlFTu|kveJyw)WMyai8_A17?`S zmk1UrA@W5nXpfRekSxW|#9f_^N_%_8++dDI9sLL9XT!h|f})sHogU&_##lY}fMB?B zr@Y<1#ja7(DEllyV3~6MI1_8xl?_FfF=+N&I=8{HPMC)udF>pEK4^@&OKXoNaZfqM z`^VYwp!L`+iv9oshQ|bs8EcM!z|{iiBS~BJXlB2UVFM@A!~lBp;fJJmq?eWCw#thRALc$ewhGrd?~{WTPSK#W@#IZD|sbY;gH# z?#)3$05`tl{Bo|=GWylcHM=U}Hj?T+Q}mjqaMC-SqyZll`Wu70IKg`-MuTdzwW?+P z)7PqNp%>-Kb|#6JQxJXHj0BVn?>vQtTwbSKGzKbFHC}+r);LxvB zWA|-cwM84ZlMPQ{29%dwS~q*&&f1Y2CQ9F#H>&LJpRAcn4P0v7Q?$lNQxMy%;04_6 z{&F*4GV5NgW;Rr}vjP}Ca%x)PlhT~yGP8NxX4TfJwgzRc=ZstQYfkWUqbxIsSV13A zvinYqs1zpb@aXO2Y>Zn*NVD*4EIW*Cv)FjqB(z|5lcDfMfRsNYPC)nYTX{qCQDcM` zWSyMmL+tlYXn(Eu5m(W^n)$LNqiDl-`BCHp_k6E)vVH`riz%|U+AL4*~ zsNHH@?(AUo2DQmqAOc_Sti9w0!0iXI4FDbtd^i}J+s>p@d_pArDcvU0w1t*!;S}oX z5=~OsW)Xr{8BN2x;_Mg|)ER?4G}jV=^IWgq=LIqRovxro`I$2FiUP%#*@7GYE`&$1 zaxO=)_2iS|3&P^&82o9Gr%Xm{dQjgR_-otm2dsCD14ntT==4t`3b-qYAy zYiC;wWZNCKFEZJm@56Y9juMW_S!{};x27@)S0=m+MBoXrPeuxIUx*#>Ph_%%__V6< z*xeRiWoJj<1KOmnbI>X?!i`o=GtU)|tEx$pLjAk%Qb&efWelyRAMS&ia>dHu`Y*?V zeGg#RnWfEuO2)W!vqwq?xD6d6e7wWVGlU!+)P@|EGD5aD)eXyxem2Y!x1)`FO>LDd z?JqR3;y2Tq+Za|0x_>WVs=Dk&Bxm%`fGgr&h@cI%a7j2zz|!R(c_D}4u~_Y&eA4G1_~y$iG~8@tRdqaeKq>t|NVzFE1p36V%uu|Z`5@NP7+ zU$8UmIZ=}}d7_}qfY&+yW6@q;e$i7pAIUHfoNN|a)__(0X8yX>&2^id&N5^l3o?Qp zjYkHD(Q?tzt5#1ufb0At{034#6kg9R%$3q}}Nf$#hO@O~!C zY6+sRV(E(fL!nHx0RU+KC-0}Jg|&(OKOEt|UOl&>#2y z7|cA`%Ar*?0KqRpW*^lw?ez;;Y3M6W(vnGQV^7s}mWhQ1bvNW1Ec#w~w= z830JyP7(#6=>|FptsJFo@<^`C*dxQsDk|P7EuHlY& zCfKF&<~HN@O~&|c!0EZt|l)x2n;<>#ABdkfI-xMyEL_`@nI6Zgen!jWLZAi~ro5bAHyg_TmW9}rJpyX3nnx@4+NIc$(yyNJ05*8+KnV9>-2%f2E9mK7&v4(=&@C6}n` zN((<2LK==se5`HdgVP(l-5fwN+xue381`C^eGZOloxfX=9>B<}(V1W3J2Tuhq$@}~ zDwEl$Le8!Ps}my@oWxZq^Q-EW^^gZU1{P^6Iv%X|=8&19JN!)D>i65!O0yaZewS;R@D3tQgR)sOGVY!%e(X zn$PTF&kXlHb~A_v!_%6Vr02(k=6i^C3H40@;bqOt*^ZP!@Atlr0ava53)Jct8G3!l zn|}yGL>vesK}o1&pm?!>yvVo31Sm8CP*p65s~AxS`O&}#xTHWko-}4 z4Ya|2GlTztKayW@JCXMGZ>ARF2+ZaD6J7%V!9cMh7S@m7t$Rvm!y(21UR>t-#2^IV z5^ZJB(G>Y^J*dLpV(~-Y<2JU?qLBA=eBye9L-rv&2+uFj0%9;74WyJy{vudZw!42` z*n}wB0W=3=&)Qi8{(w;Ihg&$f1v1)$s?4zhDZW_bd#c!KMWv4+C>+VKa=;UnJ*FI9 zgFXs|?m5(Ye8j>(DW(V#0@xgUMG)6CDRgqDPzarP;q4QGL$5e}PK4cu!D4R*RI&Uj zQ-1`faLGylpl0Lb`19D~N08z`qQC?2<7C76Mn9sYMR*m}!M-3z!cO#21LRXJG#FR` zmCSJrA(FgU@$nP|2L2pUgYRy7-?(S#NV6Jo4&F%;3XACF)+0;g80foHiU&@q;mMbi z1x!d)jEr2N9qhlYr0GQWuQmnUB8S)E8~C<-pUQiDg){Eiv)~D?alGYpqHZ7iwjler zr*;ICoGL4_KLJknNG&4ugaLiVDyQgAq5hchU_Djb>u>q8b^CG0o$Fiwxu~a>F67^4 z@C(AY2~{Y*+9;{@Yak z>}5_!XYiamGwQ z{S>syE&(>peFP3}u67NL_pBxW@B<^v_~fh)V~uM%4AdPbK?RK8U^te)jTvz-q`7l> zOwkhVCq6&IVvxL z=T)7XICnW1iP+-0vtwcNq)+c>pD6oaJ`D#0?X{jrfHT#|cR)4-x8N{kK*o-wxN8PP zz(HDF+%#hJ6#0tYjD1OeMD8b`igxJ?GP7VBa!9jQbzv5yw}#LQ4w|UA+mY9DQHY00 z(=^ciM(9Kf?DWm|ip=fJ&F#&^o%wdo<33`|x+bmb7qU0h8!Bxug)M$NK&%MIUEZE* z<)C9iH34vdh~hhzuQZHOAmzOau$ft<`BCXH=?X}+EDjJUqr$swm22h$=lwT?21B2N z_9xI6n4d~SQ|N6VeSP;H10jV=YfF&7+`SOIm@n%ErE`rJcqJZHBN zY%2r5h1AhqT=5wT>_bXV8)E5tXjKm9fa>cmWn%-(t>AJJ3Hc5Vd5Pw}#7VdnT71fO zY_A2P^1&PnaCMrezRfhs2VJS6r777r)fMs-R0;6`W4vnhzemOuNrF1Wo)SP>gxnUM zsCt(n;^Q!A$tt?2QP?vL%U!hwnmOTY_A#P3fj%m7JeO&Hi!_@UlEYh6sCIzT&(=mR zyATG~Bp=CS`(%Rgr1glrGydm}T|2*6WR=ZVY_jM~l3K=4ab)6NERrG44^EdWyqzXj zwinRb>=-`FB%6H!y=V!oA9oq$uHfW5fXAYVs?<{|rGL&`(QBH?$TI3;Cn)TE*{pOra=I!Wn$VZT4 z4c6|l(BMC31ONw4cb66@7bKb$m8;y4lr0o(>)Y1GB6C5S@+qzxP5F2MP)R*p2bSOG zIcunR{Nz7)n43shtSV)-uRWBTZ`VhfYA0aCDSS1M)+TMdB`vSmM-6d;ZtigHZjVGo zRzG+*et>cXtj)-@hPBvAStdes${4HHCw7S@>A$A`8Lnj?b2bm1_w-Qp(%v>5ec|;~ zhC=abWy4ZUkXHiSM~uUk(^lPkQA?hQSs>6pC(gNAH_<`;xr)dC}vG8;6+Q`W6C9m>#J!LswmikvuDTz4Q@V&hZ-Z)d@Yv z;V$0i8@?i6`L1!%tY#@rZ|jIG>xnFP3R#?t;s1;`^C?0-lzZ|k|KSeC)7h?kc~bP% z)1sG(AeZKPe_-}S}f3cJd!8M z)=^DE+!xT_VCMTemgfY%rbp9<>=_-0xf^rJv?k=R1gqX1<@5dU_l)wx#fMe`>$*;V zrdUI~j*-}Q*NpOc>x+>C!TKVe$hoy?VECHMh!2@xeJ$o(_kLjIcpO(do&x`^nAIr8 zt)LZf?uKuB*Z6t^$}OibPL98=Va8#~yj{T=NtVr=k#A&Xf*U;nWab%R6dKRTTOh?& z^eR3s2H#q6Tz^@~@vOWL$7$2VNS-n#r*aF|b0128vo#v*&6(ksww}h+Y5WIh+mOgx z%CEMR7J8`gBq+rVX-`~)@-Ml+v5V4mohHea?6tn}d<(GZSlHse_YXK{|3-a`1{Gb%7)^@QqqEU0GR21#tI#-Gs z77H*>g__ASy?ls`S($vGMeOj;ZFfDOymUH#Ep%VZKQdEH9{occ>bJN}yPvARzT?CMDafirayg z>jTVaK_n~dnib@Nj{j|ddS_|MV!94QDGFH8LLw-U>ZFmu`>bo}KNEh{@v?63aKn|_ z@&!$r?2FCAZS`WYQ9=f4RKLf+Wh0bWW$Z_%HYjSx*7uZwR;;rYF zjG;C83SRHy9mI5k&Mozrvr%2{>arb?zXv9}qNojT?DrM_QgV%6uajV{t6CR4+!i$* z|B1@qxh3U?zmKOceO)6?YmqcRu_~pQ1o3YtNfKdN5@W|VyD(?KybQ|1=*+hfAX~99 z!S+`)X;P2Kh*m>E4iXkB`U5$>2E9!wfy!hNi69oSyDY?hjSIa6 z3CoUR{1!Q!if993D2P3bD95Q4*(Xh1&^oXuO5BaT|C)@<`;ZpOO;ymaPWUlq;619> zg(*-YpY~#S01Y`5Y3%vno%Tw{XaX&geCV61n>1H(CEKqbdDFhr&QL$SRuB|e-xN9{ z0nFKmEO0*Q6(2n{vzeUeM9$Vcdo^l}(G9L|;Q6{#f` zVtVW&+ISh12UwE%}zN+6-oQ)9HkxajGMO-AC#n0(UkanYq9 zKz2mnn27)Ek5$lMbPyCWvC7}FBe4S;G7J@L3HMfqkdnrE4F|>Xvhnv2Yj~|FH~I4J zMZ5BzM9JrD+rr*OQ>ZHR{c}^y8q7=Ddk8XxeXg+*x!YMICkk!QtnJ=`395)vs{w)? zj3=C41#%=gXW^*0Q`KS^2bl{kOitlO&oY;N#V4yBqfLV4l$%erakfgddETAaw>Eo3 zsbiunT@aH)Y{2dH$pM?zBA{aT9(E($iw2(8@x1nHk_~(aRaH*_;>RHq4}7zpD$l1} z@~tpw7(mse-0IhNvN!J#}?FdMiqKXqlMk?UoV*CMm~7 zy&<)sP!p$T{~YN^LC22A^8XqpXI&$IP+yGDN|tT=!ze=On@&nn9Jb~Y)hq}-)Yn5| zz~SJe#`>ORJpGdK?QnhiWXSG*y+%W~xcnAb@w$YBmTW9YZO(p8+m!TpK~+)c!J<-N zpL3xp2n0n5S=Zm*I{dj2KC%gaTT=1 z#JhWD5CeB3UKBPX=2VgTly1&dzC<0@1HgHtg-$lY-coKOT33svS9#@rt}^F(x@vW= zdYA9psW!HuOdE(DllhlF=iJl~YsW7d0ki51sTua(61vD`4=0LNdRCVLdBcu*ABUpfS!yj5b`UxI~;DNkf~ zm$S9XlZ)0PcMp?@8&%(WpE_W@?sVOZQYDS-=6-rsyTaC+CU@C|=-0pSU3A#+f31iP zYw7o=Llv*)eab^}gjeZ}ug)AUy{+DkZ;OeN{fAg)YR@;<46i4uxRa>f~heS>Q0Hy{7t4?I;SFB@h z0gh}!a=ao_zg&x_uQMO={>-uDVh2Ad&?!PtTYb)|p>x}?xfqR1B}wBkbzVBhVYk2- zZIn;I^QhMBl+naUP%KRvQznyJICW9gSb$%kfh_Sr8$xW4m-C>a{7Vu2IFx89fuEA>QBQA?Q;NxNLPvG7cEjRps4@;d8h$`? zXN-J7P>j(3XT}lM$v5Uewb|ZZh2yszEk?QE$%sC8 zlENu9b7jPLXpV%%jX52}HmxRH4I7LUd~K)>a3(I`;bbqKD^+_%6gmHg;nw;Jb6>&J!mWf}o@Jgt*xUIgHaZ2`(MyO>)VANS+AWDYEd zNwR4S{bhTaZgvOh$rA>z4A@hi;>9Q~B)=G1gH2=2BE48K&I7`LV$2x2E3$J@KNifh zkvfUL0-@*jaq+&@Ty@St)X=ogJjwlkjfR$qJfK) zO~BhoK4AvcxgP|GA~6{_V|bB0F=lr}0FsS287Asl-sydCl33=e8wePee5T-;OS$R? z8CKw=FGXft9n20Ourf}0=B*(vg|8t#qQr};Lo(L+5t!yNB@4t*e}h1CypXljP%c7= zX{?xBofpmZXYKkCsFwG5eU$f3CGuq>4}UD~&2WSTpR^b*+G z=G%W3@V@mgO+G0;KGaA*Cynxt|A^Zt9fsRxZ~` zEAaAFCi{K{YXo^b3+9feMTs&10)NlRD0%wAa7RrHJ^zf*j6l%oTG;5-gk+RTvnq>5 zdSijGHk zwdUTxiR5wpSmi~P_Aw}SGtMA^`!tR;UJx~Rh1g66HLkp6k4x|>ok~Mq#zY)3S7Rl` zc{n-k#vDz`A8f0Sl!4J`CP~7?)MbB8ey9>?zZ#Puvm56_XoBeC2RU5_Zo%1_hP~mS zQr30r#~<-Q@4+y6juFf0kaItA>M2J|g4%*6fD2{NqqT7#(9#Zu0W)Au5e;l|0$I_b z&RsVdc4g?G{IuV=Vu@hAA`ql-uwnymx1;Oj*-exDF;|=j@A8_JX>_vrEtv7gj>i2Q z)NA7V0QDNpfKV3Bh3?o@#PgN&Xv$aK2>i{){Zr%QWo`mt8r9CB8_~JkHX7}ggx^w8 zUIBfz8=IFDvOiIzcw73+M#uiJ$W=m)YOXadJK@ROr8g^)U$rziMf8Hr;f2m}U6$4T zTESN>$7M&BiMA)VTXU3@eCwqeE_Q7FljH7fpKrQsxv%81U({i*sgdSN1^w)kv|6!- zGG(LDvohh--h6>)8!1NZ@Y;2Nat}XZZRIqtJAANEJn(s2*{`M;=7lU<#I(K0Ir+*@ ziLZK8Xw$A1zpp3#%r2t-8(W}9IraN42w)|}y(3VMd(}pSPj`HBa=Eu>NWqNF*H)m> zJ^yfXuOy3$7Wb_Q^YoWqmbbb_uGtUpKWEl|=u6qo3cx3j008V{{~!92wS|+jiLJ?h zoml^&Fa0+$SJ#?0P8;G#KQpywhE_@g1`Y{!;IcQy2Jn?06U7u7Sano_LKYw~ zcGSP$9cCm5;y|avdbX*O0W9=#eA|nQi#+%59M10-lyGS9kX>GnC-@9|-iS;L#I>NF z7=c$v2+aKlu#Z~Jk4|6UXpg}0L;c^JknSM1M}wLS5JU*xxL(pNTj257eEnIYQuD#1 z1Rel*lX^X}5GLc_`grbi1}y#2m{sasFbTo$brJ7mclU%I_Q+p`f00AK82_T}-H*YW zC*(;Wg_B+!^811 zRPTU{1ZGF!8k__jlm!~Qj8n250^-Q*BX%4AtGW|J)N`3=g+L-`fkycU4;tkgp+sG9rPg~cZEO5u+c(w^#4bDGPl~<@q5buS-iMk zI$=v2JTCKp#U0m32)qoPWxPc@G0Ku`w^^0cH{X0B4(pITKbm>@ahn%c`b92eX%92n z3MH=2i?7pWmKgOJ@Lr47Z3a6SJ43ev_SaHEGdDs>78h}KBEWMkF^(33)x6VcLZn~(=_I$|!Hb`naEq#U5t}oHZ^x2p>t?FkCVG^tJU52Ju3mL{ z7?dZ$Qle^EL>C>=H;0Q-Sj_`80_tG3DGZ1-+hFNS8ki@Rzf!)CjSSs7a(sT>Zl_d3 zpW;31#!h@IJ)Fn4W_H21=($m`{I=FQv)U4Kz-G(84Exm_``&tZLu$gd4j@FgUL0#d zxVdp2b<^tw3kwe(kmDP{{T(};%`5I+YRnAYbj8!Z1Y>{V?puq)2DJ`ilnmuSt51W& zkKJ!kyT-UmprI2U zu_QDhv6I_WB~ zIv5Eb9^#}y<`J2QK*e;Cba?xLEYp(bk68~qnh?huI;U!~B)Z?UF`0vYmnIi(db0iC)Eo5F%*s0_zQybm)-Wu0imum3y?S`?YUi2P3OMCy)xdq) ze)63-sb)Fy?pd@j2x6!rDm?roQS5`cjfFH(311tLyFzIx>W3A1Ku7EnCsgk(kE~*-y6zO>m9JiyY%aHY)51qCqU;gWdthI zwBGu2XS*AeN5J9~oo^9i)+xkioo^8_W^&f6W^k)*6h9Yv!`D3QYW!W)mcG-?NioL@ zE{o(TOKC4;2B2JY9rDgmjQ{4(_q$Xxe2x|&4KKU8veHYORIDV8@{}k_NE+$oAH{IU zu29d)nBAwAW#(AwFc(Wbu1y@vQz6r){fm_1OR^my-o%OM1P*1B1>#tQ>okuGjw)BJ zm|C?eiIc@$|5TZA0=EcLi|i`g7sa7K(ZCwst}Hav^MhxM-2`s>~ zU_csV;+#Ii>k&FMNcA)bxL9djRKqZ!$i^>SUDomOCQQv{vvd2BF>S|RiShtYhgAko z&BDSm!}#_*s;9d>+vOg-`Im+0bvbBTAqT3d_~?|g=+HF*D6OOTw||7Q=&IJEE80MXnURw z=mzLZRjic)GZ{LFjQK45tvbUqgL>!PcI@DS@EGiRF@EYHxwAI8l(rE!`Se77t@hj&3JESIdn{_E4qJ`mJ`7w7x%|V?bU{C z@I6g0%Bb+%BPNs1v;%{~pFCb)%&b=9W3Ijg>5?U|e?LNXPq@LVgKp8i1i9`$T;hf? zw_9K*)7N8M$}UFtl#my~QrLxF`-FTu2a*WeAgkKqQup#K$879{yrk!{kpij`*Fe#j z(q&ED_9I%DzIhkB1U(9<12dIM3M$(#axPN!zS+L0evp?l5m4Ss;_Fq32Whqm7K&Pj zp=&8|F=uA(ObUaQZNi@xiW4Kr%@5Q%SH88#uagH-`xdwwtV_2?z6OQG?B{=*M(prg zTCFt;i>1t#6`>QE)wXh%b|YxSe#ZN57f_(WRJX@09Dm{&ic8>)&2MxuxBX_Do@S`F z4R7bUnE8n4D-wixJK=m>KoI$)Py3>C8Y0IC5NWbnFBEm@D5&PY^LghCTMTUjiS~6` zI2fukZRWgof>?24e+1OOzD(d*uX}o9O8iFC7adL*0B|tn$6O_Ck}nIpvK$+}7nDgghB+SypsC^V zwi0HWgB+qCA6{bZcv)LqHfaa5)W7ecPil9}Xpo7j=mp=&1@}Jg`gmMw1(HuRRovA- zxW_J#wm39}39n6()Fnj&-1C01N2i?NG)-!D#MDpBhL`CCe5f%?UgIpBz^&4M|F31z zQRP^Q92@|k&BpHFfA5kM4hwKq+`$ZljY6)) z%q0?{JV|!$8D{b+cITUUizrHFqLQ_)W1@q_pK%JesIi3|A9iLY)H0{2U zg<&RO*{+>dea%Vm^#D0jTFs5j42Kv`O@Yc3@xJJU(Thpc1L7tYER&7mWC$dLN0R(W z{s|tO=$^)Cqyirx(CIZvfUycKAn6x=>1M_zq=V*&)4&Y|{Bcm~kqDQ0 z)x{MvO~}tYCW|-dCpIxpg$<>ns0xrRQ2bqHzUMUfBLP;ys2tcRQq(-|j#LHGUx9*n z;G46^@s~+OI0fazxMJ=Zc?x719*j_cgfIq*3N^SPA>BU^mu3)w(GYb2h)n2T|Y?PyG#G>=vK2*RtM2PvcTs&vNZmr&1sAD?8*g4_tQIyL;o|d0b9=na$HF zCjZS9bI!e&7C>`g>oGDSBHj~T$&c6q9X!iGreFrA8Z2*?Pw(Mxu4~6}pR6rd9JM;l z^z_=M$s{= z>jT6ZN@+2WT#5%WpSiQ&6|;LA4q%zCaUFUcBi&RO9J!>n7Ywkb`X8mPajNm`$^EB4 zONW_)wx3fjWypD4uEphp zP0-#DCwo+6UnSftL1U8-^G_OVB%fI(1}E*Y3%J~16MA@0mbTN!P?&ytU=vBhCyrRe z)6+;uP^-s<+NVYZ%6I-T%y7w;%a(o-i5}TTeKmZ>&&D~&&a!MXE1MmYLP zUvf36RF;-KxYGwpb!wiSl{`WC0H2Sbi97}J)PdYH<f&qdySQnB!+WYvm9@XDoFJ48uZ07YYG>q_B+7EN)taX-tHwO7TKbQa;xq7d@ zJLS6&)}PNO%MItULB^S07|^h=%}zk)X!s3y*3*igBWjmL3zlOIX_=PcgQ^C7;rT$e z;2#d|WOD_;h}BT$QR-mp4EVWdib9!njOC5>t5kPEfxG%e%jSc1eS;oVkLQJ69L1-c zzV~&Dg$c7mrl^SfqTD`P!UPj)OaY%6gEHiB7E(js|9oIv72?J}^}`HX*^x5lSd6yl zL0(s7MvJ%1tt3+^X|qtlU`tZMHpU_?C$H?a@9t?ajG>csUZT2$EXnUM=)aTIaLxT( z=GWS-U3r^*UkkQ6&RyG4ba_&NR(BU>RXgX}u3D#ho#@+g=)ZNw%7f#~hFwu1;*Xy$=<0`4ovjTh%_s6lPg35~ql&=LH$PNI3{kYCRf%iSji zwsV+LsHrF1sm1z2GWWd7IiRKeGF4U%|0W5YcI+Rt@Pk|u1Xrb(${3sfzC08MCwhiRz$IaeN#bc6CLN#f=DI0tekfK#S=Aw$lAsXJ~q7>|4z=V zk@Sy#nwk-^d(7A?D7M@Q64JAyB{Km59G)yT6vH%k1iob{|EexYh(Ts z`C^^%QNgAYw?%28!UTYHrm?Hl33@pPW_ACe}CB-nbYZ=|A$Hd z!uuaIV>^3i3p-n<|4B2xR=2V%Qb+o}(Nj1SXL@NB@_b#SJlBP7a0pys!0pe8T}lku z=QO|yatYvOX8QG!+m32D+;jfu(6o3cz}g+Fs8o_;=PdMX^^CuNmJ#@M|NhA1;hb!| z6pqgc6}>zQpCTq`$w@-#h;Ji2aM^JbK{*YHJ~cr!qX4xKHlZb#rI?lc1J%M9x5&>s zN0>C(E#~InAe5o}Yi2S)K1m&k?tnor8V5vSPIAOZ3xzl@Y1-xx9CkoTeisKIn`rFj z;3;E}D$1bu_dQV%X~dxf^Y`0EY#_UFM6-!L3WR2|+h0KhAm$HOx>;Md%KZSlh3as@ znE}@f1t5lzwUlfTP76MRbf7y$af2Qr#MXgH4McT}B)Qt^Kcg*Tgs#a9t`LCvO@A$NiHX4=t zj2S{tz|){(@X_Pq#fYk)VNl8%rXl##aD()LvEhQ6`aupTcpk0fH|*|}b!wc4LoTV+mW%=OxF zQ}+N>M!PL?|LMmti>y4_wpBb%-XbfX5mp|!9JK4`NeQsr5cbmzc3Qe|R}Oy6IBSPZ zxPU>Uv9tI@pMx(B3vQm0Pm5VcFz17gpQmpYxf?{AeTsx(t$d~qLSrf#Qpk(X*FVn2 zoy-}X$YV|6*H~-1e#RTXR#!=Gwy~md(pg&7sp zb%R~Ujq}0gOgnU*ULb+F<-rTT!*%q!DGpcap)LdSV?pR@$nae)+<{@w^Qf#>YT18_{4%l{bS7_W&M_ISj zOZ?OTSJrdg3=uAO7Zjd|-fy>|Uk;jS1b5;)gU`_utMT2xlo-ZvWTL6mrFS;EL@aLS zsza5*PaqSE%u!H2b}F58DDKls>qBFmVfIQ@$Y+Y#5L(?RM%ZUq6|e)8Wpda=kd%D` zu}}eiB=}05qvJ0=Lh5$wU^W5Io(jNDLrgj0!`~ow*d)fvzYsc2tfCk%7$oh}2jWH! zs5xm4NCik0Xvp3uVbKACsS762j4ov=pj?XO+AiAQ1OU2Ye4E)r}>E9Z^zv$_7#wgyIkZ;`7J`tTa^2>K?`J2Rj*!aT3A+!Ms`!$z7!o-3XXM)6iU;n z*JLnkR0Re??Zb;6XCJ94pnEKvL-p+kKg|m1t7v|%sX5J;?v_6KXIXZl?OX|x-6I3% zKBn#+_@of&zp~v}tNL%!6ayawsJXUx;FcsVvi z4j>zQ!mhTNU)D{;+9>mFmcJQlaYIOg@bx5{5v7Oa8SE5U@vXEH{9WZ&xtHdZzF^+6 zBRo~>+Q>`)d7o+`Wnxt!bJGffU|MF}Rb}NlHkw?^%%o|T)rz=T zsM0xqx2Kiv|dkT-N8 zTr9^IpM>;D?@g+|TR*j=|L#vEK~xzoMc~BITM>|2yJHnISdM18 zVA-V>mDI6+U&GeD#s=fs(&zi8kW|+fSYdxKcpj)GMI*taQcQCv;m;0Np^F; zL0zy(CZnYIQn}(}lSu>O%7W6QObU_<-yhrau7%7jn%2Jd#<=g>uRd3vaCqt2$QMpe9mh-C#xo zU9}X>>K&}AEPDBl)1&bF1I=xFnRlx$GzHe$7L-clgBXF9%vu3KgM-Ze}?~P=EmXmN3JPri@WdB8+x>ioD zJ!9QywbW}?sty|x2gX{y7%ku6!@`3K6#zQ3I*X8Mfh>dEs*EAk>>(B0nQf21} zJSk0&@1sO}KzsnribDWC1$`A*f;7x6nV9AXN!BH;A&dFpXfPOW!7PDM0&Q8wZPq}n zF)PRLk2#e@02o0M#lc%d2O}c#zHARpwT4Hv6(*1_8f6}e8P|6Uhen}$Y%Q=gkJOL} zF>x%(m~OJpyhf|;J77f0E0h?aza?4EZ;ClVUi@$D1?RT+XiVZ>A77Su{BB9%k(#SN zO*c@ zdiO`lNEC|^sPiZ=NB_P(AC{YVEVjS7DnBPObW5;fMz8geUK#z3LwFHd_RHJ~kXwYWw4M+{&L3cOrsxJP;oz5Zo`j&@gj%C$_ z1t{KG;P!q+6%4$8k-Se8w~kB}Fq?AUuq(3v0|CDF-HI8bo1wLwD|=0hjfOTa^mhJQ zf$ss^-K`9>#X~Iaaz_+fG7+*PSIjUbjYzZl{8WF=12g52!DgiF{!MT>=Os@MJbL8b zT!jahj$%k!Ynwb;xo-N4_)H#LudS5;zJ0^d zIl2@NYp!O*UC*69;gTWn4I}2gig;!{q6tB@>O=AR=Lh%s<(iJFmf6atrhNc7Z#X1Q z@1uzq0`Cv!2fXgvtj^28>DcQk!8b%^Sr_R5-s9@m#Kppr071sY&S4%wQ|8H7@D;}$VT7%ZzZz|6o70BUWj4NZL zYzduclQ2rGZvVrq%tF>$SNsue zFeb9dBlRiZ-My-$H zO44ZtA;KfA`!NhQ*?w?TDSYvuj0^{&P7_$HmKsm;8_CF*Arl{H(`-Sllt?K0MnT< zhFyw_eZ&6et%~0#GvXB?fi? zTe5>;3qX&6P$IzH6Frg5^H9buP$SM9Lp(!$aFp8vS|*QmqJ~BSUGcuH-9-7mI@@nw zX#nx+y>#YnkgY_p|G=cAn!C{Py>6J{fgc#Xehptdi!?a_mE;K2`FOLOy?j8JwylTF zbSbZ1d84iP>Ah6@>N$HA`Z##(fVtE2*RB6)uJILV=BjzDN^(7|CIkpQa|VYv>h_t( z<)0L26BMF*McGzzFnHF)+qZgb_u0YqE+vrL41$Am+qh0xj+`{Lo3qu)Ve>k8sB~Mu z)&e@`vUt@xoqh>%Q3S=-*EaBJ>UYcY%fS@XI)~pt}tp>$%ba0V4f1szRI) zu=a#@*~JTOg7e6i3iDiVOQN4jo-?7onx(L{)sOP_y6M6W04^xZgPE4G8$X$e+$thJ zQpvo0;q1?XZYnF}M1nPvc1D|5b$?UZZ3z{aIbJe2l-+=1S!{ct2ClqKK=+VvoF!#X z#`+1IH?}^hW|wOuF<%6Jfd$>*huwcaSn-P&>${cHm;S!U-e>=9NCGV0Kt~8eez*b{ zI*?C5Du}L&+39U8n6Tofgz?lkevJQbhIm&&?sqpyjP4*z1v#vV;A71-J+IDan zokT3%{UQCyHQ{jjvU%`)`@B9-+(JkJp0JpN&=q7JX3UAZ_sIuaYm~djP7d-LZfJYa zap_p^?FowBIDK;&V_B)0SI05?5(~sT62(NtuW=XjA2ikG9-@Po3fY>Q5r+2sV_F(N zu=Btvdm<>ym{Da>j}fNNC~VQFj;u`yl`otjYJLAiol(O-ur-J=V2D1Y(=1`*#TKGE zvEn9l0tEwK)(&A){r+~)D-Z0HZgQO^UYktZavgvi3ouH|7qZ^eDI?!B!OvdfC-q>A z>4-;!M<@s|!Uo}@Ljz+c0GWv$ROk--alL!}p=0Gx^U=w6Kx2UGlxF&^V&_590-sR5 z&;M|-j(-9GT%DwO3%&FZoP_REz7~LuoA((BIaQ+OzYpGD572%9>p2w5`jy=XIkLL; zI)^{c3}`9(Fum92!34OO-T@_0+j|F?POLS)8i<4|89vbm{9^M}QM>(Sb?PA;4@CAV zGAX|(QsP{DQ7@<_E>#UX!Ekq;uNm&}oa9MC8I+|1P*5K1TzmVktsFmaUKIMR z{boEt$qR<|d&k97VOff^)!Y>a)%%;UoFB7c0;%Fgv#n>Y5oN@_^rPP?ShBc^=;FC) zPYzB9P-$TZz^e(JMWUxKsdh+kxB7EDLqx|}_x_5u& z6#?qI2@fJQD+{~3B$ZZ>O?)}lA_t*d-|m%fAij$<%fX)U#=ssY3*L`iNCrm9eT`b* zN4_3bz8+@26gTw-tT9RQ4p9Xm=?e@Tn6W_&h{pRO*`ss8FFkS5p1pf&PqPYh`>HSB zTgy+oXqqi$wn7)=Dug# zC+g@YMa_G|oZOa3he`-hu=+t!-XDcDM1p62`6~%I@mz1b&8EuvhC4zUf1Ur4UIC!c zjchkU?~4cv$axmk;Ub0%-rHvS)D?`FQ~UoDbL~oaZ^fwoEGmlw4@yAJo`UiL$GpK8 z$agDpDt>j%!3|`L;*%Z*7g{#`*sCI_PD&qdRFO>%9f`|`a4s3mUyTiy;+Q&gAV6DW zkFPi?ep9L+&2K$Fo4MSilaoc#$KiTxo% zKDzN}|7zULPKQD=CxO>2O@p=}MV#D{W{A;8hk{Cg!DV^;3!XJ&AYEwv%h1`^K94F{ zhi^3%7kfRIIb9S68+eoi(*2B0+;`6ouuC2k;jow_a>oMyl;MPFA=D4(P#?ZzDv~%Q zjkXh>R+i`@qDd*On5rHR5R#IDgW2ZlQQd*FF9>FrhcOx6%7AvtlMYDX?KqcjVrDO1 zL}#!;PzgGd?HxV9sgQoA{Mabtzw{6w#^tDx?mEZDN@uN4(2!eN1;xTs8=4rF$kT#T z*O^3pg*3$SMIYh_S79FBTs6XupRUB$-WKG*#j1}tnjyA~Bdsgdi2+_!Z+cOP76b+E z3nm;zP_KGw$;X}wAC6m2NVA{vLRHu*rI@>8U9TA_O?+gC<(wJutX4N;c~3G?e{Vs(`0EUo_+4 z7>szMPpAlLwmAhJYFuTAz{99`DJA8+uhYf@OJwk#X3eJJ3AIp&1VT{k|<(h0urTVNB`fH^f@KkCYV)+F*nwZ zDdM}TYE%gclI(~=t4aRj^120Y9B5d&soHAm;N~S}?N9q-jS7~oeQ^F{-$+YcFLhRqM=tuo^ntH<;v#L3)D4PlExh6oKqP8Sgj zb`>|AX$9bo69L+X+y(ZqRkAW}TAD;4O%)qPWR$M_Ngxzoo!40r>Zf$wIe) z))q)KK)*O>_qI+&?B(HXo0IQh48beh;RxJcL3OVE(pFo_!rNSbvx!O*7cK z1L~bbVXgjQ@rUWh;1zJ5U$$Ie?`#P|AH=O(J_QT=F`%G*2R@z2ko~x1;$=5=F1l6u zQN)3@n9tP5;Dc3}TIAWYPFJNC}&P53V6r_pTSvk4tlXpye5Rjj5eCkty5T@SI@Gp0hYfI1vD8Xn+S8^T1gmSAj10CwB)7q8V+uDIcI=bKo#_=JXUC!IGJm% znI{z8)Uenxg1#(%_cvA8!~#VrTwSvf)Zy&Ltlpqglb%_2f?f1~R4#>dZO&uNx3NJ4 zSaJlg*7rC!mce^mc*itebwyZ%AC-1FELIsZWVo=C0U1*yL>{fn?&2?f?B*I6IYL(26wC*kkob5}f}G8BwZXUMzP7C@5@LRpl+$O++^#i7 zkW-+!QLM%ad(NvxvqGaXpM_;#!OOn10_-Q(*NA`*NL5Sxj@TUD#BxVraS2VXu(T*Y z+lbFp4%h(bcWgnW*9&eF(w3yVw>GGFbMd|3d{EtrDTBWxBQCY|yM=LdDyTl{&fGU*#Hog4qUS@T%%Yp|!)Ns`2tylLBtQaq}7@|omw z>xr~nlkDCwm3Ozz<%B7eRqf_pe=}O*lfv9GD@1g^XE96Jn)3nZcPXps9-ot6=vr-f zwiYH)GPQ5v;s;b+d6N}=JPO)fbE8Ce(M%GyD(_<8$CI|h+{xoM2>Tj|N?=J*2&TU5 zK!Sh{hbZ&*3ik6IxOQ+sXG3{=i5$*itv3Y6a-}dt%W|bOB+GK8GGyE02EuluR_0Kz zhx57I@P91dIQvMiT>ZY_!T2x#+173WK5_K>fUn%{V(;QUaSZ!}uH3|v4gRx*f5SEG z61;GEi!tBY858{)WK1-Q38Qm*BtkJzMw7AX>`6`O)V~!cWMHFw0-8?;{3^mXru4{v z{V*u3?g8uWNFw6M5lA9$!rt3D*T;MI`qyNLel^qb?=i-bMvgH+6td)MM@Boe5!)=A zWCk0%JWI(@-v=sGgYAdTD-P)EmeY~n4fNniagAW^Ag90a%C|%NxyCW7u(|rH+?ln} zyX--Oh}K2o_ejvaSbikq4(Ou>>(*ir9LtxWiXcR7D>%Z&65s)BS+5&(B-%hjP`8vc z4m}h-u~AKJ^8Y16Aq$*DH?KAKcN^|+)^0>9pt%YpdcpchPxM9-kB{ov7wOWLWL*H? ziTBAtYuqT&S%KJL*S7UZ-|S2U?@|Tn`nQ(q^GWW?N2NN;Mn)h9&p-UdmLXHx4?Ia- zuOce&$RiFtr!i$~&*zmuQsY!K$Of9SD7>9P)s=%J;k8fL>l_7B&iGwdjsHhSw+PRt@M?^F{1Dru&}6p^ zKRJiCEB@mUg_xHx(dEwtjQz|9D2}{rvZaVne0N_8&mv`D!^OSM0%I&OPYRD+*B+vi z%fy^7k`|Nc9kI6$5|#e`QtQ6ePvKU01LN$@ozG|8j5UK&E?8Gk69W>xi(AZglm|yQ zvnELU#{usi!2`=xPko=Ox&O^k+3Bj4Zw=a%<~iy4JCRITD)C!fTopKEy%=#iXPdNt zj|X$T9+JKt9_-cjUyHfYK3;O_6;Ko;mfXXV=I3}c{$1r^I@J4C!krpYQZ5@xu%_p1 zkL_u*A#i`I?9o*v!>|fG0Lj`e>AA-p@o`YsgU`?l4+j4Mx}p*PUO)1Hbt<%7r{mRyoOz0DIgQM}da5Q}-+1=v~48RF|rK6wk~#OoqSGdu<{Vd-7ov*5@K#2gZ> z8+->GTagKYxTZJpRf(-lfhERj(}G53dw+&w7wcc4Eg?6gs9Sj&?Fo5}q!YPJb!X*x zY*s09kPOzjTXw3ZC@CTyAgO-?I0k*=RO_WQJ-1~YN6W#LHAj-79!#-_d1}i!Vnd3z z%mR>*9V~fQ(3~>Vi}~#sFWnYL)4;?He2N>m%6b?Xomdq#(;YOL7}1m(Q&z%M z+%VR_r=kW*GJ!&=Y??hwj&xvGq){|K8@v*i@bIL62*^gvYyO;me7t!^z;nc5c1H3Rm(s_^s+ejdtB^ZhFTQ8_=!+UlqNcC?xgd3_ehtzxE+Qt z*?bR*k@D#jm0M9+Ss5W7%D%AaPf=8>ZC6j{@ysV7 zR6^i7a>ZlA9M4~6X}*pkdgXBnx--1*9uh1p%4T%-empQ$8s_o8hT4ijf_Z;ty?L_v zV(u&Cy@fA1cn@zlkayFgg>smJ~RDH)tRG?Xn%y52@QM zm?K9^_i3H_RfS^@O=9#A3mTb@pIq``50rYJ5)Y>Pe0vgy(hRwxXV1`x zC69|*syx@54xB;R=-DdX_$>ipO_+6`LBcdEf*{6Ps}xC?S>?X@h~Ac6b+3R{F%PIF z^~XT_&;3XM@x9b?2+T26o(*uR??MQ`jKz+k#*mN}`EhCuv@j~c{cUQFm@2+(FM24V z99Hka68`Z(hq^2PO!9VVL!)(2mzFMvn6hf#-C}tVbnkHXx_)VH&O9+E3~oQEPSkM% zEy?8xYLuPH9FlKTIeDG?bDinT$(1lFrt)RGECOR?fN)ksWss z^3hx!8I8i0!~sFpH*ZD!O8ow&vMmFH5Rf|}>T zy~<`aSl|^hNr`_Hu2$$Pt4gX=)~rD9BWi5xJ<0Au=7zIfA(j$AVB3}yiHUHXS8^Yi zKXEmYk({V@9wfF zBr8Ra;yz3igrc;}LgbUpl;0dG$^_FVIkf7<)SX${@i^G$j|@8!+u434ToC}yMV|GP zZ>xuv^37F5M#O5AK;ef%ovd8lu%Rz$qjqRCp;bI+1Z{p=r{s14#A*gA(np6u`Nk@y zW~i<5V)%>oP=8aX4ZuHC>% zUVsOyIg(xyR!;0Ww$(W!T3?0>>lp+rBc-vQ8__#}TZQn^=HMs*@Mqu0%$< zKvly+VWtu%mC!#RU+z~gB;IWD`EFlc+|_(hzdY)^Gybl^w+ajhOa<)QE{v{pDmp7fA@c@=-{lBg~( zWg1es%$SdiZWb$Y>m4PyHP2E)8v;#A|nLlQg#X__KMCErkD*0tPnr8w8M9&Ab%-l8ZB&7969FKn!UHT zj#4(|bA){u!Ma7tuy2Lu`x}yyG>x0=)~NYPpzc{mf)2XZsQC@1taxn;{Wq4D>oX>m z3{4zG>}b~?DpF8MDx}>&7CZ_WF#>T#A~Kvgq8B?zf+>mzR(2*vyDH zzEv$ZFJ8}**PJx)M#TEsmT7_S5!%RnFiGM^J$@Nby6LRW9;!0a3~zcXGnR-H7w9J6 zF$Y0-hqU{QTO#P0XaFSegy=4luCI^6_Sz*Fc{9EwE414(zk^2(o+1BYg!!8rK+X7S zlIA02THmCOSVq_^T0GXsoo7YB@WF;&MAhIu?6OhS&F(-}bxh@wYz9GCDEC&aSu0YzBWi zZ7JTq{aKM^IYKo34AbhdS~INO%cr*inRgzpwOw{7Wwwh2~9PA+^jsuiIIN0FBXM5LG-o-nZB+#3D z=uE0;CT4qB8RhNSR3{-&l-gA7K1XpgN30lCUmKS<5)0 zpHP~Rro-qkDMZRAkS$54O`1W53`%)-jtCke1n-?^lIR8LQJ9Xfu6fVgF`8>9|4$gD z?|jrEi(J`}hm#sfBanpKi)FM%{U;YXakUC99vf*HdbtCuRG7iXpv;rNAS#_s9f{FV z9MXJ}`zY%HR`IioHoGjz+l>%?x%#Zmo}=8a z8%-eWwcJ6;z4D>)<9R^uOv2#R-X)3*L8~mUHqH&A?$!Qy2p*5$Vs+ssgU$Uo&*l+a z8&Cbtb0HQ)+TAS_9BN9 z#w{sf6>jZRm((}*FiCEIZdy=F@16@KVz;+S z)2aMR>`*7tuKeDUWw?9?Znj`> z+z@u7ROVr(|3ToTzf+F%-me*jcUbyyQ%;xsJAM7Y_v!h%9$1qttWaya6r8OrsjHC z$?Z$gGXcNt@tg6w-CCc9YSc2dAWn!87Y+xRl4JgCNcc_IhJM9sQLm(f|82= zi_xcgMJp5Z!p9kdwT@af*mzmT4kv)k_=l^C0L8B9u%K&S9Y+JfkaUdz0kkQjLZD3r zg&aQkil-K*SaQ73tO9idVshMoz6*n9!qrL#e6@DEse_e+3z8tlzr?3lJ!CZ?av0OL zN0EXejQ8RZM-PU1 zNdFsfah9%=a}F3RNY$>~N;K!Z$JK3V-mA<7j9I;oPnb~WTN@APs9uk3B`dw!Z*ZnX z;1kBTb>~9I=Pf`z_iPBak?S1Ze7P}dMZdgFYb8|T!$6|9HT+=JuvZk+PAROPgEjBnB zx~68xtv(7&vIPw9fSlJ)t0hK4+y~-wThv#y4K9}iOJB2*Rl^c)Q6Y#5smXf0OU%^7oC^oCO+)G4QX@(Fq6qI*%{2LbnB zU~cG+;IU;^@KS8DuqDvt=v~H@i>>UjZ+9-xR{bVWWp@DLvXi&3MnIG;XZUC=;ao(H1Nj`g_^&+!HW}nkj5}^m}w> z26?mLglc>(1&jEOaL`A9FOSIENHt+k_j6#$6|2bzJII>(pBlB;PI^Y`I){up6bhe( za1L3V4Pcai38Y*Y;qkiRNh|!-URsC zKZt<`!3)7XUI-IR%${^jv^13NaTKOJGA9@$dnH5sI@xtU8vt@k7*oo=WvufQR12@17c+~E9PYY%=e{+>tjc&eFt&5^(S{fodjAS+!H4!i>2U~6KaSIO6gi`yBm zR7Mh(Mtca^m3)2awHlo=QU5gsSb2%q)m4=jD(UGmftwP{)jUlrGd0{L`P6dZF`lB5 zw-&_8FvIt5BQs;i;d$@J4!nh1fK{O%=L#zdahXsQw>Max?!k_2cVOEa-tAFmYx>oJ zZFgdukj@QyYm4U2$qh@ah@s8p5r@+T$@=^pqblp+%s`xz&4I zt}STnp&c_2I1!}0&N5v0$R=@&)<84vOLM#swbNT+ISJal@y=lJQnf!%tq|&QORI_J zSM$M8HxJN20pqe{_R+b5g9Eg-P)Pw1xX>yD2Y_i*`$2BKA>hPKmFS9m-AA!I6`IW| zV@U*gXb|9TQkJBuX`MHBNOPXE1GiKoxkaH5e$;iAY7_B=59BXH+~W7Ajoz>$qM+#m z4c`LP_*!SDv=(!fa0kcK(X7=LCm0b((#GlvWb?(~Xc09sb366`c|QT5U=4VK;%R~8 z)N6&%m3m3v3Jg08>U7Jj1P0^N{ug8C)FTS8X3@6oK5g5!ZQHhO+vaK8wr$(C%{lXS zCz)jCp^~aU@TF3<_gcegDOYYE%TFOT)#FcR197x|;P|9G&{~yZuMIKu0J8ULUL)1B zBfwSbUio8Myi9iOyUl!$seljD92NY^A)+rVl`SW3CqZSC`PTS`08+5fTHy0i{nu0PM%)nQq*A`1}qaz&) zZ1-i|)?i?rjvXEw+5ZS!WmbA1OqaO~9VQAp$=u^(#TAiR7zov$)-~=`ATT4eB7yF3 zk>l6e+yucBNZtit4WS=JgC z7`cSN6X#@cR%&EE{v0EEBGG#P1<#TJ(yzTB2#(zwfVH+5drym8@xKW^{*UG3t5m z--N?6J)23UYY&PeV?1mJXweRrDoOfM=I_9#Y(=y#id}@=EU=G&(UG0I!r0Uu%dYbZ z-d7HIV;@Y3m|g%XDPZ|#PV)I|`T5qm{f=daf;Fq-kd{-8RY3(-VxkY>pY%T=gxpGSBqat0Cz2vMuP$;_)I|fW(yQ#x5{`uW<>0v)t zDwW&B0Wao~DqOvS!U!VB=P>{S1V4iLLl*~bKpe(*O@Tw?D64W{m^B4n%9vI?Yjw)( z9;5bL*{73d09sCg?t*n3+f>zJ1iozc)GaGI!T8qLETI*e4?Bhr{#+Cq_{X;zmH&e~ z{h_bP6az?mZ)tt_&N{D|Gl|t3u6g|T=^3}hd+DG6FILO_I98kr4~V?LqMs}=;u<2P z6aU>BC+)5<8p)XMl|Tu;pW7wtyFuGLcpq~z0G@fsP=f{yKEv@FSTe7o3M(K}VB1IS|Hi1bo62 zLLrPP-5tP(hmFrDWj~6P&U-3$8btjI8TDd@(ZE1NF5o85v*1meras-`Yrs3U+L#x% zfF7S-T6{snkb$wHhTa`95w~M)?<9IQ;r{b3Oo7Zq`C<&nRi|OpuX#aDmQf z=KWzSS7@0=xcp$T{_f}_*reHpjGxb(uniMO8TZhhBUuAkA6A6w=ohJCbz85OBGVeCK57Fm1Btwx}z5|_+OxacD zK(~K-lf7!9f1T~?=ap}*wryIUhns_KBgt)p-@UTorX^#q^&Z1@=Dv*+SMGK6P_r>m zH5$EdY%Y3SWOr^?4onj+;N%v3R`0uB4^k%{INY1E;a%~7<%Jc+=E~wVz-*wR%@xZF z_1(h-ykTQqm@fmPFe>Utzs#i~;=?Q`l{|FiN%8dW6Cb0g=OV8aCV|mc#_2%~Y;y}B z86a6Y-xH3JiAG&llE8~Do@(#Os%>JM*t`&cL%WH?EKz*?(^>?<8_yMX-P78uXEd^& zq&fX~Dd9&6cHad!2 z9>zmi$_gpKMA1o%nA-J^yJ3nkecSv472oLgpp~((&T#jpB58U>_i)10_JL!*F!OOp&q4CsaH1p$kb2gMi`v8XA`19vCM&*CAz25@UJQRc>9cCMzF~dxmG=cR6fItt7`l zv>)TS$JgVqPpm14R48nE@SFG0&Y546J~-6wa#_FV^nR63lN{@+NCJ3O7F36Ku7A7uuq;<6 z^!-aIpG8avGa{=uQ0o%TO^xiN*#tB6;v29d*aSHK<_S{D?@5lAi(k^gTNO2d1CrKF zYm-gj^Bs(WR0^u06?}u3nI_IWn~|n&6o^~iurF`l_tO#=M+!!vjJ@T7#A`^fI!Oi& z^UT&DPn?~On-I3IsL;w$q46BIVH`ja)Krxd0wvL(s7N|mGNx!MJiBz(U(U5Dez5$> z5T@vc8H^Noi4tJ+v^R+tWe9KAG6wP<07F|-=4&dGmf2pU7Q#jo{8EItmn3-{o~T#0 zExfmGop%n{6inxC6*0~>#Aw7g%9HP(qeyggHX_1NUQhg1_ACAmHez(W`2p&aALfV(-^k9^k7`(k*5VLq2(L80e&61w{ zwC-r0EV1&k4VYUS01VFCD0@JN6d@S*D5>yXMLEc?s{bxr9URrZ} z-$`Mc-ChCoEES&~hS+r9la2iA2-dO5h=nO4$;A=#3^^1_boVM7PG~c<5RyAK1;O7O3~~0xhyeMm`r^ZBUu3N0 zY+ig=j8Rmt2J<+ceU%pQCobszZ9U6yE=bKwP72{m9S9=b_k$gmuejC5^6>uO?V{6Z zRA^kv1UIeTj_Wxmv>hA?K6%_dA1a(N1_kW$7kJN+6Rzt z8~13Kt1Z1fyd+pt6fU%G_7|b-n)oElmg8a%AHWoh9~Hgc1je(%wK7!6@_YA(laztS zV{)Dd3YPeVBr-lUkkgXKyEAh0a4sC>v+}x)zehPNMEf|Gr=DaYJIaf*FsDe_M^Yk& zPuldE*u1?SRlo+{KQO=7R9|W%K5<6GpSNUO_P@KwecwoM((lymzZPr6z3+b_s7ErH zdeRFwpqKs2YfPG%58`H~+7<(@vAG6a%#<>N{YYq0C`u|uIe}Y}uS$elitd<=KuXZ( zsOk@J*C3!f6f2@f-Nsm&-_3;dB++P>AfOaCutsHdTjcPi5r6K)qU&OJ6~WA}e*L^w zSGQPghjzay)a7JTT##m8bWS;wHZWJan{Jq2WR6(j&BTNxH+hZ~2?)p!G~@5Xp)8=0Ro(tO@7spaw7)8ce4cekzvz6*Y)WzZXm!Xl=$x}cDo zt#w`~<-Yr)-?*S7tBsG7m& zZ>uFxW`mYI_(qqvM;l(dyM4(HbqIQ8HX<)NmuXqUbkI*2^D}WWh)K?%Q#d@B??rcRIUg4Bvh^#IVweAY1Iq2b z1yNXx8zGo4d!E3YA8u}wy%sy=H7mic`j|+D)4>&IKNxFk4i0mR5<4d zPRg98bS;a03@U3ztHZuqU=I^JjAp15|f%d9$dE)QX~ z<=m?Lvz2uiYuj zK%)GN9Ep9{JG_@x!XJ)zR`Th7$>_Q9!mHMiQRL8N`5GU3=T;3TB$|DNC&AIFx~mw^ z&;EPV*tvY+$b)^Dr9}VZBs26^rVq|4=wH*XUGpE2(HZ?_G7MPNY)*+nMFNj%7u)gp z7C!V?nAB&~{;Ik&0*_Lr^2=YWd47NijWV*`(>6i7C+ZUQdKQR}|)D0&7+zz#=(f#J6E+Nb{wJyBM zSN3&#YT}{UileTd^}eb_Kyfp*j!;t7x5CNOX>iBZe2se>o`)C*>Y^M3lo`hy7F0m8KDn~2G31j=R|85*P{+|99= z2`VS{0MT#KM6>D!4f!EK6Gu(v>sNY~eh=^cV{Rtc^m!jeg2-ZDd7VR_55zzJ5WHN) zA+00qO8Ff2M|YiIF*;C!k%zr~ zrou_=>&2-HgkiMWZhYadyoG7(yLBpi2hVefz=m54O5>j78mP(_D%tyOD-A~(${J?? z@SeUsHg!r$#WHS;NwJ)Z?=plG>DS!Ly1_CwOQZm7cWxt4ZM}ju)nGQIe4;=<1J#xe z(lnE7HDm{_2*^R1qc`RrA*{$i;VK>bjOY%OT0n1+4Be>lwH9mh1>{>~VZ9rn=sTZo z_XusIWLG#{8`Aa%b@5;)h8#QK&D#}?x>Zxs&bqhncf9`0BA^i;LK*b;?B~Z27pXIs z)xX!6xFzM6sR7oXK6Y@81OzpDl%XLXvvqbMkxdTIjoW*3{wYB!Eqe41zhUBLRNQ}j zlqbPYbMVH$np&Jj3SS~3j|};YcXqrMlLG2DDc;oqXMH8KR|pU{SfH<1vFV_u*hD@b zCA1puQuJ&DxFC0DjKQA6PX2F6>%2Tu!Wx@Z4aY`tB$bTjTHcBtmS$MtewPlaS(2*YaN3 z2$%|6N(1lo@p>s4^clb>?2+`Q;UE+zxyA%TzWA|hP5ay<+1y^uV6B$j1G|II!M?I0iW=CwGtzmj;+!#!%7A^7nxbbcAPEjG)~E=)$|P z?J)v^3|fJlvh+%{?Rn&R29$-gXJdEWpgRU?QTf}o%z2wL| z;@h1f6uI^<9}P=Uq;gp%o@0m%%)|)|DJTv|5C+^Le6p z@U6n7u6gltxwqvLDEr3bfAy{9&c``N@k4`e!8Tj^@oWY)oP0Fy;;I}gE#4mEP$YX{ zWpHz@$Pv{Yi3%ysEy{RFQxX+}d=-oW9@T_l_n)A;!GY+HhnKPcj2iIT;>{;`3+rH1 zug<_J%|{{JNlzn@>ZhjP_K$h1Qr!A>LOsNE4E-UoE<5lP9S0`6968KGO_yYOl|-_{ zhk-g>L)#FDuo{ev|1{iQ&*gA5(EHo4?9&^&8@StF1Q9lTStw;c*9)e1xzSRqGU#a2 z^#y8RBEM`(_bYM59UNY#=cB6XEmlwH1!7#CDcB2WXp#OmDDfF@tpqoK*BV%E-&%{l zlHp;1&eB$d-zc7(BVvswxq8&!u`D7kY-TKBM1wcS7KYwn*9~!WG$%q!2?0_h&?MuG zg+_hbSeHMt5m=yF(&iV3x@D$9U})M@7yOdT@!80tNB*ZT8WCZ%(H`!t-tX{>e;_c zB90!5H@K2*@4M-x>(qzoZlQAu%Ug+`s1@NS&5FEB%3-bVEd+acLrElvRjZUDpWQ!{ z1Ow64YOS2;Q^X+#pQMmR+o*&YhsL2w|M?yn{%ZwDs)jwifxL|=*_2GF z&iuX7#TDF^?rkZ0X^gh^t=4rIW(ku#8fvulPRE`9$}d%iY~|(;n;eTFDkk({Gnj%g zOHlr6wWbMAQ_}xF!av6)s=?(&tQ)ZFa2_s(LpL8uN>}_0g6Vj&nqkazZ zx?#-7%Plcy?U*u2u#P&RMUY*+?Fcdz-IFW0E=Enwp-p-*=lg7oK^JS*b0)6oO4;l> zJsOU+Ic0BRs>`JSQWvyY6a#BU4&frQ!m$S`}jM)xW6YZET>+l)PK0`{5TlovHM z&HG0NxF(gB6A|>zko(dYqvc9FI-%Or%$j4jRc<>TUsYKz@uJkozC`ahRjilCgk%ju z@!I%^(CYRx&Gm*iD|PJFhHF#%8mkAjoRAjw4Wwg@`naZgw&IBU*0MkFIIzCw}-_c`otvL3}AzA1##+w$~An^sY1XPD%aqRj6bA z?>oK!fn9g=B^CPD>=*U=hg~m10svt8KY}fK_Kqet2F@;yCVD3B7XLHi5~xk7_}|OL z9U1@-q?G~y03;k40HFFGeSLEb@n=SlVL6|{gx+z%Hz}#5si^@yUIIsA{j|WJKZ(jp zArhr68Her8?{+FanVjnZv~3bRY$GQp%`7(~hleXjq27b`d9Wg|*XQHQ4V*UWfItcV z(O5qxCm_qDZxc+?MESi;QM0s~zj#BrxMx?jV~k)%Y3&avC%BUJr11Sj_srn~W~xX# zD;)Q^C8)^wFZyO=Q&p5QoeNSD9Qc7^zi5h;jtXUnj0X1w3SWha_C8*SXG4|y!aFdr zNUkNP@fm`Z;&Pos%uh~_;~amzHIG!Ja=)h)B}_%+ywL*AE_z(k@}7X_L%~9XqrDc^ zm^|kBndZ!*nr;3tan!SBO3-0!UOyadH4%nQhsddGPkdkwm){ur!jfrYIHx=+C9%}ii^zq% zbF((JAv~A@fRxgr0tFXfUjK8pBU^Y2JNl=68{9gPUxd!6%e%_XX-!%ZM*17 zU6ipXk0Wq`^7D2A+?)H{%k6+S8?$QBpF-hq0|8_%=&}6R-sA=~7#OMi2T& zAwZN}_0Ym{s@h?9f8IPD1h#=F{4U4R#X);V2trN{7jXr4xjJRIo-bKG#N2OI*( zQ*;C3LJ#7-h1XFSFY-Vv4y1h!>F@Y}tgyfw%+7?#69s6XFr+sh26z%7@@{dEes461 z-AkhxJ4Aw~rQt=Rk&RdKlT5Aw_#1JS{r+|Py%)(nPyRbbOWJ-!LeIi7s!&oyX*g>< zI`;!dO1cm>B;viK0xir)UQ)SGV*GV>Mxf~#j(`01kCF3C?1lCHtH5l3uAYP=KcYBq z2ooW^P&?=4yxr80&S6fDT;kzBSAMkzU+sPW@aw1FEy2nC9Y?s^D7@AB zGN+!6({<$-wg6d*24U-|7lj`sm2u@2%7Wd&%G55R)&Hso6kfRMD|DQ44K%LjbS085 z4C>#7jVoUKIm?*8I8~mh%`8i|p+cH{wfpiX9Th$VYBRe~*cvMwEq&ZGp@T98Pp$iR ziYkPDUzph_d= zOC||Qz+s~pSl@~J}v(+1A7k|-M)u#r06pZimpxjmt=n#G&fno!;hfKo1 z8Kvly%P8NVq^y6Ta0a6WG1%D$Bya{yz5W{L9qa8@pPbGC?Ii$&+|v%+*(0daa-@S> zuX^MdNOLQi_z#5A<`uof`t0L$zHn#^A5;8Y8(OZAGrilzW#{#oi6YTD;_2ks>jOtR z8(giuix|?Sj=G79R3KE_9WrSZbtTq8kU%eT6yQV;mzf6bbff0#<^>XyM1X{+dtqPv zRoa`bZ!|RZ4VD?+)_sxnkH~pk=?{o=DP@T(0sWORIcdu_!btkT~8m42R4o`*W zM`kJBN`gu!QEo?BKB_6LvIWOQp#eZ?Q+FRY8fD9XX6x*-;bxv_IX$G)-y~1USMLAW zr^rGCg{AR$Bq1n1BaG=}s3}FmHTH#A`_FJ@Z}^J=QkoRU08YA*{&g%{QG!x``=s-c z^L4r?@r%FRDeM}cJ~mF6J!|S-A5p*Ekn*94BMNQ4l4C15^wVBQqKbZ2*wnTbuo0E=J%uXdWUb_Tp|MNARe8j zK%dri8t-R;c@h{=Zmsn5GGJXB`3*MHuSo1@>#2Vx>Whdee^%KYevinzj4D;wRps+E zh4RoPI~?}1$2&k5$1|=cZsRHvOV&AN8IF=N6h{?qFRIoiG6;9^J*3it4N3#;MY4t~ zi}&Dxz#*N&-ZPR9_i_)8jQdlFX5{m<^D<PbNwJBEd!W&}5nWc{BA*e#8ofdeyXeinlW;cX7UzX``hHtar9nG{w{- zjj>?#KlLijKEZ<5UPmoFlmGxt{r?YFpS`1nt+R>a|3tc{0_py5{JbS@E4KsohTTUh z<|z)GF@!&3jS_AjNC(8(hx};S#viOs>|oLdTqCA7Nx>P{pPP0n5-ldw$P3+Eoo=`w zQpKK?MU>drfK|4!}3`SoOZn$$nHqInT6i!0OAhaXInn|Msm689pwFkAd*m7L9E_p zXP|-rA4?F;jO+~S*9jp6%H}{~5nDMPa_gkdfbZLM&Eu*Woi_oU05A{YDLRu-Tl^2! z7_Kvp)gCYb0~2)#Mfo?zfzqQq1V9+L_=cbjkrZXS?hQ@b^D!~6dDaGlaQ{wE%{|yG zLLv<(_2(fYjxmZ4xKL+_5;?ABYmB8H3HP}zh_TiU`b(WSI_Kg6oReT_obllYk5an< zRikx4)iFK_Mmb#jW6Ao9-3hXWzzqp~Ky+EWMb8j%M}az7Mf-vtT067j@k|`|pn?bj zVcTjqe^%{w)8AzA#Pq*r>Au$Z)Z{ca(#@6EtG=E{)%tjt)K%8_);xbyt#gE}#Lip& z-Tt$!i#Lyw>4Tx2*JFALz3AJ0Zt=}~`J(>$A!-NAi$7=O8r`x&*XUYWyqnv~m1Y9Q zu3l<|kJRn6%?hw^mKx#gN_?qXXH~u05jc9Hp=F1q7k(tNd4}H}WgBNo(`oS%?Ru$d z_p&x^b6c{xiZS7~bJO}{`l-oP3joibX5pqes{dVCQd2rC!oB?&yCQ^X?695zmd!fr z3_G6Cbb6h;!YE&Lqy?P}A$?&Yywp3dY4Zl>InQ5adMl-ocWvd@3CezEd^&x3LNccA zt;3~Xv|=OIDY_#-^1a#EGtX)Z8>8ded0O<+v(jnR+Vlgt*y*FVWur%AI+G=uGzN?& zkImor+PLYPrMz~7p;_zZWz^J}gS7UA%M9|bgZRX8-mxi=(SB>by@M_s^MP-by#Ad6 zcFm(qXHgw-aTfHV843eNERaF0PDpoYp628eK+aKGULVvg)^2Z8S; z)lfL^&&IeavXL`sth3UiKmFLr`arkWkN07-&s)^9k}~JZOp^(nF6JA=lXc_AGiO~B z-Hl20^ICXL%L76CTB5ZfD9*473r)B{%cNftmAXRLD^C?#0F7t*+j#nmJI$Hhq=c0x z=np~8PpzH4>Z{LrHSUbYerUrs#NP4TcpaMq8hkYX9N1ert22+Mj0=r4HtNupt(7vW zR6bsBAON{wgE6@k=A3oT?T$c>Yvy=3^zk_$X#_3!?qNEk6ke@t2M|o{luAO=$}jD5 zWlzDzqhS;tUCJ?#G?h0h#^jC-1dQ^14mh?*X~~<>Vib$LL=`OUY6cE1PlHhm^si6u zzHJBF-`MIK8qV3QL!XuZ9J8;|8276OTbsouq-q3H)?|mGhIJ_t-oVwr1gGR|5=?`Z5}FgT{}Yr)OJu$ll`1DQFE;%??Q4pk$ttC$~JhiuPgeBS}kuDT9RpoPh3 zY9f)-)9Xb8NC2}(Jk9)p-LH)S^9Z!!j1dh4NCrN_SN+AL@agp(=ozL>aBU2ciWPMT zZbZ`TW0RgT=X>9CCrXEH>deYAx_iMmAoAq1fs9ci7_$kSMbNi-O)M0TXaqwbHoseE z3$FF^-5eOd0|8*4LEs_d0CE6Mq=M0Hit3lD=5;S$@bmf%N;|>yqfB0R zfYi}_$+|B*XTtb=errYkr@w(VevQ-Sn?Sa3`#J-SHy2L~SRruOuLl*|S}(y4L%EI! zg9ZoxBCum02cxo;0L^r}DxO+snH8=HG{hFBx$-O*MhO8DS46Z_ngH0ND7K`>4&tY5Pty5}oqod^$A;A9^n(~dv`1cDNvh~koHH6UzVU}o}b4Ru<4VY+@?P7Oic z;FyjNtzxEF`~b3} zK815R(2^~IpkdZc-TPWqeynxl-i-rfU@p0oguQ;Y96E9xQ&YT6`eZfWAarypJWs{x zj?^^_O^+s5;pVR1R2Cg8VhZLf)NHbaZUjpPA>(HFkpu<8CiK^fH)X`byLe(6M#zE@ zCxF^t_w`m26aQe`6Egp|VOcTXg1L+vaM~JT$@V}KBqYQu{i-itS+9@Men^#s#skE; zzC=vv!;CN2PQ&mFBNO?)E0~EL(XB@uYwE!D%y>PtklRnm1 zXFgB?Cl(GIVYu+P4=91sqiTjc{GJfoAQ9GI4#Bu?Ltr_v>sX0?XtY?3r$<|uAVH2m zlSUmOdoGnP4e*bi>1|-1b0Rf|fIJ|`xU0U1fYZ}6Bo+;zuC$1Tg-OZ^LX4z?ho;aQ z_3o{&vXhxqI4)mRkDJ}nUxDX!cPl&x9JN;eXPRhv5y8k1WNZc5v`+S{jaoL%NeoLr zDl}KC_z(xyIw(~_xS}6rx9*{Kg@kd{o`$9iNJi>#lkn?OKvn{D^g6~R_h!j~)mfxy zOF=|s|5As;eT>xYI83P0_#NB6gf*hFw|>QB>b!d&Y)B|1K8;uUUFEGr5X*gf+JAhX zlmscpOh?SzjYv)RR1qfI*AQ+4$`7|N|ivMu`rg<1gXJEp_k z@FuE0<#;K?XEvzLfB~$I>fd-0weYMBI?ftgL*X8<=CS`FG`sI@yI2L@(!i9Sp|SVWCp}1x?gez zqDoEAWi$-=Aq4CX3!XGH_Q9wr_L?-1SD1y2>!9Sl zcn4He6BXydB6Kl{UTPHhcaY$rlfIZ>FFys9)WM{9?)ujNH!9ABP3>URyztmpR2z}f z!J&W74ll11D#=A=dd|)(uPpetV|~8Hrs~1Q#z_R@6K_dA?MkPw6B3-Wh9DrWF8p@t zbCj=Kv8pW)*h^{ahaWO%bZW!fUW8hkdStkN@qPrPi_fUBd^o7O-t?==Xzx&f0qDy@ z8@^M_hyYV*hwu$ zWzg~57N~>JL)+f(@81!KK5p?_W(r-M)-wUK45Q*0)BQfXW5*o~YvMwRSw{SPhX2Ha zi@U%hmb{h?ozwMUSuelFyT-f4xglFJSC3q~DBHOI`PrZU@qISGE&JHt%X3xda(4+) zS{@rCkq@A7!oI1cf6v$5-Q=oJdxeXGe~F8Qi>(~p6_S;&OsR|ZZX<()KxtPh=(XF6 zfGANPPVADH0)m1Kl;$^jv&sC+m?r7oJ>g2WIIxPAGJT;|ZFB^Ys4(dH??1)4JUFTg{cTauL>u73-1?R)HJ%-T z(fMOU!#qPg^m~fdrHO5fkQHHm)&_5UEFJ6MDnN%fpydNJ>6W&1%~H72Fv{iQm!gcY z$C+0D0AyI#d+^^M3eUOCQ^j%7e_`3^MWip$3_6C$emk<}`V$tL8(f;;eL?X(>lxnq zf$meTBtg$+DGQ*Y-w@Qc;j*5SdO$oD@}XEpxs}A{DrauB6R^d%_to9_1gg(zZSD23 z0jmD6Hx3avBQ&no4%|zIzsn(qWhtSDYA@hxMGK(>H{t^wcv!I$&#LsSr(Xr!(imnL zlcA#i{W1QN3`Ewoe1o)W*wJ8%0bFzdlo}qt1C_I%wcAmqg=(f%=#T!AF$_=k0mqk(Ap>{hS9;; zXGxvT&*Z8MnqDFbUksfI6%Bviz@xC1QKS^NB9yrHn}nub0>8#N3@TyqJ@)w`}HW7b9;0P zuWwj*)@kN3PwAPJu|bHIOh}dv6s9VGzN?9Mm~!^0Qps8nFNC(1SV{*i9VnqsY;s%W z{8Akc3tI?{+yW#s$p$S&E3NAFyCqRBDc6>IClA+0vveQP7@eZxY4L$Njt zm`yq=fkht`qK9&XE^+dG;O!BSD|sUBNhvt&2cuR`2!Ybs)^|_%MrhC)iyHNZxKnwU zikTp;BWNwKk~Hjl;-$5)QL>G9Ejwx^D}#CEG)OaUyA9;Tqq?0GAA|$t+2g+Z)W>{5 z?7jvPPoDk&Nsx6nfR)RI*J3IWLSmv`XG@>7D>RJW!UBFR`SxESDP+)I*CoID4}oFg z%C%-9cH=I4uBAX`nFuRK-=09Nxrxho&Jx+41P@zhGp@0HppGq*itGS>ZX@+fE7RCG zJS)Xelx*tBn?|G*vpeRa36v4s{DcXP$j$cyj(#+MXhq~SY>Z283*gt`TB}84|M&#l zdiiOJyk(&3!^_+%_v4j+p*E7*cA!ZOyy5XxAQ;_8SK`)^!b6$EW(U-ZbWu=sg#zXd z-vwxmK~ixS%-JA!_~L@2jAX*?sC(wAU8W_I#28#WY#4#4p5!3UZo_#H^_HEue~ls) zsHIJf4}k~KI|StHUN`xC2mn%r?>?m|d00l+jxCzBi1d~7Y{mi;BI8Nq53v1RH^*E6 z6Y82tXn)1+g&d~q9JQ;~rmHmob5A1l8!SoIw(|?XE*EXxJnRQ?>h3y>Gt}qY5G^Et zAFS8Pa5Hi=WPl%X8^9>%r#9`F?_7=?_eRPGzAa~a)9L)edMIZC@_;xF=q%$x)X)%! z?dHA7AlSGClF2u5wzx`t$a?ZnDZ5>}cYcf7s3R-0HM-X_FzFi3zRo zQnM2oE4^=*EWn&Rob*SEy!{pE$^%Fe=l&-%Ll3@8bi~p`m9DXyag(kQxJ9v^()gp0 zCw8iXB4dAjGo8<0XH@T-t)K8+EHt6PoR_+>kdcd)sS972N<=mEYVWE~O{CEDK8Ujp zuikwQ(lNO3NhVQZ12jX=O8eP)Q?kFt+%E)A6GEc*9v{7)nZbIG=a|dWI;Lg4VCCy<66nq85>e3bk-1g8!((+J^fg{Q zLsB4#j|@5%Ci~^}GzgY+CekA9zDFikS$d>$%)x<&<`m<`5co!`6W@+m#*JODm{;-P zkG=6I8}K!yAeU=Pcr4AjEYFwy)ws;`?sj2b-8Q{G=Bw@=wKw4zF=%UW=jgt2)z_=c zPEVB1ctbTz65OUctdxMy?DJrdnRu}J&}9)sv;`L21+S-p4kj`$j=& z0jX!~I{I+?6=RQ+u5RQ7memmPtshny2@JX)XMlnqeI*gLP4SHm2;~n|Ri}oarp8tp z%P*XPnvdxR*AG**K#A3edFLQ*9W9Oi1N)ExxRxfQFADI3AQkn_9ak*-%6Ss8$?}&Z zm?SqCxGRICim8_z_6ZsH>E{f@9>=sY~qQc zI~NOf=Jjoy6~{Wt=~~&?eI^3xR*jb2>_*)ViIj2lrd{ls56z4S^jFVq^)47eU`ccvP@Wk*08N$^oJR z0+*!u{mM-$0$#ivhiSaM+iiMP_d(y+<=N$IymY+WKYm*QfG5hE8NxTNqYKJK#CI@; z3qwx`I4q4xVXK17&d|J_26&L*zh@cDlHIUAgal2vA(R z$C=i4olMu?w24Cp;Qqd-!<#jaDnyW>nBPz7=$Du32l^ zxkRP0>hffZJh;z`(qkE#*X*)>z%<$k2~X9C{<9qsdSeKNn2bU}`x1^2o`1o)=S-X$ zN)aZpI`oy{KG;}On|f(CYp@{sK&1XB*MlRH(cP@}XPjgVL(ePOhOIx;$&bpsp1L4& zEcA%(``E>UgF|YTZ?uSSHw##+v5nayPgTN5W728@>9Qm9cU8ny`m(IIl87gdBf5I`ocP4G?;yilhef%N4N&>}%(D{2{qF?&dD&@9c_JjOyI zX=?`x-DJ&ejF~L`S|pO9r1LC}uWH-0^iRq)1yYsfY6Xa2-?z}k3gu$^!J!bQz#k;4 zfpH~V`CjB7O%|+247|MDRkufpmKD#osTCkmUPC^k*@54JP0qA4@@+dKX}c$zU<}F@ z2G((?XCiA%?-8dKxdNdCu6W-eF-P=(7U=Yiu%)1(UHSV;o6Zo061T0 zA)#`-^^hZt36qXc=0OM>vyH_c#_9XPs(F=EK|+#hrby7uSL^hg<6sN2Ki!h6r)gga z=7a|_+>z-jv}=bfFoMUpv!zo72Y(oqCF{$@iukKa$;d|S>cwnaKOd{w)b&QDMm!wu z0YsMdN!t&LN%wH3qKtAU;+g8fEn?PdD@px{V zyP`R9Z{@$mIKgJ%h-vI6-(olx83VVFw^@(iXn6}eecP%ECM8)1!-s{Y#Dj6s;L|7P zKhdGJb`#EJ>oKr=7TXnM6^>+-Met#DR>|4quE%8*Qoj3rP(>bdu((Ib7l4w4^gs-=F?BjX47@VqEC~C zvGeRQ{-hYkSB-^a6u)OTTrkx02wH;?5}urShw46153L_`#7l%BC1}YyYe2TOMT+PB z>gzpnN|pLhr3<>08xtu038W1ubTX{1I2bG$o{wDHyR158Tui!6?}wy4iW`KLo{fYl zGAa%C4+NCN+*>!Nf4eN6BJV@qgr`564SUxbH*H`x(w~KbPZ%LoZqp{#o!3m@jB!-`^~7b!WcXQfu*Au{1@Q68*zwQEg@$LWbdxhd_8x;H88BS+b(ghi(y z;AGlR<;s~%RIEf8p;S`LyuX{p2%$1!gzmITe}X%S(Dmvps%1GY?Xny{s?*7Fryipk zP{aXTXfQ=if0K7^o}J6Gw4uoWA@Am}Xl9-Wk5>Xg^ow0El=QD7c&xDTC|{ zG>|IVdWxj>ozWpyBG@W;a-Sc;(L*8!Dq&bboBPfHZ^FL$B~k4F(!4<=z2e)JEEhHg z`+hX2KOvb+j|~ChQ-48`x>6BqpWbRddCt_ph(qY?E|*88&kz0m;f+bU7!;27(=I2A zjHC_$kFpF!8sY7MT+z!Lr5gN2y$CG-*}oQ9!Qks|?d=>+Z=+8HxVXwG*6Ihvp*9I4 z0G*z@MIwFDyeqyS6GQnpeFA*tIRGZe)3(o`ONkhsQXHzo?YU#>StTKKHS%Md^Fom- z3@yPwuq#B_D{OiLIdJ=N!Dy#gp;KO##iv$evEciYpriZDR|zez?BKvxi`E#N8==&B z;JWDyxiHZ1w%_y$(&u|Cc~tkw>Ax6zrzp|FAk8vu+qP|+H*MRtZQHhO^QLXvwr=KR zO|O~mn(kRu&*$~5Sn=#t#vhU48O*Nk8@G9-pmW;5827D&5ZpD9}$v;~*R>pmZ%!PSGy6_X-ntV-okw*w-@FS1V8h&M#JlEpu-u23 zM56_)G*o*wUCRR;4?k*mJ#Od~k&5(Ojo4DYa;JDIGkkDS0TrEoAvPbb`u2FYN4YBX z)va0x^z>C`B~EV(lWbn!-i#p}?que5$@g)!G}z=C*}Via7bGfkfR_KA_R`)3@E zIe}3+BAPEpz+vex#hQ*@0qu7kFQTpdah)WT>9HScZZ9@vUq#V5Xi`RHt@yF+#jxkb zuyh4i%O5G!l(cH!mai5qkmMHOuHJkQ zT0HU2dv4`}=c1|ypw;imC_d~m^5Aepw_cMHWKI6kH2A-Fu3HM!EY*%7{|=IajHLpR zNMVoO?K4p6SLx3^v%Fq>~`?@-@j|}D>SHoIA9+95{8%dvfE)f9yRvc9AUS!Tw zLyX7Y$O%X3c?7vil%-xx1CxTaHgk$c=mfQATtpOd{!D^?=)C47<&uj!_i{18_d-M^ zGLU+t{BbzhYl8wxO7Z`2hSwiR({yy=24oH1EcQMO;g8Rv_o538-87Yw{kD~FY_s1o z@aK?@5zX)CnZ%^x4g5Wut3J;|CAl?blZfUPcI;^=hEq-KG(b9IX-L$|2$*hKh!P1N z3%ZwUGC?<}hXUB2pS#L_YFT$2e2?d__R7qj?titNPJJ-G(G|M-ICh1|HFiW|`}Y6q;QAD(xlm#k^qh2p8%0!)gWp`TBNRj1*Wl^u}0 z=47Har;Ix-XS9oD1KrqeLeYelAVo6^=7{(<_+V(&giNs}R#_0JE{2>_g}!VUfNg8W zqvz4H6+E_h^Ngt<)5MysDN#NIBQPoN+R$1f^m;sD;_pN0FoLLojzL7DnAU)nG8g+1 zQ)X#baCBipZjW+fm)|0q0HBp{HR@IDV-qie?fcWj6!ytv3or>SxIMe*(OHOBl@c8E zNTZr3t$G1cg@rtkwAj`jnuX(>-QKJ#SB(|LpV+ni&FMa%UV#m_zc5X|^}x1|ZfhrR zzi(=6YGb>}m_aGiL(&$hJ6suZ+<&~Iswm%(2pzylBqxn+_(p0QCkOzg+9cO5Ete~z zcTWfWW`iFcZyn92)xk%^B(m-XvKqft-$!Y{RahgX&tsw3eo?3*uH#!BjKuV`p<5$U zg;%oBCLua) zjDje&fOI0u@LaMrk;*FUt7ir(Z2*{j)7!W8*WB{wlF!z`o@Rdp%HN@DtOPUc*;!m@ zlZX#E66TN)sq~}XWZGQhgl4*7Sd>U%>M6FoP=xq%u=Ccbp*;W?VlBoUO6^SnlxJf0 z&523JK}dKrqlK+Xdabzk35SWLO;jt3vfu2?bmAQWM_%NJo6@us7mc;$g}a^&05vC0 zL|3&5`}6Y-o<-o0D~q<~H&H92u)SPVv#2|=Oavzl-!uV>c#vm~PMm*%f&Ff$+-ye{ zC6o1T`>e=i&>Z68qL@pYbJGjDx%7IwtylNjP42)r$`*xZ2`dW8$m64AF)56ZYyN-pE;Lh8QFqM!HBr?XW ze}$pbb^da(&*ZP%4`UC0{P236f5zFW+{}P?;Alp8s zNw7(DvIWNKyhn}ElkoHqb^HTd3W6Qp<6JLxG{aW_wcZH^PPG?y9w0^+FOzBr!WqDcJExFG8RY7e#v|L!583M zQdN^R6gn@kd~*NTDFTs@#x=Ok*<0x&j_l;-tYdK{uv4~Y+bIQhR6RbMvM02S4L`m7 zjPYi;Tzi6k{c3+02vIM7{-h4be5uZjR6z=0bJ1cxGXi#0(Kcs@>P&u)$|`up?S!=} z@82zrTykpxY*RJOSXGgUs$+j2@f zhg5Z%4(NrcJFrM)7HqH=W7-3)(?g-9BLS{`!Is)u+#9I$U*GR56a88QSq%(yZ2N@) zf3{FtMrroD+coGl+(uAzr$Xe5MCqCOQ8QUBz^8PLAXqhQ^&=Vu`)yHcSKSOyKA*@D z0OTSsJ9dzR#x#r_Fc&W_d#(RL$H3Y1># z#JF0NXi#K8`FRRJl}U3gBcz&D;29ka+a?xfGKYeZRO{+Mjixyo*AmKc+8C~b=6#kj z#|{6|@Il`I!xYReHa-4HA2$e}bPTultT)FLgi&0R{&mTdnhb7`%t1+U2bGC4mK~lF zLO#S3z{4p=&j=gdv=E+;!!wXW6q~PgJ+HoZXsFZLZ(HvhGV= zvaSk+CU=?PDWs)}GNv*;Y-@Uy1Y}v0#jMH5u(iD-^iHzR=|ehgVK(Sz!_o?K^a1jV zOK$pxok#B43|v8-8v!$Tq1w3jWE{w0L$Mz$uHIu!$46Biv(G={2fjka5&C=G0nJ<_ z6V*~a2T?%u*APKJpIrUR2xL2MU<87=;E zA(6S-TXa3h-l1`&g&WXPXCk#RVbaa?$EZBA*E%ma+5!|F3b>eO``AOag~KKd695i8 zh9JkcFl(zc)Wzo4krs9_C-gg@_Gstn^e;()lxw6Xzsphzt?Rw|2me1Mt0N9Zyz8B| z#vl*?0FF5S4L`KAiIX$!Z`AXDK0_&-o0nl0;`;xdp|ogdIUTek`JSphHPY9Q#7|xg zp)tEy;xgn@88)?MSkFKouK{T@qM59ZpU52d<=L)lTSpTzw|GE^S^8}MbE(o|Qd65l z&uZgPuSII_>HP*46QU7@qj~6zsiK}6M4-t`(RUa>M88?&kk#0NH%0=Nvp@=m*cuB{ zc7z~`CyL>%70V4Ai`msDJZQP}44&5iF#%$f({4_$ z-zL8oQsTr7Mvfr4a|bU2h9nF`94$B*qt6Jz>Y0OG;kn6W13|^RYh(rKc~)YvHG>Ac zntG?yf}ng|K-cX&Q8=&-at*mh;6NVQkafptFW5q^0}4$7u|FFMZ~}v0vIL6TVkK{a zON6-InXSM8iJ0PvV&XuQR{jSjm;C^qgV0ZmFV0y4mDz{}85WHzBaCk|OcM5 zT#C1lz=0xQ){Qxf5-MZ=ER{8jYl>w9;}P5#f_B;dS-`;t>&PjUyVHUfiRHeaa6TZc zA@&e$zDj*D91eT^k~_?>9BEuR_WB>FFuv@X1p5aUWID)GzjaT{fDs!cLkSKu(DF?3 zEecDsY>1V*%xP>ST|J?BkX`iE>@g$0b!u5$VIBX>`mQZB?T8P`tA5Xc#n_K_CG7U| zFqY=4?N_!G#-d2pFm@kU0(z;sV%B8v4!Xbzy#gz)89{m@TChSQA8i66A zcXElN%BF`kxeJZ9!Zp+}3+!hDWIj+0R26o+*viXDHwJZ=toeq5Ron6^Xy~KtkE#m! zt6r^2v%*l^nfBC`|vdA9Z> zdDMYEwE)yam`&qH5P-dX?6KXXKU~IoVfKA2JB)u|#oKb;>s_X#!R2TXU5TwBhj=~Y8aaycbSZ@H1 zIroeP#v!DC6+{N?Tl-1IP`$j%$vM82j% zo`sV%Hc+UZnG&+9Vto>5#3AG&OLQUsB$=37kCC9CT*7R#%4`WeR#f@Aj;+n`36pLA zbL(ofT$w{v5UAQBDlvafCW^8&`yr--Xre?ZE8Cif_q~HbLolm)w%F?KYee_T7N`bT#U5B%R7fD+s=&qGr*#%oySD~s7 zXUcHcZBCcUDP10ZJNw}Dy4Klo81VO2qq&$q0;r_G)X+o=`fS~zJg>l z!xpHnxPS;w^K__qx-0=3k82Zq+o|{EDV11o?ktjis7peT)~@3-Mv|gdN5x&=7U)Yy z6Z&YErh?05r%xmQ7e|EStl>GXIc?$6Kq+{dJ${J!Sl+W_(r= z$GpeXX;hMEm7#b4wsA@i1WiZ=QG}=sW|!3+Fx|dOhypKLk!+d^{HxdZVJMVyu*%*D zEHFO5&$pb8wHunVzP}}&JaiGOOs0h)-HpfxvzGDfwZLljeKzdri!~Q3tvG)3AE(dr z(iqT2`}?=KkL?HV*L2_}m#2faZQ$wGfoZ**Qd+tsnuAbu|@RiJj3^^ zv#hMk$9Y(6;DggS4W1GE$@7-$iV7k%S3lFv?KL(w{lFu73=vwCWKev6k1Nr6zg+>R zbA`+mIL~3%AWMQ_)l8ut(ngRv_44s9gEr6})nzRSo4=l**e^&nT3a>XkWM>`I1cWx zK;H_nc8dcIPAhDn=(0cTE#U8+H~{`Ebgs7F_*K7~HV?`^;Q`5kK_S-nYiXhWaG+>q z1};Zn4Vwat*a!}b*v=LdUPAI-fcF=HtIa%MBQ_&^I$>O)@oq&qD3VQF7d4`57Ajx> zsJ5bLzor@R@7#qTNG1n=0hE(el`Eh}1CsxwVllQKA5dPX^`HSl=SGzC?udbaD5ww? z^sscWWN~cklsdo?H4x%y#^MN=J+|1S*BCt*G2GWQwf_x}7>LXpTop7Fs=N`(aDe1j zoX*(Aa*Lv4 za` =QB5Yk*?znQaB;jv>Dgw>SEJ)xo%v20j5-Hq@I)6>B`7%))`>^f(i_Gi!7Vr zYkbjX=d#SOCS$fSeJuj)OCX?DT3U*QTPiD_^sb(z>wZh8 zeElkMyZqw}tCtFWLxh<>aZGwTyRdyS<4;@aHdSXu%n7onS)0>8SJQHsYiz4g^Fnj> zi*{%(f9+CV`7$!*+LZBPYr2PwZCMpu8;ZPA;bT>aRrz0T5Dh zTw~*{N@7?(U^*&{k~4g6NG;)iHgKQ*W1|x>j-YIa(ThP0_e6rG6qS;ou%cLpQZ5>Z^#lLb8G!FambQXl(%!07)>D*CtBO zmW4VMiwTj~rekM+n3vc%KT)2?&oZF@5X9bw~@z~m*qh*1Ec*rNxM#s%NMG6jDosEJy;Y8^E2@K;uQ8(hHG12NbsEld%Um=R~3V-#bSx!vqYP z7<%LCNdg8F)!BT>GytUb)G$As#1_=dkXK-Dov=AMSP0TF8|J2n43(iNA=4K&X6TId zUFg?1!+%iuRcT-OK&*2alRHgGI9gj^e)@>^H38Xe+E1F?+&_@5--?%6a_^&dF4JY2 zC1B+#DrG6OA+Wx4Zb#WH+zDz&I;Ws!^W2AqNu%wjju%0Rtrv)2qlDPPf4GCiOq+DU zCDD^{<+8P3a-5sdD97wt$HHgAf(xE#3$KAecv-}_)O&j$Dq^+LX}G4+2=-=tIg^Cd z;80gJe!5ZHHocaf8k(3}`2(fwDUlyX!%C;TM(HzH5w)!Z44Yi@tBSkTAh&P$BtbxI z^BsjJg%j96ZY*gpf!52o0>ZzBFup+{@(PZD6{2Ep`H`0rVez>20h`@%Jf*B_kQ?5@ zrQrcC%%PVMw-aN)^^XLp*_+JEBCgvH`%Q@I?kjOCN}T77A9HVa=oy3*VHF|aGVJgA z^)G2(u;cCKO8h;8hCiq-5H>GCcikJhsR^C80E{|MK5VaqzP=%EkZ^9goJkx8>2M|u zPoM!s(SqquSXL*s9!l=!S<4}tFZjN)OzAsgi?s7$t{ajy)#X&1wP8`4^EU~Ca!>*S z|J)w(kU}_AKX@F59z2Ul(LJZSrn{r?IiWw)I^xh(>mL?%@9B)Pc+`-_IYJ2Y1ja#P zh8t#Uk~HB6U5`KWm_DwJZiH&gCW^uub6^26n2cl!q&WC)K~;3kzDp}1`I!-e3gh2VbIxur`}C!Grl zEci>j0m+;uji*lU-ht0w6L#}P*u??duo7==3X!k2CUC5Y^6ZFyd7$4Lqu;0L47+uO z|E!6AnTgS$%Z^{uwt{n@4Zq}T<1d?~TN8E$D~&`__^?u5Sgh#yc170m_d{*fQRMbf z>)#6J>LpEYXNnVVLxcyoTU%6}4;$Crw zJ$r(V>_V-!bQ*}VzZydPdhD-5M>@jOi?96hx0W;v5$)(%=sb>o;o~W`Q0|5Jy=kp9i|!QeoGe+|O#z9LiaX#1UjcoV#^NFwH_! z^_;(~Un6jKs6M?49Jf66Fsoo%=s)T`3h?$|ed?(yr9vtBE4FS*idIDfP^IzZ($=m} zE4Z@lv6UG{rM=}3;!(JS4K7Y!v(zpbGLmiHO<FXwKECgo$w1zK=UxKm z&6E*njDz-_eb%ThG{(~AipChnTXIpm)u}Fe>>mPlvK3CUex3bOY(CIxy~|!!_Ywkb zWSfin38vv-CjM$Jy*sl+A-k(G6-f{B3yEm;#n|rqln{Plu3Kg3?2}0LtAX7x6ER}C zEp-{yM_8=pcshN9;OB3(Uyp$}KHG~|9mUE_zpd)|qtA4EG==KFj8{m|XSJ`AQ9~r7oF_ugc`kbFq6Lin}7dH zp3{GzM(VqJB8=ZCI_O{A$namb{~q=x#j&z~W>F zv;v#K``8T5C~d0z!>+dJb3*KH{l>J$|GpkTo|s{~?yR9649~xSy^`&->S2ey^7>q0 zYrmUK`5PvfeZA7#x(#Nz$x4gs=uAO?$^9Bb%wX74vypQi#!nUqNbl@H9=Og1PV$-p4d`9+ z*w>D*KshZx8om6Oq5*a!dCINU9^t+!K)^cXy$*40piJqB`GHlY>U@(C)t2lcw#2T5< zqnfhjkxC3P!59lMp30rZKBm)NCURWGIZ);673nvM4pOTg^Thq%?uCBBf#L(GgSezJ zr-J60qEq^@2a>UxHeYyp)olC;6@(fU-WZ8Y69S%sJjYZ2bFng!1O?M{2@N#u!xflS zuSiHd<$xsnPA7b}dBa6U1`3KIdsWz2r_l7BMGECHkc_J4}RO=_CY2g2~rQQW`$;R>HX z@B6Rf@6Q%}@NV&c7`jzAZL;mtLmM}HAG4>GUQHa@sVZ*F7+*`S&oBN%Pd6h=?n$@w zt+m|_zRd-vHGZ2}C#k7%PZ)f9I&U^JvBm<>e-Nfqe6yLK%hf>T@P@1yAu5aVxc!iD zrf~-Qw$*rREkbrdQ1(>GqRYAMbMW zGf5NL63^CX*tNmMWh9PFk10xA*M2xk0Dr>^v&IW%k{x{Kf)$%LYiIU-Uzdp3~rqg{+Gc&>}f!H zKL|L70!hxmd)6BfoRBWDJ)GkHiNy%^N7s&#B18Qs`<-adx+9fR+Wi9Xuf%3nlsL}B zQh>8zLMY#=0Y@?okfB6TrxT8K11R@L0y>??!uBkLwfemD41@%BbwEcTI*EM!qe?eH ziOht++a+n75Ol@N$_5JXgJYsm~r|H*CYNrRcU!5&uYq=Cm_yj^u*r+!a22J^;mcSkd= zG&Q;|EO_wJYX&10|2E>)R!=22Yi@~B8;<&RS)d-wbH}-hUx0d@i`#R`T(0~ z?MY{6)0mH;^L{Ly3mEhI`881@;mZw2gHrAli_7g4Ywv+t*)Bg&y_j6q<}L1xhe1d2 z_U)0iQl&|<&}+?q=X3Sz#+GuUvE%Z+0mn~ap)NLXo&CU9lT+AEM@**^7;u*dL^=1@ zW6cVomF?ALKxJZf<_E4M*Zqyty-|iKaz64z44g{|@yv{$Xgoo>G6)m(w@=a4M&CRk z9t>k(qb&}QOs=`Ba2OT*Ez?#yFv?-T8<$MSyn3wNYHzE{$|ebm@%k4e>wLe0u{8OmFcYA8Hvdk46U7`#7X!SL0DX zzH((9mr`UJFN+)9u~4boPsT$vwom z9XrxshLyB?7uJcsSaqt(1FZ2t?+TK@OO{hqaASBQj)>i>NB|QGVc!*u^IS>Wq;(;5 zdvrV?IXImQe`ZFzYLe;I5IIy|@XF$YoM*N6(n^Boy0oDTqjMUUJ@Eqi3hnw@Fn|lu z39?Y?iH^U8VFZ~18TKV%lC>0(Zo3kxm{9ckyDD)zYp(WD#7IX0^LQf})(9hH`lG?^ zs|KR~4I6$BZ_UJQV0Qm3ZD@9PxwZ1`A)1qrJGF{BvEaf%d%73SW%L2*!6ONS*?&Y1 zxHSyMAByUL9rEngk$|9rv80+um%Jj{+=?oY(dx~32qlCH37cA#D(j{fDT_5^X!>b| z63UnvV`b+mwvW`fu>IWwQW*7Fh3HtXYC`h9xkS>;6M&xn3u~Gp6r?KXr-fjBy!Xkcou7D`aK~>d-tD;6wO2;a z;~@JtM+b9eswXQw2-A1_S*c#iG#E8w_URAmYOqM}Fn0hS(;Nc(txvqBSupmiq%lq^ z)6_?(k*k#K`3OzDgo&=r1Z<`V7UV58S@V_`=Y&wp%_-@p^C~qj!@YE0E6AGes z;HP%EN!cDbG91g@aPru@f?1ta6Vs!mS%;p2WxNO4mmwX+J1NpNlKgVc25M-gSvm5Q zew?$Ds?q1RvRYji5|Nm=(iNMM!l||_|GMx=u^6~JUc_6CcL3%CL_^WH+L@4^pm{Qa z$9D^yV1;$B!9R98io2C%@=GyJg_eoU#{+r4iC9c`mDm*M02Evf6fmTYaXFK4Jici| z@&0bnOjL|0h6Lq|ioFjUk{p)k!n#pXWs$-{+}|^)g61GaY`EgAo2iyEQ*p?gsE3>f zhK+SNeXB=Hi5z{rgdj$EF@GlIKmuDt;%W)@3r{t$ym!3L&g>13Q;|)xxCL(7RRyeC z0s)(MkZ@y6cKh%_42+4H1Q4(>oCx$uiRI)=4vr?ZOK;S0`6^=#wFN(8SK#1=g^K z4)JazB5;==%10eI>V!E7X+xF-NT7~t5?!8x*k9{Gy{vYTY%fzS>bJBwFG1PLT?}!- zj|!e-v^5$I5O#*QEUXL4?y;+gtW2{AbRGHxd|e2u?rP9GGDP!4=Yeso*A-n8m^GOp z!AcMHVr`-`opENu+^pq1Ywi`Pgy8@)6rWrUV&?Pq=iENL+5NNp&Z54O4))rJ0tLW` zERQra)v7poI%e_>r2*Reo@xk)1m-n@1&LaLkOqi-G+-&{UjjpsY(n$NL|G?Hxm;~W zcH-N$9l?5X{~B*4Y$&M%hg7CEp$NZ+QdY5DNM?w**RVZT82HH>=MmUn{E`DTM9 zRDB`|bUC;{Sbh#KZ!*OhN4Pv)!sYzt<`SU?Q;nLei8LXeqS*(7b8WftT`idz)Gx^k z!BTi#%pnp~h&HKI$f>ty&^RsqEC2UuO-Y|3yUyMUgU_)h&H>#1%4?lX|0&WHaXN8e z2*WPlb-$1ls0m6Ub>f~lQ%+rBZu(fmFSfh?QR=^CoyD&V^;7lvjB}9v(nM=~ZiC(O z>D;JrISP`9v^@1xas{YKI6SzG3bkUpbLpAam}WUvv?=TBs0(7q@@~Q#EbfS?rli4z}#*MUoae1a67r$eRv87~Xfp$+4S`zJ9Ls>s#`sw}hiD>YDwv)sI?9JJGw zyT!jneSR^6R7p4mVBy#5!~?D2nYjt~(68#8qgdUk{bqy9#AXO<<#=DXk>D*o{;s}C zuOqIMi@)N%G{)ZSYxYU9s5-$#`sRE ze3N#?AEMB~tg81|;#mE4HYW-aH5>B9s-hX<4cC zSO!V+PCX0s-|xpngR?w&J;F_?!mXgSsmqSySA6=+&KEmhmM@|+?yLjJ5I%qH*(TZa zngevSw&J2v_qCY{T^!q0uW@mhVbDd{=%FxJ$y~G?cTqj*7IqO?o07Y0?U{X)lIv}< zZ>Kah8j>%6ce0)NDoP9_3e%N9hvWb47PBq)oQquHu913I_hJLAvUR2atcqA1UeHiV z;v8C0&3gAB!L{bOJx9;u*`yx@qi6H$o0MO+$SiKa_vPR*Vw;DUS`Xn@O994)!guV^ z6ddem15<9pzw?digp*JsMp`#n3FMu>)jfU|qn{w7xQ&lIpaRu>8S26p0W!q@CzC3F zQ{+oUEY6^HKszhL&9yxvKLdJOD28|+wYwZ5L_b}|6j!b^5Y;0>QP8g>XcM(5F0q(J z4DMWk@De;=93-L W|Rs#UO|;Z7M|{6R1@&mZgeHF;m5S*_Yiwd%@5q^VXyIpEo` zMR9wV@R(8!E%D71t+HFp+}{Dk86y3c%YZ~tTq6wpSBC#bpvfIm<(Lrl?&}Z1JN{a|7U%yL`A`FlMTUlwT`2h{yv{eG%wT{ zgG{(0U%B0_=S5rr2u|<;j!tY-l!`H_{Y(7wppRrszUyoZIq}%l$Os9oOkJ35* zm+2gCXog|f(pn=#GyGw8n`YKbZ`e`3R((BOjnE%N*1?O=Z<+Q#^0g=Q$9MilM;GE! zUQhR2yO>dl3v&%PHDFj6YIW?itBT=;x`l@c0n$$yzn9HSpt zP1EN@CvWySZK3Etio~AxuAWTm|4##gjPwZTO(nr`cnE}O0>z98BzMFj25NF)NlSM~x(yp@HLEviRJZbGPL5adD^HA2@A8lNo;&zm)$o3+V#%zfZl?sfh2cG*z zX|h_6sUva;r}GeM(nsS&2~j@Y!hHL7pub6qj`W1HD9D90D6>t7x$m%4!7{{KIqo%& z(Uv=}aFZi}`I7K{ScF?sm^09ct5s+TI7rJ;%jZlo5r!XwvCl%Irj&UNG^Wi2k#m9(whA~j#m&eS|1zdbMaaHwdaejaR1JM@`RGLoeuoeoAaIw9_~RR_lhRSp#H$C8QGVX1_ho6L}eyL)||)3 zN0z!Z!F%=esk3~TzIBU@i9i?JDfCl8?QGfOCU;pm(}LO~ZC>(bUgHwS2UT0g)wRBP zK3C9D!0G1af#0=!6um=jbM7L`VEiz9 zh>wpR9n^n*4cB4*7IUx@yL=1S3$(&lWDnV8&>|T0^z#kZTO?0zIGSKQdGIx!JHJRe zTSz#p!ag6Nt2Q)h0HFuXV!a)WfGa?l7ij!4dh6x`)!PRlz*;ObAemIam7hkk%B5eDMqzBNbiKSX-e?!3YE;aZ1X<D~;+mY5g;dUmWuP{QqzxTQ}ub48kE!IF#5aC9K`xK@z-`Qvw zF#TRq9L&5?16)u-C{!%*MuRpyp7hQr)IoQ@a@VZtsK2fNa74s78WXiD`p}e_u?BS+ zyH%zjkfu+G41o)ph)ih@LJ*3sz7gnyDAE=lrp>0d<|Ja=!d~v~2SXbW8Ftuz>Q#8y zG4PNAt6?zrO{Y%gg11;#qSq!m<1cMy+hMO55E46pa!$b|K49@M-{GP50i9^QC&pCb zQ(DlO5H!-R1VFIpwZ25UkTBeM9WoIJ4#Oj-hs;0@Z zo8Mb}=_{D_PPtvaMKi{b+dcb*F-IS}z*RarmiJxxa>gpb7OVs1%sMNzX`ebO5i^k` zh4+ws$)@oHzISt|tgxn39Q9>3|rehFq==4}hkndoVEQ@3V~QcT7pA=H?hDYd0c35N@RP*m_PNmgDK zF;c>1{&WRPbS3?T@)heDqew31b2e(u{5l1?-6xbOo?U>QSlg+?Wm#}9n8=H8Q;ukD zUvL)HJ7e0KIz_H^l~wJdFw*GqKi58t)G}u>GbK7lB9Bv-cW?*h*P~?#sgln)Z>*%d zaW>(*%%8qh5FV=tom{CD1{ae3*jqUp{8tk_tD;E1nkjNIC+}s%>jf)W z$kd^)_D z@gPe0efW6>pNZ53ueSTfw&II~rWovS6t_3RNORgZ%aiL>+7|kP?hT|wuTP&t63ozf z7>@7v2EYX^B*nkVC-zl!`a7ocmLk_8fY~J9G~hwhhl3*+J_OKZ=*`;^iG1}j4W)y2 z->HwDTQe<1-y~pxUWJ3a#!qi96bA>!o9v%x3e>H7&brS06P)pVlp&vY@8?yOG&N3I z!t&bD_5tF*R?DcehwRjIZgPV$(-ih@)bhP#DGKPoh7}?-HZLIv7VJJeDq1!2xHXzD zf=7KIev)x*80!KHj^8TOy2Fv~w9n+d$&kzaEJs@$=K)tr@w~hljrVO1$m5v{j8{qz z{Akj(C{|(sIwv=9CiL;%Fmwx~(lX{|q_8o{&yV=vS_U0A{ufSL=m&`Y$NHa0X~UoV zL}Zg4CU8nbx`IW8C@b+}bzp^jZ+s;-O_M{11R$wRFg9osmUOXx3d&FA5&T^a5l@16 zMC>)a_}g@dup{doLu+}A&-ntb6Qf!;3^fC(#;`d+RQe>ZMT~pMcR4vD-mQB1!{DUH z{G4*qgj=`pyTwGOC&WKVe~SZJ)!#C_ww+pky|#b8`MSWA|m zd*(oMG)8QuR>@H>0*XPXr9wpLKVOZ$XmZ`n1F-~jLlVIG#LQn~4v(tb`#Uca zepisYkqbbMR04=+O+9UQmdM^&eLkrrsdWMG_FycjH(5m(KA5b2gjaR9rnUGb6sMnz z;`4nCey~fV9ZM=}t0rLR7b6lVt^8c}2dxrIAE{>5?hO3wd9c~I^fuU3%R>G>C~D#G z?Tq~-zixHlwQBzpB<^ts`SnhbR8iVlF5w{Xl*^7N*j+o5Y$H;PmQPv>Z#pUYXv++Y zJ`{QXthvxu<}Cm}0#R;=jKV`q!a;0^7;ls=v?FdvJ?-$6B9KY3z%G6FJzuQ5zAv5f z1@xMLNljlL*_=R!`p^vbPvo0daR+>u8FO(9a9dn+LkMqTcUASOg1J6fJW}WDnp32| z>`nHGqN~ZM7=+|dhoIhsUuzf!4CteCoB6|hg5=KDyTiv2Wq3Wzi-F|6uw6uDjR5l$ z;6BM8Agzuy8i6lv@%GGT#l1%^eV|Q)))u9*39b^MuyNS>#cBs*MtTl*LT>VgdAZJY zys{r!8&i5$0{giVNYo)iaNGEhDj9LnU{sI&>O672DZ*X!8UiZx&kx#F@Cc;R(^Ssb z7$*d zZ2}-t3{g1btM&$YL_%p?=wCoTZ)2Y^gT_g*dXy7ZQ$z`Io?(;H9E@LPy~l;7`@y*c zR0=_gD3r$4S*?4FdB#XptE3s7-_>Wg9b(KbRyTbPC-ci8-8PGg_gSE#8Upsg4W&&Z zxJ6zOIq40fm)x(+=s_vyoM<7RwvLktwuvb-P6)>pBw4{f%Qbcqkr`oanq`vws(H47 zHOVFyvdj?lUqR~AUJHMA$POOZMsC?-{r4D^Y_fAcQ(C)IOEa@O)ay-560?R&r#!r< zmi7=}Vc=H!_XJ>JE?D9{o7z{E0gvxCK*h{U!#=?Xkr>uv~QCx2qN5-q6}5XEDDdBP|`^}z|! zB*`^M<;AdrNui&81_q8g8CcB3aF;SD71*@p0N-b~pNqNaT4JUG9?dao5eCg~ChSCR zW%JmVrF6>eXB>3(#QHw(MwN}^H}^-?_bw7g$Tt=D+Illgl{WkHBpGvLzCodCAir}o zGD8^1ADD&iLJ}4%b%iY&^?NgFU&hc}v|dk#ivsi|>d>D(b=1DGz~3h+!~5N|9?vq# zWa4&@0J>CmKDF*YbQ_(mG?WjG-thx=N3Pgc?IHL1kv2P0kUlwu<=E2mUL$Y?&{$tMsfz+yjLT10O}=ZDM^&chWew-KwZblY zSelJpK(3j>AgQR8X{y_X#jSw_^H_=u1*@DoW5pV!zjqQ5=w}>NuyGMX29F## zCx`U|AMe|(X}3+mRgyT*TJ1%z_TbeMJ0=>gPn}nHA?pLwO`+0F7g2nAmu=iCa9yzB z2R?L;4=V+0#@&A-Z_Y@!_pLgE&|P5FT$a({=Rfb%d+^&ZcsyC%-yHAup7n=74W389 zJpV7o-Z457?_JYQ$F^bKZk_=gj)gsxP%F zpY!bfT>A!*F~mmAqc^FXUh#XE56NR4y6$y|PPfYAPP9&7d6+(36#3?gN%aks_7HWo z|A|-30Y*_9QTPfVzc6Vbsf}Qh&-NN|YqxWXvIfXl^Rh$oT(||u!EfVP4;cB7kMote zV|%Bt_!#=b2L&!0nT+{fYHG90_!*rH6wK89*u;@nxE_mKmfiE4S^Ln{**)B$B297G zb&m}d9ZJ9dp6Pp!p^O`is@D>WwL}#zzoR~L^0O(aSefw;<)-N8e^b}f>#7T+B}DzQ zf&>BS{#r(O|HUGr|I^XL&`RIVR^Qan!up?bssGqa^cjte{te6Q{xtcYHj*MW?buZb z^e9<@%#>r%O;2rJBNrdoR1s+hvB9I>7JZNjLEX#|B}mgfN_CCsq;6Amd|$Ko(8n*w`>t9Ms#8k7F^#WAm%GK zGs@gOR!-QLy*qZ_IAfX}R)SP*FNCZzREj<vD@BD&s|~X9e^pVh@O}= z0R{uW{?*AGFM=Q$>RXFFV$Vu!@B^Cy9(6cf)}A#Sa`G;fiw1X2cHjeDurm+d*zr;} z#ssy#)d9V|x~LV4YE~ayPxe+E5Sv6CV@IA$t80Mkh7>soYYR!fR{E`}hsSz$ zIMV}dVEOa$ELZY_6qD_NfJQMgPujR%5?n6dd~dc|%$v$5Z&MMOCbcC5buv9Z)qN81 zBaS<(=g~f|P$mx^4P_|OgS6lZmP4OZ^lHBUJbugt-Eo}2O$>dDjs37+)ktWptjBZw z*hTY}n*es@HgU{|Io!Cd=l5>W4_Qzilsu*_$4IA4Ah9-noeVNIi=CO*VA^)$j7cjro#m~-_$=%FpRJKq}#@V7#rPJ=%`-3Vf za-Hxrb7LyC!X**Q^c<69*mUlc2SKn9TgmUP4YjTB)?MOHxBZ_X%2y++nIQ|85Sf8K z=m3rItrt7UyZ(jtP5smhIsQLNKc$)UXSs6tIx@s_t-s$Tq-2k=H&|7VHPxOr`opCQ zaL4$Uc;wMN@@}t$(-I&ySw6j^iXD;XdOAt%6j-Ee$8vAo3D5Y(n&Ax+P$E_hcuSwZs6Sozgm!BgKouZ!>e+IbGC@A&_2mAAKTzW_`=O38oMm*i#_$Jf zPd+u>X2tG;DJ)@DEwxP~RCr&)tb}v#;jBQpy^FQUz9ymqPe-W@1s<^LOv|DjGGi?8 zfcdS$3LUB7@s8@6x4D!^MwX7rUK@7A0QUn+-dT_Gt!pM*+h&D@xO*F%7h4zz6Ku>( zF}sr{uvl$M(DU5t#eJvI*AVI~8hdcJ_yQds&6q_Y1b6tg5_eY*lTw#BkV*6+X+^4w4gXvWsG+h3b=as34v2c(1;9$7`mg(&C=qFI6C zuqclArsyRB?tu=J#q|hC4gvwPrI(e%;bZKPI}Z4e0|f{H+swN^aSwT74Jhk9@PVQN zFph8i@d^pXNDiJzhA9#(C{mHTw?6a%(XG=BA>PPW$~ zmq?FM!$s04+!_EImG8cXl7SWrPm1G{JDRxfGvkv(x)4~7Y68^e2LK4gSq2M%&<-_& zNk}_7DH%l+?Xr^S@8D$e*Fn<8J#)}oj<6WPtP_riMJ~+nWCOnxp^fl~1x5*3@eYyD z0jkCUS=Vtpu*W}PxrI@ZQ|c&8ioV5r`Se39bzRxAhkX-3!hc;osw2$W0uqC55U%qK znrsDi**v7}2AbYqRqgW(K2J9gq%N~_V1;K|$~Ij1Zy@3yv>EUj=pe{~J?DXZZvX_f z+Az~^;6K2h)*}pq(XWwF7CV@@bI&!bN#Dsx*D`HK@OTpWSssR3xVuU3g#b0lFZx_n z7W;&p0HQ=08DK$qY_+QatRdZ7KS5HLMZbZU2GEPO|9#w{Qp8-y_LvHeqQPvJ(N$A%X5jg z>lr7{$)Q*JB{MEVZxY#2f|Yatz;P>ARP|w29C8rBkT*uk{d~ZqXc>DM_^OfdE~eG= z(i~-uH%l9nxx&KS?dDo7l<~Hz;xL7aqWdS zIWK5sokc#w#zNL>bh`#D9?+~gJ&iuo(hH>Lg?71T9`AdGvVyj}2k{*xUOW+)1 zb2T@u&%kt$ryIx@+ z6p$R7Vqvj(l_Ae^*`BFoCexmxvS2e6wyi(FQvE$|pBbTD#`?m0d;$R*s%F@N0{r!Xov1m( zFLmNUiPkrRuj;$Z7F_5A#|yJ0sx>)i__ir+Bj2a6JDjuCx6h?houYfsj}X(e<@lX; zBEbj$0fzCNL*AwnQFz)U@ivfjfJ=r9oGl}9n8C3t)*g|6p&lwCnDVYY$ZxIa6&f=; zvl;hxg|f#$MNEDhkS;JzToh%e5p|ByimM-Hc#XGEn&YPFxuPgp{^|GU+TSrsyT!fn zO8}P7AN%JFYGvP?qUioy-`uaQHE3@M22ACA^A^ma2r2x$3Yzrn%6>Prtu(H=%n@LX zUn(Nxiq+@z{LIm6;n3S!U|blnr= zQcsHAob9CZXx4fFP}RFZc>?bq!9bs=AK z169qs04`$%FNLTVB)#q2n-xwKaP{@hVU@)n$<-xuulvQT1twK#tY~Op4aNi`hWMy)C|m!k;`2b73#Sl#h-F0`>k1@rx zh0Q2RF)J@*Y2HVJEJHlLtmx4Q+okJ-jn|(phtNec$*bi`$PyrZlH8JW)Q83pPz6M) zAxO4N_#cu3WmY~a=ZMl9Y7vZ@ZF}*B?YN~gTkSQ_TGMN24NB^$8I!!uqcFy@%R9n? zvx~VtSq%Kg-^1K<&iI!WT7BNiGlh ziNN-)FE!WtEsSRF&;XL7M9v~X)^s|_NwkthGx8!eh{c8>_$+XSk$Azo_qCpKouAo|vk%l@Qq0yT4cF;gq})m$wj#L|nINlYm` z^oa{uML5CN`0BfW{U!0=g*d%Y{qRh&?|RsF{_kD#nkm8wpxwGP+&g&z>`_jW^&sx} zRS`U~Sl@^t2c+bQ>3qkW0O?c#T<_;{lD69^MKh@S{Z?H&&x4sAKo8ji+k|;_hRS~S zL&Z8+Weu!j-Vy9LKd0Z~6>(ej&+t6Cx(06alO)=q6$>(MFjTFvn$iMU?Ql!*>7h$> z5(gd4PQUA^shWT1kfNhVRfB?#F4FHjMYS!7f@Icx1tNxJG9%gbL%eW5+qp1H zD#q7q@5Ce+|8+^Z?HIv#)n^GKfzh-JLO zgFbZ2dO|v6Az~FMj7??ILOjVCB|WQ+8OOjEe?6U4zpb1{ko{PWe2_A7YQ;KtoSr#J+as>beRu)cyYIcqzi9)<+LK7gW(wg+AS78oFM!Zp2;uDyntWDAaO)Z0wxT8vUi4;Z zQzc&(vY@s)4gcDwV-x3QydBnuTN9BN3^%7?mVt2tk;-y>7D`psE92Uq(QnlR!478U z@zCA(oj*9jw0w_8JEweUa}QAQ$fRj|GreOGCFde=iB47s6<;?mMpVDuO)PSWWUVhN zK^WqzL`%yUH40yK9F~lB|4Nz*wr9RUIp1}dAAdT4%_k>`SJ4$jprkIYxiyCg@waH2 zl(3_1{ma}>YNq)Sj+@T37*~FR=FL4fmvOVhF?@X_w?sO6id>**7Cs(luC87(rml{I z=-)DTPkXXxcc(EN4DoPq-exPmeb7)8aGsvVQ~dn~alC_j1Kd9aJ5d!F18k5|Lz zuEX745Oe}&N!%Bc3NZO8Kr`N^a~`1-)pA!ez4wdRva=qTL56QkuDg}en8pYw`&O&- zhH2gUmXALY`~h3CX1*)xH?ATCz$w=#XY)EHvnL_D9N|Uty>_vy#1k)LJ*|jDJ9u4B zQR-8{|9#*)A#FLYd=303gn!-ft8ZfKs_$y}|2pz*>`1wgKPGF^9F=6`wG-A$BGy$m z{7F)V88>tjikU=AmwITw+Dbh~@=iXy%sS%i*HwX9=qN^00JdBAx8=jb>7~=EkyJXk zOGZZD8-v38`nVp|Rh#UbAbs@h6guYm1XD7$jFKWrQx6k4ff}1?4CrKGU8!LVl{U^Y zXSQ1%n1+xLPLus!Wn1;!=+_!68S0|R8}Z+;eOE%*Fjf|$>ssl(t4DIbfj!m1f0@F$ z#q)9;{{*X4&{LH}WZ@??cIsN*d3YrmKzPXS~4%=aYOw83UX?J-1v`U}D8wh3M>Mnh8-MA_G&zqn4g*JMen zjF82N5xUu@qx5Njf?GIhuclOCKwH#=itAd7z&4K3j*QtN#2phW&2)ywF;(4=t2LJN z0{iZ0$t_Ayl(AV}t_!jEd;JP~$3AWS6$K1mA^$1{X&I!N$Ua_JFbg2&?VQ*n$4yvE z4JnAdZ5=1}=9v+!D9dakC~?@NeH#R2&@D%bIfN|HV@G8aJs7F0uA=LNMsnDy|mIRccMV% zD$`0vaec=t@6IZR9o1*QZ62%XJoHN673hG?w^PjGGy?xN;08p22p5IJ*HvdB{@x-JO)HDt1RHX}{xKnU`BnQsY`)Wf9t!oy1%F7Ezf^aCM!-_Pq<}cV zOrCHI>s5vY%87f60{0RHF9OmGD6_PipWXOeNtXfI>TyM2`|t&(tP#X6~6xLcLR zbgw=NW{jqmn(O&W1On#)Zk7oLI@NytOh*N+WaC(X5sm`12DhB*==rOHN2aW8-Cnhr zsZ%5#ZMmU`deHiMQN0R9cj*+}M=t)%CU5E(PX|*F{C6a3GWHZDhtml%70NzY3a^&P zDWtwcEFdT_ZfhP^u?um=asgvu;Z&9L^j;|`qe5*=>MOy?_44FnO)=3R`E<3?WnX26 z2IC{ICwZq1#nZ8e4>=i*j#%~kNQPZ}W;rMHJoNg-O=kHzFDgxlh}OKFn9NK(<5;T> zvTXR0=3x{j`ND$ElD$G-jc+Znk~Un&?CAk#F0NFZN!?M?(RVX3l(f}#7*R4LNEc1p zsoH#nszr%I99d=-KKg(ZLOR3EK2WM*SYmx4C@yk_C4!8*T>c?+A~UUtuYXBP zvNf|SAeavO0muMphhMjmH}}K$Y@^r)I9&J~y$pjFH$fop$Z(d&wa} zs_(NQoK!uDD`jao7kicx3@jCAP22$`MzNpBFqi8%u9Lf55NmwdxOVYa%TYC9=4bNa z;-4-y`rmE7zUeCaP0@E{>V5aE!U>k#XvCGdhXLK@64rEF<c7s9i!ZEMb7S8&@r5-fasIU_{-5*1!qLvw#>Do2Pv8Xk zpMmCthLzLKGRpg8&5Q5+Bi^J<&!fKSNDY1evdnJ_io|HkIps8gIN3Y> z^dfo_vLJhm7s4^~#8l|j2xGM3GHCP6;UVUpfBdM|IsRGH2?~ZCl=_3h!>Q={vk79W z47u0cw_yGed*xyWV?xuE$}~~Z{KTd!n@R#VYCxnEv@!NOalEy>eCR$630)_A{GLDw z!JEElN!}W;fW8cUY2sSWVUX&+K2Tb2!Qc=I3^mBvDNa)PPFC~OUtt6CB6v&~Qb*#J zhwcQ-*7g2v2pqeVr1Q<1?q@dO{pIn{EpA@CkcQ=sV>>!ovgj&;b4tv92EpVgP#PGr z7(y$46?m9&9uHTF!#eH7!R+=nSjArRgholW?T z#iEaswQ_xj>HM94AP@YjFX}9m{CL}&t7iVlEX`f_G!n2p}vthHsdE3X-{$o2Q` z7jLprJ{o@*nP$d_DrZ(^t}F)vsB5Fcth^TnZ!Yq%BdkyRd{26rQB8* z%VX_koh7-7H@XMacSmuxFFMtD8rNYe@Z@c>|FE-JFP%&cg~~Ow@@Mi=*7<;q9mgV^ z4&poefAbva;GkmUSsHZjuwxuc-;|Thv?3?^MVpSnXeIOaJ$H0 z&ya^&J&&>2((;)*o^d}hADSRg4#L5%&RY}KbJ`71iiwFDLA150=D1>F_me5yc+tZ^24ss)VE0&KPz|*z{ zgiqt^%TmbWSR*s*p5kl~71PI>)>plEDxT&0ZEs`_MZ{P(R!%YvIruRZd!L>5>t72&1t74HbIwK!a&f}6uYxBHLLz2w9-(eoiw*ZZhy4-jx z-iyFQQM8_INroc@u{N6bgbqHL*G?-~hEZ);UMK=$l}9D?p~Z8S zxCpvKO@vsMI;NT3oHqqvXssrhaEyu9o-uj`gjN^W_Y-shDj8CKAShV z0sE18hS7)kO)%^Ty|dT07ME7LMN0-#NGrpjf1BT8)pF4{0GaKoC$pT|5~T?takkA} za4OHPSXU2>O_N7-I#u8T_s|CxTSx<#A(@K99ZLXD#p2gq+GZD&LAZ5I<248+k-}RB zaQ+a*q%HkO4e}%5y*!HnEIOtTioXl4K92_9DW83kpS!l7gP|qo;LQk4uvIda$HXA~ zsT3Fz==G+lq!VTDaGxn(!ajwnjC%H6NVi1Icq4u^FH`KT=IR8`#uAlT_zH9F1RM<0 zUyOpFv(ZNBo28Uv@X`ytwumQPW**;X6{A#Z;DAy3+7kS`! z6dR~rrcynBK}x8e%h~2f*a@|?O4wKV4E^)emP_Vn&$RCQ;D*tk`mxs7vBcVVagOK9 zMe5?tAt=$>D2n%NQml(;HNquNGoLCB4eW5PHOBSdpn#vU3P>WcYk9u8LZo!Z>`zKo)D(|D zW##k|plYj|Mr*OE{6Z;JjUluWZx)|}5SU^MH3o+}abIQ|~I5jkbj&(NJSxp$*? z$^E7p!ULQ`x#2=o9yGOSQanA|aS{Zbh~CzE>3}^qhi`o$ojH3p4sh!a964;f5k3^B zvgf#9Z`{D^sMCJym09%FMhlvpy54vAt{goC8vJ%WOsnlTK6M(@S)NgIjf?@~_FKI; zyUFy1j4(p{?M5Hz<~6)4XYX>Wy!-%@#y6{;_g(A)KZgJC%R~#i8TEJWgVT$0Dr}UH z3|a!0EK`{%mv|>iz-QL5#?$6@Vb?^CiN71FLG$^*b!so@%091@0!lrhMPmME=&bcV zeT|v4g?e^(Ms0hwo5ZESOjrg(*y-C?+-uYHLmz1mv=wpRTqxd-j0I0^963A zsCGfri=uHq72ev`gOi))$|d~AMH%4dc=!_gXIJUB`8G*Hpy;3E<+}O%lDl~G z^x)hbTHrl-Xs6(ZdgF9*1_Z9o?*E(E=^xH2RdlIw^0juKeXZU9DoaS;)WX`?#PR=> zIQ`#XtGe!&^95Aa#j z$E>MR*%gX~7*+L*Efq1PONeJFl3Kx&MXJZ>Q%p_sl;Q~tiW+Qed-X8zrqiFR#sYsd zd6ucsVg?%q43n>qSyx1eFaibU`2GyU{v?BaBSE2(y3;GN`sLE5(nwPx2PuWQ#KH06 zc1_TWe|IuWzbFH<#>&K8`n6^DVZwEiDQ6?~Nx9_xw8+!kk0Zu5Walp#yEM-m;zqHg zYuJnp&+V7cixFd9H3{F7*MZ&^`#0puJ6aSoRwWDPK)*_{C|W=fR7GuEES4ZhzWGm@ z&~#KDN)}b}irjYDvUL7}IqBZJ0UO5;aXs-AHGAH%nfq^;2`^AV)5LQWlhT$Lh4l*| z!C$h;P?^17y3lm&$T1_|tJVMh&M3K{AHFn^{c@h6bLdDAB&&5MAG&}y^5b43i})c= zocVwARRH}=X&(>yAi%}p{@7#O?pFjS^E<02248b>Sd0YS%#rX#MwMMdbZ}Z*w!TDM z9+K+mLw*B!nlK(mK_UQe!g9dW>ftv7xU_OLg0S@5l7Zsf;t>Kzhnu5LhxmGldJy2U zP!P`rh;EN2Tkx-j$=tvMnM*J!U6CEkc$>h|WP`$qSaY9r2ziY%x0sv5@TZw-wGYIR zW6rLh06KlwW`uCmp{6aMZ84U9N)=!ym5p zW(&0>ugrg4%)yts>cX&%4!0VD#2t&jwUb<=CD^YbkrB#~L5gm<$*qcFt~-W>TpFB( zQ98`U;jmT{tPJw!8|>Ia_&%19aMA4P4FnqShPPb0)E%&mBeVW-Xtr`Z_d<7C;2TG} zfX=uvEH~`69nTLCAr0yfb#~;xQ=tFa|aYc ztaA^=2sJCC{gW@0?uanCw!*pqApo-0xj)dY7yFKU)8-@llBJmazFSu-1NgqeaVGw9 zxSDmKSJ^!MY(+L3B;k-A-nf>ZJg1V!DKk%o-mp6oYpx?#4z5Ze2%N>4BF41v5ksoo zDs?x)bh}|3N#Yjm4eFj8NHHd%Q(mSK=0=#ok)V${023H&%X2(aDOOhEUWQR=5dUc$ zjM?SeNy&5@ftpu|T6ghHjGW|YYZysfcuZ3G7B#(Ul$~+{Eu0pSYGrvCt)a6|#!;AS zOWvyX-($1fGPI9J+sm?ff%9Fb0TwF(w|kqdw%|V^iGS)YWPAr`+1*XRexH=~3B-)l2hJoT{#2OReDLval3QU`ajjd>!-r zKGr5vR-hA&BD!%nd(Phf@pa147ZK7f4J-6xA8^MMEAojnkDGaZyNs9ek(kn8pM~|8 z6}6R4ye=;tw~I&B!%tgy*Cni8&@G)H`oNnRsU#7m9M6gGrh%5%t{t<{3+w!1W(nd&N+Lk$I0@5Q;;`MyoF48Hd!diW~+ z1#DEEsleOy3{!P&-hzkXwbwBvJBR)$k=ZPy!&8xju0?zSSK(C)aPlYT`bN6S@}6J? zh@XT1Fp(Ad2VV}ne3vbdT@-=KYO|v8sencyaniz0{Mbl2pHTDClJ2qZ*QS)~0DNq# zQ5bw`*v(})M6?_gZZj#3$)<-@uPPSZJA6-=*|#gCk}a5Rj4mA(8vkcMR+cKeLDz&k zH8{~Bd3B300kNhShF^6)z#UMi_HxvvB`QCByTlV$a?))5aspEtN~EL9bXFOXG*2h* z8JrTsp}VX9AIh!oCqFm;@-i!bt>gbHo?74B#M=J1neG-t_l$&%2C~?3 zY?AP&^|x|X&iFl&JrR$uuh^0G`-W1MpZpu3rpr=Aj?hrZYNz*JHQMJh&>~*k9H#9~ zS980&Q($RY?N#hoZP*VIWYFcXZ?16Bwl25jGM@6v{YKTkS%dIvdQT|vI@x*FhpTJe3S_R`LXrg7}U7TPrDJ~Fk-nxnwSeYKRbSz8q9v&4(O@A&n0_Z;a{R24}S ztgLMY%im)&ClX>1?2y^YC^+30C7ztwX&d?}5019^s<6m#lwcXKJz-czcvEb4yw(Y1!&D}C#4qGmxX!j(HkMk zYHIX^Ez3qr)+Z24?mdW<{-|xBBtr|L?5t|e-W6)CRxD;Yq_!8K@7lM~bvI@nkUAOo zkPCN02QVuI;+~YgtZUnNDGCiOM9IV*i!p%pf-J$s3*7z^XVwE!1~Kbag6B-4kI!=d z6cLFNdtz4%jAcuPm12Gu+5vWL!6jhSZea#W!IXgr=nvpuGY3GU{!PaRQRw52bY~?1 z`6-k@3mX2MMHi)V+*9&#C?!e7Y(|;|+%gz9s#X)&53UHFl~eliFw=wPxu^XN@E51V zEHf5*A!Y36-gd%7hzC1>co;{8aKs$AesqMP(xsG*3)OCAHXUZi9*Uav_=AE)uR?z6 zgOMLD$x4VEr&AlZi|b=ZO^aO73!zr8&lc4$_bbqYSv=b9^BfVAW*j^d&8<<))Tpdw z6g|iyLt@i)WPlAR-8+ywd9y{9Re=&#WqXbc7B>u~h0rX)3(g}y4S7Z6n!R->gK37u z7C8wU; z7(EVkIzh%Y_U~>2Xp6#EG@S)oO?E`+x_2w(R`YF-I@S|-;88l-rwrgBXfO5Q`XQtt z@^#4y#o0YJW6FN~RKMwi6A7+8tbNPP!>9f$n$E*#>y~o*y??FM=7xXUvF3JlhGOt_ z-I@DXzSgD9AF={?%T^)Uc6ws?Uk zlsdbI*c&0A@Ld&)pex-Q55r^W<=0v;d&j1MlZuAmTN&Qz7I**epGq4OaZlhLbYg>( zSMX@3gIdn_{BH|j(BzyoT6FPs?Ff@bK1ljqLOF$7nWD?Z|?3_zUx{nXM*=# z@u}RgG}oS(!!84$9NKeRZ0VARwr-{gz$=0rOe%VBhR8wutonFY4YZXn{i*}nT1QB8 zM{!^5yA`~q->FYuo(*T`e^0t2_?;^9t@B|gYiV6b4{;zZh))r3x&J&&f&pR@% zq?_XZAV@e@7q&Z*MD4nzlg2ql5c4&Dn6N}5k68RhWdUQo$T=dW1;gPArWS>_5(+r5 z_;~eQ;2elEc3keqclD8!RbND)3J%exVq_L_emNUoJHu z3o0DdJI56~q(a0jJoK6(C7{!?i9i)jMkQox`pQd)v=qRmnlnMAf!is}MK_3=S?7sV zSnrEbq)kj7Z71RjM8NAfBM@%9hMXkuHuMkT`%GqvQu&T^YTlrvN%JVxE+oKC&QoPv zb|Y5&m+qR<5SdkoEmXq3S~`j&$jq^5xk>`1_fkE5L|6$^)mRgv(!99E7deMCjrYKrqH zwI1$Fps*clS2GU;eXq-pgabV|(-UaHi1(e>iA)z4qHT|%U_(g5u232-qWbG!&rL|! z2jhqa-loP)uVRySMq4nTH{PfP*FQjz@Sd9RV5w>)Ti$`j#Or*K{=(H*tiwN|#Gogp z8^#yy7)IKQ;&&e}ujjf@L{S9N8SKQ5Zd{vx+sQLT+X$k+k4!&U0IrAhwvmUpkcIus zBjeeQ`k>BaCWCfE(bw1dfetNEyZmi2Q!MQLMR-uZSGh#Pn%QO$gD>wy5l`={kWhhp z(?4?!&h8*Kdn1E#tv^S~4Z#le(T;>A!Mu#ONCqDc&xobo!!M(j-F~3T+6MjzPMpr; zo5I6h1sn21_MvJi%)`Rop$ss}!dcF4Ye(r9nzI&l59Bh_IT)U(xT_y`VNdT) z!us8e8m#*%9Q)0I2W2|=oI9d9%H%Uq5r@SLT%dD@s<0q|4<-4 z7H?NmS^O?EH^!#KXq@qzyn|~jv^eF!-u~ljn%a(@FRD5wK`@c>isDMb1C{0geiT;S zD+k435kIDC!m>NkyfEcr)_1T=Kpm{^Wh*;{e{^;@yZw_x-Vr1DxO&aP4o5$D8KLHszxjjiUkLB26;=&qS>EgOJrZjDAe27|J z{0;CEW3YyvYt=3c%5ZW+ne{;>Z?|hu(r=GTXpfN6DPzq~#l%>xo0T3)HYRO~`b(%% zY5-h6b&iD%AE5%>7Hzua#c%goVkfV-c9M1!)<)ALP5Dt?{DiqHJ8&aq0T=$zx2!fq zYQ8;i`EJ%x9UEohf=>taBMqw`3Cr*ewvjlK^6avYw4plHx)cQzuq7VzDcl6yNFYKE zBdfQzRuze9gB{o>yfM;uAzj)Fgt(d1t-Msa%wa7kM}gJVM_$dYPd6OzNFA)Eyq|@z zge<+$ld69fTpH*H&yAvJy8ApIF&eVS=d}Sj%b( zs3awhw<}RF&w0npcy~op%Npd1++NP^N3q^7qc9?;*eQU^m${XnW<)A>e=SO!q=I)` zI!Pe!j)fU|mriHc>otl-;B^~RjuCwbg(_7xet1YOSg@-rZ|+!y7mw$VtOR~DwWMi2 zI2ca+n-yumdx6>oUez{u~PJxxw<)`s(a(+`%{v z;rrl_VYaj&?1fLK&#x{gDo}41S;3Wdi7h<+-%$-*g9X{*R3OoNS0q{^s+qvk)9G_eu`MKZvoiw|>qEoqZTi=o%Z2qB8~bEa zC?kS14Y9?iN4|ugptq}8}=$^0hBzVfzsh9z_Ygy5?Z z&&G7kR}_woQ`bhmE$2(nC+ogD#)qP^79)8P^p#aT{j9G8ZK1T%W(u%4J$=lds3Sxq z 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

      2h8G=&#O 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
      F`F51Y6kY9fsTWI2 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 z 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%0AGhAis%-G^R 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[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%0DvWFD{z{&#_M@~074i;||!3tUXSVPPd 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%0D$bjElX$( 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�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

      yKx*YR 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

      Ip;{pIe? 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

      svo_2b@%TrM8w`T+TZ30{sXkW>p?k07Zze{c+v z>lh>l@zU64y9vpoTDhA}%4tYyreJBikcP=Ewgn{X4j4T6dt&H?K_6?T;E%=I#Vo~( zq=%^0&_Ssd=A;3>i_iTmH1@sk4h9cEnhz1x`VUn_e{w#-LCcYLS(1lnf>H-ZYmRad zD5KfGrv~H*8di0R1GKEZ(v)6t@Ns=+vyW_dkTPW}DiAg0XH;|R`X|rJiy|lcV%Npt zHKUW2>p=$keCCbfsCS8G?~V_Z&k;vFz8p@H;GP1BOJH9ZAGdk<>)z|5oqGu-EI)@H?oixcXz^(zDmDC>G7seHo{o}ch`&44tRJFoWd zVVgiOx!VJNXvyyn+rgc;138?z82ZKR3e?C-s@0___;PdV&-j!gQ!4S#<43d0AmJ&a zwp~YfZcAsrK(b=Ges4TzA&Ieu*~$ArZtjWu5Zg*vOU3asDwB zD2LzVid}A#r|p5)%;hx==16q0`AmzJ*o6Ia@|Y1REZhf^Nszv91gjdyfYPYP%M}K{DUY`4g%efs$R7*P!C^+oLwG=@(f-xo6qV^zqGK7HIW zxgPd}`(a6GP>ay^i`gPnQ!}gg>Ej&M)JCFPGN*`PdF&~t|H%w$dXXf0DpezAVpDHN zm%dCgP-f|_MwXr45pLs;(|rC zh297T480whl6NsK%mK&Ng$~b;1oPwWQD^VQ+`M>xsbVFgXQT6A zK+!>ooTqw7T@$}X6WUc3;YS!Jm%u8#MMV;jN|u zAfT(JeiX?lX3}BV$}{vPZEPUBDt85Ai;Id13T_;5)uU`3qFt)@E`i4gy7*TAWw4YQ zL{kBy*W69v&j~Se6)6)@i5fz50D+hWqNAueiUEw%=d+=#nGp5{K8Q{^>QdRMsEj26 z34L!&5rqPb&>6>*Au|IwAA+JJVYC*xJdip74BZlqrm<_PiW@B%sTOn%^0+~#GKQ|u zwtei=c@P5{1=K+mr5s&=cVSyh6hZUEvhXjEU&M%182<^&CzgNhB^4Y3!)ncu=YPdB zWM@gm3#pia7Ipu3^q-~LT)c!SY={c@k>54+pfm-|T?^bsO^!D2?zgqY&1q_ZF>XOl zcGP|cLhWCm4|5ZAhvFvOgkWC_ponk}dY#;|ccI=zFbxUk2r-*7G>m+Z-+1uIjM*Vu z07>Rh16$B}bp=vu2$)=gGleyJ%-&J?)gIx~RG;!4F)?E-cS6`hM21Sr$%aF{O)2d2<=)rwMT zuf_B}UhmrO@@#K;U2`@Q;s(W(mq)A4^#j<)Ce9|{S%sa7uF0DN{b2p&?#^?!gaa}! zgAM1pLFV|dW^9*d99x@5Qqb*ch|-x^Uuraq`*6RY^Eob;-lUFy73ZgTsdytjJ+vSzjcZ(K+z=w`E7)0ltNho7{wyKqn=Yy-dP7w5t(OfV> zWL3G$R4{8Z!UMvabSQ4+$m{yzCY96{#k_nJPZ}2@GVyZ(>Bf@~*?Wv@y0YAOLP2SO z%LeWldFh@%8bp_L9!$J3`X`qs&sf8dQEm1Q1spF?N=K|afV=qSDW5?A?&CB#Q``lM z;4ptfY*PHzOE|j@;NS|p{12K$Q5>ng^^N_*H=WI`SG)Va?rpt1>Kv^fyx2ObL-FOS zCF2ht-;w1V0_S==6EDb1IK?fKbu<$%qCW>mn?f3Eqw86v5w9 zOXZSRvK~s&Z|Qg{xyOT+NPB!X&9cr$)rxZ-td=ZFlCGzi1)5eF7r_E?t13q^pJ%S{ zVG{X662g82nuX(kRabFfp&UGNBdriCxmEo~S|Q?oFe_w1U#<#9!5GB<4LZD5)*C

      1ZxOR^pCVO#~CHRXKkb6{@Z z49Ba4@yky~oP1&&Q3{zc4XBv$*&^w~5#J!HX;azJq z3h&GcnLUT72*dZhsr;>kK&qdgr;DFM=n*#N_j#psHs-CWY}Bb#k72fiq2 zidpESjOD8{P|k&=2H8bAp;oAX|iVY3Y2s&|plgt`;@n3XuG;_SK2L`4pOTGtGFc5j8j;3w@CochqpX1_);R)u-0hMN+5X zezyGi$?ihD#i3CNd9Y(0$O`0v(B=b7pAI=L`DxZQJ1X|+o)coBS>8%~Axkr4Qe+m9 zBc}`v@F?~~D}l8etiYo`5n0Q{G~wb#S-}lJ{3NN}l}-r~HhiGay7#+tM2+OkV)@D5 z;qk9~9jyA%?$qp-^&NKhx7nv6an$hjqUpF0bxm7eek2o0P!P#eL?f1)yAf^50zoXn zp@1y5rKOS~@+9IznRFulB7fV2W4y%Ke_kTq)~ zppO?c;aI6S|KmHL4a~NUETGXkAPivV)^?Dq{#7EsR_n7skz<{DcVIFb9`6xy=eK6& zdTGhzlC6hFCMl#A7;oZ~qbhoVQzK60(pO`3O)h_`Z>Gx&Tt)pMNJ9x4QQ3@TH{|L9 zc&?lF)rYg!EcHQ={*G{ge?I>=@X@12aIM!$9^e@2lpRLI>3m}o8lFGiCD&~MLn`DG zGcO6}sKtm(NHYE!c;Xq7f^hAWk=re zMJpbKgOn0HaETT!&k10VpCnsOaUt;t5In^AaxxMQ!KywRZB6I0`MNHt{p( zw)RTtDNnvv%X`JM?@DJj;1_0DSx}Odim6p3v{sv&$fnNH?wcfONVKaYhTdqRu+Oef zNzxKO5v2Ewxr%=>{!rQltD*231e;CrJwm1J&hk1TuLXM3BlC?+B6XJ7k6T%7gehxK z){J2WmUx?pA+ZO#;wAGN4rW%txZ?4o^LaPKE+$N;%Q?S+#Zd8FGKLRQ9tOt6oCYb! zw9`9i6rviE+~QHbis`xYCLDy9g4WbsN=k|-K2M%gA>f#j(*XMbIsyTZALN@w<+psk zdG?#sn@x4s^Q5g_I0QWN$#{iMg!_Zn9H&a8O19m%loG*C^s%h`4VP6^EB0r-ob)Jp zOwx8TvbWql&_y|5*NcEEeeH@x^1QL{$S>AcxL*z0uguLG=I*Kr=7 zmt{GM85p!FCy~r*_!eGvO>0JGv!82T*m^yyIr=q39#in?98bmw%Yj8bet5BKPsUoI z=c;E~t+)Nx#7fZ2$>&49-?HLJiVtOMvN2Km^c1Z21V?bTg(hwjEy+98%)}x$TstFN zmv45_AyzH@bTF+qHOm?=3y`G}d5o=?cgv4RPQH4HpZyGRuA=G~VedA3)oa;hBYS#L zO*A!0pS;Vn+-SrD8%c)V_o$p-K`Bi5PwVWU-D&M&$>+|S;}g)D+Gi(6o%V0<_f$+j zN8sjy`tfrywy-Ncr&FY_;7r1PE8)#;OAIN4JpxO$w2}r?S{c3rHUc*u-Kg@IM&s?l z@hcEluzdB=+t~(JS3m=93ZJIFfZ=>x#DQ1mI08-x5RoNOxEB5v}4q=v~%N!R;yf7dANKT9Fpz~0Y8VW}W z<{r#7C?i~R0mHrLK2~gVe|~mwf3(O@2lqpZ%6>8PlIHCv2{LBxr&3=$_hh@MGC*0P z(Pt9T8lvDCkvlXVZs#2a&MT;9$pky0gy`l}srDkeMCCim5a(m_ZQmt>^4r_a^<(Sn`$^VwTKnS=vR#r@l_}}@REl>U){&6WzvBVe)rMlcy zC|)QvB~=i~(=%XiUB6XpJmS`(s?2_#3X{pm()h@w3PEoNpOiX>W{t73EV4?)e+Mt& zF1!uewNGM+<=M{$=`_HzGw$}Ia8#=@AxYNOWH|?V_n<}Ns&J6dx}TnUM9~f=ds8C4 zcUrAdtBLIH1cmr~6J)H{j%2eU5|rmq$8G&r0sg=LYpKFhRW~0V%ZNV5$6#T=!NmE( zFdo~q<6o5wH6m%qH$*QKOC!`C zqN-s9^)=5Dqj7AOo~Tv<A!$voNhgg5R~Ws>Pp&-5H=>LE zmQ0AE$c;YKkFy$195_2KAUBk<02sY6LQO)jQ;P>GR5ZqSQ6Uf_%Q~CfLTi&OjMLSB z1r7SdO=5qHR)$x6VJt%V1EbEzPoi*qTI#$XnK&fO#cwdHpOIrZ=}v?QUD@1_u2$Td z4oB@64r5QNAKKafW}MB51)#L}YRB&}#S9H$yr`8oH$5Bol6ARImKE!Q_R5t}0hT9` zH5%Oj%%3cPhj+HGx3x%{*jHpuGmgMOAFT1D3WDW3&SJxa??<-3Xu^X{-O;2zplB8z zMpI_PP8RnDP1Ca5sWN{c1c(AV1rZMdHgE~z>!@Y9HMHuX4Oi@OZ+{wZ{o3cnpiVVcO^#_}E<1}>&{$emw)jK?ZX3%yvc zpC^-n*%X!cE5r60bb0F>;8~^!W1h!@UaE(Y8M;@Ll#!Stz*_?v9$YIA-yUp_XsRj! zpo-|CF_bq3u~g%mA$LU+9hR%#!CkAchT(nUWT55$ZyYixcRN_di-kSsaJy{si3cRP zge0rEpjFI(`_jbDDrOLt6Fdzi7*2SuitOO}52>>pb{>@9V{V%OgMtGN zs#(t80*}@Dj@muiD33N(#eFD%t00cht%2@NYlkx)MfUDn1z0=+zz)Or!hU~$pY-o; zlF{%g?g}<|J`LgxNKI6f1a^T^u34^XOGMLp03%Sa{aGVBYk&qkqWooh{-k_{P?{O2 zgjEy*&FrsMw+m4ywZ$`eLs^u>Rn!DpqDYE@WIj2YS~7yk<_xugb;Yw3I*d6y>xTD- zc*QBvMttOar3C4S+co~2Dt0>fb7oGU-13Nrv*|$mL(D~pByM6vM*<2Rc&ESeK;Ayu zVKT<8zZA>T5vU$P?fLpS9L!$bl%bHw32~2?;r>*AG@J4r1wO{T{0sS^v_YQQTKOquEhl!kHe|ru_;#A zm4rjCXL52YR@aQ)RG^R=ITE?aRE5Y&_Fzs&%&7U(U6+q_6)SZj72ybQzs4*Lvgc#S ztTBhnZQ>=a-h>#UXJ83NQ{~F;K%m1csh_mcayyF0s``;2MzL?caPt~hDTK&Js>HD7lc^4_5lR%woaeXOF#a%S2EQUz6vgT7=r zdQW+Swp2A^^gAUfAYofL$+CI;(_6@5&;9x4j9}XIrT;7_b zt@*FQA@HYE*&?uswM!h0B2f$t6%ddK!DfBO$odvpBAM*y(z%xjD1D^kOJqYhrZeMH z=M>}SA$=l!*2e6#v_q?OsG>Ys@CoN)7BI*v{YDk)`H^FU2^l=SX8dsS##~ctVwRk53^vpD{Ndq(CtGnmrl&owbj;Z9( z)tzAQZq8L;nh9V5djHJY{){@l7)I4Y4~s99k`N7Q;pk4%Nbt;PVFFn$(zZ)A23o87 z(Ym&c+4@>dESod}g;e%{1S0%x8z^OmtxqRvD7Hx!@=t|v&fqJ?)>OqYDYmQzV~=Oy zD=Iu*r}I$?Bzi;-lqhOJo?NJL1bVzFfEP8{G=K=hy0IN370v3@;QY{mQe<$#?<)34 zDisig{lZ;EMZML{W5JN3urZ-xAW8qDBskXG@3y(*jH-4L|ZKfrE~yI_^Q*k#^kGOVS0m_2%%!!6a}ua_YwZw-JuztkYG z#PUtCK_H_5y7mF~s4g-QRB^G^P*&G+w^odOt~8jsvhr#(7b zFSYi@S}+`A{o+kSzVJn-2LDrUcwbBAQdXyGsll@~dL9qfp1JqO8iEtNwCFy=o^TD^ z0b9N>2k3XDt4v${}2ls96X*Av&jRr|;(36)sYoFtr z3E@iRT?1Ky^Ww2n5;my*I&%4Iav|P~1P3+t5f<~yM;C(en_=*4ukQYZ~ zJKU0=YgVhexr(?kO2yQPgVp9DjVg{4MZ`g{0dX*l&NU|hmibax-4qAZy^2Yu0Hca} zOhTy{iOA!=nYmXj*2TUE=;uQYu3A1eo~~|AMN62|-f4QbOp5XA{q+4PLC@)I#=#bT zMZa~E{-G*Chg=H3+uYhx-%;tDAf24AGnJAApSV?>R2d4JEVwSbhQK273}Pw>^sYO% zj&9d34U879)^{g1dAy<#mAPVG*h*fr9U;F6GDX290>J~a(R{$g40b=@wwbKX#;3Z3 z0}CJ(*liKcpL1a?x8ai ze3_J7M99`U)_7$z`r9!b3Rdnx?Dx^o;0 zAgMX~=S*f~G3V<}&244enB*18yH(bU6z$ZVj>0E7LiT1!e1hB!x|sWwYPLt?%rfgQ zvgfiGSEWa>0QEDRL^cL?NLgChMan|T(}hrP9uOVtN!*aO135VAX3rJ}nF9VN^#W z(EIREflvz2Tud&Pg9K~I#Q#uI{&0P44*S7skYS15#;>#G2qEM2}$NVOc{5z6zL1o8K zgD5eSnQHG>mBXz%{xRH(_st$d8^^K$%APpEMcFnx3K?@)?psdZqMo@c#L8?G=#o(% zMi{#=!i+)k0BYwc$~@iPQP~VOM&;ECxs^wfFk@dw)>}b-CbtQyAfS~=;_Mr5?h*`{ zZ?9lM_5mexu;3vme5;T-rEm%bTfjHzWb_ehDmm{8?5*TJDC&xAV-p^7ez$HF1JwcR z%w0LE2E_<C&a2FL7pVzPBE{3K(j2BV`*VmC0jn>Pz+X0;@`W^GMAbxqZ75$~})mz0kmd(%V zOlgiA1)Uaj+AH}-oHYd#CE>H*71lJk=A5WvKt#iszz*5Zt2^5f3&$|^_}mBeqP<~e zi~ZV(Dj}R-I{9`qlw|`1ArN?OK zIxhG%76%>QCPJo|Ir8@j=IBDH3eQ|BSx}tpU7Xqn6RCt7T$XIJ5!lr36@cj{`{|0m z(eGEU6SScDL}L3F31ZxksbD-AM!wiII*+ta_uU3&3*$EGtIfH?f%Nim-7s!vP+wBf zOO_L;<9Cr<*h==jy(}sSKN}sh7CS2oN6j>&vMTEmIulS0;!2e-FLJ87g^$TsK!hKnIC83So zjF{nwOB_YF+KC~J9Fk4WNp1!vSAq#V{;tM#`=~mD5DLdv!lfO88m)fAP;L3^uCBt4 zoq=9q*%uiorm*K;^r^Tz3tB1+RKq5XUdLGcup6}#7Lw5rbAa$vhS6jg66#HVAB}6D z);n6>SiQ{I` zvQ~&Ay+8T!VUub<%sDn8!fYO9>*$hW9A)WPvMMip-~@!d2>3nLxEkD{VT4L=a3x9o zVg>>dpQC2!KGe9;BjbK5R;nVp(m?9Yv0}3<#Zu5@C9$rYiV}EFE)}JDqG=~?UXXTD zITa;~H#ojZM9F_<5>b|BqQuoCs|9W@$&UAl2N$$tz*kA;GMPKDmE`W=*FeeNfnrfX zemWSf!NXIQ4+(&K=Hvmg-;0&KO8Eu^B{AFU}_tM58Iz*`(#JKqQmE{>@|g*YRcub67tY-u34O1 zCp(LYic`Qz{F?=tKNU~$NVR%ZU`6F6WzF~yRtDZgk7>-#0k|2ZI^enBrBFLL6cYul zDm%Ll8!+U7y6=gv-e*M`Qfy1iEA%UJO3A@>s}lHy+Gc1AX;N#r z53*8fb3`kz?97<5ePkVciQBX&`YWnXPcgzWBTYwJFUgeKwDdRp8f8LPer)C!l6B0r zU2DDYW;)cSL0Rl_Begit)du?;x3+TktWpP}_RdoNkS^Iy_=R3YbceuL?S_3rr>%^K zkEx`v6YMPUXflv-6aO^Rp)Ut%MxZ$R&!6JsTC1@>^mcc*^XuM8Cs?c zRJ25UGoJo-_-em(RM)~t0yla;fvZg<5qIHa77M#%kPid*S|f1`W<%XE8L4O>WoP-m z`J8iG-rbzXKsvkMerxVLNpjqaNvNE}=@lmqvf?| z&5_dcu~6fh)b%W)4Qdk%7fl`>0BS>_g9llwRHdA@T-EHMR-6SxEn3yae3q^1t4ISV z==I+FlfB(md++rZ$y4mnzAVNvh1ZAvXa|Xem?6rcOxm)2`nR!Xn=<0@*V&MVnQ-PC zxkiKGNET}?^ZYKu5Bsy*=|oBX7!F4Xa7e^Sj!Bi1qO;#Vw{?6`A+mCRRPh0@E-N%( zIdhqJe?Mf?(R743#5BIOte@7~`uEJ*(b^ccxq|xnoc|Cayhk zn~Zt@OswihDAmhYbwK4Z%+6tHfVxv9koaJSXT>m@c>!P43&{n)-)l9E^zGS0-$^*TlPYr^Db-uzV9i>jf)zAi0*e1*M!78k) z5$mYA8ACp$jYJQGGzwNR>(s}nN5&>oS4mZddd3Y%SJ^ipNucH2QC!7(|7iE%?DbxUKk@%^DeKeDyAIJ1sPCir z@@gDxK7am`u0Xv%=r+Z#AfTVgK`I)3WScmx%Vo-ljC6dAuJuBQ^%fyz)CUl~aqOsY_AXv))Nc%64 z(K#nqLO^~fzK1IFs^EhXt0v>P8#P#Y>&NL>Y@~W-;$1daFe4CKe1k^KLO@_b3oi-~ zaqKj(EIoT>z_}MJOUA>pmvkpie)wmYe1I724G=iUa{bgGo;FO(SoRvM#T|)H6%I}Z z1HiI~2;erq(H;nc$2wm$Trx^w>O1X_fVtKOJ@}R$76FtSESkjFq3peMCUwLrd(tKs z<69&il>kr5pm#-o$CEzBkY=PU@{Q4zP~PpI2B*hw+8Ib?+abr=6Y# z$0x!5;roMq7!M{mX&trq_f8uDpUeHDx4;O`=svW-#TgU@17mBPJ;JmC-09>_X1qs>;2Q+gVz2b zsE+^x%p2_ex_8tLPT#c-4&3G9IL9B3_D*mO_Hu()djKhw6NKq#wXgRuk3u~-R#|~`L?%B!SA;JzQ zIX!!I+TL%UwfBOz$H%V;?WcPuzwYnuoxTVTj!y}3XQz7v%c@bL3$f`?Mu}PjzX1WVB za%tZ!$z1}|-C&kHW(wVK)I+CK7%f6Sg2RNFzVp3$7Ua@KJ*RGR)-{aoKHd4wDx+rp zvP7jO>CXdv2`#=^cUvd0i!DGa996tu|0YG2X6ORbP$fwaHTp5!gK6_>$NElu>vkO1 z#GE7`)fn1Np(zVjcLDoX5Oh*zX`_ivR6!;SHKI*94p{n)z8r-O^?DHmn>ajt2kbtZ zeyOX)SbJ8-n^Vun(wPh3njxkL-2~VB(Z{F{tLsOXe9iSDN^`+iqd2g@O>{`9ctC^U z`%X$DQSMX_CZ)`R&#B0`cv z(Lm|VfWm0>Y(iQFR|ypLU@)Nqxs=|Pjwa=fK$LS9Dm6V?aO@claKKkMQr-#nIai=A z>UxGy6m*57(K&4WqpDy8+6~nD4jH0h#02=Wj5*>Uk*HG<<*EYpmIH8s~E9(mGCNk z$7i03#MU_9$RTFSV(a~W#{sF-7oMu^+VtTqxoZu_W0>N*p1QkM>nvl?^V$FwRcEqY z@a-{+5jnMf?nAJqSy+-3#wlvf3~LrfJLB>d1u7W8>a24xEQsID&gTl@ z&JS=uxY5wR?f|cdOs}93P!<7Xa}jIlH{qxFW^!W)Q&A(2Y%Td1hJWy72P-Suvir?k zn_PC6ECn-MA%7^St=1R|;+e_VH$kq%iwa>2c!P46nVpqd4bh@*dTlThB9#a0n1z<5 z#C899mi5AoW$r{W*;2xbaNu6Ofa}RqNcUV!>fAIH!+(l;Sf%lpVs{y%u40&MCT%@{iGmZ}*P&PSCS~}zQH@}Ivu!=7`g}jUU`1t&0hYDG-)ddSH51`963fD*LRHUBMB2X9p7VZWd z#Y7Yc3UzxOHWpX~fryh10vqwPPR zZ@PWbaMJhD`nS=C&#fX{?2=_yE0UqLE0tciauoGUrPFR_@{*YxRKW~hOF3AiK5NF? zs8Id!c_9_IuDmFfXv@j7Rkteu`8Len^Ns4e2W=m3|6npV#+ienGASWncy}0cMLw5Y zkzes{&w+vhdhj5m5^dNYHfJG}2DX1pVxb8aLNE<1eZ0N#S0JCckWzt!yeOKTpzIId z%}QMj?DN+})_}|7vvKmB( z7B`~&*v`I5pZVB&x9m$e#P)|@7ImT}2z&pa0V59Ji+Ytk1q}U({K5 zNBj8;=K$G3Hl8n%C-CSv=$FSF8U)u@%^}>;e)&)58}sexSHL_PzWr5h%VEEdfj@ju zgR3xAJISlzBst_nf|ywDO8az~oSwJ_U#1KodksoKJBi4fP%NHg-l0fOivJOpz#zng zzvQ-PqCB06P5C7F*h@JDbfBT(bR;Ipd_!l?fQgO&rn}nhlbx+C{HmtLX#|*(U&;|+ zjAUK9VtrffNHai3n!xm77nu`ukujBHROQ_L3(21win~iANHNbU%rS_fUaBfU=fCUOV)~Tvuu6zLvY~fqP_v?#ag1V2L_x<@+g%V8=em{M24Q%CG;17?pK>PO} z?zvhN;ezmoYKv}QOW(qO{0s}vJn=083nBT}n;5ts*>Hc3?*%xCUfi zE0y}XGmzd=f>no-P>xX1t+fuoh34b+@l{aUPzn7*oyXrD=K7ELPikpW|05D{HZQ_@l>PIECRfW^aP z&kdA1p_J48CcKV1G)4`RJ~V>W5LDF7<_~peMY$P+8nkIpqKLYkjEZB~DQ1>}UbCy# zw6XINH-{}e14(0M#l4Q`B>oecY&`!rhD58<-xNQrQ%6V??VVMMRjf+qT!9;laCb3U ztbId}C_$HQ+qP}%wr#s_+s$fXHdUFMs$5iLos*xipOz7e zhnRBQF`l?Qgi%8V5Bg7gGVXQsjvrrM?ziCYckqvT_{MyEl7GJQhv%2Q8kR6SK5@~D z;=}!4>yB-+aGK0BjLaIQ+zUGUT$YC4yw}~`t$C&c&o zTo#a*4kgvI$InKhJoV^wJ}llCx+o4EmD+e{Fh^^&GG3cLPVKIIo?4x!_E)O4KG*+5 z>U_{1tW=wQzN5b!hz{)*Ng_rmsXKqG>*K;*J^sw_hPg#=pX7#_Sa@nx@?<11T?aT0 zm0tUOUWgF-K<#{_&s-2Si*HH2Y*KZu;<;B)X)EYFL>sf;60xa4;z6&P#%gy5MrBh) zdh)A05N2Y1N+<)~EU^NYD1~%$`;{ZNllnNBx7(YNxem{gy|?Hl<=bXP0;!0* zkn6YrH3Upd2An2018j^5JKdo*+pnO@kmaeVLApvMQ$C!3?x%_-LagiNU8;4h{lc4r zgRgw_RF1arc@JS!+iIa=Y9|6MWnsj#T_z{jXD4d-s+g^1?tfpJVk>&vsXbGGc&Xc` zF`Pv4=zG^?;dv``lvS?D!Gbz9ASTRP)^DROLt{;@PjG|C^>G*3TeONHKgZpkbZ&B* z;ozeA95qTxK5Sw*|GWe)RqYG!69uh3#vRx;Z#L}`b%uSo2*?b95Ol??!VATJ#ZyE4 zWLDVFfSyuZ&~s|GCWSGu>{??Ktl+F{2Zb$&8-c=6=-H;=a8A!1Pzl>qvS6JE!Yrb* zw`Q5K9WVxhUd~Iri~}hik^I%>NcG=}!U}(gz}Pf`qESC{bu*)|5`==(|G`U2z?>m4GBn=7$JHk3v9T%>o&xCaIAnbf9l8i*Xj`p+{;~LZ z&W<-P5SwL5;VsuhwNDb(!o}|TV8CUwf`%bepNIFhptA#{K#%h|HEsTaHBdrAN4**&2U1d=!l{gH?lL-!>aQg*G?+A&@qqmj&9pi!i%=| z;H0F^$TzwuJRc0>@4~uV@%?S>1)9CTH-+~K_1>|>i4u117*7ir<)*to=0g+UE1xD( zB*;^~8@1NlYsR0&gS&MG6f&iyt7&C-N!@PGWy}jKZ#KkybBR%(T^(VSU^&mViYmf4 z+k+h?m6O7wHUndVd68R%uJtPwKON}@&;S%OBXgdgzYWPZR znSS7>&g-MD7R3=lYx#w$YPfgDP5m)dOEk67+Q-2_^|)^(&Eh#!D{wr~y3fIeK)CzI z&GJ2}wcFTuAkeT5-M__CbH~sEVeOtZ>}iC1Y>xDm*?otGGySpb;xUw)aYwe zTo$i%$g8}2$aWlx$-X5@k|+0SJcScn@8xP1YzZ&hY`QBt)8}`NswAQ?6KmAJteBL> z-`kK`lY7|z91$(2>79OFy+CVunPuEyxlik&eV$r#&KXfwQ!E?}>`|{=hlma7p55$h zu!t`qo(wg-Zyc^=hC4_$zNNa(cs(4Tw;&f;d7GNH28diG;VG7YR5jWpB0oM{Yh7pL zbhmHOe0_<1Sbi6}c=mZ*OXoXe{VfKRbbc?I^K+@p`H5dtSU)Za*7mqndn_nfl+ZEB zCmnx(cbZkI-lG4BL;3suF~jQixi7y*JfHXb>anaeGD?N(70Hw~8+jQoA{F|wF-$VJOtMzhe9e5+}pNsw$W^u*mj(xgWgWQhdL&wvxI_sDyTh2x#rJ6UlqssA8iw8XcsXzf!>5t`jyHJiTn>g_Noy$c8 z@vlINhCN^LydMCY1)$3bjQ%VF{ajW3r)6h(`Szs!Gq*WRajm#fA3!>Zc%OC3_yL&U z$#phrb8M{5@81{MQ)<6=dh4j5$I__qo%pxKU_g^+P<~>?#cWPwB+NLs%?!Igj7dBw zc4v7oHxB%a705=te}N$lOQ`s+ba(loEN5i<-B4C^^*vtzmw-H(A_Rft2!GgFAT`V# zOhInOBLxJMETcRPTt{{=dgIQjE^2pYBmGc|CtsY$ zdFc3`;i7bOF*(4HkSC;{@p+#Up1TY%_)W?K9?}vBQL`uas9ri1P&GF^Piz>b$%yD+ z2d3suG2r2fFI`z-OrElK(=}6`DC+vl;ZTrjC8HG|rlso5!TaKAY+i&-ctK4`N`q=^*1jqb0p2Kz zaigT=(rlt+$!*s%G|V3-gcjZMm00jP4Cydx?A-@SA$miOCTpM^s9kZBSgX2Y?dG+Pq7&>N)ix8FShnejo_@*}tEymOR zK))}CvLjQ(5dBsSdpFw4BU^V^q0FFAs%cGv6Ir%K!&^+|sqfehATYOg2bpZ|=HO|+ z+W!pYbOka71cF2)I4#k8z(o0w2BHmCrRX>hBgU^hN~!9$ufnMjuxrkZXU*dg>~H+Q zBta*j5%7l(oA&@-ynn{};No@7I$pEK|Hr1>pH*@z*%DUd?$AAJN*WPMPq-?f1vgTk z@2W9c+a+2f)2sv)EHFwwMWgZ!grdflU3N=R}oH*;A*b@>6R zip}vN>i6n&F$GoCvryGjG5hYZ_5J}>liuN z?1vdiY@I6askxjszQFC^H?Dik!Zht_(^#q(c_P#S1W7XS?lR@e9hk-rUy@qMS8x+GI7t$yunWVHOe`c1U9M*Ko3ZYMm5oGvMHD z37o7f_oe;HmXxvty2L;33(e>5izN*06LxfwS|6Pe%fY&}NNNqr5QbPu+A{0%QRYOW z4OcVyp<_g~sdA_Qp**<206aJBcHWIm||J!yNPgm`!Xm}x>tfVXr*= zoRzPeqzTQAt|LvhZR1ru{JGw|T;wg6!{=((Z%eugu~6W6|E;vXOf?WGjjdFM8_i*y zNR;*Hf6txP%Sn{`Yt_h6UYb$FHq4K)HyCTr74N#VBVGB+=vp$%M*XK0a>%kSm=DKii##s}&pnN&1BRM&%&x0iyW^XiS|fom!Z;>+`Tra!^SkY>&p;^T#eoRs zUq_48QRq587TD^d4tI;oE225UI?%$lQ>b8};90mHF$PW3P~YdUh_uLyg$sYf0DsEy zX{aI`ThjY)so)qVMG2IZA~!dkBQwjpT;5qJa@R^dYKZlJN}c_|9@cySlzGq@$Jjrv z2dWfHPgT=bq0Hp8z`gk@6Ase6_NvO8_lwRN?D=0vIA zX_1*Gs*0Umiks}Ky#)BteNK#}XyEdvM+PC&CLS_^>(P6^f9vLey%%Ttz%LWZ6U+!P zK51h`p#}bg*@<2{V#1hZjY>FR|Ih)gAZJCQ)pHuZI64VE^RiA&a7R8JHSBu&b>7z} zjdRUqi3}Om?_}_X^{r!SzwB**j4cCdB$>0b1{4Ilh^h z`fxc(#{5e?Z)1Ss9lIHm?o=#j?q7N5;a!Q&>jFJ|pW~(8Cq$Dxz~@WuM%swMw*S`Q zRA+vEE&5X{Syzpn85|)70Dq3!UoP~n%2L49=Omquuu|W zT6fXIfu(>)j?-1zEm%BwMn@Vqx7YIoCjV4HY2DV~y_a;jy)57#2=`_t?-EMEX5@nU ziNWjE<5tBVPvFr*9j9OBIP#`O!(l(F%+Di=p(jd(T7i`G{eQXy8h6Hr& znHDRl)+0#u(^QOItVNAin6bM*J9@V=2=$++=i*H;k(_F_o*e=yu}e^JYqFS`=QCzv zSW+vbOdl%*c5EFq6s%ps7Kfa?yYY3Y_7y#J$F5~mlfI092zpz!%eB#Lqcf>93xnQ$ zdl90u3@9hg?4UD|F)Kf{JnecBx-bk(4V`YI^exWNA&dE!d&?jB<4inC`dUf<1GLzCOZQbQ#4SpOdqPu9FV@;>d@Bk{P+|8}Do;IP%^N1-=dJBx-p_GKgJ=uRTZ}s5~mx1k-Ko>;7e-&s{h5 zU&N70=rlXZC3Gd~Eo6Mq`)9uMYxHsGh%Q_-J7${wzZ9*A#>onr{n5%>La*fv3?g20NZ3++0qSfvxbfG-2nsGf6z*cFWM=`m*IU*wK$YLw@gd(sfx? z8$3RrgAJuX1udJixE=H+G9W0;9TpLRj2^+d;1R{!j`HRu8*W*;nGumo1|RJNAs?6c z_N7eu@DU`Q=FAc=Ti)cW>?VhFW*}8~l(vhO_W9@b<}u+j-K0?N{z6B`+X-3di!@N9 z{Tq#B)qR$^qw?b`-)O7MrCtv1+dEy{9J0%uA;(k`4fnF3Tv4D6NEr;PYwVd+cB4xT z(w)jXr|@D(Vug9ONGcN^iv2qyXI3U4&Zx08Spa%9Jx0OkJ+R;hsWwV zq4xU-`XWj)aW_&y5Ea#QPa<>1;hNm;n@>5@1-a|n$k4Oh4PSQpMc2xC?k8jfq&}GX9t6Cz3@t*-aEbA@aqwj zQay@8a%5e8N_B7%tFJukwJ_GJ3QFD0XhzrXD6E3o43^J|r_2&F__?tD_r9RQ+A_VX zBO5bOUolg?;&#i^QU0=!QhF|vROV^ED+jFvOzT(TFM7(Qutr{ApO4kOpj1vi-7o}3Q2FCp|5GbfLoR62`kK&LuNgHKy5K~Kti)(~u;^SDOt z_00QTsq}PRQ*#*4@CZuPo^6$?^-B$~Snz@_;>qFtd=NSvSdL;dDG&N6ABHu#kU~9r zV_-mrqrxHP5Y|Zn(-Jq6(|Fo%JYPF?0IT9$5VYAfpx)?FR&2&r!+Wsc#rUmGH$sLA z0)LoPAb*Y`RQn$!W0=Qh(sX>hEr&M$`u~<&V1?v=zW6j`W**fsrs(=JQ4A?orCQ`9 zyYe2$i@qhwbXpZ}lpB9$W*;SxJ~$h_X09i#0bY-4bO9(5dA&sabnsRwbf4yGX?WXN zEXXOE;aztjPtjD!{JwBOB==31w#cJfhKu0jsOp5k1W1?}BZ}D8k zSQ)IkYOjs{*2WV$)3dwS2Pu@Ip;5nqJqW(bJ?WVFdI$yGA4hv%s}-*>tyFmZ>dXMV zm{i{bu&BI}&ckVG<$Ni3`M&iz(wN62+-XeN(wH#@qehzQ^k8*(b&$4|!|Mh8o)FW?W6Fq3Jkr{nIfZ>izkWle2lC*Hy>w>%mr@Wma)X z%^2{8Qc~t9T4BY2E!#i7h96D@te7xF(B~+y91FC4b%VTc^f97!ZTUTMTkULJ-!H6f zv14ldpWt`c0(LC{Xh#wNV~u|3Fmyy*1lVw3;_YnlK+*5WF{to%8E%nh@M8rihOYn> z=!)<`;!#Wi4i`*)=fNL4yTZYWiKCH91Rl*W;M{MiKWiezb&42r)3Ixr-~iWqV8!;1 zWFTyTh;*ICDEbww3kb4cLJBB~&TN1_>)IY$UV%_Iep8CAPgc4Tih4jdwft1EW#=p0 zzb}5fsPfz_WsI74G3u<hksCj+ZRl|N;V*vTRypq1>&4)q=mMmt3fB9CI^iqM8 z``)x}8879CK7nI#`}=~n)_NeW<0TkEbBEaX6l#Gl>+rxlOo2dWMfVs&M|=Z*O=S8i zIucHS5HLY(;t}ByyrA|pMPOl7V~4JWWnrzeP^N!5piC55Uxt{8+ISa466a1^DZO$5 z_@U=aJdgQUj}xgm0Gae{cBlz=V?!RRKeFbc5Jj-`3g{h?u6 z(QwU(6f2Oj{1jT|bAzRahdv%WOOgee)?%@FD%DZ6W5*!OxH^Q!-h9Ni>biLJj7=6X z*tH7~NR7ep6c`{*n_S{wVQZ;^5uM4P7PvS+2(OnRHX_uyac&{5$Cm(mFi6>!GaCsh ztM;h^d4l6YJOhMz@W6%w6dcSXs-vH`f9d9bJ!+2Oc5ExjUvIQ78XNL5T((;0Q8#@a9;$Z;x=B8tqteBb>*3xCuBu0tequQu`7 zZJI{W!#l2MmOr9#4R17IC4YY>roklhtTyp&w_cB(n0t$uB8$wseD70U{|R<0=+&6qj9r-$Nv6fZPExt9DU$my z{<^|Pcuni9fju4?0OGI|4mqxrD~zJeMNv(qJ$$Ybt^CMYp%_-5 z_{aa5JD-zxw$Z}7-U~pjSbPmeCeHN};8qzS232~<{tCEPMWMP$j~Y!JAD|l(WS%B+ zDE<(*m!p8>ETBmZ#1p{v1OYHYmo9k=58#@^sD)`YBB&bN_XkgKrST7nloWhzw1=dW z%CaKOG54TX9ROp=d48lP(-$QHCYI7qjsO6Wz%{I~1lDZLXJLa+qlfLYd(A-3q0fJ4 zvH;{YM}5E9+0_GraxhE^nE*8Hcdip~prhsLWo+jRv)?cV(As-mk- zpLm0@$12Df`YkIXeRFnPs~%lDaN1vB8xD`}88+R7W0q`~+fn-y6?V06pBx0oR`Ht8 zcY65|_iD3()Xd+RK;8?ckBsM(99JagL#FOLIk61E_H!grw_6l4WChm3JDDTEEXMh3 zOiAQmvDLRdV}_9hnWFfuZ4Y`+g-QG;j+uU4>HqR;8?0 zn3rNeJ(2#{jpQSGe1AeqU<2^&06buq_oMSu-4AvjgQ0cRg+XjHc1+>*(aP`EqJyPG zO}TppR;WC;_%ZQhkvt=cT|th>FvS23z(;Gr8N3I@q+b1YumJJR>>0a7_7TUS=Jggu z+6Ccpe*H5Q7k$mBWsE=_Z~sS$iS6TVZsX1sXvfHn57(N{M>TFI7q4rVi+y+|R5y=# z&Sej$1%6KEKEp(n(rKr3PgArv^z--Kx8cnr3p+35vmf;JfGb0v#6@XWkIOnl>Yq#e zE-MfQFg#hDL){-+e0Z;qRsp3G53ms%|ADRqE%v^v<%+L>uV)*Uql~X-MA?_)*Fae- z0GP(E9uMbPPRQ+?tWG}OEofV>nisj>Uil-$qbB2j6q)$K?_pgYfz><%%r932@MK2+ z{c!&=v}uW&VDh;FLINBGcK^fS)zi`e?*puTs>uFUML3Xs{k6kv3eE>s^Dm^TrFMAT z@v@ifDm&{Em*~;FP!g5Ub{e5nU8CKvM-T%HNDPas@*r9cZr03ZBzwSvFhuC`z6=HSvl1MLZ6j%=xh`kU7B zvvBq1=)t>{E!T4L-$QSS7V>)N)-q>5G7BetOWRD}%0;$Ij8Xw3@+52a+>eo~Nj#Tc z#Z4cj+df+Rq{mfIdhr`y1#c+*iGR|tGH`MGD#$pBCFx9UHNfOo zKyuP!fZ~J&9%%j~ zLmoqT!R9TmJj20V-_itCN-n@^SFBk!rA1}DU!Fbb{*o+91W&usX_p0(?QyQKXfQ#Kwyt6Ri$ zp6!nN~9^3E0Cxs^-7`$;+MswVZN}#@Zpf_&POM3{iP1km_#tQ1}(n z{3c~E_<2MD$snO*C;WpH%jIIq3%~%Kz)!h44LR!CvB1BWkUad$FG=t>P+JK}M;J*s zJXLX_cYDWvH-^L4c7Ko&dgg@pQjSlCftM(sddF32D=^?zv6*3Mfl2=r2VNKOgd}>% zHu2-`&(%b2%siHuBumO8;W0)Za4w@KjtIZPEg!gju-0A_x$C^H$3|ceb4RZL8B(X= zsF}2#3CqS6>UXc5%%?-RzXW~Qd|x>ON!P!TnivbZnMxZ_y&Cd&f}3Zz_(xzO>fFoH z7QRB7mNKf7`s8g z#OF$c%#|GP0fVT2{nxG@G46d)6UuaDpDq)8X5D;$n}WHzZ!tA9h|kIV^(=-jMH(pa z=LEX|uM;M_hrkt1D0&8Xl8F~}4A4w5KnaTjmbVNepF3LnlvHQ0mFRPEBcNK=19bAC6ULplP4Y zXBYrseC@T{6y zG{hK0=1EY+0dVQ_C>ZlaFvf7@a7cK2ClowFl)1Dqe!qDhP^=1N6uf=`e9TkoF-&gQ z=4L3kVf_8i4J`KVR=rr+={Mil6q?*etW^&zO~9BE-L^*Pbb{;$mUErmC zISVtZYoipSwvd&TZqbp<7SS)NkyZU4elR#}W;PEfEMxpC&D}7CFI+&flM)W8u;ajVHf>> zO^43#eGcDjuLEu+I+qlImsDl^=IO0z#ka2$mC3N@Ylo$&Gbuv zbUV;6fW_bLD*Y^&M?yCQcHWTOqjD3l%sdPz9q11+~6iDpF7w=Tvoj{-zmtK(y{1V7yRUL**iqHoF zu_lxch~ju%0A#^dJpxxpNTfynS)53jDc2Y~=oXNWaucni>r6XP%fUhq=&~K}OgqS< zlERTo5i1`!>CD$XYncf)aGleos&V8fT2$ejiE1xrLE@F3l!eJSA_1W!pdKCN*99tK z3V~dI!UMB;qbNK2k0@}oml#i1H>C`p3!dQP`#9}?6ks}!h=<_;kZ$4KN=j)RTzwkW zG_7v|Ze@UaX4wQ-u9q8NP7+QQ{|z2@JRW}bkni!7_pLs`@a+R8KIlEqi@b3o$V&j{ zPQVEqGWU1A-~#1t5WvUP@+GEGZ(sIdSpq`E7}AB(FFO`P8I2Ck>3j3Zv(-Bl0YCqJ z^9=DK_{O7PG`#@ZDg;v_QF!_!e$QVN0Xkv~Wv(u$LtMU-*wBdz)vH5Vje}3VwT@Hf zJaaC92n5z+luQ^bb5~%yCS+6f_W>h3Kkl7`DlH4L zjMdjERU>4L1a1+#gko^T1Zcq8q5YR)Xpff-e1`~X?uZRL52(Y+`;Fh;&1^bSC4mid zPQ}~)$X^WyKIk}qE6W$u8oYrF<^0Wo(3f#cFayEHEBJqSYGy zF4S)o-U7+fpN>p!ve9Myx{Yh$)fJ)YYepg*TVGawTb+dMY;9Is z>4FM*6oka2aov=wBAQ=;l3HFX5)uL@&qvr%qR1k9mN3J*KaOh@oY2o`ZfbYy)e~Qg zE5%s%#{zr1b|Y(3`kkc0ztA3Wo`WSXyAwq-PAp5BOXj`rbiJcDOiaZjFDnnPu%}`# zIpsnmolkDm_bZ922bAkw1 z-VnW%v3?qB(w?u=B0*~+V0iD!) zU)2UDz|}7DxDwvhqfe#q66tgYS*(mZbBx*d51wODw@oomx&iS7gppFTgOSAOS+S~C|DN*`Z$Hi>E;>q8tfQo z^xP8g22_(hL51O{B6sKf)@O*ItmxE>tooXF0;nDsvM?AEDc)Y*VOS65jcU1lE6tWCo4fJP@Ig%b8n>)i7c~|`CUtI@Q5IN$ zxoS~onD!QwwV2UX{aY$C0%cF$sz^G<_p*!wMq4)R2bxnR)il`yk*AK^HZ-E)tfu|KR#i!RgVuQe-LGyFz~w6^*d<%$52%MQn*>?HatkPW#k`5vQ~BK>s$9gX zYT$6-%_@QlaCKJA$!I{TP1kW6u>qV|DNHCW_DuOA=`rd!$_cMhbe6cy9H#P{p~pB@ z^`MO7NU+x{hmk1f@6|(9SVhN;!j#+pW5rBHUyJ3DIuGm~E(__pIl86#3eq76P%Uo3 zx=d52a_UGMt~AkMr=6k-CIq|SQvfYtYC|5&YBKf2^~xfj)#ns@ot@ui1VUaMA)%+c zz5=NY$7s=~u^w;NT}A|l*|uaEIfQ|5y$Rhh)eDARz)6u&*3 zslDye&Hd`6l&eWnknJadp1Zxbp&wg}X_*3a|{{bbh~JrxU91}t+9L%W_R)H?8L#5jqBamlJWfFCsKD)19<7; z`Fs*#j7jis`cr&oDUF(HAoK~tq62L0+>aBqlB8+-#^q7sNNYla;;(bEYmKCwl!DF+ zjFDLt)gDTec2s7#yGdB(P~qVg&d#*yxoNFV@+OeMvqd~@+=5L`Bao~AVo@0-f6{Cq z7!98V`gLj3rL=fg5(Npg1L^#zlLm~;VQ`fwOq;cwvaW(3cA5Y5te6eg&C84P3ujJB zPhHua7mibb2m^;jtNLrkf>m7Q6s9X$^|7NT(UBQ5>{J8b9u2Jf9mL;-o(8dBBw>oG zijfL2b<3ME(I`eOs_+s|uJIA0(a@xrO1C5Yrd#gaCHESxX1`jICiRttquuQ~OHA5+ z0XrR$1-NQxs?@AQi5fN*pNwRQM}w3kI6(Q#5CK%vBuEnupbgmgP;BfK;*sc{7D3D+ z!C(iRU+8FmrQ3ZUoQWVUXMe^u2?B$I)b(qh;-zHfuX;+TkSR&@1XOxPsK0bmi*6K7 z-R2F7wM-vyjZDVaiK_5&xY9!3M@{(MzoFIa@RaznW&e!4IhrMycF5~Tc11j#XRXoG z0{^J~D(=KXpW-pL#H4*wYTqe1W=JE;1)W1YF~E%kMs<=*eGE(+!#v0u7vDV;?zTuV z0Qp=zE#OjFx3l}#|w68u&_&L6(%IFHl>!y zy7f$TmNpEb%{+TJk7$W-T_#6;9g%B>CK~T^?SM<}Xb7KC++kMJ90P;3S!Qm1!;;Y+ z{lj97Q4iJOao(#wDL}oeR+;SaP;nh}WsXTpJY+jycG-ENO9>T=a z0hVKGw5<=Thbki_M@7Ms=ABsAXGKclYW>2|Mj!J1KDF}2*)|5y^joHdNIU~kl$8M@ zVU)uMMisvG^*~k%Tdtq5hOI|8$Y+=Z8e?ZXnF#E`P8}a(+pAyl=YAH0QNi{uPtU-H zW8R^Q-~LP&HscSSYjxgcuq%Nj@O=#ZHi?9{&Lx+^?>{IE6ycB~vq?xKpzUJ`Yg2*- z5q(C`vyy7eWvTx8MymEf;lfvNiZZi3$ZjTa1VoE#T9)z?OQ@Uj+wBM9C&F)|=7k~8 zG>=+#1E*q(0e2y6W;BVUR!2efJrg_7sB7d}`TatOgmf?wpMVDpbEjR8U69)OXWGtj z_X)k5`1Vu)_8?*HxWZ__MA{!~S6N&1iF=uZ$9V&v0#cutF3S#Yb~rj#tw(F z#8pki zT%KWKHF%g?HDEnH<41z=gsNu9#0{PPQV92CXOii-?cpE_TbYFei{Nlx*L`$lXF?<= zOunW=D|eIqR4vYs@2B0}Sw}=PbxW#GN|y!4X#{Aa9hGYV>^u-EFC8LVV=nMG_i!v7 za|P5eBX3oHQ09f88xcXe!rz6?^|pyVu7+>~q$4$0KqRC_}b{HIJYt?4ip z?Vve3NVb!^Baj4of21|zr%+IE!$xz842cJ3b^br=fN{v2c&e%HCIooXOCf#WY9wjy zXwV-AyUE)p6deH9tNbT?d5~_WcHqv=m+H1~LhQjJ#$fPFF^TiaLUodD)$`D5AVd=( zd+VW0+%j|I2qp8mqDe%jYtdUzDxDZt@(ansOJ?#-?EZ?YGQ3x+1bCu*zS=9ki7ij? zB$nn8A?_FFJ6B9%y%g711Hn;#znXe1oJY`{$s4{jkcYhTxYVFyqRWyw*=N^k#a*9D z0sFSZG~a9sM2FP@ME*tF<1&=d0hGSPFVOJ;reZS^QIvm- z&Hx>VkVQJKBJuL(f^8r5!F=fS2-WDB8C~TfkXkz`R3SYUMqgVfd=ruZ?hv#J+r;t9 zuxWPjSv5~4TNhsrGK1sxaU0>S$?^tH*~7ci{k8r}S(Da&yy5JR*TfKkKa*>`DW!qg zoX_d_ggmX;sk5MKElAvRWncrGvo?FIl=@KbwJQ`5*3|K^(uDQe<~Vu4nP+j4Y(@`zF-Qt1x6c*7WRfIKG2yH$nIxUupd%Jd9Mn*;j^pnC#V zd?E>`|NOYYFe2`m50bDAEvX}?$cPSemE1thz{tcQG27-BGaD%>xRiP^arPzQ4!ec6 zvTx*D_A)W*RfWlRIuWkb&L5j?C=+c(3ip1SL?_1cqCB*!00L#rz-g(b!R#j!D95_O0Lr#!|<8qsSxI{tA_ZQRi2-VW*TE&Vt+(v}yo+1QizhL_!c9P%zV!YuZ zs_s_fN-Pehl(|-chC4IJABHuA>)7*z(h}^*hb){Lch*z}<#^ zeZH<1Y@!PoD&0jfbbTAXWJed?XL319%g9yK4NnG-+-_F)6@*oU(WLRtG+OZVI=`mWnZfL(Rerker+0Qb=FcP!|tO^r~O7U>iWz!fQq05V@BLEs@WEw;Ei_BN2;tv`N!2NM!jlh0B zdivNvqgd!*s9yEurLKO1u)i<{83?E~+wl5y0a^jmuq8mPkqL7Uu>>WrFbLyfS7#xJ zu+^n^&r$vnVdmH56rSN40DdSsOZE@{zu36j95xT~RH?*%6vh=Q-C`|x+5U~^2+WQw zg_Z{NWDLplUMCMe9x|Knl=#bEN6guaSDBLFg~fmr`YN8*;^H@f>2WX)YD~lNYwT0q zj47JmCAJ4Sk--6ErSj(&wG8rNx2A*b^*RVnZac+$kn;;3!&73qFe~7T%ttn*qlpH3 zG1~n5K!2kF@+6|{5*0d{WJ1q{^G=K+0=?&HUhiR-9QIhPniZK!FWD46RpPTVVO+|A zy=0>gI6Bfb0>wIA^hu+Ui_*pXv3NqVCjl|c*9A@hj(1LEjm6&_(}B4X%hY->w@0?W zwtb?gVe*bBtr84?2pAVGRYu9;k|{Fbi#f%;)t_HUO)7LJXCQq)+%>pD^i^`uUNjqo z^B=oNMo$hjg~D&2WD;dUpi4liFI0sT7y3(57PuK|Ye)hp)=jRZmQ$CW-Sb}nLqNR0 z*^dU7RF`7=Ww80&=1hTsvUw2?dL1$&0)MG3Z`YQIC-QQ(wgy8o8vJPzqDEF)xFEd5 zh|saMP5LoRDAj5^(5ZJMgg=vONhn%bVVX{Ss@;;d*~*+kws|}xI)TZvXYAXi)GhNo zb}qqo=IU?g*BMFCVeoe&uHho6#7P79B~;{N%Xa1%1 zs5m_e4ck%hVF&AyXWDq_j&9*vki6m!QYVwv*&xRIHSE&{siye^Inde-H8^Rv_ca)~ z(~sgtG*&!f`&W7TdLet_VIzidumN_>lcWdvXHZku8}U&yqe_h@S+0ZJE#NMn)3p>V zb;;ea7k2hdxv<1d!sMFhC0sm%3?1v}unb~kGQ@mWz`G{He#9n`i#Y1{976-9n(B;$ zBV}f2W8?R5K;ut&^Md(p`)Eh$;i-~sIy6y(&lP_#f&*SEqJ~M{2!1~~5}3$XWsjp} z3#%;;U^-YcH7G|_MCta}r`l(d;0fB?!DSxwrKQ|Ci5?stK`mEYXp4A-LPlBo6;!QYJ1m! z*fdYVzCJpig~SX9ZO(9Kr!rCL5o?rJ@=m3F3~Dq}eI0c8+Vk~uQ0RMg{m_lT8D*Td z+rbBe*>E-!fTl}AsY);Pp5zaq(i)Y{UOtOOX)?#J>Ww%Msfz`^mqGS4tBH#5dHaSJ z@nzbruJ7}@ehWj~OS=5}mabSOsFuu>Y(AE%LX@sA#PT9zaaQ0%t`kuPTv=nU{qFen zT21_)RKbF#^iV2JfM|6X{X3C-aD8dyiDMGp|xfUIwf zq8ltY<>ugsMTh!T`6mrW_&jH%H!*jLJj4RLa>I4BCNXSkMrSOXG6*}>DCv>9B__#@wGW?^Z` znkp}GhC#CT+cUi47&(R}aP(PaSQ1|`XU;RLLQy>M$*%IaFyiE?l<`-jl$c%}tvUGN zSF*9lYb7W9j0}hyhDi%&d4!Qm3@l3I!3@%nS^3S-X726Gr7u_S%R&;hx;i)c z6h}d|7+3~BWOi@?9lhfn20leAHA7Ea(W5(*0s#LN5GouBrIx-f7w66UvB>N>>3m7O z%xM9As%$I?SON0Y@;^pZniSz(&DxIGF7DWJ10Y%eC`L(%Teiqs@n(uaYeV|rkK!lyrqG}1U z8kv5qpJb6G!tIMfM3rEKXL-Ie4KpAyU7);W;|IPQ(|09UlEuL<4x}P;onrX`(A>~S z2Nd}+!E1Zh4pRLLsoT1c4XLxlV5r%*w(z9d7y;5JLPTcsCLT5Eo0UM?AJ2ESkHy%W{yKGl8J*%byr z=Bwo9Wy(I_3(iNp@3>Nh8bv1}!9p9YZA2x8>X^)z;yK37H9S1bHk!bRzaYrQ>IV^M(t*4XWu#e?3`A3!u(Fa*rpCR>IOA5 zkXFJ?3}_J*nhS}^#8{b;=z|0OJENoE`Y?~gMf=Z?6;AM@6S+&mo&uOm`y&Z9gHQd3n#nhUFnVzC+;}H z!n*^UMWs%sq6z4j&ji_XaAPGpQ_xviSSVaDN=@qgldIGiJtdqz43^JfPnLOE?DRR1 zGVH5zf2>mB_0{&uDlqg+Ib!7xLmDeqmOegG^@uyQ;9qpkk(7}_8d8!f88lRlw2u0w zPHjC$0D#_+M@Rhpx4-GL!=uajk*Rd#i_=!rEEsAf7+q1JPWVr!Mt}eDr}Xtqet?tn zueno*zgg$-U#=)(XHHNtKimap2C+;!$mB{2tprM?xFBk;!E&+Wj}V%Io+;O^YJ~q_ z?x25_&Uo{h5d?=Y1fZQ@d7*#JgEPy;Z03;5N6}vpECTNs zdf0kuufl%zes9n{MOC(b#|;`M%k&YiAN>u8uT@5S3p{K`M%#I5#(}@9jsUmKjtpS+ zuiE_};wu;XZ(9ItR|eo)qOK7P7TwGZwVFo~=EJE@~76bdJv@_NJL&K-ncb=!SD$t*V}@N{3TT)!3QxM_FP z{SY?Yt;{fFcB~*|A>VMj&FPLQPpK&l$rMP@b3jlRsQXaH7Y;9*q^7_oD!iZ%E=e>Z zS7bzrc08}shIp5-Yeb?-bd&F}b{k%DYn$l3OC9afNiJsevUX6z=T;ixlt}$Ng59m_ zcrBX_QD=;$0tTO6J_{qtb>X-&i{p5vwwA&luwscC4%#brT3XQoSID5GQyi&aIZadO z9!xZBD4Tk!sLIeuu;+tMqsyt+U_l6QeL~%Yb6j@tl1*p{ z*E0%YM5J2cx$~7^L}!AVTX&gIjsrh1EcCU zdJr-fbcMstg4`nX(ney!J2CF%6&6|SU0x(qFps8!9FJVSe( z(`ByDahM6;*Tx&p zRgKLN-`ZkbS-`S@m8BI%VqQJ}0}9 z{RLa8%wyK6L^73*NTXm+db*kskXxL%GiF$dw1WJK!UrXXQT)K1VMzh-y2hMa;{aw$v5>SRYJM`f6Iv#R_CRsOVT9aMd zgBN*=?{L`Pq_4_SOV|*562vH214I~$SA#|XQ;Z@r0H(iEWwcxT4k0G|Xh1@l$b9Jr zDae}&1vu_Y2N(?J!+>QRKL1NL*#&ivH*EwcgpaY7yg|6hGg!F&u)cJ1j%bE)zYR7R z@#?Eh`YKDphBssk2sCj#zGF}MEGvQA?V2_LEq?GH1PhH-6oEMIP949t7qLo zzA<<+ZXt!WaLq2((rj)pg~&r=>BwZq)wxX)iyZ7EF6Jd#A&7hqRoOv;!RVS~U&v9H zgb}ImRl9HpCM=|IR+cIwIv*&hYuVRIVOSn&6xm0k)Hs%F5A?q{Gnwxqs3}>sMmay6ViQJM%gwyXoRKOId2qGhxOq`>=kbT@s?caz#tk>(9y(-Tit2)Fmp1UB5V(~k8xBCjmDs`oRz<Er>4I7!&U>IxbGA9{o1&%PKdPgQW3ZLZe>M-W6ToI8@Yz$aUoyME%hrVn9_md zJ@oTGnQ5`SS9xO7^s~-h#(Fhg$^_sH530m?_XfQ$kL@a;%9(Dal~?$^5SGh-p}@=E zJW0Dx{PwTGZ-ug_@LRjndae&ArsFT~&3Yp^mSwC@=6M*#8x4B1&Rs6?UQA=V87|~y zy51b!aWg#W8klJ9bQLg%-Ju7FjA6_ZPrpBrmdY$x>Rsl5Ew(?I#L@2v#{ zoEYLSCjCCA7&XQQ`n{vqV48GlR`Vx*Ok-#Bh~AScXNOw|Z_ed9eIO2PQ|Fh(o;?N0 zb4=Qz^WeYWplofuQ695o`(ER7QMb(H*yuf%Da^OKFITR)04Xtevl5lT$Q~M9h7L_3I>0DXBdvLg_YH3p=^J-aEEQw3xX!C6fPNdMporeCB(Th`B zf%)Ni7{;UW;5Vx~L9K%TL+Xra2*p8)vx|b}OBY52xarL}v_uCMjXd|jI&QFF;v7>_hLrPfnyh&ymV>-zL4>h}qnM%eic(Cd{P`q_m^M;UgJ&*}sc}2a zkZeUOMeYbCUQR*LX`~a5Wstsx_59W}-t~P_WZuQ# zkC%Ql6?NFOI%v=}Qc{ZkSY9qzb1L}zGl*b$1#LDIDrmq>E)Qk~%!V7XKDTTSuiSVY zSA9F3cm>I4!!lLof88v-`f@B;Za`BD=%97e{5`+5?LdpHUKU8NC{_eqV82V zh|?PmgiFWltsI&tK$H1dKfG~%?vQp!@@P`w2pk%p448X%fwxJO%WiUWPB}Zx^-CH9 zW#q%LaHOU(SL!xl~f#8joyJ=?&50ALUoBGWxpO-CwksllT&h`G5j%K-sx8PyOf z^ARg7<7_)ijD2Tsk%UzeUhDhqYn3pyL49G29s76u?uFG=?kLn}x82#Zy|%X0#_t5` zFBKE^Y7+N*2TUgk8p(;SdQ$jPCEbg$4%2{()slLyiAu8f?y+Hd%YM7#RejgsBzep^ z8o@`bRv5 zD!C#ZXZxJj&S4Ho5pjNJtqPNG?y56w4QG`$fNGgz;n81+sZ;Wn{WN{<5ZU^p2UX+90#x6j~I2W!t%TE8r`R_zB1eh2kJ0ct}{ z&h&bzF6F_C6+mC8R;5j1Q&y4I9Np+uW+0 zgsvFog~HA4B#_BP=9VhUWWSMTN1HxwxV_o1y6Bb;le9UVudTz=rX6)PfTa#OSj_%& zdTm;Yl1m;_VAML4QK1Ys_z$A?vbKJ*{Z;NGrFAP7rbt!aSMJNBP^;J$I*9iUkQ!NR zkvX*ncex*$?3q-`Hf=e_W}zam^-QTZ7)zI1Y>h5cp26BxpCzDRXI;qxM^iR~EP5QH zK;}o;eC$s5n%R3RTM6%zR{vA=>^@R7W8Z9g@9=h;*E|-=J)eQrbBstN;v5qm>lqoy zx%L)F5UMg)c3*;8eq5o82Qj`Gw9UoX^)`6Z+<2r&%nOiQzJqE!Nd!Ht0%fzR~y1!*__5&@d zg$<(j;;wL%(UhT4#TC-k!XDIC?z7>Rn9Dah^$Ph&+<(QRZ5CMHbK6YTiQG$0lEHl&66eMK8i8?+jRIlHViC<~&AuPtO_5@}Ay*b;EqAZTkzi%a=bYe5rdgp$b!f^)3uT4Dm%TrZsLMSPJCeb$ov=M&&r=mTY^zSf^xu-+ZxDZ>2 zf@zQZ(~%KN{I7uECF%F7y{Xm+V|%GrdnnmWYI*>gT2d_VHys8sLyuDITEimLXRXQd zM{=_M@*u?G$Zt_xG280R&2?*pQ*My4ihr)^n%vJ!s&R^DfQ7?c=XZ8GYl+v^L)?Uz zA$>`5-E!@1uA-i)9&UqDM&W)bjI!J2tX{c)UO}xaehOVYfy$Bz=U9v{*3lqKBn2n? z^w(XA7Q@QrX>c17F`Z*$iD^UkOc_Qq*o2o*!{$hq-~asldA%vk^rX7L8dqXlRj!QR zSakBK{$f>ml4@@b%weuZ;mY)O2VFZl6lF~TiZ9O!v~yF#?JE4p5pbHbd{Yx(QEG6; zRy;Q+S9vpN&YdR#uH^J0mZuJWNYh{}`-i_zUId!%u5>bst-0r#QB%gkY^E@ce{nNi z1v$sWlva-{jQs^P4E}q0#|fw%wNeAUpM~Wc)(+?Lu(Yas3M12t%keSpZ#asi+t-Zj zhJ!-Y>h;fV?5M@(J{2)9E4BT(lno>s23Ir0s8BTtbF~aZ!}7z5q1B@1Ws{#km+bJ@ z8kat(<8hhF3_)8@O{%{$zv^X8mY*jx zf!QVt_l>|HJBw>uk~0gfQ(>J5%jJe6t}S{aFaPAw(TkkB2VG2zEgf#fk5Q1u;|W{( zGF1V5^Z5|#6C^9SE$0c_aN9RoMhl(xPTHOJyH>l#Hl)3Oyx-eZ->7I&Il6WQ7K8@( z?3}_Zm1tj()u2qk)Vx{43sBxr#O|9>a#Jp8miAHR%9Y^8W!Z(yx>nCCJ>_%oC`R20 z;Xk1xXRlwVZb3SU$K?2i6vaw22JbJ~^;+7%WLHzQ4ew9(UU%LcpB%Q@8&oH%J4(u! zX$x5W2+8h5{EV`Em9xc5cJAoYSbGc=@LQF!Kp!#M&m37O_W{ zXbzu<@p)uf=G$2<(R0>4$SPsE4W)1NnUwNeFv~Z6+*UBv8c=DYnz?)U<%5K3``&S3 z7hA3l>5?O2Sne3P3Sn(B!1Bv5I%&1zwtb)-O70s>2+Dfe` zSAc71)y5-jam|YS`k+Z!b#1Z~she$^EP4lN%2TE+aQ`Y*nzz`*XbvO&`me#V(&_Ny z9;M%ndRu{O+_&E)R#Iitml^&w09HU0EOV%3*yh;mO5TKVe=@?bQnY)frm5e$S?Mw= zJ@w{ZYGrF@NIh?*S=xCK5@EcnR5K-iUS3~Z>qpG^->;#s1N;*iMS~4cxLNBM${b%{we4t_#{FcFkv^!}dqjn| z5^pIwl5h|ba{J(F+w%Ox0K&F%+6eE4>8?s3c_X;k``s&qkjItr4Mn2K^=KNUOfa}A zzM}k0Ornyc-Jm=^qbrr7LlZo@+@y$R9_y%eL>{Lc6AWBz5DqXq_W)~pr(}zO5%jZ8 zBfwzcQLw+aw}&ovgI+l5rIHs&Z?CDEf(b#!w-MahG!a1=_HVxfxoi1ZW z#)ppx`=?HLo_46l9>@&Q=!WlG(X{CHD54OYQcT2=IgbWH#Ay!EHM8*}tPDe1rNNBK z<)bS)H>@8q*@kY|$N*nk-*A(_+IvpxkeYd_(4j@g&PbMXfD=pUF3#4>3lOB~;}hr_ zY0{sJ#psv<^XvC;bWG!wEaDz`%#6cACSM!7@Ah{8o@s$e1fIJ&ff`D^ zcG|@$-EhvAwzs;)?4~fcY|6GWLAzS|4o?*}@}syX_7{TxOwF%?Cx12#6&ab$znRU= zN{&#ZNfltX4G+e$QZ=sZM|GaKV!Q}hau6js(oqPEirjS@En)`+R^-uj5zPAtPB7+t$=<-d*@;pmkN8zBklo`?r zq%%VyaPPwOF#OayPxo*~U4U4k2Z;*hqd=wlAWnZ3b+3aokT(!*J(0kVgM69OkJ0KT#V|=@@O~VB9)3JgO@lk(qt50#vs5U6c_R3WJDK}86&(v9Th}z z5Ol_p+|V3(qawkO==2*T{a!}{139J%DVSZafdZ z4K_8F@wgN*8fGkoEWkR^?uoZcLx)Gp)^X3$foVI4KAIW^%Xtow>uOD6WB_#Fr#lZJ zf8jt|#uLnFK}`t`a;AZ%^OtDH@(#;@DgSl1$=Sq37nVdN5k*r>@74$|qp_huJ?CGW z%BZ4wRYHqR_R-nM3}<;rO52%>(4JwDX68o4+g_C#ozkHs_GH(Rp*`W_8JKsZrlBTs zOP*1pUB`BO%Sd_h8(HjZdDhktNIBr^$5IiO9U9$Tq9Gt4kP)>`i=Z2EDIpVdUG)dPINI$N?ujIt-bir@WL7N8YiBjz% zVT;mT`*4M}QMRjt+P(YjxA=BLE{Eg0ex&^fcp@;i>6`-7>lf_jE9G*P+3^T0A(HpY5f)(yt zkY`bkmMa|Yfk7YZOmangkbA*26Et~@%r@pN0DD-)+F3erF-T1O7z`}e)I{KB|r|6$>o*b$Jf$^>dHe9U{Dw@@-Puxc>P7yZ!@qA z-9^7S`PhOo_$Grynbk91)@%w)M%QURFv%v0>8q+_hf?kH)+iN*nHf4dzSw3ya!2PP>!6H+v_0N4tBSnoL@+gSc$bWh4TLKC4z>k^hA1nXHqVe+iV) z&3j-yZ{wO>uU2v_ZA!|IPnP$%7Fo5kiZ5DdVM>tgu}9~J?U)OJ%4`2qtVC#XUOS*a zK8W>*x6$|{yp@S+GErSuGcK@(DTFv-8*IxkTcz<}JKtnqd)L(3G`2E{GQDbp@4Z@6 zp!+gfRrbywfodP>`MnQ#o1sHsi~=Lh1Y;k~MN zkq=p64>X_in1)|?Z*BXRn(oIVZf&g*XgSLBZGi5(;!DFy<8*5D9}^dm&&oQdvo2}e z+K+|qa(1F+)Vh!NMF~ys?aMnS6b0#9XL3x}enjF;Is+&0*CMBjLGQbU0J9O0N6FTf z2_&ZWrBU+bK#OS{1(7hbrx&gYDs=E#>*PkN_tJ z%OgKqLsY^&qtF?wJyZT-bUIg%(n>Fd8Hgau%d@nLHMC#k$R*3p0iY(cj7}j(C)zGV zfWa)UzhLca!EjM4r|^R8jpw=V%5OV!?w7S?=`==X@3kTMf(e%=CtBmWN!Z>{Y;S011;$C2CL6Oz5~~hN=fA z(M3BU#}D;AkxK@#PN$%zXi=fIt`tol-*Cw^Kc7#cQ3GQbolNnFa*d{#&n?MqO=h=|*#dsO8q@`IymE70$TxlU$aS%gp6V*Zx!qO$${bsLdpysm>We?V%*J z@;kAkFU7p9=D7qb`kXtrvJuz_gz+du4;JxUMG*n590}m>A__4rFokHFv`yrZj?bNV z;+SrZFLXX><)WnT66O494Tl|N6rqHa`oEFyXn&!dD!8YF6SWN;z0G`nIx1rozolj$ z2MmlpA7`FZr!(CR`+c7XZEO|`A(r0VSnqFq%WPGNNTGA=}Pk0&WTz!(-lSF`B zbU@ore+~O_uckV{kya=|N5Mb-EV-X$FatrbKlqqjM|!Y}M$ks=!8rQ3V(qc|3zM7k z6!!^0_@7XR8|c^rIXwRCTCEY4F118%0EDG&FHX1R=h|AmCVlH}j#z0>fKuJv!l@al z9J{*jh3Vn-yuDC`Z(WC5DutUxixpB#86*_F)#^;Mo?%{pqGeQ~)h0s`)0=AQSq}KYy zF#%J51{jKXd1cu@?*c4P%7MjqbQMNuy=n%pFdEDx-$N4``IONxc4+S^x?rqwatWSL z%lZ3gNmFr;t7*U`C(`&lCa-_of0I)YXQ25nCVfpcsVhF#bkY@kZC)J*my>W5;!~p< zGG^y^OnNxp>~E{=L8G~|u^LYWqaqLm zAY^XqO69)sDrHQv!!pSrr|pAvc3A9ksq}(G3Rr_B<1B?=?Wg6wQcfGOb6G>J5GbAO zJ+tL}-KjZ^OuU>=Zc>Zz3IXQV8QgNLP7YM{QYpj%q|)_mdK7!o7_;5!xUW0>8b`O= zhT9eR%(RYPD?6U<3Ce;q$_r<<{9>>TTL9r^L}ZYBH=Fuyj`qdZ~&xP}Qg@g0txxI#t=^d5WMnLB30=#wa>&J-Jvc-67 zm*$!b?EWQfdGSWWvAdn9Z&eh{2lR!%ChL7Nyd!VvE81DARfTx9w>xHXyGeSKH(<$% zp&Vxp5tmvcfm+|`Vu|{s94d;D+R2g# zDz^lJer7@?La8S}4z3dcD*`pVjO+TP^OUTDoQDSaPWUF&)Ig<6^v6ZlsRUQG@d;R zd=Jx^H)HhS0HViOa=dmZzwFP==fK2t8d=FFH$hHhLn`kcipJ;+>(sQ(s=~MrMthd} zj>ZLiT_SnQufj~=Q>$6Q%2&0zHIrQ2d60uezZEkU*?hpc<^($aTX zI+XL7TB-Mqv}0z12I^(N4#ho1bdxb1>!_-?vL?Si$bB*%g(@QuJu50R5Ygih={f3k zuA;C<3wN>{-~crn!8QUFCsx-Ga?h7py3JvzDfiI}yCw95)#cmLb@d|n_S<-z4b(Lh z)f$b$yVfl*?l&>CNAx%KrAMD=m`1RMqr7xMG{3uXvz%$z#*O>CVHgdgir_y_EU;g`@7J_Re*Y|3r?reEw*R~{;7w@piNa8w+HLJV;W~a8}SWcop^~OzC z+eC@j+g3zjqVj=JUNvR_6pFaba`FgyT3@}qE;Evjms`l?b(6Gf+ATmr=}m!#hQ#Z08B0@4 zPx^ON{z@y2fMtKpHQ5;Wn%OIzhlsd7QB1NQ)$WukT$uomTcxPK|cn zJ%0ZiUD*|1Hj3f$UnZ5_;ysd~_W^{0sWEGAZ9IW5mGH{?ydn4|+?)3A^i-kzwN-f` zEZ=%Twbrq$obt{Gd{S4HzkFjwZb^Eb*aUR$sAL{(u8P*)B;&A3S55B8*|&m&cc)8y zw1v^KQ#f4x?@q06(J2J>k4nJ%OUEK;hicJAfN&&1D-R2zYF;b7Gbm0`XsbVQ1he$#9wcMIpKiR*qE9Jh)&{Hu86xB+Cyvn;l{T%9 zIPE(w4U18#c498zFWCNXFLIVAfD=a{sLiC`Jdoz)ZVK`ocjXK>OqHaCNR_T%#=_Dla=clAWM*~g;| z_#f+TQs`N+I1hiZcyD9ED*1RP6vT0Ds3Q-7Bt07nPt>`8r zb^`5bvpU{k1wuGFIJ)}*TEFMj(T3I1Rr8pA6qhV@Hwo6zi=jKVbCym)UZVZ2Oj+Ph zSPPg+;KhSp^eKp!v%MMRpEP5L*T+{Tn50O{xhER`W3+!Bo?REkTe>4IYk4i*bzMU> z+U}uVbGTJ;y>He~-J@rZW6_NDUg6KrE`L;hz3fL9WAD;3FI2^{ZDznxe0f#$%tcRS zg4#I11h()Wz>>9Cdh}|*{~%!aUmDNGT{oAg3K*sM0^tQyTjQ>4+*51i@^)Rp$}zhJ zuSWmIcZ9M>!gB%)^o^W$o_j{i&`u=MGIIdBnroS7t?WY;BdA1?_s!JEK9*%iC4A33 zY(1bl0Lbe8MwB0`_dDZ0>Gr?3nS%fM&JX@GFMOVt$?{E(Bw0W0WW8Qty4=HbuB7i& z_s*=y;A7*qT_tmX&FkX4&Y-re73AlgGHY30IZHt`_L6rbf{_g}xdibGtFWR+W}>uc zBRc3nj_%-V`y5|5$Jm-g+$zc`Jt1@N>8udiodY zJ9})kC<_z`ov%9%<2Bm9x3HhUy0TMqhqiL_?sBvYFkp1R zl_Vx>L95>{_N8ThMrC5ce!rvIb+~#6&(hQ*4er!P1p}cB2}6BTI#yvu3;Ln3YpI}S z&T%zE^b8`MUz*EGa$Yem`y}8<8uZPqzB6~yu zPAGW;za^onp-}EB+WazE)$+MlH@0W%(RoMBR5dtaAy>Oxpv7mx$jat_rBi7i_R~qm zTn=-||4gCzMrNPNt~xJ&^S%$L3$7+Wnuxax^6F8MxH^Fn@m)>h6oPPicXJZ~A$IY` zoa<)vU92hQk{MuT)>;@svK?vZf;iBXgbC;*ztszy$pW zUurPoLme+W4jiXn-^rZ*qFiV9v~U#JH$v-1x-V=Ci$j~?Zc)NDf$O^xNjkUO$I6qI zmAm7m(@rxUzJ;A+&!p8(bA|;x#!1dh`SqV-s`{pnvKz?t8IP}qh|GyIAKZexg(RQSvkoKIBe?^jQ(WVEn`vLA=DE{u;UMshY*WGA+aR!wH~frvSsScGHWgsLFl zLsOL0?Q^Xme?>W_6@AQSxVT(4G*xiGD?$wm7gz^*LAl&uCMXaaOamn~WF{z=8_eC| zt01O+tR^+M$gp~6Au5#`vW=^u-|y~PXiyS%`uj9>S$g38En<; zenDo7ozB?}*W!TF$$h(-CJ<gC3JfV;p)@4l0VRMOKMwLzN!&FAa%Xlwk zL`Y)l8DRWM;ffp->m-*)%jXA>l~fpE26pD#;qwHfPukzq*;k~++$zeoR$WK=s$6%? z4QuZ<&YNHSw23t_RsuWQA8#Z*Le6c@=doy7djX;xwD!@-q9DIQg9EEHo2x=vf??(= zcu~;9mh`e0L8<$iD-TcAJS(U|{(>5n(71s!mpXzOO1AOxJ}q zU7}K)*(ggZcDRMRsN5<_H0EN2T0~cd*{XEre`A(ES|cj6gU*oV8%iKlH8G;qubNt1 z_)@$`hsd%3&#%-q5?u?GT+0?{CHG&9k{i-LexGnbK^=*!GMyzO9zcDqjUh$m`{87~ zFQwf58(hY@GI2D!MA{f?LdS1 zhs9*Irfor-PoedB%?nlN0~J)8MARLmDA_S9ICk@ry+rea#>;H`SZx=*d+0NWOx;P_ zVq$G&W&=N`7YU|1kQAU~I_BM8rv%ZOa03VR?;QDqVaX z#u(lvZ?p9Rr$Ff#i-wc@(lw2U$F6J4NMUbK5T;2j;ambw)duT$nwYI0Zt9GzrnKYC zpsDVKDB%lp#$JrqwNO32?stZiK2$ur`IR$HiHxlY0e38U+fc)ijZqJD z5hI*xBn58MaDW%Z?&rbEiosEaGmuxh`q3O*^GwY6dYz~Y+>;SiB6MU5=NEzhzbyXX zp>~@`lo)q1*NVx71FT6w{qF*|cl*9^&W-Mf)MY;^d$iT5(?%&ymrpyyv0>Q4UTTge z-HejwKrh#M9;r;Cncgv*iW|YIf@xJT1U~Y@3JkTVu`orbDVbxrD5{b`R%{8#6`U9# z#CuYWfVFAxy`H040gdCPB;rVVkrc&7x-8Rw0cbIK~+;Y7QC5f8))i=1F1i z%^+Sv(AA;(R4S@jew>lT?Egk?ZA0Uu%C&Q8iA)`4zRQLF&f=vl*E)?&^bjeKb;T@$kOTU zmCiFl?o!G5N2*>6f={a4>eA~NtHa5Oo{Byx-V&zCpd$%*y*ZX=&|C_Q!o*7)fy&iT zzDb*Os(k5zZv% z7}k#IUh3iMHGUYA8c3`IN?j(kX+|;KwKdH@|DorpV;MuW%JrM@zp>Z~R#_pJ*Zn~} zj*-PS6w8JGhx;JvT}E7&`3y+mDEc&hI|_$aH7cym;&DW^>oG_z04|VH$z|+@__~NM zCnKh5J;(`AU~X29L*zv3%6om{XH#&Z< z5m;Yp+z3r>IZZn?`;+z1WOwC82FV%gL(d(gv*vbgC|%vAH_VN}=*du=Gtvt>JJRK( ztc#&E>Oy?&U6}3xnlwbT?q9p4$!!ZA>ADVv5K*xkEu75;cw4HI1o^VYJA4j6-Uaa5e5*mZ!B(y1*H>xNFvYaw-oxDt zYzk8J&`n^Bcz}F|a!q4V>x&Djhm~dF2>p(xOlG~ABw>`KnsK7=zN47E@NIk%d6DfW zGvr(PdZ*0tm2VCf$@~=GOm2enco>exz>G~XZ{?ndi7h$xBGcyT<*F|Dr5nds7Zr9U zjRs>?8H^#ryW|hqN}Y20Pqr;Imn?4Vfp!U1yTkC)D+S%5%vZA>G?`DY1X#M<9*<57 z4uC@y^x>T>H^7x!-x#511KO1USU{)0;2hIf@;!s-i`8otqUdZuBp3Cp3mF8_S9dtT za5APwoc9BH#wxp02===wGoCiHW#(=~AB>jJ@G4|;f)guFl^D!kYrC`mI>|J4QIzhiiqxj;k)$i-!^=q!74qc+9>&a*= z#@H3LJuKEIld;a2HuYpyv49iEo~_b3@jHg3YIUDfVz=m+`>R}a7?i0E1J-o3l7I@t zKr0WVG+4@lbuW9?;Rmu*<+($_s`4BRzTS%P(Kwi~kgwsQUCT=2++HA1yNCPJ2He0AtwlF(_*&Gdz8)H{V8M z%^7Ns?MNVeuEMF?+R9vYUw{|=JkVz@%`YWmzye<|y%%==zUIcZ*+f*Kmq80y;NAa3 z=61Yi@ep4Lx9YuD3Pp_y|FXHIx}LPD>h*@#-_%9{;~(obd>K5)R|RvDbEa5Nc0}(` z-&oIgx%8`F&2!E}z2@XzZ4m2K5~DukgO2=AQIft7>FR$XK3kl_4gIk7;#^ygJnC1S ziY?Rfi_(-l*R`}(7q-n*vb@AHZtV4)Za)h3 zOTEul)Qg>*ZXP3ACiUM73pGgj8X+jJxYp}$zco$G8K{~<*s~fVs?aN6I}w}Xnl(@E zxHu1ASO$dOzVI&(kE3@m&Iqp|(KyP9U8#|o{)xT*T`z-0j%og0tGRf~@!i7h%Zb(+ zRQd{*YG8MqfE=XsHB$PF6dN2uNZ&@t{Shj+nX6Tu*1rW^9mR0LhjTg?1}JSm5`cTSzSw z-ksO#z1Bx>{67y%n5&?;7vu&g>0#SpWu6tpy7hu)9>(bVh36hxhylQ(5?8eah_+u+ z*oCEbpHgi%pXWB#oX$Oq{DW4p(UL#<7%6qiR4}1T_S+7T2%N$T+_OhjP12hGaOCvj zy2WEl?q2~4`F$rIU~MCyD*b%w#_MG>MNDBUHqDd?02)!cDxv;{%NtHj{n55-sj#_m zc1}1gFsAYeuY9m)InPfg`ROD-+)4g=4sgYD>pSOVacu3f>zO)wkMgwEwUSd>W?ps1 za7?S(s_MDmrHvR}A3n#RrimFZ1Iq?GXCd4y<#kF3&1DDeik@E6c4gXaQHj_p(O3!T z%tkSvl~!itGIdOB3_&J4SF&ELCzEkz(A6}cF{5#m@lTPZ zy@Bg4R9-5t4ZK`)LV-FHcVG%Y!@-(<3PMtzV-PmHq$`1q7&K)6N#;}>HAQD9E#MH9 zjZh%g3LA|;--iy z9^($XyC_zq25&C;ZXv!z;z1u{YmAIuzy`Oqh)tb^#VSm7 z#-lJEr`t59Eurr$ty2>ey23EFrWC`ZbvYiaK|rxG~47ennC(9_<@Z;|!l z)m{tqG0*$S3*g0^K@s$~fqE_}fLM=yv_RN{+6wcZe;)n29AG(tM`ZvSyWQeI%-r}5 z7<&Z|d^xnxz=#+4fdvbK{co{Sgb!LrZ#%pH_{YY^{b?kB8We>ajzsyCaKWF_a_~P{ z3fHI90{(CCCx{E|Oj)D{Wv`{;mY(~mxUspYirZK>R&k>#S67LG=efhYmm}Y080QuC z`}8(CW(VACv^JWqB{ZWtADyN^%*{}XSN@2cb?WNw!D1_nU4n!e_-zG}Uku}2$chTN z@RY-NA9lP5z$@|QQfBcWO!|IHtIoEI$Ed=;I~=j0Bsvne`9o@)N1WF+#Uh~}siHB- z(x@!r`onZfS_Huc3)WbOJspFn9FBScsQ5P+;Aym1LSTp3pr!I4AUq2ElN_8Ba??-F z!@kuJq+G&PlUr}{oGk%e89dk=f<7XHBSaF0=K2rLcp9Mj9*Hfk#?FRbg@!u)I zCx#p8$v=jpI6Uu1sdju*n1PPpHG}SYI=<^ioTha+93>wkO1;2q?r;%B3|I##zqyo+ z5zT_$30?+&{P}{S{|Jn-{WA5pO|oj{vPFg>p<7E1YN1iF$i(8nX&YI9AJ;h~^6>y2 z*pRjegggXxdb@Hm2W0BOI)WsYvf5O86?#< zlCtU0st&HmnuL0~26S|VX(d`@7=yld+M5j>5(tbKP!NSZ*=iKyxV#GsX*5QZCVeb1 zFrZ-AeiHV`%kJhpzMRAu0Vr$|#iG66d1sK1k^3C#)B~XS7;O^iRWcf{Um=JWmhBUI zz}%neG`0KvCy^=v8I?nZjIl;I=n--dT-a67^nI14ccV=SwK6P`!r6*N3g zB-H|v1hM!FQ<=w0uLtNwWAfHtr8)KkEq2uIiW~yXRswd?(JIyNHT>c}OljvOvLg?S zHdKZgyj9$K8-EYpMm6{^F%F-U?Y2hdv2+heX9INX0y-`Sbi6=7w`W7?fs4|Ea+Dq{ z5~bU-0qUakpd6(Ki$v)+v!V3JMd?vFN{<$U(%|Uq;6N!{O$F3@(^Ji2X5LSaCjGvW z#C8JyTyx+=2`N8FtzZzPSK~qsQ zbbwZ><@*AX_Q`4|(S^Q;R(1mTSZlAayoOIXN5=>IM{OT%yB)o>UATr`GC?TyQb^bM z6ho7!Pl3v|$fI+5-zLFP>xfNPcyYxXB{&&S>t=p!Z!kO=NWjRMJn>HOn*IxyoIs*I zRCCKL9HT~{(L3V04LK0@Kh=x_^k4qwl-@}qEY8UgOcptvjy3m8ta zFeGRU5OL%1lc9=BH&0tP;U|!fK$e{ViASRm$VKOv#L7o4Bz@%xCXnO;{((4e<#|Nx ziyvG+MspC^5xU@$Mblupq5g)+J1}{N=Di06HZmi9DSJ^!zA~Po;D_FdJk>w+ws4lg zxp(D)Gb=nggQR(P0{Q_M1A{ROU*G}eWRA0~dSSd#7Ht#BVh}JZUo2bySI-u0(lVz{ zBe)@d&Uf~5WcNU4?`vm>MWp_q8*(3BsD=01D7-~;QhF1P#&vE*zp>XAtpr+1*LS|k0F{|^Dnd5#(qzb_HK~YfIuB-N` zQE@sdK&O4IE5%wv*1xpFl%d0^=Lw)vZp{yKtdOu#vKdhTI=!Y3hvd>pZ&QRRe!@wD zPoBd@fwQ^e#^LP87qyGokJ$yjM&-}gvu6et^WZ72rp9r$VO(gn9}78~UT}0T4YaEU z!dGxi6#;vgUfgj_xdb;1_Z&_^j~ocdZ2(ra7VM85B7v^gQ8dJNdra;Ha9mtqvQP0I zV4NbMsHgj}&>rT3CgPw%3D=a}7S6-uW+<*(^em)vR!|o;#%&C=dybN0FKVJQfy@oV zAuy?y9CdO%epVG9c#&gNU4oEG3^w%wFL0dLaYg3cAz~JM$#WgOV)7z8mX`tL#2!UJ z%Qslp0aTCpBckLknT*snu+sxpZAH+?on(Uf20Ln2KU0-`v){jj(kupQ%$Lqj+p71D zAx~m1ni{%#czHB)2k_&nww3z`uS9>FXardxOZ0@jpnV22g1!`JYwG;k3+p|KI znbr1_V8hB7SthhTk;4GKem^u8j-tozYEVNuTThh*tKvhhku|4NoR&(9FvOtDN_A5K zQ23GIryvI;D21AIa&ZsT?7=`)5Tn|a5YENSXqh^>A)1Q{NV%9`#x`{ZN73a3y~Zr> zfK~V|`jkm8j%ODDO))bxNCsE&5QQ1tvG(1iW|$-Oi<3XaWu1wN5*4 z4~}284muxB+b8=+Z-f8+U+cHi_G|S6xp&6BNbULK%Hy|H1oGR5ku7esEbViO^MR|TF~R+!aCHrQOJ z%wr~#3DWLzE`W6W(2#CdLh6A*>Olcg4;~z;+m#?SNIfV(>cN8}^_xmaJu*l= zDnRN{DN+kBa}$Kiuoi*3RNWlF)2N<$QTBt z_3t=jV>IR`PO6UQ!|8i9cbVIQ!BBfN>1%<{^o84ueyQoNXi>A>S_Na*Xr!te;1)B$ zlm<-|b}HZD(<%-mZ)AWPxbLOdH$F~r8U(~CC;rC=x6}3k7y8!2r1&)Z5u{HY%BxdJ zSx?)mIM}Lr;dZIt%oKvy5y3oRYQ@Ah=|v7>MZ-ZilK1Pqf4tw@ZFk!5T5VIfqY+@i ziT$*;i6PSX4-ey2)V)sKa_7OO&I{74G9GYL9VySz(+Bf(lM?@whdocjY53+M20Yp(CSIhx6zv{0MD1-PrFniBgR@U^eE#!LX|39 zV)pO>06HajUKx)wYpnbbb`MP|s>6!C1eYu`RHb=MZhZv|XJ$xEfke|NKiT59a$BcB zoqZGM+AmuMPt&Tqmp$gyHY9go?g^0XjZ@SO8GZ4_9hw^i1qe>sVm6Ch+6EL9AT^0| z<|~p@5dY$9;z2wjutO9cI8;%z0p_b!%X7vk zz4I#Fd`o%)l{?ECe9 z`4ow)uTPRnn6k$%cFG}D5^<%`7@5-!9KOq_6Zg^={ImMlW@9G6VY^Wb>iC#6* z>@9_M6&t=CqwHVa7&U9%;d^QAtm`Hm;<`)~bL!lW2AAV2XN~gJKR5mzybLy;KQEeB z+L=sR`kw~GE~2jOsv3gYMgzxpfVGn@VY+Bx(Ns4xJyO+(2o`RQ0|1pr7-+(1K4W@$1*V`>*gZMtl%hxXFT6=v*5Dz zKeu9G1K;AqQI9eT!Oe8bHmumEQ=79w;{ru&OgmJn;ZHv~G+M|BN=AV*{JhE;R*k=} zo7OWj7(L7Z^Nq!`0_V3){B6_!+jscechgyjff=hG8op4tP~DL_{XM!v1Ic&w2V^B0 z!eUlZotNN179XK;r|}TCPmX^e4l>b`*16GaSPYh{WQktQ$2hw2pBYzlef)7Uj%}0ye>B70!UJh%I+@d+u$9>pHqaHdGi1=|d&}W8o4(JFFB(>hvoJ93^rjTp1yT zE%qKXmt)xu)Nw{6aalU>*qMFF%*eY!%wBDbO|pP*GG#WLZ2Pnj+A<(_%2?=b-4aI+ z$nFIhHX$#i?l!{gT{Lfdcadm`H_}=k-#}!JE|V zNyxBU1eq;%+UGXhAC+8jawQp;6II^gV4fyu7x1o@H;rC!ewTUfux#)+3Sa+aRt4X= z5{O%=egVMqC34DCcIEAz<6629$_VH}Mb9yn*zX8spxcRu%`&L$2NlOsirWg0+X(!S zkE0Z$bhLM%232LL%*bg5(C`wE!`KqE?1L>fUTYmViUbJ4iBq**XI-0AGbeC3B8VyL z(AW!d3pZ_jwQ((EoQ4-lv9qG8+g3DnlYX|Us>>W!wc?_z%FnRGJcYbb`mLVzhWarY zq04GYFGXAkL?zuMD?AbNP13zy$C@A`Kvr*E`lih*(xo{l5H)0ZR^syk#|P6BZ*1tC zXsHk5&1Msed-^ijwfcP%jfQQY?M^`7nyR4b3XY&aDmWV6k#s35OhQ_aWV;Ny!JQ8} z{B~C8d?V$d%ekmnJB-@|i^R^NSHO*OH%JC|H}v>IudSTU4(^_dNuRgY5x;g@2M0B{ zAsWG}a8&Cwf(~dK>3tWvjzJ<2m$eQX-jE5>=Byq&jf1y|RT z*TQfds}3)IqaN(rR1JnII;B!XfRjio#8Ye^KK!YtrFfv~ytgB^Z_8P+|7hsV2r@9ZD#9-O`2>+mQ3Unv@P+IiPmTFMOqOn{E-02Ako zF%NHm8H)Q6=Yq%kE9#Czjv|&?bLJ~9XVwDog`QQd#xhFk>2w;6rJoX7VoIt+6tSvM zwQ4PR|1{V?YVVyKwGM*)!}kY!hkHluR(t>W2!}!XIy^gV2S>;4;MHDmcDnZ(+T(vu zg5BfSd*6KXjkRz!fvnsrx`&yk;eeG!O~2WZ6!R*y_7J+D#T3VS6=F#z`k50C>oxoE zfUR?d|FS4A1A(&=XLsXW)F0|Z_;e-8P9u7)*3v>v-~kXBOTp5#JIoC&-+P%B;mWN0 zJqOhbS3%rM@`6!x34+AvE&#nu#=yRCCh!47O-?Em&(CS8@kbZwrpyc61}VUM3w$WS zM5&kqobqbp317b)(L=ln^XH_fTl|0*s(H++-y{hKd!wZ+>QR(7CKqQm4Kt&3htEJZ zG@@grk#cB3%_+U2oZZO#VpvI!nQAylq{JCrVzJx_q+;VR6nvf`*Ptdf!WcNR7-}ON z&Q%Y@Ee0R52$=YW1I#&ZIOkiS7Rzu;VGR2+bOGh>d@|;z9~zpUvFw#Gp7!BnbD1#4 z4eFH*;r^3U4NptY3=mvKfGdrFHoiasl>ukUp&mA92f)X7M$6gOF2ML_@E;>Hx5{j9l z%KYylq-^W^i1rDmw)+^5#^_yh6OFHu9vg9E#h6%%_5dP_vv4Wv%(u-Jye()}qbv&w zn*UeYjvvN;jYhFJ_l4se6Ts{slHe>KoddkcPxk)r**=OHd6HzmFdUS5>G2T@x%HHHzj& z&D!Qt7PO{*7R)%5U>s#oxj}MS3ntvWgEizE&hIK@0)g&WI8e2HEy{4VLD0R3O;`W| zDy+O~SCuWojc3&#u%HDAoNBw3%PQ|PT%H5s(;~Xn;wG@`0blSugsOUh4|T*ys0hc% z{(dr0ztmOEO?G{z0pO+p;o>I81_TMOh5F8HKrmfSRZ@^w5}9TV<`YC%ZW&@aKsG{L z<#jn5vFj$=;zQ2ySTZ%R)U)BsJCV4ttgQM5|cqG*8n3laLn7EUf2n2MCUx;xKL zrew#X{8$Rh@+-TZD*=xO7&2|aW*I0h>`qvuxtFM;y<8^=iu=ZC2kYggDZT;|JMVG+ zRG2m%hPFdKGEP5pp44CYu0wPcu4-J{NCdv14QAQqyKO-ax+;{;)#oRy_h@%0Cmpr7Q;rRdWZ8j=(NX-b@Z{JapGF?N$=vcR#R1v% zlffkd-w?iuiWMkKigQvCqzke`5cTm5BlXW{B&CbNz4k%wF4lUKS5h>HK9aTDv9FN^ zqU8{>%zcWw6Qny^9N|G2T4CDluIE=lHUc9Wa>7JxxICF}yM97L!SS;;Xb zFsRjOKX4!1@9j;Xy$3|O(o89h+z;8Q^jgyP8%H70f>;)@!2~27E)*RPJ|mz)ZE)<&c&Hca~Eb-<19o|O9ALPsCgHp)dJu(nJ1BQuG#F9(|ugg zO2kLS=#*}$TdOkZ%Au5&OQ{BLV6GT6M8!y8oxZH@0_|wF(*{%u1vQGZEW%*OuwMebGb`(U%1C*9d;hIcor+od4 zUAP|)K-20B$D{4c^~>QGGN`RHX7?W%uq71fBZ=wBP0bswWWS!=(i8sB@h#PE#EucN zSbemodmD}4k;(V?qLvdNU^GpxtzK_rDQVT&2f3Qj5Ff*StXM~`Pv)!vFX2vd7d^8s zDbtot3tvs~ILlNN>+HM`VL_z=J--VaVo|!~k;UruF*=XvJtq+(7p}&?5c^7;#yOBb zRR*g?8mXh{qD!_|iJddgwxXsXJd2w3X-Wni((q`I&I@Z6$No}bbepX8@b(}u`k%Z^ z$C#G43YD4X%o?`eWX*Esx4fxJEXbAyDu%|s+|6pQjkrTL#vAf}^q`Q;SN1lBor{=~ zePXac2j3PIm)Z*Omrjja)EneDjvuKJ*Tn9?N+v0MdwoP^-ar0SY9@yxuuPXGlTkOK z;ey-HYxls7lzgyfC#i%`&d zP$^}D5u*{PE|?X3!dn&4Sg#X`ssnHbm%Kk5dMBGheYhBK`VC)8qsd?$jo@m8^lmT? zKk+*&9@bIRN3)`43PC^^W(ocpFB|=5zyIp)wKm86sSg9ajOwy${&i9OX3&L(VQliT zYpIUMK)TThugRLIvwCN&E@G7Ih;2b{2Z73i)l6U7K_RO~t}mKm6*ci8z291Rj0LTQ z2b+B@S+efDu*g^Qu{Kp$#fH-f6U_h=>IIXIn#$%$6}^GW6VdR+OSG8=a{SB)z#WI- zC}jpOgl!*fKl^hz%94(T)o*d!I(D8HWdU^93!d$SHJagp4KX{c!Y-AdV+Is14P19? zt0i2NqDKPf(~HWT<*nQu!~Rm+#I4%M1A9Rtaj0b886_B>qm6A5b_RyOiqbfhSMdTy zZ^oa(-Mw*-zICh^cO88p_^%T+iMko|3~GRmcH*&q#FyPWbqzG&-X4!a<%QT$wkze+ z%dhnyySz(oQRR1rxuw(x$bd~Ldwg3YU8BPkk9=bkUtXd=OK7T7TBAth)9KyKc^slS z7i-F^hL{70X5ga3AQz2q@z{-T6MGEHWvpu|zSGtzza|&qjOm?vQ;*`}q1N(usMXnr zN-;?586U}pU5e_$KV14)39Mv8rBJP?BpEctAth~;mrkh&D*C4+008AHYFg~k;k7nP z3AXJk%=2$qFMzQg4u?@V3YH7o(?b~*c19p@AMK;kTqX`2ID9R5$w1I~&Eg$?yx}e) zNKozY7o`}t?%@*D+pPraipi{qFjRLgnJHMb#w$gq3mvIjQ?Y9NtY^ZhHI*ROvyyLk zt)IIed3?LK%J-wovh7Gr% z^cu{7g6;M3F?8n$$%f^#vssd|*>Mzcds6J&cUJNyYA&&Q34fnRQ#y^;Ep%JJ19tr( z9MQAy^&8MJ=no+&;=S-=PO`eiFS!zpcS_V4vwoh+I>j6hjl>8zg2=jkLLpA5yh4jbQg9 zkYD8C$b~u}x(66oLnP& z%!Xzwa!*gwi%Gv6DT_jcb%|Y!;)vEqO117&Xp2;MJc^>8)B_50?sycaz^`A3vZ#L% z)Y6165zwiw2gxmuf(5M5yA&6vd_NiMq`3GM`9h!zp!LUwWkxxQyAk%Ap+|Umc{f{^ zQ_vKv`boCOu0AmpBa8+?VW!a3oRdEgYi( z2JBjFcV>ybK`O5>I!lMYiu%LU++1-FI3SRyif<9FIi`#(ejJ~r(W^Vw)Q*Lui=0gT z9!$oxZa0@-9VFM3Ka*RzgbA|+_7-jZzU11Trz#u&uRMsh9* z%0tkT|3t$gVKI)u7gPRd6Gf!V+*lu#M)1XI6x}s)8t0(pxsR%&spI(7!w`euOhpZ| z)whNI6EzB6mA5+RE%` zqod*f-9$3x7lt$1P;DsMmV5+nlUzKNaxtLzP}0b0!_Sq`M0uPEy^&M#$heD`dr2n3 z%*ZPD(mKs%&sQXTZ|U4~9`7U3XxLb+ zGKJw9#C4g|^O8At!XlEgL5MXh<4UB4Fs#LLZF0>PALGzQYUi9{hMEQgk^4LpQh8l{ zjwpu}yFvsYkFO^e>zsWdq6~Zr5QI@)tAY>vN2j|V%yZRkD1O5f%!jG-B#HDm=0qOR zjT(ly1gs{Fh{5R1AqZl2kq#wzPJGRwwIpjNnM940L0Axe1X+VHdl!ux$j^Ip2+(C* zVrKNRC8mz=H6}mpmPY>SyfmSHj8{ofRN+YVP@8(x7^ZXGW0tb1C2O6spKXJ|1Ve`` zZ#K1Tz4er*-3gm@@F0<5|1bZQdN9WY^doH(4Tu8BJ}z$@DypJExuA(Pz>?4q4-^nHf$eMp7>Nod;H>*M}s=i96a=Tq6dzyFLg%9Y7 z^Ea%^q^`z0d06D}KZ^b4;Wb|H1`tmMv=*RJA5 zsF^cuSb%v7o)vCscJ>PWKubZSl4JrC|F%p6nD%>-hLGkpgsV{$73&C+OwpqyP=XZkFOQ@E z5vm((#=?p#esy711;NMEb7Ea9IFEbrh!bptu$N=zD?)n!J*B%)`h?0VsO%fet<;&f z=GxkV*|xT*S4R)(e$*bhu3Xi#M>jflhym+yNNR{RQThCY6s{v<{`7kyaw1zHgHAPv+oa=Iw3932eU|-AWN%0{MraV_-RED3(MMz#~MVj`@tLeiD ztga&qN-e^rJ#vMZvqglhEV}}-Dv7VDPjpRZ5LNwd~86w5Q>tE!^ zVnsvvtyi#)l;feZMwR;p0@UBw2f);Plc0&ggGo!L;8W|#aj&sVViPSi7YGDe0{!(E8{q6B28i#@@uQ5osD<4v5z&qVQQt z^!m!oB<4=x#(46qd`w&HB5IQ?6)LxIY~4GpUN4)9j8%0?cpwuvp`FBNIR}$6f``a? zgIppja^KK_shPbqK`%X99tmBRW|g_5ya6D z6IbgHMlBIr0#+2Jcb1`6R1sbYsv8bBX#`R8vZ*zTKa{Q10<1T=xw(6x>DSI1U|EIm zI_Kn(W;BmypTo)MkV;;YffXS~W*O08R}i>JZ=WE#NVzP9I$;2(Ex z+dpLmxOJ!n$bRKEi?W`opmP;(-avr^!-S=1<5=-$m6Bc(&zW3!j^`mC#<&-SoDPNy z%TPq3Ff9ur$?6bHhD6PHK5;Ov0d-+gS0%bC2997w%n&635vlBScPN5U;?{sJ5x6&Q z&>95abFM&w2Bza_WY=y&`h%dG45<1Ab|9TD3@($Tr_vo#kwTF7C~IJvcrF(U$G%z2s^-#?-OV#LH0W38W$eyM-%WxlFq@6a1_X7LUMn z!fMFo=!VF=S>SsvS4?yfqEMejQsa2g|md#6XMw(Kq-IOqK6! zPiuPoyfFo_lqZvGX9m{PB`)8~tpz=jc2SfqTTC^N7{Axiem=txN74^C?nNg`=R|-K z?<4_Eb;;q<)2N7bCc2_*WryM2d8C%Nhf}Dy9RCU>@tBjoq3Getd8x`YD}QqQa!Rp^a1_f14X$z91u>mCNDS{DdD*fXyS*>)*+j{C1um4^BfkNwn15o7A-Y zK;j8`|13UUg0^?u9#5y~sW}Poa(OBW^g5zSZz`R{3YRI z%k*w0F6t75noiE?FtV9JOPc(wM#sd7oaW4OY&)~=mRk93lYbGlqk2hlF?U*aA(37! zkD(ll6m;6EY~NsdxbllmH?yuho!)N>HuX(QI05CX(=A6xXg5%XT^kwqA(4aD*(B7a&oj%*q_%csUq_Us)*UCHyzxs^~1yu$ip=Xb{DVJyqX59(o;#w`qa!>Q0MsHA-7X z#>tL9hJVUJC*(rrE|kCftP5oj7Pe4Kq_XZ??J@lD1+3TsW3E+K<6C5A$&)wpFVnR( ziWWleFYHH{$-vb4s{kon=&PJSb5-*WOxt{BhH+rFQ#HaZxF*>*7XZDJMU@jA4e`w23@rCjl!?z0c2nHykmz;C5-+_24skcK4f~8_mBqA!~ zW|FW{j}I`3Jf&Skw z&Uu0sIelhR9u)}eqGa-~QzcW!qf|?pRE3sbIqiZnWJ{yhPFpj#@*n3ifg5!TAIZ`y z+w;**!x!j(O8oWbQ^`n3CUsws`==g(^LbV>gV=hUW1}-KafNnKXr^+P+ggru8d6K& zG5SrpCPSRa+O~|vF2Bs2{*qg!G>mU$|WG)rv2afrjt1H;KyRFi*$_$_3hZ2e- zPKb4NSLMwV`t99%4mTco`srOgtb4t*#6&`m%4`&2`#3()xt1m(Tb-CfQb{DEG~*Jc zpBU|zLYaUPR#Uz;vAXGJMY22Q{lE;aJ45^G?Myv%w%$cY$g`&nb>!roQVye)nq8Yg zb?9LJniV2{=bBI}-Eu^|TBlL8SSVKF5SA9^+QKqbA=tHxCUCR=xCcZa;JZn>I%>WJ z5!^Gmn~buGsOFg^rI_S6)kZ!(kiPVsfev7!-U0<9m*S!{Bxi#j`ODyy)3o{>lJ3pl zI~ko*|7cCm`>J|Y%{-c9<1I5ks~Uvf5`?;{Zl*8>Zd7^t&|Ex|P9j?RCiP{c51lp? zC!!DdKXj6_%dO>&+|Y9kod)dGnbyB4IYTq1#<27MZ|_^X+eUT-zx!9zbf zTKjOVtWy?VonyBX%djPyY>dOy!jTx*Zv13yQ^0Ia8i9qQG)A%c0!L9C%vPR(TSy>d z@LSoc2rSIl71VS!#NSE3iBw1rX(1{pf0iR;(zK7JStcoW_3rtNc_KxDLuD>I2F z*JI+_I2HDsxMo6&D>Qu+T$hPo#;LKFF_I~u#kE>|^jx8dU&rV&M^_LNoE7oftVI(( z#^dZ#{wy0jg{~n2cz(|b#1rr=pLOSg-j|hprWt#kBl&- zNPERYiKH={xVM?WLR{S}m&a?k>{(Aa)XScG?d1s^kl~~1AZGSf!I{zVv zg?&6rXL+)EuhO>9+CEp#YwCNk(jL=sdv$o?0FKjeyWEC4!{pfF#wpFN+`{6g)T)N5 zM8~3SMBIUr5VX44ZXmY4`n=kXxdDi7XFaavw6zV;!J0`0v*{p7MM&IVC70Ga+Yq6xrp0F!v6uH5$T3YpMzcDt(`4otL3T z(dxwAIr3AC+0=Ft@!41n6TQof`S{E)a8X;Bl~^;2Y0Ksh@o$5IIkee4!G!Fv(6FBP z@Igl_O|MnGjzFTsyLl^{8i?ju@pG>E`l_Eod7f&FU@VLIrva$oSPJoB# zDBm0#=>ImQp&~Qo6X*9q`474G=To4jgkMAf7|FL&xAU>V%Aua7h^IJRIZU}jT00uwWxb(+aEV1^Z`zn^dpGJHES;-eZ#L!eF+e<*EUj6H3~!nVyysz>}yQBc)b z95Z6_cu@<>cy*%9hJT*q4u>x?0t-{^qj4XGzay?j^A`?3vNemfTA%ADwN?v1oz&0p zlP-YE#-$oyiUSaL&J78Y(evd*Yha-jZup5g;xS#fxFO`xHct$HJ#H1x^78KQMVpBR zghN0WtgwHK*{po~R$(pM=pf3vl-A`%6ErM_SQ$a2(KYYCq*=9A1cf5g_3~{>z!GOF zV2q@O2t^j@V6sA zt<-`OjFce7fAiWLq%_P{#-z~u(NN7d&H4KXe9Ha}UU$P~24)AXu*)K=N3^fA5*Z|1 zE+eIH6p;dY2!ltX%87qdq6(})Q444!ZHhH>%UE9e;zRgo4(Z>b#bkUCV4Q#d$=k26 zwSSe_zJik2bkbdiFJ$JM{sHAkO6q47TS>|mSBxbUhpO&rCOk7s)fU61W--nW0>Dtz zCo$jkFei{u%`$1Cd3U4Q3;p=U*82z0;_ro4;Ay1Z;ZN@@S^2X^7kS{l$M>R|6^B73 zt!NG6n`6-I@twSyAxn1g#7lfGY4dUA1j zAC>9;><5vLKgSg&%Tq%FWA?l-={5^QhEwY15)M z5OHXv0!|YQ58T7g62x^Bbu(caErI=%!E zp2cSzTPQm-b)ggMoUzyQwv@1Ogg^?9Uf0M~Q7T8=#JYBH!i~F*bZVS?>lSAwLo0AS zz;~7rg{Lf%o;caalbAIl>DxXOHrxVB%1fGE!aTjf${S z3R7_n1x4XORt2LCCNFAYvD3!4BksqMlV?*#X~$cV#BxHTL3CXhubK3455T1*%{ER% zD2=E%K*6=pr;NCE0Ky`Y@TprX*P|OtLr@LYB^=T^rt6S>%3@2y0XnGHIQ77uqC$u~ zClS^R>y4;@7+uZ9_WIRYtu~~Q8k4pz0TiBzspM`c*QVi>22z?*-iZ$rqdgNBiM2F? zIorxRRZ;4tu{YZkW&av`aV=RAXsRo06Ts-YLJT+kO50K*XLwvrEwSlL+Amh8oWHRY z<@Q!r!O?v(OIf1E0E|F$zms>IRUf6~IAYpRcG)s(Ozy`Yv`XtA_jCR(ElcGKPSEkkHcw09Q?POfls&DPu(!Iz$8%bP$LkGLi z9O~CT6w73QPn+5)kNUIp0nh(RS?3(7!r}+1=^-z2E)&35R1#nn1C&VSDm$BG{v{1IX{Sjgn^svDf2Xya8z?pfQC#C*5%)FT;eg*E zE|id%C)3m}*YdL{-zBp{<7E4R9k?Kj9XF&~>^K~e$%W^3AkUCekfVtqOgx^s z6I}#>r%0zCU@CJBgvA`b9{Ho2Tb^E>Fqbg&LIAi?1vbkYw~MQ1>$Gvy`c}JKdIRif z-}F^}nG(kBmLmfI)BoABth3x>mbqxin6D&Zh|%tEZftFp`AjEi5h^hF8bHBR(Or5Q zu=_^X;Xv@_F}m_1Hjp@<$}A9{pq&bo{H$NGedKxa`3+^X^{C1hwOk?df!zZpG#^+9 z5DCnmiVYAd{uf!vsF~>_KS%iL_d4KVG!J=P2{x76CWKbg61B&g>h7gYMy{-cL2p6F z)MKfs$`weyxmHH~ikd)d$cyvVhwRqv$n?WlaHoy=%VnO8c61$^;8P+)9>s@=$7z#k zi648&0stdVmVKdQ*_2-$6(mj#l|UoHaV1I4H-~@)OJh%rJ-Luu$l?}dEWvs@MP+_& z<5si&PBI;Wy2?Gajb1cNWK_cOQW{4-dpuH&+d}U`8KZ#3iNi$X89}IqdRJqTIMcDA zsNu(Rnc!ir+tzDh$l0LH@uu9x!_HE3NHnFTog8hHMWO%^l`)!p8Tiv~5s9!!4zJr* zQosDAFBzFdWHl|6QPxN7EC!A$)J6LnLYa;&(gpL_PXuzs=9X%p;IX1nJ(dEAanJG_ zzE$-)$MjAt2(yXi0l+Eou?v$yT!NP|t$OxKcu!wUpPSt{)@cz4f;uXo0jCX?PfCw! z>qUowZA_BmmGCRi#iDX{kI*TJECuGTB*Y?sLC{NuD=2z60Xo5YES;e?-i2|`M6+?D z$X`J*s(k?V?h5ckl6fl`CKuXT{OBU3(R+!a;i=@>#l|tT(zdd5Bx^A@xJ|#C&L;U0 z!hp7!%+Sl|f{h`$k*yk`wQ`s`IKBm(=w{NyrB6`G+z9_>P7?$V1u_EtjLSC3?Tv!7 zk&n*eKt!NaGIvZh~pN)SAIq z^dzN?%M;LNmP{Ll~!z)>WU2$Pp* z%yN4m98#&JE+z77-?}|s&Wq#*EqMe()p^F}AxpAiLef)S8NanVE*IEKNnw_H>_nv? zg_}!(GcA6J!L)Rvf_e-$;YGCpFj#Sy%190=YlG9-3qP-vQ_V|Eo7JTmM%{qvZ}E&> zJxbh%#}7OXfMK#!4hFq1I@pwT5h9klcm19%JTT2cN*kl;M`zD7zQN@t`_K5{0cwFew{Y{QcB` z)YJjfBz#QC6lT7sXA9%!>DX}P&6AJ5+2|Awx}% zQU&&-M07^5PQ{4+kvmDe?wS|~2O!gEYKc;nBWy(l!NsLe5nTearJ`y%7(^PkMteXp zIs`S?UTFGMLtx1=mUNaDY`w$#C^d?U2)vNs51pj?n}^ zEoN13pvSEyjD4L5`}?UQN5vv!OPjwOa zSPBIeoHmz)koL$$zGQKhmP~vv0g3$gyppvzk)*`xUUb1&lxom7{dgt#4h#EsFqE)J zHS!=@?#e{HN|Vi2RcSGONCdFL8a|K5GZCZl$#J2ciAu&(bcB$wo1T9SlfvtrJvc;x zj$?OvzL6b7PO6GMmsQsA0&j)Y`4)vmNkd{XOia-=B{bwqAL40oULthyRSi`_N{-+0 zq8&sh4h;-dN*5x!6nFmdrKBUpxO+47TO1x2stg|}e6R*S8}aY zNuk_{qT7#nWz`~4G%~`&s6Cpp#VxEwLw}~#YKktet26R3I}52!q`b1B1#R<8Ja!kO z-GvNw1HZ2-ZOGLg(^-UuTULqOt~y0EX$iB_HWjAW@sSQsf?bSF*~93(iThT9&o!*x za!;PTruczCG7e7jwC&VjMZVvawy4BH4-=tyhJ7+FFm`NIRq|FzuKhFG_PP|CURR;> z>5*f0F80dsGX*T2lcq=CCki$b;+X2;>PiaC6FjXl-ZLW*%VRZMl)X=X5X%O zUNWFDd-hVp6Ej2c5|b$Rky58JrJ(vtpX(HI$Qjp(8Z8(&@hOdjXObPbXqc!`y`h6x zV)R4BGg|j0$G8NcbC$jzt}|B3bIl+l9!geS^h6vimgS|0l~80Oj_RV}ww5Q7oyc6p zl3=n~OEvqbmYu}2%8fJBZ+ImeC-#OCuTTuB&b9jIGzz5N!+fv35f9W<6_8M-3cLiUJ@7SaT$|)4jpBz$4;)0q;feWnbgloFv3b*8`aNGKIDvnJMG_ zqoLb#hj{9O!9XI}jjGg)gGnwvWf>$CC8MDjqs0CX-=h&;rItu1bt#Ye%;>~qU9+2d z12j7?P2OG4_|b!K0L_igk8x%eF+^HuHPW;NoNf0)VK7-V=EMDczMuGSv7szSv4I^o za4@n9FP~|bf0OU&8x2AaJJCDogl1v*R3zw_QkRF#*`FnJy zv25f%;sCO;;|T$f%y^|ELaWZC9lE6nyf@W?_;lMJk{^tLrX}%E7h}g?hxOxt@$+XL z(muamc?Jqht`e~rIRjGKX{C|Y$~>%0LBa~PV`mQ2V|3J6!*bb*3Z=SXE4lmgf2@o3! zVkgTF_`WG!=F@56aYXzm0}IcOTy378){lgv7T8`WB1gbjm+1rGH4%!<^Ah5&3Jot7 zA|Uru#D0g{>4>|8sAt${z&B8M?0qzDSqx0y5Y^@c&wqp`?sSKg3q$OSv<1O97(q3$ zjSP5(d_spJAeX`xyy#jtu9-sZEg8G5K9$QHl6T85+|VXiaP0zlf=7NoJUVm@_+18D zkU$sQvdD}e*+v{bitssJ0n?`aDSD*q!bEC#W7is&#Z$oxnXW~)Zq%M9H@GqB;d_JK z$0jexm$u;N>ez{)AE~Yi$Qb4(R_CoL1Q(mUvEx^2sEiWs6$p(c(le^i3;ZK`ptb!< zt(myio$}>b>%|g8=Q#~{`>f0WGHjnN^7i!$`r-frMHtyJJ^O`7hC>^sE~4(zX`Z;$ zAE`K=8wHI`wDzMDdAr-?7fZSN9EL1PF+fUS6mkl@H&p9Kr2ERDk$=EAf>0+F(SZiF zNFRlMsud?)l!!<=M40)2|AH9k1^5K5RhL9<8rd0692G0nmkv}}GEfkP7+bo`?CL6A z@*pGrK5IptlxbL=aZxNPkf1b4F=YSAXUac?@XpmXQKX|(15tpKzBnf`=k_x;cX(t} zwpvKtpLzDQW3+d#JXXOHdUV^EP^|cFe)`tP#7L~z>#2MD<9CY;M07SvYj#+|B!jdG zVnk<=nn+LQo5=rZ$|TpS(cc*%TPl@x(y@wUR!gIaB~cT(t5I+#F9wr%E87hkmu~1; z7&5xz6Af@44iD*vX5gCK9a}sf%PBh6rkk*6RvIy|Go9QT5n-sAjdC9=IRZ7E zV=+W3x*-a=%;^Ux#`744VHMsLee!d6nFQ9f1pM_~B@(~suWV=24ddGuK-egEnH zt70lSMPn@ys@5bm$*OKiqLP&971xX;!xAu`Fnu5*11qOil)LLk3?&1NtQ>}^IHx8= zHN`n|oXIp`61Nwz6CK-dj!@#K0pP6i8>yJm2z*71wiTIu_=YhLjCK092mBNAumo{o zo4kKB@FMi%Pw4jUh``(2t)+mzio^q~c)UvKhCDf%Y#kd^$fN&N>NK8%iNkoXg_%kr z6fuNwGk|ZKe`2iU;dKsXxBdYcfO~N zcX2}*X~gx^c_`Iv+-ZlPpDc)tWk&G!+$*Q13&;>vEbs3qkOl2%lceTRdBaq`}gpopn zz`u2`(aBz>PDG9OrmzHyl#p6AS!F5>84 zV?Z2NsC*7p92;da@!6j;W`-w9Kn;!!LuWXQZR3)NV6I;veb0ya=e#g4I0vTNwi{e6I2;#oqNkvk9|S&SZ*$3e^qjZZ4TEJr}olT^4K zZJKeo&9{Ob)Ym_?Gvhz$ocMP$0bAixM!*Oa*{jwR?-4}1yAiX-_Jy%9j!^Vx=O^{E zCYd?=I(t!`{T8~w6|E9dC7Fqz-x!Q5A0aj(4P@vHY|2SYh0A!X_J5DCAcuaDlagw1 ziz1A&XCq96B4Zo*k%72bpw1s{qu2^|0+`yMuS8y(hT{OisfQ3x$xjIk9%Ny zf!{x+mWX>Ok|jzK{!T8>3Z0i;k6a7{3tRYNrOhLJXvOI`L%Nx;^hW5CFC-HpM7UKy zy*O!=c=j;L81|n;_Y(e8u5hR7%^P}D8IwgWCBeIZRP@gq!iD#!aFzkh8lK~au@oDw zCPGsUSxd~8Hq=z4b+Jt#J`SNJ-JIdC*U#wKp~b)PzZ(Fd_35g9a$LJ?w7#~MmJ+-U zdNaBB!jA>-B!5UGD>dJll(KY5m&0gv&YQE8FCtpn(tt1AnJ$cnL;SX~ZOFU}}0DJLs@~ zu5DJfE1UnK+9($C&rs$R5X^%Y`lvRR2<^w~o|I3CFAi$TRC2xOjth4c5yj}zLeIx^ zh@4G`xnb4td-o{4Q3RE^4r9@~yb!ekl*VBdJ%yv#nH&ad`}=rfqBSH^0t7SeHXo~2*OUL?M2X&fCE}E5vxz@%&QHoLgf=HhL&7q9b0Khr1BC;;S?Mtb{oOJw5bE&O?FzJeNs-T~zK3N20nv$qzqJC*EONV?~cU;XKJ z0bz#!w`Ue;0i=LXQ;T0xuroky4{pnusZ zs1e}*ow;ZQ0jQv6pjLlB?T~1*gFo*)t_S=iI_DKBITX74R@*j#j&g`|9`dMplP zZFL6CW?}oet2YUC1|7#iEEL@giZ0OxI0Z+z`g|JDqVAqu4N7!i!~kV=p%v$*6{Cr^ z<-~Fo`7*AN#DYJ~P*QOPWVXvB2!z7tXShs4!Lq#mYQ|-fF+r@!D9KJm{{;^vX3 z($R{z;6Ai0QC1#Q;38>`%$S4eX-uHR1UwZG%v{?E`TUE!j;<>8de*F6o>fm6MrYSf zYiG?WmStco?D+ibU6WlkYVZ#JbIFd*k8A(<$3J2*=Q@EZm5PirAEpKkYwJy`^{v{f zUVdq`TB?AP;&e$+CB|neZFEL4tJwzvxbRV&%`yS>jZQ9eNrGsDEFZpABU43=`9+NOoy-U&O5xF6k9iFQ{=#uUm%~S~FndlI zT8v*Qk)z@lIFHWHj;hU8^Zb9;&RQk9?(pV~YV+m|L3#70_2vz44IiVc4&|jeq0BF1 zaVlyESyuxk}R7Q%O=x;jP75@Y%yy_tz_MYdHmO zO$l6vysgb!-qHaIc~hjHi`=CwiG}7m2i`?RG#v_wXzJK!64kY56V>&fEUH#kgZ1#te1*K^AD`ozU_ecr;I4lpL%Rx}IGE#{dQcvD~$Vx1gRD5@&bp%|(ngOW2TjrC_! z8XK=oX=FCoNN=$5nzhVEPA#)BQOj)1Tg#*a%+xaaIMmF%M$J0KopvU!PCN5nozel$adony8C9Ex&t<<@Z-yelr{Fr8n4n z&Fl4E&h>h4;(EO|@AWzz;1jRcDNyznYj_SAi#0so8eRlOA%&Aec8b~o=}?~34wxu^ zGuZ}v&t@B}J=x8{g>mqzjDySuYpWSExB8mxiM70!L)RwX4qco3_0SB6Pi|8rnSp83 zy)Z8(!&sOX&u3l~fq@d0@izV`py2WdYT*l-V=hs@1Zkh`Lg<9c+tMu2tdiAIU)UbH ztpcWs#qhtr$q;D})>0v?6@ajw3SqqfgpE`P8wDV2rb5^(0AVW?!d3wY+o=$?3qaUO zg|Jfq!fq;r-2xEyQX%XW;iz<`T+8H26Np(lIg`CfP$pue0~^EX#et1s`r^RG@PBb& zV??1iurX3m9M~8EDGqFmtP}?}Mr?`$8zVteFzq;D$^@6XRJ58|(j-$6<`?k{SZhtqfROQ^4BJfVDjZtep&4J5#{g&49H#1+2XcSbNj( zY8K>L@b$64*JlI1F&6m7Y`{0i0^ghs_|{n9TeAV* z9t(VXHsCvBf$z))e0MDH-PwTejRn3pqY#Z3(Y1Usn!b>JuQ$K8j;bdo%|9+`CENxc zs|ygYcxm#&S_Jl5A=vAMV6PW~y-^7EMj_ang;b?-qi+R|xiAG1)05m}_GsbKJsTSonoz zOw-L2I%Aq{rcfW#bTfq}nWmd5l*%;SOrc+<63=N=&6tid{Pm)`M)ziN8yv-YS(~bt z^@6>uPu0st!Cp3|>SeQFFPl^KvQ@B`t*Lt1F4)WVRK4sJ>}6-FUUm!ivO85Tdj)&h zn@%c=3V(brqo`3cgQ}U%yqQ7kOlRoKpn#?`d1lZ>(-}WAsHN%5pcypObOzB3%4#~( zXa>DCosl$y3Y*Scnn9aQX*kWI*v6SqzyHpvR1`_8q9iJ&+}a`Y0UbQD7j+QW!>u1W zvW5-&Tw$y}#A-GGW)wQzQIFT4dGtrTF3JN|S%Gf>!;^i$q~l=W5vT7CS?KoxxD$r< zjT2%G9)xmh-#hG!@89$sLW|uyf!pPUb^`b2cBr)o92TkqIRhuSwFmI)5Wz&R4_#%+UrcJi~%KCDW1qiOJCgS`#0E*jG0R@X%vCw`D zIXfUrcHID^KqAKaC?deMAgBo7+Ufehkkla~$}>QU*E9+pkm62iQ)YLESmp?uAsEqe z9a|x-Tsy-D#}Or`9{gDAup&p(bK4-Q8cHF<8emwf$g#smsv_j{dQnLu zg(2vNHW1C}V4W;_Oy98u60clIE2#HbRijz><%>UB)w5%aJgt8@Yn621&^Aw*z|{=AtBG29 zlQ3V?Ft4sY3+_g}!_0wT*}kHKSvLkl5T1QNMghmMqr;kXMMt=i7$HIVxuZ17R8AZ+ z*5E5T?9J3+6Qmb3^mskZ&0Ms=VuVHMv6V>=g7%VWqCefrO&~1QSd>WHV~Io%Uk#y* z@~(xbhSe>LQf_A=AfoikOww0?GUa&=s5@o(bq z+F9+g+N>S7K3|<3;mt&G9iY+r+Ty;Ls1XXDdC1i3=!#GwQm?Zw9mm0_Kd1~I`PGD# z8^?137*=P+bKA}eI>YT@<+g%vVT(%ywg96v?g%ROt!r&nS(8r(+?#ywiH3BD90BC7U1b9c!Xb#+A$VcXJaDIT^`6Jde z2L9>LPMg<(Umau>K&9^wsqQjR2iUL!I06^4Er80+P9}^#*I4e{O!SUe*|VzPLR9RzC)*13_titufRKm-R1So9yfP$uS5k z1L=WZ&zhI@Pgl+J%Lam3t~Q|YGCixF{ec@hm$gQNonO*rh*9X<%)Cig5YO_k+0~8<}yaAs-T{Y@NLb^nLd3AyMxeRmu9Vi5tR-yGV(fa%h$IN-B zc7FK>f<}7KY*_63S6HE-1(27hRz-3&fO>uXUry?v_>XG%!Z`x^ zUT@UOFnRR`zEG#mfg_;XD;hga8sJ9L_1R=+i)NSAKeOubw>si4-UohY)CKzx`HsE{ z>PxYxjzErrecU4c_ZyPt3M@ga^nY({@6iY4^4<2trF-0)=H<`Ln&lxo1BG=;yP9YA z&CMhKhGh<#Do@%MaeukGQI+6l|rcqgO7 zbr^sIO@&0Xf#!jSsL-K=?WG{d{C0a3gqA#ii0GMj_(LRSh*ZcT0azeT5P%w%Q*<;6 z0+{CBqnsw_!NYN)ebRBd_NX_sKohHu1ac{~xORwsJ@<|;cUW4z!q`{;!MS$=3oENX zxG>Dn_i2wg1hwA2#Wca9$894TX3w!ZVmYQQo|vIPzodTRV7P zCZX;|r(a4ju{+Oy@PL?LByuCh6_cI7GffUHdH9tkGrkAh{on_{O|r1M5_^8kDb$ZV ztd8h>V@s02ft^opn7lDh_<{_6ecLr8Q zi!bo#$C(Lb3N>9?O1PHb7HfWgv@jmwC+Tv_DPsG=g^pe^-D;r2cXR>pl(uV=7^1Np zn*w(2cj%1FCbtvjsxLC%*dYf3`kAHz-@)Tocyr)`S&f<$QM%=drtkExoj`H!5odd* z@I}v_wh>~9GxqnZxH@0ys}ef{yAVqb-$obsfH~#|L52Z_hZ#lSdLeI?6RH^=$*b zZKMs-qTgTcK=m)1Q;pgZ*oxma;kV5!N?7#ll~5ygJTwhy45GvcwFSRzjU$vr&tD_4 zuBdSe^DZBL@6MeqC~6pbAoJp*~ww;lMlGm+Q=9=5}`e?_v#4w#;O4%D|@ z__mvv5jg`1A+V?|w)o{|MgOw*)2ArbdJlfv%cHgAgjw|d-)qv0!^_p5KZVBR#bB+i zA}Xszr_|z&o^f99-C#Jfd+h&BcDb&+KqP3xZZE;9p~Kf1dM|L+D$z@vbZ2?Iax3E1 zxV#eK>GBpH_1t{k z7ejG{@G_sPloBtGwJCVSH5edalMGN1RCnU$o= z`%|arNqyd5bJ9{=-k&>#&+hX+!MsK;?_`(j=lZ-7zr0SbcHsU>yxs`o>^b;syS-o4 z?bXIVmESvW+T`_nGw`0l@qL0>p2G3XHLe*w-=9Cygy%cOu;=FbzA$nmT;FME^K`!N zOQu$a@B1VKoTKym%1D}UexHH9PvZT)X1XW5-)EnMCv$&)nQ2M5zdw17p49*SHD@j9 z|NiVbe0B%;2_{xKzewZFYrwPF>!UnolUHi}*@)Z0OaUb{Df z<>0^_;sWXihuUvpi1)$!#!r+`RsZGQX;ZV*ON!BN(qq7zi^85qGelnNbXP#tmR48r zdS|B$^mBS0d`6|oE3pB7uGdbETbMeyRiYOx=E}^$%CZ)jt~Cu z8G8*61jxHpN=XbKJi0cIlOF<%HjRxi3`q|&n=~=DqT%4MINp>|81Fb^R#I^q7Om3w z;y&;fZ%NH~N@5}gCjniDb=aNuQ$!Vl^2Hf82rH+<@h=U_`=WGmDA817!FBS^BQzP^ zy&jIVThwM+VNcvJ)@ehpA6)x>&!QXI_}cK0YKpHOTJoIC93u^a+R=4xY zz@)4#YEc>nBS7Vy{mx>W>t#yBTH3@d2>88RNuG{McJPKUX7-V-tcfpc`j>V6%Lf0l zB>Y--x2Zqf(!Xr$Uv~5_yKx|U^nH|3z@cr=7}A4gq^Vvc5^_$^k+z8+(HS_WNmYCN(v0$ilTOJ*--B4 z@Qf(>?A$oR5A;Y>R!49p`0?Ow^$^sC#pv^z_IX|VyrF&Gq|a(Uh<@ABo^NZPceKyD z+UGs|EH;R8Z1X7bKt;UD{vX@Z#UfoUQg~RZcNE>p{~zXM`KhMr_xGTN-~V24!ZebN zRR&`QkD6kRgC8gF&&>?c?^ITGG+SGIqUREBd)+SqQ&>Br*XU;_W~zbw~yvE;nHd^IPqxa z|DqG0$AEv~%#mh1&zoVSn>PjHfN%JBWt{Pz-z#J*-6wl07SneUrertVf>^NI6dMh% zoF(;bg6CjQxzRNPo8m6nf=%bTANC6|vA)-(G4z{}cDSPvA)YT8H~g!?O;{?+v!Ud! zFFt>vd)l7h$s%`R)F=ih4Z&*$1Do-IIe~cjLq-Uv{*G}KDK=R` zy7brI!ocBMOR!>DjAh^axm9Y|T}L#*Sin@YOC~5;!9#{7;>i#ncGc_D07cvNqA9`* zuVEyH8Goqxr4x>N!xIc(g#O-BzzBXS3)bN}BLzdTqwI8A*cq_3rhiBz@#iwvD{hyS z{vjc+9M;3S2-kEvrLrWGP(CKDDx6MX$>@y=k-(n|O!!^*I&K@@h~V?H*iu>a{$4&| z2M25!kBOG?6E$x74LYVxhtHp-&`Dq^0e{elL^DI^15MHfqfMC6>BRZU`197pMGTsg zrhDcOYyH9Sv1Gnf0TPu!`DW5gOa@_|h>N9rjK@yc4%};}>6gS>;&T}9nID?m^WF~? z>E#*j@`vOM`ifsnvT0q3SF+JLb8hGe`IWVY}Mtzg+`~+VdyO)-AA;Ps% z^!?AZ)qjB`{gunmg!T0Yhl3|yB?1GDS3kf%2W$=h{p~l22iHro8~0!E#%ftn>l)C$ z6M8_r$qqlVwdf5w4hy+v$^7jNsh9{hN9u<${xat_B0C>;#!?kyJnAeK87>3DV~bSA z#b_OPUaKgwVK(%q#jK(T$gci0(<(BJowTKRhGp1%ODwQympYW~1<(Bf5B^a$NA$F0 zsuv-`R{W5H}+Za+^d6qsSaUEA;|Fk~-5dQ*^zc3R-|dO8d^x?uzPOJmd$H z;g2Zfhw5>5ogmsE6W%h^^~u+X9sv6fV`1DdUo`B^wU{c z*o8b6t=hwg&YN2ffw^>ZU6uks^suP8Onq<53$b}IbkV=;r4ecG*@(0iBa(#}y-Y&sU+`LP zie<1m9o@EKbkkJm1lk#Nvk0$L;>}{>d5_Y>!%x{J9zLDT#6t*^O}wa|Ih%Mo;JKK1 z5kS+#tI?gQF|46sGR{&mUYoa(7XjaRV&tF0%!_*4oOU!5Z6V{;=QZ@AuD70HcrU}$ zi}2Z=cFegf9pm+vX6!}C?K~3^UW2(8VY@pmRdU-hMw8cN@I|QaJtLWZ0h3Q#;#Q}n zTp^>+XqMOLi{~kj2e{no3U0!KGr-_%T^ML*cZVfA+BxMP)D=10EZ0v9s17OL<)>TS z(fblag&FB4MF~tq9c&Qja-;+?lqKN8epaOL?FG`5mN7E9RGgT}K{(UCOsHm!)dDBQF4q!~Z|emdzhPK=+Gep>7a9#%gC4LPjU=OHHV`VMXVpGH-S`Zq}n#&S)B$5sQU zF&=Ht4>3=1mzh$D0}ML@o4>}B!HZ{pH;#K5bNOMqCy_bIX7_e)MA@Ef74OS1PrvjS zMguVZ5ezt6%4LYs%~0YfbV81uw*6QQ0{c;-WNrZST=$`gjo3G~+ihnsWIVkK3%cla9hKYoTJY@!^>QYk{3cO{rccB31(pEjiVbk_{ z$*J5_Fhk2Ncx?krD3TB-4NJqTO(p<1Trx+bJI2YkWw>ubf>JufcMnTECOocdlv-%6 zr6_~E*sE|H1ulXS@zp_7?q|nGE28q(-ZE!x{K(_J{iJ@Tq!XYqhEkd~ZLU8f*{r+` z)^pHVrloDJ!?*Q$Qa!N&GYQG5C&@z0^G=lhW#c8!TJ8oLg{N74+kkHyKXKA?zP>RZ z9-`TFMkSkyxL%+99zm#x1y&*pBhHCHC* z-HKnf;FqmmmhE%CzBOMdv5=&5mXz3rO>=u8I)$b-cZ2OGmNoT!8@_EXB(Kox+w&Jx z3uob}g;jsLvyj}UmE7D7cAiz#)wdn^wzClYsRTdg>pM@QCoG(}XVn$@m)(Wpcwveo zcZ1!ZsA<%d3mF3myELb#Ttt zHNJdeoox|ksd%a@k3wRcPG(@0#Ura+3aZlHV$PpK$HR&>yaJsaU4Dqds{nmy_Q>OI zQsSAI_t6YzO6u-33@3)*qz-H9Se$VvOdpg}cwj|Wr%XcpgfN|)B%Cc?=gBELRp8FV zDR>PLJh|s@uAw|HZ$hVv=_$wozhH1rA@+H?D4$<|pQZ};nTY`xaX*E*_n88K{*LTC zRV2_H`F{}-luhXQ3eeAr4O&RmQ|uZ&xfm{@g`PHAXlfZ&`wu@qcxWNoFJeRZnRUS; zoajY`6U{-9yu9e5g=TpXnq{gmqo1aV7V$|Rf%5V~>i9ST?qN7pMN&r(=ns<0Ak!^@a)f7I(evi~00JuE2N!BC|^ z;M`*}r|R(u>jr+GbzMx8RLPeOU+Di73TGFZS>nSzU1NRv83t|S+bRUCBwzg2*AV|tsBd=?@yXA;It z(9`XlG~G@arKKh9&q-Ho!us0<$UC9q2_z;#%_cLE*g}M!n#{3c1G#9vlQDEls}WF) z?%0y`cM6a{2M%+Z0t*p))@g{9I>RN zx#bvXMIGia$ri$c_NxVEy8yB}x;f{ZtcC1(H|E?T%p*@ol_4wS3E^Fg37(LsKlU>` zq0Bl=OMJM6JfTcr&*KS=EiIHX9xJCOG#2i7Pl&V3vv@)iYvW{7z{=?fP3$5~2sNom z6+Hg@`8}ar6>%og%*yErCQMw|Y&$WHQvQsz9<6XNe?dP4E)exl*hlB|kx zT5TSEtzEunUAyo0$ouKr?Z6LZ0S+o0f!EYEC}Go#>eQ7R-hw%^oI7I;s+)%D`B@-7&>eR?f@%vw0Qjum>U)jfvM>j z?F0KET5^QKs7?qkd!c+f8V7g#oIjoR2uQ+f7Aja473;Wl+qb-ghM0ewUHg6y9X!|T zxNQ_CQSOk}JPAkbw&QfrNvwOJ+j02Ce*-dm^hZS5!07^JSPTSS;GXEUgH=U1(j9jF z$llq(%{xNI_Bv4ucNh{c%2!(W<(*#YL?f0gR?_j?qKe|fEiXQz$b+>PE$RZiTAXfC z?c~PsoB&6OFL!OXHwvJU6XGmlSr|_nJ3hvN^O_|#LXh)U=3v1=2KJIAMTntp9UIGy zblo1%=++MSsQVyH?x081!_ttUKMDXQzJ;Y$u=MzUk6clcMjt7l4E$=dqZK}81R!d( zQ1O;~-|eu0ABOHVb_f&1oNm`?4{64Rs^Uz4Fnq*{LYxH<3Q;*zpZx$h(8lUB`w6bEhc)0;i!CoBp9>Bw!ECet z#lW}!_>rwfEH6sdAaB7y9|9P(0*Hlm{v6;aS>JEpwYt7v`D6zruPg{A_Az*!9}g!B zcP}cub?rYcYDdl1QT61cB%50d+j9g`jZQZe_1^E`s*8e~5pX~@pQLQAMFPBBDWl9n<0ySw5-8P8>{M{bm z!rb8-MS@uj9^xaGOjDILlbX2JnYvVI(=&hQL}VvjA$GOWG)+nBkg}-!({Vs(`Xr|y zkFv~@^XE_r1+khG1&J-!AbiVgFzAWRFnm)%=?_4P1FG1Hzju7B`RY0RO%S8tk=Hij zShRaK=`NVWk_WcS3y z2Z0^j|G}9Mkt$(=@e(Z9Kv8pHwZIue#VLzl4I+pJ`-oX0u7xIC?Rvyyw&zn3FfvRM zsz9QCEE zyH7p)wzO<7mn|0W7!FT^tj{~j$E)jj$h^7Do`@!&b6 z)X(74a1zKIQ)FtbL<3jH9HO&hn!szE7EKWVZF1;=R1?2vgE_6r{zbUG=T@REtko49 ze1}ErFll_EnPxR8Gnyt-3t|()c&Bk&NDN4JX$XAof=P%x_*?p_qBkxuIHs zIQ`$z(2af(D_j>Q0j89y?nM}dxBjRHc;l3j;q1ro(InynnM|nH9$e6Ipt&rx~J zD6tWR6O&q0@~aa2QAn~S-Np+Px~AVCYJXTtsJ-=4-8<^}urOZQ-VF>C)R)C>T}!F~ zCT6OnQ295qb3jusvwi#s>hU+Wi~oq_FL52K%fNB>21)e@Q?ZI)}=@PLUk+K z3$2Sf_247&6MOrX{(*0|?g#$b5B?7bLQ>CyfEWWTfrGux^=tA?&~PR>N+4^Sn&JCE z^A@>F53GG-Y;IAwX5F6Jwyjg!#;I-Fwr$(CZM!`^wQc*WruWX|&SWN&`7M-3-mwFMTT1Dx(*kP&df`nC*Y8uM`W&+-_Vt6NF-&0rG0ly@ zAOl%0d{$hwQyP*6jjjv3^=&h|Htr&e8AGy$rbGxplkAD#(0~QbZ+3TXOphAHkjm;~ z(*$c7)rqJyrZAapk$tRK2q!L6sWPumL?>b>mRy=!oWlwVrGOtD7%*p59HY#AuP*k6%`NS zAJo@3bZ3Ndieuli-PusSrbm{OF+2k5G#3vPsjOv3k1z|hX5bUB!-FbvcO ztEr_@j^H9@!NAo-p!CSf?h#@($by%D5QV})Ftjr&C;4E!uOqQKf2Xr`p2&zS9aw0kPC=Z{EitX!*Vfc-2rbCwh-vb7 z2F+tDUDDOc;}JYX;4veR5w#D)vjy6~!~N}mcd9hM`QnH@z{lv_!pK5vG%Ev57sa-T ze<|P&B=k0dfnM?Z=}8bi9SrszHYdGohC=D_pTXCYUw~q<j1uQEbj`>GTirA#@UUXsUpANJppi_T^j3 za0}eOui`@`d~1B_g(< zMcV~e%#H{B3*z)YzF`+y814S03Kl4he=IJC<%yPSu1O|zzHq1s`A;_-UzSu`TTrXW z9A4*<4A%zlcgErFGS^!7b^RA1*aJ$VkK^U{BFxi|i&hu`)I7k2tY9-hDh8wyNefr^ zTT1rxdj=XgXJETkH1W|raeEW+@T-la^k$;}QMNSokwP!#GOlIogncIv6MU3Qf*vwR|ZzvAAbA^*}O`FLBdOT=Q$2y2{`|@r-wKP_FF(iA?9N3C7@THiJMVAZ+DW^#qf0eeb{iv7m;`X zMQr$%P7P_LoGRf!<&A-$5ilr*c&rCd*pmN;9^&?>>f%c>%iee}?_rC~cH2L;Wq2>a zP@@4js2|MC@}2Pfa$Z%PhL}iriKHg0l%QB|mn6u)mWaGq$E5?tzq%F^!x&P&O~@T% z?oL2;`-z3zi$9x zFgTl7KT>SvQ~wTSHV&y(@Q5^4^ZgX%!&eFLpbJp8cGZ|oL%Vi?%s3@MJ&*cK@PaL? z!5#RWtf8%}x!T2xV8<-_=%?WUGE1m=2ZzA2UKXnu(~I`)^4#ofKRXqi7(iN+Dpexi z+VNOeH#9Ih)GdcV2H6om^1EUnXd;Uo+h52?2&v#wc#VSTZzgA4Rn7UlOr9J;I8czV z*$E<{3K_b{okH8;dP50&%-0oCPsfG@t(%)NIIVaZQP9kF22uA1b3wsO%O($vZ|9}z zmfG1`?3RO6B1x8_2yjX+Qf*m!VGC~gD!Xlw19^=ydfk6QWNmJbXwq{0Aj0VUzTPzi!EJ01(=uB zr}fBC5p~}m>fXmUyuMGexsOfQF2frn*o&PBt_I7#1H@l3+pP*L5lK2t2`6;+0!1(n=qgJi^eSqn0-2U(HgZQx z-J&VxQNC@ktj(%fj72TujOO$~si{HEfKfr;(x#$a~x55vjua9%$aSQOC(W@$g{RPX% z=tEn5q&KO#ylP(G!C3GOPu^T=^t<{@)@Xq(EwHalbVi(SwsPB(xoX`!K^YK&+5<81 z=dCo^LAB=-IrEMR&q zV{kmrN1X+jo^wv~(9opQfQ!!-?)n@!v z-c_h^rdUPo#IyBr4gJ&uhemz0M?*9f)=iTPdwihf*-0m3#&7NrEv0B}Z0HEIFEmdM zZWNyEf#<0w$G`g4^7sC(==gt9AyI>x@Vt6IQFus=`xDRo_82~8KgnXSAU9wrEcZkj zoUKOwqWi-Rk)dop?`$kKhJze**W_Rv+GtozE#ewhje6)qfElR(I&w849kT~DHasF` z*F|6$mJ&8HvPjAq`^=J+>f_6Xozb<5()`}%%Q*lp^(#w-G2R7G^(HfH5t$dD=$2Tk`h=y|5boBqAJ z)Hm6)V_cJGnKN5(%{J#U=aZ$*hrCQwFwTN*`Rw7BFA#Se^4|S2@b+1BSWxT4IH@Tu z-=M->-C8;BH_gnekh{88;%Mny&b*HNs{X1mk@<4BbK^pmsq0*1hYKn6)UmZEuan)w z#>fiIu5EZ$-g^ObAN6E!-|N4u+@(M5xKem5c)>7u=IS=1C1`)Oz7O(Tun2UKg2hqN;M&EEL3Y8|dGEzE5tHkXgSDv2+Dj^o(Puw|BOl;C_F&xPm(>(tCm&h@rscer z|60^$)D48lD{LsaKY8^9s^wV_mw5ubh7Cy!Gh?7+VIjPtwjxHZ!Lt5NPJ@-E_TpZ1o;Qz} zhDgRZ;GpZfeOhv(tF8}z92VSp2q9EAOx@6IElMnWD_J{L*L+pJqS)$kh zlN?|ax;(iKJJn7N#9KThb8$14I+vC0TSYDz$g0luo7qZ?(B>bJo19CHNb9TGcgL$Y zUAyUPVkkQi|L#)Z=jQ6*s%?2?zjM2)cGa)}fogdBYwlx1CzwRycZ=E1-X`4I+X?;g zHu7TU318)P1=}(87G8+5$&L4cN1(%nhXHqUft0b{x?)f?4?Hp|ecgPVPk*@3JuftOMIeqdacy&7bv0?(cNry)t&Ugx#ir z+I#YT1KapudXJ5W{m5q}X6^OzL;beL5MeWRQqo&KV}}4F^>YFV{f9`_@9TYI;(_>3 zV-I1rIGmr$a9Fc&gY#a`TujaRV{G-pJHM#c!Ue*xbx4UB*Jq*PmD&k`LS|*!2}zkI zV|VbM@a)W|B;WClhr%d;K^aDVY2T)j3@!!_2!rLv3F2&K94K0W$$`NAAM9X6p_RDH zb-}Ly!B-Dx`MamDBW&PMrP^)gBgLzgC8=uleb$ICO2^JoYrT zu7DDT=EDU-43bEJ4r2MEOE|y9I#mo3RAKn82U``p`qm!sx{b$Eldsa#k1m<`7a+;i z*;$=Kd@YbTx>P3Cv<)UY(VLw|fU<_uGG+Hvq}{=86wpPyzZ+Yb^NxK!m|pUB0-Z`|J}IqPpn1W?##f=vbuH+ojL z?_`Ppcam_`{|}Pzv%c52ukc@Oi+3MYKwn$y%W06~o2}OonYYq7fT}vdI%JJ7jqnt~ z*FJ1A1`NSvVZpACNn7k~vJa#c5&G## zIQqo=S}vOXmPs3MRiU;Em+b3wliSCS#^pe)fQ}OIKe-ZzcECCASKCm_aiCpp2Sc~E z?_tu?(#6{lZ0B~p^G$utJBO)oAK^u{;zpuN>Mz%y+-3jhq6F12=SL!<$?8cZ%#0T2 zNaD5&WWCbb)QS(Ja;g#jH0!9_+igA5S#983suh&mfGwiwul7@H{#jc8P~-RiB1sl8 zQk-F<|2>TV?{)Wq=Hd)hL^`UAu^aZp#6d2|}&t3ay z8Au39WIqI#|*ZGvQQuyx7`=j=w=0^}c~Y2dE8pE+UCdm4)}}7%)xg#d z_yQ!qARP|ibu_YGP?PQ_bPp<8k`!WwNQB+0#h|4-foDUIot-pTgI!w(dxH5i;OZlI zAn%z{;S11M1|u(lGBW?qJA>%<*Ol+1=CGOp#`@I_l0&fi8oWPRcx2+(kPfN^D=f(O zFASjp6Ve|iEU{923`o;2u=9KElHn-qh8#hsn zcHor2@`NljZa>yi-Y#7)eaEBX_%;^pm)zP+^YIzrS*N_&BVBkv=e1|7mjG5E1||i^ z=If%-QJ=*q4B0C)orx=gTvz6u>U2!R&(ZS18E4U00>INO%)I36A92r@(ac4F2PJ{zkD1_P5l}I~i=UEsu5uxr@XhTOA>cohcoDGmZ@)>O{%RM0 zo!o?Tz~&m#i^iF`)O?2bk9s?#_6Q?hV>5SX^S&n^-E$$)8v5WK7)=8xXaMWK>(!uw zSt8f2E7T=5XxSnzi}7#@#m zgT#Uv#X^wTf1(^(c)tr|o+)7n0(c4D0oI^lK?b z?t0WgHS6%+=_~y`9fE=lpmE*hGc8Lhs@NU@i!LC=x?!#H`b5G?OJXZl;Y{d+Gi5P$ z;@ZJ#y>=jLbv*7)9QXbLJ>rL9;>mt<1clJ2oqmb%yExSFH+e`cIt{PvTS8M z3i3 zOd3j&++i~)q>%CX)sWjbuvn3S&r0Ds_|>p!TAj}1eEh-=4vk^7lNp2a&&sJN(}d{oQpyO?39u>9=<&)8{~;$rrCV@r zQt-APe8uo!B%1NNJq|(kM*VphRM-ulp=G^hcPl5q3Ri4DbzLjs2U+4Ou8I8Wzv2Xj z+y?``UUCw>;Pfj*IvxN-+!S%)hqQ|s&!p3^u^_8%*AV>VBt|`N%=asF=!u_{VLT;a zd+N1JnO33F`Pi;1-Vjh~0y#jX9U71GLtNAGU|8sI(>2R$wI{Y*LRm`}O8At;YSW7A+o9zKKXJ6 zr8CN#UG=(e*BCX5qc*08N~%8K_D-DNH2ngtp4hA5j#n(%V|Z%k3x(xe4F8Rxpv5jK zTZV(dA#m|ev}o)G9&P8K*KW6Y;vP*Yy2}kc8b~$>GAk>0QUvjW2ePfBt!?ICC@x~# z%E5DdV^T2Bd5L16ZD51&$uhMz$H zC^0SeE3h%(Du}PpOOr&(%2TM|32BnTju4s^n7B^i!hClzA@Z@2#zx|6jr2PTimJ=u zeP)b|NL{59rz`aN3nWL7a?R!D7ee9%w-lqZ;3>avf+_oyNIDUh3$#Bnn1#L)*>bdN z`5A+{XEyr%C^`KSNm7SmAlKKoNotIZw~JCipw{uD0>Llj(R4*ag+@_?k!~we!~==Y zfr{O)_+u@dRhJ2IafDDuy!(XJMr?PGS$JIh$}q!45VU_%k>UgIA%&x%`X36|X$LBt z68dG3HZ^qSNCI!M1NXe^$dkxN4A-ka$i}OE2Q8+5mmA(wATkvSC{j?Q?b75?_8L&b zw>XMr4I|7LI*?o8L~$gEW={~$8Y7uChX2+W&iIopxdXpmRPT7jJrIL}Nkmch81Azl zm&*3O&)7|l+AOaEEhIe>shx_jpU z+uZyVTew!MgiX%2>SWbTt}FyM+&A`(xX*ZndW(CYC61li!x{n~ag{eeEC9pi5Oiex zxZQR9z0?V3bIojE9EA_aP4bW7>j2^FvHuCwsN_lDtx6|yJ`7--$Ju=mEs8fLw{BJd zD2F~^@!5_?1O~{jFXUHG8E*>5mA$6$*d1+eU!Wdn+09f6f!X^uajg#m!NzO%K{F`P za>65wUPLB|c>ix)#QMao2B;O0ImaetjnpE+v1NZY?Yf#w)&Q|)oD&W_-*Y3N;0duO zifJ~q!Qgiv_vT}Avk0KS9Ry`e>C)NR=##Ju56fa}g*A5hi0RJwP(|S~edTShGaAM~ z@W@Dle($`FKW?}{xIkt$c1BioO!VyZ zj0~nOPA0~5PL4(lX3i!~&I~Hbzkonpg}4+wezuD{3=l9_8x;@`SU3t0(0`z!{@_6U zCu-}-mqx`RFpyC-ED*{61NA?#>COKO{6Ajszky$BU)UXpC4TeyiJF02u428hPT<+* zK&GaeXJAMwi&$L#f+sYuAiE1Cb|E#1YwY@d|E%oHVwgxxf`fkx{yn|Pf(XcvI1X*J zU`{e@nQ&xGfy__0hkTg;g|qNL%;lIEs<1ZTm=*yu4le3+0tTpxoun?)& z6JrQNwddFfXbZv+QI5Z*nG?xmPH8Z2RU2nRnl8@P6Z{fWY=Cym2o+3D2|R%tA|xum z|6^cF_`?f7bf`}hQ5B64aN`{HLOjmcr&T>oWEuLa<-j26*DpeB%rm4USqY-3DoG!b z2>;I>0=po@Fcx@l=zcuW<4a<)h*jFyij%AdL|XznEm3Sls;yDRNnrN?XAC9Ri?_E$ zthN7TZ~XJr`7*X05G<(GytP-Bu-JnYh10guAf8(ffyaXvEk3(sd>P9l^ssyn^pQGN zJwRcw11H{k^2?1h`>Xy0hyWc^BdEd1i*65gy+?8k%UKJAW4Ip=B{6r=Z8V+JRjd1v zzSzD_bO*o1?sl=$(R20{g~8)08nSt_h1;+SP1uUXbv-GU6?tgF{M56R@QW*Ueces!7^?b0dJ47{Br|k_IPPQ;jz()yS56cHA~DqUK%;M+hvT z2c76X)OBOPZF~emY57!1y6EXrk32n0aG?R9iot`AmkQ5T!Hc!#Q;0`=N3OQLiX}hV zdfI6lHgdgI6eqXw+>61xVl*JjlNE8MZWNUUi4Fz-`}WgvpOaR|jImX`Xu|%oJ;>rnABb-7%Nb2DTWav#BZB{Wp6H7)&0{I6GS@{j*exUb=rxU?|YfXVLzM^u!e2BgN zR{b5+fGG7CTMXM_^>y2=nO1@kc1INWvRVSDEm;-O0!x2yJvbwoBGbE~Gh1S}JSy=Q zFa;JbvLFr$BuqUS;u1TmZa?ZWk7gT(e4@0jj&D021B`5_dkGz_ zO|AnRwG3Koge>r7#R%Ty(EM25<`g7OBjj))dG*Is9}^aP0H+*238z*(5Pj7<#dQ*k zEPlz)Syvg@M%n*QAR`ZCqL+S0yYdo zE=6Q-d1%6V3Ly!?GtTbXc3Z3xq1g_Z)SWwnj;8r?nNgHV73#ln? zm|q`EZh=muQy5fY6ra*XOz*ljY0)SjMT7qi)Tp3L8HLL(02wHNqhv1OOg(Jyy6H#l z--tL*Td(ItH8}yGkZ2PuBvRyMibzw%31Vd_z3v&NXNAZ)-35BFHZS(oTs+=uZJNez zb-TMim{Y9aw;+G%n?zact<6qu<$8tqkU@gJ+!m%VFi^#hMe+BybL@K>w9YtoOg^UK z4jZA1fV9JwNiz$Q`Z%eFQ?Hx6P0MilMh&1JTmN-7YtC`2U0zsWzqKg*Y#(!Y2fK&r;TIkvxyDVl`0b=d?lD*%0R#h z+jMjHPwOR*E(ysu+)W&UXvqU5cCHr?SH%dPD-(6xKS9mEMdV~Nl^xHsJA}|S_TZJ9 zg2s1@865nZW&o$@53Jtr_R1?5qzZg;Kq@&NL=;j52(`5+A%{%lvMAN&Gw8}SM$eZ7 z8DNevlGfHF^hvQY$6-U4d^EsrC#(gJ)q*Q_J6k&1sP+tZvS=uN0aO*FQCFO$`%5Du zSYMwPet%kuZPj?qZkx95Bx&s8@7!*8ErSo1q*)VBRT>r9M647ssA;g{!$wr^bbSt1q#qLHC=Xu~y#h0S~WZ4!r5vRsFt3W^fZkYwr@`$vyKI>z zkY~A+R8m)*nFvH^wH`plJPi37xXZfO6D}++u_B~)W%u+G zIfDPVdcj3@r`yeuGB&Cc3p39UIEW;AK^4aEXh4#hQ)hg;GBzB>dv0ns2F1G`EXwOT zLhPqmp3izrV-p?FnqCv>^KZT`A}%!g7u|0TWPJUZ<7o!E9|EjBfrV9?0MTTooG878 z%#mhixgaa5Q)A3jD2cRmgF0?FiSbPqU&rBI46&KGuff2}nK)R5*nbl?nYKeHCY;Uf{hn5I5Xzh2x(%tpag=((LR3_4(Fsm=BexlGvcX&YqQ=|y z*~EL_fEh452#Y?xOo_f&;ST#3v(M65(`e~e{pWNG~h=ffApandWzuor7e9+3{wpj$zx zgdQ?1W`CP^WWQZ>PrQXScFd)WSRVZuioL{kp>QUp{xAX+djnR9(w{M4_yWs&PSYMG zb#7|a*$)splDN3FVYa`PJC!>_QK!zpSYuogVMC3V+Ls9^>wg0)&5@CFu?t7bQN}iS zN*pOXGxxGx5#JX8iA)rQz?*K=7tA3s5$+U(n`AcDu}HTAZ<}ux13u!obHt@o8gwkI zYXISigffVbS4m@%7uF?drOk2A1p*h?TRjUXh9l=wM+)MK6;iv$dRmrPM3VwrV!f|O zY-9UQwIpsDizP=WM^=#`|8ZYtAaR0YquGTb*SHD1U9T8!QqRdR!@k$B8!DMwBoEPa zmRD4dtd=VX+>j=UIMgeOH|1m;0bhDA*Qf;N9(~bDuM1O-qS6JovMjzoBF>E_4fJJ9 zcHnmSx(Z88Kb80Xcy~Le@QEEtao+dE6y5VZ@idHo5ENIjV6L_s98zibcRUm*D|336 z=N%pvt-j&I(uMVt$Wn_(u0~x`t+`cDA9I;IKc68Qt~gZvMFtI(t?*}#gUhu`Tw{TK z9a$!Si~H$^3Ye)8+;}YWEAH7>`MiNSBz4@Ba> z32_V$X?Ue3u^!+9UjvMlNgOxqm1~ z&E+k}Cr=vT;r~1;d0~={N!WyaOV=-yQ(vEh{5D#5yy+%AHZG%MlFF9zhLR3HFdL|| zf2-)J)FU1f2i^NvNsRsKiyT&T$VN4;YJ480J|GM+Y@V5^C7hgms$_S$+rR4FACz<1vjGKOYv;!uAijYD7krjgHe3l#T_dV170<%wNX?ge!p$XDA^x%96_j<+l|*e(#R$& zQ>$G@HL#wqSHALgJ3xAo38*+_>SolDr;RvbZ$YY2mHmb|%I{^%gNr1gp1m$nZ-Hi+ zK|@!u@)vv_Lfs0AqENf?dwi~at1|3rSX(3KL-%`=K8kp8c-*?-YO~{O z(Q;kUs%6)Eo;lv0tZf+H48ASf3|^f=XQy`scHuW<2)EF>6#8Ut-+&SHiH_E5OAp+ zpEO5~N<3ip?;UfDiP%40C)rJ0_QNAt>3TzBQ0=hEuzYA8%10WwVD7y)3Tvj~BWuFglT}j>m4u>qGAVPlhxvTHc$D zm!ISS#s@6)o8vU_1}X(A_2>JGfA15gW%KVTModS@8AA)R;;&(I&2OQ#B+BSeoMfxO za`8WO&2=UU8jMuqmXBU*)Wa|77$hzJM7=8y$SnvWbaW?_G3pe56gtR9x)i!1)oRvq z*}N87q6lIgFIIOoSctR@BWOQUSPn&7`KCzWrlCndXnS1jXUW{WA#cWREqP(e8Vufl zdwwKOEDO3lf4PrEPx;g^)o>ob#HKW=P7qc&jUGgFvJ_0@7y9R6)ZQjkJ&^a&P1Ock zr#+W&mz zYH60$DREI#6~(?@>*@`~prqpON!I!(>U3#WaxY0qJJJ`PPax@bty>InUCbc zTpl3fyg9{1WzAq}bOGj2)UiAQH~>R)@XL$5TG#eq)$}lSg5$|fKtE!Ryv=XtRNaoO z-S!l-mx%5=8nq)n-V4O(>DZMrV)fv;f5#_C$JTgH;TC$lXr{6$OU-y`_>}9Z_Rsee zWv?}kgpBD8wy^R#$o(Z`h+wpNu6jd%=bIdDzQ418_AIjGi-fVX;j%Q}oaX}I$MO%+ zM>(1-I3E-Z3Y3c7?vB?bKcUvz2-&=2Lp$9%iy>9TBj&Vw#$hf9o=e`XG%5|__X|Av z+-@3B41%>|?(f*PYxA(H_{BWz+q`ikI^jsSIx2fFVdCbQBUkwj;^1J+KOyWsF0QUI zxB_d+fg_)i#b9S?Yn2t842-VfcN4^ee`Dt$=?-a)BJOQ7j0vs*TB5e3yL^KY;)kyifc*Gj=I%s-E#xLu`l?$tu-bQtf*SH|uwMOPNaE)3jHJi%A;?J~(c{&+!t z>!n;t)-d<$)+c-1!~>E;;ewG2D=|MUv9^{=7o$!XoyymR24`@=<{-mT3FQM-nAvBy zXni0g3O1YW!L7ZnN4LFkftrl1{%Pv!+_q~y1+te^8aTyW*~abYa8Ra;r869rEP1US zmruImm_~r9YyelCvfxjdqA@~;ry8WVoNZp{o7|rY6vb?wK7iiyS4&L{sU&7--vdqM znVU@nItVadzzjXa+A=R@w?uQ7n@ct`J+~~z}-g994$}s&m*oDw=GpIcS=`Xv z_6@@Q0Pqo7P5=o72%_eBQFk8Y2I$8Vb*wFMU9-X#B?djF3!Uh6KYt4Jr2!$^-%_>r z1kAe0Baqm7ith9hGsB3t_JZ^>rQ~r6luKW5Yh6MCB@*eiktOrP%mfl^W|2X$#dtB` zDFtVfwkdk&2J0#MhG9)2-awnY8`%g?EAS#Kk6115$`#Ay;i@|QhG)vLzkzHQqaRZS zV9yn+4mQe)Ttu3qm>J&HkuEcelRy5A=8ombHlF6M;mcmpDGL}hA5H`ayyeS_`^p*c zj{s3E2&y;G|^f&gm14d-X1?TxnU`8FC0ec%)vlM+k z$8H;^cCcM4u}!k(qh8L({q=j4=Wg_j@*``f-Gf@SioEHTsF1_N3bJMe0YeS;!U)b- zuUFgO+{S8V1<1(Bnd~xHvF_Md_I;W>?t_{ zCL9GuU;wcvNm0?tmqCt&QVTH%;W_1bPuGz6BW%`T?WLsAW!sZNX45mis#>MfjXHxs z;cadN+DxM9=j}a%E}JmZJkiGN^t!jmwVD1tSnjJ>J4jchNsMW_-{UxOPN@yPNh8)i zTRK+|DO#xJjx2x7K=sNX$9V}3C6|eLw9WF6PCT@dOxGZ`)0+~R9No{*!VBY#ptG&P z<|00VbepG$g(id`hZvh2G?rQ5elk}~T}OB}Xgv}+|NLgcD0Q4}#P#E{?Stjhc^ZYjZu~2|gp3;_DG^nEBFxY$ZbZvU z#N=tZ_DeYy#(6j8VL7nZlWSI-$rOfyYL-iuENg6H*_PT^5laG4dXg#DRqn`h!vAoT z)`8z;t05PeO7Ag4-0TxN^lu+dAsz4AMv{1XC5Wp9au%b^hBFJwy!Vp$ABm+{clo%e(-I!`40NEn1wc_0WT ze5&LwrPpi%t*j>!><$nA~o9$wdf zVxcZ3XcQmO@B2|7!B=CH*R>WB^F3$W2I0^72qEVF6AE612yqeQr?8Uh@M`{$E4!=s zKwG=uaS@TQj^BBTKp9olQv%^R>95z{GS^)l4QD+*XFZ86pR+gKK~NG+-^WFlN!TE}fx3WlJN*2sNXmOOm}_8)G3j3f_$nOm6iw@~Cf(C=!PQ zTvy#A*73YIhA*{@fm9-qK&p6k_2$MS0Sk4tnC4m_LCxN=unWLM*|j3ZI`CvwhnxrQ zz4$jN)b(*bY3Lce@F=P@Q8i)t>^tt}RkRA3{O3uyhvU=ktV>ljY6K`N07udzkG*ls zA5k##ACCS6kE+f)MyAi?s^@F{5a(T7^`r;vU(jaQv3gCuUR}r*jdzqdgQZZBt;w{4 zHL*^Vb*#y>@iP2tAIP5E8Jqt`DSN1lO`0jazr;7+xyYMU(Sf7nouRhkWz$&z(%+zy z=gqw=x_e@GR1rYUk4Qnq3POsJ86l;jn}IQ6B}`qRvb!#y<-Uz!oOh&qT#N^;&`*8X zI=U)#wqS18uZ5BF7HNmg7EeK9FerGvz$5!*dA^gTU_Pm7h>yW2fw{EEf%N~&^`(Lw zC^rcyg-+davk;QMfCVu=NX=db+MxyGm72sgzIo?^<3gTNDM8k33}@6znhwCZ(1g|7 z>@;u!)ub85az6@lP$`@GSJ@36tl}1YW*jQ!i3TjC3Kn@vcmb@Zlaw#%w2N68HTlR% zVBf7c&gAOD6E+b#Hin`sATmayH2!PDfc`ez(N$P*A)=jZ_6^tMsnRJ6QH1j%6ATM? z7>N14Xm<$Brjok6loQuwXdFzM|HTr=em~f$R;r$OgZxpag+VwadkViePSXqqrH91T z?+J%X{rmBB&68Y4R|on~1B~u`NwUb-c^*Bujm*(@z3$b!kYb;cy{<+{7q(mRpLLqt zRF|dWtM2caF?c^3(v8*{C0Jv^o)k@!-u78BhmLD@brr6HpwrGLBv$v{EK7x{X7kUF z2hWjr35BYI?=~x}!9Q-AbkyW9H9&dQ zU$RqN`3WZkBb{RV>-%P*VIW&Uf+VWV(tI2?A|IMkb>@`(&c8OtYd1WIs5Cba*qfrB zRR_9kS{q?rt)7p+ypwe{cIus8P7bP3Uwzx#`SXlN#$H6|VD=(5jxAytJ2#-Z`19`8 z{H@^?yp@=iKaDxgpORSvA8ju7F^_(?4w_XL-#e}a8=jwKrg6yJLJ!CMbv-Db%S?4O zBt%Wgd?Z;ztCTL~zxT|4++ z$Ieyj(@AI0FBUUr@+1prLOn6N&;;_=?STPnRQUuo7mP<0{?;&{qye7q`Y5c==~u>` zxiB40n_l=(ZWR4_WLwCOUCz>&jgSbyJkT$|nOYx#;-^ydk0Cy2#XIoqig6TPfdUY? z2?H!ZhyHn>^_OvUY7DALMuWa8B^?rfIaDp3V{HXzfu75#--`X-uj?I>$SzPJaiC)=58yR&Hc0)OKWC{`=Z!^G0{bvJQVSh0;hLFifiu=OLpmpna*aK*o=uKU?~zo zZjiE$AI!!$Y&_Rs!g#{7VLMc2=1QhDmbEnyQ4v6q%}&Nq>;G zD3d(lsv$z^H3C)PJ_FQpinM4o&6VksfPq5omT+7p&Z!#bn&oprGM zecY!0eLFWw7Y^L=taQV+yA?`DoNT{qd$+}-7}yuu#|}+4ddz}sNdMa=3>S!#Am=qIICUw9KxpayxgdrmLO7z_xdG zyh>K~V$lzm<$DHws4%#b6;kD3mATZ;I%e5Cwr^-3bpH$r(-JLesJ ztez)e;9Jjm6Zka0E0}jew7b z&bvmfeIHkPGz7RiXWcOxExe00b1vumW2 zd8Ta@I#FXPB|3{CU(t%CnWQ@Yk`WZu5$ou~zYY{_pt9q8QbQT%?s_oG-SWI`2SR>% zc*`HkA4iSdKHkxaqmg7KEAQ`-B->Hsw#&c1I~0_AGRKsA-m8eJ;HS1xwnmDnleYd1 z`h+&D!Yt8lNQql$L(*cT`q;f&Nif3i6#nI&E8@i^SIFB#f(-ZN;aCT*HGm<%yOqSC zG}1h8giX|bL-N_~_xdhbKXSASV2$14|V>-?g8?!#u4y#Y5Pm&_2ld`HLC-;320Wy{pGQ>genPf&)2VEvb}#t z9!B)C$wK6^>4wAHwue|8K+5;!d^|C3$8crS zZ_Y$=cM0#SG31!}F==GK?PKxyxJrk8R0r20hBO3qkyTUPbcX08c=$zxu9B zTkfGeZ|!2<8_Yq~7SYeSPQ z6%+ky;{ipTW$@a};DtgnAGb^lk%QaTm*K_|Ri11S|;9t~REO#xLq>bNa3}rwmap>S}BHuC}HOsVwU1(ezzCnleVKsH?}* zclCJ6sEneno=o4>lPTS$MO{6ezN@EGIvR?)dNzGm&t|+hnq9*)$ctJ=V36@G|Gql+ zB-iMjTc}$6rOk8%K;3E;y98dYFIiY~C)?YEQ@Mh@t4DOp-rR3>6A$np>zq%cT~eP} zSqL12pQ{F*op^vH{FWy;dVZ%9Bq-a*<=M#IqkSC=pRcE{(dt$XG4*WlD0KRS+(E6Y zL6zaVSD1sK)XJJ`*i_+tc9J+ui-5BHP40gm*@uykJ+~N@6j#}Fs(l4I)uyFW?d#B~ zHV2(*6X>+@73j1vEuA*L4xKjUpwq?#I&FRhI&Dr%r_HZJr_DL&v^jxJTVH`rThr2M z>+8^IYYsYXO`y}GuRy0q)6(hD*P+v+Iq39g0-YXz1v)*RmQIhq4xJv)L8r$P==9_( z(CNvvbb9i2==5X`Iz5>{r>9?mPEV($)6=g*r>Aq!>FESIJ^KoDdNwVco_!rUJ)470 z&orH;&wC@Mo1!DtG^F~Fmz4+bJ`aYa>(4 z<~u{D$J&~yVajOs$U6zRfDD|OluI~K#l5InlX26%ctJT3e|k}oe3k4Tq}=H~7yao; zd5jp7;@sh-7d0>V=LqY6P#ux%`;F?v{!1UA*ALL^wW_M-oBa7)qMa*-ey9Oipk*fi z9fGb&k~#c)L!Z;fcAOF`(|qzAPJ!!^b}DbF*Bo=fhPCo3CmxX4TE;a_303n}*0kxC zKI7*boGZ%qZsUBgb34aUG35wowr_6)!&(~m;YJGU3HW%cm`0EOak=op12L0$ZW2XY zanu@GmT|!pz1=7qVofg?^Ce8aMA-p z_3oxDjl;FI`DuQ6K2vpGo8p#n;CIj$z@@>k zaNZ3nUMOA%-N~uHx!|uLcaFih&J*J#PQ566rp)xY@7RO-8HKGgUB^vTJ%@zZu`W4O z@m?MuH`4`kg-kD_h!S&~(n~7w`7n|?rK(nNLCsWE>2x#zwRAWdfba)>)+$tb9mO9g z>+=Oy%0snKK$`k+s&1SrlN4fpVXAEu40&>2V!hc9T!A|da>rz6f~We7fDz0RjnNtp zGAUK~%^6MA6pPR4f$G8Yx+{2V)?Ho$&7`{Q6Q)W|hHF^(v5RE|`)jay^}_S&#nsiW zA}tSzGHK^Pt#IL}vjAzM>QqtfEH$|~m6{n2!r;8??fv7USB=;0H}BplS#glJ+!fE| z)_g6j?Mf9)_V=->@GpkoiK%y~a{JpQrnyF@WDzHZlJbhUzf%!Jkub=+Si4V=D~1zE z7)yaGskF&egEb*|mQ|fuRn#8GkQ%~s7{TWH2CDHm!B94<8x<_Pnl9-jD~pDr z;y?ZVE|rmE1YtPNJXxczAf`M=u+)d@LHrLM|^16^qPUY#E8WB&ZTLzQJ+MvOJv z>8OZbrICTJ-(W0hs`{gHDpGEDDi&r6o#%nEIZq9UxJ;J{PN7(%mxihIxUH3y{>I~y zqS19?>RVg&REv~AFArasbCg0CurR2WJt3>g$>!nZ6qUD^_w=k|)jx0aL<*WZ&p6j= zX;iGb=X$7AR+s{UDIOU3d{y`#A0qGPNicWWx}%t)7VdmS)I0rX5R^8khMC@HS(Q#k zejm*oLlE=*KHEI-p#Kp>9`Z9h@eaem=u-u@f5crX;?gsGiS%d;{Xsao^!&JU5e`8| z7|F)Jht=hh0C;~myxLw{>*H6|ZWvSt!Eo);AFOSx5pwBL?KrIc!{ehbDba2fYlyPMRS3~au-9osG&O+iQDir20=KIFc!SVZM`|a_|2B=lKV7H}W=FL=g48_&D z3)2wMz^}cdv$zQM0SNP>dLQA8`!QJpjN>eAx&j;1>AQ9-*-d5AK}^`bfasf zcMK3OkgA9p=syXU!#diYIWg5aU9ME4j51h9Dee_$Vlqj;&L4B|Frj0*|P4yQ05td&RyL+rD@cBM{ zzvpdJ=APs>FG;GCsx&*`u4~eI;aCd{MT!GVoufIBW1xeTtbnUP^w3{jw8RhT)T4Tn zU6f39t7s0qM8<~`WLJx#%sAieoxqBbaZX+=7(TYz#roO<1(P;Y42P3tK|3eK1&5%x z?9$r!=*OYo4LfkC>%>u_bazmNDzG?y`Nex$U9YH;vhU)^!xxR0Z@phb)Fr&V^Pob2 z-n{dC(4PdIbU|5lI>B19xjdCyU{-UAc?(_d!`Y~v`smHOHuRp$FKGQaF#Iy+efu4@ zFg!D!;QJ9Akq30!i#8-u575W};y)N9d}J6q>%=a>K%FB{?{Rg=%<&x_S*3kJ9qo_; zB+W>1kEwc^_9>u!Wgv$o+9Q?Qi)Sd8-Lwwv9_!uh-Aq#+QTLBwGhp3fJUy`-)lCA} zI;ENu@3i%5wf30S5FC)AE^Am_wEbXxTURD^_T-JJ!Z8nBAugYw$>5DaaqE3u!Z-~Qf_*)N-?hplEiO-Hgr z07%e6qz<;MUsL%SR-EO%y9}wu_q88WAwR$Y03dfPhrMNTEU7x+u{wy-nV5v$z*|P; z!D>e<{w(%80e^uTp3=tD9d)!mfhdJVa!%qd2Ker2Zx6>q$(u$~iu3B#UaCs;Y=i$u)K= za!E&O#|AmEF1Y7nGAl%|FJ6ZKM@L_@$c6)yFr7f^lGF6Vx3QS@x@t<9mc3D*Z6~;O zlA{dx4q<`X6r#=M>I#G<0>#BGJkogx5{h^X1QwhF@h5<-fAUd08eR}T>#Hd8{#5J$ zKj;E&wOaK)^TW@I-Yt+1FHc+b_URGUGi)oapmQsmBt5@3L{*VDMmp!ko|fHG>DL#+ z@zGQvt*C}1R9~-IpDK-&%de9mjObOcRodcKv0J;5BX2Xq0xtE3V$X%6WvMh8U`OS0 z0UM^jw>fK%1qjg@nsecNdve$Z&{9UdOPZ^N2y zygq7|P**9V2*>J|)-8St!e{s5gK{CBWaXw-$R@&3UmyAX<9EN~={`FO{KLh42Op0c zJj?SSrFY^mQI?C^$@8lms-$l)s`}o$Dp5@}RY|mxxFDK|>cR9&5yQGW?+}8bP_(@g zQS;;T5p4}v0oyFmCJ1!g_*Tev2uG|yJ%Lrq1IIF>Wc=dY4pwF1g2=IKZ?2OHIrVBu z_m){KNY^$~w9Pwu(ZWfCvX8zB?a5f%*t+V4qb2F77yUEw-iEm2M?G{cG>OGWFu@=t zZn)g2 zsBpOkmHL}6wdrY2kaI&y^?!MMatNypSwwom&G0(1#)5kkTU#vs9Wblr+e7Ma*Z?Bd z;ypI^45IO$Xwz7M#S1B+)d(4iW3_}${cm-Y8tvwry%YHBu<`O_@8tJPk7zLj2E+A; zcIZ;K--mMpjH&FpBKJXu1gSndG*ICbw9J4k)SbyjFqxf&o+%hggW`q3l>LwqYotqf z5{2S>=X9V|GN_vF;pi9?G9X+%wT?@rLBueM7O(lz-tLe}c)o-IHqj6kCW_N=wIHtj z@T>#JV&$GC6$zgo9{&0kwb%a*I@!}NLjCQx^r_YDEB_qz+i!a>8|^oI0>~ML+h|>n z#d3~|!u98p1nj=RJcMhvTl-uXB=ZdDO&<|vNRco=nQFEF;2T{#OE600|r299u&9T zF~=WX0W4eFaz>CnPR?ga(+7SFdpDxZYKI>vy|nsaE#NUg47(WkR|Optj#R9SR=k^N z1Rvq1DH@~=l0aanfUazg5qy3e`!{Bf%*C~|cXG1#d$aLBbv)P%VP|dQWVC6{DB5oS z7>*)Gz{#g?$%xUcC>^*U7%K&&jh=lnl~XW7S!u&jtn%5gi(efbX!E#)?LfjtkXS)H z^g&_5?Yy}P ztub2#DqNvN2Cj=y!r>_&v9xuWPniYtUe&k|bCH!Twgi-BU2p0c(&gqa{FsQFrlp3d z6(<}qMU!K+f>z@cC$08_!8s`AL;@6YAT4QOB@70Yn66R{yLh~hM}t)qQ(kE(x*Y(1 zi2fy5=`?;M6!r|K&>=$+_C?Ds84CTlOX;e(ihM#YE<~?Ka|_EZ4p90kQRZ1%KN)A_ zETM>M(bUKTk0QchvG1tg`P79LOxzt$B20)#BBqsK_VH*FNf^dd{|PWH+nH!|1fv~9 z@y5#=aq;7LhzC=v4A+cWSmX%=GLK@hgb0(uTaMev@67W`EhR6Mbc1u)C_#^r@QX9a zHkfMJ;bYiy(XarM3qVlqrDE`7a28_?UMC0Lg?q!3Bt?eB(Fi3j1<(K(WP;7=R&|S* zXsfzj#mK-$4~Ko>cfIR4gx;-{lj_*?yK8KBK!m{WRy@KsErvlUz#Y^~wFNt$VIFzA zSY{sWplz}A>0JZ~eG%XZCy)rGaKPH;^6oMYaGAbdhCK!4kYy^|IRy^Bp2V_&0se0QKKc?tN{ISG8 zl}hW?bqhMV!_~&$zwrVz7m$56@@i~8Zqhcv^N9YV`aj=rni*vt1)-g7qZQ>);^M0p zY9urcP0i3)BRCe60z5f12oTbtgVo$*HZ`k^Y|FY}Qh@VLeoCB)-#u?tpq2M0?b?o` z||+fr-AyYgOmN2iB} zn8g(ywM$o0jQZbJJHB0!pp8v(rM65C)D?Km=0|-6V+A4YP~~RmLN#37&9-zJo|6%; zpanOHVO)MfDJ@A4g-6^TN%4Xk(+m7dhUVlEh37hpD2qTEmqQ5#jgFx!yqJcd)I)wO z+#@FtNB3cyL?L+WX$VfqGEovwWKLJCht11YC%`W!!RA` z8IVq>ny@h?US$I|68@wv7mrTr57^X*v(^AWCHhsp@=*mEl*)?H0pPwu3Eg3cI)U{? z>N;Q@vHZXz+UXvclAU&fE2^BnV?1tCNOv&@^eJ`*s}nqgtmJi&1`KinJ}U1BsZP}W z(hKES2O!7}w!q{ou88fDy3@t|Y2s&O0KaIPIe9he_uu-%&IO!Q;RF>2nY*n$?1LE6 zI={tuDnLs*EwwpRgfO#Z8o2nQI|=Jb}%iI-pw%R?27Q!=7W-rsO)!ulC&1= z+n)w-E*}#S%)&u(I2S>k*-$)4WMf(DcIIF`SAXdl%IR{_dYUx)Z#eK<9P?DGeB97^ zA!c#enLL!@Po15MHeC`ZSy7|J$a^xT(#~m`SZ5x8;92}aCXkdlxyuf521mcI(MoK8 zrlRz91U8zIbJrg9KRCw&4TG~_6i6`I&xuZ9(k63K+fn-|pon(R_DOw-uK&XD+e5Dh z)6wQ1JId66#$jvWSFPq(JPYOvY3|Ae^n2mq!Mz$l!JBydk1?=EL0A}>n}fv8l6DLf zx|>hxM%{qVK_-$wj7E5OtB01CD~z!9$w)xnnZfAtjKXSBF5gJn08~YMvYujq?LZO- z$m!g;OYCSb$Gk(PEHo`ShXD8d#OS?(=7$kS?K;ef$Z{3}asZ`=L}Tg8hom$qWL85^ zj;>Hcf^UC&dngg*a<^f4L5BfCP&>tiDOGT{r@trrkX(%%1fSH!w=C*Nrzn=oQ7Hdo zKjAnWbYfbW>nD&y%r9}njA1;53UBO8`Cp2_N=5BQ=jVtO2d0HVvTb_>4v$}hhKoMF zaClROV;3|}6ni8pXbByWVJ`&EP))r){L#ly%V4bCPca&A)a0^IVR-r~EJr;sn8WD2 z6*Y%Iemph-J{-rNP`na`c5Bq|_($#J%UAtTa?u#{A^=gsk@WBZ+2DFKA)IH#h5;)( zfJFnO0I>(3HPzt$)XTDQ^y;{xf4$#3If7llJgA?X9G{qyR72yjRjW0`Q%Pd#PErbESx#GmkpAzBe1Hdh)l*`OonpZmvDuFcOH@U1dJb% zCx%WlIY$ab1NUH|o|ZBsGGiIP)LHz=R3NfvaCWC4fI+u{r$Nn8VPaFU!WRP)3I3sQ zeU#n*)+jM_S$Yu@?E$7gYSN!}*#>FU0dCnz9|cubgo5!+cScm7m-Diarq&pHfO5S^ zdTa%65!%l`8`A$Vh0JKTU!je*U4g&IzuqoaEh6c{7o=FY%kR(6&dbya`_t>u5-ZWE zUuBC>cj!IZ9r51%9$jHs^$w2V)YI}{?^Je3u_C{z@Bgh$M|}!QT)jj`hEFEIDju4% zj3BvpjOmH?H@Sd9Hla{%*m=#HL3EYeD5>Z4N%pJ2AGB=~mPj-9I8Oe6Vq5pR{0rnu zI>OZxvoRcKIv)}jYg)_cp=8D(P+;Ezt|jk;EO!nzb3gtVMt!+|Zca+tf^PRh0vRL` zOrhsz@K1^9a7b&_AKWOXHv9a70mW!$S8CC`#cF;s}t8K$mowpG=H3A-pKYhxGoTO;ounO$<~9Xjw0{9r=qiLiNyE=OnzCEA}<9 zm%X1m)(>>%VPNhu+m13^pN@h+LU7;>DZQ)7@CH;SrHYVe+$V<(AFdcJq&_c5-x>pcv64^6B&#fSyKTTv32?OPPgKR+W; zF$6<}kf|_U<^!_mny__NNfADNe-=Z37x@#++J5tNf4|;rwo6x05`JpqsCi~^CQa&U zP6Nc6?hDUu>~cqH!hSXlY5sVc#Sb7@@@-J7Vb_B8mzii~~S5N1Y2} zRd-+|u47cjlZ;{zfauSli#-GM@(o842+YS*w7zx&c-N)PB>030Oo#?#Qeq~xkHIFO zMWTQ|Vg!xEKKb4d17U_@cebKo$S%u7C;y5&TM`eGpv%EeM5WVnW&H5YSI=7!`nx0q zMB2D;`9MeE>SsYPpyO*^0D%gIBT;=OTLeg8U~drc*FIX;&o0qjj4L$q&`ZMRY4ZEx zl@A~RU!Ee8fe`1L2BNToTsR+*R?kAZH&R#5wVx-Z%zhs+=gA1?i~KJX9-@7Vv`6TS zAIN*CBI0V}Ag#*~&4VUnnxot<0TChr@p6S6N=PE2E#3Vx(Q^pL#MJqCbzJSP;z>FS z$So_QkTayjqxHxEe|F>T9UK~WlI2Z`bat**)zH{5Z#+Vk`8t8_I z#;XfE#AM89NvDtl%+4IV?C7RlfJC=FVeMBG)e*`?bPat{*A%%(tSUVb8<7t>>g9Ct zpUAQ-qI7=5GZY-JFm44Fo%-q^?2$`j=zLC*kE2BLkEDjrVT2NVKT@}ORCs=4VCf}j zSXg2!nb*Am&M;%f%MZ~AV~I!{vO=Q>Ot-AgVL_HKnPtc;t;e$o99rgn26%|<>yD6C zWOVQKV1`j}IaIa;oEn}9>Ws;S{Gye2GvvUSbet2#XitYOC=}1rDoGEJA_&i0QFk_Y`6M1`90--6ScXfEF=Lei7d{)=2VDuq_9DqA#9oPV5nLNd^+6fri<$H#36^io`{@Qj9Y(SfzmQp9^9)?OI0u*|0FD=+lOE70YNf?0&dokHP zZBfUKRX*VZ5vqx^e<=C?(&sR?g8;ANvt2;`R*`-@;cOIx&+rRoe{)|DZx`X01fA7G z-JZ7@23cXR2&$(R#E`Al@2#{|#&c3}*9>g%oS4K%7EpojvW4K;i}Sa!j7TS|)mZRP z9LesIJY4bi5lGNg^lC+W9Z;eBsCkyK=?d(i%r&{Q?93!3F6fq4_zRd-c_5vYiC<*u zoQYQ0?3U=$9rqF~hH2$adG2TLBJ^hXgNKRUS*wb1GQ3)t^@J_u>~%-m7l^GZRB2<` zO!oO_bJ#_T3;wstiG~ts$^PhWB+dyB&5-g@SH8k8Fi%PP0}>3e<@lGryE;H3g#zMN zjDj(UyC~pSxhUX^DNwN89b^r3XoALAtAXte$xtHIHU*3kfkJwvS-N)3c}ZN228d zb^<&AvrABiG~EORqb%Vn?dIyjt1fX zv6~If@^A))6^(9~RPKw`8<@KT4u0(i!4)5(OEKyh{hP8k@IaaAhpT)oB~d%G?FFRm zUef%jD_3rqAlkxoxD>)TpbJ!Du!60bQ|e)65IQ6TA*pKYkitU=UW-$|s*a_ckba9O z3Wg=zGX}gHGcTZ6M@uOE0edp<)9aP&bVJwco*F5&8h9&SZGC!vaf`P0GiZLK?V=jAVFmiNZhGot1loNbFDJ0cd zEi=cHCYZZDqEU*g*zuLwZ%P;sstD3{b+K_~O&)Fgax@G+wJ|`i^nm}Cxzlkm|4#Mx zr$lYq-wRn4h_Vjc#O;=GOC*I7&!bF>bs>FVkCr^HK00OIVyrO1!O6rcXk9y$ATxOq zwnClW6kywrIv*TVJwre&X)h`M)1Qq7nGe+%U*a3Zow=B)9ugi>dNe+#vXf9ZXDILe zaQG^Ub7z5M)y|HId7U^=B@`&;MU5H;tqHJ`3n^#p>d>mbEbFZVlOTRjO|k;2wq7ob zmUVj_di<{r(y_3d!c}UM%tW}*g{S>hCBZAO(y_}|@cTV*{Nm3rfgqGG1(dCm(Ew9S zHIS3KqYfn-bw8!!23qXt$Q;2LPGgFStB5aTlGKNu&k}h=!3}3OSH7gXp<@Rp;!$EK zuTMuBeGFsN?7e^Iv@A)Cx@tolO#?Iafw4=Ob`}d5GfAiC#4-lbF-UK_bPUor(DqRU z!AawIoeq$my^)a{id=e9eYA$RavV64aj2_ocM2HWFY)f3euEYdaiiYQE3&CvhbZJR z>;_Z(q5czH+IO5vq8#Xp!_Cov6M2oME#<)}XlR~aJTQ`B2BZRyHKSfM*?*1ct7OEW0a zq3Ar0hMlz)+|ThA*}1R^z4BJpRGJ}Yr&dJt2W{HJ#K2dr%3(=7JLzWD`5*O-E4H-l z@FjB$qa0mbfu?{|sj53-4n461&8~ngJuri*c#kUHgLHSw?wEdFRXeA3CtFDpN%=Li zF2vGlDAsM@^x3qbao20oa&24W(qyTnF5uAAmsU;zA49Wpsl_orDKI73)ZEr2PGBG5 zDXci`u#%y$+9$W^Gtk(uKNFHiFhD;e?lX_mP}-#oT+#Bxvk)(pcp5jxdqa#PEt<|Ib4~f__%`Smk0S`sKS;d zqrj#o5f}CUgKb6Mks==8C)SmspEMbbel>+g{l5supjp<+k_PAKm`VxC+ae7vnd#TY z%cTPTSl!K49Jy2GJZ3em$Yvtda=2WAjPcDXNu-cm2*e~NRg1O)3peL|WW=59(m7C+ z8530-9Bjn|jH5bgof*ey-b?BlIIG{ON;OMklZzY#Je_6bp#BPDESnhXv)_Ju+N%H7E}gge zN<7n4zW7~K2pH5gs=;yxI|bkMYq~vgv7{-wYMDGfidGE0LbrbP$ObA3S-CWbRO-lK z=$5&AXcz>&oF&f?U7OXiPc1Q#U>6Z81Q-k| z;F!ZCQ49O^+5Az>(v)A``%sW=+x(e_!xwjb>Nxh5k z{6b=vh#-1~Ihnv4tU#)7MrOuQvIhI$fo3r(;#uP7l@#NZjco;^1zXtxh<_Z%q0*Qz z$^UB^KT&#}BFUb+^3gM|(qf%)g04%Sv>|{SQ0mq$hizd?JOpc#oc#?2oyels${rtz zpu2N71K72?fvMc05(;PC6;8<~kr+N7(8-!DN&?wxF9UyIiw@j=IoGQ1y*obO2xy9F zrn^fj`HoDim70|WPSut?1!gI|e^dJH{tc2|nwqsw!1atvxNIsBwzJ010#0En_d$JL z&W<+Ikl_%|3PCAolL$y*&nn771;!3ioK`MZ(Js&Md*-IA@@rNXNWa!I58BSBgtT+ttt{efd*H1fmM~VXJ%^0(b(i!`dpM*Z&m9`4(_$9 zvezalX3nHD%->^qb5(KR%6XRKFf(jVpE&L?sEjzZpfMi~ z$E8@=L3R3XYY_*2=Yn~oVh6>-7Dvm4(uq-q>vRvQ#`0)j?u7A46K$ewxiX<$8Lln) zX8OuHekc#ct|?t8X3-QTDp8VJu@2(_xqz;I?D2J~QD>-0*6aJ{-s=0ykW!C%#x$}IlM9rlb|cm69=dOX&f~$Craag>L(T6hb5X6hr>EO zYiOuWsuEJP1t89%18Xw@2Nsc>miWo!g4P0x;k|ba-c1cw+L-F-fySm&pcc~O*%iMc zpI4DKLZ*Z{`%3%A^3L3oj(TrJ(G~&oUVA9WB4)Wh%>hf*e8dzTcK&h-@0O7J9#C

      sHA6p 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%0AhIFT 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 ikFW9)_?bnc?_9E4SyAy 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≻p<+r}nA2kg?7 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�{Jb-Udv|K=x90ajD`*@tY{&PiWLjh` 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%0DE%DOm#!sE56LTN<6BD> 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%0A(X_ry1IJYL9u#!^wF*TOS?KMW8Z(ucbpZy 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

      &R+p4qmJ5naSD#&Ngm(%4R^{y&dk>`gXr{_~ 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
      ?3D@_ycVYb*Yy3(%4}K%KITu~ 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
      K&2v<6-L+=sj|R+w^<5$IRXn1;vc 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

      Ky@XG2U50Bu2WT+}cGZUrl^nEB1 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

      a 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://github.com/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%0A;iv33QB9fb@! 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 z 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 z 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

      2h8G=&#O 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
      F`F51Y6kY9fsTWI2 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 z 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%0AGhAis%-G^R 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[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%0DvWFD{z{&#_M@~074i;||!3tUXSVPPd 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%0D$bjElX$( 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�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

      yKx*YR zGD_au*`(BERScuDkq19iToo6s)^T>G zT>NOKzop7fv)@!0D+>Of-JX<+D6{?`xkrVu?B>;^$nU&dt&JN8h|0D!PHy2?-VB>P zjgI;5o!-nb>S}CIE49!8g7mYBot?};Csg_ZNv~OHM0!&yrPQ%X@ma;rKDE+Dtj@-S zDjUff8)k(KSs!vUs7)t`7a$eK8>hz-br}wvQzQox(yHsfR|rL@V-E%j^_j{*#B!GT zpj8YK4zxZ*U?B9{pU^9;#p+gc&ZXtDS|`t&M9%cQil2{irsuWJKD(K7X=6>VVuOv( zDt6(@5no905f>lpk6nTNQdMC+%~OfpajOdJm58E1WEX!reW4PCHpg~9d!f?N78|>L zv)cXa`t$`YwlNM;Hinrt*3ScTV;nRTut7F4LnkT>DB+Ey6$)djN5JM}6)!1FPgXw^+Fs=6h^tl92C-A(@z@F)fAc z_oO5T4dcQgc|DDyn&v}iA6XXWY7(d0f^araiP0dHM9U53Sbw}B8Y053v_XuOxhiGR z-SZMBF`;v(s8v>i%mzifqy5~Lpd zAp1Yh@qUbslKfOwjba{T5F3p;+wB%V;zx}TM{iSeQ+I*;9D0dDXq4)a%`X*3Uuzow z^j1Q)23Jt2^jwXCQ<2b?6(m$eY7*|{A}*^VYAY+^GU@x7Iia*=%}{*uWZHZQk?l9)9|;1$pNV)81#|c$D%?TWy#!b19P}r%e*#&F1FD!Q1UUzT-@Em)9fR z#Ly}{km;qIR*%#596}5)-9Z?%Z_pg1?GCT}eiRJKxGElR9{#e68cRZM3U7Mjk>ADd ztEZ{S_jb27(I8YD_j_L3A5BQQv;BIXU1tZugk<|FtV+N>FOw5Kd!m!_SCu$ArtqoR zs|X74_y`4bg!$R`2aHuIp|IOx^X>7$C&>2AdO}#LO8loXi+PNl*?Hl;DP?%%Vjn_yZ0oI~%fbmTOE9WFn8%3pUSitco1WiIQMf3<^SZhtDJv0s!{(mRz>Avqn^=nakiEq* zdID4sdilHB@h5Hz)~V{p3vxC({zMwa`gpJ1l&3_Kuu&{pX49)~yP29NZ@P%Mp+mQl zEU&8#Vi~bEr9M0A6_g}XsEkguwQ$p<)>$?4E9WV?5%2wEN^@Ak4j0&ija@uej>%_b zZd#8Bu5fxX1d%MvMeR2G&yAWmQ}9m<*wnynryey>ZImKqGNPB!43G_}Nm>5+PoY%N zOkW{SyUAf`%N6laNIJW+c57K|XshEG*{ijX;OPp2sf3dYo1qxw!n2BzZQ3{hGBCFG zjB6A&u_06P=BWK(V{d2sxn07RZYk&7nMal?-K>VTTePaG^~L;s zeAuee4@CPo%y}>wn=I{A%0;NfzDZrlccD%2F3{mInlOMEF7+qIi}{7Aj(1eicva*NFejRg&GA zVI>QDwn|fq z^ErpVs|(*gTd46Me7o?8AIaax$KNfJ*Y@13vntu-CR&7!2Q=(hn_MZmN}PFKv4RI` ztK6CFRw67X6EjRwCg+G=0uF3($1-kK%`!O5QqkhF*~Uw5@OV90Jj>+DI16S7*GqB~ z)A_F=v*ajz!+;^hk3dRr{BLVASsn7hS8gPctqE2AQDiX)lJUGH3wm>#F$zkALk6tQ zs-x+&F+dT8rBUezDT5+qTG&KYq$uKD;nOFvn%+5;#cWu#C=4x)6vj#6pNg+o&JXve z1ht_lsinBTTDbt1rSXu(AiPaug4pyn5HbrXU@duUh8VFT&4VP?m6RW0FtD(RI`n3@ zRTahM3bUz3N(`}yH@7A`4NXa;&D=R1ABt%S!%LW;fjhcnST0O#m~a(RH%n|S#CC{L z3q^-$sy-sa-bA$xS5rEZ7(X8fQHiXE`#z!%oH!>GPnj!5s~L1XfcGb~H2dCF(@4Dz zFJq77ZZvo%nH@{*dus&oLdYyg$wab%6NLW6&RAxD;#evvjV@{}1i>#gSvIH#JvGe9 zG3ha9DFqje-Z{C)w^Wc@%E&=-egrI$k?S&+M~uEx(VWyNW}E>|!H9mXVoEFK238TQ zC*TTHW{js8MN-r!LwbrLTK}6zMYJZ9sneFEq>{iaoCl#)cEFBXx0XSCaLuhL`+ib{ zhM1D7lVaLPAtQfMu2Xn{(FEN37;O@SuH$#gM1JmU%y0_8sSC1HVdT z&2sGCp{Tw0yg_p?^gqC*19h9;un+Ux2j;y^y~S=5zf|kF7(uHZL_NRDDod300E;bk zK>xa3_rEo&c@{6)#-fNcI-1R6w#|%HXs~G<7+SKkhrb+3?GZ(XlyM>n;gg(Jf|iAegGSopzAqnQ(=j{Eq)V^_ z7z~lz$%8uE1YLO&B?KMV5LcAUP}vU142Em=1fe2>VViy0rToD4uSFM;wEAPWi)9~< zB5;+EV9*gYD6Au+V>pzAPcJ8~uC0rp0;OAIq*QX1Mw?q)kIKbYk~jalDS7a? zj8ebl_lSvmh6o?p)>1e?Qw4)YOl0lk4j z&I%}oitL75@$5+r74ppZOCQ#@-%c!YGcG!*#q+g2<9?Q>x9^@kd6*7&d_^z+oDMh^ zg>uwoh+1GR`F!O`XNWB7tpyc~V@9}G8wXjQe{ny!D3d~o^U;O2i5|A)2PP`@YO`<_ZPUnE)g=AA*4GL47SgD5$Qq0o&bbif-ZFw zeqb>4Mq~<1A+(7!_YtUY%lujLf}eWx5Q< zr2|6ebQyp|i=oM8<~gN21~hG)<_c>0fju!pwFW@chQhi*DZ<`V1t|h*p*L_PfSg^1L@yRX zL3AuQ0s)Fi+~e@*v80CwErO&v*gAZ(b5uU&=L%Jdeq|(rDOAalDPWNJCBq{ao}U{C z19OC!3WslAzT7&LaGhz-2?%Baa7yvx^Nj?S3gQRQ5d6toUssvEwWlYE(&1^fzQ}e^ zFN_~+3YmgH!RZa9&)7rOzEKbhI7XS<*v=uHd~6#=I?J@D!XXiJ31vVR3m+FS0ld&F zJ_bUG<`or#HAManlD?~p5p7TSF(Ly_eRz?G)e7DoJ3rOg`gR*mdpP|M_VP8O9qwgiWid_rV0HtHc?8#E=CdD;Gw zX0WRP`K?^;FK|%C{P^m&-5oA8sz_7>bI>Csk`U_GqYc}c@i<*bs zdgFzp>5eW$FB=UBGiHj7jPlM%3{j)ytA^V;FbY~twYEl;EV4G;EJGTUm~B#UM#LY- zz_W=tR>k#$Po+}Dc)-QS=wPhFr)C<70(it%eChzjyE`YQ&P7i)L~x|*=Bcbn($i!$ zQMXoW{oppS*`oK_HwY`{WkAN4=sg-OR7NLGS*}$XwL!9V!FXsaW$9{u?g=T#fmNhJ z#FH)0-p2DCwB>00l!ByYb96>pxg~MW@lzcRqSg&s@8#w7unBbOwZ!KvAJ=MaKSWVV zEs+*dyJ(M#5)L9%?T|?zugmKqV+iP=PFMhmIIsHwvPdbf zNoP}#F~nHRg}Ni@)Pz_(?gcT0KNHKw-RW)I06|TR1 z*2L}i9^rh?!}dZArty<{3abY;<)^`*wGjOXuHbrZJOCCgeEi)~^g#X^4La~0C{vX7 z%i7+7xvQgTvZ)gcYw8$PSmYO5L$FLbP5&`u=s-e;)8OPqrKpjZnBB?=!*5OokAI$` z9*j}{vIRF3FBj)z2vitdiDe| zCI&loYfLI6W9V}=VDiXMKW*r^30uMA`pSo=a>wrX#bIM#>;q?Y=nWA&>?s9>Bc`Bs zjxJ!p382{{2D+$8Tc@`6{lYEH*k;N=zoAX@V~@Bvr;xPN2bPaKs0ev6VmUZ68Rg=@ zIB*`dTGG}9pGGJi5z0?PTB%NTR5(68t>Qlw?F?X*&1N{MZ~DQhQ3(~g|JssJU#sKO z%E+u)T>TuW#t_^P7`7Wk(-AB6Z|h4{;a#*sv0Nt+Xy1dyx`wXpG&|(qfaOv#da#Yz z(86nGHGqYOISap7S{+di<1|k4ArWbZk<^Jz^XMoAH1LOSpW(7M`mX*a5sWC6CT!7G z&0AWuWmAjOXE`qeL_&bxt<+bRQBtmgjy9pP72#31I)t!g5j!1UG9je zAwRWH~XDc#D~f_kwanZ8yx^BVrHq%Bt?!6B6G<9tkUT%LgUB{&cnnUAK~q zj~&2iBEjS9DVs;a-3QUY({4;!gkxR^Ew#u-4B;b9+`pl4JtB|j!}7)fTx}@;s)WYE zSSlL_>+bMYer@u;Z4Jk_u}dsU3L`+GJU2n{c#M&PZ zg1$HpF2)j)nM~&E30Fg-&sHxAbD6E1OK%w51D6sTIWa{)CO5MF68D0PwTRNt=->j< zjkqqxE5R$`bw7wq1mwt&Jv-T$t}C5L%Bw_i3gS3g>&aY^@A~m8R!wz0693WIV=@^U zVPx+iOKiJgz_ILZHurZn;Zft&_Rkpr9&xA?JCBN-u8X!|5>O=cx=OmUK~=0uX$~p1 z=@l6()3^22xU3tW8mK%H1%Zm9h`&-`t@e3TDf`D&#nA1O-HlZ(zodHkYocO)Y~lnQ zHx%+bc{RfTOCcMQo1ojSg`=CUXA+)yvFVL4JWSs+VCyxNit^yX_FEbwpSC)-S`5v){la~L>b*6JtmY}VS3Nx!qZ zDaGMj$6IwRVr>=$2xG$^gfy3#g^@X;YH=!-0Gs02US)YaR>jX72iwT})HsAX?W<~G zes1oV(Tr=z7;wxh%vd{o(`Z0m9#f#j zdO1rNkjXZ3ao&kdkc+%3;8XHJ%0L-`Rq)W72&!~yrzk#QHY68PR@#kBm>5?m;fEdl znD#v?^D%;_5mrQDmz0+)QB+USb%F9NFl~+E9Rx!zq`Xp5@#I__eloHdxjy-3I=V#; z0hp8@R}k8SF;UueE@2Oqc%)M`FugFo5sDkMwU8{;RJW40hl7Z6Nz!Vk9dzMBCPM$B zFUkboEiP{cK&&eie28kt@st`NTr*Jo5cPuyJW53!MT0_-1;}g@FJK>h7s}HpF0dO} zUBi(PNVDv5&!Umn+B_4pOxnAV-kWUkSL-~;w>(0V)Y-z`0R;QICe^ACUJ}w zil+-|q)+OrB9ZMMiEPD`RI4RYB9_UPgj;;#oN#E9RjFFAvYj}o9_p2-6vs&I#C*&t zndV%jgLz&@Yc-&D+fIr<0MBW%!x`D(Kra)FNtCvt9TsaLPnFz+mUjTJj0hVVNj%26 zK=>BcK3?>d`L9|(tObwasi~3IN>?A*T8XOA(051G`<>%B6i z$wIPoY(O;&N9}qb%SZ)Wv**D0wNkJyMPJ2m09Md|1I?U;S(BY2y7k-l6rh;e@18Fs z{BIn4FXG3po#WzZafL1#wlYc)-KRu!SCKA1g`8TuquxJ@-aoU=Gh@dFA-G~VV7R{# zbnC{>_Qs)&PZ~dHMC$qTAW*2usth+*>06rfR2PZA&v&d(ILf6ydBQ<%b?Z+*#x1BF zjhI76X%a_Ap0U#r5>wA?qE1LY6;NWFa+;(nX;z)2S8{AWC$+N4LJSk4+Nn7xXKZJSl-8^ySH*x^(l(w9I5cE% z($adC+-wGDINGo%ExZA4$-TzwYZHnf1W}4K;;%0i*i4g6xYwPH=Z#G#ipfUTS$(7L zh3z10|L0;bkT-Qw98#39Dyy#;(*`4aq942O zWuyx1q^WxZ*V>ln5%S^6($bPRzkwPULN6pMa&)U=XQ{E$qqWE6PK!vM9H$B)UMh7V zw(zupU4Vw@NX2BVu*hMH{RaF{7S!ScOIeR~5%)#1Wl)4RD`SF{I@0UQD=XiAyT-*W zulz({g69;M5pBDa6b3#o`eQw&Z~ASh2lYU&#>2KbvNHOXJk#3Rv|b?<$^!a`li2e| zinvZ&Hg%a#M=3-T%*aclwv1-#x=@TN6$T!R`Gk(wme-8lwQep@6a-Yb2UBx}Us~Qp zt7R^uw|@V->qX>GqW!2Ojqb_mRWe687fE4beQWDl;S84<2dINO=o& zI>X?1ukR3b8WS*)_kmm+b!1MopCqIRS%_*YB^-?@xTNZ=D9dtTczNEU>0EjG?0rRb zW1GX|RN?)o6pH_#+2&x3!JCz5BRP5^8G%w{Au4np>?}2u-LCSViDEI~DT?u(;oAA_ z@^>qu3=G5G(c;Qai|<}nCT##rZLWkK+3rWPzU%KD@q{DQ7?Jak=msdO$FTLdRw;hp zm^Nq6>jlG`S?Y6p{)HQaW|bTGX#Y@7MEgBPT8)+*tttUiHH2jb#|lOYjg|`VLRVfi zv^q+WYmKcjL)e~K+S~U=C_jb`qHJT}tCA{52+{a$=w86l-@Va&qfk#e$dfz7@a%F` zKmPXF^TW+51p)P3U`l{h@ZaN_?u>^>Jz_Couf<}BI(lesYh?2*>HRn})1$Z?IIw;l zC=4VzV?AxxEFFyiX00KXC!-e4IHec!?CI0T&*tV~)<}$_oPCvtEsPS=6v>GJ%ZmJJ zn>@tajt6Ji?-wa6i?3XoOwY!J~P$|QE2~K&;MIbyzis(3$`v? zBH*xHdA73rLe67SM-@VY=<`+iH~#A%CI z%^yU;a;d@OAA(-)O=+Bs;k=uC*g=|QZ2!@4{On0xY^yU^owR<73gctgtUg4EV;4iG zvY3#4*Mk9UPB_#QJ$ZpHaujaXl_6-&RXLHfc=q_)CtOPQ)}@}OS?by;MC~_Z%32wN zaAq3PEG#cnGSB=h168SOvW(6$JGQj)WNFFBbQ3}j^nhpuPd_}PZ}8|zePw*5R^NLj z0qbXE;({jmz^IEwZ`4}E9xk>z_4Xoae7uA^h8 zVr7p;urJR23uM8i+JS%^v~N38HD5_ck&_Y2KS|$I!wJONXyh=ga!@ctyp&*2^2g!{ z@RQHPH{x4?xLQ?}JHAq4rRtrtJiXAOft08v1$zjH8H`boBM;pOeE zZufS_;`WtmvWl3BhMA>qH?k9y>?gCEG+w%>*u{~QEq=(dpi8&gQJkLJLasBGoY-t# z6uUl`fWAXx)R0|WqRQr@^4xRIy#5Cz{7M}h$J&4l-Y);h!!JG4nz&7%xc=iGDldb= z`e;_U{0&~T7FWdXb7RWAK62iAUi4J@YIXYJkyt*v&lDqPiNUMs%E+$hwC)rqI39%( z&4W&XdX7C~jfNvug;QVPz$vPwMmu#gHkeiMFZvjs$wlvMKIS_vdco-{W8={y1B-;2 zZ)my!wc2yT5H-tg4;V=}$6%>o5CRkyy4~pVsCFYd+{w5HJr$Th6~z)cU^?OaRPgw8 zMWY)L&KIHVOP-8)mpmhsO_Wy767*C-LK;*tBflBw(r{=?PhBw(PK?BTSQ;c5Gh3Zr zLd+~6Ca5vY0C?}F%Y$5Xwf{k?|6`)9>7qD7rGV6!i$q)^F7$c`NnA=ThW_H!*2d=6 zL34BGMPqMzFf}u^Wpv3epnL6Zr`4}tp33;&tj}m}?=^PbY;I{~fgM@bYJ-mQcJK>4 z*?+yY*F4lW&KXUcomM?et@qqKa?d7j&?SiFL(BzZ|vZ- zGeQe)#LXRkIyYw}p%I!#J`Hnk?ihK~b6ZJN?)0Qfl!ThBTE4h|H^iQcC;tb!wU`-q zHvaufbN`^ZxBnJdAO)?Zg@ptv#L5ThFyI@IE@Vmp*$9zE35)7tVG+j$G1wB7L>;Fw z!8gg?80qEMsdQ2A_2@-KOen-k7ZqZ)MYe8oN%53sSw^F*va(Byr?gAU&bMS_;mJwlc8X}JI z0C8)R<36tj*_U>?*?hV8rqOJYQnOsvzs35xc+3ywquuR2=xOu zMjwrk$`#RUx+CCiXQe};WHG(M`8mgEyjb%9ploBgBF@8d4cYb531gU}d$t zz$8F2?$R30Jifscx=ATTz;ikH+?2J#?WqxNBk5#>d}X>${G(Qi-MO7MnXg<1gXO>J}#)B?quPg78Wta5-9B`vTNq!G*-mclE!k($XjSE1KwKf zDrYLGt=H=jS71GF;i;DQR-;I2b~eJ{FEBnG9GRAa8^_m z>KlLJzuHqQS{;%w7Yy$)0{D0om<|v2sSUEVogWBl2ms zic+H1xyav2t;R62gRy+Lj5Z{S(@)dO3_5Dvhq=m^eN3;$c$ryS=DthRFJ;GZ#(%o} z*kbxc+Ew5oI)`Tar(; zt)HAAV9n+o-RlY>&A$!?1B%sBg%x8|{&u{Z;&}JB&s@@H&Rk3r{pR8c|03VHti_Lc z{}M+!PBy!*Tx>umJIo~qKlUWeaW0-jJI}>yPNvLqCCN)gQ+-Jo6Jh-5O4KvMCEMP3 zJpM1YEkgpf9G6HA5%4bd!S@NvBV0uN0OuZgJ(>U(5}{Ex&UQiFOlxf`BL(?|CRl|n z;L~AGQgr=XH5Z@RN#uc0ucsU($xg=c0L;;hU~v)ytpGCSjyI8*Mki+~6Jc-~lJ`(L z{5T7cCz(0J3y8qEV;BaM#oE{<=>Za?Bxj8N<6 zR=gGHpHUDoRij|sx-_y*F6tjokzB>gxNF^U)adBFg{9{hFL!F2V?LQ?_ho8fw^wCO6-qj7HFg!_E;wA-h#yeJlg z(OKZilc*iEfym&v9k6k-R@tQP`e-Xfo1es-`_KP{;OtY4WX#mcNG6W^#bMeaf~I~K zju{cEP69NO(Pkz(`CJ=UwfUu**gdRm?jJ@oMxonis0cHQIaT61BYu;N zHmyQQ%Evmc)V9Is%9KSjUmP4p55>}{5q*`Q*C^bZG?1YmNbhz8Gj|oJFc_kbFDJ?ZBy8K}*crFI~h}f7| z*5SBs1x&UQJMU6!vEwmwFbu>GRi=ss>HdMIhwgem=+}T)F56P;Vf$vF6 z$Q!;d;tTdh6-2ZY@+^a=pXuI9{`AOrDjBXttjtQC36)|k? znAt42*B#kR8R|miQ;v%U8KF6Qc^E_=^}0vC9iUUkY_H)d=Rj?8Q8-HD*L|Xic2qn3CPX zI-HgmwCJ3c-)nhc$lEftUBDNi)0X0xEQGb#Kw4JM zuVVBZoguJkc63gn>u%o&Rcah&8OMhO^+ngo9q>RH&0CIEb(x&Gu1& zR)ZZ?e2pH}+TQv2!m$dlGG}HS_xa}NBpH8>R4)x5Uu?hI-4ZoiYbY?K%jeK@yX^8% z725PBM2{4FgLv6N(2jgPrXdoh4WuPeNp|8|DLB&y=g`o9CVPf2412s-8Vx3%;sTbH zG(*#+Knqm^^-ZK`vp86{2D)-cb_ zAvs@cZ5+LU`PR1rn_5Vd2#xMz4-^c$5gNTPNAqbMVJTs(Qua!;TL^af>OJ~{t~-mr z>NT^vSY6yXSag&{;-r+01vZ*mT^t#L>?}RGjGNwX>rPl*KPev{Z`A(n)_y-dT|KE( zPnN3}u?8gaKr?kB*#Jt0$eB%!F^jOn9K1`$?xL4?lY#?MK$l36FT17#)_TGTtzIr7 zJTxUW8*yf;$K%ycUxdDuSRkG?ob0CPTOa=Zhg)+2N=k3@K_j6ht>;tFZ*o zfmxU@SIv6w-YD+^BIj4Z5cNSMxyNkq4hCIb)4t@&!mvbXDl+nJ6Zt zfSO@dal&we8weHko6IHWiC0Hd0)iyLi1sM+Ai=C;#H4&=)EhL@dd6=lH|#;Y@$m+T zEWA{>4sG@$_Xg#u1)(8li1}2bNU2Mq-I|g+OI`ZftJV-2@kq;zi^W(I6Yk1cy`y3> zUW&<8#Lwx}mN^`GcJ3jX;6%s1^e3IR5;ID5B&jMDo^QR}-V+}W4%Z#nRpJGFE`B=c zJ8QS(Spa!EL9p&PYf^@W{+;VfYjWyPM()D_vG;;}vwZQrV81QDR;x(Y1XKkU=_Ooj z9p*sw&$?5wJQ(=RI=(SqMRh-Vq)gs#`3e{%!Tc&27O`aJkGcYsmuuk%k-tKsz^K+* z%}7=-Kk?t&g0372yn1`}mk&oSs7Ct;E(#Z1OT@U%=$-hbn9FIRs1;hqWj|<`D(7PTa(oO7*QA zFV}1m-4BrCCy5)vULkA=<(BQk6^fRTq+Fq}7^aILyPX4THB#vjA5X<7LAjmR8ELsx z1c+zWpc|LOn|J-|$8?fggGJG_$TNnFO(=*}f4s<#B~Tn5!hLihJ@+pV@R|JNkuoON zOsV)$uA1B8$DxN%O8AXM5@;E8Tg2+e9T-Sh!ko13V!RQ1+O-UVXu$S6@tB zC(H^K_K?O3`96uA3FG@8*!IStkEC(}x4*f1b2OLA!uFwO1B~JE@srwjr>74)lJO-* zdOvGk=}F3pV>PFt1H=h$4aRtZ!LXv1L>0&JX=I*;;W(MIyVFGSDBoqmt@j*jCN}$< zD1E8I@S|s^IwU2i8A`3s#-w~iVLsrG1ID~aic(%!1W_? z(pkM!RYGnO!YHuKD-)oF?29^75qMik zRu`=pwZN$X2G9tbp%A7h8BcV^Bq-{PF#@3tTjvyEVw;c#Pf$c2ld7P!khP@9V%&ZN z7$lrBr8&ZZ!U20yU!j1giTDJ~Iv&tA`!FVwe8+=QZ?p6hJqop0FtH~M z>IBCZlP4KxNVd+F>jhHJg$Uly3-G-}+f+ z+;27U6hPAty!W80L`kJiN~Q4YU+W8WrTTRd_^*@7NmOj{>yzcLF%gu>U{?pbQA-!o zd$lHtAzDK?t#*`_F6ujb%|mK^#&|euV&*(}LXvIp@*0UTjOml75bg-=Bt30u#TLS} z>rTpMG`TCPI&AyE+&=v*ysgE?{^i@|u9VHZvzBn^)}0fQY_1YsqywWZ@gn8YPr275 zINJ%=cFG#NxW8Loj((s-dFY?>gS5MSRD9}1H?0CIH*>m5L{5vTPN<4*Hw+?ycA_zG zU{P+>q@RT#M%*@AJ%c5Vg6M`Qaesp+mMXQ9xuO_tJT88r?8zX1*ftJ$d8V6rQv8TdZqNRZEI_+UVwSTa zY28?JUTYCKu8ShlcKiz2bZNKvz+Yo_{-oIMlE?@tb5tgo7`8oxhZ-)P!9TKcu1zOY zf@OSx!h0Z~Y*kqssAno^QS}Yi0XIxmtx~GrDhJ#1yN|B35*XTVY(A^+xhs)Nhxra> z0)NE9SCHR|l_*{zm8cGflj09b=GyT~dg@}rvl0~0?Pg62PQ&C0ph=}sqPBFq>J^=v zY9itn^Q8Co`N~h?#p^=1y$H(>4)5RJpxr!h4u@XO24HYp0;~lH^WRmM*)+Ls#GWXpH3GO=j31Vlb@c5-E)ui9d}=9b8SF(=?(t zeQF0+4>Yq@XS4_)#zK|e`hDmZ9C>g<(99ZqE>v4xO~&_JjC>$=4}Weba&FE6%gg_S zxtYvT(LqAvZ|qh(a*0}mm1)adP2s3Xj`n^hTrY0FIFyg!+j?nD4YAa7Y8@w<#7G(x zVa#HdqEVcFF)wz24M4+p9aB(8E0O9VQrKUjl;qp`W5Llxbu8&9(7SBI`dLe=DOp7s z^Sc1jDi#+T!og;LO*qG8Qq{pNrCqlu!3n|dG>w(Y+xfufibTjCGAg|Z} z7TPa|+|T;7iP8$BtQ3y9#Xg1b-K#=*jIKiuyTl;;Z_V3fvlatEoC$V^PvK*!O z6;7?vnZ28mG(T!P9AekFBzjf0)IfYiSboM6bfmRX>SEYh0>NET%7NK3}()G=iyv>033fv!ej zw3r&QBhJvYlj?C?pAbPS<(5GqqAm!rOoM)1l)d^zT{y3$%KZg8=QyJFJv@>-#ffn* z3gP7MOUgrCQvNF#e=0GZXM$e|u1RrB8*H419Fw>jqRMudgM+z0pA^YFH6xmf<1>uY z^j;gX!=m)4;f*ZnB&CnLzL^4M-WdszkRgMMf)@=h-9Z?%Id)N|bWh>+S)vRsBwa*t zKXDuh!fuPvsG6d<=xI(7TcR{uT9&oy?7`doB}JlUNN`GLv_tDfxbBZ(3f4tkg0|6$ zLM;K_z3DrQrhk5xdh@&ctxrqS=+ZYq8dtK@3(E>c86dPT#@By|>A{~avz>W4rAln4 z314wcjW}X<$S{noguh>4WX1@q2qQiOV~h}n>ZC53?`^-=m+&NMBYuWDPVUk}8go-8?>Cc}z-uqde@6VIv;B z&_HLJ3KHvKEk0Rpt*EnUIjET~){GhytvSihzPKi7(9%}Bej48x2HT@L@^HOKYh(02 zwY5=XT+66%%BoLUAF?+q%Gx8(=}%S4jE|+Yh!L0%W>`PXUudR{Q?kAlGQ>($s2`t1 z$;p_d_LOW}qSPr*pVXJ@%dtf8WP1DM@|f0q*k1Ecc+eA1>dyo^MIzUr>CA%-lc7^o zkyHQojtt2{hzW|(&vkH2T?&~Kgi~_q`tSdmNy%g-r9lvZCwpY&|GzYU`6o{@nUnam z^B@>4UIsmHk+r?dsnJuuMH>&Gppn;8EZpQpF&JNehja_SEHCMA%aR#+NwKDsIhhqy zLRqNThObH<)<(T;7Z(dfg(ictc^hM#+@VxP;!3QA8IJown17L_+47(~4)9&^)$2F! znrH#s+}+>YLIc2ek5_E>-fY7JrO(3k30G$O=92z2;Ww1C_-9YjihuUR@Cur^T{G`z z(OG#p7`O{rtFwF0)!EIlI$`CZ8@~d7MBlf58!oGduOQXm>o;1zIo8a;U0G_XMbm19 z=0oQJ7R$s{a)+fb!yNYmnBqs|_o)NiqfIeK>WmfiUp>#Z-0jut+M9RCpoC(9kqj0@ z*bT1WT0Z6lpRl7v^VlBR2t9rJv?^GslN2wr5eOuqUX-XZ=n_VTAq2`H!t9W_wVDj! zj)ETJM5E2QDo(v_Vx8+ypghuL0P5r8$A6j6$e*?j9-fDb%V)QFG{xikwKPRr7UkDl zRaWY4?YyXbe$L&822ikN{6evo`@k@bf`OFFrHWpRB=-F47lv|eE;80Puoz)kG`nrv zPJeFhffmyo`%T!zVFwFy9pBoFITzhsXZ){NtTPIXQYKih3419}cd%jWyyoa@;vnJj zb~e%6K-)RidrufC7A?{-`aFZ`LCWyTAGI!tix)eayk=^TS3WH`@UL(_E!27_23a`| zd;S2Q8b5_&h^pcZ!)HDB!f&lVUa6h?BefuFeJ;Ik7Z1hB7uJJh74R&oWRJ8%vL5)M zD|z+=i-KcYRq^b}xzPdsl`I@GM1{i-UG}plW*;h(JKXnR1=V^k3~byeD34SPmxwI% zHaC%(&dJakr9wsgr^OFPFQ%INPAl1&M*R>O-8gKuj}ltU>=2y^FeXry1mpx*)(SeE zdVy%r0=>h1!ap%h^a~TFDZ6O2xNZ&|fTHtrPh3F#twkXEs&pvn{Q}DIYe4nz2ZnYC zgDx-xAwBz_K7F&u)#o>Lr!dj^t=%VYi~@s}_b`%ue>_4y3~od&MbAdM*Jd>4k=Q(H z9=zF;g=)xFjCv>`S|?Pbpwp%%R6<=VY?Fffh~9AT*X1ZM0U@}0`C^eE4X(QDCvl|Z zct{~ayE}2#DC~P`WTD&;ngbp zMQ_wvj4~PmAQ-1EQH7zqenzk{P|c zaq)DeQdtw>dpO9~(F^Jp3PDVeukcYtT6AK%C7lYs-Il~ZW?@9LR+SLS$@-8%5u zm1ucu5&RdhD(@pB%(H$-J#e2T%u99Rov80!g+ci2%R<(<_qE~PV}?Vs0KyI(A z8jcRopM^Fo1&VJi6ElmFjmRdOkEMJve$*E#b8!=>NP0R4f3d}53vvDwGJaPgK5eaN zzWsOMoaH>t&MYid4O~ZNu`mY=(`;Ik3%jou%s_s%Hs|eoEwQH8{evyp z890~vH1CA^tVTc+3dx59s`)q^I#Rlat>AHeMSp+#fvhsn?fD9-!0&h?d9%0uZb4Cj zh4Ix&k`5T2DPPbD`CoN5=A_{hwor`E!k@gdGnh4@wpVl)m|S?gfYPK(j$T`epxn_D zkQl|ewWKH2bbr8zKNt-<(h~gW^$P~jBH?^(;%ImN1|JI$OyY=wOteq2E~~i9cWU~#q5`e)7;#8x7BFAKG=G({Z8nhw$vVT&vS;{N zB@mtU&>xJH!V(x(kD|l2NvKK4g?`UV#085{L@QR` zCc)H`5EM5)+J`s_D+^V^?4wT6%~J}M#6^~9mF8-n9|^(ASJ7laCV|og?MQ4^!&{l& z$1@unLgf|5ho-jqM!AtO>WG@6Wbia|r^&M7CFLz4#k|KD@@*r;s>@Ns-f=c$o zC`44y_p0(&I3no-A%e5J2jbax-B>oXDAhb7J0WJRfF^;VUyy|HkixwN_6`C)fnQNwDLo3hQbL6 zgO#bCTDW%b(muu#S8yg-wV|<_y&yJ1;TTPEs=~WQD=F*%kQX#^A>slpy=<8ge4@5xe!*EraY0Y5{K2sGV09KF36I} zSTVg=BLPAxI*6(0tT85_jRaPJ@epW;*&g?LH#qkQR1-aQj^yW2Fq+MqW4{+T?D47G(j4Aq>W&!I;h?N~|Fm!zz`8dlc>G zZ|Jp(R?_MQ=xPk)CFoyJNJR>1fk$u)Za$+MaT&0|r0U4JqhNPn$&0F>Uc&}}pA|Bt zg9*p97{#w2^si@i=;EYeFYBpzfl8 zxwuD~02j=$iXZHW#3GrI_~$>77;Npo5X;~HV+C%G3k%|{J46tFOC95fTz-I4YB7PK7)J8wx6f&g72=Mllqi%?qQ3;3sld^h)oU|> z!g^JqR7zAZHG}MzVZz|pB>fgKbr@-zLyba{_i)=oGx2p5!M!Q{CYKt*8=7b#p3m2p14sL7`Nc+nr%gW(0={$M5WWdcAb zH1UoC9BX>ROnZyb^n zLGlzA>8E>zjtRF#Qr+5e{RzRmUctIzvtn4`=RO=jH>;E|;uSI~fdVZihDIg9xXi$; zxN&yHj-wq_f{aQk?^OHPa{tK?;>MS>BVC$F1sqzQYW=- zd75HK&!d&T$}(#BzqLAexN!#5lbua#^wr2K>`}TBN;d>FK8J1I5BfFy%2!W=J&cM> ziB*msE6qkjGlbLPm}e$={$_h8BgXjT2;|cup;HSH=C%2Jqw&+>&c@*@PKa&UIC$B7 zyM6Skd<-P5)@>`se&ewDYHQ~;1&v3}wpPRKS<{K;jtwLChPK4#qmD+gEq?sx>W?x= z$9g~barOHjwM);jp^$+iT?$D*eG-O-hJ0>ha`SONkj;lRhdXG!+ny{4ofi#cLvX=f zv|1`4GG?veGJyQz0Ro*LG?`MwT9)#W~WXyBQu6lS@Hlm!H4M&Hke>`RPkXl=mPL`IRs#vbF z2|3HK1Hf{>WY#FQ-$sk?%jaRx9gjT4yYuxLuPcOb23HsPLn)|+9x|mKtb@`bM@o|M zk`_6aP9Y0&3Q<$Oe{rL|Jz>iRIss96Pne)W$oGT0h$z z497~~4-$_YNbJZXXA=~w^-Cie4-Zy;-I{SZUgly@7rjeH#wQPrcKuO|zQb{LUD@>lf)ql9Wc#8;#n_~`9YTSdyC0KhdV zhp+0AkcoE(16PKe4AJnKB;$P+H{wC+4?4tx5EzQZ7@`Eb}eq zipAF6P~|5Ab!M?BxTk6#7=HSn5jcIMH(!7f$?(5Ktmg9w_h5t{GF_~)X;9&otd<+8@ zd7t78c7k!A2npNlNm-+0sW2ETZsNl?&kv7)*V;r*TKW@5myDfFt<^Y+v;#_POj^~1 ztkK{EjSE9)-57S6JL2LEKCcqHR%9f)>m9n?0bbAY&8LJ1A)D;v%j&Kd9{OMHE`P$Azezq={7MB1fW(EulEm* zcDL9`h2MlEn~?g_NFikuQ;*GT)QGf+B@r}?YA_hdFypeQ;QT)*!NHpsv(?=7ZoA0| z$WtGmAE&)oFV%)67(DY)F)g7C@oqf9$#js@a!_o@^CZl294on_4KCzEw)%xWS%uaD zQDniGdW=y~OUVfHI{eGzxO=9V7~LVhN(LV`8`N>t3#$MCg50|(UMeJ@Jz1mN1Lt{H$%BB&+6Dgx^q9E%i@SHdIWd~)9eqZ~h z8&2DtwRPzZ-4+6Vg(JTe#&>3-ebcl^>6dwlabfC_hQAE$7r=Ub>hT^tW?{;4MU6oOvPZUty=M|l3@4cisM$&z)9f<<>l3QegjmWAqc!si^3^~ zp&&UvCe_60shtx;M`2ci>%i?L&4jf67>Z$ef`sDLcpGuWiDVm5_|fMW;AkyDm10}q zs(2HOIkv!Ahk5{2;%Y^Ocpq&$EwMSOJm7ap1_2MpLywxmO7F913`vx~!1xLaB-1BJ zeH1c@MfE7y4=}2AlBl}~U-(4B7fx7|s@6h0lyUwnb_n808^D;nt(_MFaooQ{SqFVA zAov$N+ub=mJ$x?8QohTC@{>Vn2rw{xm+BA;BtNe0q^68^D|>^Xxo)c-D%UL|8XE8{ z7~lE|L(HJM-*2-8Z+Pj!4BbZWu9$w}jiC?e3eWtC3{2uPjPxNea zC%ygD(|E&_y|I0Sb0?nT^1vW;iyWG=9nb=V<@7;alS;gPm1a&t-<;p14}#r)!rCMQ z7$6WX^2@YqF>n*tMsDo)nnYSVZp$N2TP~1pa!u|6J!k(-t=41nT@6b@GxFbeU{eKILL5q z<$>of)cnWaJ}cK3s7I*91>&|U@L}bLPXn*!4tupL#H+y{(9PPjCzVOvjBuR&f^Y;A6-l^uyiqAs zci!^=mM7aF_5_-vc6k|-S&By|!FZtZ=A6Uw3m7G6#V6UCPdZ{pWzm=LzR;~Q2_t|R zul&&Vi2M!Qi#R0HP06Y|HpkYvla(+!cNhjhyDrP~r@jyi+8B43&$m4EUQr=L@~^5W z!o>`EDDFXhB_8@9*EmgyIgodrLz|P3AD>{E*X&0XVR03_%WDD=^)slE!7SML`3BdJ zib#K)zwy|#=pStMv8WkODrIDiDnFFP@y;&rMwK7zZj`AXl^>3)kZnP{-l!dHA2tN# zJ*m@2=`?ruB<#BEUpJv6x#y(F*2$kI_4v$K+bvIf6YVryHB+Fx=f&&o1{pphZIPg9 za9|iX)m6Ifl+YUEMtnM{yTmSiY7YDs{y>^GRYt5I%d@+gXH_knd_1r~Em0cE1}&H?ssX zRsL0B!YchGfIJVpa+0(C0Ly3k8Z`iUGC*zePl?7*$)CkURhc2zLpYt!R$UyrA_JJg zMn3oxBqw4wmQ(yJHB|D!)MGcDb(Nee9T5H2aB6>yA(Od6@3*nnA63%pXW@<_AEk#G zF5smm50|JIflV}o#(f*-%+ffcHO^Fy$d+ZPcAH=vGfl~$b%#s_^QBzhb8j8Wi~3xn zdl%i{e7%4I&t^ZsZA1_Hfnm}{>TM#yw8J8*o1*JVipQh~D7*~d7AhgJqnaj)dW49# zOGTG5)J^fe@CI@>&Y!m$?M-j1^Wx&=Kx7Y7(e);|1cBuK2a=m_1Sqx7WkB?85fs@vs7i&7|ys3Mm(M6Bk*wQVgE$Bk( zbQQCw(y)3+cW(H{KSXMo%1$a~Hl3E%a#!1B&kL==Qj&-ijSmSCtzDjU=(4+S)f98{ zBNOdZFOYT9f5Vwn1+E0E4yh+3iRd6e6mZ||O7+E^D~zp9tHKv)HL-xySp73pt@V;g zC8TM0ytkXn?$|{!ZVmM*yN5MpEYfAKTA0{ylxl&zx0}NPIbjYkwI)&S_bQRm2ignm z?V9FuXfU+5>lhZblMRw0IixIXo$?ztPW@hU6!3_{ySSx#zvmPt8<^vEn?@c}DzYC4 zu7`*SDTqhSQEvb#$kfkHtBwBhn+~QL4@Q77c%Wh^Yx4pB7`HG`r{C9gW||u2F*bxW z)G5O%PR8kXAgn0YYr!q9;d^nfgv<`(pOHelM8 z#ga4*mV_)G;S5UbNEhEMav9d1ljEB-DWr35jx!`0HJH{YA&h2K*qbe@+JVFGb;VpY zn?EKqD)5bXLC!5uiubIsQ6|N7LnGz8bf^n}K=;QUq*Re%uNZ%SG`!JAhQs|Asn}PL`{r>L(&%EA%3&h-#PT+k3KVu}a;`L!C7PhX)3oXy~4ESe6AMgThrJ z9!USasNqis&WKebK-ih8U(~{z-g(dsrz}FuxzN~u@j`nB$O6`#L{XMKycW(zIaclW zCVo|9PAU_hnqrY?)M(;X%>#pGPQ5NDSEGo=;%ozb#Z}QWtXAP%j`JpXR7)GFjp-+F zX-b6^va~c{@5n6joupJ4V?x|tKXFgw7ie8+4L8d!A;QrL50?- zGr>pTgEINvaz+gP)!YFF<;7-2 z7vdO+9at#3Hdv@EO&AIDSz2j1TC}$-PEJl7t1n1;uSfVeM=P;_)a*{Y)#i|4V1ozi z1l#IOXscBXq|T@Y7C+1TN?X?`8*{t$1#9X{u~H_8hwVibnHy+cYS=@bL6x@oZytl|O|!>NNpw$pY%Vd=9$UY*%n=V?Z5(WEVwBq#2m8B(XaPSaVphaW6wxny zNRG04!%MW9m=7q0mI+$EJMy&kmW-s#fn`+6`e(3fOz-G@EgVx{Z|>~iag=QUsfR&B zJF&mL9s$!1k_QpdOc3UWaM|z&I@uSFrzJ708k1&dxh#w4#po(9OuKF4qA<(4Y93y+ z@YrUKS6xg zbHZSdQ4RDOyW4DcyM%8>@yO02LCgab)`SZ{7k6)(;7ysX$t0^P-jXa^Y#$0;YavHF z9CGM0eCtt0Hz9p-T8Re3ke!GaiAl9!qvBGd?DSa@ooJ^hkf!8Di3>L}NLtLneQ*24 z*5MKHjg!o#aaWxHv!df|!^WL?lF$pSRFJdXi z!*y>8@!)IZL(oHHt8M~8N@YtC&!vF?K-%5)g6%wxuH$5#j3NX0&IgG4p${7T#b!+M zhdOZZyWxILc%U2vD z&((b}K-$IA&=+eDSi~x(aimv z9I`hpOLmu>Q;q7Q-LqmP$P;j4Es6n|Q^7cGrFk>WdiQw{Us7m-64G}l`nl!~ zFNW?pWt8g{B7>nznT+j*7)#z2MuL+^AUR~A z_xKGCfXB^x(}nBLbzO+Jm&7kg^kt482j%uH z8PI2Lyc#gH!i0L^7k*nW9Unh$Y{JCBIy)_4VF&1IHNasbxwuebRO*rpw~T5O@-Jh^ z)9wv7y+P%|A+=sQDIXs#06gM279w zAF&M$5Y}9mUnBDdhjiy~5Fr+N14Se-Hv~tB+nfCA<#rckFC5g7B;Y!#@SV5q zj$FC{lYO+>ZCW#|n z5Ds`#ri}M#RUpavRS+k;J4Azdg->B+D)Sf^E(&hoUB-d zoc!Zth01aAx7rK=^7%W!wx+rIFzZ-A(b>ZR%i$HwZPycxgPj*BMI>@87$y|1iv2<` zfFT>I3n#~*U>YA`mB};`+^Tea@^PuUd~z#8&tGe3v3aFrZ;?#{ISUVLqH=_=yRiFF za^R{+0^>d$sX)|*3UG?(gFmVlq>%(3X2Z@k7V0>{X*y*6z&BaF+~t5(1s{D?wW?)K zA`S*C;}X`Q;$P}ahps!r-nw&gvP?QFnfc^oMZkX!SNL}5OOX{8hNU!wl$iR^i@J4W zWq4H@dJ*Fgn-?$K_Z~rdMQUB{g?l58*kYuALoioBct_}|Cz)lLAJcB?bdujt=XhNv z+USN-bKZFLh_W=B9pH*u=)11S9~;?D@N{6f(reJf`T7R-^p|q4Q?dB z1dHR6b5d|fFZ&Sz{+ppkZC{t4YeGFZDT>FpYdZDfuc#Il%%+Q@jpsXCiyxtrWgi#B zB@BKAh<(?CX~7@I{;8SykP8LQuYUoJt}o0ZwnQteFO&y9aJ?{6mBo*g(QMV}`m#|9 zv}oz8KNgjKOe~yyTKtG%aLk&-(j)O^Z*%K~Q19RwKY)Gv>!;I`%0ju4$oJ9jl&N}E zb4A2cbk>)kFl@vhiznriN}cNOU=sj$K#0G^M?&rEh>rL{oH$4Dp8yKxY{GwL5d@iC z#v)*2RK?=Sr%xxt#mX9EX_sBZZ5Lt1$P&Vp?T^J@kAM8(bdf8eM^*ZFE#5{-OJw@sZ&T;`e!XTo1)hj zj*sVnYMzu8Rn^oLDLlU7e;17EicCg|O#F(AMM)lruu8si%Br9ATA4pA!tNtsD2Oiv;Dp<;q8+ zn)Q-fTC>WktT8%fd8x8?tC5zO_QK`y=-`*;7Tz5rV}goBY4=c9$VNo^o-1h|6SO{> zRjlg~>-P%M7S1dk7~B*S0CDn3OrCs4oO~~eR7FKwE7n5DMbB*bK0Y+VU`T-XNUwqG zhZ}w8M%l>mWm;MEK^rl#E{v}Uzrp;gCr8RWulIos_~c{#O_s#a=DeD8qR4Jaw{I7Z zEpmxgHdy6mg>DHRjG|7vsk`U)GwLgz%nuP&B7wCbEUv7H{WjWJzlAx-K2ih zZ2a;P9ygozV@lb4+t{jLn>II&HY7%ZJe4k@Pa6mO zz&92olh4z@HIbxUk1QO4gK~xDN`ggCpNF;Ru$_1kCzd}Rm)L4`>Y(KcxlYjfhodCz zD5PiMsAoKrHqE#{ph;|JyR-fLa2<7khiPva^=`e$pO}KuJ1K>eak5PB$P6>^p<5{3r%td{Tsv*w$OAEoejQW$%Q@#M4KAtn%rmP8WtQAGmD zcgNxOl4bg7YHWs#K{j!&CkrdhAevLloqqCVKnnBZWuOWN?@9ki+7QI63q5v}kjmJ4 z!tOqos09*{{}wK288G(862p^|%vu@686kq$H?nkm{Nw5Ai|w7QQj*EcD%TDKnx?@L zQ#4$L_0|!x+IEnuKBMc&kU$h5FXXsd9Yfv~N>LRt0+vO3`gBcdm6;}LnHIz)nnywT z(&X|}+F?m-RXw>yyfd|~AqC-_RSaBzI7{QK_^C6tGZuUGK22tyMYMC2l2u?u7|t#k zpf$c$t-23M33x-BjcP$x9AKCzTA#JXpoz5)BjNG2+o z>MGut(WU|IwcT@6WNcu8`-e|NJq$ERt(hs;>MGSbFlr^8QAY{rJ^S{3yb&+9-|cR# zM&Xo|Wy7;?msXZ|7Bu*MYK*m5d9LAcS?f_LC2gbgveAg#FR4tHMBR)oPXtC$GT4AN2L` z;qAbc22wCayRVv?zrfXw56r@B^q?Q0Pau?DvfIAU{?El=V6=DhUoT(3j)ZHH1|@br z(g}rFlCu?d#Suv2#QpFJlhuti?FmO7DGpN=?;7|H8SPO+(baP_%sOVRwx!_RarFke z9S8a9K<^f5!Aga77|He|C>~PKx=}(n^4xZnLBOTgLfYU;scVc1Fq+ctYuzS&t7@}T zf8T4VvoP1B)PBNCDJ(*Klyc0`R4E&$i8IB(cjEPi@}b8GArSz)WMI9h2$`rZ#D1U8 zXPGIb@bj@!TD54#btx3a%uIQ-b4Vf4rAnTHi6WQLC3e#a>XULvN5GSIAfB0y+?0qs z(OyHqHMCkn0Jdm6|KHxbD4-o`$$8`?w7rx?*A(!I3b+weMSukR$`2!Ih>`-NnrHxl zchx-GAl3y{Q=CpzW-lsML>z}Tl@}y#Falkl%iss+rn7={)J$gvXJKVEJE$dR`SYIl z;RvpsZ?6)*6zpR(k_hW?VwOmP7A>6CWiT8gPdS%r5Hciln};imFh>r_6c= zLO0BgQh%7F)5wU2)^{3A*@jil9Ir+r&uGQ2!&B2+gn+~BxEyXrBG=0@(rDUOaiW%g zm>>OHmdmOFHQ4T7+sWkVJ7*rgpCoSeoI27T{o6d^)pPDjv~p%1lc)%V=8|JEO1{t_ zF{t4G_B(|a1iy9<#m{~?cDuAY%KgA#?m|A#`ZtI(b439R@qi~&W1t^Dm^DpAGyW^r zFPszoreYB!QNT;RWX=Q;kQfVB&pE!ik&H73;HPK==EYAN+cVOrXk=>Wq=)fObBzpk zS&d>S;zSwXG-RkjL4_HIRo?p+w(s9&vXgB`%F<^IM;FF!FYxIDVfadf^= zc)h{s24lWb_(sniN(Usg?AquKE?rf`4w3;@GI8+)ttQ^|;pQ!YC&~IK-@b_C5BDW> z-2dw?`Ac7SX?D*quDi#_tp>-WhH~`JXd67=74LS$xAmo}_^!T$;-A;A8`Qt&-Ld!5 z>wBMd=6}1(%>Twe(NH0kRg-=>i1Yok3BvXCw=;dFFC32hC!%4V=J14;{L}h(vmM+f zjk@~^&O65cPhPWuQcvL*;wRWm|C;mx#=Yg+Z{OPwK1{gWS^F#J(7Y9`Dqle`?pe;W zHXGpIkba~IEFM&3F-d4~FR_Io!p{)I=iwpl&MWZvADHaO0o|x2XSh;-QeVE;rOBeh zoBo9lv%eUp$Fq`@{{!@U!k!p}vArAlWKLqak@SuG=2R;(_oTj31zINu!{bnsur+ec z&wUv*oAlk4@JS)SgUbNoO!9;?TpxJTFCyu;@L#%k%VotQd7AFjzh zZzhdG%k4MWcamur*V_+t5fKC_0TxUR0R0ylcKmJH@waKm-=-ZCO*^t|JCsgCsyP0( z?f469JN~PUJHBWmi~pl$1xanj!y6iW!3Hc$>%w*r#*L2t#Ze7)xN7{s^F)b(dmfN7 zbOs%_fy8%jpp8kBBwZTqGDc@1y2uEG%905~{F(sM{$;lXKlZOXk z56HqHjW>X!n>sf{$1c8&iHzkNVI77MnE&}0;h)-~(fDCTzAwWZ#GcY=@tf~4sb%T| zNp30$%Y+%bVo8vy&NmAmXA$7dCij|MJS2p5vV4#bX&K%p<7u|uHG-`1B^Kf0aoo_?nGYo2e0E=2YX zk^a!VfI()mwYTw7cOo)o$lZ4QZ-x%cI{wd-jUPzR^T9>un{VWo@0Qe;r}7K;$Wna9 ztT$ARp(rl^`Cnk286voidvw22392|uyniv;Rw!3KoUCw{Le>PeVxMWfvPR*~Jdd$p zTL#~82WqTEz^E)t69V6l4jT=wP4*SlaoqaXYmB8ZTglsf+pafln4YtD)bW%=~D))p2mb z=fUS}>>$SoXU|KDaxz71*!Ql&Abd8X9OfrO4s+*I=6^zNHc>Bhhsz5dmivb<7MIfw z%ZIqK4BLi=^C1c}LCpW`B{bneCAmDw<4F!L9QWmO{Kl{7+YFqyNriJ?*6rDzv>~Sb1>Ym;ggXW+yQ6(9hgKS6;Qby{sLwSX52NpH} zsq|}`QhtiMI=_E`$YmHj*#a2IXXmNgH%qR3>K4SPG$hl<@PBSOd@-LB&QA2|v~ntS zC>nr19^#Mii?miovmhEwi$R7GKA(yBPO7CdC0@_fd5PRKbtsEDYb_M!3kJe;FWqw3 zZH>loBfL=-;(Z*1 zDs_ajP|?>d_?mA^Z|!#3J&U)tj9b;B|W z+{M~&9s2NEXbscWiFE$ykwiU4NWJ4yv=NZ(Xf-N{5i z9)^rA3~xuhfjn(!(AG~TZ$;x^I&o+iFkWkBggc|D#bjm1uA8#uWTt4>VV;x{iCCtQ z%cT{NrlOgDP%!19byD{P)sPR0pgD8b+kqk@X&eda1Ru6td`-+ z5CxE{lo26$hJ{mmKDfwSJ?_%uv5D6T;CU^D*UdHlg!r$PYN z-heojOBu_#I3kB7iMjoz9=nJ=o$Lm_-q~jT>X@Hdt$E0 z1SoME%Iq^@7-Oh=eBwlJaI z|KL;d5fTRbHkw|`0~11w=^|1``euYiTRd5&xEYIUGWZWX zZXz^vGalJZ{)#dz{<_WOw%4$(jctrX7}Ycb9nLfE$ED-u>7!I~Y`kb5zhH_?4=+8p z-8|M|JmE#<_`Sq+*bzad$>bWp5-8OnoO{=(Og12{UUn2fEruWeZ`U^`hHCyo)*= zhs({CsOg@EL3cdzrlf{GLx#!27Ec^{14Kd(h7%J5*&m0^ohkK)mr$r$*PM3T*9K>H zCch6{jX!D*F@1Wu>HB@$f8P(T`gYP@&>nZQSGasVWq}9Xc2n&I408kv{VIQANmVm5 zz`Y&GX6Saj$uo3y89-Zg*@>j{Y-Ae|a%Eh5u8{W;qCFhAEw3hL*WfzYerSQo$AVcL zxf2<2g|MXfoTVHfR7C7((XpW^{Hxc;!Z{QtH7#n2%vUh=tB~(%h(G}p-TO?GOJq-T zRwpQk9BxLyi;WhGaM>2Z*C!Gb%mgYLgq{!`^)i$GFsNC^vV-2e<3% z=(&u^UlqLoqm0)BGBlnyMDb!_t2vt#VxN5BYx;3xg}%~w9OP!4ZKc&I8gsuWVJBFKl> z|9!fK^*!{lkjsx7&Ye<`h4hrjXyN#BOHQqIjlO}b{>X;``<+lV7NH9LBMQF+0q*Ay2A6#$oMUFB*ieGvjd>}(o8S2;~1?z?MD{QXt544>_nbEbDOGiz4T_>J}~Y@A|D_ z5C)x*fl#OKoTdl)S8*3|LHA=0E~$N;%gP-Nuk@m z{Z5qE<$!Qzv!)yA)J1im9W}C+$z&$$_@I2Kawjq~wfT6Isj-E$O7~&aVOa2yJA~~9 zZ}FB7#GE1kYU5_FPc%T=)c4`}2W=9^`;sOPB_Ps9Kj?Jij2k(0WEz%O2ct`z$?m8L zdt44n6upM~6iu=*c1gH^iH`hp>>P?x5n8=Kn`J*Qk*3mN1j}&X57{tBb=~j!qZ=}T zP3<{QnpeRU5{D|%W_*9;4rPcC`pLBrC~G%py~i-dFvgG}B$vf=4~D@LEA{2?>Q6Dw z5}c#Bt>CCeA$beMHyrR(Lc1ot-QipV{)gf%1uRaD#uy1_U0Yc0Y+}$i9g7|zAWp`O~hOft?k=%w#NXXtac-51VJZ8(_ z92pLMbXCQ&`yTq8L2H=Uns>&X&E~9=H0$`b<%zj{J%q7>H$W8-I z!{y?XVokDcjU^*hq6rfui_;i+eH1E*f73++IZ2#g#i%nSoqotYNjv@mt|JST?1Er_ zMMkq^EW5OWo z-_#$Adc7VmWCaRY&RfbZ@B%~BbADT2NhwUVo(1gqNOwU~vs~j9xdv;UnOxPGikit@ ztV!p3-~r%dTzvhyQH>5QRtW(c0L{9QF7+};4!q}V&I1vF>{zbL-~VH!P*_-45O4YL zT#dD&8X>@p2S}=b?e?&Dcv4ldjYCh~@v#)9Wn+>7$<#iv^YS=N(9D4PU#HARNE-1_ zuBu?iSU$sfq^Tq$N{xrcO)#cX)cK9n7%6@7*oKsFdAyM-p+FFNJ>EX$*-6E$)K`|^ zO35646~Fa@HU=ew+~}`)^h*29y6ARTvuCb@u~)fUzd&_#;UtBWvOh+c8JD32=%VFc zj5#WuayqU@F@SmMzbv?+q-3!~ek5|ZBO#pRlp~dAlg%dh5D5cP8UU3o%CEJ6GMA=R zoRH|0av>Z|>B7OTC~~knlybt^xah9<*OE1;V@T*fid&6W`@)%*7UZaOrxs)D9TXPM zmVama`N77)FU{>uvFu>5-x>4&ymfH6y}u`lJ@-Gs@Gd1BZ2i2=MGUPtp8Cw*w{m48hr3axz&yphT1QqJW+eRQpud~Xx+(Yota~BThfBt89 z{`kjp3?~a4?0dT9FuyA)HQi0%0!>#K&4uZ@fOrxZKpU9b;RjFV^??NnM?eioO)gjx zVK+Hr|Mvr?YY#>MlK2UULoI_o=9II8IDvx=LehFmM!;uoi zbK`Nexw8|dxMR$`$nVOnL&Iy?dcu4QF(un%ja0nBIdw!gX{c6zD|eG4@>eN$a}Y%X zg)RHe_OP4<5)k|BQlv#~g}w<8=U#XNkK(0-0C=Fho3CSZdwN`62L2f}5||y{F-FN@g@PQQP}@Z@=UU`>Rz>;tzylT%ZZ~OU zH_83zo5A%NE$ijq;!EcY#Vyye?kGA;YYsej)?IT#JF4<@2rF=dgO`er%Sr3_dGbaBHzka zZtMLj!30K-c&tw${g$%HCdHaUrUT~7Rj7EtbEJUcrOFYdZVPXheL;&+&|)kvT`w>3 zpE$qKg-O|Fg|s9JVy`WYd~fK2+4XS(bkHGJgmBcegrVp4f?-q`qz=bDMq+J?&d|NU zJ0kv8S5x2_1L1xMe8%FWF&Ox*_fYB(@&vtz9a?$%O#I|O2fo9V+81qTG;;r~vJX!kgnOGDn;$%UrJX=|Q z@8j8Ul0+AOefOh=+m zT{A8Ve=s z;w!h^CjXF9w0=tbhL;t;j6j7F<5EqLVo3y^${hS9Ln2uLk@5=S)z!!n>!0B*4KEL< zM>KB{C(enphJK7R2PPvk+bp?jE_3sdY*85w}pw z%}$f3!}%zkwN_KKJW2snhbsS2;qH#mB0 zHF3L^Gvnu;5`Hd6yB4;F{$M06OtRaS)byd%PzI9>q}qVxL#iL)^K}$>nI!inIG>{W z>V_UyLx;j2g~093L{cVMj7;CQcem1+n|gIcK0jMB#1bnd4pVa%-&S*X*--ItNDsW1 zKF4ml$9@vxULzgfD0%kCoQlb<$*}B0*9eSw7s+FGP)AK%X`GtPt(_OmCf*(KyxtNu zE#z0PzMrV$%vi3^X%xv|Wa>cb!C*_jEUD{*3w0(OLivRA4_&>a5lPVsC5mm67@bYx zKg9spl)&IaOqKKe#im#}see07n|&9nBQz1#3hR~nxAg^OQft`NNFuI`il~GMhDC?m zFBRB>G~ICvBPt`WmC;*IjE5H<84!&EF&H1DKN7B|PVNnF*_<773{@#BR z_TSx=L>j0q9h}XCZ%Y8RO)@0SX%~u5p5l|ry|?>;VVe@$z1Dc>kI<3Jg#{i~Wu%Ww|KgI| zyVy*yO`+GIY8(hQ`QS28&!zlS$w>{05JGP2w@!~2d4**rqKcIx~H_9kg|ylZF~ zJ9bf~$jnxV=VOf0L=^;Ff&^pvl9&7-6xGuTgha!eCX6#-p z|DUW5&deS!}FqY7&)8d!9go{QK z&+()@zAob#7=c=X8#< z`N=TCQD+Z1D>*I$zvHvVe{E-yX>5k|c3LcJt*fs6&fbSe6_K5Ec|(8WwWtejq2Fb`XVRrs+b5y*X$Z`E*K?U0EKTxUdLu1vaJb;5jc! zpx6DN>Gem$8-C+B7FE0#klWltpt#N52f__=#}ehiZ=0JDylpOx4*!oLw`o(;|5?no zIb=<_z<6ieW3Y`IV4_#vdx^*SCGhVxbVUL$1E@KLhtzOr0i`CADqb|pN?0Hx0 zssJ>5lg8XRvpgf~a~4e~?p03YNCx7`-C z9vr$pET`HiXu*2W8cp=;RFu7}&mRF)}OF!xi+wm4w76#5%ezIr;fh-{V?20G#6&A{%dnt1o zEkvOvUI?vf7-z)higZXu1lAD9YY(BI$B)QGK&C<7bN#INvC+5{HKM3BT(dQLt`eP@ zddgZypjAec9nD~9s{^TqjSwbYt78^6LD2m`lh!0IJJMm19fTw)G#IoGhaR>Z_O2=q zU$2_-h$t;awh&R<{zo`fNII!`*Fi#yEyuQcXH1-OS=fr_okP zKP_UrM+mbVr)c~Vj@*bZ<$<0TFeu`DEr=$wZP_e712S#E*+Q|cerBDrcv^o-`;J0d z@5pGxHM|Nu_eL)8B~tW`MYJwqZ_<7yQu~r1Paq54gBi@e?=c$D5Etocd=yI5W3}#Q z9?)1}(HNSty_6(;kJT!1eP@b}-dTM-@OXzwiY~mc@j?$zTROJXd%i;9T*ZP}M<8@~ zozSHac?&UUm?$?ZbiYy|vw21j#0klIUS=*T#*?k zbkl=D?GbLA5szCEI#wv)iw|yhjIdfrMi@D*ZxljkLI%5L#~D5R<*>QCzxifoi*f2< zmk}g;YJ}5#QvY+=z*R}{R!clz^0AXv@)Tj`3?ihGbHWM-i?2mvu$VOT%&-aim^Ll9 z1VrLoOQ}lC(9z*qGIFlbh8L@Au!qm0HknNL>Pf;$(I5@BBw;%Fyu&;l2sJ?zzYD&v zE-F<-t`+Oleh1nC&qF7}k}ENtFc2`GR!2*pXL=`1PtFN(^igoHtvHvu3FoG4zq!w_ z@tBv(LlZ@kC{{W91CT>&slYs@QbVB*vLPH#NEY+1N+YMtmIL%}PTp_j1py@ybTc5Z z&j?;k9-kT5U-g9YZgxX%OTaV9Y&v2n5FJ!9vx<^P$lAe}_-eRs1n~Ww2`Ws8>&%;s*+y3$Klc(u$ z0h8})wV`|wD{}^zpZmcfPX8kf)-QM71U?4VHkoWr>h|| z=3kWVf=iYEVmdcXqWNQqbsoqrjnKPL$F!^CrocQQL%{-_r(bZY!5i;-v<@GcncMKp z%p3+fcX&C3+>F@mU_u8=k=WZxIEK6%4s|FLwi9q_E@BwB-lrq9+~HCdr|OE_lXM0e z_J?l8dS2s~JEOt~_O0j>JW{xL!hh&w!GD#-8ny<$i5h2Km2Z|8OO$CDzJE0+iGL;m z2(o}&Al+I_{KF0&v$x<5wG!M6J4@kgp`B>c%8`G@oFB8ZV|@p)zMfC#OK`Qh~K`!iw7^Ts0 z?AeQO7E~`Q-$TTNnoAxde;qon__BUQa9QTXA<;4DiIoc%q1ycUHeIj|WSsMV; zD%AjH-&>cxpe>#~c>>#@+fLZeVJ;F@8(F#|xg3#Ht~)>Ozd3sS=IBSc3P(+2!O9dE zuCZw)f1a`?OHsdbg!8$|IC=s=+MWE(EhWM#@B6iphso9sxb34c-$ha%RKY?5LpFQ4fo8P$nG%gR7M}=d@pc1uitH{*g zOwjozk|%$X=BLwpJ*KJTes*FX4I6{+cRjYm0%A0jhI}_lxv0zxvMVwP4FakG*;tjN z2|sWY@l$Ee?s3>mx{zqv+wDCS?e@ji#?hODt-~@D9WUo% zd_9W=`Abo|jQif^wHSXO+|ZfK#$oP(Qg(XsI5s zc56goau87r))3l!%EBV=nit~{Myu8!^UBJ=TGA{irI=D2bx2nu%hQ@UGVRg!itKBG z2JR?2y8wa+<?-VS*1DnoZ8YjC^J9wV!>C~uzISz?`*3}Q-p?}4v%3WdrCwN6 zUA)V*ALN+!hxIF%7jOCqBs1>2BM)Z`_AZbGJ+CmvdIjMm<7BagDb9Asb8#8OhWhi4 z^uvrzh8*Pn`|L5T-3xMZ8zPP6I7mt=$Obv-@dxApB2{Y--3vtjySWRvWQ2N@J)-tG z_usKrF9+l6?@|n^lSWfBi#R1o&qC2dLG7b+scutTI(L6I3A;V)f3Rg_dhT_eJK!06 zA12k1>7+7a&v7@N3+&~6I%w|H2koAg{m-f~)blodxu#nut%k$l=v2N}96d7BMI)!c&1OB45?0XP`bL~7@zyLq0` zWGYWf)2nOJ&YX$-<=R4WpG0h6#+I4kC^JDHXBfJ>E&oX+Bv$@ZXPCHHXu1h*ip*!~ zBAmO!hMJpgFQs-OV;d!LpmAF)-+Nmm)t7UZgMqt%Ac8qygv@P|wpnJ_57HEq@e60H zI?s+JiyE8|_MV=-WuK*0_opKnNIwrTv*&-wq-NCmfV0?p()2rUHLr?einUIL6Z?r< z2yWKp<@!>DYpSjzY_B;&wi}vz^S0z6kH6Cj*ZKwYzLK(azuaSaB0>3-y1`RVZw6Y(e;>;tzS*q7 zjQ%;PUV~HF1K#kna|pBY+cS7gWGy8fg<f z#%h#4GzO_M`R9;&CCy;C7J4HEIw*^YGAW?c@u2OFyynom@Ix{;V_R11CV0CT22C{` zCXUFwID#zFof79r21@eUQtO7^cX3bFPn$^ zZw?yzvH7O40m+XW$U7XP8!uK{#vm8y**o+`<6&Pct%=)H?B|@!f82QVXvQ20pY)q= zD5Kn?;rM(AOBRjDpcuvT_1md=^JT24W0A6E&l{4Oda`Qk{Exep`}(Z=`bP)Pbnm>CFj>R830OY@G9JvipLqzp+uLj4&ass8%j z+0&d8w=AO7C76LPB-y);hg2%+Gi1Ph>p-yv-|=bIT9=NZ)?)M z!%6iM{2bcSoE|y%ttcC`u9~zlynm#QC(XT zekKcoMBy{g)H$^u@k`!@KEQ&zGdZiKHl0xxvYF7_@Ww%yYFhIkCQj4Un0H10OPM2f z36jo8+Mh{m?`@~I~g*|{4D7PYI<%}LhCyq*S;!KQFfcc3!q8W0ogpN4e z-g~*TCHD6B4qt5?Y;ESNKu3R_lD2mZG$2}?QbeVKiwEYoFTrf2EahY&{nnIDO|pEy|LgD5 z(-2rEjc>}({#m2?*WXS57jB=%#-nI@@cEQtwz(m1M#g_cDX80KFKU$4*LoO-6r1?_EVJZFBkO7*ZFNYZ96T_i?!beTnSxVTZGYY3ixNaTcd>x9f z!;_P~lT^GoCqiDcy-dM^GT6HM9O511w9%x?Z0>Q`jqkO4jZSPInB+YXUQ|XEbyTp{P&}NT`X8G?%QPgm9dn6C?m0Dg71900+wTDZmfSnbux+Pr*VrT&s?Tot$92sp#D6kY& zPF5j@8jUsd(RW?Nxm&3}6Xl+JbB^(+p4OLtu(a^Wqi$yGElJ7|Ul!ftTB8*BEty7I zK*@CJQ0`NLto|-CD&o`JE0bK`lARy^9`;^ zm}rF0(BWJ2(4%e?%*=4%^zt zXkE+*qDfIc=j4~s89O`u%epYl5E_YbC+_OY@X)g0zs$D}htb=Y!ypWIgZ7xQ9dmZ~Zhahj2+Ww+6Q(;_QKrFCa}AN6T0RVW0bEyr z3jgI(*AK+}u51hpb{`R{LiZ*_vM<3VZ-|_Q_!#Nd4NxjCopzcrqe?rQ7@m({H}C68 zPTQcK9)jslQu0q~W`50O6xk-FgA2Q_le+ab>(={TYX&=$JFb=))bQp^8xvia(;3Ya zaV39D!8gIXi3etUMc`}{%~Z!JRS(|u{p+{D0)11Ln%?8%<)zb8Grs9JW(d^!5++=E zl8z{sB@vLkN)->Nly#CV!bn>sxmH#7W_nl~Z9eC#pH0Y+tW0i#MC`G9G-U>s!(wkf zbGKi9?jFw+@i&4s(b}92hO~;iddj7>FM!gDsjDo8eQ3|SMg=Enf z#Rx>BqR1i`BH5sGF1LT9p>r|VETiK>zpMx`j0A{g>yg-8#a-n{T*MJDT53N~z7brL zE(IgEa-(Drg6<86xZyxy5tbt(4r7mG=W%Q6V(_kYajB{&qkOoM!&lo-NTp7gRR3iJ4fZ??D)EnHeU!{ zRR6)5+$7*N306jN{Y)uXppnr%XQ0U4p{HZ%g>kKEk>2>J#C(gHhDV z9R3e(?2hX|OWXZ!tPBGX4}UJ>KZlnFjL=O=S@keTFHMTz6ndvFcIA|jNRFj!wMT+c zifKa`$mT+6e1&qg!w@)(wXcwkWcOgm3-bj;zr-V6MJH5fVBH($5s(M@0n*VLbFPbd zPDuxs;q|BM8}!k}^If7M0{>&U-nngN0dk$Y62B!nL@czrB#cAijEVNiBr>D+&qC>s zlEDp1j6P)g;EJz1I&~2kiclua@_w}EIJTN76a>1(Nr$-8Jado9QD4#u`W>9zg~A+baL@}b z#F6rp|9%tSH<3D{sz_z17u{A9Syz(XL&J{_eray~ys=Y;=Q3wStbDK2d&8j-qb!g? z^7tjA1)s-%z86B@tp@Ozss`ZxOocpEwN3{|ozlp*A5xV@!(^1nC>ZcLhppv; z;|*rcAe}#xOb6GkEHTK~lE#PX9eSOvca3aKyNf;@i4nm`G!;0+8E18RE6i;rE2Wua9+E^(P_jm~&t0BzljLGk z()LIxmD1m=>QQk-K0}-n&w{q!>3A4Xga=-2b1Ep_-ui?_S%1|jx&$+-<+%)YomO!|ewIWB`(?TsL z%bHiZ1-Zy%U%(s1*Ix-bLR?7=GR zw{BD%A4j#-sS3aD;Uy^ms*i*MFFTiR{{prFT(u*5Kg4u$bpg(`R)pN2?BB?QB1%2#>(Wtz+vxQ^PdK_{2tO z;_2FC?k0x2KcQ7R11upz`5B)}vmk?H;>#%LrNrZ5#e;j{;vp*h)D3JQE2MP=jTOkc$La@mkUgwu0YSr2bGHnG{5m#S{c_ z@5N%d57>NVa!nGSe|k+cHIepC^&+jz0mp%#z%BJXIHW^d`FP<`+gHwbD9UQi^#oU> zM;36EU7&NRU9n=-kS7vj<8BxxgeO)`tr#y3}Kl>_$S?dr)e- z-&K-01dqFc6-d$_JcD6R zg+f6-CE%-OV}I|(_RI1yGzYTGi(1AXyxQuA7_Whr3SV&g9sgoHWS~0*ITkWTnP@~8 zAi6D%eWYzSP>$r;>c}~Hp=adego-|LeAzahmDpfzDfascD4tiCkKeM))jjqbt+z1OK226yF4?bkB0CH0)&o=%y!d2qRlBSMO8W*gcT+TDAyfCH$x$AQ%#dt7fWl-6!Oco7l6qMG-r6gB%Jj5z|w*#~ck>jTD;*Y$$F{%>x**m$#Zv<_$BpKG+T$1SZ*ynA@zbI4)*>Xva}3VC`3o@I+YOl}nd#NW!DeHB z6AESiPNq<~clKXz?KKY>wTN1>yV*QEI@rbtc?HtW9C+l)#)ceI=W6oKO$r?yNHY>Iw>%$h&{I8l*sPw-Oh$Qcd?ICTgJ;NXOTn zRbK9e7h4UdtmE88#{xRXB_yd;I0}ZWRwDVkVHM$|&S6wKt8;9ylhqL=Io1HCKv}<3 zhjl*nnJ{^EEmr^eu9A&-!Yw|wOpi}{c=U*}p1~JU z`yfmW8ToToV9{~zWZ9UxQmI6n6V}x0gMAvC#_QJw(n-PEL)DQxzPKFGpn1b#z&6gX z3S&Ipqem5!;hLy?%cnfFQe}otjun?>7*$ANUo0&}T4|HbXv~yZo)FndVjylAY18eR zCMAuT5T+)ze0MTxPO*wI!Jg8xXo*i+!6^urb^=4YkZgKV>Yq%LZWuZ4zwZZEed{ui z8fmc@e{5!vPB4Z}*LaeW&?we;VkXS6hPBL}COH9G>ojM?M`V&FUc`Wsq+i7bgS?lq zL=iJw$M!I|dmp_3Nt;Td17yEObXKr$4u|VhmM?8@LUbnzh|C1ux#MGw?+`pLp+XJzNEPF;Kq6Z^;;}J zh>-6+&BtL<1dI1Z^~g*ux;s<(vt3bRcR}+uYTfCqYbqJgkqV#PV9|6=3wTLl#+oUq zjUtxP5Uk)`+IYa=;zj;6UhHhH7k4&|2QM-2OUle&-@P;58ah(jx-v>ahX58U@W53VHCQ7Xe?7n@0e94S!9MgW1OBOXfYew3&=RW5dl)Z66zWND@NyUTzakdSdq#}0!$RH`?9N)cBQu#=Tr^cOy z6UZ3Ivw}tY=N^(5GvtOW*AJyc4~exh{mhKGlZ-ns-MPU)TvdSF{U98*$*_L_zr8_I zS@fhB`lG1AN-_~i@v0m19Vks|X}XuhU)11Cv|&n=1#_R)z5C+4&F(5BVaRGSmC>wu zG)XpB2J&wqP(cnl-)K}BuEsPe?hQsaMgi}DY1>973cc(oExPPs!mbY-o-37l-BGJk zgCl}nwzrkNyHOA=?fwT?+5QC{;D`_*BUp=bbi)sZUR{)TccX9}Hk6K0mfLUnsP>iH zsO`6rVrtN^8nNg#Wzz)=2C#*7ogdYyAKb2R7-)&G*=k6VYGjq$ZJ$9A7=fE^>~L-n zVhx$TmVm@rg_p$lB96o_^k3dv18jBds#A(J-2xFQ8L1M@(3KiAca|3DEH)~#o(Y2= zF^~j!g3OuOVCA0Am~J0Dbj@HkO;P5UJwgku@{l%1Whg^`|S^( zL>AYrEEQx}aJ+gL<&e%>-a)Y87U74a^%+R|XQ7Kp?9}LFG^_-Od)Ie3*fZ@htV|fB z^8n_BP2Kr~Lv3`Z%ecZvjs!i7WV=!P8#GS7IW3XdDLsL_+A_Odxm zrErgV9X4bXrw@h)17CJ&ziT*tNZ|b1ko*HS1@^$IqhC8GwH-%NC1bAugA(nK)NLa- zjXBKo0#T_^^WMRWsgJkLwimXB{y+h|-oV9O`k|CU+7j$z44raYaNCCA_FFvQ*)nzv zouy|_F4!_K#FZ&ZrAZEU%EdJU-pWwFa8IR)0c=bH-5CZ0Zz%htT)C(zX*(jzw@*ay zSP5MT{-xlTJON<8D=A!qVRmR5lJHaN_%JvlLZ|!_I>eB5$=y{Ty_f>2zU`jlz4COr6 z4h(C4!;qE#;tLRa`J{e)yz=<;^po+uzVL|_$y2#V$k&8yeOTg$Syikz#BWtm#<=__ zk$S#Bozc+{vWHi?sPNx8oO|!1pE9O`P{9fC8$!qOH@&)bg6w5+K5bb*l72wHqcjn{ zr#mQKGsz9rGVX&Rin$;tJ8fA1IM0N)U>Up+`r)Z)$*>VUK>&l z`_vo36il!gV~$SBG!6V~YZNId<6775XQ8!KmDQY3X zB}rPCybw1SkgB-D_%vvtkiaT~ulD2`H4NU9bf(*dv0hQZmf5zRVfCKW zA3tJ8LQX^1jkDs%M&mYl4N-~Cq`RI9b%cVWC_CvQnR2ktVVFpu7#0a>g7ey>J8z_# zw4L^7WN7OSJ#0P6ak)fJuxk71Md^cXJn6KRYbvi~VcWnCx;{HzbbOeAabH?X*xnk- zNXmGvilECiZ`YXNsnnjK!a?%7uc?p|6*+`GafOv@TqE6y1g zIQ%oP(P)XVDoP#${HtWHuSrHD77nLfmQyr-2}kbbm!(*HVqD0)0fqyV*LX9PU!(BF zKl|a>?QXy+1?*#0{49~mpVpt!hQvMmbceofZ`6>Xd+v=~@mL&qUC-sk3zyBy;62^e zm|VZC6SQh4K#hPD^mznw%3waU^zC7MX&$xFuDHXH#!5Cv(JVPN+4=73;lY+nYpns} zhk4A;DrG{x0ybI6IR9X$h8~{m97b5CFdc_T9;1PZybGVThj=eC%3&bd7COO~n-$`v z)g5^>V%s2XGVHRYI;wxPe->hpa$V{7=xj3u^MIOd!pvp-mK9W@rZI9OR~0hhjM&z| zCASQ|<(7$atW>SSU>M;fVGX6mw!$)r&ykRb?v1{DyXxeLU4|2bkcrpCHI}SaY$^?| z9UYvhC!5@jC|+}SjA~k>Nkn7aeoTT$e3E7&A$w<* zt)9zfu1YexTH+~{kDWrs2u()sNd{C>qT__LrR1EjWY=Q6ol-*Ta%MpFrek5VVbHbI z5v4{k$;!D#BtxvO!L~n(+GJiAR!MHwdbgRLxHUNG^ih7gd73&LkJw=Ob5gwMijR zhKlrjD~?Z>LAU-WZUEulIB2pcB_p;V+S?)1of3VJ+gZxwSk+er`X*p`1Q<;R;f4sd zE>hO%%Wi2^M0d9lin2!GYJC9~Unfmvmlrc8c3qtp{|ev=b_q9&7Eq0Iw{jXr~+ka$Uvy4iymXN?l)Fy5_gN&>O-M=iHI*6jl`^Avt=^_#&t& za-ou0LnEdr^wYv3B*pS7x`CW9U|5FZ&=9|{(5BUDqM034;F~Np3sYy%6-;f9B}27t z#YQV{F5t?C;C*n8UR>dq!PR2|_G@`CVc05e_{(?is`1v(Ra;$(>>l73*?~RQ4elQP z+~5b9=Og)RDNi6on%_68GxJ!!w%g36-X?j@nJq4YI)kP!Ia0P_)p36+R{ufT38;0>Z#3!N3(< zcQC1AEjuM4LdJo-kc6BDWE0#r%w_9+o>%}mZx8XEsKd6QTml;Rhi=Du{t!RW1>=lj z_bmUG2yRD#L1bK9o3-F*KYa}NQwkSP_z%PW@Ly%IhE0NRqQ?1G<(uWj5@lM3?_Ui{ z;-867Ldkf#JRTG@FUcpUxI?oy<4#p~{6F}Mnma{zG6GYng}THD@5 zzrj@}Ek{>H>FsuLUpTUfu~osLfu!1!_+DK1HtK`MA=0i^Qp=}aKwIiuScZLmTI6Aj zw~+{>p3?+g(h5Y&M43717u~+x+Oamo-RETWt;=4}7SEnM5$H0Y?u|xX%LiX(>W<_f zBT-m)e%ya^^!m-wk9urJO=BWt8uS@z#u7^1pjn!!A%to6J}uO@-fdM3O&=Z-bFmiK zWneys6D8VFOg>b}RTX&7$p(_(U=(l;YF?}=)syJrVL3^$9Aeec?uDd@up8ThL#qSt zoER0c3KBJyOr&-}7Xtf^o}$_UeHK>Jl9N%dckPeFvL04M<;!lG+TgLMZS$!AODy@x zM`-4e&E**9_Cp=nwr1e2c7o7eW#agmSpep|?nn=$ULupjN19eckfPxzgQfpVt*J7O zdpQ#{4mH^Y_rj|oD=BL4B;l^%;;#`*?RoUH7)is(vNHlCR`bbDK*|k=I2DZQ&X3E> zj9{Z>M(|#q`<1GYVZL0v(XO#{<;oAeDq^)Ds!hE{jcPkd4=pc&`XnETm$}83I(jA0B`^LBg zlDqE4!Q1UU<(TtgYvbt6!4_jQ8288a#rQh^?a^yJdZf&8X1ha9?`g~y=i;(;9aZ&m z=y`qd3Yado&{M!Se@avSlpbf?oIB9*^RT^Aw=Dgz$^_uU&{IMga<%HX{t!7*mbx+$ zHmdVNI2&4ITEoy9(Td4q3TQX0=5;%x&>trz{V(X{CKrR%>RPalo6 za2@v-9;J0=s^O5)_QCl2yY#l_(mOmlpYCxn#lm7WEl)1)YwO|6x#AWk98gJ_xJX75 z6*a(#!k;kzIBKhOXp(B8v{Wj3yRvfE3X!~2xY4l8t?p#{J9*(-#<4MK=q9S4vd}SN zaOz?D2kim5a`XyP7f@a$laAthpYwkZBb8D7I>%^%TTt)ABoR|7HWl|b>Q^N_glWHC zbW;rcWx80dBf4cNdpmb9@K_f^p1fBsxrM_lAW2pFF$G9B40^G`Vj&gCT$`-!?f!&T zR1Y-08<5|4tTe56m7^F28m{F=`gvX;&K@HjnpSF-?JF0i(Ix#T_67fCIzA96Axzr( zSy?UPDeeyyykKob9u8+~H0$dQ-0Jb#`a79Nt8&pkS`q?!5ihbLGZ)Edr}(?Gwk8(z zOZ<_N0-208vVLAm7(8GqrA(a>7{}S1VnSt=Cyc1EMfvj7$ zY9-Wy}LL4Ek(_VME-IU zJU~PFA=$d&{Ifs#XKwvNwtT{g_}O|8xmMke0hUGFkMq?QKv^s=Cx~o~?#1LP8^3#U zx3ZXfvbC}lo1dkJ56#Ga>3nRq7;ggSs%Xh{cE%*;DU>sVdgk2T38uH!dA;5E6UnMg zoYKhaU3(H|-~Te&o9yDB*1bQIK*zfo5$eb|(>5EOjqAw6cc^-%UoiYW`rBV0A3s(e z#5ow*)HLbbYiiPo_SB5t7+pf{!1mA0i&t*z{VKV?a|gxa`ZfAuqe(6rktmot+~q1B zJH^M6(t8SD3FT}v=0+OY^N`2e+1w$$a5{Ba*9kO^958Vp{a+5I?Fz5mrTn57Z5cq96 zJE_yKM=;dK5b^}QDwebI^qKg{e_nID{slrC8jU~qMpvl&S-O6z`hayUT|ayAqQO5o zcnFq*dTyw4Hz1U#@qmw0qx)_*3{;dVgvAlz`S5*tr6LFiyFODu?`W&%6yHbjr==D# z(i~RLU^NZl`N78C=KgN2SiBWqiziFpJrj#!MVu^%m1iri#U`PHH3 zW$p0gHd4u%jU$k0sZki-3m^m!p}STCEkDm9Ts$2c zcj#0lXn=!3l6}6WZy#*Gvmm&Yk+-j}6Hx$|kqMJY=P!^es!s=*AyTUNYi-ZLY_{## zF%@B#XBuu3wYLY=+L(osU1`72ZR5l~45OOxWl6HCcOh(_5g*Rvit?^dS%SGtsTgyu za+>6VCGW6Yt5#XM7);UsnbC_6g5um#+<3CJ%M*FM&jsgq7v*vaLU+1$9Qr!G#`EF* z0vajQV4vX+3Pu}rfdLEYS?+V=C-dO=X^h+uurwQC7_^WW!B}qhTXj+XX=A%$o4e>t zu;R!BncUM_D*kE=0gm}OB}za#tECkvpCLmDryd55D8s!SzI7eC!M}Q4d-IOH#fRMd z6dA)PAg?QVL7-G+Dp4*kU@ki&PsjPnHgPizoq-6W=fN4-nJGsslskNi4AoCcXscd6 zKK`{_S*m@vQTw-B`~BnX>FG&jQ5vUTV4|}%ChGpVDvuO1x*!KiDSnhy-k#L!3yUxo zizv@vxXk+>#qqNk_ScD&jGV8bf=K@|_gyYt?*GzIWn+j|V_a zldNHk#dG!8+zOUivjeOVEZAWWE$q`Xe36pO1W~w~$awo(CUDaD$~X{td$4 z(CrR`D8xcj>&pe>0Jhr+LW+gPNoN&3p<&k##W(e33f!f3x;yOpgs8}lNo@{LtjfaO z9D3akdgswnW}x1mWdt3=vBpuLKi|}y8MRZI6aM@l*mL;=AAeG_?gn)oc@^Q+kKmV# zb%ofE-j%X+#=lW-(DY$Pu20lM%qf|Dl)fZtzS{65taHwk2G20HC8wwgMJSH@Td&SO zjStm?`%YOjf_aUfzaVNJFXgj>Q&=L7!3A7_&I-GHEe%4O&*-J5}jGo33F6g9AOPs&QRIG~nX!xt0^z%*&?=NWQ15Fs)65fr4NEM}iDU8G!2i5C;pewhrH&2`s zhi={^=0-X9i9_#CNXSh{2~p~Gco}?q$Ab>rWK63bfA zylaBvg2ljJ&M&UJ5e?jazAN7Cu{zb=Y-=iJKxF zSr%>&VdIu*<(6CJcI^%)sN2$fg)CSbWovWOzYa`5(^)LiC)=V&mGr*-_WfrJoo3eb zVFqwk>n`~jy#MAtGnP$aGW*^L4W=wdVd4v#&8?jm%_ee}xOBEeO*7W&)%R17gi}@_ z;+Z|C4NoDk?sbeXWRr2o$>4%>_c$B!IK^fp%kXDF4rN}v4TkUCVF2eT0~$-O0qAvY zx&BljvR~^G2ose4uz?&CZ%vb-)mrzW)e^NX{s-SySDli%I2QV&fOIs!^-Tz>$#6VI zel)*BXr$@LNJ#B0IHdfPLRKO%u@yd}ilTs!{)=#*3#T}`-*1v<^p2jsPd=CU)8w8X zK*OmA{CDA;Ws{AIp*K)(&WQ6dLudqH&5EVrsAW_Cwl=`{V}q761=sLT7a->_bjz0N z<#tPo<@?-Wl;_laDvJbJ{MFsi(1Wn^7)c6zK9QeK^B=!|o8|r`X1OSkI(NPtYtvL4 z($8bIkz2NWe|x(LCUueZkYNBuTdDF8=5`j$LEZuVfStz9!IPv7q62d&Vy666GDi7g{mNr25 z5ItKadoxdG%1nN7lUVDa)8&`rOgL$9k_;Limp;lf&Qhe>XCyPDsIVR3d>x9f!;_P~ zBcyHsMv)T==cMSf&>EURK@3Px_D9qhiHPjfq|VMrYo#*hEN#j@-;D7h_WQ_c=tYd} z6WNnl+-khqfA~qXM+AZ2WK-_8Jrj!Cx!J5F{#NAvt;m&%T>S%5!|&CH(CmJxirqLs z&fH{&V;z=as*mvl^D?RYQ<8(aBTmbo#r=H2e$I0?GR$2EHPzwya}RGXLWKBl8)@Tk zdPqZvyBu{7a@al4ahK!3n{Wo)iSi?{|Fr>0{EIPWMEbTGodpDLrf&XU4o1=-ysLUF zWpC%f`VYsjub2v=V+nFiJL8t${!^;`Dg296qzIxXSLsjp<^!T zGmdwo3a}gcs98DWCW@8%Gg0ojH*m9CsXwhR|4^}ACnsAQL5;8~;20dbt&yd z11~jacOA##&ZcAmWrA6ex8`+RA>BmMp{aey8~1@P&6!6d59#mxX<36}5I^#bQ`Z-R zwJrU+a#H^$Gk)K!3nS%{r$+fRubSB;zkk)~J7-6#OTQJ%!xuWKkKWIF;PPUAfIHrU z-STqX^ZuHfp1Po0$Zr(B6i(Z3q+pc>ap?Uv_MvxYwU;Haw|@j*BrE~?JfMu#EC3IS zPB84%XWDr$|K4g-w%W_T_tt0FTQ5J;w(9tImwFwjc{Sx-Nfv)@2JW7s6WiuRd2IFN zDV)Ja$4_dTqfUweXA=E{H{pjT3*)O5y1v7Yr~3o1|7LIdoqq79?_a-#S^oxmy9}K@ zK3-lrJ;nE9cy)!ZC~+%zTwnR{w7!H1SDp~oc4BU_7!ujbDt4`u%$6hcwaP*84@$xD&{tj-=BU!?B_Z1NAXt0TY_h&;~n7;_>LT6bf7Cw^!$s<5j+Ti4XA3& z_A5`KZ(%7&SNYmd!wrD6n`l2fo^S@Yf1@B4n4FXP6#CVI^8^dw5a4t-(yB5A%Z`E3 zL-8NHJGtjT)G-#i5;IaISO6h~QBzvM5G{tgH*o9SM#!-eoFRx5M)zkOJ=Iga#qCX? z>X*Uz;!;(LYGw@T3}4yS+|_tyB;)W|2_1?{&;4*yyM%6g)DU!ENWLO(vXBW4tIQ%_!i{IzYa#BgAlMik}}S!1a3h z6)tvFnneH@w+h{a*4_`J9whrIy z9F>oYnWda^P4#wX)8WkX zaDCnQd28cHZ0){2+CNxTPuZf(53zQ**tmmm+~t^u$j?X#cPGGe7+Hs&+ZqPYWQjc5 zfPtb0hy2%JcTa@c&ey}7xy*WB5DzPa`Kunc|HuN(WjueW!$4w|pG8|g0^ z&HeNjFW}$i&VJ)3yTqOCy`SLi;VY=leuGWf{AF)rce^1|rDc4zaj@Cg-`v9H;<-y{ zjN12JY`>KGIzF25Yr_^rvuG|GjrC#|NS_Ndv(dl@bcHt_(rkbP!A!tExk?WCBu~aQrCJmXRdxoHv>ndKVtl1gxL$b|E|LN|Zx*LqrgE!%Gw{ z0U2zeRcPp6^x-G+@2Y6`MwjY}uIIK<$2Jc;ZY0X^j4uOp@X@8Fhc^Pa8Ia?Xi^(d` z=E6Md*Kj0=e4wtajUl31ZWkqHNF3k>(67Ba+^%RmW$3g$cGUvfr?$L-G++(-d^b}n zqw^cldm@mRR;{_gcj%ru9`XB@5>sJWz-1%M0HoE{r|2Xr7$&4%=sf`@8=6o)PM`JD z>T$c@#ietw_5a>%A8c{L&8@~x^Kfeu$b0x2E5O*E@+KHmTuva@mc3R0!4kaDH) zv7lIrk1My#N}$v#mKbh><1QM#vaxpY zj7}}Y8NPFQv9jp=yA^5cU2mwAjU%^I3dKH#6AP2U0A=kid!xOeiAA|)T!eaKSrYT@ zTFO0fODIvT6wRV6b%FpDwxBxAQ5rsHe!8Zw2!AcZK@AX{!AAWUTtaey-t z__|&-LTrypp@1%gK7tZJYr;#C0Cl~Q@Z8oVWg58`5*{A%_Pr}?jF>rA_#!E4aVnPzlFT!i_}^ML;r(2 z@-TsH((PJG)i1vJCRf$WtB2!rU@u2FzR))n_pQe+4=`nY&j>+z;SCp_2vShOPAgxu6&)BHDHR)yJU%A@E^CEbvZ~@>sBqu}71U%qWv`Wt1p`Jf#0?#3Q zy-lC9m;3oz;_BR8mUp)eQ%P*;qWF;u5iL3uZ;1!byC>$HR=f#>HX6?ct#iqpp;Of@d=QgHFKRoeOQ;>tzuia%0X;-vxf;Jvp0gBnREIcq zY9Dv12f%f)s=yO?lGURk4m@>PR^dwJD;=+TfB+Y@9R$HJL_g-cg9z*bTfbf$z&AYK z{Qg)`8&cKkTDPtTsy*RNI1FI?VSYRu0Xg-^58!J?BnfDu(LQdNXdohapf>)4qnDTb z;}IQLf-@M*1lNl!a;dgc5~UIr1?5mTYCG^{y_m|R$nv60J5@jCbEwh_dW;ao0 zH)olqBn_q`bClLIs$Ms1<|{H!-qgCynj_n`%uN(CYBP5Ro88uSXh*R4kt6F2(J^1s z5=9SiwTFs1YTuyb z>qB-=jD%gvuTh3CCczY2PFPDgF9&KN%cMf%rSh$gL0VWQZ`dTwQcB*mw<1xav`Qdd z09$N2wXVbS%Noo+sTOIV<$WQ3OQ5WLQy|n5cK6w zBsvg%(~H<%xFqGrvMrxuCNrOFs|Nv zvMNYcyNcu(?z}y@SdnNHmw6~r5Il5yP61aHTIoT_xKxdmT2R-5jQavgqF(PG9HE8? z_^aU%4j35*gEv%PI>Lw2qrgWi2ZbbO-LvUBTsTfxrs4f<=wBZnKdyZzZLQ=<=wFM+ z^{4D#fY$ri!>%{-VBg}g*B14zD~OD-s?hx#Iz z0rH`7hwZS2a~%wOgjO)>q7Ls#gtHaOe10E?)g7?_1}$=Mxi+A=k(g$KZnPvW@7&|Xgoe7Rs+uftUAKk>kSL;oV(QWL!GT-pyLa?YbUEd% z<)jqj=*xt(Xlw#E(^RjGz{LrzWJF}sawE~v?IH-E-`;3Ez^iovEE7i8*nyfokh+;r z@p@0|&#H9dh}YW<9b}RO>_F%P($!%h$^(a-<<33H4x&6MFknKeO2tH=BIENB%-i`G z#RFf+3Z`!;bW&59#TN3c(u}rn4)a>Z>Pn_D_;AZsf^f}GpguT1v#&4TxoeU@ePWhM zP$F?jIm1kv^RA;mW-@!3U({p?j6F6`> z*7eySH>A}ekFqqi4$Nty0rp{NRXel3B(xMJXw^vsAsrerwK4X=%dN?5^n~|vm1ZW$ zn$N_rYoi%TL-%%;+xhZtk2t$G_U*z{tw~XFqU%aonACk~1w1L{Uwre8vW!4g;S@C? zyxi)O6Q{l>DP1s|v?!Gdb6Bo;0Yqg8H0+yiqUJ+OQBgnkQO~0T`LggK^o$}(T*v z420Zw{2Oj}@A^R`l0!bZ?T6SQtb~plWUuqu(&jSNwNs&ybJMopOD!(TEYM6e*#g!~ znTpSB*Ekt&IU{YlG4Q0MG1K4Fq`>ox!{*M!n%(gz=phnkS}MN}cLsmd97ZxCJ3%jK zkGo#Ru*#Pchd$jzdE6()!%UcNH(QK9HnT`$)o2A3jVCDyjbe=_X2J~1b2G_Ammq7= z^=Z3EXk4;HVo1~`Fcu@f=SAzefXaaJ5RMI!(ZCJW>yz0FlAjJx9on2BB>@JAwPWiY z%(rjIcI z6}fSbof`_Qig{tuPWA{u3s5xH6RZh|C?+hL_s|=2{E(m{A#II^L#CoE>LSssJHSxt z_@&!56e`m1Qaf9snGc@Fitj}4hw(rf0|ED|v=%)bVgwcMLBA#&YJlWM>}QNJCOA)Y z2SgjBKwBokK(eiYDvw8z4cW4+5Z0ZOq=r}-rzSMMPGL&TXafCU7=A& zchp0JFlB8Jk{>26zma=!;kE4(jD+hEgaRh@kxv^Lq6ckX$>pO|MqIf=TzKK)d4S_j zPz!WZ;oi_xV2cYe3(e>I`0;vY<7lg3*;tw{sG1EtmEgjuK@wG$={8FDMR#}s3$8z+ z8%ShXiOSoCV>}{@Nw$=g zqxpQ}a0~5u%E#ImlnjfxP2YS|Ftiqy#i(&pk|xWIS6hvrw)cRcf*jf!PDUk5+!3Yu za@^cGZlru#DWun-)VVnPm_Dllx$ENP62}4z3mQgSbVFN31EuVXjqP{UlvofLB!j`K zqld~eD8%{4ML_yh^4ZeYSdRz}wqwNCT)6idw|i6E$NR?huOAy$=(00bgAKR{^_-e! zTA5o@$Z@R`+EZa3ny^OApMRXFXxJwNv~J|WD}j_ea?X+toK5cFDqSS~jr4>riZh=` zapqh^BBNLfKpTDc*%Jc`1M<+#T^@<@Iv}mwBYF3%BQ=+7pH?Hq*QizK3=1^2VosHW zYFE=Y&!;R_1!%BfH5V!1jVHw#lllofy4_DD@5$;Ar8=!l^a=p#6My}G!Ykl^*6csU zUZ2jW=)iKK)5eK812_pI`4rY>f_;2+|BPMF8~JbNcfOo|**EjXl#4mcwS6sb*thb= zluLQz?>qV5ck;jQFMfnComMl1r`Mxe=Qi&ZJzm~laYJwX?_baxf8Wpl zzMtp5pEuxo{@qjidfv#mpHr!5Ed2rZzgC=F^v8vJ-roPdv;Tc(|NGAV_nlqc%2SEV z|IJ(c|5e}q^h~1YJ%&mo9>wr&6MTKl@Bl-sS(ly>I_d<4E@X+58oAFna(8(8JD4 ziL$|xY-?AqBc#N+l7bM7B#wb$8L+HPbpQ5S->T}zJV4mV=5y}mcsBxOx~r>SRn^t? z0Qr~fc)}|Xgy8pKQi~e+U*7WxyytKWyr+8;`P}F5k7GX3|MH&ytK9&a^Z{_o#Nq0#elmYMu^PqCv;TropBt-ne zAG;?z`$6$Psng?PEm&J!tp%*x+Syi~@!Ob^T>j6&r}*dPNpfHCF2eN~`dZ^fa77a39o!!S7n zpNqr9ROHRXp^|BTxDK~4TqJ(gs)ZE9qeghi~&sAe!ZMf5M5bZD5mf96+K;vs8y*nw^?H$;(U_K=b;Qd+qFx@QknK@MV*c zHI0dHm^8?KV6>UIJMJDDatlZEK-`~5eBJoEB?)6E31k%P9Pen{&`GB7t5l0Lk>R8> z2qZ1DR}FJ}5RK|Is39Ck>tT1Kn4XdeRHoHZ6dv6aI=19m+FxE0we6^xR&JbwX>=5XSTF@H|AgtyLffnH* z(C?dCIyD5g6&(WltsbT%Y3ja6^xss-^uM*Uo$5P8z1y(+P7NNfXM@3g-2~9Z4j@@f zo2coTVM1?_kFvm7Hm+&)xnRF|VuDZ7LEvZ-UXu(Dc9br`zQK#O$O>w1h-kCMa-luB=r}&eni!p6VmdE7eYy zg!~vK`$i}IF!?d^2vl<;e!Cdl6hV%$)JiJ ze44x;;Y2IZM=ZqvZP3acIuQ;+HkR@tDO+$Z^610_k|RK-vLFjf3k`Y-BZIJm!{fq# zPYd4CNelgEgB$wN;DFRLh%*zn8j0BW5EAyvyjO_mvSYWcuei}@(m7KD$Lzi$QJDUG6Yw_> zWWwyIdE6w~xxl@QVV#P};kN}29LR+|7=^&I0hSuPr{wD=Zuqh0+%r zjF6kd0XUR6npCD-4odn+BGb0*K{&ScBz(kwqub}G4xn8lyc7W z9t4kpDyUzjuj%P3aehy$Duj@nq%0_~ymRRe!X?lOOAz zncj~Pcbqmp(>z!dYC=~KPL^Ewq-bjo}s;pskSaL_U5$^H+7C~`G8yMv36 zh1jSzU#lM_l}(hA9oB$J9I(565ENI})}O!l{EIKY`dS`s6@%}R^?lZ2SO6PW5#8uc zZR`^ama+w2B;KSJy=1c-#phcO-Csp8EEDLwytBGoysfNt|cN~g54Ql@e6 z3VO&O$Q7L*P0y%R!ldpluepF#HUSx^d*53pqSVS*mG-RakGFBi-`&$-+9!~;wr zuaYNH**NJ+~-aXi*QVvAfRVZVTedI(HKie!)H7NM-u8nlZqn^yebrfx0btd27 z*u~a7SDk-;6KJ?(66ZhLJTINCUlhH8piLs|1z(=I-9+7pvdUSfF$GS!GMk5B+Io-Yl&ncwByy|C;!P_9-7NO>84~Y%%Zg zeHDKmnGr5r6lG084f%FnZfzsr)+A`QwsviL(7hDAYp#r$E_&*o`;? z(P6jW`REA;4GEzg%js(r-l@(CL+wCtdkVsScOXnP0U`heggG?kQg}oDMIU#1uywL~ zct8vNXs@}o6IAd7cLKFQAMNiPoYY?)ZvV7YK9qT^V|Nr>mb%wBxECwefz@YYpC4TLvLrVp(T{^+^%3(b>fMVlo$f=L&EyUO z_zd{yF9joJt^8bC0|G0xm7kGohJLMd&kOwUyzt98pdwW#>-l0L7wepL0DsQU@%QKR z%Gs0P;=C%4&JhCZfy0|iHjldOKLW_tQMf7MLmz@@>!@*pbN$W&rBW}FGwau`7+w|q zT0;7Wrc@;IsrL%P|9C-FdY`JpMO16G$_%FTSHK3_F;m&rsc2t!`h(34Uz;gvZy9A- zm#+>M$sAZY$2zfW`v7GLJWNFQkQl014$|*MQOm<=Fp7CGAdAyKW_@BCNIDhj$Q$`3 z1xsSBa`0~}ABanbQGGOW*HXH3SW8BX{ZMo~l5cl>=dih}v2w;S)9Dlop%Lq+r)L3; zcE0xDJV>6cn279J-XvBc*{MJ1u)&Y^dTt|s>|p$U{j#y}{mwiM#=Up(ZSZ+xy|MPfX{vg3-u~t7UrIkcczlwU z7lN7xlAP@)_DNSDQQTL(Eu4vmq2ynSBREX9fs`LL$C}@A-Tz?0ee)9T?^pvbYvY^MEWnNP}oSdTa|d29fRwly-Bp_7{5d&?x;P zrYR5JE~;AGQ&k>5o}7GuuP$)3F!@OWk7o~r%5vaHn3RZ;792mZ;OK-^I(v3O2dpJW zFdQYI(Qk*^(>2Tk%pE)ikU9!{G#k+4hc>EbAR1aa196tUC8>^pY5!vuP30~lu`u~@ zn+CdV8X&-|S{S|{#yReYQ4^%lI6!_s5kb$-O`iF1%)9_>S_@Huj%D$NzhML6P>1-ONMO7!& zLiry7WuY_fa+j}!whj+|*g0-t^BXF#P}Wv?d)q{8&@8JwHEhV%jYdZK?r_jcJ(mmB zq@JBdZT9Tt`W2!JHP1r1h8>wG1|4hF+KNKbhIw*`;~pDNh7-;P3wKb2VHaD+lvN;z z&xpR$YDS3myNrZBZ&-4yS+<%7hX=b`&As-U!`4ZzMT;x)@^p7^+m=b{y>6pBHoxG( zn+7Fu54NMq*L~~*jeZV0bFLr#(S)6JBF}@SFsMJPbGM%bRga%bdyb=frQoIBozD1D zY8c;}knToTo^ErKCrmpcPf-iy?w+sP%1#oXrn>hxcm0vU=%Zju)%nmFf9vjB(;y(h zvr+%U$2Ufm=CowReMw!L>C+!L%F^)fKG35m8vjA#o8N=f5Oc$`kr;LLkcng(GX+5m z2ihV;Lv@$)sK?d9r*+p0te>e?$_JOH7tSe`Fd50b6pdb9wVE9nRndL~)nGO4Cs7}m z7o~aOTa>A3@$8Lu3JY^Vh7mijojZ z`7O;~MTuhRRg`?`ucFkOC05a#iTAa~^MwfF$#25JIGlKL0GPCxFWKB`xyCy*Kh7wK zesG%hAlQn=_mlq3?KG%tRfF}_)fYAV{ha@P$$!7%zh4KhCSe$~qU-4$q=Z+n6kz^e zV?ev3ZiB!4IDxs-2mnezwZDSKYpl&58o>{p*$pAba9()%;rn9McMZU?0{%tEWJLI8CMYHkQs>;)ASgh(F`H6hEcD7-4`>T z7=%^~Sv#>9Ns*-8?=psCItYc6_b?@bUqd+Q)A{L3TF+`Y))>Ahm|pdHieZ3eZjPy= zaymnB3=OOy<|&e)I;Osgz(mqtBVkN^c*HZ`*0q2|rKzXZE~=h_cfWb?dT*yK#Vh;G zt>eSG+$P8){_$}a2?dLi2yuxfAh_{U`Pshk74enQTDx6ZCtS~dmNUp|{cAdg(1UgY z>()V{f;0;^XxjMj<%> zrk@&K2igK-$J!tHt@8$Om>GXTXG?7dCfOSu9VEHwbZ@1^A-6ySK`&SZTA?o@9rphj zR%Ef(Bx&j6L+c=-&J*RG9#W;%Hk+hfcn&ts3&_3|vS>m`CaUqo6>=7_+lO0AkeWD{GZ3rZ9u`FB5KhwlyN1>5R6 zVjLGfaTW1uNHL1c!oie!=@?;q1$z+s2Fo&5uR?>`f)a#7Oo{;#q256XDi$sX4ad&bKyMFboQ!C7w*jYV0l;lJd8gpIqXJ`RA6 zZO_;1y}`hP!OsZNzysrYO3P&w1#IyjBIWB{2S(}QMha(&Xa+}_3sC{4`*BXX#B!Six*rkL`D-&{);8J5! z)y+d7Ii`I8UVCTaj>-Xsu`?;_6qx$L$E;u$)RveG4fU36o5d0;v~$pWxrYnn<}sW8 zJq}I64q0dt*DVF@qkcAO z$wuVv<35@dU;&MCm(o<(FeJX#QcTLRy3W}P^@ciMeWD?6&M;V8UQ3z%1;UDa=EQ*s z2&2JSnOa(c#+BifR*oBs=geaOg)9SLXYi!T#@9;z>MLgC$N28~y7}G>Z@OKMe42Ed zYqoGgwa#+s*K@t<(go*wAUgGp=Umoo@wHI1wb_YNHk3^gDpWcYH!|WB!z=J0>vzX` zxPP5nUkd!EJ5Fk#3UQZA!pA0et15pt{hm*JWF(KoLkJ9`>*?ahZ7pjI1NZlo=67N5V*A(55L$ zI8A8K+DqE-WoWT3M^RbM29fv|)}T;QS%_6oTr_jUONu!OW|V#3He+J-PNlUPNhtCk z!LQoOx90I{ty?)Wf|UsH%~t!(&fXEpLdUEcPYu`i-)?-rdGzyO1fj7D-~v^gm>9I=aP-oHi= z>D^bYqBtcMOTW;^L&`U?X8rAG;f&w0L!s+B`&m7a+8*4Z7c2U0iE2r9lG3>7;y-5w z%8T%9 z-0EZEn%LfLE!{@CTh+AGxu3>3$n>I8`W~H5R7&FzWD(P9pUh5jFjN1qM4>Du%y3d1 zhfjsS3&)W&Xolk`!{N7ChNF-FScW4f;rR@Q-+wB@(X;ns42O%&WjN_v19KTp0@dG> z;n)E3c?`!y(N8m+Ir#|wo%xLmwEw&D8v!|gn&6q$csN)`L}=VIxh``vh10`OyN zp<3|tRenv6qop4+Ukh>=+u}8vG;_+q=@Cws+)s1wFg}v&Ee$oF1D<9%V4A>uDj!T> z_ODaX1>@PYkfqokr=?4bgJDd|4@CZK{1JW5XGnjLhB9}-EN1pOWevwScZHR+{lo2@ zxja6HtHWjhw~q5zxw)Jy3lo2`J0#)4l|Zi`!{iYHEUM3Eoio4_E+HGc(HB%Io~Mc=dq z(hSj-P@wBL@t-AG3p-SwZHQmt=ZoEkDBP%zjH-0W8Vd@=XZxIM_!1B@>lq60(?P zTP**G>mx^k{@~jqI~Gc7lhkQ@ukF3fo0aW7Z(c@|usfT?{r4E0Cr^U2xzG}2=gqhW zduhNQ1vQ>sjVBRS^kZPXlEx$}S@7hiqa}yQ$c)66=e25p`9+uL4jbf;`Y?-n46+d*UvMa!V9>b_C-dWG)}Pgl`Mt5eU&i)+zSX_4xc}4G+kfFr zz`w@YzIFP#d7NWwzuIut_QZnzJ3@{*#FQpkWDN?S=VM za#i_8GsdzJ2;|v?%UxioWuJ3>SYuJg*0Bx_*ljaF!dAB2quwGi>^%ZrQa^ySWW%*y zAnT1U)@woBhmj!r8h0jxKIaorcT+Yb9=_l~4{kd>;%GNcuy)kMJfB)i2j>u@ zPdPddY@56{rrNsHkpJ+reNio~Dl>+e=xzU+ExqJR>HBJxZGsboCJ!Bfyq-d6PP1!3 zZn<`KF?2hVim?^O&z}34n)&_Cs6U=@{tKcRkjr9?Olw!F`rmfLIL21?HVEfSkEi$x z|6K7@W(ge;Pz_;OZ+!l>x(~A_e@uJwn(aWRgZuQv5$4+Ii{vm$H_~&uD*NHOLjtDVt$1#>=CmrdfT4@o6^aS~-vP*>ESy{Mwc)nq_lP8%oet3#tqnj}eQ1xM+;(EkBt01_ z$RYRN)P9^%=|BdZUQgmICssy=h)HQjtmrS)vmjc*cfoTuqg_i-)%?$_-2Sx*OEVkw|B)c3KQxc!i$_iA$LYcDf8i4zNc{b` zdUhq(^zLuY)lah%MVf2;L(>4lal zea_7EN$^;4Z)SV_E*0(dD%-nTM2P;r^Ao0Y@APq-_S66vKpA>jDXmvc+u3$$$z8n* z@BQXmr!PMOjpeY?X7Z5RC_NYavg2|cPH9(qsGSLJ%ifLV;KCs7@(?QY9FyiR7Y{3M zeVC_JO>2t4h9nmLl38NM;9ydaVJdtO)8!!om4j8~lkanr#%M1C6Z za^usFpXh8bRnbhCTQj6Bf&m>OnnC3n`Bzy|wUF9qWx3jrHWI%PV-i z^TVzwxz+#*N!y&g6BDh%f~4I5xxcN5ZkD-)azUQrSRs!ET-hnvYeAx-oIHQzL#<}I zLGM)khE<1i3W8w$)eqcZpqPdTOw}&g|7%p#x@e8+D5i;uukYF8P)o?srZ3l9a};?? zh0$0LK;z?l(UNGf#ZFy^5kKp+}-npR>=& zdf>s?9jRYxJoOjKEgxv3_kEl!RO9l#-?4iCpn?GFdLaT{A*vM4?*_fiL{d6+>BM^@ zIvBfKq|0(_hnCT7LV8AUNYsob80zNHE(hqrg$M(%gb^*%ch0VAf*=#Ps}KWfrW}b; zqM0N^tJ%?DU-PRzE=u~{cerDnmZ2>QHKE*~XM2dd-;o4$XA|rdg|Xs|l0@|$hM?a( z?JQ#7X%;Giirt0<#D2paJ>K)$;NX)8S;ukfkXLp#2?WXE3%`v%FeDh`nch|cN&o#Q zsPv~Uk6#!vDB@_Ndrq`x2VM=!OiB2vi~G78TBBQ=;xyc-@>sSa96fb0$YRB2s@W-L z|NTCv4Wj4VpBSM(kS}U**SU{*D7%9h)>7vhivuyR@2BjQkK+Uf)$7|87rWJ~Lj}#9 z&I{|O?`VtDv|dS~4;+S37s@~co1e;kx%RV%tX$Z?ZE2ou9&36WtBN&M20_m30+CHN=`9>5);05;PnQdGoM#0XsNk zf)~ujdFWfM(p3mSDp+d}t?o>95sXP@n+gom#JeyYi`9S(wZ!t0;>fhd3R+3FH;o$q zqL@H0TZel)Cp+_l0wq(`nTt%#p==_bJUPp~?o5_aJ#0GhohJ2sl7iu9B288VTeby| zrRpUK7OlNz>rE1o1?BP~N|FV+-+Ph+$$LKsz)vlcN50+MeVdC;x=aov#(3{!zqxym zgNXk&9}P+ToZkn1mz@i~uv8l&YZ8>6*PJqQARQg=h>LT2UK4NU52MN6J$DRkfqWQy zhg*No9|3-ogSD<%z&_mDZa0s&-t7LcGk0yL%gvdJ^9Lk67zu$ZfOSBB-bMNMmOv0EBWOHbyzMNn6cxUgh zx!p#t@eKZXWwMaAcaErYE%nad-#K}6xSau&Tj;U1t(&@iaM(URIoVx2ahcMKXYKXg z;manz?!RPH9I?%qJAFCDm&N|b`Eoph`BCm9rLtvrb%RbcTh+y040 ze|JlJzK}qsip~{gBtzR=c}8F7kf~fV=QNqhD>O%@XdckkefxHfTw&fWsQl{7FBeqC z+Xa(j^rG9JyB}WAml>IE%6vmZ{IG5l`FS+avsNQE~IpuG#^Xh z=9ODK_mAQ>A5ywan(n1=^U5t=T94v3A5yx_o*=|NRzs`E}=bncC zi*&f_wY8)Ioxo)E^}j(2PgPkD{|Q=n6C%{<;eVqRehuH7`|alG$>DzU?^dr=(a$BK zQ&`RQ+s049biye%r_S_?`V)@g84TJ)`(>2zP^V)k!ciy@QrSQi14jiUTzaR=db<%{ z%J}U-Nmx+@YyUl4RdQpR7$s?$NTU=d6uphA?^&MhJnpYL!*;tf?zf7Bn&hDIaV2!bPMA2Aj~ z3D_D2vWU5t4L{^5uH@F%8qX6ZuGkc>nj{%j&bA{S4R*CCt=2@%yU$87i?!IOG%l*f zUxl5@v|FIGAG<2BER3!*cSRTlsMFJ%zI$5JPHJ+eQA9&?$~O=0?j_~~H`jf>*J5k5 z=%(MrC4ypL)cPI8YCv;8EcO$v{7xjew?sCY3S#>Tpxn<$m zQ7edr6v!IeCgj{e=hf5Yn7a_IHB4^Bs-30g>03H69JhDg9yJfP=Q?QQIBX=%7V=oh zTAG!l8OOgl+d#-)b^D3+La=E>J2UW=v+F2=U5_2jIGkdqmO9;bXZ8VEKiU1W6i>SJ zeT9m%r|3vi)|&?S0!#wEgN8n}HOe(txzQ-|U6=O&&t%*WuW5v46J*t~tYkQgr@?JB z;#kH69U+oA_%QC^aO)(OGJ}Dn2w~A$0@8F6Y^GG;O!T~3D!G!igSOK!N!YDw-8EZCzW+$_e zxq_5AoKCSeMiE%4T3F)}6D;K&ttlT7;!IOqqRnE-_&Y*gG}A?*vm1DX0hmT(ED0J^ zS}Jx7Y8$$c zG>W_Z*Kxz+wP(!&CSfP0HDHXfJ5$ajOHJ!qkR|B4#SA6^i2V&XpRE`WOCE4twXz#K zaJsDPTnn00U0t)7)BUgo_+-fW2+WjG?ERRB`QASIU>+hw&sRdN7K?2K`pVR-T<#8qdzF#Y?qp z5bKLjTTqFow7=VGvExY04eHO$;(6&8zVdt^B!}r977goho6;lt2BhYg^jE8;D>XB% zY(PXCd^#JEbVl-2qL(b(k0QRh$LJw}paB7C{8$oe-l4>QRxGbTKh?YOf$o^QvMl%?$ubP4Df+-ox07 zr?^%JnAk;$C94bomJjeYw%bqbWlT{}Iq|Q78%4+O5A^~9fDk`16*4@V2!@|~u~A4L zV|-Z$k{eDM$Ld|tF$@^qW5h&f<6vu>cNuXj5+vXw#!84C5*;gN>y6J>MgDp^xAV#V z6ugCh*-aLQxBXr3vS8&hk`lIeVP(+VTUsbvN-kUb`fEALuh!Bs+KbGg|0_r_e-4T5 z>+HZ+)M*YRqi&xsZ6;knv~w|k!|Ls6*7MZi!fQ`sc<2hF1-8!Xi|8&arp$2`#_9Cw zpJCx|*dM*`4EjCS(nf$otJsQjMX_bCEnCq9?P{IAY@KlG0B$+^SZ(CZQG{Q#4-QYv z$=R!y|K0zY|2*#w^@E#Ho1h&YdrEwAUmy-$F~mm z_lYXBF#MImnTgH8Q89i|2oD?UBR~n@3yy>&#YPU%27F#>S$9+UlR|xVEm= zwaRci!F6~CiJcAwx5TDmHZ|p>m7VtyZI(eOj_8+GExlEyGC)jC2f@`zKAP0<%_vwP zrRmYCCQ{u*MJT8HlisoAXMgn0T9tN!XT*D-*<|L*$E76A`3;Uj^c_Z+SP3>p5iKJE z6!O`tIfz-^rCzuFsasw_@^0#TLL3TFqE7(K|RJaGY;Vm;J_qwamsy$!qX z`fUCi7%$%9)>g4bf8V~v$Lp=X75TVZJbd}Lt)kj-BEpf(?=KVB<(mp5CyC&#rd>xnt>6)~(o2$_~$t z+SXR}8z~2rG7!+9@*j6L$%B^T(joeC-3LUD{B9yZi8LcCh9S)Mt z=h))zbF4ISnM4bsE~6rB*_268z281OJvll(aX@~uq&K%XYt?oSw)Rf9cXA8Bu;b*o z2^%<0Ly_Q?kdXs~D;jsZo5d0>4>^FCwbh1e?xydD&$ZsZt@*WRIW18Oi>&5q@tVul zD&KUw&jb2XPS-y@+VWeapUbpLi`=h;HJ?)}uT}q_tq&HJ*Qz+QNcvsA#jBiodoiBg z57_af#Bj20;JTpuo|t&s*6N3`YXkwJu#^&Px4AEKd%ENC-D{il8WSy1K z9;oTNXkx-x5=~4(lb({3x~YdX{9|WhhC(2)bFZSCS!|Nh@)_1_c+ZHQQ@b-SpKuf> zN%O^EIQ9;9Je#gVfuc?h42-tdo1^;aTgHuMMf%1>HH%6I=}?CEEXsqaRxg=N|5D&MJvr!+HWSzk#=TR!Kn=oW9 zb8_0+GA^?$i4~Pv(H9zzrxn5L*?CY$0=;wpTQ$|dbTSKVQhGnS9$&iutJ>fw>kzmI zB&Qlgl)>~ka=1ZM;Utr@G4_BcC0S%1Lig;?q*YqURjYv&D-6~&(Hg#+bjBpPOl}f8 zgX8ponsuYWY&Z(OS`8}P%z2eX5d{LJp}TnQ-bU~a=~&vreGw74AMMvECT3h2WeI8kB6Bk&n-haW6nrx=YNcOD?%a8MQjZ>Np<#bC zi@9jA9x=thkD?ci)e#haND@iaPW_1(0Z0L+6frBbbVP8;4$Lg!l0h}A!my*OG#HFp?wO(1rE1NX1i+ut zK;gB16^bUCw>uq*i0k*K^c-dJw|B zo?OvZduIa0_LJQm#_zjd2EWSZFusrFdmnnC?s%+k@FF!Wb{0hbx|u}puoGkrLVD=% zQe$lr7C|Jvx$VRj4n_?p!TWG3y7@RXvn|8QG1>_p}HNpkEmGs1?T)a&TkJvJ;pIlsC}lA zFGE6%r!h2czrvfzelu)x|B#KyG;L9hH+^cJ#HvBU(=3SPl{k{MTPSje=B2fJ+vy zCbx=NG{MFC3PbyB z4+{23%P#JE1Tu#Z7W}Q5pN*&J+xXGCO)D<5CVr~3%vWgc?KHP{4_>#o4^PrpV5VMh zrn0S?MYMa-^nvhcPgMw-}=L_ zO9-p14zxD*o8|Me9{fkN3&xve`YiLEUTUFpJxtH?XeBmXvfa5CnR&$=5R2#B`l%9f zq4!#izln_WLeDb68+?O%D!TSkmH^9N5B>=(0ZiN-b8|KSqT*2tfYgP_f-4W&w!g3f zE)mGn1Sqc8c`RM5YgTW(ht0N$;L0Z$CV>owCH6ks1DeqgAhbO8LIh|b`GF2eo$HW= z3O)_VhLAwH1YGo^Z0ltYt|6nu*KOv|s2At(6>7=J@!5I#S(QQXI{iS5!7o%4@tleT z`aTJR->T@&;n`H(MQ8rN(`vE#+5RgzbEv-DsazxoiX8d5-*Upb-n>-x!|=LD2P#%J zhCw-A*=Rf~t*n$U75tXy6f}F2G2!csoK38A$T0VzD$p6F(2sh!LhMkNMt>xyHLcEyfpHj(2N0yVKTu75!y$G?G_@VpEeyPTCyazfTsBy|CMl_G;vY)_ z2$s#vO(37>HWICkqj*}^s5%OTb!i@^yl4=Oro)kj7$Wb`F-OfsX+{R53hOM&Md$7| z8ibZk0G*pC>ZwKV2CL0j$dgDzXvC@`i8!F8_0<<&nIkS%Tdxf3V7Z*(8&U*hods`cq?V@3v-sTIJDiIRTLUgOm%VrC< zX1JKHaP_JzFi-wWs0mb(d>ZTsFI82)alJs7Mb4-Np88A_O;$^$AglqlU7yxKZZ_ic z_$7K*mZ1=^R{aj`yF4O!ZDoae|E=_2@OQU!QiyNn5&A{fT zxsdf(r5917xWE2xG^qur&<3+1Ibw1LV#pxNrW=7_saaoNHs)!f#nU-0y}}h3mZZm_ zRei^szvkkX+W=WY6|BDXX;7-j;8s=P(w|5yCCOu?W&%uM?!d^50$&^e5Iai=2^&t( zsCLG*saYu=$x)7tc5NdtJ-`K8cRC}=P1Ji9YQu&InCR&ZtE!g0I||I0n2bX-G$ntg zOLl5txoet)4X=AsEbHA)@U7Wt=Y=O8i9WZuu9WSv%)ig9OL@iW6!|zFOA(yHY|2v&)Y<&*q!n?k1_9%U0l z*V(Q{0}1M>%99mYuI=j$gwaBXP$N_rS%7n-U6`ZCD!BA;H@o|Gf5&)qm}?DYN0*JG zvhpOo$A>D2>a%hlVfZFJ39@EmCMNG$??tqA;lszo9`OhR&psl>^ucwr)bZbvpPnz*8P(H7^Y>{O*@5NnS3MIpJvgf!C*;rpj)M1tk4d&@e>pRbHmGLCB zhWjj(e#X*_dYf2BQ*)hh2YNT|#47F6Zs&xL8YGl`6CjMK zlGUEl!ZO8bgIj@>7Cokdb0dSCDiQfPfpEM3Q%$7HkXl%z^keBYH$YrAANrU$@ zoP0(3!TIJ4_8j?>Z&CX(0j5DGIKDvS-QV$huFklO!dUu&X@|)cyD*R00CUvl!j-e1 z3EWw;{(mk$Lv*lKDc8zyp$~Ajg4#~n@H=X=E*dCy{!o`Gb^Kt0U%ce6o=Mv&jc$Fn zR56}IyuFz@x@T0ep%~4OXrtG*x{94vksa zZ@qj`oujtm;^M1-+YfY-RaGIMv(%?f1Fkwho`SnxCDF^cc**+)^3u;@?`V~@t55q> z=79f(uQiLj?m@3%jXf%=R~4U!SsJ5*7yZAWXq$6CEgC*WcJFW4bthq$xb@R(4r%wJ zn=0I6Xjvj{dXt~JhOpngURh9^Wn29YdQE#ybR{4RT7=zv*#gSV#pMLczu!ZQXdG=s z9inWY!TV#o&o(Zzetb(rY-&9ryxV#+C&jd%Ks9eg#af+TKO&n9vRSk>ic!yH9* z@o_NTB;G1h!pc4!Yniv6<2z>(LaNOK@K(iElg{u>Rp|A=&|+Tht}DK;KHi&H5iX+y z_OANhOHj68t(yDRA-P6eWNocx{P;Bsn884~MKTkZ<;fUnlNP{F4Ypvw${rFYlIo?r zXb8($q|2rS)mezBxqi(vh#b<x7FKd&^m_71n2 zdp3l4U2b)}U?&gNY$c7cP_yDieiafO4 zpg+2fs_DV;nNr)kq?lhu!hOywrpH|42WgD+I(P4^&{AaSl;|#O2i3$Ri0|WRI1GwZ zu*i~E2NFiX=spLXDcs}JldDX%`D6aX3P#x2L?`Al@ytbDY6B3S^%=aZ4Mk(Hw9P7K z^0;afSjqcT-Erqbwk95OJxwx_dj0-OO*)mg8Qp!V*^*T8huv+WU$1udT3+ATz7;KG zyfolKV%{15x>T(vFmTa;A?A5CACU76oQnyY!OlVD?c4ddXuT{lh*&iT*5l~F6=NsWUiWb_aI>&pYH9n3=lB56VCG7nSg-| z>g>%>CYc)+b*#m-A=sUB!p_^Bowp|}8A4wVZ5|+9g5b!zvhPa>5k_1tHCU*WVY?NQ zb~BLc2Yd42bL4EKo(#7kiIILokdfIhE)|$5(;f|*OLmv@ zy6O#m{;i&i0Jk@3^z5xnraZAIdaTw+*1Q>AUhn_Z}m>j*V9bGm@`1AaUrM3 z@C18f!HqVxfr6S?^d!&%UbiH-)NP!=j=bFf5jvvcYRl9e0opo~a17$R9?Sf|2z z+iwmJ{~lEM?4^#oMmtXc1`(ZpB{4~IaEvArT@c*knqFMuaBZ+B5S+ooLtcE95*NvH zu!3|9z1wiikw4bT7|LGwf!zZHowl~!D6|oTU~;orTH~+WV!Ip7M$^r;0(dr_O6i>X zu%Ke)y4(OK%JONYr0|1Z?bnTGfzf&=Y&6~DG$IlNq?CFbd={)-dUwRE?iMXNELn8y zqKR#|7<`w~)0-C;)yoxKk7pO@cIL!I%mlN_kA>(fB@0jg&D4^r2uw+lx!OE;5-n*O z`Zjz861t$YG@7-%N=58Qs8K!?OTReK527-U!>K)8%Nlh-H;jz}t-N39WT~gEV^&@#7`Sx=ym>p<6NSHbv;3- zk}0K$3`dWMbnh?Ua;S)Zy&ad;c!m4KmU|K%TjGQ8&g_$o+76pj&hmDR$J8L zcfOIEZydPU@C-r|FWOinlVL9Kq^8cRWrINQ;Hd%*&h%wg3Lb;0!I^rns6x{* z8%)J68g>>iXhJLpWOC$zl{OSYLq!AAo(`7hMn-*=D5torct>63yx%0y8}ZthZNQqw z7|bwFxim;8Jr&kHSA|P;_k2dZOEco^<1rdz2+tkYfd%Q3UL5})6 z?)}Twc5d$zuDH&eW;39Xb^2$qFK8sZ3NcD$H%x&t(vRpgguD6uPMHWbT;CJsJ;n2) zT0wZDK)sb`B12}_>6r&PVZ&?T%2O=R(aoeY)F=V682g-t8}jmuGu%j+eDzJx)}Fsu)sDQ%O6VX#Rs>>N^4bT;hzHYh=^>13tsb8 zot`goPQ30&7z^IZ^twNFB2T@JAf`gIHThvp0@oza#Egk`Sk1+3sNpGs`>{oD?f{t) zSWYT;Mff!gdMjs&MOJRtHa;V4KV5Lo3F~-X%n9sBgd`!fFBuNfJ8BY%k-JznZAQR$ z4fw~^(KCKgw1FpiY65uh7k%N+o0ZLU;i4xC`sE7t>hs6uG0o7WzAOiEchVn=$zK%l zy*q5B&5*0_(EKAsCivH%_9&~j7}-r~WjKg1mm=)f$K||ZthouvSTw9IxU2AQPmIX} zv)Hx9R~V4B#@CH6tSz}lH)S;1V=;Yzx)+;ULDd+H#Qi`vP7NQEFeBFb+$V(Kq*>}E z(|^hZ@%IYz`+&8+H~sDgR-%}qY1HAEb)#Z2Y9lXLYjM@JN;Ff^cscTB@t_$iRkW>Y z+H;>lc;@HIX4M@@A(_srVyzdkGA+6rsfreuvY;v!zzD_4ZciL>+FJh@symEqG*xO# zU>Nioo=Bnii{F%ARwm$NvSUD^jI zQ5R%FJcIaW`(pT_eMK8Eo?XRLk|bzNo&9XX-?T6IM$w$_Q)U*6(BO!AQ?n*e5;kq< z<7mn*DIAMxaIdkWgitWexE^$FbTh&_cP#OAlU;I1s5OXQL1c%DjWu8=k=KJ(ML!pb za`Z||N#=`q-06n(IK-V876*KuK;0|}Rp0OVIJXpOgi$22)xJ|-Ev~0_DOsZySETCC zhr8jE5845*>Qs#Y(3gY5lRDo?f1KfC0p1{C7t30pK>`%`8tI>#QRq&%OZV9#XN`8K z>ik(^>z)8MJ-N9J4Go6A)7_OhF|C2g|so*{F2WB(ujLD2~A@Ao&=$XA(8Otsq3CJ4e#lY zua@nASK`~wgo)bKq=OU?RUGq7Fm~(VZrvsPghA}*zzwk8Mm~@bi zkO@n1jl-n@*HxkJu1+Nj0w7Btu!AJw6NlO0i`J>NJFp#gx4N3NNk$#J1gMSP_b1Va zB{ehHJYO2-s}nTFQ*|$_K&9$d6xHrUUACR$Vb%}2%u+8(k?Tyhh-2pHJPY{*?p zZCv9nB0g3K7Kwl$Y`Th7p~h>_1j%%2XW1-5mMmXYFp3hVWEh5`Fx3g;IqQl*&6IHnu^~{`aI%rhrI;iIzWaM&or~Q`=e<%LE4iLSsL`AjX*`~Ct{bF zb;KT#*h&h<1uY3Gcq_pz*$|w2d`J@vGrl$P+*qx0KtUrL;G;7eOocHClI`7tlbvHa zGST1m-foL;Ty!wyV_iR=o6m-Q(paSzwC zc4KmsVrZ|lSWT5yy4106;t-({z4Rr}m^56Ulh+RB_E6zjJipuI`1Yon9TCY0Z1Z=O z+%#hjt?3?{Ye@Jjoj0;7>#1TsAnuX$23o))~g&qFTJB@nm3$+ zHTz(cev#35DCdv7*Gnz$4Ix)j6q_VF!rm>YaBN<-NMv211X@fT92jqyo5G9Etar5& z7S-or%NpF9jl`KJVq-q=G3(ht$}DL2B4ORhg}lPh z;(zEMa6}_PW3k0%Q>5#&TERrj@Ff?BURv3z`Rl=qwt4Ht6q60XAy*F$4|ashc>*OE z-uPbk%_bb)mG9`ILWadAEBK`h6U|3%{%U2D5jSA&m7J;bm$MoU--J`s5-otrYHhWq zsIf_+#k5?l7Ja)g4L)J-PIC}8M!G=)kTJ>-;|osiX9mNBO!AAH;7R59*9QIXrhMsD z&mAQPB4)u3hvW!FVzk z`y>V{G$kwYvXtbQD~at5fUXG}jn+flK1)$9Z|P@fmVIS;fv?*FWil>#9<5-*Nk_kM zZOGAog(7GtTDvDZ#zineSeTyJw3HmWic=Ok2)P7BlpCUvx51Q>9MVS}eLW`zX?{Pf zn2vZ|HR?6=({=CKm~{>~K*v0lnngFOTKiW}xpe!F(mU;24!!fFfJcd*S9UY873LAW zLqrhVA445KnLOH^k|mGkRgye$)`KoxGo4+fYCb*RA$uoIy2`^T(LXGUPF)* zq0YkP7ZT^j!Fpr8yJoSm#&z0VXt;^Q-}Y~Ap-EU)7 zv|es-k1EuSYPA}C7Mz`})&l(h;zFwdjm=<{n*T6ClyMudM9dU&8kb@@h!$)T6-I>1 zkpvyQs8%-uzqN|{@$I+4S3pdjoSpRvF)XC;1EHN-lHWQYRSufcYVW?{IX*r;XzxKm zS6X9sSi;=G^HeU<@%Kgkb$Xs_*`Md4+}Pon@l&VZIfPA#iWaH`G2b(aMfNmMmrFRy zcH8np!Wj)Bl31r$;1$i+1Hl#%=6ap+)THq?_E2SU09B0SwzW}BZfSkV(R)o=n@-#w zh8v~5-GiNjL*&pc2H*WBLILiyT8O+s<=d~=d>y93dG8QSG`E87&t~bb_?_S4dFijT zv`e<|{PuacT*2x$!9&V*Xy}Da)EKjpZOy-=1-HZ6?$kB4QyPgyERBqWBD1U)t|n?t*JE9ypfp@3Rqy zkM7b*3VB>6yyr|nD`hVB!UQ1&*$dB{fN+c58DiBmyWMa+6;m#;a_?N3T*nMVGNm@) z2{gFgEEme^e}gbtY7`Dj%Jbs+)AQ2#f1a1m*-K?`4bw^%ACs31iuioo7_*D2F|!l_ zT06F=z4PGw$>KH@2&=m{f=dNjc5M|n5aVSWPRllPp}71Tt5!~NGKj9O)Fsj+{Nqxb z^w-7@f;GWR{iEu88vR+P+ZlGSC7p>Z*ulEIAJQrM-8Ujph~0t)48IHeBxE7+PwbBP zwETI!i15XAfkq{~;r$2SB*UfC#l{%6*4eWQ_JewxzKEuQ>h)Q>k9A?L^1POpXqelQbF=d#a$6{i z2*SR-4KiSp^@=tNkjuE?)lPOji=y_^zi}T)sBMW)4x1Mf@=vo1UrnNS^k86h$+$+5 zJa-mM|GMVXFQaZ)3p6%?q&niTKJjhP(VUw z+qP}nwr!hlZQHhO+q-XV+qSK~_NPsobdviuAM-KE4D@96}m78L)|JraC7^F&`7 zL5zW0wkaHDb}TR%;U$>Z74+{DGydEPMT8ecz`cKA47cOANz_C4n{Z*z51QcF5#g6B z8n74%Bjd5E;c^i3n>h-&JA_qh+PoiD5>IhJ7r$USEYd_>tqXbG<(1zWXycL2P)<0$ z_1L1T1uIK3Q@x01Bzdu+Xsuw=v6o_Pl|*CaV-oZ>=ME3rbwD51myI-}Es)CcSDqoD zxQ8A;Z*S{tRtoP_Nets+2zuKx%DY&8Q~-`TCU83@Tz_{=*q*Qn#Ea| zrV4T4sn(T^gLk7k;|vwVjMoU=>Ol;Yo^FVY9sljneTT!bvU*r}3XrUAdqeme_1??; z+iUXJ@%$nI67yZ)(H+I37I#K}emxJuvH)#2#T4j%(j*xND0+F~_4w^Y<1}kYlZ6eV zc%=L9hn^$#9`Ob-ne1!CJ{PsVoWBeE78wn!W2Pk$Mj+X2*R6mQlh!LA{3+lLIaR}$ zlIu#Q-uwrB#9n^_dg}2+L;JU>def!J&Nf;sJ%L3Y?M;(58;nLGJqsZWU0{mlJqsNV zEi|T?gKpPPvimx*NH%pn_U6ylyF%)`?SODKe-5Lw^dD(d9W=%FO4#TXM#w5g4ifjM zQ)7ZvyC*fRI&6?_NE!)}iTn*xs-UdHL1jJG2kXV0jB}ZTmS0_h1{AEPOT}n&;;<&N z1mj^I5kPKNh%1`U-1xNZXg^4Z-H#eFx5f-1$fOk%NMT;_tj{N-1acdMFO)w&3aR1! zAcO%8ztC}uxXCow&rsZl7cx?7DANeGwot-a7Ta)jtNHVHxRRKV4ewOnLi&v4Hh0Gv z@!;WzR!Y2Q8ulN@317)v-wkiJHVEP^SN)tZKAbb2pr16b!Zch0t@WYRxU_CKLnjw z5n5{ZUFkn+Av({xB$8|h+erXj0;k4JUWTItW`;o16t*Qsvw@+{vox{IXl2OTY_L5v z%dYJU9~*eUW3oic<0Hq*NY65&gSB3=K5%>I2g2Z%V80Wh)U!Cr9m@=@*2hj*Tvvd! zD`Rv#$!OXO)y2_9&5YPsq;5DOKk;Q?91`p8JRsA0M6ZNxf6eMK%Nu!l>eq0P+(PUA zF|892y{WWX-f(kz5Y9vNY%EmZvM_cymNh}9wbgd-rPMUKiD*-hL!!&cs3HbtOq!-; zBZoI}o}1eqLm3p!X51Yx$&hy>ocUCeVhmrGUM-KUMbN;gbD zF0i044k4!%@2YH$xxRm4+nUnEgsFW+c!#B+o@4`Z!|=)R<@ZJ_u9IlV@gWsnr-m^p z-G3PV*bVDCwGmTwVo1J(vq>F+$M%C3{p(v)u#FkW7;)IaYRS;ho<=2+I>3vzm0<$$ z>LqO8N(vlf*8uSl7oPx9*+4MT9hvk0)9(m=5bARi@b3AZ?Q#l)Ra`LUVN|^BqXX~Y z_?MW7@i?5%XDrN$j(w012j~yYa_=JxvFGoaVa;_fZ6wIBKd?0r<~xI#LS=>-4`gP4 z=QqSAEuLsmczFC!N3aZFNz<@IU!wBNW~ayafS*c*R-bn@cp8^!Z;L{g%i=!l0%Jl=>P zDPmF(J_e(XDVtbxikr}~zX4*>NTj8QXgF3Sfz)`^FK$9ZNz{*s$;e24+NmKXbnf&Z zl#)Lg3?B3zXMZ?u0|(Uos}M-95GqQ;67b79+g=EkPSchOnY1od4LuxTCUtZI^+HOs z&oVUqnj-RSSK3xIdYg1kdviGU^vsoF!|Z$|RYTm1gxR)PjoeWTiOCzxK0CWxR@B8= zka>q*`!5rBJ+r%z!V<3Z*)uRAMJ7S3=p`S@T#RbKg`xaWQ1uqA?NPMFSuhk?L7y?d zqn&QOKn9)(iP~VVO$j76m4s-s|B$M{+O4`H0<|nbxJZmflxT_#BOJ8@|JxCpkN(EI zt?1rcpjwFE&r`5BHU4g-)|+&wA?eDO*c47be}E|!D!(9mlq(Q-juTrmwTlJnFi(a%@PKNT<+>vw;izVX!E907ZGBmw4GvEp8ZtEgERjW6>n zaZfW%ScbDh1!$G!jwVVPs1%LqW@ca+fN=>rgh1mb(Fvh#AL}TaSV^TJ-cc33hf!xS zetF!(#&MJ;vSGrGG{;*CE5Hnygsy+Y7_=1#dh*t#!07(6nG2ZW51y_sZreP~k%+@H z*&=DMk|f>jM_a=t0&A~XFw}qgVX|QQ4ixj?wmJRjXrvbUilHWBNe4Iz9(+T`fPAc; zayMhiypWqLVi#`u*e+{r!3wPs#R_c{+;-;symASI7dE`Djn~#vmtpb? zQi->h7xh^tzJ?k5H#d~^sB&&?NOQ7-ZJwRgLd9HKsI%IVGABoZTLs==1!b$IEm|sD zV@NlpP1a2FvP;o>`z-Whs{`KU57%YdT;>@c+Q(7tv-8mSdTmG*8^3H-zD*` z^=B+`t5@$K$}}-|>}^~<{3DC+X!{cOAYZoD>v~w=wtU`p0MdmUs`nn-EQ7P4W__-c zsGjk_LC&oK{e9aPS+He8^tJaUG&DC!Qph;;qPSKN9wFQ+(jX=DRF$AHegK*vUJDqk7x4J90Ul)So`AF1eB| zS++7I7O;B0!lYC9NdR~YyV()EX{)}R!A#ws5v<*RFXuni2m+02Z@nhZsS7km1J@-t zIkp@;g2A#ZxS_`lo&F!v-@w3Sm+0k~s8dH>J+^0od(ZuS28QyN>k*(k7YN~e2E#Mr z7VM}Sp$_bU^-xlk%b{@W-jGmAu0YeHQ1gS`FNRck0A30?vNzq_6>Z7e zq8RB{_iXILnX=45ZiA+HmuD&WnJi@!?c5***K8bTf))-_c>PrB^~NV%d{ag-Jy9^- zyKzt!D7RnRU zpF8N3sU`lR!h`8v@w3xae(^4eI<@%7x+u;NeEQw;Kk0<&J+W<-Jy_{-Jul)`V)hkd zcjscC6nnvagbY_ik2#1%POkFdq$KWFBClmH#pbVo4hELYNRTTI+=+$+nqg`|TwJ5=b&4uw|dN9yd?AUbK}nl(6kI?1ick6l`Dbcl4AY zuZ@$9bDo4hthZkKens77il^ZgLUVi~fw&qHfL>YZOQ2?WDFQ>r3X37EfuZ9N-es%M zmP-4`W8%jm>aIOwlJQD3*u4_^1?{h3XEbRfHl>yuM2DMM*P0aA^8KBvio!`EJi=qh zW+PZdozG5cbQ*Y}Nv)$(#OfVkS?+uVnnTmC=m?tAHb?~9+&vnxkk!%z=>9l4Nrne!Xg|ek)u8l5sn^;r(&z>8#9K-d^?| zt<8zsS<-iR;WuEWzat{QC%d`{;JhT!0ZPPOjf=bVGhU?7DP5XrwDTUNVTURGvBC9d z#jjq8Fa@Ybt;HkqW0PMKoHQBb|9*Nv=;NPz7616j>HW>Wl@Gf*Iy(#R9ev{E1fjfp zwNLhv+{HipB=-50+x44EYmbZgd`s6A%N-TlXL&yo!Gq|4SsnN*`NhCtlG87mGU~)X zg8{ov**F|{9Zl*)Pc4}QjAZuHgWlv)f0xF$vOE!$>Wc%MNJ{85;CTX)B!` z*CxkvLT<4{&Y!_7u(#`wQ*X*~U)C#~cqn}G*9H-c`70|wQ!xFlHVmtli2kL}%U+4S z7`BQcTXY7g!IBdh46Wej(cxHdP%__y!mh!<4NrS?$GJy9)IYW7I$HnLex!QcYj%e% zY{<<1>jyzr!1vba02H!~7LnIz--P)p_xu${ZXprW3*4a?mBOCNU?0!OYv8WS0I?zN*I)7 z^t(XQaMy2kD`Fqq@qh-+m8Xq9^RQGwZBRzdNz-=R*ZWVqierKrtdi`);c2lUkNy}s4FW(G`jl5yKyu^-cYf_BOy%tPZr{jD8nZ8IVpPhrtg;O!1S&qzkrg5C^8IvSUrcC%A5LBYMHGQlmAWTXLU;)*ZL&pRZ0&pGL zEbD}5qw`mOZos@1(p0or)u#ObK!>lSu>9(pBYMFl&BbII%YJxukGEuv0zsZ4G`&91 zuDMsr6!B~Zz-=m4C3mf&jUJMY62U*`@!hT~o$da+9t-%5o+#J4Wy{G@d09qD8uHc# z{g%#av)SG&=S>c06BjbM##qxC7lZKeDWdEEnN%w%;8Ey7nlb>a$+*ImfzEc0Z&{dv zaJElF7X@vbGtETODmP8^38jS%((?+W8+AFRww&=>*DKOXRH*{9{$X#Ip8=;+!m9Ec zeZx4I5wguV{L8yro8KW#HK>(bWZ~V~S_~IGu;1dM(~j{{DGL|UVbz1t-AdJ(y^%xX zh=7h3p zz^TpJsw}?mEkDp_eqNtn_p2kcUynZj%tZ*aAVNT+6pvM^pzktf)<{e&V+`2tl%Vsi zpjeBf#SpDTP+%EnbLo;yCrG2%ns9b zvHJMx)Zp#o;9o-+Fx>5T(`|lLKPwlF{o=7vf0ZiKeL;JuQD0OCDwmOiEi<6Lv}^Dt zWB26O2cpme2$I#TZLKl`69eW{L6~{!^Kh()X46f~#m(3MNT$^Z0m$H$qc3d&zR;I* z6|#n?_)AvAf2H_rmx;}1iKrd8O1MPRpR68g3sFBOp9r$_Rvm(ft7=bj<5nA=4%Y- zQp+*|R$8w%QkzkEoNMm?R#B#pbSHsE8RG1{2vV0RVnCtqujo93kv*nXZ=8$XwJp5& z!oHipBoHd%zH!xq%^HH6t|Hl4F(?fHc++>6jfE{g84gKTw*1R6nSzxDt#M(%*h2nq zC`6)kQu3t#UA0ZbjyRH&J~CjSVFv|TTCDOH%vnxR=LE5eK9&IR4pvfGp96H7L_5MW znEFiJgn}xgX#Ge-YSbwytQ<#m2@8;|OdxM{=B+!EO|!1;FMq8%d_2~$ZB%AJm#bP&hw2RMaVH?8Z6FQo~s9falje4U2=o}9mq{~Bx}V0Orj9eF8O znYY@ag|bYY=q#)OAX3NTksFVeA;+!3mD zlP9w1v%p&0SsQeZMYzSf2D$vQ0uwCvD4>e1%iT`v z8$r1W(Y1Ls$bu6-6QTM8%O6?Q3pPZ{47Iw}rRwCwpLw?wLzUQemI!M9I(qpA?R2r2 z3*M?px0*mluPsIHmiO8GRoe)C4|KEOZ?K_SRe0?NnVBjo?kl?c+A_Wj>`Q2E4@S3K zkW@~*BW}(~CO7nJ`045(U;aAM+_nth;@5z7xgF!bj1Dd2&dNvUG2CR;@MP}KpVIh0uR<=ZZ}OG7#(!iYTtGN2>XN&)*-PI)tWPwz7ae*9tl zh+#{_V~*E|Nt-}SxMeMmXTz=&+`QkzN@V}Xcf?E)V0Rd%&n=48yCB|)vE5#8U?nw+ ze8GEdl36Ida?#0I*{ns%8l#Auz{U1lguSlFS)~gOJmjHF)fQIdZ{m;k>Sq8;fAv_ znkBim!%R%YRPl=6IVLI`RF(c@YJ4mlKO|1&f2;hsyUL4~z~(C}P?Q*}M#hs^70pf{ z*C}o3Ftm;ABstgU8;6gUP>u5Wut`;!o}p1ms!9tg$Rw2>SVDOBD|`#ABgDsfKo5b( zW=m|n6RqI1>kP$kO-~{`4@92V(|C?+)b6ts zc!G~dV^6)2Dxxxsp&r5T_9K%zYofdNWI?{FHFd+kT(#|a4vw4IpZT+5#CaxjL>bDK zZr1sB-erV0&WTl~#YS2mzMt!#XES&tu0J5|Hjh$8e*q!7zc$rb~C{6yNd?;vTI(QKU~ll}~E0igZE%_st$x?6Mz;pBj9 zo_;>eHxBs{z)vThca@981oB#9UC*8N%GY`r~eMu7`u%9>$=E8JlPoC~p4_@Je9`RGG_zTT7seZ4I)2L(map`?%? zKbK)&c7GhI4V4X~R;~3efhh8f8VWqsxpIY-pwbZ@i9jeyIxt;bcubVDSG=4EisysM zI3nNxBk!$Y6dT^FGfr5dFBCK-qzFF~bHQnHn2@uTQW1DoxahtHSm4bL z0WjCZ#8W{AL5>;@mGO{M3pDGhU2lwBP$D6CXB)n*ln+e&aY~W00V_)@dO0N2$87+k^!c)_zT{@bHv|_>mdFnr6L9zViqw0 zOlruM0u3kwD8>+Pmhc!QDJL8H6Q>FpCt{ZA@}~Om=5i;n&%;g$`3p^x{(;*E3|Sf> zf~*h20B^uF9ERTT9pO3qND9Ml_zoTy{$v$yh@|eJ=2~sMxU$U;(t2J@JQVhrNL-9rpWmdT6a^`R3l!?1X`&q+&U@{9fOVJZGFNBYiHu!Ul+wFTF z#hd_Qo}pvm#_ul}|E5P-_38m!=DDRv;f}oRjEi6jyHCYq3?0Ptr&jP+n@1K77m{rc z``9kNRFQ|!08?xyRjU@X_j1{x+aFnp&Ja~hQj-)o3EiFojThFDw9nJP)S+t(xi)E; zi}G-RVe|b8lLk&tL7tEN9Rfbf3IPH*^0R^$EZCo7YD)Z=Yv?wcylw7;+pU2{`?EGJAlpiIi#z?3X@kPS zr~3p@`%7VpG<{N|eNl4J9>6s$so9#`{x1}WQ)9%PfyQc4w!J3K+nuQXDk1*Eq_S*! zL-Xpd5R4jdWdlTbVtgN1UAW9l-hbBaIFe%Mkd8-WbHIG`siLAs3wt%3BxTUu@x^cU zy?)0gdyL!f_5S=zrdL?}#u)c!thB8qNB)7!bnRAkS;XV+zTJ`v@04I?-;td%&8fb` zHkL$Tt0Htesi;OOnwtLVgc5A9E@LB$owbVHon{AWjV4y=($iv*(bCN!wI=Ty0j=%M z*Zs+K+S~}{sns!0+~G^#Ib~z62x*Yh+cN5T*kT?G?2{vu;SEFcrZn1KLo-n?i`~03 z0lowq9kf(os6tHzRcTCP{?9@RCRm@v(}GMt=MezMDpA+-%lS*Lf|g@eo>c~DD%)tp zBpY=+@hLiMKo6EUOTLyF@>Nwv3eaLG!eMPA5(&DZc%60Ics)JHNl@oI33|}azKMLu zzdaGXBy`@VR10XcTI4>$|%kT#@7=N67}vRnl4dpV#_uJFNg zL%*E7GcoWXyC5{u&QE+s=`t@5nGywoOn4}076PZ-(_klfcsW**pv(%krccUMm%fA84;Cex~L8JG6d;Fo9!`Pj5x-evt&}yd(Pb zH)%}QkLWR8Pb3XmpleRbCzyV~Ephbu$wU~^D!lP+=NOGpA)Y@p`Tp5GU)1Gg@A1~~ z@jK>sMsY0vq%M5O2%ZFz|NI-_jOEqkq7-*^;;V&fD{75nbD;iN!IZ;?UaOV)F)wH- zk;zSN$oAC80pBvyTNk{GaDOw~CZA#^Hd_W?Q%UvZrlhnElR;_LfF@LOrDS#sO%B&r zl45axg7wR=zD0rj33+GMH2w$l8>izIhllp(+c|qNO?Q=zpzcu%XQ;|kO#-sn9AG+RXa z(ft-ROgI*cAlVt!;osT|%bjH+r}vN#qKWu+*N*u$?w0yDO`;oKV|v<-R6pT9Y%h4H z*0|T0WbgU@`gkFKjG9p=I$X~%j`7FLRZf%7S2U4a_c5 zD;2v->9J3=H%yFElqu}rXKlvxef#q^;+Khq3&`JEf7d&{#^b~jmH8L3qp3Zk_*Fp1 z>S8#W9Z^e-i#H=tD5$H0C8}t*kRkgdx?9*iR#lo%753Buzu~D1P``Lsmb|r;q_Pq^ z0(Qsxu73Moa$7qhh|g!w)McEhOc14^NIF!Gl;PkY+9CEDJ9s1+8deu3)Z&{X)gGYJ ze|@)k?RlG;s#0voN#IooZ*QiYU!VT1ZWlKp7Mg$nYlvZIK{!!a0{da@pgCZ{u>b*r zahIAR`v{a%C`CtUz0xh;dzgTTreNfW z$lQ=7y%a}N$S2Km6}XItjVg_>3fR@%^r~@5YXxz0WYv=N7Rrs)Ah|^ z$0xMwO@Vk`f4r8cioJL#y7!%Gs^QzYJJjkuU^AZ+64QE{IUgHSZKr8!-eWX#ZR#kg zzr@-%VjCtI+|rvczin-go3KFSgVG0UE&FJEl1-3Ph1h;tou$SH_~QB3_#Yln&8X|% z-wL(zMNj7ypB6N+cSIE}pFL}el?&{HC~{s8DJi6nDPi>H?2BLYJ$Lme zSFEYf?yo0np8oWujD@7ueB;CyN2P4yrxWw}g&1Vb;t%}dzWw&(UrYbp@MM)8t6(>* z{g&S9&4E|xEVptmx!#mZJSGEv{0_o@0f^ml4wF3UyCC8m6u!Fi?u%8{r~gN z=~Zu#RX=wg)|uaSH0|}Pmim}IVUYF0b#+`OiHtI-Bgv2oSLuPeiO}e6r zG#h6y$!wW|_{&ts^j2@gZs76l!n#aOeJRy>ahP!bR%wD!J``aT1r((Rcwko!^%fco&`b zFDmn=AHkw&FCAe@odX9%I=R8|C-veuiMOdX@Ew9=h0~x^D*;~|i}dOQRz4e61e&h| zXm-Brg&1!o4f5@Xv=EdO`F>oQ1k03)cf!O~i%K~6!+;2BcAA#|a#`e^X}~EsK!H~J z_Tzn@8M^k%Lvvj;0Kn*VLmfamVpCtnc?r=k5j8qaXmIY92^>MyX;VI38_>szRAC!7 zK666E!B&A{MVrY2j7tuQilm~ne~ZPbXq8>2d(o3GXbrZpdm$u1=Gz+SAvL`xJqgMZb-8&{9lSo$V1=`8xU@P`!nQgu#J1+gYIAqhIXyJ zyP{dp)a+dHDwHPB&&u{l#m&bz`D9BXE+w&4)l>PUyG9}vn{TfrAaQPPke$O}9m>)4n+(F@#V+Z~J- z;lnUfenSY$wsklN)a*>CWjAH1VkT0xZ#GcTXKC+3Nn9nfMwIa}5P}9uxRaQ=FW9`p z66zcDdzZ_(P^sP0C;+t&oRF!voV(ng{36FNj%{uN*XfhX*`3~w#W_0Vz0XZ<*r;F! zy}y>cyQ;GM8TiGHJb+GGI{+kUn?D(y&9=HwEt_F4WCq(KyvpnuB?_U*1uCL*G!#%R znSV)`Cxo;n>+9vH1lt8`%PT`4pqJb>-~r!}6jRErNTw~W+KDNqs-3WOr*L&C0*k@J z>&pqx4CtF*fGS;wjY<0zN~p>A1%Q?>AV>lD$a&K1OJL7nb|*;Nh~?R+Yl=@AcRQi&rW z7LPzQ?rz2l_5&xN{6wlcjq;u)!3fw__qcP8K!_bD`WJmye{MB}mv*IzKV52GhOzgs&ekE1u;fqCAFhL* zQC{DMiSO~w*XA^_?qW&o5vHjcaYM3^g`vFO1u^3e+k_dYz&GRkRW#KThv7u|YE_>ICsBi1{gNoTkj^~?nym!n0${yszfv_+6DV`l=U{v<)2Reu zSD~uxCMa^kE2eCrXuLcuhXF95>J5s8IGw8$stA)d<@R~D^_NtC_+L|=OgPfHQz{Ltp zLy}QtB@Mc}iM-((tp)}Z;HYsi6*a*mZo+q_DKg|6aZ*jmdf_|XtSXkoqNVK4z|DvH z-|rX*fXrkncnZ{3DtFv2PC0sp90i03f(WjD z*K%q0Hac5`(9KwUXftFMHukA~va>kT^uC|K zn<9@l86x+Zt}$?_r4WG(+h`;-fCBKct?x^X(UmFgNr#dKA6qoXYrN#z-lnUNHF+9- z@gDv9tCZYj&wS!@HIl1j{LILy7f@CfV*b+Ibi#(7Ted~|Re~)8TMSuQG*0{}({p`8 z%+REUSC*SfVR0t|o)opknj-nD$dd6FcOf< z!>VjSk-G3DN4y%TZYq5Wm3-?f!yI;{lX3=gIh>kZYdXur8ZJ#nBr^jdkSV4c6RW{>bZ&M=!M<6!B-c*b#B=z)_nT9G7zN%r~N7+06S$frz(#U!kKgv`)3 zAT>qIGPRxhQ8KKWZfd8|G-^DKBGcTF*K##O>&EuW{l1hH+_QIz*mk^?YDJR(v3sU| zVqvl$Yqq%B&g78VTlJF8hp4KOujbH;jADk8jAr}?&dvEV{IMm@v>C(f+{v)0O;04* z^t>jIoZKOB%BW50t0K;`cwxrDG?)H>bE+B9`x~omt?g*#^Xc1%?B#P^%f`=74*Rvw zn_HJp^(oyr3q;T~ywt#c)sy!Px%wOY+RBvx*eBH0jL}3YbDVNl^YRl?JrmbvTAKD_ zOu$nY}8KJM=uV^~UG*i}^Qg{{8|R zf`K%9%kJ*>x@lP@Ev;zOZrmJyE=c71O^i`m26vz+Lp zUJY+PZf^8jL%2RvEIaq-wM4)M8Ec<%_77dNEysvU*?`%_Z5jZv`aio@H~ZTT+c3JN z+foY2nm{SNEzEx_XcTbfWt`$c8oAfjgebTl_U#UIKMU=|j+_~#i@hK!7%ua)@FjXS zm)~3OPa!(cu^bvz~^^^=X~?>vSjW0B7l$tf1Pdj@LVB%TP%CVsjve6 z!fU*yao2@|;6GBdZD8GVXd&KnAWho~xBM+OvN^p~8+=}**PuUY@U~(*gg^Mwg?N2W z^c2d~R~w9ZrDBNmgjlB^ZeK{U7;)IIGDw$#qD9jtYpGpgagl)s240|UgLt`eyXE$x zt%eP-S8DFfx=3Z8TdmDOy^yHlvS?2458|?m;2}aroK>AUK3BUZw*5I1P(cYRCIt}T z8E~p`a_kN_n>WF`Tua1CjOC`4Pm2Y^*oue^QT)1S;JHPSJ=-D|GmbWUsHeV`-$x+V zmXv*H&Ui6+-Cw-McQmUk^>jnIb=m67KDl@mDeai*usaY0E_DRMbI=1;sYwC__&0aS z2b(7-E*uxizZE5*@pr%#6LsNWA6A?-fzCrm{Si&mm1+ODYY~S7S>9K-LRJ;qyWu~_ z1ch$Y($YaBk0`U@cmwXlZMw;1)}OJ+e`Ey1JbH{JOaTZB1iK4rx9Z8{-;IIy zy%W$%T?{PSn{rvG)YUt~^1Zs+=lb%xY0p)6@74s{=NapEm3jT{ z{!xG%6EHuG^<}Lt3n=8hl4c$~waOsaNXI2#HGDwMmSsiUwj5|4kJK#Sm@r&Z*$^-u zS<3JvkI(PzN)SZ<sIsi6RKCoCZ^1CnFAv~P}tw4ir!2xBpVsKe&v$t zP;E=F>hNVO^+JFo=Z3xW@wAQnq1r|L#8a~Cq}1QC(Oio+bD3fzeE{go>r*>fX#!fP z_&P+pf`5C{yLIi~;v$IdkJO2++$E15)X$SBPJJn}T*aw#Zn!-l&Hl<_go)0lA=W7?>@_2QapN#2ga))q?XMZm0SLgW{rn-<_Q*SQDaUIDIXg+7)S55#D z3QmrD`Vi`EI>F`!v-G}X`b)-uWw?AO-E*lC(5U#6)hX;b#2HG-qn?^^#1Wlm&1VvA ze2I+lrdgaaSEZ{|nAEXtrFYYUaA|AKtHyu_p~_l`P|7o6jUCA80^OKZid`Md(Zy&%T?>%4r6sE@0oB#a z1r1$>sgPAw23CuhF6&xX7y#C0sa(^>oNcgfrbd(am~~{XuhBj1di}Vr_BMb|IeQP{ z^*A%)3>k*>b7gLm`Lbb~7Ausmg_ zyZ4u|-*hK*0vb(8-QKhzNKaG03Omepg^n9_f_o=-O1}d@f#?Y_-qrybWi>NcG)P>e zFtk~#Ot{vum#ts1PGvN>wWgBp)gRlJqX2$gUTR>5*PgnEOfUa_sA;%y=2mmX{sW^f zEwNd$ZfE|00v1iNjZ~;szIE%ePR7h#(^Xl^SfvudK99*}0nEh3<7WCR#f2Tvk3 zLyeSaq?wuHa9ry6wc(%{+HNzw1@)hoo)G!AzJ_eQ&x;&LX1^-wy4nSLW!_ZT_k)+w zKUBT!sdu2D=8lBhS>C!(_we_TbaL?xazmvA_|X%gt2X`P{hCZ(n}geKqmo5uF$x7zU!mCRTQ zyx=!d1XjXG^kOo^nmF|e2+LH$!PIjTHTiP|H33+20L1`EsH3XZ;BJOZ_(+cFqMc|| zSv$R)P!_2G9KjZ; z%l(#aqsWtKvXFDU6JAyF8FPmT6NMn0coZUy$Sny12*;tmi&*)bGbuS4BjMI)Z^&o$ z`MgW{aBTYA3%_z}VR5;8f(m(xo543Ma}$+awyo9so9fNUARftZo3y3_6a;KvrD5< zQtGPQT0KIbWUr;teXxmQjT)gZ^*@d_^EfAFOWN|p#VMj&<%qNt$hJYxNm*Tz^Z*bX zs0_y!4tk-#xx!5OM=4IsJEE#`;9h`cx3?G~Ig7ia9h7QJwSlUzYqYZ|4VvkY;R{~Y3}mJ^uGxEX#dFisQ;I+|5w}AYm=?+ zN9X$=JAs-z<~Yqv7h8UNfn~aplU_dC&PUBH)@HF)&SpI2h(s^**B2l@kz#Xvqe+*$ zLu;K#5P?MS4otw_8+bVvU*)i&0TA2?an#{h_6|AJ5agan?r$>_0y83vNW6jWkyj6- zn%4lpN5UNvp!iM+#)rlk0z6_kgf#e^cK^UV_gW6QAwNv$oI`G(K`po;M<&FY*9S`M z;7|4k5to{3Yb{_rP`yXhm7Oj}PXWFB{JXl|^ky%?` z-^tn2udmpDyW=&NtT;gkb8unfE$EOY4{X`s!1VV+2GDRpzEu0~YjFW&fAOb@0Uh}t z%pncd<2ObteUK9yC>obAh_Vv=LaG?x$$pl~)^j5)E=C#E7IdB+v04rGMkSA4Puj(r zbrv1=;L-QUDJH;FR=x&Cz*KQe#d#aDldA~DCVsLc=OZihCEOpPzWpAFy_chTpl=L>ZyVsRANn$BU^NPDxFF~8;k;sRoorF=~lJ4J&lw^!gBlv zPwyBc%F=X=j&0kvZQHhO+qP}nwrykYv1j(!ymOxKjoVSbx~n>>R<6v7tj^Ai6*1km z97v6@=rV!`zvYJWKmfAZ131_mkw;pR%8|Zo`(A1BW4USD^SXK&pAn=pK=ND+BEues ze%Qc<1mBCrU;Y*lv=DXl-oxVFfFNIrE@T4YKD6j&^WD;jE{0ZwjFayIuT5V6``o?$ z&skW;DA3iH9?d~f2&p2(6vv$H1o{?ugMx7OgeK-IdzJ}4caxCTfsiFTimY}NNq9tW z7vnw5(8G~Eqod6b+sd?KpuqATI1dz_jLwS-vF=xX}>Sj1qPk0LnFN}3p6)LI|7AGS0pwxH{CtBhis3EQIIkk z7k=OIv%QNHEZ0D{UB$RunkCJw8RenvI$A{@ZKcV1El} zUPNQxM$0wY9r|WJC}z4vq##6h8v8|0Bw3l=5S*VoH>r8^JS82|TJ}f{j2^2~t+#S# zLrp^?O%v>Wn@@qs*C&RFG^-l=)w3jO6nd-!qv{7)YuG zKqbo`Avgwh-Yt&wQK-z_zLpia_6WO|6 zQJ5Htd)o-}<@4(|`M=NK=hvIpZG_wPECtteaDcC99KT5;q&>cNVJir?3UNpN0tqM;r83FpRxi3Ai1G>`Zj_GPZ)wA z2Uui&0i+fB%@U7a8Zx&qINp$3Re=$=AfNNUp>!wX6X51Rh=6&C5<@cox|@z^;Hfwy99lj9$q&Yvm>=qKI)*Xd=Kat+5g!vEZobhd}Gm z!;yn1N`?jnd>16^@mexa4&%~QS3AeG>^$+m%Nr&nwkn15X!okAb`n|$lANz3$w>@u z4&ZS>R?FjL^?2FdXBzDFD-{MPdl6xX@;ts~BH(eMUB184o~x4S z*r75WVgB=~UhpH{9Zu^<0SnG2;2CSjwN^`--6&cXTB=E`BT@k&WkW5nm_mrXtQD+H zK(>V&y!z3i>uw7jU$^Ww+^T<P;9lx3r@r|l+N0F!XzO2 z+-Sk2r{O>cYw4!_wa(C*v>*Tzx@eXWHJBXLOvtxC>l~$qWJU@W_3)>JfG%CAgpYvy zBoT7zC}BB7Ye0Vjs&VNes#|nB#3d0gI~J!l)udeQOSP3KnA(m^yij_MN6LS*~2ssFL3M zSNEC}N>U}Waw1(95Lb_~pe(x-?pC`abKxGF?BCO{swif7XIoi_*loqBQVHg=KuwvF z*rB-7Vxp}%gyo($HqzA$g~KkzK*pJ$P794xx@uNJ3kBz%qo6Qo(Iq5c3c?{Fn1rVdec97*-z zEgIk&VTCO)$-LSkBf=#k`f!SGAc39jI(#Ywdg^Rpd};hTGkAHJ5Qz>(oFr!zWhC6J zlfCq-#Z}{=-KxS$J5b5lnS5wbxQ@gN@twrbFA0GM*`oI z;$NHlSaIq4scdOV{M%EP@O0re7rOIhTb`t{ilN^+Gq2)SEuO8O&5>S#^VFzvLhfX4 z)apk*6j4H+T0Y7Ya}}*ZbO*5kK#_b^I*wnoq8boMM{mq<#%EA1-!hOwvf(XX3fqa1 z%Bgg!vJRfAP+`BP`6KO!q23fVO#2YdiJ-K3b&%myVj&l;1o%oP-MGyTkF^9%EHwqC z&{2PJz(A&u`{VT-T0|*v7LZvht{Y*RJ!&#hkQmn+&LK1A#S+8oKrHbZH{J3$w^PuF zUbu_|n_Gk$@6JUM!bq6-S*tv#V1EuNGPn&{iuz!Qdl${ zG0%y@NuDW9!fDMmD#*iHNLq+HW23-0QS{&wgmXMhUY1#vPXI7wdkx}Lp`+d~H;X1s z$6JvQIW-P#_8oH-_@M9`5C{LxG#5DmS^~&^E05i%ssD-U`gTYkGd%j10=BDB_gwkM z1X0g97#AQXBJ9-85x0)K(H*}OE*xbg&k2n$w~%nv3il$XMQ;ALo!s2}gAOO>U%s2X z+`JGgGx^>xGz)co$#dBrd(%H(}%wMdV+F#o;!e+0BIRo^pUESKm#3$Tj>>Q6tp0MY)>$mRbDwr%9TcZ!) z*&dResTux#VwF)9me|@P`B93RE=Ta{+*6>&wmk+in>MV);^v}HRe5K*!oi^Qoau7ra+@Ie67%yNSj9o5vBd=!_YTk`@);W?N~ z*j%(K=pY5lEqH08bp5(<6wF;i5Zwu9G|@d3^YOGGA0kP*r~oOq=!aON5!LQ2&e=9y zTtz-)wyPM$IZ^yrFfdm}*=?m2H!3w7G>a0N*HEC(Bk&94L4u@VsGEla+=gA{>&IdI z)%aYe)+zn7A2ZkGrosnIE)S7)%@!Vbx##SVu?ri-TpU{};KT)PSqEO{qA@a|mTAix7 zt4O+3tEtP!bS62}?UPx$3z6M_ur;>etlS?QquS_`I08 zn8(MkecShL_pf&ujn_ca;=C(|o}hjYIrH^^!NYHk{2*g0*33i$AWpDmsddQ(Um@5+_bE_> zqu4WgA?T4QH)Xu}=3L1;+anNu_)P^Use~+2(q6u)lVtPf%8H~_MR5{W0Ab*rdQuEg z_4B%7&(hSBL_@r-V!w=Om<%*B{8+FEZnC&a@wsxNnZ8`x&|=*y0GG61RKXTuP}Dj2 zArMFmGT!9ja>O;ewVq3eFCd9QTt&E1S=3dece*qRWm1WvaZJX@B1i5|97moei;yax zzweV=Q69$rJ6=LZ_nSNfx}kamr^*_1r|Gd^DNil%23q!%NkBA1axyUG7ZQL}t5l(D zrZB;KTGhfQ`e-WMk6a?tf*T?=L+j!>cCB`n$99zmd}C#nK`N|J!cQw*w!!MtdD8vU36o8uFUcXoapxmc`A$*^@#L6^Fxt z6Lb+rrr`#8e_iD?0&k)n2q`Av26YG>nns~Zv+oVaFl%h3 zXf7V@ycr>9g~@lIhaynK00Kv`o6nWrQymIF#(aAOFuQbV;XJHO2KJ&OO7`sbxm|gtjr$?)J%9~k$`n? zg}%B-eB7h_XpyoHOP^usaLR|CwLQ$=)#||J;QSxQMhyK}Z++#6zZWHZq^_66cF?qn z&6+u+ROSicP-PvgLrE_>MW>qXM0HlEOe>M|GQHN1rmkWbii4#=wO65QE^a5slq%-a z94ffFua#~&tLJ==57P0uF!TA24B)=u)yqNambw7zrZ!;Nz7#t5 zHj> z2x)QS4}W^HCBht^s3TK|BAF2_GMDplSZ{b@E|)X* zt(Q=CAt}fex5Enm{a8m$>HD<#lhoI7bh;_#3Mw#nm`C92{utm!WeRL%W2%5IQtn;z z``|kTxHjp{LfFlNLvh_H)bKbb@5NTK6zk|Sfv8$N?^CBT1@D~HutVEa0$ZZ3rL>*< zqVqXONAcO5b?26)%SCS3TIpQb5~Ko=W@MSxmcRHjy?P;kUKp^4S27-4)sjrT6CF8B z%VF^u7fKfTP5$7y~2V4Ypc!2 zlmaIy(>jlJ^1og&TtSIrX3!Cv1@f z)Q8#P`L>9#dhmPpaZ2phgdH2cZH0PLBw2bLnbIuLhnmq`J)BIZ4P7LBy8 zf*bXh)H>D!J_F2>QB*~Vclxj$LE>ZW1JM_XiaNVA&%HWU{}VsAz>*|D zss=h2?h==mWl}I*mcJeO>?{~T(7~?jg!jA)E^8;8AqrWC&RrZ)Rr)?jK=HFbBIt|I zpHTEB`|A{AiIQ!VjP!+LBJo!Hl>OOx@bdCo-oNvIPF#StVmuf{UHf9Pqw;Ip_(8ww zi0{#ecRHP)8wd4P(UN==v&{^0d1Lp5!C9t`-ov^!2+=S!%y&~-T{GqvF$<8*-J2Ol6yl)67 zO=s8Bti-r2?}kkun*y_Q;1dpG5qVCYtz<7QN&8II#W08z=d=eI@$OhXA22z}7Ww;a zbCTt>1#wP;XOFgcakxyR=ldrH-~Y9^nm9E+zB^vL?X5MbB(foh-%O8OtaChMvwR4r}tI4z;AvEI1yz#03%GXjDTYTx({E^x#lnn?}G9X^oA%*6|!2@WFes9E+MjU1d!i;v7 zA}cYlb#wQ7ZSfRG2uJSImp~ts!H9xTL>m&k1M6sQY03U z^y6k={@N=Nostc%xP-kmcHi?h43DYPew2G283@Z4A^%D0lJ4f7f64!uCv+gDkRs+y z0$__!Ub&7UC->=ZYVYm-2D-nh{b?b^Zxpq-!X-)P$s6#9>ACjq>80(}+I_YB8dWvCC#=^_VaLQc(@;RIU`a%d9&_e{mT!~W;zwx zHu>{z&<;8RHg1Om)-UgaHG`%Q{WmwZHa%S1;<({o;Q;QXs`q~8&6C0+nmmn3Wl#qe zGT5zY7JhZ4b?SDhXC_5obg~`->s()W37-i9f87rZ?$=X;^W$Z8?eJW@$!yWR-q`Gs z6NtF(OuYHNUm1qfVpA!Ca&stLJR3U~zg?~U!R9Jm;v;||MSn7cDF#H6>Z{4BPYa}m z=+jAX;?qe>=1Ge7A9Vj>YY+`!w`B)?<#QD?du#ijy>%5^;f&3@QswNwC^@x7hQ(wh z<5^NDv5#}p#W`<~LnarGIJQEaF08y1stS8g)3_9eXaFT4Ol1HGviv2vA?{UjFEiz& zfz_A|@MtVHv;z5x1WZ>hZ96u$y_^>|^*4BSsWl>^(yVYY=OPJ z7BNZ92iBiSW|If}c;6j=N`do~F*tbB3wjsEkiOgfTl)Jy9vOdzD0;T&d;e^2{o$mt z#ry7hdABe}r#P6-wY`Ec<<#(pv?Wx^c3Fd$y8W5`tNRWD-0|jhqiXqLdWujm!ISam zb80I2b#0f{mMD_ic1Hij`Dcq}-)=F|==%%688`N=lRIlc7_xTSJ0ld|1kZ0!<}rj> zvUafZ+MD74U(lZxqyfoNVb9R{a&_nXB+Z7AB;_&XAX?Xo7QTntl{vk7PmbgCzT;y-*YwYfC?Au;~mh#6}3cJ70 zpjhqNgXj9fi+w$QO_rFgV2k*xN()#Q7_3AXO*V$+bAn@VzQyU4WS*bCv;}C){i7iy z35G)iS$Kb0IRK@ACFoA1Y1H^>aO=-5hxyw|7|j+HO*m zKdxf81j&w^ZX{Osl@~TA zixTd^7D&^$<6=C}__#cyt(SE=LW;e4y7$A>x`{UC;*-0!e|jhWR@C6i$TerN{COVDIj5Zml+;q&JhY|aLpHC)F4?0zJ^c7seZlTkCNS^sOFMNNKnfq+bI&fuE-# z#o$1$2m^_pm1$;x9?@oufu1VRECMkf1ue!dHV+GHHpA2rxGVlwk>eO>^+D1mW}2C- zH{>4SH!@qOikE8E4`rVbpw#h|*3m2+Rbyhuo+Zi%?+F<{md|I2qD^v%=OC6#BnQ0W zZA(^THuV$+`xC3SJ`m`&v36gm%9ByR?BVLw2GW-oHZ3II1>1-B#hSoj~`kuAW!s*0VMj!TW z&Lknfimh>61`9x`t7;4BHahjL%E}hojS?fs$||^#e>rk~Gxn;UAyw6SyNQ2trK)>e zvYqao_$K(gjg7D~5>6<`_o%9>ZC=JtroKuxtq)0uV(_Crd4#(;8>L*?gML0cxN7r` z`QIMB>Liv4pDbC*-Fv2wMi3Vk6p?nIn+s7}^VB>~wuxYCa~ zq^)q!asp1WW)eRNz~qGuEL_rt7VZDwPe?c2L zX;+=S3SXDK|A+RaZtW<$_p0%EPI2pm>r~N@PLD>V1-ZG4p{32*QVUD9LkLASQEnsaf%^VAn3`QnYi*WZR+oo{~}WiFCge`3u2s zIxb^cX+?C>2({TD^L~x_6Gi@3D;UkR>NCa_TT!uKX72xbm2H`KAg@ZuD7Kd6%@+GD z%`yczZHaZ;Eok9rEK}xu!EjW`YLuR z{WfZ}x0TWR-M{Lmvh#s54SBQ1ZR%Bc%gn9kYfHA1B_+r-A_|svXs5G&jpGulY0Pq0 zjm31(RqTH$k5&sph-T0!hkO+4E19CWrn_f>fAj~?q~Hq7gpS7$zr|wryTPk6)GuwRjY?-`s_TQH$GetO#GR|vD zytL?MO#==qE4oOkngF+MgLF?!wYH&%W0gjr82L)^R+B$vX>sRh)L|doS)pUTjPjn6 z$K2;~f-goE9j{mZ1Mr@GRe+8Zd*ajZ|CiW#n^UKawr9X`l?pw(MoJTem1OGT) zx8~I3J5F^|Zm^aEP31#lNaHk)@}u`VcmHLs1UaYubM%u9Uhe&`guAF`?U3o&t5uy) zH$AI$no2r}j0C2Q@vLpMD+K;tkTvh}|;Q>b0>eV5-Ne_)PD zZvjhXGcwhQp}nRzF~oW!m}JtNM2b(Rro%pWN7=f+xb43zAi z2HOe8&_o_=@Q#-?bHEw-gk{B6`FkEqh1P;UUGK&x1XZO81n1&ED)LuKGK)B!$v{DM zNPfFNC3_|GSE@-tG({>wC^i>z|G#C>uJA~^gp-um*vGhpMxpd_{obyN+5I2s?D}h? zIxf>P%G0c*xCo!F`?S7JQ9hb~7r#oNiFy)?3>j~d`Pl6Fs}h8NZRx7sXOJqhVAnjG z^?zq!WzHpj{1d5xV&6+v*Ubd^Nw;%t}<1m7}N#BuJO`VfL$TmDd+VP3>As8qR{{ zJO6RqT>zcdqCe>-t0|Q79E3TAYu49w=L-Fj)11TwxMp#|=r;(jkkfLL#QVh6ic9N;5jm1zYrM|67=#t3cX(8_yLpx{+V` z9or&F<%>9)`!c>}_!L8xbTCv<%Kv8MwRWb;zb`xi%hYa<#t!QF$eTl@l9AxmUVTLJ zpOXDg|Nk+o2dN?FVk1#8^OEKkWLqx%QUBjVU4nN>-UQe`G&Ra?U5fv|QR2&-be!b{SO3xSSIi2y6g)!{ z(Pr(N%gIcXUM968E8QPi*R8(fb>8;uLNAwHBGy^|9&OfSq__rSa-5Czd4ha=ZvZHz zrbCp!&qE#|rnsWT6{xvaG=H{%(%w_- z3LiRer(29b&qqa_l%tzYOODNFOFA|gC{ss$XUCz{qe(*XuNTV57#|!jCgdr3dMykG zEk~iCP|+u86;gP~ow=yV%$iD(m#PU$7n7T@Bt!!POa70onx{%mX`1_mW`qBuR7Jka zcBhVZ@+P$etDpLOv#u3;iI`%wj!s}F`^$(|TLL>jL&l2aP)HY0ojxL3H`&Ch zNog7KKRAH@&k#37PwlP*bk(LGURd$3I=yG6Tf|qT=FMhFN|GN5I5l(pr&L!!i};Tu z-CvWLmjbiVPYGDOpG4y{3|~%ouD%&r%F`Os(f?0$yDHdYX)F>|ylXTb|FbDi$#&-* z0yV}cCmkY#|5=cXw}Sr^V+}j%E_irS$(%LBxdUhR}pw4SkorqCsKXn%SHcbih(`X8EUX36dW+k^f!czptDu>D4++*7)0yk zvVVx)*rA_1C;cZ~2X-7|X2N!C<>o)!|7~~I)vfz_8|>-mKJJzIv&CD*G|X{VPQ4Y@ z*>IY5XTPQZYnbx?F!GXs&#Bai&0%D(EX7X$MV{ku^#As<;=zTD{4snD=(@)x`aD*@6)u|b`usHcJvx7VU#!mFbcGCtM6dTG zgQq0YSraPpq&Yv6vZ(grOt)gnSjAvTD@F_4ezGeo73ehvH@i42UXw1Q)OgKN^f$uz zzoYVOPD?0>$|p-M*hsoPs`bs*G^yiPUb$wv*B+KFm|%<_9LXot7SE=xtFC>-m-AQF zWsEUfZ)inU#<0Y_fzQ1o*fT8~#*^qKRcZwrGNxN4I}lVdji$##g3{~_#3tP9%V=I( z|L7I52c9#E>Rsrur(z=1Uvp{$$o)%1Bqh^mK%uoydv&w2m9xIqI9W*f9O=(%r3GlN z^`NC{4N7^6UCK)6>QL01R1l!0zfg7vfSU7Z5rayz?8$>JrzMVOu^WTPX% zO;$E3G#}xirAsPUVNEZ$`XY8GVl2_`kEmEqtIb?#56UR9NjB_0Pe+|uJdQHvO!BLS z*RwF!Lb&TUF)liUO5Vd+ai2A`tt3}Oh~*~$OMDl>ghKks91Rz#N_ZPThMfvwk0jd^peKFBbX z2e%e=X9~F5kRv4rN>S~_HbygJId-u))6FwESTS)!ggbG%4uynA($YD^p!BByqnWnw z;K!Pnjo0lZ)WPk$@7NkteT;C;Yw*|dViLXMAvb8gf%6)2)aT}WVLG@-ujxj`+M(QjDO8e?9y5HeT+(7!!6#JF8EebT_f#=lWCRbrZsLq z-qF9tedGb}%`0>q@IA{qQM^Tu0Bf8H)2TxKZ=SM5M$OR_eecE`CsA~3UkUdTa=w`{ zj|e{hankcR&Gx`|KDFE5<4>$TZi2vhKjCpcN1drsi?U}YNpL4}?lTLfYo``l7}5oD zF4?i9Q#@c2@S-mO;EIb_oMP^Vn$>+`4#`fRB*Yb!5dQN^l~}-V>47p*vZPg{Vp4I6 zaSWO=k>F~DqNzUsL{u|sY+(`aPRR^Jc9}sEM4K|}0hx?>5!X=D7C&BuqBM%V_W?!` z=erC%6+(?<%^F*@nX<+tDPqM(9IZ2gqHqVE!%!r?lY!=l*qdQZtEez;D;6ovYgIZQ zMgzQcb3&PP#!L1s+wsN<5t+`NivmKP63zwF#rbn?9t|h zEaECMYldh08NGnr!-?pzr9-w<=_dj2G^SU zL{(m|u8bJKvxss+xXF~mxKZC6SW`w5Og3W3JX2NmfscgK=5`*@a%V7eai(9%ljoG} zz4(SGa4?d*n328jp&b8m4Zdp&*Gb`tCPF4JsrQboW<^)kW?icaXHU77^%E~w7UymI z^l`^x{4p_KB^~D+YOYoIoW7}H(m``B#B&7@-=Un95hRbgRijdyKlZ~4m_eHsNN*&Z zCA(3WnN4tti4F$M!6jRcAqyd}#xXuK&_9G9B_d(4LkooEH!$4frYKe^WB+>enZGa| zL74Ip&s3FMS*M{G!P^I17b-qG|Z~sjS!DT<&a=>&X2h1yR5Od z`DdSQKuc8*s(Ls;fVST4Ff!GX$1c?|xnCNf)0;sw(a=06R{4mP4MFqP~IG1gaYk%*wRSC#ImZ zL~|=4SeypZE2b`=mNLfi`zZ+HY?hQ6`7v|&2qcnchSHmyNnZ+8i8itJj0*hx!e|iG z(55Qq_w)>QsBDUHCHXP2Sk;@9;V9yQpI*~Fd{eK2K5cLDTK>zOM(%<^TFAPippbUa z2d)8Dq5_1Rp;^@C#d`_Ao0ze#fO> zI?8z*&SzU3@O`!_9Ae-s=JMMa+@va|S-7XtuC=5&n;X1JVfWaya`yGD))AMe{pgty zW~5t*a$Q?#FIn2DHs|BQ06H$L%aTj%w8I;14-;x{tlsCT0x49zI>{0AQjZ98Z}mwjRG6Cyq>~ZNS*l$ zvvAzzNc}^GXuWL`NXqvvN6LASlv5E4x;){vv%wmvjY$ZjCR0fNGC`MUYwysHP-psi7(F@b!u{`A&@Q z)Z`H3(C0mJw38%xZ9p7YX221wA<|frK}D*_Ptemue{-9@hpXcG?yYi?af51B03SMs z6$d4Wgo>XzNC7#rCXo~vt^iw20nS9J#RAu3!7R!(OSGRWa%TOH3P)RC{klPA;A z#?=!^!270gtE#r0;A%KyHSCB76f>q_@p#QZ@(3HrPWo<}-q+*7ZSLa+nf>=wul(ED zx~{_L$8ACZD_{cG8h^THJwRSX?`S}R?OUjE7m>RCcADskr z6^t?r(W#Ce@IQ$ji-AQwA8VW4_Kw`_odPnAK5ty1YhxK3l02`J-?Gcemc4k)*%d-D zP*r_|Yr?KHqpSL=b7C8`_KX}H&G%8~tRq1@j>;GeS&4eajV82%>h}Uu(g{=veq5+U z$nexAMo_bEuDT?au)&PC;8~(4d_VO{f2bY0A@0BhJ^KX6fracOPMUT_2GO?3QBZE8|ckq z4E*@g(A%8^qeNAJC_nL31ssphvM``_%E08fYStVaMqG}C<_-X6b65^Sq}|`bki2N- z0$K)VPn}uKRR!c8%@xK#Ctu7|uoRn|q5nv9lop#RX)o|RB2XO?ZSNdBY}yF zwVXrD=yRYXr65vWKqm!;#%CW0PVzh#WR|4Q@qhQOLNF`Y2jJpjYka{vZt(E=Y9q3zE0KSeL1V@KEu1}5_7|S6w!gd^G9Nen)~#CL0&qwWU!7a0VzD3>7 z9u4$kv0|Cwt72$sWb9*zI*NX!sxcX1Ok^tmmc7B?qtjrYY*o|BCPBf8w9Cc9 zB-y;KQ8vt_HfKgDP+6pcMNxO#mWH67C>^4dOfk;y?vfsHtr4^z=g!WHqtoT*L^zy9 zqXIacMU}wwD3*K%{xu0mjY~xXYD+Od{LyqJo7B@7;?b^RQ>8E$NhZmo&V4WBCzJNu zqrdg39uBQ%%kv9Xzb>9;l$1qdLmR!gpJD=d@O8v^N_H;c++GCHfWoOKZA5m%CSA&5 zSY-f@G~OI{YNQdjr3X8 zbDWeAZnr3+F;6*bZ86KX#s2DH$#D-3G2pT_cND`}0>g2pr6-xC_Za9qU45J{2dvlO zEynM)Z;~w4nWW;HQqE;jsYtN7EC7z{Bg}@d1mv~8y>6f-(5$#m=}AjpAu6nHsJgpl2Q76;oKpCC0GPT#lGLv9eUcXa*|-01Xp?D}%HcG&uL!CFJ* z%&~?Y5)xPLIwPtZf(kPKEc-T3<+U~CJeKRe#|}{w>)g_y*5%&}Q1-QJ_aa7UX|QxN z^0C{;!<7`id8vuk2V|&Ms;XW?l?^${u?kqr_kfJN;u^?G_5y^gD;{BP>0@m{vM@ z7sd2kdac`(?6bGA3U5@p5fLrrmyFriAC-JO1% z@ASVTVD9m&KQCo<1p)2J8-F=p#t0of+D+cR74T$sIKCpCDihl@1G zTYE4B5W~%~n^ppZD-zo)B+rs?ZMN~n+`hNUMcxyL$ntM!%D~#TEX?NlRgLobqWU)! zjM2Xr68+qqFHC7B&$Qy;G|)C3tZ6%ZdYM68A_`nZrB-gE`OE-$r}CQKlN+k%)VAyO z?e^|W%79f*u0%@Tj$D!-GDn zCO6z&mSIEt(Rus1Zx;jsT%hl8ZB@UZ?K!Ysr4Po{pVsY1rlbAN zZqC`xwzyMQk=W$1m?VWqKt>5u+JPTfIT3~7NmW|d^F@hU_V9J0`uyXl*&`=DEhElmLv+GC}I~Qcb^m#@nN4f zMNH2~b{+lVdK8Qoabd|A zztxSh(G_47qOwh7yVblpHzlhh4I38dz#7=fchB}BgTVZ3`i8zxB!(R|zZ$+T7w3omf0j(o#o%M^Eu1cskk5)~9>>fK7(0z6D<$3O?VBPVfQHKa}FP zcPjkUC;Dfs=9JPi>t^m=mUimi4Y$7iHgp$TKYc4sc=XfXrhh-$DHkXxFy^c`ol}tCE$5(%_Z@HH~mdtSET%%H^U$l&jLri;<;w!z5 z$LkL0!yzm*y<42RFDI1eO^cFgg*)&%R|Y734*`B$--r}&{s)quLVgfQ5y9GrDux>B zFS6RVrqVd!{lw>yxAlktxUw8FynesV?zU6Pbt%$!bznNdwRy_DBsBh4#X?{`X&m|^ zG|na26&i<*OYc<@=GN5BIO&_O1n86lyQosFqcw+xSVCcIlF z{CWyyB5-9gfz1>UF;%W)FIY@&`oB!bsN4&o0e{ZeL7*eJ!MAe5{c;+dFZykoRoEa+ zfBRu+-zng1czA@nVE;v)r(_HYI{@LH%(PqMS~ z)%V5t+~HdFOYlqa5AZ8OcD1#E$JAj-1vBWd1LF+kNbaqMbUf|z0wxFx*@_ra%eGWN zEhDnT_JI04jHUcGt|Sp_V0LaB@f>4qk&u}bbpE#i60ft(j_TgZEfBQ7Somaz1|ips zxa3(G!8@gzI0`2jIg~n9;{_emvSOA9Oa2u}l6fCYB>-S}1iQ+7dN>5913>(b+ac`r zj6hoACwniFDGeMe*!g2ths2(^qKsJYk?P``L^k&nxuH(pNAWK?y?hO%J?si~riSr2 zfb`4RF<^i(P5SFba~@1W@%S6uT$tyz4Z*DKO_I}`meeV1IRMOecwybaJo3?XFewkF zPNIMvtwd^=%_dyqtQVq5!s%ku7+zBEOHC(-$vL=CdZLa~Ih~35O4pi&g@p3f)P z@?sKL%dLk9DxcFtej_CJO+Y4iaYS8|iG{rq_-sSinIQF?Fu$|oO_)w#uNjV-ByE8N zrsqqwt%5A_W(cBoq9M=M=dh!#dmgKRboXjS-mQzEvk)0X z1lOz`%fkE|ya3R~JyP$;8PkbHu;FQ*X9H@AL-^61nIYn4vMS2cOmx4CeO&S6Z zK?nGkBs3F8aM|c`x;x&qz4vR?e_p)4{JC$AhJQbZ!Hu%mtT612LonmbrvIFz>MxinDY9yf$7f*BmZrHS|Yr#0C2E@X6uK$y6E&cOpn`WiOM#Jkx z6(-N}n+b`%5IIWWsu};&no<<5a$*cAlAOFj)QA9aYC$>gGb-KM?}*C?$?#P}NgnV; z2QIPb0uydAPu!hudfbzm7}iF{nGEpIShI~^1T6||9e1lmTIZVMUsT;;;<O7>A6%b>y*Sp>E*j;B0;~bhQa3KR>r~<16e; z1zkFgYn+y_m21pbSeoU!iZqAuUka}km)+M&rAkHPh%aLvB~xZv3f0jjuH7lWTgd+p zhd_A0SFAh|D^KLc&5V-H(yE%i0ySrznLw{ptzlq3*pyl>mF(GLU8q>>AG*R~IDwo3 z2gkZt%w%nD!LEmzW&QA)(Iq z79cI%QHpiae{BaPRxsiAYgtQjW(RWw4226xrrZXYbWmsklPR|Wc6f9EoQ?&H6K(_Q z`Rf-4Td(#Hjx1<8?KS{kAM9G7;H_*6(RO8^%s5EcRuG8b0NrMjS{NzoC5lO=&p@dH z7I>i~s=>H|I*WveD1{0V42D5a0Dbz>Q9FjH#v?+P6xQ@-SPXH4mqWrq)K*#;#nnk# zHOeqD*s#5cHx;1QG>`MEn8Yu(#uz>!u&JSOa-4lV~=+31$(+jN(rKo`O=nqH#uUP zxlg3FfC1ZOtfA3)uew&$kHXAwyeoOB2#HdI09AjW`^9}PVN^90F%G21#-d) zEP~=q=e1rg28oOCmVgzUdl3sAyrdz-=Ks8S^-B1|$-pC-5#F-{xf=VYkiwyF8pX(K zHQ%6azo1{YKwKvHPBqgJHn)I6R3@_Kn>iWfIm&FLx}lCdGVXllUxOlXW=qH;k_mP? zyM+Z_OWbG}rkCJx_})r&;@lHAN0EGG^h(R^FQkOP?;hQ*+E-ms(h zTZ8PqBRg(elUMSYr~7&Y_;?dVR%V-Bf75E-CE*D39| zwwPJ;{lFZ4kb`=Kci%bNp}BlwNsY}Sl`Zm*X94)JSRlPw$Ofnlf-dmY)xDavlz31P zkr}S>v#{(sbMN}gB73zf%9ixlz2xN!4zbQGC59bfYdPRn<^7`{#uP-^>%t6p-OGky zpl)!JD>7^pvC-1ka%z3+f&LSY6F0BaTG_ae?Wi$qsW}{tqhTOpo|1d>Yg%?oh}``2 zc?dl-$WXKB$C)ISRlJ^VQe@uo(B?1cgS_&^02$5+h{D0=8(l~#@g7?1okTsl7nKoB zrnq$`9W0L&xLf45y*nDUsuc{lQc>|ws;ovt`nvdSL)5$Ql|#DdT)7J2R1}qlWw|I% z;XMngqQdiJU{)x2iGr+@h&!j@q$RmfH?>q^=_iFPBD#b0#|nFT#y=F$&JoIcuNLrcr(MINe|H%@*ylB& zwOP=7m%GG~*jPT2U`qoT8A=;MphnDD6r&yUKw2PcEr3Zli6^i}+RBf5hw4yNP?%2+ zc3@AduFi82Kme4ZH=4BQk@6op|rq>|Mx8Bt4laJJJ7 z-l$fdmF=P=e{u@)Ef?yKk|bi5%)AA}9JyDj-<63d?ik9qPO|*>QmZIMB^#_P_4_11 z3w1h9!D{-j()f0XSZ2UffHv}bUWm~#N#hC-04?-SP|H?E&$G3%Scgwnq=OrvmCw9@ ztq&E@VMc}>U~K{w0-{x2X{@YNvF+QqHa&3~MS6)UgC$n(8p%5h3Ma;fObZxU(;qM5 zB8nrtsIcNSz;TFxq0BC%xOf2P`6Q8&KHB8R(R*~fFlL7dGNjGG$QRd8x%e{vS&(d8 zHfu?cqB%w_S4o&{67C=J7b`DL%E8VXkEOmxWH^o&wX0{FpSM(Nsq2Il^$(IZzlv*p?me`tf;eLs1}X z9V{4abY4N~Z@`%15d3@~8!+jfq>CmhFrJE2Zv&vjB58P2r@x z;FTMT`Z$~nl3+BDE2B}mox>PUJU`s5Tj(gkU*krms94&$jM+MOULGE;@9wI76VDAF zndQRxb845qoeP>j!}tMExZ8~;A?x;!{0OH4b~Aw*N6FYDdrJ`yy!ea{0(A4}OaC5? zlGgviy}{fvHkxp%Y&7w2^KEzIT@$6@ctb-g9bXJV&~N&an{1Yr0wTrgQM^jOD#UO~ z>b`%-pXHS4&(sXH>qBg5Pd$}*cbboN8(XqnWfVloh}0If`aMMh$)1T zg*Wv^Xov}~5dbTtEJ0$(Q=ClteWbS-=T*Wuxm1x-8@3J?t|A=ZQ?+v9ZiJ5t*fXQc zB}<^q9i11TRF$n1;QoG;!s?BHX2iM8%b38 zn$VLFNcu{IPFAW+2t0LG)5o(VZPY;g2i|)tN=su+jILtPW6Rk%*N6jw<*bmHu`JKV z6FoWz2W+ax?F#tPI}@SM9w2j|=VLnj|K?7Y6g!fZ<5}s%!vIr8;b$d3paz6E@W(LT zHk*2ariUB|?;{*e>d>Vt4a1*4ehh_g z&WtK2E_AAOB&O)p#Y@{2I2`GkDq?&H% zHFG`*W3TU9atWGn8NpJ9qW4r}Pdxu%3vH1HVYNW^5ijlea*3R=c_*-g3@(+_7#0D5 zr*rI}->R0X%rkOOUG@wEtXaF}j2U|gEeOUvaY{U`_5(7E zdW1$KLFcmAj0OKx8&|R4qBxVC=r|Xt+Qz`8c{53i4Lm!MLL%OuzY)j44cB)QEi<+S zO%4hJ>tytXFm|xDRn;i&^cz>de;$Xa7xdIYUR@kn_0CW&)x^7S%1;ult8_E)`umN@ z0Ch_lr8UK}SY_Q#XPYPcs5`mS7oi^z0U=+KxG&M+^IR&|cuIIPKpf(roxl&D(Y=`l z6ZDtN!3K^iOG4>#k|+?px1jbIu5>!cKz-zgQUlPHumeUu{iLpsRQ<$~ODxp@xIyRv z9QLO!+V@GJMwiSEQFuVXLE!Mx^T%{^iF8#>YkI-*{31+m94ie7Ttq0oz$--=k33*) z4mC<8ZapllOW-H$#=DYiq0JrS1g;_uW{_jCq%aarn2dsJ>fE1cG8&EUHEzOYRC(9@ z>Z|7Iv$*+;3NBTe&1$Kj#rB1MK`YheennSQKF0mXzZl^Xwj}$~W3k>>k$cBYTir(; zcirqjvTzGG8mJd=RM^`&JluKtLY@y$x7$7;NMVtNsT{)z_6Rp8w_FLNRc=>464klF zD?`I#P&%PA#p$q)P20hc=ZNw@HQVQoITCv>~i~A zwl6fzgjMYsGxv^Fcc$~p<`*qnlT8;s^Z{ilLFkc$oh}?>W|fex!d9VTP94*G6oVwyC?o7EKSt;N2O!j{C|ixoU5orXV*=jr;Kiww?j!$(KJMy#ZO=yM z)}9kD@h(Mv)6_*3-aDca_gNH5hW!{VbY4}Rvx7d3K~<NPUW$B3&rtY+wY4g%x4cWJNC?-tpqJUwcLiEr0zDv$saLnV4RFSq;FN6} z8K;Jp${~#H6Jjb)I4XUR-vnex82q1c)o>fBwDdBXE0)H=pII!O$!x^Zmc-5;(0oLt z+%sZTAEVog=}lJlEG&vzwKO&grFkqX;$~LT4KGbYo14p*Bvz$%6P-#pe^WaI1w4$> zfH{SLBC*ClX}uMvv+jaON%!FF>$ke@q~A7^mC zFq-46vivreuE(TXg%&UYi0Y|L)weE9M+m^9^fS5}bXz=!>LN}owN>nUCMUZPx3AX`ArjSmTFtUZdq%xyp zk6+0)XtN26L+xn_;6E+z$5ee`00)5ub1qoKYsGq8zKsl@HA7G>(d*So+RK-l_0hHJ zBzc93l}Yw~+T1zlSW6?VPC_95U}ch2mS!|cnsNUe6)^5jeU%aekIgClu=>I6loo{e zY)T7gNUZIx=hXT=zV0%+E*0S%pGSPRTjgFapgU!B_%TU>LA)rctd!#Bx$Zkk{nYoz zjg{J>j@*()pVqVn>r(RCS^C$L?)CH)v$<|A-XAm?^hBd+h5<%>Jj5njiG|n8jPLoE z%W|<8!k`P_!u)*q%d+186Zgx`7#U&+b5g#;C9@(^>FvTw=Syaqjn1fLKFKRL`GF-t zsEAc@%%hbG7CM~JFo2vV(Wpb0p^&uLR8Y!B;~}jY<(+VmN(lR>f^*sTetKPErVaQshLY zeP)@0NOc0Ik&R{8pT_M~J}Mvg)_>Znqh$69_pVp~3B#nt8FW613p*!>d&wu{??|*N}F=+8?Rrz?Dr;ro`k3s{Yx(kqN zvbQK>^4pbnYhnvs<>Da25SDdt3-(EJki&h6LlHFv^rhTA&o;CQw^ z&q}r`DAQki_V}DAbsifD42Ly)%%fzJHF4|FH~Vt0gRT?s)~**Nt%h}*=X~gJoW@+> z=7NPTQKjyXyg+4r4^oG+?@;hovc6MU;LMAxQbpHLCJhQUme|QZx+jLtS7Y?)Kbj;2 zL#i~4qQPLIgEx=;5$hF{>pP9I>`4G1TUEQlDnX!DqkkZu7**MFZ-A%VOQUVnD>){e zuCj_WYPa{ll)ginqt2<|8C|)x%dY)!yDXJAh^I>{X7E;4lD$xrR;j~uFHh-;+c}YM z{Q82pl3$!N`GU-APIrs0F1LQRI;lams=D}j)jp?~9MV6fY%ppTu(vzpqkx}+LuhiT zG^^x60=OLM{jOCdvo-)Nef^<{*gqP})zS?d>Iv;k&vK-9eQD!wFym)X`ki9Ak+!W1 zeZPnH!|NNn`y1=KuyEy59Z!PJ4f&Oa8Z6$RP9JO=xT`W8HcIQSkM>bF31P^`JRs8e zQx}o+LOm7~!NyLj_{XjF&8-6ohhGXHv7I9o#QJt#ZtqKSg^xLfuxj)@US_B7JZ5;9 zA)XddfZ2z#v=Jm_Dy7mV%8;?lYFg-rkj(sG8b&gZl8o6w;dtd7uJ0YK9}sK|XK?O~ z*?;b_4JkuJ2E~-Fvyb2RL8YA$nuu9?sAk z8@Wg5jg1z4dpJPf`2bBrnTM5M%vl;*SaBL$DnyZ+AEXh9Qs-z+`tz>l(>R~t991ZW z{Z!g=OH%UXQ4ZDVd%KD+pdxy3t$Ys@<(_Wfaag}7GGdlePFwS?-S0+N3`XT%xc?DE zCg-*tt97s1ah@Ko+4HX16{-+^*ca@YVi<2wi)tSB|BUj}VTJ4VsnNnI_G0F&F`iIeCw3bn6nFF9M% zHhg4=d5*G9w4EW77;=B(>RMPt5I&s+{lr=hm3fNd*}ll+uzwMO>cR+PYWO3f8Dhga9?|MU zAWW@p5va}zJ3wkEsMj(cvU>w|j7^3Rvc^WXD>H02R*R*Y4i5|accQuA@3xi%?rxHX zvWIW%r?Fb8e-QPbMg4dAF3BfO7ta3pQu!E{^;QfRs99P)lou8%oV%=CV3gSf#xei2 zIjjm~pgwa{9e9(vq0Tq+i9=~hl~Mh@+%C7L4q3&@-G-&>5+L=344;^yE=boWQ~HU4 z-L+NA zWyH$z?^Oq=u-mdCiBe)`S&>~M!AeSx7Qd$YG9xm!VG*@xXk?^kS&NpBaIzrT#FZqn zZkTFO*Q#|^FQlGNsaZqcicT&6*k=_-%~foOcAQ?1m|666lwI}O>nB>59lI4GUxqkb6@0BRL3-eeL;&-s8ptW0$q zXW~jr6v>iBSkP}=JcXZ~R?OjVJyrozab(jmnedyXyN^RwQ~2=qH}S64Y*vqD=nOG} zor}B}%Vk0{wT4y5N#sd0Sbbsq&&m1oIyn4#CQaTN$5j{)8I+=xDxhseRl+Q+4nt+f z!K`UTH6V74i*D5K(^Jxm>9}#YBY_ge9^5d8}CoF5}6EGIZ3veru6ol{pHBJ(>bK+lp#gQKMOIm4s-K5OiO*W zvGPbd`-|oIi#c?uBWZgyIu4_1gLI9h8#p-ZvW+B=0sOec_Lt{@3QocXASxW<@z$HI ztv5#k-5ga}rPb51MH;q7D>8@r1n9I!1rLweqO&JsYv=%|ba=$0I6xXF^=>fk08rFfCzt74@8lg(n?;OV zsqY7HsFdeCvwJLwlSv{8dJB%YFlIww%CThEAERf{!6m!YYLw>a7^8?mqlOh^42>C- zZDeeD-?XzR=U1gUM$%`-wYr_Hb?je^#`dVMiR#4ybor2lSpZ zZe&#qXDaG*03}2_3fY`6?vA1WBbBf%MgvQ4AvCf?MvugPmpH*e6upPmj3@o%a?QRl z@p>P;Fu|xdQJ?Bo{%EDk-t>gwbeRN0qI2g4r-q~=_EeOt%gjD$Xjk(z6|F$hM0@V{ zq@6vE$xz@*2nUp6O&w9}Sm$|4<7klhC!Z1j@efh9cp4eMMRIy(p~km4AKq5fag71m zP0ZYj&XS50SJ%7Y!?rDt)GV3XxI8@%!X@(GZE7Ir z8}l5mi*1i!yIBYteghrwVlXu1`5fqrWEnglo1}B{1cLAZ$nkhs&9GhhhbePxl9Hk? zofi`}lXMGqMvB|5b64KRt*QGLAmvos77%CjZk$Uw)`XQS7;roTTv@l-+Q=N(7BS`1 z)!S_V79*5%n_z!r&nzinmy#^;;U~2}So;QlzYk&6d=PdA6F5127bm?SYMgza5?^ia z>~4u#ABamImMtWfO}Ls#QeG0}^YS8~my~byd1e>-i@31*9ESwoNCjwSVOUns<ja~SInH>9K zJB9P8A#2@$PFWi*JZGQyD&Sihj?w^S`NEg9U82(keWKAP*tk&w;&vHL#1IH)!h(N` zda=8;_Hn(A_ou)_-D3OH3b42JT9%oF)TrcqpSD*PoQmx2;tx3-0sDsGn+W2}rz5*i zk-81$xwwO$pFiq0D>Xe<@H*&0-g!?Vq_dNz9YCmfEzrEEHrK1|)q8LCmS=O=rhxz$c#gDf~tnU6#^ z_n-fdb#CI1Nc|<+RQUVdd3fV16#bnU(c>VU8D&FY}%jsbCJ=32q|1Pq{sqIP&U)JJhT zTZFNB!``Z-Dk|Ltv0YFF2%gR4ocGcVN?G~5*x0a#316j#C8>|HPn%ZdBR|mH8%4fC zjZF2CfA8XdabrU&vc;l4%X!x=FjWn?+wGj*(hs-nX18-r1hms1c&9wUc1lU!DmuC3 zxggaJK6VH>Zsj_w)c%ny*bNtx-F6F0R0qm(HGh#kuvdO&2D1}enser5?#t{{{c>M<)Iq|W!0%*>^SX&2{;5W0l1>Tckjc>la&az}m(d)>3_nU8TP3T=P zWI(Ovl1+Q=TafOJPf?PdBV4zVF5_l$If8?KCK*q5m2o2)+ zcySU9U?|99_F&^sJZby`y#bE9B!6tgvUPDXp1=X%WP-FOGouvli-p>_8>4i8 zQ7l+FYfD1DVT14$N-jPt;akw95Y?jQi-i$n{&? z#EFhCigyg8ZG(f*pM~^((-ck zZ!M?@28uje!q(@LCtM2E$pMW$B3j;f>|H`r|Lvoi)4=y)9!5as(1#|)9xF1Rj{`W_ zGU!%oS+<3}O`l;bYqa+}^ROINA>H%h#WePT7XxBt6!#w^J>YuZGCPOP;BLVUNN6>U(jg=j==5Ok~fr78DW`2wTuL$DFeeS5?rXDvv>qU+d1zq z?3Q

      (%DYL5n>>+fL=Ldj2S8Lkt$1V!0uZa;3N6CZd03z85Ny7i9_y;lzp{hu-W} zJ?sMWDXLp9H>>1vxfcbA4!=P9y@Ax?l^>kNYowUDN3$08t_huye&Vu=8iM z%3)@c9lHk~fKZb66KpU#VlnoAy<2HK&?Q%LIUr?!RT;T=K0$+a9)asF$*wE!n(v^m ze9qV^&zI@__eqratx^Y`d6Be`1i+ANlXX52Rx4Ts)g~b{O59mhvz(W*oT6bLhgBLi z3izS>&D`Ew2V+wYc~6?Rd?wKtRU2xM%4(FV zW|)-u@eMkSmp#S(7H-!YrtiY;ez~oLSJo&%Z#j+clyvCvTSy!X)C`SvKM`)4O%fNk z92%{`q=cUJx*wvlQXPl|oJst!ds#<^5-b@7ew|DP?8*7a1v;icM-|A1Y9n5O3w-bf ztx9p(XO7QF*a?cWx?2KcAY?-#%7joweV2(iDeHScR-dnL{B*dxe)wan+OA5>4T0Fe z{?S2uA5<{7T?+_&=jh<|R!hHaukRjiwW{+~B@(bQVXB*tk)ni6?n(zZ)Ihy8;AyjU zn}9^7{f4?#sGcdbu((i;#AJxO*v5t#slbm-!}0KWM}Hk?s9{F0tSxl-+E~xN?Jw{b zpNWho;>ig`DS=I>7ini5F^39)6VJXj2$TAHcs8Gm2nv9}(O`#QtP#jj{nXw@Mlx)= zjuo)KG7i+odddVw>ncmbFgU6akH)~@l>XzZ`s+8;wo^FoQ{Tw^HQN+VI%v`613huP z2}g#yHX1sv??D2p;>6;3B==N13`{6Ct9Z1RF>y4<0;2?}91$m#2xKD~;gOTZnL@zu zsH#dMdl1jfwjW53XVtM!VE6*kidLwjykLvEBX>U2&FJ>B4g1BH?$$7VgS>L)1FnsZ zY{|!smQiJ7(|Jr1vNVo4EJcF1) z8+Xsz_S0et4vBT7UI_2vQs)H#B1*6kW~v*{jQhAlIQNfCxKNS}0h@m5%%F)KO7cE! z(kEdIg7-d4qr8zAAb(;bCRT`E3!&IxG7QBx%VL45X)ltIyaaOYn6LuJTC)vy+CqkwIHN0KRXh@p#S`&V&!yr)=N%q2iIZw2s5Z`!6U_z6VZ5*_$4<3y zvJ4t$RcS&Ov+U6Ny3#mf8x#fPFoL;YI)<<0 zAU;}*?G^9k%*|doJ$Mz_pR6N^!GtdJl{nhR^{zDbsYg{ErYOekSl%g=dtQ7tjFTRz z)iVq!$=v~}G#Q6dUaJ)C4=3TPaU?dr{@OCAi+tszhfRAVpfjj?K>HvZ->d8(;@qIB zr0;~zz=I9qXo=BoCro@5t5M@P)^K72OO$p+sN^7WLE4;LOd%mv~pVM*l9#;b6Aa33sp&DW{Awk5wkry@u zN9kB!sxV%$uV$NvbwEb8VKg~CQ``0_xt~?^otOwT<@St~3hdtMItZzcMg3vqgW(7- z0eT8a6e1TQaT<-&$P)U}`b#BgS6DEAvNJ7>lVpEE90Om4zSHnt z%FqQmM0w-)F{(KGp}b4`g{}t|VQn0ey8<2|b`HgtdcdFKt=*}wLD4FR@fy%mji56a z`v>V^Rk#$i+`OSn{(|&~T_)7`UM*4g{?S;b{p25w6Axs?X4Sf%3&bbT1hDJ&U~f$^jD68KASL9J3Bv^i5-Vdd)nLYgT=) zs&1DJeO;gtt)|+Ta;){~lUW8l1yu)}n>`g9Qz4CZprHWGa+2SdOOdhejjohUm?ksh31=-taBa zn>RPUC&Hoy7e!fUce*ONxG0F>NNyzGyeaT{zTw92fsCktgbpb)_G&lrl75QpIQ(}C ze37@YfiCT#g`$gT?50?3L)uU9-vRu0G1%yg@h`1V$!S0MRkxg9zj<>Xj@4~6aG|&O z>w_y}@6Nu}X)p^AlrqnVOrO`eIxcj#)um!wxrKjqM5eTZbx!+ZJ*;D$*Sc6wuZyI^ zgb)Vh{z%(@vtRamAzvESG|%j6^~szM1ntfQEj6%T79F!zYhIIc-Lv0~vRSy5@1Xq; zqH=lNn#DysW=r{K$EgLf_TZ;I*H62`Ek-Eqsa-w%5uVF!;r4^?@(17L{{IQz<@asY zY^{P@8#Y@o3Qe0+N9n(!aWi!Bi!^Ue4gEbEIH#Z&nK3VYB*1=qDaO0+Cr@3{N_!OlJism_od~0 zO)ICUIYY@jJ!eRss_3kWWi_33$^SA{XX(jgbly8wN(h3r59Vq&^rWPfNp^!1R0=Vy zb{9Bpfsx3VBEvxunmv+iXB#VTA787Ya%-ro&x}(f_(zW1I+9gyRaYC28>>~BI`)BO zqvVywqeqYaQ7t+D1_@q{xb84Gtr4G<(GSQTxnRM=rU_HT?m|u*u$eYWl8bOanD1pM z8$M3cKBk>sar6)gv1@A>zcsnI!kmM8E__V(G@6Vy!GuTmF{?hz>8ZI z>!oDrYgx&}-ZQU~DttU;K2G5r5tol=fSErtR9=n<4#hK$=7J1NJo39i{}MPI-<=+7 zaHh76<~7x#<9s}siX3SeGH^l)XYO!&MX(DluJkuT4jqs}eL;FutHb{&bOg%QQaua8 z576kIiA4cx>%@wDN$fzgn$VNk#0!X;djb3;GE|fqel8>xz5< zj;|My-WX+hCOGH3IPi!5@WdZ4S!&D1O(ZlMYD2LZ9cL-b56Q$THH>lXUyx&679!x% zIs<|iugN+iK>wQ5?=+o|L#eVLIjoUzQWrXw^Pm#SK=EZ{4AHDNPpikzo=JJ-EeHlF z3eH9^(pihh1?IckeGn{Q1Y#}Tlp-{Kb@9S+I!vFhUy`PdW5x2`a6uXfxr_1k?4<5S z!E#ieGOLj$=?;sL0q3kmd&u#5)|E(kd3n)j$#qE2f9x`(DyObOnZqnXRYa{p`95t4 z&Wq>3PR9ce`+|qn#M3R8my7H_8c)a`c>$!R!ko=ZN3nGh!uFJ}TC{%Xv0%0XSO?Pp z^|lw@mX9m#_Ph3Cv)yj5KqIj3L$sT~+oqG~gA5hli--fG@-XA$fG)f!_$#pTC|u`S zb8E;}fbGI8K{8Pe#e4q1yF|I3M+*Gpk}rhu=z1ex4!@}w@9A_;?K-Qv$Iv2#*imJy zHkp5wruw~g!=Nf?5H&$n$!Y1KsJ5$B{3ENT*Wy~e4-u>_uiFjatdzi-IvqqOC+y}~ zpdv1m!8l65aiF}U6hqOj{-asmxl}T=>irIx|Ofsd)Zz$c|kn%KJ1*?`yj{?~je~)hg5R0`#ea6N+ zDxc;0+DAH44BW2=C!PxKDm{l|Bo!@M1pg=cpG51k)b1dRISRa9&H?z_(J5!T*8 z2`f#U-;j~5XB8*kZMGQC@yRO5)4(r#S^U`K8xGTrufeDfM$ z{?fo@lH;w|{dYUtJC8O{!Za-;NZ@?j#eJM_;I1*tn*lSZ^xyYf5<^} z$H6FR#ZB+}f6bHY{xuoAG-gR(P>X+9SJYPJ?9H5>vGw7=yi09beJLyq7b!E?q(rhc z-+ueMrH2|T@$pBi%4g_<8`wOZ@=V?xIVxQ+#EuE08ut#F%4yAwd9fQsBgi+VYcFNP zSd9f1AZa|o)Y=BHkH0$DfAMmCZ>zq$b9lrj=|9V^Q5nqQ(4E2`JxRB-dz5pl#;WC5 zGh$tiMWHOHy((8 z-?>HXrlKN-yVyTjmn*%X-xsOODNq?fr&XL!n82^YdabBQ5MPTGS~O}>uy?>Hlx#v_ za@S82rqFtFhu%ef`6_h>`JBwkLewMz;tfYcT(gLdc-VRG`y(-kB5Rj8@e?{UDhfCq zxv~ooThY?3G#60VmX+n&!s>4;%Zqe6B|X0&MCFm#_lGgZ@`N2KlsEcRDyP>g%Nf)C zj7^&zQ5Nt^SaT!nY15lDcchY8EV?T3$W|DdYJ{Ak>+T5JdVXn1L#V<{(b*3%v`e}P zC%!b7M)5zz1eH3wM>fN>tEU2K@ycnH6MjgHTV<%`zPVw(OG88zJx92EmdJ0(zIeD}36qxz;Oa+$C za24FT8QBUo5Kh?~g!>C}7TCLMxeGa%K4-5~>9M{bugivB(mh+OSN}r%2F>|rXE^fE zE@z4UMR*Rgj_#M~aJPx;xDFzuS+?U^Qa%$$LK8~+*1hl~yzGjZ67`+A5`#_7_gnK3 zJ`UA2oC4*U$_s4Wi5Z|ad*YLzK^65aQC;vpoh(+<1c4;32s&MLtE`IB(lSyT2c5R^ zd5x|APG-ZuXFg-=Zj8p(bWUUIMy$rx-FS_yTxMhIM%>0$itcZ@{KnQ_o8j1+iQ|xC z!nrGdCDvl=kKrxykt}av{zce}tUUJoW|C5tj3nh zYiwnijjg+J8~qu$jsCUV#`fL0jjDH2>sAfRv8_^O;5fD&j$?Zo$FZHyacuv0avc48 z<~X+R#&K*<=Qy@+#BprjjpNwP_+Oo04IZT z<#DK2zo8M=S-C2MgZHWcxj{ zli9mqNp4)NLC3k-+qdR0=rf=uXBrcP==bu3y(w=qzjx`mouIDEL{+>TEJKsp9A zHe)Ko=4N&{H)Z~=RFs84CFLQMJeTH6^VE}p?{Tw}u}9u`q1<>Jc&EN{Z^qs$CL@@T zSP+1cB04y7#|z}%DLWvjzW3D=VDu6|f}W_hmsg%tkDd7NGs2LCq~)_#z?RI7?94i? zS>c_;(O{DJG!4?1#oKnf`3`Njepus2Wokb5k$v$8Nvqm?`&;uJ{#R?Z8|_B3s>%3< zQr8at%?`d1GPz6#K5)>pXfSm}>b$82mVwd5E68g6cU1cPd-Lz7)ni$PxASxX%^H_0 zx_#vwTVY@U=2EpP7_!cC{C5KaYA&majfRp%BX{}wbaylDky~1$m&*!oc8I$|9sGES zm*@y^#b>_|-q=TjWp+&jE2-hkv>Y!R2^72_)_#m6)YwPwG1}t=Yo?R=i$KE5knKe+ zjiXH!?DE&>cG~14y~J;~UXhnf+wo-9DQ9xl=S`h>JMaJ@>oZksGCGxWsDjGJL89sX z?k)B6oNad3QcnQbv>ONg7!V5fkcLSjz>=l7BM?Almw1sU)1iYGr{Mf zB0B|DULA>?ewWd~H}TcqIZ^Fp89E%Hk!+PThM#Y5R$-OTKtqI7i zQ2;UU!`!Agvv7IILCcF)S^oOcC~Z0;{TM4@L-srQGggPMTb(yDFf-Vin}cS+-n6sX z?y8~n{BX05j!5uAysEX9d*??_(V5Ie%*;x$|6Y(NU}3hBYiy?cmH^K#F2#CcuV z8Hc*P+-p&Ji|~cRP7-yfqpdPYF|2@6SK+XnuHMa2wp>+6OX8Na)U!9sOhs;j;ChH! z(=jILB*PKpp#9&?t2u$$`H5VPLeSy7+l|S$n?L9~3H&nYo(O!i1fB)_$1EI(5Zoz? zRnQ$(P#i8resY-;PPBLup2|4_qHf1dWSB<(_>2Sgm_YWh3V$Q=omyt@KQNbqP*O>9Yau}Czm zKS#geiq9ZAImZPujT&9oDD)q$n?*2+s~wg3yRMk4W}YZpFhy)Mpz;+^3KB4yV-qQn z(Qd?$lvte|oGA_v9djh(OT;O1IE&It_V~o!h+;%3!ll@59&I9WoHR40<&OHanY)EGvH;>^4HiMpa*NgtMaJ=&Ok0lcS zveVPaa0GOxktV*IulHU(-+5{KjGt{2xKo%-*&0zsR!Y?IwQQ~&9ztl^u`1*Y-NQcb z&b1K}TeY-Iu7?P8-d!WmnL#Z;TKAgkPYg$=T3X4rzBC<&ZcaHhf~9DtmHvl@+SK)4 z7j83m{Umyp)%O|6-G6ad&HE#14!kMhHJz1cm3Sa>cP4T<1N>#tw$v&uYInhK3 z@YbR?5~xykKLlR7lRT6K%A(#+Q4d z-ENniwsPz)OwMTq7Rb$BPPfV)f@qT5zLD%e#A=HJ1|t_R7@T{$8m|Il=8r>f(5N|6 zQ%#jJOVpK4W+r{_Q1O9UGU+Nb)$SsQuNz8V5d9mbpXa~-Sh;c$cp&EPQqH{ z_2(O#W%3%H_PSZZxDdT4?)~d@G}3Ay`=QwUpBJxQEy*|-h-!4G8bs$}G9o3D1{R-fIHMX>CLZCgisrJ;oW&cIYsF3*FeuOLc#YheKdO*nuMBActlNRIDKx&PAyaQnaBl>skT` z8AIYk#umw)m)6Sa>Nnp!V{j|0KauF1LZyv^(=%4|1oi-aPs52G)7N3oXZ@2OC*z(y zvH>$t$~(Sx*+zlfgl>2k!MF~gjR{A4yB5ooj8ZzKCiibd*>L(qeFc?HHPG`jFNq@z z^ERd9^_6E<@4A<#=Rt@9epY6KhpvCx?b>McOAvm0(cIgz`-@l$xz1RMzxFUDoOBnzV#WR6j%)D6o0arAFLERzU%LPBGT-;kVV{p7e@~n{r=%^D$7JY)_>_Vf4{!oTwo3%b~7@T@h zY*)FCN&AO#B6{!RGaKaytKd*F83URP0+>D3Mx0_OgGg ztFeN1qUTb@M5aE9c2hh)zIEZ6cyw&x3Q_3bLeKv%J_qB(rOS?sSD&t~Jbp}AdghIL zU5crt^`fl)hy@C)2fbgMwwrzX^y%_9Rtutd)xtjCUP1OM24b;)*t~WfIDq>a@aDK7 ze#B6oTkqfd*bVV&;KwmV8Rh%U*!MZ6CZdBs0O}eBU8EZn?1|k`PsWPaN-o4Kc^P!e z#|wYLiVk2@hc`ROvx4oXd+`QFX5&3r?X*FcfblkLS0AHH?5-6zYMo&IGr6)*w+*qBQE9`sEB%pOn zh9Bz(FFJecZ$#5iy5x+!+3h!a%{Z9=qT7C9-GM_1o&=PqM4DZycyv%DjKAwf?d_a+ z*KAg2BuI}||MB#j%ur1oqT_{A=o)lPbrn_a#k79q!kMj?Gf}fU(lcw2WYiIhbMtT; zPGD7>1gFS=pZNWVLz46&ysk&j;4(I5kQhZ;PpteTeIFZM-CbsJ;*E8QynvVDAL1Kf zyA-w0N)_{LJ<6Pxwmvz?Z&g0hZ@uzyWB=v$&WjGe%BYoBSGwIheTUcgqXb=JA%WCjJo5&9#piilS?@%FVm*^8PpV`3D}e znyX^(xi#gMU7WC|GEmqVpfTa`U8WfMN*GfgNQ2DyNhb)7Vg9`dfxYjXhKMu z>%avCs59mb%+XP;#t~*`;l@xNJ2fcQ?&Y|q7@AgEG0O80&KDkw6-P##%Pb>QOq5oS zGxU`06;W1X(ZvD?cetC%r?dIe!<<}ql%IteL8VGlk}`V~^P3@W5>T~k<5jENg|m0R zd`0hjKex)m59a+~uT>_2Yxz!6Pg*ZT<0dHhzib>->|$KHh=UG6L(S_2(SZRYU4T~EYo0W_<&q94`%!mA!r7Vbq9Xdukzbe3AiVR1rR4XHFzxc!EqNPp8yw-{4qBknBj)Q+ zJnbGMK|hLGC*JrOG41doPP~6V6Y?5n1z=QGZG~&dfiia}0r_C)d4>umAYI|@rXd9- zkNTv)%PGO=mttXWeP_4zbXhqt$Ja&FZ}u;Oq#%-Ii6}MYZ_!`tu8vdLZwK9&lf5ww z&(`ye&7BR@@?AgN*x8Y$6x7T&pOUe7BdYC{!L}r^KEyYTcYW3xAs|t0tmme>~pOw`re|fpPdiY9yS$TpV zC=33FIdF5SVZ&r!QY~{quPg)TFbjALjmMApp9JcL5x8~05FmIT{Ba@$`K(lpm$rBv zFYSy`te1{zkQYp3W>7_U7D+^*jyhNpcbnT?S`=GI3qCSg7Z;X|o0SXC23;v+#TeF_ z@yl;!i{fO+-g7FJ!hGkCR&`-x3I|mwdH3SYVRO-9=+yoQ3E6poQfuJ5fD;q7Fu1sR zWl*S~CVhVA(`cqof8Z!{#rHnYAu!^9(D>%}AT`8P@g!vL(mhg(iSbUXhg=-2E7&_j zGPLHvcd;!XxJh?4aY167DOkw|cg|D83wPu7ZR8TH-!w6 zZqwb!;Jn1kXxs}z54cxL$!duLdRw^CDCJjOgeHFkjlHfRQ)sNca2!;2VOH|%x(g~h zfQnH$lkG03Oq#&Eo624bFm3D)BF{0rnszjrseBQ#4tgEV-9h`IyXb4VkXqIHxOx}u z92Z={RAe(z|Lk_;=9CM-N&v%_^3g-hEUysE`6C~$Qg!vGjWQflQD zwLH(l=^m(53f*!ooc=J1m1DM~>vqkS^mNktd@xFSyu_3G z?iF{^mA)hSuoblV^`IM6DMiihlNR?GdU`(YQxDK<{O^+hL(zxm4;nL!<+To}Q^I>y z>WIgvS~#z-7mv*tk{*4H&_;)?u4X`8$G?Wdlm z8zyi4O0IB+JMkQmV(;+R!{%NPzW4>T-gqmDJ}9QM%2=zcJ3=5zN~J$%5ZwOLF@bg1 z&w)$A3vx2Opo>fxo~qnWs!IfI5~A`gQV=q^;+mH@&ccN^9HE|`R5D`6vb=}Z^{(H@ zZxqY3*;i3Gq(-44>HzJCI5+_B$j0bTdfr)ZJpj2jc`cz#Lve>Bv}$G*AToA_cb@f| z%)4fi=EYC!|0$p-7HBL~U51&SU-5U?<=V(ZPFVM~U)5nX`x~cdQI8IbcQYc4@Q;RC+wNc7+ zq-%&n-!P}2y7phLpsTp>5 zouZ6p*J1q>DrD_Gjha%+bT6s&;+_pT^h#0ofV1P&nHRjjahcKNVr99px{R1eFDn*= zYsM)L)dKJ7V@GxU`dN+*2SI;60eaWX>!NTt30~QCv9td4aRCLg7z8y**tJKi7N;Pm z>1;IeYWLFOx1w&PEG{4~wD`kJQ=3k1bk5)g`%J5f|7jd9yOj?gt5htWN zO~_foRGycQR%M2#kMaA{$Ij=cj|~VbIYe9BI(5e$+1bf}G#wnGS>uT>PI0c9fYgl= zFl^9A=DkFZDZs%v)^7C2GnzjiRBJ7T@?SnN;%EZ$D zed347Vt)NBno(q;dgmb`j%dmd33;^SriH=%G^;#Yv#fTMujOrA>62=4;16MUyu~z( zy0<7~u~EsWf3qc`mFI4&g_T!Ffi6~2R#uI(#uxpzv`Q!$?Sv7oS4s#Y%ac)|7yBH= zsjNgzN{!;P2oA2F1epw*nx;yJgh;pRQo10qbeGb7;Z^IcpFHnw^fvvi{`TpMvmb+< zfBnzL_`mA|cF0gmQyQ0*JspPGrQZBAcnKAaJmP&jH3K6QQm2C4% z(~;;F?yMDtrQKPZ^gmBYD|eA3dMZ^TuOn1D7?n>_KWSS^6&y z<#aEkM}eAha8_%F^F0uwdpa(wVg0gbkXdj!4bD3t-;F96w8}je|3MTgr%yA)WWrqT zkr}7-;j~E1 z5aIknMTc5%-0}FL_b!K61BIiv>nJ&J!i*bdqAtGsuCuxpu+Tf`8o+E-VGl<>gy)w6dZ_H4_@`YIXGdw`euV$!#TROq3#PwKM>DRo zU1p!KR+u955q%-k2k1eZc*!JgRemVVOorQ)A7J@cbYVqw+2x{6rg&09dF?e<(^ffj zx43Kqe{Y4Pa!vkr6(aV%V1V&0B4AyjQ-Eq*3N=b|ZZIQzN}NG1&HYsdb1r`zLtl@h z(El>a>!13gya%@)QX^SQ4ImOMl?4q}>{wWN!=G8Gcacq)E)>Wj+zhNQVeMMkOt5=} zkzxEKvUM;{TPGg*-IgLzIiE7%w9m%Ray^fduKV$zLXf^NhW_yAVCOy@77(0VR-V4J zRE`HG{?1Ii8qj=1{bKleZe}Ij@X|E2xjBK7Se2qmI`z-HgwI(JiJUra-gSiN)^z7f zBr{g`?@e@2OmghOc5^?FpL>!a$ie->XpXbW^4pvzNw_l=a!@Q+p6~}zq#R*^tl5

      Ip;{pIe? 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

      svo_2b@%TrM8w`T+TZ30{sXkW>p?k07Zze{c+v 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%0A5y^ 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 Ezs#wZI{*Lx diff --git a/vendor/gmock-1.7.0/CMakeLists.txt b/vendor/gmock-1.7.0/CMakeLists.txt new file mode 100644 index 0000000000..0be57a5e8d --- /dev/null +++ b/vendor/gmock-1.7.0/CMakeLists.txt @@ -0,0 +1,11 @@ +find_package(Threads) + +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/gtest/include) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/gtest) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) +add_library(gmock_main STATIC EXCLUDE_FROM_ALL gtest/src/gtest-all.cc src/gmock-all.cc src/gmock_main.cc) +target_link_libraries(gmock_main ${CMAKE_THREAD_LIBS_INIT}) + +set(GMOCK_INCLUDE_DIRS "${CMAKE_SOURCE_DIR}/vendor/gmock-1.7.0/include" "${CMAKE_SOURCE_DIR}/vendor/gmock-1.7.0/gtest/include" PARENT_SCOPE) +set(GMOCK_LIBRARIES gmock_main PARENT_SCOPE) diff --git a/vendor/gmock-1.7.0/LICENSE b/vendor/gmock-1.7.0/LICENSE new file mode 100644 index 0000000000..1941a11f8c --- /dev/null +++ b/vendor/gmock-1.7.0/LICENSE @@ -0,0 +1,28 @@ +Copyright 2008, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/gmock-1.7.0/README b/vendor/gmock-1.7.0/README new file mode 100644 index 0000000000..ed2e69bac3 --- /dev/null +++ b/vendor/gmock-1.7.0/README @@ -0,0 +1,369 @@ +Google C++ Mocking Framework +============================ + +http://code.google.com/p/googlemock/ + +Overview +-------- + +Google's framework for writing and using C++ mock classes on a variety +of platforms (Linux, Mac OS X, Windows, Windows CE, Symbian, etc). +Inspired by jMock, EasyMock, and Hamcrest, and designed with C++'s +specifics in mind, it can help you derive better designs of your +system and write better tests. + +Google Mock: + +- provides a declarative syntax for defining mocks, +- can easily define partial (hybrid) mocks, which are a cross of real + and mock objects, +- handles functions of arbitrary types and overloaded functions, +- comes with a rich set of matchers for validating function arguments, +- uses an intuitive syntax for controlling the behavior of a mock, +- does automatic verification of expectations (no record-and-replay + needed), +- allows arbitrary (partial) ordering constraints on + function calls to be expressed, +- lets a user extend it by defining new matchers and actions. +- does not use exceptions, and +- is easy to learn and use. + +Please see the project page above for more information as well as the +mailing list for questions, discussions, and development. There is +also an IRC channel on OFTC (irc.oftc.net) #gtest available. Please +join us! + +Please note that code under scripts/generator/ is from the cppclean +project (http://code.google.com/p/cppclean/) and under the Apache +License, which is different from Google Mock's license. + +Requirements for End Users +-------------------------- + +Google Mock is implemented on top of the Google Test C++ testing +framework (http://code.google.com/p/googletest/), and includes the +latter as part of the SVN repositary and distribution package. You +must use the bundled version of Google Test when using Google Mock, or +you may get compiler/linker errors. + +You can also easily configure Google Mock to work with another testing +framework of your choice; although it will still need Google Test as +an internal dependency. Please read +http://code.google.com/p/googlemock/wiki/ForDummies#Using_Google_Mock_with_Any_Testing_Framework +for how to do it. + +Google Mock depends on advanced C++ features and thus requires a more +modern compiler. The following are needed to use Google Mock: + +### Linux Requirements ### + +These are the base requirements to build and use Google Mock from a source +package (as described below): + + * GNU-compatible Make or "gmake" + * POSIX-standard shell + * POSIX(-2) Regular Expressions (regex.h) + * C++98-standard-compliant compiler (e.g. GCC 3.4 or newer) + +### Windows Requirements ### + + * Microsoft Visual C++ 8.0 SP1 or newer + +### Mac OS X Requirements ### + + * Mac OS X 10.4 Tiger or newer + * Developer Tools Installed + +Requirements for Contributors +----------------------------- + +We welcome patches. If you plan to contribute a patch, you need to +build Google Mock and its own tests from an SVN checkout (described +below), which has further requirements: + + * Automake version 1.9 or newer + * Autoconf version 2.59 or newer + * Libtool / Libtoolize + * Python version 2.3 or newer (for running some of the tests and + re-generating certain source files from templates) + +Getting the Source +------------------ + +There are two primary ways of getting Google Mock's source code: you +can download a stable source release in your preferred archive format, +or directly check out the source from our Subversion (SVN) repositary. +The SVN checkout requires a few extra steps and some extra software +packages on your system, but lets you track development and make +patches much more easily, so we highly encourage it. + +### Source Package ### + +Google Mock is released in versioned source packages which can be +downloaded from the download page [1]. Several different archive +formats are provided, but the only difference is the tools needed to +extract their contents, and the size of the resulting file. Download +whichever you are most comfortable with. + + [1] http://code.google.com/p/googlemock/downloads/list + +Once downloaded expand the archive using whichever tools you prefer +for that type. This will always result in a new directory with the +name "gmock-X.Y.Z" which contains all of the source code. Here are +some examples on Linux: + + tar -xvzf gmock-X.Y.Z.tar.gz + tar -xvjf gmock-X.Y.Z.tar.bz2 + unzip gmock-X.Y.Z.zip + +### SVN Checkout ### + +To check out the main branch (also known as the "trunk") of Google +Mock, run the following Subversion command: + + svn checkout http://googlemock.googlecode.com/svn/trunk/ gmock-svn + +If you are using a *nix system and plan to use the GNU Autotools build +system to build Google Mock (described below), you'll need to +configure it now. Otherwise you are done with getting the source +files. + +To prepare the Autotools build system, enter the target directory of +the checkout command you used ('gmock-svn') and proceed with the +following command: + + autoreconf -fvi + +Once you have completed this step, you are ready to build the library. +Note that you should only need to complete this step once. The +subsequent 'make' invocations will automatically re-generate the bits +of the build system that need to be changed. + +If your system uses older versions of the autotools, the above command +will fail. You may need to explicitly specify a version to use. For +instance, if you have both GNU Automake 1.4 and 1.9 installed and +'automake' would invoke the 1.4, use instead: + + AUTOMAKE=automake-1.9 ACLOCAL=aclocal-1.9 autoreconf -fvi + +Make sure you're using the same version of automake and aclocal. + +Setting up the Build +-------------------- + +To build Google Mock and your tests that use it, you need to tell your +build system where to find its headers and source files. The exact +way to do it depends on which build system you use, and is usually +straightforward. + +### Generic Build Instructions ### + +This section shows how you can integrate Google Mock into your +existing build system. + +Suppose you put Google Mock in directory ${GMOCK_DIR} and Google Test +in ${GTEST_DIR} (the latter is ${GMOCK_DIR}/gtest by default). To +build Google Mock, create a library build target (or a project as +called by Visual Studio and Xcode) to compile + + ${GTEST_DIR}/src/gtest-all.cc and ${GMOCK_DIR}/src/gmock-all.cc + +with + + ${GTEST_DIR}/include and ${GMOCK_DIR}/include + +in the system header search path, and + + ${GTEST_DIR} and ${GMOCK_DIR} + +in the normal header search path. Assuming a Linux-like system and gcc, +something like the following will do: + + g++ -isystem ${GTEST_DIR}/include -I${GTEST_DIR} \ + -isystem ${GMOCK_DIR}/include -I${GMOCK_DIR} \ + -pthread -c ${GTEST_DIR}/src/gtest-all.cc + g++ -isystem ${GTEST_DIR}/include -I${GTEST_DIR} \ + -isystem ${GMOCK_DIR}/include -I${GMOCK_DIR} \ + -pthread -c ${GMOCK_DIR}/src/gmock-all.cc + ar -rv libgmock.a gtest-all.o gmock-all.o + +(We need -pthread as Google Test and Google Mock use threads.) + +Next, you should compile your test source file with +${GTEST_DIR}/include and ${GMOCK_DIR}/include in the header search +path, and link it with gmock and any other necessary libraries: + + g++ -isystem ${GTEST_DIR}/include -isystem ${GMOCK_DIR}/include \ + -pthread path/to/your_test.cc libgmock.a -o your_test + +As an example, the make/ directory contains a Makefile that you can +use to build Google Mock on systems where GNU make is available +(e.g. Linux, Mac OS X, and Cygwin). It doesn't try to build Google +Mock's own tests. Instead, it just builds the Google Mock library and +a sample test. You can use it as a starting point for your own build +script. + +If the default settings are correct for your environment, the +following commands should succeed: + + cd ${GMOCK_DIR}/make + make + ./gmock_test + +If you see errors, try to tweak the contents of make/Makefile to make +them go away. There are instructions in make/Makefile on how to do +it. + +### Windows ### + +The msvc/2005 directory contains VC++ 2005 projects and the msvc/2010 +directory contains VC++ 2010 projects for building Google Mock and +selected tests. + +Change to the appropriate directory and run "msbuild gmock.sln" to +build the library and tests (or open the gmock.sln in the MSVC IDE). +If you want to create your own project to use with Google Mock, you'll +have to configure it to use the gmock_config propety sheet. For that: + + * Open the Property Manager window (View | Other Windows | Property Manager) + * Right-click on your project and select "Add Existing Property Sheet..." + * Navigate to gmock_config.vsprops or gmock_config.props and select it. + * In Project Properties | Configuration Properties | General | Additional + Include Directories, type /include. + +Tweaking Google Mock +-------------------- + +Google Mock can be used in diverse environments. The default +configuration may not work (or may not work well) out of the box in +some environments. However, you can easily tweak Google Mock by +defining control macros on the compiler command line. Generally, +these macros are named like GTEST_XYZ and you define them to either 1 +or 0 to enable or disable a certain feature. + +We list the most frequently used macros below. For a complete list, +see file ${GTEST_DIR}/include/gtest/internal/gtest-port.h. + +### Choosing a TR1 Tuple Library ### + +Google Mock uses the C++ Technical Report 1 (TR1) tuple library +heavily. Unfortunately TR1 tuple is not yet widely available with all +compilers. The good news is that Google Test 1.4.0+ implements a +subset of TR1 tuple that's enough for Google Mock's need. Google Mock +will automatically use that implementation when the compiler doesn't +provide TR1 tuple. + +Usually you don't need to care about which tuple library Google Test +and Google Mock use. However, if your project already uses TR1 tuple, +you need to tell Google Test and Google Mock to use the same TR1 tuple +library the rest of your project uses, or the two tuple +implementations will clash. To do that, add + + -DGTEST_USE_OWN_TR1_TUPLE=0 + +to the compiler flags while compiling Google Test, Google Mock, and +your tests. If you want to force Google Test and Google Mock to use +their own tuple library, just add + + -DGTEST_USE_OWN_TR1_TUPLE=1 + +to the compiler flags instead. + +If you want to use Boost's TR1 tuple library with Google Mock, please +refer to the Boost website (http://www.boost.org/) for how to obtain +it and set it up. + +### As a Shared Library (DLL) ### + +Google Mock is compact, so most users can build and link it as a static +library for the simplicity. Google Mock can be used as a DLL, but the +same DLL must contain Google Test as well. See Google Test's README +file for instructions on how to set up necessary compiler settings. + +### Tweaking Google Mock ### + +Most of Google Test's control macros apply to Google Mock as well. +Please see file ${GTEST_DIR}/README for how to tweak them. + +Upgrading from an Earlier Version +--------------------------------- + +We strive to keep Google Mock releases backward compatible. +Sometimes, though, we have to make some breaking changes for the +users' long-term benefits. This section describes what you'll need to +do if you are upgrading from an earlier version of Google Mock. + +### Upgrading from 1.1.0 or Earlier ### + +You may need to explicitly enable or disable Google Test's own TR1 +tuple library. See the instructions in section "Choosing a TR1 Tuple +Library". + +### Upgrading from 1.4.0 or Earlier ### + +On platforms where the pthread library is available, Google Test and +Google Mock use it in order to be thread-safe. For this to work, you +may need to tweak your compiler and/or linker flags. Please see the +"Multi-threaded Tests" section in file ${GTEST_DIR}/README for what +you may need to do. + +If you have custom matchers defined using MatcherInterface or +MakePolymorphicMatcher(), you'll need to update their definitions to +use the new matcher API [2]. Matchers defined using MATCHER() or +MATCHER_P*() aren't affected. + + [2] http://code.google.com/p/googlemock/wiki/CookBook#Writing_New_Monomorphic_Matchers, + http://code.google.com/p/googlemock/wiki/CookBook#Writing_New_Polymorphic_Matchers + +Developing Google Mock +---------------------- + +This section discusses how to make your own changes to Google Mock. + +### Testing Google Mock Itself ### + +To make sure your changes work as intended and don't break existing +functionality, you'll want to compile and run Google Test's own tests. +For that you'll need Autotools. First, make sure you have followed +the instructions in section "SVN Checkout" to configure Google Mock. +Then, create a build output directory and enter it. Next, + + ${GMOCK_DIR}/configure # Standard GNU configure script, --help for more info + +Once you have successfully configured Google Mock, the build steps are +standard for GNU-style OSS packages. + + make # Standard makefile following GNU conventions + make check # Builds and runs all tests - all should pass. + +Note that when building your project against Google Mock, you are building +against Google Test as well. There is no need to configure Google Test +separately. + +### Regenerating Source Files ### + +Some of Google Mock's source files are generated from templates (not +in the C++ sense) using a script. A template file is named FOO.pump, +where FOO is the name of the file it will generate. For example, the +file include/gmock/gmock-generated-actions.h.pump is used to generate +gmock-generated-actions.h in the same directory. + +Normally you don't need to worry about regenerating the source files, +unless you need to modify them. In that case, you should modify the +corresponding .pump files instead and run the 'pump' script (for Pump +is Useful for Meta Programming) to regenerate them. You can find +pump.py in the ${GTEST_DIR}/scripts/ directory. Read the Pump manual +[3] for how to use it. + + [3] http://code.google.com/p/googletest/wiki/PumpManual. + +### Contributing a Patch ### + +We welcome patches. Please read the Google Mock developer's guide [4] +for how you can contribute. In particular, make sure you have signed +the Contributor License Agreement, or we won't be able to accept the +patch. + + [4] http://code.google.com/p/googlemock/wiki/DevGuide + +Happy testing! diff --git a/vendor/gmock-1.7.0/gtest/CONTRIBUTORS b/vendor/gmock-1.7.0/gtest/CONTRIBUTORS new file mode 100644 index 0000000000..feae2fc044 --- /dev/null +++ b/vendor/gmock-1.7.0/gtest/CONTRIBUTORS @@ -0,0 +1,37 @@ +# This file contains a list of people who've made non-trivial +# contribution to the Google C++ Testing Framework project. People +# who commit code to the project are encouraged to add their names +# here. Please keep the list sorted by first names. + +Ajay Joshi +Balázs Dán +Bharat Mediratta +Chandler Carruth +Chris Prince +Chris Taylor +Dan Egnor +Eric Roman +Hady Zalek +Jeffrey Yasskin +Jói Sigurðsson +Keir Mierle +Keith Ray +Kenton Varda +Manuel Klimek +Markus Heule +Mika Raento +Miklós Fazekas +Pasi Valminen +Patrick Hanna +Patrick Riley +Peter Kaminski +Preston Jackson +Rainer Klaffenboeck +Russ Cox +Russ Rufer +Sean Mcafee +Sigurður Ásgeirsson +Tracy Bialik +Vadim Berman +Vlad Losev +Zhanyong Wan diff --git a/vendor/gmock-1.7.0/gtest/LICENSE b/vendor/gmock-1.7.0/gtest/LICENSE new file mode 100644 index 0000000000..1941a11f8c --- /dev/null +++ b/vendor/gmock-1.7.0/gtest/LICENSE @@ -0,0 +1,28 @@ +Copyright 2008, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/gmock-1.7.0/gtest/README b/vendor/gmock-1.7.0/gtest/README new file mode 100644 index 0000000000..26f35a8479 --- /dev/null +++ b/vendor/gmock-1.7.0/gtest/README @@ -0,0 +1,435 @@ +Google C++ Testing Framework +============================ + +http://code.google.com/p/googletest/ + +Overview +-------- + +Google's framework for writing C++ tests on a variety of platforms +(Linux, Mac OS X, Windows, Windows CE, Symbian, etc). Based on the +xUnit architecture. Supports automatic test discovery, a rich set of +assertions, user-defined assertions, death tests, fatal and non-fatal +failures, various options for running the tests, and XML test report +generation. + +Please see the project page above for more information as well as the +mailing list for questions, discussions, and development. There is +also an IRC channel on OFTC (irc.oftc.net) #gtest available. Please +join us! + +Requirements for End Users +-------------------------- + +Google Test is designed to have fairly minimal requirements to build +and use with your projects, but there are some. Currently, we support +Linux, Windows, Mac OS X, and Cygwin. We will also make our best +effort to support other platforms (e.g. Solaris, AIX, and z/OS). +However, since core members of the Google Test project have no access +to these platforms, Google Test may have outstanding issues there. If +you notice any problems on your platform, please notify +googletestframework@googlegroups.com. Patches for fixing them are +even more welcome! + +### Linux Requirements ### + +These are the base requirements to build and use Google Test from a source +package (as described below): + * GNU-compatible Make or gmake + * POSIX-standard shell + * POSIX(-2) Regular Expressions (regex.h) + * A C++98-standard-compliant compiler + +### Windows Requirements ### + + * Microsoft Visual C++ 7.1 or newer + +### Cygwin Requirements ### + + * Cygwin 1.5.25-14 or newer + +### Mac OS X Requirements ### + + * Mac OS X 10.4 Tiger or newer + * Developer Tools Installed + +Also, you'll need CMake 2.6.4 or higher if you want to build the +samples using the provided CMake script, regardless of the platform. + +Requirements for Contributors +----------------------------- + +We welcome patches. If you plan to contribute a patch, you need to +build Google Test and its own tests from an SVN checkout (described +below), which has further requirements: + + * Python version 2.3 or newer (for running some of the tests and + re-generating certain source files from templates) + * CMake 2.6.4 or newer + +Getting the Source +------------------ + +There are two primary ways of getting Google Test's source code: you +can download a stable source release in your preferred archive format, +or directly check out the source from our Subversion (SVN) repositary. +The SVN checkout requires a few extra steps and some extra software +packages on your system, but lets you track the latest development and +make patches much more easily, so we highly encourage it. + +### Source Package ### + +Google Test is released in versioned source packages which can be +downloaded from the download page [1]. Several different archive +formats are provided, but the only difference is the tools used to +manipulate them, and the size of the resulting file. Download +whichever you are most comfortable with. + + [1] http://code.google.com/p/googletest/downloads/list + +Once the package is downloaded, expand it using whichever tools you +prefer for that type. This will result in a new directory with the +name "gtest-X.Y.Z" which contains all of the source code. Here are +some examples on Linux: + + tar -xvzf gtest-X.Y.Z.tar.gz + tar -xvjf gtest-X.Y.Z.tar.bz2 + unzip gtest-X.Y.Z.zip + +### SVN Checkout ### + +To check out the main branch (also known as the "trunk") of Google +Test, run the following Subversion command: + + svn checkout http://googletest.googlecode.com/svn/trunk/ gtest-svn + +Setting up the Build +-------------------- + +To build Google Test and your tests that use it, you need to tell your +build system where to find its headers and source files. The exact +way to do it depends on which build system you use, and is usually +straightforward. + +### Generic Build Instructions ### + +Suppose you put Google Test in directory ${GTEST_DIR}. To build it, +create a library build target (or a project as called by Visual Studio +and Xcode) to compile + + ${GTEST_DIR}/src/gtest-all.cc + +with ${GTEST_DIR}/include in the system header search path and ${GTEST_DIR} +in the normal header search path. Assuming a Linux-like system and gcc, +something like the following will do: + + g++ -isystem ${GTEST_DIR}/include -I${GTEST_DIR} \ + -pthread -c ${GTEST_DIR}/src/gtest-all.cc + ar -rv libgtest.a gtest-all.o + +(We need -pthread as Google Test uses threads.) + +Next, you should compile your test source file with +${GTEST_DIR}/include in the system header search path, and link it +with gtest and any other necessary libraries: + + g++ -isystem ${GTEST_DIR}/include -pthread path/to/your_test.cc libgtest.a \ + -o your_test + +As an example, the make/ directory contains a Makefile that you can +use to build Google Test on systems where GNU make is available +(e.g. Linux, Mac OS X, and Cygwin). It doesn't try to build Google +Test's own tests. Instead, it just builds the Google Test library and +a sample test. You can use it as a starting point for your own build +script. + +If the default settings are correct for your environment, the +following commands should succeed: + + cd ${GTEST_DIR}/make + make + ./sample1_unittest + +If you see errors, try to tweak the contents of make/Makefile to make +them go away. There are instructions in make/Makefile on how to do +it. + +### Using CMake ### + +Google Test comes with a CMake build script (CMakeLists.txt) that can +be used on a wide range of platforms ("C" stands for cross-platofrm.). +If you don't have CMake installed already, you can download it for +free from http://www.cmake.org/. + +CMake works by generating native makefiles or build projects that can +be used in the compiler environment of your choice. The typical +workflow starts with: + + mkdir mybuild # Create a directory to hold the build output. + cd mybuild + cmake ${GTEST_DIR} # Generate native build scripts. + +If you want to build Google Test's samples, you should replace the +last command with + + cmake -Dgtest_build_samples=ON ${GTEST_DIR} + +If you are on a *nix system, you should now see a Makefile in the +current directory. Just type 'make' to build gtest. + +If you use Windows and have Vistual Studio installed, a gtest.sln file +and several .vcproj files will be created. You can then build them +using Visual Studio. + +On Mac OS X with Xcode installed, a .xcodeproj file will be generated. + +### Legacy Build Scripts ### + +Before settling on CMake, we have been providing hand-maintained build +projects/scripts for Visual Studio, Xcode, and Autotools. While we +continue to provide them for convenience, they are not actively +maintained any more. We highly recommend that you follow the +instructions in the previous two sections to integrate Google Test +with your existing build system. + +If you still need to use the legacy build scripts, here's how: + +The msvc\ folder contains two solutions with Visual C++ projects. +Open the gtest.sln or gtest-md.sln file using Visual Studio, and you +are ready to build Google Test the same way you build any Visual +Studio project. Files that have names ending with -md use DLL +versions of Microsoft runtime libraries (the /MD or the /MDd compiler +option). Files without that suffix use static versions of the runtime +libraries (the /MT or the /MTd option). Please note that one must use +the same option to compile both gtest and the test code. If you use +Visual Studio 2005 or above, we recommend the -md version as /MD is +the default for new projects in these versions of Visual Studio. + +On Mac OS X, open the gtest.xcodeproj in the xcode/ folder using +Xcode. Build the "gtest" target. The universal binary framework will +end up in your selected build directory (selected in the Xcode +"Preferences..." -> "Building" pane and defaults to xcode/build). +Alternatively, at the command line, enter: + + xcodebuild + +This will build the "Release" configuration of gtest.framework in your +default build location. See the "xcodebuild" man page for more +information about building different configurations and building in +different locations. + +If you wish to use the Google Test Xcode project with Xcode 4.x and +above, you need to either: + * update the SDK configuration options in xcode/Config/General.xconfig. + Comment options SDKROOT, MACOS_DEPLOYMENT_TARGET, and GCC_VERSION. If + you choose this route you lose the ability to target earlier versions + of MacOS X. + * Install an SDK for an earlier version. This doesn't appear to be + supported by Apple, but has been reported to work + (http://stackoverflow.com/questions/5378518). + +Tweaking Google Test +-------------------- + +Google Test can be used in diverse environments. The default +configuration may not work (or may not work well) out of the box in +some environments. However, you can easily tweak Google Test by +defining control macros on the compiler command line. Generally, +these macros are named like GTEST_XYZ and you define them to either 1 +or 0 to enable or disable a certain feature. + +We list the most frequently used macros below. For a complete list, +see file include/gtest/internal/gtest-port.h. + +### Choosing a TR1 Tuple Library ### + +Some Google Test features require the C++ Technical Report 1 (TR1) +tuple library, which is not yet available with all compilers. The +good news is that Google Test implements a subset of TR1 tuple that's +enough for its own need, and will automatically use this when the +compiler doesn't provide TR1 tuple. + +Usually you don't need to care about which tuple library Google Test +uses. However, if your project already uses TR1 tuple, you need to +tell Google Test to use the same TR1 tuple library the rest of your +project uses, or the two tuple implementations will clash. To do +that, add + + -DGTEST_USE_OWN_TR1_TUPLE=0 + +to the compiler flags while compiling Google Test and your tests. If +you want to force Google Test to use its own tuple library, just add + + -DGTEST_USE_OWN_TR1_TUPLE=1 + +to the compiler flags instead. + +If you don't want Google Test to use tuple at all, add + + -DGTEST_HAS_TR1_TUPLE=0 + +and all features using tuple will be disabled. + +### Multi-threaded Tests ### + +Google Test is thread-safe where the pthread library is available. +After #include "gtest/gtest.h", you can check the GTEST_IS_THREADSAFE +macro to see whether this is the case (yes if the macro is #defined to +1, no if it's undefined.). + +If Google Test doesn't correctly detect whether pthread is available +in your environment, you can force it with + + -DGTEST_HAS_PTHREAD=1 + +or + + -DGTEST_HAS_PTHREAD=0 + +When Google Test uses pthread, you may need to add flags to your +compiler and/or linker to select the pthread library, or you'll get +link errors. If you use the CMake script or the deprecated Autotools +script, this is taken care of for you. If you use your own build +script, you'll need to read your compiler and linker's manual to +figure out what flags to add. + +### As a Shared Library (DLL) ### + +Google Test is compact, so most users can build and link it as a +static library for the simplicity. You can choose to use Google Test +as a shared library (known as a DLL on Windows) if you prefer. + +To compile *gtest* as a shared library, add + + -DGTEST_CREATE_SHARED_LIBRARY=1 + +to the compiler flags. You'll also need to tell the linker to produce +a shared library instead - consult your linker's manual for how to do +it. + +To compile your *tests* that use the gtest shared library, add + + -DGTEST_LINKED_AS_SHARED_LIBRARY=1 + +to the compiler flags. + +Note: while the above steps aren't technically necessary today when +using some compilers (e.g. GCC), they may become necessary in the +future, if we decide to improve the speed of loading the library (see +http://gcc.gnu.org/wiki/Visibility for details). Therefore you are +recommended to always add the above flags when using Google Test as a +shared library. Otherwise a future release of Google Test may break +your build script. + +### Avoiding Macro Name Clashes ### + +In C++, macros don't obey namespaces. Therefore two libraries that +both define a macro of the same name will clash if you #include both +definitions. In case a Google Test macro clashes with another +library, you can force Google Test to rename its macro to avoid the +conflict. + +Specifically, if both Google Test and some other code define macro +FOO, you can add + + -DGTEST_DONT_DEFINE_FOO=1 + +to the compiler flags to tell Google Test to change the macro's name +from FOO to GTEST_FOO. Currently FOO can be FAIL, SUCCEED, or TEST. +For example, with -DGTEST_DONT_DEFINE_TEST=1, you'll need to write + + GTEST_TEST(SomeTest, DoesThis) { ... } + +instead of + + TEST(SomeTest, DoesThis) { ... } + +in order to define a test. + +Upgrating from an Earlier Version +--------------------------------- + +We strive to keep Google Test releases backward compatible. +Sometimes, though, we have to make some breaking changes for the +users' long-term benefits. This section describes what you'll need to +do if you are upgrading from an earlier version of Google Test. + +### Upgrading from 1.3.0 or Earlier ### + +You may need to explicitly enable or disable Google Test's own TR1 +tuple library. See the instructions in section "Choosing a TR1 Tuple +Library". + +### Upgrading from 1.4.0 or Earlier ### + +The Autotools build script (configure + make) is no longer officially +supportted. You are encouraged to migrate to your own build system or +use CMake. If you still need to use Autotools, you can find +instructions in the README file from Google Test 1.4.0. + +On platforms where the pthread library is available, Google Test uses +it in order to be thread-safe. See the "Multi-threaded Tests" section +for what this means to your build script. + +If you use Microsoft Visual C++ 7.1 with exceptions disabled, Google +Test will no longer compile. This should affect very few people, as a +large portion of STL (including ) doesn't compile in this mode +anyway. We decided to stop supporting it in order to greatly simplify +Google Test's implementation. + +Developing Google Test +---------------------- + +This section discusses how to make your own changes to Google Test. + +### Testing Google Test Itself ### + +To make sure your changes work as intended and don't break existing +functionality, you'll want to compile and run Google Test's own tests. +For that you can use CMake: + + mkdir mybuild + cd mybuild + cmake -Dgtest_build_tests=ON ${GTEST_DIR} + +Make sure you have Python installed, as some of Google Test's tests +are written in Python. If the cmake command complains about not being +able to find Python ("Could NOT find PythonInterp (missing: +PYTHON_EXECUTABLE)"), try telling it explicitly where your Python +executable can be found: + + cmake -DPYTHON_EXECUTABLE=path/to/python -Dgtest_build_tests=ON ${GTEST_DIR} + +Next, you can build Google Test and all of its own tests. On *nix, +this is usually done by 'make'. To run the tests, do + + make test + +All tests should pass. + +### Regenerating Source Files ### + +Some of Google Test's source files are generated from templates (not +in the C++ sense) using a script. A template file is named FOO.pump, +where FOO is the name of the file it will generate. For example, the +file include/gtest/internal/gtest-type-util.h.pump is used to generate +gtest-type-util.h in the same directory. + +Normally you don't need to worry about regenerating the source files, +unless you need to modify them. In that case, you should modify the +corresponding .pump files instead and run the pump.py Python script to +regenerate them. You can find pump.py in the scripts/ directory. +Read the Pump manual [2] for how to use it. + + [2] http://code.google.com/p/googletest/wiki/PumpManual + +### Contributing a Patch ### + +We welcome patches. Please read the Google Test developer's guide [3] +for how you can contribute. In particular, make sure you have signed +the Contributor License Agreement, or we won't be able to accept the +patch. + + [3] http://code.google.com/p/googletest/wiki/GoogleTestDevGuide + +Happy testing! diff --git a/vendor/gmock-1.7.0/gtest/cmake/internal_utils.cmake b/vendor/gmock-1.7.0/gtest/cmake/internal_utils.cmake new file mode 100644 index 0000000000..8cb21894ce --- /dev/null +++ b/vendor/gmock-1.7.0/gtest/cmake/internal_utils.cmake @@ -0,0 +1,227 @@ +# Defines functions and macros useful for building Google Test and +# Google Mock. +# +# Note: +# +# - This file will be run twice when building Google Mock (once via +# Google Test's CMakeLists.txt, and once via Google Mock's). +# Therefore it shouldn't have any side effects other than defining +# the functions and macros. +# +# - The functions/macros defined in this file may depend on Google +# Test and Google Mock's option() definitions, and thus must be +# called *after* the options have been defined. + +# Tweaks CMake's default compiler/linker settings to suit Google Test's needs. +# +# This must be a macro(), as inside a function string() can only +# update variables in the function scope. +macro(fix_default_compiler_settings_) + if (MSVC) + # For MSVC, CMake sets certain flags to defaults we want to override. + # This replacement code is taken from sample in the CMake Wiki at + # http://www.cmake.org/Wiki/CMake_FAQ#Dynamic_Replace. + foreach (flag_var + CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE + CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO) + if (NOT BUILD_SHARED_LIBS AND NOT gtest_force_shared_crt) + # When Google Test is built as a shared library, it should also use + # shared runtime libraries. Otherwise, it may end up with multiple + # copies of runtime library data in different modules, resulting in + # hard-to-find crashes. When it is built as a static library, it is + # preferable to use CRT as static libraries, as we don't have to rely + # on CRT DLLs being available. CMake always defaults to using shared + # CRT libraries, so we override that default here. + string(REPLACE "/MD" "-MT" ${flag_var} "${${flag_var}}") + endif() + + # We prefer more strict warning checking for building Google Test. + # Replaces /W3 with /W4 in defaults. + string(REPLACE "/W3" "-W4" ${flag_var} "${${flag_var}}") + endforeach() + endif() +endmacro() + +# Defines the compiler/linker flags used to build Google Test and +# Google Mock. You can tweak these definitions to suit your need. A +# variable's value is empty before it's explicitly assigned to. +macro(config_compiler_and_linker) + if (NOT gtest_disable_pthreads) + # Defines CMAKE_USE_PTHREADS_INIT and CMAKE_THREAD_LIBS_INIT. + find_package(Threads) + endif() + + fix_default_compiler_settings_() + if (MSVC) + # Newlines inside flags variables break CMake's NMake generator. + # TODO(vladl@google.com): Add -RTCs and -RTCu to debug builds. + set(cxx_base_flags "-GS -W4 -WX -wd4127 -wd4251 -wd4275 -nologo -J -Zi") + if (MSVC_VERSION LESS 1400) + # Suppress spurious warnings MSVC 7.1 sometimes issues. + # Forcing value to bool. + set(cxx_base_flags "${cxx_base_flags} -wd4800") + # Copy constructor and assignment operator could not be generated. + set(cxx_base_flags "${cxx_base_flags} -wd4511 -wd4512") + # Compatibility warnings not applicable to Google Test. + # Resolved overload was found by argument-dependent lookup. + set(cxx_base_flags "${cxx_base_flags} -wd4675") + endif() + set(cxx_base_flags "${cxx_base_flags} -D_UNICODE -DUNICODE -DWIN32 -D_WIN32") + set(cxx_base_flags "${cxx_base_flags} -DSTRICT -DWIN32_LEAN_AND_MEAN") + set(cxx_exception_flags "-EHsc -D_HAS_EXCEPTIONS=1") + set(cxx_no_exception_flags "-D_HAS_EXCEPTIONS=0") + set(cxx_no_rtti_flags "-GR-") + elseif (CMAKE_COMPILER_IS_GNUCXX) + set(cxx_base_flags "-Wall -Wshadow") + set(cxx_exception_flags "-fexceptions") + set(cxx_no_exception_flags "-fno-exceptions") + # Until version 4.3.2, GCC doesn't define a macro to indicate + # whether RTTI is enabled. Therefore we define GTEST_HAS_RTTI + # explicitly. + set(cxx_no_rtti_flags "-fno-rtti -DGTEST_HAS_RTTI=0") + set(cxx_strict_flags + "-Wextra -Wno-unused-parameter -Wno-missing-field-initializers") + elseif (CMAKE_CXX_COMPILER_ID STREQUAL "SunPro") + set(cxx_exception_flags "-features=except") + # Sun Pro doesn't provide macros to indicate whether exceptions and + # RTTI are enabled, so we define GTEST_HAS_* explicitly. + set(cxx_no_exception_flags "-features=no%except -DGTEST_HAS_EXCEPTIONS=0") + set(cxx_no_rtti_flags "-features=no%rtti -DGTEST_HAS_RTTI=0") + elseif (CMAKE_CXX_COMPILER_ID STREQUAL "VisualAge" OR + CMAKE_CXX_COMPILER_ID STREQUAL "XL") + # CMake 2.8 changes Visual Age's compiler ID to "XL". + set(cxx_exception_flags "-qeh") + set(cxx_no_exception_flags "-qnoeh") + # Until version 9.0, Visual Age doesn't define a macro to indicate + # whether RTTI is enabled. Therefore we define GTEST_HAS_RTTI + # explicitly. + set(cxx_no_rtti_flags "-qnortti -DGTEST_HAS_RTTI=0") + elseif (CMAKE_CXX_COMPILER_ID STREQUAL "HP") + set(cxx_base_flags "-AA -mt") + set(cxx_exception_flags "-DGTEST_HAS_EXCEPTIONS=1") + set(cxx_no_exception_flags "+noeh -DGTEST_HAS_EXCEPTIONS=0") + # RTTI can not be disabled in HP aCC compiler. + set(cxx_no_rtti_flags "") + endif() + + if (CMAKE_USE_PTHREADS_INIT) # The pthreads library is available and allowed. + set(cxx_base_flags "${cxx_base_flags} -DGTEST_HAS_PTHREAD=1") + else() + set(cxx_base_flags "${cxx_base_flags} -DGTEST_HAS_PTHREAD=0") + endif() + + # For building gtest's own tests and samples. + set(cxx_exception "${CMAKE_CXX_FLAGS} ${cxx_base_flags} ${cxx_exception_flags}") + set(cxx_no_exception + "${CMAKE_CXX_FLAGS} ${cxx_base_flags} ${cxx_no_exception_flags}") + set(cxx_default "${cxx_exception}") + set(cxx_no_rtti "${cxx_default} ${cxx_no_rtti_flags}") + set(cxx_use_own_tuple "${cxx_default} -DGTEST_USE_OWN_TR1_TUPLE=1") + + # For building the gtest libraries. + set(cxx_strict "${cxx_default} ${cxx_strict_flags}") +endmacro() + +# Defines the gtest & gtest_main libraries. User tests should link +# with one of them. +function(cxx_library_with_type name type cxx_flags) + # type can be either STATIC or SHARED to denote a static or shared library. + # ARGN refers to additional arguments after 'cxx_flags'. + add_library(${name} ${type} ${ARGN}) + set_target_properties(${name} + PROPERTIES + COMPILE_FLAGS "${cxx_flags}") + if (BUILD_SHARED_LIBS OR type STREQUAL "SHARED") + set_target_properties(${name} + PROPERTIES + COMPILE_DEFINITIONS "GTEST_CREATE_SHARED_LIBRARY=1") + endif() + if (CMAKE_USE_PTHREADS_INIT) + target_link_libraries(${name} ${CMAKE_THREAD_LIBS_INIT}) + endif() +endfunction() + +######################################################################## +# +# Helper functions for creating build targets. + +function(cxx_shared_library name cxx_flags) + cxx_library_with_type(${name} SHARED "${cxx_flags}" ${ARGN}) +endfunction() + +function(cxx_library name cxx_flags) + cxx_library_with_type(${name} "" "${cxx_flags}" ${ARGN}) +endfunction() + +# cxx_executable_with_flags(name cxx_flags libs srcs...) +# +# creates a named C++ executable that depends on the given libraries and +# is built from the given source files with the given compiler flags. +function(cxx_executable_with_flags name cxx_flags libs) + add_executable(${name} ${ARGN}) + if (cxx_flags) + set_target_properties(${name} + PROPERTIES + COMPILE_FLAGS "${cxx_flags}") + endif() + if (BUILD_SHARED_LIBS) + set_target_properties(${name} + PROPERTIES + COMPILE_DEFINITIONS "GTEST_LINKED_AS_SHARED_LIBRARY=1") + endif() + # To support mixing linking in static and dynamic libraries, link each + # library in with an extra call to target_link_libraries. + foreach (lib "${libs}") + target_link_libraries(${name} ${lib}) + endforeach() +endfunction() + +# cxx_executable(name dir lib srcs...) +# +# creates a named target that depends on the given libs and is built +# from the given source files. dir/name.cc is implicitly included in +# the source file list. +function(cxx_executable name dir libs) + cxx_executable_with_flags( + ${name} "${cxx_default}" "${libs}" "${dir}/${name}.cc" ${ARGN}) +endfunction() + +# Sets PYTHONINTERP_FOUND and PYTHON_EXECUTABLE. +find_package(PythonInterp) + +# cxx_test_with_flags(name cxx_flags libs srcs...) +# +# creates a named C++ test that depends on the given libs and is built +# from the given source files with the given compiler flags. +function(cxx_test_with_flags name cxx_flags libs) + cxx_executable_with_flags(${name} "${cxx_flags}" "${libs}" ${ARGN}) + add_test(${name} ${name}) +endfunction() + +# cxx_test(name libs srcs...) +# +# creates a named test target that depends on the given libs and is +# built from the given source files. Unlike cxx_test_with_flags, +# test/name.cc is already implicitly included in the source file list. +function(cxx_test name libs) + cxx_test_with_flags("${name}" "${cxx_default}" "${libs}" + "test/${name}.cc" ${ARGN}) +endfunction() + +# py_test(name) +# +# creates a Python test with the given name whose main module is in +# test/name.py. It does nothing if Python is not installed. +function(py_test name) + # We are not supporting Python tests on Linux yet as they consider + # all Linux environments to be google3 and try to use google3 features. + if (PYTHONINTERP_FOUND) + # ${CMAKE_BINARY_DIR} is known at configuration time, so we can + # directly bind it from cmake. ${CTEST_CONFIGURATION_TYPE} is known + # only at ctest runtime (by calling ctest -c ), so + # we have to escape $ to delay variable substitution here. + add_test(${name} + ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/${name}.py + --build_dir=${CMAKE_CURRENT_BINARY_DIR}/\${CTEST_CONFIGURATION_TYPE}) + endif() +endfunction() diff --git a/vendor/gmock-1.7.0/gtest/include/gtest/gtest-death-test.h b/vendor/gmock-1.7.0/gtest/include/gtest/gtest-death-test.h new file mode 100644 index 0000000000..957a69c6a9 --- /dev/null +++ b/vendor/gmock-1.7.0/gtest/include/gtest/gtest-death-test.h @@ -0,0 +1,294 @@ +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) +// +// The Google C++ Testing Framework (Google Test) +// +// This header file defines the public API for death tests. It is +// #included by gtest.h so a user doesn't need to include this +// directly. + +#ifndef GTEST_INCLUDE_GTEST_GTEST_DEATH_TEST_H_ +#define GTEST_INCLUDE_GTEST_GTEST_DEATH_TEST_H_ + +#include "gtest/internal/gtest-death-test-internal.h" + +namespace testing { + +// This flag controls the style of death tests. Valid values are "threadsafe", +// meaning that the death test child process will re-execute the test binary +// from the start, running only a single death test, or "fast", +// meaning that the child process will execute the test logic immediately +// after forking. +GTEST_DECLARE_string_(death_test_style); + +#if GTEST_HAS_DEATH_TEST + +namespace internal { + +// Returns a Boolean value indicating whether the caller is currently +// executing in the context of the death test child process. Tools such as +// Valgrind heap checkers may need this to modify their behavior in death +// tests. IMPORTANT: This is an internal utility. Using it may break the +// implementation of death tests. User code MUST NOT use it. +GTEST_API_ bool InDeathTestChild(); + +} // namespace internal + +// The following macros are useful for writing death tests. + +// Here's what happens when an ASSERT_DEATH* or EXPECT_DEATH* is +// executed: +// +// 1. It generates a warning if there is more than one active +// thread. This is because it's safe to fork() or clone() only +// when there is a single thread. +// +// 2. The parent process clone()s a sub-process and runs the death +// test in it; the sub-process exits with code 0 at the end of the +// death test, if it hasn't exited already. +// +// 3. The parent process waits for the sub-process to terminate. +// +// 4. The parent process checks the exit code and error message of +// the sub-process. +// +// Examples: +// +// ASSERT_DEATH(server.SendMessage(56, "Hello"), "Invalid port number"); +// for (int i = 0; i < 5; i++) { +// EXPECT_DEATH(server.ProcessRequest(i), +// "Invalid request .* in ProcessRequest()") +// << "Failed to die on request " << i; +// } +// +// ASSERT_EXIT(server.ExitNow(), ::testing::ExitedWithCode(0), "Exiting"); +// +// bool KilledBySIGHUP(int exit_code) { +// return WIFSIGNALED(exit_code) && WTERMSIG(exit_code) == SIGHUP; +// } +// +// ASSERT_EXIT(client.HangUpServer(), KilledBySIGHUP, "Hanging up!"); +// +// On the regular expressions used in death tests: +// +// On POSIX-compliant systems (*nix), we use the library, +// which uses the POSIX extended regex syntax. +// +// On other platforms (e.g. Windows), we only support a simple regex +// syntax implemented as part of Google Test. This limited +// implementation should be enough most of the time when writing +// death tests; though it lacks many features you can find in PCRE +// or POSIX extended regex syntax. For example, we don't support +// union ("x|y"), grouping ("(xy)"), brackets ("[xy]"), and +// repetition count ("x{5,7}"), among others. +// +// Below is the syntax that we do support. We chose it to be a +// subset of both PCRE and POSIX extended regex, so it's easy to +// learn wherever you come from. In the following: 'A' denotes a +// literal character, period (.), or a single \\ escape sequence; +// 'x' and 'y' denote regular expressions; 'm' and 'n' are for +// natural numbers. +// +// c matches any literal character c +// \\d matches any decimal digit +// \\D matches any character that's not a decimal digit +// \\f matches \f +// \\n matches \n +// \\r matches \r +// \\s matches any ASCII whitespace, including \n +// \\S matches any character that's not a whitespace +// \\t matches \t +// \\v matches \v +// \\w matches any letter, _, or decimal digit +// \\W matches any character that \\w doesn't match +// \\c matches any literal character c, which must be a punctuation +// . matches any single character except \n +// A? matches 0 or 1 occurrences of A +// A* matches 0 or many occurrences of A +// A+ matches 1 or many occurrences of A +// ^ matches the beginning of a string (not that of each line) +// $ matches the end of a string (not that of each line) +// xy matches x followed by y +// +// If you accidentally use PCRE or POSIX extended regex features +// not implemented by us, you will get a run-time failure. In that +// case, please try to rewrite your regular expression within the +// above syntax. +// +// This implementation is *not* meant to be as highly tuned or robust +// as a compiled regex library, but should perform well enough for a +// death test, which already incurs significant overhead by launching +// a child process. +// +// Known caveats: +// +// A "threadsafe" style death test obtains the path to the test +// program from argv[0] and re-executes it in the sub-process. For +// simplicity, the current implementation doesn't search the PATH +// when launching the sub-process. This means that the user must +// invoke the test program via a path that contains at least one +// path separator (e.g. path/to/foo_test and +// /absolute/path/to/bar_test are fine, but foo_test is not). This +// is rarely a problem as people usually don't put the test binary +// directory in PATH. +// +// TODO(wan@google.com): make thread-safe death tests search the PATH. + +// Asserts that a given statement causes the program to exit, with an +// integer exit status that satisfies predicate, and emitting error output +// that matches regex. +# define ASSERT_EXIT(statement, predicate, regex) \ + GTEST_DEATH_TEST_(statement, predicate, regex, GTEST_FATAL_FAILURE_) + +// Like ASSERT_EXIT, but continues on to successive tests in the +// test case, if any: +# define EXPECT_EXIT(statement, predicate, regex) \ + GTEST_DEATH_TEST_(statement, predicate, regex, GTEST_NONFATAL_FAILURE_) + +// Asserts that a given statement causes the program to exit, either by +// explicitly exiting with a nonzero exit code or being killed by a +// signal, and emitting error output that matches regex. +# define ASSERT_DEATH(statement, regex) \ + ASSERT_EXIT(statement, ::testing::internal::ExitedUnsuccessfully, regex) + +// Like ASSERT_DEATH, but continues on to successive tests in the +// test case, if any: +# define EXPECT_DEATH(statement, regex) \ + EXPECT_EXIT(statement, ::testing::internal::ExitedUnsuccessfully, regex) + +// Two predicate classes that can be used in {ASSERT,EXPECT}_EXIT*: + +// Tests that an exit code describes a normal exit with a given exit code. +class GTEST_API_ ExitedWithCode { + public: + explicit ExitedWithCode(int exit_code); + bool operator()(int exit_status) const; + private: + // No implementation - assignment is unsupported. + void operator=(const ExitedWithCode& other); + + const int exit_code_; +}; + +# if !GTEST_OS_WINDOWS +// Tests that an exit code describes an exit due to termination by a +// given signal. +class GTEST_API_ KilledBySignal { + public: + explicit KilledBySignal(int signum); + bool operator()(int exit_status) const; + private: + const int signum_; +}; +# endif // !GTEST_OS_WINDOWS + +// EXPECT_DEBUG_DEATH asserts that the given statements die in debug mode. +// The death testing framework causes this to have interesting semantics, +// since the sideeffects of the call are only visible in opt mode, and not +// in debug mode. +// +// In practice, this can be used to test functions that utilize the +// LOG(DFATAL) macro using the following style: +// +// int DieInDebugOr12(int* sideeffect) { +// if (sideeffect) { +// *sideeffect = 12; +// } +// LOG(DFATAL) << "death"; +// return 12; +// } +// +// TEST(TestCase, TestDieOr12WorksInDgbAndOpt) { +// int sideeffect = 0; +// // Only asserts in dbg. +// EXPECT_DEBUG_DEATH(DieInDebugOr12(&sideeffect), "death"); +// +// #ifdef NDEBUG +// // opt-mode has sideeffect visible. +// EXPECT_EQ(12, sideeffect); +// #else +// // dbg-mode no visible sideeffect. +// EXPECT_EQ(0, sideeffect); +// #endif +// } +// +// This will assert that DieInDebugReturn12InOpt() crashes in debug +// mode, usually due to a DCHECK or LOG(DFATAL), but returns the +// appropriate fallback value (12 in this case) in opt mode. If you +// need to test that a function has appropriate side-effects in opt +// mode, include assertions against the side-effects. A general +// pattern for this is: +// +// EXPECT_DEBUG_DEATH({ +// // Side-effects here will have an effect after this statement in +// // opt mode, but none in debug mode. +// EXPECT_EQ(12, DieInDebugOr12(&sideeffect)); +// }, "death"); +// +# ifdef NDEBUG + +# define EXPECT_DEBUG_DEATH(statement, regex) \ + GTEST_EXECUTE_STATEMENT_(statement, regex) + +# define ASSERT_DEBUG_DEATH(statement, regex) \ + GTEST_EXECUTE_STATEMENT_(statement, regex) + +# else + +# define EXPECT_DEBUG_DEATH(statement, regex) \ + EXPECT_DEATH(statement, regex) + +# define ASSERT_DEBUG_DEATH(statement, regex) \ + ASSERT_DEATH(statement, regex) + +# endif // NDEBUG for EXPECT_DEBUG_DEATH +#endif // GTEST_HAS_DEATH_TEST + +// EXPECT_DEATH_IF_SUPPORTED(statement, regex) and +// ASSERT_DEATH_IF_SUPPORTED(statement, regex) expand to real death tests if +// death tests are supported; otherwise they just issue a warning. This is +// useful when you are combining death test assertions with normal test +// assertions in one test. +#if GTEST_HAS_DEATH_TEST +# define EXPECT_DEATH_IF_SUPPORTED(statement, regex) \ + EXPECT_DEATH(statement, regex) +# define ASSERT_DEATH_IF_SUPPORTED(statement, regex) \ + ASSERT_DEATH(statement, regex) +#else +# define EXPECT_DEATH_IF_SUPPORTED(statement, regex) \ + GTEST_UNSUPPORTED_DEATH_TEST_(statement, regex, ) +# define ASSERT_DEATH_IF_SUPPORTED(statement, regex) \ + GTEST_UNSUPPORTED_DEATH_TEST_(statement, regex, return) +#endif + +} // namespace testing + +#endif // GTEST_INCLUDE_GTEST_GTEST_DEATH_TEST_H_ diff --git a/vendor/gmock-1.7.0/gtest/include/gtest/gtest-message.h b/vendor/gmock-1.7.0/gtest/include/gtest/gtest-message.h new file mode 100644 index 0000000000..fe879bca79 --- /dev/null +++ b/vendor/gmock-1.7.0/gtest/include/gtest/gtest-message.h @@ -0,0 +1,250 @@ +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) +// +// The Google C++ Testing Framework (Google Test) +// +// This header file defines the Message class. +// +// IMPORTANT NOTE: Due to limitation of the C++ language, we have to +// leave some internal implementation details in this header file. +// They are clearly marked by comments like this: +// +// // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +// +// Such code is NOT meant to be used by a user directly, and is subject +// to CHANGE WITHOUT NOTICE. Therefore DO NOT DEPEND ON IT in a user +// program! + +#ifndef GTEST_INCLUDE_GTEST_GTEST_MESSAGE_H_ +#define GTEST_INCLUDE_GTEST_GTEST_MESSAGE_H_ + +#include + +#include "gtest/internal/gtest-port.h" + +// Ensures that there is at least one operator<< in the global namespace. +// See Message& operator<<(...) below for why. +void operator<<(const testing::internal::Secret&, int); + +namespace testing { + +// The Message class works like an ostream repeater. +// +// Typical usage: +// +// 1. You stream a bunch of values to a Message object. +// It will remember the text in a stringstream. +// 2. Then you stream the Message object to an ostream. +// This causes the text in the Message to be streamed +// to the ostream. +// +// For example; +// +// testing::Message foo; +// foo << 1 << " != " << 2; +// std::cout << foo; +// +// will print "1 != 2". +// +// Message is not intended to be inherited from. In particular, its +// destructor is not virtual. +// +// Note that stringstream behaves differently in gcc and in MSVC. You +// can stream a NULL char pointer to it in the former, but not in the +// latter (it causes an access violation if you do). The Message +// class hides this difference by treating a NULL char pointer as +// "(null)". +class GTEST_API_ Message { + private: + // The type of basic IO manipulators (endl, ends, and flush) for + // narrow streams. + typedef std::ostream& (*BasicNarrowIoManip)(std::ostream&); + + public: + // Constructs an empty Message. + Message(); + + // Copy constructor. + Message(const Message& msg) : ss_(new ::std::stringstream) { // NOLINT + *ss_ << msg.GetString(); + } + + // Constructs a Message from a C-string. + explicit Message(const char* str) : ss_(new ::std::stringstream) { + *ss_ << str; + } + +#if GTEST_OS_SYMBIAN + // Streams a value (either a pointer or not) to this object. + template + inline Message& operator <<(const T& value) { + StreamHelper(typename internal::is_pointer::type(), value); + return *this; + } +#else + // Streams a non-pointer value to this object. + template + inline Message& operator <<(const T& val) { + // Some libraries overload << for STL containers. These + // overloads are defined in the global namespace instead of ::std. + // + // C++'s symbol lookup rule (i.e. Koenig lookup) says that these + // overloads are visible in either the std namespace or the global + // namespace, but not other namespaces, including the testing + // namespace which Google Test's Message class is in. + // + // To allow STL containers (and other types that has a << operator + // defined in the global namespace) to be used in Google Test + // assertions, testing::Message must access the custom << operator + // from the global namespace. With this using declaration, + // overloads of << defined in the global namespace and those + // visible via Koenig lookup are both exposed in this function. + using ::operator <<; + *ss_ << val; + return *this; + } + + // Streams a pointer value to this object. + // + // This function is an overload of the previous one. When you + // stream a pointer to a Message, this definition will be used as it + // is more specialized. (The C++ Standard, section + // [temp.func.order].) If you stream a non-pointer, then the + // previous definition will be used. + // + // The reason for this overload is that streaming a NULL pointer to + // ostream is undefined behavior. Depending on the compiler, you + // may get "0", "(nil)", "(null)", or an access violation. To + // ensure consistent result across compilers, we always treat NULL + // as "(null)". + template + inline Message& operator <<(T* const& pointer) { // NOLINT + if (pointer == NULL) { + *ss_ << "(null)"; + } else { + *ss_ << pointer; + } + return *this; + } +#endif // GTEST_OS_SYMBIAN + + // Since the basic IO manipulators are overloaded for both narrow + // and wide streams, we have to provide this specialized definition + // of operator <<, even though its body is the same as the + // templatized version above. Without this definition, streaming + // endl or other basic IO manipulators to Message will confuse the + // compiler. + Message& operator <<(BasicNarrowIoManip val) { + *ss_ << val; + return *this; + } + + // Instead of 1/0, we want to see true/false for bool values. + Message& operator <<(bool b) { + return *this << (b ? "true" : "false"); + } + + // These two overloads allow streaming a wide C string to a Message + // using the UTF-8 encoding. + Message& operator <<(const wchar_t* wide_c_str); + Message& operator <<(wchar_t* wide_c_str); + +#if GTEST_HAS_STD_WSTRING + // Converts the given wide string to a narrow string using the UTF-8 + // encoding, and streams the result to this Message object. + Message& operator <<(const ::std::wstring& wstr); +#endif // GTEST_HAS_STD_WSTRING + +#if GTEST_HAS_GLOBAL_WSTRING + // Converts the given wide string to a narrow string using the UTF-8 + // encoding, and streams the result to this Message object. + Message& operator <<(const ::wstring& wstr); +#endif // GTEST_HAS_GLOBAL_WSTRING + + // Gets the text streamed to this object so far as an std::string. + // Each '\0' character in the buffer is replaced with "\\0". + // + // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. + std::string GetString() const; + + private: + +#if GTEST_OS_SYMBIAN + // These are needed as the Nokia Symbian Compiler cannot decide between + // const T& and const T* in a function template. The Nokia compiler _can_ + // decide between class template specializations for T and T*, so a + // tr1::type_traits-like is_pointer works, and we can overload on that. + template + inline void StreamHelper(internal::true_type /*is_pointer*/, T* pointer) { + if (pointer == NULL) { + *ss_ << "(null)"; + } else { + *ss_ << pointer; + } + } + template + inline void StreamHelper(internal::false_type /*is_pointer*/, + const T& value) { + // See the comments in Message& operator <<(const T&) above for why + // we need this using statement. + using ::operator <<; + *ss_ << value; + } +#endif // GTEST_OS_SYMBIAN + + // We'll hold the text streamed to this object here. + const internal::scoped_ptr< ::std::stringstream> ss_; + + // We declare (but don't implement) this to prevent the compiler + // from implementing the assignment operator. + void operator=(const Message&); +}; + +// Streams a Message to an ostream. +inline std::ostream& operator <<(std::ostream& os, const Message& sb) { + return os << sb.GetString(); +} + +namespace internal { + +// Converts a streamable value to an std::string. A NULL pointer is +// converted to "(null)". When the input value is a ::string, +// ::std::string, ::wstring, or ::std::wstring object, each NUL +// character in it is replaced with "\\0". +template +std::string StreamableToString(const T& streamable) { + return (Message() << streamable).GetString(); +} + +} // namespace internal +} // namespace testing + +#endif // GTEST_INCLUDE_GTEST_GTEST_MESSAGE_H_ diff --git a/vendor/gmock-1.7.0/gtest/include/gtest/gtest-param-test.h b/vendor/gmock-1.7.0/gtest/include/gtest/gtest-param-test.h new file mode 100644 index 0000000000..d6702c8f16 --- /dev/null +++ b/vendor/gmock-1.7.0/gtest/include/gtest/gtest-param-test.h @@ -0,0 +1,1421 @@ +// This file was GENERATED by command: +// pump.py gtest-param-test.h.pump +// DO NOT EDIT BY HAND!!! + +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Authors: vladl@google.com (Vlad Losev) +// +// Macros and functions for implementing parameterized tests +// in Google C++ Testing Framework (Google Test) +// +// This file is generated by a SCRIPT. DO NOT EDIT BY HAND! +// +#ifndef GTEST_INCLUDE_GTEST_GTEST_PARAM_TEST_H_ +#define GTEST_INCLUDE_GTEST_GTEST_PARAM_TEST_H_ + + +// Value-parameterized tests allow you to test your code with different +// parameters without writing multiple copies of the same test. +// +// Here is how you use value-parameterized tests: + +#if 0 + +// To write value-parameterized tests, first you should define a fixture +// class. It is usually derived from testing::TestWithParam (see below for +// another inheritance scheme that's sometimes useful in more complicated +// class hierarchies), where the type of your parameter values. +// TestWithParam is itself derived from testing::Test. T can be any +// copyable type. If it's a raw pointer, you are responsible for managing the +// lifespan of the pointed values. + +class FooTest : public ::testing::TestWithParam { + // You can implement all the usual class fixture members here. +}; + +// Then, use the TEST_P macro to define as many parameterized tests +// for this fixture as you want. The _P suffix is for "parameterized" +// or "pattern", whichever you prefer to think. + +TEST_P(FooTest, DoesBlah) { + // Inside a test, access the test parameter with the GetParam() method + // of the TestWithParam class: + EXPECT_TRUE(foo.Blah(GetParam())); + ... +} + +TEST_P(FooTest, HasBlahBlah) { + ... +} + +// Finally, you can use INSTANTIATE_TEST_CASE_P to instantiate the test +// case with any set of parameters you want. Google Test defines a number +// of functions for generating test parameters. They return what we call +// (surprise!) parameter generators. Here is a summary of them, which +// are all in the testing namespace: +// +// +// Range(begin, end [, step]) - Yields values {begin, begin+step, +// begin+step+step, ...}. The values do not +// include end. step defaults to 1. +// Values(v1, v2, ..., vN) - Yields values {v1, v2, ..., vN}. +// ValuesIn(container) - Yields values from a C-style array, an STL +// ValuesIn(begin,end) container, or an iterator range [begin, end). +// Bool() - Yields sequence {false, true}. +// Combine(g1, g2, ..., gN) - Yields all combinations (the Cartesian product +// for the math savvy) of the values generated +// by the N generators. +// +// For more details, see comments at the definitions of these functions below +// in this file. +// +// The following statement will instantiate tests from the FooTest test case +// each with parameter values "meeny", "miny", and "moe". + +INSTANTIATE_TEST_CASE_P(InstantiationName, + FooTest, + Values("meeny", "miny", "moe")); + +// To distinguish different instances of the pattern, (yes, you +// can instantiate it more then once) the first argument to the +// INSTANTIATE_TEST_CASE_P macro is a prefix that will be added to the +// actual test case name. Remember to pick unique prefixes for different +// instantiations. The tests from the instantiation above will have +// these names: +// +// * InstantiationName/FooTest.DoesBlah/0 for "meeny" +// * InstantiationName/FooTest.DoesBlah/1 for "miny" +// * InstantiationName/FooTest.DoesBlah/2 for "moe" +// * InstantiationName/FooTest.HasBlahBlah/0 for "meeny" +// * InstantiationName/FooTest.HasBlahBlah/1 for "miny" +// * InstantiationName/FooTest.HasBlahBlah/2 for "moe" +// +// You can use these names in --gtest_filter. +// +// This statement will instantiate all tests from FooTest again, each +// with parameter values "cat" and "dog": + +const char* pets[] = {"cat", "dog"}; +INSTANTIATE_TEST_CASE_P(AnotherInstantiationName, FooTest, ValuesIn(pets)); + +// The tests from the instantiation above will have these names: +// +// * AnotherInstantiationName/FooTest.DoesBlah/0 for "cat" +// * AnotherInstantiationName/FooTest.DoesBlah/1 for "dog" +// * AnotherInstantiationName/FooTest.HasBlahBlah/0 for "cat" +// * AnotherInstantiationName/FooTest.HasBlahBlah/1 for "dog" +// +// Please note that INSTANTIATE_TEST_CASE_P will instantiate all tests +// in the given test case, whether their definitions come before or +// AFTER the INSTANTIATE_TEST_CASE_P statement. +// +// Please also note that generator expressions (including parameters to the +// generators) are evaluated in InitGoogleTest(), after main() has started. +// This allows the user on one hand, to adjust generator parameters in order +// to dynamically determine a set of tests to run and on the other hand, +// give the user a chance to inspect the generated tests with Google Test +// reflection API before RUN_ALL_TESTS() is executed. +// +// You can see samples/sample7_unittest.cc and samples/sample8_unittest.cc +// for more examples. +// +// In the future, we plan to publish the API for defining new parameter +// generators. But for now this interface remains part of the internal +// implementation and is subject to change. +// +// +// A parameterized test fixture must be derived from testing::Test and from +// testing::WithParamInterface, where T is the type of the parameter +// values. Inheriting from TestWithParam satisfies that requirement because +// TestWithParam inherits from both Test and WithParamInterface. In more +// complicated hierarchies, however, it is occasionally useful to inherit +// separately from Test and WithParamInterface. For example: + +class BaseTest : public ::testing::Test { + // You can inherit all the usual members for a non-parameterized test + // fixture here. +}; + +class DerivedTest : public BaseTest, public ::testing::WithParamInterface { + // The usual test fixture members go here too. +}; + +TEST_F(BaseTest, HasFoo) { + // This is an ordinary non-parameterized test. +} + +TEST_P(DerivedTest, DoesBlah) { + // GetParam works just the same here as if you inherit from TestWithParam. + EXPECT_TRUE(foo.Blah(GetParam())); +} + +#endif // 0 + +#include "gtest/internal/gtest-port.h" + +#if !GTEST_OS_SYMBIAN +# include +#endif + +// scripts/fuse_gtest.py depends on gtest's own header being #included +// *unconditionally*. Therefore these #includes cannot be moved +// inside #if GTEST_HAS_PARAM_TEST. +#include "gtest/internal/gtest-internal.h" +#include "gtest/internal/gtest-param-util.h" +#include "gtest/internal/gtest-param-util-generated.h" + +#if GTEST_HAS_PARAM_TEST + +namespace testing { + +// Functions producing parameter generators. +// +// Google Test uses these generators to produce parameters for value- +// parameterized tests. When a parameterized test case is instantiated +// with a particular generator, Google Test creates and runs tests +// for each element in the sequence produced by the generator. +// +// In the following sample, tests from test case FooTest are instantiated +// each three times with parameter values 3, 5, and 8: +// +// class FooTest : public TestWithParam { ... }; +// +// TEST_P(FooTest, TestThis) { +// } +// TEST_P(FooTest, TestThat) { +// } +// INSTANTIATE_TEST_CASE_P(TestSequence, FooTest, Values(3, 5, 8)); +// + +// Range() returns generators providing sequences of values in a range. +// +// Synopsis: +// Range(start, end) +// - returns a generator producing a sequence of values {start, start+1, +// start+2, ..., }. +// Range(start, end, step) +// - returns a generator producing a sequence of values {start, start+step, +// start+step+step, ..., }. +// Notes: +// * The generated sequences never include end. For example, Range(1, 5) +// returns a generator producing a sequence {1, 2, 3, 4}. Range(1, 9, 2) +// returns a generator producing {1, 3, 5, 7}. +// * start and end must have the same type. That type may be any integral or +// floating-point type or a user defined type satisfying these conditions: +// * It must be assignable (have operator=() defined). +// * It must have operator+() (operator+(int-compatible type) for +// two-operand version). +// * It must have operator<() defined. +// Elements in the resulting sequences will also have that type. +// * Condition start < end must be satisfied in order for resulting sequences +// to contain any elements. +// +template +internal::ParamGenerator Range(T start, T end, IncrementT step) { + return internal::ParamGenerator( + new internal::RangeGenerator(start, end, step)); +} + +template +internal::ParamGenerator Range(T start, T end) { + return Range(start, end, 1); +} + +// ValuesIn() function allows generation of tests with parameters coming from +// a container. +// +// Synopsis: +// ValuesIn(const T (&array)[N]) +// - returns a generator producing sequences with elements from +// a C-style array. +// ValuesIn(const Container& container) +// - returns a generator producing sequences with elements from +// an STL-style container. +// ValuesIn(Iterator begin, Iterator end) +// - returns a generator producing sequences with elements from +// a range [begin, end) defined by a pair of STL-style iterators. These +// iterators can also be plain C pointers. +// +// Please note that ValuesIn copies the values from the containers +// passed in and keeps them to generate tests in RUN_ALL_TESTS(). +// +// Examples: +// +// This instantiates tests from test case StringTest +// each with C-string values of "foo", "bar", and "baz": +// +// const char* strings[] = {"foo", "bar", "baz"}; +// INSTANTIATE_TEST_CASE_P(StringSequence, SrtingTest, ValuesIn(strings)); +// +// This instantiates tests from test case StlStringTest +// each with STL strings with values "a" and "b": +// +// ::std::vector< ::std::string> GetParameterStrings() { +// ::std::vector< ::std::string> v; +// v.push_back("a"); +// v.push_back("b"); +// return v; +// } +// +// INSTANTIATE_TEST_CASE_P(CharSequence, +// StlStringTest, +// ValuesIn(GetParameterStrings())); +// +// +// This will also instantiate tests from CharTest +// each with parameter values 'a' and 'b': +// +// ::std::list GetParameterChars() { +// ::std::list list; +// list.push_back('a'); +// list.push_back('b'); +// return list; +// } +// ::std::list l = GetParameterChars(); +// INSTANTIATE_TEST_CASE_P(CharSequence2, +// CharTest, +// ValuesIn(l.begin(), l.end())); +// +template +internal::ParamGenerator< + typename ::testing::internal::IteratorTraits::value_type> +ValuesIn(ForwardIterator begin, ForwardIterator end) { + typedef typename ::testing::internal::IteratorTraits + ::value_type ParamType; + return internal::ParamGenerator( + new internal::ValuesInIteratorRangeGenerator(begin, end)); +} + +template +internal::ParamGenerator ValuesIn(const T (&array)[N]) { + return ValuesIn(array, array + N); +} + +template +internal::ParamGenerator ValuesIn( + const Container& container) { + return ValuesIn(container.begin(), container.end()); +} + +// Values() allows generating tests from explicitly specified list of +// parameters. +// +// Synopsis: +// Values(T v1, T v2, ..., T vN) +// - returns a generator producing sequences with elements v1, v2, ..., vN. +// +// For example, this instantiates tests from test case BarTest each +// with values "one", "two", and "three": +// +// INSTANTIATE_TEST_CASE_P(NumSequence, BarTest, Values("one", "two", "three")); +// +// This instantiates tests from test case BazTest each with values 1, 2, 3.5. +// The exact type of values will depend on the type of parameter in BazTest. +// +// INSTANTIATE_TEST_CASE_P(FloatingNumbers, BazTest, Values(1, 2, 3.5)); +// +// Currently, Values() supports from 1 to 50 parameters. +// +template +internal::ValueArray1 Values(T1 v1) { + return internal::ValueArray1(v1); +} + +template +internal::ValueArray2 Values(T1 v1, T2 v2) { + return internal::ValueArray2(v1, v2); +} + +template +internal::ValueArray3 Values(T1 v1, T2 v2, T3 v3) { + return internal::ValueArray3(v1, v2, v3); +} + +template +internal::ValueArray4 Values(T1 v1, T2 v2, T3 v3, T4 v4) { + return internal::ValueArray4(v1, v2, v3, v4); +} + +template +internal::ValueArray5 Values(T1 v1, T2 v2, T3 v3, T4 v4, + T5 v5) { + return internal::ValueArray5(v1, v2, v3, v4, v5); +} + +template +internal::ValueArray6 Values(T1 v1, T2 v2, T3 v3, + T4 v4, T5 v5, T6 v6) { + return internal::ValueArray6(v1, v2, v3, v4, v5, v6); +} + +template +internal::ValueArray7 Values(T1 v1, T2 v2, T3 v3, + T4 v4, T5 v5, T6 v6, T7 v7) { + return internal::ValueArray7(v1, v2, v3, v4, v5, + v6, v7); +} + +template +internal::ValueArray8 Values(T1 v1, T2 v2, + T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8) { + return internal::ValueArray8(v1, v2, v3, v4, + v5, v6, v7, v8); +} + +template +internal::ValueArray9 Values(T1 v1, T2 v2, + T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9) { + return internal::ValueArray9(v1, v2, v3, + v4, v5, v6, v7, v8, v9); +} + +template +internal::ValueArray10 Values(T1 v1, + T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10) { + return internal::ValueArray10(v1, + v2, v3, v4, v5, v6, v7, v8, v9, v10); +} + +template +internal::ValueArray11 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11) { + return internal::ValueArray11(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11); +} + +template +internal::ValueArray12 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12) { + return internal::ValueArray12(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12); +} + +template +internal::ValueArray13 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13) { + return internal::ValueArray13(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13); +} + +template +internal::ValueArray14 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14) { + return internal::ValueArray14(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, + v14); +} + +template +internal::ValueArray15 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, + T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15) { + return internal::ValueArray15(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, + v13, v14, v15); +} + +template +internal::ValueArray16 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, + T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, + T16 v16) { + return internal::ValueArray16(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, + v12, v13, v14, v15, v16); +} + +template +internal::ValueArray17 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, + T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, + T16 v16, T17 v17) { + return internal::ValueArray17(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, + v11, v12, v13, v14, v15, v16, v17); +} + +template +internal::ValueArray18 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, + T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, + T16 v16, T17 v17, T18 v18) { + return internal::ValueArray18(v1, v2, v3, v4, v5, v6, v7, v8, v9, + v10, v11, v12, v13, v14, v15, v16, v17, v18); +} + +template +internal::ValueArray19 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, + T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, + T15 v15, T16 v16, T17 v17, T18 v18, T19 v19) { + return internal::ValueArray19(v1, v2, v3, v4, v5, v6, v7, v8, + v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19); +} + +template +internal::ValueArray20 Values(T1 v1, T2 v2, T3 v3, T4 v4, + T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, + T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20) { + return internal::ValueArray20(v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20); +} + +template +internal::ValueArray21 Values(T1 v1, T2 v2, T3 v3, T4 v4, + T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, + T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21) { + return internal::ValueArray21(v1, v2, v3, v4, v5, v6, + v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21); +} + +template +internal::ValueArray22 Values(T1 v1, T2 v2, T3 v3, + T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, + T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, + T21 v21, T22 v22) { + return internal::ValueArray22(v1, v2, v3, v4, + v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, + v20, v21, v22); +} + +template +internal::ValueArray23 Values(T1 v1, T2 v2, + T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, + T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, + T21 v21, T22 v22, T23 v23) { + return internal::ValueArray23(v1, v2, v3, + v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, + v20, v21, v22, v23); +} + +template +internal::ValueArray24 Values(T1 v1, T2 v2, + T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, + T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, + T21 v21, T22 v22, T23 v23, T24 v24) { + return internal::ValueArray24(v1, v2, + v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, + v19, v20, v21, v22, v23, v24); +} + +template +internal::ValueArray25 Values(T1 v1, + T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, + T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, + T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25) { + return internal::ValueArray25(v1, + v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, + v18, v19, v20, v21, v22, v23, v24, v25); +} + +template +internal::ValueArray26 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26) { + return internal::ValueArray26(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, + v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26); +} + +template +internal::ValueArray27 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27) { + return internal::ValueArray27(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, + v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27); +} + +template +internal::ValueArray28 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28) { + return internal::ValueArray28(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, + v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, + v28); +} + +template +internal::ValueArray29 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29) { + return internal::ValueArray29(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, + v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, + v27, v28, v29); +} + +template +internal::ValueArray30 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, + T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, + T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, + T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30) { + return internal::ValueArray30(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, + v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, + v26, v27, v28, v29, v30); +} + +template +internal::ValueArray31 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, + T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, + T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, + T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31) { + return internal::ValueArray31(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, + v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, + v25, v26, v27, v28, v29, v30, v31); +} + +template +internal::ValueArray32 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, + T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, + T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, + T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, + T32 v32) { + return internal::ValueArray32(v1, v2, v3, v4, v5, v6, v7, v8, v9, + v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, + v24, v25, v26, v27, v28, v29, v30, v31, v32); +} + +template +internal::ValueArray33 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, + T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, + T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, + T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, + T32 v32, T33 v33) { + return internal::ValueArray33(v1, v2, v3, v4, v5, v6, v7, v8, + v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, + v24, v25, v26, v27, v28, v29, v30, v31, v32, v33); +} + +template +internal::ValueArray34 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, + T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, + T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, + T23 v23, T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, + T31 v31, T32 v32, T33 v33, T34 v34) { + return internal::ValueArray34(v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, + v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34); +} + +template +internal::ValueArray35 Values(T1 v1, T2 v2, T3 v3, T4 v4, + T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, + T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, + T22 v22, T23 v23, T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, + T30 v30, T31 v31, T32 v32, T33 v33, T34 v34, T35 v35) { + return internal::ValueArray35(v1, v2, v3, v4, v5, v6, + v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, + v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35); +} + +template +internal::ValueArray36 Values(T1 v1, T2 v2, T3 v3, T4 v4, + T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, + T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, + T22 v22, T23 v23, T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, + T30 v30, T31 v31, T32 v32, T33 v33, T34 v34, T35 v35, T36 v36) { + return internal::ValueArray36(v1, v2, v3, v4, + v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, + v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, + v34, v35, v36); +} + +template +internal::ValueArray37 Values(T1 v1, T2 v2, T3 v3, + T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, + T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, + T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, + T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, T34 v34, T35 v35, T36 v36, + T37 v37) { + return internal::ValueArray37(v1, v2, v3, + v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, + v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, + v34, v35, v36, v37); +} + +template +internal::ValueArray38 Values(T1 v1, T2 v2, + T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, + T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, + T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, + T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, T34 v34, T35 v35, T36 v36, + T37 v37, T38 v38) { + return internal::ValueArray38(v1, v2, + v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, + v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, + v33, v34, v35, v36, v37, v38); +} + +template +internal::ValueArray39 Values(T1 v1, T2 v2, + T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, + T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, + T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, + T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, T34 v34, T35 v35, T36 v36, + T37 v37, T38 v38, T39 v39) { + return internal::ValueArray39(v1, + v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, + v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, + v32, v33, v34, v35, v36, v37, v38, v39); +} + +template +internal::ValueArray40 Values(T1 v1, + T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, + T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, + T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, T26 v26, T27 v27, + T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, T34 v34, T35 v35, + T36 v36, T37 v37, T38 v38, T39 v39, T40 v40) { + return internal::ValueArray40(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, + v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, + v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40); +} + +template +internal::ValueArray41 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41) { + return internal::ValueArray41(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, + v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, + v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41); +} + +template +internal::ValueArray42 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42) { + return internal::ValueArray42(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, + v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, + v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, + v42); +} + +template +internal::ValueArray43 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42, T43 v43) { + return internal::ValueArray43(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, + v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, + v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, + v41, v42, v43); +} + +template +internal::ValueArray44 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42, T43 v43, T44 v44) { + return internal::ValueArray44(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, + v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, + v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, + v40, v41, v42, v43, v44); +} + +template +internal::ValueArray45 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, + T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, + T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, + T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, + T33 v33, T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, + T41 v41, T42 v42, T43 v43, T44 v44, T45 v45) { + return internal::ValueArray45(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, + v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, + v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, + v39, v40, v41, v42, v43, v44, v45); +} + +template +internal::ValueArray46 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, + T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, + T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, + T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, + T32 v32, T33 v33, T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, + T40 v40, T41 v41, T42 v42, T43 v43, T44 v44, T45 v45, T46 v46) { + return internal::ValueArray46(v1, v2, v3, v4, v5, v6, v7, v8, v9, + v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, + v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, + v38, v39, v40, v41, v42, v43, v44, v45, v46); +} + +template +internal::ValueArray47 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, + T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, + T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, + T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, + T32 v32, T33 v33, T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, + T40 v40, T41 v41, T42 v42, T43 v43, T44 v44, T45 v45, T46 v46, T47 v47) { + return internal::ValueArray47(v1, v2, v3, v4, v5, v6, v7, v8, + v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, + v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, + v38, v39, v40, v41, v42, v43, v44, v45, v46, v47); +} + +template +internal::ValueArray48 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, + T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, + T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, + T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, + T32 v32, T33 v33, T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, + T40 v40, T41 v41, T42 v42, T43 v43, T44 v44, T45 v45, T46 v46, T47 v47, + T48 v48) { + return internal::ValueArray48(v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, + v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, + v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48); +} + +template +internal::ValueArray49 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, + T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, + T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, + T23 v23, T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, + T31 v31, T32 v32, T33 v33, T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, + T39 v39, T40 v40, T41 v41, T42 v42, T43 v43, T44 v44, T45 v45, T46 v46, + T47 v47, T48 v48, T49 v49) { + return internal::ValueArray49(v1, v2, v3, v4, v5, v6, + v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, + v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, + v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49); +} + +template +internal::ValueArray50 Values(T1 v1, T2 v2, T3 v3, T4 v4, + T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, + T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, + T22 v22, T23 v23, T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, + T30 v30, T31 v31, T32 v32, T33 v33, T34 v34, T35 v35, T36 v36, T37 v37, + T38 v38, T39 v39, T40 v40, T41 v41, T42 v42, T43 v43, T44 v44, T45 v45, + T46 v46, T47 v47, T48 v48, T49 v49, T50 v50) { + return internal::ValueArray50(v1, v2, v3, v4, + v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, + v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, + v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, + v48, v49, v50); +} + +// Bool() allows generating tests with parameters in a set of (false, true). +// +// Synopsis: +// Bool() +// - returns a generator producing sequences with elements {false, true}. +// +// It is useful when testing code that depends on Boolean flags. Combinations +// of multiple flags can be tested when several Bool()'s are combined using +// Combine() function. +// +// In the following example all tests in the test case FlagDependentTest +// will be instantiated twice with parameters false and true. +// +// class FlagDependentTest : public testing::TestWithParam { +// virtual void SetUp() { +// external_flag = GetParam(); +// } +// } +// INSTANTIATE_TEST_CASE_P(BoolSequence, FlagDependentTest, Bool()); +// +inline internal::ParamGenerator Bool() { + return Values(false, true); +} + +# if GTEST_HAS_COMBINE +// Combine() allows the user to combine two or more sequences to produce +// values of a Cartesian product of those sequences' elements. +// +// Synopsis: +// Combine(gen1, gen2, ..., genN) +// - returns a generator producing sequences with elements coming from +// the Cartesian product of elements from the sequences generated by +// gen1, gen2, ..., genN. The sequence elements will have a type of +// tuple where T1, T2, ..., TN are the types +// of elements from sequences produces by gen1, gen2, ..., genN. +// +// Combine can have up to 10 arguments. This number is currently limited +// by the maximum number of elements in the tuple implementation used by Google +// Test. +// +// Example: +// +// This will instantiate tests in test case AnimalTest each one with +// the parameter values tuple("cat", BLACK), tuple("cat", WHITE), +// tuple("dog", BLACK), and tuple("dog", WHITE): +// +// enum Color { BLACK, GRAY, WHITE }; +// class AnimalTest +// : public testing::TestWithParam > {...}; +// +// TEST_P(AnimalTest, AnimalLooksNice) {...} +// +// INSTANTIATE_TEST_CASE_P(AnimalVariations, AnimalTest, +// Combine(Values("cat", "dog"), +// Values(BLACK, WHITE))); +// +// This will instantiate tests in FlagDependentTest with all variations of two +// Boolean flags: +// +// class FlagDependentTest +// : public testing::TestWithParam > { +// virtual void SetUp() { +// // Assigns external_flag_1 and external_flag_2 values from the tuple. +// tie(external_flag_1, external_flag_2) = GetParam(); +// } +// }; +// +// TEST_P(FlagDependentTest, TestFeature1) { +// // Test your code using external_flag_1 and external_flag_2 here. +// } +// INSTANTIATE_TEST_CASE_P(TwoBoolSequence, FlagDependentTest, +// Combine(Bool(), Bool())); +// +template +internal::CartesianProductHolder2 Combine( + const Generator1& g1, const Generator2& g2) { + return internal::CartesianProductHolder2( + g1, g2); +} + +template +internal::CartesianProductHolder3 Combine( + const Generator1& g1, const Generator2& g2, const Generator3& g3) { + return internal::CartesianProductHolder3( + g1, g2, g3); +} + +template +internal::CartesianProductHolder4 Combine( + const Generator1& g1, const Generator2& g2, const Generator3& g3, + const Generator4& g4) { + return internal::CartesianProductHolder4( + g1, g2, g3, g4); +} + +template +internal::CartesianProductHolder5 Combine( + const Generator1& g1, const Generator2& g2, const Generator3& g3, + const Generator4& g4, const Generator5& g5) { + return internal::CartesianProductHolder5( + g1, g2, g3, g4, g5); +} + +template +internal::CartesianProductHolder6 Combine( + const Generator1& g1, const Generator2& g2, const Generator3& g3, + const Generator4& g4, const Generator5& g5, const Generator6& g6) { + return internal::CartesianProductHolder6( + g1, g2, g3, g4, g5, g6); +} + +template +internal::CartesianProductHolder7 Combine( + const Generator1& g1, const Generator2& g2, const Generator3& g3, + const Generator4& g4, const Generator5& g5, const Generator6& g6, + const Generator7& g7) { + return internal::CartesianProductHolder7( + g1, g2, g3, g4, g5, g6, g7); +} + +template +internal::CartesianProductHolder8 Combine( + const Generator1& g1, const Generator2& g2, const Generator3& g3, + const Generator4& g4, const Generator5& g5, const Generator6& g6, + const Generator7& g7, const Generator8& g8) { + return internal::CartesianProductHolder8( + g1, g2, g3, g4, g5, g6, g7, g8); +} + +template +internal::CartesianProductHolder9 Combine( + const Generator1& g1, const Generator2& g2, const Generator3& g3, + const Generator4& g4, const Generator5& g5, const Generator6& g6, + const Generator7& g7, const Generator8& g8, const Generator9& g9) { + return internal::CartesianProductHolder9( + g1, g2, g3, g4, g5, g6, g7, g8, g9); +} + +template +internal::CartesianProductHolder10 Combine( + const Generator1& g1, const Generator2& g2, const Generator3& g3, + const Generator4& g4, const Generator5& g5, const Generator6& g6, + const Generator7& g7, const Generator8& g8, const Generator9& g9, + const Generator10& g10) { + return internal::CartesianProductHolder10( + g1, g2, g3, g4, g5, g6, g7, g8, g9, g10); +} +# endif // GTEST_HAS_COMBINE + + + +# define TEST_P(test_case_name, test_name) \ + class GTEST_TEST_CLASS_NAME_(test_case_name, test_name) \ + : public test_case_name { \ + public: \ + GTEST_TEST_CLASS_NAME_(test_case_name, test_name)() {} \ + virtual void TestBody(); \ + private: \ + static int AddToRegistry() { \ + ::testing::UnitTest::GetInstance()->parameterized_test_registry(). \ + GetTestCasePatternHolder(\ + #test_case_name, __FILE__, __LINE__)->AddTestPattern(\ + #test_case_name, \ + #test_name, \ + new ::testing::internal::TestMetaFactory< \ + GTEST_TEST_CLASS_NAME_(test_case_name, test_name)>()); \ + return 0; \ + } \ + static int gtest_registering_dummy_; \ + GTEST_DISALLOW_COPY_AND_ASSIGN_(\ + GTEST_TEST_CLASS_NAME_(test_case_name, test_name)); \ + }; \ + int GTEST_TEST_CLASS_NAME_(test_case_name, \ + test_name)::gtest_registering_dummy_ = \ + GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::AddToRegistry(); \ + void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::TestBody() + +# define INSTANTIATE_TEST_CASE_P(prefix, test_case_name, generator) \ + ::testing::internal::ParamGenerator \ + gtest_##prefix##test_case_name##_EvalGenerator_() { return generator; } \ + int gtest_##prefix##test_case_name##_dummy_ = \ + ::testing::UnitTest::GetInstance()->parameterized_test_registry(). \ + GetTestCasePatternHolder(\ + #test_case_name, __FILE__, __LINE__)->AddTestCaseInstantiation(\ + #prefix, \ + >est_##prefix##test_case_name##_EvalGenerator_, \ + __FILE__, __LINE__) + +} // namespace testing + +#endif // GTEST_HAS_PARAM_TEST + +#endif // GTEST_INCLUDE_GTEST_GTEST_PARAM_TEST_H_ diff --git a/vendor/gmock-1.7.0/gtest/include/gtest/gtest-param-test.h.pump b/vendor/gmock-1.7.0/gtest/include/gtest/gtest-param-test.h.pump new file mode 100644 index 0000000000..2dc9303b5e --- /dev/null +++ b/vendor/gmock-1.7.0/gtest/include/gtest/gtest-param-test.h.pump @@ -0,0 +1,487 @@ +$$ -*- mode: c++; -*- +$var n = 50 $$ Maximum length of Values arguments we want to support. +$var maxtuple = 10 $$ Maximum number of Combine arguments we want to support. +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Authors: vladl@google.com (Vlad Losev) +// +// Macros and functions for implementing parameterized tests +// in Google C++ Testing Framework (Google Test) +// +// This file is generated by a SCRIPT. DO NOT EDIT BY HAND! +// +#ifndef GTEST_INCLUDE_GTEST_GTEST_PARAM_TEST_H_ +#define GTEST_INCLUDE_GTEST_GTEST_PARAM_TEST_H_ + + +// Value-parameterized tests allow you to test your code with different +// parameters without writing multiple copies of the same test. +// +// Here is how you use value-parameterized tests: + +#if 0 + +// To write value-parameterized tests, first you should define a fixture +// class. It is usually derived from testing::TestWithParam (see below for +// another inheritance scheme that's sometimes useful in more complicated +// class hierarchies), where the type of your parameter values. +// TestWithParam is itself derived from testing::Test. T can be any +// copyable type. If it's a raw pointer, you are responsible for managing the +// lifespan of the pointed values. + +class FooTest : public ::testing::TestWithParam { + // You can implement all the usual class fixture members here. +}; + +// Then, use the TEST_P macro to define as many parameterized tests +// for this fixture as you want. The _P suffix is for "parameterized" +// or "pattern", whichever you prefer to think. + +TEST_P(FooTest, DoesBlah) { + // Inside a test, access the test parameter with the GetParam() method + // of the TestWithParam class: + EXPECT_TRUE(foo.Blah(GetParam())); + ... +} + +TEST_P(FooTest, HasBlahBlah) { + ... +} + +// Finally, you can use INSTANTIATE_TEST_CASE_P to instantiate the test +// case with any set of parameters you want. Google Test defines a number +// of functions for generating test parameters. They return what we call +// (surprise!) parameter generators. Here is a summary of them, which +// are all in the testing namespace: +// +// +// Range(begin, end [, step]) - Yields values {begin, begin+step, +// begin+step+step, ...}. The values do not +// include end. step defaults to 1. +// Values(v1, v2, ..., vN) - Yields values {v1, v2, ..., vN}. +// ValuesIn(container) - Yields values from a C-style array, an STL +// ValuesIn(begin,end) container, or an iterator range [begin, end). +// Bool() - Yields sequence {false, true}. +// Combine(g1, g2, ..., gN) - Yields all combinations (the Cartesian product +// for the math savvy) of the values generated +// by the N generators. +// +// For more details, see comments at the definitions of these functions below +// in this file. +// +// The following statement will instantiate tests from the FooTest test case +// each with parameter values "meeny", "miny", and "moe". + +INSTANTIATE_TEST_CASE_P(InstantiationName, + FooTest, + Values("meeny", "miny", "moe")); + +// To distinguish different instances of the pattern, (yes, you +// can instantiate it more then once) the first argument to the +// INSTANTIATE_TEST_CASE_P macro is a prefix that will be added to the +// actual test case name. Remember to pick unique prefixes for different +// instantiations. The tests from the instantiation above will have +// these names: +// +// * InstantiationName/FooTest.DoesBlah/0 for "meeny" +// * InstantiationName/FooTest.DoesBlah/1 for "miny" +// * InstantiationName/FooTest.DoesBlah/2 for "moe" +// * InstantiationName/FooTest.HasBlahBlah/0 for "meeny" +// * InstantiationName/FooTest.HasBlahBlah/1 for "miny" +// * InstantiationName/FooTest.HasBlahBlah/2 for "moe" +// +// You can use these names in --gtest_filter. +// +// This statement will instantiate all tests from FooTest again, each +// with parameter values "cat" and "dog": + +const char* pets[] = {"cat", "dog"}; +INSTANTIATE_TEST_CASE_P(AnotherInstantiationName, FooTest, ValuesIn(pets)); + +// The tests from the instantiation above will have these names: +// +// * AnotherInstantiationName/FooTest.DoesBlah/0 for "cat" +// * AnotherInstantiationName/FooTest.DoesBlah/1 for "dog" +// * AnotherInstantiationName/FooTest.HasBlahBlah/0 for "cat" +// * AnotherInstantiationName/FooTest.HasBlahBlah/1 for "dog" +// +// Please note that INSTANTIATE_TEST_CASE_P will instantiate all tests +// in the given test case, whether their definitions come before or +// AFTER the INSTANTIATE_TEST_CASE_P statement. +// +// Please also note that generator expressions (including parameters to the +// generators) are evaluated in InitGoogleTest(), after main() has started. +// This allows the user on one hand, to adjust generator parameters in order +// to dynamically determine a set of tests to run and on the other hand, +// give the user a chance to inspect the generated tests with Google Test +// reflection API before RUN_ALL_TESTS() is executed. +// +// You can see samples/sample7_unittest.cc and samples/sample8_unittest.cc +// for more examples. +// +// In the future, we plan to publish the API for defining new parameter +// generators. But for now this interface remains part of the internal +// implementation and is subject to change. +// +// +// A parameterized test fixture must be derived from testing::Test and from +// testing::WithParamInterface, where T is the type of the parameter +// values. Inheriting from TestWithParam satisfies that requirement because +// TestWithParam inherits from both Test and WithParamInterface. In more +// complicated hierarchies, however, it is occasionally useful to inherit +// separately from Test and WithParamInterface. For example: + +class BaseTest : public ::testing::Test { + // You can inherit all the usual members for a non-parameterized test + // fixture here. +}; + +class DerivedTest : public BaseTest, public ::testing::WithParamInterface { + // The usual test fixture members go here too. +}; + +TEST_F(BaseTest, HasFoo) { + // This is an ordinary non-parameterized test. +} + +TEST_P(DerivedTest, DoesBlah) { + // GetParam works just the same here as if you inherit from TestWithParam. + EXPECT_TRUE(foo.Blah(GetParam())); +} + +#endif // 0 + +#include "gtest/internal/gtest-port.h" + +#if !GTEST_OS_SYMBIAN +# include +#endif + +// scripts/fuse_gtest.py depends on gtest's own header being #included +// *unconditionally*. Therefore these #includes cannot be moved +// inside #if GTEST_HAS_PARAM_TEST. +#include "gtest/internal/gtest-internal.h" +#include "gtest/internal/gtest-param-util.h" +#include "gtest/internal/gtest-param-util-generated.h" + +#if GTEST_HAS_PARAM_TEST + +namespace testing { + +// Functions producing parameter generators. +// +// Google Test uses these generators to produce parameters for value- +// parameterized tests. When a parameterized test case is instantiated +// with a particular generator, Google Test creates and runs tests +// for each element in the sequence produced by the generator. +// +// In the following sample, tests from test case FooTest are instantiated +// each three times with parameter values 3, 5, and 8: +// +// class FooTest : public TestWithParam { ... }; +// +// TEST_P(FooTest, TestThis) { +// } +// TEST_P(FooTest, TestThat) { +// } +// INSTANTIATE_TEST_CASE_P(TestSequence, FooTest, Values(3, 5, 8)); +// + +// Range() returns generators providing sequences of values in a range. +// +// Synopsis: +// Range(start, end) +// - returns a generator producing a sequence of values {start, start+1, +// start+2, ..., }. +// Range(start, end, step) +// - returns a generator producing a sequence of values {start, start+step, +// start+step+step, ..., }. +// Notes: +// * The generated sequences never include end. For example, Range(1, 5) +// returns a generator producing a sequence {1, 2, 3, 4}. Range(1, 9, 2) +// returns a generator producing {1, 3, 5, 7}. +// * start and end must have the same type. That type may be any integral or +// floating-point type or a user defined type satisfying these conditions: +// * It must be assignable (have operator=() defined). +// * It must have operator+() (operator+(int-compatible type) for +// two-operand version). +// * It must have operator<() defined. +// Elements in the resulting sequences will also have that type. +// * Condition start < end must be satisfied in order for resulting sequences +// to contain any elements. +// +template +internal::ParamGenerator Range(T start, T end, IncrementT step) { + return internal::ParamGenerator( + new internal::RangeGenerator(start, end, step)); +} + +template +internal::ParamGenerator Range(T start, T end) { + return Range(start, end, 1); +} + +// ValuesIn() function allows generation of tests with parameters coming from +// a container. +// +// Synopsis: +// ValuesIn(const T (&array)[N]) +// - returns a generator producing sequences with elements from +// a C-style array. +// ValuesIn(const Container& container) +// - returns a generator producing sequences with elements from +// an STL-style container. +// ValuesIn(Iterator begin, Iterator end) +// - returns a generator producing sequences with elements from +// a range [begin, end) defined by a pair of STL-style iterators. These +// iterators can also be plain C pointers. +// +// Please note that ValuesIn copies the values from the containers +// passed in and keeps them to generate tests in RUN_ALL_TESTS(). +// +// Examples: +// +// This instantiates tests from test case StringTest +// each with C-string values of "foo", "bar", and "baz": +// +// const char* strings[] = {"foo", "bar", "baz"}; +// INSTANTIATE_TEST_CASE_P(StringSequence, SrtingTest, ValuesIn(strings)); +// +// This instantiates tests from test case StlStringTest +// each with STL strings with values "a" and "b": +// +// ::std::vector< ::std::string> GetParameterStrings() { +// ::std::vector< ::std::string> v; +// v.push_back("a"); +// v.push_back("b"); +// return v; +// } +// +// INSTANTIATE_TEST_CASE_P(CharSequence, +// StlStringTest, +// ValuesIn(GetParameterStrings())); +// +// +// This will also instantiate tests from CharTest +// each with parameter values 'a' and 'b': +// +// ::std::list GetParameterChars() { +// ::std::list list; +// list.push_back('a'); +// list.push_back('b'); +// return list; +// } +// ::std::list l = GetParameterChars(); +// INSTANTIATE_TEST_CASE_P(CharSequence2, +// CharTest, +// ValuesIn(l.begin(), l.end())); +// +template +internal::ParamGenerator< + typename ::testing::internal::IteratorTraits::value_type> +ValuesIn(ForwardIterator begin, ForwardIterator end) { + typedef typename ::testing::internal::IteratorTraits + ::value_type ParamType; + return internal::ParamGenerator( + new internal::ValuesInIteratorRangeGenerator(begin, end)); +} + +template +internal::ParamGenerator ValuesIn(const T (&array)[N]) { + return ValuesIn(array, array + N); +} + +template +internal::ParamGenerator ValuesIn( + const Container& container) { + return ValuesIn(container.begin(), container.end()); +} + +// Values() allows generating tests from explicitly specified list of +// parameters. +// +// Synopsis: +// Values(T v1, T v2, ..., T vN) +// - returns a generator producing sequences with elements v1, v2, ..., vN. +// +// For example, this instantiates tests from test case BarTest each +// with values "one", "two", and "three": +// +// INSTANTIATE_TEST_CASE_P(NumSequence, BarTest, Values("one", "two", "three")); +// +// This instantiates tests from test case BazTest each with values 1, 2, 3.5. +// The exact type of values will depend on the type of parameter in BazTest. +// +// INSTANTIATE_TEST_CASE_P(FloatingNumbers, BazTest, Values(1, 2, 3.5)); +// +// Currently, Values() supports from 1 to $n parameters. +// +$range i 1..n +$for i [[ +$range j 1..i + +template <$for j, [[typename T$j]]> +internal::ValueArray$i<$for j, [[T$j]]> Values($for j, [[T$j v$j]]) { + return internal::ValueArray$i<$for j, [[T$j]]>($for j, [[v$j]]); +} + +]] + +// Bool() allows generating tests with parameters in a set of (false, true). +// +// Synopsis: +// Bool() +// - returns a generator producing sequences with elements {false, true}. +// +// It is useful when testing code that depends on Boolean flags. Combinations +// of multiple flags can be tested when several Bool()'s are combined using +// Combine() function. +// +// In the following example all tests in the test case FlagDependentTest +// will be instantiated twice with parameters false and true. +// +// class FlagDependentTest : public testing::TestWithParam { +// virtual void SetUp() { +// external_flag = GetParam(); +// } +// } +// INSTANTIATE_TEST_CASE_P(BoolSequence, FlagDependentTest, Bool()); +// +inline internal::ParamGenerator Bool() { + return Values(false, true); +} + +# if GTEST_HAS_COMBINE +// Combine() allows the user to combine two or more sequences to produce +// values of a Cartesian product of those sequences' elements. +// +// Synopsis: +// Combine(gen1, gen2, ..., genN) +// - returns a generator producing sequences with elements coming from +// the Cartesian product of elements from the sequences generated by +// gen1, gen2, ..., genN. The sequence elements will have a type of +// tuple where T1, T2, ..., TN are the types +// of elements from sequences produces by gen1, gen2, ..., genN. +// +// Combine can have up to $maxtuple arguments. This number is currently limited +// by the maximum number of elements in the tuple implementation used by Google +// Test. +// +// Example: +// +// This will instantiate tests in test case AnimalTest each one with +// the parameter values tuple("cat", BLACK), tuple("cat", WHITE), +// tuple("dog", BLACK), and tuple("dog", WHITE): +// +// enum Color { BLACK, GRAY, WHITE }; +// class AnimalTest +// : public testing::TestWithParam > {...}; +// +// TEST_P(AnimalTest, AnimalLooksNice) {...} +// +// INSTANTIATE_TEST_CASE_P(AnimalVariations, AnimalTest, +// Combine(Values("cat", "dog"), +// Values(BLACK, WHITE))); +// +// This will instantiate tests in FlagDependentTest with all variations of two +// Boolean flags: +// +// class FlagDependentTest +// : public testing::TestWithParam > { +// virtual void SetUp() { +// // Assigns external_flag_1 and external_flag_2 values from the tuple. +// tie(external_flag_1, external_flag_2) = GetParam(); +// } +// }; +// +// TEST_P(FlagDependentTest, TestFeature1) { +// // Test your code using external_flag_1 and external_flag_2 here. +// } +// INSTANTIATE_TEST_CASE_P(TwoBoolSequence, FlagDependentTest, +// Combine(Bool(), Bool())); +// +$range i 2..maxtuple +$for i [[ +$range j 1..i + +template <$for j, [[typename Generator$j]]> +internal::CartesianProductHolder$i<$for j, [[Generator$j]]> Combine( + $for j, [[const Generator$j& g$j]]) { + return internal::CartesianProductHolder$i<$for j, [[Generator$j]]>( + $for j, [[g$j]]); +} + +]] +# endif // GTEST_HAS_COMBINE + + + +# define TEST_P(test_case_name, test_name) \ + class GTEST_TEST_CLASS_NAME_(test_case_name, test_name) \ + : public test_case_name { \ + public: \ + GTEST_TEST_CLASS_NAME_(test_case_name, test_name)() {} \ + virtual void TestBody(); \ + private: \ + static int AddToRegistry() { \ + ::testing::UnitTest::GetInstance()->parameterized_test_registry(). \ + GetTestCasePatternHolder(\ + #test_case_name, __FILE__, __LINE__)->AddTestPattern(\ + #test_case_name, \ + #test_name, \ + new ::testing::internal::TestMetaFactory< \ + GTEST_TEST_CLASS_NAME_(test_case_name, test_name)>()); \ + return 0; \ + } \ + static int gtest_registering_dummy_; \ + GTEST_DISALLOW_COPY_AND_ASSIGN_(\ + GTEST_TEST_CLASS_NAME_(test_case_name, test_name)); \ + }; \ + int GTEST_TEST_CLASS_NAME_(test_case_name, \ + test_name)::gtest_registering_dummy_ = \ + GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::AddToRegistry(); \ + void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::TestBody() + +# define INSTANTIATE_TEST_CASE_P(prefix, test_case_name, generator) \ + ::testing::internal::ParamGenerator \ + gtest_##prefix##test_case_name##_EvalGenerator_() { return generator; } \ + int gtest_##prefix##test_case_name##_dummy_ = \ + ::testing::UnitTest::GetInstance()->parameterized_test_registry(). \ + GetTestCasePatternHolder(\ + #test_case_name, __FILE__, __LINE__)->AddTestCaseInstantiation(\ + #prefix, \ + >est_##prefix##test_case_name##_EvalGenerator_, \ + __FILE__, __LINE__) + +} // namespace testing + +#endif // GTEST_HAS_PARAM_TEST + +#endif // GTEST_INCLUDE_GTEST_GTEST_PARAM_TEST_H_ diff --git a/vendor/gmock-1.7.0/gtest/include/gtest/gtest-printers.h b/vendor/gmock-1.7.0/gtest/include/gtest/gtest-printers.h new file mode 100644 index 0000000000..0639d9f586 --- /dev/null +++ b/vendor/gmock-1.7.0/gtest/include/gtest/gtest-printers.h @@ -0,0 +1,855 @@ +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) + +// Google Test - The Google C++ Testing Framework +// +// This file implements a universal value printer that can print a +// value of any type T: +// +// void ::testing::internal::UniversalPrinter::Print(value, ostream_ptr); +// +// A user can teach this function how to print a class type T by +// defining either operator<<() or PrintTo() in the namespace that +// defines T. More specifically, the FIRST defined function in the +// following list will be used (assuming T is defined in namespace +// foo): +// +// 1. foo::PrintTo(const T&, ostream*) +// 2. operator<<(ostream&, const T&) defined in either foo or the +// global namespace. +// +// If none of the above is defined, it will print the debug string of +// the value if it is a protocol buffer, or print the raw bytes in the +// value otherwise. +// +// To aid debugging: when T is a reference type, the address of the +// value is also printed; when T is a (const) char pointer, both the +// pointer value and the NUL-terminated string it points to are +// printed. +// +// We also provide some convenient wrappers: +// +// // Prints a value to a string. For a (const or not) char +// // pointer, the NUL-terminated string (but not the pointer) is +// // printed. +// std::string ::testing::PrintToString(const T& value); +// +// // Prints a value tersely: for a reference type, the referenced +// // value (but not the address) is printed; for a (const or not) char +// // pointer, the NUL-terminated string (but not the pointer) is +// // printed. +// void ::testing::internal::UniversalTersePrint(const T& value, ostream*); +// +// // Prints value using the type inferred by the compiler. The difference +// // from UniversalTersePrint() is that this function prints both the +// // pointer and the NUL-terminated string for a (const or not) char pointer. +// void ::testing::internal::UniversalPrint(const T& value, ostream*); +// +// // Prints the fields of a tuple tersely to a string vector, one +// // element for each field. Tuple support must be enabled in +// // gtest-port.h. +// std::vector UniversalTersePrintTupleFieldsToStrings( +// const Tuple& value); +// +// Known limitation: +// +// The print primitives print the elements of an STL-style container +// using the compiler-inferred type of *iter where iter is a +// const_iterator of the container. When const_iterator is an input +// iterator but not a forward iterator, this inferred type may not +// match value_type, and the print output may be incorrect. In +// practice, this is rarely a problem as for most containers +// const_iterator is a forward iterator. We'll fix this if there's an +// actual need for it. Note that this fix cannot rely on value_type +// being defined as many user-defined container types don't have +// value_type. + +#ifndef GTEST_INCLUDE_GTEST_GTEST_PRINTERS_H_ +#define GTEST_INCLUDE_GTEST_GTEST_PRINTERS_H_ + +#include // NOLINT +#include +#include +#include +#include +#include "gtest/internal/gtest-port.h" +#include "gtest/internal/gtest-internal.h" + +namespace testing { + +// Definitions in the 'internal' and 'internal2' name spaces are +// subject to change without notice. DO NOT USE THEM IN USER CODE! +namespace internal2 { + +// Prints the given number of bytes in the given object to the given +// ostream. +GTEST_API_ void PrintBytesInObjectTo(const unsigned char* obj_bytes, + size_t count, + ::std::ostream* os); + +// For selecting which printer to use when a given type has neither << +// nor PrintTo(). +enum TypeKind { + kProtobuf, // a protobuf type + kConvertibleToInteger, // a type implicitly convertible to BiggestInt + // (e.g. a named or unnamed enum type) + kOtherType // anything else +}; + +// TypeWithoutFormatter::PrintValue(value, os) is called +// by the universal printer to print a value of type T when neither +// operator<< nor PrintTo() is defined for T, where kTypeKind is the +// "kind" of T as defined by enum TypeKind. +template +class TypeWithoutFormatter { + public: + // This default version is called when kTypeKind is kOtherType. + static void PrintValue(const T& value, ::std::ostream* os) { + PrintBytesInObjectTo(reinterpret_cast(&value), + sizeof(value), os); + } +}; + +// We print a protobuf using its ShortDebugString() when the string +// doesn't exceed this many characters; otherwise we print it using +// DebugString() for better readability. +const size_t kProtobufOneLinerMaxLength = 50; + +template +class TypeWithoutFormatter { + public: + static void PrintValue(const T& value, ::std::ostream* os) { + const ::testing::internal::string short_str = value.ShortDebugString(); + const ::testing::internal::string pretty_str = + short_str.length() <= kProtobufOneLinerMaxLength ? + short_str : ("\n" + value.DebugString()); + *os << ("<" + pretty_str + ">"); + } +}; + +template +class TypeWithoutFormatter { + public: + // Since T has no << operator or PrintTo() but can be implicitly + // converted to BiggestInt, we print it as a BiggestInt. + // + // Most likely T is an enum type (either named or unnamed), in which + // case printing it as an integer is the desired behavior. In case + // T is not an enum, printing it as an integer is the best we can do + // given that it has no user-defined printer. + static void PrintValue(const T& value, ::std::ostream* os) { + const internal::BiggestInt kBigInt = value; + *os << kBigInt; + } +}; + +// Prints the given value to the given ostream. If the value is a +// protocol message, its debug string is printed; if it's an enum or +// of a type implicitly convertible to BiggestInt, it's printed as an +// integer; otherwise the bytes in the value are printed. This is +// what UniversalPrinter::Print() does when it knows nothing about +// type T and T has neither << operator nor PrintTo(). +// +// A user can override this behavior for a class type Foo by defining +// a << operator in the namespace where Foo is defined. +// +// We put this operator in namespace 'internal2' instead of 'internal' +// to simplify the implementation, as much code in 'internal' needs to +// use << in STL, which would conflict with our own << were it defined +// in 'internal'. +// +// Note that this operator<< takes a generic std::basic_ostream type instead of the more restricted std::ostream. If +// we define it to take an std::ostream instead, we'll get an +// "ambiguous overloads" compiler error when trying to print a type +// Foo that supports streaming to std::basic_ostream, as the compiler cannot tell whether +// operator<<(std::ostream&, const T&) or +// operator<<(std::basic_stream, const Foo&) is more +// specific. +template +::std::basic_ostream& operator<<( + ::std::basic_ostream& os, const T& x) { + TypeWithoutFormatter::value ? kProtobuf : + internal::ImplicitlyConvertible::value ? + kConvertibleToInteger : kOtherType)>::PrintValue(x, &os); + return os; +} + +} // namespace internal2 +} // namespace testing + +// This namespace MUST NOT BE NESTED IN ::testing, or the name look-up +// magic needed for implementing UniversalPrinter won't work. +namespace testing_internal { + +// Used to print a value that is not an STL-style container when the +// user doesn't define PrintTo() for it. +template +void DefaultPrintNonContainerTo(const T& value, ::std::ostream* os) { + // With the following statement, during unqualified name lookup, + // testing::internal2::operator<< appears as if it was declared in + // the nearest enclosing namespace that contains both + // ::testing_internal and ::testing::internal2, i.e. the global + // namespace. For more details, refer to the C++ Standard section + // 7.3.4-1 [namespace.udir]. This allows us to fall back onto + // testing::internal2::operator<< in case T doesn't come with a << + // operator. + // + // We cannot write 'using ::testing::internal2::operator<<;', which + // gcc 3.3 fails to compile due to a compiler bug. + using namespace ::testing::internal2; // NOLINT + + // Assuming T is defined in namespace foo, in the next statement, + // the compiler will consider all of: + // + // 1. foo::operator<< (thanks to Koenig look-up), + // 2. ::operator<< (as the current namespace is enclosed in ::), + // 3. testing::internal2::operator<< (thanks to the using statement above). + // + // The operator<< whose type matches T best will be picked. + // + // We deliberately allow #2 to be a candidate, as sometimes it's + // impossible to define #1 (e.g. when foo is ::std, defining + // anything in it is undefined behavior unless you are a compiler + // vendor.). + *os << value; +} + +} // namespace testing_internal + +namespace testing { +namespace internal { + +// UniversalPrinter::Print(value, ostream_ptr) prints the given +// value to the given ostream. The caller must ensure that +// 'ostream_ptr' is not NULL, or the behavior is undefined. +// +// We define UniversalPrinter as a class template (as opposed to a +// function template), as we need to partially specialize it for +// reference types, which cannot be done with function templates. +template +class UniversalPrinter; + +template +void UniversalPrint(const T& value, ::std::ostream* os); + +// Used to print an STL-style container when the user doesn't define +// a PrintTo() for it. +template +void DefaultPrintTo(IsContainer /* dummy */, + false_type /* is not a pointer */, + const C& container, ::std::ostream* os) { + const size_t kMaxCount = 32; // The maximum number of elements to print. + *os << '{'; + size_t count = 0; + for (typename C::const_iterator it = container.begin(); + it != container.end(); ++it, ++count) { + if (count > 0) { + *os << ','; + if (count == kMaxCount) { // Enough has been printed. + *os << " ..."; + break; + } + } + *os << ' '; + // We cannot call PrintTo(*it, os) here as PrintTo() doesn't + // handle *it being a native array. + internal::UniversalPrint(*it, os); + } + + if (count > 0) { + *os << ' '; + } + *os << '}'; +} + +// Used to print a pointer that is neither a char pointer nor a member +// pointer, when the user doesn't define PrintTo() for it. (A member +// variable pointer or member function pointer doesn't really point to +// a location in the address space. Their representation is +// implementation-defined. Therefore they will be printed as raw +// bytes.) +template +void DefaultPrintTo(IsNotContainer /* dummy */, + true_type /* is a pointer */, + T* p, ::std::ostream* os) { + if (p == NULL) { + *os << "NULL"; + } else { + // C++ doesn't allow casting from a function pointer to any object + // pointer. + // + // IsTrue() silences warnings: "Condition is always true", + // "unreachable code". + if (IsTrue(ImplicitlyConvertible::value)) { + // T is not a function type. We just call << to print p, + // relying on ADL to pick up user-defined << for their pointer + // types, if any. + *os << p; + } else { + // T is a function type, so '*os << p' doesn't do what we want + // (it just prints p as bool). We want to print p as a const + // void*. However, we cannot cast it to const void* directly, + // even using reinterpret_cast, as earlier versions of gcc + // (e.g. 3.4.5) cannot compile the cast when p is a function + // pointer. Casting to UInt64 first solves the problem. + *os << reinterpret_cast( + reinterpret_cast(p)); + } + } +} + +// Used to print a non-container, non-pointer value when the user +// doesn't define PrintTo() for it. +template +void DefaultPrintTo(IsNotContainer /* dummy */, + false_type /* is not a pointer */, + const T& value, ::std::ostream* os) { + ::testing_internal::DefaultPrintNonContainerTo(value, os); +} + +// Prints the given value using the << operator if it has one; +// otherwise prints the bytes in it. This is what +// UniversalPrinter::Print() does when PrintTo() is not specialized +// or overloaded for type T. +// +// A user can override this behavior for a class type Foo by defining +// an overload of PrintTo() in the namespace where Foo is defined. We +// give the user this option as sometimes defining a << operator for +// Foo is not desirable (e.g. the coding style may prevent doing it, +// or there is already a << operator but it doesn't do what the user +// wants). +template +void PrintTo(const T& value, ::std::ostream* os) { + // DefaultPrintTo() is overloaded. The type of its first two + // arguments determine which version will be picked. If T is an + // STL-style container, the version for container will be called; if + // T is a pointer, the pointer version will be called; otherwise the + // generic version will be called. + // + // Note that we check for container types here, prior to we check + // for protocol message types in our operator<<. The rationale is: + // + // For protocol messages, we want to give people a chance to + // override Google Mock's format by defining a PrintTo() or + // operator<<. For STL containers, other formats can be + // incompatible with Google Mock's format for the container + // elements; therefore we check for container types here to ensure + // that our format is used. + // + // The second argument of DefaultPrintTo() is needed to bypass a bug + // in Symbian's C++ compiler that prevents it from picking the right + // overload between: + // + // PrintTo(const T& x, ...); + // PrintTo(T* x, ...); + DefaultPrintTo(IsContainerTest(0), is_pointer(), value, os); +} + +// The following list of PrintTo() overloads tells +// UniversalPrinter::Print() how to print standard types (built-in +// types, strings, plain arrays, and pointers). + +// Overloads for various char types. +GTEST_API_ void PrintTo(unsigned char c, ::std::ostream* os); +GTEST_API_ void PrintTo(signed char c, ::std::ostream* os); +inline void PrintTo(char c, ::std::ostream* os) { + // When printing a plain char, we always treat it as unsigned. This + // way, the output won't be affected by whether the compiler thinks + // char is signed or not. + PrintTo(static_cast(c), os); +} + +// Overloads for other simple built-in types. +inline void PrintTo(bool x, ::std::ostream* os) { + *os << (x ? "true" : "false"); +} + +// Overload for wchar_t type. +// Prints a wchar_t as a symbol if it is printable or as its internal +// code otherwise and also as its decimal code (except for L'\0'). +// The L'\0' char is printed as "L'\\0'". The decimal code is printed +// as signed integer when wchar_t is implemented by the compiler +// as a signed type and is printed as an unsigned integer when wchar_t +// is implemented as an unsigned type. +GTEST_API_ void PrintTo(wchar_t wc, ::std::ostream* os); + +// Overloads for C strings. +GTEST_API_ void PrintTo(const char* s, ::std::ostream* os); +inline void PrintTo(char* s, ::std::ostream* os) { + PrintTo(ImplicitCast_(s), os); +} + +// signed/unsigned char is often used for representing binary data, so +// we print pointers to it as void* to be safe. +inline void PrintTo(const signed char* s, ::std::ostream* os) { + PrintTo(ImplicitCast_(s), os); +} +inline void PrintTo(signed char* s, ::std::ostream* os) { + PrintTo(ImplicitCast_(s), os); +} +inline void PrintTo(const unsigned char* s, ::std::ostream* os) { + PrintTo(ImplicitCast_(s), os); +} +inline void PrintTo(unsigned char* s, ::std::ostream* os) { + PrintTo(ImplicitCast_(s), os); +} + +// MSVC can be configured to define wchar_t as a typedef of unsigned +// short. It defines _NATIVE_WCHAR_T_DEFINED when wchar_t is a native +// type. When wchar_t is a typedef, defining an overload for const +// wchar_t* would cause unsigned short* be printed as a wide string, +// possibly causing invalid memory accesses. +#if !defined(_MSC_VER) || defined(_NATIVE_WCHAR_T_DEFINED) +// Overloads for wide C strings +GTEST_API_ void PrintTo(const wchar_t* s, ::std::ostream* os); +inline void PrintTo(wchar_t* s, ::std::ostream* os) { + PrintTo(ImplicitCast_(s), os); +} +#endif + +// Overload for C arrays. Multi-dimensional arrays are printed +// properly. + +// Prints the given number of elements in an array, without printing +// the curly braces. +template +void PrintRawArrayTo(const T a[], size_t count, ::std::ostream* os) { + UniversalPrint(a[0], os); + for (size_t i = 1; i != count; i++) { + *os << ", "; + UniversalPrint(a[i], os); + } +} + +// Overloads for ::string and ::std::string. +#if GTEST_HAS_GLOBAL_STRING +GTEST_API_ void PrintStringTo(const ::string&s, ::std::ostream* os); +inline void PrintTo(const ::string& s, ::std::ostream* os) { + PrintStringTo(s, os); +} +#endif // GTEST_HAS_GLOBAL_STRING + +GTEST_API_ void PrintStringTo(const ::std::string&s, ::std::ostream* os); +inline void PrintTo(const ::std::string& s, ::std::ostream* os) { + PrintStringTo(s, os); +} + +// Overloads for ::wstring and ::std::wstring. +#if GTEST_HAS_GLOBAL_WSTRING +GTEST_API_ void PrintWideStringTo(const ::wstring&s, ::std::ostream* os); +inline void PrintTo(const ::wstring& s, ::std::ostream* os) { + PrintWideStringTo(s, os); +} +#endif // GTEST_HAS_GLOBAL_WSTRING + +#if GTEST_HAS_STD_WSTRING +GTEST_API_ void PrintWideStringTo(const ::std::wstring&s, ::std::ostream* os); +inline void PrintTo(const ::std::wstring& s, ::std::ostream* os) { + PrintWideStringTo(s, os); +} +#endif // GTEST_HAS_STD_WSTRING + +#if GTEST_HAS_TR1_TUPLE +// Overload for ::std::tr1::tuple. Needed for printing function arguments, +// which are packed as tuples. + +// Helper function for printing a tuple. T must be instantiated with +// a tuple type. +template +void PrintTupleTo(const T& t, ::std::ostream* os); + +// Overloaded PrintTo() for tuples of various arities. We support +// tuples of up-to 10 fields. The following implementation works +// regardless of whether tr1::tuple is implemented using the +// non-standard variadic template feature or not. + +inline void PrintTo(const ::std::tr1::tuple<>& t, ::std::ostream* os) { + PrintTupleTo(t, os); +} + +template +void PrintTo(const ::std::tr1::tuple& t, ::std::ostream* os) { + PrintTupleTo(t, os); +} + +template +void PrintTo(const ::std::tr1::tuple& t, ::std::ostream* os) { + PrintTupleTo(t, os); +} + +template +void PrintTo(const ::std::tr1::tuple& t, ::std::ostream* os) { + PrintTupleTo(t, os); +} + +template +void PrintTo(const ::std::tr1::tuple& t, ::std::ostream* os) { + PrintTupleTo(t, os); +} + +template +void PrintTo(const ::std::tr1::tuple& t, + ::std::ostream* os) { + PrintTupleTo(t, os); +} + +template +void PrintTo(const ::std::tr1::tuple& t, + ::std::ostream* os) { + PrintTupleTo(t, os); +} + +template +void PrintTo(const ::std::tr1::tuple& t, + ::std::ostream* os) { + PrintTupleTo(t, os); +} + +template +void PrintTo(const ::std::tr1::tuple& t, + ::std::ostream* os) { + PrintTupleTo(t, os); +} + +template +void PrintTo(const ::std::tr1::tuple& t, + ::std::ostream* os) { + PrintTupleTo(t, os); +} + +template +void PrintTo( + const ::std::tr1::tuple& t, + ::std::ostream* os) { + PrintTupleTo(t, os); +} +#endif // GTEST_HAS_TR1_TUPLE + +// Overload for std::pair. +template +void PrintTo(const ::std::pair& value, ::std::ostream* os) { + *os << '('; + // We cannot use UniversalPrint(value.first, os) here, as T1 may be + // a reference type. The same for printing value.second. + UniversalPrinter::Print(value.first, os); + *os << ", "; + UniversalPrinter::Print(value.second, os); + *os << ')'; +} + +// Implements printing a non-reference type T by letting the compiler +// pick the right overload of PrintTo() for T. +template +class UniversalPrinter { + public: + // MSVC warns about adding const to a function type, so we want to + // disable the warning. +#ifdef _MSC_VER +# pragma warning(push) // Saves the current warning state. +# pragma warning(disable:4180) // Temporarily disables warning 4180. +#endif // _MSC_VER + + // Note: we deliberately don't call this PrintTo(), as that name + // conflicts with ::testing::internal::PrintTo in the body of the + // function. + static void Print(const T& value, ::std::ostream* os) { + // By default, ::testing::internal::PrintTo() is used for printing + // the value. + // + // Thanks to Koenig look-up, if T is a class and has its own + // PrintTo() function defined in its namespace, that function will + // be visible here. Since it is more specific than the generic ones + // in ::testing::internal, it will be picked by the compiler in the + // following statement - exactly what we want. + PrintTo(value, os); + } + +#ifdef _MSC_VER +# pragma warning(pop) // Restores the warning state. +#endif // _MSC_VER +}; + +// UniversalPrintArray(begin, len, os) prints an array of 'len' +// elements, starting at address 'begin'. +template +void UniversalPrintArray(const T* begin, size_t len, ::std::ostream* os) { + if (len == 0) { + *os << "{}"; + } else { + *os << "{ "; + const size_t kThreshold = 18; + const size_t kChunkSize = 8; + // If the array has more than kThreshold elements, we'll have to + // omit some details by printing only the first and the last + // kChunkSize elements. + // TODO(wan@google.com): let the user control the threshold using a flag. + if (len <= kThreshold) { + PrintRawArrayTo(begin, len, os); + } else { + PrintRawArrayTo(begin, kChunkSize, os); + *os << ", ..., "; + PrintRawArrayTo(begin + len - kChunkSize, kChunkSize, os); + } + *os << " }"; + } +} +// This overload prints a (const) char array compactly. +GTEST_API_ void UniversalPrintArray( + const char* begin, size_t len, ::std::ostream* os); + +// This overload prints a (const) wchar_t array compactly. +GTEST_API_ void UniversalPrintArray( + const wchar_t* begin, size_t len, ::std::ostream* os); + +// Implements printing an array type T[N]. +template +class UniversalPrinter { + public: + // Prints the given array, omitting some elements when there are too + // many. + static void Print(const T (&a)[N], ::std::ostream* os) { + UniversalPrintArray(a, N, os); + } +}; + +// Implements printing a reference type T&. +template +class UniversalPrinter { + public: + // MSVC warns about adding const to a function type, so we want to + // disable the warning. +#ifdef _MSC_VER +# pragma warning(push) // Saves the current warning state. +# pragma warning(disable:4180) // Temporarily disables warning 4180. +#endif // _MSC_VER + + static void Print(const T& value, ::std::ostream* os) { + // Prints the address of the value. We use reinterpret_cast here + // as static_cast doesn't compile when T is a function type. + *os << "@" << reinterpret_cast(&value) << " "; + + // Then prints the value itself. + UniversalPrint(value, os); + } + +#ifdef _MSC_VER +# pragma warning(pop) // Restores the warning state. +#endif // _MSC_VER +}; + +// Prints a value tersely: for a reference type, the referenced value +// (but not the address) is printed; for a (const) char pointer, the +// NUL-terminated string (but not the pointer) is printed. + +template +class UniversalTersePrinter { + public: + static void Print(const T& value, ::std::ostream* os) { + UniversalPrint(value, os); + } +}; +template +class UniversalTersePrinter { + public: + static void Print(const T& value, ::std::ostream* os) { + UniversalPrint(value, os); + } +}; +template +class UniversalTersePrinter { + public: + static void Print(const T (&value)[N], ::std::ostream* os) { + UniversalPrinter::Print(value, os); + } +}; +template <> +class UniversalTersePrinter { + public: + static void Print(const char* str, ::std::ostream* os) { + if (str == NULL) { + *os << "NULL"; + } else { + UniversalPrint(string(str), os); + } + } +}; +template <> +class UniversalTersePrinter { + public: + static void Print(char* str, ::std::ostream* os) { + UniversalTersePrinter::Print(str, os); + } +}; + +#if GTEST_HAS_STD_WSTRING +template <> +class UniversalTersePrinter { + public: + static void Print(const wchar_t* str, ::std::ostream* os) { + if (str == NULL) { + *os << "NULL"; + } else { + UniversalPrint(::std::wstring(str), os); + } + } +}; +#endif + +template <> +class UniversalTersePrinter { + public: + static void Print(wchar_t* str, ::std::ostream* os) { + UniversalTersePrinter::Print(str, os); + } +}; + +template +void UniversalTersePrint(const T& value, ::std::ostream* os) { + UniversalTersePrinter::Print(value, os); +} + +// Prints a value using the type inferred by the compiler. The +// difference between this and UniversalTersePrint() is that for a +// (const) char pointer, this prints both the pointer and the +// NUL-terminated string. +template +void UniversalPrint(const T& value, ::std::ostream* os) { + // A workarond for the bug in VC++ 7.1 that prevents us from instantiating + // UniversalPrinter with T directly. + typedef T T1; + UniversalPrinter::Print(value, os); +} + +#if GTEST_HAS_TR1_TUPLE +typedef ::std::vector Strings; + +// This helper template allows PrintTo() for tuples and +// UniversalTersePrintTupleFieldsToStrings() to be defined by +// induction on the number of tuple fields. The idea is that +// TuplePrefixPrinter::PrintPrefixTo(t, os) prints the first N +// fields in tuple t, and can be defined in terms of +// TuplePrefixPrinter. + +// The inductive case. +template +struct TuplePrefixPrinter { + // Prints the first N fields of a tuple. + template + static void PrintPrefixTo(const Tuple& t, ::std::ostream* os) { + TuplePrefixPrinter::PrintPrefixTo(t, os); + *os << ", "; + UniversalPrinter::type> + ::Print(::std::tr1::get(t), os); + } + + // Tersely prints the first N fields of a tuple to a string vector, + // one element for each field. + template + static void TersePrintPrefixToStrings(const Tuple& t, Strings* strings) { + TuplePrefixPrinter::TersePrintPrefixToStrings(t, strings); + ::std::stringstream ss; + UniversalTersePrint(::std::tr1::get(t), &ss); + strings->push_back(ss.str()); + } +}; + +// Base cases. +template <> +struct TuplePrefixPrinter<0> { + template + static void PrintPrefixTo(const Tuple&, ::std::ostream*) {} + + template + static void TersePrintPrefixToStrings(const Tuple&, Strings*) {} +}; +// We have to specialize the entire TuplePrefixPrinter<> class +// template here, even though the definition of +// TersePrintPrefixToStrings() is the same as the generic version, as +// Embarcadero (formerly CodeGear, formerly Borland) C++ doesn't +// support specializing a method template of a class template. +template <> +struct TuplePrefixPrinter<1> { + template + static void PrintPrefixTo(const Tuple& t, ::std::ostream* os) { + UniversalPrinter::type>:: + Print(::std::tr1::get<0>(t), os); + } + + template + static void TersePrintPrefixToStrings(const Tuple& t, Strings* strings) { + ::std::stringstream ss; + UniversalTersePrint(::std::tr1::get<0>(t), &ss); + strings->push_back(ss.str()); + } +}; + +// Helper function for printing a tuple. T must be instantiated with +// a tuple type. +template +void PrintTupleTo(const T& t, ::std::ostream* os) { + *os << "("; + TuplePrefixPrinter< ::std::tr1::tuple_size::value>:: + PrintPrefixTo(t, os); + *os << ")"; +} + +// Prints the fields of a tuple tersely to a string vector, one +// element for each field. See the comment before +// UniversalTersePrint() for how we define "tersely". +template +Strings UniversalTersePrintTupleFieldsToStrings(const Tuple& value) { + Strings result; + TuplePrefixPrinter< ::std::tr1::tuple_size::value>:: + TersePrintPrefixToStrings(value, &result); + return result; +} +#endif // GTEST_HAS_TR1_TUPLE + +} // namespace internal + +template +::std::string PrintToString(const T& value) { + ::std::stringstream ss; + internal::UniversalTersePrinter::Print(value, &ss); + return ss.str(); +} + +} // namespace testing + +#endif // GTEST_INCLUDE_GTEST_GTEST_PRINTERS_H_ diff --git a/vendor/gmock-1.7.0/gtest/include/gtest/gtest-spi.h b/vendor/gmock-1.7.0/gtest/include/gtest/gtest-spi.h new file mode 100644 index 0000000000..f63fa9a1b2 --- /dev/null +++ b/vendor/gmock-1.7.0/gtest/include/gtest/gtest-spi.h @@ -0,0 +1,232 @@ +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) +// +// Utilities for testing Google Test itself and code that uses Google Test +// (e.g. frameworks built on top of Google Test). + +#ifndef GTEST_INCLUDE_GTEST_GTEST_SPI_H_ +#define GTEST_INCLUDE_GTEST_GTEST_SPI_H_ + +#include "gtest/gtest.h" + +namespace testing { + +// This helper class can be used to mock out Google Test failure reporting +// so that we can test Google Test or code that builds on Google Test. +// +// An object of this class appends a TestPartResult object to the +// TestPartResultArray object given in the constructor whenever a Google Test +// failure is reported. It can either intercept only failures that are +// generated in the same thread that created this object or it can intercept +// all generated failures. The scope of this mock object can be controlled with +// the second argument to the two arguments constructor. +class GTEST_API_ ScopedFakeTestPartResultReporter + : public TestPartResultReporterInterface { + public: + // The two possible mocking modes of this object. + enum InterceptMode { + INTERCEPT_ONLY_CURRENT_THREAD, // Intercepts only thread local failures. + INTERCEPT_ALL_THREADS // Intercepts all failures. + }; + + // The c'tor sets this object as the test part result reporter used + // by Google Test. The 'result' parameter specifies where to report the + // results. This reporter will only catch failures generated in the current + // thread. DEPRECATED + explicit ScopedFakeTestPartResultReporter(TestPartResultArray* result); + + // Same as above, but you can choose the interception scope of this object. + ScopedFakeTestPartResultReporter(InterceptMode intercept_mode, + TestPartResultArray* result); + + // The d'tor restores the previous test part result reporter. + virtual ~ScopedFakeTestPartResultReporter(); + + // Appends the TestPartResult object to the TestPartResultArray + // received in the constructor. + // + // This method is from the TestPartResultReporterInterface + // interface. + virtual void ReportTestPartResult(const TestPartResult& result); + private: + void Init(); + + const InterceptMode intercept_mode_; + TestPartResultReporterInterface* old_reporter_; + TestPartResultArray* const result_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(ScopedFakeTestPartResultReporter); +}; + +namespace internal { + +// A helper class for implementing EXPECT_FATAL_FAILURE() and +// EXPECT_NONFATAL_FAILURE(). Its destructor verifies that the given +// TestPartResultArray contains exactly one failure that has the given +// type and contains the given substring. If that's not the case, a +// non-fatal failure will be generated. +class GTEST_API_ SingleFailureChecker { + public: + // The constructor remembers the arguments. + SingleFailureChecker(const TestPartResultArray* results, + TestPartResult::Type type, + const string& substr); + ~SingleFailureChecker(); + private: + const TestPartResultArray* const results_; + const TestPartResult::Type type_; + const string substr_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(SingleFailureChecker); +}; + +} // namespace internal + +} // namespace testing + +// A set of macros for testing Google Test assertions or code that's expected +// to generate Google Test fatal failures. It verifies that the given +// statement will cause exactly one fatal Google Test failure with 'substr' +// being part of the failure message. +// +// There are two different versions of this macro. EXPECT_FATAL_FAILURE only +// affects and considers failures generated in the current thread and +// EXPECT_FATAL_FAILURE_ON_ALL_THREADS does the same but for all threads. +// +// The verification of the assertion is done correctly even when the statement +// throws an exception or aborts the current function. +// +// Known restrictions: +// - 'statement' cannot reference local non-static variables or +// non-static members of the current object. +// - 'statement' cannot return a value. +// - You cannot stream a failure message to this macro. +// +// Note that even though the implementations of the following two +// macros are much alike, we cannot refactor them to use a common +// helper macro, due to some peculiarity in how the preprocessor +// works. The AcceptsMacroThatExpandsToUnprotectedComma test in +// gtest_unittest.cc will fail to compile if we do that. +#define EXPECT_FATAL_FAILURE(statement, substr) \ + do { \ + class GTestExpectFatalFailureHelper {\ + public:\ + static void Execute() { statement; }\ + };\ + ::testing::TestPartResultArray gtest_failures;\ + ::testing::internal::SingleFailureChecker gtest_checker(\ + >est_failures, ::testing::TestPartResult::kFatalFailure, (substr));\ + {\ + ::testing::ScopedFakeTestPartResultReporter gtest_reporter(\ + ::testing::ScopedFakeTestPartResultReporter:: \ + INTERCEPT_ONLY_CURRENT_THREAD, >est_failures);\ + GTestExpectFatalFailureHelper::Execute();\ + }\ + } while (::testing::internal::AlwaysFalse()) + +#define EXPECT_FATAL_FAILURE_ON_ALL_THREADS(statement, substr) \ + do { \ + class GTestExpectFatalFailureHelper {\ + public:\ + static void Execute() { statement; }\ + };\ + ::testing::TestPartResultArray gtest_failures;\ + ::testing::internal::SingleFailureChecker gtest_checker(\ + >est_failures, ::testing::TestPartResult::kFatalFailure, (substr));\ + {\ + ::testing::ScopedFakeTestPartResultReporter gtest_reporter(\ + ::testing::ScopedFakeTestPartResultReporter:: \ + INTERCEPT_ALL_THREADS, >est_failures);\ + GTestExpectFatalFailureHelper::Execute();\ + }\ + } while (::testing::internal::AlwaysFalse()) + +// A macro for testing Google Test assertions or code that's expected to +// generate Google Test non-fatal failures. It asserts that the given +// statement will cause exactly one non-fatal Google Test failure with 'substr' +// being part of the failure message. +// +// There are two different versions of this macro. EXPECT_NONFATAL_FAILURE only +// affects and considers failures generated in the current thread and +// EXPECT_NONFATAL_FAILURE_ON_ALL_THREADS does the same but for all threads. +// +// 'statement' is allowed to reference local variables and members of +// the current object. +// +// The verification of the assertion is done correctly even when the statement +// throws an exception or aborts the current function. +// +// Known restrictions: +// - You cannot stream a failure message to this macro. +// +// Note that even though the implementations of the following two +// macros are much alike, we cannot refactor them to use a common +// helper macro, due to some peculiarity in how the preprocessor +// works. If we do that, the code won't compile when the user gives +// EXPECT_NONFATAL_FAILURE() a statement that contains a macro that +// expands to code containing an unprotected comma. The +// AcceptsMacroThatExpandsToUnprotectedComma test in gtest_unittest.cc +// catches that. +// +// For the same reason, we have to write +// if (::testing::internal::AlwaysTrue()) { statement; } +// instead of +// GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement) +// to avoid an MSVC warning on unreachable code. +#define EXPECT_NONFATAL_FAILURE(statement, substr) \ + do {\ + ::testing::TestPartResultArray gtest_failures;\ + ::testing::internal::SingleFailureChecker gtest_checker(\ + >est_failures, ::testing::TestPartResult::kNonFatalFailure, \ + (substr));\ + {\ + ::testing::ScopedFakeTestPartResultReporter gtest_reporter(\ + ::testing::ScopedFakeTestPartResultReporter:: \ + INTERCEPT_ONLY_CURRENT_THREAD, >est_failures);\ + if (::testing::internal::AlwaysTrue()) { statement; }\ + }\ + } while (::testing::internal::AlwaysFalse()) + +#define EXPECT_NONFATAL_FAILURE_ON_ALL_THREADS(statement, substr) \ + do {\ + ::testing::TestPartResultArray gtest_failures;\ + ::testing::internal::SingleFailureChecker gtest_checker(\ + >est_failures, ::testing::TestPartResult::kNonFatalFailure, \ + (substr));\ + {\ + ::testing::ScopedFakeTestPartResultReporter gtest_reporter(\ + ::testing::ScopedFakeTestPartResultReporter::INTERCEPT_ALL_THREADS, \ + >est_failures);\ + if (::testing::internal::AlwaysTrue()) { statement; }\ + }\ + } while (::testing::internal::AlwaysFalse()) + +#endif // GTEST_INCLUDE_GTEST_GTEST_SPI_H_ diff --git a/vendor/gmock-1.7.0/gtest/include/gtest/gtest-test-part.h b/vendor/gmock-1.7.0/gtest/include/gtest/gtest-test-part.h new file mode 100644 index 0000000000..77eb844839 --- /dev/null +++ b/vendor/gmock-1.7.0/gtest/include/gtest/gtest-test-part.h @@ -0,0 +1,179 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: mheule@google.com (Markus Heule) +// + +#ifndef GTEST_INCLUDE_GTEST_GTEST_TEST_PART_H_ +#define GTEST_INCLUDE_GTEST_GTEST_TEST_PART_H_ + +#include +#include +#include "gtest/internal/gtest-internal.h" +#include "gtest/internal/gtest-string.h" + +namespace testing { + +// A copyable object representing the result of a test part (i.e. an +// assertion or an explicit FAIL(), ADD_FAILURE(), or SUCCESS()). +// +// Don't inherit from TestPartResult as its destructor is not virtual. +class GTEST_API_ TestPartResult { + public: + // The possible outcomes of a test part (i.e. an assertion or an + // explicit SUCCEED(), FAIL(), or ADD_FAILURE()). + enum Type { + kSuccess, // Succeeded. + kNonFatalFailure, // Failed but the test can continue. + kFatalFailure // Failed and the test should be terminated. + }; + + // C'tor. TestPartResult does NOT have a default constructor. + // Always use this constructor (with parameters) to create a + // TestPartResult object. + TestPartResult(Type a_type, + const char* a_file_name, + int a_line_number, + const char* a_message) + : type_(a_type), + file_name_(a_file_name == NULL ? "" : a_file_name), + line_number_(a_line_number), + summary_(ExtractSummary(a_message)), + message_(a_message) { + } + + // Gets the outcome of the test part. + Type type() const { return type_; } + + // Gets the name of the source file where the test part took place, or + // NULL if it's unknown. + const char* file_name() const { + return file_name_.empty() ? NULL : file_name_.c_str(); + } + + // Gets the line in the source file where the test part took place, + // or -1 if it's unknown. + int line_number() const { return line_number_; } + + // Gets the summary of the failure message. + const char* summary() const { return summary_.c_str(); } + + // Gets the message associated with the test part. + const char* message() const { return message_.c_str(); } + + // Returns true iff the test part passed. + bool passed() const { return type_ == kSuccess; } + + // Returns true iff the test part failed. + bool failed() const { return type_ != kSuccess; } + + // Returns true iff the test part non-fatally failed. + bool nonfatally_failed() const { return type_ == kNonFatalFailure; } + + // Returns true iff the test part fatally failed. + bool fatally_failed() const { return type_ == kFatalFailure; } + + private: + Type type_; + + // Gets the summary of the failure message by omitting the stack + // trace in it. + static std::string ExtractSummary(const char* message); + + // The name of the source file where the test part took place, or + // "" if the source file is unknown. + std::string file_name_; + // The line in the source file where the test part took place, or -1 + // if the line number is unknown. + int line_number_; + std::string summary_; // The test failure summary. + std::string message_; // The test failure message. +}; + +// Prints a TestPartResult object. +std::ostream& operator<<(std::ostream& os, const TestPartResult& result); + +// An array of TestPartResult objects. +// +// Don't inherit from TestPartResultArray as its destructor is not +// virtual. +class GTEST_API_ TestPartResultArray { + public: + TestPartResultArray() {} + + // Appends the given TestPartResult to the array. + void Append(const TestPartResult& result); + + // Returns the TestPartResult at the given index (0-based). + const TestPartResult& GetTestPartResult(int index) const; + + // Returns the number of TestPartResult objects in the array. + int size() const; + + private: + std::vector array_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(TestPartResultArray); +}; + +// This interface knows how to report a test part result. +class TestPartResultReporterInterface { + public: + virtual ~TestPartResultReporterInterface() {} + + virtual void ReportTestPartResult(const TestPartResult& result) = 0; +}; + +namespace internal { + +// This helper class is used by {ASSERT|EXPECT}_NO_FATAL_FAILURE to check if a +// statement generates new fatal failures. To do so it registers itself as the +// current test part result reporter. Besides checking if fatal failures were +// reported, it only delegates the reporting to the former result reporter. +// The original result reporter is restored in the destructor. +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +class GTEST_API_ HasNewFatalFailureHelper + : public TestPartResultReporterInterface { + public: + HasNewFatalFailureHelper(); + virtual ~HasNewFatalFailureHelper(); + virtual void ReportTestPartResult(const TestPartResult& result); + bool has_new_fatal_failure() const { return has_new_fatal_failure_; } + private: + bool has_new_fatal_failure_; + TestPartResultReporterInterface* original_reporter_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(HasNewFatalFailureHelper); +}; + +} // namespace internal + +} // namespace testing + +#endif // GTEST_INCLUDE_GTEST_GTEST_TEST_PART_H_ diff --git a/vendor/gmock-1.7.0/gtest/include/gtest/gtest-typed-test.h b/vendor/gmock-1.7.0/gtest/include/gtest/gtest-typed-test.h new file mode 100644 index 0000000000..fe1e83b274 --- /dev/null +++ b/vendor/gmock-1.7.0/gtest/include/gtest/gtest-typed-test.h @@ -0,0 +1,259 @@ +// Copyright 2008 Google Inc. +// All Rights Reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) + +#ifndef GTEST_INCLUDE_GTEST_GTEST_TYPED_TEST_H_ +#define GTEST_INCLUDE_GTEST_GTEST_TYPED_TEST_H_ + +// This header implements typed tests and type-parameterized tests. + +// Typed (aka type-driven) tests repeat the same test for types in a +// list. You must know which types you want to test with when writing +// typed tests. Here's how you do it: + +#if 0 + +// First, define a fixture class template. It should be parameterized +// by a type. Remember to derive it from testing::Test. +template +class FooTest : public testing::Test { + public: + ... + typedef std::list List; + static T shared_; + T value_; +}; + +// Next, associate a list of types with the test case, which will be +// repeated for each type in the list. The typedef is necessary for +// the macro to parse correctly. +typedef testing::Types MyTypes; +TYPED_TEST_CASE(FooTest, MyTypes); + +// If the type list contains only one type, you can write that type +// directly without Types<...>: +// TYPED_TEST_CASE(FooTest, int); + +// Then, use TYPED_TEST() instead of TEST_F() to define as many typed +// tests for this test case as you want. +TYPED_TEST(FooTest, DoesBlah) { + // Inside a test, refer to TypeParam to get the type parameter. + // Since we are inside a derived class template, C++ requires use to + // visit the members of FooTest via 'this'. + TypeParam n = this->value_; + + // To visit static members of the fixture, add the TestFixture:: + // prefix. + n += TestFixture::shared_; + + // To refer to typedefs in the fixture, add the "typename + // TestFixture::" prefix. + typename TestFixture::List values; + values.push_back(n); + ... +} + +TYPED_TEST(FooTest, HasPropertyA) { ... } + +#endif // 0 + +// Type-parameterized tests are abstract test patterns parameterized +// by a type. Compared with typed tests, type-parameterized tests +// allow you to define the test pattern without knowing what the type +// parameters are. The defined pattern can be instantiated with +// different types any number of times, in any number of translation +// units. +// +// If you are designing an interface or concept, you can define a +// suite of type-parameterized tests to verify properties that any +// valid implementation of the interface/concept should have. Then, +// each implementation can easily instantiate the test suite to verify +// that it conforms to the requirements, without having to write +// similar tests repeatedly. Here's an example: + +#if 0 + +// First, define a fixture class template. It should be parameterized +// by a type. Remember to derive it from testing::Test. +template +class FooTest : public testing::Test { + ... +}; + +// Next, declare that you will define a type-parameterized test case +// (the _P suffix is for "parameterized" or "pattern", whichever you +// prefer): +TYPED_TEST_CASE_P(FooTest); + +// Then, use TYPED_TEST_P() to define as many type-parameterized tests +// for this type-parameterized test case as you want. +TYPED_TEST_P(FooTest, DoesBlah) { + // Inside a test, refer to TypeParam to get the type parameter. + TypeParam n = 0; + ... +} + +TYPED_TEST_P(FooTest, HasPropertyA) { ... } + +// Now the tricky part: you need to register all test patterns before +// you can instantiate them. The first argument of the macro is the +// test case name; the rest are the names of the tests in this test +// case. +REGISTER_TYPED_TEST_CASE_P(FooTest, + DoesBlah, HasPropertyA); + +// Finally, you are free to instantiate the pattern with the types you +// want. If you put the above code in a header file, you can #include +// it in multiple C++ source files and instantiate it multiple times. +// +// To distinguish different instances of the pattern, the first +// argument to the INSTANTIATE_* macro is a prefix that will be added +// to the actual test case name. Remember to pick unique prefixes for +// different instances. +typedef testing::Types MyTypes; +INSTANTIATE_TYPED_TEST_CASE_P(My, FooTest, MyTypes); + +// If the type list contains only one type, you can write that type +// directly without Types<...>: +// INSTANTIATE_TYPED_TEST_CASE_P(My, FooTest, int); + +#endif // 0 + +#include "gtest/internal/gtest-port.h" +#include "gtest/internal/gtest-type-util.h" + +// Implements typed tests. + +#if GTEST_HAS_TYPED_TEST + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Expands to the name of the typedef for the type parameters of the +// given test case. +# define GTEST_TYPE_PARAMS_(TestCaseName) gtest_type_params_##TestCaseName##_ + +// The 'Types' template argument below must have spaces around it +// since some compilers may choke on '>>' when passing a template +// instance (e.g. Types) +# define TYPED_TEST_CASE(CaseName, Types) \ + typedef ::testing::internal::TypeList< Types >::type \ + GTEST_TYPE_PARAMS_(CaseName) + +# define TYPED_TEST(CaseName, TestName) \ + template \ + class GTEST_TEST_CLASS_NAME_(CaseName, TestName) \ + : public CaseName { \ + private: \ + typedef CaseName TestFixture; \ + typedef gtest_TypeParam_ TypeParam; \ + virtual void TestBody(); \ + }; \ + bool gtest_##CaseName##_##TestName##_registered_ GTEST_ATTRIBUTE_UNUSED_ = \ + ::testing::internal::TypeParameterizedTest< \ + CaseName, \ + ::testing::internal::TemplateSel< \ + GTEST_TEST_CLASS_NAME_(CaseName, TestName)>, \ + GTEST_TYPE_PARAMS_(CaseName)>::Register(\ + "", #CaseName, #TestName, 0); \ + template \ + void GTEST_TEST_CLASS_NAME_(CaseName, TestName)::TestBody() + +#endif // GTEST_HAS_TYPED_TEST + +// Implements type-parameterized tests. + +#if GTEST_HAS_TYPED_TEST_P + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Expands to the namespace name that the type-parameterized tests for +// the given type-parameterized test case are defined in. The exact +// name of the namespace is subject to change without notice. +# define GTEST_CASE_NAMESPACE_(TestCaseName) \ + gtest_case_##TestCaseName##_ + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Expands to the name of the variable used to remember the names of +// the defined tests in the given test case. +# define GTEST_TYPED_TEST_CASE_P_STATE_(TestCaseName) \ + gtest_typed_test_case_p_state_##TestCaseName##_ + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE DIRECTLY. +// +// Expands to the name of the variable used to remember the names of +// the registered tests in the given test case. +# define GTEST_REGISTERED_TEST_NAMES_(TestCaseName) \ + gtest_registered_test_names_##TestCaseName##_ + +// The variables defined in the type-parameterized test macros are +// static as typically these macros are used in a .h file that can be +// #included in multiple translation units linked together. +# define TYPED_TEST_CASE_P(CaseName) \ + static ::testing::internal::TypedTestCasePState \ + GTEST_TYPED_TEST_CASE_P_STATE_(CaseName) + +# define TYPED_TEST_P(CaseName, TestName) \ + namespace GTEST_CASE_NAMESPACE_(CaseName) { \ + template \ + class TestName : public CaseName { \ + private: \ + typedef CaseName TestFixture; \ + typedef gtest_TypeParam_ TypeParam; \ + virtual void TestBody(); \ + }; \ + static bool gtest_##TestName##_defined_ GTEST_ATTRIBUTE_UNUSED_ = \ + GTEST_TYPED_TEST_CASE_P_STATE_(CaseName).AddTestName(\ + __FILE__, __LINE__, #CaseName, #TestName); \ + } \ + template \ + void GTEST_CASE_NAMESPACE_(CaseName)::TestName::TestBody() + +# define REGISTER_TYPED_TEST_CASE_P(CaseName, ...) \ + namespace GTEST_CASE_NAMESPACE_(CaseName) { \ + typedef ::testing::internal::Templates<__VA_ARGS__>::type gtest_AllTests_; \ + } \ + static const char* const GTEST_REGISTERED_TEST_NAMES_(CaseName) = \ + GTEST_TYPED_TEST_CASE_P_STATE_(CaseName).VerifyRegisteredTestNames(\ + __FILE__, __LINE__, #__VA_ARGS__) + +// The 'Types' template argument below must have spaces around it +// since some compilers may choke on '>>' when passing a template +// instance (e.g. Types) +# define INSTANTIATE_TYPED_TEST_CASE_P(Prefix, CaseName, Types) \ + bool gtest_##Prefix##_##CaseName GTEST_ATTRIBUTE_UNUSED_ = \ + ::testing::internal::TypeParameterizedTestCase::type>::Register(\ + #Prefix, #CaseName, GTEST_REGISTERED_TEST_NAMES_(CaseName)) + +#endif // GTEST_HAS_TYPED_TEST_P + +#endif // GTEST_INCLUDE_GTEST_GTEST_TYPED_TEST_H_ diff --git a/vendor/gmock-1.7.0/gtest/include/gtest/gtest.h b/vendor/gmock-1.7.0/gtest/include/gtest/gtest.h new file mode 100644 index 0000000000..6fa0a3925e --- /dev/null +++ b/vendor/gmock-1.7.0/gtest/include/gtest/gtest.h @@ -0,0 +1,2291 @@ +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) +// +// The Google C++ Testing Framework (Google Test) +// +// This header file defines the public API for Google Test. It should be +// included by any test program that uses Google Test. +// +// IMPORTANT NOTE: Due to limitation of the C++ language, we have to +// leave some internal implementation details in this header file. +// They are clearly marked by comments like this: +// +// // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +// +// Such code is NOT meant to be used by a user directly, and is subject +// to CHANGE WITHOUT NOTICE. Therefore DO NOT DEPEND ON IT in a user +// program! +// +// Acknowledgment: Google Test borrowed the idea of automatic test +// registration from Barthelemy Dagenais' (barthelemy@prologique.com) +// easyUnit framework. + +#ifndef GTEST_INCLUDE_GTEST_GTEST_H_ +#define GTEST_INCLUDE_GTEST_GTEST_H_ + +#include +#include +#include + +#include "gtest/internal/gtest-internal.h" +#include "gtest/internal/gtest-string.h" +#include "gtest/gtest-death-test.h" +#include "gtest/gtest-message.h" +#include "gtest/gtest-param-test.h" +#include "gtest/gtest-printers.h" +#include "gtest/gtest_prod.h" +#include "gtest/gtest-test-part.h" +#include "gtest/gtest-typed-test.h" + +// Depending on the platform, different string classes are available. +// On Linux, in addition to ::std::string, Google also makes use of +// class ::string, which has the same interface as ::std::string, but +// has a different implementation. +// +// The user can define GTEST_HAS_GLOBAL_STRING to 1 to indicate that +// ::string is available AND is a distinct type to ::std::string, or +// define it to 0 to indicate otherwise. +// +// If the user's ::std::string and ::string are the same class due to +// aliasing, he should define GTEST_HAS_GLOBAL_STRING to 0. +// +// If the user doesn't define GTEST_HAS_GLOBAL_STRING, it is defined +// heuristically. + +namespace testing { + +// Declares the flags. + +// This flag temporary enables the disabled tests. +GTEST_DECLARE_bool_(also_run_disabled_tests); + +// This flag brings the debugger on an assertion failure. +GTEST_DECLARE_bool_(break_on_failure); + +// This flag controls whether Google Test catches all test-thrown exceptions +// and logs them as failures. +GTEST_DECLARE_bool_(catch_exceptions); + +// This flag enables using colors in terminal output. Available values are +// "yes" to enable colors, "no" (disable colors), or "auto" (the default) +// to let Google Test decide. +GTEST_DECLARE_string_(color); + +// This flag sets up the filter to select by name using a glob pattern +// the tests to run. If the filter is not given all tests are executed. +GTEST_DECLARE_string_(filter); + +// This flag causes the Google Test to list tests. None of the tests listed +// are actually run if the flag is provided. +GTEST_DECLARE_bool_(list_tests); + +// This flag controls whether Google Test emits a detailed XML report to a file +// in addition to its normal textual output. +GTEST_DECLARE_string_(output); + +// This flags control whether Google Test prints the elapsed time for each +// test. +GTEST_DECLARE_bool_(print_time); + +// This flag specifies the random number seed. +GTEST_DECLARE_int32_(random_seed); + +// This flag sets how many times the tests are repeated. The default value +// is 1. If the value is -1 the tests are repeating forever. +GTEST_DECLARE_int32_(repeat); + +// This flag controls whether Google Test includes Google Test internal +// stack frames in failure stack traces. +GTEST_DECLARE_bool_(show_internal_stack_frames); + +// When this flag is specified, tests' order is randomized on every iteration. +GTEST_DECLARE_bool_(shuffle); + +// This flag specifies the maximum number of stack frames to be +// printed in a failure message. +GTEST_DECLARE_int32_(stack_trace_depth); + +// When this flag is specified, a failed assertion will throw an +// exception if exceptions are enabled, or exit the program with a +// non-zero code otherwise. +GTEST_DECLARE_bool_(throw_on_failure); + +// When this flag is set with a "host:port" string, on supported +// platforms test results are streamed to the specified port on +// the specified host machine. +GTEST_DECLARE_string_(stream_result_to); + +// The upper limit for valid stack trace depths. +const int kMaxStackTraceDepth = 100; + +namespace internal { + +class AssertHelper; +class DefaultGlobalTestPartResultReporter; +class ExecDeathTest; +class NoExecDeathTest; +class FinalSuccessChecker; +class GTestFlagSaver; +class StreamingListenerTest; +class TestResultAccessor; +class TestEventListenersAccessor; +class TestEventRepeater; +class UnitTestRecordPropertyTestHelper; +class WindowsDeathTest; +class UnitTestImpl* GetUnitTestImpl(); +void ReportFailureInUnknownLocation(TestPartResult::Type result_type, + const std::string& message); + +} // namespace internal + +// The friend relationship of some of these classes is cyclic. +// If we don't forward declare them the compiler might confuse the classes +// in friendship clauses with same named classes on the scope. +class Test; +class TestCase; +class TestInfo; +class UnitTest; + +// A class for indicating whether an assertion was successful. When +// the assertion wasn't successful, the AssertionResult object +// remembers a non-empty message that describes how it failed. +// +// To create an instance of this class, use one of the factory functions +// (AssertionSuccess() and AssertionFailure()). +// +// This class is useful for two purposes: +// 1. Defining predicate functions to be used with Boolean test assertions +// EXPECT_TRUE/EXPECT_FALSE and their ASSERT_ counterparts +// 2. Defining predicate-format functions to be +// used with predicate assertions (ASSERT_PRED_FORMAT*, etc). +// +// For example, if you define IsEven predicate: +// +// testing::AssertionResult IsEven(int n) { +// if ((n % 2) == 0) +// return testing::AssertionSuccess(); +// else +// return testing::AssertionFailure() << n << " is odd"; +// } +// +// Then the failed expectation EXPECT_TRUE(IsEven(Fib(5))) +// will print the message +// +// Value of: IsEven(Fib(5)) +// Actual: false (5 is odd) +// Expected: true +// +// instead of a more opaque +// +// Value of: IsEven(Fib(5)) +// Actual: false +// Expected: true +// +// in case IsEven is a simple Boolean predicate. +// +// If you expect your predicate to be reused and want to support informative +// messages in EXPECT_FALSE and ASSERT_FALSE (negative assertions show up +// about half as often as positive ones in our tests), supply messages for +// both success and failure cases: +// +// testing::AssertionResult IsEven(int n) { +// if ((n % 2) == 0) +// return testing::AssertionSuccess() << n << " is even"; +// else +// return testing::AssertionFailure() << n << " is odd"; +// } +// +// Then a statement EXPECT_FALSE(IsEven(Fib(6))) will print +// +// Value of: IsEven(Fib(6)) +// Actual: true (8 is even) +// Expected: false +// +// NB: Predicates that support negative Boolean assertions have reduced +// performance in positive ones so be careful not to use them in tests +// that have lots (tens of thousands) of positive Boolean assertions. +// +// To use this class with EXPECT_PRED_FORMAT assertions such as: +// +// // Verifies that Foo() returns an even number. +// EXPECT_PRED_FORMAT1(IsEven, Foo()); +// +// you need to define: +// +// testing::AssertionResult IsEven(const char* expr, int n) { +// if ((n % 2) == 0) +// return testing::AssertionSuccess(); +// else +// return testing::AssertionFailure() +// << "Expected: " << expr << " is even\n Actual: it's " << n; +// } +// +// If Foo() returns 5, you will see the following message: +// +// Expected: Foo() is even +// Actual: it's 5 +// +class GTEST_API_ AssertionResult { + public: + // Copy constructor. + // Used in EXPECT_TRUE/FALSE(assertion_result). + AssertionResult(const AssertionResult& other); + // Used in the EXPECT_TRUE/FALSE(bool_expression). + explicit AssertionResult(bool success) : success_(success) {} + + // Returns true iff the assertion succeeded. + operator bool() const { return success_; } // NOLINT + + // Returns the assertion's negation. Used with EXPECT/ASSERT_FALSE. + AssertionResult operator!() const; + + // Returns the text streamed into this AssertionResult. Test assertions + // use it when they fail (i.e., the predicate's outcome doesn't match the + // assertion's expectation). When nothing has been streamed into the + // object, returns an empty string. + const char* message() const { + return message_.get() != NULL ? message_->c_str() : ""; + } + // TODO(vladl@google.com): Remove this after making sure no clients use it. + // Deprecated; please use message() instead. + const char* failure_message() const { return message(); } + + // Streams a custom failure message into this object. + template AssertionResult& operator<<(const T& value) { + AppendMessage(Message() << value); + return *this; + } + + // Allows streaming basic output manipulators such as endl or flush into + // this object. + AssertionResult& operator<<( + ::std::ostream& (*basic_manipulator)(::std::ostream& stream)) { + AppendMessage(Message() << basic_manipulator); + return *this; + } + + private: + // Appends the contents of message to message_. + void AppendMessage(const Message& a_message) { + if (message_.get() == NULL) + message_.reset(new ::std::string); + message_->append(a_message.GetString().c_str()); + } + + // Stores result of the assertion predicate. + bool success_; + // Stores the message describing the condition in case the expectation + // construct is not satisfied with the predicate's outcome. + // Referenced via a pointer to avoid taking too much stack frame space + // with test assertions. + internal::scoped_ptr< ::std::string> message_; + + GTEST_DISALLOW_ASSIGN_(AssertionResult); +}; + +// Makes a successful assertion result. +GTEST_API_ AssertionResult AssertionSuccess(); + +// Makes a failed assertion result. +GTEST_API_ AssertionResult AssertionFailure(); + +// Makes a failed assertion result with the given failure message. +// Deprecated; use AssertionFailure() << msg. +GTEST_API_ AssertionResult AssertionFailure(const Message& msg); + +// The abstract class that all tests inherit from. +// +// In Google Test, a unit test program contains one or many TestCases, and +// each TestCase contains one or many Tests. +// +// When you define a test using the TEST macro, you don't need to +// explicitly derive from Test - the TEST macro automatically does +// this for you. +// +// The only time you derive from Test is when defining a test fixture +// to be used a TEST_F. For example: +// +// class FooTest : public testing::Test { +// protected: +// virtual void SetUp() { ... } +// virtual void TearDown() { ... } +// ... +// }; +// +// TEST_F(FooTest, Bar) { ... } +// TEST_F(FooTest, Baz) { ... } +// +// Test is not copyable. +class GTEST_API_ Test { + public: + friend class TestInfo; + + // Defines types for pointers to functions that set up and tear down + // a test case. + typedef internal::SetUpTestCaseFunc SetUpTestCaseFunc; + typedef internal::TearDownTestCaseFunc TearDownTestCaseFunc; + + // The d'tor is virtual as we intend to inherit from Test. + virtual ~Test(); + + // Sets up the stuff shared by all tests in this test case. + // + // Google Test will call Foo::SetUpTestCase() before running the first + // test in test case Foo. Hence a sub-class can define its own + // SetUpTestCase() method to shadow the one defined in the super + // class. + static void SetUpTestCase() {} + + // Tears down the stuff shared by all tests in this test case. + // + // Google Test will call Foo::TearDownTestCase() after running the last + // test in test case Foo. Hence a sub-class can define its own + // TearDownTestCase() method to shadow the one defined in the super + // class. + static void TearDownTestCase() {} + + // Returns true iff the current test has a fatal failure. + static bool HasFatalFailure(); + + // Returns true iff the current test has a non-fatal failure. + static bool HasNonfatalFailure(); + + // Returns true iff the current test has a (either fatal or + // non-fatal) failure. + static bool HasFailure() { return HasFatalFailure() || HasNonfatalFailure(); } + + // Logs a property for the current test, test case, or for the entire + // invocation of the test program when used outside of the context of a + // test case. Only the last value for a given key is remembered. These + // are public static so they can be called from utility functions that are + // not members of the test fixture. Calls to RecordProperty made during + // lifespan of the test (from the moment its constructor starts to the + // moment its destructor finishes) will be output in XML as attributes of + // the element. Properties recorded from fixture's + // SetUpTestCase or TearDownTestCase are logged as attributes of the + // corresponding element. Calls to RecordProperty made in the + // global context (before or after invocation of RUN_ALL_TESTS and from + // SetUp/TearDown method of Environment objects registered with Google + // Test) will be output as attributes of the element. + static void RecordProperty(const std::string& key, const std::string& value); + static void RecordProperty(const std::string& key, int value); + + protected: + // Creates a Test object. + Test(); + + // Sets up the test fixture. + virtual void SetUp(); + + // Tears down the test fixture. + virtual void TearDown(); + + private: + // Returns true iff the current test has the same fixture class as + // the first test in the current test case. + static bool HasSameFixtureClass(); + + // Runs the test after the test fixture has been set up. + // + // A sub-class must implement this to define the test logic. + // + // DO NOT OVERRIDE THIS FUNCTION DIRECTLY IN A USER PROGRAM. + // Instead, use the TEST or TEST_F macro. + virtual void TestBody() = 0; + + // Sets up, executes, and tears down the test. + void Run(); + + // Deletes self. We deliberately pick an unusual name for this + // internal method to avoid clashing with names used in user TESTs. + void DeleteSelf_() { delete this; } + + // Uses a GTestFlagSaver to save and restore all Google Test flags. + const internal::GTestFlagSaver* const gtest_flag_saver_; + + // Often a user mis-spells SetUp() as Setup() and spends a long time + // wondering why it is never called by Google Test. The declaration of + // the following method is solely for catching such an error at + // compile time: + // + // - The return type is deliberately chosen to be not void, so it + // will be a conflict if a user declares void Setup() in his test + // fixture. + // + // - This method is private, so it will be another compiler error + // if a user calls it from his test fixture. + // + // DO NOT OVERRIDE THIS FUNCTION. + // + // If you see an error about overriding the following function or + // about it being private, you have mis-spelled SetUp() as Setup(). + struct Setup_should_be_spelled_SetUp {}; + virtual Setup_should_be_spelled_SetUp* Setup() { return NULL; } + + // We disallow copying Tests. + GTEST_DISALLOW_COPY_AND_ASSIGN_(Test); +}; + +typedef internal::TimeInMillis TimeInMillis; + +// A copyable object representing a user specified test property which can be +// output as a key/value string pair. +// +// Don't inherit from TestProperty as its destructor is not virtual. +class TestProperty { + public: + // C'tor. TestProperty does NOT have a default constructor. + // Always use this constructor (with parameters) to create a + // TestProperty object. + TestProperty(const std::string& a_key, const std::string& a_value) : + key_(a_key), value_(a_value) { + } + + // Gets the user supplied key. + const char* key() const { + return key_.c_str(); + } + + // Gets the user supplied value. + const char* value() const { + return value_.c_str(); + } + + // Sets a new value, overriding the one supplied in the constructor. + void SetValue(const std::string& new_value) { + value_ = new_value; + } + + private: + // The key supplied by the user. + std::string key_; + // The value supplied by the user. + std::string value_; +}; + +// The result of a single Test. This includes a list of +// TestPartResults, a list of TestProperties, a count of how many +// death tests there are in the Test, and how much time it took to run +// the Test. +// +// TestResult is not copyable. +class GTEST_API_ TestResult { + public: + // Creates an empty TestResult. + TestResult(); + + // D'tor. Do not inherit from TestResult. + ~TestResult(); + + // Gets the number of all test parts. This is the sum of the number + // of successful test parts and the number of failed test parts. + int total_part_count() const; + + // Returns the number of the test properties. + int test_property_count() const; + + // Returns true iff the test passed (i.e. no test part failed). + bool Passed() const { return !Failed(); } + + // Returns true iff the test failed. + bool Failed() const; + + // Returns true iff the test fatally failed. + bool HasFatalFailure() const; + + // Returns true iff the test has a non-fatal failure. + bool HasNonfatalFailure() const; + + // Returns the elapsed time, in milliseconds. + TimeInMillis elapsed_time() const { return elapsed_time_; } + + // Returns the i-th test part result among all the results. i can range + // from 0 to test_property_count() - 1. If i is not in that range, aborts + // the program. + const TestPartResult& GetTestPartResult(int i) const; + + // Returns the i-th test property. i can range from 0 to + // test_property_count() - 1. If i is not in that range, aborts the + // program. + const TestProperty& GetTestProperty(int i) const; + + private: + friend class TestInfo; + friend class TestCase; + friend class UnitTest; + friend class internal::DefaultGlobalTestPartResultReporter; + friend class internal::ExecDeathTest; + friend class internal::TestResultAccessor; + friend class internal::UnitTestImpl; + friend class internal::WindowsDeathTest; + + // Gets the vector of TestPartResults. + const std::vector& test_part_results() const { + return test_part_results_; + } + + // Gets the vector of TestProperties. + const std::vector& test_properties() const { + return test_properties_; + } + + // Sets the elapsed time. + void set_elapsed_time(TimeInMillis elapsed) { elapsed_time_ = elapsed; } + + // Adds a test property to the list. The property is validated and may add + // a non-fatal failure if invalid (e.g., if it conflicts with reserved + // key names). If a property is already recorded for the same key, the + // value will be updated, rather than storing multiple values for the same + // key. xml_element specifies the element for which the property is being + // recorded and is used for validation. + void RecordProperty(const std::string& xml_element, + const TestProperty& test_property); + + // Adds a failure if the key is a reserved attribute of Google Test + // testcase tags. Returns true if the property is valid. + // TODO(russr): Validate attribute names are legal and human readable. + static bool ValidateTestProperty(const std::string& xml_element, + const TestProperty& test_property); + + // Adds a test part result to the list. + void AddTestPartResult(const TestPartResult& test_part_result); + + // Returns the death test count. + int death_test_count() const { return death_test_count_; } + + // Increments the death test count, returning the new count. + int increment_death_test_count() { return ++death_test_count_; } + + // Clears the test part results. + void ClearTestPartResults(); + + // Clears the object. + void Clear(); + + // Protects mutable state of the property vector and of owned + // properties, whose values may be updated. + internal::Mutex test_properites_mutex_; + + // The vector of TestPartResults + std::vector test_part_results_; + // The vector of TestProperties + std::vector test_properties_; + // Running count of death tests. + int death_test_count_; + // The elapsed time, in milliseconds. + TimeInMillis elapsed_time_; + + // We disallow copying TestResult. + GTEST_DISALLOW_COPY_AND_ASSIGN_(TestResult); +}; // class TestResult + +// A TestInfo object stores the following information about a test: +// +// Test case name +// Test name +// Whether the test should be run +// A function pointer that creates the test object when invoked +// Test result +// +// The constructor of TestInfo registers itself with the UnitTest +// singleton such that the RUN_ALL_TESTS() macro knows which tests to +// run. +class GTEST_API_ TestInfo { + public: + // Destructs a TestInfo object. This function is not virtual, so + // don't inherit from TestInfo. + ~TestInfo(); + + // Returns the test case name. + const char* test_case_name() const { return test_case_name_.c_str(); } + + // Returns the test name. + const char* name() const { return name_.c_str(); } + + // Returns the name of the parameter type, or NULL if this is not a typed + // or a type-parameterized test. + const char* type_param() const { + if (type_param_.get() != NULL) + return type_param_->c_str(); + return NULL; + } + + // Returns the text representation of the value parameter, or NULL if this + // is not a value-parameterized test. + const char* value_param() const { + if (value_param_.get() != NULL) + return value_param_->c_str(); + return NULL; + } + + // Returns true if this test should run, that is if the test is not + // disabled (or it is disabled but the also_run_disabled_tests flag has + // been specified) and its full name matches the user-specified filter. + // + // Google Test allows the user to filter the tests by their full names. + // The full name of a test Bar in test case Foo is defined as + // "Foo.Bar". Only the tests that match the filter will run. + // + // A filter is a colon-separated list of glob (not regex) patterns, + // optionally followed by a '-' and a colon-separated list of + // negative patterns (tests to exclude). A test is run if it + // matches one of the positive patterns and does not match any of + // the negative patterns. + // + // For example, *A*:Foo.* is a filter that matches any string that + // contains the character 'A' or starts with "Foo.". + bool should_run() const { return should_run_; } + + // Returns true iff this test will appear in the XML report. + bool is_reportable() const { + // For now, the XML report includes all tests matching the filter. + // In the future, we may trim tests that are excluded because of + // sharding. + return matches_filter_; + } + + // Returns the result of the test. + const TestResult* result() const { return &result_; } + + private: +#if GTEST_HAS_DEATH_TEST + friend class internal::DefaultDeathTestFactory; +#endif // GTEST_HAS_DEATH_TEST + friend class Test; + friend class TestCase; + friend class internal::UnitTestImpl; + friend class internal::StreamingListenerTest; + friend TestInfo* internal::MakeAndRegisterTestInfo( + const char* test_case_name, + const char* name, + const char* type_param, + const char* value_param, + internal::TypeId fixture_class_id, + Test::SetUpTestCaseFunc set_up_tc, + Test::TearDownTestCaseFunc tear_down_tc, + internal::TestFactoryBase* factory); + + // Constructs a TestInfo object. The newly constructed instance assumes + // ownership of the factory object. + TestInfo(const std::string& test_case_name, + const std::string& name, + const char* a_type_param, // NULL if not a type-parameterized test + const char* a_value_param, // NULL if not a value-parameterized test + internal::TypeId fixture_class_id, + internal::TestFactoryBase* factory); + + // Increments the number of death tests encountered in this test so + // far. + int increment_death_test_count() { + return result_.increment_death_test_count(); + } + + // Creates the test object, runs it, records its result, and then + // deletes it. + void Run(); + + static void ClearTestResult(TestInfo* test_info) { + test_info->result_.Clear(); + } + + // These fields are immutable properties of the test. + const std::string test_case_name_; // Test case name + const std::string name_; // Test name + // Name of the parameter type, or NULL if this is not a typed or a + // type-parameterized test. + const internal::scoped_ptr type_param_; + // Text representation of the value parameter, or NULL if this is not a + // value-parameterized test. + const internal::scoped_ptr value_param_; + const internal::TypeId fixture_class_id_; // ID of the test fixture class + bool should_run_; // True iff this test should run + bool is_disabled_; // True iff this test is disabled + bool matches_filter_; // True if this test matches the + // user-specified filter. + internal::TestFactoryBase* const factory_; // The factory that creates + // the test object + + // This field is mutable and needs to be reset before running the + // test for the second time. + TestResult result_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(TestInfo); +}; + +// A test case, which consists of a vector of TestInfos. +// +// TestCase is not copyable. +class GTEST_API_ TestCase { + public: + // Creates a TestCase with the given name. + // + // TestCase does NOT have a default constructor. Always use this + // constructor to create a TestCase object. + // + // Arguments: + // + // name: name of the test case + // a_type_param: the name of the test's type parameter, or NULL if + // this is not a type-parameterized test. + // set_up_tc: pointer to the function that sets up the test case + // tear_down_tc: pointer to the function that tears down the test case + TestCase(const char* name, const char* a_type_param, + Test::SetUpTestCaseFunc set_up_tc, + Test::TearDownTestCaseFunc tear_down_tc); + + // Destructor of TestCase. + virtual ~TestCase(); + + // Gets the name of the TestCase. + const char* name() const { return name_.c_str(); } + + // Returns the name of the parameter type, or NULL if this is not a + // type-parameterized test case. + const char* type_param() const { + if (type_param_.get() != NULL) + return type_param_->c_str(); + return NULL; + } + + // Returns true if any test in this test case should run. + bool should_run() const { return should_run_; } + + // Gets the number of successful tests in this test case. + int successful_test_count() const; + + // Gets the number of failed tests in this test case. + int failed_test_count() const; + + // Gets the number of disabled tests that will be reported in the XML report. + int reportable_disabled_test_count() const; + + // Gets the number of disabled tests in this test case. + int disabled_test_count() const; + + // Gets the number of tests to be printed in the XML report. + int reportable_test_count() const; + + // Get the number of tests in this test case that should run. + int test_to_run_count() const; + + // Gets the number of all tests in this test case. + int total_test_count() const; + + // Returns true iff the test case passed. + bool Passed() const { return !Failed(); } + + // Returns true iff the test case failed. + bool Failed() const { return failed_test_count() > 0; } + + // Returns the elapsed time, in milliseconds. + TimeInMillis elapsed_time() const { return elapsed_time_; } + + // Returns the i-th test among all the tests. i can range from 0 to + // total_test_count() - 1. If i is not in that range, returns NULL. + const TestInfo* GetTestInfo(int i) const; + + // Returns the TestResult that holds test properties recorded during + // execution of SetUpTestCase and TearDownTestCase. + const TestResult& ad_hoc_test_result() const { return ad_hoc_test_result_; } + + private: + friend class Test; + friend class internal::UnitTestImpl; + + // Gets the (mutable) vector of TestInfos in this TestCase. + std::vector& test_info_list() { return test_info_list_; } + + // Gets the (immutable) vector of TestInfos in this TestCase. + const std::vector& test_info_list() const { + return test_info_list_; + } + + // Returns the i-th test among all the tests. i can range from 0 to + // total_test_count() - 1. If i is not in that range, returns NULL. + TestInfo* GetMutableTestInfo(int i); + + // Sets the should_run member. + void set_should_run(bool should) { should_run_ = should; } + + // Adds a TestInfo to this test case. Will delete the TestInfo upon + // destruction of the TestCase object. + void AddTestInfo(TestInfo * test_info); + + // Clears the results of all tests in this test case. + void ClearResult(); + + // Clears the results of all tests in the given test case. + static void ClearTestCaseResult(TestCase* test_case) { + test_case->ClearResult(); + } + + // Runs every test in this TestCase. + void Run(); + + // Runs SetUpTestCase() for this TestCase. This wrapper is needed + // for catching exceptions thrown from SetUpTestCase(). + void RunSetUpTestCase() { (*set_up_tc_)(); } + + // Runs TearDownTestCase() for this TestCase. This wrapper is + // needed for catching exceptions thrown from TearDownTestCase(). + void RunTearDownTestCase() { (*tear_down_tc_)(); } + + // Returns true iff test passed. + static bool TestPassed(const TestInfo* test_info) { + return test_info->should_run() && test_info->result()->Passed(); + } + + // Returns true iff test failed. + static bool TestFailed(const TestInfo* test_info) { + return test_info->should_run() && test_info->result()->Failed(); + } + + // Returns true iff the test is disabled and will be reported in the XML + // report. + static bool TestReportableDisabled(const TestInfo* test_info) { + return test_info->is_reportable() && test_info->is_disabled_; + } + + // Returns true iff test is disabled. + static bool TestDisabled(const TestInfo* test_info) { + return test_info->is_disabled_; + } + + // Returns true iff this test will appear in the XML report. + static bool TestReportable(const TestInfo* test_info) { + return test_info->is_reportable(); + } + + // Returns true if the given test should run. + static bool ShouldRunTest(const TestInfo* test_info) { + return test_info->should_run(); + } + + // Shuffles the tests in this test case. + void ShuffleTests(internal::Random* random); + + // Restores the test order to before the first shuffle. + void UnshuffleTests(); + + // Name of the test case. + std::string name_; + // Name of the parameter type, or NULL if this is not a typed or a + // type-parameterized test. + const internal::scoped_ptr type_param_; + // The vector of TestInfos in their original order. It owns the + // elements in the vector. + std::vector test_info_list_; + // Provides a level of indirection for the test list to allow easy + // shuffling and restoring the test order. The i-th element in this + // vector is the index of the i-th test in the shuffled test list. + std::vector test_indices_; + // Pointer to the function that sets up the test case. + Test::SetUpTestCaseFunc set_up_tc_; + // Pointer to the function that tears down the test case. + Test::TearDownTestCaseFunc tear_down_tc_; + // True iff any test in this test case should run. + bool should_run_; + // Elapsed time, in milliseconds. + TimeInMillis elapsed_time_; + // Holds test properties recorded during execution of SetUpTestCase and + // TearDownTestCase. + TestResult ad_hoc_test_result_; + + // We disallow copying TestCases. + GTEST_DISALLOW_COPY_AND_ASSIGN_(TestCase); +}; + +// An Environment object is capable of setting up and tearing down an +// environment. The user should subclass this to define his own +// environment(s). +// +// An Environment object does the set-up and tear-down in virtual +// methods SetUp() and TearDown() instead of the constructor and the +// destructor, as: +// +// 1. You cannot safely throw from a destructor. This is a problem +// as in some cases Google Test is used where exceptions are enabled, and +// we may want to implement ASSERT_* using exceptions where they are +// available. +// 2. You cannot use ASSERT_* directly in a constructor or +// destructor. +class Environment { + public: + // The d'tor is virtual as we need to subclass Environment. + virtual ~Environment() {} + + // Override this to define how to set up the environment. + virtual void SetUp() {} + + // Override this to define how to tear down the environment. + virtual void TearDown() {} + private: + // If you see an error about overriding the following function or + // about it being private, you have mis-spelled SetUp() as Setup(). + struct Setup_should_be_spelled_SetUp {}; + virtual Setup_should_be_spelled_SetUp* Setup() { return NULL; } +}; + +// The interface for tracing execution of tests. The methods are organized in +// the order the corresponding events are fired. +class TestEventListener { + public: + virtual ~TestEventListener() {} + + // Fired before any test activity starts. + virtual void OnTestProgramStart(const UnitTest& unit_test) = 0; + + // Fired before each iteration of tests starts. There may be more than + // one iteration if GTEST_FLAG(repeat) is set. iteration is the iteration + // index, starting from 0. + virtual void OnTestIterationStart(const UnitTest& unit_test, + int iteration) = 0; + + // Fired before environment set-up for each iteration of tests starts. + virtual void OnEnvironmentsSetUpStart(const UnitTest& unit_test) = 0; + + // Fired after environment set-up for each iteration of tests ends. + virtual void OnEnvironmentsSetUpEnd(const UnitTest& unit_test) = 0; + + // Fired before the test case starts. + virtual void OnTestCaseStart(const TestCase& test_case) = 0; + + // Fired before the test starts. + virtual void OnTestStart(const TestInfo& test_info) = 0; + + // Fired after a failed assertion or a SUCCEED() invocation. + virtual void OnTestPartResult(const TestPartResult& test_part_result) = 0; + + // Fired after the test ends. + virtual void OnTestEnd(const TestInfo& test_info) = 0; + + // Fired after the test case ends. + virtual void OnTestCaseEnd(const TestCase& test_case) = 0; + + // Fired before environment tear-down for each iteration of tests starts. + virtual void OnEnvironmentsTearDownStart(const UnitTest& unit_test) = 0; + + // Fired after environment tear-down for each iteration of tests ends. + virtual void OnEnvironmentsTearDownEnd(const UnitTest& unit_test) = 0; + + // Fired after each iteration of tests finishes. + virtual void OnTestIterationEnd(const UnitTest& unit_test, + int iteration) = 0; + + // Fired after all test activities have ended. + virtual void OnTestProgramEnd(const UnitTest& unit_test) = 0; +}; + +// The convenience class for users who need to override just one or two +// methods and are not concerned that a possible change to a signature of +// the methods they override will not be caught during the build. For +// comments about each method please see the definition of TestEventListener +// above. +class EmptyTestEventListener : public TestEventListener { + public: + virtual void OnTestProgramStart(const UnitTest& /*unit_test*/) {} + virtual void OnTestIterationStart(const UnitTest& /*unit_test*/, + int /*iteration*/) {} + virtual void OnEnvironmentsSetUpStart(const UnitTest& /*unit_test*/) {} + virtual void OnEnvironmentsSetUpEnd(const UnitTest& /*unit_test*/) {} + virtual void OnTestCaseStart(const TestCase& /*test_case*/) {} + virtual void OnTestStart(const TestInfo& /*test_info*/) {} + virtual void OnTestPartResult(const TestPartResult& /*test_part_result*/) {} + virtual void OnTestEnd(const TestInfo& /*test_info*/) {} + virtual void OnTestCaseEnd(const TestCase& /*test_case*/) {} + virtual void OnEnvironmentsTearDownStart(const UnitTest& /*unit_test*/) {} + virtual void OnEnvironmentsTearDownEnd(const UnitTest& /*unit_test*/) {} + virtual void OnTestIterationEnd(const UnitTest& /*unit_test*/, + int /*iteration*/) {} + virtual void OnTestProgramEnd(const UnitTest& /*unit_test*/) {} +}; + +// TestEventListeners lets users add listeners to track events in Google Test. +class GTEST_API_ TestEventListeners { + public: + TestEventListeners(); + ~TestEventListeners(); + + // Appends an event listener to the end of the list. Google Test assumes + // the ownership of the listener (i.e. it will delete the listener when + // the test program finishes). + void Append(TestEventListener* listener); + + // Removes the given event listener from the list and returns it. It then + // becomes the caller's responsibility to delete the listener. Returns + // NULL if the listener is not found in the list. + TestEventListener* Release(TestEventListener* listener); + + // Returns the standard listener responsible for the default console + // output. Can be removed from the listeners list to shut down default + // console output. Note that removing this object from the listener list + // with Release transfers its ownership to the caller and makes this + // function return NULL the next time. + TestEventListener* default_result_printer() const { + return default_result_printer_; + } + + // Returns the standard listener responsible for the default XML output + // controlled by the --gtest_output=xml flag. Can be removed from the + // listeners list by users who want to shut down the default XML output + // controlled by this flag and substitute it with custom one. Note that + // removing this object from the listener list with Release transfers its + // ownership to the caller and makes this function return NULL the next + // time. + TestEventListener* default_xml_generator() const { + return default_xml_generator_; + } + + private: + friend class TestCase; + friend class TestInfo; + friend class internal::DefaultGlobalTestPartResultReporter; + friend class internal::NoExecDeathTest; + friend class internal::TestEventListenersAccessor; + friend class internal::UnitTestImpl; + + // Returns repeater that broadcasts the TestEventListener events to all + // subscribers. + TestEventListener* repeater(); + + // Sets the default_result_printer attribute to the provided listener. + // The listener is also added to the listener list and previous + // default_result_printer is removed from it and deleted. The listener can + // also be NULL in which case it will not be added to the list. Does + // nothing if the previous and the current listener objects are the same. + void SetDefaultResultPrinter(TestEventListener* listener); + + // Sets the default_xml_generator attribute to the provided listener. The + // listener is also added to the listener list and previous + // default_xml_generator is removed from it and deleted. The listener can + // also be NULL in which case it will not be added to the list. Does + // nothing if the previous and the current listener objects are the same. + void SetDefaultXmlGenerator(TestEventListener* listener); + + // Controls whether events will be forwarded by the repeater to the + // listeners in the list. + bool EventForwardingEnabled() const; + void SuppressEventForwarding(); + + // The actual list of listeners. + internal::TestEventRepeater* repeater_; + // Listener responsible for the standard result output. + TestEventListener* default_result_printer_; + // Listener responsible for the creation of the XML output file. + TestEventListener* default_xml_generator_; + + // We disallow copying TestEventListeners. + GTEST_DISALLOW_COPY_AND_ASSIGN_(TestEventListeners); +}; + +// A UnitTest consists of a vector of TestCases. +// +// This is a singleton class. The only instance of UnitTest is +// created when UnitTest::GetInstance() is first called. This +// instance is never deleted. +// +// UnitTest is not copyable. +// +// This class is thread-safe as long as the methods are called +// according to their specification. +class GTEST_API_ UnitTest { + public: + // Gets the singleton UnitTest object. The first time this method + // is called, a UnitTest object is constructed and returned. + // Consecutive calls will return the same object. + static UnitTest* GetInstance(); + + // Runs all tests in this UnitTest object and prints the result. + // Returns 0 if successful, or 1 otherwise. + // + // This method can only be called from the main thread. + // + // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. + int Run() GTEST_MUST_USE_RESULT_; + + // Returns the working directory when the first TEST() or TEST_F() + // was executed. The UnitTest object owns the string. + const char* original_working_dir() const; + + // Returns the TestCase object for the test that's currently running, + // or NULL if no test is running. + const TestCase* current_test_case() const + GTEST_LOCK_EXCLUDED_(mutex_); + + // Returns the TestInfo object for the test that's currently running, + // or NULL if no test is running. + const TestInfo* current_test_info() const + GTEST_LOCK_EXCLUDED_(mutex_); + + // Returns the random seed used at the start of the current test run. + int random_seed() const; + +#if GTEST_HAS_PARAM_TEST + // Returns the ParameterizedTestCaseRegistry object used to keep track of + // value-parameterized tests and instantiate and register them. + // + // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. + internal::ParameterizedTestCaseRegistry& parameterized_test_registry() + GTEST_LOCK_EXCLUDED_(mutex_); +#endif // GTEST_HAS_PARAM_TEST + + // Gets the number of successful test cases. + int successful_test_case_count() const; + + // Gets the number of failed test cases. + int failed_test_case_count() const; + + // Gets the number of all test cases. + int total_test_case_count() const; + + // Gets the number of all test cases that contain at least one test + // that should run. + int test_case_to_run_count() const; + + // Gets the number of successful tests. + int successful_test_count() const; + + // Gets the number of failed tests. + int failed_test_count() const; + + // Gets the number of disabled tests that will be reported in the XML report. + int reportable_disabled_test_count() const; + + // Gets the number of disabled tests. + int disabled_test_count() const; + + // Gets the number of tests to be printed in the XML report. + int reportable_test_count() const; + + // Gets the number of all tests. + int total_test_count() const; + + // Gets the number of tests that should run. + int test_to_run_count() const; + + // Gets the time of the test program start, in ms from the start of the + // UNIX epoch. + TimeInMillis start_timestamp() const; + + // Gets the elapsed time, in milliseconds. + TimeInMillis elapsed_time() const; + + // Returns true iff the unit test passed (i.e. all test cases passed). + bool Passed() const; + + // Returns true iff the unit test failed (i.e. some test case failed + // or something outside of all tests failed). + bool Failed() const; + + // Gets the i-th test case among all the test cases. i can range from 0 to + // total_test_case_count() - 1. If i is not in that range, returns NULL. + const TestCase* GetTestCase(int i) const; + + // Returns the TestResult containing information on test failures and + // properties logged outside of individual test cases. + const TestResult& ad_hoc_test_result() const; + + // Returns the list of event listeners that can be used to track events + // inside Google Test. + TestEventListeners& listeners(); + + private: + // Registers and returns a global test environment. When a test + // program is run, all global test environments will be set-up in + // the order they were registered. After all tests in the program + // have finished, all global test environments will be torn-down in + // the *reverse* order they were registered. + // + // The UnitTest object takes ownership of the given environment. + // + // This method can only be called from the main thread. + Environment* AddEnvironment(Environment* env); + + // Adds a TestPartResult to the current TestResult object. All + // Google Test assertion macros (e.g. ASSERT_TRUE, EXPECT_EQ, etc) + // eventually call this to report their results. The user code + // should use the assertion macros instead of calling this directly. + void AddTestPartResult(TestPartResult::Type result_type, + const char* file_name, + int line_number, + const std::string& message, + const std::string& os_stack_trace) + GTEST_LOCK_EXCLUDED_(mutex_); + + // Adds a TestProperty to the current TestResult object when invoked from + // inside a test, to current TestCase's ad_hoc_test_result_ when invoked + // from SetUpTestCase or TearDownTestCase, or to the global property set + // when invoked elsewhere. If the result already contains a property with + // the same key, the value will be updated. + void RecordProperty(const std::string& key, const std::string& value); + + // Gets the i-th test case among all the test cases. i can range from 0 to + // total_test_case_count() - 1. If i is not in that range, returns NULL. + TestCase* GetMutableTestCase(int i); + + // Accessors for the implementation object. + internal::UnitTestImpl* impl() { return impl_; } + const internal::UnitTestImpl* impl() const { return impl_; } + + // These classes and funcions are friends as they need to access private + // members of UnitTest. + friend class Test; + friend class internal::AssertHelper; + friend class internal::ScopedTrace; + friend class internal::StreamingListenerTest; + friend class internal::UnitTestRecordPropertyTestHelper; + friend Environment* AddGlobalTestEnvironment(Environment* env); + friend internal::UnitTestImpl* internal::GetUnitTestImpl(); + friend void internal::ReportFailureInUnknownLocation( + TestPartResult::Type result_type, + const std::string& message); + + // Creates an empty UnitTest. + UnitTest(); + + // D'tor + virtual ~UnitTest(); + + // Pushes a trace defined by SCOPED_TRACE() on to the per-thread + // Google Test trace stack. + void PushGTestTrace(const internal::TraceInfo& trace) + GTEST_LOCK_EXCLUDED_(mutex_); + + // Pops a trace from the per-thread Google Test trace stack. + void PopGTestTrace() + GTEST_LOCK_EXCLUDED_(mutex_); + + // Protects mutable state in *impl_. This is mutable as some const + // methods need to lock it too. + mutable internal::Mutex mutex_; + + // Opaque implementation object. This field is never changed once + // the object is constructed. We don't mark it as const here, as + // doing so will cause a warning in the constructor of UnitTest. + // Mutable state in *impl_ is protected by mutex_. + internal::UnitTestImpl* impl_; + + // We disallow copying UnitTest. + GTEST_DISALLOW_COPY_AND_ASSIGN_(UnitTest); +}; + +// A convenient wrapper for adding an environment for the test +// program. +// +// You should call this before RUN_ALL_TESTS() is called, probably in +// main(). If you use gtest_main, you need to call this before main() +// starts for it to take effect. For example, you can define a global +// variable like this: +// +// testing::Environment* const foo_env = +// testing::AddGlobalTestEnvironment(new FooEnvironment); +// +// However, we strongly recommend you to write your own main() and +// call AddGlobalTestEnvironment() there, as relying on initialization +// of global variables makes the code harder to read and may cause +// problems when you register multiple environments from different +// translation units and the environments have dependencies among them +// (remember that the compiler doesn't guarantee the order in which +// global variables from different translation units are initialized). +inline Environment* AddGlobalTestEnvironment(Environment* env) { + return UnitTest::GetInstance()->AddEnvironment(env); +} + +// Initializes Google Test. This must be called before calling +// RUN_ALL_TESTS(). In particular, it parses a command line for the +// flags that Google Test recognizes. Whenever a Google Test flag is +// seen, it is removed from argv, and *argc is decremented. +// +// No value is returned. Instead, the Google Test flag variables are +// updated. +// +// Calling the function for the second time has no user-visible effect. +GTEST_API_ void InitGoogleTest(int* argc, char** argv); + +// This overloaded version can be used in Windows programs compiled in +// UNICODE mode. +GTEST_API_ void InitGoogleTest(int* argc, wchar_t** argv); + +namespace internal { + +// FormatForComparison::Format(value) formats a +// value of type ToPrint that is an operand of a comparison assertion +// (e.g. ASSERT_EQ). OtherOperand is the type of the other operand in +// the comparison, and is used to help determine the best way to +// format the value. In particular, when the value is a C string +// (char pointer) and the other operand is an STL string object, we +// want to format the C string as a string, since we know it is +// compared by value with the string object. If the value is a char +// pointer but the other operand is not an STL string object, we don't +// know whether the pointer is supposed to point to a NUL-terminated +// string, and thus want to print it as a pointer to be safe. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. + +// The default case. +template +class FormatForComparison { + public: + static ::std::string Format(const ToPrint& value) { + return ::testing::PrintToString(value); + } +}; + +// Array. +template +class FormatForComparison { + public: + static ::std::string Format(const ToPrint* value) { + return FormatForComparison::Format(value); + } +}; + +// By default, print C string as pointers to be safe, as we don't know +// whether they actually point to a NUL-terminated string. + +#define GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(CharType) \ + template \ + class FormatForComparison { \ + public: \ + static ::std::string Format(CharType* value) { \ + return ::testing::PrintToString(static_cast(value)); \ + } \ + } + +GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(char); +GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(const char); +GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(wchar_t); +GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(const wchar_t); + +#undef GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_ + +// If a C string is compared with an STL string object, we know it's meant +// to point to a NUL-terminated string, and thus can print it as a string. + +#define GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(CharType, OtherStringType) \ + template <> \ + class FormatForComparison { \ + public: \ + static ::std::string Format(CharType* value) { \ + return ::testing::PrintToString(value); \ + } \ + } + +GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(char, ::std::string); +GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(const char, ::std::string); + +#if GTEST_HAS_GLOBAL_STRING +GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(char, ::string); +GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(const char, ::string); +#endif + +#if GTEST_HAS_GLOBAL_WSTRING +GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(wchar_t, ::wstring); +GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(const wchar_t, ::wstring); +#endif + +#if GTEST_HAS_STD_WSTRING +GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(wchar_t, ::std::wstring); +GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(const wchar_t, ::std::wstring); +#endif + +#undef GTEST_IMPL_FORMAT_C_STRING_AS_STRING_ + +// Formats a comparison assertion (e.g. ASSERT_EQ, EXPECT_LT, and etc) +// operand to be used in a failure message. The type (but not value) +// of the other operand may affect the format. This allows us to +// print a char* as a raw pointer when it is compared against another +// char* or void*, and print it as a C string when it is compared +// against an std::string object, for example. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +template +std::string FormatForComparisonFailureMessage( + const T1& value, const T2& /* other_operand */) { + return FormatForComparison::Format(value); +} + +// The helper function for {ASSERT|EXPECT}_EQ. +template +AssertionResult CmpHelperEQ(const char* expected_expression, + const char* actual_expression, + const T1& expected, + const T2& actual) { +#ifdef _MSC_VER +# pragma warning(push) // Saves the current warning state. +# pragma warning(disable:4389) // Temporarily disables warning on + // signed/unsigned mismatch. +#endif + + if (expected == actual) { + return AssertionSuccess(); + } + +#ifdef _MSC_VER +# pragma warning(pop) // Restores the warning state. +#endif + + return EqFailure(expected_expression, + actual_expression, + FormatForComparisonFailureMessage(expected, actual), + FormatForComparisonFailureMessage(actual, expected), + false); +} + +// With this overloaded version, we allow anonymous enums to be used +// in {ASSERT|EXPECT}_EQ when compiled with gcc 4, as anonymous enums +// can be implicitly cast to BiggestInt. +GTEST_API_ AssertionResult CmpHelperEQ(const char* expected_expression, + const char* actual_expression, + BiggestInt expected, + BiggestInt actual); + +// The helper class for {ASSERT|EXPECT}_EQ. The template argument +// lhs_is_null_literal is true iff the first argument to ASSERT_EQ() +// is a null pointer literal. The following default implementation is +// for lhs_is_null_literal being false. +template +class EqHelper { + public: + // This templatized version is for the general case. + template + static AssertionResult Compare(const char* expected_expression, + const char* actual_expression, + const T1& expected, + const T2& actual) { + return CmpHelperEQ(expected_expression, actual_expression, expected, + actual); + } + + // With this overloaded version, we allow anonymous enums to be used + // in {ASSERT|EXPECT}_EQ when compiled with gcc 4, as anonymous + // enums can be implicitly cast to BiggestInt. + // + // Even though its body looks the same as the above version, we + // cannot merge the two, as it will make anonymous enums unhappy. + static AssertionResult Compare(const char* expected_expression, + const char* actual_expression, + BiggestInt expected, + BiggestInt actual) { + return CmpHelperEQ(expected_expression, actual_expression, expected, + actual); + } +}; + +// This specialization is used when the first argument to ASSERT_EQ() +// is a null pointer literal, like NULL, false, or 0. +template <> +class EqHelper { + public: + // We define two overloaded versions of Compare(). The first + // version will be picked when the second argument to ASSERT_EQ() is + // NOT a pointer, e.g. ASSERT_EQ(0, AnIntFunction()) or + // EXPECT_EQ(false, a_bool). + template + static AssertionResult Compare( + const char* expected_expression, + const char* actual_expression, + const T1& expected, + const T2& actual, + // The following line prevents this overload from being considered if T2 + // is not a pointer type. We need this because ASSERT_EQ(NULL, my_ptr) + // expands to Compare("", "", NULL, my_ptr), which requires a conversion + // to match the Secret* in the other overload, which would otherwise make + // this template match better. + typename EnableIf::value>::type* = 0) { + return CmpHelperEQ(expected_expression, actual_expression, expected, + actual); + } + + // This version will be picked when the second argument to ASSERT_EQ() is a + // pointer, e.g. ASSERT_EQ(NULL, a_pointer). + template + static AssertionResult Compare( + const char* expected_expression, + const char* actual_expression, + // We used to have a second template parameter instead of Secret*. That + // template parameter would deduce to 'long', making this a better match + // than the first overload even without the first overload's EnableIf. + // Unfortunately, gcc with -Wconversion-null warns when "passing NULL to + // non-pointer argument" (even a deduced integral argument), so the old + // implementation caused warnings in user code. + Secret* /* expected (NULL) */, + T* actual) { + // We already know that 'expected' is a null pointer. + return CmpHelperEQ(expected_expression, actual_expression, + static_cast(NULL), actual); + } +}; + +// A macro for implementing the helper functions needed to implement +// ASSERT_?? and EXPECT_??. It is here just to avoid copy-and-paste +// of similar code. +// +// For each templatized helper function, we also define an overloaded +// version for BiggestInt in order to reduce code bloat and allow +// anonymous enums to be used with {ASSERT|EXPECT}_?? when compiled +// with gcc 4. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +#define GTEST_IMPL_CMP_HELPER_(op_name, op)\ +template \ +AssertionResult CmpHelper##op_name(const char* expr1, const char* expr2, \ + const T1& val1, const T2& val2) {\ + if (val1 op val2) {\ + return AssertionSuccess();\ + } else {\ + return AssertionFailure() \ + << "Expected: (" << expr1 << ") " #op " (" << expr2\ + << "), actual: " << FormatForComparisonFailureMessage(val1, val2)\ + << " vs " << FormatForComparisonFailureMessage(val2, val1);\ + }\ +}\ +GTEST_API_ AssertionResult CmpHelper##op_name(\ + const char* expr1, const char* expr2, BiggestInt val1, BiggestInt val2) + +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. + +// Implements the helper function for {ASSERT|EXPECT}_NE +GTEST_IMPL_CMP_HELPER_(NE, !=); +// Implements the helper function for {ASSERT|EXPECT}_LE +GTEST_IMPL_CMP_HELPER_(LE, <=); +// Implements the helper function for {ASSERT|EXPECT}_LT +GTEST_IMPL_CMP_HELPER_(LT, <); +// Implements the helper function for {ASSERT|EXPECT}_GE +GTEST_IMPL_CMP_HELPER_(GE, >=); +// Implements the helper function for {ASSERT|EXPECT}_GT +GTEST_IMPL_CMP_HELPER_(GT, >); + +#undef GTEST_IMPL_CMP_HELPER_ + +// The helper function for {ASSERT|EXPECT}_STREQ. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +GTEST_API_ AssertionResult CmpHelperSTREQ(const char* expected_expression, + const char* actual_expression, + const char* expected, + const char* actual); + +// The helper function for {ASSERT|EXPECT}_STRCASEEQ. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +GTEST_API_ AssertionResult CmpHelperSTRCASEEQ(const char* expected_expression, + const char* actual_expression, + const char* expected, + const char* actual); + +// The helper function for {ASSERT|EXPECT}_STRNE. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +GTEST_API_ AssertionResult CmpHelperSTRNE(const char* s1_expression, + const char* s2_expression, + const char* s1, + const char* s2); + +// The helper function for {ASSERT|EXPECT}_STRCASENE. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +GTEST_API_ AssertionResult CmpHelperSTRCASENE(const char* s1_expression, + const char* s2_expression, + const char* s1, + const char* s2); + + +// Helper function for *_STREQ on wide strings. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +GTEST_API_ AssertionResult CmpHelperSTREQ(const char* expected_expression, + const char* actual_expression, + const wchar_t* expected, + const wchar_t* actual); + +// Helper function for *_STRNE on wide strings. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +GTEST_API_ AssertionResult CmpHelperSTRNE(const char* s1_expression, + const char* s2_expression, + const wchar_t* s1, + const wchar_t* s2); + +} // namespace internal + +// IsSubstring() and IsNotSubstring() are intended to be used as the +// first argument to {EXPECT,ASSERT}_PRED_FORMAT2(), not by +// themselves. They check whether needle is a substring of haystack +// (NULL is considered a substring of itself only), and return an +// appropriate error message when they fail. +// +// The {needle,haystack}_expr arguments are the stringified +// expressions that generated the two real arguments. +GTEST_API_ AssertionResult IsSubstring( + const char* needle_expr, const char* haystack_expr, + const char* needle, const char* haystack); +GTEST_API_ AssertionResult IsSubstring( + const char* needle_expr, const char* haystack_expr, + const wchar_t* needle, const wchar_t* haystack); +GTEST_API_ AssertionResult IsNotSubstring( + const char* needle_expr, const char* haystack_expr, + const char* needle, const char* haystack); +GTEST_API_ AssertionResult IsNotSubstring( + const char* needle_expr, const char* haystack_expr, + const wchar_t* needle, const wchar_t* haystack); +GTEST_API_ AssertionResult IsSubstring( + const char* needle_expr, const char* haystack_expr, + const ::std::string& needle, const ::std::string& haystack); +GTEST_API_ AssertionResult IsNotSubstring( + const char* needle_expr, const char* haystack_expr, + const ::std::string& needle, const ::std::string& haystack); + +#if GTEST_HAS_STD_WSTRING +GTEST_API_ AssertionResult IsSubstring( + const char* needle_expr, const char* haystack_expr, + const ::std::wstring& needle, const ::std::wstring& haystack); +GTEST_API_ AssertionResult IsNotSubstring( + const char* needle_expr, const char* haystack_expr, + const ::std::wstring& needle, const ::std::wstring& haystack); +#endif // GTEST_HAS_STD_WSTRING + +namespace internal { + +// Helper template function for comparing floating-points. +// +// Template parameter: +// +// RawType: the raw floating-point type (either float or double) +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +template +AssertionResult CmpHelperFloatingPointEQ(const char* expected_expression, + const char* actual_expression, + RawType expected, + RawType actual) { + const FloatingPoint lhs(expected), rhs(actual); + + if (lhs.AlmostEquals(rhs)) { + return AssertionSuccess(); + } + + ::std::stringstream expected_ss; + expected_ss << std::setprecision(std::numeric_limits::digits10 + 2) + << expected; + + ::std::stringstream actual_ss; + actual_ss << std::setprecision(std::numeric_limits::digits10 + 2) + << actual; + + return EqFailure(expected_expression, + actual_expression, + StringStreamToString(&expected_ss), + StringStreamToString(&actual_ss), + false); +} + +// Helper function for implementing ASSERT_NEAR. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +GTEST_API_ AssertionResult DoubleNearPredFormat(const char* expr1, + const char* expr2, + const char* abs_error_expr, + double val1, + double val2, + double abs_error); + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// A class that enables one to stream messages to assertion macros +class GTEST_API_ AssertHelper { + public: + // Constructor. + AssertHelper(TestPartResult::Type type, + const char* file, + int line, + const char* message); + ~AssertHelper(); + + // Message assignment is a semantic trick to enable assertion + // streaming; see the GTEST_MESSAGE_ macro below. + void operator=(const Message& message) const; + + private: + // We put our data in a struct so that the size of the AssertHelper class can + // be as small as possible. This is important because gcc is incapable of + // re-using stack space even for temporary variables, so every EXPECT_EQ + // reserves stack space for another AssertHelper. + struct AssertHelperData { + AssertHelperData(TestPartResult::Type t, + const char* srcfile, + int line_num, + const char* msg) + : type(t), file(srcfile), line(line_num), message(msg) { } + + TestPartResult::Type const type; + const char* const file; + int const line; + std::string const message; + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(AssertHelperData); + }; + + AssertHelperData* const data_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(AssertHelper); +}; + +} // namespace internal + +#if GTEST_HAS_PARAM_TEST +// The pure interface class that all value-parameterized tests inherit from. +// A value-parameterized class must inherit from both ::testing::Test and +// ::testing::WithParamInterface. In most cases that just means inheriting +// from ::testing::TestWithParam, but more complicated test hierarchies +// may need to inherit from Test and WithParamInterface at different levels. +// +// This interface has support for accessing the test parameter value via +// the GetParam() method. +// +// Use it with one of the parameter generator defining functions, like Range(), +// Values(), ValuesIn(), Bool(), and Combine(). +// +// class FooTest : public ::testing::TestWithParam { +// protected: +// FooTest() { +// // Can use GetParam() here. +// } +// virtual ~FooTest() { +// // Can use GetParam() here. +// } +// virtual void SetUp() { +// // Can use GetParam() here. +// } +// virtual void TearDown { +// // Can use GetParam() here. +// } +// }; +// TEST_P(FooTest, DoesBar) { +// // Can use GetParam() method here. +// Foo foo; +// ASSERT_TRUE(foo.DoesBar(GetParam())); +// } +// INSTANTIATE_TEST_CASE_P(OneToTenRange, FooTest, ::testing::Range(1, 10)); + +template +class WithParamInterface { + public: + typedef T ParamType; + virtual ~WithParamInterface() {} + + // The current parameter value. Is also available in the test fixture's + // constructor. This member function is non-static, even though it only + // references static data, to reduce the opportunity for incorrect uses + // like writing 'WithParamInterface::GetParam()' for a test that + // uses a fixture whose parameter type is int. + const ParamType& GetParam() const { + GTEST_CHECK_(parameter_ != NULL) + << "GetParam() can only be called inside a value-parameterized test " + << "-- did you intend to write TEST_P instead of TEST_F?"; + return *parameter_; + } + + private: + // Sets parameter value. The caller is responsible for making sure the value + // remains alive and unchanged throughout the current test. + static void SetParam(const ParamType* parameter) { + parameter_ = parameter; + } + + // Static value used for accessing parameter during a test lifetime. + static const ParamType* parameter_; + + // TestClass must be a subclass of WithParamInterface and Test. + template friend class internal::ParameterizedTestFactory; +}; + +template +const T* WithParamInterface::parameter_ = NULL; + +// Most value-parameterized classes can ignore the existence of +// WithParamInterface, and can just inherit from ::testing::TestWithParam. + +template +class TestWithParam : public Test, public WithParamInterface { +}; + +#endif // GTEST_HAS_PARAM_TEST + +// Macros for indicating success/failure in test code. + +// ADD_FAILURE unconditionally adds a failure to the current test. +// SUCCEED generates a success - it doesn't automatically make the +// current test successful, as a test is only successful when it has +// no failure. +// +// EXPECT_* verifies that a certain condition is satisfied. If not, +// it behaves like ADD_FAILURE. In particular: +// +// EXPECT_TRUE verifies that a Boolean condition is true. +// EXPECT_FALSE verifies that a Boolean condition is false. +// +// FAIL and ASSERT_* are similar to ADD_FAILURE and EXPECT_*, except +// that they will also abort the current function on failure. People +// usually want the fail-fast behavior of FAIL and ASSERT_*, but those +// writing data-driven tests often find themselves using ADD_FAILURE +// and EXPECT_* more. + +// Generates a nonfatal failure with a generic message. +#define ADD_FAILURE() GTEST_NONFATAL_FAILURE_("Failed") + +// Generates a nonfatal failure at the given source file location with +// a generic message. +#define ADD_FAILURE_AT(file, line) \ + GTEST_MESSAGE_AT_(file, line, "Failed", \ + ::testing::TestPartResult::kNonFatalFailure) + +// Generates a fatal failure with a generic message. +#define GTEST_FAIL() GTEST_FATAL_FAILURE_("Failed") + +// Define this macro to 1 to omit the definition of FAIL(), which is a +// generic name and clashes with some other libraries. +#if !GTEST_DONT_DEFINE_FAIL +# define FAIL() GTEST_FAIL() +#endif + +// Generates a success with a generic message. +#define GTEST_SUCCEED() GTEST_SUCCESS_("Succeeded") + +// Define this macro to 1 to omit the definition of SUCCEED(), which +// is a generic name and clashes with some other libraries. +#if !GTEST_DONT_DEFINE_SUCCEED +# define SUCCEED() GTEST_SUCCEED() +#endif + +// Macros for testing exceptions. +// +// * {ASSERT|EXPECT}_THROW(statement, expected_exception): +// Tests that the statement throws the expected exception. +// * {ASSERT|EXPECT}_NO_THROW(statement): +// Tests that the statement doesn't throw any exception. +// * {ASSERT|EXPECT}_ANY_THROW(statement): +// Tests that the statement throws an exception. + +#define EXPECT_THROW(statement, expected_exception) \ + GTEST_TEST_THROW_(statement, expected_exception, GTEST_NONFATAL_FAILURE_) +#define EXPECT_NO_THROW(statement) \ + GTEST_TEST_NO_THROW_(statement, GTEST_NONFATAL_FAILURE_) +#define EXPECT_ANY_THROW(statement) \ + GTEST_TEST_ANY_THROW_(statement, GTEST_NONFATAL_FAILURE_) +#define ASSERT_THROW(statement, expected_exception) \ + GTEST_TEST_THROW_(statement, expected_exception, GTEST_FATAL_FAILURE_) +#define ASSERT_NO_THROW(statement) \ + GTEST_TEST_NO_THROW_(statement, GTEST_FATAL_FAILURE_) +#define ASSERT_ANY_THROW(statement) \ + GTEST_TEST_ANY_THROW_(statement, GTEST_FATAL_FAILURE_) + +// Boolean assertions. Condition can be either a Boolean expression or an +// AssertionResult. For more information on how to use AssertionResult with +// these macros see comments on that class. +#define EXPECT_TRUE(condition) \ + GTEST_TEST_BOOLEAN_(condition, #condition, false, true, \ + GTEST_NONFATAL_FAILURE_) +#define EXPECT_FALSE(condition) \ + GTEST_TEST_BOOLEAN_(!(condition), #condition, true, false, \ + GTEST_NONFATAL_FAILURE_) +#define ASSERT_TRUE(condition) \ + GTEST_TEST_BOOLEAN_(condition, #condition, false, true, \ + GTEST_FATAL_FAILURE_) +#define ASSERT_FALSE(condition) \ + GTEST_TEST_BOOLEAN_(!(condition), #condition, true, false, \ + GTEST_FATAL_FAILURE_) + +// Includes the auto-generated header that implements a family of +// generic predicate assertion macros. +#include "gtest/gtest_pred_impl.h" + +// Macros for testing equalities and inequalities. +// +// * {ASSERT|EXPECT}_EQ(expected, actual): Tests that expected == actual +// * {ASSERT|EXPECT}_NE(v1, v2): Tests that v1 != v2 +// * {ASSERT|EXPECT}_LT(v1, v2): Tests that v1 < v2 +// * {ASSERT|EXPECT}_LE(v1, v2): Tests that v1 <= v2 +// * {ASSERT|EXPECT}_GT(v1, v2): Tests that v1 > v2 +// * {ASSERT|EXPECT}_GE(v1, v2): Tests that v1 >= v2 +// +// When they are not, Google Test prints both the tested expressions and +// their actual values. The values must be compatible built-in types, +// or you will get a compiler error. By "compatible" we mean that the +// values can be compared by the respective operator. +// +// Note: +// +// 1. It is possible to make a user-defined type work with +// {ASSERT|EXPECT}_??(), but that requires overloading the +// comparison operators and is thus discouraged by the Google C++ +// Usage Guide. Therefore, you are advised to use the +// {ASSERT|EXPECT}_TRUE() macro to assert that two objects are +// equal. +// +// 2. The {ASSERT|EXPECT}_??() macros do pointer comparisons on +// pointers (in particular, C strings). Therefore, if you use it +// with two C strings, you are testing how their locations in memory +// are related, not how their content is related. To compare two C +// strings by content, use {ASSERT|EXPECT}_STR*(). +// +// 3. {ASSERT|EXPECT}_EQ(expected, actual) is preferred to +// {ASSERT|EXPECT}_TRUE(expected == actual), as the former tells you +// what the actual value is when it fails, and similarly for the +// other comparisons. +// +// 4. Do not depend on the order in which {ASSERT|EXPECT}_??() +// evaluate their arguments, which is undefined. +// +// 5. These macros evaluate their arguments exactly once. +// +// Examples: +// +// EXPECT_NE(5, Foo()); +// EXPECT_EQ(NULL, a_pointer); +// ASSERT_LT(i, array_size); +// ASSERT_GT(records.size(), 0) << "There is no record left."; + +#define EXPECT_EQ(expected, actual) \ + EXPECT_PRED_FORMAT2(::testing::internal:: \ + EqHelper::Compare, \ + expected, actual) +#define EXPECT_NE(expected, actual) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperNE, expected, actual) +#define EXPECT_LE(val1, val2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperLE, val1, val2) +#define EXPECT_LT(val1, val2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperLT, val1, val2) +#define EXPECT_GE(val1, val2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperGE, val1, val2) +#define EXPECT_GT(val1, val2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperGT, val1, val2) + +#define GTEST_ASSERT_EQ(expected, actual) \ + ASSERT_PRED_FORMAT2(::testing::internal:: \ + EqHelper::Compare, \ + expected, actual) +#define GTEST_ASSERT_NE(val1, val2) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperNE, val1, val2) +#define GTEST_ASSERT_LE(val1, val2) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperLE, val1, val2) +#define GTEST_ASSERT_LT(val1, val2) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperLT, val1, val2) +#define GTEST_ASSERT_GE(val1, val2) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperGE, val1, val2) +#define GTEST_ASSERT_GT(val1, val2) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperGT, val1, val2) + +// Define macro GTEST_DONT_DEFINE_ASSERT_XY to 1 to omit the definition of +// ASSERT_XY(), which clashes with some users' own code. + +#if !GTEST_DONT_DEFINE_ASSERT_EQ +# define ASSERT_EQ(val1, val2) GTEST_ASSERT_EQ(val1, val2) +#endif + +#if !GTEST_DONT_DEFINE_ASSERT_NE +# define ASSERT_NE(val1, val2) GTEST_ASSERT_NE(val1, val2) +#endif + +#if !GTEST_DONT_DEFINE_ASSERT_LE +# define ASSERT_LE(val1, val2) GTEST_ASSERT_LE(val1, val2) +#endif + +#if !GTEST_DONT_DEFINE_ASSERT_LT +# define ASSERT_LT(val1, val2) GTEST_ASSERT_LT(val1, val2) +#endif + +#if !GTEST_DONT_DEFINE_ASSERT_GE +# define ASSERT_GE(val1, val2) GTEST_ASSERT_GE(val1, val2) +#endif + +#if !GTEST_DONT_DEFINE_ASSERT_GT +# define ASSERT_GT(val1, val2) GTEST_ASSERT_GT(val1, val2) +#endif + +// C-string Comparisons. All tests treat NULL and any non-NULL string +// as different. Two NULLs are equal. +// +// * {ASSERT|EXPECT}_STREQ(s1, s2): Tests that s1 == s2 +// * {ASSERT|EXPECT}_STRNE(s1, s2): Tests that s1 != s2 +// * {ASSERT|EXPECT}_STRCASEEQ(s1, s2): Tests that s1 == s2, ignoring case +// * {ASSERT|EXPECT}_STRCASENE(s1, s2): Tests that s1 != s2, ignoring case +// +// For wide or narrow string objects, you can use the +// {ASSERT|EXPECT}_??() macros. +// +// Don't depend on the order in which the arguments are evaluated, +// which is undefined. +// +// These macros evaluate their arguments exactly once. + +#define EXPECT_STREQ(expected, actual) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperSTREQ, expected, actual) +#define EXPECT_STRNE(s1, s2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperSTRNE, s1, s2) +#define EXPECT_STRCASEEQ(expected, actual) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperSTRCASEEQ, expected, actual) +#define EXPECT_STRCASENE(s1, s2)\ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperSTRCASENE, s1, s2) + +#define ASSERT_STREQ(expected, actual) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperSTREQ, expected, actual) +#define ASSERT_STRNE(s1, s2) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperSTRNE, s1, s2) +#define ASSERT_STRCASEEQ(expected, actual) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperSTRCASEEQ, expected, actual) +#define ASSERT_STRCASENE(s1, s2)\ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperSTRCASENE, s1, s2) + +// Macros for comparing floating-point numbers. +// +// * {ASSERT|EXPECT}_FLOAT_EQ(expected, actual): +// Tests that two float values are almost equal. +// * {ASSERT|EXPECT}_DOUBLE_EQ(expected, actual): +// Tests that two double values are almost equal. +// * {ASSERT|EXPECT}_NEAR(v1, v2, abs_error): +// Tests that v1 and v2 are within the given distance to each other. +// +// Google Test uses ULP-based comparison to automatically pick a default +// error bound that is appropriate for the operands. See the +// FloatingPoint template class in gtest-internal.h if you are +// interested in the implementation details. + +#define EXPECT_FLOAT_EQ(expected, actual)\ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperFloatingPointEQ, \ + expected, actual) + +#define EXPECT_DOUBLE_EQ(expected, actual)\ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperFloatingPointEQ, \ + expected, actual) + +#define ASSERT_FLOAT_EQ(expected, actual)\ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperFloatingPointEQ, \ + expected, actual) + +#define ASSERT_DOUBLE_EQ(expected, actual)\ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperFloatingPointEQ, \ + expected, actual) + +#define EXPECT_NEAR(val1, val2, abs_error)\ + EXPECT_PRED_FORMAT3(::testing::internal::DoubleNearPredFormat, \ + val1, val2, abs_error) + +#define ASSERT_NEAR(val1, val2, abs_error)\ + ASSERT_PRED_FORMAT3(::testing::internal::DoubleNearPredFormat, \ + val1, val2, abs_error) + +// These predicate format functions work on floating-point values, and +// can be used in {ASSERT|EXPECT}_PRED_FORMAT2*(), e.g. +// +// EXPECT_PRED_FORMAT2(testing::DoubleLE, Foo(), 5.0); + +// Asserts that val1 is less than, or almost equal to, val2. Fails +// otherwise. In particular, it fails if either val1 or val2 is NaN. +GTEST_API_ AssertionResult FloatLE(const char* expr1, const char* expr2, + float val1, float val2); +GTEST_API_ AssertionResult DoubleLE(const char* expr1, const char* expr2, + double val1, double val2); + + +#if GTEST_OS_WINDOWS + +// Macros that test for HRESULT failure and success, these are only useful +// on Windows, and rely on Windows SDK macros and APIs to compile. +// +// * {ASSERT|EXPECT}_HRESULT_{SUCCEEDED|FAILED}(expr) +// +// When expr unexpectedly fails or succeeds, Google Test prints the +// expected result and the actual result with both a human-readable +// string representation of the error, if available, as well as the +// hex result code. +# define EXPECT_HRESULT_SUCCEEDED(expr) \ + EXPECT_PRED_FORMAT1(::testing::internal::IsHRESULTSuccess, (expr)) + +# define ASSERT_HRESULT_SUCCEEDED(expr) \ + ASSERT_PRED_FORMAT1(::testing::internal::IsHRESULTSuccess, (expr)) + +# define EXPECT_HRESULT_FAILED(expr) \ + EXPECT_PRED_FORMAT1(::testing::internal::IsHRESULTFailure, (expr)) + +# define ASSERT_HRESULT_FAILED(expr) \ + ASSERT_PRED_FORMAT1(::testing::internal::IsHRESULTFailure, (expr)) + +#endif // GTEST_OS_WINDOWS + +// Macros that execute statement and check that it doesn't generate new fatal +// failures in the current thread. +// +// * {ASSERT|EXPECT}_NO_FATAL_FAILURE(statement); +// +// Examples: +// +// EXPECT_NO_FATAL_FAILURE(Process()); +// ASSERT_NO_FATAL_FAILURE(Process()) << "Process() failed"; +// +#define ASSERT_NO_FATAL_FAILURE(statement) \ + GTEST_TEST_NO_FATAL_FAILURE_(statement, GTEST_FATAL_FAILURE_) +#define EXPECT_NO_FATAL_FAILURE(statement) \ + GTEST_TEST_NO_FATAL_FAILURE_(statement, GTEST_NONFATAL_FAILURE_) + +// Causes a trace (including the source file path, the current line +// number, and the given message) to be included in every test failure +// message generated by code in the current scope. The effect is +// undone when the control leaves the current scope. +// +// The message argument can be anything streamable to std::ostream. +// +// In the implementation, we include the current line number as part +// of the dummy variable name, thus allowing multiple SCOPED_TRACE()s +// to appear in the same block - as long as they are on different +// lines. +#define SCOPED_TRACE(message) \ + ::testing::internal::ScopedTrace GTEST_CONCAT_TOKEN_(gtest_trace_, __LINE__)(\ + __FILE__, __LINE__, ::testing::Message() << (message)) + +// Compile-time assertion for type equality. +// StaticAssertTypeEq() compiles iff type1 and type2 are +// the same type. The value it returns is not interesting. +// +// Instead of making StaticAssertTypeEq a class template, we make it a +// function template that invokes a helper class template. This +// prevents a user from misusing StaticAssertTypeEq by +// defining objects of that type. +// +// CAVEAT: +// +// When used inside a method of a class template, +// StaticAssertTypeEq() is effective ONLY IF the method is +// instantiated. For example, given: +// +// template class Foo { +// public: +// void Bar() { testing::StaticAssertTypeEq(); } +// }; +// +// the code: +// +// void Test1() { Foo foo; } +// +// will NOT generate a compiler error, as Foo::Bar() is never +// actually instantiated. Instead, you need: +// +// void Test2() { Foo foo; foo.Bar(); } +// +// to cause a compiler error. +template +bool StaticAssertTypeEq() { + (void)internal::StaticAssertTypeEqHelper(); + return true; +} + +// Defines a test. +// +// The first parameter is the name of the test case, and the second +// parameter is the name of the test within the test case. +// +// The convention is to end the test case name with "Test". For +// example, a test case for the Foo class can be named FooTest. +// +// The user should put his test code between braces after using this +// macro. Example: +// +// TEST(FooTest, InitializesCorrectly) { +// Foo foo; +// EXPECT_TRUE(foo.StatusIsOK()); +// } + +// Note that we call GetTestTypeId() instead of GetTypeId< +// ::testing::Test>() here to get the type ID of testing::Test. This +// is to work around a suspected linker bug when using Google Test as +// a framework on Mac OS X. The bug causes GetTypeId< +// ::testing::Test>() to return different values depending on whether +// the call is from the Google Test framework itself or from user test +// code. GetTestTypeId() is guaranteed to always return the same +// value, as it always calls GetTypeId<>() from the Google Test +// framework. +#define GTEST_TEST(test_case_name, test_name)\ + GTEST_TEST_(test_case_name, test_name, \ + ::testing::Test, ::testing::internal::GetTestTypeId()) + +// Define this macro to 1 to omit the definition of TEST(), which +// is a generic name and clashes with some other libraries. +#if !GTEST_DONT_DEFINE_TEST +# define TEST(test_case_name, test_name) GTEST_TEST(test_case_name, test_name) +#endif + +// Defines a test that uses a test fixture. +// +// The first parameter is the name of the test fixture class, which +// also doubles as the test case name. The second parameter is the +// name of the test within the test case. +// +// A test fixture class must be declared earlier. The user should put +// his test code between braces after using this macro. Example: +// +// class FooTest : public testing::Test { +// protected: +// virtual void SetUp() { b_.AddElement(3); } +// +// Foo a_; +// Foo b_; +// }; +// +// TEST_F(FooTest, InitializesCorrectly) { +// EXPECT_TRUE(a_.StatusIsOK()); +// } +// +// TEST_F(FooTest, ReturnsElementCountCorrectly) { +// EXPECT_EQ(0, a_.size()); +// EXPECT_EQ(1, b_.size()); +// } + +#define TEST_F(test_fixture, test_name)\ + GTEST_TEST_(test_fixture, test_name, test_fixture, \ + ::testing::internal::GetTypeId()) + +} // namespace testing + +// Use this function in main() to run all tests. It returns 0 if all +// tests are successful, or 1 otherwise. +// +// RUN_ALL_TESTS() should be invoked after the command line has been +// parsed by InitGoogleTest(). +// +// This function was formerly a macro; thus, it is in the global +// namespace and has an all-caps name. +int RUN_ALL_TESTS() GTEST_MUST_USE_RESULT_; + +inline int RUN_ALL_TESTS() { + return ::testing::UnitTest::GetInstance()->Run(); +} + +#endif // GTEST_INCLUDE_GTEST_GTEST_H_ diff --git a/vendor/gmock-1.7.0/gtest/include/gtest/gtest_pred_impl.h b/vendor/gmock-1.7.0/gtest/include/gtest/gtest_pred_impl.h new file mode 100644 index 0000000000..30ae712f50 --- /dev/null +++ b/vendor/gmock-1.7.0/gtest/include/gtest/gtest_pred_impl.h @@ -0,0 +1,358 @@ +// Copyright 2006, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// This file is AUTOMATICALLY GENERATED on 10/31/2011 by command +// 'gen_gtest_pred_impl.py 5'. DO NOT EDIT BY HAND! +// +// Implements a family of generic predicate assertion macros. + +#ifndef GTEST_INCLUDE_GTEST_GTEST_PRED_IMPL_H_ +#define GTEST_INCLUDE_GTEST_GTEST_PRED_IMPL_H_ + +// Makes sure this header is not included before gtest.h. +#ifndef GTEST_INCLUDE_GTEST_GTEST_H_ +# error Do not include gtest_pred_impl.h directly. Include gtest.h instead. +#endif // GTEST_INCLUDE_GTEST_GTEST_H_ + +// This header implements a family of generic predicate assertion +// macros: +// +// ASSERT_PRED_FORMAT1(pred_format, v1) +// ASSERT_PRED_FORMAT2(pred_format, v1, v2) +// ... +// +// where pred_format is a function or functor that takes n (in the +// case of ASSERT_PRED_FORMATn) values and their source expression +// text, and returns a testing::AssertionResult. See the definition +// of ASSERT_EQ in gtest.h for an example. +// +// If you don't care about formatting, you can use the more +// restrictive version: +// +// ASSERT_PRED1(pred, v1) +// ASSERT_PRED2(pred, v1, v2) +// ... +// +// where pred is an n-ary function or functor that returns bool, +// and the values v1, v2, ..., must support the << operator for +// streaming to std::ostream. +// +// We also define the EXPECT_* variations. +// +// For now we only support predicates whose arity is at most 5. +// Please email googletestframework@googlegroups.com if you need +// support for higher arities. + +// GTEST_ASSERT_ is the basic statement to which all of the assertions +// in this file reduce. Don't use this in your code. + +#define GTEST_ASSERT_(expression, on_failure) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (const ::testing::AssertionResult gtest_ar = (expression)) \ + ; \ + else \ + on_failure(gtest_ar.failure_message()) + + +// Helper function for implementing {EXPECT|ASSERT}_PRED1. Don't use +// this in your code. +template +AssertionResult AssertPred1Helper(const char* pred_text, + const char* e1, + Pred pred, + const T1& v1) { + if (pred(v1)) return AssertionSuccess(); + + return AssertionFailure() << pred_text << "(" + << e1 << ") evaluates to false, where" + << "\n" << e1 << " evaluates to " << v1; +} + +// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT1. +// Don't use this in your code. +#define GTEST_PRED_FORMAT1_(pred_format, v1, on_failure)\ + GTEST_ASSERT_(pred_format(#v1, v1), \ + on_failure) + +// Internal macro for implementing {EXPECT|ASSERT}_PRED1. Don't use +// this in your code. +#define GTEST_PRED1_(pred, v1, on_failure)\ + GTEST_ASSERT_(::testing::AssertPred1Helper(#pred, \ + #v1, \ + pred, \ + v1), on_failure) + +// Unary predicate assertion macros. +#define EXPECT_PRED_FORMAT1(pred_format, v1) \ + GTEST_PRED_FORMAT1_(pred_format, v1, GTEST_NONFATAL_FAILURE_) +#define EXPECT_PRED1(pred, v1) \ + GTEST_PRED1_(pred, v1, GTEST_NONFATAL_FAILURE_) +#define ASSERT_PRED_FORMAT1(pred_format, v1) \ + GTEST_PRED_FORMAT1_(pred_format, v1, GTEST_FATAL_FAILURE_) +#define ASSERT_PRED1(pred, v1) \ + GTEST_PRED1_(pred, v1, GTEST_FATAL_FAILURE_) + + + +// Helper function for implementing {EXPECT|ASSERT}_PRED2. Don't use +// this in your code. +template +AssertionResult AssertPred2Helper(const char* pred_text, + const char* e1, + const char* e2, + Pred pred, + const T1& v1, + const T2& v2) { + if (pred(v1, v2)) return AssertionSuccess(); + + return AssertionFailure() << pred_text << "(" + << e1 << ", " + << e2 << ") evaluates to false, where" + << "\n" << e1 << " evaluates to " << v1 + << "\n" << e2 << " evaluates to " << v2; +} + +// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT2. +// Don't use this in your code. +#define GTEST_PRED_FORMAT2_(pred_format, v1, v2, on_failure)\ + GTEST_ASSERT_(pred_format(#v1, #v2, v1, v2), \ + on_failure) + +// Internal macro for implementing {EXPECT|ASSERT}_PRED2. Don't use +// this in your code. +#define GTEST_PRED2_(pred, v1, v2, on_failure)\ + GTEST_ASSERT_(::testing::AssertPred2Helper(#pred, \ + #v1, \ + #v2, \ + pred, \ + v1, \ + v2), on_failure) + +// Binary predicate assertion macros. +#define EXPECT_PRED_FORMAT2(pred_format, v1, v2) \ + GTEST_PRED_FORMAT2_(pred_format, v1, v2, GTEST_NONFATAL_FAILURE_) +#define EXPECT_PRED2(pred, v1, v2) \ + GTEST_PRED2_(pred, v1, v2, GTEST_NONFATAL_FAILURE_) +#define ASSERT_PRED_FORMAT2(pred_format, v1, v2) \ + GTEST_PRED_FORMAT2_(pred_format, v1, v2, GTEST_FATAL_FAILURE_) +#define ASSERT_PRED2(pred, v1, v2) \ + GTEST_PRED2_(pred, v1, v2, GTEST_FATAL_FAILURE_) + + + +// Helper function for implementing {EXPECT|ASSERT}_PRED3. Don't use +// this in your code. +template +AssertionResult AssertPred3Helper(const char* pred_text, + const char* e1, + const char* e2, + const char* e3, + Pred pred, + const T1& v1, + const T2& v2, + const T3& v3) { + if (pred(v1, v2, v3)) return AssertionSuccess(); + + return AssertionFailure() << pred_text << "(" + << e1 << ", " + << e2 << ", " + << e3 << ") evaluates to false, where" + << "\n" << e1 << " evaluates to " << v1 + << "\n" << e2 << " evaluates to " << v2 + << "\n" << e3 << " evaluates to " << v3; +} + +// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT3. +// Don't use this in your code. +#define GTEST_PRED_FORMAT3_(pred_format, v1, v2, v3, on_failure)\ + GTEST_ASSERT_(pred_format(#v1, #v2, #v3, v1, v2, v3), \ + on_failure) + +// Internal macro for implementing {EXPECT|ASSERT}_PRED3. Don't use +// this in your code. +#define GTEST_PRED3_(pred, v1, v2, v3, on_failure)\ + GTEST_ASSERT_(::testing::AssertPred3Helper(#pred, \ + #v1, \ + #v2, \ + #v3, \ + pred, \ + v1, \ + v2, \ + v3), on_failure) + +// Ternary predicate assertion macros. +#define EXPECT_PRED_FORMAT3(pred_format, v1, v2, v3) \ + GTEST_PRED_FORMAT3_(pred_format, v1, v2, v3, GTEST_NONFATAL_FAILURE_) +#define EXPECT_PRED3(pred, v1, v2, v3) \ + GTEST_PRED3_(pred, v1, v2, v3, GTEST_NONFATAL_FAILURE_) +#define ASSERT_PRED_FORMAT3(pred_format, v1, v2, v3) \ + GTEST_PRED_FORMAT3_(pred_format, v1, v2, v3, GTEST_FATAL_FAILURE_) +#define ASSERT_PRED3(pred, v1, v2, v3) \ + GTEST_PRED3_(pred, v1, v2, v3, GTEST_FATAL_FAILURE_) + + + +// Helper function for implementing {EXPECT|ASSERT}_PRED4. Don't use +// this in your code. +template +AssertionResult AssertPred4Helper(const char* pred_text, + const char* e1, + const char* e2, + const char* e3, + const char* e4, + Pred pred, + const T1& v1, + const T2& v2, + const T3& v3, + const T4& v4) { + if (pred(v1, v2, v3, v4)) return AssertionSuccess(); + + return AssertionFailure() << pred_text << "(" + << e1 << ", " + << e2 << ", " + << e3 << ", " + << e4 << ") evaluates to false, where" + << "\n" << e1 << " evaluates to " << v1 + << "\n" << e2 << " evaluates to " << v2 + << "\n" << e3 << " evaluates to " << v3 + << "\n" << e4 << " evaluates to " << v4; +} + +// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT4. +// Don't use this in your code. +#define GTEST_PRED_FORMAT4_(pred_format, v1, v2, v3, v4, on_failure)\ + GTEST_ASSERT_(pred_format(#v1, #v2, #v3, #v4, v1, v2, v3, v4), \ + on_failure) + +// Internal macro for implementing {EXPECT|ASSERT}_PRED4. Don't use +// this in your code. +#define GTEST_PRED4_(pred, v1, v2, v3, v4, on_failure)\ + GTEST_ASSERT_(::testing::AssertPred4Helper(#pred, \ + #v1, \ + #v2, \ + #v3, \ + #v4, \ + pred, \ + v1, \ + v2, \ + v3, \ + v4), on_failure) + +// 4-ary predicate assertion macros. +#define EXPECT_PRED_FORMAT4(pred_format, v1, v2, v3, v4) \ + GTEST_PRED_FORMAT4_(pred_format, v1, v2, v3, v4, GTEST_NONFATAL_FAILURE_) +#define EXPECT_PRED4(pred, v1, v2, v3, v4) \ + GTEST_PRED4_(pred, v1, v2, v3, v4, GTEST_NONFATAL_FAILURE_) +#define ASSERT_PRED_FORMAT4(pred_format, v1, v2, v3, v4) \ + GTEST_PRED_FORMAT4_(pred_format, v1, v2, v3, v4, GTEST_FATAL_FAILURE_) +#define ASSERT_PRED4(pred, v1, v2, v3, v4) \ + GTEST_PRED4_(pred, v1, v2, v3, v4, GTEST_FATAL_FAILURE_) + + + +// Helper function for implementing {EXPECT|ASSERT}_PRED5. Don't use +// this in your code. +template +AssertionResult AssertPred5Helper(const char* pred_text, + const char* e1, + const char* e2, + const char* e3, + const char* e4, + const char* e5, + Pred pred, + const T1& v1, + const T2& v2, + const T3& v3, + const T4& v4, + const T5& v5) { + if (pred(v1, v2, v3, v4, v5)) return AssertionSuccess(); + + return AssertionFailure() << pred_text << "(" + << e1 << ", " + << e2 << ", " + << e3 << ", " + << e4 << ", " + << e5 << ") evaluates to false, where" + << "\n" << e1 << " evaluates to " << v1 + << "\n" << e2 << " evaluates to " << v2 + << "\n" << e3 << " evaluates to " << v3 + << "\n" << e4 << " evaluates to " << v4 + << "\n" << e5 << " evaluates to " << v5; +} + +// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT5. +// Don't use this in your code. +#define GTEST_PRED_FORMAT5_(pred_format, v1, v2, v3, v4, v5, on_failure)\ + GTEST_ASSERT_(pred_format(#v1, #v2, #v3, #v4, #v5, v1, v2, v3, v4, v5), \ + on_failure) + +// Internal macro for implementing {EXPECT|ASSERT}_PRED5. Don't use +// this in your code. +#define GTEST_PRED5_(pred, v1, v2, v3, v4, v5, on_failure)\ + GTEST_ASSERT_(::testing::AssertPred5Helper(#pred, \ + #v1, \ + #v2, \ + #v3, \ + #v4, \ + #v5, \ + pred, \ + v1, \ + v2, \ + v3, \ + v4, \ + v5), on_failure) + +// 5-ary predicate assertion macros. +#define EXPECT_PRED_FORMAT5(pred_format, v1, v2, v3, v4, v5) \ + GTEST_PRED_FORMAT5_(pred_format, v1, v2, v3, v4, v5, GTEST_NONFATAL_FAILURE_) +#define EXPECT_PRED5(pred, v1, v2, v3, v4, v5) \ + GTEST_PRED5_(pred, v1, v2, v3, v4, v5, GTEST_NONFATAL_FAILURE_) +#define ASSERT_PRED_FORMAT5(pred_format, v1, v2, v3, v4, v5) \ + GTEST_PRED_FORMAT5_(pred_format, v1, v2, v3, v4, v5, GTEST_FATAL_FAILURE_) +#define ASSERT_PRED5(pred, v1, v2, v3, v4, v5) \ + GTEST_PRED5_(pred, v1, v2, v3, v4, v5, GTEST_FATAL_FAILURE_) + + + +#endif // GTEST_INCLUDE_GTEST_GTEST_PRED_IMPL_H_ diff --git a/vendor/gmock-1.7.0/gtest/include/gtest/gtest_prod.h b/vendor/gmock-1.7.0/gtest/include/gtest/gtest_prod.h new file mode 100644 index 0000000000..da80ddc6c7 --- /dev/null +++ b/vendor/gmock-1.7.0/gtest/include/gtest/gtest_prod.h @@ -0,0 +1,58 @@ +// Copyright 2006, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) +// +// Google C++ Testing Framework definitions useful in production code. + +#ifndef GTEST_INCLUDE_GTEST_GTEST_PROD_H_ +#define GTEST_INCLUDE_GTEST_GTEST_PROD_H_ + +// When you need to test the private or protected members of a class, +// use the FRIEND_TEST macro to declare your tests as friends of the +// class. For example: +// +// class MyClass { +// private: +// void MyMethod(); +// FRIEND_TEST(MyClassTest, MyMethod); +// }; +// +// class MyClassTest : public testing::Test { +// // ... +// }; +// +// TEST_F(MyClassTest, MyMethod) { +// // Can call MyClass::MyMethod() here. +// } + +#define FRIEND_TEST(test_case_name, test_name)\ +friend class test_case_name##_##test_name##_Test + +#endif // GTEST_INCLUDE_GTEST_GTEST_PROD_H_ diff --git a/vendor/gmock-1.7.0/gtest/include/gtest/internal/gtest-death-test-internal.h b/vendor/gmock-1.7.0/gtest/include/gtest/internal/gtest-death-test-internal.h new file mode 100644 index 0000000000..2b3a78f5bf --- /dev/null +++ b/vendor/gmock-1.7.0/gtest/include/gtest/internal/gtest-death-test-internal.h @@ -0,0 +1,319 @@ +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Authors: wan@google.com (Zhanyong Wan), eefacm@gmail.com (Sean Mcafee) +// +// The Google C++ Testing Framework (Google Test) +// +// This header file defines internal utilities needed for implementing +// death tests. They are subject to change without notice. + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_DEATH_TEST_INTERNAL_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_DEATH_TEST_INTERNAL_H_ + +#include "gtest/internal/gtest-internal.h" + +#include + +namespace testing { +namespace internal { + +GTEST_DECLARE_string_(internal_run_death_test); + +// Names of the flags (needed for parsing Google Test flags). +const char kDeathTestStyleFlag[] = "death_test_style"; +const char kDeathTestUseFork[] = "death_test_use_fork"; +const char kInternalRunDeathTestFlag[] = "internal_run_death_test"; + +#if GTEST_HAS_DEATH_TEST + +// DeathTest is a class that hides much of the complexity of the +// GTEST_DEATH_TEST_ macro. It is abstract; its static Create method +// returns a concrete class that depends on the prevailing death test +// style, as defined by the --gtest_death_test_style and/or +// --gtest_internal_run_death_test flags. + +// In describing the results of death tests, these terms are used with +// the corresponding definitions: +// +// exit status: The integer exit information in the format specified +// by wait(2) +// exit code: The integer code passed to exit(3), _exit(2), or +// returned from main() +class GTEST_API_ DeathTest { + public: + // Create returns false if there was an error determining the + // appropriate action to take for the current death test; for example, + // if the gtest_death_test_style flag is set to an invalid value. + // The LastMessage method will return a more detailed message in that + // case. Otherwise, the DeathTest pointer pointed to by the "test" + // argument is set. If the death test should be skipped, the pointer + // is set to NULL; otherwise, it is set to the address of a new concrete + // DeathTest object that controls the execution of the current test. + static bool Create(const char* statement, const RE* regex, + const char* file, int line, DeathTest** test); + DeathTest(); + virtual ~DeathTest() { } + + // A helper class that aborts a death test when it's deleted. + class ReturnSentinel { + public: + explicit ReturnSentinel(DeathTest* test) : test_(test) { } + ~ReturnSentinel() { test_->Abort(TEST_ENCOUNTERED_RETURN_STATEMENT); } + private: + DeathTest* const test_; + GTEST_DISALLOW_COPY_AND_ASSIGN_(ReturnSentinel); + } GTEST_ATTRIBUTE_UNUSED_; + + // An enumeration of possible roles that may be taken when a death + // test is encountered. EXECUTE means that the death test logic should + // be executed immediately. OVERSEE means that the program should prepare + // the appropriate environment for a child process to execute the death + // test, then wait for it to complete. + enum TestRole { OVERSEE_TEST, EXECUTE_TEST }; + + // An enumeration of the three reasons that a test might be aborted. + enum AbortReason { + TEST_ENCOUNTERED_RETURN_STATEMENT, + TEST_THREW_EXCEPTION, + TEST_DID_NOT_DIE + }; + + // Assumes one of the above roles. + virtual TestRole AssumeRole() = 0; + + // Waits for the death test to finish and returns its status. + virtual int Wait() = 0; + + // Returns true if the death test passed; that is, the test process + // exited during the test, its exit status matches a user-supplied + // predicate, and its stderr output matches a user-supplied regular + // expression. + // The user-supplied predicate may be a macro expression rather + // than a function pointer or functor, or else Wait and Passed could + // be combined. + virtual bool Passed(bool exit_status_ok) = 0; + + // Signals that the death test did not die as expected. + virtual void Abort(AbortReason reason) = 0; + + // Returns a human-readable outcome message regarding the outcome of + // the last death test. + static const char* LastMessage(); + + static void set_last_death_test_message(const std::string& message); + + private: + // A string containing a description of the outcome of the last death test. + static std::string last_death_test_message_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(DeathTest); +}; + +// Factory interface for death tests. May be mocked out for testing. +class DeathTestFactory { + public: + virtual ~DeathTestFactory() { } + virtual bool Create(const char* statement, const RE* regex, + const char* file, int line, DeathTest** test) = 0; +}; + +// A concrete DeathTestFactory implementation for normal use. +class DefaultDeathTestFactory : public DeathTestFactory { + public: + virtual bool Create(const char* statement, const RE* regex, + const char* file, int line, DeathTest** test); +}; + +// Returns true if exit_status describes a process that was terminated +// by a signal, or exited normally with a nonzero exit code. +GTEST_API_ bool ExitedUnsuccessfully(int exit_status); + +// Traps C++ exceptions escaping statement and reports them as test +// failures. Note that trapping SEH exceptions is not implemented here. +# if GTEST_HAS_EXCEPTIONS +# define GTEST_EXECUTE_DEATH_TEST_STATEMENT_(statement, death_test) \ + try { \ + GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ + } catch (const ::std::exception& gtest_exception) { \ + fprintf(\ + stderr, \ + "\n%s: Caught std::exception-derived exception escaping the " \ + "death test statement. Exception message: %s\n", \ + ::testing::internal::FormatFileLocation(__FILE__, __LINE__).c_str(), \ + gtest_exception.what()); \ + fflush(stderr); \ + death_test->Abort(::testing::internal::DeathTest::TEST_THREW_EXCEPTION); \ + } catch (...) { \ + death_test->Abort(::testing::internal::DeathTest::TEST_THREW_EXCEPTION); \ + } + +# else +# define GTEST_EXECUTE_DEATH_TEST_STATEMENT_(statement, death_test) \ + GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement) + +# endif + +// This macro is for implementing ASSERT_DEATH*, EXPECT_DEATH*, +// ASSERT_EXIT*, and EXPECT_EXIT*. +# define GTEST_DEATH_TEST_(statement, predicate, regex, fail) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (::testing::internal::AlwaysTrue()) { \ + const ::testing::internal::RE& gtest_regex = (regex); \ + ::testing::internal::DeathTest* gtest_dt; \ + if (!::testing::internal::DeathTest::Create(#statement, >est_regex, \ + __FILE__, __LINE__, >est_dt)) { \ + goto GTEST_CONCAT_TOKEN_(gtest_label_, __LINE__); \ + } \ + if (gtest_dt != NULL) { \ + ::testing::internal::scoped_ptr< ::testing::internal::DeathTest> \ + gtest_dt_ptr(gtest_dt); \ + switch (gtest_dt->AssumeRole()) { \ + case ::testing::internal::DeathTest::OVERSEE_TEST: \ + if (!gtest_dt->Passed(predicate(gtest_dt->Wait()))) { \ + goto GTEST_CONCAT_TOKEN_(gtest_label_, __LINE__); \ + } \ + break; \ + case ::testing::internal::DeathTest::EXECUTE_TEST: { \ + ::testing::internal::DeathTest::ReturnSentinel \ + gtest_sentinel(gtest_dt); \ + GTEST_EXECUTE_DEATH_TEST_STATEMENT_(statement, gtest_dt); \ + gtest_dt->Abort(::testing::internal::DeathTest::TEST_DID_NOT_DIE); \ + break; \ + } \ + default: \ + break; \ + } \ + } \ + } else \ + GTEST_CONCAT_TOKEN_(gtest_label_, __LINE__): \ + fail(::testing::internal::DeathTest::LastMessage()) +// The symbol "fail" here expands to something into which a message +// can be streamed. + +// This macro is for implementing ASSERT/EXPECT_DEBUG_DEATH when compiled in +// NDEBUG mode. In this case we need the statements to be executed, the regex is +// ignored, and the macro must accept a streamed message even though the message +// is never printed. +# define GTEST_EXECUTE_STATEMENT_(statement, regex) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (::testing::internal::AlwaysTrue()) { \ + GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ + } else \ + ::testing::Message() + +// A class representing the parsed contents of the +// --gtest_internal_run_death_test flag, as it existed when +// RUN_ALL_TESTS was called. +class InternalRunDeathTestFlag { + public: + InternalRunDeathTestFlag(const std::string& a_file, + int a_line, + int an_index, + int a_write_fd) + : file_(a_file), line_(a_line), index_(an_index), + write_fd_(a_write_fd) {} + + ~InternalRunDeathTestFlag() { + if (write_fd_ >= 0) + posix::Close(write_fd_); + } + + const std::string& file() const { return file_; } + int line() const { return line_; } + int index() const { return index_; } + int write_fd() const { return write_fd_; } + + private: + std::string file_; + int line_; + int index_; + int write_fd_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(InternalRunDeathTestFlag); +}; + +// Returns a newly created InternalRunDeathTestFlag object with fields +// initialized from the GTEST_FLAG(internal_run_death_test) flag if +// the flag is specified; otherwise returns NULL. +InternalRunDeathTestFlag* ParseInternalRunDeathTestFlag(); + +#else // GTEST_HAS_DEATH_TEST + +// This macro is used for implementing macros such as +// EXPECT_DEATH_IF_SUPPORTED and ASSERT_DEATH_IF_SUPPORTED on systems where +// death tests are not supported. Those macros must compile on such systems +// iff EXPECT_DEATH and ASSERT_DEATH compile with the same parameters on +// systems that support death tests. This allows one to write such a macro +// on a system that does not support death tests and be sure that it will +// compile on a death-test supporting system. +// +// Parameters: +// statement - A statement that a macro such as EXPECT_DEATH would test +// for program termination. This macro has to make sure this +// statement is compiled but not executed, to ensure that +// EXPECT_DEATH_IF_SUPPORTED compiles with a certain +// parameter iff EXPECT_DEATH compiles with it. +// regex - A regex that a macro such as EXPECT_DEATH would use to test +// the output of statement. This parameter has to be +// compiled but not evaluated by this macro, to ensure that +// this macro only accepts expressions that a macro such as +// EXPECT_DEATH would accept. +// terminator - Must be an empty statement for EXPECT_DEATH_IF_SUPPORTED +// and a return statement for ASSERT_DEATH_IF_SUPPORTED. +// This ensures that ASSERT_DEATH_IF_SUPPORTED will not +// compile inside functions where ASSERT_DEATH doesn't +// compile. +// +// The branch that has an always false condition is used to ensure that +// statement and regex are compiled (and thus syntactically correct) but +// never executed. The unreachable code macro protects the terminator +// statement from generating an 'unreachable code' warning in case +// statement unconditionally returns or throws. The Message constructor at +// the end allows the syntax of streaming additional messages into the +// macro, for compilational compatibility with EXPECT_DEATH/ASSERT_DEATH. +# define GTEST_UNSUPPORTED_DEATH_TEST_(statement, regex, terminator) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (::testing::internal::AlwaysTrue()) { \ + GTEST_LOG_(WARNING) \ + << "Death tests are not supported on this platform.\n" \ + << "Statement '" #statement "' cannot be verified."; \ + } else if (::testing::internal::AlwaysFalse()) { \ + ::testing::internal::RE::PartialMatch(".*", (regex)); \ + GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ + terminator; \ + } else \ + ::testing::Message() + +#endif // GTEST_HAS_DEATH_TEST + +} // namespace internal +} // namespace testing + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_DEATH_TEST_INTERNAL_H_ diff --git a/vendor/gmock-1.7.0/gtest/include/gtest/internal/gtest-filepath.h b/vendor/gmock-1.7.0/gtest/include/gtest/internal/gtest-filepath.h new file mode 100644 index 0000000000..7a13b4b0de --- /dev/null +++ b/vendor/gmock-1.7.0/gtest/include/gtest/internal/gtest-filepath.h @@ -0,0 +1,206 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: keith.ray@gmail.com (Keith Ray) +// +// Google Test filepath utilities +// +// This header file declares classes and functions used internally by +// Google Test. They are subject to change without notice. +// +// This file is #included in . +// Do not include this header file separately! + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_FILEPATH_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_FILEPATH_H_ + +#include "gtest/internal/gtest-string.h" + +namespace testing { +namespace internal { + +// FilePath - a class for file and directory pathname manipulation which +// handles platform-specific conventions (like the pathname separator). +// Used for helper functions for naming files in a directory for xml output. +// Except for Set methods, all methods are const or static, which provides an +// "immutable value object" -- useful for peace of mind. +// A FilePath with a value ending in a path separator ("like/this/") represents +// a directory, otherwise it is assumed to represent a file. In either case, +// it may or may not represent an actual file or directory in the file system. +// Names are NOT checked for syntax correctness -- no checking for illegal +// characters, malformed paths, etc. + +class GTEST_API_ FilePath { + public: + FilePath() : pathname_("") { } + FilePath(const FilePath& rhs) : pathname_(rhs.pathname_) { } + + explicit FilePath(const std::string& pathname) : pathname_(pathname) { + Normalize(); + } + + FilePath& operator=(const FilePath& rhs) { + Set(rhs); + return *this; + } + + void Set(const FilePath& rhs) { + pathname_ = rhs.pathname_; + } + + const std::string& string() const { return pathname_; } + const char* c_str() const { return pathname_.c_str(); } + + // Returns the current working directory, or "" if unsuccessful. + static FilePath GetCurrentDir(); + + // Given directory = "dir", base_name = "test", number = 0, + // extension = "xml", returns "dir/test.xml". If number is greater + // than zero (e.g., 12), returns "dir/test_12.xml". + // On Windows platform, uses \ as the separator rather than /. + static FilePath MakeFileName(const FilePath& directory, + const FilePath& base_name, + int number, + const char* extension); + + // Given directory = "dir", relative_path = "test.xml", + // returns "dir/test.xml". + // On Windows, uses \ as the separator rather than /. + static FilePath ConcatPaths(const FilePath& directory, + const FilePath& relative_path); + + // Returns a pathname for a file that does not currently exist. The pathname + // will be directory/base_name.extension or + // directory/base_name_.extension if directory/base_name.extension + // already exists. The number will be incremented until a pathname is found + // that does not already exist. + // Examples: 'dir/foo_test.xml' or 'dir/foo_test_1.xml'. + // There could be a race condition if two or more processes are calling this + // function at the same time -- they could both pick the same filename. + static FilePath GenerateUniqueFileName(const FilePath& directory, + const FilePath& base_name, + const char* extension); + + // Returns true iff the path is "". + bool IsEmpty() const { return pathname_.empty(); } + + // If input name has a trailing separator character, removes it and returns + // the name, otherwise return the name string unmodified. + // On Windows platform, uses \ as the separator, other platforms use /. + FilePath RemoveTrailingPathSeparator() const; + + // Returns a copy of the FilePath with the directory part removed. + // Example: FilePath("path/to/file").RemoveDirectoryName() returns + // FilePath("file"). If there is no directory part ("just_a_file"), it returns + // the FilePath unmodified. If there is no file part ("just_a_dir/") it + // returns an empty FilePath (""). + // On Windows platform, '\' is the path separator, otherwise it is '/'. + FilePath RemoveDirectoryName() const; + + // RemoveFileName returns the directory path with the filename removed. + // Example: FilePath("path/to/file").RemoveFileName() returns "path/to/". + // If the FilePath is "a_file" or "/a_file", RemoveFileName returns + // FilePath("./") or, on Windows, FilePath(".\\"). If the filepath does + // not have a file, like "just/a/dir/", it returns the FilePath unmodified. + // On Windows platform, '\' is the path separator, otherwise it is '/'. + FilePath RemoveFileName() const; + + // Returns a copy of the FilePath with the case-insensitive extension removed. + // Example: FilePath("dir/file.exe").RemoveExtension("EXE") returns + // FilePath("dir/file"). If a case-insensitive extension is not + // found, returns a copy of the original FilePath. + FilePath RemoveExtension(const char* extension) const; + + // Creates directories so that path exists. Returns true if successful or if + // the directories already exist; returns false if unable to create + // directories for any reason. Will also return false if the FilePath does + // not represent a directory (that is, it doesn't end with a path separator). + bool CreateDirectoriesRecursively() const; + + // Create the directory so that path exists. Returns true if successful or + // if the directory already exists; returns false if unable to create the + // directory for any reason, including if the parent directory does not + // exist. Not named "CreateDirectory" because that's a macro on Windows. + bool CreateFolder() const; + + // Returns true if FilePath describes something in the file-system, + // either a file, directory, or whatever, and that something exists. + bool FileOrDirectoryExists() const; + + // Returns true if pathname describes a directory in the file-system + // that exists. + bool DirectoryExists() const; + + // Returns true if FilePath ends with a path separator, which indicates that + // it is intended to represent a directory. Returns false otherwise. + // This does NOT check that a directory (or file) actually exists. + bool IsDirectory() const; + + // Returns true if pathname describes a root directory. (Windows has one + // root directory per disk drive.) + bool IsRootDirectory() const; + + // Returns true if pathname describes an absolute path. + bool IsAbsolutePath() const; + + private: + // Replaces multiple consecutive separators with a single separator. + // For example, "bar///foo" becomes "bar/foo". Does not eliminate other + // redundancies that might be in a pathname involving "." or "..". + // + // A pathname with multiple consecutive separators may occur either through + // user error or as a result of some scripts or APIs that generate a pathname + // with a trailing separator. On other platforms the same API or script + // may NOT generate a pathname with a trailing "/". Then elsewhere that + // pathname may have another "/" and pathname components added to it, + // without checking for the separator already being there. + // The script language and operating system may allow paths like "foo//bar" + // but some of the functions in FilePath will not handle that correctly. In + // particular, RemoveTrailingPathSeparator() only removes one separator, and + // it is called in CreateDirectoriesRecursively() assuming that it will change + // a pathname from directory syntax (trailing separator) to filename syntax. + // + // On Windows this method also replaces the alternate path separator '/' with + // the primary path separator '\\', so that for example "bar\\/\\foo" becomes + // "bar\\foo". + + void Normalize(); + + // Returns a pointer to the last occurence of a valid path separator in + // the FilePath. On Windows, for example, both '/' and '\' are valid path + // separators. Returns NULL if no path separator was found. + const char* FindLastPathSeparator() const; + + std::string pathname_; +}; // class FilePath + +} // namespace internal +} // namespace testing + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_FILEPATH_H_ diff --git a/vendor/gmock-1.7.0/gtest/include/gtest/internal/gtest-internal.h b/vendor/gmock-1.7.0/gtest/include/gtest/internal/gtest-internal.h new file mode 100644 index 0000000000..0dcc3a3194 --- /dev/null +++ b/vendor/gmock-1.7.0/gtest/include/gtest/internal/gtest-internal.h @@ -0,0 +1,1158 @@ +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Authors: wan@google.com (Zhanyong Wan), eefacm@gmail.com (Sean Mcafee) +// +// The Google C++ Testing Framework (Google Test) +// +// This header file declares functions and macros used internally by +// Google Test. They are subject to change without notice. + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_INTERNAL_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_INTERNAL_H_ + +#include "gtest/internal/gtest-port.h" + +#if GTEST_OS_LINUX +# include +# include +# include +# include +#endif // GTEST_OS_LINUX + +#if GTEST_HAS_EXCEPTIONS +# include +#endif + +#include +#include +#include +#include +#include +#include + +#include "gtest/gtest-message.h" +#include "gtest/internal/gtest-string.h" +#include "gtest/internal/gtest-filepath.h" +#include "gtest/internal/gtest-type-util.h" + +// Due to C++ preprocessor weirdness, we need double indirection to +// concatenate two tokens when one of them is __LINE__. Writing +// +// foo ## __LINE__ +// +// will result in the token foo__LINE__, instead of foo followed by +// the current line number. For more details, see +// http://www.parashift.com/c++-faq-lite/misc-technical-issues.html#faq-39.6 +#define GTEST_CONCAT_TOKEN_(foo, bar) GTEST_CONCAT_TOKEN_IMPL_(foo, bar) +#define GTEST_CONCAT_TOKEN_IMPL_(foo, bar) foo ## bar + +class ProtocolMessage; +namespace proto2 { class Message; } + +namespace testing { + +// Forward declarations. + +class AssertionResult; // Result of an assertion. +class Message; // Represents a failure message. +class Test; // Represents a test. +class TestInfo; // Information about a test. +class TestPartResult; // Result of a test part. +class UnitTest; // A collection of test cases. + +template +::std::string PrintToString(const T& value); + +namespace internal { + +struct TraceInfo; // Information about a trace point. +class ScopedTrace; // Implements scoped trace. +class TestInfoImpl; // Opaque implementation of TestInfo +class UnitTestImpl; // Opaque implementation of UnitTest + +// How many times InitGoogleTest() has been called. +GTEST_API_ extern int g_init_gtest_count; + +// The text used in failure messages to indicate the start of the +// stack trace. +GTEST_API_ extern const char kStackTraceMarker[]; + +// Two overloaded helpers for checking at compile time whether an +// expression is a null pointer literal (i.e. NULL or any 0-valued +// compile-time integral constant). Their return values have +// different sizes, so we can use sizeof() to test which version is +// picked by the compiler. These helpers have no implementations, as +// we only need their signatures. +// +// Given IsNullLiteralHelper(x), the compiler will pick the first +// version if x can be implicitly converted to Secret*, and pick the +// second version otherwise. Since Secret is a secret and incomplete +// type, the only expression a user can write that has type Secret* is +// a null pointer literal. Therefore, we know that x is a null +// pointer literal if and only if the first version is picked by the +// compiler. +char IsNullLiteralHelper(Secret* p); +char (&IsNullLiteralHelper(...))[2]; // NOLINT + +// A compile-time bool constant that is true if and only if x is a +// null pointer literal (i.e. NULL or any 0-valued compile-time +// integral constant). +#ifdef GTEST_ELLIPSIS_NEEDS_POD_ +// We lose support for NULL detection where the compiler doesn't like +// passing non-POD classes through ellipsis (...). +# define GTEST_IS_NULL_LITERAL_(x) false +#else +# define GTEST_IS_NULL_LITERAL_(x) \ + (sizeof(::testing::internal::IsNullLiteralHelper(x)) == 1) +#endif // GTEST_ELLIPSIS_NEEDS_POD_ + +// Appends the user-supplied message to the Google-Test-generated message. +GTEST_API_ std::string AppendUserMessage( + const std::string& gtest_msg, const Message& user_msg); + +#if GTEST_HAS_EXCEPTIONS + +// This exception is thrown by (and only by) a failed Google Test +// assertion when GTEST_FLAG(throw_on_failure) is true (if exceptions +// are enabled). We derive it from std::runtime_error, which is for +// errors presumably detectable only at run time. Since +// std::runtime_error inherits from std::exception, many testing +// frameworks know how to extract and print the message inside it. +class GTEST_API_ GoogleTestFailureException : public ::std::runtime_error { + public: + explicit GoogleTestFailureException(const TestPartResult& failure); +}; + +#endif // GTEST_HAS_EXCEPTIONS + +// A helper class for creating scoped traces in user programs. +class GTEST_API_ ScopedTrace { + public: + // The c'tor pushes the given source file location and message onto + // a trace stack maintained by Google Test. + ScopedTrace(const char* file, int line, const Message& message); + + // The d'tor pops the info pushed by the c'tor. + // + // Note that the d'tor is not virtual in order to be efficient. + // Don't inherit from ScopedTrace! + ~ScopedTrace(); + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(ScopedTrace); +} GTEST_ATTRIBUTE_UNUSED_; // A ScopedTrace object does its job in its + // c'tor and d'tor. Therefore it doesn't + // need to be used otherwise. + +// Constructs and returns the message for an equality assertion +// (e.g. ASSERT_EQ, EXPECT_STREQ, etc) failure. +// +// The first four parameters are the expressions used in the assertion +// and their values, as strings. For example, for ASSERT_EQ(foo, bar) +// where foo is 5 and bar is 6, we have: +// +// expected_expression: "foo" +// actual_expression: "bar" +// expected_value: "5" +// actual_value: "6" +// +// The ignoring_case parameter is true iff the assertion is a +// *_STRCASEEQ*. When it's true, the string " (ignoring case)" will +// be inserted into the message. +GTEST_API_ AssertionResult EqFailure(const char* expected_expression, + const char* actual_expression, + const std::string& expected_value, + const std::string& actual_value, + bool ignoring_case); + +// Constructs a failure message for Boolean assertions such as EXPECT_TRUE. +GTEST_API_ std::string GetBoolAssertionFailureMessage( + const AssertionResult& assertion_result, + const char* expression_text, + const char* actual_predicate_value, + const char* expected_predicate_value); + +// This template class represents an IEEE floating-point number +// (either single-precision or double-precision, depending on the +// template parameters). +// +// The purpose of this class is to do more sophisticated number +// comparison. (Due to round-off error, etc, it's very unlikely that +// two floating-points will be equal exactly. Hence a naive +// comparison by the == operation often doesn't work.) +// +// Format of IEEE floating-point: +// +// The most-significant bit being the leftmost, an IEEE +// floating-point looks like +// +// sign_bit exponent_bits fraction_bits +// +// Here, sign_bit is a single bit that designates the sign of the +// number. +// +// For float, there are 8 exponent bits and 23 fraction bits. +// +// For double, there are 11 exponent bits and 52 fraction bits. +// +// More details can be found at +// http://en.wikipedia.org/wiki/IEEE_floating-point_standard. +// +// Template parameter: +// +// RawType: the raw floating-point type (either float or double) +template +class FloatingPoint { + public: + // Defines the unsigned integer type that has the same size as the + // floating point number. + typedef typename TypeWithSize::UInt Bits; + + // Constants. + + // # of bits in a number. + static const size_t kBitCount = 8*sizeof(RawType); + + // # of fraction bits in a number. + static const size_t kFractionBitCount = + std::numeric_limits::digits - 1; + + // # of exponent bits in a number. + static const size_t kExponentBitCount = kBitCount - 1 - kFractionBitCount; + + // The mask for the sign bit. + static const Bits kSignBitMask = static_cast(1) << (kBitCount - 1); + + // The mask for the fraction bits. + static const Bits kFractionBitMask = + ~static_cast(0) >> (kExponentBitCount + 1); + + // The mask for the exponent bits. + static const Bits kExponentBitMask = ~(kSignBitMask | kFractionBitMask); + + // How many ULP's (Units in the Last Place) we want to tolerate when + // comparing two numbers. The larger the value, the more error we + // allow. A 0 value means that two numbers must be exactly the same + // to be considered equal. + // + // The maximum error of a single floating-point operation is 0.5 + // units in the last place. On Intel CPU's, all floating-point + // calculations are done with 80-bit precision, while double has 64 + // bits. Therefore, 4 should be enough for ordinary use. + // + // See the following article for more details on ULP: + // http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/ + static const size_t kMaxUlps = 4; + + // Constructs a FloatingPoint from a raw floating-point number. + // + // On an Intel CPU, passing a non-normalized NAN (Not a Number) + // around may change its bits, although the new value is guaranteed + // to be also a NAN. Therefore, don't expect this constructor to + // preserve the bits in x when x is a NAN. + explicit FloatingPoint(const RawType& x) { u_.value_ = x; } + + // Static methods + + // Reinterprets a bit pattern as a floating-point number. + // + // This function is needed to test the AlmostEquals() method. + static RawType ReinterpretBits(const Bits bits) { + FloatingPoint fp(0); + fp.u_.bits_ = bits; + return fp.u_.value_; + } + + // Returns the floating-point number that represent positive infinity. + static RawType Infinity() { + return ReinterpretBits(kExponentBitMask); + } + + // Returns the maximum representable finite floating-point number. + static RawType Max(); + + // Non-static methods + + // Returns the bits that represents this number. + const Bits &bits() const { return u_.bits_; } + + // Returns the exponent bits of this number. + Bits exponent_bits() const { return kExponentBitMask & u_.bits_; } + + // Returns the fraction bits of this number. + Bits fraction_bits() const { return kFractionBitMask & u_.bits_; } + + // Returns the sign bit of this number. + Bits sign_bit() const { return kSignBitMask & u_.bits_; } + + // Returns true iff this is NAN (not a number). + bool is_nan() const { + // It's a NAN if the exponent bits are all ones and the fraction + // bits are not entirely zeros. + return (exponent_bits() == kExponentBitMask) && (fraction_bits() != 0); + } + + // Returns true iff this number is at most kMaxUlps ULP's away from + // rhs. In particular, this function: + // + // - returns false if either number is (or both are) NAN. + // - treats really large numbers as almost equal to infinity. + // - thinks +0.0 and -0.0 are 0 DLP's apart. + bool AlmostEquals(const FloatingPoint& rhs) const { + // The IEEE standard says that any comparison operation involving + // a NAN must return false. + if (is_nan() || rhs.is_nan()) return false; + + return DistanceBetweenSignAndMagnitudeNumbers(u_.bits_, rhs.u_.bits_) + <= kMaxUlps; + } + + private: + // The data type used to store the actual floating-point number. + union FloatingPointUnion { + RawType value_; // The raw floating-point number. + Bits bits_; // The bits that represent the number. + }; + + // Converts an integer from the sign-and-magnitude representation to + // the biased representation. More precisely, let N be 2 to the + // power of (kBitCount - 1), an integer x is represented by the + // unsigned number x + N. + // + // For instance, + // + // -N + 1 (the most negative number representable using + // sign-and-magnitude) is represented by 1; + // 0 is represented by N; and + // N - 1 (the biggest number representable using + // sign-and-magnitude) is represented by 2N - 1. + // + // Read http://en.wikipedia.org/wiki/Signed_number_representations + // for more details on signed number representations. + static Bits SignAndMagnitudeToBiased(const Bits &sam) { + if (kSignBitMask & sam) { + // sam represents a negative number. + return ~sam + 1; + } else { + // sam represents a positive number. + return kSignBitMask | sam; + } + } + + // Given two numbers in the sign-and-magnitude representation, + // returns the distance between them as an unsigned number. + static Bits DistanceBetweenSignAndMagnitudeNumbers(const Bits &sam1, + const Bits &sam2) { + const Bits biased1 = SignAndMagnitudeToBiased(sam1); + const Bits biased2 = SignAndMagnitudeToBiased(sam2); + return (biased1 >= biased2) ? (biased1 - biased2) : (biased2 - biased1); + } + + FloatingPointUnion u_; +}; + +// We cannot use std::numeric_limits::max() as it clashes with the max() +// macro defined by . +template <> +inline float FloatingPoint::Max() { return FLT_MAX; } +template <> +inline double FloatingPoint::Max() { return DBL_MAX; } + +// Typedefs the instances of the FloatingPoint template class that we +// care to use. +typedef FloatingPoint Float; +typedef FloatingPoint Double; + +// In order to catch the mistake of putting tests that use different +// test fixture classes in the same test case, we need to assign +// unique IDs to fixture classes and compare them. The TypeId type is +// used to hold such IDs. The user should treat TypeId as an opaque +// type: the only operation allowed on TypeId values is to compare +// them for equality using the == operator. +typedef const void* TypeId; + +template +class TypeIdHelper { + public: + // dummy_ must not have a const type. Otherwise an overly eager + // compiler (e.g. MSVC 7.1 & 8.0) may try to merge + // TypeIdHelper::dummy_ for different Ts as an "optimization". + static bool dummy_; +}; + +template +bool TypeIdHelper::dummy_ = false; + +// GetTypeId() returns the ID of type T. Different values will be +// returned for different types. Calling the function twice with the +// same type argument is guaranteed to return the same ID. +template +TypeId GetTypeId() { + // The compiler is required to allocate a different + // TypeIdHelper::dummy_ variable for each T used to instantiate + // the template. Therefore, the address of dummy_ is guaranteed to + // be unique. + return &(TypeIdHelper::dummy_); +} + +// Returns the type ID of ::testing::Test. Always call this instead +// of GetTypeId< ::testing::Test>() to get the type ID of +// ::testing::Test, as the latter may give the wrong result due to a +// suspected linker bug when compiling Google Test as a Mac OS X +// framework. +GTEST_API_ TypeId GetTestTypeId(); + +// Defines the abstract factory interface that creates instances +// of a Test object. +class TestFactoryBase { + public: + virtual ~TestFactoryBase() {} + + // Creates a test instance to run. The instance is both created and destroyed + // within TestInfoImpl::Run() + virtual Test* CreateTest() = 0; + + protected: + TestFactoryBase() {} + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(TestFactoryBase); +}; + +// This class provides implementation of TeastFactoryBase interface. +// It is used in TEST and TEST_F macros. +template +class TestFactoryImpl : public TestFactoryBase { + public: + virtual Test* CreateTest() { return new TestClass; } +}; + +#if GTEST_OS_WINDOWS + +// Predicate-formatters for implementing the HRESULT checking macros +// {ASSERT|EXPECT}_HRESULT_{SUCCEEDED|FAILED} +// We pass a long instead of HRESULT to avoid causing an +// include dependency for the HRESULT type. +GTEST_API_ AssertionResult IsHRESULTSuccess(const char* expr, + long hr); // NOLINT +GTEST_API_ AssertionResult IsHRESULTFailure(const char* expr, + long hr); // NOLINT + +#endif // GTEST_OS_WINDOWS + +// Types of SetUpTestCase() and TearDownTestCase() functions. +typedef void (*SetUpTestCaseFunc)(); +typedef void (*TearDownTestCaseFunc)(); + +// Creates a new TestInfo object and registers it with Google Test; +// returns the created object. +// +// Arguments: +// +// test_case_name: name of the test case +// name: name of the test +// type_param the name of the test's type parameter, or NULL if +// this is not a typed or a type-parameterized test. +// value_param text representation of the test's value parameter, +// or NULL if this is not a type-parameterized test. +// fixture_class_id: ID of the test fixture class +// set_up_tc: pointer to the function that sets up the test case +// tear_down_tc: pointer to the function that tears down the test case +// factory: pointer to the factory that creates a test object. +// The newly created TestInfo instance will assume +// ownership of the factory object. +GTEST_API_ TestInfo* MakeAndRegisterTestInfo( + const char* test_case_name, + const char* name, + const char* type_param, + const char* value_param, + TypeId fixture_class_id, + SetUpTestCaseFunc set_up_tc, + TearDownTestCaseFunc tear_down_tc, + TestFactoryBase* factory); + +// If *pstr starts with the given prefix, modifies *pstr to be right +// past the prefix and returns true; otherwise leaves *pstr unchanged +// and returns false. None of pstr, *pstr, and prefix can be NULL. +GTEST_API_ bool SkipPrefix(const char* prefix, const char** pstr); + +#if GTEST_HAS_TYPED_TEST || GTEST_HAS_TYPED_TEST_P + +// State of the definition of a type-parameterized test case. +class GTEST_API_ TypedTestCasePState { + public: + TypedTestCasePState() : registered_(false) {} + + // Adds the given test name to defined_test_names_ and return true + // if the test case hasn't been registered; otherwise aborts the + // program. + bool AddTestName(const char* file, int line, const char* case_name, + const char* test_name) { + if (registered_) { + fprintf(stderr, "%s Test %s must be defined before " + "REGISTER_TYPED_TEST_CASE_P(%s, ...).\n", + FormatFileLocation(file, line).c_str(), test_name, case_name); + fflush(stderr); + posix::Abort(); + } + defined_test_names_.insert(test_name); + return true; + } + + // Verifies that registered_tests match the test names in + // defined_test_names_; returns registered_tests if successful, or + // aborts the program otherwise. + const char* VerifyRegisteredTestNames( + const char* file, int line, const char* registered_tests); + + private: + bool registered_; + ::std::set defined_test_names_; +}; + +// Skips to the first non-space char after the first comma in 'str'; +// returns NULL if no comma is found in 'str'. +inline const char* SkipComma(const char* str) { + const char* comma = strchr(str, ','); + if (comma == NULL) { + return NULL; + } + while (IsSpace(*(++comma))) {} + return comma; +} + +// Returns the prefix of 'str' before the first comma in it; returns +// the entire string if it contains no comma. +inline std::string GetPrefixUntilComma(const char* str) { + const char* comma = strchr(str, ','); + return comma == NULL ? str : std::string(str, comma); +} + +// TypeParameterizedTest::Register() +// registers a list of type-parameterized tests with Google Test. The +// return value is insignificant - we just need to return something +// such that we can call this function in a namespace scope. +// +// Implementation note: The GTEST_TEMPLATE_ macro declares a template +// template parameter. It's defined in gtest-type-util.h. +template +class TypeParameterizedTest { + public: + // 'index' is the index of the test in the type list 'Types' + // specified in INSTANTIATE_TYPED_TEST_CASE_P(Prefix, TestCase, + // Types). Valid values for 'index' are [0, N - 1] where N is the + // length of Types. + static bool Register(const char* prefix, const char* case_name, + const char* test_names, int index) { + typedef typename Types::Head Type; + typedef Fixture FixtureClass; + typedef typename GTEST_BIND_(TestSel, Type) TestClass; + + // First, registers the first type-parameterized test in the type + // list. + MakeAndRegisterTestInfo( + (std::string(prefix) + (prefix[0] == '\0' ? "" : "/") + case_name + "/" + + StreamableToString(index)).c_str(), + GetPrefixUntilComma(test_names).c_str(), + GetTypeName().c_str(), + NULL, // No value parameter. + GetTypeId(), + TestClass::SetUpTestCase, + TestClass::TearDownTestCase, + new TestFactoryImpl); + + // Next, recurses (at compile time) with the tail of the type list. + return TypeParameterizedTest + ::Register(prefix, case_name, test_names, index + 1); + } +}; + +// The base case for the compile time recursion. +template +class TypeParameterizedTest { + public: + static bool Register(const char* /*prefix*/, const char* /*case_name*/, + const char* /*test_names*/, int /*index*/) { + return true; + } +}; + +// TypeParameterizedTestCase::Register() +// registers *all combinations* of 'Tests' and 'Types' with Google +// Test. The return value is insignificant - we just need to return +// something such that we can call this function in a namespace scope. +template +class TypeParameterizedTestCase { + public: + static bool Register(const char* prefix, const char* case_name, + const char* test_names) { + typedef typename Tests::Head Head; + + // First, register the first test in 'Test' for each type in 'Types'. + TypeParameterizedTest::Register( + prefix, case_name, test_names, 0); + + // Next, recurses (at compile time) with the tail of the test list. + return TypeParameterizedTestCase + ::Register(prefix, case_name, SkipComma(test_names)); + } +}; + +// The base case for the compile time recursion. +template +class TypeParameterizedTestCase { + public: + static bool Register(const char* /*prefix*/, const char* /*case_name*/, + const char* /*test_names*/) { + return true; + } +}; + +#endif // GTEST_HAS_TYPED_TEST || GTEST_HAS_TYPED_TEST_P + +// Returns the current OS stack trace as an std::string. +// +// The maximum number of stack frames to be included is specified by +// the gtest_stack_trace_depth flag. The skip_count parameter +// specifies the number of top frames to be skipped, which doesn't +// count against the number of frames to be included. +// +// For example, if Foo() calls Bar(), which in turn calls +// GetCurrentOsStackTraceExceptTop(..., 1), Foo() will be included in +// the trace but Bar() and GetCurrentOsStackTraceExceptTop() won't. +GTEST_API_ std::string GetCurrentOsStackTraceExceptTop( + UnitTest* unit_test, int skip_count); + +// Helpers for suppressing warnings on unreachable code or constant +// condition. + +// Always returns true. +GTEST_API_ bool AlwaysTrue(); + +// Always returns false. +inline bool AlwaysFalse() { return !AlwaysTrue(); } + +// Helper for suppressing false warning from Clang on a const char* +// variable declared in a conditional expression always being NULL in +// the else branch. +struct GTEST_API_ ConstCharPtr { + ConstCharPtr(const char* str) : value(str) {} + operator bool() const { return true; } + const char* value; +}; + +// A simple Linear Congruential Generator for generating random +// numbers with a uniform distribution. Unlike rand() and srand(), it +// doesn't use global state (and therefore can't interfere with user +// code). Unlike rand_r(), it's portable. An LCG isn't very random, +// but it's good enough for our purposes. +class GTEST_API_ Random { + public: + static const UInt32 kMaxRange = 1u << 31; + + explicit Random(UInt32 seed) : state_(seed) {} + + void Reseed(UInt32 seed) { state_ = seed; } + + // Generates a random number from [0, range). Crashes if 'range' is + // 0 or greater than kMaxRange. + UInt32 Generate(UInt32 range); + + private: + UInt32 state_; + GTEST_DISALLOW_COPY_AND_ASSIGN_(Random); +}; + +// Defining a variable of type CompileAssertTypesEqual will cause a +// compiler error iff T1 and T2 are different types. +template +struct CompileAssertTypesEqual; + +template +struct CompileAssertTypesEqual { +}; + +// Removes the reference from a type if it is a reference type, +// otherwise leaves it unchanged. This is the same as +// tr1::remove_reference, which is not widely available yet. +template +struct RemoveReference { typedef T type; }; // NOLINT +template +struct RemoveReference { typedef T type; }; // NOLINT + +// A handy wrapper around RemoveReference that works when the argument +// T depends on template parameters. +#define GTEST_REMOVE_REFERENCE_(T) \ + typename ::testing::internal::RemoveReference::type + +// Removes const from a type if it is a const type, otherwise leaves +// it unchanged. This is the same as tr1::remove_const, which is not +// widely available yet. +template +struct RemoveConst { typedef T type; }; // NOLINT +template +struct RemoveConst { typedef T type; }; // NOLINT + +// MSVC 8.0, Sun C++, and IBM XL C++ have a bug which causes the above +// definition to fail to remove the const in 'const int[3]' and 'const +// char[3][4]'. The following specialization works around the bug. +template +struct RemoveConst { + typedef typename RemoveConst::type type[N]; +}; + +#if defined(_MSC_VER) && _MSC_VER < 1400 +// This is the only specialization that allows VC++ 7.1 to remove const in +// 'const int[3] and 'const int[3][4]'. However, it causes trouble with GCC +// and thus needs to be conditionally compiled. +template +struct RemoveConst { + typedef typename RemoveConst::type type[N]; +}; +#endif + +// A handy wrapper around RemoveConst that works when the argument +// T depends on template parameters. +#define GTEST_REMOVE_CONST_(T) \ + typename ::testing::internal::RemoveConst::type + +// Turns const U&, U&, const U, and U all into U. +#define GTEST_REMOVE_REFERENCE_AND_CONST_(T) \ + GTEST_REMOVE_CONST_(GTEST_REMOVE_REFERENCE_(T)) + +// Adds reference to a type if it is not a reference type, +// otherwise leaves it unchanged. This is the same as +// tr1::add_reference, which is not widely available yet. +template +struct AddReference { typedef T& type; }; // NOLINT +template +struct AddReference { typedef T& type; }; // NOLINT + +// A handy wrapper around AddReference that works when the argument T +// depends on template parameters. +#define GTEST_ADD_REFERENCE_(T) \ + typename ::testing::internal::AddReference::type + +// Adds a reference to const on top of T as necessary. For example, +// it transforms +// +// char ==> const char& +// const char ==> const char& +// char& ==> const char& +// const char& ==> const char& +// +// The argument T must depend on some template parameters. +#define GTEST_REFERENCE_TO_CONST_(T) \ + GTEST_ADD_REFERENCE_(const GTEST_REMOVE_REFERENCE_(T)) + +// ImplicitlyConvertible::value is a compile-time bool +// constant that's true iff type From can be implicitly converted to +// type To. +template +class ImplicitlyConvertible { + private: + // We need the following helper functions only for their types. + // They have no implementations. + + // MakeFrom() is an expression whose type is From. We cannot simply + // use From(), as the type From may not have a public default + // constructor. + static From MakeFrom(); + + // These two functions are overloaded. Given an expression + // Helper(x), the compiler will pick the first version if x can be + // implicitly converted to type To; otherwise it will pick the + // second version. + // + // The first version returns a value of size 1, and the second + // version returns a value of size 2. Therefore, by checking the + // size of Helper(x), which can be done at compile time, we can tell + // which version of Helper() is used, and hence whether x can be + // implicitly converted to type To. + static char Helper(To); + static char (&Helper(...))[2]; // NOLINT + + // We have to put the 'public' section after the 'private' section, + // or MSVC refuses to compile the code. + public: + // MSVC warns about implicitly converting from double to int for + // possible loss of data, so we need to temporarily disable the + // warning. +#ifdef _MSC_VER +# pragma warning(push) // Saves the current warning state. +# pragma warning(disable:4244) // Temporarily disables warning 4244. + + static const bool value = + sizeof(Helper(ImplicitlyConvertible::MakeFrom())) == 1; +# pragma warning(pop) // Restores the warning state. +#elif defined(__BORLANDC__) + // C++Builder cannot use member overload resolution during template + // instantiation. The simplest workaround is to use its C++0x type traits + // functions (C++Builder 2009 and above only). + static const bool value = __is_convertible(From, To); +#else + static const bool value = + sizeof(Helper(ImplicitlyConvertible::MakeFrom())) == 1; +#endif // _MSV_VER +}; +template +const bool ImplicitlyConvertible::value; + +// IsAProtocolMessage::value is a compile-time bool constant that's +// true iff T is type ProtocolMessage, proto2::Message, or a subclass +// of those. +template +struct IsAProtocolMessage + : public bool_constant< + ImplicitlyConvertible::value || + ImplicitlyConvertible::value> { +}; + +// When the compiler sees expression IsContainerTest(0), if C is an +// STL-style container class, the first overload of IsContainerTest +// will be viable (since both C::iterator* and C::const_iterator* are +// valid types and NULL can be implicitly converted to them). It will +// be picked over the second overload as 'int' is a perfect match for +// the type of argument 0. If C::iterator or C::const_iterator is not +// a valid type, the first overload is not viable, and the second +// overload will be picked. Therefore, we can determine whether C is +// a container class by checking the type of IsContainerTest(0). +// The value of the expression is insignificant. +// +// Note that we look for both C::iterator and C::const_iterator. The +// reason is that C++ injects the name of a class as a member of the +// class itself (e.g. you can refer to class iterator as either +// 'iterator' or 'iterator::iterator'). If we look for C::iterator +// only, for example, we would mistakenly think that a class named +// iterator is an STL container. +// +// Also note that the simpler approach of overloading +// IsContainerTest(typename C::const_iterator*) and +// IsContainerTest(...) doesn't work with Visual Age C++ and Sun C++. +typedef int IsContainer; +template +IsContainer IsContainerTest(int /* dummy */, + typename C::iterator* /* it */ = NULL, + typename C::const_iterator* /* const_it */ = NULL) { + return 0; +} + +typedef char IsNotContainer; +template +IsNotContainer IsContainerTest(long /* dummy */) { return '\0'; } + +// EnableIf::type is void when 'Cond' is true, and +// undefined when 'Cond' is false. To use SFINAE to make a function +// overload only apply when a particular expression is true, add +// "typename EnableIf::type* = 0" as the last parameter. +template struct EnableIf; +template<> struct EnableIf { typedef void type; }; // NOLINT + +// Utilities for native arrays. + +// ArrayEq() compares two k-dimensional native arrays using the +// elements' operator==, where k can be any integer >= 0. When k is +// 0, ArrayEq() degenerates into comparing a single pair of values. + +template +bool ArrayEq(const T* lhs, size_t size, const U* rhs); + +// This generic version is used when k is 0. +template +inline bool ArrayEq(const T& lhs, const U& rhs) { return lhs == rhs; } + +// This overload is used when k >= 1. +template +inline bool ArrayEq(const T(&lhs)[N], const U(&rhs)[N]) { + return internal::ArrayEq(lhs, N, rhs); +} + +// This helper reduces code bloat. If we instead put its logic inside +// the previous ArrayEq() function, arrays with different sizes would +// lead to different copies of the template code. +template +bool ArrayEq(const T* lhs, size_t size, const U* rhs) { + for (size_t i = 0; i != size; i++) { + if (!internal::ArrayEq(lhs[i], rhs[i])) + return false; + } + return true; +} + +// Finds the first element in the iterator range [begin, end) that +// equals elem. Element may be a native array type itself. +template +Iter ArrayAwareFind(Iter begin, Iter end, const Element& elem) { + for (Iter it = begin; it != end; ++it) { + if (internal::ArrayEq(*it, elem)) + return it; + } + return end; +} + +// CopyArray() copies a k-dimensional native array using the elements' +// operator=, where k can be any integer >= 0. When k is 0, +// CopyArray() degenerates into copying a single value. + +template +void CopyArray(const T* from, size_t size, U* to); + +// This generic version is used when k is 0. +template +inline void CopyArray(const T& from, U* to) { *to = from; } + +// This overload is used when k >= 1. +template +inline void CopyArray(const T(&from)[N], U(*to)[N]) { + internal::CopyArray(from, N, *to); +} + +// This helper reduces code bloat. If we instead put its logic inside +// the previous CopyArray() function, arrays with different sizes +// would lead to different copies of the template code. +template +void CopyArray(const T* from, size_t size, U* to) { + for (size_t i = 0; i != size; i++) { + internal::CopyArray(from[i], to + i); + } +} + +// The relation between an NativeArray object (see below) and the +// native array it represents. +enum RelationToSource { + kReference, // The NativeArray references the native array. + kCopy // The NativeArray makes a copy of the native array and + // owns the copy. +}; + +// Adapts a native array to a read-only STL-style container. Instead +// of the complete STL container concept, this adaptor only implements +// members useful for Google Mock's container matchers. New members +// should be added as needed. To simplify the implementation, we only +// support Element being a raw type (i.e. having no top-level const or +// reference modifier). It's the client's responsibility to satisfy +// this requirement. Element can be an array type itself (hence +// multi-dimensional arrays are supported). +template +class NativeArray { + public: + // STL-style container typedefs. + typedef Element value_type; + typedef Element* iterator; + typedef const Element* const_iterator; + + // Constructs from a native array. + NativeArray(const Element* array, size_t count, RelationToSource relation) { + Init(array, count, relation); + } + + // Copy constructor. + NativeArray(const NativeArray& rhs) { + Init(rhs.array_, rhs.size_, rhs.relation_to_source_); + } + + ~NativeArray() { + // Ensures that the user doesn't instantiate NativeArray with a + // const or reference type. + static_cast(StaticAssertTypeEqHelper()); + if (relation_to_source_ == kCopy) + delete[] array_; + } + + // STL-style container methods. + size_t size() const { return size_; } + const_iterator begin() const { return array_; } + const_iterator end() const { return array_ + size_; } + bool operator==(const NativeArray& rhs) const { + return size() == rhs.size() && + ArrayEq(begin(), size(), rhs.begin()); + } + + private: + // Initializes this object; makes a copy of the input array if + // 'relation' is kCopy. + void Init(const Element* array, size_t a_size, RelationToSource relation) { + if (relation == kReference) { + array_ = array; + } else { + Element* const copy = new Element[a_size]; + CopyArray(array, a_size, copy); + array_ = copy; + } + size_ = a_size; + relation_to_source_ = relation; + } + + const Element* array_; + size_t size_; + RelationToSource relation_to_source_; + + GTEST_DISALLOW_ASSIGN_(NativeArray); +}; + +} // namespace internal +} // namespace testing + +#define GTEST_MESSAGE_AT_(file, line, message, result_type) \ + ::testing::internal::AssertHelper(result_type, file, line, message) \ + = ::testing::Message() + +#define GTEST_MESSAGE_(message, result_type) \ + GTEST_MESSAGE_AT_(__FILE__, __LINE__, message, result_type) + +#define GTEST_FATAL_FAILURE_(message) \ + return GTEST_MESSAGE_(message, ::testing::TestPartResult::kFatalFailure) + +#define GTEST_NONFATAL_FAILURE_(message) \ + GTEST_MESSAGE_(message, ::testing::TestPartResult::kNonFatalFailure) + +#define GTEST_SUCCESS_(message) \ + GTEST_MESSAGE_(message, ::testing::TestPartResult::kSuccess) + +// Suppresses MSVC warnings 4072 (unreachable code) for the code following +// statement if it returns or throws (or doesn't return or throw in some +// situations). +#define GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement) \ + if (::testing::internal::AlwaysTrue()) { statement; } + +#define GTEST_TEST_THROW_(statement, expected_exception, fail) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (::testing::internal::ConstCharPtr gtest_msg = "") { \ + bool gtest_caught_expected = false; \ + try { \ + GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ + } \ + catch (expected_exception const&) { \ + gtest_caught_expected = true; \ + } \ + catch (...) { \ + gtest_msg.value = \ + "Expected: " #statement " throws an exception of type " \ + #expected_exception ".\n Actual: it throws a different type."; \ + goto GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__); \ + } \ + if (!gtest_caught_expected) { \ + gtest_msg.value = \ + "Expected: " #statement " throws an exception of type " \ + #expected_exception ".\n Actual: it throws nothing."; \ + goto GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__); \ + } \ + } else \ + GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__): \ + fail(gtest_msg.value) + +#define GTEST_TEST_NO_THROW_(statement, fail) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (::testing::internal::AlwaysTrue()) { \ + try { \ + GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ + } \ + catch (...) { \ + goto GTEST_CONCAT_TOKEN_(gtest_label_testnothrow_, __LINE__); \ + } \ + } else \ + GTEST_CONCAT_TOKEN_(gtest_label_testnothrow_, __LINE__): \ + fail("Expected: " #statement " doesn't throw an exception.\n" \ + " Actual: it throws.") + +#define GTEST_TEST_ANY_THROW_(statement, fail) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (::testing::internal::AlwaysTrue()) { \ + bool gtest_caught_any = false; \ + try { \ + GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ + } \ + catch (...) { \ + gtest_caught_any = true; \ + } \ + if (!gtest_caught_any) { \ + goto GTEST_CONCAT_TOKEN_(gtest_label_testanythrow_, __LINE__); \ + } \ + } else \ + GTEST_CONCAT_TOKEN_(gtest_label_testanythrow_, __LINE__): \ + fail("Expected: " #statement " throws an exception.\n" \ + " Actual: it doesn't.") + + +// Implements Boolean test assertions such as EXPECT_TRUE. expression can be +// either a boolean expression or an AssertionResult. text is a textual +// represenation of expression as it was passed into the EXPECT_TRUE. +#define GTEST_TEST_BOOLEAN_(expression, text, actual, expected, fail) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (const ::testing::AssertionResult gtest_ar_ = \ + ::testing::AssertionResult(expression)) \ + ; \ + else \ + fail(::testing::internal::GetBoolAssertionFailureMessage(\ + gtest_ar_, text, #actual, #expected).c_str()) + +#define GTEST_TEST_NO_FATAL_FAILURE_(statement, fail) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (::testing::internal::AlwaysTrue()) { \ + ::testing::internal::HasNewFatalFailureHelper gtest_fatal_failure_checker; \ + GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ + if (gtest_fatal_failure_checker.has_new_fatal_failure()) { \ + goto GTEST_CONCAT_TOKEN_(gtest_label_testnofatal_, __LINE__); \ + } \ + } else \ + GTEST_CONCAT_TOKEN_(gtest_label_testnofatal_, __LINE__): \ + fail("Expected: " #statement " doesn't generate new fatal " \ + "failures in the current thread.\n" \ + " Actual: it does.") + +// Expands to the name of the class that implements the given test. +#define GTEST_TEST_CLASS_NAME_(test_case_name, test_name) \ + test_case_name##_##test_name##_Test + +// Helper macro for defining tests. +#define GTEST_TEST_(test_case_name, test_name, parent_class, parent_id)\ +class GTEST_TEST_CLASS_NAME_(test_case_name, test_name) : public parent_class {\ + public:\ + GTEST_TEST_CLASS_NAME_(test_case_name, test_name)() {}\ + private:\ + virtual void TestBody();\ + static ::testing::TestInfo* const test_info_ GTEST_ATTRIBUTE_UNUSED_;\ + GTEST_DISALLOW_COPY_AND_ASSIGN_(\ + GTEST_TEST_CLASS_NAME_(test_case_name, test_name));\ +};\ +\ +::testing::TestInfo* const GTEST_TEST_CLASS_NAME_(test_case_name, test_name)\ + ::test_info_ =\ + ::testing::internal::MakeAndRegisterTestInfo(\ + #test_case_name, #test_name, NULL, NULL, \ + (parent_id), \ + parent_class::SetUpTestCase, \ + parent_class::TearDownTestCase, \ + new ::testing::internal::TestFactoryImpl<\ + GTEST_TEST_CLASS_NAME_(test_case_name, test_name)>);\ +void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::TestBody() + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_INTERNAL_H_ diff --git a/vendor/gmock-1.7.0/gtest/include/gtest/internal/gtest-linked_ptr.h b/vendor/gmock-1.7.0/gtest/include/gtest/internal/gtest-linked_ptr.h new file mode 100644 index 0000000000..b1362cd002 --- /dev/null +++ b/vendor/gmock-1.7.0/gtest/include/gtest/internal/gtest-linked_ptr.h @@ -0,0 +1,233 @@ +// Copyright 2003 Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Authors: Dan Egnor (egnor@google.com) +// +// A "smart" pointer type with reference tracking. Every pointer to a +// particular object is kept on a circular linked list. When the last pointer +// to an object is destroyed or reassigned, the object is deleted. +// +// Used properly, this deletes the object when the last reference goes away. +// There are several caveats: +// - Like all reference counting schemes, cycles lead to leaks. +// - Each smart pointer is actually two pointers (8 bytes instead of 4). +// - Every time a pointer is assigned, the entire list of pointers to that +// object is traversed. This class is therefore NOT SUITABLE when there +// will often be more than two or three pointers to a particular object. +// - References are only tracked as long as linked_ptr<> objects are copied. +// If a linked_ptr<> is converted to a raw pointer and back, BAD THINGS +// will happen (double deletion). +// +// A good use of this class is storing object references in STL containers. +// You can safely put linked_ptr<> in a vector<>. +// Other uses may not be as good. +// +// Note: If you use an incomplete type with linked_ptr<>, the class +// *containing* linked_ptr<> must have a constructor and destructor (even +// if they do nothing!). +// +// Bill Gibbons suggested we use something like this. +// +// Thread Safety: +// Unlike other linked_ptr implementations, in this implementation +// a linked_ptr object is thread-safe in the sense that: +// - it's safe to copy linked_ptr objects concurrently, +// - it's safe to copy *from* a linked_ptr and read its underlying +// raw pointer (e.g. via get()) concurrently, and +// - it's safe to write to two linked_ptrs that point to the same +// shared object concurrently. +// TODO(wan@google.com): rename this to safe_linked_ptr to avoid +// confusion with normal linked_ptr. + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_LINKED_PTR_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_LINKED_PTR_H_ + +#include +#include + +#include "gtest/internal/gtest-port.h" + +namespace testing { +namespace internal { + +// Protects copying of all linked_ptr objects. +GTEST_API_ GTEST_DECLARE_STATIC_MUTEX_(g_linked_ptr_mutex); + +// This is used internally by all instances of linked_ptr<>. It needs to be +// a non-template class because different types of linked_ptr<> can refer to +// the same object (linked_ptr(obj) vs linked_ptr(obj)). +// So, it needs to be possible for different types of linked_ptr to participate +// in the same circular linked list, so we need a single class type here. +// +// DO NOT USE THIS CLASS DIRECTLY YOURSELF. Use linked_ptr. +class linked_ptr_internal { + public: + // Create a new circle that includes only this instance. + void join_new() { + next_ = this; + } + + // Many linked_ptr operations may change p.link_ for some linked_ptr + // variable p in the same circle as this object. Therefore we need + // to prevent two such operations from occurring concurrently. + // + // Note that different types of linked_ptr objects can coexist in a + // circle (e.g. linked_ptr, linked_ptr, and + // linked_ptr). Therefore we must use a single mutex to + // protect all linked_ptr objects. This can create serious + // contention in production code, but is acceptable in a testing + // framework. + + // Join an existing circle. + void join(linked_ptr_internal const* ptr) + GTEST_LOCK_EXCLUDED_(g_linked_ptr_mutex) { + MutexLock lock(&g_linked_ptr_mutex); + + linked_ptr_internal const* p = ptr; + while (p->next_ != ptr) p = p->next_; + p->next_ = this; + next_ = ptr; + } + + // Leave whatever circle we're part of. Returns true if we were the + // last member of the circle. Once this is done, you can join() another. + bool depart() + GTEST_LOCK_EXCLUDED_(g_linked_ptr_mutex) { + MutexLock lock(&g_linked_ptr_mutex); + + if (next_ == this) return true; + linked_ptr_internal const* p = next_; + while (p->next_ != this) p = p->next_; + p->next_ = next_; + return false; + } + + private: + mutable linked_ptr_internal const* next_; +}; + +template +class linked_ptr { + public: + typedef T element_type; + + // Take over ownership of a raw pointer. This should happen as soon as + // possible after the object is created. + explicit linked_ptr(T* ptr = NULL) { capture(ptr); } + ~linked_ptr() { depart(); } + + // Copy an existing linked_ptr<>, adding ourselves to the list of references. + template linked_ptr(linked_ptr const& ptr) { copy(&ptr); } + linked_ptr(linked_ptr const& ptr) { // NOLINT + assert(&ptr != this); + copy(&ptr); + } + + // Assignment releases the old value and acquires the new. + template linked_ptr& operator=(linked_ptr const& ptr) { + depart(); + copy(&ptr); + return *this; + } + + linked_ptr& operator=(linked_ptr const& ptr) { + if (&ptr != this) { + depart(); + copy(&ptr); + } + return *this; + } + + // Smart pointer members. + void reset(T* ptr = NULL) { + depart(); + capture(ptr); + } + T* get() const { return value_; } + T* operator->() const { return value_; } + T& operator*() const { return *value_; } + + bool operator==(T* p) const { return value_ == p; } + bool operator!=(T* p) const { return value_ != p; } + template + bool operator==(linked_ptr const& ptr) const { + return value_ == ptr.get(); + } + template + bool operator!=(linked_ptr const& ptr) const { + return value_ != ptr.get(); + } + + private: + template + friend class linked_ptr; + + T* value_; + linked_ptr_internal link_; + + void depart() { + if (link_.depart()) delete value_; + } + + void capture(T* ptr) { + value_ = ptr; + link_.join_new(); + } + + template void copy(linked_ptr const* ptr) { + value_ = ptr->get(); + if (value_) + link_.join(&ptr->link_); + else + link_.join_new(); + } +}; + +template inline +bool operator==(T* ptr, const linked_ptr& x) { + return ptr == x.get(); +} + +template inline +bool operator!=(T* ptr, const linked_ptr& x) { + return ptr != x.get(); +} + +// A function to convert T* into linked_ptr +// Doing e.g. make_linked_ptr(new FooBarBaz(arg)) is a shorter notation +// for linked_ptr >(new FooBarBaz(arg)) +template +linked_ptr make_linked_ptr(T* ptr) { + return linked_ptr(ptr); +} + +} // namespace internal +} // namespace testing + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_LINKED_PTR_H_ diff --git a/vendor/gmock-1.7.0/gtest/include/gtest/internal/gtest-param-util-generated.h b/vendor/gmock-1.7.0/gtest/include/gtest/internal/gtest-param-util-generated.h new file mode 100644 index 0000000000..e80548592c --- /dev/null +++ b/vendor/gmock-1.7.0/gtest/include/gtest/internal/gtest-param-util-generated.h @@ -0,0 +1,5143 @@ +// This file was GENERATED by command: +// pump.py gtest-param-util-generated.h.pump +// DO NOT EDIT BY HAND!!! + +// Copyright 2008 Google Inc. +// All Rights Reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: vladl@google.com (Vlad Losev) + +// Type and function utilities for implementing parameterized tests. +// This file is generated by a SCRIPT. DO NOT EDIT BY HAND! +// +// Currently Google Test supports at most 50 arguments in Values, +// and at most 10 arguments in Combine. Please contact +// googletestframework@googlegroups.com if you need more. +// Please note that the number of arguments to Combine is limited +// by the maximum arity of the implementation of tr1::tuple which is +// currently set at 10. + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_GENERATED_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_GENERATED_H_ + +// scripts/fuse_gtest.py depends on gtest's own header being #included +// *unconditionally*. Therefore these #includes cannot be moved +// inside #if GTEST_HAS_PARAM_TEST. +#include "gtest/internal/gtest-param-util.h" +#include "gtest/internal/gtest-port.h" + +#if GTEST_HAS_PARAM_TEST + +namespace testing { + +// Forward declarations of ValuesIn(), which is implemented in +// include/gtest/gtest-param-test.h. +template +internal::ParamGenerator< + typename ::testing::internal::IteratorTraits::value_type> +ValuesIn(ForwardIterator begin, ForwardIterator end); + +template +internal::ParamGenerator ValuesIn(const T (&array)[N]); + +template +internal::ParamGenerator ValuesIn( + const Container& container); + +namespace internal { + +// Used in the Values() function to provide polymorphic capabilities. +template +class ValueArray1 { + public: + explicit ValueArray1(T1 v1) : v1_(v1) {} + + template + operator ParamGenerator() const { return ValuesIn(&v1_, &v1_ + 1); } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray1& other); + + const T1 v1_; +}; + +template +class ValueArray2 { + public: + ValueArray2(T1 v1, T2 v2) : v1_(v1), v2_(v2) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray2& other); + + const T1 v1_; + const T2 v2_; +}; + +template +class ValueArray3 { + public: + ValueArray3(T1 v1, T2 v2, T3 v3) : v1_(v1), v2_(v2), v3_(v3) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray3& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; +}; + +template +class ValueArray4 { + public: + ValueArray4(T1 v1, T2 v2, T3 v3, T4 v4) : v1_(v1), v2_(v2), v3_(v3), + v4_(v4) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_), static_cast(v4_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray4& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; +}; + +template +class ValueArray5 { + public: + ValueArray5(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5) : v1_(v1), v2_(v2), v3_(v3), + v4_(v4), v5_(v5) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_), static_cast(v4_), static_cast(v5_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray5& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; +}; + +template +class ValueArray6 { + public: + ValueArray6(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6) : v1_(v1), v2_(v2), + v3_(v3), v4_(v4), v5_(v5), v6_(v6) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_), static_cast(v4_), static_cast(v5_), + static_cast(v6_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray6& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; +}; + +template +class ValueArray7 { + public: + ValueArray7(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7) : v1_(v1), + v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_), static_cast(v4_), static_cast(v5_), + static_cast(v6_), static_cast(v7_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray7& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; +}; + +template +class ValueArray8 { + public: + ValueArray8(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, + T8 v8) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_), static_cast(v4_), static_cast(v5_), + static_cast(v6_), static_cast(v7_), static_cast(v8_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray8& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; +}; + +template +class ValueArray9 { + public: + ValueArray9(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, + T9 v9) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_), static_cast(v4_), static_cast(v5_), + static_cast(v6_), static_cast(v7_), static_cast(v8_), + static_cast(v9_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray9& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; +}; + +template +class ValueArray10 { + public: + ValueArray10(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9), v10_(v10) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_), static_cast(v4_), static_cast(v5_), + static_cast(v6_), static_cast(v7_), static_cast(v8_), + static_cast(v9_), static_cast(v10_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray10& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; +}; + +template +class ValueArray11 { + public: + ValueArray11(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), + v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_), static_cast(v4_), static_cast(v5_), + static_cast(v6_), static_cast(v7_), static_cast(v8_), + static_cast(v9_), static_cast(v10_), static_cast(v11_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray11& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; +}; + +template +class ValueArray12 { + public: + ValueArray12(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), + v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_), static_cast(v4_), static_cast(v5_), + static_cast(v6_), static_cast(v7_), static_cast(v8_), + static_cast(v9_), static_cast(v10_), static_cast(v11_), + static_cast(v12_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray12& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; +}; + +template +class ValueArray13 { + public: + ValueArray13(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), + v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), + v12_(v12), v13_(v13) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_), static_cast(v4_), static_cast(v5_), + static_cast(v6_), static_cast(v7_), static_cast(v8_), + static_cast(v9_), static_cast(v10_), static_cast(v11_), + static_cast(v12_), static_cast(v13_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray13& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; +}; + +template +class ValueArray14 { + public: + ValueArray14(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14) : v1_(v1), v2_(v2), v3_(v3), + v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), + v11_(v11), v12_(v12), v13_(v13), v14_(v14) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_), static_cast(v4_), static_cast(v5_), + static_cast(v6_), static_cast(v7_), static_cast(v8_), + static_cast(v9_), static_cast(v10_), static_cast(v11_), + static_cast(v12_), static_cast(v13_), static_cast(v14_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray14& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; +}; + +template +class ValueArray15 { + public: + ValueArray15(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15) : v1_(v1), v2_(v2), + v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), + v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_), static_cast(v4_), static_cast(v5_), + static_cast(v6_), static_cast(v7_), static_cast(v8_), + static_cast(v9_), static_cast(v10_), static_cast(v11_), + static_cast(v12_), static_cast(v13_), static_cast(v14_), + static_cast(v15_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray15& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; +}; + +template +class ValueArray16 { + public: + ValueArray16(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16) : v1_(v1), + v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), + v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), + v16_(v16) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_), static_cast(v4_), static_cast(v5_), + static_cast(v6_), static_cast(v7_), static_cast(v8_), + static_cast(v9_), static_cast(v10_), static_cast(v11_), + static_cast(v12_), static_cast(v13_), static_cast(v14_), + static_cast(v15_), static_cast(v16_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray16& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; +}; + +template +class ValueArray17 { + public: + ValueArray17(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, + T17 v17) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), + v15_(v15), v16_(v16), v17_(v17) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_), static_cast(v4_), static_cast(v5_), + static_cast(v6_), static_cast(v7_), static_cast(v8_), + static_cast(v9_), static_cast(v10_), static_cast(v11_), + static_cast(v12_), static_cast(v13_), static_cast(v14_), + static_cast(v15_), static_cast(v16_), static_cast(v17_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray17& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; +}; + +template +class ValueArray18 { + public: + ValueArray18(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), + v15_(v15), v16_(v16), v17_(v17), v18_(v18) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_), static_cast(v4_), static_cast(v5_), + static_cast(v6_), static_cast(v7_), static_cast(v8_), + static_cast(v9_), static_cast(v10_), static_cast(v11_), + static_cast(v12_), static_cast(v13_), static_cast(v14_), + static_cast(v15_), static_cast(v16_), static_cast(v17_), + static_cast(v18_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray18& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; +}; + +template +class ValueArray19 { + public: + ValueArray19(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), + v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), + v14_(v14), v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_), static_cast(v4_), static_cast(v5_), + static_cast(v6_), static_cast(v7_), static_cast(v8_), + static_cast(v9_), static_cast(v10_), static_cast(v11_), + static_cast(v12_), static_cast(v13_), static_cast(v14_), + static_cast(v15_), static_cast(v16_), static_cast(v17_), + static_cast(v18_), static_cast(v19_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray19& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; +}; + +template +class ValueArray20 { + public: + ValueArray20(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), + v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), + v13_(v13), v14_(v14), v15_(v15), v16_(v16), v17_(v17), v18_(v18), + v19_(v19), v20_(v20) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_), static_cast(v4_), static_cast(v5_), + static_cast(v6_), static_cast(v7_), static_cast(v8_), + static_cast(v9_), static_cast(v10_), static_cast(v11_), + static_cast(v12_), static_cast(v13_), static_cast(v14_), + static_cast(v15_), static_cast(v16_), static_cast(v17_), + static_cast(v18_), static_cast(v19_), static_cast(v20_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray20& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; +}; + +template +class ValueArray21 { + public: + ValueArray21(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), + v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), + v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), v17_(v17), + v18_(v18), v19_(v19), v20_(v20), v21_(v21) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_), static_cast(v4_), static_cast(v5_), + static_cast(v6_), static_cast(v7_), static_cast(v8_), + static_cast(v9_), static_cast(v10_), static_cast(v11_), + static_cast(v12_), static_cast(v13_), static_cast(v14_), + static_cast(v15_), static_cast(v16_), static_cast(v17_), + static_cast(v18_), static_cast(v19_), static_cast(v20_), + static_cast(v21_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray21& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; +}; + +template +class ValueArray22 { + public: + ValueArray22(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22) : v1_(v1), v2_(v2), v3_(v3), + v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), + v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), + v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_), static_cast(v4_), static_cast(v5_), + static_cast(v6_), static_cast(v7_), static_cast(v8_), + static_cast(v9_), static_cast(v10_), static_cast(v11_), + static_cast(v12_), static_cast(v13_), static_cast(v14_), + static_cast(v15_), static_cast(v16_), static_cast(v17_), + static_cast(v18_), static_cast(v19_), static_cast(v20_), + static_cast(v21_), static_cast(v22_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray22& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; +}; + +template +class ValueArray23 { + public: + ValueArray23(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23) : v1_(v1), v2_(v2), + v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), + v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), + v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22), + v23_(v23) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_), static_cast(v4_), static_cast(v5_), + static_cast(v6_), static_cast(v7_), static_cast(v8_), + static_cast(v9_), static_cast(v10_), static_cast(v11_), + static_cast(v12_), static_cast(v13_), static_cast(v14_), + static_cast(v15_), static_cast(v16_), static_cast(v17_), + static_cast(v18_), static_cast(v19_), static_cast(v20_), + static_cast(v21_), static_cast(v22_), static_cast(v23_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray23& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; +}; + +template +class ValueArray24 { + public: + ValueArray24(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24) : v1_(v1), + v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), + v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), + v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), + v22_(v22), v23_(v23), v24_(v24) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_), static_cast(v4_), static_cast(v5_), + static_cast(v6_), static_cast(v7_), static_cast(v8_), + static_cast(v9_), static_cast(v10_), static_cast(v11_), + static_cast(v12_), static_cast(v13_), static_cast(v14_), + static_cast(v15_), static_cast(v16_), static_cast(v17_), + static_cast(v18_), static_cast(v19_), static_cast(v20_), + static_cast(v21_), static_cast(v22_), static_cast(v23_), + static_cast(v24_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray24& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; +}; + +template +class ValueArray25 { + public: + ValueArray25(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, + T25 v25) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), + v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), + v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_), static_cast(v4_), static_cast(v5_), + static_cast(v6_), static_cast(v7_), static_cast(v8_), + static_cast(v9_), static_cast(v10_), static_cast(v11_), + static_cast(v12_), static_cast(v13_), static_cast(v14_), + static_cast(v15_), static_cast(v16_), static_cast(v17_), + static_cast(v18_), static_cast(v19_), static_cast(v20_), + static_cast(v21_), static_cast(v22_), static_cast(v23_), + static_cast(v24_), static_cast(v25_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray25& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; +}; + +template +class ValueArray26 { + public: + ValueArray26(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), + v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), + v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_), static_cast(v4_), static_cast(v5_), + static_cast(v6_), static_cast(v7_), static_cast(v8_), + static_cast(v9_), static_cast(v10_), static_cast(v11_), + static_cast(v12_), static_cast(v13_), static_cast(v14_), + static_cast(v15_), static_cast(v16_), static_cast(v17_), + static_cast(v18_), static_cast(v19_), static_cast(v20_), + static_cast(v21_), static_cast(v22_), static_cast(v23_), + static_cast(v24_), static_cast(v25_), static_cast(v26_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray26& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; +}; + +template +class ValueArray27 { + public: + ValueArray27(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), + v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), + v14_(v14), v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), + v20_(v20), v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25), + v26_(v26), v27_(v27) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_), static_cast(v4_), static_cast(v5_), + static_cast(v6_), static_cast(v7_), static_cast(v8_), + static_cast(v9_), static_cast(v10_), static_cast(v11_), + static_cast(v12_), static_cast(v13_), static_cast(v14_), + static_cast(v15_), static_cast(v16_), static_cast(v17_), + static_cast(v18_), static_cast(v19_), static_cast(v20_), + static_cast(v21_), static_cast(v22_), static_cast(v23_), + static_cast(v24_), static_cast(v25_), static_cast(v26_), + static_cast(v27_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray27& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; +}; + +template +class ValueArray28 { + public: + ValueArray28(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), + v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), + v13_(v13), v14_(v14), v15_(v15), v16_(v16), v17_(v17), v18_(v18), + v19_(v19), v20_(v20), v21_(v21), v22_(v22), v23_(v23), v24_(v24), + v25_(v25), v26_(v26), v27_(v27), v28_(v28) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_), static_cast(v4_), static_cast(v5_), + static_cast(v6_), static_cast(v7_), static_cast(v8_), + static_cast(v9_), static_cast(v10_), static_cast(v11_), + static_cast(v12_), static_cast(v13_), static_cast(v14_), + static_cast(v15_), static_cast(v16_), static_cast(v17_), + static_cast(v18_), static_cast(v19_), static_cast(v20_), + static_cast(v21_), static_cast(v22_), static_cast(v23_), + static_cast(v24_), static_cast(v25_), static_cast(v26_), + static_cast(v27_), static_cast(v28_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray28& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; +}; + +template +class ValueArray29 { + public: + ValueArray29(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), + v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), + v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), v17_(v17), + v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22), v23_(v23), + v24_(v24), v25_(v25), v26_(v26), v27_(v27), v28_(v28), v29_(v29) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_), static_cast(v4_), static_cast(v5_), + static_cast(v6_), static_cast(v7_), static_cast(v8_), + static_cast(v9_), static_cast(v10_), static_cast(v11_), + static_cast(v12_), static_cast(v13_), static_cast(v14_), + static_cast(v15_), static_cast(v16_), static_cast(v17_), + static_cast(v18_), static_cast(v19_), static_cast(v20_), + static_cast(v21_), static_cast(v22_), static_cast(v23_), + static_cast(v24_), static_cast(v25_), static_cast(v26_), + static_cast(v27_), static_cast(v28_), static_cast(v29_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray29& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; +}; + +template +class ValueArray30 { + public: + ValueArray30(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30) : v1_(v1), v2_(v2), v3_(v3), + v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), + v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), + v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22), + v23_(v23), v24_(v24), v25_(v25), v26_(v26), v27_(v27), v28_(v28), + v29_(v29), v30_(v30) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_), static_cast(v4_), static_cast(v5_), + static_cast(v6_), static_cast(v7_), static_cast(v8_), + static_cast(v9_), static_cast(v10_), static_cast(v11_), + static_cast(v12_), static_cast(v13_), static_cast(v14_), + static_cast(v15_), static_cast(v16_), static_cast(v17_), + static_cast(v18_), static_cast(v19_), static_cast(v20_), + static_cast(v21_), static_cast(v22_), static_cast(v23_), + static_cast(v24_), static_cast(v25_), static_cast(v26_), + static_cast(v27_), static_cast(v28_), static_cast(v29_), + static_cast(v30_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray30& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; +}; + +template +class ValueArray31 { + public: + ValueArray31(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31) : v1_(v1), v2_(v2), + v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), + v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), + v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22), + v23_(v23), v24_(v24), v25_(v25), v26_(v26), v27_(v27), v28_(v28), + v29_(v29), v30_(v30), v31_(v31) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_), static_cast(v4_), static_cast(v5_), + static_cast(v6_), static_cast(v7_), static_cast(v8_), + static_cast(v9_), static_cast(v10_), static_cast(v11_), + static_cast(v12_), static_cast(v13_), static_cast(v14_), + static_cast(v15_), static_cast(v16_), static_cast(v17_), + static_cast(v18_), static_cast(v19_), static_cast(v20_), + static_cast(v21_), static_cast(v22_), static_cast(v23_), + static_cast(v24_), static_cast(v25_), static_cast(v26_), + static_cast(v27_), static_cast(v28_), static_cast(v29_), + static_cast(v30_), static_cast(v31_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray31& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; +}; + +template +class ValueArray32 { + public: + ValueArray32(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32) : v1_(v1), + v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), + v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), + v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), + v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26), v27_(v27), + v28_(v28), v29_(v29), v30_(v30), v31_(v31), v32_(v32) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_), static_cast(v4_), static_cast(v5_), + static_cast(v6_), static_cast(v7_), static_cast(v8_), + static_cast(v9_), static_cast(v10_), static_cast(v11_), + static_cast(v12_), static_cast(v13_), static_cast(v14_), + static_cast(v15_), static_cast(v16_), static_cast(v17_), + static_cast(v18_), static_cast(v19_), static_cast(v20_), + static_cast(v21_), static_cast(v22_), static_cast(v23_), + static_cast(v24_), static_cast(v25_), static_cast(v26_), + static_cast(v27_), static_cast(v28_), static_cast(v29_), + static_cast(v30_), static_cast(v31_), static_cast(v32_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray32& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; +}; + +template +class ValueArray33 { + public: + ValueArray33(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, + T33 v33) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), + v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), + v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26), + v27_(v27), v28_(v28), v29_(v29), v30_(v30), v31_(v31), v32_(v32), + v33_(v33) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_), static_cast(v4_), static_cast(v5_), + static_cast(v6_), static_cast(v7_), static_cast(v8_), + static_cast(v9_), static_cast(v10_), static_cast(v11_), + static_cast(v12_), static_cast(v13_), static_cast(v14_), + static_cast(v15_), static_cast(v16_), static_cast(v17_), + static_cast(v18_), static_cast(v19_), static_cast(v20_), + static_cast(v21_), static_cast(v22_), static_cast(v23_), + static_cast(v24_), static_cast(v25_), static_cast(v26_), + static_cast(v27_), static_cast(v28_), static_cast(v29_), + static_cast(v30_), static_cast(v31_), static_cast(v32_), + static_cast(v33_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray33& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; +}; + +template +class ValueArray34 { + public: + ValueArray34(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), + v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), + v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26), + v27_(v27), v28_(v28), v29_(v29), v30_(v30), v31_(v31), v32_(v32), + v33_(v33), v34_(v34) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_), static_cast(v4_), static_cast(v5_), + static_cast(v6_), static_cast(v7_), static_cast(v8_), + static_cast(v9_), static_cast(v10_), static_cast(v11_), + static_cast(v12_), static_cast(v13_), static_cast(v14_), + static_cast(v15_), static_cast(v16_), static_cast(v17_), + static_cast(v18_), static_cast(v19_), static_cast(v20_), + static_cast(v21_), static_cast(v22_), static_cast(v23_), + static_cast(v24_), static_cast(v25_), static_cast(v26_), + static_cast(v27_), static_cast(v28_), static_cast(v29_), + static_cast(v30_), static_cast(v31_), static_cast(v32_), + static_cast(v33_), static_cast(v34_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray34& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; +}; + +template +class ValueArray35 { + public: + ValueArray35(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), + v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), + v14_(v14), v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), + v20_(v20), v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25), + v26_(v26), v27_(v27), v28_(v28), v29_(v29), v30_(v30), v31_(v31), + v32_(v32), v33_(v33), v34_(v34), v35_(v35) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_), static_cast(v4_), static_cast(v5_), + static_cast(v6_), static_cast(v7_), static_cast(v8_), + static_cast(v9_), static_cast(v10_), static_cast(v11_), + static_cast(v12_), static_cast(v13_), static_cast(v14_), + static_cast(v15_), static_cast(v16_), static_cast(v17_), + static_cast(v18_), static_cast(v19_), static_cast(v20_), + static_cast(v21_), static_cast(v22_), static_cast(v23_), + static_cast(v24_), static_cast(v25_), static_cast(v26_), + static_cast(v27_), static_cast(v28_), static_cast(v29_), + static_cast(v30_), static_cast(v31_), static_cast(v32_), + static_cast(v33_), static_cast(v34_), static_cast(v35_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray35& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; +}; + +template +class ValueArray36 { + public: + ValueArray36(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), + v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), + v13_(v13), v14_(v14), v15_(v15), v16_(v16), v17_(v17), v18_(v18), + v19_(v19), v20_(v20), v21_(v21), v22_(v22), v23_(v23), v24_(v24), + v25_(v25), v26_(v26), v27_(v27), v28_(v28), v29_(v29), v30_(v30), + v31_(v31), v32_(v32), v33_(v33), v34_(v34), v35_(v35), v36_(v36) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_), static_cast(v4_), static_cast(v5_), + static_cast(v6_), static_cast(v7_), static_cast(v8_), + static_cast(v9_), static_cast(v10_), static_cast(v11_), + static_cast(v12_), static_cast(v13_), static_cast(v14_), + static_cast(v15_), static_cast(v16_), static_cast(v17_), + static_cast(v18_), static_cast(v19_), static_cast(v20_), + static_cast(v21_), static_cast(v22_), static_cast(v23_), + static_cast(v24_), static_cast(v25_), static_cast(v26_), + static_cast(v27_), static_cast(v28_), static_cast(v29_), + static_cast(v30_), static_cast(v31_), static_cast(v32_), + static_cast(v33_), static_cast(v34_), static_cast(v35_), + static_cast(v36_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray36& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; +}; + +template +class ValueArray37 { + public: + ValueArray37(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), + v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), + v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), v17_(v17), + v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22), v23_(v23), + v24_(v24), v25_(v25), v26_(v26), v27_(v27), v28_(v28), v29_(v29), + v30_(v30), v31_(v31), v32_(v32), v33_(v33), v34_(v34), v35_(v35), + v36_(v36), v37_(v37) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_), static_cast(v4_), static_cast(v5_), + static_cast(v6_), static_cast(v7_), static_cast(v8_), + static_cast(v9_), static_cast(v10_), static_cast(v11_), + static_cast(v12_), static_cast(v13_), static_cast(v14_), + static_cast(v15_), static_cast(v16_), static_cast(v17_), + static_cast(v18_), static_cast(v19_), static_cast(v20_), + static_cast(v21_), static_cast(v22_), static_cast(v23_), + static_cast(v24_), static_cast(v25_), static_cast(v26_), + static_cast(v27_), static_cast(v28_), static_cast(v29_), + static_cast(v30_), static_cast(v31_), static_cast(v32_), + static_cast(v33_), static_cast(v34_), static_cast(v35_), + static_cast(v36_), static_cast(v37_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray37& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; +}; + +template +class ValueArray38 { + public: + ValueArray38(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38) : v1_(v1), v2_(v2), v3_(v3), + v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), + v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), + v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22), + v23_(v23), v24_(v24), v25_(v25), v26_(v26), v27_(v27), v28_(v28), + v29_(v29), v30_(v30), v31_(v31), v32_(v32), v33_(v33), v34_(v34), + v35_(v35), v36_(v36), v37_(v37), v38_(v38) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_), static_cast(v4_), static_cast(v5_), + static_cast(v6_), static_cast(v7_), static_cast(v8_), + static_cast(v9_), static_cast(v10_), static_cast(v11_), + static_cast(v12_), static_cast(v13_), static_cast(v14_), + static_cast(v15_), static_cast(v16_), static_cast(v17_), + static_cast(v18_), static_cast(v19_), static_cast(v20_), + static_cast(v21_), static_cast(v22_), static_cast(v23_), + static_cast(v24_), static_cast(v25_), static_cast(v26_), + static_cast(v27_), static_cast(v28_), static_cast(v29_), + static_cast(v30_), static_cast(v31_), static_cast(v32_), + static_cast(v33_), static_cast(v34_), static_cast(v35_), + static_cast(v36_), static_cast(v37_), static_cast(v38_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray38& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; +}; + +template +class ValueArray39 { + public: + ValueArray39(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39) : v1_(v1), v2_(v2), + v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), + v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), + v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22), + v23_(v23), v24_(v24), v25_(v25), v26_(v26), v27_(v27), v28_(v28), + v29_(v29), v30_(v30), v31_(v31), v32_(v32), v33_(v33), v34_(v34), + v35_(v35), v36_(v36), v37_(v37), v38_(v38), v39_(v39) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_), static_cast(v4_), static_cast(v5_), + static_cast(v6_), static_cast(v7_), static_cast(v8_), + static_cast(v9_), static_cast(v10_), static_cast(v11_), + static_cast(v12_), static_cast(v13_), static_cast(v14_), + static_cast(v15_), static_cast(v16_), static_cast(v17_), + static_cast(v18_), static_cast(v19_), static_cast(v20_), + static_cast(v21_), static_cast(v22_), static_cast(v23_), + static_cast(v24_), static_cast(v25_), static_cast(v26_), + static_cast(v27_), static_cast(v28_), static_cast(v29_), + static_cast(v30_), static_cast(v31_), static_cast(v32_), + static_cast(v33_), static_cast(v34_), static_cast(v35_), + static_cast(v36_), static_cast(v37_), static_cast(v38_), + static_cast(v39_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray39& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; +}; + +template +class ValueArray40 { + public: + ValueArray40(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40) : v1_(v1), + v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), + v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), + v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), + v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26), v27_(v27), + v28_(v28), v29_(v29), v30_(v30), v31_(v31), v32_(v32), v33_(v33), + v34_(v34), v35_(v35), v36_(v36), v37_(v37), v38_(v38), v39_(v39), + v40_(v40) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_), static_cast(v4_), static_cast(v5_), + static_cast(v6_), static_cast(v7_), static_cast(v8_), + static_cast(v9_), static_cast(v10_), static_cast(v11_), + static_cast(v12_), static_cast(v13_), static_cast(v14_), + static_cast(v15_), static_cast(v16_), static_cast(v17_), + static_cast(v18_), static_cast(v19_), static_cast(v20_), + static_cast(v21_), static_cast(v22_), static_cast(v23_), + static_cast(v24_), static_cast(v25_), static_cast(v26_), + static_cast(v27_), static_cast(v28_), static_cast(v29_), + static_cast(v30_), static_cast(v31_), static_cast(v32_), + static_cast(v33_), static_cast(v34_), static_cast(v35_), + static_cast(v36_), static_cast(v37_), static_cast(v38_), + static_cast(v39_), static_cast(v40_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray40& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; + const T40 v40_; +}; + +template +class ValueArray41 { + public: + ValueArray41(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, + T41 v41) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), + v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), + v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26), + v27_(v27), v28_(v28), v29_(v29), v30_(v30), v31_(v31), v32_(v32), + v33_(v33), v34_(v34), v35_(v35), v36_(v36), v37_(v37), v38_(v38), + v39_(v39), v40_(v40), v41_(v41) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_), static_cast(v4_), static_cast(v5_), + static_cast(v6_), static_cast(v7_), static_cast(v8_), + static_cast(v9_), static_cast(v10_), static_cast(v11_), + static_cast(v12_), static_cast(v13_), static_cast(v14_), + static_cast(v15_), static_cast(v16_), static_cast(v17_), + static_cast(v18_), static_cast(v19_), static_cast(v20_), + static_cast(v21_), static_cast(v22_), static_cast(v23_), + static_cast(v24_), static_cast(v25_), static_cast(v26_), + static_cast(v27_), static_cast(v28_), static_cast(v29_), + static_cast(v30_), static_cast(v31_), static_cast(v32_), + static_cast(v33_), static_cast(v34_), static_cast(v35_), + static_cast(v36_), static_cast(v37_), static_cast(v38_), + static_cast(v39_), static_cast(v40_), static_cast(v41_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray41& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; + const T40 v40_; + const T41 v41_; +}; + +template +class ValueArray42 { + public: + ValueArray42(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), + v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), + v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26), + v27_(v27), v28_(v28), v29_(v29), v30_(v30), v31_(v31), v32_(v32), + v33_(v33), v34_(v34), v35_(v35), v36_(v36), v37_(v37), v38_(v38), + v39_(v39), v40_(v40), v41_(v41), v42_(v42) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_), static_cast(v4_), static_cast(v5_), + static_cast(v6_), static_cast(v7_), static_cast(v8_), + static_cast(v9_), static_cast(v10_), static_cast(v11_), + static_cast(v12_), static_cast(v13_), static_cast(v14_), + static_cast(v15_), static_cast(v16_), static_cast(v17_), + static_cast(v18_), static_cast(v19_), static_cast(v20_), + static_cast(v21_), static_cast(v22_), static_cast(v23_), + static_cast(v24_), static_cast(v25_), static_cast(v26_), + static_cast(v27_), static_cast(v28_), static_cast(v29_), + static_cast(v30_), static_cast(v31_), static_cast(v32_), + static_cast(v33_), static_cast(v34_), static_cast(v35_), + static_cast(v36_), static_cast(v37_), static_cast(v38_), + static_cast(v39_), static_cast(v40_), static_cast(v41_), + static_cast(v42_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray42& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; + const T40 v40_; + const T41 v41_; + const T42 v42_; +}; + +template +class ValueArray43 { + public: + ValueArray43(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42, T43 v43) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), + v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), + v14_(v14), v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), + v20_(v20), v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25), + v26_(v26), v27_(v27), v28_(v28), v29_(v29), v30_(v30), v31_(v31), + v32_(v32), v33_(v33), v34_(v34), v35_(v35), v36_(v36), v37_(v37), + v38_(v38), v39_(v39), v40_(v40), v41_(v41), v42_(v42), v43_(v43) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_), static_cast(v4_), static_cast(v5_), + static_cast(v6_), static_cast(v7_), static_cast(v8_), + static_cast(v9_), static_cast(v10_), static_cast(v11_), + static_cast(v12_), static_cast(v13_), static_cast(v14_), + static_cast(v15_), static_cast(v16_), static_cast(v17_), + static_cast(v18_), static_cast(v19_), static_cast(v20_), + static_cast(v21_), static_cast(v22_), static_cast(v23_), + static_cast(v24_), static_cast(v25_), static_cast(v26_), + static_cast(v27_), static_cast(v28_), static_cast(v29_), + static_cast(v30_), static_cast(v31_), static_cast(v32_), + static_cast(v33_), static_cast(v34_), static_cast(v35_), + static_cast(v36_), static_cast(v37_), static_cast(v38_), + static_cast(v39_), static_cast(v40_), static_cast(v41_), + static_cast(v42_), static_cast(v43_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray43& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; + const T40 v40_; + const T41 v41_; + const T42 v42_; + const T43 v43_; +}; + +template +class ValueArray44 { + public: + ValueArray44(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42, T43 v43, T44 v44) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), + v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), + v13_(v13), v14_(v14), v15_(v15), v16_(v16), v17_(v17), v18_(v18), + v19_(v19), v20_(v20), v21_(v21), v22_(v22), v23_(v23), v24_(v24), + v25_(v25), v26_(v26), v27_(v27), v28_(v28), v29_(v29), v30_(v30), + v31_(v31), v32_(v32), v33_(v33), v34_(v34), v35_(v35), v36_(v36), + v37_(v37), v38_(v38), v39_(v39), v40_(v40), v41_(v41), v42_(v42), + v43_(v43), v44_(v44) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_), static_cast(v4_), static_cast(v5_), + static_cast(v6_), static_cast(v7_), static_cast(v8_), + static_cast(v9_), static_cast(v10_), static_cast(v11_), + static_cast(v12_), static_cast(v13_), static_cast(v14_), + static_cast(v15_), static_cast(v16_), static_cast(v17_), + static_cast(v18_), static_cast(v19_), static_cast(v20_), + static_cast(v21_), static_cast(v22_), static_cast(v23_), + static_cast(v24_), static_cast(v25_), static_cast(v26_), + static_cast(v27_), static_cast(v28_), static_cast(v29_), + static_cast(v30_), static_cast(v31_), static_cast(v32_), + static_cast(v33_), static_cast(v34_), static_cast(v35_), + static_cast(v36_), static_cast(v37_), static_cast(v38_), + static_cast(v39_), static_cast(v40_), static_cast(v41_), + static_cast(v42_), static_cast(v43_), static_cast(v44_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray44& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; + const T40 v40_; + const T41 v41_; + const T42 v42_; + const T43 v43_; + const T44 v44_; +}; + +template +class ValueArray45 { + public: + ValueArray45(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42, T43 v43, T44 v44, T45 v45) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), + v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), + v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), v17_(v17), + v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22), v23_(v23), + v24_(v24), v25_(v25), v26_(v26), v27_(v27), v28_(v28), v29_(v29), + v30_(v30), v31_(v31), v32_(v32), v33_(v33), v34_(v34), v35_(v35), + v36_(v36), v37_(v37), v38_(v38), v39_(v39), v40_(v40), v41_(v41), + v42_(v42), v43_(v43), v44_(v44), v45_(v45) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_), static_cast(v4_), static_cast(v5_), + static_cast(v6_), static_cast(v7_), static_cast(v8_), + static_cast(v9_), static_cast(v10_), static_cast(v11_), + static_cast(v12_), static_cast(v13_), static_cast(v14_), + static_cast(v15_), static_cast(v16_), static_cast(v17_), + static_cast(v18_), static_cast(v19_), static_cast(v20_), + static_cast(v21_), static_cast(v22_), static_cast(v23_), + static_cast(v24_), static_cast(v25_), static_cast(v26_), + static_cast(v27_), static_cast(v28_), static_cast(v29_), + static_cast(v30_), static_cast(v31_), static_cast(v32_), + static_cast(v33_), static_cast(v34_), static_cast(v35_), + static_cast(v36_), static_cast(v37_), static_cast(v38_), + static_cast(v39_), static_cast(v40_), static_cast(v41_), + static_cast(v42_), static_cast(v43_), static_cast(v44_), + static_cast(v45_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray45& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; + const T40 v40_; + const T41 v41_; + const T42 v42_; + const T43 v43_; + const T44 v44_; + const T45 v45_; +}; + +template +class ValueArray46 { + public: + ValueArray46(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42, T43 v43, T44 v44, T45 v45, T46 v46) : v1_(v1), v2_(v2), v3_(v3), + v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), + v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), + v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22), + v23_(v23), v24_(v24), v25_(v25), v26_(v26), v27_(v27), v28_(v28), + v29_(v29), v30_(v30), v31_(v31), v32_(v32), v33_(v33), v34_(v34), + v35_(v35), v36_(v36), v37_(v37), v38_(v38), v39_(v39), v40_(v40), + v41_(v41), v42_(v42), v43_(v43), v44_(v44), v45_(v45), v46_(v46) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_), static_cast(v4_), static_cast(v5_), + static_cast(v6_), static_cast(v7_), static_cast(v8_), + static_cast(v9_), static_cast(v10_), static_cast(v11_), + static_cast(v12_), static_cast(v13_), static_cast(v14_), + static_cast(v15_), static_cast(v16_), static_cast(v17_), + static_cast(v18_), static_cast(v19_), static_cast(v20_), + static_cast(v21_), static_cast(v22_), static_cast(v23_), + static_cast(v24_), static_cast(v25_), static_cast(v26_), + static_cast(v27_), static_cast(v28_), static_cast(v29_), + static_cast(v30_), static_cast(v31_), static_cast(v32_), + static_cast(v33_), static_cast(v34_), static_cast(v35_), + static_cast(v36_), static_cast(v37_), static_cast(v38_), + static_cast(v39_), static_cast(v40_), static_cast(v41_), + static_cast(v42_), static_cast(v43_), static_cast(v44_), + static_cast(v45_), static_cast(v46_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray46& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; + const T40 v40_; + const T41 v41_; + const T42 v42_; + const T43 v43_; + const T44 v44_; + const T45 v45_; + const T46 v46_; +}; + +template +class ValueArray47 { + public: + ValueArray47(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42, T43 v43, T44 v44, T45 v45, T46 v46, T47 v47) : v1_(v1), v2_(v2), + v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), + v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), + v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22), + v23_(v23), v24_(v24), v25_(v25), v26_(v26), v27_(v27), v28_(v28), + v29_(v29), v30_(v30), v31_(v31), v32_(v32), v33_(v33), v34_(v34), + v35_(v35), v36_(v36), v37_(v37), v38_(v38), v39_(v39), v40_(v40), + v41_(v41), v42_(v42), v43_(v43), v44_(v44), v45_(v45), v46_(v46), + v47_(v47) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_), static_cast(v4_), static_cast(v5_), + static_cast(v6_), static_cast(v7_), static_cast(v8_), + static_cast(v9_), static_cast(v10_), static_cast(v11_), + static_cast(v12_), static_cast(v13_), static_cast(v14_), + static_cast(v15_), static_cast(v16_), static_cast(v17_), + static_cast(v18_), static_cast(v19_), static_cast(v20_), + static_cast(v21_), static_cast(v22_), static_cast(v23_), + static_cast(v24_), static_cast(v25_), static_cast(v26_), + static_cast(v27_), static_cast(v28_), static_cast(v29_), + static_cast(v30_), static_cast(v31_), static_cast(v32_), + static_cast(v33_), static_cast(v34_), static_cast(v35_), + static_cast(v36_), static_cast(v37_), static_cast(v38_), + static_cast(v39_), static_cast(v40_), static_cast(v41_), + static_cast(v42_), static_cast(v43_), static_cast(v44_), + static_cast(v45_), static_cast(v46_), static_cast(v47_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray47& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; + const T40 v40_; + const T41 v41_; + const T42 v42_; + const T43 v43_; + const T44 v44_; + const T45 v45_; + const T46 v46_; + const T47 v47_; +}; + +template +class ValueArray48 { + public: + ValueArray48(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42, T43 v43, T44 v44, T45 v45, T46 v46, T47 v47, T48 v48) : v1_(v1), + v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), + v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), + v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), + v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26), v27_(v27), + v28_(v28), v29_(v29), v30_(v30), v31_(v31), v32_(v32), v33_(v33), + v34_(v34), v35_(v35), v36_(v36), v37_(v37), v38_(v38), v39_(v39), + v40_(v40), v41_(v41), v42_(v42), v43_(v43), v44_(v44), v45_(v45), + v46_(v46), v47_(v47), v48_(v48) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_), static_cast(v4_), static_cast(v5_), + static_cast(v6_), static_cast(v7_), static_cast(v8_), + static_cast(v9_), static_cast(v10_), static_cast(v11_), + static_cast(v12_), static_cast(v13_), static_cast(v14_), + static_cast(v15_), static_cast(v16_), static_cast(v17_), + static_cast(v18_), static_cast(v19_), static_cast(v20_), + static_cast(v21_), static_cast(v22_), static_cast(v23_), + static_cast(v24_), static_cast(v25_), static_cast(v26_), + static_cast(v27_), static_cast(v28_), static_cast(v29_), + static_cast(v30_), static_cast(v31_), static_cast(v32_), + static_cast(v33_), static_cast(v34_), static_cast(v35_), + static_cast(v36_), static_cast(v37_), static_cast(v38_), + static_cast(v39_), static_cast(v40_), static_cast(v41_), + static_cast(v42_), static_cast(v43_), static_cast(v44_), + static_cast(v45_), static_cast(v46_), static_cast(v47_), + static_cast(v48_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray48& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; + const T40 v40_; + const T41 v41_; + const T42 v42_; + const T43 v43_; + const T44 v44_; + const T45 v45_; + const T46 v46_; + const T47 v47_; + const T48 v48_; +}; + +template +class ValueArray49 { + public: + ValueArray49(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42, T43 v43, T44 v44, T45 v45, T46 v46, T47 v47, T48 v48, + T49 v49) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), + v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), + v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26), + v27_(v27), v28_(v28), v29_(v29), v30_(v30), v31_(v31), v32_(v32), + v33_(v33), v34_(v34), v35_(v35), v36_(v36), v37_(v37), v38_(v38), + v39_(v39), v40_(v40), v41_(v41), v42_(v42), v43_(v43), v44_(v44), + v45_(v45), v46_(v46), v47_(v47), v48_(v48), v49_(v49) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_), static_cast(v4_), static_cast(v5_), + static_cast(v6_), static_cast(v7_), static_cast(v8_), + static_cast(v9_), static_cast(v10_), static_cast(v11_), + static_cast(v12_), static_cast(v13_), static_cast(v14_), + static_cast(v15_), static_cast(v16_), static_cast(v17_), + static_cast(v18_), static_cast(v19_), static_cast(v20_), + static_cast(v21_), static_cast(v22_), static_cast(v23_), + static_cast(v24_), static_cast(v25_), static_cast(v26_), + static_cast(v27_), static_cast(v28_), static_cast(v29_), + static_cast(v30_), static_cast(v31_), static_cast(v32_), + static_cast(v33_), static_cast(v34_), static_cast(v35_), + static_cast(v36_), static_cast(v37_), static_cast(v38_), + static_cast(v39_), static_cast(v40_), static_cast(v41_), + static_cast(v42_), static_cast(v43_), static_cast(v44_), + static_cast(v45_), static_cast(v46_), static_cast(v47_), + static_cast(v48_), static_cast(v49_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray49& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; + const T40 v40_; + const T41 v41_; + const T42 v42_; + const T43 v43_; + const T44 v44_; + const T45 v45_; + const T46 v46_; + const T47 v47_; + const T48 v48_; + const T49 v49_; +}; + +template +class ValueArray50 { + public: + ValueArray50(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42, T43 v43, T44 v44, T45 v45, T46 v46, T47 v47, T48 v48, T49 v49, + T50 v50) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), + v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), + v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26), + v27_(v27), v28_(v28), v29_(v29), v30_(v30), v31_(v31), v32_(v32), + v33_(v33), v34_(v34), v35_(v35), v36_(v36), v37_(v37), v38_(v38), + v39_(v39), v40_(v40), v41_(v41), v42_(v42), v43_(v43), v44_(v44), + v45_(v45), v46_(v46), v47_(v47), v48_(v48), v49_(v49), v50_(v50) {} + + template + operator ParamGenerator() const { + const T array[] = {static_cast(v1_), static_cast(v2_), + static_cast(v3_), static_cast(v4_), static_cast(v5_), + static_cast(v6_), static_cast(v7_), static_cast(v8_), + static_cast(v9_), static_cast(v10_), static_cast(v11_), + static_cast(v12_), static_cast(v13_), static_cast(v14_), + static_cast(v15_), static_cast(v16_), static_cast(v17_), + static_cast(v18_), static_cast(v19_), static_cast(v20_), + static_cast(v21_), static_cast(v22_), static_cast(v23_), + static_cast(v24_), static_cast(v25_), static_cast(v26_), + static_cast(v27_), static_cast(v28_), static_cast(v29_), + static_cast(v30_), static_cast(v31_), static_cast(v32_), + static_cast(v33_), static_cast(v34_), static_cast(v35_), + static_cast(v36_), static_cast(v37_), static_cast(v38_), + static_cast(v39_), static_cast(v40_), static_cast(v41_), + static_cast(v42_), static_cast(v43_), static_cast(v44_), + static_cast(v45_), static_cast(v46_), static_cast(v47_), + static_cast(v48_), static_cast(v49_), static_cast(v50_)}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray50& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; + const T40 v40_; + const T41 v41_; + const T42 v42_; + const T43 v43_; + const T44 v44_; + const T45 v45_; + const T46 v46_; + const T47 v47_; + const T48 v48_; + const T49 v49_; + const T50 v50_; +}; + +# if GTEST_HAS_COMBINE +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Generates values from the Cartesian product of values produced +// by the argument generators. +// +template +class CartesianProductGenerator2 + : public ParamGeneratorInterface< ::std::tr1::tuple > { + public: + typedef ::std::tr1::tuple ParamType; + + CartesianProductGenerator2(const ParamGenerator& g1, + const ParamGenerator& g2) + : g1_(g1), g2_(g2) {} + virtual ~CartesianProductGenerator2() {} + + virtual ParamIteratorInterface* Begin() const { + return new Iterator(this, g1_, g1_.begin(), g2_, g2_.begin()); + } + virtual ParamIteratorInterface* End() const { + return new Iterator(this, g1_, g1_.end(), g2_, g2_.end()); + } + + private: + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, + const ParamGenerator& g1, + const typename ParamGenerator::iterator& current1, + const ParamGenerator& g2, + const typename ParamGenerator::iterator& current2) + : base_(base), + begin1_(g1.begin()), end1_(g1.end()), current1_(current1), + begin2_(g2.begin()), end2_(g2.end()), current2_(current2) { + ComputeCurrentValue(); + } + virtual ~Iterator() {} + + virtual const ParamGeneratorInterface* BaseGenerator() const { + return base_; + } + // Advance should not be called on beyond-of-range iterators + // so no component iterators must be beyond end of range, either. + virtual void Advance() { + assert(!AtEnd()); + ++current2_; + if (current2_ == end2_) { + current2_ = begin2_; + ++current1_; + } + ComputeCurrentValue(); + } + virtual ParamIteratorInterface* Clone() const { + return new Iterator(*this); + } + virtual const ParamType* Current() const { return ¤t_value_; } + virtual bool Equals(const ParamIteratorInterface& other) const { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const Iterator* typed_other = + CheckedDowncastToActualType(&other); + // We must report iterators equal if they both point beyond their + // respective ranges. That can happen in a variety of fashions, + // so we have to consult AtEnd(). + return (AtEnd() && typed_other->AtEnd()) || + ( + current1_ == typed_other->current1_ && + current2_ == typed_other->current2_); + } + + private: + Iterator(const Iterator& other) + : base_(other.base_), + begin1_(other.begin1_), + end1_(other.end1_), + current1_(other.current1_), + begin2_(other.begin2_), + end2_(other.end2_), + current2_(other.current2_) { + ComputeCurrentValue(); + } + + void ComputeCurrentValue() { + if (!AtEnd()) + current_value_ = ParamType(*current1_, *current2_); + } + bool AtEnd() const { + // We must report iterator past the end of the range when either of the + // component iterators has reached the end of its range. + return + current1_ == end1_ || + current2_ == end2_; + } + + // No implementation - assignment is unsupported. + void operator=(const Iterator& other); + + const ParamGeneratorInterface* const base_; + // begin[i]_ and end[i]_ define the i-th range that Iterator traverses. + // current[i]_ is the actual traversing iterator. + const typename ParamGenerator::iterator begin1_; + const typename ParamGenerator::iterator end1_; + typename ParamGenerator::iterator current1_; + const typename ParamGenerator::iterator begin2_; + const typename ParamGenerator::iterator end2_; + typename ParamGenerator::iterator current2_; + ParamType current_value_; + }; // class CartesianProductGenerator2::Iterator + + // No implementation - assignment is unsupported. + void operator=(const CartesianProductGenerator2& other); + + const ParamGenerator g1_; + const ParamGenerator g2_; +}; // class CartesianProductGenerator2 + + +template +class CartesianProductGenerator3 + : public ParamGeneratorInterface< ::std::tr1::tuple > { + public: + typedef ::std::tr1::tuple ParamType; + + CartesianProductGenerator3(const ParamGenerator& g1, + const ParamGenerator& g2, const ParamGenerator& g3) + : g1_(g1), g2_(g2), g3_(g3) {} + virtual ~CartesianProductGenerator3() {} + + virtual ParamIteratorInterface* Begin() const { + return new Iterator(this, g1_, g1_.begin(), g2_, g2_.begin(), g3_, + g3_.begin()); + } + virtual ParamIteratorInterface* End() const { + return new Iterator(this, g1_, g1_.end(), g2_, g2_.end(), g3_, g3_.end()); + } + + private: + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, + const ParamGenerator& g1, + const typename ParamGenerator::iterator& current1, + const ParamGenerator& g2, + const typename ParamGenerator::iterator& current2, + const ParamGenerator& g3, + const typename ParamGenerator::iterator& current3) + : base_(base), + begin1_(g1.begin()), end1_(g1.end()), current1_(current1), + begin2_(g2.begin()), end2_(g2.end()), current2_(current2), + begin3_(g3.begin()), end3_(g3.end()), current3_(current3) { + ComputeCurrentValue(); + } + virtual ~Iterator() {} + + virtual const ParamGeneratorInterface* BaseGenerator() const { + return base_; + } + // Advance should not be called on beyond-of-range iterators + // so no component iterators must be beyond end of range, either. + virtual void Advance() { + assert(!AtEnd()); + ++current3_; + if (current3_ == end3_) { + current3_ = begin3_; + ++current2_; + } + if (current2_ == end2_) { + current2_ = begin2_; + ++current1_; + } + ComputeCurrentValue(); + } + virtual ParamIteratorInterface* Clone() const { + return new Iterator(*this); + } + virtual const ParamType* Current() const { return ¤t_value_; } + virtual bool Equals(const ParamIteratorInterface& other) const { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const Iterator* typed_other = + CheckedDowncastToActualType(&other); + // We must report iterators equal if they both point beyond their + // respective ranges. That can happen in a variety of fashions, + // so we have to consult AtEnd(). + return (AtEnd() && typed_other->AtEnd()) || + ( + current1_ == typed_other->current1_ && + current2_ == typed_other->current2_ && + current3_ == typed_other->current3_); + } + + private: + Iterator(const Iterator& other) + : base_(other.base_), + begin1_(other.begin1_), + end1_(other.end1_), + current1_(other.current1_), + begin2_(other.begin2_), + end2_(other.end2_), + current2_(other.current2_), + begin3_(other.begin3_), + end3_(other.end3_), + current3_(other.current3_) { + ComputeCurrentValue(); + } + + void ComputeCurrentValue() { + if (!AtEnd()) + current_value_ = ParamType(*current1_, *current2_, *current3_); + } + bool AtEnd() const { + // We must report iterator past the end of the range when either of the + // component iterators has reached the end of its range. + return + current1_ == end1_ || + current2_ == end2_ || + current3_ == end3_; + } + + // No implementation - assignment is unsupported. + void operator=(const Iterator& other); + + const ParamGeneratorInterface* const base_; + // begin[i]_ and end[i]_ define the i-th range that Iterator traverses. + // current[i]_ is the actual traversing iterator. + const typename ParamGenerator::iterator begin1_; + const typename ParamGenerator::iterator end1_; + typename ParamGenerator::iterator current1_; + const typename ParamGenerator::iterator begin2_; + const typename ParamGenerator::iterator end2_; + typename ParamGenerator::iterator current2_; + const typename ParamGenerator::iterator begin3_; + const typename ParamGenerator::iterator end3_; + typename ParamGenerator::iterator current3_; + ParamType current_value_; + }; // class CartesianProductGenerator3::Iterator + + // No implementation - assignment is unsupported. + void operator=(const CartesianProductGenerator3& other); + + const ParamGenerator g1_; + const ParamGenerator g2_; + const ParamGenerator g3_; +}; // class CartesianProductGenerator3 + + +template +class CartesianProductGenerator4 + : public ParamGeneratorInterface< ::std::tr1::tuple > { + public: + typedef ::std::tr1::tuple ParamType; + + CartesianProductGenerator4(const ParamGenerator& g1, + const ParamGenerator& g2, const ParamGenerator& g3, + const ParamGenerator& g4) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4) {} + virtual ~CartesianProductGenerator4() {} + + virtual ParamIteratorInterface* Begin() const { + return new Iterator(this, g1_, g1_.begin(), g2_, g2_.begin(), g3_, + g3_.begin(), g4_, g4_.begin()); + } + virtual ParamIteratorInterface* End() const { + return new Iterator(this, g1_, g1_.end(), g2_, g2_.end(), g3_, g3_.end(), + g4_, g4_.end()); + } + + private: + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, + const ParamGenerator& g1, + const typename ParamGenerator::iterator& current1, + const ParamGenerator& g2, + const typename ParamGenerator::iterator& current2, + const ParamGenerator& g3, + const typename ParamGenerator::iterator& current3, + const ParamGenerator& g4, + const typename ParamGenerator::iterator& current4) + : base_(base), + begin1_(g1.begin()), end1_(g1.end()), current1_(current1), + begin2_(g2.begin()), end2_(g2.end()), current2_(current2), + begin3_(g3.begin()), end3_(g3.end()), current3_(current3), + begin4_(g4.begin()), end4_(g4.end()), current4_(current4) { + ComputeCurrentValue(); + } + virtual ~Iterator() {} + + virtual const ParamGeneratorInterface* BaseGenerator() const { + return base_; + } + // Advance should not be called on beyond-of-range iterators + // so no component iterators must be beyond end of range, either. + virtual void Advance() { + assert(!AtEnd()); + ++current4_; + if (current4_ == end4_) { + current4_ = begin4_; + ++current3_; + } + if (current3_ == end3_) { + current3_ = begin3_; + ++current2_; + } + if (current2_ == end2_) { + current2_ = begin2_; + ++current1_; + } + ComputeCurrentValue(); + } + virtual ParamIteratorInterface* Clone() const { + return new Iterator(*this); + } + virtual const ParamType* Current() const { return ¤t_value_; } + virtual bool Equals(const ParamIteratorInterface& other) const { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const Iterator* typed_other = + CheckedDowncastToActualType(&other); + // We must report iterators equal if they both point beyond their + // respective ranges. That can happen in a variety of fashions, + // so we have to consult AtEnd(). + return (AtEnd() && typed_other->AtEnd()) || + ( + current1_ == typed_other->current1_ && + current2_ == typed_other->current2_ && + current3_ == typed_other->current3_ && + current4_ == typed_other->current4_); + } + + private: + Iterator(const Iterator& other) + : base_(other.base_), + begin1_(other.begin1_), + end1_(other.end1_), + current1_(other.current1_), + begin2_(other.begin2_), + end2_(other.end2_), + current2_(other.current2_), + begin3_(other.begin3_), + end3_(other.end3_), + current3_(other.current3_), + begin4_(other.begin4_), + end4_(other.end4_), + current4_(other.current4_) { + ComputeCurrentValue(); + } + + void ComputeCurrentValue() { + if (!AtEnd()) + current_value_ = ParamType(*current1_, *current2_, *current3_, + *current4_); + } + bool AtEnd() const { + // We must report iterator past the end of the range when either of the + // component iterators has reached the end of its range. + return + current1_ == end1_ || + current2_ == end2_ || + current3_ == end3_ || + current4_ == end4_; + } + + // No implementation - assignment is unsupported. + void operator=(const Iterator& other); + + const ParamGeneratorInterface* const base_; + // begin[i]_ and end[i]_ define the i-th range that Iterator traverses. + // current[i]_ is the actual traversing iterator. + const typename ParamGenerator::iterator begin1_; + const typename ParamGenerator::iterator end1_; + typename ParamGenerator::iterator current1_; + const typename ParamGenerator::iterator begin2_; + const typename ParamGenerator::iterator end2_; + typename ParamGenerator::iterator current2_; + const typename ParamGenerator::iterator begin3_; + const typename ParamGenerator::iterator end3_; + typename ParamGenerator::iterator current3_; + const typename ParamGenerator::iterator begin4_; + const typename ParamGenerator::iterator end4_; + typename ParamGenerator::iterator current4_; + ParamType current_value_; + }; // class CartesianProductGenerator4::Iterator + + // No implementation - assignment is unsupported. + void operator=(const CartesianProductGenerator4& other); + + const ParamGenerator g1_; + const ParamGenerator g2_; + const ParamGenerator g3_; + const ParamGenerator g4_; +}; // class CartesianProductGenerator4 + + +template +class CartesianProductGenerator5 + : public ParamGeneratorInterface< ::std::tr1::tuple > { + public: + typedef ::std::tr1::tuple ParamType; + + CartesianProductGenerator5(const ParamGenerator& g1, + const ParamGenerator& g2, const ParamGenerator& g3, + const ParamGenerator& g4, const ParamGenerator& g5) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5) {} + virtual ~CartesianProductGenerator5() {} + + virtual ParamIteratorInterface* Begin() const { + return new Iterator(this, g1_, g1_.begin(), g2_, g2_.begin(), g3_, + g3_.begin(), g4_, g4_.begin(), g5_, g5_.begin()); + } + virtual ParamIteratorInterface* End() const { + return new Iterator(this, g1_, g1_.end(), g2_, g2_.end(), g3_, g3_.end(), + g4_, g4_.end(), g5_, g5_.end()); + } + + private: + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, + const ParamGenerator& g1, + const typename ParamGenerator::iterator& current1, + const ParamGenerator& g2, + const typename ParamGenerator::iterator& current2, + const ParamGenerator& g3, + const typename ParamGenerator::iterator& current3, + const ParamGenerator& g4, + const typename ParamGenerator::iterator& current4, + const ParamGenerator& g5, + const typename ParamGenerator::iterator& current5) + : base_(base), + begin1_(g1.begin()), end1_(g1.end()), current1_(current1), + begin2_(g2.begin()), end2_(g2.end()), current2_(current2), + begin3_(g3.begin()), end3_(g3.end()), current3_(current3), + begin4_(g4.begin()), end4_(g4.end()), current4_(current4), + begin5_(g5.begin()), end5_(g5.end()), current5_(current5) { + ComputeCurrentValue(); + } + virtual ~Iterator() {} + + virtual const ParamGeneratorInterface* BaseGenerator() const { + return base_; + } + // Advance should not be called on beyond-of-range iterators + // so no component iterators must be beyond end of range, either. + virtual void Advance() { + assert(!AtEnd()); + ++current5_; + if (current5_ == end5_) { + current5_ = begin5_; + ++current4_; + } + if (current4_ == end4_) { + current4_ = begin4_; + ++current3_; + } + if (current3_ == end3_) { + current3_ = begin3_; + ++current2_; + } + if (current2_ == end2_) { + current2_ = begin2_; + ++current1_; + } + ComputeCurrentValue(); + } + virtual ParamIteratorInterface* Clone() const { + return new Iterator(*this); + } + virtual const ParamType* Current() const { return ¤t_value_; } + virtual bool Equals(const ParamIteratorInterface& other) const { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const Iterator* typed_other = + CheckedDowncastToActualType(&other); + // We must report iterators equal if they both point beyond their + // respective ranges. That can happen in a variety of fashions, + // so we have to consult AtEnd(). + return (AtEnd() && typed_other->AtEnd()) || + ( + current1_ == typed_other->current1_ && + current2_ == typed_other->current2_ && + current3_ == typed_other->current3_ && + current4_ == typed_other->current4_ && + current5_ == typed_other->current5_); + } + + private: + Iterator(const Iterator& other) + : base_(other.base_), + begin1_(other.begin1_), + end1_(other.end1_), + current1_(other.current1_), + begin2_(other.begin2_), + end2_(other.end2_), + current2_(other.current2_), + begin3_(other.begin3_), + end3_(other.end3_), + current3_(other.current3_), + begin4_(other.begin4_), + end4_(other.end4_), + current4_(other.current4_), + begin5_(other.begin5_), + end5_(other.end5_), + current5_(other.current5_) { + ComputeCurrentValue(); + } + + void ComputeCurrentValue() { + if (!AtEnd()) + current_value_ = ParamType(*current1_, *current2_, *current3_, + *current4_, *current5_); + } + bool AtEnd() const { + // We must report iterator past the end of the range when either of the + // component iterators has reached the end of its range. + return + current1_ == end1_ || + current2_ == end2_ || + current3_ == end3_ || + current4_ == end4_ || + current5_ == end5_; + } + + // No implementation - assignment is unsupported. + void operator=(const Iterator& other); + + const ParamGeneratorInterface* const base_; + // begin[i]_ and end[i]_ define the i-th range that Iterator traverses. + // current[i]_ is the actual traversing iterator. + const typename ParamGenerator::iterator begin1_; + const typename ParamGenerator::iterator end1_; + typename ParamGenerator::iterator current1_; + const typename ParamGenerator::iterator begin2_; + const typename ParamGenerator::iterator end2_; + typename ParamGenerator::iterator current2_; + const typename ParamGenerator::iterator begin3_; + const typename ParamGenerator::iterator end3_; + typename ParamGenerator::iterator current3_; + const typename ParamGenerator::iterator begin4_; + const typename ParamGenerator::iterator end4_; + typename ParamGenerator::iterator current4_; + const typename ParamGenerator::iterator begin5_; + const typename ParamGenerator::iterator end5_; + typename ParamGenerator::iterator current5_; + ParamType current_value_; + }; // class CartesianProductGenerator5::Iterator + + // No implementation - assignment is unsupported. + void operator=(const CartesianProductGenerator5& other); + + const ParamGenerator g1_; + const ParamGenerator g2_; + const ParamGenerator g3_; + const ParamGenerator g4_; + const ParamGenerator g5_; +}; // class CartesianProductGenerator5 + + +template +class CartesianProductGenerator6 + : public ParamGeneratorInterface< ::std::tr1::tuple > { + public: + typedef ::std::tr1::tuple ParamType; + + CartesianProductGenerator6(const ParamGenerator& g1, + const ParamGenerator& g2, const ParamGenerator& g3, + const ParamGenerator& g4, const ParamGenerator& g5, + const ParamGenerator& g6) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6) {} + virtual ~CartesianProductGenerator6() {} + + virtual ParamIteratorInterface* Begin() const { + return new Iterator(this, g1_, g1_.begin(), g2_, g2_.begin(), g3_, + g3_.begin(), g4_, g4_.begin(), g5_, g5_.begin(), g6_, g6_.begin()); + } + virtual ParamIteratorInterface* End() const { + return new Iterator(this, g1_, g1_.end(), g2_, g2_.end(), g3_, g3_.end(), + g4_, g4_.end(), g5_, g5_.end(), g6_, g6_.end()); + } + + private: + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, + const ParamGenerator& g1, + const typename ParamGenerator::iterator& current1, + const ParamGenerator& g2, + const typename ParamGenerator::iterator& current2, + const ParamGenerator& g3, + const typename ParamGenerator::iterator& current3, + const ParamGenerator& g4, + const typename ParamGenerator::iterator& current4, + const ParamGenerator& g5, + const typename ParamGenerator::iterator& current5, + const ParamGenerator& g6, + const typename ParamGenerator::iterator& current6) + : base_(base), + begin1_(g1.begin()), end1_(g1.end()), current1_(current1), + begin2_(g2.begin()), end2_(g2.end()), current2_(current2), + begin3_(g3.begin()), end3_(g3.end()), current3_(current3), + begin4_(g4.begin()), end4_(g4.end()), current4_(current4), + begin5_(g5.begin()), end5_(g5.end()), current5_(current5), + begin6_(g6.begin()), end6_(g6.end()), current6_(current6) { + ComputeCurrentValue(); + } + virtual ~Iterator() {} + + virtual const ParamGeneratorInterface* BaseGenerator() const { + return base_; + } + // Advance should not be called on beyond-of-range iterators + // so no component iterators must be beyond end of range, either. + virtual void Advance() { + assert(!AtEnd()); + ++current6_; + if (current6_ == end6_) { + current6_ = begin6_; + ++current5_; + } + if (current5_ == end5_) { + current5_ = begin5_; + ++current4_; + } + if (current4_ == end4_) { + current4_ = begin4_; + ++current3_; + } + if (current3_ == end3_) { + current3_ = begin3_; + ++current2_; + } + if (current2_ == end2_) { + current2_ = begin2_; + ++current1_; + } + ComputeCurrentValue(); + } + virtual ParamIteratorInterface* Clone() const { + return new Iterator(*this); + } + virtual const ParamType* Current() const { return ¤t_value_; } + virtual bool Equals(const ParamIteratorInterface& other) const { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const Iterator* typed_other = + CheckedDowncastToActualType(&other); + // We must report iterators equal if they both point beyond their + // respective ranges. That can happen in a variety of fashions, + // so we have to consult AtEnd(). + return (AtEnd() && typed_other->AtEnd()) || + ( + current1_ == typed_other->current1_ && + current2_ == typed_other->current2_ && + current3_ == typed_other->current3_ && + current4_ == typed_other->current4_ && + current5_ == typed_other->current5_ && + current6_ == typed_other->current6_); + } + + private: + Iterator(const Iterator& other) + : base_(other.base_), + begin1_(other.begin1_), + end1_(other.end1_), + current1_(other.current1_), + begin2_(other.begin2_), + end2_(other.end2_), + current2_(other.current2_), + begin3_(other.begin3_), + end3_(other.end3_), + current3_(other.current3_), + begin4_(other.begin4_), + end4_(other.end4_), + current4_(other.current4_), + begin5_(other.begin5_), + end5_(other.end5_), + current5_(other.current5_), + begin6_(other.begin6_), + end6_(other.end6_), + current6_(other.current6_) { + ComputeCurrentValue(); + } + + void ComputeCurrentValue() { + if (!AtEnd()) + current_value_ = ParamType(*current1_, *current2_, *current3_, + *current4_, *current5_, *current6_); + } + bool AtEnd() const { + // We must report iterator past the end of the range when either of the + // component iterators has reached the end of its range. + return + current1_ == end1_ || + current2_ == end2_ || + current3_ == end3_ || + current4_ == end4_ || + current5_ == end5_ || + current6_ == end6_; + } + + // No implementation - assignment is unsupported. + void operator=(const Iterator& other); + + const ParamGeneratorInterface* const base_; + // begin[i]_ and end[i]_ define the i-th range that Iterator traverses. + // current[i]_ is the actual traversing iterator. + const typename ParamGenerator::iterator begin1_; + const typename ParamGenerator::iterator end1_; + typename ParamGenerator::iterator current1_; + const typename ParamGenerator::iterator begin2_; + const typename ParamGenerator::iterator end2_; + typename ParamGenerator::iterator current2_; + const typename ParamGenerator::iterator begin3_; + const typename ParamGenerator::iterator end3_; + typename ParamGenerator::iterator current3_; + const typename ParamGenerator::iterator begin4_; + const typename ParamGenerator::iterator end4_; + typename ParamGenerator::iterator current4_; + const typename ParamGenerator::iterator begin5_; + const typename ParamGenerator::iterator end5_; + typename ParamGenerator::iterator current5_; + const typename ParamGenerator::iterator begin6_; + const typename ParamGenerator::iterator end6_; + typename ParamGenerator::iterator current6_; + ParamType current_value_; + }; // class CartesianProductGenerator6::Iterator + + // No implementation - assignment is unsupported. + void operator=(const CartesianProductGenerator6& other); + + const ParamGenerator g1_; + const ParamGenerator g2_; + const ParamGenerator g3_; + const ParamGenerator g4_; + const ParamGenerator g5_; + const ParamGenerator g6_; +}; // class CartesianProductGenerator6 + + +template +class CartesianProductGenerator7 + : public ParamGeneratorInterface< ::std::tr1::tuple > { + public: + typedef ::std::tr1::tuple ParamType; + + CartesianProductGenerator7(const ParamGenerator& g1, + const ParamGenerator& g2, const ParamGenerator& g3, + const ParamGenerator& g4, const ParamGenerator& g5, + const ParamGenerator& g6, const ParamGenerator& g7) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6), g7_(g7) {} + virtual ~CartesianProductGenerator7() {} + + virtual ParamIteratorInterface* Begin() const { + return new Iterator(this, g1_, g1_.begin(), g2_, g2_.begin(), g3_, + g3_.begin(), g4_, g4_.begin(), g5_, g5_.begin(), g6_, g6_.begin(), g7_, + g7_.begin()); + } + virtual ParamIteratorInterface* End() const { + return new Iterator(this, g1_, g1_.end(), g2_, g2_.end(), g3_, g3_.end(), + g4_, g4_.end(), g5_, g5_.end(), g6_, g6_.end(), g7_, g7_.end()); + } + + private: + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, + const ParamGenerator& g1, + const typename ParamGenerator::iterator& current1, + const ParamGenerator& g2, + const typename ParamGenerator::iterator& current2, + const ParamGenerator& g3, + const typename ParamGenerator::iterator& current3, + const ParamGenerator& g4, + const typename ParamGenerator::iterator& current4, + const ParamGenerator& g5, + const typename ParamGenerator::iterator& current5, + const ParamGenerator& g6, + const typename ParamGenerator::iterator& current6, + const ParamGenerator& g7, + const typename ParamGenerator::iterator& current7) + : base_(base), + begin1_(g1.begin()), end1_(g1.end()), current1_(current1), + begin2_(g2.begin()), end2_(g2.end()), current2_(current2), + begin3_(g3.begin()), end3_(g3.end()), current3_(current3), + begin4_(g4.begin()), end4_(g4.end()), current4_(current4), + begin5_(g5.begin()), end5_(g5.end()), current5_(current5), + begin6_(g6.begin()), end6_(g6.end()), current6_(current6), + begin7_(g7.begin()), end7_(g7.end()), current7_(current7) { + ComputeCurrentValue(); + } + virtual ~Iterator() {} + + virtual const ParamGeneratorInterface* BaseGenerator() const { + return base_; + } + // Advance should not be called on beyond-of-range iterators + // so no component iterators must be beyond end of range, either. + virtual void Advance() { + assert(!AtEnd()); + ++current7_; + if (current7_ == end7_) { + current7_ = begin7_; + ++current6_; + } + if (current6_ == end6_) { + current6_ = begin6_; + ++current5_; + } + if (current5_ == end5_) { + current5_ = begin5_; + ++current4_; + } + if (current4_ == end4_) { + current4_ = begin4_; + ++current3_; + } + if (current3_ == end3_) { + current3_ = begin3_; + ++current2_; + } + if (current2_ == end2_) { + current2_ = begin2_; + ++current1_; + } + ComputeCurrentValue(); + } + virtual ParamIteratorInterface* Clone() const { + return new Iterator(*this); + } + virtual const ParamType* Current() const { return ¤t_value_; } + virtual bool Equals(const ParamIteratorInterface& other) const { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const Iterator* typed_other = + CheckedDowncastToActualType(&other); + // We must report iterators equal if they both point beyond their + // respective ranges. That can happen in a variety of fashions, + // so we have to consult AtEnd(). + return (AtEnd() && typed_other->AtEnd()) || + ( + current1_ == typed_other->current1_ && + current2_ == typed_other->current2_ && + current3_ == typed_other->current3_ && + current4_ == typed_other->current4_ && + current5_ == typed_other->current5_ && + current6_ == typed_other->current6_ && + current7_ == typed_other->current7_); + } + + private: + Iterator(const Iterator& other) + : base_(other.base_), + begin1_(other.begin1_), + end1_(other.end1_), + current1_(other.current1_), + begin2_(other.begin2_), + end2_(other.end2_), + current2_(other.current2_), + begin3_(other.begin3_), + end3_(other.end3_), + current3_(other.current3_), + begin4_(other.begin4_), + end4_(other.end4_), + current4_(other.current4_), + begin5_(other.begin5_), + end5_(other.end5_), + current5_(other.current5_), + begin6_(other.begin6_), + end6_(other.end6_), + current6_(other.current6_), + begin7_(other.begin7_), + end7_(other.end7_), + current7_(other.current7_) { + ComputeCurrentValue(); + } + + void ComputeCurrentValue() { + if (!AtEnd()) + current_value_ = ParamType(*current1_, *current2_, *current3_, + *current4_, *current5_, *current6_, *current7_); + } + bool AtEnd() const { + // We must report iterator past the end of the range when either of the + // component iterators has reached the end of its range. + return + current1_ == end1_ || + current2_ == end2_ || + current3_ == end3_ || + current4_ == end4_ || + current5_ == end5_ || + current6_ == end6_ || + current7_ == end7_; + } + + // No implementation - assignment is unsupported. + void operator=(const Iterator& other); + + const ParamGeneratorInterface* const base_; + // begin[i]_ and end[i]_ define the i-th range that Iterator traverses. + // current[i]_ is the actual traversing iterator. + const typename ParamGenerator::iterator begin1_; + const typename ParamGenerator::iterator end1_; + typename ParamGenerator::iterator current1_; + const typename ParamGenerator::iterator begin2_; + const typename ParamGenerator::iterator end2_; + typename ParamGenerator::iterator current2_; + const typename ParamGenerator::iterator begin3_; + const typename ParamGenerator::iterator end3_; + typename ParamGenerator::iterator current3_; + const typename ParamGenerator::iterator begin4_; + const typename ParamGenerator::iterator end4_; + typename ParamGenerator::iterator current4_; + const typename ParamGenerator::iterator begin5_; + const typename ParamGenerator::iterator end5_; + typename ParamGenerator::iterator current5_; + const typename ParamGenerator::iterator begin6_; + const typename ParamGenerator::iterator end6_; + typename ParamGenerator::iterator current6_; + const typename ParamGenerator::iterator begin7_; + const typename ParamGenerator::iterator end7_; + typename ParamGenerator::iterator current7_; + ParamType current_value_; + }; // class CartesianProductGenerator7::Iterator + + // No implementation - assignment is unsupported. + void operator=(const CartesianProductGenerator7& other); + + const ParamGenerator g1_; + const ParamGenerator g2_; + const ParamGenerator g3_; + const ParamGenerator g4_; + const ParamGenerator g5_; + const ParamGenerator g6_; + const ParamGenerator g7_; +}; // class CartesianProductGenerator7 + + +template +class CartesianProductGenerator8 + : public ParamGeneratorInterface< ::std::tr1::tuple > { + public: + typedef ::std::tr1::tuple ParamType; + + CartesianProductGenerator8(const ParamGenerator& g1, + const ParamGenerator& g2, const ParamGenerator& g3, + const ParamGenerator& g4, const ParamGenerator& g5, + const ParamGenerator& g6, const ParamGenerator& g7, + const ParamGenerator& g8) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6), g7_(g7), + g8_(g8) {} + virtual ~CartesianProductGenerator8() {} + + virtual ParamIteratorInterface* Begin() const { + return new Iterator(this, g1_, g1_.begin(), g2_, g2_.begin(), g3_, + g3_.begin(), g4_, g4_.begin(), g5_, g5_.begin(), g6_, g6_.begin(), g7_, + g7_.begin(), g8_, g8_.begin()); + } + virtual ParamIteratorInterface* End() const { + return new Iterator(this, g1_, g1_.end(), g2_, g2_.end(), g3_, g3_.end(), + g4_, g4_.end(), g5_, g5_.end(), g6_, g6_.end(), g7_, g7_.end(), g8_, + g8_.end()); + } + + private: + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, + const ParamGenerator& g1, + const typename ParamGenerator::iterator& current1, + const ParamGenerator& g2, + const typename ParamGenerator::iterator& current2, + const ParamGenerator& g3, + const typename ParamGenerator::iterator& current3, + const ParamGenerator& g4, + const typename ParamGenerator::iterator& current4, + const ParamGenerator& g5, + const typename ParamGenerator::iterator& current5, + const ParamGenerator& g6, + const typename ParamGenerator::iterator& current6, + const ParamGenerator& g7, + const typename ParamGenerator::iterator& current7, + const ParamGenerator& g8, + const typename ParamGenerator::iterator& current8) + : base_(base), + begin1_(g1.begin()), end1_(g1.end()), current1_(current1), + begin2_(g2.begin()), end2_(g2.end()), current2_(current2), + begin3_(g3.begin()), end3_(g3.end()), current3_(current3), + begin4_(g4.begin()), end4_(g4.end()), current4_(current4), + begin5_(g5.begin()), end5_(g5.end()), current5_(current5), + begin6_(g6.begin()), end6_(g6.end()), current6_(current6), + begin7_(g7.begin()), end7_(g7.end()), current7_(current7), + begin8_(g8.begin()), end8_(g8.end()), current8_(current8) { + ComputeCurrentValue(); + } + virtual ~Iterator() {} + + virtual const ParamGeneratorInterface* BaseGenerator() const { + return base_; + } + // Advance should not be called on beyond-of-range iterators + // so no component iterators must be beyond end of range, either. + virtual void Advance() { + assert(!AtEnd()); + ++current8_; + if (current8_ == end8_) { + current8_ = begin8_; + ++current7_; + } + if (current7_ == end7_) { + current7_ = begin7_; + ++current6_; + } + if (current6_ == end6_) { + current6_ = begin6_; + ++current5_; + } + if (current5_ == end5_) { + current5_ = begin5_; + ++current4_; + } + if (current4_ == end4_) { + current4_ = begin4_; + ++current3_; + } + if (current3_ == end3_) { + current3_ = begin3_; + ++current2_; + } + if (current2_ == end2_) { + current2_ = begin2_; + ++current1_; + } + ComputeCurrentValue(); + } + virtual ParamIteratorInterface* Clone() const { + return new Iterator(*this); + } + virtual const ParamType* Current() const { return ¤t_value_; } + virtual bool Equals(const ParamIteratorInterface& other) const { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const Iterator* typed_other = + CheckedDowncastToActualType(&other); + // We must report iterators equal if they both point beyond their + // respective ranges. That can happen in a variety of fashions, + // so we have to consult AtEnd(). + return (AtEnd() && typed_other->AtEnd()) || + ( + current1_ == typed_other->current1_ && + current2_ == typed_other->current2_ && + current3_ == typed_other->current3_ && + current4_ == typed_other->current4_ && + current5_ == typed_other->current5_ && + current6_ == typed_other->current6_ && + current7_ == typed_other->current7_ && + current8_ == typed_other->current8_); + } + + private: + Iterator(const Iterator& other) + : base_(other.base_), + begin1_(other.begin1_), + end1_(other.end1_), + current1_(other.current1_), + begin2_(other.begin2_), + end2_(other.end2_), + current2_(other.current2_), + begin3_(other.begin3_), + end3_(other.end3_), + current3_(other.current3_), + begin4_(other.begin4_), + end4_(other.end4_), + current4_(other.current4_), + begin5_(other.begin5_), + end5_(other.end5_), + current5_(other.current5_), + begin6_(other.begin6_), + end6_(other.end6_), + current6_(other.current6_), + begin7_(other.begin7_), + end7_(other.end7_), + current7_(other.current7_), + begin8_(other.begin8_), + end8_(other.end8_), + current8_(other.current8_) { + ComputeCurrentValue(); + } + + void ComputeCurrentValue() { + if (!AtEnd()) + current_value_ = ParamType(*current1_, *current2_, *current3_, + *current4_, *current5_, *current6_, *current7_, *current8_); + } + bool AtEnd() const { + // We must report iterator past the end of the range when either of the + // component iterators has reached the end of its range. + return + current1_ == end1_ || + current2_ == end2_ || + current3_ == end3_ || + current4_ == end4_ || + current5_ == end5_ || + current6_ == end6_ || + current7_ == end7_ || + current8_ == end8_; + } + + // No implementation - assignment is unsupported. + void operator=(const Iterator& other); + + const ParamGeneratorInterface* const base_; + // begin[i]_ and end[i]_ define the i-th range that Iterator traverses. + // current[i]_ is the actual traversing iterator. + const typename ParamGenerator::iterator begin1_; + const typename ParamGenerator::iterator end1_; + typename ParamGenerator::iterator current1_; + const typename ParamGenerator::iterator begin2_; + const typename ParamGenerator::iterator end2_; + typename ParamGenerator::iterator current2_; + const typename ParamGenerator::iterator begin3_; + const typename ParamGenerator::iterator end3_; + typename ParamGenerator::iterator current3_; + const typename ParamGenerator::iterator begin4_; + const typename ParamGenerator::iterator end4_; + typename ParamGenerator::iterator current4_; + const typename ParamGenerator::iterator begin5_; + const typename ParamGenerator::iterator end5_; + typename ParamGenerator::iterator current5_; + const typename ParamGenerator::iterator begin6_; + const typename ParamGenerator::iterator end6_; + typename ParamGenerator::iterator current6_; + const typename ParamGenerator::iterator begin7_; + const typename ParamGenerator::iterator end7_; + typename ParamGenerator::iterator current7_; + const typename ParamGenerator::iterator begin8_; + const typename ParamGenerator::iterator end8_; + typename ParamGenerator::iterator current8_; + ParamType current_value_; + }; // class CartesianProductGenerator8::Iterator + + // No implementation - assignment is unsupported. + void operator=(const CartesianProductGenerator8& other); + + const ParamGenerator g1_; + const ParamGenerator g2_; + const ParamGenerator g3_; + const ParamGenerator g4_; + const ParamGenerator g5_; + const ParamGenerator g6_; + const ParamGenerator g7_; + const ParamGenerator g8_; +}; // class CartesianProductGenerator8 + + +template +class CartesianProductGenerator9 + : public ParamGeneratorInterface< ::std::tr1::tuple > { + public: + typedef ::std::tr1::tuple ParamType; + + CartesianProductGenerator9(const ParamGenerator& g1, + const ParamGenerator& g2, const ParamGenerator& g3, + const ParamGenerator& g4, const ParamGenerator& g5, + const ParamGenerator& g6, const ParamGenerator& g7, + const ParamGenerator& g8, const ParamGenerator& g9) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6), g7_(g7), g8_(g8), + g9_(g9) {} + virtual ~CartesianProductGenerator9() {} + + virtual ParamIteratorInterface* Begin() const { + return new Iterator(this, g1_, g1_.begin(), g2_, g2_.begin(), g3_, + g3_.begin(), g4_, g4_.begin(), g5_, g5_.begin(), g6_, g6_.begin(), g7_, + g7_.begin(), g8_, g8_.begin(), g9_, g9_.begin()); + } + virtual ParamIteratorInterface* End() const { + return new Iterator(this, g1_, g1_.end(), g2_, g2_.end(), g3_, g3_.end(), + g4_, g4_.end(), g5_, g5_.end(), g6_, g6_.end(), g7_, g7_.end(), g8_, + g8_.end(), g9_, g9_.end()); + } + + private: + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, + const ParamGenerator& g1, + const typename ParamGenerator::iterator& current1, + const ParamGenerator& g2, + const typename ParamGenerator::iterator& current2, + const ParamGenerator& g3, + const typename ParamGenerator::iterator& current3, + const ParamGenerator& g4, + const typename ParamGenerator::iterator& current4, + const ParamGenerator& g5, + const typename ParamGenerator::iterator& current5, + const ParamGenerator& g6, + const typename ParamGenerator::iterator& current6, + const ParamGenerator& g7, + const typename ParamGenerator::iterator& current7, + const ParamGenerator& g8, + const typename ParamGenerator::iterator& current8, + const ParamGenerator& g9, + const typename ParamGenerator::iterator& current9) + : base_(base), + begin1_(g1.begin()), end1_(g1.end()), current1_(current1), + begin2_(g2.begin()), end2_(g2.end()), current2_(current2), + begin3_(g3.begin()), end3_(g3.end()), current3_(current3), + begin4_(g4.begin()), end4_(g4.end()), current4_(current4), + begin5_(g5.begin()), end5_(g5.end()), current5_(current5), + begin6_(g6.begin()), end6_(g6.end()), current6_(current6), + begin7_(g7.begin()), end7_(g7.end()), current7_(current7), + begin8_(g8.begin()), end8_(g8.end()), current8_(current8), + begin9_(g9.begin()), end9_(g9.end()), current9_(current9) { + ComputeCurrentValue(); + } + virtual ~Iterator() {} + + virtual const ParamGeneratorInterface* BaseGenerator() const { + return base_; + } + // Advance should not be called on beyond-of-range iterators + // so no component iterators must be beyond end of range, either. + virtual void Advance() { + assert(!AtEnd()); + ++current9_; + if (current9_ == end9_) { + current9_ = begin9_; + ++current8_; + } + if (current8_ == end8_) { + current8_ = begin8_; + ++current7_; + } + if (current7_ == end7_) { + current7_ = begin7_; + ++current6_; + } + if (current6_ == end6_) { + current6_ = begin6_; + ++current5_; + } + if (current5_ == end5_) { + current5_ = begin5_; + ++current4_; + } + if (current4_ == end4_) { + current4_ = begin4_; + ++current3_; + } + if (current3_ == end3_) { + current3_ = begin3_; + ++current2_; + } + if (current2_ == end2_) { + current2_ = begin2_; + ++current1_; + } + ComputeCurrentValue(); + } + virtual ParamIteratorInterface* Clone() const { + return new Iterator(*this); + } + virtual const ParamType* Current() const { return ¤t_value_; } + virtual bool Equals(const ParamIteratorInterface& other) const { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const Iterator* typed_other = + CheckedDowncastToActualType(&other); + // We must report iterators equal if they both point beyond their + // respective ranges. That can happen in a variety of fashions, + // so we have to consult AtEnd(). + return (AtEnd() && typed_other->AtEnd()) || + ( + current1_ == typed_other->current1_ && + current2_ == typed_other->current2_ && + current3_ == typed_other->current3_ && + current4_ == typed_other->current4_ && + current5_ == typed_other->current5_ && + current6_ == typed_other->current6_ && + current7_ == typed_other->current7_ && + current8_ == typed_other->current8_ && + current9_ == typed_other->current9_); + } + + private: + Iterator(const Iterator& other) + : base_(other.base_), + begin1_(other.begin1_), + end1_(other.end1_), + current1_(other.current1_), + begin2_(other.begin2_), + end2_(other.end2_), + current2_(other.current2_), + begin3_(other.begin3_), + end3_(other.end3_), + current3_(other.current3_), + begin4_(other.begin4_), + end4_(other.end4_), + current4_(other.current4_), + begin5_(other.begin5_), + end5_(other.end5_), + current5_(other.current5_), + begin6_(other.begin6_), + end6_(other.end6_), + current6_(other.current6_), + begin7_(other.begin7_), + end7_(other.end7_), + current7_(other.current7_), + begin8_(other.begin8_), + end8_(other.end8_), + current8_(other.current8_), + begin9_(other.begin9_), + end9_(other.end9_), + current9_(other.current9_) { + ComputeCurrentValue(); + } + + void ComputeCurrentValue() { + if (!AtEnd()) + current_value_ = ParamType(*current1_, *current2_, *current3_, + *current4_, *current5_, *current6_, *current7_, *current8_, + *current9_); + } + bool AtEnd() const { + // We must report iterator past the end of the range when either of the + // component iterators has reached the end of its range. + return + current1_ == end1_ || + current2_ == end2_ || + current3_ == end3_ || + current4_ == end4_ || + current5_ == end5_ || + current6_ == end6_ || + current7_ == end7_ || + current8_ == end8_ || + current9_ == end9_; + } + + // No implementation - assignment is unsupported. + void operator=(const Iterator& other); + + const ParamGeneratorInterface* const base_; + // begin[i]_ and end[i]_ define the i-th range that Iterator traverses. + // current[i]_ is the actual traversing iterator. + const typename ParamGenerator::iterator begin1_; + const typename ParamGenerator::iterator end1_; + typename ParamGenerator::iterator current1_; + const typename ParamGenerator::iterator begin2_; + const typename ParamGenerator::iterator end2_; + typename ParamGenerator::iterator current2_; + const typename ParamGenerator::iterator begin3_; + const typename ParamGenerator::iterator end3_; + typename ParamGenerator::iterator current3_; + const typename ParamGenerator::iterator begin4_; + const typename ParamGenerator::iterator end4_; + typename ParamGenerator::iterator current4_; + const typename ParamGenerator::iterator begin5_; + const typename ParamGenerator::iterator end5_; + typename ParamGenerator::iterator current5_; + const typename ParamGenerator::iterator begin6_; + const typename ParamGenerator::iterator end6_; + typename ParamGenerator::iterator current6_; + const typename ParamGenerator::iterator begin7_; + const typename ParamGenerator::iterator end7_; + typename ParamGenerator::iterator current7_; + const typename ParamGenerator::iterator begin8_; + const typename ParamGenerator::iterator end8_; + typename ParamGenerator::iterator current8_; + const typename ParamGenerator::iterator begin9_; + const typename ParamGenerator::iterator end9_; + typename ParamGenerator::iterator current9_; + ParamType current_value_; + }; // class CartesianProductGenerator9::Iterator + + // No implementation - assignment is unsupported. + void operator=(const CartesianProductGenerator9& other); + + const ParamGenerator g1_; + const ParamGenerator g2_; + const ParamGenerator g3_; + const ParamGenerator g4_; + const ParamGenerator g5_; + const ParamGenerator g6_; + const ParamGenerator g7_; + const ParamGenerator g8_; + const ParamGenerator g9_; +}; // class CartesianProductGenerator9 + + +template +class CartesianProductGenerator10 + : public ParamGeneratorInterface< ::std::tr1::tuple > { + public: + typedef ::std::tr1::tuple ParamType; + + CartesianProductGenerator10(const ParamGenerator& g1, + const ParamGenerator& g2, const ParamGenerator& g3, + const ParamGenerator& g4, const ParamGenerator& g5, + const ParamGenerator& g6, const ParamGenerator& g7, + const ParamGenerator& g8, const ParamGenerator& g9, + const ParamGenerator& g10) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6), g7_(g7), g8_(g8), + g9_(g9), g10_(g10) {} + virtual ~CartesianProductGenerator10() {} + + virtual ParamIteratorInterface* Begin() const { + return new Iterator(this, g1_, g1_.begin(), g2_, g2_.begin(), g3_, + g3_.begin(), g4_, g4_.begin(), g5_, g5_.begin(), g6_, g6_.begin(), g7_, + g7_.begin(), g8_, g8_.begin(), g9_, g9_.begin(), g10_, g10_.begin()); + } + virtual ParamIteratorInterface* End() const { + return new Iterator(this, g1_, g1_.end(), g2_, g2_.end(), g3_, g3_.end(), + g4_, g4_.end(), g5_, g5_.end(), g6_, g6_.end(), g7_, g7_.end(), g8_, + g8_.end(), g9_, g9_.end(), g10_, g10_.end()); + } + + private: + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, + const ParamGenerator& g1, + const typename ParamGenerator::iterator& current1, + const ParamGenerator& g2, + const typename ParamGenerator::iterator& current2, + const ParamGenerator& g3, + const typename ParamGenerator::iterator& current3, + const ParamGenerator& g4, + const typename ParamGenerator::iterator& current4, + const ParamGenerator& g5, + const typename ParamGenerator::iterator& current5, + const ParamGenerator& g6, + const typename ParamGenerator::iterator& current6, + const ParamGenerator& g7, + const typename ParamGenerator::iterator& current7, + const ParamGenerator& g8, + const typename ParamGenerator::iterator& current8, + const ParamGenerator& g9, + const typename ParamGenerator::iterator& current9, + const ParamGenerator& g10, + const typename ParamGenerator::iterator& current10) + : base_(base), + begin1_(g1.begin()), end1_(g1.end()), current1_(current1), + begin2_(g2.begin()), end2_(g2.end()), current2_(current2), + begin3_(g3.begin()), end3_(g3.end()), current3_(current3), + begin4_(g4.begin()), end4_(g4.end()), current4_(current4), + begin5_(g5.begin()), end5_(g5.end()), current5_(current5), + begin6_(g6.begin()), end6_(g6.end()), current6_(current6), + begin7_(g7.begin()), end7_(g7.end()), current7_(current7), + begin8_(g8.begin()), end8_(g8.end()), current8_(current8), + begin9_(g9.begin()), end9_(g9.end()), current9_(current9), + begin10_(g10.begin()), end10_(g10.end()), current10_(current10) { + ComputeCurrentValue(); + } + virtual ~Iterator() {} + + virtual const ParamGeneratorInterface* BaseGenerator() const { + return base_; + } + // Advance should not be called on beyond-of-range iterators + // so no component iterators must be beyond end of range, either. + virtual void Advance() { + assert(!AtEnd()); + ++current10_; + if (current10_ == end10_) { + current10_ = begin10_; + ++current9_; + } + if (current9_ == end9_) { + current9_ = begin9_; + ++current8_; + } + if (current8_ == end8_) { + current8_ = begin8_; + ++current7_; + } + if (current7_ == end7_) { + current7_ = begin7_; + ++current6_; + } + if (current6_ == end6_) { + current6_ = begin6_; + ++current5_; + } + if (current5_ == end5_) { + current5_ = begin5_; + ++current4_; + } + if (current4_ == end4_) { + current4_ = begin4_; + ++current3_; + } + if (current3_ == end3_) { + current3_ = begin3_; + ++current2_; + } + if (current2_ == end2_) { + current2_ = begin2_; + ++current1_; + } + ComputeCurrentValue(); + } + virtual ParamIteratorInterface* Clone() const { + return new Iterator(*this); + } + virtual const ParamType* Current() const { return ¤t_value_; } + virtual bool Equals(const ParamIteratorInterface& other) const { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const Iterator* typed_other = + CheckedDowncastToActualType(&other); + // We must report iterators equal if they both point beyond their + // respective ranges. That can happen in a variety of fashions, + // so we have to consult AtEnd(). + return (AtEnd() && typed_other->AtEnd()) || + ( + current1_ == typed_other->current1_ && + current2_ == typed_other->current2_ && + current3_ == typed_other->current3_ && + current4_ == typed_other->current4_ && + current5_ == typed_other->current5_ && + current6_ == typed_other->current6_ && + current7_ == typed_other->current7_ && + current8_ == typed_other->current8_ && + current9_ == typed_other->current9_ && + current10_ == typed_other->current10_); + } + + private: + Iterator(const Iterator& other) + : base_(other.base_), + begin1_(other.begin1_), + end1_(other.end1_), + current1_(other.current1_), + begin2_(other.begin2_), + end2_(other.end2_), + current2_(other.current2_), + begin3_(other.begin3_), + end3_(other.end3_), + current3_(other.current3_), + begin4_(other.begin4_), + end4_(other.end4_), + current4_(other.current4_), + begin5_(other.begin5_), + end5_(other.end5_), + current5_(other.current5_), + begin6_(other.begin6_), + end6_(other.end6_), + current6_(other.current6_), + begin7_(other.begin7_), + end7_(other.end7_), + current7_(other.current7_), + begin8_(other.begin8_), + end8_(other.end8_), + current8_(other.current8_), + begin9_(other.begin9_), + end9_(other.end9_), + current9_(other.current9_), + begin10_(other.begin10_), + end10_(other.end10_), + current10_(other.current10_) { + ComputeCurrentValue(); + } + + void ComputeCurrentValue() { + if (!AtEnd()) + current_value_ = ParamType(*current1_, *current2_, *current3_, + *current4_, *current5_, *current6_, *current7_, *current8_, + *current9_, *current10_); + } + bool AtEnd() const { + // We must report iterator past the end of the range when either of the + // component iterators has reached the end of its range. + return + current1_ == end1_ || + current2_ == end2_ || + current3_ == end3_ || + current4_ == end4_ || + current5_ == end5_ || + current6_ == end6_ || + current7_ == end7_ || + current8_ == end8_ || + current9_ == end9_ || + current10_ == end10_; + } + + // No implementation - assignment is unsupported. + void operator=(const Iterator& other); + + const ParamGeneratorInterface* const base_; + // begin[i]_ and end[i]_ define the i-th range that Iterator traverses. + // current[i]_ is the actual traversing iterator. + const typename ParamGenerator::iterator begin1_; + const typename ParamGenerator::iterator end1_; + typename ParamGenerator::iterator current1_; + const typename ParamGenerator::iterator begin2_; + const typename ParamGenerator::iterator end2_; + typename ParamGenerator::iterator current2_; + const typename ParamGenerator::iterator begin3_; + const typename ParamGenerator::iterator end3_; + typename ParamGenerator::iterator current3_; + const typename ParamGenerator::iterator begin4_; + const typename ParamGenerator::iterator end4_; + typename ParamGenerator::iterator current4_; + const typename ParamGenerator::iterator begin5_; + const typename ParamGenerator::iterator end5_; + typename ParamGenerator::iterator current5_; + const typename ParamGenerator::iterator begin6_; + const typename ParamGenerator::iterator end6_; + typename ParamGenerator::iterator current6_; + const typename ParamGenerator::iterator begin7_; + const typename ParamGenerator::iterator end7_; + typename ParamGenerator::iterator current7_; + const typename ParamGenerator::iterator begin8_; + const typename ParamGenerator::iterator end8_; + typename ParamGenerator::iterator current8_; + const typename ParamGenerator::iterator begin9_; + const typename ParamGenerator::iterator end9_; + typename ParamGenerator::iterator current9_; + const typename ParamGenerator::iterator begin10_; + const typename ParamGenerator::iterator end10_; + typename ParamGenerator::iterator current10_; + ParamType current_value_; + }; // class CartesianProductGenerator10::Iterator + + // No implementation - assignment is unsupported. + void operator=(const CartesianProductGenerator10& other); + + const ParamGenerator g1_; + const ParamGenerator g2_; + const ParamGenerator g3_; + const ParamGenerator g4_; + const ParamGenerator g5_; + const ParamGenerator g6_; + const ParamGenerator g7_; + const ParamGenerator g8_; + const ParamGenerator g9_; + const ParamGenerator g10_; +}; // class CartesianProductGenerator10 + + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Helper classes providing Combine() with polymorphic features. They allow +// casting CartesianProductGeneratorN to ParamGenerator if T is +// convertible to U. +// +template +class CartesianProductHolder2 { + public: +CartesianProductHolder2(const Generator1& g1, const Generator2& g2) + : g1_(g1), g2_(g2) {} + template + operator ParamGenerator< ::std::tr1::tuple >() const { + return ParamGenerator< ::std::tr1::tuple >( + new CartesianProductGenerator2( + static_cast >(g1_), + static_cast >(g2_))); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const CartesianProductHolder2& other); + + const Generator1 g1_; + const Generator2 g2_; +}; // class CartesianProductHolder2 + +template +class CartesianProductHolder3 { + public: +CartesianProductHolder3(const Generator1& g1, const Generator2& g2, + const Generator3& g3) + : g1_(g1), g2_(g2), g3_(g3) {} + template + operator ParamGenerator< ::std::tr1::tuple >() const { + return ParamGenerator< ::std::tr1::tuple >( + new CartesianProductGenerator3( + static_cast >(g1_), + static_cast >(g2_), + static_cast >(g3_))); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const CartesianProductHolder3& other); + + const Generator1 g1_; + const Generator2 g2_; + const Generator3 g3_; +}; // class CartesianProductHolder3 + +template +class CartesianProductHolder4 { + public: +CartesianProductHolder4(const Generator1& g1, const Generator2& g2, + const Generator3& g3, const Generator4& g4) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4) {} + template + operator ParamGenerator< ::std::tr1::tuple >() const { + return ParamGenerator< ::std::tr1::tuple >( + new CartesianProductGenerator4( + static_cast >(g1_), + static_cast >(g2_), + static_cast >(g3_), + static_cast >(g4_))); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const CartesianProductHolder4& other); + + const Generator1 g1_; + const Generator2 g2_; + const Generator3 g3_; + const Generator4 g4_; +}; // class CartesianProductHolder4 + +template +class CartesianProductHolder5 { + public: +CartesianProductHolder5(const Generator1& g1, const Generator2& g2, + const Generator3& g3, const Generator4& g4, const Generator5& g5) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5) {} + template + operator ParamGenerator< ::std::tr1::tuple >() const { + return ParamGenerator< ::std::tr1::tuple >( + new CartesianProductGenerator5( + static_cast >(g1_), + static_cast >(g2_), + static_cast >(g3_), + static_cast >(g4_), + static_cast >(g5_))); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const CartesianProductHolder5& other); + + const Generator1 g1_; + const Generator2 g2_; + const Generator3 g3_; + const Generator4 g4_; + const Generator5 g5_; +}; // class CartesianProductHolder5 + +template +class CartesianProductHolder6 { + public: +CartesianProductHolder6(const Generator1& g1, const Generator2& g2, + const Generator3& g3, const Generator4& g4, const Generator5& g5, + const Generator6& g6) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6) {} + template + operator ParamGenerator< ::std::tr1::tuple >() const { + return ParamGenerator< ::std::tr1::tuple >( + new CartesianProductGenerator6( + static_cast >(g1_), + static_cast >(g2_), + static_cast >(g3_), + static_cast >(g4_), + static_cast >(g5_), + static_cast >(g6_))); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const CartesianProductHolder6& other); + + const Generator1 g1_; + const Generator2 g2_; + const Generator3 g3_; + const Generator4 g4_; + const Generator5 g5_; + const Generator6 g6_; +}; // class CartesianProductHolder6 + +template +class CartesianProductHolder7 { + public: +CartesianProductHolder7(const Generator1& g1, const Generator2& g2, + const Generator3& g3, const Generator4& g4, const Generator5& g5, + const Generator6& g6, const Generator7& g7) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6), g7_(g7) {} + template + operator ParamGenerator< ::std::tr1::tuple >() const { + return ParamGenerator< ::std::tr1::tuple >( + new CartesianProductGenerator7( + static_cast >(g1_), + static_cast >(g2_), + static_cast >(g3_), + static_cast >(g4_), + static_cast >(g5_), + static_cast >(g6_), + static_cast >(g7_))); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const CartesianProductHolder7& other); + + const Generator1 g1_; + const Generator2 g2_; + const Generator3 g3_; + const Generator4 g4_; + const Generator5 g5_; + const Generator6 g6_; + const Generator7 g7_; +}; // class CartesianProductHolder7 + +template +class CartesianProductHolder8 { + public: +CartesianProductHolder8(const Generator1& g1, const Generator2& g2, + const Generator3& g3, const Generator4& g4, const Generator5& g5, + const Generator6& g6, const Generator7& g7, const Generator8& g8) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6), g7_(g7), + g8_(g8) {} + template + operator ParamGenerator< ::std::tr1::tuple >() const { + return ParamGenerator< ::std::tr1::tuple >( + new CartesianProductGenerator8( + static_cast >(g1_), + static_cast >(g2_), + static_cast >(g3_), + static_cast >(g4_), + static_cast >(g5_), + static_cast >(g6_), + static_cast >(g7_), + static_cast >(g8_))); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const CartesianProductHolder8& other); + + const Generator1 g1_; + const Generator2 g2_; + const Generator3 g3_; + const Generator4 g4_; + const Generator5 g5_; + const Generator6 g6_; + const Generator7 g7_; + const Generator8 g8_; +}; // class CartesianProductHolder8 + +template +class CartesianProductHolder9 { + public: +CartesianProductHolder9(const Generator1& g1, const Generator2& g2, + const Generator3& g3, const Generator4& g4, const Generator5& g5, + const Generator6& g6, const Generator7& g7, const Generator8& g8, + const Generator9& g9) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6), g7_(g7), g8_(g8), + g9_(g9) {} + template + operator ParamGenerator< ::std::tr1::tuple >() const { + return ParamGenerator< ::std::tr1::tuple >( + new CartesianProductGenerator9( + static_cast >(g1_), + static_cast >(g2_), + static_cast >(g3_), + static_cast >(g4_), + static_cast >(g5_), + static_cast >(g6_), + static_cast >(g7_), + static_cast >(g8_), + static_cast >(g9_))); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const CartesianProductHolder9& other); + + const Generator1 g1_; + const Generator2 g2_; + const Generator3 g3_; + const Generator4 g4_; + const Generator5 g5_; + const Generator6 g6_; + const Generator7 g7_; + const Generator8 g8_; + const Generator9 g9_; +}; // class CartesianProductHolder9 + +template +class CartesianProductHolder10 { + public: +CartesianProductHolder10(const Generator1& g1, const Generator2& g2, + const Generator3& g3, const Generator4& g4, const Generator5& g5, + const Generator6& g6, const Generator7& g7, const Generator8& g8, + const Generator9& g9, const Generator10& g10) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6), g7_(g7), g8_(g8), + g9_(g9), g10_(g10) {} + template + operator ParamGenerator< ::std::tr1::tuple >() const { + return ParamGenerator< ::std::tr1::tuple >( + new CartesianProductGenerator10( + static_cast >(g1_), + static_cast >(g2_), + static_cast >(g3_), + static_cast >(g4_), + static_cast >(g5_), + static_cast >(g6_), + static_cast >(g7_), + static_cast >(g8_), + static_cast >(g9_), + static_cast >(g10_))); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const CartesianProductHolder10& other); + + const Generator1 g1_; + const Generator2 g2_; + const Generator3 g3_; + const Generator4 g4_; + const Generator5 g5_; + const Generator6 g6_; + const Generator7 g7_; + const Generator8 g8_; + const Generator9 g9_; + const Generator10 g10_; +}; // class CartesianProductHolder10 + +# endif // GTEST_HAS_COMBINE + +} // namespace internal +} // namespace testing + +#endif // GTEST_HAS_PARAM_TEST + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_GENERATED_H_ diff --git a/vendor/gmock-1.7.0/gtest/include/gtest/internal/gtest-param-util-generated.h.pump b/vendor/gmock-1.7.0/gtest/include/gtest/internal/gtest-param-util-generated.h.pump new file mode 100644 index 0000000000..009206fd31 --- /dev/null +++ b/vendor/gmock-1.7.0/gtest/include/gtest/internal/gtest-param-util-generated.h.pump @@ -0,0 +1,301 @@ +$$ -*- mode: c++; -*- +$var n = 50 $$ Maximum length of Values arguments we want to support. +$var maxtuple = 10 $$ Maximum number of Combine arguments we want to support. +// Copyright 2008 Google Inc. +// All Rights Reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: vladl@google.com (Vlad Losev) + +// Type and function utilities for implementing parameterized tests. +// This file is generated by a SCRIPT. DO NOT EDIT BY HAND! +// +// Currently Google Test supports at most $n arguments in Values, +// and at most $maxtuple arguments in Combine. Please contact +// googletestframework@googlegroups.com if you need more. +// Please note that the number of arguments to Combine is limited +// by the maximum arity of the implementation of tr1::tuple which is +// currently set at $maxtuple. + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_GENERATED_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_GENERATED_H_ + +// scripts/fuse_gtest.py depends on gtest's own header being #included +// *unconditionally*. Therefore these #includes cannot be moved +// inside #if GTEST_HAS_PARAM_TEST. +#include "gtest/internal/gtest-param-util.h" +#include "gtest/internal/gtest-port.h" + +#if GTEST_HAS_PARAM_TEST + +namespace testing { + +// Forward declarations of ValuesIn(), which is implemented in +// include/gtest/gtest-param-test.h. +template +internal::ParamGenerator< + typename ::testing::internal::IteratorTraits::value_type> +ValuesIn(ForwardIterator begin, ForwardIterator end); + +template +internal::ParamGenerator ValuesIn(const T (&array)[N]); + +template +internal::ParamGenerator ValuesIn( + const Container& container); + +namespace internal { + +// Used in the Values() function to provide polymorphic capabilities. +template +class ValueArray1 { + public: + explicit ValueArray1(T1 v1) : v1_(v1) {} + + template + operator ParamGenerator() const { return ValuesIn(&v1_, &v1_ + 1); } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray1& other); + + const T1 v1_; +}; + +$range i 2..n +$for i [[ +$range j 1..i + +template <$for j, [[typename T$j]]> +class ValueArray$i { + public: + ValueArray$i($for j, [[T$j v$j]]) : $for j, [[v$(j)_(v$j)]] {} + + template + operator ParamGenerator() const { + const T array[] = {$for j, [[static_cast(v$(j)_)]]}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray$i& other); + +$for j [[ + + const T$j v$(j)_; +]] + +}; + +]] + +# if GTEST_HAS_COMBINE +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Generates values from the Cartesian product of values produced +// by the argument generators. +// +$range i 2..maxtuple +$for i [[ +$range j 1..i +$range k 2..i + +template <$for j, [[typename T$j]]> +class CartesianProductGenerator$i + : public ParamGeneratorInterface< ::std::tr1::tuple<$for j, [[T$j]]> > { + public: + typedef ::std::tr1::tuple<$for j, [[T$j]]> ParamType; + + CartesianProductGenerator$i($for j, [[const ParamGenerator& g$j]]) + : $for j, [[g$(j)_(g$j)]] {} + virtual ~CartesianProductGenerator$i() {} + + virtual ParamIteratorInterface* Begin() const { + return new Iterator(this, $for j, [[g$(j)_, g$(j)_.begin()]]); + } + virtual ParamIteratorInterface* End() const { + return new Iterator(this, $for j, [[g$(j)_, g$(j)_.end()]]); + } + + private: + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, $for j, [[ + + const ParamGenerator& g$j, + const typename ParamGenerator::iterator& current$(j)]]) + : base_(base), +$for j, [[ + + begin$(j)_(g$j.begin()), end$(j)_(g$j.end()), current$(j)_(current$j) +]] { + ComputeCurrentValue(); + } + virtual ~Iterator() {} + + virtual const ParamGeneratorInterface* BaseGenerator() const { + return base_; + } + // Advance should not be called on beyond-of-range iterators + // so no component iterators must be beyond end of range, either. + virtual void Advance() { + assert(!AtEnd()); + ++current$(i)_; + +$for k [[ + if (current$(i+2-k)_ == end$(i+2-k)_) { + current$(i+2-k)_ = begin$(i+2-k)_; + ++current$(i+2-k-1)_; + } + +]] + ComputeCurrentValue(); + } + virtual ParamIteratorInterface* Clone() const { + return new Iterator(*this); + } + virtual const ParamType* Current() const { return ¤t_value_; } + virtual bool Equals(const ParamIteratorInterface& other) const { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const Iterator* typed_other = + CheckedDowncastToActualType(&other); + // We must report iterators equal if they both point beyond their + // respective ranges. That can happen in a variety of fashions, + // so we have to consult AtEnd(). + return (AtEnd() && typed_other->AtEnd()) || + ($for j && [[ + + current$(j)_ == typed_other->current$(j)_ +]]); + } + + private: + Iterator(const Iterator& other) + : base_(other.base_), $for j, [[ + + begin$(j)_(other.begin$(j)_), + end$(j)_(other.end$(j)_), + current$(j)_(other.current$(j)_) +]] { + ComputeCurrentValue(); + } + + void ComputeCurrentValue() { + if (!AtEnd()) + current_value_ = ParamType($for j, [[*current$(j)_]]); + } + bool AtEnd() const { + // We must report iterator past the end of the range when either of the + // component iterators has reached the end of its range. + return +$for j || [[ + + current$(j)_ == end$(j)_ +]]; + } + + // No implementation - assignment is unsupported. + void operator=(const Iterator& other); + + const ParamGeneratorInterface* const base_; + // begin[i]_ and end[i]_ define the i-th range that Iterator traverses. + // current[i]_ is the actual traversing iterator. +$for j [[ + + const typename ParamGenerator::iterator begin$(j)_; + const typename ParamGenerator::iterator end$(j)_; + typename ParamGenerator::iterator current$(j)_; +]] + + ParamType current_value_; + }; // class CartesianProductGenerator$i::Iterator + + // No implementation - assignment is unsupported. + void operator=(const CartesianProductGenerator$i& other); + + +$for j [[ + const ParamGenerator g$(j)_; + +]] +}; // class CartesianProductGenerator$i + + +]] + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Helper classes providing Combine() with polymorphic features. They allow +// casting CartesianProductGeneratorN to ParamGenerator if T is +// convertible to U. +// +$range i 2..maxtuple +$for i [[ +$range j 1..i + +template <$for j, [[class Generator$j]]> +class CartesianProductHolder$i { + public: +CartesianProductHolder$i($for j, [[const Generator$j& g$j]]) + : $for j, [[g$(j)_(g$j)]] {} + template <$for j, [[typename T$j]]> + operator ParamGenerator< ::std::tr1::tuple<$for j, [[T$j]]> >() const { + return ParamGenerator< ::std::tr1::tuple<$for j, [[T$j]]> >( + new CartesianProductGenerator$i<$for j, [[T$j]]>( +$for j,[[ + + static_cast >(g$(j)_) +]])); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const CartesianProductHolder$i& other); + + +$for j [[ + const Generator$j g$(j)_; + +]] +}; // class CartesianProductHolder$i + +]] + +# endif // GTEST_HAS_COMBINE + +} // namespace internal +} // namespace testing + +#endif // GTEST_HAS_PARAM_TEST + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_GENERATED_H_ diff --git a/vendor/gmock-1.7.0/gtest/include/gtest/internal/gtest-param-util.h b/vendor/gmock-1.7.0/gtest/include/gtest/internal/gtest-param-util.h new file mode 100644 index 0000000000..d5e1028b0c --- /dev/null +++ b/vendor/gmock-1.7.0/gtest/include/gtest/internal/gtest-param-util.h @@ -0,0 +1,619 @@ +// Copyright 2008 Google Inc. +// All Rights Reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: vladl@google.com (Vlad Losev) + +// Type and function utilities for implementing parameterized tests. + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_H_ + +#include +#include +#include + +// scripts/fuse_gtest.py depends on gtest's own header being #included +// *unconditionally*. Therefore these #includes cannot be moved +// inside #if GTEST_HAS_PARAM_TEST. +#include "gtest/internal/gtest-internal.h" +#include "gtest/internal/gtest-linked_ptr.h" +#include "gtest/internal/gtest-port.h" +#include "gtest/gtest-printers.h" + +#if GTEST_HAS_PARAM_TEST + +namespace testing { +namespace internal { + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Outputs a message explaining invalid registration of different +// fixture class for the same test case. This may happen when +// TEST_P macro is used to define two tests with the same name +// but in different namespaces. +GTEST_API_ void ReportInvalidTestCaseType(const char* test_case_name, + const char* file, int line); + +template class ParamGeneratorInterface; +template class ParamGenerator; + +// Interface for iterating over elements provided by an implementation +// of ParamGeneratorInterface. +template +class ParamIteratorInterface { + public: + virtual ~ParamIteratorInterface() {} + // A pointer to the base generator instance. + // Used only for the purposes of iterator comparison + // to make sure that two iterators belong to the same generator. + virtual const ParamGeneratorInterface* BaseGenerator() const = 0; + // Advances iterator to point to the next element + // provided by the generator. The caller is responsible + // for not calling Advance() on an iterator equal to + // BaseGenerator()->End(). + virtual void Advance() = 0; + // Clones the iterator object. Used for implementing copy semantics + // of ParamIterator. + virtual ParamIteratorInterface* Clone() const = 0; + // Dereferences the current iterator and provides (read-only) access + // to the pointed value. It is the caller's responsibility not to call + // Current() on an iterator equal to BaseGenerator()->End(). + // Used for implementing ParamGenerator::operator*(). + virtual const T* Current() const = 0; + // Determines whether the given iterator and other point to the same + // element in the sequence generated by the generator. + // Used for implementing ParamGenerator::operator==(). + virtual bool Equals(const ParamIteratorInterface& other) const = 0; +}; + +// Class iterating over elements provided by an implementation of +// ParamGeneratorInterface. It wraps ParamIteratorInterface +// and implements the const forward iterator concept. +template +class ParamIterator { + public: + typedef T value_type; + typedef const T& reference; + typedef ptrdiff_t difference_type; + + // ParamIterator assumes ownership of the impl_ pointer. + ParamIterator(const ParamIterator& other) : impl_(other.impl_->Clone()) {} + ParamIterator& operator=(const ParamIterator& other) { + if (this != &other) + impl_.reset(other.impl_->Clone()); + return *this; + } + + const T& operator*() const { return *impl_->Current(); } + const T* operator->() const { return impl_->Current(); } + // Prefix version of operator++. + ParamIterator& operator++() { + impl_->Advance(); + return *this; + } + // Postfix version of operator++. + ParamIterator operator++(int /*unused*/) { + ParamIteratorInterface* clone = impl_->Clone(); + impl_->Advance(); + return ParamIterator(clone); + } + bool operator==(const ParamIterator& other) const { + return impl_.get() == other.impl_.get() || impl_->Equals(*other.impl_); + } + bool operator!=(const ParamIterator& other) const { + return !(*this == other); + } + + private: + friend class ParamGenerator; + explicit ParamIterator(ParamIteratorInterface* impl) : impl_(impl) {} + scoped_ptr > impl_; +}; + +// ParamGeneratorInterface is the binary interface to access generators +// defined in other translation units. +template +class ParamGeneratorInterface { + public: + typedef T ParamType; + + virtual ~ParamGeneratorInterface() {} + + // Generator interface definition + virtual ParamIteratorInterface* Begin() const = 0; + virtual ParamIteratorInterface* End() const = 0; +}; + +// Wraps ParamGeneratorInterface and provides general generator syntax +// compatible with the STL Container concept. +// This class implements copy initialization semantics and the contained +// ParamGeneratorInterface instance is shared among all copies +// of the original object. This is possible because that instance is immutable. +template +class ParamGenerator { + public: + typedef ParamIterator iterator; + + explicit ParamGenerator(ParamGeneratorInterface* impl) : impl_(impl) {} + ParamGenerator(const ParamGenerator& other) : impl_(other.impl_) {} + + ParamGenerator& operator=(const ParamGenerator& other) { + impl_ = other.impl_; + return *this; + } + + iterator begin() const { return iterator(impl_->Begin()); } + iterator end() const { return iterator(impl_->End()); } + + private: + linked_ptr > impl_; +}; + +// Generates values from a range of two comparable values. Can be used to +// generate sequences of user-defined types that implement operator+() and +// operator<(). +// This class is used in the Range() function. +template +class RangeGenerator : public ParamGeneratorInterface { + public: + RangeGenerator(T begin, T end, IncrementT step) + : begin_(begin), end_(end), + step_(step), end_index_(CalculateEndIndex(begin, end, step)) {} + virtual ~RangeGenerator() {} + + virtual ParamIteratorInterface* Begin() const { + return new Iterator(this, begin_, 0, step_); + } + virtual ParamIteratorInterface* End() const { + return new Iterator(this, end_, end_index_, step_); + } + + private: + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, T value, int index, + IncrementT step) + : base_(base), value_(value), index_(index), step_(step) {} + virtual ~Iterator() {} + + virtual const ParamGeneratorInterface* BaseGenerator() const { + return base_; + } + virtual void Advance() { + value_ = value_ + step_; + index_++; + } + virtual ParamIteratorInterface* Clone() const { + return new Iterator(*this); + } + virtual const T* Current() const { return &value_; } + virtual bool Equals(const ParamIteratorInterface& other) const { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const int other_index = + CheckedDowncastToActualType(&other)->index_; + return index_ == other_index; + } + + private: + Iterator(const Iterator& other) + : ParamIteratorInterface(), + base_(other.base_), value_(other.value_), index_(other.index_), + step_(other.step_) {} + + // No implementation - assignment is unsupported. + void operator=(const Iterator& other); + + const ParamGeneratorInterface* const base_; + T value_; + int index_; + const IncrementT step_; + }; // class RangeGenerator::Iterator + + static int CalculateEndIndex(const T& begin, + const T& end, + const IncrementT& step) { + int end_index = 0; + for (T i = begin; i < end; i = i + step) + end_index++; + return end_index; + } + + // No implementation - assignment is unsupported. + void operator=(const RangeGenerator& other); + + const T begin_; + const T end_; + const IncrementT step_; + // The index for the end() iterator. All the elements in the generated + // sequence are indexed (0-based) to aid iterator comparison. + const int end_index_; +}; // class RangeGenerator + + +// Generates values from a pair of STL-style iterators. Used in the +// ValuesIn() function. The elements are copied from the source range +// since the source can be located on the stack, and the generator +// is likely to persist beyond that stack frame. +template +class ValuesInIteratorRangeGenerator : public ParamGeneratorInterface { + public: + template + ValuesInIteratorRangeGenerator(ForwardIterator begin, ForwardIterator end) + : container_(begin, end) {} + virtual ~ValuesInIteratorRangeGenerator() {} + + virtual ParamIteratorInterface* Begin() const { + return new Iterator(this, container_.begin()); + } + virtual ParamIteratorInterface* End() const { + return new Iterator(this, container_.end()); + } + + private: + typedef typename ::std::vector ContainerType; + + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, + typename ContainerType::const_iterator iterator) + : base_(base), iterator_(iterator) {} + virtual ~Iterator() {} + + virtual const ParamGeneratorInterface* BaseGenerator() const { + return base_; + } + virtual void Advance() { + ++iterator_; + value_.reset(); + } + virtual ParamIteratorInterface* Clone() const { + return new Iterator(*this); + } + // We need to use cached value referenced by iterator_ because *iterator_ + // can return a temporary object (and of type other then T), so just + // having "return &*iterator_;" doesn't work. + // value_ is updated here and not in Advance() because Advance() + // can advance iterator_ beyond the end of the range, and we cannot + // detect that fact. The client code, on the other hand, is + // responsible for not calling Current() on an out-of-range iterator. + virtual const T* Current() const { + if (value_.get() == NULL) + value_.reset(new T(*iterator_)); + return value_.get(); + } + virtual bool Equals(const ParamIteratorInterface& other) const { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + return iterator_ == + CheckedDowncastToActualType(&other)->iterator_; + } + + private: + Iterator(const Iterator& other) + // The explicit constructor call suppresses a false warning + // emitted by gcc when supplied with the -Wextra option. + : ParamIteratorInterface(), + base_(other.base_), + iterator_(other.iterator_) {} + + const ParamGeneratorInterface* const base_; + typename ContainerType::const_iterator iterator_; + // A cached value of *iterator_. We keep it here to allow access by + // pointer in the wrapping iterator's operator->(). + // value_ needs to be mutable to be accessed in Current(). + // Use of scoped_ptr helps manage cached value's lifetime, + // which is bound by the lifespan of the iterator itself. + mutable scoped_ptr value_; + }; // class ValuesInIteratorRangeGenerator::Iterator + + // No implementation - assignment is unsupported. + void operator=(const ValuesInIteratorRangeGenerator& other); + + const ContainerType container_; +}; // class ValuesInIteratorRangeGenerator + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Stores a parameter value and later creates tests parameterized with that +// value. +template +class ParameterizedTestFactory : public TestFactoryBase { + public: + typedef typename TestClass::ParamType ParamType; + explicit ParameterizedTestFactory(ParamType parameter) : + parameter_(parameter) {} + virtual Test* CreateTest() { + TestClass::SetParam(¶meter_); + return new TestClass(); + } + + private: + const ParamType parameter_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(ParameterizedTestFactory); +}; + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// TestMetaFactoryBase is a base class for meta-factories that create +// test factories for passing into MakeAndRegisterTestInfo function. +template +class TestMetaFactoryBase { + public: + virtual ~TestMetaFactoryBase() {} + + virtual TestFactoryBase* CreateTestFactory(ParamType parameter) = 0; +}; + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// TestMetaFactory creates test factories for passing into +// MakeAndRegisterTestInfo function. Since MakeAndRegisterTestInfo receives +// ownership of test factory pointer, same factory object cannot be passed +// into that method twice. But ParameterizedTestCaseInfo is going to call +// it for each Test/Parameter value combination. Thus it needs meta factory +// creator class. +template +class TestMetaFactory + : public TestMetaFactoryBase { + public: + typedef typename TestCase::ParamType ParamType; + + TestMetaFactory() {} + + virtual TestFactoryBase* CreateTestFactory(ParamType parameter) { + return new ParameterizedTestFactory(parameter); + } + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(TestMetaFactory); +}; + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// ParameterizedTestCaseInfoBase is a generic interface +// to ParameterizedTestCaseInfo classes. ParameterizedTestCaseInfoBase +// accumulates test information provided by TEST_P macro invocations +// and generators provided by INSTANTIATE_TEST_CASE_P macro invocations +// and uses that information to register all resulting test instances +// in RegisterTests method. The ParameterizeTestCaseRegistry class holds +// a collection of pointers to the ParameterizedTestCaseInfo objects +// and calls RegisterTests() on each of them when asked. +class ParameterizedTestCaseInfoBase { + public: + virtual ~ParameterizedTestCaseInfoBase() {} + + // Base part of test case name for display purposes. + virtual const string& GetTestCaseName() const = 0; + // Test case id to verify identity. + virtual TypeId GetTestCaseTypeId() const = 0; + // UnitTest class invokes this method to register tests in this + // test case right before running them in RUN_ALL_TESTS macro. + // This method should not be called more then once on any single + // instance of a ParameterizedTestCaseInfoBase derived class. + virtual void RegisterTests() = 0; + + protected: + ParameterizedTestCaseInfoBase() {} + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(ParameterizedTestCaseInfoBase); +}; + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// ParameterizedTestCaseInfo accumulates tests obtained from TEST_P +// macro invocations for a particular test case and generators +// obtained from INSTANTIATE_TEST_CASE_P macro invocations for that +// test case. It registers tests with all values generated by all +// generators when asked. +template +class ParameterizedTestCaseInfo : public ParameterizedTestCaseInfoBase { + public: + // ParamType and GeneratorCreationFunc are private types but are required + // for declarations of public methods AddTestPattern() and + // AddTestCaseInstantiation(). + typedef typename TestCase::ParamType ParamType; + // A function that returns an instance of appropriate generator type. + typedef ParamGenerator(GeneratorCreationFunc)(); + + explicit ParameterizedTestCaseInfo(const char* name) + : test_case_name_(name) {} + + // Test case base name for display purposes. + virtual const string& GetTestCaseName() const { return test_case_name_; } + // Test case id to verify identity. + virtual TypeId GetTestCaseTypeId() const { return GetTypeId(); } + // TEST_P macro uses AddTestPattern() to record information + // about a single test in a LocalTestInfo structure. + // test_case_name is the base name of the test case (without invocation + // prefix). test_base_name is the name of an individual test without + // parameter index. For the test SequenceA/FooTest.DoBar/1 FooTest is + // test case base name and DoBar is test base name. + void AddTestPattern(const char* test_case_name, + const char* test_base_name, + TestMetaFactoryBase* meta_factory) { + tests_.push_back(linked_ptr(new TestInfo(test_case_name, + test_base_name, + meta_factory))); + } + // INSTANTIATE_TEST_CASE_P macro uses AddGenerator() to record information + // about a generator. + int AddTestCaseInstantiation(const string& instantiation_name, + GeneratorCreationFunc* func, + const char* /* file */, + int /* line */) { + instantiations_.push_back(::std::make_pair(instantiation_name, func)); + return 0; // Return value used only to run this method in namespace scope. + } + // UnitTest class invokes this method to register tests in this test case + // test cases right before running tests in RUN_ALL_TESTS macro. + // This method should not be called more then once on any single + // instance of a ParameterizedTestCaseInfoBase derived class. + // UnitTest has a guard to prevent from calling this method more then once. + virtual void RegisterTests() { + for (typename TestInfoContainer::iterator test_it = tests_.begin(); + test_it != tests_.end(); ++test_it) { + linked_ptr test_info = *test_it; + for (typename InstantiationContainer::iterator gen_it = + instantiations_.begin(); gen_it != instantiations_.end(); + ++gen_it) { + const string& instantiation_name = gen_it->first; + ParamGenerator generator((*gen_it->second)()); + + string test_case_name; + if ( !instantiation_name.empty() ) + test_case_name = instantiation_name + "/"; + test_case_name += test_info->test_case_base_name; + + int i = 0; + for (typename ParamGenerator::iterator param_it = + generator.begin(); + param_it != generator.end(); ++param_it, ++i) { + Message test_name_stream; + test_name_stream << test_info->test_base_name << "/" << i; + MakeAndRegisterTestInfo( + test_case_name.c_str(), + test_name_stream.GetString().c_str(), + NULL, // No type parameter. + PrintToString(*param_it).c_str(), + GetTestCaseTypeId(), + TestCase::SetUpTestCase, + TestCase::TearDownTestCase, + test_info->test_meta_factory->CreateTestFactory(*param_it)); + } // for param_it + } // for gen_it + } // for test_it + } // RegisterTests + + private: + // LocalTestInfo structure keeps information about a single test registered + // with TEST_P macro. + struct TestInfo { + TestInfo(const char* a_test_case_base_name, + const char* a_test_base_name, + TestMetaFactoryBase* a_test_meta_factory) : + test_case_base_name(a_test_case_base_name), + test_base_name(a_test_base_name), + test_meta_factory(a_test_meta_factory) {} + + const string test_case_base_name; + const string test_base_name; + const scoped_ptr > test_meta_factory; + }; + typedef ::std::vector > TestInfoContainer; + // Keeps pairs of + // received from INSTANTIATE_TEST_CASE_P macros. + typedef ::std::vector > + InstantiationContainer; + + const string test_case_name_; + TestInfoContainer tests_; + InstantiationContainer instantiations_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(ParameterizedTestCaseInfo); +}; // class ParameterizedTestCaseInfo + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// ParameterizedTestCaseRegistry contains a map of ParameterizedTestCaseInfoBase +// classes accessed by test case names. TEST_P and INSTANTIATE_TEST_CASE_P +// macros use it to locate their corresponding ParameterizedTestCaseInfo +// descriptors. +class ParameterizedTestCaseRegistry { + public: + ParameterizedTestCaseRegistry() {} + ~ParameterizedTestCaseRegistry() { + for (TestCaseInfoContainer::iterator it = test_case_infos_.begin(); + it != test_case_infos_.end(); ++it) { + delete *it; + } + } + + // Looks up or creates and returns a structure containing information about + // tests and instantiations of a particular test case. + template + ParameterizedTestCaseInfo* GetTestCasePatternHolder( + const char* test_case_name, + const char* file, + int line) { + ParameterizedTestCaseInfo* typed_test_info = NULL; + for (TestCaseInfoContainer::iterator it = test_case_infos_.begin(); + it != test_case_infos_.end(); ++it) { + if ((*it)->GetTestCaseName() == test_case_name) { + if ((*it)->GetTestCaseTypeId() != GetTypeId()) { + // Complain about incorrect usage of Google Test facilities + // and terminate the program since we cannot guaranty correct + // test case setup and tear-down in this case. + ReportInvalidTestCaseType(test_case_name, file, line); + posix::Abort(); + } else { + // At this point we are sure that the object we found is of the same + // type we are looking for, so we downcast it to that type + // without further checks. + typed_test_info = CheckedDowncastToActualType< + ParameterizedTestCaseInfo >(*it); + } + break; + } + } + if (typed_test_info == NULL) { + typed_test_info = new ParameterizedTestCaseInfo(test_case_name); + test_case_infos_.push_back(typed_test_info); + } + return typed_test_info; + } + void RegisterTests() { + for (TestCaseInfoContainer::iterator it = test_case_infos_.begin(); + it != test_case_infos_.end(); ++it) { + (*it)->RegisterTests(); + } + } + + private: + typedef ::std::vector TestCaseInfoContainer; + + TestCaseInfoContainer test_case_infos_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(ParameterizedTestCaseRegistry); +}; + +} // namespace internal +} // namespace testing + +#endif // GTEST_HAS_PARAM_TEST + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_H_ diff --git a/vendor/gmock-1.7.0/gtest/include/gtest/internal/gtest-port.h b/vendor/gmock-1.7.0/gtest/include/gtest/internal/gtest-port.h new file mode 100644 index 0000000000..dc4fe0cb6b --- /dev/null +++ b/vendor/gmock-1.7.0/gtest/include/gtest/internal/gtest-port.h @@ -0,0 +1,1947 @@ +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Authors: wan@google.com (Zhanyong Wan) +// +// Low-level types and utilities for porting Google Test to various +// platforms. They are subject to change without notice. DO NOT USE +// THEM IN USER CODE. +// +// This file is fundamental to Google Test. All other Google Test source +// files are expected to #include this. Therefore, it cannot #include +// any other Google Test header. + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_H_ + +// The user can define the following macros in the build script to +// control Google Test's behavior. If the user doesn't define a macro +// in this list, Google Test will define it. +// +// GTEST_HAS_CLONE - Define it to 1/0 to indicate that clone(2) +// is/isn't available. +// GTEST_HAS_EXCEPTIONS - Define it to 1/0 to indicate that exceptions +// are enabled. +// GTEST_HAS_GLOBAL_STRING - Define it to 1/0 to indicate that ::string +// is/isn't available (some systems define +// ::string, which is different to std::string). +// GTEST_HAS_GLOBAL_WSTRING - Define it to 1/0 to indicate that ::string +// is/isn't available (some systems define +// ::wstring, which is different to std::wstring). +// GTEST_HAS_POSIX_RE - Define it to 1/0 to indicate that POSIX regular +// expressions are/aren't available. +// GTEST_HAS_PTHREAD - Define it to 1/0 to indicate that +// is/isn't available. +// GTEST_HAS_RTTI - Define it to 1/0 to indicate that RTTI is/isn't +// enabled. +// GTEST_HAS_STD_WSTRING - Define it to 1/0 to indicate that +// std::wstring does/doesn't work (Google Test can +// be used where std::wstring is unavailable). +// GTEST_HAS_TR1_TUPLE - Define it to 1/0 to indicate tr1::tuple +// is/isn't available. +// GTEST_HAS_SEH - Define it to 1/0 to indicate whether the +// compiler supports Microsoft's "Structured +// Exception Handling". +// GTEST_HAS_STREAM_REDIRECTION +// - Define it to 1/0 to indicate whether the +// platform supports I/O stream redirection using +// dup() and dup2(). +// GTEST_USE_OWN_TR1_TUPLE - Define it to 1/0 to indicate whether Google +// Test's own tr1 tuple implementation should be +// used. Unused when the user sets +// GTEST_HAS_TR1_TUPLE to 0. +// GTEST_LANG_CXX11 - Define it to 1/0 to indicate that Google Test +// is building in C++11/C++98 mode. +// GTEST_LINKED_AS_SHARED_LIBRARY +// - Define to 1 when compiling tests that use +// Google Test as a shared library (known as +// DLL on Windows). +// GTEST_CREATE_SHARED_LIBRARY +// - Define to 1 when compiling Google Test itself +// as a shared library. + +// This header defines the following utilities: +// +// Macros indicating the current platform (defined to 1 if compiled on +// the given platform; otherwise undefined): +// GTEST_OS_AIX - IBM AIX +// GTEST_OS_CYGWIN - Cygwin +// GTEST_OS_HPUX - HP-UX +// GTEST_OS_LINUX - Linux +// GTEST_OS_LINUX_ANDROID - Google Android +// GTEST_OS_MAC - Mac OS X +// GTEST_OS_IOS - iOS +// GTEST_OS_IOS_SIMULATOR - iOS simulator +// GTEST_OS_NACL - Google Native Client (NaCl) +// GTEST_OS_OPENBSD - OpenBSD +// GTEST_OS_QNX - QNX +// GTEST_OS_SOLARIS - Sun Solaris +// GTEST_OS_SYMBIAN - Symbian +// GTEST_OS_WINDOWS - Windows (Desktop, MinGW, or Mobile) +// GTEST_OS_WINDOWS_DESKTOP - Windows Desktop +// GTEST_OS_WINDOWS_MINGW - MinGW +// GTEST_OS_WINDOWS_MOBILE - Windows Mobile +// GTEST_OS_ZOS - z/OS +// +// Among the platforms, Cygwin, Linux, Max OS X, and Windows have the +// most stable support. Since core members of the Google Test project +// don't have access to other platforms, support for them may be less +// stable. If you notice any problems on your platform, please notify +// googletestframework@googlegroups.com (patches for fixing them are +// even more welcome!). +// +// Note that it is possible that none of the GTEST_OS_* macros are defined. +// +// Macros indicating available Google Test features (defined to 1 if +// the corresponding feature is supported; otherwise undefined): +// GTEST_HAS_COMBINE - the Combine() function (for value-parameterized +// tests) +// GTEST_HAS_DEATH_TEST - death tests +// GTEST_HAS_PARAM_TEST - value-parameterized tests +// GTEST_HAS_TYPED_TEST - typed tests +// GTEST_HAS_TYPED_TEST_P - type-parameterized tests +// GTEST_USES_POSIX_RE - enhanced POSIX regex is used. Do not confuse with +// GTEST_HAS_POSIX_RE (see above) which users can +// define themselves. +// GTEST_USES_SIMPLE_RE - our own simple regex is used; +// the above two are mutually exclusive. +// GTEST_CAN_COMPARE_NULL - accepts untyped NULL in EXPECT_EQ(). +// +// Macros for basic C++ coding: +// GTEST_AMBIGUOUS_ELSE_BLOCKER_ - for disabling a gcc warning. +// GTEST_ATTRIBUTE_UNUSED_ - declares that a class' instances or a +// variable don't have to be used. +// GTEST_DISALLOW_ASSIGN_ - disables operator=. +// GTEST_DISALLOW_COPY_AND_ASSIGN_ - disables copy ctor and operator=. +// GTEST_MUST_USE_RESULT_ - declares that a function's result must be used. +// +// Synchronization: +// Mutex, MutexLock, ThreadLocal, GetThreadCount() +// - synchronization primitives. +// GTEST_IS_THREADSAFE - defined to 1 to indicate that the above +// synchronization primitives have real implementations +// and Google Test is thread-safe; or 0 otherwise. +// +// Template meta programming: +// is_pointer - as in TR1; needed on Symbian and IBM XL C/C++ only. +// IteratorTraits - partial implementation of std::iterator_traits, which +// is not available in libCstd when compiled with Sun C++. +// +// Smart pointers: +// scoped_ptr - as in TR2. +// +// Regular expressions: +// RE - a simple regular expression class using the POSIX +// Extended Regular Expression syntax on UNIX-like +// platforms, or a reduced regular exception syntax on +// other platforms, including Windows. +// +// Logging: +// GTEST_LOG_() - logs messages at the specified severity level. +// LogToStderr() - directs all log messages to stderr. +// FlushInfoLog() - flushes informational log messages. +// +// Stdout and stderr capturing: +// CaptureStdout() - starts capturing stdout. +// GetCapturedStdout() - stops capturing stdout and returns the captured +// string. +// CaptureStderr() - starts capturing stderr. +// GetCapturedStderr() - stops capturing stderr and returns the captured +// string. +// +// Integer types: +// TypeWithSize - maps an integer to a int type. +// Int32, UInt32, Int64, UInt64, TimeInMillis +// - integers of known sizes. +// BiggestInt - the biggest signed integer type. +// +// Command-line utilities: +// GTEST_FLAG() - references a flag. +// GTEST_DECLARE_*() - declares a flag. +// GTEST_DEFINE_*() - defines a flag. +// GetInjectableArgvs() - returns the command line as a vector of strings. +// +// Environment variable utilities: +// GetEnv() - gets the value of an environment variable. +// BoolFromGTestEnv() - parses a bool environment variable. +// Int32FromGTestEnv() - parses an Int32 environment variable. +// StringFromGTestEnv() - parses a string environment variable. + +#include // for isspace, etc +#include // for ptrdiff_t +#include +#include +#include +#ifndef _WIN32_WCE +# include +# include +#endif // !_WIN32_WCE + +#if defined __APPLE__ +# include +# include +#endif + +#include // NOLINT +#include // NOLINT +#include // NOLINT + +#define GTEST_DEV_EMAIL_ "googletestframework@@googlegroups.com" +#define GTEST_FLAG_PREFIX_ "gtest_" +#define GTEST_FLAG_PREFIX_DASH_ "gtest-" +#define GTEST_FLAG_PREFIX_UPPER_ "GTEST_" +#define GTEST_NAME_ "Google Test" +#define GTEST_PROJECT_URL_ "http://code.google.com/p/googletest/" + +// Determines the version of gcc that is used to compile this. +#ifdef __GNUC__ +// 40302 means version 4.3.2. +# define GTEST_GCC_VER_ \ + (__GNUC__*10000 + __GNUC_MINOR__*100 + __GNUC_PATCHLEVEL__) +#endif // __GNUC__ + +// Determines the platform on which Google Test is compiled. +#ifdef __CYGWIN__ +# define GTEST_OS_CYGWIN 1 +#elif defined __SYMBIAN32__ +# define GTEST_OS_SYMBIAN 1 +#elif defined _WIN32 +# define GTEST_OS_WINDOWS 1 +# ifdef _WIN32_WCE +# define GTEST_OS_WINDOWS_MOBILE 1 +# elif defined(__MINGW__) || defined(__MINGW32__) +# define GTEST_OS_WINDOWS_MINGW 1 +# else +# define GTEST_OS_WINDOWS_DESKTOP 1 +# endif // _WIN32_WCE +#elif defined __APPLE__ +# define GTEST_OS_MAC 1 +# if TARGET_OS_IPHONE +# define GTEST_OS_IOS 1 +# if TARGET_IPHONE_SIMULATOR +# define GTEST_OS_IOS_SIMULATOR 1 +# endif +# endif +#elif defined __linux__ +# define GTEST_OS_LINUX 1 +# if defined __ANDROID__ +# define GTEST_OS_LINUX_ANDROID 1 +# endif +#elif defined __MVS__ +# define GTEST_OS_ZOS 1 +#elif defined(__sun) && defined(__SVR4) +# define GTEST_OS_SOLARIS 1 +#elif defined(_AIX) +# define GTEST_OS_AIX 1 +#elif defined(__hpux) +# define GTEST_OS_HPUX 1 +#elif defined __native_client__ +# define GTEST_OS_NACL 1 +#elif defined __OpenBSD__ +# define GTEST_OS_OPENBSD 1 +#elif defined __QNX__ +# define GTEST_OS_QNX 1 +#endif // __CYGWIN__ + +#ifndef GTEST_LANG_CXX11 +// gcc and clang define __GXX_EXPERIMENTAL_CXX0X__ when +// -std={c,gnu}++{0x,11} is passed. The C++11 standard specifies a +// value for __cplusplus, and recent versions of clang, gcc, and +// probably other compilers set that too in C++11 mode. +# if __GXX_EXPERIMENTAL_CXX0X__ || __cplusplus >= 201103L +// Compiling in at least C++11 mode. +# define GTEST_LANG_CXX11 1 +# else +# define GTEST_LANG_CXX11 0 +# endif +#endif + +// Brings in definitions for functions used in the testing::internal::posix +// namespace (read, write, close, chdir, isatty, stat). We do not currently +// use them on Windows Mobile. +#if !GTEST_OS_WINDOWS +// This assumes that non-Windows OSes provide unistd.h. For OSes where this +// is not the case, we need to include headers that provide the functions +// mentioned above. +# include +# include +#elif !GTEST_OS_WINDOWS_MOBILE +# include +# include +#endif + +#if GTEST_OS_LINUX_ANDROID +// Used to define __ANDROID_API__ matching the target NDK API level. +# include // NOLINT +#endif + +// Defines this to true iff Google Test can use POSIX regular expressions. +#ifndef GTEST_HAS_POSIX_RE +# if GTEST_OS_LINUX_ANDROID +// On Android, is only available starting with Gingerbread. +# define GTEST_HAS_POSIX_RE (__ANDROID_API__ >= 9) +# else +# define GTEST_HAS_POSIX_RE (!GTEST_OS_WINDOWS) +# endif +#endif + +#if GTEST_HAS_POSIX_RE + +// On some platforms, needs someone to define size_t, and +// won't compile otherwise. We can #include it here as we already +// included , which is guaranteed to define size_t through +// . +# include // NOLINT + +# define GTEST_USES_POSIX_RE 1 + +#elif GTEST_OS_WINDOWS + +// is not available on Windows. Use our own simple regex +// implementation instead. +# define GTEST_USES_SIMPLE_RE 1 + +#else + +// may not be available on this platform. Use our own +// simple regex implementation instead. +# define GTEST_USES_SIMPLE_RE 1 + +#endif // GTEST_HAS_POSIX_RE + +#ifndef GTEST_HAS_EXCEPTIONS +// The user didn't tell us whether exceptions are enabled, so we need +// to figure it out. +# if defined(_MSC_VER) || defined(__BORLANDC__) +// MSVC's and C++Builder's implementations of the STL use the _HAS_EXCEPTIONS +// macro to enable exceptions, so we'll do the same. +// Assumes that exceptions are enabled by default. +# ifndef _HAS_EXCEPTIONS +# define _HAS_EXCEPTIONS 1 +# endif // _HAS_EXCEPTIONS +# define GTEST_HAS_EXCEPTIONS _HAS_EXCEPTIONS +# elif defined(__GNUC__) && __EXCEPTIONS +// gcc defines __EXCEPTIONS to 1 iff exceptions are enabled. +# define GTEST_HAS_EXCEPTIONS 1 +# elif defined(__SUNPRO_CC) +// Sun Pro CC supports exceptions. However, there is no compile-time way of +// detecting whether they are enabled or not. Therefore, we assume that +// they are enabled unless the user tells us otherwise. +# define GTEST_HAS_EXCEPTIONS 1 +# elif defined(__IBMCPP__) && __EXCEPTIONS +// xlC defines __EXCEPTIONS to 1 iff exceptions are enabled. +# define GTEST_HAS_EXCEPTIONS 1 +# elif defined(__HP_aCC) +// Exception handling is in effect by default in HP aCC compiler. It has to +// be turned of by +noeh compiler option if desired. +# define GTEST_HAS_EXCEPTIONS 1 +# else +// For other compilers, we assume exceptions are disabled to be +// conservative. +# define GTEST_HAS_EXCEPTIONS 0 +# endif // defined(_MSC_VER) || defined(__BORLANDC__) +#endif // GTEST_HAS_EXCEPTIONS + +#if !defined(GTEST_HAS_STD_STRING) +// Even though we don't use this macro any longer, we keep it in case +// some clients still depend on it. +# define GTEST_HAS_STD_STRING 1 +#elif !GTEST_HAS_STD_STRING +// The user told us that ::std::string isn't available. +# error "Google Test cannot be used where ::std::string isn't available." +#endif // !defined(GTEST_HAS_STD_STRING) + +#ifndef GTEST_HAS_GLOBAL_STRING +// The user didn't tell us whether ::string is available, so we need +// to figure it out. + +# define GTEST_HAS_GLOBAL_STRING 0 + +#endif // GTEST_HAS_GLOBAL_STRING + +#ifndef GTEST_HAS_STD_WSTRING +// The user didn't tell us whether ::std::wstring is available, so we need +// to figure it out. +// TODO(wan@google.com): uses autoconf to detect whether ::std::wstring +// is available. + +// Cygwin 1.7 and below doesn't support ::std::wstring. +// Solaris' libc++ doesn't support it either. Android has +// no support for it at least as recent as Froyo (2.2). +# define GTEST_HAS_STD_WSTRING \ + (!(GTEST_OS_LINUX_ANDROID || GTEST_OS_CYGWIN || GTEST_OS_SOLARIS)) + +#endif // GTEST_HAS_STD_WSTRING + +#ifndef GTEST_HAS_GLOBAL_WSTRING +// The user didn't tell us whether ::wstring is available, so we need +// to figure it out. +# define GTEST_HAS_GLOBAL_WSTRING \ + (GTEST_HAS_STD_WSTRING && GTEST_HAS_GLOBAL_STRING) +#endif // GTEST_HAS_GLOBAL_WSTRING + +// Determines whether RTTI is available. +#ifndef GTEST_HAS_RTTI +// The user didn't tell us whether RTTI is enabled, so we need to +// figure it out. + +# ifdef _MSC_VER + +# ifdef _CPPRTTI // MSVC defines this macro iff RTTI is enabled. +# define GTEST_HAS_RTTI 1 +# else +# define GTEST_HAS_RTTI 0 +# endif + +// Starting with version 4.3.2, gcc defines __GXX_RTTI iff RTTI is enabled. +# elif defined(__GNUC__) && (GTEST_GCC_VER_ >= 40302) + +# ifdef __GXX_RTTI +// When building against STLport with the Android NDK and with +// -frtti -fno-exceptions, the build fails at link time with undefined +// references to __cxa_bad_typeid. Note sure if STL or toolchain bug, +// so disable RTTI when detected. +# if GTEST_OS_LINUX_ANDROID && defined(_STLPORT_MAJOR) && \ + !defined(__EXCEPTIONS) +# define GTEST_HAS_RTTI 0 +# else +# define GTEST_HAS_RTTI 1 +# endif // GTEST_OS_LINUX_ANDROID && __STLPORT_MAJOR && !__EXCEPTIONS +# else +# define GTEST_HAS_RTTI 0 +# endif // __GXX_RTTI + +// Clang defines __GXX_RTTI starting with version 3.0, but its manual recommends +// using has_feature instead. has_feature(cxx_rtti) is supported since 2.7, the +// first version with C++ support. +# elif defined(__clang__) + +# define GTEST_HAS_RTTI __has_feature(cxx_rtti) + +// Starting with version 9.0 IBM Visual Age defines __RTTI_ALL__ to 1 if +// both the typeid and dynamic_cast features are present. +# elif defined(__IBMCPP__) && (__IBMCPP__ >= 900) + +# ifdef __RTTI_ALL__ +# define GTEST_HAS_RTTI 1 +# else +# define GTEST_HAS_RTTI 0 +# endif + +# else + +// For all other compilers, we assume RTTI is enabled. +# define GTEST_HAS_RTTI 1 + +# endif // _MSC_VER + +#endif // GTEST_HAS_RTTI + +// It's this header's responsibility to #include when RTTI +// is enabled. +#if GTEST_HAS_RTTI +# include +#endif + +// Determines whether Google Test can use the pthreads library. +#ifndef GTEST_HAS_PTHREAD +// The user didn't tell us explicitly, so we assume pthreads support is +// available on Linux and Mac. +// +// To disable threading support in Google Test, add -DGTEST_HAS_PTHREAD=0 +// to your compiler flags. +# define GTEST_HAS_PTHREAD (GTEST_OS_LINUX || GTEST_OS_MAC || GTEST_OS_HPUX \ + || GTEST_OS_QNX) +#endif // GTEST_HAS_PTHREAD + +#if GTEST_HAS_PTHREAD +// gtest-port.h guarantees to #include when GTEST_HAS_PTHREAD is +// true. +# include // NOLINT + +// For timespec and nanosleep, used below. +# include // NOLINT +#endif + +// Determines whether Google Test can use tr1/tuple. You can define +// this macro to 0 to prevent Google Test from using tuple (any +// feature depending on tuple with be disabled in this mode). +#ifndef GTEST_HAS_TR1_TUPLE +# if GTEST_OS_LINUX_ANDROID && defined(_STLPORT_MAJOR) +// STLport, provided with the Android NDK, has neither or . +# define GTEST_HAS_TR1_TUPLE 0 +# else +// The user didn't tell us not to do it, so we assume it's OK. +# define GTEST_HAS_TR1_TUPLE 1 +# endif +#endif // GTEST_HAS_TR1_TUPLE + +// Determines whether Google Test's own tr1 tuple implementation +// should be used. +#ifndef GTEST_USE_OWN_TR1_TUPLE +// The user didn't tell us, so we need to figure it out. + +// We use our own TR1 tuple if we aren't sure the user has an +// implementation of it already. At this time, libstdc++ 4.0.0+ and +// MSVC 2010 are the only mainstream standard libraries that come +// with a TR1 tuple implementation. NVIDIA's CUDA NVCC compiler +// pretends to be GCC by defining __GNUC__ and friends, but cannot +// compile GCC's tuple implementation. MSVC 2008 (9.0) provides TR1 +// tuple in a 323 MB Feature Pack download, which we cannot assume the +// user has. QNX's QCC compiler is a modified GCC but it doesn't +// support TR1 tuple. libc++ only provides std::tuple, in C++11 mode, +// and it can be used with some compilers that define __GNUC__. +# if (defined(__GNUC__) && !defined(__CUDACC__) && (GTEST_GCC_VER_ >= 40000) \ + && !GTEST_OS_QNX && !defined(_LIBCPP_VERSION)) || _MSC_VER >= 1600 +# define GTEST_ENV_HAS_TR1_TUPLE_ 1 +# endif + +// C++11 specifies that provides std::tuple. Use that if gtest is used +// in C++11 mode and libstdc++ isn't very old (binaries targeting OS X 10.6 +// can build with clang but need to use gcc4.2's libstdc++). +# if GTEST_LANG_CXX11 && (!defined(__GLIBCXX__) || __GLIBCXX__ > 20110325) +# define GTEST_ENV_HAS_STD_TUPLE_ 1 +# endif + +# if GTEST_ENV_HAS_TR1_TUPLE_ || GTEST_ENV_HAS_STD_TUPLE_ +# define GTEST_USE_OWN_TR1_TUPLE 0 +# else +# define GTEST_USE_OWN_TR1_TUPLE 1 +# endif + +#endif // GTEST_USE_OWN_TR1_TUPLE + +// To avoid conditional compilation everywhere, we make it +// gtest-port.h's responsibility to #include the header implementing +// tr1/tuple. +#if GTEST_HAS_TR1_TUPLE + +# if GTEST_USE_OWN_TR1_TUPLE +# include "gtest/internal/gtest-tuple.h" +# elif GTEST_ENV_HAS_STD_TUPLE_ +# include +// C++11 puts its tuple into the ::std namespace rather than +// ::std::tr1. gtest expects tuple to live in ::std::tr1, so put it there. +// This causes undefined behavior, but supported compilers react in +// the way we intend. +namespace std { +namespace tr1 { +using ::std::get; +using ::std::make_tuple; +using ::std::tuple; +using ::std::tuple_element; +using ::std::tuple_size; +} +} + +# elif GTEST_OS_SYMBIAN + +// On Symbian, BOOST_HAS_TR1_TUPLE causes Boost's TR1 tuple library to +// use STLport's tuple implementation, which unfortunately doesn't +// work as the copy of STLport distributed with Symbian is incomplete. +// By making sure BOOST_HAS_TR1_TUPLE is undefined, we force Boost to +// use its own tuple implementation. +# ifdef BOOST_HAS_TR1_TUPLE +# undef BOOST_HAS_TR1_TUPLE +# endif // BOOST_HAS_TR1_TUPLE + +// This prevents , which defines +// BOOST_HAS_TR1_TUPLE, from being #included by Boost's . +# define BOOST_TR1_DETAIL_CONFIG_HPP_INCLUDED +# include + +# elif defined(__GNUC__) && (GTEST_GCC_VER_ >= 40000) +// GCC 4.0+ implements tr1/tuple in the header. This does +// not conform to the TR1 spec, which requires the header to be . + +# if !GTEST_HAS_RTTI && GTEST_GCC_VER_ < 40302 +// Until version 4.3.2, gcc has a bug that causes , +// which is #included by , to not compile when RTTI is +// disabled. _TR1_FUNCTIONAL is the header guard for +// . Hence the following #define is a hack to prevent +// from being included. +# define _TR1_FUNCTIONAL 1 +# include +# undef _TR1_FUNCTIONAL // Allows the user to #include + // if he chooses to. +# else +# include // NOLINT +# endif // !GTEST_HAS_RTTI && GTEST_GCC_VER_ < 40302 + +# else +// If the compiler is not GCC 4.0+, we assume the user is using a +// spec-conforming TR1 implementation. +# include // NOLINT +# endif // GTEST_USE_OWN_TR1_TUPLE + +#endif // GTEST_HAS_TR1_TUPLE + +// Determines whether clone(2) is supported. +// Usually it will only be available on Linux, excluding +// Linux on the Itanium architecture. +// Also see http://linux.die.net/man/2/clone. +#ifndef GTEST_HAS_CLONE +// The user didn't tell us, so we need to figure it out. + +# if GTEST_OS_LINUX && !defined(__ia64__) +# if GTEST_OS_LINUX_ANDROID +// On Android, clone() is only available on ARM starting with Gingerbread. +# if defined(__arm__) && __ANDROID_API__ >= 9 +# define GTEST_HAS_CLONE 1 +# else +# define GTEST_HAS_CLONE 0 +# endif +# else +# define GTEST_HAS_CLONE 1 +# endif +# else +# define GTEST_HAS_CLONE 0 +# endif // GTEST_OS_LINUX && !defined(__ia64__) + +#endif // GTEST_HAS_CLONE + +// Determines whether to support stream redirection. This is used to test +// output correctness and to implement death tests. +#ifndef GTEST_HAS_STREAM_REDIRECTION +// By default, we assume that stream redirection is supported on all +// platforms except known mobile ones. +# if GTEST_OS_WINDOWS_MOBILE || GTEST_OS_SYMBIAN +# define GTEST_HAS_STREAM_REDIRECTION 0 +# else +# define GTEST_HAS_STREAM_REDIRECTION 1 +# endif // !GTEST_OS_WINDOWS_MOBILE && !GTEST_OS_SYMBIAN +#endif // GTEST_HAS_STREAM_REDIRECTION + +// Determines whether to support death tests. +// Google Test does not support death tests for VC 7.1 and earlier as +// abort() in a VC 7.1 application compiled as GUI in debug config +// pops up a dialog window that cannot be suppressed programmatically. +#if (GTEST_OS_LINUX || GTEST_OS_CYGWIN || GTEST_OS_SOLARIS || \ + (GTEST_OS_MAC && !GTEST_OS_IOS) || GTEST_OS_IOS_SIMULATOR || \ + (GTEST_OS_WINDOWS_DESKTOP && _MSC_VER >= 1400) || \ + GTEST_OS_WINDOWS_MINGW || GTEST_OS_AIX || GTEST_OS_HPUX || \ + GTEST_OS_OPENBSD || GTEST_OS_QNX) +# define GTEST_HAS_DEATH_TEST 1 +# include // NOLINT +#endif + +// We don't support MSVC 7.1 with exceptions disabled now. Therefore +// all the compilers we care about are adequate for supporting +// value-parameterized tests. +#define GTEST_HAS_PARAM_TEST 1 + +// Determines whether to support type-driven tests. + +// Typed tests need and variadic macros, which GCC, VC++ 8.0, +// Sun Pro CC, IBM Visual Age, and HP aCC support. +#if defined(__GNUC__) || (_MSC_VER >= 1400) || defined(__SUNPRO_CC) || \ + defined(__IBMCPP__) || defined(__HP_aCC) +# define GTEST_HAS_TYPED_TEST 1 +# define GTEST_HAS_TYPED_TEST_P 1 +#endif + +// Determines whether to support Combine(). This only makes sense when +// value-parameterized tests are enabled. The implementation doesn't +// work on Sun Studio since it doesn't understand templated conversion +// operators. +#if GTEST_HAS_PARAM_TEST && GTEST_HAS_TR1_TUPLE && !defined(__SUNPRO_CC) +# define GTEST_HAS_COMBINE 1 +#endif + +// Determines whether the system compiler uses UTF-16 for encoding wide strings. +#define GTEST_WIDE_STRING_USES_UTF16_ \ + (GTEST_OS_WINDOWS || GTEST_OS_CYGWIN || GTEST_OS_SYMBIAN || GTEST_OS_AIX) + +// Determines whether test results can be streamed to a socket. +#if GTEST_OS_LINUX +# define GTEST_CAN_STREAM_RESULTS_ 1 +#endif + +// Defines some utility macros. + +// The GNU compiler emits a warning if nested "if" statements are followed by +// an "else" statement and braces are not used to explicitly disambiguate the +// "else" binding. This leads to problems with code like: +// +// if (gate) +// ASSERT_*(condition) << "Some message"; +// +// The "switch (0) case 0:" idiom is used to suppress this. +#ifdef __INTEL_COMPILER +# define GTEST_AMBIGUOUS_ELSE_BLOCKER_ +#else +# define GTEST_AMBIGUOUS_ELSE_BLOCKER_ switch (0) case 0: default: // NOLINT +#endif + +// Use this annotation at the end of a struct/class definition to +// prevent the compiler from optimizing away instances that are never +// used. This is useful when all interesting logic happens inside the +// c'tor and / or d'tor. Example: +// +// struct Foo { +// Foo() { ... } +// } GTEST_ATTRIBUTE_UNUSED_; +// +// Also use it after a variable or parameter declaration to tell the +// compiler the variable/parameter does not have to be used. +#if defined(__GNUC__) && !defined(COMPILER_ICC) +# define GTEST_ATTRIBUTE_UNUSED_ __attribute__ ((unused)) +#else +# define GTEST_ATTRIBUTE_UNUSED_ +#endif + +// A macro to disallow operator= +// This should be used in the private: declarations for a class. +#define GTEST_DISALLOW_ASSIGN_(type)\ + void operator=(type const &) + +// A macro to disallow copy constructor and operator= +// This should be used in the private: declarations for a class. +#define GTEST_DISALLOW_COPY_AND_ASSIGN_(type)\ + type(type const &);\ + GTEST_DISALLOW_ASSIGN_(type) + +// Tell the compiler to warn about unused return values for functions declared +// with this macro. The macro should be used on function declarations +// following the argument list: +// +// Sprocket* AllocateSprocket() GTEST_MUST_USE_RESULT_; +#if defined(__GNUC__) && (GTEST_GCC_VER_ >= 30400) && !defined(COMPILER_ICC) +# define GTEST_MUST_USE_RESULT_ __attribute__ ((warn_unused_result)) +#else +# define GTEST_MUST_USE_RESULT_ +#endif // __GNUC__ && (GTEST_GCC_VER_ >= 30400) && !COMPILER_ICC + +// Determine whether the compiler supports Microsoft's Structured Exception +// Handling. This is supported by several Windows compilers but generally +// does not exist on any other system. +#ifndef GTEST_HAS_SEH +// The user didn't tell us, so we need to figure it out. + +# if defined(_MSC_VER) || defined(__BORLANDC__) +// These two compilers are known to support SEH. +# define GTEST_HAS_SEH 1 +# else +// Assume no SEH. +# define GTEST_HAS_SEH 0 +# endif + +#endif // GTEST_HAS_SEH + +#ifdef _MSC_VER + +# if GTEST_LINKED_AS_SHARED_LIBRARY +# define GTEST_API_ __declspec(dllimport) +# elif GTEST_CREATE_SHARED_LIBRARY +# define GTEST_API_ __declspec(dllexport) +# endif + +#endif // _MSC_VER + +#ifndef GTEST_API_ +# define GTEST_API_ +#endif + +#ifdef __GNUC__ +// Ask the compiler to never inline a given function. +# define GTEST_NO_INLINE_ __attribute__((noinline)) +#else +# define GTEST_NO_INLINE_ +#endif + +// _LIBCPP_VERSION is defined by the libc++ library from the LLVM project. +#if defined(__GLIBCXX__) || defined(_LIBCPP_VERSION) +# define GTEST_HAS_CXXABI_H_ 1 +#else +# define GTEST_HAS_CXXABI_H_ 0 +#endif + +namespace testing { + +class Message; + +namespace internal { + +// A secret type that Google Test users don't know about. It has no +// definition on purpose. Therefore it's impossible to create a +// Secret object, which is what we want. +class Secret; + +// The GTEST_COMPILE_ASSERT_ macro can be used to verify that a compile time +// expression is true. For example, you could use it to verify the +// size of a static array: +// +// GTEST_COMPILE_ASSERT_(ARRAYSIZE(content_type_names) == CONTENT_NUM_TYPES, +// content_type_names_incorrect_size); +// +// or to make sure a struct is smaller than a certain size: +// +// GTEST_COMPILE_ASSERT_(sizeof(foo) < 128, foo_too_large); +// +// The second argument to the macro is the name of the variable. If +// the expression is false, most compilers will issue a warning/error +// containing the name of the variable. + +template +struct CompileAssert { +}; + +#define GTEST_COMPILE_ASSERT_(expr, msg) \ + typedef ::testing::internal::CompileAssert<(static_cast(expr))> \ + msg[static_cast(expr) ? 1 : -1] GTEST_ATTRIBUTE_UNUSED_ + +// Implementation details of GTEST_COMPILE_ASSERT_: +// +// - GTEST_COMPILE_ASSERT_ works by defining an array type that has -1 +// elements (and thus is invalid) when the expression is false. +// +// - The simpler definition +// +// #define GTEST_COMPILE_ASSERT_(expr, msg) typedef char msg[(expr) ? 1 : -1] +// +// does not work, as gcc supports variable-length arrays whose sizes +// are determined at run-time (this is gcc's extension and not part +// of the C++ standard). As a result, gcc fails to reject the +// following code with the simple definition: +// +// int foo; +// GTEST_COMPILE_ASSERT_(foo, msg); // not supposed to compile as foo is +// // not a compile-time constant. +// +// - By using the type CompileAssert<(bool(expr))>, we ensures that +// expr is a compile-time constant. (Template arguments must be +// determined at compile-time.) +// +// - The outter parentheses in CompileAssert<(bool(expr))> are necessary +// to work around a bug in gcc 3.4.4 and 4.0.1. If we had written +// +// CompileAssert +// +// instead, these compilers will refuse to compile +// +// GTEST_COMPILE_ASSERT_(5 > 0, some_message); +// +// (They seem to think the ">" in "5 > 0" marks the end of the +// template argument list.) +// +// - The array size is (bool(expr) ? 1 : -1), instead of simply +// +// ((expr) ? 1 : -1). +// +// This is to avoid running into a bug in MS VC 7.1, which +// causes ((0.0) ? 1 : -1) to incorrectly evaluate to 1. + +// StaticAssertTypeEqHelper is used by StaticAssertTypeEq defined in gtest.h. +// +// This template is declared, but intentionally undefined. +template +struct StaticAssertTypeEqHelper; + +template +struct StaticAssertTypeEqHelper {}; + +#if GTEST_HAS_GLOBAL_STRING +typedef ::string string; +#else +typedef ::std::string string; +#endif // GTEST_HAS_GLOBAL_STRING + +#if GTEST_HAS_GLOBAL_WSTRING +typedef ::wstring wstring; +#elif GTEST_HAS_STD_WSTRING +typedef ::std::wstring wstring; +#endif // GTEST_HAS_GLOBAL_WSTRING + +// A helper for suppressing warnings on constant condition. It just +// returns 'condition'. +GTEST_API_ bool IsTrue(bool condition); + +// Defines scoped_ptr. + +// This implementation of scoped_ptr is PARTIAL - it only contains +// enough stuff to satisfy Google Test's need. +template +class scoped_ptr { + public: + typedef T element_type; + + explicit scoped_ptr(T* p = NULL) : ptr_(p) {} + ~scoped_ptr() { reset(); } + + T& operator*() const { return *ptr_; } + T* operator->() const { return ptr_; } + T* get() const { return ptr_; } + + T* release() { + T* const ptr = ptr_; + ptr_ = NULL; + return ptr; + } + + void reset(T* p = NULL) { + if (p != ptr_) { + if (IsTrue(sizeof(T) > 0)) { // Makes sure T is a complete type. + delete ptr_; + } + ptr_ = p; + } + } + + private: + T* ptr_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(scoped_ptr); +}; + +// Defines RE. + +// A simple C++ wrapper for . It uses the POSIX Extended +// Regular Expression syntax. +class GTEST_API_ RE { + public: + // A copy constructor is required by the Standard to initialize object + // references from r-values. + RE(const RE& other) { Init(other.pattern()); } + + // Constructs an RE from a string. + RE(const ::std::string& regex) { Init(regex.c_str()); } // NOLINT + +#if GTEST_HAS_GLOBAL_STRING + + RE(const ::string& regex) { Init(regex.c_str()); } // NOLINT + +#endif // GTEST_HAS_GLOBAL_STRING + + RE(const char* regex) { Init(regex); } // NOLINT + ~RE(); + + // Returns the string representation of the regex. + const char* pattern() const { return pattern_; } + + // FullMatch(str, re) returns true iff regular expression re matches + // the entire str. + // PartialMatch(str, re) returns true iff regular expression re + // matches a substring of str (including str itself). + // + // TODO(wan@google.com): make FullMatch() and PartialMatch() work + // when str contains NUL characters. + static bool FullMatch(const ::std::string& str, const RE& re) { + return FullMatch(str.c_str(), re); + } + static bool PartialMatch(const ::std::string& str, const RE& re) { + return PartialMatch(str.c_str(), re); + } + +#if GTEST_HAS_GLOBAL_STRING + + static bool FullMatch(const ::string& str, const RE& re) { + return FullMatch(str.c_str(), re); + } + static bool PartialMatch(const ::string& str, const RE& re) { + return PartialMatch(str.c_str(), re); + } + +#endif // GTEST_HAS_GLOBAL_STRING + + static bool FullMatch(const char* str, const RE& re); + static bool PartialMatch(const char* str, const RE& re); + + private: + void Init(const char* regex); + + // We use a const char* instead of an std::string, as Google Test used to be + // used where std::string is not available. TODO(wan@google.com): change to + // std::string. + const char* pattern_; + bool is_valid_; + +#if GTEST_USES_POSIX_RE + + regex_t full_regex_; // For FullMatch(). + regex_t partial_regex_; // For PartialMatch(). + +#else // GTEST_USES_SIMPLE_RE + + const char* full_pattern_; // For FullMatch(); + +#endif + + GTEST_DISALLOW_ASSIGN_(RE); +}; + +// Formats a source file path and a line number as they would appear +// in an error message from the compiler used to compile this code. +GTEST_API_ ::std::string FormatFileLocation(const char* file, int line); + +// Formats a file location for compiler-independent XML output. +// Although this function is not platform dependent, we put it next to +// FormatFileLocation in order to contrast the two functions. +GTEST_API_ ::std::string FormatCompilerIndependentFileLocation(const char* file, + int line); + +// Defines logging utilities: +// GTEST_LOG_(severity) - logs messages at the specified severity level. The +// message itself is streamed into the macro. +// LogToStderr() - directs all log messages to stderr. +// FlushInfoLog() - flushes informational log messages. + +enum GTestLogSeverity { + GTEST_INFO, + GTEST_WARNING, + GTEST_ERROR, + GTEST_FATAL +}; + +// Formats log entry severity, provides a stream object for streaming the +// log message, and terminates the message with a newline when going out of +// scope. +class GTEST_API_ GTestLog { + public: + GTestLog(GTestLogSeverity severity, const char* file, int line); + + // Flushes the buffers and, if severity is GTEST_FATAL, aborts the program. + ~GTestLog(); + + ::std::ostream& GetStream() { return ::std::cerr; } + + private: + const GTestLogSeverity severity_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(GTestLog); +}; + +#define GTEST_LOG_(severity) \ + ::testing::internal::GTestLog(::testing::internal::GTEST_##severity, \ + __FILE__, __LINE__).GetStream() + +inline void LogToStderr() {} +inline void FlushInfoLog() { fflush(NULL); } + +// INTERNAL IMPLEMENTATION - DO NOT USE. +// +// GTEST_CHECK_ is an all-mode assert. It aborts the program if the condition +// is not satisfied. +// Synopsys: +// GTEST_CHECK_(boolean_condition); +// or +// GTEST_CHECK_(boolean_condition) << "Additional message"; +// +// This checks the condition and if the condition is not satisfied +// it prints message about the condition violation, including the +// condition itself, plus additional message streamed into it, if any, +// and then it aborts the program. It aborts the program irrespective of +// whether it is built in the debug mode or not. +#define GTEST_CHECK_(condition) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (::testing::internal::IsTrue(condition)) \ + ; \ + else \ + GTEST_LOG_(FATAL) << "Condition " #condition " failed. " + +// An all-mode assert to verify that the given POSIX-style function +// call returns 0 (indicating success). Known limitation: this +// doesn't expand to a balanced 'if' statement, so enclose the macro +// in {} if you need to use it as the only statement in an 'if' +// branch. +#define GTEST_CHECK_POSIX_SUCCESS_(posix_call) \ + if (const int gtest_error = (posix_call)) \ + GTEST_LOG_(FATAL) << #posix_call << "failed with error " \ + << gtest_error + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Use ImplicitCast_ as a safe version of static_cast for upcasting in +// the type hierarchy (e.g. casting a Foo* to a SuperclassOfFoo* or a +// const Foo*). When you use ImplicitCast_, the compiler checks that +// the cast is safe. Such explicit ImplicitCast_s are necessary in +// surprisingly many situations where C++ demands an exact type match +// instead of an argument type convertable to a target type. +// +// The syntax for using ImplicitCast_ is the same as for static_cast: +// +// ImplicitCast_(expr) +// +// ImplicitCast_ would have been part of the C++ standard library, +// but the proposal was submitted too late. It will probably make +// its way into the language in the future. +// +// This relatively ugly name is intentional. It prevents clashes with +// similar functions users may have (e.g., implicit_cast). The internal +// namespace alone is not enough because the function can be found by ADL. +template +inline To ImplicitCast_(To x) { return x; } + +// When you upcast (that is, cast a pointer from type Foo to type +// SuperclassOfFoo), it's fine to use ImplicitCast_<>, since upcasts +// always succeed. When you downcast (that is, cast a pointer from +// type Foo to type SubclassOfFoo), static_cast<> isn't safe, because +// how do you know the pointer is really of type SubclassOfFoo? It +// could be a bare Foo, or of type DifferentSubclassOfFoo. Thus, +// when you downcast, you should use this macro. In debug mode, we +// use dynamic_cast<> to double-check the downcast is legal (we die +// if it's not). In normal mode, we do the efficient static_cast<> +// instead. Thus, it's important to test in debug mode to make sure +// the cast is legal! +// This is the only place in the code we should use dynamic_cast<>. +// In particular, you SHOULDN'T be using dynamic_cast<> in order to +// do RTTI (eg code like this: +// if (dynamic_cast(foo)) HandleASubclass1Object(foo); +// if (dynamic_cast(foo)) HandleASubclass2Object(foo); +// You should design the code some other way not to need this. +// +// This relatively ugly name is intentional. It prevents clashes with +// similar functions users may have (e.g., down_cast). The internal +// namespace alone is not enough because the function can be found by ADL. +template // use like this: DownCast_(foo); +inline To DownCast_(From* f) { // so we only accept pointers + // Ensures that To is a sub-type of From *. This test is here only + // for compile-time type checking, and has no overhead in an + // optimized build at run-time, as it will be optimized away + // completely. + if (false) { + const To to = NULL; + ::testing::internal::ImplicitCast_(to); + } + +#if GTEST_HAS_RTTI + // RTTI: debug mode only! + GTEST_CHECK_(f == NULL || dynamic_cast(f) != NULL); +#endif + return static_cast(f); +} + +// Downcasts the pointer of type Base to Derived. +// Derived must be a subclass of Base. The parameter MUST +// point to a class of type Derived, not any subclass of it. +// When RTTI is available, the function performs a runtime +// check to enforce this. +template +Derived* CheckedDowncastToActualType(Base* base) { +#if GTEST_HAS_RTTI + GTEST_CHECK_(typeid(*base) == typeid(Derived)); + return dynamic_cast(base); // NOLINT +#else + return static_cast(base); // Poor man's downcast. +#endif +} + +#if GTEST_HAS_STREAM_REDIRECTION + +// Defines the stderr capturer: +// CaptureStdout - starts capturing stdout. +// GetCapturedStdout - stops capturing stdout and returns the captured string. +// CaptureStderr - starts capturing stderr. +// GetCapturedStderr - stops capturing stderr and returns the captured string. +// +GTEST_API_ void CaptureStdout(); +GTEST_API_ std::string GetCapturedStdout(); +GTEST_API_ void CaptureStderr(); +GTEST_API_ std::string GetCapturedStderr(); + +#endif // GTEST_HAS_STREAM_REDIRECTION + + +#if GTEST_HAS_DEATH_TEST + +const ::std::vector& GetInjectableArgvs(); +void SetInjectableArgvs(const ::std::vector* + new_argvs); + +// A copy of all command line arguments. Set by InitGoogleTest(). +extern ::std::vector g_argvs; + +#endif // GTEST_HAS_DEATH_TEST + +// Defines synchronization primitives. + +#if GTEST_HAS_PTHREAD + +// Sleeps for (roughly) n milli-seconds. This function is only for +// testing Google Test's own constructs. Don't use it in user tests, +// either directly or indirectly. +inline void SleepMilliseconds(int n) { + const timespec time = { + 0, // 0 seconds. + n * 1000L * 1000L, // And n ms. + }; + nanosleep(&time, NULL); +} + +// Allows a controller thread to pause execution of newly created +// threads until notified. Instances of this class must be created +// and destroyed in the controller thread. +// +// This class is only for testing Google Test's own constructs. Do not +// use it in user tests, either directly or indirectly. +class Notification { + public: + Notification() : notified_(false) { + GTEST_CHECK_POSIX_SUCCESS_(pthread_mutex_init(&mutex_, NULL)); + } + ~Notification() { + pthread_mutex_destroy(&mutex_); + } + + // Notifies all threads created with this notification to start. Must + // be called from the controller thread. + void Notify() { + pthread_mutex_lock(&mutex_); + notified_ = true; + pthread_mutex_unlock(&mutex_); + } + + // Blocks until the controller thread notifies. Must be called from a test + // thread. + void WaitForNotification() { + for (;;) { + pthread_mutex_lock(&mutex_); + const bool notified = notified_; + pthread_mutex_unlock(&mutex_); + if (notified) + break; + SleepMilliseconds(10); + } + } + + private: + pthread_mutex_t mutex_; + bool notified_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(Notification); +}; + +// As a C-function, ThreadFuncWithCLinkage cannot be templated itself. +// Consequently, it cannot select a correct instantiation of ThreadWithParam +// in order to call its Run(). Introducing ThreadWithParamBase as a +// non-templated base class for ThreadWithParam allows us to bypass this +// problem. +class ThreadWithParamBase { + public: + virtual ~ThreadWithParamBase() {} + virtual void Run() = 0; +}; + +// pthread_create() accepts a pointer to a function type with the C linkage. +// According to the Standard (7.5/1), function types with different linkages +// are different even if they are otherwise identical. Some compilers (for +// example, SunStudio) treat them as different types. Since class methods +// cannot be defined with C-linkage we need to define a free C-function to +// pass into pthread_create(). +extern "C" inline void* ThreadFuncWithCLinkage(void* thread) { + static_cast(thread)->Run(); + return NULL; +} + +// Helper class for testing Google Test's multi-threading constructs. +// To use it, write: +// +// void ThreadFunc(int param) { /* Do things with param */ } +// Notification thread_can_start; +// ... +// // The thread_can_start parameter is optional; you can supply NULL. +// ThreadWithParam thread(&ThreadFunc, 5, &thread_can_start); +// thread_can_start.Notify(); +// +// These classes are only for testing Google Test's own constructs. Do +// not use them in user tests, either directly or indirectly. +template +class ThreadWithParam : public ThreadWithParamBase { + public: + typedef void (*UserThreadFunc)(T); + + ThreadWithParam( + UserThreadFunc func, T param, Notification* thread_can_start) + : func_(func), + param_(param), + thread_can_start_(thread_can_start), + finished_(false) { + ThreadWithParamBase* const base = this; + // The thread can be created only after all fields except thread_ + // have been initialized. + GTEST_CHECK_POSIX_SUCCESS_( + pthread_create(&thread_, 0, &ThreadFuncWithCLinkage, base)); + } + ~ThreadWithParam() { Join(); } + + void Join() { + if (!finished_) { + GTEST_CHECK_POSIX_SUCCESS_(pthread_join(thread_, 0)); + finished_ = true; + } + } + + virtual void Run() { + if (thread_can_start_ != NULL) + thread_can_start_->WaitForNotification(); + func_(param_); + } + + private: + const UserThreadFunc func_; // User-supplied thread function. + const T param_; // User-supplied parameter to the thread function. + // When non-NULL, used to block execution until the controller thread + // notifies. + Notification* const thread_can_start_; + bool finished_; // true iff we know that the thread function has finished. + pthread_t thread_; // The native thread object. + + GTEST_DISALLOW_COPY_AND_ASSIGN_(ThreadWithParam); +}; + +// MutexBase and Mutex implement mutex on pthreads-based platforms. They +// are used in conjunction with class MutexLock: +// +// Mutex mutex; +// ... +// MutexLock lock(&mutex); // Acquires the mutex and releases it at the end +// // of the current scope. +// +// MutexBase implements behavior for both statically and dynamically +// allocated mutexes. Do not use MutexBase directly. Instead, write +// the following to define a static mutex: +// +// GTEST_DEFINE_STATIC_MUTEX_(g_some_mutex); +// +// You can forward declare a static mutex like this: +// +// GTEST_DECLARE_STATIC_MUTEX_(g_some_mutex); +// +// To create a dynamic mutex, just define an object of type Mutex. +class MutexBase { + public: + // Acquires this mutex. + void Lock() { + GTEST_CHECK_POSIX_SUCCESS_(pthread_mutex_lock(&mutex_)); + owner_ = pthread_self(); + has_owner_ = true; + } + + // Releases this mutex. + void Unlock() { + // Since the lock is being released the owner_ field should no longer be + // considered valid. We don't protect writing to has_owner_ here, as it's + // the caller's responsibility to ensure that the current thread holds the + // mutex when this is called. + has_owner_ = false; + GTEST_CHECK_POSIX_SUCCESS_(pthread_mutex_unlock(&mutex_)); + } + + // Does nothing if the current thread holds the mutex. Otherwise, crashes + // with high probability. + void AssertHeld() const { + GTEST_CHECK_(has_owner_ && pthread_equal(owner_, pthread_self())) + << "The current thread is not holding the mutex @" << this; + } + + // A static mutex may be used before main() is entered. It may even + // be used before the dynamic initialization stage. Therefore we + // must be able to initialize a static mutex object at link time. + // This means MutexBase has to be a POD and its member variables + // have to be public. + public: + pthread_mutex_t mutex_; // The underlying pthread mutex. + // has_owner_ indicates whether the owner_ field below contains a valid thread + // ID and is therefore safe to inspect (e.g., to use in pthread_equal()). All + // accesses to the owner_ field should be protected by a check of this field. + // An alternative might be to memset() owner_ to all zeros, but there's no + // guarantee that a zero'd pthread_t is necessarily invalid or even different + // from pthread_self(). + bool has_owner_; + pthread_t owner_; // The thread holding the mutex. +}; + +// Forward-declares a static mutex. +# define GTEST_DECLARE_STATIC_MUTEX_(mutex) \ + extern ::testing::internal::MutexBase mutex + +// Defines and statically (i.e. at link time) initializes a static mutex. +// The initialization list here does not explicitly initialize each field, +// instead relying on default initialization for the unspecified fields. In +// particular, the owner_ field (a pthread_t) is not explicitly initialized. +// This allows initialization to work whether pthread_t is a scalar or struct. +// The flag -Wmissing-field-initializers must not be specified for this to work. +# define GTEST_DEFINE_STATIC_MUTEX_(mutex) \ + ::testing::internal::MutexBase mutex = { PTHREAD_MUTEX_INITIALIZER, false } + +// The Mutex class can only be used for mutexes created at runtime. It +// shares its API with MutexBase otherwise. +class Mutex : public MutexBase { + public: + Mutex() { + GTEST_CHECK_POSIX_SUCCESS_(pthread_mutex_init(&mutex_, NULL)); + has_owner_ = false; + } + ~Mutex() { + GTEST_CHECK_POSIX_SUCCESS_(pthread_mutex_destroy(&mutex_)); + } + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(Mutex); +}; + +// We cannot name this class MutexLock as the ctor declaration would +// conflict with a macro named MutexLock, which is defined on some +// platforms. Hence the typedef trick below. +class GTestMutexLock { + public: + explicit GTestMutexLock(MutexBase* mutex) + : mutex_(mutex) { mutex_->Lock(); } + + ~GTestMutexLock() { mutex_->Unlock(); } + + private: + MutexBase* const mutex_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(GTestMutexLock); +}; + +typedef GTestMutexLock MutexLock; + +// Helpers for ThreadLocal. + +// pthread_key_create() requires DeleteThreadLocalValue() to have +// C-linkage. Therefore it cannot be templatized to access +// ThreadLocal. Hence the need for class +// ThreadLocalValueHolderBase. +class ThreadLocalValueHolderBase { + public: + virtual ~ThreadLocalValueHolderBase() {} +}; + +// Called by pthread to delete thread-local data stored by +// pthread_setspecific(). +extern "C" inline void DeleteThreadLocalValue(void* value_holder) { + delete static_cast(value_holder); +} + +// Implements thread-local storage on pthreads-based systems. +// +// // Thread 1 +// ThreadLocal tl(100); // 100 is the default value for each thread. +// +// // Thread 2 +// tl.set(150); // Changes the value for thread 2 only. +// EXPECT_EQ(150, tl.get()); +// +// // Thread 1 +// EXPECT_EQ(100, tl.get()); // In thread 1, tl has the original value. +// tl.set(200); +// EXPECT_EQ(200, tl.get()); +// +// The template type argument T must have a public copy constructor. +// In addition, the default ThreadLocal constructor requires T to have +// a public default constructor. +// +// An object managed for a thread by a ThreadLocal instance is deleted +// when the thread exits. Or, if the ThreadLocal instance dies in +// that thread, when the ThreadLocal dies. It's the user's +// responsibility to ensure that all other threads using a ThreadLocal +// have exited when it dies, or the per-thread objects for those +// threads will not be deleted. +// +// Google Test only uses global ThreadLocal objects. That means they +// will die after main() has returned. Therefore, no per-thread +// object managed by Google Test will be leaked as long as all threads +// using Google Test have exited when main() returns. +template +class ThreadLocal { + public: + ThreadLocal() : key_(CreateKey()), + default_() {} + explicit ThreadLocal(const T& value) : key_(CreateKey()), + default_(value) {} + + ~ThreadLocal() { + // Destroys the managed object for the current thread, if any. + DeleteThreadLocalValue(pthread_getspecific(key_)); + + // Releases resources associated with the key. This will *not* + // delete managed objects for other threads. + GTEST_CHECK_POSIX_SUCCESS_(pthread_key_delete(key_)); + } + + T* pointer() { return GetOrCreateValue(); } + const T* pointer() const { return GetOrCreateValue(); } + const T& get() const { return *pointer(); } + void set(const T& value) { *pointer() = value; } + + private: + // Holds a value of type T. + class ValueHolder : public ThreadLocalValueHolderBase { + public: + explicit ValueHolder(const T& value) : value_(value) {} + + T* pointer() { return &value_; } + + private: + T value_; + GTEST_DISALLOW_COPY_AND_ASSIGN_(ValueHolder); + }; + + static pthread_key_t CreateKey() { + pthread_key_t key; + // When a thread exits, DeleteThreadLocalValue() will be called on + // the object managed for that thread. + GTEST_CHECK_POSIX_SUCCESS_( + pthread_key_create(&key, &DeleteThreadLocalValue)); + return key; + } + + T* GetOrCreateValue() const { + ThreadLocalValueHolderBase* const holder = + static_cast(pthread_getspecific(key_)); + if (holder != NULL) { + return CheckedDowncastToActualType(holder)->pointer(); + } + + ValueHolder* const new_holder = new ValueHolder(default_); + ThreadLocalValueHolderBase* const holder_base = new_holder; + GTEST_CHECK_POSIX_SUCCESS_(pthread_setspecific(key_, holder_base)); + return new_holder->pointer(); + } + + // A key pthreads uses for looking up per-thread values. + const pthread_key_t key_; + const T default_; // The default value for each thread. + + GTEST_DISALLOW_COPY_AND_ASSIGN_(ThreadLocal); +}; + +# define GTEST_IS_THREADSAFE 1 + +#else // GTEST_HAS_PTHREAD + +// A dummy implementation of synchronization primitives (mutex, lock, +// and thread-local variable). Necessary for compiling Google Test where +// mutex is not supported - using Google Test in multiple threads is not +// supported on such platforms. + +class Mutex { + public: + Mutex() {} + void Lock() {} + void Unlock() {} + void AssertHeld() const {} +}; + +# define GTEST_DECLARE_STATIC_MUTEX_(mutex) \ + extern ::testing::internal::Mutex mutex + +# define GTEST_DEFINE_STATIC_MUTEX_(mutex) ::testing::internal::Mutex mutex + +class GTestMutexLock { + public: + explicit GTestMutexLock(Mutex*) {} // NOLINT +}; + +typedef GTestMutexLock MutexLock; + +template +class ThreadLocal { + public: + ThreadLocal() : value_() {} + explicit ThreadLocal(const T& value) : value_(value) {} + T* pointer() { return &value_; } + const T* pointer() const { return &value_; } + const T& get() const { return value_; } + void set(const T& value) { value_ = value; } + private: + T value_; +}; + +// The above synchronization primitives have dummy implementations. +// Therefore Google Test is not thread-safe. +# define GTEST_IS_THREADSAFE 0 + +#endif // GTEST_HAS_PTHREAD + +// Returns the number of threads running in the process, or 0 to indicate that +// we cannot detect it. +GTEST_API_ size_t GetThreadCount(); + +// Passing non-POD classes through ellipsis (...) crashes the ARM +// compiler and generates a warning in Sun Studio. The Nokia Symbian +// and the IBM XL C/C++ compiler try to instantiate a copy constructor +// for objects passed through ellipsis (...), failing for uncopyable +// objects. We define this to ensure that only POD is passed through +// ellipsis on these systems. +#if defined(__SYMBIAN32__) || defined(__IBMCPP__) || defined(__SUNPRO_CC) +// We lose support for NULL detection where the compiler doesn't like +// passing non-POD classes through ellipsis (...). +# define GTEST_ELLIPSIS_NEEDS_POD_ 1 +#else +# define GTEST_CAN_COMPARE_NULL 1 +#endif + +// The Nokia Symbian and IBM XL C/C++ compilers cannot decide between +// const T& and const T* in a function template. These compilers +// _can_ decide between class template specializations for T and T*, +// so a tr1::type_traits-like is_pointer works. +#if defined(__SYMBIAN32__) || defined(__IBMCPP__) +# define GTEST_NEEDS_IS_POINTER_ 1 +#endif + +template +struct bool_constant { + typedef bool_constant type; + static const bool value = bool_value; +}; +template const bool bool_constant::value; + +typedef bool_constant false_type; +typedef bool_constant true_type; + +template +struct is_pointer : public false_type {}; + +template +struct is_pointer : public true_type {}; + +template +struct IteratorTraits { + typedef typename Iterator::value_type value_type; +}; + +template +struct IteratorTraits { + typedef T value_type; +}; + +template +struct IteratorTraits { + typedef T value_type; +}; + +#if GTEST_OS_WINDOWS +# define GTEST_PATH_SEP_ "\\" +# define GTEST_HAS_ALT_PATH_SEP_ 1 +// The biggest signed integer type the compiler supports. +typedef __int64 BiggestInt; +#else +# define GTEST_PATH_SEP_ "/" +# define GTEST_HAS_ALT_PATH_SEP_ 0 +typedef long long BiggestInt; // NOLINT +#endif // GTEST_OS_WINDOWS + +// Utilities for char. + +// isspace(int ch) and friends accept an unsigned char or EOF. char +// may be signed, depending on the compiler (or compiler flags). +// Therefore we need to cast a char to unsigned char before calling +// isspace(), etc. + +inline bool IsAlpha(char ch) { + return isalpha(static_cast(ch)) != 0; +} +inline bool IsAlNum(char ch) { + return isalnum(static_cast(ch)) != 0; +} +inline bool IsDigit(char ch) { + return isdigit(static_cast(ch)) != 0; +} +inline bool IsLower(char ch) { + return islower(static_cast(ch)) != 0; +} +inline bool IsSpace(char ch) { + return isspace(static_cast(ch)) != 0; +} +inline bool IsUpper(char ch) { + return isupper(static_cast(ch)) != 0; +} +inline bool IsXDigit(char ch) { + return isxdigit(static_cast(ch)) != 0; +} +inline bool IsXDigit(wchar_t ch) { + const unsigned char low_byte = static_cast(ch); + return ch == low_byte && isxdigit(low_byte) != 0; +} + +inline char ToLower(char ch) { + return static_cast(tolower(static_cast(ch))); +} +inline char ToUpper(char ch) { + return static_cast(toupper(static_cast(ch))); +} + +// The testing::internal::posix namespace holds wrappers for common +// POSIX functions. These wrappers hide the differences between +// Windows/MSVC and POSIX systems. Since some compilers define these +// standard functions as macros, the wrapper cannot have the same name +// as the wrapped function. + +namespace posix { + +// Functions with a different name on Windows. + +#if GTEST_OS_WINDOWS + +typedef struct _stat StatStruct; + +# ifdef __BORLANDC__ +inline int IsATTY(int fd) { return isatty(fd); } +inline int StrCaseCmp(const char* s1, const char* s2) { + return stricmp(s1, s2); +} +inline char* StrDup(const char* src) { return strdup(src); } +# else // !__BORLANDC__ +# if GTEST_OS_WINDOWS_MOBILE +inline int IsATTY(int /* fd */) { return 0; } +# else +inline int IsATTY(int fd) { return _isatty(fd); } +# endif // GTEST_OS_WINDOWS_MOBILE +inline int StrCaseCmp(const char* s1, const char* s2) { + return _stricmp(s1, s2); +} +inline char* StrDup(const char* src) { return _strdup(src); } +# endif // __BORLANDC__ + +# if GTEST_OS_WINDOWS_MOBILE +inline int FileNo(FILE* file) { return reinterpret_cast(_fileno(file)); } +// Stat(), RmDir(), and IsDir() are not needed on Windows CE at this +// time and thus not defined there. +# else +inline int FileNo(FILE* file) { return _fileno(file); } +inline int Stat(const char* path, StatStruct* buf) { return _stat(path, buf); } +inline int RmDir(const char* dir) { return _rmdir(dir); } +inline bool IsDir(const StatStruct& st) { + return (_S_IFDIR & st.st_mode) != 0; +} +# endif // GTEST_OS_WINDOWS_MOBILE + +#else + +typedef struct stat StatStruct; + +inline int FileNo(FILE* file) { return fileno(file); } +inline int IsATTY(int fd) { return isatty(fd); } +inline int Stat(const char* path, StatStruct* buf) { return stat(path, buf); } +inline int StrCaseCmp(const char* s1, const char* s2) { + return strcasecmp(s1, s2); +} +inline char* StrDup(const char* src) { return strdup(src); } +inline int RmDir(const char* dir) { return rmdir(dir); } +inline bool IsDir(const StatStruct& st) { return S_ISDIR(st.st_mode); } + +#endif // GTEST_OS_WINDOWS + +// Functions deprecated by MSVC 8.0. + +#ifdef _MSC_VER +// Temporarily disable warning 4996 (deprecated function). +# pragma warning(push) +# pragma warning(disable:4996) +#endif + +inline const char* StrNCpy(char* dest, const char* src, size_t n) { + return strncpy(dest, src, n); +} + +// ChDir(), FReopen(), FDOpen(), Read(), Write(), Close(), and +// StrError() aren't needed on Windows CE at this time and thus not +// defined there. + +#if !GTEST_OS_WINDOWS_MOBILE +inline int ChDir(const char* dir) { return chdir(dir); } +#endif +inline FILE* FOpen(const char* path, const char* mode) { + return fopen(path, mode); +} +#if !GTEST_OS_WINDOWS_MOBILE +inline FILE *FReopen(const char* path, const char* mode, FILE* stream) { + return freopen(path, mode, stream); +} +inline FILE* FDOpen(int fd, const char* mode) { return fdopen(fd, mode); } +#endif +inline int FClose(FILE* fp) { return fclose(fp); } +#if !GTEST_OS_WINDOWS_MOBILE +inline int Read(int fd, void* buf, unsigned int count) { + return static_cast(read(fd, buf, count)); +} +inline int Write(int fd, const void* buf, unsigned int count) { + return static_cast(write(fd, buf, count)); +} +inline int Close(int fd) { return close(fd); } +inline const char* StrError(int errnum) { return strerror(errnum); } +#endif +inline const char* GetEnv(const char* name) { +#if GTEST_OS_WINDOWS_MOBILE + // We are on Windows CE, which has no environment variables. + return NULL; +#elif defined(__BORLANDC__) || defined(__SunOS_5_8) || defined(__SunOS_5_9) + // Environment variables which we programmatically clear will be set to the + // empty string rather than unset (NULL). Handle that case. + const char* const env = getenv(name); + return (env != NULL && env[0] != '\0') ? env : NULL; +#else + return getenv(name); +#endif +} + +#ifdef _MSC_VER +# pragma warning(pop) // Restores the warning state. +#endif + +#if GTEST_OS_WINDOWS_MOBILE +// Windows CE has no C library. The abort() function is used in +// several places in Google Test. This implementation provides a reasonable +// imitation of standard behaviour. +void Abort(); +#else +inline void Abort() { abort(); } +#endif // GTEST_OS_WINDOWS_MOBILE + +} // namespace posix + +// MSVC "deprecates" snprintf and issues warnings wherever it is used. In +// order to avoid these warnings, we need to use _snprintf or _snprintf_s on +// MSVC-based platforms. We map the GTEST_SNPRINTF_ macro to the appropriate +// function in order to achieve that. We use macro definition here because +// snprintf is a variadic function. +#if _MSC_VER >= 1400 && !GTEST_OS_WINDOWS_MOBILE +// MSVC 2005 and above support variadic macros. +# define GTEST_SNPRINTF_(buffer, size, format, ...) \ + _snprintf_s(buffer, size, size, format, __VA_ARGS__) +#elif defined(_MSC_VER) +// Windows CE does not define _snprintf_s and MSVC prior to 2005 doesn't +// complain about _snprintf. +# define GTEST_SNPRINTF_ _snprintf +#else +# define GTEST_SNPRINTF_ snprintf +#endif + +// The maximum number a BiggestInt can represent. This definition +// works no matter BiggestInt is represented in one's complement or +// two's complement. +// +// We cannot rely on numeric_limits in STL, as __int64 and long long +// are not part of standard C++ and numeric_limits doesn't need to be +// defined for them. +const BiggestInt kMaxBiggestInt = + ~(static_cast(1) << (8*sizeof(BiggestInt) - 1)); + +// This template class serves as a compile-time function from size to +// type. It maps a size in bytes to a primitive type with that +// size. e.g. +// +// TypeWithSize<4>::UInt +// +// is typedef-ed to be unsigned int (unsigned integer made up of 4 +// bytes). +// +// Such functionality should belong to STL, but I cannot find it +// there. +// +// Google Test uses this class in the implementation of floating-point +// comparison. +// +// For now it only handles UInt (unsigned int) as that's all Google Test +// needs. Other types can be easily added in the future if need +// arises. +template +class TypeWithSize { + public: + // This prevents the user from using TypeWithSize with incorrect + // values of N. + typedef void UInt; +}; + +// The specialization for size 4. +template <> +class TypeWithSize<4> { + public: + // unsigned int has size 4 in both gcc and MSVC. + // + // As base/basictypes.h doesn't compile on Windows, we cannot use + // uint32, uint64, and etc here. + typedef int Int; + typedef unsigned int UInt; +}; + +// The specialization for size 8. +template <> +class TypeWithSize<8> { + public: +#if GTEST_OS_WINDOWS + typedef __int64 Int; + typedef unsigned __int64 UInt; +#else + typedef long long Int; // NOLINT + typedef unsigned long long UInt; // NOLINT +#endif // GTEST_OS_WINDOWS +}; + +// Integer types of known sizes. +typedef TypeWithSize<4>::Int Int32; +typedef TypeWithSize<4>::UInt UInt32; +typedef TypeWithSize<8>::Int Int64; +typedef TypeWithSize<8>::UInt UInt64; +typedef TypeWithSize<8>::Int TimeInMillis; // Represents time in milliseconds. + +// Utilities for command line flags and environment variables. + +// Macro for referencing flags. +#define GTEST_FLAG(name) FLAGS_gtest_##name + +// Macros for declaring flags. +#define GTEST_DECLARE_bool_(name) GTEST_API_ extern bool GTEST_FLAG(name) +#define GTEST_DECLARE_int32_(name) \ + GTEST_API_ extern ::testing::internal::Int32 GTEST_FLAG(name) +#define GTEST_DECLARE_string_(name) \ + GTEST_API_ extern ::std::string GTEST_FLAG(name) + +// Macros for defining flags. +#define GTEST_DEFINE_bool_(name, default_val, doc) \ + GTEST_API_ bool GTEST_FLAG(name) = (default_val) +#define GTEST_DEFINE_int32_(name, default_val, doc) \ + GTEST_API_ ::testing::internal::Int32 GTEST_FLAG(name) = (default_val) +#define GTEST_DEFINE_string_(name, default_val, doc) \ + GTEST_API_ ::std::string GTEST_FLAG(name) = (default_val) + +// Thread annotations +#define GTEST_EXCLUSIVE_LOCK_REQUIRED_(locks) +#define GTEST_LOCK_EXCLUDED_(locks) + +// Parses 'str' for a 32-bit signed integer. If successful, writes the result +// to *value and returns true; otherwise leaves *value unchanged and returns +// false. +// TODO(chandlerc): Find a better way to refactor flag and environment parsing +// out of both gtest-port.cc and gtest.cc to avoid exporting this utility +// function. +bool ParseInt32(const Message& src_text, const char* str, Int32* value); + +// Parses a bool/Int32/string from the environment variable +// corresponding to the given Google Test flag. +bool BoolFromGTestEnv(const char* flag, bool default_val); +GTEST_API_ Int32 Int32FromGTestEnv(const char* flag, Int32 default_val); +const char* StringFromGTestEnv(const char* flag, const char* default_val); + +} // namespace internal +} // namespace testing + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_H_ diff --git a/vendor/gmock-1.7.0/gtest/include/gtest/internal/gtest-string.h b/vendor/gmock-1.7.0/gtest/include/gtest/internal/gtest-string.h new file mode 100644 index 0000000000..97f1a7fdd2 --- /dev/null +++ b/vendor/gmock-1.7.0/gtest/include/gtest/internal/gtest-string.h @@ -0,0 +1,167 @@ +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Authors: wan@google.com (Zhanyong Wan), eefacm@gmail.com (Sean Mcafee) +// +// The Google C++ Testing Framework (Google Test) +// +// This header file declares the String class and functions used internally by +// Google Test. They are subject to change without notice. They should not used +// by code external to Google Test. +// +// This header file is #included by . +// It should not be #included by other files. + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_STRING_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_STRING_H_ + +#ifdef __BORLANDC__ +// string.h is not guaranteed to provide strcpy on C++ Builder. +# include +#endif + +#include +#include + +#include "gtest/internal/gtest-port.h" + +namespace testing { +namespace internal { + +// String - an abstract class holding static string utilities. +class GTEST_API_ String { + public: + // Static utility methods + + // Clones a 0-terminated C string, allocating memory using new. The + // caller is responsible for deleting the return value using + // delete[]. Returns the cloned string, or NULL if the input is + // NULL. + // + // This is different from strdup() in string.h, which allocates + // memory using malloc(). + static const char* CloneCString(const char* c_str); + +#if GTEST_OS_WINDOWS_MOBILE + // Windows CE does not have the 'ANSI' versions of Win32 APIs. To be + // able to pass strings to Win32 APIs on CE we need to convert them + // to 'Unicode', UTF-16. + + // Creates a UTF-16 wide string from the given ANSI string, allocating + // memory using new. The caller is responsible for deleting the return + // value using delete[]. Returns the wide string, or NULL if the + // input is NULL. + // + // The wide string is created using the ANSI codepage (CP_ACP) to + // match the behaviour of the ANSI versions of Win32 calls and the + // C runtime. + static LPCWSTR AnsiToUtf16(const char* c_str); + + // Creates an ANSI string from the given wide string, allocating + // memory using new. The caller is responsible for deleting the return + // value using delete[]. Returns the ANSI string, or NULL if the + // input is NULL. + // + // The returned string is created using the ANSI codepage (CP_ACP) to + // match the behaviour of the ANSI versions of Win32 calls and the + // C runtime. + static const char* Utf16ToAnsi(LPCWSTR utf16_str); +#endif + + // Compares two C strings. Returns true iff they have the same content. + // + // Unlike strcmp(), this function can handle NULL argument(s). A + // NULL C string is considered different to any non-NULL C string, + // including the empty string. + static bool CStringEquals(const char* lhs, const char* rhs); + + // Converts a wide C string to a String using the UTF-8 encoding. + // NULL will be converted to "(null)". If an error occurred during + // the conversion, "(failed to convert from wide string)" is + // returned. + static std::string ShowWideCString(const wchar_t* wide_c_str); + + // Compares two wide C strings. Returns true iff they have the same + // content. + // + // Unlike wcscmp(), this function can handle NULL argument(s). A + // NULL C string is considered different to any non-NULL C string, + // including the empty string. + static bool WideCStringEquals(const wchar_t* lhs, const wchar_t* rhs); + + // Compares two C strings, ignoring case. Returns true iff they + // have the same content. + // + // Unlike strcasecmp(), this function can handle NULL argument(s). + // A NULL C string is considered different to any non-NULL C string, + // including the empty string. + static bool CaseInsensitiveCStringEquals(const char* lhs, + const char* rhs); + + // Compares two wide C strings, ignoring case. Returns true iff they + // have the same content. + // + // Unlike wcscasecmp(), this function can handle NULL argument(s). + // A NULL C string is considered different to any non-NULL wide C string, + // including the empty string. + // NB: The implementations on different platforms slightly differ. + // On windows, this method uses _wcsicmp which compares according to LC_CTYPE + // environment variable. On GNU platform this method uses wcscasecmp + // which compares according to LC_CTYPE category of the current locale. + // On MacOS X, it uses towlower, which also uses LC_CTYPE category of the + // current locale. + static bool CaseInsensitiveWideCStringEquals(const wchar_t* lhs, + const wchar_t* rhs); + + // Returns true iff the given string ends with the given suffix, ignoring + // case. Any string is considered to end with an empty suffix. + static bool EndsWithCaseInsensitive( + const std::string& str, const std::string& suffix); + + // Formats an int value as "%02d". + static std::string FormatIntWidth2(int value); // "%02d" for width == 2 + + // Formats an int value as "%X". + static std::string FormatHexInt(int value); + + // Formats a byte as "%02X". + static std::string FormatByte(unsigned char value); + + private: + String(); // Not meant to be instantiated. +}; // class String + +// Gets the content of the stringstream's buffer as an std::string. Each '\0' +// character in the buffer is replaced with "\\0". +GTEST_API_ std::string StringStreamToString(::std::stringstream* stream); + +} // namespace internal +} // namespace testing + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_STRING_H_ diff --git a/vendor/gmock-1.7.0/gtest/include/gtest/internal/gtest-tuple.h b/vendor/gmock-1.7.0/gtest/include/gtest/internal/gtest-tuple.h new file mode 100644 index 0000000000..7b3dfc312d --- /dev/null +++ b/vendor/gmock-1.7.0/gtest/include/gtest/internal/gtest-tuple.h @@ -0,0 +1,1012 @@ +// This file was GENERATED by command: +// pump.py gtest-tuple.h.pump +// DO NOT EDIT BY HAND!!! + +// Copyright 2009 Google Inc. +// All Rights Reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) + +// Implements a subset of TR1 tuple needed by Google Test and Google Mock. + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TUPLE_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TUPLE_H_ + +#include // For ::std::pair. + +// The compiler used in Symbian has a bug that prevents us from declaring the +// tuple template as a friend (it complains that tuple is redefined). This +// hack bypasses the bug by declaring the members that should otherwise be +// private as public. +// Sun Studio versions < 12 also have the above bug. +#if defined(__SYMBIAN32__) || (defined(__SUNPRO_CC) && __SUNPRO_CC < 0x590) +# define GTEST_DECLARE_TUPLE_AS_FRIEND_ public: +#else +# define GTEST_DECLARE_TUPLE_AS_FRIEND_ \ + template friend class tuple; \ + private: +#endif + +// GTEST_n_TUPLE_(T) is the type of an n-tuple. +#define GTEST_0_TUPLE_(T) tuple<> +#define GTEST_1_TUPLE_(T) tuple +#define GTEST_2_TUPLE_(T) tuple +#define GTEST_3_TUPLE_(T) tuple +#define GTEST_4_TUPLE_(T) tuple +#define GTEST_5_TUPLE_(T) tuple +#define GTEST_6_TUPLE_(T) tuple +#define GTEST_7_TUPLE_(T) tuple +#define GTEST_8_TUPLE_(T) tuple +#define GTEST_9_TUPLE_(T) tuple +#define GTEST_10_TUPLE_(T) tuple + +// GTEST_n_TYPENAMES_(T) declares a list of n typenames. +#define GTEST_0_TYPENAMES_(T) +#define GTEST_1_TYPENAMES_(T) typename T##0 +#define GTEST_2_TYPENAMES_(T) typename T##0, typename T##1 +#define GTEST_3_TYPENAMES_(T) typename T##0, typename T##1, typename T##2 +#define GTEST_4_TYPENAMES_(T) typename T##0, typename T##1, typename T##2, \ + typename T##3 +#define GTEST_5_TYPENAMES_(T) typename T##0, typename T##1, typename T##2, \ + typename T##3, typename T##4 +#define GTEST_6_TYPENAMES_(T) typename T##0, typename T##1, typename T##2, \ + typename T##3, typename T##4, typename T##5 +#define GTEST_7_TYPENAMES_(T) typename T##0, typename T##1, typename T##2, \ + typename T##3, typename T##4, typename T##5, typename T##6 +#define GTEST_8_TYPENAMES_(T) typename T##0, typename T##1, typename T##2, \ + typename T##3, typename T##4, typename T##5, typename T##6, typename T##7 +#define GTEST_9_TYPENAMES_(T) typename T##0, typename T##1, typename T##2, \ + typename T##3, typename T##4, typename T##5, typename T##6, \ + typename T##7, typename T##8 +#define GTEST_10_TYPENAMES_(T) typename T##0, typename T##1, typename T##2, \ + typename T##3, typename T##4, typename T##5, typename T##6, \ + typename T##7, typename T##8, typename T##9 + +// In theory, defining stuff in the ::std namespace is undefined +// behavior. We can do this as we are playing the role of a standard +// library vendor. +namespace std { +namespace tr1 { + +template +class tuple; + +// Anything in namespace gtest_internal is Google Test's INTERNAL +// IMPLEMENTATION DETAIL and MUST NOT BE USED DIRECTLY in user code. +namespace gtest_internal { + +// ByRef::type is T if T is a reference; otherwise it's const T&. +template +struct ByRef { typedef const T& type; }; // NOLINT +template +struct ByRef { typedef T& type; }; // NOLINT + +// A handy wrapper for ByRef. +#define GTEST_BY_REF_(T) typename ::std::tr1::gtest_internal::ByRef::type + +// AddRef::type is T if T is a reference; otherwise it's T&. This +// is the same as tr1::add_reference::type. +template +struct AddRef { typedef T& type; }; // NOLINT +template +struct AddRef { typedef T& type; }; // NOLINT + +// A handy wrapper for AddRef. +#define GTEST_ADD_REF_(T) typename ::std::tr1::gtest_internal::AddRef::type + +// A helper for implementing get(). +template class Get; + +// A helper for implementing tuple_element. kIndexValid is true +// iff k < the number of fields in tuple type T. +template +struct TupleElement; + +template +struct TupleElement { + typedef T0 type; +}; + +template +struct TupleElement { + typedef T1 type; +}; + +template +struct TupleElement { + typedef T2 type; +}; + +template +struct TupleElement { + typedef T3 type; +}; + +template +struct TupleElement { + typedef T4 type; +}; + +template +struct TupleElement { + typedef T5 type; +}; + +template +struct TupleElement { + typedef T6 type; +}; + +template +struct TupleElement { + typedef T7 type; +}; + +template +struct TupleElement { + typedef T8 type; +}; + +template +struct TupleElement { + typedef T9 type; +}; + +} // namespace gtest_internal + +template <> +class tuple<> { + public: + tuple() {} + tuple(const tuple& /* t */) {} + tuple& operator=(const tuple& /* t */) { return *this; } +}; + +template +class GTEST_1_TUPLE_(T) { + public: + template friend class gtest_internal::Get; + + tuple() : f0_() {} + + explicit tuple(GTEST_BY_REF_(T0) f0) : f0_(f0) {} + + tuple(const tuple& t) : f0_(t.f0_) {} + + template + tuple(const GTEST_1_TUPLE_(U)& t) : f0_(t.f0_) {} + + tuple& operator=(const tuple& t) { return CopyFrom(t); } + + template + tuple& operator=(const GTEST_1_TUPLE_(U)& t) { + return CopyFrom(t); + } + + GTEST_DECLARE_TUPLE_AS_FRIEND_ + + template + tuple& CopyFrom(const GTEST_1_TUPLE_(U)& t) { + f0_ = t.f0_; + return *this; + } + + T0 f0_; +}; + +template +class GTEST_2_TUPLE_(T) { + public: + template friend class gtest_internal::Get; + + tuple() : f0_(), f1_() {} + + explicit tuple(GTEST_BY_REF_(T0) f0, GTEST_BY_REF_(T1) f1) : f0_(f0), + f1_(f1) {} + + tuple(const tuple& t) : f0_(t.f0_), f1_(t.f1_) {} + + template + tuple(const GTEST_2_TUPLE_(U)& t) : f0_(t.f0_), f1_(t.f1_) {} + template + tuple(const ::std::pair& p) : f0_(p.first), f1_(p.second) {} + + tuple& operator=(const tuple& t) { return CopyFrom(t); } + + template + tuple& operator=(const GTEST_2_TUPLE_(U)& t) { + return CopyFrom(t); + } + template + tuple& operator=(const ::std::pair& p) { + f0_ = p.first; + f1_ = p.second; + return *this; + } + + GTEST_DECLARE_TUPLE_AS_FRIEND_ + + template + tuple& CopyFrom(const GTEST_2_TUPLE_(U)& t) { + f0_ = t.f0_; + f1_ = t.f1_; + return *this; + } + + T0 f0_; + T1 f1_; +}; + +template +class GTEST_3_TUPLE_(T) { + public: + template friend class gtest_internal::Get; + + tuple() : f0_(), f1_(), f2_() {} + + explicit tuple(GTEST_BY_REF_(T0) f0, GTEST_BY_REF_(T1) f1, + GTEST_BY_REF_(T2) f2) : f0_(f0), f1_(f1), f2_(f2) {} + + tuple(const tuple& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_) {} + + template + tuple(const GTEST_3_TUPLE_(U)& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_) {} + + tuple& operator=(const tuple& t) { return CopyFrom(t); } + + template + tuple& operator=(const GTEST_3_TUPLE_(U)& t) { + return CopyFrom(t); + } + + GTEST_DECLARE_TUPLE_AS_FRIEND_ + + template + tuple& CopyFrom(const GTEST_3_TUPLE_(U)& t) { + f0_ = t.f0_; + f1_ = t.f1_; + f2_ = t.f2_; + return *this; + } + + T0 f0_; + T1 f1_; + T2 f2_; +}; + +template +class GTEST_4_TUPLE_(T) { + public: + template friend class gtest_internal::Get; + + tuple() : f0_(), f1_(), f2_(), f3_() {} + + explicit tuple(GTEST_BY_REF_(T0) f0, GTEST_BY_REF_(T1) f1, + GTEST_BY_REF_(T2) f2, GTEST_BY_REF_(T3) f3) : f0_(f0), f1_(f1), f2_(f2), + f3_(f3) {} + + tuple(const tuple& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), f3_(t.f3_) {} + + template + tuple(const GTEST_4_TUPLE_(U)& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), + f3_(t.f3_) {} + + tuple& operator=(const tuple& t) { return CopyFrom(t); } + + template + tuple& operator=(const GTEST_4_TUPLE_(U)& t) { + return CopyFrom(t); + } + + GTEST_DECLARE_TUPLE_AS_FRIEND_ + + template + tuple& CopyFrom(const GTEST_4_TUPLE_(U)& t) { + f0_ = t.f0_; + f1_ = t.f1_; + f2_ = t.f2_; + f3_ = t.f3_; + return *this; + } + + T0 f0_; + T1 f1_; + T2 f2_; + T3 f3_; +}; + +template +class GTEST_5_TUPLE_(T) { + public: + template friend class gtest_internal::Get; + + tuple() : f0_(), f1_(), f2_(), f3_(), f4_() {} + + explicit tuple(GTEST_BY_REF_(T0) f0, GTEST_BY_REF_(T1) f1, + GTEST_BY_REF_(T2) f2, GTEST_BY_REF_(T3) f3, + GTEST_BY_REF_(T4) f4) : f0_(f0), f1_(f1), f2_(f2), f3_(f3), f4_(f4) {} + + tuple(const tuple& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), f3_(t.f3_), + f4_(t.f4_) {} + + template + tuple(const GTEST_5_TUPLE_(U)& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), + f3_(t.f3_), f4_(t.f4_) {} + + tuple& operator=(const tuple& t) { return CopyFrom(t); } + + template + tuple& operator=(const GTEST_5_TUPLE_(U)& t) { + return CopyFrom(t); + } + + GTEST_DECLARE_TUPLE_AS_FRIEND_ + + template + tuple& CopyFrom(const GTEST_5_TUPLE_(U)& t) { + f0_ = t.f0_; + f1_ = t.f1_; + f2_ = t.f2_; + f3_ = t.f3_; + f4_ = t.f4_; + return *this; + } + + T0 f0_; + T1 f1_; + T2 f2_; + T3 f3_; + T4 f4_; +}; + +template +class GTEST_6_TUPLE_(T) { + public: + template friend class gtest_internal::Get; + + tuple() : f0_(), f1_(), f2_(), f3_(), f4_(), f5_() {} + + explicit tuple(GTEST_BY_REF_(T0) f0, GTEST_BY_REF_(T1) f1, + GTEST_BY_REF_(T2) f2, GTEST_BY_REF_(T3) f3, GTEST_BY_REF_(T4) f4, + GTEST_BY_REF_(T5) f5) : f0_(f0), f1_(f1), f2_(f2), f3_(f3), f4_(f4), + f5_(f5) {} + + tuple(const tuple& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), f3_(t.f3_), + f4_(t.f4_), f5_(t.f5_) {} + + template + tuple(const GTEST_6_TUPLE_(U)& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), + f3_(t.f3_), f4_(t.f4_), f5_(t.f5_) {} + + tuple& operator=(const tuple& t) { return CopyFrom(t); } + + template + tuple& operator=(const GTEST_6_TUPLE_(U)& t) { + return CopyFrom(t); + } + + GTEST_DECLARE_TUPLE_AS_FRIEND_ + + template + tuple& CopyFrom(const GTEST_6_TUPLE_(U)& t) { + f0_ = t.f0_; + f1_ = t.f1_; + f2_ = t.f2_; + f3_ = t.f3_; + f4_ = t.f4_; + f5_ = t.f5_; + return *this; + } + + T0 f0_; + T1 f1_; + T2 f2_; + T3 f3_; + T4 f4_; + T5 f5_; +}; + +template +class GTEST_7_TUPLE_(T) { + public: + template friend class gtest_internal::Get; + + tuple() : f0_(), f1_(), f2_(), f3_(), f4_(), f5_(), f6_() {} + + explicit tuple(GTEST_BY_REF_(T0) f0, GTEST_BY_REF_(T1) f1, + GTEST_BY_REF_(T2) f2, GTEST_BY_REF_(T3) f3, GTEST_BY_REF_(T4) f4, + GTEST_BY_REF_(T5) f5, GTEST_BY_REF_(T6) f6) : f0_(f0), f1_(f1), f2_(f2), + f3_(f3), f4_(f4), f5_(f5), f6_(f6) {} + + tuple(const tuple& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), f3_(t.f3_), + f4_(t.f4_), f5_(t.f5_), f6_(t.f6_) {} + + template + tuple(const GTEST_7_TUPLE_(U)& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), + f3_(t.f3_), f4_(t.f4_), f5_(t.f5_), f6_(t.f6_) {} + + tuple& operator=(const tuple& t) { return CopyFrom(t); } + + template + tuple& operator=(const GTEST_7_TUPLE_(U)& t) { + return CopyFrom(t); + } + + GTEST_DECLARE_TUPLE_AS_FRIEND_ + + template + tuple& CopyFrom(const GTEST_7_TUPLE_(U)& t) { + f0_ = t.f0_; + f1_ = t.f1_; + f2_ = t.f2_; + f3_ = t.f3_; + f4_ = t.f4_; + f5_ = t.f5_; + f6_ = t.f6_; + return *this; + } + + T0 f0_; + T1 f1_; + T2 f2_; + T3 f3_; + T4 f4_; + T5 f5_; + T6 f6_; +}; + +template +class GTEST_8_TUPLE_(T) { + public: + template friend class gtest_internal::Get; + + tuple() : f0_(), f1_(), f2_(), f3_(), f4_(), f5_(), f6_(), f7_() {} + + explicit tuple(GTEST_BY_REF_(T0) f0, GTEST_BY_REF_(T1) f1, + GTEST_BY_REF_(T2) f2, GTEST_BY_REF_(T3) f3, GTEST_BY_REF_(T4) f4, + GTEST_BY_REF_(T5) f5, GTEST_BY_REF_(T6) f6, + GTEST_BY_REF_(T7) f7) : f0_(f0), f1_(f1), f2_(f2), f3_(f3), f4_(f4), + f5_(f5), f6_(f6), f7_(f7) {} + + tuple(const tuple& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), f3_(t.f3_), + f4_(t.f4_), f5_(t.f5_), f6_(t.f6_), f7_(t.f7_) {} + + template + tuple(const GTEST_8_TUPLE_(U)& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), + f3_(t.f3_), f4_(t.f4_), f5_(t.f5_), f6_(t.f6_), f7_(t.f7_) {} + + tuple& operator=(const tuple& t) { return CopyFrom(t); } + + template + tuple& operator=(const GTEST_8_TUPLE_(U)& t) { + return CopyFrom(t); + } + + GTEST_DECLARE_TUPLE_AS_FRIEND_ + + template + tuple& CopyFrom(const GTEST_8_TUPLE_(U)& t) { + f0_ = t.f0_; + f1_ = t.f1_; + f2_ = t.f2_; + f3_ = t.f3_; + f4_ = t.f4_; + f5_ = t.f5_; + f6_ = t.f6_; + f7_ = t.f7_; + return *this; + } + + T0 f0_; + T1 f1_; + T2 f2_; + T3 f3_; + T4 f4_; + T5 f5_; + T6 f6_; + T7 f7_; +}; + +template +class GTEST_9_TUPLE_(T) { + public: + template friend class gtest_internal::Get; + + tuple() : f0_(), f1_(), f2_(), f3_(), f4_(), f5_(), f6_(), f7_(), f8_() {} + + explicit tuple(GTEST_BY_REF_(T0) f0, GTEST_BY_REF_(T1) f1, + GTEST_BY_REF_(T2) f2, GTEST_BY_REF_(T3) f3, GTEST_BY_REF_(T4) f4, + GTEST_BY_REF_(T5) f5, GTEST_BY_REF_(T6) f6, GTEST_BY_REF_(T7) f7, + GTEST_BY_REF_(T8) f8) : f0_(f0), f1_(f1), f2_(f2), f3_(f3), f4_(f4), + f5_(f5), f6_(f6), f7_(f7), f8_(f8) {} + + tuple(const tuple& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), f3_(t.f3_), + f4_(t.f4_), f5_(t.f5_), f6_(t.f6_), f7_(t.f7_), f8_(t.f8_) {} + + template + tuple(const GTEST_9_TUPLE_(U)& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), + f3_(t.f3_), f4_(t.f4_), f5_(t.f5_), f6_(t.f6_), f7_(t.f7_), f8_(t.f8_) {} + + tuple& operator=(const tuple& t) { return CopyFrom(t); } + + template + tuple& operator=(const GTEST_9_TUPLE_(U)& t) { + return CopyFrom(t); + } + + GTEST_DECLARE_TUPLE_AS_FRIEND_ + + template + tuple& CopyFrom(const GTEST_9_TUPLE_(U)& t) { + f0_ = t.f0_; + f1_ = t.f1_; + f2_ = t.f2_; + f3_ = t.f3_; + f4_ = t.f4_; + f5_ = t.f5_; + f6_ = t.f6_; + f7_ = t.f7_; + f8_ = t.f8_; + return *this; + } + + T0 f0_; + T1 f1_; + T2 f2_; + T3 f3_; + T4 f4_; + T5 f5_; + T6 f6_; + T7 f7_; + T8 f8_; +}; + +template +class tuple { + public: + template friend class gtest_internal::Get; + + tuple() : f0_(), f1_(), f2_(), f3_(), f4_(), f5_(), f6_(), f7_(), f8_(), + f9_() {} + + explicit tuple(GTEST_BY_REF_(T0) f0, GTEST_BY_REF_(T1) f1, + GTEST_BY_REF_(T2) f2, GTEST_BY_REF_(T3) f3, GTEST_BY_REF_(T4) f4, + GTEST_BY_REF_(T5) f5, GTEST_BY_REF_(T6) f6, GTEST_BY_REF_(T7) f7, + GTEST_BY_REF_(T8) f8, GTEST_BY_REF_(T9) f9) : f0_(f0), f1_(f1), f2_(f2), + f3_(f3), f4_(f4), f5_(f5), f6_(f6), f7_(f7), f8_(f8), f9_(f9) {} + + tuple(const tuple& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), f3_(t.f3_), + f4_(t.f4_), f5_(t.f5_), f6_(t.f6_), f7_(t.f7_), f8_(t.f8_), f9_(t.f9_) {} + + template + tuple(const GTEST_10_TUPLE_(U)& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), + f3_(t.f3_), f4_(t.f4_), f5_(t.f5_), f6_(t.f6_), f7_(t.f7_), f8_(t.f8_), + f9_(t.f9_) {} + + tuple& operator=(const tuple& t) { return CopyFrom(t); } + + template + tuple& operator=(const GTEST_10_TUPLE_(U)& t) { + return CopyFrom(t); + } + + GTEST_DECLARE_TUPLE_AS_FRIEND_ + + template + tuple& CopyFrom(const GTEST_10_TUPLE_(U)& t) { + f0_ = t.f0_; + f1_ = t.f1_; + f2_ = t.f2_; + f3_ = t.f3_; + f4_ = t.f4_; + f5_ = t.f5_; + f6_ = t.f6_; + f7_ = t.f7_; + f8_ = t.f8_; + f9_ = t.f9_; + return *this; + } + + T0 f0_; + T1 f1_; + T2 f2_; + T3 f3_; + T4 f4_; + T5 f5_; + T6 f6_; + T7 f7_; + T8 f8_; + T9 f9_; +}; + +// 6.1.3.2 Tuple creation functions. + +// Known limitations: we don't support passing an +// std::tr1::reference_wrapper to make_tuple(). And we don't +// implement tie(). + +inline tuple<> make_tuple() { return tuple<>(); } + +template +inline GTEST_1_TUPLE_(T) make_tuple(const T0& f0) { + return GTEST_1_TUPLE_(T)(f0); +} + +template +inline GTEST_2_TUPLE_(T) make_tuple(const T0& f0, const T1& f1) { + return GTEST_2_TUPLE_(T)(f0, f1); +} + +template +inline GTEST_3_TUPLE_(T) make_tuple(const T0& f0, const T1& f1, const T2& f2) { + return GTEST_3_TUPLE_(T)(f0, f1, f2); +} + +template +inline GTEST_4_TUPLE_(T) make_tuple(const T0& f0, const T1& f1, const T2& f2, + const T3& f3) { + return GTEST_4_TUPLE_(T)(f0, f1, f2, f3); +} + +template +inline GTEST_5_TUPLE_(T) make_tuple(const T0& f0, const T1& f1, const T2& f2, + const T3& f3, const T4& f4) { + return GTEST_5_TUPLE_(T)(f0, f1, f2, f3, f4); +} + +template +inline GTEST_6_TUPLE_(T) make_tuple(const T0& f0, const T1& f1, const T2& f2, + const T3& f3, const T4& f4, const T5& f5) { + return GTEST_6_TUPLE_(T)(f0, f1, f2, f3, f4, f5); +} + +template +inline GTEST_7_TUPLE_(T) make_tuple(const T0& f0, const T1& f1, const T2& f2, + const T3& f3, const T4& f4, const T5& f5, const T6& f6) { + return GTEST_7_TUPLE_(T)(f0, f1, f2, f3, f4, f5, f6); +} + +template +inline GTEST_8_TUPLE_(T) make_tuple(const T0& f0, const T1& f1, const T2& f2, + const T3& f3, const T4& f4, const T5& f5, const T6& f6, const T7& f7) { + return GTEST_8_TUPLE_(T)(f0, f1, f2, f3, f4, f5, f6, f7); +} + +template +inline GTEST_9_TUPLE_(T) make_tuple(const T0& f0, const T1& f1, const T2& f2, + const T3& f3, const T4& f4, const T5& f5, const T6& f6, const T7& f7, + const T8& f8) { + return GTEST_9_TUPLE_(T)(f0, f1, f2, f3, f4, f5, f6, f7, f8); +} + +template +inline GTEST_10_TUPLE_(T) make_tuple(const T0& f0, const T1& f1, const T2& f2, + const T3& f3, const T4& f4, const T5& f5, const T6& f6, const T7& f7, + const T8& f8, const T9& f9) { + return GTEST_10_TUPLE_(T)(f0, f1, f2, f3, f4, f5, f6, f7, f8, f9); +} + +// 6.1.3.3 Tuple helper classes. + +template struct tuple_size; + +template +struct tuple_size { + static const int value = 0; +}; + +template +struct tuple_size { + static const int value = 1; +}; + +template +struct tuple_size { + static const int value = 2; +}; + +template +struct tuple_size { + static const int value = 3; +}; + +template +struct tuple_size { + static const int value = 4; +}; + +template +struct tuple_size { + static const int value = 5; +}; + +template +struct tuple_size { + static const int value = 6; +}; + +template +struct tuple_size { + static const int value = 7; +}; + +template +struct tuple_size { + static const int value = 8; +}; + +template +struct tuple_size { + static const int value = 9; +}; + +template +struct tuple_size { + static const int value = 10; +}; + +template +struct tuple_element { + typedef typename gtest_internal::TupleElement< + k < (tuple_size::value), k, Tuple>::type type; +}; + +#define GTEST_TUPLE_ELEMENT_(k, Tuple) typename tuple_element::type + +// 6.1.3.4 Element access. + +namespace gtest_internal { + +template <> +class Get<0> { + public: + template + static GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_(0, Tuple)) + Field(Tuple& t) { return t.f0_; } // NOLINT + + template + static GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_(0, Tuple)) + ConstField(const Tuple& t) { return t.f0_; } +}; + +template <> +class Get<1> { + public: + template + static GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_(1, Tuple)) + Field(Tuple& t) { return t.f1_; } // NOLINT + + template + static GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_(1, Tuple)) + ConstField(const Tuple& t) { return t.f1_; } +}; + +template <> +class Get<2> { + public: + template + static GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_(2, Tuple)) + Field(Tuple& t) { return t.f2_; } // NOLINT + + template + static GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_(2, Tuple)) + ConstField(const Tuple& t) { return t.f2_; } +}; + +template <> +class Get<3> { + public: + template + static GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_(3, Tuple)) + Field(Tuple& t) { return t.f3_; } // NOLINT + + template + static GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_(3, Tuple)) + ConstField(const Tuple& t) { return t.f3_; } +}; + +template <> +class Get<4> { + public: + template + static GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_(4, Tuple)) + Field(Tuple& t) { return t.f4_; } // NOLINT + + template + static GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_(4, Tuple)) + ConstField(const Tuple& t) { return t.f4_; } +}; + +template <> +class Get<5> { + public: + template + static GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_(5, Tuple)) + Field(Tuple& t) { return t.f5_; } // NOLINT + + template + static GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_(5, Tuple)) + ConstField(const Tuple& t) { return t.f5_; } +}; + +template <> +class Get<6> { + public: + template + static GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_(6, Tuple)) + Field(Tuple& t) { return t.f6_; } // NOLINT + + template + static GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_(6, Tuple)) + ConstField(const Tuple& t) { return t.f6_; } +}; + +template <> +class Get<7> { + public: + template + static GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_(7, Tuple)) + Field(Tuple& t) { return t.f7_; } // NOLINT + + template + static GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_(7, Tuple)) + ConstField(const Tuple& t) { return t.f7_; } +}; + +template <> +class Get<8> { + public: + template + static GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_(8, Tuple)) + Field(Tuple& t) { return t.f8_; } // NOLINT + + template + static GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_(8, Tuple)) + ConstField(const Tuple& t) { return t.f8_; } +}; + +template <> +class Get<9> { + public: + template + static GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_(9, Tuple)) + Field(Tuple& t) { return t.f9_; } // NOLINT + + template + static GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_(9, Tuple)) + ConstField(const Tuple& t) { return t.f9_; } +}; + +} // namespace gtest_internal + +template +GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_(k, GTEST_10_TUPLE_(T))) +get(GTEST_10_TUPLE_(T)& t) { + return gtest_internal::Get::Field(t); +} + +template +GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_(k, GTEST_10_TUPLE_(T))) +get(const GTEST_10_TUPLE_(T)& t) { + return gtest_internal::Get::ConstField(t); +} + +// 6.1.3.5 Relational operators + +// We only implement == and !=, as we don't have a need for the rest yet. + +namespace gtest_internal { + +// SameSizeTuplePrefixComparator::Eq(t1, t2) returns true if the +// first k fields of t1 equals the first k fields of t2. +// SameSizeTuplePrefixComparator(k1, k2) would be a compiler error if +// k1 != k2. +template +struct SameSizeTuplePrefixComparator; + +template <> +struct SameSizeTuplePrefixComparator<0, 0> { + template + static bool Eq(const Tuple1& /* t1 */, const Tuple2& /* t2 */) { + return true; + } +}; + +template +struct SameSizeTuplePrefixComparator { + template + static bool Eq(const Tuple1& t1, const Tuple2& t2) { + return SameSizeTuplePrefixComparator::Eq(t1, t2) && + ::std::tr1::get(t1) == ::std::tr1::get(t2); + } +}; + +} // namespace gtest_internal + +template +inline bool operator==(const GTEST_10_TUPLE_(T)& t, + const GTEST_10_TUPLE_(U)& u) { + return gtest_internal::SameSizeTuplePrefixComparator< + tuple_size::value, + tuple_size::value>::Eq(t, u); +} + +template +inline bool operator!=(const GTEST_10_TUPLE_(T)& t, + const GTEST_10_TUPLE_(U)& u) { return !(t == u); } + +// 6.1.4 Pairs. +// Unimplemented. + +} // namespace tr1 +} // namespace std + +#undef GTEST_0_TUPLE_ +#undef GTEST_1_TUPLE_ +#undef GTEST_2_TUPLE_ +#undef GTEST_3_TUPLE_ +#undef GTEST_4_TUPLE_ +#undef GTEST_5_TUPLE_ +#undef GTEST_6_TUPLE_ +#undef GTEST_7_TUPLE_ +#undef GTEST_8_TUPLE_ +#undef GTEST_9_TUPLE_ +#undef GTEST_10_TUPLE_ + +#undef GTEST_0_TYPENAMES_ +#undef GTEST_1_TYPENAMES_ +#undef GTEST_2_TYPENAMES_ +#undef GTEST_3_TYPENAMES_ +#undef GTEST_4_TYPENAMES_ +#undef GTEST_5_TYPENAMES_ +#undef GTEST_6_TYPENAMES_ +#undef GTEST_7_TYPENAMES_ +#undef GTEST_8_TYPENAMES_ +#undef GTEST_9_TYPENAMES_ +#undef GTEST_10_TYPENAMES_ + +#undef GTEST_DECLARE_TUPLE_AS_FRIEND_ +#undef GTEST_BY_REF_ +#undef GTEST_ADD_REF_ +#undef GTEST_TUPLE_ELEMENT_ + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TUPLE_H_ diff --git a/vendor/gmock-1.7.0/gtest/include/gtest/internal/gtest-tuple.h.pump b/vendor/gmock-1.7.0/gtest/include/gtest/internal/gtest-tuple.h.pump new file mode 100644 index 0000000000..c7d9e039b1 --- /dev/null +++ b/vendor/gmock-1.7.0/gtest/include/gtest/internal/gtest-tuple.h.pump @@ -0,0 +1,339 @@ +$$ -*- mode: c++; -*- +$var n = 10 $$ Maximum number of tuple fields we want to support. +$$ This meta comment fixes auto-indentation in Emacs. }} +// Copyright 2009 Google Inc. +// All Rights Reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) + +// Implements a subset of TR1 tuple needed by Google Test and Google Mock. + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TUPLE_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TUPLE_H_ + +#include // For ::std::pair. + +// The compiler used in Symbian has a bug that prevents us from declaring the +// tuple template as a friend (it complains that tuple is redefined). This +// hack bypasses the bug by declaring the members that should otherwise be +// private as public. +// Sun Studio versions < 12 also have the above bug. +#if defined(__SYMBIAN32__) || (defined(__SUNPRO_CC) && __SUNPRO_CC < 0x590) +# define GTEST_DECLARE_TUPLE_AS_FRIEND_ public: +#else +# define GTEST_DECLARE_TUPLE_AS_FRIEND_ \ + template friend class tuple; \ + private: +#endif + + +$range i 0..n-1 +$range j 0..n +$range k 1..n +// GTEST_n_TUPLE_(T) is the type of an n-tuple. +#define GTEST_0_TUPLE_(T) tuple<> + +$for k [[ +$range m 0..k-1 +$range m2 k..n-1 +#define GTEST_$(k)_TUPLE_(T) tuple<$for m, [[T##$m]]$for m2 [[, void]]> + +]] + +// GTEST_n_TYPENAMES_(T) declares a list of n typenames. + +$for j [[ +$range m 0..j-1 +#define GTEST_$(j)_TYPENAMES_(T) $for m, [[typename T##$m]] + + +]] + +// In theory, defining stuff in the ::std namespace is undefined +// behavior. We can do this as we are playing the role of a standard +// library vendor. +namespace std { +namespace tr1 { + +template <$for i, [[typename T$i = void]]> +class tuple; + +// Anything in namespace gtest_internal is Google Test's INTERNAL +// IMPLEMENTATION DETAIL and MUST NOT BE USED DIRECTLY in user code. +namespace gtest_internal { + +// ByRef::type is T if T is a reference; otherwise it's const T&. +template +struct ByRef { typedef const T& type; }; // NOLINT +template +struct ByRef { typedef T& type; }; // NOLINT + +// A handy wrapper for ByRef. +#define GTEST_BY_REF_(T) typename ::std::tr1::gtest_internal::ByRef::type + +// AddRef::type is T if T is a reference; otherwise it's T&. This +// is the same as tr1::add_reference::type. +template +struct AddRef { typedef T& type; }; // NOLINT +template +struct AddRef { typedef T& type; }; // NOLINT + +// A handy wrapper for AddRef. +#define GTEST_ADD_REF_(T) typename ::std::tr1::gtest_internal::AddRef::type + +// A helper for implementing get(). +template class Get; + +// A helper for implementing tuple_element. kIndexValid is true +// iff k < the number of fields in tuple type T. +template +struct TupleElement; + + +$for i [[ +template +struct TupleElement { + typedef T$i type; +}; + + +]] +} // namespace gtest_internal + +template <> +class tuple<> { + public: + tuple() {} + tuple(const tuple& /* t */) {} + tuple& operator=(const tuple& /* t */) { return *this; } +}; + + +$for k [[ +$range m 0..k-1 +template +class $if k < n [[GTEST_$(k)_TUPLE_(T)]] $else [[tuple]] { + public: + template friend class gtest_internal::Get; + + tuple() : $for m, [[f$(m)_()]] {} + + explicit tuple($for m, [[GTEST_BY_REF_(T$m) f$m]]) : [[]] +$for m, [[f$(m)_(f$m)]] {} + + tuple(const tuple& t) : $for m, [[f$(m)_(t.f$(m)_)]] {} + + template + tuple(const GTEST_$(k)_TUPLE_(U)& t) : $for m, [[f$(m)_(t.f$(m)_)]] {} + +$if k == 2 [[ + template + tuple(const ::std::pair& p) : f0_(p.first), f1_(p.second) {} + +]] + + tuple& operator=(const tuple& t) { return CopyFrom(t); } + + template + tuple& operator=(const GTEST_$(k)_TUPLE_(U)& t) { + return CopyFrom(t); + } + +$if k == 2 [[ + template + tuple& operator=(const ::std::pair& p) { + f0_ = p.first; + f1_ = p.second; + return *this; + } + +]] + + GTEST_DECLARE_TUPLE_AS_FRIEND_ + + template + tuple& CopyFrom(const GTEST_$(k)_TUPLE_(U)& t) { + +$for m [[ + f$(m)_ = t.f$(m)_; + +]] + return *this; + } + + +$for m [[ + T$m f$(m)_; + +]] +}; + + +]] +// 6.1.3.2 Tuple creation functions. + +// Known limitations: we don't support passing an +// std::tr1::reference_wrapper to make_tuple(). And we don't +// implement tie(). + +inline tuple<> make_tuple() { return tuple<>(); } + +$for k [[ +$range m 0..k-1 + +template +inline GTEST_$(k)_TUPLE_(T) make_tuple($for m, [[const T$m& f$m]]) { + return GTEST_$(k)_TUPLE_(T)($for m, [[f$m]]); +} + +]] + +// 6.1.3.3 Tuple helper classes. + +template struct tuple_size; + + +$for j [[ +template +struct tuple_size { + static const int value = $j; +}; + + +]] +template +struct tuple_element { + typedef typename gtest_internal::TupleElement< + k < (tuple_size::value), k, Tuple>::type type; +}; + +#define GTEST_TUPLE_ELEMENT_(k, Tuple) typename tuple_element::type + +// 6.1.3.4 Element access. + +namespace gtest_internal { + + +$for i [[ +template <> +class Get<$i> { + public: + template + static GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_($i, Tuple)) + Field(Tuple& t) { return t.f$(i)_; } // NOLINT + + template + static GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_($i, Tuple)) + ConstField(const Tuple& t) { return t.f$(i)_; } +}; + + +]] +} // namespace gtest_internal + +template +GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_(k, GTEST_$(n)_TUPLE_(T))) +get(GTEST_$(n)_TUPLE_(T)& t) { + return gtest_internal::Get::Field(t); +} + +template +GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_(k, GTEST_$(n)_TUPLE_(T))) +get(const GTEST_$(n)_TUPLE_(T)& t) { + return gtest_internal::Get::ConstField(t); +} + +// 6.1.3.5 Relational operators + +// We only implement == and !=, as we don't have a need for the rest yet. + +namespace gtest_internal { + +// SameSizeTuplePrefixComparator::Eq(t1, t2) returns true if the +// first k fields of t1 equals the first k fields of t2. +// SameSizeTuplePrefixComparator(k1, k2) would be a compiler error if +// k1 != k2. +template +struct SameSizeTuplePrefixComparator; + +template <> +struct SameSizeTuplePrefixComparator<0, 0> { + template + static bool Eq(const Tuple1& /* t1 */, const Tuple2& /* t2 */) { + return true; + } +}; + +template +struct SameSizeTuplePrefixComparator { + template + static bool Eq(const Tuple1& t1, const Tuple2& t2) { + return SameSizeTuplePrefixComparator::Eq(t1, t2) && + ::std::tr1::get(t1) == ::std::tr1::get(t2); + } +}; + +} // namespace gtest_internal + +template +inline bool operator==(const GTEST_$(n)_TUPLE_(T)& t, + const GTEST_$(n)_TUPLE_(U)& u) { + return gtest_internal::SameSizeTuplePrefixComparator< + tuple_size::value, + tuple_size::value>::Eq(t, u); +} + +template +inline bool operator!=(const GTEST_$(n)_TUPLE_(T)& t, + const GTEST_$(n)_TUPLE_(U)& u) { return !(t == u); } + +// 6.1.4 Pairs. +// Unimplemented. + +} // namespace tr1 +} // namespace std + + +$for j [[ +#undef GTEST_$(j)_TUPLE_ + +]] + + +$for j [[ +#undef GTEST_$(j)_TYPENAMES_ + +]] + +#undef GTEST_DECLARE_TUPLE_AS_FRIEND_ +#undef GTEST_BY_REF_ +#undef GTEST_ADD_REF_ +#undef GTEST_TUPLE_ELEMENT_ + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TUPLE_H_ diff --git a/vendor/gmock-1.7.0/gtest/include/gtest/internal/gtest-type-util.h b/vendor/gmock-1.7.0/gtest/include/gtest/internal/gtest-type-util.h new file mode 100644 index 0000000000..e46f7cfcb4 --- /dev/null +++ b/vendor/gmock-1.7.0/gtest/include/gtest/internal/gtest-type-util.h @@ -0,0 +1,3331 @@ +// This file was GENERATED by command: +// pump.py gtest-type-util.h.pump +// DO NOT EDIT BY HAND!!! + +// Copyright 2008 Google Inc. +// All Rights Reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) + +// Type utilities needed for implementing typed and type-parameterized +// tests. This file is generated by a SCRIPT. DO NOT EDIT BY HAND! +// +// Currently we support at most 50 types in a list, and at most 50 +// type-parameterized tests in one type-parameterized test case. +// Please contact googletestframework@googlegroups.com if you need +// more. + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TYPE_UTIL_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TYPE_UTIL_H_ + +#include "gtest/internal/gtest-port.h" + +// #ifdef __GNUC__ is too general here. It is possible to use gcc without using +// libstdc++ (which is where cxxabi.h comes from). +# if GTEST_HAS_CXXABI_H_ +# include +# elif defined(__HP_aCC) +# include +# endif // GTEST_HASH_CXXABI_H_ + +namespace testing { +namespace internal { + +// GetTypeName() returns a human-readable name of type T. +// NB: This function is also used in Google Mock, so don't move it inside of +// the typed-test-only section below. +template +std::string GetTypeName() { +# if GTEST_HAS_RTTI + + const char* const name = typeid(T).name(); +# if GTEST_HAS_CXXABI_H_ || defined(__HP_aCC) + int status = 0; + // gcc's implementation of typeid(T).name() mangles the type name, + // so we have to demangle it. +# if GTEST_HAS_CXXABI_H_ + using abi::__cxa_demangle; +# endif // GTEST_HAS_CXXABI_H_ + char* const readable_name = __cxa_demangle(name, 0, 0, &status); + const std::string name_str(status == 0 ? readable_name : name); + free(readable_name); + return name_str; +# else + return name; +# endif // GTEST_HAS_CXXABI_H_ || __HP_aCC + +# else + + return ""; + +# endif // GTEST_HAS_RTTI +} + +#if GTEST_HAS_TYPED_TEST || GTEST_HAS_TYPED_TEST_P + +// AssertyTypeEq::type is defined iff T1 and T2 are the same +// type. This can be used as a compile-time assertion to ensure that +// two types are equal. + +template +struct AssertTypeEq; + +template +struct AssertTypeEq { + typedef bool type; +}; + +// A unique type used as the default value for the arguments of class +// template Types. This allows us to simulate variadic templates +// (e.g. Types, Type, and etc), which C++ doesn't +// support directly. +struct None {}; + +// The following family of struct and struct templates are used to +// represent type lists. In particular, TypesN +// represents a type list with N types (T1, T2, ..., and TN) in it. +// Except for Types0, every struct in the family has two member types: +// Head for the first type in the list, and Tail for the rest of the +// list. + +// The empty type list. +struct Types0 {}; + +// Type lists of length 1, 2, 3, and so on. + +template +struct Types1 { + typedef T1 Head; + typedef Types0 Tail; +}; +template +struct Types2 { + typedef T1 Head; + typedef Types1 Tail; +}; + +template +struct Types3 { + typedef T1 Head; + typedef Types2 Tail; +}; + +template +struct Types4 { + typedef T1 Head; + typedef Types3 Tail; +}; + +template +struct Types5 { + typedef T1 Head; + typedef Types4 Tail; +}; + +template +struct Types6 { + typedef T1 Head; + typedef Types5 Tail; +}; + +template +struct Types7 { + typedef T1 Head; + typedef Types6 Tail; +}; + +template +struct Types8 { + typedef T1 Head; + typedef Types7 Tail; +}; + +template +struct Types9 { + typedef T1 Head; + typedef Types8 Tail; +}; + +template +struct Types10 { + typedef T1 Head; + typedef Types9 Tail; +}; + +template +struct Types11 { + typedef T1 Head; + typedef Types10 Tail; +}; + +template +struct Types12 { + typedef T1 Head; + typedef Types11 Tail; +}; + +template +struct Types13 { + typedef T1 Head; + typedef Types12 Tail; +}; + +template +struct Types14 { + typedef T1 Head; + typedef Types13 Tail; +}; + +template +struct Types15 { + typedef T1 Head; + typedef Types14 Tail; +}; + +template +struct Types16 { + typedef T1 Head; + typedef Types15 Tail; +}; + +template +struct Types17 { + typedef T1 Head; + typedef Types16 Tail; +}; + +template +struct Types18 { + typedef T1 Head; + typedef Types17 Tail; +}; + +template +struct Types19 { + typedef T1 Head; + typedef Types18 Tail; +}; + +template +struct Types20 { + typedef T1 Head; + typedef Types19 Tail; +}; + +template +struct Types21 { + typedef T1 Head; + typedef Types20 Tail; +}; + +template +struct Types22 { + typedef T1 Head; + typedef Types21 Tail; +}; + +template +struct Types23 { + typedef T1 Head; + typedef Types22 Tail; +}; + +template +struct Types24 { + typedef T1 Head; + typedef Types23 Tail; +}; + +template +struct Types25 { + typedef T1 Head; + typedef Types24 Tail; +}; + +template +struct Types26 { + typedef T1 Head; + typedef Types25 Tail; +}; + +template +struct Types27 { + typedef T1 Head; + typedef Types26 Tail; +}; + +template +struct Types28 { + typedef T1 Head; + typedef Types27 Tail; +}; + +template +struct Types29 { + typedef T1 Head; + typedef Types28 Tail; +}; + +template +struct Types30 { + typedef T1 Head; + typedef Types29 Tail; +}; + +template +struct Types31 { + typedef T1 Head; + typedef Types30 Tail; +}; + +template +struct Types32 { + typedef T1 Head; + typedef Types31 Tail; +}; + +template +struct Types33 { + typedef T1 Head; + typedef Types32 Tail; +}; + +template +struct Types34 { + typedef T1 Head; + typedef Types33 Tail; +}; + +template +struct Types35 { + typedef T1 Head; + typedef Types34 Tail; +}; + +template +struct Types36 { + typedef T1 Head; + typedef Types35 Tail; +}; + +template +struct Types37 { + typedef T1 Head; + typedef Types36 Tail; +}; + +template +struct Types38 { + typedef T1 Head; + typedef Types37 Tail; +}; + +template +struct Types39 { + typedef T1 Head; + typedef Types38 Tail; +}; + +template +struct Types40 { + typedef T1 Head; + typedef Types39 Tail; +}; + +template +struct Types41 { + typedef T1 Head; + typedef Types40 Tail; +}; + +template +struct Types42 { + typedef T1 Head; + typedef Types41 Tail; +}; + +template +struct Types43 { + typedef T1 Head; + typedef Types42 Tail; +}; + +template +struct Types44 { + typedef T1 Head; + typedef Types43 Tail; +}; + +template +struct Types45 { + typedef T1 Head; + typedef Types44 Tail; +}; + +template +struct Types46 { + typedef T1 Head; + typedef Types45 Tail; +}; + +template +struct Types47 { + typedef T1 Head; + typedef Types46 Tail; +}; + +template +struct Types48 { + typedef T1 Head; + typedef Types47 Tail; +}; + +template +struct Types49 { + typedef T1 Head; + typedef Types48 Tail; +}; + +template +struct Types50 { + typedef T1 Head; + typedef Types49 Tail; +}; + + +} // namespace internal + +// We don't want to require the users to write TypesN<...> directly, +// as that would require them to count the length. Types<...> is much +// easier to write, but generates horrible messages when there is a +// compiler error, as gcc insists on printing out each template +// argument, even if it has the default value (this means Types +// will appear as Types in the compiler +// errors). +// +// Our solution is to combine the best part of the two approaches: a +// user would write Types, and Google Test will translate +// that to TypesN internally to make error messages +// readable. The translation is done by the 'type' member of the +// Types template. +template +struct Types { + typedef internal::Types50 type; +}; + +template <> +struct Types { + typedef internal::Types0 type; +}; +template +struct Types { + typedef internal::Types1 type; +}; +template +struct Types { + typedef internal::Types2 type; +}; +template +struct Types { + typedef internal::Types3 type; +}; +template +struct Types { + typedef internal::Types4 type; +}; +template +struct Types { + typedef internal::Types5 type; +}; +template +struct Types { + typedef internal::Types6 type; +}; +template +struct Types { + typedef internal::Types7 type; +}; +template +struct Types { + typedef internal::Types8 type; +}; +template +struct Types { + typedef internal::Types9 type; +}; +template +struct Types { + typedef internal::Types10 type; +}; +template +struct Types { + typedef internal::Types11 type; +}; +template +struct Types { + typedef internal::Types12 type; +}; +template +struct Types { + typedef internal::Types13 type; +}; +template +struct Types { + typedef internal::Types14 type; +}; +template +struct Types { + typedef internal::Types15 type; +}; +template +struct Types { + typedef internal::Types16 type; +}; +template +struct Types { + typedef internal::Types17 type; +}; +template +struct Types { + typedef internal::Types18 type; +}; +template +struct Types { + typedef internal::Types19 type; +}; +template +struct Types { + typedef internal::Types20 type; +}; +template +struct Types { + typedef internal::Types21 type; +}; +template +struct Types { + typedef internal::Types22 type; +}; +template +struct Types { + typedef internal::Types23 type; +}; +template +struct Types { + typedef internal::Types24 type; +}; +template +struct Types { + typedef internal::Types25 type; +}; +template +struct Types { + typedef internal::Types26 type; +}; +template +struct Types { + typedef internal::Types27 type; +}; +template +struct Types { + typedef internal::Types28 type; +}; +template +struct Types { + typedef internal::Types29 type; +}; +template +struct Types { + typedef internal::Types30 type; +}; +template +struct Types { + typedef internal::Types31 type; +}; +template +struct Types { + typedef internal::Types32 type; +}; +template +struct Types { + typedef internal::Types33 type; +}; +template +struct Types { + typedef internal::Types34 type; +}; +template +struct Types { + typedef internal::Types35 type; +}; +template +struct Types { + typedef internal::Types36 type; +}; +template +struct Types { + typedef internal::Types37 type; +}; +template +struct Types { + typedef internal::Types38 type; +}; +template +struct Types { + typedef internal::Types39 type; +}; +template +struct Types { + typedef internal::Types40 type; +}; +template +struct Types { + typedef internal::Types41 type; +}; +template +struct Types { + typedef internal::Types42 type; +}; +template +struct Types { + typedef internal::Types43 type; +}; +template +struct Types { + typedef internal::Types44 type; +}; +template +struct Types { + typedef internal::Types45 type; +}; +template +struct Types { + typedef internal::Types46 type; +}; +template +struct Types { + typedef internal::Types47 type; +}; +template +struct Types { + typedef internal::Types48 type; +}; +template +struct Types { + typedef internal::Types49 type; +}; + +namespace internal { + +# define GTEST_TEMPLATE_ template class + +// The template "selector" struct TemplateSel is used to +// represent Tmpl, which must be a class template with one type +// parameter, as a type. TemplateSel::Bind::type is defined +// as the type Tmpl. This allows us to actually instantiate the +// template "selected" by TemplateSel. +// +// This trick is necessary for simulating typedef for class templates, +// which C++ doesn't support directly. +template +struct TemplateSel { + template + struct Bind { + typedef Tmpl type; + }; +}; + +# define GTEST_BIND_(TmplSel, T) \ + TmplSel::template Bind::type + +// A unique struct template used as the default value for the +// arguments of class template Templates. This allows us to simulate +// variadic templates (e.g. Templates, Templates, +// and etc), which C++ doesn't support directly. +template +struct NoneT {}; + +// The following family of struct and struct templates are used to +// represent template lists. In particular, TemplatesN represents a list of N templates (T1, T2, ..., and TN). Except +// for Templates0, every struct in the family has two member types: +// Head for the selector of the first template in the list, and Tail +// for the rest of the list. + +// The empty template list. +struct Templates0 {}; + +// Template lists of length 1, 2, 3, and so on. + +template +struct Templates1 { + typedef TemplateSel Head; + typedef Templates0 Tail; +}; +template +struct Templates2 { + typedef TemplateSel Head; + typedef Templates1 Tail; +}; + +template +struct Templates3 { + typedef TemplateSel Head; + typedef Templates2 Tail; +}; + +template +struct Templates4 { + typedef TemplateSel Head; + typedef Templates3 Tail; +}; + +template +struct Templates5 { + typedef TemplateSel Head; + typedef Templates4 Tail; +}; + +template +struct Templates6 { + typedef TemplateSel Head; + typedef Templates5 Tail; +}; + +template +struct Templates7 { + typedef TemplateSel Head; + typedef Templates6 Tail; +}; + +template +struct Templates8 { + typedef TemplateSel Head; + typedef Templates7 Tail; +}; + +template +struct Templates9 { + typedef TemplateSel Head; + typedef Templates8 Tail; +}; + +template +struct Templates10 { + typedef TemplateSel Head; + typedef Templates9 Tail; +}; + +template +struct Templates11 { + typedef TemplateSel Head; + typedef Templates10 Tail; +}; + +template +struct Templates12 { + typedef TemplateSel Head; + typedef Templates11 Tail; +}; + +template +struct Templates13 { + typedef TemplateSel Head; + typedef Templates12 Tail; +}; + +template +struct Templates14 { + typedef TemplateSel Head; + typedef Templates13 Tail; +}; + +template +struct Templates15 { + typedef TemplateSel Head; + typedef Templates14 Tail; +}; + +template +struct Templates16 { + typedef TemplateSel Head; + typedef Templates15 Tail; +}; + +template +struct Templates17 { + typedef TemplateSel Head; + typedef Templates16 Tail; +}; + +template +struct Templates18 { + typedef TemplateSel Head; + typedef Templates17 Tail; +}; + +template +struct Templates19 { + typedef TemplateSel Head; + typedef Templates18 Tail; +}; + +template +struct Templates20 { + typedef TemplateSel Head; + typedef Templates19 Tail; +}; + +template +struct Templates21 { + typedef TemplateSel Head; + typedef Templates20 Tail; +}; + +template +struct Templates22 { + typedef TemplateSel Head; + typedef Templates21 Tail; +}; + +template +struct Templates23 { + typedef TemplateSel Head; + typedef Templates22 Tail; +}; + +template +struct Templates24 { + typedef TemplateSel Head; + typedef Templates23 Tail; +}; + +template +struct Templates25 { + typedef TemplateSel Head; + typedef Templates24 Tail; +}; + +template +struct Templates26 { + typedef TemplateSel Head; + typedef Templates25 Tail; +}; + +template +struct Templates27 { + typedef TemplateSel Head; + typedef Templates26 Tail; +}; + +template +struct Templates28 { + typedef TemplateSel Head; + typedef Templates27 Tail; +}; + +template +struct Templates29 { + typedef TemplateSel Head; + typedef Templates28 Tail; +}; + +template +struct Templates30 { + typedef TemplateSel Head; + typedef Templates29 Tail; +}; + +template +struct Templates31 { + typedef TemplateSel Head; + typedef Templates30 Tail; +}; + +template +struct Templates32 { + typedef TemplateSel Head; + typedef Templates31 Tail; +}; + +template +struct Templates33 { + typedef TemplateSel Head; + typedef Templates32 Tail; +}; + +template +struct Templates34 { + typedef TemplateSel Head; + typedef Templates33 Tail; +}; + +template +struct Templates35 { + typedef TemplateSel Head; + typedef Templates34 Tail; +}; + +template +struct Templates36 { + typedef TemplateSel Head; + typedef Templates35 Tail; +}; + +template +struct Templates37 { + typedef TemplateSel Head; + typedef Templates36 Tail; +}; + +template +struct Templates38 { + typedef TemplateSel Head; + typedef Templates37 Tail; +}; + +template +struct Templates39 { + typedef TemplateSel Head; + typedef Templates38 Tail; +}; + +template +struct Templates40 { + typedef TemplateSel Head; + typedef Templates39 Tail; +}; + +template +struct Templates41 { + typedef TemplateSel Head; + typedef Templates40 Tail; +}; + +template +struct Templates42 { + typedef TemplateSel Head; + typedef Templates41 Tail; +}; + +template +struct Templates43 { + typedef TemplateSel Head; + typedef Templates42 Tail; +}; + +template +struct Templates44 { + typedef TemplateSel Head; + typedef Templates43 Tail; +}; + +template +struct Templates45 { + typedef TemplateSel Head; + typedef Templates44 Tail; +}; + +template +struct Templates46 { + typedef TemplateSel Head; + typedef Templates45 Tail; +}; + +template +struct Templates47 { + typedef TemplateSel Head; + typedef Templates46 Tail; +}; + +template +struct Templates48 { + typedef TemplateSel Head; + typedef Templates47 Tail; +}; + +template +struct Templates49 { + typedef TemplateSel Head; + typedef Templates48 Tail; +}; + +template +struct Templates50 { + typedef TemplateSel Head; + typedef Templates49 Tail; +}; + + +// We don't want to require the users to write TemplatesN<...> directly, +// as that would require them to count the length. Templates<...> is much +// easier to write, but generates horrible messages when there is a +// compiler error, as gcc insists on printing out each template +// argument, even if it has the default value (this means Templates +// will appear as Templates in the compiler +// errors). +// +// Our solution is to combine the best part of the two approaches: a +// user would write Templates, and Google Test will translate +// that to TemplatesN internally to make error messages +// readable. The translation is done by the 'type' member of the +// Templates template. +template +struct Templates { + typedef Templates50 type; +}; + +template <> +struct Templates { + typedef Templates0 type; +}; +template +struct Templates { + typedef Templates1 type; +}; +template +struct Templates { + typedef Templates2 type; +}; +template +struct Templates { + typedef Templates3 type; +}; +template +struct Templates { + typedef Templates4 type; +}; +template +struct Templates { + typedef Templates5 type; +}; +template +struct Templates { + typedef Templates6 type; +}; +template +struct Templates { + typedef Templates7 type; +}; +template +struct Templates { + typedef Templates8 type; +}; +template +struct Templates { + typedef Templates9 type; +}; +template +struct Templates { + typedef Templates10 type; +}; +template +struct Templates { + typedef Templates11 type; +}; +template +struct Templates { + typedef Templates12 type; +}; +template +struct Templates { + typedef Templates13 type; +}; +template +struct Templates { + typedef Templates14 type; +}; +template +struct Templates { + typedef Templates15 type; +}; +template +struct Templates { + typedef Templates16 type; +}; +template +struct Templates { + typedef Templates17 type; +}; +template +struct Templates { + typedef Templates18 type; +}; +template +struct Templates { + typedef Templates19 type; +}; +template +struct Templates { + typedef Templates20 type; +}; +template +struct Templates { + typedef Templates21 type; +}; +template +struct Templates { + typedef Templates22 type; +}; +template +struct Templates { + typedef Templates23 type; +}; +template +struct Templates { + typedef Templates24 type; +}; +template +struct Templates { + typedef Templates25 type; +}; +template +struct Templates { + typedef Templates26 type; +}; +template +struct Templates { + typedef Templates27 type; +}; +template +struct Templates { + typedef Templates28 type; +}; +template +struct Templates { + typedef Templates29 type; +}; +template +struct Templates { + typedef Templates30 type; +}; +template +struct Templates { + typedef Templates31 type; +}; +template +struct Templates { + typedef Templates32 type; +}; +template +struct Templates { + typedef Templates33 type; +}; +template +struct Templates { + typedef Templates34 type; +}; +template +struct Templates { + typedef Templates35 type; +}; +template +struct Templates { + typedef Templates36 type; +}; +template +struct Templates { + typedef Templates37 type; +}; +template +struct Templates { + typedef Templates38 type; +}; +template +struct Templates { + typedef Templates39 type; +}; +template +struct Templates { + typedef Templates40 type; +}; +template +struct Templates { + typedef Templates41 type; +}; +template +struct Templates { + typedef Templates42 type; +}; +template +struct Templates { + typedef Templates43 type; +}; +template +struct Templates { + typedef Templates44 type; +}; +template +struct Templates { + typedef Templates45 type; +}; +template +struct Templates { + typedef Templates46 type; +}; +template +struct Templates { + typedef Templates47 type; +}; +template +struct Templates { + typedef Templates48 type; +}; +template +struct Templates { + typedef Templates49 type; +}; + +// The TypeList template makes it possible to use either a single type +// or a Types<...> list in TYPED_TEST_CASE() and +// INSTANTIATE_TYPED_TEST_CASE_P(). + +template +struct TypeList { + typedef Types1 type; +}; + +template +struct TypeList > { + typedef typename Types::type type; +}; + +#endif // GTEST_HAS_TYPED_TEST || GTEST_HAS_TYPED_TEST_P + +} // namespace internal +} // namespace testing + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TYPE_UTIL_H_ diff --git a/vendor/gmock-1.7.0/gtest/include/gtest/internal/gtest-type-util.h.pump b/vendor/gmock-1.7.0/gtest/include/gtest/internal/gtest-type-util.h.pump new file mode 100644 index 0000000000..251fdf025b --- /dev/null +++ b/vendor/gmock-1.7.0/gtest/include/gtest/internal/gtest-type-util.h.pump @@ -0,0 +1,297 @@ +$$ -*- mode: c++; -*- +$var n = 50 $$ Maximum length of type lists we want to support. +// Copyright 2008 Google Inc. +// All Rights Reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) + +// Type utilities needed for implementing typed and type-parameterized +// tests. This file is generated by a SCRIPT. DO NOT EDIT BY HAND! +// +// Currently we support at most $n types in a list, and at most $n +// type-parameterized tests in one type-parameterized test case. +// Please contact googletestframework@googlegroups.com if you need +// more. + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TYPE_UTIL_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TYPE_UTIL_H_ + +#include "gtest/internal/gtest-port.h" + +// #ifdef __GNUC__ is too general here. It is possible to use gcc without using +// libstdc++ (which is where cxxabi.h comes from). +# if GTEST_HAS_CXXABI_H_ +# include +# elif defined(__HP_aCC) +# include +# endif // GTEST_HASH_CXXABI_H_ + +namespace testing { +namespace internal { + +// GetTypeName() returns a human-readable name of type T. +// NB: This function is also used in Google Mock, so don't move it inside of +// the typed-test-only section below. +template +std::string GetTypeName() { +# if GTEST_HAS_RTTI + + const char* const name = typeid(T).name(); +# if GTEST_HAS_CXXABI_H_ || defined(__HP_aCC) + int status = 0; + // gcc's implementation of typeid(T).name() mangles the type name, + // so we have to demangle it. +# if GTEST_HAS_CXXABI_H_ + using abi::__cxa_demangle; +# endif // GTEST_HAS_CXXABI_H_ + char* const readable_name = __cxa_demangle(name, 0, 0, &status); + const std::string name_str(status == 0 ? readable_name : name); + free(readable_name); + return name_str; +# else + return name; +# endif // GTEST_HAS_CXXABI_H_ || __HP_aCC + +# else + + return ""; + +# endif // GTEST_HAS_RTTI +} + +#if GTEST_HAS_TYPED_TEST || GTEST_HAS_TYPED_TEST_P + +// AssertyTypeEq::type is defined iff T1 and T2 are the same +// type. This can be used as a compile-time assertion to ensure that +// two types are equal. + +template +struct AssertTypeEq; + +template +struct AssertTypeEq { + typedef bool type; +}; + +// A unique type used as the default value for the arguments of class +// template Types. This allows us to simulate variadic templates +// (e.g. Types, Type, and etc), which C++ doesn't +// support directly. +struct None {}; + +// The following family of struct and struct templates are used to +// represent type lists. In particular, TypesN +// represents a type list with N types (T1, T2, ..., and TN) in it. +// Except for Types0, every struct in the family has two member types: +// Head for the first type in the list, and Tail for the rest of the +// list. + +// The empty type list. +struct Types0 {}; + +// Type lists of length 1, 2, 3, and so on. + +template +struct Types1 { + typedef T1 Head; + typedef Types0 Tail; +}; + +$range i 2..n + +$for i [[ +$range j 1..i +$range k 2..i +template <$for j, [[typename T$j]]> +struct Types$i { + typedef T1 Head; + typedef Types$(i-1)<$for k, [[T$k]]> Tail; +}; + + +]] + +} // namespace internal + +// We don't want to require the users to write TypesN<...> directly, +// as that would require them to count the length. Types<...> is much +// easier to write, but generates horrible messages when there is a +// compiler error, as gcc insists on printing out each template +// argument, even if it has the default value (this means Types +// will appear as Types in the compiler +// errors). +// +// Our solution is to combine the best part of the two approaches: a +// user would write Types, and Google Test will translate +// that to TypesN internally to make error messages +// readable. The translation is done by the 'type' member of the +// Types template. + +$range i 1..n +template <$for i, [[typename T$i = internal::None]]> +struct Types { + typedef internal::Types$n<$for i, [[T$i]]> type; +}; + +template <> +struct Types<$for i, [[internal::None]]> { + typedef internal::Types0 type; +}; + +$range i 1..n-1 +$for i [[ +$range j 1..i +$range k i+1..n +template <$for j, [[typename T$j]]> +struct Types<$for j, [[T$j]]$for k[[, internal::None]]> { + typedef internal::Types$i<$for j, [[T$j]]> type; +}; + +]] + +namespace internal { + +# define GTEST_TEMPLATE_ template class + +// The template "selector" struct TemplateSel is used to +// represent Tmpl, which must be a class template with one type +// parameter, as a type. TemplateSel::Bind::type is defined +// as the type Tmpl. This allows us to actually instantiate the +// template "selected" by TemplateSel. +// +// This trick is necessary for simulating typedef for class templates, +// which C++ doesn't support directly. +template +struct TemplateSel { + template + struct Bind { + typedef Tmpl type; + }; +}; + +# define GTEST_BIND_(TmplSel, T) \ + TmplSel::template Bind::type + +// A unique struct template used as the default value for the +// arguments of class template Templates. This allows us to simulate +// variadic templates (e.g. Templates, Templates, +// and etc), which C++ doesn't support directly. +template +struct NoneT {}; + +// The following family of struct and struct templates are used to +// represent template lists. In particular, TemplatesN represents a list of N templates (T1, T2, ..., and TN). Except +// for Templates0, every struct in the family has two member types: +// Head for the selector of the first template in the list, and Tail +// for the rest of the list. + +// The empty template list. +struct Templates0 {}; + +// Template lists of length 1, 2, 3, and so on. + +template +struct Templates1 { + typedef TemplateSel Head; + typedef Templates0 Tail; +}; + +$range i 2..n + +$for i [[ +$range j 1..i +$range k 2..i +template <$for j, [[GTEST_TEMPLATE_ T$j]]> +struct Templates$i { + typedef TemplateSel Head; + typedef Templates$(i-1)<$for k, [[T$k]]> Tail; +}; + + +]] + +// We don't want to require the users to write TemplatesN<...> directly, +// as that would require them to count the length. Templates<...> is much +// easier to write, but generates horrible messages when there is a +// compiler error, as gcc insists on printing out each template +// argument, even if it has the default value (this means Templates +// will appear as Templates in the compiler +// errors). +// +// Our solution is to combine the best part of the two approaches: a +// user would write Templates, and Google Test will translate +// that to TemplatesN internally to make error messages +// readable. The translation is done by the 'type' member of the +// Templates template. + +$range i 1..n +template <$for i, [[GTEST_TEMPLATE_ T$i = NoneT]]> +struct Templates { + typedef Templates$n<$for i, [[T$i]]> type; +}; + +template <> +struct Templates<$for i, [[NoneT]]> { + typedef Templates0 type; +}; + +$range i 1..n-1 +$for i [[ +$range j 1..i +$range k i+1..n +template <$for j, [[GTEST_TEMPLATE_ T$j]]> +struct Templates<$for j, [[T$j]]$for k[[, NoneT]]> { + typedef Templates$i<$for j, [[T$j]]> type; +}; + +]] + +// The TypeList template makes it possible to use either a single type +// or a Types<...> list in TYPED_TEST_CASE() and +// INSTANTIATE_TYPED_TEST_CASE_P(). + +template +struct TypeList { + typedef Types1 type; +}; + + +$range i 1..n +template <$for i, [[typename T$i]]> +struct TypeList > { + typedef typename Types<$for i, [[T$i]]>::type type; +}; + +#endif // GTEST_HAS_TYPED_TEST || GTEST_HAS_TYPED_TEST_P + +} // namespace internal +} // namespace testing + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TYPE_UTIL_H_ diff --git a/vendor/gmock-1.7.0/gtest/src/gtest-all.cc b/vendor/gmock-1.7.0/gtest/src/gtest-all.cc new file mode 100644 index 0000000000..0a9cee5223 --- /dev/null +++ b/vendor/gmock-1.7.0/gtest/src/gtest-all.cc @@ -0,0 +1,48 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: mheule@google.com (Markus Heule) +// +// Google C++ Testing Framework (Google Test) +// +// Sometimes it's desirable to build Google Test by compiling a single file. +// This file serves this purpose. + +// This line ensures that gtest.h can be compiled on its own, even +// when it's fused. +#include "gtest/gtest.h" + +// The following lines pull in the real gtest *.cc files. +#include "src/gtest.cc" +#include "src/gtest-death-test.cc" +#include "src/gtest-filepath.cc" +#include "src/gtest-port.cc" +#include "src/gtest-printers.cc" +#include "src/gtest-test-part.cc" +#include "src/gtest-typed-test.cc" diff --git a/vendor/gmock-1.7.0/gtest/src/gtest-death-test.cc b/vendor/gmock-1.7.0/gtest/src/gtest-death-test.cc new file mode 100644 index 0000000000..a6023fce4f --- /dev/null +++ b/vendor/gmock-1.7.0/gtest/src/gtest-death-test.cc @@ -0,0 +1,1344 @@ +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan), vladl@google.com (Vlad Losev) +// +// This file implements death tests. + +#include "gtest/gtest-death-test.h" +#include "gtest/internal/gtest-port.h" + +#if GTEST_HAS_DEATH_TEST + +# if GTEST_OS_MAC +# include +# endif // GTEST_OS_MAC + +# include +# include +# include + +# if GTEST_OS_LINUX +# include +# endif // GTEST_OS_LINUX + +# include + +# if GTEST_OS_WINDOWS +# include +# else +# include +# include +# endif // GTEST_OS_WINDOWS + +# if GTEST_OS_QNX +# include +# endif // GTEST_OS_QNX + +#endif // GTEST_HAS_DEATH_TEST + +#include "gtest/gtest-message.h" +#include "gtest/internal/gtest-string.h" + +// Indicates that this translation unit is part of Google Test's +// implementation. It must come before gtest-internal-inl.h is +// included, or there will be a compiler error. This trick is to +// prevent a user from accidentally including gtest-internal-inl.h in +// his code. +#define GTEST_IMPLEMENTATION_ 1 +#include "src/gtest-internal-inl.h" +#undef GTEST_IMPLEMENTATION_ + +namespace testing { + +// Constants. + +// The default death test style. +static const char kDefaultDeathTestStyle[] = "fast"; + +GTEST_DEFINE_string_( + death_test_style, + internal::StringFromGTestEnv("death_test_style", kDefaultDeathTestStyle), + "Indicates how to run a death test in a forked child process: " + "\"threadsafe\" (child process re-executes the test binary " + "from the beginning, running only the specific death test) or " + "\"fast\" (child process runs the death test immediately " + "after forking)."); + +GTEST_DEFINE_bool_( + death_test_use_fork, + internal::BoolFromGTestEnv("death_test_use_fork", false), + "Instructs to use fork()/_exit() instead of clone() in death tests. " + "Ignored and always uses fork() on POSIX systems where clone() is not " + "implemented. Useful when running under valgrind or similar tools if " + "those do not support clone(). Valgrind 3.3.1 will just fail if " + "it sees an unsupported combination of clone() flags. " + "It is not recommended to use this flag w/o valgrind though it will " + "work in 99% of the cases. Once valgrind is fixed, this flag will " + "most likely be removed."); + +namespace internal { +GTEST_DEFINE_string_( + internal_run_death_test, "", + "Indicates the file, line number, temporal index of " + "the single death test to run, and a file descriptor to " + "which a success code may be sent, all separated by " + "the '|' characters. This flag is specified if and only if the current " + "process is a sub-process launched for running a thread-safe " + "death test. FOR INTERNAL USE ONLY."); +} // namespace internal + +#if GTEST_HAS_DEATH_TEST + +namespace internal { + +// Valid only for fast death tests. Indicates the code is running in the +// child process of a fast style death test. +static bool g_in_fast_death_test_child = false; + +// Returns a Boolean value indicating whether the caller is currently +// executing in the context of the death test child process. Tools such as +// Valgrind heap checkers may need this to modify their behavior in death +// tests. IMPORTANT: This is an internal utility. Using it may break the +// implementation of death tests. User code MUST NOT use it. +bool InDeathTestChild() { +# if GTEST_OS_WINDOWS + + // On Windows, death tests are thread-safe regardless of the value of the + // death_test_style flag. + return !GTEST_FLAG(internal_run_death_test).empty(); + +# else + + if (GTEST_FLAG(death_test_style) == "threadsafe") + return !GTEST_FLAG(internal_run_death_test).empty(); + else + return g_in_fast_death_test_child; +#endif +} + +} // namespace internal + +// ExitedWithCode constructor. +ExitedWithCode::ExitedWithCode(int exit_code) : exit_code_(exit_code) { +} + +// ExitedWithCode function-call operator. +bool ExitedWithCode::operator()(int exit_status) const { +# if GTEST_OS_WINDOWS + + return exit_status == exit_code_; + +# else + + return WIFEXITED(exit_status) && WEXITSTATUS(exit_status) == exit_code_; + +# endif // GTEST_OS_WINDOWS +} + +# if !GTEST_OS_WINDOWS +// KilledBySignal constructor. +KilledBySignal::KilledBySignal(int signum) : signum_(signum) { +} + +// KilledBySignal function-call operator. +bool KilledBySignal::operator()(int exit_status) const { + return WIFSIGNALED(exit_status) && WTERMSIG(exit_status) == signum_; +} +# endif // !GTEST_OS_WINDOWS + +namespace internal { + +// Utilities needed for death tests. + +// Generates a textual description of a given exit code, in the format +// specified by wait(2). +static std::string ExitSummary(int exit_code) { + Message m; + +# if GTEST_OS_WINDOWS + + m << "Exited with exit status " << exit_code; + +# else + + if (WIFEXITED(exit_code)) { + m << "Exited with exit status " << WEXITSTATUS(exit_code); + } else if (WIFSIGNALED(exit_code)) { + m << "Terminated by signal " << WTERMSIG(exit_code); + } +# ifdef WCOREDUMP + if (WCOREDUMP(exit_code)) { + m << " (core dumped)"; + } +# endif +# endif // GTEST_OS_WINDOWS + + return m.GetString(); +} + +// Returns true if exit_status describes a process that was terminated +// by a signal, or exited normally with a nonzero exit code. +bool ExitedUnsuccessfully(int exit_status) { + return !ExitedWithCode(0)(exit_status); +} + +# if !GTEST_OS_WINDOWS +// Generates a textual failure message when a death test finds more than +// one thread running, or cannot determine the number of threads, prior +// to executing the given statement. It is the responsibility of the +// caller not to pass a thread_count of 1. +static std::string DeathTestThreadWarning(size_t thread_count) { + Message msg; + msg << "Death tests use fork(), which is unsafe particularly" + << " in a threaded context. For this test, " << GTEST_NAME_ << " "; + if (thread_count == 0) + msg << "couldn't detect the number of threads."; + else + msg << "detected " << thread_count << " threads."; + return msg.GetString(); +} +# endif // !GTEST_OS_WINDOWS + +// Flag characters for reporting a death test that did not die. +static const char kDeathTestLived = 'L'; +static const char kDeathTestReturned = 'R'; +static const char kDeathTestThrew = 'T'; +static const char kDeathTestInternalError = 'I'; + +// An enumeration describing all of the possible ways that a death test can +// conclude. DIED means that the process died while executing the test +// code; LIVED means that process lived beyond the end of the test code; +// RETURNED means that the test statement attempted to execute a return +// statement, which is not allowed; THREW means that the test statement +// returned control by throwing an exception. IN_PROGRESS means the test +// has not yet concluded. +// TODO(vladl@google.com): Unify names and possibly values for +// AbortReason, DeathTestOutcome, and flag characters above. +enum DeathTestOutcome { IN_PROGRESS, DIED, LIVED, RETURNED, THREW }; + +// Routine for aborting the program which is safe to call from an +// exec-style death test child process, in which case the error +// message is propagated back to the parent process. Otherwise, the +// message is simply printed to stderr. In either case, the program +// then exits with status 1. +void DeathTestAbort(const std::string& message) { + // On a POSIX system, this function may be called from a threadsafe-style + // death test child process, which operates on a very small stack. Use + // the heap for any additional non-minuscule memory requirements. + const InternalRunDeathTestFlag* const flag = + GetUnitTestImpl()->internal_run_death_test_flag(); + if (flag != NULL) { + FILE* parent = posix::FDOpen(flag->write_fd(), "w"); + fputc(kDeathTestInternalError, parent); + fprintf(parent, "%s", message.c_str()); + fflush(parent); + _exit(1); + } else { + fprintf(stderr, "%s", message.c_str()); + fflush(stderr); + posix::Abort(); + } +} + +// A replacement for CHECK that calls DeathTestAbort if the assertion +// fails. +# define GTEST_DEATH_TEST_CHECK_(expression) \ + do { \ + if (!::testing::internal::IsTrue(expression)) { \ + DeathTestAbort( \ + ::std::string("CHECK failed: File ") + __FILE__ + ", line " \ + + ::testing::internal::StreamableToString(__LINE__) + ": " \ + + #expression); \ + } \ + } while (::testing::internal::AlwaysFalse()) + +// This macro is similar to GTEST_DEATH_TEST_CHECK_, but it is meant for +// evaluating any system call that fulfills two conditions: it must return +// -1 on failure, and set errno to EINTR when it is interrupted and +// should be tried again. The macro expands to a loop that repeatedly +// evaluates the expression as long as it evaluates to -1 and sets +// errno to EINTR. If the expression evaluates to -1 but errno is +// something other than EINTR, DeathTestAbort is called. +# define GTEST_DEATH_TEST_CHECK_SYSCALL_(expression) \ + do { \ + int gtest_retval; \ + do { \ + gtest_retval = (expression); \ + } while (gtest_retval == -1 && errno == EINTR); \ + if (gtest_retval == -1) { \ + DeathTestAbort( \ + ::std::string("CHECK failed: File ") + __FILE__ + ", line " \ + + ::testing::internal::StreamableToString(__LINE__) + ": " \ + + #expression + " != -1"); \ + } \ + } while (::testing::internal::AlwaysFalse()) + +// Returns the message describing the last system error in errno. +std::string GetLastErrnoDescription() { + return errno == 0 ? "" : posix::StrError(errno); +} + +// This is called from a death test parent process to read a failure +// message from the death test child process and log it with the FATAL +// severity. On Windows, the message is read from a pipe handle. On other +// platforms, it is read from a file descriptor. +static void FailFromInternalError(int fd) { + Message error; + char buffer[256]; + int num_read; + + do { + while ((num_read = posix::Read(fd, buffer, 255)) > 0) { + buffer[num_read] = '\0'; + error << buffer; + } + } while (num_read == -1 && errno == EINTR); + + if (num_read == 0) { + GTEST_LOG_(FATAL) << error.GetString(); + } else { + const int last_error = errno; + GTEST_LOG_(FATAL) << "Error while reading death test internal: " + << GetLastErrnoDescription() << " [" << last_error << "]"; + } +} + +// Death test constructor. Increments the running death test count +// for the current test. +DeathTest::DeathTest() { + TestInfo* const info = GetUnitTestImpl()->current_test_info(); + if (info == NULL) { + DeathTestAbort("Cannot run a death test outside of a TEST or " + "TEST_F construct"); + } +} + +// Creates and returns a death test by dispatching to the current +// death test factory. +bool DeathTest::Create(const char* statement, const RE* regex, + const char* file, int line, DeathTest** test) { + return GetUnitTestImpl()->death_test_factory()->Create( + statement, regex, file, line, test); +} + +const char* DeathTest::LastMessage() { + return last_death_test_message_.c_str(); +} + +void DeathTest::set_last_death_test_message(const std::string& message) { + last_death_test_message_ = message; +} + +std::string DeathTest::last_death_test_message_; + +// Provides cross platform implementation for some death functionality. +class DeathTestImpl : public DeathTest { + protected: + DeathTestImpl(const char* a_statement, const RE* a_regex) + : statement_(a_statement), + regex_(a_regex), + spawned_(false), + status_(-1), + outcome_(IN_PROGRESS), + read_fd_(-1), + write_fd_(-1) {} + + // read_fd_ is expected to be closed and cleared by a derived class. + ~DeathTestImpl() { GTEST_DEATH_TEST_CHECK_(read_fd_ == -1); } + + void Abort(AbortReason reason); + virtual bool Passed(bool status_ok); + + const char* statement() const { return statement_; } + const RE* regex() const { return regex_; } + bool spawned() const { return spawned_; } + void set_spawned(bool is_spawned) { spawned_ = is_spawned; } + int status() const { return status_; } + void set_status(int a_status) { status_ = a_status; } + DeathTestOutcome outcome() const { return outcome_; } + void set_outcome(DeathTestOutcome an_outcome) { outcome_ = an_outcome; } + int read_fd() const { return read_fd_; } + void set_read_fd(int fd) { read_fd_ = fd; } + int write_fd() const { return write_fd_; } + void set_write_fd(int fd) { write_fd_ = fd; } + + // Called in the parent process only. Reads the result code of the death + // test child process via a pipe, interprets it to set the outcome_ + // member, and closes read_fd_. Outputs diagnostics and terminates in + // case of unexpected codes. + void ReadAndInterpretStatusByte(); + + private: + // The textual content of the code this object is testing. This class + // doesn't own this string and should not attempt to delete it. + const char* const statement_; + // The regular expression which test output must match. DeathTestImpl + // doesn't own this object and should not attempt to delete it. + const RE* const regex_; + // True if the death test child process has been successfully spawned. + bool spawned_; + // The exit status of the child process. + int status_; + // How the death test concluded. + DeathTestOutcome outcome_; + // Descriptor to the read end of the pipe to the child process. It is + // always -1 in the child process. The child keeps its write end of the + // pipe in write_fd_. + int read_fd_; + // Descriptor to the child's write end of the pipe to the parent process. + // It is always -1 in the parent process. The parent keeps its end of the + // pipe in read_fd_. + int write_fd_; +}; + +// Called in the parent process only. Reads the result code of the death +// test child process via a pipe, interprets it to set the outcome_ +// member, and closes read_fd_. Outputs diagnostics and terminates in +// case of unexpected codes. +void DeathTestImpl::ReadAndInterpretStatusByte() { + char flag; + int bytes_read; + + // The read() here blocks until data is available (signifying the + // failure of the death test) or until the pipe is closed (signifying + // its success), so it's okay to call this in the parent before + // the child process has exited. + do { + bytes_read = posix::Read(read_fd(), &flag, 1); + } while (bytes_read == -1 && errno == EINTR); + + if (bytes_read == 0) { + set_outcome(DIED); + } else if (bytes_read == 1) { + switch (flag) { + case kDeathTestReturned: + set_outcome(RETURNED); + break; + case kDeathTestThrew: + set_outcome(THREW); + break; + case kDeathTestLived: + set_outcome(LIVED); + break; + case kDeathTestInternalError: + FailFromInternalError(read_fd()); // Does not return. + break; + default: + GTEST_LOG_(FATAL) << "Death test child process reported " + << "unexpected status byte (" + << static_cast(flag) << ")"; + } + } else { + GTEST_LOG_(FATAL) << "Read from death test child process failed: " + << GetLastErrnoDescription(); + } + GTEST_DEATH_TEST_CHECK_SYSCALL_(posix::Close(read_fd())); + set_read_fd(-1); +} + +// Signals that the death test code which should have exited, didn't. +// Should be called only in a death test child process. +// Writes a status byte to the child's status file descriptor, then +// calls _exit(1). +void DeathTestImpl::Abort(AbortReason reason) { + // The parent process considers the death test to be a failure if + // it finds any data in our pipe. So, here we write a single flag byte + // to the pipe, then exit. + const char status_ch = + reason == TEST_DID_NOT_DIE ? kDeathTestLived : + reason == TEST_THREW_EXCEPTION ? kDeathTestThrew : kDeathTestReturned; + + GTEST_DEATH_TEST_CHECK_SYSCALL_(posix::Write(write_fd(), &status_ch, 1)); + // We are leaking the descriptor here because on some platforms (i.e., + // when built as Windows DLL), destructors of global objects will still + // run after calling _exit(). On such systems, write_fd_ will be + // indirectly closed from the destructor of UnitTestImpl, causing double + // close if it is also closed here. On debug configurations, double close + // may assert. As there are no in-process buffers to flush here, we are + // relying on the OS to close the descriptor after the process terminates + // when the destructors are not run. + _exit(1); // Exits w/o any normal exit hooks (we were supposed to crash) +} + +// Returns an indented copy of stderr output for a death test. +// This makes distinguishing death test output lines from regular log lines +// much easier. +static ::std::string FormatDeathTestOutput(const ::std::string& output) { + ::std::string ret; + for (size_t at = 0; ; ) { + const size_t line_end = output.find('\n', at); + ret += "[ DEATH ] "; + if (line_end == ::std::string::npos) { + ret += output.substr(at); + break; + } + ret += output.substr(at, line_end + 1 - at); + at = line_end + 1; + } + return ret; +} + +// Assesses the success or failure of a death test, using both private +// members which have previously been set, and one argument: +// +// Private data members: +// outcome: An enumeration describing how the death test +// concluded: DIED, LIVED, THREW, or RETURNED. The death test +// fails in the latter three cases. +// status: The exit status of the child process. On *nix, it is in the +// in the format specified by wait(2). On Windows, this is the +// value supplied to the ExitProcess() API or a numeric code +// of the exception that terminated the program. +// regex: A regular expression object to be applied to +// the test's captured standard error output; the death test +// fails if it does not match. +// +// Argument: +// status_ok: true if exit_status is acceptable in the context of +// this particular death test, which fails if it is false +// +// Returns true iff all of the above conditions are met. Otherwise, the +// first failing condition, in the order given above, is the one that is +// reported. Also sets the last death test message string. +bool DeathTestImpl::Passed(bool status_ok) { + if (!spawned()) + return false; + + const std::string error_message = GetCapturedStderr(); + + bool success = false; + Message buffer; + + buffer << "Death test: " << statement() << "\n"; + switch (outcome()) { + case LIVED: + buffer << " Result: failed to die.\n" + << " Error msg:\n" << FormatDeathTestOutput(error_message); + break; + case THREW: + buffer << " Result: threw an exception.\n" + << " Error msg:\n" << FormatDeathTestOutput(error_message); + break; + case RETURNED: + buffer << " Result: illegal return in test statement.\n" + << " Error msg:\n" << FormatDeathTestOutput(error_message); + break; + case DIED: + if (status_ok) { + const bool matched = RE::PartialMatch(error_message.c_str(), *regex()); + if (matched) { + success = true; + } else { + buffer << " Result: died but not with expected error.\n" + << " Expected: " << regex()->pattern() << "\n" + << "Actual msg:\n" << FormatDeathTestOutput(error_message); + } + } else { + buffer << " Result: died but not with expected exit code:\n" + << " " << ExitSummary(status()) << "\n" + << "Actual msg:\n" << FormatDeathTestOutput(error_message); + } + break; + case IN_PROGRESS: + default: + GTEST_LOG_(FATAL) + << "DeathTest::Passed somehow called before conclusion of test"; + } + + DeathTest::set_last_death_test_message(buffer.GetString()); + return success; +} + +# if GTEST_OS_WINDOWS +// WindowsDeathTest implements death tests on Windows. Due to the +// specifics of starting new processes on Windows, death tests there are +// always threadsafe, and Google Test considers the +// --gtest_death_test_style=fast setting to be equivalent to +// --gtest_death_test_style=threadsafe there. +// +// A few implementation notes: Like the Linux version, the Windows +// implementation uses pipes for child-to-parent communication. But due to +// the specifics of pipes on Windows, some extra steps are required: +// +// 1. The parent creates a communication pipe and stores handles to both +// ends of it. +// 2. The parent starts the child and provides it with the information +// necessary to acquire the handle to the write end of the pipe. +// 3. The child acquires the write end of the pipe and signals the parent +// using a Windows event. +// 4. Now the parent can release the write end of the pipe on its side. If +// this is done before step 3, the object's reference count goes down to +// 0 and it is destroyed, preventing the child from acquiring it. The +// parent now has to release it, or read operations on the read end of +// the pipe will not return when the child terminates. +// 5. The parent reads child's output through the pipe (outcome code and +// any possible error messages) from the pipe, and its stderr and then +// determines whether to fail the test. +// +// Note: to distinguish Win32 API calls from the local method and function +// calls, the former are explicitly resolved in the global namespace. +// +class WindowsDeathTest : public DeathTestImpl { + public: + WindowsDeathTest(const char* a_statement, + const RE* a_regex, + const char* file, + int line) + : DeathTestImpl(a_statement, a_regex), file_(file), line_(line) {} + + // All of these virtual functions are inherited from DeathTest. + virtual int Wait(); + virtual TestRole AssumeRole(); + + private: + // The name of the file in which the death test is located. + const char* const file_; + // The line number on which the death test is located. + const int line_; + // Handle to the write end of the pipe to the child process. + AutoHandle write_handle_; + // Child process handle. + AutoHandle child_handle_; + // Event the child process uses to signal the parent that it has + // acquired the handle to the write end of the pipe. After seeing this + // event the parent can release its own handles to make sure its + // ReadFile() calls return when the child terminates. + AutoHandle event_handle_; +}; + +// Waits for the child in a death test to exit, returning its exit +// status, or 0 if no child process exists. As a side effect, sets the +// outcome data member. +int WindowsDeathTest::Wait() { + if (!spawned()) + return 0; + + // Wait until the child either signals that it has acquired the write end + // of the pipe or it dies. + const HANDLE wait_handles[2] = { child_handle_.Get(), event_handle_.Get() }; + switch (::WaitForMultipleObjects(2, + wait_handles, + FALSE, // Waits for any of the handles. + INFINITE)) { + case WAIT_OBJECT_0: + case WAIT_OBJECT_0 + 1: + break; + default: + GTEST_DEATH_TEST_CHECK_(false); // Should not get here. + } + + // The child has acquired the write end of the pipe or exited. + // We release the handle on our side and continue. + write_handle_.Reset(); + event_handle_.Reset(); + + ReadAndInterpretStatusByte(); + + // Waits for the child process to exit if it haven't already. This + // returns immediately if the child has already exited, regardless of + // whether previous calls to WaitForMultipleObjects synchronized on this + // handle or not. + GTEST_DEATH_TEST_CHECK_( + WAIT_OBJECT_0 == ::WaitForSingleObject(child_handle_.Get(), + INFINITE)); + DWORD status_code; + GTEST_DEATH_TEST_CHECK_( + ::GetExitCodeProcess(child_handle_.Get(), &status_code) != FALSE); + child_handle_.Reset(); + set_status(static_cast(status_code)); + return status(); +} + +// The AssumeRole process for a Windows death test. It creates a child +// process with the same executable as the current process to run the +// death test. The child process is given the --gtest_filter and +// --gtest_internal_run_death_test flags such that it knows to run the +// current death test only. +DeathTest::TestRole WindowsDeathTest::AssumeRole() { + const UnitTestImpl* const impl = GetUnitTestImpl(); + const InternalRunDeathTestFlag* const flag = + impl->internal_run_death_test_flag(); + const TestInfo* const info = impl->current_test_info(); + const int death_test_index = info->result()->death_test_count(); + + if (flag != NULL) { + // ParseInternalRunDeathTestFlag() has performed all the necessary + // processing. + set_write_fd(flag->write_fd()); + return EXECUTE_TEST; + } + + // WindowsDeathTest uses an anonymous pipe to communicate results of + // a death test. + SECURITY_ATTRIBUTES handles_are_inheritable = { + sizeof(SECURITY_ATTRIBUTES), NULL, TRUE }; + HANDLE read_handle, write_handle; + GTEST_DEATH_TEST_CHECK_( + ::CreatePipe(&read_handle, &write_handle, &handles_are_inheritable, + 0) // Default buffer size. + != FALSE); + set_read_fd(::_open_osfhandle(reinterpret_cast(read_handle), + O_RDONLY)); + write_handle_.Reset(write_handle); + event_handle_.Reset(::CreateEvent( + &handles_are_inheritable, + TRUE, // The event will automatically reset to non-signaled state. + FALSE, // The initial state is non-signalled. + NULL)); // The even is unnamed. + GTEST_DEATH_TEST_CHECK_(event_handle_.Get() != NULL); + const std::string filter_flag = + std::string("--") + GTEST_FLAG_PREFIX_ + kFilterFlag + "=" + + info->test_case_name() + "." + info->name(); + const std::string internal_flag = + std::string("--") + GTEST_FLAG_PREFIX_ + kInternalRunDeathTestFlag + + "=" + file_ + "|" + StreamableToString(line_) + "|" + + StreamableToString(death_test_index) + "|" + + StreamableToString(static_cast(::GetCurrentProcessId())) + + // size_t has the same width as pointers on both 32-bit and 64-bit + // Windows platforms. + // See http://msdn.microsoft.com/en-us/library/tcxf1dw6.aspx. + "|" + StreamableToString(reinterpret_cast(write_handle)) + + "|" + StreamableToString(reinterpret_cast(event_handle_.Get())); + + char executable_path[_MAX_PATH + 1]; // NOLINT + GTEST_DEATH_TEST_CHECK_( + _MAX_PATH + 1 != ::GetModuleFileNameA(NULL, + executable_path, + _MAX_PATH)); + + std::string command_line = + std::string(::GetCommandLineA()) + " " + filter_flag + " \"" + + internal_flag + "\""; + + DeathTest::set_last_death_test_message(""); + + CaptureStderr(); + // Flush the log buffers since the log streams are shared with the child. + FlushInfoLog(); + + // The child process will share the standard handles with the parent. + STARTUPINFOA startup_info; + memset(&startup_info, 0, sizeof(STARTUPINFO)); + startup_info.dwFlags = STARTF_USESTDHANDLES; + startup_info.hStdInput = ::GetStdHandle(STD_INPUT_HANDLE); + startup_info.hStdOutput = ::GetStdHandle(STD_OUTPUT_HANDLE); + startup_info.hStdError = ::GetStdHandle(STD_ERROR_HANDLE); + + PROCESS_INFORMATION process_info; + GTEST_DEATH_TEST_CHECK_(::CreateProcessA( + executable_path, + const_cast(command_line.c_str()), + NULL, // Retuned process handle is not inheritable. + NULL, // Retuned thread handle is not inheritable. + TRUE, // Child inherits all inheritable handles (for write_handle_). + 0x0, // Default creation flags. + NULL, // Inherit the parent's environment. + UnitTest::GetInstance()->original_working_dir(), + &startup_info, + &process_info) != FALSE); + child_handle_.Reset(process_info.hProcess); + ::CloseHandle(process_info.hThread); + set_spawned(true); + return OVERSEE_TEST; +} +# else // We are not on Windows. + +// ForkingDeathTest provides implementations for most of the abstract +// methods of the DeathTest interface. Only the AssumeRole method is +// left undefined. +class ForkingDeathTest : public DeathTestImpl { + public: + ForkingDeathTest(const char* statement, const RE* regex); + + // All of these virtual functions are inherited from DeathTest. + virtual int Wait(); + + protected: + void set_child_pid(pid_t child_pid) { child_pid_ = child_pid; } + + private: + // PID of child process during death test; 0 in the child process itself. + pid_t child_pid_; +}; + +// Constructs a ForkingDeathTest. +ForkingDeathTest::ForkingDeathTest(const char* a_statement, const RE* a_regex) + : DeathTestImpl(a_statement, a_regex), + child_pid_(-1) {} + +// Waits for the child in a death test to exit, returning its exit +// status, or 0 if no child process exists. As a side effect, sets the +// outcome data member. +int ForkingDeathTest::Wait() { + if (!spawned()) + return 0; + + ReadAndInterpretStatusByte(); + + int status_value; + GTEST_DEATH_TEST_CHECK_SYSCALL_(waitpid(child_pid_, &status_value, 0)); + set_status(status_value); + return status_value; +} + +// A concrete death test class that forks, then immediately runs the test +// in the child process. +class NoExecDeathTest : public ForkingDeathTest { + public: + NoExecDeathTest(const char* a_statement, const RE* a_regex) : + ForkingDeathTest(a_statement, a_regex) { } + virtual TestRole AssumeRole(); +}; + +// The AssumeRole process for a fork-and-run death test. It implements a +// straightforward fork, with a simple pipe to transmit the status byte. +DeathTest::TestRole NoExecDeathTest::AssumeRole() { + const size_t thread_count = GetThreadCount(); + if (thread_count != 1) { + GTEST_LOG_(WARNING) << DeathTestThreadWarning(thread_count); + } + + int pipe_fd[2]; + GTEST_DEATH_TEST_CHECK_(pipe(pipe_fd) != -1); + + DeathTest::set_last_death_test_message(""); + CaptureStderr(); + // When we fork the process below, the log file buffers are copied, but the + // file descriptors are shared. We flush all log files here so that closing + // the file descriptors in the child process doesn't throw off the + // synchronization between descriptors and buffers in the parent process. + // This is as close to the fork as possible to avoid a race condition in case + // there are multiple threads running before the death test, and another + // thread writes to the log file. + FlushInfoLog(); + + const pid_t child_pid = fork(); + GTEST_DEATH_TEST_CHECK_(child_pid != -1); + set_child_pid(child_pid); + if (child_pid == 0) { + GTEST_DEATH_TEST_CHECK_SYSCALL_(close(pipe_fd[0])); + set_write_fd(pipe_fd[1]); + // Redirects all logging to stderr in the child process to prevent + // concurrent writes to the log files. We capture stderr in the parent + // process and append the child process' output to a log. + LogToStderr(); + // Event forwarding to the listeners of event listener API mush be shut + // down in death test subprocesses. + GetUnitTestImpl()->listeners()->SuppressEventForwarding(); + g_in_fast_death_test_child = true; + return EXECUTE_TEST; + } else { + GTEST_DEATH_TEST_CHECK_SYSCALL_(close(pipe_fd[1])); + set_read_fd(pipe_fd[0]); + set_spawned(true); + return OVERSEE_TEST; + } +} + +// A concrete death test class that forks and re-executes the main +// program from the beginning, with command-line flags set that cause +// only this specific death test to be run. +class ExecDeathTest : public ForkingDeathTest { + public: + ExecDeathTest(const char* a_statement, const RE* a_regex, + const char* file, int line) : + ForkingDeathTest(a_statement, a_regex), file_(file), line_(line) { } + virtual TestRole AssumeRole(); + private: + static ::std::vector + GetArgvsForDeathTestChildProcess() { + ::std::vector args = GetInjectableArgvs(); + return args; + } + // The name of the file in which the death test is located. + const char* const file_; + // The line number on which the death test is located. + const int line_; +}; + +// Utility class for accumulating command-line arguments. +class Arguments { + public: + Arguments() { + args_.push_back(NULL); + } + + ~Arguments() { + for (std::vector::iterator i = args_.begin(); i != args_.end(); + ++i) { + free(*i); + } + } + void AddArgument(const char* argument) { + args_.insert(args_.end() - 1, posix::StrDup(argument)); + } + + template + void AddArguments(const ::std::vector& arguments) { + for (typename ::std::vector::const_iterator i = arguments.begin(); + i != arguments.end(); + ++i) { + args_.insert(args_.end() - 1, posix::StrDup(i->c_str())); + } + } + char* const* Argv() { + return &args_[0]; + } + + private: + std::vector args_; +}; + +// A struct that encompasses the arguments to the child process of a +// threadsafe-style death test process. +struct ExecDeathTestArgs { + char* const* argv; // Command-line arguments for the child's call to exec + int close_fd; // File descriptor to close; the read end of a pipe +}; + +# if GTEST_OS_MAC +inline char** GetEnviron() { + // When Google Test is built as a framework on MacOS X, the environ variable + // is unavailable. Apple's documentation (man environ) recommends using + // _NSGetEnviron() instead. + return *_NSGetEnviron(); +} +# else +// Some POSIX platforms expect you to declare environ. extern "C" makes +// it reside in the global namespace. +extern "C" char** environ; +inline char** GetEnviron() { return environ; } +# endif // GTEST_OS_MAC + +# if !GTEST_OS_QNX +// The main function for a threadsafe-style death test child process. +// This function is called in a clone()-ed process and thus must avoid +// any potentially unsafe operations like malloc or libc functions. +static int ExecDeathTestChildMain(void* child_arg) { + ExecDeathTestArgs* const args = static_cast(child_arg); + GTEST_DEATH_TEST_CHECK_SYSCALL_(close(args->close_fd)); + + // We need to execute the test program in the same environment where + // it was originally invoked. Therefore we change to the original + // working directory first. + const char* const original_dir = + UnitTest::GetInstance()->original_working_dir(); + // We can safely call chdir() as it's a direct system call. + if (chdir(original_dir) != 0) { + DeathTestAbort(std::string("chdir(\"") + original_dir + "\") failed: " + + GetLastErrnoDescription()); + return EXIT_FAILURE; + } + + // We can safely call execve() as it's a direct system call. We + // cannot use execvp() as it's a libc function and thus potentially + // unsafe. Since execve() doesn't search the PATH, the user must + // invoke the test program via a valid path that contains at least + // one path separator. + execve(args->argv[0], args->argv, GetEnviron()); + DeathTestAbort(std::string("execve(") + args->argv[0] + ", ...) in " + + original_dir + " failed: " + + GetLastErrnoDescription()); + return EXIT_FAILURE; +} +# endif // !GTEST_OS_QNX + +// Two utility routines that together determine the direction the stack +// grows. +// This could be accomplished more elegantly by a single recursive +// function, but we want to guard against the unlikely possibility of +// a smart compiler optimizing the recursion away. +// +// GTEST_NO_INLINE_ is required to prevent GCC 4.6 from inlining +// StackLowerThanAddress into StackGrowsDown, which then doesn't give +// correct answer. +void StackLowerThanAddress(const void* ptr, bool* result) GTEST_NO_INLINE_; +void StackLowerThanAddress(const void* ptr, bool* result) { + int dummy; + *result = (&dummy < ptr); +} + +bool StackGrowsDown() { + int dummy; + bool result; + StackLowerThanAddress(&dummy, &result); + return result; +} + +// Spawns a child process with the same executable as the current process in +// a thread-safe manner and instructs it to run the death test. The +// implementation uses fork(2) + exec. On systems where clone(2) is +// available, it is used instead, being slightly more thread-safe. On QNX, +// fork supports only single-threaded environments, so this function uses +// spawn(2) there instead. The function dies with an error message if +// anything goes wrong. +static pid_t ExecDeathTestSpawnChild(char* const* argv, int close_fd) { + ExecDeathTestArgs args = { argv, close_fd }; + pid_t child_pid = -1; + +# if GTEST_OS_QNX + // Obtains the current directory and sets it to be closed in the child + // process. + const int cwd_fd = open(".", O_RDONLY); + GTEST_DEATH_TEST_CHECK_(cwd_fd != -1); + GTEST_DEATH_TEST_CHECK_SYSCALL_(fcntl(cwd_fd, F_SETFD, FD_CLOEXEC)); + // We need to execute the test program in the same environment where + // it was originally invoked. Therefore we change to the original + // working directory first. + const char* const original_dir = + UnitTest::GetInstance()->original_working_dir(); + // We can safely call chdir() as it's a direct system call. + if (chdir(original_dir) != 0) { + DeathTestAbort(std::string("chdir(\"") + original_dir + "\") failed: " + + GetLastErrnoDescription()); + return EXIT_FAILURE; + } + + int fd_flags; + // Set close_fd to be closed after spawn. + GTEST_DEATH_TEST_CHECK_SYSCALL_(fd_flags = fcntl(close_fd, F_GETFD)); + GTEST_DEATH_TEST_CHECK_SYSCALL_(fcntl(close_fd, F_SETFD, + fd_flags | FD_CLOEXEC)); + struct inheritance inherit = {0}; + // spawn is a system call. + child_pid = spawn(args.argv[0], 0, NULL, &inherit, args.argv, GetEnviron()); + // Restores the current working directory. + GTEST_DEATH_TEST_CHECK_(fchdir(cwd_fd) != -1); + GTEST_DEATH_TEST_CHECK_SYSCALL_(close(cwd_fd)); + +# else // GTEST_OS_QNX +# if GTEST_OS_LINUX + // When a SIGPROF signal is received while fork() or clone() are executing, + // the process may hang. To avoid this, we ignore SIGPROF here and re-enable + // it after the call to fork()/clone() is complete. + struct sigaction saved_sigprof_action; + struct sigaction ignore_sigprof_action; + memset(&ignore_sigprof_action, 0, sizeof(ignore_sigprof_action)); + sigemptyset(&ignore_sigprof_action.sa_mask); + ignore_sigprof_action.sa_handler = SIG_IGN; + GTEST_DEATH_TEST_CHECK_SYSCALL_(sigaction( + SIGPROF, &ignore_sigprof_action, &saved_sigprof_action)); +# endif // GTEST_OS_LINUX + +# if GTEST_HAS_CLONE + const bool use_fork = GTEST_FLAG(death_test_use_fork); + + if (!use_fork) { + static const bool stack_grows_down = StackGrowsDown(); + const size_t stack_size = getpagesize(); + // MMAP_ANONYMOUS is not defined on Mac, so we use MAP_ANON instead. + void* const stack = mmap(NULL, stack_size, PROT_READ | PROT_WRITE, + MAP_ANON | MAP_PRIVATE, -1, 0); + GTEST_DEATH_TEST_CHECK_(stack != MAP_FAILED); + + // Maximum stack alignment in bytes: For a downward-growing stack, this + // amount is subtracted from size of the stack space to get an address + // that is within the stack space and is aligned on all systems we care + // about. As far as I know there is no ABI with stack alignment greater + // than 64. We assume stack and stack_size already have alignment of + // kMaxStackAlignment. + const size_t kMaxStackAlignment = 64; + void* const stack_top = + static_cast(stack) + + (stack_grows_down ? stack_size - kMaxStackAlignment : 0); + GTEST_DEATH_TEST_CHECK_(stack_size > kMaxStackAlignment && + reinterpret_cast(stack_top) % kMaxStackAlignment == 0); + + child_pid = clone(&ExecDeathTestChildMain, stack_top, SIGCHLD, &args); + + GTEST_DEATH_TEST_CHECK_(munmap(stack, stack_size) != -1); + } +# else + const bool use_fork = true; +# endif // GTEST_HAS_CLONE + + if (use_fork && (child_pid = fork()) == 0) { + ExecDeathTestChildMain(&args); + _exit(0); + } +# endif // GTEST_OS_QNX +# if GTEST_OS_LINUX + GTEST_DEATH_TEST_CHECK_SYSCALL_( + sigaction(SIGPROF, &saved_sigprof_action, NULL)); +# endif // GTEST_OS_LINUX + + GTEST_DEATH_TEST_CHECK_(child_pid != -1); + return child_pid; +} + +// The AssumeRole process for a fork-and-exec death test. It re-executes the +// main program from the beginning, setting the --gtest_filter +// and --gtest_internal_run_death_test flags to cause only the current +// death test to be re-run. +DeathTest::TestRole ExecDeathTest::AssumeRole() { + const UnitTestImpl* const impl = GetUnitTestImpl(); + const InternalRunDeathTestFlag* const flag = + impl->internal_run_death_test_flag(); + const TestInfo* const info = impl->current_test_info(); + const int death_test_index = info->result()->death_test_count(); + + if (flag != NULL) { + set_write_fd(flag->write_fd()); + return EXECUTE_TEST; + } + + int pipe_fd[2]; + GTEST_DEATH_TEST_CHECK_(pipe(pipe_fd) != -1); + // Clear the close-on-exec flag on the write end of the pipe, lest + // it be closed when the child process does an exec: + GTEST_DEATH_TEST_CHECK_(fcntl(pipe_fd[1], F_SETFD, 0) != -1); + + const std::string filter_flag = + std::string("--") + GTEST_FLAG_PREFIX_ + kFilterFlag + "=" + + info->test_case_name() + "." + info->name(); + const std::string internal_flag = + std::string("--") + GTEST_FLAG_PREFIX_ + kInternalRunDeathTestFlag + "=" + + file_ + "|" + StreamableToString(line_) + "|" + + StreamableToString(death_test_index) + "|" + + StreamableToString(pipe_fd[1]); + Arguments args; + args.AddArguments(GetArgvsForDeathTestChildProcess()); + args.AddArgument(filter_flag.c_str()); + args.AddArgument(internal_flag.c_str()); + + DeathTest::set_last_death_test_message(""); + + CaptureStderr(); + // See the comment in NoExecDeathTest::AssumeRole for why the next line + // is necessary. + FlushInfoLog(); + + const pid_t child_pid = ExecDeathTestSpawnChild(args.Argv(), pipe_fd[0]); + GTEST_DEATH_TEST_CHECK_SYSCALL_(close(pipe_fd[1])); + set_child_pid(child_pid); + set_read_fd(pipe_fd[0]); + set_spawned(true); + return OVERSEE_TEST; +} + +# endif // !GTEST_OS_WINDOWS + +// Creates a concrete DeathTest-derived class that depends on the +// --gtest_death_test_style flag, and sets the pointer pointed to +// by the "test" argument to its address. If the test should be +// skipped, sets that pointer to NULL. Returns true, unless the +// flag is set to an invalid value. +bool DefaultDeathTestFactory::Create(const char* statement, const RE* regex, + const char* file, int line, + DeathTest** test) { + UnitTestImpl* const impl = GetUnitTestImpl(); + const InternalRunDeathTestFlag* const flag = + impl->internal_run_death_test_flag(); + const int death_test_index = impl->current_test_info() + ->increment_death_test_count(); + + if (flag != NULL) { + if (death_test_index > flag->index()) { + DeathTest::set_last_death_test_message( + "Death test count (" + StreamableToString(death_test_index) + + ") somehow exceeded expected maximum (" + + StreamableToString(flag->index()) + ")"); + return false; + } + + if (!(flag->file() == file && flag->line() == line && + flag->index() == death_test_index)) { + *test = NULL; + return true; + } + } + +# if GTEST_OS_WINDOWS + + if (GTEST_FLAG(death_test_style) == "threadsafe" || + GTEST_FLAG(death_test_style) == "fast") { + *test = new WindowsDeathTest(statement, regex, file, line); + } + +# else + + if (GTEST_FLAG(death_test_style) == "threadsafe") { + *test = new ExecDeathTest(statement, regex, file, line); + } else if (GTEST_FLAG(death_test_style) == "fast") { + *test = new NoExecDeathTest(statement, regex); + } + +# endif // GTEST_OS_WINDOWS + + else { // NOLINT - this is more readable than unbalanced brackets inside #if. + DeathTest::set_last_death_test_message( + "Unknown death test style \"" + GTEST_FLAG(death_test_style) + + "\" encountered"); + return false; + } + + return true; +} + +// Splits a given string on a given delimiter, populating a given +// vector with the fields. GTEST_HAS_DEATH_TEST implies that we have +// ::std::string, so we can use it here. +static void SplitString(const ::std::string& str, char delimiter, + ::std::vector< ::std::string>* dest) { + ::std::vector< ::std::string> parsed; + ::std::string::size_type pos = 0; + while (::testing::internal::AlwaysTrue()) { + const ::std::string::size_type colon = str.find(delimiter, pos); + if (colon == ::std::string::npos) { + parsed.push_back(str.substr(pos)); + break; + } else { + parsed.push_back(str.substr(pos, colon - pos)); + pos = colon + 1; + } + } + dest->swap(parsed); +} + +# if GTEST_OS_WINDOWS +// Recreates the pipe and event handles from the provided parameters, +// signals the event, and returns a file descriptor wrapped around the pipe +// handle. This function is called in the child process only. +int GetStatusFileDescriptor(unsigned int parent_process_id, + size_t write_handle_as_size_t, + size_t event_handle_as_size_t) { + AutoHandle parent_process_handle(::OpenProcess(PROCESS_DUP_HANDLE, + FALSE, // Non-inheritable. + parent_process_id)); + if (parent_process_handle.Get() == INVALID_HANDLE_VALUE) { + DeathTestAbort("Unable to open parent process " + + StreamableToString(parent_process_id)); + } + + // TODO(vladl@google.com): Replace the following check with a + // compile-time assertion when available. + GTEST_CHECK_(sizeof(HANDLE) <= sizeof(size_t)); + + const HANDLE write_handle = + reinterpret_cast(write_handle_as_size_t); + HANDLE dup_write_handle; + + // The newly initialized handle is accessible only in in the parent + // process. To obtain one accessible within the child, we need to use + // DuplicateHandle. + if (!::DuplicateHandle(parent_process_handle.Get(), write_handle, + ::GetCurrentProcess(), &dup_write_handle, + 0x0, // Requested privileges ignored since + // DUPLICATE_SAME_ACCESS is used. + FALSE, // Request non-inheritable handler. + DUPLICATE_SAME_ACCESS)) { + DeathTestAbort("Unable to duplicate the pipe handle " + + StreamableToString(write_handle_as_size_t) + + " from the parent process " + + StreamableToString(parent_process_id)); + } + + const HANDLE event_handle = reinterpret_cast(event_handle_as_size_t); + HANDLE dup_event_handle; + + if (!::DuplicateHandle(parent_process_handle.Get(), event_handle, + ::GetCurrentProcess(), &dup_event_handle, + 0x0, + FALSE, + DUPLICATE_SAME_ACCESS)) { + DeathTestAbort("Unable to duplicate the event handle " + + StreamableToString(event_handle_as_size_t) + + " from the parent process " + + StreamableToString(parent_process_id)); + } + + const int write_fd = + ::_open_osfhandle(reinterpret_cast(dup_write_handle), O_APPEND); + if (write_fd == -1) { + DeathTestAbort("Unable to convert pipe handle " + + StreamableToString(write_handle_as_size_t) + + " to a file descriptor"); + } + + // Signals the parent that the write end of the pipe has been acquired + // so the parent can release its own write end. + ::SetEvent(dup_event_handle); + + return write_fd; +} +# endif // GTEST_OS_WINDOWS + +// Returns a newly created InternalRunDeathTestFlag object with fields +// initialized from the GTEST_FLAG(internal_run_death_test) flag if +// the flag is specified; otherwise returns NULL. +InternalRunDeathTestFlag* ParseInternalRunDeathTestFlag() { + if (GTEST_FLAG(internal_run_death_test) == "") return NULL; + + // GTEST_HAS_DEATH_TEST implies that we have ::std::string, so we + // can use it here. + int line = -1; + int index = -1; + ::std::vector< ::std::string> fields; + SplitString(GTEST_FLAG(internal_run_death_test).c_str(), '|', &fields); + int write_fd = -1; + +# if GTEST_OS_WINDOWS + + unsigned int parent_process_id = 0; + size_t write_handle_as_size_t = 0; + size_t event_handle_as_size_t = 0; + + if (fields.size() != 6 + || !ParseNaturalNumber(fields[1], &line) + || !ParseNaturalNumber(fields[2], &index) + || !ParseNaturalNumber(fields[3], &parent_process_id) + || !ParseNaturalNumber(fields[4], &write_handle_as_size_t) + || !ParseNaturalNumber(fields[5], &event_handle_as_size_t)) { + DeathTestAbort("Bad --gtest_internal_run_death_test flag: " + + GTEST_FLAG(internal_run_death_test)); + } + write_fd = GetStatusFileDescriptor(parent_process_id, + write_handle_as_size_t, + event_handle_as_size_t); +# else + + if (fields.size() != 4 + || !ParseNaturalNumber(fields[1], &line) + || !ParseNaturalNumber(fields[2], &index) + || !ParseNaturalNumber(fields[3], &write_fd)) { + DeathTestAbort("Bad --gtest_internal_run_death_test flag: " + + GTEST_FLAG(internal_run_death_test)); + } + +# endif // GTEST_OS_WINDOWS + + return new InternalRunDeathTestFlag(fields[0], line, index, write_fd); +} + +} // namespace internal + +#endif // GTEST_HAS_DEATH_TEST + +} // namespace testing diff --git a/vendor/gmock-1.7.0/gtest/src/gtest-filepath.cc b/vendor/gmock-1.7.0/gtest/src/gtest-filepath.cc new file mode 100644 index 0000000000..6be58b6fca --- /dev/null +++ b/vendor/gmock-1.7.0/gtest/src/gtest-filepath.cc @@ -0,0 +1,382 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Authors: keith.ray@gmail.com (Keith Ray) + +#include "gtest/gtest-message.h" +#include "gtest/internal/gtest-filepath.h" +#include "gtest/internal/gtest-port.h" + +#include + +#if GTEST_OS_WINDOWS_MOBILE +# include +#elif GTEST_OS_WINDOWS +# include +# include +#elif GTEST_OS_SYMBIAN +// Symbian OpenC has PATH_MAX in sys/syslimits.h +# include +#else +# include +# include // Some Linux distributions define PATH_MAX here. +#endif // GTEST_OS_WINDOWS_MOBILE + +#if GTEST_OS_WINDOWS +# define GTEST_PATH_MAX_ _MAX_PATH +#elif defined(PATH_MAX) +# define GTEST_PATH_MAX_ PATH_MAX +#elif defined(_XOPEN_PATH_MAX) +# define GTEST_PATH_MAX_ _XOPEN_PATH_MAX +#else +# define GTEST_PATH_MAX_ _POSIX_PATH_MAX +#endif // GTEST_OS_WINDOWS + +#include "gtest/internal/gtest-string.h" + +namespace testing { +namespace internal { + +#if GTEST_OS_WINDOWS +// On Windows, '\\' is the standard path separator, but many tools and the +// Windows API also accept '/' as an alternate path separator. Unless otherwise +// noted, a file path can contain either kind of path separators, or a mixture +// of them. +const char kPathSeparator = '\\'; +const char kAlternatePathSeparator = '/'; +const char kPathSeparatorString[] = "\\"; +const char kAlternatePathSeparatorString[] = "/"; +# if GTEST_OS_WINDOWS_MOBILE +// Windows CE doesn't have a current directory. You should not use +// the current directory in tests on Windows CE, but this at least +// provides a reasonable fallback. +const char kCurrentDirectoryString[] = "\\"; +// Windows CE doesn't define INVALID_FILE_ATTRIBUTES +const DWORD kInvalidFileAttributes = 0xffffffff; +# else +const char kCurrentDirectoryString[] = ".\\"; +# endif // GTEST_OS_WINDOWS_MOBILE +#else +const char kPathSeparator = '/'; +const char kPathSeparatorString[] = "/"; +const char kCurrentDirectoryString[] = "./"; +#endif // GTEST_OS_WINDOWS + +// Returns whether the given character is a valid path separator. +static bool IsPathSeparator(char c) { +#if GTEST_HAS_ALT_PATH_SEP_ + return (c == kPathSeparator) || (c == kAlternatePathSeparator); +#else + return c == kPathSeparator; +#endif +} + +// Returns the current working directory, or "" if unsuccessful. +FilePath FilePath::GetCurrentDir() { +#if GTEST_OS_WINDOWS_MOBILE + // Windows CE doesn't have a current directory, so we just return + // something reasonable. + return FilePath(kCurrentDirectoryString); +#elif GTEST_OS_WINDOWS + char cwd[GTEST_PATH_MAX_ + 1] = { '\0' }; + return FilePath(_getcwd(cwd, sizeof(cwd)) == NULL ? "" : cwd); +#else + char cwd[GTEST_PATH_MAX_ + 1] = { '\0' }; + return FilePath(getcwd(cwd, sizeof(cwd)) == NULL ? "" : cwd); +#endif // GTEST_OS_WINDOWS_MOBILE +} + +// Returns a copy of the FilePath with the case-insensitive extension removed. +// Example: FilePath("dir/file.exe").RemoveExtension("EXE") returns +// FilePath("dir/file"). If a case-insensitive extension is not +// found, returns a copy of the original FilePath. +FilePath FilePath::RemoveExtension(const char* extension) const { + const std::string dot_extension = std::string(".") + extension; + if (String::EndsWithCaseInsensitive(pathname_, dot_extension)) { + return FilePath(pathname_.substr( + 0, pathname_.length() - dot_extension.length())); + } + return *this; +} + +// Returns a pointer to the last occurence of a valid path separator in +// the FilePath. On Windows, for example, both '/' and '\' are valid path +// separators. Returns NULL if no path separator was found. +const char* FilePath::FindLastPathSeparator() const { + const char* const last_sep = strrchr(c_str(), kPathSeparator); +#if GTEST_HAS_ALT_PATH_SEP_ + const char* const last_alt_sep = strrchr(c_str(), kAlternatePathSeparator); + // Comparing two pointers of which only one is NULL is undefined. + if (last_alt_sep != NULL && + (last_sep == NULL || last_alt_sep > last_sep)) { + return last_alt_sep; + } +#endif + return last_sep; +} + +// Returns a copy of the FilePath with the directory part removed. +// Example: FilePath("path/to/file").RemoveDirectoryName() returns +// FilePath("file"). If there is no directory part ("just_a_file"), it returns +// the FilePath unmodified. If there is no file part ("just_a_dir/") it +// returns an empty FilePath (""). +// On Windows platform, '\' is the path separator, otherwise it is '/'. +FilePath FilePath::RemoveDirectoryName() const { + const char* const last_sep = FindLastPathSeparator(); + return last_sep ? FilePath(last_sep + 1) : *this; +} + +// RemoveFileName returns the directory path with the filename removed. +// Example: FilePath("path/to/file").RemoveFileName() returns "path/to/". +// If the FilePath is "a_file" or "/a_file", RemoveFileName returns +// FilePath("./") or, on Windows, FilePath(".\\"). If the filepath does +// not have a file, like "just/a/dir/", it returns the FilePath unmodified. +// On Windows platform, '\' is the path separator, otherwise it is '/'. +FilePath FilePath::RemoveFileName() const { + const char* const last_sep = FindLastPathSeparator(); + std::string dir; + if (last_sep) { + dir = std::string(c_str(), last_sep + 1 - c_str()); + } else { + dir = kCurrentDirectoryString; + } + return FilePath(dir); +} + +// Helper functions for naming files in a directory for xml output. + +// Given directory = "dir", base_name = "test", number = 0, +// extension = "xml", returns "dir/test.xml". If number is greater +// than zero (e.g., 12), returns "dir/test_12.xml". +// On Windows platform, uses \ as the separator rather than /. +FilePath FilePath::MakeFileName(const FilePath& directory, + const FilePath& base_name, + int number, + const char* extension) { + std::string file; + if (number == 0) { + file = base_name.string() + "." + extension; + } else { + file = base_name.string() + "_" + StreamableToString(number) + + "." + extension; + } + return ConcatPaths(directory, FilePath(file)); +} + +// Given directory = "dir", relative_path = "test.xml", returns "dir/test.xml". +// On Windows, uses \ as the separator rather than /. +FilePath FilePath::ConcatPaths(const FilePath& directory, + const FilePath& relative_path) { + if (directory.IsEmpty()) + return relative_path; + const FilePath dir(directory.RemoveTrailingPathSeparator()); + return FilePath(dir.string() + kPathSeparator + relative_path.string()); +} + +// Returns true if pathname describes something findable in the file-system, +// either a file, directory, or whatever. +bool FilePath::FileOrDirectoryExists() const { +#if GTEST_OS_WINDOWS_MOBILE + LPCWSTR unicode = String::AnsiToUtf16(pathname_.c_str()); + const DWORD attributes = GetFileAttributes(unicode); + delete [] unicode; + return attributes != kInvalidFileAttributes; +#else + posix::StatStruct file_stat; + return posix::Stat(pathname_.c_str(), &file_stat) == 0; +#endif // GTEST_OS_WINDOWS_MOBILE +} + +// Returns true if pathname describes a directory in the file-system +// that exists. +bool FilePath::DirectoryExists() const { + bool result = false; +#if GTEST_OS_WINDOWS + // Don't strip off trailing separator if path is a root directory on + // Windows (like "C:\\"). + const FilePath& path(IsRootDirectory() ? *this : + RemoveTrailingPathSeparator()); +#else + const FilePath& path(*this); +#endif + +#if GTEST_OS_WINDOWS_MOBILE + LPCWSTR unicode = String::AnsiToUtf16(path.c_str()); + const DWORD attributes = GetFileAttributes(unicode); + delete [] unicode; + if ((attributes != kInvalidFileAttributes) && + (attributes & FILE_ATTRIBUTE_DIRECTORY)) { + result = true; + } +#else + posix::StatStruct file_stat; + result = posix::Stat(path.c_str(), &file_stat) == 0 && + posix::IsDir(file_stat); +#endif // GTEST_OS_WINDOWS_MOBILE + + return result; +} + +// Returns true if pathname describes a root directory. (Windows has one +// root directory per disk drive.) +bool FilePath::IsRootDirectory() const { +#if GTEST_OS_WINDOWS + // TODO(wan@google.com): on Windows a network share like + // \\server\share can be a root directory, although it cannot be the + // current directory. Handle this properly. + return pathname_.length() == 3 && IsAbsolutePath(); +#else + return pathname_.length() == 1 && IsPathSeparator(pathname_.c_str()[0]); +#endif +} + +// Returns true if pathname describes an absolute path. +bool FilePath::IsAbsolutePath() const { + const char* const name = pathname_.c_str(); +#if GTEST_OS_WINDOWS + return pathname_.length() >= 3 && + ((name[0] >= 'a' && name[0] <= 'z') || + (name[0] >= 'A' && name[0] <= 'Z')) && + name[1] == ':' && + IsPathSeparator(name[2]); +#else + return IsPathSeparator(name[0]); +#endif +} + +// Returns a pathname for a file that does not currently exist. The pathname +// will be directory/base_name.extension or +// directory/base_name_.extension if directory/base_name.extension +// already exists. The number will be incremented until a pathname is found +// that does not already exist. +// Examples: 'dir/foo_test.xml' or 'dir/foo_test_1.xml'. +// There could be a race condition if two or more processes are calling this +// function at the same time -- they could both pick the same filename. +FilePath FilePath::GenerateUniqueFileName(const FilePath& directory, + const FilePath& base_name, + const char* extension) { + FilePath full_pathname; + int number = 0; + do { + full_pathname.Set(MakeFileName(directory, base_name, number++, extension)); + } while (full_pathname.FileOrDirectoryExists()); + return full_pathname; +} + +// Returns true if FilePath ends with a path separator, which indicates that +// it is intended to represent a directory. Returns false otherwise. +// This does NOT check that a directory (or file) actually exists. +bool FilePath::IsDirectory() const { + return !pathname_.empty() && + IsPathSeparator(pathname_.c_str()[pathname_.length() - 1]); +} + +// Create directories so that path exists. Returns true if successful or if +// the directories already exist; returns false if unable to create directories +// for any reason. +bool FilePath::CreateDirectoriesRecursively() const { + if (!this->IsDirectory()) { + return false; + } + + if (pathname_.length() == 0 || this->DirectoryExists()) { + return true; + } + + const FilePath parent(this->RemoveTrailingPathSeparator().RemoveFileName()); + return parent.CreateDirectoriesRecursively() && this->CreateFolder(); +} + +// Create the directory so that path exists. Returns true if successful or +// if the directory already exists; returns false if unable to create the +// directory for any reason, including if the parent directory does not +// exist. Not named "CreateDirectory" because that's a macro on Windows. +bool FilePath::CreateFolder() const { +#if GTEST_OS_WINDOWS_MOBILE + FilePath removed_sep(this->RemoveTrailingPathSeparator()); + LPCWSTR unicode = String::AnsiToUtf16(removed_sep.c_str()); + int result = CreateDirectory(unicode, NULL) ? 0 : -1; + delete [] unicode; +#elif GTEST_OS_WINDOWS + int result = _mkdir(pathname_.c_str()); +#else + int result = mkdir(pathname_.c_str(), 0777); +#endif // GTEST_OS_WINDOWS_MOBILE + + if (result == -1) { + return this->DirectoryExists(); // An error is OK if the directory exists. + } + return true; // No error. +} + +// If input name has a trailing separator character, remove it and return the +// name, otherwise return the name string unmodified. +// On Windows platform, uses \ as the separator, other platforms use /. +FilePath FilePath::RemoveTrailingPathSeparator() const { + return IsDirectory() + ? FilePath(pathname_.substr(0, pathname_.length() - 1)) + : *this; +} + +// Removes any redundant separators that might be in the pathname. +// For example, "bar///foo" becomes "bar/foo". Does not eliminate other +// redundancies that might be in a pathname involving "." or "..". +// TODO(wan@google.com): handle Windows network shares (e.g. \\server\share). +void FilePath::Normalize() { + if (pathname_.c_str() == NULL) { + pathname_ = ""; + return; + } + const char* src = pathname_.c_str(); + char* const dest = new char[pathname_.length() + 1]; + char* dest_ptr = dest; + memset(dest_ptr, 0, pathname_.length() + 1); + + while (*src != '\0') { + *dest_ptr = *src; + if (!IsPathSeparator(*src)) { + src++; + } else { +#if GTEST_HAS_ALT_PATH_SEP_ + if (*dest_ptr == kAlternatePathSeparator) { + *dest_ptr = kPathSeparator; + } +#endif + while (IsPathSeparator(*src)) + src++; + } + dest_ptr++; + } + *dest_ptr = '\0'; + pathname_ = dest; + delete[] dest; +} + +} // namespace internal +} // namespace testing diff --git a/vendor/gmock-1.7.0/gtest/src/gtest-internal-inl.h b/vendor/gmock-1.7.0/gtest/src/gtest-internal-inl.h new file mode 100644 index 0000000000..35df303cca --- /dev/null +++ b/vendor/gmock-1.7.0/gtest/src/gtest-internal-inl.h @@ -0,0 +1,1218 @@ +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Utility functions and classes used by the Google C++ testing framework. +// +// Author: wan@google.com (Zhanyong Wan) +// +// This file contains purely Google Test's internal implementation. Please +// DO NOT #INCLUDE IT IN A USER PROGRAM. + +#ifndef GTEST_SRC_GTEST_INTERNAL_INL_H_ +#define GTEST_SRC_GTEST_INTERNAL_INL_H_ + +// GTEST_IMPLEMENTATION_ is defined to 1 iff the current translation unit is +// part of Google Test's implementation; otherwise it's undefined. +#if !GTEST_IMPLEMENTATION_ +// A user is trying to include this from his code - just say no. +# error "gtest-internal-inl.h is part of Google Test's internal implementation." +# error "It must not be included except by Google Test itself." +#endif // GTEST_IMPLEMENTATION_ + +#ifndef _WIN32_WCE +# include +#endif // !_WIN32_WCE +#include +#include // For strtoll/_strtoul64/malloc/free. +#include // For memmove. + +#include +#include +#include + +#include "gtest/internal/gtest-port.h" + +#if GTEST_CAN_STREAM_RESULTS_ +# include // NOLINT +# include // NOLINT +#endif + +#if GTEST_OS_WINDOWS +# include // NOLINT +#endif // GTEST_OS_WINDOWS + +#include "gtest/gtest.h" // NOLINT +#include "gtest/gtest-spi.h" + +namespace testing { + +// Declares the flags. +// +// We don't want the users to modify this flag in the code, but want +// Google Test's own unit tests to be able to access it. Therefore we +// declare it here as opposed to in gtest.h. +GTEST_DECLARE_bool_(death_test_use_fork); + +namespace internal { + +// The value of GetTestTypeId() as seen from within the Google Test +// library. This is solely for testing GetTestTypeId(). +GTEST_API_ extern const TypeId kTestTypeIdInGoogleTest; + +// Names of the flags (needed for parsing Google Test flags). +const char kAlsoRunDisabledTestsFlag[] = "also_run_disabled_tests"; +const char kBreakOnFailureFlag[] = "break_on_failure"; +const char kCatchExceptionsFlag[] = "catch_exceptions"; +const char kColorFlag[] = "color"; +const char kFilterFlag[] = "filter"; +const char kListTestsFlag[] = "list_tests"; +const char kOutputFlag[] = "output"; +const char kPrintTimeFlag[] = "print_time"; +const char kRandomSeedFlag[] = "random_seed"; +const char kRepeatFlag[] = "repeat"; +const char kShuffleFlag[] = "shuffle"; +const char kStackTraceDepthFlag[] = "stack_trace_depth"; +const char kStreamResultToFlag[] = "stream_result_to"; +const char kThrowOnFailureFlag[] = "throw_on_failure"; + +// A valid random seed must be in [1, kMaxRandomSeed]. +const int kMaxRandomSeed = 99999; + +// g_help_flag is true iff the --help flag or an equivalent form is +// specified on the command line. +GTEST_API_ extern bool g_help_flag; + +// Returns the current time in milliseconds. +GTEST_API_ TimeInMillis GetTimeInMillis(); + +// Returns true iff Google Test should use colors in the output. +GTEST_API_ bool ShouldUseColor(bool stdout_is_tty); + +// Formats the given time in milliseconds as seconds. +GTEST_API_ std::string FormatTimeInMillisAsSeconds(TimeInMillis ms); + +// Converts the given time in milliseconds to a date string in the ISO 8601 +// format, without the timezone information. N.B.: due to the use the +// non-reentrant localtime() function, this function is not thread safe. Do +// not use it in any code that can be called from multiple threads. +GTEST_API_ std::string FormatEpochTimeInMillisAsIso8601(TimeInMillis ms); + +// Parses a string for an Int32 flag, in the form of "--flag=value". +// +// On success, stores the value of the flag in *value, and returns +// true. On failure, returns false without changing *value. +GTEST_API_ bool ParseInt32Flag( + const char* str, const char* flag, Int32* value); + +// Returns a random seed in range [1, kMaxRandomSeed] based on the +// given --gtest_random_seed flag value. +inline int GetRandomSeedFromFlag(Int32 random_seed_flag) { + const unsigned int raw_seed = (random_seed_flag == 0) ? + static_cast(GetTimeInMillis()) : + static_cast(random_seed_flag); + + // Normalizes the actual seed to range [1, kMaxRandomSeed] such that + // it's easy to type. + const int normalized_seed = + static_cast((raw_seed - 1U) % + static_cast(kMaxRandomSeed)) + 1; + return normalized_seed; +} + +// Returns the first valid random seed after 'seed'. The behavior is +// undefined if 'seed' is invalid. The seed after kMaxRandomSeed is +// considered to be 1. +inline int GetNextRandomSeed(int seed) { + GTEST_CHECK_(1 <= seed && seed <= kMaxRandomSeed) + << "Invalid random seed " << seed << " - must be in [1, " + << kMaxRandomSeed << "]."; + const int next_seed = seed + 1; + return (next_seed > kMaxRandomSeed) ? 1 : next_seed; +} + +// This class saves the values of all Google Test flags in its c'tor, and +// restores them in its d'tor. +class GTestFlagSaver { + public: + // The c'tor. + GTestFlagSaver() { + also_run_disabled_tests_ = GTEST_FLAG(also_run_disabled_tests); + break_on_failure_ = GTEST_FLAG(break_on_failure); + catch_exceptions_ = GTEST_FLAG(catch_exceptions); + color_ = GTEST_FLAG(color); + death_test_style_ = GTEST_FLAG(death_test_style); + death_test_use_fork_ = GTEST_FLAG(death_test_use_fork); + filter_ = GTEST_FLAG(filter); + internal_run_death_test_ = GTEST_FLAG(internal_run_death_test); + list_tests_ = GTEST_FLAG(list_tests); + output_ = GTEST_FLAG(output); + print_time_ = GTEST_FLAG(print_time); + random_seed_ = GTEST_FLAG(random_seed); + repeat_ = GTEST_FLAG(repeat); + shuffle_ = GTEST_FLAG(shuffle); + stack_trace_depth_ = GTEST_FLAG(stack_trace_depth); + stream_result_to_ = GTEST_FLAG(stream_result_to); + throw_on_failure_ = GTEST_FLAG(throw_on_failure); + } + + // The d'tor is not virtual. DO NOT INHERIT FROM THIS CLASS. + ~GTestFlagSaver() { + GTEST_FLAG(also_run_disabled_tests) = also_run_disabled_tests_; + GTEST_FLAG(break_on_failure) = break_on_failure_; + GTEST_FLAG(catch_exceptions) = catch_exceptions_; + GTEST_FLAG(color) = color_; + GTEST_FLAG(death_test_style) = death_test_style_; + GTEST_FLAG(death_test_use_fork) = death_test_use_fork_; + GTEST_FLAG(filter) = filter_; + GTEST_FLAG(internal_run_death_test) = internal_run_death_test_; + GTEST_FLAG(list_tests) = list_tests_; + GTEST_FLAG(output) = output_; + GTEST_FLAG(print_time) = print_time_; + GTEST_FLAG(random_seed) = random_seed_; + GTEST_FLAG(repeat) = repeat_; + GTEST_FLAG(shuffle) = shuffle_; + GTEST_FLAG(stack_trace_depth) = stack_trace_depth_; + GTEST_FLAG(stream_result_to) = stream_result_to_; + GTEST_FLAG(throw_on_failure) = throw_on_failure_; + } + + private: + // Fields for saving the original values of flags. + bool also_run_disabled_tests_; + bool break_on_failure_; + bool catch_exceptions_; + std::string color_; + std::string death_test_style_; + bool death_test_use_fork_; + std::string filter_; + std::string internal_run_death_test_; + bool list_tests_; + std::string output_; + bool print_time_; + internal::Int32 random_seed_; + internal::Int32 repeat_; + bool shuffle_; + internal::Int32 stack_trace_depth_; + std::string stream_result_to_; + bool throw_on_failure_; +} GTEST_ATTRIBUTE_UNUSED_; + +// Converts a Unicode code point to a narrow string in UTF-8 encoding. +// code_point parameter is of type UInt32 because wchar_t may not be +// wide enough to contain a code point. +// If the code_point is not a valid Unicode code point +// (i.e. outside of Unicode range U+0 to U+10FFFF) it will be converted +// to "(Invalid Unicode 0xXXXXXXXX)". +GTEST_API_ std::string CodePointToUtf8(UInt32 code_point); + +// Converts a wide string to a narrow string in UTF-8 encoding. +// The wide string is assumed to have the following encoding: +// UTF-16 if sizeof(wchar_t) == 2 (on Windows, Cygwin, Symbian OS) +// UTF-32 if sizeof(wchar_t) == 4 (on Linux) +// Parameter str points to a null-terminated wide string. +// Parameter num_chars may additionally limit the number +// of wchar_t characters processed. -1 is used when the entire string +// should be processed. +// If the string contains code points that are not valid Unicode code points +// (i.e. outside of Unicode range U+0 to U+10FFFF) they will be output +// as '(Invalid Unicode 0xXXXXXXXX)'. If the string is in UTF16 encoding +// and contains invalid UTF-16 surrogate pairs, values in those pairs +// will be encoded as individual Unicode characters from Basic Normal Plane. +GTEST_API_ std::string WideStringToUtf8(const wchar_t* str, int num_chars); + +// Reads the GTEST_SHARD_STATUS_FILE environment variable, and creates the file +// if the variable is present. If a file already exists at this location, this +// function will write over it. If the variable is present, but the file cannot +// be created, prints an error and exits. +void WriteToShardStatusFileIfNeeded(); + +// Checks whether sharding is enabled by examining the relevant +// environment variable values. If the variables are present, +// but inconsistent (e.g., shard_index >= total_shards), prints +// an error and exits. If in_subprocess_for_death_test, sharding is +// disabled because it must only be applied to the original test +// process. Otherwise, we could filter out death tests we intended to execute. +GTEST_API_ bool ShouldShard(const char* total_shards_str, + const char* shard_index_str, + bool in_subprocess_for_death_test); + +// Parses the environment variable var as an Int32. If it is unset, +// returns default_val. If it is not an Int32, prints an error and +// and aborts. +GTEST_API_ Int32 Int32FromEnvOrDie(const char* env_var, Int32 default_val); + +// Given the total number of shards, the shard index, and the test id, +// returns true iff the test should be run on this shard. The test id is +// some arbitrary but unique non-negative integer assigned to each test +// method. Assumes that 0 <= shard_index < total_shards. +GTEST_API_ bool ShouldRunTestOnShard( + int total_shards, int shard_index, int test_id); + +// STL container utilities. + +// Returns the number of elements in the given container that satisfy +// the given predicate. +template +inline int CountIf(const Container& c, Predicate predicate) { + // Implemented as an explicit loop since std::count_if() in libCstd on + // Solaris has a non-standard signature. + int count = 0; + for (typename Container::const_iterator it = c.begin(); it != c.end(); ++it) { + if (predicate(*it)) + ++count; + } + return count; +} + +// Applies a function/functor to each element in the container. +template +void ForEach(const Container& c, Functor functor) { + std::for_each(c.begin(), c.end(), functor); +} + +// Returns the i-th element of the vector, or default_value if i is not +// in range [0, v.size()). +template +inline E GetElementOr(const std::vector& v, int i, E default_value) { + return (i < 0 || i >= static_cast(v.size())) ? default_value : v[i]; +} + +// Performs an in-place shuffle of a range of the vector's elements. +// 'begin' and 'end' are element indices as an STL-style range; +// i.e. [begin, end) are shuffled, where 'end' == size() means to +// shuffle to the end of the vector. +template +void ShuffleRange(internal::Random* random, int begin, int end, + std::vector* v) { + const int size = static_cast(v->size()); + GTEST_CHECK_(0 <= begin && begin <= size) + << "Invalid shuffle range start " << begin << ": must be in range [0, " + << size << "]."; + GTEST_CHECK_(begin <= end && end <= size) + << "Invalid shuffle range finish " << end << ": must be in range [" + << begin << ", " << size << "]."; + + // Fisher-Yates shuffle, from + // http://en.wikipedia.org/wiki/Fisher-Yates_shuffle + for (int range_width = end - begin; range_width >= 2; range_width--) { + const int last_in_range = begin + range_width - 1; + const int selected = begin + random->Generate(range_width); + std::swap((*v)[selected], (*v)[last_in_range]); + } +} + +// Performs an in-place shuffle of the vector's elements. +template +inline void Shuffle(internal::Random* random, std::vector* v) { + ShuffleRange(random, 0, static_cast(v->size()), v); +} + +// A function for deleting an object. Handy for being used as a +// functor. +template +static void Delete(T* x) { + delete x; +} + +// A predicate that checks the key of a TestProperty against a known key. +// +// TestPropertyKeyIs is copyable. +class TestPropertyKeyIs { + public: + // Constructor. + // + // TestPropertyKeyIs has NO default constructor. + explicit TestPropertyKeyIs(const std::string& key) : key_(key) {} + + // Returns true iff the test name of test property matches on key_. + bool operator()(const TestProperty& test_property) const { + return test_property.key() == key_; + } + + private: + std::string key_; +}; + +// Class UnitTestOptions. +// +// This class contains functions for processing options the user +// specifies when running the tests. It has only static members. +// +// In most cases, the user can specify an option using either an +// environment variable or a command line flag. E.g. you can set the +// test filter using either GTEST_FILTER or --gtest_filter. If both +// the variable and the flag are present, the latter overrides the +// former. +class GTEST_API_ UnitTestOptions { + public: + // Functions for processing the gtest_output flag. + + // Returns the output format, or "" for normal printed output. + static std::string GetOutputFormat(); + + // Returns the absolute path of the requested output file, or the + // default (test_detail.xml in the original working directory) if + // none was explicitly specified. + static std::string GetAbsolutePathToOutputFile(); + + // Functions for processing the gtest_filter flag. + + // Returns true iff the wildcard pattern matches the string. The + // first ':' or '\0' character in pattern marks the end of it. + // + // This recursive algorithm isn't very efficient, but is clear and + // works well enough for matching test names, which are short. + static bool PatternMatchesString(const char *pattern, const char *str); + + // Returns true iff the user-specified filter matches the test case + // name and the test name. + static bool FilterMatchesTest(const std::string &test_case_name, + const std::string &test_name); + +#if GTEST_OS_WINDOWS + // Function for supporting the gtest_catch_exception flag. + + // Returns EXCEPTION_EXECUTE_HANDLER if Google Test should handle the + // given SEH exception, or EXCEPTION_CONTINUE_SEARCH otherwise. + // This function is useful as an __except condition. + static int GTestShouldProcessSEH(DWORD exception_code); +#endif // GTEST_OS_WINDOWS + + // Returns true if "name" matches the ':' separated list of glob-style + // filters in "filter". + static bool MatchesFilter(const std::string& name, const char* filter); +}; + +// Returns the current application's name, removing directory path if that +// is present. Used by UnitTestOptions::GetOutputFile. +GTEST_API_ FilePath GetCurrentExecutableName(); + +// The role interface for getting the OS stack trace as a string. +class OsStackTraceGetterInterface { + public: + OsStackTraceGetterInterface() {} + virtual ~OsStackTraceGetterInterface() {} + + // Returns the current OS stack trace as an std::string. Parameters: + // + // max_depth - the maximum number of stack frames to be included + // in the trace. + // skip_count - the number of top frames to be skipped; doesn't count + // against max_depth. + virtual string CurrentStackTrace(int max_depth, int skip_count) = 0; + + // UponLeavingGTest() should be called immediately before Google Test calls + // user code. It saves some information about the current stack that + // CurrentStackTrace() will use to find and hide Google Test stack frames. + virtual void UponLeavingGTest() = 0; + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(OsStackTraceGetterInterface); +}; + +// A working implementation of the OsStackTraceGetterInterface interface. +class OsStackTraceGetter : public OsStackTraceGetterInterface { + public: + OsStackTraceGetter() : caller_frame_(NULL) {} + + virtual string CurrentStackTrace(int max_depth, int skip_count) + GTEST_LOCK_EXCLUDED_(mutex_); + + virtual void UponLeavingGTest() GTEST_LOCK_EXCLUDED_(mutex_); + + // This string is inserted in place of stack frames that are part of + // Google Test's implementation. + static const char* const kElidedFramesMarker; + + private: + Mutex mutex_; // protects all internal state + + // We save the stack frame below the frame that calls user code. + // We do this because the address of the frame immediately below + // the user code changes between the call to UponLeavingGTest() + // and any calls to CurrentStackTrace() from within the user code. + void* caller_frame_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(OsStackTraceGetter); +}; + +// Information about a Google Test trace point. +struct TraceInfo { + const char* file; + int line; + std::string message; +}; + +// This is the default global test part result reporter used in UnitTestImpl. +// This class should only be used by UnitTestImpl. +class DefaultGlobalTestPartResultReporter + : public TestPartResultReporterInterface { + public: + explicit DefaultGlobalTestPartResultReporter(UnitTestImpl* unit_test); + // Implements the TestPartResultReporterInterface. Reports the test part + // result in the current test. + virtual void ReportTestPartResult(const TestPartResult& result); + + private: + UnitTestImpl* const unit_test_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(DefaultGlobalTestPartResultReporter); +}; + +// This is the default per thread test part result reporter used in +// UnitTestImpl. This class should only be used by UnitTestImpl. +class DefaultPerThreadTestPartResultReporter + : public TestPartResultReporterInterface { + public: + explicit DefaultPerThreadTestPartResultReporter(UnitTestImpl* unit_test); + // Implements the TestPartResultReporterInterface. The implementation just + // delegates to the current global test part result reporter of *unit_test_. + virtual void ReportTestPartResult(const TestPartResult& result); + + private: + UnitTestImpl* const unit_test_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(DefaultPerThreadTestPartResultReporter); +}; + +// The private implementation of the UnitTest class. We don't protect +// the methods under a mutex, as this class is not accessible by a +// user and the UnitTest class that delegates work to this class does +// proper locking. +class GTEST_API_ UnitTestImpl { + public: + explicit UnitTestImpl(UnitTest* parent); + virtual ~UnitTestImpl(); + + // There are two different ways to register your own TestPartResultReporter. + // You can register your own repoter to listen either only for test results + // from the current thread or for results from all threads. + // By default, each per-thread test result repoter just passes a new + // TestPartResult to the global test result reporter, which registers the + // test part result for the currently running test. + + // Returns the global test part result reporter. + TestPartResultReporterInterface* GetGlobalTestPartResultReporter(); + + // Sets the global test part result reporter. + void SetGlobalTestPartResultReporter( + TestPartResultReporterInterface* reporter); + + // Returns the test part result reporter for the current thread. + TestPartResultReporterInterface* GetTestPartResultReporterForCurrentThread(); + + // Sets the test part result reporter for the current thread. + void SetTestPartResultReporterForCurrentThread( + TestPartResultReporterInterface* reporter); + + // Gets the number of successful test cases. + int successful_test_case_count() const; + + // Gets the number of failed test cases. + int failed_test_case_count() const; + + // Gets the number of all test cases. + int total_test_case_count() const; + + // Gets the number of all test cases that contain at least one test + // that should run. + int test_case_to_run_count() const; + + // Gets the number of successful tests. + int successful_test_count() const; + + // Gets the number of failed tests. + int failed_test_count() const; + + // Gets the number of disabled tests that will be reported in the XML report. + int reportable_disabled_test_count() const; + + // Gets the number of disabled tests. + int disabled_test_count() const; + + // Gets the number of tests to be printed in the XML report. + int reportable_test_count() const; + + // Gets the number of all tests. + int total_test_count() const; + + // Gets the number of tests that should run. + int test_to_run_count() const; + + // Gets the time of the test program start, in ms from the start of the + // UNIX epoch. + TimeInMillis start_timestamp() const { return start_timestamp_; } + + // Gets the elapsed time, in milliseconds. + TimeInMillis elapsed_time() const { return elapsed_time_; } + + // Returns true iff the unit test passed (i.e. all test cases passed). + bool Passed() const { return !Failed(); } + + // Returns true iff the unit test failed (i.e. some test case failed + // or something outside of all tests failed). + bool Failed() const { + return failed_test_case_count() > 0 || ad_hoc_test_result()->Failed(); + } + + // Gets the i-th test case among all the test cases. i can range from 0 to + // total_test_case_count() - 1. If i is not in that range, returns NULL. + const TestCase* GetTestCase(int i) const { + const int index = GetElementOr(test_case_indices_, i, -1); + return index < 0 ? NULL : test_cases_[i]; + } + + // Gets the i-th test case among all the test cases. i can range from 0 to + // total_test_case_count() - 1. If i is not in that range, returns NULL. + TestCase* GetMutableTestCase(int i) { + const int index = GetElementOr(test_case_indices_, i, -1); + return index < 0 ? NULL : test_cases_[index]; + } + + // Provides access to the event listener list. + TestEventListeners* listeners() { return &listeners_; } + + // Returns the TestResult for the test that's currently running, or + // the TestResult for the ad hoc test if no test is running. + TestResult* current_test_result(); + + // Returns the TestResult for the ad hoc test. + const TestResult* ad_hoc_test_result() const { return &ad_hoc_test_result_; } + + // Sets the OS stack trace getter. + // + // Does nothing if the input and the current OS stack trace getter + // are the same; otherwise, deletes the old getter and makes the + // input the current getter. + void set_os_stack_trace_getter(OsStackTraceGetterInterface* getter); + + // Returns the current OS stack trace getter if it is not NULL; + // otherwise, creates an OsStackTraceGetter, makes it the current + // getter, and returns it. + OsStackTraceGetterInterface* os_stack_trace_getter(); + + // Returns the current OS stack trace as an std::string. + // + // The maximum number of stack frames to be included is specified by + // the gtest_stack_trace_depth flag. The skip_count parameter + // specifies the number of top frames to be skipped, which doesn't + // count against the number of frames to be included. + // + // For example, if Foo() calls Bar(), which in turn calls + // CurrentOsStackTraceExceptTop(1), Foo() will be included in the + // trace but Bar() and CurrentOsStackTraceExceptTop() won't. + std::string CurrentOsStackTraceExceptTop(int skip_count) GTEST_NO_INLINE_; + + // Finds and returns a TestCase with the given name. If one doesn't + // exist, creates one and returns it. + // + // Arguments: + // + // test_case_name: name of the test case + // type_param: the name of the test's type parameter, or NULL if + // this is not a typed or a type-parameterized test. + // set_up_tc: pointer to the function that sets up the test case + // tear_down_tc: pointer to the function that tears down the test case + TestCase* GetTestCase(const char* test_case_name, + const char* type_param, + Test::SetUpTestCaseFunc set_up_tc, + Test::TearDownTestCaseFunc tear_down_tc); + + // Adds a TestInfo to the unit test. + // + // Arguments: + // + // set_up_tc: pointer to the function that sets up the test case + // tear_down_tc: pointer to the function that tears down the test case + // test_info: the TestInfo object + void AddTestInfo(Test::SetUpTestCaseFunc set_up_tc, + Test::TearDownTestCaseFunc tear_down_tc, + TestInfo* test_info) { + // In order to support thread-safe death tests, we need to + // remember the original working directory when the test program + // was first invoked. We cannot do this in RUN_ALL_TESTS(), as + // the user may have changed the current directory before calling + // RUN_ALL_TESTS(). Therefore we capture the current directory in + // AddTestInfo(), which is called to register a TEST or TEST_F + // before main() is reached. + if (original_working_dir_.IsEmpty()) { + original_working_dir_.Set(FilePath::GetCurrentDir()); + GTEST_CHECK_(!original_working_dir_.IsEmpty()) + << "Failed to get the current working directory."; + } + + GetTestCase(test_info->test_case_name(), + test_info->type_param(), + set_up_tc, + tear_down_tc)->AddTestInfo(test_info); + } + +#if GTEST_HAS_PARAM_TEST + // Returns ParameterizedTestCaseRegistry object used to keep track of + // value-parameterized tests and instantiate and register them. + internal::ParameterizedTestCaseRegistry& parameterized_test_registry() { + return parameterized_test_registry_; + } +#endif // GTEST_HAS_PARAM_TEST + + // Sets the TestCase object for the test that's currently running. + void set_current_test_case(TestCase* a_current_test_case) { + current_test_case_ = a_current_test_case; + } + + // Sets the TestInfo object for the test that's currently running. If + // current_test_info is NULL, the assertion results will be stored in + // ad_hoc_test_result_. + void set_current_test_info(TestInfo* a_current_test_info) { + current_test_info_ = a_current_test_info; + } + + // Registers all parameterized tests defined using TEST_P and + // INSTANTIATE_TEST_CASE_P, creating regular tests for each test/parameter + // combination. This method can be called more then once; it has guards + // protecting from registering the tests more then once. If + // value-parameterized tests are disabled, RegisterParameterizedTests is + // present but does nothing. + void RegisterParameterizedTests(); + + // Runs all tests in this UnitTest object, prints the result, and + // returns true if all tests are successful. If any exception is + // thrown during a test, this test is considered to be failed, but + // the rest of the tests will still be run. + bool RunAllTests(); + + // Clears the results of all tests, except the ad hoc tests. + void ClearNonAdHocTestResult() { + ForEach(test_cases_, TestCase::ClearTestCaseResult); + } + + // Clears the results of ad-hoc test assertions. + void ClearAdHocTestResult() { + ad_hoc_test_result_.Clear(); + } + + // Adds a TestProperty to the current TestResult object when invoked in a + // context of a test or a test case, or to the global property set. If the + // result already contains a property with the same key, the value will be + // updated. + void RecordProperty(const TestProperty& test_property); + + enum ReactionToSharding { + HONOR_SHARDING_PROTOCOL, + IGNORE_SHARDING_PROTOCOL + }; + + // Matches the full name of each test against the user-specified + // filter to decide whether the test should run, then records the + // result in each TestCase and TestInfo object. + // If shard_tests == HONOR_SHARDING_PROTOCOL, further filters tests + // based on sharding variables in the environment. + // Returns the number of tests that should run. + int FilterTests(ReactionToSharding shard_tests); + + // Prints the names of the tests matching the user-specified filter flag. + void ListTestsMatchingFilter(); + + const TestCase* current_test_case() const { return current_test_case_; } + TestInfo* current_test_info() { return current_test_info_; } + const TestInfo* current_test_info() const { return current_test_info_; } + + // Returns the vector of environments that need to be set-up/torn-down + // before/after the tests are run. + std::vector& environments() { return environments_; } + + // Getters for the per-thread Google Test trace stack. + std::vector& gtest_trace_stack() { + return *(gtest_trace_stack_.pointer()); + } + const std::vector& gtest_trace_stack() const { + return gtest_trace_stack_.get(); + } + +#if GTEST_HAS_DEATH_TEST + void InitDeathTestSubprocessControlInfo() { + internal_run_death_test_flag_.reset(ParseInternalRunDeathTestFlag()); + } + // Returns a pointer to the parsed --gtest_internal_run_death_test + // flag, or NULL if that flag was not specified. + // This information is useful only in a death test child process. + // Must not be called before a call to InitGoogleTest. + const InternalRunDeathTestFlag* internal_run_death_test_flag() const { + return internal_run_death_test_flag_.get(); + } + + // Returns a pointer to the current death test factory. + internal::DeathTestFactory* death_test_factory() { + return death_test_factory_.get(); + } + + void SuppressTestEventsIfInSubprocess(); + + friend class ReplaceDeathTestFactory; +#endif // GTEST_HAS_DEATH_TEST + + // Initializes the event listener performing XML output as specified by + // UnitTestOptions. Must not be called before InitGoogleTest. + void ConfigureXmlOutput(); + +#if GTEST_CAN_STREAM_RESULTS_ + // Initializes the event listener for streaming test results to a socket. + // Must not be called before InitGoogleTest. + void ConfigureStreamingOutput(); +#endif + + // Performs initialization dependent upon flag values obtained in + // ParseGoogleTestFlagsOnly. Is called from InitGoogleTest after the call to + // ParseGoogleTestFlagsOnly. In case a user neglects to call InitGoogleTest + // this function is also called from RunAllTests. Since this function can be + // called more than once, it has to be idempotent. + void PostFlagParsingInit(); + + // Gets the random seed used at the start of the current test iteration. + int random_seed() const { return random_seed_; } + + // Gets the random number generator. + internal::Random* random() { return &random_; } + + // Shuffles all test cases, and the tests within each test case, + // making sure that death tests are still run first. + void ShuffleTests(); + + // Restores the test cases and tests to their order before the first shuffle. + void UnshuffleTests(); + + // Returns the value of GTEST_FLAG(catch_exceptions) at the moment + // UnitTest::Run() starts. + bool catch_exceptions() const { return catch_exceptions_; } + + private: + friend class ::testing::UnitTest; + + // Used by UnitTest::Run() to capture the state of + // GTEST_FLAG(catch_exceptions) at the moment it starts. + void set_catch_exceptions(bool value) { catch_exceptions_ = value; } + + // The UnitTest object that owns this implementation object. + UnitTest* const parent_; + + // The working directory when the first TEST() or TEST_F() was + // executed. + internal::FilePath original_working_dir_; + + // The default test part result reporters. + DefaultGlobalTestPartResultReporter default_global_test_part_result_reporter_; + DefaultPerThreadTestPartResultReporter + default_per_thread_test_part_result_reporter_; + + // Points to (but doesn't own) the global test part result reporter. + TestPartResultReporterInterface* global_test_part_result_repoter_; + + // Protects read and write access to global_test_part_result_reporter_. + internal::Mutex global_test_part_result_reporter_mutex_; + + // Points to (but doesn't own) the per-thread test part result reporter. + internal::ThreadLocal + per_thread_test_part_result_reporter_; + + // The vector of environments that need to be set-up/torn-down + // before/after the tests are run. + std::vector environments_; + + // The vector of TestCases in their original order. It owns the + // elements in the vector. + std::vector test_cases_; + + // Provides a level of indirection for the test case list to allow + // easy shuffling and restoring the test case order. The i-th + // element of this vector is the index of the i-th test case in the + // shuffled order. + std::vector test_case_indices_; + +#if GTEST_HAS_PARAM_TEST + // ParameterizedTestRegistry object used to register value-parameterized + // tests. + internal::ParameterizedTestCaseRegistry parameterized_test_registry_; + + // Indicates whether RegisterParameterizedTests() has been called already. + bool parameterized_tests_registered_; +#endif // GTEST_HAS_PARAM_TEST + + // Index of the last death test case registered. Initially -1. + int last_death_test_case_; + + // This points to the TestCase for the currently running test. It + // changes as Google Test goes through one test case after another. + // When no test is running, this is set to NULL and Google Test + // stores assertion results in ad_hoc_test_result_. Initially NULL. + TestCase* current_test_case_; + + // This points to the TestInfo for the currently running test. It + // changes as Google Test goes through one test after another. When + // no test is running, this is set to NULL and Google Test stores + // assertion results in ad_hoc_test_result_. Initially NULL. + TestInfo* current_test_info_; + + // Normally, a user only writes assertions inside a TEST or TEST_F, + // or inside a function called by a TEST or TEST_F. Since Google + // Test keeps track of which test is current running, it can + // associate such an assertion with the test it belongs to. + // + // If an assertion is encountered when no TEST or TEST_F is running, + // Google Test attributes the assertion result to an imaginary "ad hoc" + // test, and records the result in ad_hoc_test_result_. + TestResult ad_hoc_test_result_; + + // The list of event listeners that can be used to track events inside + // Google Test. + TestEventListeners listeners_; + + // The OS stack trace getter. Will be deleted when the UnitTest + // object is destructed. By default, an OsStackTraceGetter is used, + // but the user can set this field to use a custom getter if that is + // desired. + OsStackTraceGetterInterface* os_stack_trace_getter_; + + // True iff PostFlagParsingInit() has been called. + bool post_flag_parse_init_performed_; + + // The random number seed used at the beginning of the test run. + int random_seed_; + + // Our random number generator. + internal::Random random_; + + // The time of the test program start, in ms from the start of the + // UNIX epoch. + TimeInMillis start_timestamp_; + + // How long the test took to run, in milliseconds. + TimeInMillis elapsed_time_; + +#if GTEST_HAS_DEATH_TEST + // The decomposed components of the gtest_internal_run_death_test flag, + // parsed when RUN_ALL_TESTS is called. + internal::scoped_ptr internal_run_death_test_flag_; + internal::scoped_ptr death_test_factory_; +#endif // GTEST_HAS_DEATH_TEST + + // A per-thread stack of traces created by the SCOPED_TRACE() macro. + internal::ThreadLocal > gtest_trace_stack_; + + // The value of GTEST_FLAG(catch_exceptions) at the moment RunAllTests() + // starts. + bool catch_exceptions_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(UnitTestImpl); +}; // class UnitTestImpl + +// Convenience function for accessing the global UnitTest +// implementation object. +inline UnitTestImpl* GetUnitTestImpl() { + return UnitTest::GetInstance()->impl(); +} + +#if GTEST_USES_SIMPLE_RE + +// Internal helper functions for implementing the simple regular +// expression matcher. +GTEST_API_ bool IsInSet(char ch, const char* str); +GTEST_API_ bool IsAsciiDigit(char ch); +GTEST_API_ bool IsAsciiPunct(char ch); +GTEST_API_ bool IsRepeat(char ch); +GTEST_API_ bool IsAsciiWhiteSpace(char ch); +GTEST_API_ bool IsAsciiWordChar(char ch); +GTEST_API_ bool IsValidEscape(char ch); +GTEST_API_ bool AtomMatchesChar(bool escaped, char pattern, char ch); +GTEST_API_ bool ValidateRegex(const char* regex); +GTEST_API_ bool MatchRegexAtHead(const char* regex, const char* str); +GTEST_API_ bool MatchRepetitionAndRegexAtHead( + bool escaped, char ch, char repeat, const char* regex, const char* str); +GTEST_API_ bool MatchRegexAnywhere(const char* regex, const char* str); + +#endif // GTEST_USES_SIMPLE_RE + +// Parses the command line for Google Test flags, without initializing +// other parts of Google Test. +GTEST_API_ void ParseGoogleTestFlagsOnly(int* argc, char** argv); +GTEST_API_ void ParseGoogleTestFlagsOnly(int* argc, wchar_t** argv); + +#if GTEST_HAS_DEATH_TEST + +// Returns the message describing the last system error, regardless of the +// platform. +GTEST_API_ std::string GetLastErrnoDescription(); + +# if GTEST_OS_WINDOWS +// Provides leak-safe Windows kernel handle ownership. +class AutoHandle { + public: + AutoHandle() : handle_(INVALID_HANDLE_VALUE) {} + explicit AutoHandle(HANDLE handle) : handle_(handle) {} + + ~AutoHandle() { Reset(); } + + HANDLE Get() const { return handle_; } + void Reset() { Reset(INVALID_HANDLE_VALUE); } + void Reset(HANDLE handle) { + if (handle != handle_) { + if (handle_ != INVALID_HANDLE_VALUE) + ::CloseHandle(handle_); + handle_ = handle; + } + } + + private: + HANDLE handle_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(AutoHandle); +}; +# endif // GTEST_OS_WINDOWS + +// Attempts to parse a string into a positive integer pointed to by the +// number parameter. Returns true if that is possible. +// GTEST_HAS_DEATH_TEST implies that we have ::std::string, so we can use +// it here. +template +bool ParseNaturalNumber(const ::std::string& str, Integer* number) { + // Fail fast if the given string does not begin with a digit; + // this bypasses strtoXXX's "optional leading whitespace and plus + // or minus sign" semantics, which are undesirable here. + if (str.empty() || !IsDigit(str[0])) { + return false; + } + errno = 0; + + char* end; + // BiggestConvertible is the largest integer type that system-provided + // string-to-number conversion routines can return. + +# if GTEST_OS_WINDOWS && !defined(__GNUC__) + + // MSVC and C++ Builder define __int64 instead of the standard long long. + typedef unsigned __int64 BiggestConvertible; + const BiggestConvertible parsed = _strtoui64(str.c_str(), &end, 10); + +# else + + typedef unsigned long long BiggestConvertible; // NOLINT + const BiggestConvertible parsed = strtoull(str.c_str(), &end, 10); + +# endif // GTEST_OS_WINDOWS && !defined(__GNUC__) + + const bool parse_success = *end == '\0' && errno == 0; + + // TODO(vladl@google.com): Convert this to compile time assertion when it is + // available. + GTEST_CHECK_(sizeof(Integer) <= sizeof(parsed)); + + const Integer result = static_cast(parsed); + if (parse_success && static_cast(result) == parsed) { + *number = result; + return true; + } + return false; +} +#endif // GTEST_HAS_DEATH_TEST + +// TestResult contains some private methods that should be hidden from +// Google Test user but are required for testing. This class allow our tests +// to access them. +// +// This class is supplied only for the purpose of testing Google Test's own +// constructs. Do not use it in user tests, either directly or indirectly. +class TestResultAccessor { + public: + static void RecordProperty(TestResult* test_result, + const std::string& xml_element, + const TestProperty& property) { + test_result->RecordProperty(xml_element, property); + } + + static void ClearTestPartResults(TestResult* test_result) { + test_result->ClearTestPartResults(); + } + + static const std::vector& test_part_results( + const TestResult& test_result) { + return test_result.test_part_results(); + } +}; + +#if GTEST_CAN_STREAM_RESULTS_ + +// Streams test results to the given port on the given host machine. +class StreamingListener : public EmptyTestEventListener { + public: + // Abstract base class for writing strings to a socket. + class AbstractSocketWriter { + public: + virtual ~AbstractSocketWriter() {} + + // Sends a string to the socket. + virtual void Send(const string& message) = 0; + + // Closes the socket. + virtual void CloseConnection() {} + + // Sends a string and a newline to the socket. + void SendLn(const string& message) { + Send(message + "\n"); + } + }; + + // Concrete class for actually writing strings to a socket. + class SocketWriter : public AbstractSocketWriter { + public: + SocketWriter(const string& host, const string& port) + : sockfd_(-1), host_name_(host), port_num_(port) { + MakeConnection(); + } + + virtual ~SocketWriter() { + if (sockfd_ != -1) + CloseConnection(); + } + + // Sends a string to the socket. + virtual void Send(const string& message) { + GTEST_CHECK_(sockfd_ != -1) + << "Send() can be called only when there is a connection."; + + const int len = static_cast(message.length()); + if (write(sockfd_, message.c_str(), len) != len) { + GTEST_LOG_(WARNING) + << "stream_result_to: failed to stream to " + << host_name_ << ":" << port_num_; + } + } + + private: + // Creates a client socket and connects to the server. + void MakeConnection(); + + // Closes the socket. + void CloseConnection() { + GTEST_CHECK_(sockfd_ != -1) + << "CloseConnection() can be called only when there is a connection."; + + close(sockfd_); + sockfd_ = -1; + } + + int sockfd_; // socket file descriptor + const string host_name_; + const string port_num_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(SocketWriter); + }; // class SocketWriter + + // Escapes '=', '&', '%', and '\n' characters in str as "%xx". + static string UrlEncode(const char* str); + + StreamingListener(const string& host, const string& port) + : socket_writer_(new SocketWriter(host, port)) { Start(); } + + explicit StreamingListener(AbstractSocketWriter* socket_writer) + : socket_writer_(socket_writer) { Start(); } + + void OnTestProgramStart(const UnitTest& /* unit_test */) { + SendLn("event=TestProgramStart"); + } + + void OnTestProgramEnd(const UnitTest& unit_test) { + // Note that Google Test current only report elapsed time for each + // test iteration, not for the entire test program. + SendLn("event=TestProgramEnd&passed=" + FormatBool(unit_test.Passed())); + + // Notify the streaming server to stop. + socket_writer_->CloseConnection(); + } + + void OnTestIterationStart(const UnitTest& /* unit_test */, int iteration) { + SendLn("event=TestIterationStart&iteration=" + + StreamableToString(iteration)); + } + + void OnTestIterationEnd(const UnitTest& unit_test, int /* iteration */) { + SendLn("event=TestIterationEnd&passed=" + + FormatBool(unit_test.Passed()) + "&elapsed_time=" + + StreamableToString(unit_test.elapsed_time()) + "ms"); + } + + void OnTestCaseStart(const TestCase& test_case) { + SendLn(std::string("event=TestCaseStart&name=") + test_case.name()); + } + + void OnTestCaseEnd(const TestCase& test_case) { + SendLn("event=TestCaseEnd&passed=" + FormatBool(test_case.Passed()) + + "&elapsed_time=" + StreamableToString(test_case.elapsed_time()) + + "ms"); + } + + void OnTestStart(const TestInfo& test_info) { + SendLn(std::string("event=TestStart&name=") + test_info.name()); + } + + void OnTestEnd(const TestInfo& test_info) { + SendLn("event=TestEnd&passed=" + + FormatBool((test_info.result())->Passed()) + + "&elapsed_time=" + + StreamableToString((test_info.result())->elapsed_time()) + "ms"); + } + + void OnTestPartResult(const TestPartResult& test_part_result) { + const char* file_name = test_part_result.file_name(); + if (file_name == NULL) + file_name = ""; + SendLn("event=TestPartResult&file=" + UrlEncode(file_name) + + "&line=" + StreamableToString(test_part_result.line_number()) + + "&message=" + UrlEncode(test_part_result.message())); + } + + private: + // Sends the given message and a newline to the socket. + void SendLn(const string& message) { socket_writer_->SendLn(message); } + + // Called at the start of streaming to notify the receiver what + // protocol we are using. + void Start() { SendLn("gtest_streaming_protocol_version=1.0"); } + + string FormatBool(bool value) { return value ? "1" : "0"; } + + const scoped_ptr socket_writer_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(StreamingListener); +}; // class StreamingListener + +#endif // GTEST_CAN_STREAM_RESULTS_ + +} // namespace internal +} // namespace testing + +#endif // GTEST_SRC_GTEST_INTERNAL_INL_H_ diff --git a/vendor/gmock-1.7.0/gtest/src/gtest-port.cc b/vendor/gmock-1.7.0/gtest/src/gtest-port.cc new file mode 100644 index 0000000000..0c4df5f29a --- /dev/null +++ b/vendor/gmock-1.7.0/gtest/src/gtest-port.cc @@ -0,0 +1,805 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) + +#include "gtest/internal/gtest-port.h" + +#include +#include +#include +#include + +#if GTEST_OS_WINDOWS_MOBILE +# include // For TerminateProcess() +#elif GTEST_OS_WINDOWS +# include +# include +#else +# include +#endif // GTEST_OS_WINDOWS_MOBILE + +#if GTEST_OS_MAC +# include +# include +# include +#endif // GTEST_OS_MAC + +#if GTEST_OS_QNX +# include +# include +#endif // GTEST_OS_QNX + +#include "gtest/gtest-spi.h" +#include "gtest/gtest-message.h" +#include "gtest/internal/gtest-internal.h" +#include "gtest/internal/gtest-string.h" + +// Indicates that this translation unit is part of Google Test's +// implementation. It must come before gtest-internal-inl.h is +// included, or there will be a compiler error. This trick is to +// prevent a user from accidentally including gtest-internal-inl.h in +// his code. +#define GTEST_IMPLEMENTATION_ 1 +#include "src/gtest-internal-inl.h" +#undef GTEST_IMPLEMENTATION_ + +namespace testing { +namespace internal { + +#if defined(_MSC_VER) || defined(__BORLANDC__) +// MSVC and C++Builder do not provide a definition of STDERR_FILENO. +const int kStdOutFileno = 1; +const int kStdErrFileno = 2; +#else +const int kStdOutFileno = STDOUT_FILENO; +const int kStdErrFileno = STDERR_FILENO; +#endif // _MSC_VER + +#if GTEST_OS_MAC + +// Returns the number of threads running in the process, or 0 to indicate that +// we cannot detect it. +size_t GetThreadCount() { + const task_t task = mach_task_self(); + mach_msg_type_number_t thread_count; + thread_act_array_t thread_list; + const kern_return_t status = task_threads(task, &thread_list, &thread_count); + if (status == KERN_SUCCESS) { + // task_threads allocates resources in thread_list and we need to free them + // to avoid leaks. + vm_deallocate(task, + reinterpret_cast(thread_list), + sizeof(thread_t) * thread_count); + return static_cast(thread_count); + } else { + return 0; + } +} + +#elif GTEST_OS_QNX + +// Returns the number of threads running in the process, or 0 to indicate that +// we cannot detect it. +size_t GetThreadCount() { + const int fd = open("/proc/self/as", O_RDONLY); + if (fd < 0) { + return 0; + } + procfs_info process_info; + const int status = + devctl(fd, DCMD_PROC_INFO, &process_info, sizeof(process_info), NULL); + close(fd); + if (status == EOK) { + return static_cast(process_info.num_threads); + } else { + return 0; + } +} + +#else + +size_t GetThreadCount() { + // There's no portable way to detect the number of threads, so we just + // return 0 to indicate that we cannot detect it. + return 0; +} + +#endif // GTEST_OS_MAC + +#if GTEST_USES_POSIX_RE + +// Implements RE. Currently only needed for death tests. + +RE::~RE() { + if (is_valid_) { + // regfree'ing an invalid regex might crash because the content + // of the regex is undefined. Since the regex's are essentially + // the same, one cannot be valid (or invalid) without the other + // being so too. + regfree(&partial_regex_); + regfree(&full_regex_); + } + free(const_cast(pattern_)); +} + +// Returns true iff regular expression re matches the entire str. +bool RE::FullMatch(const char* str, const RE& re) { + if (!re.is_valid_) return false; + + regmatch_t match; + return regexec(&re.full_regex_, str, 1, &match, 0) == 0; +} + +// Returns true iff regular expression re matches a substring of str +// (including str itself). +bool RE::PartialMatch(const char* str, const RE& re) { + if (!re.is_valid_) return false; + + regmatch_t match; + return regexec(&re.partial_regex_, str, 1, &match, 0) == 0; +} + +// Initializes an RE from its string representation. +void RE::Init(const char* regex) { + pattern_ = posix::StrDup(regex); + + // Reserves enough bytes to hold the regular expression used for a + // full match. + const size_t full_regex_len = strlen(regex) + 10; + char* const full_pattern = new char[full_regex_len]; + + snprintf(full_pattern, full_regex_len, "^(%s)$", regex); + is_valid_ = regcomp(&full_regex_, full_pattern, REG_EXTENDED) == 0; + // We want to call regcomp(&partial_regex_, ...) even if the + // previous expression returns false. Otherwise partial_regex_ may + // not be properly initialized can may cause trouble when it's + // freed. + // + // Some implementation of POSIX regex (e.g. on at least some + // versions of Cygwin) doesn't accept the empty string as a valid + // regex. We change it to an equivalent form "()" to be safe. + if (is_valid_) { + const char* const partial_regex = (*regex == '\0') ? "()" : regex; + is_valid_ = regcomp(&partial_regex_, partial_regex, REG_EXTENDED) == 0; + } + EXPECT_TRUE(is_valid_) + << "Regular expression \"" << regex + << "\" is not a valid POSIX Extended regular expression."; + + delete[] full_pattern; +} + +#elif GTEST_USES_SIMPLE_RE + +// Returns true iff ch appears anywhere in str (excluding the +// terminating '\0' character). +bool IsInSet(char ch, const char* str) { + return ch != '\0' && strchr(str, ch) != NULL; +} + +// Returns true iff ch belongs to the given classification. Unlike +// similar functions in , these aren't affected by the +// current locale. +bool IsAsciiDigit(char ch) { return '0' <= ch && ch <= '9'; } +bool IsAsciiPunct(char ch) { + return IsInSet(ch, "^-!\"#$%&'()*+,./:;<=>?@[\\]_`{|}~"); +} +bool IsRepeat(char ch) { return IsInSet(ch, "?*+"); } +bool IsAsciiWhiteSpace(char ch) { return IsInSet(ch, " \f\n\r\t\v"); } +bool IsAsciiWordChar(char ch) { + return ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') || + ('0' <= ch && ch <= '9') || ch == '_'; +} + +// Returns true iff "\\c" is a supported escape sequence. +bool IsValidEscape(char c) { + return (IsAsciiPunct(c) || IsInSet(c, "dDfnrsStvwW")); +} + +// Returns true iff the given atom (specified by escaped and pattern) +// matches ch. The result is undefined if the atom is invalid. +bool AtomMatchesChar(bool escaped, char pattern_char, char ch) { + if (escaped) { // "\\p" where p is pattern_char. + switch (pattern_char) { + case 'd': return IsAsciiDigit(ch); + case 'D': return !IsAsciiDigit(ch); + case 'f': return ch == '\f'; + case 'n': return ch == '\n'; + case 'r': return ch == '\r'; + case 's': return IsAsciiWhiteSpace(ch); + case 'S': return !IsAsciiWhiteSpace(ch); + case 't': return ch == '\t'; + case 'v': return ch == '\v'; + case 'w': return IsAsciiWordChar(ch); + case 'W': return !IsAsciiWordChar(ch); + } + return IsAsciiPunct(pattern_char) && pattern_char == ch; + } + + return (pattern_char == '.' && ch != '\n') || pattern_char == ch; +} + +// Helper function used by ValidateRegex() to format error messages. +std::string FormatRegexSyntaxError(const char* regex, int index) { + return (Message() << "Syntax error at index " << index + << " in simple regular expression \"" << regex << "\": ").GetString(); +} + +// Generates non-fatal failures and returns false if regex is invalid; +// otherwise returns true. +bool ValidateRegex(const char* regex) { + if (regex == NULL) { + // TODO(wan@google.com): fix the source file location in the + // assertion failures to match where the regex is used in user + // code. + ADD_FAILURE() << "NULL is not a valid simple regular expression."; + return false; + } + + bool is_valid = true; + + // True iff ?, *, or + can follow the previous atom. + bool prev_repeatable = false; + for (int i = 0; regex[i]; i++) { + if (regex[i] == '\\') { // An escape sequence + i++; + if (regex[i] == '\0') { + ADD_FAILURE() << FormatRegexSyntaxError(regex, i - 1) + << "'\\' cannot appear at the end."; + return false; + } + + if (!IsValidEscape(regex[i])) { + ADD_FAILURE() << FormatRegexSyntaxError(regex, i - 1) + << "invalid escape sequence \"\\" << regex[i] << "\"."; + is_valid = false; + } + prev_repeatable = true; + } else { // Not an escape sequence. + const char ch = regex[i]; + + if (ch == '^' && i > 0) { + ADD_FAILURE() << FormatRegexSyntaxError(regex, i) + << "'^' can only appear at the beginning."; + is_valid = false; + } else if (ch == '$' && regex[i + 1] != '\0') { + ADD_FAILURE() << FormatRegexSyntaxError(regex, i) + << "'$' can only appear at the end."; + is_valid = false; + } else if (IsInSet(ch, "()[]{}|")) { + ADD_FAILURE() << FormatRegexSyntaxError(regex, i) + << "'" << ch << "' is unsupported."; + is_valid = false; + } else if (IsRepeat(ch) && !prev_repeatable) { + ADD_FAILURE() << FormatRegexSyntaxError(regex, i) + << "'" << ch << "' can only follow a repeatable token."; + is_valid = false; + } + + prev_repeatable = !IsInSet(ch, "^$?*+"); + } + } + + return is_valid; +} + +// Matches a repeated regex atom followed by a valid simple regular +// expression. The regex atom is defined as c if escaped is false, +// or \c otherwise. repeat is the repetition meta character (?, *, +// or +). The behavior is undefined if str contains too many +// characters to be indexable by size_t, in which case the test will +// probably time out anyway. We are fine with this limitation as +// std::string has it too. +bool MatchRepetitionAndRegexAtHead( + bool escaped, char c, char repeat, const char* regex, + const char* str) { + const size_t min_count = (repeat == '+') ? 1 : 0; + const size_t max_count = (repeat == '?') ? 1 : + static_cast(-1) - 1; + // We cannot call numeric_limits::max() as it conflicts with the + // max() macro on Windows. + + for (size_t i = 0; i <= max_count; ++i) { + // We know that the atom matches each of the first i characters in str. + if (i >= min_count && MatchRegexAtHead(regex, str + i)) { + // We have enough matches at the head, and the tail matches too. + // Since we only care about *whether* the pattern matches str + // (as opposed to *how* it matches), there is no need to find a + // greedy match. + return true; + } + if (str[i] == '\0' || !AtomMatchesChar(escaped, c, str[i])) + return false; + } + return false; +} + +// Returns true iff regex matches a prefix of str. regex must be a +// valid simple regular expression and not start with "^", or the +// result is undefined. +bool MatchRegexAtHead(const char* regex, const char* str) { + if (*regex == '\0') // An empty regex matches a prefix of anything. + return true; + + // "$" only matches the end of a string. Note that regex being + // valid guarantees that there's nothing after "$" in it. + if (*regex == '$') + return *str == '\0'; + + // Is the first thing in regex an escape sequence? + const bool escaped = *regex == '\\'; + if (escaped) + ++regex; + if (IsRepeat(regex[1])) { + // MatchRepetitionAndRegexAtHead() calls MatchRegexAtHead(), so + // here's an indirect recursion. It terminates as the regex gets + // shorter in each recursion. + return MatchRepetitionAndRegexAtHead( + escaped, regex[0], regex[1], regex + 2, str); + } else { + // regex isn't empty, isn't "$", and doesn't start with a + // repetition. We match the first atom of regex with the first + // character of str and recurse. + return (*str != '\0') && AtomMatchesChar(escaped, *regex, *str) && + MatchRegexAtHead(regex + 1, str + 1); + } +} + +// Returns true iff regex matches any substring of str. regex must be +// a valid simple regular expression, or the result is undefined. +// +// The algorithm is recursive, but the recursion depth doesn't exceed +// the regex length, so we won't need to worry about running out of +// stack space normally. In rare cases the time complexity can be +// exponential with respect to the regex length + the string length, +// but usually it's must faster (often close to linear). +bool MatchRegexAnywhere(const char* regex, const char* str) { + if (regex == NULL || str == NULL) + return false; + + if (*regex == '^') + return MatchRegexAtHead(regex + 1, str); + + // A successful match can be anywhere in str. + do { + if (MatchRegexAtHead(regex, str)) + return true; + } while (*str++ != '\0'); + return false; +} + +// Implements the RE class. + +RE::~RE() { + free(const_cast(pattern_)); + free(const_cast(full_pattern_)); +} + +// Returns true iff regular expression re matches the entire str. +bool RE::FullMatch(const char* str, const RE& re) { + return re.is_valid_ && MatchRegexAnywhere(re.full_pattern_, str); +} + +// Returns true iff regular expression re matches a substring of str +// (including str itself). +bool RE::PartialMatch(const char* str, const RE& re) { + return re.is_valid_ && MatchRegexAnywhere(re.pattern_, str); +} + +// Initializes an RE from its string representation. +void RE::Init(const char* regex) { + pattern_ = full_pattern_ = NULL; + if (regex != NULL) { + pattern_ = posix::StrDup(regex); + } + + is_valid_ = ValidateRegex(regex); + if (!is_valid_) { + // No need to calculate the full pattern when the regex is invalid. + return; + } + + const size_t len = strlen(regex); + // Reserves enough bytes to hold the regular expression used for a + // full match: we need space to prepend a '^', append a '$', and + // terminate the string with '\0'. + char* buffer = static_cast(malloc(len + 3)); + full_pattern_ = buffer; + + if (*regex != '^') + *buffer++ = '^'; // Makes sure full_pattern_ starts with '^'. + + // We don't use snprintf or strncpy, as they trigger a warning when + // compiled with VC++ 8.0. + memcpy(buffer, regex, len); + buffer += len; + + if (len == 0 || regex[len - 1] != '$') + *buffer++ = '$'; // Makes sure full_pattern_ ends with '$'. + + *buffer = '\0'; +} + +#endif // GTEST_USES_POSIX_RE + +const char kUnknownFile[] = "unknown file"; + +// Formats a source file path and a line number as they would appear +// in an error message from the compiler used to compile this code. +GTEST_API_ ::std::string FormatFileLocation(const char* file, int line) { + const std::string file_name(file == NULL ? kUnknownFile : file); + + if (line < 0) { + return file_name + ":"; + } +#ifdef _MSC_VER + return file_name + "(" + StreamableToString(line) + "):"; +#else + return file_name + ":" + StreamableToString(line) + ":"; +#endif // _MSC_VER +} + +// Formats a file location for compiler-independent XML output. +// Although this function is not platform dependent, we put it next to +// FormatFileLocation in order to contrast the two functions. +// Note that FormatCompilerIndependentFileLocation() does NOT append colon +// to the file location it produces, unlike FormatFileLocation(). +GTEST_API_ ::std::string FormatCompilerIndependentFileLocation( + const char* file, int line) { + const std::string file_name(file == NULL ? kUnknownFile : file); + + if (line < 0) + return file_name; + else + return file_name + ":" + StreamableToString(line); +} + + +GTestLog::GTestLog(GTestLogSeverity severity, const char* file, int line) + : severity_(severity) { + const char* const marker = + severity == GTEST_INFO ? "[ INFO ]" : + severity == GTEST_WARNING ? "[WARNING]" : + severity == GTEST_ERROR ? "[ ERROR ]" : "[ FATAL ]"; + GetStream() << ::std::endl << marker << " " + << FormatFileLocation(file, line).c_str() << ": "; +} + +// Flushes the buffers and, if severity is GTEST_FATAL, aborts the program. +GTestLog::~GTestLog() { + GetStream() << ::std::endl; + if (severity_ == GTEST_FATAL) { + fflush(stderr); + posix::Abort(); + } +} +// Disable Microsoft deprecation warnings for POSIX functions called from +// this class (creat, dup, dup2, and close) +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable: 4996) +#endif // _MSC_VER + +#if GTEST_HAS_STREAM_REDIRECTION + +// Object that captures an output stream (stdout/stderr). +class CapturedStream { + public: + // The ctor redirects the stream to a temporary file. + explicit CapturedStream(int fd) : fd_(fd), uncaptured_fd_(dup(fd)) { +# if GTEST_OS_WINDOWS + char temp_dir_path[MAX_PATH + 1] = { '\0' }; // NOLINT + char temp_file_path[MAX_PATH + 1] = { '\0' }; // NOLINT + + ::GetTempPathA(sizeof(temp_dir_path), temp_dir_path); + const UINT success = ::GetTempFileNameA(temp_dir_path, + "gtest_redir", + 0, // Generate unique file name. + temp_file_path); + GTEST_CHECK_(success != 0) + << "Unable to create a temporary file in " << temp_dir_path; + const int captured_fd = creat(temp_file_path, _S_IREAD | _S_IWRITE); + GTEST_CHECK_(captured_fd != -1) << "Unable to open temporary file " + << temp_file_path; + filename_ = temp_file_path; +# else + // There's no guarantee that a test has write access to the current + // directory, so we create the temporary file in the /tmp directory + // instead. We use /tmp on most systems, and /sdcard on Android. + // That's because Android doesn't have /tmp. +# if GTEST_OS_LINUX_ANDROID + // Note: Android applications are expected to call the framework's + // Context.getExternalStorageDirectory() method through JNI to get + // the location of the world-writable SD Card directory. However, + // this requires a Context handle, which cannot be retrieved + // globally from native code. Doing so also precludes running the + // code as part of a regular standalone executable, which doesn't + // run in a Dalvik process (e.g. when running it through 'adb shell'). + // + // The location /sdcard is directly accessible from native code + // and is the only location (unofficially) supported by the Android + // team. It's generally a symlink to the real SD Card mount point + // which can be /mnt/sdcard, /mnt/sdcard0, /system/media/sdcard, or + // other OEM-customized locations. Never rely on these, and always + // use /sdcard. + char name_template[] = "/sdcard/gtest_captured_stream.XXXXXX"; +# else + char name_template[] = "/tmp/captured_stream.XXXXXX"; +# endif // GTEST_OS_LINUX_ANDROID + const int captured_fd = mkstemp(name_template); + filename_ = name_template; +# endif // GTEST_OS_WINDOWS + fflush(NULL); + dup2(captured_fd, fd_); + close(captured_fd); + } + + ~CapturedStream() { + remove(filename_.c_str()); + } + + std::string GetCapturedString() { + if (uncaptured_fd_ != -1) { + // Restores the original stream. + fflush(NULL); + dup2(uncaptured_fd_, fd_); + close(uncaptured_fd_); + uncaptured_fd_ = -1; + } + + FILE* const file = posix::FOpen(filename_.c_str(), "r"); + const std::string content = ReadEntireFile(file); + posix::FClose(file); + return content; + } + + private: + // Reads the entire content of a file as an std::string. + static std::string ReadEntireFile(FILE* file); + + // Returns the size (in bytes) of a file. + static size_t GetFileSize(FILE* file); + + const int fd_; // A stream to capture. + int uncaptured_fd_; + // Name of the temporary file holding the stderr output. + ::std::string filename_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(CapturedStream); +}; + +// Returns the size (in bytes) of a file. +size_t CapturedStream::GetFileSize(FILE* file) { + fseek(file, 0, SEEK_END); + return static_cast(ftell(file)); +} + +// Reads the entire content of a file as a string. +std::string CapturedStream::ReadEntireFile(FILE* file) { + const size_t file_size = GetFileSize(file); + char* const buffer = new char[file_size]; + + size_t bytes_last_read = 0; // # of bytes read in the last fread() + size_t bytes_read = 0; // # of bytes read so far + + fseek(file, 0, SEEK_SET); + + // Keeps reading the file until we cannot read further or the + // pre-determined file size is reached. + do { + bytes_last_read = fread(buffer+bytes_read, 1, file_size-bytes_read, file); + bytes_read += bytes_last_read; + } while (bytes_last_read > 0 && bytes_read < file_size); + + const std::string content(buffer, bytes_read); + delete[] buffer; + + return content; +} + +# ifdef _MSC_VER +# pragma warning(pop) +# endif // _MSC_VER + +static CapturedStream* g_captured_stderr = NULL; +static CapturedStream* g_captured_stdout = NULL; + +// Starts capturing an output stream (stdout/stderr). +void CaptureStream(int fd, const char* stream_name, CapturedStream** stream) { + if (*stream != NULL) { + GTEST_LOG_(FATAL) << "Only one " << stream_name + << " capturer can exist at a time."; + } + *stream = new CapturedStream(fd); +} + +// Stops capturing the output stream and returns the captured string. +std::string GetCapturedStream(CapturedStream** captured_stream) { + const std::string content = (*captured_stream)->GetCapturedString(); + + delete *captured_stream; + *captured_stream = NULL; + + return content; +} + +// Starts capturing stdout. +void CaptureStdout() { + CaptureStream(kStdOutFileno, "stdout", &g_captured_stdout); +} + +// Starts capturing stderr. +void CaptureStderr() { + CaptureStream(kStdErrFileno, "stderr", &g_captured_stderr); +} + +// Stops capturing stdout and returns the captured string. +std::string GetCapturedStdout() { + return GetCapturedStream(&g_captured_stdout); +} + +// Stops capturing stderr and returns the captured string. +std::string GetCapturedStderr() { + return GetCapturedStream(&g_captured_stderr); +} + +#endif // GTEST_HAS_STREAM_REDIRECTION + +#if GTEST_HAS_DEATH_TEST + +// A copy of all command line arguments. Set by InitGoogleTest(). +::std::vector g_argvs; + +static const ::std::vector* g_injected_test_argvs = + NULL; // Owned. + +void SetInjectableArgvs(const ::std::vector* argvs) { + if (g_injected_test_argvs != argvs) + delete g_injected_test_argvs; + g_injected_test_argvs = argvs; +} + +const ::std::vector& GetInjectableArgvs() { + if (g_injected_test_argvs != NULL) { + return *g_injected_test_argvs; + } + return g_argvs; +} +#endif // GTEST_HAS_DEATH_TEST + +#if GTEST_OS_WINDOWS_MOBILE +namespace posix { +void Abort() { + DebugBreak(); + TerminateProcess(GetCurrentProcess(), 1); +} +} // namespace posix +#endif // GTEST_OS_WINDOWS_MOBILE + +// Returns the name of the environment variable corresponding to the +// given flag. For example, FlagToEnvVar("foo") will return +// "GTEST_FOO" in the open-source version. +static std::string FlagToEnvVar(const char* flag) { + const std::string full_flag = + (Message() << GTEST_FLAG_PREFIX_ << flag).GetString(); + + Message env_var; + for (size_t i = 0; i != full_flag.length(); i++) { + env_var << ToUpper(full_flag.c_str()[i]); + } + + return env_var.GetString(); +} + +// Parses 'str' for a 32-bit signed integer. If successful, writes +// the result to *value and returns true; otherwise leaves *value +// unchanged and returns false. +bool ParseInt32(const Message& src_text, const char* str, Int32* value) { + // Parses the environment variable as a decimal integer. + char* end = NULL; + const long long_value = strtol(str, &end, 10); // NOLINT + + // Has strtol() consumed all characters in the string? + if (*end != '\0') { + // No - an invalid character was encountered. + Message msg; + msg << "WARNING: " << src_text + << " is expected to be a 32-bit integer, but actually" + << " has value \"" << str << "\".\n"; + printf("%s", msg.GetString().c_str()); + fflush(stdout); + return false; + } + + // Is the parsed value in the range of an Int32? + const Int32 result = static_cast(long_value); + if (long_value == LONG_MAX || long_value == LONG_MIN || + // The parsed value overflows as a long. (strtol() returns + // LONG_MAX or LONG_MIN when the input overflows.) + result != long_value + // The parsed value overflows as an Int32. + ) { + Message msg; + msg << "WARNING: " << src_text + << " is expected to be a 32-bit integer, but actually" + << " has value " << str << ", which overflows.\n"; + printf("%s", msg.GetString().c_str()); + fflush(stdout); + return false; + } + + *value = result; + return true; +} + +// Reads and returns the Boolean environment variable corresponding to +// the given flag; if it's not set, returns default_value. +// +// The value is considered true iff it's not "0". +bool BoolFromGTestEnv(const char* flag, bool default_value) { + const std::string env_var = FlagToEnvVar(flag); + const char* const string_value = posix::GetEnv(env_var.c_str()); + return string_value == NULL ? + default_value : strcmp(string_value, "0") != 0; +} + +// Reads and returns a 32-bit integer stored in the environment +// variable corresponding to the given flag; if it isn't set or +// doesn't represent a valid 32-bit integer, returns default_value. +Int32 Int32FromGTestEnv(const char* flag, Int32 default_value) { + const std::string env_var = FlagToEnvVar(flag); + const char* const string_value = posix::GetEnv(env_var.c_str()); + if (string_value == NULL) { + // The environment variable is not set. + return default_value; + } + + Int32 result = default_value; + if (!ParseInt32(Message() << "Environment variable " << env_var, + string_value, &result)) { + printf("The default value %s is used.\n", + (Message() << default_value).GetString().c_str()); + fflush(stdout); + return default_value; + } + + return result; +} + +// Reads and returns the string environment variable corresponding to +// the given flag; if it's not set, returns default_value. +const char* StringFromGTestEnv(const char* flag, const char* default_value) { + const std::string env_var = FlagToEnvVar(flag); + const char* const value = posix::GetEnv(env_var.c_str()); + return value == NULL ? default_value : value; +} + +} // namespace internal +} // namespace testing diff --git a/vendor/gmock-1.7.0/gtest/src/gtest-printers.cc b/vendor/gmock-1.7.0/gtest/src/gtest-printers.cc new file mode 100644 index 0000000000..75fa408100 --- /dev/null +++ b/vendor/gmock-1.7.0/gtest/src/gtest-printers.cc @@ -0,0 +1,363 @@ +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) + +// Google Test - The Google C++ Testing Framework +// +// This file implements a universal value printer that can print a +// value of any type T: +// +// void ::testing::internal::UniversalPrinter::Print(value, ostream_ptr); +// +// It uses the << operator when possible, and prints the bytes in the +// object otherwise. A user can override its behavior for a class +// type Foo by defining either operator<<(::std::ostream&, const Foo&) +// or void PrintTo(const Foo&, ::std::ostream*) in the namespace that +// defines Foo. + +#include "gtest/gtest-printers.h" +#include +#include +#include // NOLINT +#include +#include "gtest/internal/gtest-port.h" + +namespace testing { + +namespace { + +using ::std::ostream; + +// Prints a segment of bytes in the given object. +void PrintByteSegmentInObjectTo(const unsigned char* obj_bytes, size_t start, + size_t count, ostream* os) { + char text[5] = ""; + for (size_t i = 0; i != count; i++) { + const size_t j = start + i; + if (i != 0) { + // Organizes the bytes into groups of 2 for easy parsing by + // human. + if ((j % 2) == 0) + *os << ' '; + else + *os << '-'; + } + GTEST_SNPRINTF_(text, sizeof(text), "%02X", obj_bytes[j]); + *os << text; + } +} + +// Prints the bytes in the given value to the given ostream. +void PrintBytesInObjectToImpl(const unsigned char* obj_bytes, size_t count, + ostream* os) { + // Tells the user how big the object is. + *os << count << "-byte object <"; + + const size_t kThreshold = 132; + const size_t kChunkSize = 64; + // If the object size is bigger than kThreshold, we'll have to omit + // some details by printing only the first and the last kChunkSize + // bytes. + // TODO(wan): let the user control the threshold using a flag. + if (count < kThreshold) { + PrintByteSegmentInObjectTo(obj_bytes, 0, count, os); + } else { + PrintByteSegmentInObjectTo(obj_bytes, 0, kChunkSize, os); + *os << " ... "; + // Rounds up to 2-byte boundary. + const size_t resume_pos = (count - kChunkSize + 1)/2*2; + PrintByteSegmentInObjectTo(obj_bytes, resume_pos, count - resume_pos, os); + } + *os << ">"; +} + +} // namespace + +namespace internal2 { + +// Delegates to PrintBytesInObjectToImpl() to print the bytes in the +// given object. The delegation simplifies the implementation, which +// uses the << operator and thus is easier done outside of the +// ::testing::internal namespace, which contains a << operator that +// sometimes conflicts with the one in STL. +void PrintBytesInObjectTo(const unsigned char* obj_bytes, size_t count, + ostream* os) { + PrintBytesInObjectToImpl(obj_bytes, count, os); +} + +} // namespace internal2 + +namespace internal { + +// Depending on the value of a char (or wchar_t), we print it in one +// of three formats: +// - as is if it's a printable ASCII (e.g. 'a', '2', ' '), +// - as a hexidecimal escape sequence (e.g. '\x7F'), or +// - as a special escape sequence (e.g. '\r', '\n'). +enum CharFormat { + kAsIs, + kHexEscape, + kSpecialEscape +}; + +// Returns true if c is a printable ASCII character. We test the +// value of c directly instead of calling isprint(), which is buggy on +// Windows Mobile. +inline bool IsPrintableAscii(wchar_t c) { + return 0x20 <= c && c <= 0x7E; +} + +// Prints a wide or narrow char c as a character literal without the +// quotes, escaping it when necessary; returns how c was formatted. +// The template argument UnsignedChar is the unsigned version of Char, +// which is the type of c. +template +static CharFormat PrintAsCharLiteralTo(Char c, ostream* os) { + switch (static_cast(c)) { + case L'\0': + *os << "\\0"; + break; + case L'\'': + *os << "\\'"; + break; + case L'\\': + *os << "\\\\"; + break; + case L'\a': + *os << "\\a"; + break; + case L'\b': + *os << "\\b"; + break; + case L'\f': + *os << "\\f"; + break; + case L'\n': + *os << "\\n"; + break; + case L'\r': + *os << "\\r"; + break; + case L'\t': + *os << "\\t"; + break; + case L'\v': + *os << "\\v"; + break; + default: + if (IsPrintableAscii(c)) { + *os << static_cast(c); + return kAsIs; + } else { + *os << "\\x" + String::FormatHexInt(static_cast(c)); + return kHexEscape; + } + } + return kSpecialEscape; +} + +// Prints a wchar_t c as if it's part of a string literal, escaping it when +// necessary; returns how c was formatted. +static CharFormat PrintAsStringLiteralTo(wchar_t c, ostream* os) { + switch (c) { + case L'\'': + *os << "'"; + return kAsIs; + case L'"': + *os << "\\\""; + return kSpecialEscape; + default: + return PrintAsCharLiteralTo(c, os); + } +} + +// Prints a char c as if it's part of a string literal, escaping it when +// necessary; returns how c was formatted. +static CharFormat PrintAsStringLiteralTo(char c, ostream* os) { + return PrintAsStringLiteralTo( + static_cast(static_cast(c)), os); +} + +// Prints a wide or narrow character c and its code. '\0' is printed +// as "'\\0'", other unprintable characters are also properly escaped +// using the standard C++ escape sequence. The template argument +// UnsignedChar is the unsigned version of Char, which is the type of c. +template +void PrintCharAndCodeTo(Char c, ostream* os) { + // First, print c as a literal in the most readable form we can find. + *os << ((sizeof(c) > 1) ? "L'" : "'"); + const CharFormat format = PrintAsCharLiteralTo(c, os); + *os << "'"; + + // To aid user debugging, we also print c's code in decimal, unless + // it's 0 (in which case c was printed as '\\0', making the code + // obvious). + if (c == 0) + return; + *os << " (" << static_cast(c); + + // For more convenience, we print c's code again in hexidecimal, + // unless c was already printed in the form '\x##' or the code is in + // [1, 9]. + if (format == kHexEscape || (1 <= c && c <= 9)) { + // Do nothing. + } else { + *os << ", 0x" << String::FormatHexInt(static_cast(c)); + } + *os << ")"; +} + +void PrintTo(unsigned char c, ::std::ostream* os) { + PrintCharAndCodeTo(c, os); +} +void PrintTo(signed char c, ::std::ostream* os) { + PrintCharAndCodeTo(c, os); +} + +// Prints a wchar_t as a symbol if it is printable or as its internal +// code otherwise and also as its code. L'\0' is printed as "L'\\0'". +void PrintTo(wchar_t wc, ostream* os) { + PrintCharAndCodeTo(wc, os); +} + +// Prints the given array of characters to the ostream. CharType must be either +// char or wchar_t. +// The array starts at begin, the length is len, it may include '\0' characters +// and may not be NUL-terminated. +template +static void PrintCharsAsStringTo( + const CharType* begin, size_t len, ostream* os) { + const char* const kQuoteBegin = sizeof(CharType) == 1 ? "\"" : "L\""; + *os << kQuoteBegin; + bool is_previous_hex = false; + for (size_t index = 0; index < len; ++index) { + const CharType cur = begin[index]; + if (is_previous_hex && IsXDigit(cur)) { + // Previous character is of '\x..' form and this character can be + // interpreted as another hexadecimal digit in its number. Break string to + // disambiguate. + *os << "\" " << kQuoteBegin; + } + is_previous_hex = PrintAsStringLiteralTo(cur, os) == kHexEscape; + } + *os << "\""; +} + +// Prints a (const) char/wchar_t array of 'len' elements, starting at address +// 'begin'. CharType must be either char or wchar_t. +template +static void UniversalPrintCharArray( + const CharType* begin, size_t len, ostream* os) { + // The code + // const char kFoo[] = "foo"; + // generates an array of 4, not 3, elements, with the last one being '\0'. + // + // Therefore when printing a char array, we don't print the last element if + // it's '\0', such that the output matches the string literal as it's + // written in the source code. + if (len > 0 && begin[len - 1] == '\0') { + PrintCharsAsStringTo(begin, len - 1, os); + return; + } + + // If, however, the last element in the array is not '\0', e.g. + // const char kFoo[] = { 'f', 'o', 'o' }; + // we must print the entire array. We also print a message to indicate + // that the array is not NUL-terminated. + PrintCharsAsStringTo(begin, len, os); + *os << " (no terminating NUL)"; +} + +// Prints a (const) char array of 'len' elements, starting at address 'begin'. +void UniversalPrintArray(const char* begin, size_t len, ostream* os) { + UniversalPrintCharArray(begin, len, os); +} + +// Prints a (const) wchar_t array of 'len' elements, starting at address +// 'begin'. +void UniversalPrintArray(const wchar_t* begin, size_t len, ostream* os) { + UniversalPrintCharArray(begin, len, os); +} + +// Prints the given C string to the ostream. +void PrintTo(const char* s, ostream* os) { + if (s == NULL) { + *os << "NULL"; + } else { + *os << ImplicitCast_(s) << " pointing to "; + PrintCharsAsStringTo(s, strlen(s), os); + } +} + +// MSVC compiler can be configured to define whar_t as a typedef +// of unsigned short. Defining an overload for const wchar_t* in that case +// would cause pointers to unsigned shorts be printed as wide strings, +// possibly accessing more memory than intended and causing invalid +// memory accesses. MSVC defines _NATIVE_WCHAR_T_DEFINED symbol when +// wchar_t is implemented as a native type. +#if !defined(_MSC_VER) || defined(_NATIVE_WCHAR_T_DEFINED) +// Prints the given wide C string to the ostream. +void PrintTo(const wchar_t* s, ostream* os) { + if (s == NULL) { + *os << "NULL"; + } else { + *os << ImplicitCast_(s) << " pointing to "; + PrintCharsAsStringTo(s, wcslen(s), os); + } +} +#endif // wchar_t is native + +// Prints a ::string object. +#if GTEST_HAS_GLOBAL_STRING +void PrintStringTo(const ::string& s, ostream* os) { + PrintCharsAsStringTo(s.data(), s.size(), os); +} +#endif // GTEST_HAS_GLOBAL_STRING + +void PrintStringTo(const ::std::string& s, ostream* os) { + PrintCharsAsStringTo(s.data(), s.size(), os); +} + +// Prints a ::wstring object. +#if GTEST_HAS_GLOBAL_WSTRING +void PrintWideStringTo(const ::wstring& s, ostream* os) { + PrintCharsAsStringTo(s.data(), s.size(), os); +} +#endif // GTEST_HAS_GLOBAL_WSTRING + +#if GTEST_HAS_STD_WSTRING +void PrintWideStringTo(const ::std::wstring& s, ostream* os) { + PrintCharsAsStringTo(s.data(), s.size(), os); +} +#endif // GTEST_HAS_STD_WSTRING + +} // namespace internal + +} // namespace testing diff --git a/vendor/gmock-1.7.0/gtest/src/gtest-test-part.cc b/vendor/gmock-1.7.0/gtest/src/gtest-test-part.cc new file mode 100644 index 0000000000..c60eef3ab3 --- /dev/null +++ b/vendor/gmock-1.7.0/gtest/src/gtest-test-part.cc @@ -0,0 +1,110 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: mheule@google.com (Markus Heule) +// +// The Google C++ Testing Framework (Google Test) + +#include "gtest/gtest-test-part.h" + +// Indicates that this translation unit is part of Google Test's +// implementation. It must come before gtest-internal-inl.h is +// included, or there will be a compiler error. This trick is to +// prevent a user from accidentally including gtest-internal-inl.h in +// his code. +#define GTEST_IMPLEMENTATION_ 1 +#include "src/gtest-internal-inl.h" +#undef GTEST_IMPLEMENTATION_ + +namespace testing { + +using internal::GetUnitTestImpl; + +// Gets the summary of the failure message by omitting the stack trace +// in it. +std::string TestPartResult::ExtractSummary(const char* message) { + const char* const stack_trace = strstr(message, internal::kStackTraceMarker); + return stack_trace == NULL ? message : + std::string(message, stack_trace); +} + +// Prints a TestPartResult object. +std::ostream& operator<<(std::ostream& os, const TestPartResult& result) { + return os + << result.file_name() << ":" << result.line_number() << ": " + << (result.type() == TestPartResult::kSuccess ? "Success" : + result.type() == TestPartResult::kFatalFailure ? "Fatal failure" : + "Non-fatal failure") << ":\n" + << result.message() << std::endl; +} + +// Appends a TestPartResult to the array. +void TestPartResultArray::Append(const TestPartResult& result) { + array_.push_back(result); +} + +// Returns the TestPartResult at the given index (0-based). +const TestPartResult& TestPartResultArray::GetTestPartResult(int index) const { + if (index < 0 || index >= size()) { + printf("\nInvalid index (%d) into TestPartResultArray.\n", index); + internal::posix::Abort(); + } + + return array_[index]; +} + +// Returns the number of TestPartResult objects in the array. +int TestPartResultArray::size() const { + return static_cast(array_.size()); +} + +namespace internal { + +HasNewFatalFailureHelper::HasNewFatalFailureHelper() + : has_new_fatal_failure_(false), + original_reporter_(GetUnitTestImpl()-> + GetTestPartResultReporterForCurrentThread()) { + GetUnitTestImpl()->SetTestPartResultReporterForCurrentThread(this); +} + +HasNewFatalFailureHelper::~HasNewFatalFailureHelper() { + GetUnitTestImpl()->SetTestPartResultReporterForCurrentThread( + original_reporter_); +} + +void HasNewFatalFailureHelper::ReportTestPartResult( + const TestPartResult& result) { + if (result.fatally_failed()) + has_new_fatal_failure_ = true; + original_reporter_->ReportTestPartResult(result); +} + +} // namespace internal + +} // namespace testing diff --git a/vendor/gmock-1.7.0/gtest/src/gtest-typed-test.cc b/vendor/gmock-1.7.0/gtest/src/gtest-typed-test.cc new file mode 100644 index 0000000000..f0079f407c --- /dev/null +++ b/vendor/gmock-1.7.0/gtest/src/gtest-typed-test.cc @@ -0,0 +1,110 @@ +// Copyright 2008 Google Inc. +// All Rights Reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) + +#include "gtest/gtest-typed-test.h" +#include "gtest/gtest.h" + +namespace testing { +namespace internal { + +#if GTEST_HAS_TYPED_TEST_P + +// Skips to the first non-space char in str. Returns an empty string if str +// contains only whitespace characters. +static const char* SkipSpaces(const char* str) { + while (IsSpace(*str)) + str++; + return str; +} + +// Verifies that registered_tests match the test names in +// defined_test_names_; returns registered_tests if successful, or +// aborts the program otherwise. +const char* TypedTestCasePState::VerifyRegisteredTestNames( + const char* file, int line, const char* registered_tests) { + typedef ::std::set::const_iterator DefinedTestIter; + registered_ = true; + + // Skip initial whitespace in registered_tests since some + // preprocessors prefix stringizied literals with whitespace. + registered_tests = SkipSpaces(registered_tests); + + Message errors; + ::std::set tests; + for (const char* names = registered_tests; names != NULL; + names = SkipComma(names)) { + const std::string name = GetPrefixUntilComma(names); + if (tests.count(name) != 0) { + errors << "Test " << name << " is listed more than once.\n"; + continue; + } + + bool found = false; + for (DefinedTestIter it = defined_test_names_.begin(); + it != defined_test_names_.end(); + ++it) { + if (name == *it) { + found = true; + break; + } + } + + if (found) { + tests.insert(name); + } else { + errors << "No test named " << name + << " can be found in this test case.\n"; + } + } + + for (DefinedTestIter it = defined_test_names_.begin(); + it != defined_test_names_.end(); + ++it) { + if (tests.count(*it) == 0) { + errors << "You forgot to list test " << *it << ".\n"; + } + } + + const std::string& errors_str = errors.GetString(); + if (errors_str != "") { + fprintf(stderr, "%s %s", FormatFileLocation(file, line).c_str(), + errors_str.c_str()); + fflush(stderr); + posix::Abort(); + } + + return registered_tests; +} + +#endif // GTEST_HAS_TYPED_TEST_P + +} // namespace internal +} // namespace testing diff --git a/vendor/gmock-1.7.0/gtest/src/gtest.cc b/vendor/gmock-1.7.0/gtest/src/gtest.cc new file mode 100644 index 0000000000..6de53dd019 --- /dev/null +++ b/vendor/gmock-1.7.0/gtest/src/gtest.cc @@ -0,0 +1,5015 @@ +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) +// +// The Google C++ Testing Framework (Google Test) + +#include "gtest/gtest.h" +#include "gtest/gtest-spi.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include // NOLINT +#include +#include + +#if GTEST_OS_LINUX + +// TODO(kenton@google.com): Use autoconf to detect availability of +// gettimeofday(). +# define GTEST_HAS_GETTIMEOFDAY_ 1 + +# include // NOLINT +# include // NOLINT +# include // NOLINT +// Declares vsnprintf(). This header is not available on Windows. +# include // NOLINT +# include // NOLINT +# include // NOLINT +# include // NOLINT +# include + +#elif GTEST_OS_SYMBIAN +# define GTEST_HAS_GETTIMEOFDAY_ 1 +# include // NOLINT + +#elif GTEST_OS_ZOS +# define GTEST_HAS_GETTIMEOFDAY_ 1 +# include // NOLINT + +// On z/OS we additionally need strings.h for strcasecmp. +# include // NOLINT + +#elif GTEST_OS_WINDOWS_MOBILE // We are on Windows CE. + +# include // NOLINT + +#elif GTEST_OS_WINDOWS // We are on Windows proper. + +# include // NOLINT +# include // NOLINT +# include // NOLINT +# include // NOLINT + +# if GTEST_OS_WINDOWS_MINGW +// MinGW has gettimeofday() but not _ftime64(). +// TODO(kenton@google.com): Use autoconf to detect availability of +// gettimeofday(). +// TODO(kenton@google.com): There are other ways to get the time on +// Windows, like GetTickCount() or GetSystemTimeAsFileTime(). MinGW +// supports these. consider using them instead. +# define GTEST_HAS_GETTIMEOFDAY_ 1 +# include // NOLINT +# endif // GTEST_OS_WINDOWS_MINGW + +// cpplint thinks that the header is already included, so we want to +// silence it. +# include // NOLINT + +#else + +// Assume other platforms have gettimeofday(). +// TODO(kenton@google.com): Use autoconf to detect availability of +// gettimeofday(). +# define GTEST_HAS_GETTIMEOFDAY_ 1 + +// cpplint thinks that the header is already included, so we want to +// silence it. +# include // NOLINT +# include // NOLINT + +#endif // GTEST_OS_LINUX + +#if GTEST_HAS_EXCEPTIONS +# include +#endif + +#if GTEST_CAN_STREAM_RESULTS_ +# include // NOLINT +# include // NOLINT +#endif + +// Indicates that this translation unit is part of Google Test's +// implementation. It must come before gtest-internal-inl.h is +// included, or there will be a compiler error. This trick is to +// prevent a user from accidentally including gtest-internal-inl.h in +// his code. +#define GTEST_IMPLEMENTATION_ 1 +#include "src/gtest-internal-inl.h" +#undef GTEST_IMPLEMENTATION_ + +#if GTEST_OS_WINDOWS +# define vsnprintf _vsnprintf +#endif // GTEST_OS_WINDOWS + +namespace testing { + +using internal::CountIf; +using internal::ForEach; +using internal::GetElementOr; +using internal::Shuffle; + +// Constants. + +// A test whose test case name or test name matches this filter is +// disabled and not run. +static const char kDisableTestFilter[] = "DISABLED_*:*/DISABLED_*"; + +// A test case whose name matches this filter is considered a death +// test case and will be run before test cases whose name doesn't +// match this filter. +static const char kDeathTestCaseFilter[] = "*DeathTest:*DeathTest/*"; + +// A test filter that matches everything. +static const char kUniversalFilter[] = "*"; + +// The default output file for XML output. +static const char kDefaultOutputFile[] = "test_detail.xml"; + +// The environment variable name for the test shard index. +static const char kTestShardIndex[] = "GTEST_SHARD_INDEX"; +// The environment variable name for the total number of test shards. +static const char kTestTotalShards[] = "GTEST_TOTAL_SHARDS"; +// The environment variable name for the test shard status file. +static const char kTestShardStatusFile[] = "GTEST_SHARD_STATUS_FILE"; + +namespace internal { + +// The text used in failure messages to indicate the start of the +// stack trace. +const char kStackTraceMarker[] = "\nStack trace:\n"; + +// g_help_flag is true iff the --help flag or an equivalent form is +// specified on the command line. +bool g_help_flag = false; + +} // namespace internal + +static const char* GetDefaultFilter() { + return kUniversalFilter; +} + +GTEST_DEFINE_bool_( + also_run_disabled_tests, + internal::BoolFromGTestEnv("also_run_disabled_tests", false), + "Run disabled tests too, in addition to the tests normally being run."); + +GTEST_DEFINE_bool_( + break_on_failure, + internal::BoolFromGTestEnv("break_on_failure", false), + "True iff a failed assertion should be a debugger break-point."); + +GTEST_DEFINE_bool_( + catch_exceptions, + internal::BoolFromGTestEnv("catch_exceptions", true), + "True iff " GTEST_NAME_ + " should catch exceptions and treat them as test failures."); + +GTEST_DEFINE_string_( + color, + internal::StringFromGTestEnv("color", "auto"), + "Whether to use colors in the output. Valid values: yes, no, " + "and auto. 'auto' means to use colors if the output is " + "being sent to a terminal and the TERM environment variable " + "is set to a terminal type that supports colors."); + +GTEST_DEFINE_string_( + filter, + internal::StringFromGTestEnv("filter", GetDefaultFilter()), + "A colon-separated list of glob (not regex) patterns " + "for filtering the tests to run, optionally followed by a " + "'-' and a : separated list of negative patterns (tests to " + "exclude). A test is run if it matches one of the positive " + "patterns and does not match any of the negative patterns."); + +GTEST_DEFINE_bool_(list_tests, false, + "List all tests without running them."); + +GTEST_DEFINE_string_( + output, + internal::StringFromGTestEnv("output", ""), + "A format (currently must be \"xml\"), optionally followed " + "by a colon and an output file name or directory. A directory " + "is indicated by a trailing pathname separator. " + "Examples: \"xml:filename.xml\", \"xml::directoryname/\". " + "If a directory is specified, output files will be created " + "within that directory, with file-names based on the test " + "executable's name and, if necessary, made unique by adding " + "digits."); + +GTEST_DEFINE_bool_( + print_time, + internal::BoolFromGTestEnv("print_time", true), + "True iff " GTEST_NAME_ + " should display elapsed time in text output."); + +GTEST_DEFINE_int32_( + random_seed, + internal::Int32FromGTestEnv("random_seed", 0), + "Random number seed to use when shuffling test orders. Must be in range " + "[1, 99999], or 0 to use a seed based on the current time."); + +GTEST_DEFINE_int32_( + repeat, + internal::Int32FromGTestEnv("repeat", 1), + "How many times to repeat each test. Specify a negative number " + "for repeating forever. Useful for shaking out flaky tests."); + +GTEST_DEFINE_bool_( + show_internal_stack_frames, false, + "True iff " GTEST_NAME_ " should include internal stack frames when " + "printing test failure stack traces."); + +GTEST_DEFINE_bool_( + shuffle, + internal::BoolFromGTestEnv("shuffle", false), + "True iff " GTEST_NAME_ + " should randomize tests' order on every run."); + +GTEST_DEFINE_int32_( + stack_trace_depth, + internal::Int32FromGTestEnv("stack_trace_depth", kMaxStackTraceDepth), + "The maximum number of stack frames to print when an " + "assertion fails. The valid range is 0 through 100, inclusive."); + +GTEST_DEFINE_string_( + stream_result_to, + internal::StringFromGTestEnv("stream_result_to", ""), + "This flag specifies the host name and the port number on which to stream " + "test results. Example: \"localhost:555\". The flag is effective only on " + "Linux."); + +GTEST_DEFINE_bool_( + throw_on_failure, + internal::BoolFromGTestEnv("throw_on_failure", false), + "When this flag is specified, a failed assertion will throw an exception " + "if exceptions are enabled or exit the program with a non-zero code " + "otherwise."); + +namespace internal { + +// Generates a random number from [0, range), using a Linear +// Congruential Generator (LCG). Crashes if 'range' is 0 or greater +// than kMaxRange. +UInt32 Random::Generate(UInt32 range) { + // These constants are the same as are used in glibc's rand(3). + state_ = (1103515245U*state_ + 12345U) % kMaxRange; + + GTEST_CHECK_(range > 0) + << "Cannot generate a number in the range [0, 0)."; + GTEST_CHECK_(range <= kMaxRange) + << "Generation of a number in [0, " << range << ") was requested, " + << "but this can only generate numbers in [0, " << kMaxRange << ")."; + + // Converting via modulus introduces a bit of downward bias, but + // it's simple, and a linear congruential generator isn't too good + // to begin with. + return state_ % range; +} + +// GTestIsInitialized() returns true iff the user has initialized +// Google Test. Useful for catching the user mistake of not initializing +// Google Test before calling RUN_ALL_TESTS(). +// +// A user must call testing::InitGoogleTest() to initialize Google +// Test. g_init_gtest_count is set to the number of times +// InitGoogleTest() has been called. We don't protect this variable +// under a mutex as it is only accessed in the main thread. +GTEST_API_ int g_init_gtest_count = 0; +static bool GTestIsInitialized() { return g_init_gtest_count != 0; } + +// Iterates over a vector of TestCases, keeping a running sum of the +// results of calling a given int-returning method on each. +// Returns the sum. +static int SumOverTestCaseList(const std::vector& case_list, + int (TestCase::*method)() const) { + int sum = 0; + for (size_t i = 0; i < case_list.size(); i++) { + sum += (case_list[i]->*method)(); + } + return sum; +} + +// Returns true iff the test case passed. +static bool TestCasePassed(const TestCase* test_case) { + return test_case->should_run() && test_case->Passed(); +} + +// Returns true iff the test case failed. +static bool TestCaseFailed(const TestCase* test_case) { + return test_case->should_run() && test_case->Failed(); +} + +// Returns true iff test_case contains at least one test that should +// run. +static bool ShouldRunTestCase(const TestCase* test_case) { + return test_case->should_run(); +} + +// AssertHelper constructor. +AssertHelper::AssertHelper(TestPartResult::Type type, + const char* file, + int line, + const char* message) + : data_(new AssertHelperData(type, file, line, message)) { +} + +AssertHelper::~AssertHelper() { + delete data_; +} + +// Message assignment, for assertion streaming support. +void AssertHelper::operator=(const Message& message) const { + UnitTest::GetInstance()-> + AddTestPartResult(data_->type, data_->file, data_->line, + AppendUserMessage(data_->message, message), + UnitTest::GetInstance()->impl() + ->CurrentOsStackTraceExceptTop(1) + // Skips the stack frame for this function itself. + ); // NOLINT +} + +// Mutex for linked pointers. +GTEST_API_ GTEST_DEFINE_STATIC_MUTEX_(g_linked_ptr_mutex); + +// Application pathname gotten in InitGoogleTest. +std::string g_executable_path; + +// Returns the current application's name, removing directory path if that +// is present. +FilePath GetCurrentExecutableName() { + FilePath result; + +#if GTEST_OS_WINDOWS + result.Set(FilePath(g_executable_path).RemoveExtension("exe")); +#else + result.Set(FilePath(g_executable_path)); +#endif // GTEST_OS_WINDOWS + + return result.RemoveDirectoryName(); +} + +// Functions for processing the gtest_output flag. + +// Returns the output format, or "" for normal printed output. +std::string UnitTestOptions::GetOutputFormat() { + const char* const gtest_output_flag = GTEST_FLAG(output).c_str(); + if (gtest_output_flag == NULL) return std::string(""); + + const char* const colon = strchr(gtest_output_flag, ':'); + return (colon == NULL) ? + std::string(gtest_output_flag) : + std::string(gtest_output_flag, colon - gtest_output_flag); +} + +// Returns the name of the requested output file, or the default if none +// was explicitly specified. +std::string UnitTestOptions::GetAbsolutePathToOutputFile() { + const char* const gtest_output_flag = GTEST_FLAG(output).c_str(); + if (gtest_output_flag == NULL) + return ""; + + const char* const colon = strchr(gtest_output_flag, ':'); + if (colon == NULL) + return internal::FilePath::ConcatPaths( + internal::FilePath( + UnitTest::GetInstance()->original_working_dir()), + internal::FilePath(kDefaultOutputFile)).string(); + + internal::FilePath output_name(colon + 1); + if (!output_name.IsAbsolutePath()) + // TODO(wan@google.com): on Windows \some\path is not an absolute + // path (as its meaning depends on the current drive), yet the + // following logic for turning it into an absolute path is wrong. + // Fix it. + output_name = internal::FilePath::ConcatPaths( + internal::FilePath(UnitTest::GetInstance()->original_working_dir()), + internal::FilePath(colon + 1)); + + if (!output_name.IsDirectory()) + return output_name.string(); + + internal::FilePath result(internal::FilePath::GenerateUniqueFileName( + output_name, internal::GetCurrentExecutableName(), + GetOutputFormat().c_str())); + return result.string(); +} + +// Returns true iff the wildcard pattern matches the string. The +// first ':' or '\0' character in pattern marks the end of it. +// +// This recursive algorithm isn't very efficient, but is clear and +// works well enough for matching test names, which are short. +bool UnitTestOptions::PatternMatchesString(const char *pattern, + const char *str) { + switch (*pattern) { + case '\0': + case ':': // Either ':' or '\0' marks the end of the pattern. + return *str == '\0'; + case '?': // Matches any single character. + return *str != '\0' && PatternMatchesString(pattern + 1, str + 1); + case '*': // Matches any string (possibly empty) of characters. + return (*str != '\0' && PatternMatchesString(pattern, str + 1)) || + PatternMatchesString(pattern + 1, str); + default: // Non-special character. Matches itself. + return *pattern == *str && + PatternMatchesString(pattern + 1, str + 1); + } +} + +bool UnitTestOptions::MatchesFilter( + const std::string& name, const char* filter) { + const char *cur_pattern = filter; + for (;;) { + if (PatternMatchesString(cur_pattern, name.c_str())) { + return true; + } + + // Finds the next pattern in the filter. + cur_pattern = strchr(cur_pattern, ':'); + + // Returns if no more pattern can be found. + if (cur_pattern == NULL) { + return false; + } + + // Skips the pattern separater (the ':' character). + cur_pattern++; + } +} + +// Returns true iff the user-specified filter matches the test case +// name and the test name. +bool UnitTestOptions::FilterMatchesTest(const std::string &test_case_name, + const std::string &test_name) { + const std::string& full_name = test_case_name + "." + test_name.c_str(); + + // Split --gtest_filter at '-', if there is one, to separate into + // positive filter and negative filter portions + const char* const p = GTEST_FLAG(filter).c_str(); + const char* const dash = strchr(p, '-'); + std::string positive; + std::string negative; + if (dash == NULL) { + positive = GTEST_FLAG(filter).c_str(); // Whole string is a positive filter + negative = ""; + } else { + positive = std::string(p, dash); // Everything up to the dash + negative = std::string(dash + 1); // Everything after the dash + if (positive.empty()) { + // Treat '-test1' as the same as '*-test1' + positive = kUniversalFilter; + } + } + + // A filter is a colon-separated list of patterns. It matches a + // test if any pattern in it matches the test. + return (MatchesFilter(full_name, positive.c_str()) && + !MatchesFilter(full_name, negative.c_str())); +} + +#if GTEST_HAS_SEH +// Returns EXCEPTION_EXECUTE_HANDLER if Google Test should handle the +// given SEH exception, or EXCEPTION_CONTINUE_SEARCH otherwise. +// This function is useful as an __except condition. +int UnitTestOptions::GTestShouldProcessSEH(DWORD exception_code) { + // Google Test should handle a SEH exception if: + // 1. the user wants it to, AND + // 2. this is not a breakpoint exception, AND + // 3. this is not a C++ exception (VC++ implements them via SEH, + // apparently). + // + // SEH exception code for C++ exceptions. + // (see http://support.microsoft.com/kb/185294 for more information). + const DWORD kCxxExceptionCode = 0xe06d7363; + + bool should_handle = true; + + if (!GTEST_FLAG(catch_exceptions)) + should_handle = false; + else if (exception_code == EXCEPTION_BREAKPOINT) + should_handle = false; + else if (exception_code == kCxxExceptionCode) + should_handle = false; + + return should_handle ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH; +} +#endif // GTEST_HAS_SEH + +} // namespace internal + +// The c'tor sets this object as the test part result reporter used by +// Google Test. The 'result' parameter specifies where to report the +// results. Intercepts only failures from the current thread. +ScopedFakeTestPartResultReporter::ScopedFakeTestPartResultReporter( + TestPartResultArray* result) + : intercept_mode_(INTERCEPT_ONLY_CURRENT_THREAD), + result_(result) { + Init(); +} + +// The c'tor sets this object as the test part result reporter used by +// Google Test. The 'result' parameter specifies where to report the +// results. +ScopedFakeTestPartResultReporter::ScopedFakeTestPartResultReporter( + InterceptMode intercept_mode, TestPartResultArray* result) + : intercept_mode_(intercept_mode), + result_(result) { + Init(); +} + +void ScopedFakeTestPartResultReporter::Init() { + internal::UnitTestImpl* const impl = internal::GetUnitTestImpl(); + if (intercept_mode_ == INTERCEPT_ALL_THREADS) { + old_reporter_ = impl->GetGlobalTestPartResultReporter(); + impl->SetGlobalTestPartResultReporter(this); + } else { + old_reporter_ = impl->GetTestPartResultReporterForCurrentThread(); + impl->SetTestPartResultReporterForCurrentThread(this); + } +} + +// The d'tor restores the test part result reporter used by Google Test +// before. +ScopedFakeTestPartResultReporter::~ScopedFakeTestPartResultReporter() { + internal::UnitTestImpl* const impl = internal::GetUnitTestImpl(); + if (intercept_mode_ == INTERCEPT_ALL_THREADS) { + impl->SetGlobalTestPartResultReporter(old_reporter_); + } else { + impl->SetTestPartResultReporterForCurrentThread(old_reporter_); + } +} + +// Increments the test part result count and remembers the result. +// This method is from the TestPartResultReporterInterface interface. +void ScopedFakeTestPartResultReporter::ReportTestPartResult( + const TestPartResult& result) { + result_->Append(result); +} + +namespace internal { + +// Returns the type ID of ::testing::Test. We should always call this +// instead of GetTypeId< ::testing::Test>() to get the type ID of +// testing::Test. This is to work around a suspected linker bug when +// using Google Test as a framework on Mac OS X. The bug causes +// GetTypeId< ::testing::Test>() to return different values depending +// on whether the call is from the Google Test framework itself or +// from user test code. GetTestTypeId() is guaranteed to always +// return the same value, as it always calls GetTypeId<>() from the +// gtest.cc, which is within the Google Test framework. +TypeId GetTestTypeId() { + return GetTypeId(); +} + +// The value of GetTestTypeId() as seen from within the Google Test +// library. This is solely for testing GetTestTypeId(). +extern const TypeId kTestTypeIdInGoogleTest = GetTestTypeId(); + +// This predicate-formatter checks that 'results' contains a test part +// failure of the given type and that the failure message contains the +// given substring. +AssertionResult HasOneFailure(const char* /* results_expr */, + const char* /* type_expr */, + const char* /* substr_expr */, + const TestPartResultArray& results, + TestPartResult::Type type, + const string& substr) { + const std::string expected(type == TestPartResult::kFatalFailure ? + "1 fatal failure" : + "1 non-fatal failure"); + Message msg; + if (results.size() != 1) { + msg << "Expected: " << expected << "\n" + << " Actual: " << results.size() << " failures"; + for (int i = 0; i < results.size(); i++) { + msg << "\n" << results.GetTestPartResult(i); + } + return AssertionFailure() << msg; + } + + const TestPartResult& r = results.GetTestPartResult(0); + if (r.type() != type) { + return AssertionFailure() << "Expected: " << expected << "\n" + << " Actual:\n" + << r; + } + + if (strstr(r.message(), substr.c_str()) == NULL) { + return AssertionFailure() << "Expected: " << expected << " containing \"" + << substr << "\"\n" + << " Actual:\n" + << r; + } + + return AssertionSuccess(); +} + +// The constructor of SingleFailureChecker remembers where to look up +// test part results, what type of failure we expect, and what +// substring the failure message should contain. +SingleFailureChecker:: SingleFailureChecker( + const TestPartResultArray* results, + TestPartResult::Type type, + const string& substr) + : results_(results), + type_(type), + substr_(substr) {} + +// The destructor of SingleFailureChecker verifies that the given +// TestPartResultArray contains exactly one failure that has the given +// type and contains the given substring. If that's not the case, a +// non-fatal failure will be generated. +SingleFailureChecker::~SingleFailureChecker() { + EXPECT_PRED_FORMAT3(HasOneFailure, *results_, type_, substr_); +} + +DefaultGlobalTestPartResultReporter::DefaultGlobalTestPartResultReporter( + UnitTestImpl* unit_test) : unit_test_(unit_test) {} + +void DefaultGlobalTestPartResultReporter::ReportTestPartResult( + const TestPartResult& result) { + unit_test_->current_test_result()->AddTestPartResult(result); + unit_test_->listeners()->repeater()->OnTestPartResult(result); +} + +DefaultPerThreadTestPartResultReporter::DefaultPerThreadTestPartResultReporter( + UnitTestImpl* unit_test) : unit_test_(unit_test) {} + +void DefaultPerThreadTestPartResultReporter::ReportTestPartResult( + const TestPartResult& result) { + unit_test_->GetGlobalTestPartResultReporter()->ReportTestPartResult(result); +} + +// Returns the global test part result reporter. +TestPartResultReporterInterface* +UnitTestImpl::GetGlobalTestPartResultReporter() { + internal::MutexLock lock(&global_test_part_result_reporter_mutex_); + return global_test_part_result_repoter_; +} + +// Sets the global test part result reporter. +void UnitTestImpl::SetGlobalTestPartResultReporter( + TestPartResultReporterInterface* reporter) { + internal::MutexLock lock(&global_test_part_result_reporter_mutex_); + global_test_part_result_repoter_ = reporter; +} + +// Returns the test part result reporter for the current thread. +TestPartResultReporterInterface* +UnitTestImpl::GetTestPartResultReporterForCurrentThread() { + return per_thread_test_part_result_reporter_.get(); +} + +// Sets the test part result reporter for the current thread. +void UnitTestImpl::SetTestPartResultReporterForCurrentThread( + TestPartResultReporterInterface* reporter) { + per_thread_test_part_result_reporter_.set(reporter); +} + +// Gets the number of successful test cases. +int UnitTestImpl::successful_test_case_count() const { + return CountIf(test_cases_, TestCasePassed); +} + +// Gets the number of failed test cases. +int UnitTestImpl::failed_test_case_count() const { + return CountIf(test_cases_, TestCaseFailed); +} + +// Gets the number of all test cases. +int UnitTestImpl::total_test_case_count() const { + return static_cast(test_cases_.size()); +} + +// Gets the number of all test cases that contain at least one test +// that should run. +int UnitTestImpl::test_case_to_run_count() const { + return CountIf(test_cases_, ShouldRunTestCase); +} + +// Gets the number of successful tests. +int UnitTestImpl::successful_test_count() const { + return SumOverTestCaseList(test_cases_, &TestCase::successful_test_count); +} + +// Gets the number of failed tests. +int UnitTestImpl::failed_test_count() const { + return SumOverTestCaseList(test_cases_, &TestCase::failed_test_count); +} + +// Gets the number of disabled tests that will be reported in the XML report. +int UnitTestImpl::reportable_disabled_test_count() const { + return SumOverTestCaseList(test_cases_, + &TestCase::reportable_disabled_test_count); +} + +// Gets the number of disabled tests. +int UnitTestImpl::disabled_test_count() const { + return SumOverTestCaseList(test_cases_, &TestCase::disabled_test_count); +} + +// Gets the number of tests to be printed in the XML report. +int UnitTestImpl::reportable_test_count() const { + return SumOverTestCaseList(test_cases_, &TestCase::reportable_test_count); +} + +// Gets the number of all tests. +int UnitTestImpl::total_test_count() const { + return SumOverTestCaseList(test_cases_, &TestCase::total_test_count); +} + +// Gets the number of tests that should run. +int UnitTestImpl::test_to_run_count() const { + return SumOverTestCaseList(test_cases_, &TestCase::test_to_run_count); +} + +// Returns the current OS stack trace as an std::string. +// +// The maximum number of stack frames to be included is specified by +// the gtest_stack_trace_depth flag. The skip_count parameter +// specifies the number of top frames to be skipped, which doesn't +// count against the number of frames to be included. +// +// For example, if Foo() calls Bar(), which in turn calls +// CurrentOsStackTraceExceptTop(1), Foo() will be included in the +// trace but Bar() and CurrentOsStackTraceExceptTop() won't. +std::string UnitTestImpl::CurrentOsStackTraceExceptTop(int skip_count) { + (void)skip_count; + return ""; +} + +// Returns the current time in milliseconds. +TimeInMillis GetTimeInMillis() { +#if GTEST_OS_WINDOWS_MOBILE || defined(__BORLANDC__) + // Difference between 1970-01-01 and 1601-01-01 in milliseconds. + // http://analogous.blogspot.com/2005/04/epoch.html + const TimeInMillis kJavaEpochToWinFileTimeDelta = + static_cast(116444736UL) * 100000UL; + const DWORD kTenthMicrosInMilliSecond = 10000; + + SYSTEMTIME now_systime; + FILETIME now_filetime; + ULARGE_INTEGER now_int64; + // TODO(kenton@google.com): Shouldn't this just use + // GetSystemTimeAsFileTime()? + GetSystemTime(&now_systime); + if (SystemTimeToFileTime(&now_systime, &now_filetime)) { + now_int64.LowPart = now_filetime.dwLowDateTime; + now_int64.HighPart = now_filetime.dwHighDateTime; + now_int64.QuadPart = (now_int64.QuadPart / kTenthMicrosInMilliSecond) - + kJavaEpochToWinFileTimeDelta; + return now_int64.QuadPart; + } + return 0; +#elif GTEST_OS_WINDOWS && !GTEST_HAS_GETTIMEOFDAY_ + __timeb64 now; + +# ifdef _MSC_VER + + // MSVC 8 deprecates _ftime64(), so we want to suppress warning 4996 + // (deprecated function) there. + // TODO(kenton@google.com): Use GetTickCount()? Or use + // SystemTimeToFileTime() +# pragma warning(push) // Saves the current warning state. +# pragma warning(disable:4996) // Temporarily disables warning 4996. + _ftime64(&now); +# pragma warning(pop) // Restores the warning state. +# else + + _ftime64(&now); + +# endif // _MSC_VER + + return static_cast(now.time) * 1000 + now.millitm; +#elif GTEST_HAS_GETTIMEOFDAY_ + struct timeval now; + gettimeofday(&now, NULL); + return static_cast(now.tv_sec) * 1000 + now.tv_usec / 1000; +#else +# error "Don't know how to get the current time on your system." +#endif +} + +// Utilities + +// class String. + +#if GTEST_OS_WINDOWS_MOBILE +// Creates a UTF-16 wide string from the given ANSI string, allocating +// memory using new. The caller is responsible for deleting the return +// value using delete[]. Returns the wide string, or NULL if the +// input is NULL. +LPCWSTR String::AnsiToUtf16(const char* ansi) { + if (!ansi) return NULL; + const int length = strlen(ansi); + const int unicode_length = + MultiByteToWideChar(CP_ACP, 0, ansi, length, + NULL, 0); + WCHAR* unicode = new WCHAR[unicode_length + 1]; + MultiByteToWideChar(CP_ACP, 0, ansi, length, + unicode, unicode_length); + unicode[unicode_length] = 0; + return unicode; +} + +// Creates an ANSI string from the given wide string, allocating +// memory using new. The caller is responsible for deleting the return +// value using delete[]. Returns the ANSI string, or NULL if the +// input is NULL. +const char* String::Utf16ToAnsi(LPCWSTR utf16_str) { + if (!utf16_str) return NULL; + const int ansi_length = + WideCharToMultiByte(CP_ACP, 0, utf16_str, -1, + NULL, 0, NULL, NULL); + char* ansi = new char[ansi_length + 1]; + WideCharToMultiByte(CP_ACP, 0, utf16_str, -1, + ansi, ansi_length, NULL, NULL); + ansi[ansi_length] = 0; + return ansi; +} + +#endif // GTEST_OS_WINDOWS_MOBILE + +// Compares two C strings. Returns true iff they have the same content. +// +// Unlike strcmp(), this function can handle NULL argument(s). A NULL +// C string is considered different to any non-NULL C string, +// including the empty string. +bool String::CStringEquals(const char * lhs, const char * rhs) { + if ( lhs == NULL ) return rhs == NULL; + + if ( rhs == NULL ) return false; + + return strcmp(lhs, rhs) == 0; +} + +#if GTEST_HAS_STD_WSTRING || GTEST_HAS_GLOBAL_WSTRING + +// Converts an array of wide chars to a narrow string using the UTF-8 +// encoding, and streams the result to the given Message object. +static void StreamWideCharsToMessage(const wchar_t* wstr, size_t length, + Message* msg) { + for (size_t i = 0; i != length; ) { // NOLINT + if (wstr[i] != L'\0') { + *msg << WideStringToUtf8(wstr + i, static_cast(length - i)); + while (i != length && wstr[i] != L'\0') + i++; + } else { + *msg << '\0'; + i++; + } + } +} + +#endif // GTEST_HAS_STD_WSTRING || GTEST_HAS_GLOBAL_WSTRING + +} // namespace internal + +// Constructs an empty Message. +// We allocate the stringstream separately because otherwise each use of +// ASSERT/EXPECT in a procedure adds over 200 bytes to the procedure's +// stack frame leading to huge stack frames in some cases; gcc does not reuse +// the stack space. +Message::Message() : ss_(new ::std::stringstream) { + // By default, we want there to be enough precision when printing + // a double to a Message. + *ss_ << std::setprecision(std::numeric_limits::digits10 + 2); +} + +// These two overloads allow streaming a wide C string to a Message +// using the UTF-8 encoding. +Message& Message::operator <<(const wchar_t* wide_c_str) { + return *this << internal::String::ShowWideCString(wide_c_str); +} +Message& Message::operator <<(wchar_t* wide_c_str) { + return *this << internal::String::ShowWideCString(wide_c_str); +} + +#if GTEST_HAS_STD_WSTRING +// Converts the given wide string to a narrow string using the UTF-8 +// encoding, and streams the result to this Message object. +Message& Message::operator <<(const ::std::wstring& wstr) { + internal::StreamWideCharsToMessage(wstr.c_str(), wstr.length(), this); + return *this; +} +#endif // GTEST_HAS_STD_WSTRING + +#if GTEST_HAS_GLOBAL_WSTRING +// Converts the given wide string to a narrow string using the UTF-8 +// encoding, and streams the result to this Message object. +Message& Message::operator <<(const ::wstring& wstr) { + internal::StreamWideCharsToMessage(wstr.c_str(), wstr.length(), this); + return *this; +} +#endif // GTEST_HAS_GLOBAL_WSTRING + +// Gets the text streamed to this object so far as an std::string. +// Each '\0' character in the buffer is replaced with "\\0". +std::string Message::GetString() const { + return internal::StringStreamToString(ss_.get()); +} + +// AssertionResult constructors. +// Used in EXPECT_TRUE/FALSE(assertion_result). +AssertionResult::AssertionResult(const AssertionResult& other) + : success_(other.success_), + message_(other.message_.get() != NULL ? + new ::std::string(*other.message_) : + static_cast< ::std::string*>(NULL)) { +} + +// Returns the assertion's negation. Used with EXPECT/ASSERT_FALSE. +AssertionResult AssertionResult::operator!() const { + AssertionResult negation(!success_); + if (message_.get() != NULL) + negation << *message_; + return negation; +} + +// Makes a successful assertion result. +AssertionResult AssertionSuccess() { + return AssertionResult(true); +} + +// Makes a failed assertion result. +AssertionResult AssertionFailure() { + return AssertionResult(false); +} + +// Makes a failed assertion result with the given failure message. +// Deprecated; use AssertionFailure() << message. +AssertionResult AssertionFailure(const Message& message) { + return AssertionFailure() << message; +} + +namespace internal { + +// Constructs and returns the message for an equality assertion +// (e.g. ASSERT_EQ, EXPECT_STREQ, etc) failure. +// +// The first four parameters are the expressions used in the assertion +// and their values, as strings. For example, for ASSERT_EQ(foo, bar) +// where foo is 5 and bar is 6, we have: +// +// expected_expression: "foo" +// actual_expression: "bar" +// expected_value: "5" +// actual_value: "6" +// +// The ignoring_case parameter is true iff the assertion is a +// *_STRCASEEQ*. When it's true, the string " (ignoring case)" will +// be inserted into the message. +AssertionResult EqFailure(const char* expected_expression, + const char* actual_expression, + const std::string& expected_value, + const std::string& actual_value, + bool ignoring_case) { + Message msg; + msg << "Value of: " << actual_expression; + if (actual_value != actual_expression) { + msg << "\n Actual: " << actual_value; + } + + msg << "\nExpected: " << expected_expression; + if (ignoring_case) { + msg << " (ignoring case)"; + } + if (expected_value != expected_expression) { + msg << "\nWhich is: " << expected_value; + } + + return AssertionFailure() << msg; +} + +// Constructs a failure message for Boolean assertions such as EXPECT_TRUE. +std::string GetBoolAssertionFailureMessage( + const AssertionResult& assertion_result, + const char* expression_text, + const char* actual_predicate_value, + const char* expected_predicate_value) { + const char* actual_message = assertion_result.message(); + Message msg; + msg << "Value of: " << expression_text + << "\n Actual: " << actual_predicate_value; + if (actual_message[0] != '\0') + msg << " (" << actual_message << ")"; + msg << "\nExpected: " << expected_predicate_value; + return msg.GetString(); +} + +// Helper function for implementing ASSERT_NEAR. +AssertionResult DoubleNearPredFormat(const char* expr1, + const char* expr2, + const char* abs_error_expr, + double val1, + double val2, + double abs_error) { + const double diff = fabs(val1 - val2); + if (diff <= abs_error) return AssertionSuccess(); + + // TODO(wan): do not print the value of an expression if it's + // already a literal. + return AssertionFailure() + << "The difference between " << expr1 << " and " << expr2 + << " is " << diff << ", which exceeds " << abs_error_expr << ", where\n" + << expr1 << " evaluates to " << val1 << ",\n" + << expr2 << " evaluates to " << val2 << ", and\n" + << abs_error_expr << " evaluates to " << abs_error << "."; +} + + +// Helper template for implementing FloatLE() and DoubleLE(). +template +AssertionResult FloatingPointLE(const char* expr1, + const char* expr2, + RawType val1, + RawType val2) { + // Returns success if val1 is less than val2, + if (val1 < val2) { + return AssertionSuccess(); + } + + // or if val1 is almost equal to val2. + const FloatingPoint lhs(val1), rhs(val2); + if (lhs.AlmostEquals(rhs)) { + return AssertionSuccess(); + } + + // Note that the above two checks will both fail if either val1 or + // val2 is NaN, as the IEEE floating-point standard requires that + // any predicate involving a NaN must return false. + + ::std::stringstream val1_ss; + val1_ss << std::setprecision(std::numeric_limits::digits10 + 2) + << val1; + + ::std::stringstream val2_ss; + val2_ss << std::setprecision(std::numeric_limits::digits10 + 2) + << val2; + + return AssertionFailure() + << "Expected: (" << expr1 << ") <= (" << expr2 << ")\n" + << " Actual: " << StringStreamToString(&val1_ss) << " vs " + << StringStreamToString(&val2_ss); +} + +} // namespace internal + +// Asserts that val1 is less than, or almost equal to, val2. Fails +// otherwise. In particular, it fails if either val1 or val2 is NaN. +AssertionResult FloatLE(const char* expr1, const char* expr2, + float val1, float val2) { + return internal::FloatingPointLE(expr1, expr2, val1, val2); +} + +// Asserts that val1 is less than, or almost equal to, val2. Fails +// otherwise. In particular, it fails if either val1 or val2 is NaN. +AssertionResult DoubleLE(const char* expr1, const char* expr2, + double val1, double val2) { + return internal::FloatingPointLE(expr1, expr2, val1, val2); +} + +namespace internal { + +// The helper function for {ASSERT|EXPECT}_EQ with int or enum +// arguments. +AssertionResult CmpHelperEQ(const char* expected_expression, + const char* actual_expression, + BiggestInt expected, + BiggestInt actual) { + if (expected == actual) { + return AssertionSuccess(); + } + + return EqFailure(expected_expression, + actual_expression, + FormatForComparisonFailureMessage(expected, actual), + FormatForComparisonFailureMessage(actual, expected), + false); +} + +// A macro for implementing the helper functions needed to implement +// ASSERT_?? and EXPECT_?? with integer or enum arguments. It is here +// just to avoid copy-and-paste of similar code. +#define GTEST_IMPL_CMP_HELPER_(op_name, op)\ +AssertionResult CmpHelper##op_name(const char* expr1, const char* expr2, \ + BiggestInt val1, BiggestInt val2) {\ + if (val1 op val2) {\ + return AssertionSuccess();\ + } else {\ + return AssertionFailure() \ + << "Expected: (" << expr1 << ") " #op " (" << expr2\ + << "), actual: " << FormatForComparisonFailureMessage(val1, val2)\ + << " vs " << FormatForComparisonFailureMessage(val2, val1);\ + }\ +} + +// Implements the helper function for {ASSERT|EXPECT}_NE with int or +// enum arguments. +GTEST_IMPL_CMP_HELPER_(NE, !=) +// Implements the helper function for {ASSERT|EXPECT}_LE with int or +// enum arguments. +GTEST_IMPL_CMP_HELPER_(LE, <=) +// Implements the helper function for {ASSERT|EXPECT}_LT with int or +// enum arguments. +GTEST_IMPL_CMP_HELPER_(LT, < ) +// Implements the helper function for {ASSERT|EXPECT}_GE with int or +// enum arguments. +GTEST_IMPL_CMP_HELPER_(GE, >=) +// Implements the helper function for {ASSERT|EXPECT}_GT with int or +// enum arguments. +GTEST_IMPL_CMP_HELPER_(GT, > ) + +#undef GTEST_IMPL_CMP_HELPER_ + +// The helper function for {ASSERT|EXPECT}_STREQ. +AssertionResult CmpHelperSTREQ(const char* expected_expression, + const char* actual_expression, + const char* expected, + const char* actual) { + if (String::CStringEquals(expected, actual)) { + return AssertionSuccess(); + } + + return EqFailure(expected_expression, + actual_expression, + PrintToString(expected), + PrintToString(actual), + false); +} + +// The helper function for {ASSERT|EXPECT}_STRCASEEQ. +AssertionResult CmpHelperSTRCASEEQ(const char* expected_expression, + const char* actual_expression, + const char* expected, + const char* actual) { + if (String::CaseInsensitiveCStringEquals(expected, actual)) { + return AssertionSuccess(); + } + + return EqFailure(expected_expression, + actual_expression, + PrintToString(expected), + PrintToString(actual), + true); +} + +// The helper function for {ASSERT|EXPECT}_STRNE. +AssertionResult CmpHelperSTRNE(const char* s1_expression, + const char* s2_expression, + const char* s1, + const char* s2) { + if (!String::CStringEquals(s1, s2)) { + return AssertionSuccess(); + } else { + return AssertionFailure() << "Expected: (" << s1_expression << ") != (" + << s2_expression << "), actual: \"" + << s1 << "\" vs \"" << s2 << "\""; + } +} + +// The helper function for {ASSERT|EXPECT}_STRCASENE. +AssertionResult CmpHelperSTRCASENE(const char* s1_expression, + const char* s2_expression, + const char* s1, + const char* s2) { + if (!String::CaseInsensitiveCStringEquals(s1, s2)) { + return AssertionSuccess(); + } else { + return AssertionFailure() + << "Expected: (" << s1_expression << ") != (" + << s2_expression << ") (ignoring case), actual: \"" + << s1 << "\" vs \"" << s2 << "\""; + } +} + +} // namespace internal + +namespace { + +// Helper functions for implementing IsSubString() and IsNotSubstring(). + +// This group of overloaded functions return true iff needle is a +// substring of haystack. NULL is considered a substring of itself +// only. + +bool IsSubstringPred(const char* needle, const char* haystack) { + if (needle == NULL || haystack == NULL) + return needle == haystack; + + return strstr(haystack, needle) != NULL; +} + +bool IsSubstringPred(const wchar_t* needle, const wchar_t* haystack) { + if (needle == NULL || haystack == NULL) + return needle == haystack; + + return wcsstr(haystack, needle) != NULL; +} + +// StringType here can be either ::std::string or ::std::wstring. +template +bool IsSubstringPred(const StringType& needle, + const StringType& haystack) { + return haystack.find(needle) != StringType::npos; +} + +// This function implements either IsSubstring() or IsNotSubstring(), +// depending on the value of the expected_to_be_substring parameter. +// StringType here can be const char*, const wchar_t*, ::std::string, +// or ::std::wstring. +template +AssertionResult IsSubstringImpl( + bool expected_to_be_substring, + const char* needle_expr, const char* haystack_expr, + const StringType& needle, const StringType& haystack) { + if (IsSubstringPred(needle, haystack) == expected_to_be_substring) + return AssertionSuccess(); + + const bool is_wide_string = sizeof(needle[0]) > 1; + const char* const begin_string_quote = is_wide_string ? "L\"" : "\""; + return AssertionFailure() + << "Value of: " << needle_expr << "\n" + << " Actual: " << begin_string_quote << needle << "\"\n" + << "Expected: " << (expected_to_be_substring ? "" : "not ") + << "a substring of " << haystack_expr << "\n" + << "Which is: " << begin_string_quote << haystack << "\""; +} + +} // namespace + +// IsSubstring() and IsNotSubstring() check whether needle is a +// substring of haystack (NULL is considered a substring of itself +// only), and return an appropriate error message when they fail. + +AssertionResult IsSubstring( + const char* needle_expr, const char* haystack_expr, + const char* needle, const char* haystack) { + return IsSubstringImpl(true, needle_expr, haystack_expr, needle, haystack); +} + +AssertionResult IsSubstring( + const char* needle_expr, const char* haystack_expr, + const wchar_t* needle, const wchar_t* haystack) { + return IsSubstringImpl(true, needle_expr, haystack_expr, needle, haystack); +} + +AssertionResult IsNotSubstring( + const char* needle_expr, const char* haystack_expr, + const char* needle, const char* haystack) { + return IsSubstringImpl(false, needle_expr, haystack_expr, needle, haystack); +} + +AssertionResult IsNotSubstring( + const char* needle_expr, const char* haystack_expr, + const wchar_t* needle, const wchar_t* haystack) { + return IsSubstringImpl(false, needle_expr, haystack_expr, needle, haystack); +} + +AssertionResult IsSubstring( + const char* needle_expr, const char* haystack_expr, + const ::std::string& needle, const ::std::string& haystack) { + return IsSubstringImpl(true, needle_expr, haystack_expr, needle, haystack); +} + +AssertionResult IsNotSubstring( + const char* needle_expr, const char* haystack_expr, + const ::std::string& needle, const ::std::string& haystack) { + return IsSubstringImpl(false, needle_expr, haystack_expr, needle, haystack); +} + +#if GTEST_HAS_STD_WSTRING +AssertionResult IsSubstring( + const char* needle_expr, const char* haystack_expr, + const ::std::wstring& needle, const ::std::wstring& haystack) { + return IsSubstringImpl(true, needle_expr, haystack_expr, needle, haystack); +} + +AssertionResult IsNotSubstring( + const char* needle_expr, const char* haystack_expr, + const ::std::wstring& needle, const ::std::wstring& haystack) { + return IsSubstringImpl(false, needle_expr, haystack_expr, needle, haystack); +} +#endif // GTEST_HAS_STD_WSTRING + +namespace internal { + +#if GTEST_OS_WINDOWS + +namespace { + +// Helper function for IsHRESULT{SuccessFailure} predicates +AssertionResult HRESULTFailureHelper(const char* expr, + const char* expected, + long hr) { // NOLINT +# if GTEST_OS_WINDOWS_MOBILE + + // Windows CE doesn't support FormatMessage. + const char error_text[] = ""; + +# else + + // Looks up the human-readable system message for the HRESULT code + // and since we're not passing any params to FormatMessage, we don't + // want inserts expanded. + const DWORD kFlags = FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS; + const DWORD kBufSize = 4096; + // Gets the system's human readable message string for this HRESULT. + char error_text[kBufSize] = { '\0' }; + DWORD message_length = ::FormatMessageA(kFlags, + 0, // no source, we're asking system + hr, // the error + 0, // no line width restrictions + error_text, // output buffer + kBufSize, // buf size + NULL); // no arguments for inserts + // Trims tailing white space (FormatMessage leaves a trailing CR-LF) + for (; message_length && IsSpace(error_text[message_length - 1]); + --message_length) { + error_text[message_length - 1] = '\0'; + } + +# endif // GTEST_OS_WINDOWS_MOBILE + + const std::string error_hex("0x" + String::FormatHexInt(hr)); + return ::testing::AssertionFailure() + << "Expected: " << expr << " " << expected << ".\n" + << " Actual: " << error_hex << " " << error_text << "\n"; +} + +} // namespace + +AssertionResult IsHRESULTSuccess(const char* expr, long hr) { // NOLINT + if (SUCCEEDED(hr)) { + return AssertionSuccess(); + } + return HRESULTFailureHelper(expr, "succeeds", hr); +} + +AssertionResult IsHRESULTFailure(const char* expr, long hr) { // NOLINT + if (FAILED(hr)) { + return AssertionSuccess(); + } + return HRESULTFailureHelper(expr, "fails", hr); +} + +#endif // GTEST_OS_WINDOWS + +// Utility functions for encoding Unicode text (wide strings) in +// UTF-8. + +// A Unicode code-point can have upto 21 bits, and is encoded in UTF-8 +// like this: +// +// Code-point length Encoding +// 0 - 7 bits 0xxxxxxx +// 8 - 11 bits 110xxxxx 10xxxxxx +// 12 - 16 bits 1110xxxx 10xxxxxx 10xxxxxx +// 17 - 21 bits 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + +// The maximum code-point a one-byte UTF-8 sequence can represent. +const UInt32 kMaxCodePoint1 = (static_cast(1) << 7) - 1; + +// The maximum code-point a two-byte UTF-8 sequence can represent. +const UInt32 kMaxCodePoint2 = (static_cast(1) << (5 + 6)) - 1; + +// The maximum code-point a three-byte UTF-8 sequence can represent. +const UInt32 kMaxCodePoint3 = (static_cast(1) << (4 + 2*6)) - 1; + +// The maximum code-point a four-byte UTF-8 sequence can represent. +const UInt32 kMaxCodePoint4 = (static_cast(1) << (3 + 3*6)) - 1; + +// Chops off the n lowest bits from a bit pattern. Returns the n +// lowest bits. As a side effect, the original bit pattern will be +// shifted to the right by n bits. +inline UInt32 ChopLowBits(UInt32* bits, int n) { + const UInt32 low_bits = *bits & ((static_cast(1) << n) - 1); + *bits >>= n; + return low_bits; +} + +// Converts a Unicode code point to a narrow string in UTF-8 encoding. +// code_point parameter is of type UInt32 because wchar_t may not be +// wide enough to contain a code point. +// If the code_point is not a valid Unicode code point +// (i.e. outside of Unicode range U+0 to U+10FFFF) it will be converted +// to "(Invalid Unicode 0xXXXXXXXX)". +std::string CodePointToUtf8(UInt32 code_point) { + if (code_point > kMaxCodePoint4) { + return "(Invalid Unicode 0x" + String::FormatHexInt(code_point) + ")"; + } + + char str[5]; // Big enough for the largest valid code point. + if (code_point <= kMaxCodePoint1) { + str[1] = '\0'; + str[0] = static_cast(code_point); // 0xxxxxxx + } else if (code_point <= kMaxCodePoint2) { + str[2] = '\0'; + str[1] = static_cast(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx + str[0] = static_cast(0xC0 | code_point); // 110xxxxx + } else if (code_point <= kMaxCodePoint3) { + str[3] = '\0'; + str[2] = static_cast(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx + str[1] = static_cast(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx + str[0] = static_cast(0xE0 | code_point); // 1110xxxx + } else { // code_point <= kMaxCodePoint4 + str[4] = '\0'; + str[3] = static_cast(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx + str[2] = static_cast(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx + str[1] = static_cast(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx + str[0] = static_cast(0xF0 | code_point); // 11110xxx + } + return str; +} + +// The following two functions only make sense if the the system +// uses UTF-16 for wide string encoding. All supported systems +// with 16 bit wchar_t (Windows, Cygwin, Symbian OS) do use UTF-16. + +// Determines if the arguments constitute UTF-16 surrogate pair +// and thus should be combined into a single Unicode code point +// using CreateCodePointFromUtf16SurrogatePair. +inline bool IsUtf16SurrogatePair(wchar_t first, wchar_t second) { + return sizeof(wchar_t) == 2 && + (first & 0xFC00) == 0xD800 && (second & 0xFC00) == 0xDC00; +} + +// Creates a Unicode code point from UTF16 surrogate pair. +inline UInt32 CreateCodePointFromUtf16SurrogatePair(wchar_t first, + wchar_t second) { + const UInt32 mask = (1 << 10) - 1; + return (sizeof(wchar_t) == 2) ? + (((first & mask) << 10) | (second & mask)) + 0x10000 : + // This function should not be called when the condition is + // false, but we provide a sensible default in case it is. + static_cast(first); +} + +// Converts a wide string to a narrow string in UTF-8 encoding. +// The wide string is assumed to have the following encoding: +// UTF-16 if sizeof(wchar_t) == 2 (on Windows, Cygwin, Symbian OS) +// UTF-32 if sizeof(wchar_t) == 4 (on Linux) +// Parameter str points to a null-terminated wide string. +// Parameter num_chars may additionally limit the number +// of wchar_t characters processed. -1 is used when the entire string +// should be processed. +// If the string contains code points that are not valid Unicode code points +// (i.e. outside of Unicode range U+0 to U+10FFFF) they will be output +// as '(Invalid Unicode 0xXXXXXXXX)'. If the string is in UTF16 encoding +// and contains invalid UTF-16 surrogate pairs, values in those pairs +// will be encoded as individual Unicode characters from Basic Normal Plane. +std::string WideStringToUtf8(const wchar_t* str, int num_chars) { + if (num_chars == -1) + num_chars = static_cast(wcslen(str)); + + ::std::stringstream stream; + for (int i = 0; i < num_chars; ++i) { + UInt32 unicode_code_point; + + if (str[i] == L'\0') { + break; + } else if (i + 1 < num_chars && IsUtf16SurrogatePair(str[i], str[i + 1])) { + unicode_code_point = CreateCodePointFromUtf16SurrogatePair(str[i], + str[i + 1]); + i++; + } else { + unicode_code_point = static_cast(str[i]); + } + + stream << CodePointToUtf8(unicode_code_point); + } + return StringStreamToString(&stream); +} + +// Converts a wide C string to an std::string using the UTF-8 encoding. +// NULL will be converted to "(null)". +std::string String::ShowWideCString(const wchar_t * wide_c_str) { + if (wide_c_str == NULL) return "(null)"; + + return internal::WideStringToUtf8(wide_c_str, -1); +} + +// Compares two wide C strings. Returns true iff they have the same +// content. +// +// Unlike wcscmp(), this function can handle NULL argument(s). A NULL +// C string is considered different to any non-NULL C string, +// including the empty string. +bool String::WideCStringEquals(const wchar_t * lhs, const wchar_t * rhs) { + if (lhs == NULL) return rhs == NULL; + + if (rhs == NULL) return false; + + return wcscmp(lhs, rhs) == 0; +} + +// Helper function for *_STREQ on wide strings. +AssertionResult CmpHelperSTREQ(const char* expected_expression, + const char* actual_expression, + const wchar_t* expected, + const wchar_t* actual) { + if (String::WideCStringEquals(expected, actual)) { + return AssertionSuccess(); + } + + return EqFailure(expected_expression, + actual_expression, + PrintToString(expected), + PrintToString(actual), + false); +} + +// Helper function for *_STRNE on wide strings. +AssertionResult CmpHelperSTRNE(const char* s1_expression, + const char* s2_expression, + const wchar_t* s1, + const wchar_t* s2) { + if (!String::WideCStringEquals(s1, s2)) { + return AssertionSuccess(); + } + + return AssertionFailure() << "Expected: (" << s1_expression << ") != (" + << s2_expression << "), actual: " + << PrintToString(s1) + << " vs " << PrintToString(s2); +} + +// Compares two C strings, ignoring case. Returns true iff they have +// the same content. +// +// Unlike strcasecmp(), this function can handle NULL argument(s). A +// NULL C string is considered different to any non-NULL C string, +// including the empty string. +bool String::CaseInsensitiveCStringEquals(const char * lhs, const char * rhs) { + if (lhs == NULL) + return rhs == NULL; + if (rhs == NULL) + return false; + return posix::StrCaseCmp(lhs, rhs) == 0; +} + + // Compares two wide C strings, ignoring case. Returns true iff they + // have the same content. + // + // Unlike wcscasecmp(), this function can handle NULL argument(s). + // A NULL C string is considered different to any non-NULL wide C string, + // including the empty string. + // NB: The implementations on different platforms slightly differ. + // On windows, this method uses _wcsicmp which compares according to LC_CTYPE + // environment variable. On GNU platform this method uses wcscasecmp + // which compares according to LC_CTYPE category of the current locale. + // On MacOS X, it uses towlower, which also uses LC_CTYPE category of the + // current locale. +bool String::CaseInsensitiveWideCStringEquals(const wchar_t* lhs, + const wchar_t* rhs) { + if (lhs == NULL) return rhs == NULL; + + if (rhs == NULL) return false; + +#if GTEST_OS_WINDOWS + return _wcsicmp(lhs, rhs) == 0; +#elif GTEST_OS_LINUX && !GTEST_OS_LINUX_ANDROID + return wcscasecmp(lhs, rhs) == 0; +#else + // Android, Mac OS X and Cygwin don't define wcscasecmp. + // Other unknown OSes may not define it either. + wint_t left, right; + do { + left = towlower(*lhs++); + right = towlower(*rhs++); + } while (left && left == right); + return left == right; +#endif // OS selector +} + +// Returns true iff str ends with the given suffix, ignoring case. +// Any string is considered to end with an empty suffix. +bool String::EndsWithCaseInsensitive( + const std::string& str, const std::string& suffix) { + const size_t str_len = str.length(); + const size_t suffix_len = suffix.length(); + return (str_len >= suffix_len) && + CaseInsensitiveCStringEquals(str.c_str() + str_len - suffix_len, + suffix.c_str()); +} + +// Formats an int value as "%02d". +std::string String::FormatIntWidth2(int value) { + std::stringstream ss; + ss << std::setfill('0') << std::setw(2) << value; + return ss.str(); +} + +// Formats an int value as "%X". +std::string String::FormatHexInt(int value) { + std::stringstream ss; + ss << std::hex << std::uppercase << value; + return ss.str(); +} + +// Formats a byte as "%02X". +std::string String::FormatByte(unsigned char value) { + std::stringstream ss; + ss << std::setfill('0') << std::setw(2) << std::hex << std::uppercase + << static_cast(value); + return ss.str(); +} + +// Converts the buffer in a stringstream to an std::string, converting NUL +// bytes to "\\0" along the way. +std::string StringStreamToString(::std::stringstream* ss) { + const ::std::string& str = ss->str(); + const char* const start = str.c_str(); + const char* const end = start + str.length(); + + std::string result; + result.reserve(2 * (end - start)); + for (const char* ch = start; ch != end; ++ch) { + if (*ch == '\0') { + result += "\\0"; // Replaces NUL with "\\0"; + } else { + result += *ch; + } + } + + return result; +} + +// Appends the user-supplied message to the Google-Test-generated message. +std::string AppendUserMessage(const std::string& gtest_msg, + const Message& user_msg) { + // Appends the user message if it's non-empty. + const std::string user_msg_string = user_msg.GetString(); + if (user_msg_string.empty()) { + return gtest_msg; + } + + return gtest_msg + "\n" + user_msg_string; +} + +} // namespace internal + +// class TestResult + +// Creates an empty TestResult. +TestResult::TestResult() + : death_test_count_(0), + elapsed_time_(0) { +} + +// D'tor. +TestResult::~TestResult() { +} + +// Returns the i-th test part result among all the results. i can +// range from 0 to total_part_count() - 1. If i is not in that range, +// aborts the program. +const TestPartResult& TestResult::GetTestPartResult(int i) const { + if (i < 0 || i >= total_part_count()) + internal::posix::Abort(); + return test_part_results_.at(i); +} + +// Returns the i-th test property. i can range from 0 to +// test_property_count() - 1. If i is not in that range, aborts the +// program. +const TestProperty& TestResult::GetTestProperty(int i) const { + if (i < 0 || i >= test_property_count()) + internal::posix::Abort(); + return test_properties_.at(i); +} + +// Clears the test part results. +void TestResult::ClearTestPartResults() { + test_part_results_.clear(); +} + +// Adds a test part result to the list. +void TestResult::AddTestPartResult(const TestPartResult& test_part_result) { + test_part_results_.push_back(test_part_result); +} + +// Adds a test property to the list. If a property with the same key as the +// supplied property is already represented, the value of this test_property +// replaces the old value for that key. +void TestResult::RecordProperty(const std::string& xml_element, + const TestProperty& test_property) { + if (!ValidateTestProperty(xml_element, test_property)) { + return; + } + internal::MutexLock lock(&test_properites_mutex_); + const std::vector::iterator property_with_matching_key = + std::find_if(test_properties_.begin(), test_properties_.end(), + internal::TestPropertyKeyIs(test_property.key())); + if (property_with_matching_key == test_properties_.end()) { + test_properties_.push_back(test_property); + return; + } + property_with_matching_key->SetValue(test_property.value()); +} + +// The list of reserved attributes used in the element of XML +// output. +static const char* const kReservedTestSuitesAttributes[] = { + "disabled", + "errors", + "failures", + "name", + "random_seed", + "tests", + "time", + "timestamp" +}; + +// The list of reserved attributes used in the element of XML +// output. +static const char* const kReservedTestSuiteAttributes[] = { + "disabled", + "errors", + "failures", + "name", + "tests", + "time" +}; + +// The list of reserved attributes used in the element of XML output. +static const char* const kReservedTestCaseAttributes[] = { + "classname", + "name", + "status", + "time", + "type_param", + "value_param" +}; + +template +std::vector ArrayAsVector(const char* const (&array)[kSize]) { + return std::vector(array, array + kSize); +} + +static std::vector GetReservedAttributesForElement( + const std::string& xml_element) { + if (xml_element == "testsuites") { + return ArrayAsVector(kReservedTestSuitesAttributes); + } else if (xml_element == "testsuite") { + return ArrayAsVector(kReservedTestSuiteAttributes); + } else if (xml_element == "testcase") { + return ArrayAsVector(kReservedTestCaseAttributes); + } else { + GTEST_CHECK_(false) << "Unrecognized xml_element provided: " << xml_element; + } + // This code is unreachable but some compilers may not realizes that. + return std::vector(); +} + +static std::string FormatWordList(const std::vector& words) { + Message word_list; + for (size_t i = 0; i < words.size(); ++i) { + if (i > 0 && words.size() > 2) { + word_list << ", "; + } + if (i == words.size() - 1) { + word_list << "and "; + } + word_list << "'" << words[i] << "'"; + } + return word_list.GetString(); +} + +bool ValidateTestPropertyName(const std::string& property_name, + const std::vector& reserved_names) { + if (std::find(reserved_names.begin(), reserved_names.end(), property_name) != + reserved_names.end()) { + ADD_FAILURE() << "Reserved key used in RecordProperty(): " << property_name + << " (" << FormatWordList(reserved_names) + << " are reserved by " << GTEST_NAME_ << ")"; + return false; + } + return true; +} + +// Adds a failure if the key is a reserved attribute of the element named +// xml_element. Returns true if the property is valid. +bool TestResult::ValidateTestProperty(const std::string& xml_element, + const TestProperty& test_property) { + return ValidateTestPropertyName(test_property.key(), + GetReservedAttributesForElement(xml_element)); +} + +// Clears the object. +void TestResult::Clear() { + test_part_results_.clear(); + test_properties_.clear(); + death_test_count_ = 0; + elapsed_time_ = 0; +} + +// Returns true iff the test failed. +bool TestResult::Failed() const { + for (int i = 0; i < total_part_count(); ++i) { + if (GetTestPartResult(i).failed()) + return true; + } + return false; +} + +// Returns true iff the test part fatally failed. +static bool TestPartFatallyFailed(const TestPartResult& result) { + return result.fatally_failed(); +} + +// Returns true iff the test fatally failed. +bool TestResult::HasFatalFailure() const { + return CountIf(test_part_results_, TestPartFatallyFailed) > 0; +} + +// Returns true iff the test part non-fatally failed. +static bool TestPartNonfatallyFailed(const TestPartResult& result) { + return result.nonfatally_failed(); +} + +// Returns true iff the test has a non-fatal failure. +bool TestResult::HasNonfatalFailure() const { + return CountIf(test_part_results_, TestPartNonfatallyFailed) > 0; +} + +// Gets the number of all test parts. This is the sum of the number +// of successful test parts and the number of failed test parts. +int TestResult::total_part_count() const { + return static_cast(test_part_results_.size()); +} + +// Returns the number of the test properties. +int TestResult::test_property_count() const { + return static_cast(test_properties_.size()); +} + +// class Test + +// Creates a Test object. + +// The c'tor saves the values of all Google Test flags. +Test::Test() + : gtest_flag_saver_(new internal::GTestFlagSaver) { +} + +// The d'tor restores the values of all Google Test flags. +Test::~Test() { + delete gtest_flag_saver_; +} + +// Sets up the test fixture. +// +// A sub-class may override this. +void Test::SetUp() { +} + +// Tears down the test fixture. +// +// A sub-class may override this. +void Test::TearDown() { +} + +// Allows user supplied key value pairs to be recorded for later output. +void Test::RecordProperty(const std::string& key, const std::string& value) { + UnitTest::GetInstance()->RecordProperty(key, value); +} + +// Allows user supplied key value pairs to be recorded for later output. +void Test::RecordProperty(const std::string& key, int value) { + Message value_message; + value_message << value; + RecordProperty(key, value_message.GetString().c_str()); +} + +namespace internal { + +void ReportFailureInUnknownLocation(TestPartResult::Type result_type, + const std::string& message) { + // This function is a friend of UnitTest and as such has access to + // AddTestPartResult. + UnitTest::GetInstance()->AddTestPartResult( + result_type, + NULL, // No info about the source file where the exception occurred. + -1, // We have no info on which line caused the exception. + message, + ""); // No stack trace, either. +} + +} // namespace internal + +// Google Test requires all tests in the same test case to use the same test +// fixture class. This function checks if the current test has the +// same fixture class as the first test in the current test case. If +// yes, it returns true; otherwise it generates a Google Test failure and +// returns false. +bool Test::HasSameFixtureClass() { + internal::UnitTestImpl* const impl = internal::GetUnitTestImpl(); + const TestCase* const test_case = impl->current_test_case(); + + // Info about the first test in the current test case. + const TestInfo* const first_test_info = test_case->test_info_list()[0]; + const internal::TypeId first_fixture_id = first_test_info->fixture_class_id_; + const char* const first_test_name = first_test_info->name(); + + // Info about the current test. + const TestInfo* const this_test_info = impl->current_test_info(); + const internal::TypeId this_fixture_id = this_test_info->fixture_class_id_; + const char* const this_test_name = this_test_info->name(); + + if (this_fixture_id != first_fixture_id) { + // Is the first test defined using TEST? + const bool first_is_TEST = first_fixture_id == internal::GetTestTypeId(); + // Is this test defined using TEST? + const bool this_is_TEST = this_fixture_id == internal::GetTestTypeId(); + + if (first_is_TEST || this_is_TEST) { + // The user mixed TEST and TEST_F in this test case - we'll tell + // him/her how to fix it. + + // Gets the name of the TEST and the name of the TEST_F. Note + // that first_is_TEST and this_is_TEST cannot both be true, as + // the fixture IDs are different for the two tests. + const char* const TEST_name = + first_is_TEST ? first_test_name : this_test_name; + const char* const TEST_F_name = + first_is_TEST ? this_test_name : first_test_name; + + ADD_FAILURE() + << "All tests in the same test case must use the same test fixture\n" + << "class, so mixing TEST_F and TEST in the same test case is\n" + << "illegal. In test case " << this_test_info->test_case_name() + << ",\n" + << "test " << TEST_F_name << " is defined using TEST_F but\n" + << "test " << TEST_name << " is defined using TEST. You probably\n" + << "want to change the TEST to TEST_F or move it to another test\n" + << "case."; + } else { + // The user defined two fixture classes with the same name in + // two namespaces - we'll tell him/her how to fix it. + ADD_FAILURE() + << "All tests in the same test case must use the same test fixture\n" + << "class. However, in test case " + << this_test_info->test_case_name() << ",\n" + << "you defined test " << first_test_name + << " and test " << this_test_name << "\n" + << "using two different test fixture classes. This can happen if\n" + << "the two classes are from different namespaces or translation\n" + << "units and have the same name. You should probably rename one\n" + << "of the classes to put the tests into different test cases."; + } + return false; + } + + return true; +} + +#if GTEST_HAS_SEH + +// Adds an "exception thrown" fatal failure to the current test. This +// function returns its result via an output parameter pointer because VC++ +// prohibits creation of objects with destructors on stack in functions +// using __try (see error C2712). +static std::string* FormatSehExceptionMessage(DWORD exception_code, + const char* location) { + Message message; + message << "SEH exception with code 0x" << std::setbase(16) << + exception_code << std::setbase(10) << " thrown in " << location << "."; + + return new std::string(message.GetString()); +} + +#endif // GTEST_HAS_SEH + +namespace internal { + +#if GTEST_HAS_EXCEPTIONS + +// Adds an "exception thrown" fatal failure to the current test. +static std::string FormatCxxExceptionMessage(const char* description, + const char* location) { + Message message; + if (description != NULL) { + message << "C++ exception with description \"" << description << "\""; + } else { + message << "Unknown C++ exception"; + } + message << " thrown in " << location << "."; + + return message.GetString(); +} + +static std::string PrintTestPartResultToString( + const TestPartResult& test_part_result); + +GoogleTestFailureException::GoogleTestFailureException( + const TestPartResult& failure) + : ::std::runtime_error(PrintTestPartResultToString(failure).c_str()) {} + +#endif // GTEST_HAS_EXCEPTIONS + +// We put these helper functions in the internal namespace as IBM's xlC +// compiler rejects the code if they were declared static. + +// Runs the given method and handles SEH exceptions it throws, when +// SEH is supported; returns the 0-value for type Result in case of an +// SEH exception. (Microsoft compilers cannot handle SEH and C++ +// exceptions in the same function. Therefore, we provide a separate +// wrapper function for handling SEH exceptions.) +template +Result HandleSehExceptionsInMethodIfSupported( + T* object, Result (T::*method)(), const char* location) { +#if GTEST_HAS_SEH + __try { + return (object->*method)(); + } __except (internal::UnitTestOptions::GTestShouldProcessSEH( // NOLINT + GetExceptionCode())) { + // We create the exception message on the heap because VC++ prohibits + // creation of objects with destructors on stack in functions using __try + // (see error C2712). + std::string* exception_message = FormatSehExceptionMessage( + GetExceptionCode(), location); + internal::ReportFailureInUnknownLocation(TestPartResult::kFatalFailure, + *exception_message); + delete exception_message; + return static_cast(0); + } +#else + (void)location; + return (object->*method)(); +#endif // GTEST_HAS_SEH +} + +// Runs the given method and catches and reports C++ and/or SEH-style +// exceptions, if they are supported; returns the 0-value for type +// Result in case of an SEH exception. +template +Result HandleExceptionsInMethodIfSupported( + T* object, Result (T::*method)(), const char* location) { + // NOTE: The user code can affect the way in which Google Test handles + // exceptions by setting GTEST_FLAG(catch_exceptions), but only before + // RUN_ALL_TESTS() starts. It is technically possible to check the flag + // after the exception is caught and either report or re-throw the + // exception based on the flag's value: + // + // try { + // // Perform the test method. + // } catch (...) { + // if (GTEST_FLAG(catch_exceptions)) + // // Report the exception as failure. + // else + // throw; // Re-throws the original exception. + // } + // + // However, the purpose of this flag is to allow the program to drop into + // the debugger when the exception is thrown. On most platforms, once the + // control enters the catch block, the exception origin information is + // lost and the debugger will stop the program at the point of the + // re-throw in this function -- instead of at the point of the original + // throw statement in the code under test. For this reason, we perform + // the check early, sacrificing the ability to affect Google Test's + // exception handling in the method where the exception is thrown. + if (internal::GetUnitTestImpl()->catch_exceptions()) { +#if GTEST_HAS_EXCEPTIONS + try { + return HandleSehExceptionsInMethodIfSupported(object, method, location); + } catch (const internal::GoogleTestFailureException&) { // NOLINT + // This exception type can only be thrown by a failed Google + // Test assertion with the intention of letting another testing + // framework catch it. Therefore we just re-throw it. + throw; + } catch (const std::exception& e) { // NOLINT + internal::ReportFailureInUnknownLocation( + TestPartResult::kFatalFailure, + FormatCxxExceptionMessage(e.what(), location)); + } catch (...) { // NOLINT + internal::ReportFailureInUnknownLocation( + TestPartResult::kFatalFailure, + FormatCxxExceptionMessage(NULL, location)); + } + return static_cast(0); +#else + return HandleSehExceptionsInMethodIfSupported(object, method, location); +#endif // GTEST_HAS_EXCEPTIONS + } else { + return (object->*method)(); + } +} + +} // namespace internal + +// Runs the test and updates the test result. +void Test::Run() { + if (!HasSameFixtureClass()) return; + + internal::UnitTestImpl* const impl = internal::GetUnitTestImpl(); + impl->os_stack_trace_getter()->UponLeavingGTest(); + internal::HandleExceptionsInMethodIfSupported(this, &Test::SetUp, "SetUp()"); + // We will run the test only if SetUp() was successful. + if (!HasFatalFailure()) { + impl->os_stack_trace_getter()->UponLeavingGTest(); + internal::HandleExceptionsInMethodIfSupported( + this, &Test::TestBody, "the test body"); + } + + // However, we want to clean up as much as possible. Hence we will + // always call TearDown(), even if SetUp() or the test body has + // failed. + impl->os_stack_trace_getter()->UponLeavingGTest(); + internal::HandleExceptionsInMethodIfSupported( + this, &Test::TearDown, "TearDown()"); +} + +// Returns true iff the current test has a fatal failure. +bool Test::HasFatalFailure() { + return internal::GetUnitTestImpl()->current_test_result()->HasFatalFailure(); +} + +// Returns true iff the current test has a non-fatal failure. +bool Test::HasNonfatalFailure() { + return internal::GetUnitTestImpl()->current_test_result()-> + HasNonfatalFailure(); +} + +// class TestInfo + +// Constructs a TestInfo object. It assumes ownership of the test factory +// object. +TestInfo::TestInfo(const std::string& a_test_case_name, + const std::string& a_name, + const char* a_type_param, + const char* a_value_param, + internal::TypeId fixture_class_id, + internal::TestFactoryBase* factory) + : test_case_name_(a_test_case_name), + name_(a_name), + type_param_(a_type_param ? new std::string(a_type_param) : NULL), + value_param_(a_value_param ? new std::string(a_value_param) : NULL), + fixture_class_id_(fixture_class_id), + should_run_(false), + is_disabled_(false), + matches_filter_(false), + factory_(factory), + result_() {} + +// Destructs a TestInfo object. +TestInfo::~TestInfo() { delete factory_; } + +namespace internal { + +// Creates a new TestInfo object and registers it with Google Test; +// returns the created object. +// +// Arguments: +// +// test_case_name: name of the test case +// name: name of the test +// type_param: the name of the test's type parameter, or NULL if +// this is not a typed or a type-parameterized test. +// value_param: text representation of the test's value parameter, +// or NULL if this is not a value-parameterized test. +// fixture_class_id: ID of the test fixture class +// set_up_tc: pointer to the function that sets up the test case +// tear_down_tc: pointer to the function that tears down the test case +// factory: pointer to the factory that creates a test object. +// The newly created TestInfo instance will assume +// ownership of the factory object. +TestInfo* MakeAndRegisterTestInfo( + const char* test_case_name, + const char* name, + const char* type_param, + const char* value_param, + TypeId fixture_class_id, + SetUpTestCaseFunc set_up_tc, + TearDownTestCaseFunc tear_down_tc, + TestFactoryBase* factory) { + TestInfo* const test_info = + new TestInfo(test_case_name, name, type_param, value_param, + fixture_class_id, factory); + GetUnitTestImpl()->AddTestInfo(set_up_tc, tear_down_tc, test_info); + return test_info; +} + +#if GTEST_HAS_PARAM_TEST +void ReportInvalidTestCaseType(const char* test_case_name, + const char* file, int line) { + Message errors; + errors + << "Attempted redefinition of test case " << test_case_name << ".\n" + << "All tests in the same test case must use the same test fixture\n" + << "class. However, in test case " << test_case_name << ", you tried\n" + << "to define a test using a fixture class different from the one\n" + << "used earlier. This can happen if the two fixture classes are\n" + << "from different namespaces and have the same name. You should\n" + << "probably rename one of the classes to put the tests into different\n" + << "test cases."; + + fprintf(stderr, "%s %s", FormatFileLocation(file, line).c_str(), + errors.GetString().c_str()); +} +#endif // GTEST_HAS_PARAM_TEST + +} // namespace internal + +namespace { + +// A predicate that checks the test name of a TestInfo against a known +// value. +// +// This is used for implementation of the TestCase class only. We put +// it in the anonymous namespace to prevent polluting the outer +// namespace. +// +// TestNameIs is copyable. +class TestNameIs { + public: + // Constructor. + // + // TestNameIs has NO default constructor. + explicit TestNameIs(const char* name) + : name_(name) {} + + // Returns true iff the test name of test_info matches name_. + bool operator()(const TestInfo * test_info) const { + return test_info && test_info->name() == name_; + } + + private: + std::string name_; +}; + +} // namespace + +namespace internal { + +// This method expands all parameterized tests registered with macros TEST_P +// and INSTANTIATE_TEST_CASE_P into regular tests and registers those. +// This will be done just once during the program runtime. +void UnitTestImpl::RegisterParameterizedTests() { +#if GTEST_HAS_PARAM_TEST + if (!parameterized_tests_registered_) { + parameterized_test_registry_.RegisterTests(); + parameterized_tests_registered_ = true; + } +#endif +} + +} // namespace internal + +// Creates the test object, runs it, records its result, and then +// deletes it. +void TestInfo::Run() { + if (!should_run_) return; + + // Tells UnitTest where to store test result. + internal::UnitTestImpl* const impl = internal::GetUnitTestImpl(); + impl->set_current_test_info(this); + + TestEventListener* repeater = UnitTest::GetInstance()->listeners().repeater(); + + // Notifies the unit test event listeners that a test is about to start. + repeater->OnTestStart(*this); + + const TimeInMillis start = internal::GetTimeInMillis(); + + impl->os_stack_trace_getter()->UponLeavingGTest(); + + // Creates the test object. + Test* const test = internal::HandleExceptionsInMethodIfSupported( + factory_, &internal::TestFactoryBase::CreateTest, + "the test fixture's constructor"); + + // Runs the test only if the test object was created and its + // constructor didn't generate a fatal failure. + if ((test != NULL) && !Test::HasFatalFailure()) { + // This doesn't throw as all user code that can throw are wrapped into + // exception handling code. + test->Run(); + } + + // Deletes the test object. + impl->os_stack_trace_getter()->UponLeavingGTest(); + internal::HandleExceptionsInMethodIfSupported( + test, &Test::DeleteSelf_, "the test fixture's destructor"); + + result_.set_elapsed_time(internal::GetTimeInMillis() - start); + + // Notifies the unit test event listener that a test has just finished. + repeater->OnTestEnd(*this); + + // Tells UnitTest to stop associating assertion results to this + // test. + impl->set_current_test_info(NULL); +} + +// class TestCase + +// Gets the number of successful tests in this test case. +int TestCase::successful_test_count() const { + return CountIf(test_info_list_, TestPassed); +} + +// Gets the number of failed tests in this test case. +int TestCase::failed_test_count() const { + return CountIf(test_info_list_, TestFailed); +} + +// Gets the number of disabled tests that will be reported in the XML report. +int TestCase::reportable_disabled_test_count() const { + return CountIf(test_info_list_, TestReportableDisabled); +} + +// Gets the number of disabled tests in this test case. +int TestCase::disabled_test_count() const { + return CountIf(test_info_list_, TestDisabled); +} + +// Gets the number of tests to be printed in the XML report. +int TestCase::reportable_test_count() const { + return CountIf(test_info_list_, TestReportable); +} + +// Get the number of tests in this test case that should run. +int TestCase::test_to_run_count() const { + return CountIf(test_info_list_, ShouldRunTest); +} + +// Gets the number of all tests. +int TestCase::total_test_count() const { + return static_cast(test_info_list_.size()); +} + +// Creates a TestCase with the given name. +// +// Arguments: +// +// name: name of the test case +// a_type_param: the name of the test case's type parameter, or NULL if +// this is not a typed or a type-parameterized test case. +// set_up_tc: pointer to the function that sets up the test case +// tear_down_tc: pointer to the function that tears down the test case +TestCase::TestCase(const char* a_name, const char* a_type_param, + Test::SetUpTestCaseFunc set_up_tc, + Test::TearDownTestCaseFunc tear_down_tc) + : name_(a_name), + type_param_(a_type_param ? new std::string(a_type_param) : NULL), + set_up_tc_(set_up_tc), + tear_down_tc_(tear_down_tc), + should_run_(false), + elapsed_time_(0) { +} + +// Destructor of TestCase. +TestCase::~TestCase() { + // Deletes every Test in the collection. + ForEach(test_info_list_, internal::Delete); +} + +// Returns the i-th test among all the tests. i can range from 0 to +// total_test_count() - 1. If i is not in that range, returns NULL. +const TestInfo* TestCase::GetTestInfo(int i) const { + const int index = GetElementOr(test_indices_, i, -1); + return index < 0 ? NULL : test_info_list_[index]; +} + +// Returns the i-th test among all the tests. i can range from 0 to +// total_test_count() - 1. If i is not in that range, returns NULL. +TestInfo* TestCase::GetMutableTestInfo(int i) { + const int index = GetElementOr(test_indices_, i, -1); + return index < 0 ? NULL : test_info_list_[index]; +} + +// Adds a test to this test case. Will delete the test upon +// destruction of the TestCase object. +void TestCase::AddTestInfo(TestInfo * test_info) { + test_info_list_.push_back(test_info); + test_indices_.push_back(static_cast(test_indices_.size())); +} + +// Runs every test in this TestCase. +void TestCase::Run() { + if (!should_run_) return; + + internal::UnitTestImpl* const impl = internal::GetUnitTestImpl(); + impl->set_current_test_case(this); + + TestEventListener* repeater = UnitTest::GetInstance()->listeners().repeater(); + + repeater->OnTestCaseStart(*this); + impl->os_stack_trace_getter()->UponLeavingGTest(); + internal::HandleExceptionsInMethodIfSupported( + this, &TestCase::RunSetUpTestCase, "SetUpTestCase()"); + + const internal::TimeInMillis start = internal::GetTimeInMillis(); + for (int i = 0; i < total_test_count(); i++) { + GetMutableTestInfo(i)->Run(); + } + elapsed_time_ = internal::GetTimeInMillis() - start; + + impl->os_stack_trace_getter()->UponLeavingGTest(); + internal::HandleExceptionsInMethodIfSupported( + this, &TestCase::RunTearDownTestCase, "TearDownTestCase()"); + + repeater->OnTestCaseEnd(*this); + impl->set_current_test_case(NULL); +} + +// Clears the results of all tests in this test case. +void TestCase::ClearResult() { + ad_hoc_test_result_.Clear(); + ForEach(test_info_list_, TestInfo::ClearTestResult); +} + +// Shuffles the tests in this test case. +void TestCase::ShuffleTests(internal::Random* random) { + Shuffle(random, &test_indices_); +} + +// Restores the test order to before the first shuffle. +void TestCase::UnshuffleTests() { + for (size_t i = 0; i < test_indices_.size(); i++) { + test_indices_[i] = static_cast(i); + } +} + +// Formats a countable noun. Depending on its quantity, either the +// singular form or the plural form is used. e.g. +// +// FormatCountableNoun(1, "formula", "formuli") returns "1 formula". +// FormatCountableNoun(5, "book", "books") returns "5 books". +static std::string FormatCountableNoun(int count, + const char * singular_form, + const char * plural_form) { + return internal::StreamableToString(count) + " " + + (count == 1 ? singular_form : plural_form); +} + +// Formats the count of tests. +static std::string FormatTestCount(int test_count) { + return FormatCountableNoun(test_count, "test", "tests"); +} + +// Formats the count of test cases. +static std::string FormatTestCaseCount(int test_case_count) { + return FormatCountableNoun(test_case_count, "test case", "test cases"); +} + +// Converts a TestPartResult::Type enum to human-friendly string +// representation. Both kNonFatalFailure and kFatalFailure are translated +// to "Failure", as the user usually doesn't care about the difference +// between the two when viewing the test result. +static const char * TestPartResultTypeToString(TestPartResult::Type type) { + switch (type) { + case TestPartResult::kSuccess: + return "Success"; + + case TestPartResult::kNonFatalFailure: + case TestPartResult::kFatalFailure: +#ifdef _MSC_VER + return "error: "; +#else + return "Failure\n"; +#endif + default: + return "Unknown result type"; + } +} + +namespace internal { + +// Prints a TestPartResult to an std::string. +static std::string PrintTestPartResultToString( + const TestPartResult& test_part_result) { + return (Message() + << internal::FormatFileLocation(test_part_result.file_name(), + test_part_result.line_number()) + << " " << TestPartResultTypeToString(test_part_result.type()) + << test_part_result.message()).GetString(); +} + +// Prints a TestPartResult. +static void PrintTestPartResult(const TestPartResult& test_part_result) { + const std::string& result = + PrintTestPartResultToString(test_part_result); + printf("%s\n", result.c_str()); + fflush(stdout); + // If the test program runs in Visual Studio or a debugger, the + // following statements add the test part result message to the Output + // window such that the user can double-click on it to jump to the + // corresponding source code location; otherwise they do nothing. +#if GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MOBILE + // We don't call OutputDebugString*() on Windows Mobile, as printing + // to stdout is done by OutputDebugString() there already - we don't + // want the same message printed twice. + ::OutputDebugStringA(result.c_str()); + ::OutputDebugStringA("\n"); +#endif +} + +// class PrettyUnitTestResultPrinter + +enum GTestColor { + COLOR_DEFAULT, + COLOR_RED, + COLOR_GREEN, + COLOR_YELLOW +}; + +#if GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MOBILE + +// Returns the character attribute for the given color. +WORD GetColorAttribute(GTestColor color) { + switch (color) { + case COLOR_RED: return FOREGROUND_RED; + case COLOR_GREEN: return FOREGROUND_GREEN; + case COLOR_YELLOW: return FOREGROUND_RED | FOREGROUND_GREEN; + default: return 0; + } +} + +#else + +// Returns the ANSI color code for the given color. COLOR_DEFAULT is +// an invalid input. +const char* GetAnsiColorCode(GTestColor color) { + switch (color) { + case COLOR_RED: return "1"; + case COLOR_GREEN: return "2"; + case COLOR_YELLOW: return "3"; + default: return NULL; + }; +} + +#endif // GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MOBILE + +// Returns true iff Google Test should use colors in the output. +bool ShouldUseColor(bool stdout_is_tty) { + const char* const gtest_color = GTEST_FLAG(color).c_str(); + + if (String::CaseInsensitiveCStringEquals(gtest_color, "auto")) { +#if GTEST_OS_WINDOWS + // On Windows the TERM variable is usually not set, but the + // console there does support colors. + return stdout_is_tty; +#else + // On non-Windows platforms, we rely on the TERM variable. + const char* const term = posix::GetEnv("TERM"); + const bool term_supports_color = + String::CStringEquals(term, "xterm") || + String::CStringEquals(term, "xterm-color") || + String::CStringEquals(term, "xterm-256color") || + String::CStringEquals(term, "screen") || + String::CStringEquals(term, "screen-256color") || + String::CStringEquals(term, "linux") || + String::CStringEquals(term, "cygwin"); + return stdout_is_tty && term_supports_color; +#endif // GTEST_OS_WINDOWS + } + + return String::CaseInsensitiveCStringEquals(gtest_color, "yes") || + String::CaseInsensitiveCStringEquals(gtest_color, "true") || + String::CaseInsensitiveCStringEquals(gtest_color, "t") || + String::CStringEquals(gtest_color, "1"); + // We take "yes", "true", "t", and "1" as meaning "yes". If the + // value is neither one of these nor "auto", we treat it as "no" to + // be conservative. +} + +// Helpers for printing colored strings to stdout. Note that on Windows, we +// cannot simply emit special characters and have the terminal change colors. +// This routine must actually emit the characters rather than return a string +// that would be colored when printed, as can be done on Linux. +void ColoredPrintf(GTestColor color, const char* fmt, ...) { + va_list args; + va_start(args, fmt); + +#if GTEST_OS_WINDOWS_MOBILE || GTEST_OS_SYMBIAN || GTEST_OS_ZOS || GTEST_OS_IOS + const bool use_color = false; +#else + static const bool in_color_mode = + ShouldUseColor(posix::IsATTY(posix::FileNo(stdout)) != 0); + const bool use_color = in_color_mode && (color != COLOR_DEFAULT); +#endif // GTEST_OS_WINDOWS_MOBILE || GTEST_OS_SYMBIAN || GTEST_OS_ZOS + // The '!= 0' comparison is necessary to satisfy MSVC 7.1. + + if (!use_color) { + vprintf(fmt, args); + va_end(args); + return; + } + +#if GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MOBILE + const HANDLE stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE); + + // Gets the current text color. + CONSOLE_SCREEN_BUFFER_INFO buffer_info; + GetConsoleScreenBufferInfo(stdout_handle, &buffer_info); + const WORD old_color_attrs = buffer_info.wAttributes; + + // We need to flush the stream buffers into the console before each + // SetConsoleTextAttribute call lest it affect the text that is already + // printed but has not yet reached the console. + fflush(stdout); + SetConsoleTextAttribute(stdout_handle, + GetColorAttribute(color) | FOREGROUND_INTENSITY); + vprintf(fmt, args); + + fflush(stdout); + // Restores the text color. + SetConsoleTextAttribute(stdout_handle, old_color_attrs); +#else + printf("\033[0;3%sm", GetAnsiColorCode(color)); + vprintf(fmt, args); + printf("\033[m"); // Resets the terminal to default. +#endif // GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MOBILE + va_end(args); +} + +// Text printed in Google Test's text output and --gunit_list_tests +// output to label the type parameter and value parameter for a test. +static const char kTypeParamLabel[] = "TypeParam"; +static const char kValueParamLabel[] = "GetParam()"; + +void PrintFullTestCommentIfPresent(const TestInfo& test_info) { + const char* const type_param = test_info.type_param(); + const char* const value_param = test_info.value_param(); + + if (type_param != NULL || value_param != NULL) { + printf(", where "); + if (type_param != NULL) { + printf("%s = %s", kTypeParamLabel, type_param); + if (value_param != NULL) + printf(" and "); + } + if (value_param != NULL) { + printf("%s = %s", kValueParamLabel, value_param); + } + } +} + +// This class implements the TestEventListener interface. +// +// Class PrettyUnitTestResultPrinter is copyable. +class PrettyUnitTestResultPrinter : public TestEventListener { + public: + PrettyUnitTestResultPrinter() {} + static void PrintTestName(const char * test_case, const char * test) { + printf("%s.%s", test_case, test); + } + + // The following methods override what's in the TestEventListener class. + virtual void OnTestProgramStart(const UnitTest& /*unit_test*/) {} + virtual void OnTestIterationStart(const UnitTest& unit_test, int iteration); + virtual void OnEnvironmentsSetUpStart(const UnitTest& unit_test); + virtual void OnEnvironmentsSetUpEnd(const UnitTest& /*unit_test*/) {} + virtual void OnTestCaseStart(const TestCase& test_case); + virtual void OnTestStart(const TestInfo& test_info); + virtual void OnTestPartResult(const TestPartResult& result); + virtual void OnTestEnd(const TestInfo& test_info); + virtual void OnTestCaseEnd(const TestCase& test_case); + virtual void OnEnvironmentsTearDownStart(const UnitTest& unit_test); + virtual void OnEnvironmentsTearDownEnd(const UnitTest& /*unit_test*/) {} + virtual void OnTestIterationEnd(const UnitTest& unit_test, int iteration); + virtual void OnTestProgramEnd(const UnitTest& /*unit_test*/) {} + + private: + static void PrintFailedTests(const UnitTest& unit_test); +}; + + // Fired before each iteration of tests starts. +void PrettyUnitTestResultPrinter::OnTestIterationStart( + const UnitTest& unit_test, int iteration) { + if (GTEST_FLAG(repeat) != 1) + printf("\nRepeating all tests (iteration %d) . . .\n\n", iteration + 1); + + const char* const filter = GTEST_FLAG(filter).c_str(); + + // Prints the filter if it's not *. This reminds the user that some + // tests may be skipped. + if (!String::CStringEquals(filter, kUniversalFilter)) { + ColoredPrintf(COLOR_YELLOW, + "Note: %s filter = %s\n", GTEST_NAME_, filter); + } + + if (internal::ShouldShard(kTestTotalShards, kTestShardIndex, false)) { + const Int32 shard_index = Int32FromEnvOrDie(kTestShardIndex, -1); + ColoredPrintf(COLOR_YELLOW, + "Note: This is test shard %d of %s.\n", + static_cast(shard_index) + 1, + internal::posix::GetEnv(kTestTotalShards)); + } + + if (GTEST_FLAG(shuffle)) { + ColoredPrintf(COLOR_YELLOW, + "Note: Randomizing tests' orders with a seed of %d .\n", + unit_test.random_seed()); + } + + ColoredPrintf(COLOR_GREEN, "[==========] "); + printf("Running %s from %s.\n", + FormatTestCount(unit_test.test_to_run_count()).c_str(), + FormatTestCaseCount(unit_test.test_case_to_run_count()).c_str()); + fflush(stdout); +} + +void PrettyUnitTestResultPrinter::OnEnvironmentsSetUpStart( + const UnitTest& /*unit_test*/) { + ColoredPrintf(COLOR_GREEN, "[----------] "); + printf("Global test environment set-up.\n"); + fflush(stdout); +} + +void PrettyUnitTestResultPrinter::OnTestCaseStart(const TestCase& test_case) { + const std::string counts = + FormatCountableNoun(test_case.test_to_run_count(), "test", "tests"); + ColoredPrintf(COLOR_GREEN, "[----------] "); + printf("%s from %s", counts.c_str(), test_case.name()); + if (test_case.type_param() == NULL) { + printf("\n"); + } else { + printf(", where %s = %s\n", kTypeParamLabel, test_case.type_param()); + } + fflush(stdout); +} + +void PrettyUnitTestResultPrinter::OnTestStart(const TestInfo& test_info) { + ColoredPrintf(COLOR_GREEN, "[ RUN ] "); + PrintTestName(test_info.test_case_name(), test_info.name()); + printf("\n"); + fflush(stdout); +} + +// Called after an assertion failure. +void PrettyUnitTestResultPrinter::OnTestPartResult( + const TestPartResult& result) { + // If the test part succeeded, we don't need to do anything. + if (result.type() == TestPartResult::kSuccess) + return; + + // Print failure message from the assertion (e.g. expected this and got that). + PrintTestPartResult(result); + fflush(stdout); +} + +void PrettyUnitTestResultPrinter::OnTestEnd(const TestInfo& test_info) { + if (test_info.result()->Passed()) { + ColoredPrintf(COLOR_GREEN, "[ OK ] "); + } else { + ColoredPrintf(COLOR_RED, "[ FAILED ] "); + } + PrintTestName(test_info.test_case_name(), test_info.name()); + if (test_info.result()->Failed()) + PrintFullTestCommentIfPresent(test_info); + + if (GTEST_FLAG(print_time)) { + printf(" (%s ms)\n", internal::StreamableToString( + test_info.result()->elapsed_time()).c_str()); + } else { + printf("\n"); + } + fflush(stdout); +} + +void PrettyUnitTestResultPrinter::OnTestCaseEnd(const TestCase& test_case) { + if (!GTEST_FLAG(print_time)) return; + + const std::string counts = + FormatCountableNoun(test_case.test_to_run_count(), "test", "tests"); + ColoredPrintf(COLOR_GREEN, "[----------] "); + printf("%s from %s (%s ms total)\n\n", + counts.c_str(), test_case.name(), + internal::StreamableToString(test_case.elapsed_time()).c_str()); + fflush(stdout); +} + +void PrettyUnitTestResultPrinter::OnEnvironmentsTearDownStart( + const UnitTest& /*unit_test*/) { + ColoredPrintf(COLOR_GREEN, "[----------] "); + printf("Global test environment tear-down\n"); + fflush(stdout); +} + +// Internal helper for printing the list of failed tests. +void PrettyUnitTestResultPrinter::PrintFailedTests(const UnitTest& unit_test) { + const int failed_test_count = unit_test.failed_test_count(); + if (failed_test_count == 0) { + return; + } + + for (int i = 0; i < unit_test.total_test_case_count(); ++i) { + const TestCase& test_case = *unit_test.GetTestCase(i); + if (!test_case.should_run() || (test_case.failed_test_count() == 0)) { + continue; + } + for (int j = 0; j < test_case.total_test_count(); ++j) { + const TestInfo& test_info = *test_case.GetTestInfo(j); + if (!test_info.should_run() || test_info.result()->Passed()) { + continue; + } + ColoredPrintf(COLOR_RED, "[ FAILED ] "); + printf("%s.%s", test_case.name(), test_info.name()); + PrintFullTestCommentIfPresent(test_info); + printf("\n"); + } + } +} + +void PrettyUnitTestResultPrinter::OnTestIterationEnd(const UnitTest& unit_test, + int /*iteration*/) { + ColoredPrintf(COLOR_GREEN, "[==========] "); + printf("%s from %s ran.", + FormatTestCount(unit_test.test_to_run_count()).c_str(), + FormatTestCaseCount(unit_test.test_case_to_run_count()).c_str()); + if (GTEST_FLAG(print_time)) { + printf(" (%s ms total)", + internal::StreamableToString(unit_test.elapsed_time()).c_str()); + } + printf("\n"); + ColoredPrintf(COLOR_GREEN, "[ PASSED ] "); + printf("%s.\n", FormatTestCount(unit_test.successful_test_count()).c_str()); + + int num_failures = unit_test.failed_test_count(); + if (!unit_test.Passed()) { + const int failed_test_count = unit_test.failed_test_count(); + ColoredPrintf(COLOR_RED, "[ FAILED ] "); + printf("%s, listed below:\n", FormatTestCount(failed_test_count).c_str()); + PrintFailedTests(unit_test); + printf("\n%2d FAILED %s\n", num_failures, + num_failures == 1 ? "TEST" : "TESTS"); + } + + int num_disabled = unit_test.reportable_disabled_test_count(); + if (num_disabled && !GTEST_FLAG(also_run_disabled_tests)) { + if (!num_failures) { + printf("\n"); // Add a spacer if no FAILURE banner is displayed. + } + ColoredPrintf(COLOR_YELLOW, + " YOU HAVE %d DISABLED %s\n\n", + num_disabled, + num_disabled == 1 ? "TEST" : "TESTS"); + } + // Ensure that Google Test output is printed before, e.g., heapchecker output. + fflush(stdout); +} + +// End PrettyUnitTestResultPrinter + +// class TestEventRepeater +// +// This class forwards events to other event listeners. +class TestEventRepeater : public TestEventListener { + public: + TestEventRepeater() : forwarding_enabled_(true) {} + virtual ~TestEventRepeater(); + void Append(TestEventListener *listener); + TestEventListener* Release(TestEventListener* listener); + + // Controls whether events will be forwarded to listeners_. Set to false + // in death test child processes. + bool forwarding_enabled() const { return forwarding_enabled_; } + void set_forwarding_enabled(bool enable) { forwarding_enabled_ = enable; } + + virtual void OnTestProgramStart(const UnitTest& unit_test); + virtual void OnTestIterationStart(const UnitTest& unit_test, int iteration); + virtual void OnEnvironmentsSetUpStart(const UnitTest& unit_test); + virtual void OnEnvironmentsSetUpEnd(const UnitTest& unit_test); + virtual void OnTestCaseStart(const TestCase& test_case); + virtual void OnTestStart(const TestInfo& test_info); + virtual void OnTestPartResult(const TestPartResult& result); + virtual void OnTestEnd(const TestInfo& test_info); + virtual void OnTestCaseEnd(const TestCase& test_case); + virtual void OnEnvironmentsTearDownStart(const UnitTest& unit_test); + virtual void OnEnvironmentsTearDownEnd(const UnitTest& unit_test); + virtual void OnTestIterationEnd(const UnitTest& unit_test, int iteration); + virtual void OnTestProgramEnd(const UnitTest& unit_test); + + private: + // Controls whether events will be forwarded to listeners_. Set to false + // in death test child processes. + bool forwarding_enabled_; + // The list of listeners that receive events. + std::vector listeners_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(TestEventRepeater); +}; + +TestEventRepeater::~TestEventRepeater() { + ForEach(listeners_, Delete); +} + +void TestEventRepeater::Append(TestEventListener *listener) { + listeners_.push_back(listener); +} + +// TODO(vladl@google.com): Factor the search functionality into Vector::Find. +TestEventListener* TestEventRepeater::Release(TestEventListener *listener) { + for (size_t i = 0; i < listeners_.size(); ++i) { + if (listeners_[i] == listener) { + listeners_.erase(listeners_.begin() + i); + return listener; + } + } + + return NULL; +} + +// Since most methods are very similar, use macros to reduce boilerplate. +// This defines a member that forwards the call to all listeners. +#define GTEST_REPEATER_METHOD_(Name, Type) \ +void TestEventRepeater::Name(const Type& parameter) { \ + if (forwarding_enabled_) { \ + for (size_t i = 0; i < listeners_.size(); i++) { \ + listeners_[i]->Name(parameter); \ + } \ + } \ +} +// This defines a member that forwards the call to all listeners in reverse +// order. +#define GTEST_REVERSE_REPEATER_METHOD_(Name, Type) \ +void TestEventRepeater::Name(const Type& parameter) { \ + if (forwarding_enabled_) { \ + for (int i = static_cast(listeners_.size()) - 1; i >= 0; i--) { \ + listeners_[i]->Name(parameter); \ + } \ + } \ +} + +GTEST_REPEATER_METHOD_(OnTestProgramStart, UnitTest) +GTEST_REPEATER_METHOD_(OnEnvironmentsSetUpStart, UnitTest) +GTEST_REPEATER_METHOD_(OnTestCaseStart, TestCase) +GTEST_REPEATER_METHOD_(OnTestStart, TestInfo) +GTEST_REPEATER_METHOD_(OnTestPartResult, TestPartResult) +GTEST_REPEATER_METHOD_(OnEnvironmentsTearDownStart, UnitTest) +GTEST_REVERSE_REPEATER_METHOD_(OnEnvironmentsSetUpEnd, UnitTest) +GTEST_REVERSE_REPEATER_METHOD_(OnEnvironmentsTearDownEnd, UnitTest) +GTEST_REVERSE_REPEATER_METHOD_(OnTestEnd, TestInfo) +GTEST_REVERSE_REPEATER_METHOD_(OnTestCaseEnd, TestCase) +GTEST_REVERSE_REPEATER_METHOD_(OnTestProgramEnd, UnitTest) + +#undef GTEST_REPEATER_METHOD_ +#undef GTEST_REVERSE_REPEATER_METHOD_ + +void TestEventRepeater::OnTestIterationStart(const UnitTest& unit_test, + int iteration) { + if (forwarding_enabled_) { + for (size_t i = 0; i < listeners_.size(); i++) { + listeners_[i]->OnTestIterationStart(unit_test, iteration); + } + } +} + +void TestEventRepeater::OnTestIterationEnd(const UnitTest& unit_test, + int iteration) { + if (forwarding_enabled_) { + for (int i = static_cast(listeners_.size()) - 1; i >= 0; i--) { + listeners_[i]->OnTestIterationEnd(unit_test, iteration); + } + } +} + +// End TestEventRepeater + +// This class generates an XML output file. +class XmlUnitTestResultPrinter : public EmptyTestEventListener { + public: + explicit XmlUnitTestResultPrinter(const char* output_file); + + virtual void OnTestIterationEnd(const UnitTest& unit_test, int iteration); + + private: + // Is c a whitespace character that is normalized to a space character + // when it appears in an XML attribute value? + static bool IsNormalizableWhitespace(char c) { + return c == 0x9 || c == 0xA || c == 0xD; + } + + // May c appear in a well-formed XML document? + static bool IsValidXmlCharacter(char c) { + return IsNormalizableWhitespace(c) || c >= 0x20; + } + + // Returns an XML-escaped copy of the input string str. If + // is_attribute is true, the text is meant to appear as an attribute + // value, and normalizable whitespace is preserved by replacing it + // with character references. + static std::string EscapeXml(const std::string& str, bool is_attribute); + + // Returns the given string with all characters invalid in XML removed. + static std::string RemoveInvalidXmlCharacters(const std::string& str); + + // Convenience wrapper around EscapeXml when str is an attribute value. + static std::string EscapeXmlAttribute(const std::string& str) { + return EscapeXml(str, true); + } + + // Convenience wrapper around EscapeXml when str is not an attribute value. + static std::string EscapeXmlText(const char* str) { + return EscapeXml(str, false); + } + + // Verifies that the given attribute belongs to the given element and + // streams the attribute as XML. + static void OutputXmlAttribute(std::ostream* stream, + const std::string& element_name, + const std::string& name, + const std::string& value); + + // Streams an XML CDATA section, escaping invalid CDATA sequences as needed. + static void OutputXmlCDataSection(::std::ostream* stream, const char* data); + + // Streams an XML representation of a TestInfo object. + static void OutputXmlTestInfo(::std::ostream* stream, + const char* test_case_name, + const TestInfo& test_info); + + // Prints an XML representation of a TestCase object + static void PrintXmlTestCase(::std::ostream* stream, + const TestCase& test_case); + + // Prints an XML summary of unit_test to output stream out. + static void PrintXmlUnitTest(::std::ostream* stream, + const UnitTest& unit_test); + + // Produces a string representing the test properties in a result as space + // delimited XML attributes based on the property key="value" pairs. + // When the std::string is not empty, it includes a space at the beginning, + // to delimit this attribute from prior attributes. + static std::string TestPropertiesAsXmlAttributes(const TestResult& result); + + // The output file. + const std::string output_file_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(XmlUnitTestResultPrinter); +}; + +// Creates a new XmlUnitTestResultPrinter. +XmlUnitTestResultPrinter::XmlUnitTestResultPrinter(const char* output_file) + : output_file_(output_file) { + if (output_file_.c_str() == NULL || output_file_.empty()) { + fprintf(stderr, "XML output file may not be null\n"); + fflush(stderr); + exit(EXIT_FAILURE); + } +} + +// Called after the unit test ends. +void XmlUnitTestResultPrinter::OnTestIterationEnd(const UnitTest& unit_test, + int /*iteration*/) { + FILE* xmlout = NULL; + FilePath output_file(output_file_); + FilePath output_dir(output_file.RemoveFileName()); + + if (output_dir.CreateDirectoriesRecursively()) { + xmlout = posix::FOpen(output_file_.c_str(), "w"); + } + if (xmlout == NULL) { + // TODO(wan): report the reason of the failure. + // + // We don't do it for now as: + // + // 1. There is no urgent need for it. + // 2. It's a bit involved to make the errno variable thread-safe on + // all three operating systems (Linux, Windows, and Mac OS). + // 3. To interpret the meaning of errno in a thread-safe way, + // we need the strerror_r() function, which is not available on + // Windows. + fprintf(stderr, + "Unable to open file \"%s\"\n", + output_file_.c_str()); + fflush(stderr); + exit(EXIT_FAILURE); + } + std::stringstream stream; + PrintXmlUnitTest(&stream, unit_test); + fprintf(xmlout, "%s", StringStreamToString(&stream).c_str()); + fclose(xmlout); +} + +// Returns an XML-escaped copy of the input string str. If is_attribute +// is true, the text is meant to appear as an attribute value, and +// normalizable whitespace is preserved by replacing it with character +// references. +// +// Invalid XML characters in str, if any, are stripped from the output. +// It is expected that most, if not all, of the text processed by this +// module will consist of ordinary English text. +// If this module is ever modified to produce version 1.1 XML output, +// most invalid characters can be retained using character references. +// TODO(wan): It might be nice to have a minimally invasive, human-readable +// escaping scheme for invalid characters, rather than dropping them. +std::string XmlUnitTestResultPrinter::EscapeXml( + const std::string& str, bool is_attribute) { + Message m; + + for (size_t i = 0; i < str.size(); ++i) { + const char ch = str[i]; + switch (ch) { + case '<': + m << "<"; + break; + case '>': + m << ">"; + break; + case '&': + m << "&"; + break; + case '\'': + if (is_attribute) + m << "'"; + else + m << '\''; + break; + case '"': + if (is_attribute) + m << """; + else + m << '"'; + break; + default: + if (IsValidXmlCharacter(ch)) { + if (is_attribute && IsNormalizableWhitespace(ch)) + m << "&#x" << String::FormatByte(static_cast(ch)) + << ";"; + else + m << ch; + } + break; + } + } + + return m.GetString(); +} + +// Returns the given string with all characters invalid in XML removed. +// Currently invalid characters are dropped from the string. An +// alternative is to replace them with certain characters such as . or ?. +std::string XmlUnitTestResultPrinter::RemoveInvalidXmlCharacters( + const std::string& str) { + std::string output; + output.reserve(str.size()); + for (std::string::const_iterator it = str.begin(); it != str.end(); ++it) + if (IsValidXmlCharacter(*it)) + output.push_back(*it); + + return output; +} + +// The following routines generate an XML representation of a UnitTest +// object. +// +// This is how Google Test concepts map to the DTD: +// +// <-- corresponds to a UnitTest object +// <-- corresponds to a TestCase object +// <-- corresponds to a TestInfo object +// ... +// ... +// ... +// <-- individual assertion failures +// +// +// + +// Formats the given time in milliseconds as seconds. +std::string FormatTimeInMillisAsSeconds(TimeInMillis ms) { + ::std::stringstream ss; + ss << ms/1000.0; + return ss.str(); +} + +// Converts the given epoch time in milliseconds to a date string in the ISO +// 8601 format, without the timezone information. +std::string FormatEpochTimeInMillisAsIso8601(TimeInMillis ms) { + // Using non-reentrant version as localtime_r is not portable. + time_t seconds = static_cast(ms / 1000); +#ifdef _MSC_VER +# pragma warning(push) // Saves the current warning state. +# pragma warning(disable:4996) // Temporarily disables warning 4996 + // (function or variable may be unsafe). + const struct tm* const time_struct = localtime(&seconds); // NOLINT +# pragma warning(pop) // Restores the warning state again. +#else + const struct tm* const time_struct = localtime(&seconds); // NOLINT +#endif + if (time_struct == NULL) + return ""; // Invalid ms value + + // YYYY-MM-DDThh:mm:ss + return StreamableToString(time_struct->tm_year + 1900) + "-" + + String::FormatIntWidth2(time_struct->tm_mon + 1) + "-" + + String::FormatIntWidth2(time_struct->tm_mday) + "T" + + String::FormatIntWidth2(time_struct->tm_hour) + ":" + + String::FormatIntWidth2(time_struct->tm_min) + ":" + + String::FormatIntWidth2(time_struct->tm_sec); +} + +// Streams an XML CDATA section, escaping invalid CDATA sequences as needed. +void XmlUnitTestResultPrinter::OutputXmlCDataSection(::std::ostream* stream, + const char* data) { + const char* segment = data; + *stream << ""); + if (next_segment != NULL) { + stream->write( + segment, static_cast(next_segment - segment)); + *stream << "]]>]]>"); + } else { + *stream << segment; + break; + } + } + *stream << "]]>"; +} + +void XmlUnitTestResultPrinter::OutputXmlAttribute( + std::ostream* stream, + const std::string& element_name, + const std::string& name, + const std::string& value) { + const std::vector& allowed_names = + GetReservedAttributesForElement(element_name); + + GTEST_CHECK_(std::find(allowed_names.begin(), allowed_names.end(), name) != + allowed_names.end()) + << "Attribute " << name << " is not allowed for element <" << element_name + << ">."; + + *stream << " " << name << "=\"" << EscapeXmlAttribute(value) << "\""; +} + +// Prints an XML representation of a TestInfo object. +// TODO(wan): There is also value in printing properties with the plain printer. +void XmlUnitTestResultPrinter::OutputXmlTestInfo(::std::ostream* stream, + const char* test_case_name, + const TestInfo& test_info) { + const TestResult& result = *test_info.result(); + const std::string kTestcase = "testcase"; + + *stream << " \n"; + } + const string location = internal::FormatCompilerIndependentFileLocation( + part.file_name(), part.line_number()); + const string summary = location + "\n" + part.summary(); + *stream << " "; + const string detail = location + "\n" + part.message(); + OutputXmlCDataSection(stream, RemoveInvalidXmlCharacters(detail).c_str()); + *stream << "\n"; + } + } + + if (failures == 0) + *stream << " />\n"; + else + *stream << " \n"; +} + +// Prints an XML representation of a TestCase object +void XmlUnitTestResultPrinter::PrintXmlTestCase(std::ostream* stream, + const TestCase& test_case) { + const std::string kTestsuite = "testsuite"; + *stream << " <" << kTestsuite; + OutputXmlAttribute(stream, kTestsuite, "name", test_case.name()); + OutputXmlAttribute(stream, kTestsuite, "tests", + StreamableToString(test_case.reportable_test_count())); + OutputXmlAttribute(stream, kTestsuite, "failures", + StreamableToString(test_case.failed_test_count())); + OutputXmlAttribute( + stream, kTestsuite, "disabled", + StreamableToString(test_case.reportable_disabled_test_count())); + OutputXmlAttribute(stream, kTestsuite, "errors", "0"); + OutputXmlAttribute(stream, kTestsuite, "time", + FormatTimeInMillisAsSeconds(test_case.elapsed_time())); + *stream << TestPropertiesAsXmlAttributes(test_case.ad_hoc_test_result()) + << ">\n"; + + for (int i = 0; i < test_case.total_test_count(); ++i) { + if (test_case.GetTestInfo(i)->is_reportable()) + OutputXmlTestInfo(stream, test_case.name(), *test_case.GetTestInfo(i)); + } + *stream << " \n"; +} + +// Prints an XML summary of unit_test to output stream out. +void XmlUnitTestResultPrinter::PrintXmlUnitTest(std::ostream* stream, + const UnitTest& unit_test) { + const std::string kTestsuites = "testsuites"; + + *stream << "\n"; + *stream << "<" << kTestsuites; + + OutputXmlAttribute(stream, kTestsuites, "tests", + StreamableToString(unit_test.reportable_test_count())); + OutputXmlAttribute(stream, kTestsuites, "failures", + StreamableToString(unit_test.failed_test_count())); + OutputXmlAttribute( + stream, kTestsuites, "disabled", + StreamableToString(unit_test.reportable_disabled_test_count())); + OutputXmlAttribute(stream, kTestsuites, "errors", "0"); + OutputXmlAttribute( + stream, kTestsuites, "timestamp", + FormatEpochTimeInMillisAsIso8601(unit_test.start_timestamp())); + OutputXmlAttribute(stream, kTestsuites, "time", + FormatTimeInMillisAsSeconds(unit_test.elapsed_time())); + + if (GTEST_FLAG(shuffle)) { + OutputXmlAttribute(stream, kTestsuites, "random_seed", + StreamableToString(unit_test.random_seed())); + } + + *stream << TestPropertiesAsXmlAttributes(unit_test.ad_hoc_test_result()); + + OutputXmlAttribute(stream, kTestsuites, "name", "AllTests"); + *stream << ">\n"; + + for (int i = 0; i < unit_test.total_test_case_count(); ++i) { + if (unit_test.GetTestCase(i)->reportable_test_count() > 0) + PrintXmlTestCase(stream, *unit_test.GetTestCase(i)); + } + *stream << "\n"; +} + +// Produces a string representing the test properties in a result as space +// delimited XML attributes based on the property key="value" pairs. +std::string XmlUnitTestResultPrinter::TestPropertiesAsXmlAttributes( + const TestResult& result) { + Message attributes; + for (int i = 0; i < result.test_property_count(); ++i) { + const TestProperty& property = result.GetTestProperty(i); + attributes << " " << property.key() << "=" + << "\"" << EscapeXmlAttribute(property.value()) << "\""; + } + return attributes.GetString(); +} + +// End XmlUnitTestResultPrinter + +#if GTEST_CAN_STREAM_RESULTS_ + +// Checks if str contains '=', '&', '%' or '\n' characters. If yes, +// replaces them by "%xx" where xx is their hexadecimal value. For +// example, replaces "=" with "%3D". This algorithm is O(strlen(str)) +// in both time and space -- important as the input str may contain an +// arbitrarily long test failure message and stack trace. +string StreamingListener::UrlEncode(const char* str) { + string result; + result.reserve(strlen(str) + 1); + for (char ch = *str; ch != '\0'; ch = *++str) { + switch (ch) { + case '%': + case '=': + case '&': + case '\n': + result.append("%" + String::FormatByte(static_cast(ch))); + break; + default: + result.push_back(ch); + break; + } + } + return result; +} + +void StreamingListener::SocketWriter::MakeConnection() { + GTEST_CHECK_(sockfd_ == -1) + << "MakeConnection() can't be called when there is already a connection."; + + addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; // To allow both IPv4 and IPv6 addresses. + hints.ai_socktype = SOCK_STREAM; + addrinfo* servinfo = NULL; + + // Use the getaddrinfo() to get a linked list of IP addresses for + // the given host name. + const int error_num = getaddrinfo( + host_name_.c_str(), port_num_.c_str(), &hints, &servinfo); + if (error_num != 0) { + GTEST_LOG_(WARNING) << "stream_result_to: getaddrinfo() failed: " + << gai_strerror(error_num); + } + + // Loop through all the results and connect to the first we can. + for (addrinfo* cur_addr = servinfo; sockfd_ == -1 && cur_addr != NULL; + cur_addr = cur_addr->ai_next) { + sockfd_ = socket( + cur_addr->ai_family, cur_addr->ai_socktype, cur_addr->ai_protocol); + if (sockfd_ != -1) { + // Connect the client socket to the server socket. + if (connect(sockfd_, cur_addr->ai_addr, cur_addr->ai_addrlen) == -1) { + close(sockfd_); + sockfd_ = -1; + } + } + } + + freeaddrinfo(servinfo); // all done with this structure + + if (sockfd_ == -1) { + GTEST_LOG_(WARNING) << "stream_result_to: failed to connect to " + << host_name_ << ":" << port_num_; + } +} + +// End of class Streaming Listener +#endif // GTEST_CAN_STREAM_RESULTS__ + +// Class ScopedTrace + +// Pushes the given source file location and message onto a per-thread +// trace stack maintained by Google Test. +ScopedTrace::ScopedTrace(const char* file, int line, const Message& message) + GTEST_LOCK_EXCLUDED_(&UnitTest::mutex_) { + TraceInfo trace; + trace.file = file; + trace.line = line; + trace.message = message.GetString(); + + UnitTest::GetInstance()->PushGTestTrace(trace); +} + +// Pops the info pushed by the c'tor. +ScopedTrace::~ScopedTrace() + GTEST_LOCK_EXCLUDED_(&UnitTest::mutex_) { + UnitTest::GetInstance()->PopGTestTrace(); +} + + +// class OsStackTraceGetter + +// Returns the current OS stack trace as an std::string. Parameters: +// +// max_depth - the maximum number of stack frames to be included +// in the trace. +// skip_count - the number of top frames to be skipped; doesn't count +// against max_depth. +// +string OsStackTraceGetter::CurrentStackTrace(int /* max_depth */, + int /* skip_count */) + GTEST_LOCK_EXCLUDED_(mutex_) { + return ""; +} + +void OsStackTraceGetter::UponLeavingGTest() + GTEST_LOCK_EXCLUDED_(mutex_) { +} + +const char* const +OsStackTraceGetter::kElidedFramesMarker = + "... " GTEST_NAME_ " internal frames ..."; + +// A helper class that creates the premature-exit file in its +// constructor and deletes the file in its destructor. +class ScopedPrematureExitFile { + public: + explicit ScopedPrematureExitFile(const char* premature_exit_filepath) + : premature_exit_filepath_(premature_exit_filepath) { + // If a path to the premature-exit file is specified... + if (premature_exit_filepath != NULL && *premature_exit_filepath != '\0') { + // create the file with a single "0" character in it. I/O + // errors are ignored as there's nothing better we can do and we + // don't want to fail the test because of this. + FILE* pfile = posix::FOpen(premature_exit_filepath, "w"); + fwrite("0", 1, 1, pfile); + fclose(pfile); + } + } + + ~ScopedPrematureExitFile() { + if (premature_exit_filepath_ != NULL && *premature_exit_filepath_ != '\0') { + remove(premature_exit_filepath_); + } + } + + private: + const char* const premature_exit_filepath_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(ScopedPrematureExitFile); +}; + +} // namespace internal + +// class TestEventListeners + +TestEventListeners::TestEventListeners() + : repeater_(new internal::TestEventRepeater()), + default_result_printer_(NULL), + default_xml_generator_(NULL) { +} + +TestEventListeners::~TestEventListeners() { delete repeater_; } + +// Returns the standard listener responsible for the default console +// output. Can be removed from the listeners list to shut down default +// console output. Note that removing this object from the listener list +// with Release transfers its ownership to the user. +void TestEventListeners::Append(TestEventListener* listener) { + repeater_->Append(listener); +} + +// Removes the given event listener from the list and returns it. It then +// becomes the caller's responsibility to delete the listener. Returns +// NULL if the listener is not found in the list. +TestEventListener* TestEventListeners::Release(TestEventListener* listener) { + if (listener == default_result_printer_) + default_result_printer_ = NULL; + else if (listener == default_xml_generator_) + default_xml_generator_ = NULL; + return repeater_->Release(listener); +} + +// Returns repeater that broadcasts the TestEventListener events to all +// subscribers. +TestEventListener* TestEventListeners::repeater() { return repeater_; } + +// Sets the default_result_printer attribute to the provided listener. +// The listener is also added to the listener list and previous +// default_result_printer is removed from it and deleted. The listener can +// also be NULL in which case it will not be added to the list. Does +// nothing if the previous and the current listener objects are the same. +void TestEventListeners::SetDefaultResultPrinter(TestEventListener* listener) { + if (default_result_printer_ != listener) { + // It is an error to pass this method a listener that is already in the + // list. + delete Release(default_result_printer_); + default_result_printer_ = listener; + if (listener != NULL) + Append(listener); + } +} + +// Sets the default_xml_generator attribute to the provided listener. The +// listener is also added to the listener list and previous +// default_xml_generator is removed from it and deleted. The listener can +// also be NULL in which case it will not be added to the list. Does +// nothing if the previous and the current listener objects are the same. +void TestEventListeners::SetDefaultXmlGenerator(TestEventListener* listener) { + if (default_xml_generator_ != listener) { + // It is an error to pass this method a listener that is already in the + // list. + delete Release(default_xml_generator_); + default_xml_generator_ = listener; + if (listener != NULL) + Append(listener); + } +} + +// Controls whether events will be forwarded by the repeater to the +// listeners in the list. +bool TestEventListeners::EventForwardingEnabled() const { + return repeater_->forwarding_enabled(); +} + +void TestEventListeners::SuppressEventForwarding() { + repeater_->set_forwarding_enabled(false); +} + +// class UnitTest + +// Gets the singleton UnitTest object. The first time this method is +// called, a UnitTest object is constructed and returned. Consecutive +// calls will return the same object. +// +// We don't protect this under mutex_ as a user is not supposed to +// call this before main() starts, from which point on the return +// value will never change. +UnitTest* UnitTest::GetInstance() { + // When compiled with MSVC 7.1 in optimized mode, destroying the + // UnitTest object upon exiting the program messes up the exit code, + // causing successful tests to appear failed. We have to use a + // different implementation in this case to bypass the compiler bug. + // This implementation makes the compiler happy, at the cost of + // leaking the UnitTest object. + + // CodeGear C++Builder insists on a public destructor for the + // default implementation. Use this implementation to keep good OO + // design with private destructor. + +#if (_MSC_VER == 1310 && !defined(_DEBUG)) || defined(__BORLANDC__) + static UnitTest* const instance = new UnitTest; + return instance; +#else + static UnitTest instance; + return &instance; +#endif // (_MSC_VER == 1310 && !defined(_DEBUG)) || defined(__BORLANDC__) +} + +// Gets the number of successful test cases. +int UnitTest::successful_test_case_count() const { + return impl()->successful_test_case_count(); +} + +// Gets the number of failed test cases. +int UnitTest::failed_test_case_count() const { + return impl()->failed_test_case_count(); +} + +// Gets the number of all test cases. +int UnitTest::total_test_case_count() const { + return impl()->total_test_case_count(); +} + +// Gets the number of all test cases that contain at least one test +// that should run. +int UnitTest::test_case_to_run_count() const { + return impl()->test_case_to_run_count(); +} + +// Gets the number of successful tests. +int UnitTest::successful_test_count() const { + return impl()->successful_test_count(); +} + +// Gets the number of failed tests. +int UnitTest::failed_test_count() const { return impl()->failed_test_count(); } + +// Gets the number of disabled tests that will be reported in the XML report. +int UnitTest::reportable_disabled_test_count() const { + return impl()->reportable_disabled_test_count(); +} + +// Gets the number of disabled tests. +int UnitTest::disabled_test_count() const { + return impl()->disabled_test_count(); +} + +// Gets the number of tests to be printed in the XML report. +int UnitTest::reportable_test_count() const { + return impl()->reportable_test_count(); +} + +// Gets the number of all tests. +int UnitTest::total_test_count() const { return impl()->total_test_count(); } + +// Gets the number of tests that should run. +int UnitTest::test_to_run_count() const { return impl()->test_to_run_count(); } + +// Gets the time of the test program start, in ms from the start of the +// UNIX epoch. +internal::TimeInMillis UnitTest::start_timestamp() const { + return impl()->start_timestamp(); +} + +// Gets the elapsed time, in milliseconds. +internal::TimeInMillis UnitTest::elapsed_time() const { + return impl()->elapsed_time(); +} + +// Returns true iff the unit test passed (i.e. all test cases passed). +bool UnitTest::Passed() const { return impl()->Passed(); } + +// Returns true iff the unit test failed (i.e. some test case failed +// or something outside of all tests failed). +bool UnitTest::Failed() const { return impl()->Failed(); } + +// Gets the i-th test case among all the test cases. i can range from 0 to +// total_test_case_count() - 1. If i is not in that range, returns NULL. +const TestCase* UnitTest::GetTestCase(int i) const { + return impl()->GetTestCase(i); +} + +// Returns the TestResult containing information on test failures and +// properties logged outside of individual test cases. +const TestResult& UnitTest::ad_hoc_test_result() const { + return *impl()->ad_hoc_test_result(); +} + +// Gets the i-th test case among all the test cases. i can range from 0 to +// total_test_case_count() - 1. If i is not in that range, returns NULL. +TestCase* UnitTest::GetMutableTestCase(int i) { + return impl()->GetMutableTestCase(i); +} + +// Returns the list of event listeners that can be used to track events +// inside Google Test. +TestEventListeners& UnitTest::listeners() { + return *impl()->listeners(); +} + +// Registers and returns a global test environment. When a test +// program is run, all global test environments will be set-up in the +// order they were registered. After all tests in the program have +// finished, all global test environments will be torn-down in the +// *reverse* order they were registered. +// +// The UnitTest object takes ownership of the given environment. +// +// We don't protect this under mutex_, as we only support calling it +// from the main thread. +Environment* UnitTest::AddEnvironment(Environment* env) { + if (env == NULL) { + return NULL; + } + + impl_->environments().push_back(env); + return env; +} + +// Adds a TestPartResult to the current TestResult object. All Google Test +// assertion macros (e.g. ASSERT_TRUE, EXPECT_EQ, etc) eventually call +// this to report their results. The user code should use the +// assertion macros instead of calling this directly. +void UnitTest::AddTestPartResult( + TestPartResult::Type result_type, + const char* file_name, + int line_number, + const std::string& message, + const std::string& os_stack_trace) GTEST_LOCK_EXCLUDED_(mutex_) { + Message msg; + msg << message; + + internal::MutexLock lock(&mutex_); + if (impl_->gtest_trace_stack().size() > 0) { + msg << "\n" << GTEST_NAME_ << " trace:"; + + for (int i = static_cast(impl_->gtest_trace_stack().size()); + i > 0; --i) { + const internal::TraceInfo& trace = impl_->gtest_trace_stack()[i - 1]; + msg << "\n" << internal::FormatFileLocation(trace.file, trace.line) + << " " << trace.message; + } + } + + if (os_stack_trace.c_str() != NULL && !os_stack_trace.empty()) { + msg << internal::kStackTraceMarker << os_stack_trace; + } + + const TestPartResult result = + TestPartResult(result_type, file_name, line_number, + msg.GetString().c_str()); + impl_->GetTestPartResultReporterForCurrentThread()-> + ReportTestPartResult(result); + + if (result_type != TestPartResult::kSuccess) { + // gtest_break_on_failure takes precedence over + // gtest_throw_on_failure. This allows a user to set the latter + // in the code (perhaps in order to use Google Test assertions + // with another testing framework) and specify the former on the + // command line for debugging. + if (GTEST_FLAG(break_on_failure)) { +#if GTEST_OS_WINDOWS + // Using DebugBreak on Windows allows gtest to still break into a debugger + // when a failure happens and both the --gtest_break_on_failure and + // the --gtest_catch_exceptions flags are specified. + DebugBreak(); +#else + // Dereference NULL through a volatile pointer to prevent the compiler + // from removing. We use this rather than abort() or __builtin_trap() for + // portability: Symbian doesn't implement abort() well, and some debuggers + // don't correctly trap abort(). + *static_cast(NULL) = 1; +#endif // GTEST_OS_WINDOWS + } else if (GTEST_FLAG(throw_on_failure)) { +#if GTEST_HAS_EXCEPTIONS + throw internal::GoogleTestFailureException(result); +#else + // We cannot call abort() as it generates a pop-up in debug mode + // that cannot be suppressed in VC 7.1 or below. + exit(1); +#endif + } + } +} + +// Adds a TestProperty to the current TestResult object when invoked from +// inside a test, to current TestCase's ad_hoc_test_result_ when invoked +// from SetUpTestCase or TearDownTestCase, or to the global property set +// when invoked elsewhere. If the result already contains a property with +// the same key, the value will be updated. +void UnitTest::RecordProperty(const std::string& key, + const std::string& value) { + impl_->RecordProperty(TestProperty(key, value)); +} + +// Runs all tests in this UnitTest object and prints the result. +// Returns 0 if successful, or 1 otherwise. +// +// We don't protect this under mutex_, as we only support calling it +// from the main thread. +int UnitTest::Run() { + const bool in_death_test_child_process = + internal::GTEST_FLAG(internal_run_death_test).length() > 0; + + // Google Test implements this protocol for catching that a test + // program exits before returning control to Google Test: + // + // 1. Upon start, Google Test creates a file whose absolute path + // is specified by the environment variable + // TEST_PREMATURE_EXIT_FILE. + // 2. When Google Test has finished its work, it deletes the file. + // + // This allows a test runner to set TEST_PREMATURE_EXIT_FILE before + // running a Google-Test-based test program and check the existence + // of the file at the end of the test execution to see if it has + // exited prematurely. + + // If we are in the child process of a death test, don't + // create/delete the premature exit file, as doing so is unnecessary + // and will confuse the parent process. Otherwise, create/delete + // the file upon entering/leaving this function. If the program + // somehow exits before this function has a chance to return, the + // premature-exit file will be left undeleted, causing a test runner + // that understands the premature-exit-file protocol to report the + // test as having failed. + const internal::ScopedPrematureExitFile premature_exit_file( + in_death_test_child_process ? + NULL : internal::posix::GetEnv("TEST_PREMATURE_EXIT_FILE")); + + // Captures the value of GTEST_FLAG(catch_exceptions). This value will be + // used for the duration of the program. + impl()->set_catch_exceptions(GTEST_FLAG(catch_exceptions)); + +#if GTEST_HAS_SEH + // Either the user wants Google Test to catch exceptions thrown by the + // tests or this is executing in the context of death test child + // process. In either case the user does not want to see pop-up dialogs + // about crashes - they are expected. + if (impl()->catch_exceptions() || in_death_test_child_process) { +# if !GTEST_OS_WINDOWS_MOBILE + // SetErrorMode doesn't exist on CE. + SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOALIGNMENTFAULTEXCEPT | + SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX); +# endif // !GTEST_OS_WINDOWS_MOBILE + +# if (defined(_MSC_VER) || GTEST_OS_WINDOWS_MINGW) && !GTEST_OS_WINDOWS_MOBILE + // Death test children can be terminated with _abort(). On Windows, + // _abort() can show a dialog with a warning message. This forces the + // abort message to go to stderr instead. + _set_error_mode(_OUT_TO_STDERR); +# endif + +# if _MSC_VER >= 1400 && !GTEST_OS_WINDOWS_MOBILE + // In the debug version, Visual Studio pops up a separate dialog + // offering a choice to debug the aborted program. We need to suppress + // this dialog or it will pop up for every EXPECT/ASSERT_DEATH statement + // executed. Google Test will notify the user of any unexpected + // failure via stderr. + // + // VC++ doesn't define _set_abort_behavior() prior to the version 8.0. + // Users of prior VC versions shall suffer the agony and pain of + // clicking through the countless debug dialogs. + // TODO(vladl@google.com): find a way to suppress the abort dialog() in the + // debug mode when compiled with VC 7.1 or lower. + if (!GTEST_FLAG(break_on_failure)) + _set_abort_behavior( + 0x0, // Clear the following flags: + _WRITE_ABORT_MSG | _CALL_REPORTFAULT); // pop-up window, core dump. +# endif + } +#endif // GTEST_HAS_SEH + + return internal::HandleExceptionsInMethodIfSupported( + impl(), + &internal::UnitTestImpl::RunAllTests, + "auxiliary test code (environments or event listeners)") ? 0 : 1; +} + +// Returns the working directory when the first TEST() or TEST_F() was +// executed. +const char* UnitTest::original_working_dir() const { + return impl_->original_working_dir_.c_str(); +} + +// Returns the TestCase object for the test that's currently running, +// or NULL if no test is running. +const TestCase* UnitTest::current_test_case() const + GTEST_LOCK_EXCLUDED_(mutex_) { + internal::MutexLock lock(&mutex_); + return impl_->current_test_case(); +} + +// Returns the TestInfo object for the test that's currently running, +// or NULL if no test is running. +const TestInfo* UnitTest::current_test_info() const + GTEST_LOCK_EXCLUDED_(mutex_) { + internal::MutexLock lock(&mutex_); + return impl_->current_test_info(); +} + +// Returns the random seed used at the start of the current test run. +int UnitTest::random_seed() const { return impl_->random_seed(); } + +#if GTEST_HAS_PARAM_TEST +// Returns ParameterizedTestCaseRegistry object used to keep track of +// value-parameterized tests and instantiate and register them. +internal::ParameterizedTestCaseRegistry& + UnitTest::parameterized_test_registry() + GTEST_LOCK_EXCLUDED_(mutex_) { + return impl_->parameterized_test_registry(); +} +#endif // GTEST_HAS_PARAM_TEST + +// Creates an empty UnitTest. +UnitTest::UnitTest() { + impl_ = new internal::UnitTestImpl(this); +} + +// Destructor of UnitTest. +UnitTest::~UnitTest() { + delete impl_; +} + +// Pushes a trace defined by SCOPED_TRACE() on to the per-thread +// Google Test trace stack. +void UnitTest::PushGTestTrace(const internal::TraceInfo& trace) + GTEST_LOCK_EXCLUDED_(mutex_) { + internal::MutexLock lock(&mutex_); + impl_->gtest_trace_stack().push_back(trace); +} + +// Pops a trace from the per-thread Google Test trace stack. +void UnitTest::PopGTestTrace() + GTEST_LOCK_EXCLUDED_(mutex_) { + internal::MutexLock lock(&mutex_); + impl_->gtest_trace_stack().pop_back(); +} + +namespace internal { + +UnitTestImpl::UnitTestImpl(UnitTest* parent) + : parent_(parent), +#ifdef _MSC_VER +# pragma warning(push) // Saves the current warning state. +# pragma warning(disable:4355) // Temporarily disables warning 4355 + // (using this in initializer). + default_global_test_part_result_reporter_(this), + default_per_thread_test_part_result_reporter_(this), +# pragma warning(pop) // Restores the warning state again. +#else + default_global_test_part_result_reporter_(this), + default_per_thread_test_part_result_reporter_(this), +#endif // _MSC_VER + global_test_part_result_repoter_( + &default_global_test_part_result_reporter_), + per_thread_test_part_result_reporter_( + &default_per_thread_test_part_result_reporter_), +#if GTEST_HAS_PARAM_TEST + parameterized_test_registry_(), + parameterized_tests_registered_(false), +#endif // GTEST_HAS_PARAM_TEST + last_death_test_case_(-1), + current_test_case_(NULL), + current_test_info_(NULL), + ad_hoc_test_result_(), + os_stack_trace_getter_(NULL), + post_flag_parse_init_performed_(false), + random_seed_(0), // Will be overridden by the flag before first use. + random_(0), // Will be reseeded before first use. + start_timestamp_(0), + elapsed_time_(0), +#if GTEST_HAS_DEATH_TEST + death_test_factory_(new DefaultDeathTestFactory), +#endif + // Will be overridden by the flag before first use. + catch_exceptions_(false) { + listeners()->SetDefaultResultPrinter(new PrettyUnitTestResultPrinter); +} + +UnitTestImpl::~UnitTestImpl() { + // Deletes every TestCase. + ForEach(test_cases_, internal::Delete); + + // Deletes every Environment. + ForEach(environments_, internal::Delete); + + delete os_stack_trace_getter_; +} + +// Adds a TestProperty to the current TestResult object when invoked in a +// context of a test, to current test case's ad_hoc_test_result when invoke +// from SetUpTestCase/TearDownTestCase, or to the global property set +// otherwise. If the result already contains a property with the same key, +// the value will be updated. +void UnitTestImpl::RecordProperty(const TestProperty& test_property) { + std::string xml_element; + TestResult* test_result; // TestResult appropriate for property recording. + + if (current_test_info_ != NULL) { + xml_element = "testcase"; + test_result = &(current_test_info_->result_); + } else if (current_test_case_ != NULL) { + xml_element = "testsuite"; + test_result = &(current_test_case_->ad_hoc_test_result_); + } else { + xml_element = "testsuites"; + test_result = &ad_hoc_test_result_; + } + test_result->RecordProperty(xml_element, test_property); +} + +#if GTEST_HAS_DEATH_TEST +// Disables event forwarding if the control is currently in a death test +// subprocess. Must not be called before InitGoogleTest. +void UnitTestImpl::SuppressTestEventsIfInSubprocess() { + if (internal_run_death_test_flag_.get() != NULL) + listeners()->SuppressEventForwarding(); +} +#endif // GTEST_HAS_DEATH_TEST + +// Initializes event listeners performing XML output as specified by +// UnitTestOptions. Must not be called before InitGoogleTest. +void UnitTestImpl::ConfigureXmlOutput() { + const std::string& output_format = UnitTestOptions::GetOutputFormat(); + if (output_format == "xml") { + listeners()->SetDefaultXmlGenerator(new XmlUnitTestResultPrinter( + UnitTestOptions::GetAbsolutePathToOutputFile().c_str())); + } else if (output_format != "") { + printf("WARNING: unrecognized output format \"%s\" ignored.\n", + output_format.c_str()); + fflush(stdout); + } +} + +#if GTEST_CAN_STREAM_RESULTS_ +// Initializes event listeners for streaming test results in string form. +// Must not be called before InitGoogleTest. +void UnitTestImpl::ConfigureStreamingOutput() { + const std::string& target = GTEST_FLAG(stream_result_to); + if (!target.empty()) { + const size_t pos = target.find(':'); + if (pos != std::string::npos) { + listeners()->Append(new StreamingListener(target.substr(0, pos), + target.substr(pos+1))); + } else { + printf("WARNING: unrecognized streaming target \"%s\" ignored.\n", + target.c_str()); + fflush(stdout); + } + } +} +#endif // GTEST_CAN_STREAM_RESULTS_ + +// Performs initialization dependent upon flag values obtained in +// ParseGoogleTestFlagsOnly. Is called from InitGoogleTest after the call to +// ParseGoogleTestFlagsOnly. In case a user neglects to call InitGoogleTest +// this function is also called from RunAllTests. Since this function can be +// called more than once, it has to be idempotent. +void UnitTestImpl::PostFlagParsingInit() { + // Ensures that this function does not execute more than once. + if (!post_flag_parse_init_performed_) { + post_flag_parse_init_performed_ = true; + +#if GTEST_HAS_DEATH_TEST + InitDeathTestSubprocessControlInfo(); + SuppressTestEventsIfInSubprocess(); +#endif // GTEST_HAS_DEATH_TEST + + // Registers parameterized tests. This makes parameterized tests + // available to the UnitTest reflection API without running + // RUN_ALL_TESTS. + RegisterParameterizedTests(); + + // Configures listeners for XML output. This makes it possible for users + // to shut down the default XML output before invoking RUN_ALL_TESTS. + ConfigureXmlOutput(); + +#if GTEST_CAN_STREAM_RESULTS_ + // Configures listeners for streaming test results to the specified server. + ConfigureStreamingOutput(); +#endif // GTEST_CAN_STREAM_RESULTS_ + } +} + +// A predicate that checks the name of a TestCase against a known +// value. +// +// This is used for implementation of the UnitTest class only. We put +// it in the anonymous namespace to prevent polluting the outer +// namespace. +// +// TestCaseNameIs is copyable. +class TestCaseNameIs { + public: + // Constructor. + explicit TestCaseNameIs(const std::string& name) + : name_(name) {} + + // Returns true iff the name of test_case matches name_. + bool operator()(const TestCase* test_case) const { + return test_case != NULL && strcmp(test_case->name(), name_.c_str()) == 0; + } + + private: + std::string name_; +}; + +// Finds and returns a TestCase with the given name. If one doesn't +// exist, creates one and returns it. It's the CALLER'S +// RESPONSIBILITY to ensure that this function is only called WHEN THE +// TESTS ARE NOT SHUFFLED. +// +// Arguments: +// +// test_case_name: name of the test case +// type_param: the name of the test case's type parameter, or NULL if +// this is not a typed or a type-parameterized test case. +// set_up_tc: pointer to the function that sets up the test case +// tear_down_tc: pointer to the function that tears down the test case +TestCase* UnitTestImpl::GetTestCase(const char* test_case_name, + const char* type_param, + Test::SetUpTestCaseFunc set_up_tc, + Test::TearDownTestCaseFunc tear_down_tc) { + // Can we find a TestCase with the given name? + const std::vector::const_iterator test_case = + std::find_if(test_cases_.begin(), test_cases_.end(), + TestCaseNameIs(test_case_name)); + + if (test_case != test_cases_.end()) + return *test_case; + + // No. Let's create one. + TestCase* const new_test_case = + new TestCase(test_case_name, type_param, set_up_tc, tear_down_tc); + + // Is this a death test case? + if (internal::UnitTestOptions::MatchesFilter(test_case_name, + kDeathTestCaseFilter)) { + // Yes. Inserts the test case after the last death test case + // defined so far. This only works when the test cases haven't + // been shuffled. Otherwise we may end up running a death test + // after a non-death test. + ++last_death_test_case_; + test_cases_.insert(test_cases_.begin() + last_death_test_case_, + new_test_case); + } else { + // No. Appends to the end of the list. + test_cases_.push_back(new_test_case); + } + + test_case_indices_.push_back(static_cast(test_case_indices_.size())); + return new_test_case; +} + +// Helpers for setting up / tearing down the given environment. They +// are for use in the ForEach() function. +static void SetUpEnvironment(Environment* env) { env->SetUp(); } +static void TearDownEnvironment(Environment* env) { env->TearDown(); } + +// Runs all tests in this UnitTest object, prints the result, and +// returns true if all tests are successful. If any exception is +// thrown during a test, the test is considered to be failed, but the +// rest of the tests will still be run. +// +// When parameterized tests are enabled, it expands and registers +// parameterized tests first in RegisterParameterizedTests(). +// All other functions called from RunAllTests() may safely assume that +// parameterized tests are ready to be counted and run. +bool UnitTestImpl::RunAllTests() { + // Makes sure InitGoogleTest() was called. + if (!GTestIsInitialized()) { + printf("%s", + "\nThis test program did NOT call ::testing::InitGoogleTest " + "before calling RUN_ALL_TESTS(). Please fix it.\n"); + return false; + } + + // Do not run any test if the --help flag was specified. + if (g_help_flag) + return true; + + // Repeats the call to the post-flag parsing initialization in case the + // user didn't call InitGoogleTest. + PostFlagParsingInit(); + + // Even if sharding is not on, test runners may want to use the + // GTEST_SHARD_STATUS_FILE to query whether the test supports the sharding + // protocol. + internal::WriteToShardStatusFileIfNeeded(); + + // True iff we are in a subprocess for running a thread-safe-style + // death test. + bool in_subprocess_for_death_test = false; + +#if GTEST_HAS_DEATH_TEST + in_subprocess_for_death_test = (internal_run_death_test_flag_.get() != NULL); +#endif // GTEST_HAS_DEATH_TEST + + const bool should_shard = ShouldShard(kTestTotalShards, kTestShardIndex, + in_subprocess_for_death_test); + + // Compares the full test names with the filter to decide which + // tests to run. + const bool has_tests_to_run = FilterTests(should_shard + ? HONOR_SHARDING_PROTOCOL + : IGNORE_SHARDING_PROTOCOL) > 0; + + // Lists the tests and exits if the --gtest_list_tests flag was specified. + if (GTEST_FLAG(list_tests)) { + // This must be called *after* FilterTests() has been called. + ListTestsMatchingFilter(); + return true; + } + + random_seed_ = GTEST_FLAG(shuffle) ? + GetRandomSeedFromFlag(GTEST_FLAG(random_seed)) : 0; + + // True iff at least one test has failed. + bool failed = false; + + TestEventListener* repeater = listeners()->repeater(); + + start_timestamp_ = GetTimeInMillis(); + repeater->OnTestProgramStart(*parent_); + + // How many times to repeat the tests? We don't want to repeat them + // when we are inside the subprocess of a death test. + const int repeat = in_subprocess_for_death_test ? 1 : GTEST_FLAG(repeat); + // Repeats forever if the repeat count is negative. + const bool forever = repeat < 0; + for (int i = 0; forever || i != repeat; i++) { + // We want to preserve failures generated by ad-hoc test + // assertions executed before RUN_ALL_TESTS(). + ClearNonAdHocTestResult(); + + const TimeInMillis start = GetTimeInMillis(); + + // Shuffles test cases and tests if requested. + if (has_tests_to_run && GTEST_FLAG(shuffle)) { + random()->Reseed(random_seed_); + // This should be done before calling OnTestIterationStart(), + // such that a test event listener can see the actual test order + // in the event. + ShuffleTests(); + } + + // Tells the unit test event listeners that the tests are about to start. + repeater->OnTestIterationStart(*parent_, i); + + // Runs each test case if there is at least one test to run. + if (has_tests_to_run) { + // Sets up all environments beforehand. + repeater->OnEnvironmentsSetUpStart(*parent_); + ForEach(environments_, SetUpEnvironment); + repeater->OnEnvironmentsSetUpEnd(*parent_); + + // Runs the tests only if there was no fatal failure during global + // set-up. + if (!Test::HasFatalFailure()) { + for (int test_index = 0; test_index < total_test_case_count(); + test_index++) { + GetMutableTestCase(test_index)->Run(); + } + } + + // Tears down all environments in reverse order afterwards. + repeater->OnEnvironmentsTearDownStart(*parent_); + std::for_each(environments_.rbegin(), environments_.rend(), + TearDownEnvironment); + repeater->OnEnvironmentsTearDownEnd(*parent_); + } + + elapsed_time_ = GetTimeInMillis() - start; + + // Tells the unit test event listener that the tests have just finished. + repeater->OnTestIterationEnd(*parent_, i); + + // Gets the result and clears it. + if (!Passed()) { + failed = true; + } + + // Restores the original test order after the iteration. This + // allows the user to quickly repro a failure that happens in the + // N-th iteration without repeating the first (N - 1) iterations. + // This is not enclosed in "if (GTEST_FLAG(shuffle)) { ... }", in + // case the user somehow changes the value of the flag somewhere + // (it's always safe to unshuffle the tests). + UnshuffleTests(); + + if (GTEST_FLAG(shuffle)) { + // Picks a new random seed for each iteration. + random_seed_ = GetNextRandomSeed(random_seed_); + } + } + + repeater->OnTestProgramEnd(*parent_); + + return !failed; +} + +// Reads the GTEST_SHARD_STATUS_FILE environment variable, and creates the file +// if the variable is present. If a file already exists at this location, this +// function will write over it. If the variable is present, but the file cannot +// be created, prints an error and exits. +void WriteToShardStatusFileIfNeeded() { + const char* const test_shard_file = posix::GetEnv(kTestShardStatusFile); + if (test_shard_file != NULL) { + FILE* const file = posix::FOpen(test_shard_file, "w"); + if (file == NULL) { + ColoredPrintf(COLOR_RED, + "Could not write to the test shard status file \"%s\" " + "specified by the %s environment variable.\n", + test_shard_file, kTestShardStatusFile); + fflush(stdout); + exit(EXIT_FAILURE); + } + fclose(file); + } +} + +// Checks whether sharding is enabled by examining the relevant +// environment variable values. If the variables are present, +// but inconsistent (i.e., shard_index >= total_shards), prints +// an error and exits. If in_subprocess_for_death_test, sharding is +// disabled because it must only be applied to the original test +// process. Otherwise, we could filter out death tests we intended to execute. +bool ShouldShard(const char* total_shards_env, + const char* shard_index_env, + bool in_subprocess_for_death_test) { + if (in_subprocess_for_death_test) { + return false; + } + + const Int32 total_shards = Int32FromEnvOrDie(total_shards_env, -1); + const Int32 shard_index = Int32FromEnvOrDie(shard_index_env, -1); + + if (total_shards == -1 && shard_index == -1) { + return false; + } else if (total_shards == -1 && shard_index != -1) { + const Message msg = Message() + << "Invalid environment variables: you have " + << kTestShardIndex << " = " << shard_index + << ", but have left " << kTestTotalShards << " unset.\n"; + ColoredPrintf(COLOR_RED, msg.GetString().c_str()); + fflush(stdout); + exit(EXIT_FAILURE); + } else if (total_shards != -1 && shard_index == -1) { + const Message msg = Message() + << "Invalid environment variables: you have " + << kTestTotalShards << " = " << total_shards + << ", but have left " << kTestShardIndex << " unset.\n"; + ColoredPrintf(COLOR_RED, msg.GetString().c_str()); + fflush(stdout); + exit(EXIT_FAILURE); + } else if (shard_index < 0 || shard_index >= total_shards) { + const Message msg = Message() + << "Invalid environment variables: we require 0 <= " + << kTestShardIndex << " < " << kTestTotalShards + << ", but you have " << kTestShardIndex << "=" << shard_index + << ", " << kTestTotalShards << "=" << total_shards << ".\n"; + ColoredPrintf(COLOR_RED, msg.GetString().c_str()); + fflush(stdout); + exit(EXIT_FAILURE); + } + + return total_shards > 1; +} + +// Parses the environment variable var as an Int32. If it is unset, +// returns default_val. If it is not an Int32, prints an error +// and aborts. +Int32 Int32FromEnvOrDie(const char* var, Int32 default_val) { + const char* str_val = posix::GetEnv(var); + if (str_val == NULL) { + return default_val; + } + + Int32 result; + if (!ParseInt32(Message() << "The value of environment variable " << var, + str_val, &result)) { + exit(EXIT_FAILURE); + } + return result; +} + +// Given the total number of shards, the shard index, and the test id, +// returns true iff the test should be run on this shard. The test id is +// some arbitrary but unique non-negative integer assigned to each test +// method. Assumes that 0 <= shard_index < total_shards. +bool ShouldRunTestOnShard(int total_shards, int shard_index, int test_id) { + return (test_id % total_shards) == shard_index; +} + +// Compares the name of each test with the user-specified filter to +// decide whether the test should be run, then records the result in +// each TestCase and TestInfo object. +// If shard_tests == true, further filters tests based on sharding +// variables in the environment - see +// http://code.google.com/p/googletest/wiki/GoogleTestAdvancedGuide. +// Returns the number of tests that should run. +int UnitTestImpl::FilterTests(ReactionToSharding shard_tests) { + const Int32 total_shards = shard_tests == HONOR_SHARDING_PROTOCOL ? + Int32FromEnvOrDie(kTestTotalShards, -1) : -1; + const Int32 shard_index = shard_tests == HONOR_SHARDING_PROTOCOL ? + Int32FromEnvOrDie(kTestShardIndex, -1) : -1; + + // num_runnable_tests are the number of tests that will + // run across all shards (i.e., match filter and are not disabled). + // num_selected_tests are the number of tests to be run on + // this shard. + int num_runnable_tests = 0; + int num_selected_tests = 0; + for (size_t i = 0; i < test_cases_.size(); i++) { + TestCase* const test_case = test_cases_[i]; + const std::string &test_case_name = test_case->name(); + test_case->set_should_run(false); + + for (size_t j = 0; j < test_case->test_info_list().size(); j++) { + TestInfo* const test_info = test_case->test_info_list()[j]; + const std::string test_name(test_info->name()); + // A test is disabled if test case name or test name matches + // kDisableTestFilter. + const bool is_disabled = + internal::UnitTestOptions::MatchesFilter(test_case_name, + kDisableTestFilter) || + internal::UnitTestOptions::MatchesFilter(test_name, + kDisableTestFilter); + test_info->is_disabled_ = is_disabled; + + const bool matches_filter = + internal::UnitTestOptions::FilterMatchesTest(test_case_name, + test_name); + test_info->matches_filter_ = matches_filter; + + const bool is_runnable = + (GTEST_FLAG(also_run_disabled_tests) || !is_disabled) && + matches_filter; + + const bool is_selected = is_runnable && + (shard_tests == IGNORE_SHARDING_PROTOCOL || + ShouldRunTestOnShard(total_shards, shard_index, + num_runnable_tests)); + + num_runnable_tests += is_runnable; + num_selected_tests += is_selected; + + test_info->should_run_ = is_selected; + test_case->set_should_run(test_case->should_run() || is_selected); + } + } + return num_selected_tests; +} + +// Prints the given C-string on a single line by replacing all '\n' +// characters with string "\\n". If the output takes more than +// max_length characters, only prints the first max_length characters +// and "...". +static void PrintOnOneLine(const char* str, int max_length) { + if (str != NULL) { + for (int i = 0; *str != '\0'; ++str) { + if (i >= max_length) { + printf("..."); + break; + } + if (*str == '\n') { + printf("\\n"); + i += 2; + } else { + printf("%c", *str); + ++i; + } + } + } +} + +// Prints the names of the tests matching the user-specified filter flag. +void UnitTestImpl::ListTestsMatchingFilter() { + // Print at most this many characters for each type/value parameter. + const int kMaxParamLength = 250; + + for (size_t i = 0; i < test_cases_.size(); i++) { + const TestCase* const test_case = test_cases_[i]; + bool printed_test_case_name = false; + + for (size_t j = 0; j < test_case->test_info_list().size(); j++) { + const TestInfo* const test_info = + test_case->test_info_list()[j]; + if (test_info->matches_filter_) { + if (!printed_test_case_name) { + printed_test_case_name = true; + printf("%s.", test_case->name()); + if (test_case->type_param() != NULL) { + printf(" # %s = ", kTypeParamLabel); + // We print the type parameter on a single line to make + // the output easy to parse by a program. + PrintOnOneLine(test_case->type_param(), kMaxParamLength); + } + printf("\n"); + } + printf(" %s", test_info->name()); + if (test_info->value_param() != NULL) { + printf(" # %s = ", kValueParamLabel); + // We print the value parameter on a single line to make the + // output easy to parse by a program. + PrintOnOneLine(test_info->value_param(), kMaxParamLength); + } + printf("\n"); + } + } + } + fflush(stdout); +} + +// Sets the OS stack trace getter. +// +// Does nothing if the input and the current OS stack trace getter are +// the same; otherwise, deletes the old getter and makes the input the +// current getter. +void UnitTestImpl::set_os_stack_trace_getter( + OsStackTraceGetterInterface* getter) { + if (os_stack_trace_getter_ != getter) { + delete os_stack_trace_getter_; + os_stack_trace_getter_ = getter; + } +} + +// Returns the current OS stack trace getter if it is not NULL; +// otherwise, creates an OsStackTraceGetter, makes it the current +// getter, and returns it. +OsStackTraceGetterInterface* UnitTestImpl::os_stack_trace_getter() { + if (os_stack_trace_getter_ == NULL) { + os_stack_trace_getter_ = new OsStackTraceGetter; + } + + return os_stack_trace_getter_; +} + +// Returns the TestResult for the test that's currently running, or +// the TestResult for the ad hoc test if no test is running. +TestResult* UnitTestImpl::current_test_result() { + return current_test_info_ ? + &(current_test_info_->result_) : &ad_hoc_test_result_; +} + +// Shuffles all test cases, and the tests within each test case, +// making sure that death tests are still run first. +void UnitTestImpl::ShuffleTests() { + // Shuffles the death test cases. + ShuffleRange(random(), 0, last_death_test_case_ + 1, &test_case_indices_); + + // Shuffles the non-death test cases. + ShuffleRange(random(), last_death_test_case_ + 1, + static_cast(test_cases_.size()), &test_case_indices_); + + // Shuffles the tests inside each test case. + for (size_t i = 0; i < test_cases_.size(); i++) { + test_cases_[i]->ShuffleTests(random()); + } +} + +// Restores the test cases and tests to their order before the first shuffle. +void UnitTestImpl::UnshuffleTests() { + for (size_t i = 0; i < test_cases_.size(); i++) { + // Unshuffles the tests in each test case. + test_cases_[i]->UnshuffleTests(); + // Resets the index of each test case. + test_case_indices_[i] = static_cast(i); + } +} + +// Returns the current OS stack trace as an std::string. +// +// The maximum number of stack frames to be included is specified by +// the gtest_stack_trace_depth flag. The skip_count parameter +// specifies the number of top frames to be skipped, which doesn't +// count against the number of frames to be included. +// +// For example, if Foo() calls Bar(), which in turn calls +// GetCurrentOsStackTraceExceptTop(..., 1), Foo() will be included in +// the trace but Bar() and GetCurrentOsStackTraceExceptTop() won't. +std::string GetCurrentOsStackTraceExceptTop(UnitTest* /*unit_test*/, + int skip_count) { + // We pass skip_count + 1 to skip this wrapper function in addition + // to what the user really wants to skip. + return GetUnitTestImpl()->CurrentOsStackTraceExceptTop(skip_count + 1); +} + +// Used by the GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_ macro to +// suppress unreachable code warnings. +namespace { +class ClassUniqueToAlwaysTrue {}; +} + +bool IsTrue(bool condition) { return condition; } + +bool AlwaysTrue() { +#if GTEST_HAS_EXCEPTIONS + // This condition is always false so AlwaysTrue() never actually throws, + // but it makes the compiler think that it may throw. + if (IsTrue(false)) + throw ClassUniqueToAlwaysTrue(); +#endif // GTEST_HAS_EXCEPTIONS + return true; +} + +// If *pstr starts with the given prefix, modifies *pstr to be right +// past the prefix and returns true; otherwise leaves *pstr unchanged +// and returns false. None of pstr, *pstr, and prefix can be NULL. +bool SkipPrefix(const char* prefix, const char** pstr) { + const size_t prefix_len = strlen(prefix); + if (strncmp(*pstr, prefix, prefix_len) == 0) { + *pstr += prefix_len; + return true; + } + return false; +} + +// Parses a string as a command line flag. The string should have +// the format "--flag=value". When def_optional is true, the "=value" +// part can be omitted. +// +// Returns the value of the flag, or NULL if the parsing failed. +const char* ParseFlagValue(const char* str, + const char* flag, + bool def_optional) { + // str and flag must not be NULL. + if (str == NULL || flag == NULL) return NULL; + + // The flag must start with "--" followed by GTEST_FLAG_PREFIX_. + const std::string flag_str = std::string("--") + GTEST_FLAG_PREFIX_ + flag; + const size_t flag_len = flag_str.length(); + if (strncmp(str, flag_str.c_str(), flag_len) != 0) return NULL; + + // Skips the flag name. + const char* flag_end = str + flag_len; + + // When def_optional is true, it's OK to not have a "=value" part. + if (def_optional && (flag_end[0] == '\0')) { + return flag_end; + } + + // If def_optional is true and there are more characters after the + // flag name, or if def_optional is false, there must be a '=' after + // the flag name. + if (flag_end[0] != '=') return NULL; + + // Returns the string after "=". + return flag_end + 1; +} + +// Parses a string for a bool flag, in the form of either +// "--flag=value" or "--flag". +// +// In the former case, the value is taken as true as long as it does +// not start with '0', 'f', or 'F'. +// +// In the latter case, the value is taken as true. +// +// On success, stores the value of the flag in *value, and returns +// true. On failure, returns false without changing *value. +bool ParseBoolFlag(const char* str, const char* flag, bool* value) { + // Gets the value of the flag as a string. + const char* const value_str = ParseFlagValue(str, flag, true); + + // Aborts if the parsing failed. + if (value_str == NULL) return false; + + // Converts the string value to a bool. + *value = !(*value_str == '0' || *value_str == 'f' || *value_str == 'F'); + return true; +} + +// Parses a string for an Int32 flag, in the form of +// "--flag=value". +// +// On success, stores the value of the flag in *value, and returns +// true. On failure, returns false without changing *value. +bool ParseInt32Flag(const char* str, const char* flag, Int32* value) { + // Gets the value of the flag as a string. + const char* const value_str = ParseFlagValue(str, flag, false); + + // Aborts if the parsing failed. + if (value_str == NULL) return false; + + // Sets *value to the value of the flag. + return ParseInt32(Message() << "The value of flag --" << flag, + value_str, value); +} + +// Parses a string for a string flag, in the form of +// "--flag=value". +// +// On success, stores the value of the flag in *value, and returns +// true. On failure, returns false without changing *value. +bool ParseStringFlag(const char* str, const char* flag, std::string* value) { + // Gets the value of the flag as a string. + const char* const value_str = ParseFlagValue(str, flag, false); + + // Aborts if the parsing failed. + if (value_str == NULL) return false; + + // Sets *value to the value of the flag. + *value = value_str; + return true; +} + +// Determines whether a string has a prefix that Google Test uses for its +// flags, i.e., starts with GTEST_FLAG_PREFIX_ or GTEST_FLAG_PREFIX_DASH_. +// If Google Test detects that a command line flag has its prefix but is not +// recognized, it will print its help message. Flags starting with +// GTEST_INTERNAL_PREFIX_ followed by "internal_" are considered Google Test +// internal flags and do not trigger the help message. +static bool HasGoogleTestFlagPrefix(const char* str) { + return (SkipPrefix("--", &str) || + SkipPrefix("-", &str) || + SkipPrefix("/", &str)) && + !SkipPrefix(GTEST_FLAG_PREFIX_ "internal_", &str) && + (SkipPrefix(GTEST_FLAG_PREFIX_, &str) || + SkipPrefix(GTEST_FLAG_PREFIX_DASH_, &str)); +} + +// Prints a string containing code-encoded text. The following escape +// sequences can be used in the string to control the text color: +// +// @@ prints a single '@' character. +// @R changes the color to red. +// @G changes the color to green. +// @Y changes the color to yellow. +// @D changes to the default terminal text color. +// +// TODO(wan@google.com): Write tests for this once we add stdout +// capturing to Google Test. +static void PrintColorEncoded(const char* str) { + GTestColor color = COLOR_DEFAULT; // The current color. + + // Conceptually, we split the string into segments divided by escape + // sequences. Then we print one segment at a time. At the end of + // each iteration, the str pointer advances to the beginning of the + // next segment. + for (;;) { + const char* p = strchr(str, '@'); + if (p == NULL) { + ColoredPrintf(color, "%s", str); + return; + } + + ColoredPrintf(color, "%s", std::string(str, p).c_str()); + + const char ch = p[1]; + str = p + 2; + if (ch == '@') { + ColoredPrintf(color, "@"); + } else if (ch == 'D') { + color = COLOR_DEFAULT; + } else if (ch == 'R') { + color = COLOR_RED; + } else if (ch == 'G') { + color = COLOR_GREEN; + } else if (ch == 'Y') { + color = COLOR_YELLOW; + } else { + --str; + } + } +} + +static const char kColorEncodedHelpMessage[] = +"This program contains tests written using " GTEST_NAME_ ". You can use the\n" +"following command line flags to control its behavior:\n" +"\n" +"Test Selection:\n" +" @G--" GTEST_FLAG_PREFIX_ "list_tests@D\n" +" List the names of all tests instead of running them. The name of\n" +" TEST(Foo, Bar) is \"Foo.Bar\".\n" +" @G--" GTEST_FLAG_PREFIX_ "filter=@YPOSTIVE_PATTERNS" + "[@G-@YNEGATIVE_PATTERNS]@D\n" +" Run only the tests whose name matches one of the positive patterns but\n" +" none of the negative patterns. '?' matches any single character; '*'\n" +" matches any substring; ':' separates two patterns.\n" +" @G--" GTEST_FLAG_PREFIX_ "also_run_disabled_tests@D\n" +" Run all disabled tests too.\n" +"\n" +"Test Execution:\n" +" @G--" GTEST_FLAG_PREFIX_ "repeat=@Y[COUNT]@D\n" +" Run the tests repeatedly; use a negative count to repeat forever.\n" +" @G--" GTEST_FLAG_PREFIX_ "shuffle@D\n" +" Randomize tests' orders on every iteration.\n" +" @G--" GTEST_FLAG_PREFIX_ "random_seed=@Y[NUMBER]@D\n" +" Random number seed to use for shuffling test orders (between 1 and\n" +" 99999, or 0 to use a seed based on the current time).\n" +"\n" +"Test Output:\n" +" @G--" GTEST_FLAG_PREFIX_ "color=@Y(@Gyes@Y|@Gno@Y|@Gauto@Y)@D\n" +" Enable/disable colored output. The default is @Gauto@D.\n" +" -@G-" GTEST_FLAG_PREFIX_ "print_time=0@D\n" +" Don't print the elapsed time of each test.\n" +" @G--" GTEST_FLAG_PREFIX_ "output=xml@Y[@G:@YDIRECTORY_PATH@G" + GTEST_PATH_SEP_ "@Y|@G:@YFILE_PATH]@D\n" +" Generate an XML report in the given directory or with the given file\n" +" name. @YFILE_PATH@D defaults to @Gtest_details.xml@D.\n" +#if GTEST_CAN_STREAM_RESULTS_ +" @G--" GTEST_FLAG_PREFIX_ "stream_result_to=@YHOST@G:@YPORT@D\n" +" Stream test results to the given server.\n" +#endif // GTEST_CAN_STREAM_RESULTS_ +"\n" +"Assertion Behavior:\n" +#if GTEST_HAS_DEATH_TEST && !GTEST_OS_WINDOWS +" @G--" GTEST_FLAG_PREFIX_ "death_test_style=@Y(@Gfast@Y|@Gthreadsafe@Y)@D\n" +" Set the default death test style.\n" +#endif // GTEST_HAS_DEATH_TEST && !GTEST_OS_WINDOWS +" @G--" GTEST_FLAG_PREFIX_ "break_on_failure@D\n" +" Turn assertion failures into debugger break-points.\n" +" @G--" GTEST_FLAG_PREFIX_ "throw_on_failure@D\n" +" Turn assertion failures into C++ exceptions.\n" +" @G--" GTEST_FLAG_PREFIX_ "catch_exceptions=0@D\n" +" Do not report exceptions as test failures. Instead, allow them\n" +" to crash the program or throw a pop-up (on Windows).\n" +"\n" +"Except for @G--" GTEST_FLAG_PREFIX_ "list_tests@D, you can alternatively set " + "the corresponding\n" +"environment variable of a flag (all letters in upper-case). For example, to\n" +"disable colored text output, you can either specify @G--" GTEST_FLAG_PREFIX_ + "color=no@D or set\n" +"the @G" GTEST_FLAG_PREFIX_UPPER_ "COLOR@D environment variable to @Gno@D.\n" +"\n" +"For more information, please read the " GTEST_NAME_ " documentation at\n" +"@G" GTEST_PROJECT_URL_ "@D. If you find a bug in " GTEST_NAME_ "\n" +"(not one in your own code or tests), please report it to\n" +"@G<" GTEST_DEV_EMAIL_ ">@D.\n"; + +// Parses the command line for Google Test flags, without initializing +// other parts of Google Test. The type parameter CharType can be +// instantiated to either char or wchar_t. +template +void ParseGoogleTestFlagsOnlyImpl(int* argc, CharType** argv) { + for (int i = 1; i < *argc; i++) { + const std::string arg_string = StreamableToString(argv[i]); + const char* const arg = arg_string.c_str(); + + using internal::ParseBoolFlag; + using internal::ParseInt32Flag; + using internal::ParseStringFlag; + + // Do we see a Google Test flag? + if (ParseBoolFlag(arg, kAlsoRunDisabledTestsFlag, + >EST_FLAG(also_run_disabled_tests)) || + ParseBoolFlag(arg, kBreakOnFailureFlag, + >EST_FLAG(break_on_failure)) || + ParseBoolFlag(arg, kCatchExceptionsFlag, + >EST_FLAG(catch_exceptions)) || + ParseStringFlag(arg, kColorFlag, >EST_FLAG(color)) || + ParseStringFlag(arg, kDeathTestStyleFlag, + >EST_FLAG(death_test_style)) || + ParseBoolFlag(arg, kDeathTestUseFork, + >EST_FLAG(death_test_use_fork)) || + ParseStringFlag(arg, kFilterFlag, >EST_FLAG(filter)) || + ParseStringFlag(arg, kInternalRunDeathTestFlag, + >EST_FLAG(internal_run_death_test)) || + ParseBoolFlag(arg, kListTestsFlag, >EST_FLAG(list_tests)) || + ParseStringFlag(arg, kOutputFlag, >EST_FLAG(output)) || + ParseBoolFlag(arg, kPrintTimeFlag, >EST_FLAG(print_time)) || + ParseInt32Flag(arg, kRandomSeedFlag, >EST_FLAG(random_seed)) || + ParseInt32Flag(arg, kRepeatFlag, >EST_FLAG(repeat)) || + ParseBoolFlag(arg, kShuffleFlag, >EST_FLAG(shuffle)) || + ParseInt32Flag(arg, kStackTraceDepthFlag, + >EST_FLAG(stack_trace_depth)) || + ParseStringFlag(arg, kStreamResultToFlag, + >EST_FLAG(stream_result_to)) || + ParseBoolFlag(arg, kThrowOnFailureFlag, + >EST_FLAG(throw_on_failure)) + ) { + // Yes. Shift the remainder of the argv list left by one. Note + // that argv has (*argc + 1) elements, the last one always being + // NULL. The following loop moves the trailing NULL element as + // well. + for (int j = i; j != *argc; j++) { + argv[j] = argv[j + 1]; + } + + // Decrements the argument count. + (*argc)--; + + // We also need to decrement the iterator as we just removed + // an element. + i--; + } else if (arg_string == "--help" || arg_string == "-h" || + arg_string == "-?" || arg_string == "/?" || + HasGoogleTestFlagPrefix(arg)) { + // Both help flag and unrecognized Google Test flags (excluding + // internal ones) trigger help display. + g_help_flag = true; + } + } + + if (g_help_flag) { + // We print the help here instead of in RUN_ALL_TESTS(), as the + // latter may not be called at all if the user is using Google + // Test with another testing framework. + PrintColorEncoded(kColorEncodedHelpMessage); + } +} + +// Parses the command line for Google Test flags, without initializing +// other parts of Google Test. +void ParseGoogleTestFlagsOnly(int* argc, char** argv) { + ParseGoogleTestFlagsOnlyImpl(argc, argv); +} +void ParseGoogleTestFlagsOnly(int* argc, wchar_t** argv) { + ParseGoogleTestFlagsOnlyImpl(argc, argv); +} + +// The internal implementation of InitGoogleTest(). +// +// The type parameter CharType can be instantiated to either char or +// wchar_t. +template +void InitGoogleTestImpl(int* argc, CharType** argv) { + g_init_gtest_count++; + + // We don't want to run the initialization code twice. + if (g_init_gtest_count != 1) return; + + if (*argc <= 0) return; + + internal::g_executable_path = internal::StreamableToString(argv[0]); + +#if GTEST_HAS_DEATH_TEST + + g_argvs.clear(); + for (int i = 0; i != *argc; i++) { + g_argvs.push_back(StreamableToString(argv[i])); + } + +#endif // GTEST_HAS_DEATH_TEST + + ParseGoogleTestFlagsOnly(argc, argv); + GetUnitTestImpl()->PostFlagParsingInit(); +} + +} // namespace internal + +// Initializes Google Test. This must be called before calling +// RUN_ALL_TESTS(). In particular, it parses a command line for the +// flags that Google Test recognizes. Whenever a Google Test flag is +// seen, it is removed from argv, and *argc is decremented. +// +// No value is returned. Instead, the Google Test flag variables are +// updated. +// +// Calling the function for the second time has no user-visible effect. +void InitGoogleTest(int* argc, char** argv) { + internal::InitGoogleTestImpl(argc, argv); +} + +// This overloaded version can be used in Windows programs compiled in +// UNICODE mode. +void InitGoogleTest(int* argc, wchar_t** argv) { + internal::InitGoogleTestImpl(argc, argv); +} + +} // namespace testing diff --git a/vendor/gmock-1.7.0/gtest/src/gtest_main.cc b/vendor/gmock-1.7.0/gtest/src/gtest_main.cc new file mode 100644 index 0000000000..f302822552 --- /dev/null +++ b/vendor/gmock-1.7.0/gtest/src/gtest_main.cc @@ -0,0 +1,38 @@ +// Copyright 2006, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include + +#include "gtest/gtest.h" + +GTEST_API_ int main(int argc, char **argv) { + printf("Running main() from gtest_main.cc\n"); + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/vendor/gmock-1.7.0/include/gmock/gmock-actions.h b/vendor/gmock-1.7.0/include/gmock/gmock-actions.h new file mode 100644 index 0000000000..7e9708ec29 --- /dev/null +++ b/vendor/gmock-1.7.0/include/gmock/gmock-actions.h @@ -0,0 +1,1078 @@ +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) + +// Google Mock - a framework for writing C++ mock classes. +// +// This file implements some commonly used actions. + +#ifndef GMOCK_INCLUDE_GMOCK_GMOCK_ACTIONS_H_ +#define GMOCK_INCLUDE_GMOCK_GMOCK_ACTIONS_H_ + +#ifndef _WIN32_WCE +# include +#endif + +#include +#include + +#include "gmock/internal/gmock-internal-utils.h" +#include "gmock/internal/gmock-port.h" + +namespace testing { + +// To implement an action Foo, define: +// 1. a class FooAction that implements the ActionInterface interface, and +// 2. a factory function that creates an Action object from a +// const FooAction*. +// +// The two-level delegation design follows that of Matcher, providing +// consistency for extension developers. It also eases ownership +// management as Action objects can now be copied like plain values. + +namespace internal { + +template +class ActionAdaptor; + +// BuiltInDefaultValue::Get() returns the "built-in" default +// value for type T, which is NULL when T is a pointer type, 0 when T +// is a numeric type, false when T is bool, or "" when T is string or +// std::string. For any other type T, this value is undefined and the +// function will abort the process. +template +class BuiltInDefaultValue { + public: + // This function returns true iff type T has a built-in default value. + static bool Exists() { return false; } + static T Get() { + Assert(false, __FILE__, __LINE__, + "Default action undefined for the function return type."); + return internal::Invalid(); + // The above statement will never be reached, but is required in + // order for this function to compile. + } +}; + +// This partial specialization says that we use the same built-in +// default value for T and const T. +template +class BuiltInDefaultValue { + public: + static bool Exists() { return BuiltInDefaultValue::Exists(); } + static T Get() { return BuiltInDefaultValue::Get(); } +}; + +// This partial specialization defines the default values for pointer +// types. +template +class BuiltInDefaultValue { + public: + static bool Exists() { return true; } + static T* Get() { return NULL; } +}; + +// The following specializations define the default values for +// specific types we care about. +#define GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(type, value) \ + template <> \ + class BuiltInDefaultValue { \ + public: \ + static bool Exists() { return true; } \ + static type Get() { return value; } \ + } + +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(void, ); // NOLINT +#if GTEST_HAS_GLOBAL_STRING +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(::string, ""); +#endif // GTEST_HAS_GLOBAL_STRING +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(::std::string, ""); +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(bool, false); +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(unsigned char, '\0'); +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(signed char, '\0'); +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(char, '\0'); + +// There's no need for a default action for signed wchar_t, as that +// type is the same as wchar_t for gcc, and invalid for MSVC. +// +// There's also no need for a default action for unsigned wchar_t, as +// that type is the same as unsigned int for gcc, and invalid for +// MSVC. +#if GMOCK_WCHAR_T_IS_NATIVE_ +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(wchar_t, 0U); // NOLINT +#endif + +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(unsigned short, 0U); // NOLINT +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(signed short, 0); // NOLINT +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(unsigned int, 0U); +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(signed int, 0); +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(unsigned long, 0UL); // NOLINT +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(signed long, 0L); // NOLINT +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(UInt64, 0); +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(Int64, 0); +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(float, 0); +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(double, 0); + +#undef GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_ + +} // namespace internal + +// When an unexpected function call is encountered, Google Mock will +// let it return a default value if the user has specified one for its +// return type, or if the return type has a built-in default value; +// otherwise Google Mock won't know what value to return and will have +// to abort the process. +// +// The DefaultValue class allows a user to specify the +// default value for a type T that is both copyable and publicly +// destructible (i.e. anything that can be used as a function return +// type). The usage is: +// +// // Sets the default value for type T to be foo. +// DefaultValue::Set(foo); +template +class DefaultValue { + public: + // Sets the default value for type T; requires T to be + // copy-constructable and have a public destructor. + static void Set(T x) { + delete value_; + value_ = new T(x); + } + + // Unsets the default value for type T. + static void Clear() { + delete value_; + value_ = NULL; + } + + // Returns true iff the user has set the default value for type T. + static bool IsSet() { return value_ != NULL; } + + // Returns true if T has a default return value set by the user or there + // exists a built-in default value. + static bool Exists() { + return IsSet() || internal::BuiltInDefaultValue::Exists(); + } + + // Returns the default value for type T if the user has set one; + // otherwise returns the built-in default value if there is one; + // otherwise aborts the process. + static T Get() { + return value_ == NULL ? + internal::BuiltInDefaultValue::Get() : *value_; + } + + private: + static const T* value_; +}; + +// This partial specialization allows a user to set default values for +// reference types. +template +class DefaultValue { + public: + // Sets the default value for type T&. + static void Set(T& x) { // NOLINT + address_ = &x; + } + + // Unsets the default value for type T&. + static void Clear() { + address_ = NULL; + } + + // Returns true iff the user has set the default value for type T&. + static bool IsSet() { return address_ != NULL; } + + // Returns true if T has a default return value set by the user or there + // exists a built-in default value. + static bool Exists() { + return IsSet() || internal::BuiltInDefaultValue::Exists(); + } + + // Returns the default value for type T& if the user has set one; + // otherwise returns the built-in default value if there is one; + // otherwise aborts the process. + static T& Get() { + return address_ == NULL ? + internal::BuiltInDefaultValue::Get() : *address_; + } + + private: + static T* address_; +}; + +// This specialization allows DefaultValue::Get() to +// compile. +template <> +class DefaultValue { + public: + static bool Exists() { return true; } + static void Get() {} +}; + +// Points to the user-set default value for type T. +template +const T* DefaultValue::value_ = NULL; + +// Points to the user-set default value for type T&. +template +T* DefaultValue::address_ = NULL; + +// Implement this interface to define an action for function type F. +template +class ActionInterface { + public: + typedef typename internal::Function::Result Result; + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + ActionInterface() {} + virtual ~ActionInterface() {} + + // Performs the action. This method is not const, as in general an + // action can have side effects and be stateful. For example, a + // get-the-next-element-from-the-collection action will need to + // remember the current element. + virtual Result Perform(const ArgumentTuple& args) = 0; + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(ActionInterface); +}; + +// An Action is a copyable and IMMUTABLE (except by assignment) +// object that represents an action to be taken when a mock function +// of type F is called. The implementation of Action is just a +// linked_ptr to const ActionInterface, so copying is fairly cheap. +// Don't inherit from Action! +// +// You can view an object implementing ActionInterface as a +// concrete action (including its current state), and an Action +// object as a handle to it. +template +class Action { + public: + typedef typename internal::Function::Result Result; + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + // Constructs a null Action. Needed for storing Action objects in + // STL containers. + Action() : impl_(NULL) {} + + // Constructs an Action from its implementation. A NULL impl is + // used to represent the "do-default" action. + explicit Action(ActionInterface* impl) : impl_(impl) {} + + // Copy constructor. + Action(const Action& action) : impl_(action.impl_) {} + + // This constructor allows us to turn an Action object into an + // Action, as long as F's arguments can be implicitly converted + // to Func's and Func's return type can be implicitly converted to + // F's. + template + explicit Action(const Action& action); + + // Returns true iff this is the DoDefault() action. + bool IsDoDefault() const { return impl_.get() == NULL; } + + // Performs the action. Note that this method is const even though + // the corresponding method in ActionInterface is not. The reason + // is that a const Action means that it cannot be re-bound to + // another concrete action, not that the concrete action it binds to + // cannot change state. (Think of the difference between a const + // pointer and a pointer to const.) + Result Perform(const ArgumentTuple& args) const { + internal::Assert( + !IsDoDefault(), __FILE__, __LINE__, + "You are using DoDefault() inside a composite action like " + "DoAll() or WithArgs(). This is not supported for technical " + "reasons. Please instead spell out the default action, or " + "assign the default action to an Action variable and use " + "the variable in various places."); + return impl_->Perform(args); + } + + private: + template + friend class internal::ActionAdaptor; + + internal::linked_ptr > impl_; +}; + +// The PolymorphicAction class template makes it easy to implement a +// polymorphic action (i.e. an action that can be used in mock +// functions of than one type, e.g. Return()). +// +// To define a polymorphic action, a user first provides a COPYABLE +// implementation class that has a Perform() method template: +// +// class FooAction { +// public: +// template +// Result Perform(const ArgumentTuple& args) const { +// // Processes the arguments and returns a result, using +// // tr1::get(args) to get the N-th (0-based) argument in the tuple. +// } +// ... +// }; +// +// Then the user creates the polymorphic action using +// MakePolymorphicAction(object) where object has type FooAction. See +// the definition of Return(void) and SetArgumentPointee(value) for +// complete examples. +template +class PolymorphicAction { + public: + explicit PolymorphicAction(const Impl& impl) : impl_(impl) {} + + template + operator Action() const { + return Action(new MonomorphicImpl(impl_)); + } + + private: + template + class MonomorphicImpl : public ActionInterface { + public: + typedef typename internal::Function::Result Result; + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + explicit MonomorphicImpl(const Impl& impl) : impl_(impl) {} + + virtual Result Perform(const ArgumentTuple& args) { + return impl_.template Perform(args); + } + + private: + Impl impl_; + + GTEST_DISALLOW_ASSIGN_(MonomorphicImpl); + }; + + Impl impl_; + + GTEST_DISALLOW_ASSIGN_(PolymorphicAction); +}; + +// Creates an Action from its implementation and returns it. The +// created Action object owns the implementation. +template +Action MakeAction(ActionInterface* impl) { + return Action(impl); +} + +// Creates a polymorphic action from its implementation. This is +// easier to use than the PolymorphicAction constructor as it +// doesn't require you to explicitly write the template argument, e.g. +// +// MakePolymorphicAction(foo); +// vs +// PolymorphicAction(foo); +template +inline PolymorphicAction MakePolymorphicAction(const Impl& impl) { + return PolymorphicAction(impl); +} + +namespace internal { + +// Allows an Action object to pose as an Action, as long as F2 +// and F1 are compatible. +template +class ActionAdaptor : public ActionInterface { + public: + typedef typename internal::Function::Result Result; + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + explicit ActionAdaptor(const Action& from) : impl_(from.impl_) {} + + virtual Result Perform(const ArgumentTuple& args) { + return impl_->Perform(args); + } + + private: + const internal::linked_ptr > impl_; + + GTEST_DISALLOW_ASSIGN_(ActionAdaptor); +}; + +// Implements the polymorphic Return(x) action, which can be used in +// any function that returns the type of x, regardless of the argument +// types. +// +// Note: The value passed into Return must be converted into +// Function::Result when this action is cast to Action rather than +// when that action is performed. This is important in scenarios like +// +// MOCK_METHOD1(Method, T(U)); +// ... +// { +// Foo foo; +// X x(&foo); +// EXPECT_CALL(mock, Method(_)).WillOnce(Return(x)); +// } +// +// In the example above the variable x holds reference to foo which leaves +// scope and gets destroyed. If copying X just copies a reference to foo, +// that copy will be left with a hanging reference. If conversion to T +// makes a copy of foo, the above code is safe. To support that scenario, we +// need to make sure that the type conversion happens inside the EXPECT_CALL +// statement, and conversion of the result of Return to Action is a +// good place for that. +// +template +class ReturnAction { + public: + // Constructs a ReturnAction object from the value to be returned. + // 'value' is passed by value instead of by const reference in order + // to allow Return("string literal") to compile. + explicit ReturnAction(R value) : value_(value) {} + + // This template type conversion operator allows Return(x) to be + // used in ANY function that returns x's type. + template + operator Action() const { + // Assert statement belongs here because this is the best place to verify + // conditions on F. It produces the clearest error messages + // in most compilers. + // Impl really belongs in this scope as a local class but can't + // because MSVC produces duplicate symbols in different translation units + // in this case. Until MS fixes that bug we put Impl into the class scope + // and put the typedef both here (for use in assert statement) and + // in the Impl class. But both definitions must be the same. + typedef typename Function::Result Result; + GTEST_COMPILE_ASSERT_( + !internal::is_reference::value, + use_ReturnRef_instead_of_Return_to_return_a_reference); + return Action(new Impl(value_)); + } + + private: + // Implements the Return(x) action for a particular function type F. + template + class Impl : public ActionInterface { + public: + typedef typename Function::Result Result; + typedef typename Function::ArgumentTuple ArgumentTuple; + + // The implicit cast is necessary when Result has more than one + // single-argument constructor (e.g. Result is std::vector) and R + // has a type conversion operator template. In that case, value_(value) + // won't compile as the compiler doesn't known which constructor of + // Result to call. ImplicitCast_ forces the compiler to convert R to + // Result without considering explicit constructors, thus resolving the + // ambiguity. value_ is then initialized using its copy constructor. + explicit Impl(R value) + : value_(::testing::internal::ImplicitCast_(value)) {} + + virtual Result Perform(const ArgumentTuple&) { return value_; } + + private: + GTEST_COMPILE_ASSERT_(!internal::is_reference::value, + Result_cannot_be_a_reference_type); + Result value_; + + GTEST_DISALLOW_ASSIGN_(Impl); + }; + + R value_; + + GTEST_DISALLOW_ASSIGN_(ReturnAction); +}; + +// Implements the ReturnNull() action. +class ReturnNullAction { + public: + // Allows ReturnNull() to be used in any pointer-returning function. + template + static Result Perform(const ArgumentTuple&) { + GTEST_COMPILE_ASSERT_(internal::is_pointer::value, + ReturnNull_can_be_used_to_return_a_pointer_only); + return NULL; + } +}; + +// Implements the Return() action. +class ReturnVoidAction { + public: + // Allows Return() to be used in any void-returning function. + template + static void Perform(const ArgumentTuple&) { + CompileAssertTypesEqual(); + } +}; + +// Implements the polymorphic ReturnRef(x) action, which can be used +// in any function that returns a reference to the type of x, +// regardless of the argument types. +template +class ReturnRefAction { + public: + // Constructs a ReturnRefAction object from the reference to be returned. + explicit ReturnRefAction(T& ref) : ref_(ref) {} // NOLINT + + // This template type conversion operator allows ReturnRef(x) to be + // used in ANY function that returns a reference to x's type. + template + operator Action() const { + typedef typename Function::Result Result; + // Asserts that the function return type is a reference. This + // catches the user error of using ReturnRef(x) when Return(x) + // should be used, and generates some helpful error message. + GTEST_COMPILE_ASSERT_(internal::is_reference::value, + use_Return_instead_of_ReturnRef_to_return_a_value); + return Action(new Impl(ref_)); + } + + private: + // Implements the ReturnRef(x) action for a particular function type F. + template + class Impl : public ActionInterface { + public: + typedef typename Function::Result Result; + typedef typename Function::ArgumentTuple ArgumentTuple; + + explicit Impl(T& ref) : ref_(ref) {} // NOLINT + + virtual Result Perform(const ArgumentTuple&) { + return ref_; + } + + private: + T& ref_; + + GTEST_DISALLOW_ASSIGN_(Impl); + }; + + T& ref_; + + GTEST_DISALLOW_ASSIGN_(ReturnRefAction); +}; + +// Implements the polymorphic ReturnRefOfCopy(x) action, which can be +// used in any function that returns a reference to the type of x, +// regardless of the argument types. +template +class ReturnRefOfCopyAction { + public: + // Constructs a ReturnRefOfCopyAction object from the reference to + // be returned. + explicit ReturnRefOfCopyAction(const T& value) : value_(value) {} // NOLINT + + // This template type conversion operator allows ReturnRefOfCopy(x) to be + // used in ANY function that returns a reference to x's type. + template + operator Action() const { + typedef typename Function::Result Result; + // Asserts that the function return type is a reference. This + // catches the user error of using ReturnRefOfCopy(x) when Return(x) + // should be used, and generates some helpful error message. + GTEST_COMPILE_ASSERT_( + internal::is_reference::value, + use_Return_instead_of_ReturnRefOfCopy_to_return_a_value); + return Action(new Impl(value_)); + } + + private: + // Implements the ReturnRefOfCopy(x) action for a particular function type F. + template + class Impl : public ActionInterface { + public: + typedef typename Function::Result Result; + typedef typename Function::ArgumentTuple ArgumentTuple; + + explicit Impl(const T& value) : value_(value) {} // NOLINT + + virtual Result Perform(const ArgumentTuple&) { + return value_; + } + + private: + T value_; + + GTEST_DISALLOW_ASSIGN_(Impl); + }; + + const T value_; + + GTEST_DISALLOW_ASSIGN_(ReturnRefOfCopyAction); +}; + +// Implements the polymorphic DoDefault() action. +class DoDefaultAction { + public: + // This template type conversion operator allows DoDefault() to be + // used in any function. + template + operator Action() const { return Action(NULL); } +}; + +// Implements the Assign action to set a given pointer referent to a +// particular value. +template +class AssignAction { + public: + AssignAction(T1* ptr, T2 value) : ptr_(ptr), value_(value) {} + + template + void Perform(const ArgumentTuple& /* args */) const { + *ptr_ = value_; + } + + private: + T1* const ptr_; + const T2 value_; + + GTEST_DISALLOW_ASSIGN_(AssignAction); +}; + +#if !GTEST_OS_WINDOWS_MOBILE + +// Implements the SetErrnoAndReturn action to simulate return from +// various system calls and libc functions. +template +class SetErrnoAndReturnAction { + public: + SetErrnoAndReturnAction(int errno_value, T result) + : errno_(errno_value), + result_(result) {} + template + Result Perform(const ArgumentTuple& /* args */) const { + errno = errno_; + return result_; + } + + private: + const int errno_; + const T result_; + + GTEST_DISALLOW_ASSIGN_(SetErrnoAndReturnAction); +}; + +#endif // !GTEST_OS_WINDOWS_MOBILE + +// Implements the SetArgumentPointee(x) action for any function +// whose N-th argument (0-based) is a pointer to x's type. The +// template parameter kIsProto is true iff type A is ProtocolMessage, +// proto2::Message, or a sub-class of those. +template +class SetArgumentPointeeAction { + public: + // Constructs an action that sets the variable pointed to by the + // N-th function argument to 'value'. + explicit SetArgumentPointeeAction(const A& value) : value_(value) {} + + template + void Perform(const ArgumentTuple& args) const { + CompileAssertTypesEqual(); + *::std::tr1::get(args) = value_; + } + + private: + const A value_; + + GTEST_DISALLOW_ASSIGN_(SetArgumentPointeeAction); +}; + +template +class SetArgumentPointeeAction { + public: + // Constructs an action that sets the variable pointed to by the + // N-th function argument to 'proto'. Both ProtocolMessage and + // proto2::Message have the CopyFrom() method, so the same + // implementation works for both. + explicit SetArgumentPointeeAction(const Proto& proto) : proto_(new Proto) { + proto_->CopyFrom(proto); + } + + template + void Perform(const ArgumentTuple& args) const { + CompileAssertTypesEqual(); + ::std::tr1::get(args)->CopyFrom(*proto_); + } + + private: + const internal::linked_ptr proto_; + + GTEST_DISALLOW_ASSIGN_(SetArgumentPointeeAction); +}; + +// Implements the InvokeWithoutArgs(f) action. The template argument +// FunctionImpl is the implementation type of f, which can be either a +// function pointer or a functor. InvokeWithoutArgs(f) can be used as an +// Action as long as f's type is compatible with F (i.e. f can be +// assigned to a tr1::function). +template +class InvokeWithoutArgsAction { + public: + // The c'tor makes a copy of function_impl (either a function + // pointer or a functor). + explicit InvokeWithoutArgsAction(FunctionImpl function_impl) + : function_impl_(function_impl) {} + + // Allows InvokeWithoutArgs(f) to be used as any action whose type is + // compatible with f. + template + Result Perform(const ArgumentTuple&) { return function_impl_(); } + + private: + FunctionImpl function_impl_; + + GTEST_DISALLOW_ASSIGN_(InvokeWithoutArgsAction); +}; + +// Implements the InvokeWithoutArgs(object_ptr, &Class::Method) action. +template +class InvokeMethodWithoutArgsAction { + public: + InvokeMethodWithoutArgsAction(Class* obj_ptr, MethodPtr method_ptr) + : obj_ptr_(obj_ptr), method_ptr_(method_ptr) {} + + template + Result Perform(const ArgumentTuple&) const { + return (obj_ptr_->*method_ptr_)(); + } + + private: + Class* const obj_ptr_; + const MethodPtr method_ptr_; + + GTEST_DISALLOW_ASSIGN_(InvokeMethodWithoutArgsAction); +}; + +// Implements the IgnoreResult(action) action. +template +class IgnoreResultAction { + public: + explicit IgnoreResultAction(const A& action) : action_(action) {} + + template + operator Action() const { + // Assert statement belongs here because this is the best place to verify + // conditions on F. It produces the clearest error messages + // in most compilers. + // Impl really belongs in this scope as a local class but can't + // because MSVC produces duplicate symbols in different translation units + // in this case. Until MS fixes that bug we put Impl into the class scope + // and put the typedef both here (for use in assert statement) and + // in the Impl class. But both definitions must be the same. + typedef typename internal::Function::Result Result; + + // Asserts at compile time that F returns void. + CompileAssertTypesEqual(); + + return Action(new Impl(action_)); + } + + private: + template + class Impl : public ActionInterface { + public: + typedef typename internal::Function::Result Result; + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + explicit Impl(const A& action) : action_(action) {} + + virtual void Perform(const ArgumentTuple& args) { + // Performs the action and ignores its result. + action_.Perform(args); + } + + private: + // Type OriginalFunction is the same as F except that its return + // type is IgnoredValue. + typedef typename internal::Function::MakeResultIgnoredValue + OriginalFunction; + + const Action action_; + + GTEST_DISALLOW_ASSIGN_(Impl); + }; + + const A action_; + + GTEST_DISALLOW_ASSIGN_(IgnoreResultAction); +}; + +// A ReferenceWrapper object represents a reference to type T, +// which can be either const or not. It can be explicitly converted +// from, and implicitly converted to, a T&. Unlike a reference, +// ReferenceWrapper can be copied and can survive template type +// inference. This is used to support by-reference arguments in the +// InvokeArgument(...) action. The idea was from "reference +// wrappers" in tr1, which we don't have in our source tree yet. +template +class ReferenceWrapper { + public: + // Constructs a ReferenceWrapper object from a T&. + explicit ReferenceWrapper(T& l_value) : pointer_(&l_value) {} // NOLINT + + // Allows a ReferenceWrapper object to be implicitly converted to + // a T&. + operator T&() const { return *pointer_; } + private: + T* pointer_; +}; + +// Allows the expression ByRef(x) to be printed as a reference to x. +template +void PrintTo(const ReferenceWrapper& ref, ::std::ostream* os) { + T& value = ref; + UniversalPrinter::Print(value, os); +} + +// Does two actions sequentially. Used for implementing the DoAll(a1, +// a2, ...) action. +template +class DoBothAction { + public: + DoBothAction(Action1 action1, Action2 action2) + : action1_(action1), action2_(action2) {} + + // This template type conversion operator allows DoAll(a1, ..., a_n) + // to be used in ANY function of compatible type. + template + operator Action() const { + return Action(new Impl(action1_, action2_)); + } + + private: + // Implements the DoAll(...) action for a particular function type F. + template + class Impl : public ActionInterface { + public: + typedef typename Function::Result Result; + typedef typename Function::ArgumentTuple ArgumentTuple; + typedef typename Function::MakeResultVoid VoidResult; + + Impl(const Action& action1, const Action& action2) + : action1_(action1), action2_(action2) {} + + virtual Result Perform(const ArgumentTuple& args) { + action1_.Perform(args); + return action2_.Perform(args); + } + + private: + const Action action1_; + const Action action2_; + + GTEST_DISALLOW_ASSIGN_(Impl); + }; + + Action1 action1_; + Action2 action2_; + + GTEST_DISALLOW_ASSIGN_(DoBothAction); +}; + +} // namespace internal + +// An Unused object can be implicitly constructed from ANY value. +// This is handy when defining actions that ignore some or all of the +// mock function arguments. For example, given +// +// MOCK_METHOD3(Foo, double(const string& label, double x, double y)); +// MOCK_METHOD3(Bar, double(int index, double x, double y)); +// +// instead of +// +// double DistanceToOriginWithLabel(const string& label, double x, double y) { +// return sqrt(x*x + y*y); +// } +// double DistanceToOriginWithIndex(int index, double x, double y) { +// return sqrt(x*x + y*y); +// } +// ... +// EXEPCT_CALL(mock, Foo("abc", _, _)) +// .WillOnce(Invoke(DistanceToOriginWithLabel)); +// EXEPCT_CALL(mock, Bar(5, _, _)) +// .WillOnce(Invoke(DistanceToOriginWithIndex)); +// +// you could write +// +// // We can declare any uninteresting argument as Unused. +// double DistanceToOrigin(Unused, double x, double y) { +// return sqrt(x*x + y*y); +// } +// ... +// EXEPCT_CALL(mock, Foo("abc", _, _)).WillOnce(Invoke(DistanceToOrigin)); +// EXEPCT_CALL(mock, Bar(5, _, _)).WillOnce(Invoke(DistanceToOrigin)); +typedef internal::IgnoredValue Unused; + +// This constructor allows us to turn an Action object into an +// Action, as long as To's arguments can be implicitly converted +// to From's and From's return type cann be implicitly converted to +// To's. +template +template +Action::Action(const Action& from) + : impl_(new internal::ActionAdaptor(from)) {} + +// Creates an action that returns 'value'. 'value' is passed by value +// instead of const reference - otherwise Return("string literal") +// will trigger a compiler error about using array as initializer. +template +internal::ReturnAction Return(R value) { + return internal::ReturnAction(value); +} + +// Creates an action that returns NULL. +inline PolymorphicAction ReturnNull() { + return MakePolymorphicAction(internal::ReturnNullAction()); +} + +// Creates an action that returns from a void function. +inline PolymorphicAction Return() { + return MakePolymorphicAction(internal::ReturnVoidAction()); +} + +// Creates an action that returns the reference to a variable. +template +inline internal::ReturnRefAction ReturnRef(R& x) { // NOLINT + return internal::ReturnRefAction(x); +} + +// Creates an action that returns the reference to a copy of the +// argument. The copy is created when the action is constructed and +// lives as long as the action. +template +inline internal::ReturnRefOfCopyAction ReturnRefOfCopy(const R& x) { + return internal::ReturnRefOfCopyAction(x); +} + +// Creates an action that does the default action for the give mock function. +inline internal::DoDefaultAction DoDefault() { + return internal::DoDefaultAction(); +} + +// Creates an action that sets the variable pointed by the N-th +// (0-based) function argument to 'value'. +template +PolymorphicAction< + internal::SetArgumentPointeeAction< + N, T, internal::IsAProtocolMessage::value> > +SetArgPointee(const T& x) { + return MakePolymorphicAction(internal::SetArgumentPointeeAction< + N, T, internal::IsAProtocolMessage::value>(x)); +} + +#if !((GTEST_GCC_VER_ && GTEST_GCC_VER_ < 40000) || GTEST_OS_SYMBIAN) +// This overload allows SetArgPointee() to accept a string literal. +// GCC prior to the version 4.0 and Symbian C++ compiler cannot distinguish +// this overload from the templated version and emit a compile error. +template +PolymorphicAction< + internal::SetArgumentPointeeAction > +SetArgPointee(const char* p) { + return MakePolymorphicAction(internal::SetArgumentPointeeAction< + N, const char*, false>(p)); +} + +template +PolymorphicAction< + internal::SetArgumentPointeeAction > +SetArgPointee(const wchar_t* p) { + return MakePolymorphicAction(internal::SetArgumentPointeeAction< + N, const wchar_t*, false>(p)); +} +#endif + +// The following version is DEPRECATED. +template +PolymorphicAction< + internal::SetArgumentPointeeAction< + N, T, internal::IsAProtocolMessage::value> > +SetArgumentPointee(const T& x) { + return MakePolymorphicAction(internal::SetArgumentPointeeAction< + N, T, internal::IsAProtocolMessage::value>(x)); +} + +// Creates an action that sets a pointer referent to a given value. +template +PolymorphicAction > Assign(T1* ptr, T2 val) { + return MakePolymorphicAction(internal::AssignAction(ptr, val)); +} + +#if !GTEST_OS_WINDOWS_MOBILE + +// Creates an action that sets errno and returns the appropriate error. +template +PolymorphicAction > +SetErrnoAndReturn(int errval, T result) { + return MakePolymorphicAction( + internal::SetErrnoAndReturnAction(errval, result)); +} + +#endif // !GTEST_OS_WINDOWS_MOBILE + +// Various overloads for InvokeWithoutArgs(). + +// Creates an action that invokes 'function_impl' with no argument. +template +PolymorphicAction > +InvokeWithoutArgs(FunctionImpl function_impl) { + return MakePolymorphicAction( + internal::InvokeWithoutArgsAction(function_impl)); +} + +// Creates an action that invokes the given method on the given object +// with no argument. +template +PolymorphicAction > +InvokeWithoutArgs(Class* obj_ptr, MethodPtr method_ptr) { + return MakePolymorphicAction( + internal::InvokeMethodWithoutArgsAction( + obj_ptr, method_ptr)); +} + +// Creates an action that performs an_action and throws away its +// result. In other words, it changes the return type of an_action to +// void. an_action MUST NOT return void, or the code won't compile. +template +inline internal::IgnoreResultAction IgnoreResult(const A& an_action) { + return internal::IgnoreResultAction(an_action); +} + +// Creates a reference wrapper for the given L-value. If necessary, +// you can explicitly specify the type of the reference. For example, +// suppose 'derived' is an object of type Derived, ByRef(derived) +// would wrap a Derived&. If you want to wrap a const Base& instead, +// where Base is a base class of Derived, just write: +// +// ByRef(derived) +template +inline internal::ReferenceWrapper ByRef(T& l_value) { // NOLINT + return internal::ReferenceWrapper(l_value); +} + +} // namespace testing + +#endif // GMOCK_INCLUDE_GMOCK_GMOCK_ACTIONS_H_ diff --git a/vendor/gmock-1.7.0/include/gmock/gmock-cardinalities.h b/vendor/gmock-1.7.0/include/gmock/gmock-cardinalities.h new file mode 100644 index 0000000000..fc315f92ab --- /dev/null +++ b/vendor/gmock-1.7.0/include/gmock/gmock-cardinalities.h @@ -0,0 +1,147 @@ +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) + +// Google Mock - a framework for writing C++ mock classes. +// +// This file implements some commonly used cardinalities. More +// cardinalities can be defined by the user implementing the +// CardinalityInterface interface if necessary. + +#ifndef GMOCK_INCLUDE_GMOCK_GMOCK_CARDINALITIES_H_ +#define GMOCK_INCLUDE_GMOCK_GMOCK_CARDINALITIES_H_ + +#include +#include // NOLINT +#include "gmock/internal/gmock-port.h" +#include "gtest/gtest.h" + +namespace testing { + +// To implement a cardinality Foo, define: +// 1. a class FooCardinality that implements the +// CardinalityInterface interface, and +// 2. a factory function that creates a Cardinality object from a +// const FooCardinality*. +// +// The two-level delegation design follows that of Matcher, providing +// consistency for extension developers. It also eases ownership +// management as Cardinality objects can now be copied like plain values. + +// The implementation of a cardinality. +class CardinalityInterface { + public: + virtual ~CardinalityInterface() {} + + // Conservative estimate on the lower/upper bound of the number of + // calls allowed. + virtual int ConservativeLowerBound() const { return 0; } + virtual int ConservativeUpperBound() const { return INT_MAX; } + + // Returns true iff call_count calls will satisfy this cardinality. + virtual bool IsSatisfiedByCallCount(int call_count) const = 0; + + // Returns true iff call_count calls will saturate this cardinality. + virtual bool IsSaturatedByCallCount(int call_count) const = 0; + + // Describes self to an ostream. + virtual void DescribeTo(::std::ostream* os) const = 0; +}; + +// A Cardinality is a copyable and IMMUTABLE (except by assignment) +// object that specifies how many times a mock function is expected to +// be called. The implementation of Cardinality is just a linked_ptr +// to const CardinalityInterface, so copying is fairly cheap. +// Don't inherit from Cardinality! +class GTEST_API_ Cardinality { + public: + // Constructs a null cardinality. Needed for storing Cardinality + // objects in STL containers. + Cardinality() {} + + // Constructs a Cardinality from its implementation. + explicit Cardinality(const CardinalityInterface* impl) : impl_(impl) {} + + // Conservative estimate on the lower/upper bound of the number of + // calls allowed. + int ConservativeLowerBound() const { return impl_->ConservativeLowerBound(); } + int ConservativeUpperBound() const { return impl_->ConservativeUpperBound(); } + + // Returns true iff call_count calls will satisfy this cardinality. + bool IsSatisfiedByCallCount(int call_count) const { + return impl_->IsSatisfiedByCallCount(call_count); + } + + // Returns true iff call_count calls will saturate this cardinality. + bool IsSaturatedByCallCount(int call_count) const { + return impl_->IsSaturatedByCallCount(call_count); + } + + // Returns true iff call_count calls will over-saturate this + // cardinality, i.e. exceed the maximum number of allowed calls. + bool IsOverSaturatedByCallCount(int call_count) const { + return impl_->IsSaturatedByCallCount(call_count) && + !impl_->IsSatisfiedByCallCount(call_count); + } + + // Describes self to an ostream + void DescribeTo(::std::ostream* os) const { impl_->DescribeTo(os); } + + // Describes the given actual call count to an ostream. + static void DescribeActualCallCountTo(int actual_call_count, + ::std::ostream* os); + + private: + internal::linked_ptr impl_; +}; + +// Creates a cardinality that allows at least n calls. +GTEST_API_ Cardinality AtLeast(int n); + +// Creates a cardinality that allows at most n calls. +GTEST_API_ Cardinality AtMost(int n); + +// Creates a cardinality that allows any number of calls. +GTEST_API_ Cardinality AnyNumber(); + +// Creates a cardinality that allows between min and max calls. +GTEST_API_ Cardinality Between(int min, int max); + +// Creates a cardinality that allows exactly n calls. +GTEST_API_ Cardinality Exactly(int n); + +// Creates a cardinality from its implementation. +inline Cardinality MakeCardinality(const CardinalityInterface* c) { + return Cardinality(c); +} + +} // namespace testing + +#endif // GMOCK_INCLUDE_GMOCK_GMOCK_CARDINALITIES_H_ diff --git a/vendor/gmock-1.7.0/include/gmock/gmock-generated-actions.h b/vendor/gmock-1.7.0/include/gmock/gmock-generated-actions.h new file mode 100644 index 0000000000..2327393d6b --- /dev/null +++ b/vendor/gmock-1.7.0/include/gmock/gmock-generated-actions.h @@ -0,0 +1,2415 @@ +// This file was GENERATED by a script. DO NOT EDIT BY HAND!!! + +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) + +// Google Mock - a framework for writing C++ mock classes. +// +// This file implements some commonly used variadic actions. + +#ifndef GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_ACTIONS_H_ +#define GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_ACTIONS_H_ + +#include "gmock/gmock-actions.h" +#include "gmock/internal/gmock-port.h" + +namespace testing { +namespace internal { + +// InvokeHelper knows how to unpack an N-tuple and invoke an N-ary +// function or method with the unpacked values, where F is a function +// type that takes N arguments. +template +class InvokeHelper; + +template +class InvokeHelper > { + public: + template + static R Invoke(Function function, const ::std::tr1::tuple<>&) { + return function(); + } + + template + static R InvokeMethod(Class* obj_ptr, + MethodPtr method_ptr, + const ::std::tr1::tuple<>&) { + return (obj_ptr->*method_ptr)(); + } +}; + +template +class InvokeHelper > { + public: + template + static R Invoke(Function function, const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return function(get<0>(args)); + } + + template + static R InvokeMethod(Class* obj_ptr, + MethodPtr method_ptr, + const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return (obj_ptr->*method_ptr)(get<0>(args)); + } +}; + +template +class InvokeHelper > { + public: + template + static R Invoke(Function function, const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return function(get<0>(args), get<1>(args)); + } + + template + static R InvokeMethod(Class* obj_ptr, + MethodPtr method_ptr, + const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return (obj_ptr->*method_ptr)(get<0>(args), get<1>(args)); + } +}; + +template +class InvokeHelper > { + public: + template + static R Invoke(Function function, const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return function(get<0>(args), get<1>(args), get<2>(args)); + } + + template + static R InvokeMethod(Class* obj_ptr, + MethodPtr method_ptr, + const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return (obj_ptr->*method_ptr)(get<0>(args), get<1>(args), get<2>(args)); + } +}; + +template +class InvokeHelper > { + public: + template + static R Invoke(Function function, const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return function(get<0>(args), get<1>(args), get<2>(args), get<3>(args)); + } + + template + static R InvokeMethod(Class* obj_ptr, + MethodPtr method_ptr, + const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return (obj_ptr->*method_ptr)(get<0>(args), get<1>(args), get<2>(args), + get<3>(args)); + } +}; + +template +class InvokeHelper > { + public: + template + static R Invoke(Function function, const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return function(get<0>(args), get<1>(args), get<2>(args), get<3>(args), + get<4>(args)); + } + + template + static R InvokeMethod(Class* obj_ptr, + MethodPtr method_ptr, + const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return (obj_ptr->*method_ptr)(get<0>(args), get<1>(args), get<2>(args), + get<3>(args), get<4>(args)); + } +}; + +template +class InvokeHelper > { + public: + template + static R Invoke(Function function, const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return function(get<0>(args), get<1>(args), get<2>(args), get<3>(args), + get<4>(args), get<5>(args)); + } + + template + static R InvokeMethod(Class* obj_ptr, + MethodPtr method_ptr, + const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return (obj_ptr->*method_ptr)(get<0>(args), get<1>(args), get<2>(args), + get<3>(args), get<4>(args), get<5>(args)); + } +}; + +template +class InvokeHelper > { + public: + template + static R Invoke(Function function, const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return function(get<0>(args), get<1>(args), get<2>(args), get<3>(args), + get<4>(args), get<5>(args), get<6>(args)); + } + + template + static R InvokeMethod(Class* obj_ptr, + MethodPtr method_ptr, + const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return (obj_ptr->*method_ptr)(get<0>(args), get<1>(args), get<2>(args), + get<3>(args), get<4>(args), get<5>(args), get<6>(args)); + } +}; + +template +class InvokeHelper > { + public: + template + static R Invoke(Function function, const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return function(get<0>(args), get<1>(args), get<2>(args), get<3>(args), + get<4>(args), get<5>(args), get<6>(args), get<7>(args)); + } + + template + static R InvokeMethod(Class* obj_ptr, + MethodPtr method_ptr, + const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return (obj_ptr->*method_ptr)(get<0>(args), get<1>(args), get<2>(args), + get<3>(args), get<4>(args), get<5>(args), get<6>(args), get<7>(args)); + } +}; + +template +class InvokeHelper > { + public: + template + static R Invoke(Function function, const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return function(get<0>(args), get<1>(args), get<2>(args), get<3>(args), + get<4>(args), get<5>(args), get<6>(args), get<7>(args), get<8>(args)); + } + + template + static R InvokeMethod(Class* obj_ptr, + MethodPtr method_ptr, + const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return (obj_ptr->*method_ptr)(get<0>(args), get<1>(args), get<2>(args), + get<3>(args), get<4>(args), get<5>(args), get<6>(args), get<7>(args), + get<8>(args)); + } +}; + +template +class InvokeHelper > { + public: + template + static R Invoke(Function function, const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return function(get<0>(args), get<1>(args), get<2>(args), get<3>(args), + get<4>(args), get<5>(args), get<6>(args), get<7>(args), get<8>(args), + get<9>(args)); + } + + template + static R InvokeMethod(Class* obj_ptr, + MethodPtr method_ptr, + const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return (obj_ptr->*method_ptr)(get<0>(args), get<1>(args), get<2>(args), + get<3>(args), get<4>(args), get<5>(args), get<6>(args), get<7>(args), + get<8>(args), get<9>(args)); + } +}; + +// CallableHelper has static methods for invoking "callables", +// i.e. function pointers and functors. It uses overloading to +// provide a uniform interface for invoking different kinds of +// callables. In particular, you can use: +// +// CallableHelper::Call(callable, a1, a2, ..., an) +// +// to invoke an n-ary callable, where R is its return type. If an +// argument, say a2, needs to be passed by reference, you should write +// ByRef(a2) instead of a2 in the above expression. +template +class CallableHelper { + public: + // Calls a nullary callable. + template + static R Call(Function function) { return function(); } + + // Calls a unary callable. + + // We deliberately pass a1 by value instead of const reference here + // in case it is a C-string literal. If we had declared the + // parameter as 'const A1& a1' and write Call(function, "Hi"), the + // compiler would've thought A1 is 'char[3]', which causes trouble + // when you need to copy a value of type A1. By declaring the + // parameter as 'A1 a1', the compiler will correctly infer that A1 + // is 'const char*' when it sees Call(function, "Hi"). + // + // Since this function is defined inline, the compiler can get rid + // of the copying of the arguments. Therefore the performance won't + // be hurt. + template + static R Call(Function function, A1 a1) { return function(a1); } + + // Calls a binary callable. + template + static R Call(Function function, A1 a1, A2 a2) { + return function(a1, a2); + } + + // Calls a ternary callable. + template + static R Call(Function function, A1 a1, A2 a2, A3 a3) { + return function(a1, a2, a3); + } + + // Calls a 4-ary callable. + template + static R Call(Function function, A1 a1, A2 a2, A3 a3, A4 a4) { + return function(a1, a2, a3, a4); + } + + // Calls a 5-ary callable. + template + static R Call(Function function, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) { + return function(a1, a2, a3, a4, a5); + } + + // Calls a 6-ary callable. + template + static R Call(Function function, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6) { + return function(a1, a2, a3, a4, a5, a6); + } + + // Calls a 7-ary callable. + template + static R Call(Function function, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, + A7 a7) { + return function(a1, a2, a3, a4, a5, a6, a7); + } + + // Calls a 8-ary callable. + template + static R Call(Function function, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, + A7 a7, A8 a8) { + return function(a1, a2, a3, a4, a5, a6, a7, a8); + } + + // Calls a 9-ary callable. + template + static R Call(Function function, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, + A7 a7, A8 a8, A9 a9) { + return function(a1, a2, a3, a4, a5, a6, a7, a8, a9); + } + + // Calls a 10-ary callable. + template + static R Call(Function function, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, + A7 a7, A8 a8, A9 a9, A10 a10) { + return function(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10); + } +}; // class CallableHelper + +// An INTERNAL macro for extracting the type of a tuple field. It's +// subject to change without notice - DO NOT USE IN USER CODE! +#define GMOCK_FIELD_(Tuple, N) \ + typename ::std::tr1::tuple_element::type + +// SelectArgs::type is the +// type of an n-ary function whose i-th (1-based) argument type is the +// k{i}-th (0-based) field of ArgumentTuple, which must be a tuple +// type, and whose return type is Result. For example, +// SelectArgs, 0, 3>::type +// is int(bool, long). +// +// SelectArgs::Select(args) +// returns the selected fields (k1, k2, ..., k_n) of args as a tuple. +// For example, +// SelectArgs, 2, 0>::Select( +// ::std::tr1::make_tuple(true, 'a', 2.5)) +// returns ::std::tr1::tuple (2.5, true). +// +// The numbers in list k1, k2, ..., k_n must be >= 0, where n can be +// in the range [0, 10]. Duplicates are allowed and they don't have +// to be in an ascending or descending order. + +template +class SelectArgs { + public: + typedef Result type(GMOCK_FIELD_(ArgumentTuple, k1), + GMOCK_FIELD_(ArgumentTuple, k2), GMOCK_FIELD_(ArgumentTuple, k3), + GMOCK_FIELD_(ArgumentTuple, k4), GMOCK_FIELD_(ArgumentTuple, k5), + GMOCK_FIELD_(ArgumentTuple, k6), GMOCK_FIELD_(ArgumentTuple, k7), + GMOCK_FIELD_(ArgumentTuple, k8), GMOCK_FIELD_(ArgumentTuple, k9), + GMOCK_FIELD_(ArgumentTuple, k10)); + typedef typename Function::ArgumentTuple SelectedArgs; + static SelectedArgs Select(const ArgumentTuple& args) { + using ::std::tr1::get; + return SelectedArgs(get(args), get(args), get(args), + get(args), get(args), get(args), get(args), + get(args), get(args), get(args)); + } +}; + +template +class SelectArgs { + public: + typedef Result type(); + typedef typename Function::ArgumentTuple SelectedArgs; + static SelectedArgs Select(const ArgumentTuple& /* args */) { + using ::std::tr1::get; + return SelectedArgs(); + } +}; + +template +class SelectArgs { + public: + typedef Result type(GMOCK_FIELD_(ArgumentTuple, k1)); + typedef typename Function::ArgumentTuple SelectedArgs; + static SelectedArgs Select(const ArgumentTuple& args) { + using ::std::tr1::get; + return SelectedArgs(get(args)); + } +}; + +template +class SelectArgs { + public: + typedef Result type(GMOCK_FIELD_(ArgumentTuple, k1), + GMOCK_FIELD_(ArgumentTuple, k2)); + typedef typename Function::ArgumentTuple SelectedArgs; + static SelectedArgs Select(const ArgumentTuple& args) { + using ::std::tr1::get; + return SelectedArgs(get(args), get(args)); + } +}; + +template +class SelectArgs { + public: + typedef Result type(GMOCK_FIELD_(ArgumentTuple, k1), + GMOCK_FIELD_(ArgumentTuple, k2), GMOCK_FIELD_(ArgumentTuple, k3)); + typedef typename Function::ArgumentTuple SelectedArgs; + static SelectedArgs Select(const ArgumentTuple& args) { + using ::std::tr1::get; + return SelectedArgs(get(args), get(args), get(args)); + } +}; + +template +class SelectArgs { + public: + typedef Result type(GMOCK_FIELD_(ArgumentTuple, k1), + GMOCK_FIELD_(ArgumentTuple, k2), GMOCK_FIELD_(ArgumentTuple, k3), + GMOCK_FIELD_(ArgumentTuple, k4)); + typedef typename Function::ArgumentTuple SelectedArgs; + static SelectedArgs Select(const ArgumentTuple& args) { + using ::std::tr1::get; + return SelectedArgs(get(args), get(args), get(args), + get(args)); + } +}; + +template +class SelectArgs { + public: + typedef Result type(GMOCK_FIELD_(ArgumentTuple, k1), + GMOCK_FIELD_(ArgumentTuple, k2), GMOCK_FIELD_(ArgumentTuple, k3), + GMOCK_FIELD_(ArgumentTuple, k4), GMOCK_FIELD_(ArgumentTuple, k5)); + typedef typename Function::ArgumentTuple SelectedArgs; + static SelectedArgs Select(const ArgumentTuple& args) { + using ::std::tr1::get; + return SelectedArgs(get(args), get(args), get(args), + get(args), get(args)); + } +}; + +template +class SelectArgs { + public: + typedef Result type(GMOCK_FIELD_(ArgumentTuple, k1), + GMOCK_FIELD_(ArgumentTuple, k2), GMOCK_FIELD_(ArgumentTuple, k3), + GMOCK_FIELD_(ArgumentTuple, k4), GMOCK_FIELD_(ArgumentTuple, k5), + GMOCK_FIELD_(ArgumentTuple, k6)); + typedef typename Function::ArgumentTuple SelectedArgs; + static SelectedArgs Select(const ArgumentTuple& args) { + using ::std::tr1::get; + return SelectedArgs(get(args), get(args), get(args), + get(args), get(args), get(args)); + } +}; + +template +class SelectArgs { + public: + typedef Result type(GMOCK_FIELD_(ArgumentTuple, k1), + GMOCK_FIELD_(ArgumentTuple, k2), GMOCK_FIELD_(ArgumentTuple, k3), + GMOCK_FIELD_(ArgumentTuple, k4), GMOCK_FIELD_(ArgumentTuple, k5), + GMOCK_FIELD_(ArgumentTuple, k6), GMOCK_FIELD_(ArgumentTuple, k7)); + typedef typename Function::ArgumentTuple SelectedArgs; + static SelectedArgs Select(const ArgumentTuple& args) { + using ::std::tr1::get; + return SelectedArgs(get(args), get(args), get(args), + get(args), get(args), get(args), get(args)); + } +}; + +template +class SelectArgs { + public: + typedef Result type(GMOCK_FIELD_(ArgumentTuple, k1), + GMOCK_FIELD_(ArgumentTuple, k2), GMOCK_FIELD_(ArgumentTuple, k3), + GMOCK_FIELD_(ArgumentTuple, k4), GMOCK_FIELD_(ArgumentTuple, k5), + GMOCK_FIELD_(ArgumentTuple, k6), GMOCK_FIELD_(ArgumentTuple, k7), + GMOCK_FIELD_(ArgumentTuple, k8)); + typedef typename Function::ArgumentTuple SelectedArgs; + static SelectedArgs Select(const ArgumentTuple& args) { + using ::std::tr1::get; + return SelectedArgs(get(args), get(args), get(args), + get(args), get(args), get(args), get(args), + get(args)); + } +}; + +template +class SelectArgs { + public: + typedef Result type(GMOCK_FIELD_(ArgumentTuple, k1), + GMOCK_FIELD_(ArgumentTuple, k2), GMOCK_FIELD_(ArgumentTuple, k3), + GMOCK_FIELD_(ArgumentTuple, k4), GMOCK_FIELD_(ArgumentTuple, k5), + GMOCK_FIELD_(ArgumentTuple, k6), GMOCK_FIELD_(ArgumentTuple, k7), + GMOCK_FIELD_(ArgumentTuple, k8), GMOCK_FIELD_(ArgumentTuple, k9)); + typedef typename Function::ArgumentTuple SelectedArgs; + static SelectedArgs Select(const ArgumentTuple& args) { + using ::std::tr1::get; + return SelectedArgs(get(args), get(args), get(args), + get(args), get(args), get(args), get(args), + get(args), get(args)); + } +}; + +#undef GMOCK_FIELD_ + +// Implements the WithArgs action. +template +class WithArgsAction { + public: + explicit WithArgsAction(const InnerAction& action) : action_(action) {} + + template + operator Action() const { return MakeAction(new Impl(action_)); } + + private: + template + class Impl : public ActionInterface { + public: + typedef typename Function::Result Result; + typedef typename Function::ArgumentTuple ArgumentTuple; + + explicit Impl(const InnerAction& action) : action_(action) {} + + virtual Result Perform(const ArgumentTuple& args) { + return action_.Perform(SelectArgs::Select(args)); + } + + private: + typedef typename SelectArgs::type InnerFunctionType; + + Action action_; + }; + + const InnerAction action_; + + GTEST_DISALLOW_ASSIGN_(WithArgsAction); +}; + +// A macro from the ACTION* family (defined later in this file) +// defines an action that can be used in a mock function. Typically, +// these actions only care about a subset of the arguments of the mock +// function. For example, if such an action only uses the second +// argument, it can be used in any mock function that takes >= 2 +// arguments where the type of the second argument is compatible. +// +// Therefore, the action implementation must be prepared to take more +// arguments than it needs. The ExcessiveArg type is used to +// represent those excessive arguments. In order to keep the compiler +// error messages tractable, we define it in the testing namespace +// instead of testing::internal. However, this is an INTERNAL TYPE +// and subject to change without notice, so a user MUST NOT USE THIS +// TYPE DIRECTLY. +struct ExcessiveArg {}; + +// A helper class needed for implementing the ACTION* macros. +template +class ActionHelper { + public: + static Result Perform(Impl* impl, const ::std::tr1::tuple<>& args) { + using ::std::tr1::get; + return impl->template gmock_PerformImpl<>(args, ExcessiveArg(), + ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), + ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), + ExcessiveArg()); + } + + template + static Result Perform(Impl* impl, const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return impl->template gmock_PerformImpl(args, get<0>(args), + ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), + ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), + ExcessiveArg()); + } + + template + static Result Perform(Impl* impl, const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return impl->template gmock_PerformImpl(args, get<0>(args), + get<1>(args), ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), + ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), + ExcessiveArg()); + } + + template + static Result Perform(Impl* impl, const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return impl->template gmock_PerformImpl(args, get<0>(args), + get<1>(args), get<2>(args), ExcessiveArg(), ExcessiveArg(), + ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), + ExcessiveArg()); + } + + template + static Result Perform(Impl* impl, const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return impl->template gmock_PerformImpl(args, get<0>(args), + get<1>(args), get<2>(args), get<3>(args), ExcessiveArg(), + ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), + ExcessiveArg()); + } + + template + static Result Perform(Impl* impl, const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return impl->template gmock_PerformImpl(args, + get<0>(args), get<1>(args), get<2>(args), get<3>(args), get<4>(args), + ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), + ExcessiveArg()); + } + + template + static Result Perform(Impl* impl, const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return impl->template gmock_PerformImpl(args, + get<0>(args), get<1>(args), get<2>(args), get<3>(args), get<4>(args), + get<5>(args), ExcessiveArg(), ExcessiveArg(), ExcessiveArg(), + ExcessiveArg()); + } + + template + static Result Perform(Impl* impl, const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return impl->template gmock_PerformImpl(args, + get<0>(args), get<1>(args), get<2>(args), get<3>(args), get<4>(args), + get<5>(args), get<6>(args), ExcessiveArg(), ExcessiveArg(), + ExcessiveArg()); + } + + template + static Result Perform(Impl* impl, const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return impl->template gmock_PerformImpl(args, get<0>(args), get<1>(args), get<2>(args), get<3>(args), + get<4>(args), get<5>(args), get<6>(args), get<7>(args), ExcessiveArg(), + ExcessiveArg()); + } + + template + static Result Perform(Impl* impl, const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return impl->template gmock_PerformImpl(args, get<0>(args), get<1>(args), get<2>(args), get<3>(args), + get<4>(args), get<5>(args), get<6>(args), get<7>(args), get<8>(args), + ExcessiveArg()); + } + + template + static Result Perform(Impl* impl, const ::std::tr1::tuple& args) { + using ::std::tr1::get; + return impl->template gmock_PerformImpl(args, get<0>(args), get<1>(args), get<2>(args), get<3>(args), + get<4>(args), get<5>(args), get<6>(args), get<7>(args), get<8>(args), + get<9>(args)); + } +}; + +} // namespace internal + +// Various overloads for Invoke(). + +// WithArgs(an_action) creates an action that passes +// the selected arguments of the mock function to an_action and +// performs it. It serves as an adaptor between actions with +// different argument lists. C++ doesn't support default arguments for +// function templates, so we have to overload it. +template +inline internal::WithArgsAction +WithArgs(const InnerAction& action) { + return internal::WithArgsAction(action); +} + +template +inline internal::WithArgsAction +WithArgs(const InnerAction& action) { + return internal::WithArgsAction(action); +} + +template +inline internal::WithArgsAction +WithArgs(const InnerAction& action) { + return internal::WithArgsAction(action); +} + +template +inline internal::WithArgsAction +WithArgs(const InnerAction& action) { + return internal::WithArgsAction(action); +} + +template +inline internal::WithArgsAction +WithArgs(const InnerAction& action) { + return internal::WithArgsAction(action); +} + +template +inline internal::WithArgsAction +WithArgs(const InnerAction& action) { + return internal::WithArgsAction(action); +} + +template +inline internal::WithArgsAction +WithArgs(const InnerAction& action) { + return internal::WithArgsAction(action); +} + +template +inline internal::WithArgsAction +WithArgs(const InnerAction& action) { + return internal::WithArgsAction(action); +} + +template +inline internal::WithArgsAction +WithArgs(const InnerAction& action) { + return internal::WithArgsAction(action); +} + +template +inline internal::WithArgsAction +WithArgs(const InnerAction& action) { + return internal::WithArgsAction(action); +} + +// Creates an action that does actions a1, a2, ..., sequentially in +// each invocation. +template +inline internal::DoBothAction +DoAll(Action1 a1, Action2 a2) { + return internal::DoBothAction(a1, a2); +} + +template +inline internal::DoBothAction > +DoAll(Action1 a1, Action2 a2, Action3 a3) { + return DoAll(a1, DoAll(a2, a3)); +} + +template +inline internal::DoBothAction > > +DoAll(Action1 a1, Action2 a2, Action3 a3, Action4 a4) { + return DoAll(a1, DoAll(a2, a3, a4)); +} + +template +inline internal::DoBothAction > > > +DoAll(Action1 a1, Action2 a2, Action3 a3, Action4 a4, Action5 a5) { + return DoAll(a1, DoAll(a2, a3, a4, a5)); +} + +template +inline internal::DoBothAction > > > > +DoAll(Action1 a1, Action2 a2, Action3 a3, Action4 a4, Action5 a5, Action6 a6) { + return DoAll(a1, DoAll(a2, a3, a4, a5, a6)); +} + +template +inline internal::DoBothAction > > > > > +DoAll(Action1 a1, Action2 a2, Action3 a3, Action4 a4, Action5 a5, Action6 a6, + Action7 a7) { + return DoAll(a1, DoAll(a2, a3, a4, a5, a6, a7)); +} + +template +inline internal::DoBothAction > > > > > > +DoAll(Action1 a1, Action2 a2, Action3 a3, Action4 a4, Action5 a5, Action6 a6, + Action7 a7, Action8 a8) { + return DoAll(a1, DoAll(a2, a3, a4, a5, a6, a7, a8)); +} + +template +inline internal::DoBothAction > > > > > > > +DoAll(Action1 a1, Action2 a2, Action3 a3, Action4 a4, Action5 a5, Action6 a6, + Action7 a7, Action8 a8, Action9 a9) { + return DoAll(a1, DoAll(a2, a3, a4, a5, a6, a7, a8, a9)); +} + +template +inline internal::DoBothAction > > > > > > > > +DoAll(Action1 a1, Action2 a2, Action3 a3, Action4 a4, Action5 a5, Action6 a6, + Action7 a7, Action8 a8, Action9 a9, Action10 a10) { + return DoAll(a1, DoAll(a2, a3, a4, a5, a6, a7, a8, a9, a10)); +} + +} // namespace testing + +// The ACTION* family of macros can be used in a namespace scope to +// define custom actions easily. The syntax: +// +// ACTION(name) { statements; } +// +// will define an action with the given name that executes the +// statements. The value returned by the statements will be used as +// the return value of the action. Inside the statements, you can +// refer to the K-th (0-based) argument of the mock function by +// 'argK', and refer to its type by 'argK_type'. For example: +// +// ACTION(IncrementArg1) { +// arg1_type temp = arg1; +// return ++(*temp); +// } +// +// allows you to write +// +// ...WillOnce(IncrementArg1()); +// +// You can also refer to the entire argument tuple and its type by +// 'args' and 'args_type', and refer to the mock function type and its +// return type by 'function_type' and 'return_type'. +// +// Note that you don't need to specify the types of the mock function +// arguments. However rest assured that your code is still type-safe: +// you'll get a compiler error if *arg1 doesn't support the ++ +// operator, or if the type of ++(*arg1) isn't compatible with the +// mock function's return type, for example. +// +// Sometimes you'll want to parameterize the action. For that you can use +// another macro: +// +// ACTION_P(name, param_name) { statements; } +// +// For example: +// +// ACTION_P(Add, n) { return arg0 + n; } +// +// will allow you to write: +// +// ...WillOnce(Add(5)); +// +// Note that you don't need to provide the type of the parameter +// either. If you need to reference the type of a parameter named +// 'foo', you can write 'foo_type'. For example, in the body of +// ACTION_P(Add, n) above, you can write 'n_type' to refer to the type +// of 'n'. +// +// We also provide ACTION_P2, ACTION_P3, ..., up to ACTION_P10 to support +// multi-parameter actions. +// +// For the purpose of typing, you can view +// +// ACTION_Pk(Foo, p1, ..., pk) { ... } +// +// as shorthand for +// +// template +// FooActionPk Foo(p1_type p1, ..., pk_type pk) { ... } +// +// In particular, you can provide the template type arguments +// explicitly when invoking Foo(), as in Foo(5, false); +// although usually you can rely on the compiler to infer the types +// for you automatically. You can assign the result of expression +// Foo(p1, ..., pk) to a variable of type FooActionPk. This can be useful when composing actions. +// +// You can also overload actions with different numbers of parameters: +// +// ACTION_P(Plus, a) { ... } +// ACTION_P2(Plus, a, b) { ... } +// +// While it's tempting to always use the ACTION* macros when defining +// a new action, you should also consider implementing ActionInterface +// or using MakePolymorphicAction() instead, especially if you need to +// use the action a lot. While these approaches require more work, +// they give you more control on the types of the mock function +// arguments and the action parameters, which in general leads to +// better compiler error messages that pay off in the long run. They +// also allow overloading actions based on parameter types (as opposed +// to just based on the number of parameters). +// +// CAVEAT: +// +// ACTION*() can only be used in a namespace scope. The reason is +// that C++ doesn't yet allow function-local types to be used to +// instantiate templates. The up-coming C++0x standard will fix this. +// Once that's done, we'll consider supporting using ACTION*() inside +// a function. +// +// MORE INFORMATION: +// +// To learn more about using these macros, please search for 'ACTION' +// on http://code.google.com/p/googlemock/wiki/CookBook. + +// An internal macro needed for implementing ACTION*(). +#define GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_\ + const args_type& args GTEST_ATTRIBUTE_UNUSED_, \ + arg0_type arg0 GTEST_ATTRIBUTE_UNUSED_, \ + arg1_type arg1 GTEST_ATTRIBUTE_UNUSED_, \ + arg2_type arg2 GTEST_ATTRIBUTE_UNUSED_, \ + arg3_type arg3 GTEST_ATTRIBUTE_UNUSED_, \ + arg4_type arg4 GTEST_ATTRIBUTE_UNUSED_, \ + arg5_type arg5 GTEST_ATTRIBUTE_UNUSED_, \ + arg6_type arg6 GTEST_ATTRIBUTE_UNUSED_, \ + arg7_type arg7 GTEST_ATTRIBUTE_UNUSED_, \ + arg8_type arg8 GTEST_ATTRIBUTE_UNUSED_, \ + arg9_type arg9 GTEST_ATTRIBUTE_UNUSED_ + +// Sometimes you want to give an action explicit template parameters +// that cannot be inferred from its value parameters. ACTION() and +// ACTION_P*() don't support that. ACTION_TEMPLATE() remedies that +// and can be viewed as an extension to ACTION() and ACTION_P*(). +// +// The syntax: +// +// ACTION_TEMPLATE(ActionName, +// HAS_m_TEMPLATE_PARAMS(kind1, name1, ..., kind_m, name_m), +// AND_n_VALUE_PARAMS(p1, ..., p_n)) { statements; } +// +// defines an action template that takes m explicit template +// parameters and n value parameters. name_i is the name of the i-th +// template parameter, and kind_i specifies whether it's a typename, +// an integral constant, or a template. p_i is the name of the i-th +// value parameter. +// +// Example: +// +// // DuplicateArg(output) converts the k-th argument of the mock +// // function to type T and copies it to *output. +// ACTION_TEMPLATE(DuplicateArg, +// HAS_2_TEMPLATE_PARAMS(int, k, typename, T), +// AND_1_VALUE_PARAMS(output)) { +// *output = T(std::tr1::get(args)); +// } +// ... +// int n; +// EXPECT_CALL(mock, Foo(_, _)) +// .WillOnce(DuplicateArg<1, unsigned char>(&n)); +// +// To create an instance of an action template, write: +// +// ActionName(v1, ..., v_n) +// +// where the ts are the template arguments and the vs are the value +// arguments. The value argument types are inferred by the compiler. +// If you want to explicitly specify the value argument types, you can +// provide additional template arguments: +// +// ActionName(v1, ..., v_n) +// +// where u_i is the desired type of v_i. +// +// ACTION_TEMPLATE and ACTION/ACTION_P* can be overloaded on the +// number of value parameters, but not on the number of template +// parameters. Without the restriction, the meaning of the following +// is unclear: +// +// OverloadedAction(x); +// +// Are we using a single-template-parameter action where 'bool' refers +// to the type of x, or are we using a two-template-parameter action +// where the compiler is asked to infer the type of x? +// +// Implementation notes: +// +// GMOCK_INTERNAL_*_HAS_m_TEMPLATE_PARAMS and +// GMOCK_INTERNAL_*_AND_n_VALUE_PARAMS are internal macros for +// implementing ACTION_TEMPLATE. The main trick we use is to create +// new macro invocations when expanding a macro. For example, we have +// +// #define ACTION_TEMPLATE(name, template_params, value_params) +// ... GMOCK_INTERNAL_DECL_##template_params ... +// +// which causes ACTION_TEMPLATE(..., HAS_1_TEMPLATE_PARAMS(typename, T), ...) +// to expand to +// +// ... GMOCK_INTERNAL_DECL_HAS_1_TEMPLATE_PARAMS(typename, T) ... +// +// Since GMOCK_INTERNAL_DECL_HAS_1_TEMPLATE_PARAMS is a macro, the +// preprocessor will continue to expand it to +// +// ... typename T ... +// +// This technique conforms to the C++ standard and is portable. It +// allows us to implement action templates using O(N) code, where N is +// the maximum number of template/value parameters supported. Without +// using it, we'd have to devote O(N^2) amount of code to implement all +// combinations of m and n. + +// Declares the template parameters. +#define GMOCK_INTERNAL_DECL_HAS_1_TEMPLATE_PARAMS(kind0, name0) kind0 name0 +#define GMOCK_INTERNAL_DECL_HAS_2_TEMPLATE_PARAMS(kind0, name0, kind1, \ + name1) kind0 name0, kind1 name1 +#define GMOCK_INTERNAL_DECL_HAS_3_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2) kind0 name0, kind1 name1, kind2 name2 +#define GMOCK_INTERNAL_DECL_HAS_4_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3) kind0 name0, kind1 name1, kind2 name2, \ + kind3 name3 +#define GMOCK_INTERNAL_DECL_HAS_5_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3, kind4, name4) kind0 name0, kind1 name1, \ + kind2 name2, kind3 name3, kind4 name4 +#define GMOCK_INTERNAL_DECL_HAS_6_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3, kind4, name4, kind5, name5) kind0 name0, \ + kind1 name1, kind2 name2, kind3 name3, kind4 name4, kind5 name5 +#define GMOCK_INTERNAL_DECL_HAS_7_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3, kind4, name4, kind5, name5, kind6, \ + name6) kind0 name0, kind1 name1, kind2 name2, kind3 name3, kind4 name4, \ + kind5 name5, kind6 name6 +#define GMOCK_INTERNAL_DECL_HAS_8_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3, kind4, name4, kind5, name5, kind6, name6, \ + kind7, name7) kind0 name0, kind1 name1, kind2 name2, kind3 name3, \ + kind4 name4, kind5 name5, kind6 name6, kind7 name7 +#define GMOCK_INTERNAL_DECL_HAS_9_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3, kind4, name4, kind5, name5, kind6, name6, \ + kind7, name7, kind8, name8) kind0 name0, kind1 name1, kind2 name2, \ + kind3 name3, kind4 name4, kind5 name5, kind6 name6, kind7 name7, \ + kind8 name8 +#define GMOCK_INTERNAL_DECL_HAS_10_TEMPLATE_PARAMS(kind0, name0, kind1, \ + name1, kind2, name2, kind3, name3, kind4, name4, kind5, name5, kind6, \ + name6, kind7, name7, kind8, name8, kind9, name9) kind0 name0, \ + kind1 name1, kind2 name2, kind3 name3, kind4 name4, kind5 name5, \ + kind6 name6, kind7 name7, kind8 name8, kind9 name9 + +// Lists the template parameters. +#define GMOCK_INTERNAL_LIST_HAS_1_TEMPLATE_PARAMS(kind0, name0) name0 +#define GMOCK_INTERNAL_LIST_HAS_2_TEMPLATE_PARAMS(kind0, name0, kind1, \ + name1) name0, name1 +#define GMOCK_INTERNAL_LIST_HAS_3_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2) name0, name1, name2 +#define GMOCK_INTERNAL_LIST_HAS_4_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3) name0, name1, name2, name3 +#define GMOCK_INTERNAL_LIST_HAS_5_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3, kind4, name4) name0, name1, name2, name3, \ + name4 +#define GMOCK_INTERNAL_LIST_HAS_6_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3, kind4, name4, kind5, name5) name0, name1, \ + name2, name3, name4, name5 +#define GMOCK_INTERNAL_LIST_HAS_7_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3, kind4, name4, kind5, name5, kind6, \ + name6) name0, name1, name2, name3, name4, name5, name6 +#define GMOCK_INTERNAL_LIST_HAS_8_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3, kind4, name4, kind5, name5, kind6, name6, \ + kind7, name7) name0, name1, name2, name3, name4, name5, name6, name7 +#define GMOCK_INTERNAL_LIST_HAS_9_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \ + kind2, name2, kind3, name3, kind4, name4, kind5, name5, kind6, name6, \ + kind7, name7, kind8, name8) name0, name1, name2, name3, name4, name5, \ + name6, name7, name8 +#define GMOCK_INTERNAL_LIST_HAS_10_TEMPLATE_PARAMS(kind0, name0, kind1, \ + name1, kind2, name2, kind3, name3, kind4, name4, kind5, name5, kind6, \ + name6, kind7, name7, kind8, name8, kind9, name9) name0, name1, name2, \ + name3, name4, name5, name6, name7, name8, name9 + +// Declares the types of value parameters. +#define GMOCK_INTERNAL_DECL_TYPE_AND_0_VALUE_PARAMS() +#define GMOCK_INTERNAL_DECL_TYPE_AND_1_VALUE_PARAMS(p0) , typename p0##_type +#define GMOCK_INTERNAL_DECL_TYPE_AND_2_VALUE_PARAMS(p0, p1) , \ + typename p0##_type, typename p1##_type +#define GMOCK_INTERNAL_DECL_TYPE_AND_3_VALUE_PARAMS(p0, p1, p2) , \ + typename p0##_type, typename p1##_type, typename p2##_type +#define GMOCK_INTERNAL_DECL_TYPE_AND_4_VALUE_PARAMS(p0, p1, p2, p3) , \ + typename p0##_type, typename p1##_type, typename p2##_type, \ + typename p3##_type +#define GMOCK_INTERNAL_DECL_TYPE_AND_5_VALUE_PARAMS(p0, p1, p2, p3, p4) , \ + typename p0##_type, typename p1##_type, typename p2##_type, \ + typename p3##_type, typename p4##_type +#define GMOCK_INTERNAL_DECL_TYPE_AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, p5) , \ + typename p0##_type, typename p1##_type, typename p2##_type, \ + typename p3##_type, typename p4##_type, typename p5##_type +#define GMOCK_INTERNAL_DECL_TYPE_AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \ + p6) , typename p0##_type, typename p1##_type, typename p2##_type, \ + typename p3##_type, typename p4##_type, typename p5##_type, \ + typename p6##_type +#define GMOCK_INTERNAL_DECL_TYPE_AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \ + p6, p7) , typename p0##_type, typename p1##_type, typename p2##_type, \ + typename p3##_type, typename p4##_type, typename p5##_type, \ + typename p6##_type, typename p7##_type +#define GMOCK_INTERNAL_DECL_TYPE_AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \ + p6, p7, p8) , typename p0##_type, typename p1##_type, typename p2##_type, \ + typename p3##_type, typename p4##_type, typename p5##_type, \ + typename p6##_type, typename p7##_type, typename p8##_type +#define GMOCK_INTERNAL_DECL_TYPE_AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \ + p6, p7, p8, p9) , typename p0##_type, typename p1##_type, \ + typename p2##_type, typename p3##_type, typename p4##_type, \ + typename p5##_type, typename p6##_type, typename p7##_type, \ + typename p8##_type, typename p9##_type + +// Initializes the value parameters. +#define GMOCK_INTERNAL_INIT_AND_0_VALUE_PARAMS()\ + () +#define GMOCK_INTERNAL_INIT_AND_1_VALUE_PARAMS(p0)\ + (p0##_type gmock_p0) : p0(gmock_p0) +#define GMOCK_INTERNAL_INIT_AND_2_VALUE_PARAMS(p0, p1)\ + (p0##_type gmock_p0, p1##_type gmock_p1) : p0(gmock_p0), p1(gmock_p1) +#define GMOCK_INTERNAL_INIT_AND_3_VALUE_PARAMS(p0, p1, p2)\ + (p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2) +#define GMOCK_INTERNAL_INIT_AND_4_VALUE_PARAMS(p0, p1, p2, p3)\ + (p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3) +#define GMOCK_INTERNAL_INIT_AND_5_VALUE_PARAMS(p0, p1, p2, p3, p4)\ + (p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4) : p0(gmock_p0), p1(gmock_p1), \ + p2(gmock_p2), p3(gmock_p3), p4(gmock_p4) +#define GMOCK_INTERNAL_INIT_AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, p5)\ + (p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, \ + p5##_type gmock_p5) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4), p5(gmock_p5) +#define GMOCK_INTERNAL_INIT_AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6)\ + (p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \ + p6##_type gmock_p6) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4), p5(gmock_p5), p6(gmock_p6) +#define GMOCK_INTERNAL_INIT_AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, p7)\ + (p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \ + p6##_type gmock_p6, p7##_type gmock_p7) : p0(gmock_p0), p1(gmock_p1), \ + p2(gmock_p2), p3(gmock_p3), p4(gmock_p4), p5(gmock_p5), p6(gmock_p6), \ + p7(gmock_p7) +#define GMOCK_INTERNAL_INIT_AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7, p8)\ + (p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \ + p6##_type gmock_p6, p7##_type gmock_p7, \ + p8##_type gmock_p8) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4), p5(gmock_p5), p6(gmock_p6), p7(gmock_p7), \ + p8(gmock_p8) +#define GMOCK_INTERNAL_INIT_AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7, p8, p9)\ + (p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \ + p6##_type gmock_p6, p7##_type gmock_p7, p8##_type gmock_p8, \ + p9##_type gmock_p9) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4), p5(gmock_p5), p6(gmock_p6), p7(gmock_p7), \ + p8(gmock_p8), p9(gmock_p9) + +// Declares the fields for storing the value parameters. +#define GMOCK_INTERNAL_DEFN_AND_0_VALUE_PARAMS() +#define GMOCK_INTERNAL_DEFN_AND_1_VALUE_PARAMS(p0) p0##_type p0; +#define GMOCK_INTERNAL_DEFN_AND_2_VALUE_PARAMS(p0, p1) p0##_type p0; \ + p1##_type p1; +#define GMOCK_INTERNAL_DEFN_AND_3_VALUE_PARAMS(p0, p1, p2) p0##_type p0; \ + p1##_type p1; p2##_type p2; +#define GMOCK_INTERNAL_DEFN_AND_4_VALUE_PARAMS(p0, p1, p2, p3) p0##_type p0; \ + p1##_type p1; p2##_type p2; p3##_type p3; +#define GMOCK_INTERNAL_DEFN_AND_5_VALUE_PARAMS(p0, p1, p2, p3, \ + p4) p0##_type p0; p1##_type p1; p2##_type p2; p3##_type p3; p4##_type p4; +#define GMOCK_INTERNAL_DEFN_AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, \ + p5) p0##_type p0; p1##_type p1; p2##_type p2; p3##_type p3; p4##_type p4; \ + p5##_type p5; +#define GMOCK_INTERNAL_DEFN_AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \ + p6) p0##_type p0; p1##_type p1; p2##_type p2; p3##_type p3; p4##_type p4; \ + p5##_type p5; p6##_type p6; +#define GMOCK_INTERNAL_DEFN_AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7) p0##_type p0; p1##_type p1; p2##_type p2; p3##_type p3; p4##_type p4; \ + p5##_type p5; p6##_type p6; p7##_type p7; +#define GMOCK_INTERNAL_DEFN_AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7, p8) p0##_type p0; p1##_type p1; p2##_type p2; p3##_type p3; \ + p4##_type p4; p5##_type p5; p6##_type p6; p7##_type p7; p8##_type p8; +#define GMOCK_INTERNAL_DEFN_AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7, p8, p9) p0##_type p0; p1##_type p1; p2##_type p2; p3##_type p3; \ + p4##_type p4; p5##_type p5; p6##_type p6; p7##_type p7; p8##_type p8; \ + p9##_type p9; + +// Lists the value parameters. +#define GMOCK_INTERNAL_LIST_AND_0_VALUE_PARAMS() +#define GMOCK_INTERNAL_LIST_AND_1_VALUE_PARAMS(p0) p0 +#define GMOCK_INTERNAL_LIST_AND_2_VALUE_PARAMS(p0, p1) p0, p1 +#define GMOCK_INTERNAL_LIST_AND_3_VALUE_PARAMS(p0, p1, p2) p0, p1, p2 +#define GMOCK_INTERNAL_LIST_AND_4_VALUE_PARAMS(p0, p1, p2, p3) p0, p1, p2, p3 +#define GMOCK_INTERNAL_LIST_AND_5_VALUE_PARAMS(p0, p1, p2, p3, p4) p0, p1, \ + p2, p3, p4 +#define GMOCK_INTERNAL_LIST_AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, p5) p0, \ + p1, p2, p3, p4, p5 +#define GMOCK_INTERNAL_LIST_AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \ + p6) p0, p1, p2, p3, p4, p5, p6 +#define GMOCK_INTERNAL_LIST_AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7) p0, p1, p2, p3, p4, p5, p6, p7 +#define GMOCK_INTERNAL_LIST_AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7, p8) p0, p1, p2, p3, p4, p5, p6, p7, p8 +#define GMOCK_INTERNAL_LIST_AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7, p8, p9) p0, p1, p2, p3, p4, p5, p6, p7, p8, p9 + +// Lists the value parameter types. +#define GMOCK_INTERNAL_LIST_TYPE_AND_0_VALUE_PARAMS() +#define GMOCK_INTERNAL_LIST_TYPE_AND_1_VALUE_PARAMS(p0) , p0##_type +#define GMOCK_INTERNAL_LIST_TYPE_AND_2_VALUE_PARAMS(p0, p1) , p0##_type, \ + p1##_type +#define GMOCK_INTERNAL_LIST_TYPE_AND_3_VALUE_PARAMS(p0, p1, p2) , p0##_type, \ + p1##_type, p2##_type +#define GMOCK_INTERNAL_LIST_TYPE_AND_4_VALUE_PARAMS(p0, p1, p2, p3) , \ + p0##_type, p1##_type, p2##_type, p3##_type +#define GMOCK_INTERNAL_LIST_TYPE_AND_5_VALUE_PARAMS(p0, p1, p2, p3, p4) , \ + p0##_type, p1##_type, p2##_type, p3##_type, p4##_type +#define GMOCK_INTERNAL_LIST_TYPE_AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, p5) , \ + p0##_type, p1##_type, p2##_type, p3##_type, p4##_type, p5##_type +#define GMOCK_INTERNAL_LIST_TYPE_AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \ + p6) , p0##_type, p1##_type, p2##_type, p3##_type, p4##_type, p5##_type, \ + p6##_type +#define GMOCK_INTERNAL_LIST_TYPE_AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \ + p6, p7) , p0##_type, p1##_type, p2##_type, p3##_type, p4##_type, \ + p5##_type, p6##_type, p7##_type +#define GMOCK_INTERNAL_LIST_TYPE_AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \ + p6, p7, p8) , p0##_type, p1##_type, p2##_type, p3##_type, p4##_type, \ + p5##_type, p6##_type, p7##_type, p8##_type +#define GMOCK_INTERNAL_LIST_TYPE_AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \ + p6, p7, p8, p9) , p0##_type, p1##_type, p2##_type, p3##_type, p4##_type, \ + p5##_type, p6##_type, p7##_type, p8##_type, p9##_type + +// Declares the value parameters. +#define GMOCK_INTERNAL_DECL_AND_0_VALUE_PARAMS() +#define GMOCK_INTERNAL_DECL_AND_1_VALUE_PARAMS(p0) p0##_type p0 +#define GMOCK_INTERNAL_DECL_AND_2_VALUE_PARAMS(p0, p1) p0##_type p0, \ + p1##_type p1 +#define GMOCK_INTERNAL_DECL_AND_3_VALUE_PARAMS(p0, p1, p2) p0##_type p0, \ + p1##_type p1, p2##_type p2 +#define GMOCK_INTERNAL_DECL_AND_4_VALUE_PARAMS(p0, p1, p2, p3) p0##_type p0, \ + p1##_type p1, p2##_type p2, p3##_type p3 +#define GMOCK_INTERNAL_DECL_AND_5_VALUE_PARAMS(p0, p1, p2, p3, \ + p4) p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, p4##_type p4 +#define GMOCK_INTERNAL_DECL_AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, \ + p5) p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, p4##_type p4, \ + p5##_type p5 +#define GMOCK_INTERNAL_DECL_AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \ + p6) p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, p4##_type p4, \ + p5##_type p5, p6##_type p6 +#define GMOCK_INTERNAL_DECL_AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7) p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, p4##_type p4, \ + p5##_type p5, p6##_type p6, p7##_type p7 +#define GMOCK_INTERNAL_DECL_AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7, p8) p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, \ + p4##_type p4, p5##_type p5, p6##_type p6, p7##_type p7, p8##_type p8 +#define GMOCK_INTERNAL_DECL_AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7, p8, p9) p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, \ + p4##_type p4, p5##_type p5, p6##_type p6, p7##_type p7, p8##_type p8, \ + p9##_type p9 + +// The suffix of the class template implementing the action template. +#define GMOCK_INTERNAL_COUNT_AND_0_VALUE_PARAMS() +#define GMOCK_INTERNAL_COUNT_AND_1_VALUE_PARAMS(p0) P +#define GMOCK_INTERNAL_COUNT_AND_2_VALUE_PARAMS(p0, p1) P2 +#define GMOCK_INTERNAL_COUNT_AND_3_VALUE_PARAMS(p0, p1, p2) P3 +#define GMOCK_INTERNAL_COUNT_AND_4_VALUE_PARAMS(p0, p1, p2, p3) P4 +#define GMOCK_INTERNAL_COUNT_AND_5_VALUE_PARAMS(p0, p1, p2, p3, p4) P5 +#define GMOCK_INTERNAL_COUNT_AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, p5) P6 +#define GMOCK_INTERNAL_COUNT_AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6) P7 +#define GMOCK_INTERNAL_COUNT_AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7) P8 +#define GMOCK_INTERNAL_COUNT_AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7, p8) P9 +#define GMOCK_INTERNAL_COUNT_AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \ + p7, p8, p9) P10 + +// The name of the class template implementing the action template. +#define GMOCK_ACTION_CLASS_(name, value_params)\ + GTEST_CONCAT_TOKEN_(name##Action, GMOCK_INTERNAL_COUNT_##value_params) + +#define ACTION_TEMPLATE(name, template_params, value_params)\ + template \ + class GMOCK_ACTION_CLASS_(name, value_params) {\ + public:\ + GMOCK_ACTION_CLASS_(name, value_params)\ + GMOCK_INTERNAL_INIT_##value_params {}\ + template \ + class gmock_Impl : public ::testing::ActionInterface {\ + public:\ + typedef F function_type;\ + typedef typename ::testing::internal::Function::Result return_type;\ + typedef typename ::testing::internal::Function::ArgumentTuple\ + args_type;\ + explicit gmock_Impl GMOCK_INTERNAL_INIT_##value_params {}\ + virtual return_type Perform(const args_type& args) {\ + return ::testing::internal::ActionHelper::\ + Perform(this, args);\ + }\ + template \ + return_type gmock_PerformImpl(const args_type& args, arg0_type arg0, \ + arg1_type arg1, arg2_type arg2, arg3_type arg3, arg4_type arg4, \ + arg5_type arg5, arg6_type arg6, arg7_type arg7, arg8_type arg8, \ + arg9_type arg9) const;\ + GMOCK_INTERNAL_DEFN_##value_params\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template operator ::testing::Action() const {\ + return ::testing::Action(\ + new gmock_Impl(GMOCK_INTERNAL_LIST_##value_params));\ + }\ + GMOCK_INTERNAL_DEFN_##value_params\ + private:\ + GTEST_DISALLOW_ASSIGN_(GMOCK_ACTION_CLASS_(name, value_params));\ + };\ + template \ + inline GMOCK_ACTION_CLASS_(name, value_params)<\ + GMOCK_INTERNAL_LIST_##template_params\ + GMOCK_INTERNAL_LIST_TYPE_##value_params> name(\ + GMOCK_INTERNAL_DECL_##value_params) {\ + return GMOCK_ACTION_CLASS_(name, value_params)<\ + GMOCK_INTERNAL_LIST_##template_params\ + GMOCK_INTERNAL_LIST_TYPE_##value_params>(\ + GMOCK_INTERNAL_LIST_##value_params);\ + }\ + template \ + template \ + template \ + typename ::testing::internal::Function::Result\ + GMOCK_ACTION_CLASS_(name, value_params)<\ + GMOCK_INTERNAL_LIST_##template_params\ + GMOCK_INTERNAL_LIST_TYPE_##value_params>::gmock_Impl::\ + gmock_PerformImpl(\ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const + +#define ACTION(name)\ + class name##Action {\ + public:\ + name##Action() {}\ + template \ + class gmock_Impl : public ::testing::ActionInterface {\ + public:\ + typedef F function_type;\ + typedef typename ::testing::internal::Function::Result return_type;\ + typedef typename ::testing::internal::Function::ArgumentTuple\ + args_type;\ + gmock_Impl() {}\ + virtual return_type Perform(const args_type& args) {\ + return ::testing::internal::ActionHelper::\ + Perform(this, args);\ + }\ + template \ + return_type gmock_PerformImpl(const args_type& args, arg0_type arg0, \ + arg1_type arg1, arg2_type arg2, arg3_type arg3, arg4_type arg4, \ + arg5_type arg5, arg6_type arg6, arg7_type arg7, arg8_type arg8, \ + arg9_type arg9) const;\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template operator ::testing::Action() const {\ + return ::testing::Action(new gmock_Impl());\ + }\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##Action);\ + };\ + inline name##Action name() {\ + return name##Action();\ + }\ + template \ + template \ + typename ::testing::internal::Function::Result\ + name##Action::gmock_Impl::gmock_PerformImpl(\ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const + +#define ACTION_P(name, p0)\ + template \ + class name##ActionP {\ + public:\ + name##ActionP(p0##_type gmock_p0) : p0(gmock_p0) {}\ + template \ + class gmock_Impl : public ::testing::ActionInterface {\ + public:\ + typedef F function_type;\ + typedef typename ::testing::internal::Function::Result return_type;\ + typedef typename ::testing::internal::Function::ArgumentTuple\ + args_type;\ + explicit gmock_Impl(p0##_type gmock_p0) : p0(gmock_p0) {}\ + virtual return_type Perform(const args_type& args) {\ + return ::testing::internal::ActionHelper::\ + Perform(this, args);\ + }\ + template \ + return_type gmock_PerformImpl(const args_type& args, arg0_type arg0, \ + arg1_type arg1, arg2_type arg2, arg3_type arg3, arg4_type arg4, \ + arg5_type arg5, arg6_type arg6, arg7_type arg7, arg8_type arg8, \ + arg9_type arg9) const;\ + p0##_type p0;\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template operator ::testing::Action() const {\ + return ::testing::Action(new gmock_Impl(p0));\ + }\ + p0##_type p0;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##ActionP);\ + };\ + template \ + inline name##ActionP name(p0##_type p0) {\ + return name##ActionP(p0);\ + }\ + template \ + template \ + template \ + typename ::testing::internal::Function::Result\ + name##ActionP::gmock_Impl::gmock_PerformImpl(\ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const + +#define ACTION_P2(name, p0, p1)\ + template \ + class name##ActionP2 {\ + public:\ + name##ActionP2(p0##_type gmock_p0, p1##_type gmock_p1) : p0(gmock_p0), \ + p1(gmock_p1) {}\ + template \ + class gmock_Impl : public ::testing::ActionInterface {\ + public:\ + typedef F function_type;\ + typedef typename ::testing::internal::Function::Result return_type;\ + typedef typename ::testing::internal::Function::ArgumentTuple\ + args_type;\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1) : p0(gmock_p0), \ + p1(gmock_p1) {}\ + virtual return_type Perform(const args_type& args) {\ + return ::testing::internal::ActionHelper::\ + Perform(this, args);\ + }\ + template \ + return_type gmock_PerformImpl(const args_type& args, arg0_type arg0, \ + arg1_type arg1, arg2_type arg2, arg3_type arg3, arg4_type arg4, \ + arg5_type arg5, arg6_type arg6, arg7_type arg7, arg8_type arg8, \ + arg9_type arg9) const;\ + p0##_type p0;\ + p1##_type p1;\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template operator ::testing::Action() const {\ + return ::testing::Action(new gmock_Impl(p0, p1));\ + }\ + p0##_type p0;\ + p1##_type p1;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##ActionP2);\ + };\ + template \ + inline name##ActionP2 name(p0##_type p0, \ + p1##_type p1) {\ + return name##ActionP2(p0, p1);\ + }\ + template \ + template \ + template \ + typename ::testing::internal::Function::Result\ + name##ActionP2::gmock_Impl::gmock_PerformImpl(\ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const + +#define ACTION_P3(name, p0, p1, p2)\ + template \ + class name##ActionP3 {\ + public:\ + name##ActionP3(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2) {}\ + template \ + class gmock_Impl : public ::testing::ActionInterface {\ + public:\ + typedef F function_type;\ + typedef typename ::testing::internal::Function::Result return_type;\ + typedef typename ::testing::internal::Function::ArgumentTuple\ + args_type;\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2) {}\ + virtual return_type Perform(const args_type& args) {\ + return ::testing::internal::ActionHelper::\ + Perform(this, args);\ + }\ + template \ + return_type gmock_PerformImpl(const args_type& args, arg0_type arg0, \ + arg1_type arg1, arg2_type arg2, arg3_type arg3, arg4_type arg4, \ + arg5_type arg5, arg6_type arg6, arg7_type arg7, arg8_type arg8, \ + arg9_type arg9) const;\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template operator ::testing::Action() const {\ + return ::testing::Action(new gmock_Impl(p0, p1, p2));\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##ActionP3);\ + };\ + template \ + inline name##ActionP3 name(p0##_type p0, \ + p1##_type p1, p2##_type p2) {\ + return name##ActionP3(p0, p1, p2);\ + }\ + template \ + template \ + template \ + typename ::testing::internal::Function::Result\ + name##ActionP3::gmock_Impl::gmock_PerformImpl(\ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const + +#define ACTION_P4(name, p0, p1, p2, p3)\ + template \ + class name##ActionP4 {\ + public:\ + name##ActionP4(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2, p3##_type gmock_p3) : p0(gmock_p0), p1(gmock_p1), \ + p2(gmock_p2), p3(gmock_p3) {}\ + template \ + class gmock_Impl : public ::testing::ActionInterface {\ + public:\ + typedef F function_type;\ + typedef typename ::testing::internal::Function::Result return_type;\ + typedef typename ::testing::internal::Function::ArgumentTuple\ + args_type;\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3) {}\ + virtual return_type Perform(const args_type& args) {\ + return ::testing::internal::ActionHelper::\ + Perform(this, args);\ + }\ + template \ + return_type gmock_PerformImpl(const args_type& args, arg0_type arg0, \ + arg1_type arg1, arg2_type arg2, arg3_type arg3, arg4_type arg4, \ + arg5_type arg5, arg6_type arg6, arg7_type arg7, arg8_type arg8, \ + arg9_type arg9) const;\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template operator ::testing::Action() const {\ + return ::testing::Action(new gmock_Impl(p0, p1, p2, p3));\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##ActionP4);\ + };\ + template \ + inline name##ActionP4 name(p0##_type p0, p1##_type p1, p2##_type p2, \ + p3##_type p3) {\ + return name##ActionP4(p0, p1, \ + p2, p3);\ + }\ + template \ + template \ + template \ + typename ::testing::internal::Function::Result\ + name##ActionP4::gmock_Impl::gmock_PerformImpl(\ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const + +#define ACTION_P5(name, p0, p1, p2, p3, p4)\ + template \ + class name##ActionP5 {\ + public:\ + name##ActionP5(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2, p3##_type gmock_p3, \ + p4##_type gmock_p4) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4) {}\ + template \ + class gmock_Impl : public ::testing::ActionInterface {\ + public:\ + typedef F function_type;\ + typedef typename ::testing::internal::Function::Result return_type;\ + typedef typename ::testing::internal::Function::ArgumentTuple\ + args_type;\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4) : p0(gmock_p0), \ + p1(gmock_p1), p2(gmock_p2), p3(gmock_p3), p4(gmock_p4) {}\ + virtual return_type Perform(const args_type& args) {\ + return ::testing::internal::ActionHelper::\ + Perform(this, args);\ + }\ + template \ + return_type gmock_PerformImpl(const args_type& args, arg0_type arg0, \ + arg1_type arg1, arg2_type arg2, arg3_type arg3, arg4_type arg4, \ + arg5_type arg5, arg6_type arg6, arg7_type arg7, arg8_type arg8, \ + arg9_type arg9) const;\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template operator ::testing::Action() const {\ + return ::testing::Action(new gmock_Impl(p0, p1, p2, p3, p4));\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##ActionP5);\ + };\ + template \ + inline name##ActionP5 name(p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, \ + p4##_type p4) {\ + return name##ActionP5(p0, p1, p2, p3, p4);\ + }\ + template \ + template \ + template \ + typename ::testing::internal::Function::Result\ + name##ActionP5::gmock_Impl::gmock_PerformImpl(\ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const + +#define ACTION_P6(name, p0, p1, p2, p3, p4, p5)\ + template \ + class name##ActionP6 {\ + public:\ + name##ActionP6(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2, p3##_type gmock_p3, p4##_type gmock_p4, \ + p5##_type gmock_p5) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4), p5(gmock_p5) {}\ + template \ + class gmock_Impl : public ::testing::ActionInterface {\ + public:\ + typedef F function_type;\ + typedef typename ::testing::internal::Function::Result return_type;\ + typedef typename ::testing::internal::Function::ArgumentTuple\ + args_type;\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, \ + p5##_type gmock_p5) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4), p5(gmock_p5) {}\ + virtual return_type Perform(const args_type& args) {\ + return ::testing::internal::ActionHelper::\ + Perform(this, args);\ + }\ + template \ + return_type gmock_PerformImpl(const args_type& args, arg0_type arg0, \ + arg1_type arg1, arg2_type arg2, arg3_type arg3, arg4_type arg4, \ + arg5_type arg5, arg6_type arg6, arg7_type arg7, arg8_type arg8, \ + arg9_type arg9) const;\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template operator ::testing::Action() const {\ + return ::testing::Action(new gmock_Impl(p0, p1, p2, p3, p4, p5));\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##ActionP6);\ + };\ + template \ + inline name##ActionP6 name(p0##_type p0, p1##_type p1, p2##_type p2, \ + p3##_type p3, p4##_type p4, p5##_type p5) {\ + return name##ActionP6(p0, p1, p2, p3, p4, p5);\ + }\ + template \ + template \ + template \ + typename ::testing::internal::Function::Result\ + name##ActionP6::gmock_Impl::gmock_PerformImpl(\ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const + +#define ACTION_P7(name, p0, p1, p2, p3, p4, p5, p6)\ + template \ + class name##ActionP7 {\ + public:\ + name##ActionP7(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2, p3##_type gmock_p3, p4##_type gmock_p4, \ + p5##_type gmock_p5, p6##_type gmock_p6) : p0(gmock_p0), p1(gmock_p1), \ + p2(gmock_p2), p3(gmock_p3), p4(gmock_p4), p5(gmock_p5), \ + p6(gmock_p6) {}\ + template \ + class gmock_Impl : public ::testing::ActionInterface {\ + public:\ + typedef F function_type;\ + typedef typename ::testing::internal::Function::Result return_type;\ + typedef typename ::testing::internal::Function::ArgumentTuple\ + args_type;\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \ + p6##_type gmock_p6) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4), p5(gmock_p5), p6(gmock_p6) {}\ + virtual return_type Perform(const args_type& args) {\ + return ::testing::internal::ActionHelper::\ + Perform(this, args);\ + }\ + template \ + return_type gmock_PerformImpl(const args_type& args, arg0_type arg0, \ + arg1_type arg1, arg2_type arg2, arg3_type arg3, arg4_type arg4, \ + arg5_type arg5, arg6_type arg6, arg7_type arg7, arg8_type arg8, \ + arg9_type arg9) const;\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template operator ::testing::Action() const {\ + return ::testing::Action(new gmock_Impl(p0, p1, p2, p3, p4, p5, \ + p6));\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##ActionP7);\ + };\ + template \ + inline name##ActionP7 name(p0##_type p0, p1##_type p1, \ + p2##_type p2, p3##_type p3, p4##_type p4, p5##_type p5, \ + p6##_type p6) {\ + return name##ActionP7(p0, p1, p2, p3, p4, p5, p6);\ + }\ + template \ + template \ + template \ + typename ::testing::internal::Function::Result\ + name##ActionP7::gmock_Impl::gmock_PerformImpl(\ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const + +#define ACTION_P8(name, p0, p1, p2, p3, p4, p5, p6, p7)\ + template \ + class name##ActionP8 {\ + public:\ + name##ActionP8(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2, p3##_type gmock_p3, p4##_type gmock_p4, \ + p5##_type gmock_p5, p6##_type gmock_p6, \ + p7##_type gmock_p7) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4), p5(gmock_p5), p6(gmock_p6), \ + p7(gmock_p7) {}\ + template \ + class gmock_Impl : public ::testing::ActionInterface {\ + public:\ + typedef F function_type;\ + typedef typename ::testing::internal::Function::Result return_type;\ + typedef typename ::testing::internal::Function::ArgumentTuple\ + args_type;\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \ + p6##_type gmock_p6, p7##_type gmock_p7) : p0(gmock_p0), \ + p1(gmock_p1), p2(gmock_p2), p3(gmock_p3), p4(gmock_p4), \ + p5(gmock_p5), p6(gmock_p6), p7(gmock_p7) {}\ + virtual return_type Perform(const args_type& args) {\ + return ::testing::internal::ActionHelper::\ + Perform(this, args);\ + }\ + template \ + return_type gmock_PerformImpl(const args_type& args, arg0_type arg0, \ + arg1_type arg1, arg2_type arg2, arg3_type arg3, arg4_type arg4, \ + arg5_type arg5, arg6_type arg6, arg7_type arg7, arg8_type arg8, \ + arg9_type arg9) const;\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + p7##_type p7;\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template operator ::testing::Action() const {\ + return ::testing::Action(new gmock_Impl(p0, p1, p2, p3, p4, p5, \ + p6, p7));\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + p7##_type p7;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##ActionP8);\ + };\ + template \ + inline name##ActionP8 name(p0##_type p0, \ + p1##_type p1, p2##_type p2, p3##_type p3, p4##_type p4, p5##_type p5, \ + p6##_type p6, p7##_type p7) {\ + return name##ActionP8(p0, p1, p2, p3, p4, p5, \ + p6, p7);\ + }\ + template \ + template \ + template \ + typename ::testing::internal::Function::Result\ + name##ActionP8::gmock_Impl::gmock_PerformImpl(\ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const + +#define ACTION_P9(name, p0, p1, p2, p3, p4, p5, p6, p7, p8)\ + template \ + class name##ActionP9 {\ + public:\ + name##ActionP9(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2, p3##_type gmock_p3, p4##_type gmock_p4, \ + p5##_type gmock_p5, p6##_type gmock_p6, p7##_type gmock_p7, \ + p8##_type gmock_p8) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4), p5(gmock_p5), p6(gmock_p6), p7(gmock_p7), \ + p8(gmock_p8) {}\ + template \ + class gmock_Impl : public ::testing::ActionInterface {\ + public:\ + typedef F function_type;\ + typedef typename ::testing::internal::Function::Result return_type;\ + typedef typename ::testing::internal::Function::ArgumentTuple\ + args_type;\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \ + p6##_type gmock_p6, p7##_type gmock_p7, \ + p8##_type gmock_p8) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4), p5(gmock_p5), p6(gmock_p6), \ + p7(gmock_p7), p8(gmock_p8) {}\ + virtual return_type Perform(const args_type& args) {\ + return ::testing::internal::ActionHelper::\ + Perform(this, args);\ + }\ + template \ + return_type gmock_PerformImpl(const args_type& args, arg0_type arg0, \ + arg1_type arg1, arg2_type arg2, arg3_type arg3, arg4_type arg4, \ + arg5_type arg5, arg6_type arg6, arg7_type arg7, arg8_type arg8, \ + arg9_type arg9) const;\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + p7##_type p7;\ + p8##_type p8;\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template operator ::testing::Action() const {\ + return ::testing::Action(new gmock_Impl(p0, p1, p2, p3, p4, p5, \ + p6, p7, p8));\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + p7##_type p7;\ + p8##_type p8;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##ActionP9);\ + };\ + template \ + inline name##ActionP9 name(p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, \ + p4##_type p4, p5##_type p5, p6##_type p6, p7##_type p7, \ + p8##_type p8) {\ + return name##ActionP9(p0, p1, p2, \ + p3, p4, p5, p6, p7, p8);\ + }\ + template \ + template \ + template \ + typename ::testing::internal::Function::Result\ + name##ActionP9::gmock_Impl::gmock_PerformImpl(\ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const + +#define ACTION_P10(name, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9)\ + template \ + class name##ActionP10 {\ + public:\ + name##ActionP10(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2, p3##_type gmock_p3, p4##_type gmock_p4, \ + p5##_type gmock_p5, p6##_type gmock_p6, p7##_type gmock_p7, \ + p8##_type gmock_p8, p9##_type gmock_p9) : p0(gmock_p0), p1(gmock_p1), \ + p2(gmock_p2), p3(gmock_p3), p4(gmock_p4), p5(gmock_p5), p6(gmock_p6), \ + p7(gmock_p7), p8(gmock_p8), p9(gmock_p9) {}\ + template \ + class gmock_Impl : public ::testing::ActionInterface {\ + public:\ + typedef F function_type;\ + typedef typename ::testing::internal::Function::Result return_type;\ + typedef typename ::testing::internal::Function::ArgumentTuple\ + args_type;\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \ + p6##_type gmock_p6, p7##_type gmock_p7, p8##_type gmock_p8, \ + p9##_type gmock_p9) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4), p5(gmock_p5), p6(gmock_p6), \ + p7(gmock_p7), p8(gmock_p8), p9(gmock_p9) {}\ + virtual return_type Perform(const args_type& args) {\ + return ::testing::internal::ActionHelper::\ + Perform(this, args);\ + }\ + template \ + return_type gmock_PerformImpl(const args_type& args, arg0_type arg0, \ + arg1_type arg1, arg2_type arg2, arg3_type arg3, arg4_type arg4, \ + arg5_type arg5, arg6_type arg6, arg7_type arg7, arg8_type arg8, \ + arg9_type arg9) const;\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + p7##_type p7;\ + p8##_type p8;\ + p9##_type p9;\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template operator ::testing::Action() const {\ + return ::testing::Action(new gmock_Impl(p0, p1, p2, p3, p4, p5, \ + p6, p7, p8, p9));\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + p7##_type p7;\ + p8##_type p8;\ + p9##_type p9;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##ActionP10);\ + };\ + template \ + inline name##ActionP10 name(p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, \ + p4##_type p4, p5##_type p5, p6##_type p6, p7##_type p7, p8##_type p8, \ + p9##_type p9) {\ + return name##ActionP10(p0, \ + p1, p2, p3, p4, p5, p6, p7, p8, p9);\ + }\ + template \ + template \ + template \ + typename ::testing::internal::Function::Result\ + name##ActionP10::gmock_Impl::gmock_PerformImpl(\ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const + +namespace testing { + +// The ACTION*() macros trigger warning C4100 (unreferenced formal +// parameter) in MSVC with -W4. Unfortunately they cannot be fixed in +// the macro definition, as the warnings are generated when the macro +// is expanded and macro expansion cannot contain #pragma. Therefore +// we suppress them here. +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable:4100) +#endif + +// Various overloads for InvokeArgument(). +// +// The InvokeArgument(a1, a2, ..., a_k) action invokes the N-th +// (0-based) argument, which must be a k-ary callable, of the mock +// function, with arguments a1, a2, ..., a_k. +// +// Notes: +// +// 1. The arguments are passed by value by default. If you need to +// pass an argument by reference, wrap it inside ByRef(). For +// example, +// +// InvokeArgument<1>(5, string("Hello"), ByRef(foo)) +// +// passes 5 and string("Hello") by value, and passes foo by +// reference. +// +// 2. If the callable takes an argument by reference but ByRef() is +// not used, it will receive the reference to a copy of the value, +// instead of the original value. For example, when the 0-th +// argument of the mock function takes a const string&, the action +// +// InvokeArgument<0>(string("Hello")) +// +// makes a copy of the temporary string("Hello") object and passes a +// reference of the copy, instead of the original temporary object, +// to the callable. This makes it easy for a user to define an +// InvokeArgument action from temporary values and have it performed +// later. + +ACTION_TEMPLATE(InvokeArgument, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_0_VALUE_PARAMS()) { + return internal::CallableHelper::Call( + ::std::tr1::get(args)); +} + +ACTION_TEMPLATE(InvokeArgument, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_1_VALUE_PARAMS(p0)) { + return internal::CallableHelper::Call( + ::std::tr1::get(args), p0); +} + +ACTION_TEMPLATE(InvokeArgument, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_2_VALUE_PARAMS(p0, p1)) { + return internal::CallableHelper::Call( + ::std::tr1::get(args), p0, p1); +} + +ACTION_TEMPLATE(InvokeArgument, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_3_VALUE_PARAMS(p0, p1, p2)) { + return internal::CallableHelper::Call( + ::std::tr1::get(args), p0, p1, p2); +} + +ACTION_TEMPLATE(InvokeArgument, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_4_VALUE_PARAMS(p0, p1, p2, p3)) { + return internal::CallableHelper::Call( + ::std::tr1::get(args), p0, p1, p2, p3); +} + +ACTION_TEMPLATE(InvokeArgument, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_5_VALUE_PARAMS(p0, p1, p2, p3, p4)) { + return internal::CallableHelper::Call( + ::std::tr1::get(args), p0, p1, p2, p3, p4); +} + +ACTION_TEMPLATE(InvokeArgument, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, p5)) { + return internal::CallableHelper::Call( + ::std::tr1::get(args), p0, p1, p2, p3, p4, p5); +} + +ACTION_TEMPLATE(InvokeArgument, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6)) { + return internal::CallableHelper::Call( + ::std::tr1::get(args), p0, p1, p2, p3, p4, p5, p6); +} + +ACTION_TEMPLATE(InvokeArgument, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, p7)) { + return internal::CallableHelper::Call( + ::std::tr1::get(args), p0, p1, p2, p3, p4, p5, p6, p7); +} + +ACTION_TEMPLATE(InvokeArgument, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, p7, p8)) { + return internal::CallableHelper::Call( + ::std::tr1::get(args), p0, p1, p2, p3, p4, p5, p6, p7, p8); +} + +ACTION_TEMPLATE(InvokeArgument, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, p7, p8, p9)) { + return internal::CallableHelper::Call( + ::std::tr1::get(args), p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); +} + +// Various overloads for ReturnNew(). +// +// The ReturnNew(a1, a2, ..., a_k) action returns a pointer to a new +// instance of type T, constructed on the heap with constructor arguments +// a1, a2, ..., and a_k. The caller assumes ownership of the returned value. +ACTION_TEMPLATE(ReturnNew, + HAS_1_TEMPLATE_PARAMS(typename, T), + AND_0_VALUE_PARAMS()) { + return new T(); +} + +ACTION_TEMPLATE(ReturnNew, + HAS_1_TEMPLATE_PARAMS(typename, T), + AND_1_VALUE_PARAMS(p0)) { + return new T(p0); +} + +ACTION_TEMPLATE(ReturnNew, + HAS_1_TEMPLATE_PARAMS(typename, T), + AND_2_VALUE_PARAMS(p0, p1)) { + return new T(p0, p1); +} + +ACTION_TEMPLATE(ReturnNew, + HAS_1_TEMPLATE_PARAMS(typename, T), + AND_3_VALUE_PARAMS(p0, p1, p2)) { + return new T(p0, p1, p2); +} + +ACTION_TEMPLATE(ReturnNew, + HAS_1_TEMPLATE_PARAMS(typename, T), + AND_4_VALUE_PARAMS(p0, p1, p2, p3)) { + return new T(p0, p1, p2, p3); +} + +ACTION_TEMPLATE(ReturnNew, + HAS_1_TEMPLATE_PARAMS(typename, T), + AND_5_VALUE_PARAMS(p0, p1, p2, p3, p4)) { + return new T(p0, p1, p2, p3, p4); +} + +ACTION_TEMPLATE(ReturnNew, + HAS_1_TEMPLATE_PARAMS(typename, T), + AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, p5)) { + return new T(p0, p1, p2, p3, p4, p5); +} + +ACTION_TEMPLATE(ReturnNew, + HAS_1_TEMPLATE_PARAMS(typename, T), + AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6)) { + return new T(p0, p1, p2, p3, p4, p5, p6); +} + +ACTION_TEMPLATE(ReturnNew, + HAS_1_TEMPLATE_PARAMS(typename, T), + AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, p7)) { + return new T(p0, p1, p2, p3, p4, p5, p6, p7); +} + +ACTION_TEMPLATE(ReturnNew, + HAS_1_TEMPLATE_PARAMS(typename, T), + AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, p7, p8)) { + return new T(p0, p1, p2, p3, p4, p5, p6, p7, p8); +} + +ACTION_TEMPLATE(ReturnNew, + HAS_1_TEMPLATE_PARAMS(typename, T), + AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, p7, p8, p9)) { + return new T(p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); +} + +#ifdef _MSC_VER +# pragma warning(pop) +#endif + +} // namespace testing + +#endif // GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_ACTIONS_H_ diff --git a/vendor/gmock-1.7.0/include/gmock/gmock-generated-actions.h.pump b/vendor/gmock-1.7.0/include/gmock/gmock-generated-actions.h.pump new file mode 100644 index 0000000000..8e2b57352e --- /dev/null +++ b/vendor/gmock-1.7.0/include/gmock/gmock-generated-actions.h.pump @@ -0,0 +1,821 @@ +$$ -*- mode: c++; -*- +$$ This is a Pump source file. Please use Pump to convert it to +$$ gmock-generated-actions.h. +$$ +$var n = 10 $$ The maximum arity we support. +$$}} This meta comment fixes auto-indentation in editors. +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) + +// Google Mock - a framework for writing C++ mock classes. +// +// This file implements some commonly used variadic actions. + +#ifndef GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_ACTIONS_H_ +#define GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_ACTIONS_H_ + +#include "gmock/gmock-actions.h" +#include "gmock/internal/gmock-port.h" + +namespace testing { +namespace internal { + +// InvokeHelper knows how to unpack an N-tuple and invoke an N-ary +// function or method with the unpacked values, where F is a function +// type that takes N arguments. +template +class InvokeHelper; + + +$range i 0..n +$for i [[ +$range j 1..i +$var types = [[$for j [[, typename A$j]]]] +$var as = [[$for j, [[A$j]]]] +$var args = [[$if i==0 [[]] $else [[ args]]]] +$var import = [[$if i==0 [[]] $else [[ + using ::std::tr1::get; + +]]]] +$var gets = [[$for j, [[get<$(j - 1)>(args)]]]] +template +class InvokeHelper > { + public: + template + static R Invoke(Function function, const ::std::tr1::tuple<$as>&$args) { +$import return function($gets); + } + + template + static R InvokeMethod(Class* obj_ptr, + MethodPtr method_ptr, + const ::std::tr1::tuple<$as>&$args) { +$import return (obj_ptr->*method_ptr)($gets); + } +}; + + +]] +// CallableHelper has static methods for invoking "callables", +// i.e. function pointers and functors. It uses overloading to +// provide a uniform interface for invoking different kinds of +// callables. In particular, you can use: +// +// CallableHelper::Call(callable, a1, a2, ..., an) +// +// to invoke an n-ary callable, where R is its return type. If an +// argument, say a2, needs to be passed by reference, you should write +// ByRef(a2) instead of a2 in the above expression. +template +class CallableHelper { + public: + // Calls a nullary callable. + template + static R Call(Function function) { return function(); } + + // Calls a unary callable. + + // We deliberately pass a1 by value instead of const reference here + // in case it is a C-string literal. If we had declared the + // parameter as 'const A1& a1' and write Call(function, "Hi"), the + // compiler would've thought A1 is 'char[3]', which causes trouble + // when you need to copy a value of type A1. By declaring the + // parameter as 'A1 a1', the compiler will correctly infer that A1 + // is 'const char*' when it sees Call(function, "Hi"). + // + // Since this function is defined inline, the compiler can get rid + // of the copying of the arguments. Therefore the performance won't + // be hurt. + template + static R Call(Function function, A1 a1) { return function(a1); } + +$range i 2..n +$for i +[[ +$var arity = [[$if i==2 [[binary]] $elif i==3 [[ternary]] $else [[$i-ary]]]] + + // Calls a $arity callable. + +$range j 1..i +$var typename_As = [[$for j, [[typename A$j]]]] +$var Aas = [[$for j, [[A$j a$j]]]] +$var as = [[$for j, [[a$j]]]] +$var typename_Ts = [[$for j, [[typename T$j]]]] +$var Ts = [[$for j, [[T$j]]]] + template + static R Call(Function function, $Aas) { + return function($as); + } + +]] +}; // class CallableHelper + +// An INTERNAL macro for extracting the type of a tuple field. It's +// subject to change without notice - DO NOT USE IN USER CODE! +#define GMOCK_FIELD_(Tuple, N) \ + typename ::std::tr1::tuple_element::type + +$range i 1..n + +// SelectArgs::type is the +// type of an n-ary function whose i-th (1-based) argument type is the +// k{i}-th (0-based) field of ArgumentTuple, which must be a tuple +// type, and whose return type is Result. For example, +// SelectArgs, 0, 3>::type +// is int(bool, long). +// +// SelectArgs::Select(args) +// returns the selected fields (k1, k2, ..., k_n) of args as a tuple. +// For example, +// SelectArgs, 2, 0>::Select( +// ::std::tr1::make_tuple(true, 'a', 2.5)) +// returns ::std::tr1::tuple (2.5, true). +// +// The numbers in list k1, k2, ..., k_n must be >= 0, where n can be +// in the range [0, $n]. Duplicates are allowed and they don't have +// to be in an ascending or descending order. + +template +class SelectArgs { + public: + typedef Result type($for i, [[GMOCK_FIELD_(ArgumentTuple, k$i)]]); + typedef typename Function::ArgumentTuple SelectedArgs; + static SelectedArgs Select(const ArgumentTuple& args) { + using ::std::tr1::get; + return SelectedArgs($for i, [[get(args)]]); + } +}; + + +$for i [[ +$range j 1..n +$range j1 1..i-1 +template +class SelectArgs { + public: + typedef Result type($for j1, [[GMOCK_FIELD_(ArgumentTuple, k$j1)]]); + typedef typename Function::ArgumentTuple SelectedArgs; + static SelectedArgs Select(const ArgumentTuple& [[]] +$if i == 1 [[/* args */]] $else [[args]]) { + using ::std::tr1::get; + return SelectedArgs($for j1, [[get(args)]]); + } +}; + + +]] +#undef GMOCK_FIELD_ + +$var ks = [[$for i, [[k$i]]]] + +// Implements the WithArgs action. +template +class WithArgsAction { + public: + explicit WithArgsAction(const InnerAction& action) : action_(action) {} + + template + operator Action() const { return MakeAction(new Impl(action_)); } + + private: + template + class Impl : public ActionInterface { + public: + typedef typename Function::Result Result; + typedef typename Function::ArgumentTuple ArgumentTuple; + + explicit Impl(const InnerAction& action) : action_(action) {} + + virtual Result Perform(const ArgumentTuple& args) { + return action_.Perform(SelectArgs::Select(args)); + } + + private: + typedef typename SelectArgs::type InnerFunctionType; + + Action action_; + }; + + const InnerAction action_; + + GTEST_DISALLOW_ASSIGN_(WithArgsAction); +}; + +// A macro from the ACTION* family (defined later in this file) +// defines an action that can be used in a mock function. Typically, +// these actions only care about a subset of the arguments of the mock +// function. For example, if such an action only uses the second +// argument, it can be used in any mock function that takes >= 2 +// arguments where the type of the second argument is compatible. +// +// Therefore, the action implementation must be prepared to take more +// arguments than it needs. The ExcessiveArg type is used to +// represent those excessive arguments. In order to keep the compiler +// error messages tractable, we define it in the testing namespace +// instead of testing::internal. However, this is an INTERNAL TYPE +// and subject to change without notice, so a user MUST NOT USE THIS +// TYPE DIRECTLY. +struct ExcessiveArg {}; + +// A helper class needed for implementing the ACTION* macros. +template +class ActionHelper { + public: +$range i 0..n +$for i + +[[ +$var template = [[$if i==0 [[]] $else [[ +$range j 0..i-1 + template <$for j, [[typename A$j]]> +]]]] +$range j 0..i-1 +$var As = [[$for j, [[A$j]]]] +$var as = [[$for j, [[get<$j>(args)]]]] +$range k 1..n-i +$var eas = [[$for k, [[ExcessiveArg()]]]] +$var arg_list = [[$if (i==0) | (i==n) [[$as$eas]] $else [[$as, $eas]]]] +$template + static Result Perform(Impl* impl, const ::std::tr1::tuple<$As>& args) { + using ::std::tr1::get; + return impl->template gmock_PerformImpl<$As>(args, $arg_list); + } + +]] +}; + +} // namespace internal + +// Various overloads for Invoke(). + +// WithArgs(an_action) creates an action that passes +// the selected arguments of the mock function to an_action and +// performs it. It serves as an adaptor between actions with +// different argument lists. C++ doesn't support default arguments for +// function templates, so we have to overload it. + +$range i 1..n +$for i [[ +$range j 1..i +template <$for j [[int k$j, ]]typename InnerAction> +inline internal::WithArgsAction +WithArgs(const InnerAction& action) { + return internal::WithArgsAction(action); +} + + +]] +// Creates an action that does actions a1, a2, ..., sequentially in +// each invocation. +$range i 2..n +$for i [[ +$range j 2..i +$var types = [[$for j, [[typename Action$j]]]] +$var Aas = [[$for j [[, Action$j a$j]]]] + +template +$range k 1..i-1 + +inline $for k [[internal::DoBothAction]] + +DoAll(Action1 a1$Aas) { +$if i==2 [[ + + return internal::DoBothAction(a1, a2); +]] $else [[ +$range j2 2..i + + return DoAll(a1, DoAll($for j2, [[a$j2]])); +]] + +} + +]] + +} // namespace testing + +// The ACTION* family of macros can be used in a namespace scope to +// define custom actions easily. The syntax: +// +// ACTION(name) { statements; } +// +// will define an action with the given name that executes the +// statements. The value returned by the statements will be used as +// the return value of the action. Inside the statements, you can +// refer to the K-th (0-based) argument of the mock function by +// 'argK', and refer to its type by 'argK_type'. For example: +// +// ACTION(IncrementArg1) { +// arg1_type temp = arg1; +// return ++(*temp); +// } +// +// allows you to write +// +// ...WillOnce(IncrementArg1()); +// +// You can also refer to the entire argument tuple and its type by +// 'args' and 'args_type', and refer to the mock function type and its +// return type by 'function_type' and 'return_type'. +// +// Note that you don't need to specify the types of the mock function +// arguments. However rest assured that your code is still type-safe: +// you'll get a compiler error if *arg1 doesn't support the ++ +// operator, or if the type of ++(*arg1) isn't compatible with the +// mock function's return type, for example. +// +// Sometimes you'll want to parameterize the action. For that you can use +// another macro: +// +// ACTION_P(name, param_name) { statements; } +// +// For example: +// +// ACTION_P(Add, n) { return arg0 + n; } +// +// will allow you to write: +// +// ...WillOnce(Add(5)); +// +// Note that you don't need to provide the type of the parameter +// either. If you need to reference the type of a parameter named +// 'foo', you can write 'foo_type'. For example, in the body of +// ACTION_P(Add, n) above, you can write 'n_type' to refer to the type +// of 'n'. +// +// We also provide ACTION_P2, ACTION_P3, ..., up to ACTION_P$n to support +// multi-parameter actions. +// +// For the purpose of typing, you can view +// +// ACTION_Pk(Foo, p1, ..., pk) { ... } +// +// as shorthand for +// +// template +// FooActionPk Foo(p1_type p1, ..., pk_type pk) { ... } +// +// In particular, you can provide the template type arguments +// explicitly when invoking Foo(), as in Foo(5, false); +// although usually you can rely on the compiler to infer the types +// for you automatically. You can assign the result of expression +// Foo(p1, ..., pk) to a variable of type FooActionPk. This can be useful when composing actions. +// +// You can also overload actions with different numbers of parameters: +// +// ACTION_P(Plus, a) { ... } +// ACTION_P2(Plus, a, b) { ... } +// +// While it's tempting to always use the ACTION* macros when defining +// a new action, you should also consider implementing ActionInterface +// or using MakePolymorphicAction() instead, especially if you need to +// use the action a lot. While these approaches require more work, +// they give you more control on the types of the mock function +// arguments and the action parameters, which in general leads to +// better compiler error messages that pay off in the long run. They +// also allow overloading actions based on parameter types (as opposed +// to just based on the number of parameters). +// +// CAVEAT: +// +// ACTION*() can only be used in a namespace scope. The reason is +// that C++ doesn't yet allow function-local types to be used to +// instantiate templates. The up-coming C++0x standard will fix this. +// Once that's done, we'll consider supporting using ACTION*() inside +// a function. +// +// MORE INFORMATION: +// +// To learn more about using these macros, please search for 'ACTION' +// on http://code.google.com/p/googlemock/wiki/CookBook. + +$range i 0..n +$range k 0..n-1 + +// An internal macro needed for implementing ACTION*(). +#define GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_\ + const args_type& args GTEST_ATTRIBUTE_UNUSED_ +$for k [[, \ + arg$k[[]]_type arg$k GTEST_ATTRIBUTE_UNUSED_]] + + +// Sometimes you want to give an action explicit template parameters +// that cannot be inferred from its value parameters. ACTION() and +// ACTION_P*() don't support that. ACTION_TEMPLATE() remedies that +// and can be viewed as an extension to ACTION() and ACTION_P*(). +// +// The syntax: +// +// ACTION_TEMPLATE(ActionName, +// HAS_m_TEMPLATE_PARAMS(kind1, name1, ..., kind_m, name_m), +// AND_n_VALUE_PARAMS(p1, ..., p_n)) { statements; } +// +// defines an action template that takes m explicit template +// parameters and n value parameters. name_i is the name of the i-th +// template parameter, and kind_i specifies whether it's a typename, +// an integral constant, or a template. p_i is the name of the i-th +// value parameter. +// +// Example: +// +// // DuplicateArg(output) converts the k-th argument of the mock +// // function to type T and copies it to *output. +// ACTION_TEMPLATE(DuplicateArg, +// HAS_2_TEMPLATE_PARAMS(int, k, typename, T), +// AND_1_VALUE_PARAMS(output)) { +// *output = T(std::tr1::get(args)); +// } +// ... +// int n; +// EXPECT_CALL(mock, Foo(_, _)) +// .WillOnce(DuplicateArg<1, unsigned char>(&n)); +// +// To create an instance of an action template, write: +// +// ActionName(v1, ..., v_n) +// +// where the ts are the template arguments and the vs are the value +// arguments. The value argument types are inferred by the compiler. +// If you want to explicitly specify the value argument types, you can +// provide additional template arguments: +// +// ActionName(v1, ..., v_n) +// +// where u_i is the desired type of v_i. +// +// ACTION_TEMPLATE and ACTION/ACTION_P* can be overloaded on the +// number of value parameters, but not on the number of template +// parameters. Without the restriction, the meaning of the following +// is unclear: +// +// OverloadedAction(x); +// +// Are we using a single-template-parameter action where 'bool' refers +// to the type of x, or are we using a two-template-parameter action +// where the compiler is asked to infer the type of x? +// +// Implementation notes: +// +// GMOCK_INTERNAL_*_HAS_m_TEMPLATE_PARAMS and +// GMOCK_INTERNAL_*_AND_n_VALUE_PARAMS are internal macros for +// implementing ACTION_TEMPLATE. The main trick we use is to create +// new macro invocations when expanding a macro. For example, we have +// +// #define ACTION_TEMPLATE(name, template_params, value_params) +// ... GMOCK_INTERNAL_DECL_##template_params ... +// +// which causes ACTION_TEMPLATE(..., HAS_1_TEMPLATE_PARAMS(typename, T), ...) +// to expand to +// +// ... GMOCK_INTERNAL_DECL_HAS_1_TEMPLATE_PARAMS(typename, T) ... +// +// Since GMOCK_INTERNAL_DECL_HAS_1_TEMPLATE_PARAMS is a macro, the +// preprocessor will continue to expand it to +// +// ... typename T ... +// +// This technique conforms to the C++ standard and is portable. It +// allows us to implement action templates using O(N) code, where N is +// the maximum number of template/value parameters supported. Without +// using it, we'd have to devote O(N^2) amount of code to implement all +// combinations of m and n. + +// Declares the template parameters. + +$range j 1..n +$for j [[ +$range m 0..j-1 +#define GMOCK_INTERNAL_DECL_HAS_$j[[]] +_TEMPLATE_PARAMS($for m, [[kind$m, name$m]]) $for m, [[kind$m name$m]] + + +]] + +// Lists the template parameters. + +$for j [[ +$range m 0..j-1 +#define GMOCK_INTERNAL_LIST_HAS_$j[[]] +_TEMPLATE_PARAMS($for m, [[kind$m, name$m]]) $for m, [[name$m]] + + +]] + +// Declares the types of value parameters. + +$for i [[ +$range j 0..i-1 +#define GMOCK_INTERNAL_DECL_TYPE_AND_$i[[]] +_VALUE_PARAMS($for j, [[p$j]]) $for j [[, typename p$j##_type]] + + +]] + +// Initializes the value parameters. + +$for i [[ +$range j 0..i-1 +#define GMOCK_INTERNAL_INIT_AND_$i[[]]_VALUE_PARAMS($for j, [[p$j]])\ + ($for j, [[p$j##_type gmock_p$j]])$if i>0 [[ : ]]$for j, [[p$j(gmock_p$j)]] + + +]] + +// Declares the fields for storing the value parameters. + +$for i [[ +$range j 0..i-1 +#define GMOCK_INTERNAL_DEFN_AND_$i[[]] +_VALUE_PARAMS($for j, [[p$j]]) $for j [[p$j##_type p$j; ]] + + +]] + +// Lists the value parameters. + +$for i [[ +$range j 0..i-1 +#define GMOCK_INTERNAL_LIST_AND_$i[[]] +_VALUE_PARAMS($for j, [[p$j]]) $for j, [[p$j]] + + +]] + +// Lists the value parameter types. + +$for i [[ +$range j 0..i-1 +#define GMOCK_INTERNAL_LIST_TYPE_AND_$i[[]] +_VALUE_PARAMS($for j, [[p$j]]) $for j [[, p$j##_type]] + + +]] + +// Declares the value parameters. + +$for i [[ +$range j 0..i-1 +#define GMOCK_INTERNAL_DECL_AND_$i[[]]_VALUE_PARAMS($for j, [[p$j]]) [[]] +$for j, [[p$j##_type p$j]] + + +]] + +// The suffix of the class template implementing the action template. +$for i [[ + + +$range j 0..i-1 +#define GMOCK_INTERNAL_COUNT_AND_$i[[]]_VALUE_PARAMS($for j, [[p$j]]) [[]] +$if i==1 [[P]] $elif i>=2 [[P$i]] +]] + + +// The name of the class template implementing the action template. +#define GMOCK_ACTION_CLASS_(name, value_params)\ + GTEST_CONCAT_TOKEN_(name##Action, GMOCK_INTERNAL_COUNT_##value_params) + +$range k 0..n-1 + +#define ACTION_TEMPLATE(name, template_params, value_params)\ + template \ + class GMOCK_ACTION_CLASS_(name, value_params) {\ + public:\ + GMOCK_ACTION_CLASS_(name, value_params)\ + GMOCK_INTERNAL_INIT_##value_params {}\ + template \ + class gmock_Impl : public ::testing::ActionInterface {\ + public:\ + typedef F function_type;\ + typedef typename ::testing::internal::Function::Result return_type;\ + typedef typename ::testing::internal::Function::ArgumentTuple\ + args_type;\ + explicit gmock_Impl GMOCK_INTERNAL_INIT_##value_params {}\ + virtual return_type Perform(const args_type& args) {\ + return ::testing::internal::ActionHelper::\ + Perform(this, args);\ + }\ + template <$for k, [[typename arg$k[[]]_type]]>\ + return_type gmock_PerformImpl(const args_type& args[[]] +$for k [[, arg$k[[]]_type arg$k]]) const;\ + GMOCK_INTERNAL_DEFN_##value_params\ + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template operator ::testing::Action() const {\ + return ::testing::Action(\ + new gmock_Impl(GMOCK_INTERNAL_LIST_##value_params));\ + }\ + GMOCK_INTERNAL_DEFN_##value_params\ + private:\ + GTEST_DISALLOW_ASSIGN_(GMOCK_ACTION_CLASS_(name, value_params));\ + };\ + template \ + inline GMOCK_ACTION_CLASS_(name, value_params)<\ + GMOCK_INTERNAL_LIST_##template_params\ + GMOCK_INTERNAL_LIST_TYPE_##value_params> name(\ + GMOCK_INTERNAL_DECL_##value_params) {\ + return GMOCK_ACTION_CLASS_(name, value_params)<\ + GMOCK_INTERNAL_LIST_##template_params\ + GMOCK_INTERNAL_LIST_TYPE_##value_params>(\ + GMOCK_INTERNAL_LIST_##value_params);\ + }\ + template \ + template \ + template \ + typename ::testing::internal::Function::Result\ + GMOCK_ACTION_CLASS_(name, value_params)<\ + GMOCK_INTERNAL_LIST_##template_params\ + GMOCK_INTERNAL_LIST_TYPE_##value_params>::gmock_Impl::\ + gmock_PerformImpl(\ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const + +$for i + +[[ +$var template = [[$if i==0 [[]] $else [[ +$range j 0..i-1 + + template <$for j, [[typename p$j##_type]]>\ +]]]] +$var class_name = [[name##Action[[$if i==0 [[]] $elif i==1 [[P]] + $else [[P$i]]]]]] +$range j 0..i-1 +$var ctor_param_list = [[$for j, [[p$j##_type gmock_p$j]]]] +$var param_types_and_names = [[$for j, [[p$j##_type p$j]]]] +$var inits = [[$if i==0 [[]] $else [[ : $for j, [[p$j(gmock_p$j)]]]]]] +$var param_field_decls = [[$for j +[[ + + p$j##_type p$j;\ +]]]] +$var param_field_decls2 = [[$for j +[[ + + p$j##_type p$j;\ +]]]] +$var params = [[$for j, [[p$j]]]] +$var param_types = [[$if i==0 [[]] $else [[<$for j, [[p$j##_type]]>]]]] +$var typename_arg_types = [[$for k, [[typename arg$k[[]]_type]]]] +$var arg_types_and_names = [[$for k, [[arg$k[[]]_type arg$k]]]] +$var macro_name = [[$if i==0 [[ACTION]] $elif i==1 [[ACTION_P]] + $else [[ACTION_P$i]]]] + +#define $macro_name(name$for j [[, p$j]])\$template + class $class_name {\ + public:\ + $class_name($ctor_param_list)$inits {}\ + template \ + class gmock_Impl : public ::testing::ActionInterface {\ + public:\ + typedef F function_type;\ + typedef typename ::testing::internal::Function::Result return_type;\ + typedef typename ::testing::internal::Function::ArgumentTuple\ + args_type;\ + [[$if i==1 [[explicit ]]]]gmock_Impl($ctor_param_list)$inits {}\ + virtual return_type Perform(const args_type& args) {\ + return ::testing::internal::ActionHelper::\ + Perform(this, args);\ + }\ + template <$typename_arg_types>\ + return_type gmock_PerformImpl(const args_type& args, [[]] +$arg_types_and_names) const;\$param_field_decls + private:\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template operator ::testing::Action() const {\ + return ::testing::Action(new gmock_Impl($params));\ + }\$param_field_decls2 + private:\ + GTEST_DISALLOW_ASSIGN_($class_name);\ + };\$template + inline $class_name$param_types name($param_types_and_names) {\ + return $class_name$param_types($params);\ + }\$template + template \ + template <$typename_arg_types>\ + typename ::testing::internal::Function::Result\ + $class_name$param_types::gmock_Impl::gmock_PerformImpl(\ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const +]] +$$ } // This meta comment fixes auto-indentation in Emacs. It won't +$$ // show up in the generated code. + + +namespace testing { + +// The ACTION*() macros trigger warning C4100 (unreferenced formal +// parameter) in MSVC with -W4. Unfortunately they cannot be fixed in +// the macro definition, as the warnings are generated when the macro +// is expanded and macro expansion cannot contain #pragma. Therefore +// we suppress them here. +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable:4100) +#endif + +// Various overloads for InvokeArgument(). +// +// The InvokeArgument(a1, a2, ..., a_k) action invokes the N-th +// (0-based) argument, which must be a k-ary callable, of the mock +// function, with arguments a1, a2, ..., a_k. +// +// Notes: +// +// 1. The arguments are passed by value by default. If you need to +// pass an argument by reference, wrap it inside ByRef(). For +// example, +// +// InvokeArgument<1>(5, string("Hello"), ByRef(foo)) +// +// passes 5 and string("Hello") by value, and passes foo by +// reference. +// +// 2. If the callable takes an argument by reference but ByRef() is +// not used, it will receive the reference to a copy of the value, +// instead of the original value. For example, when the 0-th +// argument of the mock function takes a const string&, the action +// +// InvokeArgument<0>(string("Hello")) +// +// makes a copy of the temporary string("Hello") object and passes a +// reference of the copy, instead of the original temporary object, +// to the callable. This makes it easy for a user to define an +// InvokeArgument action from temporary values and have it performed +// later. + +$range i 0..n +$for i [[ +$range j 0..i-1 + +ACTION_TEMPLATE(InvokeArgument, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_$i[[]]_VALUE_PARAMS($for j, [[p$j]])) { + return internal::CallableHelper::Call( + ::std::tr1::get(args)$for j [[, p$j]]); +} + +]] + +// Various overloads for ReturnNew(). +// +// The ReturnNew(a1, a2, ..., a_k) action returns a pointer to a new +// instance of type T, constructed on the heap with constructor arguments +// a1, a2, ..., and a_k. The caller assumes ownership of the returned value. +$range i 0..n +$for i [[ +$range j 0..i-1 +$var ps = [[$for j, [[p$j]]]] + +ACTION_TEMPLATE(ReturnNew, + HAS_1_TEMPLATE_PARAMS(typename, T), + AND_$i[[]]_VALUE_PARAMS($ps)) { + return new T($ps); +} + +]] + +#ifdef _MSC_VER +# pragma warning(pop) +#endif + +} // namespace testing + +#endif // GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_ACTIONS_H_ diff --git a/vendor/gmock-1.7.0/include/gmock/gmock-generated-function-mockers.h b/vendor/gmock-1.7.0/include/gmock/gmock-generated-function-mockers.h new file mode 100644 index 0000000000..577fd9e911 --- /dev/null +++ b/vendor/gmock-1.7.0/include/gmock/gmock-generated-function-mockers.h @@ -0,0 +1,991 @@ +// This file was GENERATED by command: +// pump.py gmock-generated-function-mockers.h.pump +// DO NOT EDIT BY HAND!!! + +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) + +// Google Mock - a framework for writing C++ mock classes. +// +// This file implements function mockers of various arities. + +#ifndef GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_FUNCTION_MOCKERS_H_ +#define GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_FUNCTION_MOCKERS_H_ + +#include "gmock/gmock-spec-builders.h" +#include "gmock/internal/gmock-internal-utils.h" + +namespace testing { +namespace internal { + +template +class FunctionMockerBase; + +// Note: class FunctionMocker really belongs to the ::testing +// namespace. However if we define it in ::testing, MSVC will +// complain when classes in ::testing::internal declare it as a +// friend class template. To workaround this compiler bug, we define +// FunctionMocker in ::testing::internal and import it into ::testing. +template +class FunctionMocker; + +template +class FunctionMocker : public + internal::FunctionMockerBase { + public: + typedef R F(); + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + MockSpec& With() { + return this->current_spec(); + } + + R Invoke() { + // Even though gcc and MSVC don't enforce it, 'this->' is required + // by the C++ standard [14.6.4] here, as the base class type is + // dependent on the template argument (and thus shouldn't be + // looked into when resolving InvokeWith). + return this->InvokeWith(ArgumentTuple()); + } +}; + +template +class FunctionMocker : public + internal::FunctionMockerBase { + public: + typedef R F(A1); + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + MockSpec& With(const Matcher& m1) { + this->current_spec().SetMatchers(::std::tr1::make_tuple(m1)); + return this->current_spec(); + } + + R Invoke(A1 a1) { + // Even though gcc and MSVC don't enforce it, 'this->' is required + // by the C++ standard [14.6.4] here, as the base class type is + // dependent on the template argument (and thus shouldn't be + // looked into when resolving InvokeWith). + return this->InvokeWith(ArgumentTuple(a1)); + } +}; + +template +class FunctionMocker : public + internal::FunctionMockerBase { + public: + typedef R F(A1, A2); + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + MockSpec& With(const Matcher& m1, const Matcher& m2) { + this->current_spec().SetMatchers(::std::tr1::make_tuple(m1, m2)); + return this->current_spec(); + } + + R Invoke(A1 a1, A2 a2) { + // Even though gcc and MSVC don't enforce it, 'this->' is required + // by the C++ standard [14.6.4] here, as the base class type is + // dependent on the template argument (and thus shouldn't be + // looked into when resolving InvokeWith). + return this->InvokeWith(ArgumentTuple(a1, a2)); + } +}; + +template +class FunctionMocker : public + internal::FunctionMockerBase { + public: + typedef R F(A1, A2, A3); + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + MockSpec& With(const Matcher& m1, const Matcher& m2, + const Matcher& m3) { + this->current_spec().SetMatchers(::std::tr1::make_tuple(m1, m2, m3)); + return this->current_spec(); + } + + R Invoke(A1 a1, A2 a2, A3 a3) { + // Even though gcc and MSVC don't enforce it, 'this->' is required + // by the C++ standard [14.6.4] here, as the base class type is + // dependent on the template argument (and thus shouldn't be + // looked into when resolving InvokeWith). + return this->InvokeWith(ArgumentTuple(a1, a2, a3)); + } +}; + +template +class FunctionMocker : public + internal::FunctionMockerBase { + public: + typedef R F(A1, A2, A3, A4); + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + MockSpec& With(const Matcher& m1, const Matcher& m2, + const Matcher& m3, const Matcher& m4) { + this->current_spec().SetMatchers(::std::tr1::make_tuple(m1, m2, m3, m4)); + return this->current_spec(); + } + + R Invoke(A1 a1, A2 a2, A3 a3, A4 a4) { + // Even though gcc and MSVC don't enforce it, 'this->' is required + // by the C++ standard [14.6.4] here, as the base class type is + // dependent on the template argument (and thus shouldn't be + // looked into when resolving InvokeWith). + return this->InvokeWith(ArgumentTuple(a1, a2, a3, a4)); + } +}; + +template +class FunctionMocker : public + internal::FunctionMockerBase { + public: + typedef R F(A1, A2, A3, A4, A5); + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + MockSpec& With(const Matcher& m1, const Matcher& m2, + const Matcher& m3, const Matcher& m4, const Matcher& m5) { + this->current_spec().SetMatchers(::std::tr1::make_tuple(m1, m2, m3, m4, + m5)); + return this->current_spec(); + } + + R Invoke(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) { + // Even though gcc and MSVC don't enforce it, 'this->' is required + // by the C++ standard [14.6.4] here, as the base class type is + // dependent on the template argument (and thus shouldn't be + // looked into when resolving InvokeWith). + return this->InvokeWith(ArgumentTuple(a1, a2, a3, a4, a5)); + } +}; + +template +class FunctionMocker : public + internal::FunctionMockerBase { + public: + typedef R F(A1, A2, A3, A4, A5, A6); + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + MockSpec& With(const Matcher& m1, const Matcher& m2, + const Matcher& m3, const Matcher& m4, const Matcher& m5, + const Matcher& m6) { + this->current_spec().SetMatchers(::std::tr1::make_tuple(m1, m2, m3, m4, m5, + m6)); + return this->current_spec(); + } + + R Invoke(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6) { + // Even though gcc and MSVC don't enforce it, 'this->' is required + // by the C++ standard [14.6.4] here, as the base class type is + // dependent on the template argument (and thus shouldn't be + // looked into when resolving InvokeWith). + return this->InvokeWith(ArgumentTuple(a1, a2, a3, a4, a5, a6)); + } +}; + +template +class FunctionMocker : public + internal::FunctionMockerBase { + public: + typedef R F(A1, A2, A3, A4, A5, A6, A7); + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + MockSpec& With(const Matcher& m1, const Matcher& m2, + const Matcher& m3, const Matcher& m4, const Matcher& m5, + const Matcher& m6, const Matcher& m7) { + this->current_spec().SetMatchers(::std::tr1::make_tuple(m1, m2, m3, m4, m5, + m6, m7)); + return this->current_spec(); + } + + R Invoke(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7) { + // Even though gcc and MSVC don't enforce it, 'this->' is required + // by the C++ standard [14.6.4] here, as the base class type is + // dependent on the template argument (and thus shouldn't be + // looked into when resolving InvokeWith). + return this->InvokeWith(ArgumentTuple(a1, a2, a3, a4, a5, a6, a7)); + } +}; + +template +class FunctionMocker : public + internal::FunctionMockerBase { + public: + typedef R F(A1, A2, A3, A4, A5, A6, A7, A8); + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + MockSpec& With(const Matcher& m1, const Matcher& m2, + const Matcher& m3, const Matcher& m4, const Matcher& m5, + const Matcher& m6, const Matcher& m7, const Matcher& m8) { + this->current_spec().SetMatchers(::std::tr1::make_tuple(m1, m2, m3, m4, m5, + m6, m7, m8)); + return this->current_spec(); + } + + R Invoke(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8) { + // Even though gcc and MSVC don't enforce it, 'this->' is required + // by the C++ standard [14.6.4] here, as the base class type is + // dependent on the template argument (and thus shouldn't be + // looked into when resolving InvokeWith). + return this->InvokeWith(ArgumentTuple(a1, a2, a3, a4, a5, a6, a7, a8)); + } +}; + +template +class FunctionMocker : public + internal::FunctionMockerBase { + public: + typedef R F(A1, A2, A3, A4, A5, A6, A7, A8, A9); + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + MockSpec& With(const Matcher& m1, const Matcher& m2, + const Matcher& m3, const Matcher& m4, const Matcher& m5, + const Matcher& m6, const Matcher& m7, const Matcher& m8, + const Matcher& m9) { + this->current_spec().SetMatchers(::std::tr1::make_tuple(m1, m2, m3, m4, m5, + m6, m7, m8, m9)); + return this->current_spec(); + } + + R Invoke(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9) { + // Even though gcc and MSVC don't enforce it, 'this->' is required + // by the C++ standard [14.6.4] here, as the base class type is + // dependent on the template argument (and thus shouldn't be + // looked into when resolving InvokeWith). + return this->InvokeWith(ArgumentTuple(a1, a2, a3, a4, a5, a6, a7, a8, a9)); + } +}; + +template +class FunctionMocker : public + internal::FunctionMockerBase { + public: + typedef R F(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10); + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + MockSpec& With(const Matcher& m1, const Matcher& m2, + const Matcher& m3, const Matcher& m4, const Matcher& m5, + const Matcher& m6, const Matcher& m7, const Matcher& m8, + const Matcher& m9, const Matcher& m10) { + this->current_spec().SetMatchers(::std::tr1::make_tuple(m1, m2, m3, m4, m5, + m6, m7, m8, m9, m10)); + return this->current_spec(); + } + + R Invoke(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, + A10 a10) { + // Even though gcc and MSVC don't enforce it, 'this->' is required + // by the C++ standard [14.6.4] here, as the base class type is + // dependent on the template argument (and thus shouldn't be + // looked into when resolving InvokeWith). + return this->InvokeWith(ArgumentTuple(a1, a2, a3, a4, a5, a6, a7, a8, a9, + a10)); + } +}; + +} // namespace internal + +// The style guide prohibits "using" statements in a namespace scope +// inside a header file. However, the FunctionMocker class template +// is meant to be defined in the ::testing namespace. The following +// line is just a trick for working around a bug in MSVC 8.0, which +// cannot handle it if we define FunctionMocker in ::testing. +using internal::FunctionMocker; + +// GMOCK_RESULT_(tn, F) expands to the result type of function type F. +// We define this as a variadic macro in case F contains unprotected +// commas (the same reason that we use variadic macros in other places +// in this file). +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_RESULT_(tn, ...) \ + tn ::testing::internal::Function<__VA_ARGS__>::Result + +// The type of argument N of the given function type. +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_ARG_(tn, N, ...) \ + tn ::testing::internal::Function<__VA_ARGS__>::Argument##N + +// The matcher type for argument N of the given function type. +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_MATCHER_(tn, N, ...) \ + const ::testing::Matcher& + +// The variable for mocking the given method. +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_MOCKER_(arity, constness, Method) \ + GTEST_CONCAT_TOKEN_(gmock##constness##arity##_##Method##_, __LINE__) + +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_METHOD0_(tn, constness, ct, Method, ...) \ + GMOCK_RESULT_(tn, __VA_ARGS__) ct Method( \ + ) constness { \ + GTEST_COMPILE_ASSERT_((::std::tr1::tuple_size< \ + tn ::testing::internal::Function<__VA_ARGS__>::ArgumentTuple>::value \ + == 0), \ + this_method_does_not_take_0_arguments); \ + GMOCK_MOCKER_(0, constness, Method).SetOwnerAndName(this, #Method); \ + return GMOCK_MOCKER_(0, constness, Method).Invoke(); \ + } \ + ::testing::MockSpec<__VA_ARGS__>& \ + gmock_##Method() constness { \ + GMOCK_MOCKER_(0, constness, Method).RegisterOwner(this); \ + return GMOCK_MOCKER_(0, constness, Method).With(); \ + } \ + mutable ::testing::FunctionMocker<__VA_ARGS__> GMOCK_MOCKER_(0, constness, \ + Method) + +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_METHOD1_(tn, constness, ct, Method, ...) \ + GMOCK_RESULT_(tn, __VA_ARGS__) ct Method( \ + GMOCK_ARG_(tn, 1, __VA_ARGS__) gmock_a1) constness { \ + GTEST_COMPILE_ASSERT_((::std::tr1::tuple_size< \ + tn ::testing::internal::Function<__VA_ARGS__>::ArgumentTuple>::value \ + == 1), \ + this_method_does_not_take_1_argument); \ + GMOCK_MOCKER_(1, constness, Method).SetOwnerAndName(this, #Method); \ + return GMOCK_MOCKER_(1, constness, Method).Invoke(gmock_a1); \ + } \ + ::testing::MockSpec<__VA_ARGS__>& \ + gmock_##Method(GMOCK_MATCHER_(tn, 1, __VA_ARGS__) gmock_a1) constness { \ + GMOCK_MOCKER_(1, constness, Method).RegisterOwner(this); \ + return GMOCK_MOCKER_(1, constness, Method).With(gmock_a1); \ + } \ + mutable ::testing::FunctionMocker<__VA_ARGS__> GMOCK_MOCKER_(1, constness, \ + Method) + +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_METHOD2_(tn, constness, ct, Method, ...) \ + GMOCK_RESULT_(tn, __VA_ARGS__) ct Method( \ + GMOCK_ARG_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_ARG_(tn, 2, __VA_ARGS__) gmock_a2) constness { \ + GTEST_COMPILE_ASSERT_((::std::tr1::tuple_size< \ + tn ::testing::internal::Function<__VA_ARGS__>::ArgumentTuple>::value \ + == 2), \ + this_method_does_not_take_2_arguments); \ + GMOCK_MOCKER_(2, constness, Method).SetOwnerAndName(this, #Method); \ + return GMOCK_MOCKER_(2, constness, Method).Invoke(gmock_a1, gmock_a2); \ + } \ + ::testing::MockSpec<__VA_ARGS__>& \ + gmock_##Method(GMOCK_MATCHER_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_MATCHER_(tn, 2, __VA_ARGS__) gmock_a2) constness { \ + GMOCK_MOCKER_(2, constness, Method).RegisterOwner(this); \ + return GMOCK_MOCKER_(2, constness, Method).With(gmock_a1, gmock_a2); \ + } \ + mutable ::testing::FunctionMocker<__VA_ARGS__> GMOCK_MOCKER_(2, constness, \ + Method) + +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_METHOD3_(tn, constness, ct, Method, ...) \ + GMOCK_RESULT_(tn, __VA_ARGS__) ct Method( \ + GMOCK_ARG_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_ARG_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_ARG_(tn, 3, __VA_ARGS__) gmock_a3) constness { \ + GTEST_COMPILE_ASSERT_((::std::tr1::tuple_size< \ + tn ::testing::internal::Function<__VA_ARGS__>::ArgumentTuple>::value \ + == 3), \ + this_method_does_not_take_3_arguments); \ + GMOCK_MOCKER_(3, constness, Method).SetOwnerAndName(this, #Method); \ + return GMOCK_MOCKER_(3, constness, Method).Invoke(gmock_a1, gmock_a2, \ + gmock_a3); \ + } \ + ::testing::MockSpec<__VA_ARGS__>& \ + gmock_##Method(GMOCK_MATCHER_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_MATCHER_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_MATCHER_(tn, 3, __VA_ARGS__) gmock_a3) constness { \ + GMOCK_MOCKER_(3, constness, Method).RegisterOwner(this); \ + return GMOCK_MOCKER_(3, constness, Method).With(gmock_a1, gmock_a2, \ + gmock_a3); \ + } \ + mutable ::testing::FunctionMocker<__VA_ARGS__> GMOCK_MOCKER_(3, constness, \ + Method) + +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_METHOD4_(tn, constness, ct, Method, ...) \ + GMOCK_RESULT_(tn, __VA_ARGS__) ct Method( \ + GMOCK_ARG_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_ARG_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_ARG_(tn, 3, __VA_ARGS__) gmock_a3, \ + GMOCK_ARG_(tn, 4, __VA_ARGS__) gmock_a4) constness { \ + GTEST_COMPILE_ASSERT_((::std::tr1::tuple_size< \ + tn ::testing::internal::Function<__VA_ARGS__>::ArgumentTuple>::value \ + == 4), \ + this_method_does_not_take_4_arguments); \ + GMOCK_MOCKER_(4, constness, Method).SetOwnerAndName(this, #Method); \ + return GMOCK_MOCKER_(4, constness, Method).Invoke(gmock_a1, gmock_a2, \ + gmock_a3, gmock_a4); \ + } \ + ::testing::MockSpec<__VA_ARGS__>& \ + gmock_##Method(GMOCK_MATCHER_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_MATCHER_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_MATCHER_(tn, 3, __VA_ARGS__) gmock_a3, \ + GMOCK_MATCHER_(tn, 4, __VA_ARGS__) gmock_a4) constness { \ + GMOCK_MOCKER_(4, constness, Method).RegisterOwner(this); \ + return GMOCK_MOCKER_(4, constness, Method).With(gmock_a1, gmock_a2, \ + gmock_a3, gmock_a4); \ + } \ + mutable ::testing::FunctionMocker<__VA_ARGS__> GMOCK_MOCKER_(4, constness, \ + Method) + +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_METHOD5_(tn, constness, ct, Method, ...) \ + GMOCK_RESULT_(tn, __VA_ARGS__) ct Method( \ + GMOCK_ARG_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_ARG_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_ARG_(tn, 3, __VA_ARGS__) gmock_a3, \ + GMOCK_ARG_(tn, 4, __VA_ARGS__) gmock_a4, \ + GMOCK_ARG_(tn, 5, __VA_ARGS__) gmock_a5) constness { \ + GTEST_COMPILE_ASSERT_((::std::tr1::tuple_size< \ + tn ::testing::internal::Function<__VA_ARGS__>::ArgumentTuple>::value \ + == 5), \ + this_method_does_not_take_5_arguments); \ + GMOCK_MOCKER_(5, constness, Method).SetOwnerAndName(this, #Method); \ + return GMOCK_MOCKER_(5, constness, Method).Invoke(gmock_a1, gmock_a2, \ + gmock_a3, gmock_a4, gmock_a5); \ + } \ + ::testing::MockSpec<__VA_ARGS__>& \ + gmock_##Method(GMOCK_MATCHER_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_MATCHER_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_MATCHER_(tn, 3, __VA_ARGS__) gmock_a3, \ + GMOCK_MATCHER_(tn, 4, __VA_ARGS__) gmock_a4, \ + GMOCK_MATCHER_(tn, 5, __VA_ARGS__) gmock_a5) constness { \ + GMOCK_MOCKER_(5, constness, Method).RegisterOwner(this); \ + return GMOCK_MOCKER_(5, constness, Method).With(gmock_a1, gmock_a2, \ + gmock_a3, gmock_a4, gmock_a5); \ + } \ + mutable ::testing::FunctionMocker<__VA_ARGS__> GMOCK_MOCKER_(5, constness, \ + Method) + +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_METHOD6_(tn, constness, ct, Method, ...) \ + GMOCK_RESULT_(tn, __VA_ARGS__) ct Method( \ + GMOCK_ARG_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_ARG_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_ARG_(tn, 3, __VA_ARGS__) gmock_a3, \ + GMOCK_ARG_(tn, 4, __VA_ARGS__) gmock_a4, \ + GMOCK_ARG_(tn, 5, __VA_ARGS__) gmock_a5, \ + GMOCK_ARG_(tn, 6, __VA_ARGS__) gmock_a6) constness { \ + GTEST_COMPILE_ASSERT_((::std::tr1::tuple_size< \ + tn ::testing::internal::Function<__VA_ARGS__>::ArgumentTuple>::value \ + == 6), \ + this_method_does_not_take_6_arguments); \ + GMOCK_MOCKER_(6, constness, Method).SetOwnerAndName(this, #Method); \ + return GMOCK_MOCKER_(6, constness, Method).Invoke(gmock_a1, gmock_a2, \ + gmock_a3, gmock_a4, gmock_a5, gmock_a6); \ + } \ + ::testing::MockSpec<__VA_ARGS__>& \ + gmock_##Method(GMOCK_MATCHER_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_MATCHER_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_MATCHER_(tn, 3, __VA_ARGS__) gmock_a3, \ + GMOCK_MATCHER_(tn, 4, __VA_ARGS__) gmock_a4, \ + GMOCK_MATCHER_(tn, 5, __VA_ARGS__) gmock_a5, \ + GMOCK_MATCHER_(tn, 6, __VA_ARGS__) gmock_a6) constness { \ + GMOCK_MOCKER_(6, constness, Method).RegisterOwner(this); \ + return GMOCK_MOCKER_(6, constness, Method).With(gmock_a1, gmock_a2, \ + gmock_a3, gmock_a4, gmock_a5, gmock_a6); \ + } \ + mutable ::testing::FunctionMocker<__VA_ARGS__> GMOCK_MOCKER_(6, constness, \ + Method) + +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_METHOD7_(tn, constness, ct, Method, ...) \ + GMOCK_RESULT_(tn, __VA_ARGS__) ct Method( \ + GMOCK_ARG_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_ARG_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_ARG_(tn, 3, __VA_ARGS__) gmock_a3, \ + GMOCK_ARG_(tn, 4, __VA_ARGS__) gmock_a4, \ + GMOCK_ARG_(tn, 5, __VA_ARGS__) gmock_a5, \ + GMOCK_ARG_(tn, 6, __VA_ARGS__) gmock_a6, \ + GMOCK_ARG_(tn, 7, __VA_ARGS__) gmock_a7) constness { \ + GTEST_COMPILE_ASSERT_((::std::tr1::tuple_size< \ + tn ::testing::internal::Function<__VA_ARGS__>::ArgumentTuple>::value \ + == 7), \ + this_method_does_not_take_7_arguments); \ + GMOCK_MOCKER_(7, constness, Method).SetOwnerAndName(this, #Method); \ + return GMOCK_MOCKER_(7, constness, Method).Invoke(gmock_a1, gmock_a2, \ + gmock_a3, gmock_a4, gmock_a5, gmock_a6, gmock_a7); \ + } \ + ::testing::MockSpec<__VA_ARGS__>& \ + gmock_##Method(GMOCK_MATCHER_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_MATCHER_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_MATCHER_(tn, 3, __VA_ARGS__) gmock_a3, \ + GMOCK_MATCHER_(tn, 4, __VA_ARGS__) gmock_a4, \ + GMOCK_MATCHER_(tn, 5, __VA_ARGS__) gmock_a5, \ + GMOCK_MATCHER_(tn, 6, __VA_ARGS__) gmock_a6, \ + GMOCK_MATCHER_(tn, 7, __VA_ARGS__) gmock_a7) constness { \ + GMOCK_MOCKER_(7, constness, Method).RegisterOwner(this); \ + return GMOCK_MOCKER_(7, constness, Method).With(gmock_a1, gmock_a2, \ + gmock_a3, gmock_a4, gmock_a5, gmock_a6, gmock_a7); \ + } \ + mutable ::testing::FunctionMocker<__VA_ARGS__> GMOCK_MOCKER_(7, constness, \ + Method) + +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_METHOD8_(tn, constness, ct, Method, ...) \ + GMOCK_RESULT_(tn, __VA_ARGS__) ct Method( \ + GMOCK_ARG_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_ARG_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_ARG_(tn, 3, __VA_ARGS__) gmock_a3, \ + GMOCK_ARG_(tn, 4, __VA_ARGS__) gmock_a4, \ + GMOCK_ARG_(tn, 5, __VA_ARGS__) gmock_a5, \ + GMOCK_ARG_(tn, 6, __VA_ARGS__) gmock_a6, \ + GMOCK_ARG_(tn, 7, __VA_ARGS__) gmock_a7, \ + GMOCK_ARG_(tn, 8, __VA_ARGS__) gmock_a8) constness { \ + GTEST_COMPILE_ASSERT_((::std::tr1::tuple_size< \ + tn ::testing::internal::Function<__VA_ARGS__>::ArgumentTuple>::value \ + == 8), \ + this_method_does_not_take_8_arguments); \ + GMOCK_MOCKER_(8, constness, Method).SetOwnerAndName(this, #Method); \ + return GMOCK_MOCKER_(8, constness, Method).Invoke(gmock_a1, gmock_a2, \ + gmock_a3, gmock_a4, gmock_a5, gmock_a6, gmock_a7, gmock_a8); \ + } \ + ::testing::MockSpec<__VA_ARGS__>& \ + gmock_##Method(GMOCK_MATCHER_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_MATCHER_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_MATCHER_(tn, 3, __VA_ARGS__) gmock_a3, \ + GMOCK_MATCHER_(tn, 4, __VA_ARGS__) gmock_a4, \ + GMOCK_MATCHER_(tn, 5, __VA_ARGS__) gmock_a5, \ + GMOCK_MATCHER_(tn, 6, __VA_ARGS__) gmock_a6, \ + GMOCK_MATCHER_(tn, 7, __VA_ARGS__) gmock_a7, \ + GMOCK_MATCHER_(tn, 8, __VA_ARGS__) gmock_a8) constness { \ + GMOCK_MOCKER_(8, constness, Method).RegisterOwner(this); \ + return GMOCK_MOCKER_(8, constness, Method).With(gmock_a1, gmock_a2, \ + gmock_a3, gmock_a4, gmock_a5, gmock_a6, gmock_a7, gmock_a8); \ + } \ + mutable ::testing::FunctionMocker<__VA_ARGS__> GMOCK_MOCKER_(8, constness, \ + Method) + +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_METHOD9_(tn, constness, ct, Method, ...) \ + GMOCK_RESULT_(tn, __VA_ARGS__) ct Method( \ + GMOCK_ARG_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_ARG_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_ARG_(tn, 3, __VA_ARGS__) gmock_a3, \ + GMOCK_ARG_(tn, 4, __VA_ARGS__) gmock_a4, \ + GMOCK_ARG_(tn, 5, __VA_ARGS__) gmock_a5, \ + GMOCK_ARG_(tn, 6, __VA_ARGS__) gmock_a6, \ + GMOCK_ARG_(tn, 7, __VA_ARGS__) gmock_a7, \ + GMOCK_ARG_(tn, 8, __VA_ARGS__) gmock_a8, \ + GMOCK_ARG_(tn, 9, __VA_ARGS__) gmock_a9) constness { \ + GTEST_COMPILE_ASSERT_((::std::tr1::tuple_size< \ + tn ::testing::internal::Function<__VA_ARGS__>::ArgumentTuple>::value \ + == 9), \ + this_method_does_not_take_9_arguments); \ + GMOCK_MOCKER_(9, constness, Method).SetOwnerAndName(this, #Method); \ + return GMOCK_MOCKER_(9, constness, Method).Invoke(gmock_a1, gmock_a2, \ + gmock_a3, gmock_a4, gmock_a5, gmock_a6, gmock_a7, gmock_a8, \ + gmock_a9); \ + } \ + ::testing::MockSpec<__VA_ARGS__>& \ + gmock_##Method(GMOCK_MATCHER_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_MATCHER_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_MATCHER_(tn, 3, __VA_ARGS__) gmock_a3, \ + GMOCK_MATCHER_(tn, 4, __VA_ARGS__) gmock_a4, \ + GMOCK_MATCHER_(tn, 5, __VA_ARGS__) gmock_a5, \ + GMOCK_MATCHER_(tn, 6, __VA_ARGS__) gmock_a6, \ + GMOCK_MATCHER_(tn, 7, __VA_ARGS__) gmock_a7, \ + GMOCK_MATCHER_(tn, 8, __VA_ARGS__) gmock_a8, \ + GMOCK_MATCHER_(tn, 9, __VA_ARGS__) gmock_a9) constness { \ + GMOCK_MOCKER_(9, constness, Method).RegisterOwner(this); \ + return GMOCK_MOCKER_(9, constness, Method).With(gmock_a1, gmock_a2, \ + gmock_a3, gmock_a4, gmock_a5, gmock_a6, gmock_a7, gmock_a8, \ + gmock_a9); \ + } \ + mutable ::testing::FunctionMocker<__VA_ARGS__> GMOCK_MOCKER_(9, constness, \ + Method) + +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_METHOD10_(tn, constness, ct, Method, ...) \ + GMOCK_RESULT_(tn, __VA_ARGS__) ct Method( \ + GMOCK_ARG_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_ARG_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_ARG_(tn, 3, __VA_ARGS__) gmock_a3, \ + GMOCK_ARG_(tn, 4, __VA_ARGS__) gmock_a4, \ + GMOCK_ARG_(tn, 5, __VA_ARGS__) gmock_a5, \ + GMOCK_ARG_(tn, 6, __VA_ARGS__) gmock_a6, \ + GMOCK_ARG_(tn, 7, __VA_ARGS__) gmock_a7, \ + GMOCK_ARG_(tn, 8, __VA_ARGS__) gmock_a8, \ + GMOCK_ARG_(tn, 9, __VA_ARGS__) gmock_a9, \ + GMOCK_ARG_(tn, 10, __VA_ARGS__) gmock_a10) constness { \ + GTEST_COMPILE_ASSERT_((::std::tr1::tuple_size< \ + tn ::testing::internal::Function<__VA_ARGS__>::ArgumentTuple>::value \ + == 10), \ + this_method_does_not_take_10_arguments); \ + GMOCK_MOCKER_(10, constness, Method).SetOwnerAndName(this, #Method); \ + return GMOCK_MOCKER_(10, constness, Method).Invoke(gmock_a1, gmock_a2, \ + gmock_a3, gmock_a4, gmock_a5, gmock_a6, gmock_a7, gmock_a8, gmock_a9, \ + gmock_a10); \ + } \ + ::testing::MockSpec<__VA_ARGS__>& \ + gmock_##Method(GMOCK_MATCHER_(tn, 1, __VA_ARGS__) gmock_a1, \ + GMOCK_MATCHER_(tn, 2, __VA_ARGS__) gmock_a2, \ + GMOCK_MATCHER_(tn, 3, __VA_ARGS__) gmock_a3, \ + GMOCK_MATCHER_(tn, 4, __VA_ARGS__) gmock_a4, \ + GMOCK_MATCHER_(tn, 5, __VA_ARGS__) gmock_a5, \ + GMOCK_MATCHER_(tn, 6, __VA_ARGS__) gmock_a6, \ + GMOCK_MATCHER_(tn, 7, __VA_ARGS__) gmock_a7, \ + GMOCK_MATCHER_(tn, 8, __VA_ARGS__) gmock_a8, \ + GMOCK_MATCHER_(tn, 9, __VA_ARGS__) gmock_a9, \ + GMOCK_MATCHER_(tn, 10, \ + __VA_ARGS__) gmock_a10) constness { \ + GMOCK_MOCKER_(10, constness, Method).RegisterOwner(this); \ + return GMOCK_MOCKER_(10, constness, Method).With(gmock_a1, gmock_a2, \ + gmock_a3, gmock_a4, gmock_a5, gmock_a6, gmock_a7, gmock_a8, gmock_a9, \ + gmock_a10); \ + } \ + mutable ::testing::FunctionMocker<__VA_ARGS__> GMOCK_MOCKER_(10, constness, \ + Method) + +#define MOCK_METHOD0(m, ...) GMOCK_METHOD0_(, , , m, __VA_ARGS__) +#define MOCK_METHOD1(m, ...) GMOCK_METHOD1_(, , , m, __VA_ARGS__) +#define MOCK_METHOD2(m, ...) GMOCK_METHOD2_(, , , m, __VA_ARGS__) +#define MOCK_METHOD3(m, ...) GMOCK_METHOD3_(, , , m, __VA_ARGS__) +#define MOCK_METHOD4(m, ...) GMOCK_METHOD4_(, , , m, __VA_ARGS__) +#define MOCK_METHOD5(m, ...) GMOCK_METHOD5_(, , , m, __VA_ARGS__) +#define MOCK_METHOD6(m, ...) GMOCK_METHOD6_(, , , m, __VA_ARGS__) +#define MOCK_METHOD7(m, ...) GMOCK_METHOD7_(, , , m, __VA_ARGS__) +#define MOCK_METHOD8(m, ...) GMOCK_METHOD8_(, , , m, __VA_ARGS__) +#define MOCK_METHOD9(m, ...) GMOCK_METHOD9_(, , , m, __VA_ARGS__) +#define MOCK_METHOD10(m, ...) GMOCK_METHOD10_(, , , m, __VA_ARGS__) + +#define MOCK_CONST_METHOD0(m, ...) GMOCK_METHOD0_(, const, , m, __VA_ARGS__) +#define MOCK_CONST_METHOD1(m, ...) GMOCK_METHOD1_(, const, , m, __VA_ARGS__) +#define MOCK_CONST_METHOD2(m, ...) GMOCK_METHOD2_(, const, , m, __VA_ARGS__) +#define MOCK_CONST_METHOD3(m, ...) GMOCK_METHOD3_(, const, , m, __VA_ARGS__) +#define MOCK_CONST_METHOD4(m, ...) GMOCK_METHOD4_(, const, , m, __VA_ARGS__) +#define MOCK_CONST_METHOD5(m, ...) GMOCK_METHOD5_(, const, , m, __VA_ARGS__) +#define MOCK_CONST_METHOD6(m, ...) GMOCK_METHOD6_(, const, , m, __VA_ARGS__) +#define MOCK_CONST_METHOD7(m, ...) GMOCK_METHOD7_(, const, , m, __VA_ARGS__) +#define MOCK_CONST_METHOD8(m, ...) GMOCK_METHOD8_(, const, , m, __VA_ARGS__) +#define MOCK_CONST_METHOD9(m, ...) GMOCK_METHOD9_(, const, , m, __VA_ARGS__) +#define MOCK_CONST_METHOD10(m, ...) GMOCK_METHOD10_(, const, , m, __VA_ARGS__) + +#define MOCK_METHOD0_T(m, ...) GMOCK_METHOD0_(typename, , , m, __VA_ARGS__) +#define MOCK_METHOD1_T(m, ...) GMOCK_METHOD1_(typename, , , m, __VA_ARGS__) +#define MOCK_METHOD2_T(m, ...) GMOCK_METHOD2_(typename, , , m, __VA_ARGS__) +#define MOCK_METHOD3_T(m, ...) GMOCK_METHOD3_(typename, , , m, __VA_ARGS__) +#define MOCK_METHOD4_T(m, ...) GMOCK_METHOD4_(typename, , , m, __VA_ARGS__) +#define MOCK_METHOD5_T(m, ...) GMOCK_METHOD5_(typename, , , m, __VA_ARGS__) +#define MOCK_METHOD6_T(m, ...) GMOCK_METHOD6_(typename, , , m, __VA_ARGS__) +#define MOCK_METHOD7_T(m, ...) GMOCK_METHOD7_(typename, , , m, __VA_ARGS__) +#define MOCK_METHOD8_T(m, ...) GMOCK_METHOD8_(typename, , , m, __VA_ARGS__) +#define MOCK_METHOD9_T(m, ...) GMOCK_METHOD9_(typename, , , m, __VA_ARGS__) +#define MOCK_METHOD10_T(m, ...) GMOCK_METHOD10_(typename, , , m, __VA_ARGS__) + +#define MOCK_CONST_METHOD0_T(m, ...) \ + GMOCK_METHOD0_(typename, const, , m, __VA_ARGS__) +#define MOCK_CONST_METHOD1_T(m, ...) \ + GMOCK_METHOD1_(typename, const, , m, __VA_ARGS__) +#define MOCK_CONST_METHOD2_T(m, ...) \ + GMOCK_METHOD2_(typename, const, , m, __VA_ARGS__) +#define MOCK_CONST_METHOD3_T(m, ...) \ + GMOCK_METHOD3_(typename, const, , m, __VA_ARGS__) +#define MOCK_CONST_METHOD4_T(m, ...) \ + GMOCK_METHOD4_(typename, const, , m, __VA_ARGS__) +#define MOCK_CONST_METHOD5_T(m, ...) \ + GMOCK_METHOD5_(typename, const, , m, __VA_ARGS__) +#define MOCK_CONST_METHOD6_T(m, ...) \ + GMOCK_METHOD6_(typename, const, , m, __VA_ARGS__) +#define MOCK_CONST_METHOD7_T(m, ...) \ + GMOCK_METHOD7_(typename, const, , m, __VA_ARGS__) +#define MOCK_CONST_METHOD8_T(m, ...) \ + GMOCK_METHOD8_(typename, const, , m, __VA_ARGS__) +#define MOCK_CONST_METHOD9_T(m, ...) \ + GMOCK_METHOD9_(typename, const, , m, __VA_ARGS__) +#define MOCK_CONST_METHOD10_T(m, ...) \ + GMOCK_METHOD10_(typename, const, , m, __VA_ARGS__) + +#define MOCK_METHOD0_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD0_(, , ct, m, __VA_ARGS__) +#define MOCK_METHOD1_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD1_(, , ct, m, __VA_ARGS__) +#define MOCK_METHOD2_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD2_(, , ct, m, __VA_ARGS__) +#define MOCK_METHOD3_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD3_(, , ct, m, __VA_ARGS__) +#define MOCK_METHOD4_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD4_(, , ct, m, __VA_ARGS__) +#define MOCK_METHOD5_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD5_(, , ct, m, __VA_ARGS__) +#define MOCK_METHOD6_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD6_(, , ct, m, __VA_ARGS__) +#define MOCK_METHOD7_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD7_(, , ct, m, __VA_ARGS__) +#define MOCK_METHOD8_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD8_(, , ct, m, __VA_ARGS__) +#define MOCK_METHOD9_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD9_(, , ct, m, __VA_ARGS__) +#define MOCK_METHOD10_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD10_(, , ct, m, __VA_ARGS__) + +#define MOCK_CONST_METHOD0_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD0_(, const, ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD1_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD1_(, const, ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD2_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD2_(, const, ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD3_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD3_(, const, ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD4_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD4_(, const, ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD5_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD5_(, const, ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD6_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD6_(, const, ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD7_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD7_(, const, ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD8_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD8_(, const, ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD9_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD9_(, const, ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD10_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD10_(, const, ct, m, __VA_ARGS__) + +#define MOCK_METHOD0_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD0_(typename, , ct, m, __VA_ARGS__) +#define MOCK_METHOD1_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD1_(typename, , ct, m, __VA_ARGS__) +#define MOCK_METHOD2_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD2_(typename, , ct, m, __VA_ARGS__) +#define MOCK_METHOD3_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD3_(typename, , ct, m, __VA_ARGS__) +#define MOCK_METHOD4_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD4_(typename, , ct, m, __VA_ARGS__) +#define MOCK_METHOD5_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD5_(typename, , ct, m, __VA_ARGS__) +#define MOCK_METHOD6_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD6_(typename, , ct, m, __VA_ARGS__) +#define MOCK_METHOD7_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD7_(typename, , ct, m, __VA_ARGS__) +#define MOCK_METHOD8_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD8_(typename, , ct, m, __VA_ARGS__) +#define MOCK_METHOD9_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD9_(typename, , ct, m, __VA_ARGS__) +#define MOCK_METHOD10_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD10_(typename, , ct, m, __VA_ARGS__) + +#define MOCK_CONST_METHOD0_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD0_(typename, const, ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD1_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD1_(typename, const, ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD2_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD2_(typename, const, ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD3_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD3_(typename, const, ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD4_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD4_(typename, const, ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD5_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD5_(typename, const, ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD6_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD6_(typename, const, ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD7_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD7_(typename, const, ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD8_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD8_(typename, const, ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD9_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD9_(typename, const, ct, m, __VA_ARGS__) +#define MOCK_CONST_METHOD10_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD10_(typename, const, ct, m, __VA_ARGS__) + +// A MockFunction class has one mock method whose type is F. It is +// useful when you just want your test code to emit some messages and +// have Google Mock verify the right messages are sent (and perhaps at +// the right times). For example, if you are exercising code: +// +// Foo(1); +// Foo(2); +// Foo(3); +// +// and want to verify that Foo(1) and Foo(3) both invoke +// mock.Bar("a"), but Foo(2) doesn't invoke anything, you can write: +// +// TEST(FooTest, InvokesBarCorrectly) { +// MyMock mock; +// MockFunction check; +// { +// InSequence s; +// +// EXPECT_CALL(mock, Bar("a")); +// EXPECT_CALL(check, Call("1")); +// EXPECT_CALL(check, Call("2")); +// EXPECT_CALL(mock, Bar("a")); +// } +// Foo(1); +// check.Call("1"); +// Foo(2); +// check.Call("2"); +// Foo(3); +// } +// +// The expectation spec says that the first Bar("a") must happen +// before check point "1", the second Bar("a") must happen after check +// point "2", and nothing should happen between the two check +// points. The explicit check points make it easy to tell which +// Bar("a") is called by which call to Foo(). +template +class MockFunction; + +template +class MockFunction { + public: + MockFunction() {} + + MOCK_METHOD0_T(Call, R()); + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(MockFunction); +}; + +template +class MockFunction { + public: + MockFunction() {} + + MOCK_METHOD1_T(Call, R(A0)); + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(MockFunction); +}; + +template +class MockFunction { + public: + MockFunction() {} + + MOCK_METHOD2_T(Call, R(A0, A1)); + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(MockFunction); +}; + +template +class MockFunction { + public: + MockFunction() {} + + MOCK_METHOD3_T(Call, R(A0, A1, A2)); + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(MockFunction); +}; + +template +class MockFunction { + public: + MockFunction() {} + + MOCK_METHOD4_T(Call, R(A0, A1, A2, A3)); + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(MockFunction); +}; + +template +class MockFunction { + public: + MockFunction() {} + + MOCK_METHOD5_T(Call, R(A0, A1, A2, A3, A4)); + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(MockFunction); +}; + +template +class MockFunction { + public: + MockFunction() {} + + MOCK_METHOD6_T(Call, R(A0, A1, A2, A3, A4, A5)); + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(MockFunction); +}; + +template +class MockFunction { + public: + MockFunction() {} + + MOCK_METHOD7_T(Call, R(A0, A1, A2, A3, A4, A5, A6)); + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(MockFunction); +}; + +template +class MockFunction { + public: + MockFunction() {} + + MOCK_METHOD8_T(Call, R(A0, A1, A2, A3, A4, A5, A6, A7)); + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(MockFunction); +}; + +template +class MockFunction { + public: + MockFunction() {} + + MOCK_METHOD9_T(Call, R(A0, A1, A2, A3, A4, A5, A6, A7, A8)); + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(MockFunction); +}; + +template +class MockFunction { + public: + MockFunction() {} + + MOCK_METHOD10_T(Call, R(A0, A1, A2, A3, A4, A5, A6, A7, A8, A9)); + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(MockFunction); +}; + +} // namespace testing + +#endif // GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_FUNCTION_MOCKERS_H_ diff --git a/vendor/gmock-1.7.0/include/gmock/gmock-generated-function-mockers.h.pump b/vendor/gmock-1.7.0/include/gmock/gmock-generated-function-mockers.h.pump new file mode 100644 index 0000000000..f050caf129 --- /dev/null +++ b/vendor/gmock-1.7.0/include/gmock/gmock-generated-function-mockers.h.pump @@ -0,0 +1,265 @@ +$$ -*- mode: c++; -*- +$$ This is a Pump source file. Please use Pump to convert it to +$$ gmock-generated-function-mockers.h. +$$ +$var n = 10 $$ The maximum arity we support. +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) + +// Google Mock - a framework for writing C++ mock classes. +// +// This file implements function mockers of various arities. + +#ifndef GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_FUNCTION_MOCKERS_H_ +#define GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_FUNCTION_MOCKERS_H_ + +#include "gmock/gmock-spec-builders.h" +#include "gmock/internal/gmock-internal-utils.h" + +namespace testing { +namespace internal { + +template +class FunctionMockerBase; + +// Note: class FunctionMocker really belongs to the ::testing +// namespace. However if we define it in ::testing, MSVC will +// complain when classes in ::testing::internal declare it as a +// friend class template. To workaround this compiler bug, we define +// FunctionMocker in ::testing::internal and import it into ::testing. +template +class FunctionMocker; + + +$range i 0..n +$for i [[ +$range j 1..i +$var typename_As = [[$for j [[, typename A$j]]]] +$var As = [[$for j, [[A$j]]]] +$var as = [[$for j, [[a$j]]]] +$var Aas = [[$for j, [[A$j a$j]]]] +$var ms = [[$for j, [[m$j]]]] +$var matchers = [[$for j, [[const Matcher& m$j]]]] +template +class FunctionMocker : public + internal::FunctionMockerBase { + public: + typedef R F($As); + typedef typename internal::Function::ArgumentTuple ArgumentTuple; + + MockSpec& With($matchers) { + +$if i >= 1 [[ + this->current_spec().SetMatchers(::std::tr1::make_tuple($ms)); + +]] + return this->current_spec(); + } + + R Invoke($Aas) { + // Even though gcc and MSVC don't enforce it, 'this->' is required + // by the C++ standard [14.6.4] here, as the base class type is + // dependent on the template argument (and thus shouldn't be + // looked into when resolving InvokeWith). + return this->InvokeWith(ArgumentTuple($as)); + } +}; + + +]] +} // namespace internal + +// The style guide prohibits "using" statements in a namespace scope +// inside a header file. However, the FunctionMocker class template +// is meant to be defined in the ::testing namespace. The following +// line is just a trick for working around a bug in MSVC 8.0, which +// cannot handle it if we define FunctionMocker in ::testing. +using internal::FunctionMocker; + +// GMOCK_RESULT_(tn, F) expands to the result type of function type F. +// We define this as a variadic macro in case F contains unprotected +// commas (the same reason that we use variadic macros in other places +// in this file). +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_RESULT_(tn, ...) \ + tn ::testing::internal::Function<__VA_ARGS__>::Result + +// The type of argument N of the given function type. +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_ARG_(tn, N, ...) \ + tn ::testing::internal::Function<__VA_ARGS__>::Argument##N + +// The matcher type for argument N of the given function type. +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_MATCHER_(tn, N, ...) \ + const ::testing::Matcher& + +// The variable for mocking the given method. +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_MOCKER_(arity, constness, Method) \ + GTEST_CONCAT_TOKEN_(gmock##constness##arity##_##Method##_, __LINE__) + + +$for i [[ +$range j 1..i +$var arg_as = [[$for j, \ + [[GMOCK_ARG_(tn, $j, __VA_ARGS__) gmock_a$j]]]] +$var as = [[$for j, [[gmock_a$j]]]] +$var matcher_as = [[$for j, \ + [[GMOCK_MATCHER_(tn, $j, __VA_ARGS__) gmock_a$j]]]] +// INTERNAL IMPLEMENTATION - DON'T USE IN USER CODE!!! +#define GMOCK_METHOD$i[[]]_(tn, constness, ct, Method, ...) \ + GMOCK_RESULT_(tn, __VA_ARGS__) ct Method( \ + $arg_as) constness { \ + GTEST_COMPILE_ASSERT_((::std::tr1::tuple_size< \ + tn ::testing::internal::Function<__VA_ARGS__>::ArgumentTuple>::value == $i), \ + this_method_does_not_take_$i[[]]_argument[[$if i != 1 [[s]]]]); \ + GMOCK_MOCKER_($i, constness, Method).SetOwnerAndName(this, #Method); \ + return GMOCK_MOCKER_($i, constness, Method).Invoke($as); \ + } \ + ::testing::MockSpec<__VA_ARGS__>& \ + gmock_##Method($matcher_as) constness { \ + GMOCK_MOCKER_($i, constness, Method).RegisterOwner(this); \ + return GMOCK_MOCKER_($i, constness, Method).With($as); \ + } \ + mutable ::testing::FunctionMocker<__VA_ARGS__> GMOCK_MOCKER_($i, constness, Method) + + +]] +$for i [[ +#define MOCK_METHOD$i(m, ...) GMOCK_METHOD$i[[]]_(, , , m, __VA_ARGS__) + +]] + + +$for i [[ +#define MOCK_CONST_METHOD$i(m, ...) GMOCK_METHOD$i[[]]_(, const, , m, __VA_ARGS__) + +]] + + +$for i [[ +#define MOCK_METHOD$i[[]]_T(m, ...) GMOCK_METHOD$i[[]]_(typename, , , m, __VA_ARGS__) + +]] + + +$for i [[ +#define MOCK_CONST_METHOD$i[[]]_T(m, ...) \ + GMOCK_METHOD$i[[]]_(typename, const, , m, __VA_ARGS__) + +]] + + +$for i [[ +#define MOCK_METHOD$i[[]]_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD$i[[]]_(, , ct, m, __VA_ARGS__) + +]] + + +$for i [[ +#define MOCK_CONST_METHOD$i[[]]_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD$i[[]]_(, const, ct, m, __VA_ARGS__) + +]] + + +$for i [[ +#define MOCK_METHOD$i[[]]_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD$i[[]]_(typename, , ct, m, __VA_ARGS__) + +]] + + +$for i [[ +#define MOCK_CONST_METHOD$i[[]]_T_WITH_CALLTYPE(ct, m, ...) \ + GMOCK_METHOD$i[[]]_(typename, const, ct, m, __VA_ARGS__) + +]] + +// A MockFunction class has one mock method whose type is F. It is +// useful when you just want your test code to emit some messages and +// have Google Mock verify the right messages are sent (and perhaps at +// the right times). For example, if you are exercising code: +// +// Foo(1); +// Foo(2); +// Foo(3); +// +// and want to verify that Foo(1) and Foo(3) both invoke +// mock.Bar("a"), but Foo(2) doesn't invoke anything, you can write: +// +// TEST(FooTest, InvokesBarCorrectly) { +// MyMock mock; +// MockFunction check; +// { +// InSequence s; +// +// EXPECT_CALL(mock, Bar("a")); +// EXPECT_CALL(check, Call("1")); +// EXPECT_CALL(check, Call("2")); +// EXPECT_CALL(mock, Bar("a")); +// } +// Foo(1); +// check.Call("1"); +// Foo(2); +// check.Call("2"); +// Foo(3); +// } +// +// The expectation spec says that the first Bar("a") must happen +// before check point "1", the second Bar("a") must happen after check +// point "2", and nothing should happen between the two check +// points. The explicit check points make it easy to tell which +// Bar("a") is called by which call to Foo(). +template +class MockFunction; + + +$for i [[ +$range j 0..i-1 +template +class MockFunction { + public: + MockFunction() {} + + MOCK_METHOD$i[[]]_T(Call, R($for j, [[A$j]])); + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(MockFunction); +}; + + +]] +} // namespace testing + +#endif // GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_FUNCTION_MOCKERS_H_ diff --git a/vendor/gmock-1.7.0/include/gmock/gmock-generated-matchers.h b/vendor/gmock-1.7.0/include/gmock/gmock-generated-matchers.h new file mode 100644 index 0000000000..b4c85715a8 --- /dev/null +++ b/vendor/gmock-1.7.0/include/gmock/gmock-generated-matchers.h @@ -0,0 +1,2190 @@ +// This file was GENERATED by command: +// pump.py gmock-generated-matchers.h.pump +// DO NOT EDIT BY HAND!!! + +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Google Mock - a framework for writing C++ mock classes. +// +// This file implements some commonly used variadic matchers. + +#ifndef GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_MATCHERS_H_ +#define GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_MATCHERS_H_ + +#include +#include +#include +#include +#include "gmock/gmock-matchers.h" + +namespace testing { +namespace internal { + +// The type of the i-th (0-based) field of Tuple. +#define GMOCK_FIELD_TYPE_(Tuple, i) \ + typename ::std::tr1::tuple_element::type + +// TupleFields is for selecting fields from a +// tuple of type Tuple. It has two members: +// +// type: a tuple type whose i-th field is the ki-th field of Tuple. +// GetSelectedFields(t): returns fields k0, ..., and kn of t as a tuple. +// +// For example, in class TupleFields, 2, 0>, we have: +// +// type is tuple, and +// GetSelectedFields(make_tuple(true, 'a', 42)) is (42, true). + +template +class TupleFields; + +// This generic version is used when there are 10 selectors. +template +class TupleFields { + public: + typedef ::std::tr1::tuple type; + static type GetSelectedFields(const Tuple& t) { + using ::std::tr1::get; + return type(get(t), get(t), get(t), get(t), get(t), + get(t), get(t), get(t), get(t), get(t)); + } +}; + +// The following specialization is used for 0 ~ 9 selectors. + +template +class TupleFields { + public: + typedef ::std::tr1::tuple<> type; + static type GetSelectedFields(const Tuple& /* t */) { + using ::std::tr1::get; + return type(); + } +}; + +template +class TupleFields { + public: + typedef ::std::tr1::tuple type; + static type GetSelectedFields(const Tuple& t) { + using ::std::tr1::get; + return type(get(t)); + } +}; + +template +class TupleFields { + public: + typedef ::std::tr1::tuple type; + static type GetSelectedFields(const Tuple& t) { + using ::std::tr1::get; + return type(get(t), get(t)); + } +}; + +template +class TupleFields { + public: + typedef ::std::tr1::tuple type; + static type GetSelectedFields(const Tuple& t) { + using ::std::tr1::get; + return type(get(t), get(t), get(t)); + } +}; + +template +class TupleFields { + public: + typedef ::std::tr1::tuple type; + static type GetSelectedFields(const Tuple& t) { + using ::std::tr1::get; + return type(get(t), get(t), get(t), get(t)); + } +}; + +template +class TupleFields { + public: + typedef ::std::tr1::tuple type; + static type GetSelectedFields(const Tuple& t) { + using ::std::tr1::get; + return type(get(t), get(t), get(t), get(t), get(t)); + } +}; + +template +class TupleFields { + public: + typedef ::std::tr1::tuple type; + static type GetSelectedFields(const Tuple& t) { + using ::std::tr1::get; + return type(get(t), get(t), get(t), get(t), get(t), + get(t)); + } +}; + +template +class TupleFields { + public: + typedef ::std::tr1::tuple type; + static type GetSelectedFields(const Tuple& t) { + using ::std::tr1::get; + return type(get(t), get(t), get(t), get(t), get(t), + get(t), get(t)); + } +}; + +template +class TupleFields { + public: + typedef ::std::tr1::tuple type; + static type GetSelectedFields(const Tuple& t) { + using ::std::tr1::get; + return type(get(t), get(t), get(t), get(t), get(t), + get(t), get(t), get(t)); + } +}; + +template +class TupleFields { + public: + typedef ::std::tr1::tuple type; + static type GetSelectedFields(const Tuple& t) { + using ::std::tr1::get; + return type(get(t), get(t), get(t), get(t), get(t), + get(t), get(t), get(t), get(t)); + } +}; + +#undef GMOCK_FIELD_TYPE_ + +// Implements the Args() matcher. +template +class ArgsMatcherImpl : public MatcherInterface { + public: + // ArgsTuple may have top-level const or reference modifiers. + typedef GTEST_REMOVE_REFERENCE_AND_CONST_(ArgsTuple) RawArgsTuple; + typedef typename internal::TupleFields::type SelectedArgs; + typedef Matcher MonomorphicInnerMatcher; + + template + explicit ArgsMatcherImpl(const InnerMatcher& inner_matcher) + : inner_matcher_(SafeMatcherCast(inner_matcher)) {} + + virtual bool MatchAndExplain(ArgsTuple args, + MatchResultListener* listener) const { + const SelectedArgs& selected_args = GetSelectedArgs(args); + if (!listener->IsInterested()) + return inner_matcher_.Matches(selected_args); + + PrintIndices(listener->stream()); + *listener << "are " << PrintToString(selected_args); + + StringMatchResultListener inner_listener; + const bool match = inner_matcher_.MatchAndExplain(selected_args, + &inner_listener); + PrintIfNotEmpty(inner_listener.str(), listener->stream()); + return match; + } + + virtual void DescribeTo(::std::ostream* os) const { + *os << "are a tuple "; + PrintIndices(os); + inner_matcher_.DescribeTo(os); + } + + virtual void DescribeNegationTo(::std::ostream* os) const { + *os << "are a tuple "; + PrintIndices(os); + inner_matcher_.DescribeNegationTo(os); + } + + private: + static SelectedArgs GetSelectedArgs(ArgsTuple args) { + return TupleFields::GetSelectedFields(args); + } + + // Prints the indices of the selected fields. + static void PrintIndices(::std::ostream* os) { + *os << "whose fields ("; + const int indices[10] = { k0, k1, k2, k3, k4, k5, k6, k7, k8, k9 }; + for (int i = 0; i < 10; i++) { + if (indices[i] < 0) + break; + + if (i >= 1) + *os << ", "; + + *os << "#" << indices[i]; + } + *os << ") "; + } + + const MonomorphicInnerMatcher inner_matcher_; + + GTEST_DISALLOW_ASSIGN_(ArgsMatcherImpl); +}; + +template +class ArgsMatcher { + public: + explicit ArgsMatcher(const InnerMatcher& inner_matcher) + : inner_matcher_(inner_matcher) {} + + template + operator Matcher() const { + return MakeMatcher(new ArgsMatcherImpl(inner_matcher_)); + } + + private: + const InnerMatcher inner_matcher_; + + GTEST_DISALLOW_ASSIGN_(ArgsMatcher); +}; + +// A set of metafunctions for computing the result type of AllOf. +// AllOf(m1, ..., mN) returns +// AllOfResultN::type. + +// Although AllOf isn't defined for one argument, AllOfResult1 is defined +// to simplify the implementation. +template +struct AllOfResult1 { + typedef M1 type; +}; + +template +struct AllOfResult2 { + typedef BothOfMatcher< + typename AllOfResult1::type, + typename AllOfResult1::type + > type; +}; + +template +struct AllOfResult3 { + typedef BothOfMatcher< + typename AllOfResult1::type, + typename AllOfResult2::type + > type; +}; + +template +struct AllOfResult4 { + typedef BothOfMatcher< + typename AllOfResult2::type, + typename AllOfResult2::type + > type; +}; + +template +struct AllOfResult5 { + typedef BothOfMatcher< + typename AllOfResult2::type, + typename AllOfResult3::type + > type; +}; + +template +struct AllOfResult6 { + typedef BothOfMatcher< + typename AllOfResult3::type, + typename AllOfResult3::type + > type; +}; + +template +struct AllOfResult7 { + typedef BothOfMatcher< + typename AllOfResult3::type, + typename AllOfResult4::type + > type; +}; + +template +struct AllOfResult8 { + typedef BothOfMatcher< + typename AllOfResult4::type, + typename AllOfResult4::type + > type; +}; + +template +struct AllOfResult9 { + typedef BothOfMatcher< + typename AllOfResult4::type, + typename AllOfResult5::type + > type; +}; + +template +struct AllOfResult10 { + typedef BothOfMatcher< + typename AllOfResult5::type, + typename AllOfResult5::type + > type; +}; + +// A set of metafunctions for computing the result type of AnyOf. +// AnyOf(m1, ..., mN) returns +// AnyOfResultN::type. + +// Although AnyOf isn't defined for one argument, AnyOfResult1 is defined +// to simplify the implementation. +template +struct AnyOfResult1 { + typedef M1 type; +}; + +template +struct AnyOfResult2 { + typedef EitherOfMatcher< + typename AnyOfResult1::type, + typename AnyOfResult1::type + > type; +}; + +template +struct AnyOfResult3 { + typedef EitherOfMatcher< + typename AnyOfResult1::type, + typename AnyOfResult2::type + > type; +}; + +template +struct AnyOfResult4 { + typedef EitherOfMatcher< + typename AnyOfResult2::type, + typename AnyOfResult2::type + > type; +}; + +template +struct AnyOfResult5 { + typedef EitherOfMatcher< + typename AnyOfResult2::type, + typename AnyOfResult3::type + > type; +}; + +template +struct AnyOfResult6 { + typedef EitherOfMatcher< + typename AnyOfResult3::type, + typename AnyOfResult3::type + > type; +}; + +template +struct AnyOfResult7 { + typedef EitherOfMatcher< + typename AnyOfResult3::type, + typename AnyOfResult4::type + > type; +}; + +template +struct AnyOfResult8 { + typedef EitherOfMatcher< + typename AnyOfResult4::type, + typename AnyOfResult4::type + > type; +}; + +template +struct AnyOfResult9 { + typedef EitherOfMatcher< + typename AnyOfResult4::type, + typename AnyOfResult5::type + > type; +}; + +template +struct AnyOfResult10 { + typedef EitherOfMatcher< + typename AnyOfResult5::type, + typename AnyOfResult5::type + > type; +}; + +} // namespace internal + +// Args(a_matcher) matches a tuple if the selected +// fields of it matches a_matcher. C++ doesn't support default +// arguments for function templates, so we have to overload it. +template +inline internal::ArgsMatcher +Args(const InnerMatcher& matcher) { + return internal::ArgsMatcher(matcher); +} + +template +inline internal::ArgsMatcher +Args(const InnerMatcher& matcher) { + return internal::ArgsMatcher(matcher); +} + +template +inline internal::ArgsMatcher +Args(const InnerMatcher& matcher) { + return internal::ArgsMatcher(matcher); +} + +template +inline internal::ArgsMatcher +Args(const InnerMatcher& matcher) { + return internal::ArgsMatcher(matcher); +} + +template +inline internal::ArgsMatcher +Args(const InnerMatcher& matcher) { + return internal::ArgsMatcher(matcher); +} + +template +inline internal::ArgsMatcher +Args(const InnerMatcher& matcher) { + return internal::ArgsMatcher(matcher); +} + +template +inline internal::ArgsMatcher +Args(const InnerMatcher& matcher) { + return internal::ArgsMatcher(matcher); +} + +template +inline internal::ArgsMatcher +Args(const InnerMatcher& matcher) { + return internal::ArgsMatcher(matcher); +} + +template +inline internal::ArgsMatcher +Args(const InnerMatcher& matcher) { + return internal::ArgsMatcher(matcher); +} + +template +inline internal::ArgsMatcher +Args(const InnerMatcher& matcher) { + return internal::ArgsMatcher(matcher); +} + +template +inline internal::ArgsMatcher +Args(const InnerMatcher& matcher) { + return internal::ArgsMatcher(matcher); +} + +// ElementsAre(e_1, e_2, ... e_n) matches an STL-style container with +// n elements, where the i-th element in the container must +// match the i-th argument in the list. Each argument of +// ElementsAre() can be either a value or a matcher. We support up to +// 10 arguments. +// +// The use of DecayArray in the implementation allows ElementsAre() +// to accept string literals, whose type is const char[N], but we +// want to treat them as const char*. +// +// NOTE: Since ElementsAre() cares about the order of the elements, it +// must not be used with containers whose elements's order is +// undefined (e.g. hash_map). + +inline internal::ElementsAreMatcher< + std::tr1::tuple<> > +ElementsAre() { + typedef std::tr1::tuple<> Args; + return internal::ElementsAreMatcher(Args()); +} + +template +inline internal::ElementsAreMatcher< + std::tr1::tuple< + typename internal::DecayArray::type> > +ElementsAre(const T1& e1) { + typedef std::tr1::tuple< + typename internal::DecayArray::type> Args; + return internal::ElementsAreMatcher(Args(e1)); +} + +template +inline internal::ElementsAreMatcher< + std::tr1::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type> > +ElementsAre(const T1& e1, const T2& e2) { + typedef std::tr1::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type> Args; + return internal::ElementsAreMatcher(Args(e1, e2)); +} + +template +inline internal::ElementsAreMatcher< + std::tr1::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> > +ElementsAre(const T1& e1, const T2& e2, const T3& e3) { + typedef std::tr1::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> Args; + return internal::ElementsAreMatcher(Args(e1, e2, e3)); +} + +template +inline internal::ElementsAreMatcher< + std::tr1::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> > +ElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4) { + typedef std::tr1::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> Args; + return internal::ElementsAreMatcher(Args(e1, e2, e3, e4)); +} + +template +inline internal::ElementsAreMatcher< + std::tr1::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> > +ElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4, + const T5& e5) { + typedef std::tr1::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> Args; + return internal::ElementsAreMatcher(Args(e1, e2, e3, e4, e5)); +} + +template +inline internal::ElementsAreMatcher< + std::tr1::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> > +ElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4, + const T5& e5, const T6& e6) { + typedef std::tr1::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> Args; + return internal::ElementsAreMatcher(Args(e1, e2, e3, e4, e5, e6)); +} + +template +inline internal::ElementsAreMatcher< + std::tr1::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> > +ElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4, + const T5& e5, const T6& e6, const T7& e7) { + typedef std::tr1::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> Args; + return internal::ElementsAreMatcher(Args(e1, e2, e3, e4, e5, e6, e7)); +} + +template +inline internal::ElementsAreMatcher< + std::tr1::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> > +ElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4, + const T5& e5, const T6& e6, const T7& e7, const T8& e8) { + typedef std::tr1::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> Args; + return internal::ElementsAreMatcher(Args(e1, e2, e3, e4, e5, e6, e7, + e8)); +} + +template +inline internal::ElementsAreMatcher< + std::tr1::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> > +ElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4, + const T5& e5, const T6& e6, const T7& e7, const T8& e8, const T9& e9) { + typedef std::tr1::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> Args; + return internal::ElementsAreMatcher(Args(e1, e2, e3, e4, e5, e6, e7, + e8, e9)); +} + +template +inline internal::ElementsAreMatcher< + std::tr1::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> > +ElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4, + const T5& e5, const T6& e6, const T7& e7, const T8& e8, const T9& e9, + const T10& e10) { + typedef std::tr1::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> Args; + return internal::ElementsAreMatcher(Args(e1, e2, e3, e4, e5, e6, e7, + e8, e9, e10)); +} + +// UnorderedElementsAre(e_1, e_2, ..., e_n) is an ElementsAre extension +// that matches n elements in any order. We support up to n=10 arguments. + +inline internal::UnorderedElementsAreMatcher< + std::tr1::tuple<> > +UnorderedElementsAre() { + typedef std::tr1::tuple<> Args; + return internal::UnorderedElementsAreMatcher(Args()); +} + +template +inline internal::UnorderedElementsAreMatcher< + std::tr1::tuple< + typename internal::DecayArray::type> > +UnorderedElementsAre(const T1& e1) { + typedef std::tr1::tuple< + typename internal::DecayArray::type> Args; + return internal::UnorderedElementsAreMatcher(Args(e1)); +} + +template +inline internal::UnorderedElementsAreMatcher< + std::tr1::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type> > +UnorderedElementsAre(const T1& e1, const T2& e2) { + typedef std::tr1::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type> Args; + return internal::UnorderedElementsAreMatcher(Args(e1, e2)); +} + +template +inline internal::UnorderedElementsAreMatcher< + std::tr1::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> > +UnorderedElementsAre(const T1& e1, const T2& e2, const T3& e3) { + typedef std::tr1::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> Args; + return internal::UnorderedElementsAreMatcher(Args(e1, e2, e3)); +} + +template +inline internal::UnorderedElementsAreMatcher< + std::tr1::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> > +UnorderedElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4) { + typedef std::tr1::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> Args; + return internal::UnorderedElementsAreMatcher(Args(e1, e2, e3, e4)); +} + +template +inline internal::UnorderedElementsAreMatcher< + std::tr1::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> > +UnorderedElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4, + const T5& e5) { + typedef std::tr1::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> Args; + return internal::UnorderedElementsAreMatcher(Args(e1, e2, e3, e4, e5)); +} + +template +inline internal::UnorderedElementsAreMatcher< + std::tr1::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> > +UnorderedElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4, + const T5& e5, const T6& e6) { + typedef std::tr1::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> Args; + return internal::UnorderedElementsAreMatcher(Args(e1, e2, e3, e4, e5, + e6)); +} + +template +inline internal::UnorderedElementsAreMatcher< + std::tr1::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> > +UnorderedElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4, + const T5& e5, const T6& e6, const T7& e7) { + typedef std::tr1::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> Args; + return internal::UnorderedElementsAreMatcher(Args(e1, e2, e3, e4, e5, + e6, e7)); +} + +template +inline internal::UnorderedElementsAreMatcher< + std::tr1::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> > +UnorderedElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4, + const T5& e5, const T6& e6, const T7& e7, const T8& e8) { + typedef std::tr1::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> Args; + return internal::UnorderedElementsAreMatcher(Args(e1, e2, e3, e4, e5, + e6, e7, e8)); +} + +template +inline internal::UnorderedElementsAreMatcher< + std::tr1::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> > +UnorderedElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4, + const T5& e5, const T6& e6, const T7& e7, const T8& e8, const T9& e9) { + typedef std::tr1::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> Args; + return internal::UnorderedElementsAreMatcher(Args(e1, e2, e3, e4, e5, + e6, e7, e8, e9)); +} + +template +inline internal::UnorderedElementsAreMatcher< + std::tr1::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> > +UnorderedElementsAre(const T1& e1, const T2& e2, const T3& e3, const T4& e4, + const T5& e5, const T6& e6, const T7& e7, const T8& e8, const T9& e9, + const T10& e10) { + typedef std::tr1::tuple< + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type, + typename internal::DecayArray::type> Args; + return internal::UnorderedElementsAreMatcher(Args(e1, e2, e3, e4, e5, + e6, e7, e8, e9, e10)); +} + +// AllOf(m1, m2, ..., mk) matches any value that matches all of the given +// sub-matchers. AllOf is called fully qualified to prevent ADL from firing. + +template +inline typename internal::AllOfResult2::type +AllOf(M1 m1, M2 m2) { + return typename internal::AllOfResult2::type( + m1, + m2); +} + +template +inline typename internal::AllOfResult3::type +AllOf(M1 m1, M2 m2, M3 m3) { + return typename internal::AllOfResult3::type( + m1, + ::testing::AllOf(m2, m3)); +} + +template +inline typename internal::AllOfResult4::type +AllOf(M1 m1, M2 m2, M3 m3, M4 m4) { + return typename internal::AllOfResult4::type( + ::testing::AllOf(m1, m2), + ::testing::AllOf(m3, m4)); +} + +template +inline typename internal::AllOfResult5::type +AllOf(M1 m1, M2 m2, M3 m3, M4 m4, M5 m5) { + return typename internal::AllOfResult5::type( + ::testing::AllOf(m1, m2), + ::testing::AllOf(m3, m4, m5)); +} + +template +inline typename internal::AllOfResult6::type +AllOf(M1 m1, M2 m2, M3 m3, M4 m4, M5 m5, M6 m6) { + return typename internal::AllOfResult6::type( + ::testing::AllOf(m1, m2, m3), + ::testing::AllOf(m4, m5, m6)); +} + +template +inline typename internal::AllOfResult7::type +AllOf(M1 m1, M2 m2, M3 m3, M4 m4, M5 m5, M6 m6, M7 m7) { + return typename internal::AllOfResult7::type( + ::testing::AllOf(m1, m2, m3), + ::testing::AllOf(m4, m5, m6, m7)); +} + +template +inline typename internal::AllOfResult8::type +AllOf(M1 m1, M2 m2, M3 m3, M4 m4, M5 m5, M6 m6, M7 m7, M8 m8) { + return typename internal::AllOfResult8::type( + ::testing::AllOf(m1, m2, m3, m4), + ::testing::AllOf(m5, m6, m7, m8)); +} + +template +inline typename internal::AllOfResult9::type +AllOf(M1 m1, M2 m2, M3 m3, M4 m4, M5 m5, M6 m6, M7 m7, M8 m8, M9 m9) { + return typename internal::AllOfResult9::type( + ::testing::AllOf(m1, m2, m3, m4), + ::testing::AllOf(m5, m6, m7, m8, m9)); +} + +template +inline typename internal::AllOfResult10::type +AllOf(M1 m1, M2 m2, M3 m3, M4 m4, M5 m5, M6 m6, M7 m7, M8 m8, M9 m9, M10 m10) { + return typename internal::AllOfResult10::type( + ::testing::AllOf(m1, m2, m3, m4, m5), + ::testing::AllOf(m6, m7, m8, m9, m10)); +} + +// AnyOf(m1, m2, ..., mk) matches any value that matches any of the given +// sub-matchers. AnyOf is called fully qualified to prevent ADL from firing. + +template +inline typename internal::AnyOfResult2::type +AnyOf(M1 m1, M2 m2) { + return typename internal::AnyOfResult2::type( + m1, + m2); +} + +template +inline typename internal::AnyOfResult3::type +AnyOf(M1 m1, M2 m2, M3 m3) { + return typename internal::AnyOfResult3::type( + m1, + ::testing::AnyOf(m2, m3)); +} + +template +inline typename internal::AnyOfResult4::type +AnyOf(M1 m1, M2 m2, M3 m3, M4 m4) { + return typename internal::AnyOfResult4::type( + ::testing::AnyOf(m1, m2), + ::testing::AnyOf(m3, m4)); +} + +template +inline typename internal::AnyOfResult5::type +AnyOf(M1 m1, M2 m2, M3 m3, M4 m4, M5 m5) { + return typename internal::AnyOfResult5::type( + ::testing::AnyOf(m1, m2), + ::testing::AnyOf(m3, m4, m5)); +} + +template +inline typename internal::AnyOfResult6::type +AnyOf(M1 m1, M2 m2, M3 m3, M4 m4, M5 m5, M6 m6) { + return typename internal::AnyOfResult6::type( + ::testing::AnyOf(m1, m2, m3), + ::testing::AnyOf(m4, m5, m6)); +} + +template +inline typename internal::AnyOfResult7::type +AnyOf(M1 m1, M2 m2, M3 m3, M4 m4, M5 m5, M6 m6, M7 m7) { + return typename internal::AnyOfResult7::type( + ::testing::AnyOf(m1, m2, m3), + ::testing::AnyOf(m4, m5, m6, m7)); +} + +template +inline typename internal::AnyOfResult8::type +AnyOf(M1 m1, M2 m2, M3 m3, M4 m4, M5 m5, M6 m6, M7 m7, M8 m8) { + return typename internal::AnyOfResult8::type( + ::testing::AnyOf(m1, m2, m3, m4), + ::testing::AnyOf(m5, m6, m7, m8)); +} + +template +inline typename internal::AnyOfResult9::type +AnyOf(M1 m1, M2 m2, M3 m3, M4 m4, M5 m5, M6 m6, M7 m7, M8 m8, M9 m9) { + return typename internal::AnyOfResult9::type( + ::testing::AnyOf(m1, m2, m3, m4), + ::testing::AnyOf(m5, m6, m7, m8, m9)); +} + +template +inline typename internal::AnyOfResult10::type +AnyOf(M1 m1, M2 m2, M3 m3, M4 m4, M5 m5, M6 m6, M7 m7, M8 m8, M9 m9, M10 m10) { + return typename internal::AnyOfResult10::type( + ::testing::AnyOf(m1, m2, m3, m4, m5), + ::testing::AnyOf(m6, m7, m8, m9, m10)); +} + +} // namespace testing + + +// The MATCHER* family of macros can be used in a namespace scope to +// define custom matchers easily. +// +// Basic Usage +// =========== +// +// The syntax +// +// MATCHER(name, description_string) { statements; } +// +// defines a matcher with the given name that executes the statements, +// which must return a bool to indicate if the match succeeds. Inside +// the statements, you can refer to the value being matched by 'arg', +// and refer to its type by 'arg_type'. +// +// The description string documents what the matcher does, and is used +// to generate the failure message when the match fails. Since a +// MATCHER() is usually defined in a header file shared by multiple +// C++ source files, we require the description to be a C-string +// literal to avoid possible side effects. It can be empty, in which +// case we'll use the sequence of words in the matcher name as the +// description. +// +// For example: +// +// MATCHER(IsEven, "") { return (arg % 2) == 0; } +// +// allows you to write +// +// // Expects mock_foo.Bar(n) to be called where n is even. +// EXPECT_CALL(mock_foo, Bar(IsEven())); +// +// or, +// +// // Verifies that the value of some_expression is even. +// EXPECT_THAT(some_expression, IsEven()); +// +// If the above assertion fails, it will print something like: +// +// Value of: some_expression +// Expected: is even +// Actual: 7 +// +// where the description "is even" is automatically calculated from the +// matcher name IsEven. +// +// Argument Type +// ============= +// +// Note that the type of the value being matched (arg_type) is +// determined by the context in which you use the matcher and is +// supplied to you by the compiler, so you don't need to worry about +// declaring it (nor can you). This allows the matcher to be +// polymorphic. For example, IsEven() can be used to match any type +// where the value of "(arg % 2) == 0" can be implicitly converted to +// a bool. In the "Bar(IsEven())" example above, if method Bar() +// takes an int, 'arg_type' will be int; if it takes an unsigned long, +// 'arg_type' will be unsigned long; and so on. +// +// Parameterizing Matchers +// ======================= +// +// Sometimes you'll want to parameterize the matcher. For that you +// can use another macro: +// +// MATCHER_P(name, param_name, description_string) { statements; } +// +// For example: +// +// MATCHER_P(HasAbsoluteValue, value, "") { return abs(arg) == value; } +// +// will allow you to write: +// +// EXPECT_THAT(Blah("a"), HasAbsoluteValue(n)); +// +// which may lead to this message (assuming n is 10): +// +// Value of: Blah("a") +// Expected: has absolute value 10 +// Actual: -9 +// +// Note that both the matcher description and its parameter are +// printed, making the message human-friendly. +// +// In the matcher definition body, you can write 'foo_type' to +// reference the type of a parameter named 'foo'. For example, in the +// body of MATCHER_P(HasAbsoluteValue, value) above, you can write +// 'value_type' to refer to the type of 'value'. +// +// We also provide MATCHER_P2, MATCHER_P3, ..., up to MATCHER_P10 to +// support multi-parameter matchers. +// +// Describing Parameterized Matchers +// ================================= +// +// The last argument to MATCHER*() is a string-typed expression. The +// expression can reference all of the matcher's parameters and a +// special bool-typed variable named 'negation'. When 'negation' is +// false, the expression should evaluate to the matcher's description; +// otherwise it should evaluate to the description of the negation of +// the matcher. For example, +// +// using testing::PrintToString; +// +// MATCHER_P2(InClosedRange, low, hi, +// string(negation ? "is not" : "is") + " in range [" + +// PrintToString(low) + ", " + PrintToString(hi) + "]") { +// return low <= arg && arg <= hi; +// } +// ... +// EXPECT_THAT(3, InClosedRange(4, 6)); +// EXPECT_THAT(3, Not(InClosedRange(2, 4))); +// +// would generate two failures that contain the text: +// +// Expected: is in range [4, 6] +// ... +// Expected: is not in range [2, 4] +// +// If you specify "" as the description, the failure message will +// contain the sequence of words in the matcher name followed by the +// parameter values printed as a tuple. For example, +// +// MATCHER_P2(InClosedRange, low, hi, "") { ... } +// ... +// EXPECT_THAT(3, InClosedRange(4, 6)); +// EXPECT_THAT(3, Not(InClosedRange(2, 4))); +// +// would generate two failures that contain the text: +// +// Expected: in closed range (4, 6) +// ... +// Expected: not (in closed range (2, 4)) +// +// Types of Matcher Parameters +// =========================== +// +// For the purpose of typing, you can view +// +// MATCHER_Pk(Foo, p1, ..., pk, description_string) { ... } +// +// as shorthand for +// +// template +// FooMatcherPk +// Foo(p1_type p1, ..., pk_type pk) { ... } +// +// When you write Foo(v1, ..., vk), the compiler infers the types of +// the parameters v1, ..., and vk for you. If you are not happy with +// the result of the type inference, you can specify the types by +// explicitly instantiating the template, as in Foo(5, +// false). As said earlier, you don't get to (or need to) specify +// 'arg_type' as that's determined by the context in which the matcher +// is used. You can assign the result of expression Foo(p1, ..., pk) +// to a variable of type FooMatcherPk. This +// can be useful when composing matchers. +// +// While you can instantiate a matcher template with reference types, +// passing the parameters by pointer usually makes your code more +// readable. If, however, you still want to pass a parameter by +// reference, be aware that in the failure message generated by the +// matcher you will see the value of the referenced object but not its +// address. +// +// Explaining Match Results +// ======================== +// +// Sometimes the matcher description alone isn't enough to explain why +// the match has failed or succeeded. For example, when expecting a +// long string, it can be very helpful to also print the diff between +// the expected string and the actual one. To achieve that, you can +// optionally stream additional information to a special variable +// named result_listener, whose type is a pointer to class +// MatchResultListener: +// +// MATCHER_P(EqualsLongString, str, "") { +// if (arg == str) return true; +// +// *result_listener << "the difference: " +/// << DiffStrings(str, arg); +// return false; +// } +// +// Overloading Matchers +// ==================== +// +// You can overload matchers with different numbers of parameters: +// +// MATCHER_P(Blah, a, description_string1) { ... } +// MATCHER_P2(Blah, a, b, description_string2) { ... } +// +// Caveats +// ======= +// +// When defining a new matcher, you should also consider implementing +// MatcherInterface or using MakePolymorphicMatcher(). These +// approaches require more work than the MATCHER* macros, but also +// give you more control on the types of the value being matched and +// the matcher parameters, which may leads to better compiler error +// messages when the matcher is used wrong. They also allow +// overloading matchers based on parameter types (as opposed to just +// based on the number of parameters). +// +// MATCHER*() can only be used in a namespace scope. The reason is +// that C++ doesn't yet allow function-local types to be used to +// instantiate templates. The up-coming C++0x standard will fix this. +// Once that's done, we'll consider supporting using MATCHER*() inside +// a function. +// +// More Information +// ================ +// +// To learn more about using these macros, please search for 'MATCHER' +// on http://code.google.com/p/googlemock/wiki/CookBook. + +#define MATCHER(name, description)\ + class name##Matcher {\ + public:\ + template \ + class gmock_Impl : public ::testing::MatcherInterface {\ + public:\ + gmock_Impl()\ + {}\ + virtual bool MatchAndExplain(\ + arg_type arg, ::testing::MatchResultListener* result_listener) const;\ + virtual void DescribeTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(false);\ + }\ + virtual void DescribeNegationTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(true);\ + }\ + private:\ + ::testing::internal::string FormatDescription(bool negation) const {\ + const ::testing::internal::string gmock_description = (description);\ + if (!gmock_description.empty())\ + return gmock_description;\ + return ::testing::internal::FormatMatcherDescription(\ + negation, #name, \ + ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ + ::std::tr1::tuple<>()));\ + }\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template \ + operator ::testing::Matcher() const {\ + return ::testing::Matcher(\ + new gmock_Impl());\ + }\ + name##Matcher() {\ + }\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##Matcher);\ + };\ + inline name##Matcher name() {\ + return name##Matcher();\ + }\ + template \ + bool name##Matcher::gmock_Impl::MatchAndExplain(\ + arg_type arg, \ + ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ + const + +#define MATCHER_P(name, p0, description)\ + template \ + class name##MatcherP {\ + public:\ + template \ + class gmock_Impl : public ::testing::MatcherInterface {\ + public:\ + explicit gmock_Impl(p0##_type gmock_p0)\ + : p0(gmock_p0) {}\ + virtual bool MatchAndExplain(\ + arg_type arg, ::testing::MatchResultListener* result_listener) const;\ + virtual void DescribeTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(false);\ + }\ + virtual void DescribeNegationTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(true);\ + }\ + p0##_type p0;\ + private:\ + ::testing::internal::string FormatDescription(bool negation) const {\ + const ::testing::internal::string gmock_description = (description);\ + if (!gmock_description.empty())\ + return gmock_description;\ + return ::testing::internal::FormatMatcherDescription(\ + negation, #name, \ + ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ + ::std::tr1::tuple(p0)));\ + }\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template \ + operator ::testing::Matcher() const {\ + return ::testing::Matcher(\ + new gmock_Impl(p0));\ + }\ + name##MatcherP(p0##_type gmock_p0) : p0(gmock_p0) {\ + }\ + p0##_type p0;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##MatcherP);\ + };\ + template \ + inline name##MatcherP name(p0##_type p0) {\ + return name##MatcherP(p0);\ + }\ + template \ + template \ + bool name##MatcherP::gmock_Impl::MatchAndExplain(\ + arg_type arg, \ + ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ + const + +#define MATCHER_P2(name, p0, p1, description)\ + template \ + class name##MatcherP2 {\ + public:\ + template \ + class gmock_Impl : public ::testing::MatcherInterface {\ + public:\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1)\ + : p0(gmock_p0), p1(gmock_p1) {}\ + virtual bool MatchAndExplain(\ + arg_type arg, ::testing::MatchResultListener* result_listener) const;\ + virtual void DescribeTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(false);\ + }\ + virtual void DescribeNegationTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(true);\ + }\ + p0##_type p0;\ + p1##_type p1;\ + private:\ + ::testing::internal::string FormatDescription(bool negation) const {\ + const ::testing::internal::string gmock_description = (description);\ + if (!gmock_description.empty())\ + return gmock_description;\ + return ::testing::internal::FormatMatcherDescription(\ + negation, #name, \ + ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ + ::std::tr1::tuple(p0, p1)));\ + }\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template \ + operator ::testing::Matcher() const {\ + return ::testing::Matcher(\ + new gmock_Impl(p0, p1));\ + }\ + name##MatcherP2(p0##_type gmock_p0, p1##_type gmock_p1) : p0(gmock_p0), \ + p1(gmock_p1) {\ + }\ + p0##_type p0;\ + p1##_type p1;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##MatcherP2);\ + };\ + template \ + inline name##MatcherP2 name(p0##_type p0, \ + p1##_type p1) {\ + return name##MatcherP2(p0, p1);\ + }\ + template \ + template \ + bool name##MatcherP2::gmock_Impl::MatchAndExplain(\ + arg_type arg, \ + ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ + const + +#define MATCHER_P3(name, p0, p1, p2, description)\ + template \ + class name##MatcherP3 {\ + public:\ + template \ + class gmock_Impl : public ::testing::MatcherInterface {\ + public:\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2)\ + : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2) {}\ + virtual bool MatchAndExplain(\ + arg_type arg, ::testing::MatchResultListener* result_listener) const;\ + virtual void DescribeTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(false);\ + }\ + virtual void DescribeNegationTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(true);\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + private:\ + ::testing::internal::string FormatDescription(bool negation) const {\ + const ::testing::internal::string gmock_description = (description);\ + if (!gmock_description.empty())\ + return gmock_description;\ + return ::testing::internal::FormatMatcherDescription(\ + negation, #name, \ + ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ + ::std::tr1::tuple(p0, p1, \ + p2)));\ + }\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template \ + operator ::testing::Matcher() const {\ + return ::testing::Matcher(\ + new gmock_Impl(p0, p1, p2));\ + }\ + name##MatcherP3(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2) {\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##MatcherP3);\ + };\ + template \ + inline name##MatcherP3 name(p0##_type p0, \ + p1##_type p1, p2##_type p2) {\ + return name##MatcherP3(p0, p1, p2);\ + }\ + template \ + template \ + bool name##MatcherP3::gmock_Impl::MatchAndExplain(\ + arg_type arg, \ + ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ + const + +#define MATCHER_P4(name, p0, p1, p2, p3, description)\ + template \ + class name##MatcherP4 {\ + public:\ + template \ + class gmock_Impl : public ::testing::MatcherInterface {\ + public:\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3)\ + : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), p3(gmock_p3) {}\ + virtual bool MatchAndExplain(\ + arg_type arg, ::testing::MatchResultListener* result_listener) const;\ + virtual void DescribeTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(false);\ + }\ + virtual void DescribeNegationTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(true);\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + private:\ + ::testing::internal::string FormatDescription(bool negation) const {\ + const ::testing::internal::string gmock_description = (description);\ + if (!gmock_description.empty())\ + return gmock_description;\ + return ::testing::internal::FormatMatcherDescription(\ + negation, #name, \ + ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ + ::std::tr1::tuple(p0, p1, p2, p3)));\ + }\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template \ + operator ::testing::Matcher() const {\ + return ::testing::Matcher(\ + new gmock_Impl(p0, p1, p2, p3));\ + }\ + name##MatcherP4(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2, p3##_type gmock_p3) : p0(gmock_p0), p1(gmock_p1), \ + p2(gmock_p2), p3(gmock_p3) {\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##MatcherP4);\ + };\ + template \ + inline name##MatcherP4 name(p0##_type p0, p1##_type p1, p2##_type p2, \ + p3##_type p3) {\ + return name##MatcherP4(p0, \ + p1, p2, p3);\ + }\ + template \ + template \ + bool name##MatcherP4::gmock_Impl::MatchAndExplain(\ + arg_type arg, \ + ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ + const + +#define MATCHER_P5(name, p0, p1, p2, p3, p4, description)\ + template \ + class name##MatcherP5 {\ + public:\ + template \ + class gmock_Impl : public ::testing::MatcherInterface {\ + public:\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4)\ + : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), p3(gmock_p3), \ + p4(gmock_p4) {}\ + virtual bool MatchAndExplain(\ + arg_type arg, ::testing::MatchResultListener* result_listener) const;\ + virtual void DescribeTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(false);\ + }\ + virtual void DescribeNegationTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(true);\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + private:\ + ::testing::internal::string FormatDescription(bool negation) const {\ + const ::testing::internal::string gmock_description = (description);\ + if (!gmock_description.empty())\ + return gmock_description;\ + return ::testing::internal::FormatMatcherDescription(\ + negation, #name, \ + ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ + ::std::tr1::tuple(p0, p1, p2, p3, p4)));\ + }\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template \ + operator ::testing::Matcher() const {\ + return ::testing::Matcher(\ + new gmock_Impl(p0, p1, p2, p3, p4));\ + }\ + name##MatcherP5(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2, p3##_type gmock_p3, \ + p4##_type gmock_p4) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4) {\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##MatcherP5);\ + };\ + template \ + inline name##MatcherP5 name(p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, \ + p4##_type p4) {\ + return name##MatcherP5(p0, p1, p2, p3, p4);\ + }\ + template \ + template \ + bool name##MatcherP5::gmock_Impl::MatchAndExplain(\ + arg_type arg, \ + ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ + const + +#define MATCHER_P6(name, p0, p1, p2, p3, p4, p5, description)\ + template \ + class name##MatcherP6 {\ + public:\ + template \ + class gmock_Impl : public ::testing::MatcherInterface {\ + public:\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5)\ + : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), p3(gmock_p3), \ + p4(gmock_p4), p5(gmock_p5) {}\ + virtual bool MatchAndExplain(\ + arg_type arg, ::testing::MatchResultListener* result_listener) const;\ + virtual void DescribeTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(false);\ + }\ + virtual void DescribeNegationTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(true);\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + private:\ + ::testing::internal::string FormatDescription(bool negation) const {\ + const ::testing::internal::string gmock_description = (description);\ + if (!gmock_description.empty())\ + return gmock_description;\ + return ::testing::internal::FormatMatcherDescription(\ + negation, #name, \ + ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ + ::std::tr1::tuple(p0, p1, p2, p3, p4, p5)));\ + }\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template \ + operator ::testing::Matcher() const {\ + return ::testing::Matcher(\ + new gmock_Impl(p0, p1, p2, p3, p4, p5));\ + }\ + name##MatcherP6(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2, p3##_type gmock_p3, p4##_type gmock_p4, \ + p5##_type gmock_p5) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4), p5(gmock_p5) {\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##MatcherP6);\ + };\ + template \ + inline name##MatcherP6 name(p0##_type p0, p1##_type p1, p2##_type p2, \ + p3##_type p3, p4##_type p4, p5##_type p5) {\ + return name##MatcherP6(p0, p1, p2, p3, p4, p5);\ + }\ + template \ + template \ + bool name##MatcherP6::gmock_Impl::MatchAndExplain(\ + arg_type arg, \ + ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ + const + +#define MATCHER_P7(name, p0, p1, p2, p3, p4, p5, p6, description)\ + template \ + class name##MatcherP7 {\ + public:\ + template \ + class gmock_Impl : public ::testing::MatcherInterface {\ + public:\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \ + p6##_type gmock_p6)\ + : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), p3(gmock_p3), \ + p4(gmock_p4), p5(gmock_p5), p6(gmock_p6) {}\ + virtual bool MatchAndExplain(\ + arg_type arg, ::testing::MatchResultListener* result_listener) const;\ + virtual void DescribeTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(false);\ + }\ + virtual void DescribeNegationTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(true);\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + private:\ + ::testing::internal::string FormatDescription(bool negation) const {\ + const ::testing::internal::string gmock_description = (description);\ + if (!gmock_description.empty())\ + return gmock_description;\ + return ::testing::internal::FormatMatcherDescription(\ + negation, #name, \ + ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ + ::std::tr1::tuple(p0, p1, p2, p3, p4, p5, \ + p6)));\ + }\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template \ + operator ::testing::Matcher() const {\ + return ::testing::Matcher(\ + new gmock_Impl(p0, p1, p2, p3, p4, p5, p6));\ + }\ + name##MatcherP7(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2, p3##_type gmock_p3, p4##_type gmock_p4, \ + p5##_type gmock_p5, p6##_type gmock_p6) : p0(gmock_p0), p1(gmock_p1), \ + p2(gmock_p2), p3(gmock_p3), p4(gmock_p4), p5(gmock_p5), \ + p6(gmock_p6) {\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##MatcherP7);\ + };\ + template \ + inline name##MatcherP7 name(p0##_type p0, p1##_type p1, \ + p2##_type p2, p3##_type p3, p4##_type p4, p5##_type p5, \ + p6##_type p6) {\ + return name##MatcherP7(p0, p1, p2, p3, p4, p5, p6);\ + }\ + template \ + template \ + bool name##MatcherP7::gmock_Impl::MatchAndExplain(\ + arg_type arg, \ + ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ + const + +#define MATCHER_P8(name, p0, p1, p2, p3, p4, p5, p6, p7, description)\ + template \ + class name##MatcherP8 {\ + public:\ + template \ + class gmock_Impl : public ::testing::MatcherInterface {\ + public:\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \ + p6##_type gmock_p6, p7##_type gmock_p7)\ + : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), p3(gmock_p3), \ + p4(gmock_p4), p5(gmock_p5), p6(gmock_p6), p7(gmock_p7) {}\ + virtual bool MatchAndExplain(\ + arg_type arg, ::testing::MatchResultListener* result_listener) const;\ + virtual void DescribeTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(false);\ + }\ + virtual void DescribeNegationTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(true);\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + p7##_type p7;\ + private:\ + ::testing::internal::string FormatDescription(bool negation) const {\ + const ::testing::internal::string gmock_description = (description);\ + if (!gmock_description.empty())\ + return gmock_description;\ + return ::testing::internal::FormatMatcherDescription(\ + negation, #name, \ + ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ + ::std::tr1::tuple(p0, p1, p2, \ + p3, p4, p5, p6, p7)));\ + }\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template \ + operator ::testing::Matcher() const {\ + return ::testing::Matcher(\ + new gmock_Impl(p0, p1, p2, p3, p4, p5, p6, p7));\ + }\ + name##MatcherP8(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2, p3##_type gmock_p3, p4##_type gmock_p4, \ + p5##_type gmock_p5, p6##_type gmock_p6, \ + p7##_type gmock_p7) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4), p5(gmock_p5), p6(gmock_p6), \ + p7(gmock_p7) {\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + p7##_type p7;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##MatcherP8);\ + };\ + template \ + inline name##MatcherP8 name(p0##_type p0, \ + p1##_type p1, p2##_type p2, p3##_type p3, p4##_type p4, p5##_type p5, \ + p6##_type p6, p7##_type p7) {\ + return name##MatcherP8(p0, p1, p2, p3, p4, p5, \ + p6, p7);\ + }\ + template \ + template \ + bool name##MatcherP8::gmock_Impl::MatchAndExplain(\ + arg_type arg, \ + ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ + const + +#define MATCHER_P9(name, p0, p1, p2, p3, p4, p5, p6, p7, p8, description)\ + template \ + class name##MatcherP9 {\ + public:\ + template \ + class gmock_Impl : public ::testing::MatcherInterface {\ + public:\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \ + p6##_type gmock_p6, p7##_type gmock_p7, p8##_type gmock_p8)\ + : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), p3(gmock_p3), \ + p4(gmock_p4), p5(gmock_p5), p6(gmock_p6), p7(gmock_p7), \ + p8(gmock_p8) {}\ + virtual bool MatchAndExplain(\ + arg_type arg, ::testing::MatchResultListener* result_listener) const;\ + virtual void DescribeTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(false);\ + }\ + virtual void DescribeNegationTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(true);\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + p7##_type p7;\ + p8##_type p8;\ + private:\ + ::testing::internal::string FormatDescription(bool negation) const {\ + const ::testing::internal::string gmock_description = (description);\ + if (!gmock_description.empty())\ + return gmock_description;\ + return ::testing::internal::FormatMatcherDescription(\ + negation, #name, \ + ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ + ::std::tr1::tuple(p0, p1, p2, p3, p4, p5, p6, p7, p8)));\ + }\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template \ + operator ::testing::Matcher() const {\ + return ::testing::Matcher(\ + new gmock_Impl(p0, p1, p2, p3, p4, p5, p6, p7, p8));\ + }\ + name##MatcherP9(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2, p3##_type gmock_p3, p4##_type gmock_p4, \ + p5##_type gmock_p5, p6##_type gmock_p6, p7##_type gmock_p7, \ + p8##_type gmock_p8) : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), \ + p3(gmock_p3), p4(gmock_p4), p5(gmock_p5), p6(gmock_p6), p7(gmock_p7), \ + p8(gmock_p8) {\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + p7##_type p7;\ + p8##_type p8;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##MatcherP9);\ + };\ + template \ + inline name##MatcherP9 name(p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, \ + p4##_type p4, p5##_type p5, p6##_type p6, p7##_type p7, \ + p8##_type p8) {\ + return name##MatcherP9(p0, p1, p2, \ + p3, p4, p5, p6, p7, p8);\ + }\ + template \ + template \ + bool name##MatcherP9::gmock_Impl::MatchAndExplain(\ + arg_type arg, \ + ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ + const + +#define MATCHER_P10(name, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, description)\ + template \ + class name##MatcherP10 {\ + public:\ + template \ + class gmock_Impl : public ::testing::MatcherInterface {\ + public:\ + gmock_Impl(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \ + p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \ + p6##_type gmock_p6, p7##_type gmock_p7, p8##_type gmock_p8, \ + p9##_type gmock_p9)\ + : p0(gmock_p0), p1(gmock_p1), p2(gmock_p2), p3(gmock_p3), \ + p4(gmock_p4), p5(gmock_p5), p6(gmock_p6), p7(gmock_p7), \ + p8(gmock_p8), p9(gmock_p9) {}\ + virtual bool MatchAndExplain(\ + arg_type arg, ::testing::MatchResultListener* result_listener) const;\ + virtual void DescribeTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(false);\ + }\ + virtual void DescribeNegationTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(true);\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + p7##_type p7;\ + p8##_type p8;\ + p9##_type p9;\ + private:\ + ::testing::internal::string FormatDescription(bool negation) const {\ + const ::testing::internal::string gmock_description = (description);\ + if (!gmock_description.empty())\ + return gmock_description;\ + return ::testing::internal::FormatMatcherDescription(\ + negation, #name, \ + ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ + ::std::tr1::tuple(p0, p1, p2, p3, p4, p5, p6, p7, p8, p9)));\ + }\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template \ + operator ::testing::Matcher() const {\ + return ::testing::Matcher(\ + new gmock_Impl(p0, p1, p2, p3, p4, p5, p6, p7, p8, p9));\ + }\ + name##MatcherP10(p0##_type gmock_p0, p1##_type gmock_p1, \ + p2##_type gmock_p2, p3##_type gmock_p3, p4##_type gmock_p4, \ + p5##_type gmock_p5, p6##_type gmock_p6, p7##_type gmock_p7, \ + p8##_type gmock_p8, p9##_type gmock_p9) : p0(gmock_p0), p1(gmock_p1), \ + p2(gmock_p2), p3(gmock_p3), p4(gmock_p4), p5(gmock_p5), p6(gmock_p6), \ + p7(gmock_p7), p8(gmock_p8), p9(gmock_p9) {\ + }\ + p0##_type p0;\ + p1##_type p1;\ + p2##_type p2;\ + p3##_type p3;\ + p4##_type p4;\ + p5##_type p5;\ + p6##_type p6;\ + p7##_type p7;\ + p8##_type p8;\ + p9##_type p9;\ + private:\ + GTEST_DISALLOW_ASSIGN_(name##MatcherP10);\ + };\ + template \ + inline name##MatcherP10 name(p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, \ + p4##_type p4, p5##_type p5, p6##_type p6, p7##_type p7, p8##_type p8, \ + p9##_type p9) {\ + return name##MatcherP10(p0, \ + p1, p2, p3, p4, p5, p6, p7, p8, p9);\ + }\ + template \ + template \ + bool name##MatcherP10::gmock_Impl::MatchAndExplain(\ + arg_type arg, \ + ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ + const + +#endif // GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_MATCHERS_H_ diff --git a/vendor/gmock-1.7.0/include/gmock/gmock-generated-matchers.h.pump b/vendor/gmock-1.7.0/include/gmock/gmock-generated-matchers.h.pump new file mode 100644 index 0000000000..af02acbc16 --- /dev/null +++ b/vendor/gmock-1.7.0/include/gmock/gmock-generated-matchers.h.pump @@ -0,0 +1,674 @@ +$$ -*- mode: c++; -*- +$$ This is a Pump source file. Please use Pump to convert it to +$$ gmock-generated-actions.h. +$$ +$var n = 10 $$ The maximum arity we support. +$$ }} This line fixes auto-indentation of the following code in Emacs. +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Google Mock - a framework for writing C++ mock classes. +// +// This file implements some commonly used variadic matchers. + +#ifndef GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_MATCHERS_H_ +#define GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_MATCHERS_H_ + +#include +#include +#include +#include +#include "gmock/gmock-matchers.h" + +namespace testing { +namespace internal { + +$range i 0..n-1 + +// The type of the i-th (0-based) field of Tuple. +#define GMOCK_FIELD_TYPE_(Tuple, i) \ + typename ::std::tr1::tuple_element::type + +// TupleFields is for selecting fields from a +// tuple of type Tuple. It has two members: +// +// type: a tuple type whose i-th field is the ki-th field of Tuple. +// GetSelectedFields(t): returns fields k0, ..., and kn of t as a tuple. +// +// For example, in class TupleFields, 2, 0>, we have: +// +// type is tuple, and +// GetSelectedFields(make_tuple(true, 'a', 42)) is (42, true). + +template +class TupleFields; + +// This generic version is used when there are $n selectors. +template +class TupleFields { + public: + typedef ::std::tr1::tuple<$for i, [[GMOCK_FIELD_TYPE_(Tuple, k$i)]]> type; + static type GetSelectedFields(const Tuple& t) { + using ::std::tr1::get; + return type($for i, [[get(t)]]); + } +}; + +// The following specialization is used for 0 ~ $(n-1) selectors. + +$for i [[ +$$ }}} +$range j 0..i-1 +$range k 0..n-1 + +template +class TupleFields { + public: + typedef ::std::tr1::tuple<$for j, [[GMOCK_FIELD_TYPE_(Tuple, k$j)]]> type; + static type GetSelectedFields(const Tuple& $if i==0 [[/* t */]] $else [[t]]) { + using ::std::tr1::get; + return type($for j, [[get(t)]]); + } +}; + +]] + +#undef GMOCK_FIELD_TYPE_ + +// Implements the Args() matcher. + +$var ks = [[$for i, [[k$i]]]] +template +class ArgsMatcherImpl : public MatcherInterface { + public: + // ArgsTuple may have top-level const or reference modifiers. + typedef GTEST_REMOVE_REFERENCE_AND_CONST_(ArgsTuple) RawArgsTuple; + typedef typename internal::TupleFields::type SelectedArgs; + typedef Matcher MonomorphicInnerMatcher; + + template + explicit ArgsMatcherImpl(const InnerMatcher& inner_matcher) + : inner_matcher_(SafeMatcherCast(inner_matcher)) {} + + virtual bool MatchAndExplain(ArgsTuple args, + MatchResultListener* listener) const { + const SelectedArgs& selected_args = GetSelectedArgs(args); + if (!listener->IsInterested()) + return inner_matcher_.Matches(selected_args); + + PrintIndices(listener->stream()); + *listener << "are " << PrintToString(selected_args); + + StringMatchResultListener inner_listener; + const bool match = inner_matcher_.MatchAndExplain(selected_args, + &inner_listener); + PrintIfNotEmpty(inner_listener.str(), listener->stream()); + return match; + } + + virtual void DescribeTo(::std::ostream* os) const { + *os << "are a tuple "; + PrintIndices(os); + inner_matcher_.DescribeTo(os); + } + + virtual void DescribeNegationTo(::std::ostream* os) const { + *os << "are a tuple "; + PrintIndices(os); + inner_matcher_.DescribeNegationTo(os); + } + + private: + static SelectedArgs GetSelectedArgs(ArgsTuple args) { + return TupleFields::GetSelectedFields(args); + } + + // Prints the indices of the selected fields. + static void PrintIndices(::std::ostream* os) { + *os << "whose fields ("; + const int indices[$n] = { $ks }; + for (int i = 0; i < $n; i++) { + if (indices[i] < 0) + break; + + if (i >= 1) + *os << ", "; + + *os << "#" << indices[i]; + } + *os << ") "; + } + + const MonomorphicInnerMatcher inner_matcher_; + + GTEST_DISALLOW_ASSIGN_(ArgsMatcherImpl); +}; + +template +class ArgsMatcher { + public: + explicit ArgsMatcher(const InnerMatcher& inner_matcher) + : inner_matcher_(inner_matcher) {} + + template + operator Matcher() const { + return MakeMatcher(new ArgsMatcherImpl(inner_matcher_)); + } + + private: + const InnerMatcher inner_matcher_; + + GTEST_DISALLOW_ASSIGN_(ArgsMatcher); +}; + +// A set of metafunctions for computing the result type of AllOf. +// AllOf(m1, ..., mN) returns +// AllOfResultN::type. + +// Although AllOf isn't defined for one argument, AllOfResult1 is defined +// to simplify the implementation. +template +struct AllOfResult1 { + typedef M1 type; +}; + +$range i 1..n + +$range i 2..n +$for i [[ +$range j 2..i +$var m = i/2 +$range k 1..m +$range t m+1..i + +template +struct AllOfResult$i { + typedef BothOfMatcher< + typename AllOfResult$m<$for k, [[M$k]]>::type, + typename AllOfResult$(i-m)<$for t, [[M$t]]>::type + > type; +}; + +]] + +// A set of metafunctions for computing the result type of AnyOf. +// AnyOf(m1, ..., mN) returns +// AnyOfResultN::type. + +// Although AnyOf isn't defined for one argument, AnyOfResult1 is defined +// to simplify the implementation. +template +struct AnyOfResult1 { + typedef M1 type; +}; + +$range i 1..n + +$range i 2..n +$for i [[ +$range j 2..i +$var m = i/2 +$range k 1..m +$range t m+1..i + +template +struct AnyOfResult$i { + typedef EitherOfMatcher< + typename AnyOfResult$m<$for k, [[M$k]]>::type, + typename AnyOfResult$(i-m)<$for t, [[M$t]]>::type + > type; +}; + +]] + +} // namespace internal + +// Args(a_matcher) matches a tuple if the selected +// fields of it matches a_matcher. C++ doesn't support default +// arguments for function templates, so we have to overload it. + +$range i 0..n +$for i [[ +$range j 1..i +template <$for j [[int k$j, ]]typename InnerMatcher> +inline internal::ArgsMatcher +Args(const InnerMatcher& matcher) { + return internal::ArgsMatcher(matcher); +} + + +]] +// ElementsAre(e_1, e_2, ... e_n) matches an STL-style container with +// n elements, where the i-th element in the container must +// match the i-th argument in the list. Each argument of +// ElementsAre() can be either a value or a matcher. We support up to +// $n arguments. +// +// The use of DecayArray in the implementation allows ElementsAre() +// to accept string literals, whose type is const char[N], but we +// want to treat them as const char*. +// +// NOTE: Since ElementsAre() cares about the order of the elements, it +// must not be used with containers whose elements's order is +// undefined (e.g. hash_map). + +$range i 0..n +$for i [[ + +$range j 1..i + +$if i>0 [[ + +template <$for j, [[typename T$j]]> +]] + +inline internal::ElementsAreMatcher< + std::tr1::tuple< +$for j, [[ + + typename internal::DecayArray::type]]> > +ElementsAre($for j, [[const T$j& e$j]]) { + typedef std::tr1::tuple< +$for j, [[ + + typename internal::DecayArray::type]]> Args; + return internal::ElementsAreMatcher(Args($for j, [[e$j]])); +} + +]] + +// UnorderedElementsAre(e_1, e_2, ..., e_n) is an ElementsAre extension +// that matches n elements in any order. We support up to n=$n arguments. + +$range i 0..n +$for i [[ + +$range j 1..i + +$if i>0 [[ + +template <$for j, [[typename T$j]]> +]] + +inline internal::UnorderedElementsAreMatcher< + std::tr1::tuple< +$for j, [[ + + typename internal::DecayArray::type]]> > +UnorderedElementsAre($for j, [[const T$j& e$j]]) { + typedef std::tr1::tuple< +$for j, [[ + + typename internal::DecayArray::type]]> Args; + return internal::UnorderedElementsAreMatcher(Args($for j, [[e$j]])); +} + +]] + +// AllOf(m1, m2, ..., mk) matches any value that matches all of the given +// sub-matchers. AllOf is called fully qualified to prevent ADL from firing. + +$range i 2..n +$for i [[ +$range j 1..i +$var m = i/2 +$range k 1..m +$range t m+1..i + +template <$for j, [[typename M$j]]> +inline typename internal::AllOfResult$i<$for j, [[M$j]]>::type +AllOf($for j, [[M$j m$j]]) { + return typename internal::AllOfResult$i<$for j, [[M$j]]>::type( + $if m == 1 [[m1]] $else [[::testing::AllOf($for k, [[m$k]])]], + $if m+1 == i [[m$i]] $else [[::testing::AllOf($for t, [[m$t]])]]); +} + +]] + +// AnyOf(m1, m2, ..., mk) matches any value that matches any of the given +// sub-matchers. AnyOf is called fully qualified to prevent ADL from firing. + +$range i 2..n +$for i [[ +$range j 1..i +$var m = i/2 +$range k 1..m +$range t m+1..i + +template <$for j, [[typename M$j]]> +inline typename internal::AnyOfResult$i<$for j, [[M$j]]>::type +AnyOf($for j, [[M$j m$j]]) { + return typename internal::AnyOfResult$i<$for j, [[M$j]]>::type( + $if m == 1 [[m1]] $else [[::testing::AnyOf($for k, [[m$k]])]], + $if m+1 == i [[m$i]] $else [[::testing::AnyOf($for t, [[m$t]])]]); +} + +]] + +} // namespace testing +$$ } // This Pump meta comment fixes auto-indentation in Emacs. It will not +$$ // show up in the generated code. + + +// The MATCHER* family of macros can be used in a namespace scope to +// define custom matchers easily. +// +// Basic Usage +// =========== +// +// The syntax +// +// MATCHER(name, description_string) { statements; } +// +// defines a matcher with the given name that executes the statements, +// which must return a bool to indicate if the match succeeds. Inside +// the statements, you can refer to the value being matched by 'arg', +// and refer to its type by 'arg_type'. +// +// The description string documents what the matcher does, and is used +// to generate the failure message when the match fails. Since a +// MATCHER() is usually defined in a header file shared by multiple +// C++ source files, we require the description to be a C-string +// literal to avoid possible side effects. It can be empty, in which +// case we'll use the sequence of words in the matcher name as the +// description. +// +// For example: +// +// MATCHER(IsEven, "") { return (arg % 2) == 0; } +// +// allows you to write +// +// // Expects mock_foo.Bar(n) to be called where n is even. +// EXPECT_CALL(mock_foo, Bar(IsEven())); +// +// or, +// +// // Verifies that the value of some_expression is even. +// EXPECT_THAT(some_expression, IsEven()); +// +// If the above assertion fails, it will print something like: +// +// Value of: some_expression +// Expected: is even +// Actual: 7 +// +// where the description "is even" is automatically calculated from the +// matcher name IsEven. +// +// Argument Type +// ============= +// +// Note that the type of the value being matched (arg_type) is +// determined by the context in which you use the matcher and is +// supplied to you by the compiler, so you don't need to worry about +// declaring it (nor can you). This allows the matcher to be +// polymorphic. For example, IsEven() can be used to match any type +// where the value of "(arg % 2) == 0" can be implicitly converted to +// a bool. In the "Bar(IsEven())" example above, if method Bar() +// takes an int, 'arg_type' will be int; if it takes an unsigned long, +// 'arg_type' will be unsigned long; and so on. +// +// Parameterizing Matchers +// ======================= +// +// Sometimes you'll want to parameterize the matcher. For that you +// can use another macro: +// +// MATCHER_P(name, param_name, description_string) { statements; } +// +// For example: +// +// MATCHER_P(HasAbsoluteValue, value, "") { return abs(arg) == value; } +// +// will allow you to write: +// +// EXPECT_THAT(Blah("a"), HasAbsoluteValue(n)); +// +// which may lead to this message (assuming n is 10): +// +// Value of: Blah("a") +// Expected: has absolute value 10 +// Actual: -9 +// +// Note that both the matcher description and its parameter are +// printed, making the message human-friendly. +// +// In the matcher definition body, you can write 'foo_type' to +// reference the type of a parameter named 'foo'. For example, in the +// body of MATCHER_P(HasAbsoluteValue, value) above, you can write +// 'value_type' to refer to the type of 'value'. +// +// We also provide MATCHER_P2, MATCHER_P3, ..., up to MATCHER_P$n to +// support multi-parameter matchers. +// +// Describing Parameterized Matchers +// ================================= +// +// The last argument to MATCHER*() is a string-typed expression. The +// expression can reference all of the matcher's parameters and a +// special bool-typed variable named 'negation'. When 'negation' is +// false, the expression should evaluate to the matcher's description; +// otherwise it should evaluate to the description of the negation of +// the matcher. For example, +// +// using testing::PrintToString; +// +// MATCHER_P2(InClosedRange, low, hi, +// string(negation ? "is not" : "is") + " in range [" + +// PrintToString(low) + ", " + PrintToString(hi) + "]") { +// return low <= arg && arg <= hi; +// } +// ... +// EXPECT_THAT(3, InClosedRange(4, 6)); +// EXPECT_THAT(3, Not(InClosedRange(2, 4))); +// +// would generate two failures that contain the text: +// +// Expected: is in range [4, 6] +// ... +// Expected: is not in range [2, 4] +// +// If you specify "" as the description, the failure message will +// contain the sequence of words in the matcher name followed by the +// parameter values printed as a tuple. For example, +// +// MATCHER_P2(InClosedRange, low, hi, "") { ... } +// ... +// EXPECT_THAT(3, InClosedRange(4, 6)); +// EXPECT_THAT(3, Not(InClosedRange(2, 4))); +// +// would generate two failures that contain the text: +// +// Expected: in closed range (4, 6) +// ... +// Expected: not (in closed range (2, 4)) +// +// Types of Matcher Parameters +// =========================== +// +// For the purpose of typing, you can view +// +// MATCHER_Pk(Foo, p1, ..., pk, description_string) { ... } +// +// as shorthand for +// +// template +// FooMatcherPk +// Foo(p1_type p1, ..., pk_type pk) { ... } +// +// When you write Foo(v1, ..., vk), the compiler infers the types of +// the parameters v1, ..., and vk for you. If you are not happy with +// the result of the type inference, you can specify the types by +// explicitly instantiating the template, as in Foo(5, +// false). As said earlier, you don't get to (or need to) specify +// 'arg_type' as that's determined by the context in which the matcher +// is used. You can assign the result of expression Foo(p1, ..., pk) +// to a variable of type FooMatcherPk. This +// can be useful when composing matchers. +// +// While you can instantiate a matcher template with reference types, +// passing the parameters by pointer usually makes your code more +// readable. If, however, you still want to pass a parameter by +// reference, be aware that in the failure message generated by the +// matcher you will see the value of the referenced object but not its +// address. +// +// Explaining Match Results +// ======================== +// +// Sometimes the matcher description alone isn't enough to explain why +// the match has failed or succeeded. For example, when expecting a +// long string, it can be very helpful to also print the diff between +// the expected string and the actual one. To achieve that, you can +// optionally stream additional information to a special variable +// named result_listener, whose type is a pointer to class +// MatchResultListener: +// +// MATCHER_P(EqualsLongString, str, "") { +// if (arg == str) return true; +// +// *result_listener << "the difference: " +/// << DiffStrings(str, arg); +// return false; +// } +// +// Overloading Matchers +// ==================== +// +// You can overload matchers with different numbers of parameters: +// +// MATCHER_P(Blah, a, description_string1) { ... } +// MATCHER_P2(Blah, a, b, description_string2) { ... } +// +// Caveats +// ======= +// +// When defining a new matcher, you should also consider implementing +// MatcherInterface or using MakePolymorphicMatcher(). These +// approaches require more work than the MATCHER* macros, but also +// give you more control on the types of the value being matched and +// the matcher parameters, which may leads to better compiler error +// messages when the matcher is used wrong. They also allow +// overloading matchers based on parameter types (as opposed to just +// based on the number of parameters). +// +// MATCHER*() can only be used in a namespace scope. The reason is +// that C++ doesn't yet allow function-local types to be used to +// instantiate templates. The up-coming C++0x standard will fix this. +// Once that's done, we'll consider supporting using MATCHER*() inside +// a function. +// +// More Information +// ================ +// +// To learn more about using these macros, please search for 'MATCHER' +// on http://code.google.com/p/googlemock/wiki/CookBook. + +$range i 0..n +$for i + +[[ +$var macro_name = [[$if i==0 [[MATCHER]] $elif i==1 [[MATCHER_P]] + $else [[MATCHER_P$i]]]] +$var class_name = [[name##Matcher[[$if i==0 [[]] $elif i==1 [[P]] + $else [[P$i]]]]]] +$range j 0..i-1 +$var template = [[$if i==0 [[]] $else [[ + + template <$for j, [[typename p$j##_type]]>\ +]]]] +$var ctor_param_list = [[$for j, [[p$j##_type gmock_p$j]]]] +$var impl_ctor_param_list = [[$for j, [[p$j##_type gmock_p$j]]]] +$var impl_inits = [[$if i==0 [[]] $else [[ : $for j, [[p$j(gmock_p$j)]]]]]] +$var inits = [[$if i==0 [[]] $else [[ : $for j, [[p$j(gmock_p$j)]]]]]] +$var params = [[$for j, [[p$j]]]] +$var param_types = [[$if i==0 [[]] $else [[<$for j, [[p$j##_type]]>]]]] +$var param_types_and_names = [[$for j, [[p$j##_type p$j]]]] +$var param_field_decls = [[$for j +[[ + + p$j##_type p$j;\ +]]]] +$var param_field_decls2 = [[$for j +[[ + + p$j##_type p$j;\ +]]]] + +#define $macro_name(name$for j [[, p$j]], description)\$template + class $class_name {\ + public:\ + template \ + class gmock_Impl : public ::testing::MatcherInterface {\ + public:\ + [[$if i==1 [[explicit ]]]]gmock_Impl($impl_ctor_param_list)\ + $impl_inits {}\ + virtual bool MatchAndExplain(\ + arg_type arg, ::testing::MatchResultListener* result_listener) const;\ + virtual void DescribeTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(false);\ + }\ + virtual void DescribeNegationTo(::std::ostream* gmock_os) const {\ + *gmock_os << FormatDescription(true);\ + }\$param_field_decls + private:\ + ::testing::internal::string FormatDescription(bool negation) const {\ + const ::testing::internal::string gmock_description = (description);\ + if (!gmock_description.empty())\ + return gmock_description;\ + return ::testing::internal::FormatMatcherDescription(\ + negation, #name, \ + ::testing::internal::UniversalTersePrintTupleFieldsToStrings(\ + ::std::tr1::tuple<$for j, [[p$j##_type]]>($for j, [[p$j]])));\ + }\ + GTEST_DISALLOW_ASSIGN_(gmock_Impl);\ + };\ + template \ + operator ::testing::Matcher() const {\ + return ::testing::Matcher(\ + new gmock_Impl($params));\ + }\ + $class_name($ctor_param_list)$inits {\ + }\$param_field_decls2 + private:\ + GTEST_DISALLOW_ASSIGN_($class_name);\ + };\$template + inline $class_name$param_types name($param_types_and_names) {\ + return $class_name$param_types($params);\ + }\$template + template \ + bool $class_name$param_types::gmock_Impl::MatchAndExplain(\ + arg_type arg, \ + ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_)\ + const +]] + + +#endif // GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_MATCHERS_H_ diff --git a/vendor/gmock-1.7.0/include/gmock/gmock-generated-nice-strict.h b/vendor/gmock-1.7.0/include/gmock/gmock-generated-nice-strict.h new file mode 100644 index 0000000000..4095f4d5bc --- /dev/null +++ b/vendor/gmock-1.7.0/include/gmock/gmock-generated-nice-strict.h @@ -0,0 +1,397 @@ +// This file was GENERATED by command: +// pump.py gmock-generated-nice-strict.h.pump +// DO NOT EDIT BY HAND!!! + +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) + +// Implements class templates NiceMock, NaggyMock, and StrictMock. +// +// Given a mock class MockFoo that is created using Google Mock, +// NiceMock is a subclass of MockFoo that allows +// uninteresting calls (i.e. calls to mock methods that have no +// EXPECT_CALL specs), NaggyMock is a subclass of MockFoo +// that prints a warning when an uninteresting call occurs, and +// StrictMock is a subclass of MockFoo that treats all +// uninteresting calls as errors. +// +// Currently a mock is naggy by default, so MockFoo and +// NaggyMock behave like the same. However, we will soon +// switch the default behavior of mocks to be nice, as that in general +// leads to more maintainable tests. When that happens, MockFoo will +// stop behaving like NaggyMock and start behaving like +// NiceMock. +// +// NiceMock, NaggyMock, and StrictMock "inherit" the constructors of +// their respective base class, with up-to 10 arguments. Therefore +// you can write NiceMock(5, "a") to construct a nice mock +// where MockFoo has a constructor that accepts (int, const char*), +// for example. +// +// A known limitation is that NiceMock, NaggyMock, +// and StrictMock only works for mock methods defined using +// the MOCK_METHOD* family of macros DIRECTLY in the MockFoo class. +// If a mock method is defined in a base class of MockFoo, the "nice" +// or "strict" modifier may not affect it, depending on the compiler. +// In particular, nesting NiceMock, NaggyMock, and StrictMock is NOT +// supported. +// +// Another known limitation is that the constructors of the base mock +// cannot have arguments passed by non-const reference, which are +// banned by the Google C++ style guide anyway. + +#ifndef GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_NICE_STRICT_H_ +#define GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_NICE_STRICT_H_ + +#include "gmock/gmock-spec-builders.h" +#include "gmock/internal/gmock-port.h" + +namespace testing { + +template +class NiceMock : public MockClass { + public: + // We don't factor out the constructor body to a common method, as + // we have to avoid a possible clash with members of MockClass. + NiceMock() { + ::testing::Mock::AllowUninterestingCalls( + internal::ImplicitCast_(this)); + } + + // C++ doesn't (yet) allow inheritance of constructors, so we have + // to define it for each arity. + template + explicit NiceMock(const A1& a1) : MockClass(a1) { + ::testing::Mock::AllowUninterestingCalls( + internal::ImplicitCast_(this)); + } + template + NiceMock(const A1& a1, const A2& a2) : MockClass(a1, a2) { + ::testing::Mock::AllowUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + NiceMock(const A1& a1, const A2& a2, const A3& a3) : MockClass(a1, a2, a3) { + ::testing::Mock::AllowUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + NiceMock(const A1& a1, const A2& a2, const A3& a3, + const A4& a4) : MockClass(a1, a2, a3, a4) { + ::testing::Mock::AllowUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + NiceMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5) : MockClass(a1, a2, a3, a4, a5) { + ::testing::Mock::AllowUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + NiceMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5, const A6& a6) : MockClass(a1, a2, a3, a4, a5, a6) { + ::testing::Mock::AllowUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + NiceMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5, const A6& a6, const A7& a7) : MockClass(a1, a2, a3, a4, a5, + a6, a7) { + ::testing::Mock::AllowUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + NiceMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5, const A6& a6, const A7& a7, const A8& a8) : MockClass(a1, + a2, a3, a4, a5, a6, a7, a8) { + ::testing::Mock::AllowUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + NiceMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5, const A6& a6, const A7& a7, const A8& a8, + const A9& a9) : MockClass(a1, a2, a3, a4, a5, a6, a7, a8, a9) { + ::testing::Mock::AllowUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + NiceMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5, const A6& a6, const A7& a7, const A8& a8, const A9& a9, + const A10& a10) : MockClass(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) { + ::testing::Mock::AllowUninterestingCalls( + internal::ImplicitCast_(this)); + } + + virtual ~NiceMock() { + ::testing::Mock::UnregisterCallReaction( + internal::ImplicitCast_(this)); + } + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(NiceMock); +}; + +template +class NaggyMock : public MockClass { + public: + // We don't factor out the constructor body to a common method, as + // we have to avoid a possible clash with members of MockClass. + NaggyMock() { + ::testing::Mock::WarnUninterestingCalls( + internal::ImplicitCast_(this)); + } + + // C++ doesn't (yet) allow inheritance of constructors, so we have + // to define it for each arity. + template + explicit NaggyMock(const A1& a1) : MockClass(a1) { + ::testing::Mock::WarnUninterestingCalls( + internal::ImplicitCast_(this)); + } + template + NaggyMock(const A1& a1, const A2& a2) : MockClass(a1, a2) { + ::testing::Mock::WarnUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + NaggyMock(const A1& a1, const A2& a2, const A3& a3) : MockClass(a1, a2, a3) { + ::testing::Mock::WarnUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + NaggyMock(const A1& a1, const A2& a2, const A3& a3, + const A4& a4) : MockClass(a1, a2, a3, a4) { + ::testing::Mock::WarnUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + NaggyMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5) : MockClass(a1, a2, a3, a4, a5) { + ::testing::Mock::WarnUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + NaggyMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5, const A6& a6) : MockClass(a1, a2, a3, a4, a5, a6) { + ::testing::Mock::WarnUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + NaggyMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5, const A6& a6, const A7& a7) : MockClass(a1, a2, a3, a4, a5, + a6, a7) { + ::testing::Mock::WarnUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + NaggyMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5, const A6& a6, const A7& a7, const A8& a8) : MockClass(a1, + a2, a3, a4, a5, a6, a7, a8) { + ::testing::Mock::WarnUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + NaggyMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5, const A6& a6, const A7& a7, const A8& a8, + const A9& a9) : MockClass(a1, a2, a3, a4, a5, a6, a7, a8, a9) { + ::testing::Mock::WarnUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + NaggyMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5, const A6& a6, const A7& a7, const A8& a8, const A9& a9, + const A10& a10) : MockClass(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) { + ::testing::Mock::WarnUninterestingCalls( + internal::ImplicitCast_(this)); + } + + virtual ~NaggyMock() { + ::testing::Mock::UnregisterCallReaction( + internal::ImplicitCast_(this)); + } + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(NaggyMock); +}; + +template +class StrictMock : public MockClass { + public: + // We don't factor out the constructor body to a common method, as + // we have to avoid a possible clash with members of MockClass. + StrictMock() { + ::testing::Mock::FailUninterestingCalls( + internal::ImplicitCast_(this)); + } + + // C++ doesn't (yet) allow inheritance of constructors, so we have + // to define it for each arity. + template + explicit StrictMock(const A1& a1) : MockClass(a1) { + ::testing::Mock::FailUninterestingCalls( + internal::ImplicitCast_(this)); + } + template + StrictMock(const A1& a1, const A2& a2) : MockClass(a1, a2) { + ::testing::Mock::FailUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + StrictMock(const A1& a1, const A2& a2, const A3& a3) : MockClass(a1, a2, a3) { + ::testing::Mock::FailUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + StrictMock(const A1& a1, const A2& a2, const A3& a3, + const A4& a4) : MockClass(a1, a2, a3, a4) { + ::testing::Mock::FailUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + StrictMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5) : MockClass(a1, a2, a3, a4, a5) { + ::testing::Mock::FailUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + StrictMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5, const A6& a6) : MockClass(a1, a2, a3, a4, a5, a6) { + ::testing::Mock::FailUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + StrictMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5, const A6& a6, const A7& a7) : MockClass(a1, a2, a3, a4, a5, + a6, a7) { + ::testing::Mock::FailUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + StrictMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5, const A6& a6, const A7& a7, const A8& a8) : MockClass(a1, + a2, a3, a4, a5, a6, a7, a8) { + ::testing::Mock::FailUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + StrictMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5, const A6& a6, const A7& a7, const A8& a8, + const A9& a9) : MockClass(a1, a2, a3, a4, a5, a6, a7, a8, a9) { + ::testing::Mock::FailUninterestingCalls( + internal::ImplicitCast_(this)); + } + + template + StrictMock(const A1& a1, const A2& a2, const A3& a3, const A4& a4, + const A5& a5, const A6& a6, const A7& a7, const A8& a8, const A9& a9, + const A10& a10) : MockClass(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) { + ::testing::Mock::FailUninterestingCalls( + internal::ImplicitCast_(this)); + } + + virtual ~StrictMock() { + ::testing::Mock::UnregisterCallReaction( + internal::ImplicitCast_(this)); + } + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(StrictMock); +}; + +// The following specializations catch some (relatively more common) +// user errors of nesting nice and strict mocks. They do NOT catch +// all possible errors. + +// These specializations are declared but not defined, as NiceMock, +// NaggyMock, and StrictMock cannot be nested. + +template +class NiceMock >; +template +class NiceMock >; +template +class NiceMock >; + +template +class NaggyMock >; +template +class NaggyMock >; +template +class NaggyMock >; + +template +class StrictMock >; +template +class StrictMock >; +template +class StrictMock >; + +} // namespace testing + +#endif // GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_NICE_STRICT_H_ diff --git a/vendor/gmock-1.7.0/include/gmock/gmock-generated-nice-strict.h.pump b/vendor/gmock-1.7.0/include/gmock/gmock-generated-nice-strict.h.pump new file mode 100644 index 0000000000..3ee1ce7f30 --- /dev/null +++ b/vendor/gmock-1.7.0/include/gmock/gmock-generated-nice-strict.h.pump @@ -0,0 +1,161 @@ +$$ -*- mode: c++; -*- +$$ This is a Pump source file. Please use Pump to convert it to +$$ gmock-generated-nice-strict.h. +$$ +$var n = 10 $$ The maximum arity we support. +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) + +// Implements class templates NiceMock, NaggyMock, and StrictMock. +// +// Given a mock class MockFoo that is created using Google Mock, +// NiceMock is a subclass of MockFoo that allows +// uninteresting calls (i.e. calls to mock methods that have no +// EXPECT_CALL specs), NaggyMock is a subclass of MockFoo +// that prints a warning when an uninteresting call occurs, and +// StrictMock is a subclass of MockFoo that treats all +// uninteresting calls as errors. +// +// Currently a mock is naggy by default, so MockFoo and +// NaggyMock behave like the same. However, we will soon +// switch the default behavior of mocks to be nice, as that in general +// leads to more maintainable tests. When that happens, MockFoo will +// stop behaving like NaggyMock and start behaving like +// NiceMock. +// +// NiceMock, NaggyMock, and StrictMock "inherit" the constructors of +// their respective base class, with up-to $n arguments. Therefore +// you can write NiceMock(5, "a") to construct a nice mock +// where MockFoo has a constructor that accepts (int, const char*), +// for example. +// +// A known limitation is that NiceMock, NaggyMock, +// and StrictMock only works for mock methods defined using +// the MOCK_METHOD* family of macros DIRECTLY in the MockFoo class. +// If a mock method is defined in a base class of MockFoo, the "nice" +// or "strict" modifier may not affect it, depending on the compiler. +// In particular, nesting NiceMock, NaggyMock, and StrictMock is NOT +// supported. +// +// Another known limitation is that the constructors of the base mock +// cannot have arguments passed by non-const reference, which are +// banned by the Google C++ style guide anyway. + +#ifndef GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_NICE_STRICT_H_ +#define GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_NICE_STRICT_H_ + +#include "gmock/gmock-spec-builders.h" +#include "gmock/internal/gmock-port.h" + +namespace testing { + +$range kind 0..2 +$for kind [[ + +$var clazz=[[$if kind==0 [[NiceMock]] + $elif kind==1 [[NaggyMock]] + $else [[StrictMock]]]] + +$var method=[[$if kind==0 [[AllowUninterestingCalls]] + $elif kind==1 [[WarnUninterestingCalls]] + $else [[FailUninterestingCalls]]]] + +template +class $clazz : public MockClass { + public: + // We don't factor out the constructor body to a common method, as + // we have to avoid a possible clash with members of MockClass. + $clazz() { + ::testing::Mock::$method( + internal::ImplicitCast_(this)); + } + + // C++ doesn't (yet) allow inheritance of constructors, so we have + // to define it for each arity. + template + explicit $clazz(const A1& a1) : MockClass(a1) { + ::testing::Mock::$method( + internal::ImplicitCast_(this)); + } + +$range i 2..n +$for i [[ +$range j 1..i + template <$for j, [[typename A$j]]> + $clazz($for j, [[const A$j& a$j]]) : MockClass($for j, [[a$j]]) { + ::testing::Mock::$method( + internal::ImplicitCast_(this)); + } + + +]] + virtual ~$clazz() { + ::testing::Mock::UnregisterCallReaction( + internal::ImplicitCast_(this)); + } + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_($clazz); +}; + +]] + +// The following specializations catch some (relatively more common) +// user errors of nesting nice and strict mocks. They do NOT catch +// all possible errors. + +// These specializations are declared but not defined, as NiceMock, +// NaggyMock, and StrictMock cannot be nested. + +template +class NiceMock >; +template +class NiceMock >; +template +class NiceMock >; + +template +class NaggyMock >; +template +class NaggyMock >; +template +class NaggyMock >; + +template +class StrictMock >; +template +class StrictMock >; +template +class StrictMock >; + +} // namespace testing + +#endif // GMOCK_INCLUDE_GMOCK_GMOCK_GENERATED_NICE_STRICT_H_ diff --git a/vendor/gmock-1.7.0/include/gmock/gmock-matchers.h b/vendor/gmock-1.7.0/include/gmock/gmock-matchers.h new file mode 100644 index 0000000000..44055c9355 --- /dev/null +++ b/vendor/gmock-1.7.0/include/gmock/gmock-matchers.h @@ -0,0 +1,3986 @@ +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) + +// Google Mock - a framework for writing C++ mock classes. +// +// This file implements some commonly used argument matchers. More +// matchers can be defined by the user implementing the +// MatcherInterface interface if necessary. + +#ifndef GMOCK_INCLUDE_GMOCK_GMOCK_MATCHERS_H_ +#define GMOCK_INCLUDE_GMOCK_GMOCK_MATCHERS_H_ + +#include +#include +#include +#include +#include // NOLINT +#include +#include +#include +#include + +#include "gmock/internal/gmock-internal-utils.h" +#include "gmock/internal/gmock-port.h" +#include "gtest/gtest.h" + +#if GTEST_LANG_CXX11 +#include // NOLINT -- must be after gtest.h +#endif + +namespace testing { + +// To implement a matcher Foo for type T, define: +// 1. a class FooMatcherImpl that implements the +// MatcherInterface interface, and +// 2. a factory function that creates a Matcher object from a +// FooMatcherImpl*. +// +// The two-level delegation design makes it possible to allow a user +// to write "v" instead of "Eq(v)" where a Matcher is expected, which +// is impossible if we pass matchers by pointers. It also eases +// ownership management as Matcher objects can now be copied like +// plain values. + +// MatchResultListener is an abstract class. Its << operator can be +// used by a matcher to explain why a value matches or doesn't match. +// +// TODO(wan@google.com): add method +// bool InterestedInWhy(bool result) const; +// to indicate whether the listener is interested in why the match +// result is 'result'. +class MatchResultListener { + public: + // Creates a listener object with the given underlying ostream. The + // listener does not own the ostream, and does not dereference it + // in the constructor or destructor. + explicit MatchResultListener(::std::ostream* os) : stream_(os) {} + virtual ~MatchResultListener() = 0; // Makes this class abstract. + + // Streams x to the underlying ostream; does nothing if the ostream + // is NULL. + template + MatchResultListener& operator<<(const T& x) { + if (stream_ != NULL) + *stream_ << x; + return *this; + } + + // Returns the underlying ostream. + ::std::ostream* stream() { return stream_; } + + // Returns true iff the listener is interested in an explanation of + // the match result. A matcher's MatchAndExplain() method can use + // this information to avoid generating the explanation when no one + // intends to hear it. + bool IsInterested() const { return stream_ != NULL; } + + private: + ::std::ostream* const stream_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(MatchResultListener); +}; + +inline MatchResultListener::~MatchResultListener() { +} + +// An instance of a subclass of this knows how to describe itself as a +// matcher. +class MatcherDescriberInterface { + public: + virtual ~MatcherDescriberInterface() {} + + // Describes this matcher to an ostream. The function should print + // a verb phrase that describes the property a value matching this + // matcher should have. The subject of the verb phrase is the value + // being matched. For example, the DescribeTo() method of the Gt(7) + // matcher prints "is greater than 7". + virtual void DescribeTo(::std::ostream* os) const = 0; + + // Describes the negation of this matcher to an ostream. For + // example, if the description of this matcher is "is greater than + // 7", the negated description could be "is not greater than 7". + // You are not required to override this when implementing + // MatcherInterface, but it is highly advised so that your matcher + // can produce good error messages. + virtual void DescribeNegationTo(::std::ostream* os) const { + *os << "not ("; + DescribeTo(os); + *os << ")"; + } +}; + +// The implementation of a matcher. +template +class MatcherInterface : public MatcherDescriberInterface { + public: + // Returns true iff the matcher matches x; also explains the match + // result to 'listener' if necessary (see the next paragraph), in + // the form of a non-restrictive relative clause ("which ...", + // "whose ...", etc) that describes x. For example, the + // MatchAndExplain() method of the Pointee(...) matcher should + // generate an explanation like "which points to ...". + // + // Implementations of MatchAndExplain() should add an explanation of + // the match result *if and only if* they can provide additional + // information that's not already present (or not obvious) in the + // print-out of x and the matcher's description. Whether the match + // succeeds is not a factor in deciding whether an explanation is + // needed, as sometimes the caller needs to print a failure message + // when the match succeeds (e.g. when the matcher is used inside + // Not()). + // + // For example, a "has at least 10 elements" matcher should explain + // what the actual element count is, regardless of the match result, + // as it is useful information to the reader; on the other hand, an + // "is empty" matcher probably only needs to explain what the actual + // size is when the match fails, as it's redundant to say that the + // size is 0 when the value is already known to be empty. + // + // You should override this method when defining a new matcher. + // + // It's the responsibility of the caller (Google Mock) to guarantee + // that 'listener' is not NULL. This helps to simplify a matcher's + // implementation when it doesn't care about the performance, as it + // can talk to 'listener' without checking its validity first. + // However, in order to implement dummy listeners efficiently, + // listener->stream() may be NULL. + virtual bool MatchAndExplain(T x, MatchResultListener* listener) const = 0; + + // Inherits these methods from MatcherDescriberInterface: + // virtual void DescribeTo(::std::ostream* os) const = 0; + // virtual void DescribeNegationTo(::std::ostream* os) const; +}; + +// A match result listener that stores the explanation in a string. +class StringMatchResultListener : public MatchResultListener { + public: + StringMatchResultListener() : MatchResultListener(&ss_) {} + + // Returns the explanation accumulated so far. + internal::string str() const { return ss_.str(); } + + // Clears the explanation accumulated so far. + void Clear() { ss_.str(""); } + + private: + ::std::stringstream ss_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(StringMatchResultListener); +}; + +namespace internal { + +// A match result listener that ignores the explanation. +class DummyMatchResultListener : public MatchResultListener { + public: + DummyMatchResultListener() : MatchResultListener(NULL) {} + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(DummyMatchResultListener); +}; + +// A match result listener that forwards the explanation to a given +// ostream. The difference between this and MatchResultListener is +// that the former is concrete. +class StreamMatchResultListener : public MatchResultListener { + public: + explicit StreamMatchResultListener(::std::ostream* os) + : MatchResultListener(os) {} + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(StreamMatchResultListener); +}; + +// An internal class for implementing Matcher, which will derive +// from it. We put functionalities common to all Matcher +// specializations here to avoid code duplication. +template +class MatcherBase { + public: + // Returns true iff the matcher matches x; also explains the match + // result to 'listener'. + bool MatchAndExplain(T x, MatchResultListener* listener) const { + return impl_->MatchAndExplain(x, listener); + } + + // Returns true iff this matcher matches x. + bool Matches(T x) const { + DummyMatchResultListener dummy; + return MatchAndExplain(x, &dummy); + } + + // Describes this matcher to an ostream. + void DescribeTo(::std::ostream* os) const { impl_->DescribeTo(os); } + + // Describes the negation of this matcher to an ostream. + void DescribeNegationTo(::std::ostream* os) const { + impl_->DescribeNegationTo(os); + } + + // Explains why x matches, or doesn't match, the matcher. + void ExplainMatchResultTo(T x, ::std::ostream* os) const { + StreamMatchResultListener listener(os); + MatchAndExplain(x, &listener); + } + + // Returns the describer for this matcher object; retains ownership + // of the describer, which is only guaranteed to be alive when + // this matcher object is alive. + const MatcherDescriberInterface* GetDescriber() const { + return impl_.get(); + } + + protected: + MatcherBase() {} + + // Constructs a matcher from its implementation. + explicit MatcherBase(const MatcherInterface* impl) + : impl_(impl) {} + + virtual ~MatcherBase() {} + + private: + // shared_ptr (util/gtl/shared_ptr.h) and linked_ptr have similar + // interfaces. The former dynamically allocates a chunk of memory + // to hold the reference count, while the latter tracks all + // references using a circular linked list without allocating + // memory. It has been observed that linked_ptr performs better in + // typical scenarios. However, shared_ptr can out-perform + // linked_ptr when there are many more uses of the copy constructor + // than the default constructor. + // + // If performance becomes a problem, we should see if using + // shared_ptr helps. + ::testing::internal::linked_ptr > impl_; +}; + +} // namespace internal + +// A Matcher is a copyable and IMMUTABLE (except by assignment) +// object that can check whether a value of type T matches. The +// implementation of Matcher is just a linked_ptr to const +// MatcherInterface, so copying is fairly cheap. Don't inherit +// from Matcher! +template +class Matcher : public internal::MatcherBase { + public: + // Constructs a null matcher. Needed for storing Matcher objects in STL + // containers. A default-constructed matcher is not yet initialized. You + // cannot use it until a valid value has been assigned to it. + Matcher() {} + + // Constructs a matcher from its implementation. + explicit Matcher(const MatcherInterface* impl) + : internal::MatcherBase(impl) {} + + // Implicit constructor here allows people to write + // EXPECT_CALL(foo, Bar(5)) instead of EXPECT_CALL(foo, Bar(Eq(5))) sometimes + Matcher(T value); // NOLINT +}; + +// The following two specializations allow the user to write str +// instead of Eq(str) and "foo" instead of Eq("foo") when a string +// matcher is expected. +template <> +class GTEST_API_ Matcher + : public internal::MatcherBase { + public: + Matcher() {} + + explicit Matcher(const MatcherInterface* impl) + : internal::MatcherBase(impl) {} + + // Allows the user to write str instead of Eq(str) sometimes, where + // str is a string object. + Matcher(const internal::string& s); // NOLINT + + // Allows the user to write "foo" instead of Eq("foo") sometimes. + Matcher(const char* s); // NOLINT +}; + +template <> +class GTEST_API_ Matcher + : public internal::MatcherBase { + public: + Matcher() {} + + explicit Matcher(const MatcherInterface* impl) + : internal::MatcherBase(impl) {} + + // Allows the user to write str instead of Eq(str) sometimes, where + // str is a string object. + Matcher(const internal::string& s); // NOLINT + + // Allows the user to write "foo" instead of Eq("foo") sometimes. + Matcher(const char* s); // NOLINT +}; + +#if GTEST_HAS_STRING_PIECE_ +// The following two specializations allow the user to write str +// instead of Eq(str) and "foo" instead of Eq("foo") when a StringPiece +// matcher is expected. +template <> +class GTEST_API_ Matcher + : public internal::MatcherBase { + public: + Matcher() {} + + explicit Matcher(const MatcherInterface* impl) + : internal::MatcherBase(impl) {} + + // Allows the user to write str instead of Eq(str) sometimes, where + // str is a string object. + Matcher(const internal::string& s); // NOLINT + + // Allows the user to write "foo" instead of Eq("foo") sometimes. + Matcher(const char* s); // NOLINT + + // Allows the user to pass StringPieces directly. + Matcher(StringPiece s); // NOLINT +}; + +template <> +class GTEST_API_ Matcher + : public internal::MatcherBase { + public: + Matcher() {} + + explicit Matcher(const MatcherInterface* impl) + : internal::MatcherBase(impl) {} + + // Allows the user to write str instead of Eq(str) sometimes, where + // str is a string object. + Matcher(const internal::string& s); // NOLINT + + // Allows the user to write "foo" instead of Eq("foo") sometimes. + Matcher(const char* s); // NOLINT + + // Allows the user to pass StringPieces directly. + Matcher(StringPiece s); // NOLINT +}; +#endif // GTEST_HAS_STRING_PIECE_ + +// The PolymorphicMatcher class template makes it easy to implement a +// polymorphic matcher (i.e. a matcher that can match values of more +// than one type, e.g. Eq(n) and NotNull()). +// +// To define a polymorphic matcher, a user should provide an Impl +// class that has a DescribeTo() method and a DescribeNegationTo() +// method, and define a member function (or member function template) +// +// bool MatchAndExplain(const Value& value, +// MatchResultListener* listener) const; +// +// See the definition of NotNull() for a complete example. +template +class PolymorphicMatcher { + public: + explicit PolymorphicMatcher(const Impl& an_impl) : impl_(an_impl) {} + + // Returns a mutable reference to the underlying matcher + // implementation object. + Impl& mutable_impl() { return impl_; } + + // Returns an immutable reference to the underlying matcher + // implementation object. + const Impl& impl() const { return impl_; } + + template + operator Matcher() const { + return Matcher(new MonomorphicImpl(impl_)); + } + + private: + template + class MonomorphicImpl : public MatcherInterface { + public: + explicit MonomorphicImpl(const Impl& impl) : impl_(impl) {} + + virtual void DescribeTo(::std::ostream* os) const { + impl_.DescribeTo(os); + } + + virtual void DescribeNegationTo(::std::ostream* os) const { + impl_.DescribeNegationTo(os); + } + + virtual bool MatchAndExplain(T x, MatchResultListener* listener) const { + return impl_.MatchAndExplain(x, listener); + } + + private: + const Impl impl_; + + GTEST_DISALLOW_ASSIGN_(MonomorphicImpl); + }; + + Impl impl_; + + GTEST_DISALLOW_ASSIGN_(PolymorphicMatcher); +}; + +// Creates a matcher from its implementation. This is easier to use +// than the Matcher constructor as it doesn't require you to +// explicitly write the template argument, e.g. +// +// MakeMatcher(foo); +// vs +// Matcher(foo); +template +inline Matcher MakeMatcher(const MatcherInterface* impl) { + return Matcher(impl); +} + +// Creates a polymorphic matcher from its implementation. This is +// easier to use than the PolymorphicMatcher constructor as it +// doesn't require you to explicitly write the template argument, e.g. +// +// MakePolymorphicMatcher(foo); +// vs +// PolymorphicMatcher(foo); +template +inline PolymorphicMatcher MakePolymorphicMatcher(const Impl& impl) { + return PolymorphicMatcher(impl); +} + +// Anything inside the 'internal' namespace IS INTERNAL IMPLEMENTATION +// and MUST NOT BE USED IN USER CODE!!! +namespace internal { + +// The MatcherCastImpl class template is a helper for implementing +// MatcherCast(). We need this helper in order to partially +// specialize the implementation of MatcherCast() (C++ allows +// class/struct templates to be partially specialized, but not +// function templates.). + +// This general version is used when MatcherCast()'s argument is a +// polymorphic matcher (i.e. something that can be converted to a +// Matcher but is not one yet; for example, Eq(value)) or a value (for +// example, "hello"). +template +class MatcherCastImpl { + public: + static Matcher Cast(M polymorphic_matcher_or_value) { + // M can be a polymorhic matcher, in which case we want to use + // its conversion operator to create Matcher. Or it can be a value + // that should be passed to the Matcher's constructor. + // + // We can't call Matcher(polymorphic_matcher_or_value) when M is a + // polymorphic matcher because it'll be ambiguous if T has an implicit + // constructor from M (this usually happens when T has an implicit + // constructor from any type). + // + // It won't work to unconditionally implict_cast + // polymorphic_matcher_or_value to Matcher because it won't trigger + // a user-defined conversion from M to T if one exists (assuming M is + // a value). + return CastImpl( + polymorphic_matcher_or_value, + BooleanConstant< + internal::ImplicitlyConvertible >::value>()); + } + + private: + static Matcher CastImpl(M value, BooleanConstant) { + // M can't be implicitly converted to Matcher, so M isn't a polymorphic + // matcher. It must be a value then. Use direct initialization to create + // a matcher. + return Matcher(ImplicitCast_(value)); + } + + static Matcher CastImpl(M polymorphic_matcher_or_value, + BooleanConstant) { + // M is implicitly convertible to Matcher, which means that either + // M is a polymorhpic matcher or Matcher has an implicit constructor + // from M. In both cases using the implicit conversion will produce a + // matcher. + // + // Even if T has an implicit constructor from M, it won't be called because + // creating Matcher would require a chain of two user-defined conversions + // (first to create T from M and then to create Matcher from T). + return polymorphic_matcher_or_value; + } +}; + +// This more specialized version is used when MatcherCast()'s argument +// is already a Matcher. This only compiles when type T can be +// statically converted to type U. +template +class MatcherCastImpl > { + public: + static Matcher Cast(const Matcher& source_matcher) { + return Matcher(new Impl(source_matcher)); + } + + private: + class Impl : public MatcherInterface { + public: + explicit Impl(const Matcher& source_matcher) + : source_matcher_(source_matcher) {} + + // We delegate the matching logic to the source matcher. + virtual bool MatchAndExplain(T x, MatchResultListener* listener) const { + return source_matcher_.MatchAndExplain(static_cast(x), listener); + } + + virtual void DescribeTo(::std::ostream* os) const { + source_matcher_.DescribeTo(os); + } + + virtual void DescribeNegationTo(::std::ostream* os) const { + source_matcher_.DescribeNegationTo(os); + } + + private: + const Matcher source_matcher_; + + GTEST_DISALLOW_ASSIGN_(Impl); + }; +}; + +// This even more specialized version is used for efficiently casting +// a matcher to its own type. +template +class MatcherCastImpl > { + public: + static Matcher Cast(const Matcher& matcher) { return matcher; } +}; + +} // namespace internal + +// In order to be safe and clear, casting between different matcher +// types is done explicitly via MatcherCast(m), which takes a +// matcher m and returns a Matcher. It compiles only when T can be +// statically converted to the argument type of m. +template +inline Matcher MatcherCast(M matcher) { + return internal::MatcherCastImpl::Cast(matcher); +} + +// Implements SafeMatcherCast(). +// +// We use an intermediate class to do the actual safe casting as Nokia's +// Symbian compiler cannot decide between +// template ... (M) and +// template ... (const Matcher&) +// for function templates but can for member function templates. +template +class SafeMatcherCastImpl { + public: + // This overload handles polymorphic matchers and values only since + // monomorphic matchers are handled by the next one. + template + static inline Matcher Cast(M polymorphic_matcher_or_value) { + return internal::MatcherCastImpl::Cast(polymorphic_matcher_or_value); + } + + // This overload handles monomorphic matchers. + // + // In general, if type T can be implicitly converted to type U, we can + // safely convert a Matcher to a Matcher (i.e. Matcher is + // contravariant): just keep a copy of the original Matcher, convert the + // argument from type T to U, and then pass it to the underlying Matcher. + // The only exception is when U is a reference and T is not, as the + // underlying Matcher may be interested in the argument's address, which + // is not preserved in the conversion from T to U. + template + static inline Matcher Cast(const Matcher& matcher) { + // Enforce that T can be implicitly converted to U. + GTEST_COMPILE_ASSERT_((internal::ImplicitlyConvertible::value), + T_must_be_implicitly_convertible_to_U); + // Enforce that we are not converting a non-reference type T to a reference + // type U. + GTEST_COMPILE_ASSERT_( + internal::is_reference::value || !internal::is_reference::value, + cannot_convert_non_referentce_arg_to_reference); + // In case both T and U are arithmetic types, enforce that the + // conversion is not lossy. + typedef GTEST_REMOVE_REFERENCE_AND_CONST_(T) RawT; + typedef GTEST_REMOVE_REFERENCE_AND_CONST_(U) RawU; + const bool kTIsOther = GMOCK_KIND_OF_(RawT) == internal::kOther; + const bool kUIsOther = GMOCK_KIND_OF_(RawU) == internal::kOther; + GTEST_COMPILE_ASSERT_( + kTIsOther || kUIsOther || + (internal::LosslessArithmeticConvertible::value), + conversion_of_arithmetic_types_must_be_lossless); + return MatcherCast(matcher); + } +}; + +template +inline Matcher SafeMatcherCast(const M& polymorphic_matcher) { + return SafeMatcherCastImpl::Cast(polymorphic_matcher); +} + +// A() returns a matcher that matches any value of type T. +template +Matcher A(); + +// Anything inside the 'internal' namespace IS INTERNAL IMPLEMENTATION +// and MUST NOT BE USED IN USER CODE!!! +namespace internal { + +// If the explanation is not empty, prints it to the ostream. +inline void PrintIfNotEmpty(const internal::string& explanation, + ::std::ostream* os) { + if (explanation != "" && os != NULL) { + *os << ", " << explanation; + } +} + +// Returns true if the given type name is easy to read by a human. +// This is used to decide whether printing the type of a value might +// be helpful. +inline bool IsReadableTypeName(const string& type_name) { + // We consider a type name readable if it's short or doesn't contain + // a template or function type. + return (type_name.length() <= 20 || + type_name.find_first_of("<(") == string::npos); +} + +// Matches the value against the given matcher, prints the value and explains +// the match result to the listener. Returns the match result. +// 'listener' must not be NULL. +// Value cannot be passed by const reference, because some matchers take a +// non-const argument. +template +bool MatchPrintAndExplain(Value& value, const Matcher& matcher, + MatchResultListener* listener) { + if (!listener->IsInterested()) { + // If the listener is not interested, we do not need to construct the + // inner explanation. + return matcher.Matches(value); + } + + StringMatchResultListener inner_listener; + const bool match = matcher.MatchAndExplain(value, &inner_listener); + + UniversalPrint(value, listener->stream()); +#if GTEST_HAS_RTTI + const string& type_name = GetTypeName(); + if (IsReadableTypeName(type_name)) + *listener->stream() << " (of type " << type_name << ")"; +#endif + PrintIfNotEmpty(inner_listener.str(), listener->stream()); + + return match; +} + +// An internal helper class for doing compile-time loop on a tuple's +// fields. +template +class TuplePrefix { + public: + // TuplePrefix::Matches(matcher_tuple, value_tuple) returns true + // iff the first N fields of matcher_tuple matches the first N + // fields of value_tuple, respectively. + template + static bool Matches(const MatcherTuple& matcher_tuple, + const ValueTuple& value_tuple) { + using ::std::tr1::get; + return TuplePrefix::Matches(matcher_tuple, value_tuple) + && get(matcher_tuple).Matches(get(value_tuple)); + } + + // TuplePrefix::ExplainMatchFailuresTo(matchers, values, os) + // describes failures in matching the first N fields of matchers + // against the first N fields of values. If there is no failure, + // nothing will be streamed to os. + template + static void ExplainMatchFailuresTo(const MatcherTuple& matchers, + const ValueTuple& values, + ::std::ostream* os) { + using ::std::tr1::tuple_element; + using ::std::tr1::get; + + // First, describes failures in the first N - 1 fields. + TuplePrefix::ExplainMatchFailuresTo(matchers, values, os); + + // Then describes the failure (if any) in the (N - 1)-th (0-based) + // field. + typename tuple_element::type matcher = + get(matchers); + typedef typename tuple_element::type Value; + Value value = get(values); + StringMatchResultListener listener; + if (!matcher.MatchAndExplain(value, &listener)) { + // TODO(wan): include in the message the name of the parameter + // as used in MOCK_METHOD*() when possible. + *os << " Expected arg #" << N - 1 << ": "; + get(matchers).DescribeTo(os); + *os << "\n Actual: "; + // We remove the reference in type Value to prevent the + // universal printer from printing the address of value, which + // isn't interesting to the user most of the time. The + // matcher's MatchAndExplain() method handles the case when + // the address is interesting. + internal::UniversalPrint(value, os); + PrintIfNotEmpty(listener.str(), os); + *os << "\n"; + } + } +}; + +// The base case. +template <> +class TuplePrefix<0> { + public: + template + static bool Matches(const MatcherTuple& /* matcher_tuple */, + const ValueTuple& /* value_tuple */) { + return true; + } + + template + static void ExplainMatchFailuresTo(const MatcherTuple& /* matchers */, + const ValueTuple& /* values */, + ::std::ostream* /* os */) {} +}; + +// TupleMatches(matcher_tuple, value_tuple) returns true iff all +// matchers in matcher_tuple match the corresponding fields in +// value_tuple. It is a compiler error if matcher_tuple and +// value_tuple have different number of fields or incompatible field +// types. +template +bool TupleMatches(const MatcherTuple& matcher_tuple, + const ValueTuple& value_tuple) { + using ::std::tr1::tuple_size; + // Makes sure that matcher_tuple and value_tuple have the same + // number of fields. + GTEST_COMPILE_ASSERT_(tuple_size::value == + tuple_size::value, + matcher_and_value_have_different_numbers_of_fields); + return TuplePrefix::value>:: + Matches(matcher_tuple, value_tuple); +} + +// Describes failures in matching matchers against values. If there +// is no failure, nothing will be streamed to os. +template +void ExplainMatchFailureTupleTo(const MatcherTuple& matchers, + const ValueTuple& values, + ::std::ostream* os) { + using ::std::tr1::tuple_size; + TuplePrefix::value>::ExplainMatchFailuresTo( + matchers, values, os); +} + +// TransformTupleValues and its helper. +// +// TransformTupleValuesHelper hides the internal machinery that +// TransformTupleValues uses to implement a tuple traversal. +template +class TransformTupleValuesHelper { + private: + typedef typename ::std::tr1::tuple_size TupleSize; + + public: + // For each member of tuple 't', taken in order, evaluates '*out++ = f(t)'. + // Returns the final value of 'out' in case the caller needs it. + static OutIter Run(Func f, const Tuple& t, OutIter out) { + return IterateOverTuple()(f, t, out); + } + + private: + template + struct IterateOverTuple { + OutIter operator() (Func f, const Tup& t, OutIter out) const { + *out++ = f(::std::tr1::get(t)); + return IterateOverTuple()(f, t, out); + } + }; + template + struct IterateOverTuple { + OutIter operator() (Func /* f */, const Tup& /* t */, OutIter out) const { + return out; + } + }; +}; + +// Successively invokes 'f(element)' on each element of the tuple 't', +// appending each result to the 'out' iterator. Returns the final value +// of 'out'. +template +OutIter TransformTupleValues(Func f, const Tuple& t, OutIter out) { + return TransformTupleValuesHelper::Run(f, t, out); +} + +// Implements A(). +template +class AnyMatcherImpl : public MatcherInterface { + public: + virtual bool MatchAndExplain( + T /* x */, MatchResultListener* /* listener */) const { return true; } + virtual void DescribeTo(::std::ostream* os) const { *os << "is anything"; } + virtual void DescribeNegationTo(::std::ostream* os) const { + // This is mostly for completeness' safe, as it's not very useful + // to write Not(A()). However we cannot completely rule out + // such a possibility, and it doesn't hurt to be prepared. + *os << "never matches"; + } +}; + +// Implements _, a matcher that matches any value of any +// type. This is a polymorphic matcher, so we need a template type +// conversion operator to make it appearing as a Matcher for any +// type T. +class AnythingMatcher { + public: + template + operator Matcher() const { return A(); } +}; + +// Implements a matcher that compares a given value with a +// pre-supplied value using one of the ==, <=, <, etc, operators. The +// two values being compared don't have to have the same type. +// +// The matcher defined here is polymorphic (for example, Eq(5) can be +// used to match an int, a short, a double, etc). Therefore we use +// a template type conversion operator in the implementation. +// +// We define this as a macro in order to eliminate duplicated source +// code. +// +// The following template definition assumes that the Rhs parameter is +// a "bare" type (i.e. neither 'const T' nor 'T&'). +#define GMOCK_IMPLEMENT_COMPARISON_MATCHER_( \ + name, op, relation, negated_relation) \ + template class name##Matcher { \ + public: \ + explicit name##Matcher(const Rhs& rhs) : rhs_(rhs) {} \ + template \ + operator Matcher() const { \ + return MakeMatcher(new Impl(rhs_)); \ + } \ + private: \ + template \ + class Impl : public MatcherInterface { \ + public: \ + explicit Impl(const Rhs& rhs) : rhs_(rhs) {} \ + virtual bool MatchAndExplain(\ + Lhs lhs, MatchResultListener* /* listener */) const { \ + return lhs op rhs_; \ + } \ + virtual void DescribeTo(::std::ostream* os) const { \ + *os << relation " "; \ + UniversalPrint(rhs_, os); \ + } \ + virtual void DescribeNegationTo(::std::ostream* os) const { \ + *os << negated_relation " "; \ + UniversalPrint(rhs_, os); \ + } \ + private: \ + Rhs rhs_; \ + GTEST_DISALLOW_ASSIGN_(Impl); \ + }; \ + Rhs rhs_; \ + GTEST_DISALLOW_ASSIGN_(name##Matcher); \ + } + +// Implements Eq(v), Ge(v), Gt(v), Le(v), Lt(v), and Ne(v) +// respectively. +GMOCK_IMPLEMENT_COMPARISON_MATCHER_(Eq, ==, "is equal to", "isn't equal to"); +GMOCK_IMPLEMENT_COMPARISON_MATCHER_(Ge, >=, "is >=", "isn't >="); +GMOCK_IMPLEMENT_COMPARISON_MATCHER_(Gt, >, "is >", "isn't >"); +GMOCK_IMPLEMENT_COMPARISON_MATCHER_(Le, <=, "is <=", "isn't <="); +GMOCK_IMPLEMENT_COMPARISON_MATCHER_(Lt, <, "is <", "isn't <"); +GMOCK_IMPLEMENT_COMPARISON_MATCHER_(Ne, !=, "isn't equal to", "is equal to"); + +#undef GMOCK_IMPLEMENT_COMPARISON_MATCHER_ + +// Implements the polymorphic IsNull() matcher, which matches any raw or smart +// pointer that is NULL. +class IsNullMatcher { + public: + template + bool MatchAndExplain(const Pointer& p, + MatchResultListener* /* listener */) const { + return GetRawPointer(p) == NULL; + } + + void DescribeTo(::std::ostream* os) const { *os << "is NULL"; } + void DescribeNegationTo(::std::ostream* os) const { + *os << "isn't NULL"; + } +}; + +// Implements the polymorphic NotNull() matcher, which matches any raw or smart +// pointer that is not NULL. +class NotNullMatcher { + public: + template + bool MatchAndExplain(const Pointer& p, + MatchResultListener* /* listener */) const { + return GetRawPointer(p) != NULL; + } + + void DescribeTo(::std::ostream* os) const { *os << "isn't NULL"; } + void DescribeNegationTo(::std::ostream* os) const { + *os << "is NULL"; + } +}; + +// Ref(variable) matches any argument that is a reference to +// 'variable'. This matcher is polymorphic as it can match any +// super type of the type of 'variable'. +// +// The RefMatcher template class implements Ref(variable). It can +// only be instantiated with a reference type. This prevents a user +// from mistakenly using Ref(x) to match a non-reference function +// argument. For example, the following will righteously cause a +// compiler error: +// +// int n; +// Matcher m1 = Ref(n); // This won't compile. +// Matcher m2 = Ref(n); // This will compile. +template +class RefMatcher; + +template +class RefMatcher { + // Google Mock is a generic framework and thus needs to support + // mocking any function types, including those that take non-const + // reference arguments. Therefore the template parameter T (and + // Super below) can be instantiated to either a const type or a + // non-const type. + public: + // RefMatcher() takes a T& instead of const T&, as we want the + // compiler to catch using Ref(const_value) as a matcher for a + // non-const reference. + explicit RefMatcher(T& x) : object_(x) {} // NOLINT + + template + operator Matcher() const { + // By passing object_ (type T&) to Impl(), which expects a Super&, + // we make sure that Super is a super type of T. In particular, + // this catches using Ref(const_value) as a matcher for a + // non-const reference, as you cannot implicitly convert a const + // reference to a non-const reference. + return MakeMatcher(new Impl(object_)); + } + + private: + template + class Impl : public MatcherInterface { + public: + explicit Impl(Super& x) : object_(x) {} // NOLINT + + // MatchAndExplain() takes a Super& (as opposed to const Super&) + // in order to match the interface MatcherInterface. + virtual bool MatchAndExplain( + Super& x, MatchResultListener* listener) const { + *listener << "which is located @" << static_cast(&x); + return &x == &object_; + } + + virtual void DescribeTo(::std::ostream* os) const { + *os << "references the variable "; + UniversalPrinter::Print(object_, os); + } + + virtual void DescribeNegationTo(::std::ostream* os) const { + *os << "does not reference the variable "; + UniversalPrinter::Print(object_, os); + } + + private: + const Super& object_; + + GTEST_DISALLOW_ASSIGN_(Impl); + }; + + T& object_; + + GTEST_DISALLOW_ASSIGN_(RefMatcher); +}; + +// Polymorphic helper functions for narrow and wide string matchers. +inline bool CaseInsensitiveCStringEquals(const char* lhs, const char* rhs) { + return String::CaseInsensitiveCStringEquals(lhs, rhs); +} + +inline bool CaseInsensitiveCStringEquals(const wchar_t* lhs, + const wchar_t* rhs) { + return String::CaseInsensitiveWideCStringEquals(lhs, rhs); +} + +// String comparison for narrow or wide strings that can have embedded NUL +// characters. +template +bool CaseInsensitiveStringEquals(const StringType& s1, + const StringType& s2) { + // Are the heads equal? + if (!CaseInsensitiveCStringEquals(s1.c_str(), s2.c_str())) { + return false; + } + + // Skip the equal heads. + const typename StringType::value_type nul = 0; + const size_t i1 = s1.find(nul), i2 = s2.find(nul); + + // Are we at the end of either s1 or s2? + if (i1 == StringType::npos || i2 == StringType::npos) { + return i1 == i2; + } + + // Are the tails equal? + return CaseInsensitiveStringEquals(s1.substr(i1 + 1), s2.substr(i2 + 1)); +} + +// String matchers. + +// Implements equality-based string matchers like StrEq, StrCaseNe, and etc. +template +class StrEqualityMatcher { + public: + StrEqualityMatcher(const StringType& str, bool expect_eq, + bool case_sensitive) + : string_(str), expect_eq_(expect_eq), case_sensitive_(case_sensitive) {} + + // Accepts pointer types, particularly: + // const char* + // char* + // const wchar_t* + // wchar_t* + template + bool MatchAndExplain(CharType* s, MatchResultListener* listener) const { + if (s == NULL) { + return !expect_eq_; + } + return MatchAndExplain(StringType(s), listener); + } + + // Matches anything that can convert to StringType. + // + // This is a template, not just a plain function with const StringType&, + // because StringPiece has some interfering non-explicit constructors. + template + bool MatchAndExplain(const MatcheeStringType& s, + MatchResultListener* /* listener */) const { + const StringType& s2(s); + const bool eq = case_sensitive_ ? s2 == string_ : + CaseInsensitiveStringEquals(s2, string_); + return expect_eq_ == eq; + } + + void DescribeTo(::std::ostream* os) const { + DescribeToHelper(expect_eq_, os); + } + + void DescribeNegationTo(::std::ostream* os) const { + DescribeToHelper(!expect_eq_, os); + } + + private: + void DescribeToHelper(bool expect_eq, ::std::ostream* os) const { + *os << (expect_eq ? "is " : "isn't "); + *os << "equal to "; + if (!case_sensitive_) { + *os << "(ignoring case) "; + } + UniversalPrint(string_, os); + } + + const StringType string_; + const bool expect_eq_; + const bool case_sensitive_; + + GTEST_DISALLOW_ASSIGN_(StrEqualityMatcher); +}; + +// Implements the polymorphic HasSubstr(substring) matcher, which +// can be used as a Matcher as long as T can be converted to a +// string. +template +class HasSubstrMatcher { + public: + explicit HasSubstrMatcher(const StringType& substring) + : substring_(substring) {} + + // Accepts pointer types, particularly: + // const char* + // char* + // const wchar_t* + // wchar_t* + template + bool MatchAndExplain(CharType* s, MatchResultListener* listener) const { + return s != NULL && MatchAndExplain(StringType(s), listener); + } + + // Matches anything that can convert to StringType. + // + // This is a template, not just a plain function with const StringType&, + // because StringPiece has some interfering non-explicit constructors. + template + bool MatchAndExplain(const MatcheeStringType& s, + MatchResultListener* /* listener */) const { + const StringType& s2(s); + return s2.find(substring_) != StringType::npos; + } + + // Describes what this matcher matches. + void DescribeTo(::std::ostream* os) const { + *os << "has substring "; + UniversalPrint(substring_, os); + } + + void DescribeNegationTo(::std::ostream* os) const { + *os << "has no substring "; + UniversalPrint(substring_, os); + } + + private: + const StringType substring_; + + GTEST_DISALLOW_ASSIGN_(HasSubstrMatcher); +}; + +// Implements the polymorphic StartsWith(substring) matcher, which +// can be used as a Matcher as long as T can be converted to a +// string. +template +class StartsWithMatcher { + public: + explicit StartsWithMatcher(const StringType& prefix) : prefix_(prefix) { + } + + // Accepts pointer types, particularly: + // const char* + // char* + // const wchar_t* + // wchar_t* + template + bool MatchAndExplain(CharType* s, MatchResultListener* listener) const { + return s != NULL && MatchAndExplain(StringType(s), listener); + } + + // Matches anything that can convert to StringType. + // + // This is a template, not just a plain function with const StringType&, + // because StringPiece has some interfering non-explicit constructors. + template + bool MatchAndExplain(const MatcheeStringType& s, + MatchResultListener* /* listener */) const { + const StringType& s2(s); + return s2.length() >= prefix_.length() && + s2.substr(0, prefix_.length()) == prefix_; + } + + void DescribeTo(::std::ostream* os) const { + *os << "starts with "; + UniversalPrint(prefix_, os); + } + + void DescribeNegationTo(::std::ostream* os) const { + *os << "doesn't start with "; + UniversalPrint(prefix_, os); + } + + private: + const StringType prefix_; + + GTEST_DISALLOW_ASSIGN_(StartsWithMatcher); +}; + +// Implements the polymorphic EndsWith(substring) matcher, which +// can be used as a Matcher as long as T can be converted to a +// string. +template +class EndsWithMatcher { + public: + explicit EndsWithMatcher(const StringType& suffix) : suffix_(suffix) {} + + // Accepts pointer types, particularly: + // const char* + // char* + // const wchar_t* + // wchar_t* + template + bool MatchAndExplain(CharType* s, MatchResultListener* listener) const { + return s != NULL && MatchAndExplain(StringType(s), listener); + } + + // Matches anything that can convert to StringType. + // + // This is a template, not just a plain function with const StringType&, + // because StringPiece has some interfering non-explicit constructors. + template + bool MatchAndExplain(const MatcheeStringType& s, + MatchResultListener* /* listener */) const { + const StringType& s2(s); + return s2.length() >= suffix_.length() && + s2.substr(s2.length() - suffix_.length()) == suffix_; + } + + void DescribeTo(::std::ostream* os) const { + *os << "ends with "; + UniversalPrint(suffix_, os); + } + + void DescribeNegationTo(::std::ostream* os) const { + *os << "doesn't end with "; + UniversalPrint(suffix_, os); + } + + private: + const StringType suffix_; + + GTEST_DISALLOW_ASSIGN_(EndsWithMatcher); +}; + +// Implements polymorphic matchers MatchesRegex(regex) and +// ContainsRegex(regex), which can be used as a Matcher as long as +// T can be converted to a string. +class MatchesRegexMatcher { + public: + MatchesRegexMatcher(const RE* regex, bool full_match) + : regex_(regex), full_match_(full_match) {} + + // Accepts pointer types, particularly: + // const char* + // char* + // const wchar_t* + // wchar_t* + template + bool MatchAndExplain(CharType* s, MatchResultListener* listener) const { + return s != NULL && MatchAndExplain(internal::string(s), listener); + } + + // Matches anything that can convert to internal::string. + // + // This is a template, not just a plain function with const internal::string&, + // because StringPiece has some interfering non-explicit constructors. + template + bool MatchAndExplain(const MatcheeStringType& s, + MatchResultListener* /* listener */) const { + const internal::string& s2(s); + return full_match_ ? RE::FullMatch(s2, *regex_) : + RE::PartialMatch(s2, *regex_); + } + + void DescribeTo(::std::ostream* os) const { + *os << (full_match_ ? "matches" : "contains") + << " regular expression "; + UniversalPrinter::Print(regex_->pattern(), os); + } + + void DescribeNegationTo(::std::ostream* os) const { + *os << "doesn't " << (full_match_ ? "match" : "contain") + << " regular expression "; + UniversalPrinter::Print(regex_->pattern(), os); + } + + private: + const internal::linked_ptr regex_; + const bool full_match_; + + GTEST_DISALLOW_ASSIGN_(MatchesRegexMatcher); +}; + +// Implements a matcher that compares the two fields of a 2-tuple +// using one of the ==, <=, <, etc, operators. The two fields being +// compared don't have to have the same type. +// +// The matcher defined here is polymorphic (for example, Eq() can be +// used to match a tuple, a tuple, +// etc). Therefore we use a template type conversion operator in the +// implementation. +// +// We define this as a macro in order to eliminate duplicated source +// code. +#define GMOCK_IMPLEMENT_COMPARISON2_MATCHER_(name, op, relation) \ + class name##2Matcher { \ + public: \ + template \ + operator Matcher< ::std::tr1::tuple >() const { \ + return MakeMatcher(new Impl< ::std::tr1::tuple >); \ + } \ + template \ + operator Matcher&>() const { \ + return MakeMatcher(new Impl&>); \ + } \ + private: \ + template \ + class Impl : public MatcherInterface { \ + public: \ + virtual bool MatchAndExplain( \ + Tuple args, \ + MatchResultListener* /* listener */) const { \ + return ::std::tr1::get<0>(args) op ::std::tr1::get<1>(args); \ + } \ + virtual void DescribeTo(::std::ostream* os) const { \ + *os << "are " relation; \ + } \ + virtual void DescribeNegationTo(::std::ostream* os) const { \ + *os << "aren't " relation; \ + } \ + }; \ + } + +// Implements Eq(), Ge(), Gt(), Le(), Lt(), and Ne() respectively. +GMOCK_IMPLEMENT_COMPARISON2_MATCHER_(Eq, ==, "an equal pair"); +GMOCK_IMPLEMENT_COMPARISON2_MATCHER_( + Ge, >=, "a pair where the first >= the second"); +GMOCK_IMPLEMENT_COMPARISON2_MATCHER_( + Gt, >, "a pair where the first > the second"); +GMOCK_IMPLEMENT_COMPARISON2_MATCHER_( + Le, <=, "a pair where the first <= the second"); +GMOCK_IMPLEMENT_COMPARISON2_MATCHER_( + Lt, <, "a pair where the first < the second"); +GMOCK_IMPLEMENT_COMPARISON2_MATCHER_(Ne, !=, "an unequal pair"); + +#undef GMOCK_IMPLEMENT_COMPARISON2_MATCHER_ + +// Implements the Not(...) matcher for a particular argument type T. +// We do not nest it inside the NotMatcher class template, as that +// will prevent different instantiations of NotMatcher from sharing +// the same NotMatcherImpl class. +template +class NotMatcherImpl : public MatcherInterface { + public: + explicit NotMatcherImpl(const Matcher& matcher) + : matcher_(matcher) {} + + virtual bool MatchAndExplain(T x, MatchResultListener* listener) const { + return !matcher_.MatchAndExplain(x, listener); + } + + virtual void DescribeTo(::std::ostream* os) const { + matcher_.DescribeNegationTo(os); + } + + virtual void DescribeNegationTo(::std::ostream* os) const { + matcher_.DescribeTo(os); + } + + private: + const Matcher matcher_; + + GTEST_DISALLOW_ASSIGN_(NotMatcherImpl); +}; + +// Implements the Not(m) matcher, which matches a value that doesn't +// match matcher m. +template +class NotMatcher { + public: + explicit NotMatcher(InnerMatcher matcher) : matcher_(matcher) {} + + // This template type conversion operator allows Not(m) to be used + // to match any type m can match. + template + operator Matcher() const { + return Matcher(new NotMatcherImpl(SafeMatcherCast(matcher_))); + } + + private: + InnerMatcher matcher_; + + GTEST_DISALLOW_ASSIGN_(NotMatcher); +}; + +// Implements the AllOf(m1, m2) matcher for a particular argument type +// T. We do not nest it inside the BothOfMatcher class template, as +// that will prevent different instantiations of BothOfMatcher from +// sharing the same BothOfMatcherImpl class. +template +class BothOfMatcherImpl : public MatcherInterface { + public: + BothOfMatcherImpl(const Matcher& matcher1, const Matcher& matcher2) + : matcher1_(matcher1), matcher2_(matcher2) {} + + virtual void DescribeTo(::std::ostream* os) const { + *os << "("; + matcher1_.DescribeTo(os); + *os << ") and ("; + matcher2_.DescribeTo(os); + *os << ")"; + } + + virtual void DescribeNegationTo(::std::ostream* os) const { + *os << "("; + matcher1_.DescribeNegationTo(os); + *os << ") or ("; + matcher2_.DescribeNegationTo(os); + *os << ")"; + } + + virtual bool MatchAndExplain(T x, MatchResultListener* listener) const { + // If either matcher1_ or matcher2_ doesn't match x, we only need + // to explain why one of them fails. + StringMatchResultListener listener1; + if (!matcher1_.MatchAndExplain(x, &listener1)) { + *listener << listener1.str(); + return false; + } + + StringMatchResultListener listener2; + if (!matcher2_.MatchAndExplain(x, &listener2)) { + *listener << listener2.str(); + return false; + } + + // Otherwise we need to explain why *both* of them match. + const internal::string s1 = listener1.str(); + const internal::string s2 = listener2.str(); + + if (s1 == "") { + *listener << s2; + } else { + *listener << s1; + if (s2 != "") { + *listener << ", and " << s2; + } + } + return true; + } + + private: + const Matcher matcher1_; + const Matcher matcher2_; + + GTEST_DISALLOW_ASSIGN_(BothOfMatcherImpl); +}; + +#if GTEST_LANG_CXX11 +// MatcherList provides mechanisms for storing a variable number of matchers in +// a list structure (ListType) and creating a combining matcher from such a +// list. +// The template is defined recursively using the following template paramters: +// * kSize is the length of the MatcherList. +// * Head is the type of the first matcher of the list. +// * Tail denotes the types of the remaining matchers of the list. +template +struct MatcherList { + typedef MatcherList MatcherListTail; + typedef ::std::pair ListType; + + // BuildList stores variadic type values in a nested pair structure. + // Example: + // MatcherList<3, int, string, float>::BuildList(5, "foo", 2.0) will return + // the corresponding result of type pair>. + static ListType BuildList(const Head& matcher, const Tail&... tail) { + return ListType(matcher, MatcherListTail::BuildList(tail...)); + } + + // CreateMatcher creates a Matcher from a given list of matchers (built + // by BuildList()). CombiningMatcher is used to combine the matchers of the + // list. CombiningMatcher must implement MatcherInterface and have a + // constructor taking two Matchers as input. + template class CombiningMatcher> + static Matcher CreateMatcher(const ListType& matchers) { + return Matcher(new CombiningMatcher( + SafeMatcherCast(matchers.first), + MatcherListTail::template CreateMatcher( + matchers.second))); + } +}; + +// The following defines the base case for the recursive definition of +// MatcherList. +template +struct MatcherList<2, Matcher1, Matcher2> { + typedef ::std::pair ListType; + + static ListType BuildList(const Matcher1& matcher1, + const Matcher2& matcher2) { + return ::std::pair(matcher1, matcher2); + } + + template class CombiningMatcher> + static Matcher CreateMatcher(const ListType& matchers) { + return Matcher(new CombiningMatcher( + SafeMatcherCast(matchers.first), + SafeMatcherCast(matchers.second))); + } +}; + +// VariadicMatcher is used for the variadic implementation of +// AllOf(m_1, m_2, ...) and AnyOf(m_1, m_2, ...). +// CombiningMatcher is used to recursively combine the provided matchers +// (of type Args...). +template

      5@XYt>8?%LXf82OvrU_Yh$Hz6r*^?853J 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%0A5y^ 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 Ezs#wZI{*Lx literal 0 HcmV?d00001 diff --git a/vendor/gmock.cmake b/vendor/gmock.cmake new file mode 100644 index 0000000000..c99834ba7b --- /dev/null +++ b/vendor/gmock.cmake @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 2.8.12) +include(ExternalProject) + +# Add an external project to build gmock +externalproject_add( + gmock + PREFIX "${PROJECT_BINARY_DIR}" + URL "file://${VENDOR_DIRECTORY}/gmock-1.7.0.zip" + URL_MD5 "073b984d8798ea1594f5e44d85b20d66" + BUILD_IN_SOURCE 1 + INSTALL_COMMAND "" +) + +# Set some useful variables based on the source directory +externalproject_get_property(gmock SOURCE_DIR) +set(GMOCK_INCLUDE_DIRS "${SOURCE_DIR}/include" "${SOURCE_DIR}/gtest/include") +set(GMOCK_LIBRARIES "${SOURCE_DIR}/libgmock.a" "${SOURCE_DIR}/libgmock_main.a") From c2d6bb06803f03d90b7e677db6dbdc7792f174a7 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Mon, 28 Apr 2014 17:42:53 -0700 Subject: [PATCH 1814/3753] (maint) Replace file utility functions with Boost file_system. Adding Boost file_system to the build. This is a better cross-platform library for file system abstractions. --- CMakeLists.txt | 2 +- exe/cfacter.cc | 4 +++- lib/CMakeLists.txt | 2 +- lib/inc/facter/util/file.hpp | 7 ------- lib/src/facts/linux/operating_system_resolver.cc | 16 +++++++++------- lib/src/util/{posix => }/file.cc | 13 ------------- 6 files changed, 14 insertions(+), 30 deletions(-) rename lib/src/util/{posix => }/file.cc (59%) diff --git a/CMakeLists.txt b/CMakeLists.txt index d57d8cba2a..1f35d04f1b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,7 +15,7 @@ configure_file ( ) # Find Boost -find_package(Boost 1.48 COMPONENTS program_options filesystem REQUIRED) +find_package(Boost 1.48 COMPONENTS program_options system filesystem REQUIRED) if (NOT Boost_FOUND) message(FATAL_ERROR "Boost 1.48 or newer is required. Please install it before building.") endif() diff --git a/exe/cfacter.cc b/exe/cfacter.cc index 5f87fc905e..82803898f6 100644 --- a/exe/cfacter.cc +++ b/exe/cfacter.cc @@ -9,11 +9,13 @@ #include #include #include +#include using namespace std; using namespace log4cxx; using namespace facter::util; using namespace facter::facts; +using namespace boost::filesystem; using boost::format; namespace po = boost::program_options; @@ -50,7 +52,7 @@ void help(po::options_description& desc) void configure_logger(LevelPtr level, string const& properties_file) { - if (!properties_file.empty() && file::exists(properties_file)) { + if (!properties_file.empty() && is_regular_file(properties_file)) { PropertyConfigurator::configure(properties_file); return; } diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 1846efe19f..22ce803e37 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -17,6 +17,7 @@ set(LIBFACTER_COMMON_SOURCES "${CMAKE_CURRENT_LIST_DIR}/src/facts/fact_map.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/fact_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/string_value.cc" + "${CMAKE_CURRENT_LIST_DIR}/src/util/file.cc" "${CMAKE_CURRENT_LIST_DIR}/src/util/string.cc" ) @@ -27,7 +28,6 @@ if (UNIX) "${CMAKE_CURRENT_LIST_DIR}/src/facts/posix/kernel_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/posix/networking_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/posix/operating_system_resolver.cc" - "${CMAKE_CURRENT_LIST_DIR}/src/util/posix/file.cc" "${CMAKE_CURRENT_LIST_DIR}/src/logging/posix/logging.cc" ) endif() diff --git a/lib/inc/facter/util/file.hpp b/lib/inc/facter/util/file.hpp index 1b2a73375e..63d6fdb74e 100644 --- a/lib/inc/facter/util/file.hpp +++ b/lib/inc/facter/util/file.hpp @@ -10,13 +10,6 @@ namespace facter { namespace util { */ struct file { - /** - * Determines if the given path exists. - * @param path The path to check for existence. - * @return Returns true if the path exists or false if it does not or access denied. - */ - static bool exists(std::string const& path); - /** * Reads the entire contents of the given file into a string. * @param path The path of the file to read. diff --git a/lib/src/facts/linux/operating_system_resolver.cc b/lib/src/facts/linux/operating_system_resolver.cc index 4ef8a97aeb..85fa497e45 100644 --- a/lib/src/facts/linux/operating_system_resolver.cc +++ b/lib/src/facts/linux/operating_system_resolver.cc @@ -9,11 +9,13 @@ #include #include #include +#include using namespace std; using namespace facter::util; using namespace facter::execution; using namespace facter::facts::posix; +using namespace boost::filesystem; namespace facter { namespace facts { namespace linux { @@ -221,7 +223,7 @@ namespace facter { namespace facts { namespace linux { string operating_system_resolver::check_cumulus_linux() { // Check for Cumulus Linux in a generic os-release file - if (file::exists(release_file::os)) { + if (is_regular_file(release_file::os)) { string contents = trim(file::read(release_file::os)); string release; if (RE2::PartialMatch(contents, "(?m)^NAME=[\"']?(.+?)[\"']?$", &release)) { @@ -237,7 +239,7 @@ namespace facter { namespace facts { namespace linux { string operating_system_resolver::check_debian_linux(string_value const* dist_id) { // Check for Debian variants - if (file::exists(release_file::debian)) { + if (is_regular_file(release_file::debian)) { if (dist_id) { if (dist_id->value() == os::ubuntu || dist_id->value() == os::linux_mint) { return dist_id->value(); @@ -250,8 +252,8 @@ namespace facter { namespace facts { namespace linux { string operating_system_resolver::check_oracle_linux() { - if (file::exists(release_file::oracle_enterprise_linux)) { - if (file::exists(release_file::oracle_vm_linux)) { + if (is_regular_file(release_file::oracle_enterprise_linux)) { + if (is_regular_file(release_file::oracle_vm_linux)) { return os::oracle_vm_linux; } return os::oracle_enterprise_linux; @@ -261,7 +263,7 @@ namespace facter { namespace facts { namespace linux { string operating_system_resolver::check_redhat_linux() { - if (file::exists(release_file::redhat)) { + if (is_regular_file(release_file::redhat)) { static map regexs { { "(?i)centos", string(os::centos) }, { "(?i)scientific linux CERN", string(os::scientific_cern) }, @@ -287,7 +289,7 @@ namespace facter { namespace facts { namespace linux { string operating_system_resolver::check_suse_linux() { - if (file::exists(release_file::suse)) { + if (is_regular_file(release_file::suse)) { static map regexs { { "(?im)^SUSE LINUX Enterprise Server", string(os::suse_enterprise_server) }, { "(?im)^SUSE LINUX Enterprise Desktop", string(os::suse_enterprise_desktop) }, @@ -325,7 +327,7 @@ namespace facter { namespace facts { namespace linux { }; for (auto const& kvp : files) { - if (file::exists(kvp.first)) { + if (is_regular_file(kvp.first)) { return kvp.second; } } diff --git a/lib/src/util/posix/file.cc b/lib/src/util/file.cc similarity index 59% rename from lib/src/util/posix/file.cc rename to lib/src/util/file.cc index 94d183b089..3f714723e6 100644 --- a/lib/src/util/posix/file.cc +++ b/lib/src/util/file.cc @@ -1,5 +1,4 @@ #include -#include #include #include @@ -7,17 +6,6 @@ using namespace std; namespace facter { namespace util { - bool file::exists(string const& path) - { - struct stat buffer; - if (stat(path.c_str(), &buffer) != 0) { - return false; - } - - return S_ISREG(buffer.st_mode); - } - - // TODO: this is standard-compliant, so it should shared between POSIX and Windows string file::read(string const& path) { ifstream in(path, ios::in | ios::binary); @@ -28,7 +16,6 @@ namespace facter { namespace util { return contents.str(); } - // TODO: this is standard-compliant, so it can be shared between POSIX and Windows string file::read_first_line(string const& path) { ifstream in(path); From 315217db8c7052680909170ed142b1a41690ff44 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Mon, 28 Apr 2014 18:45:38 -0700 Subject: [PATCH 1815/3753] (CFACT-26) Add block device facts for Linux. Implementing the following facts for Linux: - blockdevice__size - blockdevice__vendor - blockdevice__model - blockdevices --- exe/cfacter.cc | 4 +- lib/CMakeLists.txt | 3 +- lib/inc/facter/facts/fact.hpp | 2 + .../facts/linux/block_device_resolver.hpp | 39 +++++++++ lib/src/facts/linux/block_device_resolver.cc | 85 +++++++++++++++++++ .../facts/linux/operating_system_resolver.cc | 21 +++-- lib/src/facts/linux/platform.cc | 2 + 7 files changed, 147 insertions(+), 9 deletions(-) create mode 100644 lib/inc/facter/facts/linux/block_device_resolver.hpp create mode 100644 lib/src/facts/linux/block_device_resolver.cc diff --git a/exe/cfacter.cc b/exe/cfacter.cc index 82803898f6..dae2dbab64 100644 --- a/exe/cfacter.cc +++ b/exe/cfacter.cc @@ -18,6 +18,7 @@ using namespace facter::facts; using namespace boost::filesystem; using boost::format; namespace po = boost::program_options; +namespace bs = boost::system; LOG_DECLARE_NAMESPACE("main"); @@ -52,7 +53,8 @@ void help(po::options_description& desc) void configure_logger(LevelPtr level, string const& properties_file) { - if (!properties_file.empty() && is_regular_file(properties_file)) { + bs::error_code ec; + if (!properties_file.empty() && is_regular_file(properties_file, ec)) { PropertyConfigurator::configure(properties_file); return; } diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 22ce803e37..222a93afc9 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -4,7 +4,7 @@ cmake_minimum_required(VERSION 2.8.12) if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra -Werror -Wno-unused-parameter -Wno-tautological-constant-out-of-range-compare") elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra -Werror -Wno-unused-parameter") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra -Werror -Wno-unused-parameter -Wno-unused-local-typedefs") elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel") elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") endif() @@ -42,6 +42,7 @@ if ("${CMAKE_SYSTEM_NAME}" MATCHES "Darwin") elseif ("${CMAKE_SYSTEM_NAME}" MATCHES "Linux") set(LIBFACTER_PLATFORM_SOURCES "${CMAKE_CURRENT_LIST_DIR}/src/facts/bsd/networking_resolver.cc" + "${CMAKE_CURRENT_LIST_DIR}/src/facts/linux/block_device_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/linux/lsb_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/linux/networking_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/linux/operating_system_resolver.cc" diff --git a/lib/inc/facter/facts/fact.hpp b/lib/inc/facter/facts/fact.hpp index e75533b05e..81ab0c7638 100644 --- a/lib/inc/facter/facts/fact.hpp +++ b/lib/inc/facter/facts/fact.hpp @@ -35,6 +35,8 @@ namespace facter { namespace facts { constexpr static char const* interfaces = "interfaces"; constexpr static char const* domain = "domain"; constexpr static char const* fqdn = "fqdn"; + constexpr static char const* block_device = "blockdevice"; + constexpr static char const* block_devices = "blockdevices"; }; }} // namespace facter::facts diff --git a/lib/inc/facter/facts/linux/block_device_resolver.hpp b/lib/inc/facter/facts/linux/block_device_resolver.hpp new file mode 100644 index 0000000000..cd596882f6 --- /dev/null +++ b/lib/inc/facter/facts/linux/block_device_resolver.hpp @@ -0,0 +1,39 @@ +#ifndef FACTER_FACTS_LINUX_BLOCK_DEVICE_RESOLVER_HPP_ +#define FACTER_FACTS_LINUX_BLOCK_DEVICE_RESOLVER_HPP_ + +#include "../fact_resolver.hpp" +#include "../fact.hpp" + +namespace facter { namespace facts { namespace linux { + + /** + * Responsible for resolving block device facts. + */ + struct block_device_resolver : fact_resolver + { + /** + * Constructs the block_device_resolver. + */ + block_device_resolver() : + fact_resolver( + "block device", + { + fact::block_devices, + }, + { + std::string("^") + fact::block_device + "_", + }) + { + } + + protected: + /** + * Called to resolve all facts the resolver is responsible for. + * @param facts The fact map that is resolving facts. + */ + virtual void resolve_facts(fact_map& facts); + }; + +}}} // namespace facter::facts::linux + +#endif // FACTER_FACTS_LINUX_BLOCK_DEVICE_RESOLVER_HPP_ diff --git a/lib/src/facts/linux/block_device_resolver.cc b/lib/src/facts/linux/block_device_resolver.cc new file mode 100644 index 0000000000..44a5040bdc --- /dev/null +++ b/lib/src/facts/linux/block_device_resolver.cc @@ -0,0 +1,85 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace facter::util; +using namespace boost::filesystem; +using boost::lexical_cast; +using boost::bad_lexical_cast; +namespace bs = boost::system; + +LOG_DECLARE_NAMESPACE("facts.linux.blockdevices"); + +namespace facter { namespace facts { namespace linux { + + void block_device_resolver::resolve_facts(fact_map& facts) + { + static string devices_directory = "/sys/block/"; + + bs::error_code ec; + if (!is_directory(devices_directory, ec)) { + LOG_DEBUG("directory %1%: %2%: block device facts are unavailable.", devices_directory, ec.message()); + return; + } + + // Enumerate all block devices + ostringstream devices; + directory_iterator end; + directory_iterator it(devices_directory); + for (; it != end; ++it) { + bs::error_code ec; + if (!is_directory(it->status())) { + continue; + } + + // Check for the device directory + string device = it->path().filename().string(); + string device_directory = devices_directory + device + "/device/"; + if (!is_directory(device_directory, ec)) { + continue; + } + + string size_file = devices_directory + device + "/size"; + string vendor_file = device_directory + "vendor"; + string model_file = device_directory + "model"; + + // Read the size of the block device + // The size is in 512 byte sectors + if (is_regular_file(size_file, ec)) { + try { + uint64_t size = lexical_cast(trim(file::read(size_file))); + facts.add(string(fact::block_device) + "_" + device + "_size" , make_value(to_string(size * 512))); + } catch (bad_lexical_cast& ex) { + LOG_DEBUG("size of block device %1% is invalid: fact %2%_%1%_size is unavailable.", device, fact::block_device); + } + } + + // Read the vendor fact + if (is_regular_file(vendor_file, ec)) { + facts.add(string(fact::block_device) + "_" + device + "_vendor" , make_value(trim(file::read(vendor_file)))); + } + + // Read the model fact + if (is_regular_file(model_file, ec)) { + facts.add(string(fact::block_device) + "_" + device + "_model" , make_value(trim(file::read(model_file)))); + } + + // Add the device to the devices fact + if (devices.tellp() != 0) { + devices << ','; + } + devices << device; + } + + if (devices.tellp() > 0) { + facts.add(fact::block_devices, make_value(devices.str())); + } + } + +}}} // namespace facter::facts::linux diff --git a/lib/src/facts/linux/operating_system_resolver.cc b/lib/src/facts/linux/operating_system_resolver.cc index 85fa497e45..14e5c08148 100644 --- a/lib/src/facts/linux/operating_system_resolver.cc +++ b/lib/src/facts/linux/operating_system_resolver.cc @@ -16,6 +16,7 @@ using namespace facter::util; using namespace facter::execution; using namespace facter::facts::posix; using namespace boost::filesystem; +namespace bs = boost::system; namespace facter { namespace facts { namespace linux { @@ -223,7 +224,8 @@ namespace facter { namespace facts { namespace linux { string operating_system_resolver::check_cumulus_linux() { // Check for Cumulus Linux in a generic os-release file - if (is_regular_file(release_file::os)) { + bs::error_code ec; + if (is_regular_file(release_file::os, ec)) { string contents = trim(file::read(release_file::os)); string release; if (RE2::PartialMatch(contents, "(?m)^NAME=[\"']?(.+?)[\"']?$", &release)) { @@ -239,7 +241,8 @@ namespace facter { namespace facts { namespace linux { string operating_system_resolver::check_debian_linux(string_value const* dist_id) { // Check for Debian variants - if (is_regular_file(release_file::debian)) { + bs::error_code ec; + if (is_regular_file(release_file::debian, ec)) { if (dist_id) { if (dist_id->value() == os::ubuntu || dist_id->value() == os::linux_mint) { return dist_id->value(); @@ -252,8 +255,9 @@ namespace facter { namespace facts { namespace linux { string operating_system_resolver::check_oracle_linux() { - if (is_regular_file(release_file::oracle_enterprise_linux)) { - if (is_regular_file(release_file::oracle_vm_linux)) { + bs::error_code ec; + if (is_regular_file(release_file::oracle_enterprise_linux, ec)) { + if (is_regular_file(release_file::oracle_vm_linux, ec)) { return os::oracle_vm_linux; } return os::oracle_enterprise_linux; @@ -263,7 +267,8 @@ namespace facter { namespace facts { namespace linux { string operating_system_resolver::check_redhat_linux() { - if (is_regular_file(release_file::redhat)) { + bs::error_code ec; + if (is_regular_file(release_file::redhat, ec)) { static map regexs { { "(?i)centos", string(os::centos) }, { "(?i)scientific linux CERN", string(os::scientific_cern) }, @@ -289,7 +294,8 @@ namespace facter { namespace facts { namespace linux { string operating_system_resolver::check_suse_linux() { - if (is_regular_file(release_file::suse)) { + bs::error_code ec; + if (is_regular_file(release_file::suse, ec)) { static map regexs { { "(?im)^SUSE LINUX Enterprise Server", string(os::suse_enterprise_server) }, { "(?im)^SUSE LINUX Enterprise Desktop", string(os::suse_enterprise_desktop) }, @@ -327,7 +333,8 @@ namespace facter { namespace facts { namespace linux { }; for (auto const& kvp : files) { - if (is_regular_file(kvp.first)) { + bs::error_code ec; + if (is_regular_file(kvp.first, ec)) { return kvp.second; } } diff --git a/lib/src/facts/linux/platform.cc b/lib/src/facts/linux/platform.cc index 128bcfb72b..254112404f 100644 --- a/lib/src/facts/linux/platform.cc +++ b/lib/src/facts/linux/platform.cc @@ -3,6 +3,7 @@ #include #include #include +#include using namespace std; @@ -14,6 +15,7 @@ namespace facter { namespace facts { facts.add(make_shared()); facts.add(make_shared()); facts.add(make_shared()); + facts.add(make_shared()); } }} // namespace facter::facts From 544dfa51ea24d033c535c397e46b00fe1843b6da Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 29 Apr 2014 15:39:30 -0700 Subject: [PATCH 1816/3753] (maint) explicitly include stdexcept In fact_map.hpp and fact_resolver.hpp, exceptions defined in stdexcept were being used but the header itself was not being excluded. In recent versions of gcc and clang stdexcept was being included by another library but in gcc 4.7 and clang 3.3 this was not included. This commit explicitly requires stdexcept to avoid the version dependent behavior. --- lib/inc/facter/facts/fact_map.hpp | 1 + lib/inc/facter/facts/fact_resolver.hpp | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/inc/facter/facts/fact_map.hpp b/lib/inc/facter/facts/fact_map.hpp index 0d8bcf7d2f..c535c23b4f 100644 --- a/lib/inc/facter/facts/fact_map.hpp +++ b/lib/inc/facter/facts/fact_map.hpp @@ -8,6 +8,7 @@ #include #include "value.hpp" #include "fact_resolver.hpp" +#include namespace facter { namespace facts { diff --git a/lib/inc/facter/facts/fact_resolver.hpp b/lib/inc/facter/facts/fact_resolver.hpp index d95904d85b..7c71ec407d 100644 --- a/lib/inc/facter/facts/fact_resolver.hpp +++ b/lib/inc/facter/facts/fact_resolver.hpp @@ -3,6 +3,7 @@ #include #include +#include #include // Forward declare RE2 so users of this header don't have to include re2 From 4417957111a53f5b30305395189bc6f874923be7 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Sun, 27 Apr 2014 18:55:48 -0500 Subject: [PATCH 1817/3753] (CFACT-25) Add processor facts for OSX and Linux. Implementing the following facts for OSX and Linux: - processor[0-9]+ - processorcount - physicalprocesscount - hardwareisa - hardwaremodel - architecture --- lib/CMakeLists.txt | 3 + lib/inc/facter/facts/fact.hpp | 6 ++ .../facter/facts/linux/processor_resolver.hpp | 28 +++++ .../facter/facts/osx/processor_resolver.hpp | 23 ++++ lib/inc/facter/facts/posix/os.hpp | 1 + .../facter/facts/posix/processor_resolver.hpp | 66 ++++++++++++ lib/src/execution/posix/execution.cc | 6 +- lib/src/facts/linux/platform.cc | 2 + lib/src/facts/linux/processor_resolver.cc | 100 ++++++++++++++++++ lib/src/facts/osx/platform.cc | 2 + lib/src/facts/osx/processor_resolver.cc | 60 +++++++++++ lib/src/facts/posix/networking_resolver.cc | 8 +- lib/src/facts/posix/processor_resolver.cc | 65 ++++++++++++ 13 files changed, 363 insertions(+), 7 deletions(-) create mode 100644 lib/inc/facter/facts/linux/processor_resolver.hpp create mode 100644 lib/inc/facter/facts/osx/processor_resolver.hpp create mode 100644 lib/inc/facter/facts/posix/processor_resolver.hpp create mode 100644 lib/src/facts/linux/processor_resolver.cc create mode 100644 lib/src/facts/osx/processor_resolver.cc create mode 100644 lib/src/facts/posix/processor_resolver.cc diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 222a93afc9..b4a3ae7e4e 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -28,6 +28,7 @@ if (UNIX) "${CMAKE_CURRENT_LIST_DIR}/src/facts/posix/kernel_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/posix/networking_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/posix/operating_system_resolver.cc" + "${CMAKE_CURRENT_LIST_DIR}/src/facts/posix/processor_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/logging/posix/logging.cc" ) endif() @@ -38,6 +39,7 @@ if ("${CMAKE_SYSTEM_NAME}" MATCHES "Darwin") "${CMAKE_CURRENT_LIST_DIR}/src/facts/bsd/networking_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/osx/networking_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/osx/platform.cc" + "${CMAKE_CURRENT_LIST_DIR}/src/facts/osx/processor_resolver.cc" ) elseif ("${CMAKE_SYSTEM_NAME}" MATCHES "Linux") set(LIBFACTER_PLATFORM_SOURCES @@ -47,6 +49,7 @@ elseif ("${CMAKE_SYSTEM_NAME}" MATCHES "Linux") "${CMAKE_CURRENT_LIST_DIR}/src/facts/linux/networking_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/linux/operating_system_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/linux/platform.cc" + "${CMAKE_CURRENT_LIST_DIR}/src/facts/linux/processor_resolver.cc" ) endif() diff --git a/lib/inc/facter/facts/fact.hpp b/lib/inc/facter/facts/fact.hpp index 81ab0c7638..71721f1304 100644 --- a/lib/inc/facter/facts/fact.hpp +++ b/lib/inc/facter/facts/fact.hpp @@ -37,6 +37,12 @@ namespace facter { namespace facts { constexpr static char const* fqdn = "fqdn"; constexpr static char const* block_device = "blockdevice"; constexpr static char const* block_devices = "blockdevices"; + constexpr static char const* processor = "processor"; + constexpr static char const* processor_count = "processorcount"; + constexpr static char const* physical_processor_count = "physicalprocessorcount"; + constexpr static char const* hardware_isa = "hardwareisa"; + constexpr static char const* hardware_model = "hardwaremodel"; + constexpr static char const* architecture = "architecture"; }; }} // namespace facter::facts diff --git a/lib/inc/facter/facts/linux/processor_resolver.hpp b/lib/inc/facter/facts/linux/processor_resolver.hpp new file mode 100644 index 0000000000..a713977c7b --- /dev/null +++ b/lib/inc/facter/facts/linux/processor_resolver.hpp @@ -0,0 +1,28 @@ +#ifndef FACTER_FACTS_LINUX_PROCESSOR_RESOLVER_HPP_ +#define FACTER_FACTS_LINUX_PROCESSOR_RESOLVER_HPP_ + +#include "../posix/processor_resolver.hpp" + +namespace facter { namespace facts { namespace linux { + + /** + * Responsible for resolving processor-related facts. + */ + struct processor_resolver : posix::processor_resolver + { + protected: + /** + * Called to resolve the hardware architecture fact. + * @param facts The fact map that is resolving facts. + */ + virtual void resolve_architecture(fact_map& facts); + /** + * Called to resolve processor count, physical processor count, and description facts. + * @param facts The fact map that is resolving facts. + */ + virtual void resolve_processors(fact_map& facts); + }; + +}}} // namespace facter::facts::linux + +#endif // FACTER_FACTS_LINUX_PROCESSOR_RESOLVER_HPP_ diff --git a/lib/inc/facter/facts/osx/processor_resolver.hpp b/lib/inc/facter/facts/osx/processor_resolver.hpp new file mode 100644 index 0000000000..ebad30f81b --- /dev/null +++ b/lib/inc/facter/facts/osx/processor_resolver.hpp @@ -0,0 +1,23 @@ +#ifndef FACTER_FACTS_OSX_PROCESSOR_RESOLVER_HPP_ +#define FACTER_FACTS_OSX_PROCESSOR_RESOLVER_HPP_ + +#include "../posix/processor_resolver.hpp" + +namespace facter { namespace facts { namespace osx { + + /** + * Responsible for resolving processor-related facts. + */ + struct processor_resolver : posix::processor_resolver + { + protected: + /** + * Called to resolve processor count, physical processor count, and description facts. + * @param facts The fact map that is resolving facts. + */ + virtual void resolve_processors(fact_map& facts); + }; + +}}} // namespace facter::facts::osx + +#endif // FACTER_FACTS_OSX_PROCESSOR_RESOLVER_HPP_ diff --git a/lib/inc/facter/facts/posix/os.hpp b/lib/inc/facter/facts/posix/os.hpp index f7da1cbee5..b22af114b6 100644 --- a/lib/inc/facter/facts/posix/os.hpp +++ b/lib/inc/facter/facts/posix/os.hpp @@ -47,6 +47,7 @@ namespace facter { namespace facts { namespace posix { constexpr static char const* alpine = "Alpine"; constexpr static char const* cumulus = "CumulusLinux"; constexpr static char const* zen_cloud_platform = "XCP"; + constexpr static char const* kfreebsd = "GNU/kFreeBSD"; }; }}} // namespace facter::facts::posix diff --git a/lib/inc/facter/facts/posix/processor_resolver.hpp b/lib/inc/facter/facts/posix/processor_resolver.hpp new file mode 100644 index 0000000000..26124bd56a --- /dev/null +++ b/lib/inc/facter/facts/posix/processor_resolver.hpp @@ -0,0 +1,66 @@ +#ifndef FACTER_FACTS_POSIX_PROCESSOR_RESOLVER_HPP_ +#define FACTER_FACTS_POSIX_PROCESSOR_RESOLVER_HPP_ + +#include "../fact_resolver.hpp" +#include "../fact.hpp" +#include +#include + +namespace facter { namespace facts { namespace posix { + + /** + * Responsible for resolving processor-related facts. + */ + struct processor_resolver : fact_resolver + { + /** + * Constructs the processor_resolver. + */ + processor_resolver() : + fact_resolver( + "processor", + { + fact::processor_count, + fact::physical_processor_count, + fact::hardware_isa, + fact::hardware_model, + }, + { + std::string("^") + fact::processor + "[0-9]+$", + }) + { + } + + protected: + /** + * Called to resolve all facts the resolver is responsible for. + * @param facts The fact map that is resolving facts. + */ + virtual void resolve_facts(fact_map& facts); + /** + * Called to resolve the hardware ISA fact. + * @param facts The fact map that is resolving facts. + * @param name The result of the uname call. + */ + virtual void resolve_hardware_isa(fact_map& facts, utsname const& name); + /** + * Called to resolve the hardware model fact. + * @param facts The fact map that is resolving facts. + * @param name The result of the uname call. + */ + virtual void resolve_hardware_model(fact_map& facts, utsname const& name); + /** + * Called to resolve the hardware architecture fact. + * @param facts The fact map that is resolving facts. + */ + virtual void resolve_architecture(fact_map& facts); + /** + * Called to resolve processor count, physical processor count, and description facts. + * @param facts The fact map that is resolving facts. + */ + virtual void resolve_processors(fact_map& facts) = 0; + }; + +}}} // namespace facter::facts::posix + +#endif // FACTER_FACTS_POSIX_PROCESSOR_RESOLVER_HPP_ diff --git a/lib/src/execution/posix/execution.cc b/lib/src/execution/posix/execution.cc index e7fede3eb8..543bacb33c 100644 --- a/lib/src/execution/posix/execution.cc +++ b/lib/src/execution/posix/execution.cc @@ -116,6 +116,9 @@ namespace facter { namespace execution { // Wait for the child to exit string result = output.str(); + if (options[execution_options::trim_output]) { + trim(result); + } int status; waitpid(child, &status, 0); if (WIFEXITED(status)) { @@ -131,9 +134,6 @@ namespace facter { namespace execution { throw child_signal_exception(status, result, "child process was terminated by signal."); } } - if (options[execution_options::trim_output]) { - return trim(result); - } return result; } diff --git a/lib/src/facts/linux/platform.cc b/lib/src/facts/linux/platform.cc index 254112404f..bc6686e25f 100644 --- a/lib/src/facts/linux/platform.cc +++ b/lib/src/facts/linux/platform.cc @@ -4,6 +4,7 @@ #include #include #include +#include using namespace std; @@ -16,6 +17,7 @@ namespace facter { namespace facts { facts.add(make_shared()); facts.add(make_shared()); facts.add(make_shared()); + facts.add(make_shared()); } }} // namespace facter::facts diff --git a/lib/src/facts/linux/processor_resolver.cc b/lib/src/facts/linux/processor_resolver.cc new file mode 100644 index 0000000000..b4b0629aa3 --- /dev/null +++ b/lib/src/facts/linux/processor_resolver.cc @@ -0,0 +1,100 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace re2; +using namespace facter::facts; +using namespace facter::facts::posix; +using namespace facter::util; + +LOG_DECLARE_NAMESPACE("facts.linux.processor"); + +namespace facter { namespace facts { namespace linux { + + void processor_resolver::resolve_architecture(fact_map& facts) + { + // Get the hardware model + auto model = facts.get(fact::hardware_model, false); + if (!model) { + posix::processor_resolver::resolve_architecture(facts); + return; + } + + // Get the operating system + auto os = facts.get(fact::operating_system); + if (!os) { + posix::processor_resolver::resolve_architecture(facts); + return; + } + + // For certain distros, use "amd64" for x86_64 + string value = model->value(); + if (model->value() == "x86_64") { + if (os->value() == os::debian || + os->value() == os::gentoo || + os->value() == os::kfreebsd || + os->value() == os::ubuntu) { + value = "amd64"; + } + // For 32-bit, use "x86" for Gentoo and "i386" for everyone else + } else if (RE2::PartialMatch(model->value(), "i[3456]86|pentium")) { + if (os->value() == os::gentoo) { + value = "x86"; + } else { + value = "i386"; + } + } + + facts.add(fact::architecture, make_value(move(value))); + } + + void processor_resolver::resolve_processors(fact_map& facts) + { + // For linux, we need to search through the output of /proc/cpuinfo + ifstream cpuinfo("/proc/cpuinfo", ifstream::in); + + unordered_set cpus; + size_t logical_processor_count = 0; + + // Search through each line of output + string line; + string id; + while (getline(cpuinfo, line)) { + auto pos = line.find(":"); + string key = trim(line.substr(0, pos)); + string value = trim(line.substr(pos + 1)); + + // If the key is processor, it's the start of a processor + if (key == "processor") { + id = move(value); + ++logical_processor_count; + } else if (key == "model name" && !id.empty()) { + // Add the processor description fact + facts.add(fact::processor + id, make_value(move(value))); + } else if (key == "physical id") { + // Add the physical id to the set so we only count each one once + cpus.emplace(move(value)); + } + } + + // Logical count should be at least the physical count + if (logical_processor_count < cpus.size()) { + logical_processor_count = cpus.size(); + } + + // Add the count facts + facts.add(fact::physical_processor_count, make_value(to_string(cpus.size()))); + facts.add(fact::processor_count, make_value(to_string(logical_processor_count))); + } + +}}} // namespace facter::facts::linux diff --git a/lib/src/facts/osx/platform.cc b/lib/src/facts/osx/platform.cc index 23e43d8f4f..bac7d3aaca 100644 --- a/lib/src/facts/osx/platform.cc +++ b/lib/src/facts/osx/platform.cc @@ -2,6 +2,7 @@ #include #include #include +#include using namespace std; @@ -12,6 +13,7 @@ namespace facter { namespace facts { facts.add(make_shared()); facts.add(make_shared()); facts.add(make_shared()); + facts.add(make_shared()); } }} // namespace facter::facts diff --git a/lib/src/facts/osx/processor_resolver.cc b/lib/src/facts/osx/processor_resolver.cc new file mode 100644 index 0000000000..27a093714a --- /dev/null +++ b/lib/src/facts/osx/processor_resolver.cc @@ -0,0 +1,60 @@ +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace facter::facts; + +LOG_DECLARE_NAMESPACE("facts.osx.processor"); + +namespace facter { namespace facts { namespace osx { + + void processor_resolver::resolve_processors(fact_map& facts) + { + // Get the logical count of processors + int logical_count = 0; + size_t size = sizeof(logical_count); + if (sysctlbyname("hw.logicalcpu_max", &logical_count, &size, nullptr, 0) != 0) { + LOG_DEBUG("sysctlbyname failed: %1% (%2%): %3% fact is unavailable.", strerror(errno), errno, fact::processor_count); + } else { + facts.add(fact::processor_count, make_value(to_string(logical_count))); + } + + // Get the physical count of processors + int physical_count = 0; + size = sizeof(physical_count); + if (sysctlbyname("hw.physicalcpu_max", &physical_count, &size, nullptr, 0) != 0) { + LOG_DEBUG("sysctlbyname failed: %1% (%2%): %3% fact is unavailable.", strerror(errno), errno, fact::physical_processor_count); + } else { + facts.add(fact::physical_processor_count, make_value(to_string(physical_count))); + } + + // For each logical processor, output a fact with the model name + if (logical_count > 0) { + // Note: we're using the same description string for all the processor facts + vector buffer(256); + do { + size_t size = buffer.size(); + if (sysctlbyname("machdep.cpu.brand_string", buffer.data(), &size, nullptr, 0) == 0) { + buffer.resize(size + 1); + break; + } + if (errno != ENOMEM) { + LOG_DEBUG("sysctlbyname failed: %1% (%2%): %3% facts are unavailable.", strerror(errno), errno, fact::processor); + return; + } + buffer.resize(buffer.size() * 2); + } while (true); + + string description(buffer.data()); + for (int i = 0; i < logical_count; ++i) { + facts.add(string(fact::processor) + to_string(i), make_value(description)); + } + } + } + +}}} // namespace facter::facts::osx diff --git a/lib/src/facts/posix/networking_resolver.cc b/lib/src/facts/posix/networking_resolver.cc index 1ae5026f89..e98883d41b 100644 --- a/lib/src/facts/posix/networking_resolver.cc +++ b/lib/src/facts/posix/networking_resolver.cc @@ -36,7 +36,7 @@ namespace facter { namespace facts { namespace posix { int max = sysconf(_SC_HOST_NAME_MAX); vector name(max); if (gethostname(name.data(), max) != 0) { - LOG_WARNING("gethostname failed: %1% (%2%): hostname fact is unavailable.", strerror(errno), errno); + LOG_WARNING("gethostname failed: %1% (%2%): %3% fact is unavailable.", strerror(errno), errno, fact::hostname); return; } @@ -57,7 +57,7 @@ namespace facter { namespace facts { namespace posix { { auto hostname = facts.get(fact::hostname, false); if (!hostname) { - LOG_WARNING("domain and fqdn facts cannot be resolved without the hostname fact."); + LOG_WARNING("%1% and %2% facts cannot be resolved without the %3% fact.", fact::fqdn, fact::domain, fact::hostname); return; } @@ -65,10 +65,10 @@ namespace facter { namespace facts { namespace posix { scoped_addrinfo info(hostname->value()); if (info.result() != 0) { if (info.result() == EAI_NONAME) { - LOG_WARNING("domain is unknown for host %1%: domain and fqdn facts are unavailable.", hostname->value()); + LOG_WARNING("domain is unknown for host %1%: %2% and %3% facts are unavailable.", hostname->value(), fact::fqdn, fact::domain); return; } - LOG_WARNING("getaddrinfo failed: %1% (%2%): domain and fqdn facts are unavailable.", gai_strerror(info.result()), info.result()); + LOG_WARNING("getaddrinfo failed: %1% (%2%): %3% and %4% facts are unavailable.", gai_strerror(info.result()), info.result(), fact::fqdn, fact::domain); return; } diff --git a/lib/src/facts/posix/processor_resolver.cc b/lib/src/facts/posix/processor_resolver.cc new file mode 100644 index 0000000000..8884398f53 --- /dev/null +++ b/lib/src/facts/posix/processor_resolver.cc @@ -0,0 +1,65 @@ +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace facter::facts; +using namespace facter::execution; + +LOG_DECLARE_NAMESPACE("facts.posix.processor"); + +namespace facter { namespace facts { namespace posix { + + void processor_resolver::resolve_facts(fact_map& facts) + { + // Resolve the hardware related facts + utsname name; + memset(&name, 0, sizeof(name)); + if (uname(&name) != 0) { + LOG_WARNING("uname failed: %1% (%2%): %3% and %4% facts are unavailable.", strerror(errno), errno, fact::hardware_isa, fact::hardware_model); + } else { + resolve_hardware_model(facts, name); + resolve_hardware_isa(facts, name); + } + + // Resolve the architecture + resolve_architecture(facts); + + // Resolve the processors facts + resolve_processors(facts); + } + + void processor_resolver::resolve_hardware_isa(fact_map& facts, utsname const& name) + { + // The utsname struct doesn't have a member for "uname -p", so we need to execute + string value = execute("uname", { "-p" }); + if (value.empty()) { + return; + } + facts.add(fact::hardware_isa, make_value(move(value))); + } + + void processor_resolver::resolve_hardware_model(fact_map& facts, utsname const& name) + { + // There is a corresponding field for "uname -m", so use it + string value = name.machine; + if (value.empty()) { + return; + } + facts.add(fact::hardware_model, make_value(move(value))); + } + + void processor_resolver::resolve_architecture(fact_map& facts) + { + // By default, use the hardware model + auto model = facts.get(fact::hardware_model, false); + if (!model) { + return; + } + facts.add(fact::architecture, make_value(model->value())); + } + +}}} // namespace facter::facts::posix From ceb8cde01a262e21cb2a07d0f8819ef75c97a588 Mon Sep 17 00:00:00 2001 From: Chris Portman Date: Fri, 22 Nov 2013 10:45:19 +1100 Subject: [PATCH 1818/3753] (FACT-233) Add dhcp_servers fact. Add a fact to show dhcp servers for interfaces Fact: dhcp_servers Returns: Hash or nil. Hash contains interface names as keys and their dhcp servers as values. If the 'system' interface (determined by the machines device connecting the default gateway) is dhcp enabled, a key 'system' will also be present with the associated dhcp server. Purpose: Return the DHCP server addresses for all dhcp enabled interfaces --- lib/facter/dhcp_servers.rb | 39 +++++++++ lib/facter/util/dhcp_servers.rb | 19 +++++ spec/fixtures/unit/dhcp_servers/nmcli_devices | 4 + .../dhcp_servers/nmcli_devices_disconnected | 4 + .../unit/dhcp_servers/nmcli_eth0_dhcp | 36 ++++++++ .../unit/dhcp_servers/nmcli_eth0_static | 24 ++++++ .../unit/dhcp_servers/nmcli_wlan0_dhcp | 49 +++++++++++ .../unit/dhcp_servers/nmcli_wlan0_static | 37 ++++++++ spec/fixtures/unit/dhcp_servers/route | 4 + spec/fixtures/unit/dhcp_servers/route_nogw | 4 + spec/unit/dhcp_servers_spec.rb | 84 +++++++++++++++++++ 11 files changed, 304 insertions(+) create mode 100644 lib/facter/dhcp_servers.rb create mode 100644 lib/facter/util/dhcp_servers.rb create mode 100644 spec/fixtures/unit/dhcp_servers/nmcli_devices create mode 100644 spec/fixtures/unit/dhcp_servers/nmcli_devices_disconnected create mode 100644 spec/fixtures/unit/dhcp_servers/nmcli_eth0_dhcp create mode 100644 spec/fixtures/unit/dhcp_servers/nmcli_eth0_static create mode 100644 spec/fixtures/unit/dhcp_servers/nmcli_wlan0_dhcp create mode 100644 spec/fixtures/unit/dhcp_servers/nmcli_wlan0_static create mode 100644 spec/fixtures/unit/dhcp_servers/route create mode 100644 spec/fixtures/unit/dhcp_servers/route_nogw create mode 100644 spec/unit/dhcp_servers_spec.rb diff --git a/lib/facter/dhcp_servers.rb b/lib/facter/dhcp_servers.rb new file mode 100644 index 0000000000..d3ea817fea --- /dev/null +++ b/lib/facter/dhcp_servers.rb @@ -0,0 +1,39 @@ +# Fact: dhcp_servers +# +# Purpose: +# Return the DHCP server addresses for all interfaces as a hash. +# If the interface that is the default gateway is dhcp assigned, there +# will also be a 'system' entry in the hash. +# +# Resolution: +# Parses the output of nmcli to find the DHCP server for the interface if available +# +# Caveats: +# Requires nmcli to be available and the interface must use network-manager. +# + +require 'facter' +require 'facter/util/dhcp_servers' + + +Facter.add(:dhcp_servers) do + confine :kernel => :linux + confine do + Facter::Core::Execution.which('nmcli') + end + + setcode do + gwdev = Facter::Util::DHCPServers.gateway_device + devices = Facter::Util::DHCPServers.devices + + dhcp_servers = {} + devices.each do |device| + if server = Facter::Util::DHCPServers.device_dhcp_server(device) + dhcp_servers['system'] = server if device == gwdev + dhcp_servers[device] = server + end + end + + dhcp_servers.keys.length > 0 ? dhcp_servers : nil + end +end diff --git a/lib/facter/util/dhcp_servers.rb b/lib/facter/util/dhcp_servers.rb new file mode 100644 index 0000000000..f1aabd9c5d --- /dev/null +++ b/lib/facter/util/dhcp_servers.rb @@ -0,0 +1,19 @@ +module Facter::Util::DHCPServers + def self.gateway_device + Facter::Util::Resolution.exec("route -n").scan(/^0\.0\.0\.0.*?(\S+)$/).flatten.first + end + + def self.devices + if Facter::Core::Execution.which('nmcli') + Facter::Util::Resolution.exec("nmcli d").split("\n").select {|d| d =~ /\sconnected/i }.collect{ |line| line.split[0] } + else + [] + end + end + + def self.device_dhcp_server(device) + if Facter::Core::Execution.which('nmcli') + Facter::Util::Resolution.exec("nmcli d list iface #{device}").scan(/dhcp_server_identifier.*?(\d+\.\d+\.\d+\.\d+)$/).flatten.first + end + end +end diff --git a/spec/fixtures/unit/dhcp_servers/nmcli_devices b/spec/fixtures/unit/dhcp_servers/nmcli_devices new file mode 100644 index 0000000000..2eccc59829 --- /dev/null +++ b/spec/fixtures/unit/dhcp_servers/nmcli_devices @@ -0,0 +1,4 @@ +DEVICE TYPE STATE +wlan0 802-11-wireless connected +eth0 802-3-ethernet connected +eth1 802-3-ethernet disconnected diff --git a/spec/fixtures/unit/dhcp_servers/nmcli_devices_disconnected b/spec/fixtures/unit/dhcp_servers/nmcli_devices_disconnected new file mode 100644 index 0000000000..8513a231cd --- /dev/null +++ b/spec/fixtures/unit/dhcp_servers/nmcli_devices_disconnected @@ -0,0 +1,4 @@ +DEVICE TYPE STATE +wlan0 802-11-wireless disconnected +eth0 802-3-ethernet disconnected +eth1 802-3-ethernet disconnected diff --git a/spec/fixtures/unit/dhcp_servers/nmcli_eth0_dhcp b/spec/fixtures/unit/dhcp_servers/nmcli_eth0_dhcp new file mode 100644 index 0000000000..740a32bbde --- /dev/null +++ b/spec/fixtures/unit/dhcp_servers/nmcli_eth0_dhcp @@ -0,0 +1,36 @@ +GENERAL.DEVICE: eth0 +GENERAL.TYPE: 802-3-ethernet +GENERAL.VENDOR: Realtek Semiconductor Co., Ltd. +GENERAL.PRODUCT: RTL8111/8168 PCI Express Gigabit Ethernet controller +GENERAL.DRIVER: r8169 +GENERAL.DRIVER-VERSION: 2.3LK-NAPI +GENERAL.FIRMWARE-VERSION: +GENERAL.HWADDR: D4:BE:D9:34:67:68 +GENERAL.STATE: 100 (connected) +GENERAL.REASON: 0 (No reason given) +GENERAL.UDI: /sys/devices/pci0000:00/0000:00:1c.0/0000:07:00.0/net/eth0 +GENERAL.IP-IFACE: eth0 +GENERAL.NM-MANAGED: yes +GENERAL.AUTOCONNECT: yes +GENERAL.FIRMWARE-MISSING: no +GENERAL.CONNECTION: /org/freedesktop/NetworkManager/ActiveConnection/9 +CAPABILITIES.CARRIER-DETECT: yes +CAPABILITIES.SPEED: 1000 Mb/s +CONNECTIONS.AVAILABLE-CONNECTION-PATHS: /org/freedesktop/NetworkManager/Settings/{1,2} +CONNECTIONS.AVAILABLE-CONNECTIONS[1]: acafdb99-23e4-49e5-a3e6-0a00575312b5 | Office +CONNECTIONS.AVAILABLE-CONNECTIONS[2]: d6aaee51-2ed8-4b05-82ac-365c9c74b44e | DHCP +WIRED-PROPERTIES.CARRIER: on +IP4.ADDRESS[1]: ip = 192.168.1.10, gw = 192.168.1.1 +IP4.DNS[1]: 192.168.1.1 +DHCP4.OPTION[1]: time_offset = 0 +DHCP4.OPTION[2]: expiry = 1401991971 +DHCP4.OPTION[3]: broadcast_address = 192.168.1.255 +DHCP4.OPTION[4]: dhcp_message_type = 5 +DHCP4.OPTION[5]: dhcp_lease_time = 9999999 +DHCP4.OPTION[6]: ip_address = 192.168.1.10 +DHCP4.OPTION[7]: subnet_mask = 255.255.255.0 +DHCP4.OPTION[8]: routers = 192.168.1.1 +DHCP4.OPTION[9]: domain_name_servers = 192.168.1.1 +DHCP4.OPTION[10]: network_number = 192.168.1.0 +DHCP4.OPTION[11]: default_ip_ttl = 64 +DHCP4.OPTION[12]: dhcp_server_identifier = 192.168.1.1 diff --git a/spec/fixtures/unit/dhcp_servers/nmcli_eth0_static b/spec/fixtures/unit/dhcp_servers/nmcli_eth0_static new file mode 100644 index 0000000000..1c0ce09403 --- /dev/null +++ b/spec/fixtures/unit/dhcp_servers/nmcli_eth0_static @@ -0,0 +1,24 @@ +GENERAL.DEVICE: eth0 +GENERAL.TYPE: 802-3-ethernet +GENERAL.VENDOR: Realtek Semiconductor Co., Ltd. +GENERAL.PRODUCT: RTL8111/8168 PCI Express Gigabit Ethernet controller +GENERAL.DRIVER: r8169 +GENERAL.DRIVER-VERSION: 2.3LK-NAPI +GENERAL.FIRMWARE-VERSION: +GENERAL.HWADDR: D4:BE:D9:34:67:68 +GENERAL.STATE: 100 (connected) +GENERAL.REASON: 0 (No reason given) +GENERAL.UDI: /sys/devices/pci0000:00/0000:00:1c.0/0000:07:00.0/net/eth0 +GENERAL.IP-IFACE: eth0 +GENERAL.NM-MANAGED: yes +GENERAL.AUTOCONNECT: yes +GENERAL.FIRMWARE-MISSING: no +GENERAL.CONNECTION: /org/freedesktop/NetworkManager/ActiveConnection/9 +CAPABILITIES.CARRIER-DETECT: yes +CAPABILITIES.SPEED: 1000 Mb/s +CONNECTIONS.AVAILABLE-CONNECTION-PATHS: /org/freedesktop/NetworkManager/Settings/{1,2} +CONNECTIONS.AVAILABLE-CONNECTIONS[1]: acafdb99-23e4-49e5-a3e6-0a00575312b5 | Office +CONNECTIONS.AVAILABLE-CONNECTIONS[2]: d6aaee51-2ed8-4b05-82ac-365c9c74b44e | DHCP +WIRED-PROPERTIES.CARRIER: on +IP4.ADDRESS[1]: ip = 192.168.1.10, gw = 192.168.1.1 +IP4.DNS[1]: 192.168.1.1 diff --git a/spec/fixtures/unit/dhcp_servers/nmcli_wlan0_dhcp b/spec/fixtures/unit/dhcp_servers/nmcli_wlan0_dhcp new file mode 100644 index 0000000000..67d577a5d0 --- /dev/null +++ b/spec/fixtures/unit/dhcp_servers/nmcli_wlan0_dhcp @@ -0,0 +1,49 @@ +GENERAL.DEVICE: wlan0 +GENERAL.TYPE: 802-11-wireless +GENERAL.VENDOR: Intel Corporation +GENERAL.PRODUCT: Centrino Advanced-N 6235 AGN +GENERAL.DRIVER: iwlwifi +GENERAL.DRIVER-VERSION: 3.11.0-12-generic +GENERAL.FIRMWARE-VERSION: 18.168.6.1 +GENERAL.HWADDR: C4:85:08:06:2C:55 +GENERAL.STATE: 100 (connected) +GENERAL.REASON: 0 (No reason given) +GENERAL.UDI: /sys/devices/pci0000:00/0000:00:1c.2/0000:08:00.0/net/wlan0 +GENERAL.IP-IFACE: wlan0 +GENERAL.NM-MANAGED: yes +GENERAL.AUTOCONNECT: yes +GENERAL.FIRMWARE-MISSING: no +GENERAL.CONNECTION: /org/freedesktop/NetworkManager/ActiveConnection/10 +CAPABILITIES.CARRIER-DETECT: no +CAPABILITIES.SPEED: 13 Mb/s +CONNECTIONS.AVAILABLE-CONNECTION-PATHS: /org/freedesktop/NetworkManager/Settings/{3} +CONNECTIONS.AVAILABLE-CONNECTIONS[1]: 7a82c94b-afba-498b-9a62-30ebb335b365 | Auto OptusCD3_80ac32 +WIFI-PROPERTIES.WEP: yes +WIFI-PROPERTIES.WPA: yes +WIFI-PROPERTIES.WPA2: yes +WIFI-PROPERTIES.TKIP: yes +WIFI-PROPERTIES.CCMP: yes +WIFI-PROPERTIES.AP: yes +WIFI-PROPERTIES.ADHOC: yes +AP[1].SSID: 'TestSSid' +AP[1].BSSID: C8:F9:F9:BE:F1:4E +AP[1].MODE: Infrastructure +AP[1].FREQ: 5745 MHz +AP[1].RATE: 54 MB/s +AP[1].SIGNAL: 72 +AP[1].SECURITY: WPA WPA2 Enterprise +AP[1].ACTIVE: no +IP4.ADDRESS[1]: ip = 192.168.2.10/24, gw = 192.168.2.1 +IP4.DNS[1]: 192.168.2.1 +DHCP4.OPTION[1]: time_offset = 0 +DHCP4.OPTION[2]: expiry = 1401991971 +DHCP4.OPTION[3]: broadcast_address = 192.168.0.255 +DHCP4.OPTION[4]: dhcp_message_type = 5 +DHCP4.OPTION[5]: dhcp_lease_time = 9999999 +DHCP4.OPTION[6]: ip_address = 192.168.2.10 +DHCP4.OPTION[7]: subnet_mask = 255.255.255.0 +DHCP4.OPTION[8]: routers = 192.168.2.1 +DHCP4.OPTION[9]: domain_name_servers = 192.168.2.1 +DHCP4.OPTION[10]: network_number = 192.168.2.0 +DHCP4.OPTION[11]: default_ip_ttl = 64 +DHCP4.OPTION[12]: dhcp_server_identifier = 192.168.2.1 diff --git a/spec/fixtures/unit/dhcp_servers/nmcli_wlan0_static b/spec/fixtures/unit/dhcp_servers/nmcli_wlan0_static new file mode 100644 index 0000000000..2f47436453 --- /dev/null +++ b/spec/fixtures/unit/dhcp_servers/nmcli_wlan0_static @@ -0,0 +1,37 @@ +GENERAL.DEVICE: wlan0 +GENERAL.TYPE: 802-11-wireless +GENERAL.VENDOR: Intel Corporation +GENERAL.PRODUCT: Centrino Advanced-N 6235 AGN +GENERAL.DRIVER: iwlwifi +GENERAL.DRIVER-VERSION: 3.11.0-12-generic +GENERAL.FIRMWARE-VERSION: 18.168.6.1 +GENERAL.HWADDR: C4:85:08:06:2C:55 +GENERAL.STATE: 100 (connected) +GENERAL.REASON: 0 (No reason given) +GENERAL.UDI: /sys/devices/pci0000:00/0000:00:1c.2/0000:08:00.0/net/wlan0 +GENERAL.IP-IFACE: wlan0 +GENERAL.NM-MANAGED: yes +GENERAL.AUTOCONNECT: yes +GENERAL.FIRMWARE-MISSING: no +GENERAL.CONNECTION: /org/freedesktop/NetworkManager/ActiveConnection/10 +CAPABILITIES.CARRIER-DETECT: no +CAPABILITIES.SPEED: 13 Mb/s +CONNECTIONS.AVAILABLE-CONNECTION-PATHS: /org/freedesktop/NetworkManager/Settings/{3} +CONNECTIONS.AVAILABLE-CONNECTIONS[1]: 7a82c94b-afba-498b-9a62-30ebb335b365 | Auto OptusCD3_80ac32 +WIFI-PROPERTIES.WEP: yes +WIFI-PROPERTIES.WPA: yes +WIFI-PROPERTIES.WPA2: yes +WIFI-PROPERTIES.TKIP: yes +WIFI-PROPERTIES.CCMP: yes +WIFI-PROPERTIES.AP: yes +WIFI-PROPERTIES.ADHOC: yes +AP[1].SSID: 'TestSSid' +AP[1].BSSID: C8:F9:F9:BE:F1:4E +AP[1].MODE: Infrastructure +AP[1].FREQ: 5745 MHz +AP[1].RATE: 54 MB/s +AP[1].SIGNAL: 72 +AP[1].SECURITY: WPA WPA2 Enterprise +AP[1].ACTIVE: no +IP4.ADDRESS[1]: ip = 192.168.2.10/24, gw = 192.168.2.1 +IP4.DNS[1]: 192.168.2.1 diff --git a/spec/fixtures/unit/dhcp_servers/route b/spec/fixtures/unit/dhcp_servers/route new file mode 100644 index 0000000000..46ee6aefc3 --- /dev/null +++ b/spec/fixtures/unit/dhcp_servers/route @@ -0,0 +1,4 @@ +Kernel IP routing table +Destination Gateway Genmask Flags Metric Ref Use Iface +0.0.0.0 192.168.1.1 0.0.0.0 UG 0 0 0 eth0 +192.168.2.0 0.0.0.0 255.255.255.0 U 9 0 0 wlan0 diff --git a/spec/fixtures/unit/dhcp_servers/route_nogw b/spec/fixtures/unit/dhcp_servers/route_nogw new file mode 100644 index 0000000000..ae42372197 --- /dev/null +++ b/spec/fixtures/unit/dhcp_servers/route_nogw @@ -0,0 +1,4 @@ +Kernel IP routing table +Destination Gateway Genmask Flags Metric Ref Use Iface +192.168.1.0 0.0.0.0 255.255.255.0 UG 0 0 0 eth0 +192.168.2.0 0.0.0.0 255.255.255.0 U 9 0 0 wlan0 diff --git a/spec/unit/dhcp_servers_spec.rb b/spec/unit/dhcp_servers_spec.rb new file mode 100644 index 0000000000..d82e5e74f5 --- /dev/null +++ b/spec/unit/dhcp_servers_spec.rb @@ -0,0 +1,84 @@ +require 'spec_helper' + +describe "DHCP server facts" do + describe "on Linux OS's" do + before :each do + Facter::Util::Resolution.stubs(:exec).with("route -n").returns(my_fixture_read("route")) + end + + describe "with nmcli available" do + before :each do + Facter::Core::Execution.stubs(:which).with('nmcli').returns('/usr/bin/nmcli') + end + + describe "with a main interface configured with DHCP" do + before :each do + Facter::Util::Resolution.stubs(:exec).with("nmcli d").returns(my_fixture_read("nmcli_devices")) + Facter::Util::Resolution.stubs(:exec).with("nmcli d list iface eth0").returns(my_fixture_read("nmcli_eth0_dhcp")) + Facter::Util::Resolution.stubs(:exec).with("nmcli d list iface wlan0").returns(my_fixture_read("nmcli_wlan0_dhcp")) + end + + it "should produce a dhcp_servers fact that includes values for 'system' as well as each dhcp enabled interface" do + Facter.fact(:dhcp_servers).value.should == { 'system' => '192.168.1.1', 'eth0' => '192.168.1.1', 'wlan0' => '192.168.2.1' } + end + end + + describe "with a main interface NOT configured with DHCP" do + before :each do + Facter::Util::Resolution.stubs(:exec).with("nmcli d").returns(my_fixture_read("nmcli_devices")) + Facter::Util::Resolution.stubs(:exec).with("nmcli d list iface eth0").returns(my_fixture_read("nmcli_eth0_static")) + Facter::Util::Resolution.stubs(:exec).with("nmcli d list iface wlan0").returns(my_fixture_read("nmcli_wlan0_dhcp")) + end + + it "should a dhcp_servers fact that includes values for each dhcp enables interface and NO 'system' value" do + Facter.fact(:dhcp_servers).value.should == {'wlan0' => '192.168.2.1' } + end + end + + describe "with no default gateway" do + before :each do + Facter::Util::Resolution.stubs(:exec).with("route -n").returns(my_fixture_read("route_nogw")) + Facter::Util::Resolution.stubs(:exec).with("nmcli d").returns(my_fixture_read("nmcli_devices")) + Facter::Util::Resolution.stubs(:exec).with("nmcli d list iface eth0").returns(my_fixture_read("nmcli_eth0_dhcp")) + Facter::Util::Resolution.stubs(:exec).with("nmcli d list iface wlan0").returns(my_fixture_read("nmcli_wlan0_dhcp")) + end + + it "should a dhcp_servers fact that includes values for each dhcp enables interface and NO 'system' value" do + Facter.fact(:dhcp_servers).value.should == {'eth0' => '192.168.1.1', 'wlan0' => '192.168.2.1' } + end + end + + describe "with no DHCP enabled interfaces" do + before :each do + Facter::Util::Resolution.stubs(:exec).with("nmcli d").returns(my_fixture_read("nmcli_devices")) + Facter::Util::Resolution.stubs(:exec).with("nmcli d list iface eth0").returns(my_fixture_read("nmcli_eth0_static")) + Facter::Util::Resolution.stubs(:exec).with("nmcli d list iface wlan0").returns(my_fixture_read("nmcli_wlan0_static")) + end + + it "should not produce a dhcp_servers fact" do + Facter.fact(:dhcp_servers).value.should be_nil + end + end + + describe "with no CONNECTED devices" do + before :each do + Facter::Util::Resolution.stubs(:exec).with("nmcli d").returns(my_fixture_read("nmcli_devices_disconnected")) + end + + it "should not produce a dhcp_servers fact" do + Facter.fact(:dhcp_servers).value.should be_nil + end + end + end + + describe "without nmcli available" do + before :each do + Facter::Core::Execution.stubs(:which).with('nmcli').returns(nil) + end + + it "should not produce a dhcp_server fact" do + Facter.fact(:dhcp_servers).value.should be_nil + end + end + end +end From 74ac1fc3582589fc058185b1781666966c7546b5 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 1 May 2014 12:04:20 -0700 Subject: [PATCH 1819/3753] (maint) Use Facter::Core::Execution for running commands --- lib/facter/util/dhcp_servers.rb | 6 +++--- spec/unit/dhcp_servers_spec.rb | 30 +++++++++++++++--------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/facter/util/dhcp_servers.rb b/lib/facter/util/dhcp_servers.rb index f1aabd9c5d..5cf8259adb 100644 --- a/lib/facter/util/dhcp_servers.rb +++ b/lib/facter/util/dhcp_servers.rb @@ -1,11 +1,11 @@ module Facter::Util::DHCPServers def self.gateway_device - Facter::Util::Resolution.exec("route -n").scan(/^0\.0\.0\.0.*?(\S+)$/).flatten.first + Facter::Core::Execution.exec("route -n").scan(/^0\.0\.0\.0.*?(\S+)$/).flatten.first end def self.devices if Facter::Core::Execution.which('nmcli') - Facter::Util::Resolution.exec("nmcli d").split("\n").select {|d| d =~ /\sconnected/i }.collect{ |line| line.split[0] } + Facter::Core::Execution.exec("nmcli d").split("\n").select {|d| d =~ /\sconnected/i }.collect{ |line| line.split[0] } else [] end @@ -13,7 +13,7 @@ def self.devices def self.device_dhcp_server(device) if Facter::Core::Execution.which('nmcli') - Facter::Util::Resolution.exec("nmcli d list iface #{device}").scan(/dhcp_server_identifier.*?(\d+\.\d+\.\d+\.\d+)$/).flatten.first + Facter::Core::Execution.exec("nmcli d list iface #{device}").scan(/dhcp_server_identifier.*?(\d+\.\d+\.\d+\.\d+)$/).flatten.first end end end diff --git a/spec/unit/dhcp_servers_spec.rb b/spec/unit/dhcp_servers_spec.rb index d82e5e74f5..2c5e141ce3 100644 --- a/spec/unit/dhcp_servers_spec.rb +++ b/spec/unit/dhcp_servers_spec.rb @@ -3,7 +3,7 @@ describe "DHCP server facts" do describe "on Linux OS's" do before :each do - Facter::Util::Resolution.stubs(:exec).with("route -n").returns(my_fixture_read("route")) + Facter::Core::Execution.stubs(:exec).with("route -n").returns(my_fixture_read("route")) end describe "with nmcli available" do @@ -13,9 +13,9 @@ describe "with a main interface configured with DHCP" do before :each do - Facter::Util::Resolution.stubs(:exec).with("nmcli d").returns(my_fixture_read("nmcli_devices")) - Facter::Util::Resolution.stubs(:exec).with("nmcli d list iface eth0").returns(my_fixture_read("nmcli_eth0_dhcp")) - Facter::Util::Resolution.stubs(:exec).with("nmcli d list iface wlan0").returns(my_fixture_read("nmcli_wlan0_dhcp")) + Facter::Core::Execution.stubs(:exec).with("nmcli d").returns(my_fixture_read("nmcli_devices")) + Facter::Core::Execution.stubs(:exec).with("nmcli d list iface eth0").returns(my_fixture_read("nmcli_eth0_dhcp")) + Facter::Core::Execution.stubs(:exec).with("nmcli d list iface wlan0").returns(my_fixture_read("nmcli_wlan0_dhcp")) end it "should produce a dhcp_servers fact that includes values for 'system' as well as each dhcp enabled interface" do @@ -25,9 +25,9 @@ describe "with a main interface NOT configured with DHCP" do before :each do - Facter::Util::Resolution.stubs(:exec).with("nmcli d").returns(my_fixture_read("nmcli_devices")) - Facter::Util::Resolution.stubs(:exec).with("nmcli d list iface eth0").returns(my_fixture_read("nmcli_eth0_static")) - Facter::Util::Resolution.stubs(:exec).with("nmcli d list iface wlan0").returns(my_fixture_read("nmcli_wlan0_dhcp")) + Facter::Core::Execution.stubs(:exec).with("nmcli d").returns(my_fixture_read("nmcli_devices")) + Facter::Core::Execution.stubs(:exec).with("nmcli d list iface eth0").returns(my_fixture_read("nmcli_eth0_static")) + Facter::Core::Execution.stubs(:exec).with("nmcli d list iface wlan0").returns(my_fixture_read("nmcli_wlan0_dhcp")) end it "should a dhcp_servers fact that includes values for each dhcp enables interface and NO 'system' value" do @@ -37,10 +37,10 @@ describe "with no default gateway" do before :each do - Facter::Util::Resolution.stubs(:exec).with("route -n").returns(my_fixture_read("route_nogw")) - Facter::Util::Resolution.stubs(:exec).with("nmcli d").returns(my_fixture_read("nmcli_devices")) - Facter::Util::Resolution.stubs(:exec).with("nmcli d list iface eth0").returns(my_fixture_read("nmcli_eth0_dhcp")) - Facter::Util::Resolution.stubs(:exec).with("nmcli d list iface wlan0").returns(my_fixture_read("nmcli_wlan0_dhcp")) + Facter::Core::Execution.stubs(:exec).with("route -n").returns(my_fixture_read("route_nogw")) + Facter::Core::Execution.stubs(:exec).with("nmcli d").returns(my_fixture_read("nmcli_devices")) + Facter::Core::Execution.stubs(:exec).with("nmcli d list iface eth0").returns(my_fixture_read("nmcli_eth0_dhcp")) + Facter::Core::Execution.stubs(:exec).with("nmcli d list iface wlan0").returns(my_fixture_read("nmcli_wlan0_dhcp")) end it "should a dhcp_servers fact that includes values for each dhcp enables interface and NO 'system' value" do @@ -50,9 +50,9 @@ describe "with no DHCP enabled interfaces" do before :each do - Facter::Util::Resolution.stubs(:exec).with("nmcli d").returns(my_fixture_read("nmcli_devices")) - Facter::Util::Resolution.stubs(:exec).with("nmcli d list iface eth0").returns(my_fixture_read("nmcli_eth0_static")) - Facter::Util::Resolution.stubs(:exec).with("nmcli d list iface wlan0").returns(my_fixture_read("nmcli_wlan0_static")) + Facter::Core::Execution.stubs(:exec).with("nmcli d").returns(my_fixture_read("nmcli_devices")) + Facter::Core::Execution.stubs(:exec).with("nmcli d list iface eth0").returns(my_fixture_read("nmcli_eth0_static")) + Facter::Core::Execution.stubs(:exec).with("nmcli d list iface wlan0").returns(my_fixture_read("nmcli_wlan0_static")) end it "should not produce a dhcp_servers fact" do @@ -62,7 +62,7 @@ describe "with no CONNECTED devices" do before :each do - Facter::Util::Resolution.stubs(:exec).with("nmcli d").returns(my_fixture_read("nmcli_devices_disconnected")) + Facter::Core::Execution.stubs(:exec).with("nmcli d").returns(my_fixture_read("nmcli_devices_disconnected")) end it "should not produce a dhcp_servers fact" do From 60cba508726bcbcb1ede6b894767cdf9bea894bb Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Thu, 1 May 2014 12:04:40 -0700 Subject: [PATCH 1820/3753] (maint) Stub out kernel fact for linux dhcp server tests --- spec/unit/dhcp_servers_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/unit/dhcp_servers_spec.rb b/spec/unit/dhcp_servers_spec.rb index 2c5e141ce3..abda8d06f8 100644 --- a/spec/unit/dhcp_servers_spec.rb +++ b/spec/unit/dhcp_servers_spec.rb @@ -3,6 +3,7 @@ describe "DHCP server facts" do describe "on Linux OS's" do before :each do + Facter.fact(:kernel).stubs(:value).returns 'Linux' Facter::Core::Execution.stubs(:exec).with("route -n").returns(my_fixture_read("route")) end From 28a341ff360dfff5388ab59a91a0a268c1b202c5 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Tue, 29 Apr 2014 17:00:20 -0700 Subject: [PATCH 1821/3753] (CFACT-28) Add DMI facts for Linux and OSX. Adding the following DMI facts for OSX: - productname Adding the following DMI facts for Linux: - bios_vendor - bios_version - bios_release_date - boardmanufacturer - boardproductname - boardserialnumber - manufacturer - productname - serialnumber - productuuid (renamed from facter-2 for clarity) - chassistype (renamed from facter-2 for clarity) --- lib/CMakeLists.txt | 2 + lib/inc/facter/facts/fact.hpp | 11 +++ lib/inc/facter/facts/linux/dmi_resolver.hpp | 28 ++++++ lib/inc/facter/facts/osx/dmi_resolver.hpp | 27 ++++++ lib/inc/facter/facts/posix/dmi_resolver.hpp | 47 ++++++++++ lib/inc/facter/util/file.hpp | 51 ++++++----- lib/src/facts/linux/block_device_resolver.cc | 12 ++- lib/src/facts/linux/dmi_resolver.cc | 95 ++++++++++++++++++++ lib/src/facts/linux/platform.cc | 2 + lib/src/facts/osx/dmi_resolver.cc | 40 +++++++++ lib/src/facts/osx/platform.cc | 2 + lib/src/util/file.cc | 42 ++++++--- 12 files changed, 324 insertions(+), 35 deletions(-) create mode 100644 lib/inc/facter/facts/linux/dmi_resolver.hpp create mode 100644 lib/inc/facter/facts/osx/dmi_resolver.hpp create mode 100644 lib/inc/facter/facts/posix/dmi_resolver.hpp create mode 100644 lib/src/facts/linux/dmi_resolver.cc create mode 100644 lib/src/facts/osx/dmi_resolver.cc diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index b4a3ae7e4e..bb2417364e 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -36,6 +36,7 @@ endif() # Set the platform-specific sources if ("${CMAKE_SYSTEM_NAME}" MATCHES "Darwin") set(LIBFACTER_PLATFORM_SOURCES + "${CMAKE_CURRENT_LIST_DIR}/src/facts/osx/dmi_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/bsd/networking_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/osx/networking_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/osx/platform.cc" @@ -45,6 +46,7 @@ elseif ("${CMAKE_SYSTEM_NAME}" MATCHES "Linux") set(LIBFACTER_PLATFORM_SOURCES "${CMAKE_CURRENT_LIST_DIR}/src/facts/bsd/networking_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/linux/block_device_resolver.cc" + "${CMAKE_CURRENT_LIST_DIR}/src/facts/linux/dmi_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/linux/lsb_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/linux/networking_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/linux/operating_system_resolver.cc" diff --git a/lib/inc/facter/facts/fact.hpp b/lib/inc/facter/facts/fact.hpp index 71721f1304..b22c8e1b9b 100644 --- a/lib/inc/facter/facts/fact.hpp +++ b/lib/inc/facter/facts/fact.hpp @@ -43,6 +43,17 @@ namespace facter { namespace facts { constexpr static char const* hardware_isa = "hardwareisa"; constexpr static char const* hardware_model = "hardwaremodel"; constexpr static char const* architecture = "architecture"; + constexpr static char const* bios_vendor = "bios_vendor"; + constexpr static char const* bios_version = "bios_version"; + constexpr static char const* bios_release_date = "bios_release_date"; + constexpr static char const* board_manufacturer = "boardmanufacturer"; + constexpr static char const* board_product_name = "boardproductname"; + constexpr static char const* board_serial_number = "boardserialnumber"; + constexpr static char const* manufacturer = "manufacturer"; + constexpr static char const* product_name = "productname"; + constexpr static char const* serial_number = "serialnumber"; + constexpr static char const* product_uuid = "productuuid"; + constexpr static char const* chassis_type = "chassistype"; }; }} // namespace facter::facts diff --git a/lib/inc/facter/facts/linux/dmi_resolver.hpp b/lib/inc/facter/facts/linux/dmi_resolver.hpp new file mode 100644 index 0000000000..ae56588f36 --- /dev/null +++ b/lib/inc/facter/facts/linux/dmi_resolver.hpp @@ -0,0 +1,28 @@ +#ifndef FACTER_FACTS_LINUX_DMI_RESOLVER_HPP_ +#define FACTER_FACTS_LINUX_DMI_RESOLVER_HPP_ + +#include "../posix/dmi_resolver.hpp" +#include + +namespace facter { namespace facts { namespace linux { + + /** + * Responsible for resolving DMI facts. + */ + struct dmi_resolver : posix::dmi_resolver + { + protected: + /** + * Called to resolve all facts the resolver is responsible for. + * @param facts The fact map that is resolving facts. + */ + virtual void resolve_facts(fact_map& facts); + + private: + static std::string get_chassis_description(std::string const& type); + }; + +}}} // namespace facter::facts::linux + +#endif // FACTER_FACTS_LINUX_DMI_RESOLVER_HPP_ + diff --git a/lib/inc/facter/facts/osx/dmi_resolver.hpp b/lib/inc/facter/facts/osx/dmi_resolver.hpp new file mode 100644 index 0000000000..e2b98a80fd --- /dev/null +++ b/lib/inc/facter/facts/osx/dmi_resolver.hpp @@ -0,0 +1,27 @@ +#ifndef FACTER_FACTS_OSX_DMI_RESOLVER_HPP_ +#define FACTER_FACTS_OSX_DMI_RESOLVER_HPP_ + +#include "../posix/dmi_resolver.hpp" + +namespace facter { namespace facts { namespace osx { + + /** + * Responsible for resolving DMI facts. + */ + struct dmi_resolver : posix::dmi_resolver + { + protected: + /** + * Called to resolve all facts the resolver is responsible for. + * @param facts The fact map that is resolving facts. + */ + virtual void resolve_facts(fact_map& facts); + + private: + void resolve_product_name(fact_map& facts); + }; + +}}} // namespace facter::facts::osx + +#endif // FACTER_FACTS_OSX_DMI_RESOLVER_HPP_ + diff --git a/lib/inc/facter/facts/posix/dmi_resolver.hpp b/lib/inc/facter/facts/posix/dmi_resolver.hpp new file mode 100644 index 0000000000..f725e5480c --- /dev/null +++ b/lib/inc/facter/facts/posix/dmi_resolver.hpp @@ -0,0 +1,47 @@ +#ifndef FACTER_FACTS_POSIX_DMI_RESOLVER_HPP_ +#define FACTER_FACTS_POSIX_DMI_RESOLVER_HPP_ + +#include "../fact_resolver.hpp" +#include "../fact.hpp" + +namespace facter { namespace facts { namespace posix { + + /** + * Responsible for resolving DMI facts. + */ + struct dmi_resolver : fact_resolver + { + /** + * Constructs the dmi_resolver. + */ + dmi_resolver() : + fact_resolver( + "desktop management information", + { + fact::bios_vendor, + fact::bios_version, + fact::bios_release_date, + fact::board_manufacturer, + fact::board_product_name, + fact::board_serial_number, + fact::manufacturer, + fact::product_name, + fact::serial_number, + fact::product_uuid, + fact::chassis_type, + }) + { + } + + protected: + /** + * Called to resolve all facts the resolver is responsible for. + * @param facts The fact map that is resolving facts. + */ + virtual void resolve_facts(fact_map& facts) = 0; + }; + +}}} // namespace facter::facts::posix + +#endif // FACTER_FACTS_POSIX_DMI_RESOLVER_HPP_ + diff --git a/lib/inc/facter/util/file.hpp b/lib/inc/facter/util/file.hpp index 63d6fdb74e..361d9b807d 100644 --- a/lib/inc/facter/util/file.hpp +++ b/lib/inc/facter/util/file.hpp @@ -2,29 +2,40 @@ #define FACTER_UTIL_FILE_HPP_ #include +#include -namespace facter { namespace util { +namespace facter { namespace util { namespace file { /** - * Utility type for interacting with files. + * Reads the entire contents of the given file into a string. + * @param path The path of the file to read. + * @return Returns the file contents as a string. */ - struct file - { - /** - * Reads the entire contents of the given file into a string. - * @param path The path of the file to read. - * @return Returns the file contents as a string or empty string if the file cannot be read. - */ - static std::string read(std::string const& path); - - /** - * Reads the first line of the given file into a string. - * @param path The path of the file to read. - * @return Returns the first line of the file or an empty string if the file cannot be read. - */ - static std::string read_first_line(std::string const& path); - }; - -}} // namespace facter::util + std::string read(std::string const& path); + + /** + * Reads the entire contents of the given file into a string. + * @param path The path of the file to read. + * @param contents The returned file contents. + * @return Returns true if the contents were read or false if the file is not readable. + */ + bool read(std::string const& path, std::string& contents); + + /** + * Reads the first line of the given file into a string. + * @param path The path of the file to read. + * @return Returns the first line of the file. + */ + std::string read_first_line(std::string const& path); + + /** + * Reads the first line of the given file into a string. + * @param path The path of the file to read. + * @param line The returned first line of the file. + * @return Returns true if the line was read or false if the file is not readable. + */ + bool read_first_line(std::string const& path, std::string& line); + +}}} // namespace facter::util::file #endif // FACTER_UTIL_FILE_HPP_ diff --git a/lib/src/facts/linux/block_device_resolver.cc b/lib/src/facts/linux/block_device_resolver.cc index 44a5040bdc..9bc586b815 100644 --- a/lib/src/facts/linux/block_device_resolver.cc +++ b/lib/src/facts/linux/block_device_resolver.cc @@ -24,14 +24,22 @@ namespace facter { namespace facts { namespace linux { bs::error_code ec; if (!is_directory(devices_directory, ec)) { - LOG_DEBUG("directory %1%: %2%: block device facts are unavailable.", devices_directory, ec.message()); + LOG_DEBUG("%1%: %2%: block device facts are unavailable.", devices_directory, ec.message()); return; } // Enumerate all block devices ostringstream devices; directory_iterator end; - directory_iterator it(devices_directory); + directory_iterator it; + + try { + it = directory_iterator(devices_directory); + } catch (filesystem_error& ex) { + LOG_DEBUG("%1%: %2%: block device facts are unavailable.", devices_directory, ex.what()); + return; + } + for (; it != end; ++it) { bs::error_code ec; if (!is_directory(it->status())) { diff --git a/lib/src/facts/linux/dmi_resolver.cc b/lib/src/facts/linux/dmi_resolver.cc new file mode 100644 index 0000000000..6c6b74c6e5 --- /dev/null +++ b/lib/src/facts/linux/dmi_resolver.cc @@ -0,0 +1,95 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace facter::util; +using namespace boost::filesystem; +namespace bs = boost::system; + +LOG_DECLARE_NAMESPACE("facts.osx.dmi"); + +namespace facter { namespace facts { namespace linux { + + void dmi_resolver::resolve_facts(fact_map& facts) + { + static map dmi_files { + { string(fact::bios_vendor), "/sys/class/dmi/id/bios_vendor" }, + { string(fact::bios_version), "/sys/class/dmi/id/bios_version" }, + { string(fact::bios_release_date), "/sys/class/dmi/id/bios_date" }, + { string(fact::board_manufacturer), "/sys/class/dmi/id/board_vendor" }, + { string(fact::board_product_name), "/sys/class/dmi/id/board_name" }, + { string(fact::board_serial_number), "/sys/class/dmi/id/board_serial" }, + { string(fact::manufacturer), "/sys/class/dmi/id/sys_vendor" }, + { string(fact::product_name), "/sys/class/dmi/id/product_name" }, + { string(fact::serial_number), "/sys/class/dmi/id/product_serial" }, + { string(fact::product_uuid), "/sys/class/dmi/id/product_uuid" }, + { string(fact::chassis_type), "/sys/class/dmi/id/chassis_type" }, + }; + + for (auto const& kvp : dmi_files) { + bs::error_code ec; + if (!is_regular_file(kvp.second, ec)) { + LOG_DEBUG("%1%: %2%: %3% fact is unavailable.", kvp.second, ec.message(), kvp.first); + continue; + } + + string value; + if (!file::read(kvp.second, value)) { + LOG_DEBUG("%1%: permission denied: %2% fact is unavailable.", kvp.second, kvp.first); + continue; + } + + trim(value); + + // If this is the chassis fact, get the description string + if (kvp.first == fact::chassis_type) { + value = get_chassis_description(value); + } + + facts.add(string(kvp.first), make_value(move(value))); + } + } + + string dmi_resolver::get_chassis_description(string const& type) + { + static map descriptions = { + { "1", "Other" }, + // 2 is Unknown, which we'll output if it's not in the map anyway + { "3", "Desktop" }, + { "4", "Low Profile Desktop" }, + { "5", "Pizza Box" }, + { "6", "Mini Tower" }, + { "7", "Tower" }, + { "8", "Portable" }, + { "9", "Laptop" }, + { "10", "Notebook" }, + { "11", "Hand Held" }, + { "12", "Docking Station" }, + { "13", "All in One" }, + { "14", "Sub Notebook" }, + { "15", "Space-Saving" }, + { "16", "Lunch Box" }, + { "17", "Main System Chassis" }, + { "18", "Expansion Chassis" }, + { "19", "SubChassis" }, + { "20", "Bus Expansion Chassis" }, + { "21", "Peripheral Chassis" }, + { "22", "Storage Chassis" }, + { "23", "Rack Mount Chassis" }, + { "24", "Sealed-Case PC" }, + }; + + auto it = descriptions.find(type); + if (it != descriptions.end()) { + return it->second; + } + return "Unknown"; + } + +}}} // namespace facter::facts::linux diff --git a/lib/src/facts/linux/platform.cc b/lib/src/facts/linux/platform.cc index bc6686e25f..50eded39f1 100644 --- a/lib/src/facts/linux/platform.cc +++ b/lib/src/facts/linux/platform.cc @@ -4,6 +4,7 @@ #include #include #include +#include #include using namespace std; @@ -17,6 +18,7 @@ namespace facter { namespace facts { facts.add(make_shared()); facts.add(make_shared()); facts.add(make_shared()); + facts.add(make_shared()); facts.add(make_shared()); } diff --git a/lib/src/facts/osx/dmi_resolver.cc b/lib/src/facts/osx/dmi_resolver.cc new file mode 100644 index 0000000000..7d13645bb1 --- /dev/null +++ b/lib/src/facts/osx/dmi_resolver.cc @@ -0,0 +1,40 @@ +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +LOG_DECLARE_NAMESPACE("facts.osx.dmi"); + +namespace facter { namespace facts { namespace osx { + + void dmi_resolver::resolve_facts(fact_map& facts) + { + // We only support getting the product name on OSX + resolve_product_name(facts); + } + + void dmi_resolver::resolve_product_name(fact_map& facts) + { + int mib[] = { CTL_HW, HW_MODEL }; + size_t length = 0; + + if (sysctl(mib, sizeof(mib) / sizeof(mib[0]), nullptr, &length, nullptr, 0) != 0) { + LOG_DEBUG("sysctl failed: %1% (%2%): %3% fact is unavailable.", strerror(errno), errno, fact::product_name); + return; + } + + vector model_name(length); + if (sysctl(mib, sizeof(mib) / sizeof(mib[0]), model_name.data(), &length, nullptr, 0) != 0) { + LOG_DEBUG("sysctl failed: %1% (%2%): %3% fact is unavailable.", strerror(errno), errno, fact::product_name); + return; + } + + facts.add(fact::product_name, make_value(model_name.data())); + } + +}}} // namespace facter::facts::osx diff --git a/lib/src/facts/osx/platform.cc b/lib/src/facts/osx/platform.cc index bac7d3aaca..d5e51c8d55 100644 --- a/lib/src/facts/osx/platform.cc +++ b/lib/src/facts/osx/platform.cc @@ -3,6 +3,7 @@ #include #include #include +#include using namespace std; @@ -14,6 +15,7 @@ namespace facter { namespace facts { facts.add(make_shared()); facts.add(make_shared()); facts.add(make_shared()); + facts.add(make_shared()); } }} // namespace facter::facts diff --git a/lib/src/util/file.cc b/lib/src/util/file.cc index 3f714723e6..190836fd71 100644 --- a/lib/src/util/file.cc +++ b/lib/src/util/file.cc @@ -4,26 +4,42 @@ using namespace std; -namespace facter { namespace util { +namespace facter { namespace util { namespace file { - string file::read(string const& path) + string read(string const& path) + { + string contents; + if (!read(path, contents)) { + return {}; + } + return contents; + } + + bool read(string const& path, string& contents) { ifstream in(path, ios::in | ios::binary); - ostringstream contents; - if (in) { - contents << in.rdbuf(); + ostringstream buffer; + if (!in) { + return false; } - return contents.str(); + buffer << in.rdbuf(); + contents = buffer.str(); + return true; } - string file::read_first_line(string const& path) + string read_first_line(string const& path) { - ifstream in(path); - string value; - if (getline(in, value)) { - return value; + string line; + if (!read_first_line(path, line)) { + return {}; } - return {}; + return line; + } + + bool read_first_line(string const& path, string& line) + { + ifstream in(path); + return static_cast(getline(in, line)); } -}} // namespace facter::util +}}} // namespace facter::util::file From ba1ed9b2bc6aa7127eb30b01719697ba07cf2336 Mon Sep 17 00:00:00 2001 From: Jasper Lievisse Adriaanse Date: Tue, 15 Apr 2014 14:07:33 +0200 Subject: [PATCH 1822/3753] (FACT-477) On OpenBSD, when running on oVirt, correctly set is_virtual/virtual facts --- lib/facter/virtual.rb | 1 + spec/unit/virtual_spec.rb | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 516db2e578..9f195ea4a1 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -105,6 +105,7 @@ next "vmware" if lines.any? {|l| l =~ /VMware/ } next "virtualbox" if lines.any? {|l| l =~ /VirtualBox/ } next "xenhvm" if lines.any? {|l| l =~ /HVM domU/ } + next "ovirt" if lines.any? {|l| l =~ /oVirt Node/ } end end end diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index fe0aa0d6ee..16f08d6a27 100755 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -279,6 +279,11 @@ Facter::Core::Execution.stubs(:exec).with('sysctl -n hw.product 2>/dev/null').returns("HVM domU") Facter.fact(:virtual).value.should == "xenhvm" end + + it "should be ovirt with oVirt Node product name from sysctl" do + Facter::Core::Execution.stubs(:exec).with('sysctl -n hw.product 2>/dev/null').returns("oVirt Node") + Facter.fact(:virtual).value.should == "ovirt" + end end describe "on Windows" do From 452ff6e390724c7637d4239a723daa6e66755ea9 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Sun, 4 May 2014 20:01:38 -0700 Subject: [PATCH 1823/3753] (cfact-21) Add integer_value support --- lib/inc/facter/facts/integer_value.hpp | 68 ++++++++++++++++++++++++++ lib/tests/CMakeLists.txt | 1 + lib/tests/facts/integer_value.cc | 26 ++++++++++ 3 files changed, 95 insertions(+) create mode 100644 lib/inc/facter/facts/integer_value.hpp create mode 100644 lib/tests/facts/integer_value.cc diff --git a/lib/inc/facter/facts/integer_value.hpp b/lib/inc/facter/facts/integer_value.hpp new file mode 100644 index 0000000000..9062799605 --- /dev/null +++ b/lib/inc/facter/facts/integer_value.hpp @@ -0,0 +1,68 @@ +#ifndef FACTER_FACTS_INTEGER_VALUE_HPP_ +#define FACTER_FACTS_INTEGER_VALUE_HPP_ + +#include + +#include "value.hpp" + +namespace facter { namespace facts { + + /** + * Represents a simple integer value. + */ + struct integer_value : value + { + /** + * Constructs a integer_value. + * @param value The integer value. + */ + explicit integer_value(int64_t value) : + _value(value) + { + } + + /** + * Constructs a integer_value. + * @param value The integer value. + */ + explicit integer_value(std::string const& value) + { + try + { + _value = boost::lexical_cast(value); + } + catch (const boost::bad_lexical_cast& e) + { + // TODO: warn? + _value = 0; + } + } + + // Force non-copyable + integer_value(integer_value const&) = delete; + integer_value& operator=(integer_value const&) = delete; + + // Allow movable + integer_value(integer_value&&) = default; + integer_value& operator=(integer_value&&) = default; + + /** + * Converts the value to a string representation. + * @return Returns the string representation of the value. + */ + std::string to_string() const { return std::to_string(_value); } + + /** + * Gets the integer value. + * @return Returns the integer value. + */ + int64_t const& value() const { return _value; } + + private: + int64_t _value; + }; + +}} // namespace facter::facts + +#endif // FACTER_FACTS_INTEGER_VALUE_HPP_ + diff --git a/lib/tests/CMakeLists.txt b/lib/tests/CMakeLists.txt index 4090dec70a..a76fa92b38 100644 --- a/lib/tests/CMakeLists.txt +++ b/lib/tests/CMakeLists.txt @@ -11,6 +11,7 @@ endif() # Set the common (platform-independent) sources set(LIBFACTER_TESTS_COMMON_SOURCES + "${CMAKE_CURRENT_LIST_DIR}/facts/integer_value.cc" "${CMAKE_CURRENT_LIST_DIR}/util/string.cc" ) diff --git a/lib/tests/facts/integer_value.cc b/lib/tests/facts/integer_value.cc new file mode 100644 index 0000000000..826303c567 --- /dev/null +++ b/lib/tests/facts/integer_value.cc @@ -0,0 +1,26 @@ +#include +#include + +using namespace facter::facts; + +TEST(facter_facts_integer_value, to_string) { + // small integer + integer_value foo(42); + ASSERT_EQ("42", foo.to_string()); + + // very large integer but in range + int64_t large_int = 1LL << 62; + integer_value large_int_value(large_int); + ASSERT_EQ("4611686018427387904", large_int_value.to_string()); +} + +TEST(facter_facts_integer_value, string_constructor) { + // string integer + integer_value foo("42"); + ASSERT_EQ(42, foo.value()); + + // string not-an-integer + // TODO: expect a warning log message + integer_value not_an_integer("i am not an integer"); + ASSERT_EQ(0, not_an_integer.value()); +} From 65c79269aa92c6ebbc4a4ce33160dd49a2d3d55a Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Sun, 4 May 2014 20:03:23 -0700 Subject: [PATCH 1824/3753] (cfact-21) Add a posix resolver for uptime facts --- lib/CMakeLists.txt | 1 + lib/inc/facter/facts/fact.hpp | 17 +++ .../facter/facts/posix/uptime_resolver.hpp | 71 +++++++++++ lib/src/facts/posix/uptime_resolver.cc | 118 ++++++++++++++++++ lib/tests/CMakeLists.txt | 1 + lib/tests/facts/posix/uptime_resolver.cc | 47 +++++++ 6 files changed, 255 insertions(+) create mode 100644 lib/inc/facter/facts/posix/uptime_resolver.hpp create mode 100644 lib/src/facts/posix/uptime_resolver.cc create mode 100644 lib/tests/facts/posix/uptime_resolver.cc diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index bb2417364e..2e4e5719e1 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -29,6 +29,7 @@ if (UNIX) "${CMAKE_CURRENT_LIST_DIR}/src/facts/posix/networking_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/posix/operating_system_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/posix/processor_resolver.cc" + "${CMAKE_CURRENT_LIST_DIR}/src/facts/posix/uptime_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/logging/posix/logging.cc" ) endif() diff --git a/lib/inc/facter/facts/fact.hpp b/lib/inc/facter/facts/fact.hpp index b22c8e1b9b..9026169a85 100644 --- a/lib/inc/facter/facts/fact.hpp +++ b/lib/inc/facter/facts/fact.hpp @@ -8,10 +8,13 @@ namespace facter { namespace facts { */ struct fact { + // kernel facts constexpr static char const* kernel = "kernel"; constexpr static char const* kernel_version = "kernelversion"; constexpr static char const* kernel_release = "kernelrelease"; constexpr static char const* kernel_major_version = "kernelmajversion"; + + // operating system facts constexpr static char const* operating_system = "operatingsystem"; constexpr static char const* os_family = "osfamily"; constexpr static char const* operating_system_release = "operatingsystemrelease"; @@ -24,6 +27,8 @@ namespace facter { namespace facts { constexpr static char const* lsb_dist_major_release = "lsbmajdistrelease"; constexpr static char const* lsb_dist_minor_release = "lsbminordistrelease"; constexpr static char const* lsb_release = "lsbrelease"; + + // network facts constexpr static char const* ipaddress = "ipaddress"; constexpr static char const* ipaddress6 = "ipaddress6"; constexpr static char const* mtu = "mtu"; @@ -35,11 +40,17 @@ namespace facter { namespace facts { constexpr static char const* interfaces = "interfaces"; constexpr static char const* domain = "domain"; constexpr static char const* fqdn = "fqdn"; + + // block device facts constexpr static char const* block_device = "blockdevice"; constexpr static char const* block_devices = "blockdevices"; + + // processor facts constexpr static char const* processor = "processor"; constexpr static char const* processor_count = "processorcount"; constexpr static char const* physical_processor_count = "physicalprocessorcount"; + + // dmidecode facts constexpr static char const* hardware_isa = "hardwareisa"; constexpr static char const* hardware_model = "hardwaremodel"; constexpr static char const* architecture = "architecture"; @@ -54,6 +65,12 @@ namespace facter { namespace facts { constexpr static char const* serial_number = "serialnumber"; constexpr static char const* product_uuid = "productuuid"; constexpr static char const* chassis_type = "chassistype"; + + // uptime facts + constexpr static char const* uptime = "uptime"; + constexpr static char const* uptime_days = "uptime_days"; + constexpr static char const* uptime_hours = "uptime_hours"; + constexpr static char const* uptime_seconds = "uptime_seconds"; }; }} // namespace facter::facts diff --git a/lib/inc/facter/facts/posix/uptime_resolver.hpp b/lib/inc/facter/facts/posix/uptime_resolver.hpp new file mode 100644 index 0000000000..c19aa760f0 --- /dev/null +++ b/lib/inc/facter/facts/posix/uptime_resolver.hpp @@ -0,0 +1,71 @@ +#ifndef FACTER_FACTS_POSIX_UPTIME_RESOLVER_HPP_ +#define FACTER_FACTS_POSIX_UPTIME_RESOLVER_HPP_ + +#include "../fact_resolver.hpp" +#include "../fact.hpp" + +namespace facter { namespace facts { namespace posix { + + /** + * Responsible for resolving uptime facts. + */ + struct uptime_resolver : fact_resolver + { + /** + * Constructs the uptime_resolver. + */ + uptime_resolver() : + fact_resolver( + "uptime", + { + fact::uptime, + fact::uptime_days, + fact::uptime_hours, + fact::uptime_seconds + }) + { + } + + /** + * Utility function to convert the output of the uptime executable + * to an int number of seconds. + * @param output The output of the uptime executable. + * @return Returns the number of uptime seconds. + */ + static int parse_executable_uptime(std::string const& output); + + protected: + /** + * Called to resolve all facts the resolver is responsible for. + * @param facts The fact map that is resolving facts. + */ + virtual void resolve_facts(fact_map& facts); + /** + * Called to resolve the uptime fact. + * @param facts The fact map that is resolving facts. + */ + virtual void resolve_uptime(fact_map& facts); + /** + * Called to resolve the uptime days fact. + * @param facts The fact map that is resolving facts. + */ + virtual void resolve_uptime_days(fact_map& facts); + /** + * Called to resolve the uptime hours fact. + * @param facts The fact map that is resolving facts. + */ + virtual void resolve_uptime_hours(fact_map& facts); + /** + * Called to resolve the uptime seconds fact. + * @param facts The fact map that is resolving facts. + */ + virtual void resolve_uptime_seconds(fact_map& facts); + + private: + int executable_uptime(); + }; + +}}} // namespace facter::facts::posix + +#endif // FACTER_FACTS_POSIX_UPTIME_RESOLVER_HPP_ + diff --git a/lib/src/facts/posix/uptime_resolver.cc b/lib/src/facts/posix/uptime_resolver.cc new file mode 100644 index 0000000000..1485534197 --- /dev/null +++ b/lib/src/facts/posix/uptime_resolver.cc @@ -0,0 +1,118 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using boost::format; +using namespace facter::util; +using namespace facter::execution; + +namespace facter { namespace facts { namespace posix { + + void uptime_resolver::resolve_facts(fact_map& facts) + { + // Resolve all uptime-related facts + resolve_uptime_seconds(facts); // must be first b/c the following facts are based on this + resolve_uptime_hours(facts); + resolve_uptime_days(facts); + resolve_uptime(facts); + } + + void uptime_resolver::resolve_uptime_seconds(fact_map& facts) + { + int value = executable_uptime(); + facts.add(fact::uptime_seconds, make_value(value)); + } + + void uptime_resolver::resolve_uptime_hours(fact_map& facts) + { + auto uptime_seconds = facts.get(fact::uptime_seconds); + if (!uptime_seconds) { + return; + } + int uptime_hours = uptime_seconds->value() / (60 * 60); + string value = to_string(uptime_hours); + facts.add(fact::uptime_hours, make_value(value)); + } + + void uptime_resolver::resolve_uptime_days(fact_map& facts) + { + auto uptime_seconds = facts.get(fact::uptime_seconds); + if (!uptime_seconds) { + return; + } + int uptime_days = uptime_seconds->value() / (60 * 60 * 24); + string value = to_string(uptime_days); + facts.add(fact::uptime_days, make_value(value)); + } + + void uptime_resolver::resolve_uptime(fact_map& facts) + { + auto uptime_seconds = facts.get(fact::uptime_seconds); + if (!uptime_seconds) { + return; + } + int seconds = uptime_seconds->value(); + + int days = seconds / (60 * 60 * 24); + int hours = (seconds / (60 * 60)) % 24; + int minutes = (seconds / 60) % 60; + + string value; + switch (days) { + case 0: + value = (format("%%d:%02d hours") % hours % minutes).str(); + break; + case 1: + value = "1 day"; + break; + default: + value = (format("%d days") % days).str(); + } + facts.add(fact::uptime, make_value(std::move(value))); + } + + // call the uptime executable + int uptime_resolver::executable_uptime() + { + string uptime_output = execute("uptime"); + if (uptime_output.empty()) { + return 0; + } + return parse_executable_uptime(uptime_output); + } + + // parse the output from the uptime executable + int uptime_resolver::parse_executable_uptime(string const& output) + { + // This regex parsing is directly ported from facter: + // https://github.com/puppetlabs/facter/blob/2.0.1/lib/facter/util/uptime.rb#L42-L60 + + int days, hours, minutes; + + if (RE2::PartialMatch(output, "(\\d+) day(?:s|\\(s\\))?,\\s+(\\d+):(\\d+)", &days, &hours, &minutes)) { + return 86400 * days + 3600 * hours + 60 * minutes; + } else if (RE2::PartialMatch(output, "(\\d+) day(?:s|\\(s\\))?,\\s+(\\d+) hr(?:s|\\(s\\))?,", &days, &hours)) { + return 86400 * days + 3600 * hours; + } else if (RE2::PartialMatch(output, "(\\d+) day(?:s|\\(s\\))?,\\s+(\\d+) min(?:s|\\(s\\))?,", &days, &minutes)) { + return 86400 * days + 60 * minutes; + } else if (RE2::PartialMatch(output, "(\\d+) day(?:s|\\(s\\))?,", &days)) { + return 86400 * days; + } else if (RE2::PartialMatch(output, "up\\s+(\\d+):(\\d+),", &hours, &minutes)) { + return 3600 * hours + 60 * minutes; + } else if (RE2::PartialMatch(output, "(\\d+) hr(?:s|\\(s\\))?,", &hours)) { + return 3600 * hours; + } else if (RE2::PartialMatch(output, "(\\d+) min(?:s|\\(s\\))?,", &minutes)) { + return 60 * minutes; + } else { + return 0; + } + } + +}}} // namespace facter::facts::posix diff --git a/lib/tests/CMakeLists.txt b/lib/tests/CMakeLists.txt index a76fa92b38..b3252ee29b 100644 --- a/lib/tests/CMakeLists.txt +++ b/lib/tests/CMakeLists.txt @@ -12,6 +12,7 @@ endif() # Set the common (platform-independent) sources set(LIBFACTER_TESTS_COMMON_SOURCES "${CMAKE_CURRENT_LIST_DIR}/facts/integer_value.cc" + "${CMAKE_CURRENT_LIST_DIR}/facts/posix/uptime_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/util/string.cc" ) diff --git a/lib/tests/facts/posix/uptime_resolver.cc b/lib/tests/facts/posix/uptime_resolver.cc new file mode 100644 index 0000000000..ab843eaf1c --- /dev/null +++ b/lib/tests/facts/posix/uptime_resolver.cc @@ -0,0 +1,47 @@ +#include +#include +#include + +using namespace std; +using namespace facter::facts::posix; + +TEST(uptime_resolver, parse_executable_uptime) { + // The test cases are directly ported from facter specs: + // https://github.com/puppetlabs/facter/blob/2.0.1/spec/unit/util/uptime_spec.rb#L50-L98 + map test_cases = { + {" 4:42pm up 1 min(s), 0 users, load average: 0.95, 0.25, 0.09", 1*60}, + {"13:16 up 58 mins, 2 users, load average: 0.00, 0.02, 0.05", 58*60}, + {"13:18 up 1 hr, 1 user, load average: 0.58, 0.23, 0.14", 1*60*60 }, + {" 10:14pm up 3 hr(s), 0 users, load average: 0.00, 0.00, 0.00", 3*60*60 }, + {"14:18 up 2 hrs, 0 users, load average: 0.33, 0.27, 0.29", 2*60*60 }, + {" 9:01pm up 1:47, 0 users, load average: 0.00, 0.00, 0.00", 1*60*60 + 47*60}, + {"13:19 up 1:01, 1 user, load average: 0.10, 0.26, 0.21", 1*60*60 + 1*60}, + {"10:49 up 22:31, 0 users, load average: 0.26, 0.34, 0.27", 22*60*60 + 31*60}, + {"12:18 up 1 day, 0 users, load average: 0.74, 0.20, 0.10", 1*24*60*60 }, + {" 2:48pm up 1 day(s), 0 users, load average: 0.21, 0.20, 0.17", 1*24*60*60 }, + {"12:18 up 2 days, 0 users, load average: 0.50, 0.27, 0.16", 2*24*60*60 }, + {" 1:56pm up 25 day(s), 2 users, load average: 0.59, 0.56, 0.50", 25*24*60*60 }, + {" 1:29pm up 485 days, 0 users, load average: 0.00, 0.01, 0.01", 485*24*60*60 }, + {" 18:11:24 up 69 days, 0 min, 0 users, load average: 0.00, 0.00, 0.00", 69*24*60*60 }, + {"12:19 up 1 day, 1 min, 0 users, load average: 0.07, 0.16, 0.13", 1*24*60*60 + 1*60}, + {" 3:23pm up 25 day(s), 27 min(s), 2 users, load average: 0.49, 0.45, 0.46", 25*24*60*60 + 27*60}, + {" 02:42PM up 1 day, 39 mins, 0 users, load average: 1.49, 1.74, 1.80", 1*24*60*60 + 39*60}, + {" 18:13:13 up 245 days, 44 min, 1 user, load average: 0.00, 0.00, 0.00", 245*24*60*60 + 44*60}, + {" 6:09pm up 350 days, 2 min, 1 user, load average: 0.02, 0.03, 0.00", 350*24*60*60 + 2*60}, + {" 1:07pm up 174 day(s), 16 hr(s), 0 users, load average: 0.05, 0.04, 0.03", 174*24*60*60 + 16*60*60 }, + {" 02:34PM up 621 days, 18 hrs, 0 users, load average: 2.67, 2.52, 2.56", 621*24*60*60 + 18*60*60 }, + {" 3:30am up 108 days, 1 hr, 31 users, load average: 0.39, 0.40, 0.41", 108*24*60*60 + 1*60*60 }, + {"13:18 up 1 day, 1 hr, 0 users, load average: 0.78, 0.33, 0.18", 1*24*60*60 + 1*60*60 }, + {"14:18 up 1 day, 2 hrs, 0 users, load average: 1.17, 0.48, 0.41", 1*24*60*60 + 2*60*60 }, + {"15:56 up 152 days, 17 hrs, 0 users, load average: 0.01, 0.06, 0.07", 152*24*60*60 + 17*60*60 }, + {" 5:37pm up 25 days, 21:00, 0 users, load average: 0.01, 0.02, 0.00", 25*24*60*60 + 21*60*60 }, + {" 8:59pm up 94 day(s), 3:17, 46 users, load average: 0.66, 0.67, 0.70", 94*24*60*60 + 3*60*60 + 17*60}, + {" 3:01pm up 4496 day(s), 21:19, 32 users, load average: 0.61, 0.62, 0.62", 4496*24*60*60 + 21*60*60 + 19*60}, + {" 02:42PM up 41 days, 2:38, 0 users, load average: 0.38, 0.70, 0.55", 41*24*60*60 + 2*60*60 + 38*60}, + {" 18:13:29 up 25 days, 21:36, 0 users, load average: 0.00, 0.00, 0.00", 25*24*60*60 + 21*60*60 + 36*60}, + {" 13:36:05 up 118 days, 1:15, 1 user, load average: 0.00, 0.00, 0.00", 118*24*60*60 + 1*60*60 + 15*60} + }; + for (auto t : test_cases) { + EXPECT_EQ(uptime_resolver::parse_executable_uptime(t.first), t.second); + } +} From 1b23e94eccc71f8bc835e5188c6faeb41d454914 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Sun, 4 May 2014 22:48:37 +0000 Subject: [PATCH 1825/3753] (cfact-21) Add linux-specific resolver for uptime_seconds --- lib/CMakeLists.txt | 1 + .../facter/facts/linux/uptime_resolver.hpp | 23 +++++++++++++++++++ lib/src/facts/linux/platform.cc | 2 ++ lib/src/facts/linux/uptime_resolver.cc | 18 +++++++++++++++ 4 files changed, 44 insertions(+) create mode 100644 lib/inc/facter/facts/linux/uptime_resolver.hpp create mode 100644 lib/src/facts/linux/uptime_resolver.cc diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 2e4e5719e1..b631e601e3 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -51,6 +51,7 @@ elseif ("${CMAKE_SYSTEM_NAME}" MATCHES "Linux") "${CMAKE_CURRENT_LIST_DIR}/src/facts/linux/lsb_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/linux/networking_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/linux/operating_system_resolver.cc" + "${CMAKE_CURRENT_LIST_DIR}/src/facts/linux/uptime_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/linux/platform.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/linux/processor_resolver.cc" ) diff --git a/lib/inc/facter/facts/linux/uptime_resolver.hpp b/lib/inc/facter/facts/linux/uptime_resolver.hpp new file mode 100644 index 0000000000..18cde1b35b --- /dev/null +++ b/lib/inc/facter/facts/linux/uptime_resolver.hpp @@ -0,0 +1,23 @@ +#ifndef FACTER_FACTS_LINUX_UPTIME_RESOLVER_HPP_ +#define FACTER_FACTS_LINUX_UPTIME_RESOLVER_HPP_ + +#include "../posix/uptime_resolver.hpp" + +namespace facter { namespace facts { namespace linux { + + /** + * Responsible for resolving uptime facts. + */ + struct uptime_resolver : posix::uptime_resolver + { + protected: + /** + * Resolves the uptime in seconds on linux. + * @param facts The fact map that is resolving facts. + */ + virtual void resolve_uptime_seconds(fact_map& facts); + }; + +}}} // namespace facter::facts::linux + +#endif // FACTER_FACTS_LINUX_UPTIME_RESOLVER_HPP_ diff --git a/lib/src/facts/linux/platform.cc b/lib/src/facts/linux/platform.cc index 50eded39f1..538329849d 100644 --- a/lib/src/facts/linux/platform.cc +++ b/lib/src/facts/linux/platform.cc @@ -6,6 +6,7 @@ #include #include #include +#include using namespace std; @@ -20,6 +21,7 @@ namespace facter { namespace facts { facts.add(make_shared()); facts.add(make_shared()); facts.add(make_shared()); + facts.add(make_shared()); } }} // namespace facter::facts diff --git a/lib/src/facts/linux/uptime_resolver.cc b/lib/src/facts/linux/uptime_resolver.cc new file mode 100644 index 0000000000..e903e6502e --- /dev/null +++ b/lib/src/facts/linux/uptime_resolver.cc @@ -0,0 +1,18 @@ +#include +#include +#include +#include + +namespace facter { namespace facts { namespace linux { + + void uptime_resolver::resolve_uptime_seconds(fact_map& facts) + { + struct sysinfo info; + if (sysinfo(&info) == 0) { + facts.add(fact::uptime_seconds, make_value(info.uptime)); + } else { + facter::facts::posix::uptime_resolver::resolve_uptime_seconds(facts); + } + } + +}}} // namespace facter::facts::linux From 869619705f6c5f02bd26d5367a3b730f8a42cca5 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Sun, 4 May 2014 20:38:49 -0700 Subject: [PATCH 1826/3753] (cfact-21) Add bsd-specific resolver for uptime_seconds --- lib/CMakeLists.txt | 1 + lib/inc/facter/facts/bsd/uptime_resolver.hpp | 23 ++++++++++++++++++ lib/src/facts/bsd/uptime_resolver.cc | 25 ++++++++++++++++++++ lib/src/facts/osx/platform.cc | 2 ++ 4 files changed, 51 insertions(+) create mode 100644 lib/inc/facter/facts/bsd/uptime_resolver.hpp create mode 100644 lib/src/facts/bsd/uptime_resolver.cc diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index b631e601e3..a3c30fd6a7 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -39,6 +39,7 @@ if ("${CMAKE_SYSTEM_NAME}" MATCHES "Darwin") set(LIBFACTER_PLATFORM_SOURCES "${CMAKE_CURRENT_LIST_DIR}/src/facts/osx/dmi_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/bsd/networking_resolver.cc" + "${CMAKE_CURRENT_LIST_DIR}/src/facts/bsd/uptime_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/osx/networking_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/osx/platform.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/osx/processor_resolver.cc" diff --git a/lib/inc/facter/facts/bsd/uptime_resolver.hpp b/lib/inc/facter/facts/bsd/uptime_resolver.hpp new file mode 100644 index 0000000000..75e15a8d1a --- /dev/null +++ b/lib/inc/facter/facts/bsd/uptime_resolver.hpp @@ -0,0 +1,23 @@ +#ifndef FACTER_FACTS_BSD_UPTIME_RESOLVER_HPP_ +#define FACTER_FACTS_BSD_UPTIME_RESOLVER_HPP_ + +#include "../posix/uptime_resolver.hpp" + +namespace facter { namespace facts { namespace bsd { + + /** + * Responsible for resolving uptime facts. + */ + struct uptime_resolver : posix::uptime_resolver + { + protected: + /** + * Resolves the uptime in seconds on bsd. + * @param facts The fact map that is resolving facts. + */ + virtual void resolve_uptime_seconds(fact_map& facts); + }; + +}}} // namespace facter::facts::bsd + +#endif // FACTER_FACTS_BSD_UPTIME_RESOLVER_HPP_ diff --git a/lib/src/facts/bsd/uptime_resolver.cc b/lib/src/facts/bsd/uptime_resolver.cc new file mode 100644 index 0000000000..f421878ccc --- /dev/null +++ b/lib/src/facts/bsd/uptime_resolver.cc @@ -0,0 +1,25 @@ +#include +#include +#include +#include +#include + +namespace facter { namespace facts { namespace bsd { + + void uptime_resolver::resolve_uptime_seconds(fact_map& facts) + { + // this approach adapted from: http://stackoverflow.com/a/11676260/1004272 + timeval boottime; + size_t len = sizeof(boottime); + int mib[2] = { CTL_KERN, KERN_BOOTTIME }; + if (sysctl(mib, 2, &boottime, &len, NULL, 0) == 0) { + time_t bsec = boottime.tv_sec; + time_t now = time(NULL); + int uptime = now - bsec; + facts.add(fact::uptime_seconds, make_value(uptime)); + } else { + facter::facts::posix::uptime_resolver::resolve_uptime_seconds(facts); + } + } + +}}} // namespace facter::facts::bsd diff --git a/lib/src/facts/osx/platform.cc b/lib/src/facts/osx/platform.cc index d5e51c8d55..5bc3c0c0d0 100644 --- a/lib/src/facts/osx/platform.cc +++ b/lib/src/facts/osx/platform.cc @@ -4,6 +4,7 @@ #include #include #include +#include using namespace std; @@ -13,6 +14,7 @@ namespace facter { namespace facts { { facts.add(make_shared()); facts.add(make_shared()); + facts.add(make_shared()); facts.add(make_shared()); facts.add(make_shared()); facts.add(make_shared()); From abf00492da80f0ad78a7c118f14aa48cd7795ac4 Mon Sep 17 00:00:00 2001 From: "Ethan J. Brown" Date: Mon, 5 May 2014 12:10:20 -0700 Subject: [PATCH 1827/3753] (maint) Fix Travis failures for 1.8.7 Working around RVM issues on Travis CI by explicitly specifying the patch level for 1.8.7 so that REE isn't used. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2342bc0641..af9fa9a350 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ notifications: email: false rvm: - 1.9.3 - - 1.8.7 + - 1.8.7-p374 - 2.0.0 - ruby-head matrix: From 9463d50254b6fc4fe4fa1f26a78ba928ff36ba97 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Mon, 5 May 2014 13:01:54 -0700 Subject: [PATCH 1828/3753] (fact-233) Add json schema validation for the new 'dhcp_servers' fact. --- schema/facter.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/schema/facter.json b/schema/facter.json index ddf9191aa9..42f6e635b1 100644 --- a/schema/facter.json +++ b/schema/facter.json @@ -38,6 +38,11 @@ "boardproductname" : { "type": "string" }, "boardserialnumber" : { "type": "string" }, "cfkey" : { "type": "string" }, + "dhcp_servers" : { "type": "object", + "patternProperties": { + "^[A-Za-z0-9_]+$" : { "$ref" : "#/definitions/ipaddress" } + } + }, "domain" : { "type": "string" }, "facterversion" : { "type": "string" }, "filesystems" : { "type": "string" }, From f44969b37d20a22816270ec248b1538957b938c5 Mon Sep 17 00:00:00 2001 From: EC2 Default User Date: Tue, 6 May 2014 02:43:54 +0000 Subject: [PATCH 1829/3753] Use ruby lib from fedora and new rhel for amazon linux --- ext/redhat/facter.spec.erb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/redhat/facter.spec.erb b/ext/redhat/facter.spec.erb index 288426bd54..a38d5bea48 100644 --- a/ext/redhat/facter.spec.erb +++ b/ext/redhat/facter.spec.erb @@ -1,6 +1,6 @@ # Fedora 17 ships with ruby 1.9, RHEL 7 with ruby 2.0, which use vendorlibdir instead # of sitelibdir -%if 0%{?fedora} >= 17 || 0%{?rhel} >= 7 +%if 0%{?fedora} >= 17 || 0%{?rhel} >= 7 || %{_vendor} == "amazon" %global facter_libdir %(ruby -rrbconfig -e 'puts RbConfig::CONFIG["vendorlibdir"]') %else %global facter_libdir %(ruby -rrbconfig -e 'puts RbConfig::CONFIG["sitelibdir"]') @@ -36,7 +36,7 @@ Requires: ruby >= 1.8.7 BuildRequires: ruby >= 1.8.7 # In Fedora 17+ or RHEL 7+ ruby-rdoc is called rubygem-rdoc -%if 0%{?fedora} >= 17 || 0%{?rhel} >= 7 +%if 0%{?fedora} >= 17 || 0%{?rhel} >= 7 || %{_vendor} == "amazon" BuildRequires: rubygem-rdoc %else BuildRequires: ruby-rdoc From 071cdf6d0ad84eb15c54365a02f97143e03062a5 Mon Sep 17 00:00:00 2001 From: Jeff McCune Date: Mon, 5 May 2014 11:44:23 -0700 Subject: [PATCH 1830/3753] Add docker detection to virtual and is_virtual facts Without this patch facter is not detecting the Docker virtual environment. This is a problem because docker containers are a lightweight virtualization approach and as such should be flagged. This patch addresses the problem by specifically looking for the docker specific substring in the progress group hierarchy of the init process. This is very similar, but distinct, from the LXC detection. Docker builds upon generic Linux containers and customizes the detection to indicate a docker specific environment. This patch detects the docker specific environment. --- lib/facter/util/virtual.rb | 15 ++++++++-- lib/facter/virtual.rb | 11 +++++++ .../proc_1_cgroup/in_a_docker_container | 8 +++++ spec/unit/util/virtual_spec.rb | 30 +++++++++++++++++++ spec/unit/virtual_spec.rb | 18 +++++++++++ 5 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 spec/fixtures/virtual/proc_1_cgroup/in_a_docker_container diff --git a/lib/facter/util/virtual.rb b/lib/facter/util/virtual.rb index 8d225957c4..fa106743bd 100644 --- a/lib/facter/util/virtual.rb +++ b/lib/facter/util/virtual.rb @@ -138,8 +138,19 @@ def self.hpvm? def self.lxc? path = Pathname.new('/proc/1/cgroup') return false unless path.readable? - lxc_hierarchies = path.readlines.map {|l| l.split(":")[2].to_s.start_with? '/lxc/' } - return true if lxc_hierarchies.include?(true) + in_lxc = path.readlines.any? {|l| l.split(":")[2].to_s.start_with? '/lxc/' } + return true if in_lxc + return false + end + + ## + # docker? returns true if the process is running inside of a docker container. + # Implementation derived from observation of a boot2docker system + def self.docker? + path = Pathname.new('/proc/1/cgroup') + return false unless path.readable? + in_docker = path.readlines.any? {|l| l.split(":")[2].to_s.start_with? '/docker/' } + return true if in_docker return false end diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 9f195ea4a1..907da828a2 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -265,6 +265,17 @@ end end +## +# virtual fact specific to docker containers. +Facter.add("virtual") do + has_weight 750 + confine :kernel => "Linux" + + setcode do + "docker" if Facter::Util::Virtual.docker? + end +end + # Fact: is_virtual # # Purpose: returning true or false for if a machine is virtualised or not. diff --git a/spec/fixtures/virtual/proc_1_cgroup/in_a_docker_container b/spec/fixtures/virtual/proc_1_cgroup/in_a_docker_container new file mode 100644 index 0000000000..a34d4eab31 --- /dev/null +++ b/spec/fixtures/virtual/proc_1_cgroup/in_a_docker_container @@ -0,0 +1,8 @@ +9:perf_event:/ +8:blkio:/ +7:freezer:/ +6:devices:/docker/0e3c605ac1470c776c34a8eff362a0c816bcaac78559a43955173bd786281b7f +5:memory:/ +4:cpuacct:/ +3:cpu:/docker/0e3c605ac1470c776c34a8eff362a0c816bcaac78559a43955173bd786281b7f +2:cpuset:/ diff --git a/spec/unit/util/virtual_spec.rb b/spec/unit/util/virtual_spec.rb index e9ef65186c..2fe57f2d9e 100755 --- a/spec/unit/util/virtual_spec.rb +++ b/spec/unit/util/virtual_spec.rb @@ -321,4 +321,34 @@ end end end + + describe '.docker?' do + subject do + Facter::Util::Virtual.docker? + end + + fixture_path = fixtures('virtual', 'proc_1_cgroup') + + context '/proc/1/cgroup has at least one hierarchy rooted in /docker/' do + before :each do + fakepath = Pathname.new(File.join(fixture_path, 'in_a_docker_container')) + Pathname.stubs(:new).with('/proc/1/cgroup').returns(fakepath) + end + + it 'is true' do + subject.should be_true + end + end + + context '/proc/1/cgroup has no hierarchies rooted in /docker/' do + before :each do + fakepath = Pathname.new(File.join(fixture_path, 'not_in_a_container')) + Pathname.stubs(:new).with('/proc/1/cgroup').returns(fakepath) + end + + it 'is false' do + subject.should be_false + end + end + end end diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index 16f08d6a27..cb18c53bbd 100755 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -6,6 +6,7 @@ describe "Virtual fact" do before(:each) do + Facter::Util::Virtual.stubs(:docker?).returns(false) Facter::Util::Virtual.stubs(:lxc?).returns(false) Facter::Util::Virtual.stubs(:zone?).returns(false) Facter::Util::Virtual.stubs(:openvz?).returns(false) @@ -182,6 +183,17 @@ end end + context "In a Docker Container (docker)" do + before :each do + Facter.fact(:kernel).stubs(:value).returns("Linux") + end + + it 'is "docker" when Facter::Util::Virtual.docker? is true' do + Facter::Util::Virtual.stubs(:docker?).returns(true) + Facter.fact(:virtual).value.should == 'docker' + end + end + context "In Google Compute Engine" do before :each do Facter.fact(:kernel).stubs(:value).returns("Linux") @@ -485,4 +497,10 @@ Facter.fact(:virtual).stubs(:value).returns("lxc") Facter.fact(:is_virtual).value.should == "true" end + + it "should be true when running in docker" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:virtual).stubs(:value).returns("docker") + Facter.fact(:is_virtual).value.should == "true" + end end From 82cacdadd39aed040802bd6d1e54d273c77f7f2b Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Mon, 5 May 2014 23:14:10 -0700 Subject: [PATCH 1831/3753] (maint) Set Release as the default build type. Without setting the default build type, toolset defaults are used during the build. This change makes it so that running cmake without specifying a CMAKE_BUILD_TYPE variable will default the build to "Release". --- CMakeLists.txt | 5 +++++ lib/src/execution/posix/execution.cc | 5 ++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1f35d04f1b..f898180866 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,11 @@ cmake_minimum_required(VERSION 2.8.12) project(CFACTER) +if (NOT CMAKE_BUILD_TYPE) + message(STATUS "Defaulting to a release build.") + set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel." FORCE) +endif() + set(VENDOR_DIRECTORY "${PROJECT_SOURCE_DIR}/vendor") list(APPEND CMAKE_MODULE_PATH ${VENDOR_DIRECTORY}) diff --git a/lib/src/execution/posix/execution.cc b/lib/src/execution/posix/execution.cc index 543bacb33c..1db6e700b5 100644 --- a/lib/src/execution/posix/execution.cc +++ b/lib/src/execution/posix/execution.cc @@ -188,7 +188,10 @@ namespace facter { namespace execution { if (options[execution_options::redirect_stderr]) { string message = ex.what(); message += "\n"; - write(stdout_write, message.c_str(), message.size()); + int result = write(stdout_write, message.c_str(), message.size()); + if (result == -1) { + // We don't really care if writing the error message failed + } } exit(-1); } From 64fe56ce9c694c59c498f5426d52b3125608e2ef Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 6 May 2014 11:33:16 -0700 Subject: [PATCH 1832/3753] (maint) Correct uptime hour format string --- lib/src/facts/posix/uptime_resolver.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/facts/posix/uptime_resolver.cc b/lib/src/facts/posix/uptime_resolver.cc index 1485534197..07435ef609 100644 --- a/lib/src/facts/posix/uptime_resolver.cc +++ b/lib/src/facts/posix/uptime_resolver.cc @@ -67,7 +67,7 @@ namespace facter { namespace facts { namespace posix { string value; switch (days) { case 0: - value = (format("%%d:%02d hours") % hours % minutes).str(); + value = (format("%d:%02d hours") % hours % minutes).str(); break; case 1: value = "1 day"; From ddeb2824c15451577c6f86f0bd147723a3b6d1bc Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Sat, 3 May 2014 22:18:00 -0700 Subject: [PATCH 1833/3753] (maint) Move version information to the facter library. Moving version.h to facter library so that it reflects the facter implementation version. cfacter now calls into the facter library to determine the version rather than having the versionc compiled into two different executables. Adding version to facter library name. --- .gitignore | 2 +- CMakeLists.txt | 10 -------- exe/cfacter.cc | 6 ++--- lib/CMakeLists.txt | 12 +++++++++- lib/inc/facter/facterlib.h | 7 ++++++ lib/src/facterlib.cc | 49 +++++++++++++++++++++----------------- lib/src/facts/fact.cc | 4 ++-- lib/version.h.in | 10 ++++++++ version.h.in | 8 ------- 9 files changed, 61 insertions(+), 47 deletions(-) create mode 100644 lib/version.h.in delete mode 100644 version.h.in diff --git a/.gitignore b/.gitignore index 7f9424303c..982571654d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,6 @@ compile_commands.json # Generated files -/version.h +/lib/inc/facter/version.h /debug/ /release/ diff --git a/CMakeLists.txt b/CMakeLists.txt index f898180866..9e8de2e1a2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,16 +9,6 @@ endif() set(VENDOR_DIRECTORY "${PROJECT_SOURCE_DIR}/vendor") list(APPEND CMAKE_MODULE_PATH ${VENDOR_DIRECTORY}) -set(CFACTER_VERSION_MAJOR 0) -set(CFACTER_VERSION_MINOR 1) -set(CFACTER_VERSION_PATCH 0) - -# Generate a file containing the above version numbers -configure_file ( - "${PROJECT_SOURCE_DIR}/version.h.in" - "${PROJECT_SOURCE_DIR}/version.h" -) - # Find Boost find_package(Boost 1.48 COMPONENTS program_options system filesystem REQUIRED) if (NOT Boost_FOUND) diff --git a/exe/cfacter.cc b/exe/cfacter.cc index dae2dbab64..fa652d625c 100644 --- a/exe/cfacter.cc +++ b/exe/cfacter.cc @@ -1,5 +1,5 @@ -#include "../version.h" #include +#include #include #include #include @@ -146,7 +146,7 @@ int main(int argc, char **argv) // Check for printing the version if (vm.count("version")) { - cout << CFACTER_VERSION << endl; + cout << get_facter_version() << endl; return EXIT_SUCCESS; } @@ -179,7 +179,7 @@ int main(int argc, char **argv) LOG_INFO("Resolving all facts."); // Print all facts in the map - facts.each([](string const& name, value const* value) { + facts.each([](string const& name, facter::facts::value const* value) { if (value) { cout << format("%1% => %2%\n") % name % value->to_string(); } diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index a3c30fd6a7..563996f786 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -1,5 +1,15 @@ cmake_minimum_required(VERSION 2.8.12) +set(LIBFACTER_VERSION_MAJOR 0) +set(LIBFACTER_VERSION_MINOR 1) +set(LIBFACTER_VERSION_PATCH 0) + +# Generate a file containing the above version numbers +configure_file ( + "${CMAKE_CURRENT_LIST_DIR}/version.h.in" + "${CMAKE_CURRENT_LIST_DIR}/inc/facter/version.h" +) + # Set compiler-specific flags if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra -Werror -Wno-unused-parameter -Wno-tautological-constant-out-of-range-compare") @@ -60,7 +70,7 @@ endif() # Add the library target without a prefix (name already has the 'lib') add_library(libfacter SHARED ${LIBFACTER_COMMON_SOURCES} ${LIBFACTER_POSIX_SOURCES} ${LIBFACTER_PLATFORM_SOURCES}) -set_target_properties(libfacter PROPERTIES PREFIX "") +set_target_properties(libfacter PROPERTIES PREFIX "" VERSION "${LIBFACTER_VERSION_MAJOR}.${LIBFACTER_VERSION_MINOR}.${LIBFACTER_VERSION_PATCH}") install(TARGETS libfacter DESTINATION .) # Set include directories diff --git a/lib/inc/facter/facterlib.h b/lib/inc/facter/facterlib.h index bb5bffb728..01144bac57 100644 --- a/lib/inc/facter/facterlib.h +++ b/lib/inc/facter/facterlib.h @@ -3,12 +3,19 @@ #include +#ifdef __cplusplus extern "C" { +#endif // __cplusplus + + char const* get_facter_version(); void clear(); void loadfacts(); int to_json(char *facts, size_t facts_len); int value(const char *fact, char *value, size_t value_len); void search_external(const char *dirs); + +#ifdef __cplusplus } +#endif // __cplusplus #endif // FACTER_FACTERLIB_H_ diff --git a/lib/src/facterlib.cc b/lib/src/facterlib.cc index 4e166e8c98..c149e880a4 100644 --- a/lib/src/facterlib.cc +++ b/lib/src/facterlib.cc @@ -1,34 +1,39 @@ #include -#include +#include #include "rapidjson/document.h" #include "rapidjson/prettywriter.h" #include "rapidjson/stringbuffer.h" - using namespace std; -using namespace facter::facts; -void loadfacts() -{ - // This is a no-op of the fact map -} +extern "C" { + char const* get_facter_version() + { + return LIBFACTER_VERSION; + } -int to_json(char *facts_json, size_t facts_len) -{ - // TODO: re-implement this with support for structured facts - strncpy(facts_json, "", facts_len); - return 0; -} + void loadfacts() + { + // This is a no-op of the fact map + } -int value(const char *fact, char *value, size_t value_len) -{ - // TODO: reimplement this with support for structured facts - strncpy(value, "", value_len); - return 0; -} + int to_json(char *facts_json, size_t facts_len) + { + // TODO: re-implement this with support for structured facts + strncpy(facts_json, "", facts_len); + return 0; + } + + int value(const char *fact, char *value, size_t value_len) + { + // TODO: reimplement this with support for structured facts + strncpy(value, "", value_len); + return 0; + } -void search_external(const char *dirs) -{ - // TODO + void search_external(const char *dirs) + { + // TODO + } } diff --git a/lib/src/facts/fact.cc b/lib/src/facts/fact.cc index 7bf60b5395..3c416c5fff 100644 --- a/lib/src/facts/fact.cc +++ b/lib/src/facts/fact.cc @@ -1,4 +1,4 @@ -#include "../../../version.h" +#include #include #include @@ -8,7 +8,7 @@ namespace facter { namespace facts { void populate_common_facts(fact_map& facts) { - facts.add("cfacterversion", make_value(CFACTER_VERSION)); + facts.add("cfacterversion", make_value(LIBFACTER_VERSION)); } }} // namespace facter::facts diff --git a/lib/version.h.in b/lib/version.h.in new file mode 100644 index 0000000000..2554b77abc --- /dev/null +++ b/lib/version.h.in @@ -0,0 +1,10 @@ +#ifndef FACTER_VERSION_H_ +#define FACTER_VERSION_H_ + +#define LIBFACTER_VERSION_MAJOR @LIBFACTER_VERSION_MAJOR@ +#define LIBFACTER_VERSION_MINOR @LIBFACTER_VERSION_MINOR@ +#define LIBFACTER_VERSION_PATCH @LIBFACTER_VERSION_PATCH@ + +#define LIBFACTER_VERSION "@LIBFACTER_VERSION_MAJOR@.@LIBFACTER_VERSION_MINOR@.@LIBFACTER_VERSION_PATCH@" + +#endif // FACTER_VERSION_H_ \ No newline at end of file diff --git a/version.h.in b/version.h.in deleted file mode 100644 index 247a4c82f6..0000000000 --- a/version.h.in +++ /dev/null @@ -1,8 +0,0 @@ -#define CFACTER_VERSION_MAJOR @CFACTER_VERSION_MAJOR@ -#define CFACTER_VERSION_MINOR @CFACTER_VERSION_MINOR@ -#define CFACTER_VERSION_PATCH @CFACTER_VERSION_PATCH@ - -#define STRINGIFYX(x) #x -#define STRINGIFY(x) STRINGIFYX(x) - -#define CFACTER_VERSION STRINGIFY(CFACTER_VERSION_MAJOR) "." STRINGIFY(CFACTER_VERSION_MINOR) "." STRINGIFY(CFACTER_VERSION_PATCH) \ No newline at end of file From 4bee5dfa8d8317caa501b0a20a2ca25a89732571 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Sat, 3 May 2014 22:23:52 -0700 Subject: [PATCH 1834/3753] (maint) Fix where cfacter gets installed to. Now that we don't build cfacter's dependencies, change back to installing cfacter to /usr/local/[bin|lib] by default. --- CMakeLists.txt | 14 -------------- README.md | 6 +++--- exe/CMakeLists.txt | 2 +- lib/CMakeLists.txt | 2 +- 4 files changed, 5 insertions(+), 19 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9e8de2e1a2..7bb00fefdb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,20 +31,6 @@ endif() include(${VENDOR_DIRECTORY}/rapidjson.cmake) include(${VENDOR_DIRECTORY}/gmock.cmake) -if(APPLE) - # Set the RPATH to OSX magic value @executable_path (where the loading executable is located) - set(CMAKE_INSTALL_RPATH "@executable_path/") - set(CMAKE_MACOSX_RPATH 1) -elseif(UNIX) - # Set the RPATH to be magic value $ORIGIN (where the loading executable is located) - set(CMAKE_INSTALL_RPATH "$ORIGIN") -endif() - -# Set the default install path -if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) - set(CMAKE_INSTALL_PREFIX "/opt/cfacter" CACHE PATH "default install path" FORCE) -endif() - add_subdirectory(exe) # diff --git a/README.md b/README.md index 4a472de631..9a7818bab4 100644 --- a/README.md +++ b/README.md @@ -61,15 +61,15 @@ You can install cfacter into your system: $ cd release $ make && sudo make install -By default, this will install cfacter into `/opt/cfacter`. +By default, cfacter will install into `/usr/local/bin` and `/usr/local/lib`. To install to a different location, set the install prefix: $ cd release - $ cmake -DCMAKE_INSTALL_PREFIX=~/cfacter .. + $ cmake -DCMAKE_INSTALL_PREFIX=~ .. $ make clean install -This would install cfacter into `~/cfacter`. +This would install cfacter into `~/bin` and `~/lib`. Uninstall --------- diff --git a/exe/CMakeLists.txt b/exe/CMakeLists.txt index a5a1a5595d..9d0fb8b12a 100644 --- a/exe/CMakeLists.txt +++ b/exe/CMakeLists.txt @@ -25,4 +25,4 @@ include_directories( add_executable(cfacter ${CFACTER_SOURCES}) target_link_libraries(cfacter libfacter ${LOG4CXX_LIBRARIES} ${Boost_LIBRARIES}) -install(TARGETS cfacter DESTINATION .) +install(TARGETS cfacter DESTINATION bin) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 563996f786..ecb987d374 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -71,7 +71,7 @@ endif() # Add the library target without a prefix (name already has the 'lib') add_library(libfacter SHARED ${LIBFACTER_COMMON_SOURCES} ${LIBFACTER_POSIX_SOURCES} ${LIBFACTER_PLATFORM_SOURCES}) set_target_properties(libfacter PROPERTIES PREFIX "" VERSION "${LIBFACTER_VERSION_MAJOR}.${LIBFACTER_VERSION_MINOR}.${LIBFACTER_VERSION_PATCH}") -install(TARGETS libfacter DESTINATION .) +install(TARGETS libfacter DESTINATION lib) # Set include directories include_directories( From 13213b8331299ff3aea7d0156f0b221158b13c4e Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Sat, 3 May 2014 22:46:09 -0700 Subject: [PATCH 1835/3753] (CFACT-20) Implement JSON output. Implementing JSON output of facts. Implementing map_value to support complex fact values. Implementing integer_value to support integer types in JSON output. Fixing output of existing integer types. Normalizing fact names taken on the command line. --- exe/cfacter.cc | 67 ++++++---- lib/CMakeLists.txt | 3 + lib/inc/facter/facts/array_value.hpp | 20 +-- lib/inc/facter/facts/fact_map.hpp | 51 ++++++-- lib/inc/facter/facts/integer_value.hpp | 35 +++-- lib/inc/facter/facts/map_value.hpp | 68 ++++++++++ lib/inc/facter/facts/string_value.hpp | 15 ++- lib/inc/facter/facts/value.hpp | 43 ++++++- lib/src/facts/array_value.cc | 38 ++++-- lib/src/facts/fact_map.cc | 129 +++++++++++++++---- lib/src/facts/integer_value.cc | 34 +++++ lib/src/facts/linux/block_device_resolver.cc | 3 +- lib/src/facts/map_value.cc | 44 +++++++ lib/src/facts/string_value.cc | 12 +- lib/src/facts/value.cc | 12 ++ lib/tests/facts/integer_value.cc | 14 +- 16 files changed, 477 insertions(+), 111 deletions(-) create mode 100644 lib/inc/facter/facts/map_value.hpp create mode 100644 lib/src/facts/integer_value.cc create mode 100644 lib/src/facts/map_value.cc create mode 100644 lib/src/facts/value.cc diff --git a/exe/cfacter.cc b/exe/cfacter.cc index fa652d625c..1f1add4504 100644 --- a/exe/cfacter.cc +++ b/exe/cfacter.cc @@ -2,21 +2,23 @@ #include #include #include -#include +#include #include #include #include #include #include -#include #include +#include +#include +#include +#include using namespace std; using namespace log4cxx; using namespace facter::util; using namespace facter::facts; using namespace boost::filesystem; -using boost::format; namespace po = boost::program_options; namespace bs = boost::system; @@ -81,13 +83,22 @@ void log_command_line(int argc, char** argv) LOG_INFO("Executed with command line: %1%.", command_line.str()); } -void log_requested_facts(vector const& facts) +void log_requested_facts(set const& facts) { if (!LOG_IS_INFO_ENABLED()) { return; } + + if (facts.empty()) { + LOG_INFO("Resolving all facts."); + return; + } + ostringstream requested_facts; for (auto const& fact : facts) { + if (fact.empty()) { + continue; + } if (requested_facts.tellp() != 0) { requested_facts << ' '; } @@ -108,6 +119,7 @@ int main(int argc, char **argv) visible_options.add_options() ("debug,d", "Enable debug output.") ("help", "Print this help message.") + ("json,j", "Output in JSON format.") ("propfile,p", po::value(&properties_file), "Configure logging with a log4cxx properties file.") ("verbose", "Enable verbose (info) output.") ("version,v", "Print the version and exit."); @@ -162,30 +174,35 @@ int main(int argc, char **argv) configure_logger(log_level, properties_file); log_command_line(argc, argv); - fact_map& facts = fact_map::instance(); + set requested_facts; if (vm.count("fact")) { - auto const& requested_facts = vm["fact"].as>(); - log_requested_facts(requested_facts); - - // Print only the given facts - for (auto const& fact : requested_facts) { - auto value = facts[fact]; - if (!value) { - continue; - } - cout << format("%1% => %2%\n") % fact % value->to_string(); - } + auto const& fact_parameters = vm["fact"].as>(); + + // Convert the given strings into a set of unique lowercase fact names + transform( + fact_parameters.begin(), + fact_parameters.end(), + inserter(requested_facts, requested_facts.end()), + [](string const& s) { + auto s2 = s; + trim(to_lower(s2)); + return s2; + }); + } + + log_requested_facts(requested_facts); + + // Resolve the facts and output the result + fact_map facts; + facts.resolve(requested_facts); + + // Output the facts + if (vm.count("json")) { + facts.write_json(cout); } else { - LOG_INFO("Resolving all facts."); - - // Print all facts in the map - facts.each([](string const& name, facter::facts::value const* value) { - if (value) { - cout << format("%1% => %2%\n") % name % value->to_string(); - } - return false; - }); + cout << facts; } + cout << '\n'; } catch (exception& ex) { LOG_FATAL("Unhandled exception: %1%.", ex.what()); return EXIT_FAILURE; diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index ecb987d374..a21bffcf38 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -26,7 +26,10 @@ set(LIBFACTER_COMMON_SOURCES "${CMAKE_CURRENT_LIST_DIR}/src/facts/fact.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/fact_map.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/fact_resolver.cc" + "${CMAKE_CURRENT_LIST_DIR}/src/facts/integer_value.cc" + "${CMAKE_CURRENT_LIST_DIR}/src/facts/map_value.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/string_value.cc" + "${CMAKE_CURRENT_LIST_DIR}/src/facts/value.cc" "${CMAKE_CURRENT_LIST_DIR}/src/util/file.cc" "${CMAKE_CURRENT_LIST_DIR}/src/util/string.cc" ) diff --git a/lib/inc/facter/facts/array_value.hpp b/lib/inc/facter/facts/array_value.hpp index f35a77d06f..91a82ff6d3 100644 --- a/lib/inc/facter/facts/array_value.hpp +++ b/lib/inc/facter/facts/array_value.hpp @@ -37,22 +37,25 @@ namespace facter { namespace facts { array_value& operator=(array_value&&) = default; /** - * Converts the value to a string representation. - * @return Returns the string representation of the value. + * Converts the value to a JSON value. + * @param allocator The allocator to use for creating the JSON value. + * @param value The returned JSON value. */ - std::string to_string() const; + virtual void to_json(rapidjson::Allocator& allocator, rapidjson::Value& value) const; /** * Gets the vector of elements in the array. * @return Returns the vector of elements in the array. */ - std::vector>& elements() { return _elements; } + std::vector> const& elements() const { return _elements; } + protected: /** - * Gets the vector of elements in the array. - * @return Returns the vector of elements in the array. - */ - std::vector> const& elements() const { return _elements; } + * Writes the value to the given stream. + * @param os The stream to write to. + * @returns Returns the stream being written to. + */ + virtual std::ostream& write(std::ostream& os) const; private: std::vector> _elements; @@ -61,4 +64,3 @@ namespace facter { namespace facts { }} // namespace facter::facts #endif // FACTER_FACTS_ARRAY_VALUE_HPP_ - diff --git a/lib/inc/facter/facts/fact_map.hpp b/lib/inc/facter/facts/fact_map.hpp index c535c23b4f..7cef5faeb0 100644 --- a/lib/inc/facter/facts/fact_map.hpp +++ b/lib/inc/facter/facts/fact_map.hpp @@ -3,12 +3,14 @@ #include #include +#include #include #include #include +#include +#include #include "value.hpp" #include "fact_resolver.hpp" -#include namespace facter { namespace facts { @@ -41,8 +43,10 @@ namespace facter { namespace facts { */ struct fact_map { - typedef std::map> fact_map_type; - typedef std::map> resolver_map_type; + /** + * Constructs a fact_map. + */ + fact_map(); /** * Adds a resolver to the fact map. @@ -50,7 +54,6 @@ namespace facter { namespace facts { */ void add(std::shared_ptr const& resolver); - /** * Adds a fact to the map. * @param name The name of the fact. @@ -72,6 +75,7 @@ namespace facter { namespace facts { /** * Clears the entire fact map. + * This will remove all built-in facts and resolvers from the map. */ void clear(); @@ -81,6 +85,19 @@ namespace facter { namespace facts { */ bool empty() const; + /** + * Checks to see if the fact map has been resolved. + * @return Returns true if all fact resolvers have been resolved or false if at least fact resolver remains unresolved. + */ + bool resolved() const; + + /** + * Resolves all facts. + * This forces each resolver in the map to resolve. + * @param facts The set of fact names to filter the resolution to. If empty, all facts will be resolved. + */ + void resolve(std::set const& facts = std::set()); + /** * Gets a fact value by name. * @tparam T The expected type of the value. @@ -108,28 +125,36 @@ namespace facter { namespace facts { * Enumerates all facts in the map. * @param func The callback function called for each fact in the map. */ - void each(std::function func); + void each(std::function func) const; /** - * Gets the singleton instance of the fact map. - * @return Returns the singleton instance of the fact map. + * Writes the contents of the fact map as JSON to the given stream. + * @param stream The stream to write the JSON to. */ - static fact_map& instance(); + void write_json(std::ostream& stream) const; private: - fact_map() {} - void load_facts(); - void resolve_facts(); + typedef std::map> fact_map_type; + typedef std::map> resolver_map_type; + + friend std::ostream& operator<<(std::ostream& os, fact_map const& facts); + std::shared_ptr find_resolver(std::string const& name); value const* get_value(std::string const& name, bool resolve); - static fact_map _instance; - fact_map_type _facts; std::list> _resolvers; resolver_map_type _resolver_map; }; + /** + * Insertion operator for fact_map. + * @param os The output stream to write to. + * @param facts The facts to write to the stream. + * @return Returns the given output stream. + */ + std::ostream& operator<<(std::ostream& os, fact_map const& facts); + }} // namespace facter::facts #endif // FACTER_FACTS_FACT_MAP_HPP_ diff --git a/lib/inc/facter/facts/integer_value.hpp b/lib/inc/facter/facts/integer_value.hpp index 9062799605..aaf838285f 100644 --- a/lib/inc/facter/facts/integer_value.hpp +++ b/lib/inc/facter/facts/integer_value.hpp @@ -1,9 +1,8 @@ #ifndef FACTER_FACTS_INTEGER_VALUE_HPP_ #define FACTER_FACTS_INTEGER_VALUE_HPP_ -#include - #include "value.hpp" +#include namespace facter { namespace facts { @@ -13,7 +12,7 @@ namespace facter { namespace facts { struct integer_value : value { /** - * Constructs a integer_value. + * Constructs an integer value. * @param value The integer value. */ explicit integer_value(int64_t value) : @@ -23,20 +22,9 @@ namespace facter { namespace facts { /** * Constructs a integer_value. - * @param value The integer value. + * @param value The integer value as a string. */ - explicit integer_value(std::string const& value) - { - try - { - _value = boost::lexical_cast(value); - } - catch (const boost::bad_lexical_cast& e) - { - // TODO: warn? - _value = 0; - } - } + explicit integer_value(std::string const& value); // Force non-copyable integer_value(integer_value const&) = delete; @@ -47,10 +35,11 @@ namespace facter { namespace facts { integer_value& operator=(integer_value&&) = default; /** - * Converts the value to a string representation. - * @return Returns the string representation of the value. + * Converts the value to a JSON value. + * @param allocator The allocator to use for creating the JSON value. + * @param value The returned JSON value. */ - std::string to_string() const { return std::to_string(_value); } + virtual void to_json(rapidjson::Allocator& allocator, rapidjson::Value& value) const; /** * Gets the integer value. @@ -58,6 +47,14 @@ namespace facter { namespace facts { */ int64_t const& value() const { return _value; } + protected: + /** + * Writes the value to the given stream. + * @param os The stream to write to. + * @returns Returns the stream being written to. + */ + virtual std::ostream& write(std::ostream& os) const; + private: int64_t _value; }; diff --git a/lib/inc/facter/facts/map_value.hpp b/lib/inc/facter/facts/map_value.hpp new file mode 100644 index 0000000000..c54132b855 --- /dev/null +++ b/lib/inc/facter/facts/map_value.hpp @@ -0,0 +1,68 @@ +#ifndef FACTER_FACTS_MAP_VALUE_HPP_ +#define FACTER_FACTS_MAP_VALUE_HPP_ + +#include "value.hpp" +#include +#include +#include + +namespace facter { namespace facts { + + /** + * Represents a fact value that maps fact names to values. + */ + struct map_value : value + { + /** + * Constructs a map value. + */ + map_value() + { + } + + /** + * Constructs a map value. + * @param elements The elements to store in the map value. + */ + explicit map_value(std::map>&& elements) : + _elements(std::move(elements)) + { + } + + // Force non-copyable + map_value(map_value const&) = delete; + map_value& operator=(map_value const&) = delete; + + // Allow movable + map_value(map_value&&) = default; + map_value& operator=(map_value&&) = default; + + /** + * Converts the value to a JSON value. + * @param allocator The allocator to use for creating the JSON value. + * @param value The returned JSON value. + */ + virtual void to_json(rapidjson::Allocator& allocator, rapidjson::Value& value) const; + + /** + * Gets the map of elements in the value. + * @return Returns the vector of elements in the array. + */ + std::map> const& elements() const { return _elements; } + + protected: + /** + * Writes the value to the given stream. + * @param os The stream to write to. + * @returns Returns the stream being written to. + */ + virtual std::ostream& write(std::ostream& os) const; + + private: + std::map> _elements; + }; + +}} // namespace facter::facts + +#endif // FACTER_FACTS_MAP_VALUE_HPP_ + diff --git a/lib/inc/facter/facts/string_value.hpp b/lib/inc/facter/facts/string_value.hpp index 07fd0260f8..e61b0fd790 100644 --- a/lib/inc/facter/facts/string_value.hpp +++ b/lib/inc/facter/facts/string_value.hpp @@ -37,10 +37,11 @@ namespace facter { namespace facts { string_value& operator=(string_value&&) = default; /** - * Converts the value to a string representation. - * @return Returns the string representation of the value. + * Converts the value to a JSON value. + * @param allocator The allocator to use for creating the JSON value. + * @param value The returned JSON value. */ - std::string to_string() const; + virtual void to_json(rapidjson::Allocator& allocator, rapidjson::Value& value) const; /** * Gets the string value. @@ -48,6 +49,14 @@ namespace facter { namespace facts { */ std::string const& value() const { return _value; } + protected: + /** + * Writes the value to the given stream. + * @param os The stream to write to. + * @returns Returns the stream being written to. + */ + virtual std::ostream& write(std::ostream& os) const; + private: std::string _value; }; diff --git a/lib/inc/facter/facts/value.hpp b/lib/inc/facter/facts/value.hpp index 498fd28249..a66cd211fc 100644 --- a/lib/inc/facter/facts/value.hpp +++ b/lib/inc/facter/facts/value.hpp @@ -4,6 +4,19 @@ #include #include #include +#include + +// Forward delcare needed rapidjson classes. +namespace rapidjson { + class CrtAllocator; + template class MemoryPoolAllocator; + template class GenericValue; + template struct UTF8; + typedef GenericValue, MemoryPoolAllocator> Value; + typedef MemoryPoolAllocator Allocator; + template class GenericDocument; + typedef GenericDocument, MemoryPoolAllocator> Document; +} namespace facter { namespace facts { @@ -18,10 +31,9 @@ namespace facter { namespace facts { value() {} /** - * Converts the value to a string representation. - * @return Returns the string representation of the value. + * Destructs a value. */ - virtual std::string to_string() const = 0; + virtual ~value() {} // Force non-copyable value(value const&) = delete; @@ -30,6 +42,22 @@ namespace facter { namespace facts { // Allow movable value(value&&) = default; value& operator=(value&&) = default; + + /** + * Converts the value to a JSON value. + * @param allocator The allocator to use for creating the JSON value. + * @param value The returned JSON value. + */ + virtual void to_json(rapidjson::Allocator& allocator, rapidjson::Value& value) const = 0; + + protected: + /** + * Writes the value to the given stream. + * @param os The stream to write to. + * @returns Returns the stream being written to. + */ + virtual std::ostream& write(std::ostream& os) const = 0; + friend std::ostream& operator<<(std::ostream& os, value const& val); }; /** @@ -45,7 +73,14 @@ namespace facter { namespace facts { return std::unique_ptr(new T(std::forward(args)...)); } + /** + * Insertion operator for value. + * @param os The output stream to write to. + * @param val The value to write to the stream. + * @return Returns the given output stream. + */ + std::ostream& operator<<(std::ostream& os, value const& val); + }} // namespace facter::facts #endif // FACTER_FACTS_VALUE_HPP_ - diff --git a/lib/src/facts/array_value.cc b/lib/src/facts/array_value.cc index 22da077114..f4f15f9ec7 100644 --- a/lib/src/facts/array_value.cc +++ b/lib/src/facts/array_value.cc @@ -1,24 +1,44 @@ #include -#include +#include using namespace std; +using namespace rapidjson; namespace facter { namespace facts { - string array_value::to_string() const + void array_value::to_json(Allocator& allocator, Value& value) const { - ostringstream result; + value.SetArray(); + for (auto const& element : _elements) { + if (!element) { + continue; + } + + Value child; + element->to_json(allocator, child); + value.PushBack(child, allocator); + } + } + + ostream& array_value::write(ostream& os) const + { // Write out the elements in the array - result << "["; + os << "[ "; + bool first = true; for (auto const& element : _elements) { - if (result.tellp() != 0) { - result << ", "; + if (!element) { + continue; + } + if (first) { + first = false; + } else { + os << ", "; } - result << element->to_string(); + os << *element; } - result << "]"; - return result.str(); + os << " ]"; + return os; } }} // namespace facter::facts diff --git a/lib/src/facts/fact_map.cc b/lib/src/facts/fact_map.cc index 9b2460bbc9..a6ff25689d 100644 --- a/lib/src/facts/fact_map.cc +++ b/lib/src/facts/fact_map.cc @@ -1,10 +1,15 @@ #include +#include +#include #include #include +#include +#include using namespace std; +using namespace rapidjson; -LOG_DECLARE_NAMESPACE("facts"); +LOG_DECLARE_NAMESPACE("facts.map"); namespace facter { namespace facts { @@ -20,7 +25,11 @@ namespace facter { namespace facts { */ extern void populate_platform_facts(fact_map& facts); - fact_map fact_map::_instance; + fact_map::fact_map() + { + populate_common_facts(*this); + populate_platform_facts(*this); + } void fact_map::add(shared_ptr const& resolver) { @@ -46,7 +55,15 @@ namespace facter { namespace facts { throw fact_exists_exception("fact " + name + " already exists."); } - LOG_DEBUG("fact %1% has resolved to \"%2%\".", name, value ? value->to_string() : ""); + if (LOG_IS_DEBUG_ENABLED()) { + ostringstream ss; + if (value) { + ss << *value; + } else { + ss << ""; + } + LOG_DEBUG("fact %1% has resolved to \"%2%\".", name, ss.str()); + } // Remove any mapped resolver for this fact _resolver_map.erase(name); @@ -90,32 +107,38 @@ namespace facter { namespace facts { return _facts.empty() && _resolvers.empty(); } - void fact_map::each(function func) + bool fact_map::resolved() const { - load_facts(); - resolve_facts(); - - find_if(begin(_facts), end(_facts), [&func](fact_map_type::value_type const& it) { - return func(it.first, it.second.get()); - }); + return _resolvers.empty(); } - fact_map& fact_map::instance() + void fact_map::resolve(set const& facts) { - return fact_map::_instance; - } + if (!facts.empty()) { + // Resolve the given facts + for (auto const& fact : facts) { + if (fact.empty()) { + continue; + } + auto value = get_value(fact, true); + if (!value) { + LOG_DEBUG("fact %1% was not resolved.", fact); + } + } - void fact_map::load_facts() - { - if (!empty()) { + // Remove facts that resolved but aren't in the filter + for (auto it = _facts.begin(); it != _facts.end();) { + if (facts.count(it->first)) { + // In the requested set of facts so move next + ++it; + continue; + } + it = _facts.erase(it); + } return; } - populate_common_facts(*this); - populate_platform_facts(*this); - } - void fact_map::resolve_facts() - { + // No filter given, resolve all facts for (auto& resolver : _resolvers) { resolver->resolve(*this); } @@ -133,10 +156,51 @@ namespace facter { namespace facts { _resolver_map.clear(); } - value const* fact_map::get_value(string const& name, bool resolve) + void fact_map::each(function func) const + { + find_if(begin(_facts), end(_facts), [&func](fact_map_type::value_type const& it) { + return func(it.first, it.second.get()); + }); + } + + struct stream_adapter { - load_facts(); + explicit stream_adapter(ostream& stream) : _stream(stream) + { + } + + void Put(char c) + { + _stream << c; + } + private: + ostream& _stream; + }; + + void fact_map::write_json(ostream& stream) const + { + Document document; + document.SetObject(); + + for (auto const& kvp : _facts) { + if (!kvp.second) { + continue; + } + + Value value; + kvp.second->to_json(document.GetAllocator(), value); + document.AddMember(kvp.first.c_str(), value, document.GetAllocator()); + } + + stream_adapter adapter(stream); + PrettyWriter writer(adapter); + writer.SetIndent(' ', 2); + document.Accept(writer); + } + + value const* fact_map::get_value(string const& name, bool resolve) + { // Lookup the fact auto it = _facts.find(name); while (it == _facts.end()) { @@ -173,4 +237,23 @@ namespace facter { namespace facts { return nullptr; } + ostream& operator<<(ostream& os, fact_map const& facts) + { + // Print all facts in the map + bool first = true; + for (auto const& kvp : facts._facts) { + if (!kvp.second) { + continue; + } + + if (first) { + first = false; + } else { + os << '\n'; + } + os << kvp.first << " => " << *kvp.second; + } + return os; + } + }} // namespace facter::facts diff --git a/lib/src/facts/integer_value.cc b/lib/src/facts/integer_value.cc new file mode 100644 index 0000000000..e6dfd8ffda --- /dev/null +++ b/lib/src/facts/integer_value.cc @@ -0,0 +1,34 @@ +#include +#include +#include + +using namespace std; +using namespace rapidjson; +using boost::lexical_cast; +using boost::bad_lexical_cast; + +namespace facter { namespace facts { + + integer_value::integer_value(string const& value) + { + try { + _value = lexical_cast(value); + } + catch (const bad_lexical_cast& e) { + // TODO: warn? + _value = 0; + } + } + + void integer_value::to_json(Allocator& allocator, Value& value) const + { + value.SetInt64(_value); + } + + ostream& integer_value::write(ostream& os) const + { + os << _value; + return os; + } + +}} // namespace facter::facts diff --git a/lib/src/facts/linux/block_device_resolver.cc b/lib/src/facts/linux/block_device_resolver.cc index 9bc586b815..0527a33227 100644 --- a/lib/src/facts/linux/block_device_resolver.cc +++ b/lib/src/facts/linux/block_device_resolver.cc @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -62,7 +63,7 @@ namespace facter { namespace facts { namespace linux { if (is_regular_file(size_file, ec)) { try { uint64_t size = lexical_cast(trim(file::read(size_file))); - facts.add(string(fact::block_device) + "_" + device + "_size" , make_value(to_string(size * 512))); + facts.add(string(fact::block_device) + "_" + device + "_size" , make_value(static_cast(size * 512))); } catch (bad_lexical_cast& ex) { LOG_DEBUG("size of block device %1% is invalid: fact %2%_%1%_size is unavailable.", device, fact::block_device); } diff --git a/lib/src/facts/map_value.cc b/lib/src/facts/map_value.cc new file mode 100644 index 0000000000..bea3be0d9c --- /dev/null +++ b/lib/src/facts/map_value.cc @@ -0,0 +1,44 @@ +#include +#include + +using namespace std; +using namespace rapidjson; + +namespace facter { namespace facts { + + void map_value::to_json(Allocator& allocator, Value& value) const + { + value.SetObject(); + + for (auto const& kvp : _elements) { + if (!kvp.second) { + continue; + } + + Value child; + kvp.second->to_json(allocator, child); + value.AddMember(kvp.first.c_str(), child, allocator); + } + } + + ostream& map_value::write(ostream& os) const + { + // Write out the elements in the map + os << "{ "; + bool first = true; + for (auto const& kvp : _elements) { + if (!kvp.second) { + continue; + } + if (first) { + first = false; + } else { + os << ", "; + } + os << kvp.first << " => " << *kvp.second; + } + os << " }"; + return os; + } + +}} // namespace facter::facts diff --git a/lib/src/facts/string_value.cc b/lib/src/facts/string_value.cc index 8a347fb6ce..e5c5d9742b 100644 --- a/lib/src/facts/string_value.cc +++ b/lib/src/facts/string_value.cc @@ -1,12 +1,20 @@ #include +#include using namespace std; +using namespace rapidjson; namespace facter { namespace facts { - string string_value::to_string() const + void string_value::to_json(Allocator& allocator, Value& value) const { - return _value; + value.SetString(_value.c_str(), _value.size()); + } + + ostream& string_value::write(ostream& os) const + { + os << _value; + return os; } }} // namespace facter::facts diff --git a/lib/src/facts/value.cc b/lib/src/facts/value.cc new file mode 100644 index 0000000000..54a3658a88 --- /dev/null +++ b/lib/src/facts/value.cc @@ -0,0 +1,12 @@ +#include + +using namespace std; + +namespace facter { namespace facts { + + ostream& operator<<(ostream& os, value const& val) + { + return val.write(os); + } + +}} // namespace facter::facts diff --git a/lib/tests/facts/integer_value.cc b/lib/tests/facts/integer_value.cc index 826303c567..064b712ec6 100644 --- a/lib/tests/facts/integer_value.cc +++ b/lib/tests/facts/integer_value.cc @@ -6,12 +6,20 @@ using namespace facter::facts; TEST(facter_facts_integer_value, to_string) { // small integer integer_value foo(42); - ASSERT_EQ("42", foo.to_string()); + ASSERT_EQ(42, foo.value()); + + // small integer string + integer_value foo2("42"); + ASSERT_EQ(42, foo2.value()); // very large integer but in range int64_t large_int = 1LL << 62; - integer_value large_int_value(large_int); - ASSERT_EQ("4611686018427387904", large_int_value.to_string()); + integer_value foo3(large_int); + ASSERT_EQ(4611686018427387904LL, foo3.value()); + + // very large integer string + integer_value foo4("4611686018427387904"); + ASSERT_EQ(4611686018427387904LL, foo4.value()); } TEST(facter_facts_integer_value, string_constructor) { From 849b4ccf8c65fb6dba69d40d7c91db413fb70060 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Tue, 6 May 2014 14:28:08 -0700 Subject: [PATCH 1836/3753] (FACT-480) Remove current directory from Ruby load path. The current directory ('.') is on the load path for Ruby 1.8.7. This is a security vulnerability as it allows arbitrary code loading if users create ruby source files with names that correspond to those that facter is trying to load. The fix is to explicitly remove '.' from the load path before any code is loaded by facter. --- bin/facter | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bin/facter b/bin/facter index 0616157063..7aec0679f9 100755 --- a/bin/facter +++ b/bin/facter @@ -1,5 +1,9 @@ #!/usr/bin/env ruby +# For security reasons, ensure that '.' is not on the load path +# This is primarily for 1.8.7 since 1.9.2+ doesn't put '.' on the load path +$LOAD_PATH.delete '.' + # Bundler and rubygems maintain a set of directories from which to # load gems. If Bundler is loaded, let it determine what can be # loaded. If it's not loaded, then use rubygems. But do this before From ef4b6df912325079dcf604a696898a3ea8390e8f Mon Sep 17 00:00:00 2001 From: Jeff '2 bits' Bachtel Date: Wed, 7 May 2014 17:52:38 +0000 Subject: [PATCH 1837/3753] Previously, RPMS built on Amazon Linux would point to the improper ruby directory, or would have improper dependencies. Use %{amzn} as a rpm macro to determine if the rpm is being built on an Amazon Linux distro. If %{amzn} is set, use the Fedora/RHEL7 method of discovering ruby lib directory. Use the rubygem-rdoc package dependency instead of ruby-rdoc --- ext/redhat/facter.spec.erb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/redhat/facter.spec.erb b/ext/redhat/facter.spec.erb index a38d5bea48..435507c150 100644 --- a/ext/redhat/facter.spec.erb +++ b/ext/redhat/facter.spec.erb @@ -1,6 +1,6 @@ # Fedora 17 ships with ruby 1.9, RHEL 7 with ruby 2.0, which use vendorlibdir instead # of sitelibdir -%if 0%{?fedora} >= 17 || 0%{?rhel} >= 7 || %{_vendor} == "amazon" +%if 0%{?fedora} >= 17 || 0%{?rhel} >= 7 || 0%{?amzn} >= 1 %global facter_libdir %(ruby -rrbconfig -e 'puts RbConfig::CONFIG["vendorlibdir"]') %else %global facter_libdir %(ruby -rrbconfig -e 'puts RbConfig::CONFIG["sitelibdir"]') @@ -36,7 +36,7 @@ Requires: ruby >= 1.8.7 BuildRequires: ruby >= 1.8.7 # In Fedora 17+ or RHEL 7+ ruby-rdoc is called rubygem-rdoc -%if 0%{?fedora} >= 17 || 0%{?rhel} >= 7 || %{_vendor} == "amazon" +%if 0%{?fedora} >= 17 || 0%{?rhel} >= 7 || 0%{?amzn} >= 1 BuildRequires: rubygem-rdoc %else BuildRequires: ruby-rdoc From d61ac20c4d65cff92954c6e1f9b49cd624fde1c1 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Thu, 1 May 2014 20:59:31 -0700 Subject: [PATCH 1838/3753] (maint) Add unit tests for util and execution. Adding tests for: - facter::util::file - facter::util::option_set - facter::util::posix::scoped_descriptor - facter::util::posix::scoped_addrinfo - facter::util::bsd::scoped_ifaddrs - facter::execution::posix --- .gitignore | 1 + lib/inc/facter/util/option_set.hpp | 13 +- lib/inc/facter/util/posix/scoped_addrinfo.hpp | 1 + lib/tests/CMakeLists.txt | 15 + lib/tests/environment.cc | 28 ++ lib/tests/execution/posix/execution.cc | 56 ++++ lib/tests/fixtures.cc | 23 ++ lib/tests/fixtures.hpp.in | 15 + lib/tests/fixtures/execution/ls/file1.txt | 1 + lib/tests/fixtures/execution/ls/file2.txt | 1 + lib/tests/fixtures/execution/ls/file3.txt | 1 + lib/tests/fixtures/execution/selfkill.sh | 3 + lib/tests/fixtures/util/multiline_file.txt | 3 + lib/tests/util/bsd/scoped_ifaddrs.cc | 10 + lib/tests/util/file.cc | 50 ++++ lib/tests/util/option_set.cc | 282 ++++++++++++++++++ lib/tests/util/posix/scoped_addrinfo.cc | 11 + lib/tests/util/posix/scoped_descriptor.cc | 21 ++ 18 files changed, 528 insertions(+), 7 deletions(-) create mode 100644 lib/tests/environment.cc create mode 100644 lib/tests/execution/posix/execution.cc create mode 100644 lib/tests/fixtures.cc create mode 100644 lib/tests/fixtures.hpp.in create mode 100644 lib/tests/fixtures/execution/ls/file1.txt create mode 100644 lib/tests/fixtures/execution/ls/file2.txt create mode 100644 lib/tests/fixtures/execution/ls/file3.txt create mode 100644 lib/tests/fixtures/execution/selfkill.sh create mode 100644 lib/tests/fixtures/util/multiline_file.txt create mode 100644 lib/tests/util/bsd/scoped_ifaddrs.cc create mode 100644 lib/tests/util/file.cc create mode 100644 lib/tests/util/option_set.cc create mode 100644 lib/tests/util/posix/scoped_addrinfo.cc create mode 100644 lib/tests/util/posix/scoped_descriptor.cc diff --git a/.gitignore b/.gitignore index 982571654d..3383c56e12 100644 --- a/.gitignore +++ b/.gitignore @@ -6,5 +6,6 @@ compile_commands.json # Generated files /lib/inc/facter/version.h +/lib/tests/fixtures.hpp /debug/ /release/ diff --git a/lib/inc/facter/util/option_set.hpp b/lib/inc/facter/util/option_set.hpp index ab0c4fef60..28da2503b1 100644 --- a/lib/inc/facter/util/option_set.hpp +++ b/lib/inc/facter/util/option_set.hpp @@ -90,7 +90,7 @@ namespace facter { namespace util { */ option_set& set(enum_type option) { - _value |= option; + _value |= static_cast(option); return *this; } @@ -101,7 +101,7 @@ namespace facter { namespace util { */ option_set& clear(enum_type option) { - _value &= ~option; + _value &= ~static_cast(option); return *this; } @@ -132,7 +132,7 @@ namespace facter { namespace util { */ option_set& toggle(enum_type option) { - _value ^= option; + _value ^= static_cast(option); return *this; } @@ -194,7 +194,7 @@ namespace facter { namespace util { template option_set operator &(option_set const& lhs, option_set const& rhs) { - return option_set(option_set::value_type(lhs) & option_set::value_type(rhs)); + return option_set(static_cast::value_type>(lhs) & static_cast::value_type>(rhs)); } /** @@ -206,7 +206,7 @@ namespace facter { namespace util { template option_set operator |(option_set const& lhs, option_set const& rhs) { - return option_set(option_set::value_type(lhs) | option_set::value_type(rhs)); + return option_set(static_cast::value_type>(lhs) | static_cast::value_type>(rhs)); } /** @@ -215,11 +215,10 @@ namespace facter { namespace util { * @param rhs The righthand option_set. * @return Returns an option_set that is the bitwise XOR of the two given option_sets. */ - template option_set operator ^(option_set const& lhs, option_set const& rhs) { - return option_set(option_set::value_type(lhs) ^ option_set::value_type(rhs)); + return option_set(static_cast::value_type>(lhs) ^ static_cast::value_type>(rhs)); } }} // namespace facter::util diff --git a/lib/inc/facter/util/posix/scoped_addrinfo.hpp b/lib/inc/facter/util/posix/scoped_addrinfo.hpp index 68f6b452b3..1041521d1b 100644 --- a/lib/inc/facter/util/posix/scoped_addrinfo.hpp +++ b/lib/inc/facter/util/posix/scoped_addrinfo.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include namespace facter { namespace util { namespace posix { diff --git a/lib/tests/CMakeLists.txt b/lib/tests/CMakeLists.txt index b3252ee29b..690138c030 100644 --- a/lib/tests/CMakeLists.txt +++ b/lib/tests/CMakeLists.txt @@ -11,23 +11,32 @@ endif() # Set the common (platform-independent) sources set(LIBFACTER_TESTS_COMMON_SOURCES + "${CMAKE_CURRENT_LIST_DIR}/environment.cc" "${CMAKE_CURRENT_LIST_DIR}/facts/integer_value.cc" "${CMAKE_CURRENT_LIST_DIR}/facts/posix/uptime_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/util/string.cc" + "${CMAKE_CURRENT_LIST_DIR}/util/file.cc" + "${CMAKE_CURRENT_LIST_DIR}/util/option_set.cc" + "${CMAKE_CURRENT_LIST_DIR}/fixtures.cc" ) # Set the POSIX sources if on a POSIX platform if (UNIX) set(LIBFACTER_TESTS_POSIX_SOURCES + "${CMAKE_CURRENT_LIST_DIR}/execution/posix/execution.cc" + "${CMAKE_CURRENT_LIST_DIR}/util/posix/scoped_addrinfo.cc" + "${CMAKE_CURRENT_LIST_DIR}/util/posix/scoped_descriptor.cc" ) endif() # Set the platform-specific sources if ("${CMAKE_SYSTEM_NAME}" MATCHES "Darwin") set(LIBFACTER_TESTS_PLATFORM_SOURCES + "${CMAKE_CURRENT_LIST_DIR}/util/bsd/scoped_ifaddrs.cc" ) elseif ("${CMAKE_SYSTEM_NAME}" MATCHES "Linux") set(LIBFACTER_TESTS_PLATFORM_SOURCES + "${CMAKE_CURRENT_LIST_DIR}/util/bsd/scoped_ifaddrs.cc" ) endif() @@ -40,3 +49,9 @@ include_directories( add_executable(libfacter_test ${LIBFACTER_TESTS_COMMON_SOURCES} ${LIBFACTER_TESTS_PLATFORM_SOURCES} ${LIBFACTER_TESTS_POSIX_SOURCES}) target_link_libraries(libfacter_test libfacter ${LOG4CXX_LIBRARIES} ${Boost_LIBRARIES} ${GMOCK_LIBRARIES}) + +# Generate a file containing the above version numbers +configure_file ( + "${CMAKE_CURRENT_LIST_DIR}/fixtures.hpp.in" + "${CMAKE_CURRENT_LIST_DIR}/fixtures.hpp" +) diff --git a/lib/tests/environment.cc b/lib/tests/environment.cc new file mode 100644 index 0000000000..944929e24a --- /dev/null +++ b/lib/tests/environment.cc @@ -0,0 +1,28 @@ +#include +#include +#include +#include +#include + +using namespace std; +using namespace log4cxx; + +struct Environment : testing::Environment +{ + virtual void SetUp() + { + // Setup log4cxx + LayoutPtr layout = new PatternLayout("%d %-5p %c - %m%n"); + AppenderPtr appender = new ConsoleAppender(layout); + Logger::getRootLogger()->addAppender(appender); + + // To change logging output, set this to your desired level + Logger::getRootLogger()->setLevel(Level::getWarn()); + } + + virtual void TearDown() + { + } +}; + +static ::testing::Environment* const env = ::testing::AddGlobalTestEnvironment(new Environment()); diff --git a/lib/tests/execution/posix/execution.cc b/lib/tests/execution/posix/execution.cc new file mode 100644 index 0000000000..ae7fde0397 --- /dev/null +++ b/lib/tests/execution/posix/execution.cc @@ -0,0 +1,56 @@ +#include +#include +#include +#include "../../fixtures.hpp" + +using namespace std; +using namespace facter::util; +using namespace facter::execution; +using namespace facter::testing; + +TEST(execution_posix, simple_execution) { + string output = execute("cat", { LIBFACTER_TESTS_DIRECTORY "/fixtures/execution/ls/file3.txt" }); + ASSERT_EQ("file3", output); +} + +TEST(execution_posix, simple_execution_with_args) { + string output = execute("ls", { LIBFACTER_TESTS_DIRECTORY "/fixtures/execution/ls" }); + ASSERT_EQ("file1.txt\nfile2.txt\nfile3.txt", output); +} + +TEST(execution_posix, stderr_redirection) { + // By default, we don't return stderr + string output = execute("ls", { "does_not_exist" }); + ASSERT_EQ("", output); + + output = execute("ls", { "does_not_exist" }, { execution_options::defaults, execution_options::redirect_stderr }); + ASSERT_TRUE(ends_with(output, "No such file or directory")); +} + +TEST(execution_posix, throw_on_nonzero_exit) { + // By default, we don't throw an exception + string output = execute("ls", { "does_not_exist" }); + ASSERT_EQ("", output); + + ASSERT_THROW(execute("ls", { "does_not_exist" }, { execution_options::defaults, execution_options::throw_on_nonzero_exit }), child_exit_exception); +} + +TEST(execution_posix, throw_on_signal) { + // By default, we don't throw an exception + string output = execute("sh", { LIBFACTER_TESTS_DIRECTORY "/fixtures/execution/selfkill.sh" }); + ASSERT_EQ("", output); + + ASSERT_THROW(execute("sh", { LIBFACTER_TESTS_DIRECTORY "/fixtures/execution/selfkill.sh" }, { execution_options::defaults, execution_options::throw_on_signal }), child_signal_exception); +} + +TEST(execution_posix, trim_output) { + // We should trim output by default + string output = execute("cat", { LIBFACTER_TESTS_DIRECTORY "/fixtures/execution/ls/file1.txt" }); + ASSERT_EQ("this is a test of trimming", output); + + // Now try again without any execution options + option_set options = { execution_options::defaults }; + options.clear(execution_options::trim_output); + output = execute("cat", { LIBFACTER_TESTS_DIRECTORY "/fixtures/execution/ls/file1.txt" }, options); + ASSERT_EQ(" this is a test of trimming ", output); +} diff --git a/lib/tests/fixtures.cc b/lib/tests/fixtures.cc new file mode 100644 index 0000000000..3331444495 --- /dev/null +++ b/lib/tests/fixtures.cc @@ -0,0 +1,23 @@ +#include "fixtures.hpp" +#include +#include +#include + +using namespace std; + +namespace facter { namespace testing { + + bool load_fixture(string const& name, string& data) + { + string path = string(LIBFACTER_TESTS_DIRECTORY) + "/fixtures/" + name; + ifstream in(path); + if (!in) { + return false; + } + ostringstream buffer; + buffer << in.rdbuf(); + data = buffer.str(); + return true; + } + +}} // namespace facter::testing diff --git a/lib/tests/fixtures.hpp.in b/lib/tests/fixtures.hpp.in new file mode 100644 index 0000000000..addb5a0215 --- /dev/null +++ b/lib/tests/fixtures.hpp.in @@ -0,0 +1,15 @@ +#ifndef LIB_TESTS_FIXTURES_HPP_ +#define LIB_TESTS_FIXTURES_HPP_ + +#include + +#define LIBFACTER_TESTS_DIRECTORY "@CMAKE_CURRENT_LIST_DIR@" + +namespace facter { namespace testing { + + bool load_fixture(std::string const& name, std::string& data); + +}} // namespace facter::testing + +#endif // LIB_TESTS_FIXTURES_HPP_ + diff --git a/lib/tests/fixtures/execution/ls/file1.txt b/lib/tests/fixtures/execution/ls/file1.txt new file mode 100644 index 0000000000..42654d7d79 --- /dev/null +++ b/lib/tests/fixtures/execution/ls/file1.txt @@ -0,0 +1 @@ + this is a test of trimming \ No newline at end of file diff --git a/lib/tests/fixtures/execution/ls/file2.txt b/lib/tests/fixtures/execution/ls/file2.txt new file mode 100644 index 0000000000..30d67d4672 --- /dev/null +++ b/lib/tests/fixtures/execution/ls/file2.txt @@ -0,0 +1 @@ +file2 \ No newline at end of file diff --git a/lib/tests/fixtures/execution/ls/file3.txt b/lib/tests/fixtures/execution/ls/file3.txt new file mode 100644 index 0000000000..873fb8d667 --- /dev/null +++ b/lib/tests/fixtures/execution/ls/file3.txt @@ -0,0 +1 @@ +file3 \ No newline at end of file diff --git a/lib/tests/fixtures/execution/selfkill.sh b/lib/tests/fixtures/execution/selfkill.sh new file mode 100644 index 0000000000..b39226e2cc --- /dev/null +++ b/lib/tests/fixtures/execution/selfkill.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env sh + +kill -9 $$ diff --git a/lib/tests/fixtures/util/multiline_file.txt b/lib/tests/fixtures/util/multiline_file.txt new file mode 100644 index 0000000000..be6312e63b --- /dev/null +++ b/lib/tests/fixtures/util/multiline_file.txt @@ -0,0 +1,3 @@ +this is a line of text +this is another line of text +this is a third line of text \ No newline at end of file diff --git a/lib/tests/util/bsd/scoped_ifaddrs.cc b/lib/tests/util/bsd/scoped_ifaddrs.cc new file mode 100644 index 0000000000..1ee4faa0dd --- /dev/null +++ b/lib/tests/util/bsd/scoped_ifaddrs.cc @@ -0,0 +1,10 @@ +#include +#include + +using namespace std; +using namespace facter::util::bsd; + +TEST(facter_util_bsd_scoped_ifaddrs, construction) { + scoped_ifaddrs addrs; + ASSERT_NE(nullptr, static_cast(addrs)); +} diff --git a/lib/tests/util/file.cc b/lib/tests/util/file.cc new file mode 100644 index 0000000000..4112d05e9a --- /dev/null +++ b/lib/tests/util/file.cc @@ -0,0 +1,50 @@ +#include +#include +#include +#include "../fixtures.hpp" + +using namespace std; +using namespace facter::util; +using namespace facter::testing; + +TEST(facter_util_file, read) { + string fixture_path = "util/multiline_file.txt"; + string fixture_file_path = LIBFACTER_TESTS_DIRECTORY "/fixtures/" + fixture_path; + + // Test non-existant file + string data; + ASSERT_EQ("", file::read("does_not_exist")); + ASSERT_EQ("", file::read("")); + ASSERT_FALSE(file::read("does_not_exist", data)); + ASSERT_FALSE(file::read("", data)); + + // Test read + string fixture; + ASSERT_TRUE(load_fixture(fixture_path, fixture)); + ASSERT_EQ(fixture, file::read(fixture_file_path)); + ASSERT_TRUE(file::read(fixture_file_path, data)); + ASSERT_EQ(fixture, data); +} + +TEST(facter_util_file, read_first_line) { + string fixture_path = "util/multiline_file.txt"; + string fixture_file_path = LIBFACTER_TESTS_DIRECTORY "/fixtures/" + fixture_path; + + // Test non-existant file + string data; + ASSERT_EQ("", file::read_first_line("does_not_exist")); + ASSERT_EQ("", file::read_first_line("")); + ASSERT_FALSE(file::read_first_line("does_not_exist", data)); + ASSERT_FALSE(file::read_first_line("", data)); + + // Test read + string fixture; + ASSERT_TRUE(load_fixture(fixture_path, fixture)); + + vector lines = split(fixture, '\n'); + ASSERT_EQ(3u, lines.size()); + + ASSERT_EQ(lines[0], file::read_first_line(fixture_file_path)); + ASSERT_TRUE(file::read_first_line(fixture_file_path, data)); + ASSERT_EQ(lines[0], data); +} diff --git a/lib/tests/util/option_set.cc b/lib/tests/util/option_set.cc new file mode 100644 index 0000000000..686a77accb --- /dev/null +++ b/lib/tests/util/option_set.cc @@ -0,0 +1,282 @@ +#include +#include + +using namespace std; +using namespace facter::util; + +enum class options +{ + foo = (1 << 1), + bar = (1 << 2), + baz = (1 << 3) +}; + +TEST(facter_util_option_set, construction) { + option_set s1; + ASSERT_EQ(0u, s1.count()); + ASSERT_FALSE(s1[options::foo]); + ASSERT_FALSE(s1[options::bar]); + ASSERT_FALSE(s1[options::baz]); + + option_set s2 = { options::foo }; + ASSERT_EQ(1u, s2.count()); + ASSERT_TRUE(s2[options::foo]); + ASSERT_FALSE(s2[options::bar]); + ASSERT_FALSE(s2[options::baz]); + + option_set s3 = { options::bar, options::foo }; + ASSERT_EQ(2u, s3.count()); + ASSERT_TRUE(s3[options::foo]); + ASSERT_TRUE(s3[options::bar]); + ASSERT_FALSE(s3[options::baz]); + + option_set s4 = { options::baz, options::foo, options::bar }; + ASSERT_EQ(3u, s4.count()); + ASSERT_TRUE(s4[options::foo]); + ASSERT_TRUE(s4[options::bar]); + ASSERT_TRUE(s4[options::baz]); + + option_set s5(1 | 2 | 4); + ASSERT_EQ(3u, s4.count()); + ASSERT_TRUE(s4[options::foo]); + ASSERT_TRUE(s4[options::bar]); + ASSERT_TRUE(s4[options::baz]); +} + +TEST(facter_util_option_set, set_all) { + option_set s; + ASSERT_EQ(0u, s.count()); + ASSERT_FALSE(s[options::foo]); + ASSERT_FALSE(s[options::bar]); + ASSERT_FALSE(s[options::baz]); + + s.set_all(); + ASSERT_EQ(s.size(), s.count()); + ASSERT_TRUE(s[options::foo]); + ASSERT_TRUE(s[options::bar]); + ASSERT_TRUE(s[options::baz]); +} + +TEST(facter_util_option_set, set) { + option_set s; + ASSERT_EQ(0u, s.count()); + ASSERT_FALSE(s[options::foo]); + ASSERT_FALSE(s[options::bar]); + ASSERT_FALSE(s[options::baz]); + + s.set(options::foo); + ASSERT_EQ(1u, s.count()); + ASSERT_TRUE(s[options::foo]); + ASSERT_FALSE(s[options::bar]); + ASSERT_FALSE(s[options::baz]); + + s.set(options::bar); + ASSERT_EQ(2u, s.count()); + ASSERT_TRUE(s[options::foo]); + ASSERT_TRUE(s[options::bar]); + ASSERT_FALSE(s[options::baz]); + + s.set(options::baz); + ASSERT_EQ(3u, s.count()); + ASSERT_TRUE(s[options::foo]); + ASSERT_TRUE(s[options::bar]); + ASSERT_TRUE(s[options::baz]); +} + +TEST(facter_util_option_set, clear) { + option_set s = { options::foo, options::bar, options::baz }; + ASSERT_EQ(3u, s.count()); + ASSERT_TRUE(s[options::foo]); + ASSERT_TRUE(s[options::bar]); + ASSERT_TRUE(s[options::baz]); + + s.clear(options::foo); + ASSERT_EQ(2u, s.count()); + ASSERT_FALSE(s[options::foo]); + ASSERT_TRUE(s[options::bar]); + ASSERT_TRUE(s[options::baz]); + + s.clear(options::bar); + ASSERT_EQ(1u, s.count()); + ASSERT_FALSE(s[options::foo]); + ASSERT_FALSE(s[options::bar]); + ASSERT_TRUE(s[options::baz]); + + s.clear(options::baz); + ASSERT_EQ(0u, s.count()); + ASSERT_FALSE(s[options::foo]); + ASSERT_FALSE(s[options::bar]); + ASSERT_FALSE(s[options::baz]); +} + +TEST(facter_util_option_set, reset) { + option_set s; + ASSERT_EQ(0u, s.count()); + ASSERT_FALSE(s[options::foo]); + ASSERT_FALSE(s[options::bar]); + ASSERT_FALSE(s[options::baz]); + + s.reset(); + ASSERT_EQ(0u, s.count()); + ASSERT_FALSE(s[options::foo]); + ASSERT_FALSE(s[options::bar]); + ASSERT_FALSE(s[options::baz]); + + s.set(options::foo).set(options::bar).set(options::baz); + ASSERT_EQ(3u, s.count()); + ASSERT_TRUE(s[options::foo]); + ASSERT_TRUE(s[options::bar]); + ASSERT_TRUE(s[options::baz]); + + s.reset(); + ASSERT_EQ(0u, s.count()); + ASSERT_FALSE(s[options::foo]); + ASSERT_FALSE(s[options::bar]); + ASSERT_FALSE(s[options::baz]); +} + +TEST(facter_util_option_set, toggle) { + option_set s; + ASSERT_EQ(0u, s.count()); + ASSERT_FALSE(s[options::foo]); + ASSERT_FALSE(s[options::bar]); + ASSERT_FALSE(s[options::baz]); + + s.toggle(); + ASSERT_EQ(s.size(), s.count()); + ASSERT_TRUE(s[options::foo]); + ASSERT_TRUE(s[options::bar]); + ASSERT_TRUE(s[options::baz]); + + s.reset(); + s.set(options::foo).set(options::bar).set(options::baz); + ASSERT_EQ(3u, s.count()); + ASSERT_TRUE(s[options::foo]); + ASSERT_TRUE(s[options::bar]); + ASSERT_TRUE(s[options::baz]); + + s.toggle(); + ASSERT_EQ(s.size() - 3, s.count()); + ASSERT_FALSE(s[options::foo]); + ASSERT_FALSE(s[options::bar]); + ASSERT_FALSE(s[options::baz]); +} + +TEST(facter_util_option_set, toggle_option) { + option_set s; + ASSERT_EQ(0u, s.count()); + ASSERT_FALSE(s[options::foo]); + + s.toggle(options::foo); + ASSERT_EQ(1u, s.count()); + ASSERT_TRUE(s[options::foo]); + + s.toggle(options::foo); + ASSERT_EQ(0u, s.count()); + ASSERT_FALSE(s[options::foo]); +} + +TEST(facter_util_option_set, count) { + option_set s; + ASSERT_EQ(0u, s.count()); + + s.toggle(); + ASSERT_EQ(s.size(), s.count()); + + s.reset(); + s.set(options::foo); + ASSERT_EQ(1u, s.count()); + + s.set(options::bar); + ASSERT_EQ(2u, s.count()); + + s.set(options::baz); + ASSERT_EQ(3u, s.count()); +} + +TEST(facter_util_option_set, size) { + option_set s; + ASSERT_EQ(sizeof(int) * 8, s.size()); +} + +TEST(facter_util_option_set, test) { + option_set s; + ASSERT_EQ(0u, s.count()); + ASSERT_FALSE(s.test(options::foo)); + ASSERT_FALSE(s.test(options::bar)); + ASSERT_FALSE(s.test(options::baz)); + + s.set(options::bar); + ASSERT_EQ(1u, s.count()); + ASSERT_FALSE(s.test(options::foo)); + ASSERT_TRUE(s.test(options::bar)); + ASSERT_FALSE(s.test(options::baz)); +} + +TEST(facter_util_option_set, empty) { + option_set s; + ASSERT_TRUE(s.empty()); + + s.set(options::bar); + ASSERT_FALSE(s.empty()); +} + +TEST(facter_util_option_set, bitwise_and) { + option_set s1 = { options::foo, options::bar }; + ASSERT_EQ(2u, s1.count()); + ASSERT_TRUE(s1[options::foo]); + ASSERT_TRUE(s1[options::bar]); + ASSERT_FALSE(s1[options::baz]); + + option_set s2 = { options::bar, options::baz }; + ASSERT_EQ(2u, s2.count()); + ASSERT_FALSE(s2[options::foo]); + ASSERT_TRUE(s2[options::bar]); + ASSERT_TRUE(s2[options::baz]); + + option_set s3 = s1 & s2; + ASSERT_EQ(1u, s3.count()); + ASSERT_FALSE(s3[options::foo]); + ASSERT_TRUE(s3[options::bar]); + ASSERT_FALSE(s3[options::baz]); +} + +TEST(facter_util_option_set, bitwise_or) { + option_set s1 = { options::foo, options::bar }; + ASSERT_EQ(2u, s1.count()); + ASSERT_TRUE(s1[options::foo]); + ASSERT_TRUE(s1[options::bar]); + ASSERT_FALSE(s1[options::baz]); + + option_set s2 = { options::baz }; + ASSERT_EQ(1u, s2.count()); + ASSERT_FALSE(s2[options::foo]); + ASSERT_FALSE(s2[options::bar]); + ASSERT_TRUE(s2[options::baz]); + + option_set s3 = s1 | s2; + ASSERT_EQ(3u, s3.count()); + ASSERT_TRUE(s3[options::foo]); + ASSERT_TRUE(s3[options::bar]); + ASSERT_TRUE(s3[options::baz]); +} + +TEST(facter_util_option_set, bitwise_xor) { + option_set s1 = { options::foo, options::bar }; + ASSERT_EQ(2u, s1.count()); + ASSERT_TRUE(s1[options::foo]); + ASSERT_TRUE(s1[options::bar]); + ASSERT_FALSE(s1[options::baz]); + + option_set s2 = { options::bar, options::baz }; + ASSERT_EQ(2u, s2.count()); + ASSERT_FALSE(s2[options::foo]); + ASSERT_TRUE(s2[options::bar]); + ASSERT_TRUE(s2[options::baz]); + + option_set s3 = s1 ^ s2; + ASSERT_EQ(2u, s3.count()); + ASSERT_TRUE(s3[options::foo]); + ASSERT_FALSE(s3[options::bar]); + ASSERT_TRUE(s3[options::baz]); +} diff --git a/lib/tests/util/posix/scoped_addrinfo.cc b/lib/tests/util/posix/scoped_addrinfo.cc new file mode 100644 index 0000000000..79cd522f87 --- /dev/null +++ b/lib/tests/util/posix/scoped_addrinfo.cc @@ -0,0 +1,11 @@ +#include +#include + +using namespace std; +using namespace facter::util::posix; + +TEST(facter_util_posix_scoped_addrinfo, construction) { + scoped_addrinfo info("localhost"); + ASSERT_EQ(0, info.result()); + ASSERT_NE(nullptr, static_cast(info)); +} diff --git a/lib/tests/util/posix/scoped_descriptor.cc b/lib/tests/util/posix/scoped_descriptor.cc new file mode 100644 index 0000000000..f99df8386a --- /dev/null +++ b/lib/tests/util/posix/scoped_descriptor.cc @@ -0,0 +1,21 @@ +#include +#include +#include +#include + +using namespace std; +using namespace facter::util::posix; + +TEST(facter_util_posix_scoped_descriptor, construction) { + int sock = socket(AF_INET, SOCK_DGRAM, 0); + + { + scoped_descriptor scoped(sock); + } + + // This check could theoretically fail if the OS reassigns the file + // descriptor between the above destructor call and this line + // This likely will not happen during testing. + ASSERT_EQ(-1, fcntl(sock, F_GETFD)); + ASSERT_EQ(EBADF, errno); +} From 2ce588070b5921e25e645e033a594f688362b9f7 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Sat, 3 May 2014 23:42:12 -0700 Subject: [PATCH 1839/3753] (maint) Add test target to cmake output. Adding a test target to cmake output so tests can be run with just "make test". Updating travis to run tests through ctest. Updating README to cover running tests. --- CMakeLists.txt | 5 +++++ README.md | 18 ++++++++++++++++++ lib/tests/CMakeLists.txt | 2 +- scripts/travis_target.sh | 8 ++------ 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7bb00fefdb..d40ca5f973 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,8 @@ if (NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel." FORCE) endif() +enable_testing() + set(VENDOR_DIRECTORY "${PROJECT_SOURCE_DIR}/vendor") list(APPEND CMAKE_MODULE_PATH ${VENDOR_DIRECTORY}) @@ -76,3 +78,6 @@ endif() add_custom_target(cppcheck COMMAND cppcheck lib exe ) + +# Add test executables for unit testing +add_test("library\\ tests" "${PROJECT_BINARY_DIR}/lib/tests/libfacter_test" "--gtest_color=yes") diff --git a/README.md b/README.md index 9a7818bab4..c5ef6ef7c9 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,24 @@ For a debug build: `$ debug/exe/cfacter` +Test +---- + +You can run cfacter tests using the test target: + + $ cd release + $ make test + +For a debug build: + + $ cd debug + $ make test + +For verbose test output, run `ctest` instead of using the test target: + + $ cd release + $ ctest -V + Install ------- diff --git a/lib/tests/CMakeLists.txt b/lib/tests/CMakeLists.txt index 690138c030..71c397618b 100644 --- a/lib/tests/CMakeLists.txt +++ b/lib/tests/CMakeLists.txt @@ -11,7 +11,7 @@ endif() # Set the common (platform-independent) sources set(LIBFACTER_TESTS_COMMON_SOURCES - "${CMAKE_CURRENT_LIST_DIR}/environment.cc" + "${CMAKE_CURRENT_LIST_DIR}/environment.cc" "${CMAKE_CURRENT_LIST_DIR}/facts/integer_value.cc" "${CMAKE_CURRENT_LIST_DIR}/facts/posix/uptime_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/util/string.cc" diff --git a/scripts/travis_target.sh b/scripts/travis_target.sh index 4de8c74ca5..2969d5a437 100755 --- a/scripts/travis_target.sh +++ b/scripts/travis_target.sh @@ -30,12 +30,8 @@ function travis_make() # Run library tests if not doing cpplint if [ $1 != "cpplint" ]; then - lib/tests/libfacter_test - local lib_test_status=$? - - # TODO: run executable tests - - if [ $lib_test_status -ne 0 ]; then + ctest -V + if [ $? -ne 0 ]; then echo "tests reported an error." exit 1 fi From 4c8323b2869db4c4d0c241ffcf8872773682a6ac Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Mon, 5 May 2014 15:53:07 -0700 Subject: [PATCH 1840/3753] (maint) Add unit tests for logging. Adding unit tests for debug, info, warning, error, and fatal logging. --- lib/tests/CMakeLists.txt | 1 + lib/tests/logging/posix/logging.cc | 125 +++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 lib/tests/logging/posix/logging.cc diff --git a/lib/tests/CMakeLists.txt b/lib/tests/CMakeLists.txt index 71c397618b..ecf5e8375c 100644 --- a/lib/tests/CMakeLists.txt +++ b/lib/tests/CMakeLists.txt @@ -26,6 +26,7 @@ if (UNIX) "${CMAKE_CURRENT_LIST_DIR}/execution/posix/execution.cc" "${CMAKE_CURRENT_LIST_DIR}/util/posix/scoped_addrinfo.cc" "${CMAKE_CURRENT_LIST_DIR}/util/posix/scoped_descriptor.cc" + "${CMAKE_CURRENT_LIST_DIR}/logging/posix/logging.cc" ) endif() diff --git a/lib/tests/logging/posix/logging.cc b/lib/tests/logging/posix/logging.cc new file mode 100644 index 0000000000..76ae175b8f --- /dev/null +++ b/lib/tests/logging/posix/logging.cc @@ -0,0 +1,125 @@ +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace facter::logging; +using namespace log4cxx; + +LOG_DECLARE_NAMESPACE("logging.test"); + +bool g_color = isatty(fileno(stdout)); + +struct custom_log_appender : AppenderSkeleton +{ + public: + DECLARE_LOG4CXX_OBJECT(custom_log_appender) + BEGIN_LOG4CXX_CAST_MAP() + LOG4CXX_CAST_ENTRY(custom_log_appender) + LOG4CXX_CAST_ENTRY_CHAIN(AppenderSkeleton) + END_LOG4CXX_CAST_MAP() + + void append(const spi::LoggingEventPtr& event, log4cxx::helpers::Pool& p) + { + _level = event->getLevel()->toString(); + _message = event->getMessage(); + } + + void close() {} + bool requiresLayout() const { return false; } + + string const& last_level() const { return _level; } + string const& last_message() const { return _message; } + + private: + string _level; + string _message; +}; + +IMPLEMENT_LOG4CXX_OBJECT(custom_log_appender); + +struct facter_logging : ::testing::Test { + protected: + virtual void SetUp() + { + auto root = Logger::getRootLogger(); + + _level = root->getLevel(); + root->setLevel(Level::getDebug()); + + _appenders = root->getAllAppenders(); + root->removeAllAppenders(); + + _appender = new custom_log_appender(); + root->addAppender(_appender); + } + + virtual void TearDown() + { + auto root = Logger::getRootLogger(); + + Logger::getRootLogger()->setLevel(_level); + + root->removeAllAppenders(); + for (auto const& appender : _appenders) { + root->addAppender(appender); + } + } + + custom_log_appender* _appender; + AppenderList _appenders; + LevelPtr _level; +}; + +TEST_F(facter_logging, debug) { + ASSERT_EQ(true, LOG_IS_DEBUG_ENABLED()); + LOG_DEBUG("testing %1% %2% %3%", 1, "2", 3.0); + ASSERT_EQ("DEBUG", _appender->last_level()); + ASSERT_EQ(g_color ? "\x1B[0;36mtesting 1 2 3\x1B[0m" : "testing 1 2 3", _appender->last_message()); + log(g_logger, log_level::debug, "testing %1% %2% %3%", 1, "2", 3.0); + ASSERT_EQ("DEBUG", _appender->last_level()); + ASSERT_EQ(g_color ? "\x1B[0;36mtesting 1 2 3\x1B[0m" : "testing 1 2 3", _appender->last_message()); +} + +TEST_F(facter_logging, info) { + ASSERT_EQ(true, LOG_IS_INFO_ENABLED()); + LOG_INFO("testing %1% %2% %3%", 1, "2", 3.0); + ASSERT_EQ("INFO", _appender->last_level()); + ASSERT_EQ(g_color ? "\x1B[0;32mtesting 1 2 3\x1B[0m" : "testing 1 2 3", _appender->last_message()); + log(g_logger, log_level::info, "testing %1% %2% %3%", 1, "2", 3.0); + ASSERT_EQ("INFO", _appender->last_level()); + ASSERT_EQ(g_color ? "\x1B[0;32mtesting 1 2 3\x1B[0m" : "testing 1 2 3", _appender->last_message()); +} + +TEST_F(facter_logging, warning) { + ASSERT_EQ(true, LOG_IS_WARNING_ENABLED()); + LOG_WARNING("testing %1% %2% %3%", 1, "2", 3.0); + ASSERT_EQ("WARN", _appender->last_level()); + ASSERT_EQ(g_color ? "\x1B[0;33mtesting 1 2 3\x1B[0m" : "testing 1 2 3", _appender->last_message()); + log(g_logger, log_level::warning, "testing %1% %2% %3%", 1, "2", 3.0); + ASSERT_EQ("WARN", _appender->last_level()); + ASSERT_EQ(g_color ? "\x1B[0;33mtesting 1 2 3\x1B[0m" : "testing 1 2 3", _appender->last_message()); +} + +TEST_F(facter_logging, error) { + ASSERT_EQ(true, LOG_IS_ERROR_ENABLED()); + LOG_ERROR("testing %1% %2% %3%", 1, "2", 3.0); + ASSERT_EQ("ERROR", _appender->last_level()); + ASSERT_EQ(g_color ? "\x1B[0;31mtesting 1 2 3\x1B[0m" : "testing 1 2 3", _appender->last_message()); + log(g_logger, log_level::error, "testing %1% %2% %3%", 1, "2", 3.0); + ASSERT_EQ("ERROR", _appender->last_level()); + ASSERT_EQ(g_color ? "\x1B[0;31mtesting 1 2 3\x1B[0m" : "testing 1 2 3", _appender->last_message()); +} + +TEST_F(facter_logging, fatal) { + ASSERT_EQ(true, LOG_IS_FATAL_ENABLED()); + LOG_FATAL("testing %1% %2% %3%", 1, "2", 3.0); + ASSERT_EQ("FATAL", _appender->last_level()); + ASSERT_EQ(g_color ? "\x1B[0;31mtesting 1 2 3\x1B[0m" : "testing 1 2 3", _appender->last_message()); + log(g_logger, log_level::fatal, "testing %1% %2% %3%", 1, "2", 3.0); + ASSERT_EQ("FATAL", _appender->last_level()); + ASSERT_EQ(g_color ? "\x1B[0;31mtesting 1 2 3\x1B[0m" : "testing 1 2 3", _appender->last_message()); +} From 24cfa2c7ef4a2d1b4592c45d222bbf66035353d3 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Mon, 5 May 2014 19:08:44 -0700 Subject: [PATCH 1841/3753] (maint) Add tests for array_value. Adding unit tests for facter::facts::array_value. --- lib/inc/facter/facts/array_value.hpp | 7 +++ lib/tests/CMakeLists.txt | 1 + lib/tests/facts/array_value.cc | 89 ++++++++++++++++++++++++++++ 3 files changed, 97 insertions(+) create mode 100644 lib/tests/facts/array_value.cc diff --git a/lib/inc/facter/facts/array_value.hpp b/lib/inc/facter/facts/array_value.hpp index 91a82ff6d3..e50605e8fa 100644 --- a/lib/inc/facter/facts/array_value.hpp +++ b/lib/inc/facter/facts/array_value.hpp @@ -49,6 +49,13 @@ namespace facter { namespace facts { */ std::vector> const& elements() const { return _elements; } + /** + * Gets the value at the given index. + * @param i The index in the array to get the element at. + * @return Returns the value at the given index. + */ + value const* operator[](size_t i) const { return _elements.at(i).get(); } + protected: /** * Writes the value to the given stream. diff --git a/lib/tests/CMakeLists.txt b/lib/tests/CMakeLists.txt index ecf5e8375c..a0bf54eb04 100644 --- a/lib/tests/CMakeLists.txt +++ b/lib/tests/CMakeLists.txt @@ -12,6 +12,7 @@ endif() # Set the common (platform-independent) sources set(LIBFACTER_TESTS_COMMON_SOURCES "${CMAKE_CURRENT_LIST_DIR}/environment.cc" + "${CMAKE_CURRENT_LIST_DIR}/facts/array_value.cc" "${CMAKE_CURRENT_LIST_DIR}/facts/integer_value.cc" "${CMAKE_CURRENT_LIST_DIR}/facts/posix/uptime_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/util/string.cc" diff --git a/lib/tests/facts/array_value.cc b/lib/tests/facts/array_value.cc new file mode 100644 index 0000000000..c6067108fe --- /dev/null +++ b/lib/tests/facts/array_value.cc @@ -0,0 +1,89 @@ +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace facter::facts; +using namespace rapidjson; + +TEST(facter_facts_array_value, default_constructor) { + array_value value; + ASSERT_EQ(0u, value.elements().size()); +} + +TEST(facter_facts_array_value, vector_constructor) { + vector> subelements; + subelements.emplace_back(make_value("child")); + + vector> elements; + elements.emplace_back(make_value("1")); + elements.emplace_back(make_value(2)); + elements.emplace_back(make_value(move(subelements))); + + array_value value(move(elements)); + ASSERT_EQ(3u, value.elements().size()); + + auto str = dynamic_cast(value[0]); + ASSERT_NE(nullptr, str); + ASSERT_EQ("1", str->value()); + + auto integer = dynamic_cast(value[1]); + ASSERT_NE(nullptr, integer); + ASSERT_EQ(2, integer->value()); + + auto array = dynamic_cast(value[2]); + ASSERT_NE(nullptr, array); + ASSERT_EQ(1u, array->elements().size()); + + str = dynamic_cast((*array)[0]); + ASSERT_NE(nullptr, str); + ASSERT_EQ("child", str->value()); +} + +TEST(facter_facts_array_value, to_json) { + vector> subelements; + subelements.emplace_back(make_value("child")); + + vector> elements; + elements.emplace_back(make_value("1")); + elements.emplace_back(make_value(2)); + elements.emplace_back(make_value(move(subelements))); + + array_value value(move(elements)); + + Value json_value; + MemoryPoolAllocator<> allocator; + value.to_json(allocator, json_value); + ASSERT_TRUE(json_value.IsArray()); + ASSERT_EQ(3u, json_value.Size()); + + ASSERT_TRUE(json_value[0u].IsString()); + ASSERT_EQ("1", string(json_value[0u].GetString())); + + ASSERT_TRUE(json_value[1u].IsNumber()); + ASSERT_EQ(2ll, json_value[1u].GetInt64()); + + ASSERT_TRUE(json_value[2u].IsArray()); + ASSERT_EQ(1u, json_value[2u].Size()); + ASSERT_TRUE(json_value[2u][0u].IsString()); + ASSERT_EQ("child", string(json_value[2u][0u].GetString())); +} + +TEST(facter_facts_array_value, insertion_operator) { + vector> subelements; + subelements.emplace_back(make_value("child")); + + vector> elements; + elements.emplace_back(make_value("1")); + elements.emplace_back(make_value(2)); + elements.emplace_back(make_value(move(subelements))); + + array_value value(move(elements)); + + ostringstream stream; + stream << value; + ASSERT_EQ("[ 1, 2, [ child ] ]", stream.str()); +} From 3393c0cb5014f157a4725766881b6c79a710589e Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Mon, 5 May 2014 19:15:46 -0700 Subject: [PATCH 1842/3753] (maint) Add tests for string_value. Adding tests for facter::facts::string_value. --- lib/tests/CMakeLists.txt | 1 + lib/tests/facts/string_value.cc | 40 +++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 lib/tests/facts/string_value.cc diff --git a/lib/tests/CMakeLists.txt b/lib/tests/CMakeLists.txt index a0bf54eb04..26a0dd27fe 100644 --- a/lib/tests/CMakeLists.txt +++ b/lib/tests/CMakeLists.txt @@ -14,6 +14,7 @@ set(LIBFACTER_TESTS_COMMON_SOURCES "${CMAKE_CURRENT_LIST_DIR}/environment.cc" "${CMAKE_CURRENT_LIST_DIR}/facts/array_value.cc" "${CMAKE_CURRENT_LIST_DIR}/facts/integer_value.cc" + "${CMAKE_CURRENT_LIST_DIR}/facts/string_value.cc" "${CMAKE_CURRENT_LIST_DIR}/facts/posix/uptime_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/util/string.cc" "${CMAKE_CURRENT_LIST_DIR}/util/file.cc" diff --git a/lib/tests/facts/string_value.cc b/lib/tests/facts/string_value.cc new file mode 100644 index 0000000000..ec2374fcb8 --- /dev/null +++ b/lib/tests/facts/string_value.cc @@ -0,0 +1,40 @@ +#include +#include +#include +#include + +using namespace std; +using namespace facter::facts; +using namespace rapidjson; + +TEST(facter_facts_string_value, move_constructor) { + string s = "hello world"; + string_value value(move(s)); + ASSERT_EQ("", s); + ASSERT_EQ("hello world", value.value()); +} + +TEST(facter_facts_string_value, copy_constructor) { + string s = "hello world"; + string_value value(s); + ASSERT_EQ("hello world", s); + ASSERT_EQ("hello world", value.value()); +} + +TEST(facter_facts_string_value, to_json) { + string_value value("hello world"); + + Value json_value; + MemoryPoolAllocator<> allocator; + value.to_json(allocator, json_value); + ASSERT_TRUE(json_value.IsString()); + ASSERT_EQ("hello world", string(json_value.GetString())); +} + +TEST(facter_facts_string_value, insertion_operator) { + string_value value("hello world"); + + ostringstream stream; + stream << value; + ASSERT_EQ("hello world", stream.str()); +} From 5dd744c82571b3331c4b868f1f03d3d0dddc9394 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Mon, 5 May 2014 19:18:48 -0700 Subject: [PATCH 1843/3753] (maint) Add tests for integer_value. Add additional unit tests for facter::facts::integer_value. --- lib/tests/facts/integer_value.cc | 36 ++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/lib/tests/facts/integer_value.cc b/lib/tests/facts/integer_value.cc index 064b712ec6..7aeafc1a87 100644 --- a/lib/tests/facts/integer_value.cc +++ b/lib/tests/facts/integer_value.cc @@ -1,25 +1,21 @@ #include #include +#include +#include +using namespace std; using namespace facter::facts; +using namespace rapidjson; -TEST(facter_facts_integer_value, to_string) { +TEST(facter_facts_integer_value, integer_constructor) { // small integer integer_value foo(42); ASSERT_EQ(42, foo.value()); - // small integer string - integer_value foo2("42"); - ASSERT_EQ(42, foo2.value()); - // very large integer but in range int64_t large_int = 1LL << 62; integer_value foo3(large_int); ASSERT_EQ(4611686018427387904LL, foo3.value()); - - // very large integer string - integer_value foo4("4611686018427387904"); - ASSERT_EQ(4611686018427387904LL, foo4.value()); } TEST(facter_facts_integer_value, string_constructor) { @@ -27,8 +23,30 @@ TEST(facter_facts_integer_value, string_constructor) { integer_value foo("42"); ASSERT_EQ(42, foo.value()); + // very large integer string + integer_value foo4("4611686018427387904"); + ASSERT_EQ(4611686018427387904LL, foo4.value()); + // string not-an-integer // TODO: expect a warning log message integer_value not_an_integer("i am not an integer"); ASSERT_EQ(0, not_an_integer.value()); } + +TEST(facter_facts_integer_value, to_json) { + integer_value value(1337); + + Value json_value; + MemoryPoolAllocator<> allocator; + value.to_json(allocator, json_value); + ASSERT_TRUE(json_value.IsNumber()); + ASSERT_EQ(1337, json_value.GetInt64()); +} + +TEST(facter_facts_integer_value, insertion_operator) { + integer_value value(5); + + ostringstream stream; + stream << value; + ASSERT_EQ("5", stream.str()); +} From abff2859d5451f74b66316f1ae83bbc08a817351 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Mon, 5 May 2014 19:48:44 -0700 Subject: [PATCH 1844/3753] (maint) Add tests for map_value. Adding unit tests for facter::facts::map_value. --- lib/inc/facter/facts/map_value.hpp | 7 ++ lib/src/facts/map_value.cc | 9 +++ lib/tests/CMakeLists.txt | 1 + lib/tests/facts/map_value.cc | 117 +++++++++++++++++++++++++++++ 4 files changed, 134 insertions(+) create mode 100644 lib/tests/facts/map_value.cc diff --git a/lib/inc/facter/facts/map_value.hpp b/lib/inc/facter/facts/map_value.hpp index c54132b855..e872960ada 100644 --- a/lib/inc/facter/facts/map_value.hpp +++ b/lib/inc/facter/facts/map_value.hpp @@ -50,6 +50,13 @@ namespace facter { namespace facts { */ std::map> const& elements() const { return _elements; } + /** + * Gets the value in the map of the given name. + * @param name The name of the value in the map to get. + * @return Returns the value in the map or nullptr if the value is not in the map. + */ + value const* operator[](std::string const& name) const; + protected: /** * Writes the value to the given stream. diff --git a/lib/src/facts/map_value.cc b/lib/src/facts/map_value.cc index bea3be0d9c..fafb1c3a23 100644 --- a/lib/src/facts/map_value.cc +++ b/lib/src/facts/map_value.cc @@ -6,6 +6,15 @@ using namespace rapidjson; namespace facter { namespace facts { + value const* map_value::operator[](string const& name) const + { + auto it = _elements.find(name); + if (it == _elements.end()) { + return nullptr; + } + return it->second.get(); + } + void map_value::to_json(Allocator& allocator, Value& value) const { value.SetObject(); diff --git a/lib/tests/CMakeLists.txt b/lib/tests/CMakeLists.txt index 26a0dd27fe..6dfffbdbd7 100644 --- a/lib/tests/CMakeLists.txt +++ b/lib/tests/CMakeLists.txt @@ -14,6 +14,7 @@ set(LIBFACTER_TESTS_COMMON_SOURCES "${CMAKE_CURRENT_LIST_DIR}/environment.cc" "${CMAKE_CURRENT_LIST_DIR}/facts/array_value.cc" "${CMAKE_CURRENT_LIST_DIR}/facts/integer_value.cc" + "${CMAKE_CURRENT_LIST_DIR}/facts/map_value.cc" "${CMAKE_CURRENT_LIST_DIR}/facts/string_value.cc" "${CMAKE_CURRENT_LIST_DIR}/facts/posix/uptime_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/util/string.cc" diff --git a/lib/tests/facts/map_value.cc b/lib/tests/facts/map_value.cc new file mode 100644 index 0000000000..10ce81c409 --- /dev/null +++ b/lib/tests/facts/map_value.cc @@ -0,0 +1,117 @@ +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace facter::facts; +using namespace rapidjson; + +TEST(facter_facts_map_value, default_constructor) { + map_value value; + ASSERT_EQ(0u, value.elements().size()); +} + +TEST(facter_facts_map_value, map_constructor) { + map> elements; + elements["string"] = make_value("hello"); + elements["integer"] = make_value(5); + + vector> array_elements; + array_elements.emplace_back(make_value("1")); + array_elements.emplace_back(make_value(2)); + elements["array"] = make_value(move(array_elements)); + + map> submap; + submap["foo"] = make_value("bar"); + elements["map"] = make_value(move(submap)); + + map_value value(move(elements)); + ASSERT_EQ(4u, value.elements().size()); + + auto str = dynamic_cast(value["string"]); + ASSERT_NE(nullptr, str); + ASSERT_EQ("hello", str->value()); + + auto integer = dynamic_cast(value["integer"]); + ASSERT_NE(nullptr, integer); + ASSERT_EQ(5, integer->value()); + + auto array = dynamic_cast(value["array"]); + ASSERT_NE(nullptr, array); + ASSERT_EQ(2u, array->elements().size()); + str = dynamic_cast((*array)[0]); + ASSERT_EQ("1", str->value()); + integer = dynamic_cast((*array)[1]); + ASSERT_EQ(2u, integer->value()); + + auto mapval = dynamic_cast(value["map"]); + ASSERT_NE(nullptr, mapval); + ASSERT_EQ(1u, mapval->elements().size()); + str = dynamic_cast((*mapval)["foo"]); + ASSERT_NE(nullptr, str); + ASSERT_EQ("bar", str->value()); +} + +TEST(facter_facts_map_value, to_json) { + map> elements; + elements["string"] = make_value("hello"); + elements["integer"] = make_value(5); + + vector> array_elements; + array_elements.emplace_back(make_value("1")); + array_elements.emplace_back(make_value(2)); + elements["array"] = make_value(move(array_elements)); + + map> submap; + submap["foo"] = make_value("bar"); + elements["map"] = make_value(move(submap)); + + map_value value(move(elements)); + + Value json_value; + MemoryPoolAllocator<> allocator; + value.to_json(allocator, json_value); + ASSERT_TRUE(json_value.IsObject()); + + ASSERT_TRUE(json_value["string"].IsString()); + ASSERT_EQ("hello", string(json_value["string"].GetString())); + + ASSERT_TRUE(json_value["integer"].IsNumber()); + ASSERT_EQ(5ll, json_value["integer"].GetInt64()); + + ASSERT_TRUE(json_value["array"].IsArray()); + ASSERT_EQ(2u, json_value["array"].Size()); + ASSERT_TRUE(json_value["array"][0u].IsString()); + ASSERT_EQ("1", string(json_value["array"][0u].GetString())); + ASSERT_TRUE(json_value["array"][1u].IsNumber()); + ASSERT_EQ(2ll, json_value["array"][1u].GetInt64()); + + ASSERT_TRUE(json_value["map"].IsObject()); + ASSERT_TRUE(json_value["map"]["foo"].IsString()); + ASSERT_EQ("bar", string(json_value["map"]["foo"].GetString())); +} + +TEST(facter_facts_map_value, insertion_operator) { + map> elements; + elements["string"] = make_value("hello"); + elements["integer"] = make_value(5); + + vector> array_elements; + array_elements.emplace_back(make_value("1")); + array_elements.emplace_back(make_value(2)); + elements["array"] = make_value(move(array_elements)); + + map> submap; + submap["foo"] = make_value("bar"); + elements["map"] = make_value(move(submap)); + + map_value value(move(elements)); + + ostringstream stream; + stream << value; + ASSERT_EQ("{ array => [ 1, 2 ], integer => 5, map => { foo => bar }, string => hello }", stream.str()); +} From 5b1d91880b94722d623038573211ba67d6295bf7 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Thu, 8 May 2014 22:55:21 -0700 Subject: [PATCH 1845/3753] (fact-233) Update confine to reflect where the fact is supported --- lib/facter/dhcp_servers.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/dhcp_servers.rb b/lib/facter/dhcp_servers.rb index d3ea817fea..2242b3451e 100644 --- a/lib/facter/dhcp_servers.rb +++ b/lib/facter/dhcp_servers.rb @@ -17,7 +17,7 @@ Facter.add(:dhcp_servers) do - confine :kernel => :linux + confine :operatingsystem => "Fedora", :operatingsystemrelease => "19" confine do Facter::Core::Execution.which('nmcli') end From d598f92bac7681097c541d6e81c95cc92e10d85f Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Thu, 8 May 2014 23:09:29 -0700 Subject: [PATCH 1846/3753] (fact-233) Fix spec test to match updated confines --- spec/unit/dhcp_servers_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/unit/dhcp_servers_spec.rb b/spec/unit/dhcp_servers_spec.rb index abda8d06f8..f77f9b17fe 100644 --- a/spec/unit/dhcp_servers_spec.rb +++ b/spec/unit/dhcp_servers_spec.rb @@ -3,7 +3,8 @@ describe "DHCP server facts" do describe "on Linux OS's" do before :each do - Facter.fact(:kernel).stubs(:value).returns 'Linux' + Facter.fact(:operatingsystem).stubs(:value).returns 'Fedora' + Facter.fact(:operatingsystemrelease).stubs(:value).returns '19' Facter::Core::Execution.stubs(:exec).with("route -n").returns(my_fixture_read("route")) end From c9fc18765ea3bc49f1b5129d7cef3f56001e902f Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Wed, 7 May 2014 16:43:03 -0700 Subject: [PATCH 1847/3753] (maint) Fix Linux physicalprocessorcount fact to handle missing topology information. CPU topology information may not be present in /proc/cpuinfo. If it is missing, we were not properly counting the physical CPUs present on the system. The fix is to first attempt to use sysfs to count CPUs as it more reliable for the topology information. If sysfs isn't present, we fall back to using topology information in /proc/cpuinfo. This change matches what we do in Ruby facter. --- lib/inc/facter/util/file.hpp | 9 +++ lib/src/facts/linux/processor_resolver.cc | 91 +++++++++++++++-------- lib/src/util/file.cc | 16 ++++ 3 files changed, 83 insertions(+), 33 deletions(-) diff --git a/lib/inc/facter/util/file.hpp b/lib/inc/facter/util/file.hpp index 361d9b807d..2f70920a71 100644 --- a/lib/inc/facter/util/file.hpp +++ b/lib/inc/facter/util/file.hpp @@ -3,9 +3,18 @@ #include #include +#include namespace facter { namespace util { namespace file { + /** + * Reads each line from the given file. + * @param path The path to the file to read. + * @param callback The callback function that is passed each line in the file. + * @return Returns true if the file was opened successfully or false if it was not. + */ + bool each_line(std::string const& path, std::function callback); + /** * Reads the entire contents of the given file into a string. * @param path The path of the file to read. diff --git a/lib/src/facts/linux/processor_resolver.cc b/lib/src/facts/linux/processor_resolver.cc index b4b0629aa3..d18bf39b93 100644 --- a/lib/src/facts/linux/processor_resolver.cc +++ b/lib/src/facts/linux/processor_resolver.cc @@ -4,11 +4,9 @@ #include #include #include -#include -#include +#include +#include #include -#include -#include #include using namespace std; @@ -16,6 +14,7 @@ using namespace re2; using namespace facter::facts; using namespace facter::facts::posix; using namespace facter::util; +using namespace boost::filesystem; LOG_DECLARE_NAMESPACE("facts.linux.processor"); @@ -60,41 +59,67 @@ namespace facter { namespace facts { namespace linux { void processor_resolver::resolve_processors(fact_map& facts) { - // For linux, we need to search through the output of /proc/cpuinfo - ifstream cpuinfo("/proc/cpuinfo", ifstream::in); - unordered_set cpus; - size_t logical_processor_count = 0; - - // Search through each line of output - string line; - string id; - while (getline(cpuinfo, line)) { - auto pos = line.find(":"); - string key = trim(line.substr(0, pos)); - string value = trim(line.substr(pos + 1)); + size_t logical_count = 0; + size_t physical_count = 0; - // If the key is processor, it's the start of a processor - if (key == "processor") { - id = move(value); - ++logical_processor_count; - } else if (key == "model name" && !id.empty()) { - // Add the processor description fact - facts.add(fact::processor + id, make_value(move(value))); - } else if (key == "physical id") { - // Add the physical id to the set so we only count each one once - cpus.emplace(move(value)); - } + // To determine physical CPU count, we need to look at sysfs. + // The topology information may not be present in /proc/cpuinfo for older kernels + directory_iterator end; + try { + for (auto it = directory_iterator("/sys/devices/system/cpu"); it != end; ++it) { + if (!is_directory(it->status()) || !RE2::FullMatch(it->path().filename().string(), "^cpu\\d+$")) { + continue; + } + ++logical_count; + string id = trim(file::read((it->path() / "/topology/physical_package_id").string())); + if (id.empty() || cpus.emplace(move(id)).second) { + // Haven't seen this processor before + ++physical_count; + } + } + } catch (filesystem_error&) { + // Couldn't determine counts; fall back to cpuinfo + logical_count = 0; + physical_count = 0; + cpus.clear(); } - // Logical count should be at least the physical count - if (logical_processor_count < cpus.size()) { - logical_processor_count = cpus.size(); - } + // To determine model information, parse /proc/cpuinfo + bool have_counts = logical_count > 0; + string id; + file::each_line("/proc/cpuinfo", [&](string& line) { + // Split the line on colon + auto pos = line.find(":"); + if (pos == string::npos) { + return true; + } + string key = trim(line.substr(0, pos)); + string value = trim(line.substr(pos + 1)); + + if (key == "processor") { + // Start of a logical processor + id = move(value); + if (!have_counts) { + ++logical_count; + } + } else if (!id.empty() && key == "model name") { + // Add the model name fact for this logical processor + facts.add(fact::processor + id, make_value(move(value))); + } else if (!have_counts && key == "physical id" && cpus.emplace(move(value)).second) { + // Couldn't determine physical count from sysfs, but CPU topology is present, so use it + ++physical_count; + } + return true; + }); // Add the count facts - facts.add(fact::physical_processor_count, make_value(to_string(cpus.size()))); - facts.add(fact::processor_count, make_value(to_string(logical_processor_count))); + if (logical_count > 0) { + facts.add(fact::processor_count, make_value(to_string(logical_count))); + } + if (physical_count > 0) { + facts.add(fact::physical_processor_count, make_value(to_string(physical_count))); + } } }}} // namespace facter::facts::linux diff --git a/lib/src/util/file.cc b/lib/src/util/file.cc index 190836fd71..eb01b4b1c8 100644 --- a/lib/src/util/file.cc +++ b/lib/src/util/file.cc @@ -6,6 +6,22 @@ using namespace std; namespace facter { namespace util { namespace file { + bool each_line(string const& path, function callback) + { + ifstream in(path); + if (!in) { + return false; + } + + string line; + while (getline(in, line)) { + if (!callback(line)) { + break; + } + } + return true; + } + string read(string const& path) { string contents; From d924f58d3fe4a2b1afc73d749cae72b9aee8ad87 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Fri, 9 May 2014 13:59:55 -0700 Subject: [PATCH 1848/3753] (maint) Add unit test for file::each_line. Adding a unit test for facter::util::file::each_line. --- lib/tests/util/file.cc | 54 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/lib/tests/util/file.cc b/lib/tests/util/file.cc index 4112d05e9a..3ebf42e969 100644 --- a/lib/tests/util/file.cc +++ b/lib/tests/util/file.cc @@ -6,12 +6,62 @@ using namespace std; using namespace facter::util; using namespace facter::testing; +using testing::ElementsAre; + +TEST(facter_util_file, each_line) { + string fixture_path = "util/multiline_file.txt"; + string fixture_file_path = LIBFACTER_TESTS_DIRECTORY "/fixtures/" + fixture_path; + + string data; + ASSERT_TRUE(load_fixture(fixture_path, data)); + vector fixture_lines = split(data, '\n'); + + // Ensure there's no carriage returns + transform( + fixture_lines.begin(), + fixture_lines.end(), + fixture_lines.begin(), + [](string& s) { + rtrim(s, { '\r' }); + return s; + }); + + // Test non-existent file + bool failed = false; + ASSERT_FALSE(file::each_line("does_not_exist", [&failed](string& line) { + failed = true; + return true; + })); + + if (failed) { + FAIL(); + } + + // Test all lines + vector lines; + ASSERT_TRUE(file::each_line(fixture_file_path, [&lines](string& line) { + lines.emplace_back(move(line)); + return true; + })); + ASSERT_EQ(3u, lines.size()); + ASSERT_THAT(lines, fixture_lines); + + // Test short circuiting + int count = 0; + lines.clear(); + ASSERT_TRUE(file::each_line(fixture_file_path, [&lines, &count](string& line) { + lines.emplace_back(move(line)); + return ++count < 2; + })); + ASSERT_EQ(2u, lines.size()); + ASSERT_THAT(lines, ElementsAre(lines[0], lines[1])); +} TEST(facter_util_file, read) { string fixture_path = "util/multiline_file.txt"; string fixture_file_path = LIBFACTER_TESTS_DIRECTORY "/fixtures/" + fixture_path; - // Test non-existant file + // Test non-existent file string data; ASSERT_EQ("", file::read("does_not_exist")); ASSERT_EQ("", file::read("")); @@ -30,7 +80,7 @@ TEST(facter_util_file, read_first_line) { string fixture_path = "util/multiline_file.txt"; string fixture_file_path = LIBFACTER_TESTS_DIRECTORY "/fixtures/" + fixture_path; - // Test non-existant file + // Test non-existent file string data; ASSERT_EQ("", file::read_first_line("does_not_exist")); ASSERT_EQ("", file::read_first_line("")); From b4e92b3479f004cf3e60e7c31930ed7e990e6a5a Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Fri, 9 May 2014 22:18:33 -0700 Subject: [PATCH 1849/3753] (maint) Speed up travis builds by grabbing a pre-built cmake --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index df81a15063..5eeccd28b1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,9 +14,9 @@ before_install: # and g++ 4.8 - sudo apt-get -y install g++-4.8 - sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.8 50 - # and we need a newer cmake, so build it ourselves (maybe should build this and host it somewhere) - - wget http://www.cmake.org/files/v2.8/cmake-2.8.12.2.tar.gz -O $HOME/cmake-2.8.12.2.tar.gz - - pushd $HOME && tar xzf cmake-2.8.12.2.tar.gz && cd $HOME/cmake-2.8.12.2 && ./configure --prefix=$HOME > /dev/null && make > /dev/null && make install > /dev/null && popd + # grab a pre-built cmake 2.8.12 from s3 + - wget https://s3.amazonaws.com/kylo_sandbox/cmake_install.tar.bz2 + - tar xjvf cmake_install.tar.bz2 --strip 1 -C $HOME # Install dependencies of cfacter - sudo apt-get -y install libboost-filesystem1.48-dev libboost-program-options1.48-dev liblog4cxx10-dev - wget https://re2.googlecode.com/files/re2-20140304.tgz -O $HOME/re2-20140304.tgz From 724102cde7da1bd4c552817da6ee68a59ea525f4 Mon Sep 17 00:00:00 2001 From: Roger Ignazio Date: Wed, 23 Apr 2014 14:36:21 -0700 Subject: [PATCH 1850/3753] (FACT-460) Modify $GEM_SOURCE for CI bootstrap It is preferable to override the authoritative source (rubygems.org) with our own internal resources, as opposed to the other way around. This change ensures that if $GEM_SOURCE is set in the environment, it is honored. Otherwise, we fall back to the public authority, rubygems.org. This change is analogous to the following line in our Gemfiles: source ENV['GEM_SOURCE'] || "https://rubygems.org/" --- acceptance/bin/ci-bootstrap-from-artifacts.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/acceptance/bin/ci-bootstrap-from-artifacts.sh b/acceptance/bin/ci-bootstrap-from-artifacts.sh index 2b55a2e1ee..ef1afa004e 100755 --- a/acceptance/bin/ci-bootstrap-from-artifacts.sh +++ b/acceptance/bin/ci-bootstrap-from-artifacts.sh @@ -12,9 +12,9 @@ set -x -# Use our internal rubygems mirror for the bundle install +# If $GEM_SOURCE is not set, fall back to rubygems.org if [ -z $GEM_SOURCE ]; then - export GEM_SOURCE='http://rubygems.delivery.puppetlabs.net/' + export GEM_SOURCE='https://rubygems.org/' fi echo "SHA: ${SHA}" From ffd0050e295385c66f36b0bba3aaa50b684c137c Mon Sep 17 00:00:00 2001 From: Roger Ignazio Date: Wed, 23 Apr 2014 14:36:21 -0700 Subject: [PATCH 1851/3753] (FACT-460) Modify $GEM_SOURCE for CI bootstrap It is preferable to override the authoritative source (rubygems.org) with our own internal resources, as opposed to the other way around. This change ensures that if $GEM_SOURCE is set in the environment, it is honored. Otherwise, we fall back to the public authority, rubygems.org. This change is analogous to the following line in our Gemfiles: source ENV['GEM_SOURCE'] || "https://rubygems.org/" --- acceptance/bin/ci-bootstrap-from-artifacts.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/acceptance/bin/ci-bootstrap-from-artifacts.sh b/acceptance/bin/ci-bootstrap-from-artifacts.sh index 2b55a2e1ee..ef1afa004e 100755 --- a/acceptance/bin/ci-bootstrap-from-artifacts.sh +++ b/acceptance/bin/ci-bootstrap-from-artifacts.sh @@ -12,9 +12,9 @@ set -x -# Use our internal rubygems mirror for the bundle install +# If $GEM_SOURCE is not set, fall back to rubygems.org if [ -z $GEM_SOURCE ]; then - export GEM_SOURCE='http://rubygems.delivery.puppetlabs.net/' + export GEM_SOURCE='https://rubygems.org/' fi echo "SHA: ${SHA}" From 083ced43f87d13a6aa8d89ed6d9bb9425ea98d81 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Wed, 7 May 2014 13:57:58 -0700 Subject: [PATCH 1852/3753] (CFACT-29) Implement SELinux facts --- lib/CMakeLists.txt | 1 + lib/inc/facter/facts/fact.hpp | 8 ++ .../facter/facts/linux/selinux_resolver.hpp | 73 ++++++++++++ lib/src/facts/linux/platform.cc | 2 + lib/src/facts/linux/selinux_resolver.cc | 105 ++++++++++++++++++ 5 files changed, 189 insertions(+) create mode 100644 lib/inc/facter/facts/linux/selinux_resolver.hpp create mode 100644 lib/src/facts/linux/selinux_resolver.cc diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index a21bffcf38..0f4b739e25 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -68,6 +68,7 @@ elseif ("${CMAKE_SYSTEM_NAME}" MATCHES "Linux") "${CMAKE_CURRENT_LIST_DIR}/src/facts/linux/uptime_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/linux/platform.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/linux/processor_resolver.cc" + "${CMAKE_CURRENT_LIST_DIR}/src/facts/linux/selinux_resolver.cc" ) endif() diff --git a/lib/inc/facter/facts/fact.hpp b/lib/inc/facter/facts/fact.hpp index 9026169a85..e56af1fdd0 100644 --- a/lib/inc/facter/facts/fact.hpp +++ b/lib/inc/facter/facts/fact.hpp @@ -71,6 +71,14 @@ namespace facter { namespace facts { constexpr static char const* uptime_days = "uptime_days"; constexpr static char const* uptime_hours = "uptime_hours"; constexpr static char const* uptime_seconds = "uptime_seconds"; + + // selinux facts + constexpr static char const* selinux = "selinux"; + constexpr static char const* selinux_enforced = "selinux_enforced"; + constexpr static char const* selinux_policyversion = "selinux_policyversion"; + constexpr static char const* selinux_current_mode = "selinux_current_mode"; + constexpr static char const* selinux_config_mode = "selinux_config_mode"; + constexpr static char const* selinux_config_policy = "selinux_config_policy"; }; }} // namespace facter::facts diff --git a/lib/inc/facter/facts/linux/selinux_resolver.hpp b/lib/inc/facter/facts/linux/selinux_resolver.hpp new file mode 100644 index 0000000000..7a2a6f58e1 --- /dev/null +++ b/lib/inc/facter/facts/linux/selinux_resolver.hpp @@ -0,0 +1,73 @@ +#ifndef FACTER_FACTS_LINUX_SELINUX_RESOLVER_HPP_ +#define FACTER_FACTS_LINUX_SELINUX_RESOLVER_HPP_ + +#include "../fact_resolver.hpp" +#include "../fact.hpp" + +namespace facter { namespace facts { namespace linux { + + /** + * Responsible for resolving SELinux facts + */ + struct selinux_resolver : fact_resolver + { + /** + * Constructs the selinux resolver. + */ + selinux_resolver() : + fact_resolver( + "selinux", + { + fact::selinux, + fact::selinux_enforced, + fact::selinux_policyversion, + fact::selinux_current_mode, + fact::selinux_config_mode, + fact::selinux_config_policy, + }) + { + } + + protected: + /** + * Called to resolve all facts the resolver is responsible for. + * @param facts The fact map that is resolving facts. + */ + virtual void resolve_facts(fact_map& facts); + + /** + * Called to resolve all facts read from the SELinux pseudo filesystem. + * @param facts The fact map that is resolving facts. + */ + virtual void resolve_selinux_fs_facts(fact_map& facts); + + /** + * Called to resolve whether SELinux is enabled and enforcing. + * @param facts The fact map that is resolving facts. + * @param mount The SELinux mount point + */ + virtual void resolve_selinux_enforce(fact_map& facts, const std::string& mount); + + /** + * Called to resolve the SELINUX policy version + * @param facts The fact map that is resolving facts. + * @param mount The SELinux mount point + */ + virtual void resolve_selinux_policyvers(fact_map& facts, const std::string& mount); + + /** + * Called to resolve all facts read from the SELinux configuration file. + * @param facts The fact map that is resolving facts. + */ + virtual void resolve_selinux_config_facts(fact_map& facts); + + /** + * Determine where the selinux pseudo filesystem is mounted. + */ + bool selinux_fs_mountpoint(std::string& selinux_mount); + }; + +}}} // namespace facter::facts::linux + +#endif // FACTER_FACTS_LINUX_SELINUX_RESOLVER_HPP_ + diff --git a/lib/src/facts/linux/platform.cc b/lib/src/facts/linux/platform.cc index 538329849d..5644990dd5 100644 --- a/lib/src/facts/linux/platform.cc +++ b/lib/src/facts/linux/platform.cc @@ -7,6 +7,7 @@ #include #include #include +#include using namespace std; @@ -22,6 +23,7 @@ namespace facter { namespace facts { facts.add(make_shared()); facts.add(make_shared()); facts.add(make_shared()); + facts.add(make_shared()); } }} // namespace facter::facts diff --git a/lib/src/facts/linux/selinux_resolver.cc b/lib/src/facts/linux/selinux_resolver.cc new file mode 100644 index 0000000000..ff8ce78cb9 --- /dev/null +++ b/lib/src/facts/linux/selinux_resolver.cc @@ -0,0 +1,105 @@ +#include +#include +#include +#include +#include + +using namespace std; +using namespace facter::util; + +namespace facter { namespace facts { namespace linux { + + void selinux_resolver::resolve_facts(fact_map& facts) + { + resolve_selinux_fs_facts(facts); + resolve_selinux_config_facts(facts); + } + + void selinux_resolver::resolve_selinux_fs_facts(fact_map& facts) + { + string selinux_mount; + if (selinux_fs_mountpoint(selinux_mount)) { + facts.add(fact::selinux, make_value("true")); + + resolve_selinux_enforce(facts, selinux_mount); + resolve_selinux_policyvers(facts, selinux_mount); + } else { + facts.add(fact::selinux, make_value("false")); + } + } + + void selinux_resolver::resolve_selinux_enforce(fact_map& facts, string const& mount) + { + string path = mount + "/enforce"; + string buffer = file::read(path); + + if (buffer.empty()) { + return; + } + + if (buffer == "1") { + facts.add(fact::selinux_enforced, make_value("true")); + facts.add(fact::selinux_current_mode, make_value("enforcing")); + } else { + facts.add(fact::selinux_enforced, make_value("false")); + facts.add(fact::selinux_current_mode, make_value("permissive")); + } + } + + void selinux_resolver::resolve_selinux_policyvers(fact_map& facts, string const& mount) + { + string path = mount + "/policyvers"; + string buffer = file::read(path); + + if (buffer.empty()) { + return; + } + + facts.add(fact::selinux_policyversion, make_value(move(buffer))); + } + + /** + * Called to resolve all facts read from the SELinux configuration file. + * @param facts The fact map that is resolving facts. + */ + void selinux_resolver::resolve_selinux_config_facts(fact_map& facts) + { + string buffer = file::read("/etc/selinux/config"); + + if (buffer.empty()) { + return; + } + + string mode; + if (RE2::PartialMatch(buffer, "(?m)^SELINUX=(\\w+)$" , &mode)) { + facts.add(fact::selinux_config_mode, make_value(move(mode))); + } + + string type; + if (RE2::PartialMatch(buffer, "(?m)^SELINUXTYPE=(\\w+)$" , &type)) { + facts.add(fact::selinux_config_policy, make_value(move(type))); + } + } + + /** + * Determine if the selinux pseudo filesystem is mounted and where it's mounted. + * @param selinux_mount The selinux mountpoint + */ + bool selinux_resolver::selinux_fs_mountpoint(string& selinux_mount) + { + RE2 regexp("\\S+ (\\S+) selinuxfs"); + bool is_mounted = false; + file::each_line("/proc/self/mounts", [&](string& line) { + string mountpoint; + if (RE2::PartialMatch(line, regexp, &mountpoint)) { + selinux_mount = mountpoint; + is_mounted = true; + return false; + } + return true; + }); + return is_mounted; + } + +}}} // facter::facts::linux + From cad0799caa6cdf4ea74d2531e95c405e746e7ac9 Mon Sep 17 00:00:00 2001 From: Jasper Lievisse Adriaanse Date: Thu, 27 Jun 2013 22:59:20 +0200 Subject: [PATCH 1853/3753] Add physicalprocessorcount fact for OpenBSD --- lib/facter/physicalprocessorcount.rb | 7 +++++++ spec/unit/physicalprocessorcount_spec.rb | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/lib/facter/physicalprocessorcount.rb b/lib/facter/physicalprocessorcount.rb index 68917af3bc..3263e20d7b 100644 --- a/lib/facter/physicalprocessorcount.rb +++ b/lib/facter/physicalprocessorcount.rb @@ -80,3 +80,10 @@ end end end + +Facter.add('physicalprocessorcount') do + confine :kernel => :openbsd + setcode do + Facter::Util::Resolution.exec("sysctl -n hw.ncpufound") + end +end diff --git a/spec/unit/physicalprocessorcount_spec.rb b/spec/unit/physicalprocessorcount_spec.rb index acdd646462..af7aa76069 100755 --- a/spec/unit/physicalprocessorcount_spec.rb +++ b/spec/unit/physicalprocessorcount_spec.rb @@ -73,4 +73,12 @@ end end end + + describe "on openbsd" do + it "should return 4 physical CPUs" do + Facter.fact(:kernel).stubs(:value).returns("OpenBSD") + Facter::Util::Resolution.expects(:exec).with("sysctl -n hw.ncpufound").returns("4") + Facter.fact(:physicalprocessorcount).value.should == "4" + end + end end From b8fbbf3547b289cbb70f7a88a80861799e4394e1 Mon Sep 17 00:00:00 2001 From: Jasper Lievisse Adriaanse Date: Tue, 2 Jul 2013 17:10:32 +0200 Subject: [PATCH 1854/3753] Add tests for OpenBSD ifconfig with bridge(4) rules. http://projects.puppetlabs.com/issues/16630 --- spec/fixtures/ifconfig/openbsd_bridge_rules | 11 +++++++++++ spec/unit/macaddress_spec.rb | 12 ++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 spec/fixtures/ifconfig/openbsd_bridge_rules diff --git a/spec/fixtures/ifconfig/openbsd_bridge_rules b/spec/fixtures/ifconfig/openbsd_bridge_rules new file mode 100644 index 0000000000..357de0194f --- /dev/null +++ b/spec/fixtures/ifconfig/openbsd_bridge_rules @@ -0,0 +1,11 @@ +bridge0: flags=41 + groups: bridge + priority 32768 hellotime 2 fwddelay 15 maxage 20 holdcnt 6 proto rstp + vlan1 flags=3 + port 5 ifpriority 0 ifcost 0 + em0 flags=7 + port 1 ifpriority 0 ifcost 0 +block out on em0 src 00:24:21:ef:1b:de +pflog0: flags=41 mtu 33152 + priority: 0 + groups: pflog diff --git a/spec/unit/macaddress_spec.rb b/spec/unit/macaddress_spec.rb index 4550a939fb..1821c8d07e 100755 --- a/spec/unit/macaddress_spec.rb +++ b/spec/unit/macaddress_spec.rb @@ -58,4 +58,16 @@ def ifconfig_fixture(filename) end end + describe "when run on OpenBSD with bridge(4) rules" do + it "should return macaddress information" do + Facter.fact(:kernel).stubs(:value).returns("OpenBSD") + Facter::Util::IP.stubs(:get_ifconfig).returns("/sbin/ifconfig") + Facter::Util::IP.stubs(:exec_ifconfig). + returns(ifconfig_fixture('openbsd_bridge_rules')) + + proc { Facter.value(:macaddress) }.should_not raise_error + Facter.value(:macaddress).should be_nil + end + end + end From bd5ee8598eca88e4756996cd6618acb7986f8fc5 Mon Sep 17 00:00:00 2001 From: Jasper Lievisse Adriaanse Date: Wed, 24 Jul 2013 21:08:14 +0200 Subject: [PATCH 1855/3753] Consolidate several queries for hw.physmem on BSD. Conflicts: lib/facter/util/memory.rb spec/unit/memory_spec.rb --- lib/facter/util/memory.rb | 6 +----- spec/unit/memory_spec.rb | 3 +-- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/lib/facter/util/memory.rb b/lib/facter/util/memory.rb index 818b0fd86b..920391999c 100644 --- a/lib/facter/util/memory.rb +++ b/lib/facter/util/memory.rb @@ -111,14 +111,10 @@ def self.mem_size(kernel = Facter.value(:kernel)) def self.mem_size_info(kernel = Facter.value(:kernel)) case kernel - when /OpenBSD/i - Facter::Core::Execution.exec("sysctl hw.physmem | cut -d'=' -f2") - when /FreeBSD/i + when /Dragonfly/i, /FreeBSD/i, /OpenBSD/i Facter::Core::Execution.exec("sysctl -n hw.physmem") when /Darwin/i Facter::Core::Execution.exec("sysctl -n hw.memsize") - when /Dragonfly/i - Facter::Core::Execution.exec("sysctl -n hw.physmem") when /AIX/i if Facter::Core::Execution.exec("/usr/bin/svmon -O unit=KB") =~ /^memory\s+(\d+)\s+/ $1 diff --git a/spec/unit/memory_spec.rb b/spec/unit/memory_spec.rb index 53a31983ec..04dc73c114 100755 --- a/spec/unit/memory_spec.rb +++ b/spec/unit/memory_spec.rb @@ -230,8 +230,7 @@ 0 0 0 11048 181028 39 0 0 0 0 0 0 1 3 90 17 0 0 100 EOS Facter::Core::Execution.stubs(:exec).with('vmstat').returns(vmstat) - - Facter::Core::Execution.stubs(:exec).with("sysctl hw.physmem | cut -d'=' -f2").returns('267321344') + Facter::Core::Execution.stubs(:exec).with('sysctl -n hw.physmem').returns('267321344') Facter.collection.internal_loader.load(:memory) end From c85c08153b83e08603bf6baf403ef138e0cd300d Mon Sep 17 00:00:00 2001 From: Jasper Lievisse Adriaanse Date: Thu, 27 Jun 2013 21:56:42 +0200 Subject: [PATCH 1856/3753] Add fact for encrypted swap on OpenBSD Conflicts: lib/facter/memory.rb --- lib/facter/memory.rb | 24 +++++++++++++++--------- spec/unit/memory_spec.rb | 6 ++++++ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb index 8ef0c4fc78..6300a28f5f 100644 --- a/lib/facter/memory.rb +++ b/lib/facter/memory.rb @@ -82,15 +82,21 @@ end end -if Facter.value(:kernel) == "Darwin" - Facter.add("SwapEncrypted") do - confine :kernel => :Darwin - setcode do - swap = Facter::Core::Execution.exec('sysctl vm.swapusage') - encrypted = false - if swap =~ /\(encrypted\)/ then encrypted = true; end - encrypted - end +Facter.add("SwapEncrypted") do + confine :kernel => :openbsd + setcode do + sysctl_encrypted = Facter::Core::Execution.exec("sysctl -n vm.swapencrypt.enable").to_i + !(sysctl_encrypted.zero?) + end +end + +Facter.add("SwapEncrypted") do + confine :kernel => :Darwin + setcode do + swap = Facter::Core::Execution.exec('sysctl vm.swapusage') + encrypted = false + if swap =~ /\(encrypted\)/ then encrypted = true; end + encrypted end end diff --git a/spec/unit/memory_spec.rb b/spec/unit/memory_spec.rb index 04dc73c114..fa92367f35 100755 --- a/spec/unit/memory_spec.rb +++ b/spec/unit/memory_spec.rb @@ -232,6 +232,8 @@ Facter::Core::Execution.stubs(:exec).with('vmstat').returns(vmstat) Facter::Core::Execution.stubs(:exec).with('sysctl -n hw.physmem').returns('267321344') + Facter::Core::Execution.stubs(:exec).with('sysctl -n vm.swapencrypt.enable').returns('1') + Facter.collection.internal_loader.load(:memory) end @@ -254,6 +256,10 @@ it "should return the current memory size in MB" do Facter.fact(:memorysize_mb).value.should == "254.94" end + + it "should return whether swap is encrypted" do + Facter.fact(:swapencrypted).value.should == true + end end describe "on Solaris" do From c0f24dc3807843c86aab44f3f89065f927cbf36b Mon Sep 17 00:00:00 2001 From: Antoine Jacoutot Date: Sat, 11 May 2013 10:30:14 +0200 Subject: [PATCH 1857/3753] (#19293) Enhance the release fact for OpenBSD Currently facter only outputs the main release version number without the actual substring information. e.g. 5.3 versus 5.3-beta --- lib/facter/kernelrelease.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/facter/kernelrelease.rb b/lib/facter/kernelrelease.rb index b30a6080ff..c723ebc0dc 100644 --- a/lib/facter/kernelrelease.rb +++ b/lib/facter/kernelrelease.rb @@ -20,6 +20,13 @@ setcode 'oslevel -s' end +Facter.add("kernelrelease") do + confine :kernel => :openbsd + setcode do + Facter::Util::Resolution.exec("/sbin/sysctl -n kern.version").split(' ')[1] + end +end + Facter.add(:kernelrelease) do confine :kernel => "hp-ux" setcode do From 1e967b54ebd8b2ad8f6f3ec8291334f9df1307cb Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Wed, 12 Jun 2013 11:24:08 -0700 Subject: [PATCH 1858/3753] (#19293) Unit tests for openbsd kernelrelease fact --- spec/fixtures/unit/kernelrelease/openbsd-5.3 | 2 ++ .../unit/kernelrelease/openbsd-5.3-current | 3 +++ spec/unit/kernelrelease_spec.rb | 16 ++++++++++++++++ 3 files changed, 21 insertions(+) create mode 100644 spec/fixtures/unit/kernelrelease/openbsd-5.3 create mode 100644 spec/fixtures/unit/kernelrelease/openbsd-5.3-current diff --git a/spec/fixtures/unit/kernelrelease/openbsd-5.3 b/spec/fixtures/unit/kernelrelease/openbsd-5.3 new file mode 100644 index 0000000000..181030d09f --- /dev/null +++ b/spec/fixtures/unit/kernelrelease/openbsd-5.3 @@ -0,0 +1,2 @@ +OpenBSD 5.3 (GENERIC.MP) #2: Fri May 17 15:54:55 CEST 2013 +root@binpatch-53-i386.mtier.org:/home/jasper/binpatchng/work-binpatch53-i386/src/sys/arch/i386/compile/GENERIC.MP diff --git a/spec/fixtures/unit/kernelrelease/openbsd-5.3-current b/spec/fixtures/unit/kernelrelease/openbsd-5.3-current new file mode 100644 index 0000000000..c1b4913a5b --- /dev/null +++ b/spec/fixtures/unit/kernelrelease/openbsd-5.3-current @@ -0,0 +1,3 @@ +OpenBSD 5.3-current (GENERIC.MP) #130: Wed Jun 5 15:15:03 MDT 2013 +deraadt@amd64.openbsd.org:/usr/src/sys/arch/amd64/compile/GENERIC.MP + diff --git a/spec/unit/kernelrelease_spec.rb b/spec/unit/kernelrelease_spec.rb index d0a5133e83..39f7cfacd3 100644 --- a/spec/unit/kernelrelease_spec.rb +++ b/spec/unit/kernelrelease_spec.rb @@ -50,4 +50,20 @@ Facter.fact(:kernelrelease).value.should == "test_kernel" end end + + describe 'on OpenBSD' do + before do + Facter.fact(:kernel).stubs(:value).returns :openbsd + end + + it 'parses 5.3-current sysctl output' do + Facter::Util::Resolution.expects(:exec).with("/sbin/sysctl -n kern.version").returns(my_fixture_read('openbsd-5.3-current')) + Facter.value(:kernelrelease).should == '5.3-current' + end + + it 'parses 5.3 sysctl output' do + Facter::Util::Resolution.expects(:exec).with("/sbin/sysctl -n kern.version").returns(my_fixture_read('openbsd-5.3')) + Facter.value(:kernelrelease).should == '5.3' + end + end end From 1b01b17ee6f86395dfb85a656d93c92c0960bc14 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Wed, 5 Jun 2013 11:15:40 -0700 Subject: [PATCH 1859/3753] (maint) Extract heredoc fixtures from memory spec Conflicts: spec/unit/memory_spec.rb --- spec/fixtures/unit/memory/aix-svmon | 9 ++ spec/fixtures/unit/memory/aix-swap_l | 2 + .../unit/memory/darwin-swapinfo-multiple | 3 + .../unit/memory/darwin-swapinfo-single | 2 + spec/fixtures/unit/memory/darwin-vm_stat | 13 ++ spec/fixtures/unit/memory/dragonfly-vmstat | 3 + spec/fixtures/unit/memory/freebsd-vmstat | 3 + spec/fixtures/unit/memory/linux-proc_meminfo | 10 ++ spec/fixtures/unit/memory/openbsd-vmstat | 3 + spec/fixtures/unit/memory/solaris-prtconf | 4 + .../unit/memory/solaris-swap_l-multiple | 3 + .../unit/memory/solaris-swap_l-single | 2 + spec/fixtures/unit/memory/solaris-vmstat | 3 + spec/unit/memory_spec.rb | 134 +++--------------- 14 files changed, 81 insertions(+), 113 deletions(-) create mode 100644 spec/fixtures/unit/memory/aix-svmon create mode 100644 spec/fixtures/unit/memory/aix-swap_l create mode 100644 spec/fixtures/unit/memory/darwin-swapinfo-multiple create mode 100644 spec/fixtures/unit/memory/darwin-swapinfo-single create mode 100644 spec/fixtures/unit/memory/darwin-vm_stat create mode 100644 spec/fixtures/unit/memory/dragonfly-vmstat create mode 100644 spec/fixtures/unit/memory/freebsd-vmstat create mode 100644 spec/fixtures/unit/memory/linux-proc_meminfo create mode 100644 spec/fixtures/unit/memory/openbsd-vmstat create mode 100644 spec/fixtures/unit/memory/solaris-prtconf create mode 100644 spec/fixtures/unit/memory/solaris-swap_l-multiple create mode 100644 spec/fixtures/unit/memory/solaris-swap_l-single create mode 100644 spec/fixtures/unit/memory/solaris-vmstat diff --git a/spec/fixtures/unit/memory/aix-svmon b/spec/fixtures/unit/memory/aix-svmon new file mode 100644 index 0000000000..bb96729300 --- /dev/null +++ b/spec/fixtures/unit/memory/aix-svmon @@ -0,0 +1,9 @@ +Unit: KB +-------------------------------------------------------------------------------------- + size inuse free pin virtual available mmode +memory 32768000 9948408 22819592 2432080 4448928 27231828 Ded +pg space 34078720 15000 + + work pers clnt other +pin 1478228 0 0 953852 +in use 4448928 0 5499480 diff --git a/spec/fixtures/unit/memory/aix-swap_l b/spec/fixtures/unit/memory/aix-swap_l new file mode 100644 index 0000000000..1966dbf9ba --- /dev/null +++ b/spec/fixtures/unit/memory/aix-swap_l @@ -0,0 +1,2 @@ +device maj,min total free +/dev/hd6 10, 2 512MB 508MB diff --git a/spec/fixtures/unit/memory/darwin-swapinfo-multiple b/spec/fixtures/unit/memory/darwin-swapinfo-multiple new file mode 100644 index 0000000000..f5e9305a25 --- /dev/null +++ b/spec/fixtures/unit/memory/darwin-swapinfo-multiple @@ -0,0 +1,3 @@ +Device 1K-blocks Used Avail Capacity +/dev/da0p3 2048540 0 1048540 0% +/dev/da0p4 3048540 0 1048540 0% diff --git a/spec/fixtures/unit/memory/darwin-swapinfo-single b/spec/fixtures/unit/memory/darwin-swapinfo-single new file mode 100644 index 0000000000..8b41560644 --- /dev/null +++ b/spec/fixtures/unit/memory/darwin-swapinfo-single @@ -0,0 +1,2 @@ +Device 1K-blocks Used Avail Capacity +/dev/da0p3 2048540 0 1048540 0% diff --git a/spec/fixtures/unit/memory/darwin-vm_stat b/spec/fixtures/unit/memory/darwin-vm_stat new file mode 100644 index 0000000000..300f405152 --- /dev/null +++ b/spec/fixtures/unit/memory/darwin-vm_stat @@ -0,0 +1,13 @@ +Mach Virtual Memory Statistics: (page size of 4096 bytes) +Pages free: 28430. +Pages active: 1152576. +Pages inactive: 489054. +Pages speculative: 7076. +Pages wired down: 418217. +"Translation faults": 1340091228. +Pages copy-on-write: 16851357. +Pages zero filled: 665168768. +Pages reactivated: 3082708. +Pageins: 13862917. +Pageouts: 1384383. +Object cache: 14 hits of 2619925 lookups (0% hit rate) diff --git a/spec/fixtures/unit/memory/dragonfly-vmstat b/spec/fixtures/unit/memory/dragonfly-vmstat new file mode 100644 index 0000000000..bae884cc05 --- /dev/null +++ b/spec/fixtures/unit/memory/dragonfly-vmstat @@ -0,0 +1,3 @@ + procs memory page disks faults cpu + r b w avm fre flt re pi po fr sr da0 sg1 in sy cs us sy id + 0 0 0 33152 13940 1902120 2198 53119 11642 6544597 5460994 0 0 6148243 7087927 3484264 0 1 9 diff --git a/spec/fixtures/unit/memory/freebsd-vmstat b/spec/fixtures/unit/memory/freebsd-vmstat new file mode 100644 index 0000000000..3525abf511 --- /dev/null +++ b/spec/fixtures/unit/memory/freebsd-vmstat @@ -0,0 +1,3 @@ + procs memory page disks faults cpu + r b w avm fre flt re pi po fr sr da0 cd0 in sy cs us sy id + 1 0 0 207600 656640 10 0 0 0 13 0 0 0 51 164 257 0 1 99 diff --git a/spec/fixtures/unit/memory/linux-proc_meminfo b/spec/fixtures/unit/memory/linux-proc_meminfo new file mode 100644 index 0000000000..bf68aa0a1a --- /dev/null +++ b/spec/fixtures/unit/memory/linux-proc_meminfo @@ -0,0 +1,10 @@ +MemTotal: 255908 kB +MemFree: 69936 kB +Buffers: 15812 kB +Cached: 115124 kB +SwapCached: 0 kB +Active: 92700 kB +Inactive: 63792 kB +SwapTotal: 524280 kB +SwapFree: 524280 kB +Dirty: 4 kB diff --git a/spec/fixtures/unit/memory/openbsd-vmstat b/spec/fixtures/unit/memory/openbsd-vmstat new file mode 100644 index 0000000000..df57d8e9d3 --- /dev/null +++ b/spec/fixtures/unit/memory/openbsd-vmstat @@ -0,0 +1,3 @@ + procs memory page disks traps cpu + r b w avm fre flt re pi po fr sr cd0 sd0 int sys cs us sy id + 0 0 0 11048 181028 39 0 0 0 0 0 0 1 3 90 17 0 0 100 diff --git a/spec/fixtures/unit/memory/solaris-prtconf b/spec/fixtures/unit/memory/solaris-prtconf new file mode 100644 index 0000000000..5da9238d91 --- /dev/null +++ b/spec/fixtures/unit/memory/solaris-prtconf @@ -0,0 +1,4 @@ +System Configuration: Sun Microsystems sun4u +Memory size: 2048 Megabytes +System Peripherals (Software Nodes): + diff --git a/spec/fixtures/unit/memory/solaris-swap_l-multiple b/spec/fixtures/unit/memory/solaris-swap_l-multiple new file mode 100644 index 0000000000..843eb736da --- /dev/null +++ b/spec/fixtures/unit/memory/solaris-swap_l-multiple @@ -0,0 +1,3 @@ +swapfile dev swaplo blocks free +/dev/swap 4294967295,4294967295 16 2097136 2097136 +/dev/swap2 4294967295,4294967295 16 2097136 2097136 diff --git a/spec/fixtures/unit/memory/solaris-swap_l-single b/spec/fixtures/unit/memory/solaris-swap_l-single new file mode 100644 index 0000000000..1f4e804165 --- /dev/null +++ b/spec/fixtures/unit/memory/solaris-swap_l-single @@ -0,0 +1,2 @@ +swapfile dev swaplo blocks free +/dev/swap 4294967295,4294967295 16 2097136 2097136 diff --git a/spec/fixtures/unit/memory/solaris-vmstat b/spec/fixtures/unit/memory/solaris-vmstat new file mode 100644 index 0000000000..d3c9d11d35 --- /dev/null +++ b/spec/fixtures/unit/memory/solaris-vmstat @@ -0,0 +1,3 @@ + kthr memory page disk faults cpu + r b w swap free re mf pi po fr de sr s0 s3 -- -- in sy cs us sy id + 0 0 0 1154552 476224 8 19 0 0 0 0 0 0 0 0 0 460 294 236 1 2 97 diff --git a/spec/unit/memory_spec.rb b/spec/unit/memory_spec.rb index fa92367f35..bb35111d19 100755 --- a/spec/unit/memory_spec.rb +++ b/spec/unit/memory_spec.rb @@ -49,22 +49,7 @@ Facter.clear Facter.fact(:kernel).stubs(:value).returns("Darwin") Facter::Core::Execution.stubs(:exec).with('sysctl -n hw.memsize').returns('8589934592') - sample_vm_stat = </dev/null').returns(swapusage) - - svmon = </dev/null').returns(my_fixture_read('aix-swap_l')) + Facter::Core::Execution.stubs(:exec).with('/usr/bin/svmon -O unit=KB').returns(my_fixture_read('aix-svmon')) Facter.collection.internal_loader.load(:memory) end @@ -224,14 +181,9 @@ swapusage = "total: 148342k bytes allocated = 0k used, 148342k available" Facter::Core::Execution.stubs(:exec).with('swapctl -s').returns(swapusage) - vmstat = </dev/null').returns sample_prtconf - - vmstat_lines = </dev/null').returns(my_fixture_read('solaris-prtconf')) + Facter::Core::Execution.stubs(:exec).with('vmstat').returns(my_fixture_read('solaris-vmstat')) end after(:each) do @@ -288,11 +229,7 @@ describe "when single swap exists" do before(:each) do - sample_swap_line = </dev/null').returns sample_swap_line + Facter::Core::Execution.stubs(:exec).with('/usr/sbin/swap -l 2>/dev/null').returns my_fixture_read('solaris-swap_l-single') Facter.collection.internal_loader.load(:memory) end @@ -316,12 +253,7 @@ describe "when multiple swaps exist" do before(:each) do - sample_swap_line = </dev/null').returns sample_swap_line + Facter::Core::Execution.stubs(:exec).with('/usr/sbin/swap -l 2>/dev/null').returns my_fixture_read('solaris-swap_l-multiple') Facter.collection.internal_loader.load(:memory) end @@ -378,12 +310,7 @@ Facter::Core::Execution.stubs(:exec).with('/sbin/sysctl -n vm.swap_anon_use').returns("2635") Facter::Core::Execution.stubs(:exec).with('/sbin/sysctl -n vm.swap_cache_use').returns("0") - vmstat = < Date: Mon, 12 May 2014 16:19:00 -0700 Subject: [PATCH 1860/3753] Add Facter::Util::POSIX for a consistent way of dealing with sysctl(7). Conflicts: lib/facter/memory.rb lib/facter/util/blockdevices/freebsd.rb lib/facter/util/memory.rb lib/facter/util/virtual.rb lib/facter/virtual.rb spec/unit/blockdevices_spec.rb spec/unit/kernelrelease_spec.rb spec/unit/memory_spec.rb spec/unit/processor_spec.rb spec/unit/util/virtual_spec.rb spec/unit/virtual_spec.rb Conflicts: spec/unit/kernelrelease_spec.rb --- lib/facter/kernelrelease.rb | 3 ++- lib/facter/memory.rb | 16 ++++++++-------- lib/facter/physicalprocessorcount.rb | 4 +++- lib/facter/processor.rb | 13 ++++++++++--- lib/facter/util/manufacturer.rb | 5 +++-- lib/facter/util/memory.rb | 8 +++++--- lib/facter/util/posix.rb | 16 ++++++++++++++++ lib/facter/util/virtual.rb | 3 ++- lib/facter/virtual.rb | 2 +- spec/unit/kernelrelease_spec.rb | 4 ++-- spec/unit/memory_spec.rb | 20 ++++++++++---------- spec/unit/physicalprocessorcount_spec.rb | 3 ++- spec/unit/processor_spec.rb | 11 ++++++----- spec/unit/util/virtual_spec.rb | 4 ++-- spec/unit/virtual_spec.rb | 10 +++++----- 15 files changed, 77 insertions(+), 45 deletions(-) create mode 100644 lib/facter/util/posix.rb diff --git a/lib/facter/kernelrelease.rb b/lib/facter/kernelrelease.rb index c723ebc0dc..d3eed4ebd3 100644 --- a/lib/facter/kernelrelease.rb +++ b/lib/facter/kernelrelease.rb @@ -10,6 +10,7 @@ # # Caveats: # +require 'facter/util/posix' Facter.add(:kernelrelease) do setcode 'uname -r' @@ -23,7 +24,7 @@ Facter.add("kernelrelease") do confine :kernel => :openbsd setcode do - Facter::Util::Resolution.exec("/sbin/sysctl -n kern.version").split(' ')[1] + Facter::Util::POSIX.sysctl("kern.version").split(' ')[1] end end diff --git a/lib/facter/memory.rb b/lib/facter/memory.rb index 6300a28f5f..9ed92ede53 100644 --- a/lib/facter/memory.rb +++ b/lib/facter/memory.rb @@ -85,7 +85,7 @@ Facter.add("SwapEncrypted") do confine :kernel => :openbsd setcode do - sysctl_encrypted = Facter::Core::Execution.exec("sysctl -n vm.swapencrypt.enable").to_i + sysctl_encrypted = Facter::Util::POSIX.sysctl("vm.swapencrypt.enable").to_i !(sysctl_encrypted.zero?) end end @@ -93,7 +93,7 @@ Facter.add("SwapEncrypted") do confine :kernel => :Darwin setcode do - swap = Facter::Core::Execution.exec('sysctl vm.swapusage') + swap = Facter::Util::POSIX.sysctl('vm.swapusage') encrypted = false if swap =~ /\(encrypted\)/ then encrypted = true; end encrypted @@ -149,8 +149,8 @@ Facter.add("swapsize_mb") do confine :kernel => :dragonfly setcode do - page_size = Facter::Core::Execution.exec("/sbin/sysctl -n hw.pagesize").to_f - swaptotal = Facter::Core::Execution.exec("/sbin/sysctl -n vm.swap_size").to_f * page_size + page_size = Facter::Util::POSIX.sysctl("hw.pagesize").to_f + swaptotal = Facter::Util::POSIX.sysctl("vm.swap_size").to_f * page_size "%.2f" % [(swaptotal.to_f / 1024.0) / 1024.0] end end @@ -158,10 +158,10 @@ Facter.add("swapfree_mb") do confine :kernel => :dragonfly setcode do - page_size = Facter::Core::Execution.exec("/sbin/sysctl -n hw.pagesize").to_f - swaptotal = Facter::Core::Execution.exec("/sbin/sysctl -n vm.swap_size").to_f * page_size - swap_anon_use = Facter::Core::Execution.exec("/sbin/sysctl -n vm.swap_anon_use").to_f * page_size - swap_cache_use = Facter::Core::Execution.exec("/sbin/sysctl -n vm.swap_cache_use").to_f * page_size + page_size = Facter::Util::POSIX.sysctl("hw.pagesize").to_f + swaptotal = Facter::Util::POSIX.sysctl("vm.swap_size").to_f * page_size + swap_anon_use = Facter::Util::POSIX.sysctl("vm.swap_anon_use").to_f * page_size + swap_cache_use = Facter::Util::POSIX.sysctl("vm.swap_cache_use").to_f * page_size swapfree = swaptotal - swap_anon_use - swap_cache_use "%.2f" % [(swapfree.to_f / 1024.0) / 1024.0] end diff --git a/lib/facter/physicalprocessorcount.rb b/lib/facter/physicalprocessorcount.rb index 3263e20d7b..9d6dfbe81c 100644 --- a/lib/facter/physicalprocessorcount.rb +++ b/lib/facter/physicalprocessorcount.rb @@ -9,6 +9,8 @@ # # Caveats: # +require 'facter/util/posix' + Facter.add('physicalprocessorcount') do confine :kernel => :linux @@ -84,6 +86,6 @@ Facter.add('physicalprocessorcount') do confine :kernel => :openbsd setcode do - Facter::Util::Resolution.exec("sysctl -n hw.ncpufound") + Facter::Util::POSIX.sysctl("hw.ncpufound") end end diff --git a/lib/facter/processor.rb b/lib/facter/processor.rb index 3b254c0b46..d317e933f6 100644 --- a/lib/facter/processor.rb +++ b/lib/facter/processor.rb @@ -21,6 +21,7 @@ require 'thread' require 'facter/util/processor' +require 'facter/util/posix' # We have to enumerate these outside a Facter.add block to get the processorN # descriptions iteratively (but we need them inside the Facter.add block above @@ -95,7 +96,9 @@ Facter.add("ProcessorCount") do confine :kernel => :Darwin - setcode "sysctl -n hw.ncpu" + setcode do + Facter::Util::POSIX.sysctl("hw.ncpu") + end end if Facter.value(:kernel) == "windows" @@ -138,12 +141,16 @@ Facter.add("Processor") do confine :kernel => [:dragonfly,:freebsd] - setcode "sysctl -n hw.model" + setcode do + Facter::Util::POSIX.sysctl("hw.model") + end end Facter.add("ProcessorCount") do confine :kernel => [:dragonfly,:freebsd,:openbsd] - setcode "sysctl -n hw.ncpu" + setcode do + Facter::Util::POSIX.sysctl("hw.ncpu") + end end Facter.add("ProcessorCount") do diff --git a/lib/facter/util/manufacturer.rb b/lib/facter/util/manufacturer.rb index 2622d5dc25..d76903647a 100644 --- a/lib/facter/util/manufacturer.rb +++ b/lib/facter/util/manufacturer.rb @@ -1,8 +1,9 @@ # mamufacturer.rb # Support methods for manufacturer specific facts -module Facter::Manufacturer +require 'facter/util/posix' +module Facter::Manufacturer def self.get_dmi_table() case Facter.value(:kernel) when 'Linux', 'GNU/kFreeBSD' @@ -52,7 +53,7 @@ def self.sysctl_find_system_info(name) name.each do |sysctlkey,facterkey| Facter.add(facterkey) do confine :kernel => [:openbsd, :darwin] - setcode "sysctl -n #{sysctlkey} 2>/dev/null" + setcode Facter::Util::POSIX.sysctl(sysctlkey) end end end diff --git a/lib/facter/util/memory.rb b/lib/facter/util/memory.rb index 920391999c..11752820b0 100644 --- a/lib/facter/util/memory.rb +++ b/lib/facter/util/memory.rb @@ -2,6 +2,8 @@ ## Support module for memory related facts ## +require 'facter/util/posix' + module Facter::Memory def self.meminfo_number(tag) memsize = "" @@ -112,9 +114,9 @@ def self.mem_size(kernel = Facter.value(:kernel)) def self.mem_size_info(kernel = Facter.value(:kernel)) case kernel when /Dragonfly/i, /FreeBSD/i, /OpenBSD/i - Facter::Core::Execution.exec("sysctl -n hw.physmem") + Facter::Util::POSIX.sysctl("hw.physmem") when /Darwin/i - Facter::Core::Execution.exec("sysctl -n hw.memsize") + Facter::Util::POSIX.sysctl("hw.memsize") when /AIX/i if Facter::Core::Execution.exec("/usr/bin/svmon -O unit=KB") =~ /^memory\s+(\d+)\s+/ $1 @@ -152,7 +154,7 @@ def self.swap_info(kernel = Facter.value(:kernel)) when /FreeBSD/i Facter::Core::Execution.exec('swapinfo -k') when /Darwin/i - Facter::Core::Execution.exec('sysctl vm.swapusage') + Facter::Util::POSIX.sysctl('vm.swapusage') when /SunOS/i Facter::Core::Execution.exec('/usr/sbin/swap -l 2>/dev/null') end diff --git a/lib/facter/util/posix.rb b/lib/facter/util/posix.rb new file mode 100644 index 0000000000..dfcca557b8 --- /dev/null +++ b/lib/facter/util/posix.rb @@ -0,0 +1,16 @@ +module Facter +module Util +module POSIX + # Provides a consistent way of invoking sysctl(8) across POSIX platforms + # + # @param mib [String] the sysctl(8) MIB name + # + # @api private + def sysctl(mib) + Facter::Util::Resolution.exec("/sbin/sysctl -n #{mib} 2>/dev/null") + end + + module_function :sysctl +end +end +end diff --git a/lib/facter/util/virtual.rb b/lib/facter/util/virtual.rb index fa106743bd..12bc4549c0 100644 --- a/lib/facter/util/virtual.rb +++ b/lib/facter/util/virtual.rb @@ -1,3 +1,4 @@ +require 'facter/util/posix' require 'facter/util/file_read' require 'pathname' @@ -93,7 +94,7 @@ def self.kvm? txt = if FileTest.exists?("/proc/cpuinfo") File.read("/proc/cpuinfo") elsif ["FreeBSD", "OpenBSD"].include? Facter.value(:kernel) - Facter::Core::Execution.exec("/sbin/sysctl -n hw.model") + Facter::Util::POSIX.sysctl("hw.model") end (txt =~ /QEMU Virtual CPU/) ? true : false end diff --git a/lib/facter/virtual.rb b/lib/facter/virtual.rb index 907da828a2..a316c7d523 100644 --- a/lib/facter/virtual.rb +++ b/lib/facter/virtual.rb @@ -98,7 +98,7 @@ confine :kernel => 'OpenBSD' has_weight 10 setcode do - output = Facter::Core::Execution.exec('sysctl -n hw.product 2>/dev/null') + output = Facter::Util::POSIX.sysctl("hw.product") if output lines = output.split("\n") next "parallels" if lines.any? {|l| l =~ /Parallels/ } diff --git a/spec/unit/kernelrelease_spec.rb b/spec/unit/kernelrelease_spec.rb index 39f7cfacd3..c6c954adf8 100644 --- a/spec/unit/kernelrelease_spec.rb +++ b/spec/unit/kernelrelease_spec.rb @@ -57,12 +57,12 @@ end it 'parses 5.3-current sysctl output' do - Facter::Util::Resolution.expects(:exec).with("/sbin/sysctl -n kern.version").returns(my_fixture_read('openbsd-5.3-current')) + Facter::Util::POSIX.stubs(:sysctl).with("kern.version").returns(my_fixture_read('openbsd-5.3-current')) Facter.value(:kernelrelease).should == '5.3-current' end it 'parses 5.3 sysctl output' do - Facter::Util::Resolution.expects(:exec).with("/sbin/sysctl -n kern.version").returns(my_fixture_read('openbsd-5.3')) + Facter::Util::POSIX.stubs(:sysctl).with("kern.version").returns(my_fixture_read('openbsd-5.3')) Facter.value(:kernelrelease).should == '5.3' end end diff --git a/spec/unit/memory_spec.rb b/spec/unit/memory_spec.rb index bb35111d19..be7ec71c08 100755 --- a/spec/unit/memory_spec.rb +++ b/spec/unit/memory_spec.rb @@ -48,9 +48,9 @@ before(:each) do Facter.clear Facter.fact(:kernel).stubs(:value).returns("Darwin") - Facter::Core::Execution.stubs(:exec).with('sysctl -n hw.memsize').returns('8589934592') + Facter::Util::POSIX.stubs(:sysctl).with('hw.memsize').returns('8589934592') Facter::Core::Execution.stubs(:exec).with('vm_stat').returns(my_fixture_read('darwin-vm_stat')) - Facter::Core::Execution.stubs(:exec).with('sysctl vm.swapusage').returns("vm.swapusage: total = 64.00M used = 1.00M free = 63.00M (encrypted)") + Facter::Util::POSIX.stubs(:sysctl).with('vm.swapusage').returns("vm.swapusage: total = 64.00M used = 1.00M free = 63.00M (encrypted)") Facter.collection.internal_loader.load(:memory) end @@ -183,8 +183,8 @@ Facter::Core::Execution.stubs(:exec).with('vmstat').returns(my_fixture_read('openbsd-vmstat')) - Facter::Core::Execution.stubs(:exec).with('sysctl -n hw.physmem').returns('267321344') - Facter::Core::Execution.stubs(:exec).with('sysctl -n vm.swapencrypt.enable').returns('1') + Facter::Util::POSIX.stubs(:sysctl).with('hw.physmem').returns('267321344') + Facter::Util::POSIX.stubs(:sysctl).with('vm.swapencrypt.enable').returns('1') Facter.collection.internal_loader.load(:memory) end @@ -305,14 +305,14 @@ Facter.fact(:kernel).stubs(:value).returns("dragonfly") swapusage = "total: 148342k bytes allocated = 0k used, 148342k available" - Facter::Core::Execution.stubs(:exec).with('/sbin/sysctl -n hw.pagesize').returns("4096") - Facter::Core::Execution.stubs(:exec).with('/sbin/sysctl -n vm.swap_size').returns("128461") - Facter::Core::Execution.stubs(:exec).with('/sbin/sysctl -n vm.swap_anon_use').returns("2635") - Facter::Core::Execution.stubs(:exec).with('/sbin/sysctl -n vm.swap_cache_use').returns("0") + Facter::Util::POSIX.stubs(:sysctl).with('hw.pagesize').returns("4096") + Facter::Util::POSIX.stubs(:sysctl).with('vm.swap_size').returns("128461") + Facter::Util::POSIX.stubs(:sysctl).with('vm.swap_anon_use').returns("2635") + Facter::Util::POSIX.stubs(:sysctl).with('vm.swap_cache_use').returns("0") Facter::Core::Execution.stubs(:exec).with('vmstat').returns my_fixture_read('dragonfly-vmstat') - Facter::Core::Execution.stubs(:exec).with("sysctl -n hw.physmem").returns('248512512') + Facter::Util::POSIX.stubs(:sysctl).with("hw.physmem").returns('248512512') Facter.collection.internal_loader.load(:memory) end @@ -344,7 +344,7 @@ Facter.fact(:kernel).stubs(:value).returns("FreeBSD") Facter::Core::Execution.stubs(:exec).with('vmstat -H').returns my_fixture_read('freebsd-vmstat') - Facter::Core::Execution.stubs(:exec).with('sysctl -n hw.physmem').returns '1056276480' + Facter::Util::POSIX.stubs(:sysctl).with('hw.physmem').returns '1056276480' end after(:each) do diff --git a/spec/unit/physicalprocessorcount_spec.rb b/spec/unit/physicalprocessorcount_spec.rb index af7aa76069..65a9fc4205 100755 --- a/spec/unit/physicalprocessorcount_spec.rb +++ b/spec/unit/physicalprocessorcount_spec.rb @@ -1,6 +1,7 @@ #! /usr/bin/env ruby require 'spec_helper' +require 'facter/util/posix' describe "Physical processor count facts" do @@ -77,7 +78,7 @@ describe "on openbsd" do it "should return 4 physical CPUs" do Facter.fact(:kernel).stubs(:value).returns("OpenBSD") - Facter::Util::Resolution.expects(:exec).with("sysctl -n hw.ncpufound").returns("4") + Facter::Util::POSIX.expects(:sysctl).with("hw.ncpufound").returns("4") Facter.fact(:physicalprocessorcount).value.should == "4" end end diff --git a/spec/unit/processor_spec.rb b/spec/unit/processor_spec.rb index 533d484089..f40a7a9d7e 100755 --- a/spec/unit/processor_spec.rb +++ b/spec/unit/processor_spec.rb @@ -1,5 +1,6 @@ #! /usr/bin/env ruby +require 'facter/util/posix' require 'facter/util/processor' require 'spec_helper' require 'facter_spec/cpuinfo' @@ -185,35 +186,35 @@ def sysfs_cpu_stubs(count) it "should be 2 on dual-processor Darwin box" do Facter.fact(:kernel).stubs(:value).returns("Darwin") - Facter::Core::Execution.stubs(:execute).with("sysctl -n hw.ncpu", anything).returns('2') + Facter::Util::POSIX.stubs(:sysctl).with("hw.ncpu").returns('2') Facter.fact(:processorcount).value.should == "2" end it "should be 2 on dual-processor OpenBSD box" do Facter.fact(:kernel).stubs(:value).returns("OpenBSD") - Facter::Core::Execution.stubs(:execute).with("sysctl -n hw.ncpu", anything).returns('2') + Facter::Util::POSIX.stubs(:sysctl).with("hw.ncpu").returns('2') Facter.fact(:processorcount).value.should == "2" end it "should be 2 on dual-processor FreeBSD box" do Facter.fact(:kernel).stubs(:value).returns("FreeBSD") - Facter::Core::Execution.stubs(:execute).with("sysctl -n hw.ncpu", anything).returns('2') + Facter::Util::POSIX.stubs(:sysctl).with("hw.ncpu").returns('2') Facter.fact(:processorcount).value.should == "2" end it "should print the correct CPU Model on FreeBSD" do Facter.fact(:kernel).stubs(:value).returns("FreeBSD") - Facter::Core::Execution.stubs(:execute).with("sysctl -n hw.model", anything).returns('SomeVendor CPU 3GHz') + Facter::Util::POSIX.stubs(:sysctl).with("hw.model").returns('SomeVendor CPU 3GHz') Facter.fact(:processor).value.should == "SomeVendor CPU 3GHz" end it "should be 2 on dual-processor DragonFly box" do Facter.fact(:kernel).stubs(:value).returns("DragonFly") - Facter::Core::Execution.stubs(:execute).with("sysctl -n hw.ncpu", anything).returns('2') + Facter::Util::POSIX.stubs(:sysctl).with("hw.ncpu").returns('2') Facter.fact(:processorcount).value.should == "2" end diff --git a/spec/unit/util/virtual_spec.rb b/spec/unit/util/virtual_spec.rb index 2fe57f2d9e..c20919e8da 100755 --- a/spec/unit/util/virtual_spec.rb +++ b/spec/unit/util/virtual_spec.rb @@ -202,14 +202,14 @@ it "should detect kvm on FreeBSD" do FileTest.stubs(:exists?).with("/proc/cpuinfo").returns(false) Facter.fact(:kernel).stubs(:value).returns("FreeBSD") - Facter::Core::Execution.stubs(:exec).with("/sbin/sysctl -n hw.model").returns("QEMU Virtual CPU version 0.12.4") + Facter::Util::POSIX.stubs(:sysctl).with("hw.model").returns("QEMU Virtual CPU version 0.12.4") Facter::Util::Virtual.should be_kvm end it "should detect kvm on OpenBSD" do FileTest.stubs(:exists?).with("/proc/cpuinfo").returns(false) Facter.fact(:kernel).stubs(:value).returns("OpenBSD") - Facter::Core::Execution.stubs(:exec).with("/sbin/sysctl -n hw.model").returns('QEMU Virtual CPU version (cpu64-rhel6) ("AuthenticAMD" 686-class, 512KB L2 cache)') + Facter::Util::POSIX.stubs(:sysctl).with("hw.model").returns('QEMU Virtual CPU version (cpu64-rhel6) ("AuthenticAMD" 686-class, 512KB L2 cache)') Facter::Util::Virtual.should be_kvm end diff --git a/spec/unit/virtual_spec.rb b/spec/unit/virtual_spec.rb index cb18c53bbd..ae4e9bfea7 100755 --- a/spec/unit/virtual_spec.rb +++ b/spec/unit/virtual_spec.rb @@ -273,27 +273,27 @@ end it "should be parallels with Parallels product name from sysctl" do - Facter::Core::Execution.stubs(:exec).with('sysctl -n hw.product 2>/dev/null').returns("Parallels Virtual Platform") + Facter::Util::POSIX.stubs(:sysctl).with('hw.product').returns("Parallels Virtual Platform") Facter.fact(:virtual).value.should == "parallels" end it "should be vmware with VMware product name from sysctl" do - Facter::Core::Execution.stubs(:exec).with('sysctl -n hw.product 2>/dev/null').returns("VMware Virtual Platform") + Facter::Util::POSIX.stubs(:sysctl).with('hw.product').returns("VMware Virtual Platform") Facter.fact(:virtual).value.should == "vmware" end it "should be virtualbox with VirtualBox product name from sysctl" do - Facter::Core::Execution.stubs(:exec).with('sysctl -n hw.product 2>/dev/null').returns("VirtualBox") + Facter::Util::POSIX.stubs(:sysctl).with('hw.product').returns("VirtualBox") Facter.fact(:virtual).value.should == "virtualbox" end it "should be xenhvm with Xen HVM product name from sysctl" do - Facter::Core::Execution.stubs(:exec).with('sysctl -n hw.product 2>/dev/null').returns("HVM domU") + Facter::Util::POSIX.stubs(:sysctl).with('hw.product').returns("HVM domU") Facter.fact(:virtual).value.should == "xenhvm" end it "should be ovirt with oVirt Node product name from sysctl" do - Facter::Core::Execution.stubs(:exec).with('sysctl -n hw.product 2>/dev/null').returns("oVirt Node") + Facter::Util::POSIX.stubs(:sysctl).with('hw.product').returns("oVirt Node") Facter.fact(:virtual).value.should == "ovirt" end end From c0c7b504bcf069623464b1961849f1249ac5e869 Mon Sep 17 00:00:00 2001 From: Matt Peterson Date: Fri, 14 Jun 2013 16:25:54 -0700 Subject: [PATCH 1861/3753] (#21260) Add Cumulus Linux OS detection support --- lib/facter/operatingsystem.rb | 5 ++++- lib/facter/operatingsystemmajrelease.rb | 3 ++- lib/facter/operatingsystemrelease.rb | 11 +++++++++++ lib/facter/osfamily.rb | 2 +- spec/unit/operatingsystem_spec.rb | 8 ++++++++ spec/unit/operatingsystemmajrelease_spec.rb | 2 +- spec/unit/operatingsystemrelease_spec.rb | 7 +++++++ 7 files changed, 34 insertions(+), 4 deletions(-) diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index 20f6feb3a5..6f304eccee 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -34,7 +34,10 @@ Facter.add(:operatingsystem) do confine :kernel => :linux setcode do - if Facter.value(:lsbdistid) == "Ubuntu" + if FileTest.exists?("/etc/os-release") + match = File.read('/etc/os-release').match /^NAME=["']?(.+?)["']?$/ + match[1].gsub(/[^a-zA-Z]/, '') + elsif Facter.value(:lsbdistid) == "Ubuntu" "Ubuntu" elsif FileTest.exists?("/etc/debian_version") "Debian" diff --git a/lib/facter/operatingsystemmajrelease.rb b/lib/facter/operatingsystemmajrelease.rb index f058a9f144..03d588b4ca 100644 --- a/lib/facter/operatingsystemmajrelease.rb +++ b/lib/facter/operatingsystemmajrelease.rb @@ -29,7 +29,8 @@ :OVS, :RedHat, :Scientific, - :SLC + :SLC, + :CumulusLinux ] setcode do Facter.value('operatingsystemrelease').split('.').first diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index 8d94ffcd49..3b9c21f763 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -71,6 +71,17 @@ end end +Facter.add(:operatingsystemrelease) do +confine :operatingsystem => 'CumulusLinux' + setcode do + if release = Facter::Util::FileRead.read('/etc/os-release') + if match = release.match(/VERSION_ID=["']?(.+?)["']?$/) + match[1] + end + end + end +end + Facter.add(:operatingsystemrelease) do confine :operatingsystem => %w{SLES SLED OpenSuSE} setcode do diff --git a/lib/facter/osfamily.rb b/lib/facter/osfamily.rb index 6162ef8929..f7008c1d42 100644 --- a/lib/facter/osfamily.rb +++ b/lib/facter/osfamily.rb @@ -18,7 +18,7 @@ case Facter.value(:operatingsystem) when "RedHat", "Fedora", "CentOS", "Scientific", "SLC", "Ascendos", "CloudLinux", "PSBM", "OracleLinux", "OVS", "OEL", "Amazon", "XenServer" "RedHat" - when "Ubuntu", "Debian" + when "Ubuntu", "Debian", "CumulusLinux" "Debian" when "SLES", "SLED", "OpenSuSE", "SuSE" "Suse" diff --git a/spec/unit/operatingsystem_spec.rb b/spec/unit/operatingsystem_spec.rb index 97236c9651..0b649355c6 100755 --- a/spec/unit/operatingsystem_spec.rb +++ b/spec/unit/operatingsystem_spec.rb @@ -138,4 +138,12 @@ Facter.fact(:operatingsystem).value.should == "SLC" end end + describe "on Cumulus Linux" do + it "should identify as 'Cumulus Linux'" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + FileTest.expects(:exists?).with("/etc/os-release").returns true + File.expects(:read).with("/etc/os-release").returns 'NAME="Cumulus Linux"' + Facter.fact(:operatingsystem).value.should == "CumulusLinux" + end + end end diff --git a/spec/unit/operatingsystemmajrelease_spec.rb b/spec/unit/operatingsystemmajrelease_spec.rb index 9973b23ab3..04e89b1891 100644 --- a/spec/unit/operatingsystemmajrelease_spec.rb +++ b/spec/unit/operatingsystemmajrelease_spec.rb @@ -3,7 +3,7 @@ require 'facter' describe "OS Major Release fact" do - ['Amazon','CentOS','CloudLinux','Debian','Fedora','OEL','OracleLinux','OVS','RedHat','Scientific','SLC'].each do |operatingsystem| + ['Amazon','CentOS','CloudLinux','Debian','Fedora','OEL','OracleLinux','OVS','RedHat','Scientific','SLC','CumulusLinux'].each do |operatingsystem| context "on #{operatingsystem} operatingsystems" do it "should be derived from operatingsystemrelease" do Facter.fact(:kernel).stubs(:value).returns("Linux") diff --git a/spec/unit/operatingsystemrelease_spec.rb b/spec/unit/operatingsystemrelease_spec.rb index 93c4c87349..8acfdcf534 100755 --- a/spec/unit/operatingsystemrelease_spec.rb +++ b/spec/unit/operatingsystemrelease_spec.rb @@ -225,4 +225,11 @@ Facter.fact(:operatingsystemrelease).value.should == "10.04" end end + + it "for Cumulus Linux" do + Facter.fact(:kernel).stubs(:value).returns("Linux") + Facter.fact(:operatingsystem).stubs(:value).returns("CumulusLinux") + File.expects(:read).with("/etc/os-release").returns("VERSION_ID=1.5.0") + Facter.fact(:operatingsystemrelease).value.should == "1.5.0" + end end From b5d97851097306760da16373b329f08e05c671a7 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 13 May 2014 11:09:34 -0700 Subject: [PATCH 1862/3753] (FACT-451) Add helper for reading /etc/os-release --- lib/facter/util/operatingsystem.rb | 21 +++++ .../util/operatingsystem/cumuluslinux.txt | 8 ++ .../unit/util/operatingsystem/redhat-7.txt | 12 +++ .../unit/util/operatingsystem/sabayon.txt | 7 ++ .../unit/util/operatingsystem/wheezy.txt | 9 ++ spec/unit/util/operatingsystem_spec.rb | 92 +++++++++++++++++++ 6 files changed, 149 insertions(+) create mode 100644 lib/facter/util/operatingsystem.rb create mode 100644 spec/fixtures/unit/util/operatingsystem/cumuluslinux.txt create mode 100644 spec/fixtures/unit/util/operatingsystem/redhat-7.txt create mode 100644 spec/fixtures/unit/util/operatingsystem/sabayon.txt create mode 100644 spec/fixtures/unit/util/operatingsystem/wheezy.txt create mode 100644 spec/unit/util/operatingsystem_spec.rb diff --git a/lib/facter/util/operatingsystem.rb b/lib/facter/util/operatingsystem.rb new file mode 100644 index 0000000000..13f2ca69d0 --- /dev/null +++ b/lib/facter/util/operatingsystem.rb @@ -0,0 +1,21 @@ +module Facter + module Util + module Operatingsystem + + # @see http://www.freedesktop.org/software/systemd/man/os-release.html + def self.os_release(file = '/etc/os-release') + values = {} + + if File.readable?(file) + File.readlines(file).each do |line| + if (match = line.match(/^(\w+)=["']?(.+?)["']?$/)) + values[match[1]] = match[2] + end + end + end + + values + end + end + end +end diff --git a/spec/fixtures/unit/util/operatingsystem/cumuluslinux.txt b/spec/fixtures/unit/util/operatingsystem/cumuluslinux.txt new file mode 100644 index 0000000000..0e41ebdea0 --- /dev/null +++ b/spec/fixtures/unit/util/operatingsystem/cumuluslinux.txt @@ -0,0 +1,8 @@ +NAME="Cumulus Linux" +VERSION_ID=1.5.2 +VERSION="1.5.2-28283a7-201311181623-final" +PRETTY_NAME="Cumulus Linux" +ID=cumulus-linux +ID_LIKE=debian +CPE_NAME=cpe:/o:cumulusnetworks:cumulus_linux:1.5.2-28283a7-201311181623-final +HOME_URL="http://www.cumulusnetworks.com/" diff --git a/spec/fixtures/unit/util/operatingsystem/redhat-7.txt b/spec/fixtures/unit/util/operatingsystem/redhat-7.txt new file mode 100644 index 0000000000..41e3f3dbe9 --- /dev/null +++ b/spec/fixtures/unit/util/operatingsystem/redhat-7.txt @@ -0,0 +1,12 @@ +NAME="Red Hat Enterprise Linux Everything" +VERSION="7.0 (Maipo)" +ID="rhel" +VERSION_ID="7.0" +PRETTY_NAME="Red Hat Enterprise Linux Everything 7.0 (Maipo)" +ANSI_COLOR="0;31" +CPE_NAME="cpe:/o:redhat:enterprise_linux:7.0:beta:everything" + +REDHAT_BUGZILLA_PRODUCT="Red Hat Enterprise Linux 7" +REDHAT_BUGZILLA_PRODUCT_VERSION=7.0 +REDHAT_SUPPORT_PRODUCT="Red Hat Enterprise Linux" +REDHAT_SUPPORT_PRODUCT_VERSION=7.0 diff --git a/spec/fixtures/unit/util/operatingsystem/sabayon.txt b/spec/fixtures/unit/util/operatingsystem/sabayon.txt new file mode 100644 index 0000000000..ed82641a72 --- /dev/null +++ b/spec/fixtures/unit/util/operatingsystem/sabayon.txt @@ -0,0 +1,7 @@ +NAME=Sabayon +ID=sabayon +PRETTY_NAME="Sabayon/Linux" +ANSI_COLOR="1;32" +HOME_URL="http://www.sabayon.org/" +SUPPORT_URL="http://forum.sabayon.org/" +BUG_REPORT_URL="https://bugs.sabayon.org/" diff --git a/spec/fixtures/unit/util/operatingsystem/wheezy.txt b/spec/fixtures/unit/util/operatingsystem/wheezy.txt new file mode 100644 index 0000000000..c110e3bc18 --- /dev/null +++ b/spec/fixtures/unit/util/operatingsystem/wheezy.txt @@ -0,0 +1,9 @@ +PRETTY_NAME="Debian GNU/Linux 7 (wheezy)" +NAME="Debian GNU/Linux" +VERSION_ID="7" +VERSION="7 (wheezy)" +ID=debian +ANSI_COLOR="1;31" +HOME_URL="http://www.debian.org/" +SUPPORT_URL="http://www.debian.org/support/" +BUG_REPORT_URL="http://bugs.debian.org/" diff --git a/spec/unit/util/operatingsystem_spec.rb b/spec/unit/util/operatingsystem_spec.rb new file mode 100644 index 0000000000..a2b4c51d19 --- /dev/null +++ b/spec/unit/util/operatingsystem_spec.rb @@ -0,0 +1,92 @@ +require 'spec_helper' +require 'facter/util/operatingsystem' + +describe Facter::Util::Operatingsystem do + describe "reading the os-release file" do + + it "correctly parses the file on Cumulus Linux" do + values = described_class.os_release(my_fixture('cumuluslinux.txt')) + + expect(values).to eq({ + 'NAME' => "Cumulus Linux", + 'VERSION_ID' => "1.5.2", + 'VERSION' => "1.5.2-28283a7-201311181623-final", + 'PRETTY_NAME' => "Cumulus Linux", + 'ID' => "cumulus-linux", + 'ID_LIKE' => "debian", + 'CPE_NAME' => "cpe:/o:cumulusnetworks:cumulus_linux:1.5.2-28283a7-201311181623-final", + 'HOME_URL' => "http://www.cumulusnetworks.com/", + }) + end + + it "correctly parses the file on Sabayon" do + values = described_class.os_release(my_fixture('sabayon.txt')) + + expect(values).to eq({ + "NAME" => "Sabayon", + "ID" => "sabayon", + "PRETTY_NAME" => "Sabayon/Linux", + "ANSI_COLOR" => "1;32", + "HOME_URL" => "http://www.sabayon.org/", + "SUPPORT_URL" => "http://forum.sabayon.org/", + "BUG_REPORT_URL" => "https://bugs.sabayon.org/", + }) + end + + it "correctly parses the file on Debian Wheezy" do + values = described_class.os_release(my_fixture('wheezy.txt')) + + expect(values).to eq({ + "PRETTY_NAME" => "Debian GNU/Linux 7 (wheezy)", + "NAME" => "Debian GNU/Linux", + "VERSION_ID" => "7", + "VERSION" => "7 (wheezy)", + "ID" => "debian", + "ANSI_COLOR" => "1;31", + "HOME_URL" => "http://www.debian.org/", + "SUPPORT_URL" => "http://www.debian.org/support/", + "BUG_REPORT_URL" => "http://bugs.debian.org/", + }) + end + + it "correctly parses the file on Debian Wheezy" do + values = described_class.os_release(my_fixture('wheezy.txt')) + + expect(values).to eq({ + "PRETTY_NAME" => "Debian GNU/Linux 7 (wheezy)", + "NAME" => "Debian GNU/Linux", + "VERSION_ID" => "7", + "VERSION" => "7 (wheezy)", + "ID" => "debian", + "ANSI_COLOR" => "1;31", + "HOME_URL" => "http://www.debian.org/", + "SUPPORT_URL" => "http://www.debian.org/support/", + "BUG_REPORT_URL" => "http://bugs.debian.org/", + }) + end + + + it "correctly parses the file on RedHat 7" do + values = described_class.os_release(my_fixture('redhat-7.txt')) + expect(values).to eq({ + "NAME" => "Red Hat Enterprise Linux Everything", + "VERSION" => "7.0 (Maipo)", + "ID" => "rhel", + "VERSION_ID" => "7.0", + "PRETTY_NAME" => "Red Hat Enterprise Linux Everything 7.0 (Maipo)", + "ANSI_COLOR" => "0;31", + "CPE_NAME" => "cpe:/o:redhat:enterprise_linux:7.0:beta:everything", + "REDHAT_BUGZILLA_PRODUCT" => "Red Hat Enterprise Linux 7", + "REDHAT_BUGZILLA_PRODUCT_VERSION" => "7.0", + "REDHAT_SUPPORT_PRODUCT" => "Red Hat Enterprise Linux", + "REDHAT_SUPPORT_PRODUCT_VERSION" => "7.0", + }) + end + + it "does not try to read an unreadable '/etc/os-release' file" do + File.expects(:readable?).with('/some/nonexistent/file').returns false + + expect(described_class.os_release('/some/nonexistent/file')).to be_empty + end + end +end From 62ae435ebc6c7dd500cf28b49c83f15cb913af41 Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 13 May 2014 13:55:08 -0700 Subject: [PATCH 1863/3753] (FACT-451) Only use /etc/os-release on Cumulus Linux Without this commit the Cumulus Linux operatingsystem check always uses the contents of /etc/os-release to set the operatingsystem fact. This is backwards incompatible as this field may not resolve to the same value as the older detection. This commit adds a Cumulus Linux specific resolver that checks /etc/os-release early so it resolves before Debian, but only returns a value if the operatingsystem is Linux. --- lib/facter/operatingsystem.rb | 21 +++++++++++++++++---- spec/unit/operatingsystem_spec.rb | 19 +++++++++++-------- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/lib/facter/operatingsystem.rb b/lib/facter/operatingsystem.rb index 6f304eccee..609ace9002 100644 --- a/lib/facter/operatingsystem.rb +++ b/lib/facter/operatingsystem.rb @@ -11,6 +11,8 @@ # Caveats: # +require 'facter/util/operatingsystem' + Facter.add(:operatingsystem) do confine :kernel => :sunos setcode do @@ -31,13 +33,24 @@ end end +Facter.add(:operatingsystem) do + # Cumulus Linux is a variant of Debian so this resolution needs to come + # before the Debian resolution. + has_weight(10) + confine :kernel => :linux + + setcode do + release_info = Facter::Util::Operatingsystem.os_release + if release_info['NAME'] == "Cumulus Linux" + 'CumulusLinux' + end + end +end + Facter.add(:operatingsystem) do confine :kernel => :linux setcode do - if FileTest.exists?("/etc/os-release") - match = File.read('/etc/os-release').match /^NAME=["']?(.+?)["']?$/ - match[1].gsub(/[^a-zA-Z]/, '') - elsif Facter.value(:lsbdistid) == "Ubuntu" + if Facter.value(:lsbdistid) == "Ubuntu" "Ubuntu" elsif FileTest.exists?("/etc/debian_version") "Debian" diff --git a/spec/unit/operatingsystem_spec.rb b/spec/unit/operatingsystem_spec.rb index 0b649355c6..111b56c330 100755 --- a/spec/unit/operatingsystem_spec.rb +++ b/spec/unit/operatingsystem_spec.rb @@ -137,13 +137,16 @@ File.expects(:read).with("/etc/redhat-release").returns("Scientific Linux CERN SLC 5.7 (Boron)") Facter.fact(:operatingsystem).value.should == "SLC" end - end - describe "on Cumulus Linux" do - it "should identify as 'Cumulus Linux'" do - Facter.fact(:kernel).stubs(:value).returns("Linux") - FileTest.expects(:exists?).with("/etc/os-release").returns true - File.expects(:read).with("/etc/os-release").returns 'NAME="Cumulus Linux"' - Facter.fact(:operatingsystem).value.should == "CumulusLinux" - end + + it "should identify Cumulus Linux" do + Facter::Util::Operatingsystem.expects(:os_release).returns({'NAME' => 'Cumulus Linux'}) + Facter.fact(:operatingsystem).value.should == "CumulusLinux" end + + it "should not use '/etc/os-release' on platforms other than Cumulus Linux" do + Facter::Util::Operatingsystem.expects(:os_release).returns({'NAME' => 'Debian GNU/Linux'}) + FileTest.expects(:exists?).with('/etc/debian_version').returns true + Facter.fact(:operatingsystem).value.should == "Debian" + end + end end From 8e107264dc2b55290bc31f5ac19b4cc349459f9a Mon Sep 17 00:00:00 2001 From: Adrien Thebo Date: Tue, 13 May 2014 15:48:05 -0700 Subject: [PATCH 1864/3753] (FACT_451) Use parser for /etc/os-release for operatingsystemrelease --- lib/facter/operatingsystemrelease.rb | 7 ++----- spec/unit/operatingsystemrelease_spec.rb | 4 ++-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/lib/facter/operatingsystemrelease.rb b/lib/facter/operatingsystemrelease.rb index 3b9c21f763..f980963e56 100644 --- a/lib/facter/operatingsystemrelease.rb +++ b/lib/facter/operatingsystemrelease.rb @@ -17,6 +17,7 @@ # Caveats: # +require 'facter/util/operatingsystem' require 'facter/util/file_read' Facter.add(:operatingsystemrelease) do @@ -74,11 +75,7 @@ Facter.add(:operatingsystemrelease) do confine :operatingsystem => 'CumulusLinux' setcode do - if release = Facter::Util::FileRead.read('/etc/os-release') - if match = release.match(/VERSION_ID=["']?(.+?)["']?$/) - match[1] - end - end + Facter::Util::Operatingsystem.os_release['VERSION_ID'] end end diff --git a/spec/unit/operatingsystemrelease_spec.rb b/spec/unit/operatingsystemrelease_spec.rb index 8acfdcf534..1492c5b17a 100755 --- a/spec/unit/operatingsystemrelease_spec.rb +++ b/spec/unit/operatingsystemrelease_spec.rb @@ -226,10 +226,10 @@ end end - it "for Cumulus Linux" do + it "uses '/etc/os-release for Cumulus Linux" do Facter.fact(:kernel).stubs(:value).returns("Linux") Facter.fact(:operatingsystem).stubs(:value).returns("CumulusLinux") - File.expects(:read).with("/etc/os-release").returns("VERSION_ID=1.5.0") + Facter::Util::Operatingsystem.expects(:os_release).returns({'VERSION_ID' => '1.5.0'}) Facter.fact(:operatingsystemrelease).value.should == "1.5.0" end end From 46d480108d3cf55b531679736541c8323d5fe912 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Tue, 13 May 2014 19:09:00 -0700 Subject: [PATCH 1865/3753] (CFACT-30) Implement SSH facts. Implementing the following facts on Linux and OSX: - sshdsakey - sshrsakey - sshecdsakey - sshed25519key - sshfp_dsa - sshfp_rsa - sshfp_ecdsa - sshfp_ed25519 --- CMakeLists.txt | 6 + README.md | 1 + lib/CMakeLists.txt | 6 +- lib/inc/facter/facts/fact.hpp | 10 ++ lib/inc/facter/facts/posix/ssh_resolver.hpp | 43 +++++++ lib/inc/facter/util/posix/scoped_bio.hpp | 44 +++++++ lib/inc/facter/util/string.hpp | 10 ++ lib/src/facts/linux/platform.cc | 2 + lib/src/facts/osx/platform.cc | 2 + lib/src/facts/posix/ssh_resolver.cc | 120 ++++++++++++++++++++ lib/src/util/string.cc | 13 +++ lib/tests/CMakeLists.txt | 6 +- lib/tests/util/posix/scoped_bio.cc | 12 ++ lib/tests/util/string.cc | 8 ++ vendor/FindOPENSSL.cmake | 55 +++++++++ 15 files changed, 335 insertions(+), 3 deletions(-) create mode 100644 lib/inc/facter/facts/posix/ssh_resolver.hpp create mode 100644 lib/inc/facter/util/posix/scoped_bio.hpp create mode 100644 lib/src/facts/posix/ssh_resolver.cc create mode 100644 lib/tests/util/posix/scoped_bio.cc create mode 100644 vendor/FindOPENSSL.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index d40ca5f973..01445482fa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,6 +29,12 @@ if (NOT RE2_FOUND) message(FATAL_ERROR "RE2 is required. Please install it before building.") endif() +# Find openssl +find_package(OPENSSL) +if (NOT OPENSSL_FOUND) + message(FATAL_ERROR "openssl is required. Please install it before building.") +endif() + # Include vendor libraries include(${VENDOR_DIRECTORY}/rapidjson.cmake) include(${VENDOR_DIRECTORY}/gmock.cmake) diff --git a/README.md b/README.md index c5ef6ef7c9..90647aab57 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ Build Requirements * CMake >= 2.8.12 * Boost C++ Libraries >= 1.48 * Apache log4cxx >= 10.0 +* OpenSSL >= 1.0.1.g * Google's RE2 library Generating Build Files diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 0f4b739e25..51d30c6ad2 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -14,7 +14,7 @@ configure_file ( if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra -Werror -Wno-unused-parameter -Wno-tautological-constant-out-of-range-compare") elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra -Werror -Wno-unused-parameter -Wno-unused-local-typedefs") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra -Werror -Wno-unused-parameter -Wno-unused-local-typedefs -Wno-unknown-pragmas") elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel") elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") endif() @@ -42,6 +42,7 @@ if (UNIX) "${CMAKE_CURRENT_LIST_DIR}/src/facts/posix/networking_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/posix/operating_system_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/posix/processor_resolver.cc" + "${CMAKE_CURRENT_LIST_DIR}/src/facts/posix/ssh_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/posix/uptime_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/logging/posix/logging.cc" ) @@ -84,6 +85,7 @@ include_directories( ${RAPIDJSON_INCLUDE_DIRS} ${LOG4CXX_INCLUDE_DIRS} ${BOOST_INCLUDE_DIRS} + ${OPENSSL_INCLUDE_DIRS} ) # Link in additional libraries @@ -91,6 +93,8 @@ target_link_libraries(libfacter pthread ${RE2_LIBRARIES} ${LOG4CXX_LIBRARIES} + ${Boost_LIBRARIES} + ${OPENSSL_LIBRARIES} ) # Add a dependency on rapidjson diff --git a/lib/inc/facter/facts/fact.hpp b/lib/inc/facter/facts/fact.hpp index e56af1fdd0..82740db23a 100644 --- a/lib/inc/facter/facts/fact.hpp +++ b/lib/inc/facter/facts/fact.hpp @@ -79,6 +79,16 @@ namespace facter { namespace facts { constexpr static char const* selinux_current_mode = "selinux_current_mode"; constexpr static char const* selinux_config_mode = "selinux_config_mode"; constexpr static char const* selinux_config_policy = "selinux_config_policy"; + + // ssh facts + constexpr static char const* ssh_dsa_key = "sshdsakey"; + constexpr static char const* ssh_rsa_key = "sshrsakey"; + constexpr static char const* ssh_ecdsa_key = "sshecdsakey"; + constexpr static char const* ssh_ed25519_key = "sshed25519key"; + constexpr static char const* sshfp_dsa = "sshfp_dsa"; + constexpr static char const* sshfp_rsa = "sshfp_rsa"; + constexpr static char const* sshfp_ecdsa = "sshfp_ecdsa"; + constexpr static char const* sshfp_ed25519 = "sshfp_ed25519"; }; }} // namespace facter::facts diff --git a/lib/inc/facter/facts/posix/ssh_resolver.hpp b/lib/inc/facter/facts/posix/ssh_resolver.hpp new file mode 100644 index 0000000000..fb6be524e0 --- /dev/null +++ b/lib/inc/facter/facts/posix/ssh_resolver.hpp @@ -0,0 +1,43 @@ +#ifndef FACTER_FACTS_POSIX_SSH_RESOLVER_HPP_ +#define FACTER_FACTS_POSIX_SSH_RESOLVER_HPP_ + +#include "../fact_resolver.hpp" +#include "../fact.hpp" + +namespace facter { namespace facts { namespace posix { + + /** + * Responsible for resolving ssh facts. + */ + struct ssh_resolver : fact_resolver + { + /** + * Constructs the ssh_resolver. + */ + ssh_resolver() : + fact_resolver( + "ssh", + { + fact::ssh_dsa_key, + fact::ssh_rsa_key, + fact::ssh_ecdsa_key, + fact::ssh_ed25519_key, + fact::sshfp_dsa, + fact::sshfp_rsa, + fact::sshfp_ecdsa, + fact::sshfp_ed25519, + }) + { + } + + protected: + /** + * Called to resolve all facts the resolver is responsible for. + * @param facts The fact map that is resolving facts. + */ + virtual void resolve_facts(fact_map& facts); + }; + +}}} // namespace facter::facts::posix + +#endif // FACTER_FACTS_POSIX_SSH_RESOLVER_HPP_ diff --git a/lib/inc/facter/util/posix/scoped_bio.hpp b/lib/inc/facter/util/posix/scoped_bio.hpp new file mode 100644 index 0000000000..d69e18ebed --- /dev/null +++ b/lib/inc/facter/util/posix/scoped_bio.hpp @@ -0,0 +1,44 @@ +#ifndef FACTER_UTIL_POSIX_SCOPED_BIO_HPP_ +#define FACTER_UTIL_POSIX_SCOPED_BIO_HPP_ + +#include "../scoped_resource.hpp" +#include + +namespace facter { namespace util { namespace posix { + + /** + * Represents a scoped OpenSSL BIO object. + * Automatically frees the BIO when it goes out of scope. + */ + struct scoped_bio : scoped_resource + { + /** + * Constructs a scoped_bio. + * @param method The BIO_METHOD to use. + */ + explicit scoped_bio(BIO_METHOD* method) + { + _resource = BIO_new(method); + } + + /** + * Constructs a scoped_bio. + * @param bio The BIO to free when destroyed. + */ + explicit scoped_bio(BIO* bio) : + scoped_resource(std::move(bio), free) + { + } + + private: + static void free(BIO* bio) + { + if (bio) { + BIO_free(bio); + } + } + }; + +}}} // namespace facter::util::posix + +#endif // FACTER_UTIL_POSIX_SCOPED_BIO_HPP_ diff --git a/lib/inc/facter/util/string.hpp b/lib/inc/facter/util/string.hpp index f4875b0d8e..5872201316 100644 --- a/lib/inc/facter/util/string.hpp +++ b/lib/inc/facter/util/string.hpp @@ -6,6 +6,7 @@ #include #include #include +#include namespace facter { namespace util { @@ -122,6 +123,15 @@ namespace facter { namespace util { */ std::string& to_upper(std::string&& str); + /** + * Converts the given bytes to a hexadecimal string. + * @param bytes The pointer to the bytes to convert. + * @param length The number of bytes to convert. + * @param uppercase True if the hexadecimal string should be uppercase or false if it should be lowercase. + * @return Returns the hexadecimal string. + */ + std::string to_hex(uint8_t const* bytes, size_t length, bool uppercase = false); + /** * Character trait type for case-insensitive comparisons. * Based on Herb Sutter's post: http://www.gotw.ca/gotw/029.htm diff --git a/lib/src/facts/linux/platform.cc b/lib/src/facts/linux/platform.cc index 5644990dd5..90f0f99368 100644 --- a/lib/src/facts/linux/platform.cc +++ b/lib/src/facts/linux/platform.cc @@ -8,6 +8,7 @@ #include #include #include +#include using namespace std; @@ -24,6 +25,7 @@ namespace facter { namespace facts { facts.add(make_shared()); facts.add(make_shared()); facts.add(make_shared()); + facts.add(make_shared()); } }} // namespace facter::facts diff --git a/lib/src/facts/osx/platform.cc b/lib/src/facts/osx/platform.cc index 5bc3c0c0d0..4af879596f 100644 --- a/lib/src/facts/osx/platform.cc +++ b/lib/src/facts/osx/platform.cc @@ -5,6 +5,7 @@ #include #include #include +#include using namespace std; @@ -18,6 +19,7 @@ namespace facter { namespace facts { facts.add(make_shared()); facts.add(make_shared()); facts.add(make_shared()); + facts.add(make_shared()); } }} // namespace facter::facts diff --git a/lib/src/facts/posix/ssh_resolver.cc b/lib/src/facts/posix/ssh_resolver.cc new file mode 100644 index 0000000000..6e99ddffd6 --- /dev/null +++ b/lib/src/facts/posix/ssh_resolver.cc @@ -0,0 +1,120 @@ +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace facter::util; +using namespace facter::util::posix; +using namespace boost::system; +using namespace boost::filesystem; +using boost::format; +namespace bs = boost::system; + +LOG_DECLARE_NAMESPACE("facts.posix.ssh"); + +namespace facter { namespace facts { namespace posix { + + void ssh_resolver::resolve_facts(fact_map& facts) + { + static vector> const ssh_facts = { + make_tuple(string(fact::ssh_rsa_key), string(fact::sshfp_rsa), "ssh_host_rsa_key.pub", 1), + make_tuple(string(fact::ssh_dsa_key), string(fact::sshfp_dsa), "ssh_host_dsa_key.pub", 2), + make_tuple(string(fact::ssh_ecdsa_key), string(fact::sshfp_ecdsa), "ssh_host_ecdsa_key.pub", 3), + make_tuple(string(fact::ssh_ed25519_key), string(fact::sshfp_ed25519), "ssh_host_ed25519_key.pub", 4), + }; + + static vector const search_directories = { + "/etc/ssh", + "/usr/local/etc/ssh", + "/etc", + "/usr/local/etc", + "/etc/opt/ssh" + }; + + // Go through each key fact above + for (auto const& ssh_fact : ssh_facts) { + auto const& ssh_fact_name = get<0>(ssh_fact); + auto const& sshfp_fact_name = get<1>(ssh_fact); + auto const& key_filename = get<2>(ssh_fact); + auto finger_print_type = get<3>(ssh_fact); + + // Search the directories for the fact's key file + path key_file; + for (auto const& directory : search_directories) { + key_file = directory; + key_file /= key_filename; + + bs::error_code ec; + if (!is_regular_file(key_file, ec)) { + key_file.clear(); + continue; + } + break; + } + + // Log if we didn't find the file + if (key_file.empty()) { + LOG_DEBUG("%1% could not be located: fact %2% is unavailable.", key_filename, ssh_fact_name); + continue; + } + + // Read the file's contents + string key = file::read(key_file.string()); + if (key.empty()) { + LOG_DEBUG("%1% could not be read: fact %2% is unavailable.", key_file, ssh_fact_name); + continue; + } + + // The SSH file format should be + auto parts = split(key, ' '); + if (parts.size() < 2) { + LOG_DEBUG("unexpected contents for %1%: fact %2% is unavailable.", key_file, ssh_fact_name); + continue; + } + + // Add the key fact + key = move(parts[1]); + facts.add(string(ssh_fact_name), make_value(key)); + + // Decode the key which is expected to be base64 encoded + vector key_bytes(key.size()); + scoped_bio b64((BIO_f_base64())); + BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); + + // Despite the const_cast here, we're only reading from the string; BIO_new_mem_buf is not const-correct + scoped_bio mem(BIO_new_mem_buf(const_cast(key.c_str()), key.size())); + BIO* stream = BIO_push(b64, mem); + int length = BIO_read(stream, key_bytes.data(), key.size()); + if (length < 1) { + LOG_DEBUG("failed to decode SSH key: fact %1% is unavailable.", sshfp_fact_name); + continue; + } + + // Do a SHA1 and a SHA-256 hash for the fingerprints + uint8_t hash[SHA_DIGEST_LENGTH]; + SHA1(key_bytes.data(), length, hash); + uint8_t hash256[SHA256_DIGEST_LENGTH]; + SHA256(key_bytes.data(), length, hash256); + + // Add the key fingerprint fact + facts.add(string(sshfp_fact_name), + make_value( + (format("SSHFP %1% 1 %2%\nSSHFP %1% 2 %3%") % + finger_print_type % + to_hex(hash, sizeof(hash)) % + to_hex(hash256, sizeof(hash256))).str())); + } + } + +}}} // namespace facter::facts::posix diff --git a/lib/src/util/string.cc b/lib/src/util/string.cc index a94aef97ed..9f30c7943e 100644 --- a/lib/src/util/string.cc +++ b/lib/src/util/string.cc @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -121,6 +122,18 @@ namespace facter { namespace util { return to_upper(str); } + string to_hex(uint8_t const* bytes, size_t length, bool uppercase) + { + ostringstream ss; + if (bytes) { + ss << hex << (uppercase ? std::uppercase : std::nouppercase) << setfill('0'); + for (size_t i = 0; i < length; ++i) { + ss << setw(2) << static_cast(bytes[i]); + } + } + return ss.str(); + } + int ci_char_traits::compare(char const* s1, char const* s2, size_t n) { return strncasecmp(s1, s2, n); diff --git a/lib/tests/CMakeLists.txt b/lib/tests/CMakeLists.txt index 6dfffbdbd7..bf8a40a60c 100644 --- a/lib/tests/CMakeLists.txt +++ b/lib/tests/CMakeLists.txt @@ -4,7 +4,7 @@ cmake_minimum_required(VERSION 2.8.12) if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra -Werror -Wno-unused-parameter -Wno-tautological-constant-out-of-range-compare") elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra -Werror -Wno-unused-parameter -Wno-unused-local-typedefs") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra -Werror -Wno-unused-parameter -Wno-unused-local-typedefs -Wno-unknown-pragmas") elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel") elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") endif() @@ -28,6 +28,7 @@ if (UNIX) set(LIBFACTER_TESTS_POSIX_SOURCES "${CMAKE_CURRENT_LIST_DIR}/execution/posix/execution.cc" "${CMAKE_CURRENT_LIST_DIR}/util/posix/scoped_addrinfo.cc" + "${CMAKE_CURRENT_LIST_DIR}/util/posix/scoped_bio.cc" "${CMAKE_CURRENT_LIST_DIR}/util/posix/scoped_descriptor.cc" "${CMAKE_CURRENT_LIST_DIR}/logging/posix/logging.cc" ) @@ -49,10 +50,11 @@ include_directories( ${LOG4CXX_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS} ${GMOCK_INCLUDE_DIRS} + ${OPENSSL_INCLUDE_DIRS} ) add_executable(libfacter_test ${LIBFACTER_TESTS_COMMON_SOURCES} ${LIBFACTER_TESTS_PLATFORM_SOURCES} ${LIBFACTER_TESTS_POSIX_SOURCES}) -target_link_libraries(libfacter_test libfacter ${LOG4CXX_LIBRARIES} ${Boost_LIBRARIES} ${GMOCK_LIBRARIES}) +target_link_libraries(libfacter_test libfacter ${LOG4CXX_LIBRARIES} ${Boost_LIBRARIES} ${GMOCK_LIBRARIES} ${OPENSSL_LIBRARIES}) # Generate a file containing the above version numbers configure_file ( diff --git a/lib/tests/util/posix/scoped_bio.cc b/lib/tests/util/posix/scoped_bio.cc new file mode 100644 index 0000000000..98523d64fe --- /dev/null +++ b/lib/tests/util/posix/scoped_bio.cc @@ -0,0 +1,12 @@ +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#include +#include +#include + +using namespace std; +using namespace facter::util::posix; + +TEST(facter_util_posix_scoped_bio, construction) { + scoped_bio b64((BIO_f_base64())); + ASSERT_NE(nullptr, static_cast(b64)); +} diff --git a/lib/tests/util/string.cc b/lib/tests/util/string.cc index 652481cf39..198c25131c 100644 --- a/lib/tests/util/string.cc +++ b/lib/tests/util/string.cc @@ -88,6 +88,14 @@ TEST(facter_util_string, to_upper) { ASSERT_EQ("", to_upper("")); } +TEST(facter_util_string, to_hex) { + uint8_t buffer[] = { 0xBA, 0xAD, 0xF0, 0x0D }; + ASSERT_EQ("BAADF00D", to_hex(buffer, sizeof(buffer), true)); + ASSERT_EQ("baadf00d", to_hex(buffer, sizeof(buffer))); + ASSERT_EQ("", to_hex(nullptr, 0)); + ASSERT_EQ("", to_hex(buffer, 0)); +} + TEST(facter_util_string, ci_string) { ASSERT_EQ("hello world!", ci_string("HELLO WORLD!")); ASSERT_EQ("HELLO world!", ci_string("hello WORLD!")); diff --git a/vendor/FindOPENSSL.cmake b/vendor/FindOPENSSL.cmake new file mode 100644 index 0000000000..a8e08e4f40 --- /dev/null +++ b/vendor/FindOPENSSL.cmake @@ -0,0 +1,55 @@ +################################################################################ +# +# CMake script for finding openssl. +# The default CMake search process is used to locate files. +# +# This script creates the following variables: +# OPENSSL_FOUND: Boolean that indicates if the package was found +# OPENSSL_INCLUDE_DIRS: Paths to the necessary header files +# OPENSSL_LIBRARIES: Package libraries +# OPENSSL_LIBRARY_DIRS: Path to package libraries +# +################################################################################ + +include(FindPackageHandleStandardArgs) + +# See if OPENSSL_ROOT is not already set in CMake +if (NOT OPENSSL_ROOT) + # See if OPENSSL_ROOT is set in process environment + if (NOT $ENV{OPENSSL_ROOT} STREQUAL "") + set(OPENSSL_ROOT "$ENV{OPENSSL_ROOT}") + message(STATUS "Detected OPENSSL_ROOT set to '${OPENSSL_ROOT}'") + endif() +endif() + +# If OPENSSL_ROOT is available, set up our hints +if (OPENSSL_ROOT) + set(OPENSSL_INCLUDE_HINTS HINTS "${OPENSSL_ROOT}/include" "${OPENSSL_ROOT}") + set(OPENSSL_LIBRARY_HINTS HINTS "${OPENSSL_ROOT}/lib") +endif() + +# Find headers and libraries +find_path(OPENSSL_INCLUDE_DIR NAMES openssl/ssl.h ${OPENSSL_INCLUDE_HINTS}) +find_library(OPENSSL_LIBRARY NAMES crypto ${OPENSSL_LIBRARY_HINTS}) + +# Set OPENSSL_FOUND honoring the QUIET and REQUIRED arguments +find_package_handle_standard_args(OPENSSL DEFAULT_MSG OPENSSL_LIBRARY OPENSSL_INCLUDE_DIR) + +# Output variables +if(OPENSSL_FOUND) + # Include dirs + set(OPENSSL_INCLUDE_DIRS ${OPENSSL_INCLUDE_DIR}) + + # Libraries + if(OPENSSL_LIBRARY) + set(OPENSSL_LIBRARIES ${OPENSSL_LIBRARY}) + else() + set(OPENSSL_LIBRARIES "") + endif() + + # Link dirs + get_filename_component(OPENSSL_LIBRARY_DIRS ${OPENSSL_LIBRARY} PATH) +endif() + +# Advanced options for not cluttering the cmake UIs +mark_as_advanced(OPENSSL_INCLUDE_DIR OPENSSL_LIBRARY) \ No newline at end of file From 4aae9d07865488588b9f63386896ea692f63760c Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Wed, 14 May 2014 11:24:54 -0700 Subject: [PATCH 1866/3753] (maint) Fix Cumulus Linux support to match ruby facter. Fixing implementation of Cumulus Linux support to match what we do in ruby facter. --- lib/src/facts/linux/operating_system_resolver.cc | 5 ++--- lib/src/facts/posix/operating_system_resolver.cc | 1 + 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/src/facts/linux/operating_system_resolver.cc b/lib/src/facts/linux/operating_system_resolver.cc index 14e5c08148..a3998f454d 100644 --- a/lib/src/facts/linux/operating_system_resolver.cc +++ b/lib/src/facts/linux/operating_system_resolver.cc @@ -229,9 +229,8 @@ namespace facter { namespace facts { namespace linux { string contents = trim(file::read(release_file::os)); string release; if (RE2::PartialMatch(contents, "(?m)^NAME=[\"']?(.+?)[\"']?$", &release)) { - RE2::GlobalReplace(&release, "[^a-zA-Z]", ""); - if (release == os::cumulus) { - return release; + if (release == "Cumulus Linux") { + return os::cumulus; } } } diff --git a/lib/src/facts/posix/operating_system_resolver.cc b/lib/src/facts/posix/operating_system_resolver.cc index 520c50e5d5..0fb480032d 100644 --- a/lib/src/facts/posix/operating_system_resolver.cc +++ b/lib/src/facts/posix/operating_system_resolver.cc @@ -52,6 +52,7 @@ namespace facter { namespace facts { namespace posix { { string(os::linux_mint), string(os_family::debian) }, { string(os::ubuntu), string(os_family::debian) }, { string(os::debian), string(os_family::debian) }, + { string(os::cumulus), string(os_family::debian) }, { string(os::suse_enterprise_server), string(os_family::suse) }, { string(os::suse_enterprise_desktop), string(os_family::suse) }, { string(os::open_suse), string(os_family::suse) }, From 6cc3272d2b832bde93cb2a9eaed5f05ec54dcab3 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Fri, 16 May 2014 17:43:50 -0700 Subject: [PATCH 1867/3753] (maint) Fix cmake find scripts to be consistent with other conventions. Fixing the use of uppercase statements and spacing in find scripts to be consistent with other files. --- vendor/FindLOG4CXX.cmake | 22 +++++++++++----------- vendor/FindRE2.cmake | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/vendor/FindLOG4CXX.cmake b/vendor/FindLOG4CXX.cmake index a42e8d47ca..8f4f1bd081 100644 --- a/vendor/FindLOG4CXX.cmake +++ b/vendor/FindLOG4CXX.cmake @@ -14,19 +14,19 @@ include(FindPackageHandleStandardArgs) # See if LOG4CXX_ROOT is not already set in CMake -IF (NOT LOG4CXX_ROOT) +if (NOT LOG4CXX_ROOT) # See if LOG4CXX_ROOT is set in process environment - IF ( NOT $ENV{LOG4CXX_ROOT} STREQUAL "" ) - SET (LOG4CXX_ROOT "$ENV{LOG4CXX_ROOT}") - MESSAGE(STATUS "Detected LOG4CXX_ROOT set to '${LOG4CXX_ROOT}'") - ENDIF () -ENDIF () + if (NOT $ENV{LOG4CXX_ROOT} STREQUAL "" ) + set(LOG4CXX_ROOT "$ENV{LOG4CXX_ROOT}") + message(STATUS "Detected LOG4CXX_ROOT set to '${LOG4CXX_ROOT}'") + endif() +endif() # If LOG4CXX_ROOT is available, set up our hints -IF (LOG4CXX_ROOT) - SET (LOG4CXX_INCLUDE_HINTS HINTS "${LOG4CXX_ROOT}/include" "${LOG4CXX_ROOT}") - SET (LOG4CXX_LIBRARY_HINTS HINTS "${LOG4CXX_ROOT}/lib") -ENDIF () +if (LOG4CXX_ROOT) + set(LOG4CXX_INCLUDE_HINTS HINTS "${LOG4CXX_ROOT}/include" "${LOG4CXX_ROOT}") + set(LOG4CXX_LIBRARY_HINTS HINTS "${LOG4CXX_ROOT}/lib") +endif() # Find headers and libraries find_path(LOG4CXX_INCLUDE_DIR NAMES log4cxx/log4cxx.h ${LOG4CXX_INCLUDE_HINTS}) @@ -37,7 +37,7 @@ find_library(LOG4CXXD_LIBRARY NAMES log4cxx${CMAKE_DEBUG_POSTFIX} ${LOG4CXX_LIBR find_package_handle_standard_args(LOG4CXX DEFAULT_MSG LOG4CXX_LIBRARY LOG4CXX_INCLUDE_DIR) # Output variables -if(LOG4CXX_FOUND) +if (LOG4CXX_FOUND) # Include dirs set(LOG4CXX_INCLUDE_DIRS ${LOG4CXX_INCLUDE_DIR}) diff --git a/vendor/FindRE2.cmake b/vendor/FindRE2.cmake index e6752d0d52..5f84334411 100644 --- a/vendor/FindRE2.cmake +++ b/vendor/FindRE2.cmake @@ -36,7 +36,7 @@ find_library(RE2_LIBRARY NAMES re2 ${RE2_LIBRARY_HINTS}) find_package_handle_standard_args(RE2 DEFAULT_MSG RE2_LIBRARY RE2_INCLUDE_DIR) # Output variables -if(RE2_FOUND) +if (RE2_FOUND) # Include dirs set(RE2_INCLUDE_DIRS ${RE2_INCLUDE_DIR}) From 3339d5dd76f8b4d5d59b6c2870999473bc4049e4 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Fri, 16 May 2014 17:44:57 -0700 Subject: [PATCH 1868/3753] (maint) Replace scalar value types with scalar_value. Replacing string_value and integer_value with tempalted scalar_value class. Adding boolean_value which is a typedef for scalar_value. --- lib/CMakeLists.txt | 3 +- lib/inc/facter/facts/fact_map.hpp | 11 ++- lib/inc/facter/facts/integer_value.hpp | 65 ------------- .../facts/linux/operating_system_resolver.hpp | 2 +- lib/inc/facter/facts/scalar_value.hpp | 94 +++++++++++++++++++ lib/inc/facter/facts/string_value.hpp | 67 ------------- lib/src/facts/bsd/networking_resolver.cc | 2 +- lib/src/facts/bsd/uptime_resolver.cc | 2 +- lib/src/facts/fact.cc | 2 +- lib/src/facts/fact_map.cc | 8 +- lib/src/facts/integer_value.cc | 34 ------- lib/src/facts/linux/block_device_resolver.cc | 5 +- lib/src/facts/linux/dmi_resolver.cc | 2 +- lib/src/facts/linux/lsb_resolver.cc | 2 +- lib/src/facts/linux/networking_resolver.cc | 2 +- .../facts/linux/operating_system_resolver.cc | 2 +- lib/src/facts/linux/processor_resolver.cc | 2 +- lib/src/facts/linux/selinux_resolver.cc | 2 +- lib/src/facts/linux/uptime_resolver.cc | 2 +- lib/src/facts/osx/dmi_resolver.cc | 2 +- lib/src/facts/osx/networking_resolver.cc | 2 +- lib/src/facts/osx/processor_resolver.cc | 2 +- lib/src/facts/posix/kernel_resolver.cc | 2 +- lib/src/facts/posix/networking_resolver.cc | 2 +- .../facts/posix/operating_system_resolver.cc | 2 +- lib/src/facts/posix/processor_resolver.cc | 2 +- lib/src/facts/posix/ssh_resolver.cc | 2 +- lib/src/facts/posix/uptime_resolver.cc | 17 ++-- lib/src/facts/scalar_value.cc | 31 ++++++ lib/src/facts/string_value.cc | 20 ---- lib/tests/CMakeLists.txt | 1 + lib/tests/facts/array_value.cc | 3 +- lib/tests/facts/boolean_value.cc | 40 ++++++++ lib/tests/facts/integer_value.cc | 17 +--- lib/tests/facts/map_value.cc | 3 +- lib/tests/facts/string_value.cc | 2 +- 36 files changed, 213 insertions(+), 246 deletions(-) delete mode 100644 lib/inc/facter/facts/integer_value.hpp create mode 100644 lib/inc/facter/facts/scalar_value.hpp delete mode 100644 lib/inc/facter/facts/string_value.hpp delete mode 100644 lib/src/facts/integer_value.cc create mode 100644 lib/src/facts/scalar_value.cc delete mode 100644 lib/src/facts/string_value.cc create mode 100644 lib/tests/facts/boolean_value.cc diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 51d30c6ad2..55f5d3d25b 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -26,9 +26,8 @@ set(LIBFACTER_COMMON_SOURCES "${CMAKE_CURRENT_LIST_DIR}/src/facts/fact.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/fact_map.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/fact_resolver.cc" - "${CMAKE_CURRENT_LIST_DIR}/src/facts/integer_value.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/map_value.cc" - "${CMAKE_CURRENT_LIST_DIR}/src/facts/string_value.cc" + "${CMAKE_CURRENT_LIST_DIR}/src/facts/scalar_value.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/value.cc" "${CMAKE_CURRENT_LIST_DIR}/src/util/file.cc" "${CMAKE_CURRENT_LIST_DIR}/src/util/string.cc" diff --git a/lib/inc/facter/facts/fact_map.hpp b/lib/inc/facter/facts/fact_map.hpp index 7cef5faeb0..cf75992f7f 100644 --- a/lib/inc/facter/facts/fact_map.hpp +++ b/lib/inc/facter/facts/fact_map.hpp @@ -9,11 +9,13 @@ #include #include #include -#include "value.hpp" -#include "fact_resolver.hpp" namespace facter { namespace facts { + // Forward declare the value and resolver types + struct value; + struct fact_resolver; + /** * Thrown when a fact already exists in the map. */ @@ -48,6 +50,11 @@ namespace facter { namespace facts { */ fact_map(); + /** + * Destructor for fact_map. + */ + ~fact_map(); + /** * Adds a resolver to the fact map. * @param resolver The resolver to add to the map. diff --git a/lib/inc/facter/facts/integer_value.hpp b/lib/inc/facter/facts/integer_value.hpp deleted file mode 100644 index aaf838285f..0000000000 --- a/lib/inc/facter/facts/integer_value.hpp +++ /dev/null @@ -1,65 +0,0 @@ -#ifndef FACTER_FACTS_INTEGER_VALUE_HPP_ -#define FACTER_FACTS_INTEGER_VALUE_HPP_ - -#include "value.hpp" -#include - -namespace facter { namespace facts { - - /** - * Represents a simple integer value. - */ - struct integer_value : value - { - /** - * Constructs an integer value. - * @param value The integer value. - */ - explicit integer_value(int64_t value) : - _value(value) - { - } - - /** - * Constructs a integer_value. - * @param value The integer value as a string. - */ - explicit integer_value(std::string const& value); - - // Force non-copyable - integer_value(integer_value const&) = delete; - integer_value& operator=(integer_value const&) = delete; - - // Allow movable - integer_value(integer_value&&) = default; - integer_value& operator=(integer_value&&) = default; - - /** - * Converts the value to a JSON value. - * @param allocator The allocator to use for creating the JSON value. - * @param value The returned JSON value. - */ - virtual void to_json(rapidjson::Allocator& allocator, rapidjson::Value& value) const; - - /** - * Gets the integer value. - * @return Returns the integer value. - */ - int64_t const& value() const { return _value; } - - protected: - /** - * Writes the value to the given stream. - * @param os The stream to write to. - * @returns Returns the stream being written to. - */ - virtual std::ostream& write(std::ostream& os) const; - - private: - int64_t _value; - }; - -}} // namespace facter::facts - -#endif // FACTER_FACTS_INTEGER_VALUE_HPP_ - diff --git a/lib/inc/facter/facts/linux/operating_system_resolver.hpp b/lib/inc/facter/facts/linux/operating_system_resolver.hpp index 33606b397c..f5ad58e3bb 100644 --- a/lib/inc/facter/facts/linux/operating_system_resolver.hpp +++ b/lib/inc/facter/facts/linux/operating_system_resolver.hpp @@ -2,7 +2,7 @@ #define FACTER_FACTS_LINUX_OPERATING_SYSTEM_RESOLVER_HPP_ #include "../posix/operating_system_resolver.hpp" -#include "../string_value.hpp" +#include "../scalar_value.hpp" namespace facter { namespace facts { namespace linux { diff --git a/lib/inc/facter/facts/scalar_value.hpp b/lib/inc/facter/facts/scalar_value.hpp new file mode 100644 index 0000000000..ae44a5d7e0 --- /dev/null +++ b/lib/inc/facter/facts/scalar_value.hpp @@ -0,0 +1,94 @@ +#ifndef FACTER_FACTS_SCALAR_VALUE_HPP_ +#define FACTER_FACTS_SCALAR_VALUE_HPP_ + +#include "value.hpp" +#include +#include +#include + +namespace facter { namespace facts { + + /** + * Represents a simple scalar value. + * @tparam T The underlying scalar type. + */ + template + struct scalar_value : value + { + /** + * Constructs a scalar_value. + * @param value The scalar value to move into this object. + */ + explicit scalar_value(T&& value) : + _value(std::move(value)) + { + } + + /** + * Constructs a scalar_value. + * @param value The scalar value to copy into this object. + */ + explicit scalar_value(T const& value) : + _value(value) + { + } + + // Force non-copyable + scalar_value(scalar_value const&) = delete; + scalar_value& operator=(scalar_value const&) = delete; + + // Allow movable + scalar_value(scalar_value&&) = default; + scalar_value& operator=(scalar_value&&) = default; + + /** + * Converts the value to a JSON value. + * @param allocator The allocator to use for creating the JSON value. + * @param value The returned JSON value. + */ + virtual void to_json(rapidjson::Allocator& allocator, rapidjson::Value& value) const; + + /** + * Gets the underlying scalar value. + * @return Returns the underlying scalar value. + */ + T const& value() const { return _value; } + + protected: + /** + * Writes the value to the given stream. + * @param os The stream to write to. + * @returns Returns the stream being written to. + */ + virtual std::ostream& write(std::ostream& os) const + { + os << _value; + return os; + } + + private: + T _value; + }; + + // Declare the specializations for JSON output + template <> + void scalar_value::to_json(rapidjson::Allocator& allocator, rapidjson::Value& value) const; + template <> + void scalar_value::to_json(rapidjson::Allocator& allocator, rapidjson::Value& value) const; + template <> + void scalar_value::to_json(rapidjson::Allocator& allocator, rapidjson::Value& value) const; + + // Declare the common instantiations as external; defined in scalar_value.cc + extern template struct scalar_value; + extern template struct scalar_value; + extern template struct scalar_value; + + // Typedef the common instantiation + typedef scalar_value string_value; + typedef scalar_value integer_value; + typedef scalar_value boolean_value; + +}} // namespace facter::facts + +#endif // FACTER_FACTS_SCALAR_VALUE_HPP_ + diff --git a/lib/inc/facter/facts/string_value.hpp b/lib/inc/facter/facts/string_value.hpp deleted file mode 100644 index e61b0fd790..0000000000 --- a/lib/inc/facter/facts/string_value.hpp +++ /dev/null @@ -1,67 +0,0 @@ -#ifndef FACTER_FACTS_STRING_VALUE_HPP_ -#define FACTER_FACTS_STRING_VALUE_HPP_ - -#include "value.hpp" - -namespace facter { namespace facts { - - /** - * Represents a simple string value. - */ - struct string_value : value - { - /** - * Constructs a string_value. - * @param value The string value. - */ - explicit string_value(std::string&& value) : - _value(std::move(value)) - { - } - - /** - * Constructs a string_value. - * @param value The string value. - */ - explicit string_value(std::string const& value) : - _value(value) - { - } - - // Force non-copyable - string_value(string_value const&) = delete; - string_value& operator=(string_value const&) = delete; - - // Allow movable - string_value(string_value&&) = default; - string_value& operator=(string_value&&) = default; - - /** - * Converts the value to a JSON value. - * @param allocator The allocator to use for creating the JSON value. - * @param value The returned JSON value. - */ - virtual void to_json(rapidjson::Allocator& allocator, rapidjson::Value& value) const; - - /** - * Gets the string value. - * @return Returns the string value. - */ - std::string const& value() const { return _value; } - - protected: - /** - * Writes the value to the given stream. - * @param os The stream to write to. - * @returns Returns the stream being written to. - */ - virtual std::ostream& write(std::ostream& os) const; - - private: - std::string _value; - }; - -}} // namespace facter::facts - -#endif // FACTER_FACTS_STRING_VALUE_HPP_ - diff --git a/lib/src/facts/bsd/networking_resolver.cc b/lib/src/facts/bsd/networking_resolver.cc index c0e4945305..6c91a37b63 100644 --- a/lib/src/facts/bsd/networking_resolver.cc +++ b/lib/src/facts/bsd/networking_resolver.cc @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include #include diff --git a/lib/src/facts/bsd/uptime_resolver.cc b/lib/src/facts/bsd/uptime_resolver.cc index f421878ccc..adcb552b89 100644 --- a/lib/src/facts/bsd/uptime_resolver.cc +++ b/lib/src/facts/bsd/uptime_resolver.cc @@ -2,7 +2,7 @@ #include #include #include -#include +#include namespace facter { namespace facts { namespace bsd { diff --git a/lib/src/facts/fact.cc b/lib/src/facts/fact.cc index 3c416c5fff..8de41f9b21 100644 --- a/lib/src/facts/fact.cc +++ b/lib/src/facts/fact.cc @@ -1,6 +1,6 @@ #include #include -#include +#include using namespace std; diff --git a/lib/src/facts/fact_map.cc b/lib/src/facts/fact_map.cc index a6ff25689d..f44b568318 100644 --- a/lib/src/facts/fact_map.cc +++ b/lib/src/facts/fact_map.cc @@ -1,6 +1,6 @@ #include -#include -#include +#include +#include #include #include #include @@ -31,6 +31,10 @@ namespace facter { namespace facts { populate_platform_facts(*this); } + fact_map::~fact_map() + { + } + void fact_map::add(shared_ptr const& resolver) { if (!resolver) { diff --git a/lib/src/facts/integer_value.cc b/lib/src/facts/integer_value.cc deleted file mode 100644 index e6dfd8ffda..0000000000 --- a/lib/src/facts/integer_value.cc +++ /dev/null @@ -1,34 +0,0 @@ -#include -#include -#include - -using namespace std; -using namespace rapidjson; -using boost::lexical_cast; -using boost::bad_lexical_cast; - -namespace facter { namespace facts { - - integer_value::integer_value(string const& value) - { - try { - _value = lexical_cast(value); - } - catch (const bad_lexical_cast& e) { - // TODO: warn? - _value = 0; - } - } - - void integer_value::to_json(Allocator& allocator, Value& value) const - { - value.SetInt64(_value); - } - - ostream& integer_value::write(ostream& os) const - { - os << _value; - return os; - } - -}} // namespace facter::facts diff --git a/lib/src/facts/linux/block_device_resolver.cc b/lib/src/facts/linux/block_device_resolver.cc index 0527a33227..1b349bcad0 100644 --- a/lib/src/facts/linux/block_device_resolver.cc +++ b/lib/src/facts/linux/block_device_resolver.cc @@ -1,7 +1,6 @@ #include #include -#include -#include +#include #include #include #include @@ -63,7 +62,7 @@ namespace facter { namespace facts { namespace linux { if (is_regular_file(size_file, ec)) { try { uint64_t size = lexical_cast(trim(file::read(size_file))); - facts.add(string(fact::block_device) + "_" + device + "_size" , make_value(static_cast(size * 512))); + facts.add(string(fact::block_device) + "_" + device + "_size" , make_value(static_cast(size) * 512)); } catch (bad_lexical_cast& ex) { LOG_DEBUG("size of block device %1% is invalid: fact %2%_%1%_size is unavailable.", device, fact::block_device); } diff --git a/lib/src/facts/linux/dmi_resolver.cc b/lib/src/facts/linux/dmi_resolver.cc index 6c6b74c6e5..0397f786df 100644 --- a/lib/src/facts/linux/dmi_resolver.cc +++ b/lib/src/facts/linux/dmi_resolver.cc @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include #include diff --git a/lib/src/facts/linux/lsb_resolver.cc b/lib/src/facts/linux/lsb_resolver.cc index 945fd5e6a6..d377c5d83f 100644 --- a/lib/src/facts/linux/lsb_resolver.cc +++ b/lib/src/facts/linux/lsb_resolver.cc @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include #include diff --git a/lib/src/facts/linux/networking_resolver.cc b/lib/src/facts/linux/networking_resolver.cc index bb822516b6..0ff9a2049e 100644 --- a/lib/src/facts/linux/networking_resolver.cc +++ b/lib/src/facts/linux/networking_resolver.cc @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include #include diff --git a/lib/src/facts/linux/operating_system_resolver.cc b/lib/src/facts/linux/operating_system_resolver.cc index a3998f454d..ad125c40e3 100644 --- a/lib/src/facts/linux/operating_system_resolver.cc +++ b/lib/src/facts/linux/operating_system_resolver.cc @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/lib/src/facts/linux/processor_resolver.cc b/lib/src/facts/linux/processor_resolver.cc index d18bf39b93..9d31bd5cb3 100644 --- a/lib/src/facts/linux/processor_resolver.cc +++ b/lib/src/facts/linux/processor_resolver.cc @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include #include diff --git a/lib/src/facts/linux/selinux_resolver.cc b/lib/src/facts/linux/selinux_resolver.cc index ff8ce78cb9..02fa4a645f 100644 --- a/lib/src/facts/linux/selinux_resolver.cc +++ b/lib/src/facts/linux/selinux_resolver.cc @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include diff --git a/lib/src/facts/linux/uptime_resolver.cc b/lib/src/facts/linux/uptime_resolver.cc index e903e6502e..ac41236b77 100644 --- a/lib/src/facts/linux/uptime_resolver.cc +++ b/lib/src/facts/linux/uptime_resolver.cc @@ -1,6 +1,6 @@ #include #include -#include +#include #include namespace facter { namespace facts { namespace linux { diff --git a/lib/src/facts/osx/dmi_resolver.cc b/lib/src/facts/osx/dmi_resolver.cc index 7d13645bb1..2a5cca3fc6 100644 --- a/lib/src/facts/osx/dmi_resolver.cc +++ b/lib/src/facts/osx/dmi_resolver.cc @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include #include diff --git a/lib/src/facts/osx/networking_resolver.cc b/lib/src/facts/osx/networking_resolver.cc index 7f36663ea2..ee9c833f17 100644 --- a/lib/src/facts/osx/networking_resolver.cc +++ b/lib/src/facts/osx/networking_resolver.cc @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include #include diff --git a/lib/src/facts/osx/processor_resolver.cc b/lib/src/facts/osx/processor_resolver.cc index 27a093714a..b3b73f204c 100644 --- a/lib/src/facts/osx/processor_resolver.cc +++ b/lib/src/facts/osx/processor_resolver.cc @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include #include diff --git a/lib/src/facts/posix/kernel_resolver.cc b/lib/src/facts/posix/kernel_resolver.cc index 02c3ba023c..a4bd58485b 100644 --- a/lib/src/facts/posix/kernel_resolver.cc +++ b/lib/src/facts/posix/kernel_resolver.cc @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include #include diff --git a/lib/src/facts/posix/networking_resolver.cc b/lib/src/facts/posix/networking_resolver.cc index e98883d41b..0bc892b2b8 100644 --- a/lib/src/facts/posix/networking_resolver.cc +++ b/lib/src/facts/posix/networking_resolver.cc @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include #include diff --git a/lib/src/facts/posix/operating_system_resolver.cc b/lib/src/facts/posix/operating_system_resolver.cc index 0fb480032d..773e0bd10b 100644 --- a/lib/src/facts/posix/operating_system_resolver.cc +++ b/lib/src/facts/posix/operating_system_resolver.cc @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include #include diff --git a/lib/src/facts/posix/processor_resolver.cc b/lib/src/facts/posix/processor_resolver.cc index 8884398f53..20d2a2ffbb 100644 --- a/lib/src/facts/posix/processor_resolver.cc +++ b/lib/src/facts/posix/processor_resolver.cc @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include #include diff --git a/lib/src/facts/posix/ssh_resolver.cc b/lib/src/facts/posix/ssh_resolver.cc index 6e99ddffd6..9b705ec246 100644 --- a/lib/src/facts/posix/ssh_resolver.cc +++ b/lib/src/facts/posix/ssh_resolver.cc @@ -1,7 +1,7 @@ #pragma clang diagnostic ignored "-Wdeprecated-declarations" #include #include -#include +#include #include #include #include diff --git a/lib/src/facts/posix/uptime_resolver.cc b/lib/src/facts/posix/uptime_resolver.cc index 07435ef609..f92df3d885 100644 --- a/lib/src/facts/posix/uptime_resolver.cc +++ b/lib/src/facts/posix/uptime_resolver.cc @@ -1,8 +1,7 @@ #include #include #include -#include -#include +#include #include #include #include @@ -32,29 +31,25 @@ namespace facter { namespace facts { namespace posix { void uptime_resolver::resolve_uptime_hours(fact_map& facts) { - auto uptime_seconds = facts.get(fact::uptime_seconds); + auto uptime_seconds = facts.get(fact::uptime_seconds, false); if (!uptime_seconds) { return; } - int uptime_hours = uptime_seconds->value() / (60 * 60); - string value = to_string(uptime_hours); - facts.add(fact::uptime_hours, make_value(value)); + facts.add(fact::uptime_hours, make_value(uptime_seconds->value() / (60 * 60))); } void uptime_resolver::resolve_uptime_days(fact_map& facts) { - auto uptime_seconds = facts.get(fact::uptime_seconds); + auto uptime_seconds = facts.get(fact::uptime_seconds, false); if (!uptime_seconds) { return; } - int uptime_days = uptime_seconds->value() / (60 * 60 * 24); - string value = to_string(uptime_days); - facts.add(fact::uptime_days, make_value(value)); + facts.add(fact::uptime_days, make_value(uptime_seconds->value() / (60 * 60 * 24))); } void uptime_resolver::resolve_uptime(fact_map& facts) { - auto uptime_seconds = facts.get(fact::uptime_seconds); + auto uptime_seconds = facts.get(fact::uptime_seconds, false); if (!uptime_seconds) { return; } diff --git a/lib/src/facts/scalar_value.cc b/lib/src/facts/scalar_value.cc new file mode 100644 index 0000000000..be4998582a --- /dev/null +++ b/lib/src/facts/scalar_value.cc @@ -0,0 +1,31 @@ +#include +#include + +using namespace std; +using namespace rapidjson; + +namespace facter { namespace facts { + + template <> + void scalar_value::to_json(Allocator& allocator, Value& value) const + { + value.SetString(_value.c_str(), _value.size()); + } + + template <> + void scalar_value::to_json(Allocator& allocator, Value& value) const + { + value.SetInt64(_value); + } + + template <> + void scalar_value::to_json(Allocator& allocator, Value& value) const + { + value.SetBool(_value); + } + + template struct scalar_value; + template struct scalar_value; + template struct scalar_value; + +}} // namespace facter::facts diff --git a/lib/src/facts/string_value.cc b/lib/src/facts/string_value.cc deleted file mode 100644 index e5c5d9742b..0000000000 --- a/lib/src/facts/string_value.cc +++ /dev/null @@ -1,20 +0,0 @@ -#include -#include - -using namespace std; -using namespace rapidjson; - -namespace facter { namespace facts { - - void string_value::to_json(Allocator& allocator, Value& value) const - { - value.SetString(_value.c_str(), _value.size()); - } - - ostream& string_value::write(ostream& os) const - { - os << _value; - return os; - } - -}} // namespace facter::facts diff --git a/lib/tests/CMakeLists.txt b/lib/tests/CMakeLists.txt index bf8a40a60c..abfe5d4ba3 100644 --- a/lib/tests/CMakeLists.txt +++ b/lib/tests/CMakeLists.txt @@ -13,6 +13,7 @@ endif() set(LIBFACTER_TESTS_COMMON_SOURCES "${CMAKE_CURRENT_LIST_DIR}/environment.cc" "${CMAKE_CURRENT_LIST_DIR}/facts/array_value.cc" + "${CMAKE_CURRENT_LIST_DIR}/facts/boolean_value.cc" "${CMAKE_CURRENT_LIST_DIR}/facts/integer_value.cc" "${CMAKE_CURRENT_LIST_DIR}/facts/map_value.cc" "${CMAKE_CURRENT_LIST_DIR}/facts/string_value.cc" diff --git a/lib/tests/facts/array_value.cc b/lib/tests/facts/array_value.cc index c6067108fe..e8a4475358 100644 --- a/lib/tests/facts/array_value.cc +++ b/lib/tests/facts/array_value.cc @@ -1,7 +1,6 @@ #include #include -#include -#include +#include #include #include diff --git a/lib/tests/facts/boolean_value.cc b/lib/tests/facts/boolean_value.cc new file mode 100644 index 0000000000..5ca180df70 --- /dev/null +++ b/lib/tests/facts/boolean_value.cc @@ -0,0 +1,40 @@ +#include +#include +#include +#include + +using namespace std; +using namespace facter::facts; +using namespace rapidjson; + +TEST(facter_facts_boolean_value, constructor) { + { + boolean_value value(true); + ASSERT_TRUE(value.value()); + } + { + boolean_value value(false); + ASSERT_FALSE(value.value()); + } +} + +TEST(facter_facts_boolean_value, to_json) { + { + boolean_value value(true); + + Value json_value; + MemoryPoolAllocator<> allocator; + value.to_json(allocator, json_value); + ASSERT_TRUE(json_value.IsBool()); + ASSERT_TRUE(json_value.GetBool()); + } + { + boolean_value value(false); + + Value json_value; + MemoryPoolAllocator<> allocator; + value.to_json(allocator, json_value); + ASSERT_TRUE(json_value.IsBool()); + ASSERT_FALSE(json_value.GetBool()); + } +} diff --git a/lib/tests/facts/integer_value.cc b/lib/tests/facts/integer_value.cc index 7aeafc1a87..84691669d6 100644 --- a/lib/tests/facts/integer_value.cc +++ b/lib/tests/facts/integer_value.cc @@ -1,5 +1,5 @@ #include -#include +#include #include #include @@ -18,21 +18,6 @@ TEST(facter_facts_integer_value, integer_constructor) { ASSERT_EQ(4611686018427387904LL, foo3.value()); } -TEST(facter_facts_integer_value, string_constructor) { - // string integer - integer_value foo("42"); - ASSERT_EQ(42, foo.value()); - - // very large integer string - integer_value foo4("4611686018427387904"); - ASSERT_EQ(4611686018427387904LL, foo4.value()); - - // string not-an-integer - // TODO: expect a warning log message - integer_value not_an_integer("i am not an integer"); - ASSERT_EQ(0, not_an_integer.value()); -} - TEST(facter_facts_integer_value, to_json) { integer_value value(1337); diff --git a/lib/tests/facts/map_value.cc b/lib/tests/facts/map_value.cc index 10ce81c409..9a8ba1c0bf 100644 --- a/lib/tests/facts/map_value.cc +++ b/lib/tests/facts/map_value.cc @@ -1,8 +1,7 @@ #include #include #include -#include -#include +#include #include #include diff --git a/lib/tests/facts/string_value.cc b/lib/tests/facts/string_value.cc index ec2374fcb8..0272afef2c 100644 --- a/lib/tests/facts/string_value.cc +++ b/lib/tests/facts/string_value.cc @@ -1,5 +1,5 @@ #include -#include +#include #include #include From 2128d9e9668ed45952065802c83432d03114b12f Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Fri, 16 May 2014 17:50:48 -0700 Subject: [PATCH 1869/3753] (CFACT-23) Implement YAML output. Note: this commit adds a new dependency on yaml-cpp. Implementing YAML output with the addition of a --yaml (-y) command line option. --- .travis.yml | 2 + CMakeLists.txt | 12 ++++-- README.md | 1 + exe/cfacter.cc | 11 +++++- lib/CMakeLists.txt | 4 +- lib/inc/facter/facts/array_value.hpp | 7 ++++ lib/inc/facter/facts/fact_map.hpp | 6 +++ lib/inc/facter/facts/map_value.hpp | 7 ++++ lib/inc/facter/facts/scalar_value.hpp | 15 ++++++++ lib/inc/facter/facts/value.hpp | 21 ++++++++++ lib/src/facts/array_value.cc | 19 ++++++++- lib/src/facts/fact_map.cc | 18 ++++++++- lib/src/facts/map_value.cc | 17 ++++++++- lib/src/facts/scalar_value.cc | 19 +++++++-- lib/src/facts/value.cc | 7 ++++ lib/tests/facts/array_value.cc | 20 +++++++++- lib/tests/facts/boolean_value.cc | 21 +++++++++- lib/tests/facts/integer_value.cc | 14 ++++++- lib/tests/facts/map_value.cc | 25 +++++++++++- lib/tests/facts/string_value.cc | 12 +++++- vendor/FindYAMLCPP.cmake | 55 +++++++++++++++++++++++++++ 21 files changed, 292 insertions(+), 21 deletions(-) create mode 100644 vendor/FindYAMLCPP.cmake diff --git a/.travis.yml b/.travis.yml index 5eeccd28b1..a5f2712af3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,6 +21,8 @@ before_install: - sudo apt-get -y install libboost-filesystem1.48-dev libboost-program-options1.48-dev liblog4cxx10-dev - wget https://re2.googlecode.com/files/re2-20140304.tgz -O $HOME/re2-20140304.tgz - pushd $HOME && tar xzf re2-20140304.tgz && cd $HOME/re2 && make > /dev/null && sudo make install > /dev/null && popd + - wget https://yaml-cpp.googlecode.com/files/yaml-cpp-0.5.1.tar.gz -O $HOME/yaml-cpp-0.5.1.tgz + - pushd $HOME && tar xzf yaml-cpp-0.5.1.tgz && cd $HOME/yaml-cpp-0.5.1 && cmake -DBUILD_SHARED_LIBS=ON . && make > /dev/null && sudo make install > /dev/null && popd script: ./scripts/travis_target.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 01445482fa..ed713bc491 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,19 +12,19 @@ set(VENDOR_DIRECTORY "${PROJECT_SOURCE_DIR}/vendor") list(APPEND CMAKE_MODULE_PATH ${VENDOR_DIRECTORY}) # Find Boost -find_package(Boost 1.48 COMPONENTS program_options system filesystem REQUIRED) +find_package(Boost 1.48 COMPONENTS program_options system filesystem) if (NOT Boost_FOUND) message(FATAL_ERROR "Boost 1.48 or newer is required. Please install it before building.") endif() # Find Log4cxx -find_package(LOG4CXX REQUIRED) +find_package(LOG4CXX) if (NOT LOG4CXX_FOUND) message(FATAL_ERROR "Log4cxx version 10.0 is required. Please install it before building.") endif() # Find RE2 -find_package(RE2 REQUIRED) +find_package(RE2) if (NOT RE2_FOUND) message(FATAL_ERROR "RE2 is required. Please install it before building.") endif() @@ -35,6 +35,12 @@ if (NOT OPENSSL_FOUND) message(FATAL_ERROR "openssl is required. Please install it before building.") endif() +# Find YAMLCPP +find_package(YAMLCPP) +if (NOT YAMLCPP_FOUND) + message(FATAL_ERROR "yaml-cpp is required. Please install it before building.") +endif() + # Include vendor libraries include(${VENDOR_DIRECTORY}/rapidjson.cmake) include(${VENDOR_DIRECTORY}/gmock.cmake) diff --git a/README.md b/README.md index 90647aab57..62eff2948b 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ Build Requirements * Boost C++ Libraries >= 1.48 * Apache log4cxx >= 10.0 * OpenSSL >= 1.0.1.g +* yaml-cpp >= 0.5.1 * Google's RE2 library Generating Build Files diff --git a/exe/cfacter.cc b/exe/cfacter.cc index 1f1add4504..f130b379e1 100644 --- a/exe/cfacter.cc +++ b/exe/cfacter.cc @@ -1,4 +1,3 @@ -#include #include #include #include @@ -122,7 +121,8 @@ int main(int argc, char **argv) ("json,j", "Output in JSON format.") ("propfile,p", po::value(&properties_file), "Configure logging with a log4cxx properties file.") ("verbose", "Enable verbose (info) output.") - ("version,v", "Print the version and exit."); + ("version,v", "Print the version and exit.") + ("yaml,y", "Output in YAML format."); // Build a list of "hidden" options that are not visible on the command line po::options_description hidden_options(""); @@ -149,6 +149,11 @@ int main(int argc, char **argv) } po::notify(vm); + + // Check for conflicting options + if (vm.count("json") && vm.count("yaml")) { + throw po::error("json and yaml options conflict. please specify one or the other."); + } } catch(po::error& ex) { cerr << "error: " << ex.what() << "\n\n"; @@ -199,6 +204,8 @@ int main(int argc, char **argv) // Output the facts if (vm.count("json")) { facts.write_json(cout); + } else if (vm.count("yaml")) { + facts.write_yaml(cout); } else { cout << facts; } diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 55f5d3d25b..bf54b5424a 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -83,8 +83,9 @@ include_directories( ${RE2_INCLUDE_DIRS} ${RAPIDJSON_INCLUDE_DIRS} ${LOG4CXX_INCLUDE_DIRS} - ${BOOST_INCLUDE_DIRS} + ${Boost_INCLUDE_DIRS} ${OPENSSL_INCLUDE_DIRS} + ${YAMLCPP_INCLUDE_DIRS} ) # Link in additional libraries @@ -94,6 +95,7 @@ target_link_libraries(libfacter ${LOG4CXX_LIBRARIES} ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} + ${YAMLCPP_LIBRARIES} ) # Add a dependency on rapidjson diff --git a/lib/inc/facter/facts/array_value.hpp b/lib/inc/facter/facts/array_value.hpp index e50605e8fa..4649998476 100644 --- a/lib/inc/facter/facts/array_value.hpp +++ b/lib/inc/facter/facts/array_value.hpp @@ -64,6 +64,13 @@ namespace facter { namespace facts { */ virtual std::ostream& write(std::ostream& os) const; + /** + * Writes the value to the given YAML emitter. + * @param emitter The YAML emitter to write to. + * @returns Returns the given YAML emitter. + */ + virtual YAML::Emitter& write(YAML::Emitter& emitter) const; + private: std::vector> _elements; }; diff --git a/lib/inc/facter/facts/fact_map.hpp b/lib/inc/facter/facts/fact_map.hpp index cf75992f7f..87dcf30a80 100644 --- a/lib/inc/facter/facts/fact_map.hpp +++ b/lib/inc/facter/facts/fact_map.hpp @@ -140,6 +140,12 @@ namespace facter { namespace facts { */ void write_json(std::ostream& stream) const; + /** + * Writes the contents of the fact map as YAML to the given stream. + * @param stream The stream to write the YAML to. + */ + void write_yaml(std::ostream& stream) const; + private: typedef std::map> fact_map_type; typedef std::map> resolver_map_type; diff --git a/lib/inc/facter/facts/map_value.hpp b/lib/inc/facter/facts/map_value.hpp index e872960ada..44ce8add7f 100644 --- a/lib/inc/facter/facts/map_value.hpp +++ b/lib/inc/facter/facts/map_value.hpp @@ -65,6 +65,13 @@ namespace facter { namespace facts { */ virtual std::ostream& write(std::ostream& os) const; + /** + * Writes the value to the given YAML emitter. + * @param emitter The YAML emitter to write to. + * @returns Returns the given YAML emitter. + */ + virtual YAML::Emitter& write(YAML::Emitter& emitter) const; + private: std::map> _elements; }; diff --git a/lib/inc/facter/facts/scalar_value.hpp b/lib/inc/facter/facts/scalar_value.hpp index ae44a5d7e0..9abb365813 100644 --- a/lib/inc/facter/facts/scalar_value.hpp +++ b/lib/inc/facter/facts/scalar_value.hpp @@ -66,6 +66,17 @@ namespace facter { namespace facts { return os; } + /** + * Writes the value to the given YAML emitter. + * @param emitter The YAML emitter to write to. + * @returns Returns the given YAML emitter. + */ + virtual YAML::Emitter& write(YAML::Emitter& emitter) const + { + emitter << _value; + return emitter; + } + private: T _value; }; @@ -78,6 +89,10 @@ namespace facter { namespace facts { template <> void scalar_value::to_json(rapidjson::Allocator& allocator, rapidjson::Value& value) const; + // Declare the specializations for YAML output + template <> + YAML::Emitter& scalar_value::write(YAML::Emitter& emitter) const; + // Declare the common instantiations as external; defined in scalar_value.cc extern template struct scalar_value; extern template struct scalar_value; diff --git a/lib/inc/facter/facts/value.hpp b/lib/inc/facter/facts/value.hpp index a66cd211fc..14124d08ec 100644 --- a/lib/inc/facter/facts/value.hpp +++ b/lib/inc/facter/facts/value.hpp @@ -6,6 +6,11 @@ #include #include +// Forward declare needed yaml-cpp classes. +namespace YAML { + class Emitter; +} + // Forward delcare needed rapidjson classes. namespace rapidjson { class CrtAllocator; @@ -58,6 +63,14 @@ namespace facter { namespace facts { */ virtual std::ostream& write(std::ostream& os) const = 0; friend std::ostream& operator<<(std::ostream& os, value const& val); + + /** + * Writes the value to the given YAML emitter. + * @param emitter The YAML emitter to write to. + * @returns Returns the given YAML emitter. + */ + virtual YAML::Emitter& write(YAML::Emitter& emitter) const = 0; + friend YAML::Emitter& operator<<(YAML::Emitter& emitter, value const& val); }; /** @@ -81,6 +94,14 @@ namespace facter { namespace facts { */ std::ostream& operator<<(std::ostream& os, value const& val); + /** + * Insertion operator for value. + * @param emitter The YAML emitter to write to. + * @param val The value to write to the YAML emitter. + * @return Returns the given YAML emitter. + */ + YAML::Emitter& operator<<(YAML::Emitter& emitter, value const& val); + }} // namespace facter::facts #endif // FACTER_FACTS_VALUE_HPP_ diff --git a/lib/src/facts/array_value.cc b/lib/src/facts/array_value.cc index f4f15f9ec7..4054b7690f 100644 --- a/lib/src/facts/array_value.cc +++ b/lib/src/facts/array_value.cc @@ -1,12 +1,14 @@ #include #include +#include using namespace std; using namespace rapidjson; +using namespace YAML; namespace facter { namespace facts { - void array_value::to_json(Allocator& allocator, Value& value) const + void array_value::to_json(Allocator& allocator, rapidjson::Value& value) const { value.SetArray(); @@ -15,7 +17,7 @@ namespace facter { namespace facts { continue; } - Value child; + rapidjson::Value child; element->to_json(allocator, child); value.PushBack(child, allocator); } @@ -41,4 +43,17 @@ namespace facter { namespace facts { return os; } + Emitter& array_value::write(Emitter& emitter) const + { + emitter << BeginSeq; + for (auto const& element : _elements) { + if (!element) { + continue; + } + emitter << *element; + } + emitter << EndSeq; + return emitter; + } + }} // namespace facter::facts diff --git a/lib/src/facts/fact_map.cc b/lib/src/facts/fact_map.cc index f44b568318..6140bf85b1 100644 --- a/lib/src/facts/fact_map.cc +++ b/lib/src/facts/fact_map.cc @@ -5,9 +5,11 @@ #include #include #include +#include using namespace std; using namespace rapidjson; +using namespace YAML; LOG_DECLARE_NAMESPACE("facts.map"); @@ -192,7 +194,7 @@ namespace facter { namespace facts { continue; } - Value value; + rapidjson::Value value; kvp.second->to_json(document.GetAllocator(), value); document.AddMember(kvp.first.c_str(), value, document.GetAllocator()); } @@ -203,6 +205,20 @@ namespace facter { namespace facts { document.Accept(writer); } + void fact_map::write_yaml(ostream& stream) const + { + Emitter emitter(stream); + emitter << BeginMap; + for (auto const& kvp : _facts) { + if (!kvp.second) { + continue; + } + emitter << Key << kvp.first; + emitter << YAML::Value << *kvp.second; + } + emitter << EndMap; + } + value const* fact_map::get_value(string const& name, bool resolve) { // Lookup the fact diff --git a/lib/src/facts/map_value.cc b/lib/src/facts/map_value.cc index fafb1c3a23..2c8f2611d4 100644 --- a/lib/src/facts/map_value.cc +++ b/lib/src/facts/map_value.cc @@ -1,8 +1,10 @@ #include #include +#include using namespace std; using namespace rapidjson; +using namespace YAML; namespace facter { namespace facts { @@ -15,7 +17,7 @@ namespace facter { namespace facts { return it->second.get(); } - void map_value::to_json(Allocator& allocator, Value& value) const + void map_value::to_json(Allocator& allocator, rapidjson::Value& value) const { value.SetObject(); @@ -24,7 +26,7 @@ namespace facter { namespace facts { continue; } - Value child; + rapidjson::Value child; kvp.second->to_json(allocator, child); value.AddMember(kvp.first.c_str(), child, allocator); } @@ -50,4 +52,15 @@ namespace facter { namespace facts { return os; } + Emitter& map_value::write(Emitter& emitter) const + { + emitter << BeginMap; + for (auto const& kvp : _elements) { + emitter << Key << kvp.first; + emitter << YAML::Value << *kvp.second; + } + emitter << EndMap; + return emitter; + } + }} // namespace facter::facts diff --git a/lib/src/facts/scalar_value.cc b/lib/src/facts/scalar_value.cc index be4998582a..ef77b271e5 100644 --- a/lib/src/facts/scalar_value.cc +++ b/lib/src/facts/scalar_value.cc @@ -1,29 +1,42 @@ #include #include +#include using namespace std; using namespace rapidjson; +using namespace YAML; namespace facter { namespace facts { template <> - void scalar_value::to_json(Allocator& allocator, Value& value) const + void scalar_value::to_json(Allocator& allocator, rapidjson::Value& value) const { value.SetString(_value.c_str(), _value.size()); } template <> - void scalar_value::to_json(Allocator& allocator, Value& value) const + void scalar_value::to_json(Allocator& allocator, rapidjson::Value& value) const { value.SetInt64(_value); } template <> - void scalar_value::to_json(Allocator& allocator, Value& value) const + void scalar_value::to_json(Allocator& allocator, rapidjson::Value& value) const { value.SetBool(_value); } + template <> + Emitter& scalar_value::write(Emitter& emitter) const + { + // Unfortunately, yaml-cpp doesn't handle quoting strings automatically that well + // For instance, if the string is an integer, no quotes are written out + // This will cause someone parsing the YAML to see the type as an integer and + // not as a string. + emitter << DoubleQuoted << _value; + return emitter; + } + template struct scalar_value; template struct scalar_value; template struct scalar_value; diff --git a/lib/src/facts/value.cc b/lib/src/facts/value.cc index 54a3658a88..87a1b5379b 100644 --- a/lib/src/facts/value.cc +++ b/lib/src/facts/value.cc @@ -1,6 +1,8 @@ #include +#include using namespace std; +using namespace YAML; namespace facter { namespace facts { @@ -9,4 +11,9 @@ namespace facter { namespace facts { return val.write(os); } + Emitter& operator<<(Emitter& emitter, value const& val) + { + return val.write(emitter); + } + }} // namespace facter::facts diff --git a/lib/tests/facts/array_value.cc b/lib/tests/facts/array_value.cc index e8a4475358..38d3255423 100644 --- a/lib/tests/facts/array_value.cc +++ b/lib/tests/facts/array_value.cc @@ -2,11 +2,13 @@ #include #include #include +#include #include using namespace std; using namespace facter::facts; using namespace rapidjson; +using namespace YAML; TEST(facter_facts_array_value, default_constructor) { array_value value; @@ -53,7 +55,7 @@ TEST(facter_facts_array_value, to_json) { array_value value(move(elements)); - Value json_value; + rapidjson::Value json_value; MemoryPoolAllocator<> allocator; value.to_json(allocator, json_value); ASSERT_TRUE(json_value.IsArray()); @@ -86,3 +88,19 @@ TEST(facter_facts_array_value, insertion_operator) { stream << value; ASSERT_EQ("[ 1, 2, [ child ] ]", stream.str()); } + +TEST(facter_facts_array_value, yaml_insertion_operator) { + vector> subelements; + subelements.emplace_back(make_value("child")); + + vector> elements; + elements.emplace_back(make_value("1")); + elements.emplace_back(make_value(2)); + elements.emplace_back(make_value(move(subelements))); + + array_value value(move(elements)); + + Emitter emitter; + emitter << value; + ASSERT_EQ("- \"1\"\n- 2\n-\n - \"child\"", string(emitter.c_str())); +} diff --git a/lib/tests/facts/boolean_value.cc b/lib/tests/facts/boolean_value.cc index 5ca180df70..176153586f 100644 --- a/lib/tests/facts/boolean_value.cc +++ b/lib/tests/facts/boolean_value.cc @@ -1,11 +1,13 @@ #include #include #include +#include #include using namespace std; using namespace facter::facts; using namespace rapidjson; +using namespace YAML; TEST(facter_facts_boolean_value, constructor) { { @@ -22,7 +24,7 @@ TEST(facter_facts_boolean_value, to_json) { { boolean_value value(true); - Value json_value; + rapidjson::Value json_value; MemoryPoolAllocator<> allocator; value.to_json(allocator, json_value); ASSERT_TRUE(json_value.IsBool()); @@ -31,10 +33,25 @@ TEST(facter_facts_boolean_value, to_json) { { boolean_value value(false); - Value json_value; + rapidjson::Value json_value; MemoryPoolAllocator<> allocator; value.to_json(allocator, json_value); ASSERT_TRUE(json_value.IsBool()); ASSERT_FALSE(json_value.GetBool()); } } + +TEST(facter_facts_boolean_value, insertion_operator) { + { + boolean_value value(true); + Emitter emitter; + emitter << value; + ASSERT_EQ("true", string(emitter.c_str())); + } + { + boolean_value value(false); + Emitter emitter; + emitter << value; + ASSERT_EQ("false", string(emitter.c_str())); + } +} diff --git a/lib/tests/facts/integer_value.cc b/lib/tests/facts/integer_value.cc index 84691669d6..420249a422 100644 --- a/lib/tests/facts/integer_value.cc +++ b/lib/tests/facts/integer_value.cc @@ -1,13 +1,15 @@ #include #include #include +#include #include using namespace std; using namespace facter::facts; using namespace rapidjson; +using namespace YAML; -TEST(facter_facts_integer_value, integer_constructor) { +TEST(facter_facts_integer_value, constructor) { // small integer integer_value foo(42); ASSERT_EQ(42, foo.value()); @@ -21,7 +23,7 @@ TEST(facter_facts_integer_value, integer_constructor) { TEST(facter_facts_integer_value, to_json) { integer_value value(1337); - Value json_value; + rapidjson::Value json_value; MemoryPoolAllocator<> allocator; value.to_json(allocator, json_value); ASSERT_TRUE(json_value.IsNumber()); @@ -35,3 +37,11 @@ TEST(facter_facts_integer_value, insertion_operator) { stream << value; ASSERT_EQ("5", stream.str()); } + +TEST(facter_facts_integer_value, yaml_insertion_operator) { + integer_value value(5); + + Emitter emitter; + emitter << value; + ASSERT_EQ("5", string(emitter.c_str())); +} diff --git a/lib/tests/facts/map_value.cc b/lib/tests/facts/map_value.cc index 9a8ba1c0bf..19a23c598c 100644 --- a/lib/tests/facts/map_value.cc +++ b/lib/tests/facts/map_value.cc @@ -3,11 +3,13 @@ #include #include #include +#include #include using namespace std; using namespace facter::facts; using namespace rapidjson; +using namespace YAML; TEST(facter_facts_map_value, default_constructor) { map_value value; @@ -71,7 +73,7 @@ TEST(facter_facts_map_value, to_json) { map_value value(move(elements)); - Value json_value; + rapidjson::Value json_value; MemoryPoolAllocator<> allocator; value.to_json(allocator, json_value); ASSERT_TRUE(json_value.IsObject()); @@ -114,3 +116,24 @@ TEST(facter_facts_map_value, insertion_operator) { stream << value; ASSERT_EQ("{ array => [ 1, 2 ], integer => 5, map => { foo => bar }, string => hello }", stream.str()); } + +TEST(facter_facts_map_value, yaml_insertion_operator) { + map> elements; + elements["string"] = make_value("hello"); + elements["integer"] = make_value(5); + + vector> array_elements; + array_elements.emplace_back(make_value("1")); + array_elements.emplace_back(make_value(2)); + elements["array"] = make_value(move(array_elements)); + + map> submap; + submap["foo"] = make_value("bar"); + elements["map"] = make_value(move(submap)); + + map_value value(move(elements)); + + Emitter emitter; + emitter << value; + ASSERT_EQ("array:\n - \"1\"\n - 2\ninteger: 5\nmap:\n foo: \"bar\"\nstring: \"hello\"", string(emitter.c_str())); +} diff --git a/lib/tests/facts/string_value.cc b/lib/tests/facts/string_value.cc index 0272afef2c..ab9e0c4752 100644 --- a/lib/tests/facts/string_value.cc +++ b/lib/tests/facts/string_value.cc @@ -1,11 +1,13 @@ #include #include #include +#include #include using namespace std; using namespace facter::facts; using namespace rapidjson; +using namespace YAML; TEST(facter_facts_string_value, move_constructor) { string s = "hello world"; @@ -24,7 +26,7 @@ TEST(facter_facts_string_value, copy_constructor) { TEST(facter_facts_string_value, to_json) { string_value value("hello world"); - Value json_value; + rapidjson::Value json_value; MemoryPoolAllocator<> allocator; value.to_json(allocator, json_value); ASSERT_TRUE(json_value.IsString()); @@ -38,3 +40,11 @@ TEST(facter_facts_string_value, insertion_operator) { stream << value; ASSERT_EQ("hello world", stream.str()); } + +TEST(facter_facts_string_value, yaml_insertion_operator) { + string_value value("hello world"); + + Emitter emitter; + emitter << value; + ASSERT_EQ("\"hello world\"", string(emitter.c_str())); +} diff --git a/vendor/FindYAMLCPP.cmake b/vendor/FindYAMLCPP.cmake new file mode 100644 index 0000000000..520d502751 --- /dev/null +++ b/vendor/FindYAMLCPP.cmake @@ -0,0 +1,55 @@ +################################################################################ +# +# CMake script for finding yaml-cpp. +# The default CMake search process is used to locate files. +# +# This script creates the following variables: +# YAMLCPP_FOUND: Boolean that indicates if the package was found +# YAMLCPP_INCLUDE_DIRS: Paths to the necessary header files +# YAMLCPP_LIBRARIES: Package libraries +# YAMLCPP_LIBRARY_DIRS: Path to package libraries +# +################################################################################ + +include(FindPackageHandleStandardArgs) + +# See if YAMLCPP_ROOT is not already set in CMake +if (NOT YAMLCPP_ROOT) + # See if YAMLCPP_ROOT is set in process environment + if (NOT $ENV{YAMLCPP_ROOT} STREQUAL "") + set(YAMLCPP_ROOT "$ENV{YAMLCPP_ROOT}") + message(STATUS "Detected YAMLCPP_ROOT set to '${YAMLCPP_ROOT}'") + endif() +endif() + +# If YAMLCPP_ROOT is available, set up our hints +if (YAMLCPP_ROOT) + set(YAMLCPP_INCLUDE_HINTS HINTS "${YAMLCPP_ROOT}/include" "${YAMLCPP_ROOT}") + set(YAMLCPP_LIBRARY_HINTS HINTS "${YAMLCPP_ROOT}/lib") +endif() + +# Find headers and libraries +find_path(YAMLCPP_INCLUDE_DIR NAMES yaml-cpp/yaml.h ${YAMLCPP_INCLUDE_HINTS}) +find_library(YAMLCPP_LIBRARY NAMES yaml-cpp ${YAMLCPP_LIBRARY_HINTS}) + +# Set YAMLCPP_FOUND honoring the QUIET and REQUIRED arguments +find_package_handle_standard_args(YAMLCPP DEFAULT_MSG YAMLCPP_LIBRARY YAMLCPP_INCLUDE_DIR) + +# Output variables +if (YAMLCPP_FOUND) + # Include dirs + set(YAMLCPP_INCLUDE_DIRS ${YAMLCPP_INCLUDE_DIR}) + + # Libraries + if (YAMLCPP_LIBRARY) + set(YAMLCPP_LIBRARIES ${YAMLCPP_LIBRARY}) + else() + set(YAMLCPP_LIBRARIES "") + endif() + + # Link dirs + get_filename_component(YAMLCPP_LIBRARY_DIRS ${YAMLCPP_LIBRARY} PATH) +endif() + +# Advanced options for not cluttering the cmake UIs +mark_as_advanced(YAMLCPP_INCLUDE_DIR YAMLCPP_LIBRARY) \ No newline at end of file From 1a237a6f870643b7aa38374e067bcf541f88a493 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Wed, 7 May 2014 14:47:50 -0700 Subject: [PATCH 1870/3753] (maint) Fix r-value overloads of string utility functions. The r-value overloads were returning an l-value reference which meant that despite being used, copy assignment/construction would still occur. Fixing these functions to return an r-value reference. --- lib/inc/facter/util/string.hpp | 10 +++++----- lib/src/facts/linux/lsb_resolver.cc | 2 +- lib/src/util/string.cc | 20 ++++++++++---------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/lib/inc/facter/util/string.hpp b/lib/inc/facter/util/string.hpp index 5872201316..2c5cd1701f 100644 --- a/lib/inc/facter/util/string.hpp +++ b/lib/inc/facter/util/string.hpp @@ -41,7 +41,7 @@ namespace facter { namespace util { * @param set The set of characters to trim. Defaults to whitespace characters. * @return Returns the given string. */ - std::string& ltrim(std::string&& str, std::initializer_list const& set = default_trim_set); + std::string&& ltrim(std::string&& str, std::initializer_list const& set = default_trim_set); /** * In-place trims characters from the end (right side) of a string. @@ -56,7 +56,7 @@ namespace facter { namespace util { * @param set The set of characters to trim. Defaults to whitespace characters. * @return Returns the given string. */ - std::string& rtrim(std::string&& str, std::initializer_list const& set = default_trim_set); + std::string&& rtrim(std::string&& str, std::initializer_list const& set = default_trim_set); /** * In-place trims characters from the the start and end (both sides) of a string. @@ -71,7 +71,7 @@ namespace facter { namespace util { * @param set The set of characters to trim. Defaults to whitespace characters. * @return Returns the given string. */ - std::string& trim(std::string&& str, std::initializer_list const& set = default_trim_set); + std::string&& trim(std::string&& str, std::initializer_list const& set = default_trim_set); /** * Tokenizes the given string. @@ -108,7 +108,7 @@ namespace facter { namespace util { * @param str The string to convert to lowercase. * @return Returns the given string. */ - std::string& to_lower(std::string&& str); + std::string&& to_lower(std::string&& str); /** * Converts the given string to uppercase. @@ -121,7 +121,7 @@ namespace facter { namespace util { * @param str The string to convert to uppercase. * @return Returns the given string. */ - std::string& to_upper(std::string&& str); + std::string&& to_upper(std::string&& str); /** * Converts the given bytes to a hexadecimal string. diff --git a/lib/src/facts/linux/lsb_resolver.cc b/lib/src/facts/linux/lsb_resolver.cc index d377c5d83f..4fd3298fca 100644 --- a/lib/src/facts/linux/lsb_resolver.cc +++ b/lib/src/facts/linux/lsb_resolver.cc @@ -57,7 +57,7 @@ namespace facter { namespace facts { namespace linux { } // The value may be quoted; trim the quotes - facts.add(fact::lsb_dist_description, make_value(move(trim(value, { '\"' })))); + facts.add(fact::lsb_dist_description, make_value(trim(move(value), { '\"' }))); } void lsb_resolver::resolve_dist_version(fact_map& facts) diff --git a/lib/src/util/string.cc b/lib/src/util/string.cc index 9f30c7943e..e9e399b165 100644 --- a/lib/src/util/string.cc +++ b/lib/src/util/string.cc @@ -35,9 +35,9 @@ namespace facter { namespace util { return str; } - string& ltrim(string&& str, initializer_list const& set) + string&& ltrim(string&& str, initializer_list const& set) { - return ltrim(str, set); + return move(ltrim(str, set)); } string& rtrim(string& str, initializer_list const& set) @@ -48,9 +48,9 @@ namespace facter { namespace util { return str; } - string& rtrim(string&& str, initializer_list const& set) + string&& rtrim(string&& str, initializer_list const& set) { - return rtrim(str, set); + return move(rtrim(str, set)); } string& trim(string& str, initializer_list const& set) @@ -58,9 +58,9 @@ namespace facter { namespace util { return ltrim(rtrim(str, set), set); } - string& trim(string&& str, initializer_list const& set) + string&& trim(string&& str, initializer_list const& set) { - return trim(str, set); + return move(trim(str, set)); } vector tokenize(string const& str) @@ -106,9 +106,9 @@ namespace facter { namespace util { return str; } - string& to_lower(string&& str) + string&& to_lower(string&& str) { - return to_lower(str); + return move(to_lower(str)); } string& to_upper(string& str) @@ -117,9 +117,9 @@ namespace facter { namespace util { return str; } - string& to_upper(string&& str) + string&& to_upper(string&& str) { - return to_upper(str); + return move(to_upper(str)); } string to_hex(uint8_t const* bytes, size_t length, bool uppercase) From 4a0fbe46d8f2df8207124604554577cb42ab9e13 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Tue, 20 May 2014 15:04:57 -0700 Subject: [PATCH 1871/3753] (CFACT-19) Implement cfacter gem. Re-implementing the cfacter gem. Removing JSON dependency on the gem since we now use callbacks to populate a hash instead of emitting and parsing a JSON string. projects. Adding Rakefile to gem directory with tasks for packaging and installing the gem. Adding tasks for running specs. Adding specs that do some basic validation of the cfacter gem. Updating README for instructions on how to build, test, and install the gem. --- README.md | 31 +++++- gem/.gitignore | 6 +- gem/Gemfile | 14 +++ gem/{README => README.md} | 0 gem/Rakefile | 8 ++ gem/cfacter.gemspec | 17 ++-- gem/lib/cfacter.rb | 138 +++++++++++++++++++------- gem/spec/spec_helper.rb | 55 ++++++++++ gem/spec/unit/cfacter_spec.rb | 118 ++++++++++++++++++++++ gem/tasks/build.rake | 14 +++ gem/tasks/spec.rake | 26 +++++ lib/inc/facter/facterlib.h | 91 +++++++++++++++-- lib/inc/facter/facts/array_value.hpp | 23 ++++- lib/inc/facter/facts/map_value.hpp | 18 ++++ lib/inc/facter/facts/scalar_value.hpp | 15 +++ lib/inc/facter/facts/value.hpp | 11 ++ lib/src/facterlib.cc | 77 +++++++++++--- lib/src/facts/array_value.cc | 24 +++++ lib/src/facts/fact_map.cc | 27 ++--- lib/src/facts/map_value.cc | 24 +++++ lib/src/facts/scalar_value.cc | 25 +++++ lib/tests/facts/array_value.cc | 8 +- lib/tests/facts/map_value.cc | 14 +-- 23 files changed, 684 insertions(+), 100 deletions(-) create mode 100644 gem/Gemfile rename gem/{README => README.md} (100%) create mode 100644 gem/Rakefile create mode 100644 gem/spec/spec_helper.rb create mode 100644 gem/spec/unit/cfacter_spec.rb create mode 100644 gem/tasks/build.rake create mode 100644 gem/tasks/spec.rake diff --git a/README.md b/README.md index 62eff2948b..a946d74ed9 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,8 @@ Build Requirements * yaml-cpp >= 0.5.1 * Google's RE2 library -Generating Build Files ----------------------- +Pre-Build +--------- All of the following examples start by assuming the current directory is the root of the repo. @@ -30,6 +30,10 @@ To generate build files with debug information: $ cd debug $ cmake -DCMAKE_BUILD_TYPE=Debug .. +Before building the gem, install the cfacter bundle: + + $ cd gem + $ bundle install Build ----- @@ -44,6 +48,13 @@ To build cfacter with debug information: $ cd debug $ make +To build the cfacter gem: + + $ cd gem + $ rake gem + +The gem will be created in the `gem/pkg` directory. + Run --- @@ -73,6 +84,11 @@ For verbose test output, run `ctest` instead of using the test target: $ cd release $ ctest -V +To run gem tests: + + $ cd gem + $ rspec + Install ------- @@ -91,9 +107,18 @@ To install to a different location, set the install prefix: This would install cfacter into `~/bin` and `~/lib`. +To install the gem (assumes gem is already built): + + $ cd gem + $ gem install pkg/*.gem + Uninstall --------- Run the following command to remove files that were previously installed: -`$ sudo xargs rm < release/install_manifest.txt` + $ sudo xargs rm < release/install_manifest.txt + +To uninstall the gem: + + $ gem uninstall cfacter diff --git a/gem/.gitignore b/gem/.gitignore index a68922fec9..5ee206ff3d 100644 --- a/gem/.gitignore +++ b/gem/.gitignore @@ -1 +1,5 @@ -cfacter-*gem +pkg/ +.ruby-version +.ruby-gemset +Gemfile.lock +Gemfile.local diff --git a/gem/Gemfile b/gem/Gemfile new file mode 100644 index 0000000000..9cd9030094 --- /dev/null +++ b/gem/Gemfile @@ -0,0 +1,14 @@ +source ENV['GEM_SOURCE'] || "https://rubygems.org/" + +gem 'ffi', '~> 1.9.3', :require => false +gem 'cfacter', :path => File.expand_path("..", __FILE__), :require => false + +group :development, :test do + gem 'rake', "~> 10.1.0" + gem 'rspec', "~> 2.11.0" + gem 'mocha', "~> 0.10.5" +end + +if File.exists? "#{__FILE__}.local" + eval(File.read("#{__FILE__}.local"), binding) +end diff --git a/gem/README b/gem/README.md similarity index 100% rename from gem/README rename to gem/README.md diff --git a/gem/Rakefile b/gem/Rakefile new file mode 100644 index 0000000000..f828ff0443 --- /dev/null +++ b/gem/Rakefile @@ -0,0 +1,8 @@ +# Rakefile for cfacter + +require 'rake' +Dir['tasks/**/*.rake'].each { |t| load t } + +task :default do + exec 'rake -T' +end diff --git a/gem/cfacter.gemspec b/gem/cfacter.gemspec index 0752b5fc90..95d74ee667 100644 --- a/gem/cfacter.gemspec +++ b/gem/cfacter.gemspec @@ -1,11 +1,12 @@ Gem::Specification.new do |s| s.name = 'cfacter' - s.version = '0.0.1' - s.summary = "cfacter" - s.description = "A lightweight facter replacement" - s.authors = ["Kylo Ginsberg"] - s.email = 'kylo@kylo.net' - s.files = ["lib/cfacter.rb"] - s.add_runtime_dependency 'json', '~> 1.8', '>= 1.8.1' - s.add_runtime_dependency 'ffi', '1.9.0' + s.version = '0.1.0' + s.summary = 'cfacter' + s.description = 'A lightweight facter replacement' + s.authors = ["Puppet Labs"] + s.email = 'cfacter@puppetlabs.com' + s.homepage = "http://puppetlabs.com/" + s.license = 'Apache-2.0' + s.files = ['lib/cfacter.rb'] + s.add_runtime_dependency 'ffi', '1.9.3' end diff --git a/gem/lib/cfacter.rb b/gem/lib/cfacter.rb index 58cd14f672..0d68efe38d 100644 --- a/gem/lib/cfacter.rb +++ b/gem/lib/cfacter.rb @@ -1,56 +1,122 @@ require 'ffi' -require 'json' module CFacter - private - extend FFI::Library - ffi_lib "libfacter.so" - # to_json is used for to_hash but no need to make it public - attach_function :to_json, [:pointer, :size_t], :int + # This module defines cfacter's C interface in Ruby. + module FacterLib + extend FFI::Library - class Constants - JSON_STRING_MAX_LEN = 1024000 - end + begin + ffi_lib ['libfacter', 'facter'] + rescue LoadError + raise LoadError.new('libfacter was not found. Please make sure cfacter is installed.') + end - public - attach_function :clear, [], :void - attach_function :loadfacts, [], :void + callback :string_callback, [:string, :string], :void + callback :integer_callback, [:string, :int64], :void + callback :boolean_callback, [:string, :uint8], :void + callback :array_start_callback, [:string], :void + callback :array_end_callback, [], :void + callback :map_start_callback, [:string], :void + callback :map_end_callback, [], :void - def self.to_hash - ptr = FFI::MemoryPointer.new(:char, Constants::JSON_STRING_MAX_LEN) - success = to_json(ptr, Constants::JSON_STRING_MAX_LEN) - if success != 0 - return {} + class EnumerationCallbacks < FFI::Struct + layout :string, :string_callback, + :integer, :integer_callback, + :boolean, :boolean_callback, + :array_start, :array_start_callback, + :array_end, :array_end_callback, + :map_start, :map_start_callback, + :map_end, :map_end_callback end - JSON.parse(ptr.read_string()) - end - def self.search(*dirs) - # no ruby load paths for cfacter + attach_function :get_facter_version, [], :string + attach_function :load_facts, [:string], :void + attach_function :clear_facts, [], :void + attach_function :search_external, [:string], :void + attach_function :enumerate_facts, [:pointer], :void + attach_function :get_fact_value, [:string, :pointer], :bool end - def self.value(name) - ptr = FFI::MemoryPointer.new(:char, Constants::JSON_STRING_MAX_LEN) - success = c_value(name.to_s, ptr, Constants::JSON_STRING_MAX_LEN) - if success != 0 - return "" - end - ptr.read_string() + FACTER_VERSION = '0.1.0' + + raise LoadError.new("Expected cfacter #{FACTER_VERSION} but found #{FacterLib.get_facter_version}.") if FacterLib.get_facter_version != FACTER_VERSION + + def self.version + FacterLib.get_facter_version end - CFact = Struct.new(:value) + def self.loadfacts + FacterLib.load_facts nil + end - def self.[](name) - CFact.new(value(name)) + def self.clear + FacterLib.clear_facts end - def search_external(dirs) - dirs.each { |dir| c_search_external(dir) } + # This method creates the callbacks used when enumerating facts from cfacter. + # Each callback simply appends the corresponding Ruby type to the hash/array + # being built up during the enumeration. This allows us to effectively copy + # the structure of the facts from cfacter into native Ruby types. + def self.create_enumeration_callbacks(initial) + callbacks = FacterLib::EnumerationCallbacks.new + current = initial + stack = [] + + add = Proc.new do |name, value| + if current.is_a? Array + current << value + else + current[name] = value + end + end + + callbacks[:string] = Proc.new do |name, value| + add.call name, value + end + + callbacks[:integer] = Proc.new do |name, value| + add.call name, value + end + + callbacks[:boolean] = Proc.new do |name, value| + add.call name, (value != 0) + end + + callbacks[:array_start] = Proc.new do |name| + value = [] + add.call name, value + stack.push current + current = value + end + + callbacks[:array_end] = Proc.new { current = stack.pop } + + callbacks[:map_start] = Proc.new do |name| + value = {} + add.call name, value + stack.push current + current = value + end + + # Reuse the callback for array end since it has the same signature and + # performs the same operation. + callbacks[:map_end] = callbacks[:array_end] + + callbacks end - private - attach_function :c_value, :value, [:string, :pointer, :size_t], :int - attach_function :c_search_external, :search_external, [:pointer], :void + def self.to_hash + result = {} + FacterLib.enumerate_facts(self.create_enumeration_callbacks(result)) + result + end + def self.value(name) + # To share the enumeration callbacks with to_hash, pass in an array and return the + # first element, which will be the value of the requested fact. + result = [] + return nil unless FacterLib.get_fact_value(name, self.create_enumeration_callbacks(result)) + result[0] + end end diff --git a/gem/spec/spec_helper.rb b/gem/spec/spec_helper.rb new file mode 100644 index 0000000000..74e29bc650 --- /dev/null +++ b/gem/spec/spec_helper.rb @@ -0,0 +1,55 @@ +require 'rubygems' +require 'mocha' +require 'rspec' +require 'cfacter' +require 'fileutils' +require 'pathname' + +# load shared_context within this project's spec directory +dir = File.expand_path(File.dirname(__FILE__)) +$LOAD_PATH.unshift File.join(dir, 'lib') + +Pathname.glob("#{dir}/shared_contexts/*.rb") do |file| + require file.relative_path_from(Pathname.new(dir)) +end + +module LogSpecOrder + # Log the spec order to a file, but only if the LOG_SPEC_ORDER environment + # variable is set. This could be enabled on Jenkins runs, as it can + # be used with Nick L.'s bisect script to help identify and debug + # order-dependent spec failures. + # + # jpartlow 2013-07-05: this was in puppet and I pulled it into facter because + # I was seeing similar ordering issues in the specs...and needed to bisect them :/ + def self.log_spec_order + if ENV['LOG_SPEC_ORDER'] + File.open("./spec_order.txt", "w") do |logfile| + RSpec.configuration.files_to_run.each { |f| logfile.puts f } + end + end + end +end + +# Executing here rather than after :suite, so that we can get the order output +# even when the issue breaks rspec when specs are first loaded. +LogSpecOrder.log_spec_order + +RSpec.configure do |config| + config.mock_with :mocha + + config.before :each do + CFacter.clear + + # Store any environment variables away to be restored later + @old_env = {} + ENV.each_key {|k| @old_env[k] = ENV[k]} + end + + config.after :each do + # Restore environment variables after execution of each test + @old_env.each_pair {|k, v| ENV[k] = v} + to_remove = ENV.keys.reject {|key| @old_env.include? key } + to_remove.each {|key| ENV.delete key } + end +end + diff --git a/gem/spec/unit/cfacter_spec.rb b/gem/spec/unit/cfacter_spec.rb new file mode 100644 index 0000000000..f81aba691e --- /dev/null +++ b/gem/spec/unit/cfacter_spec.rb @@ -0,0 +1,118 @@ +require 'spec_helper' + +shared_context "enumeration" do + + let(:enumeration_helper) { + helper = lambda do |name, value, callbacks| + if value.is_a? String + callbacks[:string].call name, value + elsif value.is_a? Integer + callbacks[:integer].call name, value + elsif value.is_a?(TrueClass) || value.is_a?(FalseClass) + callbacks[:boolean].call name, (if value then 1 else 0 end) + elsif value.is_a? Array + callbacks[:array_start].call name + value.each do |child| + helper.call '', child, callbacks + end + callbacks[:array_end].call + elsif value.is_a? Hash + callbacks[:map_start].call name + value.each do |k, v| + helper.call k, v, callbacks + end + callbacks[:map_end].call + else + raise 'Unexpected value type.' + end + end + } + + def enumerate(facts) + CFacter::FacterLib.stubs(:enumerate_facts).with do |*args| + facts.each do |k, v| + enumeration_helper.call k, v, *args + end + end + CFacter.to_hash.should eq facts + CFacter::FacterLib.unstub :enumerate_facts + end +end + +describe CFacter do + + it "provides a version" do + CFacter.version.should_not be_nil + end + + it "should return an empty hash initially" do + CFacter.to_hash.should be_empty + end + + it "should return nil for a value initially" do + CFacter.value('cfacterversion').should be_nil + end + + it "contains a matching cfacter version" do + CFacter.loadfacts + version = CFacter.value('cfacterversion') + version.should eq CFacter.version + version.should eq CFacter::FACTER_VERSION + end + + it "should load facts" do + CFacter.loadfacts + CFacter.to_hash.should_not be_empty + end + + it "should clear facts" do + CFacter.loadfacts + CFacter.to_hash.should_not be_empty + CFacter.clear + CFacter.to_hash.should be_empty + end + + describe "should enumerate" do + include_context "enumeration" + + it "string facts" do + enumerate({ + 'fact1' => 'value1', + 'fact2' => 'value2', + 'fact3' => 'value3' + }) + end + + it "integer facts" do + enumerate({ + 'fact1' => 1, + 'fact2' => 2, + 'fact3' => 3 + }) + end + + it "boolean facts" do + enumerate({ + 'fact1' => true, + 'fact2' => false + }) + end + + it "array facts" do + enumerate({ + 'fact1' => [ 'one', 2, 'three' ], + 'fact2' => [ 'one', ['two', 3] ], + 'fact3' => [] + }) + end + + it "hash facts" do + enumerate({ + 'fact1' => { 'array' => [ 'one', 2, 'three' ], 'string' => 'world', 'integer' => 5 }, + 'fact2' => { 'hash' => { 'foo' => 'bar', 'integer' => 1 } }, + 'fact3' => { 'array' => [ { 'foo' => 'bar' }] } + }) + end + end + +end diff --git a/gem/tasks/build.rake b/gem/tasks/build.rake new file mode 100644 index 0000000000..439d5127d9 --- /dev/null +++ b/gem/tasks/build.rake @@ -0,0 +1,14 @@ +require 'rubygems' +require 'rubygems/package_task' + +spec = Gem::Specification.load(File.join(File.dirname(__FILE__), '../cfacter.gemspec')) + +Gem::PackageTask.new(spec) do |pkg| + pkg.need_tar = true +end + +desc "Install the cfacter gem." +task :install => [:gem] do |t| + file = "cfacter-#{spec.version}.gem" + exec "gem install #{File.join(File.dirname(__FILE__), '../pkg', file)}" +end diff --git a/gem/tasks/spec.rake b/gem/tasks/spec.rake new file mode 100644 index 0000000000..5a1b6c37ce --- /dev/null +++ b/gem/tasks/spec.rake @@ -0,0 +1,26 @@ +['rubygems', +'rspec', +'rspec/core/rake_task', +'rcov',].each do |lib| + begin + require lib + rescue LoadError + end +end + +if defined?(RSpec::Core::RakeTask) + desc "Run all specs" + RSpec::Core::RakeTask.new do |t| + t.pattern ='spec/{unit,integration}/**/*_spec.rb' + t.fail_on_error = true + end + + RSpec::Core::RakeTask.new('spec:rcov') do |t| + t.pattern ='spec/{unit,integration}/**/*_spec.rb' + t.fail_on_error = true + if defined?(Rcov) + t.rcov = true + t.rcov_opts = ['--exclude', 'spec/*,test/*,results/*,/usr/lib/*,/usr/local/lib/*,gems/*'] + end + end +end \ No newline at end of file diff --git a/lib/inc/facter/facterlib.h b/lib/inc/facter/facterlib.h index 01144bac57..bec873abd6 100644 --- a/lib/inc/facter/facterlib.h +++ b/lib/inc/facter/facterlib.h @@ -1,18 +1,95 @@ #ifndef FACTER_FACTERLIB_H_ #define FACTER_FACTERLIB_H_ -#include - #ifdef __cplusplus +#include +#include +#include extern "C" { +#else +#include +#include +#include #endif // __cplusplus + /// + /// Gets the facter library version. + /// @return Returns the facter library version as a string. + /// char const* get_facter_version(); - void clear(); - void loadfacts(); - int to_json(char *facts, size_t facts_len); - int value(const char *fact, char *value, size_t value_len); - void search_external(const char *dirs); + + /// + /// Loads and resolves all facts. + /// @param names The comma-delimited list of fact names to resolve. If null, all facts are resolved. + /// + void load_facts(char const* names); + + /// + /// Clears the facts. + /// + void clear_facts(); + + /// + /// Simple structure to store enumeration callbacks. + /// + typedef struct _enumeration_callbacks + { + /// + /// Called when a string value is enumerated. + /// @param name The name of the fact for this value. May be empty if the value is a member of an array. + /// @param value The value of the string fact. + /// + void(*string)(char const* name, char const* value); + /// + /// Called when an integer value is enumerated. + /// @param name The name of the fact for this value. May be empty if the value is a member of an array. + /// @param value The value of the integer fact. + /// + void(*integer)(char const* name, int64_t value); + /// + /// Called when a boolean value is enumerated. + /// @param name The name of the fact for this value. May be empty if the value is a member of an array. + /// @param value The value of the boolean fact (zero is false, non-zero true). + /// + void(*boolean)(char const* name, uint8_t value); + /// + /// Called when an array value has started being enumerated. + /// @param name The name of the fact for this value. May be empty if the value is a member of an array. + /// + void(*array_start)(char const* name); + /// + /// Called when an array value has ended enumeration. + /// + void(*array_end)(); + /// + /// Called when a map value has started being enumerated. + /// @param name The name of the fact for this value. May be empty if the value is a member of an array. + /// + void(*map_start)(char const* name); + /// + /// Called when a map value has ended enumeration. + /// + void(*map_end)(); + } enumeration_callbacks; + + /// + /// Enumerates all facts. + /// @param callbacks The callback functions to use. + /// + void enumerate_facts(enumeration_callbacks* callbacks); + + /// + /// Gets the value of a single fact. + /// @param callbacks The callback functions to use. + /// @return Returns true if the fact exists or false if the fact does not. + /// + bool get_fact_value(char const* name, enumeration_callbacks* callbacks); + + /// + /// Searches the given directories for external facts. + /// @param directories The directories to search for external facts. + /// + void search_external(char const* directories); #ifdef __cplusplus } diff --git a/lib/inc/facter/facts/array_value.hpp b/lib/inc/facter/facts/array_value.hpp index 4649998476..c8a3bd6ad5 100644 --- a/lib/inc/facter/facts/array_value.hpp +++ b/lib/inc/facter/facts/array_value.hpp @@ -43,18 +43,39 @@ namespace facter { namespace facts { */ virtual void to_json(rapidjson::Allocator& allocator, rapidjson::Value& value) const; + /** + * Notifies the appropriate callback based on the type of the value. + * @param name The fact name to pass to the callback. + * @param callbacks The callbacks to use to notify. + */ + virtual void notify(std::string const& name, enumeration_callbacks const* callbacks) const; + /** * Gets the vector of elements in the array. * @return Returns the vector of elements in the array. */ std::vector> const& elements() const { return _elements; } + /** + * Gets the element at the given index. + * @tparam T The expected type of the value. + * @param i The index in the array to get the element at. + * @return Returns the value at the given index or nullptr if the value is not of the expected type. + */ + template T const* get(size_t i) const + { + return dynamic_cast(_elements.at(i).get()); + } + /** * Gets the value at the given index. * @param i The index in the array to get the element at. * @return Returns the value at the given index. */ - value const* operator[](size_t i) const { return _elements.at(i).get(); } + value const* operator[](size_t i) const + { + return _elements.at(i).get(); + } protected: /** diff --git a/lib/inc/facter/facts/map_value.hpp b/lib/inc/facter/facts/map_value.hpp index 44ce8add7f..1cace44e4d 100644 --- a/lib/inc/facter/facts/map_value.hpp +++ b/lib/inc/facter/facts/map_value.hpp @@ -44,12 +44,30 @@ namespace facter { namespace facts { */ virtual void to_json(rapidjson::Allocator& allocator, rapidjson::Value& value) const; + /** + * Notifies the appropriate callback based on the type of the value. + * @param name The fact name to pass to the callback. + * @param callbacks The callbacks to use to notify. + */ + virtual void notify(std::string const& name, enumeration_callbacks const* callbacks) const; + /** * Gets the map of elements in the value. * @return Returns the vector of elements in the array. */ std::map> const& elements() const { return _elements; } + /** + * Gets the value in the map of the given name. + * @tparam T The expected type of the value. + * @param name The name of the value in the map to get. + * @return Returns the value in the map or nullptr if the value is not in the map or expected type. + */ + template T const* get(std::string const& name) const + { + return dynamic_cast(this->operator [](name)); + } + /** * Gets the value in the map of the given name. * @param name The name of the value in the map to get. diff --git a/lib/inc/facter/facts/scalar_value.hpp b/lib/inc/facter/facts/scalar_value.hpp index 9abb365813..809d4e8c18 100644 --- a/lib/inc/facter/facts/scalar_value.hpp +++ b/lib/inc/facter/facts/scalar_value.hpp @@ -48,6 +48,13 @@ namespace facter { namespace facts { */ virtual void to_json(rapidjson::Allocator& allocator, rapidjson::Value& value) const; + /** + * Notifies the appropriate callback based on the type of the value. + * @param name The fact name to pass to the callback. + * @param callbacks The callbacks to use to notify. + */ + virtual void notify(std::string const& name, enumeration_callbacks const* callbacks) const; + /** * Gets the underlying scalar value. * @return Returns the underlying scalar value. @@ -89,6 +96,14 @@ namespace facter { namespace facts { template <> void scalar_value::to_json(rapidjson::Allocator& allocator, rapidjson::Value& value) const; + // Declare the specializations for notification + template <> + void scalar_value::notify(std::string const& name, enumeration_callbacks const* callbacks) const; + template <> + void scalar_value::notify(std::string const& name, enumeration_callbacks const* callbacks) const; + template <> + void scalar_value::notify(std::string const& name, enumeration_callbacks const* callbacks) const; + // Declare the specializations for YAML output template <> YAML::Emitter& scalar_value::write(YAML::Emitter& emitter) const; diff --git a/lib/inc/facter/facts/value.hpp b/lib/inc/facter/facts/value.hpp index 14124d08ec..701e72a259 100644 --- a/lib/inc/facter/facts/value.hpp +++ b/lib/inc/facter/facts/value.hpp @@ -23,6 +23,10 @@ namespace rapidjson { typedef GenericDocument, MemoryPoolAllocator> Document; } +extern "C" { + typedef struct _enumeration_callbacks enumeration_callbacks; +} + namespace facter { namespace facts { /** @@ -55,6 +59,13 @@ namespace facter { namespace facts { */ virtual void to_json(rapidjson::Allocator& allocator, rapidjson::Value& value) const = 0; + /** + * Notifies the appropriate callback based on the type of the value. + * @param name The fact name to pass to the callback. + * @param callbacks The callbacks to use to notify. + */ + virtual void notify(std::string const& name, enumeration_callbacks const* callbacks) const = 0; + protected: /** * Writes the value to the given stream. diff --git a/lib/src/facterlib.cc b/lib/src/facterlib.cc index c149e880a4..bd52140685 100644 --- a/lib/src/facterlib.cc +++ b/lib/src/facterlib.cc @@ -1,11 +1,19 @@ #include #include - -#include "rapidjson/document.h" -#include "rapidjson/prettywriter.h" -#include "rapidjson/stringbuffer.h" +#include +#include +#include +#include +#include +#include +#include using namespace std; +using namespace facter::util; +using namespace facter::facts; +using namespace log4cxx; + +static unique_ptr g_facts; extern "C" { char const* get_facter_version() @@ -13,27 +21,64 @@ extern "C" { return LIBFACTER_VERSION; } - void loadfacts() + void load_facts(char const* names) { - // This is a no-op of the fact map + // TODO: figure out a callback mechanism for log output + // Until then, disable logging when using the C interface + if (Logger::getRootLogger()->getAllAppenders().size() == 0) { + Logger::getRootLogger()->setLevel(Level::getOff()); + } + + g_facts.reset(new fact_map()); + + set requested_facts; + if (names) { + for (auto& name : split(names, ',')) { + requested_facts.emplace(trim(to_lower(move(name)))); + } + } + g_facts->resolve(requested_facts); } - int to_json(char *facts_json, size_t facts_len) + void clear_facts() { - // TODO: re-implement this with support for structured facts - strncpy(facts_json, "", facts_len); - return 0; + if (!g_facts) { + return; + } + g_facts.reset(nullptr); } - int value(const char *fact, char *value, size_t value_len) + void enumerate_facts(enumeration_callbacks* callbacks) { - // TODO: reimplement this with support for structured facts - strncpy(value, "", value_len); - return 0; + if (!g_facts || !callbacks) { + return; + } + g_facts->each([&](string const& name, value const* val) { + val->notify(name, callbacks); + return true; + }); + } + + bool get_fact_value(char const* name, enumeration_callbacks* callbacks) + { + if (!g_facts || !name || !callbacks) { + return false; + } + + // Get the fact + string fact = trim(to_lower(name)); + auto val = (*g_facts)[fact]; + if (!val) { + return false; + } + + // Notify of the fact value + val->notify(fact, callbacks); + return true; } - void search_external(const char *dirs) + void search_external(char const* directories) { - // TODO + // TODO: implement } } diff --git a/lib/src/facts/array_value.cc b/lib/src/facts/array_value.cc index 4054b7690f..d1847290b1 100644 --- a/lib/src/facts/array_value.cc +++ b/lib/src/facts/array_value.cc @@ -1,4 +1,5 @@ #include +#include #include #include @@ -23,6 +24,29 @@ namespace facter { namespace facts { } } + void array_value::notify(string const& name, enumeration_callbacks const* callbacks) const + { + if (!callbacks) { + return; + } + + if (callbacks->array_start) { + callbacks->array_start(name.c_str()); + } + + // Call notify on each element in the array + for (auto const& element : _elements) { + if (!element) { + continue; + } + element->notify({}, callbacks); + } + + if (callbacks->array_end) { + callbacks->array_end(); + } + } + ostream& array_value::write(ostream& os) const { // Write out the elements in the array diff --git a/lib/src/facts/fact_map.cc b/lib/src/facts/fact_map.cc index 6140bf85b1..d4aa3a81cd 100644 --- a/lib/src/facts/fact_map.cc +++ b/lib/src/facts/fact_map.cc @@ -55,6 +55,11 @@ namespace facter { namespace facts { void fact_map::add(string&& name, unique_ptr&& value) { + if (!value) { + LOG_DEBUG("fact %1% resolved to null and will be ignored.", name); + return; + } + // Search for the fact first auto const& it = _facts.lower_bound(name); if (it != _facts.end() && !(_facts.key_comp()(name, it->first))) { @@ -63,11 +68,7 @@ namespace facter { namespace facts { if (LOG_IS_DEBUG_ENABLED()) { ostringstream ss; - if (value) { - ss << *value; - } else { - ss << ""; - } + ss << *value; LOG_DEBUG("fact %1% has resolved to \"%2%\".", name, ss.str()); } @@ -120,6 +121,9 @@ namespace facter { namespace facts { void fact_map::resolve(set const& facts) { + if (resolved()) { + return; + } if (!facts.empty()) { // Resolve the given facts for (auto const& fact : facts) { @@ -165,7 +169,7 @@ namespace facter { namespace facts { void fact_map::each(function func) const { find_if(begin(_facts), end(_facts), [&func](fact_map_type::value_type const& it) { - return func(it.first, it.second.get()); + return !func(it.first, it.second.get()); }); } @@ -190,10 +194,6 @@ namespace facter { namespace facts { document.SetObject(); for (auto const& kvp : _facts) { - if (!kvp.second) { - continue; - } - rapidjson::Value value; kvp.second->to_json(document.GetAllocator(), value); document.AddMember(kvp.first.c_str(), value, document.GetAllocator()); @@ -210,9 +210,6 @@ namespace facter { namespace facts { Emitter emitter(stream); emitter << BeginMap; for (auto const& kvp : _facts) { - if (!kvp.second) { - continue; - } emitter << Key << kvp.first; emitter << YAML::Value << *kvp.second; } @@ -262,10 +259,6 @@ namespace facter { namespace facts { // Print all facts in the map bool first = true; for (auto const& kvp : facts._facts) { - if (!kvp.second) { - continue; - } - if (first) { first = false; } else { diff --git a/lib/src/facts/map_value.cc b/lib/src/facts/map_value.cc index 2c8f2611d4..b4bba18a64 100644 --- a/lib/src/facts/map_value.cc +++ b/lib/src/facts/map_value.cc @@ -1,4 +1,5 @@ #include +#include #include #include @@ -32,6 +33,29 @@ namespace facter { namespace facts { } } + void map_value::notify(string const& name, enumeration_callbacks const* callbacks) const + { + if (!callbacks) { + return; + } + + if (callbacks->map_start) { + callbacks->map_start(name.c_str()); + } + + // Call notify on each element in the array + for (auto const& element : _elements) { + if (!element.second) { + continue; + } + element.second->notify(element.first, callbacks); + } + + if (callbacks->map_end) { + callbacks->map_end(); + } + } + ostream& map_value::write(ostream& os) const { // Write out the elements in the map diff --git a/lib/src/facts/scalar_value.cc b/lib/src/facts/scalar_value.cc index ef77b271e5..7edc884493 100644 --- a/lib/src/facts/scalar_value.cc +++ b/lib/src/facts/scalar_value.cc @@ -1,4 +1,5 @@ #include +#include #include #include @@ -26,6 +27,30 @@ namespace facter { namespace facts { value.SetBool(_value); } + template <> + void scalar_value::notify(string const& name, enumeration_callbacks const* callbacks) const + { + if (callbacks && callbacks->string) { + callbacks->string(name.c_str(), _value.c_str()); + } + } + + template <> + void scalar_value::notify(string const& name, enumeration_callbacks const* callbacks) const + { + if (callbacks && callbacks->integer) { + callbacks->integer(name.c_str(), _value); + } + } + + template <> + void scalar_value::notify(string const& name, enumeration_callbacks const* callbacks) const + { + if (callbacks && callbacks->boolean) { + callbacks->boolean(name.c_str(), _value ? 1 : 0); + } + } + template <> Emitter& scalar_value::write(Emitter& emitter) const { diff --git a/lib/tests/facts/array_value.cc b/lib/tests/facts/array_value.cc index 38d3255423..19a8839d9c 100644 --- a/lib/tests/facts/array_value.cc +++ b/lib/tests/facts/array_value.cc @@ -27,19 +27,19 @@ TEST(facter_facts_array_value, vector_constructor) { array_value value(move(elements)); ASSERT_EQ(3u, value.elements().size()); - auto str = dynamic_cast(value[0]); + auto str = value.get(0); ASSERT_NE(nullptr, str); ASSERT_EQ("1", str->value()); - auto integer = dynamic_cast(value[1]); + auto integer = value.get(1); ASSERT_NE(nullptr, integer); ASSERT_EQ(2, integer->value()); - auto array = dynamic_cast(value[2]); + auto array = value.get(2); ASSERT_NE(nullptr, array); ASSERT_EQ(1u, array->elements().size()); - str = dynamic_cast((*array)[0]); + str = array->get(0); ASSERT_NE(nullptr, str); ASSERT_EQ("child", str->value()); } diff --git a/lib/tests/facts/map_value.cc b/lib/tests/facts/map_value.cc index 19a23c598c..e7d98d831e 100644 --- a/lib/tests/facts/map_value.cc +++ b/lib/tests/facts/map_value.cc @@ -33,26 +33,26 @@ TEST(facter_facts_map_value, map_constructor) { map_value value(move(elements)); ASSERT_EQ(4u, value.elements().size()); - auto str = dynamic_cast(value["string"]); + auto str = value.get("string"); ASSERT_NE(nullptr, str); ASSERT_EQ("hello", str->value()); - auto integer = dynamic_cast(value["integer"]); + auto integer = value.get("integer"); ASSERT_NE(nullptr, integer); ASSERT_EQ(5, integer->value()); - auto array = dynamic_cast(value["array"]); + auto array = value.get("array"); ASSERT_NE(nullptr, array); ASSERT_EQ(2u, array->elements().size()); - str = dynamic_cast((*array)[0]); + str = array->get(0); ASSERT_EQ("1", str->value()); - integer = dynamic_cast((*array)[1]); + integer = array->get(1); ASSERT_EQ(2u, integer->value()); - auto mapval = dynamic_cast(value["map"]); + auto mapval = value.get("map"); ASSERT_NE(nullptr, mapval); ASSERT_EQ(1u, mapval->elements().size()); - str = dynamic_cast((*mapval)["foo"]); + str = mapval->get("foo"); ASSERT_NE(nullptr, str); ASSERT_EQ("bar", str->value()); } From 5bc59a238fe5dd6a86d367d3b9da1f5a244a2615 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Tue, 20 May 2014 15:05:55 -0700 Subject: [PATCH 1872/3753] (maint) Use Apache 2.0 license. Adding a LICENSE file like we have for other Puppet Labs projects. cfacter is now under the Apache 2.0 License. --- LICENSE | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000..964dceb9fd --- /dev/null +++ b/LICENSE @@ -0,0 +1,17 @@ + cfacter - Tool for collecting system facts. + + Copyright (C) 2014 Puppet Labs Inc + + Puppet Labs can be contacted at: info@puppetlabs.com + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. From 5bfa41076dc7c7f7d814517f985213c20889b6c9 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Thu, 15 May 2014 17:25:48 -0700 Subject: [PATCH 1873/3753] (maint) Change static maps to vector of tuples. There's a few places where we declare a static map but then only iterate over the map, effectively treating it as a vector. Replacing those instances with a vector>. --- lib/src/facts/linux/dmi_resolver.cc | 61 +++++++------- .../facts/linux/operating_system_resolver.cc | 82 ++++++++++--------- .../facts/posix/operating_system_resolver.cc | 2 +- 3 files changed, 76 insertions(+), 69 deletions(-) diff --git a/lib/src/facts/linux/dmi_resolver.cc b/lib/src/facts/linux/dmi_resolver.cc index 0397f786df..4598e4d092 100644 --- a/lib/src/facts/linux/dmi_resolver.cc +++ b/lib/src/facts/linux/dmi_resolver.cc @@ -6,6 +6,8 @@ #include #include #include +#include +#include using namespace std; using namespace facter::util; @@ -18,56 +20,59 @@ namespace facter { namespace facts { namespace linux { void dmi_resolver::resolve_facts(fact_map& facts) { - static map dmi_files { - { string(fact::bios_vendor), "/sys/class/dmi/id/bios_vendor" }, - { string(fact::bios_version), "/sys/class/dmi/id/bios_version" }, - { string(fact::bios_release_date), "/sys/class/dmi/id/bios_date" }, - { string(fact::board_manufacturer), "/sys/class/dmi/id/board_vendor" }, - { string(fact::board_product_name), "/sys/class/dmi/id/board_name" }, - { string(fact::board_serial_number), "/sys/class/dmi/id/board_serial" }, - { string(fact::manufacturer), "/sys/class/dmi/id/sys_vendor" }, - { string(fact::product_name), "/sys/class/dmi/id/product_name" }, - { string(fact::serial_number), "/sys/class/dmi/id/product_serial" }, - { string(fact::product_uuid), "/sys/class/dmi/id/product_uuid" }, - { string(fact::chassis_type), "/sys/class/dmi/id/chassis_type" }, + static vector> const dmi_files { + make_tuple(string(fact::bios_vendor), "/sys/class/dmi/id/bios_vendor"), + make_tuple(string(fact::bios_version), "/sys/class/dmi/id/bios_version"), + make_tuple(string(fact::bios_release_date), "/sys/class/dmi/id/bios_date"), + make_tuple(string(fact::board_manufacturer), "/sys/class/dmi/id/board_vendor"), + make_tuple(string(fact::board_product_name), "/sys/class/dmi/id/board_name"), + make_tuple(string(fact::board_serial_number), "/sys/class/dmi/id/board_serial"), + make_tuple(string(fact::manufacturer), "/sys/class/dmi/id/sys_vendor"), + make_tuple(string(fact::product_name), "/sys/class/dmi/id/product_name"), + make_tuple(string(fact::serial_number), "/sys/class/dmi/id/product_serial"), + make_tuple(string(fact::product_uuid), "/sys/class/dmi/id/product_uuid"), + make_tuple(string(fact::chassis_type), "/sys/class/dmi/id/chassis_type"), }; - for (auto const& kvp : dmi_files) { + for (auto const& dmi_file : dmi_files) { + auto fact_name = get<0>(dmi_file); + auto const& filename = get<1>(dmi_file); + bs::error_code ec; - if (!is_regular_file(kvp.second, ec)) { - LOG_DEBUG("%1%: %2%: %3% fact is unavailable.", kvp.second, ec.message(), kvp.first); + if (!is_regular_file(filename, ec)) { + LOG_DEBUG("%1%: %2%: %3% fact is unavailable.", filename, ec.message(), fact_name); continue; } string value; - if (!file::read(kvp.second, value)) { - LOG_DEBUG("%1%: permission denied: %2% fact is unavailable.", kvp.second, kvp.first); + if (!file::read(filename, value)) { + LOG_DEBUG("%1%: permission denied: %2% fact is unavailable.", filename, fact_name); continue; } trim(value); // If this is the chassis fact, get the description string - if (kvp.first == fact::chassis_type) { + if (fact_name == fact::chassis_type) { value = get_chassis_description(value); } - facts.add(string(kvp.first), make_value(move(value))); + facts.add(move(fact_name), make_value(move(value))); } } string dmi_resolver::get_chassis_description(string const& type) { - static map descriptions = { - { "1", "Other" }, + static map const descriptions = { + { "1", "Other" }, // 2 is Unknown, which we'll output if it's not in the map anyway - { "3", "Desktop" }, - { "4", "Low Profile Desktop" }, - { "5", "Pizza Box" }, - { "6", "Mini Tower" }, - { "7", "Tower" }, - { "8", "Portable" }, - { "9", "Laptop" }, + { "3", "Desktop" }, + { "4", "Low Profile Desktop" }, + { "5", "Pizza Box" }, + { "6", "Mini Tower" }, + { "7", "Tower" }, + { "8", "Portable" }, + { "9", "Laptop" }, { "10", "Notebook" }, { "11", "Hand Held" }, { "12", "Docking Station" }, diff --git a/lib/src/facts/linux/operating_system_resolver.cc b/lib/src/facts/linux/operating_system_resolver.cc index ad125c40e3..62d4170c2e 100644 --- a/lib/src/facts/linux/operating_system_resolver.cc +++ b/lib/src/facts/linux/operating_system_resolver.cc @@ -8,8 +8,10 @@ #include #include #include -#include #include +#include +#include +#include using namespace std; using namespace facter::util; @@ -72,7 +74,7 @@ namespace facter { namespace facts { namespace linux { } // Map of release files that contain a "release X.X.X" on the first line - static map release_files = { + static map const release_files = { { string(os::centos), string(release_file::redhat) }, { string(os::redhat), string(release_file::redhat) }, { string(os::scientific), string(release_file::redhat) }, @@ -268,22 +270,22 @@ namespace facter { namespace facts { namespace linux { { bs::error_code ec; if (is_regular_file(release_file::redhat, ec)) { - static map regexs { - { "(?i)centos", string(os::centos) }, - { "(?i)scientific linux CERN", string(os::scientific_cern) }, - { "(?i)scientific linux release", string(os::scientific) }, - { "(?im)^cloudlinux", string(os::cloud_linux) }, - { "(?i)Ascendos", string(os::ascendos) }, - { "(?im)^XenServer", string(os::xen_server) }, - { "XCP", string(os::zen_cloud_platform) }, - { "(?im)^Parallels Server Bare Metal", string(os::psbm) }, - { "(?m)^Fedora release", string(os::fedora) }, + static vector> const regexs { + make_tuple("(?i)centos", string(os::centos)), + make_tuple("(?i)scientific linux CERN", string(os::scientific_cern)), + make_tuple("(?i)scientific linux release", string(os::scientific)), + make_tuple("(?im)^cloudlinux", string(os::cloud_linux)), + make_tuple("(?i)Ascendos", string(os::ascendos)), + make_tuple("(?im)^XenServer", string(os::xen_server)), + make_tuple("XCP", string(os::zen_cloud_platform)), + make_tuple("(?im)^Parallels Server Bare Metal", string(os::psbm)), + make_tuple("(?m)^Fedora release", string(os::fedora)), }; string contents = trim(file::read(release_file::redhat)); - for (auto const& kvp : regexs) { - if (RE2::PartialMatch(contents, kvp.first)) { - return kvp.second; + for (auto const& regex : regexs) { + if (RE2::PartialMatch(contents, get<0>(regex))) { + return get<1>(regex); } } return os::redhat; @@ -295,16 +297,16 @@ namespace facter { namespace facts { namespace linux { { bs::error_code ec; if (is_regular_file(release_file::suse, ec)) { - static map regexs { - { "(?im)^SUSE LINUX Enterprise Server", string(os::suse_enterprise_server) }, - { "(?im)^SUSE LINUX Enterprise Desktop", string(os::suse_enterprise_desktop) }, - { "(?im)^openSUSE", string(os::open_suse) }, + static vector> const regexs { + make_tuple("(?im)^SUSE LINUX Enterprise Server", string(os::suse_enterprise_server)), + make_tuple("(?im)^SUSE LINUX Enterprise Desktop", string(os::suse_enterprise_desktop)), + make_tuple("(?im)^openSUSE", string(os::open_suse)), }; string contents = trim(file::read(release_file::suse)); - for (auto const& kvp : regexs) { - if (RE2::PartialMatch(contents, kvp.first)) { - return kvp.second; + for (auto const& regex : regexs) { + if (RE2::PartialMatch(contents, get<0>(regex))) { + return get<1>(regex); } } return os::suse; @@ -314,27 +316,27 @@ namespace facter { namespace facts { namespace linux { string operating_system_resolver::check_other_linux() { - static map files { - { string(release_file::openwrt), string(os::openwrt) }, - { string(release_file::gentoo), string(os::gentoo) }, - { string(release_file::mandriva), string(os::mandriva) }, - { string(release_file::mandrake), string(os::mandrake) }, - { string(release_file::meego), string(os::meego) }, - { string(release_file::archlinux), string(os::archlinux) }, - { string(release_file::oracle_linux), string(os::oracle_linux) }, - { string(release_file::vmware_esx), string(os::vmware_esx) }, - { string(release_file::bluewhite), string(os::bluewhite) }, - { string(release_file::slack_amd64), string(os::slack_amd64) }, - { string(release_file::slackware), string(os::slackware) }, - { string(release_file::alpine), string(os::alpine) }, - { string(release_file::mageia), string(os::mageia) }, - { string(release_file::amazon), string(os::amazon) }, + static vector> const files { + make_tuple(string(release_file::openwrt), string(os::openwrt)), + make_tuple(string(release_file::gentoo), string(os::gentoo)), + make_tuple(string(release_file::mandriva), string(os::mandriva)), + make_tuple(string(release_file::mandrake), string(os::mandrake)), + make_tuple(string(release_file::meego), string(os::meego)), + make_tuple(string(release_file::archlinux), string(os::archlinux)), + make_tuple(string(release_file::oracle_linux), string(os::oracle_linux)), + make_tuple(string(release_file::vmware_esx), string(os::vmware_esx)), + make_tuple(string(release_file::bluewhite), string(os::bluewhite)), + make_tuple(string(release_file::slack_amd64), string(os::slack_amd64)), + make_tuple(string(release_file::slackware), string(os::slackware)), + make_tuple(string(release_file::alpine), string(os::alpine)), + make_tuple(string(release_file::mageia), string(os::mageia)), + make_tuple(string(release_file::amazon), string(os::amazon)), }; - for (auto const& kvp : files) { + for (auto const& file : files) { bs::error_code ec; - if (is_regular_file(kvp.first, ec)) { - return kvp.second; + if (is_regular_file(get<0>(file), ec)) { + return get<1>(file); } } return {}; diff --git a/lib/src/facts/posix/operating_system_resolver.cc b/lib/src/facts/posix/operating_system_resolver.cc index 773e0bd10b..8579149e7f 100644 --- a/lib/src/facts/posix/operating_system_resolver.cc +++ b/lib/src/facts/posix/operating_system_resolver.cc @@ -35,7 +35,7 @@ namespace facter { namespace facts { namespace posix { auto os = facts.get(fact::operating_system); string value; if (os) { - static map systems = { + static map const systems = { { string(os::redhat), string(os_family::redhat) }, { string(os::fedora), string(os_family::redhat) }, { string(os::centos), string(os_family::redhat) }, From 495eff19158092d836e01dbeb41dc5b70e59e767 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Fri, 16 May 2014 12:33:34 -0700 Subject: [PATCH 1874/3753] (maint) Add double_value to the facter type system. Adding a typedef scalar_value to double_value. Adding function to handle converting to a JSON number value. Adding tests for double_value. --- gem/lib/cfacter.rb | 6 ++++ gem/spec/unit/cfacter_spec.rb | 11 +++++++ lib/inc/facter/facterlib.h | 6 ++++ lib/inc/facter/facts/scalar_value.hpp | 7 ++++- lib/src/facts/array_value.cc | 1 + lib/src/facts/scalar_value.cc | 15 ++++++++++ lib/tests/CMakeLists.txt | 1 + lib/tests/facts/double_value.cc | 41 +++++++++++++++++++++++++++ 8 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 lib/tests/facts/double_value.cc diff --git a/gem/lib/cfacter.rb b/gem/lib/cfacter.rb index 0d68efe38d..5a29fed20c 100644 --- a/gem/lib/cfacter.rb +++ b/gem/lib/cfacter.rb @@ -15,6 +15,7 @@ module FacterLib callback :string_callback, [:string, :string], :void callback :integer_callback, [:string, :int64], :void callback :boolean_callback, [:string, :uint8], :void + callback :double_callback, [:string, :double], :void callback :array_start_callback, [:string], :void callback :array_end_callback, [], :void callback :map_start_callback, [:string], :void @@ -24,6 +25,7 @@ class EnumerationCallbacks < FFI::Struct layout :string, :string_callback, :integer, :integer_callback, :boolean, :boolean_callback, + :double, :double_callback, :array_start, :array_start_callback, :array_end, :array_end_callback, :map_start, :map_start_callback, @@ -83,6 +85,10 @@ def self.create_enumeration_callbacks(initial) add.call name, (value != 0) end + callbacks[:double] = Proc.new do |name, value| + add.call name, value + end + callbacks[:array_start] = Proc.new do |name| value = [] add.call name, value diff --git a/gem/spec/unit/cfacter_spec.rb b/gem/spec/unit/cfacter_spec.rb index f81aba691e..c7c18514a6 100644 --- a/gem/spec/unit/cfacter_spec.rb +++ b/gem/spec/unit/cfacter_spec.rb @@ -10,6 +10,8 @@ callbacks[:integer].call name, value elsif value.is_a?(TrueClass) || value.is_a?(FalseClass) callbacks[:boolean].call name, (if value then 1 else 0 end) + elsif value.is_a? Float + callbacks[:double].call name, value elsif value.is_a? Array callbacks[:array_start].call name value.each do |child| @@ -98,6 +100,15 @@ def enumerate(facts) }) end + it "double facts" do + enumerate({ + 'fact1' => 123.456, + 'fact2' => 654.321, + 'fact3' => Float::MIN, + 'fact4' => Float::MAX + }) + end + it "array facts" do enumerate({ 'fact1' => [ 'one', 2, 'three' ], diff --git a/lib/inc/facter/facterlib.h b/lib/inc/facter/facterlib.h index bec873abd6..6988d1f602 100644 --- a/lib/inc/facter/facterlib.h +++ b/lib/inc/facter/facterlib.h @@ -53,6 +53,12 @@ extern "C" { /// void(*boolean)(char const* name, uint8_t value); /// + /// Called when a double value is enumerated. + /// @param name The name of the fact for this value. May be empty if the value is a member of an array. + /// @param value The value of the double fact. + /// + void(*dbl)(char const* name, double value); + /// /// Called when an array value has started being enumerated. /// @param name The name of the fact for this value. May be empty if the value is a member of an array. /// diff --git a/lib/inc/facter/facts/scalar_value.hpp b/lib/inc/facter/facts/scalar_value.hpp index 809d4e8c18..1bf33b45d2 100644 --- a/lib/inc/facter/facts/scalar_value.hpp +++ b/lib/inc/facter/facts/scalar_value.hpp @@ -95,6 +95,8 @@ namespace facter { namespace facts { void scalar_value::to_json(rapidjson::Allocator& allocator, rapidjson::Value& value) const; template <> void scalar_value::to_json(rapidjson::Allocator& allocator, rapidjson::Value& value) const; + template <> + void scalar_value::to_json(rapidjson::Allocator& allocator, rapidjson::Value& value) const; // Declare the specializations for notification template <> @@ -103,6 +105,8 @@ namespace facter { namespace facts { void scalar_value::notify(std::string const& name, enumeration_callbacks const* callbacks) const; template <> void scalar_value::notify(std::string const& name, enumeration_callbacks const* callbacks) const; + template <> + void scalar_value::notify(std::string const& name, enumeration_callbacks const* callbacks) const; // Declare the specializations for YAML output template <> @@ -112,13 +116,14 @@ namespace facter { namespace facts { extern template struct scalar_value; extern template struct scalar_value; extern template struct scalar_value; + extern template struct scalar_value; // Typedef the common instantiation typedef scalar_value string_value; typedef scalar_value integer_value; typedef scalar_value boolean_value; + typedef scalar_value double_value; }} // namespace facter::facts #endif // FACTER_FACTS_SCALAR_VALUE_HPP_ - diff --git a/lib/src/facts/array_value.cc b/lib/src/facts/array_value.cc index d1847290b1..7e49c898c0 100644 --- a/lib/src/facts/array_value.cc +++ b/lib/src/facts/array_value.cc @@ -12,6 +12,7 @@ namespace facter { namespace facts { void array_value::to_json(Allocator& allocator, rapidjson::Value& value) const { value.SetArray(); + value.Reserve(_elements.size(), allocator); for (auto const& element : _elements) { if (!element) { diff --git a/lib/src/facts/scalar_value.cc b/lib/src/facts/scalar_value.cc index 7edc884493..ade5b916f0 100644 --- a/lib/src/facts/scalar_value.cc +++ b/lib/src/facts/scalar_value.cc @@ -27,6 +27,12 @@ namespace facter { namespace facts { value.SetBool(_value); } + template <> + void scalar_value::to_json(Allocator& allocator, rapidjson::Value& value) const + { + value.SetDouble(_value); + } + template <> void scalar_value::notify(string const& name, enumeration_callbacks const* callbacks) const { @@ -51,6 +57,14 @@ namespace facter { namespace facts { } } + template <> + void scalar_value::notify(string const& name, enumeration_callbacks const* callbacks) const + { + if (callbacks && callbacks->dbl) { + callbacks->dbl(name.c_str(), _value); + } + } + template <> Emitter& scalar_value::write(Emitter& emitter) const { @@ -65,5 +79,6 @@ namespace facter { namespace facts { template struct scalar_value; template struct scalar_value; template struct scalar_value; + template struct scalar_value; }} // namespace facter::facts diff --git a/lib/tests/CMakeLists.txt b/lib/tests/CMakeLists.txt index abfe5d4ba3..33e7001adc 100644 --- a/lib/tests/CMakeLists.txt +++ b/lib/tests/CMakeLists.txt @@ -14,6 +14,7 @@ set(LIBFACTER_TESTS_COMMON_SOURCES "${CMAKE_CURRENT_LIST_DIR}/environment.cc" "${CMAKE_CURRENT_LIST_DIR}/facts/array_value.cc" "${CMAKE_CURRENT_LIST_DIR}/facts/boolean_value.cc" + "${CMAKE_CURRENT_LIST_DIR}/facts/double_value.cc" "${CMAKE_CURRENT_LIST_DIR}/facts/integer_value.cc" "${CMAKE_CURRENT_LIST_DIR}/facts/map_value.cc" "${CMAKE_CURRENT_LIST_DIR}/facts/string_value.cc" diff --git a/lib/tests/facts/double_value.cc b/lib/tests/facts/double_value.cc new file mode 100644 index 0000000000..226443dc31 --- /dev/null +++ b/lib/tests/facts/double_value.cc @@ -0,0 +1,41 @@ +#include +#include +#include +#include +#include + +using namespace std; +using namespace facter::facts; +using namespace rapidjson; +using namespace YAML; + +TEST(facter_facts_double_value, constructor) { + double_value foo(42.0); + ASSERT_EQ(42.0, foo.value()); +} + +TEST(facter_facts_double_value, to_json) { + double_value value(1337.1337); + + rapidjson::Value json_value; + MemoryPoolAllocator<> allocator; + value.to_json(allocator, json_value); + ASSERT_TRUE(json_value.IsNumber()); + ASSERT_EQ(1337.1337, json_value.GetDouble()); +} + +TEST(facter_facts_double_value, insertion_operator) { + double_value value(123.456); + + ostringstream stream; + stream << value; + ASSERT_EQ("123.456", stream.str()); +} + +TEST(facter_facts_double_value, yaml_insertion_operator) { + double_value value(123.456); + + Emitter emitter; + emitter << value; + ASSERT_EQ("123.456", string(emitter.c_str())); +} From d1a08cb66f62fae4d701e9a88ddffea521badee4 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Wed, 21 May 2014 21:45:00 -0700 Subject: [PATCH 1875/3753] (maint) Install facter header files. Adding facter header files to install target. This allows users to build against libfacter from a C++11 program. Updating README with an example program. Deleting vender README as it's outdated and the information is present in the root README. --- README.md | 31 +++++++++++++++++++++++++++++-- lib/CMakeLists.txt | 1 + vendor/README.md | 8 -------- 3 files changed, 30 insertions(+), 10 deletions(-) delete mode 100644 vendor/README.md diff --git a/README.md b/README.md index a946d74ed9..b32e752bfd 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ You can install cfacter into your system: $ cd release $ make && sudo make install -By default, cfacter will install into `/usr/local/bin` and `/usr/local/lib`. +By default, cfacter will install files into `/usr/local/bin`, `/usr/local/lib`, and `/usr/local/include`. To install to a different location, set the install prefix: @@ -105,7 +105,7 @@ To install to a different location, set the install prefix: $ cmake -DCMAKE_INSTALL_PREFIX=~ .. $ make clean install -This would install cfacter into `~/bin` and `~/lib`. +This would install cfacter into `~/bin`, `~/lib`, and `~/include`. To install the gem (assumes gem is already built): @@ -122,3 +122,30 @@ Run the following command to remove files that were previously installed: To uninstall the gem: $ gem uninstall cfacter + +Using The C++11 API +------------------- + +This section assumes that cfacter has been installed into the system. + +Here's a simple example of using the C++11 API to output all facts. + +```C++ +#include +#include + +using namespace std; +using namespace facter::facts; + +int main() { + fact_map facts; + facts.resolve(); + facts.resolve_external(); + cout << facts << endl; +} +``` + +To build the above, link with libfacter: + + $ g++ example.cc -o myfacter --std=c++11 -lfacter + $ ./myfacter \ No newline at end of file diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index bf54b5424a..3aad96f443 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -76,6 +76,7 @@ endif() add_library(libfacter SHARED ${LIBFACTER_COMMON_SOURCES} ${LIBFACTER_POSIX_SOURCES} ${LIBFACTER_PLATFORM_SOURCES}) set_target_properties(libfacter PROPERTIES PREFIX "" VERSION "${LIBFACTER_VERSION_MAJOR}.${LIBFACTER_VERSION_MINOR}.${LIBFACTER_VERSION_PATCH}") install(TARGETS libfacter DESTINATION lib) +install(DIRECTORY inc/facter DESTINATION include) # Set include directories include_directories( diff --git a/vendor/README.md b/vendor/README.md deleted file mode 100644 index 9fe8521485..0000000000 --- a/vendor/README.md +++ /dev/null @@ -1,8 +0,0 @@ -Vendor Libraries ----------------- - -This directory contains third-party vendor libraries. - -The following libraries are used by cfacter: -* [re2](https://code.google.com/p/re2/) - Google's regular expression library -* [rapidjson](https://code.google.com/p/rapidjson) - A fast JSON parsing/generating library. \ No newline at end of file From 44de08dd832cc9b358d1c290ba366dbb1ded27fd Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Fri, 16 May 2014 17:17:23 -0700 Subject: [PATCH 1876/3753] (CFACT-12) Implement external facts in YAML files. Implementing external fact support for facts defined in YAML files. Adding --external-dir and no-external-dir options to cfacter. --- exe/cfacter.cc | 16 +- lib/CMakeLists.txt | 2 + lib/inc/facter/facts/external/resolver.hpp | 41 +++++ .../facter/facts/external/yaml_resolver.hpp | 24 +++ lib/inc/facter/facts/fact_map.hpp | 27 +-- lib/inc/facter/facts/scalar_value.hpp | 6 +- lib/src/facterlib.cc | 4 +- lib/src/facts/array_value.cc | 12 +- lib/src/facts/external/yaml_resolver.cc | 99 +++++++++++ lib/src/facts/fact_map.cc | 130 ++++++++++++-- lib/src/facts/linux/block_device_resolver.cc | 2 +- lib/src/facts/map_value.cc | 23 ++- lib/src/facts/posix/platform.cc | 26 +++ lib/src/facts/scalar_value.cc | 8 + lib/tests/CMakeLists.txt | 2 + lib/tests/facts/array_value.cc | 2 +- lib/tests/facts/external/yaml_resolver.cc | 54 ++++++ lib/tests/facts/fact_map.cc | 167 ++++++++++++++++++ lib/tests/facts/map_value.cc | 2 +- .../fixtures/facts/external/yaml/facts.yaml | 11 ++ .../fixtures/facts/external/yaml/invalid.yaml | 1 + 21 files changed, 614 insertions(+), 45 deletions(-) create mode 100644 lib/inc/facter/facts/external/resolver.hpp create mode 100644 lib/inc/facter/facts/external/yaml_resolver.hpp create mode 100644 lib/src/facts/external/yaml_resolver.cc create mode 100644 lib/src/facts/posix/platform.cc create mode 100644 lib/tests/facts/external/yaml_resolver.cc create mode 100644 lib/tests/facts/fact_map.cc create mode 100644 lib/tests/fixtures/facts/external/yaml/facts.yaml create mode 100644 lib/tests/fixtures/facts/external/yaml/invalid.yaml diff --git a/exe/cfacter.cc b/exe/cfacter.cc index f130b379e1..a34a59c8fd 100644 --- a/exe/cfacter.cc +++ b/exe/cfacter.cc @@ -117,8 +117,10 @@ int main(int argc, char **argv) po::options_description visible_options(""); visible_options.add_options() ("debug,d", "Enable debug output.") + ("external-dir", po::value>()->multitoken(), "The directory to use for external facts.") ("help", "Print this help message.") ("json,j", "Output in JSON format.") + ("no-external-dir", "Turn off external facts") ("propfile,p", po::value(&properties_file), "Configure logging with a log4cxx properties file.") ("verbose", "Enable verbose (info) output.") ("version,v", "Print the version and exit.") @@ -154,6 +156,9 @@ int main(int argc, char **argv) if (vm.count("json") && vm.count("yaml")) { throw po::error("json and yaml options conflict. please specify one or the other."); } + if (vm.count("no-external-dir") && vm.count("external-dir")) { + throw po::error("no-external-dir and external-dir options conflict. please specify one or the other."); + } } catch(po::error& ex) { cerr << "error: " << ex.what() << "\n\n"; @@ -199,6 +204,15 @@ int main(int argc, char **argv) // Resolve the facts and output the result fact_map facts; + + if (!vm.count("no-external-dir")) { + if (vm["external-dir"].empty()) { + facts.resolve_external(); + } else { + facts.resolve_external(vm["external-dir"].as>()); + } + } + facts.resolve(requested_facts); // Output the facts @@ -211,7 +225,7 @@ int main(int argc, char **argv) } cout << '\n'; } catch (exception& ex) { - LOG_FATAL("Unhandled exception: %1%.", ex.what()); + LOG_FATAL("Unhandled exception: %1%", ex.what()); return EXIT_FAILURE; } } diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index bf54b5424a..74ca703789 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -23,6 +23,7 @@ endif() set(LIBFACTER_COMMON_SOURCES "${CMAKE_CURRENT_LIST_DIR}/src/facterlib.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/array_value.cc" + "${CMAKE_CURRENT_LIST_DIR}/src/facts/external/yaml_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/fact.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/fact_map.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/fact_resolver.cc" @@ -40,6 +41,7 @@ if (UNIX) "${CMAKE_CURRENT_LIST_DIR}/src/facts/posix/kernel_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/posix/networking_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/posix/operating_system_resolver.cc" + "${CMAKE_CURRENT_LIST_DIR}/src/facts/posix/platform.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/posix/processor_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/posix/ssh_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/posix/uptime_resolver.cc" diff --git a/lib/inc/facter/facts/external/resolver.hpp b/lib/inc/facter/facts/external/resolver.hpp new file mode 100644 index 0000000000..99dbfce9e8 --- /dev/null +++ b/lib/inc/facter/facts/external/resolver.hpp @@ -0,0 +1,41 @@ +#ifndef FACTER_FACTS_EXTERNAL_RESOLVER_HPP_ +#define FACTER_FACTS_EXTERNAL_RESOLVER_HPP_ + +#include +#include + +namespace facter { namespace facts { + struct fact_map; +}} // namespace facter::facts + +namespace facter { namespace facts { namespace external { + + /** + * Thrown when there is an error processing an external fact. + */ + struct external_fact_exception : std::runtime_error + { + /** + * Constructs a external_fact_exception. + * @param message The exception message. + */ + explicit external_fact_exception(std::string const& message) : std::runtime_error(message) {} + }; + + /** + * Base class for external resolvers + */ + struct resolver + { + /** + * Resolves facts from the given file. + * @param path The path to the file to resolve facts from. + * @param facts The fact map to populate the external facts into. + * @return Returns true if the facts were resolved or false if the given file is not supported. + */ + virtual bool resolve(std::string const& path, fact_map& facts) const = 0; + }; + +}}} // namespace facter::facts::external + +#endif // FACTER_FACTS_EXTERNAL_RESOLVER_HPP_ diff --git a/lib/inc/facter/facts/external/yaml_resolver.hpp b/lib/inc/facter/facts/external/yaml_resolver.hpp new file mode 100644 index 0000000000..638c56615d --- /dev/null +++ b/lib/inc/facter/facts/external/yaml_resolver.hpp @@ -0,0 +1,24 @@ +#ifndef FACTER_FACTS_EXTERNAL_YAML_RESOLVER_HPP_ +#define FACTER_FACTS_EXTERNAL_YAML_RESOLVER_HPP_ + +#include "resolver.hpp" + +namespace facter { namespace facts { namespace external { + + /** + * Responsible for resolving facts from YAML files. + */ + struct yaml_resolver : resolver + { + /** + * Resolves facts from the given file. + * @param path The path to the file to resolve facts from. + * @param facts The fact map to populate the external facts into. + * @return Returns true if the facts were resolved or false if the given file is not supported. + */ + virtual bool resolve(std::string const& path, fact_map& facts) const; + }; + +}}} // namespace facter::facts::external + +#endif // FACTER_FACTS_EXTERNAL_YAML_RESOLVER_HPP_ diff --git a/lib/inc/facter/facts/fact_map.hpp b/lib/inc/facter/facts/fact_map.hpp index 87dcf30a80..c7e5a93550 100644 --- a/lib/inc/facter/facts/fact_map.hpp +++ b/lib/inc/facter/facts/fact_map.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -16,18 +17,6 @@ namespace facter { namespace facts { struct value; struct fact_resolver; - /** - * Thrown when a fact already exists in the map. - */ - struct fact_exists_exception : std::runtime_error - { - /** - * Constructs a fact_exists_exception. - * @param message The exception message. - */ - explicit fact_exists_exception(std::string const& message) : std::runtime_error(message) {} - }; - /** * Thrown when a fact already has an associated resolver. */ @@ -94,10 +83,16 @@ namespace facter { namespace facts { /** * Checks to see if the fact map has been resolved. - * @return Returns true if all fact resolvers have been resolved or false if at least fact resolver remains unresolved. + * @return Returns true if all fact resolvers have been resolved or false if at least one fact resolver remains unresolved. */ bool resolved() const; + /** + * Gets the size of the fact map. + * @return Returns the number of resolved top-level facts in the fact map. + */ + size_t size() const; + /** * Resolves all facts. * This forces each resolver in the map to resolve. @@ -105,6 +100,12 @@ namespace facter { namespace facts { */ void resolve(std::set const& facts = std::set()); + /** + * Resolves all external facts into the fact map. + * @param directories The directories to search for external facts. + */ + void resolve_external(std::vector const& directories = {}); + /** * Gets a fact value by name. * @tparam T The expected type of the value. diff --git a/lib/inc/facter/facts/scalar_value.hpp b/lib/inc/facter/facts/scalar_value.hpp index 1bf33b45d2..5a8f68a3c3 100644 --- a/lib/inc/facter/facts/scalar_value.hpp +++ b/lib/inc/facter/facts/scalar_value.hpp @@ -88,7 +88,7 @@ namespace facter { namespace facts { T _value; }; - // Declare the specializations for JSON output + // Declare the specializations for JSON outputc template <> void scalar_value::to_json(rapidjson::Allocator& allocator, rapidjson::Value& value) const; template <> @@ -112,6 +112,10 @@ namespace facter { namespace facts { template <> YAML::Emitter& scalar_value::write(YAML::Emitter& emitter) const; + // Declare the specializations for string output + template <> + std::ostream& scalar_value::write(std::ostream& os) const; + // Declare the common instantiations as external; defined in scalar_value.cc extern template struct scalar_value; extern template struct scalar_value; diff --git a/lib/src/facterlib.cc b/lib/src/facterlib.cc index bd52140685..7e9e6f8484 100644 --- a/lib/src/facterlib.cc +++ b/lib/src/facterlib.cc @@ -1,9 +1,7 @@ #include #include #include -#include -#include -#include +#include #include #include #include diff --git a/lib/src/facts/array_value.cc b/lib/src/facts/array_value.cc index 7e49c898c0..1384bc8b47 100644 --- a/lib/src/facts/array_value.cc +++ b/lib/src/facts/array_value.cc @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -51,7 +52,7 @@ namespace facter { namespace facts { ostream& array_value::write(ostream& os) const { // Write out the elements in the array - os << "[ "; + os << "["; bool first = true; for (auto const& element : _elements) { if (!element) { @@ -62,9 +63,16 @@ namespace facter { namespace facts { } else { os << ", "; } + bool quote = dynamic_cast(element.get()); + if (quote) { + os << '"'; + } os << *element; + if (quote) { + os << '"'; + } } - os << " ]"; + os << "]"; return os; } diff --git a/lib/src/facts/external/yaml_resolver.cc b/lib/src/facts/external/yaml_resolver.cc new file mode 100644 index 0000000000..d476bf82a1 --- /dev/null +++ b/lib/src/facts/external/yaml_resolver.cc @@ -0,0 +1,99 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace facter::util; +using namespace YAML; + +LOG_DECLARE_NAMESPACE("facts.external.yaml"); + +namespace facter { namespace facts { namespace external { + + static void add_value( + string const& name, + Node const& node, + fact_map& facts, + vector>* array_parent = nullptr, + map>* map_parent = nullptr) + { + unique_ptr val; + // For scalars, code the value into a specific value type + if (node.IsScalar()) { + bool bool_val; + int64_t int_val; + double double_val; + if (convert::decode(node, bool_val)) { + val = make_value(bool_val); + } else if (convert::decode(node, int_val)) { + val = make_value(int_val); + } else if (convert::decode(node, double_val)) { + val = make_value(double_val); + } else { + val = make_value(node.as()); + } + } else if (node.IsSequence()) { + // For sequences, convert to an array value + vector> members; + for (auto const& child : node) { + add_value({}, child, facts, &members); + } + + val = make_value(move(members)); + } else if (node.IsMap()) { + // For maps, convert to a map value + map> members; + for (auto const& child : node) { + add_value(child.first.as(), child.second, facts, nullptr, &members); + } + val = make_value(move(members)); + } else if (!node.IsNull()) { + // Ignore nodes we don't understand + return; + } + + // Put the value in the array, map, or directly as a top-level fact + if (array_parent) { + array_parent->emplace_back(move(val)); + } else if (map_parent) { + map_parent->emplace(name, move(val)); + } else { + facts.add(string(name), move(val)); + } + } + + bool yaml_resolver::resolve(std::string const& path, fact_map& facts) const + { + string full_path = path; + if (!ends_with(to_lower(full_path), ".yaml")) { + return false; + } + + LOG_DEBUG("resolving facts from YAML file \"%1%\".", path); + + ifstream stream(path); + if (!stream) { + throw external_fact_exception("file could not be opened."); + } + + try { + Node node = YAML::Load(stream); + for (auto const& kvp : node) { + add_value(kvp.first.as(), kvp.second, facts); + } + } catch (Exception& ex) { + throw external_fact_exception(ex.msg); + } + + LOG_DEBUG("completed resolving facts from YAML file \"%1%\".", path); + return true; + } + +}}} // namespace facter::facts::external diff --git a/lib/src/facts/fact_map.cc b/lib/src/facts/fact_map.cc index d4aa3a81cd..e49f23aa58 100644 --- a/lib/src/facts/fact_map.cc +++ b/lib/src/facts/fact_map.cc @@ -1,7 +1,10 @@ #include #include #include +#include +#include #include +#include #include #include #include @@ -10,6 +13,8 @@ using namespace std; using namespace rapidjson; using namespace YAML; +using namespace boost::filesystem; +namespace bs = boost::system; LOG_DECLARE_NAMESPACE("facts.map"); @@ -27,6 +32,12 @@ namespace facter { namespace facts { */ extern void populate_platform_facts(fact_map& facts); + /** + * Called to populate the external fact directories for the current platform. + * @param directories The vector of directories being populated. + */ + extern void populate_external_directories(vector& directories); + fact_map::fact_map() { populate_common_facts(*this); @@ -46,7 +57,7 @@ namespace facter { namespace facts { for (auto const& fact_name : resolver->names()) { auto const& it = _resolver_map.lower_bound(fact_name); if (it != _resolver_map.end() && !(_resolver_map.key_comp()(fact_name, it->first))) { - throw resolver_exists_exception("a resolver for fact " + fact_name + " already exists."); + throw resolver_exists_exception("a resolver for fact \"" + fact_name + "\" already exists."); } _resolver_map.insert(it, make_pair(fact_name, resolver)); } @@ -55,26 +66,37 @@ namespace facter { namespace facts { void fact_map::add(string&& name, unique_ptr&& value) { - if (!value) { - LOG_DEBUG("fact %1% resolved to null and will be ignored.", name); - return; - } - // Search for the fact first auto const& it = _facts.lower_bound(name); if (it != _facts.end() && !(_facts.key_comp()(name, it->first))) { - throw fact_exists_exception("fact " + name + " already exists."); - } - - if (LOG_IS_DEBUG_ENABLED()) { - ostringstream ss; - ss << *value; - LOG_DEBUG("fact %1% has resolved to \"%2%\".", name, ss.str()); + if (!value) { + LOG_DEBUG("fact \"%1%\" resolved to null and the existing value will be removed.", name); + _facts.erase(it); + return; + } + if (LOG_IS_DEBUG_ENABLED()) { + ostringstream old_value; + ostringstream new_value; + old_value << *it->second; + new_value << *value; + LOG_DEBUG("fact \"%1%\" has changed from \"%2%\" to \"%3%\".", name, old_value.str(), new_value.str()); + } + it->second = move(value); + } else { + if (!value) { + LOG_DEBUG("fact \"%1%\" resolved to null and will not be added.", name); + return; + } + if (LOG_IS_DEBUG_ENABLED()) { + ostringstream ss; + ss << *value; + LOG_DEBUG("fact \"%1%\" has resolved to \"%2%\".", name, ss.str()); + } + _facts.insert(it, make_pair(move(name), move(value))); } // Remove any mapped resolver for this fact _resolver_map.erase(name); - _facts.insert(it, make_pair(move(name), move(value))); } void fact_map::remove(shared_ptr const& resolver) @@ -88,7 +110,7 @@ namespace facter { namespace facts { if (LOG_IS_DEBUG_ENABLED()) { auto it = _facts.find(name); if (it == _facts.end()) { - LOG_DEBUG("fact %1% was not resolved.", name); + LOG_DEBUG("fact \"%1%\" was not resolved.", name); } } _resolver_map.erase(name); @@ -119,6 +141,11 @@ namespace facter { namespace facts { return _resolvers.empty(); } + size_t fact_map::size() const + { + return _facts.size(); + } + void fact_map::resolve(set const& facts) { if (resolved()) { @@ -132,7 +159,9 @@ namespace facter { namespace facts { } auto value = get_value(fact, true); if (!value) { - LOG_DEBUG("fact %1% was not resolved.", fact); + // For parity with Ruby facter, add an empty string + LOG_DEBUG("fact \"%1%\" was requested but not resolved; adding empty string value.", fact); + add(string(fact), make_value("")); } } @@ -159,13 +188,74 @@ namespace facter { namespace facts { for (auto kvp : _resolver_map) { auto it = _facts.find(kvp.first); if (it == _facts.end()) { - LOG_DEBUG("fact %1% was not resolved.", kvp.first); + LOG_DEBUG("fact \"%1%\" was not resolved.", kvp.first); } } } _resolver_map.clear(); } + void fact_map::resolve_external(vector const& directories) + { + static unique_ptr const resolvers[] = { + unique_ptr(new external::yaml_resolver()) + }; + + auto search_directories = directories; + if (search_directories.empty()) { + populate_external_directories(search_directories); + } + + // Go through each search directory + for (auto const& directory : search_directories) { + directory_iterator end; + directory_iterator it; + + // Attempt to iterate the directory + try { + it = directory_iterator(directory); + } catch (filesystem_error& ex) { + // Warn the user if not using the default search directories + if (!directories.empty()) { + LOG_WARNING("skipping external facts for \"%1%\": %2%", directory, ex.code().message()); + } else { + LOG_DEBUG("skipping external facts for \"%1%\": %2%", directory, ex.code().message()); + } + continue; + } + + LOG_DEBUG("searching \"%1%\" for external facts.", directory); + + // Search for regular files in the directory + for (; it != end; ++it) { + bs::error_code ec; + if (!is_regular_file(it->status())) { + continue; + } + + // Search for a resolver that will process the file + try + { + bool resolved = false; + for (auto const& resolver : resolvers) { + if (resolver->resolve(it->path().string(), *this)) { + resolved = true; + break; + } + } + + if (!resolved) { + LOG_DEBUG("file %1% is not supported for external facts.", it->path()); + continue; + } + } + catch (external::external_fact_exception& ex) { + LOG_ERROR("error while processing %1% for external facts: %2%", it->path(), ex.what()); + } + } + } + } + void fact_map::each(function func) const { find_if(begin(_facts), end(_facts), [&func](fact_map_type::value_type const& it) { @@ -256,6 +346,12 @@ namespace facter { namespace facts { ostream& operator<<(ostream& os, fact_map const& facts) { + // If there's only one fact, print it without the name + if (facts._facts.size() == 1) { + os << *facts._facts.begin()->second; + return os; + } + // Print all facts in the map bool first = true; for (auto const& kvp : facts._facts) { diff --git a/lib/src/facts/linux/block_device_resolver.cc b/lib/src/facts/linux/block_device_resolver.cc index 1b349bcad0..13b993d1ec 100644 --- a/lib/src/facts/linux/block_device_resolver.cc +++ b/lib/src/facts/linux/block_device_resolver.cc @@ -36,7 +36,7 @@ namespace facter { namespace facts { namespace linux { try { it = directory_iterator(devices_directory); } catch (filesystem_error& ex) { - LOG_DEBUG("%1%: %2%: block device facts are unavailable.", devices_directory, ex.what()); + LOG_DEBUG("%1%: %2%: block device facts are unavailable.", devices_directory, ex.code().message()); return; } diff --git a/lib/src/facts/map_value.cc b/lib/src/facts/map_value.cc index b4bba18a64..14d89e2df9 100644 --- a/lib/src/facts/map_value.cc +++ b/lib/src/facts/map_value.cc @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -26,7 +27,6 @@ namespace facter { namespace facts { if (!kvp.second) { continue; } - rapidjson::Value child; kvp.second->to_json(allocator, child); value.AddMember(kvp.first.c_str(), child, allocator); @@ -59,7 +59,7 @@ namespace facter { namespace facts { ostream& map_value::write(ostream& os) const { // Write out the elements in the map - os << "{ "; + os << "{"; bool first = true; for (auto const& kvp : _elements) { if (!kvp.second) { @@ -70,9 +70,17 @@ namespace facter { namespace facts { } else { os << ", "; } - os << kvp.first << " => " << *kvp.second; + os << '"' << kvp.first << "\"=>"; + bool quote = dynamic_cast(kvp.second.get()); + if (quote) { + os << '"'; + } + os << *kvp.second; + if (quote) { + os << '"'; + } } - os << " }"; + os << "}"; return os; } @@ -81,7 +89,12 @@ namespace facter { namespace facts { emitter << BeginMap; for (auto const& kvp : _elements) { emitter << Key << kvp.first; - emitter << YAML::Value << *kvp.second; + emitter << YAML::Value; + if (!kvp.second) { + emitter << Null; + } else { + emitter << *kvp.second; + } } emitter << EndMap; return emitter; diff --git a/lib/src/facts/posix/platform.cc b/lib/src/facts/posix/platform.cc new file mode 100644 index 0000000000..3cd6feee0f --- /dev/null +++ b/lib/src/facts/posix/platform.cc @@ -0,0 +1,26 @@ +#include +#include +#include +#include +#include + +using namespace std; +using namespace boost::filesystem; + +namespace facter { namespace facts { + + void populate_external_directories(vector& directories) + { + if (getuid()) { + auto home_dir = getenv("HOME"); + if (!home_dir) { + return; + } + directories.emplace_back(string(home_dir) + "/.facter/facts.d"); + } else { + directories.emplace_back("/etc/facter/facts.d"); + directories.emplace_back("/etc/puppetlabs/facter/facts.d"); + } + } + +}} // namespace facter::facts diff --git a/lib/src/facts/scalar_value.cc b/lib/src/facts/scalar_value.cc index ade5b916f0..0b95a69c9e 100644 --- a/lib/src/facts/scalar_value.cc +++ b/lib/src/facts/scalar_value.cc @@ -2,6 +2,7 @@ #include #include #include +#include using namespace std; using namespace rapidjson; @@ -76,6 +77,13 @@ namespace facter { namespace facts { return emitter; } + template <> + ostream& scalar_value::write(ostream& os) const + { + os << boolalpha << _value; + return os; + } + template struct scalar_value; template struct scalar_value; template struct scalar_value; diff --git a/lib/tests/CMakeLists.txt b/lib/tests/CMakeLists.txt index 33e7001adc..83677ea85b 100644 --- a/lib/tests/CMakeLists.txt +++ b/lib/tests/CMakeLists.txt @@ -15,6 +15,8 @@ set(LIBFACTER_TESTS_COMMON_SOURCES "${CMAKE_CURRENT_LIST_DIR}/facts/array_value.cc" "${CMAKE_CURRENT_LIST_DIR}/facts/boolean_value.cc" "${CMAKE_CURRENT_LIST_DIR}/facts/double_value.cc" + "${CMAKE_CURRENT_LIST_DIR}/facts/external/yaml_resolver.cc" + "${CMAKE_CURRENT_LIST_DIR}/facts/fact_map.cc" "${CMAKE_CURRENT_LIST_DIR}/facts/integer_value.cc" "${CMAKE_CURRENT_LIST_DIR}/facts/map_value.cc" "${CMAKE_CURRENT_LIST_DIR}/facts/string_value.cc" diff --git a/lib/tests/facts/array_value.cc b/lib/tests/facts/array_value.cc index 19a8839d9c..2b2ba15eb3 100644 --- a/lib/tests/facts/array_value.cc +++ b/lib/tests/facts/array_value.cc @@ -86,7 +86,7 @@ TEST(facter_facts_array_value, insertion_operator) { ostringstream stream; stream << value; - ASSERT_EQ("[ 1, 2, [ child ] ]", stream.str()); + ASSERT_EQ("[\"1\", 2, [\"child\"]]", stream.str()); } TEST(facter_facts_array_value, yaml_insertion_operator) { diff --git a/lib/tests/facts/external/yaml_resolver.cc b/lib/tests/facts/external/yaml_resolver.cc new file mode 100644 index 0000000000..759e850b24 --- /dev/null +++ b/lib/tests/facts/external/yaml_resolver.cc @@ -0,0 +1,54 @@ +#include +#include +#include +#include +#include +#include +#include "../../fixtures.hpp" + +using namespace std; +using namespace facter::facts; +using namespace facter::facts::external; + +TEST(facter_facts_external_yaml_resolver, default_constructor) { + yaml_resolver resolver; +} + +TEST(facter_facts_external_yaml_resolver, resolve_non_yaml) { + yaml_resolver resolver; + fact_map facts; + ASSERT_FALSE(resolver.resolve("notyaml.txt", facts)); +} + +TEST(facter_facts_external_yaml_resolver, resolve_nonexistent_yaml) { + yaml_resolver resolver; + fact_map facts; + ASSERT_THROW(resolver.resolve("foo.yaml", facts), external_fact_exception); +} + +TEST(facter_facts_external_yaml_resolver, resolve_invalid_yaml) { + yaml_resolver resolver; + fact_map facts; + ASSERT_THROW(resolver.resolve(LIBFACTER_TESTS_DIRECTORY "/fixtures/facts/external/yaml/invalid.yaml", facts), external_fact_exception); +} + +TEST(facter_facts_external_yaml_resolver, resolve_yaml) { + yaml_resolver resolver; + fact_map facts; + ASSERT_TRUE(resolver.resolve(LIBFACTER_TESTS_DIRECTORY "/fixtures/facts/external/yaml/facts.yaml", facts)); + ASSERT_TRUE(!facts.empty()); + ASSERT_NE(nullptr, facts.get("yaml_fact1")); + ASSERT_EQ("foo", facts.get("yaml_fact1")->value()); + ASSERT_NE(nullptr, facts.get("yaml_fact2")); + ASSERT_EQ(5, facts.get("yaml_fact2")->value()); + ASSERT_NE(nullptr, facts.get("yaml_fact3")); + ASSERT_TRUE(facts.get("yaml_fact3")->value()); + ASSERT_NE(nullptr, facts.get("yaml_fact4")); + ASSERT_EQ(5.1, facts.get("yaml_fact4")->value()); + auto array = facts.get("yaml_fact5"); + ASSERT_NE(nullptr, array); + ASSERT_EQ(3u, array->elements().size()); + auto map = facts.get("yaml_fact6"); + ASSERT_NE(nullptr, map); + ASSERT_EQ(2u, map->elements().size()); +} diff --git a/lib/tests/facts/fact_map.cc b/lib/tests/facts/fact_map.cc new file mode 100644 index 0000000000..871e2d1bdc --- /dev/null +++ b/lib/tests/facts/fact_map.cc @@ -0,0 +1,167 @@ +#include +#include +#include +#include +#include +#include +#include "../fixtures.hpp" +#include + +using namespace std; +using namespace facter::facts; + +TEST(facter_facts_fact_map, default_constructor) { + fact_map facts; + ASSERT_FALSE(facts.empty()); + ASSERT_FALSE(facts.resolved()); + ASSERT_EQ(1u, facts.size()); +} + +TEST(facter_facts_fact_map, simple_fact) { + fact_map facts; + facts.clear(); + facts.add("foo", make_value("bar")); + ASSERT_EQ(1u, facts.size()); + ASSERT_FALSE(facts.empty()); + ASSERT_TRUE(facts.resolved()); + auto fact = facts.get("foo"); + ASSERT_NE(nullptr, fact); + ASSERT_EQ("bar", fact->value()); + fact = dynamic_cast(facts["foo"]); + ASSERT_NE(nullptr, fact); + ASSERT_EQ("bar", fact->value()); +} + +struct simple_resolver : fact_resolver +{ + simple_resolver() : fact_resolver("test", { "foo" }) + { + } + + protected: + virtual void resolve_facts(fact_map& facts) + { + facts.add("foo", make_value("bar")); + } +}; + +TEST(facter_facts_fact_map, simple_resolver) { + fact_map facts; + facts.clear(); + facts.add(make_shared()); + ASSERT_FALSE(facts.empty()); + ASSERT_FALSE(facts.resolved()); + ASSERT_EQ(0u, facts.size()); + ASSERT_EQ("bar", facts.get("foo")->value()); + ASSERT_TRUE(facts.resolved()); + ASSERT_EQ(1u, facts.size()); +} + +struct multi_resolver : fact_resolver +{ + multi_resolver() : fact_resolver("test", { "foo", "bar" }) + { + } + + protected: + virtual void resolve_facts(fact_map& facts) + { + facts.add("foo", make_value("bar")); + facts.add("bar", make_value("foo")); + } +}; + +TEST(facter_facts_fact_map, resolve_specific) { + fact_map facts; + facts.clear(); + facts.add(make_shared()); + ASSERT_FALSE(facts.empty()); + ASSERT_FALSE(facts.resolved()); + facts.resolve({ "bar" }); + ASSERT_TRUE(facts.resolved()); + ASSERT_EQ(1u, facts.size()); + ASSERT_EQ(nullptr, facts.get("foo")); + ASSERT_EQ("foo", facts.get("bar")->value()); +} + +TEST(facter_facts_fact_map, resolve_external) { + fact_map facts; + facts.clear(); + ASSERT_TRUE(facts.empty()); + ASSERT_TRUE(facts.resolved()); + facts.resolve_external({LIBFACTER_TESTS_DIRECTORY "/fixtures/facts/external/yaml"}); + ASSERT_FALSE(facts.empty()); + ASSERT_TRUE(facts.resolved()); + ASSERT_EQ(6u, facts.size()); + ASSERT_NE(nullptr, facts.get("yaml_fact1")); + ASSERT_NE(nullptr, facts.get("yaml_fact2")); + ASSERT_NE(nullptr, facts.get("yaml_fact3")); + ASSERT_NE(nullptr, facts.get("yaml_fact4")); + ASSERT_NE(nullptr, facts.get("yaml_fact5")); + ASSERT_NE(nullptr, facts.get("yaml_fact6")); +} + +TEST(facter_facts_fact_map, each) { + fact_map facts; + facts.clear(); + facts.add(make_shared()); + size_t count = 0; + bool failed_foo = true; + bool failed_bar = true; + facts.resolve(); + facts.each([&](string const& name, value const* val) { + auto string_val = dynamic_cast(val); + if (string_val) { + if (name == "foo") { + failed_foo = string_val->value() != "bar"; + } else if (name == "bar") { + failed_bar = string_val->value() != "foo"; + } + } + ++count; + return true; + }); + ASSERT_EQ(2u, count); + ASSERT_FALSE(failed_foo); + ASSERT_FALSE(failed_bar); +} + +TEST(facter_facts_fact_map, write_json) { + fact_map facts; + facts.clear(); + facts.add(make_shared()); + facts.resolve(); + ostringstream ss; + facts.write_json(ss); + ASSERT_EQ("{\n \"bar\": \"foo\",\n \"foo\": \"bar\"\n}", ss.str()); +} + +TEST(facter_facts_fact_map, write_yaml) { + fact_map facts; + facts.clear(); + facts.add(make_shared()); + facts.resolve(); + ostringstream ss; + facts.write_yaml(ss); + ASSERT_EQ("bar: \"foo\"\nfoo: \"bar\"", ss.str()); +} + +TEST(facter_facts_fact_map, insertion_operator) { + fact_map facts; + facts.clear(); + facts.add(make_shared()); + facts.resolve(); + ostringstream ss; + ss << facts; + ASSERT_EQ("bar => foo\nfoo => bar", ss.str()); +} + +TEST(facter_facts_fact_map, insertion_operator_simple) { + fact_map facts; + facts.clear(); + facts.add(make_shared()); + facts.resolve(); + ostringstream ss; + ss << facts; + ASSERT_EQ("bar", ss.str()); +} diff --git a/lib/tests/facts/map_value.cc b/lib/tests/facts/map_value.cc index e7d98d831e..bb8febeba4 100644 --- a/lib/tests/facts/map_value.cc +++ b/lib/tests/facts/map_value.cc @@ -114,7 +114,7 @@ TEST(facter_facts_map_value, insertion_operator) { ostringstream stream; stream << value; - ASSERT_EQ("{ array => [ 1, 2 ], integer => 5, map => { foo => bar }, string => hello }", stream.str()); + ASSERT_EQ("{\"array\"=>[\"1\", 2], \"integer\"=>5, \"map\"=>{\"foo\"=>\"bar\"}, \"string\"=>\"hello\"}", stream.str()); } TEST(facter_facts_map_value, yaml_insertion_operator) { diff --git a/lib/tests/fixtures/facts/external/yaml/facts.yaml b/lib/tests/fixtures/facts/external/yaml/facts.yaml new file mode 100644 index 0000000000..e25bf08068 --- /dev/null +++ b/lib/tests/fixtures/facts/external/yaml/facts.yaml @@ -0,0 +1,11 @@ +yaml_fact1: foo +yaml_fact2: 5 +yaml_fact3: true +yaml_fact4: 5.1 +yaml_fact5: + - 1 + - 2 + - 3 +yaml_fact6: + element1: 1 + element2: 2 \ No newline at end of file diff --git a/lib/tests/fixtures/facts/external/yaml/invalid.yaml b/lib/tests/fixtures/facts/external/yaml/invalid.yaml new file mode 100644 index 0000000000..9496a27d16 --- /dev/null +++ b/lib/tests/fixtures/facts/external/yaml/invalid.yaml @@ -0,0 +1 @@ +!!!this is not valid yaml!!! \ No newline at end of file From 81d42b25f1865e2ffcbeadff66c276f8173dc3f6 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Tue, 20 May 2014 19:39:07 -0700 Subject: [PATCH 1877/3753] (maint) Make array_value and map_value mutable. Allowing adding new elements to array_value and map_value. Removing vector/map constructors from both, respectively. Removing elements member that returns the underlying vector/map respectively. Adding size, empty, and each members for consistency. Updating tests for new interface. --- lib/inc/facter/facts/array_value.hpp | 44 +++++--- lib/inc/facter/facts/fact_map.hpp | 2 +- lib/inc/facter/facts/map_value.hpp | 45 +++++--- lib/src/facts/array_value.cc | 49 ++++++--- lib/src/facts/external/yaml_resolver.cc | 23 ++-- lib/src/facts/map_value.cc | 52 ++++++--- lib/tests/facts/array_value.cc | 95 ++++++++++------ lib/tests/facts/external/yaml_resolver.cc | 4 +- lib/tests/facts/map_value.cc | 128 +++++++++++++--------- 9 files changed, 276 insertions(+), 166 deletions(-) diff --git a/lib/inc/facter/facts/array_value.hpp b/lib/inc/facter/facts/array_value.hpp index c8a3bd6ad5..98a0d7eef9 100644 --- a/lib/inc/facter/facts/array_value.hpp +++ b/lib/inc/facter/facts/array_value.hpp @@ -4,6 +4,7 @@ #include "value.hpp" #include #include +#include namespace facter { namespace facts { @@ -15,18 +16,7 @@ namespace facter { namespace facts { /** * Constructs an array_value. */ - array_value() - { - } - - /** - * Constructs an array value. - * @param elements The elements that make up the array. - */ - explicit array_value(std::vector>&& elements) : - _elements(std::move(elements)) - { - } + array_value(); // Force non-copyable array_value(array_value const&) = delete; @@ -36,6 +26,30 @@ namespace facter { namespace facts { array_value(array_value&&) = default; array_value& operator=(array_value&&) = default; + /** + * Adds a value to the array. + * @param value The value to add to the array. + */ + void add(std::unique_ptr&& value); + + /** + * Checks to see if the array is empty. + * @return Returns true if the array is empty or false if it is not. + */ + bool empty() const; + + /** + * Gets the size of the array. + * @return Returns the number of values in the array. + */ + size_t size() const; + + /** + * Enumerates all facts in the array. + * @param func The callback function called for each value in the array. + */ + void each(std::function func) const; + /** * Converts the value to a JSON value. * @param allocator The allocator to use for creating the JSON value. @@ -50,12 +64,6 @@ namespace facter { namespace facts { */ virtual void notify(std::string const& name, enumeration_callbacks const* callbacks) const; - /** - * Gets the vector of elements in the array. - * @return Returns the vector of elements in the array. - */ - std::vector> const& elements() const { return _elements; } - /** * Gets the element at the given index. * @tparam T The expected type of the value. diff --git a/lib/inc/facter/facts/fact_map.hpp b/lib/inc/facter/facts/fact_map.hpp index c7e5a93550..3257b8693e 100644 --- a/lib/inc/facter/facts/fact_map.hpp +++ b/lib/inc/facter/facts/fact_map.hpp @@ -77,7 +77,7 @@ namespace facter { namespace facts { /** * Checks to see if the fact map is empty. - * @return Returns true if the fact map is entry or false if it is not. + * @return Returns true if the fact map is empty or false if it is not. */ bool empty() const; diff --git a/lib/inc/facter/facts/map_value.hpp b/lib/inc/facter/facts/map_value.hpp index 1cace44e4d..9013038e21 100644 --- a/lib/inc/facter/facts/map_value.hpp +++ b/lib/inc/facter/facts/map_value.hpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace facter { namespace facts { @@ -16,18 +17,7 @@ namespace facter { namespace facts { /** * Constructs a map value. */ - map_value() - { - } - - /** - * Constructs a map value. - * @param elements The elements to store in the map value. - */ - explicit map_value(std::map>&& elements) : - _elements(std::move(elements)) - { - } + map_value(); // Force non-copyable map_value(map_value const&) = delete; @@ -37,6 +27,31 @@ namespace facter { namespace facts { map_value(map_value&&) = default; map_value& operator=(map_value&&) = default; + /** + * Adds a value to the map. + * @param name The name of map element. + * @param value The value of the map element. + */ + void add(std::string&& name, std::unique_ptr&& value); + + /** + * Checks to see if the map is empty. + * @return Returns true if the map is empty or false if it is not. + */ + bool empty() const; + + /** + * Gets the size of the map. + * @return Returns the number of elements in the map. + */ + size_t size() const; + + /** + * Enumerates all facts in the map. + * @param func The callback function called for each element in the map. + */ + void each(std::function func) const; + /** * Converts the value to a JSON value. * @param allocator The allocator to use for creating the JSON value. @@ -51,12 +66,6 @@ namespace facter { namespace facts { */ virtual void notify(std::string const& name, enumeration_callbacks const* callbacks) const; - /** - * Gets the map of elements in the value. - * @return Returns the vector of elements in the array. - */ - std::map> const& elements() const { return _elements; } - /** * Gets the value in the map of the given name. * @tparam T The expected type of the value. diff --git a/lib/src/facts/array_value.cc b/lib/src/facts/array_value.cc index 1384bc8b47..7761066283 100644 --- a/lib/src/facts/array_value.cc +++ b/lib/src/facts/array_value.cc @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -8,18 +9,49 @@ using namespace std; using namespace rapidjson; using namespace YAML; +LOG_DECLARE_NAMESPACE("facts.value.array"); + namespace facter { namespace facts { + array_value::array_value() + { + } + + void array_value::add(unique_ptr&& value) + { + if (!value) { + LOG_DEBUG("null value cannot be added to array."); + return; + } + + _elements.emplace_back(move(value)); + } + + bool array_value::empty() const + { + return _elements.empty(); + } + + size_t array_value::size() const + { + return _elements.size(); + } + + void array_value::each(function func) const + { + for (auto const& element : _elements) { + if (!func(element.get())) { + break; + } + } + } + void array_value::to_json(Allocator& allocator, rapidjson::Value& value) const { value.SetArray(); value.Reserve(_elements.size(), allocator); for (auto const& element : _elements) { - if (!element) { - continue; - } - rapidjson::Value child; element->to_json(allocator, child); value.PushBack(child, allocator); @@ -38,9 +70,6 @@ namespace facter { namespace facts { // Call notify on each element in the array for (auto const& element : _elements) { - if (!element) { - continue; - } element->notify({}, callbacks); } @@ -55,9 +84,6 @@ namespace facter { namespace facts { os << "["; bool first = true; for (auto const& element : _elements) { - if (!element) { - continue; - } if (first) { first = false; } else { @@ -80,9 +106,6 @@ namespace facter { namespace facts { { emitter << BeginSeq; for (auto const& element : _elements) { - if (!element) { - continue; - } emitter << *element; } emitter << EndSeq; diff --git a/lib/src/facts/external/yaml_resolver.cc b/lib/src/facts/external/yaml_resolver.cc index d476bf82a1..378cde2ea0 100644 --- a/lib/src/facts/external/yaml_resolver.cc +++ b/lib/src/facts/external/yaml_resolver.cc @@ -21,8 +21,8 @@ namespace facter { namespace facts { namespace external { string const& name, Node const& node, fact_map& facts, - vector>* array_parent = nullptr, - map>* map_parent = nullptr) + array_value* array_parent = nullptr, + map_value* map_parent = nullptr) { unique_ptr val; // For scalars, code the value into a specific value type @@ -40,20 +40,17 @@ namespace facter { namespace facts { namespace external { val = make_value(node.as()); } } else if (node.IsSequence()) { - // For sequences, convert to an array value - vector> members; + // For arrays, convert to a array value + val = make_value(); for (auto const& child : node) { - add_value({}, child, facts, &members); + add_value({}, child, facts, static_cast(val.get())); } - - val = make_value(move(members)); } else if (node.IsMap()) { // For maps, convert to a map value - map> members; + val = make_value(); for (auto const& child : node) { - add_value(child.first.as(), child.second, facts, nullptr, &members); + add_value(child.first.as(), child.second, facts, nullptr, static_cast(val.get())); } - val = make_value(move(members)); } else if (!node.IsNull()) { // Ignore nodes we don't understand return; @@ -61,15 +58,15 @@ namespace facter { namespace facts { namespace external { // Put the value in the array, map, or directly as a top-level fact if (array_parent) { - array_parent->emplace_back(move(val)); + array_parent->add(move(val)); } else if (map_parent) { - map_parent->emplace(name, move(val)); + map_parent->add(string(name), move(val)); } else { facts.add(string(name), move(val)); } } - bool yaml_resolver::resolve(std::string const& path, fact_map& facts) const + bool yaml_resolver::resolve(string const& path, fact_map& facts) const { string full_path = path; if (!ends_with(to_lower(full_path), ".yaml")) { diff --git a/lib/src/facts/map_value.cc b/lib/src/facts/map_value.cc index 14d89e2df9..7faf8ea128 100644 --- a/lib/src/facts/map_value.cc +++ b/lib/src/facts/map_value.cc @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -8,8 +9,43 @@ using namespace std; using namespace rapidjson; using namespace YAML; +LOG_DECLARE_NAMESPACE("facts.value.map"); + namespace facter { namespace facts { + map_value::map_value() + { + } + + void map_value::add(string&& name, unique_ptr&& value) + { + if (!value) { + LOG_DEBUG("null value cannot be added to map."); + return; + } + + _elements.emplace(move(name), move(value)); + } + + bool map_value::empty() const + { + return _elements.empty(); + } + + size_t map_value::size() const + { + return _elements.size(); + } + + void map_value::each(function func) const + { + for (auto const& kvp : _elements) { + if (!func(kvp.first, kvp.second.get())) { + break; + } + } + } + value const* map_value::operator[](string const& name) const { auto it = _elements.find(name); @@ -24,9 +60,6 @@ namespace facter { namespace facts { value.SetObject(); for (auto const& kvp : _elements) { - if (!kvp.second) { - continue; - } rapidjson::Value child; kvp.second->to_json(allocator, child); value.AddMember(kvp.first.c_str(), child, allocator); @@ -45,9 +78,6 @@ namespace facter { namespace facts { // Call notify on each element in the array for (auto const& element : _elements) { - if (!element.second) { - continue; - } element.second->notify(element.first, callbacks); } @@ -62,9 +92,6 @@ namespace facter { namespace facts { os << "{"; bool first = true; for (auto const& kvp : _elements) { - if (!kvp.second) { - continue; - } if (first) { first = false; } else { @@ -89,12 +116,7 @@ namespace facter { namespace facts { emitter << BeginMap; for (auto const& kvp : _elements) { emitter << Key << kvp.first; - emitter << YAML::Value; - if (!kvp.second) { - emitter << Null; - } else { - emitter << *kvp.second; - } + emitter << YAML::Value << *kvp.second; } emitter << EndMap; return emitter; diff --git a/lib/tests/facts/array_value.cc b/lib/tests/facts/array_value.cc index 2b2ba15eb3..7ea10d79af 100644 --- a/lib/tests/facts/array_value.cc +++ b/lib/tests/facts/array_value.cc @@ -12,20 +12,24 @@ using namespace YAML; TEST(facter_facts_array_value, default_constructor) { array_value value; - ASSERT_EQ(0u, value.elements().size()); + ASSERT_EQ(0u, value.size()); } -TEST(facter_facts_array_value, vector_constructor) { - vector> subelements; - subelements.emplace_back(make_value("child")); +TEST(facter_facts_array_value, null_add) { + array_value value; + value.add(nullptr); + ASSERT_EQ(0u, value.size()); +} - vector> elements; - elements.emplace_back(make_value("1")); - elements.emplace_back(make_value(2)); - elements.emplace_back(make_value(move(subelements))); +TEST(facter_facts_array_value, vector_constructor) { + auto subarray = make_value(); + static_cast(subarray.get())->add(make_value("child")); - array_value value(move(elements)); - ASSERT_EQ(3u, value.elements().size()); + array_value value; + value.add(make_value("1")); + value.add(make_value(2)); + value.add(move(subarray)); + ASSERT_EQ(3u, value.size()); auto str = value.get(0); ASSERT_NE(nullptr, str); @@ -37,23 +41,48 @@ TEST(facter_facts_array_value, vector_constructor) { auto array = value.get(2); ASSERT_NE(nullptr, array); - ASSERT_EQ(1u, array->elements().size()); + ASSERT_EQ(1u, array->size()); str = array->get(0); ASSERT_NE(nullptr, str); ASSERT_EQ("child", str->value()); } -TEST(facter_facts_array_value, to_json) { - vector> subelements; - subelements.emplace_back(make_value("child")); +TEST(facter_facts_array_value, each) { + array_value value; + value.add(make_value("1")); + value.add(make_value("2")); + value.add(make_value("3")); + + size_t count = 0; + bool failed = false; + value.each([&](struct value const* val) { + auto string_val = dynamic_cast(val); + if (!string_val) { + failed = true; + return false; + } + if ((count == 0 && string_val->value() != "1") || + (count == 1 && string_val->value() != "2") || + (count == 2 && string_val->value() != "3")) { + failed = true; + return false; + } + ++count; + return true; + }); + ASSERT_FALSE(failed); + ASSERT_EQ(3u, count); +} - vector> elements; - elements.emplace_back(make_value("1")); - elements.emplace_back(make_value(2)); - elements.emplace_back(make_value(move(subelements))); +TEST(facter_facts_array_value, to_json) { + auto subarray = make_value(); + static_cast(subarray.get())->add(make_value("child")); - array_value value(move(elements)); + array_value value; + value.add(make_value("1")); + value.add(make_value(2)); + value.add(move(subarray)); rapidjson::Value json_value; MemoryPoolAllocator<> allocator; @@ -74,15 +103,13 @@ TEST(facter_facts_array_value, to_json) { } TEST(facter_facts_array_value, insertion_operator) { - vector> subelements; - subelements.emplace_back(make_value("child")); - - vector> elements; - elements.emplace_back(make_value("1")); - elements.emplace_back(make_value(2)); - elements.emplace_back(make_value(move(subelements))); + auto subarray = make_value(); + static_cast(subarray.get())->add(make_value("child")); - array_value value(move(elements)); + array_value value; + value.add(make_value("1")); + value.add(make_value(2)); + value.add(move(subarray)); ostringstream stream; stream << value; @@ -90,15 +117,13 @@ TEST(facter_facts_array_value, insertion_operator) { } TEST(facter_facts_array_value, yaml_insertion_operator) { - vector> subelements; - subelements.emplace_back(make_value("child")); - - vector> elements; - elements.emplace_back(make_value("1")); - elements.emplace_back(make_value(2)); - elements.emplace_back(make_value(move(subelements))); + auto subarray = make_value(); + static_cast(subarray.get())->add(make_value("child")); - array_value value(move(elements)); + array_value value; + value.add(make_value("1")); + value.add(make_value(2)); + value.add(move(subarray)); Emitter emitter; emitter << value; diff --git a/lib/tests/facts/external/yaml_resolver.cc b/lib/tests/facts/external/yaml_resolver.cc index 759e850b24..9770620116 100644 --- a/lib/tests/facts/external/yaml_resolver.cc +++ b/lib/tests/facts/external/yaml_resolver.cc @@ -47,8 +47,8 @@ TEST(facter_facts_external_yaml_resolver, resolve_yaml) { ASSERT_EQ(5.1, facts.get("yaml_fact4")->value()); auto array = facts.get("yaml_fact5"); ASSERT_NE(nullptr, array); - ASSERT_EQ(3u, array->elements().size()); + ASSERT_EQ(3u, array->size()); auto map = facts.get("yaml_fact6"); ASSERT_NE(nullptr, map); - ASSERT_EQ(2u, map->elements().size()); + ASSERT_EQ(2u, map->size()); } diff --git a/lib/tests/facts/map_value.cc b/lib/tests/facts/map_value.cc index bb8febeba4..02f5c10488 100644 --- a/lib/tests/facts/map_value.cc +++ b/lib/tests/facts/map_value.cc @@ -13,25 +13,30 @@ using namespace YAML; TEST(facter_facts_map_value, default_constructor) { map_value value; - ASSERT_EQ(0u, value.elements().size()); + ASSERT_EQ(0u, value.size()); +} + +TEST(facter_facts_map_value, null_add) { + map_value value; + value.add("null", nullptr); + ASSERT_EQ(0u, value.size()); } TEST(facter_facts_map_value, map_constructor) { - map> elements; - elements["string"] = make_value("hello"); - elements["integer"] = make_value(5); + map_value value; + value.add("string", make_value("hello")); + value.add("integer", make_value(5)); - vector> array_elements; - array_elements.emplace_back(make_value("1")); - array_elements.emplace_back(make_value(2)); - elements["array"] = make_value(move(array_elements)); + auto array_element = make_value(); + static_cast(array_element.get())->add(make_value("1")); + static_cast(array_element.get())->add(make_value(2)); + value.add("array", move(array_element)); - map> submap; - submap["foo"] = make_value("bar"); - elements["map"] = make_value(move(submap)); + auto map_element = make_value(); + static_cast(map_element.get())->add("foo", make_value("bar")); + value.add("map", move(map_element)); - map_value value(move(elements)); - ASSERT_EQ(4u, value.elements().size()); + ASSERT_EQ(4u, value.size()); auto str = value.get("string"); ASSERT_NE(nullptr, str); @@ -43,7 +48,7 @@ TEST(facter_facts_map_value, map_constructor) { auto array = value.get("array"); ASSERT_NE(nullptr, array); - ASSERT_EQ(2u, array->elements().size()); + ASSERT_EQ(2u, array->size()); str = array->get(0); ASSERT_EQ("1", str->value()); integer = array->get(1); @@ -51,27 +56,52 @@ TEST(facter_facts_map_value, map_constructor) { auto mapval = value.get("map"); ASSERT_NE(nullptr, mapval); - ASSERT_EQ(1u, mapval->elements().size()); + ASSERT_EQ(1u, mapval->size()); str = mapval->get("foo"); ASSERT_NE(nullptr, str); ASSERT_EQ("bar", str->value()); } -TEST(facter_facts_map_value, to_json) { - map> elements; - elements["string"] = make_value("hello"); - elements["integer"] = make_value(5); +TEST(facter_facts_map_value, each) { + map_value value; + value.add("fact1", make_value("1")); + value.add("fact2", make_value("2")); + value.add("fact3", make_value("3")); + + size_t count = 0; + bool failed = false; + value.each([&](string const& name, struct value const* val) { + auto string_val = dynamic_cast(val); + if (!string_val) { + failed = true; + return false; + } + if ((name == "fact1" && string_val->value() != "1") || + (name == "fact2" && string_val->value() != "2") || + (name == "fact3" && string_val->value() != "3")) { + failed = true; + return false; + } + ++count; + return true; + }); + ASSERT_FALSE(failed); + ASSERT_EQ(3u, count); +} - vector> array_elements; - array_elements.emplace_back(make_value("1")); - array_elements.emplace_back(make_value(2)); - elements["array"] = make_value(move(array_elements)); +TEST(facter_facts_map_value, to_json) { + map_value value; + value.add("string", make_value("hello")); + value.add("integer", make_value(5)); - map> submap; - submap["foo"] = make_value("bar"); - elements["map"] = make_value(move(submap)); + auto array_element = make_value(); + static_cast(array_element.get())->add(make_value("1")); + static_cast(array_element.get())->add(make_value(2)); + value.add("array", move(array_element)); - map_value value(move(elements)); + auto map_element = make_value(); + static_cast(map_element.get())->add("foo", make_value("bar")); + value.add("map", move(map_element)); rapidjson::Value json_value; MemoryPoolAllocator<> allocator; @@ -97,20 +127,18 @@ TEST(facter_facts_map_value, to_json) { } TEST(facter_facts_map_value, insertion_operator) { - map> elements; - elements["string"] = make_value("hello"); - elements["integer"] = make_value(5); - - vector> array_elements; - array_elements.emplace_back(make_value("1")); - array_elements.emplace_back(make_value(2)); - elements["array"] = make_value(move(array_elements)); + map_value value; + value.add("string", make_value("hello")); + value.add("integer", make_value(5)); - map> submap; - submap["foo"] = make_value("bar"); - elements["map"] = make_value(move(submap)); + auto array_element = make_value(); + static_cast(array_element.get())->add(make_value("1")); + static_cast(array_element.get())->add(make_value(2)); + value.add("array", move(array_element)); - map_value value(move(elements)); + auto map_element = make_value(); + static_cast(map_element.get())->add("foo", make_value("bar")); + value.add("map", move(map_element)); ostringstream stream; stream << value; @@ -118,20 +146,18 @@ TEST(facter_facts_map_value, insertion_operator) { } TEST(facter_facts_map_value, yaml_insertion_operator) { - map> elements; - elements["string"] = make_value("hello"); - elements["integer"] = make_value(5); - - vector> array_elements; - array_elements.emplace_back(make_value("1")); - array_elements.emplace_back(make_value(2)); - elements["array"] = make_value(move(array_elements)); + map_value value; + value.add("string", make_value("hello")); + value.add("integer", make_value(5)); - map> submap; - submap["foo"] = make_value("bar"); - elements["map"] = make_value(move(submap)); + auto array_element = make_value(); + static_cast(array_element.get())->add(make_value("1")); + static_cast(array_element.get())->add(make_value(2)); + value.add("array", move(array_element)); - map_value value(move(elements)); + auto map_element = make_value(); + static_cast(map_element.get())->add("foo", make_value("bar")); + value.add("map", move(map_element)); Emitter emitter; emitter << value; From dbc7ce28da8b3a02c265062e8a6692da09094426 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Wed, 21 May 2014 11:06:31 -0700 Subject: [PATCH 1878/3753] (maint) Fix resource leak in scoped_bio. Fixing resource leak in scoped_bio where the base constructor wasn't being called so the deleter function wasn't set. Removing the default base constructor to prevent this from happening in the future. --- lib/inc/facter/util/bsd/scoped_ifaddrs.hpp | 5 ++--- lib/inc/facter/util/posix/scoped_addrinfo.hpp | 5 ++--- lib/inc/facter/util/posix/scoped_bio.hpp | 4 ++-- lib/inc/facter/util/scoped_resource.hpp | 9 --------- 4 files changed, 6 insertions(+), 17 deletions(-) diff --git a/lib/inc/facter/util/bsd/scoped_ifaddrs.hpp b/lib/inc/facter/util/bsd/scoped_ifaddrs.hpp index d5b179829c..98f444a109 100644 --- a/lib/inc/facter/util/bsd/scoped_ifaddrs.hpp +++ b/lib/inc/facter/util/bsd/scoped_ifaddrs.hpp @@ -15,13 +15,12 @@ namespace facter { namespace util { namespace bsd { * Default constructor. * This constructor will handle calling getifaddrs. */ - scoped_ifaddrs() + scoped_ifaddrs() : + scoped_resource(nullptr, free) { // Get the linked list of interfaces if (getifaddrs(&_resource) != 0) { _resource = nullptr; - } else { - _deleter = free; } } diff --git a/lib/inc/facter/util/posix/scoped_addrinfo.hpp b/lib/inc/facter/util/posix/scoped_addrinfo.hpp index 1041521d1b..591d8c2b81 100644 --- a/lib/inc/facter/util/posix/scoped_addrinfo.hpp +++ b/lib/inc/facter/util/posix/scoped_addrinfo.hpp @@ -20,7 +20,8 @@ namespace facter { namespace util { namespace posix { * Constructs a scoped_addrinfo. * @param info The address info to free when destroyed. */ - explicit scoped_addrinfo(std::string const& hostname) + explicit scoped_addrinfo(std::string const& hostname) : + scoped_resource(nullptr, free) { addrinfo hints; std::memset(&hints, 0, sizeof hints); @@ -31,8 +32,6 @@ namespace facter { namespace util { namespace posix { _result = getaddrinfo(hostname.c_str(), nullptr, &hints, &_resource); if (_result != 0) { _resource = nullptr; - } else { - _deleter = free; } } diff --git a/lib/inc/facter/util/posix/scoped_bio.hpp b/lib/inc/facter/util/posix/scoped_bio.hpp index d69e18ebed..4b2dc0b4c4 100644 --- a/lib/inc/facter/util/posix/scoped_bio.hpp +++ b/lib/inc/facter/util/posix/scoped_bio.hpp @@ -16,9 +16,9 @@ namespace facter { namespace util { namespace posix { * Constructs a scoped_bio. * @param method The BIO_METHOD to use. */ - explicit scoped_bio(BIO_METHOD* method) + explicit scoped_bio(BIO_METHOD* method) : + scoped_resource(BIO_new(method), free) { - _resource = BIO_new(method); } /** diff --git a/lib/inc/facter/util/scoped_resource.hpp b/lib/inc/facter/util/scoped_resource.hpp index 0e50db2496..07c8560e3d 100644 --- a/lib/inc/facter/util/scoped_resource.hpp +++ b/lib/inc/facter/util/scoped_resource.hpp @@ -12,15 +12,6 @@ namespace facter { namespace util { */ template struct scoped_resource { - /** - * Default constructor for scoped_resource. - */ - scoped_resource() : - _resource(), - _deleter() - { - } - /** * Constructs a scoped_resource. * Takes ownership of the given resource. From 999f5dc2caa90cc692fe11202f7d0d7d740499c7 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Wed, 21 May 2014 11:11:27 -0700 Subject: [PATCH 1879/3753] (CFACT-12) Implement external facts in JSON files. Implementing a JSON external facts resolver. Adding scoped_file to scope FILE*. Fixing tests where ASSERT_EQ was used for doubles (ASSERT_DOUBLE_EQ should be used instead). --- lib/CMakeLists.txt | 1 + .../facter/facts/external/json_resolver.hpp | 24 +++ lib/inc/facter/util/scoped_file.hpp | 46 ++++ lib/src/facts/external/json_resolver.cc | 203 ++++++++++++++++++ lib/src/facts/fact_map.cc | 4 +- lib/tests/CMakeLists.txt | 1 + lib/tests/facts/double_value.cc | 4 +- lib/tests/facts/external/json_resolver.cc | 54 +++++ lib/tests/facts/external/yaml_resolver.cc | 2 +- lib/tests/facts/fact_map.cc | 13 +- .../fixtures/facts/external/json/facts.json | 11 + .../fixtures/facts/external/json/invalid.json | 1 + 12 files changed, 358 insertions(+), 6 deletions(-) create mode 100644 lib/inc/facter/facts/external/json_resolver.hpp create mode 100644 lib/inc/facter/util/scoped_file.hpp create mode 100644 lib/src/facts/external/json_resolver.cc create mode 100644 lib/tests/facts/external/json_resolver.cc create mode 100644 lib/tests/fixtures/facts/external/json/facts.json create mode 100644 lib/tests/fixtures/facts/external/json/invalid.json diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 74ca703789..d38ce8c96e 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -23,6 +23,7 @@ endif() set(LIBFACTER_COMMON_SOURCES "${CMAKE_CURRENT_LIST_DIR}/src/facterlib.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/array_value.cc" + "${CMAKE_CURRENT_LIST_DIR}/src/facts/external/json_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/external/yaml_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/fact.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/fact_map.cc" diff --git a/lib/inc/facter/facts/external/json_resolver.hpp b/lib/inc/facter/facts/external/json_resolver.hpp new file mode 100644 index 0000000000..c9d1f19d8c --- /dev/null +++ b/lib/inc/facter/facts/external/json_resolver.hpp @@ -0,0 +1,24 @@ +#ifndef FACTER_FACTS_EXTERNAL_JSON_RESOLVER_HPP_ +#define FACTER_FACTS_EXTERNAL_JSON_RESOLVER_HPP_ + +#include "resolver.hpp" + +namespace facter { namespace facts { namespace external { + + /** + * Responsible for resolving facts from JSON files. + */ + struct json_resolver : resolver + { + /** + * Resolves facts from the given file. + * @param path The path to the file to resolve facts from. + * @param facts The fact map to populate the external facts into. + * @return Returns true if the facts were resolved or false if the given file is not supported. + */ + virtual bool resolve(std::string const& path, fact_map& facts) const; + }; + +}}} // namespace facter::facts::external + +#endif // FACTER_FACTS_EXTERNAL_JSON_RESOLVER_HPP_ diff --git a/lib/inc/facter/util/scoped_file.hpp b/lib/inc/facter/util/scoped_file.hpp new file mode 100644 index 0000000000..687117af1f --- /dev/null +++ b/lib/inc/facter/util/scoped_file.hpp @@ -0,0 +1,46 @@ +#ifndef FACTER_UTIL_SCOPED_FILE_HPP_ +#define FACTER_UTIL_SCOPED_FILE_HPP_ + +#include "scoped_resource.hpp" +#include +#include + +namespace facter { namespace util { + + /** + * Represents a scoped file. + * Automatically closes the file when it goes out of scope. + */ + struct scoped_file : scoped_resource + { + /** + * Constructs a scoped_file. + * @param path The path to the file. + * @param mode The open mode. + */ + explicit scoped_file(std::string const& path, std::string const& mode) : + scoped_resource(std::fopen(path.c_str(), mode.c_str()), close) + { + } + + /** + * Constructs a scoped_file. + * @param file The existing file pointer. + */ + explicit scoped_file(std::FILE* file) : + scoped_resource(std::move(file), close) + { + } + + private: + static void close(std::FILE* file) + { + if (file) { + fclose(file); + } + } + }; + +}} // namespace facter::util + +#endif // FACTER_UTIL_SCOPED_FILE_HPP_ diff --git a/lib/src/facts/external/json_resolver.cc b/lib/src/facts/external/json_resolver.cc new file mode 100644 index 0000000000..5273a17ba6 --- /dev/null +++ b/lib/src/facts/external/json_resolver.cc @@ -0,0 +1,203 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace facter::util; +using namespace facter::facts; +using namespace rapidjson; + +LOG_DECLARE_NAMESPACE("facts.external.json"); + +namespace facter { namespace facts { namespace external { + + // Helper event handler for parsing JSON data + struct json_event_handler + { + explicit json_event_handler(fact_map& facts) : + _initialized(false), + _facts(facts) + { + } + + void Null() + { + check_initialized(); + + // Ignore this fact as values cannot be null + _key.clear(); + } + + void Bool(bool b) + { + add_value(make_value(b)); + } + + void Int(int i) + { + Uint64(static_cast(i)); + } + + void Uint(unsigned int i) + { + Uint64(static_cast(i)); + } + + void Int64(int64_t i) + { + Uint64(static_cast(i)); + } + + void Uint64(uint64_t i) + { + add_value(make_value(i)); + } + + void Double(double d) + { + add_value(make_value(d)); + } + + void String(char const* s, SizeType len, bool copy) + { + // If the stack is empty or the top is a map and we don't have a key yet, set the key + if ((_stack.empty() || dynamic_cast(get<1>(_stack.top()).get())) && _key.empty()) { + check_initialized(); + _key = s; + return; + } + + add_value(make_value(s)); + } + + void StartObject() + { + if (!_initialized) { + _initialized = true; + return; + } + + // Push a map onto the stack + _stack.emplace(make_tuple(move(_key), make_value())); + } + + void EndObject(SizeType count) + { + // Check to see if the stack is empty since we don't push for the top-level object + if (_stack.empty()) { + return; + } + + // Pop the data off the stack + auto top = move(_stack.top()); + _stack.pop(); + + // Restore the key and add the value + _key = move(get<0>(top)); + add_value(move(get<1>(top))); + } + + void StartArray() + { + check_initialized(); + + // Push an array onto the stack + _stack.emplace(make_tuple(move(_key), make_value())); + } + + void EndArray(SizeType count) + { + // Pop the data off the stack + auto top = move(_stack.top()); + _stack.pop(); + + // Restore the key and add the value + _key = move(get<0>(top)); + add_value(move(get<1>(top))); + } + + private: + template void add_value(unique_ptr&& val) + { + check_initialized(); + + // If the stack is empty, just add it as a top-level fact + if (_stack.empty()) { + if (_key.empty()) { + throw external::external_fact_exception("expected non-empty key in object."); + } + _facts.add(move(_key), move(val)); + return; + } + + // If there's an array or map on the stack, add the value as an element + auto& top = _stack.top(); + auto& current = get<1>(top); + auto array = dynamic_cast(current.get()); + if (array) { + array->add(move(val)); + return; + } + auto map = dynamic_cast(current.get()); + if (map) { + if (_key.empty()) { + throw external::external_fact_exception("expected non-empty key in object."); + } + map->add(move(_key), move(val)); + } + } + + void check_initialized() + { + if (!_initialized) { + throw external::external_fact_exception("expected document to contain an object."); + } + } + + bool _initialized; + fact_map& _facts; + string _key; + stack>> _stack; + }; + + bool json_resolver::resolve(string const& path, fact_map& facts) const + { + string full_path = path; + if (!ends_with(to_lower(full_path), ".json")) { + return false; + } + + LOG_DEBUG("resolving facts from JSON file \"%1%\".", path); + + // Open the file + // We used a scoped_file here because rapidjson expects a FILE* + scoped_file file(path, "r"); + if (file == nullptr) { + throw external_fact_exception("file could not be opened."); + } + + // Use the existing FileStream class + FileStream stream(file); + + // Parse the file and report any errors + Reader reader; + json_event_handler handler(facts); + reader.Parse<0>(stream, handler); + if (reader.HasParseError()) { + throw external_fact_exception(reader.GetParseError()); + } + + LOG_DEBUG("completed resolving facts from JSON file \"%1%\".", path); + return true; + } + +}}} // namespace facter::facts::external diff --git a/lib/src/facts/fact_map.cc b/lib/src/facts/fact_map.cc index e49f23aa58..a0be01bfde 100644 --- a/lib/src/facts/fact_map.cc +++ b/lib/src/facts/fact_map.cc @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -198,7 +199,8 @@ namespace facter { namespace facts { void fact_map::resolve_external(vector const& directories) { static unique_ptr const resolvers[] = { - unique_ptr(new external::yaml_resolver()) + unique_ptr(new external::json_resolver()), + unique_ptr(new external::yaml_resolver()), }; auto search_directories = directories; diff --git a/lib/tests/CMakeLists.txt b/lib/tests/CMakeLists.txt index 83677ea85b..4019708e93 100644 --- a/lib/tests/CMakeLists.txt +++ b/lib/tests/CMakeLists.txt @@ -15,6 +15,7 @@ set(LIBFACTER_TESTS_COMMON_SOURCES "${CMAKE_CURRENT_LIST_DIR}/facts/array_value.cc" "${CMAKE_CURRENT_LIST_DIR}/facts/boolean_value.cc" "${CMAKE_CURRENT_LIST_DIR}/facts/double_value.cc" + "${CMAKE_CURRENT_LIST_DIR}/facts/external/json_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/facts/external/yaml_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/facts/fact_map.cc" "${CMAKE_CURRENT_LIST_DIR}/facts/integer_value.cc" diff --git a/lib/tests/facts/double_value.cc b/lib/tests/facts/double_value.cc index 226443dc31..3b85053c3a 100644 --- a/lib/tests/facts/double_value.cc +++ b/lib/tests/facts/double_value.cc @@ -11,7 +11,7 @@ using namespace YAML; TEST(facter_facts_double_value, constructor) { double_value foo(42.0); - ASSERT_EQ(42.0, foo.value()); + ASSERT_DOUBLE_EQ(42.0, foo.value()); } TEST(facter_facts_double_value, to_json) { @@ -21,7 +21,7 @@ TEST(facter_facts_double_value, to_json) { MemoryPoolAllocator<> allocator; value.to_json(allocator, json_value); ASSERT_TRUE(json_value.IsNumber()); - ASSERT_EQ(1337.1337, json_value.GetDouble()); + ASSERT_DOUBLE_EQ(1337.1337, json_value.GetDouble()); } TEST(facter_facts_double_value, insertion_operator) { diff --git a/lib/tests/facts/external/json_resolver.cc b/lib/tests/facts/external/json_resolver.cc new file mode 100644 index 0000000000..c2ffa264bb --- /dev/null +++ b/lib/tests/facts/external/json_resolver.cc @@ -0,0 +1,54 @@ +#include +#include +#include +#include +#include +#include +#include "../../fixtures.hpp" + +using namespace std; +using namespace facter::facts; +using namespace facter::facts::external; + +TEST(facter_facts_external_json_resolver, default_constructor) { + json_resolver resolver; +} + +TEST(facter_facts_external_json_resolver, resolve_non_json) { + json_resolver resolver; + fact_map facts; + ASSERT_FALSE(resolver.resolve("notjson.txt", facts)); +} + +TEST(facter_facts_external_json_resolver, resolve_nonexistent_json) { + json_resolver resolver; + fact_map facts; + ASSERT_THROW(resolver.resolve("foo.json", facts), external_fact_exception); +} + +TEST(facter_facts_external_json_resolver, resolve_invalid_json) { + json_resolver resolver; + fact_map facts; + ASSERT_THROW(resolver.resolve(LIBFACTER_TESTS_DIRECTORY "/fixtures/facts/external/json/invalid.json", facts), external_fact_exception); +} + +TEST(facter_facts_external_json_resolver, resolve_json) { + json_resolver resolver; + fact_map facts; + ASSERT_TRUE(resolver.resolve(LIBFACTER_TESTS_DIRECTORY "/fixtures/facts/external/json/facts.json", facts)); + ASSERT_TRUE(!facts.empty()); + ASSERT_NE(nullptr, facts.get("json_fact1")); + ASSERT_EQ("foo", facts.get("json_fact1")->value()); + ASSERT_NE(nullptr, facts.get("json_fact2")); + ASSERT_EQ(5, facts.get("json_fact2")->value()); + ASSERT_NE(nullptr, facts.get("json_fact3")); + ASSERT_TRUE(facts.get("json_fact3")->value()); + ASSERT_NE(nullptr, facts.get("json_fact4")); + ASSERT_DOUBLE_EQ(5.1, facts.get("json_fact4")->value()); + auto array = facts.get("json_fact5"); + ASSERT_NE(nullptr, array); + ASSERT_EQ(3u, array->size()); + auto map = facts.get("json_fact6"); + ASSERT_NE(nullptr, map); + ASSERT_EQ(2u, map->size()); +} diff --git a/lib/tests/facts/external/yaml_resolver.cc b/lib/tests/facts/external/yaml_resolver.cc index 9770620116..4b03015302 100644 --- a/lib/tests/facts/external/yaml_resolver.cc +++ b/lib/tests/facts/external/yaml_resolver.cc @@ -44,7 +44,7 @@ TEST(facter_facts_external_yaml_resolver, resolve_yaml) { ASSERT_NE(nullptr, facts.get("yaml_fact3")); ASSERT_TRUE(facts.get("yaml_fact3")->value()); ASSERT_NE(nullptr, facts.get("yaml_fact4")); - ASSERT_EQ(5.1, facts.get("yaml_fact4")->value()); + ASSERT_DOUBLE_EQ(5.1, facts.get("yaml_fact4")->value()); auto array = facts.get("yaml_fact5"); ASSERT_NE(nullptr, array); ASSERT_EQ(3u, array->size()); diff --git a/lib/tests/facts/fact_map.cc b/lib/tests/facts/fact_map.cc index 871e2d1bdc..73e58c477d 100644 --- a/lib/tests/facts/fact_map.cc +++ b/lib/tests/facts/fact_map.cc @@ -89,16 +89,25 @@ TEST(facter_facts_fact_map, resolve_external) { facts.clear(); ASSERT_TRUE(facts.empty()); ASSERT_TRUE(facts.resolved()); - facts.resolve_external({LIBFACTER_TESTS_DIRECTORY "/fixtures/facts/external/yaml"}); + facts.resolve_external({ + LIBFACTER_TESTS_DIRECTORY "/fixtures/facts/external/yaml", + LIBFACTER_TESTS_DIRECTORY "/fixtures/facts/external/json" + }); ASSERT_FALSE(facts.empty()); ASSERT_TRUE(facts.resolved()); - ASSERT_EQ(6u, facts.size()); + ASSERT_EQ(12u, facts.size()); ASSERT_NE(nullptr, facts.get("yaml_fact1")); ASSERT_NE(nullptr, facts.get("yaml_fact2")); ASSERT_NE(nullptr, facts.get("yaml_fact3")); ASSERT_NE(nullptr, facts.get("yaml_fact4")); ASSERT_NE(nullptr, facts.get("yaml_fact5")); ASSERT_NE(nullptr, facts.get("yaml_fact6")); + ASSERT_NE(nullptr, facts.get("json_fact1")); + ASSERT_NE(nullptr, facts.get("json_fact2")); + ASSERT_NE(nullptr, facts.get("json_fact3")); + ASSERT_NE(nullptr, facts.get("json_fact4")); + ASSERT_NE(nullptr, facts.get("json_fact5")); + ASSERT_NE(nullptr, facts.get("json_fact6")); } TEST(facter_facts_fact_map, each) { diff --git a/lib/tests/fixtures/facts/external/json/facts.json b/lib/tests/fixtures/facts/external/json/facts.json new file mode 100644 index 0000000000..53d9144e9c --- /dev/null +++ b/lib/tests/fixtures/facts/external/json/facts.json @@ -0,0 +1,11 @@ +{ + "json_fact1": "foo", + "json_fact2": 5, + "json_fact3": true, + "json_fact4": 5.1, + "json_fact5": [1, 2, 3], + "json_fact6": { + "element1" : 1, + "element2" : 2 + } +} diff --git a/lib/tests/fixtures/facts/external/json/invalid.json b/lib/tests/fixtures/facts/external/json/invalid.json new file mode 100644 index 0000000000..3009e27a98 --- /dev/null +++ b/lib/tests/fixtures/facts/external/json/invalid.json @@ -0,0 +1 @@ +!!!this is not valid json!!! \ No newline at end of file From 96bba1b527fc274a3b12e11de2afaf636c337a4e Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Wed, 21 May 2014 13:02:29 -0700 Subject: [PATCH 1880/3753] (maint) Change cfacter logging output from stdout to stderr. Fixing outputting of debug/info/warning/error/fatal messages to stderr instead of stdout. This allows users to redirect the output to /dev/null to ignore those messages but still get the fact output. --- exe/cfacter.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exe/cfacter.cc b/exe/cfacter.cc index a34a59c8fd..ebd90dadcc 100644 --- a/exe/cfacter.cc +++ b/exe/cfacter.cc @@ -62,7 +62,7 @@ void configure_logger(LevelPtr level, string const& properties_file) // If no configuration file given, use default settings LayoutPtr layout = new PatternLayout("%d %-5p %c - %m%n"); - AppenderPtr appender = new ConsoleAppender(layout); + AppenderPtr appender = new ConsoleAppender(layout, "System.err"); Logger::getRootLogger()->addAppender(appender); Logger::getRootLogger()->setLevel(level); } From afa72e382da49558cfb889748ef623ee64682c92 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Wed, 21 May 2014 13:13:10 -0700 Subject: [PATCH 1881/3753] (CFACT-12) Sort external fact files. Sorting the external fact files so that we process them in alphabetical order rather than discovery order, which is file system dependent. --- lib/src/facts/fact_map.cc | 38 +++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/lib/src/facts/fact_map.cc b/lib/src/facts/fact_map.cc index a0be01bfde..aba9475d83 100644 --- a/lib/src/facts/fact_map.cc +++ b/lib/src/facts/fact_map.cc @@ -209,6 +209,7 @@ namespace facter { namespace facts { } // Go through each search directory + vector files; for (auto const& directory : search_directories) { directory_iterator end; directory_iterator it; @@ -235,26 +236,33 @@ namespace facter { namespace facts { continue; } - // Search for a resolver that will process the file - try - { - bool resolved = false; - for (auto const& resolver : resolvers) { - if (resolver->resolve(it->path().string(), *this)) { - resolved = true; - break; - } - } + files.push_back(it->path().string()); + } + } - if (!resolved) { - LOG_DEBUG("file %1% is not supported for external facts.", it->path()); - continue; + // Sort the files so there is a deterministic ordering to the external facts + sort(files.begin(), files.end()); + + // For each file, find a resolver for it + for (auto const& file : files) { + try + { + bool resolved = false; + for (auto const& resolver : resolvers) { + if (resolver->resolve(file, *this)) { + resolved = true; + break; } } - catch (external::external_fact_exception& ex) { - LOG_ERROR("error while processing %1% for external facts: %2%", it->path(), ex.what()); + + if (!resolved) { + LOG_DEBUG("file \"%1%\" is not supported for external facts.", file); + continue; } } + catch (external::external_fact_exception& ex) { + LOG_ERROR("error while processing \"%1%\" for external facts: %2%", file, ex.what()); + } } } From 079fa721976998114e635152fbb9b39c87d88e8d Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Wed, 21 May 2014 13:39:28 -0700 Subject: [PATCH 1882/3753] (maint) Fix execution_failure_exception to derive from execution_exception. Fixing execution_failure_exception to derive from execution_exception instead of runtime_error. --- lib/inc/facter/execution/execution.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/inc/facter/execution/execution.hpp b/lib/inc/facter/execution/execution.hpp index 80f24c0cfd..428e12210c 100644 --- a/lib/inc/facter/execution/execution.hpp +++ b/lib/inc/facter/execution/execution.hpp @@ -59,7 +59,7 @@ namespace facter { namespace execution { /** * Base class for execution failures. */ - struct execution_failure_exception : std::runtime_error + struct execution_failure_exception : execution_exception { /** * Constructs a execution_failure_exception. @@ -67,7 +67,7 @@ namespace facter { namespace execution { * @param message The exception message. */ execution_failure_exception(std::string const& output, std::string const& message) : - std::runtime_error(message), + execution_exception(message), _output(output) { } From c644ce7327ad37806ad0b5419e8fba3dae3897c6 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Wed, 21 May 2014 14:13:16 -0700 Subject: [PATCH 1883/3753] (maint) Add each_line string util function. Adding an each_line string util function to cfacter. --- lib/inc/facter/util/string.hpp | 7 +++++++ lib/src/util/string.cc | 11 +++++++++++ lib/tests/util/string.cc | 18 ++++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/lib/inc/facter/util/string.hpp b/lib/inc/facter/util/string.hpp index 2c5cd1701f..a5758887c3 100644 --- a/lib/inc/facter/util/string.hpp +++ b/lib/inc/facter/util/string.hpp @@ -132,6 +132,13 @@ namespace facter { namespace util { */ std::string to_hex(uint8_t const* bytes, size_t length, bool uppercase = false); + /** + * Reads each line from the given string. + * @param s The string to read. + * @param callback The callback function that is passed each line in the string. + */ + void each_line(std::string const& s, std::function callback); + /** * Character trait type for case-insensitive comparisons. * Based on Herb Sutter's post: http://www.gotw.ca/gotw/029.htm diff --git a/lib/src/util/string.cc b/lib/src/util/string.cc index e9e399b165..21d7a9aa95 100644 --- a/lib/src/util/string.cc +++ b/lib/src/util/string.cc @@ -134,6 +134,17 @@ namespace facter { namespace util { return ss.str(); } + void each_line(string const& s, function callback) + { + string line; + istringstream in(s); + while (getline(in, line)) { + if (!callback(line)) { + break; + } + } + } + int ci_char_traits::compare(char const* s1, char const* s2, size_t n) { return strncasecmp(s1, s2, n); diff --git a/lib/tests/util/string.cc b/lib/tests/util/string.cc index 198c25131c..53e67241bb 100644 --- a/lib/tests/util/string.cc +++ b/lib/tests/util/string.cc @@ -1,6 +1,7 @@ #include #include +using namespace std; using namespace facter::util; using testing::ElementsAre; @@ -103,3 +104,20 @@ TEST(facter_util_string, ci_string) { ASSERT_NE("not hello WORLD!", ci_string("hello WORLD!")); ASSERT_EQ("", ci_string("")); } + +TEST(facter_util_string, each_line) { + size_t count = 0; + bool failed = false; + each_line("line1\nline2\nline3", [&](string const& line) { + if ((count == 0 && line != "line1") || + (count == 1 && line != "line2") || + (count == 2 && line != "line3")) { + failed = true; + return false; + } + ++count; + return true; + }); + ASSERT_FALSE(failed); + ASSERT_EQ(3u, count); +} From 05bd7390a0400be2c85f7c302cd628151302c5df Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Wed, 21 May 2014 14:20:38 -0700 Subject: [PATCH 1884/3753] (CFACT-12) Fix external fact precedence, filtering, and platform-specific resolving. Fixing external fact precedence so that external facts are resolved after built-in facts. Fixing external fact filtering so that we remove resolved external facts that aren't in the filter. Moving external fact resolver list to be platform specific. This will allow different execution resolving implementations. --- exe/cfacter.cc | 10 ++++----- lib/inc/facter/facts/fact_map.hpp | 3 ++- lib/src/facts/fact_map.cc | 36 +++++++++++++++++++++---------- lib/src/facts/posix/platform.cc | 21 ++++++++++++++---- 4 files changed, 49 insertions(+), 21 deletions(-) diff --git a/exe/cfacter.cc b/exe/cfacter.cc index ebd90dadcc..61a1d5b9c2 100644 --- a/exe/cfacter.cc +++ b/exe/cfacter.cc @@ -202,19 +202,19 @@ int main(int argc, char **argv) log_requested_facts(requested_facts); - // Resolve the facts and output the result + // Resolve the facts fact_map facts; + facts.resolve(requested_facts); + // Resolve external facts next; this allows external facts to take precedence over built-in facts if (!vm.count("no-external-dir")) { if (vm["external-dir"].empty()) { - facts.resolve_external(); + facts.resolve_external({}, requested_facts); } else { - facts.resolve_external(vm["external-dir"].as>()); + facts.resolve_external(vm["external-dir"].as>(), requested_facts); } } - facts.resolve(requested_facts); - // Output the facts if (vm.count("json")) { facts.write_json(cout); diff --git a/lib/inc/facter/facts/fact_map.hpp b/lib/inc/facter/facts/fact_map.hpp index 3257b8693e..09bdec377d 100644 --- a/lib/inc/facter/facts/fact_map.hpp +++ b/lib/inc/facter/facts/fact_map.hpp @@ -103,8 +103,9 @@ namespace facter { namespace facts { /** * Resolves all external facts into the fact map. * @param directories The directories to search for external facts. + * @param facts The set of fact names to filter the resolution to. If empty, all external facts will be resolved. */ - void resolve_external(std::vector const& directories = {}); + void resolve_external(std::vector const& directories = {}, std::set const& facts = std::set()); /** * Gets a fact value by name. diff --git a/lib/src/facts/fact_map.cc b/lib/src/facts/fact_map.cc index aba9475d83..0b37cf1a24 100644 --- a/lib/src/facts/fact_map.cc +++ b/lib/src/facts/fact_map.cc @@ -2,8 +2,7 @@ #include #include #include -#include -#include +#include #include #include #include @@ -34,10 +33,16 @@ namespace facter { namespace facts { extern void populate_platform_facts(fact_map& facts); /** - * Called to populate the external fact directories for the current platform. - * @param directories The vector of directories being populated. + * Called to get the external fact search directories for the current platform. + * @returns Returns the vector of search directories for external facts. */ - extern void populate_external_directories(vector& directories); + extern vector get_external_directories(); + + /** + * Called to get the external fact resolvers for the current platform. + * @returns Returns the vector of external fact resolvers. + */ + extern vector> get_external_resolvers(); fact_map::fact_map() { @@ -196,16 +201,13 @@ namespace facter { namespace facts { _resolver_map.clear(); } - void fact_map::resolve_external(vector const& directories) + void fact_map::resolve_external(vector const& directories, set const& facts) { - static unique_ptr const resolvers[] = { - unique_ptr(new external::json_resolver()), - unique_ptr(new external::yaml_resolver()), - }; + vector> resolvers = get_external_resolvers(); auto search_directories = directories; if (search_directories.empty()) { - populate_external_directories(search_directories); + search_directories = get_external_directories(); } // Go through each search directory @@ -264,6 +266,18 @@ namespace facter { namespace facts { LOG_ERROR("error while processing \"%1%\" for external facts: %2%", file, ex.what()); } } + + // Remove facts that resolved but aren't in the filter + if (!facts.empty()) { + for (auto it = _facts.begin(); it != _facts.end();) { + if (facts.count(it->first)) { + // In the requested set of facts so move next + ++it; + continue; + } + it = _facts.erase(it); + } + } } void fact_map::each(function func) const diff --git a/lib/src/facts/posix/platform.cc b/lib/src/facts/posix/platform.cc index 3cd6feee0f..6ea2195193 100644 --- a/lib/src/facts/posix/platform.cc +++ b/lib/src/facts/posix/platform.cc @@ -1,26 +1,39 @@ +#include +#include #include #include #include #include +#include #include using namespace std; using namespace boost::filesystem; +using namespace facter::facts::external; namespace facter { namespace facts { - void populate_external_directories(vector& directories) + vector get_external_directories() { + vector directories; if (getuid()) { auto home_dir = getenv("HOME"); - if (!home_dir) { - return; + if (home_dir) { + directories.emplace_back(string(home_dir) + "/.facter/facts.d"); } - directories.emplace_back(string(home_dir) + "/.facter/facts.d"); } else { directories.emplace_back("/etc/facter/facts.d"); directories.emplace_back("/etc/puppetlabs/facter/facts.d"); } + return directories; + } + + vector> get_external_resolvers() + { + vector> resolvers; + resolvers.emplace_back(new yaml_resolver()); + resolvers.emplace_back(new json_resolver()); + return resolvers; } }} // namespace facter::facts From f376fc6a1c3ec6db454cfda306682d6043d55a85 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Wed, 21 May 2014 15:36:09 -0700 Subject: [PATCH 1885/3753] (CFACT-12) Implement external facts in the gem. Implementing external fact searching in the gem. Adding documentation to cfacter gem methods. --- gem/lib/cfacter.rb | 39 ++++++++++++++++++++++++++++++++++- gem/spec/unit/cfacter_spec.rb | 29 ++++++++++++++++++++++++-- lib/src/facterlib.cc | 16 +++++++++++++- 3 files changed, 80 insertions(+), 4 deletions(-) diff --git a/gem/lib/cfacter.rb b/gem/lib/cfacter.rb index 5a29fed20c..d6526b5ecc 100644 --- a/gem/lib/cfacter.rb +++ b/gem/lib/cfacter.rb @@ -3,6 +3,8 @@ module CFacter # This module defines cfacter's C interface in Ruby. + # + # @api private module FacterLib extend FFI::Library @@ -40,26 +42,52 @@ class EnumerationCallbacks < FFI::Struct attach_function :get_fact_value, [:string, :pointer], :bool end + # The facter gem version. FACTER_VERSION = '0.1.0' + # Ensure the facter library that was loaded matches the gem version. raise LoadError.new("Expected cfacter #{FACTER_VERSION} but found #{FacterLib.get_facter_version}.") if FacterLib.get_facter_version != FACTER_VERSION + # Gets the version of the facter library that was loaded. + # + # @return [String] The facter library version string. + # @api public def self.version FacterLib.get_facter_version end + # Loads all facts. + # + # @return [void] + # @api public def self.loadfacts FacterLib.load_facts nil end + # Clears all cached values and removes all facts from memory. + # + # @return [void] + # @api public def self.clear FacterLib.clear_facts end - # This method creates the callbacks used when enumerating facts from cfacter. + # Registers directories to be searched for external facts. + # + # @param dirs [Array] directories to search + # @return [void] + # @api public + def self.search_external(dirs) + FacterLib.search_external(dirs.join(':')) + end + + # Creates callbacks used when enumerating facts from cfacter. # Each callback simply appends the corresponding Ruby type to the hash/array # being built up during the enumeration. This allows us to effectively copy # the structure of the facts from cfacter into native Ruby types. + # @param initial The initial object to populate with facts. + # @return [EnumerationCallbacks] The enumeration callbacks. + # @api private def self.create_enumeration_callbacks(initial) callbacks = FacterLib::EnumerationCallbacks.new current = initial @@ -112,12 +140,21 @@ def self.create_enumeration_callbacks(initial) callbacks end + # Gets a hash mapping fact names to their values + # + # @return [Hash{String => Object}] the hash of fact names and values + # @api public def self.to_hash result = {} FacterLib.enumerate_facts(self.create_enumeration_callbacks(result)) result end + # Gets the value for a fact. Returns `nil` if no such fact exists. + # + # @param name [String] The fact name. + # @return [Object, nil] The value of the fact, or nil if no fact is found. + # @api public def self.value(name) # To share the enumeration callbacks with to_hash, pass in an array and return the # first element, which will be the value of the requested fact. diff --git a/gem/spec/unit/cfacter_spec.rb b/gem/spec/unit/cfacter_spec.rb index c7c18514a6..0a2201bacf 100644 --- a/gem/spec/unit/cfacter_spec.rb +++ b/gem/spec/unit/cfacter_spec.rb @@ -43,7 +43,11 @@ def enumerate(facts) describe CFacter do - it "provides a version" do + after :each do + CFacter.clear + end + + it "should provide a version" do CFacter.version.should_not be_nil end @@ -55,7 +59,7 @@ def enumerate(facts) CFacter.value('cfacterversion').should be_nil end - it "contains a matching cfacter version" do + it "should contain a matching cfacter version" do CFacter.loadfacts version = CFacter.value('cfacterversion') version.should eq CFacter.version @@ -126,4 +130,25 @@ def enumerate(facts) end end + it "should load external facts" do + CFacter.search_external([ + File.expand_path('../../../lib/tests/fixtures/facts/external/yaml', File.dirname(__FILE__)), + File.expand_path('../../../lib/tests/fixtures/facts/external/json', File.dirname(__FILE__)), + ]) + CFacter.loadfacts + facts = CFacter.to_hash + facts["yaml_fact1"].should be_a String + facts["yaml_fact2"].should be_a Integer + facts["yaml_fact3"].should satisfy { |v| v == true || v == false } + facts["yaml_fact4"].should be_a Float + facts["yaml_fact5"].should be_a Array + facts["yaml_fact6"].should be_a Hash + facts["json_fact1"].should be_a String + facts["json_fact2"].should be_a Integer + facts["json_fact3"].should satisfy { |v| v == true || v == false } + facts["json_fact4"].should be_a Float + facts["json_fact5"].should be_a Array + facts["json_fact6"].should be_a Hash + end + end diff --git a/lib/src/facterlib.cc b/lib/src/facterlib.cc index 7e9e6f8484..f58f97ba03 100644 --- a/lib/src/facterlib.cc +++ b/lib/src/facterlib.cc @@ -5,6 +5,8 @@ #include #include #include +#include +#include using namespace std; using namespace facter::util; @@ -12,6 +14,7 @@ using namespace facter::facts; using namespace log4cxx; static unique_ptr g_facts; +static vector g_external_directories; extern "C" { char const* get_facter_version() @@ -35,7 +38,12 @@ extern "C" { requested_facts.emplace(trim(to_lower(move(name)))); } } + + // Resolve facts g_facts->resolve(requested_facts); + + // Load external facts + g_facts->resolve_external(g_external_directories, requested_facts); } void clear_facts() @@ -77,6 +85,12 @@ extern "C" { void search_external(char const* directories) { - // TODO: implement + if (!directories) { + return; + } + + for (auto& directory : split(directories, ':')) { + g_external_directories.emplace_back(move(directory)); + } } } From e95dbe308f8007f7e74ddd7869cfc605a472c710 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Wed, 21 May 2014 15:50:27 -0700 Subject: [PATCH 1886/3753] (maint) Add gem testing to Travis CI. Updating Travis script to build, install, and test the cfacter gem. --- scripts/travis_target.sh | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/scripts/travis_target.sh b/scripts/travis_target.sh index 2969d5a437..f0c4583343 100755 --- a/scripts/travis_target.sh +++ b/scripts/travis_target.sh @@ -28,13 +28,45 @@ function travis_make() exit 1 fi - # Run library tests if not doing cpplint + # Run tests if not doing cpplint if [ $1 != "cpplint" ]; then ctest -V if [ $? -ne 0 ]; then echo "tests reported an error." exit 1 fi + + # Install into the system for the gem + sudo make install + if [ $? -ne 0 ]; then + echo "install failed." + exit 1 + fi + sudo ldconfig + + # Package, install, and test the gem + pushd ../gem + bundle install + if [ $? -ne 0 ]; then + echo "bundle install failed." + exit 1 + fi + rake gem + if [ $? -ne 0 ]; then + echo "gem build failed." + exit 1 + fi + gem install pkg/*.gem + if [ $? -ne 0 ]; then + echo "gem install failed." + exit 1 + fi + rspec + if [ $? -ne 0 ]; then + echo "gem specs failed." + exit 1 + fi + popd fi } From 9e99a3638d22e88f0d40bd61dd5a06593989a4a6 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Wed, 21 May 2014 17:35:51 -0700 Subject: [PATCH 1887/3753] (CFACT-12) Implement external facts from executable scripts. Implementing executing files to populate external facts. Any executable files in the external facts directories are run. Any output line of key=value is turned into a string fact. --- gem/spec/unit/cfacter_spec.rb | 4 ++ lib/CMakeLists.txt | 1 + .../external/posix/execution_resolver.hpp | 24 +++++++++ .../external/posix/execution_resolver.cc | 52 +++++++++++++++++++ lib/src/facts/posix/platform.cc | 5 ++ lib/tests/CMakeLists.txt | 1 + .../external/posix/execution_resolver.cc | 44 ++++++++++++++++ lib/tests/facts/fact_map.cc | 7 ++- .../facts/external/posix/execution/facts | 4 ++ .../facts/external/posix/execution/failed | 3 ++ .../external/posix/execution/not_executable | 1 + 11 files changed, 144 insertions(+), 2 deletions(-) create mode 100644 lib/inc/facter/facts/external/posix/execution_resolver.hpp create mode 100644 lib/src/facts/external/posix/execution_resolver.cc create mode 100644 lib/tests/facts/external/posix/execution_resolver.cc create mode 100755 lib/tests/fixtures/facts/external/posix/execution/facts create mode 100755 lib/tests/fixtures/facts/external/posix/execution/failed create mode 100644 lib/tests/fixtures/facts/external/posix/execution/not_executable diff --git a/gem/spec/unit/cfacter_spec.rb b/gem/spec/unit/cfacter_spec.rb index 0a2201bacf..b329540c30 100644 --- a/gem/spec/unit/cfacter_spec.rb +++ b/gem/spec/unit/cfacter_spec.rb @@ -134,6 +134,7 @@ def enumerate(facts) CFacter.search_external([ File.expand_path('../../../lib/tests/fixtures/facts/external/yaml', File.dirname(__FILE__)), File.expand_path('../../../lib/tests/fixtures/facts/external/json', File.dirname(__FILE__)), + File.expand_path('../../../lib/tests/fixtures/facts/external/posix/execution', File.dirname(__FILE__)), ]) CFacter.loadfacts facts = CFacter.to_hash @@ -149,6 +150,9 @@ def enumerate(facts) facts["json_fact4"].should be_a Float facts["json_fact5"].should be_a Array facts["json_fact6"].should be_a Hash + facts["exe_fact1"].should be_a String + facts["exe_fact2"].should be_a String + facts["exe_fact3"].should be_nil end end diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index d38ce8c96e..f7220c4dcc 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -39,6 +39,7 @@ set(LIBFACTER_COMMON_SOURCES if (UNIX) set(LIBFACTER_POSIX_SOURCES "${CMAKE_CURRENT_LIST_DIR}/src/execution/posix/execution.cc" + "${CMAKE_CURRENT_LIST_DIR}/src/facts/external/posix/execution_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/posix/kernel_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/posix/networking_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/posix/operating_system_resolver.cc" diff --git a/lib/inc/facter/facts/external/posix/execution_resolver.hpp b/lib/inc/facter/facts/external/posix/execution_resolver.hpp new file mode 100644 index 0000000000..1b5e9765c1 --- /dev/null +++ b/lib/inc/facter/facts/external/posix/execution_resolver.hpp @@ -0,0 +1,24 @@ +#ifndef FACTER_FACTS_EXTERNAL_POSIX_EXECUTION_RESOLVER_HPP_ +#define FACTER_FACTS_EXTERNAL_POSIX_EXECUTION_RESOLVER_HPP_ + +#include "../resolver.hpp" + +namespace facter { namespace facts { namespace external { namespace posix { + + /** + * Responsible for resolving facts from executable files. + */ + struct execution_resolver : resolver + { + /** + * Resolves facts from the given file. + * @param path The path to the file to resolve facts from. + * @param facts The fact map to populate the external facts into. + * @return Returns true if the facts were resolved or false if the given file is not supported. + */ + virtual bool resolve(std::string const& path, fact_map& facts) const; + }; + +}}}} // namespace facter::facts::external::posix + +#endif // FACTER_FACTS_EXTERNAL_POSIX_EXECUTION_RESOLVER_HPP_ diff --git a/lib/src/facts/external/posix/execution_resolver.cc b/lib/src/facts/external/posix/execution_resolver.cc new file mode 100644 index 0000000000..3fd34cdab1 --- /dev/null +++ b/lib/src/facts/external/posix/execution_resolver.cc @@ -0,0 +1,52 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace facter::execution; +using namespace facter::facts; +using namespace facter::facts::external; +using namespace facter::util; + +LOG_DECLARE_NAMESPACE("facts.external.posix.execution"); + +namespace facter { namespace facts { namespace external { namespace posix { + + bool execution_resolver::resolve(string const& path, fact_map& facts) const + { + if (access(path.c_str(), X_OK) == -1) { + // Because this is the last resolver to execute, log a warning if it's not executable + LOG_WARNING("file \"%1%\" is not executable.", path); + return false; + } + + LOG_DEBUG("resolving facts from executable file \"%1%\".", path); + + try + { + each_line(execute(path, { execution_options::defaults, execution_options::throw_on_failure }), [&facts](string const& line) { + auto pos = line.find('='); + if (pos == string::npos) { + LOG_DEBUG("ignoring line in output: %1%", line); + return true; + } + // Add as a string fact + facts.add(line.substr(0, pos), make_value(line.substr(pos+1))); + return true; + }); + } + catch (execution_exception& ex) { + throw external_fact_exception(ex.what()); + } + + LOG_DEBUG("completed resolving facts from executable file \"%1%\".", path); + return true; + } + +}}}} // namespace facter::facts::external::posix diff --git a/lib/src/facts/posix/platform.cc b/lib/src/facts/posix/platform.cc index 6ea2195193..906a97022a 100644 --- a/lib/src/facts/posix/platform.cc +++ b/lib/src/facts/posix/platform.cc @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -10,6 +11,7 @@ using namespace std; using namespace boost::filesystem; using namespace facter::facts::external; +using namespace facter::facts::external::posix; namespace facter { namespace facts { @@ -33,6 +35,9 @@ namespace facter { namespace facts { vector> resolvers; resolvers.emplace_back(new yaml_resolver()); resolvers.emplace_back(new json_resolver()); + + // The execution resolver should go last as it doesn't check file extensions + resolvers.emplace_back(new execution_resolver()); return resolvers; } diff --git a/lib/tests/CMakeLists.txt b/lib/tests/CMakeLists.txt index 4019708e93..788cc705c6 100644 --- a/lib/tests/CMakeLists.txt +++ b/lib/tests/CMakeLists.txt @@ -32,6 +32,7 @@ set(LIBFACTER_TESTS_COMMON_SOURCES if (UNIX) set(LIBFACTER_TESTS_POSIX_SOURCES "${CMAKE_CURRENT_LIST_DIR}/execution/posix/execution.cc" + "${CMAKE_CURRENT_LIST_DIR}/facts/external/posix/execution_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/util/posix/scoped_addrinfo.cc" "${CMAKE_CURRENT_LIST_DIR}/util/posix/scoped_bio.cc" "${CMAKE_CURRENT_LIST_DIR}/util/posix/scoped_descriptor.cc" diff --git a/lib/tests/facts/external/posix/execution_resolver.cc b/lib/tests/facts/external/posix/execution_resolver.cc new file mode 100644 index 0000000000..ab8e790259 --- /dev/null +++ b/lib/tests/facts/external/posix/execution_resolver.cc @@ -0,0 +1,44 @@ +#include +#include +#include +#include +#include "../../../fixtures.hpp" + +using namespace std; +using namespace facter::facts; +using namespace facter::facts::external; +using namespace facter::facts::external::posix; + +TEST(facter_facts_external_posix_execution_resolver, default_constructor) { + execution_resolver resolver; +} + +TEST(facter_facts_external_posix_execution_resolver, resolve_nonexistent_execution) { + execution_resolver resolver; + fact_map facts; + ASSERT_FALSE(resolver.resolve("does_not_exist", facts)); +} + +TEST(facter_facts_external_posix_execution_resolver, resolve_not_executable) { + execution_resolver resolver; + fact_map facts; + ASSERT_FALSE(resolver.resolve(LIBFACTER_TESTS_DIRECTORY "/fixtures/facts/external/posix/execution/not_executable", facts)); +} + +TEST(facter_facts_external_posix_execution_resolver, resolve_failed_execution) { + execution_resolver resolver; + fact_map facts; + ASSERT_THROW(resolver.resolve(LIBFACTER_TESTS_DIRECTORY "/fixtures/facts/external/posix/execution/failed", facts), external_fact_exception); +} + +TEST(facter_facts_external_posix_execution_resolver, resolve_execution) { + execution_resolver resolver; + fact_map facts; + ASSERT_TRUE(resolver.resolve(LIBFACTER_TESTS_DIRECTORY "/fixtures/facts/external/posix/execution/facts", facts)); + ASSERT_TRUE(!facts.empty()); + ASSERT_NE(nullptr, facts.get("exe_fact1")); + ASSERT_EQ("value1", facts.get("exe_fact1")->value()); + ASSERT_NE(nullptr, facts.get("exe_fact2")); + ASSERT_EQ("", facts.get("exe_fact2")->value()); + ASSERT_EQ(nullptr, facts.get("exe_fact3")); +} diff --git a/lib/tests/facts/fact_map.cc b/lib/tests/facts/fact_map.cc index 73e58c477d..5dec46b5ba 100644 --- a/lib/tests/facts/fact_map.cc +++ b/lib/tests/facts/fact_map.cc @@ -91,11 +91,12 @@ TEST(facter_facts_fact_map, resolve_external) { ASSERT_TRUE(facts.resolved()); facts.resolve_external({ LIBFACTER_TESTS_DIRECTORY "/fixtures/facts/external/yaml", - LIBFACTER_TESTS_DIRECTORY "/fixtures/facts/external/json" + LIBFACTER_TESTS_DIRECTORY "/fixtures/facts/external/json", + LIBFACTER_TESTS_DIRECTORY "/fixtures/facts/external/posix/execution", }); ASSERT_FALSE(facts.empty()); ASSERT_TRUE(facts.resolved()); - ASSERT_EQ(12u, facts.size()); + ASSERT_EQ(14u, facts.size()); ASSERT_NE(nullptr, facts.get("yaml_fact1")); ASSERT_NE(nullptr, facts.get("yaml_fact2")); ASSERT_NE(nullptr, facts.get("yaml_fact3")); @@ -108,6 +109,8 @@ TEST(facter_facts_fact_map, resolve_external) { ASSERT_NE(nullptr, facts.get("json_fact4")); ASSERT_NE(nullptr, facts.get("json_fact5")); ASSERT_NE(nullptr, facts.get("json_fact6")); + ASSERT_NE(nullptr, facts.get("exe_fact1")); + ASSERT_NE(nullptr, facts.get("exe_fact2")); } TEST(facter_facts_fact_map, each) { diff --git a/lib/tests/fixtures/facts/external/posix/execution/facts b/lib/tests/fixtures/facts/external/posix/execution/facts new file mode 100755 index 0000000000..58cb5e91b4 --- /dev/null +++ b/lib/tests/fixtures/facts/external/posix/execution/facts @@ -0,0 +1,4 @@ +#! /usr/bin/env sh +echo 'exe_fact1=value1' +echo 'exe_fact2=' +echo 'exe_fact3' \ No newline at end of file diff --git a/lib/tests/fixtures/facts/external/posix/execution/failed b/lib/tests/fixtures/facts/external/posix/execution/failed new file mode 100755 index 0000000000..4347274205 --- /dev/null +++ b/lib/tests/fixtures/facts/external/posix/execution/failed @@ -0,0 +1,3 @@ +#! /usr/bin/env sh +echo 'this script fails' +exit 1 \ No newline at end of file diff --git a/lib/tests/fixtures/facts/external/posix/execution/not_executable b/lib/tests/fixtures/facts/external/posix/execution/not_executable new file mode 100644 index 0000000000..16265bb1e2 --- /dev/null +++ b/lib/tests/fixtures/facts/external/posix/execution/not_executable @@ -0,0 +1 @@ +This file is not executable. \ No newline at end of file From 4f7b5c5214c85f47bc7b1881f986bdc61b2150da Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Wed, 21 May 2014 18:09:20 -0700 Subject: [PATCH 1888/3753] (maint) Fix crash when missing interface name or address. When enumerating interfaces in the BSD network facts provider, check that ifa_addr and ifa_name are populated for the interface. These members are allowed to be nullptr. Fixing the unnecessary copy of the netmask for IPv6 address-to-string conversions. Replacing the use of inet_ntoa with inet_ntop. --- lib/src/facts/bsd/networking_resolver.cc | 7 ++++--- lib/src/facts/posix/networking_resolver.cc | 10 +++++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/src/facts/bsd/networking_resolver.cc b/lib/src/facts/bsd/networking_resolver.cc index 6c91a37b63..3f5689f524 100644 --- a/lib/src/facts/bsd/networking_resolver.cc +++ b/lib/src/facts/bsd/networking_resolver.cc @@ -32,9 +32,10 @@ namespace facter { namespace facts { namespace bsd { multimap interface_map; for (ifaddrs* ptr = addrs; ptr; ptr = ptr->ifa_next) { // We only support IPv4, IPv6, and link interfaces - if (ptr->ifa_addr->sa_family != AF_INET && - ptr->ifa_addr->sa_family != AF_INET6 && - !is_link_address(ptr->ifa_addr)) { + if (!ptr->ifa_addr || !ptr->ifa_name || + (ptr->ifa_addr->sa_family != AF_INET && + ptr->ifa_addr->sa_family != AF_INET6 && + !is_link_address(ptr->ifa_addr))) { continue; } diff --git a/lib/src/facts/posix/networking_resolver.cc b/lib/src/facts/posix/networking_resolver.cc index 0bc892b2b8..3bd5d78874 100644 --- a/lib/src/facts/posix/networking_resolver.cc +++ b/lib/src/facts/posix/networking_resolver.cc @@ -101,17 +101,21 @@ namespace facter { namespace facts { namespace posix { if (mask && mask->sa_family == addr->sa_family) { ip.s_addr &= reinterpret_cast(mask)->sin_addr.s_addr; } - return inet_ntoa(ip); + + char buffer[INET_ADDRSTRLEN] = {}; + inet_ntop(AF_INET, &ip, buffer, sizeof(buffer)); + return buffer; } else if (addr->sa_family == AF_INET6) { in6_addr ip = reinterpret_cast(addr)->sin6_addr; // Apply an IPv6 mask if (mask && mask->sa_family == addr->sa_family) { - in6_addr mask_ip = reinterpret_cast(mask)->sin6_addr; + auto mask_ptr = reinterpret_cast(mask); for (size_t i = 0; i < 16; ++i) { - ip.s6_addr[i] &= mask_ip.s6_addr[i]; + ip.s6_addr[i] &= mask_ptr->sin6_addr.s6_addr[i]; } } + char buffer[INET6_ADDRSTRLEN] = {}; inet_ntop(AF_INET6, &ip, buffer, sizeof(buffer)); return buffer; From ed02c9a181e519544220f62f058e3f727f945757 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Wed, 21 May 2014 20:43:02 -0700 Subject: [PATCH 1889/3753] (maint) Fix inability to lookup netmask6 and network6 facts. The networking facts provider did not include the netmask6[_*] and network6[_*] facts in its constructor. As a result, we couldn't query these facts directly on cfacter's command line. --- lib/inc/facter/facts/posix/networking_resolver.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/inc/facter/facts/posix/networking_resolver.hpp b/lib/inc/facter/facts/posix/networking_resolver.hpp index 2a0223836d..ace698a95f 100644 --- a/lib/inc/facter/facts/posix/networking_resolver.hpp +++ b/lib/inc/facter/facts/posix/networking_resolver.hpp @@ -23,7 +23,9 @@ namespace facter { namespace facts { namespace posix { fact::ipaddress, fact::ipaddress6, fact::netmask, + fact::netmask6, fact::network, + fact::network6, fact::macaddress, fact::interfaces, fact::domain, @@ -34,7 +36,9 @@ namespace facter { namespace facts { namespace posix { std::string("^") + fact::ipaddress6 + "_", std::string("^") + fact::mtu + "_", std::string("^") + fact::netmask + "_", + std::string("^") + fact::netmask6 + "_", std::string("^") + fact::network + "_", + std::string("^") + fact::network6 + "_", std::string("^") + fact::macaddress + "_", }) { From 35d06b826f0e50f36653416c924aa51ad6523be6 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Wed, 21 May 2014 20:47:12 -0700 Subject: [PATCH 1890/3753] (maint) Enable better crash reporting in Travis. For the native unit tests, use libSegFault.so to print stack traces for a crash. For both the native and ruby tests, pipe the output through c++filt, which will demangle C++ symbols in any stack traces. --- scripts/travis_target.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/travis_target.sh b/scripts/travis_target.sh index f0c4583343..248b646c49 100755 --- a/scripts/travis_target.sh +++ b/scripts/travis_target.sh @@ -30,8 +30,8 @@ function travis_make() # Run tests if not doing cpplint if [ $1 != "cpplint" ]; then - ctest -V - if [ $? -ne 0 ]; then + LD_PRELOAD=/lib/x86_64-linux-gnu/libSegFault.so ctest -V 2>&1 | c++filt + if [ ${PIPESTATUS[0]} -ne 0 ]; then echo "tests reported an error." exit 1 fi @@ -61,8 +61,8 @@ function travis_make() echo "gem install failed." exit 1 fi - rspec - if [ $? -ne 0 ]; then + rspec 2>&1 | c++filt + if [ ${PIPESTATUS[0]} -ne 0 ]; then echo "gem specs failed." exit 1 fi From a44badad18ef507522f958563fe06eaffc980a15 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Thu, 22 May 2014 17:36:00 -0700 Subject: [PATCH 1891/3753] (CFACT-12) Implement external facts from text files. Implementing reading text files to populate external facts. Any line of key=value is turned into a string fact. --- gem/spec/unit/cfacter_spec.rb | 4 ++ lib/CMakeLists.txt | 1 + .../facter/facts/external/text_resolver.hpp | 24 +++++++++++ lib/src/facts/external/text_resolver.cc | 41 +++++++++++++++++++ lib/src/facts/posix/platform.cc | 2 + lib/tests/CMakeLists.txt | 1 + lib/tests/facts/external/text_resolver.cc | 37 +++++++++++++++++ lib/tests/facts/fact_map.cc | 7 +++- .../fixtures/facts/external/text/facts.txt | 3 ++ 9 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 lib/inc/facter/facts/external/text_resolver.hpp create mode 100644 lib/src/facts/external/text_resolver.cc create mode 100644 lib/tests/facts/external/text_resolver.cc create mode 100644 lib/tests/fixtures/facts/external/text/facts.txt diff --git a/gem/spec/unit/cfacter_spec.rb b/gem/spec/unit/cfacter_spec.rb index b329540c30..3b4eee7ffb 100644 --- a/gem/spec/unit/cfacter_spec.rb +++ b/gem/spec/unit/cfacter_spec.rb @@ -134,6 +134,7 @@ def enumerate(facts) CFacter.search_external([ File.expand_path('../../../lib/tests/fixtures/facts/external/yaml', File.dirname(__FILE__)), File.expand_path('../../../lib/tests/fixtures/facts/external/json', File.dirname(__FILE__)), + File.expand_path('../../../lib/tests/fixtures/facts/external/text', File.dirname(__FILE__)), File.expand_path('../../../lib/tests/fixtures/facts/external/posix/execution', File.dirname(__FILE__)), ]) CFacter.loadfacts @@ -153,6 +154,9 @@ def enumerate(facts) facts["exe_fact1"].should be_a String facts["exe_fact2"].should be_a String facts["exe_fact3"].should be_nil + facts["txt_fact1"].should be_a String + facts["txt_fact2"].should be_a String + facts["txt_fact3"].should be_nil end end diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index f7220c4dcc..dffa98be9d 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -24,6 +24,7 @@ set(LIBFACTER_COMMON_SOURCES "${CMAKE_CURRENT_LIST_DIR}/src/facterlib.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/array_value.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/external/json_resolver.cc" + "${CMAKE_CURRENT_LIST_DIR}/src/facts/external/text_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/external/yaml_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/fact.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/fact_map.cc" diff --git a/lib/inc/facter/facts/external/text_resolver.hpp b/lib/inc/facter/facts/external/text_resolver.hpp new file mode 100644 index 0000000000..3a95b5bd34 --- /dev/null +++ b/lib/inc/facter/facts/external/text_resolver.hpp @@ -0,0 +1,24 @@ +#ifndef FACTER_FACTS_EXTERNAL_TEXT_RESOLVER_HPP_ +#define FACTER_FACTS_EXTERNAL_TEXT_RESOLVER_HPP_ + +#include "resolver.hpp" + +namespace facter { namespace facts { namespace external { + + /** + * Responsible for resolving facts from text files. + */ + struct text_resolver : resolver + { + /** + * Resolves facts from the given file. + * @param path The path to the file to resolve facts from. + * @param facts The fact map to populate the external facts into. + * @return Returns true if the facts were resolved or false if the given file is not supported. + */ + virtual bool resolve(std::string const& path, fact_map& facts) const; + }; + +}}} // namespace facter::facts::external + +#endif // FACTER_FACTS_EXTERNAL_TEXT_RESOLVER_HPP_ diff --git a/lib/src/facts/external/text_resolver.cc b/lib/src/facts/external/text_resolver.cc new file mode 100644 index 0000000000..8c0104804c --- /dev/null +++ b/lib/src/facts/external/text_resolver.cc @@ -0,0 +1,41 @@ +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace facter::util; + +LOG_DECLARE_NAMESPACE("facts.external.text"); + +namespace facter { namespace facts { namespace external { + + bool text_resolver::resolve(string const& path, fact_map& facts) const + { + string full_path = path; + if (!ends_with(to_lower(full_path), ".txt")) { + return false; + } + + LOG_DEBUG("resolving facts from text file \"%1%\".", path); + + if (!file::each_line(path, [&facts](string& line) { + auto pos = line.find('='); + if (pos == string::npos) { + LOG_DEBUG("ignoring line in output: %1%", line); + return true; + } + // Add as a string fact + facts.add(line.substr(0, pos), make_value(line.substr(pos+1))); + return true; + })) { + throw external_fact_exception("file could not be opened."); + } + + LOG_DEBUG("completed resolving facts from text file \"%1%\".", path); + return true; + } + +}}} // namespace facter::facts::external diff --git a/lib/src/facts/posix/platform.cc b/lib/src/facts/posix/platform.cc index 906a97022a..bcc49f695e 100644 --- a/lib/src/facts/posix/platform.cc +++ b/lib/src/facts/posix/platform.cc @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -33,6 +34,7 @@ namespace facter { namespace facts { vector> get_external_resolvers() { vector> resolvers; + resolvers.emplace_back(new text_resolver()); resolvers.emplace_back(new yaml_resolver()); resolvers.emplace_back(new json_resolver()); diff --git a/lib/tests/CMakeLists.txt b/lib/tests/CMakeLists.txt index 788cc705c6..44465af788 100644 --- a/lib/tests/CMakeLists.txt +++ b/lib/tests/CMakeLists.txt @@ -16,6 +16,7 @@ set(LIBFACTER_TESTS_COMMON_SOURCES "${CMAKE_CURRENT_LIST_DIR}/facts/boolean_value.cc" "${CMAKE_CURRENT_LIST_DIR}/facts/double_value.cc" "${CMAKE_CURRENT_LIST_DIR}/facts/external/json_resolver.cc" + "${CMAKE_CURRENT_LIST_DIR}/facts/external/text_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/facts/external/yaml_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/facts/fact_map.cc" "${CMAKE_CURRENT_LIST_DIR}/facts/integer_value.cc" diff --git a/lib/tests/facts/external/text_resolver.cc b/lib/tests/facts/external/text_resolver.cc new file mode 100644 index 0000000000..372717daa1 --- /dev/null +++ b/lib/tests/facts/external/text_resolver.cc @@ -0,0 +1,37 @@ +#include +#include +#include +#include +#include "../../fixtures.hpp" + +using namespace std; +using namespace facter::facts; +using namespace facter::facts::external; + +TEST(facter_facts_external_text_resolver, default_constructor) { + text_resolver resolver; +} + +TEST(facter_facts_external_text_resolver, resolve_non_text) { + text_resolver resolver; + fact_map facts; + ASSERT_FALSE(resolver.resolve("foo.json", facts)); +} + +TEST(facter_facts_external_text_resolver, resolve_nonexistent_text) { + text_resolver resolver; + fact_map facts; + ASSERT_THROW(resolver.resolve("doesnotexist.txt", facts), external_fact_exception); +} + +TEST(facter_facts_external_text_resolver, resolve_text) { + text_resolver resolver; + fact_map facts; + ASSERT_TRUE(resolver.resolve(LIBFACTER_TESTS_DIRECTORY "/fixtures/facts/external/text/facts.txt", facts)); + ASSERT_TRUE(!facts.empty()); + ASSERT_NE(nullptr, facts.get("txt_fact1")); + ASSERT_EQ("value1", facts.get("txt_fact1")->value()); + ASSERT_NE(nullptr, facts.get("txt_fact2")); + ASSERT_EQ("", facts.get("txt_fact2")->value()); + ASSERT_EQ(nullptr, facts.get("txt_fact3")); +} diff --git a/lib/tests/facts/fact_map.cc b/lib/tests/facts/fact_map.cc index 5dec46b5ba..97fd8535cd 100644 --- a/lib/tests/facts/fact_map.cc +++ b/lib/tests/facts/fact_map.cc @@ -92,11 +92,12 @@ TEST(facter_facts_fact_map, resolve_external) { facts.resolve_external({ LIBFACTER_TESTS_DIRECTORY "/fixtures/facts/external/yaml", LIBFACTER_TESTS_DIRECTORY "/fixtures/facts/external/json", + LIBFACTER_TESTS_DIRECTORY "/fixtures/facts/external/text", LIBFACTER_TESTS_DIRECTORY "/fixtures/facts/external/posix/execution", }); ASSERT_FALSE(facts.empty()); ASSERT_TRUE(facts.resolved()); - ASSERT_EQ(14u, facts.size()); + ASSERT_EQ(16u, facts.size()); ASSERT_NE(nullptr, facts.get("yaml_fact1")); ASSERT_NE(nullptr, facts.get("yaml_fact2")); ASSERT_NE(nullptr, facts.get("yaml_fact3")); @@ -111,6 +112,10 @@ TEST(facter_facts_fact_map, resolve_external) { ASSERT_NE(nullptr, facts.get("json_fact6")); ASSERT_NE(nullptr, facts.get("exe_fact1")); ASSERT_NE(nullptr, facts.get("exe_fact2")); + ASSERT_EQ(nullptr, facts.get("exe_fact3")); + ASSERT_NE(nullptr, facts.get("txt_fact1")); + ASSERT_NE(nullptr, facts.get("txt_fact2")); + ASSERT_EQ(nullptr, facts.get("txt_fact3")); } TEST(facter_facts_fact_map, each) { diff --git a/lib/tests/fixtures/facts/external/text/facts.txt b/lib/tests/fixtures/facts/external/text/facts.txt new file mode 100644 index 0000000000..eba6802abe --- /dev/null +++ b/lib/tests/fixtures/facts/external/text/facts.txt @@ -0,0 +1,3 @@ +txt_fact1=value1 +txt_fact2= +not a fact From 29ebad9e7737578e72466fe37009b8e9e118e711 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Fri, 23 May 2014 13:15:06 -0700 Subject: [PATCH 1892/3753] Revert "(maint) Ensure specs don't generate flattened ec2 facts" This reverts commit a417998966928c55dd0ed104608b7ac4311c3a62. --- spec/unit/ec2_spec.rb | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/spec/unit/ec2_spec.rb b/spec/unit/ec2_spec.rb index 28ad20a536..109c9700e8 100755 --- a/spec/unit/ec2_spec.rb +++ b/spec/unit/ec2_spec.rb @@ -6,11 +6,9 @@ before do Facter::EC2::Metadata.stubs(:new).returns querier - + Facter.collection.internal_loader.load(:ec2) # Prevent flattened facts from forcing evaluation of the ec2 metadata fact Facter.stubs(:value).with(:ec2_metadata) - Facter.collection.internal_loader.load(:ec2) - Facter.unstub(:value) end subject { Facter.fact(:ec2_metadata).resolution(:rest) } @@ -56,11 +54,7 @@ before do Facter::EC2::Userdata.stubs(:new).returns querier - - # Prevent flattened facts from forcing evaluation of the ec2 metadata fact - Facter.stubs(:value).with(:ec2_metadata) Facter.collection.internal_loader.load(:ec2) - Facter.unstub(:value) end subject { Facter.fact(:ec2_userdata).resolution(:rest) } From adf8f4250a82d6cc6ec2f6328b750739237be6e3 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Fri, 23 May 2014 13:15:14 -0700 Subject: [PATCH 1893/3753] Revert "(maint) Don't evaluate ec2_metadata fact when loading for tests" This reverts commit b9e5e6d062db52c6b9e5d715cee858e41b151fa0. --- spec/unit/ec2_spec.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/spec/unit/ec2_spec.rb b/spec/unit/ec2_spec.rb index 109c9700e8..bf2aa2f5ea 100755 --- a/spec/unit/ec2_spec.rb +++ b/spec/unit/ec2_spec.rb @@ -7,14 +7,11 @@ before do Facter::EC2::Metadata.stubs(:new).returns querier Facter.collection.internal_loader.load(:ec2) - # Prevent flattened facts from forcing evaluation of the ec2 metadata fact - Facter.stubs(:value).with(:ec2_metadata) end subject { Facter.fact(:ec2_metadata).resolution(:rest) } it "is unsuitable if the virtual fact is not xen" do - querier.stubs(:reachable?).returns false Facter.fact(:virtual).stubs(:value).returns "kvm" expect(subject).to_not be_suitable end @@ -60,7 +57,6 @@ subject { Facter.fact(:ec2_userdata).resolution(:rest) } it "is unsuitable if the virtual fact is not xen" do - querier.stubs(:reachable?).returns(true) Facter.fact(:virtual).stubs(:value).returns "kvm" expect(subject).to_not be_suitable end From 75402bee698cc5c8d5049af5ce0e15b928f5021b Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Fri, 23 May 2014 13:15:27 -0700 Subject: [PATCH 1894/3753] Revert "Merge pull request #661 from adrienthebo/feature/facter-2/fact-185-structured-ec2-metadata" This reverts commit e3da03097164fd973aef79bef507426edb7e5ced, reversing changes made to 2ce8849b75339f577f08375adabfdbf50d932c71. --- lib/facter/core/suitable.rb | 6 +- lib/facter/ec2.rb | 59 +++--- lib/facter/ec2/rest.rb | 129 ------------ lib/facter/util/ec2.rb | 5 - lib/facter/util/values.rb | 29 --- spec/fixtures/unit/ec2/rest/meta-data/root | 20 -- spec/unit/core/suitable_spec.rb | 10 - spec/unit/ec2/rest_spec.rb | 140 ------------- spec/unit/ec2_spec.rb | 228 ++++++++++++++------- spec/unit/util/ec2_spec.rb | 4 - spec/unit/util/values_spec.rb | 40 ---- 11 files changed, 180 insertions(+), 490 deletions(-) delete mode 100644 lib/facter/ec2/rest.rb delete mode 100644 spec/fixtures/unit/ec2/rest/meta-data/root delete mode 100644 spec/unit/ec2/rest_spec.rb diff --git a/lib/facter/core/suitable.rb b/lib/facter/core/suitable.rb index 0262470919..1b3c04fceb 100644 --- a/lib/facter/core/suitable.rb +++ b/lib/facter/core/suitable.rb @@ -108,6 +108,10 @@ def weight # # @api private def suitable? - @confines.all? { |confine| confine.true? } + unless defined? @suitable + @suitable = ! @confines.detect { |confine| ! confine.true? } + end + + return @suitable end end diff --git a/lib/facter/ec2.rb b/lib/facter/ec2.rb index 2e575927cd..09e0109528 100644 --- a/lib/facter/ec2.rb +++ b/lib/facter/ec2.rb @@ -1,44 +1,37 @@ -require 'facter/ec2/rest' +require 'facter/util/ec2' +require 'open-uri' -Facter.define_fact(:ec2_metadata) do - define_resolution(:rest) do - confine do - Facter.value(:virtual).match /^xen/ - end - - @querier = Facter::EC2::Metadata.new - confine do - @querier.reachable? - end - - setcode do - @querier.fetch +def metadata(id = "") + open("http://169.254.169.254/2008-02-01/meta-data/#{id||=''}").read. + split("\n").each do |o| + key = "#{id}#{o.gsub(/\=.*$/, '/')}" + if key[-1..-1] != '/' + value = open("http://169.254.169.254/2008-02-01/meta-data/#{key}").read. + split("\n") + symbol = "ec2_#{key.gsub(/\-|\//, '_')}".to_sym + Facter.add(symbol) { setcode { value.join(',') } } + else + metadata(key) end end +rescue => details + Facter.warn "Could not retrieve ec2 metadata: #{details.message}" end -Facter.define_fact(:ec2_userdata) do - define_resolution(:rest) do - confine do - Facter.value(:virtual).match /^xen/ - end - - @querier = Facter::EC2::Userdata.new - confine do - @querier.reachable? - end - +def userdata() + Facter.add(:ec2_userdata) do setcode do - @querier.fetch + if userdata = Facter::Util::EC2.userdata + userdata.split + end end end end -# The flattened version of the EC2 facts are deprecated and will be removed in -# a future release of Facter. -if (ec2_metadata = Facter.value(:ec2_metadata)) - ec2_facts = Facter::Util::Values.flatten_structure("ec2", ec2_metadata) - ec2_facts.each_pair do |factname, factvalue| - Facter.add(factname, :value => factvalue) - end +if (Facter::Util::EC2.has_euca_mac? || Facter::Util::EC2.has_openstack_mac? || + Facter::Util::EC2.has_ec2_arp?) && Facter::Util::EC2.can_connect? + metadata + userdata +else + Facter.debug "Not an EC2 host" end diff --git a/lib/facter/ec2/rest.rb b/lib/facter/ec2/rest.rb deleted file mode 100644 index e9c1fcadde..0000000000 --- a/lib/facter/ec2/rest.rb +++ /dev/null @@ -1,129 +0,0 @@ -require 'timeout' -require 'open-uri' - -module Facter - module EC2 - CONNECTION_ERRORS = [ - Errno::EHOSTDOWN, - Errno::EHOSTUNREACH, - Errno::ENETUNREACH, - Errno::ECONNABORTED, - Errno::ECONNREFUSED, - Errno::ECONNRESET, - Errno::ETIMEDOUT, - ] - - class Base - def reachable?(retry_limit = 3) - timeout = 0.2 - able_to_connect = false - attempts = 0 - - begin - Timeout.timeout(timeout) do - open(@baseurl).read - end - able_to_connect = true - rescue OpenURI::HTTPError => e - if e.message.match /404 Not Found/i - able_to_connect = false - else - retry if attempts < retry_limit - end - rescue Timeout::Error - retry if attempts < retry_limit - rescue *CONNECTION_ERRORS - retry if attempts < retry_limit - ensure - attempts = attempts + 1 - end - - able_to_connect - end - end - - class Metadata < Base - - DEFAULT_URI = "http://169.254.169.254/latest/meta-data/" - - def initialize(uri = DEFAULT_URI) - @baseurl = uri - end - - def fetch(path = '') - results = {} - - keys = fetch_endpoint(path) - keys.each do |key| - if key.match(%r[/$]) - # If a metadata key is suffixed with '/' then it's a general metadata - # resource, so we have to recursively look up all the keys in the given - # collection. - name = key[0..-2] - results[name] = fetch("#{path}#{key}") - else - # This is a simple key/value pair, we can just query the given endpoint - # and store the results. - ret = fetch_endpoint("#{path}#{key}") - results[key] = ret.size > 1 ? ret : ret.first - end - end - - results - end - - # @param path [String] The path relative to the object base url - # - # @return [Array, NilClass] - def fetch_endpoint(path) - uri = @baseurl + path - body = open(uri).read - parse_results(body) - rescue OpenURI::HTTPError => e - if e.message.match /404 Not Found/i - return nil - else - Facter.log_exception(e, "Failed to fetch ec2 uri #{uri}: #{e.message}") - return nil - end - rescue *CONNECTION_ERRORS => e - Facter.log_exception(e, "Failed to fetch ec2 uri #{uri}: #{e.message}") - return nil - end - - private - - def parse_results(body) - lines = body.split("\n") - lines.map do |line| - if (match = line.match(/^(\d+)=.*$/)) - # Metadata arrays are formatted like '=/', so - # we need to extract the index from that output. - "#{match[1]}/" - else - line - end - end - end - end - - class Userdata < Base - DEFAULT_URI = "http://169.254.169.254/latest/user-data/" - - def initialize(uri = DEFAULT_URI) - @baseurl = uri - end - - def fetch - open(@baseurl).read - rescue OpenURI::HTTPError => e - if e.message.match /404 Not Found/i - return nil - else - Facter.log_exception(e, "Failed to fetch ec2 uri #{uri}: #{e.message}") - return nil - end - end - end - end -end diff --git a/lib/facter/util/ec2.rb b/lib/facter/util/ec2.rb index b81c8febe8..c6e5dcadd4 100644 --- a/lib/facter/util/ec2.rb +++ b/lib/facter/util/ec2.rb @@ -11,7 +11,6 @@ class << self # The +wait_sec+ parameter provides you with an adjustable timeout. # def can_connect?(wait_sec=2) - Facter.warnonce("#{self}.#{__method__} is deprecated; see the Facter::EC2 classes instead") url = "http://169.254.169.254/" Timeout::timeout(wait_sec) {open(url)} return true @@ -24,7 +23,6 @@ def can_connect?(wait_sec=2) # Test if this host has a mac address used by Eucalyptus clouds, which # normally is +d0:0d+. def has_euca_mac? - Facter.warnonce("#{self}.#{__method__} is deprecated; see the Facter::EC2 classes instead") !!(Facter.value(:macaddress) =~ %r{^[dD]0:0[dD]:}) end @@ -32,14 +30,12 @@ def has_euca_mac? # normally starts with FA:16:3E (older versions of OpenStack # may generate mac addresses starting with 02:16:3E) def has_openstack_mac? - Facter.warnonce("#{self}.#{__method__} is deprecated; see the Facter::EC2 classes instead") !!(Facter.value(:macaddress) =~ %r{^(02|[fF][aA]):16:3[eE]}) end # Test if the host has an arp entry in its cache that matches the EC2 arp, # which is normally +fe:ff:ff:ff:ff:ff+. def has_ec2_arp? - Facter.warnonce("#{self}.#{__method__} is deprecated; see the Facter::EC2 classes instead") kernel = Facter.value(:kernel) mac_address_re = case kernel @@ -77,7 +73,6 @@ def has_ec2_arp? # # @return [String] containing the response body or `nil` def self.userdata(version="latest") - Facter.warnonce("#{self}.#{__method__} is deprecated; see the Facter::EC2 classes instead") uri = "http://169.254.169.254/#{version}/user-data/" begin read_uri(uri) diff --git a/lib/facter/util/values.rb b/lib/facter/util/values.rb index 1fbb1b686e..a7048d54da 100644 --- a/lib/facter/util/values.rb +++ b/lib/facter/util/values.rb @@ -1,4 +1,3 @@ - module Facter module Util # A util module for facter containing helper methods @@ -76,34 +75,6 @@ def convert(value) value = value.downcase if value.is_a?(String) value end - - # Flatten the given data structure to something that's suitable to return - # as flat facts. - # - # @param path [String] The fact path to be prefixed to the given value. - # @param structure [Object] The data structure to flatten. Nested hashes - # will be recursively flattened, everything else will be returned as-is. - # - # @return [Hash] The given data structure prefixed with the given path - def flatten_structure(path, structure) - results = {} - - if structure.is_a? Hash - structure.each_pair do |name, value| - new_path = "#{path}_#{name}".gsub(/\-|\//, '_') - results.merge! flatten_structure(new_path, value) - end - elsif structure.is_a? Array - structure.each_with_index do |value, index| - new_path = "#{path}_#{index}" - results.merge! flatten_structure(new_path, value) - end - else - results[path] = structure - end - - results - end end end end diff --git a/spec/fixtures/unit/ec2/rest/meta-data/root b/spec/fixtures/unit/ec2/rest/meta-data/root deleted file mode 100644 index 9ec3bbedad..0000000000 --- a/spec/fixtures/unit/ec2/rest/meta-data/root +++ /dev/null @@ -1,20 +0,0 @@ -ami-id -ami-launch-index -ami-manifest-path -block-device-mapping/ -hostname -instance-action -instance-id -instance-type -kernel-id -local-hostname -local-ipv4 -mac -metrics/ -network/ -placement/ -profile -public-hostname -public-ipv4 -public-keys/ -reservation-id diff --git a/spec/unit/core/suitable_spec.rb b/spec/unit/core/suitable_spec.rb index 8277408799..4c0b1fde06 100644 --- a/spec/unit/core/suitable_spec.rb +++ b/spec/unit/core/suitable_spec.rb @@ -92,15 +92,5 @@ def initialize expect(subject).to_not be_suitable end - - it "recalculates suitability on every invocation" do - subject.confine :kernel => 'Linux' - - subject.confines.first.stubs(:true?).returns false - expect(subject).to_not be_suitable - subject.confines.first.unstub(:true?) - subject.confines.first.stubs(:true?).returns true - expect(subject).to be_suitable - end end end diff --git a/spec/unit/ec2/rest_spec.rb b/spec/unit/ec2/rest_spec.rb deleted file mode 100644 index 5c74b495f4..0000000000 --- a/spec/unit/ec2/rest_spec.rb +++ /dev/null @@ -1,140 +0,0 @@ -require 'spec_helper' -require 'facter/ec2/rest' - -shared_examples_for "an ec2 rest querier" do - describe "determining if the uri is reachable" do - it "retries if the connection times out" do - subject.stubs(:open).returns(stub(:read => nil)) - Timeout.expects(:timeout).with(0.2).twice.raises(Timeout::Error).returns(true) - expect(subject).to be_reachable - end - - it "retries if the connection is reset" do - subject.expects(:open).twice.raises(Errno::ECONNREFUSED).returns(StringIO.new("woo")) - expect(subject).to be_reachable - end - - it "is false if the given uri returns a 404" do - subject.expects(:open).with(anything).once.raises(OpenURI::HTTPError.new("404 Not Found", StringIO.new("woo"))) - expect(subject).to_not be_reachable - end - end - -end - -describe Facter::EC2::Metadata do - - subject { described_class.new('http://0.0.0.0/latest/meta-data/') } - - let(:response) { StringIO.new } - - describe "fetching a metadata endpoint" do - it "splits the body into an array" do - response.string = my_fixture_read("meta-data/root") - subject.stubs(:open).with("http://0.0.0.0/latest/meta-data/").returns response - output = subject.fetch_endpoint('') - - expect(output).to eq %w[ - ami-id ami-launch-index ami-manifest-path block-device-mapping/ hostname - instance-action instance-id instance-type kernel-id local-hostname - local-ipv4 mac metrics/ network/ placement/ profile public-hostname - public-ipv4 public-keys/ reservation-id - ] - end - - it "reformats keys that are array indices" do - response.string = "0=adrien@grey/" - subject.stubs(:open).with("http://0.0.0.0/latest/meta-data/public-keys/").returns response - output = subject.fetch_endpoint("public-keys/") - - expect(output).to eq %w[0/] - end - - it "returns nil if the endpoint returns a 404" do - Facter.expects(:log_exception).never - subject.stubs(:open).with("http://0.0.0.0/latest/meta-data/public-keys/1/").raises OpenURI::HTTPError.new("404 Not Found", response) - output = subject.fetch_endpoint('public-keys/1/') - - expect(output).to be_nil - end - - it "logs an error if the endpoint raises a non-404 HTTPError" do - Facter.expects(:log_exception).with(instance_of(OpenURI::HTTPError), anything) - - subject.stubs(:open).with("http://0.0.0.0/latest/meta-data/").raises OpenURI::HTTPError.new("418 I'm a Teapot", response) - output = subject.fetch_endpoint("") - - expect(output).to be_nil - end - - it "logs an error if the endpoint raises a connection error" do - Facter.expects(:log_exception).with(instance_of(Errno::ECONNREFUSED), anything) - - subject.stubs(:open).with("http://0.0.0.0/latest/meta-data/").raises Errno::ECONNREFUSED - output = subject.fetch_endpoint('') - - expect(output).to be_nil - end - end - - describe "recursively fetching the EC2 metadata API" do - it "queries the given endpoint for metadata keys" do - subject.expects(:fetch_endpoint).with("").returns([]) - subject.fetch - end - - it "fetches the value for a simple metadata key" do - subject.expects(:fetch_endpoint).with("").returns(['indexthing']) - subject.expects(:fetch_endpoint).with("indexthing").returns(['first', 'second']) - - output = subject.fetch - expect(output).to eq({'indexthing' => ['first', 'second']}) - end - - it "unwraps metadata values that are in single element arrays" do - subject.expects(:fetch_endpoint).with("").returns(['ami-id']) - subject.expects(:fetch_endpoint).with("ami-id").returns(['i-12x']) - - output = subject.fetch - expect(output).to eq({'ami-id' => 'i-12x'}) - end - - it "recursively queries an endpoint if the key ends with '/'" do - subject.expects(:fetch_endpoint).with("").returns(['metrics/']) - subject.expects(:fetch_endpoint).with("metrics/").returns(['vhostmd']) - subject.expects(:fetch_endpoint).with("metrics/vhostmd").returns(['woo']) - - output = subject.fetch - expect(output).to eq({'metrics' => {'vhostmd' => 'woo'}}) - end - end - - it_behaves_like "an ec2 rest querier" -end - -describe Facter::EC2::Userdata do - - subject { described_class.new('http://0.0.0.0/latest/user-data/') } - - let(:response) { StringIO.new } - - describe "reaching the userdata" do - it "queries the userdata URI" do - subject.expects(:open).with('http://0.0.0.0/latest/user-data/').returns(response) - subject.fetch - end - - it "returns the result of the query without modification" do - response.string = "clooouuuuud" - subject.expects(:open).with('http://0.0.0.0/latest/user-data/').returns(response) - expect(subject.fetch).to eq "clooouuuuud" - end - - it "is nil if the URI returned a 404" do - subject.expects(:open).with('http://0.0.0.0/latest/user-data/').once.raises(OpenURI::HTTPError.new("404 Not Found", StringIO.new("woo"))) - expect(subject.fetch).to be_nil - end - end - - it_behaves_like "an ec2 rest querier" -end diff --git a/spec/unit/ec2_spec.rb b/spec/unit/ec2_spec.rb index bf2aa2f5ea..f26a61316f 100755 --- a/spec/unit/ec2_spec.rb +++ b/spec/unit/ec2_spec.rb @@ -1,117 +1,187 @@ +#! /usr/bin/env ruby + require 'spec_helper' -require 'facter/ec2/rest' +require 'facter/util/ec2' + +describe "ec2 facts" do + # This is the standard prefix for making an API call in EC2 (or fake) + # environments. + let(:api_prefix) { "http://169.254.169.254/" } + + describe "when running on ec2" do + before :each do + # This is an ec2 instance, not a eucalyptus instance + Facter::Util::EC2.stubs(:has_euca_mac?).returns(false) + Facter::Util::EC2.stubs(:has_openstack_mac?).returns(false) + Facter::Util::EC2.stubs(:has_ec2_arp?).returns(true) + + # Assume we can connect + Facter::Util::EC2.stubs(:can_connect?).returns(true) + end -describe "ec2_metadata" do - let(:querier) { stub('EC2 metadata querier') } + it "should create flat meta-data facts" do + Object.any_instance.expects(:open). + with("#{api_prefix}/2008-02-01/meta-data/"). + at_least_once.returns(StringIO.new("foo")) - before do - Facter::EC2::Metadata.stubs(:new).returns querier - Facter.collection.internal_loader.load(:ec2) - end + Object.any_instance.expects(:open). + with("#{api_prefix}/2008-02-01/meta-data/foo"). + at_least_once.returns(StringIO.new("bar")) - subject { Facter.fact(:ec2_metadata).resolution(:rest) } + Facter.collection.internal_loader.load(:ec2) - it "is unsuitable if the virtual fact is not xen" do - Facter.fact(:virtual).stubs(:value).returns "kvm" - expect(subject).to_not be_suitable - end + Facter.fact(:ec2_foo).value.should == "bar" + end - it "is unsuitable if ec2 endpoint is not reachable" do - Facter.fact(:virtual).stubs(:value).returns "xen" - querier.stubs(:reachable?).returns false - expect(subject).to_not be_suitable - end + it "should create flat meta-data facts with comma seperation" do + Object.any_instance.expects(:open). + with("#{api_prefix}/2008-02-01/meta-data/"). + at_least_once.returns(StringIO.new("foo")) + + Object.any_instance.expects(:open). + with("#{api_prefix}/2008-02-01/meta-data/foo"). + at_least_once.returns(StringIO.new("bar\nbaz")) - describe "when the ec2 endpoint is reachable" do - before do - querier.stubs(:reachable?).returns true + Facter.collection.internal_loader.load(:ec2) + + Facter.fact(:ec2_foo).value.should == "bar,baz" end - it "is suitable if the virtual fact is xen" do - Facter.fact(:virtual).stubs(:value).returns "xen" - subject.suitable? + it "should create structured meta-data facts" do + Object.any_instance.expects(:open). + with("#{api_prefix}/2008-02-01/meta-data/"). + at_least_once.returns(StringIO.new("foo/")) + + Object.any_instance.expects(:open). + with("#{api_prefix}/2008-02-01/meta-data/foo/"). + at_least_once.returns(StringIO.new("bar")) - expect(subject).to be_suitable + Object.any_instance.expects(:open). + with("#{api_prefix}/2008-02-01/meta-data/foo/bar"). + at_least_once.returns(StringIO.new("baz")) + + Facter.collection.internal_loader.load(:ec2) + + Facter.fact(:ec2_foo_bar).value.should == "baz" end - it "is suitable if the virtual fact is xenu" do - Facter.fact(:virtual).stubs(:value).returns "xenu" - expect(subject).to be_suitable + it "should create ec2_user_data fact" do + # No meta-data + Object.any_instance.expects(:open). + with("#{api_prefix}/2008-02-01/meta-data/"). + at_least_once.returns(StringIO.new("")) + + Facter::Util::EC2.stubs(:read_uri). + with("#{api_prefix}/latest/user-data/"). + returns("test") + + Facter.collection.internal_loader.load(:ec2) + Facter.fact(:ec2_userdata).value.should == ["test"] end end - it "resolves the value by recursively querying the rest endpoint" do - querier.expects(:fetch).returns({"hello" => "world"}) - expect(subject.value).to eq({"hello" => "world"}) - end -end + describe "when running on eucalyptus" do + before :each do + # Return false for ec2, true for eucalyptus + Facter::Util::EC2.stubs(:has_euca_mac?).returns(true) + Facter::Util::EC2.stubs(:has_openstack_mac?).returns(false) + Facter::Util::EC2.stubs(:has_ec2_arp?).returns(false) -describe "ec2_userdata" do - let(:querier) { stub('EC2 metadata querier') } + # Assume we can connect + Facter::Util::EC2.stubs(:can_connect?).returns(true) + end - before do - Facter::EC2::Userdata.stubs(:new).returns querier - Facter.collection.internal_loader.load(:ec2) - end + it "should create ec2_user_data fact" do + # No meta-data + Object.any_instance.expects(:open).\ + with("#{api_prefix}/2008-02-01/meta-data/").\ + at_least_once.returns(StringIO.new("")) - subject { Facter.fact(:ec2_userdata).resolution(:rest) } + Facter::Util::EC2.stubs(:read_uri). + with("#{api_prefix}/latest/user-data/"). + returns("test") - it "is unsuitable if the virtual fact is not xen" do - Facter.fact(:virtual).stubs(:value).returns "kvm" - expect(subject).to_not be_suitable - end + # Force a fact load + Facter.collection.internal_loader.load(:ec2) - it "is unsuitable if ec2 endpoint is not reachable" do - Facter.fact(:virtual).stubs(:value).returns "xen" - querier.stubs(:reachable?).returns false - expect(subject).to_not be_suitable + Facter.fact(:ec2_userdata).value.should == ["test"] + end end - describe "when the ec2 endpoint is reachable" do - before do - querier.stubs(:reachable?).returns true + describe "when running on openstack" do + before :each do + # Return false for ec2, true for eucalyptus + Facter::Util::EC2.stubs(:has_openstack_mac?).returns(true) + Facter::Util::EC2.stubs(:has_euca_mac?).returns(false) + Facter::Util::EC2.stubs(:has_ec2_arp?).returns(false) + + # Assume we can connect + Facter::Util::EC2.stubs(:can_connect?).returns(true) end - it "is suitable if the virtual fact is xen" do - Facter.fact(:virtual).stubs(:value).returns "xen" - expect(subject).to be_suitable + it "should create ec2_user_data fact" do + # No meta-data + Object.any_instance.expects(:open).\ + with("#{api_prefix}/2008-02-01/meta-data/").\ + at_least_once.returns(StringIO.new("")) + + Facter::Util::EC2.stubs(:read_uri). + with("#{api_prefix}/latest/user-data/"). + returns("test") + + # Force a fact load + Facter.collection.internal_loader.load(:ec2) + + Facter.fact(:ec2_userdata).value.should == ["test"] end - it "is suitable if the virtual fact is xenu" do - Facter.fact(:virtual).stubs(:value).returns "xenu" - expect(subject).to be_suitable + it "should return nil if open fails" do + Facter.stubs(:warn) # do not pollute test output + Facter.expects(:warn).with('Could not retrieve ec2 metadata: host unreachable') + + Object.any_instance.expects(:open). + with("#{api_prefix}/2008-02-01/meta-data/"). + at_least_once.raises(RuntimeError, 'host unreachable') + + Facter::Util::EC2.stubs(:read_uri). + with("#{api_prefix}/latest/user-data/"). + raises(RuntimeError, 'host unreachable') + + # Force a fact load + Facter.collection.internal_loader.load(:ec2) + + Facter.fact(:ec2_userdata).value.should be_nil end - end - it "resolves the value by fetching the rest endpoint" do - querier.expects(:fetch).returns "user data!" - expect(subject.value).to eq "user data!" end -end -describe "flattened versions of ec2 facts" do - # These facts are tricky to test because they are dynamic facts, and they are - # generated from a fact that is defined in the same file. In order to pull - # this off we need to define the ec2_metadata fact ahead of time so that we - # can stub the value, and then manually load the correct files. + describe "when api connect test fails" do + before :each do + Facter.stubs(:warnonce) + end - it "unpacks the ec2_metadata fact" do - Facter.define_fact(:ec2_metadata).stubs(:value).returns({"hello" => "world"}) - Facter.collection.internal_loader.load(:ec2) + it "should not populate ec2_userdata" do + # Emulate ec2 for now as it matters little to this test + Facter::Util::EC2.stubs(:has_euca_mac?).returns(true) + Facter::Util::EC2.stubs(:has_ec2_arp?).never + Facter::Util::EC2.expects(:can_connect?).at_least_once.returns(false) - expect(Facter.value("ec2_hello")).to eq "world" - end + # The API should never be called at this point + Object.any_instance.expects(:open). + with("#{api_prefix}/2008-02-01/meta-data/").never + Object.any_instance.expects(:open). + with("#{api_prefix}/2008-02-01/user-data/").never - it "does not set any flat ec2 facts if the ec2_metadata fact is nil" do - Facter.define_fact(:ec2_metadata).stubs(:value) - Facter.define_fact(:ec2_userdata).stubs(:value).returns(nil) + # Force a fact load + Facter.collection.internal_loader.load(:ec2) - Facter.collection.internal_loader.load(:ec2) + Facter.fact(:ec2_userdata).should == nil + end - all_facts = Facter.collection.to_hash + it "should rescue the exception" do + Facter::Util::EC2.expects(:open).with("#{api_prefix}:80/").raises(Timeout::Error) - ec2_facts = all_facts.keys.select { |k| k =~ /^ec2_/ } - expect(ec2_facts).to be_empty + Facter::Util::EC2.should_not be_can_connect + end end - end diff --git a/spec/unit/util/ec2_spec.rb b/spec/unit/util/ec2_spec.rb index 7af59d8334..f963db6c32 100755 --- a/spec/unit/util/ec2_spec.rb +++ b/spec/unit/util/ec2_spec.rb @@ -4,10 +4,6 @@ require 'facter/util/ec2' describe Facter::Util::EC2 do - before do - # Squelch deprecation notices - Facter.stubs(:warnonce) - end # This is the standard prefix for making an API call in EC2 (or fake) # environments. let(:api_prefix) { "http://169.254.169.254/" } diff --git a/spec/unit/util/values_spec.rb b/spec/unit/util/values_spec.rb index bc347c49a6..557eb195e6 100644 --- a/spec/unit/util/values_spec.rb +++ b/spec/unit/util/values_spec.rb @@ -128,44 +128,4 @@ end end end - - describe "flatten_structure" do - it "converts a string to a hash containing that string" do - input = "foo" - output = described_class.flatten_structure("path", input) - expect(output).to eq({"path" => "foo"}) - end - - it "converts an array to a hash with the array elements with indexes" do - input = ["foo"] - output = described_class.flatten_structure("path", input) - expect(output).to eq({"path_0" => "foo"}) - end - - it "prefixes a non-nested hash with the given path" do - input = {"foo" => "bar"} - output = described_class.flatten_structure("path", input) - expect(output).to eq({"path_foo" => "bar"}) - end - - it "flattens elements till it reaches the first non-flattenable structure" do - input = { - "first" => "second", - "arr" => ["zero", "one"], - "nested_array" => [ - "hash" => "string", - ], - "top" => {"middle" => ['bottom']}, - } - output = described_class.flatten_structure("path", input) - - expect(output).to eq({ - "path_first" => "second", - "path_arr_0" => "zero", - "path_arr_1" => "one", - "path_nested_array_0_hash" => "string", - "path_top_middle_0" => "bottom" - }) - end - end end From 4fad4dece13e08209a95011a6e94381562df59ca Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Fri, 23 May 2014 13:17:53 -0700 Subject: [PATCH 1895/3753] Revert "Merge branch 'stable' into facter-2" This reverts commit 14eef77237ac1c9c0906890af696ad35e0f3b78e, reversing changes made to 07629d6a8b4f9c7a5d00cdb87dbc90befd6d1f3f. --- lib/facter/core/suitable.rb | 6 +- lib/facter/ec2.rb | 59 +++--- lib/facter/ec2/rest.rb | 129 ++++++++++++ lib/facter/util/ec2.rb | 5 + lib/facter/util/values.rb | 29 +++ spec/fixtures/unit/ec2/rest/meta-data/root | 20 ++ spec/unit/core/suitable_spec.rb | 10 + spec/unit/ec2/rest_spec.rb | 140 ++++++++++++ spec/unit/ec2_spec.rb | 234 ++++++++------------- spec/unit/util/ec2_spec.rb | 4 + spec/unit/util/values_spec.rb | 40 ++++ 11 files changed, 498 insertions(+), 178 deletions(-) create mode 100644 lib/facter/ec2/rest.rb create mode 100644 spec/fixtures/unit/ec2/rest/meta-data/root create mode 100644 spec/unit/ec2/rest_spec.rb diff --git a/lib/facter/core/suitable.rb b/lib/facter/core/suitable.rb index 1b3c04fceb..0262470919 100644 --- a/lib/facter/core/suitable.rb +++ b/lib/facter/core/suitable.rb @@ -108,10 +108,6 @@ def weight # # @api private def suitable? - unless defined? @suitable - @suitable = ! @confines.detect { |confine| ! confine.true? } - end - - return @suitable + @confines.all? { |confine| confine.true? } end end diff --git a/lib/facter/ec2.rb b/lib/facter/ec2.rb index 09e0109528..2e575927cd 100644 --- a/lib/facter/ec2.rb +++ b/lib/facter/ec2.rb @@ -1,37 +1,44 @@ -require 'facter/util/ec2' -require 'open-uri' +require 'facter/ec2/rest' -def metadata(id = "") - open("http://169.254.169.254/2008-02-01/meta-data/#{id||=''}").read. - split("\n").each do |o| - key = "#{id}#{o.gsub(/\=.*$/, '/')}" - if key[-1..-1] != '/' - value = open("http://169.254.169.254/2008-02-01/meta-data/#{key}").read. - split("\n") - symbol = "ec2_#{key.gsub(/\-|\//, '_')}".to_sym - Facter.add(symbol) { setcode { value.join(',') } } - else - metadata(key) +Facter.define_fact(:ec2_metadata) do + define_resolution(:rest) do + confine do + Facter.value(:virtual).match /^xen/ + end + + @querier = Facter::EC2::Metadata.new + confine do + @querier.reachable? + end + + setcode do + @querier.fetch end end -rescue => details - Facter.warn "Could not retrieve ec2 metadata: #{details.message}" end -def userdata() - Facter.add(:ec2_userdata) do +Facter.define_fact(:ec2_userdata) do + define_resolution(:rest) do + confine do + Facter.value(:virtual).match /^xen/ + end + + @querier = Facter::EC2::Userdata.new + confine do + @querier.reachable? + end + setcode do - if userdata = Facter::Util::EC2.userdata - userdata.split - end + @querier.fetch end end end -if (Facter::Util::EC2.has_euca_mac? || Facter::Util::EC2.has_openstack_mac? || - Facter::Util::EC2.has_ec2_arp?) && Facter::Util::EC2.can_connect? - metadata - userdata -else - Facter.debug "Not an EC2 host" +# The flattened version of the EC2 facts are deprecated and will be removed in +# a future release of Facter. +if (ec2_metadata = Facter.value(:ec2_metadata)) + ec2_facts = Facter::Util::Values.flatten_structure("ec2", ec2_metadata) + ec2_facts.each_pair do |factname, factvalue| + Facter.add(factname, :value => factvalue) + end end diff --git a/lib/facter/ec2/rest.rb b/lib/facter/ec2/rest.rb new file mode 100644 index 0000000000..e9c1fcadde --- /dev/null +++ b/lib/facter/ec2/rest.rb @@ -0,0 +1,129 @@ +require 'timeout' +require 'open-uri' + +module Facter + module EC2 + CONNECTION_ERRORS = [ + Errno::EHOSTDOWN, + Errno::EHOSTUNREACH, + Errno::ENETUNREACH, + Errno::ECONNABORTED, + Errno::ECONNREFUSED, + Errno::ECONNRESET, + Errno::ETIMEDOUT, + ] + + class Base + def reachable?(retry_limit = 3) + timeout = 0.2 + able_to_connect = false + attempts = 0 + + begin + Timeout.timeout(timeout) do + open(@baseurl).read + end + able_to_connect = true + rescue OpenURI::HTTPError => e + if e.message.match /404 Not Found/i + able_to_connect = false + else + retry if attempts < retry_limit + end + rescue Timeout::Error + retry if attempts < retry_limit + rescue *CONNECTION_ERRORS + retry if attempts < retry_limit + ensure + attempts = attempts + 1 + end + + able_to_connect + end + end + + class Metadata < Base + + DEFAULT_URI = "http://169.254.169.254/latest/meta-data/" + + def initialize(uri = DEFAULT_URI) + @baseurl = uri + end + + def fetch(path = '') + results = {} + + keys = fetch_endpoint(path) + keys.each do |key| + if key.match(%r[/$]) + # If a metadata key is suffixed with '/' then it's a general metadata + # resource, so we have to recursively look up all the keys in the given + # collection. + name = key[0..-2] + results[name] = fetch("#{path}#{key}") + else + # This is a simple key/value pair, we can just query the given endpoint + # and store the results. + ret = fetch_endpoint("#{path}#{key}") + results[key] = ret.size > 1 ? ret : ret.first + end + end + + results + end + + # @param path [String] The path relative to the object base url + # + # @return [Array, NilClass] + def fetch_endpoint(path) + uri = @baseurl + path + body = open(uri).read + parse_results(body) + rescue OpenURI::HTTPError => e + if e.message.match /404 Not Found/i + return nil + else + Facter.log_exception(e, "Failed to fetch ec2 uri #{uri}: #{e.message}") + return nil + end + rescue *CONNECTION_ERRORS => e + Facter.log_exception(e, "Failed to fetch ec2 uri #{uri}: #{e.message}") + return nil + end + + private + + def parse_results(body) + lines = body.split("\n") + lines.map do |line| + if (match = line.match(/^(\d+)=.*$/)) + # Metadata arrays are formatted like '=/', so + # we need to extract the index from that output. + "#{match[1]}/" + else + line + end + end + end + end + + class Userdata < Base + DEFAULT_URI = "http://169.254.169.254/latest/user-data/" + + def initialize(uri = DEFAULT_URI) + @baseurl = uri + end + + def fetch + open(@baseurl).read + rescue OpenURI::HTTPError => e + if e.message.match /404 Not Found/i + return nil + else + Facter.log_exception(e, "Failed to fetch ec2 uri #{uri}: #{e.message}") + return nil + end + end + end + end +end diff --git a/lib/facter/util/ec2.rb b/lib/facter/util/ec2.rb index c6e5dcadd4..b81c8febe8 100644 --- a/lib/facter/util/ec2.rb +++ b/lib/facter/util/ec2.rb @@ -11,6 +11,7 @@ class << self # The +wait_sec+ parameter provides you with an adjustable timeout. # def can_connect?(wait_sec=2) + Facter.warnonce("#{self}.#{__method__} is deprecated; see the Facter::EC2 classes instead") url = "http://169.254.169.254/" Timeout::timeout(wait_sec) {open(url)} return true @@ -23,6 +24,7 @@ def can_connect?(wait_sec=2) # Test if this host has a mac address used by Eucalyptus clouds, which # normally is +d0:0d+. def has_euca_mac? + Facter.warnonce("#{self}.#{__method__} is deprecated; see the Facter::EC2 classes instead") !!(Facter.value(:macaddress) =~ %r{^[dD]0:0[dD]:}) end @@ -30,12 +32,14 @@ def has_euca_mac? # normally starts with FA:16:3E (older versions of OpenStack # may generate mac addresses starting with 02:16:3E) def has_openstack_mac? + Facter.warnonce("#{self}.#{__method__} is deprecated; see the Facter::EC2 classes instead") !!(Facter.value(:macaddress) =~ %r{^(02|[fF][aA]):16:3[eE]}) end # Test if the host has an arp entry in its cache that matches the EC2 arp, # which is normally +fe:ff:ff:ff:ff:ff+. def has_ec2_arp? + Facter.warnonce("#{self}.#{__method__} is deprecated; see the Facter::EC2 classes instead") kernel = Facter.value(:kernel) mac_address_re = case kernel @@ -73,6 +77,7 @@ def has_ec2_arp? # # @return [String] containing the response body or `nil` def self.userdata(version="latest") + Facter.warnonce("#{self}.#{__method__} is deprecated; see the Facter::EC2 classes instead") uri = "http://169.254.169.254/#{version}/user-data/" begin read_uri(uri) diff --git a/lib/facter/util/values.rb b/lib/facter/util/values.rb index a7048d54da..1fbb1b686e 100644 --- a/lib/facter/util/values.rb +++ b/lib/facter/util/values.rb @@ -1,3 +1,4 @@ + module Facter module Util # A util module for facter containing helper methods @@ -75,6 +76,34 @@ def convert(value) value = value.downcase if value.is_a?(String) value end + + # Flatten the given data structure to something that's suitable to return + # as flat facts. + # + # @param path [String] The fact path to be prefixed to the given value. + # @param structure [Object] The data structure to flatten. Nested hashes + # will be recursively flattened, everything else will be returned as-is. + # + # @return [Hash] The given data structure prefixed with the given path + def flatten_structure(path, structure) + results = {} + + if structure.is_a? Hash + structure.each_pair do |name, value| + new_path = "#{path}_#{name}".gsub(/\-|\//, '_') + results.merge! flatten_structure(new_path, value) + end + elsif structure.is_a? Array + structure.each_with_index do |value, index| + new_path = "#{path}_#{index}" + results.merge! flatten_structure(new_path, value) + end + else + results[path] = structure + end + + results + end end end end diff --git a/spec/fixtures/unit/ec2/rest/meta-data/root b/spec/fixtures/unit/ec2/rest/meta-data/root new file mode 100644 index 0000000000..9ec3bbedad --- /dev/null +++ b/spec/fixtures/unit/ec2/rest/meta-data/root @@ -0,0 +1,20 @@ +ami-id +ami-launch-index +ami-manifest-path +block-device-mapping/ +hostname +instance-action +instance-id +instance-type +kernel-id +local-hostname +local-ipv4 +mac +metrics/ +network/ +placement/ +profile +public-hostname +public-ipv4 +public-keys/ +reservation-id diff --git a/spec/unit/core/suitable_spec.rb b/spec/unit/core/suitable_spec.rb index 4c0b1fde06..8277408799 100644 --- a/spec/unit/core/suitable_spec.rb +++ b/spec/unit/core/suitable_spec.rb @@ -92,5 +92,15 @@ def initialize expect(subject).to_not be_suitable end + + it "recalculates suitability on every invocation" do + subject.confine :kernel => 'Linux' + + subject.confines.first.stubs(:true?).returns false + expect(subject).to_not be_suitable + subject.confines.first.unstub(:true?) + subject.confines.first.stubs(:true?).returns true + expect(subject).to be_suitable + end end end diff --git a/spec/unit/ec2/rest_spec.rb b/spec/unit/ec2/rest_spec.rb new file mode 100644 index 0000000000..5c74b495f4 --- /dev/null +++ b/spec/unit/ec2/rest_spec.rb @@ -0,0 +1,140 @@ +require 'spec_helper' +require 'facter/ec2/rest' + +shared_examples_for "an ec2 rest querier" do + describe "determining if the uri is reachable" do + it "retries if the connection times out" do + subject.stubs(:open).returns(stub(:read => nil)) + Timeout.expects(:timeout).with(0.2).twice.raises(Timeout::Error).returns(true) + expect(subject).to be_reachable + end + + it "retries if the connection is reset" do + subject.expects(:open).twice.raises(Errno::ECONNREFUSED).returns(StringIO.new("woo")) + expect(subject).to be_reachable + end + + it "is false if the given uri returns a 404" do + subject.expects(:open).with(anything).once.raises(OpenURI::HTTPError.new("404 Not Found", StringIO.new("woo"))) + expect(subject).to_not be_reachable + end + end + +end + +describe Facter::EC2::Metadata do + + subject { described_class.new('http://0.0.0.0/latest/meta-data/') } + + let(:response) { StringIO.new } + + describe "fetching a metadata endpoint" do + it "splits the body into an array" do + response.string = my_fixture_read("meta-data/root") + subject.stubs(:open).with("http://0.0.0.0/latest/meta-data/").returns response + output = subject.fetch_endpoint('') + + expect(output).to eq %w[ + ami-id ami-launch-index ami-manifest-path block-device-mapping/ hostname + instance-action instance-id instance-type kernel-id local-hostname + local-ipv4 mac metrics/ network/ placement/ profile public-hostname + public-ipv4 public-keys/ reservation-id + ] + end + + it "reformats keys that are array indices" do + response.string = "0=adrien@grey/" + subject.stubs(:open).with("http://0.0.0.0/latest/meta-data/public-keys/").returns response + output = subject.fetch_endpoint("public-keys/") + + expect(output).to eq %w[0/] + end + + it "returns nil if the endpoint returns a 404" do + Facter.expects(:log_exception).never + subject.stubs(:open).with("http://0.0.0.0/latest/meta-data/public-keys/1/").raises OpenURI::HTTPError.new("404 Not Found", response) + output = subject.fetch_endpoint('public-keys/1/') + + expect(output).to be_nil + end + + it "logs an error if the endpoint raises a non-404 HTTPError" do + Facter.expects(:log_exception).with(instance_of(OpenURI::HTTPError), anything) + + subject.stubs(:open).with("http://0.0.0.0/latest/meta-data/").raises OpenURI::HTTPError.new("418 I'm a Teapot", response) + output = subject.fetch_endpoint("") + + expect(output).to be_nil + end + + it "logs an error if the endpoint raises a connection error" do + Facter.expects(:log_exception).with(instance_of(Errno::ECONNREFUSED), anything) + + subject.stubs(:open).with("http://0.0.0.0/latest/meta-data/").raises Errno::ECONNREFUSED + output = subject.fetch_endpoint('') + + expect(output).to be_nil + end + end + + describe "recursively fetching the EC2 metadata API" do + it "queries the given endpoint for metadata keys" do + subject.expects(:fetch_endpoint).with("").returns([]) + subject.fetch + end + + it "fetches the value for a simple metadata key" do + subject.expects(:fetch_endpoint).with("").returns(['indexthing']) + subject.expects(:fetch_endpoint).with("indexthing").returns(['first', 'second']) + + output = subject.fetch + expect(output).to eq({'indexthing' => ['first', 'second']}) + end + + it "unwraps metadata values that are in single element arrays" do + subject.expects(:fetch_endpoint).with("").returns(['ami-id']) + subject.expects(:fetch_endpoint).with("ami-id").returns(['i-12x']) + + output = subject.fetch + expect(output).to eq({'ami-id' => 'i-12x'}) + end + + it "recursively queries an endpoint if the key ends with '/'" do + subject.expects(:fetch_endpoint).with("").returns(['metrics/']) + subject.expects(:fetch_endpoint).with("metrics/").returns(['vhostmd']) + subject.expects(:fetch_endpoint).with("metrics/vhostmd").returns(['woo']) + + output = subject.fetch + expect(output).to eq({'metrics' => {'vhostmd' => 'woo'}}) + end + end + + it_behaves_like "an ec2 rest querier" +end + +describe Facter::EC2::Userdata do + + subject { described_class.new('http://0.0.0.0/latest/user-data/') } + + let(:response) { StringIO.new } + + describe "reaching the userdata" do + it "queries the userdata URI" do + subject.expects(:open).with('http://0.0.0.0/latest/user-data/').returns(response) + subject.fetch + end + + it "returns the result of the query without modification" do + response.string = "clooouuuuud" + subject.expects(:open).with('http://0.0.0.0/latest/user-data/').returns(response) + expect(subject.fetch).to eq "clooouuuuud" + end + + it "is nil if the URI returned a 404" do + subject.expects(:open).with('http://0.0.0.0/latest/user-data/').once.raises(OpenURI::HTTPError.new("404 Not Found", StringIO.new("woo"))) + expect(subject.fetch).to be_nil + end + end + + it_behaves_like "an ec2 rest querier" +end diff --git a/spec/unit/ec2_spec.rb b/spec/unit/ec2_spec.rb index f26a61316f..28ad20a536 100755 --- a/spec/unit/ec2_spec.rb +++ b/spec/unit/ec2_spec.rb @@ -1,187 +1,127 @@ -#! /usr/bin/env ruby - require 'spec_helper' -require 'facter/util/ec2' - -describe "ec2 facts" do - # This is the standard prefix for making an API call in EC2 (or fake) - # environments. - let(:api_prefix) { "http://169.254.169.254/" } - - describe "when running on ec2" do - before :each do - # This is an ec2 instance, not a eucalyptus instance - Facter::Util::EC2.stubs(:has_euca_mac?).returns(false) - Facter::Util::EC2.stubs(:has_openstack_mac?).returns(false) - Facter::Util::EC2.stubs(:has_ec2_arp?).returns(true) - - # Assume we can connect - Facter::Util::EC2.stubs(:can_connect?).returns(true) - end +require 'facter/ec2/rest' - it "should create flat meta-data facts" do - Object.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/meta-data/"). - at_least_once.returns(StringIO.new("foo")) +describe "ec2_metadata" do + let(:querier) { stub('EC2 metadata querier') } - Object.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/meta-data/foo"). - at_least_once.returns(StringIO.new("bar")) + before do + Facter::EC2::Metadata.stubs(:new).returns querier - Facter.collection.internal_loader.load(:ec2) - - Facter.fact(:ec2_foo).value.should == "bar" - end + # Prevent flattened facts from forcing evaluation of the ec2 metadata fact + Facter.stubs(:value).with(:ec2_metadata) + Facter.collection.internal_loader.load(:ec2) + Facter.unstub(:value) + end - it "should create flat meta-data facts with comma seperation" do - Object.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/meta-data/"). - at_least_once.returns(StringIO.new("foo")) + subject { Facter.fact(:ec2_metadata).resolution(:rest) } - Object.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/meta-data/foo"). - at_least_once.returns(StringIO.new("bar\nbaz")) + it "is unsuitable if the virtual fact is not xen" do + querier.stubs(:reachable?).returns false + Facter.fact(:virtual).stubs(:value).returns "kvm" + expect(subject).to_not be_suitable + end - Facter.collection.internal_loader.load(:ec2) + it "is unsuitable if ec2 endpoint is not reachable" do + Facter.fact(:virtual).stubs(:value).returns "xen" + querier.stubs(:reachable?).returns false + expect(subject).to_not be_suitable + end - Facter.fact(:ec2_foo).value.should == "bar,baz" + describe "when the ec2 endpoint is reachable" do + before do + querier.stubs(:reachable?).returns true end - it "should create structured meta-data facts" do - Object.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/meta-data/"). - at_least_once.returns(StringIO.new("foo/")) - - Object.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/meta-data/foo/"). - at_least_once.returns(StringIO.new("bar")) + it "is suitable if the virtual fact is xen" do + Facter.fact(:virtual).stubs(:value).returns "xen" + subject.suitable? - Object.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/meta-data/foo/bar"). - at_least_once.returns(StringIO.new("baz")) - - Facter.collection.internal_loader.load(:ec2) - - Facter.fact(:ec2_foo_bar).value.should == "baz" + expect(subject).to be_suitable end - it "should create ec2_user_data fact" do - # No meta-data - Object.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/meta-data/"). - at_least_once.returns(StringIO.new("")) - - Facter::Util::EC2.stubs(:read_uri). - with("#{api_prefix}/latest/user-data/"). - returns("test") - - Facter.collection.internal_loader.load(:ec2) - Facter.fact(:ec2_userdata).value.should == ["test"] + it "is suitable if the virtual fact is xenu" do + Facter.fact(:virtual).stubs(:value).returns "xenu" + expect(subject).to be_suitable end end - describe "when running on eucalyptus" do - before :each do - # Return false for ec2, true for eucalyptus - Facter::Util::EC2.stubs(:has_euca_mac?).returns(true) - Facter::Util::EC2.stubs(:has_openstack_mac?).returns(false) - Facter::Util::EC2.stubs(:has_ec2_arp?).returns(false) + it "resolves the value by recursively querying the rest endpoint" do + querier.expects(:fetch).returns({"hello" => "world"}) + expect(subject.value).to eq({"hello" => "world"}) + end +end - # Assume we can connect - Facter::Util::EC2.stubs(:can_connect?).returns(true) - end +describe "ec2_userdata" do + let(:querier) { stub('EC2 metadata querier') } - it "should create ec2_user_data fact" do - # No meta-data - Object.any_instance.expects(:open).\ - with("#{api_prefix}/2008-02-01/meta-data/").\ - at_least_once.returns(StringIO.new("")) + before do + Facter::EC2::Userdata.stubs(:new).returns querier - Facter::Util::EC2.stubs(:read_uri). - with("#{api_prefix}/latest/user-data/"). - returns("test") + # Prevent flattened facts from forcing evaluation of the ec2 metadata fact + Facter.stubs(:value).with(:ec2_metadata) + Facter.collection.internal_loader.load(:ec2) + Facter.unstub(:value) + end - # Force a fact load - Facter.collection.internal_loader.load(:ec2) + subject { Facter.fact(:ec2_userdata).resolution(:rest) } - Facter.fact(:ec2_userdata).value.should == ["test"] - end + it "is unsuitable if the virtual fact is not xen" do + querier.stubs(:reachable?).returns(true) + Facter.fact(:virtual).stubs(:value).returns "kvm" + expect(subject).to_not be_suitable end - describe "when running on openstack" do - before :each do - # Return false for ec2, true for eucalyptus - Facter::Util::EC2.stubs(:has_openstack_mac?).returns(true) - Facter::Util::EC2.stubs(:has_euca_mac?).returns(false) - Facter::Util::EC2.stubs(:has_ec2_arp?).returns(false) + it "is unsuitable if ec2 endpoint is not reachable" do + Facter.fact(:virtual).stubs(:value).returns "xen" + querier.stubs(:reachable?).returns false + expect(subject).to_not be_suitable + end - # Assume we can connect - Facter::Util::EC2.stubs(:can_connect?).returns(true) + describe "when the ec2 endpoint is reachable" do + before do + querier.stubs(:reachable?).returns true end - it "should create ec2_user_data fact" do - # No meta-data - Object.any_instance.expects(:open).\ - with("#{api_prefix}/2008-02-01/meta-data/").\ - at_least_once.returns(StringIO.new("")) - - Facter::Util::EC2.stubs(:read_uri). - with("#{api_prefix}/latest/user-data/"). - returns("test") - - # Force a fact load - Facter.collection.internal_loader.load(:ec2) - - Facter.fact(:ec2_userdata).value.should == ["test"] + it "is suitable if the virtual fact is xen" do + Facter.fact(:virtual).stubs(:value).returns "xen" + expect(subject).to be_suitable end - it "should return nil if open fails" do - Facter.stubs(:warn) # do not pollute test output - Facter.expects(:warn).with('Could not retrieve ec2 metadata: host unreachable') - - Object.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/meta-data/"). - at_least_once.raises(RuntimeError, 'host unreachable') - - Facter::Util::EC2.stubs(:read_uri). - with("#{api_prefix}/latest/user-data/"). - raises(RuntimeError, 'host unreachable') - - # Force a fact load - Facter.collection.internal_loader.load(:ec2) - - Facter.fact(:ec2_userdata).value.should be_nil + it "is suitable if the virtual fact is xenu" do + Facter.fact(:virtual).stubs(:value).returns "xenu" + expect(subject).to be_suitable end + end + it "resolves the value by fetching the rest endpoint" do + querier.expects(:fetch).returns "user data!" + expect(subject.value).to eq "user data!" end +end - describe "when api connect test fails" do - before :each do - Facter.stubs(:warnonce) - end +describe "flattened versions of ec2 facts" do + # These facts are tricky to test because they are dynamic facts, and they are + # generated from a fact that is defined in the same file. In order to pull + # this off we need to define the ec2_metadata fact ahead of time so that we + # can stub the value, and then manually load the correct files. - it "should not populate ec2_userdata" do - # Emulate ec2 for now as it matters little to this test - Facter::Util::EC2.stubs(:has_euca_mac?).returns(true) - Facter::Util::EC2.stubs(:has_ec2_arp?).never - Facter::Util::EC2.expects(:can_connect?).at_least_once.returns(false) + it "unpacks the ec2_metadata fact" do + Facter.define_fact(:ec2_metadata).stubs(:value).returns({"hello" => "world"}) + Facter.collection.internal_loader.load(:ec2) - # The API should never be called at this point - Object.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/meta-data/").never - Object.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/user-data/").never + expect(Facter.value("ec2_hello")).to eq "world" + end - # Force a fact load - Facter.collection.internal_loader.load(:ec2) + it "does not set any flat ec2 facts if the ec2_metadata fact is nil" do + Facter.define_fact(:ec2_metadata).stubs(:value) + Facter.define_fact(:ec2_userdata).stubs(:value).returns(nil) - Facter.fact(:ec2_userdata).should == nil - end + Facter.collection.internal_loader.load(:ec2) - it "should rescue the exception" do - Facter::Util::EC2.expects(:open).with("#{api_prefix}:80/").raises(Timeout::Error) + all_facts = Facter.collection.to_hash - Facter::Util::EC2.should_not be_can_connect - end + ec2_facts = all_facts.keys.select { |k| k =~ /^ec2_/ } + expect(ec2_facts).to be_empty end + end diff --git a/spec/unit/util/ec2_spec.rb b/spec/unit/util/ec2_spec.rb index f963db6c32..7af59d8334 100755 --- a/spec/unit/util/ec2_spec.rb +++ b/spec/unit/util/ec2_spec.rb @@ -4,6 +4,10 @@ require 'facter/util/ec2' describe Facter::Util::EC2 do + before do + # Squelch deprecation notices + Facter.stubs(:warnonce) + end # This is the standard prefix for making an API call in EC2 (or fake) # environments. let(:api_prefix) { "http://169.254.169.254/" } diff --git a/spec/unit/util/values_spec.rb b/spec/unit/util/values_spec.rb index 557eb195e6..bc347c49a6 100644 --- a/spec/unit/util/values_spec.rb +++ b/spec/unit/util/values_spec.rb @@ -128,4 +128,44 @@ end end end + + describe "flatten_structure" do + it "converts a string to a hash containing that string" do + input = "foo" + output = described_class.flatten_structure("path", input) + expect(output).to eq({"path" => "foo"}) + end + + it "converts an array to a hash with the array elements with indexes" do + input = ["foo"] + output = described_class.flatten_structure("path", input) + expect(output).to eq({"path_0" => "foo"}) + end + + it "prefixes a non-nested hash with the given path" do + input = {"foo" => "bar"} + output = described_class.flatten_structure("path", input) + expect(output).to eq({"path_foo" => "bar"}) + end + + it "flattens elements till it reaches the first non-flattenable structure" do + input = { + "first" => "second", + "arr" => ["zero", "one"], + "nested_array" => [ + "hash" => "string", + ], + "top" => {"middle" => ['bottom']}, + } + output = described_class.flatten_structure("path", input) + + expect(output).to eq({ + "path_first" => "second", + "path_arr_0" => "zero", + "path_arr_1" => "one", + "path_nested_array_0_hash" => "string", + "path_top_middle_0" => "bottom" + }) + end + end end From 397806e73f5212a4cebc141896c307bbadf151b3 Mon Sep 17 00:00:00 2001 From: Dominic Cleal Date: Thu, 8 May 2014 11:58:00 +0100 Subject: [PATCH 1896/3753] (FACT-481) Infinite loop when EC2 metadata server times out The retry loop for contacting the EC2 metadata server failed to increment the attempt counter on each iteration, as the 'ensure' block only executes once the entire begin/rescue block has been completed, not on each retry. --- lib/facter/ec2/rest.rb | 5 +++-- spec/unit/ec2/rest_spec.rb | 5 +++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/facter/ec2/rest.rb b/lib/facter/ec2/rest.rb index e9c1fcadde..9fa2cc72ee 100644 --- a/lib/facter/ec2/rest.rb +++ b/lib/facter/ec2/rest.rb @@ -28,14 +28,15 @@ def reachable?(retry_limit = 3) if e.message.match /404 Not Found/i able_to_connect = false else + attempts = attempts + 1 retry if attempts < retry_limit end rescue Timeout::Error + attempts = attempts + 1 retry if attempts < retry_limit rescue *CONNECTION_ERRORS - retry if attempts < retry_limit - ensure attempts = attempts + 1 + retry if attempts < retry_limit end able_to_connect diff --git a/spec/unit/ec2/rest_spec.rb b/spec/unit/ec2/rest_spec.rb index 5c74b495f4..1e64cd58c3 100644 --- a/spec/unit/ec2/rest_spec.rb +++ b/spec/unit/ec2/rest_spec.rb @@ -18,6 +18,11 @@ subject.expects(:open).with(anything).once.raises(OpenURI::HTTPError.new("404 Not Found", StringIO.new("woo"))) expect(subject).to_not be_reachable end + + it "is false if the connection always times out" do + Timeout.expects(:timeout).with(0.2).times(3).raises(Timeout::Error) + expect(subject).to_not be_reachable + end end end From 6456bf76e607e5b843dd117ccaade6bf7dcd1d82 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Fri, 23 May 2014 17:19:40 -0700 Subject: [PATCH 1897/3753] (cfact-32) Add a first draft of Extensibility.md --- Extensibility.md | 65 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 Extensibility.md diff --git a/Extensibility.md b/Extensibility.md new file mode 100644 index 0000000000..1cea8aa07b --- /dev/null +++ b/Extensibility.md @@ -0,0 +1,65 @@ +Extensibility +============= + +Native Facter has the following extensibility goals: +* Clear guidelines for what facts are supported in-project +* Compatibility with 90+% of existing Facter custom facts +* Compatibility with 100% of existing Facter external facts +* New features for external facts, including depends/requires logic + +The following sections discuss those goals in more depth. + +Note that this doc is work-in-progress and should be updated as extensibility features are implemented, refined or ruled out. + +Built-in facts +-------------- + +These are the criteria for Native Facter pull requests: +* Additional resolutions for existing built-in facts (e.g. the operatingsystem facts for new Linux Distros) must conform to the facter.json schema. +* New facts must be accompanied by support in the facter.json schema. +* For [SemVer](http://semver.org) purposes, a change in fact value is only considered breaking if a) the fact is documented in the schema, and b) the fact is applicable on a platform we test againt in [Puppet CI](http://jenkins.puppetlabs.com). + +Legacy Custom Facts Compatibility +--------------------------------- + +The Ruby Facter project supports "custom facts" which are fact implementations written using the internal Facter API. For compatibility purposes, Native Facter will support the most commonly used subset of that API specifically: +``` +Facter.value +Facter.add +confine +setcode +has_weight (maybe?) +``` + +It is TBD whether Native Facter will support custom facts which rely on other custom facts. (See New Features for External Facts below for possible mitigation.) + +In terms of implementation, there are two avenues we may pursue: +* A standalone ruby shim which executes existing custom facts. This might be able to run as just another external fact. +* A custom fact resolver which calls the Ruby C API to execute custom facts. +We will decide between these two approaches after some spiking to see which will be most robust and maintainable. + +Long-term, the new features for external facts should encourage users to port their legacy custom facts to external facts, so at some point (e.g. a couple major versions down the line) this shim logic should be retired. + +External Facts Compatiblity +--------------------------- + +Native Facter will support all 4 forms of "external facts" which Ruby Facter supports: +* JSON files with the .json extension whose key-value pairs will be mapped to fact-value pairs. +* YAML files with the .yaml extension whose key-value pairs will be mapped to fact-value pairs. +* Text files with the .txt extension containing `fact=some_value` strings +* Executable files returning `fact=some_value` strings + +New Features for External Facts +------------------------------- + +Caveat: It is TBD which of these features will be implemented in what releases. + +* Executable external facts can take a json object on stdin describing all known facts. This will allow logic like the confine/value methods provide in the custom facts API. + +* To support the use case of facts which depend on other facts: executable external facts can describe what facts they depend on and what facts they provide, via a json schema. (This schema could either be a parallel file distributed with the external fact following some naming convention (e.g. foo.schema for an external fact called foo.sh or foo.py), or the schema could be provided on stdout when executing the external fact with some parameter (e.g. foo.sh --show-schema).) + +* Native Facter might add a weight indicator for returned facts, to support fact precedence for external facts, along the lines of the `has_weight` functionality. + +* Native Facter might add a volatility indicator, such as a ttl field, to the json schema provided by external facts. This would allow facter in a long-running process to avoid unnecessary refreshes of non-volatile facts. + +* Native Facter might include some language-specific shims for its external facts support, most likely for Ruby and Python, to ease writing new external facts (including ports of legacy custom facts). From 819369b352be711ec6359cc17ccfffbafd671a9d Mon Sep 17 00:00:00 2001 From: Melissa Stone Date: Tue, 27 May 2014 10:48:55 -0700 Subject: [PATCH 1898/3753] (packaging) Update FACTERVERSION to 2.0.2 --- lib/facter/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/facter/version.rb b/lib/facter/version.rb index 0dc068887b..4d802b9163 100644 --- a/lib/facter/version.rb +++ b/lib/facter/version.rb @@ -1,6 +1,6 @@ module Facter if not defined? FACTERVERSION then - FACTERVERSION = '2.0.1' + FACTERVERSION = '2.0.2' end # Returns the running version of Facter. From 7845c9aa0dff4e5cc9831615306f394f6db0137c Mon Sep 17 00:00:00 2001 From: Matthaus Owens Date: Tue, 22 Apr 2014 15:36:48 -0700 Subject: [PATCH 1899/3753] (FACT-462) Raring went EOL in Jan 2014, so we should no longer build packages for it. --- ext/build_defaults.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/build_defaults.yaml b/ext/build_defaults.yaml index a42c1f60dc..84a9679f75 100644 --- a/ext/build_defaults.yaml +++ b/ext/build_defaults.yaml @@ -2,7 +2,7 @@ packaging_url: 'git://github.com/puppetlabs/packaging.git --branch=master' packaging_repo: 'packaging' default_cow: 'base-squeeze-i386.cow' -cows: 'base-lucid-i386.cow base-lucid-amd64.cow base-precise-i386.cow base-precise-amd64.cow base-quantal-i386.cow base-quantal-amd64.cow base-raring-i386.cow base-raring-amd64.cow base-saucy-i386.cow base-saucy-amd64.cow base-sid-i386.cow base-sid-amd64.cow base-squeeze-i386.cow base-squeeze-amd64.cow base-stable-i386.cow base-stable-amd64.cow base-testing-i386.cow base-testing-amd64.cow base-trusty-i386.cow base-trusty-amd64.cow base-unstable-i386.cow base-unstable-amd64.cow base-wheezy-i386.cow base-wheezy-amd64.cow' +cows: 'base-lucid-i386.cow base-lucid-amd64.cow base-precise-i386.cow base-precise-amd64.cow base-quantal-i386.cow base-quantal-amd64.cow base-saucy-i386.cow base-saucy-amd64.cow base-sid-i386.cow base-sid-amd64.cow base-squeeze-i386.cow base-squeeze-amd64.cow base-stable-i386.cow base-stable-amd64.cow base-testing-i386.cow base-testing-amd64.cow base-trusty-i386.cow base-trusty-amd64.cow base-unstable-i386.cow base-unstable-amd64.cow base-wheezy-i386.cow base-wheezy-amd64.cow' pbuild_conf: '/etc/pbuilderrc' packager: 'puppetlabs' gpg_name: 'info@puppetlabs.com' From 82428721472a6d7c3a8ef84249f3a5763038bc1b Mon Sep 17 00:00:00 2001 From: Chris Portman Date: Mon, 12 May 2014 08:41:45 +1000 Subject: [PATCH 1900/3753] (FACT-233) support nmcli >=0.9.9 syntax Adding support for nmcli version >= 0.9.9 that ships in Fedora 20 and RHEL7. --- lib/facter/dhcp_servers.rb | 2 +- lib/facter/util/dhcp_servers.rb | 12 ++++- spec/unit/dhcp_servers_spec.rb | 72 +++++++++++++++++++++++++++-- spec/unit/util/dhcp_servers_spec.rb | 27 +++++++++++ 4 files changed, 108 insertions(+), 5 deletions(-) create mode 100644 spec/unit/util/dhcp_servers_spec.rb diff --git a/lib/facter/dhcp_servers.rb b/lib/facter/dhcp_servers.rb index 2242b3451e..f19dca01b2 100644 --- a/lib/facter/dhcp_servers.rb +++ b/lib/facter/dhcp_servers.rb @@ -17,8 +17,8 @@ Facter.add(:dhcp_servers) do - confine :operatingsystem => "Fedora", :operatingsystemrelease => "19" confine do + confine :kernel => :linux Facter::Core::Execution.which('nmcli') end diff --git a/lib/facter/util/dhcp_servers.rb b/lib/facter/util/dhcp_servers.rb index 5cf8259adb..e05b7d1c1a 100644 --- a/lib/facter/util/dhcp_servers.rb +++ b/lib/facter/util/dhcp_servers.rb @@ -13,7 +13,17 @@ def self.devices def self.device_dhcp_server(device) if Facter::Core::Execution.which('nmcli') - Facter::Core::Execution.exec("nmcli d list iface #{device}").scan(/dhcp_server_identifier.*?(\d+\.\d+\.\d+\.\d+)$/).flatten.first + if self.nmcli_version and self.nmcli_version >= 990 + Facter::Core::Execution.exec("nmcli d show #{device}").scan(/dhcp_server_identifier.*?(\d+\.\d+\.\d+\.\d+)$/).flatten.first + else + Facter::Core::Execution.exec("nmcli d list iface #{device}").scan(/dhcp_server_identifier.*?(\d+\.\d+\.\d+\.\d+)$/).flatten.first + end + end + end + + def self.nmcli_version + if version = Facter::Core::Execution.exec("nmcli --version").scan(/version\s([\d\.]+)/).flatten.first + version.gsub(/\./,'').to_i end end end diff --git a/spec/unit/dhcp_servers_spec.rb b/spec/unit/dhcp_servers_spec.rb index f77f9b17fe..344e579806 100644 --- a/spec/unit/dhcp_servers_spec.rb +++ b/spec/unit/dhcp_servers_spec.rb @@ -3,14 +3,14 @@ describe "DHCP server facts" do describe "on Linux OS's" do before :each do - Facter.fact(:operatingsystem).stubs(:value).returns 'Fedora' - Facter.fact(:operatingsystemrelease).stubs(:value).returns '19' + Facter.fact(:kernel).stubs(:value).returns 'Linux' Facter::Core::Execution.stubs(:exec).with("route -n").returns(my_fixture_read("route")) end - describe "with nmcli available" do + describe "with nmcli version <= 0.9.8 available" do before :each do Facter::Core::Execution.stubs(:which).with('nmcli').returns('/usr/bin/nmcli') + Facter::Core::Execution.stubs(:exec).with('nmcli --version').returns('nmcli tool, version 0.9.8.0') end describe "with a main interface configured with DHCP" do @@ -73,6 +73,72 @@ end end + describe "with nmcli version >= 0.9.9 available" do + before :each do + Facter::Core::Execution.stubs(:which).with('nmcli').returns('/usr/bin/nmcli') + Facter::Core::Execution.stubs(:exec).with('nmcli --version').returns('nmcli tool, version 0.9.9.0-20.git20131003.fc20') + end + + describe "with a main interface configured with DHCP" do + before :each do + Facter::Core::Execution.stubs(:exec).with("nmcli d").returns(my_fixture_read("nmcli_devices")) + Facter::Core::Execution.stubs(:exec).with("nmcli d show eth0").returns(my_fixture_read("nmcli_eth0_dhcp")) + Facter::Core::Execution.stubs(:exec).with("nmcli d show wlan0").returns(my_fixture_read("nmcli_wlan0_dhcp")) + end + + it "should produce a dhcp_servers fact that includes values for 'system' as well as each dhcp enabled interface" do + Facter.fact(:dhcp_servers).value.should == { 'system' => '192.168.1.1', 'eth0' => '192.168.1.1', 'wlan0' => '192.168.2.1' } + end + end + + describe "with a main interface NOT configured with DHCP" do + before :each do + Facter::Core::Execution.stubs(:exec).with("nmcli d").returns(my_fixture_read("nmcli_devices")) + Facter::Core::Execution.stubs(:exec).with("nmcli d show eth0").returns(my_fixture_read("nmcli_eth0_static")) + Facter::Core::Execution.stubs(:exec).with("nmcli d show wlan0").returns(my_fixture_read("nmcli_wlan0_dhcp")) + end + + it "should a dhcp_servers fact that includes values for each dhcp enables interface and NO 'system' value" do + Facter.fact(:dhcp_servers).value.should == {'wlan0' => '192.168.2.1' } + end + end + + describe "with no default gateway" do + before :each do + Facter::Core::Execution.stubs(:exec).with("route -n").returns(my_fixture_read("route_nogw")) + Facter::Core::Execution.stubs(:exec).with("nmcli d").returns(my_fixture_read("nmcli_devices")) + Facter::Core::Execution.stubs(:exec).with("nmcli d show eth0").returns(my_fixture_read("nmcli_eth0_dhcp")) + Facter::Core::Execution.stubs(:exec).with("nmcli d show wlan0").returns(my_fixture_read("nmcli_wlan0_dhcp")) + end + + it "should a dhcp_servers fact that includes values for each dhcp enables interface and NO 'system' value" do + Facter.fact(:dhcp_servers).value.should == {'eth0' => '192.168.1.1', 'wlan0' => '192.168.2.1' } + end + end + + describe "with no DHCP enabled interfaces" do + before :each do + Facter::Core::Execution.stubs(:exec).with("nmcli d").returns(my_fixture_read("nmcli_devices")) + Facter::Core::Execution.stubs(:exec).with("nmcli d show eth0").returns(my_fixture_read("nmcli_eth0_static")) + Facter::Core::Execution.stubs(:exec).with("nmcli d show wlan0").returns(my_fixture_read("nmcli_wlan0_static")) + end + + it "should not produce a dhcp_servers fact" do + Facter.fact(:dhcp_servers).value.should be_nil + end + end + + describe "with no CONNECTED devices" do + before :each do + Facter::Core::Execution.stubs(:exec).with("nmcli d").returns(my_fixture_read("nmcli_devices_disconnected")) + end + + it "should not produce a dhcp_servers fact" do + Facter.fact(:dhcp_servers).value.should be_nil + end + end + end + describe "without nmcli available" do before :each do Facter::Core::Execution.stubs(:which).with('nmcli').returns(nil) diff --git a/spec/unit/util/dhcp_servers_spec.rb b/spec/unit/util/dhcp_servers_spec.rb new file mode 100644 index 0000000000..0a5eeaff26 --- /dev/null +++ b/spec/unit/util/dhcp_servers_spec.rb @@ -0,0 +1,27 @@ +#! /usr/bin/env ruby + +require 'spec_helper' +require 'facter/util/dhcp_servers' + +describe Facter::Util::DHCPServers do + describe "nmcli_version" do + { + 'nmcli tool, version 0.9.8.0' => 980, + 'nmcli tool, version 0.9.8.9' => 989, + 'nmcli tool, version 0.9.9.0' => 990, + 'nmcli tool, version 0.9.9.9' => 999, + 'version 0.9.9.0-20.git20131003.fc20' => 990, + }.each do |version, expected| + it "should turn #{version} into the integer #{expected}" do + Facter::Core::Execution.stubs(:which).with('nmcli').returns('/usr/bin/nmcli') + Facter::Core::Execution.stubs(:exec).with('nmcli --version').returns(version) + + result = Facter::Util::DHCPServers.nmcli_version + result.is_a?(Integer).should be true + result.should == expected + end + end + end +end + + From 8bbad973b37335b78d8ac317098440ffa9c9dce6 Mon Sep 17 00:00:00 2001 From: Kylo Ginsberg Date: Wed, 28 May 2014 12:02:58 -0700 Subject: [PATCH 1901/3753] (fact-233) Pass '-f all' to nmcli to ensure the dhcp_server field is available Prior to this change 'nmcli d' was invoked without specifying which fields were needed. This happened to work on fedora 19 and fedora 20, but doesn't work on redhat 7, which defaults to return a restricted set of fields for devices. This patch specifies '-f all' to nmcli so that all fields are returned. I've tested this change on all 3 of the above distros. It works on all 3 and doesn't seem notably slower (I had some concern that maybe this would cause a slowdown, so wanted to check that out.) --- lib/facter/util/dhcp_servers.rb | 4 ++-- spec/unit/dhcp_servers_spec.rb | 32 ++++++++++++++++---------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/facter/util/dhcp_servers.rb b/lib/facter/util/dhcp_servers.rb index e05b7d1c1a..8fc0ffc722 100644 --- a/lib/facter/util/dhcp_servers.rb +++ b/lib/facter/util/dhcp_servers.rb @@ -14,9 +14,9 @@ def self.devices def self.device_dhcp_server(device) if Facter::Core::Execution.which('nmcli') if self.nmcli_version and self.nmcli_version >= 990 - Facter::Core::Execution.exec("nmcli d show #{device}").scan(/dhcp_server_identifier.*?(\d+\.\d+\.\d+\.\d+)$/).flatten.first + Facter::Core::Execution.exec("nmcli -f all d show #{device}").scan(/dhcp_server_identifier.*?(\d+\.\d+\.\d+\.\d+)$/).flatten.first else - Facter::Core::Execution.exec("nmcli d list iface #{device}").scan(/dhcp_server_identifier.*?(\d+\.\d+\.\d+\.\d+)$/).flatten.first + Facter::Core::Execution.exec("nmcli -f all d list iface #{device}").scan(/dhcp_server_identifier.*?(\d+\.\d+\.\d+\.\d+)$/).flatten.first end end end diff --git a/spec/unit/dhcp_servers_spec.rb b/spec/unit/dhcp_servers_spec.rb index 344e579806..3e99685d06 100644 --- a/spec/unit/dhcp_servers_spec.rb +++ b/spec/unit/dhcp_servers_spec.rb @@ -16,8 +16,8 @@ describe "with a main interface configured with DHCP" do before :each do Facter::Core::Execution.stubs(:exec).with("nmcli d").returns(my_fixture_read("nmcli_devices")) - Facter::Core::Execution.stubs(:exec).with("nmcli d list iface eth0").returns(my_fixture_read("nmcli_eth0_dhcp")) - Facter::Core::Execution.stubs(:exec).with("nmcli d list iface wlan0").returns(my_fixture_read("nmcli_wlan0_dhcp")) + Facter::Core::Execution.stubs(:exec).with("nmcli -f all d list iface eth0").returns(my_fixture_read("nmcli_eth0_dhcp")) + Facter::Core::Execution.stubs(:exec).with("nmcli -f all d list iface wlan0").returns(my_fixture_read("nmcli_wlan0_dhcp")) end it "should produce a dhcp_servers fact that includes values for 'system' as well as each dhcp enabled interface" do @@ -28,8 +28,8 @@ describe "with a main interface NOT configured with DHCP" do before :each do Facter::Core::Execution.stubs(:exec).with("nmcli d").returns(my_fixture_read("nmcli_devices")) - Facter::Core::Execution.stubs(:exec).with("nmcli d list iface eth0").returns(my_fixture_read("nmcli_eth0_static")) - Facter::Core::Execution.stubs(:exec).with("nmcli d list iface wlan0").returns(my_fixture_read("nmcli_wlan0_dhcp")) + Facter::Core::Execution.stubs(:exec).with("nmcli -f all d list iface eth0").returns(my_fixture_read("nmcli_eth0_static")) + Facter::Core::Execution.stubs(:exec).with("nmcli -f all d list iface wlan0").returns(my_fixture_read("nmcli_wlan0_dhcp")) end it "should a dhcp_servers fact that includes values for each dhcp enables interface and NO 'system' value" do @@ -41,8 +41,8 @@ before :each do Facter::Core::Execution.stubs(:exec).with("route -n").returns(my_fixture_read("route_nogw")) Facter::Core::Execution.stubs(:exec).with("nmcli d").returns(my_fixture_read("nmcli_devices")) - Facter::Core::Execution.stubs(:exec).with("nmcli d list iface eth0").returns(my_fixture_read("nmcli_eth0_dhcp")) - Facter::Core::Execution.stubs(:exec).with("nmcli d list iface wlan0").returns(my_fixture_read("nmcli_wlan0_dhcp")) + Facter::Core::Execution.stubs(:exec).with("nmcli -f all d list iface eth0").returns(my_fixture_read("nmcli_eth0_dhcp")) + Facter::Core::Execution.stubs(:exec).with("nmcli -f all d list iface wlan0").returns(my_fixture_read("nmcli_wlan0_dhcp")) end it "should a dhcp_servers fact that includes values for each dhcp enables interface and NO 'system' value" do @@ -53,8 +53,8 @@ describe "with no DHCP enabled interfaces" do before :each do Facter::Core::Execution.stubs(:exec).with("nmcli d").returns(my_fixture_read("nmcli_devices")) - Facter::Core::Execution.stubs(:exec).with("nmcli d list iface eth0").returns(my_fixture_read("nmcli_eth0_static")) - Facter::Core::Execution.stubs(:exec).with("nmcli d list iface wlan0").returns(my_fixture_read("nmcli_wlan0_static")) + Facter::Core::Execution.stubs(:exec).with("nmcli -f all d list iface eth0").returns(my_fixture_read("nmcli_eth0_static")) + Facter::Core::Execution.stubs(:exec).with("nmcli -f all d list iface wlan0").returns(my_fixture_read("nmcli_wlan0_static")) end it "should not produce a dhcp_servers fact" do @@ -82,8 +82,8 @@ describe "with a main interface configured with DHCP" do before :each do Facter::Core::Execution.stubs(:exec).with("nmcli d").returns(my_fixture_read("nmcli_devices")) - Facter::Core::Execution.stubs(:exec).with("nmcli d show eth0").returns(my_fixture_read("nmcli_eth0_dhcp")) - Facter::Core::Execution.stubs(:exec).with("nmcli d show wlan0").returns(my_fixture_read("nmcli_wlan0_dhcp")) + Facter::Core::Execution.stubs(:exec).with("nmcli -f all d show eth0").returns(my_fixture_read("nmcli_eth0_dhcp")) + Facter::Core::Execution.stubs(:exec).with("nmcli -f all d show wlan0").returns(my_fixture_read("nmcli_wlan0_dhcp")) end it "should produce a dhcp_servers fact that includes values for 'system' as well as each dhcp enabled interface" do @@ -94,8 +94,8 @@ describe "with a main interface NOT configured with DHCP" do before :each do Facter::Core::Execution.stubs(:exec).with("nmcli d").returns(my_fixture_read("nmcli_devices")) - Facter::Core::Execution.stubs(:exec).with("nmcli d show eth0").returns(my_fixture_read("nmcli_eth0_static")) - Facter::Core::Execution.stubs(:exec).with("nmcli d show wlan0").returns(my_fixture_read("nmcli_wlan0_dhcp")) + Facter::Core::Execution.stubs(:exec).with("nmcli -f all d show eth0").returns(my_fixture_read("nmcli_eth0_static")) + Facter::Core::Execution.stubs(:exec).with("nmcli -f all d show wlan0").returns(my_fixture_read("nmcli_wlan0_dhcp")) end it "should a dhcp_servers fact that includes values for each dhcp enables interface and NO 'system' value" do @@ -107,8 +107,8 @@ before :each do Facter::Core::Execution.stubs(:exec).with("route -n").returns(my_fixture_read("route_nogw")) Facter::Core::Execution.stubs(:exec).with("nmcli d").returns(my_fixture_read("nmcli_devices")) - Facter::Core::Execution.stubs(:exec).with("nmcli d show eth0").returns(my_fixture_read("nmcli_eth0_dhcp")) - Facter::Core::Execution.stubs(:exec).with("nmcli d show wlan0").returns(my_fixture_read("nmcli_wlan0_dhcp")) + Facter::Core::Execution.stubs(:exec).with("nmcli -f all d show eth0").returns(my_fixture_read("nmcli_eth0_dhcp")) + Facter::Core::Execution.stubs(:exec).with("nmcli -f all d show wlan0").returns(my_fixture_read("nmcli_wlan0_dhcp")) end it "should a dhcp_servers fact that includes values for each dhcp enables interface and NO 'system' value" do @@ -119,8 +119,8 @@ describe "with no DHCP enabled interfaces" do before :each do Facter::Core::Execution.stubs(:exec).with("nmcli d").returns(my_fixture_read("nmcli_devices")) - Facter::Core::Execution.stubs(:exec).with("nmcli d show eth0").returns(my_fixture_read("nmcli_eth0_static")) - Facter::Core::Execution.stubs(:exec).with("nmcli d show wlan0").returns(my_fixture_read("nmcli_wlan0_static")) + Facter::Core::Execution.stubs(:exec).with("nmcli -f all d show eth0").returns(my_fixture_read("nmcli_eth0_static")) + Facter::Core::Execution.stubs(:exec).with("nmcli -f all d show wlan0").returns(my_fixture_read("nmcli_wlan0_static")) end it "should not produce a dhcp_servers fact" do From 9496aadf629092132ba80f11be98a6e106c062bc Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Wed, 28 May 2014 16:38:59 -0700 Subject: [PATCH 1902/3753] (maint) Set RPATH for non-default lib install path. If we're using an install prefix that is not targeting a directory normally searched by the loader, tell CMake to set an install RPATH to the directory containing libfacter.so. Also instructing CMake to add "@rpath/" to the install name of libfacter when building and installing to a non-default lib directory on OSX. This allows libfacter to be loaded from the RPATH for that platform. --- CMakeLists.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index ed713bc491..4e99a144c0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,6 +41,13 @@ if (NOT YAMLCPP_FOUND) message(FATAL_ERROR "yaml-cpp is required. Please install it before building.") endif() +# Set RPATH if not installing to a system library directory +list(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_PREFIX}/lib" INSTALL_IS_SYSTEM_DIR) +if ("${INSTALL_IS_SYSTEM_DIR}" STREQUAL "-1") + set(CMAKE_MACOSX_RPATH 1) + set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") +endif() + # Include vendor libraries include(${VENDOR_DIRECTORY}/rapidjson.cmake) include(${VENDOR_DIRECTORY}/gmock.cmake) From 34ef9d723cfe369e1e89de1443b50fe49cb57e64 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Tue, 27 May 2014 14:32:55 -0700 Subject: [PATCH 1903/3753] (maint) Add API documentation generation. Adding support for generating API documents using doxygen. Updating all source files so that there are no doxygen warnings. Adding documentation generation to Travis CI to ensure no future warnings. Ensuring facter library header files are free from implementation (save templated types). --- .travis.yml | 3 + README.md | 37 +- lib/.gitignore | 1 + lib/CMakeLists.txt | 8 + lib/Doxyfile | 2312 +++++++++++++++++ lib/docs/namespaces.dox | 85 + lib/inc/facter/execution/execution.hpp | 30 +- lib/inc/facter/facterlib.h | 5 + lib/inc/facter/facts/array_value.hpp | 35 +- .../facter/facts/bsd/networking_resolver.hpp | 4 + lib/inc/facter/facts/bsd/uptime_resolver.hpp | 4 + .../{posix => }/execution_resolver.hpp | 16 +- .../facter/facts/external/json_resolver.hpp | 4 + lib/inc/facter/facts/external/resolver.hpp | 6 +- .../facter/facts/external/text_resolver.hpp | 4 + .../facter/facts/external/yaml_resolver.hpp | 4 + lib/inc/facter/facts/fact.hpp | 210 +- lib/inc/facter/facts/fact_map.hpp | 12 +- lib/inc/facter/facts/fact_resolver.hpp | 56 +- .../facts/linux/block_device_resolver.hpp | 17 +- lib/inc/facter/facts/linux/dmi_resolver.hpp | 4 + lib/inc/facter/facts/linux/lsb_resolver.hpp | 20 +- .../facts/linux/networking_resolver.hpp | 4 + .../facts/linux/operating_system_resolver.hpp | 4 + .../facter/facts/linux/processor_resolver.hpp | 4 + lib/inc/facter/facts/linux/release_file.hpp | 76 + .../facter/facts/linux/selinux_resolver.hpp | 25 +- .../facter/facts/linux/uptime_resolver.hpp | 4 + lib/inc/facter/facts/map_value.hpp | 30 +- lib/inc/facter/facts/osx/dmi_resolver.hpp | 4 + .../facter/facts/osx/networking_resolver.hpp | 4 + .../facter/facts/osx/processor_resolver.hpp | 4 + lib/inc/facter/facts/posix/dmi_resolver.hpp | 24 +- .../facter/facts/posix/kernel_resolver.hpp | 17 +- .../facts/posix/networking_resolver.hpp | 38 +- .../facts/posix/operating_system_resolver.hpp | 19 +- lib/inc/facter/facts/posix/os.hpp | 124 + lib/inc/facter/facts/posix/os_family.hpp | 25 + .../facter/facts/posix/processor_resolver.hpp | 21 +- lib/inc/facter/facts/posix/ssh_resolver.hpp | 21 +- .../facter/facts/posix/uptime_resolver.hpp | 18 +- lib/inc/facter/facts/scalar_value.hpp | 42 +- lib/inc/facter/facts/value.hpp | 35 +- lib/inc/facter/logging/logging.hpp | 64 + lib/inc/facter/util/bsd/scoped_ifaddrs.hpp | 27 +- lib/inc/facter/util/file.hpp | 4 + lib/inc/facter/util/option_set.hpp | 4 + lib/inc/facter/util/posix/scoped_addrinfo.hpp | 36 +- lib/inc/facter/util/posix/scoped_bio.hpp | 21 +- .../facter/util/posix/scoped_descriptor.hpp | 16 +- lib/inc/facter/util/scoped_file.hpp | 21 +- lib/inc/facter/util/scoped_resource.hpp | 34 +- lib/inc/facter/util/string.hpp | 40 + lib/src/execution/posix/execution.cc | 38 + lib/src/facts/array_value.cc | 9 +- lib/src/facts/bsd/networking_resolver.cc | 1 + lib/src/facts/bsd/uptime_resolver.cc | 5 +- .../external/posix/execution_resolver.cc | 8 +- lib/src/facts/external/resolver.cc | 12 + lib/src/facts/fact_map.cc | 11 + lib/src/facts/fact_resolver.cc | 38 + lib/src/facts/linux/block_device_resolver.cc | 13 + lib/src/facts/linux/dmi_resolver.cc | 1 + lib/src/facts/linux/lsb_resolver.cc | 18 +- .../facts/linux/operating_system_resolver.cc | 3 +- lib/src/facts/linux/processor_resolver.cc | 1 + lib/src/facts/linux/selinux_resolver.cc | 29 +- lib/src/facts/linux/uptime_resolver.cc | 1 + lib/src/facts/map_value.cc | 4 - lib/src/facts/osx/dmi_resolver.cc | 1 + lib/src/facts/osx/networking_resolver.cc | 1 + lib/src/facts/osx/processor_resolver.cc | 1 + lib/src/facts/posix/dmi_resolver.cc | 27 + lib/src/facts/posix/kernel_resolver.cc | 13 + lib/src/facts/posix/networking_resolver.cc | 30 + .../facts/posix/operating_system_resolver.cc | 13 + lib/src/facts/posix/platform.cc | 3 +- lib/src/facts/posix/processor_resolver.cc | 16 + lib/src/facts/posix/ssh_resolver.cc | 17 + lib/src/facts/posix/uptime_resolver.cc | 15 +- lib/src/util/bsd/scoped_ifaddrs.cc | 28 + lib/src/util/posix/scoped_addrinfo.cc | 40 + lib/src/util/posix/scoped_bio.cc | 25 + lib/src/util/posix/scoped_descriptor.cc | 19 + lib/src/util/scoped_file.cc | 24 + .../external/posix/execution_resolver.cc | 3 +- lib/version.h.in | 16 + scripts/travis_target.sh | 10 + 88 files changed, 3785 insertions(+), 396 deletions(-) create mode 100644 lib/.gitignore create mode 100644 lib/Doxyfile create mode 100644 lib/docs/namespaces.dox rename lib/inc/facter/facts/external/{posix => }/execution_resolver.hpp (59%) create mode 100644 lib/src/facts/external/resolver.cc create mode 100644 lib/src/facts/posix/dmi_resolver.cc create mode 100644 lib/src/util/bsd/scoped_ifaddrs.cc create mode 100644 lib/src/util/posix/scoped_addrinfo.cc create mode 100644 lib/src/util/posix/scoped_bio.cc create mode 100644 lib/src/util/posix/scoped_descriptor.cc create mode 100644 lib/src/util/scoped_file.cc diff --git a/.travis.yml b/.travis.yml index a5f2712af3..4d5f8deb42 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,6 +17,9 @@ before_install: # grab a pre-built cmake 2.8.12 from s3 - wget https://s3.amazonaws.com/kylo_sandbox/cmake_install.tar.bz2 - tar xjvf cmake_install.tar.bz2 --strip 1 -C $HOME + # Install doxygen + - wget https://github.com/doxygen/doxygen/archive/Release_1_8_7.tar.gz -O $HOME/doxygen-1.8.7.tgz + - pushd $HOME && tar xzf doxygen-1.8.7.tgz && cd $HOME/doxygen-Release_1_8_7 && ./configure > /dev/null && make > /dev/null && sudo make install > /dev/null && popd # Install dependencies of cfacter - sudo apt-get -y install libboost-filesystem1.48-dev libboost-program-options1.48-dev liblog4cxx10-dev - wget https://re2.googlecode.com/files/re2-20140304.tgz -O $HOME/re2-20140304.tgz diff --git a/README.md b/README.md index b32e752bfd..219a8075b7 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ This would install cfacter into `~/bin`, `~/lib`, and `~/include`. To install the gem (assumes gem is already built): $ cd gem - $ gem install pkg/*.gem + $ gem install pkg/cfacter*.gem Uninstall --------- @@ -123,6 +123,17 @@ To uninstall the gem: $ gem uninstall cfacter +Documentation +------------- + +To generate API documentation, install doxygen 1.8.7 or later. + + $ cd lib + $ doxygen + +To view the documentation, open `lib/html/index.html` in a web browser. + + Using The C++11 API ------------------- @@ -130,22 +141,20 @@ This section assumes that cfacter has been installed into the system. Here's a simple example of using the C++11 API to output all facts. -```C++ -#include -#include + #include + #include -using namespace std; -using namespace facter::facts; + using namespace std; + using namespace facter::facts; -int main() { - fact_map facts; - facts.resolve(); - facts.resolve_external(); - cout << facts << endl; -} -``` + int main() { + fact_map facts; + facts.resolve(); + facts.resolve_external(); + cout << facts << endl; + } To build the above, link with libfacter: $ g++ example.cc -o myfacter --std=c++11 -lfacter - $ ./myfacter \ No newline at end of file + $ ./myfacter diff --git a/lib/.gitignore b/lib/.gitignore new file mode 100644 index 0000000000..5ccff1a6be --- /dev/null +++ b/lib/.gitignore @@ -0,0 +1 @@ +html/ diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 377ce71138..20bbd952ec 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -24,6 +24,7 @@ set(LIBFACTER_COMMON_SOURCES "${CMAKE_CURRENT_LIST_DIR}/src/facterlib.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/array_value.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/external/json_resolver.cc" + "${CMAKE_CURRENT_LIST_DIR}/src/facts/external/resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/external/text_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/external/yaml_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/fact.cc" @@ -33,6 +34,7 @@ set(LIBFACTER_COMMON_SOURCES "${CMAKE_CURRENT_LIST_DIR}/src/facts/scalar_value.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/value.cc" "${CMAKE_CURRENT_LIST_DIR}/src/util/file.cc" + "${CMAKE_CURRENT_LIST_DIR}/src/util/scoped_file.cc" "${CMAKE_CURRENT_LIST_DIR}/src/util/string.cc" ) @@ -41,6 +43,7 @@ if (UNIX) set(LIBFACTER_POSIX_SOURCES "${CMAKE_CURRENT_LIST_DIR}/src/execution/posix/execution.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/external/posix/execution_resolver.cc" + "${CMAKE_CURRENT_LIST_DIR}/src/facts/posix/dmi_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/posix/kernel_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/posix/networking_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/posix/operating_system_resolver.cc" @@ -49,6 +52,9 @@ if (UNIX) "${CMAKE_CURRENT_LIST_DIR}/src/facts/posix/ssh_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/posix/uptime_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/logging/posix/logging.cc" + "${CMAKE_CURRENT_LIST_DIR}/src/util/posix/scoped_addrinfo.cc" + "${CMAKE_CURRENT_LIST_DIR}/src/util/posix/scoped_bio.cc" + "${CMAKE_CURRENT_LIST_DIR}/src/util/posix/scoped_descriptor.cc" ) endif() @@ -61,6 +67,7 @@ if ("${CMAKE_SYSTEM_NAME}" MATCHES "Darwin") "${CMAKE_CURRENT_LIST_DIR}/src/facts/osx/networking_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/osx/platform.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/osx/processor_resolver.cc" + "${CMAKE_CURRENT_LIST_DIR}/src/util/bsd/scoped_ifaddrs.cc" ) elseif ("${CMAKE_SYSTEM_NAME}" MATCHES "Linux") set(LIBFACTER_PLATFORM_SOURCES @@ -74,6 +81,7 @@ elseif ("${CMAKE_SYSTEM_NAME}" MATCHES "Linux") "${CMAKE_CURRENT_LIST_DIR}/src/facts/linux/platform.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/linux/processor_resolver.cc" "${CMAKE_CURRENT_LIST_DIR}/src/facts/linux/selinux_resolver.cc" + "${CMAKE_CURRENT_LIST_DIR}/src/util/bsd/scoped_ifaddrs.cc" ) endif() diff --git a/lib/Doxyfile b/lib/Doxyfile new file mode 100644 index 0000000000..1ba92b222b --- /dev/null +++ b/lib/Doxyfile @@ -0,0 +1,2312 @@ +# Doxyfile 1.8.7 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all text +# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv +# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv +# for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = cfacter + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = 0.1.0 + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify an logo or icon that is included in +# the documentation. The maximum height of the logo should not exceed 55 pixels +# and the maximum width should not exceed 200 pixels. Doxygen will copy the logo +# to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = YES + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = YES + +# If the FULL_PATH_NAMES tag is set to YES doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = inc/ + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = YES + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce a +# new page for each member. If set to NO, the documentation of a member will be +# part of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines. + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding "class=itcl::class" +# will allow you to use the command class in the itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, Javascript, +# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: +# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: +# Fortran. In the later case the parser tries to guess whether the code is fixed +# or free formatted code, this is the default for Fortran type files), VHDL. For +# instance to make doxygen treat .inc files as Fortran files (default is PHP), +# and .f files as C (default is Fortran), use: inc=Fortran f=C. +# +# Note For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by by putting a % sign in front of the word +# or globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = YES + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = YES + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = YES + +# If the EXTRACT_STATIC tag is set to YES all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = YES + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO these classes will be included in the various overviews. This option has +# no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# (class|struct|union) declarations. If set to NO these declarations will be +# included in the documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file +# names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. +# The default value is: system dependent. + +CASE_SENSE_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = NO + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = YES + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = YES + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable ( YES) or disable ( NO) the +# todo list. This list is created by putting \todo commands in the +# documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable ( YES) or disable ( NO) the +# test list. This list is created by putting \test commands in the +# documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable ( YES) or disable ( NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable ( YES) or disable ( NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES the list +# will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. Do not use file names with spaces, bibtex cannot handle them. See +# also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error ( stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES, then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO doxygen will only warn about wrong or incomplete parameter +# documentation, but not about the absence of documentation. +# The default value is: NO. + +WARN_NO_PARAMDOC = YES + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). + +WARN_LOGFILE = html/warnings.txt + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. +# Note: If this tag is empty the current directory is searched. + +INPUT = ../README.md \ + inc \ + docs + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: http://www.gnu.org/software/libiconv) for the list of +# possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank the +# following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii, +# *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, +# *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, +# *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, +# *.qsf, *.as and *.js. + +FILE_PATTERNS = + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = rapidjson* + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER ) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = README.md + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# function all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES, then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see http://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the config file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in +# which the alphabetical index list will be split. +# Minimum value: 1, maximum value: 20, default value: 5. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify an additional user- +# defined cascading style sheet that is included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefor more robust against future updates. +# Doxygen will copy the style sheet file to the output directory. For an example +# see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the stylesheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# http://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to NO can help when comparing the output of multiple runs. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: http://developer.apple.com/tools/xcode/), introduced with +# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a +# Makefile in the HTML output directory. Running make will produce the docset in +# that directory and running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on +# Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler ( hhc.exe). If non-empty +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated ( +# YES) or that it should be included in the master .chm file ( NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index ( hhk), content ( hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated ( +# YES) or a normal table of contents ( NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- +# folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location of Qt's +# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the +# generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom stylesheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# http://www.mathjax.org) which uses client side Javascript for the rendering +# instead of using prerendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from http://www.mathjax.org before deployment. +# The default value is: http://cdn.mathjax.org/mathjax/latest. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /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

      5@XYt>8?%LXf82OvrU_Yh$Hz6r*^?853J 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